diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeception.xml b/.idea/codeception.xml new file mode 100644 index 0000000..fb995da --- /dev/null +++ b/.idea/codeception.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/.idea/commandlinetools/Symfony_9_11_24__9_41_PM.xml b/.idea/commandlinetools/Symfony_9_11_24__9_41_PM.xml new file mode 100644 index 0000000..653f546 --- /dev/null +++ b/.idea/commandlinetools/Symfony_9_11_24__9_41_PM.xml @@ -0,0 +1,1275 @@ + + + + + _complete +
Options:
--shell(-s)The shell type ("bash", "fish", "zsh")
--input(-i)An array of input tokens (e.g. COMP_WORDS or argv)
--current(-c)The index of the "input" array that the cursor is in (e.g. COMP_CWORD)
--api-version(-a)The API version of the completion script
--symfony(-S)deprecated
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + + + + + + +
+ + about + about command displays information about the current Symfony project.

The PHP section displays important configuration that could affect your application. The values might
be different between web and CLI.

Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + +
+ + completion + completion command dumps the shell completion script required
to use shell autocompletion (currently, bash, fish, zsh completion are supported).

Static installation
-------------------

Dump the script to a global completion file and restart your shell:

/home/skylar/Projects/mycomments-api/bin/console completion bash | sudo tee /etc/bash_completion.d/console

Or dump the script to a local file and source it:

/home/skylar/Projects/mycomments-api/bin/console completion bash > completion.sh

# source the file whenever you use the project
source completion.sh

# or add this line at the end of your "~/.bashrc" file:
source /path/to/completion.sh

Dynamic installation
--------------------

Add this to the end of your shell configuration file (e.g. "~/.bashrc"):

eval "$(/home/skylar/Projects/mycomments-api/bin/console completion bash)"

Options:
--debugTail the completion debug log
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ shell[=null] + + + command]]> + + + + + + + + + + + +
+ + help + help command displays help for a given command:

/home/skylar/Projects/mycomments-api/bin/console help list

You can also output the help in other formats by using the --format option:

/home/skylar/Projects/mycomments-api/bin/console help --format=xml list

To display the list of available commands, please use the list command.

Options:
--formatThe output format (txt, xml, json, or md)
--rawTo output raw command help
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]> + command_name[=null] + + + +
command]]> + + + + + + + + + + + + + + list + list command lists all commands:

/home/skylar/Projects/mycomments-api/bin/console list

You can also display the commands for a specific namespace:

/home/skylar/Projects/mycomments-api/bin/console list test

You can also output the information in other formats by using the --format option:

/home/skylar/Projects/mycomments-api/bin/console list --format=xml

It's also possible to get raw list of commands (useful for embedding command runner):

/home/skylar/Projects/mycomments-api/bin/console list --raw

Options:
--rawTo output raw command list
--formatThe output format (txt, xml, json, or md)
--shortTo skip describing commands' arguments
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ namespace[=null] + + + + + + + + + + + + + + + +
+ + assets:install + assets:install command installs bundle assets into a given
directory (e.g. the public directory).

php /home/skylar/Projects/mycomments-api/bin/console assets:install public

A "bundles" directory will be created inside the target directory and the
"Resources/public" directory of each bundle will be copied into it.

To create a symlink to each bundle instead of copying its assets, use the
--symlink option (will fall back to hard copies when symbolic links aren't possible:

php /home/skylar/Projects/mycomments-api/bin/console assets:install public --symlink

To make symlink relative, add the --relative option:

php /home/skylar/Projects/mycomments-api/bin/console assets:install public --symlink --relative


Options:
--symlinkSymlink the assets instead of copying them
--relativeMake relative symlinks
--no-cleanupDo not remove the assets of the bundles that no longer exist
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ target[=null] + + + + + + + + + + + + + + + +
+ + cache:clear + cache:clear command clears and warms up the application cache for a given environment
and debug mode:

php /home/skylar/Projects/mycomments-api/bin/console cache:clear --env=dev
php /home/skylar/Projects/mycomments-api/bin/console cache:clear --env=prod --no-debug

Options:
--no-warmupDo not warm up the cache
--no-optional-warmersSkip optional cache warmers (faster)
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + + + +
+ + cache:pool:clear + cache:pool:clear command clears the given cache pools or cache pool clearers.

/home/skylar/Projects/mycomments-api/bin/console cache:pool:clear [...]

Options:
--allClear all cache pools
--excludeA list of cache pools or cache pool clearers to exclude
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ pools[=null] + + + + + + + + + + + + + + +
+ + cache:pool:delete + cache:pool:delete deletes an item from a given cache pool.

/home/skylar/Projects/mycomments-api/bin/console cache:pool:delete

Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ pool key + + + + + + + + + + + + +
+ + cache:pool:invalidate-tags + cache:pool:invalidate-tags command invalidates tags from taggable pools. By default, all pools
have the passed tags invalidated. Pass --pool=my_pool to invalidate tags on a specific pool.

php /home/skylar/Projects/mycomments-api/bin/console cache:pool:invalidate-tags tag1 tag2
php /home/skylar/Projects/mycomments-api/bin/console cache:pool:invalidate-tags tag1 tag2 --pool=cache2 --pool=cache1

Options:
--pool(-p)The pools to invalidate on
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ tags + + + + + + + + + + + + + +
+ + cache:pool:list + cache:pool:list command lists all available cache pools.

Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + +
+ + cache:pool:prune + cache:pool:prune command deletes all expired items from all pruneable pools.

/home/skylar/Projects/mycomments-api/bin/console cache:pool:prune

Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + +
+ + cache:warmup + cache:warmup command warms up the cache.

Before running this command, the cache must be empty.


Options:
--no-optional-warmersSkip optional cache warmers (faster)
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + + +
+ + config:dump-reference + config:dump-reference command dumps the default configuration for an
extension/bundle.

Either the extension alias or bundle name can be used:

php /home/skylar/Projects/mycomments-api/bin/console config:dump-reference framework
php /home/skylar/Projects/mycomments-api/bin/console config:dump-reference FrameworkBundle

The --format option specifies the format of the configuration,
these are "yaml", "xml".

php /home/skylar/Projects/mycomments-api/bin/console config:dump-reference FrameworkBundle --format=xml

For dumping a specific option, add its path as second argument (only available for the yaml format):

php /home/skylar/Projects/mycomments-api/bin/console config:dump-reference framework http_client.default_options


Options:
--formatThe output format ("yaml", "xml")
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ name[=null] path[=null] + + + + + + + + + + + + + +
+ + debug:autowiring + debug:autowiring command displays the classes and interfaces that
you can use as type-hints for autowiring:

php /home/skylar/Projects/mycomments-api/bin/console debug:autowiring

You can also pass a search term to filter the list:

php /home/skylar/Projects/mycomments-api/bin/console debug:autowiring log


Options:
--allShow also services that are not aliased
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ search[=null] + + + + + + + + + + + + + +
+ + debug:config + debug:config command dumps the current configuration for an
extension/bundle.

Either the extension alias or bundle name can be used:

php /home/skylar/Projects/mycomments-api/bin/console debug:config framework
php /home/skylar/Projects/mycomments-api/bin/console debug:config FrameworkBundle

The --format option specifies the format of the configuration,
these are "txt", "yaml", "json".

php /home/skylar/Projects/mycomments-api/bin/console debug:config framework --format=json

For dumping a specific option, add its path as second argument:

php /home/skylar/Projects/mycomments-api/bin/console debug:config framework serializer.enabled


Options:
--resolve-envDisplay resolved environment variable values instead of placeholders
--formatThe output format ("txt", "yaml", "json")
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ name[=null] path[=null] + + + + + + + + + + + + + + +
+ + debug:container + debug:container command displays all configured public services:

php /home/skylar/Projects/mycomments-api/bin/console debug:container

To see deprecations generated during container compilation and cache warmup, use the --deprecations option:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --deprecations

To get specific information about a service, specify its name:

php /home/skylar/Projects/mycomments-api/bin/console debug:container validator

To get specific information about a service including all its arguments, use the --show-arguments flag:

php /home/skylar/Projects/mycomments-api/bin/console debug:container validator --show-arguments

To see available types that can be used for autowiring, use the --types flag:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --types

To see environment variables used by the container, use the --env-vars flag:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --env-vars

Display a specific environment variable by specifying its name with the --env-var option:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --env-var=APP_ENV

Use the --tags option to display tagged public services grouped by tag:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --tags

Find all services with a specific tag by specifying the tag name with the --tag option:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --tag=form.type

Use the --parameters option to display all parameters:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --parameters

Display a specific parameter by specifying its name with the --parameter option:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --parameter=kernel.debug

By default, internal services are hidden. You can display them
using the --show-hidden flag:

php /home/skylar/Projects/mycomments-api/bin/console debug:container --show-hidden


Options:
--show-argumentsShow arguments in services
--show-hiddenShow hidden (internal) services
--tagShow all services with a specific tag
--tagsDisplay tagged services for an application
--parameterDisplay a specific parameter for an application
--parametersDisplay parameters for an application
--typesDisplay types (classes/interfaces) available in the container
--env-varDisplay a specific environment variable used in the container
--env-varsDisplay environment variables used in the container
--formatThe output format ("txt", "xml", "json", "md")
--rawTo output raw description
--deprecationsDisplay deprecations generated when compiling and warming up the container
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ name[=null] + + + + + + + + + + + + + + + + + + + + + + + + +
+ + debug:dotenv + /home/skylar/Projects/mycomments-api/bin/console debug:dotenv command displays all the environment variables configured by dotenv:

php /home/skylar/Projects/mycomments-api/bin/console debug:dotenv

To get specific variables, specify its full or partial name:

php /home/skylar/Projects/mycomments-api/bin/console debug:dotenv FOO_BAR


Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ filter[=null] + + + + + + + + + + + + +
+ + debug:event-dispatcher + debug:event-dispatcher command displays all configured listeners:

php /home/skylar/Projects/mycomments-api/bin/console debug:event-dispatcher

To get specific listeners for an event, specify its name:

php /home/skylar/Projects/mycomments-api/bin/console debug:event-dispatcher kernel.request

Options:
--dispatcherTo view events of a specific event dispatcher
--formatThe output format ("txt", "xml", "json", "md")
--rawTo output raw description
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ event[=null] + + + + + + + + + + + + + + + +
+ + debug:router + debug:router displays the configured routes:

php /home/skylar/Projects/mycomments-api/bin/console debug:router


Options:
--show-controllersShow assigned controllers in overview
--show-aliasesShow aliases in overview
--formatThe output format ("txt", "xml", "json", "md")
--rawTo output raw route(s)
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ name[=null] + + + + + + + + + + + + + + + + +
+ + lint:container +
Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + +
+ + lint:yaml + lint:yaml command lints a YAML file and outputs to STDOUT
the first encountered syntax error.

You can validates YAML contents passed from STDIN:

cat filename | php /home/skylar/Projects/mycomments-api/bin/console lint:yaml -

You can also validate the syntax of a file:

php /home/skylar/Projects/mycomments-api/bin/console lint:yaml filename

Or of a whole directory:

php /home/skylar/Projects/mycomments-api/bin/console lint:yaml dirname
php /home/skylar/Projects/mycomments-api/bin/console lint:yaml dirname --format=json

You can also exclude one or more specific files:

php /home/skylar/Projects/mycomments-api/bin/console lint:yaml dirname --exclude="dirname/foo.yaml" --exclude="dirname/bar.yaml"

Or find all files in a bundle:

php /home/skylar/Projects/mycomments-api/bin/console lint:yaml @AcmeDemoBundle


Options:
--formatThe output format ("txt", "json", "github")
--excludePath(s) to exclude
--parse-tagsParse custom tags
--no-parse-tagsNegate the "--parse-tags" option
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ filename[=null] + + + + + + + + + + + + + + + + +
+ + router:match + router:match shows which routes match a given request and which don't and for what reason:

php /home/skylar/Projects/mycomments-api/bin/console router:match /foo

or

php /home/skylar/Projects/mycomments-api/bin/console router:match /foo --method POST --scheme https --host symfony.com --verbose


Options:
--methodSet the HTTP method
--schemeSet the URI scheme (usually http or https)
--hostSet the URI host
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ path_info + + + + + + + + + + + + + + + +
+ + secrets:decrypt-to-local + secrets:decrypt-to-local command decrypts all secrets and copies them in the local vault.

/home/skylar/Projects/mycomments-api/bin/console secrets:decrypt-to-local

When the --force option is provided, secrets that already exist in the local vault are overridden.

/home/skylar/Projects/mycomments-api/bin/console secrets:decrypt-to-local --force

Options:
--force(-f)Force overriding of secrets that already exist in the local vault
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + + +
+ + secrets:encrypt-from-local + secrets:encrypt-from-local command encrypts all locally overridden secrets to the vault.

/home/skylar/Projects/mycomments-api/bin/console secrets:encrypt-from-local

Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + +
+ + secrets:generate-keys + secrets:generate-keys command generates a new encryption key.

/home/skylar/Projects/mycomments-api/bin/console secrets:generate-keys

If encryption keys already exist, the command must be called with
the --rotate option in order to override those keys and re-encrypt
existing secrets.

/home/skylar/Projects/mycomments-api/bin/console secrets:generate-keys --rotate

Options:
--local(-l)Update the local vault.
--rotate(-r)Re-encrypt existing secrets with the newly generated keys.
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + + + +
+ + secrets:list + secrets:list command list all stored secrets.

/home/skylar/Projects/mycomments-api/bin/console secrets:list

When the option --reveal is provided, the decrypted secrets are also displayed.

/home/skylar/Projects/mycomments-api/bin/console secrets:list --reveal

Options:
--reveal(-r)Display decrypted values alongside names
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ + + + + + + + + + + + + +
+ + secrets:remove + secrets:remove command removes a secret from the vault.

/home/skylar/Projects/mycomments-api/bin/console secrets:remove

Options:
--local(-l)Update the local vault.
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ name + + + + + + + + + + + + + +
+ + secrets:reveal + secrets:reveal command reveals a stored secret.

/home/skylar/Projects/mycomments-api/bin/console secrets:reveal

Options:
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ name + + + + + + + + + + + + +
+ + secrets:set + secrets:set command stores a secret in the vault.

/home/skylar/Projects/mycomments-api/bin/console secrets:set

To reference secrets in services.yaml or any other config
files, use "%env()%".

By default, the secret value should be entered interactively.
Alternatively, provide a file where to read the secret from:

php /home/skylar/Projects/mycomments-api/bin/console secrets:set filename

Use "-" as a file name to read from STDIN:

cat filename | php /home/skylar/Projects/mycomments-api/bin/console secrets:set -

Use --local to override secrets for local needs.

Options:
--local(-l)Update the local vault.
--random(-r)Generate a random value.
--help(-h)Display help for the given command. When no command is given display help for the list command
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce (or disable --no-ansi) ANSI output
--no-ansiNegate the "--ansi" option
--no-interaction(-n)Do not ask any interactive question
--env(-e)The Environment name.
--no-debugSwitch off debug mode.
--profileEnables profiling (requires debug).

]]>
+ name file[=null] + + + + + + + + + + + + + + +
+
+ diff --git a/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd b/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd new file mode 100644 index 0000000..f2efc6d --- /dev/null +++ b/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..61954aa --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/mycomments-api.iml b/.idea/mycomments-api.iml new file mode 100644 index 0000000..fbbad53 --- /dev/null +++ b/.idea/mycomments-api.iml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..b1ebf1d --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/phpspec.xml b/.idea/phpspec.xml new file mode 100644 index 0000000..dac988c --- /dev/null +++ b/.idea/phpspec.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml new file mode 100644 index 0000000..4f8104c --- /dev/null +++ b/.idea/phpunit.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..1c7a022 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/config/packages/hwi_oauth.yaml b/config/packages/hwi_oauth.yaml new file mode 100644 index 0000000..b451310 --- /dev/null +++ b/config/packages/hwi_oauth.yaml @@ -0,0 +1,8 @@ +hwi_oauth: + # https://github.com/hwi/HWIOAuthBundle/blob/master/docs/2-configuring_resource_owners.md + resource_owners: + facebook: + type: facebook + client_id: '%env(FB_ID)%' + client_secret: '%env(FB_SECRET)%' + scope: "email public_profile" diff --git a/config/packages/lexik_jwt_authentication.yaml b/config/packages/lexik_jwt_authentication.yaml new file mode 100644 index 0000000..edfb69d --- /dev/null +++ b/config/packages/lexik_jwt_authentication.yaml @@ -0,0 +1,4 @@ +lexik_jwt_authentication: + secret_key: '%env(resolve:JWT_SECRET_KEY)%' + public_key: '%env(resolve:JWT_PUBLIC_KEY)%' + pass_phrase: '%env(JWT_PASSPHRASE)%' diff --git a/config/packages/security.yaml b/config/packages/security.yaml new file mode 100644 index 0000000..367af25 --- /dev/null +++ b/config/packages/security.yaml @@ -0,0 +1,39 @@ +security: + # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords + password_hashers: + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider + providers: + users_in_memory: { memory: null } + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true + provider: users_in_memory + + # activate different ways to authenticate + # https://symfony.com/doc/current/security.html#the-firewall + + # https://symfony.com/doc/current/security/impersonating_user.html + # switch_user: true + + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } + +when@test: + security: + password_hashers: + # By default, password hashers are resource intensive and take time. This is + # important to generate secure password hashes. In tests however, secure hashes + # are not important, waste resources and increase test times. The following + # reduces the work factor to the lowest possible values. + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: + algorithm: auto + cost: 4 # Lowest possible value for bcrypt + time_cost: 3 # Lowest possible value for argon + memory_cost: 10 # Lowest possible value for argon diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml new file mode 100644 index 0000000..3f795d9 --- /dev/null +++ b/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/config/routes/hwi_oauth_routing.yaml b/config/routes/hwi_oauth_routing.yaml new file mode 100644 index 0000000..190b994 --- /dev/null +++ b/config/routes/hwi_oauth_routing.yaml @@ -0,0 +1,11 @@ +hwi_oauth_redirect: + resource: "@HWIOAuthBundle/Resources/config/routing/redirect.php" + prefix: /connect + +hwi_oauth_connect: + resource: "@HWIOAuthBundle/Resources/config/routing/connect.php" + prefix: /connect + +hwi_oauth_login: + resource: "@HWIOAuthBundle/Resources/config/routing/login.php" + prefix: /login diff --git a/config/routes/security.yaml b/config/routes/security.yaml new file mode 100644 index 0000000..f853be1 --- /dev/null +++ b/config/routes/security.yaml @@ -0,0 +1,3 @@ +_security_logout: + resource: security.route_loader.logout + type: service diff --git a/src/Controller/CommentController.php b/src/Controller/CommentController.php new file mode 100644 index 0000000..9181f4d --- /dev/null +++ b/src/Controller/CommentController.php @@ -0,0 +1,34 @@ +entityManager->getRepository(Comment::class)->findAll(); + $response = ['result' => 'ok']; + foreach($comments as $comment) + { + $response['comments'][] = $serializer->normalize($comment); + } + + return new JsonResponse($response); + } +} diff --git a/src/Repository/CommentRepository.php b/src/Repository/CommentRepository.php new file mode 100644 index 0000000..dc8bcc6 --- /dev/null +++ b/src/Repository/CommentRepository.php @@ -0,0 +1,43 @@ + + */ +class CommentRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Comment::class); + } + +// /** +// * @return Comment[] Returns an array of Comment objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('c') +// ->andWhere('c.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('c.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Comment +// { +// return $this->createQueryBuilder('c') +// ->andWhere('c.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..92027e3 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,43 @@ + + */ +class UserRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + +// /** +// * @return User[] Returns an array of User objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('u.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?User +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/templates/base.html.twig b/templates/base.html.twig new file mode 100644 index 0000000..1069c14 --- /dev/null +++ b/templates/base.html.twig @@ -0,0 +1,16 @@ + + + + + {% block title %}Welcome!{% endblock %} + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + diff --git a/var/cache/dev/App_KernelDevDebugContainer.php b/var/cache/dev/App_KernelDevDebugContainer.php new file mode 100644 index 0000000..7b4eb6d --- /dev/null +++ b/var/cache/dev/App_KernelDevDebugContainer.php @@ -0,0 +1,22 @@ + '6Szx89D', + 'container.build_id' => '447774a9', + 'container.build_time' => 1726114796, + 'container.runtime_mode' => \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? 'web=0' : 'web=1', +], __DIR__.\DIRECTORY_SEPARATOR.'Container6Szx89D'); diff --git a/var/cache/dev/App_KernelDevDebugContainer.php.lock b/var/cache/dev/App_KernelDevDebugContainer.php.lock new file mode 100644 index 0000000..e69de29 diff --git a/var/cache/dev/App_KernelDevDebugContainer.php.meta b/var/cache/dev/App_KernelDevDebugContainer.php.meta new file mode 100644 index 0000000..d049e8c Binary files /dev/null and b/var/cache/dev/App_KernelDevDebugContainer.php.meta differ diff --git a/var/cache/dev/App_KernelDevDebugContainer.preload.php b/var/cache/dev/App_KernelDevDebugContainer.preload.php new file mode 100644 index 0000000..9ecbf72 --- /dev/null +++ b/var/cache/dev/App_KernelDevDebugContainer.preload.php @@ -0,0 +1,386 @@ += 7.4 when preloading is desired + +use Symfony\Component\DependencyInjection\Dumper\Preloader; + +if (in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + return; +} + +require dirname(__DIR__, 3).'/vendor/autoload.php'; +(require __DIR__.'/App_KernelDevDebugContainer.php')->set(\Container6Szx89D\App_KernelDevDebugContainer::class, null); +require __DIR__.'/Container6Szx89D/EntityManagerGhost614a58f.php'; +require __DIR__.'/Container6Szx89D/RequestPayloadValueResolverGhost01ca9cc.php'; +require __DIR__.'/Container6Szx89D/getTwig_Runtime_SecurityCsrfService.php'; +require __DIR__.'/Container6Szx89D/getTwig_Runtime_HttpkernelService.php'; +require __DIR__.'/Container6Szx89D/getTwig_Mailer_MessageListenerService.php'; +require __DIR__.'/Container6Szx89D/getTwig_Form_RendererService.php'; +require __DIR__.'/Container6Szx89D/getTwig_Form_EngineService.php'; +require __DIR__.'/Container6Szx89D/getTwigService.php'; +require __DIR__.'/Container6Szx89D/getSession_Handler_NativeService.php'; +require __DIR__.'/Container6Szx89D/getSession_FactoryService.php'; +require __DIR__.'/Container6Szx89D/getServicesResetterService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_RouteLoader_LogoutService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_PasswordHasherFactoryService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Logout_Listener_CsrfTokenClearingService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Listener_UserProviderService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Listener_UserChecker_MainService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Listener_Session_MainService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Listener_PasswordMigratingService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Listener_Main_UserProviderService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Listener_CsrfProtectionService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Listener_CheckAuthenticatorCredentialsService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_HttpUtilsService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Firewall_Map_Context_MainService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_Firewall_Map_Context_DevService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_ChannelListenerService.php'; +require __DIR__.'/Container6Szx89D/getSecurity_AccessListenerService.php'; +require __DIR__.'/Container6Szx89D/getSecrets_VaultService.php'; +require __DIR__.'/Container6Szx89D/getSecrets_EnvVarLoaderService.php'; +require __DIR__.'/Container6Szx89D/getRouting_LoaderService.php'; +require __DIR__.'/Container6Szx89D/getPropertyAccessorService.php'; +require __DIR__.'/Container6Szx89D/getLexikJwtAuthentication_KeyLoaderService.php'; +require __DIR__.'/Container6Szx89D/getLexikJwtAuthentication_JwtManagerService.php'; +require __DIR__.'/Container6Szx89D/getLexikJwtAuthentication_EncoderService.php'; +require __DIR__.'/Container6Szx89D/getHwiOauth_UserCheckerService.php'; +require __DIR__.'/Container6Szx89D/getHwiOauth_Twig_Extension_Oauth_RuntimeService.php'; +require __DIR__.'/Container6Szx89D/getHwiOauth_Security_OauthUtilsService.php'; +require __DIR__.'/Container6Szx89D/getFragment_Renderer_InlineService.php'; +require __DIR__.'/Container6Szx89D/getForm_TypeGuesser_DoctrineService.php'; +require __DIR__.'/Container6Szx89D/getForm_TypeExtension_Password_PasswordHasherService.php'; +require __DIR__.'/Container6Szx89D/getForm_TypeExtension_Form_PasswordHasherService.php'; +require __DIR__.'/Container6Szx89D/getForm_TypeExtension_Form_HttpFoundationService.php'; +require __DIR__.'/Container6Szx89D/getForm_TypeExtension_CsrfService.php'; +require __DIR__.'/Container6Szx89D/getForm_Type_FormService.php'; +require __DIR__.'/Container6Szx89D/getForm_Type_FileService.php'; +require __DIR__.'/Container6Szx89D/getForm_Type_EntityService.php'; +require __DIR__.'/Container6Szx89D/getForm_Type_ColorService.php'; +require __DIR__.'/Container6Szx89D/getForm_Type_ChoiceService.php'; +require __DIR__.'/Container6Szx89D/getForm_ServerParamsService.php'; +require __DIR__.'/Container6Szx89D/getForm_RegistryService.php'; +require __DIR__.'/Container6Szx89D/getForm_Listener_PasswordHasherService.php'; +require __DIR__.'/Container6Szx89D/getForm_FactoryService.php'; +require __DIR__.'/Container6Szx89D/getForm_ChoiceListFactory_CachedService.php'; +require __DIR__.'/Container6Szx89D/getErrorControllerService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_UuidGeneratorService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_UlidGeneratorService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Orm_Listeners_PdoSessionHandlerSchemaListenerService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Orm_Listeners_LockStoreSchemaListenerService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineTokenProviderSchemaListenerService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineDbalCacheAdapterSchemaListenerService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Orm_DefaultListeners_AttachEntityListenersService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Orm_DefaultEntityManagerService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Dbal_DefaultConnection_EventManagerService.php'; +require __DIR__.'/Container6Szx89D/getDoctrine_Dbal_DefaultConnectionService.php'; +require __DIR__.'/Container6Szx89D/getDoctrineService.php'; +require __DIR__.'/Container6Szx89D/getDebug_Security_Voter_VoteListenerService.php'; +require __DIR__.'/Container6Szx89D/getDebug_Security_Firewall_Authenticator_MainService.php'; +require __DIR__.'/Container6Szx89D/getDebug_ErrorHandlerConfiguratorService.php'; +require __DIR__.'/Container6Szx89D/getController_TemplateAttributeListenerService.php'; +require __DIR__.'/Container6Szx89D/getContainer_GetRoutingConditionServiceService.php'; +require __DIR__.'/Container6Szx89D/getContainer_EnvVarProcessorsLocatorService.php'; +require __DIR__.'/Container6Szx89D/getContainer_EnvVarProcessorService.php'; +require __DIR__.'/Container6Szx89D/getCache_SystemClearerService.php'; +require __DIR__.'/Container6Szx89D/getCache_SystemService.php'; +require __DIR__.'/Container6Szx89D/getCache_SecurityIsGrantedAttributeExpressionLanguageService.php'; +require __DIR__.'/Container6Szx89D/getCache_SecurityIsCsrfTokenValidAttributeExpressionLanguageService.php'; +require __DIR__.'/Container6Szx89D/getCache_GlobalClearerService.php'; +require __DIR__.'/Container6Szx89D/getCache_AppClearerService.php'; +require __DIR__.'/Container6Szx89D/getCache_AppService.php'; +require __DIR__.'/Container6Szx89D/getTemplateControllerService.php'; +require __DIR__.'/Container6Szx89D/getRedirectControllerService.php'; +require __DIR__.'/Container6Szx89D/getRedirectToServiceControllerService.php'; +require __DIR__.'/Container6Szx89D/getLoginControllerService.php'; +require __DIR__.'/Container6Szx89D/getRegisterControllerService.php'; +require __DIR__.'/Container6Szx89D/getConnectControllerService.php'; +require __DIR__.'/Container6Szx89D/getUserRepositoryService.php'; +require __DIR__.'/Container6Szx89D/getIdRepositoryService.php'; +require __DIR__.'/Container6Szx89D/getCommentRepositoryService.php'; +require __DIR__.'/Container6Szx89D/get_ServiceLocator_DyzWi7xService.php'; +require __DIR__.'/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelregisterContainerConfigurationService.php'; +require __DIR__.'/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelloadRoutesService.php'; +require __DIR__.'/Container6Szx89D/get_ServiceLocator_4wc4Ag1Service.php'; +require __DIR__.'/Container6Szx89D/get_Security_RequestMatcher_GOpgIHxService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_Security_UserValueResolverService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_Security_SecurityTokenValueResolverService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_Doctrine_Orm_EntityValueResolverService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_VariadicService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_SessionService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_ServiceService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestPayloadService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestAttributeService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_QueryParameterValueResolverService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_NotTaggedControllerService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DefaultService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DatetimeService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_BackedEnumResolverService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_Security_Voter_Security_Access_SimpleRoleVoterService.php'; +require __DIR__.'/Container6Szx89D/get_Debug_Security_Voter_Security_Access_AuthenticatedVoterService.php'; + +$classes = []; +$classes[] = 'Symfony\Bundle\FrameworkBundle\FrameworkBundle'; +$classes[] = 'Symfony\Bundle\MakerBundle\MakerBundle'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\DoctrineBundle'; +$classes[] = 'Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle'; +$classes[] = 'Symfony\Bundle\SecurityBundle\SecurityBundle'; +$classes[] = 'Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle'; +$classes[] = 'Symfony\Bundle\TwigBundle\TwigBundle'; +$classes[] = 'HWI\Bundle\OAuthBundle\HWIOAuthBundle'; +$classes[] = 'Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter'; +$classes[] = 'Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter'; +$classes[] = 'Symfony\Component\Security\Core\Authorization\Voter\RoleVoter'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver'; +$classes[] = 'Symfony\Component\Clock\Clock'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\NotTaggedControllerValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver'; +$classes[] = 'Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver'; +$classes[] = 'Symfony\Bridge\Doctrine\Attribute\MapEntity'; +$classes[] = 'Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver'; +$classes[] = 'Symfony\Component\Security\Http\Controller\UserValueResolver'; +$classes[] = 'Symfony\Component\HttpFoundation\ChainRequestMatcher'; +$classes[] = 'Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher'; +$classes[] = 'Symfony\Component\DependencyInjection\ServiceLocator'; +$classes[] = 'App\Repository\CommentRepository'; +$classes[] = 'App\Repository\IdRepository'; +$classes[] = 'App\Repository\UserRepository'; +$classes[] = 'HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController'; +$classes[] = 'HWI\Bundle\OAuthBundle\Controller\Connect\RegisterController'; +$classes[] = 'HWI\Bundle\OAuthBundle\Controller\LoginController'; +$classes[] = 'Symfony\Component\Security\Http\Authentication\AuthenticationUtils'; +$classes[] = 'HWI\Bundle\OAuthBundle\Controller\RedirectToServiceController'; +$classes[] = 'HWI\Bundle\OAuthBundle\Util\DomainWhitelist'; +$classes[] = 'Symfony\Bundle\FrameworkBundle\Controller\RedirectController'; +$classes[] = 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController'; +$classes[] = 'Symfony\Component\Cache\Adapter\FilesystemAdapter'; +$classes[] = 'Symfony\Component\Cache\Marshaller\DefaultMarshaller'; +$classes[] = 'Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer'; +$classes[] = 'Symfony\Component\Cache\Adapter\ArrayAdapter'; +$classes[] = 'Symfony\Component\Cache\Adapter\AdapterInterface'; +$classes[] = 'Symfony\Component\Cache\Adapter\AbstractAdapter'; +$classes[] = 'Symfony\Component\Config\Resource\SelfCheckingResourceChecker'; +$classes[] = 'Symfony\Component\DependencyInjection\EnvVarProcessor'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\CacheAttributeListener'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener'; +$classes[] = 'Symfony\Bridge\Twig\EventListener\TemplateAttributeListener'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\DebugHandlersListener'; +$classes[] = 'Symfony\Component\HttpKernel\Debug\ErrorHandlerConfigurator'; +$classes[] = 'Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter'; +$classes[] = 'Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager'; +$classes[] = 'Symfony\Component\Security\Core\Authorization\AccessDecisionManager'; +$classes[] = 'Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy'; +$classes[] = 'Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher'; +$classes[] = 'Symfony\Component\EventDispatcher\EventDispatcher'; +$classes[] = 'Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener'; +$classes[] = 'Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener'; +$classes[] = 'Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener'; +$classes[] = 'Symfony\Component\Security\Http\Authentication\AuthenticatorManager'; +$classes[] = 'Symfony\Bundle\SecurityBundle\EventListener\VoteListener'; +$classes[] = 'Symfony\Component\Stopwatch\Stopwatch'; +$classes[] = 'Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Registry'; +$classes[] = 'Doctrine\DBAL\Connection'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\ConnectionFactory'; +$classes[] = 'Doctrine\DBAL\Configuration'; +$classes[] = 'Doctrine\DBAL\Schema\LegacySchemaManagerFactory'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Middleware\DebugMiddleware'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Middleware\IdleConnectionMiddleware'; +$classes[] = 'Doctrine\DBAL\Tools\DsnParser'; +$classes[] = 'Symfony\Bridge\Doctrine\ContainerAwareEventManager'; +$classes[] = 'Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Middleware\BacktraceDebugDataHolder'; +$classes[] = 'Doctrine\ORM\Mapping\Driver\AttributeDriver'; +$classes[] = 'Doctrine\ORM\Proxy\Autoloader'; +$classes[] = 'Doctrine\ORM\EntityManager'; +$classes[] = 'Doctrine\ORM\Configuration'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver'; +$classes[] = 'Doctrine\Persistence\Mapping\Driver\MappingDriverChain'; +$classes[] = 'Doctrine\ORM\Mapping\UnderscoreNamingStrategy'; +$classes[] = 'Doctrine\ORM\Mapping\DefaultQuoteStrategy'; +$classes[] = 'Doctrine\ORM\Mapping\DefaultTypedFieldMapper'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Repository\ContainerRepositoryFactory'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\ManagerConfigurator'; +$classes[] = 'Doctrine\ORM\Tools\AttachEntityListenersListener'; +$classes[] = 'Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaListener'; +$classes[] = 'Symfony\Bridge\Doctrine\SchemaListener\RememberMeTokenProviderDoctrineSchemaListener'; +$classes[] = 'Symfony\Bridge\Doctrine\SchemaListener\LockStoreSchemaListener'; +$classes[] = 'Symfony\Bridge\Doctrine\SchemaListener\PdoSessionHandlerSchemaListener'; +$classes[] = 'Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator'; +$classes[] = 'Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ErrorController'; +$classes[] = 'Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer'; +$classes[] = 'Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer'; +$classes[] = 'Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\ErrorListener'; +$classes[] = 'Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator'; +$classes[] = 'Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator'; +$classes[] = 'Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory'; +$classes[] = 'Symfony\Component\Form\FormFactory'; +$classes[] = 'Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener'; +$classes[] = 'Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher'; +$classes[] = 'Symfony\Component\Form\FormRegistry'; +$classes[] = 'Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'; +$classes[] = 'Symfony\Component\Form\ResolvedFormTypeFactory'; +$classes[] = 'Symfony\Component\Form\Util\ServerParams'; +$classes[] = 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'; +$classes[] = 'Symfony\Component\Form\Extension\Core\Type\ColorType'; +$classes[] = 'Symfony\Bridge\Doctrine\Form\Type\EntityType'; +$classes[] = 'Symfony\Component\Form\Extension\Core\Type\FileType'; +$classes[] = 'Symfony\Component\Form\Extension\Core\Type\FormType'; +$classes[] = 'Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension'; +$classes[] = 'Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension'; +$classes[] = 'Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler'; +$classes[] = 'Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension'; +$classes[] = 'Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension'; +$classes[] = 'Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension'; +$classes[] = 'Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension'; +$classes[] = 'Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension'; +$classes[] = 'Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser'; +$classes[] = 'Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer'; +$classes[] = 'Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner'; +$classes[] = 'Symfony\Component\Runtime\Runner\Symfony\ResponseRunner'; +$classes[] = 'Symfony\Component\Runtime\SymfonyRuntime'; +$classes[] = 'Symfony\Component\HttpKernel\HttpKernel'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\TraceableControllerResolver'; +$classes[] = 'Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver'; +$classes[] = 'Symfony\Component\HttpKernel\Controller\ArgumentResolver'; +$classes[] = 'Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory'; +$classes[] = 'HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator'; +$classes[] = 'HWI\Bundle\OAuthBundle\Security\OAuthUtils'; +$classes[] = 'HWI\Bundle\OAuthBundle\Twig\Extension\OAuthRuntime'; +$classes[] = 'Symfony\Component\Security\Core\User\InMemoryUserChecker'; +$classes[] = 'App\Kernel'; +$classes[] = 'Lexik\Bundle\JWTAuthenticationBundle\Encoder\LcobucciJWTEncoder'; +$classes[] = 'Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\LcobucciJWSProvider'; +$classes[] = 'Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManager'; +$classes[] = 'Lexik\Bundle\JWTAuthenticationBundle\Services\PayloadEnrichment\ChainEnrichment'; +$classes[] = 'Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\RawKeyLoader'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\LocaleAwareListener'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\LocaleListener'; +$classes[] = 'Symfony\Component\HttpKernel\Log\Logger'; +$classes[] = 'Symfony\Component\Mailer\EventListener\EnvelopeListener'; +$classes[] = 'Symfony\Component\Mailer\EventListener\MessageLoggerListener'; +$classes[] = 'Symfony\Component\Mailer\EventListener\MessengerTransportListener'; +$classes[] = 'Symfony\Component\PropertyAccess\PropertyAccessor'; +$classes[] = 'Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor'; +$classes[] = 'Symfony\Component\HttpFoundation\RequestStack'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\ResponseListener'; +$classes[] = 'Symfony\Bundle\FrameworkBundle\Routing\Router'; +$classes[] = 'Symfony\Component\DependencyInjection\ParameterBag\ContainerBag'; +$classes[] = 'Symfony\Component\Config\ResourceCheckerConfigCacheFactory'; +$classes[] = 'Symfony\Component\Routing\RequestContext'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\RouterListener'; +$classes[] = 'Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader'; +$classes[] = 'Symfony\Component\Config\Loader\LoaderResolver'; +$classes[] = 'Symfony\Component\Routing\Loader\XmlFileLoader'; +$classes[] = 'Symfony\Component\HttpKernel\Config\FileLocator'; +$classes[] = 'Symfony\Component\Routing\Loader\YamlFileLoader'; +$classes[] = 'Symfony\Component\Routing\Loader\PhpFileLoader'; +$classes[] = 'Symfony\Component\Routing\Loader\GlobFileLoader'; +$classes[] = 'Symfony\Component\Routing\Loader\DirectoryLoader'; +$classes[] = 'Symfony\Component\Routing\Loader\ContainerLoader'; +$classes[] = 'Symfony\Bundle\FrameworkBundle\Routing\AttributeRouteControllerLoader'; +$classes[] = 'Symfony\Component\Routing\Loader\AttributeDirectoryLoader'; +$classes[] = 'Symfony\Component\Routing\Loader\AttributeFileLoader'; +$classes[] = 'Symfony\Component\Routing\Loader\Psr4DirectoryLoader'; +$classes[] = 'Symfony\Component\DependencyInjection\StaticEnvVarLoader'; +$classes[] = 'Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault'; +$classes[] = 'Symfony\Component\String\LazyString'; +$classes[] = 'Symfony\Component\Security\Http\Firewall\AccessListener'; +$classes[] = 'Symfony\Component\Security\Http\AccessMap'; +$classes[] = 'Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver'; +$classes[] = 'Symfony\Component\Security\Core\Authorization\AuthorizationChecker'; +$classes[] = 'Symfony\Component\Security\Http\Firewall\ChannelListener'; +$classes[] = 'Symfony\Component\Security\Http\Firewall\ContextListener'; +$classes[] = 'Symfony\Component\Security\Csrf\CsrfTokenManager'; +$classes[] = 'Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator'; +$classes[] = 'Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage'; +$classes[] = 'Symfony\Bundle\SecurityBundle\Security\FirewallMap'; +$classes[] = 'Symfony\Bundle\SecurityBundle\Security\FirewallContext'; +$classes[] = 'Symfony\Bundle\SecurityBundle\Security\FirewallConfig'; +$classes[] = 'Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext'; +$classes[] = 'Symfony\Component\Security\Http\Firewall\ExceptionListener'; +$classes[] = 'Symfony\Component\Security\Http\HttpUtils'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\CheckCredentialsListener'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\CsrfProtectionListener'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\UserProviderListener'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\PasswordMigratingListener'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\SessionStrategyListener'; +$classes[] = 'Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\UserCheckerListener'; +$classes[] = 'Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener'; +$classes[] = 'Symfony\Component\Security\Http\Logout\LogoutUrlGenerator'; +$classes[] = 'Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory'; +$classes[] = 'Symfony\Bundle\SecurityBundle\Routing\LogoutRouteLoader'; +$classes[] = 'Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage'; +$classes[] = 'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage'; +$classes[] = 'Symfony\Component\Security\Core\User\InMemoryUserProvider'; +$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface'; +$classes[] = 'Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter'; +$classes[] = 'Symfony\Component\HttpFoundation\Session\SessionFactory'; +$classes[] = 'Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorageFactory'; +$classes[] = 'Symfony\Component\HttpFoundation\Session\Storage\MetadataBag'; +$classes[] = 'Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\SessionListener'; +$classes[] = 'Symfony\Component\String\Slugger\AsciiSlugger'; +$classes[] = 'Twig\Cache\FilesystemCache'; +$classes[] = 'Twig\Extension\CoreExtension'; +$classes[] = 'Twig\Extension\EscaperExtension'; +$classes[] = 'Twig\Extension\OptimizerExtension'; +$classes[] = 'Twig\Extension\StagingExtension'; +$classes[] = 'Twig\ExtensionSet'; +$classes[] = 'Twig\Template'; +$classes[] = 'Twig\TemplateWrapper'; +$classes[] = 'Twig\Environment'; +$classes[] = 'Twig\Loader\FilesystemLoader'; +$classes[] = 'Symfony\Bridge\Twig\Extension\CsrfExtension'; +$classes[] = 'Symfony\Bridge\Twig\Extension\LogoutUrlExtension'; +$classes[] = 'Symfony\Bridge\Twig\Extension\SecurityExtension'; +$classes[] = 'Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator'; +$classes[] = 'Symfony\Bridge\Twig\Extension\ProfilerExtension'; +$classes[] = 'Twig\Profiler\Profile'; +$classes[] = 'Symfony\Bridge\Twig\Extension\TranslationExtension'; +$classes[] = 'Symfony\Bridge\Twig\Extension\RoutingExtension'; +$classes[] = 'Symfony\Bridge\Twig\Extension\YamlExtension'; +$classes[] = 'Symfony\Bridge\Twig\Extension\StopwatchExtension'; +$classes[] = 'Symfony\Bridge\Twig\Extension\HttpKernelExtension'; +$classes[] = 'Symfony\Bridge\Twig\Extension\HttpFoundationExtension'; +$classes[] = 'Symfony\Component\HttpFoundation\UrlHelper'; +$classes[] = 'Symfony\Bridge\Twig\Extension\FormExtension'; +$classes[] = 'Doctrine\Bundle\DoctrineBundle\Twig\DoctrineExtension'; +$classes[] = 'Twig\Extension\DebugExtension'; +$classes[] = 'HWI\Bundle\OAuthBundle\Twig\Extension\OAuthExtension'; +$classes[] = 'Symfony\Bridge\Twig\AppVariable'; +$classes[] = 'Twig\RuntimeLoader\ContainerRuntimeLoader'; +$classes[] = 'Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator'; +$classes[] = 'Symfony\Bridge\Twig\Form\TwigRendererEngine'; +$classes[] = 'Symfony\Component\Form\FormRenderer'; +$classes[] = 'Symfony\Component\Mailer\EventListener\MessageListener'; +$classes[] = 'Symfony\Bridge\Twig\Mime\BodyRenderer'; +$classes[] = 'Symfony\Bridge\Twig\Extension\HttpKernelRuntime'; +$classes[] = 'Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler'; +$classes[] = 'Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator'; +$classes[] = 'Symfony\Component\HttpFoundation\UriSigner'; +$classes[] = 'Symfony\Bridge\Twig\Extension\CsrfRuntime'; +$classes[] = 'Symfony\Component\HttpKernel\EventListener\ValidateRequestListener'; + +$preloaded = Preloader::preload($classes); + +$classes = []; +$classes[] = 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator'; +$classes[] = 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher'; +$preloaded = Preloader::preload($classes, $preloaded); diff --git a/var/cache/dev/App_KernelDevDebugContainer.xml b/var/cache/dev/App_KernelDevDebugContainer.xml new file mode 100644 index 0000000..4f4d561 --- /dev/null +++ b/var/cache/dev/App_KernelDevDebugContainer.xml @@ -0,0 +1,5983 @@ + + + + /home/skylar/Projects/mycomments-api + dev + %env(default:kernel.environment:APP_RUNTIME_ENV)% + %env(query_string:default:container.runtime_mode:APP_RUNTIME_MODE)% + %env(bool:default::key:web:default:kernel.runtime_mode:)% + %env(not:default:kernel.runtime_mode.web:)% + %env(bool:default::key:worker:default:kernel.runtime_mode:)% + true + /home/skylar/Projects/mycomments-api/var/cache/dev + /home/skylar/Projects/mycomments-api/var/cache/dev + /home/skylar/Projects/mycomments-api/var/log + + Symfony\Bundle\FrameworkBundle\FrameworkBundle + Symfony\Bundle\MakerBundle\MakerBundle + Doctrine\Bundle\DoctrineBundle\DoctrineBundle + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle + Symfony\Bundle\SecurityBundle\SecurityBundle + Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle + Symfony\Bundle\TwigBundle\TwigBundle + HWI\Bundle\OAuthBundle\HWIOAuthBundle + + + + /home/skylar/Projects/mycomments-api/vendor/symfony/framework-bundle + Symfony\Bundle\FrameworkBundle + + + /home/skylar/Projects/mycomments-api/vendor/symfony/maker-bundle/src + Symfony\Bundle\MakerBundle + + + /home/skylar/Projects/mycomments-api/vendor/doctrine/doctrine-bundle + Doctrine\Bundle\DoctrineBundle + + + /home/skylar/Projects/mycomments-api/vendor/doctrine/doctrine-migrations-bundle + Doctrine\Bundle\MigrationsBundle + + + /home/skylar/Projects/mycomments-api/vendor/symfony/security-bundle + Symfony\Bundle\SecurityBundle + + + /home/skylar/Projects/mycomments-api/vendor/lexik/jwt-authentication-bundle + Lexik\Bundle\JWTAuthenticationBundle + + + /home/skylar/Projects/mycomments-api/vendor/symfony/twig-bundle + Symfony\Bundle\TwigBundle + + + /home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src + HWI\Bundle\OAuthBundle + + + UTF-8 + App_KernelDevDebugContainer + + console.command + console.error + console.signal + console.terminate + form.pre_submit + form.submit + form.post_submit + form.pre_set_data + form.post_set_data + kernel.controller_arguments + kernel.controller + kernel.response + kernel.finish_request + kernel.request + kernel.view + kernel.exception + kernel.terminate + security.authentication.success + security.interactive_login + security.switch_user + + null + /_fragment + %env(APP_SECRET)% + false + false + + en + + error_controller + %env(default::SYMFONY_IDE)% + -1 + /home/skylar/Projects/mycomments-api/var/cache/dev/App_KernelDevDebugContainer.xml + localhost + http + + kernel::loadRoutes + /home/skylar/Projects/mycomments-api/var/cache/dev + 80 + 443 + _/home/skylar/Projects/mycomments-api.App_KernelDevDebugContainer + _sf2_meta + + 0 + auto + true + lax + 1 + + null + 0 + true + _token + validators + + Doctrine\DBAL\Configuration + Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector + Symfony\Bridge\Doctrine\ContainerAwareEventManager + Doctrine\Bundle\DoctrineBundle\ConnectionFactory + Doctrine\DBAL\Event\Listeners\MysqlSessionInit + Doctrine\DBAL\Event\Listeners\OracleSessionInit + Doctrine\Bundle\DoctrineBundle\Registry + + doctrine.orm.default_entity_manager + + default + + + doctrine.dbal.default_connection + + default + Doctrine\ORM\Configuration + Doctrine\ORM\EntityManager + Doctrine\Bundle\DoctrineBundle\ManagerConfigurator + Doctrine\Common\Cache\ArrayCache + Doctrine\Common\Cache\ApcCache + Doctrine\Common\Cache\MemcacheCache + localhost + 11211 + Memcache + Doctrine\Common\Cache\MemcachedCache + localhost + 11211 + Memcached + Doctrine\Common\Cache\RedisCache + localhost + 6379 + Redis + Doctrine\Common\Cache\XcacheCache + Doctrine\Common\Cache\WinCacheCache + Doctrine\Common\Cache\ZendDataCache + Doctrine\Persistence\Mapping\Driver\MappingDriverChain + Doctrine\ORM\Mapping\Driver\AnnotationDriver + Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver + Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver + Doctrine\ORM\Mapping\Driver\PHPDriver + Doctrine\ORM\Mapping\Driver\StaticPHPDriver + Doctrine\ORM\Mapping\Driver\AttributeDriver + Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer + Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser + Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator + Symfony\Bridge\Doctrine\Validator\DoctrineInitializer + Symfony\Bridge\Doctrine\Security\User\EntityUserProvider + Doctrine\ORM\Tools\ResolveTargetEntityListener + Doctrine\ORM\Tools\AttachEntityListenersListener + Doctrine\ORM\Mapping\DefaultNamingStrategy + Doctrine\ORM\Mapping\UnderscoreNamingStrategy + Doctrine\ORM\Mapping\DefaultQuoteStrategy + Doctrine\ORM\Mapping\AnsiQuoteStrategy + Doctrine\ORM\Mapping\DefaultTypedFieldMapper + Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver + Doctrine\ORM\Cache\DefaultCacheFactory + Doctrine\ORM\Cache\Region\DefaultRegion + Doctrine\ORM\Cache\Region\FileLockRegion + Doctrine\ORM\Cache\Logging\CacheLoggerChain + Doctrine\ORM\Cache\Logging\StatisticsCacheLogger + Doctrine\ORM\Cache\CacheConfiguration + Doctrine\ORM\Cache\RegionsConfiguration + true + true + /home/skylar/Projects/mycomments-api/var/cache/dev/doctrine/orm/Proxies + Proxies + null + null + + null + true + migrate + true + + dev + main + + + %env(JWT_PASSPHRASE)% + 3600 + 0 + false + username + RS256 + + form_div_layout.html.twig + + /home/skylar/Projects/mycomments-api/templates + null + + false + false + hwi_oauth_connect + IS_AUTHENTICATED_REMEMBERED + + facebook + + false + false + null + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\DeezerResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\HubicResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\DiscogsResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\DailymotionResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\XingResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\StackExchangeResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\TwitchResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\OdnoklassnikiResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GitHubResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\FlickrResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\Office365ResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\ThirtySevenSignalsResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\EventbriteResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\YahooResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\WindowsLiveResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\KeycloakResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\FacebookResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\CleverResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GitLabResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\RunKeeperResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\TwitterResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\LinkedinResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\JiraResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\SlackResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\SinaWeiboResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\SpotifyResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\YoutubeResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\FiwareResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\OAuth1ResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\TraktResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AzureResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GoogleResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AmazonResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\SensioConnectResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\VkontakteResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\BufferAppResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\StereomoodResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\BitlyResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\TelegramResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\OAuth2ResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\JawboneResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\DisqusResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\DropboxResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AppleResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\SoundcloudResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\FoursquareResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\YandexResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GeniusResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\InstagramResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\Bitbucket2ResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\BitbucketResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\ItembaseResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\RedditResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AsanaResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\TrelloResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\WordpressResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\ToshlResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\BoxResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\StravaResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\Auth0ResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\DeviantartResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\QQResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\MailRuResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\SalesforceResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\PassageResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\EveOnlineResourceOwner + HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\PaypalResourceOwner + + + + + + + + + + + + + + + + + + + + + + + + + + controller.argument_value_resolver + + + controller.argument_value_resolver + + + + controller.targeted_value_resolver + + + + controller.argument_value_resolver + + + controller.argument_value_resolver + + + controller.argument_value_resolver + + + controller.argument_value_resolver + + + + controller.argument_value_resolver + + + controller.argument_value_resolver + + + controller.targeted_value_resolver + + + + + UTF-8 + false + + + + + + + + en + + false + + + + + + + + + + + + + error_controller + + + + + + + + error_controller + + true + + + + + + + + + + + + + + + + + + + + true + + + + + + + null + + true + + + + /home/skylar/Projects/mycomments-api/var/cache/dev/http_cache + + + + + + + + + true + /home/skylar/Projects/mycomments-api/var/cache/dev/App_KernelDevDebugContainerDeprecations.log + + + + + + + + + + %env(APP_SECRET)% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + reset + + + reset + + + reset + + + reset + + + reset + + + ?reset + + + reset + + + reset + + + reset + + + reset + + + reset + + + reset + + + reset + + + reset + + + disableUsageTracking + setToken + + + reset + + + reset + + + reset + + + reset + + + reset + + + reset + + + reset + + + + + + + + + + + + + + + + + + + + + + + + en + + + + + + getEnv + + + + + + + + get + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + /_fragment + + + + + + + + + /_fragment + + + + + + + true + + + + UTF-8 + + /home/skylar/Projects/mycomments-api + + + + + + + + + + + + + + + + + + + + + + + + + + about + + + Display information about the current project + + + + + + + /home/skylar/Projects/mycomments-api + + assets:install + + + Install bundle's web assets under a public directory + + + + + + + + + cache:clear + + + Clear the cache + + + + + + + + cache.app + cache.system + cache.validator + cache.serializer + cache.property_info + cache.doctrine.orm.default.result + cache.doctrine.orm.default.query + cache.security_expression_language + cache.security_is_granted_attribute_expression_language + cache.security_is_csrf_token_valid_attribute_expression_language + + + cache:pool:clear + + + Clear cache pools + + + + + + + + cache:pool:prune + + + Prune cache pools + + + + + + + + cache:pool:invalidate-tags + + + Invalidate cache tags for all or a specific pool + + + + + + + + cache.app + cache.system + cache.validator + cache.serializer + cache.property_info + cache.doctrine.orm.default.result + cache.doctrine.orm.default.query + cache.security_expression_language + cache.security_is_granted_attribute_expression_language + cache.security_is_csrf_token_valid_attribute_expression_language + + + cache:pool:delete + + + Delete an item from a cache pool + + + + + + + cache.app + cache.system + cache.validator + cache.serializer + cache.property_info + cache.doctrine.orm.default.result + cache.doctrine.orm.default.query + cache.security_expression_language + cache.security_is_granted_attribute_expression_language + cache.security_is_csrf_token_valid_attribute_expression_language + + + cache:pool:list + + + List available cache pools + + + + + + + + cache:warmup + + + Warm up an empty cache + + + + + + + debug:config + + + Dump the current configuration for an extension + + + + + + + config:dump-reference + + + Dump the default configuration for an extension + + + + + + + debug:container + + + Display current services for an application + + + + + + + lint:container + + + Ensure that arguments injected into services match type declarations + + + + + + null + + + debug:autowiring + + + List classes/interfaces you can use for autowiring + + + + + + dev + /home/skylar/Projects/mycomments-api + + debug:dotenv + + + List all dotenv files with variables and values + + + + + + + + debug:event-dispatcher + + + Display configured listeners for an application + + + + + + + + + debug:router + + + Display current routes for an application + + + + + + + + + router:match + + + Help debug routes by simulating a path info match + + + + + + + lint:yaml + + + Lint a YAML file and outputs encountered errors + + + + + + + + Symfony\Component\Form\Extension\Core\Type + Symfony\Bridge\Doctrine\Form\Type + + + Symfony\Component\Form\Extension\Core\Type\FormType + Symfony\Component\Form\Extension\Core\Type\ChoiceType + Symfony\Component\Form\Extension\Core\Type\FileType + Symfony\Component\Form\Extension\Core\Type\ColorType + Symfony\Bridge\Doctrine\Form\Type\EntityType + + + Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension + Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension + Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension + Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension + Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension + Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension + Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension + + + Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser + + + + debug:form + + + Display form type information + + + + + + + + + secrets:set + + + Set a secret in the vault + + + + + + + + + secrets:remove + + + Remove a secret from the vault + + + + + + + + + secrets:generate-keys + + + Generate new encryption keys + + + + + + + + + secrets:list + + + List all secrets + + + + + + + + + secrets:reveal + + + Reveal the value of a secret + + + + + + + + + secrets:decrypt-to-local + + + Decrypt all secrets and stores them in the local vault + + + + + + + + + secrets:encrypt-from-local + + + Encrypt all local secrets to the vault + + + + + + false + + + + + + + + + + eKxI7xeGjc + 0 + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/app + + + + + + + + + + + + + X+61g8xV65 + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + ZIig0kgICk + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + upyJmb8pRk + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + 6-JsMVn9LN + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + + 0 + %container.build_id% + + + + + + + + + 0 + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/app + + + + + + + + PSR-6 provider service + + 0 + + + + + Redis connection service + + 0 + + + + + + + + + Redis connection service + + 0 + + + + + + + + + Memcached connection service + + 0 + + + + + + + + + DBAL connection service + + 0 + + + + + + + + + + PDO connection service + + 0 + + + + + + + + + + 0 + + + + + + null + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + http codes + delay ms + multiplier + max delay ms + jitter + + + + + + null + + + + + GuzzleHttp\UriTemplate\UriTemplate + expand + + + + + + + + + expand + + + + + + + + + + null + + + + + smtp://null + + + + + + + + smtp://null + + + + + + + + + null + null + + + + + + + + + + + + + + + mailer:test + + + Test Mailer transports by sending an email + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + -1 + true + true + null + + + + + null + %env(bool:default::key:web:default:kernel.runtime_mode:)% + + + %env(default::SYMFONY_IDE)% + + + + true + + + + event_dispatcher.dispatcher + + + + + + + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + checkPassport + + 1024 + + + kernel.response + + + onKernelResponse + + 0 + + + kernel.response + + + onKernelResponse + + 0 + + + kernel.request + + + setDefaultLocale + + 100 + + + kernel.request + + + onKernelRequest + + 16 + + + kernel.finish_request + + + onKernelFinishRequest + + 0 + + + kernel.request + + + onKernelRequest + + 256 + + + kernel.response + + + onResponse + + -255 + + + kernel.controller_arguments + + + onControllerArguments + + 0 + + + kernel.exception + + + logKernelException + + 0 + + + kernel.exception + + + onKernelException + + -128 + + + kernel.response + + + removeCspHeader + + -128 + + + kernel.controller_arguments + + + onKernelControllerArguments + + 10 + + + kernel.response + + + onKernelResponse + + -10 + + + kernel.request + + + onKernelRequest + + 15 + + + kernel.finish_request + + + onKernelFinishRequest + + -15 + + + console.error + + + onConsoleError + + -128 + + + console.terminate + + + onConsoleTerminate + + -128 + + + console.error + + + onConsoleError + + 0 + + + Symfony\Component\Mailer\Event\MessageEvent + + + onMessage + + -255 + + + Symfony\Component\Mailer\Event\MessageEvent + + + onMessage + + -255 + + + Symfony\Component\Mailer\Event\MessageEvent + + + onMessage + + 0 + + + kernel.request + + + configure + + 2048 + + + console.command + + + configure + + 2048 + + + kernel.request + + + onKernelRequest + + 32 + + + kernel.finish_request + + + onKernelFinishRequest + + 0 + + + kernel.exception + + + onKernelException + + -64 + + + kernel.request + + + onKernelRequest + + 128 + + + kernel.response + + + onKernelResponse + + -1000 + + + console.error + + + onConsoleError + + 0 + + + console.terminate + + + onConsoleTerminate + + 0 + + + kernel.request + + + onKernelRequest + + 192 + + + kernel.controller_arguments + + + onKernelControllerArguments + + 20 + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + checkPassport + + 0 + + + Symfony\Component\Security\Http\Event\LoginSuccessEvent + + + onLoginSuccess + + 0 + + + debug.security.authorization.vote + + + onVoterVote + + 0 + + + kernel.request + + + configureLogoutUrlGenerator + + 8 + + + kernel.request + + + onKernelRequest + + 8 + + + kernel.finish_request + + + onKernelFinishRequest + + 0 + + + kernel.view + + + onKernelView + + -128 + + + Symfony\Component\Mailer\Event\MessageEvent + + + onMessage + + 0 + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + checkPassport + + 512 + + + kernel.controller_arguments + + + onKernelControllerArguments + + 25 + + + Symfony\Component\Security\Http\Event\LogoutEvent + + + onLogout + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dev + + + + + dev + + + + + dev + + + + + dev + + + + + dev + + + + + dev + + + + dev + + + + + + + + + + + + + + + + + + + true + + + + + + + + kernel::loadRoutes + + /home/skylar/Projects/mycomments-api/var/cache/dev + true + Symfony\Component\Routing\Generator\CompiledUrlGenerator + Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper + Symfony\Bundle\FrameworkBundle\Routing\RedirectableCompiledUrlMatcher + Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper + true + service + + + + + en + + + + + + + localhost + http + 80 + 443 + + + + + + + + + + + + + + + + /home/skylar/Projects/mycomments-api + true + + + + + + + + + + + + + + + + + + + 3 + 2 + + + + + + /home/skylar/Projects/mycomments-api/config/secrets/%env(default:kernel.environment:APP_RUNTIME_ENV)% + + + + + + + + + base64:default::SYMFONY_DECRYPTION_SECRET + + + /home/skylar/Projects/mycomments-api/.env.dev.local + + + null + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + redis://localhost + + true + + + + memcached://localhost + + true + + + + 0 + false + + + + + + + onSessionUsage + + + + %session.storage.options% + + + + _sf2_meta + 0 + + + true + + + + + + _sf2_meta + 0 + + + true + + + /home/skylar/Projects/mycomments-api/var/cache/dev/sessions + MOCKSESSID + + + _sf2_meta + 0 + + + + + + + + + + + + null + + + + + A string or a connection object + + + + + + + + + + true + %session.storage.options% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + + + + null + + + + null + + + + null + + + + + + + + + + + + + + + + + + + + + true + _token + null + validators + + + + + + + + + + + + /home/skylar/Projects/mycomments-api + /home/skylar/Projects/mycomments-api/templates + + + App + + + + + + + + + + + + + + + + App\Entity + + + + + App\Entity + + + + + + + %env(default::string:MAKER_PHP_CS_FIXER_BINARY_PATH)% + %env(default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH)% + + + + + + + + + + App + null + + + + + + + + + + + + + + + + + true + false + App + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + + + + + + + + + + + + + + The "%service_id%" service is deprecated, use "maker.maker.make_test" instead. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The "%service_id%" service is deprecated, use "maker.maker.make_listener" instead. + + + + + + + + + + The "%service_id%" service is deprecated, use "maker.maker.make_test" instead. + + + + + + + + + + + + + + + + + + /home/skylar/Projects/mycomments-api + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + ibm_db2 + pdo_sqlsrv + pdo_mysql + pdo_mysql + pdo_pgsql + pdo_pgsql + pdo_pgsql + pdo_sqlite + pdo_sqlite + + + + + + + + + + + + + %doctrine.connections% + %doctrine.entity_managers% + default + default + + + + + + + + + + + + + + doctrine:database:create + + + + + + + + doctrine:database:drop + + + + + + + + doctrine:query:sql + + + + + + + + dbal:run-sql + + + + + + + + + + + + + + + + + + + + + + + + + + + postGenerateSchema + + doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener + + + + postGenerateSchema + + doctrine.orm.listeners.doctrine_token_provider_schema_listener + + + + postGenerateSchema + + doctrine.orm.listeners.pdo_session_handler_schema_listener + + + + postGenerateSchema + + doctrine.orm.listeners.lock_store_schema_listener + + + + loadClassMetadata + + doctrine.orm.default_listeners.attach_entity_listeners + + + + + + %env(resolve:DATABASE_URL)% + true + pdo_mysql + 600 + localhost + null + root + null + + + + + + + + true + + + + + + + + + %doctrine.connections% + %doctrine.entity_managers% + default + default + + + + + + + + default + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + true + + + + + + + null + + + + null + + + + + + controller.argument_value_resolver + + null + + + null + null + null + + null + null + null + null + false + + + + + + + + + doctrine:cache:clear-metadata + + + + + + + + doctrine:cache:clear-query + + + + + + + + doctrine:cache:clear-result + + + + + + + + doctrine:cache:clear-collection-region + + + + + + + + doctrine:schema:create + + + + + + + + doctrine:schema:drop + + + + + + + + doctrine:cache:clear-entity-region + + + + + + + + doctrine:mapping:info + + + + + + + + doctrine:cache:clear-query-region + + + + + + + + doctrine:query:dql + + + + + + + + doctrine:schema:update + + + + + + + + doctrine:schema:validate + + + + + + + App\Entity + + + + + + + + + + + + + + + + /home/skylar/Projects/mycomments-api/var/cache/dev/doctrine/orm/Proxies + + + Proxies + + + true + + + + + + Doctrine\Bundle\DoctrineBundle\Mapping\ClassMetadataFactory + + + Doctrine\ORM\EntityRepository + + + + + + + + + + + + + + + true + + + + + + + + + + + /home/skylar/Projects/mycomments-api/src/Entity + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Doctrine\Migrations\Version\MigrationFactory + + + + + + + + + + + + + + + + + DoctrineMigrations + /home/skylar/Projects/mycomments-api/migrations + + + false + + + true + + + true + + + + + + + + + + + + + doctrine:migrations:diff + + doctrine:migrations:diff + + + Generate a migration by comparing your current database to your mapping information. + + + + + + + doctrine:migrations:sync-metadata-storage + + doctrine:migrations:sync-metadata-storage + + + Ensures that the metadata storage is at the latest version. + + + + + + + doctrine:migrations:versions + + doctrine:migrations:list + + + Display a list of all available migrations and their status. + + + + + + + doctrine:migrations:current + + doctrine:migrations:current + + + Outputs the current version + + + + + + + doctrine:migrations:dump-schema + + doctrine:migrations:dump-schema + + + Dump the schema for your database to a migration. + + + + + + + doctrine:migrations:execute + + doctrine:migrations:execute + + + Execute one or more migration versions up or down manually. + + + + + + + doctrine:migrations:generate + + doctrine:migrations:generate + + + Generate a blank migration class. + + + + + + + doctrine:migrations:latest + + doctrine:migrations:latest + + + Outputs the latest version + + + + + + + doctrine:migrations:migrate + + doctrine:migrations:migrate + + + Execute a migration to a specified version or the latest available version. + + + + + + + doctrine:migrations:rollup + + doctrine:migrations:rollup + + + Rollup migrations by deleting all tracked versions and insert the one version that exists. + + + + + + + doctrine:migrations:status + + doctrine:migrations:status + + + View the status of a set of migrations. + + + + + + + doctrine:migrations:up-to-date + + doctrine:migrations:up-to-date + + + Tells you if your schema is up-to-date. + + + + + + + doctrine:migrations:version + + doctrine:migrations:version + + + Manually add and delete migration versions from the version table. + + + + + + + + + + + + + + + + + + null + null + + + + controller.argument_value_resolver + + + + controller.argument_value_resolver + + + + + migrate + + + + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + + + + + + LogoutListener + FirewallConfig + + + + + LogoutListener + FirewallConfig + + + + name + user_checker + request_matcher + false + false + null + null + null + null + null + + null + null + + + + + + + + + + security.logout_uris + + + firewall + + + + security.ldap.ldap + base dn + search dn + search password + default_roles + uid key + filter + password_attribute + extra_fields (email etc) + + + + + + {^https?://%%s$}i + {^https://%%s$}i + + + + + + + + + + nzWl-ja7BM + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + + null + + + + + 7H+I-t+3hW + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + +yFEAOIjaq + 0 + %container.build_id% + /home/skylar/Projects/mycomments-api/var/cache/dev/pools/system + + + + + + + auto + + sha512 + 40 + false + true + 5000 + null + null + null + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Provider Key + + + + + + enableUsageTracking + + + + + + event dispatcher + + + + + + + + target url + + + + + + + + Provider-shared Key + + + + + + + + The custom success handler service + + Provider-shared Key + + + + + + + + The custom failure handler service + + + + + + + + + + + + + + + Provider-shared Key + + null + + + false + + + + + User Provider + User Checker + Provider Key + + + _switch_user + ROLE_ALLOWED_TO_SWITCH + + false + + Target Route + + + + + + + + + + + + + + + authenticators + + + provider key + + true + true + required badges + + + + + + + + + + + + + authenticator manager + + + + + + + + + + + user provider + + + + + + + user checker + + + + + + + request rate limiter + + + + realm name + user provider + + + + + user provider + authentication success handler + authentication failure handler + options + + + + user provider + authentication success handler + authentication failure handler + options + + + + + + + + user provider + + firewall name + user key + credentials key + + credentials user identifier + + + + user provider + + firewall name + user key + + + + + + + access token handler + access token extractor + null + null + null + null + + + access token extractors + + + http client options + + + + http client + + claim + + + signature algorithm + signature key + audience + issuers + sub + + + + + signature key + The "%service_id%" service is deprecated. Please use "security.access_token_handler.oidc.jwkset" instead + + + signature keyset + + + + + + signature algorithms + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %security.firewalls% + + + + + + false + + debug:firewall + + + Display information about your security firewall(s) + + + + + + + dev + security.user_checker + .security.request_matcher.gOpgIHx + false + false + null + null + null + null + null + + null + null + + + ^/(_(profiler|wdt)|css|images|js)/ + + + + + + + + + null + null + + + + main + security.user_checker + null + true + false + security.user.provider.concrete.users_in_memory + main + null + null + null + + null + null + + + + + + + + + + + + + + + + main + + + + + + enableUsageTracking + + + + + + + + + + + + main + + true + true + + + + + + + + + + + + + + + + main + null + null + null + + false + + + + + + + + + + null + + + + + + + + + + + + + + + + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface + + + security:hash-password + + + Hash a user password + + + + + + username + + + + + + + + + + %env(JWT_PASSPHRASE)% + + + + %env(resolve:JWT_SECRET_KEY)% + %env(resolve:JWT_PUBLIC_KEY)% + %env(JWT_PASSPHRASE)% + + + + + + + + RS256 + 3600 + 0 + false + + + + + + + true + + + + + null + + + + + + + + + + + + + + + Bearer + Authorization + + + + + + + + + + + + + + + + + + RS256 + + lexik:jwt:check-config + + + Checks that the bundle is properly configured. + + + + + + + %env(JWT_PASSPHRASE)% + RS256 + + lexik:jwt:migrate-config + + + Migrate LexikJWTAuthenticationBundle configuration to the Web-Token one. + + + + + + null + + lexik:jwt:enable-encryption + + + Enable Web-Token encryption support. + + + + + + + + + + + lexik:jwt:generate-token + + + Generates a JWT token for a given user. + + + + + + + %env(resolve:JWT_SECRET_KEY)% + %env(resolve:JWT_PUBLIC_KEY)% + %env(JWT_PASSPHRASE)% + RS256 + + lexik:jwt:generate-keypair + + + Generate public/private keys for use in your application. + + + + + + + + + + + + + + /home/skylar/Projects/mycomments-api/var/cache/dev/twig + UTF-8 + true + true + name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + app + + + + + + + + + + dev + + + true + + + + + + + + + + + + + + + email + null + + /home/skylar/Projects/mycomments-api/templates + + *.twig + + + + + + + + + + + + /home/skylar/Projects/mycomments-api + + /home/skylar/Projects/mycomments-api/vendor/doctrine/doctrine-bundle/templates + Doctrine + + + /home/skylar/Projects/mycomments-api/vendor/doctrine/doctrine-bundle/templates + !Doctrine + + + /home/skylar/Projects/mycomments-api/vendor/doctrine/doctrine-migrations-bundle/Resources/views + DoctrineMigrations + + + /home/skylar/Projects/mycomments-api/vendor/doctrine/doctrine-migrations-bundle/Resources/views + !DoctrineMigrations + + + /home/skylar/Projects/mycomments-api/vendor/symfony/security-bundle/Resources/views + Security + + + /home/skylar/Projects/mycomments-api/vendor/symfony/security-bundle/Resources/views + !Security + + + /home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views + HWIOAuth + + + /home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views + !HWIOAuth + + + /home/skylar/Projects/mycomments-api/templates + + + /home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Email + email + + + /home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Email + !email + + + /home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form + + + + + + + + + + + + + + + + + null + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + F j, Y H:i + %d days + null + 0 + . + , + + + + + + + + + + + true + + + + + + + + + + + + + + + null + + + + %twig.form.resources% + + + + + + + + + + + + /home/skylar/Projects/mycomments-api + %kernel.bundles_metadata% + /home/skylar/Projects/mycomments-api/templates + + + debug:twig + + + Show a list of twig functions, filters, globals and tests + + + + + + + + *.twig + + + lint:twig + + + Lint a Twig template and outputs encountered errors + + + + + null + + + + + + + + + + + + + + + + + IS_AUTHENTICATED_REMEMBERED + false + hwi_oauth_connect + false + null + + + + + + + + + + + IS_AUTHENTICATED_REMEMBERED + null + null + null + + + + + + + + false + IS_AUTHENTICATED_REMEMBERED + + + + + + null + false + false + + + + + + + Provider-shared Key + + + + + + + + + + + + + + + User entity class name + an array of properties, where key is resource owner name & value is property name in User entity + + + OAuthAuthenticator or AuthenticationProviderInterface + + + + + + + + + + + + false + IS_AUTHENTICATED_REMEMBERED + + + + + false + + + + %hwi_oauth.resource_owners% + + + + + + + + + + + + + + + + + + + + + + + %env(FB_ID)% + %env(FB_SECRET)% + email public_profile + + + + facebook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + make:auth + + + Create a Guard authenticator of different flavors + + + + + + + + + + + make:command + + + Create a new console command class + + + + + + + + + + + make:twig-component + + + Create a twig (or live) component + + + + + + + + + + + make:controller + + + Create a new controller class + + + + + + + + + + + make:crud + + + Create CRUD for Doctrine entity class + + + + + + + + + + + make:docker:database + + + Add a database container to your compose.yaml file + + + + + + + + + + + make:entity + + + Create or update a Doctrine entity class, and optionally an API Platform resource + + + + + + + + + + + make:fixtures + + + Create a new class to load Doctrine fixtures + + + + + + + + + + + make:form + + + Create a new form class + + + + + + + + + + + + make:listener + + + + make:subscriber + + + + Creates a new event subscriber class or a new event listener class + + + + + + + + + + + make:message + + + Create a new message and handler + + + + + + + + + + + make:messenger-middleware + + + Create a new messenger middleware + + + + + + + + + + + make:registration-form + + + Create a new registration form system + + + + + + + + + + + make:reset-password + + + Create controller, entity, and repositories for use with symfonycasts/reset-password-bundle + + + + + + + + + + + make:schedule + + + Create a scheduler component + + + + + + + + + + + make:serializer:encoder + + + Create a new serializer encoder class + + + + + + + + + + + make:serializer:normalizer + + + Create a new serializer normalizer class + + + + + + + + + + + make:twig-extension + + + Create a new Twig extension with its runtime class + + + + + + + + + + + + + make:test + + + + make:unit-test + make:functional-test + + + + Create a new test class + + + + + + + + + + + make:validator + + + Create a new validator and constraint class + + + + + + + + + + + make:voter + + + Create a new security voter class + + + + + + + + + + + make:user + + + Create a new security user class + + + + + + + + + + + make:migration + + + Create a new migration based on database changes + + + + + + + + + + + make:stimulus-controller + + + Create a new Stimulus controller + + + + + + + + + + + make:security:form-login + + + Generate the code needed for the form_login authenticator + + + + + + + + + + + make:security:custom + + + Create a custom security authenticator. + + + + + + + + + + + make:webhook + + + Create a new Webhook + + + + event_dispatcher.dispatcher + + + + + + + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + checkPassport + + 2048 + + + Symfony\Component\Security\Http\Event\LoginSuccessEvent + + + onSuccessfulLogin + + 0 + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + preCheckCredentials + + 256 + + + security.authentication.success + + + postCheckCredentials + + 256 + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + checkPassport + + 1024 + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + checkPassport + + 0 + + + Symfony\Component\Security\Http\Event\LoginSuccessEvent + + + onLoginSuccess + + 0 + + + Symfony\Component\Security\Http\Event\CheckPassportEvent + + + checkPassport + + 512 + + + Symfony\Component\Security\Http\Event\LogoutEvent + + + onLogout + + 0 + + + + + + + + + + + kernel::registerContainerConfiguration() + + + + + + kernel::loadRoutes() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default + + + + + + 600 + + + default + + + + + + + + + + + + + + + + + + + null + + + + + + + + + + null + null + null + + %env(bool:default::key:web:default:kernel.runtime_mode:)% + + + + + + + + + + router.default + + + + + + + + + + + + router.cache_warmer + + + + + + + + + + + + twig.template_cache_warmer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Symfony\Bundle\FrameworkBundle\Controller\AbstractController + Symfony\Bundle\FrameworkBundle\Controller\TemplateController + + + + + App\Kernel + Doctrine\Bundle\DoctrineBundle\Controller\ProfilerController + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + App\Entity + + + + + + + about + + Display information about the current project + false + + + + assets:install + + Install bundle's web assets under a public directory + false + + + + cache:clear + + Clear the cache + false + + + + cache:pool:clear + + Clear cache pools + false + + + + cache:pool:prune + + Prune cache pools + false + + + + cache:pool:invalidate-tags + + Invalidate cache tags for all or a specific pool + false + + + + cache:pool:delete + + Delete an item from a cache pool + false + + + + cache:pool:list + + List available cache pools + false + + + + cache:warmup + + Warm up an empty cache + false + + + + debug:config + + Dump the current configuration for an extension + false + + + + config:dump-reference + + Dump the default configuration for an extension + false + + + + debug:container + + Display current services for an application + false + + + + lint:container + + Ensure that arguments injected into services match type declarations + false + + + + debug:autowiring + + List classes/interfaces you can use for autowiring + false + + + + debug:dotenv + + List all dotenv files with variables and values + false + + + + debug:event-dispatcher + + Display configured listeners for an application + false + + + + debug:router + + Display current routes for an application + false + + + + router:match + + Help debug routes by simulating a path info match + false + + + + lint:yaml + + Lint a YAML file and outputs encountered errors + false + + + + debug:form + + Display form type information + false + + + + secrets:set + + Set a secret in the vault + false + + + + secrets:remove + + Remove a secret from the vault + false + + + + secrets:generate-keys + + Generate new encryption keys + false + + + + secrets:list + + List all secrets + false + + + + secrets:reveal + + Reveal the value of a secret + false + + + + secrets:decrypt-to-local + + Decrypt all secrets and stores them in the local vault + false + + + + secrets:encrypt-from-local + + Encrypt all local secrets to the vault + false + + + + mailer:test + + Test Mailer transports by sending an email + false + + + + doctrine:migrations:diff + + Generate a migration by comparing your current database to your mapping information. + false + + + + doctrine:migrations:sync-metadata-storage + + Ensures that the metadata storage is at the latest version. + false + + + + doctrine:migrations:list + + Display a list of all available migrations and their status. + false + + + + doctrine:migrations:current + + Outputs the current version + false + + + + doctrine:migrations:dump-schema + + Dump the schema for your database to a migration. + false + + + + doctrine:migrations:execute + + Execute one or more migration versions up or down manually. + false + + + + doctrine:migrations:generate + + Generate a blank migration class. + false + + + + doctrine:migrations:latest + + Outputs the latest version + false + + + + doctrine:migrations:migrate + + Execute a migration to a specified version or the latest available version. + false + + + + doctrine:migrations:rollup + + Rollup migrations by deleting all tracked versions and insert the one version that exists. + false + + + + doctrine:migrations:status + + View the status of a set of migrations. + false + + + + doctrine:migrations:up-to-date + + Tells you if your schema is up-to-date. + false + + + + doctrine:migrations:version + + Manually add and delete migration versions from the version table. + false + + + + debug:firewall + + Display information about your security firewall(s) + false + + + + security:hash-password + + Hash a user password + false + + + + lexik:jwt:check-config + + Checks that the bundle is properly configured. + false + + + + lexik:jwt:migrate-config + + Migrate LexikJWTAuthenticationBundle configuration to the Web-Token one. + false + + + + lexik:jwt:enable-encryption + + Enable Web-Token encryption support. + false + + + + lexik:jwt:generate-token + + Generates a JWT token for a given user. + false + + + + lexik:jwt:generate-keypair + + Generate public/private keys for use in your application. + false + + + + debug:twig + + Show a list of twig functions, filters, globals and tests + false + + + + lint:twig + + Lint a Twig template and outputs encountered errors + false + + + + make:auth + + Create a Guard authenticator of different flavors + false + + + + make:command + + Create a new console command class + false + + + + make:twig-component + + Create a twig (or live) component + false + + + + make:controller + + Create a new controller class + false + + + + make:crud + + Create CRUD for Doctrine entity class + false + + + + make:docker:database + + Add a database container to your compose.yaml file + false + + + + make:entity + + Create or update a Doctrine entity class, and optionally an API Platform resource + false + + + + make:fixtures + + Create a new class to load Doctrine fixtures + false + + + + make:form + + Create a new form class + false + + + + make:listener + + make:subscriber + + Creates a new event subscriber class or a new event listener class + false + + + + make:message + + Create a new message and handler + false + + + + make:messenger-middleware + + Create a new messenger middleware + false + + + + make:registration-form + + Create a new registration form system + false + + + + make:reset-password + + Create controller, entity, and repositories for use with symfonycasts/reset-password-bundle + false + + + + make:schedule + + Create a scheduler component + false + + + + make:serializer:encoder + + Create a new serializer encoder class + false + + + + make:serializer:normalizer + + Create a new serializer normalizer class + false + + + + make:twig-extension + + Create a new Twig extension with its runtime class + false + + + + make:test + + make:unit-test + make:functional-test + + Create a new test class + false + + + + make:validator + + Create a new validator and constraint class + false + + + + make:voter + + Create a new security voter class + false + + + + make:user + + Create a new security user class + false + + + + make:migration + + Create a new migration based on database changes + false + + + + make:stimulus-controller + + Create a new Stimulus controller + false + + + + make:security:form-login + + Generate the code needed for the form_login authenticator + false + + + + make:security:custom + + Create a custom security authenticator. + false + + + + make:webhook + + Create a new Webhook + false + + + + + + + console.command.about + console.command.assets_install + console.command.cache_clear + console.command.cache_pool_clear + console.command.cache_pool_prune + console.command.cache_pool_invalidate_tags + console.command.cache_pool_delete + console.command.cache_pool_list + console.command.cache_warmup + console.command.config_debug + console.command.config_dump_reference + console.command.container_debug + console.command.container_lint + console.command.debug_autowiring + console.command.dotenv_debug + console.command.event_dispatcher_debug + console.command.router_debug + console.command.router_match + console.command.yaml_lint + console.command.form_debug + console.command.secrets_set + console.command.secrets_remove + console.command.secrets_generate_key + console.command.secrets_list + console.command.secrets_reveal + console.command.secrets_decrypt_to_local + console.command.secrets_encrypt_from_local + console.command.mailer_test + doctrine.database_create_command + doctrine.database_drop_command + doctrine.query_sql_command + Doctrine\DBAL\Tools\Console\Command\RunSqlCommand + doctrine.cache_clear_metadata_command + doctrine.cache_clear_query_cache_command + doctrine.cache_clear_result_command + doctrine.cache_collection_region_command + doctrine.schema_create_command + doctrine.schema_drop_command + doctrine.clear_entity_region_command + doctrine.mapping_info_command + doctrine.clear_query_region_command + doctrine.query_dql_command + doctrine.schema_update_command + doctrine.schema_validate_command + doctrine_migrations.diff_command + doctrine_migrations.sync_metadata_command + doctrine_migrations.versions_command + doctrine_migrations.current_command + doctrine_migrations.dump_schema_command + doctrine_migrations.execute_command + doctrine_migrations.generate_command + doctrine_migrations.latest_command + doctrine_migrations.migrate_command + doctrine_migrations.rollup_command + doctrine_migrations.status_command + doctrine_migrations.up_to_date_command + doctrine_migrations.version_command + security.command.debug_firewall + security.command.user_password_hash + lexik_jwt_authentication.check_config_command + lexik_jwt_authentication.migrate_config_command + lexik_jwt_authentication.enable_encryption_config_command + lexik_jwt_authentication.generate_token_command + lexik_jwt_authentication.generate_keypair_command + twig.command.debug + twig.command.lint + maker.auto_command.make_auth + maker.auto_command.make_command + maker.auto_command.make_twig_component + maker.auto_command.make_controller + maker.auto_command.make_crud + maker.auto_command.make_docker_database + maker.auto_command.make_entity + maker.auto_command.make_fixtures + maker.auto_command.make_form + maker.auto_command.make_listener + maker.auto_command.make_listener + maker.auto_command.make_message + maker.auto_command.make_messenger_middleware + maker.auto_command.make_registration_form + maker.auto_command.make_reset_password + maker.auto_command.make_schedule + maker.auto_command.make_serializer_encoder + maker.auto_command.make_serializer_normalizer + maker.auto_command.make_twig_extension + maker.auto_command.make_test + maker.auto_command.make_test + maker.auto_command.make_test + maker.auto_command.make_validator + maker.auto_command.make_voter + maker.auto_command.make_user + maker.auto_command.make_migration + maker.auto_command.make_stimulus_controller + maker.auto_command.make_security_form_login + maker.auto_command.make_security_custom + maker.auto_command.make_webhook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/var/cache/dev/App_KernelDevDebugContainer.xml.meta b/var/cache/dev/App_KernelDevDebugContainer.xml.meta new file mode 100644 index 0000000..2bcff94 Binary files /dev/null and b/var/cache/dev/App_KernelDevDebugContainer.xml.meta differ diff --git a/var/cache/dev/App_KernelDevDebugContainerCompiler.log b/var/cache/dev/App_KernelDevDebugContainerCompiler.log new file mode 100644 index 0000000..c3af124 --- /dev/null +++ b/var/cache/dev/App_KernelDevDebugContainerCompiler.log @@ -0,0 +1,594 @@ +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for ".instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\CommentRepository" (parent: .abstract.instanceof.App\Repository\CommentRepository). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "App\Repository\CommentRepository" (parent: .instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\CommentRepository). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for ".instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\IdRepository" (parent: .abstract.instanceof.App\Repository\IdRepository). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "App\Repository\IdRepository" (parent: .instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\IdRepository). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for ".instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\UserRepository" (parent: .abstract.instanceof.App\Repository\UserRepository). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "App\Repository\UserRepository" (parent: .instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\UserRepository). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.app" (parent: cache.adapter.filesystem). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.system" (parent: cache.adapter.system). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.validator" (parent: cache.system). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.serializer" (parent: cache.system). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.property_info" (parent: cache.system). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.system_clearer" (parent: cache.default_clearer). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.global_clearer" (parent: cache.default_clearer). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "mailer.transport_factory.native" (parent: mailer.transport_factory.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "mailer.transport_factory.null" (parent: mailer.transport_factory.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "mailer.transport_factory.sendmail" (parent: mailer.transport_factory.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "mailer.transport_factory.smtp" (parent: mailer.transport_factory.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "secrets.decryption_key" (parent: container.env). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.dbal.default_connection.configuration" (parent: doctrine.dbal.connection.configuration). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.dbal.default_connection.event_manager" (parent: doctrine.dbal.connection.event_manager). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.dbal.default_connection" (parent: doctrine.dbal.connection). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.orm.default_configuration" (parent: doctrine.orm.configuration). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.orm.default_manager_configurator" (parent: doctrine.orm.manager_configurator.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.orm.default_entity_manager" (parent: doctrine.orm.entity_manager.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.security_expression_language" (parent: cache.system). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.security_is_granted_attribute_expression_language" (parent: cache.system). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "cache.security_is_csrf_token_valid_attribute_expression_language" (parent: cache.system). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.user.provider.concrete.users_in_memory" (parent: security.user.provider.in_memory). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.firewall.map.config.dev" (parent: security.firewall.config). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.firewall.map.context.dev" (parent: security.firewall.context). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.firewall.map.config.main" (parent: security.firewall.config). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.listener.main.user_provider" (parent: security.listener.user_provider.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.context_listener.0" (parent: security.context_listener). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.listener.session.main" (parent: security.listener.session). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.authenticator.manager.main" (parent: security.authenticator.manager). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.firewall.authenticator.main" (parent: security.firewall.authenticator). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.listener.user_checker.main" (parent: security.listener.user_checker). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.exception_listener.main" (parent: security.exception_listener). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "security.firewall.map.context.main" (parent: security.firewall.lazy_context). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "lexik_jwt_authentication.key_loader.raw" (parent: lexik_jwt_authentication.key_loader.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "hwi_oauth.authentication.listener.oauth" (parent: security.authentication.listener.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_auth" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_command" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_twig_component" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_controller" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_crud" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_docker_database" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_entity" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_fixtures" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_form" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_listener" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_message" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_messenger_middleware" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_registration_form" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_reset_password" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_schedule" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_serializer_encoder" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_serializer_normalizer" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_twig_extension" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_test" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_validator" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_voter" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_user" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_migration" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_stimulus_controller" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_security_form_login" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_security_custom" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "maker.auto_command.make_webhook" (parent: maker.auto_command.abstract). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.dbal.debug_middleware.default" (parent: doctrine.dbal.debug_middleware). +Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass: Resolving inheritance for "doctrine.dbal.idle_connection_middleware.default" (parent: doctrine.dbal.idle_connection_middleware). +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\EventDispatcher\EventDispatcherInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Contracts\EventDispatcher\EventDispatcherInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\EventDispatcher\EventDispatcherInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpKernel\HttpKernelInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpFoundation\RequestStack"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpKernel\HttpCache\StoreInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpFoundation\UrlHelper"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpKernel\KernelInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Filesystem\Filesystem"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpKernel\Config\FileLocator"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpFoundation\UriSigner"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\DependencyInjection\ReverseContainer"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\String\Slugger\SluggerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Clock\ClockInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Clock\ClockInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "error_renderer.html"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "error_renderer"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service ".Psr\Container\ContainerInterface $parameter_bag"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Container\ContainerInterface $parameterBag"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Cache\CacheItemPoolInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Contracts\Cache\CacheInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Contracts\Cache\TagAwareCacheInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Contracts\HttpClient\HttpClientInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "mailer"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Mailer\MailerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Mailer\Transport\TransportInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Stopwatch\Stopwatch"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Routing\RouterInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Routing\Generator\UrlGeneratorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Routing\Matcher\UrlMatcherInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Routing\RequestContextAwareInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Routing\RequestContext"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyAccess\PropertyAccessorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyListExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "cache.default_redis_provider"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "cache.default_memcached_provider"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "cache.default_doctrine_dbal_provider"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "SessionHandlerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "session.storage.factory"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "session.handler"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Csrf\CsrfTokenManagerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Form\ResolvedFormTypeFactoryInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Form\FormRegistryInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Form\FormFactoryInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "form.property_accessor"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "form.choice_list_factory"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Mime\MimeTypesInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Mime\MimeTypeGuesserInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Doctrine\DBAL\Connection"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Doctrine\Persistence\ManagerRegistry"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Doctrine\Common\Persistence\ManagerRegistry"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.dbal.event_manager"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Doctrine\DBAL\Connection $defaultConnection"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Doctrine\ORM\EntityManagerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.orm.default_metadata_cache"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.orm.default_result_cache"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.orm.default_query_cache"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Doctrine\ORM\EntityManagerInterface $defaultEntityManager"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.orm.default_entity_manager.event_manager"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.migrations.metadata_storage"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Bundle\SecurityBundle\Security"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Http\Authentication\AuthenticationUtils"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Core\Role\RoleHierarchyInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Http\Firewall"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Http\FirewallMapInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Http\HttpUtils"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.password_hasher"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.firewall"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.user_providers"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Core\User\UserProviderInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.authentication.session_strategy.main"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.user_checker.main"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.firewall.context_locator"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Security\Core\User\UserCheckerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\JWSProviderInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Twig\Environment"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\Mime\BodyRendererInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "hwi_oauth.resource_owners.locator"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "twig.loader.filesystem"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "argument_resolver.controller_locator"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.id_generator_locator"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "twig.loader"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Log\LoggerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "http_client"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "controller_resolver"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "argument_resolver"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.access.decision_manager"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.firewall.authenticator.main"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "twig.error_renderer.html.inner"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "security.event_dispatcher.main"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "doctrine.orm.default_metadata_driver"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service ".service_locator.diNoEIK"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "locale_listener" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "http_kernel" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "url_helper" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "services_resetter" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "fragment.renderer.inline" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "console.command.router_debug" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "console.command.router_match" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "mailer.mailer" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "mailer.transport_factory.abstract" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "mailer.transport_factory.native" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "mailer.transport_factory.null" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "mailer.transport_factory.sendmail" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "mailer.transport_factory.smtp" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "router_listener" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "Symfony\Bundle\FrameworkBundle\Controller\RedirectController" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "maker.event_registry" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "maker.maker.make_registration_form" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "maker.maker.make_reset_password" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.user_checker_locator" previously pointing to "security.user_checker" to "hwi_oauth.user_checker". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.logout_url_generator" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.http_utils" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.http_utils" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.context_listener" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.authentication.listener.abstract" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.authentication.switchuser_listener" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.authentication.switchuser_listener" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.authenticator.manager" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "debug.security.firewall" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "security.listener.user_checker.main" previously pointing to "security.user_checker" to "hwi_oauth.user_checker". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.jwt_manager" previously pointing to "lexik_jwt_authentication.encoder.lcobucci" to "lexik_jwt_authentication.encoder". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.jwt_manager" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.jws_provider.lcobucci" previously pointing to "lexik_jwt_authentication.key_loader.raw" to "lexik_jwt_authentication.key_loader". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.handler.authentication_success" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.handler.authentication_failure" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.security.jwt_authenticator" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.check_config_command" previously pointing to "lexik_jwt_authentication.key_loader.raw" to "lexik_jwt_authentication.key_loader". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "lexik_jwt_authentication.migrate_config_command" previously pointing to "lexik_jwt_authentication.key_loader.raw" to "lexik_jwt_authentication.key_loader". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "twig.extension.routing" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController" previously pointing to "security.user_checker" to "hwi_oauth.user_checker". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "HWI\Bundle\OAuthBundle\Controller\Connect\RegisterController" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "HWI\Bundle\OAuthBundle\Controller\Connect\RegisterController" previously pointing to "security.user_checker" to "hwi_oauth.user_checker". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "HWI\Bundle\OAuthBundle\Controller\LoginController" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "hwi_oauth.authentication.listener.oauth" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service "hwi_oauth.authentication.failure_handler" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service ".debug.security.voter.security.access.authenticated_voter" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service ".debug.security.voter.security.access.simple_role_voter" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service ".service_locator.YAcX0.U" previously pointing to "router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\ReplaceAliasByActualDefinitionPass: Changed reference of service ".service_locator.YbySDCA" previously pointing to "debug.event_dispatcher" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "App\Entity"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "container.env"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "Symfony\Component\Config\Loader\LoaderInterface"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "Symfony\Component\HttpFoundation\Request"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "Symfony\Component\HttpFoundation\Response"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "Symfony\Component\HttpFoundation\Session\SessionInterface"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.system"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.apcu"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.filesystem"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.psr6"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.redis"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.redis_tag_aware"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.memcached"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.doctrine_dbal"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.pdo"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "cache.adapter.array"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "http_client.abstract_retry_strategy"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "mailer.transport_factory.abstract"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "maker.auto_command.abstract"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.dbal.connection"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.dbal.connection.event_manager"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.dbal.connection.configuration"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.dbal.schema_asset_filter_manager"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.dbal.debug_middleware"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.dbal.idle_connection_middleware"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.orm.configuration"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.orm.entity_manager.abstract"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.orm.manager_configurator.abstract"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "doctrine.orm.security.user.provider"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.firewall.context"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.firewall.lazy_context"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.firewall.config"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.user.provider.missing"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.user.provider.in_memory"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.user.provider.ldap"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.user.provider.chain"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.logout_listener"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.logout.listener.session"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.logout.listener.clear_site_data"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.logout.listener.cookie_clearing"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.logout.listener.default"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authentication.listener.abstract"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authentication.custom_success_handler"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authentication.success_handler"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authentication.custom_failure_handler"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authentication.failure_handler"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.exception_listener"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authentication.switchuser_listener"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.manager"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.firewall.authenticator"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.listener.user_provider.abstract"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.listener.user_checker"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.listener.session"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.listener.login_throttling"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.http_basic"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.form_login"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.json_login"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.x509"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.remote_user"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.access_token"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.authenticator.access_token.chain_extractor"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.access_token_handler.oidc_user_info.http_client"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.access_token_handler.oidc_user_info"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.access_token_handler.oidc"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.access_token_handler.oidc.jwk"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.access_token_handler.oidc.jwkset"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "security.access_token_handler.oidc.signature"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "lexik_jwt_authentication.key_loader.abstract"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "lexik_jwt_authentication.security.jwt_authenticator"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "hwi_oauth.authentication.listener.oauth"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "hwi_oauth.context_listener.abstract_token_refresher"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service "hwi_oauth.abstract_resource_ownermap"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service ".instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\CommentRepository"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service ".abstract.instanceof.App\Repository\CommentRepository"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service ".instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\IdRepository"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service ".abstract.instanceof.App\Repository\IdRepository"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service ".instanceof.Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface.0.App\Repository\UserRepository"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveAbstractDefinitionsPass: Removed service ".abstract.instanceof.App\Repository\UserRepository"; reason: abstract. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "http_cache"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "http_cache.store"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "reverse_container"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "process.messenger.process_message_handler"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "console.messenger.application"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "console.messenger.execute_command_handler"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "cache.validator"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "cache.serializer"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "cache.property_info"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "http_client.uri_template_expander.guzzle"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "http_client.uri_template_expander.rize"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "http_client.messenger.ping_webhook_handler"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "mailer.mailer"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "mailer.default_transport"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "mailer.messenger.message_handler"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "type_info.type_context_factory"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "type_info.resolver"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "type_info.resolver.reflection_type"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "type_info.resolver.reflection_parameter"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "type_info.resolver.reflection_property"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "type_info.resolver.reflection_return"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "property_info"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service ".cache_connection.MfCypIA"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service ".cache_connection.8kvDmRs"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "session.storage.factory.php_bridge"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "session.storage.factory.mock_file"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "session.handler.native_file"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "session.abstract_handler"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "session.marshaller"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "mime_types"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "maker.php_compat_util"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "maker.maker.make_functional_test"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "maker.maker.make_subscriber"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "maker.maker.make_unit_test"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "data_collector.doctrine"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.dbal.well_known_schema_asset_filter"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.dbal.default_schema_manager_factory"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service ".1_ServiceLocator~SH57ROo"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.orm.validator.unique"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.orm.validator_initializer"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.orm.listeners.resolve_target_entity"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.orm.naming_strategy.default"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.orm.naming_strategy.underscore"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.orm.quote_strategy.ansi"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.orm.default_entity_manager.property_info_extractor"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.migrations.connection_loader"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.migrations.em_loader"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "doctrine.migrations.connection_registry_loader"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.helper"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.authentication.session_strategy_noop"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.user_checker_locator"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.role_hierarchy"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.validator.user_password"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "cache.security_expression_language"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.context_listener"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.firewall.event_dispatcher_locator"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.authenticator.managers_locator"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.user_authenticator"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_extractor.header"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_extractor.query_string"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_extractor.request_body"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.algorithm_manager_factory"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.ES256"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.ES384"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.ES512"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.RS256"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.RS384"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.RS512"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.PS256"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.PS384"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.access_token_handler.oidc.signature.PS512"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "data_collector.security"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.user_checker.chain.main"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.payload_enrichment.random_jti_enrichment"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.handler.authentication_success"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.handler.authentication_failure"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.extractor.chain_extractor"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.extractor.authorization_header_extractor"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.extractor.query_parameter_extractor"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.extractor.cookie_extractor"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.extractor.split_cookie_extractor"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "lexik_jwt_authentication.security.jwt_user_provider"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "twig.loader.chain"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "data_collector.twig"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "twig.extension.htmlsanitizer"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "twig.extension.weblink"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "twig.runtime.serializer"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "twig.extension.serializer"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.authentication.provider.oauth"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.authentication.entry_point.oauth"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.user.provider"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.user.provider.entity"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.storage.session"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.authentication.failure_handler"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.http_client"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "hwi_oauth.resource_owner.facebook"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service ".service_locator.7RRTrQH"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "security.ldap_locator"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service ".service_locator.NLcq8cs"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service ".service_locator.PZ0UHmb"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service ".service_locator.NXTTdTW"; reason: unused. +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "clock" to "argument_resolver.datetime". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.error_renderer.html" to "error_controller". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.controller_resolver" to "http_kernel". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.argument_resolver" to "http_kernel". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.GIuJv7e" to "container.get_routing_condition_service". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.va_rxC4" to "fragment.handler". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "uri_signer" to "fragment.uri_generator". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "cache_clearer" to "console.command.cache_clear". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.jkL9kAz" to "console.command.cache_pool_invalidate_tags". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "cache.default_marshaller" to "cache.app". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "http_client.uri_template.inner" to "http_client.uri_template". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "mailer.transport_factory" to "mailer.transports". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "mailer.transports" to "console.command.mailer_test". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.controller_resolver.inner" to "debug.controller_resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.argument_resolver.inner" to "debug.argument_resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.xml" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.yml" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.php" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.glob" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.directory" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.container" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.attribute.directory" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.attribute.file" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.psr4" to "routing.resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.Hu7m0tQ" to "routing.loader.container". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.resolver" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.YAcX0.U.router.cache_warmer" to "router.cache_warmer". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "cache.property_access" to "property_accessor". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "property_info.reflection_extractor" to "property_accessor". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "property_info.reflection_extractor" to "property_accessor". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "secrets.decryption_key" to "secrets.vault". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "container.getenv" to "secrets.decryption_key". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "session.storage.factory.native" to "session.factory". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.zqtiJAI" to "session_listener". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.csrf.token_generator" to "security.csrf.token_manager". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "form.extension" to "form.registry". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "form.resolved_type_factory" to "form.registry". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.9QYCuGc" to "form.extension". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "form.choice_list_factory.default" to "form.choice_list_factory.property_access". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "form.choice_list_factory.property_access" to "form.choice_list_factory.cached". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "form.type_extension.form.request_handler" to "form.type_extension.form.http_foundation". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.autoloader_util" to "maker.file_manager". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.autoloader_finder" to "maker.autoloader_util". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.template_component_generator" to "maker.generator". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.event_registry" to "maker.maker.make_listener". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.user_class_builder" to "maker.maker.make_user". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.dbal.connection_factory.dsn_parser" to "doctrine.dbal.connection_factory". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.dbal.legacy_schema_manager_factory" to "doctrine.dbal.default_connection.configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.dbal.debug_middleware.default" to "doctrine.dbal.default_connection.configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.dbal.idle_connection_middleware.default" to "doctrine.dbal.default_connection.configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.ZSS2D.Y" to "doctrine.dbal.default_connection.event_manager". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.dbal.default_connection.configuration" to "doctrine.dbal.default_connection". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.dbal.connection_factory" to "doctrine.dbal.default_connection". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.oLzz3BH" to "doctrine.orm.container_repository_factory". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "cache.doctrine.orm.default.metadata" to "doctrine.orm.default_configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".doctrine.orm.default_metadata_driver" to "doctrine.orm.default_configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.naming_strategy.underscore_number_aware" to "doctrine.orm.default_configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.quote_strategy.default" to "doctrine.orm.default_configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.typed_field_mapper.default" to "doctrine.orm.default_configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.default_entity_listener_resolver" to "doctrine.orm.default_configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.container_repository_factory" to "doctrine.orm.default_configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.default_configuration" to "doctrine.orm.default_entity_manager". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.default_manager_configurator" to "doctrine.orm.default_entity_manager". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.migrations.configuration_loader" to "doctrine.migrations.dependency_factory". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.migrations.entity_manager_registry_loader" to "doctrine.migrations.dependency_factory". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.migrations.configuration" to "doctrine.migrations.configuration_loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.migrations.storage.table_storage" to "doctrine.migrations.configuration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.mDV6p8L" to "security.token_storage". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.user_password_hasher" to "form.listener.password_hasher". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.impersonate_url_generator" to "twig.extension.security". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.security.access.decision_manager.inner" to "debug.security.access.decision_manager". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".security.request_matcher.kQIRvor" to ".security.request_matcher.gOpgIHx". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.firewall.map.config.dev" to "security.firewall.map.context.dev". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.authentication.session_strategy" to "security.listener.session.main". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.security.firewall.authenticator.main.inner" to "debug.security.firewall.authenticator.main". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.exception_listener.main" to "security.firewall.map.context.main". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.firewall.map.config.main" to "security.firewall.map.context.main". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "lexik_jwt_authentication.payload_enrichment" to "lexik_jwt_authentication.jwt_manager". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.loader.native_filesystem" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.security_csrf" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.logout_url" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.security" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.profiler" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.trans" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.routing" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.yaml" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.debug.stopwatch" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.httpkernel" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.httpfoundation" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.form" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.twig.doctrine_extension" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.extension.debug" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "hwi_oauth.twig.extension.oauth" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.app_variable" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.runtime_loader" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.configurator.environment" to "twig". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.Fuy.CqR.twig.template_cache_warmer" to "twig.template_cache_warmer". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.template_iterator" to "twig.template_cache_warmer". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.profile" to "twig.extension.profiler". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "fragment.handler" to "twig.runtime.httpkernel". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "fragment.uri_generator" to "twig.runtime.httpkernel". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "url_helper" to "twig.extension.httpfoundation". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.QGq_FxH" to "twig.runtime_loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "error_handler.error_renderer.html" to "twig.error_renderer.html". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "twig.mime_body_renderer" to "twig.mailer.message_listener". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.authentication_utils" to "HWI\Bundle\OAuthBundle\Controller\LoginController". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "hwi_oauth.util.domain_whitelist" to "HWI\Bundle\OAuthBundle\Controller\RedirectToServiceController". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_authenticator" to "maker.auto_command.make_auth". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_command" to "maker.auto_command.make_command". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_twig_component" to "maker.auto_command.make_twig_component". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_controller" to "maker.auto_command.make_controller". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_crud" to "maker.auto_command.make_crud". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_docker_database" to "maker.auto_command.make_docker_database". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_entity" to "maker.auto_command.make_entity". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_fixtures" to "maker.auto_command.make_fixtures". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_form" to "maker.auto_command.make_form". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_listener" to "maker.auto_command.make_listener". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_message" to "maker.auto_command.make_message". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_messenger_middleware" to "maker.auto_command.make_messenger_middleware". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_registration_form" to "maker.auto_command.make_registration_form". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_reset_password" to "maker.auto_command.make_reset_password". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_schedule" to "maker.auto_command.make_schedule". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_serializer_encoder" to "maker.auto_command.make_serializer_encoder". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_serializer_normalizer" to "maker.auto_command.make_serializer_normalizer". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_twig_extension" to "maker.auto_command.make_twig_extension". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_test" to "maker.auto_command.make_test". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_validator" to "maker.auto_command.make_validator". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_voter" to "maker.auto_command.make_voter". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_user" to "maker.auto_command.make_user". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_migration" to "maker.auto_command.make_migration". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_stimulus_controller" to "maker.auto_command.make_stimulus_controller". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_form_login" to "maker.auto_command.make_security_form_login". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_custom_authenticator" to "maker.auto_command.make_security_custom". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "maker.maker.make_webhook" to "maker.auto_command.make_webhook". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.security.event_dispatcher.main.inner" to "debug.security.event_dispatcher.main". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.user_value_resolver" to ".debug.value_resolver.security.user_value_resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.security_token_value_resolver" to ".debug.value_resolver.security.security_token_value_resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "doctrine.orm.entity_value_resolver" to ".debug.value_resolver.doctrine.orm.entity_value_resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.backed_enum_resolver" to ".debug.value_resolver.argument_resolver.backed_enum_resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.datetime" to ".debug.value_resolver.argument_resolver.datetime". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.request_attribute" to ".debug.value_resolver.argument_resolver.request_attribute". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.request" to ".debug.value_resolver.argument_resolver.request". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.session" to ".debug.value_resolver.argument_resolver.session". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.service" to ".debug.value_resolver.argument_resolver.service". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.default" to ".debug.value_resolver.argument_resolver.default". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.variadic" to ".debug.value_resolver.argument_resolver.variadic". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.not_tagged_controller" to ".debug.value_resolver.argument_resolver.not_tagged_controller". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_resolver.query_parameter_value_resolver" to ".debug.value_resolver.argument_resolver.query_parameter_value_resolver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".doctrine.orm.default_metadata_driver.inner" to ".doctrine.orm.default_metadata_driver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.BxSdgVt" to ".doctrine.orm.default_metadata_driver". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.access.authenticated_voter" to ".debug.security.voter.security.access.authenticated_voter". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.access.simple_role_voter" to ".debug.security.voter.security.access.simple_role_voter". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.JI6krIU" to ".service_locator.JI6krIU.router.default". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.YAcX0.U" to ".service_locator.YAcX0.U.router.cache_warmer". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.Fuy.CqR" to ".service_locator.Fuy.CqR.twig.template_cache_warmer". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "argument_metadata_factory" to "debug.argument_resolver.inner". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.ccTBFaB" to "debug.argument_resolver.inner". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "security.authenticator.manager.main" to "debug.security.firewall.authenticator.main.inner". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.iQKYtlE" to "console.command_loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service ".service_locator.JI6krIU.router.default" to "router". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "parameter_bag" to "router". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "config_cache_factory" to "router". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "lexik_jwt_authentication.jws_provider.lcobucci" to "lexik_jwt_authentication.encoder". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "debug.event_dispatcher.inner" to "event_dispatcher". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.attribute" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.attribute" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "routing.loader.attribute" to "routing.loader". +Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "file_locator" to "routing.loader". +Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass: Tag "container.error" was defined on service(s) "argument_resolver.request_payload", but was never used. Did you mean "container.preload", "container.decorator", "container.stack"? +Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass: Tag "container.decorator" was defined on service(s) "http_client.uri_template", "debug.security.access.decision_manager", "debug.security.firewall.authenticator.main", "debug.security.event_dispatcher.main", "event_dispatcher", but was never used. Did you mean "container.error"? \ No newline at end of file diff --git a/var/cache/dev/App_KernelDevDebugContainerDeprecations.log b/var/cache/dev/App_KernelDevDebugContainerDeprecations.log new file mode 100644 index 0000000..b3bb271 --- /dev/null +++ b/var/cache/dev/App_KernelDevDebugContainerDeprecations.log @@ -0,0 +1 @@ +a:1:{i:0;a:6:{s:4:"type";i:16384;s:7:"message";s:337:"The "Symfony\Component\HttpKernel\DependencyInjection\Extension" class is considered internal since Symfony 7.1, to be deprecated in 8.1; use Symfony\Component\DependencyInjection\Extension\Extension instead. It may change without further notice. You should not use it from "HWI\Bundle\OAuthBundle\DependencyInjection\HWIOAuthExtension".";s:4:"file";s:86:"/home/skylar/Projects/mycomments-api/vendor/symfony/error-handler/DebugClassLoader.php";s:4:"line";i:341;s:5:"trace";a:1:{i:0;a:3:{s:4:"file";s:81:"/home/skylar/Projects/mycomments-api/vendor/symfony/http-kernel/Bundle/Bundle.php";s:4:"line";i:143;s:8:"function";s:12:"class_exists";}}s:5:"count";i:1;}} \ No newline at end of file diff --git a/var/cache/dev/Container6Szx89D/App_KernelDevDebugContainer.php b/var/cache/dev/Container6Szx89D/App_KernelDevDebugContainer.php new file mode 100644 index 0000000..5cc9eae --- /dev/null +++ b/var/cache/dev/Container6Szx89D/App_KernelDevDebugContainer.php @@ -0,0 +1,1031 @@ +targetDir = \dirname($containerDir); + $this->parameters = $this->getDefaultParameters(); + + $this->services = $this->privates = []; + $this->syntheticIds = [ + 'kernel' => true, + ]; + $this->methodMap = [ + 'debug.stopwatch' => 'getDebug_StopwatchService', + 'event_dispatcher' => 'getEventDispatcherService', + 'http_kernel' => 'getHttpKernelService', + 'request_stack' => 'getRequestStackService', + 'router' => 'getRouterService', + ]; + $this->fileMap = [ + 'HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\ConnectController' => 'getConnectControllerService', + 'HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\RegisterController' => 'getRegisterControllerService', + 'HWI\\Bundle\\OAuthBundle\\Controller\\LoginController' => 'getLoginControllerService', + 'HWI\\Bundle\\OAuthBundle\\Controller\\RedirectToServiceController' => 'getRedirectToServiceControllerService', + 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController' => 'getRedirectControllerService', + 'Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController' => 'getTemplateControllerService', + 'cache.app' => 'getCache_AppService', + 'cache.app_clearer' => 'getCache_AppClearerService', + 'cache.global_clearer' => 'getCache_GlobalClearerService', + 'cache.security_is_csrf_token_valid_attribute_expression_language' => 'getCache_SecurityIsCsrfTokenValidAttributeExpressionLanguageService', + 'cache.security_is_granted_attribute_expression_language' => 'getCache_SecurityIsGrantedAttributeExpressionLanguageService', + 'cache.system' => 'getCache_SystemService', + 'cache.system_clearer' => 'getCache_SystemClearerService', + 'cache_warmer' => 'getCacheWarmerService', + 'console.command_loader' => 'getConsole_CommandLoaderService', + 'container.env_var_processors_locator' => 'getContainer_EnvVarProcessorsLocatorService', + 'container.get_routing_condition_service' => 'getContainer_GetRoutingConditionServiceService', + 'debug.error_handler_configurator' => 'getDebug_ErrorHandlerConfiguratorService', + 'doctrine' => 'getDoctrineService', + 'doctrine.dbal.default_connection' => 'getDoctrine_Dbal_DefaultConnectionService', + 'doctrine.orm.default_entity_manager' => 'getDoctrine_Orm_DefaultEntityManagerService', + 'error_controller' => 'getErrorControllerService', + 'hwi_oauth.user_checker' => 'getHwiOauth_UserCheckerService', + 'lexik_jwt_authentication.encoder' => 'getLexikJwtAuthentication_EncoderService', + 'lexik_jwt_authentication.generate_token_command' => 'getLexikJwtAuthentication_GenerateTokenCommandService', + 'lexik_jwt_authentication.jwt_manager' => 'getLexikJwtAuthentication_JwtManagerService', + 'lexik_jwt_authentication.key_loader' => 'getLexikJwtAuthentication_KeyLoaderService', + 'routing.loader' => 'getRouting_LoaderService', + 'services_resetter' => 'getServicesResetterService', + ]; + $this->aliases = [ + 'App\\Kernel' => 'kernel', + 'database_connection' => 'doctrine.dbal.default_connection', + 'doctrine.orm.entity_manager' => 'doctrine.orm.default_entity_manager', + ]; + + $this->privates['service_container'] = static function ($container) { + include_once \dirname(__DIR__, 4).'/vendor/symfony/event-dispatcher/EventSubscriberInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/ResponseListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/LocaleListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/ErrorListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/CacheAttributeListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/runtime/RunnerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/runtime/Runner/Symfony/ResponseRunner.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/runtime/RuntimeInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/runtime/GenericRuntime.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/runtime/SymfonyRuntime.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/HttpKernelInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/TerminableInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/HttpKernel.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Controller/ControllerResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/framework-bundle/Controller/ControllerResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Controller/ArgumentResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/container/src/ContainerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/service-contracts/ServiceProviderInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/service-contracts/ServiceCollectionInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/service-contracts/ServiceLocatorTrait.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/dependency-injection/ServiceLocator.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-foundation/RequestStack.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/LocaleAwareListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/service-contracts/ResetInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/stopwatch/Stopwatch.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/routing/RequestContext.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/RouterListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/EventListener/SessionListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-csrf/TokenStorage/TokenStorageInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-csrf/TokenStorage/ClearableTokenStorageInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-csrf/CsrfTokenManagerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-csrf/CsrfTokenManager.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-csrf/TokenGenerator/TokenGeneratorInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-csrf/TokenGenerator/UriSafeTokenGenerator.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Listener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authorization/AuthorizationCheckerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authorization/AuthorizationChecker.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorageInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/service-contracts/ServiceSubscriberInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authentication/Token/Storage/UsageTrackingTokenStorage.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorage.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authentication/AuthenticationTrustResolverInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authentication/AuthenticationTrustResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/FirewallMapInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-bundle/Security/FirewallMap.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/Logout/LogoutUrlGenerator.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/EventListener/IsGrantedAttributeListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authorization/AccessDecisionManagerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authorization/TraceableAccessDecisionManager.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authorization/AccessDecisionManager.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authorization/Strategy/AccessDecisionStrategyInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-core/Authorization/Strategy/AffirmativeStrategy.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/Firewall.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-bundle/EventListener/FirewallListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-bundle/Debug/TraceableFirewallListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/Firewall/FirewallListenerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/Firewall/AbstractListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/Firewall/ContextListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/event-dispatcher/EventDispatcherInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/event-dispatcher/EventDispatcher.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/security-http/EventListener/IsCsrfTokenValidAttributeListener.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/log/src/LoggerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/log/src/LoggerTrait.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/log/src/AbstractLogger.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Log/Logger.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/routing/RequestContextAwareInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/routing/Matcher/UrlMatcherInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/routing/Generator/UrlGeneratorInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/routing/RouterInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/routing/Matcher/RequestMatcherInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/routing/Router.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/framework-bundle/Routing/Router.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/dependency-injection/ParameterBag/ParameterBag.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/dependency-injection/ParameterBag/ContainerBag.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/config/ConfigCacheFactoryInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php'; + }; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return require $this->containerDir.\DIRECTORY_SEPARATOR.'removed-ids.php'; + } + + protected function load($file, $lazyLoad = true): mixed + { + if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { + return $class::do($this, $lazyLoad); + } + + if ('.' === $file[-4]) { + $class = substr($class, 0, -4); + } else { + $file .= '.php'; + } + + $service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file; + + return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service; + } + + protected function createProxy($class, \Closure $factory) + { + class_exists($class, false) || require __DIR__.'/'.$class.'.php'; + + return $factory(); + } + + /** + * Gets the public 'debug.stopwatch' shared service. + * + * @return \Symfony\Component\Stopwatch\Stopwatch + */ + protected static function getDebug_StopwatchService($container) + { + return $container->services['debug.stopwatch'] = new \Symfony\Component\Stopwatch\Stopwatch(true); + } + + /** + * Gets the public 'event_dispatcher' shared service. + * + * @return \Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher + */ + protected static function getEventDispatcherService($container) + { + $container->services['event_dispatcher'] = $instance = new \Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher(new \Symfony\Component\EventDispatcher\EventDispatcher(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true)), ($container->privates['logger'] ?? self::getLoggerService($container)), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack())); + + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.user_provider', class: 'Symfony\\Component\\Security\\Http\\EventListener\\UserProviderListener')] fn () => ($container->privates['security.listener.user_provider'] ?? $container->load('getSecurity_Listener_UserProviderService')), 'checkPassport'], 1024); + $instance->addListener('kernel.response', [#[\Closure(name: 'security.context_listener.0', class: 'Symfony\\Component\\Security\\Http\\Firewall\\ContextListener')] fn () => ($container->privates['security.context_listener.0'] ?? self::getSecurity_ContextListener_0Service($container)), 'onKernelResponse'], 0); + $instance->addListener('kernel.response', [#[\Closure(name: 'response_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener')] fn () => ($container->privates['response_listener'] ??= new \Symfony\Component\HttpKernel\EventListener\ResponseListener('UTF-8', false)), 'onKernelResponse'], 0); + $instance->addListener('kernel.request', [#[\Closure(name: 'locale_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener')] fn () => ($container->privates['locale_listener'] ?? self::getLocaleListenerService($container)), 'setDefaultLocale'], 100); + $instance->addListener('kernel.request', [#[\Closure(name: 'locale_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener')] fn () => ($container->privates['locale_listener'] ?? self::getLocaleListenerService($container)), 'onKernelRequest'], 16); + $instance->addListener('kernel.finish_request', [#[\Closure(name: 'locale_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener')] fn () => ($container->privates['locale_listener'] ?? self::getLocaleListenerService($container)), 'onKernelFinishRequest'], 0); + $instance->addListener('kernel.request', [#[\Closure(name: 'validate_request_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener')] fn () => ($container->privates['validate_request_listener'] ??= new \Symfony\Component\HttpKernel\EventListener\ValidateRequestListener()), 'onKernelRequest'], 256); + $instance->addListener('kernel.response', [#[\Closure(name: 'disallow_search_engine_index_response_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener')] fn () => ($container->privates['disallow_search_engine_index_response_listener'] ??= new \Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener()), 'onResponse'], -255); + $instance->addListener('kernel.controller_arguments', [#[\Closure(name: 'exception_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener')] fn () => ($container->privates['exception_listener'] ?? self::getExceptionListenerService($container)), 'onControllerArguments'], 0); + $instance->addListener('kernel.exception', [#[\Closure(name: 'exception_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener')] fn () => ($container->privates['exception_listener'] ?? self::getExceptionListenerService($container)), 'logKernelException'], 0); + $instance->addListener('kernel.exception', [#[\Closure(name: 'exception_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener')] fn () => ($container->privates['exception_listener'] ?? self::getExceptionListenerService($container)), 'onKernelException'], -128); + $instance->addListener('kernel.response', [#[\Closure(name: 'exception_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener')] fn () => ($container->privates['exception_listener'] ?? self::getExceptionListenerService($container)), 'removeCspHeader'], -128); + $instance->addListener('kernel.controller_arguments', [#[\Closure(name: 'controller.cache_attribute_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\CacheAttributeListener')] fn () => ($container->privates['controller.cache_attribute_listener'] ??= new \Symfony\Component\HttpKernel\EventListener\CacheAttributeListener()), 'onKernelControllerArguments'], 10); + $instance->addListener('kernel.response', [#[\Closure(name: 'controller.cache_attribute_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\CacheAttributeListener')] fn () => ($container->privates['controller.cache_attribute_listener'] ??= new \Symfony\Component\HttpKernel\EventListener\CacheAttributeListener()), 'onKernelResponse'], -10); + $instance->addListener('kernel.request', [#[\Closure(name: 'locale_aware_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener')] fn () => ($container->privates['locale_aware_listener'] ?? self::getLocaleAwareListenerService($container)), 'onKernelRequest'], 15); + $instance->addListener('kernel.finish_request', [#[\Closure(name: 'locale_aware_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener')] fn () => ($container->privates['locale_aware_listener'] ?? self::getLocaleAwareListenerService($container)), 'onKernelFinishRequest'], -15); + $instance->addListener('console.error', [#[\Closure(name: 'console.error_listener', class: 'Symfony\\Component\\Console\\EventListener\\ErrorListener')] fn () => ($container->privates['console.error_listener'] ?? $container->load('getConsole_ErrorListenerService')), 'onConsoleError'], -128); + $instance->addListener('console.terminate', [#[\Closure(name: 'console.error_listener', class: 'Symfony\\Component\\Console\\EventListener\\ErrorListener')] fn () => ($container->privates['console.error_listener'] ?? $container->load('getConsole_ErrorListenerService')), 'onConsoleTerminate'], -128); + $instance->addListener('console.error', [#[\Closure(name: 'console.suggest_missing_package_subscriber', class: 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SuggestMissingPackageSubscriber')] fn () => ($container->privates['console.suggest_missing_package_subscriber'] ??= new \Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber()), 'onConsoleError'], 0); + $instance->addListener('Symfony\\Component\\Mailer\\Event\\MessageEvent', [#[\Closure(name: 'mailer.envelope_listener', class: 'Symfony\\Component\\Mailer\\EventListener\\EnvelopeListener')] fn () => ($container->privates['mailer.envelope_listener'] ??= new \Symfony\Component\Mailer\EventListener\EnvelopeListener(NULL, NULL, [])), 'onMessage'], -255); + $instance->addListener('Symfony\\Component\\Mailer\\Event\\MessageEvent', [#[\Closure(name: 'mailer.message_logger_listener', class: 'Symfony\\Component\\Mailer\\EventListener\\MessageLoggerListener')] fn () => ($container->privates['mailer.message_logger_listener'] ??= new \Symfony\Component\Mailer\EventListener\MessageLoggerListener()), 'onMessage'], -255); + $instance->addListener('Symfony\\Component\\Mailer\\Event\\MessageEvent', [#[\Closure(name: 'mailer.messenger_transport_listener', class: 'Symfony\\Component\\Mailer\\EventListener\\MessengerTransportListener')] fn () => ($container->privates['mailer.messenger_transport_listener'] ??= new \Symfony\Component\Mailer\EventListener\MessengerTransportListener()), 'onMessage'], 0); + $instance->addListener('kernel.request', [#[\Closure(name: 'debug.debug_handlers_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener')] fn () => ($container->privates['debug.debug_handlers_listener'] ??= new \Symfony\Component\HttpKernel\EventListener\DebugHandlersListener(NULL, $container->getEnv('bool:default::key:web:default:kernel.runtime_mode:'))), 'configure'], 2048); + $instance->addListener('console.command', [#[\Closure(name: 'debug.debug_handlers_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener')] fn () => ($container->privates['debug.debug_handlers_listener'] ??= new \Symfony\Component\HttpKernel\EventListener\DebugHandlersListener(NULL, $container->getEnv('bool:default::key:web:default:kernel.runtime_mode:'))), 'configure'], 2048); + $instance->addListener('kernel.request', [#[\Closure(name: 'router_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener')] fn () => ($container->privates['router_listener'] ?? self::getRouterListenerService($container)), 'onKernelRequest'], 32); + $instance->addListener('kernel.finish_request', [#[\Closure(name: 'router_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener')] fn () => ($container->privates['router_listener'] ?? self::getRouterListenerService($container)), 'onKernelFinishRequest'], 0); + $instance->addListener('kernel.exception', [#[\Closure(name: 'router_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener')] fn () => ($container->privates['router_listener'] ?? self::getRouterListenerService($container)), 'onKernelException'], -64); + $instance->addListener('kernel.request', [#[\Closure(name: 'session_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener')] fn () => ($container->privates['session_listener'] ?? self::getSessionListenerService($container)), 'onKernelRequest'], 128); + $instance->addListener('kernel.response', [#[\Closure(name: 'session_listener', class: 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener')] fn () => ($container->privates['session_listener'] ?? self::getSessionListenerService($container)), 'onKernelResponse'], -1000); + $instance->addListener('console.error', [#[\Closure(name: 'maker.console_error_listener', class: 'Symfony\\Bundle\\MakerBundle\\Event\\ConsoleErrorSubscriber')] fn () => ($container->privates['maker.console_error_listener'] ??= new \Symfony\Bundle\MakerBundle\Event\ConsoleErrorSubscriber()), 'onConsoleError'], 0); + $instance->addListener('console.terminate', [#[\Closure(name: 'maker.console_error_listener', class: 'Symfony\\Bundle\\MakerBundle\\Event\\ConsoleErrorSubscriber')] fn () => ($container->privates['maker.console_error_listener'] ??= new \Symfony\Bundle\MakerBundle\Event\ConsoleErrorSubscriber()), 'onConsoleTerminate'], 0); + $instance->addListener('kernel.request', [#[\Closure(name: 'doctrine.dbal.idle_connection_listener', class: 'Symfony\\Bridge\\Doctrine\\Middleware\\IdleConnection\\Listener')] fn () => ($container->privates['doctrine.dbal.idle_connection_listener'] ?? self::getDoctrine_Dbal_IdleConnectionListenerService($container)), 'onKernelRequest'], 192); + $instance->addListener('kernel.controller_arguments', [#[\Closure(name: 'controller.is_granted_attribute_listener', class: 'Symfony\\Component\\Security\\Http\\EventListener\\IsGrantedAttributeListener')] fn () => ($container->privates['controller.is_granted_attribute_listener'] ?? self::getController_IsGrantedAttributeListenerService($container)), 'onKernelControllerArguments'], 20); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.check_authenticator_credentials', class: 'Symfony\\Component\\Security\\Http\\EventListener\\CheckCredentialsListener')] fn () => ($container->privates['security.listener.check_authenticator_credentials'] ?? $container->load('getSecurity_Listener_CheckAuthenticatorCredentialsService')), 'checkPassport'], 0); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\LoginSuccessEvent', [#[\Closure(name: 'security.listener.password_migrating', class: 'Symfony\\Component\\Security\\Http\\EventListener\\PasswordMigratingListener')] fn () => ($container->privates['security.listener.password_migrating'] ?? $container->load('getSecurity_Listener_PasswordMigratingService')), 'onLoginSuccess'], 0); + $instance->addListener('debug.security.authorization.vote', [#[\Closure(name: 'debug.security.voter.vote_listener', class: 'Symfony\\Bundle\\SecurityBundle\\EventListener\\VoteListener')] fn () => ($container->privates['debug.security.voter.vote_listener'] ?? $container->load('getDebug_Security_Voter_VoteListenerService')), 'onVoterVote'], 0); + $instance->addListener('kernel.request', [#[\Closure(name: 'debug.security.firewall', class: 'Symfony\\Bundle\\SecurityBundle\\Debug\\TraceableFirewallListener')] fn () => ($container->privates['debug.security.firewall'] ?? self::getDebug_Security_FirewallService($container)), 'configureLogoutUrlGenerator'], 8); + $instance->addListener('kernel.request', [#[\Closure(name: 'debug.security.firewall', class: 'Symfony\\Bundle\\SecurityBundle\\Debug\\TraceableFirewallListener')] fn () => ($container->privates['debug.security.firewall'] ?? self::getDebug_Security_FirewallService($container)), 'onKernelRequest'], 8); + $instance->addListener('kernel.finish_request', [#[\Closure(name: 'debug.security.firewall', class: 'Symfony\\Bundle\\SecurityBundle\\Debug\\TraceableFirewallListener')] fn () => ($container->privates['debug.security.firewall'] ?? self::getDebug_Security_FirewallService($container)), 'onKernelFinishRequest'], 0); + $instance->addListener('kernel.view', [#[\Closure(name: 'controller.template_attribute_listener', class: 'Symfony\\Bridge\\Twig\\EventListener\\TemplateAttributeListener')] fn () => ($container->privates['controller.template_attribute_listener'] ?? $container->load('getController_TemplateAttributeListenerService')), 'onKernelView'], -128); + $instance->addListener('Symfony\\Component\\Mailer\\Event\\MessageEvent', [#[\Closure(name: 'twig.mailer.message_listener', class: 'Symfony\\Component\\Mailer\\EventListener\\MessageListener')] fn () => ($container->privates['twig.mailer.message_listener'] ?? $container->load('getTwig_Mailer_MessageListenerService')), 'onMessage'], 0); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.csrf_protection', class: 'Symfony\\Component\\Security\\Http\\EventListener\\CsrfProtectionListener')] fn () => ($container->privates['security.listener.csrf_protection'] ?? $container->load('getSecurity_Listener_CsrfProtectionService')), 'checkPassport'], 512); + $instance->addListener('kernel.controller_arguments', [#[\Closure(name: 'controller.is_csrf_token_valid_attribute_listener', class: 'Symfony\\Component\\Security\\Http\\EventListener\\IsCsrfTokenValidAttributeListener')] fn () => ($container->privates['controller.is_csrf_token_valid_attribute_listener'] ?? self::getController_IsCsrfTokenValidAttributeListenerService($container)), 'onKernelControllerArguments'], 25); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\LogoutEvent', [#[\Closure(name: 'security.logout.listener.csrf_token_clearing', class: 'Symfony\\Component\\Security\\Http\\EventListener\\CsrfTokenClearingLogoutListener')] fn () => ($container->privates['security.logout.listener.csrf_token_clearing'] ?? $container->load('getSecurity_Logout_Listener_CsrfTokenClearingService')), 'onLogout'], 0); + + return $instance; + } + + /** + * Gets the public 'http_kernel' shared service. + * + * @return \Symfony\Component\HttpKernel\HttpKernel + */ + protected static function getHttpKernelService($container) + { + $a = ($container->services['event_dispatcher'] ?? self::getEventDispatcherService($container)); + + if (isset($container->services['http_kernel'])) { + return $container->services['http_kernel']; + } + $b = new \Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver($container, ($container->privates['logger'] ?? self::getLoggerService($container))); + $b->allowControllers(['Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController']); + $b->allowControllers(['App\\Kernel', 'Doctrine\\Bundle\\DoctrineBundle\\Controller\\ProfilerController']); + $c = ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true)); + + return $container->services['http_kernel'] = new \Symfony\Component\HttpKernel\HttpKernel($a, new \Symfony\Component\HttpKernel\Controller\TraceableControllerResolver($b, $c), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), new \Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver(new \Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory(), new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['.debug.value_resolver.security.user_value_resolver'] ?? $container->load('get_Debug_ValueResolver_Security_UserValueResolverService')); + yield 1 => ($container->privates['.debug.value_resolver.security.security_token_value_resolver'] ?? $container->load('get_Debug_ValueResolver_Security_SecurityTokenValueResolverService')); + yield 2 => ($container->privates['.debug.value_resolver.doctrine.orm.entity_value_resolver'] ?? $container->load('get_Debug_ValueResolver_Doctrine_Orm_EntityValueResolverService')); + yield 3 => ($container->privates['.debug.value_resolver.argument_resolver.backed_enum_resolver'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_BackedEnumResolverService')); + yield 4 => ($container->privates['.debug.value_resolver.argument_resolver.datetime'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_DatetimeService')); + yield 5 => ($container->privates['.debug.value_resolver.argument_resolver.request_attribute'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_RequestAttributeService')); + yield 6 => ($container->privates['.debug.value_resolver.argument_resolver.request'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_RequestService')); + yield 7 => ($container->privates['.debug.value_resolver.argument_resolver.session'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_SessionService')); + yield 8 => ($container->privates['.debug.value_resolver.argument_resolver.service'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_ServiceService')); + yield 9 => ($container->privates['.debug.value_resolver.argument_resolver.default'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_DefaultService')); + yield 10 => ($container->privates['.debug.value_resolver.argument_resolver.variadic'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_VariadicService')); + yield 11 => ($container->privates['.debug.value_resolver.argument_resolver.not_tagged_controller'] ?? $container->load('get_Debug_ValueResolver_ArgumentResolver_NotTaggedControllerService')); + }, 12), new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestPayloadValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.request_payload', 'get_Debug_ValueResolver_ArgumentResolver_RequestPayloadService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\QueryParameterValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.query_parameter_value_resolver', 'get_Debug_ValueResolver_ArgumentResolver_QueryParameterValueResolverService', true], + 'Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver' => ['privates', '.debug.value_resolver.security.user_value_resolver', 'get_Debug_ValueResolver_Security_UserValueResolverService', true], + 'Symfony\\Component\\Security\\Http\\Controller\\SecurityTokenValueResolver' => ['privates', '.debug.value_resolver.security.security_token_value_resolver', 'get_Debug_ValueResolver_Security_SecurityTokenValueResolverService', true], + 'Symfony\\Bridge\\Doctrine\\ArgumentResolver\\EntityValueResolver' => ['privates', '.debug.value_resolver.doctrine.orm.entity_value_resolver', 'get_Debug_ValueResolver_Doctrine_Orm_EntityValueResolverService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\BackedEnumValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.backed_enum_resolver', 'get_Debug_ValueResolver_ArgumentResolver_BackedEnumResolverService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DateTimeValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.datetime', 'get_Debug_ValueResolver_ArgumentResolver_DatetimeService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.request_attribute', 'get_Debug_ValueResolver_ArgumentResolver_RequestAttributeService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.request', 'get_Debug_ValueResolver_ArgumentResolver_RequestService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.session', 'get_Debug_ValueResolver_ArgumentResolver_SessionService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.service', 'get_Debug_ValueResolver_ArgumentResolver_ServiceService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.default', 'get_Debug_ValueResolver_ArgumentResolver_DefaultService', true], + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => ['privates', '.debug.value_resolver.argument_resolver.variadic', 'get_Debug_ValueResolver_ArgumentResolver_VariadicService', true], + 'argument_resolver.not_tagged_controller' => ['privates', '.debug.value_resolver.argument_resolver.not_tagged_controller', 'get_Debug_ValueResolver_ArgumentResolver_NotTaggedControllerService', true], + ], [ + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestPayloadValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\QueryParameterValueResolver' => '?', + 'Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver' => '?', + 'Symfony\\Component\\Security\\Http\\Controller\\SecurityTokenValueResolver' => '?', + 'Symfony\\Bridge\\Doctrine\\ArgumentResolver\\EntityValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\BackedEnumValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DateTimeValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => '?', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => '?', + 'argument_resolver.not_tagged_controller' => '?', + ])), $c), true); + } + + /** + * Gets the public 'request_stack' shared service. + * + * @return \Symfony\Component\HttpFoundation\RequestStack + */ + protected static function getRequestStackService($container) + { + return $container->services['request_stack'] = new \Symfony\Component\HttpFoundation\RequestStack(); + } + + /** + * Gets the public 'router' shared service. + * + * @return \Symfony\Bundle\FrameworkBundle\Routing\Router + */ + protected static function getRouterService($container) + { + $container->services['router'] = $instance = new \Symfony\Bundle\FrameworkBundle\Routing\Router((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'routing.loader' => ['services', 'routing.loader', 'getRouting_LoaderService', true], + ], [ + 'routing.loader' => 'Symfony\\Component\\Config\\Loader\\LoaderInterface', + ]))->withContext('router.default', $container), 'kernel::loadRoutes', ['cache_dir' => $container->targetDir.'', 'debug' => true, 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator', 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper', 'matcher_class' => 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher', 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper', 'strict_requirements' => true, 'resource_type' => 'service'], ($container->privates['router.request_context'] ?? self::getRouter_RequestContextService($container)), new \Symfony\Component\DependencyInjection\ParameterBag\ContainerBag($container), ($container->privates['logger'] ?? self::getLoggerService($container)), 'en'); + + $instance->setConfigCacheFactory(new \Symfony\Component\Config\ResourceCheckerConfigCacheFactory(new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['dependency_injection.config.container_parameters_resource_checker'] ??= new \Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker($container)); + yield 1 => ($container->privates['config.resource.self_checking_resource_checker'] ??= new \Symfony\Component\Config\Resource\SelfCheckingResourceChecker()); + }, 2))); + + return $instance; + } + + /** + * Gets the private '.service_locator.bsoXAxw' shared service. + * + * @return \Symfony\Component\DependencyInjection\ServiceLocator + */ + protected static function get_ServiceLocator_BsoXAxwService($container) + { + return $container->privates['.service_locator.bsoXAxw'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'security.firewall.map.context.dev' => ['privates', 'security.firewall.map.context.dev', 'getSecurity_Firewall_Map_Context_DevService', true], + 'security.firewall.map.context.main' => ['privates', 'security.firewall.map.context.main', 'getSecurity_Firewall_Map_Context_MainService', true], + ], [ + 'security.firewall.map.context.dev' => '?', + 'security.firewall.map.context.main' => '?', + ]); + } + + /** + * Gets the private 'controller.is_csrf_token_valid_attribute_listener' shared service. + * + * @return \Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener + */ + protected static function getController_IsCsrfTokenValidAttributeListenerService($container) + { + return $container->privates['controller.is_csrf_token_valid_attribute_listener'] = new \Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener(($container->privates['security.csrf.token_manager'] ?? self::getSecurity_Csrf_TokenManagerService($container)), NULL); + } + + /** + * Gets the private 'controller.is_granted_attribute_listener' shared service. + * + * @return \Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener + */ + protected static function getController_IsGrantedAttributeListenerService($container) + { + $a = ($container->privates['security.authorization_checker'] ?? self::getSecurity_AuthorizationCheckerService($container)); + + if (isset($container->privates['controller.is_granted_attribute_listener'])) { + return $container->privates['controller.is_granted_attribute_listener']; + } + + return $container->privates['controller.is_granted_attribute_listener'] = new \Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener($a, NULL); + } + + /** + * Gets the private 'debug.security.access.decision_manager' shared service. + * + * @return \Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager + */ + protected static function getDebug_Security_Access_DecisionManagerService($container) + { + return $container->privates['debug.security.access.decision_manager'] = new \Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager(new \Symfony\Component\Security\Core\Authorization\AccessDecisionManager(new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['.debug.security.voter.security.access.authenticated_voter'] ?? $container->load('get_Debug_Security_Voter_Security_Access_AuthenticatedVoterService')); + yield 1 => ($container->privates['.debug.security.voter.security.access.simple_role_voter'] ?? $container->load('get_Debug_Security_Voter_Security_Access_SimpleRoleVoterService')); + }, 2), new \Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy(false))); + } + + /** + * Gets the private 'debug.security.event_dispatcher.main' shared service. + * + * @return \Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher + */ + protected static function getDebug_Security_EventDispatcher_MainService($container) + { + $container->privates['debug.security.event_dispatcher.main'] = $instance = new \Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher(new \Symfony\Component\EventDispatcher\EventDispatcher(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true)), ($container->privates['logger'] ?? self::getLoggerService($container)), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack())); + + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.main.user_provider', class: 'Symfony\\Component\\Security\\Http\\EventListener\\UserProviderListener')] fn () => ($container->privates['security.listener.main.user_provider'] ?? $container->load('getSecurity_Listener_Main_UserProviderService')), 'checkPassport'], 2048); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\LoginSuccessEvent', [#[\Closure(name: 'security.listener.session.main', class: 'Symfony\\Component\\Security\\Http\\EventListener\\SessionStrategyListener')] fn () => ($container->privates['security.listener.session.main'] ?? $container->load('getSecurity_Listener_Session_MainService')), 'onSuccessfulLogin'], 0); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.user_checker.main', class: 'Symfony\\Component\\Security\\Http\\EventListener\\UserCheckerListener')] fn () => ($container->privates['security.listener.user_checker.main'] ?? $container->load('getSecurity_Listener_UserChecker_MainService')), 'preCheckCredentials'], 256); + $instance->addListener('security.authentication.success', [#[\Closure(name: 'security.listener.user_checker.main', class: 'Symfony\\Component\\Security\\Http\\EventListener\\UserCheckerListener')] fn () => ($container->privates['security.listener.user_checker.main'] ?? $container->load('getSecurity_Listener_UserChecker_MainService')), 'postCheckCredentials'], 256); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.user_provider', class: 'Symfony\\Component\\Security\\Http\\EventListener\\UserProviderListener')] fn () => ($container->privates['security.listener.user_provider'] ?? $container->load('getSecurity_Listener_UserProviderService')), 'checkPassport'], 1024); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.check_authenticator_credentials', class: 'Symfony\\Component\\Security\\Http\\EventListener\\CheckCredentialsListener')] fn () => ($container->privates['security.listener.check_authenticator_credentials'] ?? $container->load('getSecurity_Listener_CheckAuthenticatorCredentialsService')), 'checkPassport'], 0); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\LoginSuccessEvent', [#[\Closure(name: 'security.listener.password_migrating', class: 'Symfony\\Component\\Security\\Http\\EventListener\\PasswordMigratingListener')] fn () => ($container->privates['security.listener.password_migrating'] ?? $container->load('getSecurity_Listener_PasswordMigratingService')), 'onLoginSuccess'], 0); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent', [#[\Closure(name: 'security.listener.csrf_protection', class: 'Symfony\\Component\\Security\\Http\\EventListener\\CsrfProtectionListener')] fn () => ($container->privates['security.listener.csrf_protection'] ?? $container->load('getSecurity_Listener_CsrfProtectionService')), 'checkPassport'], 512); + $instance->addListener('Symfony\\Component\\Security\\Http\\Event\\LogoutEvent', [#[\Closure(name: 'security.logout.listener.csrf_token_clearing', class: 'Symfony\\Component\\Security\\Http\\EventListener\\CsrfTokenClearingLogoutListener')] fn () => ($container->privates['security.logout.listener.csrf_token_clearing'] ?? $container->load('getSecurity_Logout_Listener_CsrfTokenClearingService')), 'onLogout'], 0); + + return $instance; + } + + /** + * Gets the private 'debug.security.firewall' shared service. + * + * @return \Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener + */ + protected static function getDebug_Security_FirewallService($container) + { + $a = ($container->privates['security.firewall.map'] ?? self::getSecurity_Firewall_MapService($container)); + + if (isset($container->privates['debug.security.firewall'])) { + return $container->privates['debug.security.firewall']; + } + $b = ($container->services['event_dispatcher'] ?? self::getEventDispatcherService($container)); + + if (isset($container->privates['debug.security.firewall'])) { + return $container->privates['debug.security.firewall']; + } + + return $container->privates['debug.security.firewall'] = new \Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener($a, $b, ($container->privates['security.logout_url_generator'] ?? self::getSecurity_LogoutUrlGeneratorService($container))); + } + + /** + * Gets the private 'doctrine.dbal.idle_connection_listener' shared service. + * + * @return \Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener + */ + protected static function getDoctrine_Dbal_IdleConnectionListenerService($container) + { + return $container->privates['doctrine.dbal.idle_connection_listener'] = new \Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener(($container->privates['doctrine.dbal.connection_expiries'] ??= new \ArrayObject()), $container); + } + + /** + * Gets the private 'exception_listener' shared service. + * + * @return \Symfony\Component\HttpKernel\EventListener\ErrorListener + */ + protected static function getExceptionListenerService($container) + { + return $container->privates['exception_listener'] = new \Symfony\Component\HttpKernel\EventListener\ErrorListener('error_controller', ($container->privates['logger'] ?? self::getLoggerService($container)), true, []); + } + + /** + * Gets the private 'locale_aware_listener' shared service. + * + * @return \Symfony\Component\HttpKernel\EventListener\LocaleAwareListener + */ + protected static function getLocaleAwareListenerService($container) + { + return $container->privates['locale_aware_listener'] = new \Symfony\Component\HttpKernel\EventListener\LocaleAwareListener(new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['slugger'] ??= new \Symfony\Component\String\Slugger\AsciiSlugger('en')); + }, 1), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack())); + } + + /** + * Gets the private 'locale_listener' shared service. + * + * @return \Symfony\Component\HttpKernel\EventListener\LocaleListener + */ + protected static function getLocaleListenerService($container) + { + return $container->privates['locale_listener'] = new \Symfony\Component\HttpKernel\EventListener\LocaleListener(($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), 'en', ($container->services['router'] ?? self::getRouterService($container)), false, []); + } + + /** + * Gets the private 'logger' shared service. + * + * @return \Symfony\Component\HttpKernel\Log\Logger + */ + protected static function getLoggerService($container) + { + return $container->privates['logger'] = new \Symfony\Component\HttpKernel\Log\Logger(NULL, NULL, NULL, ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), $container->getEnv('bool:default::key:web:default:kernel.runtime_mode:')); + } + + /** + * Gets the private 'router.request_context' shared service. + * + * @return \Symfony\Component\Routing\RequestContext + */ + protected static function getRouter_RequestContextService($container) + { + return $container->privates['router.request_context'] = \Symfony\Component\Routing\RequestContext::fromUri('', 'localhost', 'http', 80, 443); + } + + /** + * Gets the private 'router_listener' shared service. + * + * @return \Symfony\Component\HttpKernel\EventListener\RouterListener + */ + protected static function getRouterListenerService($container) + { + return $container->privates['router_listener'] = new \Symfony\Component\HttpKernel\EventListener\RouterListener(($container->services['router'] ?? self::getRouterService($container)), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), ($container->privates['router.request_context'] ?? self::getRouter_RequestContextService($container)), ($container->privates['logger'] ?? self::getLoggerService($container)), \dirname(__DIR__, 4), true); + } + + /** + * Gets the private 'security.authorization_checker' shared service. + * + * @return \Symfony\Component\Security\Core\Authorization\AuthorizationChecker + */ + protected static function getSecurity_AuthorizationCheckerService($container) + { + $a = ($container->privates['debug.security.access.decision_manager'] ?? self::getDebug_Security_Access_DecisionManagerService($container)); + + if (isset($container->privates['security.authorization_checker'])) { + return $container->privates['security.authorization_checker']; + } + + return $container->privates['security.authorization_checker'] = new \Symfony\Component\Security\Core\Authorization\AuthorizationChecker(($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)), $a); + } + + /** + * Gets the private 'security.context_listener.0' shared service. + * + * @return \Symfony\Component\Security\Http\Firewall\ContextListener + */ + protected static function getSecurity_ContextListener_0Service($container) + { + return $container->privates['security.context_listener.0'] = new \Symfony\Component\Security\Http\Firewall\ContextListener(($container->privates['security.untracked_token_storage'] ??= new \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage()), new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['security.user.provider.concrete.users_in_memory'] ??= new \Symfony\Component\Security\Core\User\InMemoryUserProvider([])); + }, 1), 'main', ($container->privates['logger'] ?? self::getLoggerService($container)), ($container->privates['debug.security.event_dispatcher.main'] ?? self::getDebug_Security_EventDispatcher_MainService($container)), ($container->privates['security.authentication.trust_resolver'] ??= new \Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver()), [($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)), 'enableUsageTracking']); + } + + /** + * Gets the private 'security.csrf.token_manager' shared service. + * + * @return \Symfony\Component\Security\Csrf\CsrfTokenManager + */ + protected static function getSecurity_Csrf_TokenManagerService($container) + { + return $container->privates['security.csrf.token_manager'] = new \Symfony\Component\Security\Csrf\CsrfTokenManager(new \Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator(), ($container->privates['security.csrf.token_storage'] ?? self::getSecurity_Csrf_TokenStorageService($container)), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack())); + } + + /** + * Gets the private 'security.csrf.token_storage' shared service. + * + * @return \Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage + */ + protected static function getSecurity_Csrf_TokenStorageService($container) + { + return $container->privates['security.csrf.token_storage'] = new \Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage(($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack())); + } + + /** + * Gets the private 'security.firewall.map' shared service. + * + * @return \Symfony\Bundle\SecurityBundle\Security\FirewallMap + */ + protected static function getSecurity_Firewall_MapService($container) + { + $a = ($container->privates['.service_locator.bsoXAxw'] ?? self::get_ServiceLocator_BsoXAxwService($container)); + + if (isset($container->privates['security.firewall.map'])) { + return $container->privates['security.firewall.map']; + } + + return $container->privates['security.firewall.map'] = new \Symfony\Bundle\SecurityBundle\Security\FirewallMap($a, new RewindableGenerator(function () use ($container) { + yield 'security.firewall.map.context.dev' => ($container->privates['.security.request_matcher.gOpgIHx'] ?? $container->load('get_Security_RequestMatcher_GOpgIHxService')); + yield 'security.firewall.map.context.main' => NULL; + }, 2)); + } + + /** + * Gets the private 'security.logout_url_generator' shared service. + * + * @return \Symfony\Component\Security\Http\Logout\LogoutUrlGenerator + */ + protected static function getSecurity_LogoutUrlGeneratorService($container) + { + return $container->privates['security.logout_url_generator'] = new \Symfony\Component\Security\Http\Logout\LogoutUrlGenerator(($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), ($container->services['router'] ?? self::getRouterService($container)), ($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container))); + } + + /** + * Gets the private 'security.token_storage' shared service. + * + * @return \Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage + */ + protected static function getSecurity_TokenStorageService($container) + { + return $container->privates['security.token_storage'] = new \Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage(($container->privates['security.untracked_token_storage'] ??= new \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage()), new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'request_stack' => ['services', 'request_stack', 'getRequestStackService', false], + ], [ + 'request_stack' => '?', + ])); + } + + /** + * Gets the private 'session_listener' shared service. + * + * @return \Symfony\Component\HttpKernel\EventListener\SessionListener + */ + protected static function getSessionListenerService($container) + { + return $container->privates['session_listener'] = new \Symfony\Component\HttpKernel\EventListener\SessionListener(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'session_factory' => ['privates', 'session.factory', 'getSession_FactoryService', true], + 'logger' => ['privates', 'logger', 'getLoggerService', false], + ], [ + 'session_factory' => '?', + 'logger' => '?', + ]), true, $container->parameters['session.storage.options']); + } + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (isset($this->buildParameters[$name])) { + return $this->buildParameters[$name]; + } + + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { + throw new ParameterNotFoundException($name); + } + if (isset($this->loadedDynamicParameters[$name])) { + return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + + return $this->parameters[$name]; + } + + public function hasParameter(string $name): bool + { + if (isset($this->buildParameters[$name])) { + return true; + } + + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); + } + + public function setParameter(string $name, $value): void + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + public function getParameterBag(): ParameterBagInterface + { + if (!isset($this->parameterBag)) { + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + foreach ($this->buildParameters as $name => $value) { + $parameters[$name] = $value; + } + $this->parameterBag = new FrozenParameterBag($parameters); + } + + return $this->parameterBag; + } + + private $loadedDynamicParameters = [ + 'kernel.runtime_environment' => false, + 'kernel.runtime_mode' => false, + 'kernel.runtime_mode.web' => false, + 'kernel.runtime_mode.cli' => false, + 'kernel.runtime_mode.worker' => false, + 'kernel.build_dir' => false, + 'kernel.cache_dir' => false, + 'kernel.secret' => false, + 'debug.file_link_format' => false, + 'debug.container.dump' => false, + 'router.cache_dir' => false, + 'doctrine.orm.proxy_dir' => false, + 'lexik_jwt_authentication.pass_phrase' => false, + ]; + private $dynamicParameters = []; + + private function getDynamicParameter(string $name) + { + $container = $this; + $value = match ($name) { + 'kernel.runtime_environment' => $container->getEnv('default:kernel.environment:APP_RUNTIME_ENV'), + 'kernel.runtime_mode' => $container->getEnv('query_string:default:container.runtime_mode:APP_RUNTIME_MODE'), + 'kernel.runtime_mode.web' => $container->getEnv('bool:default::key:web:default:kernel.runtime_mode:'), + 'kernel.runtime_mode.cli' => $container->getEnv('not:default:kernel.runtime_mode.web:'), + 'kernel.runtime_mode.worker' => $container->getEnv('bool:default::key:worker:default:kernel.runtime_mode:'), + 'kernel.build_dir' => $container->targetDir.'', + 'kernel.cache_dir' => $container->targetDir.'', + 'kernel.secret' => $container->getEnv('APP_SECRET'), + 'debug.file_link_format' => $container->getEnv('default::SYMFONY_IDE'), + 'debug.container.dump' => ($container->targetDir.''.'/App_KernelDevDebugContainer.xml'), + 'router.cache_dir' => $container->targetDir.'', + 'doctrine.orm.proxy_dir' => ($container->targetDir.''.'/doctrine/orm/Proxies'), + 'lexik_jwt_authentication.pass_phrase' => $container->getEnv('JWT_PASSPHRASE'), + default => throw new ParameterNotFoundException($name), + }; + $this->loadedDynamicParameters[$name] = true; + + return $this->dynamicParameters[$name] = $value; + } + + protected function getDefaultParameters(): array + { + return [ + 'kernel.project_dir' => \dirname(__DIR__, 4), + 'kernel.environment' => 'dev', + 'kernel.debug' => true, + 'kernel.logs_dir' => (\dirname(__DIR__, 3).'/log'), + 'kernel.bundles' => [ + 'FrameworkBundle' => 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle', + 'MakerBundle' => 'Symfony\\Bundle\\MakerBundle\\MakerBundle', + 'DoctrineBundle' => 'Doctrine\\Bundle\\DoctrineBundle\\DoctrineBundle', + 'DoctrineMigrationsBundle' => 'Doctrine\\Bundle\\MigrationsBundle\\DoctrineMigrationsBundle', + 'SecurityBundle' => 'Symfony\\Bundle\\SecurityBundle\\SecurityBundle', + 'LexikJWTAuthenticationBundle' => 'Lexik\\Bundle\\JWTAuthenticationBundle\\LexikJWTAuthenticationBundle', + 'TwigBundle' => 'Symfony\\Bundle\\TwigBundle\\TwigBundle', + 'HWIOAuthBundle' => 'HWI\\Bundle\\OAuthBundle\\HWIOAuthBundle', + ], + 'kernel.bundles_metadata' => [ + 'FrameworkBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/symfony/framework-bundle'), + 'namespace' => 'Symfony\\Bundle\\FrameworkBundle', + ], + 'MakerBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/symfony/maker-bundle/src'), + 'namespace' => 'Symfony\\Bundle\\MakerBundle', + ], + 'DoctrineBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle'), + 'namespace' => 'Doctrine\\Bundle\\DoctrineBundle', + ], + 'DoctrineMigrationsBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/doctrine/doctrine-migrations-bundle'), + 'namespace' => 'Doctrine\\Bundle\\MigrationsBundle', + ], + 'SecurityBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/symfony/security-bundle'), + 'namespace' => 'Symfony\\Bundle\\SecurityBundle', + ], + 'LexikJWTAuthenticationBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/lexik/jwt-authentication-bundle'), + 'namespace' => 'Lexik\\Bundle\\JWTAuthenticationBundle', + ], + 'TwigBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/symfony/twig-bundle'), + 'namespace' => 'Symfony\\Bundle\\TwigBundle', + ], + 'HWIOAuthBundle' => [ + 'path' => (\dirname(__DIR__, 4).'/vendor/hwi/oauth-bundle/src'), + 'namespace' => 'HWI\\Bundle\\OAuthBundle', + ], + ], + 'kernel.charset' => 'UTF-8', + 'kernel.container_class' => 'App_KernelDevDebugContainer', + 'event_dispatcher.event_aliases' => [ + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => 'console.command', + 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => 'console.error', + 'Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => 'console.signal', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => 'console.terminate', + 'Symfony\\Component\\Form\\Event\\PreSubmitEvent' => 'form.pre_submit', + 'Symfony\\Component\\Form\\Event\\SubmitEvent' => 'form.submit', + 'Symfony\\Component\\Form\\Event\\PostSubmitEvent' => 'form.post_submit', + 'Symfony\\Component\\Form\\Event\\PreSetDataEvent' => 'form.pre_set_data', + 'Symfony\\Component\\Form\\Event\\PostSetDataEvent' => 'form.post_set_data', + 'Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => 'kernel.controller_arguments', + 'Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => 'kernel.controller', + 'Symfony\\Component\\HttpKernel\\Event\\ResponseEvent' => 'kernel.response', + 'Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent' => 'kernel.finish_request', + 'Symfony\\Component\\HttpKernel\\Event\\RequestEvent' => 'kernel.request', + 'Symfony\\Component\\HttpKernel\\Event\\ViewEvent' => 'kernel.view', + 'Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent' => 'kernel.exception', + 'Symfony\\Component\\HttpKernel\\Event\\TerminateEvent' => 'kernel.terminate', + 'Symfony\\Component\\Security\\Core\\Event\\AuthenticationSuccessEvent' => 'security.authentication.success', + 'Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent' => 'security.interactive_login', + 'Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent' => 'security.switch_user', + ], + 'fragment.renderer.hinclude.global_template' => NULL, + 'fragment.path' => '/_fragment', + 'kernel.http_method_override' => false, + 'kernel.trust_x_sendfile_type_header' => false, + 'kernel.trusted_hosts' => [ + + ], + 'kernel.default_locale' => 'en', + 'kernel.enabled_locales' => [ + + ], + 'kernel.error_controller' => 'error_controller', + 'debug.error_handler.throw_at' => -1, + 'router.request_context.host' => 'localhost', + 'router.request_context.scheme' => 'http', + 'router.request_context.base_url' => '', + 'router.resource' => 'kernel::loadRoutes', + 'request_listener.http_port' => 80, + 'request_listener.https_port' => 443, + 'session.metadata.storage_key' => '_sf2_meta', + 'session.storage.options' => [ + 'cache_limiter' => '0', + 'cookie_secure' => 'auto', + 'cookie_httponly' => true, + 'cookie_samesite' => 'lax', + 'gc_probability' => 1, + ], + 'session.save_path' => NULL, + 'session.metadata.update_threshold' => 0, + 'form.type_extension.csrf.enabled' => true, + 'form.type_extension.csrf.field_name' => '_token', + 'validator.translation_domain' => 'validators', + 'data_collector.templates' => [ + + ], + 'doctrine.dbal.configuration.class' => 'Doctrine\\DBAL\\Configuration', + 'doctrine.data_collector.class' => 'Doctrine\\Bundle\\DoctrineBundle\\DataCollector\\DoctrineDataCollector', + 'doctrine.dbal.connection.event_manager.class' => 'Symfony\\Bridge\\Doctrine\\ContainerAwareEventManager', + 'doctrine.dbal.connection_factory.class' => 'Doctrine\\Bundle\\DoctrineBundle\\ConnectionFactory', + 'doctrine.dbal.events.mysql_session_init.class' => 'Doctrine\\DBAL\\Event\\Listeners\\MysqlSessionInit', + 'doctrine.dbal.events.oracle_session_init.class' => 'Doctrine\\DBAL\\Event\\Listeners\\OracleSessionInit', + 'doctrine.class' => 'Doctrine\\Bundle\\DoctrineBundle\\Registry', + 'doctrine.entity_managers' => [ + 'default' => 'doctrine.orm.default_entity_manager', + ], + 'doctrine.default_entity_manager' => 'default', + 'doctrine.dbal.connection_factory.types' => [ + + ], + 'doctrine.connections' => [ + 'default' => 'doctrine.dbal.default_connection', + ], + 'doctrine.default_connection' => 'default', + 'doctrine.orm.configuration.class' => 'Doctrine\\ORM\\Configuration', + 'doctrine.orm.entity_manager.class' => 'Doctrine\\ORM\\EntityManager', + 'doctrine.orm.manager_configurator.class' => 'Doctrine\\Bundle\\DoctrineBundle\\ManagerConfigurator', + 'doctrine.orm.cache.array.class' => 'Doctrine\\Common\\Cache\\ArrayCache', + 'doctrine.orm.cache.apc.class' => 'Doctrine\\Common\\Cache\\ApcCache', + 'doctrine.orm.cache.memcache.class' => 'Doctrine\\Common\\Cache\\MemcacheCache', + 'doctrine.orm.cache.memcache_host' => 'localhost', + 'doctrine.orm.cache.memcache_port' => 11211, + 'doctrine.orm.cache.memcache_instance.class' => 'Memcache', + 'doctrine.orm.cache.memcached.class' => 'Doctrine\\Common\\Cache\\MemcachedCache', + 'doctrine.orm.cache.memcached_host' => 'localhost', + 'doctrine.orm.cache.memcached_port' => 11211, + 'doctrine.orm.cache.memcached_instance.class' => 'Memcached', + 'doctrine.orm.cache.redis.class' => 'Doctrine\\Common\\Cache\\RedisCache', + 'doctrine.orm.cache.redis_host' => 'localhost', + 'doctrine.orm.cache.redis_port' => 6379, + 'doctrine.orm.cache.redis_instance.class' => 'Redis', + 'doctrine.orm.cache.xcache.class' => 'Doctrine\\Common\\Cache\\XcacheCache', + 'doctrine.orm.cache.wincache.class' => 'Doctrine\\Common\\Cache\\WinCacheCache', + 'doctrine.orm.cache.zenddata.class' => 'Doctrine\\Common\\Cache\\ZendDataCache', + 'doctrine.orm.metadata.driver_chain.class' => 'Doctrine\\Persistence\\Mapping\\Driver\\MappingDriverChain', + 'doctrine.orm.metadata.annotation.class' => 'Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver', + 'doctrine.orm.metadata.xml.class' => 'Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver', + 'doctrine.orm.metadata.yml.class' => 'Doctrine\\ORM\\Mapping\\Driver\\SimplifiedYamlDriver', + 'doctrine.orm.metadata.php.class' => 'Doctrine\\ORM\\Mapping\\Driver\\PHPDriver', + 'doctrine.orm.metadata.staticphp.class' => 'Doctrine\\ORM\\Mapping\\Driver\\StaticPHPDriver', + 'doctrine.orm.metadata.attribute.class' => 'Doctrine\\ORM\\Mapping\\Driver\\AttributeDriver', + 'doctrine.orm.proxy_cache_warmer.class' => 'Symfony\\Bridge\\Doctrine\\CacheWarmer\\ProxyCacheWarmer', + 'form.type_guesser.doctrine.class' => 'Symfony\\Bridge\\Doctrine\\Form\\DoctrineOrmTypeGuesser', + 'doctrine.orm.validator.unique.class' => 'Symfony\\Bridge\\Doctrine\\Validator\\Constraints\\UniqueEntityValidator', + 'doctrine.orm.validator_initializer.class' => 'Symfony\\Bridge\\Doctrine\\Validator\\DoctrineInitializer', + 'doctrine.orm.security.user.provider.class' => 'Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider', + 'doctrine.orm.listeners.resolve_target_entity.class' => 'Doctrine\\ORM\\Tools\\ResolveTargetEntityListener', + 'doctrine.orm.listeners.attach_entity_listeners.class' => 'Doctrine\\ORM\\Tools\\AttachEntityListenersListener', + 'doctrine.orm.naming_strategy.default.class' => 'Doctrine\\ORM\\Mapping\\DefaultNamingStrategy', + 'doctrine.orm.naming_strategy.underscore.class' => 'Doctrine\\ORM\\Mapping\\UnderscoreNamingStrategy', + 'doctrine.orm.quote_strategy.default.class' => 'Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy', + 'doctrine.orm.quote_strategy.ansi.class' => 'Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy', + 'doctrine.orm.typed_field_mapper.default.class' => 'Doctrine\\ORM\\Mapping\\DefaultTypedFieldMapper', + 'doctrine.orm.entity_listener_resolver.class' => 'Doctrine\\Bundle\\DoctrineBundle\\Mapping\\ContainerEntityListenerResolver', + 'doctrine.orm.second_level_cache.default_cache_factory.class' => 'Doctrine\\ORM\\Cache\\DefaultCacheFactory', + 'doctrine.orm.second_level_cache.default_region.class' => 'Doctrine\\ORM\\Cache\\Region\\DefaultRegion', + 'doctrine.orm.second_level_cache.filelock_region.class' => 'Doctrine\\ORM\\Cache\\Region\\FileLockRegion', + 'doctrine.orm.second_level_cache.logger_chain.class' => 'Doctrine\\ORM\\Cache\\Logging\\CacheLoggerChain', + 'doctrine.orm.second_level_cache.logger_statistics.class' => 'Doctrine\\ORM\\Cache\\Logging\\StatisticsCacheLogger', + 'doctrine.orm.second_level_cache.cache_configuration.class' => 'Doctrine\\ORM\\Cache\\CacheConfiguration', + 'doctrine.orm.second_level_cache.regions_configuration.class' => 'Doctrine\\ORM\\Cache\\RegionsConfiguration', + 'doctrine.orm.auto_generate_proxy_classes' => true, + 'doctrine.orm.enable_lazy_ghost_objects' => true, + 'doctrine.orm.proxy_namespace' => 'Proxies', + 'doctrine.migrations.preferred_em' => NULL, + 'doctrine.migrations.preferred_connection' => NULL, + 'security.role_hierarchy.roles' => [ + + ], + 'security.access.denied_url' => NULL, + 'security.authentication.manager.erase_credentials' => true, + 'security.authentication.session_strategy.strategy' => 'migrate', + 'security.authentication.hide_user_not_found' => true, + 'security.firewalls' => [ + 0 => 'dev', + 1 => 'main', + ], + 'security.logout_uris' => [ + + ], + 'lexik_jwt_authentication.token_ttl' => 3600, + 'lexik_jwt_authentication.clock_skew' => 0, + 'lexik_jwt_authentication.allow_no_expiration' => false, + 'lexik_jwt_authentication.user_id_claim' => 'username', + 'lexik_jwt_authentication.encoder.signature_algorithm' => 'RS256', + 'twig.form.resources' => [ + 0 => 'form_div_layout.html.twig', + ], + 'twig.default_path' => (\dirname(__DIR__, 4).'/templates'), + 'hwi_oauth.target_path_parameter' => NULL, + 'hwi_oauth.target_path_domains_whitelist' => [ + + ], + 'hwi_oauth.use_referer' => false, + 'hwi_oauth.failed_use_referer' => false, + 'hwi_oauth.failed_auth_path' => 'hwi_oauth_connect', + 'hwi_oauth.grant_rule' => 'IS_AUTHENTICATED_REMEMBERED', + 'hwi_oauth.resource_owners' => [ + 'facebook' => 'facebook', + ], + 'hwi_oauth.connect' => false, + 'hwi_oauth.connect.confirmation' => false, + 'hwi_oauth.connect.registration_form' => NULL, + 'hwi_oauth.resource_owner.deezer.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\DeezerResourceOwner', + 'hwi_oauth.resource_owner.hubic.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\HubicResourceOwner', + 'hwi_oauth.resource_owner.discogs.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\DiscogsResourceOwner', + 'hwi_oauth.resource_owner.dailymotion.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\DailymotionResourceOwner', + 'hwi_oauth.resource_owner.xing.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\XingResourceOwner', + 'hwi_oauth.resource_owner.stack_exchange.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\StackExchangeResourceOwner', + 'hwi_oauth.resource_owner.twitch.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\TwitchResourceOwner', + 'hwi_oauth.resource_owner.odnoklassniki.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\OdnoklassnikiResourceOwner', + 'hwi_oauth.resource_owner.github.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\GitHubResourceOwner', + 'hwi_oauth.resource_owner.flickr.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\FlickrResourceOwner', + 'hwi_oauth.resource_owner.office365.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\Office365ResourceOwner', + 'hwi_oauth.resource_owner.37signals.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\ThirtySevenSignalsResourceOwner', + 'hwi_oauth.resource_owner.eventbrite.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\EventbriteResourceOwner', + 'hwi_oauth.resource_owner.yahoo.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\YahooResourceOwner', + 'hwi_oauth.resource_owner.windows_live.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\WindowsLiveResourceOwner', + 'hwi_oauth.resource_owner.keycloak.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\KeycloakResourceOwner', + 'hwi_oauth.resource_owner.facebook.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\FacebookResourceOwner', + 'hwi_oauth.resource_owner.clever.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\CleverResourceOwner', + 'hwi_oauth.resource_owner.gitlab.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\GitLabResourceOwner', + 'hwi_oauth.resource_owner.runkeeper.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\RunKeeperResourceOwner', + 'hwi_oauth.resource_owner.twitter.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\TwitterResourceOwner', + 'hwi_oauth.resource_owner.linkedin.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\LinkedinResourceOwner', + 'hwi_oauth.resource_owner.jira.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\JiraResourceOwner', + 'hwi_oauth.resource_owner.slack.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\SlackResourceOwner', + 'hwi_oauth.resource_owner.sina_weibo.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\SinaWeiboResourceOwner', + 'hwi_oauth.resource_owner.spotify.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\SpotifyResourceOwner', + 'hwi_oauth.resource_owner.youtube.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\YoutubeResourceOwner', + 'hwi_oauth.resource_owner.fiware.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\FiwareResourceOwner', + 'hwi_oauth.resource_owner.oauth1.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\OAuth1ResourceOwner', + 'hwi_oauth.resource_owner.trakt.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\TraktResourceOwner', + 'hwi_oauth.resource_owner.azure.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\AzureResourceOwner', + 'hwi_oauth.resource_owner.google.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\GoogleResourceOwner', + 'hwi_oauth.resource_owner.amazon.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\AmazonResourceOwner', + 'hwi_oauth.resource_owner.sensio_connect.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\SensioConnectResourceOwner', + 'hwi_oauth.resource_owner.vkontakte.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\VkontakteResourceOwner', + 'hwi_oauth.resource_owner.bufferapp.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\BufferAppResourceOwner', + 'hwi_oauth.resource_owner.stereomood.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\StereomoodResourceOwner', + 'hwi_oauth.resource_owner.bitly.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\BitlyResourceOwner', + 'hwi_oauth.resource_owner.telegram.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\TelegramResourceOwner', + 'hwi_oauth.resource_owner.oauth2.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\OAuth2ResourceOwner', + 'hwi_oauth.resource_owner.jawbone.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\JawboneResourceOwner', + 'hwi_oauth.resource_owner.disqus.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\DisqusResourceOwner', + 'hwi_oauth.resource_owner.dropbox.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\DropboxResourceOwner', + 'hwi_oauth.resource_owner.apple.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\AppleResourceOwner', + 'hwi_oauth.resource_owner.soundcloud.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\SoundcloudResourceOwner', + 'hwi_oauth.resource_owner.foursquare.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\FoursquareResourceOwner', + 'hwi_oauth.resource_owner.yandex.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\YandexResourceOwner', + 'hwi_oauth.resource_owner.genius.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\GeniusResourceOwner', + 'hwi_oauth.resource_owner.instagram.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\InstagramResourceOwner', + 'hwi_oauth.resource_owner.bitbucket2.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\Bitbucket2ResourceOwner', + 'hwi_oauth.resource_owner.bitbucket.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\BitbucketResourceOwner', + 'hwi_oauth.resource_owner.itembase.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\ItembaseResourceOwner', + 'hwi_oauth.resource_owner.reddit.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\RedditResourceOwner', + 'hwi_oauth.resource_owner.asana.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\AsanaResourceOwner', + 'hwi_oauth.resource_owner.trello.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\TrelloResourceOwner', + 'hwi_oauth.resource_owner.wordpress.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\WordpressResourceOwner', + 'hwi_oauth.resource_owner.toshl.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\ToshlResourceOwner', + 'hwi_oauth.resource_owner.box.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\BoxResourceOwner', + 'hwi_oauth.resource_owner.strava.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\StravaResourceOwner', + 'hwi_oauth.resource_owner.auth0.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\Auth0ResourceOwner', + 'hwi_oauth.resource_owner.deviantart.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\DeviantartResourceOwner', + 'hwi_oauth.resource_owner.qq.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\QQResourceOwner', + 'hwi_oauth.resource_owner.mailru.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\MailRuResourceOwner', + 'hwi_oauth.resource_owner.salesforce.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\SalesforceResourceOwner', + 'hwi_oauth.resource_owner.passage.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\PassageResourceOwner', + 'hwi_oauth.resource_owner.eveonline.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\EveOnlineResourceOwner', + 'hwi_oauth.resource_owner.paypal.class' => 'HWI\\Bundle\\OAuthBundle\\OAuth\\ResourceOwner\\PaypalResourceOwner', + 'console.command.ids' => [ + + ], + ]; + } +} diff --git a/var/cache/dev/Container6Szx89D/EntityManagerGhost614a58f.php b/var/cache/dev/Container6Szx89D/EntityManagerGhost614a58f.php new file mode 100644 index 0000000..9f5bb50 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/EntityManagerGhost614a58f.php @@ -0,0 +1,45 @@ + [parent::class, 'cache', null], + "\0".parent::class."\0".'closed' => [parent::class, 'closed', null], + "\0".parent::class."\0".'config' => [parent::class, 'config', null], + "\0".parent::class."\0".'conn' => [parent::class, 'conn', null], + "\0".parent::class."\0".'eventManager' => [parent::class, 'eventManager', null], + "\0".parent::class."\0".'expressionBuilder' => [parent::class, 'expressionBuilder', null], + "\0".parent::class."\0".'filterCollection' => [parent::class, 'filterCollection', null], + "\0".parent::class."\0".'metadataFactory' => [parent::class, 'metadataFactory', null], + "\0".parent::class."\0".'proxyFactory' => [parent::class, 'proxyFactory', null], + "\0".parent::class."\0".'repositoryFactory' => [parent::class, 'repositoryFactory', null], + "\0".parent::class."\0".'unitOfWork' => [parent::class, 'unitOfWork', null], + 'cache' => [parent::class, 'cache', null], + 'closed' => [parent::class, 'closed', null], + 'config' => [parent::class, 'config', null], + 'conn' => [parent::class, 'conn', null], + 'eventManager' => [parent::class, 'eventManager', null], + 'expressionBuilder' => [parent::class, 'expressionBuilder', null], + 'filterCollection' => [parent::class, 'filterCollection', null], + 'metadataFactory' => [parent::class, 'metadataFactory', null], + 'proxyFactory' => [parent::class, 'proxyFactory', null], + 'repositoryFactory' => [parent::class, 'repositoryFactory', null], + 'unitOfWork' => [parent::class, 'unitOfWork', null], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +if (!\class_exists('EntityManagerGhost614a58f', false)) { + \class_alias(__NAMESPACE__.'\\EntityManagerGhost614a58f', 'EntityManagerGhost614a58f', false); +} diff --git a/var/cache/dev/Container6Szx89D/RequestPayloadValueResolverGhost01ca9cc.php b/var/cache/dev/Container6Szx89D/RequestPayloadValueResolverGhost01ca9cc.php new file mode 100644 index 0000000..515156d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/RequestPayloadValueResolverGhost01ca9cc.php @@ -0,0 +1,28 @@ + [parent::class, 'serializer', parent::class], + "\0".parent::class."\0".'translator' => [parent::class, 'translator', parent::class], + "\0".parent::class."\0".'validator' => [parent::class, 'validator', parent::class], + 'serializer' => [parent::class, 'serializer', parent::class], + 'translator' => [parent::class, 'translator', parent::class], + 'validator' => [parent::class, 'validator', parent::class], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +if (!\class_exists('RequestPayloadValueResolverGhost01ca9cc', false)) { + \class_alias(__NAMESPACE__.'\\RequestPayloadValueResolverGhost01ca9cc', 'RequestPayloadValueResolverGhost01ca9cc', false); +} diff --git a/var/cache/dev/Container6Szx89D/getCacheWarmerService.php b/var/cache/dev/Container6Szx89D/getCacheWarmerService.php new file mode 100644 index 0000000..318ae02 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCacheWarmerService.php @@ -0,0 +1,31 @@ +services['cache_warmer'] = new \Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate(new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['config_builder.warmer'] ?? $container->load('getConfigBuilder_WarmerService')); + yield 1 => ($container->privates['router.cache_warmer'] ?? $container->load('getRouter_CacheWarmerService')); + yield 2 => ($container->privates['doctrine.orm.proxy_cache_warmer'] ?? $container->load('getDoctrine_Orm_ProxyCacheWarmerService')); + yield 3 => ($container->privates['twig.template_cache_warmer'] ?? $container->load('getTwig_TemplateCacheWarmerService')); + }, 4), true, ($container->targetDir.''.'/App_KernelDevDebugContainerDeprecations.log')); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_AppClearerService.php b/var/cache/dev/Container6Szx89D/getCache_AppClearerService.php new file mode 100644 index 0000000..014d7c8 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_AppClearerService.php @@ -0,0 +1,26 @@ +services['cache.app_clearer'] = new \Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer(['cache.app' => ($container->services['cache.app'] ?? $container->load('getCache_AppService'))]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_AppService.php b/var/cache/dev/Container6Szx89D/getCache_AppService.php new file mode 100644 index 0000000..32aefce --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_AppService.php @@ -0,0 +1,44 @@ +services['cache.app'] = $instance = new \Symfony\Component\Cache\Adapter\FilesystemAdapter('eKxI7xeGjc', 0, ($container->targetDir.''.'/pools/app'), new \Symfony\Component\Cache\Marshaller\DefaultMarshaller(NULL, true)); + + $instance->setLogger(($container->privates['logger'] ?? self::getLoggerService($container))); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_App_TaggableService.php b/var/cache/dev/Container6Szx89D/getCache_App_TaggableService.php new file mode 100644 index 0000000..742ad2a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_App_TaggableService.php @@ -0,0 +1,36 @@ +privates['cache.app.taggable'] = new \Symfony\Component\Cache\Adapter\TagAwareAdapter(($container->services['cache.app'] ?? $container->load('getCache_AppService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_GlobalClearerService.php b/var/cache/dev/Container6Szx89D/getCache_GlobalClearerService.php new file mode 100644 index 0000000..5dc6bda --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_GlobalClearerService.php @@ -0,0 +1,33 @@ +services['cache.global_clearer'] = new \Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer(['cache.app' => ($container->services['cache.app'] ?? $container->load('getCache_AppService')), 'cache.system' => ($container->services['cache.system'] ?? $container->load('getCache_SystemService')), 'cache.doctrine.orm.default.result' => ($container->privates['cache.doctrine.orm.default.result'] ??= new \Symfony\Component\Cache\Adapter\ArrayAdapter()), 'cache.doctrine.orm.default.query' => ($container->privates['cache.doctrine.orm.default.query'] ??= new \Symfony\Component\Cache\Adapter\ArrayAdapter()), 'cache.security_is_granted_attribute_expression_language' => ($container->services['cache.security_is_granted_attribute_expression_language'] ?? $container->load('getCache_SecurityIsGrantedAttributeExpressionLanguageService')), 'cache.security_is_csrf_token_valid_attribute_expression_language' => ($container->services['cache.security_is_csrf_token_valid_attribute_expression_language'] ?? $container->load('getCache_SecurityIsCsrfTokenValidAttributeExpressionLanguageService'))]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_SecurityIsCsrfTokenValidAttributeExpressionLanguageService.php b/var/cache/dev/Container6Szx89D/getCache_SecurityIsCsrfTokenValidAttributeExpressionLanguageService.php new file mode 100644 index 0000000..5130f88 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_SecurityIsCsrfTokenValidAttributeExpressionLanguageService.php @@ -0,0 +1,34 @@ +services['cache.security_is_csrf_token_valid_attribute_expression_language'] = \Symfony\Component\Cache\Adapter\AbstractAdapter::createSystemCache('+yFEAOIjaq', 0, $container->getParameter('container.build_id'), ($container->targetDir.''.'/pools/system'), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_SecurityIsGrantedAttributeExpressionLanguageService.php b/var/cache/dev/Container6Szx89D/getCache_SecurityIsGrantedAttributeExpressionLanguageService.php new file mode 100644 index 0000000..d3c7f27 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_SecurityIsGrantedAttributeExpressionLanguageService.php @@ -0,0 +1,34 @@ +services['cache.security_is_granted_attribute_expression_language'] = \Symfony\Component\Cache\Adapter\AbstractAdapter::createSystemCache('7H+I-t+3hW', 0, $container->getParameter('container.build_id'), ($container->targetDir.''.'/pools/system'), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_SystemClearerService.php b/var/cache/dev/Container6Szx89D/getCache_SystemClearerService.php new file mode 100644 index 0000000..7b5d4f4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_SystemClearerService.php @@ -0,0 +1,26 @@ +services['cache.system_clearer'] = new \Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer(['cache.system' => ($container->services['cache.system'] ?? $container->load('getCache_SystemService')), 'cache.security_is_granted_attribute_expression_language' => ($container->services['cache.security_is_granted_attribute_expression_language'] ?? $container->load('getCache_SecurityIsGrantedAttributeExpressionLanguageService')), 'cache.security_is_csrf_token_valid_attribute_expression_language' => ($container->services['cache.security_is_csrf_token_valid_attribute_expression_language'] ?? $container->load('getCache_SecurityIsCsrfTokenValidAttributeExpressionLanguageService'))]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCache_SystemService.php b/var/cache/dev/Container6Szx89D/getCache_SystemService.php new file mode 100644 index 0000000..ac271d9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCache_SystemService.php @@ -0,0 +1,34 @@ +services['cache.system'] = \Symfony\Component\Cache\Adapter\AbstractAdapter::createSystemCache('X+61g8xV65', 0, $container->getParameter('container.build_id'), ($container->targetDir.''.'/pools/system'), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getCommentRepositoryService.php b/var/cache/dev/Container6Szx89D/getCommentRepositoryService.php new file mode 100644 index 0000000..0653211 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getCommentRepositoryService.php @@ -0,0 +1,31 @@ +privates['App\\Repository\\CommentRepository'] = new \App\Repository\CommentRepository(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getConfigBuilder_WarmerService.php b/var/cache/dev/Container6Szx89D/getConfigBuilder_WarmerService.php new file mode 100644 index 0000000..5e2004b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConfigBuilder_WarmerService.php @@ -0,0 +1,26 @@ +privates['config_builder.warmer'] = new \Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer(($container->services['kernel'] ?? $container->get('kernel', 1)), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getConnectControllerService.php b/var/cache/dev/Container6Szx89D/getConnectControllerService.php new file mode 100644 index 0000000..e241d9f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConnectControllerService.php @@ -0,0 +1,29 @@ +services['HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\ConnectController'] = new \HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController(($container->privates['hwi_oauth.security.oauth_utils'] ?? $container->load('getHwiOauth_Security_OauthUtilsService')), ($container->privates['hwi_oauth.resource_ownermap_locator'] ??= new \HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator()), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), ($container->services['event_dispatcher'] ?? self::getEventDispatcherService($container)), ($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)), ($container->services['hwi_oauth.user_checker'] ??= new \Symfony\Component\Security\Core\User\InMemoryUserChecker()), ($container->privates['security.authorization_checker'] ?? self::getSecurity_AuthorizationCheckerService($container)), ($container->privates['form.factory'] ?? $container->load('getForm_FactoryService')), ($container->privates['twig'] ?? $container->load('getTwigService')), ($container->services['router'] ?? self::getRouterService($container)), 'IS_AUTHENTICATED_REMEMBERED', false, 'hwi_oauth_connect', false, NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_CommandLoaderService.php b/var/cache/dev/Container6Szx89D/getConsole_CommandLoaderService.php new file mode 100644 index 0000000..92eaf89 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_CommandLoaderService.php @@ -0,0 +1,214 @@ +services['console.command_loader'] = new \Symfony\Component\Console\CommandLoader\ContainerCommandLoader(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'console.command.about' => ['privates', '.console.command.about.lazy', 'get_Console_Command_About_LazyService', true], + 'console.command.assets_install' => ['privates', '.console.command.assets_install.lazy', 'get_Console_Command_AssetsInstall_LazyService', true], + 'console.command.cache_clear' => ['privates', '.console.command.cache_clear.lazy', 'get_Console_Command_CacheClear_LazyService', true], + 'console.command.cache_pool_clear' => ['privates', '.console.command.cache_pool_clear.lazy', 'get_Console_Command_CachePoolClear_LazyService', true], + 'console.command.cache_pool_prune' => ['privates', '.console.command.cache_pool_prune.lazy', 'get_Console_Command_CachePoolPrune_LazyService', true], + 'console.command.cache_pool_invalidate_tags' => ['privates', '.console.command.cache_pool_invalidate_tags.lazy', 'get_Console_Command_CachePoolInvalidateTags_LazyService', true], + 'console.command.cache_pool_delete' => ['privates', '.console.command.cache_pool_delete.lazy', 'get_Console_Command_CachePoolDelete_LazyService', true], + 'console.command.cache_pool_list' => ['privates', '.console.command.cache_pool_list.lazy', 'get_Console_Command_CachePoolList_LazyService', true], + 'console.command.cache_warmup' => ['privates', '.console.command.cache_warmup.lazy', 'get_Console_Command_CacheWarmup_LazyService', true], + 'console.command.config_debug' => ['privates', '.console.command.config_debug.lazy', 'get_Console_Command_ConfigDebug_LazyService', true], + 'console.command.config_dump_reference' => ['privates', '.console.command.config_dump_reference.lazy', 'get_Console_Command_ConfigDumpReference_LazyService', true], + 'console.command.container_debug' => ['privates', '.console.command.container_debug.lazy', 'get_Console_Command_ContainerDebug_LazyService', true], + 'console.command.container_lint' => ['privates', '.console.command.container_lint.lazy', 'get_Console_Command_ContainerLint_LazyService', true], + 'console.command.debug_autowiring' => ['privates', '.console.command.debug_autowiring.lazy', 'get_Console_Command_DebugAutowiring_LazyService', true], + 'console.command.dotenv_debug' => ['privates', '.console.command.dotenv_debug.lazy', 'get_Console_Command_DotenvDebug_LazyService', true], + 'console.command.event_dispatcher_debug' => ['privates', '.console.command.event_dispatcher_debug.lazy', 'get_Console_Command_EventDispatcherDebug_LazyService', true], + 'console.command.router_debug' => ['privates', '.console.command.router_debug.lazy', 'get_Console_Command_RouterDebug_LazyService', true], + 'console.command.router_match' => ['privates', '.console.command.router_match.lazy', 'get_Console_Command_RouterMatch_LazyService', true], + 'console.command.yaml_lint' => ['privates', '.console.command.yaml_lint.lazy', 'get_Console_Command_YamlLint_LazyService', true], + 'console.command.form_debug' => ['privates', '.console.command.form_debug.lazy', 'get_Console_Command_FormDebug_LazyService', true], + 'console.command.secrets_set' => ['privates', '.console.command.secrets_set.lazy', 'get_Console_Command_SecretsSet_LazyService', true], + 'console.command.secrets_remove' => ['privates', '.console.command.secrets_remove.lazy', 'get_Console_Command_SecretsRemove_LazyService', true], + 'console.command.secrets_generate_key' => ['privates', '.console.command.secrets_generate_key.lazy', 'get_Console_Command_SecretsGenerateKey_LazyService', true], + 'console.command.secrets_list' => ['privates', '.console.command.secrets_list.lazy', 'get_Console_Command_SecretsList_LazyService', true], + 'console.command.secrets_reveal' => ['privates', '.console.command.secrets_reveal.lazy', 'get_Console_Command_SecretsReveal_LazyService', true], + 'console.command.secrets_decrypt_to_local' => ['privates', '.console.command.secrets_decrypt_to_local.lazy', 'get_Console_Command_SecretsDecryptToLocal_LazyService', true], + 'console.command.secrets_encrypt_from_local' => ['privates', '.console.command.secrets_encrypt_from_local.lazy', 'get_Console_Command_SecretsEncryptFromLocal_LazyService', true], + 'console.command.mailer_test' => ['privates', '.console.command.mailer_test.lazy', 'get_Console_Command_MailerTest_LazyService', true], + 'doctrine.database_create_command' => ['privates', 'doctrine.database_create_command', 'getDoctrine_DatabaseCreateCommandService', true], + 'doctrine.database_drop_command' => ['privates', 'doctrine.database_drop_command', 'getDoctrine_DatabaseDropCommandService', true], + 'doctrine.query_sql_command' => ['privates', 'doctrine.query_sql_command', 'getDoctrine_QuerySqlCommandService', true], + 'Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand' => ['privates', 'Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand', 'getRunSqlCommandService', true], + 'doctrine.cache_clear_metadata_command' => ['privates', 'doctrine.cache_clear_metadata_command', 'getDoctrine_CacheClearMetadataCommandService', true], + 'doctrine.cache_clear_query_cache_command' => ['privates', 'doctrine.cache_clear_query_cache_command', 'getDoctrine_CacheClearQueryCacheCommandService', true], + 'doctrine.cache_clear_result_command' => ['privates', 'doctrine.cache_clear_result_command', 'getDoctrine_CacheClearResultCommandService', true], + 'doctrine.cache_collection_region_command' => ['privates', 'doctrine.cache_collection_region_command', 'getDoctrine_CacheCollectionRegionCommandService', true], + 'doctrine.schema_create_command' => ['privates', 'doctrine.schema_create_command', 'getDoctrine_SchemaCreateCommandService', true], + 'doctrine.schema_drop_command' => ['privates', 'doctrine.schema_drop_command', 'getDoctrine_SchemaDropCommandService', true], + 'doctrine.clear_entity_region_command' => ['privates', 'doctrine.clear_entity_region_command', 'getDoctrine_ClearEntityRegionCommandService', true], + 'doctrine.mapping_info_command' => ['privates', 'doctrine.mapping_info_command', 'getDoctrine_MappingInfoCommandService', true], + 'doctrine.clear_query_region_command' => ['privates', 'doctrine.clear_query_region_command', 'getDoctrine_ClearQueryRegionCommandService', true], + 'doctrine.query_dql_command' => ['privates', 'doctrine.query_dql_command', 'getDoctrine_QueryDqlCommandService', true], + 'doctrine.schema_update_command' => ['privates', 'doctrine.schema_update_command', 'getDoctrine_SchemaUpdateCommandService', true], + 'doctrine.schema_validate_command' => ['privates', 'doctrine.schema_validate_command', 'getDoctrine_SchemaValidateCommandService', true], + 'doctrine_migrations.diff_command' => ['privates', '.doctrine_migrations.diff_command.lazy', 'get_DoctrineMigrations_DiffCommand_LazyService', true], + 'doctrine_migrations.sync_metadata_command' => ['privates', '.doctrine_migrations.sync_metadata_command.lazy', 'get_DoctrineMigrations_SyncMetadataCommand_LazyService', true], + 'doctrine_migrations.versions_command' => ['privates', '.doctrine_migrations.versions_command.lazy', 'get_DoctrineMigrations_VersionsCommand_LazyService', true], + 'doctrine_migrations.current_command' => ['privates', '.doctrine_migrations.current_command.lazy', 'get_DoctrineMigrations_CurrentCommand_LazyService', true], + 'doctrine_migrations.dump_schema_command' => ['privates', '.doctrine_migrations.dump_schema_command.lazy', 'get_DoctrineMigrations_DumpSchemaCommand_LazyService', true], + 'doctrine_migrations.execute_command' => ['privates', '.doctrine_migrations.execute_command.lazy', 'get_DoctrineMigrations_ExecuteCommand_LazyService', true], + 'doctrine_migrations.generate_command' => ['privates', '.doctrine_migrations.generate_command.lazy', 'get_DoctrineMigrations_GenerateCommand_LazyService', true], + 'doctrine_migrations.latest_command' => ['privates', '.doctrine_migrations.latest_command.lazy', 'get_DoctrineMigrations_LatestCommand_LazyService', true], + 'doctrine_migrations.migrate_command' => ['privates', '.doctrine_migrations.migrate_command.lazy', 'get_DoctrineMigrations_MigrateCommand_LazyService', true], + 'doctrine_migrations.rollup_command' => ['privates', '.doctrine_migrations.rollup_command.lazy', 'get_DoctrineMigrations_RollupCommand_LazyService', true], + 'doctrine_migrations.status_command' => ['privates', '.doctrine_migrations.status_command.lazy', 'get_DoctrineMigrations_StatusCommand_LazyService', true], + 'doctrine_migrations.up_to_date_command' => ['privates', '.doctrine_migrations.up_to_date_command.lazy', 'get_DoctrineMigrations_UpToDateCommand_LazyService', true], + 'doctrine_migrations.version_command' => ['privates', '.doctrine_migrations.version_command.lazy', 'get_DoctrineMigrations_VersionCommand_LazyService', true], + 'security.command.debug_firewall' => ['privates', '.security.command.debug_firewall.lazy', 'get_Security_Command_DebugFirewall_LazyService', true], + 'security.command.user_password_hash' => ['privates', '.security.command.user_password_hash.lazy', 'get_Security_Command_UserPasswordHash_LazyService', true], + 'lexik_jwt_authentication.check_config_command' => ['privates', '.lexik_jwt_authentication.check_config_command.lazy', 'get_LexikJwtAuthentication_CheckConfigCommand_LazyService', true], + 'lexik_jwt_authentication.migrate_config_command' => ['privates', '.lexik_jwt_authentication.migrate_config_command.lazy', 'get_LexikJwtAuthentication_MigrateConfigCommand_LazyService', true], + 'lexik_jwt_authentication.enable_encryption_config_command' => ['privates', '.lexik_jwt_authentication.enable_encryption_config_command.lazy', 'get_LexikJwtAuthentication_EnableEncryptionConfigCommand_LazyService', true], + 'lexik_jwt_authentication.generate_token_command' => ['privates', '.lexik_jwt_authentication.generate_token_command.lazy', 'get_LexikJwtAuthentication_GenerateTokenCommand_LazyService', true], + 'lexik_jwt_authentication.generate_keypair_command' => ['privates', '.lexik_jwt_authentication.generate_keypair_command.lazy', 'get_LexikJwtAuthentication_GenerateKeypairCommand_LazyService', true], + 'twig.command.debug' => ['privates', '.twig.command.debug.lazy', 'get_Twig_Command_Debug_LazyService', true], + 'twig.command.lint' => ['privates', '.twig.command.lint.lazy', 'get_Twig_Command_Lint_LazyService', true], + 'maker.auto_command.make_auth' => ['privates', '.maker.auto_command.make_auth.lazy', 'get_Maker_AutoCommand_MakeAuth_LazyService', true], + 'maker.auto_command.make_command' => ['privates', '.maker.auto_command.make_command.lazy', 'get_Maker_AutoCommand_MakeCommand_LazyService', true], + 'maker.auto_command.make_twig_component' => ['privates', '.maker.auto_command.make_twig_component.lazy', 'get_Maker_AutoCommand_MakeTwigComponent_LazyService', true], + 'maker.auto_command.make_controller' => ['privates', '.maker.auto_command.make_controller.lazy', 'get_Maker_AutoCommand_MakeController_LazyService', true], + 'maker.auto_command.make_crud' => ['privates', '.maker.auto_command.make_crud.lazy', 'get_Maker_AutoCommand_MakeCrud_LazyService', true], + 'maker.auto_command.make_docker_database' => ['privates', '.maker.auto_command.make_docker_database.lazy', 'get_Maker_AutoCommand_MakeDockerDatabase_LazyService', true], + 'maker.auto_command.make_entity' => ['privates', '.maker.auto_command.make_entity.lazy', 'get_Maker_AutoCommand_MakeEntity_LazyService', true], + 'maker.auto_command.make_fixtures' => ['privates', '.maker.auto_command.make_fixtures.lazy', 'get_Maker_AutoCommand_MakeFixtures_LazyService', true], + 'maker.auto_command.make_form' => ['privates', '.maker.auto_command.make_form.lazy', 'get_Maker_AutoCommand_MakeForm_LazyService', true], + 'maker.auto_command.make_listener' => ['privates', '.maker.auto_command.make_listener.lazy', 'get_Maker_AutoCommand_MakeListener_LazyService', true], + 'maker.auto_command.make_message' => ['privates', '.maker.auto_command.make_message.lazy', 'get_Maker_AutoCommand_MakeMessage_LazyService', true], + 'maker.auto_command.make_messenger_middleware' => ['privates', '.maker.auto_command.make_messenger_middleware.lazy', 'get_Maker_AutoCommand_MakeMessengerMiddleware_LazyService', true], + 'maker.auto_command.make_registration_form' => ['privates', '.maker.auto_command.make_registration_form.lazy', 'get_Maker_AutoCommand_MakeRegistrationForm_LazyService', true], + 'maker.auto_command.make_reset_password' => ['privates', '.maker.auto_command.make_reset_password.lazy', 'get_Maker_AutoCommand_MakeResetPassword_LazyService', true], + 'maker.auto_command.make_schedule' => ['privates', '.maker.auto_command.make_schedule.lazy', 'get_Maker_AutoCommand_MakeSchedule_LazyService', true], + 'maker.auto_command.make_serializer_encoder' => ['privates', '.maker.auto_command.make_serializer_encoder.lazy', 'get_Maker_AutoCommand_MakeSerializerEncoder_LazyService', true], + 'maker.auto_command.make_serializer_normalizer' => ['privates', '.maker.auto_command.make_serializer_normalizer.lazy', 'get_Maker_AutoCommand_MakeSerializerNormalizer_LazyService', true], + 'maker.auto_command.make_twig_extension' => ['privates', '.maker.auto_command.make_twig_extension.lazy', 'get_Maker_AutoCommand_MakeTwigExtension_LazyService', true], + 'maker.auto_command.make_test' => ['privates', '.maker.auto_command.make_test.lazy', 'get_Maker_AutoCommand_MakeTest_LazyService', true], + 'maker.auto_command.make_validator' => ['privates', '.maker.auto_command.make_validator.lazy', 'get_Maker_AutoCommand_MakeValidator_LazyService', true], + 'maker.auto_command.make_voter' => ['privates', '.maker.auto_command.make_voter.lazy', 'get_Maker_AutoCommand_MakeVoter_LazyService', true], + 'maker.auto_command.make_user' => ['privates', '.maker.auto_command.make_user.lazy', 'get_Maker_AutoCommand_MakeUser_LazyService', true], + 'maker.auto_command.make_migration' => ['privates', '.maker.auto_command.make_migration.lazy', 'get_Maker_AutoCommand_MakeMigration_LazyService', true], + 'maker.auto_command.make_stimulus_controller' => ['privates', '.maker.auto_command.make_stimulus_controller.lazy', 'get_Maker_AutoCommand_MakeStimulusController_LazyService', true], + 'maker.auto_command.make_security_form_login' => ['privates', '.maker.auto_command.make_security_form_login.lazy', 'get_Maker_AutoCommand_MakeSecurityFormLogin_LazyService', true], + 'maker.auto_command.make_security_custom' => ['privates', '.maker.auto_command.make_security_custom.lazy', 'get_Maker_AutoCommand_MakeSecurityCustom_LazyService', true], + 'maker.auto_command.make_webhook' => ['privates', '.maker.auto_command.make_webhook.lazy', 'get_Maker_AutoCommand_MakeWebhook_LazyService', true], + ], [ + 'console.command.about' => '?', + 'console.command.assets_install' => '?', + 'console.command.cache_clear' => '?', + 'console.command.cache_pool_clear' => '?', + 'console.command.cache_pool_prune' => '?', + 'console.command.cache_pool_invalidate_tags' => '?', + 'console.command.cache_pool_delete' => '?', + 'console.command.cache_pool_list' => '?', + 'console.command.cache_warmup' => '?', + 'console.command.config_debug' => '?', + 'console.command.config_dump_reference' => '?', + 'console.command.container_debug' => '?', + 'console.command.container_lint' => '?', + 'console.command.debug_autowiring' => '?', + 'console.command.dotenv_debug' => '?', + 'console.command.event_dispatcher_debug' => '?', + 'console.command.router_debug' => '?', + 'console.command.router_match' => '?', + 'console.command.yaml_lint' => '?', + 'console.command.form_debug' => '?', + 'console.command.secrets_set' => '?', + 'console.command.secrets_remove' => '?', + 'console.command.secrets_generate_key' => '?', + 'console.command.secrets_list' => '?', + 'console.command.secrets_reveal' => '?', + 'console.command.secrets_decrypt_to_local' => '?', + 'console.command.secrets_encrypt_from_local' => '?', + 'console.command.mailer_test' => '?', + 'doctrine.database_create_command' => 'Doctrine\\Bundle\\DoctrineBundle\\Command\\CreateDatabaseDoctrineCommand', + 'doctrine.database_drop_command' => 'Doctrine\\Bundle\\DoctrineBundle\\Command\\DropDatabaseDoctrineCommand', + 'doctrine.query_sql_command' => 'Doctrine\\Bundle\\DoctrineBundle\\Command\\Proxy\\RunSqlDoctrineCommand', + 'Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand' => 'Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand', + 'doctrine.cache_clear_metadata_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\ClearCache\\MetadataCommand', + 'doctrine.cache_clear_query_cache_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\ClearCache\\QueryCommand', + 'doctrine.cache_clear_result_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\ClearCache\\ResultCommand', + 'doctrine.cache_collection_region_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\ClearCache\\CollectionRegionCommand', + 'doctrine.schema_create_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\SchemaTool\\CreateCommand', + 'doctrine.schema_drop_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\SchemaTool\\DropCommand', + 'doctrine.clear_entity_region_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\ClearCache\\EntityRegionCommand', + 'doctrine.mapping_info_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\InfoCommand', + 'doctrine.clear_query_region_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\ClearCache\\QueryRegionCommand', + 'doctrine.query_dql_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\RunDqlCommand', + 'doctrine.schema_update_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\SchemaTool\\UpdateCommand', + 'doctrine.schema_validate_command' => 'Doctrine\\ORM\\Tools\\Console\\Command\\ValidateSchemaCommand', + 'doctrine_migrations.diff_command' => '?', + 'doctrine_migrations.sync_metadata_command' => '?', + 'doctrine_migrations.versions_command' => '?', + 'doctrine_migrations.current_command' => '?', + 'doctrine_migrations.dump_schema_command' => '?', + 'doctrine_migrations.execute_command' => '?', + 'doctrine_migrations.generate_command' => '?', + 'doctrine_migrations.latest_command' => '?', + 'doctrine_migrations.migrate_command' => '?', + 'doctrine_migrations.rollup_command' => '?', + 'doctrine_migrations.status_command' => '?', + 'doctrine_migrations.up_to_date_command' => '?', + 'doctrine_migrations.version_command' => '?', + 'security.command.debug_firewall' => '?', + 'security.command.user_password_hash' => '?', + 'lexik_jwt_authentication.check_config_command' => '?', + 'lexik_jwt_authentication.migrate_config_command' => '?', + 'lexik_jwt_authentication.enable_encryption_config_command' => '?', + 'lexik_jwt_authentication.generate_token_command' => '?', + 'lexik_jwt_authentication.generate_keypair_command' => '?', + 'twig.command.debug' => '?', + 'twig.command.lint' => '?', + 'maker.auto_command.make_auth' => '?', + 'maker.auto_command.make_command' => '?', + 'maker.auto_command.make_twig_component' => '?', + 'maker.auto_command.make_controller' => '?', + 'maker.auto_command.make_crud' => '?', + 'maker.auto_command.make_docker_database' => '?', + 'maker.auto_command.make_entity' => '?', + 'maker.auto_command.make_fixtures' => '?', + 'maker.auto_command.make_form' => '?', + 'maker.auto_command.make_listener' => '?', + 'maker.auto_command.make_message' => '?', + 'maker.auto_command.make_messenger_middleware' => '?', + 'maker.auto_command.make_registration_form' => '?', + 'maker.auto_command.make_reset_password' => '?', + 'maker.auto_command.make_schedule' => '?', + 'maker.auto_command.make_serializer_encoder' => '?', + 'maker.auto_command.make_serializer_normalizer' => '?', + 'maker.auto_command.make_twig_extension' => '?', + 'maker.auto_command.make_test' => '?', + 'maker.auto_command.make_validator' => '?', + 'maker.auto_command.make_voter' => '?', + 'maker.auto_command.make_user' => '?', + 'maker.auto_command.make_migration' => '?', + 'maker.auto_command.make_stimulus_controller' => '?', + 'maker.auto_command.make_security_form_login' => '?', + 'maker.auto_command.make_security_custom' => '?', + 'maker.auto_command.make_webhook' => '?', + ]), ['about' => 'console.command.about', 'assets:install' => 'console.command.assets_install', 'cache:clear' => 'console.command.cache_clear', 'cache:pool:clear' => 'console.command.cache_pool_clear', 'cache:pool:prune' => 'console.command.cache_pool_prune', 'cache:pool:invalidate-tags' => 'console.command.cache_pool_invalidate_tags', 'cache:pool:delete' => 'console.command.cache_pool_delete', 'cache:pool:list' => 'console.command.cache_pool_list', 'cache:warmup' => 'console.command.cache_warmup', 'debug:config' => 'console.command.config_debug', 'config:dump-reference' => 'console.command.config_dump_reference', 'debug:container' => 'console.command.container_debug', 'lint:container' => 'console.command.container_lint', 'debug:autowiring' => 'console.command.debug_autowiring', 'debug:dotenv' => 'console.command.dotenv_debug', 'debug:event-dispatcher' => 'console.command.event_dispatcher_debug', 'debug:router' => 'console.command.router_debug', 'router:match' => 'console.command.router_match', 'lint:yaml' => 'console.command.yaml_lint', 'debug:form' => 'console.command.form_debug', 'secrets:set' => 'console.command.secrets_set', 'secrets:remove' => 'console.command.secrets_remove', 'secrets:generate-keys' => 'console.command.secrets_generate_key', 'secrets:list' => 'console.command.secrets_list', 'secrets:reveal' => 'console.command.secrets_reveal', 'secrets:decrypt-to-local' => 'console.command.secrets_decrypt_to_local', 'secrets:encrypt-from-local' => 'console.command.secrets_encrypt_from_local', 'mailer:test' => 'console.command.mailer_test', 'doctrine:database:create' => 'doctrine.database_create_command', 'doctrine:database:drop' => 'doctrine.database_drop_command', 'doctrine:query:sql' => 'doctrine.query_sql_command', 'dbal:run-sql' => 'Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand', 'doctrine:cache:clear-metadata' => 'doctrine.cache_clear_metadata_command', 'doctrine:cache:clear-query' => 'doctrine.cache_clear_query_cache_command', 'doctrine:cache:clear-result' => 'doctrine.cache_clear_result_command', 'doctrine:cache:clear-collection-region' => 'doctrine.cache_collection_region_command', 'doctrine:schema:create' => 'doctrine.schema_create_command', 'doctrine:schema:drop' => 'doctrine.schema_drop_command', 'doctrine:cache:clear-entity-region' => 'doctrine.clear_entity_region_command', 'doctrine:mapping:info' => 'doctrine.mapping_info_command', 'doctrine:cache:clear-query-region' => 'doctrine.clear_query_region_command', 'doctrine:query:dql' => 'doctrine.query_dql_command', 'doctrine:schema:update' => 'doctrine.schema_update_command', 'doctrine:schema:validate' => 'doctrine.schema_validate_command', 'doctrine:migrations:diff' => 'doctrine_migrations.diff_command', 'doctrine:migrations:sync-metadata-storage' => 'doctrine_migrations.sync_metadata_command', 'doctrine:migrations:list' => 'doctrine_migrations.versions_command', 'doctrine:migrations:current' => 'doctrine_migrations.current_command', 'doctrine:migrations:dump-schema' => 'doctrine_migrations.dump_schema_command', 'doctrine:migrations:execute' => 'doctrine_migrations.execute_command', 'doctrine:migrations:generate' => 'doctrine_migrations.generate_command', 'doctrine:migrations:latest' => 'doctrine_migrations.latest_command', 'doctrine:migrations:migrate' => 'doctrine_migrations.migrate_command', 'doctrine:migrations:rollup' => 'doctrine_migrations.rollup_command', 'doctrine:migrations:status' => 'doctrine_migrations.status_command', 'doctrine:migrations:up-to-date' => 'doctrine_migrations.up_to_date_command', 'doctrine:migrations:version' => 'doctrine_migrations.version_command', 'debug:firewall' => 'security.command.debug_firewall', 'security:hash-password' => 'security.command.user_password_hash', 'lexik:jwt:check-config' => 'lexik_jwt_authentication.check_config_command', 'lexik:jwt:migrate-config' => 'lexik_jwt_authentication.migrate_config_command', 'lexik:jwt:enable-encryption' => 'lexik_jwt_authentication.enable_encryption_config_command', 'lexik:jwt:generate-token' => 'lexik_jwt_authentication.generate_token_command', 'lexik:jwt:generate-keypair' => 'lexik_jwt_authentication.generate_keypair_command', 'debug:twig' => 'twig.command.debug', 'lint:twig' => 'twig.command.lint', 'make:auth' => 'maker.auto_command.make_auth', 'make:command' => 'maker.auto_command.make_command', 'make:twig-component' => 'maker.auto_command.make_twig_component', 'make:controller' => 'maker.auto_command.make_controller', 'make:crud' => 'maker.auto_command.make_crud', 'make:docker:database' => 'maker.auto_command.make_docker_database', 'make:entity' => 'maker.auto_command.make_entity', 'make:fixtures' => 'maker.auto_command.make_fixtures', 'make:form' => 'maker.auto_command.make_form', 'make:listener' => 'maker.auto_command.make_listener', 'make:subscriber' => 'maker.auto_command.make_listener', 'make:message' => 'maker.auto_command.make_message', 'make:messenger-middleware' => 'maker.auto_command.make_messenger_middleware', 'make:registration-form' => 'maker.auto_command.make_registration_form', 'make:reset-password' => 'maker.auto_command.make_reset_password', 'make:schedule' => 'maker.auto_command.make_schedule', 'make:serializer:encoder' => 'maker.auto_command.make_serializer_encoder', 'make:serializer:normalizer' => 'maker.auto_command.make_serializer_normalizer', 'make:twig-extension' => 'maker.auto_command.make_twig_extension', 'make:test' => 'maker.auto_command.make_test', 'make:unit-test' => 'maker.auto_command.make_test', 'make:functional-test' => 'maker.auto_command.make_test', 'make:validator' => 'maker.auto_command.make_validator', 'make:voter' => 'maker.auto_command.make_voter', 'make:user' => 'maker.auto_command.make_user', 'make:migration' => 'maker.auto_command.make_migration', 'make:stimulus-controller' => 'maker.auto_command.make_stimulus_controller', 'make:security:form-login' => 'maker.auto_command.make_security_form_login', 'make:security:custom' => 'maker.auto_command.make_security_custom', 'make:webhook' => 'maker.auto_command.make_webhook']); + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_AboutService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_AboutService.php new file mode 100644 index 0000000..b3ce6f1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_AboutService.php @@ -0,0 +1,31 @@ +privates['console.command.about'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\AboutCommand(); + + $instance->setName('about'); + $instance->setDescription('Display information about the current project'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_AssetsInstallService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_AssetsInstallService.php new file mode 100644 index 0000000..ff9ebd5 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_AssetsInstallService.php @@ -0,0 +1,32 @@ +privates['console.command.assets_install'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand(($container->privates['filesystem'] ??= new \Symfony\Component\Filesystem\Filesystem()), \dirname(__DIR__, 4)); + + $instance->setName('assets:install'); + $instance->setDescription('Install bundle\'s web assets under a public directory'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_CacheClearService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_CacheClearService.php new file mode 100644 index 0000000..3fb5deb --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_CacheClearService.php @@ -0,0 +1,36 @@ +privates['console.command.cache_clear'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand(new \Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer(new RewindableGenerator(function () use ($container) { + yield 0 => ($container->services['cache.system_clearer'] ?? $container->load('getCache_SystemClearerService')); + }, 1)), ($container->privates['filesystem'] ??= new \Symfony\Component\Filesystem\Filesystem())); + + $instance->setName('cache:clear'); + $instance->setDescription('Clear the cache'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolClearService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolClearService.php new file mode 100644 index 0000000..f44f6fc --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolClearService.php @@ -0,0 +1,31 @@ +privates['console.command.cache_pool_clear'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand(($container->services['cache.global_clearer'] ?? $container->load('getCache_GlobalClearerService')), ['cache.app', 'cache.system', 'cache.validator', 'cache.serializer', 'cache.property_info', 'cache.doctrine.orm.default.result', 'cache.doctrine.orm.default.query', 'cache.security_expression_language', 'cache.security_is_granted_attribute_expression_language', 'cache.security_is_csrf_token_valid_attribute_expression_language']); + + $instance->setName('cache:pool:clear'); + $instance->setDescription('Clear cache pools'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolDeleteService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolDeleteService.php new file mode 100644 index 0000000..0b4c6d1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolDeleteService.php @@ -0,0 +1,31 @@ +privates['console.command.cache_pool_delete'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand(($container->services['cache.global_clearer'] ?? $container->load('getCache_GlobalClearerService')), ['cache.app', 'cache.system', 'cache.validator', 'cache.serializer', 'cache.property_info', 'cache.doctrine.orm.default.result', 'cache.doctrine.orm.default.query', 'cache.security_expression_language', 'cache.security_is_granted_attribute_expression_language', 'cache.security_is_csrf_token_valid_attribute_expression_language']); + + $instance->setName('cache:pool:delete'); + $instance->setDescription('Delete an item from a cache pool'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolInvalidateTagsService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolInvalidateTagsService.php new file mode 100644 index 0000000..dba0504 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolInvalidateTagsService.php @@ -0,0 +1,35 @@ +privates['console.command.cache_pool_invalidate_tags'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\CachePoolInvalidateTagsCommand(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'cache.app' => ['privates', 'cache.app.taggable', 'getCache_App_TaggableService', true], + ], [ + 'cache.app' => 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter', + ])); + + $instance->setName('cache:pool:invalidate-tags'); + $instance->setDescription('Invalidate cache tags for all or a specific pool'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolListService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolListService.php new file mode 100644 index 0000000..90123f9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolListService.php @@ -0,0 +1,31 @@ +privates['console.command.cache_pool_list'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand(['cache.app', 'cache.system', 'cache.validator', 'cache.serializer', 'cache.property_info', 'cache.doctrine.orm.default.result', 'cache.doctrine.orm.default.query', 'cache.security_expression_language', 'cache.security_is_granted_attribute_expression_language', 'cache.security_is_csrf_token_valid_attribute_expression_language']); + + $instance->setName('cache:pool:list'); + $instance->setDescription('List available cache pools'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolPruneService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolPruneService.php new file mode 100644 index 0000000..ce67b10 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_CachePoolPruneService.php @@ -0,0 +1,33 @@ +privates['console.command.cache_pool_prune'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand(new RewindableGenerator(function () use ($container) { + yield 'cache.app' => ($container->services['cache.app'] ?? $container->load('getCache_AppService')); + }, 1)); + + $instance->setName('cache:pool:prune'); + $instance->setDescription('Prune cache pools'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_CacheWarmupService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_CacheWarmupService.php new file mode 100644 index 0000000..4231e51 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_CacheWarmupService.php @@ -0,0 +1,31 @@ +privates['console.command.cache_warmup'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand(($container->services['cache_warmer'] ?? $container->load('getCacheWarmerService'))); + + $instance->setName('cache:warmup'); + $instance->setDescription('Warm up an empty cache'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_ConfigDebugService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_ConfigDebugService.php new file mode 100644 index 0000000..403d78c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_ConfigDebugService.php @@ -0,0 +1,34 @@ +privates['console.command.config_debug'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand(); + + $instance->setName('debug:config'); + $instance->setDescription('Dump the current configuration for an extension'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_ConfigDumpReferenceService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_ConfigDumpReferenceService.php new file mode 100644 index 0000000..52186f4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_ConfigDumpReferenceService.php @@ -0,0 +1,34 @@ +privates['console.command.config_dump_reference'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand(); + + $instance->setName('config:dump-reference'); + $instance->setDescription('Dump the default configuration for an extension'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_ContainerDebugService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_ContainerDebugService.php new file mode 100644 index 0000000..cc804cd --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_ContainerDebugService.php @@ -0,0 +1,32 @@ +privates['console.command.container_debug'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\ContainerDebugCommand(); + + $instance->setName('debug:container'); + $instance->setDescription('Display current services for an application'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_ContainerLintService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_ContainerLintService.php new file mode 100644 index 0000000..25f97cc --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_ContainerLintService.php @@ -0,0 +1,31 @@ +privates['console.command.container_lint'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand(); + + $instance->setName('lint:container'); + $instance->setDescription('Ensure that arguments injected into services match type declarations'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_DebugAutowiringService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_DebugAutowiringService.php new file mode 100644 index 0000000..deb8c7d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_DebugAutowiringService.php @@ -0,0 +1,34 @@ +privates['console.command.debug_autowiring'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand(NULL, ($container->privates['debug.file_link_formatter'] ??= new \Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter($container->getEnv('default::SYMFONY_IDE')))); + + $instance->setName('debug:autowiring'); + $instance->setDescription('List classes/interfaces you can use for autowiring'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_DotenvDebugService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_DotenvDebugService.php new file mode 100644 index 0000000..3d89152 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_DotenvDebugService.php @@ -0,0 +1,31 @@ +privates['console.command.dotenv_debug'] = $instance = new \Symfony\Component\Dotenv\Command\DebugCommand('dev', \dirname(__DIR__, 4)); + + $instance->setName('debug:dotenv'); + $instance->setDescription('List all dotenv files with variables and values'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_EventDispatcherDebugService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_EventDispatcherDebugService.php new file mode 100644 index 0000000..61a0781 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_EventDispatcherDebugService.php @@ -0,0 +1,31 @@ +privates['console.command.event_dispatcher_debug'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand(($container->privates['.service_locator.YbySDCA'] ?? $container->load('get_ServiceLocator_YbySDCAService'))); + + $instance->setName('debug:event-dispatcher'); + $instance->setDescription('Display configured listeners for an application'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_FormDebugService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_FormDebugService.php new file mode 100644 index 0000000..238b09b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_FormDebugService.php @@ -0,0 +1,32 @@ +privates['console.command.form_debug'] = $instance = new \Symfony\Component\Form\Command\DebugCommand(($container->privates['form.registry'] ?? $container->load('getForm_RegistryService')), ['Symfony\\Component\\Form\\Extension\\Core\\Type', 'Symfony\\Bridge\\Doctrine\\Form\\Type'], ['Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType', 'Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType', 'Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType', 'Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType', 'Symfony\\Bridge\\Doctrine\\Form\\Type\\EntityType'], ['Symfony\\Component\\Form\\Extension\\Core\\Type\\TransformationFailureExtension', 'Symfony\\Component\\Form\\Extension\\HttpFoundation\\Type\\FormTypeHttpFoundationExtension', 'Symfony\\Component\\Form\\Extension\\Validator\\Type\\RepeatedTypeValidatorExtension', 'Symfony\\Component\\Form\\Extension\\Validator\\Type\\SubmitTypeValidatorExtension', 'Symfony\\Component\\Form\\Extension\\Csrf\\Type\\FormTypeCsrfExtension', 'Symfony\\Component\\Form\\Extension\\PasswordHasher\\Type\\FormTypePasswordHasherExtension', 'Symfony\\Component\\Form\\Extension\\PasswordHasher\\Type\\PasswordTypePasswordHasherExtension'], ['Symfony\\Bridge\\Doctrine\\Form\\DoctrineOrmTypeGuesser'], ($container->privates['debug.file_link_formatter'] ??= new \Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter($container->getEnv('default::SYMFONY_IDE')))); + + $instance->setName('debug:form'); + $instance->setDescription('Display form type information'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_MailerTestService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_MailerTestService.php new file mode 100644 index 0000000..76fa600 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_MailerTestService.php @@ -0,0 +1,39 @@ +privates['console.command.mailer_test'] = $instance = new \Symfony\Component\Mailer\Command\MailerTestCommand((new \Symfony\Component\Mailer\Transport(new RewindableGenerator(function () use ($container) { + yield 0 => $container->load('getMailer_TransportFactory_NativeService'); + yield 1 => $container->load('getMailer_TransportFactory_NullService'); + yield 2 => $container->load('getMailer_TransportFactory_SendmailService'); + yield 3 => $container->load('getMailer_TransportFactory_SmtpService'); + }, 4)))->fromStrings(['main' => 'smtp://null'])); + + $instance->setName('mailer:test'); + $instance->setDescription('Test Mailer transports by sending an email'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_RouterDebugService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_RouterDebugService.php new file mode 100644 index 0000000..0494542 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_RouterDebugService.php @@ -0,0 +1,33 @@ +privates['console.command.router_debug'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand(($container->services['router'] ?? self::getRouterService($container)), ($container->privates['debug.file_link_formatter'] ??= new \Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter($container->getEnv('default::SYMFONY_IDE')))); + + $instance->setName('debug:router'); + $instance->setDescription('Display current routes for an application'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_RouterMatchService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_RouterMatchService.php new file mode 100644 index 0000000..bf22e28 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_RouterMatchService.php @@ -0,0 +1,31 @@ +privates['console.command.router_match'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand(($container->services['router'] ?? self::getRouterService($container)), new RewindableGenerator(fn () => new \EmptyIterator(), 0)); + + $instance->setName('router:match'); + $instance->setDescription('Help debug routes by simulating a path info match'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsDecryptToLocalService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsDecryptToLocalService.php new file mode 100644 index 0000000..3258dc6 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsDecryptToLocalService.php @@ -0,0 +1,33 @@ +privates['console.command.secrets_decrypt_to_local'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService')), ($container->privates['secrets.local_vault'] ??= new \Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault((\dirname(__DIR__, 4).'/.env.dev.local')))); + + $instance->setName('secrets:decrypt-to-local'); + $instance->setDescription('Decrypt all secrets and stores them in the local vault'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsEncryptFromLocalService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsEncryptFromLocalService.php new file mode 100644 index 0000000..2e76306 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsEncryptFromLocalService.php @@ -0,0 +1,33 @@ +privates['console.command.secrets_encrypt_from_local'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService')), ($container->privates['secrets.local_vault'] ??= new \Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault((\dirname(__DIR__, 4).'/.env.dev.local')))); + + $instance->setName('secrets:encrypt-from-local'); + $instance->setDescription('Encrypt all local secrets to the vault'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsGenerateKeyService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsGenerateKeyService.php new file mode 100644 index 0000000..2b76572 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsGenerateKeyService.php @@ -0,0 +1,33 @@ +privates['console.command.secrets_generate_key'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService')), ($container->privates['secrets.local_vault'] ??= new \Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault((\dirname(__DIR__, 4).'/.env.dev.local')))); + + $instance->setName('secrets:generate-keys'); + $instance->setDescription('Generate new encryption keys'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsListService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsListService.php new file mode 100644 index 0000000..52e5839 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsListService.php @@ -0,0 +1,33 @@ +privates['console.command.secrets_list'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService')), ($container->privates['secrets.local_vault'] ??= new \Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault((\dirname(__DIR__, 4).'/.env.dev.local')))); + + $instance->setName('secrets:list'); + $instance->setDescription('List all secrets'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsRemoveService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsRemoveService.php new file mode 100644 index 0000000..4a3aff7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsRemoveService.php @@ -0,0 +1,33 @@ +privates['console.command.secrets_remove'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService')), ($container->privates['secrets.local_vault'] ??= new \Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault((\dirname(__DIR__, 4).'/.env.dev.local')))); + + $instance->setName('secrets:remove'); + $instance->setDescription('Remove a secret from the vault'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsRevealService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsRevealService.php new file mode 100644 index 0000000..d6f5dfb --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsRevealService.php @@ -0,0 +1,33 @@ +privates['console.command.secrets_reveal'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService')), ($container->privates['secrets.local_vault'] ??= new \Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault((\dirname(__DIR__, 4).'/.env.dev.local')))); + + $instance->setName('secrets:reveal'); + $instance->setDescription('Reveal the value of a secret'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsSetService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsSetService.php new file mode 100644 index 0000000..9db8e0a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_SecretsSetService.php @@ -0,0 +1,33 @@ +privates['console.command.secrets_set'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService')), ($container->privates['secrets.local_vault'] ??= new \Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault((\dirname(__DIR__, 4).'/.env.dev.local')))); + + $instance->setName('secrets:set'); + $instance->setDescription('Set a secret in the vault'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_Command_YamlLintService.php b/var/cache/dev/Container6Szx89D/getConsole_Command_YamlLintService.php new file mode 100644 index 0000000..4cd7937 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_Command_YamlLintService.php @@ -0,0 +1,32 @@ +privates['console.command.yaml_lint'] = $instance = new \Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand(); + + $instance->setName('lint:yaml'); + $instance->setDescription('Lint a YAML file and outputs encountered errors'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getConsole_ErrorListenerService.php b/var/cache/dev/Container6Szx89D/getConsole_ErrorListenerService.php new file mode 100644 index 0000000..23b5df3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getConsole_ErrorListenerService.php @@ -0,0 +1,25 @@ +privates['console.error_listener'] = new \Symfony\Component\Console\EventListener\ErrorListener(($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getContainer_EnvVarProcessorService.php b/var/cache/dev/Container6Szx89D/getContainer_EnvVarProcessorService.php new file mode 100644 index 0000000..87ac18b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getContainer_EnvVarProcessorService.php @@ -0,0 +1,28 @@ +privates['container.env_var_processor'] = new \Symfony\Component\DependencyInjection\EnvVarProcessor($container, new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['secrets.env_var_loader'] ?? $container->load('getSecrets_EnvVarLoaderService')); + }, 1)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getContainer_EnvVarProcessorsLocatorService.php b/var/cache/dev/Container6Szx89D/getContainer_EnvVarProcessorsLocatorService.php new file mode 100644 index 0000000..f51f8f9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getContainer_EnvVarProcessorsLocatorService.php @@ -0,0 +1,67 @@ +services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'base64' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'bool' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'not' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'const' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'csv' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'file' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'float' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'int' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'json' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'key' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'url' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'query_string' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'resolve' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'default' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'string' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'trim' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'require' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'enum' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'shuffle' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'defined' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + 'urlencode' => ['privates', 'container.env_var_processor', 'getContainer_EnvVarProcessorService', true], + ], [ + 'base64' => '?', + 'bool' => '?', + 'not' => '?', + 'const' => '?', + 'csv' => '?', + 'file' => '?', + 'float' => '?', + 'int' => '?', + 'json' => '?', + 'key' => '?', + 'url' => '?', + 'query_string' => '?', + 'resolve' => '?', + 'default' => '?', + 'string' => '?', + 'trim' => '?', + 'require' => '?', + 'enum' => '?', + 'shuffle' => '?', + 'defined' => '?', + 'urlencode' => '?', + ]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getContainer_GetRoutingConditionServiceService.php b/var/cache/dev/Container6Szx89D/getContainer_GetRoutingConditionServiceService.php new file mode 100644 index 0000000..b313eae --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getContainer_GetRoutingConditionServiceService.php @@ -0,0 +1,23 @@ +services['container.get_routing_condition_service'] = (new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [], []))->get(...); + } +} diff --git a/var/cache/dev/Container6Szx89D/getController_TemplateAttributeListenerService.php b/var/cache/dev/Container6Szx89D/getController_TemplateAttributeListenerService.php new file mode 100644 index 0000000..3034612 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getController_TemplateAttributeListenerService.php @@ -0,0 +1,31 @@ +privates['twig'] ?? $container->load('getTwigService')); + + if (isset($container->privates['controller.template_attribute_listener'])) { + return $container->privates['controller.template_attribute_listener']; + } + + return $container->privates['controller.template_attribute_listener'] = new \Symfony\Bridge\Twig\EventListener\TemplateAttributeListener($a); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDebug_ErrorHandlerConfiguratorService.php b/var/cache/dev/Container6Szx89D/getDebug_ErrorHandlerConfiguratorService.php new file mode 100644 index 0000000..3ee4e41 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDebug_ErrorHandlerConfiguratorService.php @@ -0,0 +1,25 @@ +services['debug.error_handler_configurator'] = new \Symfony\Component\HttpKernel\Debug\ErrorHandlerConfigurator(($container->privates['logger'] ?? self::getLoggerService($container)), NULL, -1, true, true, NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDebug_Security_Firewall_Authenticator_MainService.php b/var/cache/dev/Container6Szx89D/getDebug_Security_Firewall_Authenticator_MainService.php new file mode 100644 index 0000000..8bbe72e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDebug_Security_Firewall_Authenticator_MainService.php @@ -0,0 +1,29 @@ +privates['debug.security.firewall.authenticator.main'] = new \Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener(new \Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener(new \Symfony\Component\Security\Http\Authentication\AuthenticatorManager([], ($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)), ($container->privates['debug.security.event_dispatcher.main'] ?? self::getDebug_Security_EventDispatcher_MainService($container)), 'main', ($container->privates['logger'] ?? self::getLoggerService($container)), true, true, []))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDebug_Security_Voter_VoteListenerService.php b/var/cache/dev/Container6Szx89D/getDebug_Security_Voter_VoteListenerService.php new file mode 100644 index 0000000..90bf794 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDebug_Security_Voter_VoteListenerService.php @@ -0,0 +1,31 @@ +privates['debug.security.access.decision_manager'] ?? self::getDebug_Security_Access_DecisionManagerService($container)); + + if (isset($container->privates['debug.security.voter.vote_listener'])) { + return $container->privates['debug.security.voter.vote_listener']; + } + + return $container->privates['debug.security.voter.vote_listener'] = new \Symfony\Bundle\SecurityBundle\EventListener\VoteListener($a); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_CurrentCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_CurrentCommandService.php new file mode 100644 index 0000000..a8206d3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_CurrentCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.current_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\CurrentCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:current'); + + $instance->setName('doctrine:migrations:current'); + $instance->setDescription('Outputs the current version'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_DiffCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_DiffCommandService.php new file mode 100644 index 0000000..c3fcefc --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_DiffCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.diff_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\DiffCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:diff'); + + $instance->setName('doctrine:migrations:diff'); + $instance->setDescription('Generate a migration by comparing your current database to your mapping information.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_DumpSchemaCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_DumpSchemaCommandService.php new file mode 100644 index 0000000..9a170a1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_DumpSchemaCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.dump_schema_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:dump-schema'); + + $instance->setName('doctrine:migrations:dump-schema'); + $instance->setDescription('Dump the schema for your database to a migration.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_ExecuteCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_ExecuteCommandService.php new file mode 100644 index 0000000..e8e984a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_ExecuteCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.execute_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\ExecuteCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:execute'); + + $instance->setName('doctrine:migrations:execute'); + $instance->setDescription('Execute one or more migration versions up or down manually.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_GenerateCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_GenerateCommandService.php new file mode 100644 index 0000000..e671dfa --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_GenerateCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.generate_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\GenerateCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:generate'); + + $instance->setName('doctrine:migrations:generate'); + $instance->setDescription('Generate a blank migration class.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_LatestCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_LatestCommandService.php new file mode 100644 index 0000000..20eb0a5 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_LatestCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.latest_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\LatestCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:latest'); + + $instance->setName('doctrine:migrations:latest'); + $instance->setDescription('Outputs the latest version'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_MigrateCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_MigrateCommandService.php new file mode 100644 index 0000000..74b6c57 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_MigrateCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.migrate_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\MigrateCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:migrate'); + + $instance->setName('doctrine:migrations:migrate'); + $instance->setDescription('Execute a migration to a specified version or the latest available version.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_RollupCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_RollupCommandService.php new file mode 100644 index 0000000..3225a81 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_RollupCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.rollup_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\RollupCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:rollup'); + + $instance->setName('doctrine:migrations:rollup'); + $instance->setDescription('Rollup migrations by deleting all tracked versions and insert the one version that exists.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_StatusCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_StatusCommandService.php new file mode 100644 index 0000000..9ded6bc --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_StatusCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.status_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\StatusCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:status'); + + $instance->setName('doctrine:migrations:status'); + $instance->setDescription('View the status of a set of migrations.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_SyncMetadataCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_SyncMetadataCommandService.php new file mode 100644 index 0000000..d215a3e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_SyncMetadataCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.sync_metadata_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:sync-metadata-storage'); + + $instance->setName('doctrine:migrations:sync-metadata-storage'); + $instance->setDescription('Ensures that the metadata storage is at the latest version.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_UpToDateCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_UpToDateCommandService.php new file mode 100644 index 0000000..1eed0b8 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_UpToDateCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.up_to_date_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\UpToDateCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:up-to-date'); + + $instance->setName('doctrine:migrations:up-to-date'); + $instance->setDescription('Tells you if your schema is up-to-date.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_VersionCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_VersionCommandService.php new file mode 100644 index 0000000..9ffe181 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_VersionCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.version_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\VersionCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:version'); + + $instance->setName('doctrine:migrations:version'); + $instance->setDescription('Manually add and delete migration versions from the version table.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineMigrations_VersionsCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_VersionsCommandService.php new file mode 100644 index 0000000..16d3867 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineMigrations_VersionsCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine_migrations.versions_command'] = $instance = new \Doctrine\Migrations\Tools\Console\Command\ListCommand(($container->privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')), 'doctrine:migrations:versions'); + + $instance->setName('doctrine:migrations:list'); + $instance->setDescription('Display a list of all available migrations and their status.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrineService.php b/var/cache/dev/Container6Szx89D/getDoctrineService.php new file mode 100644 index 0000000..d309c18 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrineService.php @@ -0,0 +1,29 @@ +services['doctrine'] = new \Doctrine\Bundle\DoctrineBundle\Registry($container, $container->parameters['doctrine.connections'], $container->parameters['doctrine.entity_managers'], 'default', 'default'); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearMetadataCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearMetadataCommandService.php new file mode 100644 index 0000000..78acdfb --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearMetadataCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.cache_clear_metadata_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:cache:clear-metadata'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearQueryCacheCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearQueryCacheCommandService.php new file mode 100644 index 0000000..c95bd58 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearQueryCacheCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.cache_clear_query_cache_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:cache:clear-query'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearResultCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearResultCommandService.php new file mode 100644 index 0000000..56d0fa1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_CacheClearResultCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.cache_clear_result_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:cache:clear-result'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_CacheCollectionRegionCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_CacheCollectionRegionCommandService.php new file mode 100644 index 0000000..40e7201 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_CacheCollectionRegionCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.cache_collection_region_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\ClearCache\CollectionRegionCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:cache:clear-collection-region'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_ClearEntityRegionCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_ClearEntityRegionCommandService.php new file mode 100644 index 0000000..c564c3b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_ClearEntityRegionCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.clear_entity_region_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\ClearCache\EntityRegionCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:cache:clear-entity-region'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_ClearQueryRegionCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_ClearQueryRegionCommandService.php new file mode 100644 index 0000000..114b219 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_ClearQueryRegionCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.clear_query_region_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryRegionCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:cache:clear-query-region'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_DatabaseCreateCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_DatabaseCreateCommandService.php new file mode 100644 index 0000000..9386a97 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_DatabaseCreateCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.database_create_command'] = $instance = new \Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + + $instance->setName('doctrine:database:create'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_DatabaseDropCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_DatabaseDropCommandService.php new file mode 100644 index 0000000..24ed081 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_DatabaseDropCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.database_drop_command'] = $instance = new \Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + + $instance->setName('doctrine:database:drop'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Dbal_DefaultConnectionService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Dbal_DefaultConnectionService.php new file mode 100644 index 0000000..b33746a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Dbal_DefaultConnectionService.php @@ -0,0 +1,50 @@ +privates['doctrine.debug_data_holder'] ??= new \Doctrine\Bundle\DoctrineBundle\Middleware\BacktraceDebugDataHolder(['default'])), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + $b->setConnectionName('default'); + $c = new \Doctrine\Bundle\DoctrineBundle\Middleware\IdleConnectionMiddleware(($container->privates['doctrine.dbal.connection_expiries'] ??= new \ArrayObject()), ['default' => 600]); + $c->setConnectionName('default'); + + $a->setSchemaManagerFactory(new \Doctrine\DBAL\Schema\LegacySchemaManagerFactory()); + $a->setMiddlewares([$b, $c]); + + $container->services['doctrine.dbal.default_connection'] = $instance = (new \Doctrine\Bundle\DoctrineBundle\ConnectionFactory([], new \Doctrine\DBAL\Tools\DsnParser(['db2' => 'ibm_db2', 'mssql' => 'pdo_sqlsrv', 'mysql' => 'pdo_mysql', 'mysql2' => 'pdo_mysql', 'postgres' => 'pdo_pgsql', 'postgresql' => 'pdo_pgsql', 'pgsql' => 'pdo_pgsql', 'sqlite' => 'pdo_sqlite', 'sqlite3' => 'pdo_sqlite'])))->createConnection(['url' => $container->getEnv('resolve:DATABASE_URL'), 'use_savepoints' => true, 'driver' => 'pdo_mysql', 'idle_connection_ttl' => 600, 'host' => 'localhost', 'port' => NULL, 'user' => 'root', 'password' => NULL, 'driverOptions' => [], 'defaultTableOptions' => []], $a, ($container->privates['doctrine.dbal.default_connection.event_manager'] ?? $container->load('getDoctrine_Dbal_DefaultConnection_EventManagerService')), []); + + $instance->setNestTransactionsWithSavepoints(true); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Dbal_DefaultConnection_EventManagerService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Dbal_DefaultConnection_EventManagerService.php new file mode 100644 index 0000000..6c2cece --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Dbal_DefaultConnection_EventManagerService.php @@ -0,0 +1,38 @@ +privates['doctrine.dbal.default_connection.event_manager'] = new \Symfony\Bridge\Doctrine\ContainerAwareEventManager(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener' => ['privates', 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener', 'getDoctrine_Orm_Listeners_DoctrineDbalCacheAdapterSchemaListenerService', true], + 'doctrine.orm.listeners.doctrine_token_provider_schema_listener' => ['privates', 'doctrine.orm.listeners.doctrine_token_provider_schema_listener', 'getDoctrine_Orm_Listeners_DoctrineTokenProviderSchemaListenerService', true], + 'doctrine.orm.listeners.pdo_session_handler_schema_listener' => ['privates', 'doctrine.orm.listeners.pdo_session_handler_schema_listener', 'getDoctrine_Orm_Listeners_PdoSessionHandlerSchemaListenerService', true], + 'doctrine.orm.listeners.lock_store_schema_listener' => ['privates', 'doctrine.orm.listeners.lock_store_schema_listener', 'getDoctrine_Orm_Listeners_LockStoreSchemaListenerService', true], + 'doctrine.orm.default_listeners.attach_entity_listeners' => ['privates', 'doctrine.orm.default_listeners.attach_entity_listeners', 'getDoctrine_Orm_DefaultListeners_AttachEntityListenersService', true], + ], [ + 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener' => '?', + 'doctrine.orm.listeners.doctrine_token_provider_schema_listener' => '?', + 'doctrine.orm.listeners.pdo_session_handler_schema_listener' => '?', + 'doctrine.orm.listeners.lock_store_schema_listener' => '?', + 'doctrine.orm.default_listeners.attach_entity_listeners' => '?', + ]), [[['postGenerateSchema'], 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener'], [['postGenerateSchema'], 'doctrine.orm.listeners.doctrine_token_provider_schema_listener'], [['postGenerateSchema'], 'doctrine.orm.listeners.pdo_session_handler_schema_listener'], [['postGenerateSchema'], 'doctrine.orm.listeners.lock_store_schema_listener'], [['loadClassMetadata'], 'doctrine.orm.default_listeners.attach_entity_listeners']]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_MappingInfoCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_MappingInfoCommandService.php new file mode 100644 index 0000000..ebab276 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_MappingInfoCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.mapping_info_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\InfoCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:mapping:info'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Migrations_DependencyFactoryService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Migrations_DependencyFactoryService.php new file mode 100644 index 0000000..11140d7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Migrations_DependencyFactoryService.php @@ -0,0 +1,43 @@ +addMigrationsDirectory('DoctrineMigrations', (\dirname(__DIR__, 4).'/migrations')); + $a->setAllOrNothing(false); + $a->setCheckDatabasePlatform(true); + $a->setTransactional(true); + $a->setMetadataStorageConfiguration(new \Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration()); + + $container->privates['doctrine.migrations.dependency_factory'] = $instance = \Doctrine\Migrations\DependencyFactory::fromEntityManager(new \Doctrine\Migrations\Configuration\Migration\ExistingConfiguration($a), \Doctrine\Migrations\Configuration\EntityManager\ManagerRegistryEntityManager::withSimpleDefault(($container->services['doctrine'] ?? $container->load('getDoctrineService'))), ($container->privates['logger'] ?? self::getLoggerService($container))); + + $instance->setDefinition('Doctrine\\Migrations\\Version\\MigrationFactory', #[\Closure(name: 'doctrine.migrations.migrations_factory', class: 'Doctrine\\Migrations\\Version\\MigrationFactory')] fn () => ($container->privates['doctrine.migrations.migrations_factory'] ?? $container->load('getDoctrine_Migrations_MigrationsFactoryService'))); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Migrations_MigrationsFactoryService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Migrations_MigrationsFactoryService.php new file mode 100644 index 0000000..a810943 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Migrations_MigrationsFactoryService.php @@ -0,0 +1,31 @@ +privates['doctrine.migrations.dependency_factory'] ?? $container->load('getDoctrine_Migrations_DependencyFactoryService')); + + if (isset($container->privates['doctrine.migrations.migrations_factory'])) { + return $container->privates['doctrine.migrations.migrations_factory']; + } + + return $container->privates['doctrine.migrations.migrations_factory'] = $a->getMigrationFactory(); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Command_EntityManagerProviderService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Command_EntityManagerProviderService.php new file mode 100644 index 0000000..0cdb00c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Command_EntityManagerProviderService.php @@ -0,0 +1,26 @@ +privates['doctrine.orm.command.entity_manager_provider'] = new \Doctrine\Bundle\DoctrineBundle\Orm\ManagerRegistryAwareEntityManagerProvider(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_DefaultEntityManagerService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_DefaultEntityManagerService.php new file mode 100644 index 0000000..a9f1620 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_DefaultEntityManagerService.php @@ -0,0 +1,103 @@ +services['doctrine.orm.default_entity_manager'] = $container->createProxy('EntityManagerGhost614a58f', static fn () => \EntityManagerGhost614a58f::createLazyGhost(static fn ($proxy) => self::do($container, $proxy))); + } + + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Proxy/Autoloader.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/persistence/src/Persistence/ObjectManager.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/EntityManagerInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/EntityManager.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/dbal/src/Configuration.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Configuration.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/cache/src/CacheItemPoolInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/cache/Adapter/AdapterInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/cache-contracts/CacheInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/log/src/LoggerAwareInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/cache/ResettableInterface.php'; + include_once \dirname(__DIR__, 4).'/vendor/psr/log/src/LoggerAwareTrait.php'; + include_once \dirname(__DIR__, 4).'/vendor/symfony/cache/Adapter/ArrayAdapter.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriver.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/src/Mapping/MappingDriver.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriverChain.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/NamingStrategy.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/QuoteStrategy.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Internal/SQLResultCasing.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/src/Mapping/EntityListenerServiceResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/src/Mapping/ContainerEntityListenerResolver.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Repository/RepositoryFactory.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/src/Repository/RepositoryFactoryCompatibility.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/src/Repository/ContainerRepositoryFactory.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/src/ManagerConfigurator.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/ColocatedMappingDriver.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php'; + include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php'; + + $a = new \Doctrine\ORM\Configuration(); + + $b = new \Doctrine\Persistence\Mapping\Driver\MappingDriverChain(); + $b->addDriver(($container->privates['doctrine.orm.default_attribute_metadata_driver'] ??= new \Doctrine\ORM\Mapping\Driver\AttributeDriver([(\dirname(__DIR__, 4).'/src/Entity')], true)), 'App\\Entity'); + + $a->setEntityNamespaces(['App' => 'App\\Entity']); + $a->setMetadataCache(new \Symfony\Component\Cache\Adapter\ArrayAdapter()); + $a->setQueryCache(($container->privates['cache.doctrine.orm.default.query'] ??= new \Symfony\Component\Cache\Adapter\ArrayAdapter())); + $a->setResultCache(($container->privates['cache.doctrine.orm.default.result'] ??= new \Symfony\Component\Cache\Adapter\ArrayAdapter())); + $a->setMetadataDriverImpl(new \Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver($b, new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'doctrine.ulid_generator' => ['privates', 'doctrine.ulid_generator', 'getDoctrine_UlidGeneratorService', true], + 'doctrine.uuid_generator' => ['privates', 'doctrine.uuid_generator', 'getDoctrine_UuidGeneratorService', true], + ], [ + 'doctrine.ulid_generator' => '?', + 'doctrine.uuid_generator' => '?', + ]))); + $a->setProxyDir(($container->targetDir.''.'/doctrine/orm/Proxies')); + $a->setProxyNamespace('Proxies'); + $a->setAutoGenerateProxyClasses(true); + $a->setSchemaIgnoreClasses([]); + $a->setClassMetadataFactoryName('Doctrine\\Bundle\\DoctrineBundle\\Mapping\\ClassMetadataFactory'); + $a->setDefaultRepositoryClassName('Doctrine\\ORM\\EntityRepository'); + $a->setNamingStrategy(new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(0, true)); + $a->setQuoteStrategy(new \Doctrine\ORM\Mapping\DefaultQuoteStrategy()); + $a->setTypedFieldMapper(new \Doctrine\ORM\Mapping\DefaultTypedFieldMapper()); + $a->setEntityListenerResolver(new \Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver($container)); + $a->setLazyGhostObjectEnabled(true); + $a->setIdentityGenerationPreferences([]); + $a->setRepositoryFactory(new \Doctrine\Bundle\DoctrineBundle\Repository\ContainerRepositoryFactory(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'App\\Repository\\CommentRepository' => ['privates', 'App\\Repository\\CommentRepository', 'getCommentRepositoryService', true], + 'App\\Repository\\IdRepository' => ['privates', 'App\\Repository\\IdRepository', 'getIdRepositoryService', true], + 'App\\Repository\\UserRepository' => ['privates', 'App\\Repository\\UserRepository', 'getUserRepositoryService', true], + ], [ + 'App\\Repository\\CommentRepository' => '?', + 'App\\Repository\\IdRepository' => '?', + 'App\\Repository\\UserRepository' => '?', + ]))); + + $instance = ($lazyLoad->__construct(($container->services['doctrine.dbal.default_connection'] ?? $container->load('getDoctrine_Dbal_DefaultConnectionService')), $a, ($container->privates['doctrine.dbal.default_connection.event_manager'] ?? $container->load('getDoctrine_Dbal_DefaultConnection_EventManagerService'))) && false ?: $lazyLoad); + + (new \Doctrine\Bundle\DoctrineBundle\ManagerConfigurator([], []))->configure($instance); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_DefaultListeners_AttachEntityListenersService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_DefaultListeners_AttachEntityListenersService.php new file mode 100644 index 0000000..25a5abf --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_DefaultListeners_AttachEntityListenersService.php @@ -0,0 +1,25 @@ +privates['doctrine.orm.default_listeners.attach_entity_listeners'] = new \Doctrine\ORM\Tools\AttachEntityListenersListener(); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineDbalCacheAdapterSchemaListenerService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineDbalCacheAdapterSchemaListenerService.php new file mode 100644 index 0000000..8fc0a8c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineDbalCacheAdapterSchemaListenerService.php @@ -0,0 +1,26 @@ +privates['doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener'] = new \Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaListener([]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineTokenProviderSchemaListenerService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineTokenProviderSchemaListenerService.php new file mode 100644 index 0000000..28e1178 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_DoctrineTokenProviderSchemaListenerService.php @@ -0,0 +1,26 @@ +privates['doctrine.orm.listeners.doctrine_token_provider_schema_listener'] = new \Symfony\Bridge\Doctrine\SchemaListener\RememberMeTokenProviderDoctrineSchemaListener(new RewindableGenerator(fn () => new \EmptyIterator(), 0)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_LockStoreSchemaListenerService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_LockStoreSchemaListenerService.php new file mode 100644 index 0000000..b458186 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_LockStoreSchemaListenerService.php @@ -0,0 +1,26 @@ +privates['doctrine.orm.listeners.lock_store_schema_listener'] = new \Symfony\Bridge\Doctrine\SchemaListener\LockStoreSchemaListener(new RewindableGenerator(fn () => new \EmptyIterator(), 0)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_PdoSessionHandlerSchemaListenerService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_PdoSessionHandlerSchemaListenerService.php new file mode 100644 index 0000000..0fccf3a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_Listeners_PdoSessionHandlerSchemaListenerService.php @@ -0,0 +1,26 @@ +privates['doctrine.orm.listeners.pdo_session_handler_schema_listener'] = new \Symfony\Bridge\Doctrine\SchemaListener\PdoSessionHandlerSchemaListener(($container->privates['session.handler.native'] ?? $container->load('getSession_Handler_NativeService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_Orm_ProxyCacheWarmerService.php b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_ProxyCacheWarmerService.php new file mode 100644 index 0000000..6bce1be --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_Orm_ProxyCacheWarmerService.php @@ -0,0 +1,26 @@ +privates['doctrine.orm.proxy_cache_warmer'] = new \Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_QueryDqlCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_QueryDqlCommandService.php new file mode 100644 index 0000000..b6d8549 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_QueryDqlCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.query_dql_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:query:dql'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_QuerySqlCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_QuerySqlCommandService.php new file mode 100644 index 0000000..7e3d544 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_QuerySqlCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine.query_sql_command'] = $instance = new \Doctrine\Bundle\DoctrineBundle\Command\Proxy\RunSqlDoctrineCommand(($container->privates['Doctrine\\Bundle\\DoctrineBundle\\Dbal\\ManagerRegistryAwareConnectionProvider'] ?? $container->load('getManagerRegistryAwareConnectionProviderService'))); + + $instance->setName('doctrine:query:sql'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_SchemaCreateCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaCreateCommandService.php new file mode 100644 index 0000000..9eea352 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaCreateCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine.schema_create_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:schema:create'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_SchemaDropCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaDropCommandService.php new file mode 100644 index 0000000..5230b6b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaDropCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine.schema_drop_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:schema:drop'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_SchemaUpdateCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaUpdateCommandService.php new file mode 100644 index 0000000..6e99a4d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaUpdateCommandService.php @@ -0,0 +1,32 @@ +privates['doctrine.schema_update_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:schema:update'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_SchemaValidateCommandService.php b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaValidateCommandService.php new file mode 100644 index 0000000..a325efb --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_SchemaValidateCommandService.php @@ -0,0 +1,31 @@ +privates['doctrine.schema_validate_command'] = $instance = new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(($container->privates['doctrine.orm.command.entity_manager_provider'] ?? $container->load('getDoctrine_Orm_Command_EntityManagerProviderService'))); + + $instance->setName('doctrine:schema:validate'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_UlidGeneratorService.php b/var/cache/dev/Container6Szx89D/getDoctrine_UlidGeneratorService.php new file mode 100644 index 0000000..ee83b28 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_UlidGeneratorService.php @@ -0,0 +1,26 @@ +privates['doctrine.ulid_generator'] = new \Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator(NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getDoctrine_UuidGeneratorService.php b/var/cache/dev/Container6Szx89D/getDoctrine_UuidGeneratorService.php new file mode 100644 index 0000000..9e67e08 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getDoctrine_UuidGeneratorService.php @@ -0,0 +1,26 @@ +privates['doctrine.uuid_generator'] = new \Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator(NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getErrorControllerService.php b/var/cache/dev/Container6Szx89D/getErrorControllerService.php new file mode 100644 index 0000000..612e965 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getErrorControllerService.php @@ -0,0 +1,31 @@ +services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()); + + return $container->services['error_controller'] = new \Symfony\Component\HttpKernel\Controller\ErrorController(($container->services['http_kernel'] ?? self::getHttpKernelService($container)), 'error_controller', new \Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer(($container->privates['twig'] ?? $container->load('getTwigService')), new \Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer(\Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer::isDebug($a, true), 'UTF-8', ($container->privates['debug.file_link_formatter'] ??= new \Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter($container->getEnv('default::SYMFONY_IDE'))), \dirname(__DIR__, 4), \Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer::getAndCleanOutputBuffer($a), ($container->privates['logger'] ?? self::getLoggerService($container))), \Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer::isDebug($a, true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_ChoiceListFactory_CachedService.php b/var/cache/dev/Container6Szx89D/getForm_ChoiceListFactory_CachedService.php new file mode 100644 index 0000000..b328901 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_ChoiceListFactory_CachedService.php @@ -0,0 +1,28 @@ +privates['form.choice_list_factory.cached'] = new \Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator(new \Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator(new \Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory(), ($container->privates['property_accessor'] ?? $container->load('getPropertyAccessorService')))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_FactoryService.php b/var/cache/dev/Container6Szx89D/getForm_FactoryService.php new file mode 100644 index 0000000..6154e16 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_FactoryService.php @@ -0,0 +1,26 @@ +privates['form.factory'] = new \Symfony\Component\Form\FormFactory(($container->privates['form.registry'] ?? $container->load('getForm_RegistryService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_Listener_PasswordHasherService.php b/var/cache/dev/Container6Szx89D/getForm_Listener_PasswordHasherService.php new file mode 100644 index 0000000..98e089d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_Listener_PasswordHasherService.php @@ -0,0 +1,27 @@ +privates['form.listener.password_hasher'] = new \Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener(new \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher(($container->privates['security.password_hasher_factory'] ?? $container->load('getSecurity_PasswordHasherFactoryService'))), ($container->privates['property_accessor'] ?? $container->load('getPropertyAccessorService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_RegistryService.php b/var/cache/dev/Container6Szx89D/getForm_RegistryService.php new file mode 100644 index 0000000..f575242 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_RegistryService.php @@ -0,0 +1,55 @@ +privates['form.registry'] = new \Symfony\Component\Form\FormRegistry([new \Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType' => ['privates', 'form.type.form', 'getForm_Type_FormService', true], + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType' => ['privates', 'form.type.choice', 'getForm_Type_ChoiceService', true], + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType' => ['privates', 'form.type.file', 'getForm_Type_FileService', true], + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType' => ['privates', 'form.type.color', 'getForm_Type_ColorService', true], + 'Symfony\\Bridge\\Doctrine\\Form\\Type\\EntityType' => ['privates', 'form.type.entity', 'getForm_Type_EntityService', true], + ], [ + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType' => '?', + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType' => '?', + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType' => '?', + 'Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType' => '?', + 'Symfony\\Bridge\\Doctrine\\Form\\Type\\EntityType' => '?', + ]), ['Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType' => new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['form.type_extension.form.transformation_failure_handling'] ??= new \Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension(NULL)); + yield 1 => ($container->privates['form.type_extension.form.http_foundation'] ?? $container->load('getForm_TypeExtension_Form_HttpFoundationService')); + yield 2 => ($container->privates['form.type_extension.csrf'] ?? $container->load('getForm_TypeExtension_CsrfService')); + yield 3 => ($container->privates['form.type_extension.form.password_hasher'] ?? $container->load('getForm_TypeExtension_Form_PasswordHasherService')); + }, 4), 'Symfony\\Component\\Form\\Extension\\Core\\Type\\RepeatedType' => new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['form.type_extension.repeated.validator'] ??= new \Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension()); + }, 1), 'Symfony\\Component\\Form\\Extension\\Core\\Type\\SubmitType' => new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['form.type_extension.submit.validator'] ??= new \Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension()); + }, 1), 'Symfony\\Component\\Form\\Extension\\Core\\Type\\PasswordType' => new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['form.type_extension.password.password_hasher'] ?? $container->load('getForm_TypeExtension_Password_PasswordHasherService')); + }, 1)], new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['form.type_guesser.doctrine'] ?? $container->load('getForm_TypeGuesser_DoctrineService')); + }, 1))], new \Symfony\Component\Form\ResolvedFormTypeFactory()); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_ServerParamsService.php b/var/cache/dev/Container6Szx89D/getForm_ServerParamsService.php new file mode 100644 index 0000000..c43ddcb --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_ServerParamsService.php @@ -0,0 +1,25 @@ +privates['form.server_params'] = new \Symfony\Component\Form\Util\ServerParams(($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack())); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_TypeExtension_CsrfService.php b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_CsrfService.php new file mode 100644 index 0000000..4cde870 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_CsrfService.php @@ -0,0 +1,27 @@ +privates['form.type_extension.csrf'] = new \Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension(($container->privates['security.csrf.token_manager'] ?? self::getSecurity_Csrf_TokenManagerService($container)), true, '_token', NULL, 'validators', ($container->privates['form.server_params'] ?? $container->load('getForm_ServerParamsService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Form_HttpFoundationService.php b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Form_HttpFoundationService.php new file mode 100644 index 0000000..29fd89a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Form_HttpFoundationService.php @@ -0,0 +1,29 @@ +privates['form.type_extension.form.http_foundation'] = new \Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension(new \Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler(($container->privates['form.server_params'] ?? $container->load('getForm_ServerParamsService')))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Form_PasswordHasherService.php b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Form_PasswordHasherService.php new file mode 100644 index 0000000..69ee585 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Form_PasswordHasherService.php @@ -0,0 +1,27 @@ +privates['form.type_extension.form.password_hasher'] = new \Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension(($container->privates['form.listener.password_hasher'] ?? $container->load('getForm_Listener_PasswordHasherService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Password_PasswordHasherService.php b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Password_PasswordHasherService.php new file mode 100644 index 0000000..9892cfa --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_TypeExtension_Password_PasswordHasherService.php @@ -0,0 +1,27 @@ +privates['form.type_extension.password.password_hasher'] = new \Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension(($container->privates['form.listener.password_hasher'] ?? $container->load('getForm_Listener_PasswordHasherService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_TypeGuesser_DoctrineService.php b/var/cache/dev/Container6Szx89D/getForm_TypeGuesser_DoctrineService.php new file mode 100644 index 0000000..3e27d62 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_TypeGuesser_DoctrineService.php @@ -0,0 +1,26 @@ +privates['form.type_guesser.doctrine'] = new \Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_Type_ChoiceService.php b/var/cache/dev/Container6Szx89D/getForm_Type_ChoiceService.php new file mode 100644 index 0000000..a9524aa --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_Type_ChoiceService.php @@ -0,0 +1,27 @@ +privates['form.type.choice'] = new \Symfony\Component\Form\Extension\Core\Type\ChoiceType(($container->privates['form.choice_list_factory.cached'] ?? $container->load('getForm_ChoiceListFactory_CachedService')), NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_Type_ColorService.php b/var/cache/dev/Container6Szx89D/getForm_Type_ColorService.php new file mode 100644 index 0000000..5718d48 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_Type_ColorService.php @@ -0,0 +1,27 @@ +privates['form.type.color'] = new \Symfony\Component\Form\Extension\Core\Type\ColorType(NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_Type_EntityService.php b/var/cache/dev/Container6Szx89D/getForm_Type_EntityService.php new file mode 100644 index 0000000..6ea4814 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_Type_EntityService.php @@ -0,0 +1,28 @@ +privates['form.type.entity'] = new \Symfony\Bridge\Doctrine\Form\Type\EntityType(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_Type_FileService.php b/var/cache/dev/Container6Szx89D/getForm_Type_FileService.php new file mode 100644 index 0000000..da7d026 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_Type_FileService.php @@ -0,0 +1,27 @@ +privates['form.type.file'] = new \Symfony\Component\Form\Extension\Core\Type\FileType(NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getForm_Type_FormService.php b/var/cache/dev/Container6Szx89D/getForm_Type_FormService.php new file mode 100644 index 0000000..3f75ed9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getForm_Type_FormService.php @@ -0,0 +1,28 @@ +privates['form.type.form'] = new \Symfony\Component\Form\Extension\Core\Type\FormType(($container->privates['property_accessor'] ?? $container->load('getPropertyAccessorService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getFragment_Renderer_InlineService.php b/var/cache/dev/Container6Szx89D/getFragment_Renderer_InlineService.php new file mode 100644 index 0000000..8c4af80 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getFragment_Renderer_InlineService.php @@ -0,0 +1,42 @@ +services['http_kernel'] ?? self::getHttpKernelService($container)); + + if (isset($container->privates['fragment.renderer.inline'])) { + return $container->privates['fragment.renderer.inline']; + } + $b = ($container->services['event_dispatcher'] ?? self::getEventDispatcherService($container)); + + if (isset($container->privates['fragment.renderer.inline'])) { + return $container->privates['fragment.renderer.inline']; + } + + $container->privates['fragment.renderer.inline'] = $instance = new \Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer($a, $b); + + $instance->setFragmentPath('/_fragment'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getHttpClient_TransportService.php b/var/cache/dev/Container6Szx89D/getHttpClient_TransportService.php new file mode 100644 index 0000000..aef9b15 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getHttpClient_TransportService.php @@ -0,0 +1,30 @@ +privates['http_client.transport'] = $instance = \Symfony\Component\HttpClient\HttpClient::create([], 6); + + $instance->setLogger(($container->privates['logger'] ?? self::getLoggerService($container))); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getHttpClient_UriTemplateService.php b/var/cache/dev/Container6Szx89D/getHttpClient_UriTemplateService.php new file mode 100644 index 0000000..5bce276 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getHttpClient_UriTemplateService.php @@ -0,0 +1,27 @@ +privates['http_client.uri_template'] = new \Symfony\Component\HttpClient\UriTemplateHttpClient(($container->privates['http_client.transport'] ?? $container->load('getHttpClient_TransportService')), NULL, []); + } +} diff --git a/var/cache/dev/Container6Szx89D/getHwiOauth_Security_OauthUtilsService.php b/var/cache/dev/Container6Szx89D/getHwiOauth_Security_OauthUtilsService.php new file mode 100644 index 0000000..038e06f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getHwiOauth_Security_OauthUtilsService.php @@ -0,0 +1,31 @@ +privates['security.authorization_checker'] ?? self::getSecurity_AuthorizationCheckerService($container)); + + if (isset($container->privates['hwi_oauth.security.oauth_utils'])) { + return $container->privates['hwi_oauth.security.oauth_utils']; + } + + return $container->privates['hwi_oauth.security.oauth_utils'] = new \HWI\Bundle\OAuthBundle\Security\OAuthUtils(($container->privates['security.http_utils'] ?? $container->load('getSecurity_HttpUtilsService')), $a, ($container->privates['security.firewall.map'] ?? self::getSecurity_Firewall_MapService($container)), false, 'IS_AUTHENTICATED_REMEMBERED'); + } +} diff --git a/var/cache/dev/Container6Szx89D/getHwiOauth_Twig_Extension_Oauth_RuntimeService.php b/var/cache/dev/Container6Szx89D/getHwiOauth_Twig_Extension_Oauth_RuntimeService.php new file mode 100644 index 0000000..43e30de --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getHwiOauth_Twig_Extension_Oauth_RuntimeService.php @@ -0,0 +1,32 @@ +privates['hwi_oauth.security.oauth_utils'] ?? $container->load('getHwiOauth_Security_OauthUtilsService')); + + if (isset($container->privates['hwi_oauth.twig.extension.oauth.runtime'])) { + return $container->privates['hwi_oauth.twig.extension.oauth.runtime']; + } + + return $container->privates['hwi_oauth.twig.extension.oauth.runtime'] = new \HWI\Bundle\OAuthBundle\Twig\Extension\OAuthRuntime($a, ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack())); + } +} diff --git a/var/cache/dev/Container6Szx89D/getHwiOauth_UserCheckerService.php b/var/cache/dev/Container6Szx89D/getHwiOauth_UserCheckerService.php new file mode 100644 index 0000000..06b6dc9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getHwiOauth_UserCheckerService.php @@ -0,0 +1,26 @@ +services['hwi_oauth.user_checker'] = new \Symfony\Component\Security\Core\User\InMemoryUserChecker(); + } +} diff --git a/var/cache/dev/Container6Szx89D/getIdRepositoryService.php b/var/cache/dev/Container6Szx89D/getIdRepositoryService.php new file mode 100644 index 0000000..116ef9f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getIdRepositoryService.php @@ -0,0 +1,31 @@ +privates['App\\Repository\\IdRepository'] = new \App\Repository\IdRepository(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_CheckConfigCommandService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_CheckConfigCommandService.php new file mode 100644 index 0000000..c6e90bc --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_CheckConfigCommandService.php @@ -0,0 +1,31 @@ +privates['lexik_jwt_authentication.check_config_command'] = $instance = new \Lexik\Bundle\JWTAuthenticationBundle\Command\CheckConfigCommand(($container->services['lexik_jwt_authentication.key_loader'] ?? $container->load('getLexikJwtAuthentication_KeyLoaderService')), 'RS256'); + + $instance->setName('lexik:jwt:check-config'); + $instance->setDescription('Checks that the bundle is properly configured.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_EnableEncryptionConfigCommandService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_EnableEncryptionConfigCommandService.php new file mode 100644 index 0000000..e179287 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_EnableEncryptionConfigCommandService.php @@ -0,0 +1,34 @@ +privates['lexik_jwt_authentication.enable_encryption_config_command'] = $instance = new \Lexik\Bundle\JWTAuthenticationBundle\Command\EnableEncryptionConfigCommand(NULL); + + $instance->setName('lexik:jwt:enable-encryption'); + $instance->setDescription('Enable Web-Token encryption support.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_EncoderService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_EncoderService.php new file mode 100644 index 0000000..e2cc889 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_EncoderService.php @@ -0,0 +1,29 @@ +services['lexik_jwt_authentication.encoder'] = new \Lexik\Bundle\JWTAuthenticationBundle\Encoder\LcobucciJWTEncoder(new \Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\LcobucciJWSProvider(($container->services['lexik_jwt_authentication.key_loader'] ?? $container->load('getLexikJwtAuthentication_KeyLoaderService')), 'RS256', 3600, 0, false)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_GenerateKeypairCommandService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_GenerateKeypairCommandService.php new file mode 100644 index 0000000..715a484 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_GenerateKeypairCommandService.php @@ -0,0 +1,32 @@ +privates['lexik_jwt_authentication.generate_keypair_command'] = $instance = new \Lexik\Bundle\JWTAuthenticationBundle\Command\GenerateKeyPairCommand(($container->privates['filesystem'] ??= new \Symfony\Component\Filesystem\Filesystem()), $container->getEnv('resolve:JWT_SECRET_KEY'), $container->getEnv('resolve:JWT_PUBLIC_KEY'), $container->getEnv('JWT_PASSPHRASE'), 'RS256'); + + $instance->setName('lexik:jwt:generate-keypair'); + $instance->setDescription('Generate public/private keys for use in your application.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_GenerateTokenCommandService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_GenerateTokenCommandService.php new file mode 100644 index 0000000..9635d0f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_GenerateTokenCommandService.php @@ -0,0 +1,33 @@ +services['lexik_jwt_authentication.generate_token_command'] = $instance = new \Lexik\Bundle\JWTAuthenticationBundle\Command\GenerateTokenCommand(($container->services['lexik_jwt_authentication.jwt_manager'] ?? $container->load('getLexikJwtAuthentication_JwtManagerService')), new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['security.user.provider.concrete.users_in_memory'] ??= new \Symfony\Component\Security\Core\User\InMemoryUserProvider([])); + }, 1)); + + $instance->setName('lexik:jwt:generate-token'); + $instance->setDescription('Generates a JWT token for a given user.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_JwtManagerService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_JwtManagerService.php new file mode 100644 index 0000000..3dd8429 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_JwtManagerService.php @@ -0,0 +1,28 @@ +services['lexik_jwt_authentication.jwt_manager'] = new \Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManager(($container->services['lexik_jwt_authentication.encoder'] ?? $container->load('getLexikJwtAuthentication_EncoderService')), ($container->services['event_dispatcher'] ?? self::getEventDispatcherService($container)), 'username', new \Lexik\Bundle\JWTAuthenticationBundle\Services\PayloadEnrichment\ChainEnrichment([])); + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_KeyLoaderService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_KeyLoaderService.php new file mode 100644 index 0000000..61e0025 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_KeyLoaderService.php @@ -0,0 +1,28 @@ +services['lexik_jwt_authentication.key_loader'] = new \Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\RawKeyLoader($container->getEnv('resolve:JWT_SECRET_KEY'), $container->getEnv('resolve:JWT_PUBLIC_KEY'), $container->getEnv('JWT_PASSPHRASE'), []); + } +} diff --git a/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_MigrateConfigCommandService.php b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_MigrateConfigCommandService.php new file mode 100644 index 0000000..488795c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLexikJwtAuthentication_MigrateConfigCommandService.php @@ -0,0 +1,34 @@ +privates['lexik_jwt_authentication.migrate_config_command'] = $instance = new \Lexik\Bundle\JWTAuthenticationBundle\Command\MigrateConfigCommand(($container->services['lexik_jwt_authentication.key_loader'] ?? $container->load('getLexikJwtAuthentication_KeyLoaderService')), $container->getEnv('JWT_PASSPHRASE'), 'RS256'); + + $instance->setName('lexik:jwt:migrate-config'); + $instance->setDescription('Migrate LexikJWTAuthenticationBundle configuration to the Web-Token one.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getLoaderInterfaceService.php b/var/cache/dev/Container6Szx89D/getLoaderInterfaceService.php new file mode 100644 index 0000000..e3f1896 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getLoaderInterfaceService.php @@ -0,0 +1,23 @@ +services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()); + + return $container->services['HWI\\Bundle\\OAuthBundle\\Controller\\LoginController'] = new \HWI\Bundle\OAuthBundle\Controller\LoginController(new \Symfony\Component\Security\Http\Authentication\AuthenticationUtils($a), ($container->services['router'] ?? self::getRouterService($container)), ($container->privates['security.authorization_checker'] ?? self::getSecurity_AuthorizationCheckerService($container)), $a, ($container->privates['twig'] ?? $container->load('getTwigService')), false, 'IS_AUTHENTICATED_REMEMBERED'); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_NativeService.php b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_NativeService.php new file mode 100644 index 0000000..8495d13 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_NativeService.php @@ -0,0 +1,27 @@ +services['event_dispatcher'] ?? self::getEventDispatcherService($container)), ($container->privates['http_client.uri_template'] ?? $container->load('getHttpClient_UriTemplateService')), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_NullService.php b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_NullService.php new file mode 100644 index 0000000..c12d94d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_NullService.php @@ -0,0 +1,27 @@ +services['event_dispatcher'] ?? self::getEventDispatcherService($container)), ($container->privates['http_client.uri_template'] ?? $container->load('getHttpClient_UriTemplateService')), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_SendmailService.php b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_SendmailService.php new file mode 100644 index 0000000..477f8ce --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_SendmailService.php @@ -0,0 +1,27 @@ +services['event_dispatcher'] ?? self::getEventDispatcherService($container)), ($container->privates['http_client.uri_template'] ?? $container->load('getHttpClient_UriTemplateService')), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_SmtpService.php b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_SmtpService.php new file mode 100644 index 0000000..05960a2 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMailer_TransportFactory_SmtpService.php @@ -0,0 +1,27 @@ +services['event_dispatcher'] ?? self::getEventDispatcherService($container)), ($container->privates['http_client.uri_template'] ?? $container->load('getHttpClient_UriTemplateService')), ($container->privates['logger'] ?? self::getLoggerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeAuthService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeAuthService.php new file mode 100644 index 0000000..78c3e0e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeAuthService.php @@ -0,0 +1,40 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + $b = ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')); + + $container->privates['maker.auto_command.make_auth'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeAuthenticator($a, ($container->privates['maker.security_config_updater'] ??= new \Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater()), $b, ($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService')), ($container->privates['maker.security_controller_builder'] ??= new \Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder())), $a, $b, ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:auth'); + $instance->setDescription('Create a Guard authenticator of different flavors'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeCommandService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeCommandService.php new file mode 100644 index 0000000..34b6026 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeCommandService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_command'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeCommand(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:command'); + $instance->setDescription('Create a new console command class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeControllerService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeControllerService.php new file mode 100644 index 0000000..47ffac1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeControllerService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_controller'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeController(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:controller'); + $instance->setDescription('Create a new controller class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeCrudService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeCrudService.php new file mode 100644 index 0000000..6b2d5c3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeCrudService.php @@ -0,0 +1,36 @@ +privates['maker.auto_command.make_crud'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeCrud(($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService')), ($container->privates['maker.renderer.form_type_renderer'] ?? $container->load('getMaker_Renderer_FormTypeRendererService'))), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:crud'); + $instance->setDescription('Create CRUD for Doctrine entity class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeDockerDatabaseService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeDockerDatabaseService.php new file mode 100644 index 0000000..76407d3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeDockerDatabaseService.php @@ -0,0 +1,37 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_docker_database'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeDockerDatabase($a), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:docker:database'); + $instance->setDescription('Add a database container to your compose.yaml file'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeEntityService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeEntityService.php new file mode 100644 index 0000000..9706252 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeEntityService.php @@ -0,0 +1,40 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + $b = ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')); + + $container->privates['maker.auto_command.make_entity'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeEntity($a, ($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService')), NULL, $b, ($container->privates['maker.entity_class_generator'] ?? $container->load('getMaker_EntityClassGeneratorService'))), $a, $b, ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:entity'); + $instance->setDescription('Create or update a Doctrine entity class, and optionally an API Platform resource'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeFixturesService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeFixturesService.php new file mode 100644 index 0000000..b17d23d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeFixturesService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_fixtures'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeFixtures(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:fixtures'); + $instance->setDescription('Create a new class to load Doctrine fixtures'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeFormService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeFormService.php new file mode 100644 index 0000000..ce928b7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeFormService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_form'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeForm(($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService')), ($container->privates['maker.renderer.form_type_renderer'] ?? $container->load('getMaker_Renderer_FormTypeRendererService'))), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:form'); + $instance->setDescription('Create a new form class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeListenerService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeListenerService.php new file mode 100644 index 0000000..2fe1dd1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeListenerService.php @@ -0,0 +1,37 @@ +privates['maker.auto_command.make_listener'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeListener(new \Symfony\Bundle\MakerBundle\EventRegistry(($container->services['event_dispatcher'] ?? self::getEventDispatcherService($container)))), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:listener'); + $instance->setAliases(['make:subscriber']); + $instance->setDescription('Creates a new event subscriber class or a new event listener class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMessageService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMessageService.php new file mode 100644 index 0000000..be6d2cd --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMessageService.php @@ -0,0 +1,37 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_message'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeMessage($a), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:message'); + $instance->setDescription('Create a new message and handler'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMessengerMiddlewareService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMessengerMiddlewareService.php new file mode 100644 index 0000000..ea0719d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMessengerMiddlewareService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_messenger_middleware'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeMessengerMiddleware(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:messenger-middleware'); + $instance->setDescription('Create a new messenger middleware'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMigrationService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMigrationService.php new file mode 100644 index 0000000..5de455d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeMigrationService.php @@ -0,0 +1,36 @@ +privates['maker.auto_command.make_migration'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeMigration(\dirname(__DIR__, 4), ($container->privates['maker.file_link_formatter'] ?? $container->load('getMaker_FileLinkFormatterService'))), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:migration'); + $instance->setDescription('Create a new migration based on database changes'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeRegistrationFormService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeRegistrationFormService.php new file mode 100644 index 0000000..ee2c4b6 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeRegistrationFormService.php @@ -0,0 +1,38 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_registration_form'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeRegistrationForm($a, ($container->privates['maker.renderer.form_type_renderer'] ?? $container->load('getMaker_Renderer_FormTypeRendererService')), ($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService')), ($container->services['router'] ?? self::getRouterService($container))), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:registration-form'); + $instance->setDescription('Create a new registration form system'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeResetPasswordService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeResetPasswordService.php new file mode 100644 index 0000000..ccef81c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeResetPasswordService.php @@ -0,0 +1,39 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_reset_password'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeResetPassword($a, ($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService')), ($container->privates['maker.entity_class_generator'] ?? $container->load('getMaker_EntityClassGeneratorService')), ($container->services['router'] ?? self::getRouterService($container))), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:reset-password'); + $instance->setDescription('Create controller, entity, and repositories for use with symfonycasts/reset-password-bundle'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeScheduleService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeScheduleService.php new file mode 100644 index 0000000..4d0c942 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeScheduleService.php @@ -0,0 +1,37 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_schedule'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeSchedule($a), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:schedule'); + $instance->setDescription('Create a scheduler component'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSecurityCustomService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSecurityCustomService.php new file mode 100644 index 0000000..86a52ff --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSecurityCustomService.php @@ -0,0 +1,39 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + $b = ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')); + + $container->privates['maker.auto_command.make_security_custom'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\Security\MakeCustomAuthenticator($a, $b), $a, $b, ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:security:custom'); + $instance->setDescription('Create a custom security authenticator.'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSecurityFormLoginService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSecurityFormLoginService.php new file mode 100644 index 0000000..fb369b2 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSecurityFormLoginService.php @@ -0,0 +1,40 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_security_form_login'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\Security\MakeFormLogin($a, ($container->privates['maker.security_config_updater'] ??= new \Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater()), ($container->privates['maker.security_controller_builder'] ??= new \Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder())), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:security:form-login'); + $instance->setDescription('Generate the code needed for the form_login authenticator'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSerializerEncoderService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSerializerEncoderService.php new file mode 100644 index 0000000..7fdc4eb --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSerializerEncoderService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_serializer_encoder'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeSerializerEncoder(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:serializer:encoder'); + $instance->setDescription('Create a new serializer encoder class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSerializerNormalizerService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSerializerNormalizerService.php new file mode 100644 index 0000000..d347c79 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeSerializerNormalizerService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_serializer_normalizer'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeSerializerNormalizer(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:serializer:normalizer'); + $instance->setDescription('Create a new serializer normalizer class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeStimulusControllerService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeStimulusControllerService.php new file mode 100644 index 0000000..ff5dc73 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeStimulusControllerService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_stimulus_controller'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeStimulusController(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:stimulus-controller'); + $instance->setDescription('Create a new Stimulus controller'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTestService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTestService.php new file mode 100644 index 0000000..3e6f543 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTestService.php @@ -0,0 +1,37 @@ +privates['maker.auto_command.make_test'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeTest(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:test'); + $instance->setAliases(['make:unit-test', 'make:functional-test']); + $instance->setDescription('Create a new test class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTwigComponentService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTwigComponentService.php new file mode 100644 index 0000000..1371f62 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTwigComponentService.php @@ -0,0 +1,37 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_twig_component'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeTwigComponent($a), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:twig-component'); + $instance->setDescription('Create a twig (or live) component'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTwigExtensionService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTwigExtensionService.php new file mode 100644 index 0000000..89c39e7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeTwigExtensionService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_twig_extension'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeTwigExtension(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:twig-extension'); + $instance->setDescription('Create a new Twig extension with its runtime class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeUserService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeUserService.php new file mode 100644 index 0000000..1f955d0 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeUserService.php @@ -0,0 +1,40 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + + $container->privates['maker.auto_command.make_user'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeUser($a, new \Symfony\Bundle\MakerBundle\Security\UserClassBuilder(), ($container->privates['maker.security_config_updater'] ??= new \Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater()), ($container->privates['maker.entity_class_generator'] ?? $container->load('getMaker_EntityClassGeneratorService')), ($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService'))), $a, ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:user'); + $instance->setDescription('Create a new security user class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeValidatorService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeValidatorService.php new file mode 100644 index 0000000..62460bc --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeValidatorService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_validator'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeValidator(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:validator'); + $instance->setDescription('Create a new validator and constraint class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeVoterService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeVoterService.php new file mode 100644 index 0000000..419592a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeVoterService.php @@ -0,0 +1,35 @@ +privates['maker.auto_command.make_voter'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeVoter(), ($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:voter'); + $instance->setDescription('Create a new security voter class'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeWebhookService.php b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeWebhookService.php new file mode 100644 index 0000000..d8a742a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_AutoCommand_MakeWebhookService.php @@ -0,0 +1,40 @@ +privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')); + $b = ($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')); + + $container->privates['maker.auto_command.make_webhook'] = $instance = new \Symfony\Bundle\MakerBundle\Command\MakerCommand(new \Symfony\Bundle\MakerBundle\Maker\MakeWebhook($a, $b), $a, $b, ($container->privates['maker.template_linter'] ??= new \Symfony\Bundle\MakerBundle\Util\TemplateLinter($container->getEnv('default::string:MAKER_PHP_CS_FIXER_BINARY_PATH'), $container->getEnv('default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH')))); + + $instance->setName('make:webhook'); + $instance->setDescription('Create a new Webhook'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_DoctrineHelperService.php b/var/cache/dev/Container6Szx89D/getMaker_DoctrineHelperService.php new file mode 100644 index 0000000..85104be --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_DoctrineHelperService.php @@ -0,0 +1,29 @@ +privates['maker.doctrine_helper'] = new \Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper('App\\Entity', ($container->services['doctrine'] ?? $container->load('getDoctrineService')), ['default' => [['App\\Entity', ($container->privates['doctrine.orm.default_attribute_metadata_driver'] ??= new \Doctrine\ORM\Mapping\Driver\AttributeDriver([(\dirname(__DIR__, 4).'/src/Entity')], true))]]]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_EntityClassGeneratorService.php b/var/cache/dev/Container6Szx89D/getMaker_EntityClassGeneratorService.php new file mode 100644 index 0000000..1c66cca --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_EntityClassGeneratorService.php @@ -0,0 +1,25 @@ +privates['maker.entity_class_generator'] = new \Symfony\Bundle\MakerBundle\Doctrine\EntityClassGenerator(($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService')), ($container->privates['maker.doctrine_helper'] ?? $container->load('getMaker_DoctrineHelperService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_FileLinkFormatterService.php b/var/cache/dev/Container6Szx89D/getMaker_FileLinkFormatterService.php new file mode 100644 index 0000000..3444734 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_FileLinkFormatterService.php @@ -0,0 +1,26 @@ +privates['maker.file_link_formatter'] = new \Symfony\Bundle\MakerBundle\Util\MakerFileLinkFormatter(($container->privates['debug.file_link_formatter'] ??= new \Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter($container->getEnv('default::SYMFONY_IDE')))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_FileManagerService.php b/var/cache/dev/Container6Szx89D/getMaker_FileManagerService.php new file mode 100644 index 0000000..73a83c7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_FileManagerService.php @@ -0,0 +1,28 @@ +privates['maker.file_manager'] = new \Symfony\Bundle\MakerBundle\FileManager(($container->privates['filesystem'] ??= new \Symfony\Component\Filesystem\Filesystem()), new \Symfony\Bundle\MakerBundle\Util\AutoloaderUtil(new \Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder('App')), ($container->privates['maker.file_link_formatter'] ?? $container->load('getMaker_FileLinkFormatterService')), \dirname(__DIR__, 4), (\dirname(__DIR__, 4).'/templates')); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_GeneratorService.php b/var/cache/dev/Container6Szx89D/getMaker_GeneratorService.php new file mode 100644 index 0000000..c1476cf --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_GeneratorService.php @@ -0,0 +1,26 @@ +privates['maker.generator'] = new \Symfony\Bundle\MakerBundle\Generator(($container->privates['maker.file_manager'] ?? $container->load('getMaker_FileManagerService')), 'App', NULL, new \Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator(true, false, 'App')); + } +} diff --git a/var/cache/dev/Container6Szx89D/getMaker_Renderer_FormTypeRendererService.php b/var/cache/dev/Container6Szx89D/getMaker_Renderer_FormTypeRendererService.php new file mode 100644 index 0000000..f9eb9b4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getMaker_Renderer_FormTypeRendererService.php @@ -0,0 +1,25 @@ +privates['maker.renderer.form_type_renderer'] = new \Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer(($container->privates['maker.generator'] ?? $container->load('getMaker_GeneratorService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getManagerRegistryAwareConnectionProviderService.php b/var/cache/dev/Container6Szx89D/getManagerRegistryAwareConnectionProviderService.php new file mode 100644 index 0000000..8639769 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getManagerRegistryAwareConnectionProviderService.php @@ -0,0 +1,31 @@ +privates['Doctrine\\Bundle\\DoctrineBundle\\Dbal\\ManagerRegistryAwareConnectionProvider'] = new \Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider(new \Doctrine\Bundle\DoctrineBundle\Registry($container, $container->parameters['doctrine.connections'], $container->parameters['doctrine.entity_managers'], 'default', 'default')); + } +} diff --git a/var/cache/dev/Container6Szx89D/getPropertyAccessorService.php b/var/cache/dev/Container6Szx89D/getPropertyAccessorService.php new file mode 100644 index 0000000..98cb40f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getPropertyAccessorService.php @@ -0,0 +1,43 @@ +privates['property_accessor'] = new \Symfony\Component\PropertyAccess\PropertyAccessor(3, 2, new \Symfony\Component\Cache\Adapter\ArrayAdapter(0, false), $a, $a); + } +} diff --git a/var/cache/dev/Container6Szx89D/getRedirectControllerService.php b/var/cache/dev/Container6Szx89D/getRedirectControllerService.php new file mode 100644 index 0000000..d9351d1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getRedirectControllerService.php @@ -0,0 +1,27 @@ +privates['router.request_context'] ?? self::getRouter_RequestContextService($container)); + + return $container->services['Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController'] = new \Symfony\Bundle\FrameworkBundle\Controller\RedirectController(($container->services['router'] ?? self::getRouterService($container)), $a->getHttpPort(), $a->getHttpsPort()); + } +} diff --git a/var/cache/dev/Container6Szx89D/getRedirectToServiceControllerService.php b/var/cache/dev/Container6Szx89D/getRedirectToServiceControllerService.php new file mode 100644 index 0000000..2576c95 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getRedirectToServiceControllerService.php @@ -0,0 +1,27 @@ +services['HWI\\Bundle\\OAuthBundle\\Controller\\RedirectToServiceController'] = new \HWI\Bundle\OAuthBundle\Controller\RedirectToServiceController(($container->privates['hwi_oauth.security.oauth_utils'] ?? $container->load('getHwiOauth_Security_OauthUtilsService')), new \HWI\Bundle\OAuthBundle\Util\DomainWhitelist([]), ($container->privates['hwi_oauth.resource_ownermap_locator'] ??= new \HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator()), NULL, false, false); + } +} diff --git a/var/cache/dev/Container6Szx89D/getRegisterControllerService.php b/var/cache/dev/Container6Szx89D/getRegisterControllerService.php new file mode 100644 index 0000000..aa82c8a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getRegisterControllerService.php @@ -0,0 +1,29 @@ +services['HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\RegisterController'] = new \HWI\Bundle\OAuthBundle\Controller\Connect\RegisterController(($container->privates['hwi_oauth.resource_ownermap_locator'] ??= new \HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator()), ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), ($container->services['event_dispatcher'] ?? self::getEventDispatcherService($container)), ($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)), ($container->services['hwi_oauth.user_checker'] ??= new \Symfony\Component\Security\Core\User\InMemoryUserChecker()), ($container->privates['security.authorization_checker'] ?? self::getSecurity_AuthorizationCheckerService($container)), ($container->privates['form.factory'] ?? $container->load('getForm_FactoryService')), ($container->privates['twig'] ?? $container->load('getTwigService')), 'IS_AUTHENTICATED_REMEMBERED', NULL, NULL, NULL); + } +} diff --git a/var/cache/dev/Container6Szx89D/getRouter_CacheWarmerService.php b/var/cache/dev/Container6Szx89D/getRouter_CacheWarmerService.php new file mode 100644 index 0000000..2459b4a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getRouter_CacheWarmerService.php @@ -0,0 +1,30 @@ +privates['router.cache_warmer'] = new \Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'router' => ['services', 'router', 'getRouterService', false], + ], [ + 'router' => '?', + ]))->withContext('router.cache_warmer', $container)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getRouting_LoaderService.php b/var/cache/dev/Container6Szx89D/getRouting_LoaderService.php new file mode 100644 index 0000000..c9156ed --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getRouting_LoaderService.php @@ -0,0 +1,72 @@ +services['kernel'] ?? $container->get('kernel', 1))); + $c = new \Symfony\Bundle\FrameworkBundle\Routing\AttributeRouteControllerLoader('dev'); + + $a->addLoader(new \Symfony\Component\Routing\Loader\XmlFileLoader($b, 'dev')); + $a->addLoader(new \Symfony\Component\Routing\Loader\YamlFileLoader($b, 'dev')); + $a->addLoader(new \Symfony\Component\Routing\Loader\PhpFileLoader($b, 'dev')); + $a->addLoader(new \Symfony\Component\Routing\Loader\GlobFileLoader($b, 'dev')); + $a->addLoader(new \Symfony\Component\Routing\Loader\DirectoryLoader($b, 'dev')); + $a->addLoader(new \Symfony\Component\Routing\Loader\ContainerLoader(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'kernel' => ['services', 'kernel', 'getKernelService', true], + 'security.route_loader.logout' => ['privates', 'security.route_loader.logout', 'getSecurity_RouteLoader_LogoutService', true], + ], [ + 'kernel' => 'App\\Kernel', + 'security.route_loader.logout' => 'Symfony\\Bundle\\SecurityBundle\\Routing\\LogoutRouteLoader', + ]), 'dev')); + $a->addLoader($c); + $a->addLoader(new \Symfony\Component\Routing\Loader\AttributeDirectoryLoader($b, $c)); + $a->addLoader(new \Symfony\Component\Routing\Loader\AttributeFileLoader($b, $c)); + $a->addLoader(new \Symfony\Component\Routing\Loader\Psr4DirectoryLoader($b)); + + return $container->services['routing.loader'] = new \Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader($a, ['utf8' => true], []); + } +} diff --git a/var/cache/dev/Container6Szx89D/getRunSqlCommandService.php b/var/cache/dev/Container6Szx89D/getRunSqlCommandService.php new file mode 100644 index 0000000..1e7c052 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getRunSqlCommandService.php @@ -0,0 +1,31 @@ +privates['Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand'] = $instance = new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(($container->privates['Doctrine\\Bundle\\DoctrineBundle\\Dbal\\ManagerRegistryAwareConnectionProvider'] ?? $container->load('getManagerRegistryAwareConnectionProviderService'))); + + $instance->setName('dbal:run-sql'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecrets_EnvVarLoaderService.php b/var/cache/dev/Container6Szx89D/getSecrets_EnvVarLoaderService.php new file mode 100644 index 0000000..c17e632 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecrets_EnvVarLoaderService.php @@ -0,0 +1,26 @@ +privates['secrets.env_var_loader'] = new \Symfony\Component\DependencyInjection\StaticEnvVarLoader(($container->privates['secrets.vault'] ?? $container->load('getSecrets_VaultService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecrets_VaultService.php b/var/cache/dev/Container6Szx89D/getSecrets_VaultService.php new file mode 100644 index 0000000..d781a59 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecrets_VaultService.php @@ -0,0 +1,28 @@ +privates['secrets.vault'] = new \Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault((\dirname(__DIR__, 4).'/config/secrets/'.$container->getEnv('string:default:kernel.environment:APP_RUNTIME_ENV')), \Symfony\Component\String\LazyString::fromCallable($container->getEnv(...), 'base64:default::SYMFONY_DECRYPTION_SECRET')); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_AccessListenerService.php b/var/cache/dev/Container6Szx89D/getSecurity_AccessListenerService.php new file mode 100644 index 0000000..13a08b4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_AccessListenerService.php @@ -0,0 +1,33 @@ +privates['debug.security.access.decision_manager'] ?? self::getDebug_Security_Access_DecisionManagerService($container)); + + if (isset($container->privates['security.access_listener'])) { + return $container->privates['security.access_listener']; + } + + return $container->privates['security.access_listener'] = new \Symfony\Component\Security\Http\Firewall\AccessListener(($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)), $a, ($container->privates['security.access_map'] ??= new \Symfony\Component\Security\Http\AccessMap())); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_ChannelListenerService.php b/var/cache/dev/Container6Szx89D/getSecurity_ChannelListenerService.php new file mode 100644 index 0000000..75b7854 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_ChannelListenerService.php @@ -0,0 +1,29 @@ +privates['router.request_context'] ?? self::getRouter_RequestContextService($container)); + + return $container->privates['security.channel_listener'] = new \Symfony\Component\Security\Http\Firewall\ChannelListener(($container->privates['security.access_map'] ??= new \Symfony\Component\Security\Http\AccessMap()), ($container->privates['logger'] ?? self::getLoggerService($container)), $a->getHttpPort(), $a->getHttpsPort()); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Command_DebugFirewallService.php b/var/cache/dev/Container6Szx89D/getSecurity_Command_DebugFirewallService.php new file mode 100644 index 0000000..ab9be84 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Command_DebugFirewallService.php @@ -0,0 +1,31 @@ +privates['security.command.debug_firewall'] = $instance = new \Symfony\Bundle\SecurityBundle\Command\DebugFirewallCommand($container->parameters['security.firewalls'], ($container->privates['.service_locator.bsoXAxw'] ?? self::get_ServiceLocator_BsoXAxwService($container)), ($container->privates['.service_locator.YbySDCA'] ?? $container->load('get_ServiceLocator_YbySDCAService')), ['main' => []], false); + + $instance->setName('debug:firewall'); + $instance->setDescription('Display information about your security firewall(s)'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Command_UserPasswordHashService.php b/var/cache/dev/Container6Szx89D/getSecurity_Command_UserPasswordHashService.php new file mode 100644 index 0000000..05d526c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Command_UserPasswordHashService.php @@ -0,0 +1,31 @@ +privates['security.command.user_password_hash'] = $instance = new \Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand(($container->privates['security.password_hasher_factory'] ?? $container->load('getSecurity_PasswordHasherFactoryService')), ['Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface']); + + $instance->setName('security:hash-password'); + $instance->setDescription('Hash a user password'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Firewall_Map_Context_DevService.php b/var/cache/dev/Container6Szx89D/getSecurity_Firewall_Map_Context_DevService.php new file mode 100644 index 0000000..405709b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Firewall_Map_Context_DevService.php @@ -0,0 +1,26 @@ +privates['security.firewall.map.context.dev'] = new \Symfony\Bundle\SecurityBundle\Security\FirewallContext(new RewindableGenerator(fn () => new \EmptyIterator(), 0), NULL, NULL, new \Symfony\Bundle\SecurityBundle\Security\FirewallConfig('dev', 'security.user_checker', '.security.request_matcher.gOpgIHx', false, false, NULL, NULL, NULL, NULL, NULL, [], NULL, NULL)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Firewall_Map_Context_MainService.php b/var/cache/dev/Container6Szx89D/getSecurity_Firewall_Map_Context_MainService.php new file mode 100644 index 0000000..982ceb7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Firewall_Map_Context_MainService.php @@ -0,0 +1,34 @@ +privates['security.firewall.map.context.main'] = new \Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext(new RewindableGenerator(function () use ($container) { + yield 0 => ($container->privates['security.channel_listener'] ?? $container->load('getSecurity_ChannelListenerService')); + yield 1 => ($container->privates['security.context_listener.0'] ?? self::getSecurity_ContextListener_0Service($container)); + yield 2 => ($container->privates['debug.security.firewall.authenticator.main'] ?? $container->load('getDebug_Security_Firewall_Authenticator_MainService')); + yield 3 => ($container->privates['security.access_listener'] ?? $container->load('getSecurity_AccessListenerService')); + }, 4), new \Symfony\Component\Security\Http\Firewall\ExceptionListener(($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)), ($container->privates['security.authentication.trust_resolver'] ??= new \Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver()), ($container->privates['security.http_utils'] ?? $container->load('getSecurity_HttpUtilsService')), 'main', NULL, NULL, NULL, ($container->privates['logger'] ?? self::getLoggerService($container)), false), NULL, new \Symfony\Bundle\SecurityBundle\Security\FirewallConfig('main', 'security.user_checker', NULL, true, false, 'security.user.provider.concrete.users_in_memory', 'main', NULL, NULL, NULL, [], NULL, NULL), ($container->privates['security.untracked_token_storage'] ??= new \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage())); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_HttpUtilsService.php b/var/cache/dev/Container6Szx89D/getSecurity_HttpUtilsService.php new file mode 100644 index 0000000..75c26f0 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_HttpUtilsService.php @@ -0,0 +1,27 @@ +services['router'] ?? self::getRouterService($container)); + + return $container->privates['security.http_utils'] = new \Symfony\Component\Security\Http\HttpUtils($a, $a, '{^https?://%s$}i', '{^https://%s$}i'); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Listener_CheckAuthenticatorCredentialsService.php b/var/cache/dev/Container6Szx89D/getSecurity_Listener_CheckAuthenticatorCredentialsService.php new file mode 100644 index 0000000..cea45f1 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Listener_CheckAuthenticatorCredentialsService.php @@ -0,0 +1,25 @@ +privates['security.listener.check_authenticator_credentials'] = new \Symfony\Component\Security\Http\EventListener\CheckCredentialsListener(($container->privates['security.password_hasher_factory'] ?? $container->load('getSecurity_PasswordHasherFactoryService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Listener_CsrfProtectionService.php b/var/cache/dev/Container6Szx89D/getSecurity_Listener_CsrfProtectionService.php new file mode 100644 index 0000000..88d98f3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Listener_CsrfProtectionService.php @@ -0,0 +1,25 @@ +privates['security.listener.csrf_protection'] = new \Symfony\Component\Security\Http\EventListener\CsrfProtectionListener(($container->privates['security.csrf.token_manager'] ?? self::getSecurity_Csrf_TokenManagerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Listener_Main_UserProviderService.php b/var/cache/dev/Container6Szx89D/getSecurity_Listener_Main_UserProviderService.php new file mode 100644 index 0000000..cc7ab2f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Listener_Main_UserProviderService.php @@ -0,0 +1,27 @@ +privates['security.listener.main.user_provider'] = new \Symfony\Component\Security\Http\EventListener\UserProviderListener(($container->privates['security.user.provider.concrete.users_in_memory'] ??= new \Symfony\Component\Security\Core\User\InMemoryUserProvider([]))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Listener_PasswordMigratingService.php b/var/cache/dev/Container6Szx89D/getSecurity_Listener_PasswordMigratingService.php new file mode 100644 index 0000000..6037bf6 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Listener_PasswordMigratingService.php @@ -0,0 +1,25 @@ +privates['security.listener.password_migrating'] = new \Symfony\Component\Security\Http\EventListener\PasswordMigratingListener(($container->privates['security.password_hasher_factory'] ?? $container->load('getSecurity_PasswordHasherFactoryService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Listener_Session_MainService.php b/var/cache/dev/Container6Szx89D/getSecurity_Listener_Session_MainService.php new file mode 100644 index 0000000..b1d5130 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Listener_Session_MainService.php @@ -0,0 +1,27 @@ +privates['security.listener.session.main'] = new \Symfony\Component\Security\Http\EventListener\SessionStrategyListener(new \Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy('migrate', ($container->privates['security.csrf.token_storage'] ?? self::getSecurity_Csrf_TokenStorageService($container)))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Listener_UserChecker_MainService.php b/var/cache/dev/Container6Szx89D/getSecurity_Listener_UserChecker_MainService.php new file mode 100644 index 0000000..6a18aba --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Listener_UserChecker_MainService.php @@ -0,0 +1,27 @@ +privates['security.listener.user_checker.main'] = new \Symfony\Component\Security\Http\EventListener\UserCheckerListener(($container->services['hwi_oauth.user_checker'] ??= new \Symfony\Component\Security\Core\User\InMemoryUserChecker())); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Listener_UserProviderService.php b/var/cache/dev/Container6Szx89D/getSecurity_Listener_UserProviderService.php new file mode 100644 index 0000000..ef72f43 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Listener_UserProviderService.php @@ -0,0 +1,27 @@ +privates['security.listener.user_provider'] = new \Symfony\Component\Security\Http\EventListener\UserProviderListener(($container->privates['security.user.provider.concrete.users_in_memory'] ??= new \Symfony\Component\Security\Core\User\InMemoryUserProvider([]))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_Logout_Listener_CsrfTokenClearingService.php b/var/cache/dev/Container6Szx89D/getSecurity_Logout_Listener_CsrfTokenClearingService.php new file mode 100644 index 0000000..cc0fa2c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_Logout_Listener_CsrfTokenClearingService.php @@ -0,0 +1,25 @@ +privates['security.logout.listener.csrf_token_clearing'] = new \Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener(($container->privates['security.csrf.token_storage'] ?? self::getSecurity_Csrf_TokenStorageService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_PasswordHasherFactoryService.php b/var/cache/dev/Container6Szx89D/getSecurity_PasswordHasherFactoryService.php new file mode 100644 index 0000000..e453c9c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_PasswordHasherFactoryService.php @@ -0,0 +1,26 @@ +privates['security.password_hasher_factory'] = new \Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory(['Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface' => ['algorithm' => 'auto', 'migrate_from' => [], 'hash_algorithm' => 'sha512', 'key_length' => 40, 'ignore_case' => false, 'encode_as_base64' => true, 'iterations' => 5000, 'cost' => NULL, 'memory_cost' => NULL, 'time_cost' => NULL]]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSecurity_RouteLoader_LogoutService.php b/var/cache/dev/Container6Szx89D/getSecurity_RouteLoader_LogoutService.php new file mode 100644 index 0000000..8698dad --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSecurity_RouteLoader_LogoutService.php @@ -0,0 +1,25 @@ +privates['security.route_loader.logout'] = new \Symfony\Bundle\SecurityBundle\Routing\LogoutRouteLoader([], 'security.logout_uris'); + } +} diff --git a/var/cache/dev/Container6Szx89D/getServicesResetterService.php b/var/cache/dev/Container6Szx89D/getServicesResetterService.php new file mode 100644 index 0000000..f2b30b8 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getServicesResetterService.php @@ -0,0 +1,92 @@ +services['services_resetter'] = new \Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter(new RewindableGenerator(function () use ($container) { + if (isset($container->services['cache.app'])) { + yield 'cache.app' => ($container->services['cache.app'] ?? null); + } + if (isset($container->services['cache.system'])) { + yield 'cache.system' => ($container->services['cache.system'] ?? null); + } + if (false) { + yield 'cache.validator' => null; + } + if (false) { + yield 'cache.serializer' => null; + } + if (false) { + yield 'cache.property_info' => null; + } + if (isset($container->privates['http_client.transport'])) { + yield 'http_client.transport' => ($container->privates['http_client.transport'] ?? null); + } + if (isset($container->privates['mailer.message_logger_listener'])) { + yield 'mailer.message_logger_listener' => ($container->privates['mailer.message_logger_listener'] ?? null); + } + if (isset($container->services['debug.stopwatch'])) { + yield 'debug.stopwatch' => ($container->services['debug.stopwatch'] ?? null); + } + if (isset($container->services['event_dispatcher'])) { + yield 'debug.event_dispatcher' => ($container->services['event_dispatcher'] ?? null); + } + if (isset($container->privates['session_listener'])) { + yield 'session_listener' => ($container->privates['session_listener'] ?? null); + } + if (isset($container->privates['form.choice_list_factory.cached'])) { + yield 'form.choice_list_factory.cached' => ($container->privates['form.choice_list_factory.cached'] ?? null); + } + if (isset($container->services['doctrine'])) { + yield 'doctrine' => ($container->services['doctrine'] ?? null); + } + if (isset($container->privates['doctrine.debug_data_holder'])) { + yield 'doctrine.debug_data_holder' => ($container->privates['doctrine.debug_data_holder'] ?? null); + } + if (isset($container->privates['form.type.entity'])) { + yield 'form.type.entity' => ($container->privates['form.type.entity'] ?? null); + } + if (isset($container->privates['security.token_storage'])) { + yield 'security.token_storage' => ($container->privates['security.token_storage'] ?? null); + } + if (false) { + yield 'cache.security_expression_language' => null; + } + if (isset($container->services['cache.security_is_granted_attribute_expression_language'])) { + yield 'cache.security_is_granted_attribute_expression_language' => ($container->services['cache.security_is_granted_attribute_expression_language'] ?? null); + } + if (isset($container->services['cache.security_is_csrf_token_valid_attribute_expression_language'])) { + yield 'cache.security_is_csrf_token_valid_attribute_expression_language' => ($container->services['cache.security_is_csrf_token_valid_attribute_expression_language'] ?? null); + } + if (isset($container->privates['debug.security.firewall'])) { + yield 'debug.security.firewall' => ($container->privates['debug.security.firewall'] ?? null); + } + if (isset($container->privates['debug.security.firewall.authenticator.main'])) { + yield 'debug.security.firewall.authenticator.main' => ($container->privates['debug.security.firewall.authenticator.main'] ?? null); + } + if (isset($container->privates['twig.form.engine'])) { + yield 'twig.form.engine' => ($container->privates['twig.form.engine'] ?? null); + } + if (isset($container->privates['debug.security.event_dispatcher.main'])) { + yield 'debug.security.event_dispatcher.main' => ($container->privates['debug.security.event_dispatcher.main'] ?? null); + } + }, fn () => 0 + (int) (isset($container->services['cache.app'])) + (int) (isset($container->services['cache.system'])) + (int) (false) + (int) (false) + (int) (false) + (int) (isset($container->privates['http_client.transport'])) + (int) (isset($container->privates['mailer.message_logger_listener'])) + (int) (isset($container->services['debug.stopwatch'])) + (int) (isset($container->services['event_dispatcher'])) + (int) (isset($container->privates['session_listener'])) + (int) (isset($container->privates['form.choice_list_factory.cached'])) + (int) (isset($container->services['doctrine'])) + (int) (isset($container->privates['doctrine.debug_data_holder'])) + (int) (isset($container->privates['form.type.entity'])) + (int) (isset($container->privates['security.token_storage'])) + (int) (false) + (int) (isset($container->services['cache.security_is_granted_attribute_expression_language'])) + (int) (isset($container->services['cache.security_is_csrf_token_valid_attribute_expression_language'])) + (int) (isset($container->privates['debug.security.firewall'])) + (int) (isset($container->privates['debug.security.firewall.authenticator.main'])) + (int) (isset($container->privates['twig.form.engine'])) + (int) (isset($container->privates['debug.security.event_dispatcher.main']))), ['cache.app' => ['reset'], 'cache.system' => ['reset'], 'cache.validator' => ['reset'], 'cache.serializer' => ['reset'], 'cache.property_info' => ['reset'], 'http_client.transport' => ['?reset'], 'mailer.message_logger_listener' => ['reset'], 'debug.stopwatch' => ['reset'], 'debug.event_dispatcher' => ['reset'], 'session_listener' => ['reset'], 'form.choice_list_factory.cached' => ['reset'], 'doctrine' => ['reset'], 'doctrine.debug_data_holder' => ['reset'], 'form.type.entity' => ['reset'], 'security.token_storage' => ['disableUsageTracking', 'setToken'], 'cache.security_expression_language' => ['reset'], 'cache.security_is_granted_attribute_expression_language' => ['reset'], 'cache.security_is_csrf_token_valid_attribute_expression_language' => ['reset'], 'debug.security.firewall' => ['reset'], 'debug.security.firewall.authenticator.main' => ['reset'], 'twig.form.engine' => ['reset'], 'debug.security.event_dispatcher.main' => ['reset']]); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSession_FactoryService.php b/var/cache/dev/Container6Szx89D/getSession_FactoryService.php new file mode 100644 index 0000000..a60118f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSession_FactoryService.php @@ -0,0 +1,36 @@ +privates['session_listener'] ?? self::getSessionListenerService($container)); + + if (isset($container->privates['session.factory'])) { + return $container->privates['session.factory']; + } + + return $container->privates['session.factory'] = new \Symfony\Component\HttpFoundation\Session\SessionFactory(($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()), new \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorageFactory($container->parameters['session.storage.options'], ($container->privates['session.handler.native'] ?? $container->load('getSession_Handler_NativeService')), new \Symfony\Component\HttpFoundation\Session\Storage\MetadataBag('_sf2_meta', 0), true), [$a, 'onSessionUsage']); + } +} diff --git a/var/cache/dev/Container6Szx89D/getSession_Handler_NativeService.php b/var/cache/dev/Container6Szx89D/getSession_Handler_NativeService.php new file mode 100644 index 0000000..e028faf --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getSession_Handler_NativeService.php @@ -0,0 +1,26 @@ +privates['session.handler.native'] = new \Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler(new \SessionHandler()); + } +} diff --git a/var/cache/dev/Container6Szx89D/getTemplateControllerService.php b/var/cache/dev/Container6Szx89D/getTemplateControllerService.php new file mode 100644 index 0000000..cf97315 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTemplateControllerService.php @@ -0,0 +1,25 @@ +services['Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController'] = new \Symfony\Bundle\FrameworkBundle\Controller\TemplateController(($container->privates['twig'] ?? $container->load('getTwigService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwigService.php b/var/cache/dev/Container6Szx89D/getTwigService.php new file mode 100644 index 0000000..a645454 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwigService.php @@ -0,0 +1,117 @@ +addPath((\dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/templates'), 'Doctrine'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/doctrine/doctrine-bundle/templates'), '!Doctrine'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/doctrine/doctrine-migrations-bundle/Resources/views'), 'DoctrineMigrations'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/doctrine/doctrine-migrations-bundle/Resources/views'), '!DoctrineMigrations'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/symfony/security-bundle/Resources/views'), 'Security'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/symfony/security-bundle/Resources/views'), '!Security'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/hwi/oauth-bundle/src/Resources/views'), 'HWIOAuth'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/hwi/oauth-bundle/src/Resources/views'), '!HWIOAuth'); + $a->addPath((\dirname(__DIR__, 4).'/templates')); + $a->addPath((\dirname(__DIR__, 4).'/vendor/symfony/twig-bridge/Resources/views/Email'), 'email'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/symfony/twig-bridge/Resources/views/Email'), '!email'); + $a->addPath((\dirname(__DIR__, 4).'/vendor/symfony/twig-bridge/Resources/views/Form')); + + $container->privates['twig'] = $instance = new \Twig\Environment($a, ['cache' => ($container->targetDir.''.'/twig'), 'charset' => 'UTF-8', 'debug' => true, 'strict_variables' => true, 'autoescape' => 'name']); + + $b = ($container->services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()); + $c = ($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container)); + $d = ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true)); + $e = ($container->services['router'] ?? self::getRouterService($container)); + $f = new \Symfony\Bridge\Twig\AppVariable(); + $f->setEnvironment('dev'); + $f->setDebug(true); + $f->setTokenStorage($c); + if ($container->has('request_stack')) { + $f->setRequestStack($b); + } + $f->setEnabledLocales([]); + + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\CsrfExtension()); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\LogoutUrlExtension(($container->privates['security.logout_url_generator'] ?? self::getSecurity_LogoutUrlGeneratorService($container)))); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\SecurityExtension(($container->privates['security.authorization_checker'] ?? self::getSecurity_AuthorizationCheckerService($container)), new \Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator($b, ($container->privates['security.firewall.map'] ?? self::getSecurity_Firewall_MapService($container)), $c))); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\ProfilerExtension(new \Twig\Profiler\Profile(), $d)); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\TranslationExtension(NULL)); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\RoutingExtension($e)); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\YamlExtension()); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\StopwatchExtension($d, true)); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\HttpKernelExtension()); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\HttpFoundationExtension(new \Symfony\Component\HttpFoundation\UrlHelper($b, $e))); + $instance->addExtension(new \Symfony\Bridge\Twig\Extension\FormExtension(NULL)); + $instance->addExtension(new \Doctrine\Bundle\DoctrineBundle\Twig\DoctrineExtension()); + $instance->addExtension(new \Twig\Extension\DebugExtension()); + $instance->addExtension(new \HWI\Bundle\OAuthBundle\Twig\Extension\OAuthExtension()); + $instance->addGlobal('app', $f); + $instance->addRuntimeLoader(new \Twig\RuntimeLoader\ContainerRuntimeLoader(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'Symfony\\Bridge\\Twig\\Extension\\CsrfRuntime' => ['privates', 'twig.runtime.security_csrf', 'getTwig_Runtime_SecurityCsrfService', true], + 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelRuntime' => ['privates', 'twig.runtime.httpkernel', 'getTwig_Runtime_HttpkernelService', true], + 'Symfony\\Component\\Form\\FormRenderer' => ['privates', 'twig.form.renderer', 'getTwig_Form_RendererService', true], + 'HWI\\Bundle\\OAuthBundle\\Twig\\Extension\\OAuthRuntime' => ['privates', 'hwi_oauth.twig.extension.oauth.runtime', 'getHwiOauth_Twig_Extension_Oauth_RuntimeService', true], + ], [ + 'Symfony\\Bridge\\Twig\\Extension\\CsrfRuntime' => '?', + 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelRuntime' => '?', + 'Symfony\\Component\\Form\\FormRenderer' => '?', + 'HWI\\Bundle\\OAuthBundle\\Twig\\Extension\\OAuthRuntime' => '?', + ]))); + (new \Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator('F j, Y H:i', '%d days', NULL, 0, '.', ','))->configure($instance); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_Command_DebugService.php b/var/cache/dev/Container6Szx89D/getTwig_Command_DebugService.php new file mode 100644 index 0000000..791f87e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_Command_DebugService.php @@ -0,0 +1,32 @@ +privates['twig.command.debug'] = $instance = new \Symfony\Bridge\Twig\Command\DebugCommand(($container->privates['twig'] ?? $container->load('getTwigService')), \dirname(__DIR__, 4), $container->parameters['kernel.bundles_metadata'], (\dirname(__DIR__, 4).'/templates'), ($container->privates['debug.file_link_formatter'] ??= new \Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter($container->getEnv('default::SYMFONY_IDE')))); + + $instance->setName('debug:twig'); + $instance->setDescription('Show a list of twig functions, filters, globals and tests'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_Command_LintService.php b/var/cache/dev/Container6Szx89D/getTwig_Command_LintService.php new file mode 100644 index 0000000..260d791 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_Command_LintService.php @@ -0,0 +1,32 @@ +privates['twig.command.lint'] = $instance = new \Symfony\Bundle\TwigBundle\Command\LintCommand(($container->privates['twig'] ?? $container->load('getTwigService')), ['*.twig']); + + $instance->setName('lint:twig'); + $instance->setDescription('Lint a Twig template and outputs encountered errors'); + + return $instance; + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_Form_EngineService.php b/var/cache/dev/Container6Szx89D/getTwig_Form_EngineService.php new file mode 100644 index 0000000..909d219 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_Form_EngineService.php @@ -0,0 +1,33 @@ +privates['twig'] ?? $container->load('getTwigService')); + + if (isset($container->privates['twig.form.engine'])) { + return $container->privates['twig.form.engine']; + } + + return $container->privates['twig.form.engine'] = new \Symfony\Bridge\Twig\Form\TwigRendererEngine($container->parameters['twig.form.resources'], $a); + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_Form_RendererService.php b/var/cache/dev/Container6Szx89D/getTwig_Form_RendererService.php new file mode 100644 index 0000000..22ab4b5 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_Form_RendererService.php @@ -0,0 +1,32 @@ +privates['twig.form.engine'] ?? $container->load('getTwig_Form_EngineService')); + + if (isset($container->privates['twig.form.renderer'])) { + return $container->privates['twig.form.renderer']; + } + + return $container->privates['twig.form.renderer'] = new \Symfony\Component\Form\FormRenderer($a, ($container->privates['security.csrf.token_manager'] ?? self::getSecurity_Csrf_TokenManagerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_Mailer_MessageListenerService.php b/var/cache/dev/Container6Szx89D/getTwig_Mailer_MessageListenerService.php new file mode 100644 index 0000000..d45c98e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_Mailer_MessageListenerService.php @@ -0,0 +1,33 @@ +privates['twig'] ?? $container->load('getTwigService')); + + if (isset($container->privates['twig.mailer.message_listener'])) { + return $container->privates['twig.mailer.message_listener']; + } + + return $container->privates['twig.mailer.message_listener'] = new \Symfony\Component\Mailer\EventListener\MessageListener(NULL, new \Symfony\Bridge\Twig\Mime\BodyRenderer($a)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_Runtime_HttpkernelService.php b/var/cache/dev/Container6Szx89D/getTwig_Runtime_HttpkernelService.php new file mode 100644 index 0000000..2647ad5 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_Runtime_HttpkernelService.php @@ -0,0 +1,36 @@ +services['request_stack'] ??= new \Symfony\Component\HttpFoundation\RequestStack()); + + return $container->privates['twig.runtime.httpkernel'] = new \Symfony\Bridge\Twig\Extension\HttpKernelRuntime(new \Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'inline' => ['privates', 'fragment.renderer.inline', 'getFragment_Renderer_InlineService', true], + ], [ + 'inline' => '?', + ]), $a, true), new \Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator('/_fragment', new \Symfony\Component\HttpFoundation\UriSigner($container->getEnv('APP_SECRET')), $a)); + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_Runtime_SecurityCsrfService.php b/var/cache/dev/Container6Szx89D/getTwig_Runtime_SecurityCsrfService.php new file mode 100644 index 0000000..b25e4d4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_Runtime_SecurityCsrfService.php @@ -0,0 +1,25 @@ +privates['twig.runtime.security_csrf'] = new \Symfony\Bridge\Twig\Extension\CsrfRuntime(($container->privates['security.csrf.token_manager'] ?? self::getSecurity_Csrf_TokenManagerService($container))); + } +} diff --git a/var/cache/dev/Container6Szx89D/getTwig_TemplateCacheWarmerService.php b/var/cache/dev/Container6Szx89D/getTwig_TemplateCacheWarmerService.php new file mode 100644 index 0000000..4927e2e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getTwig_TemplateCacheWarmerService.php @@ -0,0 +1,31 @@ +privates['twig.template_cache_warmer'] = new \Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheWarmer((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'twig' => ['privates', 'twig', 'getTwigService', true], + ], [ + 'twig' => 'Twig\\Environment', + ]))->withContext('twig.template_cache_warmer', $container), new \Symfony\Bundle\TwigBundle\TemplateIterator(($container->services['kernel'] ?? $container->get('kernel', 1)), [(\dirname(__DIR__, 4).'/vendor/symfony/twig-bridge/Resources/views/Email') => 'email', (\dirname(__DIR__, 4).'/vendor/symfony/twig-bridge/Resources/views/Form') => NULL], (\dirname(__DIR__, 4).'/templates'), ['*.twig'])); + } +} diff --git a/var/cache/dev/Container6Szx89D/getUserRepositoryService.php b/var/cache/dev/Container6Szx89D/getUserRepositoryService.php new file mode 100644 index 0000000..8de5019 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/getUserRepositoryService.php @@ -0,0 +1,31 @@ +privates['App\\Repository\\UserRepository'] = new \App\Repository\UserRepository(($container->services['doctrine'] ?? $container->load('getDoctrineService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_About_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_About_LazyService.php new file mode 100644 index 0000000..e889e1b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_About_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.about.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('about', [], 'Display information about the current project', false, #[\Closure(name: 'console.command.about', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\AboutCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\AboutCommand => ($container->privates['console.command.about'] ?? $container->load('getConsole_Command_AboutService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_AssetsInstall_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_AssetsInstall_LazyService.php new file mode 100644 index 0000000..180b283 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_AssetsInstall_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.assets_install.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('assets:install', [], 'Install bundle\'s web assets under a public directory', false, #[\Closure(name: 'console.command.assets_install', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\AssetsInstallCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand => ($container->privates['console.command.assets_install'] ?? $container->load('getConsole_Command_AssetsInstallService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_CacheClear_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_CacheClear_LazyService.php new file mode 100644 index 0000000..269e20b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_CacheClear_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.cache_clear.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('cache:clear', [], 'Clear the cache', false, #[\Closure(name: 'console.command.cache_clear', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheClearCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand => ($container->privates['console.command.cache_clear'] ?? $container->load('getConsole_Command_CacheClearService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolClear_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolClear_LazyService.php new file mode 100644 index 0000000..b48dd68 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolClear_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.cache_pool_clear.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('cache:pool:clear', [], 'Clear cache pools', false, #[\Closure(name: 'console.command.cache_pool_clear', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolClearCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand => ($container->privates['console.command.cache_pool_clear'] ?? $container->load('getConsole_Command_CachePoolClearService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolDelete_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolDelete_LazyService.php new file mode 100644 index 0000000..7f8d049 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolDelete_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.cache_pool_delete.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('cache:pool:delete', [], 'Delete an item from a cache pool', false, #[\Closure(name: 'console.command.cache_pool_delete', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolDeleteCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand => ($container->privates['console.command.cache_pool_delete'] ?? $container->load('getConsole_Command_CachePoolDeleteService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolInvalidateTags_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolInvalidateTags_LazyService.php new file mode 100644 index 0000000..9a48a41 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolInvalidateTags_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.cache_pool_invalidate_tags.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('cache:pool:invalidate-tags', [], 'Invalidate cache tags for all or a specific pool', false, #[\Closure(name: 'console.command.cache_pool_invalidate_tags', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolInvalidateTagsCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\CachePoolInvalidateTagsCommand => ($container->privates['console.command.cache_pool_invalidate_tags'] ?? $container->load('getConsole_Command_CachePoolInvalidateTagsService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolList_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolList_LazyService.php new file mode 100644 index 0000000..2d32ddb --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolList_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.cache_pool_list.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('cache:pool:list', [], 'List available cache pools', false, #[\Closure(name: 'console.command.cache_pool_list', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolListCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand => ($container->privates['console.command.cache_pool_list'] ?? $container->load('getConsole_Command_CachePoolListService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolPrune_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolPrune_LazyService.php new file mode 100644 index 0000000..963f9b9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_CachePoolPrune_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.cache_pool_prune.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('cache:pool:prune', [], 'Prune cache pools', false, #[\Closure(name: 'console.command.cache_pool_prune', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolPruneCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand => ($container->privates['console.command.cache_pool_prune'] ?? $container->load('getConsole_Command_CachePoolPruneService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_CacheWarmup_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_CacheWarmup_LazyService.php new file mode 100644 index 0000000..75a377e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_CacheWarmup_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.cache_warmup.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('cache:warmup', [], 'Warm up an empty cache', false, #[\Closure(name: 'console.command.cache_warmup', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheWarmupCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand => ($container->privates['console.command.cache_warmup'] ?? $container->load('getConsole_Command_CacheWarmupService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_ConfigDebug_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_ConfigDebug_LazyService.php new file mode 100644 index 0000000..600e973 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_ConfigDebug_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.config_debug.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:config', [], 'Dump the current configuration for an extension', false, #[\Closure(name: 'console.command.config_debug', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDebugCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand => ($container->privates['console.command.config_debug'] ?? $container->load('getConsole_Command_ConfigDebugService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_ConfigDumpReference_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_ConfigDumpReference_LazyService.php new file mode 100644 index 0000000..7016897 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_ConfigDumpReference_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.config_dump_reference.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('config:dump-reference', [], 'Dump the default configuration for an extension', false, #[\Closure(name: 'console.command.config_dump_reference', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDumpReferenceCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand => ($container->privates['console.command.config_dump_reference'] ?? $container->load('getConsole_Command_ConfigDumpReferenceService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_ContainerDebug_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_ContainerDebug_LazyService.php new file mode 100644 index 0000000..44a57b5 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_ContainerDebug_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.container_debug.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:container', [], 'Display current services for an application', false, #[\Closure(name: 'console.command.container_debug', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerDebugCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\ContainerDebugCommand => ($container->privates['console.command.container_debug'] ?? $container->load('getConsole_Command_ContainerDebugService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_ContainerLint_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_ContainerLint_LazyService.php new file mode 100644 index 0000000..cd2eec3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_ContainerLint_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.container_lint.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lint:container', [], 'Ensure that arguments injected into services match type declarations', false, #[\Closure(name: 'console.command.container_lint', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerLintCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand => ($container->privates['console.command.container_lint'] ?? $container->load('getConsole_Command_ContainerLintService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_DebugAutowiring_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_DebugAutowiring_LazyService.php new file mode 100644 index 0000000..a06d832 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_DebugAutowiring_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.debug_autowiring.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:autowiring', [], 'List classes/interfaces you can use for autowiring', false, #[\Closure(name: 'console.command.debug_autowiring', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\DebugAutowiringCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand => ($container->privates['console.command.debug_autowiring'] ?? $container->load('getConsole_Command_DebugAutowiringService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_DotenvDebug_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_DotenvDebug_LazyService.php new file mode 100644 index 0000000..cc7085d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_DotenvDebug_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.dotenv_debug.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:dotenv', [], 'List all dotenv files with variables and values', false, #[\Closure(name: 'console.command.dotenv_debug', class: 'Symfony\\Component\\Dotenv\\Command\\DebugCommand')] fn (): \Symfony\Component\Dotenv\Command\DebugCommand => ($container->privates['console.command.dotenv_debug'] ?? $container->load('getConsole_Command_DotenvDebugService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_EventDispatcherDebug_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_EventDispatcherDebug_LazyService.php new file mode 100644 index 0000000..6ece5a7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_EventDispatcherDebug_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.event_dispatcher_debug.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:event-dispatcher', [], 'Display configured listeners for an application', false, #[\Closure(name: 'console.command.event_dispatcher_debug', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\EventDispatcherDebugCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand => ($container->privates['console.command.event_dispatcher_debug'] ?? $container->load('getConsole_Command_EventDispatcherDebugService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_FormDebug_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_FormDebug_LazyService.php new file mode 100644 index 0000000..bed6970 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_FormDebug_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.form_debug.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:form', [], 'Display form type information', false, #[\Closure(name: 'console.command.form_debug', class: 'Symfony\\Component\\Form\\Command\\DebugCommand')] fn (): \Symfony\Component\Form\Command\DebugCommand => ($container->privates['console.command.form_debug'] ?? $container->load('getConsole_Command_FormDebugService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_MailerTest_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_MailerTest_LazyService.php new file mode 100644 index 0000000..5cadf65 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_MailerTest_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.mailer_test.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('mailer:test', [], 'Test Mailer transports by sending an email', false, #[\Closure(name: 'console.command.mailer_test', class: 'Symfony\\Component\\Mailer\\Command\\MailerTestCommand')] fn (): \Symfony\Component\Mailer\Command\MailerTestCommand => ($container->privates['console.command.mailer_test'] ?? $container->load('getConsole_Command_MailerTestService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_RouterDebug_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_RouterDebug_LazyService.php new file mode 100644 index 0000000..6989fab --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_RouterDebug_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.router_debug.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:router', [], 'Display current routes for an application', false, #[\Closure(name: 'console.command.router_debug', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\RouterDebugCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand => ($container->privates['console.command.router_debug'] ?? $container->load('getConsole_Command_RouterDebugService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_RouterMatch_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_RouterMatch_LazyService.php new file mode 100644 index 0000000..ddd6e45 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_RouterMatch_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.router_match.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('router:match', [], 'Help debug routes by simulating a path info match', false, #[\Closure(name: 'console.command.router_match', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\RouterMatchCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand => ($container->privates['console.command.router_match'] ?? $container->load('getConsole_Command_RouterMatchService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsDecryptToLocal_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsDecryptToLocal_LazyService.php new file mode 100644 index 0000000..ff4368b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsDecryptToLocal_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.secrets_decrypt_to_local.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('secrets:decrypt-to-local', [], 'Decrypt all secrets and stores them in the local vault', false, #[\Closure(name: 'console.command.secrets_decrypt_to_local', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsDecryptToLocalCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand => ($container->privates['console.command.secrets_decrypt_to_local'] ?? $container->load('getConsole_Command_SecretsDecryptToLocalService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsEncryptFromLocal_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsEncryptFromLocal_LazyService.php new file mode 100644 index 0000000..9d807d4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsEncryptFromLocal_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.secrets_encrypt_from_local.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('secrets:encrypt-from-local', [], 'Encrypt all local secrets to the vault', false, #[\Closure(name: 'console.command.secrets_encrypt_from_local', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsEncryptFromLocalCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand => ($container->privates['console.command.secrets_encrypt_from_local'] ?? $container->load('getConsole_Command_SecretsEncryptFromLocalService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsGenerateKey_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsGenerateKey_LazyService.php new file mode 100644 index 0000000..2a29f35 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsGenerateKey_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.secrets_generate_key.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('secrets:generate-keys', [], 'Generate new encryption keys', false, #[\Closure(name: 'console.command.secrets_generate_key', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsGenerateKeysCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand => ($container->privates['console.command.secrets_generate_key'] ?? $container->load('getConsole_Command_SecretsGenerateKeyService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsList_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsList_LazyService.php new file mode 100644 index 0000000..78a0191 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsList_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.secrets_list.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('secrets:list', [], 'List all secrets', false, #[\Closure(name: 'console.command.secrets_list', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsListCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand => ($container->privates['console.command.secrets_list'] ?? $container->load('getConsole_Command_SecretsListService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsRemove_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsRemove_LazyService.php new file mode 100644 index 0000000..e89c371 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsRemove_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.secrets_remove.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('secrets:remove', [], 'Remove a secret from the vault', false, #[\Closure(name: 'console.command.secrets_remove', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsRemoveCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand => ($container->privates['console.command.secrets_remove'] ?? $container->load('getConsole_Command_SecretsRemoveService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsReveal_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsReveal_LazyService.php new file mode 100644 index 0000000..dcc4860 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsReveal_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.secrets_reveal.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('secrets:reveal', [], 'Reveal the value of a secret', false, #[\Closure(name: 'console.command.secrets_reveal', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsRevealCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand => ($container->privates['console.command.secrets_reveal'] ?? $container->load('getConsole_Command_SecretsRevealService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsSet_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsSet_LazyService.php new file mode 100644 index 0000000..86e7ed4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_SecretsSet_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.secrets_set.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('secrets:set', [], 'Set a secret in the vault', false, #[\Closure(name: 'console.command.secrets_set', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsSetCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand => ($container->privates['console.command.secrets_set'] ?? $container->load('getConsole_Command_SecretsSetService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Console_Command_YamlLint_LazyService.php b/var/cache/dev/Container6Szx89D/get_Console_Command_YamlLint_LazyService.php new file mode 100644 index 0000000..2242a1f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Console_Command_YamlLint_LazyService.php @@ -0,0 +1,26 @@ +privates['.console.command.yaml_lint.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lint:yaml', [], 'Lint a YAML file and outputs encountered errors', false, #[\Closure(name: 'console.command.yaml_lint', class: 'Symfony\\Bundle\\FrameworkBundle\\Command\\YamlLintCommand')] fn (): \Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand => ($container->privates['console.command.yaml_lint'] ?? $container->load('getConsole_Command_YamlLintService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_Security_Voter_Security_Access_AuthenticatedVoterService.php b/var/cache/dev/Container6Szx89D/get_Debug_Security_Voter_Security_Access_AuthenticatedVoterService.php new file mode 100644 index 0000000..73daff0 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_Security_Voter_Security_Access_AuthenticatedVoterService.php @@ -0,0 +1,34 @@ +services['event_dispatcher'] ?? self::getEventDispatcherService($container)); + + if (isset($container->privates['.debug.security.voter.security.access.authenticated_voter'])) { + return $container->privates['.debug.security.voter.security.access.authenticated_voter']; + } + + return $container->privates['.debug.security.voter.security.access.authenticated_voter'] = new \Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter(new \Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter(($container->privates['security.authentication.trust_resolver'] ??= new \Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver())), $a); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_Security_Voter_Security_Access_SimpleRoleVoterService.php b/var/cache/dev/Container6Szx89D/get_Debug_Security_Voter_Security_Access_SimpleRoleVoterService.php new file mode 100644 index 0000000..78f2615 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_Security_Voter_Security_Access_SimpleRoleVoterService.php @@ -0,0 +1,34 @@ +services['event_dispatcher'] ?? self::getEventDispatcherService($container)); + + if (isset($container->privates['.debug.security.voter.security.access.simple_role_voter'])) { + return $container->privates['.debug.security.voter.security.access.simple_role_voter']; + } + + return $container->privates['.debug.security.voter.security.access.simple_role_voter'] = new \Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter(new \Symfony\Component\Security\Core\Authorization\Voter\RoleVoter(), $a); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_BackedEnumResolverService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_BackedEnumResolverService.php new file mode 100644 index 0000000..d73f2ab --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_BackedEnumResolverService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.backed_enum_resolver'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DatetimeService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DatetimeService.php new file mode 100644 index 0000000..80a5335 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DatetimeService.php @@ -0,0 +1,30 @@ +privates['.debug.value_resolver.argument_resolver.datetime'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver(new \Symfony\Component\Clock\Clock()), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DefaultService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DefaultService.php new file mode 100644 index 0000000..70684a4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_DefaultService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.default'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_NotTaggedControllerService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_NotTaggedControllerService.php new file mode 100644 index 0000000..58f00d6 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_NotTaggedControllerService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.not_tagged_controller'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\NotTaggedControllerValueResolver(($container->privates['.service_locator.dyzWi7x'] ?? $container->load('get_ServiceLocator_DyzWi7xService'))), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_QueryParameterValueResolverService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_QueryParameterValueResolverService.php new file mode 100644 index 0000000..f87bed7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_QueryParameterValueResolverService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.query_parameter_value_resolver'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestAttributeService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestAttributeService.php new file mode 100644 index 0000000..9c4c8f7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestAttributeService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.request_attribute'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestPayloadService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestPayloadService.php new file mode 100644 index 0000000..6e6f1f9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestPayloadService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.request_payload'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(throw new RuntimeException('You can neither use "#[MapRequestPayload]" nor "#[MapQueryString]" since the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestService.php new file mode 100644 index 0000000..ef1bd9c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_RequestService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.request'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_ServiceService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_ServiceService.php new file mode 100644 index 0000000..8c8665c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_ServiceService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.service'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver(($container->privates['.service_locator.dyzWi7x'] ?? $container->load('get_ServiceLocator_DyzWi7xService'))), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_SessionService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_SessionService.php new file mode 100644 index 0000000..50b0222 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_SessionService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.session'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_VariadicService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_VariadicService.php new file mode 100644 index 0000000..2399d42 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_ArgumentResolver_VariadicService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.argument_resolver.variadic'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver(), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Doctrine_Orm_EntityValueResolverService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Doctrine_Orm_EntityValueResolverService.php new file mode 100644 index 0000000..e902ba4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Doctrine_Orm_EntityValueResolverService.php @@ -0,0 +1,29 @@ +privates['.debug.value_resolver.doctrine.orm.entity_value_resolver'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver(($container->services['doctrine'] ?? $container->load('getDoctrineService')), NULL, new \Symfony\Bridge\Doctrine\Attribute\MapEntity(NULL, NULL, NULL, [], NULL, NULL, NULL, NULL, false)), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Security_SecurityTokenValueResolverService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Security_SecurityTokenValueResolverService.php new file mode 100644 index 0000000..5bdccc3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Security_SecurityTokenValueResolverService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.security.security_token_value_resolver'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver(($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container))), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Security_UserValueResolverService.php b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Security_UserValueResolverService.php new file mode 100644 index 0000000..6b2db2f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Debug_ValueResolver_Security_UserValueResolverService.php @@ -0,0 +1,27 @@ +privates['.debug.value_resolver.security.user_value_resolver'] = new \Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver(new \Symfony\Component\Security\Http\Controller\UserValueResolver(($container->privates['security.token_storage'] ?? self::getSecurity_TokenStorageService($container))), ($container->services['debug.stopwatch'] ??= new \Symfony\Component\Stopwatch\Stopwatch(true))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_CurrentCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_CurrentCommand_LazyService.php new file mode 100644 index 0000000..7f9a95c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_CurrentCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.current_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:current', [], 'Outputs the current version', false, #[\Closure(name: 'doctrine_migrations.current_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\CurrentCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\CurrentCommand => ($container->privates['doctrine_migrations.current_command'] ?? $container->load('getDoctrineMigrations_CurrentCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_DiffCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_DiffCommand_LazyService.php new file mode 100644 index 0000000..8736094 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_DiffCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.diff_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:diff', [], 'Generate a migration by comparing your current database to your mapping information.', false, #[\Closure(name: 'doctrine_migrations.diff_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\DiffCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\DiffCommand => ($container->privates['doctrine_migrations.diff_command'] ?? $container->load('getDoctrineMigrations_DiffCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_DumpSchemaCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_DumpSchemaCommand_LazyService.php new file mode 100644 index 0000000..5dcedc2 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_DumpSchemaCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.dump_schema_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:dump-schema', [], 'Dump the schema for your database to a migration.', false, #[\Closure(name: 'doctrine_migrations.dump_schema_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\DumpSchemaCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand => ($container->privates['doctrine_migrations.dump_schema_command'] ?? $container->load('getDoctrineMigrations_DumpSchemaCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_ExecuteCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_ExecuteCommand_LazyService.php new file mode 100644 index 0000000..d4a9b64 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_ExecuteCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.execute_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:execute', [], 'Execute one or more migration versions up or down manually.', false, #[\Closure(name: 'doctrine_migrations.execute_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\ExecuteCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\ExecuteCommand => ($container->privates['doctrine_migrations.execute_command'] ?? $container->load('getDoctrineMigrations_ExecuteCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_GenerateCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_GenerateCommand_LazyService.php new file mode 100644 index 0000000..eb5e37c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_GenerateCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.generate_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:generate', [], 'Generate a blank migration class.', false, #[\Closure(name: 'doctrine_migrations.generate_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\GenerateCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\GenerateCommand => ($container->privates['doctrine_migrations.generate_command'] ?? $container->load('getDoctrineMigrations_GenerateCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_LatestCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_LatestCommand_LazyService.php new file mode 100644 index 0000000..220b076 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_LatestCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.latest_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:latest', [], 'Outputs the latest version', false, #[\Closure(name: 'doctrine_migrations.latest_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\LatestCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\LatestCommand => ($container->privates['doctrine_migrations.latest_command'] ?? $container->load('getDoctrineMigrations_LatestCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_MigrateCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_MigrateCommand_LazyService.php new file mode 100644 index 0000000..1a8cf63 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_MigrateCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.migrate_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:migrate', [], 'Execute a migration to a specified version or the latest available version.', false, #[\Closure(name: 'doctrine_migrations.migrate_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\MigrateCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\MigrateCommand => ($container->privates['doctrine_migrations.migrate_command'] ?? $container->load('getDoctrineMigrations_MigrateCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_RollupCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_RollupCommand_LazyService.php new file mode 100644 index 0000000..c23920a --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_RollupCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.rollup_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:rollup', [], 'Rollup migrations by deleting all tracked versions and insert the one version that exists.', false, #[\Closure(name: 'doctrine_migrations.rollup_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\RollupCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\RollupCommand => ($container->privates['doctrine_migrations.rollup_command'] ?? $container->load('getDoctrineMigrations_RollupCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_StatusCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_StatusCommand_LazyService.php new file mode 100644 index 0000000..bd615e6 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_StatusCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.status_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:status', [], 'View the status of a set of migrations.', false, #[\Closure(name: 'doctrine_migrations.status_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\StatusCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\StatusCommand => ($container->privates['doctrine_migrations.status_command'] ?? $container->load('getDoctrineMigrations_StatusCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_SyncMetadataCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_SyncMetadataCommand_LazyService.php new file mode 100644 index 0000000..77a5e00 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_SyncMetadataCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.sync_metadata_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:sync-metadata-storage', [], 'Ensures that the metadata storage is at the latest version.', false, #[\Closure(name: 'doctrine_migrations.sync_metadata_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\SyncMetadataCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand => ($container->privates['doctrine_migrations.sync_metadata_command'] ?? $container->load('getDoctrineMigrations_SyncMetadataCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_UpToDateCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_UpToDateCommand_LazyService.php new file mode 100644 index 0000000..ac50c9f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_UpToDateCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.up_to_date_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:up-to-date', [], 'Tells you if your schema is up-to-date.', false, #[\Closure(name: 'doctrine_migrations.up_to_date_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\UpToDateCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\UpToDateCommand => ($container->privates['doctrine_migrations.up_to_date_command'] ?? $container->load('getDoctrineMigrations_UpToDateCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_VersionCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_VersionCommand_LazyService.php new file mode 100644 index 0000000..9610245 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_VersionCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.version_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:version', [], 'Manually add and delete migration versions from the version table.', false, #[\Closure(name: 'doctrine_migrations.version_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\VersionCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\VersionCommand => ($container->privates['doctrine_migrations.version_command'] ?? $container->load('getDoctrineMigrations_VersionCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_VersionsCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_VersionsCommand_LazyService.php new file mode 100644 index 0000000..1b58e58 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_DoctrineMigrations_VersionsCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.doctrine_migrations.versions_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('doctrine:migrations:list', [], 'Display a list of all available migrations and their status.', false, #[\Closure(name: 'doctrine_migrations.versions_command', class: 'Doctrine\\Migrations\\Tools\\Console\\Command\\ListCommand')] fn (): \Doctrine\Migrations\Tools\Console\Command\ListCommand => ($container->privates['doctrine_migrations.versions_command'] ?? $container->load('getDoctrineMigrations_VersionsCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_CheckConfigCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_CheckConfigCommand_LazyService.php new file mode 100644 index 0000000..4e07e5d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_CheckConfigCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.lexik_jwt_authentication.check_config_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lexik:jwt:check-config', [], 'Checks that the bundle is properly configured.', false, #[\Closure(name: 'lexik_jwt_authentication.check_config_command', class: 'Lexik\\Bundle\\JWTAuthenticationBundle\\Command\\CheckConfigCommand')] fn (): \Lexik\Bundle\JWTAuthenticationBundle\Command\CheckConfigCommand => ($container->privates['lexik_jwt_authentication.check_config_command'] ?? $container->load('getLexikJwtAuthentication_CheckConfigCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_EnableEncryptionConfigCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_EnableEncryptionConfigCommand_LazyService.php new file mode 100644 index 0000000..bc8256f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_EnableEncryptionConfigCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.lexik_jwt_authentication.enable_encryption_config_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lexik:jwt:enable-encryption', [], 'Enable Web-Token encryption support.', false, #[\Closure(name: 'lexik_jwt_authentication.enable_encryption_config_command', class: 'Lexik\\Bundle\\JWTAuthenticationBundle\\Command\\EnableEncryptionConfigCommand')] fn (): \Lexik\Bundle\JWTAuthenticationBundle\Command\EnableEncryptionConfigCommand => ($container->privates['lexik_jwt_authentication.enable_encryption_config_command'] ?? $container->load('getLexikJwtAuthentication_EnableEncryptionConfigCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_GenerateKeypairCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_GenerateKeypairCommand_LazyService.php new file mode 100644 index 0000000..6f847c6 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_GenerateKeypairCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.lexik_jwt_authentication.generate_keypair_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lexik:jwt:generate-keypair', [], 'Generate public/private keys for use in your application.', false, #[\Closure(name: 'lexik_jwt_authentication.generate_keypair_command', class: 'Lexik\\Bundle\\JWTAuthenticationBundle\\Command\\GenerateKeyPairCommand')] fn (): \Lexik\Bundle\JWTAuthenticationBundle\Command\GenerateKeyPairCommand => ($container->privates['lexik_jwt_authentication.generate_keypair_command'] ?? $container->load('getLexikJwtAuthentication_GenerateKeypairCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_GenerateTokenCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_GenerateTokenCommand_LazyService.php new file mode 100644 index 0000000..b747cb7 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_GenerateTokenCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.lexik_jwt_authentication.generate_token_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lexik:jwt:generate-token', [], 'Generates a JWT token for a given user.', false, #[\Closure(name: 'lexik_jwt_authentication.generate_token_command', class: 'Lexik\\Bundle\\JWTAuthenticationBundle\\Command\\GenerateTokenCommand')] fn (): \Lexik\Bundle\JWTAuthenticationBundle\Command\GenerateTokenCommand => ($container->services['lexik_jwt_authentication.generate_token_command'] ?? $container->load('getLexikJwtAuthentication_GenerateTokenCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_MigrateConfigCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_MigrateConfigCommand_LazyService.php new file mode 100644 index 0000000..7a1bba8 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_LexikJwtAuthentication_MigrateConfigCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.lexik_jwt_authentication.migrate_config_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lexik:jwt:migrate-config', [], 'Migrate LexikJWTAuthenticationBundle configuration to the Web-Token one.', false, #[\Closure(name: 'lexik_jwt_authentication.migrate_config_command', class: 'Lexik\\Bundle\\JWTAuthenticationBundle\\Command\\MigrateConfigCommand')] fn (): \Lexik\Bundle\JWTAuthenticationBundle\Command\MigrateConfigCommand => ($container->privates['lexik_jwt_authentication.migrate_config_command'] ?? $container->load('getLexikJwtAuthentication_MigrateConfigCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeAuth_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeAuth_LazyService.php new file mode 100644 index 0000000..c44579b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeAuth_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_auth.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:auth', [], 'Create a Guard authenticator of different flavors', false, #[\Closure(name: 'maker.auto_command.make_auth', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_auth'] ?? $container->load('getMaker_AutoCommand_MakeAuthService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeCommand_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeCommand_LazyService.php new file mode 100644 index 0000000..97eb33c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeCommand_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_command.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:command', [], 'Create a new console command class', false, #[\Closure(name: 'maker.auto_command.make_command', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_command'] ?? $container->load('getMaker_AutoCommand_MakeCommandService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeController_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeController_LazyService.php new file mode 100644 index 0000000..66e1913 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeController_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_controller.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:controller', [], 'Create a new controller class', false, #[\Closure(name: 'maker.auto_command.make_controller', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_controller'] ?? $container->load('getMaker_AutoCommand_MakeControllerService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeCrud_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeCrud_LazyService.php new file mode 100644 index 0000000..87fbd5f --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeCrud_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_crud.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:crud', [], 'Create CRUD for Doctrine entity class', false, #[\Closure(name: 'maker.auto_command.make_crud', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_crud'] ?? $container->load('getMaker_AutoCommand_MakeCrudService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeDockerDatabase_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeDockerDatabase_LazyService.php new file mode 100644 index 0000000..b5c87d3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeDockerDatabase_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_docker_database.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:docker:database', [], 'Add a database container to your compose.yaml file', false, #[\Closure(name: 'maker.auto_command.make_docker_database', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_docker_database'] ?? $container->load('getMaker_AutoCommand_MakeDockerDatabaseService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeEntity_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeEntity_LazyService.php new file mode 100644 index 0000000..f26e3c4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeEntity_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_entity.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:entity', [], 'Create or update a Doctrine entity class, and optionally an API Platform resource', false, #[\Closure(name: 'maker.auto_command.make_entity', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_entity'] ?? $container->load('getMaker_AutoCommand_MakeEntityService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeFixtures_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeFixtures_LazyService.php new file mode 100644 index 0000000..bfec41c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeFixtures_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_fixtures.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:fixtures', [], 'Create a new class to load Doctrine fixtures', false, #[\Closure(name: 'maker.auto_command.make_fixtures', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_fixtures'] ?? $container->load('getMaker_AutoCommand_MakeFixturesService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeForm_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeForm_LazyService.php new file mode 100644 index 0000000..8ccac6d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeForm_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_form.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:form', [], 'Create a new form class', false, #[\Closure(name: 'maker.auto_command.make_form', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_form'] ?? $container->load('getMaker_AutoCommand_MakeFormService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeListener_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeListener_LazyService.php new file mode 100644 index 0000000..f6a3177 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeListener_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_listener.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:listener', ['make:subscriber'], 'Creates a new event subscriber class or a new event listener class', false, #[\Closure(name: 'maker.auto_command.make_listener', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_listener'] ?? $container->load('getMaker_AutoCommand_MakeListenerService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMessage_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMessage_LazyService.php new file mode 100644 index 0000000..0698857 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMessage_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_message.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:message', [], 'Create a new message and handler', false, #[\Closure(name: 'maker.auto_command.make_message', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_message'] ?? $container->load('getMaker_AutoCommand_MakeMessageService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMessengerMiddleware_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMessengerMiddleware_LazyService.php new file mode 100644 index 0000000..0e5d8c6 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMessengerMiddleware_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_messenger_middleware.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:messenger-middleware', [], 'Create a new messenger middleware', false, #[\Closure(name: 'maker.auto_command.make_messenger_middleware', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_messenger_middleware'] ?? $container->load('getMaker_AutoCommand_MakeMessengerMiddlewareService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMigration_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMigration_LazyService.php new file mode 100644 index 0000000..577a2c2 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeMigration_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_migration.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:migration', [], 'Create a new migration based on database changes', false, #[\Closure(name: 'maker.auto_command.make_migration', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_migration'] ?? $container->load('getMaker_AutoCommand_MakeMigrationService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeRegistrationForm_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeRegistrationForm_LazyService.php new file mode 100644 index 0000000..c56b046 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeRegistrationForm_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_registration_form.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:registration-form', [], 'Create a new registration form system', false, #[\Closure(name: 'maker.auto_command.make_registration_form', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_registration_form'] ?? $container->load('getMaker_AutoCommand_MakeRegistrationFormService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeResetPassword_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeResetPassword_LazyService.php new file mode 100644 index 0000000..a445fb2 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeResetPassword_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_reset_password.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:reset-password', [], 'Create controller, entity, and repositories for use with symfonycasts/reset-password-bundle', false, #[\Closure(name: 'maker.auto_command.make_reset_password', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_reset_password'] ?? $container->load('getMaker_AutoCommand_MakeResetPasswordService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSchedule_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSchedule_LazyService.php new file mode 100644 index 0000000..b6b0b91 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSchedule_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_schedule.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:schedule', [], 'Create a scheduler component', false, #[\Closure(name: 'maker.auto_command.make_schedule', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_schedule'] ?? $container->load('getMaker_AutoCommand_MakeScheduleService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSecurityCustom_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSecurityCustom_LazyService.php new file mode 100644 index 0000000..b7c56ec --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSecurityCustom_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_security_custom.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:security:custom', [], 'Create a custom security authenticator.', false, #[\Closure(name: 'maker.auto_command.make_security_custom', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_security_custom'] ?? $container->load('getMaker_AutoCommand_MakeSecurityCustomService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSecurityFormLogin_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSecurityFormLogin_LazyService.php new file mode 100644 index 0000000..5ef93bd --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSecurityFormLogin_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_security_form_login.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:security:form-login', [], 'Generate the code needed for the form_login authenticator', false, #[\Closure(name: 'maker.auto_command.make_security_form_login', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_security_form_login'] ?? $container->load('getMaker_AutoCommand_MakeSecurityFormLoginService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSerializerEncoder_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSerializerEncoder_LazyService.php new file mode 100644 index 0000000..3d5e599 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSerializerEncoder_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_serializer_encoder.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:serializer:encoder', [], 'Create a new serializer encoder class', false, #[\Closure(name: 'maker.auto_command.make_serializer_encoder', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_serializer_encoder'] ?? $container->load('getMaker_AutoCommand_MakeSerializerEncoderService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSerializerNormalizer_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSerializerNormalizer_LazyService.php new file mode 100644 index 0000000..aec7944 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeSerializerNormalizer_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_serializer_normalizer.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:serializer:normalizer', [], 'Create a new serializer normalizer class', false, #[\Closure(name: 'maker.auto_command.make_serializer_normalizer', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_serializer_normalizer'] ?? $container->load('getMaker_AutoCommand_MakeSerializerNormalizerService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeStimulusController_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeStimulusController_LazyService.php new file mode 100644 index 0000000..c8c8f5c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeStimulusController_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_stimulus_controller.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:stimulus-controller', [], 'Create a new Stimulus controller', false, #[\Closure(name: 'maker.auto_command.make_stimulus_controller', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_stimulus_controller'] ?? $container->load('getMaker_AutoCommand_MakeStimulusControllerService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTest_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTest_LazyService.php new file mode 100644 index 0000000..ede2a2d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTest_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_test.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:test', ['make:unit-test', 'make:functional-test'], 'Create a new test class', false, #[\Closure(name: 'maker.auto_command.make_test', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_test'] ?? $container->load('getMaker_AutoCommand_MakeTestService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTwigComponent_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTwigComponent_LazyService.php new file mode 100644 index 0000000..38753e4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTwigComponent_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_twig_component.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:twig-component', [], 'Create a twig (or live) component', false, #[\Closure(name: 'maker.auto_command.make_twig_component', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_twig_component'] ?? $container->load('getMaker_AutoCommand_MakeTwigComponentService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTwigExtension_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTwigExtension_LazyService.php new file mode 100644 index 0000000..98bec7e --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeTwigExtension_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_twig_extension.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:twig-extension', [], 'Create a new Twig extension with its runtime class', false, #[\Closure(name: 'maker.auto_command.make_twig_extension', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_twig_extension'] ?? $container->load('getMaker_AutoCommand_MakeTwigExtensionService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeUser_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeUser_LazyService.php new file mode 100644 index 0000000..8c4ce05 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeUser_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_user.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:user', [], 'Create a new security user class', false, #[\Closure(name: 'maker.auto_command.make_user', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_user'] ?? $container->load('getMaker_AutoCommand_MakeUserService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeValidator_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeValidator_LazyService.php new file mode 100644 index 0000000..5dfd20c --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeValidator_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_validator.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:validator', [], 'Create a new validator and constraint class', false, #[\Closure(name: 'maker.auto_command.make_validator', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_validator'] ?? $container->load('getMaker_AutoCommand_MakeValidatorService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeVoter_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeVoter_LazyService.php new file mode 100644 index 0000000..8eafce4 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeVoter_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_voter.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:voter', [], 'Create a new security voter class', false, #[\Closure(name: 'maker.auto_command.make_voter', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_voter'] ?? $container->load('getMaker_AutoCommand_MakeVoterService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeWebhook_LazyService.php b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeWebhook_LazyService.php new file mode 100644 index 0000000..0e4050b --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Maker_AutoCommand_MakeWebhook_LazyService.php @@ -0,0 +1,26 @@ +privates['.maker.auto_command.make_webhook.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('make:webhook', [], 'Create a new Webhook', false, #[\Closure(name: 'maker.auto_command.make_webhook', class: 'Symfony\\Bundle\\MakerBundle\\Command\\MakerCommand')] fn (): \Symfony\Bundle\MakerBundle\Command\MakerCommand => ($container->privates['maker.auto_command.make_webhook'] ?? $container->load('getMaker_AutoCommand_MakeWebhookService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Security_Command_DebugFirewall_LazyService.php b/var/cache/dev/Container6Szx89D/get_Security_Command_DebugFirewall_LazyService.php new file mode 100644 index 0000000..f576054 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Security_Command_DebugFirewall_LazyService.php @@ -0,0 +1,26 @@ +privates['.security.command.debug_firewall.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:firewall', [], 'Display information about your security firewall(s)', false, #[\Closure(name: 'security.command.debug_firewall', class: 'Symfony\\Bundle\\SecurityBundle\\Command\\DebugFirewallCommand')] fn (): \Symfony\Bundle\SecurityBundle\Command\DebugFirewallCommand => ($container->privates['security.command.debug_firewall'] ?? $container->load('getSecurity_Command_DebugFirewallService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Security_Command_UserPasswordHash_LazyService.php b/var/cache/dev/Container6Szx89D/get_Security_Command_UserPasswordHash_LazyService.php new file mode 100644 index 0000000..9ad32b3 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Security_Command_UserPasswordHash_LazyService.php @@ -0,0 +1,26 @@ +privates['.security.command.user_password_hash.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('security:hash-password', [], 'Hash a user password', false, #[\Closure(name: 'security.command.user_password_hash', class: 'Symfony\\Component\\PasswordHasher\\Command\\UserPasswordHashCommand')] fn (): \Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand => ($container->privates['security.command.user_password_hash'] ?? $container->load('getSecurity_Command_UserPasswordHashService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Security_RequestMatcher_GOpgIHxService.php b/var/cache/dev/Container6Szx89D/get_Security_RequestMatcher_GOpgIHxService.php new file mode 100644 index 0000000..d05e817 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Security_RequestMatcher_GOpgIHxService.php @@ -0,0 +1,27 @@ +privates['.security.request_matcher.gOpgIHx'] = new \Symfony\Component\HttpFoundation\ChainRequestMatcher([new \Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher('^/(_(profiler|wdt)|css|images|js)/')]); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1Service.php b/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1Service.php new file mode 100644 index 0000000..5c6d067 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1Service.php @@ -0,0 +1,27 @@ +privates['.service_locator.4wc4Ag1'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'loader' => ['privates', '.errored..service_locator.4wc4Ag1.Symfony\\Component\\Config\\Loader\\LoaderInterface', NULL, 'Cannot autowire service ".service_locator.4wc4Ag1": it needs an instance of "Symfony\\Component\\Config\\Loader\\LoaderInterface" but this type has been excluded from autowiring.'], + ], [ + 'loader' => 'Symfony\\Component\\Config\\Loader\\LoaderInterface', + ]); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelloadRoutesService.php b/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelloadRoutesService.php new file mode 100644 index 0000000..9e62874 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelloadRoutesService.php @@ -0,0 +1,23 @@ +privates['.service_locator.4wc4Ag1.kernel::loadRoutes()'] = ($container->privates['.service_locator.4wc4Ag1'] ?? $container->load('get_ServiceLocator_4wc4Ag1Service'))->withContext('kernel::loadRoutes()', $container); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelregisterContainerConfigurationService.php b/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelregisterContainerConfigurationService.php new file mode 100644 index 0000000..d6d43f9 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_ServiceLocator_4wc4Ag1_KernelregisterContainerConfigurationService.php @@ -0,0 +1,23 @@ +privates['.service_locator.4wc4Ag1.kernel::registerContainerConfiguration()'] = ($container->privates['.service_locator.4wc4Ag1'] ?? $container->load('get_ServiceLocator_4wc4Ag1Service'))->withContext('kernel::registerContainerConfiguration()', $container); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_ServiceLocator_DyzWi7xService.php b/var/cache/dev/Container6Szx89D/get_ServiceLocator_DyzWi7xService.php new file mode 100644 index 0000000..2d6b3ec --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_ServiceLocator_DyzWi7xService.php @@ -0,0 +1,37 @@ +privates['.service_locator.dyzWi7x'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'kernel::registerContainerConfiguration' => ['privates', '.service_locator.4wc4Ag1.kernel::registerContainerConfiguration()', 'get_ServiceLocator_4wc4Ag1_KernelregisterContainerConfigurationService', true], + 'App\\Kernel::registerContainerConfiguration' => ['privates', '.service_locator.4wc4Ag1.kernel::registerContainerConfiguration()', 'get_ServiceLocator_4wc4Ag1_KernelregisterContainerConfigurationService', true], + 'kernel::loadRoutes' => ['privates', '.service_locator.4wc4Ag1.kernel::loadRoutes()', 'get_ServiceLocator_4wc4Ag1_KernelloadRoutesService', true], + 'App\\Kernel::loadRoutes' => ['privates', '.service_locator.4wc4Ag1.kernel::loadRoutes()', 'get_ServiceLocator_4wc4Ag1_KernelloadRoutesService', true], + 'kernel:registerContainerConfiguration' => ['privates', '.service_locator.4wc4Ag1.kernel::registerContainerConfiguration()', 'get_ServiceLocator_4wc4Ag1_KernelregisterContainerConfigurationService', true], + 'kernel:loadRoutes' => ['privates', '.service_locator.4wc4Ag1.kernel::loadRoutes()', 'get_ServiceLocator_4wc4Ag1_KernelloadRoutesService', true], + ], [ + 'kernel::registerContainerConfiguration' => '?', + 'App\\Kernel::registerContainerConfiguration' => '?', + 'kernel::loadRoutes' => '?', + 'App\\Kernel::loadRoutes' => '?', + 'kernel:registerContainerConfiguration' => '?', + 'kernel:loadRoutes' => '?', + ]); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_ServiceLocator_YbySDCAService.php b/var/cache/dev/Container6Szx89D/get_ServiceLocator_YbySDCAService.php new file mode 100644 index 0000000..cc2ac90 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_ServiceLocator_YbySDCAService.php @@ -0,0 +1,29 @@ +privates['.service_locator.YbySDCA'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'event_dispatcher' => ['services', 'event_dispatcher', 'getEventDispatcherService', false], + 'security.event_dispatcher.main' => ['privates', 'debug.security.event_dispatcher.main', 'getDebug_Security_EventDispatcher_MainService', false], + ], [ + 'event_dispatcher' => '?', + 'security.event_dispatcher.main' => '?', + ]); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Twig_Command_Debug_LazyService.php b/var/cache/dev/Container6Szx89D/get_Twig_Command_Debug_LazyService.php new file mode 100644 index 0000000..f00371d --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Twig_Command_Debug_LazyService.php @@ -0,0 +1,26 @@ +privates['.twig.command.debug.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('debug:twig', [], 'Show a list of twig functions, filters, globals and tests', false, #[\Closure(name: 'twig.command.debug', class: 'Symfony\\Bridge\\Twig\\Command\\DebugCommand')] fn (): \Symfony\Bridge\Twig\Command\DebugCommand => ($container->privates['twig.command.debug'] ?? $container->load('getTwig_Command_DebugService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/get_Twig_Command_Lint_LazyService.php b/var/cache/dev/Container6Szx89D/get_Twig_Command_Lint_LazyService.php new file mode 100644 index 0000000..495abd2 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/get_Twig_Command_Lint_LazyService.php @@ -0,0 +1,26 @@ +privates['.twig.command.lint.lazy'] = new \Symfony\Component\Console\Command\LazyCommand('lint:twig', [], 'Lint a Twig template and outputs encountered errors', false, #[\Closure(name: 'twig.command.lint', class: 'Symfony\\Bundle\\TwigBundle\\Command\\LintCommand')] fn (): \Symfony\Bundle\TwigBundle\Command\LintCommand => ($container->privates['twig.command.lint'] ?? $container->load('getTwig_Command_LintService'))); + } +} diff --git a/var/cache/dev/Container6Szx89D/removed-ids.php b/var/cache/dev/Container6Szx89D/removed-ids.php new file mode 100644 index 0000000..8039ca8 --- /dev/null +++ b/var/cache/dev/Container6Szx89D/removed-ids.php @@ -0,0 +1,671 @@ + true, + 'App\\Repository\\CommentRepository' => true, + 'App\\Repository\\IdRepository' => true, + 'App\\Repository\\UserRepository' => true, + 'Doctrine\\Bundle\\DoctrineBundle\\Controller\\ProfilerController' => true, + 'Doctrine\\Bundle\\DoctrineBundle\\Dbal\\ManagerRegistryAwareConnectionProvider' => true, + 'Doctrine\\Common\\Persistence\\ManagerRegistry' => true, + 'Doctrine\\DBAL\\Connection' => true, + 'Doctrine\\DBAL\\Connection $defaultConnection' => true, + 'Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand' => true, + 'Doctrine\\ORM\\EntityManagerInterface' => true, + 'Doctrine\\ORM\\EntityManagerInterface $defaultEntityManager' => true, + 'Doctrine\\Persistence\\ManagerRegistry' => true, + 'Lexik\\Bundle\\JWTAuthenticationBundle\\Encoder\\JWTEncoderInterface' => true, + 'Lexik\\Bundle\\JWTAuthenticationBundle\\Security\\Http\\Authentication\\AuthenticationFailureHandler' => true, + 'Lexik\\Bundle\\JWTAuthenticationBundle\\Security\\Http\\Authentication\\AuthenticationSuccessHandler' => true, + 'Lexik\\Bundle\\JWTAuthenticationBundle\\Services\\JWSProvider\\JWSProviderInterface' => true, + 'Lexik\\Bundle\\JWTAuthenticationBundle\\Services\\JWTTokenManagerInterface' => true, + 'Lexik\\Bundle\\JWTAuthenticationBundle\\TokenExtractor\\TokenExtractorInterface' => true, + 'Psr\\Cache\\CacheItemPoolInterface' => true, + 'Psr\\Clock\\ClockInterface' => true, + 'Psr\\Container\\ContainerInterface $parameterBag' => true, + 'Psr\\EventDispatcher\\EventDispatcherInterface' => true, + 'Psr\\Log\\LoggerInterface' => true, + 'SessionHandlerInterface' => true, + 'Symfony\\Bundle\\SecurityBundle\\Security' => true, + 'Symfony\\Component\\Clock\\ClockInterface' => true, + 'Symfony\\Component\\Config\\Loader\\LoaderInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ReverseContainer' => true, + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\FileLinkFormatter' => true, + 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => true, + 'Symfony\\Component\\Filesystem\\Filesystem' => true, + 'Symfony\\Component\\Form\\FormFactoryInterface' => true, + 'Symfony\\Component\\Form\\FormRegistryInterface' => true, + 'Symfony\\Component\\Form\\ResolvedFormTypeFactoryInterface' => true, + 'Symfony\\Component\\HttpFoundation\\Request' => true, + 'Symfony\\Component\\HttpFoundation\\RequestStack' => true, + 'Symfony\\Component\\HttpFoundation\\Response' => true, + 'Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => true, + 'Symfony\\Component\\HttpFoundation\\UriSigner' => true, + 'Symfony\\Component\\HttpFoundation\\UrlHelper' => true, + 'Symfony\\Component\\HttpKernel\\Config\\FileLocator' => true, + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGeneratorInterface' => true, + 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => true, + 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => true, + 'Symfony\\Component\\HttpKernel\\KernelInterface' => true, + 'Symfony\\Component\\Mailer\\MailerInterface' => true, + 'Symfony\\Component\\Mailer\\Transport\\TransportInterface' => true, + 'Symfony\\Component\\Mime\\BodyRendererInterface' => true, + 'Symfony\\Component\\Mime\\MimeTypeGuesserInterface' => true, + 'Symfony\\Component\\Mime\\MimeTypesInterface' => true, + 'Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactoryInterface' => true, + 'Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface' => true, + 'Symfony\\Component\\PropertyAccess\\PropertyAccessorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyInfoExtractorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyReadInfoExtractorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface' => true, + 'Symfony\\Component\\PropertyInfo\\PropertyWriteInfoExtractorInterface' => true, + 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => true, + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => true, + 'Symfony\\Component\\Routing\\RequestContext' => true, + 'Symfony\\Component\\Routing\\RequestContextAwareInterface' => true, + 'Symfony\\Component\\Routing\\RouterInterface' => true, + 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface' => true, + 'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface' => true, + 'Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationCheckerInterface' => true, + 'Symfony\\Component\\Security\\Core\\Role\\RoleHierarchyInterface' => true, + 'Symfony\\Component\\Security\\Core\\User\\UserCheckerInterface' => true, + 'Symfony\\Component\\Security\\Core\\User\\UserProviderInterface' => true, + 'Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface' => true, + 'Symfony\\Component\\Security\\Csrf\\TokenGenerator\\TokenGeneratorInterface' => true, + 'Symfony\\Component\\Security\\Csrf\\TokenStorage\\TokenStorageInterface' => true, + 'Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationUtils' => true, + 'Symfony\\Component\\Security\\Http\\Authentication\\UserAuthenticatorInterface' => true, + 'Symfony\\Component\\Security\\Http\\Firewall' => true, + 'Symfony\\Component\\Security\\Http\\FirewallMapInterface' => true, + 'Symfony\\Component\\Security\\Http\\HttpUtils' => true, + 'Symfony\\Component\\Security\\Http\\Session\\SessionAuthenticationStrategyInterface' => true, + 'Symfony\\Component\\Stopwatch\\Stopwatch' => true, + 'Symfony\\Component\\String\\Slugger\\SluggerInterface' => true, + 'Symfony\\Component\\TypeInfo\\TypeResolver\\TypeResolverInterface' => true, + 'Symfony\\Contracts\\Cache\\CacheInterface' => true, + 'Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => true, + 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => true, + 'Symfony\\Contracts\\HttpClient\\HttpClientInterface' => true, + 'Twig\\Environment' => true, + 'argument_metadata_factory' => true, + 'argument_resolver' => true, + 'argument_resolver.backed_enum_resolver' => true, + 'argument_resolver.controller_locator' => true, + 'argument_resolver.datetime' => true, + 'argument_resolver.default' => true, + 'argument_resolver.not_tagged_controller' => true, + 'argument_resolver.query_parameter_value_resolver' => true, + 'argument_resolver.request' => true, + 'argument_resolver.request_attribute' => true, + 'argument_resolver.request_payload' => true, + 'argument_resolver.service' => true, + 'argument_resolver.session' => true, + 'argument_resolver.variadic' => true, + 'cache.adapter.apcu' => true, + 'cache.adapter.array' => true, + 'cache.adapter.doctrine_dbal' => true, + 'cache.adapter.filesystem' => true, + 'cache.adapter.memcached' => true, + 'cache.adapter.pdo' => true, + 'cache.adapter.psr6' => true, + 'cache.adapter.redis' => true, + 'cache.adapter.redis_tag_aware' => true, + 'cache.adapter.system' => true, + 'cache.app.taggable' => true, + 'cache.default_clearer' => true, + 'cache.default_doctrine_dbal_provider' => true, + 'cache.default_marshaller' => true, + 'cache.default_memcached_provider' => true, + 'cache.default_redis_provider' => true, + 'cache.doctrine.orm.default.metadata' => true, + 'cache.doctrine.orm.default.query' => true, + 'cache.doctrine.orm.default.result' => true, + 'cache.early_expiration_handler' => true, + 'cache.property_access' => true, + 'cache.property_info' => true, + 'cache.security_expression_language' => true, + 'cache.serializer' => true, + 'cache.validator' => true, + 'cache_clearer' => true, + 'clock' => true, + 'config.resource.self_checking_resource_checker' => true, + 'config_builder.warmer' => true, + 'config_cache_factory' => true, + 'console.command.about' => true, + 'console.command.assets_install' => true, + 'console.command.cache_clear' => true, + 'console.command.cache_pool_clear' => true, + 'console.command.cache_pool_delete' => true, + 'console.command.cache_pool_invalidate_tags' => true, + 'console.command.cache_pool_list' => true, + 'console.command.cache_pool_prune' => true, + 'console.command.cache_warmup' => true, + 'console.command.config_debug' => true, + 'console.command.config_dump_reference' => true, + 'console.command.container_debug' => true, + 'console.command.container_lint' => true, + 'console.command.debug_autowiring' => true, + 'console.command.dotenv_debug' => true, + 'console.command.event_dispatcher_debug' => true, + 'console.command.form_debug' => true, + 'console.command.mailer_test' => true, + 'console.command.router_debug' => true, + 'console.command.router_match' => true, + 'console.command.secrets_decrypt_to_local' => true, + 'console.command.secrets_encrypt_from_local' => true, + 'console.command.secrets_generate_key' => true, + 'console.command.secrets_list' => true, + 'console.command.secrets_remove' => true, + 'console.command.secrets_reveal' => true, + 'console.command.secrets_set' => true, + 'console.command.yaml_lint' => true, + 'console.error_listener' => true, + 'console.messenger.application' => true, + 'console.messenger.execute_command_handler' => true, + 'console.suggest_missing_package_subscriber' => true, + 'container.env' => true, + 'container.env_var_processor' => true, + 'container.getenv' => true, + 'controller.cache_attribute_listener' => true, + 'controller.is_csrf_token_valid_attribute_listener' => true, + 'controller.is_granted_attribute_listener' => true, + 'controller.template_attribute_listener' => true, + 'controller_resolver' => true, + 'data_collector.doctrine' => true, + 'data_collector.security' => true, + 'data_collector.twig' => true, + 'debug.argument_resolver' => true, + 'debug.argument_resolver.inner' => true, + 'debug.controller_resolver' => true, + 'debug.controller_resolver.inner' => true, + 'debug.debug_handlers_listener' => true, + 'debug.event_dispatcher' => true, + 'debug.event_dispatcher.inner' => true, + 'debug.file_link_formatter' => true, + 'debug.security.access.decision_manager' => true, + 'debug.security.access.decision_manager.inner' => true, + 'debug.security.event_dispatcher.main' => true, + 'debug.security.event_dispatcher.main.inner' => true, + 'debug.security.firewall' => true, + 'debug.security.firewall.authenticator.main' => true, + 'debug.security.firewall.authenticator.main.inner' => true, + 'debug.security.voter.vote_listener' => true, + 'dependency_injection.config.container_parameters_resource_checker' => true, + 'disallow_search_engine_index_response_listener' => true, + 'doctrine.cache_clear_metadata_command' => true, + 'doctrine.cache_clear_query_cache_command' => true, + 'doctrine.cache_clear_result_command' => true, + 'doctrine.cache_collection_region_command' => true, + 'doctrine.clear_entity_region_command' => true, + 'doctrine.clear_query_region_command' => true, + 'doctrine.database_create_command' => true, + 'doctrine.database_drop_command' => true, + 'doctrine.dbal.connection' => true, + 'doctrine.dbal.connection.configuration' => true, + 'doctrine.dbal.connection.event_manager' => true, + 'doctrine.dbal.connection_expiries' => true, + 'doctrine.dbal.connection_factory' => true, + 'doctrine.dbal.connection_factory.dsn_parser' => true, + 'doctrine.dbal.debug_middleware' => true, + 'doctrine.dbal.debug_middleware.default' => true, + 'doctrine.dbal.default_connection.configuration' => true, + 'doctrine.dbal.default_connection.event_manager' => true, + 'doctrine.dbal.default_schema_manager_factory' => true, + 'doctrine.dbal.event_manager' => true, + 'doctrine.dbal.idle_connection_listener' => true, + 'doctrine.dbal.idle_connection_middleware' => true, + 'doctrine.dbal.idle_connection_middleware.default' => true, + 'doctrine.dbal.legacy_schema_manager_factory' => true, + 'doctrine.dbal.logging_middleware' => true, + 'doctrine.dbal.schema_asset_filter_manager' => true, + 'doctrine.dbal.well_known_schema_asset_filter' => true, + 'doctrine.debug_data_holder' => true, + 'doctrine.id_generator_locator' => true, + 'doctrine.mapping_info_command' => true, + 'doctrine.migrations.configuration' => true, + 'doctrine.migrations.configuration_loader' => true, + 'doctrine.migrations.connection_loader' => true, + 'doctrine.migrations.connection_registry_loader' => true, + 'doctrine.migrations.dependency_factory' => true, + 'doctrine.migrations.em_loader' => true, + 'doctrine.migrations.entity_manager_registry_loader' => true, + 'doctrine.migrations.metadata_storage' => true, + 'doctrine.migrations.migrations_factory' => true, + 'doctrine.migrations.storage.table_storage' => true, + 'doctrine.orm.command.entity_manager_provider' => true, + 'doctrine.orm.configuration' => true, + 'doctrine.orm.container_repository_factory' => true, + 'doctrine.orm.default_attribute_metadata_driver' => true, + 'doctrine.orm.default_configuration' => true, + 'doctrine.orm.default_entity_listener_resolver' => true, + 'doctrine.orm.default_entity_manager.event_manager' => true, + 'doctrine.orm.default_entity_manager.property_info_extractor' => true, + 'doctrine.orm.default_listeners.attach_entity_listeners' => true, + 'doctrine.orm.default_manager_configurator' => true, + 'doctrine.orm.default_metadata_cache' => true, + 'doctrine.orm.default_metadata_driver' => true, + 'doctrine.orm.default_query_cache' => true, + 'doctrine.orm.default_result_cache' => true, + 'doctrine.orm.entity_manager.abstract' => true, + 'doctrine.orm.entity_value_resolver' => true, + 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener' => true, + 'doctrine.orm.listeners.doctrine_token_provider_schema_listener' => true, + 'doctrine.orm.listeners.lock_store_schema_listener' => true, + 'doctrine.orm.listeners.pdo_session_handler_schema_listener' => true, + 'doctrine.orm.listeners.resolve_target_entity' => true, + 'doctrine.orm.manager_configurator.abstract' => true, + 'doctrine.orm.naming_strategy.default' => true, + 'doctrine.orm.naming_strategy.underscore' => true, + 'doctrine.orm.naming_strategy.underscore_number_aware' => true, + 'doctrine.orm.proxy_cache_warmer' => true, + 'doctrine.orm.quote_strategy.ansi' => true, + 'doctrine.orm.quote_strategy.default' => true, + 'doctrine.orm.security.user.provider' => true, + 'doctrine.orm.typed_field_mapper.default' => true, + 'doctrine.orm.validator.unique' => true, + 'doctrine.orm.validator_initializer' => true, + 'doctrine.query_dql_command' => true, + 'doctrine.query_sql_command' => true, + 'doctrine.schema_create_command' => true, + 'doctrine.schema_drop_command' => true, + 'doctrine.schema_update_command' => true, + 'doctrine.schema_validate_command' => true, + 'doctrine.twig.doctrine_extension' => true, + 'doctrine.ulid_generator' => true, + 'doctrine.uuid_generator' => true, + 'doctrine_migrations.current_command' => true, + 'doctrine_migrations.diff_command' => true, + 'doctrine_migrations.dump_schema_command' => true, + 'doctrine_migrations.execute_command' => true, + 'doctrine_migrations.generate_command' => true, + 'doctrine_migrations.latest_command' => true, + 'doctrine_migrations.migrate_command' => true, + 'doctrine_migrations.rollup_command' => true, + 'doctrine_migrations.status_command' => true, + 'doctrine_migrations.sync_metadata_command' => true, + 'doctrine_migrations.up_to_date_command' => true, + 'doctrine_migrations.version_command' => true, + 'doctrine_migrations.versions_command' => true, + 'error_handler.error_renderer.html' => true, + 'error_renderer' => true, + 'error_renderer.html' => true, + 'exception_listener' => true, + 'file_locator' => true, + 'filesystem' => true, + 'form.choice_list_factory' => true, + 'form.choice_list_factory.cached' => true, + 'form.choice_list_factory.default' => true, + 'form.choice_list_factory.property_access' => true, + 'form.extension' => true, + 'form.factory' => true, + 'form.listener.password_hasher' => true, + 'form.property_accessor' => true, + 'form.registry' => true, + 'form.resolved_type_factory' => true, + 'form.server_params' => true, + 'form.type.choice' => true, + 'form.type.color' => true, + 'form.type.entity' => true, + 'form.type.file' => true, + 'form.type.form' => true, + 'form.type_extension.csrf' => true, + 'form.type_extension.form.http_foundation' => true, + 'form.type_extension.form.password_hasher' => true, + 'form.type_extension.form.request_handler' => true, + 'form.type_extension.form.transformation_failure_handling' => true, + 'form.type_extension.password.password_hasher' => true, + 'form.type_extension.repeated.validator' => true, + 'form.type_extension.submit.validator' => true, + 'form.type_guesser.doctrine' => true, + 'fragment.handler' => true, + 'fragment.renderer.inline' => true, + 'fragment.uri_generator' => true, + 'http_cache' => true, + 'http_cache.store' => true, + 'http_client' => true, + 'http_client.abstract_retry_strategy' => true, + 'http_client.messenger.ping_webhook_handler' => true, + 'http_client.transport' => true, + 'http_client.uri_template' => true, + 'http_client.uri_template.inner' => true, + 'http_client.uri_template_expander.guzzle' => true, + 'http_client.uri_template_expander.rize' => true, + 'hwi_oauth.abstract_resource_ownermap' => true, + 'hwi_oauth.authentication.entry_point.oauth' => true, + 'hwi_oauth.authentication.failure_handler' => true, + 'hwi_oauth.authentication.listener.oauth' => true, + 'hwi_oauth.authentication.provider.oauth' => true, + 'hwi_oauth.context_listener.abstract_token_refresher' => true, + 'hwi_oauth.http_client' => true, + 'hwi_oauth.resource_owner.facebook' => true, + 'hwi_oauth.resource_ownermap_locator' => true, + 'hwi_oauth.resource_owners.locator' => true, + 'hwi_oauth.security.oauth_utils' => true, + 'hwi_oauth.storage.session' => true, + 'hwi_oauth.twig.extension.oauth' => true, + 'hwi_oauth.twig.extension.oauth.runtime' => true, + 'hwi_oauth.user.provider' => true, + 'hwi_oauth.user.provider.entity' => true, + 'hwi_oauth.util.domain_whitelist' => true, + 'lexik_jwt_authentication.check_config_command' => true, + 'lexik_jwt_authentication.enable_encryption_config_command' => true, + 'lexik_jwt_authentication.encoder.lcobucci' => true, + 'lexik_jwt_authentication.extractor.authorization_header_extractor' => true, + 'lexik_jwt_authentication.extractor.chain_extractor' => true, + 'lexik_jwt_authentication.extractor.cookie_extractor' => true, + 'lexik_jwt_authentication.extractor.query_parameter_extractor' => true, + 'lexik_jwt_authentication.extractor.split_cookie_extractor' => true, + 'lexik_jwt_authentication.generate_keypair_command' => true, + 'lexik_jwt_authentication.handler.authentication_failure' => true, + 'lexik_jwt_authentication.handler.authentication_success' => true, + 'lexik_jwt_authentication.jws_provider.lcobucci' => true, + 'lexik_jwt_authentication.key_loader.abstract' => true, + 'lexik_jwt_authentication.key_loader.raw' => true, + 'lexik_jwt_authentication.migrate_config_command' => true, + 'lexik_jwt_authentication.payload_enrichment' => true, + 'lexik_jwt_authentication.payload_enrichment.random_jti_enrichment' => true, + 'lexik_jwt_authentication.security.jwt_authenticator' => true, + 'lexik_jwt_authentication.security.jwt_user_provider' => true, + 'locale_aware_listener' => true, + 'locale_listener' => true, + 'logger' => true, + 'mailer' => true, + 'mailer.default_transport' => true, + 'mailer.envelope_listener' => true, + 'mailer.mailer' => true, + 'mailer.message_logger_listener' => true, + 'mailer.messenger.message_handler' => true, + 'mailer.messenger_transport_listener' => true, + 'mailer.transport_factory' => true, + 'mailer.transport_factory.abstract' => true, + 'mailer.transport_factory.native' => true, + 'mailer.transport_factory.null' => true, + 'mailer.transport_factory.sendmail' => true, + 'mailer.transport_factory.smtp' => true, + 'mailer.transports' => true, + 'maker.auto_command.abstract' => true, + 'maker.auto_command.make_auth' => true, + 'maker.auto_command.make_command' => true, + 'maker.auto_command.make_controller' => true, + 'maker.auto_command.make_crud' => true, + 'maker.auto_command.make_docker_database' => true, + 'maker.auto_command.make_entity' => true, + 'maker.auto_command.make_fixtures' => true, + 'maker.auto_command.make_form' => true, + 'maker.auto_command.make_listener' => true, + 'maker.auto_command.make_message' => true, + 'maker.auto_command.make_messenger_middleware' => true, + 'maker.auto_command.make_migration' => true, + 'maker.auto_command.make_registration_form' => true, + 'maker.auto_command.make_reset_password' => true, + 'maker.auto_command.make_schedule' => true, + 'maker.auto_command.make_security_custom' => true, + 'maker.auto_command.make_security_form_login' => true, + 'maker.auto_command.make_serializer_encoder' => true, + 'maker.auto_command.make_serializer_normalizer' => true, + 'maker.auto_command.make_stimulus_controller' => true, + 'maker.auto_command.make_test' => true, + 'maker.auto_command.make_twig_component' => true, + 'maker.auto_command.make_twig_extension' => true, + 'maker.auto_command.make_user' => true, + 'maker.auto_command.make_validator' => true, + 'maker.auto_command.make_voter' => true, + 'maker.auto_command.make_webhook' => true, + 'maker.autoloader_finder' => true, + 'maker.autoloader_util' => true, + 'maker.console_error_listener' => true, + 'maker.doctrine_helper' => true, + 'maker.entity_class_generator' => true, + 'maker.event_registry' => true, + 'maker.file_link_formatter' => true, + 'maker.file_manager' => true, + 'maker.generator' => true, + 'maker.maker.make_authenticator' => true, + 'maker.maker.make_command' => true, + 'maker.maker.make_controller' => true, + 'maker.maker.make_crud' => true, + 'maker.maker.make_custom_authenticator' => true, + 'maker.maker.make_docker_database' => true, + 'maker.maker.make_entity' => true, + 'maker.maker.make_fixtures' => true, + 'maker.maker.make_form' => true, + 'maker.maker.make_form_login' => true, + 'maker.maker.make_functional_test' => true, + 'maker.maker.make_listener' => true, + 'maker.maker.make_message' => true, + 'maker.maker.make_messenger_middleware' => true, + 'maker.maker.make_migration' => true, + 'maker.maker.make_registration_form' => true, + 'maker.maker.make_reset_password' => true, + 'maker.maker.make_schedule' => true, + 'maker.maker.make_serializer_encoder' => true, + 'maker.maker.make_serializer_normalizer' => true, + 'maker.maker.make_stimulus_controller' => true, + 'maker.maker.make_subscriber' => true, + 'maker.maker.make_test' => true, + 'maker.maker.make_twig_component' => true, + 'maker.maker.make_twig_extension' => true, + 'maker.maker.make_unit_test' => true, + 'maker.maker.make_user' => true, + 'maker.maker.make_validator' => true, + 'maker.maker.make_voter' => true, + 'maker.maker.make_webhook' => true, + 'maker.php_compat_util' => true, + 'maker.renderer.form_type_renderer' => true, + 'maker.security_config_updater' => true, + 'maker.security_controller_builder' => true, + 'maker.template_component_generator' => true, + 'maker.template_linter' => true, + 'maker.user_class_builder' => true, + 'mime_types' => true, + 'parameter_bag' => true, + 'process.messenger.process_message_handler' => true, + 'property_accessor' => true, + 'property_info' => true, + 'property_info.reflection_extractor' => true, + 'response_listener' => true, + 'reverse_container' => true, + 'router.cache_warmer' => true, + 'router.default' => true, + 'router.request_context' => true, + 'router_listener' => true, + 'routing.loader.attribute' => true, + 'routing.loader.attribute.directory' => true, + 'routing.loader.attribute.file' => true, + 'routing.loader.container' => true, + 'routing.loader.directory' => true, + 'routing.loader.glob' => true, + 'routing.loader.php' => true, + 'routing.loader.psr4' => true, + 'routing.loader.xml' => true, + 'routing.loader.yml' => true, + 'routing.resolver' => true, + 'secrets.decryption_key' => true, + 'secrets.env_var_loader' => true, + 'secrets.local_vault' => true, + 'secrets.vault' => true, + 'security.access.authenticated_voter' => true, + 'security.access.decision_manager' => true, + 'security.access.simple_role_voter' => true, + 'security.access_listener' => true, + 'security.access_map' => true, + 'security.access_token_extractor.header' => true, + 'security.access_token_extractor.query_string' => true, + 'security.access_token_extractor.request_body' => true, + 'security.access_token_handler.oidc' => true, + 'security.access_token_handler.oidc.algorithm_manager_factory' => true, + 'security.access_token_handler.oidc.jwk' => true, + 'security.access_token_handler.oidc.jwkset' => true, + 'security.access_token_handler.oidc.signature' => true, + 'security.access_token_handler.oidc.signature.ES256' => true, + 'security.access_token_handler.oidc.signature.ES384' => true, + 'security.access_token_handler.oidc.signature.ES512' => true, + 'security.access_token_handler.oidc.signature.PS256' => true, + 'security.access_token_handler.oidc.signature.PS384' => true, + 'security.access_token_handler.oidc.signature.PS512' => true, + 'security.access_token_handler.oidc.signature.RS256' => true, + 'security.access_token_handler.oidc.signature.RS384' => true, + 'security.access_token_handler.oidc.signature.RS512' => true, + 'security.access_token_handler.oidc_user_info' => true, + 'security.access_token_handler.oidc_user_info.http_client' => true, + 'security.authentication.custom_failure_handler' => true, + 'security.authentication.custom_success_handler' => true, + 'security.authentication.failure_handler' => true, + 'security.authentication.listener.abstract' => true, + 'security.authentication.session_strategy' => true, + 'security.authentication.session_strategy.main' => true, + 'security.authentication.session_strategy_noop' => true, + 'security.authentication.success_handler' => true, + 'security.authentication.switchuser_listener' => true, + 'security.authentication.trust_resolver' => true, + 'security.authentication_utils' => true, + 'security.authenticator.access_token' => true, + 'security.authenticator.access_token.chain_extractor' => true, + 'security.authenticator.form_login' => true, + 'security.authenticator.http_basic' => true, + 'security.authenticator.json_login' => true, + 'security.authenticator.manager' => true, + 'security.authenticator.manager.main' => true, + 'security.authenticator.managers_locator' => true, + 'security.authenticator.remote_user' => true, + 'security.authenticator.x509' => true, + 'security.authorization_checker' => true, + 'security.channel_listener' => true, + 'security.command.debug_firewall' => true, + 'security.command.user_password_hash' => true, + 'security.context_listener' => true, + 'security.context_listener.0' => true, + 'security.csrf.token_generator' => true, + 'security.csrf.token_manager' => true, + 'security.csrf.token_storage' => true, + 'security.event_dispatcher.main' => true, + 'security.exception_listener' => true, + 'security.exception_listener.main' => true, + 'security.firewall' => true, + 'security.firewall.authenticator' => true, + 'security.firewall.authenticator.main' => true, + 'security.firewall.config' => true, + 'security.firewall.context' => true, + 'security.firewall.context_locator' => true, + 'security.firewall.event_dispatcher_locator' => true, + 'security.firewall.lazy_context' => true, + 'security.firewall.map' => true, + 'security.firewall.map.config.dev' => true, + 'security.firewall.map.config.main' => true, + 'security.firewall.map.context.dev' => true, + 'security.firewall.map.context.main' => true, + 'security.helper' => true, + 'security.http_utils' => true, + 'security.impersonate_url_generator' => true, + 'security.ldap_locator' => true, + 'security.listener.check_authenticator_credentials' => true, + 'security.listener.csrf_protection' => true, + 'security.listener.login_throttling' => true, + 'security.listener.main.user_provider' => true, + 'security.listener.password_migrating' => true, + 'security.listener.session' => true, + 'security.listener.session.main' => true, + 'security.listener.user_checker' => true, + 'security.listener.user_checker.main' => true, + 'security.listener.user_provider' => true, + 'security.listener.user_provider.abstract' => true, + 'security.logout.listener.clear_site_data' => true, + 'security.logout.listener.cookie_clearing' => true, + 'security.logout.listener.csrf_token_clearing' => true, + 'security.logout.listener.default' => true, + 'security.logout.listener.session' => true, + 'security.logout_listener' => true, + 'security.logout_url_generator' => true, + 'security.password_hasher' => true, + 'security.password_hasher_factory' => true, + 'security.role_hierarchy' => true, + 'security.route_loader.logout' => true, + 'security.security_token_value_resolver' => true, + 'security.token_storage' => true, + 'security.untracked_token_storage' => true, + 'security.user.provider.chain' => true, + 'security.user.provider.concrete.users_in_memory' => true, + 'security.user.provider.in_memory' => true, + 'security.user.provider.ldap' => true, + 'security.user.provider.missing' => true, + 'security.user_authenticator' => true, + 'security.user_checker' => true, + 'security.user_checker.chain.main' => true, + 'security.user_checker.main' => true, + 'security.user_checker_locator' => true, + 'security.user_password_hasher' => true, + 'security.user_providers' => true, + 'security.user_value_resolver' => true, + 'security.validator.user_password' => true, + 'session.abstract_handler' => true, + 'session.factory' => true, + 'session.handler' => true, + 'session.handler.native' => true, + 'session.handler.native_file' => true, + 'session.marshaller' => true, + 'session.marshalling_handler' => true, + 'session.storage.factory' => true, + 'session.storage.factory.mock_file' => true, + 'session.storage.factory.native' => true, + 'session.storage.factory.php_bridge' => true, + 'session_listener' => true, + 'slugger' => true, + 'twig' => true, + 'twig.app_variable' => true, + 'twig.command.debug' => true, + 'twig.command.lint' => true, + 'twig.configurator.environment' => true, + 'twig.error_renderer.html' => true, + 'twig.error_renderer.html.inner' => true, + 'twig.extension.assets' => true, + 'twig.extension.debug' => true, + 'twig.extension.debug.stopwatch' => true, + 'twig.extension.emoji' => true, + 'twig.extension.expression' => true, + 'twig.extension.form' => true, + 'twig.extension.htmlsanitizer' => true, + 'twig.extension.httpfoundation' => true, + 'twig.extension.httpkernel' => true, + 'twig.extension.logout_url' => true, + 'twig.extension.profiler' => true, + 'twig.extension.routing' => true, + 'twig.extension.security' => true, + 'twig.extension.security_csrf' => true, + 'twig.extension.serializer' => true, + 'twig.extension.trans' => true, + 'twig.extension.weblink' => true, + 'twig.extension.yaml' => true, + 'twig.form.engine' => true, + 'twig.form.renderer' => true, + 'twig.loader' => true, + 'twig.loader.chain' => true, + 'twig.loader.filesystem' => true, + 'twig.loader.native_filesystem' => true, + 'twig.mailer.message_listener' => true, + 'twig.mime_body_renderer' => true, + 'twig.profile' => true, + 'twig.runtime.httpkernel' => true, + 'twig.runtime.security_csrf' => true, + 'twig.runtime.serializer' => true, + 'twig.runtime_loader' => true, + 'twig.template_cache_warmer' => true, + 'twig.template_iterator' => true, + 'type_info.resolver' => true, + 'type_info.resolver.reflection_parameter' => true, + 'type_info.resolver.reflection_property' => true, + 'type_info.resolver.reflection_return' => true, + 'type_info.resolver.reflection_type' => true, + 'type_info.type_context_factory' => true, + 'uri_signer' => true, + 'url_helper' => true, + 'validate_request_listener' => true, + 'workflow.twig_extension' => true, +]; diff --git a/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig.php new file mode 100644 index 0000000..8d8c26d --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig.php @@ -0,0 +1,1275 @@ +_usedProperties['url'] = true; + $this->url = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dbname($value): static + { + $this->_usedProperties['dbname'] = true; + $this->dbname = $value; + + return $this; + } + + /** + * Defaults to "localhost" at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function host($value): static + { + $this->_usedProperties['host'] = true; + $this->host = $value; + + return $this; + } + + /** + * Defaults to null at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function port($value): static + { + $this->_usedProperties['port'] = true; + $this->port = $value; + + return $this; + } + + /** + * Defaults to "root" at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function user($value): static + { + $this->_usedProperties['user'] = true; + $this->user = $value; + + return $this; + } + + /** + * Defaults to null at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function password($value): static + { + $this->_usedProperties['password'] = true; + $this->password = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @deprecated The "doctrine.dbal.override_url" configuration key is deprecated. + * @return $this + */ + public function overrideUrl($value): static + { + $this->_usedProperties['overrideUrl'] = true; + $this->overrideUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dbnameSuffix($value): static + { + $this->_usedProperties['dbnameSuffix'] = true; + $this->dbnameSuffix = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function applicationName($value): static + { + $this->_usedProperties['applicationName'] = true; + $this->applicationName = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function charset($value): static + { + $this->_usedProperties['charset'] = true; + $this->charset = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function memory($value): static + { + $this->_usedProperties['memory'] = true; + $this->memory = $value; + + return $this; + } + + /** + * The unix socket to use for MySQL + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function unixSocket($value): static + { + $this->_usedProperties['unixSocket'] = true; + $this->unixSocket = $value; + + return $this; + } + + /** + * True to use as persistent connection for the ibm_db2 driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function persistent($value): static + { + $this->_usedProperties['persistent'] = true; + $this->persistent = $value; + + return $this; + } + + /** + * The protocol to use for the ibm_db2 driver (default to TCPIP if omitted) + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function protocol($value): static + { + $this->_usedProperties['protocol'] = true; + $this->protocol = $value; + + return $this; + } + + /** + * True to use SERVICE_NAME as connection parameter instead of SID for Oracle + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function servicename($value): static + { + $this->_usedProperties['servicename'] = true; + $this->servicename = $value; + + return $this; + } + + /** + * The session mode to use for the oci8 driver + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sessionMode($value): static + { + $this->_usedProperties['sessionMode'] = true; + $this->sessionMode = $value; + + return $this; + } + + /** + * The name of a running database server to connect to for SQL Anywhere. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function server($value): static + { + $this->_usedProperties['server'] = true; + $this->server = $value; + + return $this; + } + + /** + * Override the default database (postgres) to connect to for PostgreSQL connexion. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultDbname($value): static + { + $this->_usedProperties['defaultDbname'] = true; + $this->defaultDbname = $value; + + return $this; + } + + /** + * Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslmode($value): static + { + $this->_usedProperties['sslmode'] = true; + $this->sslmode = $value; + + return $this; + } + + /** + * The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslrootcert($value): static + { + $this->_usedProperties['sslrootcert'] = true; + $this->sslrootcert = $value; + + return $this; + } + + /** + * The path to the SSL client certificate file for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslcert($value): static + { + $this->_usedProperties['sslcert'] = true; + $this->sslcert = $value; + + return $this; + } + + /** + * The path to the SSL client key file for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslkey($value): static + { + $this->_usedProperties['sslkey'] = true; + $this->sslkey = $value; + + return $this; + } + + /** + * The file name of the SSL certificate revocation list for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslcrl($value): static + { + $this->_usedProperties['sslcrl'] = true; + $this->sslcrl = $value; + + return $this; + } + + /** + * True to use a pooled server with the oci8/pdo_oracle driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function pooled($value): static + { + $this->_usedProperties['pooled'] = true; + $this->pooled = $value; + + return $this; + } + + /** + * Configuring MultipleActiveResultSets for the pdo_sqlsrv driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function multipleActiveResultSets($value): static + { + $this->_usedProperties['multipleActiveResultSets'] = true; + $this->multipleActiveResultSets = $value; + + return $this; + } + + /** + * Use savepoints for nested transactions + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useSavepoints($value): static + { + $this->_usedProperties['useSavepoints'] = true; + $this->useSavepoints = $value; + + return $this; + } + + /** + * Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function instancename($value): static + { + $this->_usedProperties['instancename'] = true; + $this->instancename = $value; + + return $this; + } + + /** + * Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function connectstring($value): static + { + $this->_usedProperties['connectstring'] = true; + $this->connectstring = $value; + + return $this; + } + + /** + * @default 'pdo_mysql' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function driver($value): static + { + $this->_usedProperties['driver'] = true; + $this->driver = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @deprecated The "platform_service" configuration key is deprecated since doctrine-bundle 2.9. DBAL 4 will not support setting a custom platform via connection params anymore. + * @return $this + */ + public function platformService($value): static + { + $this->_usedProperties['platformService'] = true; + $this->platformService = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function autoCommit($value): static + { + $this->_usedProperties['autoCommit'] = true; + $this->autoCommit = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function schemaFilter($value): static + { + $this->_usedProperties['schemaFilter'] = true; + $this->schemaFilter = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function logging($value): static + { + $this->_usedProperties['logging'] = true; + $this->logging = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function profiling($value): static + { + $this->_usedProperties['profiling'] = true; + $this->profiling = $value; + + return $this; + } + + /** + * Enables collecting backtraces when profiling is enabled + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function profilingCollectBacktrace($value): static + { + $this->_usedProperties['profilingCollectBacktrace'] = true; + $this->profilingCollectBacktrace = $value; + + return $this; + } + + /** + * Enables collecting schema errors when profiling is enabled + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function profilingCollectSchemaErrors($value): static + { + $this->_usedProperties['profilingCollectSchemaErrors'] = true; + $this->profilingCollectSchemaErrors = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function disableTypeComments($value): static + { + $this->_usedProperties['disableTypeComments'] = true; + $this->disableTypeComments = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function serverVersion($value): static + { + $this->_usedProperties['serverVersion'] = true; + $this->serverVersion = $value; + + return $this; + } + + /** + * @default 600 + * @param ParamConfigurator|int $value + * @return $this + */ + public function idleConnectionTtl($value): static + { + $this->_usedProperties['idleConnectionTtl'] = true; + $this->idleConnectionTtl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function driverClass($value): static + { + $this->_usedProperties['driverClass'] = true; + $this->driverClass = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function wrapperClass($value): static + { + $this->_usedProperties['wrapperClass'] = true; + $this->wrapperClass = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @deprecated The "keep_slave" configuration key is deprecated since doctrine-bundle 2.2. Use the "keep_replica" configuration key instead. + * @return $this + */ + public function keepSlave($value): static + { + $this->_usedProperties['keepSlave'] = true; + $this->keepSlave = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function keepReplica($value): static + { + $this->_usedProperties['keepReplica'] = true; + $this->keepReplica = $value; + + return $this; + } + + /** + * @return $this + */ + public function option(string $key, mixed $value): static + { + $this->_usedProperties['options'] = true; + $this->options[$key] = $value; + + return $this; + } + + /** + * @return $this + */ + public function mappingType(string $name, mixed $value): static + { + $this->_usedProperties['mappingTypes'] = true; + $this->mappingTypes[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function defaultTableOption(string $name, mixed $value): static + { + $this->_usedProperties['defaultTableOptions'] = true; + $this->defaultTableOptions[$name] = $value; + + return $this; + } + + /** + * @default 'doctrine.dbal.legacy_schema_manager_factory' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function schemaManagerFactory($value): static + { + $this->_usedProperties['schemaManagerFactory'] = true; + $this->schemaManagerFactory = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function resultCache($value): static + { + $this->_usedProperties['resultCache'] = true; + $this->resultCache = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @deprecated The "slaves" configuration key will be renamed to "replicas" in doctrine-bundle 3.0. "slaves" is deprecated since doctrine-bundle 2.2. + * @return \Symfony\Config\Doctrine\Dbal\ConnectionConfig\SlaveConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Dbal\ConnectionConfig\SlaveConfig : static) + */ + public function slave(string $name, mixed $value = []): \Symfony\Config\Doctrine\Dbal\ConnectionConfig\SlaveConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['slaves'] = true; + $this->slaves[$name] = $value; + + return $this; + } + + if (!isset($this->slaves[$name]) || !$this->slaves[$name] instanceof \Symfony\Config\Doctrine\Dbal\ConnectionConfig\SlaveConfig) { + $this->_usedProperties['slaves'] = true; + $this->slaves[$name] = new \Symfony\Config\Doctrine\Dbal\ConnectionConfig\SlaveConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "slave()" has already been initialized. You cannot pass values the second time you call slave().'); + } + + return $this->slaves[$name]; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Doctrine\Dbal\ConnectionConfig\ReplicaConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Dbal\ConnectionConfig\ReplicaConfig : static) + */ + public function replica(string $name, mixed $value = []): \Symfony\Config\Doctrine\Dbal\ConnectionConfig\ReplicaConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['replicas'] = true; + $this->replicas[$name] = $value; + + return $this; + } + + if (!isset($this->replicas[$name]) || !$this->replicas[$name] instanceof \Symfony\Config\Doctrine\Dbal\ConnectionConfig\ReplicaConfig) { + $this->_usedProperties['replicas'] = true; + $this->replicas[$name] = new \Symfony\Config\Doctrine\Dbal\ConnectionConfig\ReplicaConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "replica()" has already been initialized. You cannot pass values the second time you call replica().'); + } + + return $this->replicas[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('url', $value)) { + $this->_usedProperties['url'] = true; + $this->url = $value['url']; + unset($value['url']); + } + + if (array_key_exists('dbname', $value)) { + $this->_usedProperties['dbname'] = true; + $this->dbname = $value['dbname']; + unset($value['dbname']); + } + + if (array_key_exists('host', $value)) { + $this->_usedProperties['host'] = true; + $this->host = $value['host']; + unset($value['host']); + } + + if (array_key_exists('port', $value)) { + $this->_usedProperties['port'] = true; + $this->port = $value['port']; + unset($value['port']); + } + + if (array_key_exists('user', $value)) { + $this->_usedProperties['user'] = true; + $this->user = $value['user']; + unset($value['user']); + } + + if (array_key_exists('password', $value)) { + $this->_usedProperties['password'] = true; + $this->password = $value['password']; + unset($value['password']); + } + + if (array_key_exists('override_url', $value)) { + $this->_usedProperties['overrideUrl'] = true; + $this->overrideUrl = $value['override_url']; + unset($value['override_url']); + } + + if (array_key_exists('dbname_suffix', $value)) { + $this->_usedProperties['dbnameSuffix'] = true; + $this->dbnameSuffix = $value['dbname_suffix']; + unset($value['dbname_suffix']); + } + + if (array_key_exists('application_name', $value)) { + $this->_usedProperties['applicationName'] = true; + $this->applicationName = $value['application_name']; + unset($value['application_name']); + } + + if (array_key_exists('charset', $value)) { + $this->_usedProperties['charset'] = true; + $this->charset = $value['charset']; + unset($value['charset']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('memory', $value)) { + $this->_usedProperties['memory'] = true; + $this->memory = $value['memory']; + unset($value['memory']); + } + + if (array_key_exists('unix_socket', $value)) { + $this->_usedProperties['unixSocket'] = true; + $this->unixSocket = $value['unix_socket']; + unset($value['unix_socket']); + } + + if (array_key_exists('persistent', $value)) { + $this->_usedProperties['persistent'] = true; + $this->persistent = $value['persistent']; + unset($value['persistent']); + } + + if (array_key_exists('protocol', $value)) { + $this->_usedProperties['protocol'] = true; + $this->protocol = $value['protocol']; + unset($value['protocol']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('servicename', $value)) { + $this->_usedProperties['servicename'] = true; + $this->servicename = $value['servicename']; + unset($value['servicename']); + } + + if (array_key_exists('sessionMode', $value)) { + $this->_usedProperties['sessionMode'] = true; + $this->sessionMode = $value['sessionMode']; + unset($value['sessionMode']); + } + + if (array_key_exists('server', $value)) { + $this->_usedProperties['server'] = true; + $this->server = $value['server']; + unset($value['server']); + } + + if (array_key_exists('default_dbname', $value)) { + $this->_usedProperties['defaultDbname'] = true; + $this->defaultDbname = $value['default_dbname']; + unset($value['default_dbname']); + } + + if (array_key_exists('sslmode', $value)) { + $this->_usedProperties['sslmode'] = true; + $this->sslmode = $value['sslmode']; + unset($value['sslmode']); + } + + if (array_key_exists('sslrootcert', $value)) { + $this->_usedProperties['sslrootcert'] = true; + $this->sslrootcert = $value['sslrootcert']; + unset($value['sslrootcert']); + } + + if (array_key_exists('sslcert', $value)) { + $this->_usedProperties['sslcert'] = true; + $this->sslcert = $value['sslcert']; + unset($value['sslcert']); + } + + if (array_key_exists('sslkey', $value)) { + $this->_usedProperties['sslkey'] = true; + $this->sslkey = $value['sslkey']; + unset($value['sslkey']); + } + + if (array_key_exists('sslcrl', $value)) { + $this->_usedProperties['sslcrl'] = true; + $this->sslcrl = $value['sslcrl']; + unset($value['sslcrl']); + } + + if (array_key_exists('pooled', $value)) { + $this->_usedProperties['pooled'] = true; + $this->pooled = $value['pooled']; + unset($value['pooled']); + } + + if (array_key_exists('MultipleActiveResultSets', $value)) { + $this->_usedProperties['multipleActiveResultSets'] = true; + $this->multipleActiveResultSets = $value['MultipleActiveResultSets']; + unset($value['MultipleActiveResultSets']); + } + + if (array_key_exists('use_savepoints', $value)) { + $this->_usedProperties['useSavepoints'] = true; + $this->useSavepoints = $value['use_savepoints']; + unset($value['use_savepoints']); + } + + if (array_key_exists('instancename', $value)) { + $this->_usedProperties['instancename'] = true; + $this->instancename = $value['instancename']; + unset($value['instancename']); + } + + if (array_key_exists('connectstring', $value)) { + $this->_usedProperties['connectstring'] = true; + $this->connectstring = $value['connectstring']; + unset($value['connectstring']); + } + + if (array_key_exists('driver', $value)) { + $this->_usedProperties['driver'] = true; + $this->driver = $value['driver']; + unset($value['driver']); + } + + if (array_key_exists('platform_service', $value)) { + $this->_usedProperties['platformService'] = true; + $this->platformService = $value['platform_service']; + unset($value['platform_service']); + } + + if (array_key_exists('auto_commit', $value)) { + $this->_usedProperties['autoCommit'] = true; + $this->autoCommit = $value['auto_commit']; + unset($value['auto_commit']); + } + + if (array_key_exists('schema_filter', $value)) { + $this->_usedProperties['schemaFilter'] = true; + $this->schemaFilter = $value['schema_filter']; + unset($value['schema_filter']); + } + + if (array_key_exists('logging', $value)) { + $this->_usedProperties['logging'] = true; + $this->logging = $value['logging']; + unset($value['logging']); + } + + if (array_key_exists('profiling', $value)) { + $this->_usedProperties['profiling'] = true; + $this->profiling = $value['profiling']; + unset($value['profiling']); + } + + if (array_key_exists('profiling_collect_backtrace', $value)) { + $this->_usedProperties['profilingCollectBacktrace'] = true; + $this->profilingCollectBacktrace = $value['profiling_collect_backtrace']; + unset($value['profiling_collect_backtrace']); + } + + if (array_key_exists('profiling_collect_schema_errors', $value)) { + $this->_usedProperties['profilingCollectSchemaErrors'] = true; + $this->profilingCollectSchemaErrors = $value['profiling_collect_schema_errors']; + unset($value['profiling_collect_schema_errors']); + } + + if (array_key_exists('disable_type_comments', $value)) { + $this->_usedProperties['disableTypeComments'] = true; + $this->disableTypeComments = $value['disable_type_comments']; + unset($value['disable_type_comments']); + } + + if (array_key_exists('server_version', $value)) { + $this->_usedProperties['serverVersion'] = true; + $this->serverVersion = $value['server_version']; + unset($value['server_version']); + } + + if (array_key_exists('idle_connection_ttl', $value)) { + $this->_usedProperties['idleConnectionTtl'] = true; + $this->idleConnectionTtl = $value['idle_connection_ttl']; + unset($value['idle_connection_ttl']); + } + + if (array_key_exists('driver_class', $value)) { + $this->_usedProperties['driverClass'] = true; + $this->driverClass = $value['driver_class']; + unset($value['driver_class']); + } + + if (array_key_exists('wrapper_class', $value)) { + $this->_usedProperties['wrapperClass'] = true; + $this->wrapperClass = $value['wrapper_class']; + unset($value['wrapper_class']); + } + + if (array_key_exists('keep_slave', $value)) { + $this->_usedProperties['keepSlave'] = true; + $this->keepSlave = $value['keep_slave']; + unset($value['keep_slave']); + } + + if (array_key_exists('keep_replica', $value)) { + $this->_usedProperties['keepReplica'] = true; + $this->keepReplica = $value['keep_replica']; + unset($value['keep_replica']); + } + + if (array_key_exists('options', $value)) { + $this->_usedProperties['options'] = true; + $this->options = $value['options']; + unset($value['options']); + } + + if (array_key_exists('mapping_types', $value)) { + $this->_usedProperties['mappingTypes'] = true; + $this->mappingTypes = $value['mapping_types']; + unset($value['mapping_types']); + } + + if (array_key_exists('default_table_options', $value)) { + $this->_usedProperties['defaultTableOptions'] = true; + $this->defaultTableOptions = $value['default_table_options']; + unset($value['default_table_options']); + } + + if (array_key_exists('schema_manager_factory', $value)) { + $this->_usedProperties['schemaManagerFactory'] = true; + $this->schemaManagerFactory = $value['schema_manager_factory']; + unset($value['schema_manager_factory']); + } + + if (array_key_exists('result_cache', $value)) { + $this->_usedProperties['resultCache'] = true; + $this->resultCache = $value['result_cache']; + unset($value['result_cache']); + } + + if (array_key_exists('slaves', $value)) { + $this->_usedProperties['slaves'] = true; + $this->slaves = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Doctrine\Dbal\ConnectionConfig\SlaveConfig($v) : $v, $value['slaves']); + unset($value['slaves']); + } + + if (array_key_exists('replicas', $value)) { + $this->_usedProperties['replicas'] = true; + $this->replicas = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Doctrine\Dbal\ConnectionConfig\ReplicaConfig($v) : $v, $value['replicas']); + unset($value['replicas']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['url'])) { + $output['url'] = $this->url; + } + if (isset($this->_usedProperties['dbname'])) { + $output['dbname'] = $this->dbname; + } + if (isset($this->_usedProperties['host'])) { + $output['host'] = $this->host; + } + if (isset($this->_usedProperties['port'])) { + $output['port'] = $this->port; + } + if (isset($this->_usedProperties['user'])) { + $output['user'] = $this->user; + } + if (isset($this->_usedProperties['password'])) { + $output['password'] = $this->password; + } + if (isset($this->_usedProperties['overrideUrl'])) { + $output['override_url'] = $this->overrideUrl; + } + if (isset($this->_usedProperties['dbnameSuffix'])) { + $output['dbname_suffix'] = $this->dbnameSuffix; + } + if (isset($this->_usedProperties['applicationName'])) { + $output['application_name'] = $this->applicationName; + } + if (isset($this->_usedProperties['charset'])) { + $output['charset'] = $this->charset; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['memory'])) { + $output['memory'] = $this->memory; + } + if (isset($this->_usedProperties['unixSocket'])) { + $output['unix_socket'] = $this->unixSocket; + } + if (isset($this->_usedProperties['persistent'])) { + $output['persistent'] = $this->persistent; + } + if (isset($this->_usedProperties['protocol'])) { + $output['protocol'] = $this->protocol; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['servicename'])) { + $output['servicename'] = $this->servicename; + } + if (isset($this->_usedProperties['sessionMode'])) { + $output['sessionMode'] = $this->sessionMode; + } + if (isset($this->_usedProperties['server'])) { + $output['server'] = $this->server; + } + if (isset($this->_usedProperties['defaultDbname'])) { + $output['default_dbname'] = $this->defaultDbname; + } + if (isset($this->_usedProperties['sslmode'])) { + $output['sslmode'] = $this->sslmode; + } + if (isset($this->_usedProperties['sslrootcert'])) { + $output['sslrootcert'] = $this->sslrootcert; + } + if (isset($this->_usedProperties['sslcert'])) { + $output['sslcert'] = $this->sslcert; + } + if (isset($this->_usedProperties['sslkey'])) { + $output['sslkey'] = $this->sslkey; + } + if (isset($this->_usedProperties['sslcrl'])) { + $output['sslcrl'] = $this->sslcrl; + } + if (isset($this->_usedProperties['pooled'])) { + $output['pooled'] = $this->pooled; + } + if (isset($this->_usedProperties['multipleActiveResultSets'])) { + $output['MultipleActiveResultSets'] = $this->multipleActiveResultSets; + } + if (isset($this->_usedProperties['useSavepoints'])) { + $output['use_savepoints'] = $this->useSavepoints; + } + if (isset($this->_usedProperties['instancename'])) { + $output['instancename'] = $this->instancename; + } + if (isset($this->_usedProperties['connectstring'])) { + $output['connectstring'] = $this->connectstring; + } + if (isset($this->_usedProperties['driver'])) { + $output['driver'] = $this->driver; + } + if (isset($this->_usedProperties['platformService'])) { + $output['platform_service'] = $this->platformService; + } + if (isset($this->_usedProperties['autoCommit'])) { + $output['auto_commit'] = $this->autoCommit; + } + if (isset($this->_usedProperties['schemaFilter'])) { + $output['schema_filter'] = $this->schemaFilter; + } + if (isset($this->_usedProperties['logging'])) { + $output['logging'] = $this->logging; + } + if (isset($this->_usedProperties['profiling'])) { + $output['profiling'] = $this->profiling; + } + if (isset($this->_usedProperties['profilingCollectBacktrace'])) { + $output['profiling_collect_backtrace'] = $this->profilingCollectBacktrace; + } + if (isset($this->_usedProperties['profilingCollectSchemaErrors'])) { + $output['profiling_collect_schema_errors'] = $this->profilingCollectSchemaErrors; + } + if (isset($this->_usedProperties['disableTypeComments'])) { + $output['disable_type_comments'] = $this->disableTypeComments; + } + if (isset($this->_usedProperties['serverVersion'])) { + $output['server_version'] = $this->serverVersion; + } + if (isset($this->_usedProperties['idleConnectionTtl'])) { + $output['idle_connection_ttl'] = $this->idleConnectionTtl; + } + if (isset($this->_usedProperties['driverClass'])) { + $output['driver_class'] = $this->driverClass; + } + if (isset($this->_usedProperties['wrapperClass'])) { + $output['wrapper_class'] = $this->wrapperClass; + } + if (isset($this->_usedProperties['keepSlave'])) { + $output['keep_slave'] = $this->keepSlave; + } + if (isset($this->_usedProperties['keepReplica'])) { + $output['keep_replica'] = $this->keepReplica; + } + if (isset($this->_usedProperties['options'])) { + $output['options'] = $this->options; + } + if (isset($this->_usedProperties['mappingTypes'])) { + $output['mapping_types'] = $this->mappingTypes; + } + if (isset($this->_usedProperties['defaultTableOptions'])) { + $output['default_table_options'] = $this->defaultTableOptions; + } + if (isset($this->_usedProperties['schemaManagerFactory'])) { + $output['schema_manager_factory'] = $this->schemaManagerFactory; + } + if (isset($this->_usedProperties['resultCache'])) { + $output['result_cache'] = $this->resultCache; + } + if (isset($this->_usedProperties['slaves'])) { + $output['slaves'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Doctrine\Dbal\ConnectionConfig\SlaveConfig ? $v->toArray() : $v, $this->slaves); + } + if (isset($this->_usedProperties['replicas'])) { + $output['replicas'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Doctrine\Dbal\ConnectionConfig\ReplicaConfig ? $v->toArray() : $v, $this->replicas); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig/ReplicaConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig/ReplicaConfig.php new file mode 100644 index 0000000..3fee328 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig/ReplicaConfig.php @@ -0,0 +1,743 @@ +_usedProperties['url'] = true; + $this->url = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dbname($value): static + { + $this->_usedProperties['dbname'] = true; + $this->dbname = $value; + + return $this; + } + + /** + * Defaults to "localhost" at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function host($value): static + { + $this->_usedProperties['host'] = true; + $this->host = $value; + + return $this; + } + + /** + * Defaults to null at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function port($value): static + { + $this->_usedProperties['port'] = true; + $this->port = $value; + + return $this; + } + + /** + * Defaults to "root" at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function user($value): static + { + $this->_usedProperties['user'] = true; + $this->user = $value; + + return $this; + } + + /** + * Defaults to null at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function password($value): static + { + $this->_usedProperties['password'] = true; + $this->password = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @deprecated The "doctrine.dbal.override_url" configuration key is deprecated. + * @return $this + */ + public function overrideUrl($value): static + { + $this->_usedProperties['overrideUrl'] = true; + $this->overrideUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dbnameSuffix($value): static + { + $this->_usedProperties['dbnameSuffix'] = true; + $this->dbnameSuffix = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function applicationName($value): static + { + $this->_usedProperties['applicationName'] = true; + $this->applicationName = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function charset($value): static + { + $this->_usedProperties['charset'] = true; + $this->charset = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function memory($value): static + { + $this->_usedProperties['memory'] = true; + $this->memory = $value; + + return $this; + } + + /** + * The unix socket to use for MySQL + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function unixSocket($value): static + { + $this->_usedProperties['unixSocket'] = true; + $this->unixSocket = $value; + + return $this; + } + + /** + * True to use as persistent connection for the ibm_db2 driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function persistent($value): static + { + $this->_usedProperties['persistent'] = true; + $this->persistent = $value; + + return $this; + } + + /** + * The protocol to use for the ibm_db2 driver (default to TCPIP if omitted) + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function protocol($value): static + { + $this->_usedProperties['protocol'] = true; + $this->protocol = $value; + + return $this; + } + + /** + * True to use SERVICE_NAME as connection parameter instead of SID for Oracle + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function servicename($value): static + { + $this->_usedProperties['servicename'] = true; + $this->servicename = $value; + + return $this; + } + + /** + * The session mode to use for the oci8 driver + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sessionMode($value): static + { + $this->_usedProperties['sessionMode'] = true; + $this->sessionMode = $value; + + return $this; + } + + /** + * The name of a running database server to connect to for SQL Anywhere. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function server($value): static + { + $this->_usedProperties['server'] = true; + $this->server = $value; + + return $this; + } + + /** + * Override the default database (postgres) to connect to for PostgreSQL connexion. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultDbname($value): static + { + $this->_usedProperties['defaultDbname'] = true; + $this->defaultDbname = $value; + + return $this; + } + + /** + * Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslmode($value): static + { + $this->_usedProperties['sslmode'] = true; + $this->sslmode = $value; + + return $this; + } + + /** + * The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslrootcert($value): static + { + $this->_usedProperties['sslrootcert'] = true; + $this->sslrootcert = $value; + + return $this; + } + + /** + * The path to the SSL client certificate file for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslcert($value): static + { + $this->_usedProperties['sslcert'] = true; + $this->sslcert = $value; + + return $this; + } + + /** + * The path to the SSL client key file for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslkey($value): static + { + $this->_usedProperties['sslkey'] = true; + $this->sslkey = $value; + + return $this; + } + + /** + * The file name of the SSL certificate revocation list for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslcrl($value): static + { + $this->_usedProperties['sslcrl'] = true; + $this->sslcrl = $value; + + return $this; + } + + /** + * True to use a pooled server with the oci8/pdo_oracle driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function pooled($value): static + { + $this->_usedProperties['pooled'] = true; + $this->pooled = $value; + + return $this; + } + + /** + * Configuring MultipleActiveResultSets for the pdo_sqlsrv driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function multipleActiveResultSets($value): static + { + $this->_usedProperties['multipleActiveResultSets'] = true; + $this->multipleActiveResultSets = $value; + + return $this; + } + + /** + * Use savepoints for nested transactions + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useSavepoints($value): static + { + $this->_usedProperties['useSavepoints'] = true; + $this->useSavepoints = $value; + + return $this; + } + + /** + * Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function instancename($value): static + { + $this->_usedProperties['instancename'] = true; + $this->instancename = $value; + + return $this; + } + + /** + * Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function connectstring($value): static + { + $this->_usedProperties['connectstring'] = true; + $this->connectstring = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('url', $value)) { + $this->_usedProperties['url'] = true; + $this->url = $value['url']; + unset($value['url']); + } + + if (array_key_exists('dbname', $value)) { + $this->_usedProperties['dbname'] = true; + $this->dbname = $value['dbname']; + unset($value['dbname']); + } + + if (array_key_exists('host', $value)) { + $this->_usedProperties['host'] = true; + $this->host = $value['host']; + unset($value['host']); + } + + if (array_key_exists('port', $value)) { + $this->_usedProperties['port'] = true; + $this->port = $value['port']; + unset($value['port']); + } + + if (array_key_exists('user', $value)) { + $this->_usedProperties['user'] = true; + $this->user = $value['user']; + unset($value['user']); + } + + if (array_key_exists('password', $value)) { + $this->_usedProperties['password'] = true; + $this->password = $value['password']; + unset($value['password']); + } + + if (array_key_exists('override_url', $value)) { + $this->_usedProperties['overrideUrl'] = true; + $this->overrideUrl = $value['override_url']; + unset($value['override_url']); + } + + if (array_key_exists('dbname_suffix', $value)) { + $this->_usedProperties['dbnameSuffix'] = true; + $this->dbnameSuffix = $value['dbname_suffix']; + unset($value['dbname_suffix']); + } + + if (array_key_exists('application_name', $value)) { + $this->_usedProperties['applicationName'] = true; + $this->applicationName = $value['application_name']; + unset($value['application_name']); + } + + if (array_key_exists('charset', $value)) { + $this->_usedProperties['charset'] = true; + $this->charset = $value['charset']; + unset($value['charset']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('memory', $value)) { + $this->_usedProperties['memory'] = true; + $this->memory = $value['memory']; + unset($value['memory']); + } + + if (array_key_exists('unix_socket', $value)) { + $this->_usedProperties['unixSocket'] = true; + $this->unixSocket = $value['unix_socket']; + unset($value['unix_socket']); + } + + if (array_key_exists('persistent', $value)) { + $this->_usedProperties['persistent'] = true; + $this->persistent = $value['persistent']; + unset($value['persistent']); + } + + if (array_key_exists('protocol', $value)) { + $this->_usedProperties['protocol'] = true; + $this->protocol = $value['protocol']; + unset($value['protocol']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('servicename', $value)) { + $this->_usedProperties['servicename'] = true; + $this->servicename = $value['servicename']; + unset($value['servicename']); + } + + if (array_key_exists('sessionMode', $value)) { + $this->_usedProperties['sessionMode'] = true; + $this->sessionMode = $value['sessionMode']; + unset($value['sessionMode']); + } + + if (array_key_exists('server', $value)) { + $this->_usedProperties['server'] = true; + $this->server = $value['server']; + unset($value['server']); + } + + if (array_key_exists('default_dbname', $value)) { + $this->_usedProperties['defaultDbname'] = true; + $this->defaultDbname = $value['default_dbname']; + unset($value['default_dbname']); + } + + if (array_key_exists('sslmode', $value)) { + $this->_usedProperties['sslmode'] = true; + $this->sslmode = $value['sslmode']; + unset($value['sslmode']); + } + + if (array_key_exists('sslrootcert', $value)) { + $this->_usedProperties['sslrootcert'] = true; + $this->sslrootcert = $value['sslrootcert']; + unset($value['sslrootcert']); + } + + if (array_key_exists('sslcert', $value)) { + $this->_usedProperties['sslcert'] = true; + $this->sslcert = $value['sslcert']; + unset($value['sslcert']); + } + + if (array_key_exists('sslkey', $value)) { + $this->_usedProperties['sslkey'] = true; + $this->sslkey = $value['sslkey']; + unset($value['sslkey']); + } + + if (array_key_exists('sslcrl', $value)) { + $this->_usedProperties['sslcrl'] = true; + $this->sslcrl = $value['sslcrl']; + unset($value['sslcrl']); + } + + if (array_key_exists('pooled', $value)) { + $this->_usedProperties['pooled'] = true; + $this->pooled = $value['pooled']; + unset($value['pooled']); + } + + if (array_key_exists('MultipleActiveResultSets', $value)) { + $this->_usedProperties['multipleActiveResultSets'] = true; + $this->multipleActiveResultSets = $value['MultipleActiveResultSets']; + unset($value['MultipleActiveResultSets']); + } + + if (array_key_exists('use_savepoints', $value)) { + $this->_usedProperties['useSavepoints'] = true; + $this->useSavepoints = $value['use_savepoints']; + unset($value['use_savepoints']); + } + + if (array_key_exists('instancename', $value)) { + $this->_usedProperties['instancename'] = true; + $this->instancename = $value['instancename']; + unset($value['instancename']); + } + + if (array_key_exists('connectstring', $value)) { + $this->_usedProperties['connectstring'] = true; + $this->connectstring = $value['connectstring']; + unset($value['connectstring']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['url'])) { + $output['url'] = $this->url; + } + if (isset($this->_usedProperties['dbname'])) { + $output['dbname'] = $this->dbname; + } + if (isset($this->_usedProperties['host'])) { + $output['host'] = $this->host; + } + if (isset($this->_usedProperties['port'])) { + $output['port'] = $this->port; + } + if (isset($this->_usedProperties['user'])) { + $output['user'] = $this->user; + } + if (isset($this->_usedProperties['password'])) { + $output['password'] = $this->password; + } + if (isset($this->_usedProperties['overrideUrl'])) { + $output['override_url'] = $this->overrideUrl; + } + if (isset($this->_usedProperties['dbnameSuffix'])) { + $output['dbname_suffix'] = $this->dbnameSuffix; + } + if (isset($this->_usedProperties['applicationName'])) { + $output['application_name'] = $this->applicationName; + } + if (isset($this->_usedProperties['charset'])) { + $output['charset'] = $this->charset; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['memory'])) { + $output['memory'] = $this->memory; + } + if (isset($this->_usedProperties['unixSocket'])) { + $output['unix_socket'] = $this->unixSocket; + } + if (isset($this->_usedProperties['persistent'])) { + $output['persistent'] = $this->persistent; + } + if (isset($this->_usedProperties['protocol'])) { + $output['protocol'] = $this->protocol; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['servicename'])) { + $output['servicename'] = $this->servicename; + } + if (isset($this->_usedProperties['sessionMode'])) { + $output['sessionMode'] = $this->sessionMode; + } + if (isset($this->_usedProperties['server'])) { + $output['server'] = $this->server; + } + if (isset($this->_usedProperties['defaultDbname'])) { + $output['default_dbname'] = $this->defaultDbname; + } + if (isset($this->_usedProperties['sslmode'])) { + $output['sslmode'] = $this->sslmode; + } + if (isset($this->_usedProperties['sslrootcert'])) { + $output['sslrootcert'] = $this->sslrootcert; + } + if (isset($this->_usedProperties['sslcert'])) { + $output['sslcert'] = $this->sslcert; + } + if (isset($this->_usedProperties['sslkey'])) { + $output['sslkey'] = $this->sslkey; + } + if (isset($this->_usedProperties['sslcrl'])) { + $output['sslcrl'] = $this->sslcrl; + } + if (isset($this->_usedProperties['pooled'])) { + $output['pooled'] = $this->pooled; + } + if (isset($this->_usedProperties['multipleActiveResultSets'])) { + $output['MultipleActiveResultSets'] = $this->multipleActiveResultSets; + } + if (isset($this->_usedProperties['useSavepoints'])) { + $output['use_savepoints'] = $this->useSavepoints; + } + if (isset($this->_usedProperties['instancename'])) { + $output['instancename'] = $this->instancename; + } + if (isset($this->_usedProperties['connectstring'])) { + $output['connectstring'] = $this->connectstring; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig/SlaveConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig/SlaveConfig.php new file mode 100644 index 0000000..82b37f0 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Dbal/ConnectionConfig/SlaveConfig.php @@ -0,0 +1,743 @@ +_usedProperties['url'] = true; + $this->url = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dbname($value): static + { + $this->_usedProperties['dbname'] = true; + $this->dbname = $value; + + return $this; + } + + /** + * Defaults to "localhost" at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function host($value): static + { + $this->_usedProperties['host'] = true; + $this->host = $value; + + return $this; + } + + /** + * Defaults to null at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function port($value): static + { + $this->_usedProperties['port'] = true; + $this->port = $value; + + return $this; + } + + /** + * Defaults to "root" at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function user($value): static + { + $this->_usedProperties['user'] = true; + $this->user = $value; + + return $this; + } + + /** + * Defaults to null at runtime. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function password($value): static + { + $this->_usedProperties['password'] = true; + $this->password = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @deprecated The "doctrine.dbal.override_url" configuration key is deprecated. + * @return $this + */ + public function overrideUrl($value): static + { + $this->_usedProperties['overrideUrl'] = true; + $this->overrideUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dbnameSuffix($value): static + { + $this->_usedProperties['dbnameSuffix'] = true; + $this->dbnameSuffix = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function applicationName($value): static + { + $this->_usedProperties['applicationName'] = true; + $this->applicationName = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function charset($value): static + { + $this->_usedProperties['charset'] = true; + $this->charset = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function memory($value): static + { + $this->_usedProperties['memory'] = true; + $this->memory = $value; + + return $this; + } + + /** + * The unix socket to use for MySQL + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function unixSocket($value): static + { + $this->_usedProperties['unixSocket'] = true; + $this->unixSocket = $value; + + return $this; + } + + /** + * True to use as persistent connection for the ibm_db2 driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function persistent($value): static + { + $this->_usedProperties['persistent'] = true; + $this->persistent = $value; + + return $this; + } + + /** + * The protocol to use for the ibm_db2 driver (default to TCPIP if omitted) + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function protocol($value): static + { + $this->_usedProperties['protocol'] = true; + $this->protocol = $value; + + return $this; + } + + /** + * True to use SERVICE_NAME as connection parameter instead of SID for Oracle + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function servicename($value): static + { + $this->_usedProperties['servicename'] = true; + $this->servicename = $value; + + return $this; + } + + /** + * The session mode to use for the oci8 driver + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sessionMode($value): static + { + $this->_usedProperties['sessionMode'] = true; + $this->sessionMode = $value; + + return $this; + } + + /** + * The name of a running database server to connect to for SQL Anywhere. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function server($value): static + { + $this->_usedProperties['server'] = true; + $this->server = $value; + + return $this; + } + + /** + * Override the default database (postgres) to connect to for PostgreSQL connexion. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultDbname($value): static + { + $this->_usedProperties['defaultDbname'] = true; + $this->defaultDbname = $value; + + return $this; + } + + /** + * Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslmode($value): static + { + $this->_usedProperties['sslmode'] = true; + $this->sslmode = $value; + + return $this; + } + + /** + * The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslrootcert($value): static + { + $this->_usedProperties['sslrootcert'] = true; + $this->sslrootcert = $value; + + return $this; + } + + /** + * The path to the SSL client certificate file for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslcert($value): static + { + $this->_usedProperties['sslcert'] = true; + $this->sslcert = $value; + + return $this; + } + + /** + * The path to the SSL client key file for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslkey($value): static + { + $this->_usedProperties['sslkey'] = true; + $this->sslkey = $value; + + return $this; + } + + /** + * The file name of the SSL certificate revocation list for PostgreSQL. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function sslcrl($value): static + { + $this->_usedProperties['sslcrl'] = true; + $this->sslcrl = $value; + + return $this; + } + + /** + * True to use a pooled server with the oci8/pdo_oracle driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function pooled($value): static + { + $this->_usedProperties['pooled'] = true; + $this->pooled = $value; + + return $this; + } + + /** + * Configuring MultipleActiveResultSets for the pdo_sqlsrv driver + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function multipleActiveResultSets($value): static + { + $this->_usedProperties['multipleActiveResultSets'] = true; + $this->multipleActiveResultSets = $value; + + return $this; + } + + /** + * Use savepoints for nested transactions + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useSavepoints($value): static + { + $this->_usedProperties['useSavepoints'] = true; + $this->useSavepoints = $value; + + return $this; + } + + /** + * Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function instancename($value): static + { + $this->_usedProperties['instancename'] = true; + $this->instancename = $value; + + return $this; + } + + /** + * Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function connectstring($value): static + { + $this->_usedProperties['connectstring'] = true; + $this->connectstring = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('url', $value)) { + $this->_usedProperties['url'] = true; + $this->url = $value['url']; + unset($value['url']); + } + + if (array_key_exists('dbname', $value)) { + $this->_usedProperties['dbname'] = true; + $this->dbname = $value['dbname']; + unset($value['dbname']); + } + + if (array_key_exists('host', $value)) { + $this->_usedProperties['host'] = true; + $this->host = $value['host']; + unset($value['host']); + } + + if (array_key_exists('port', $value)) { + $this->_usedProperties['port'] = true; + $this->port = $value['port']; + unset($value['port']); + } + + if (array_key_exists('user', $value)) { + $this->_usedProperties['user'] = true; + $this->user = $value['user']; + unset($value['user']); + } + + if (array_key_exists('password', $value)) { + $this->_usedProperties['password'] = true; + $this->password = $value['password']; + unset($value['password']); + } + + if (array_key_exists('override_url', $value)) { + $this->_usedProperties['overrideUrl'] = true; + $this->overrideUrl = $value['override_url']; + unset($value['override_url']); + } + + if (array_key_exists('dbname_suffix', $value)) { + $this->_usedProperties['dbnameSuffix'] = true; + $this->dbnameSuffix = $value['dbname_suffix']; + unset($value['dbname_suffix']); + } + + if (array_key_exists('application_name', $value)) { + $this->_usedProperties['applicationName'] = true; + $this->applicationName = $value['application_name']; + unset($value['application_name']); + } + + if (array_key_exists('charset', $value)) { + $this->_usedProperties['charset'] = true; + $this->charset = $value['charset']; + unset($value['charset']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('memory', $value)) { + $this->_usedProperties['memory'] = true; + $this->memory = $value['memory']; + unset($value['memory']); + } + + if (array_key_exists('unix_socket', $value)) { + $this->_usedProperties['unixSocket'] = true; + $this->unixSocket = $value['unix_socket']; + unset($value['unix_socket']); + } + + if (array_key_exists('persistent', $value)) { + $this->_usedProperties['persistent'] = true; + $this->persistent = $value['persistent']; + unset($value['persistent']); + } + + if (array_key_exists('protocol', $value)) { + $this->_usedProperties['protocol'] = true; + $this->protocol = $value['protocol']; + unset($value['protocol']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('servicename', $value)) { + $this->_usedProperties['servicename'] = true; + $this->servicename = $value['servicename']; + unset($value['servicename']); + } + + if (array_key_exists('sessionMode', $value)) { + $this->_usedProperties['sessionMode'] = true; + $this->sessionMode = $value['sessionMode']; + unset($value['sessionMode']); + } + + if (array_key_exists('server', $value)) { + $this->_usedProperties['server'] = true; + $this->server = $value['server']; + unset($value['server']); + } + + if (array_key_exists('default_dbname', $value)) { + $this->_usedProperties['defaultDbname'] = true; + $this->defaultDbname = $value['default_dbname']; + unset($value['default_dbname']); + } + + if (array_key_exists('sslmode', $value)) { + $this->_usedProperties['sslmode'] = true; + $this->sslmode = $value['sslmode']; + unset($value['sslmode']); + } + + if (array_key_exists('sslrootcert', $value)) { + $this->_usedProperties['sslrootcert'] = true; + $this->sslrootcert = $value['sslrootcert']; + unset($value['sslrootcert']); + } + + if (array_key_exists('sslcert', $value)) { + $this->_usedProperties['sslcert'] = true; + $this->sslcert = $value['sslcert']; + unset($value['sslcert']); + } + + if (array_key_exists('sslkey', $value)) { + $this->_usedProperties['sslkey'] = true; + $this->sslkey = $value['sslkey']; + unset($value['sslkey']); + } + + if (array_key_exists('sslcrl', $value)) { + $this->_usedProperties['sslcrl'] = true; + $this->sslcrl = $value['sslcrl']; + unset($value['sslcrl']); + } + + if (array_key_exists('pooled', $value)) { + $this->_usedProperties['pooled'] = true; + $this->pooled = $value['pooled']; + unset($value['pooled']); + } + + if (array_key_exists('MultipleActiveResultSets', $value)) { + $this->_usedProperties['multipleActiveResultSets'] = true; + $this->multipleActiveResultSets = $value['MultipleActiveResultSets']; + unset($value['MultipleActiveResultSets']); + } + + if (array_key_exists('use_savepoints', $value)) { + $this->_usedProperties['useSavepoints'] = true; + $this->useSavepoints = $value['use_savepoints']; + unset($value['use_savepoints']); + } + + if (array_key_exists('instancename', $value)) { + $this->_usedProperties['instancename'] = true; + $this->instancename = $value['instancename']; + unset($value['instancename']); + } + + if (array_key_exists('connectstring', $value)) { + $this->_usedProperties['connectstring'] = true; + $this->connectstring = $value['connectstring']; + unset($value['connectstring']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['url'])) { + $output['url'] = $this->url; + } + if (isset($this->_usedProperties['dbname'])) { + $output['dbname'] = $this->dbname; + } + if (isset($this->_usedProperties['host'])) { + $output['host'] = $this->host; + } + if (isset($this->_usedProperties['port'])) { + $output['port'] = $this->port; + } + if (isset($this->_usedProperties['user'])) { + $output['user'] = $this->user; + } + if (isset($this->_usedProperties['password'])) { + $output['password'] = $this->password; + } + if (isset($this->_usedProperties['overrideUrl'])) { + $output['override_url'] = $this->overrideUrl; + } + if (isset($this->_usedProperties['dbnameSuffix'])) { + $output['dbname_suffix'] = $this->dbnameSuffix; + } + if (isset($this->_usedProperties['applicationName'])) { + $output['application_name'] = $this->applicationName; + } + if (isset($this->_usedProperties['charset'])) { + $output['charset'] = $this->charset; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['memory'])) { + $output['memory'] = $this->memory; + } + if (isset($this->_usedProperties['unixSocket'])) { + $output['unix_socket'] = $this->unixSocket; + } + if (isset($this->_usedProperties['persistent'])) { + $output['persistent'] = $this->persistent; + } + if (isset($this->_usedProperties['protocol'])) { + $output['protocol'] = $this->protocol; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['servicename'])) { + $output['servicename'] = $this->servicename; + } + if (isset($this->_usedProperties['sessionMode'])) { + $output['sessionMode'] = $this->sessionMode; + } + if (isset($this->_usedProperties['server'])) { + $output['server'] = $this->server; + } + if (isset($this->_usedProperties['defaultDbname'])) { + $output['default_dbname'] = $this->defaultDbname; + } + if (isset($this->_usedProperties['sslmode'])) { + $output['sslmode'] = $this->sslmode; + } + if (isset($this->_usedProperties['sslrootcert'])) { + $output['sslrootcert'] = $this->sslrootcert; + } + if (isset($this->_usedProperties['sslcert'])) { + $output['sslcert'] = $this->sslcert; + } + if (isset($this->_usedProperties['sslkey'])) { + $output['sslkey'] = $this->sslkey; + } + if (isset($this->_usedProperties['sslcrl'])) { + $output['sslcrl'] = $this->sslcrl; + } + if (isset($this->_usedProperties['pooled'])) { + $output['pooled'] = $this->pooled; + } + if (isset($this->_usedProperties['multipleActiveResultSets'])) { + $output['MultipleActiveResultSets'] = $this->multipleActiveResultSets; + } + if (isset($this->_usedProperties['useSavepoints'])) { + $output['use_savepoints'] = $this->useSavepoints; + } + if (isset($this->_usedProperties['instancename'])) { + $output['instancename'] = $this->instancename; + } + if (isset($this->_usedProperties['connectstring'])) { + $output['connectstring'] = $this->connectstring; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Dbal/TypeConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Dbal/TypeConfig.php new file mode 100644 index 0000000..0077d85 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Dbal/TypeConfig.php @@ -0,0 +1,76 @@ +_usedProperties['class'] = true; + $this->class = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @deprecated The doctrine-bundle type commenting features were removed; the corresponding config parameter was deprecated in 2.0 and will be dropped in 3.0. + * @return $this + */ + public function commented($value): static + { + $this->_usedProperties['commented'] = true; + $this->commented = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('class', $value)) { + $this->_usedProperties['class'] = true; + $this->class = $value['class']; + unset($value['class']); + } + + if (array_key_exists('commented', $value)) { + $this->_usedProperties['commented'] = true; + $this->commented = $value['commented']; + unset($value['commented']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['class'])) { + $output['class'] = $this->class; + } + if (isset($this->_usedProperties['commented'])) { + $output['commented'] = $this->commented; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/DbalConfig.php b/var/cache/dev/Symfony/Config/Doctrine/DbalConfig.php new file mode 100644 index 0000000..c24b4d8 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/DbalConfig.php @@ -0,0 +1,146 @@ +_usedProperties['defaultConnection'] = true; + $this->defaultConnection = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Doctrine\Dbal\TypeConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Dbal\TypeConfig : static) + */ + public function type(string $name, string|array $value = []): \Symfony\Config\Doctrine\Dbal\TypeConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['types'] = true; + $this->types[$name] = $value; + + return $this; + } + + if (!isset($this->types[$name]) || !$this->types[$name] instanceof \Symfony\Config\Doctrine\Dbal\TypeConfig) { + $this->_usedProperties['types'] = true; + $this->types[$name] = new \Symfony\Config\Doctrine\Dbal\TypeConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "type()" has already been initialized. You cannot pass values the second time you call type().'); + } + + return $this->types[$name]; + } + + /** + * @return $this + */ + public function driverScheme(string $scheme, mixed $value): static + { + $this->_usedProperties['driverSchemes'] = true; + $this->driverSchemes[$scheme] = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Doctrine\Dbal\ConnectionConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Dbal\ConnectionConfig : static) + */ + public function connection(string $name, mixed $value = []): \Symfony\Config\Doctrine\Dbal\ConnectionConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['connections'] = true; + $this->connections[$name] = $value; + + return $this; + } + + if (!isset($this->connections[$name]) || !$this->connections[$name] instanceof \Symfony\Config\Doctrine\Dbal\ConnectionConfig) { + $this->_usedProperties['connections'] = true; + $this->connections[$name] = new \Symfony\Config\Doctrine\Dbal\ConnectionConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "connection()" has already been initialized. You cannot pass values the second time you call connection().'); + } + + return $this->connections[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('default_connection', $value)) { + $this->_usedProperties['defaultConnection'] = true; + $this->defaultConnection = $value['default_connection']; + unset($value['default_connection']); + } + + if (array_key_exists('types', $value)) { + $this->_usedProperties['types'] = true; + $this->types = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Doctrine\Dbal\TypeConfig($v) : $v, $value['types']); + unset($value['types']); + } + + if (array_key_exists('driver_schemes', $value)) { + $this->_usedProperties['driverSchemes'] = true; + $this->driverSchemes = $value['driver_schemes']; + unset($value['driver_schemes']); + } + + if (array_key_exists('connections', $value)) { + $this->_usedProperties['connections'] = true; + $this->connections = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Doctrine\Dbal\ConnectionConfig($v) : $v, $value['connections']); + unset($value['connections']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['defaultConnection'])) { + $output['default_connection'] = $this->defaultConnection; + } + if (isset($this->_usedProperties['types'])) { + $output['types'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Doctrine\Dbal\TypeConfig ? $v->toArray() : $v, $this->types); + } + if (isset($this->_usedProperties['driverSchemes'])) { + $output['driver_schemes'] = $this->driverSchemes; + } + if (isset($this->_usedProperties['connections'])) { + $output['connections'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Doctrine\Dbal\ConnectionConfig ? $v->toArray() : $v, $this->connections); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/ControllerResolverConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/ControllerResolverConfig.php new file mode 100644 index 0000000..6e0e1f5 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/ControllerResolverConfig.php @@ -0,0 +1,100 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * Set to false to disable using route placeholders as lookup criteria when the primary key doesn't match the argument name + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function autoMapping($value): static + { + $this->_usedProperties['autoMapping'] = true; + $this->autoMapping = $value; + + return $this; + } + + /** + * Set to true to fetch the entity from the database instead of using the cache, if any + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function evictCache($value): static + { + $this->_usedProperties['evictCache'] = true; + $this->evictCache = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('auto_mapping', $value)) { + $this->_usedProperties['autoMapping'] = true; + $this->autoMapping = $value['auto_mapping']; + unset($value['auto_mapping']); + } + + if (array_key_exists('evict_cache', $value)) { + $this->_usedProperties['evictCache'] = true; + $this->evictCache = $value['evict_cache']; + unset($value['evict_cache']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['autoMapping'])) { + $output['auto_mapping'] = $this->autoMapping; + } + if (isset($this->_usedProperties['evictCache'])) { + $output['evict_cache'] = $this->evictCache; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig.php new file mode 100644 index 0000000..e9b3b1e --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig.php @@ -0,0 +1,615 @@ +_usedProperties['queryCacheDriver'] = true; + $this->queryCacheDriver = $value; + + return $this; + } + + if (!$this->queryCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\QueryCacheDriverConfig) { + $this->_usedProperties['queryCacheDriver'] = true; + $this->queryCacheDriver = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\QueryCacheDriverConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "queryCacheDriver()" has already been initialized. You cannot pass values the second time you call queryCacheDriver().'); + } + + return $this->queryCacheDriver; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MetadataCacheDriverConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MetadataCacheDriverConfig : static) + */ + public function metadataCacheDriver(string|array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MetadataCacheDriverConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['metadataCacheDriver'] = true; + $this->metadataCacheDriver = $value; + + return $this; + } + + if (!$this->metadataCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MetadataCacheDriverConfig) { + $this->_usedProperties['metadataCacheDriver'] = true; + $this->metadataCacheDriver = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MetadataCacheDriverConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "metadataCacheDriver()" has already been initialized. You cannot pass values the second time you call metadataCacheDriver().'); + } + + return $this->metadataCacheDriver; + } + + /** + * @template TValue + * @param TValue $value + * @default {"type":null} + * @return \Symfony\Config\Doctrine\Orm\EntityManagerConfig\ResultCacheDriverConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Orm\EntityManagerConfig\ResultCacheDriverConfig : static) + */ + public function resultCacheDriver(string|array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\ResultCacheDriverConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['resultCacheDriver'] = true; + $this->resultCacheDriver = $value; + + return $this; + } + + if (!$this->resultCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\ResultCacheDriverConfig) { + $this->_usedProperties['resultCacheDriver'] = true; + $this->resultCacheDriver = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\ResultCacheDriverConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "resultCacheDriver()" has already been initialized. You cannot pass values the second time you call resultCacheDriver().'); + } + + return $this->resultCacheDriver; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListenersConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListenersConfig : static) + */ + public function entityListeners(mixed $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListenersConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['entityListeners'] = true; + $this->entityListeners = $value; + + return $this; + } + + if (!$this->entityListeners instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListenersConfig) { + $this->_usedProperties['entityListeners'] = true; + $this->entityListeners = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListenersConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "entityListeners()" has already been initialized. You cannot pass values the second time you call entityListeners().'); + } + + return $this->entityListeners; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function connection($value): static + { + $this->_usedProperties['connection'] = true; + $this->connection = $value; + + return $this; + } + + /** + * @default 'Doctrine\\ORM\\Mapping\\ClassMetadataFactory' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function classMetadataFactoryName($value): static + { + $this->_usedProperties['classMetadataFactoryName'] = true; + $this->classMetadataFactoryName = $value; + + return $this; + } + + /** + * @default 'Doctrine\\ORM\\EntityRepository' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultRepositoryClass($value): static + { + $this->_usedProperties['defaultRepositoryClass'] = true; + $this->defaultRepositoryClass = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function autoMapping($value): static + { + $this->_usedProperties['autoMapping'] = true; + $this->autoMapping = $value; + + return $this; + } + + /** + * @default 'doctrine.orm.naming_strategy.default' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function namingStrategy($value): static + { + $this->_usedProperties['namingStrategy'] = true; + $this->namingStrategy = $value; + + return $this; + } + + /** + * @default 'doctrine.orm.quote_strategy.default' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function quoteStrategy($value): static + { + $this->_usedProperties['quoteStrategy'] = true; + $this->quoteStrategy = $value; + + return $this; + } + + /** + * @default 'doctrine.orm.typed_field_mapper.default' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function typedFieldMapper($value): static + { + $this->_usedProperties['typedFieldMapper'] = true; + $this->typedFieldMapper = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function entityListenerResolver($value): static + { + $this->_usedProperties['entityListenerResolver'] = true; + $this->entityListenerResolver = $value; + + return $this; + } + + /** + * @default 'doctrine.orm.container_repository_factory' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function repositoryFactory($value): static + { + $this->_usedProperties['repositoryFactory'] = true; + $this->repositoryFactory = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function schemaIgnoreClasses(ParamConfigurator|array $value): static + { + $this->_usedProperties['schemaIgnoreClasses'] = true; + $this->schemaIgnoreClasses = $value; + + return $this; + } + + /** + * Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455. + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function reportFieldsWhereDeclared($value): static + { + $this->_usedProperties['reportFieldsWhereDeclared'] = true; + $this->reportFieldsWhereDeclared = $value; + + return $this; + } + + /** + * Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function validateXmlMapping($value): static + { + $this->_usedProperties['validateXmlMapping'] = true; + $this->validateXmlMapping = $value; + + return $this; + } + + public function secondLevelCache(array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCacheConfig + { + if (null === $this->secondLevelCache) { + $this->_usedProperties['secondLevelCache'] = true; + $this->secondLevelCache = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCacheConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "secondLevelCache()" has already been initialized. You cannot pass values the second time you call secondLevelCache().'); + } + + return $this->secondLevelCache; + } + + /** + * @return $this + */ + public function hydrator(string $name, mixed $value): static + { + $this->_usedProperties['hydrators'] = true; + $this->hydrators[$name] = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MappingConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MappingConfig : static) + */ + public function mapping(string $name, string|array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MappingConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['mappings'] = true; + $this->mappings[$name] = $value; + + return $this; + } + + if (!isset($this->mappings[$name]) || !$this->mappings[$name] instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MappingConfig) { + $this->_usedProperties['mappings'] = true; + $this->mappings[$name] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MappingConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "mapping()" has already been initialized. You cannot pass values the second time you call mapping().'); + } + + return $this->mappings[$name]; + } + + public function dql(array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\DqlConfig + { + if (null === $this->dql) { + $this->_usedProperties['dql'] = true; + $this->dql = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\DqlConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "dql()" has already been initialized. You cannot pass values the second time you call dql().'); + } + + return $this->dql; + } + + /** + * @template TValue + * @param TValue $value + * Register SQL Filters in the entity manager + * @return \Symfony\Config\Doctrine\Orm\EntityManagerConfig\FilterConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\Orm\EntityManagerConfig\FilterConfig : static) + */ + public function filter(string $name, mixed $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\FilterConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['filters'] = true; + $this->filters[$name] = $value; + + return $this; + } + + if (!isset($this->filters[$name]) || !$this->filters[$name] instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\FilterConfig) { + $this->_usedProperties['filters'] = true; + $this->filters[$name] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\FilterConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "filter()" has already been initialized. You cannot pass values the second time you call filter().'); + } + + return $this->filters[$name]; + } + + /** + * @return $this + */ + public function identityGenerationPreference(string $platform, mixed $value): static + { + $this->_usedProperties['identityGenerationPreferences'] = true; + $this->identityGenerationPreferences[$platform] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('query_cache_driver', $value)) { + $this->_usedProperties['queryCacheDriver'] = true; + $this->queryCacheDriver = \is_array($value['query_cache_driver']) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\QueryCacheDriverConfig($value['query_cache_driver']) : $value['query_cache_driver']; + unset($value['query_cache_driver']); + } + + if (array_key_exists('metadata_cache_driver', $value)) { + $this->_usedProperties['metadataCacheDriver'] = true; + $this->metadataCacheDriver = \is_array($value['metadata_cache_driver']) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MetadataCacheDriverConfig($value['metadata_cache_driver']) : $value['metadata_cache_driver']; + unset($value['metadata_cache_driver']); + } + + if (array_key_exists('result_cache_driver', $value)) { + $this->_usedProperties['resultCacheDriver'] = true; + $this->resultCacheDriver = \is_array($value['result_cache_driver']) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\ResultCacheDriverConfig($value['result_cache_driver']) : $value['result_cache_driver']; + unset($value['result_cache_driver']); + } + + if (array_key_exists('entity_listeners', $value)) { + $this->_usedProperties['entityListeners'] = true; + $this->entityListeners = \is_array($value['entity_listeners']) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListenersConfig($value['entity_listeners']) : $value['entity_listeners']; + unset($value['entity_listeners']); + } + + if (array_key_exists('connection', $value)) { + $this->_usedProperties['connection'] = true; + $this->connection = $value['connection']; + unset($value['connection']); + } + + if (array_key_exists('class_metadata_factory_name', $value)) { + $this->_usedProperties['classMetadataFactoryName'] = true; + $this->classMetadataFactoryName = $value['class_metadata_factory_name']; + unset($value['class_metadata_factory_name']); + } + + if (array_key_exists('default_repository_class', $value)) { + $this->_usedProperties['defaultRepositoryClass'] = true; + $this->defaultRepositoryClass = $value['default_repository_class']; + unset($value['default_repository_class']); + } + + if (array_key_exists('auto_mapping', $value)) { + $this->_usedProperties['autoMapping'] = true; + $this->autoMapping = $value['auto_mapping']; + unset($value['auto_mapping']); + } + + if (array_key_exists('naming_strategy', $value)) { + $this->_usedProperties['namingStrategy'] = true; + $this->namingStrategy = $value['naming_strategy']; + unset($value['naming_strategy']); + } + + if (array_key_exists('quote_strategy', $value)) { + $this->_usedProperties['quoteStrategy'] = true; + $this->quoteStrategy = $value['quote_strategy']; + unset($value['quote_strategy']); + } + + if (array_key_exists('typed_field_mapper', $value)) { + $this->_usedProperties['typedFieldMapper'] = true; + $this->typedFieldMapper = $value['typed_field_mapper']; + unset($value['typed_field_mapper']); + } + + if (array_key_exists('entity_listener_resolver', $value)) { + $this->_usedProperties['entityListenerResolver'] = true; + $this->entityListenerResolver = $value['entity_listener_resolver']; + unset($value['entity_listener_resolver']); + } + + if (array_key_exists('repository_factory', $value)) { + $this->_usedProperties['repositoryFactory'] = true; + $this->repositoryFactory = $value['repository_factory']; + unset($value['repository_factory']); + } + + if (array_key_exists('schema_ignore_classes', $value)) { + $this->_usedProperties['schemaIgnoreClasses'] = true; + $this->schemaIgnoreClasses = $value['schema_ignore_classes']; + unset($value['schema_ignore_classes']); + } + + if (array_key_exists('report_fields_where_declared', $value)) { + $this->_usedProperties['reportFieldsWhereDeclared'] = true; + $this->reportFieldsWhereDeclared = $value['report_fields_where_declared']; + unset($value['report_fields_where_declared']); + } + + if (array_key_exists('validate_xml_mapping', $value)) { + $this->_usedProperties['validateXmlMapping'] = true; + $this->validateXmlMapping = $value['validate_xml_mapping']; + unset($value['validate_xml_mapping']); + } + + if (array_key_exists('second_level_cache', $value)) { + $this->_usedProperties['secondLevelCache'] = true; + $this->secondLevelCache = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCacheConfig($value['second_level_cache']); + unset($value['second_level_cache']); + } + + if (array_key_exists('hydrators', $value)) { + $this->_usedProperties['hydrators'] = true; + $this->hydrators = $value['hydrators']; + unset($value['hydrators']); + } + + if (array_key_exists('mappings', $value)) { + $this->_usedProperties['mappings'] = true; + $this->mappings = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MappingConfig($v) : $v, $value['mappings']); + unset($value['mappings']); + } + + if (array_key_exists('dql', $value)) { + $this->_usedProperties['dql'] = true; + $this->dql = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\DqlConfig($value['dql']); + unset($value['dql']); + } + + if (array_key_exists('filters', $value)) { + $this->_usedProperties['filters'] = true; + $this->filters = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\FilterConfig($v) : $v, $value['filters']); + unset($value['filters']); + } + + if (array_key_exists('identity_generation_preferences', $value)) { + $this->_usedProperties['identityGenerationPreferences'] = true; + $this->identityGenerationPreferences = $value['identity_generation_preferences']; + unset($value['identity_generation_preferences']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['queryCacheDriver'])) { + $output['query_cache_driver'] = $this->queryCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\QueryCacheDriverConfig ? $this->queryCacheDriver->toArray() : $this->queryCacheDriver; + } + if (isset($this->_usedProperties['metadataCacheDriver'])) { + $output['metadata_cache_driver'] = $this->metadataCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MetadataCacheDriverConfig ? $this->metadataCacheDriver->toArray() : $this->metadataCacheDriver; + } + if (isset($this->_usedProperties['resultCacheDriver'])) { + $output['result_cache_driver'] = $this->resultCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\ResultCacheDriverConfig ? $this->resultCacheDriver->toArray() : $this->resultCacheDriver; + } + if (isset($this->_usedProperties['entityListeners'])) { + $output['entity_listeners'] = $this->entityListeners instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListenersConfig ? $this->entityListeners->toArray() : $this->entityListeners; + } + if (isset($this->_usedProperties['connection'])) { + $output['connection'] = $this->connection; + } + if (isset($this->_usedProperties['classMetadataFactoryName'])) { + $output['class_metadata_factory_name'] = $this->classMetadataFactoryName; + } + if (isset($this->_usedProperties['defaultRepositoryClass'])) { + $output['default_repository_class'] = $this->defaultRepositoryClass; + } + if (isset($this->_usedProperties['autoMapping'])) { + $output['auto_mapping'] = $this->autoMapping; + } + if (isset($this->_usedProperties['namingStrategy'])) { + $output['naming_strategy'] = $this->namingStrategy; + } + if (isset($this->_usedProperties['quoteStrategy'])) { + $output['quote_strategy'] = $this->quoteStrategy; + } + if (isset($this->_usedProperties['typedFieldMapper'])) { + $output['typed_field_mapper'] = $this->typedFieldMapper; + } + if (isset($this->_usedProperties['entityListenerResolver'])) { + $output['entity_listener_resolver'] = $this->entityListenerResolver; + } + if (isset($this->_usedProperties['repositoryFactory'])) { + $output['repository_factory'] = $this->repositoryFactory; + } + if (isset($this->_usedProperties['schemaIgnoreClasses'])) { + $output['schema_ignore_classes'] = $this->schemaIgnoreClasses; + } + if (isset($this->_usedProperties['reportFieldsWhereDeclared'])) { + $output['report_fields_where_declared'] = $this->reportFieldsWhereDeclared; + } + if (isset($this->_usedProperties['validateXmlMapping'])) { + $output['validate_xml_mapping'] = $this->validateXmlMapping; + } + if (isset($this->_usedProperties['secondLevelCache'])) { + $output['second_level_cache'] = $this->secondLevelCache->toArray(); + } + if (isset($this->_usedProperties['hydrators'])) { + $output['hydrators'] = $this->hydrators; + } + if (isset($this->_usedProperties['mappings'])) { + $output['mappings'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\MappingConfig ? $v->toArray() : $v, $this->mappings); + } + if (isset($this->_usedProperties['dql'])) { + $output['dql'] = $this->dql->toArray(); + } + if (isset($this->_usedProperties['filters'])) { + $output['filters'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\FilterConfig ? $v->toArray() : $v, $this->filters); + } + if (isset($this->_usedProperties['identityGenerationPreferences'])) { + $output['identity_generation_preferences'] = $this->identityGenerationPreferences; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/DqlConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/DqlConfig.php new file mode 100644 index 0000000..76088e5 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/DqlConfig.php @@ -0,0 +1,92 @@ +_usedProperties['stringFunctions'] = true; + $this->stringFunctions[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function numericFunction(string $name, mixed $value): static + { + $this->_usedProperties['numericFunctions'] = true; + $this->numericFunctions[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function datetimeFunction(string $name, mixed $value): static + { + $this->_usedProperties['datetimeFunctions'] = true; + $this->datetimeFunctions[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('string_functions', $value)) { + $this->_usedProperties['stringFunctions'] = true; + $this->stringFunctions = $value['string_functions']; + unset($value['string_functions']); + } + + if (array_key_exists('numeric_functions', $value)) { + $this->_usedProperties['numericFunctions'] = true; + $this->numericFunctions = $value['numeric_functions']; + unset($value['numeric_functions']); + } + + if (array_key_exists('datetime_functions', $value)) { + $this->_usedProperties['datetimeFunctions'] = true; + $this->datetimeFunctions = $value['datetime_functions']; + unset($value['datetime_functions']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['stringFunctions'])) { + $output['string_functions'] = $this->stringFunctions; + } + if (isset($this->_usedProperties['numericFunctions'])) { + $output['numeric_functions'] = $this->numericFunctions; + } + if (isset($this->_usedProperties['datetimeFunctions'])) { + $output['datetime_functions'] = $this->datetimeFunctions; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig.php new file mode 100644 index 0000000..dbc492a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig.php @@ -0,0 +1,52 @@ +listeners[$class])) { + $this->_usedProperties['listeners'] = true; + $this->listeners[$class] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListeners\EntityConfig\ListenerConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "listener()" has already been initialized. You cannot pass values the second time you call listener().'); + } + + return $this->listeners[$class]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('listeners', $value)) { + $this->_usedProperties['listeners'] = true; + $this->listeners = array_map(fn ($v) => new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListeners\EntityConfig\ListenerConfig($v), $value['listeners']); + unset($value['listeners']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['listeners'])) { + $output['listeners'] = array_map(fn ($v) => $v->toArray(), $this->listeners); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig/ListenerConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig/ListenerConfig.php new file mode 100644 index 0000000..dc62d97 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig/ListenerConfig.php @@ -0,0 +1,47 @@ +_usedProperties['events'] = true; + + return $this->events[] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListeners\EntityConfig\ListenerConfig\EventConfig($value); + } + + public function __construct(array $value = []) + { + if (array_key_exists('events', $value)) { + $this->_usedProperties['events'] = true; + $this->events = array_map(fn ($v) => new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListeners\EntityConfig\ListenerConfig\EventConfig($v), $value['events']); + unset($value['events']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['events'])) { + $output['events'] = array_map(fn ($v) => $v->toArray(), $this->events); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig/ListenerConfig/EventConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig/ListenerConfig/EventConfig.php new file mode 100644 index 0000000..435cf96 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListeners/EntityConfig/ListenerConfig/EventConfig.php @@ -0,0 +1,75 @@ +_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function method($value): static + { + $this->_usedProperties['method'] = true; + $this->method = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('method', $value)) { + $this->_usedProperties['method'] = true; + $this->method = $value['method']; + unset($value['method']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['method'])) { + $output['method'] = $this->method; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListenersConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListenersConfig.php new file mode 100644 index 0000000..a06ab05 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/EntityListenersConfig.php @@ -0,0 +1,52 @@ +entities[$class])) { + $this->_usedProperties['entities'] = true; + $this->entities[$class] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListeners\EntityConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "entity()" has already been initialized. You cannot pass values the second time you call entity().'); + } + + return $this->entities[$class]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('entities', $value)) { + $this->_usedProperties['entities'] = true; + $this->entities = array_map(fn ($v) => new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\EntityListeners\EntityConfig($v), $value['entities']); + unset($value['entities']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['entities'])) { + $output['entities'] = array_map(fn ($v) => $v->toArray(), $this->entities); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/FilterConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/FilterConfig.php new file mode 100644 index 0000000..8dcf105 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/FilterConfig.php @@ -0,0 +1,96 @@ +_usedProperties['class'] = true; + $this->class = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enabled($value): static + { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @return $this + */ + public function parameter(string $name, mixed $value): static + { + $this->_usedProperties['parameters'] = true; + $this->parameters[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('class', $value)) { + $this->_usedProperties['class'] = true; + $this->class = $value['class']; + unset($value['class']); + } + + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('parameters', $value)) { + $this->_usedProperties['parameters'] = true; + $this->parameters = $value['parameters']; + unset($value['parameters']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['class'])) { + $output['class'] = $this->class; + } + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['parameters'])) { + $output['parameters'] = $this->parameters; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/MappingConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/MappingConfig.php new file mode 100644 index 0000000..aa20004 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/MappingConfig.php @@ -0,0 +1,167 @@ +_usedProperties['mapping'] = true; + $this->mapping = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function type($value): static + { + $this->_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dir($value): static + { + $this->_usedProperties['dir'] = true; + $this->dir = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function alias($value): static + { + $this->_usedProperties['alias'] = true; + $this->alias = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function prefix($value): static + { + $this->_usedProperties['prefix'] = true; + $this->prefix = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function isBundle($value): static + { + $this->_usedProperties['isBundle'] = true; + $this->isBundle = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('mapping', $value)) { + $this->_usedProperties['mapping'] = true; + $this->mapping = $value['mapping']; + unset($value['mapping']); + } + + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('dir', $value)) { + $this->_usedProperties['dir'] = true; + $this->dir = $value['dir']; + unset($value['dir']); + } + + if (array_key_exists('alias', $value)) { + $this->_usedProperties['alias'] = true; + $this->alias = $value['alias']; + unset($value['alias']); + } + + if (array_key_exists('prefix', $value)) { + $this->_usedProperties['prefix'] = true; + $this->prefix = $value['prefix']; + unset($value['prefix']); + } + + if (array_key_exists('is_bundle', $value)) { + $this->_usedProperties['isBundle'] = true; + $this->isBundle = $value['is_bundle']; + unset($value['is_bundle']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['mapping'])) { + $output['mapping'] = $this->mapping; + } + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['dir'])) { + $output['dir'] = $this->dir; + } + if (isset($this->_usedProperties['alias'])) { + $output['alias'] = $this->alias; + } + if (isset($this->_usedProperties['prefix'])) { + $output['prefix'] = $this->prefix; + } + if (isset($this->_usedProperties['isBundle'])) { + $output['is_bundle'] = $this->isBundle; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/MetadataCacheDriverConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/MetadataCacheDriverConfig.php new file mode 100644 index 0000000..e7e82b7 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/MetadataCacheDriverConfig.php @@ -0,0 +1,98 @@ +_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function id($value): static + { + $this->_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function pool($value): static + { + $this->_usedProperties['pool'] = true; + $this->pool = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('pool', $value)) { + $this->_usedProperties['pool'] = true; + $this->pool = $value['pool']; + unset($value['pool']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['pool'])) { + $output['pool'] = $this->pool; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/QueryCacheDriverConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/QueryCacheDriverConfig.php new file mode 100644 index 0000000..c9a096a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/QueryCacheDriverConfig.php @@ -0,0 +1,98 @@ +_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function id($value): static + { + $this->_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function pool($value): static + { + $this->_usedProperties['pool'] = true; + $this->pool = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('pool', $value)) { + $this->_usedProperties['pool'] = true; + $this->pool = $value['pool']; + unset($value['pool']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['pool'])) { + $output['pool'] = $this->pool; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/ResultCacheDriverConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/ResultCacheDriverConfig.php new file mode 100644 index 0000000..907276d --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/ResultCacheDriverConfig.php @@ -0,0 +1,98 @@ +_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function id($value): static + { + $this->_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function pool($value): static + { + $this->_usedProperties['pool'] = true; + $this->pool = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('pool', $value)) { + $this->_usedProperties['pool'] = true; + $this->pool = $value['pool']; + unset($value['pool']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['pool'])) { + $output['pool'] = $this->pool; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/LoggerConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/LoggerConfig.php new file mode 100644 index 0000000..1d8dcae --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/LoggerConfig.php @@ -0,0 +1,75 @@ +_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionCacheDriverConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionCacheDriverConfig.php new file mode 100644 index 0000000..b70304b --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionCacheDriverConfig.php @@ -0,0 +1,98 @@ +_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function id($value): static + { + $this->_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function pool($value): static + { + $this->_usedProperties['pool'] = true; + $this->pool = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('pool', $value)) { + $this->_usedProperties['pool'] = true; + $this->pool = $value['pool']; + unset($value['pool']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['pool'])) { + $output['pool'] = $this->pool; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionConfig.php new file mode 100644 index 0000000..3c4f322 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionConfig.php @@ -0,0 +1,205 @@ +_usedProperties['cacheDriver'] = true; + $this->cacheDriver = $value; + + return $this; + } + + if (!$this->cacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionConfig\CacheDriverConfig) { + $this->_usedProperties['cacheDriver'] = true; + $this->cacheDriver = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionConfig\CacheDriverConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "cacheDriver()" has already been initialized. You cannot pass values the second time you call cacheDriver().'); + } + + return $this->cacheDriver; + } + + /** + * @default '%kernel.cache_dir%/doctrine/orm/slc/filelock' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function lockPath($value): static + { + $this->_usedProperties['lockPath'] = true; + $this->lockPath = $value; + + return $this; + } + + /** + * @default 60 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function lockLifetime($value): static + { + $this->_usedProperties['lockLifetime'] = true; + $this->lockLifetime = $value; + + return $this; + } + + /** + * @default 'default' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function type($value): static + { + $this->_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default 0 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function lifetime($value): static + { + $this->_usedProperties['lifetime'] = true; + $this->lifetime = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function name($value): static + { + $this->_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('cache_driver', $value)) { + $this->_usedProperties['cacheDriver'] = true; + $this->cacheDriver = \is_array($value['cache_driver']) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionConfig\CacheDriverConfig($value['cache_driver']) : $value['cache_driver']; + unset($value['cache_driver']); + } + + if (array_key_exists('lock_path', $value)) { + $this->_usedProperties['lockPath'] = true; + $this->lockPath = $value['lock_path']; + unset($value['lock_path']); + } + + if (array_key_exists('lock_lifetime', $value)) { + $this->_usedProperties['lockLifetime'] = true; + $this->lockLifetime = $value['lock_lifetime']; + unset($value['lock_lifetime']); + } + + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('lifetime', $value)) { + $this->_usedProperties['lifetime'] = true; + $this->lifetime = $value['lifetime']; + unset($value['lifetime']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['cacheDriver'])) { + $output['cache_driver'] = $this->cacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionConfig\CacheDriverConfig ? $this->cacheDriver->toArray() : $this->cacheDriver; + } + if (isset($this->_usedProperties['lockPath'])) { + $output['lock_path'] = $this->lockPath; + } + if (isset($this->_usedProperties['lockLifetime'])) { + $output['lock_lifetime'] = $this->lockLifetime; + } + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['lifetime'])) { + $output['lifetime'] = $this->lifetime; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionConfig/CacheDriverConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionConfig/CacheDriverConfig.php new file mode 100644 index 0000000..922cc69 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCache/RegionConfig/CacheDriverConfig.php @@ -0,0 +1,98 @@ +_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function id($value): static + { + $this->_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function pool($value): static + { + $this->_usedProperties['pool'] = true; + $this->pool = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('pool', $value)) { + $this->_usedProperties['pool'] = true; + $this->pool = $value['pool']; + unset($value['pool']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['pool'])) { + $output['pool'] = $this->pool; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCacheConfig.php b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCacheConfig.php new file mode 100644 index 0000000..4c4e9ec --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/Orm/EntityManagerConfig/SecondLevelCacheConfig.php @@ -0,0 +1,228 @@ +_usedProperties['regionCacheDriver'] = true; + $this->regionCacheDriver = $value; + + return $this; + } + + if (!$this->regionCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionCacheDriverConfig) { + $this->_usedProperties['regionCacheDriver'] = true; + $this->regionCacheDriver = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionCacheDriverConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "regionCacheDriver()" has already been initialized. You cannot pass values the second time you call regionCacheDriver().'); + } + + return $this->regionCacheDriver; + } + + /** + * @default 60 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function regionLockLifetime($value): static + { + $this->_usedProperties['regionLockLifetime'] = true; + $this->regionLockLifetime = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function logEnabled($value): static + { + $this->_usedProperties['logEnabled'] = true; + $this->logEnabled = $value; + + return $this; + } + + /** + * @default 3600 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function regionLifetime($value): static + { + $this->_usedProperties['regionLifetime'] = true; + $this->regionLifetime = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enabled($value): static + { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function factory($value): static + { + $this->_usedProperties['factory'] = true; + $this->factory = $value; + + return $this; + } + + public function region(string $name, array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionConfig + { + if (!isset($this->regions[$name])) { + $this->_usedProperties['regions'] = true; + $this->regions[$name] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "region()" has already been initialized. You cannot pass values the second time you call region().'); + } + + return $this->regions[$name]; + } + + public function logger(string $name, array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\LoggerConfig + { + if (!isset($this->loggers[$name])) { + $this->_usedProperties['loggers'] = true; + $this->loggers[$name] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\LoggerConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "logger()" has already been initialized. You cannot pass values the second time you call logger().'); + } + + return $this->loggers[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('region_cache_driver', $value)) { + $this->_usedProperties['regionCacheDriver'] = true; + $this->regionCacheDriver = \is_array($value['region_cache_driver']) ? new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionCacheDriverConfig($value['region_cache_driver']) : $value['region_cache_driver']; + unset($value['region_cache_driver']); + } + + if (array_key_exists('region_lock_lifetime', $value)) { + $this->_usedProperties['regionLockLifetime'] = true; + $this->regionLockLifetime = $value['region_lock_lifetime']; + unset($value['region_lock_lifetime']); + } + + if (array_key_exists('log_enabled', $value)) { + $this->_usedProperties['logEnabled'] = true; + $this->logEnabled = $value['log_enabled']; + unset($value['log_enabled']); + } + + if (array_key_exists('region_lifetime', $value)) { + $this->_usedProperties['regionLifetime'] = true; + $this->regionLifetime = $value['region_lifetime']; + unset($value['region_lifetime']); + } + + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('factory', $value)) { + $this->_usedProperties['factory'] = true; + $this->factory = $value['factory']; + unset($value['factory']); + } + + if (array_key_exists('regions', $value)) { + $this->_usedProperties['regions'] = true; + $this->regions = array_map(fn ($v) => new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionConfig($v), $value['regions']); + unset($value['regions']); + } + + if (array_key_exists('loggers', $value)) { + $this->_usedProperties['loggers'] = true; + $this->loggers = array_map(fn ($v) => new \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\LoggerConfig($v), $value['loggers']); + unset($value['loggers']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['regionCacheDriver'])) { + $output['region_cache_driver'] = $this->regionCacheDriver instanceof \Symfony\Config\Doctrine\Orm\EntityManagerConfig\SecondLevelCache\RegionCacheDriverConfig ? $this->regionCacheDriver->toArray() : $this->regionCacheDriver; + } + if (isset($this->_usedProperties['regionLockLifetime'])) { + $output['region_lock_lifetime'] = $this->regionLockLifetime; + } + if (isset($this->_usedProperties['logEnabled'])) { + $output['log_enabled'] = $this->logEnabled; + } + if (isset($this->_usedProperties['regionLifetime'])) { + $output['region_lifetime'] = $this->regionLifetime; + } + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['factory'])) { + $output['factory'] = $this->factory; + } + if (isset($this->_usedProperties['regions'])) { + $output['regions'] = array_map(fn ($v) => $v->toArray(), $this->regions); + } + if (isset($this->_usedProperties['loggers'])) { + $output['loggers'] = array_map(fn ($v) => $v->toArray(), $this->loggers); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Doctrine/OrmConfig.php b/var/cache/dev/Symfony/Config/Doctrine/OrmConfig.php new file mode 100644 index 0000000..bb5e7fc --- /dev/null +++ b/var/cache/dev/Symfony/Config/Doctrine/OrmConfig.php @@ -0,0 +1,217 @@ +_usedProperties['defaultEntityManager'] = true; + $this->defaultEntityManager = $value; + + return $this; + } + + /** + * Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL", "FILE_NOT_EXISTS_OR_CHANGED" + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function autoGenerateProxyClasses($value): static + { + $this->_usedProperties['autoGenerateProxyClasses'] = true; + $this->autoGenerateProxyClasses = $value; + + return $this; + } + + /** + * Enables the new implementation of proxies based on lazy ghosts instead of using the legacy implementation + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enableLazyGhostObjects($value): static + { + $this->_usedProperties['enableLazyGhostObjects'] = true; + $this->enableLazyGhostObjects = $value; + + return $this; + } + + /** + * @default '%kernel.cache_dir%/doctrine/orm/Proxies' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function proxyDir($value): static + { + $this->_usedProperties['proxyDir'] = true; + $this->proxyDir = $value; + + return $this; + } + + /** + * @default 'Proxies' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function proxyNamespace($value): static + { + $this->_usedProperties['proxyNamespace'] = true; + $this->proxyNamespace = $value; + + return $this; + } + + /** + * @default {"enabled":true,"auto_mapping":null,"evict_cache":false} + */ + public function controllerResolver(array $value = []): \Symfony\Config\Doctrine\Orm\ControllerResolverConfig + { + if (null === $this->controllerResolver) { + $this->_usedProperties['controllerResolver'] = true; + $this->controllerResolver = new \Symfony\Config\Doctrine\Orm\ControllerResolverConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "controllerResolver()" has already been initialized. You cannot pass values the second time you call controllerResolver().'); + } + + return $this->controllerResolver; + } + + public function entityManager(string $name, array $value = []): \Symfony\Config\Doctrine\Orm\EntityManagerConfig + { + if (!isset($this->entityManagers[$name])) { + $this->_usedProperties['entityManagers'] = true; + $this->entityManagers[$name] = new \Symfony\Config\Doctrine\Orm\EntityManagerConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "entityManager()" has already been initialized. You cannot pass values the second time you call entityManager().'); + } + + return $this->entityManagers[$name]; + } + + /** + * @return $this + */ + public function resolveTargetEntity(string $interface, mixed $value): static + { + $this->_usedProperties['resolveTargetEntities'] = true; + $this->resolveTargetEntities[$interface] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('default_entity_manager', $value)) { + $this->_usedProperties['defaultEntityManager'] = true; + $this->defaultEntityManager = $value['default_entity_manager']; + unset($value['default_entity_manager']); + } + + if (array_key_exists('auto_generate_proxy_classes', $value)) { + $this->_usedProperties['autoGenerateProxyClasses'] = true; + $this->autoGenerateProxyClasses = $value['auto_generate_proxy_classes']; + unset($value['auto_generate_proxy_classes']); + } + + if (array_key_exists('enable_lazy_ghost_objects', $value)) { + $this->_usedProperties['enableLazyGhostObjects'] = true; + $this->enableLazyGhostObjects = $value['enable_lazy_ghost_objects']; + unset($value['enable_lazy_ghost_objects']); + } + + if (array_key_exists('proxy_dir', $value)) { + $this->_usedProperties['proxyDir'] = true; + $this->proxyDir = $value['proxy_dir']; + unset($value['proxy_dir']); + } + + if (array_key_exists('proxy_namespace', $value)) { + $this->_usedProperties['proxyNamespace'] = true; + $this->proxyNamespace = $value['proxy_namespace']; + unset($value['proxy_namespace']); + } + + if (array_key_exists('controller_resolver', $value)) { + $this->_usedProperties['controllerResolver'] = true; + $this->controllerResolver = new \Symfony\Config\Doctrine\Orm\ControllerResolverConfig($value['controller_resolver']); + unset($value['controller_resolver']); + } + + if (array_key_exists('entity_managers', $value)) { + $this->_usedProperties['entityManagers'] = true; + $this->entityManagers = array_map(fn ($v) => new \Symfony\Config\Doctrine\Orm\EntityManagerConfig($v), $value['entity_managers']); + unset($value['entity_managers']); + } + + if (array_key_exists('resolve_target_entities', $value)) { + $this->_usedProperties['resolveTargetEntities'] = true; + $this->resolveTargetEntities = $value['resolve_target_entities']; + unset($value['resolve_target_entities']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['defaultEntityManager'])) { + $output['default_entity_manager'] = $this->defaultEntityManager; + } + if (isset($this->_usedProperties['autoGenerateProxyClasses'])) { + $output['auto_generate_proxy_classes'] = $this->autoGenerateProxyClasses; + } + if (isset($this->_usedProperties['enableLazyGhostObjects'])) { + $output['enable_lazy_ghost_objects'] = $this->enableLazyGhostObjects; + } + if (isset($this->_usedProperties['proxyDir'])) { + $output['proxy_dir'] = $this->proxyDir; + } + if (isset($this->_usedProperties['proxyNamespace'])) { + $output['proxy_namespace'] = $this->proxyNamespace; + } + if (isset($this->_usedProperties['controllerResolver'])) { + $output['controller_resolver'] = $this->controllerResolver->toArray(); + } + if (isset($this->_usedProperties['entityManagers'])) { + $output['entity_managers'] = array_map(fn ($v) => $v->toArray(), $this->entityManagers); + } + if (isset($this->_usedProperties['resolveTargetEntities'])) { + $output['resolve_target_entities'] = $this->resolveTargetEntities; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/DoctrineConfig.php b/var/cache/dev/Symfony/Config/DoctrineConfig.php new file mode 100644 index 0000000..b905318 --- /dev/null +++ b/var/cache/dev/Symfony/Config/DoctrineConfig.php @@ -0,0 +1,106 @@ +_usedProperties['dbal'] = true; + $this->dbal = $value; + + return $this; + } + + if (!$this->dbal instanceof \Symfony\Config\Doctrine\DbalConfig) { + $this->_usedProperties['dbal'] = true; + $this->dbal = new \Symfony\Config\Doctrine\DbalConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "dbal()" has already been initialized. You cannot pass values the second time you call dbal().'); + } + + return $this->dbal; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Doctrine\OrmConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Doctrine\OrmConfig : static) + */ + public function orm(mixed $value = []): \Symfony\Config\Doctrine\OrmConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['orm'] = true; + $this->orm = $value; + + return $this; + } + + if (!$this->orm instanceof \Symfony\Config\Doctrine\OrmConfig) { + $this->_usedProperties['orm'] = true; + $this->orm = new \Symfony\Config\Doctrine\OrmConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "orm()" has already been initialized. You cannot pass values the second time you call orm().'); + } + + return $this->orm; + } + + public function getExtensionAlias(): string + { + return 'doctrine'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('dbal', $value)) { + $this->_usedProperties['dbal'] = true; + $this->dbal = \is_array($value['dbal']) ? new \Symfony\Config\Doctrine\DbalConfig($value['dbal']) : $value['dbal']; + unset($value['dbal']); + } + + if (array_key_exists('orm', $value)) { + $this->_usedProperties['orm'] = true; + $this->orm = \is_array($value['orm']) ? new \Symfony\Config\Doctrine\OrmConfig($value['orm']) : $value['orm']; + unset($value['orm']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['dbal'])) { + $output['dbal'] = $this->dbal instanceof \Symfony\Config\Doctrine\DbalConfig ? $this->dbal->toArray() : $this->dbal; + } + if (isset($this->_usedProperties['orm'])) { + $output['orm'] = $this->orm instanceof \Symfony\Config\Doctrine\OrmConfig ? $this->orm->toArray() : $this->orm; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/DoctrineMigrations/Storage/TableStorageConfig.php b/var/cache/dev/Symfony/Config/DoctrineMigrations/Storage/TableStorageConfig.php new file mode 100644 index 0000000..3454166 --- /dev/null +++ b/var/cache/dev/Symfony/Config/DoctrineMigrations/Storage/TableStorageConfig.php @@ -0,0 +1,144 @@ +_usedProperties['tableName'] = true; + $this->tableName = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function versionColumnName($value): static + { + $this->_usedProperties['versionColumnName'] = true; + $this->versionColumnName = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function versionColumnLength($value): static + { + $this->_usedProperties['versionColumnLength'] = true; + $this->versionColumnLength = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function executedAtColumnName($value): static + { + $this->_usedProperties['executedAtColumnName'] = true; + $this->executedAtColumnName = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function executionTimeColumnName($value): static + { + $this->_usedProperties['executionTimeColumnName'] = true; + $this->executionTimeColumnName = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('table_name', $value)) { + $this->_usedProperties['tableName'] = true; + $this->tableName = $value['table_name']; + unset($value['table_name']); + } + + if (array_key_exists('version_column_name', $value)) { + $this->_usedProperties['versionColumnName'] = true; + $this->versionColumnName = $value['version_column_name']; + unset($value['version_column_name']); + } + + if (array_key_exists('version_column_length', $value)) { + $this->_usedProperties['versionColumnLength'] = true; + $this->versionColumnLength = $value['version_column_length']; + unset($value['version_column_length']); + } + + if (array_key_exists('executed_at_column_name', $value)) { + $this->_usedProperties['executedAtColumnName'] = true; + $this->executedAtColumnName = $value['executed_at_column_name']; + unset($value['executed_at_column_name']); + } + + if (array_key_exists('execution_time_column_name', $value)) { + $this->_usedProperties['executionTimeColumnName'] = true; + $this->executionTimeColumnName = $value['execution_time_column_name']; + unset($value['execution_time_column_name']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['tableName'])) { + $output['table_name'] = $this->tableName; + } + if (isset($this->_usedProperties['versionColumnName'])) { + $output['version_column_name'] = $this->versionColumnName; + } + if (isset($this->_usedProperties['versionColumnLength'])) { + $output['version_column_length'] = $this->versionColumnLength; + } + if (isset($this->_usedProperties['executedAtColumnName'])) { + $output['executed_at_column_name'] = $this->executedAtColumnName; + } + if (isset($this->_usedProperties['executionTimeColumnName'])) { + $output['execution_time_column_name'] = $this->executionTimeColumnName; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/DoctrineMigrations/StorageConfig.php b/var/cache/dev/Symfony/Config/DoctrineMigrations/StorageConfig.php new file mode 100644 index 0000000..27b1077 --- /dev/null +++ b/var/cache/dev/Symfony/Config/DoctrineMigrations/StorageConfig.php @@ -0,0 +1,56 @@ +tableStorage) { + $this->_usedProperties['tableStorage'] = true; + $this->tableStorage = new \Symfony\Config\DoctrineMigrations\Storage\TableStorageConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "tableStorage()" has already been initialized. You cannot pass values the second time you call tableStorage().'); + } + + return $this->tableStorage; + } + + public function __construct(array $value = []) + { + if (array_key_exists('table_storage', $value)) { + $this->_usedProperties['tableStorage'] = true; + $this->tableStorage = new \Symfony\Config\DoctrineMigrations\Storage\TableStorageConfig($value['table_storage']); + unset($value['table_storage']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['tableStorage'])) { + $output['table_storage'] = $this->tableStorage->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/DoctrineMigrationsConfig.php b/var/cache/dev/Symfony/Config/DoctrineMigrationsConfig.php new file mode 100644 index 0000000..3635365 --- /dev/null +++ b/var/cache/dev/Symfony/Config/DoctrineMigrationsConfig.php @@ -0,0 +1,340 @@ +_usedProperties['migrationsPaths'] = true; + $this->migrationsPaths[$namespace] = $value; + + return $this; + } + + /** + * @return $this + */ + public function services(string $service, mixed $value): static + { + $this->_usedProperties['services'] = true; + $this->services[$service] = $value; + + return $this; + } + + /** + * @return $this + */ + public function factories(string $factory, mixed $value): static + { + $this->_usedProperties['factories'] = true; + $this->factories[$factory] = $value; + + return $this; + } + + /** + * Storage to use for migration status metadata. + * @default {"table_storage":{"table_name":null,"version_column_name":null,"version_column_length":null,"executed_at_column_name":null,"execution_time_column_name":null}} + */ + public function storage(array $value = []): \Symfony\Config\DoctrineMigrations\StorageConfig + { + if (null === $this->storage) { + $this->_usedProperties['storage'] = true; + $this->storage = new \Symfony\Config\DoctrineMigrations\StorageConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "storage()" has already been initialized. You cannot pass values the second time you call storage().'); + } + + return $this->storage; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function migrations(ParamConfigurator|array $value): static + { + $this->_usedProperties['migrations'] = true; + $this->migrations = $value; + + return $this; + } + + /** + * Connection name to use for the migrations database. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function connection($value): static + { + $this->_usedProperties['connection'] = true; + $this->connection = $value; + + return $this; + } + + /** + * Entity manager name to use for the migrations database (available when doctrine/orm is installed). + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function em($value): static + { + $this->_usedProperties['em'] = true; + $this->em = $value; + + return $this; + } + + /** + * Run all migrations in a transaction. + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function allOrNothing($value): static + { + $this->_usedProperties['allOrNothing'] = true; + $this->allOrNothing = $value; + + return $this; + } + + /** + * Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on. + * @default true + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkDatabasePlatform($value): static + { + $this->_usedProperties['checkDatabasePlatform'] = true; + $this->checkDatabasePlatform = $value; + + return $this; + } + + /** + * Custom template path for generated migration classes. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function customTemplate($value): static + { + $this->_usedProperties['customTemplate'] = true; + $this->customTemplate = $value; + + return $this; + } + + /** + * Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function organizeMigrations($value): static + { + $this->_usedProperties['organizeMigrations'] = true; + $this->organizeMigrations = $value; + + return $this; + } + + /** + * Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enableProfiler($value): static + { + $this->_usedProperties['enableProfiler'] = true; + $this->enableProfiler = $value; + + return $this; + } + + /** + * Whether or not to wrap migrations in a single transaction. + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function transactional($value): static + { + $this->_usedProperties['transactional'] = true; + $this->transactional = $value; + + return $this; + } + + public function getExtensionAlias(): string + { + return 'doctrine_migrations'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('migrations_paths', $value)) { + $this->_usedProperties['migrationsPaths'] = true; + $this->migrationsPaths = $value['migrations_paths']; + unset($value['migrations_paths']); + } + + if (array_key_exists('services', $value)) { + $this->_usedProperties['services'] = true; + $this->services = $value['services']; + unset($value['services']); + } + + if (array_key_exists('factories', $value)) { + $this->_usedProperties['factories'] = true; + $this->factories = $value['factories']; + unset($value['factories']); + } + + if (array_key_exists('storage', $value)) { + $this->_usedProperties['storage'] = true; + $this->storage = new \Symfony\Config\DoctrineMigrations\StorageConfig($value['storage']); + unset($value['storage']); + } + + if (array_key_exists('migrations', $value)) { + $this->_usedProperties['migrations'] = true; + $this->migrations = $value['migrations']; + unset($value['migrations']); + } + + if (array_key_exists('connection', $value)) { + $this->_usedProperties['connection'] = true; + $this->connection = $value['connection']; + unset($value['connection']); + } + + if (array_key_exists('em', $value)) { + $this->_usedProperties['em'] = true; + $this->em = $value['em']; + unset($value['em']); + } + + if (array_key_exists('all_or_nothing', $value)) { + $this->_usedProperties['allOrNothing'] = true; + $this->allOrNothing = $value['all_or_nothing']; + unset($value['all_or_nothing']); + } + + if (array_key_exists('check_database_platform', $value)) { + $this->_usedProperties['checkDatabasePlatform'] = true; + $this->checkDatabasePlatform = $value['check_database_platform']; + unset($value['check_database_platform']); + } + + if (array_key_exists('custom_template', $value)) { + $this->_usedProperties['customTemplate'] = true; + $this->customTemplate = $value['custom_template']; + unset($value['custom_template']); + } + + if (array_key_exists('organize_migrations', $value)) { + $this->_usedProperties['organizeMigrations'] = true; + $this->organizeMigrations = $value['organize_migrations']; + unset($value['organize_migrations']); + } + + if (array_key_exists('enable_profiler', $value)) { + $this->_usedProperties['enableProfiler'] = true; + $this->enableProfiler = $value['enable_profiler']; + unset($value['enable_profiler']); + } + + if (array_key_exists('transactional', $value)) { + $this->_usedProperties['transactional'] = true; + $this->transactional = $value['transactional']; + unset($value['transactional']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['migrationsPaths'])) { + $output['migrations_paths'] = $this->migrationsPaths; + } + if (isset($this->_usedProperties['services'])) { + $output['services'] = $this->services; + } + if (isset($this->_usedProperties['factories'])) { + $output['factories'] = $this->factories; + } + if (isset($this->_usedProperties['storage'])) { + $output['storage'] = $this->storage->toArray(); + } + if (isset($this->_usedProperties['migrations'])) { + $output['migrations'] = $this->migrations; + } + if (isset($this->_usedProperties['connection'])) { + $output['connection'] = $this->connection; + } + if (isset($this->_usedProperties['em'])) { + $output['em'] = $this->em; + } + if (isset($this->_usedProperties['allOrNothing'])) { + $output['all_or_nothing'] = $this->allOrNothing; + } + if (isset($this->_usedProperties['checkDatabasePlatform'])) { + $output['check_database_platform'] = $this->checkDatabasePlatform; + } + if (isset($this->_usedProperties['customTemplate'])) { + $output['custom_template'] = $this->customTemplate; + } + if (isset($this->_usedProperties['organizeMigrations'])) { + $output['organize_migrations'] = $this->organizeMigrations; + } + if (isset($this->_usedProperties['enableProfiler'])) { + $output['enable_profiler'] = $this->enableProfiler; + } + if (isset($this->_usedProperties['transactional'])) { + $output['transactional'] = $this->transactional; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/AnnotationsConfig.php b/var/cache/dev/Symfony/Config/Framework/AnnotationsConfig.php new file mode 100644 index 0000000..34e6051 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/AnnotationsConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/AssetMapperConfig.php b/var/cache/dev/Symfony/Config/Framework/AssetMapperConfig.php new file mode 100644 index 0000000..965b30f --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/AssetMapperConfig.php @@ -0,0 +1,306 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @return $this + */ + public function path(string $namespace, mixed $value): static + { + $this->_usedProperties['paths'] = true; + $this->paths[$namespace] = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function excludedPatterns(ParamConfigurator|array $value): static + { + $this->_usedProperties['excludedPatterns'] = true; + $this->excludedPatterns = $value; + + return $this; + } + + /** + * If true, any files starting with "." will be excluded from the asset mapper + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function excludeDotfiles($value): static + { + $this->_usedProperties['excludeDotfiles'] = true; + $this->excludeDotfiles = $value; + + return $this; + } + + /** + * If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default) + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function server($value): static + { + $this->_usedProperties['server'] = true; + $this->server = $value; + + return $this; + } + + /** + * The public path where the assets will be written to (and served from when "server" is true) + * @default '/assets/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function publicPrefix($value): static + { + $this->_usedProperties['publicPrefix'] = true; + $this->publicPrefix = $value; + + return $this; + } + + /** + * Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. + * @default 'warn' + * @param ParamConfigurator|'strict'|'warn'|'ignore' $value + * @return $this + */ + public function missingImportMode($value): static + { + $this->_usedProperties['missingImportMode'] = true; + $this->missingImportMode = $value; + + return $this; + } + + /** + * @return $this + */ + public function extension(string $extension, mixed $value): static + { + $this->_usedProperties['extensions'] = true; + $this->extensions[$extension] = $value; + + return $this; + } + + /** + * The path of the importmap.php file. + * @default '%kernel.project_dir%/importmap.php' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function importmapPath($value): static + { + $this->_usedProperties['importmapPath'] = true; + $this->importmapPath = $value; + + return $this; + } + + /** + * The importmap name that will be used to load the polyfill. Set to false to disable. + * @default 'es-module-shims' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function importmapPolyfill($value): static + { + $this->_usedProperties['importmapPolyfill'] = true; + $this->importmapPolyfill = $value; + + return $this; + } + + /** + * @return $this + */ + public function importmapScriptAttribute(string $key, mixed $value): static + { + $this->_usedProperties['importmapScriptAttributes'] = true; + $this->importmapScriptAttributes[$key] = $value; + + return $this; + } + + /** + * The directory to store JavaScript vendors. + * @default '%kernel.project_dir%/assets/vendor' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function vendorDir($value): static + { + $this->_usedProperties['vendorDir'] = true; + $this->vendorDir = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('paths', $value)) { + $this->_usedProperties['paths'] = true; + $this->paths = $value['paths']; + unset($value['paths']); + } + + if (array_key_exists('excluded_patterns', $value)) { + $this->_usedProperties['excludedPatterns'] = true; + $this->excludedPatterns = $value['excluded_patterns']; + unset($value['excluded_patterns']); + } + + if (array_key_exists('exclude_dotfiles', $value)) { + $this->_usedProperties['excludeDotfiles'] = true; + $this->excludeDotfiles = $value['exclude_dotfiles']; + unset($value['exclude_dotfiles']); + } + + if (array_key_exists('server', $value)) { + $this->_usedProperties['server'] = true; + $this->server = $value['server']; + unset($value['server']); + } + + if (array_key_exists('public_prefix', $value)) { + $this->_usedProperties['publicPrefix'] = true; + $this->publicPrefix = $value['public_prefix']; + unset($value['public_prefix']); + } + + if (array_key_exists('missing_import_mode', $value)) { + $this->_usedProperties['missingImportMode'] = true; + $this->missingImportMode = $value['missing_import_mode']; + unset($value['missing_import_mode']); + } + + if (array_key_exists('extensions', $value)) { + $this->_usedProperties['extensions'] = true; + $this->extensions = $value['extensions']; + unset($value['extensions']); + } + + if (array_key_exists('importmap_path', $value)) { + $this->_usedProperties['importmapPath'] = true; + $this->importmapPath = $value['importmap_path']; + unset($value['importmap_path']); + } + + if (array_key_exists('importmap_polyfill', $value)) { + $this->_usedProperties['importmapPolyfill'] = true; + $this->importmapPolyfill = $value['importmap_polyfill']; + unset($value['importmap_polyfill']); + } + + if (array_key_exists('importmap_script_attributes', $value)) { + $this->_usedProperties['importmapScriptAttributes'] = true; + $this->importmapScriptAttributes = $value['importmap_script_attributes']; + unset($value['importmap_script_attributes']); + } + + if (array_key_exists('vendor_dir', $value)) { + $this->_usedProperties['vendorDir'] = true; + $this->vendorDir = $value['vendor_dir']; + unset($value['vendor_dir']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['paths'])) { + $output['paths'] = $this->paths; + } + if (isset($this->_usedProperties['excludedPatterns'])) { + $output['excluded_patterns'] = $this->excludedPatterns; + } + if (isset($this->_usedProperties['excludeDotfiles'])) { + $output['exclude_dotfiles'] = $this->excludeDotfiles; + } + if (isset($this->_usedProperties['server'])) { + $output['server'] = $this->server; + } + if (isset($this->_usedProperties['publicPrefix'])) { + $output['public_prefix'] = $this->publicPrefix; + } + if (isset($this->_usedProperties['missingImportMode'])) { + $output['missing_import_mode'] = $this->missingImportMode; + } + if (isset($this->_usedProperties['extensions'])) { + $output['extensions'] = $this->extensions; + } + if (isset($this->_usedProperties['importmapPath'])) { + $output['importmap_path'] = $this->importmapPath; + } + if (isset($this->_usedProperties['importmapPolyfill'])) { + $output['importmap_polyfill'] = $this->importmapPolyfill; + } + if (isset($this->_usedProperties['importmapScriptAttributes'])) { + $output['importmap_script_attributes'] = $this->importmapScriptAttributes; + } + if (isset($this->_usedProperties['vendorDir'])) { + $output['vendor_dir'] = $this->vendorDir; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Assets/PackageConfig.php b/var/cache/dev/Symfony/Config/Framework/Assets/PackageConfig.php new file mode 100644 index 0000000..4da7d04 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Assets/PackageConfig.php @@ -0,0 +1,190 @@ +_usedProperties['strictMode'] = true; + $this->strictMode = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function versionStrategy($value): static + { + $this->_usedProperties['versionStrategy'] = true; + $this->versionStrategy = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function version($value): static + { + $this->_usedProperties['version'] = true; + $this->version = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function versionFormat($value): static + { + $this->_usedProperties['versionFormat'] = true; + $this->versionFormat = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function jsonManifestPath($value): static + { + $this->_usedProperties['jsonManifestPath'] = true; + $this->jsonManifestPath = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function basePath($value): static + { + $this->_usedProperties['basePath'] = true; + $this->basePath = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|mixed $value + * + * @return $this + */ + public function baseUrls(mixed $value): static + { + $this->_usedProperties['baseUrls'] = true; + $this->baseUrls = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('strict_mode', $value)) { + $this->_usedProperties['strictMode'] = true; + $this->strictMode = $value['strict_mode']; + unset($value['strict_mode']); + } + + if (array_key_exists('version_strategy', $value)) { + $this->_usedProperties['versionStrategy'] = true; + $this->versionStrategy = $value['version_strategy']; + unset($value['version_strategy']); + } + + if (array_key_exists('version', $value)) { + $this->_usedProperties['version'] = true; + $this->version = $value['version']; + unset($value['version']); + } + + if (array_key_exists('version_format', $value)) { + $this->_usedProperties['versionFormat'] = true; + $this->versionFormat = $value['version_format']; + unset($value['version_format']); + } + + if (array_key_exists('json_manifest_path', $value)) { + $this->_usedProperties['jsonManifestPath'] = true; + $this->jsonManifestPath = $value['json_manifest_path']; + unset($value['json_manifest_path']); + } + + if (array_key_exists('base_path', $value)) { + $this->_usedProperties['basePath'] = true; + $this->basePath = $value['base_path']; + unset($value['base_path']); + } + + if (array_key_exists('base_urls', $value)) { + $this->_usedProperties['baseUrls'] = true; + $this->baseUrls = $value['base_urls']; + unset($value['base_urls']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['strictMode'])) { + $output['strict_mode'] = $this->strictMode; + } + if (isset($this->_usedProperties['versionStrategy'])) { + $output['version_strategy'] = $this->versionStrategy; + } + if (isset($this->_usedProperties['version'])) { + $output['version'] = $this->version; + } + if (isset($this->_usedProperties['versionFormat'])) { + $output['version_format'] = $this->versionFormat; + } + if (isset($this->_usedProperties['jsonManifestPath'])) { + $output['json_manifest_path'] = $this->jsonManifestPath; + } + if (isset($this->_usedProperties['basePath'])) { + $output['base_path'] = $this->basePath; + } + if (isset($this->_usedProperties['baseUrls'])) { + $output['base_urls'] = $this->baseUrls; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/AssetsConfig.php b/var/cache/dev/Symfony/Config/Framework/AssetsConfig.php new file mode 100644 index 0000000..6b9add4 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/AssetsConfig.php @@ -0,0 +1,237 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * Throw an exception if an entry is missing from the manifest.json + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function strictMode($value): static + { + $this->_usedProperties['strictMode'] = true; + $this->strictMode = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function versionStrategy($value): static + { + $this->_usedProperties['versionStrategy'] = true; + $this->versionStrategy = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function version($value): static + { + $this->_usedProperties['version'] = true; + $this->version = $value; + + return $this; + } + + /** + * @default '%%s?%%s' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function versionFormat($value): static + { + $this->_usedProperties['versionFormat'] = true; + $this->versionFormat = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function jsonManifestPath($value): static + { + $this->_usedProperties['jsonManifestPath'] = true; + $this->jsonManifestPath = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function basePath($value): static + { + $this->_usedProperties['basePath'] = true; + $this->basePath = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|mixed $value + * + * @return $this + */ + public function baseUrls(mixed $value): static + { + $this->_usedProperties['baseUrls'] = true; + $this->baseUrls = $value; + + return $this; + } + + public function package(string $name, array $value = []): \Symfony\Config\Framework\Assets\PackageConfig + { + if (!isset($this->packages[$name])) { + $this->_usedProperties['packages'] = true; + $this->packages[$name] = new \Symfony\Config\Framework\Assets\PackageConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "package()" has already been initialized. You cannot pass values the second time you call package().'); + } + + return $this->packages[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('strict_mode', $value)) { + $this->_usedProperties['strictMode'] = true; + $this->strictMode = $value['strict_mode']; + unset($value['strict_mode']); + } + + if (array_key_exists('version_strategy', $value)) { + $this->_usedProperties['versionStrategy'] = true; + $this->versionStrategy = $value['version_strategy']; + unset($value['version_strategy']); + } + + if (array_key_exists('version', $value)) { + $this->_usedProperties['version'] = true; + $this->version = $value['version']; + unset($value['version']); + } + + if (array_key_exists('version_format', $value)) { + $this->_usedProperties['versionFormat'] = true; + $this->versionFormat = $value['version_format']; + unset($value['version_format']); + } + + if (array_key_exists('json_manifest_path', $value)) { + $this->_usedProperties['jsonManifestPath'] = true; + $this->jsonManifestPath = $value['json_manifest_path']; + unset($value['json_manifest_path']); + } + + if (array_key_exists('base_path', $value)) { + $this->_usedProperties['basePath'] = true; + $this->basePath = $value['base_path']; + unset($value['base_path']); + } + + if (array_key_exists('base_urls', $value)) { + $this->_usedProperties['baseUrls'] = true; + $this->baseUrls = $value['base_urls']; + unset($value['base_urls']); + } + + if (array_key_exists('packages', $value)) { + $this->_usedProperties['packages'] = true; + $this->packages = array_map(fn ($v) => new \Symfony\Config\Framework\Assets\PackageConfig($v), $value['packages']); + unset($value['packages']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['strictMode'])) { + $output['strict_mode'] = $this->strictMode; + } + if (isset($this->_usedProperties['versionStrategy'])) { + $output['version_strategy'] = $this->versionStrategy; + } + if (isset($this->_usedProperties['version'])) { + $output['version'] = $this->version; + } + if (isset($this->_usedProperties['versionFormat'])) { + $output['version_format'] = $this->versionFormat; + } + if (isset($this->_usedProperties['jsonManifestPath'])) { + $output['json_manifest_path'] = $this->jsonManifestPath; + } + if (isset($this->_usedProperties['basePath'])) { + $output['base_path'] = $this->basePath; + } + if (isset($this->_usedProperties['baseUrls'])) { + $output['base_urls'] = $this->baseUrls; + } + if (isset($this->_usedProperties['packages'])) { + $output['packages'] = array_map(fn ($v) => $v->toArray(), $this->packages); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Cache/PoolConfig.php b/var/cache/dev/Symfony/Config/Framework/Cache/PoolConfig.php new file mode 100644 index 0000000..1036a38 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Cache/PoolConfig.php @@ -0,0 +1,194 @@ +|mixed $value + * + * @return $this + */ + public function adapters(mixed $value): static + { + $this->_usedProperties['adapters'] = true; + $this->adapters = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function tags($value): static + { + $this->_usedProperties['tags'] = true; + $this->tags = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function public($value): static + { + $this->_usedProperties['public'] = true; + $this->public = $value; + + return $this; + } + + /** + * Default lifetime of the pool + * @example "300" for 5 minutes expressed in seconds, "PT5M" for five minutes expressed as ISO 8601 time interval, or "5 minutes" as a date expression + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultLifetime($value): static + { + $this->_usedProperties['defaultLifetime'] = true; + $this->defaultLifetime = $value; + + return $this; + } + + /** + * Overwrite the setting from the default provider for this adapter. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function provider($value): static + { + $this->_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @example "messenger.default_bus" to send early expiration events to the default Messenger bus. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function earlyExpirationMessageBus($value): static + { + $this->_usedProperties['earlyExpirationMessageBus'] = true; + $this->earlyExpirationMessageBus = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function clearer($value): static + { + $this->_usedProperties['clearer'] = true; + $this->clearer = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('adapters', $value)) { + $this->_usedProperties['adapters'] = true; + $this->adapters = $value['adapters']; + unset($value['adapters']); + } + + if (array_key_exists('tags', $value)) { + $this->_usedProperties['tags'] = true; + $this->tags = $value['tags']; + unset($value['tags']); + } + + if (array_key_exists('public', $value)) { + $this->_usedProperties['public'] = true; + $this->public = $value['public']; + unset($value['public']); + } + + if (array_key_exists('default_lifetime', $value)) { + $this->_usedProperties['defaultLifetime'] = true; + $this->defaultLifetime = $value['default_lifetime']; + unset($value['default_lifetime']); + } + + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('early_expiration_message_bus', $value)) { + $this->_usedProperties['earlyExpirationMessageBus'] = true; + $this->earlyExpirationMessageBus = $value['early_expiration_message_bus']; + unset($value['early_expiration_message_bus']); + } + + if (array_key_exists('clearer', $value)) { + $this->_usedProperties['clearer'] = true; + $this->clearer = $value['clearer']; + unset($value['clearer']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['adapters'])) { + $output['adapters'] = $this->adapters; + } + if (isset($this->_usedProperties['tags'])) { + $output['tags'] = $this->tags; + } + if (isset($this->_usedProperties['public'])) { + $output['public'] = $this->public; + } + if (isset($this->_usedProperties['defaultLifetime'])) { + $output['default_lifetime'] = $this->defaultLifetime; + } + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['earlyExpirationMessageBus'])) { + $output['early_expiration_message_bus'] = $this->earlyExpirationMessageBus; + } + if (isset($this->_usedProperties['clearer'])) { + $output['clearer'] = $this->clearer; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/CacheConfig.php b/var/cache/dev/Symfony/Config/Framework/CacheConfig.php new file mode 100644 index 0000000..c8e5165 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/CacheConfig.php @@ -0,0 +1,277 @@ +_usedProperties['prefixSeed'] = true; + $this->prefixSeed = $value; + + return $this; + } + + /** + * App related cache pools configuration + * @default 'cache.adapter.filesystem' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function app($value): static + { + $this->_usedProperties['app'] = true; + $this->app = $value; + + return $this; + } + + /** + * System related cache pools configuration + * @default 'cache.adapter.system' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function system($value): static + { + $this->_usedProperties['system'] = true; + $this->system = $value; + + return $this; + } + + /** + * @default '%kernel.cache_dir%/pools/app' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function directory($value): static + { + $this->_usedProperties['directory'] = true; + $this->directory = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultPsr6Provider($value): static + { + $this->_usedProperties['defaultPsr6Provider'] = true; + $this->defaultPsr6Provider = $value; + + return $this; + } + + /** + * @default 'redis://localhost' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultRedisProvider($value): static + { + $this->_usedProperties['defaultRedisProvider'] = true; + $this->defaultRedisProvider = $value; + + return $this; + } + + /** + * @default 'memcached://localhost' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultMemcachedProvider($value): static + { + $this->_usedProperties['defaultMemcachedProvider'] = true; + $this->defaultMemcachedProvider = $value; + + return $this; + } + + /** + * @default 'database_connection' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultDoctrineDbalProvider($value): static + { + $this->_usedProperties['defaultDoctrineDbalProvider'] = true; + $this->defaultDoctrineDbalProvider = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultPdoProvider($value): static + { + $this->_usedProperties['defaultPdoProvider'] = true; + $this->defaultPdoProvider = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Cache\PoolConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Cache\PoolConfig : static) + */ + public function pool(string $name, mixed $value = []): \Symfony\Config\Framework\Cache\PoolConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['pools'] = true; + $this->pools[$name] = $value; + + return $this; + } + + if (!isset($this->pools[$name]) || !$this->pools[$name] instanceof \Symfony\Config\Framework\Cache\PoolConfig) { + $this->_usedProperties['pools'] = true; + $this->pools[$name] = new \Symfony\Config\Framework\Cache\PoolConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "pool()" has already been initialized. You cannot pass values the second time you call pool().'); + } + + return $this->pools[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('prefix_seed', $value)) { + $this->_usedProperties['prefixSeed'] = true; + $this->prefixSeed = $value['prefix_seed']; + unset($value['prefix_seed']); + } + + if (array_key_exists('app', $value)) { + $this->_usedProperties['app'] = true; + $this->app = $value['app']; + unset($value['app']); + } + + if (array_key_exists('system', $value)) { + $this->_usedProperties['system'] = true; + $this->system = $value['system']; + unset($value['system']); + } + + if (array_key_exists('directory', $value)) { + $this->_usedProperties['directory'] = true; + $this->directory = $value['directory']; + unset($value['directory']); + } + + if (array_key_exists('default_psr6_provider', $value)) { + $this->_usedProperties['defaultPsr6Provider'] = true; + $this->defaultPsr6Provider = $value['default_psr6_provider']; + unset($value['default_psr6_provider']); + } + + if (array_key_exists('default_redis_provider', $value)) { + $this->_usedProperties['defaultRedisProvider'] = true; + $this->defaultRedisProvider = $value['default_redis_provider']; + unset($value['default_redis_provider']); + } + + if (array_key_exists('default_memcached_provider', $value)) { + $this->_usedProperties['defaultMemcachedProvider'] = true; + $this->defaultMemcachedProvider = $value['default_memcached_provider']; + unset($value['default_memcached_provider']); + } + + if (array_key_exists('default_doctrine_dbal_provider', $value)) { + $this->_usedProperties['defaultDoctrineDbalProvider'] = true; + $this->defaultDoctrineDbalProvider = $value['default_doctrine_dbal_provider']; + unset($value['default_doctrine_dbal_provider']); + } + + if (array_key_exists('default_pdo_provider', $value)) { + $this->_usedProperties['defaultPdoProvider'] = true; + $this->defaultPdoProvider = $value['default_pdo_provider']; + unset($value['default_pdo_provider']); + } + + if (array_key_exists('pools', $value)) { + $this->_usedProperties['pools'] = true; + $this->pools = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Cache\PoolConfig($v) : $v, $value['pools']); + unset($value['pools']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['prefixSeed'])) { + $output['prefix_seed'] = $this->prefixSeed; + } + if (isset($this->_usedProperties['app'])) { + $output['app'] = $this->app; + } + if (isset($this->_usedProperties['system'])) { + $output['system'] = $this->system; + } + if (isset($this->_usedProperties['directory'])) { + $output['directory'] = $this->directory; + } + if (isset($this->_usedProperties['defaultPsr6Provider'])) { + $output['default_psr6_provider'] = $this->defaultPsr6Provider; + } + if (isset($this->_usedProperties['defaultRedisProvider'])) { + $output['default_redis_provider'] = $this->defaultRedisProvider; + } + if (isset($this->_usedProperties['defaultMemcachedProvider'])) { + $output['default_memcached_provider'] = $this->defaultMemcachedProvider; + } + if (isset($this->_usedProperties['defaultDoctrineDbalProvider'])) { + $output['default_doctrine_dbal_provider'] = $this->defaultDoctrineDbalProvider; + } + if (isset($this->_usedProperties['defaultPdoProvider'])) { + $output['default_pdo_provider'] = $this->defaultPdoProvider; + } + if (isset($this->_usedProperties['pools'])) { + $output['pools'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Cache\PoolConfig ? $v->toArray() : $v, $this->pools); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/CsrfProtectionConfig.php b/var/cache/dev/Symfony/Config/Framework/CsrfProtectionConfig.php new file mode 100644 index 0000000..8827d52 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/CsrfProtectionConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/EsiConfig.php b/var/cache/dev/Symfony/Config/Framework/EsiConfig.php new file mode 100644 index 0000000..322e8a9 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/EsiConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/ExceptionConfig.php b/var/cache/dev/Symfony/Config/Framework/ExceptionConfig.php new file mode 100644 index 0000000..8139665 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/ExceptionConfig.php @@ -0,0 +1,77 @@ +_usedProperties['logLevel'] = true; + $this->logLevel = $value; + + return $this; + } + + /** + * The status code of the response. Null or 0 to let Symfony decide. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function statusCode($value): static + { + $this->_usedProperties['statusCode'] = true; + $this->statusCode = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('log_level', $value)) { + $this->_usedProperties['logLevel'] = true; + $this->logLevel = $value['log_level']; + unset($value['log_level']); + } + + if (array_key_exists('status_code', $value)) { + $this->_usedProperties['statusCode'] = true; + $this->statusCode = $value['status_code']; + unset($value['status_code']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['logLevel'])) { + $output['log_level'] = $this->logLevel; + } + if (isset($this->_usedProperties['statusCode'])) { + $output['status_code'] = $this->statusCode; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Form/CsrfProtectionConfig.php b/var/cache/dev/Symfony/Config/Framework/Form/CsrfProtectionConfig.php new file mode 100644 index 0000000..745a2da --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Form/CsrfProtectionConfig.php @@ -0,0 +1,75 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default '_token' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function fieldName($value): static + { + $this->_usedProperties['fieldName'] = true; + $this->fieldName = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('field_name', $value)) { + $this->_usedProperties['fieldName'] = true; + $this->fieldName = $value['field_name']; + unset($value['field_name']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['fieldName'])) { + $output['field_name'] = $this->fieldName; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/FormConfig.php b/var/cache/dev/Symfony/Config/Framework/FormConfig.php new file mode 100644 index 0000000..075afa1 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/FormConfig.php @@ -0,0 +1,79 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default {"enabled":null,"field_name":"_token"} + */ + public function csrfProtection(array $value = []): \Symfony\Config\Framework\Form\CsrfProtectionConfig + { + if (null === $this->csrfProtection) { + $this->_usedProperties['csrfProtection'] = true; + $this->csrfProtection = new \Symfony\Config\Framework\Form\CsrfProtectionConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "csrfProtection()" has already been initialized. You cannot pass values the second time you call csrfProtection().'); + } + + return $this->csrfProtection; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('csrf_protection', $value)) { + $this->_usedProperties['csrfProtection'] = true; + $this->csrfProtection = new \Symfony\Config\Framework\Form\CsrfProtectionConfig($value['csrf_protection']); + unset($value['csrf_protection']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['csrfProtection'])) { + $output['csrf_protection'] = $this->csrfProtection->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/FragmentsConfig.php b/var/cache/dev/Symfony/Config/Framework/FragmentsConfig.php new file mode 100644 index 0000000..10b2083 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/FragmentsConfig.php @@ -0,0 +1,98 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function hincludeDefaultTemplate($value): static + { + $this->_usedProperties['hincludeDefaultTemplate'] = true; + $this->hincludeDefaultTemplate = $value; + + return $this; + } + + /** + * @default '/_fragment' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('hinclude_default_template', $value)) { + $this->_usedProperties['hincludeDefaultTemplate'] = true; + $this->hincludeDefaultTemplate = $value['hinclude_default_template']; + unset($value['hinclude_default_template']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['hincludeDefaultTemplate'])) { + $output['hinclude_default_template'] = $this->hincludeDefaultTemplate; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HtmlSanitizer/SanitizerConfig.php b/var/cache/dev/Symfony/Config/Framework/HtmlSanitizer/SanitizerConfig.php new file mode 100644 index 0000000..9fcadcd --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HtmlSanitizer/SanitizerConfig.php @@ -0,0 +1,445 @@ +_usedProperties['allowSafeElements'] = true; + $this->allowSafeElements = $value; + + return $this; + } + + /** + * Allows all static elements and attributes from the W3C Sanitizer API standard. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowStaticElements($value): static + { + $this->_usedProperties['allowStaticElements'] = true; + $this->allowStaticElements = $value; + + return $this; + } + + /** + * @return $this + */ + public function allowElement(string $name, mixed $value): static + { + $this->_usedProperties['allowElements'] = true; + $this->allowElements[$name] = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function blockElements(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['blockElements'] = true; + $this->blockElements = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function dropElements(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['dropElements'] = true; + $this->dropElements = $value; + + return $this; + } + + /** + * @return $this + */ + public function allowAttribute(string $name, mixed $value): static + { + $this->_usedProperties['allowAttributes'] = true; + $this->allowAttributes[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function dropAttribute(string $name, mixed $value): static + { + $this->_usedProperties['dropAttributes'] = true; + $this->dropAttributes[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function forceAttribute(string $name, ParamConfigurator|array $value): static + { + $this->_usedProperties['forceAttributes'] = true; + $this->forceAttributes[$name] = $value; + + return $this; + } + + /** + * Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function forceHttpsUrls($value): static + { + $this->_usedProperties['forceHttpsUrls'] = true; + $this->forceHttpsUrls = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function allowedLinkSchemes(ParamConfigurator|array $value): static + { + $this->_usedProperties['allowedLinkSchemes'] = true; + $this->allowedLinkSchemes = $value; + + return $this; + } + + /** + * Allows only a given list of hosts to be used in links href attributes. + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function allowedLinkHosts(mixed $value = NULL): static + { + $this->_usedProperties['allowedLinkHosts'] = true; + $this->allowedLinkHosts = $value; + + return $this; + } + + /** + * Allows relative URLs to be used in links href attributes. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowRelativeLinks($value): static + { + $this->_usedProperties['allowRelativeLinks'] = true; + $this->allowRelativeLinks = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function allowedMediaSchemes(ParamConfigurator|array $value): static + { + $this->_usedProperties['allowedMediaSchemes'] = true; + $this->allowedMediaSchemes = $value; + + return $this; + } + + /** + * Allows only a given list of hosts to be used in media source attributes (img, audio, video, ...). + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function allowedMediaHosts(mixed $value = NULL): static + { + $this->_usedProperties['allowedMediaHosts'] = true; + $this->allowedMediaHosts = $value; + + return $this; + } + + /** + * Allows relative URLs to be used in media source attributes (img, audio, video, ...). + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowRelativeMedias($value): static + { + $this->_usedProperties['allowRelativeMedias'] = true; + $this->allowRelativeMedias = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function withAttributeSanitizers(ParamConfigurator|array $value): static + { + $this->_usedProperties['withAttributeSanitizers'] = true; + $this->withAttributeSanitizers = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function withoutAttributeSanitizers(ParamConfigurator|array $value): static + { + $this->_usedProperties['withoutAttributeSanitizers'] = true; + $this->withoutAttributeSanitizers = $value; + + return $this; + } + + /** + * The maximum length allowed for the sanitized input. + * @default 0 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxInputLength($value): static + { + $this->_usedProperties['maxInputLength'] = true; + $this->maxInputLength = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('allow_safe_elements', $value)) { + $this->_usedProperties['allowSafeElements'] = true; + $this->allowSafeElements = $value['allow_safe_elements']; + unset($value['allow_safe_elements']); + } + + if (array_key_exists('allow_static_elements', $value)) { + $this->_usedProperties['allowStaticElements'] = true; + $this->allowStaticElements = $value['allow_static_elements']; + unset($value['allow_static_elements']); + } + + if (array_key_exists('allow_elements', $value)) { + $this->_usedProperties['allowElements'] = true; + $this->allowElements = $value['allow_elements']; + unset($value['allow_elements']); + } + + if (array_key_exists('block_elements', $value)) { + $this->_usedProperties['blockElements'] = true; + $this->blockElements = $value['block_elements']; + unset($value['block_elements']); + } + + if (array_key_exists('drop_elements', $value)) { + $this->_usedProperties['dropElements'] = true; + $this->dropElements = $value['drop_elements']; + unset($value['drop_elements']); + } + + if (array_key_exists('allow_attributes', $value)) { + $this->_usedProperties['allowAttributes'] = true; + $this->allowAttributes = $value['allow_attributes']; + unset($value['allow_attributes']); + } + + if (array_key_exists('drop_attributes', $value)) { + $this->_usedProperties['dropAttributes'] = true; + $this->dropAttributes = $value['drop_attributes']; + unset($value['drop_attributes']); + } + + if (array_key_exists('force_attributes', $value)) { + $this->_usedProperties['forceAttributes'] = true; + $this->forceAttributes = $value['force_attributes']; + unset($value['force_attributes']); + } + + if (array_key_exists('force_https_urls', $value)) { + $this->_usedProperties['forceHttpsUrls'] = true; + $this->forceHttpsUrls = $value['force_https_urls']; + unset($value['force_https_urls']); + } + + if (array_key_exists('allowed_link_schemes', $value)) { + $this->_usedProperties['allowedLinkSchemes'] = true; + $this->allowedLinkSchemes = $value['allowed_link_schemes']; + unset($value['allowed_link_schemes']); + } + + if (array_key_exists('allowed_link_hosts', $value)) { + $this->_usedProperties['allowedLinkHosts'] = true; + $this->allowedLinkHosts = $value['allowed_link_hosts']; + unset($value['allowed_link_hosts']); + } + + if (array_key_exists('allow_relative_links', $value)) { + $this->_usedProperties['allowRelativeLinks'] = true; + $this->allowRelativeLinks = $value['allow_relative_links']; + unset($value['allow_relative_links']); + } + + if (array_key_exists('allowed_media_schemes', $value)) { + $this->_usedProperties['allowedMediaSchemes'] = true; + $this->allowedMediaSchemes = $value['allowed_media_schemes']; + unset($value['allowed_media_schemes']); + } + + if (array_key_exists('allowed_media_hosts', $value)) { + $this->_usedProperties['allowedMediaHosts'] = true; + $this->allowedMediaHosts = $value['allowed_media_hosts']; + unset($value['allowed_media_hosts']); + } + + if (array_key_exists('allow_relative_medias', $value)) { + $this->_usedProperties['allowRelativeMedias'] = true; + $this->allowRelativeMedias = $value['allow_relative_medias']; + unset($value['allow_relative_medias']); + } + + if (array_key_exists('with_attribute_sanitizers', $value)) { + $this->_usedProperties['withAttributeSanitizers'] = true; + $this->withAttributeSanitizers = $value['with_attribute_sanitizers']; + unset($value['with_attribute_sanitizers']); + } + + if (array_key_exists('without_attribute_sanitizers', $value)) { + $this->_usedProperties['withoutAttributeSanitizers'] = true; + $this->withoutAttributeSanitizers = $value['without_attribute_sanitizers']; + unset($value['without_attribute_sanitizers']); + } + + if (array_key_exists('max_input_length', $value)) { + $this->_usedProperties['maxInputLength'] = true; + $this->maxInputLength = $value['max_input_length']; + unset($value['max_input_length']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['allowSafeElements'])) { + $output['allow_safe_elements'] = $this->allowSafeElements; + } + if (isset($this->_usedProperties['allowStaticElements'])) { + $output['allow_static_elements'] = $this->allowStaticElements; + } + if (isset($this->_usedProperties['allowElements'])) { + $output['allow_elements'] = $this->allowElements; + } + if (isset($this->_usedProperties['blockElements'])) { + $output['block_elements'] = $this->blockElements; + } + if (isset($this->_usedProperties['dropElements'])) { + $output['drop_elements'] = $this->dropElements; + } + if (isset($this->_usedProperties['allowAttributes'])) { + $output['allow_attributes'] = $this->allowAttributes; + } + if (isset($this->_usedProperties['dropAttributes'])) { + $output['drop_attributes'] = $this->dropAttributes; + } + if (isset($this->_usedProperties['forceAttributes'])) { + $output['force_attributes'] = $this->forceAttributes; + } + if (isset($this->_usedProperties['forceHttpsUrls'])) { + $output['force_https_urls'] = $this->forceHttpsUrls; + } + if (isset($this->_usedProperties['allowedLinkSchemes'])) { + $output['allowed_link_schemes'] = $this->allowedLinkSchemes; + } + if (isset($this->_usedProperties['allowedLinkHosts'])) { + $output['allowed_link_hosts'] = $this->allowedLinkHosts; + } + if (isset($this->_usedProperties['allowRelativeLinks'])) { + $output['allow_relative_links'] = $this->allowRelativeLinks; + } + if (isset($this->_usedProperties['allowedMediaSchemes'])) { + $output['allowed_media_schemes'] = $this->allowedMediaSchemes; + } + if (isset($this->_usedProperties['allowedMediaHosts'])) { + $output['allowed_media_hosts'] = $this->allowedMediaHosts; + } + if (isset($this->_usedProperties['allowRelativeMedias'])) { + $output['allow_relative_medias'] = $this->allowRelativeMedias; + } + if (isset($this->_usedProperties['withAttributeSanitizers'])) { + $output['with_attribute_sanitizers'] = $this->withAttributeSanitizers; + } + if (isset($this->_usedProperties['withoutAttributeSanitizers'])) { + $output['without_attribute_sanitizers'] = $this->withoutAttributeSanitizers; + } + if (isset($this->_usedProperties['maxInputLength'])) { + $output['max_input_length'] = $this->maxInputLength; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HtmlSanitizerConfig.php b/var/cache/dev/Symfony/Config/Framework/HtmlSanitizerConfig.php new file mode 100644 index 0000000..69983ba --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HtmlSanitizerConfig.php @@ -0,0 +1,76 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function sanitizer(string $name, array $value = []): \Symfony\Config\Framework\HtmlSanitizer\SanitizerConfig + { + if (!isset($this->sanitizers[$name])) { + $this->_usedProperties['sanitizers'] = true; + $this->sanitizers[$name] = new \Symfony\Config\Framework\HtmlSanitizer\SanitizerConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "sanitizer()" has already been initialized. You cannot pass values the second time you call sanitizer().'); + } + + return $this->sanitizers[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('sanitizers', $value)) { + $this->_usedProperties['sanitizers'] = true; + $this->sanitizers = array_map(fn ($v) => new \Symfony\Config\Framework\HtmlSanitizer\SanitizerConfig($v), $value['sanitizers']); + unset($value['sanitizers']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['sanitizers'])) { + $output['sanitizers'] = array_map(fn ($v) => $v->toArray(), $this->sanitizers); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpCacheConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpCacheConfig.php new file mode 100644 index 0000000..46a1c45 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpCacheConfig.php @@ -0,0 +1,305 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default '%kernel.debug%' + * @param ParamConfigurator|bool $value + * @return $this + */ + public function debug($value): static + { + $this->_usedProperties['debug'] = true; + $this->debug = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|'none'|'short'|'full' $value + * @return $this + */ + public function traceLevel($value): static + { + $this->_usedProperties['traceLevel'] = true; + $this->traceLevel = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function traceHeader($value): static + { + $this->_usedProperties['traceHeader'] = true; + $this->traceHeader = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function defaultTtl($value): static + { + $this->_usedProperties['defaultTtl'] = true; + $this->defaultTtl = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function privateHeaders(ParamConfigurator|array $value): static + { + $this->_usedProperties['privateHeaders'] = true; + $this->privateHeaders = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function skipResponseHeaders(ParamConfigurator|array $value): static + { + $this->_usedProperties['skipResponseHeaders'] = true; + $this->skipResponseHeaders = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowReload($value): static + { + $this->_usedProperties['allowReload'] = true; + $this->allowReload = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowRevalidate($value): static + { + $this->_usedProperties['allowRevalidate'] = true; + $this->allowRevalidate = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function staleWhileRevalidate($value): static + { + $this->_usedProperties['staleWhileRevalidate'] = true; + $this->staleWhileRevalidate = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function staleIfError($value): static + { + $this->_usedProperties['staleIfError'] = true; + $this->staleIfError = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function terminateOnCacheHit($value): static + { + $this->_usedProperties['terminateOnCacheHit'] = true; + $this->terminateOnCacheHit = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('debug', $value)) { + $this->_usedProperties['debug'] = true; + $this->debug = $value['debug']; + unset($value['debug']); + } + + if (array_key_exists('trace_level', $value)) { + $this->_usedProperties['traceLevel'] = true; + $this->traceLevel = $value['trace_level']; + unset($value['trace_level']); + } + + if (array_key_exists('trace_header', $value)) { + $this->_usedProperties['traceHeader'] = true; + $this->traceHeader = $value['trace_header']; + unset($value['trace_header']); + } + + if (array_key_exists('default_ttl', $value)) { + $this->_usedProperties['defaultTtl'] = true; + $this->defaultTtl = $value['default_ttl']; + unset($value['default_ttl']); + } + + if (array_key_exists('private_headers', $value)) { + $this->_usedProperties['privateHeaders'] = true; + $this->privateHeaders = $value['private_headers']; + unset($value['private_headers']); + } + + if (array_key_exists('skip_response_headers', $value)) { + $this->_usedProperties['skipResponseHeaders'] = true; + $this->skipResponseHeaders = $value['skip_response_headers']; + unset($value['skip_response_headers']); + } + + if (array_key_exists('allow_reload', $value)) { + $this->_usedProperties['allowReload'] = true; + $this->allowReload = $value['allow_reload']; + unset($value['allow_reload']); + } + + if (array_key_exists('allow_revalidate', $value)) { + $this->_usedProperties['allowRevalidate'] = true; + $this->allowRevalidate = $value['allow_revalidate']; + unset($value['allow_revalidate']); + } + + if (array_key_exists('stale_while_revalidate', $value)) { + $this->_usedProperties['staleWhileRevalidate'] = true; + $this->staleWhileRevalidate = $value['stale_while_revalidate']; + unset($value['stale_while_revalidate']); + } + + if (array_key_exists('stale_if_error', $value)) { + $this->_usedProperties['staleIfError'] = true; + $this->staleIfError = $value['stale_if_error']; + unset($value['stale_if_error']); + } + + if (array_key_exists('terminate_on_cache_hit', $value)) { + $this->_usedProperties['terminateOnCacheHit'] = true; + $this->terminateOnCacheHit = $value['terminate_on_cache_hit']; + unset($value['terminate_on_cache_hit']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['debug'])) { + $output['debug'] = $this->debug; + } + if (isset($this->_usedProperties['traceLevel'])) { + $output['trace_level'] = $this->traceLevel; + } + if (isset($this->_usedProperties['traceHeader'])) { + $output['trace_header'] = $this->traceHeader; + } + if (isset($this->_usedProperties['defaultTtl'])) { + $output['default_ttl'] = $this->defaultTtl; + } + if (isset($this->_usedProperties['privateHeaders'])) { + $output['private_headers'] = $this->privateHeaders; + } + if (isset($this->_usedProperties['skipResponseHeaders'])) { + $output['skip_response_headers'] = $this->skipResponseHeaders; + } + if (isset($this->_usedProperties['allowReload'])) { + $output['allow_reload'] = $this->allowReload; + } + if (isset($this->_usedProperties['allowRevalidate'])) { + $output['allow_revalidate'] = $this->allowRevalidate; + } + if (isset($this->_usedProperties['staleWhileRevalidate'])) { + $output['stale_while_revalidate'] = $this->staleWhileRevalidate; + } + if (isset($this->_usedProperties['staleIfError'])) { + $output['stale_if_error'] = $this->staleIfError; + } + if (isset($this->_usedProperties['terminateOnCacheHit'])) { + $output['terminate_on_cache_hit'] = $this->terminateOnCacheHit; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/PeerFingerprintConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/PeerFingerprintConfig.php new file mode 100644 index 0000000..910bc00 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/PeerFingerprintConfig.php @@ -0,0 +1,101 @@ +_usedProperties['sha1'] = true; + $this->sha1 = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function pinsha256(mixed $value): static + { + $this->_usedProperties['pinsha256'] = true; + $this->pinsha256 = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function md5(mixed $value): static + { + $this->_usedProperties['md5'] = true; + $this->md5 = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('sha1', $value)) { + $this->_usedProperties['sha1'] = true; + $this->sha1 = $value['sha1']; + unset($value['sha1']); + } + + if (array_key_exists('pin-sha256', $value)) { + $this->_usedProperties['pinsha256'] = true; + $this->pinsha256 = $value['pin-sha256']; + unset($value['pin-sha256']); + } + + if (array_key_exists('md5', $value)) { + $this->_usedProperties['md5'] = true; + $this->md5 = $value['md5']; + unset($value['md5']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['sha1'])) { + $output['sha1'] = $this->sha1; + } + if (isset($this->_usedProperties['pinsha256'])) { + $output['pin-sha256'] = $this->pinsha256; + } + if (isset($this->_usedProperties['md5'])) { + $output['md5'] = $this->md5; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/RetryFailed/HttpCodeConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/RetryFailed/HttpCodeConfig.php new file mode 100644 index 0000000..a4d7c62 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/RetryFailed/HttpCodeConfig.php @@ -0,0 +1,75 @@ +_usedProperties['code'] = true; + $this->code = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function methods(ParamConfigurator|array $value): static + { + $this->_usedProperties['methods'] = true; + $this->methods = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('code', $value)) { + $this->_usedProperties['code'] = true; + $this->code = $value['code']; + unset($value['code']); + } + + if (array_key_exists('methods', $value)) { + $this->_usedProperties['methods'] = true; + $this->methods = $value['methods']; + unset($value['methods']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['code'])) { + $output['code'] = $this->code; + } + if (isset($this->_usedProperties['methods'])) { + $output['methods'] = $this->methods; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/RetryFailedConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/RetryFailedConfig.php new file mode 100644 index 0000000..fe1fcff --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptions/RetryFailedConfig.php @@ -0,0 +1,233 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * service id to override the retry strategy + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function retryStrategy($value): static + { + $this->_usedProperties['retryStrategy'] = true; + $this->retryStrategy = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * A list of HTTP status code that triggers a retry + * @return \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailed\HttpCodeConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailed\HttpCodeConfig : static) + */ + public function httpCode(string $code, array $value = []): \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailed\HttpCodeConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['httpCodes'] = true; + $this->httpCodes[$code] = $value; + + return $this; + } + + if (!isset($this->httpCodes[$code]) || !$this->httpCodes[$code] instanceof \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailed\HttpCodeConfig) { + $this->_usedProperties['httpCodes'] = true; + $this->httpCodes[$code] = new \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailed\HttpCodeConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "httpCode()" has already been initialized. You cannot pass values the second time you call httpCode().'); + } + + return $this->httpCodes[$code]; + } + + /** + * @default 3 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxRetries($value): static + { + $this->_usedProperties['maxRetries'] = true; + $this->maxRetries = $value; + + return $this; + } + + /** + * Time in ms to delay (or the initial value when multiplier is used) + * @default 1000 + * @param ParamConfigurator|int $value + * @return $this + */ + public function delay($value): static + { + $this->_usedProperties['delay'] = true; + $this->delay = $value; + + return $this; + } + + /** + * If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries) + * @default 2 + * @param ParamConfigurator|float $value + * @return $this + */ + public function multiplier($value): static + { + $this->_usedProperties['multiplier'] = true; + $this->multiplier = $value; + + return $this; + } + + /** + * Max time in ms that a retry should ever be delayed (0 = infinite) + * @default 0 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxDelay($value): static + { + $this->_usedProperties['maxDelay'] = true; + $this->maxDelay = $value; + + return $this; + } + + /** + * Randomness in percent (between 0 and 1) to apply to the delay + * @default 0.1 + * @param ParamConfigurator|float $value + * @return $this + */ + public function jitter($value): static + { + $this->_usedProperties['jitter'] = true; + $this->jitter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('retry_strategy', $value)) { + $this->_usedProperties['retryStrategy'] = true; + $this->retryStrategy = $value['retry_strategy']; + unset($value['retry_strategy']); + } + + if (array_key_exists('http_codes', $value)) { + $this->_usedProperties['httpCodes'] = true; + $this->httpCodes = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailed\HttpCodeConfig($v) : $v, $value['http_codes']); + unset($value['http_codes']); + } + + if (array_key_exists('max_retries', $value)) { + $this->_usedProperties['maxRetries'] = true; + $this->maxRetries = $value['max_retries']; + unset($value['max_retries']); + } + + if (array_key_exists('delay', $value)) { + $this->_usedProperties['delay'] = true; + $this->delay = $value['delay']; + unset($value['delay']); + } + + if (array_key_exists('multiplier', $value)) { + $this->_usedProperties['multiplier'] = true; + $this->multiplier = $value['multiplier']; + unset($value['multiplier']); + } + + if (array_key_exists('max_delay', $value)) { + $this->_usedProperties['maxDelay'] = true; + $this->maxDelay = $value['max_delay']; + unset($value['max_delay']); + } + + if (array_key_exists('jitter', $value)) { + $this->_usedProperties['jitter'] = true; + $this->jitter = $value['jitter']; + unset($value['jitter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['retryStrategy'])) { + $output['retry_strategy'] = $this->retryStrategy; + } + if (isset($this->_usedProperties['httpCodes'])) { + $output['http_codes'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailed\HttpCodeConfig ? $v->toArray() : $v, $this->httpCodes); + } + if (isset($this->_usedProperties['maxRetries'])) { + $output['max_retries'] = $this->maxRetries; + } + if (isset($this->_usedProperties['delay'])) { + $output['delay'] = $this->delay; + } + if (isset($this->_usedProperties['multiplier'])) { + $output['multiplier'] = $this->multiplier; + } + if (isset($this->_usedProperties['maxDelay'])) { + $output['max_delay'] = $this->maxDelay; + } + if (isset($this->_usedProperties['jitter'])) { + $output['jitter'] = $this->jitter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptionsConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptionsConfig.php new file mode 100644 index 0000000..3d70687 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/DefaultOptionsConfig.php @@ -0,0 +1,589 @@ +_usedProperties['headers'] = true; + $this->headers[$name] = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function vars(ParamConfigurator|array $value): static + { + $this->_usedProperties['vars'] = true; + $this->vars = $value; + + return $this; + } + + /** + * The maximum number of redirects to follow. + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxRedirects($value): static + { + $this->_usedProperties['maxRedirects'] = true; + $this->maxRedirects = $value; + + return $this; + } + + /** + * The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function httpVersion($value): static + { + $this->_usedProperties['httpVersion'] = true; + $this->httpVersion = $value; + + return $this; + } + + /** + * @return $this + */ + public function resolve(string $host, mixed $value): static + { + $this->_usedProperties['resolve'] = true; + $this->resolve[$host] = $value; + + return $this; + } + + /** + * The URL of the proxy to pass requests through or null for automatic detection. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function proxy($value): static + { + $this->_usedProperties['proxy'] = true; + $this->proxy = $value; + + return $this; + } + + /** + * A comma separated list of hosts that do not require a proxy to be reached. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function noProxy($value): static + { + $this->_usedProperties['noProxy'] = true; + $this->noProxy = $value; + + return $this; + } + + /** + * The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * @default null + * @param ParamConfigurator|float $value + * @return $this + */ + public function timeout($value): static + { + $this->_usedProperties['timeout'] = true; + $this->timeout = $value; + + return $this; + } + + /** + * The maximum execution time for the request+response as a whole. + * @default null + * @param ParamConfigurator|float $value + * @return $this + */ + public function maxDuration($value): static + { + $this->_usedProperties['maxDuration'] = true; + $this->maxDuration = $value; + + return $this; + } + + /** + * A network interface name, IP address, a host name or a UNIX socket to bind to. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function bindto($value): static + { + $this->_usedProperties['bindto'] = true; + $this->bindto = $value; + + return $this; + } + + /** + * Indicates if the peer should be verified in a TLS context. + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function verifyPeer($value): static + { + $this->_usedProperties['verifyPeer'] = true; + $this->verifyPeer = $value; + + return $this; + } + + /** + * Indicates if the host should exist as a certificate common name. + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function verifyHost($value): static + { + $this->_usedProperties['verifyHost'] = true; + $this->verifyHost = $value; + + return $this; + } + + /** + * A certificate authority file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cafile($value): static + { + $this->_usedProperties['cafile'] = true; + $this->cafile = $value; + + return $this; + } + + /** + * A directory that contains multiple certificate authority files. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function capath($value): static + { + $this->_usedProperties['capath'] = true; + $this->capath = $value; + + return $this; + } + + /** + * A PEM formatted certificate file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function localCert($value): static + { + $this->_usedProperties['localCert'] = true; + $this->localCert = $value; + + return $this; + } + + /** + * A private key file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function localPk($value): static + { + $this->_usedProperties['localPk'] = true; + $this->localPk = $value; + + return $this; + } + + /** + * The passphrase used to encrypt the "local_pk" file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passphrase($value): static + { + $this->_usedProperties['passphrase'] = true; + $this->passphrase = $value; + + return $this; + } + + /** + * A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function ciphers($value): static + { + $this->_usedProperties['ciphers'] = true; + $this->ciphers = $value; + + return $this; + } + + /** + * Associative array: hashing algorithm => hash(es). + */ + public function peerFingerprint(array $value = []): \Symfony\Config\Framework\HttpClient\DefaultOptions\PeerFingerprintConfig + { + if (null === $this->peerFingerprint) { + $this->_usedProperties['peerFingerprint'] = true; + $this->peerFingerprint = new \Symfony\Config\Framework\HttpClient\DefaultOptions\PeerFingerprintConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "peerFingerprint()" has already been initialized. You cannot pass values the second time you call peerFingerprint().'); + } + + return $this->peerFingerprint; + } + + /** + * The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cryptoMethod($value): static + { + $this->_usedProperties['cryptoMethod'] = true; + $this->cryptoMethod = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function extra(ParamConfigurator|array $value): static + { + $this->_usedProperties['extra'] = true; + $this->extra = $value; + + return $this; + } + + /** + * Rate limiter name to use for throttling requests + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function rateLimiter($value): static + { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"retry_strategy":null,"http_codes":[],"max_retries":3,"delay":1000,"multiplier":2,"max_delay":0,"jitter":0.1} + * @return \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailedConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailedConfig : static) + */ + public function retryFailed(mixed $value = []): \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailedConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['retryFailed'] = true; + $this->retryFailed = $value; + + return $this; + } + + if (!$this->retryFailed instanceof \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailedConfig) { + $this->_usedProperties['retryFailed'] = true; + $this->retryFailed = new \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailedConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "retryFailed()" has already been initialized. You cannot pass values the second time you call retryFailed().'); + } + + return $this->retryFailed; + } + + public function __construct(array $value = []) + { + if (array_key_exists('headers', $value)) { + $this->_usedProperties['headers'] = true; + $this->headers = $value['headers']; + unset($value['headers']); + } + + if (array_key_exists('vars', $value)) { + $this->_usedProperties['vars'] = true; + $this->vars = $value['vars']; + unset($value['vars']); + } + + if (array_key_exists('max_redirects', $value)) { + $this->_usedProperties['maxRedirects'] = true; + $this->maxRedirects = $value['max_redirects']; + unset($value['max_redirects']); + } + + if (array_key_exists('http_version', $value)) { + $this->_usedProperties['httpVersion'] = true; + $this->httpVersion = $value['http_version']; + unset($value['http_version']); + } + + if (array_key_exists('resolve', $value)) { + $this->_usedProperties['resolve'] = true; + $this->resolve = $value['resolve']; + unset($value['resolve']); + } + + if (array_key_exists('proxy', $value)) { + $this->_usedProperties['proxy'] = true; + $this->proxy = $value['proxy']; + unset($value['proxy']); + } + + if (array_key_exists('no_proxy', $value)) { + $this->_usedProperties['noProxy'] = true; + $this->noProxy = $value['no_proxy']; + unset($value['no_proxy']); + } + + if (array_key_exists('timeout', $value)) { + $this->_usedProperties['timeout'] = true; + $this->timeout = $value['timeout']; + unset($value['timeout']); + } + + if (array_key_exists('max_duration', $value)) { + $this->_usedProperties['maxDuration'] = true; + $this->maxDuration = $value['max_duration']; + unset($value['max_duration']); + } + + if (array_key_exists('bindto', $value)) { + $this->_usedProperties['bindto'] = true; + $this->bindto = $value['bindto']; + unset($value['bindto']); + } + + if (array_key_exists('verify_peer', $value)) { + $this->_usedProperties['verifyPeer'] = true; + $this->verifyPeer = $value['verify_peer']; + unset($value['verify_peer']); + } + + if (array_key_exists('verify_host', $value)) { + $this->_usedProperties['verifyHost'] = true; + $this->verifyHost = $value['verify_host']; + unset($value['verify_host']); + } + + if (array_key_exists('cafile', $value)) { + $this->_usedProperties['cafile'] = true; + $this->cafile = $value['cafile']; + unset($value['cafile']); + } + + if (array_key_exists('capath', $value)) { + $this->_usedProperties['capath'] = true; + $this->capath = $value['capath']; + unset($value['capath']); + } + + if (array_key_exists('local_cert', $value)) { + $this->_usedProperties['localCert'] = true; + $this->localCert = $value['local_cert']; + unset($value['local_cert']); + } + + if (array_key_exists('local_pk', $value)) { + $this->_usedProperties['localPk'] = true; + $this->localPk = $value['local_pk']; + unset($value['local_pk']); + } + + if (array_key_exists('passphrase', $value)) { + $this->_usedProperties['passphrase'] = true; + $this->passphrase = $value['passphrase']; + unset($value['passphrase']); + } + + if (array_key_exists('ciphers', $value)) { + $this->_usedProperties['ciphers'] = true; + $this->ciphers = $value['ciphers']; + unset($value['ciphers']); + } + + if (array_key_exists('peer_fingerprint', $value)) { + $this->_usedProperties['peerFingerprint'] = true; + $this->peerFingerprint = new \Symfony\Config\Framework\HttpClient\DefaultOptions\PeerFingerprintConfig($value['peer_fingerprint']); + unset($value['peer_fingerprint']); + } + + if (array_key_exists('crypto_method', $value)) { + $this->_usedProperties['cryptoMethod'] = true; + $this->cryptoMethod = $value['crypto_method']; + unset($value['crypto_method']); + } + + if (array_key_exists('extra', $value)) { + $this->_usedProperties['extra'] = true; + $this->extra = $value['extra']; + unset($value['extra']); + } + + if (array_key_exists('rate_limiter', $value)) { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = $value['rate_limiter']; + unset($value['rate_limiter']); + } + + if (array_key_exists('retry_failed', $value)) { + $this->_usedProperties['retryFailed'] = true; + $this->retryFailed = \is_array($value['retry_failed']) ? new \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailedConfig($value['retry_failed']) : $value['retry_failed']; + unset($value['retry_failed']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['headers'])) { + $output['headers'] = $this->headers; + } + if (isset($this->_usedProperties['vars'])) { + $output['vars'] = $this->vars; + } + if (isset($this->_usedProperties['maxRedirects'])) { + $output['max_redirects'] = $this->maxRedirects; + } + if (isset($this->_usedProperties['httpVersion'])) { + $output['http_version'] = $this->httpVersion; + } + if (isset($this->_usedProperties['resolve'])) { + $output['resolve'] = $this->resolve; + } + if (isset($this->_usedProperties['proxy'])) { + $output['proxy'] = $this->proxy; + } + if (isset($this->_usedProperties['noProxy'])) { + $output['no_proxy'] = $this->noProxy; + } + if (isset($this->_usedProperties['timeout'])) { + $output['timeout'] = $this->timeout; + } + if (isset($this->_usedProperties['maxDuration'])) { + $output['max_duration'] = $this->maxDuration; + } + if (isset($this->_usedProperties['bindto'])) { + $output['bindto'] = $this->bindto; + } + if (isset($this->_usedProperties['verifyPeer'])) { + $output['verify_peer'] = $this->verifyPeer; + } + if (isset($this->_usedProperties['verifyHost'])) { + $output['verify_host'] = $this->verifyHost; + } + if (isset($this->_usedProperties['cafile'])) { + $output['cafile'] = $this->cafile; + } + if (isset($this->_usedProperties['capath'])) { + $output['capath'] = $this->capath; + } + if (isset($this->_usedProperties['localCert'])) { + $output['local_cert'] = $this->localCert; + } + if (isset($this->_usedProperties['localPk'])) { + $output['local_pk'] = $this->localPk; + } + if (isset($this->_usedProperties['passphrase'])) { + $output['passphrase'] = $this->passphrase; + } + if (isset($this->_usedProperties['ciphers'])) { + $output['ciphers'] = $this->ciphers; + } + if (isset($this->_usedProperties['peerFingerprint'])) { + $output['peer_fingerprint'] = $this->peerFingerprint->toArray(); + } + if (isset($this->_usedProperties['cryptoMethod'])) { + $output['crypto_method'] = $this->cryptoMethod; + } + if (isset($this->_usedProperties['extra'])) { + $output['extra'] = $this->extra; + } + if (isset($this->_usedProperties['rateLimiter'])) { + $output['rate_limiter'] = $this->rateLimiter; + } + if (isset($this->_usedProperties['retryFailed'])) { + $output['retry_failed'] = $this->retryFailed instanceof \Symfony\Config\Framework\HttpClient\DefaultOptions\RetryFailedConfig ? $this->retryFailed->toArray() : $this->retryFailed; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig.php new file mode 100644 index 0000000..1afc80a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig.php @@ -0,0 +1,683 @@ +_usedProperties['scope'] = true; + $this->scope = $value; + + return $this; + } + + /** + * The URI to resolve relative URLs, following rules in RFC 3985, section 2. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function baseUri($value): static + { + $this->_usedProperties['baseUri'] = true; + $this->baseUri = $value; + + return $this; + } + + /** + * An HTTP Basic authentication "username:password". + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function authBasic($value): static + { + $this->_usedProperties['authBasic'] = true; + $this->authBasic = $value; + + return $this; + } + + /** + * A token enabling HTTP Bearer authorization. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function authBearer($value): static + { + $this->_usedProperties['authBearer'] = true; + $this->authBearer = $value; + + return $this; + } + + /** + * A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension). + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function authNtlm($value): static + { + $this->_usedProperties['authNtlm'] = true; + $this->authNtlm = $value; + + return $this; + } + + /** + * @return $this + */ + public function query(string $key, mixed $value): static + { + $this->_usedProperties['query'] = true; + $this->query[$key] = $value; + + return $this; + } + + /** + * @return $this + */ + public function header(string $name, mixed $value): static + { + $this->_usedProperties['headers'] = true; + $this->headers[$name] = $value; + + return $this; + } + + /** + * The maximum number of redirects to follow. + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxRedirects($value): static + { + $this->_usedProperties['maxRedirects'] = true; + $this->maxRedirects = $value; + + return $this; + } + + /** + * The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function httpVersion($value): static + { + $this->_usedProperties['httpVersion'] = true; + $this->httpVersion = $value; + + return $this; + } + + /** + * @return $this + */ + public function resolve(string $host, mixed $value): static + { + $this->_usedProperties['resolve'] = true; + $this->resolve[$host] = $value; + + return $this; + } + + /** + * The URL of the proxy to pass requests through or null for automatic detection. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function proxy($value): static + { + $this->_usedProperties['proxy'] = true; + $this->proxy = $value; + + return $this; + } + + /** + * A comma separated list of hosts that do not require a proxy to be reached. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function noProxy($value): static + { + $this->_usedProperties['noProxy'] = true; + $this->noProxy = $value; + + return $this; + } + + /** + * The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * @default null + * @param ParamConfigurator|float $value + * @return $this + */ + public function timeout($value): static + { + $this->_usedProperties['timeout'] = true; + $this->timeout = $value; + + return $this; + } + + /** + * The maximum execution time for the request+response as a whole. + * @default null + * @param ParamConfigurator|float $value + * @return $this + */ + public function maxDuration($value): static + { + $this->_usedProperties['maxDuration'] = true; + $this->maxDuration = $value; + + return $this; + } + + /** + * A network interface name, IP address, a host name or a UNIX socket to bind to. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function bindto($value): static + { + $this->_usedProperties['bindto'] = true; + $this->bindto = $value; + + return $this; + } + + /** + * Indicates if the peer should be verified in a TLS context. + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function verifyPeer($value): static + { + $this->_usedProperties['verifyPeer'] = true; + $this->verifyPeer = $value; + + return $this; + } + + /** + * Indicates if the host should exist as a certificate common name. + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function verifyHost($value): static + { + $this->_usedProperties['verifyHost'] = true; + $this->verifyHost = $value; + + return $this; + } + + /** + * A certificate authority file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cafile($value): static + { + $this->_usedProperties['cafile'] = true; + $this->cafile = $value; + + return $this; + } + + /** + * A directory that contains multiple certificate authority files. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function capath($value): static + { + $this->_usedProperties['capath'] = true; + $this->capath = $value; + + return $this; + } + + /** + * A PEM formatted certificate file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function localCert($value): static + { + $this->_usedProperties['localCert'] = true; + $this->localCert = $value; + + return $this; + } + + /** + * A private key file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function localPk($value): static + { + $this->_usedProperties['localPk'] = true; + $this->localPk = $value; + + return $this; + } + + /** + * The passphrase used to encrypt the "local_pk" file. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passphrase($value): static + { + $this->_usedProperties['passphrase'] = true; + $this->passphrase = $value; + + return $this; + } + + /** + * A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function ciphers($value): static + { + $this->_usedProperties['ciphers'] = true; + $this->ciphers = $value; + + return $this; + } + + /** + * Associative array: hashing algorithm => hash(es). + */ + public function peerFingerprint(array $value = []): \Symfony\Config\Framework\HttpClient\ScopedClientConfig\PeerFingerprintConfig + { + if (null === $this->peerFingerprint) { + $this->_usedProperties['peerFingerprint'] = true; + $this->peerFingerprint = new \Symfony\Config\Framework\HttpClient\ScopedClientConfig\PeerFingerprintConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "peerFingerprint()" has already been initialized. You cannot pass values the second time you call peerFingerprint().'); + } + + return $this->peerFingerprint; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function extra(ParamConfigurator|array $value): static + { + $this->_usedProperties['extra'] = true; + $this->extra = $value; + + return $this; + } + + /** + * Rate limiter name to use for throttling requests + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function rateLimiter($value): static + { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"retry_strategy":null,"http_codes":[],"max_retries":3,"delay":1000,"multiplier":2,"max_delay":0,"jitter":0.1} + * @return \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig : static) + */ + public function retryFailed(mixed $value = []): \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['retryFailed'] = true; + $this->retryFailed = $value; + + return $this; + } + + if (!$this->retryFailed instanceof \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig) { + $this->_usedProperties['retryFailed'] = true; + $this->retryFailed = new \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "retryFailed()" has already been initialized. You cannot pass values the second time you call retryFailed().'); + } + + return $this->retryFailed; + } + + public function __construct(array $value = []) + { + if (array_key_exists('scope', $value)) { + $this->_usedProperties['scope'] = true; + $this->scope = $value['scope']; + unset($value['scope']); + } + + if (array_key_exists('base_uri', $value)) { + $this->_usedProperties['baseUri'] = true; + $this->baseUri = $value['base_uri']; + unset($value['base_uri']); + } + + if (array_key_exists('auth_basic', $value)) { + $this->_usedProperties['authBasic'] = true; + $this->authBasic = $value['auth_basic']; + unset($value['auth_basic']); + } + + if (array_key_exists('auth_bearer', $value)) { + $this->_usedProperties['authBearer'] = true; + $this->authBearer = $value['auth_bearer']; + unset($value['auth_bearer']); + } + + if (array_key_exists('auth_ntlm', $value)) { + $this->_usedProperties['authNtlm'] = true; + $this->authNtlm = $value['auth_ntlm']; + unset($value['auth_ntlm']); + } + + if (array_key_exists('query', $value)) { + $this->_usedProperties['query'] = true; + $this->query = $value['query']; + unset($value['query']); + } + + if (array_key_exists('headers', $value)) { + $this->_usedProperties['headers'] = true; + $this->headers = $value['headers']; + unset($value['headers']); + } + + if (array_key_exists('max_redirects', $value)) { + $this->_usedProperties['maxRedirects'] = true; + $this->maxRedirects = $value['max_redirects']; + unset($value['max_redirects']); + } + + if (array_key_exists('http_version', $value)) { + $this->_usedProperties['httpVersion'] = true; + $this->httpVersion = $value['http_version']; + unset($value['http_version']); + } + + if (array_key_exists('resolve', $value)) { + $this->_usedProperties['resolve'] = true; + $this->resolve = $value['resolve']; + unset($value['resolve']); + } + + if (array_key_exists('proxy', $value)) { + $this->_usedProperties['proxy'] = true; + $this->proxy = $value['proxy']; + unset($value['proxy']); + } + + if (array_key_exists('no_proxy', $value)) { + $this->_usedProperties['noProxy'] = true; + $this->noProxy = $value['no_proxy']; + unset($value['no_proxy']); + } + + if (array_key_exists('timeout', $value)) { + $this->_usedProperties['timeout'] = true; + $this->timeout = $value['timeout']; + unset($value['timeout']); + } + + if (array_key_exists('max_duration', $value)) { + $this->_usedProperties['maxDuration'] = true; + $this->maxDuration = $value['max_duration']; + unset($value['max_duration']); + } + + if (array_key_exists('bindto', $value)) { + $this->_usedProperties['bindto'] = true; + $this->bindto = $value['bindto']; + unset($value['bindto']); + } + + if (array_key_exists('verify_peer', $value)) { + $this->_usedProperties['verifyPeer'] = true; + $this->verifyPeer = $value['verify_peer']; + unset($value['verify_peer']); + } + + if (array_key_exists('verify_host', $value)) { + $this->_usedProperties['verifyHost'] = true; + $this->verifyHost = $value['verify_host']; + unset($value['verify_host']); + } + + if (array_key_exists('cafile', $value)) { + $this->_usedProperties['cafile'] = true; + $this->cafile = $value['cafile']; + unset($value['cafile']); + } + + if (array_key_exists('capath', $value)) { + $this->_usedProperties['capath'] = true; + $this->capath = $value['capath']; + unset($value['capath']); + } + + if (array_key_exists('local_cert', $value)) { + $this->_usedProperties['localCert'] = true; + $this->localCert = $value['local_cert']; + unset($value['local_cert']); + } + + if (array_key_exists('local_pk', $value)) { + $this->_usedProperties['localPk'] = true; + $this->localPk = $value['local_pk']; + unset($value['local_pk']); + } + + if (array_key_exists('passphrase', $value)) { + $this->_usedProperties['passphrase'] = true; + $this->passphrase = $value['passphrase']; + unset($value['passphrase']); + } + + if (array_key_exists('ciphers', $value)) { + $this->_usedProperties['ciphers'] = true; + $this->ciphers = $value['ciphers']; + unset($value['ciphers']); + } + + if (array_key_exists('peer_fingerprint', $value)) { + $this->_usedProperties['peerFingerprint'] = true; + $this->peerFingerprint = new \Symfony\Config\Framework\HttpClient\ScopedClientConfig\PeerFingerprintConfig($value['peer_fingerprint']); + unset($value['peer_fingerprint']); + } + + if (array_key_exists('extra', $value)) { + $this->_usedProperties['extra'] = true; + $this->extra = $value['extra']; + unset($value['extra']); + } + + if (array_key_exists('rate_limiter', $value)) { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = $value['rate_limiter']; + unset($value['rate_limiter']); + } + + if (array_key_exists('retry_failed', $value)) { + $this->_usedProperties['retryFailed'] = true; + $this->retryFailed = \is_array($value['retry_failed']) ? new \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig($value['retry_failed']) : $value['retry_failed']; + unset($value['retry_failed']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['scope'])) { + $output['scope'] = $this->scope; + } + if (isset($this->_usedProperties['baseUri'])) { + $output['base_uri'] = $this->baseUri; + } + if (isset($this->_usedProperties['authBasic'])) { + $output['auth_basic'] = $this->authBasic; + } + if (isset($this->_usedProperties['authBearer'])) { + $output['auth_bearer'] = $this->authBearer; + } + if (isset($this->_usedProperties['authNtlm'])) { + $output['auth_ntlm'] = $this->authNtlm; + } + if (isset($this->_usedProperties['query'])) { + $output['query'] = $this->query; + } + if (isset($this->_usedProperties['headers'])) { + $output['headers'] = $this->headers; + } + if (isset($this->_usedProperties['maxRedirects'])) { + $output['max_redirects'] = $this->maxRedirects; + } + if (isset($this->_usedProperties['httpVersion'])) { + $output['http_version'] = $this->httpVersion; + } + if (isset($this->_usedProperties['resolve'])) { + $output['resolve'] = $this->resolve; + } + if (isset($this->_usedProperties['proxy'])) { + $output['proxy'] = $this->proxy; + } + if (isset($this->_usedProperties['noProxy'])) { + $output['no_proxy'] = $this->noProxy; + } + if (isset($this->_usedProperties['timeout'])) { + $output['timeout'] = $this->timeout; + } + if (isset($this->_usedProperties['maxDuration'])) { + $output['max_duration'] = $this->maxDuration; + } + if (isset($this->_usedProperties['bindto'])) { + $output['bindto'] = $this->bindto; + } + if (isset($this->_usedProperties['verifyPeer'])) { + $output['verify_peer'] = $this->verifyPeer; + } + if (isset($this->_usedProperties['verifyHost'])) { + $output['verify_host'] = $this->verifyHost; + } + if (isset($this->_usedProperties['cafile'])) { + $output['cafile'] = $this->cafile; + } + if (isset($this->_usedProperties['capath'])) { + $output['capath'] = $this->capath; + } + if (isset($this->_usedProperties['localCert'])) { + $output['local_cert'] = $this->localCert; + } + if (isset($this->_usedProperties['localPk'])) { + $output['local_pk'] = $this->localPk; + } + if (isset($this->_usedProperties['passphrase'])) { + $output['passphrase'] = $this->passphrase; + } + if (isset($this->_usedProperties['ciphers'])) { + $output['ciphers'] = $this->ciphers; + } + if (isset($this->_usedProperties['peerFingerprint'])) { + $output['peer_fingerprint'] = $this->peerFingerprint->toArray(); + } + if (isset($this->_usedProperties['extra'])) { + $output['extra'] = $this->extra; + } + if (isset($this->_usedProperties['rateLimiter'])) { + $output['rate_limiter'] = $this->rateLimiter; + } + if (isset($this->_usedProperties['retryFailed'])) { + $output['retry_failed'] = $this->retryFailed instanceof \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig ? $this->retryFailed->toArray() : $this->retryFailed; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/PeerFingerprintConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/PeerFingerprintConfig.php new file mode 100644 index 0000000..20cd203 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/PeerFingerprintConfig.php @@ -0,0 +1,101 @@ +_usedProperties['sha1'] = true; + $this->sha1 = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function pinsha256(mixed $value): static + { + $this->_usedProperties['pinsha256'] = true; + $this->pinsha256 = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function md5(mixed $value): static + { + $this->_usedProperties['md5'] = true; + $this->md5 = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('sha1', $value)) { + $this->_usedProperties['sha1'] = true; + $this->sha1 = $value['sha1']; + unset($value['sha1']); + } + + if (array_key_exists('pin-sha256', $value)) { + $this->_usedProperties['pinsha256'] = true; + $this->pinsha256 = $value['pin-sha256']; + unset($value['pin-sha256']); + } + + if (array_key_exists('md5', $value)) { + $this->_usedProperties['md5'] = true; + $this->md5 = $value['md5']; + unset($value['md5']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['sha1'])) { + $output['sha1'] = $this->sha1; + } + if (isset($this->_usedProperties['pinsha256'])) { + $output['pin-sha256'] = $this->pinsha256; + } + if (isset($this->_usedProperties['md5'])) { + $output['md5'] = $this->md5; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/RetryFailed/HttpCodeConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/RetryFailed/HttpCodeConfig.php new file mode 100644 index 0000000..309911a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/RetryFailed/HttpCodeConfig.php @@ -0,0 +1,75 @@ +_usedProperties['code'] = true; + $this->code = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function methods(ParamConfigurator|array $value): static + { + $this->_usedProperties['methods'] = true; + $this->methods = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('code', $value)) { + $this->_usedProperties['code'] = true; + $this->code = $value['code']; + unset($value['code']); + } + + if (array_key_exists('methods', $value)) { + $this->_usedProperties['methods'] = true; + $this->methods = $value['methods']; + unset($value['methods']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['code'])) { + $output['code'] = $this->code; + } + if (isset($this->_usedProperties['methods'])) { + $output['methods'] = $this->methods; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/RetryFailedConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/RetryFailedConfig.php new file mode 100644 index 0000000..150538f --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClient/ScopedClientConfig/RetryFailedConfig.php @@ -0,0 +1,233 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * service id to override the retry strategy + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function retryStrategy($value): static + { + $this->_usedProperties['retryStrategy'] = true; + $this->retryStrategy = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * A list of HTTP status code that triggers a retry + * @return \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig : static) + */ + public function httpCode(string $code, array $value = []): \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['httpCodes'] = true; + $this->httpCodes[$code] = $value; + + return $this; + } + + if (!isset($this->httpCodes[$code]) || !$this->httpCodes[$code] instanceof \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig) { + $this->_usedProperties['httpCodes'] = true; + $this->httpCodes[$code] = new \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "httpCode()" has already been initialized. You cannot pass values the second time you call httpCode().'); + } + + return $this->httpCodes[$code]; + } + + /** + * @default 3 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxRetries($value): static + { + $this->_usedProperties['maxRetries'] = true; + $this->maxRetries = $value; + + return $this; + } + + /** + * Time in ms to delay (or the initial value when multiplier is used) + * @default 1000 + * @param ParamConfigurator|int $value + * @return $this + */ + public function delay($value): static + { + $this->_usedProperties['delay'] = true; + $this->delay = $value; + + return $this; + } + + /** + * If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries) + * @default 2 + * @param ParamConfigurator|float $value + * @return $this + */ + public function multiplier($value): static + { + $this->_usedProperties['multiplier'] = true; + $this->multiplier = $value; + + return $this; + } + + /** + * Max time in ms that a retry should ever be delayed (0 = infinite) + * @default 0 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxDelay($value): static + { + $this->_usedProperties['maxDelay'] = true; + $this->maxDelay = $value; + + return $this; + } + + /** + * Randomness in percent (between 0 and 1) to apply to the delay + * @default 0.1 + * @param ParamConfigurator|float $value + * @return $this + */ + public function jitter($value): static + { + $this->_usedProperties['jitter'] = true; + $this->jitter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('retry_strategy', $value)) { + $this->_usedProperties['retryStrategy'] = true; + $this->retryStrategy = $value['retry_strategy']; + unset($value['retry_strategy']); + } + + if (array_key_exists('http_codes', $value)) { + $this->_usedProperties['httpCodes'] = true; + $this->httpCodes = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig($v) : $v, $value['http_codes']); + unset($value['http_codes']); + } + + if (array_key_exists('max_retries', $value)) { + $this->_usedProperties['maxRetries'] = true; + $this->maxRetries = $value['max_retries']; + unset($value['max_retries']); + } + + if (array_key_exists('delay', $value)) { + $this->_usedProperties['delay'] = true; + $this->delay = $value['delay']; + unset($value['delay']); + } + + if (array_key_exists('multiplier', $value)) { + $this->_usedProperties['multiplier'] = true; + $this->multiplier = $value['multiplier']; + unset($value['multiplier']); + } + + if (array_key_exists('max_delay', $value)) { + $this->_usedProperties['maxDelay'] = true; + $this->maxDelay = $value['max_delay']; + unset($value['max_delay']); + } + + if (array_key_exists('jitter', $value)) { + $this->_usedProperties['jitter'] = true; + $this->jitter = $value['jitter']; + unset($value['jitter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['retryStrategy'])) { + $output['retry_strategy'] = $this->retryStrategy; + } + if (isset($this->_usedProperties['httpCodes'])) { + $output['http_codes'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig ? $v->toArray() : $v, $this->httpCodes); + } + if (isset($this->_usedProperties['maxRetries'])) { + $output['max_retries'] = $this->maxRetries; + } + if (isset($this->_usedProperties['delay'])) { + $output['delay'] = $this->delay; + } + if (isset($this->_usedProperties['multiplier'])) { + $output['multiplier'] = $this->multiplier; + } + if (isset($this->_usedProperties['maxDelay'])) { + $output['max_delay'] = $this->maxDelay; + } + if (isset($this->_usedProperties['jitter'])) { + $output['jitter'] = $this->jitter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/HttpClientConfig.php b/var/cache/dev/Symfony/Config/Framework/HttpClientConfig.php new file mode 100644 index 0000000..8813306 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/HttpClientConfig.php @@ -0,0 +1,160 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * The maximum number of connections to a single host. + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxHostConnections($value): static + { + $this->_usedProperties['maxHostConnections'] = true; + $this->maxHostConnections = $value; + + return $this; + } + + public function defaultOptions(array $value = []): \Symfony\Config\Framework\HttpClient\DefaultOptionsConfig + { + if (null === $this->defaultOptions) { + $this->_usedProperties['defaultOptions'] = true; + $this->defaultOptions = new \Symfony\Config\Framework\HttpClient\DefaultOptionsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "defaultOptions()" has already been initialized. You cannot pass values the second time you call defaultOptions().'); + } + + return $this->defaultOptions; + } + + /** + * The id of the service that should generate mock responses. It should be either an invokable or an iterable. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function mockResponseFactory($value): static + { + $this->_usedProperties['mockResponseFactory'] = true; + $this->mockResponseFactory = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\HttpClient\ScopedClientConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HttpClient\ScopedClientConfig : static) + */ + public function scopedClient(string $name, mixed $value = []): \Symfony\Config\Framework\HttpClient\ScopedClientConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['scopedClients'] = true; + $this->scopedClients[$name] = $value; + + return $this; + } + + if (!isset($this->scopedClients[$name]) || !$this->scopedClients[$name] instanceof \Symfony\Config\Framework\HttpClient\ScopedClientConfig) { + $this->_usedProperties['scopedClients'] = true; + $this->scopedClients[$name] = new \Symfony\Config\Framework\HttpClient\ScopedClientConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "scopedClient()" has already been initialized. You cannot pass values the second time you call scopedClient().'); + } + + return $this->scopedClients[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('max_host_connections', $value)) { + $this->_usedProperties['maxHostConnections'] = true; + $this->maxHostConnections = $value['max_host_connections']; + unset($value['max_host_connections']); + } + + if (array_key_exists('default_options', $value)) { + $this->_usedProperties['defaultOptions'] = true; + $this->defaultOptions = new \Symfony\Config\Framework\HttpClient\DefaultOptionsConfig($value['default_options']); + unset($value['default_options']); + } + + if (array_key_exists('mock_response_factory', $value)) { + $this->_usedProperties['mockResponseFactory'] = true; + $this->mockResponseFactory = $value['mock_response_factory']; + unset($value['mock_response_factory']); + } + + if (array_key_exists('scoped_clients', $value)) { + $this->_usedProperties['scopedClients'] = true; + $this->scopedClients = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\HttpClient\ScopedClientConfig($v) : $v, $value['scoped_clients']); + unset($value['scoped_clients']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['maxHostConnections'])) { + $output['max_host_connections'] = $this->maxHostConnections; + } + if (isset($this->_usedProperties['defaultOptions'])) { + $output['default_options'] = $this->defaultOptions->toArray(); + } + if (isset($this->_usedProperties['mockResponseFactory'])) { + $output['mock_response_factory'] = $this->mockResponseFactory; + } + if (isset($this->_usedProperties['scopedClients'])) { + $output['scoped_clients'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\HttpClient\ScopedClientConfig ? $v->toArray() : $v, $this->scopedClients); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/LockConfig.php b/var/cache/dev/Symfony/Config/Framework/LockConfig.php new file mode 100644 index 0000000..93ec3cd --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/LockConfig.php @@ -0,0 +1,73 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @return $this + */ + public function resource(string $name, ParamConfigurator|string|array $value): static + { + $this->_usedProperties['resources'] = true; + $this->resources[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('resources', $value)) { + $this->_usedProperties['resources'] = true; + $this->resources = $value['resources']; + unset($value['resources']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['resources'])) { + $output['resources'] = $this->resources; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Mailer/EnvelopeConfig.php b/var/cache/dev/Symfony/Config/Framework/Mailer/EnvelopeConfig.php new file mode 100644 index 0000000..8e2229d --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Mailer/EnvelopeConfig.php @@ -0,0 +1,98 @@ +_usedProperties['sender'] = true; + $this->sender = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function recipients(ParamConfigurator|array $value): static + { + $this->_usedProperties['recipients'] = true; + $this->recipients = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function allowedRecipients(ParamConfigurator|array $value): static + { + $this->_usedProperties['allowedRecipients'] = true; + $this->allowedRecipients = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('sender', $value)) { + $this->_usedProperties['sender'] = true; + $this->sender = $value['sender']; + unset($value['sender']); + } + + if (array_key_exists('recipients', $value)) { + $this->_usedProperties['recipients'] = true; + $this->recipients = $value['recipients']; + unset($value['recipients']); + } + + if (array_key_exists('allowed_recipients', $value)) { + $this->_usedProperties['allowedRecipients'] = true; + $this->allowedRecipients = $value['allowed_recipients']; + unset($value['allowed_recipients']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['sender'])) { + $output['sender'] = $this->sender; + } + if (isset($this->_usedProperties['recipients'])) { + $output['recipients'] = $this->recipients; + } + if (isset($this->_usedProperties['allowedRecipients'])) { + $output['allowed_recipients'] = $this->allowedRecipients; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Mailer/HeaderConfig.php b/var/cache/dev/Symfony/Config/Framework/Mailer/HeaderConfig.php new file mode 100644 index 0000000..76eba10 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Mailer/HeaderConfig.php @@ -0,0 +1,53 @@ +_usedProperties['value'] = true; + $this->value = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('value', $value)) { + $this->_usedProperties['value'] = true; + $this->value = $value['value']; + unset($value['value']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['value'])) { + $output['value'] = $this->value; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/MailerConfig.php b/var/cache/dev/Symfony/Config/Framework/MailerConfig.php new file mode 100644 index 0000000..a6ead06 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/MailerConfig.php @@ -0,0 +1,183 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * The message bus to use. Defaults to the default bus if the Messenger component is installed. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function messageBus($value): static + { + $this->_usedProperties['messageBus'] = true; + $this->messageBus = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dsn($value): static + { + $this->_usedProperties['dsn'] = true; + $this->dsn = $value; + + return $this; + } + + /** + * @return $this + */ + public function transport(string $name, mixed $value): static + { + $this->_usedProperties['transports'] = true; + $this->transports[$name] = $value; + + return $this; + } + + /** + * Mailer Envelope configuration + */ + public function envelope(array $value = []): \Symfony\Config\Framework\Mailer\EnvelopeConfig + { + if (null === $this->envelope) { + $this->_usedProperties['envelope'] = true; + $this->envelope = new \Symfony\Config\Framework\Mailer\EnvelopeConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "envelope()" has already been initialized. You cannot pass values the second time you call envelope().'); + } + + return $this->envelope; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Mailer\HeaderConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Mailer\HeaderConfig : static) + */ + public function header(string $name, mixed $value = []): \Symfony\Config\Framework\Mailer\HeaderConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['headers'] = true; + $this->headers[$name] = $value; + + return $this; + } + + if (!isset($this->headers[$name]) || !$this->headers[$name] instanceof \Symfony\Config\Framework\Mailer\HeaderConfig) { + $this->_usedProperties['headers'] = true; + $this->headers[$name] = new \Symfony\Config\Framework\Mailer\HeaderConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "header()" has already been initialized. You cannot pass values the second time you call header().'); + } + + return $this->headers[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('message_bus', $value)) { + $this->_usedProperties['messageBus'] = true; + $this->messageBus = $value['message_bus']; + unset($value['message_bus']); + } + + if (array_key_exists('dsn', $value)) { + $this->_usedProperties['dsn'] = true; + $this->dsn = $value['dsn']; + unset($value['dsn']); + } + + if (array_key_exists('transports', $value)) { + $this->_usedProperties['transports'] = true; + $this->transports = $value['transports']; + unset($value['transports']); + } + + if (array_key_exists('envelope', $value)) { + $this->_usedProperties['envelope'] = true; + $this->envelope = new \Symfony\Config\Framework\Mailer\EnvelopeConfig($value['envelope']); + unset($value['envelope']); + } + + if (array_key_exists('headers', $value)) { + $this->_usedProperties['headers'] = true; + $this->headers = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Mailer\HeaderConfig($v) : $v, $value['headers']); + unset($value['headers']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['messageBus'])) { + $output['message_bus'] = $this->messageBus; + } + if (isset($this->_usedProperties['dsn'])) { + $output['dsn'] = $this->dsn; + } + if (isset($this->_usedProperties['transports'])) { + $output['transports'] = $this->transports; + } + if (isset($this->_usedProperties['envelope'])) { + $output['envelope'] = $this->envelope->toArray(); + } + if (isset($this->_usedProperties['headers'])) { + $output['headers'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Mailer\HeaderConfig ? $v->toArray() : $v, $this->headers); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig.php new file mode 100644 index 0000000..e30e364 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig.php @@ -0,0 +1,95 @@ +_usedProperties['defaultMiddleware'] = true; + $this->defaultMiddleware = $value; + + return $this; + } + + if (!$this->defaultMiddleware instanceof \Symfony\Config\Framework\Messenger\BusConfig\DefaultMiddlewareConfig) { + $this->_usedProperties['defaultMiddleware'] = true; + $this->defaultMiddleware = new \Symfony\Config\Framework\Messenger\BusConfig\DefaultMiddlewareConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "defaultMiddleware()" has already been initialized. You cannot pass values the second time you call defaultMiddleware().'); + } + + return $this->defaultMiddleware; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Messenger\BusConfig\MiddlewareConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Messenger\BusConfig\MiddlewareConfig : static) + */ + public function middleware(mixed $value = []): \Symfony\Config\Framework\Messenger\BusConfig\MiddlewareConfig|static + { + $this->_usedProperties['middleware'] = true; + if (!\is_array($value)) { + $this->middleware[] = $value; + + return $this; + } + + return $this->middleware[] = new \Symfony\Config\Framework\Messenger\BusConfig\MiddlewareConfig($value); + } + + public function __construct(array $value = []) + { + if (array_key_exists('default_middleware', $value)) { + $this->_usedProperties['defaultMiddleware'] = true; + $this->defaultMiddleware = \is_array($value['default_middleware']) ? new \Symfony\Config\Framework\Messenger\BusConfig\DefaultMiddlewareConfig($value['default_middleware']) : $value['default_middleware']; + unset($value['default_middleware']); + } + + if (array_key_exists('middleware', $value)) { + $this->_usedProperties['middleware'] = true; + $this->middleware = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Messenger\BusConfig\MiddlewareConfig($v) : $v, $value['middleware']); + unset($value['middleware']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['defaultMiddleware'])) { + $output['default_middleware'] = $this->defaultMiddleware instanceof \Symfony\Config\Framework\Messenger\BusConfig\DefaultMiddlewareConfig ? $this->defaultMiddleware->toArray() : $this->defaultMiddleware; + } + if (isset($this->_usedProperties['middleware'])) { + $output['middleware'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Messenger\BusConfig\MiddlewareConfig ? $v->toArray() : $v, $this->middleware); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig/DefaultMiddlewareConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig/DefaultMiddlewareConfig.php new file mode 100644 index 0000000..9eee221 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig/DefaultMiddlewareConfig.php @@ -0,0 +1,98 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowNoHandlers($value): static + { + $this->_usedProperties['allowNoHandlers'] = true; + $this->allowNoHandlers = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowNoSenders($value): static + { + $this->_usedProperties['allowNoSenders'] = true; + $this->allowNoSenders = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('allow_no_handlers', $value)) { + $this->_usedProperties['allowNoHandlers'] = true; + $this->allowNoHandlers = $value['allow_no_handlers']; + unset($value['allow_no_handlers']); + } + + if (array_key_exists('allow_no_senders', $value)) { + $this->_usedProperties['allowNoSenders'] = true; + $this->allowNoSenders = $value['allow_no_senders']; + unset($value['allow_no_senders']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['allowNoHandlers'])) { + $output['allow_no_handlers'] = $this->allowNoHandlers; + } + if (isset($this->_usedProperties['allowNoSenders'])) { + $output['allow_no_senders'] = $this->allowNoSenders; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig/MiddlewareConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig/MiddlewareConfig.php new file mode 100644 index 0000000..d96a5a5 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/BusConfig/MiddlewareConfig.php @@ -0,0 +1,75 @@ +_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function arguments(ParamConfigurator|array $value): static + { + $this->_usedProperties['arguments'] = true; + $this->arguments = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('arguments', $value)) { + $this->_usedProperties['arguments'] = true; + $this->arguments = $value['arguments']; + unset($value['arguments']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['arguments'])) { + $output['arguments'] = $this->arguments; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/RoutingConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/RoutingConfig.php new file mode 100644 index 0000000..0cebfbb --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/RoutingConfig.php @@ -0,0 +1,52 @@ + $value + * + * @return $this + */ + public function senders(ParamConfigurator|array $value): static + { + $this->_usedProperties['senders'] = true; + $this->senders = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('senders', $value)) { + $this->_usedProperties['senders'] = true; + $this->senders = $value['senders']; + unset($value['senders']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['senders'])) { + $output['senders'] = $this->senders; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/Serializer/SymfonySerializerConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/Serializer/SymfonySerializerConfig.php new file mode 100644 index 0000000..671e1f7 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/Serializer/SymfonySerializerConfig.php @@ -0,0 +1,74 @@ +_usedProperties['format'] = true; + $this->format = $value; + + return $this; + } + + /** + * @return $this + */ + public function context(string $name, mixed $value): static + { + $this->_usedProperties['context'] = true; + $this->context[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('format', $value)) { + $this->_usedProperties['format'] = true; + $this->format = $value['format']; + unset($value['format']); + } + + if (array_key_exists('context', $value)) { + $this->_usedProperties['context'] = true; + $this->context = $value['context']; + unset($value['context']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['format'])) { + $output['format'] = $this->format; + } + if (isset($this->_usedProperties['context'])) { + $output['context'] = $this->context; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/SerializerConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/SerializerConfig.php new file mode 100644 index 0000000..819bcab --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/SerializerConfig.php @@ -0,0 +1,80 @@ +_usedProperties['defaultSerializer'] = true; + $this->defaultSerializer = $value; + + return $this; + } + + /** + * @default {"format":"json","context":[]} + */ + public function symfonySerializer(array $value = []): \Symfony\Config\Framework\Messenger\Serializer\SymfonySerializerConfig + { + if (null === $this->symfonySerializer) { + $this->_usedProperties['symfonySerializer'] = true; + $this->symfonySerializer = new \Symfony\Config\Framework\Messenger\Serializer\SymfonySerializerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "symfonySerializer()" has already been initialized. You cannot pass values the second time you call symfonySerializer().'); + } + + return $this->symfonySerializer; + } + + public function __construct(array $value = []) + { + if (array_key_exists('default_serializer', $value)) { + $this->_usedProperties['defaultSerializer'] = true; + $this->defaultSerializer = $value['default_serializer']; + unset($value['default_serializer']); + } + + if (array_key_exists('symfony_serializer', $value)) { + $this->_usedProperties['symfonySerializer'] = true; + $this->symfonySerializer = new \Symfony\Config\Framework\Messenger\Serializer\SymfonySerializerConfig($value['symfony_serializer']); + unset($value['symfony_serializer']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['defaultSerializer'])) { + $output['default_serializer'] = $this->defaultSerializer; + } + if (isset($this->_usedProperties['symfonySerializer'])) { + $output['symfony_serializer'] = $this->symfonySerializer->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/TransportConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/TransportConfig.php new file mode 100644 index 0000000..a8bb3b6 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/TransportConfig.php @@ -0,0 +1,185 @@ +_usedProperties['dsn'] = true; + $this->dsn = $value; + + return $this; + } + + /** + * Service id of a custom serializer to use. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function serializer($value): static + { + $this->_usedProperties['serializer'] = true; + $this->serializer = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function options(ParamConfigurator|array $value): static + { + $this->_usedProperties['options'] = true; + $this->options = $value; + + return $this; + } + + /** + * Transport name to send failed messages to (after all retries have failed). + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureTransport($value): static + { + $this->_usedProperties['failureTransport'] = true; + $this->failureTransport = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @default {"service":null,"max_retries":3,"delay":1000,"multiplier":2,"max_delay":0,"jitter":0.1} + * @return \Symfony\Config\Framework\Messenger\TransportConfig\RetryStrategyConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Messenger\TransportConfig\RetryStrategyConfig : static) + */ + public function retryStrategy(mixed $value = []): \Symfony\Config\Framework\Messenger\TransportConfig\RetryStrategyConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['retryStrategy'] = true; + $this->retryStrategy = $value; + + return $this; + } + + if (!$this->retryStrategy instanceof \Symfony\Config\Framework\Messenger\TransportConfig\RetryStrategyConfig) { + $this->_usedProperties['retryStrategy'] = true; + $this->retryStrategy = new \Symfony\Config\Framework\Messenger\TransportConfig\RetryStrategyConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "retryStrategy()" has already been initialized. You cannot pass values the second time you call retryStrategy().'); + } + + return $this->retryStrategy; + } + + /** + * Rate limiter name to use when processing messages + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function rateLimiter($value): static + { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('dsn', $value)) { + $this->_usedProperties['dsn'] = true; + $this->dsn = $value['dsn']; + unset($value['dsn']); + } + + if (array_key_exists('serializer', $value)) { + $this->_usedProperties['serializer'] = true; + $this->serializer = $value['serializer']; + unset($value['serializer']); + } + + if (array_key_exists('options', $value)) { + $this->_usedProperties['options'] = true; + $this->options = $value['options']; + unset($value['options']); + } + + if (array_key_exists('failure_transport', $value)) { + $this->_usedProperties['failureTransport'] = true; + $this->failureTransport = $value['failure_transport']; + unset($value['failure_transport']); + } + + if (array_key_exists('retry_strategy', $value)) { + $this->_usedProperties['retryStrategy'] = true; + $this->retryStrategy = \is_array($value['retry_strategy']) ? new \Symfony\Config\Framework\Messenger\TransportConfig\RetryStrategyConfig($value['retry_strategy']) : $value['retry_strategy']; + unset($value['retry_strategy']); + } + + if (array_key_exists('rate_limiter', $value)) { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = $value['rate_limiter']; + unset($value['rate_limiter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['dsn'])) { + $output['dsn'] = $this->dsn; + } + if (isset($this->_usedProperties['serializer'])) { + $output['serializer'] = $this->serializer; + } + if (isset($this->_usedProperties['options'])) { + $output['options'] = $this->options; + } + if (isset($this->_usedProperties['failureTransport'])) { + $output['failure_transport'] = $this->failureTransport; + } + if (isset($this->_usedProperties['retryStrategy'])) { + $output['retry_strategy'] = $this->retryStrategy instanceof \Symfony\Config\Framework\Messenger\TransportConfig\RetryStrategyConfig ? $this->retryStrategy->toArray() : $this->retryStrategy; + } + if (isset($this->_usedProperties['rateLimiter'])) { + $output['rate_limiter'] = $this->rateLimiter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Messenger/TransportConfig/RetryStrategyConfig.php b/var/cache/dev/Symfony/Config/Framework/Messenger/TransportConfig/RetryStrategyConfig.php new file mode 100644 index 0000000..fc3c1eb --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Messenger/TransportConfig/RetryStrategyConfig.php @@ -0,0 +1,172 @@ +_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default 3 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxRetries($value): static + { + $this->_usedProperties['maxRetries'] = true; + $this->maxRetries = $value; + + return $this; + } + + /** + * Time in ms to delay (or the initial value when multiplier is used) + * @default 1000 + * @param ParamConfigurator|int $value + * @return $this + */ + public function delay($value): static + { + $this->_usedProperties['delay'] = true; + $this->delay = $value; + + return $this; + } + + /** + * If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)) + * @default 2 + * @param ParamConfigurator|float $value + * @return $this + */ + public function multiplier($value): static + { + $this->_usedProperties['multiplier'] = true; + $this->multiplier = $value; + + return $this; + } + + /** + * Max time in ms that a retry should ever be delayed (0 = infinite) + * @default 0 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxDelay($value): static + { + $this->_usedProperties['maxDelay'] = true; + $this->maxDelay = $value; + + return $this; + } + + /** + * Randomness to apply to the delay (between 0 and 1) + * @default 0.1 + * @param ParamConfigurator|float $value + * @return $this + */ + public function jitter($value): static + { + $this->_usedProperties['jitter'] = true; + $this->jitter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('max_retries', $value)) { + $this->_usedProperties['maxRetries'] = true; + $this->maxRetries = $value['max_retries']; + unset($value['max_retries']); + } + + if (array_key_exists('delay', $value)) { + $this->_usedProperties['delay'] = true; + $this->delay = $value['delay']; + unset($value['delay']); + } + + if (array_key_exists('multiplier', $value)) { + $this->_usedProperties['multiplier'] = true; + $this->multiplier = $value['multiplier']; + unset($value['multiplier']); + } + + if (array_key_exists('max_delay', $value)) { + $this->_usedProperties['maxDelay'] = true; + $this->maxDelay = $value['max_delay']; + unset($value['max_delay']); + } + + if (array_key_exists('jitter', $value)) { + $this->_usedProperties['jitter'] = true; + $this->jitter = $value['jitter']; + unset($value['jitter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['maxRetries'])) { + $output['max_retries'] = $this->maxRetries; + } + if (isset($this->_usedProperties['delay'])) { + $output['delay'] = $this->delay; + } + if (isset($this->_usedProperties['multiplier'])) { + $output['multiplier'] = $this->multiplier; + } + if (isset($this->_usedProperties['maxDelay'])) { + $output['max_delay'] = $this->maxDelay; + } + if (isset($this->_usedProperties['jitter'])) { + $output['jitter'] = $this->jitter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/MessengerConfig.php b/var/cache/dev/Symfony/Config/Framework/MessengerConfig.php new file mode 100644 index 0000000..8ebab97 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/MessengerConfig.php @@ -0,0 +1,247 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Messenger\RoutingConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Messenger\RoutingConfig : static) + */ + public function routing(string $message_class, array $value = []): \Symfony\Config\Framework\Messenger\RoutingConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['routing'] = true; + $this->routing[$message_class] = $value; + + return $this; + } + + if (!isset($this->routing[$message_class]) || !$this->routing[$message_class] instanceof \Symfony\Config\Framework\Messenger\RoutingConfig) { + $this->_usedProperties['routing'] = true; + $this->routing[$message_class] = new \Symfony\Config\Framework\Messenger\RoutingConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "routing()" has already been initialized. You cannot pass values the second time you call routing().'); + } + + return $this->routing[$message_class]; + } + + /** + * @default {"default_serializer":"messenger.transport.native_php_serializer","symfony_serializer":{"format":"json","context":[]}} + */ + public function serializer(array $value = []): \Symfony\Config\Framework\Messenger\SerializerConfig + { + if (null === $this->serializer) { + $this->_usedProperties['serializer'] = true; + $this->serializer = new \Symfony\Config\Framework\Messenger\SerializerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "serializer()" has already been initialized. You cannot pass values the second time you call serializer().'); + } + + return $this->serializer; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Messenger\TransportConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Messenger\TransportConfig : static) + */ + public function transport(string $name, string|array $value = []): \Symfony\Config\Framework\Messenger\TransportConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['transports'] = true; + $this->transports[$name] = $value; + + return $this; + } + + if (!isset($this->transports[$name]) || !$this->transports[$name] instanceof \Symfony\Config\Framework\Messenger\TransportConfig) { + $this->_usedProperties['transports'] = true; + $this->transports[$name] = new \Symfony\Config\Framework\Messenger\TransportConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "transport()" has already been initialized. You cannot pass values the second time you call transport().'); + } + + return $this->transports[$name]; + } + + /** + * Transport name to send failed messages to (after all retries have failed). + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureTransport($value): static + { + $this->_usedProperties['failureTransport'] = true; + $this->failureTransport = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function stopWorkerOnSignals(ParamConfigurator|array $value): static + { + $this->_usedProperties['stopWorkerOnSignals'] = true; + $this->stopWorkerOnSignals = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultBus($value): static + { + $this->_usedProperties['defaultBus'] = true; + $this->defaultBus = $value; + + return $this; + } + + /** + * @default {"messenger.bus.default":{"default_middleware":{"enabled":true,"allow_no_handlers":false,"allow_no_senders":true},"middleware":[]}} + */ + public function bus(string $name, array $value = []): \Symfony\Config\Framework\Messenger\BusConfig + { + if (!isset($this->buses[$name])) { + $this->_usedProperties['buses'] = true; + $this->buses[$name] = new \Symfony\Config\Framework\Messenger\BusConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "bus()" has already been initialized. You cannot pass values the second time you call bus().'); + } + + return $this->buses[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('routing', $value)) { + $this->_usedProperties['routing'] = true; + $this->routing = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Messenger\RoutingConfig($v) : $v, $value['routing']); + unset($value['routing']); + } + + if (array_key_exists('serializer', $value)) { + $this->_usedProperties['serializer'] = true; + $this->serializer = new \Symfony\Config\Framework\Messenger\SerializerConfig($value['serializer']); + unset($value['serializer']); + } + + if (array_key_exists('transports', $value)) { + $this->_usedProperties['transports'] = true; + $this->transports = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Messenger\TransportConfig($v) : $v, $value['transports']); + unset($value['transports']); + } + + if (array_key_exists('failure_transport', $value)) { + $this->_usedProperties['failureTransport'] = true; + $this->failureTransport = $value['failure_transport']; + unset($value['failure_transport']); + } + + if (array_key_exists('stop_worker_on_signals', $value)) { + $this->_usedProperties['stopWorkerOnSignals'] = true; + $this->stopWorkerOnSignals = $value['stop_worker_on_signals']; + unset($value['stop_worker_on_signals']); + } + + if (array_key_exists('default_bus', $value)) { + $this->_usedProperties['defaultBus'] = true; + $this->defaultBus = $value['default_bus']; + unset($value['default_bus']); + } + + if (array_key_exists('buses', $value)) { + $this->_usedProperties['buses'] = true; + $this->buses = array_map(fn ($v) => new \Symfony\Config\Framework\Messenger\BusConfig($v), $value['buses']); + unset($value['buses']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['routing'])) { + $output['routing'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Messenger\RoutingConfig ? $v->toArray() : $v, $this->routing); + } + if (isset($this->_usedProperties['serializer'])) { + $output['serializer'] = $this->serializer->toArray(); + } + if (isset($this->_usedProperties['transports'])) { + $output['transports'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Messenger\TransportConfig ? $v->toArray() : $v, $this->transports); + } + if (isset($this->_usedProperties['failureTransport'])) { + $output['failure_transport'] = $this->failureTransport; + } + if (isset($this->_usedProperties['stopWorkerOnSignals'])) { + $output['stop_worker_on_signals'] = $this->stopWorkerOnSignals; + } + if (isset($this->_usedProperties['defaultBus'])) { + $output['default_bus'] = $this->defaultBus; + } + if (isset($this->_usedProperties['buses'])) { + $output['buses'] = array_map(fn ($v) => $v->toArray(), $this->buses); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Notifier/AdminRecipientConfig.php b/var/cache/dev/Symfony/Config/Framework/Notifier/AdminRecipientConfig.php new file mode 100644 index 0000000..9ef5995 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Notifier/AdminRecipientConfig.php @@ -0,0 +1,74 @@ +_usedProperties['email'] = true; + $this->email = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function phone($value): static + { + $this->_usedProperties['phone'] = true; + $this->phone = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('email', $value)) { + $this->_usedProperties['email'] = true; + $this->email = $value['email']; + unset($value['email']); + } + + if (array_key_exists('phone', $value)) { + $this->_usedProperties['phone'] = true; + $this->phone = $value['phone']; + unset($value['phone']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['email'])) { + $output['email'] = $this->email; + } + if (isset($this->_usedProperties['phone'])) { + $output['phone'] = $this->phone; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/NotifierConfig.php b/var/cache/dev/Symfony/Config/Framework/NotifierConfig.php new file mode 100644 index 0000000..a04aa3a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/NotifierConfig.php @@ -0,0 +1,181 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * The message bus to use. Defaults to the default bus if the Messenger component is installed. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function messageBus($value): static + { + $this->_usedProperties['messageBus'] = true; + $this->messageBus = $value; + + return $this; + } + + /** + * @return $this + */ + public function chatterTransport(string $name, mixed $value): static + { + $this->_usedProperties['chatterTransports'] = true; + $this->chatterTransports[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function texterTransport(string $name, mixed $value): static + { + $this->_usedProperties['texterTransports'] = true; + $this->texterTransports[$name] = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function notificationOnFailedMessages($value): static + { + $this->_usedProperties['notificationOnFailedMessages'] = true; + $this->notificationOnFailedMessages = $value; + + return $this; + } + + /** + * @return $this + */ + public function channelPolicy(string $name, ParamConfigurator|string|array $value): static + { + $this->_usedProperties['channelPolicy'] = true; + $this->channelPolicy[$name] = $value; + + return $this; + } + + public function adminRecipient(array $value = []): \Symfony\Config\Framework\Notifier\AdminRecipientConfig + { + $this->_usedProperties['adminRecipients'] = true; + + return $this->adminRecipients[] = new \Symfony\Config\Framework\Notifier\AdminRecipientConfig($value); + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('message_bus', $value)) { + $this->_usedProperties['messageBus'] = true; + $this->messageBus = $value['message_bus']; + unset($value['message_bus']); + } + + if (array_key_exists('chatter_transports', $value)) { + $this->_usedProperties['chatterTransports'] = true; + $this->chatterTransports = $value['chatter_transports']; + unset($value['chatter_transports']); + } + + if (array_key_exists('texter_transports', $value)) { + $this->_usedProperties['texterTransports'] = true; + $this->texterTransports = $value['texter_transports']; + unset($value['texter_transports']); + } + + if (array_key_exists('notification_on_failed_messages', $value)) { + $this->_usedProperties['notificationOnFailedMessages'] = true; + $this->notificationOnFailedMessages = $value['notification_on_failed_messages']; + unset($value['notification_on_failed_messages']); + } + + if (array_key_exists('channel_policy', $value)) { + $this->_usedProperties['channelPolicy'] = true; + $this->channelPolicy = $value['channel_policy']; + unset($value['channel_policy']); + } + + if (array_key_exists('admin_recipients', $value)) { + $this->_usedProperties['adminRecipients'] = true; + $this->adminRecipients = array_map(fn ($v) => new \Symfony\Config\Framework\Notifier\AdminRecipientConfig($v), $value['admin_recipients']); + unset($value['admin_recipients']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['messageBus'])) { + $output['message_bus'] = $this->messageBus; + } + if (isset($this->_usedProperties['chatterTransports'])) { + $output['chatter_transports'] = $this->chatterTransports; + } + if (isset($this->_usedProperties['texterTransports'])) { + $output['texter_transports'] = $this->texterTransports; + } + if (isset($this->_usedProperties['notificationOnFailedMessages'])) { + $output['notification_on_failed_messages'] = $this->notificationOnFailedMessages; + } + if (isset($this->_usedProperties['channelPolicy'])) { + $output['channel_policy'] = $this->channelPolicy; + } + if (isset($this->_usedProperties['adminRecipients'])) { + $output['admin_recipients'] = array_map(fn ($v) => $v->toArray(), $this->adminRecipients); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/PhpErrorsConfig.php b/var/cache/dev/Symfony/Config/Framework/PhpErrorsConfig.php new file mode 100644 index 0000000..3c7d0b7 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/PhpErrorsConfig.php @@ -0,0 +1,79 @@ +_usedProperties['log'] = true; + $this->log = $value; + + return $this; + } + + /** + * Throw PHP errors as \ErrorException instances. + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function throw($value): static + { + $this->_usedProperties['throw'] = true; + $this->throw = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('log', $value)) { + $this->_usedProperties['log'] = true; + $this->log = $value['log']; + unset($value['log']); + } + + if (array_key_exists('throw', $value)) { + $this->_usedProperties['throw'] = true; + $this->throw = $value['throw']; + unset($value['throw']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['log'])) { + $output['log'] = $this->log; + } + if (isset($this->_usedProperties['throw'])) { + $output['throw'] = $this->throw; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/ProfilerConfig.php b/var/cache/dev/Symfony/Config/Framework/ProfilerConfig.php new file mode 100644 index 0000000..890c3bc --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/ProfilerConfig.php @@ -0,0 +1,192 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function collect($value): static + { + $this->_usedProperties['collect'] = true; + $this->collect = $value; + + return $this; + } + + /** + * The name of the parameter to use to enable or disable collection on a per request basis + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function collectParameter($value): static + { + $this->_usedProperties['collectParameter'] = true; + $this->collectParameter = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function onlyExceptions($value): static + { + $this->_usedProperties['onlyExceptions'] = true; + $this->onlyExceptions = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function onlyMainRequests($value): static + { + $this->_usedProperties['onlyMainRequests'] = true; + $this->onlyMainRequests = $value; + + return $this; + } + + /** + * @default 'file:%kernel.cache_dir%/profiler' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dsn($value): static + { + $this->_usedProperties['dsn'] = true; + $this->dsn = $value; + + return $this; + } + + /** + * Enables the serializer data collector and profiler panel + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function collectSerializerData($value): static + { + $this->_usedProperties['collectSerializerData'] = true; + $this->collectSerializerData = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('collect', $value)) { + $this->_usedProperties['collect'] = true; + $this->collect = $value['collect']; + unset($value['collect']); + } + + if (array_key_exists('collect_parameter', $value)) { + $this->_usedProperties['collectParameter'] = true; + $this->collectParameter = $value['collect_parameter']; + unset($value['collect_parameter']); + } + + if (array_key_exists('only_exceptions', $value)) { + $this->_usedProperties['onlyExceptions'] = true; + $this->onlyExceptions = $value['only_exceptions']; + unset($value['only_exceptions']); + } + + if (array_key_exists('only_main_requests', $value)) { + $this->_usedProperties['onlyMainRequests'] = true; + $this->onlyMainRequests = $value['only_main_requests']; + unset($value['only_main_requests']); + } + + if (array_key_exists('dsn', $value)) { + $this->_usedProperties['dsn'] = true; + $this->dsn = $value['dsn']; + unset($value['dsn']); + } + + if (array_key_exists('collect_serializer_data', $value)) { + $this->_usedProperties['collectSerializerData'] = true; + $this->collectSerializerData = $value['collect_serializer_data']; + unset($value['collect_serializer_data']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['collect'])) { + $output['collect'] = $this->collect; + } + if (isset($this->_usedProperties['collectParameter'])) { + $output['collect_parameter'] = $this->collectParameter; + } + if (isset($this->_usedProperties['onlyExceptions'])) { + $output['only_exceptions'] = $this->onlyExceptions; + } + if (isset($this->_usedProperties['onlyMainRequests'])) { + $output['only_main_requests'] = $this->onlyMainRequests; + } + if (isset($this->_usedProperties['dsn'])) { + $output['dsn'] = $this->dsn; + } + if (isset($this->_usedProperties['collectSerializerData'])) { + $output['collect_serializer_data'] = $this->collectSerializerData; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/PropertyAccessConfig.php b/var/cache/dev/Symfony/Config/Framework/PropertyAccessConfig.php new file mode 100644 index 0000000..f63d95e --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/PropertyAccessConfig.php @@ -0,0 +1,167 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function magicCall($value): static + { + $this->_usedProperties['magicCall'] = true; + $this->magicCall = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function magicGet($value): static + { + $this->_usedProperties['magicGet'] = true; + $this->magicGet = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function magicSet($value): static + { + $this->_usedProperties['magicSet'] = true; + $this->magicSet = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function throwExceptionOnInvalidIndex($value): static + { + $this->_usedProperties['throwExceptionOnInvalidIndex'] = true; + $this->throwExceptionOnInvalidIndex = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function throwExceptionOnInvalidPropertyPath($value): static + { + $this->_usedProperties['throwExceptionOnInvalidPropertyPath'] = true; + $this->throwExceptionOnInvalidPropertyPath = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('magic_call', $value)) { + $this->_usedProperties['magicCall'] = true; + $this->magicCall = $value['magic_call']; + unset($value['magic_call']); + } + + if (array_key_exists('magic_get', $value)) { + $this->_usedProperties['magicGet'] = true; + $this->magicGet = $value['magic_get']; + unset($value['magic_get']); + } + + if (array_key_exists('magic_set', $value)) { + $this->_usedProperties['magicSet'] = true; + $this->magicSet = $value['magic_set']; + unset($value['magic_set']); + } + + if (array_key_exists('throw_exception_on_invalid_index', $value)) { + $this->_usedProperties['throwExceptionOnInvalidIndex'] = true; + $this->throwExceptionOnInvalidIndex = $value['throw_exception_on_invalid_index']; + unset($value['throw_exception_on_invalid_index']); + } + + if (array_key_exists('throw_exception_on_invalid_property_path', $value)) { + $this->_usedProperties['throwExceptionOnInvalidPropertyPath'] = true; + $this->throwExceptionOnInvalidPropertyPath = $value['throw_exception_on_invalid_property_path']; + unset($value['throw_exception_on_invalid_property_path']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['magicCall'])) { + $output['magic_call'] = $this->magicCall; + } + if (isset($this->_usedProperties['magicGet'])) { + $output['magic_get'] = $this->magicGet; + } + if (isset($this->_usedProperties['magicSet'])) { + $output['magic_set'] = $this->magicSet; + } + if (isset($this->_usedProperties['throwExceptionOnInvalidIndex'])) { + $output['throw_exception_on_invalid_index'] = $this->throwExceptionOnInvalidIndex; + } + if (isset($this->_usedProperties['throwExceptionOnInvalidPropertyPath'])) { + $output['throw_exception_on_invalid_property_path'] = $this->throwExceptionOnInvalidPropertyPath; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/PropertyInfoConfig.php b/var/cache/dev/Symfony/Config/Framework/PropertyInfoConfig.php new file mode 100644 index 0000000..17f01b6 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/PropertyInfoConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/RateLimiter/LimiterConfig.php b/var/cache/dev/Symfony/Config/Framework/RateLimiter/LimiterConfig.php new file mode 100644 index 0000000..ab6983e --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/RateLimiter/LimiterConfig.php @@ -0,0 +1,200 @@ +_usedProperties['lockFactory'] = true; + $this->lockFactory = $value; + + return $this; + } + + /** + * The cache pool to use for storing the current limiter state + * @default 'cache.rate_limiter' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cachePool($value): static + { + $this->_usedProperties['cachePool'] = true; + $this->cachePool = $value; + + return $this; + } + + /** + * The service ID of a custom storage implementation, this precedes any configured "cache_pool" + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function storageService($value): static + { + $this->_usedProperties['storageService'] = true; + $this->storageService = $value; + + return $this; + } + + /** + * The algorithm to be used by this limiter + * @default null + * @param ParamConfigurator|'fixed_window'|'token_bucket'|'sliding_window'|'no_limit' $value + * @return $this + */ + public function policy($value): static + { + $this->_usedProperties['policy'] = true; + $this->policy = $value; + + return $this; + } + + /** + * The maximum allowed hits in a fixed interval or burst + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function limit($value): static + { + $this->_usedProperties['limit'] = true; + $this->limit = $value; + + return $this; + } + + /** + * Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function interval($value): static + { + $this->_usedProperties['interval'] = true; + $this->interval = $value; + + return $this; + } + + /** + * Configures the fill rate if "policy" is set to "token_bucket" + */ + public function rate(array $value = []): \Symfony\Config\Framework\RateLimiter\LimiterConfig\RateConfig + { + if (null === $this->rate) { + $this->_usedProperties['rate'] = true; + $this->rate = new \Symfony\Config\Framework\RateLimiter\LimiterConfig\RateConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "rate()" has already been initialized. You cannot pass values the second time you call rate().'); + } + + return $this->rate; + } + + public function __construct(array $value = []) + { + if (array_key_exists('lock_factory', $value)) { + $this->_usedProperties['lockFactory'] = true; + $this->lockFactory = $value['lock_factory']; + unset($value['lock_factory']); + } + + if (array_key_exists('cache_pool', $value)) { + $this->_usedProperties['cachePool'] = true; + $this->cachePool = $value['cache_pool']; + unset($value['cache_pool']); + } + + if (array_key_exists('storage_service', $value)) { + $this->_usedProperties['storageService'] = true; + $this->storageService = $value['storage_service']; + unset($value['storage_service']); + } + + if (array_key_exists('policy', $value)) { + $this->_usedProperties['policy'] = true; + $this->policy = $value['policy']; + unset($value['policy']); + } + + if (array_key_exists('limit', $value)) { + $this->_usedProperties['limit'] = true; + $this->limit = $value['limit']; + unset($value['limit']); + } + + if (array_key_exists('interval', $value)) { + $this->_usedProperties['interval'] = true; + $this->interval = $value['interval']; + unset($value['interval']); + } + + if (array_key_exists('rate', $value)) { + $this->_usedProperties['rate'] = true; + $this->rate = new \Symfony\Config\Framework\RateLimiter\LimiterConfig\RateConfig($value['rate']); + unset($value['rate']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['lockFactory'])) { + $output['lock_factory'] = $this->lockFactory; + } + if (isset($this->_usedProperties['cachePool'])) { + $output['cache_pool'] = $this->cachePool; + } + if (isset($this->_usedProperties['storageService'])) { + $output['storage_service'] = $this->storageService; + } + if (isset($this->_usedProperties['policy'])) { + $output['policy'] = $this->policy; + } + if (isset($this->_usedProperties['limit'])) { + $output['limit'] = $this->limit; + } + if (isset($this->_usedProperties['interval'])) { + $output['interval'] = $this->interval; + } + if (isset($this->_usedProperties['rate'])) { + $output['rate'] = $this->rate->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/RateLimiter/LimiterConfig/RateConfig.php b/var/cache/dev/Symfony/Config/Framework/RateLimiter/LimiterConfig/RateConfig.php new file mode 100644 index 0000000..efc1456 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/RateLimiter/LimiterConfig/RateConfig.php @@ -0,0 +1,77 @@ +_usedProperties['interval'] = true; + $this->interval = $value; + + return $this; + } + + /** + * Amount of tokens to add each interval + * @default 1 + * @param ParamConfigurator|int $value + * @return $this + */ + public function amount($value): static + { + $this->_usedProperties['amount'] = true; + $this->amount = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('interval', $value)) { + $this->_usedProperties['interval'] = true; + $this->interval = $value['interval']; + unset($value['interval']); + } + + if (array_key_exists('amount', $value)) { + $this->_usedProperties['amount'] = true; + $this->amount = $value['amount']; + unset($value['amount']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['interval'])) { + $output['interval'] = $this->interval; + } + if (isset($this->_usedProperties['amount'])) { + $output['amount'] = $this->amount; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/RateLimiterConfig.php b/var/cache/dev/Symfony/Config/Framework/RateLimiterConfig.php new file mode 100644 index 0000000..f5c7fc1 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/RateLimiterConfig.php @@ -0,0 +1,76 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function limiter(string $name, array $value = []): \Symfony\Config\Framework\RateLimiter\LimiterConfig + { + if (!isset($this->limiters[$name])) { + $this->_usedProperties['limiters'] = true; + $this->limiters[$name] = new \Symfony\Config\Framework\RateLimiter\LimiterConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "limiter()" has already been initialized. You cannot pass values the second time you call limiter().'); + } + + return $this->limiters[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('limiters', $value)) { + $this->_usedProperties['limiters'] = true; + $this->limiters = array_map(fn ($v) => new \Symfony\Config\Framework\RateLimiter\LimiterConfig($v), $value['limiters']); + unset($value['limiters']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['limiters'])) { + $output['limiters'] = array_map(fn ($v) => $v->toArray(), $this->limiters); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/RemoteeventConfig.php b/var/cache/dev/Symfony/Config/Framework/RemoteeventConfig.php new file mode 100644 index 0000000..2b8f3a2 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/RemoteeventConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/RequestConfig.php b/var/cache/dev/Symfony/Config/Framework/RequestConfig.php new file mode 100644 index 0000000..178ca44 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/RequestConfig.php @@ -0,0 +1,73 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @return $this + */ + public function format(string $name, mixed $value): static + { + $this->_usedProperties['formats'] = true; + $this->formats[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('formats', $value)) { + $this->_usedProperties['formats'] = true; + $this->formats = $value['formats']; + unset($value['formats']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['formats'])) { + $output['formats'] = $this->formats; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/RouterConfig.php b/var/cache/dev/Symfony/Config/Framework/RouterConfig.php new file mode 100644 index 0000000..8e40ad2 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/RouterConfig.php @@ -0,0 +1,242 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function resource($value): static + { + $this->_usedProperties['resource'] = true; + $this->resource = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function type($value): static + { + $this->_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default '%kernel.build_dir%' + * @param ParamConfigurator|mixed $value + * @deprecated Setting the "router.cache_dir" configuration option is deprecated. It will be removed in version 8.0. + * @return $this + */ + public function cacheDir($value): static + { + $this->_usedProperties['cacheDir'] = true; + $this->cacheDir = $value; + + return $this; + } + + /** + * The default URI used to generate URLs in a non-HTTP context + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultUri($value): static + { + $this->_usedProperties['defaultUri'] = true; + $this->defaultUri = $value; + + return $this; + } + + /** + * @default 80 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function httpPort($value): static + { + $this->_usedProperties['httpPort'] = true; + $this->httpPort = $value; + + return $this; + } + + /** + * @default 443 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function httpsPort($value): static + { + $this->_usedProperties['httpsPort'] = true; + $this->httpsPort = $value; + + return $this; + } + + /** + * set to true to throw an exception when a parameter does not match the requirements + set to false to disable exceptions when a parameter does not match the requirements (and return null instead) + set to null to disable parameter checks against requirements + 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production + * @default true + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function strictRequirements($value): static + { + $this->_usedProperties['strictRequirements'] = true; + $this->strictRequirements = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function utf8($value): static + { + $this->_usedProperties['utf8'] = true; + $this->utf8 = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('resource', $value)) { + $this->_usedProperties['resource'] = true; + $this->resource = $value['resource']; + unset($value['resource']); + } + + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('cache_dir', $value)) { + $this->_usedProperties['cacheDir'] = true; + $this->cacheDir = $value['cache_dir']; + unset($value['cache_dir']); + } + + if (array_key_exists('default_uri', $value)) { + $this->_usedProperties['defaultUri'] = true; + $this->defaultUri = $value['default_uri']; + unset($value['default_uri']); + } + + if (array_key_exists('http_port', $value)) { + $this->_usedProperties['httpPort'] = true; + $this->httpPort = $value['http_port']; + unset($value['http_port']); + } + + if (array_key_exists('https_port', $value)) { + $this->_usedProperties['httpsPort'] = true; + $this->httpsPort = $value['https_port']; + unset($value['https_port']); + } + + if (array_key_exists('strict_requirements', $value)) { + $this->_usedProperties['strictRequirements'] = true; + $this->strictRequirements = $value['strict_requirements']; + unset($value['strict_requirements']); + } + + if (array_key_exists('utf8', $value)) { + $this->_usedProperties['utf8'] = true; + $this->utf8 = $value['utf8']; + unset($value['utf8']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['resource'])) { + $output['resource'] = $this->resource; + } + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['cacheDir'])) { + $output['cache_dir'] = $this->cacheDir; + } + if (isset($this->_usedProperties['defaultUri'])) { + $output['default_uri'] = $this->defaultUri; + } + if (isset($this->_usedProperties['httpPort'])) { + $output['http_port'] = $this->httpPort; + } + if (isset($this->_usedProperties['httpsPort'])) { + $output['https_port'] = $this->httpsPort; + } + if (isset($this->_usedProperties['strictRequirements'])) { + $output['strict_requirements'] = $this->strictRequirements; + } + if (isset($this->_usedProperties['utf8'])) { + $output['utf8'] = $this->utf8; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/SchedulerConfig.php b/var/cache/dev/Symfony/Config/Framework/SchedulerConfig.php new file mode 100644 index 0000000..2f330e1 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/SchedulerConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/SecretsConfig.php b/var/cache/dev/Symfony/Config/Framework/SecretsConfig.php new file mode 100644 index 0000000..d4ca653 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/SecretsConfig.php @@ -0,0 +1,121 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default '%kernel.project_dir%/config/secrets/%kernel.runtime_environment%' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function vaultDirectory($value): static + { + $this->_usedProperties['vaultDirectory'] = true; + $this->vaultDirectory = $value; + + return $this; + } + + /** + * @default '%kernel.project_dir%/.env.%kernel.environment%.local' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function localDotenvFile($value): static + { + $this->_usedProperties['localDotenvFile'] = true; + $this->localDotenvFile = $value; + + return $this; + } + + /** + * @default 'base64:default::SYMFONY_DECRYPTION_SECRET' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function decryptionEnvVar($value): static + { + $this->_usedProperties['decryptionEnvVar'] = true; + $this->decryptionEnvVar = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('vault_directory', $value)) { + $this->_usedProperties['vaultDirectory'] = true; + $this->vaultDirectory = $value['vault_directory']; + unset($value['vault_directory']); + } + + if (array_key_exists('local_dotenv_file', $value)) { + $this->_usedProperties['localDotenvFile'] = true; + $this->localDotenvFile = $value['local_dotenv_file']; + unset($value['local_dotenv_file']); + } + + if (array_key_exists('decryption_env_var', $value)) { + $this->_usedProperties['decryptionEnvVar'] = true; + $this->decryptionEnvVar = $value['decryption_env_var']; + unset($value['decryption_env_var']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['vaultDirectory'])) { + $output['vault_directory'] = $this->vaultDirectory; + } + if (isset($this->_usedProperties['localDotenvFile'])) { + $output['local_dotenv_file'] = $this->localDotenvFile; + } + if (isset($this->_usedProperties['decryptionEnvVar'])) { + $output['decryption_env_var'] = $this->decryptionEnvVar; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/SemaphoreConfig.php b/var/cache/dev/Symfony/Config/Framework/SemaphoreConfig.php new file mode 100644 index 0000000..f5c8886 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/SemaphoreConfig.php @@ -0,0 +1,73 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @return $this + */ + public function resource(string $name, mixed $value): static + { + $this->_usedProperties['resources'] = true; + $this->resources[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('resources', $value)) { + $this->_usedProperties['resources'] = true; + $this->resources = $value['resources']; + unset($value['resources']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['resources'])) { + $output['resources'] = $this->resources; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Serializer/MappingConfig.php b/var/cache/dev/Symfony/Config/Framework/Serializer/MappingConfig.php new file mode 100644 index 0000000..10d5a45 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Serializer/MappingConfig.php @@ -0,0 +1,52 @@ + $value + * + * @return $this + */ + public function paths(ParamConfigurator|array $value): static + { + $this->_usedProperties['paths'] = true; + $this->paths = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('paths', $value)) { + $this->_usedProperties['paths'] = true; + $this->paths = $value['paths']; + unset($value['paths']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['paths'])) { + $output['paths'] = $this->paths; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/SerializerConfig.php b/var/cache/dev/Symfony/Config/Framework/SerializerConfig.php new file mode 100644 index 0000000..e5a4b5e --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/SerializerConfig.php @@ -0,0 +1,194 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enableAttributes($value): static + { + $this->_usedProperties['enableAttributes'] = true; + $this->enableAttributes = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function nameConverter($value): static + { + $this->_usedProperties['nameConverter'] = true; + $this->nameConverter = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function circularReferenceHandler($value): static + { + $this->_usedProperties['circularReferenceHandler'] = true; + $this->circularReferenceHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function maxDepthHandler($value): static + { + $this->_usedProperties['maxDepthHandler'] = true; + $this->maxDepthHandler = $value; + + return $this; + } + + /** + * @default {"paths":[]} + */ + public function mapping(array $value = []): \Symfony\Config\Framework\Serializer\MappingConfig + { + if (null === $this->mapping) { + $this->_usedProperties['mapping'] = true; + $this->mapping = new \Symfony\Config\Framework\Serializer\MappingConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "mapping()" has already been initialized. You cannot pass values the second time you call mapping().'); + } + + return $this->mapping; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function defaultContext(ParamConfigurator|array $value): static + { + $this->_usedProperties['defaultContext'] = true; + $this->defaultContext = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('enable_attributes', $value)) { + $this->_usedProperties['enableAttributes'] = true; + $this->enableAttributes = $value['enable_attributes']; + unset($value['enable_attributes']); + } + + if (array_key_exists('name_converter', $value)) { + $this->_usedProperties['nameConverter'] = true; + $this->nameConverter = $value['name_converter']; + unset($value['name_converter']); + } + + if (array_key_exists('circular_reference_handler', $value)) { + $this->_usedProperties['circularReferenceHandler'] = true; + $this->circularReferenceHandler = $value['circular_reference_handler']; + unset($value['circular_reference_handler']); + } + + if (array_key_exists('max_depth_handler', $value)) { + $this->_usedProperties['maxDepthHandler'] = true; + $this->maxDepthHandler = $value['max_depth_handler']; + unset($value['max_depth_handler']); + } + + if (array_key_exists('mapping', $value)) { + $this->_usedProperties['mapping'] = true; + $this->mapping = new \Symfony\Config\Framework\Serializer\MappingConfig($value['mapping']); + unset($value['mapping']); + } + + if (array_key_exists('default_context', $value)) { + $this->_usedProperties['defaultContext'] = true; + $this->defaultContext = $value['default_context']; + unset($value['default_context']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['enableAttributes'])) { + $output['enable_attributes'] = $this->enableAttributes; + } + if (isset($this->_usedProperties['nameConverter'])) { + $output['name_converter'] = $this->nameConverter; + } + if (isset($this->_usedProperties['circularReferenceHandler'])) { + $output['circular_reference_handler'] = $this->circularReferenceHandler; + } + if (isset($this->_usedProperties['maxDepthHandler'])) { + $output['max_depth_handler'] = $this->maxDepthHandler; + } + if (isset($this->_usedProperties['mapping'])) { + $output['mapping'] = $this->mapping->toArray(); + } + if (isset($this->_usedProperties['defaultContext'])) { + $output['default_context'] = $this->defaultContext; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/SessionConfig.php b/var/cache/dev/Symfony/Config/Framework/SessionConfig.php new file mode 100644 index 0000000..ca20954 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/SessionConfig.php @@ -0,0 +1,446 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default 'session.storage.factory.native' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function storageFactoryId($value): static + { + $this->_usedProperties['storageFactoryId'] = true; + $this->storageFactoryId = $value; + + return $this; + } + + /** + * Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function handlerId($value): static + { + $this->_usedProperties['handlerId'] = true; + $this->handlerId = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function name($value): static + { + $this->_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cookieLifetime($value): static + { + $this->_usedProperties['cookieLifetime'] = true; + $this->cookieLifetime = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cookiePath($value): static + { + $this->_usedProperties['cookiePath'] = true; + $this->cookiePath = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cookieDomain($value): static + { + $this->_usedProperties['cookieDomain'] = true; + $this->cookieDomain = $value; + + return $this; + } + + /** + * @default 'auto' + * @param ParamConfigurator|true|false|'auto' $value + * @return $this + */ + public function cookieSecure($value): static + { + $this->_usedProperties['cookieSecure'] = true; + $this->cookieSecure = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function cookieHttponly($value): static + { + $this->_usedProperties['cookieHttponly'] = true; + $this->cookieHttponly = $value; + + return $this; + } + + /** + * @default 'lax' + * @param ParamConfigurator|NULL|'lax'|'strict'|'none' $value + * @return $this + */ + public function cookieSamesite($value): static + { + $this->_usedProperties['cookieSamesite'] = true; + $this->cookieSamesite = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useCookies($value): static + { + $this->_usedProperties['useCookies'] = true; + $this->useCookies = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function gcDivisor($value): static + { + $this->_usedProperties['gcDivisor'] = true; + $this->gcDivisor = $value; + + return $this; + } + + /** + * @default 1 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function gcProbability($value): static + { + $this->_usedProperties['gcProbability'] = true; + $this->gcProbability = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function gcMaxlifetime($value): static + { + $this->_usedProperties['gcMaxlifetime'] = true; + $this->gcMaxlifetime = $value; + + return $this; + } + + /** + * Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function savePath($value): static + { + $this->_usedProperties['savePath'] = true; + $this->savePath = $value; + + return $this; + } + + /** + * seconds to wait between 2 session metadata updates + * @default 0 + * @param ParamConfigurator|int $value + * @return $this + */ + public function metadataUpdateThreshold($value): static + { + $this->_usedProperties['metadataUpdateThreshold'] = true; + $this->metadataUpdateThreshold = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function sidLength($value): static + { + $this->_usedProperties['sidLength'] = true; + $this->sidLength = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function sidBitsPerCharacter($value): static + { + $this->_usedProperties['sidBitsPerCharacter'] = true; + $this->sidBitsPerCharacter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('storage_factory_id', $value)) { + $this->_usedProperties['storageFactoryId'] = true; + $this->storageFactoryId = $value['storage_factory_id']; + unset($value['storage_factory_id']); + } + + if (array_key_exists('handler_id', $value)) { + $this->_usedProperties['handlerId'] = true; + $this->handlerId = $value['handler_id']; + unset($value['handler_id']); + } + + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if (array_key_exists('cookie_lifetime', $value)) { + $this->_usedProperties['cookieLifetime'] = true; + $this->cookieLifetime = $value['cookie_lifetime']; + unset($value['cookie_lifetime']); + } + + if (array_key_exists('cookie_path', $value)) { + $this->_usedProperties['cookiePath'] = true; + $this->cookiePath = $value['cookie_path']; + unset($value['cookie_path']); + } + + if (array_key_exists('cookie_domain', $value)) { + $this->_usedProperties['cookieDomain'] = true; + $this->cookieDomain = $value['cookie_domain']; + unset($value['cookie_domain']); + } + + if (array_key_exists('cookie_secure', $value)) { + $this->_usedProperties['cookieSecure'] = true; + $this->cookieSecure = $value['cookie_secure']; + unset($value['cookie_secure']); + } + + if (array_key_exists('cookie_httponly', $value)) { + $this->_usedProperties['cookieHttponly'] = true; + $this->cookieHttponly = $value['cookie_httponly']; + unset($value['cookie_httponly']); + } + + if (array_key_exists('cookie_samesite', $value)) { + $this->_usedProperties['cookieSamesite'] = true; + $this->cookieSamesite = $value['cookie_samesite']; + unset($value['cookie_samesite']); + } + + if (array_key_exists('use_cookies', $value)) { + $this->_usedProperties['useCookies'] = true; + $this->useCookies = $value['use_cookies']; + unset($value['use_cookies']); + } + + if (array_key_exists('gc_divisor', $value)) { + $this->_usedProperties['gcDivisor'] = true; + $this->gcDivisor = $value['gc_divisor']; + unset($value['gc_divisor']); + } + + if (array_key_exists('gc_probability', $value)) { + $this->_usedProperties['gcProbability'] = true; + $this->gcProbability = $value['gc_probability']; + unset($value['gc_probability']); + } + + if (array_key_exists('gc_maxlifetime', $value)) { + $this->_usedProperties['gcMaxlifetime'] = true; + $this->gcMaxlifetime = $value['gc_maxlifetime']; + unset($value['gc_maxlifetime']); + } + + if (array_key_exists('save_path', $value)) { + $this->_usedProperties['savePath'] = true; + $this->savePath = $value['save_path']; + unset($value['save_path']); + } + + if (array_key_exists('metadata_update_threshold', $value)) { + $this->_usedProperties['metadataUpdateThreshold'] = true; + $this->metadataUpdateThreshold = $value['metadata_update_threshold']; + unset($value['metadata_update_threshold']); + } + + if (array_key_exists('sid_length', $value)) { + $this->_usedProperties['sidLength'] = true; + $this->sidLength = $value['sid_length']; + unset($value['sid_length']); + } + + if (array_key_exists('sid_bits_per_character', $value)) { + $this->_usedProperties['sidBitsPerCharacter'] = true; + $this->sidBitsPerCharacter = $value['sid_bits_per_character']; + unset($value['sid_bits_per_character']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['storageFactoryId'])) { + $output['storage_factory_id'] = $this->storageFactoryId; + } + if (isset($this->_usedProperties['handlerId'])) { + $output['handler_id'] = $this->handlerId; + } + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + if (isset($this->_usedProperties['cookieLifetime'])) { + $output['cookie_lifetime'] = $this->cookieLifetime; + } + if (isset($this->_usedProperties['cookiePath'])) { + $output['cookie_path'] = $this->cookiePath; + } + if (isset($this->_usedProperties['cookieDomain'])) { + $output['cookie_domain'] = $this->cookieDomain; + } + if (isset($this->_usedProperties['cookieSecure'])) { + $output['cookie_secure'] = $this->cookieSecure; + } + if (isset($this->_usedProperties['cookieHttponly'])) { + $output['cookie_httponly'] = $this->cookieHttponly; + } + if (isset($this->_usedProperties['cookieSamesite'])) { + $output['cookie_samesite'] = $this->cookieSamesite; + } + if (isset($this->_usedProperties['useCookies'])) { + $output['use_cookies'] = $this->useCookies; + } + if (isset($this->_usedProperties['gcDivisor'])) { + $output['gc_divisor'] = $this->gcDivisor; + } + if (isset($this->_usedProperties['gcProbability'])) { + $output['gc_probability'] = $this->gcProbability; + } + if (isset($this->_usedProperties['gcMaxlifetime'])) { + $output['gc_maxlifetime'] = $this->gcMaxlifetime; + } + if (isset($this->_usedProperties['savePath'])) { + $output['save_path'] = $this->savePath; + } + if (isset($this->_usedProperties['metadataUpdateThreshold'])) { + $output['metadata_update_threshold'] = $this->metadataUpdateThreshold; + } + if (isset($this->_usedProperties['sidLength'])) { + $output['sid_length'] = $this->sidLength; + } + if (isset($this->_usedProperties['sidBitsPerCharacter'])) { + $output['sid_bits_per_character'] = $this->sidBitsPerCharacter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/SsiConfig.php b/var/cache/dev/Symfony/Config/Framework/SsiConfig.php new file mode 100644 index 0000000..0f00bf7 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/SsiConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Translator/ProviderConfig.php b/var/cache/dev/Symfony/Config/Framework/Translator/ProviderConfig.php new file mode 100644 index 0000000..087d629 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Translator/ProviderConfig.php @@ -0,0 +1,98 @@ +_usedProperties['dsn'] = true; + $this->dsn = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function domains(ParamConfigurator|array $value): static + { + $this->_usedProperties['domains'] = true; + $this->domains = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function locales(ParamConfigurator|array $value): static + { + $this->_usedProperties['locales'] = true; + $this->locales = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('dsn', $value)) { + $this->_usedProperties['dsn'] = true; + $this->dsn = $value['dsn']; + unset($value['dsn']); + } + + if (array_key_exists('domains', $value)) { + $this->_usedProperties['domains'] = true; + $this->domains = $value['domains']; + unset($value['domains']); + } + + if (array_key_exists('locales', $value)) { + $this->_usedProperties['locales'] = true; + $this->locales = $value['locales']; + unset($value['locales']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['dsn'])) { + $output['dsn'] = $this->dsn; + } + if (isset($this->_usedProperties['domains'])) { + $output['domains'] = $this->domains; + } + if (isset($this->_usedProperties['locales'])) { + $output['locales'] = $this->locales; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Translator/PseudoLocalizationConfig.php b/var/cache/dev/Symfony/Config/Framework/Translator/PseudoLocalizationConfig.php new file mode 100644 index 0000000..fa61472 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Translator/PseudoLocalizationConfig.php @@ -0,0 +1,167 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function accents($value): static + { + $this->_usedProperties['accents'] = true; + $this->accents = $value; + + return $this; + } + + /** + * @default 1.0 + * @param ParamConfigurator|float $value + * @return $this + */ + public function expansionFactor($value): static + { + $this->_usedProperties['expansionFactor'] = true; + $this->expansionFactor = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function brackets($value): static + { + $this->_usedProperties['brackets'] = true; + $this->brackets = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function parseHtml($value): static + { + $this->_usedProperties['parseHtml'] = true; + $this->parseHtml = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function localizableHtmlAttributes(ParamConfigurator|array $value): static + { + $this->_usedProperties['localizableHtmlAttributes'] = true; + $this->localizableHtmlAttributes = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('accents', $value)) { + $this->_usedProperties['accents'] = true; + $this->accents = $value['accents']; + unset($value['accents']); + } + + if (array_key_exists('expansion_factor', $value)) { + $this->_usedProperties['expansionFactor'] = true; + $this->expansionFactor = $value['expansion_factor']; + unset($value['expansion_factor']); + } + + if (array_key_exists('brackets', $value)) { + $this->_usedProperties['brackets'] = true; + $this->brackets = $value['brackets']; + unset($value['brackets']); + } + + if (array_key_exists('parse_html', $value)) { + $this->_usedProperties['parseHtml'] = true; + $this->parseHtml = $value['parse_html']; + unset($value['parse_html']); + } + + if (array_key_exists('localizable_html_attributes', $value)) { + $this->_usedProperties['localizableHtmlAttributes'] = true; + $this->localizableHtmlAttributes = $value['localizable_html_attributes']; + unset($value['localizable_html_attributes']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['accents'])) { + $output['accents'] = $this->accents; + } + if (isset($this->_usedProperties['expansionFactor'])) { + $output['expansion_factor'] = $this->expansionFactor; + } + if (isset($this->_usedProperties['brackets'])) { + $output['brackets'] = $this->brackets; + } + if (isset($this->_usedProperties['parseHtml'])) { + $output['parse_html'] = $this->parseHtml; + } + if (isset($this->_usedProperties['localizableHtmlAttributes'])) { + $output['localizable_html_attributes'] = $this->localizableHtmlAttributes; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/TranslatorConfig.php b/var/cache/dev/Symfony/Config/Framework/TranslatorConfig.php new file mode 100644 index 0000000..3167bc7 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/TranslatorConfig.php @@ -0,0 +1,255 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function fallbacks(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['fallbacks'] = true; + $this->fallbacks = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function logging($value): static + { + $this->_usedProperties['logging'] = true; + $this->logging = $value; + + return $this; + } + + /** + * @default 'translator.formatter.default' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function formatter($value): static + { + $this->_usedProperties['formatter'] = true; + $this->formatter = $value; + + return $this; + } + + /** + * @default '%kernel.cache_dir%/translations' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cacheDir($value): static + { + $this->_usedProperties['cacheDir'] = true; + $this->cacheDir = $value; + + return $this; + } + + /** + * The default path used to load translations + * @default '%kernel.project_dir%/translations' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultPath($value): static + { + $this->_usedProperties['defaultPath'] = true; + $this->defaultPath = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function paths(ParamConfigurator|array $value): static + { + $this->_usedProperties['paths'] = true; + $this->paths = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"accents":true,"expansion_factor":1,"brackets":true,"parse_html":false,"localizable_html_attributes":[]} + * @return \Symfony\Config\Framework\Translator\PseudoLocalizationConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Translator\PseudoLocalizationConfig : static) + */ + public function pseudoLocalization(array $value = []): \Symfony\Config\Framework\Translator\PseudoLocalizationConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['pseudoLocalization'] = true; + $this->pseudoLocalization = $value; + + return $this; + } + + if (!$this->pseudoLocalization instanceof \Symfony\Config\Framework\Translator\PseudoLocalizationConfig) { + $this->_usedProperties['pseudoLocalization'] = true; + $this->pseudoLocalization = new \Symfony\Config\Framework\Translator\PseudoLocalizationConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "pseudoLocalization()" has already been initialized. You cannot pass values the second time you call pseudoLocalization().'); + } + + return $this->pseudoLocalization; + } + + /** + * Translation providers you can read/write your translations from + */ + public function provider(string $name, array $value = []): \Symfony\Config\Framework\Translator\ProviderConfig + { + if (!isset($this->providers[$name])) { + $this->_usedProperties['providers'] = true; + $this->providers[$name] = new \Symfony\Config\Framework\Translator\ProviderConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "provider()" has already been initialized. You cannot pass values the second time you call provider().'); + } + + return $this->providers[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('fallbacks', $value)) { + $this->_usedProperties['fallbacks'] = true; + $this->fallbacks = $value['fallbacks']; + unset($value['fallbacks']); + } + + if (array_key_exists('logging', $value)) { + $this->_usedProperties['logging'] = true; + $this->logging = $value['logging']; + unset($value['logging']); + } + + if (array_key_exists('formatter', $value)) { + $this->_usedProperties['formatter'] = true; + $this->formatter = $value['formatter']; + unset($value['formatter']); + } + + if (array_key_exists('cache_dir', $value)) { + $this->_usedProperties['cacheDir'] = true; + $this->cacheDir = $value['cache_dir']; + unset($value['cache_dir']); + } + + if (array_key_exists('default_path', $value)) { + $this->_usedProperties['defaultPath'] = true; + $this->defaultPath = $value['default_path']; + unset($value['default_path']); + } + + if (array_key_exists('paths', $value)) { + $this->_usedProperties['paths'] = true; + $this->paths = $value['paths']; + unset($value['paths']); + } + + if (array_key_exists('pseudo_localization', $value)) { + $this->_usedProperties['pseudoLocalization'] = true; + $this->pseudoLocalization = \is_array($value['pseudo_localization']) ? new \Symfony\Config\Framework\Translator\PseudoLocalizationConfig($value['pseudo_localization']) : $value['pseudo_localization']; + unset($value['pseudo_localization']); + } + + if (array_key_exists('providers', $value)) { + $this->_usedProperties['providers'] = true; + $this->providers = array_map(fn ($v) => new \Symfony\Config\Framework\Translator\ProviderConfig($v), $value['providers']); + unset($value['providers']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['fallbacks'])) { + $output['fallbacks'] = $this->fallbacks; + } + if (isset($this->_usedProperties['logging'])) { + $output['logging'] = $this->logging; + } + if (isset($this->_usedProperties['formatter'])) { + $output['formatter'] = $this->formatter; + } + if (isset($this->_usedProperties['cacheDir'])) { + $output['cache_dir'] = $this->cacheDir; + } + if (isset($this->_usedProperties['defaultPath'])) { + $output['default_path'] = $this->defaultPath; + } + if (isset($this->_usedProperties['paths'])) { + $output['paths'] = $this->paths; + } + if (isset($this->_usedProperties['pseudoLocalization'])) { + $output['pseudo_localization'] = $this->pseudoLocalization instanceof \Symfony\Config\Framework\Translator\PseudoLocalizationConfig ? $this->pseudoLocalization->toArray() : $this->pseudoLocalization; + } + if (isset($this->_usedProperties['providers'])) { + $output['providers'] = array_map(fn ($v) => $v->toArray(), $this->providers); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/TypeInfoConfig.php b/var/cache/dev/Symfony/Config/Framework/TypeInfoConfig.php new file mode 100644 index 0000000..ee5ada8 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/TypeInfoConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/UidConfig.php b/var/cache/dev/Symfony/Config/Framework/UidConfig.php new file mode 100644 index 0000000..0a0c804 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/UidConfig.php @@ -0,0 +1,167 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default 7 + * @param ParamConfigurator|7|6|4|1 $value + * @return $this + */ + public function defaultUuidVersion($value): static + { + $this->_usedProperties['defaultUuidVersion'] = true; + $this->defaultUuidVersion = $value; + + return $this; + } + + /** + * @default 5 + * @param ParamConfigurator|5|3 $value + * @return $this + */ + public function nameBasedUuidVersion($value): static + { + $this->_usedProperties['nameBasedUuidVersion'] = true; + $this->nameBasedUuidVersion = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function nameBasedUuidNamespace($value): static + { + $this->_usedProperties['nameBasedUuidNamespace'] = true; + $this->nameBasedUuidNamespace = $value; + + return $this; + } + + /** + * @default 7 + * @param ParamConfigurator|7|6|1 $value + * @return $this + */ + public function timeBasedUuidVersion($value): static + { + $this->_usedProperties['timeBasedUuidVersion'] = true; + $this->timeBasedUuidVersion = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function timeBasedUuidNode($value): static + { + $this->_usedProperties['timeBasedUuidNode'] = true; + $this->timeBasedUuidNode = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('default_uuid_version', $value)) { + $this->_usedProperties['defaultUuidVersion'] = true; + $this->defaultUuidVersion = $value['default_uuid_version']; + unset($value['default_uuid_version']); + } + + if (array_key_exists('name_based_uuid_version', $value)) { + $this->_usedProperties['nameBasedUuidVersion'] = true; + $this->nameBasedUuidVersion = $value['name_based_uuid_version']; + unset($value['name_based_uuid_version']); + } + + if (array_key_exists('name_based_uuid_namespace', $value)) { + $this->_usedProperties['nameBasedUuidNamespace'] = true; + $this->nameBasedUuidNamespace = $value['name_based_uuid_namespace']; + unset($value['name_based_uuid_namespace']); + } + + if (array_key_exists('time_based_uuid_version', $value)) { + $this->_usedProperties['timeBasedUuidVersion'] = true; + $this->timeBasedUuidVersion = $value['time_based_uuid_version']; + unset($value['time_based_uuid_version']); + } + + if (array_key_exists('time_based_uuid_node', $value)) { + $this->_usedProperties['timeBasedUuidNode'] = true; + $this->timeBasedUuidNode = $value['time_based_uuid_node']; + unset($value['time_based_uuid_node']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['defaultUuidVersion'])) { + $output['default_uuid_version'] = $this->defaultUuidVersion; + } + if (isset($this->_usedProperties['nameBasedUuidVersion'])) { + $output['name_based_uuid_version'] = $this->nameBasedUuidVersion; + } + if (isset($this->_usedProperties['nameBasedUuidNamespace'])) { + $output['name_based_uuid_namespace'] = $this->nameBasedUuidNamespace; + } + if (isset($this->_usedProperties['timeBasedUuidVersion'])) { + $output['time_based_uuid_version'] = $this->timeBasedUuidVersion; + } + if (isset($this->_usedProperties['timeBasedUuidNode'])) { + $output['time_based_uuid_node'] = $this->timeBasedUuidNode; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Validation/AutoMappingConfig.php b/var/cache/dev/Symfony/Config/Framework/Validation/AutoMappingConfig.php new file mode 100644 index 0000000..9265970 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Validation/AutoMappingConfig.php @@ -0,0 +1,52 @@ + $value + * + * @return $this + */ + public function services(ParamConfigurator|array $value): static + { + $this->_usedProperties['services'] = true; + $this->services = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('services', $value)) { + $this->_usedProperties['services'] = true; + $this->services = $value['services']; + unset($value['services']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['services'])) { + $output['services'] = $this->services; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Validation/MappingConfig.php b/var/cache/dev/Symfony/Config/Framework/Validation/MappingConfig.php new file mode 100644 index 0000000..ca4a90a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Validation/MappingConfig.php @@ -0,0 +1,52 @@ + $value + * + * @return $this + */ + public function paths(ParamConfigurator|array $value): static + { + $this->_usedProperties['paths'] = true; + $this->paths = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('paths', $value)) { + $this->_usedProperties['paths'] = true; + $this->paths = $value['paths']; + unset($value['paths']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['paths'])) { + $output['paths'] = $this->paths; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Validation/NotCompromisedPasswordConfig.php b/var/cache/dev/Symfony/Config/Framework/Validation/NotCompromisedPasswordConfig.php new file mode 100644 index 0000000..58853a9 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Validation/NotCompromisedPasswordConfig.php @@ -0,0 +1,77 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * API endpoint for the NotCompromisedPassword Validator. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function endpoint($value): static + { + $this->_usedProperties['endpoint'] = true; + $this->endpoint = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('endpoint', $value)) { + $this->_usedProperties['endpoint'] = true; + $this->endpoint = $value['endpoint']; + unset($value['endpoint']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['endpoint'])) { + $output['endpoint'] = $this->endpoint; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/ValidationConfig.php b/var/cache/dev/Symfony/Config/Framework/ValidationConfig.php new file mode 100644 index 0000000..57f557c --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/ValidationConfig.php @@ -0,0 +1,259 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cache($value): static + { + $this->_usedProperties['cache'] = true; + $this->cache = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enableAttributes($value): static + { + $this->_usedProperties['enableAttributes'] = true; + $this->enableAttributes = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function staticMethod(ParamConfigurator|array $value): static + { + $this->_usedProperties['staticMethod'] = true; + $this->staticMethod = $value; + + return $this; + } + + /** + * @default 'validators' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function translationDomain($value): static + { + $this->_usedProperties['translationDomain'] = true; + $this->translationDomain = $value; + + return $this; + } + + /** + * @default 'html5' + * @param ParamConfigurator|'html5'|'loose'|'strict' $value + * @return $this + */ + public function emailValidationMode($value): static + { + $this->_usedProperties['emailValidationMode'] = true; + $this->emailValidationMode = $value; + + return $this; + } + + /** + * @default {"paths":[]} + */ + public function mapping(array $value = []): \Symfony\Config\Framework\Validation\MappingConfig + { + if (null === $this->mapping) { + $this->_usedProperties['mapping'] = true; + $this->mapping = new \Symfony\Config\Framework\Validation\MappingConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "mapping()" has already been initialized. You cannot pass values the second time you call mapping().'); + } + + return $this->mapping; + } + + /** + * @default {"enabled":true,"endpoint":null} + */ + public function notCompromisedPassword(array $value = []): \Symfony\Config\Framework\Validation\NotCompromisedPasswordConfig + { + if (null === $this->notCompromisedPassword) { + $this->_usedProperties['notCompromisedPassword'] = true; + $this->notCompromisedPassword = new \Symfony\Config\Framework\Validation\NotCompromisedPasswordConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "notCompromisedPassword()" has already been initialized. You cannot pass values the second time you call notCompromisedPassword().'); + } + + return $this->notCompromisedPassword; + } + + /** + * @template TValue + * @param TValue $value + * A collection of namespaces for which auto-mapping will be enabled by default, or null to opt-in with the EnableAutoMapping constraint. + * @example [] + * @example ["validator.property_info_loader"] + * @return \Symfony\Config\Framework\Validation\AutoMappingConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Validation\AutoMappingConfig : static) + */ + public function autoMapping(string $namespace, array $value = []): \Symfony\Config\Framework\Validation\AutoMappingConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['autoMapping'] = true; + $this->autoMapping[$namespace] = $value; + + return $this; + } + + if (!isset($this->autoMapping[$namespace]) || !$this->autoMapping[$namespace] instanceof \Symfony\Config\Framework\Validation\AutoMappingConfig) { + $this->_usedProperties['autoMapping'] = true; + $this->autoMapping[$namespace] = new \Symfony\Config\Framework\Validation\AutoMappingConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "autoMapping()" has already been initialized. You cannot pass values the second time you call autoMapping().'); + } + + return $this->autoMapping[$namespace]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('cache', $value)) { + $this->_usedProperties['cache'] = true; + $this->cache = $value['cache']; + unset($value['cache']); + } + + if (array_key_exists('enable_attributes', $value)) { + $this->_usedProperties['enableAttributes'] = true; + $this->enableAttributes = $value['enable_attributes']; + unset($value['enable_attributes']); + } + + if (array_key_exists('static_method', $value)) { + $this->_usedProperties['staticMethod'] = true; + $this->staticMethod = $value['static_method']; + unset($value['static_method']); + } + + if (array_key_exists('translation_domain', $value)) { + $this->_usedProperties['translationDomain'] = true; + $this->translationDomain = $value['translation_domain']; + unset($value['translation_domain']); + } + + if (array_key_exists('email_validation_mode', $value)) { + $this->_usedProperties['emailValidationMode'] = true; + $this->emailValidationMode = $value['email_validation_mode']; + unset($value['email_validation_mode']); + } + + if (array_key_exists('mapping', $value)) { + $this->_usedProperties['mapping'] = true; + $this->mapping = new \Symfony\Config\Framework\Validation\MappingConfig($value['mapping']); + unset($value['mapping']); + } + + if (array_key_exists('not_compromised_password', $value)) { + $this->_usedProperties['notCompromisedPassword'] = true; + $this->notCompromisedPassword = new \Symfony\Config\Framework\Validation\NotCompromisedPasswordConfig($value['not_compromised_password']); + unset($value['not_compromised_password']); + } + + if (array_key_exists('auto_mapping', $value)) { + $this->_usedProperties['autoMapping'] = true; + $this->autoMapping = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Validation\AutoMappingConfig($v) : $v, $value['auto_mapping']); + unset($value['auto_mapping']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['cache'])) { + $output['cache'] = $this->cache; + } + if (isset($this->_usedProperties['enableAttributes'])) { + $output['enable_attributes'] = $this->enableAttributes; + } + if (isset($this->_usedProperties['staticMethod'])) { + $output['static_method'] = $this->staticMethod; + } + if (isset($this->_usedProperties['translationDomain'])) { + $output['translation_domain'] = $this->translationDomain; + } + if (isset($this->_usedProperties['emailValidationMode'])) { + $output['email_validation_mode'] = $this->emailValidationMode; + } + if (isset($this->_usedProperties['mapping'])) { + $output['mapping'] = $this->mapping->toArray(); + } + if (isset($this->_usedProperties['notCompromisedPassword'])) { + $output['not_compromised_password'] = $this->notCompromisedPassword->toArray(); + } + if (isset($this->_usedProperties['autoMapping'])) { + $output['auto_mapping'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Validation\AutoMappingConfig ? $v->toArray() : $v, $this->autoMapping); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/WebLinkConfig.php b/var/cache/dev/Symfony/Config/Framework/WebLinkConfig.php new file mode 100644 index 0000000..9b61a87 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/WebLinkConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Webhook/RoutingConfig.php b/var/cache/dev/Symfony/Config/Framework/Webhook/RoutingConfig.php new file mode 100644 index 0000000..12f84d0 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Webhook/RoutingConfig.php @@ -0,0 +1,74 @@ +_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function secret($value): static + { + $this->_usedProperties['secret'] = true; + $this->secret = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('secret', $value)) { + $this->_usedProperties['secret'] = true; + $this->secret = $value['secret']; + unset($value['secret']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['secret'])) { + $output['secret'] = $this->secret; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/WebhookConfig.php b/var/cache/dev/Symfony/Config/Framework/WebhookConfig.php new file mode 100644 index 0000000..87435a5 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/WebhookConfig.php @@ -0,0 +1,100 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * The message bus to use. + * @default 'messenger.default_bus' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function messageBus($value): static + { + $this->_usedProperties['messageBus'] = true; + $this->messageBus = $value; + + return $this; + } + + public function routing(string $type, array $value = []): \Symfony\Config\Framework\Webhook\RoutingConfig + { + if (!isset($this->routing[$type])) { + $this->_usedProperties['routing'] = true; + $this->routing[$type] = new \Symfony\Config\Framework\Webhook\RoutingConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "routing()" has already been initialized. You cannot pass values the second time you call routing().'); + } + + return $this->routing[$type]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('message_bus', $value)) { + $this->_usedProperties['messageBus'] = true; + $this->messageBus = $value['message_bus']; + unset($value['message_bus']); + } + + if (array_key_exists('routing', $value)) { + $this->_usedProperties['routing'] = true; + $this->routing = array_map(fn ($v) => new \Symfony\Config\Framework\Webhook\RoutingConfig($v), $value['routing']); + unset($value['routing']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['messageBus'])) { + $output['message_bus'] = $this->messageBus; + } + if (isset($this->_usedProperties['routing'])) { + $output['routing'] = array_map(fn ($v) => $v->toArray(), $this->routing); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig.php b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig.php new file mode 100644 index 0000000..3114318 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig.php @@ -0,0 +1,290 @@ +_usedProperties['auditTrail'] = true; + $this->auditTrail = $value; + + return $this; + } + + if (!$this->auditTrail instanceof \Symfony\Config\Framework\Workflows\WorkflowsConfig\AuditTrailConfig) { + $this->_usedProperties['auditTrail'] = true; + $this->auditTrail = new \Symfony\Config\Framework\Workflows\WorkflowsConfig\AuditTrailConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "auditTrail()" has already been initialized. You cannot pass values the second time you call auditTrail().'); + } + + return $this->auditTrail; + } + + /** + * @default 'state_machine' + * @param ParamConfigurator|'workflow'|'state_machine' $value + * @return $this + */ + public function type($value): static + { + $this->_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + public function markingStore(array $value = []): \Symfony\Config\Framework\Workflows\WorkflowsConfig\MarkingStoreConfig + { + if (null === $this->markingStore) { + $this->_usedProperties['markingStore'] = true; + $this->markingStore = new \Symfony\Config\Framework\Workflows\WorkflowsConfig\MarkingStoreConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "markingStore()" has already been initialized. You cannot pass values the second time you call markingStore().'); + } + + return $this->markingStore; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function supports(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['supports'] = true; + $this->supports = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function supportStrategy($value): static + { + $this->_usedProperties['supportStrategy'] = true; + $this->supportStrategy = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|mixed $value + * + * @return $this + */ + public function initialMarking(mixed $value): static + { + $this->_usedProperties['initialMarking'] = true; + $this->initialMarking = $value; + + return $this; + } + + /** + * Select which Transition events should be dispatched for this Workflow + * @example workflow.enter + * @example workflow.transition + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function eventsToDispatch(mixed $value = NULL): static + { + $this->_usedProperties['eventsToDispatch'] = true; + $this->eventsToDispatch = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig : static) + */ + public function place(mixed $value = []): \Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig|static + { + $this->_usedProperties['places'] = true; + if (!\is_array($value)) { + $this->places[] = $value; + + return $this; + } + + return $this->places[] = new \Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig($value); + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Workflows\WorkflowsConfig\TransitionConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Workflows\WorkflowsConfig\TransitionConfig : static) + */ + public function transition(mixed $value = []): \Symfony\Config\Framework\Workflows\WorkflowsConfig\TransitionConfig|static + { + $this->_usedProperties['transitions'] = true; + if (!\is_array($value)) { + $this->transitions[] = $value; + + return $this; + } + + return $this->transitions[] = new \Symfony\Config\Framework\Workflows\WorkflowsConfig\TransitionConfig($value); + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function metadata(ParamConfigurator|array $value): static + { + $this->_usedProperties['metadata'] = true; + $this->metadata = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('audit_trail', $value)) { + $this->_usedProperties['auditTrail'] = true; + $this->auditTrail = \is_array($value['audit_trail']) ? new \Symfony\Config\Framework\Workflows\WorkflowsConfig\AuditTrailConfig($value['audit_trail']) : $value['audit_trail']; + unset($value['audit_trail']); + } + + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('marking_store', $value)) { + $this->_usedProperties['markingStore'] = true; + $this->markingStore = new \Symfony\Config\Framework\Workflows\WorkflowsConfig\MarkingStoreConfig($value['marking_store']); + unset($value['marking_store']); + } + + if (array_key_exists('supports', $value)) { + $this->_usedProperties['supports'] = true; + $this->supports = $value['supports']; + unset($value['supports']); + } + + if (array_key_exists('support_strategy', $value)) { + $this->_usedProperties['supportStrategy'] = true; + $this->supportStrategy = $value['support_strategy']; + unset($value['support_strategy']); + } + + if (array_key_exists('initial_marking', $value)) { + $this->_usedProperties['initialMarking'] = true; + $this->initialMarking = $value['initial_marking']; + unset($value['initial_marking']); + } + + if (array_key_exists('events_to_dispatch', $value)) { + $this->_usedProperties['eventsToDispatch'] = true; + $this->eventsToDispatch = $value['events_to_dispatch']; + unset($value['events_to_dispatch']); + } + + if (array_key_exists('places', $value)) { + $this->_usedProperties['places'] = true; + $this->places = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig($v) : $v, $value['places']); + unset($value['places']); + } + + if (array_key_exists('transitions', $value)) { + $this->_usedProperties['transitions'] = true; + $this->transitions = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Workflows\WorkflowsConfig\TransitionConfig($v) : $v, $value['transitions']); + unset($value['transitions']); + } + + if (array_key_exists('metadata', $value)) { + $this->_usedProperties['metadata'] = true; + $this->metadata = $value['metadata']; + unset($value['metadata']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['auditTrail'])) { + $output['audit_trail'] = $this->auditTrail instanceof \Symfony\Config\Framework\Workflows\WorkflowsConfig\AuditTrailConfig ? $this->auditTrail->toArray() : $this->auditTrail; + } + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['markingStore'])) { + $output['marking_store'] = $this->markingStore->toArray(); + } + if (isset($this->_usedProperties['supports'])) { + $output['supports'] = $this->supports; + } + if (isset($this->_usedProperties['supportStrategy'])) { + $output['support_strategy'] = $this->supportStrategy; + } + if (isset($this->_usedProperties['initialMarking'])) { + $output['initial_marking'] = $this->initialMarking; + } + if (isset($this->_usedProperties['eventsToDispatch'])) { + $output['events_to_dispatch'] = $this->eventsToDispatch; + } + if (isset($this->_usedProperties['places'])) { + $output['places'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig ? $v->toArray() : $v, $this->places); + } + if (isset($this->_usedProperties['transitions'])) { + $output['transitions'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Workflows\WorkflowsConfig\TransitionConfig ? $v->toArray() : $v, $this->transitions); + } + if (isset($this->_usedProperties['metadata'])) { + $output['metadata'] = $this->metadata; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/AuditTrailConfig.php b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/AuditTrailConfig.php new file mode 100644 index 0000000..7188794 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/AuditTrailConfig.php @@ -0,0 +1,52 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/MarkingStoreConfig.php b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/MarkingStoreConfig.php new file mode 100644 index 0000000..e6cee3b --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/MarkingStoreConfig.php @@ -0,0 +1,98 @@ +_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function property($value): static + { + $this->_usedProperties['property'] = true; + $this->property = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('property', $value)) { + $this->_usedProperties['property'] = true; + $this->property = $value['property']; + unset($value['property']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['property'])) { + $output['property'] = $this->property; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/PlaceConfig.php b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/PlaceConfig.php new file mode 100644 index 0000000..f062c44 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/PlaceConfig.php @@ -0,0 +1,75 @@ +_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function metadata(ParamConfigurator|array $value): static + { + $this->_usedProperties['metadata'] = true; + $this->metadata = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if (array_key_exists('metadata', $value)) { + $this->_usedProperties['metadata'] = true; + $this->metadata = $value['metadata']; + unset($value['metadata']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + if (isset($this->_usedProperties['metadata'])) { + $output['metadata'] = $this->metadata; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/TransitionConfig.php b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/TransitionConfig.php new file mode 100644 index 0000000..8a814c5 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/Workflows/WorkflowsConfig/TransitionConfig.php @@ -0,0 +1,146 @@ +_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + /** + * An expression to block the transition + * @example is_fully_authenticated() and is_granted('ROLE_JOURNALIST') and subject.getTitle() == 'My first article' + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function guard($value): static + { + $this->_usedProperties['guard'] = true; + $this->guard = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function from(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['from'] = true; + $this->from = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function to(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['to'] = true; + $this->to = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function metadata(ParamConfigurator|array $value): static + { + $this->_usedProperties['metadata'] = true; + $this->metadata = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if (array_key_exists('guard', $value)) { + $this->_usedProperties['guard'] = true; + $this->guard = $value['guard']; + unset($value['guard']); + } + + if (array_key_exists('from', $value)) { + $this->_usedProperties['from'] = true; + $this->from = $value['from']; + unset($value['from']); + } + + if (array_key_exists('to', $value)) { + $this->_usedProperties['to'] = true; + $this->to = $value['to']; + unset($value['to']); + } + + if (array_key_exists('metadata', $value)) { + $this->_usedProperties['metadata'] = true; + $this->metadata = $value['metadata']; + unset($value['metadata']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + if (isset($this->_usedProperties['guard'])) { + $output['guard'] = $this->guard; + } + if (isset($this->_usedProperties['from'])) { + $output['from'] = $this->from; + } + if (isset($this->_usedProperties['to'])) { + $output['to'] = $this->to; + } + if (isset($this->_usedProperties['metadata'])) { + $output['metadata'] = $this->metadata; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Framework/WorkflowsConfig.php b/var/cache/dev/Symfony/Config/Framework/WorkflowsConfig.php new file mode 100644 index 0000000..4c359e6 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Framework/WorkflowsConfig.php @@ -0,0 +1,89 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Framework\Workflows\WorkflowsConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\Workflows\WorkflowsConfig : static) + */ + public function workflows(string $name, mixed $value = []): \Symfony\Config\Framework\Workflows\WorkflowsConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['workflows'] = true; + $this->workflows[$name] = $value; + + return $this; + } + + if (!isset($this->workflows[$name]) || !$this->workflows[$name] instanceof \Symfony\Config\Framework\Workflows\WorkflowsConfig) { + $this->_usedProperties['workflows'] = true; + $this->workflows[$name] = new \Symfony\Config\Framework\Workflows\WorkflowsConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "workflows()" has already been initialized. You cannot pass values the second time you call workflows().'); + } + + return $this->workflows[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('workflows', $value)) { + $this->_usedProperties['workflows'] = true; + $this->workflows = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Framework\Workflows\WorkflowsConfig($v) : $v, $value['workflows']); + unset($value['workflows']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['workflows'])) { + $output['workflows'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Framework\Workflows\WorkflowsConfig ? $v->toArray() : $v, $this->workflows); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/FrameworkConfig.php b/var/cache/dev/Symfony/Config/FrameworkConfig.php new file mode 100644 index 0000000..7edfc4b --- /dev/null +++ b/var/cache/dev/Symfony/Config/FrameworkConfig.php @@ -0,0 +1,1677 @@ +_usedProperties['secret'] = true; + $this->secret = $value; + + return $this; + } + + /** + * Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function httpMethodOverride($value): static + { + $this->_usedProperties['httpMethodOverride'] = true; + $this->httpMethodOverride = $value; + + return $this; + } + + /** + * Set true to enable support for xsendfile in binary file responses. + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function trustXSendfileTypeHeader($value): static + { + $this->_usedProperties['trustXSendfileTypeHeader'] = true; + $this->trustXSendfileTypeHeader = $value; + + return $this; + } + + /** + * @default '%env(default::SYMFONY_IDE)%' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function ide($value): static + { + $this->_usedProperties['ide'] = true; + $this->ide = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function test($value): static + { + $this->_usedProperties['test'] = true; + $this->test = $value; + + return $this; + } + + /** + * @default 'en' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultLocale($value): static + { + $this->_usedProperties['defaultLocale'] = true; + $this->defaultLocale = $value; + + return $this; + } + + /** + * Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function setLocaleFromAcceptLanguage($value): static + { + $this->_usedProperties['setLocaleFromAcceptLanguage'] = true; + $this->setLocaleFromAcceptLanguage = $value; + + return $this; + } + + /** + * Whether to set the Content-Language HTTP header on the Response using the Request locale. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function setContentLanguageFromLocale($value): static + { + $this->_usedProperties['setContentLanguageFromLocale'] = true; + $this->setContentLanguageFromLocale = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function enabledLocales(ParamConfigurator|array $value): static + { + $this->_usedProperties['enabledLocales'] = true; + $this->enabledLocales = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function trustedHosts(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['trustedHosts'] = true; + $this->trustedHosts = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function trustedProxies($value): static + { + $this->_usedProperties['trustedProxies'] = true; + $this->trustedProxies = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function trustedHeaders(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['trustedHeaders'] = true; + $this->trustedHeaders = $value; + + return $this; + } + + /** + * @default 'error_controller' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function errorController($value): static + { + $this->_usedProperties['errorController'] = true; + $this->errorController = $value; + + return $this; + } + + /** + * HttpKernel will handle all kinds of \Throwable + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function handleAllThrowables($value): static + { + $this->_usedProperties['handleAllThrowables'] = true; + $this->handleAllThrowables = $value; + + return $this; + } + + /** + * @default {"enabled":null} + */ + public function csrfProtection(array $value = []): \Symfony\Config\Framework\CsrfProtectionConfig + { + if (null === $this->csrfProtection) { + $this->_usedProperties['csrfProtection'] = true; + $this->csrfProtection = new \Symfony\Config\Framework\CsrfProtectionConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "csrfProtection()" has already been initialized. You cannot pass values the second time you call csrfProtection().'); + } + + return $this->csrfProtection; + } + + /** + * form configuration + * @default {"enabled":true,"csrf_protection":{"enabled":null,"field_name":"_token"}} + */ + public function form(array $value = []): \Symfony\Config\Framework\FormConfig + { + if (null === $this->form) { + $this->_usedProperties['form'] = true; + $this->form = new \Symfony\Config\Framework\FormConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "form()" has already been initialized. You cannot pass values the second time you call form().'); + } + + return $this->form; + } + + /** + * @template TValue + * @param TValue $value + * HTTP cache configuration + * @default {"enabled":false,"debug":"%kernel.debug%","private_headers":[],"skip_response_headers":[]} + * @return \Symfony\Config\Framework\HttpCacheConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HttpCacheConfig : static) + */ + public function httpCache(array $value = []): \Symfony\Config\Framework\HttpCacheConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['httpCache'] = true; + $this->httpCache = $value; + + return $this; + } + + if (!$this->httpCache instanceof \Symfony\Config\Framework\HttpCacheConfig) { + $this->_usedProperties['httpCache'] = true; + $this->httpCache = new \Symfony\Config\Framework\HttpCacheConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "httpCache()" has already been initialized. You cannot pass values the second time you call httpCache().'); + } + + return $this->httpCache; + } + + /** + * @template TValue + * @param TValue $value + * esi configuration + * @default {"enabled":false} + * @return \Symfony\Config\Framework\EsiConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\EsiConfig : static) + */ + public function esi(array $value = []): \Symfony\Config\Framework\EsiConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['esi'] = true; + $this->esi = $value; + + return $this; + } + + if (!$this->esi instanceof \Symfony\Config\Framework\EsiConfig) { + $this->_usedProperties['esi'] = true; + $this->esi = new \Symfony\Config\Framework\EsiConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "esi()" has already been initialized. You cannot pass values the second time you call esi().'); + } + + return $this->esi; + } + + /** + * @template TValue + * @param TValue $value + * ssi configuration + * @default {"enabled":false} + * @return \Symfony\Config\Framework\SsiConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\SsiConfig : static) + */ + public function ssi(array $value = []): \Symfony\Config\Framework\SsiConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['ssi'] = true; + $this->ssi = $value; + + return $this; + } + + if (!$this->ssi instanceof \Symfony\Config\Framework\SsiConfig) { + $this->_usedProperties['ssi'] = true; + $this->ssi = new \Symfony\Config\Framework\SsiConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "ssi()" has already been initialized. You cannot pass values the second time you call ssi().'); + } + + return $this->ssi; + } + + /** + * @template TValue + * @param TValue $value + * fragments configuration + * @default {"enabled":false,"hinclude_default_template":null,"path":"\/_fragment"} + * @return \Symfony\Config\Framework\FragmentsConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\FragmentsConfig : static) + */ + public function fragments(array $value = []): \Symfony\Config\Framework\FragmentsConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['fragments'] = true; + $this->fragments = $value; + + return $this; + } + + if (!$this->fragments instanceof \Symfony\Config\Framework\FragmentsConfig) { + $this->_usedProperties['fragments'] = true; + $this->fragments = new \Symfony\Config\Framework\FragmentsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "fragments()" has already been initialized. You cannot pass values the second time you call fragments().'); + } + + return $this->fragments; + } + + /** + * @template TValue + * @param TValue $value + * profiler configuration + * @default {"enabled":false,"collect":true,"collect_parameter":null,"only_exceptions":false,"only_main_requests":false,"dsn":"file:%kernel.cache_dir%\/profiler","collect_serializer_data":false} + * @return \Symfony\Config\Framework\ProfilerConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\ProfilerConfig : static) + */ + public function profiler(array $value = []): \Symfony\Config\Framework\ProfilerConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['profiler'] = true; + $this->profiler = $value; + + return $this; + } + + if (!$this->profiler instanceof \Symfony\Config\Framework\ProfilerConfig) { + $this->_usedProperties['profiler'] = true; + $this->profiler = new \Symfony\Config\Framework\ProfilerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "profiler()" has already been initialized. You cannot pass values the second time you call profiler().'); + } + + return $this->profiler; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"workflows":[]} + * @return \Symfony\Config\Framework\WorkflowsConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\WorkflowsConfig : static) + */ + public function workflows(mixed $value = []): \Symfony\Config\Framework\WorkflowsConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['workflows'] = true; + $this->workflows = $value; + + return $this; + } + + if (!$this->workflows instanceof \Symfony\Config\Framework\WorkflowsConfig) { + $this->_usedProperties['workflows'] = true; + $this->workflows = new \Symfony\Config\Framework\WorkflowsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "workflows()" has already been initialized. You cannot pass values the second time you call workflows().'); + } + + return $this->workflows; + } + + /** + * @template TValue + * @param TValue $value + * router configuration + * @default {"enabled":false,"cache_dir":"%kernel.build_dir%","default_uri":null,"http_port":80,"https_port":443,"strict_requirements":true,"utf8":true} + * @return \Symfony\Config\Framework\RouterConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\RouterConfig : static) + */ + public function router(array $value = []): \Symfony\Config\Framework\RouterConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['router'] = true; + $this->router = $value; + + return $this; + } + + if (!$this->router instanceof \Symfony\Config\Framework\RouterConfig) { + $this->_usedProperties['router'] = true; + $this->router = new \Symfony\Config\Framework\RouterConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "router()" has already been initialized. You cannot pass values the second time you call router().'); + } + + return $this->router; + } + + /** + * @template TValue + * @param TValue $value + * session configuration + * @default {"enabled":false,"storage_factory_id":"session.storage.factory.native","cookie_secure":"auto","cookie_httponly":true,"cookie_samesite":"lax","gc_probability":1,"metadata_update_threshold":0} + * @return \Symfony\Config\Framework\SessionConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\SessionConfig : static) + */ + public function session(array $value = []): \Symfony\Config\Framework\SessionConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['session'] = true; + $this->session = $value; + + return $this; + } + + if (!$this->session instanceof \Symfony\Config\Framework\SessionConfig) { + $this->_usedProperties['session'] = true; + $this->session = new \Symfony\Config\Framework\SessionConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "session()" has already been initialized. You cannot pass values the second time you call session().'); + } + + return $this->session; + } + + /** + * @template TValue + * @param TValue $value + * request configuration + * @default {"enabled":false,"formats":[]} + * @return \Symfony\Config\Framework\RequestConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\RequestConfig : static) + */ + public function request(array $value = []): \Symfony\Config\Framework\RequestConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['request'] = true; + $this->request = $value; + + return $this; + } + + if (!$this->request instanceof \Symfony\Config\Framework\RequestConfig) { + $this->_usedProperties['request'] = true; + $this->request = new \Symfony\Config\Framework\RequestConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "request()" has already been initialized. You cannot pass values the second time you call request().'); + } + + return $this->request; + } + + /** + * @template TValue + * @param TValue $value + * assets configuration + * @default {"enabled":false,"strict_mode":false,"version_strategy":null,"version":null,"version_format":"%%s?%%s","json_manifest_path":null,"base_path":"","base_urls":[],"packages":[]} + * @return \Symfony\Config\Framework\AssetsConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\AssetsConfig : static) + */ + public function assets(array $value = []): \Symfony\Config\Framework\AssetsConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['assets'] = true; + $this->assets = $value; + + return $this; + } + + if (!$this->assets instanceof \Symfony\Config\Framework\AssetsConfig) { + $this->_usedProperties['assets'] = true; + $this->assets = new \Symfony\Config\Framework\AssetsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "assets()" has already been initialized. You cannot pass values the second time you call assets().'); + } + + return $this->assets; + } + + /** + * @template TValue + * @param TValue $value + * Asset Mapper configuration + * @default {"enabled":false,"paths":[],"excluded_patterns":[],"exclude_dotfiles":true,"server":true,"public_prefix":"\/assets\/","missing_import_mode":"warn","extensions":[],"importmap_path":"%kernel.project_dir%\/importmap.php","importmap_polyfill":"es-module-shims","importmap_script_attributes":[],"vendor_dir":"%kernel.project_dir%\/assets\/vendor"} + * @return \Symfony\Config\Framework\AssetMapperConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\AssetMapperConfig : static) + */ + public function assetMapper(array $value = []): \Symfony\Config\Framework\AssetMapperConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['assetMapper'] = true; + $this->assetMapper = $value; + + return $this; + } + + if (!$this->assetMapper instanceof \Symfony\Config\Framework\AssetMapperConfig) { + $this->_usedProperties['assetMapper'] = true; + $this->assetMapper = new \Symfony\Config\Framework\AssetMapperConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "assetMapper()" has already been initialized. You cannot pass values the second time you call assetMapper().'); + } + + return $this->assetMapper; + } + + /** + * @template TValue + * @param TValue $value + * translator configuration + * @default {"enabled":false,"fallbacks":[],"logging":false,"formatter":"translator.formatter.default","cache_dir":"%kernel.cache_dir%\/translations","default_path":"%kernel.project_dir%\/translations","paths":[],"pseudo_localization":{"enabled":false,"accents":true,"expansion_factor":1,"brackets":true,"parse_html":false,"localizable_html_attributes":[]},"providers":[]} + * @return \Symfony\Config\Framework\TranslatorConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\TranslatorConfig : static) + */ + public function translator(array $value = []): \Symfony\Config\Framework\TranslatorConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['translator'] = true; + $this->translator = $value; + + return $this; + } + + if (!$this->translator instanceof \Symfony\Config\Framework\TranslatorConfig) { + $this->_usedProperties['translator'] = true; + $this->translator = new \Symfony\Config\Framework\TranslatorConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "translator()" has already been initialized. You cannot pass values the second time you call translator().'); + } + + return $this->translator; + } + + /** + * @template TValue + * @param TValue $value + * validation configuration + * @default {"enabled":false,"enable_attributes":true,"static_method":["loadValidatorMetadata"],"translation_domain":"validators","email_validation_mode":"html5","mapping":{"paths":[]},"not_compromised_password":{"enabled":true,"endpoint":null},"auto_mapping":[]} + * @return \Symfony\Config\Framework\ValidationConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\ValidationConfig : static) + */ + public function validation(array $value = []): \Symfony\Config\Framework\ValidationConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['validation'] = true; + $this->validation = $value; + + return $this; + } + + if (!$this->validation instanceof \Symfony\Config\Framework\ValidationConfig) { + $this->_usedProperties['validation'] = true; + $this->validation = new \Symfony\Config\Framework\ValidationConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "validation()" has already been initialized. You cannot pass values the second time you call validation().'); + } + + return $this->validation; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false} + * @return \Symfony\Config\Framework\AnnotationsConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\AnnotationsConfig : static) + */ + public function annotations(array $value = []): \Symfony\Config\Framework\AnnotationsConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['annotations'] = true; + $this->annotations = $value; + + return $this; + } + + if (!$this->annotations instanceof \Symfony\Config\Framework\AnnotationsConfig) { + $this->_usedProperties['annotations'] = true; + $this->annotations = new \Symfony\Config\Framework\AnnotationsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "annotations()" has already been initialized. You cannot pass values the second time you call annotations().'); + } + + return $this->annotations; + } + + /** + * @template TValue + * @param TValue $value + * serializer configuration + * @default {"enabled":false,"enable_attributes":true,"mapping":{"paths":[]},"default_context":[]} + * @return \Symfony\Config\Framework\SerializerConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\SerializerConfig : static) + */ + public function serializer(array $value = []): \Symfony\Config\Framework\SerializerConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['serializer'] = true; + $this->serializer = $value; + + return $this; + } + + if (!$this->serializer instanceof \Symfony\Config\Framework\SerializerConfig) { + $this->_usedProperties['serializer'] = true; + $this->serializer = new \Symfony\Config\Framework\SerializerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "serializer()" has already been initialized. You cannot pass values the second time you call serializer().'); + } + + return $this->serializer; + } + + /** + * Property access configuration + * @default {"enabled":true,"magic_call":false,"magic_get":true,"magic_set":true,"throw_exception_on_invalid_index":false,"throw_exception_on_invalid_property_path":true} + */ + public function propertyAccess(array $value = []): \Symfony\Config\Framework\PropertyAccessConfig + { + if (null === $this->propertyAccess) { + $this->_usedProperties['propertyAccess'] = true; + $this->propertyAccess = new \Symfony\Config\Framework\PropertyAccessConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "propertyAccess()" has already been initialized. You cannot pass values the second time you call propertyAccess().'); + } + + return $this->propertyAccess; + } + + /** + * Type info configuration + * @default {"enabled":true} + */ + public function typeInfo(array $value = []): \Symfony\Config\Framework\TypeInfoConfig + { + if (null === $this->typeInfo) { + $this->_usedProperties['typeInfo'] = true; + $this->typeInfo = new \Symfony\Config\Framework\TypeInfoConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "typeInfo()" has already been initialized. You cannot pass values the second time you call typeInfo().'); + } + + return $this->typeInfo; + } + + /** + * Property info configuration + * @default {"enabled":true} + */ + public function propertyInfo(array $value = []): \Symfony\Config\Framework\PropertyInfoConfig + { + if (null === $this->propertyInfo) { + $this->_usedProperties['propertyInfo'] = true; + $this->propertyInfo = new \Symfony\Config\Framework\PropertyInfoConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "propertyInfo()" has already been initialized. You cannot pass values the second time you call propertyInfo().'); + } + + return $this->propertyInfo; + } + + /** + * Cache configuration + * @default {"prefix_seed":"_%kernel.project_dir%.%kernel.container_class%","app":"cache.adapter.filesystem","system":"cache.adapter.system","directory":"%kernel.cache_dir%\/pools\/app","default_redis_provider":"redis:\/\/localhost","default_memcached_provider":"memcached:\/\/localhost","default_doctrine_dbal_provider":"database_connection","default_pdo_provider":null,"pools":[]} + */ + public function cache(array $value = []): \Symfony\Config\Framework\CacheConfig + { + if (null === $this->cache) { + $this->_usedProperties['cache'] = true; + $this->cache = new \Symfony\Config\Framework\CacheConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "cache()" has already been initialized. You cannot pass values the second time you call cache().'); + } + + return $this->cache; + } + + /** + * PHP errors handling configuration + * @default {"log":true,"throw":true} + */ + public function phpErrors(array $value = []): \Symfony\Config\Framework\PhpErrorsConfig + { + if (null === $this->phpErrors) { + $this->_usedProperties['phpErrors'] = true; + $this->phpErrors = new \Symfony\Config\Framework\PhpErrorsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "phpErrors()" has already been initialized. You cannot pass values the second time you call phpErrors().'); + } + + return $this->phpErrors; + } + + /** + * Exception handling configuration + */ + public function exception(string $class, array $value = []): \Symfony\Config\Framework\ExceptionConfig + { + if (!isset($this->exceptions[$class])) { + $this->_usedProperties['exceptions'] = true; + $this->exceptions[$class] = new \Symfony\Config\Framework\ExceptionConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "exception()" has already been initialized. You cannot pass values the second time you call exception().'); + } + + return $this->exceptions[$class]; + } + + /** + * @template TValue + * @param TValue $value + * web links configuration + * @default {"enabled":false} + * @return \Symfony\Config\Framework\WebLinkConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\WebLinkConfig : static) + */ + public function webLink(array $value = []): \Symfony\Config\Framework\WebLinkConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['webLink'] = true; + $this->webLink = $value; + + return $this; + } + + if (!$this->webLink instanceof \Symfony\Config\Framework\WebLinkConfig) { + $this->_usedProperties['webLink'] = true; + $this->webLink = new \Symfony\Config\Framework\WebLinkConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "webLink()" has already been initialized. You cannot pass values the second time you call webLink().'); + } + + return $this->webLink; + } + + /** + * @template TValue + * @param TValue $value + * Lock configuration + * @default {"enabled":false,"resources":{"default":["flock"]}} + * @return \Symfony\Config\Framework\LockConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\LockConfig : static) + */ + public function lock(mixed $value = []): \Symfony\Config\Framework\LockConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['lock'] = true; + $this->lock = $value; + + return $this; + } + + if (!$this->lock instanceof \Symfony\Config\Framework\LockConfig) { + $this->_usedProperties['lock'] = true; + $this->lock = new \Symfony\Config\Framework\LockConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "lock()" has already been initialized. You cannot pass values the second time you call lock().'); + } + + return $this->lock; + } + + /** + * @template TValue + * @param TValue $value + * Semaphore configuration + * @default {"enabled":false,"resources":[]} + * @return \Symfony\Config\Framework\SemaphoreConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\SemaphoreConfig : static) + */ + public function semaphore(mixed $value = []): \Symfony\Config\Framework\SemaphoreConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['semaphore'] = true; + $this->semaphore = $value; + + return $this; + } + + if (!$this->semaphore instanceof \Symfony\Config\Framework\SemaphoreConfig) { + $this->_usedProperties['semaphore'] = true; + $this->semaphore = new \Symfony\Config\Framework\SemaphoreConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "semaphore()" has already been initialized. You cannot pass values the second time you call semaphore().'); + } + + return $this->semaphore; + } + + /** + * @template TValue + * @param TValue $value + * Messenger configuration + * @default {"enabled":false,"routing":[],"serializer":{"default_serializer":"messenger.transport.native_php_serializer","symfony_serializer":{"format":"json","context":[]}},"transports":[],"failure_transport":null,"stop_worker_on_signals":[],"default_bus":null,"buses":{"messenger.bus.default":{"default_middleware":{"enabled":true,"allow_no_handlers":false,"allow_no_senders":true},"middleware":[]}}} + * @return \Symfony\Config\Framework\MessengerConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\MessengerConfig : static) + */ + public function messenger(array $value = []): \Symfony\Config\Framework\MessengerConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['messenger'] = true; + $this->messenger = $value; + + return $this; + } + + if (!$this->messenger instanceof \Symfony\Config\Framework\MessengerConfig) { + $this->_usedProperties['messenger'] = true; + $this->messenger = new \Symfony\Config\Framework\MessengerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "messenger()" has already been initialized. You cannot pass values the second time you call messenger().'); + } + + return $this->messenger; + } + + /** + * @template TValue + * @param TValue $value + * Scheduler configuration + * @default {"enabled":false} + * @return \Symfony\Config\Framework\SchedulerConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\SchedulerConfig : static) + */ + public function scheduler(array $value = []): \Symfony\Config\Framework\SchedulerConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['scheduler'] = true; + $this->scheduler = $value; + + return $this; + } + + if (!$this->scheduler instanceof \Symfony\Config\Framework\SchedulerConfig) { + $this->_usedProperties['scheduler'] = true; + $this->scheduler = new \Symfony\Config\Framework\SchedulerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "scheduler()" has already been initialized. You cannot pass values the second time you call scheduler().'); + } + + return $this->scheduler; + } + + /** + * Enabled by default when debug is enabled. + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function disallowSearchEngineIndex($value): static + { + $this->_usedProperties['disallowSearchEngineIndex'] = true; + $this->disallowSearchEngineIndex = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * HTTP Client configuration + * @default {"enabled":true,"scoped_clients":[]} + * @return \Symfony\Config\Framework\HttpClientConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HttpClientConfig : static) + */ + public function httpClient(mixed $value = []): \Symfony\Config\Framework\HttpClientConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['httpClient'] = true; + $this->httpClient = $value; + + return $this; + } + + if (!$this->httpClient instanceof \Symfony\Config\Framework\HttpClientConfig) { + $this->_usedProperties['httpClient'] = true; + $this->httpClient = new \Symfony\Config\Framework\HttpClientConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "httpClient()" has already been initialized. You cannot pass values the second time you call httpClient().'); + } + + return $this->httpClient; + } + + /** + * Mailer configuration + * @default {"enabled":true,"message_bus":null,"dsn":null,"transports":[],"headers":[]} + */ + public function mailer(array $value = []): \Symfony\Config\Framework\MailerConfig + { + if (null === $this->mailer) { + $this->_usedProperties['mailer'] = true; + $this->mailer = new \Symfony\Config\Framework\MailerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "mailer()" has already been initialized. You cannot pass values the second time you call mailer().'); + } + + return $this->mailer; + } + + /** + * @default {"enabled":true,"vault_directory":"%kernel.project_dir%\/config\/secrets\/%kernel.runtime_environment%","local_dotenv_file":"%kernel.project_dir%\/.env.%kernel.environment%.local","decryption_env_var":"base64:default::SYMFONY_DECRYPTION_SECRET"} + */ + public function secrets(array $value = []): \Symfony\Config\Framework\SecretsConfig + { + if (null === $this->secrets) { + $this->_usedProperties['secrets'] = true; + $this->secrets = new \Symfony\Config\Framework\SecretsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "secrets()" has already been initialized. You cannot pass values the second time you call secrets().'); + } + + return $this->secrets; + } + + /** + * @template TValue + * @param TValue $value + * Notifier configuration + * @default {"enabled":false,"message_bus":null,"chatter_transports":[],"texter_transports":[],"notification_on_failed_messages":false,"channel_policy":[],"admin_recipients":[]} + * @return \Symfony\Config\Framework\NotifierConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\NotifierConfig : static) + */ + public function notifier(array $value = []): \Symfony\Config\Framework\NotifierConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['notifier'] = true; + $this->notifier = $value; + + return $this; + } + + if (!$this->notifier instanceof \Symfony\Config\Framework\NotifierConfig) { + $this->_usedProperties['notifier'] = true; + $this->notifier = new \Symfony\Config\Framework\NotifierConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "notifier()" has already been initialized. You cannot pass values the second time you call notifier().'); + } + + return $this->notifier; + } + + /** + * @template TValue + * @param TValue $value + * Rate limiter configuration + * @default {"enabled":false,"limiters":[]} + * @return \Symfony\Config\Framework\RateLimiterConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\RateLimiterConfig : static) + */ + public function rateLimiter(mixed $value = []): \Symfony\Config\Framework\RateLimiterConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = $value; + + return $this; + } + + if (!$this->rateLimiter instanceof \Symfony\Config\Framework\RateLimiterConfig) { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = new \Symfony\Config\Framework\RateLimiterConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "rateLimiter()" has already been initialized. You cannot pass values the second time you call rateLimiter().'); + } + + return $this->rateLimiter; + } + + /** + * @template TValue + * @param TValue $value + * Uid configuration + * @default {"enabled":false,"default_uuid_version":7,"name_based_uuid_version":5,"time_based_uuid_version":7} + * @return \Symfony\Config\Framework\UidConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\UidConfig : static) + */ + public function uid(array $value = []): \Symfony\Config\Framework\UidConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['uid'] = true; + $this->uid = $value; + + return $this; + } + + if (!$this->uid instanceof \Symfony\Config\Framework\UidConfig) { + $this->_usedProperties['uid'] = true; + $this->uid = new \Symfony\Config\Framework\UidConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "uid()" has already been initialized. You cannot pass values the second time you call uid().'); + } + + return $this->uid; + } + + /** + * @template TValue + * @param TValue $value + * HtmlSanitizer configuration + * @default {"enabled":false,"sanitizers":[]} + * @return \Symfony\Config\Framework\HtmlSanitizerConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\HtmlSanitizerConfig : static) + */ + public function htmlSanitizer(array $value = []): \Symfony\Config\Framework\HtmlSanitizerConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['htmlSanitizer'] = true; + $this->htmlSanitizer = $value; + + return $this; + } + + if (!$this->htmlSanitizer instanceof \Symfony\Config\Framework\HtmlSanitizerConfig) { + $this->_usedProperties['htmlSanitizer'] = true; + $this->htmlSanitizer = new \Symfony\Config\Framework\HtmlSanitizerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "htmlSanitizer()" has already been initialized. You cannot pass values the second time you call htmlSanitizer().'); + } + + return $this->htmlSanitizer; + } + + /** + * @template TValue + * @param TValue $value + * Webhook configuration + * @default {"enabled":false,"message_bus":"messenger.default_bus","routing":[]} + * @return \Symfony\Config\Framework\WebhookConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\WebhookConfig : static) + */ + public function webhook(array $value = []): \Symfony\Config\Framework\WebhookConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['webhook'] = true; + $this->webhook = $value; + + return $this; + } + + if (!$this->webhook instanceof \Symfony\Config\Framework\WebhookConfig) { + $this->_usedProperties['webhook'] = true; + $this->webhook = new \Symfony\Config\Framework\WebhookConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "webhook()" has already been initialized. You cannot pass values the second time you call webhook().'); + } + + return $this->webhook; + } + + /** + * @template TValue + * @param TValue $value + * RemoteEvent configuration + * @default {"enabled":false} + * @return \Symfony\Config\Framework\RemoteeventConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Framework\RemoteeventConfig : static) + */ + public function remoteevent(array $value = []): \Symfony\Config\Framework\RemoteeventConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['remoteevent'] = true; + $this->remoteevent = $value; + + return $this; + } + + if (!$this->remoteevent instanceof \Symfony\Config\Framework\RemoteeventConfig) { + $this->_usedProperties['remoteevent'] = true; + $this->remoteevent = new \Symfony\Config\Framework\RemoteeventConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "remoteevent()" has already been initialized. You cannot pass values the second time you call remoteevent().'); + } + + return $this->remoteevent; + } + + public function getExtensionAlias(): string + { + return 'framework'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('secret', $value)) { + $this->_usedProperties['secret'] = true; + $this->secret = $value['secret']; + unset($value['secret']); + } + + if (array_key_exists('http_method_override', $value)) { + $this->_usedProperties['httpMethodOverride'] = true; + $this->httpMethodOverride = $value['http_method_override']; + unset($value['http_method_override']); + } + + if (array_key_exists('trust_x_sendfile_type_header', $value)) { + $this->_usedProperties['trustXSendfileTypeHeader'] = true; + $this->trustXSendfileTypeHeader = $value['trust_x_sendfile_type_header']; + unset($value['trust_x_sendfile_type_header']); + } + + if (array_key_exists('ide', $value)) { + $this->_usedProperties['ide'] = true; + $this->ide = $value['ide']; + unset($value['ide']); + } + + if (array_key_exists('test', $value)) { + $this->_usedProperties['test'] = true; + $this->test = $value['test']; + unset($value['test']); + } + + if (array_key_exists('default_locale', $value)) { + $this->_usedProperties['defaultLocale'] = true; + $this->defaultLocale = $value['default_locale']; + unset($value['default_locale']); + } + + if (array_key_exists('set_locale_from_accept_language', $value)) { + $this->_usedProperties['setLocaleFromAcceptLanguage'] = true; + $this->setLocaleFromAcceptLanguage = $value['set_locale_from_accept_language']; + unset($value['set_locale_from_accept_language']); + } + + if (array_key_exists('set_content_language_from_locale', $value)) { + $this->_usedProperties['setContentLanguageFromLocale'] = true; + $this->setContentLanguageFromLocale = $value['set_content_language_from_locale']; + unset($value['set_content_language_from_locale']); + } + + if (array_key_exists('enabled_locales', $value)) { + $this->_usedProperties['enabledLocales'] = true; + $this->enabledLocales = $value['enabled_locales']; + unset($value['enabled_locales']); + } + + if (array_key_exists('trusted_hosts', $value)) { + $this->_usedProperties['trustedHosts'] = true; + $this->trustedHosts = $value['trusted_hosts']; + unset($value['trusted_hosts']); + } + + if (array_key_exists('trusted_proxies', $value)) { + $this->_usedProperties['trustedProxies'] = true; + $this->trustedProxies = $value['trusted_proxies']; + unset($value['trusted_proxies']); + } + + if (array_key_exists('trusted_headers', $value)) { + $this->_usedProperties['trustedHeaders'] = true; + $this->trustedHeaders = $value['trusted_headers']; + unset($value['trusted_headers']); + } + + if (array_key_exists('error_controller', $value)) { + $this->_usedProperties['errorController'] = true; + $this->errorController = $value['error_controller']; + unset($value['error_controller']); + } + + if (array_key_exists('handle_all_throwables', $value)) { + $this->_usedProperties['handleAllThrowables'] = true; + $this->handleAllThrowables = $value['handle_all_throwables']; + unset($value['handle_all_throwables']); + } + + if (array_key_exists('csrf_protection', $value)) { + $this->_usedProperties['csrfProtection'] = true; + $this->csrfProtection = new \Symfony\Config\Framework\CsrfProtectionConfig($value['csrf_protection']); + unset($value['csrf_protection']); + } + + if (array_key_exists('form', $value)) { + $this->_usedProperties['form'] = true; + $this->form = new \Symfony\Config\Framework\FormConfig($value['form']); + unset($value['form']); + } + + if (array_key_exists('http_cache', $value)) { + $this->_usedProperties['httpCache'] = true; + $this->httpCache = \is_array($value['http_cache']) ? new \Symfony\Config\Framework\HttpCacheConfig($value['http_cache']) : $value['http_cache']; + unset($value['http_cache']); + } + + if (array_key_exists('esi', $value)) { + $this->_usedProperties['esi'] = true; + $this->esi = \is_array($value['esi']) ? new \Symfony\Config\Framework\EsiConfig($value['esi']) : $value['esi']; + unset($value['esi']); + } + + if (array_key_exists('ssi', $value)) { + $this->_usedProperties['ssi'] = true; + $this->ssi = \is_array($value['ssi']) ? new \Symfony\Config\Framework\SsiConfig($value['ssi']) : $value['ssi']; + unset($value['ssi']); + } + + if (array_key_exists('fragments', $value)) { + $this->_usedProperties['fragments'] = true; + $this->fragments = \is_array($value['fragments']) ? new \Symfony\Config\Framework\FragmentsConfig($value['fragments']) : $value['fragments']; + unset($value['fragments']); + } + + if (array_key_exists('profiler', $value)) { + $this->_usedProperties['profiler'] = true; + $this->profiler = \is_array($value['profiler']) ? new \Symfony\Config\Framework\ProfilerConfig($value['profiler']) : $value['profiler']; + unset($value['profiler']); + } + + if (array_key_exists('workflows', $value)) { + $this->_usedProperties['workflows'] = true; + $this->workflows = \is_array($value['workflows']) ? new \Symfony\Config\Framework\WorkflowsConfig($value['workflows']) : $value['workflows']; + unset($value['workflows']); + } + + if (array_key_exists('router', $value)) { + $this->_usedProperties['router'] = true; + $this->router = \is_array($value['router']) ? new \Symfony\Config\Framework\RouterConfig($value['router']) : $value['router']; + unset($value['router']); + } + + if (array_key_exists('session', $value)) { + $this->_usedProperties['session'] = true; + $this->session = \is_array($value['session']) ? new \Symfony\Config\Framework\SessionConfig($value['session']) : $value['session']; + unset($value['session']); + } + + if (array_key_exists('request', $value)) { + $this->_usedProperties['request'] = true; + $this->request = \is_array($value['request']) ? new \Symfony\Config\Framework\RequestConfig($value['request']) : $value['request']; + unset($value['request']); + } + + if (array_key_exists('assets', $value)) { + $this->_usedProperties['assets'] = true; + $this->assets = \is_array($value['assets']) ? new \Symfony\Config\Framework\AssetsConfig($value['assets']) : $value['assets']; + unset($value['assets']); + } + + if (array_key_exists('asset_mapper', $value)) { + $this->_usedProperties['assetMapper'] = true; + $this->assetMapper = \is_array($value['asset_mapper']) ? new \Symfony\Config\Framework\AssetMapperConfig($value['asset_mapper']) : $value['asset_mapper']; + unset($value['asset_mapper']); + } + + if (array_key_exists('translator', $value)) { + $this->_usedProperties['translator'] = true; + $this->translator = \is_array($value['translator']) ? new \Symfony\Config\Framework\TranslatorConfig($value['translator']) : $value['translator']; + unset($value['translator']); + } + + if (array_key_exists('validation', $value)) { + $this->_usedProperties['validation'] = true; + $this->validation = \is_array($value['validation']) ? new \Symfony\Config\Framework\ValidationConfig($value['validation']) : $value['validation']; + unset($value['validation']); + } + + if (array_key_exists('annotations', $value)) { + $this->_usedProperties['annotations'] = true; + $this->annotations = \is_array($value['annotations']) ? new \Symfony\Config\Framework\AnnotationsConfig($value['annotations']) : $value['annotations']; + unset($value['annotations']); + } + + if (array_key_exists('serializer', $value)) { + $this->_usedProperties['serializer'] = true; + $this->serializer = \is_array($value['serializer']) ? new \Symfony\Config\Framework\SerializerConfig($value['serializer']) : $value['serializer']; + unset($value['serializer']); + } + + if (array_key_exists('property_access', $value)) { + $this->_usedProperties['propertyAccess'] = true; + $this->propertyAccess = new \Symfony\Config\Framework\PropertyAccessConfig($value['property_access']); + unset($value['property_access']); + } + + if (array_key_exists('type_info', $value)) { + $this->_usedProperties['typeInfo'] = true; + $this->typeInfo = new \Symfony\Config\Framework\TypeInfoConfig($value['type_info']); + unset($value['type_info']); + } + + if (array_key_exists('property_info', $value)) { + $this->_usedProperties['propertyInfo'] = true; + $this->propertyInfo = new \Symfony\Config\Framework\PropertyInfoConfig($value['property_info']); + unset($value['property_info']); + } + + if (array_key_exists('cache', $value)) { + $this->_usedProperties['cache'] = true; + $this->cache = new \Symfony\Config\Framework\CacheConfig($value['cache']); + unset($value['cache']); + } + + if (array_key_exists('php_errors', $value)) { + $this->_usedProperties['phpErrors'] = true; + $this->phpErrors = new \Symfony\Config\Framework\PhpErrorsConfig($value['php_errors']); + unset($value['php_errors']); + } + + if (array_key_exists('exceptions', $value)) { + $this->_usedProperties['exceptions'] = true; + $this->exceptions = array_map(fn ($v) => new \Symfony\Config\Framework\ExceptionConfig($v), $value['exceptions']); + unset($value['exceptions']); + } + + if (array_key_exists('web_link', $value)) { + $this->_usedProperties['webLink'] = true; + $this->webLink = \is_array($value['web_link']) ? new \Symfony\Config\Framework\WebLinkConfig($value['web_link']) : $value['web_link']; + unset($value['web_link']); + } + + if (array_key_exists('lock', $value)) { + $this->_usedProperties['lock'] = true; + $this->lock = \is_array($value['lock']) ? new \Symfony\Config\Framework\LockConfig($value['lock']) : $value['lock']; + unset($value['lock']); + } + + if (array_key_exists('semaphore', $value)) { + $this->_usedProperties['semaphore'] = true; + $this->semaphore = \is_array($value['semaphore']) ? new \Symfony\Config\Framework\SemaphoreConfig($value['semaphore']) : $value['semaphore']; + unset($value['semaphore']); + } + + if (array_key_exists('messenger', $value)) { + $this->_usedProperties['messenger'] = true; + $this->messenger = \is_array($value['messenger']) ? new \Symfony\Config\Framework\MessengerConfig($value['messenger']) : $value['messenger']; + unset($value['messenger']); + } + + if (array_key_exists('scheduler', $value)) { + $this->_usedProperties['scheduler'] = true; + $this->scheduler = \is_array($value['scheduler']) ? new \Symfony\Config\Framework\SchedulerConfig($value['scheduler']) : $value['scheduler']; + unset($value['scheduler']); + } + + if (array_key_exists('disallow_search_engine_index', $value)) { + $this->_usedProperties['disallowSearchEngineIndex'] = true; + $this->disallowSearchEngineIndex = $value['disallow_search_engine_index']; + unset($value['disallow_search_engine_index']); + } + + if (array_key_exists('http_client', $value)) { + $this->_usedProperties['httpClient'] = true; + $this->httpClient = \is_array($value['http_client']) ? new \Symfony\Config\Framework\HttpClientConfig($value['http_client']) : $value['http_client']; + unset($value['http_client']); + } + + if (array_key_exists('mailer', $value)) { + $this->_usedProperties['mailer'] = true; + $this->mailer = new \Symfony\Config\Framework\MailerConfig($value['mailer']); + unset($value['mailer']); + } + + if (array_key_exists('secrets', $value)) { + $this->_usedProperties['secrets'] = true; + $this->secrets = new \Symfony\Config\Framework\SecretsConfig($value['secrets']); + unset($value['secrets']); + } + + if (array_key_exists('notifier', $value)) { + $this->_usedProperties['notifier'] = true; + $this->notifier = \is_array($value['notifier']) ? new \Symfony\Config\Framework\NotifierConfig($value['notifier']) : $value['notifier']; + unset($value['notifier']); + } + + if (array_key_exists('rate_limiter', $value)) { + $this->_usedProperties['rateLimiter'] = true; + $this->rateLimiter = \is_array($value['rate_limiter']) ? new \Symfony\Config\Framework\RateLimiterConfig($value['rate_limiter']) : $value['rate_limiter']; + unset($value['rate_limiter']); + } + + if (array_key_exists('uid', $value)) { + $this->_usedProperties['uid'] = true; + $this->uid = \is_array($value['uid']) ? new \Symfony\Config\Framework\UidConfig($value['uid']) : $value['uid']; + unset($value['uid']); + } + + if (array_key_exists('html_sanitizer', $value)) { + $this->_usedProperties['htmlSanitizer'] = true; + $this->htmlSanitizer = \is_array($value['html_sanitizer']) ? new \Symfony\Config\Framework\HtmlSanitizerConfig($value['html_sanitizer']) : $value['html_sanitizer']; + unset($value['html_sanitizer']); + } + + if (array_key_exists('webhook', $value)) { + $this->_usedProperties['webhook'] = true; + $this->webhook = \is_array($value['webhook']) ? new \Symfony\Config\Framework\WebhookConfig($value['webhook']) : $value['webhook']; + unset($value['webhook']); + } + + if (array_key_exists('remote-event', $value)) { + $this->_usedProperties['remoteevent'] = true; + $this->remoteevent = \is_array($value['remote-event']) ? new \Symfony\Config\Framework\RemoteeventConfig($value['remote-event']) : $value['remote-event']; + unset($value['remote-event']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['secret'])) { + $output['secret'] = $this->secret; + } + if (isset($this->_usedProperties['httpMethodOverride'])) { + $output['http_method_override'] = $this->httpMethodOverride; + } + if (isset($this->_usedProperties['trustXSendfileTypeHeader'])) { + $output['trust_x_sendfile_type_header'] = $this->trustXSendfileTypeHeader; + } + if (isset($this->_usedProperties['ide'])) { + $output['ide'] = $this->ide; + } + if (isset($this->_usedProperties['test'])) { + $output['test'] = $this->test; + } + if (isset($this->_usedProperties['defaultLocale'])) { + $output['default_locale'] = $this->defaultLocale; + } + if (isset($this->_usedProperties['setLocaleFromAcceptLanguage'])) { + $output['set_locale_from_accept_language'] = $this->setLocaleFromAcceptLanguage; + } + if (isset($this->_usedProperties['setContentLanguageFromLocale'])) { + $output['set_content_language_from_locale'] = $this->setContentLanguageFromLocale; + } + if (isset($this->_usedProperties['enabledLocales'])) { + $output['enabled_locales'] = $this->enabledLocales; + } + if (isset($this->_usedProperties['trustedHosts'])) { + $output['trusted_hosts'] = $this->trustedHosts; + } + if (isset($this->_usedProperties['trustedProxies'])) { + $output['trusted_proxies'] = $this->trustedProxies; + } + if (isset($this->_usedProperties['trustedHeaders'])) { + $output['trusted_headers'] = $this->trustedHeaders; + } + if (isset($this->_usedProperties['errorController'])) { + $output['error_controller'] = $this->errorController; + } + if (isset($this->_usedProperties['handleAllThrowables'])) { + $output['handle_all_throwables'] = $this->handleAllThrowables; + } + if (isset($this->_usedProperties['csrfProtection'])) { + $output['csrf_protection'] = $this->csrfProtection->toArray(); + } + if (isset($this->_usedProperties['form'])) { + $output['form'] = $this->form->toArray(); + } + if (isset($this->_usedProperties['httpCache'])) { + $output['http_cache'] = $this->httpCache instanceof \Symfony\Config\Framework\HttpCacheConfig ? $this->httpCache->toArray() : $this->httpCache; + } + if (isset($this->_usedProperties['esi'])) { + $output['esi'] = $this->esi instanceof \Symfony\Config\Framework\EsiConfig ? $this->esi->toArray() : $this->esi; + } + if (isset($this->_usedProperties['ssi'])) { + $output['ssi'] = $this->ssi instanceof \Symfony\Config\Framework\SsiConfig ? $this->ssi->toArray() : $this->ssi; + } + if (isset($this->_usedProperties['fragments'])) { + $output['fragments'] = $this->fragments instanceof \Symfony\Config\Framework\FragmentsConfig ? $this->fragments->toArray() : $this->fragments; + } + if (isset($this->_usedProperties['profiler'])) { + $output['profiler'] = $this->profiler instanceof \Symfony\Config\Framework\ProfilerConfig ? $this->profiler->toArray() : $this->profiler; + } + if (isset($this->_usedProperties['workflows'])) { + $output['workflows'] = $this->workflows instanceof \Symfony\Config\Framework\WorkflowsConfig ? $this->workflows->toArray() : $this->workflows; + } + if (isset($this->_usedProperties['router'])) { + $output['router'] = $this->router instanceof \Symfony\Config\Framework\RouterConfig ? $this->router->toArray() : $this->router; + } + if (isset($this->_usedProperties['session'])) { + $output['session'] = $this->session instanceof \Symfony\Config\Framework\SessionConfig ? $this->session->toArray() : $this->session; + } + if (isset($this->_usedProperties['request'])) { + $output['request'] = $this->request instanceof \Symfony\Config\Framework\RequestConfig ? $this->request->toArray() : $this->request; + } + if (isset($this->_usedProperties['assets'])) { + $output['assets'] = $this->assets instanceof \Symfony\Config\Framework\AssetsConfig ? $this->assets->toArray() : $this->assets; + } + if (isset($this->_usedProperties['assetMapper'])) { + $output['asset_mapper'] = $this->assetMapper instanceof \Symfony\Config\Framework\AssetMapperConfig ? $this->assetMapper->toArray() : $this->assetMapper; + } + if (isset($this->_usedProperties['translator'])) { + $output['translator'] = $this->translator instanceof \Symfony\Config\Framework\TranslatorConfig ? $this->translator->toArray() : $this->translator; + } + if (isset($this->_usedProperties['validation'])) { + $output['validation'] = $this->validation instanceof \Symfony\Config\Framework\ValidationConfig ? $this->validation->toArray() : $this->validation; + } + if (isset($this->_usedProperties['annotations'])) { + $output['annotations'] = $this->annotations instanceof \Symfony\Config\Framework\AnnotationsConfig ? $this->annotations->toArray() : $this->annotations; + } + if (isset($this->_usedProperties['serializer'])) { + $output['serializer'] = $this->serializer instanceof \Symfony\Config\Framework\SerializerConfig ? $this->serializer->toArray() : $this->serializer; + } + if (isset($this->_usedProperties['propertyAccess'])) { + $output['property_access'] = $this->propertyAccess->toArray(); + } + if (isset($this->_usedProperties['typeInfo'])) { + $output['type_info'] = $this->typeInfo->toArray(); + } + if (isset($this->_usedProperties['propertyInfo'])) { + $output['property_info'] = $this->propertyInfo->toArray(); + } + if (isset($this->_usedProperties['cache'])) { + $output['cache'] = $this->cache->toArray(); + } + if (isset($this->_usedProperties['phpErrors'])) { + $output['php_errors'] = $this->phpErrors->toArray(); + } + if (isset($this->_usedProperties['exceptions'])) { + $output['exceptions'] = array_map(fn ($v) => $v->toArray(), $this->exceptions); + } + if (isset($this->_usedProperties['webLink'])) { + $output['web_link'] = $this->webLink instanceof \Symfony\Config\Framework\WebLinkConfig ? $this->webLink->toArray() : $this->webLink; + } + if (isset($this->_usedProperties['lock'])) { + $output['lock'] = $this->lock instanceof \Symfony\Config\Framework\LockConfig ? $this->lock->toArray() : $this->lock; + } + if (isset($this->_usedProperties['semaphore'])) { + $output['semaphore'] = $this->semaphore instanceof \Symfony\Config\Framework\SemaphoreConfig ? $this->semaphore->toArray() : $this->semaphore; + } + if (isset($this->_usedProperties['messenger'])) { + $output['messenger'] = $this->messenger instanceof \Symfony\Config\Framework\MessengerConfig ? $this->messenger->toArray() : $this->messenger; + } + if (isset($this->_usedProperties['scheduler'])) { + $output['scheduler'] = $this->scheduler instanceof \Symfony\Config\Framework\SchedulerConfig ? $this->scheduler->toArray() : $this->scheduler; + } + if (isset($this->_usedProperties['disallowSearchEngineIndex'])) { + $output['disallow_search_engine_index'] = $this->disallowSearchEngineIndex; + } + if (isset($this->_usedProperties['httpClient'])) { + $output['http_client'] = $this->httpClient instanceof \Symfony\Config\Framework\HttpClientConfig ? $this->httpClient->toArray() : $this->httpClient; + } + if (isset($this->_usedProperties['mailer'])) { + $output['mailer'] = $this->mailer->toArray(); + } + if (isset($this->_usedProperties['secrets'])) { + $output['secrets'] = $this->secrets->toArray(); + } + if (isset($this->_usedProperties['notifier'])) { + $output['notifier'] = $this->notifier instanceof \Symfony\Config\Framework\NotifierConfig ? $this->notifier->toArray() : $this->notifier; + } + if (isset($this->_usedProperties['rateLimiter'])) { + $output['rate_limiter'] = $this->rateLimiter instanceof \Symfony\Config\Framework\RateLimiterConfig ? $this->rateLimiter->toArray() : $this->rateLimiter; + } + if (isset($this->_usedProperties['uid'])) { + $output['uid'] = $this->uid instanceof \Symfony\Config\Framework\UidConfig ? $this->uid->toArray() : $this->uid; + } + if (isset($this->_usedProperties['htmlSanitizer'])) { + $output['html_sanitizer'] = $this->htmlSanitizer instanceof \Symfony\Config\Framework\HtmlSanitizerConfig ? $this->htmlSanitizer->toArray() : $this->htmlSanitizer; + } + if (isset($this->_usedProperties['webhook'])) { + $output['webhook'] = $this->webhook instanceof \Symfony\Config\Framework\WebhookConfig ? $this->webhook->toArray() : $this->webhook; + } + if (isset($this->_usedProperties['remoteevent'])) { + $output['remote-event'] = $this->remoteevent instanceof \Symfony\Config\Framework\RemoteeventConfig ? $this->remoteevent->toArray() : $this->remoteevent; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/HwiOauth/ConnectConfig.php b/var/cache/dev/Symfony/Config/HwiOauth/ConnectConfig.php new file mode 100644 index 0000000..aa7fa81 --- /dev/null +++ b/var/cache/dev/Symfony/Config/HwiOauth/ConnectConfig.php @@ -0,0 +1,121 @@ +_usedProperties['confirmation'] = true; + $this->confirmation = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function accountConnector($value): static + { + $this->_usedProperties['accountConnector'] = true; + $this->accountConnector = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function registrationFormHandler($value): static + { + $this->_usedProperties['registrationFormHandler'] = true; + $this->registrationFormHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function registrationForm($value): static + { + $this->_usedProperties['registrationForm'] = true; + $this->registrationForm = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('confirmation', $value)) { + $this->_usedProperties['confirmation'] = true; + $this->confirmation = $value['confirmation']; + unset($value['confirmation']); + } + + if (array_key_exists('account_connector', $value)) { + $this->_usedProperties['accountConnector'] = true; + $this->accountConnector = $value['account_connector']; + unset($value['account_connector']); + } + + if (array_key_exists('registration_form_handler', $value)) { + $this->_usedProperties['registrationFormHandler'] = true; + $this->registrationFormHandler = $value['registration_form_handler']; + unset($value['registration_form_handler']); + } + + if (array_key_exists('registration_form', $value)) { + $this->_usedProperties['registrationForm'] = true; + $this->registrationForm = $value['registration_form']; + unset($value['registration_form']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['confirmation'])) { + $output['confirmation'] = $this->confirmation; + } + if (isset($this->_usedProperties['accountConnector'])) { + $output['account_connector'] = $this->accountConnector; + } + if (isset($this->_usedProperties['registrationFormHandler'])) { + $output['registration_form_handler'] = $this->registrationFormHandler; + } + if (isset($this->_usedProperties['registrationForm'])) { + $output['registration_form'] = $this->registrationForm; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/HwiOauth/ResourceOwnerConfig.php b/var/cache/dev/Symfony/Config/HwiOauth/ResourceOwnerConfig.php new file mode 100644 index 0000000..1ade3af --- /dev/null +++ b/var/cache/dev/Symfony/Config/HwiOauth/ResourceOwnerConfig.php @@ -0,0 +1,427 @@ +_usedProperties['baseUrl'] = true; + $this->baseUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function accessTokenUrl($value): static + { + $this->_usedProperties['accessTokenUrl'] = true; + $this->accessTokenUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function authorizationUrl($value): static + { + $this->_usedProperties['authorizationUrl'] = true; + $this->authorizationUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function requestTokenUrl($value): static + { + $this->_usedProperties['requestTokenUrl'] = true; + $this->requestTokenUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function revokeTokenUrl($value): static + { + $this->_usedProperties['revokeTokenUrl'] = true; + $this->revokeTokenUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function infosUrl($value): static + { + $this->_usedProperties['infosUrl'] = true; + $this->infosUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function clientId($value): static + { + $this->_usedProperties['clientId'] = true; + $this->clientId = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function clientSecret($value): static + { + $this->_usedProperties['clientSecret'] = true; + $this->clientSecret = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function realm($value): static + { + $this->_usedProperties['realm'] = true; + $this->realm = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function scope($value): static + { + $this->_usedProperties['scope'] = true; + $this->scope = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function userResponseClass($value): static + { + $this->_usedProperties['userResponseClass'] = true; + $this->userResponseClass = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function class($value): static + { + $this->_usedProperties['class'] = true; + $this->class = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function type($value): static + { + $this->_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function useAuthorizationToGetToken($value): static + { + $this->_usedProperties['useAuthorizationToGetToken'] = true; + $this->useAuthorizationToGetToken = $value; + + return $this; + } + + /** + * @return $this + */ + public function paths(string $name, mixed $value): static + { + $this->_usedProperties['paths'] = true; + $this->paths[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function options(string $name, mixed $value): static + { + $this->_usedProperties['options'] = true; + $this->options[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('base_url', $value)) { + $this->_usedProperties['baseUrl'] = true; + $this->baseUrl = $value['base_url']; + unset($value['base_url']); + } + + if (array_key_exists('access_token_url', $value)) { + $this->_usedProperties['accessTokenUrl'] = true; + $this->accessTokenUrl = $value['access_token_url']; + unset($value['access_token_url']); + } + + if (array_key_exists('authorization_url', $value)) { + $this->_usedProperties['authorizationUrl'] = true; + $this->authorizationUrl = $value['authorization_url']; + unset($value['authorization_url']); + } + + if (array_key_exists('request_token_url', $value)) { + $this->_usedProperties['requestTokenUrl'] = true; + $this->requestTokenUrl = $value['request_token_url']; + unset($value['request_token_url']); + } + + if (array_key_exists('revoke_token_url', $value)) { + $this->_usedProperties['revokeTokenUrl'] = true; + $this->revokeTokenUrl = $value['revoke_token_url']; + unset($value['revoke_token_url']); + } + + if (array_key_exists('infos_url', $value)) { + $this->_usedProperties['infosUrl'] = true; + $this->infosUrl = $value['infos_url']; + unset($value['infos_url']); + } + + if (array_key_exists('client_id', $value)) { + $this->_usedProperties['clientId'] = true; + $this->clientId = $value['client_id']; + unset($value['client_id']); + } + + if (array_key_exists('client_secret', $value)) { + $this->_usedProperties['clientSecret'] = true; + $this->clientSecret = $value['client_secret']; + unset($value['client_secret']); + } + + if (array_key_exists('realm', $value)) { + $this->_usedProperties['realm'] = true; + $this->realm = $value['realm']; + unset($value['realm']); + } + + if (array_key_exists('scope', $value)) { + $this->_usedProperties['scope'] = true; + $this->scope = $value['scope']; + unset($value['scope']); + } + + if (array_key_exists('user_response_class', $value)) { + $this->_usedProperties['userResponseClass'] = true; + $this->userResponseClass = $value['user_response_class']; + unset($value['user_response_class']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('class', $value)) { + $this->_usedProperties['class'] = true; + $this->class = $value['class']; + unset($value['class']); + } + + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('use_authorization_to_get_token', $value)) { + $this->_usedProperties['useAuthorizationToGetToken'] = true; + $this->useAuthorizationToGetToken = $value['use_authorization_to_get_token']; + unset($value['use_authorization_to_get_token']); + } + + if (array_key_exists('paths', $value)) { + $this->_usedProperties['paths'] = true; + $this->paths = $value['paths']; + unset($value['paths']); + } + + if (array_key_exists('options', $value)) { + $this->_usedProperties['options'] = true; + $this->options = $value['options']; + unset($value['options']); + } + + $this->_extraKeys = $value; + + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['baseUrl'])) { + $output['base_url'] = $this->baseUrl; + } + if (isset($this->_usedProperties['accessTokenUrl'])) { + $output['access_token_url'] = $this->accessTokenUrl; + } + if (isset($this->_usedProperties['authorizationUrl'])) { + $output['authorization_url'] = $this->authorizationUrl; + } + if (isset($this->_usedProperties['requestTokenUrl'])) { + $output['request_token_url'] = $this->requestTokenUrl; + } + if (isset($this->_usedProperties['revokeTokenUrl'])) { + $output['revoke_token_url'] = $this->revokeTokenUrl; + } + if (isset($this->_usedProperties['infosUrl'])) { + $output['infos_url'] = $this->infosUrl; + } + if (isset($this->_usedProperties['clientId'])) { + $output['client_id'] = $this->clientId; + } + if (isset($this->_usedProperties['clientSecret'])) { + $output['client_secret'] = $this->clientSecret; + } + if (isset($this->_usedProperties['realm'])) { + $output['realm'] = $this->realm; + } + if (isset($this->_usedProperties['scope'])) { + $output['scope'] = $this->scope; + } + if (isset($this->_usedProperties['userResponseClass'])) { + $output['user_response_class'] = $this->userResponseClass; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['class'])) { + $output['class'] = $this->class; + } + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['useAuthorizationToGetToken'])) { + $output['use_authorization_to_get_token'] = $this->useAuthorizationToGetToken; + } + if (isset($this->_usedProperties['paths'])) { + $output['paths'] = $this->paths; + } + if (isset($this->_usedProperties['options'])) { + $output['options'] = $this->options; + } + + return $output + $this->_extraKeys; + } + + /** + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function set(string $key, mixed $value): static + { + $this->_extraKeys[$key] = $value; + + return $this; + } + +} diff --git a/var/cache/dev/Symfony/Config/HwiOauthConfig.php b/var/cache/dev/Symfony/Config/HwiOauthConfig.php new file mode 100644 index 0000000..c0e20b4 --- /dev/null +++ b/var/cache/dev/Symfony/Config/HwiOauthConfig.php @@ -0,0 +1,242 @@ + $value + * + * @return $this + */ + public function firewallNames(ParamConfigurator|array $value): static + { + $this->_usedProperties['firewallNames'] = true; + $this->firewallNames = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function targetPathParameter($value): static + { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function targetPathDomainsWhitelist(ParamConfigurator|array $value): static + { + $this->_usedProperties['targetPathDomainsWhitelist'] = true; + $this->targetPathDomainsWhitelist = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useReferer($value): static + { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function failedUseReferer($value): static + { + $this->_usedProperties['failedUseReferer'] = true; + $this->failedUseReferer = $value; + + return $this; + } + + /** + * @default 'hwi_oauth_connect' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failedAuthPath($value): static + { + $this->_usedProperties['failedAuthPath'] = true; + $this->failedAuthPath = $value; + + return $this; + } + + /** + * @default 'IS_AUTHENTICATED_REMEMBERED' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function grantRule($value): static + { + $this->_usedProperties['grantRule'] = true; + $this->grantRule = $value; + + return $this; + } + + public function connect(array $value = []): \Symfony\Config\HwiOauth\ConnectConfig + { + if (null === $this->connect) { + $this->_usedProperties['connect'] = true; + $this->connect = new \Symfony\Config\HwiOauth\ConnectConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "connect()" has already been initialized. You cannot pass values the second time you call connect().'); + } + + return $this->connect; + } + + public function resourceOwner(string $name, array $value = []): \Symfony\Config\HwiOauth\ResourceOwnerConfig + { + if (!isset($this->resourceOwners[$name])) { + $this->_usedProperties['resourceOwners'] = true; + $this->resourceOwners[$name] = new \Symfony\Config\HwiOauth\ResourceOwnerConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "resourceOwner()" has already been initialized. You cannot pass values the second time you call resourceOwner().'); + } + + return $this->resourceOwners[$name]; + } + + public function getExtensionAlias(): string + { + return 'hwi_oauth'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('firewall_names', $value)) { + $this->_usedProperties['firewallNames'] = true; + $this->firewallNames = $value['firewall_names']; + unset($value['firewall_names']); + } + + if (array_key_exists('target_path_parameter', $value)) { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value['target_path_parameter']; + unset($value['target_path_parameter']); + } + + if (array_key_exists('target_path_domains_whitelist', $value)) { + $this->_usedProperties['targetPathDomainsWhitelist'] = true; + $this->targetPathDomainsWhitelist = $value['target_path_domains_whitelist']; + unset($value['target_path_domains_whitelist']); + } + + if (array_key_exists('use_referer', $value)) { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value['use_referer']; + unset($value['use_referer']); + } + + if (array_key_exists('failed_use_referer', $value)) { + $this->_usedProperties['failedUseReferer'] = true; + $this->failedUseReferer = $value['failed_use_referer']; + unset($value['failed_use_referer']); + } + + if (array_key_exists('failed_auth_path', $value)) { + $this->_usedProperties['failedAuthPath'] = true; + $this->failedAuthPath = $value['failed_auth_path']; + unset($value['failed_auth_path']); + } + + if (array_key_exists('grant_rule', $value)) { + $this->_usedProperties['grantRule'] = true; + $this->grantRule = $value['grant_rule']; + unset($value['grant_rule']); + } + + if (array_key_exists('connect', $value)) { + $this->_usedProperties['connect'] = true; + $this->connect = new \Symfony\Config\HwiOauth\ConnectConfig($value['connect']); + unset($value['connect']); + } + + if (array_key_exists('resource_owners', $value)) { + $this->_usedProperties['resourceOwners'] = true; + $this->resourceOwners = array_map(fn ($v) => new \Symfony\Config\HwiOauth\ResourceOwnerConfig($v), $value['resource_owners']); + unset($value['resource_owners']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['firewallNames'])) { + $output['firewall_names'] = $this->firewallNames; + } + if (isset($this->_usedProperties['targetPathParameter'])) { + $output['target_path_parameter'] = $this->targetPathParameter; + } + if (isset($this->_usedProperties['targetPathDomainsWhitelist'])) { + $output['target_path_domains_whitelist'] = $this->targetPathDomainsWhitelist; + } + if (isset($this->_usedProperties['useReferer'])) { + $output['use_referer'] = $this->useReferer; + } + if (isset($this->_usedProperties['failedUseReferer'])) { + $output['failed_use_referer'] = $this->failedUseReferer; + } + if (isset($this->_usedProperties['failedAuthPath'])) { + $output['failed_auth_path'] = $this->failedAuthPath; + } + if (isset($this->_usedProperties['grantRule'])) { + $output['grant_rule'] = $this->grantRule; + } + if (isset($this->_usedProperties['connect'])) { + $output['connect'] = $this->connect->toArray(); + } + if (isset($this->_usedProperties['resourceOwners'])) { + $output['resource_owners'] = array_map(fn ($v) => $v->toArray(), $this->resourceOwners); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuance/EncryptionConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuance/EncryptionConfig.php new file mode 100644 index 0000000..afae79c --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuance/EncryptionConfig.php @@ -0,0 +1,124 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * The key encryption algorithm is used to encrypt the token. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function keyEncryptionAlgorithm($value): static + { + $this->_usedProperties['keyEncryptionAlgorithm'] = true; + $this->keyEncryptionAlgorithm = $value; + + return $this; + } + + /** + * The key encryption algorithm is used to encrypt the token. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function contentEncryptionAlgorithm($value): static + { + $this->_usedProperties['contentEncryptionAlgorithm'] = true; + $this->contentEncryptionAlgorithm = $value; + + return $this; + } + + /** + * The encryption key. It shall be JWK encoded. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function key($value): static + { + $this->_usedProperties['key'] = true; + $this->key = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('key_encryption_algorithm', $value)) { + $this->_usedProperties['keyEncryptionAlgorithm'] = true; + $this->keyEncryptionAlgorithm = $value['key_encryption_algorithm']; + unset($value['key_encryption_algorithm']); + } + + if (array_key_exists('content_encryption_algorithm', $value)) { + $this->_usedProperties['contentEncryptionAlgorithm'] = true; + $this->contentEncryptionAlgorithm = $value['content_encryption_algorithm']; + unset($value['content_encryption_algorithm']); + } + + if (array_key_exists('key', $value)) { + $this->_usedProperties['key'] = true; + $this->key = $value['key']; + unset($value['key']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['keyEncryptionAlgorithm'])) { + $output['key_encryption_algorithm'] = $this->keyEncryptionAlgorithm; + } + if (isset($this->_usedProperties['contentEncryptionAlgorithm'])) { + $output['content_encryption_algorithm'] = $this->contentEncryptionAlgorithm; + } + if (isset($this->_usedProperties['key'])) { + $output['key'] = $this->key; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuance/SignatureConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuance/SignatureConfig.php new file mode 100644 index 0000000..ab31b6e --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuance/SignatureConfig.php @@ -0,0 +1,77 @@ +_usedProperties['algorithm'] = true; + $this->algorithm = $value; + + return $this; + } + + /** + * The signature key. It shall be JWK encoded. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function key($value): static + { + $this->_usedProperties['key'] = true; + $this->key = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('algorithm', $value)) { + $this->_usedProperties['algorithm'] = true; + $this->algorithm = $value['algorithm']; + unset($value['algorithm']); + } + + if (array_key_exists('key', $value)) { + $this->_usedProperties['key'] = true; + $this->key = $value['key']; + unset($value['key']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['algorithm'])) { + $output['algorithm'] = $this->algorithm; + } + if (isset($this->_usedProperties['key'])) { + $output['key'] = $this->key; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuanceConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuanceConfig.php new file mode 100644 index 0000000..2e421a7 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenIssuanceConfig.php @@ -0,0 +1,113 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + public function signature(array $value = []): \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\SignatureConfig + { + if (null === $this->signature) { + $this->_usedProperties['signature'] = true; + $this->signature = new \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\SignatureConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "signature()" has already been initialized. You cannot pass values the second time you call signature().'); + } + + return $this->signature; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false} + * @return \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\EncryptionConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\EncryptionConfig : static) + */ + public function encryption(array $value = []): \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\EncryptionConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['encryption'] = true; + $this->encryption = $value; + + return $this; + } + + if (!$this->encryption instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\EncryptionConfig) { + $this->_usedProperties['encryption'] = true; + $this->encryption = new \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\EncryptionConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "encryption()" has already been initialized. You cannot pass values the second time you call encryption().'); + } + + return $this->encryption; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('signature', $value)) { + $this->_usedProperties['signature'] = true; + $this->signature = new \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\SignatureConfig($value['signature']); + unset($value['signature']); + } + + if (array_key_exists('encryption', $value)) { + $this->_usedProperties['encryption'] = true; + $this->encryption = \is_array($value['encryption']) ? new \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\EncryptionConfig($value['encryption']) : $value['encryption']; + unset($value['encryption']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['signature'])) { + $output['signature'] = $this->signature->toArray(); + } + if (isset($this->_usedProperties['encryption'])) { + $output['encryption'] = $this->encryption instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuance\EncryptionConfig ? $this->encryption->toArray() : $this->encryption; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerification/EncryptionConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerification/EncryptionConfig.php new file mode 100644 index 0000000..32c5071 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerification/EncryptionConfig.php @@ -0,0 +1,169 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * If enable, non-encrypted tokens or tokens that failed during decryption or verification processes are accepted. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function continueOnDecryptionFailure($value): static + { + $this->_usedProperties['continueOnDecryptionFailure'] = true; + $this->continueOnDecryptionFailure = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function headerCheckers(ParamConfigurator|array $value): static + { + $this->_usedProperties['headerCheckers'] = true; + $this->headerCheckers = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function allowedKeyEncryptionAlgorithms(ParamConfigurator|array $value): static + { + $this->_usedProperties['allowedKeyEncryptionAlgorithms'] = true; + $this->allowedKeyEncryptionAlgorithms = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function allowedContentEncryptionAlgorithms(ParamConfigurator|array $value): static + { + $this->_usedProperties['allowedContentEncryptionAlgorithms'] = true; + $this->allowedContentEncryptionAlgorithms = $value; + + return $this; + } + + /** + * The encryption keyset. It shall be JWKSet encoded. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function keyset($value): static + { + $this->_usedProperties['keyset'] = true; + $this->keyset = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('continue_on_decryption_failure', $value)) { + $this->_usedProperties['continueOnDecryptionFailure'] = true; + $this->continueOnDecryptionFailure = $value['continue_on_decryption_failure']; + unset($value['continue_on_decryption_failure']); + } + + if (array_key_exists('header_checkers', $value)) { + $this->_usedProperties['headerCheckers'] = true; + $this->headerCheckers = $value['header_checkers']; + unset($value['header_checkers']); + } + + if (array_key_exists('allowed_key_encryption_algorithms', $value)) { + $this->_usedProperties['allowedKeyEncryptionAlgorithms'] = true; + $this->allowedKeyEncryptionAlgorithms = $value['allowed_key_encryption_algorithms']; + unset($value['allowed_key_encryption_algorithms']); + } + + if (array_key_exists('allowed_content_encryption_algorithms', $value)) { + $this->_usedProperties['allowedContentEncryptionAlgorithms'] = true; + $this->allowedContentEncryptionAlgorithms = $value['allowed_content_encryption_algorithms']; + unset($value['allowed_content_encryption_algorithms']); + } + + if (array_key_exists('keyset', $value)) { + $this->_usedProperties['keyset'] = true; + $this->keyset = $value['keyset']; + unset($value['keyset']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['continueOnDecryptionFailure'])) { + $output['continue_on_decryption_failure'] = $this->continueOnDecryptionFailure; + } + if (isset($this->_usedProperties['headerCheckers'])) { + $output['header_checkers'] = $this->headerCheckers; + } + if (isset($this->_usedProperties['allowedKeyEncryptionAlgorithms'])) { + $output['allowed_key_encryption_algorithms'] = $this->allowedKeyEncryptionAlgorithms; + } + if (isset($this->_usedProperties['allowedContentEncryptionAlgorithms'])) { + $output['allowed_content_encryption_algorithms'] = $this->allowedContentEncryptionAlgorithms; + } + if (isset($this->_usedProperties['keyset'])) { + $output['keyset'] = $this->keyset; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerification/SignatureConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerification/SignatureConfig.php new file mode 100644 index 0000000..774a80a --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerification/SignatureConfig.php @@ -0,0 +1,145 @@ + $value + * + * @return $this + */ + public function headerCheckers(ParamConfigurator|array $value): static + { + $this->_usedProperties['headerCheckers'] = true; + $this->headerCheckers = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function claimCheckers(ParamConfigurator|array $value): static + { + $this->_usedProperties['claimCheckers'] = true; + $this->claimCheckers = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function mandatoryClaims(ParamConfigurator|array $value): static + { + $this->_usedProperties['mandatoryClaims'] = true; + $this->mandatoryClaims = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function allowedAlgorithms(ParamConfigurator|array $value): static + { + $this->_usedProperties['allowedAlgorithms'] = true; + $this->allowedAlgorithms = $value; + + return $this; + } + + /** + * The signature keyset. It shall be JWKSet encoded. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function keyset($value): static + { + $this->_usedProperties['keyset'] = true; + $this->keyset = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('header_checkers', $value)) { + $this->_usedProperties['headerCheckers'] = true; + $this->headerCheckers = $value['header_checkers']; + unset($value['header_checkers']); + } + + if (array_key_exists('claim_checkers', $value)) { + $this->_usedProperties['claimCheckers'] = true; + $this->claimCheckers = $value['claim_checkers']; + unset($value['claim_checkers']); + } + + if (array_key_exists('mandatory_claims', $value)) { + $this->_usedProperties['mandatoryClaims'] = true; + $this->mandatoryClaims = $value['mandatory_claims']; + unset($value['mandatory_claims']); + } + + if (array_key_exists('allowed_algorithms', $value)) { + $this->_usedProperties['allowedAlgorithms'] = true; + $this->allowedAlgorithms = $value['allowed_algorithms']; + unset($value['allowed_algorithms']); + } + + if (array_key_exists('keyset', $value)) { + $this->_usedProperties['keyset'] = true; + $this->keyset = $value['keyset']; + unset($value['keyset']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['headerCheckers'])) { + $output['header_checkers'] = $this->headerCheckers; + } + if (isset($this->_usedProperties['claimCheckers'])) { + $output['claim_checkers'] = $this->claimCheckers; + } + if (isset($this->_usedProperties['mandatoryClaims'])) { + $output['mandatory_claims'] = $this->mandatoryClaims; + } + if (isset($this->_usedProperties['allowedAlgorithms'])) { + $output['allowed_algorithms'] = $this->allowedAlgorithms; + } + if (isset($this->_usedProperties['keyset'])) { + $output['keyset'] = $this->keyset; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerificationConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerificationConfig.php new file mode 100644 index 0000000..49325d9 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/AccessTokenVerificationConfig.php @@ -0,0 +1,116 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default {"header_checkers":[],"claim_checkers":["exp_with_clock_skew","iat_with_clock_skew","nbf_with_clock_skew"],"mandatory_claims":[],"allowed_algorithms":[]} + */ + public function signature(array $value = []): \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\SignatureConfig + { + if (null === $this->signature) { + $this->_usedProperties['signature'] = true; + $this->signature = new \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\SignatureConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "signature()" has already been initialized. You cannot pass values the second time you call signature().'); + } + + return $this->signature; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"continue_on_decryption_failure":false,"header_checkers":["iat_with_clock_skew","nbf_with_clock_skew","exp_with_clock_skew"],"allowed_key_encryption_algorithms":[],"allowed_content_encryption_algorithms":[]} + * @return \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\EncryptionConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\EncryptionConfig : static) + */ + public function encryption(array $value = []): \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\EncryptionConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['encryption'] = true; + $this->encryption = $value; + + return $this; + } + + if (!$this->encryption instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\EncryptionConfig) { + $this->_usedProperties['encryption'] = true; + $this->encryption = new \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\EncryptionConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "encryption()" has already been initialized. You cannot pass values the second time you call encryption().'); + } + + return $this->encryption; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('signature', $value)) { + $this->_usedProperties['signature'] = true; + $this->signature = new \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\SignatureConfig($value['signature']); + unset($value['signature']); + } + + if (array_key_exists('encryption', $value)) { + $this->_usedProperties['encryption'] = true; + $this->encryption = \is_array($value['encryption']) ? new \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\EncryptionConfig($value['encryption']) : $value['encryption']; + unset($value['encryption']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['signature'])) { + $output['signature'] = $this->signature->toArray(); + } + if (isset($this->_usedProperties['encryption'])) { + $output['encryption'] = $this->encryption instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenVerification\EncryptionConfig ? $this->encryption->toArray() : $this->encryption; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/ApiPlatformConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/ApiPlatformConfig.php new file mode 100644 index 0000000..57bdced --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/ApiPlatformConfig.php @@ -0,0 +1,124 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * The login check path to add in OpenAPI. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkPath($value): static + { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value; + + return $this; + } + + /** + * The path to the username in the JSON body. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function usernamePath($value): static + { + $this->_usedProperties['usernamePath'] = true; + $this->usernamePath = $value; + + return $this; + } + + /** + * The path to the password in the JSON body. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passwordPath($value): static + { + $this->_usedProperties['passwordPath'] = true; + $this->passwordPath = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('check_path', $value)) { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value['check_path']; + unset($value['check_path']); + } + + if (array_key_exists('username_path', $value)) { + $this->_usedProperties['usernamePath'] = true; + $this->usernamePath = $value['username_path']; + unset($value['username_path']); + } + + if (array_key_exists('password_path', $value)) { + $this->_usedProperties['passwordPath'] = true; + $this->passwordPath = $value['password_path']; + unset($value['password_path']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['checkPath'])) { + $output['check_path'] = $this->checkPath; + } + if (isset($this->_usedProperties['usernamePath'])) { + $output['username_path'] = $this->usernamePath; + } + if (isset($this->_usedProperties['passwordPath'])) { + $output['password_path'] = $this->passwordPath; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/BlocklistTokenConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/BlocklistTokenConfig.php new file mode 100644 index 0000000..ef067b1 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/BlocklistTokenConfig.php @@ -0,0 +1,76 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * Storage to track blocked tokens + * @default 'cache.app' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cache($value): static + { + $this->_usedProperties['cache'] = true; + $this->cache = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('cache', $value)) { + $this->_usedProperties['cache'] = true; + $this->cache = $value['cache']; + unset($value['cache']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['cache'])) { + $output['cache'] = $this->cache; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/EncoderConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/EncoderConfig.php new file mode 100644 index 0000000..d86d1bb --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/EncoderConfig.php @@ -0,0 +1,75 @@ +_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default 'RS256' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function signatureAlgorithm($value): static + { + $this->_usedProperties['signatureAlgorithm'] = true; + $this->signatureAlgorithm = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('signature_algorithm', $value)) { + $this->_usedProperties['signatureAlgorithm'] = true; + $this->signatureAlgorithm = $value['signature_algorithm']; + unset($value['signature_algorithm']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['signatureAlgorithm'])) { + $output['signature_algorithm'] = $this->signatureAlgorithm; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/SetCookiesConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/SetCookiesConfig.php new file mode 100644 index 0000000..607f318 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/SetCookiesConfig.php @@ -0,0 +1,214 @@ +_usedProperties['lifetime'] = true; + $this->lifetime = $value; + + return $this; + } + + /** + * @default 'lax' + * @param ParamConfigurator|'none'|'lax'|'strict' $value + * @return $this + */ + public function samesite($value): static + { + $this->_usedProperties['samesite'] = true; + $this->samesite = $value; + + return $this; + } + + /** + * @default '/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function domain($value): static + { + $this->_usedProperties['domain'] = true; + $this->domain = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function secure($value): static + { + $this->_usedProperties['secure'] = true; + $this->secure = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function httpOnly($value): static + { + $this->_usedProperties['httpOnly'] = true; + $this->httpOnly = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function partitioned($value): static + { + $this->_usedProperties['partitioned'] = true; + $this->partitioned = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function split(ParamConfigurator|array $value): static + { + $this->_usedProperties['split'] = true; + $this->split = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('lifetime', $value)) { + $this->_usedProperties['lifetime'] = true; + $this->lifetime = $value['lifetime']; + unset($value['lifetime']); + } + + if (array_key_exists('samesite', $value)) { + $this->_usedProperties['samesite'] = true; + $this->samesite = $value['samesite']; + unset($value['samesite']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('domain', $value)) { + $this->_usedProperties['domain'] = true; + $this->domain = $value['domain']; + unset($value['domain']); + } + + if (array_key_exists('secure', $value)) { + $this->_usedProperties['secure'] = true; + $this->secure = $value['secure']; + unset($value['secure']); + } + + if (array_key_exists('httpOnly', $value)) { + $this->_usedProperties['httpOnly'] = true; + $this->httpOnly = $value['httpOnly']; + unset($value['httpOnly']); + } + + if (array_key_exists('partitioned', $value)) { + $this->_usedProperties['partitioned'] = true; + $this->partitioned = $value['partitioned']; + unset($value['partitioned']); + } + + if (array_key_exists('split', $value)) { + $this->_usedProperties['split'] = true; + $this->split = $value['split']; + unset($value['split']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['lifetime'])) { + $output['lifetime'] = $this->lifetime; + } + if (isset($this->_usedProperties['samesite'])) { + $output['samesite'] = $this->samesite; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['domain'])) { + $output['domain'] = $this->domain; + } + if (isset($this->_usedProperties['secure'])) { + $output['secure'] = $this->secure; + } + if (isset($this->_usedProperties['httpOnly'])) { + $output['httpOnly'] = $this->httpOnly; + } + if (isset($this->_usedProperties['partitioned'])) { + $output['partitioned'] = $this->partitioned; + } + if (isset($this->_usedProperties['split'])) { + $output['split'] = $this->split; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/AuthorizationHeaderConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/AuthorizationHeaderConfig.php new file mode 100644 index 0000000..0bcf5ca --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/AuthorizationHeaderConfig.php @@ -0,0 +1,98 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default 'Bearer' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function prefix($value): static + { + $this->_usedProperties['prefix'] = true; + $this->prefix = $value; + + return $this; + } + + /** + * @default 'Authorization' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function name($value): static + { + $this->_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('prefix', $value)) { + $this->_usedProperties['prefix'] = true; + $this->prefix = $value['prefix']; + unset($value['prefix']); + } + + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['prefix'])) { + $output['prefix'] = $this->prefix; + } + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/CookieConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/CookieConfig.php new file mode 100644 index 0000000..ef57e22 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/CookieConfig.php @@ -0,0 +1,75 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default 'BEARER' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function name($value): static + { + $this->_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/QueryParameterConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/QueryParameterConfig.php new file mode 100644 index 0000000..5fc3206 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/QueryParameterConfig.php @@ -0,0 +1,75 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default 'bearer' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function name($value): static + { + $this->_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/SplitCookieConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/SplitCookieConfig.php new file mode 100644 index 0000000..9f5af87 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractors/SplitCookieConfig.php @@ -0,0 +1,75 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function cookies(ParamConfigurator|array $value): static + { + $this->_usedProperties['cookies'] = true; + $this->cookies = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('cookies', $value)) { + $this->_usedProperties['cookies'] = true; + $this->cookies = $value['cookies']; + unset($value['cookies']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['cookies'])) { + $output['cookies'] = $this->cookies; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractorsConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractorsConfig.php new file mode 100644 index 0000000..956c79c --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthentication/TokenExtractorsConfig.php @@ -0,0 +1,166 @@ +authorizationHeader) { + $this->_usedProperties['authorizationHeader'] = true; + $this->authorizationHeader = new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\AuthorizationHeaderConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "authorizationHeader()" has already been initialized. You cannot pass values the second time you call authorizationHeader().'); + } + + return $this->authorizationHeader; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"name":"BEARER"} + * @return \Symfony\Config\LexikJwtAuthentication\TokenExtractors\CookieConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\TokenExtractors\CookieConfig : static) + */ + public function cookie(array $value = []): \Symfony\Config\LexikJwtAuthentication\TokenExtractors\CookieConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['cookie'] = true; + $this->cookie = $value; + + return $this; + } + + if (!$this->cookie instanceof \Symfony\Config\LexikJwtAuthentication\TokenExtractors\CookieConfig) { + $this->_usedProperties['cookie'] = true; + $this->cookie = new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\CookieConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "cookie()" has already been initialized. You cannot pass values the second time you call cookie().'); + } + + return $this->cookie; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"name":"bearer"} + * @return \Symfony\Config\LexikJwtAuthentication\TokenExtractors\QueryParameterConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\TokenExtractors\QueryParameterConfig : static) + */ + public function queryParameter(array $value = []): \Symfony\Config\LexikJwtAuthentication\TokenExtractors\QueryParameterConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['queryParameter'] = true; + $this->queryParameter = $value; + + return $this; + } + + if (!$this->queryParameter instanceof \Symfony\Config\LexikJwtAuthentication\TokenExtractors\QueryParameterConfig) { + $this->_usedProperties['queryParameter'] = true; + $this->queryParameter = new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\QueryParameterConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "queryParameter()" has already been initialized. You cannot pass values the second time you call queryParameter().'); + } + + return $this->queryParameter; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"cookies":[]} + * @return \Symfony\Config\LexikJwtAuthentication\TokenExtractors\SplitCookieConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\TokenExtractors\SplitCookieConfig : static) + */ + public function splitCookie(array $value = []): \Symfony\Config\LexikJwtAuthentication\TokenExtractors\SplitCookieConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['splitCookie'] = true; + $this->splitCookie = $value; + + return $this; + } + + if (!$this->splitCookie instanceof \Symfony\Config\LexikJwtAuthentication\TokenExtractors\SplitCookieConfig) { + $this->_usedProperties['splitCookie'] = true; + $this->splitCookie = new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\SplitCookieConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "splitCookie()" has already been initialized. You cannot pass values the second time you call splitCookie().'); + } + + return $this->splitCookie; + } + + public function __construct(array $value = []) + { + if (array_key_exists('authorization_header', $value)) { + $this->_usedProperties['authorizationHeader'] = true; + $this->authorizationHeader = new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\AuthorizationHeaderConfig($value['authorization_header']); + unset($value['authorization_header']); + } + + if (array_key_exists('cookie', $value)) { + $this->_usedProperties['cookie'] = true; + $this->cookie = \is_array($value['cookie']) ? new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\CookieConfig($value['cookie']) : $value['cookie']; + unset($value['cookie']); + } + + if (array_key_exists('query_parameter', $value)) { + $this->_usedProperties['queryParameter'] = true; + $this->queryParameter = \is_array($value['query_parameter']) ? new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\QueryParameterConfig($value['query_parameter']) : $value['query_parameter']; + unset($value['query_parameter']); + } + + if (array_key_exists('split_cookie', $value)) { + $this->_usedProperties['splitCookie'] = true; + $this->splitCookie = \is_array($value['split_cookie']) ? new \Symfony\Config\LexikJwtAuthentication\TokenExtractors\SplitCookieConfig($value['split_cookie']) : $value['split_cookie']; + unset($value['split_cookie']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['authorizationHeader'])) { + $output['authorization_header'] = $this->authorizationHeader->toArray(); + } + if (isset($this->_usedProperties['cookie'])) { + $output['cookie'] = $this->cookie instanceof \Symfony\Config\LexikJwtAuthentication\TokenExtractors\CookieConfig ? $this->cookie->toArray() : $this->cookie; + } + if (isset($this->_usedProperties['queryParameter'])) { + $output['query_parameter'] = $this->queryParameter instanceof \Symfony\Config\LexikJwtAuthentication\TokenExtractors\QueryParameterConfig ? $this->queryParameter->toArray() : $this->queryParameter; + } + if (isset($this->_usedProperties['splitCookie'])) { + $output['split_cookie'] = $this->splitCookie instanceof \Symfony\Config\LexikJwtAuthentication\TokenExtractors\SplitCookieConfig ? $this->splitCookie->toArray() : $this->splitCookie; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/LexikJwtAuthenticationConfig.php b/var/cache/dev/Symfony/Config/LexikJwtAuthenticationConfig.php new file mode 100644 index 0000000..ef23721 --- /dev/null +++ b/var/cache/dev/Symfony/Config/LexikJwtAuthenticationConfig.php @@ -0,0 +1,469 @@ +_usedProperties['publicKey'] = true; + $this->publicKey = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function additionalPublicKeys(ParamConfigurator|array $value): static + { + $this->_usedProperties['additionalPublicKeys'] = true; + $this->additionalPublicKeys = $value; + + return $this; + } + + /** + * The key used to sign tokens. It can be a raw secret (for HMAC), a raw RSA/ECDSA key or the path to a file itself being plaintext or PEM. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function secretKey($value): static + { + $this->_usedProperties['secretKey'] = true; + $this->secretKey = $value; + + return $this; + } + + /** + * The key passphrase (useless for HMAC) + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passPhrase($value): static + { + $this->_usedProperties['passPhrase'] = true; + $this->passPhrase = $value; + + return $this; + } + + /** + * @default 3600 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function tokenTtl($value): static + { + $this->_usedProperties['tokenTtl'] = true; + $this->tokenTtl = $value; + + return $this; + } + + /** + * Allow tokens without "exp" claim (i.e. indefinitely valid, no lifetime) to be considered valid. Caution: usage of this should be rare. + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowNoExpiration($value): static + { + $this->_usedProperties['allowNoExpiration'] = true; + $this->allowNoExpiration = $value; + + return $this; + } + + /** + * @default 0 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function clockSkew($value): static + { + $this->_usedProperties['clockSkew'] = true; + $this->clockSkew = $value; + + return $this; + } + + /** + * @default {"service":"lexik_jwt_authentication.encoder.lcobucci","signature_algorithm":"RS256"} + */ + public function encoder(array $value = []): \Symfony\Config\LexikJwtAuthentication\EncoderConfig + { + if (null === $this->encoder) { + $this->_usedProperties['encoder'] = true; + $this->encoder = new \Symfony\Config\LexikJwtAuthentication\EncoderConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "encoder()" has already been initialized. You cannot pass values the second time you call encoder().'); + } + + return $this->encoder; + } + + /** + * @default 'username' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function userIdClaim($value): static + { + $this->_usedProperties['userIdClaim'] = true; + $this->userIdClaim = $value; + + return $this; + } + + /** + * @default {"authorization_header":{"enabled":true,"prefix":"Bearer","name":"Authorization"},"cookie":{"enabled":false,"name":"BEARER"},"query_parameter":{"enabled":false,"name":"bearer"},"split_cookie":{"enabled":false,"cookies":[]}} + */ + public function tokenExtractors(array $value = []): \Symfony\Config\LexikJwtAuthentication\TokenExtractorsConfig + { + if (null === $this->tokenExtractors) { + $this->_usedProperties['tokenExtractors'] = true; + $this->tokenExtractors = new \Symfony\Config\LexikJwtAuthentication\TokenExtractorsConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "tokenExtractors()" has already been initialized. You cannot pass values the second time you call tokenExtractors().'); + } + + return $this->tokenExtractors; + } + + /** + * @default true + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function removeTokenFromBodyWhenCookiesUsed($value): static + { + $this->_usedProperties['removeTokenFromBodyWhenCookiesUsed'] = true; + $this->removeTokenFromBodyWhenCookiesUsed = $value; + + return $this; + } + + public function setCookies(string $name, array $value = []): \Symfony\Config\LexikJwtAuthentication\SetCookiesConfig + { + if (!isset($this->setCookies[$name])) { + $this->_usedProperties['setCookies'] = true; + $this->setCookies[$name] = new \Symfony\Config\LexikJwtAuthentication\SetCookiesConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "setCookies()" has already been initialized. You cannot pass values the second time you call setCookies().'); + } + + return $this->setCookies[$name]; + } + + /** + * @template TValue + * @param TValue $value + * API Platform compatibility: add check_path in OpenAPI documentation. + * @default {"enabled":false,"check_path":null,"username_path":null,"password_path":null} + * @return \Symfony\Config\LexikJwtAuthentication\ApiPlatformConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\ApiPlatformConfig : static) + */ + public function apiPlatform(array $value = []): \Symfony\Config\LexikJwtAuthentication\ApiPlatformConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['apiPlatform'] = true; + $this->apiPlatform = $value; + + return $this; + } + + if (!$this->apiPlatform instanceof \Symfony\Config\LexikJwtAuthentication\ApiPlatformConfig) { + $this->_usedProperties['apiPlatform'] = true; + $this->apiPlatform = new \Symfony\Config\LexikJwtAuthentication\ApiPlatformConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "apiPlatform()" has already been initialized. You cannot pass values the second time you call apiPlatform().'); + } + + return $this->apiPlatform; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"signature":[],"encryption":{"enabled":false}} + * @return \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuanceConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuanceConfig : static) + */ + public function accessTokenIssuance(array $value = []): \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuanceConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['accessTokenIssuance'] = true; + $this->accessTokenIssuance = $value; + + return $this; + } + + if (!$this->accessTokenIssuance instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuanceConfig) { + $this->_usedProperties['accessTokenIssuance'] = true; + $this->accessTokenIssuance = new \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuanceConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "accessTokenIssuance()" has already been initialized. You cannot pass values the second time you call accessTokenIssuance().'); + } + + return $this->accessTokenIssuance; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"signature":{"header_checkers":[],"claim_checkers":["exp_with_clock_skew","iat_with_clock_skew","nbf_with_clock_skew"],"mandatory_claims":[],"allowed_algorithms":[]},"encryption":{"enabled":false,"continue_on_decryption_failure":false,"header_checkers":["iat_with_clock_skew","nbf_with_clock_skew","exp_with_clock_skew"],"allowed_key_encryption_algorithms":[],"allowed_content_encryption_algorithms":[]}} + * @return \Symfony\Config\LexikJwtAuthentication\AccessTokenVerificationConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\AccessTokenVerificationConfig : static) + */ + public function accessTokenVerification(array $value = []): \Symfony\Config\LexikJwtAuthentication\AccessTokenVerificationConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['accessTokenVerification'] = true; + $this->accessTokenVerification = $value; + + return $this; + } + + if (!$this->accessTokenVerification instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenVerificationConfig) { + $this->_usedProperties['accessTokenVerification'] = true; + $this->accessTokenVerification = new \Symfony\Config\LexikJwtAuthentication\AccessTokenVerificationConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "accessTokenVerification()" has already been initialized. You cannot pass values the second time you call accessTokenVerification().'); + } + + return $this->accessTokenVerification; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"cache":"cache.app"} + * @return \Symfony\Config\LexikJwtAuthentication\BlocklistTokenConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\LexikJwtAuthentication\BlocklistTokenConfig : static) + */ + public function blocklistToken(array $value = []): \Symfony\Config\LexikJwtAuthentication\BlocklistTokenConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['blocklistToken'] = true; + $this->blocklistToken = $value; + + return $this; + } + + if (!$this->blocklistToken instanceof \Symfony\Config\LexikJwtAuthentication\BlocklistTokenConfig) { + $this->_usedProperties['blocklistToken'] = true; + $this->blocklistToken = new \Symfony\Config\LexikJwtAuthentication\BlocklistTokenConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "blocklistToken()" has already been initialized. You cannot pass values the second time you call blocklistToken().'); + } + + return $this->blocklistToken; + } + + public function getExtensionAlias(): string + { + return 'lexik_jwt_authentication'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('public_key', $value)) { + $this->_usedProperties['publicKey'] = true; + $this->publicKey = $value['public_key']; + unset($value['public_key']); + } + + if (array_key_exists('additional_public_keys', $value)) { + $this->_usedProperties['additionalPublicKeys'] = true; + $this->additionalPublicKeys = $value['additional_public_keys']; + unset($value['additional_public_keys']); + } + + if (array_key_exists('secret_key', $value)) { + $this->_usedProperties['secretKey'] = true; + $this->secretKey = $value['secret_key']; + unset($value['secret_key']); + } + + if (array_key_exists('pass_phrase', $value)) { + $this->_usedProperties['passPhrase'] = true; + $this->passPhrase = $value['pass_phrase']; + unset($value['pass_phrase']); + } + + if (array_key_exists('token_ttl', $value)) { + $this->_usedProperties['tokenTtl'] = true; + $this->tokenTtl = $value['token_ttl']; + unset($value['token_ttl']); + } + + if (array_key_exists('allow_no_expiration', $value)) { + $this->_usedProperties['allowNoExpiration'] = true; + $this->allowNoExpiration = $value['allow_no_expiration']; + unset($value['allow_no_expiration']); + } + + if (array_key_exists('clock_skew', $value)) { + $this->_usedProperties['clockSkew'] = true; + $this->clockSkew = $value['clock_skew']; + unset($value['clock_skew']); + } + + if (array_key_exists('encoder', $value)) { + $this->_usedProperties['encoder'] = true; + $this->encoder = new \Symfony\Config\LexikJwtAuthentication\EncoderConfig($value['encoder']); + unset($value['encoder']); + } + + if (array_key_exists('user_id_claim', $value)) { + $this->_usedProperties['userIdClaim'] = true; + $this->userIdClaim = $value['user_id_claim']; + unset($value['user_id_claim']); + } + + if (array_key_exists('token_extractors', $value)) { + $this->_usedProperties['tokenExtractors'] = true; + $this->tokenExtractors = new \Symfony\Config\LexikJwtAuthentication\TokenExtractorsConfig($value['token_extractors']); + unset($value['token_extractors']); + } + + if (array_key_exists('remove_token_from_body_when_cookies_used', $value)) { + $this->_usedProperties['removeTokenFromBodyWhenCookiesUsed'] = true; + $this->removeTokenFromBodyWhenCookiesUsed = $value['remove_token_from_body_when_cookies_used']; + unset($value['remove_token_from_body_when_cookies_used']); + } + + if (array_key_exists('set_cookies', $value)) { + $this->_usedProperties['setCookies'] = true; + $this->setCookies = array_map(fn ($v) => new \Symfony\Config\LexikJwtAuthentication\SetCookiesConfig($v), $value['set_cookies']); + unset($value['set_cookies']); + } + + if (array_key_exists('api_platform', $value)) { + $this->_usedProperties['apiPlatform'] = true; + $this->apiPlatform = \is_array($value['api_platform']) ? new \Symfony\Config\LexikJwtAuthentication\ApiPlatformConfig($value['api_platform']) : $value['api_platform']; + unset($value['api_platform']); + } + + if (array_key_exists('access_token_issuance', $value)) { + $this->_usedProperties['accessTokenIssuance'] = true; + $this->accessTokenIssuance = \is_array($value['access_token_issuance']) ? new \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuanceConfig($value['access_token_issuance']) : $value['access_token_issuance']; + unset($value['access_token_issuance']); + } + + if (array_key_exists('access_token_verification', $value)) { + $this->_usedProperties['accessTokenVerification'] = true; + $this->accessTokenVerification = \is_array($value['access_token_verification']) ? new \Symfony\Config\LexikJwtAuthentication\AccessTokenVerificationConfig($value['access_token_verification']) : $value['access_token_verification']; + unset($value['access_token_verification']); + } + + if (array_key_exists('blocklist_token', $value)) { + $this->_usedProperties['blocklistToken'] = true; + $this->blocklistToken = \is_array($value['blocklist_token']) ? new \Symfony\Config\LexikJwtAuthentication\BlocklistTokenConfig($value['blocklist_token']) : $value['blocklist_token']; + unset($value['blocklist_token']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['publicKey'])) { + $output['public_key'] = $this->publicKey; + } + if (isset($this->_usedProperties['additionalPublicKeys'])) { + $output['additional_public_keys'] = $this->additionalPublicKeys; + } + if (isset($this->_usedProperties['secretKey'])) { + $output['secret_key'] = $this->secretKey; + } + if (isset($this->_usedProperties['passPhrase'])) { + $output['pass_phrase'] = $this->passPhrase; + } + if (isset($this->_usedProperties['tokenTtl'])) { + $output['token_ttl'] = $this->tokenTtl; + } + if (isset($this->_usedProperties['allowNoExpiration'])) { + $output['allow_no_expiration'] = $this->allowNoExpiration; + } + if (isset($this->_usedProperties['clockSkew'])) { + $output['clock_skew'] = $this->clockSkew; + } + if (isset($this->_usedProperties['encoder'])) { + $output['encoder'] = $this->encoder->toArray(); + } + if (isset($this->_usedProperties['userIdClaim'])) { + $output['user_id_claim'] = $this->userIdClaim; + } + if (isset($this->_usedProperties['tokenExtractors'])) { + $output['token_extractors'] = $this->tokenExtractors->toArray(); + } + if (isset($this->_usedProperties['removeTokenFromBodyWhenCookiesUsed'])) { + $output['remove_token_from_body_when_cookies_used'] = $this->removeTokenFromBodyWhenCookiesUsed; + } + if (isset($this->_usedProperties['setCookies'])) { + $output['set_cookies'] = array_map(fn ($v) => $v->toArray(), $this->setCookies); + } + if (isset($this->_usedProperties['apiPlatform'])) { + $output['api_platform'] = $this->apiPlatform instanceof \Symfony\Config\LexikJwtAuthentication\ApiPlatformConfig ? $this->apiPlatform->toArray() : $this->apiPlatform; + } + if (isset($this->_usedProperties['accessTokenIssuance'])) { + $output['access_token_issuance'] = $this->accessTokenIssuance instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenIssuanceConfig ? $this->accessTokenIssuance->toArray() : $this->accessTokenIssuance; + } + if (isset($this->_usedProperties['accessTokenVerification'])) { + $output['access_token_verification'] = $this->accessTokenVerification instanceof \Symfony\Config\LexikJwtAuthentication\AccessTokenVerificationConfig ? $this->accessTokenVerification->toArray() : $this->accessTokenVerification; + } + if (isset($this->_usedProperties['blocklistToken'])) { + $output['blocklist_token'] = $this->blocklistToken instanceof \Symfony\Config\LexikJwtAuthentication\BlocklistTokenConfig ? $this->blocklistToken->toArray() : $this->blocklistToken; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/MakerConfig.php b/var/cache/dev/Symfony/Config/MakerConfig.php new file mode 100644 index 0000000..249ca58 --- /dev/null +++ b/var/cache/dev/Symfony/Config/MakerConfig.php @@ -0,0 +1,103 @@ +_usedProperties['rootNamespace'] = true; + $this->rootNamespace = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function generateFinalClasses($value): static + { + $this->_usedProperties['generateFinalClasses'] = true; + $this->generateFinalClasses = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function generateFinalEntities($value): static + { + $this->_usedProperties['generateFinalEntities'] = true; + $this->generateFinalEntities = $value; + + return $this; + } + + public function getExtensionAlias(): string + { + return 'maker'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('root_namespace', $value)) { + $this->_usedProperties['rootNamespace'] = true; + $this->rootNamespace = $value['root_namespace']; + unset($value['root_namespace']); + } + + if (array_key_exists('generate_final_classes', $value)) { + $this->_usedProperties['generateFinalClasses'] = true; + $this->generateFinalClasses = $value['generate_final_classes']; + unset($value['generate_final_classes']); + } + + if (array_key_exists('generate_final_entities', $value)) { + $this->_usedProperties['generateFinalEntities'] = true; + $this->generateFinalEntities = $value['generate_final_entities']; + unset($value['generate_final_entities']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['rootNamespace'])) { + $output['root_namespace'] = $this->rootNamespace; + } + if (isset($this->_usedProperties['generateFinalClasses'])) { + $output['generate_final_classes'] = $this->generateFinalClasses; + } + if (isset($this->_usedProperties['generateFinalEntities'])) { + $output['generate_final_entities'] = $this->generateFinalEntities; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/AccessControlConfig.php b/var/cache/dev/Symfony/Config/Security/AccessControlConfig.php new file mode 100644 index 0000000..6295e4a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/AccessControlConfig.php @@ -0,0 +1,282 @@ +_usedProperties['requestMatcher'] = true; + $this->requestMatcher = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function requiresChannel($value): static + { + $this->_usedProperties['requiresChannel'] = true; + $this->requiresChannel = $value; + + return $this; + } + + /** + * use the urldecoded format + * @example ^/path to resource/ + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function host($value): static + { + $this->_usedProperties['host'] = true; + $this->host = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function port($value): static + { + $this->_usedProperties['port'] = true; + $this->port = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function ips(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['ips'] = true; + $this->ips = $value; + + return $this; + } + + /** + * @return $this + */ + public function attribute(string $key, mixed $value): static + { + $this->_usedProperties['attributes'] = true; + $this->attributes[$key] = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function route($value): static + { + $this->_usedProperties['route'] = true; + $this->route = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function methods(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['methods'] = true; + $this->methods = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function allowIf($value): static + { + $this->_usedProperties['allowIf'] = true; + $this->allowIf = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function roles(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['roles'] = true; + $this->roles = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('request_matcher', $value)) { + $this->_usedProperties['requestMatcher'] = true; + $this->requestMatcher = $value['request_matcher']; + unset($value['request_matcher']); + } + + if (array_key_exists('requires_channel', $value)) { + $this->_usedProperties['requiresChannel'] = true; + $this->requiresChannel = $value['requires_channel']; + unset($value['requires_channel']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('host', $value)) { + $this->_usedProperties['host'] = true; + $this->host = $value['host']; + unset($value['host']); + } + + if (array_key_exists('port', $value)) { + $this->_usedProperties['port'] = true; + $this->port = $value['port']; + unset($value['port']); + } + + if (array_key_exists('ips', $value)) { + $this->_usedProperties['ips'] = true; + $this->ips = $value['ips']; + unset($value['ips']); + } + + if (array_key_exists('attributes', $value)) { + $this->_usedProperties['attributes'] = true; + $this->attributes = $value['attributes']; + unset($value['attributes']); + } + + if (array_key_exists('route', $value)) { + $this->_usedProperties['route'] = true; + $this->route = $value['route']; + unset($value['route']); + } + + if (array_key_exists('methods', $value)) { + $this->_usedProperties['methods'] = true; + $this->methods = $value['methods']; + unset($value['methods']); + } + + if (array_key_exists('allow_if', $value)) { + $this->_usedProperties['allowIf'] = true; + $this->allowIf = $value['allow_if']; + unset($value['allow_if']); + } + + if (array_key_exists('roles', $value)) { + $this->_usedProperties['roles'] = true; + $this->roles = $value['roles']; + unset($value['roles']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['requestMatcher'])) { + $output['request_matcher'] = $this->requestMatcher; + } + if (isset($this->_usedProperties['requiresChannel'])) { + $output['requires_channel'] = $this->requiresChannel; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['host'])) { + $output['host'] = $this->host; + } + if (isset($this->_usedProperties['port'])) { + $output['port'] = $this->port; + } + if (isset($this->_usedProperties['ips'])) { + $output['ips'] = $this->ips; + } + if (isset($this->_usedProperties['attributes'])) { + $output['attributes'] = $this->attributes; + } + if (isset($this->_usedProperties['route'])) { + $output['route'] = $this->route; + } + if (isset($this->_usedProperties['methods'])) { + $output['methods'] = $this->methods; + } + if (isset($this->_usedProperties['allowIf'])) { + $output['allow_if'] = $this->allowIf; + } + if (isset($this->_usedProperties['roles'])) { + $output['roles'] = $this->roles; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/AccessDecisionManagerConfig.php b/var/cache/dev/Symfony/Config/Security/AccessDecisionManagerConfig.php new file mode 100644 index 0000000..64fe406 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/AccessDecisionManagerConfig.php @@ -0,0 +1,144 @@ +_usedProperties['strategy'] = true; + $this->strategy = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function strategyService($value): static + { + $this->_usedProperties['strategyService'] = true; + $this->strategyService = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowIfAllAbstain($value): static + { + $this->_usedProperties['allowIfAllAbstain'] = true; + $this->allowIfAllAbstain = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function allowIfEqualGrantedDenied($value): static + { + $this->_usedProperties['allowIfEqualGrantedDenied'] = true; + $this->allowIfEqualGrantedDenied = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('strategy', $value)) { + $this->_usedProperties['strategy'] = true; + $this->strategy = $value['strategy']; + unset($value['strategy']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('strategy_service', $value)) { + $this->_usedProperties['strategyService'] = true; + $this->strategyService = $value['strategy_service']; + unset($value['strategy_service']); + } + + if (array_key_exists('allow_if_all_abstain', $value)) { + $this->_usedProperties['allowIfAllAbstain'] = true; + $this->allowIfAllAbstain = $value['allow_if_all_abstain']; + unset($value['allow_if_all_abstain']); + } + + if (array_key_exists('allow_if_equal_granted_denied', $value)) { + $this->_usedProperties['allowIfEqualGrantedDenied'] = true; + $this->allowIfEqualGrantedDenied = $value['allow_if_equal_granted_denied']; + unset($value['allow_if_equal_granted_denied']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['strategy'])) { + $output['strategy'] = $this->strategy; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['strategyService'])) { + $output['strategy_service'] = $this->strategyService; + } + if (isset($this->_usedProperties['allowIfAllAbstain'])) { + $output['allow_if_all_abstain'] = $this->allowIfAllAbstain; + } + if (isset($this->_usedProperties['allowIfEqualGrantedDenied'])) { + $output['allow_if_equal_granted_denied'] = $this->allowIfEqualGrantedDenied; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig.php new file mode 100644 index 0000000..bcab36f --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig.php @@ -0,0 +1,758 @@ +_usedProperties['pattern'] = true; + $this->pattern = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function host($value): static + { + $this->_usedProperties['host'] = true; + $this->host = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function methods(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['methods'] = true; + $this->methods = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function security($value): static + { + $this->_usedProperties['security'] = true; + $this->security = $value; + + return $this; + } + + /** + * The UserChecker to use when authenticating users in this firewall. + * @default 'security.user_checker' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function userChecker($value): static + { + $this->_usedProperties['userChecker'] = true; + $this->userChecker = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function requestMatcher($value): static + { + $this->_usedProperties['requestMatcher'] = true; + $this->requestMatcher = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function accessDeniedUrl($value): static + { + $this->_usedProperties['accessDeniedUrl'] = true; + $this->accessDeniedUrl = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function accessDeniedHandler($value): static + { + $this->_usedProperties['accessDeniedHandler'] = true; + $this->accessDeniedHandler = $value; + + return $this; + } + + /** + * An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface" + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function entryPoint($value): static + { + $this->_usedProperties['entryPoint'] = true; + $this->entryPoint = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function provider($value): static + { + $this->_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function stateless($value): static + { + $this->_usedProperties['stateless'] = true; + $this->stateless = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function lazy($value): static + { + $this->_usedProperties['lazy'] = true; + $this->lazy = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function context($value): static + { + $this->_usedProperties['context'] = true; + $this->context = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Security\FirewallConfig\LogoutConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\FirewallConfig\LogoutConfig : static) + */ + public function logout(mixed $value = []): \Symfony\Config\Security\FirewallConfig\LogoutConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['logout'] = true; + $this->logout = $value; + + return $this; + } + + if (!$this->logout instanceof \Symfony\Config\Security\FirewallConfig\LogoutConfig) { + $this->_usedProperties['logout'] = true; + $this->logout = new \Symfony\Config\Security\FirewallConfig\LogoutConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "logout()" has already been initialized. You cannot pass values the second time you call logout().'); + } + + return $this->logout; + } + + public function switchUser(array $value = []): \Symfony\Config\Security\FirewallConfig\SwitchUserConfig + { + if (null === $this->switchUser) { + $this->_usedProperties['switchUser'] = true; + $this->switchUser = new \Symfony\Config\Security\FirewallConfig\SwitchUserConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "switchUser()" has already been initialized. You cannot pass values the second time you call switchUser().'); + } + + return $this->switchUser; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function requiredBadges(ParamConfigurator|array $value): static + { + $this->_usedProperties['requiredBadges'] = true; + $this->requiredBadges = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function customAuthenticators(ParamConfigurator|array $value): static + { + $this->_usedProperties['customAuthenticators'] = true; + $this->customAuthenticators = $value; + + return $this; + } + + public function loginThrottling(array $value = []): \Symfony\Config\Security\FirewallConfig\LoginThrottlingConfig + { + if (null === $this->loginThrottling) { + $this->_usedProperties['loginThrottling'] = true; + $this->loginThrottling = new \Symfony\Config\Security\FirewallConfig\LoginThrottlingConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "loginThrottling()" has already been initialized. You cannot pass values the second time you call loginThrottling().'); + } + + return $this->loginThrottling; + } + + public function oauth(array $value = []): \Symfony\Config\Security\FirewallConfig\OauthConfig + { + if (null === $this->oauth) { + $this->_usedProperties['oauth'] = true; + $this->oauth = new \Symfony\Config\Security\FirewallConfig\OauthConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "oauth()" has already been initialized. You cannot pass values the second time you call oauth().'); + } + + return $this->oauth; + } + + public function x509(array $value = []): \Symfony\Config\Security\FirewallConfig\X509Config + { + if (null === $this->x509) { + $this->_usedProperties['x509'] = true; + $this->x509 = new \Symfony\Config\Security\FirewallConfig\X509Config($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "x509()" has already been initialized. You cannot pass values the second time you call x509().'); + } + + return $this->x509; + } + + public function remoteUser(array $value = []): \Symfony\Config\Security\FirewallConfig\RemoteUserConfig + { + if (null === $this->remoteUser) { + $this->_usedProperties['remoteUser'] = true; + $this->remoteUser = new \Symfony\Config\Security\FirewallConfig\RemoteUserConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "remoteUser()" has already been initialized. You cannot pass values the second time you call remoteUser().'); + } + + return $this->remoteUser; + } + + public function jwt(array $value = []): \Symfony\Config\Security\FirewallConfig\JwtConfig + { + if (null === $this->jwt) { + $this->_usedProperties['jwt'] = true; + $this->jwt = new \Symfony\Config\Security\FirewallConfig\JwtConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "jwt()" has already been initialized. You cannot pass values the second time you call jwt().'); + } + + return $this->jwt; + } + + public function loginLink(array $value = []): \Symfony\Config\Security\FirewallConfig\LoginLinkConfig + { + if (null === $this->loginLink) { + $this->_usedProperties['loginLink'] = true; + $this->loginLink = new \Symfony\Config\Security\FirewallConfig\LoginLinkConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "loginLink()" has already been initialized. You cannot pass values the second time you call loginLink().'); + } + + return $this->loginLink; + } + + public function formLogin(array $value = []): \Symfony\Config\Security\FirewallConfig\FormLoginConfig + { + if (null === $this->formLogin) { + $this->_usedProperties['formLogin'] = true; + $this->formLogin = new \Symfony\Config\Security\FirewallConfig\FormLoginConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "formLogin()" has already been initialized. You cannot pass values the second time you call formLogin().'); + } + + return $this->formLogin; + } + + public function formLoginLdap(array $value = []): \Symfony\Config\Security\FirewallConfig\FormLoginLdapConfig + { + if (null === $this->formLoginLdap) { + $this->_usedProperties['formLoginLdap'] = true; + $this->formLoginLdap = new \Symfony\Config\Security\FirewallConfig\FormLoginLdapConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "formLoginLdap()" has already been initialized. You cannot pass values the second time you call formLoginLdap().'); + } + + return $this->formLoginLdap; + } + + public function jsonLogin(array $value = []): \Symfony\Config\Security\FirewallConfig\JsonLoginConfig + { + if (null === $this->jsonLogin) { + $this->_usedProperties['jsonLogin'] = true; + $this->jsonLogin = new \Symfony\Config\Security\FirewallConfig\JsonLoginConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "jsonLogin()" has already been initialized. You cannot pass values the second time you call jsonLogin().'); + } + + return $this->jsonLogin; + } + + public function jsonLoginLdap(array $value = []): \Symfony\Config\Security\FirewallConfig\JsonLoginLdapConfig + { + if (null === $this->jsonLoginLdap) { + $this->_usedProperties['jsonLoginLdap'] = true; + $this->jsonLoginLdap = new \Symfony\Config\Security\FirewallConfig\JsonLoginLdapConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "jsonLoginLdap()" has already been initialized. You cannot pass values the second time you call jsonLoginLdap().'); + } + + return $this->jsonLoginLdap; + } + + public function accessToken(array $value = []): \Symfony\Config\Security\FirewallConfig\AccessTokenConfig + { + if (null === $this->accessToken) { + $this->_usedProperties['accessToken'] = true; + $this->accessToken = new \Symfony\Config\Security\FirewallConfig\AccessTokenConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "accessToken()" has already been initialized. You cannot pass values the second time you call accessToken().'); + } + + return $this->accessToken; + } + + public function httpBasic(array $value = []): \Symfony\Config\Security\FirewallConfig\HttpBasicConfig + { + if (null === $this->httpBasic) { + $this->_usedProperties['httpBasic'] = true; + $this->httpBasic = new \Symfony\Config\Security\FirewallConfig\HttpBasicConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "httpBasic()" has already been initialized. You cannot pass values the second time you call httpBasic().'); + } + + return $this->httpBasic; + } + + public function httpBasicLdap(array $value = []): \Symfony\Config\Security\FirewallConfig\HttpBasicLdapConfig + { + if (null === $this->httpBasicLdap) { + $this->_usedProperties['httpBasicLdap'] = true; + $this->httpBasicLdap = new \Symfony\Config\Security\FirewallConfig\HttpBasicLdapConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "httpBasicLdap()" has already been initialized. You cannot pass values the second time you call httpBasicLdap().'); + } + + return $this->httpBasicLdap; + } + + public function rememberMe(array $value = []): \Symfony\Config\Security\FirewallConfig\RememberMeConfig + { + if (null === $this->rememberMe) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = new \Symfony\Config\Security\FirewallConfig\RememberMeConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "rememberMe()" has already been initialized. You cannot pass values the second time you call rememberMe().'); + } + + return $this->rememberMe; + } + + public function __construct(array $value = []) + { + if (array_key_exists('pattern', $value)) { + $this->_usedProperties['pattern'] = true; + $this->pattern = $value['pattern']; + unset($value['pattern']); + } + + if (array_key_exists('host', $value)) { + $this->_usedProperties['host'] = true; + $this->host = $value['host']; + unset($value['host']); + } + + if (array_key_exists('methods', $value)) { + $this->_usedProperties['methods'] = true; + $this->methods = $value['methods']; + unset($value['methods']); + } + + if (array_key_exists('security', $value)) { + $this->_usedProperties['security'] = true; + $this->security = $value['security']; + unset($value['security']); + } + + if (array_key_exists('user_checker', $value)) { + $this->_usedProperties['userChecker'] = true; + $this->userChecker = $value['user_checker']; + unset($value['user_checker']); + } + + if (array_key_exists('request_matcher', $value)) { + $this->_usedProperties['requestMatcher'] = true; + $this->requestMatcher = $value['request_matcher']; + unset($value['request_matcher']); + } + + if (array_key_exists('access_denied_url', $value)) { + $this->_usedProperties['accessDeniedUrl'] = true; + $this->accessDeniedUrl = $value['access_denied_url']; + unset($value['access_denied_url']); + } + + if (array_key_exists('access_denied_handler', $value)) { + $this->_usedProperties['accessDeniedHandler'] = true; + $this->accessDeniedHandler = $value['access_denied_handler']; + unset($value['access_denied_handler']); + } + + if (array_key_exists('entry_point', $value)) { + $this->_usedProperties['entryPoint'] = true; + $this->entryPoint = $value['entry_point']; + unset($value['entry_point']); + } + + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('stateless', $value)) { + $this->_usedProperties['stateless'] = true; + $this->stateless = $value['stateless']; + unset($value['stateless']); + } + + if (array_key_exists('lazy', $value)) { + $this->_usedProperties['lazy'] = true; + $this->lazy = $value['lazy']; + unset($value['lazy']); + } + + if (array_key_exists('context', $value)) { + $this->_usedProperties['context'] = true; + $this->context = $value['context']; + unset($value['context']); + } + + if (array_key_exists('logout', $value)) { + $this->_usedProperties['logout'] = true; + $this->logout = \is_array($value['logout']) ? new \Symfony\Config\Security\FirewallConfig\LogoutConfig($value['logout']) : $value['logout']; + unset($value['logout']); + } + + if (array_key_exists('switch_user', $value)) { + $this->_usedProperties['switchUser'] = true; + $this->switchUser = new \Symfony\Config\Security\FirewallConfig\SwitchUserConfig($value['switch_user']); + unset($value['switch_user']); + } + + if (array_key_exists('required_badges', $value)) { + $this->_usedProperties['requiredBadges'] = true; + $this->requiredBadges = $value['required_badges']; + unset($value['required_badges']); + } + + if (array_key_exists('custom_authenticators', $value)) { + $this->_usedProperties['customAuthenticators'] = true; + $this->customAuthenticators = $value['custom_authenticators']; + unset($value['custom_authenticators']); + } + + if (array_key_exists('login_throttling', $value)) { + $this->_usedProperties['loginThrottling'] = true; + $this->loginThrottling = new \Symfony\Config\Security\FirewallConfig\LoginThrottlingConfig($value['login_throttling']); + unset($value['login_throttling']); + } + + if (array_key_exists('oauth', $value)) { + $this->_usedProperties['oauth'] = true; + $this->oauth = new \Symfony\Config\Security\FirewallConfig\OauthConfig($value['oauth']); + unset($value['oauth']); + } + + if (array_key_exists('x509', $value)) { + $this->_usedProperties['x509'] = true; + $this->x509 = new \Symfony\Config\Security\FirewallConfig\X509Config($value['x509']); + unset($value['x509']); + } + + if (array_key_exists('remote_user', $value)) { + $this->_usedProperties['remoteUser'] = true; + $this->remoteUser = new \Symfony\Config\Security\FirewallConfig\RemoteUserConfig($value['remote_user']); + unset($value['remote_user']); + } + + if (array_key_exists('jwt', $value)) { + $this->_usedProperties['jwt'] = true; + $this->jwt = new \Symfony\Config\Security\FirewallConfig\JwtConfig($value['jwt']); + unset($value['jwt']); + } + + if (array_key_exists('login_link', $value)) { + $this->_usedProperties['loginLink'] = true; + $this->loginLink = new \Symfony\Config\Security\FirewallConfig\LoginLinkConfig($value['login_link']); + unset($value['login_link']); + } + + if (array_key_exists('form_login', $value)) { + $this->_usedProperties['formLogin'] = true; + $this->formLogin = new \Symfony\Config\Security\FirewallConfig\FormLoginConfig($value['form_login']); + unset($value['form_login']); + } + + if (array_key_exists('form_login_ldap', $value)) { + $this->_usedProperties['formLoginLdap'] = true; + $this->formLoginLdap = new \Symfony\Config\Security\FirewallConfig\FormLoginLdapConfig($value['form_login_ldap']); + unset($value['form_login_ldap']); + } + + if (array_key_exists('json_login', $value)) { + $this->_usedProperties['jsonLogin'] = true; + $this->jsonLogin = new \Symfony\Config\Security\FirewallConfig\JsonLoginConfig($value['json_login']); + unset($value['json_login']); + } + + if (array_key_exists('json_login_ldap', $value)) { + $this->_usedProperties['jsonLoginLdap'] = true; + $this->jsonLoginLdap = new \Symfony\Config\Security\FirewallConfig\JsonLoginLdapConfig($value['json_login_ldap']); + unset($value['json_login_ldap']); + } + + if (array_key_exists('access_token', $value)) { + $this->_usedProperties['accessToken'] = true; + $this->accessToken = new \Symfony\Config\Security\FirewallConfig\AccessTokenConfig($value['access_token']); + unset($value['access_token']); + } + + if (array_key_exists('http_basic', $value)) { + $this->_usedProperties['httpBasic'] = true; + $this->httpBasic = new \Symfony\Config\Security\FirewallConfig\HttpBasicConfig($value['http_basic']); + unset($value['http_basic']); + } + + if (array_key_exists('http_basic_ldap', $value)) { + $this->_usedProperties['httpBasicLdap'] = true; + $this->httpBasicLdap = new \Symfony\Config\Security\FirewallConfig\HttpBasicLdapConfig($value['http_basic_ldap']); + unset($value['http_basic_ldap']); + } + + if (array_key_exists('remember_me', $value)) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = new \Symfony\Config\Security\FirewallConfig\RememberMeConfig($value['remember_me']); + unset($value['remember_me']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['pattern'])) { + $output['pattern'] = $this->pattern; + } + if (isset($this->_usedProperties['host'])) { + $output['host'] = $this->host; + } + if (isset($this->_usedProperties['methods'])) { + $output['methods'] = $this->methods; + } + if (isset($this->_usedProperties['security'])) { + $output['security'] = $this->security; + } + if (isset($this->_usedProperties['userChecker'])) { + $output['user_checker'] = $this->userChecker; + } + if (isset($this->_usedProperties['requestMatcher'])) { + $output['request_matcher'] = $this->requestMatcher; + } + if (isset($this->_usedProperties['accessDeniedUrl'])) { + $output['access_denied_url'] = $this->accessDeniedUrl; + } + if (isset($this->_usedProperties['accessDeniedHandler'])) { + $output['access_denied_handler'] = $this->accessDeniedHandler; + } + if (isset($this->_usedProperties['entryPoint'])) { + $output['entry_point'] = $this->entryPoint; + } + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['stateless'])) { + $output['stateless'] = $this->stateless; + } + if (isset($this->_usedProperties['lazy'])) { + $output['lazy'] = $this->lazy; + } + if (isset($this->_usedProperties['context'])) { + $output['context'] = $this->context; + } + if (isset($this->_usedProperties['logout'])) { + $output['logout'] = $this->logout instanceof \Symfony\Config\Security\FirewallConfig\LogoutConfig ? $this->logout->toArray() : $this->logout; + } + if (isset($this->_usedProperties['switchUser'])) { + $output['switch_user'] = $this->switchUser->toArray(); + } + if (isset($this->_usedProperties['requiredBadges'])) { + $output['required_badges'] = $this->requiredBadges; + } + if (isset($this->_usedProperties['customAuthenticators'])) { + $output['custom_authenticators'] = $this->customAuthenticators; + } + if (isset($this->_usedProperties['loginThrottling'])) { + $output['login_throttling'] = $this->loginThrottling->toArray(); + } + if (isset($this->_usedProperties['oauth'])) { + $output['oauth'] = $this->oauth->toArray(); + } + if (isset($this->_usedProperties['x509'])) { + $output['x509'] = $this->x509->toArray(); + } + if (isset($this->_usedProperties['remoteUser'])) { + $output['remote_user'] = $this->remoteUser->toArray(); + } + if (isset($this->_usedProperties['jwt'])) { + $output['jwt'] = $this->jwt->toArray(); + } + if (isset($this->_usedProperties['loginLink'])) { + $output['login_link'] = $this->loginLink->toArray(); + } + if (isset($this->_usedProperties['formLogin'])) { + $output['form_login'] = $this->formLogin->toArray(); + } + if (isset($this->_usedProperties['formLoginLdap'])) { + $output['form_login_ldap'] = $this->formLoginLdap->toArray(); + } + if (isset($this->_usedProperties['jsonLogin'])) { + $output['json_login'] = $this->jsonLogin->toArray(); + } + if (isset($this->_usedProperties['jsonLoginLdap'])) { + $output['json_login_ldap'] = $this->jsonLoginLdap->toArray(); + } + if (isset($this->_usedProperties['accessToken'])) { + $output['access_token'] = $this->accessToken->toArray(); + } + if (isset($this->_usedProperties['httpBasic'])) { + $output['http_basic'] = $this->httpBasic->toArray(); + } + if (isset($this->_usedProperties['httpBasicLdap'])) { + $output['http_basic_ldap'] = $this->httpBasicLdap->toArray(); + } + if (isset($this->_usedProperties['rememberMe'])) { + $output['remember_me'] = $this->rememberMe->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/CasConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/CasConfig.php new file mode 100644 index 0000000..09555a2 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/CasConfig.php @@ -0,0 +1,101 @@ +_usedProperties['validationUrl'] = true; + $this->validationUrl = $value; + + return $this; + } + + /** + * CAS prefix + * @default 'cas' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function prefix($value): static + { + $this->_usedProperties['prefix'] = true; + $this->prefix = $value; + + return $this; + } + + /** + * HTTP Client service + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function httpClient($value): static + { + $this->_usedProperties['httpClient'] = true; + $this->httpClient = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('validation_url', $value)) { + $this->_usedProperties['validationUrl'] = true; + $this->validationUrl = $value['validation_url']; + unset($value['validation_url']); + } + + if (array_key_exists('prefix', $value)) { + $this->_usedProperties['prefix'] = true; + $this->prefix = $value['prefix']; + unset($value['prefix']); + } + + if (array_key_exists('http_client', $value)) { + $this->_usedProperties['httpClient'] = true; + $this->httpClient = $value['http_client']; + unset($value['http_client']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['validationUrl'])) { + $output['validation_url'] = $this->validationUrl; + } + if (isset($this->_usedProperties['prefix'])) { + $output['prefix'] = $this->prefix; + } + if (isset($this->_usedProperties['httpClient'])) { + $output['http_client'] = $this->httpClient; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/Oidc/AlgorithmConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/Oidc/AlgorithmConfig.php new file mode 100644 index 0000000..7c04e76 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/Oidc/AlgorithmConfig.php @@ -0,0 +1,27 @@ +_usedProperties['claim'] = true; + $this->claim = $value; + + return $this; + } + + /** + * Audience set in the token, for validation purpose. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function audience($value): static + { + $this->_usedProperties['audience'] = true; + $this->audience = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function issuers(ParamConfigurator|array $value): static + { + $this->_usedProperties['issuers'] = true; + $this->issuers = $value; + + return $this; + } + + /** + * Algorithm used to sign the token. + * @deprecated The "algorithm" option is deprecated and will be removed in 8.0. Use the "algorithms" option instead. + */ + public function algorithm(array $value = []): \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\Oidc\AlgorithmConfig + { + if (null === $this->algorithm) { + $this->_usedProperties['algorithm'] = true; + $this->algorithm = new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\Oidc\AlgorithmConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "algorithm()" has already been initialized. You cannot pass values the second time you call algorithm().'); + } + + return $this->algorithm; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function algorithms(ParamConfigurator|array $value): static + { + $this->_usedProperties['algorithms'] = true; + $this->algorithms = $value; + + return $this; + } + + /** + * JSON-encoded JWK used to sign the token (must contain a "kty" key). + * @default null + * @param ParamConfigurator|mixed $value + * @deprecated The "key" option is deprecated and will be removed in 8.0. Use the "keyset" option instead. + * @return $this + */ + public function key($value): static + { + $this->_usedProperties['key'] = true; + $this->key = $value; + + return $this; + } + + /** + * JSON-encoded JWKSet used to sign the token (must contain a list of valid keys). + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function keyset($value): static + { + $this->_usedProperties['keyset'] = true; + $this->keyset = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('claim', $value)) { + $this->_usedProperties['claim'] = true; + $this->claim = $value['claim']; + unset($value['claim']); + } + + if (array_key_exists('audience', $value)) { + $this->_usedProperties['audience'] = true; + $this->audience = $value['audience']; + unset($value['audience']); + } + + if (array_key_exists('issuers', $value)) { + $this->_usedProperties['issuers'] = true; + $this->issuers = $value['issuers']; + unset($value['issuers']); + } + + if (array_key_exists('algorithm', $value)) { + $this->_usedProperties['algorithm'] = true; + $this->algorithm = new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\Oidc\AlgorithmConfig($value['algorithm']); + unset($value['algorithm']); + } + + if (array_key_exists('algorithms', $value)) { + $this->_usedProperties['algorithms'] = true; + $this->algorithms = $value['algorithms']; + unset($value['algorithms']); + } + + if (array_key_exists('key', $value)) { + $this->_usedProperties['key'] = true; + $this->key = $value['key']; + unset($value['key']); + } + + if (array_key_exists('keyset', $value)) { + $this->_usedProperties['keyset'] = true; + $this->keyset = $value['keyset']; + unset($value['keyset']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['claim'])) { + $output['claim'] = $this->claim; + } + if (isset($this->_usedProperties['audience'])) { + $output['audience'] = $this->audience; + } + if (isset($this->_usedProperties['issuers'])) { + $output['issuers'] = $this->issuers; + } + if (isset($this->_usedProperties['algorithm'])) { + $output['algorithm'] = $this->algorithm->toArray(); + } + if (isset($this->_usedProperties['algorithms'])) { + $output['algorithms'] = $this->algorithms; + } + if (isset($this->_usedProperties['key'])) { + $output['key'] = $this->key; + } + if (isset($this->_usedProperties['keyset'])) { + $output['keyset'] = $this->keyset; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/OidcUserInfoConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/OidcUserInfoConfig.php new file mode 100644 index 0000000..bfa2308 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandler/OidcUserInfoConfig.php @@ -0,0 +1,101 @@ +_usedProperties['baseUri'] = true; + $this->baseUri = $value; + + return $this; + } + + /** + * Claim which contains the user identifier (e.g. sub, email, etc.). + * @default 'sub' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function claim($value): static + { + $this->_usedProperties['claim'] = true; + $this->claim = $value; + + return $this; + } + + /** + * HttpClient service id to use to call the OIDC server. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function client($value): static + { + $this->_usedProperties['client'] = true; + $this->client = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('base_uri', $value)) { + $this->_usedProperties['baseUri'] = true; + $this->baseUri = $value['base_uri']; + unset($value['base_uri']); + } + + if (array_key_exists('claim', $value)) { + $this->_usedProperties['claim'] = true; + $this->claim = $value['claim']; + unset($value['claim']); + } + + if (array_key_exists('client', $value)) { + $this->_usedProperties['client'] = true; + $this->client = $value['client']; + unset($value['client']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['baseUri'])) { + $output['base_uri'] = $this->baseUri; + } + if (isset($this->_usedProperties['claim'])) { + $output['claim'] = $this->claim; + } + if (isset($this->_usedProperties['client'])) { + $output['client'] = $this->client; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandlerConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandlerConfig.php new file mode 100644 index 0000000..5ee229c --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessToken/TokenHandlerConfig.php @@ -0,0 +1,148 @@ +_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcUserInfoConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcUserInfoConfig : static) + */ + public function oidcUserInfo(string|array $value = []): \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcUserInfoConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['oidcUserInfo'] = true; + $this->oidcUserInfo = $value; + + return $this; + } + + if (!$this->oidcUserInfo instanceof \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcUserInfoConfig) { + $this->_usedProperties['oidcUserInfo'] = true; + $this->oidcUserInfo = new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcUserInfoConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "oidcUserInfo()" has already been initialized. You cannot pass values the second time you call oidcUserInfo().'); + } + + return $this->oidcUserInfo; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcConfig : static) + */ + public function oidc(mixed $value = []): \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['oidc'] = true; + $this->oidc = $value; + + return $this; + } + + if (!$this->oidc instanceof \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcConfig) { + $this->_usedProperties['oidc'] = true; + $this->oidc = new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "oidc()" has already been initialized. You cannot pass values the second time you call oidc().'); + } + + return $this->oidc; + } + + public function cas(array $value = []): \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\CasConfig + { + if (null === $this->cas) { + $this->_usedProperties['cas'] = true; + $this->cas = new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\CasConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "cas()" has already been initialized. You cannot pass values the second time you call cas().'); + } + + return $this->cas; + } + + public function __construct(array $value = []) + { + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('oidc_user_info', $value)) { + $this->_usedProperties['oidcUserInfo'] = true; + $this->oidcUserInfo = \is_array($value['oidc_user_info']) ? new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcUserInfoConfig($value['oidc_user_info']) : $value['oidc_user_info']; + unset($value['oidc_user_info']); + } + + if (array_key_exists('oidc', $value)) { + $this->_usedProperties['oidc'] = true; + $this->oidc = \is_array($value['oidc']) ? new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcConfig($value['oidc']) : $value['oidc']; + unset($value['oidc']); + } + + if (array_key_exists('cas', $value)) { + $this->_usedProperties['cas'] = true; + $this->cas = new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\CasConfig($value['cas']); + unset($value['cas']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['oidcUserInfo'])) { + $output['oidc_user_info'] = $this->oidcUserInfo instanceof \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcUserInfoConfig ? $this->oidcUserInfo->toArray() : $this->oidcUserInfo; + } + if (isset($this->_usedProperties['oidc'])) { + $output['oidc'] = $this->oidc instanceof \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandler\OidcConfig ? $this->oidc->toArray() : $this->oidc; + } + if (isset($this->_usedProperties['cas'])) { + $output['cas'] = $this->cas->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessTokenConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessTokenConfig.php new file mode 100644 index 0000000..616aac6 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/AccessTokenConfig.php @@ -0,0 +1,205 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function rememberMe($value): static + { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function successHandler($value): static + { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureHandler($value): static + { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function realm($value): static + { + $this->_usedProperties['realm'] = true; + $this->realm = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function tokenExtractors(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['tokenExtractors'] = true; + $this->tokenExtractors = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @example "App\\Security\\CustomTokenHandler" + * @return \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandlerConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandlerConfig : static) + */ + public function tokenHandler(mixed $value = []): \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandlerConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['tokenHandler'] = true; + $this->tokenHandler = $value; + + return $this; + } + + if (!$this->tokenHandler instanceof \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandlerConfig) { + $this->_usedProperties['tokenHandler'] = true; + $this->tokenHandler = new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandlerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "tokenHandler()" has already been initialized. You cannot pass values the second time you call tokenHandler().'); + } + + return $this->tokenHandler; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('remember_me', $value)) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value['remember_me']; + unset($value['remember_me']); + } + + if (array_key_exists('success_handler', $value)) { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value['success_handler']; + unset($value['success_handler']); + } + + if (array_key_exists('failure_handler', $value)) { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value['failure_handler']; + unset($value['failure_handler']); + } + + if (array_key_exists('realm', $value)) { + $this->_usedProperties['realm'] = true; + $this->realm = $value['realm']; + unset($value['realm']); + } + + if (array_key_exists('token_extractors', $value)) { + $this->_usedProperties['tokenExtractors'] = true; + $this->tokenExtractors = $value['token_extractors']; + unset($value['token_extractors']); + } + + if (array_key_exists('token_handler', $value)) { + $this->_usedProperties['tokenHandler'] = true; + $this->tokenHandler = \is_array($value['token_handler']) ? new \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandlerConfig($value['token_handler']) : $value['token_handler']; + unset($value['token_handler']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['rememberMe'])) { + $output['remember_me'] = $this->rememberMe; + } + if (isset($this->_usedProperties['successHandler'])) { + $output['success_handler'] = $this->successHandler; + } + if (isset($this->_usedProperties['failureHandler'])) { + $output['failure_handler'] = $this->failureHandler; + } + if (isset($this->_usedProperties['realm'])) { + $output['realm'] = $this->realm; + } + if (isset($this->_usedProperties['tokenExtractors'])) { + $output['token_extractors'] = $this->tokenExtractors; + } + if (isset($this->_usedProperties['tokenHandler'])) { + $output['token_handler'] = $this->tokenHandler instanceof \Symfony\Config\Security\FirewallConfig\AccessToken\TokenHandlerConfig ? $this->tokenHandler->toArray() : $this->tokenHandler; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/FormLoginConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/FormLoginConfig.php new file mode 100644 index 0000000..26a8cc7 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/FormLoginConfig.php @@ -0,0 +1,512 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function rememberMe($value): static + { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function successHandler($value): static + { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureHandler($value): static + { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value; + + return $this; + } + + /** + * @default '/login_check' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkPath($value): static + { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useForward($value): static + { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value; + + return $this; + } + + /** + * @default '/login' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function loginPath($value): static + { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value; + + return $this; + } + + /** + * @default '_username' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function usernameParameter($value): static + { + $this->_usedProperties['usernameParameter'] = true; + $this->usernameParameter = $value; + + return $this; + } + + /** + * @default '_password' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passwordParameter($value): static + { + $this->_usedProperties['passwordParameter'] = true; + $this->passwordParameter = $value; + + return $this; + } + + /** + * @default '_csrf_token' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function csrfParameter($value): static + { + $this->_usedProperties['csrfParameter'] = true; + $this->csrfParameter = $value; + + return $this; + } + + /** + * @default 'authenticate' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function csrfTokenId($value): static + { + $this->_usedProperties['csrfTokenId'] = true; + $this->csrfTokenId = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enableCsrf($value): static + { + $this->_usedProperties['enableCsrf'] = true; + $this->enableCsrf = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function postOnly($value): static + { + $this->_usedProperties['postOnly'] = true; + $this->postOnly = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function formOnly($value): static + { + $this->_usedProperties['formOnly'] = true; + $this->formOnly = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function alwaysUseDefaultTargetPath($value): static + { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value; + + return $this; + } + + /** + * @default '/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultTargetPath($value): static + { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value; + + return $this; + } + + /** + * @default '_target_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function targetPathParameter($value): static + { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useReferer($value): static + { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePath($value): static + { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function failureForward($value): static + { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value; + + return $this; + } + + /** + * @default '_failure_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePathParameter($value): static + { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('remember_me', $value)) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value['remember_me']; + unset($value['remember_me']); + } + + if (array_key_exists('success_handler', $value)) { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value['success_handler']; + unset($value['success_handler']); + } + + if (array_key_exists('failure_handler', $value)) { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value['failure_handler']; + unset($value['failure_handler']); + } + + if (array_key_exists('check_path', $value)) { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value['check_path']; + unset($value['check_path']); + } + + if (array_key_exists('use_forward', $value)) { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value['use_forward']; + unset($value['use_forward']); + } + + if (array_key_exists('login_path', $value)) { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value['login_path']; + unset($value['login_path']); + } + + if (array_key_exists('username_parameter', $value)) { + $this->_usedProperties['usernameParameter'] = true; + $this->usernameParameter = $value['username_parameter']; + unset($value['username_parameter']); + } + + if (array_key_exists('password_parameter', $value)) { + $this->_usedProperties['passwordParameter'] = true; + $this->passwordParameter = $value['password_parameter']; + unset($value['password_parameter']); + } + + if (array_key_exists('csrf_parameter', $value)) { + $this->_usedProperties['csrfParameter'] = true; + $this->csrfParameter = $value['csrf_parameter']; + unset($value['csrf_parameter']); + } + + if (array_key_exists('csrf_token_id', $value)) { + $this->_usedProperties['csrfTokenId'] = true; + $this->csrfTokenId = $value['csrf_token_id']; + unset($value['csrf_token_id']); + } + + if (array_key_exists('enable_csrf', $value)) { + $this->_usedProperties['enableCsrf'] = true; + $this->enableCsrf = $value['enable_csrf']; + unset($value['enable_csrf']); + } + + if (array_key_exists('post_only', $value)) { + $this->_usedProperties['postOnly'] = true; + $this->postOnly = $value['post_only']; + unset($value['post_only']); + } + + if (array_key_exists('form_only', $value)) { + $this->_usedProperties['formOnly'] = true; + $this->formOnly = $value['form_only']; + unset($value['form_only']); + } + + if (array_key_exists('always_use_default_target_path', $value)) { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value['always_use_default_target_path']; + unset($value['always_use_default_target_path']); + } + + if (array_key_exists('default_target_path', $value)) { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value['default_target_path']; + unset($value['default_target_path']); + } + + if (array_key_exists('target_path_parameter', $value)) { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value['target_path_parameter']; + unset($value['target_path_parameter']); + } + + if (array_key_exists('use_referer', $value)) { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value['use_referer']; + unset($value['use_referer']); + } + + if (array_key_exists('failure_path', $value)) { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value['failure_path']; + unset($value['failure_path']); + } + + if (array_key_exists('failure_forward', $value)) { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value['failure_forward']; + unset($value['failure_forward']); + } + + if (array_key_exists('failure_path_parameter', $value)) { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value['failure_path_parameter']; + unset($value['failure_path_parameter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['rememberMe'])) { + $output['remember_me'] = $this->rememberMe; + } + if (isset($this->_usedProperties['successHandler'])) { + $output['success_handler'] = $this->successHandler; + } + if (isset($this->_usedProperties['failureHandler'])) { + $output['failure_handler'] = $this->failureHandler; + } + if (isset($this->_usedProperties['checkPath'])) { + $output['check_path'] = $this->checkPath; + } + if (isset($this->_usedProperties['useForward'])) { + $output['use_forward'] = $this->useForward; + } + if (isset($this->_usedProperties['loginPath'])) { + $output['login_path'] = $this->loginPath; + } + if (isset($this->_usedProperties['usernameParameter'])) { + $output['username_parameter'] = $this->usernameParameter; + } + if (isset($this->_usedProperties['passwordParameter'])) { + $output['password_parameter'] = $this->passwordParameter; + } + if (isset($this->_usedProperties['csrfParameter'])) { + $output['csrf_parameter'] = $this->csrfParameter; + } + if (isset($this->_usedProperties['csrfTokenId'])) { + $output['csrf_token_id'] = $this->csrfTokenId; + } + if (isset($this->_usedProperties['enableCsrf'])) { + $output['enable_csrf'] = $this->enableCsrf; + } + if (isset($this->_usedProperties['postOnly'])) { + $output['post_only'] = $this->postOnly; + } + if (isset($this->_usedProperties['formOnly'])) { + $output['form_only'] = $this->formOnly; + } + if (isset($this->_usedProperties['alwaysUseDefaultTargetPath'])) { + $output['always_use_default_target_path'] = $this->alwaysUseDefaultTargetPath; + } + if (isset($this->_usedProperties['defaultTargetPath'])) { + $output['default_target_path'] = $this->defaultTargetPath; + } + if (isset($this->_usedProperties['targetPathParameter'])) { + $output['target_path_parameter'] = $this->targetPathParameter; + } + if (isset($this->_usedProperties['useReferer'])) { + $output['use_referer'] = $this->useReferer; + } + if (isset($this->_usedProperties['failurePath'])) { + $output['failure_path'] = $this->failurePath; + } + if (isset($this->_usedProperties['failureForward'])) { + $output['failure_forward'] = $this->failureForward; + } + if (isset($this->_usedProperties['failurePathParameter'])) { + $output['failure_path_parameter'] = $this->failurePathParameter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/FormLoginLdapConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/FormLoginLdapConfig.php new file mode 100644 index 0000000..794192c --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/FormLoginLdapConfig.php @@ -0,0 +1,625 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function rememberMe($value): static + { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function successHandler($value): static + { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureHandler($value): static + { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value; + + return $this; + } + + /** + * @default '/login_check' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkPath($value): static + { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useForward($value): static + { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value; + + return $this; + } + + /** + * @default '/login' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function loginPath($value): static + { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value; + + return $this; + } + + /** + * @default '_username' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function usernameParameter($value): static + { + $this->_usedProperties['usernameParameter'] = true; + $this->usernameParameter = $value; + + return $this; + } + + /** + * @default '_password' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passwordParameter($value): static + { + $this->_usedProperties['passwordParameter'] = true; + $this->passwordParameter = $value; + + return $this; + } + + /** + * @default '_csrf_token' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function csrfParameter($value): static + { + $this->_usedProperties['csrfParameter'] = true; + $this->csrfParameter = $value; + + return $this; + } + + /** + * @default 'authenticate' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function csrfTokenId($value): static + { + $this->_usedProperties['csrfTokenId'] = true; + $this->csrfTokenId = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function enableCsrf($value): static + { + $this->_usedProperties['enableCsrf'] = true; + $this->enableCsrf = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function postOnly($value): static + { + $this->_usedProperties['postOnly'] = true; + $this->postOnly = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function formOnly($value): static + { + $this->_usedProperties['formOnly'] = true; + $this->formOnly = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function alwaysUseDefaultTargetPath($value): static + { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value; + + return $this; + } + + /** + * @default '/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultTargetPath($value): static + { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value; + + return $this; + } + + /** + * @default '_target_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function targetPathParameter($value): static + { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useReferer($value): static + { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePath($value): static + { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function failureForward($value): static + { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value; + + return $this; + } + + /** + * @default '_failure_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePathParameter($value): static + { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value; + + return $this; + } + + /** + * @default 'ldap' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default '{user_identifier}' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dnString($value): static + { + $this->_usedProperties['dnString'] = true; + $this->dnString = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function queryString($value): static + { + $this->_usedProperties['queryString'] = true; + $this->queryString = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchDn($value): static + { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchPassword($value): static + { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('remember_me', $value)) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value['remember_me']; + unset($value['remember_me']); + } + + if (array_key_exists('success_handler', $value)) { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value['success_handler']; + unset($value['success_handler']); + } + + if (array_key_exists('failure_handler', $value)) { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value['failure_handler']; + unset($value['failure_handler']); + } + + if (array_key_exists('check_path', $value)) { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value['check_path']; + unset($value['check_path']); + } + + if (array_key_exists('use_forward', $value)) { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value['use_forward']; + unset($value['use_forward']); + } + + if (array_key_exists('login_path', $value)) { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value['login_path']; + unset($value['login_path']); + } + + if (array_key_exists('username_parameter', $value)) { + $this->_usedProperties['usernameParameter'] = true; + $this->usernameParameter = $value['username_parameter']; + unset($value['username_parameter']); + } + + if (array_key_exists('password_parameter', $value)) { + $this->_usedProperties['passwordParameter'] = true; + $this->passwordParameter = $value['password_parameter']; + unset($value['password_parameter']); + } + + if (array_key_exists('csrf_parameter', $value)) { + $this->_usedProperties['csrfParameter'] = true; + $this->csrfParameter = $value['csrf_parameter']; + unset($value['csrf_parameter']); + } + + if (array_key_exists('csrf_token_id', $value)) { + $this->_usedProperties['csrfTokenId'] = true; + $this->csrfTokenId = $value['csrf_token_id']; + unset($value['csrf_token_id']); + } + + if (array_key_exists('enable_csrf', $value)) { + $this->_usedProperties['enableCsrf'] = true; + $this->enableCsrf = $value['enable_csrf']; + unset($value['enable_csrf']); + } + + if (array_key_exists('post_only', $value)) { + $this->_usedProperties['postOnly'] = true; + $this->postOnly = $value['post_only']; + unset($value['post_only']); + } + + if (array_key_exists('form_only', $value)) { + $this->_usedProperties['formOnly'] = true; + $this->formOnly = $value['form_only']; + unset($value['form_only']); + } + + if (array_key_exists('always_use_default_target_path', $value)) { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value['always_use_default_target_path']; + unset($value['always_use_default_target_path']); + } + + if (array_key_exists('default_target_path', $value)) { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value['default_target_path']; + unset($value['default_target_path']); + } + + if (array_key_exists('target_path_parameter', $value)) { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value['target_path_parameter']; + unset($value['target_path_parameter']); + } + + if (array_key_exists('use_referer', $value)) { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value['use_referer']; + unset($value['use_referer']); + } + + if (array_key_exists('failure_path', $value)) { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value['failure_path']; + unset($value['failure_path']); + } + + if (array_key_exists('failure_forward', $value)) { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value['failure_forward']; + unset($value['failure_forward']); + } + + if (array_key_exists('failure_path_parameter', $value)) { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value['failure_path_parameter']; + unset($value['failure_path_parameter']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('dn_string', $value)) { + $this->_usedProperties['dnString'] = true; + $this->dnString = $value['dn_string']; + unset($value['dn_string']); + } + + if (array_key_exists('query_string', $value)) { + $this->_usedProperties['queryString'] = true; + $this->queryString = $value['query_string']; + unset($value['query_string']); + } + + if (array_key_exists('search_dn', $value)) { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value['search_dn']; + unset($value['search_dn']); + } + + if (array_key_exists('search_password', $value)) { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value['search_password']; + unset($value['search_password']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['rememberMe'])) { + $output['remember_me'] = $this->rememberMe; + } + if (isset($this->_usedProperties['successHandler'])) { + $output['success_handler'] = $this->successHandler; + } + if (isset($this->_usedProperties['failureHandler'])) { + $output['failure_handler'] = $this->failureHandler; + } + if (isset($this->_usedProperties['checkPath'])) { + $output['check_path'] = $this->checkPath; + } + if (isset($this->_usedProperties['useForward'])) { + $output['use_forward'] = $this->useForward; + } + if (isset($this->_usedProperties['loginPath'])) { + $output['login_path'] = $this->loginPath; + } + if (isset($this->_usedProperties['usernameParameter'])) { + $output['username_parameter'] = $this->usernameParameter; + } + if (isset($this->_usedProperties['passwordParameter'])) { + $output['password_parameter'] = $this->passwordParameter; + } + if (isset($this->_usedProperties['csrfParameter'])) { + $output['csrf_parameter'] = $this->csrfParameter; + } + if (isset($this->_usedProperties['csrfTokenId'])) { + $output['csrf_token_id'] = $this->csrfTokenId; + } + if (isset($this->_usedProperties['enableCsrf'])) { + $output['enable_csrf'] = $this->enableCsrf; + } + if (isset($this->_usedProperties['postOnly'])) { + $output['post_only'] = $this->postOnly; + } + if (isset($this->_usedProperties['formOnly'])) { + $output['form_only'] = $this->formOnly; + } + if (isset($this->_usedProperties['alwaysUseDefaultTargetPath'])) { + $output['always_use_default_target_path'] = $this->alwaysUseDefaultTargetPath; + } + if (isset($this->_usedProperties['defaultTargetPath'])) { + $output['default_target_path'] = $this->defaultTargetPath; + } + if (isset($this->_usedProperties['targetPathParameter'])) { + $output['target_path_parameter'] = $this->targetPathParameter; + } + if (isset($this->_usedProperties['useReferer'])) { + $output['use_referer'] = $this->useReferer; + } + if (isset($this->_usedProperties['failurePath'])) { + $output['failure_path'] = $this->failurePath; + } + if (isset($this->_usedProperties['failureForward'])) { + $output['failure_forward'] = $this->failureForward; + } + if (isset($this->_usedProperties['failurePathParameter'])) { + $output['failure_path_parameter'] = $this->failurePathParameter; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['dnString'])) { + $output['dn_string'] = $this->dnString; + } + if (isset($this->_usedProperties['queryString'])) { + $output['query_string'] = $this->queryString; + } + if (isset($this->_usedProperties['searchDn'])) { + $output['search_dn'] = $this->searchDn; + } + if (isset($this->_usedProperties['searchPassword'])) { + $output['search_password'] = $this->searchPassword; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/HttpBasicConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/HttpBasicConfig.php new file mode 100644 index 0000000..40fe280 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/HttpBasicConfig.php @@ -0,0 +1,75 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default 'Secured Area' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function realm($value): static + { + $this->_usedProperties['realm'] = true; + $this->realm = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('realm', $value)) { + $this->_usedProperties['realm'] = true; + $this->realm = $value['realm']; + unset($value['realm']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['realm'])) { + $output['realm'] = $this->realm; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/HttpBasicLdapConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/HttpBasicLdapConfig.php new file mode 100644 index 0000000..3efb5c0 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/HttpBasicLdapConfig.php @@ -0,0 +1,188 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default 'Secured Area' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function realm($value): static + { + $this->_usedProperties['realm'] = true; + $this->realm = $value; + + return $this; + } + + /** + * @default 'ldap' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default '{user_identifier}' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dnString($value): static + { + $this->_usedProperties['dnString'] = true; + $this->dnString = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function queryString($value): static + { + $this->_usedProperties['queryString'] = true; + $this->queryString = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchDn($value): static + { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchPassword($value): static + { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('realm', $value)) { + $this->_usedProperties['realm'] = true; + $this->realm = $value['realm']; + unset($value['realm']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('dn_string', $value)) { + $this->_usedProperties['dnString'] = true; + $this->dnString = $value['dn_string']; + unset($value['dn_string']); + } + + if (array_key_exists('query_string', $value)) { + $this->_usedProperties['queryString'] = true; + $this->queryString = $value['query_string']; + unset($value['query_string']); + } + + if (array_key_exists('search_dn', $value)) { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value['search_dn']; + unset($value['search_dn']); + } + + if (array_key_exists('search_password', $value)) { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value['search_password']; + unset($value['search_password']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['realm'])) { + $output['realm'] = $this->realm; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['dnString'])) { + $output['dn_string'] = $this->dnString; + } + if (isset($this->_usedProperties['queryString'])) { + $output['query_string'] = $this->queryString; + } + if (isset($this->_usedProperties['searchDn'])) { + $output['search_dn'] = $this->searchDn; + } + if (isset($this->_usedProperties['searchPassword'])) { + $output['search_password'] = $this->searchPassword; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/JsonLoginConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/JsonLoginConfig.php new file mode 100644 index 0000000..7a6c2ed --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/JsonLoginConfig.php @@ -0,0 +1,236 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function rememberMe($value): static + { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function successHandler($value): static + { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureHandler($value): static + { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value; + + return $this; + } + + /** + * @default '/login_check' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkPath($value): static + { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useForward($value): static + { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value; + + return $this; + } + + /** + * @default '/login' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function loginPath($value): static + { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value; + + return $this; + } + + /** + * @default 'username' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function usernamePath($value): static + { + $this->_usedProperties['usernamePath'] = true; + $this->usernamePath = $value; + + return $this; + } + + /** + * @default 'password' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passwordPath($value): static + { + $this->_usedProperties['passwordPath'] = true; + $this->passwordPath = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('remember_me', $value)) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value['remember_me']; + unset($value['remember_me']); + } + + if (array_key_exists('success_handler', $value)) { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value['success_handler']; + unset($value['success_handler']); + } + + if (array_key_exists('failure_handler', $value)) { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value['failure_handler']; + unset($value['failure_handler']); + } + + if (array_key_exists('check_path', $value)) { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value['check_path']; + unset($value['check_path']); + } + + if (array_key_exists('use_forward', $value)) { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value['use_forward']; + unset($value['use_forward']); + } + + if (array_key_exists('login_path', $value)) { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value['login_path']; + unset($value['login_path']); + } + + if (array_key_exists('username_path', $value)) { + $this->_usedProperties['usernamePath'] = true; + $this->usernamePath = $value['username_path']; + unset($value['username_path']); + } + + if (array_key_exists('password_path', $value)) { + $this->_usedProperties['passwordPath'] = true; + $this->passwordPath = $value['password_path']; + unset($value['password_path']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['rememberMe'])) { + $output['remember_me'] = $this->rememberMe; + } + if (isset($this->_usedProperties['successHandler'])) { + $output['success_handler'] = $this->successHandler; + } + if (isset($this->_usedProperties['failureHandler'])) { + $output['failure_handler'] = $this->failureHandler; + } + if (isset($this->_usedProperties['checkPath'])) { + $output['check_path'] = $this->checkPath; + } + if (isset($this->_usedProperties['useForward'])) { + $output['use_forward'] = $this->useForward; + } + if (isset($this->_usedProperties['loginPath'])) { + $output['login_path'] = $this->loginPath; + } + if (isset($this->_usedProperties['usernamePath'])) { + $output['username_path'] = $this->usernamePath; + } + if (isset($this->_usedProperties['passwordPath'])) { + $output['password_path'] = $this->passwordPath; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/JsonLoginLdapConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/JsonLoginLdapConfig.php new file mode 100644 index 0000000..36ebcdf --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/JsonLoginLdapConfig.php @@ -0,0 +1,349 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function rememberMe($value): static + { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function successHandler($value): static + { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureHandler($value): static + { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value; + + return $this; + } + + /** + * @default '/login_check' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkPath($value): static + { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useForward($value): static + { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value; + + return $this; + } + + /** + * @default '/login' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function loginPath($value): static + { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value; + + return $this; + } + + /** + * @default 'username' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function usernamePath($value): static + { + $this->_usedProperties['usernamePath'] = true; + $this->usernamePath = $value; + + return $this; + } + + /** + * @default 'password' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passwordPath($value): static + { + $this->_usedProperties['passwordPath'] = true; + $this->passwordPath = $value; + + return $this; + } + + /** + * @default 'ldap' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default '{user_identifier}' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function dnString($value): static + { + $this->_usedProperties['dnString'] = true; + $this->dnString = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function queryString($value): static + { + $this->_usedProperties['queryString'] = true; + $this->queryString = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchDn($value): static + { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value; + + return $this; + } + + /** + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchPassword($value): static + { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('remember_me', $value)) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value['remember_me']; + unset($value['remember_me']); + } + + if (array_key_exists('success_handler', $value)) { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value['success_handler']; + unset($value['success_handler']); + } + + if (array_key_exists('failure_handler', $value)) { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value['failure_handler']; + unset($value['failure_handler']); + } + + if (array_key_exists('check_path', $value)) { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value['check_path']; + unset($value['check_path']); + } + + if (array_key_exists('use_forward', $value)) { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value['use_forward']; + unset($value['use_forward']); + } + + if (array_key_exists('login_path', $value)) { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value['login_path']; + unset($value['login_path']); + } + + if (array_key_exists('username_path', $value)) { + $this->_usedProperties['usernamePath'] = true; + $this->usernamePath = $value['username_path']; + unset($value['username_path']); + } + + if (array_key_exists('password_path', $value)) { + $this->_usedProperties['passwordPath'] = true; + $this->passwordPath = $value['password_path']; + unset($value['password_path']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('dn_string', $value)) { + $this->_usedProperties['dnString'] = true; + $this->dnString = $value['dn_string']; + unset($value['dn_string']); + } + + if (array_key_exists('query_string', $value)) { + $this->_usedProperties['queryString'] = true; + $this->queryString = $value['query_string']; + unset($value['query_string']); + } + + if (array_key_exists('search_dn', $value)) { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value['search_dn']; + unset($value['search_dn']); + } + + if (array_key_exists('search_password', $value)) { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value['search_password']; + unset($value['search_password']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['rememberMe'])) { + $output['remember_me'] = $this->rememberMe; + } + if (isset($this->_usedProperties['successHandler'])) { + $output['success_handler'] = $this->successHandler; + } + if (isset($this->_usedProperties['failureHandler'])) { + $output['failure_handler'] = $this->failureHandler; + } + if (isset($this->_usedProperties['checkPath'])) { + $output['check_path'] = $this->checkPath; + } + if (isset($this->_usedProperties['useForward'])) { + $output['use_forward'] = $this->useForward; + } + if (isset($this->_usedProperties['loginPath'])) { + $output['login_path'] = $this->loginPath; + } + if (isset($this->_usedProperties['usernamePath'])) { + $output['username_path'] = $this->usernamePath; + } + if (isset($this->_usedProperties['passwordPath'])) { + $output['password_path'] = $this->passwordPath; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['dnString'])) { + $output['dn_string'] = $this->dnString; + } + if (isset($this->_usedProperties['queryString'])) { + $output['query_string'] = $this->queryString; + } + if (isset($this->_usedProperties['searchDn'])) { + $output['search_dn'] = $this->searchDn; + } + if (isset($this->_usedProperties['searchPassword'])) { + $output['search_password'] = $this->searchPassword; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/JwtConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/JwtConfig.php new file mode 100644 index 0000000..9c76863 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/JwtConfig.php @@ -0,0 +1,75 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default 'lexik_jwt_authentication.security.jwt_authenticator' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function authenticator($value): static + { + $this->_usedProperties['authenticator'] = true; + $this->authenticator = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('authenticator', $value)) { + $this->_usedProperties['authenticator'] = true; + $this->authenticator = $value['authenticator']; + unset($value['authenticator']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['authenticator'])) { + $output['authenticator'] = $this->authenticator; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/LoginLinkConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/LoginLinkConfig.php new file mode 100644 index 0000000..42dc046 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/LoginLinkConfig.php @@ -0,0 +1,428 @@ +_usedProperties['checkRoute'] = true; + $this->checkRoute = $value; + + return $this; + } + + /** + * If true, only HTTP POST requests to "check_route" will be handled by the authenticator. + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkPostOnly($value): static + { + $this->_usedProperties['checkPostOnly'] = true; + $this->checkPostOnly = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function signatureProperties(ParamConfigurator|array $value): static + { + $this->_usedProperties['signatureProperties'] = true; + $this->signatureProperties = $value; + + return $this; + } + + /** + * The lifetime of the login link in seconds. + * @default 600 + * @param ParamConfigurator|int $value + * @return $this + */ + public function lifetime($value): static + { + $this->_usedProperties['lifetime'] = true; + $this->lifetime = $value; + + return $this; + } + + /** + * Max number of times a login link can be used - null means unlimited within lifetime. + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxUses($value): static + { + $this->_usedProperties['maxUses'] = true; + $this->maxUses = $value; + + return $this; + } + + /** + * Cache service id used to expired links of max_uses is set. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function usedLinkCache($value): static + { + $this->_usedProperties['usedLinkCache'] = true; + $this->usedLinkCache = $value; + + return $this; + } + + /** + * A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function successHandler($value): static + { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value; + + return $this; + } + + /** + * A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureHandler($value): static + { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value; + + return $this; + } + + /** + * The user provider to load users from. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function provider($value): static + { + $this->_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function alwaysUseDefaultTargetPath($value): static + { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value; + + return $this; + } + + /** + * @default '/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultTargetPath($value): static + { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value; + + return $this; + } + + /** + * @default '/login' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function loginPath($value): static + { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value; + + return $this; + } + + /** + * @default '_target_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function targetPathParameter($value): static + { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useReferer($value): static + { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePath($value): static + { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function failureForward($value): static + { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value; + + return $this; + } + + /** + * @default '_failure_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePathParameter($value): static + { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('check_route', $value)) { + $this->_usedProperties['checkRoute'] = true; + $this->checkRoute = $value['check_route']; + unset($value['check_route']); + } + + if (array_key_exists('check_post_only', $value)) { + $this->_usedProperties['checkPostOnly'] = true; + $this->checkPostOnly = $value['check_post_only']; + unset($value['check_post_only']); + } + + if (array_key_exists('signature_properties', $value)) { + $this->_usedProperties['signatureProperties'] = true; + $this->signatureProperties = $value['signature_properties']; + unset($value['signature_properties']); + } + + if (array_key_exists('lifetime', $value)) { + $this->_usedProperties['lifetime'] = true; + $this->lifetime = $value['lifetime']; + unset($value['lifetime']); + } + + if (array_key_exists('max_uses', $value)) { + $this->_usedProperties['maxUses'] = true; + $this->maxUses = $value['max_uses']; + unset($value['max_uses']); + } + + if (array_key_exists('used_link_cache', $value)) { + $this->_usedProperties['usedLinkCache'] = true; + $this->usedLinkCache = $value['used_link_cache']; + unset($value['used_link_cache']); + } + + if (array_key_exists('success_handler', $value)) { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value['success_handler']; + unset($value['success_handler']); + } + + if (array_key_exists('failure_handler', $value)) { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value['failure_handler']; + unset($value['failure_handler']); + } + + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('always_use_default_target_path', $value)) { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value['always_use_default_target_path']; + unset($value['always_use_default_target_path']); + } + + if (array_key_exists('default_target_path', $value)) { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value['default_target_path']; + unset($value['default_target_path']); + } + + if (array_key_exists('login_path', $value)) { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value['login_path']; + unset($value['login_path']); + } + + if (array_key_exists('target_path_parameter', $value)) { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value['target_path_parameter']; + unset($value['target_path_parameter']); + } + + if (array_key_exists('use_referer', $value)) { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value['use_referer']; + unset($value['use_referer']); + } + + if (array_key_exists('failure_path', $value)) { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value['failure_path']; + unset($value['failure_path']); + } + + if (array_key_exists('failure_forward', $value)) { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value['failure_forward']; + unset($value['failure_forward']); + } + + if (array_key_exists('failure_path_parameter', $value)) { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value['failure_path_parameter']; + unset($value['failure_path_parameter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['checkRoute'])) { + $output['check_route'] = $this->checkRoute; + } + if (isset($this->_usedProperties['checkPostOnly'])) { + $output['check_post_only'] = $this->checkPostOnly; + } + if (isset($this->_usedProperties['signatureProperties'])) { + $output['signature_properties'] = $this->signatureProperties; + } + if (isset($this->_usedProperties['lifetime'])) { + $output['lifetime'] = $this->lifetime; + } + if (isset($this->_usedProperties['maxUses'])) { + $output['max_uses'] = $this->maxUses; + } + if (isset($this->_usedProperties['usedLinkCache'])) { + $output['used_link_cache'] = $this->usedLinkCache; + } + if (isset($this->_usedProperties['successHandler'])) { + $output['success_handler'] = $this->successHandler; + } + if (isset($this->_usedProperties['failureHandler'])) { + $output['failure_handler'] = $this->failureHandler; + } + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['alwaysUseDefaultTargetPath'])) { + $output['always_use_default_target_path'] = $this->alwaysUseDefaultTargetPath; + } + if (isset($this->_usedProperties['defaultTargetPath'])) { + $output['default_target_path'] = $this->defaultTargetPath; + } + if (isset($this->_usedProperties['loginPath'])) { + $output['login_path'] = $this->loginPath; + } + if (isset($this->_usedProperties['targetPathParameter'])) { + $output['target_path_parameter'] = $this->targetPathParameter; + } + if (isset($this->_usedProperties['useReferer'])) { + $output['use_referer'] = $this->useReferer; + } + if (isset($this->_usedProperties['failurePath'])) { + $output['failure_path'] = $this->failurePath; + } + if (isset($this->_usedProperties['failureForward'])) { + $output['failure_forward'] = $this->failureForward; + } + if (isset($this->_usedProperties['failurePathParameter'])) { + $output['failure_path_parameter'] = $this->failurePathParameter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/LoginThrottlingConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/LoginThrottlingConfig.php new file mode 100644 index 0000000..2b9f60e --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/LoginThrottlingConfig.php @@ -0,0 +1,123 @@ +_usedProperties['limiter'] = true; + $this->limiter = $value; + + return $this; + } + + /** + * @default 5 + * @param ParamConfigurator|int $value + * @return $this + */ + public function maxAttempts($value): static + { + $this->_usedProperties['maxAttempts'] = true; + $this->maxAttempts = $value; + + return $this; + } + + /** + * @default '1 minute' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function interval($value): static + { + $this->_usedProperties['interval'] = true; + $this->interval = $value; + + return $this; + } + + /** + * The service ID of the lock factory used by the login rate limiter (or null to disable locking) + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function lockFactory($value): static + { + $this->_usedProperties['lockFactory'] = true; + $this->lockFactory = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('limiter', $value)) { + $this->_usedProperties['limiter'] = true; + $this->limiter = $value['limiter']; + unset($value['limiter']); + } + + if (array_key_exists('max_attempts', $value)) { + $this->_usedProperties['maxAttempts'] = true; + $this->maxAttempts = $value['max_attempts']; + unset($value['max_attempts']); + } + + if (array_key_exists('interval', $value)) { + $this->_usedProperties['interval'] = true; + $this->interval = $value['interval']; + unset($value['interval']); + } + + if (array_key_exists('lock_factory', $value)) { + $this->_usedProperties['lockFactory'] = true; + $this->lockFactory = $value['lock_factory']; + unset($value['lock_factory']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['limiter'])) { + $output['limiter'] = $this->limiter; + } + if (isset($this->_usedProperties['maxAttempts'])) { + $output['max_attempts'] = $this->maxAttempts; + } + if (isset($this->_usedProperties['interval'])) { + $output['interval'] = $this->interval; + } + if (isset($this->_usedProperties['lockFactory'])) { + $output['lock_factory'] = $this->lockFactory; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/Logout/DeleteCookieConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/Logout/DeleteCookieConfig.php new file mode 100644 index 0000000..b8a5bd8 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/Logout/DeleteCookieConfig.php @@ -0,0 +1,144 @@ +_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function domain($value): static + { + $this->_usedProperties['domain'] = true; + $this->domain = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function secure($value): static + { + $this->_usedProperties['secure'] = true; + $this->secure = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function samesite($value): static + { + $this->_usedProperties['samesite'] = true; + $this->samesite = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function partitioned($value): static + { + $this->_usedProperties['partitioned'] = true; + $this->partitioned = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('domain', $value)) { + $this->_usedProperties['domain'] = true; + $this->domain = $value['domain']; + unset($value['domain']); + } + + if (array_key_exists('secure', $value)) { + $this->_usedProperties['secure'] = true; + $this->secure = $value['secure']; + unset($value['secure']); + } + + if (array_key_exists('samesite', $value)) { + $this->_usedProperties['samesite'] = true; + $this->samesite = $value['samesite']; + unset($value['samesite']); + } + + if (array_key_exists('partitioned', $value)) { + $this->_usedProperties['partitioned'] = true; + $this->partitioned = $value['partitioned']; + unset($value['partitioned']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['domain'])) { + $output['domain'] = $this->domain; + } + if (isset($this->_usedProperties['secure'])) { + $output['secure'] = $this->secure; + } + if (isset($this->_usedProperties['samesite'])) { + $output['samesite'] = $this->samesite; + } + if (isset($this->_usedProperties['partitioned'])) { + $output['partitioned'] = $this->partitioned; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/LogoutConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/LogoutConfig.php new file mode 100644 index 0000000..e55be1c --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/LogoutConfig.php @@ -0,0 +1,250 @@ +_usedProperties['enableCsrf'] = true; + $this->enableCsrf = $value; + + return $this; + } + + /** + * @default 'logout' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function csrfTokenId($value): static + { + $this->_usedProperties['csrfTokenId'] = true; + $this->csrfTokenId = $value; + + return $this; + } + + /** + * @default '_csrf_token' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function csrfParameter($value): static + { + $this->_usedProperties['csrfParameter'] = true; + $this->csrfParameter = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function csrfTokenManager($value): static + { + $this->_usedProperties['csrfTokenManager'] = true; + $this->csrfTokenManager = $value; + + return $this; + } + + /** + * @default '/logout' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default '/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function target($value): static + { + $this->_usedProperties['target'] = true; + $this->target = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function invalidateSession($value): static + { + $this->_usedProperties['invalidateSession'] = true; + $this->invalidateSession = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function clearSiteData(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['clearSiteData'] = true; + $this->clearSiteData = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Security\FirewallConfig\Logout\DeleteCookieConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\FirewallConfig\Logout\DeleteCookieConfig : static) + */ + public function deleteCookie(string $name, array $value = []): \Symfony\Config\Security\FirewallConfig\Logout\DeleteCookieConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['deleteCookies'] = true; + $this->deleteCookies[$name] = $value; + + return $this; + } + + if (!isset($this->deleteCookies[$name]) || !$this->deleteCookies[$name] instanceof \Symfony\Config\Security\FirewallConfig\Logout\DeleteCookieConfig) { + $this->_usedProperties['deleteCookies'] = true; + $this->deleteCookies[$name] = new \Symfony\Config\Security\FirewallConfig\Logout\DeleteCookieConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "deleteCookie()" has already been initialized. You cannot pass values the second time you call deleteCookie().'); + } + + return $this->deleteCookies[$name]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enable_csrf', $value)) { + $this->_usedProperties['enableCsrf'] = true; + $this->enableCsrf = $value['enable_csrf']; + unset($value['enable_csrf']); + } + + if (array_key_exists('csrf_token_id', $value)) { + $this->_usedProperties['csrfTokenId'] = true; + $this->csrfTokenId = $value['csrf_token_id']; + unset($value['csrf_token_id']); + } + + if (array_key_exists('csrf_parameter', $value)) { + $this->_usedProperties['csrfParameter'] = true; + $this->csrfParameter = $value['csrf_parameter']; + unset($value['csrf_parameter']); + } + + if (array_key_exists('csrf_token_manager', $value)) { + $this->_usedProperties['csrfTokenManager'] = true; + $this->csrfTokenManager = $value['csrf_token_manager']; + unset($value['csrf_token_manager']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('target', $value)) { + $this->_usedProperties['target'] = true; + $this->target = $value['target']; + unset($value['target']); + } + + if (array_key_exists('invalidate_session', $value)) { + $this->_usedProperties['invalidateSession'] = true; + $this->invalidateSession = $value['invalidate_session']; + unset($value['invalidate_session']); + } + + if (array_key_exists('clear_site_data', $value)) { + $this->_usedProperties['clearSiteData'] = true; + $this->clearSiteData = $value['clear_site_data']; + unset($value['clear_site_data']); + } + + if (array_key_exists('delete_cookies', $value)) { + $this->_usedProperties['deleteCookies'] = true; + $this->deleteCookies = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Security\FirewallConfig\Logout\DeleteCookieConfig($v) : $v, $value['delete_cookies']); + unset($value['delete_cookies']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enableCsrf'])) { + $output['enable_csrf'] = $this->enableCsrf; + } + if (isset($this->_usedProperties['csrfTokenId'])) { + $output['csrf_token_id'] = $this->csrfTokenId; + } + if (isset($this->_usedProperties['csrfParameter'])) { + $output['csrf_parameter'] = $this->csrfParameter; + } + if (isset($this->_usedProperties['csrfTokenManager'])) { + $output['csrf_token_manager'] = $this->csrfTokenManager; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['target'])) { + $output['target'] = $this->target; + } + if (isset($this->_usedProperties['invalidateSession'])) { + $output['invalidate_session'] = $this->invalidateSession; + } + if (isset($this->_usedProperties['clearSiteData'])) { + $output['clear_site_data'] = $this->clearSiteData; + } + if (isset($this->_usedProperties['deleteCookies'])) { + $output['delete_cookies'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Security\FirewallConfig\Logout\DeleteCookieConfig ? $v->toArray() : $v, $this->deleteCookies); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/Oauth/OauthUserProvider/OrmConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/Oauth/OauthUserProvider/OrmConfig.php new file mode 100644 index 0000000..1951c5f --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/Oauth/OauthUserProvider/OrmConfig.php @@ -0,0 +1,96 @@ +_usedProperties['class'] = true; + $this->class = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function managerName($value): static + { + $this->_usedProperties['managerName'] = true; + $this->managerName = $value; + + return $this; + } + + /** + * @return $this + */ + public function properties(string $name, mixed $value): static + { + $this->_usedProperties['properties'] = true; + $this->properties[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('class', $value)) { + $this->_usedProperties['class'] = true; + $this->class = $value['class']; + unset($value['class']); + } + + if (array_key_exists('manager_name', $value)) { + $this->_usedProperties['managerName'] = true; + $this->managerName = $value['manager_name']; + unset($value['manager_name']); + } + + if (array_key_exists('properties', $value)) { + $this->_usedProperties['properties'] = true; + $this->properties = $value['properties']; + unset($value['properties']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['class'])) { + $output['class'] = $this->class; + } + if (isset($this->_usedProperties['managerName'])) { + $output['manager_name'] = $this->managerName; + } + if (isset($this->_usedProperties['properties'])) { + $output['properties'] = $this->properties; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/Oauth/OauthUserProviderConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/Oauth/OauthUserProviderConfig.php new file mode 100644 index 0000000..ae16223 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/Oauth/OauthUserProviderConfig.php @@ -0,0 +1,99 @@ +orm) { + $this->_usedProperties['orm'] = true; + $this->orm = new \Symfony\Config\Security\FirewallConfig\Oauth\OauthUserProvider\OrmConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "orm()" has already been initialized. You cannot pass values the second time you call orm().'); + } + + return $this->orm; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function oauth($value): static + { + $this->_usedProperties['oauth'] = true; + $this->oauth = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('orm', $value)) { + $this->_usedProperties['orm'] = true; + $this->orm = new \Symfony\Config\Security\FirewallConfig\Oauth\OauthUserProvider\OrmConfig($value['orm']); + unset($value['orm']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('oauth', $value)) { + $this->_usedProperties['oauth'] = true; + $this->oauth = $value['oauth']; + unset($value['oauth']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['orm'])) { + $output['orm'] = $this->orm->toArray(); + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['oauth'])) { + $output['oauth'] = $this->oauth; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/OauthConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/OauthConfig.php new file mode 100644 index 0000000..10c8e98 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/OauthConfig.php @@ -0,0 +1,396 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function rememberMe($value): static + { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function successHandler($value): static + { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failureHandler($value): static + { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value; + + return $this; + } + + /** + * @default '/login_check' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function checkPath($value): static + { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useForward($value): static + { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function loginPath($value): static + { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function alwaysUseDefaultTargetPath($value): static + { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value; + + return $this; + } + + /** + * @default '/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultTargetPath($value): static + { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value; + + return $this; + } + + /** + * @default '_target_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function targetPathParameter($value): static + { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function useReferer($value): static + { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePath($value): static + { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function failureForward($value): static + { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value; + + return $this; + } + + /** + * @default '_failure_path' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function failurePathParameter($value): static + { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value; + + return $this; + } + + public function oauthUserProvider(array $value = []): \Symfony\Config\Security\FirewallConfig\Oauth\OauthUserProviderConfig + { + if (null === $this->oauthUserProvider) { + $this->_usedProperties['oauthUserProvider'] = true; + $this->oauthUserProvider = new \Symfony\Config\Security\FirewallConfig\Oauth\OauthUserProviderConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "oauthUserProvider()" has already been initialized. You cannot pass values the second time you call oauthUserProvider().'); + } + + return $this->oauthUserProvider; + } + + /** + * @return $this + */ + public function resourceOwners(string $name, mixed $value): static + { + $this->_usedProperties['resourceOwners'] = true; + $this->resourceOwners[$name] = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('remember_me', $value)) { + $this->_usedProperties['rememberMe'] = true; + $this->rememberMe = $value['remember_me']; + unset($value['remember_me']); + } + + if (array_key_exists('success_handler', $value)) { + $this->_usedProperties['successHandler'] = true; + $this->successHandler = $value['success_handler']; + unset($value['success_handler']); + } + + if (array_key_exists('failure_handler', $value)) { + $this->_usedProperties['failureHandler'] = true; + $this->failureHandler = $value['failure_handler']; + unset($value['failure_handler']); + } + + if (array_key_exists('check_path', $value)) { + $this->_usedProperties['checkPath'] = true; + $this->checkPath = $value['check_path']; + unset($value['check_path']); + } + + if (array_key_exists('use_forward', $value)) { + $this->_usedProperties['useForward'] = true; + $this->useForward = $value['use_forward']; + unset($value['use_forward']); + } + + if (array_key_exists('login_path', $value)) { + $this->_usedProperties['loginPath'] = true; + $this->loginPath = $value['login_path']; + unset($value['login_path']); + } + + if (array_key_exists('always_use_default_target_path', $value)) { + $this->_usedProperties['alwaysUseDefaultTargetPath'] = true; + $this->alwaysUseDefaultTargetPath = $value['always_use_default_target_path']; + unset($value['always_use_default_target_path']); + } + + if (array_key_exists('default_target_path', $value)) { + $this->_usedProperties['defaultTargetPath'] = true; + $this->defaultTargetPath = $value['default_target_path']; + unset($value['default_target_path']); + } + + if (array_key_exists('target_path_parameter', $value)) { + $this->_usedProperties['targetPathParameter'] = true; + $this->targetPathParameter = $value['target_path_parameter']; + unset($value['target_path_parameter']); + } + + if (array_key_exists('use_referer', $value)) { + $this->_usedProperties['useReferer'] = true; + $this->useReferer = $value['use_referer']; + unset($value['use_referer']); + } + + if (array_key_exists('failure_path', $value)) { + $this->_usedProperties['failurePath'] = true; + $this->failurePath = $value['failure_path']; + unset($value['failure_path']); + } + + if (array_key_exists('failure_forward', $value)) { + $this->_usedProperties['failureForward'] = true; + $this->failureForward = $value['failure_forward']; + unset($value['failure_forward']); + } + + if (array_key_exists('failure_path_parameter', $value)) { + $this->_usedProperties['failurePathParameter'] = true; + $this->failurePathParameter = $value['failure_path_parameter']; + unset($value['failure_path_parameter']); + } + + if (array_key_exists('oauth_user_provider', $value)) { + $this->_usedProperties['oauthUserProvider'] = true; + $this->oauthUserProvider = new \Symfony\Config\Security\FirewallConfig\Oauth\OauthUserProviderConfig($value['oauth_user_provider']); + unset($value['oauth_user_provider']); + } + + if (array_key_exists('resource_owners', $value)) { + $this->_usedProperties['resourceOwners'] = true; + $this->resourceOwners = $value['resource_owners']; + unset($value['resource_owners']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['rememberMe'])) { + $output['remember_me'] = $this->rememberMe; + } + if (isset($this->_usedProperties['successHandler'])) { + $output['success_handler'] = $this->successHandler; + } + if (isset($this->_usedProperties['failureHandler'])) { + $output['failure_handler'] = $this->failureHandler; + } + if (isset($this->_usedProperties['checkPath'])) { + $output['check_path'] = $this->checkPath; + } + if (isset($this->_usedProperties['useForward'])) { + $output['use_forward'] = $this->useForward; + } + if (isset($this->_usedProperties['loginPath'])) { + $output['login_path'] = $this->loginPath; + } + if (isset($this->_usedProperties['alwaysUseDefaultTargetPath'])) { + $output['always_use_default_target_path'] = $this->alwaysUseDefaultTargetPath; + } + if (isset($this->_usedProperties['defaultTargetPath'])) { + $output['default_target_path'] = $this->defaultTargetPath; + } + if (isset($this->_usedProperties['targetPathParameter'])) { + $output['target_path_parameter'] = $this->targetPathParameter; + } + if (isset($this->_usedProperties['useReferer'])) { + $output['use_referer'] = $this->useReferer; + } + if (isset($this->_usedProperties['failurePath'])) { + $output['failure_path'] = $this->failurePath; + } + if (isset($this->_usedProperties['failureForward'])) { + $output['failure_forward'] = $this->failureForward; + } + if (isset($this->_usedProperties['failurePathParameter'])) { + $output['failure_path_parameter'] = $this->failurePathParameter; + } + if (isset($this->_usedProperties['oauthUserProvider'])) { + $output['oauth_user_provider'] = $this->oauthUserProvider->toArray(); + } + if (isset($this->_usedProperties['resourceOwners'])) { + $output['resource_owners'] = $this->resourceOwners; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProvider/DoctrineConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProvider/DoctrineConfig.php new file mode 100644 index 0000000..585c217 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProvider/DoctrineConfig.php @@ -0,0 +1,75 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function connection($value): static + { + $this->_usedProperties['connection'] = true; + $this->connection = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('connection', $value)) { + $this->_usedProperties['connection'] = true; + $this->connection = $value['connection']; + unset($value['connection']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['connection'])) { + $output['connection'] = $this->connection; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProviderConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProviderConfig.php new file mode 100644 index 0000000..2fb02f6 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProviderConfig.php @@ -0,0 +1,91 @@ +_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @default {"enabled":false,"connection":null} + * @return \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProvider\DoctrineConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProvider\DoctrineConfig : static) + */ + public function doctrine(array $value = []): \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProvider\DoctrineConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['doctrine'] = true; + $this->doctrine = $value; + + return $this; + } + + if (!$this->doctrine instanceof \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProvider\DoctrineConfig) { + $this->_usedProperties['doctrine'] = true; + $this->doctrine = new \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProvider\DoctrineConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "doctrine()" has already been initialized. You cannot pass values the second time you call doctrine().'); + } + + return $this->doctrine; + } + + public function __construct(array $value = []) + { + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('doctrine', $value)) { + $this->_usedProperties['doctrine'] = true; + $this->doctrine = \is_array($value['doctrine']) ? new \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProvider\DoctrineConfig($value['doctrine']) : $value['doctrine']; + unset($value['doctrine']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['doctrine'])) { + $output['doctrine'] = $this->doctrine instanceof \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProvider\DoctrineConfig ? $this->doctrine->toArray() : $this->doctrine; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMeConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMeConfig.php new file mode 100644 index 0000000..02aa46b --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RememberMeConfig.php @@ -0,0 +1,412 @@ +_usedProperties['secret'] = true; + $this->secret = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function service($value): static + { + $this->_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function userProviders(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['userProviders'] = true; + $this->userProviders = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function catchExceptions($value): static + { + $this->_usedProperties['catchExceptions'] = true; + $this->catchExceptions = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function signatureProperties(ParamConfigurator|array $value): static + { + $this->_usedProperties['signatureProperties'] = true; + $this->signatureProperties = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @return \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProviderConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProviderConfig : static) + */ + public function tokenProvider(string|array $value = []): \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProviderConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['tokenProvider'] = true; + $this->tokenProvider = $value; + + return $this; + } + + if (!$this->tokenProvider instanceof \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProviderConfig) { + $this->_usedProperties['tokenProvider'] = true; + $this->tokenProvider = new \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProviderConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "tokenProvider()" has already been initialized. You cannot pass values the second time you call tokenProvider().'); + } + + return $this->tokenProvider; + } + + /** + * The service ID of a custom rememberme token verifier. + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function tokenVerifier($value): static + { + $this->_usedProperties['tokenVerifier'] = true; + $this->tokenVerifier = $value; + + return $this; + } + + /** + * @default 'REMEMBERME' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function name($value): static + { + $this->_usedProperties['name'] = true; + $this->name = $value; + + return $this; + } + + /** + * @default 31536000 + * @param ParamConfigurator|int $value + * @return $this + */ + public function lifetime($value): static + { + $this->_usedProperties['lifetime'] = true; + $this->lifetime = $value; + + return $this; + } + + /** + * @default '/' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function path($value): static + { + $this->_usedProperties['path'] = true; + $this->path = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function domain($value): static + { + $this->_usedProperties['domain'] = true; + $this->domain = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|true|false|'auto' $value + * @return $this + */ + public function secure($value): static + { + $this->_usedProperties['secure'] = true; + $this->secure = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function httponly($value): static + { + $this->_usedProperties['httponly'] = true; + $this->httponly = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|NULL|'lax'|'strict'|'none' $value + * @return $this + */ + public function samesite($value): static + { + $this->_usedProperties['samesite'] = true; + $this->samesite = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function alwaysRememberMe($value): static + { + $this->_usedProperties['alwaysRememberMe'] = true; + $this->alwaysRememberMe = $value; + + return $this; + } + + /** + * @default '_remember_me' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function rememberMeParameter($value): static + { + $this->_usedProperties['rememberMeParameter'] = true; + $this->rememberMeParameter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('secret', $value)) { + $this->_usedProperties['secret'] = true; + $this->secret = $value['secret']; + unset($value['secret']); + } + + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('user_providers', $value)) { + $this->_usedProperties['userProviders'] = true; + $this->userProviders = $value['user_providers']; + unset($value['user_providers']); + } + + if (array_key_exists('catch_exceptions', $value)) { + $this->_usedProperties['catchExceptions'] = true; + $this->catchExceptions = $value['catch_exceptions']; + unset($value['catch_exceptions']); + } + + if (array_key_exists('signature_properties', $value)) { + $this->_usedProperties['signatureProperties'] = true; + $this->signatureProperties = $value['signature_properties']; + unset($value['signature_properties']); + } + + if (array_key_exists('token_provider', $value)) { + $this->_usedProperties['tokenProvider'] = true; + $this->tokenProvider = \is_array($value['token_provider']) ? new \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProviderConfig($value['token_provider']) : $value['token_provider']; + unset($value['token_provider']); + } + + if (array_key_exists('token_verifier', $value)) { + $this->_usedProperties['tokenVerifier'] = true; + $this->tokenVerifier = $value['token_verifier']; + unset($value['token_verifier']); + } + + if (array_key_exists('name', $value)) { + $this->_usedProperties['name'] = true; + $this->name = $value['name']; + unset($value['name']); + } + + if (array_key_exists('lifetime', $value)) { + $this->_usedProperties['lifetime'] = true; + $this->lifetime = $value['lifetime']; + unset($value['lifetime']); + } + + if (array_key_exists('path', $value)) { + $this->_usedProperties['path'] = true; + $this->path = $value['path']; + unset($value['path']); + } + + if (array_key_exists('domain', $value)) { + $this->_usedProperties['domain'] = true; + $this->domain = $value['domain']; + unset($value['domain']); + } + + if (array_key_exists('secure', $value)) { + $this->_usedProperties['secure'] = true; + $this->secure = $value['secure']; + unset($value['secure']); + } + + if (array_key_exists('httponly', $value)) { + $this->_usedProperties['httponly'] = true; + $this->httponly = $value['httponly']; + unset($value['httponly']); + } + + if (array_key_exists('samesite', $value)) { + $this->_usedProperties['samesite'] = true; + $this->samesite = $value['samesite']; + unset($value['samesite']); + } + + if (array_key_exists('always_remember_me', $value)) { + $this->_usedProperties['alwaysRememberMe'] = true; + $this->alwaysRememberMe = $value['always_remember_me']; + unset($value['always_remember_me']); + } + + if (array_key_exists('remember_me_parameter', $value)) { + $this->_usedProperties['rememberMeParameter'] = true; + $this->rememberMeParameter = $value['remember_me_parameter']; + unset($value['remember_me_parameter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['secret'])) { + $output['secret'] = $this->secret; + } + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['userProviders'])) { + $output['user_providers'] = $this->userProviders; + } + if (isset($this->_usedProperties['catchExceptions'])) { + $output['catch_exceptions'] = $this->catchExceptions; + } + if (isset($this->_usedProperties['signatureProperties'])) { + $output['signature_properties'] = $this->signatureProperties; + } + if (isset($this->_usedProperties['tokenProvider'])) { + $output['token_provider'] = $this->tokenProvider instanceof \Symfony\Config\Security\FirewallConfig\RememberMe\TokenProviderConfig ? $this->tokenProvider->toArray() : $this->tokenProvider; + } + if (isset($this->_usedProperties['tokenVerifier'])) { + $output['token_verifier'] = $this->tokenVerifier; + } + if (isset($this->_usedProperties['name'])) { + $output['name'] = $this->name; + } + if (isset($this->_usedProperties['lifetime'])) { + $output['lifetime'] = $this->lifetime; + } + if (isset($this->_usedProperties['path'])) { + $output['path'] = $this->path; + } + if (isset($this->_usedProperties['domain'])) { + $output['domain'] = $this->domain; + } + if (isset($this->_usedProperties['secure'])) { + $output['secure'] = $this->secure; + } + if (isset($this->_usedProperties['httponly'])) { + $output['httponly'] = $this->httponly; + } + if (isset($this->_usedProperties['samesite'])) { + $output['samesite'] = $this->samesite; + } + if (isset($this->_usedProperties['alwaysRememberMe'])) { + $output['always_remember_me'] = $this->alwaysRememberMe; + } + if (isset($this->_usedProperties['rememberMeParameter'])) { + $output['remember_me_parameter'] = $this->rememberMeParameter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/RemoteUserConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RemoteUserConfig.php new file mode 100644 index 0000000..00a441c --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/RemoteUserConfig.php @@ -0,0 +1,75 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default 'REMOTE_USER' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function user($value): static + { + $this->_usedProperties['user'] = true; + $this->user = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('user', $value)) { + $this->_usedProperties['user'] = true; + $this->user = $value['user']; + unset($value['user']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['user'])) { + $output['user'] = $this->user; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/SwitchUserConfig.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/SwitchUserConfig.php new file mode 100644 index 0000000..b6bc8eb --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/SwitchUserConfig.php @@ -0,0 +1,121 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default '_switch_user' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function parameter($value): static + { + $this->_usedProperties['parameter'] = true; + $this->parameter = $value; + + return $this; + } + + /** + * @default 'ROLE_ALLOWED_TO_SWITCH' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function role($value): static + { + $this->_usedProperties['role'] = true; + $this->role = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function targetRoute($value): static + { + $this->_usedProperties['targetRoute'] = true; + $this->targetRoute = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('parameter', $value)) { + $this->_usedProperties['parameter'] = true; + $this->parameter = $value['parameter']; + unset($value['parameter']); + } + + if (array_key_exists('role', $value)) { + $this->_usedProperties['role'] = true; + $this->role = $value['role']; + unset($value['role']); + } + + if (array_key_exists('target_route', $value)) { + $this->_usedProperties['targetRoute'] = true; + $this->targetRoute = $value['target_route']; + unset($value['target_route']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['parameter'])) { + $output['parameter'] = $this->parameter; + } + if (isset($this->_usedProperties['role'])) { + $output['role'] = $this->role; + } + if (isset($this->_usedProperties['targetRoute'])) { + $output['target_route'] = $this->targetRoute; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/FirewallConfig/X509Config.php b/var/cache/dev/Symfony/Config/Security/FirewallConfig/X509Config.php new file mode 100644 index 0000000..808fbd0 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/FirewallConfig/X509Config.php @@ -0,0 +1,121 @@ +_usedProperties['provider'] = true; + $this->provider = $value; + + return $this; + } + + /** + * @default 'SSL_CLIENT_S_DN_Email' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function user($value): static + { + $this->_usedProperties['user'] = true; + $this->user = $value; + + return $this; + } + + /** + * @default 'SSL_CLIENT_S_DN' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function credentials($value): static + { + $this->_usedProperties['credentials'] = true; + $this->credentials = $value; + + return $this; + } + + /** + * @default 'emailAddress' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function userIdentifier($value): static + { + $this->_usedProperties['userIdentifier'] = true; + $this->userIdentifier = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('provider', $value)) { + $this->_usedProperties['provider'] = true; + $this->provider = $value['provider']; + unset($value['provider']); + } + + if (array_key_exists('user', $value)) { + $this->_usedProperties['user'] = true; + $this->user = $value['user']; + unset($value['user']); + } + + if (array_key_exists('credentials', $value)) { + $this->_usedProperties['credentials'] = true; + $this->credentials = $value['credentials']; + unset($value['credentials']); + } + + if (array_key_exists('user_identifier', $value)) { + $this->_usedProperties['userIdentifier'] = true; + $this->userIdentifier = $value['user_identifier']; + unset($value['user_identifier']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['provider'])) { + $output['provider'] = $this->provider; + } + if (isset($this->_usedProperties['user'])) { + $output['user'] = $this->user; + } + if (isset($this->_usedProperties['credentials'])) { + $output['credentials'] = $this->credentials; + } + if (isset($this->_usedProperties['userIdentifier'])) { + $output['user_identifier'] = $this->userIdentifier; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/PasswordHasherConfig.php b/var/cache/dev/Symfony/Config/Security/PasswordHasherConfig.php new file mode 100644 index 0000000..0a6fdc5 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/PasswordHasherConfig.php @@ -0,0 +1,283 @@ +_usedProperties['algorithm'] = true; + $this->algorithm = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|mixed $value + * + * @return $this + */ + public function migrateFrom(mixed $value): static + { + $this->_usedProperties['migrateFrom'] = true; + $this->migrateFrom = $value; + + return $this; + } + + /** + * Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. + * @default 'sha512' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function hashAlgorithm($value): static + { + $this->_usedProperties['hashAlgorithm'] = true; + $this->hashAlgorithm = $value; + + return $this; + } + + /** + * @default 40 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function keyLength($value): static + { + $this->_usedProperties['keyLength'] = true; + $this->keyLength = $value; + + return $this; + } + + /** + * @default false + * @param ParamConfigurator|bool $value + * @return $this + */ + public function ignoreCase($value): static + { + $this->_usedProperties['ignoreCase'] = true; + $this->ignoreCase = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function encodeAsBase64($value): static + { + $this->_usedProperties['encodeAsBase64'] = true; + $this->encodeAsBase64 = $value; + + return $this; + } + + /** + * @default 5000 + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function iterations($value): static + { + $this->_usedProperties['iterations'] = true; + $this->iterations = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function cost($value): static + { + $this->_usedProperties['cost'] = true; + $this->cost = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function memoryCost($value): static + { + $this->_usedProperties['memoryCost'] = true; + $this->memoryCost = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function timeCost($value): static + { + $this->_usedProperties['timeCost'] = true; + $this->timeCost = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function id($value): static + { + $this->_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('algorithm', $value)) { + $this->_usedProperties['algorithm'] = true; + $this->algorithm = $value['algorithm']; + unset($value['algorithm']); + } + + if (array_key_exists('migrate_from', $value)) { + $this->_usedProperties['migrateFrom'] = true; + $this->migrateFrom = $value['migrate_from']; + unset($value['migrate_from']); + } + + if (array_key_exists('hash_algorithm', $value)) { + $this->_usedProperties['hashAlgorithm'] = true; + $this->hashAlgorithm = $value['hash_algorithm']; + unset($value['hash_algorithm']); + } + + if (array_key_exists('key_length', $value)) { + $this->_usedProperties['keyLength'] = true; + $this->keyLength = $value['key_length']; + unset($value['key_length']); + } + + if (array_key_exists('ignore_case', $value)) { + $this->_usedProperties['ignoreCase'] = true; + $this->ignoreCase = $value['ignore_case']; + unset($value['ignore_case']); + } + + if (array_key_exists('encode_as_base64', $value)) { + $this->_usedProperties['encodeAsBase64'] = true; + $this->encodeAsBase64 = $value['encode_as_base64']; + unset($value['encode_as_base64']); + } + + if (array_key_exists('iterations', $value)) { + $this->_usedProperties['iterations'] = true; + $this->iterations = $value['iterations']; + unset($value['iterations']); + } + + if (array_key_exists('cost', $value)) { + $this->_usedProperties['cost'] = true; + $this->cost = $value['cost']; + unset($value['cost']); + } + + if (array_key_exists('memory_cost', $value)) { + $this->_usedProperties['memoryCost'] = true; + $this->memoryCost = $value['memory_cost']; + unset($value['memory_cost']); + } + + if (array_key_exists('time_cost', $value)) { + $this->_usedProperties['timeCost'] = true; + $this->timeCost = $value['time_cost']; + unset($value['time_cost']); + } + + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['algorithm'])) { + $output['algorithm'] = $this->algorithm; + } + if (isset($this->_usedProperties['migrateFrom'])) { + $output['migrate_from'] = $this->migrateFrom; + } + if (isset($this->_usedProperties['hashAlgorithm'])) { + $output['hash_algorithm'] = $this->hashAlgorithm; + } + if (isset($this->_usedProperties['keyLength'])) { + $output['key_length'] = $this->keyLength; + } + if (isset($this->_usedProperties['ignoreCase'])) { + $output['ignore_case'] = $this->ignoreCase; + } + if (isset($this->_usedProperties['encodeAsBase64'])) { + $output['encode_as_base64'] = $this->encodeAsBase64; + } + if (isset($this->_usedProperties['iterations'])) { + $output['iterations'] = $this->iterations; + } + if (isset($this->_usedProperties['cost'])) { + $output['cost'] = $this->cost; + } + if (isset($this->_usedProperties['memoryCost'])) { + $output['memory_cost'] = $this->memoryCost; + } + if (isset($this->_usedProperties['timeCost'])) { + $output['time_cost'] = $this->timeCost; + } + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/ProviderConfig.php b/var/cache/dev/Symfony/Config/Security/ProviderConfig.php new file mode 100644 index 0000000..e1662fd --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/ProviderConfig.php @@ -0,0 +1,168 @@ +_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + public function chain(array $value = []): \Symfony\Config\Security\ProviderConfig\ChainConfig + { + if (null === $this->chain) { + $this->_usedProperties['chain'] = true; + $this->chain = new \Symfony\Config\Security\ProviderConfig\ChainConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "chain()" has already been initialized. You cannot pass values the second time you call chain().'); + } + + return $this->chain; + } + + public function entity(array $value = []): \Symfony\Config\Security\ProviderConfig\EntityConfig + { + if (null === $this->entity) { + $this->_usedProperties['entity'] = true; + $this->entity = new \Symfony\Config\Security\ProviderConfig\EntityConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "entity()" has already been initialized. You cannot pass values the second time you call entity().'); + } + + return $this->entity; + } + + public function memory(array $value = []): \Symfony\Config\Security\ProviderConfig\MemoryConfig + { + if (null === $this->memory) { + $this->_usedProperties['memory'] = true; + $this->memory = new \Symfony\Config\Security\ProviderConfig\MemoryConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "memory()" has already been initialized. You cannot pass values the second time you call memory().'); + } + + return $this->memory; + } + + public function ldap(array $value = []): \Symfony\Config\Security\ProviderConfig\LdapConfig + { + if (null === $this->ldap) { + $this->_usedProperties['ldap'] = true; + $this->ldap = new \Symfony\Config\Security\ProviderConfig\LdapConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "ldap()" has already been initialized. You cannot pass values the second time you call ldap().'); + } + + return $this->ldap; + } + + public function lexikJwt(array $value = []): \Symfony\Config\Security\ProviderConfig\LexikJwtConfig + { + if (null === $this->lexikJwt) { + $this->_usedProperties['lexikJwt'] = true; + $this->lexikJwt = new \Symfony\Config\Security\ProviderConfig\LexikJwtConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "lexikJwt()" has already been initialized. You cannot pass values the second time you call lexikJwt().'); + } + + return $this->lexikJwt; + } + + public function __construct(array $value = []) + { + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('chain', $value)) { + $this->_usedProperties['chain'] = true; + $this->chain = new \Symfony\Config\Security\ProviderConfig\ChainConfig($value['chain']); + unset($value['chain']); + } + + if (array_key_exists('entity', $value)) { + $this->_usedProperties['entity'] = true; + $this->entity = new \Symfony\Config\Security\ProviderConfig\EntityConfig($value['entity']); + unset($value['entity']); + } + + if (array_key_exists('memory', $value)) { + $this->_usedProperties['memory'] = true; + $this->memory = new \Symfony\Config\Security\ProviderConfig\MemoryConfig($value['memory']); + unset($value['memory']); + } + + if (array_key_exists('ldap', $value)) { + $this->_usedProperties['ldap'] = true; + $this->ldap = new \Symfony\Config\Security\ProviderConfig\LdapConfig($value['ldap']); + unset($value['ldap']); + } + + if (array_key_exists('lexik_jwt', $value)) { + $this->_usedProperties['lexikJwt'] = true; + $this->lexikJwt = new \Symfony\Config\Security\ProviderConfig\LexikJwtConfig($value['lexik_jwt']); + unset($value['lexik_jwt']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['chain'])) { + $output['chain'] = $this->chain->toArray(); + } + if (isset($this->_usedProperties['entity'])) { + $output['entity'] = $this->entity->toArray(); + } + if (isset($this->_usedProperties['memory'])) { + $output['memory'] = $this->memory->toArray(); + } + if (isset($this->_usedProperties['ldap'])) { + $output['ldap'] = $this->ldap->toArray(); + } + if (isset($this->_usedProperties['lexikJwt'])) { + $output['lexik_jwt'] = $this->lexikJwt->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/ProviderConfig/ChainConfig.php b/var/cache/dev/Symfony/Config/Security/ProviderConfig/ChainConfig.php new file mode 100644 index 0000000..0df3538 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/ProviderConfig/ChainConfig.php @@ -0,0 +1,52 @@ +|string $value + * + * @return $this + */ + public function providers(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['providers'] = true; + $this->providers = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('providers', $value)) { + $this->_usedProperties['providers'] = true; + $this->providers = $value['providers']; + unset($value['providers']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['providers'])) { + $output['providers'] = $this->providers; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/ProviderConfig/EntityConfig.php b/var/cache/dev/Symfony/Config/Security/ProviderConfig/EntityConfig.php new file mode 100644 index 0000000..4bb4a9d --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/ProviderConfig/EntityConfig.php @@ -0,0 +1,99 @@ +_usedProperties['class'] = true; + $this->class = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function property($value): static + { + $this->_usedProperties['property'] = true; + $this->property = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function managerName($value): static + { + $this->_usedProperties['managerName'] = true; + $this->managerName = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('class', $value)) { + $this->_usedProperties['class'] = true; + $this->class = $value['class']; + unset($value['class']); + } + + if (array_key_exists('property', $value)) { + $this->_usedProperties['property'] = true; + $this->property = $value['property']; + unset($value['property']); + } + + if (array_key_exists('manager_name', $value)) { + $this->_usedProperties['managerName'] = true; + $this->managerName = $value['manager_name']; + unset($value['manager_name']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['class'])) { + $output['class'] = $this->class; + } + if (isset($this->_usedProperties['property'])) { + $output['property'] = $this->property; + } + if (isset($this->_usedProperties['managerName'])) { + $output['manager_name'] = $this->managerName; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/ProviderConfig/LdapConfig.php b/var/cache/dev/Symfony/Config/Security/ProviderConfig/LdapConfig.php new file mode 100644 index 0000000..5590591 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/ProviderConfig/LdapConfig.php @@ -0,0 +1,236 @@ +_usedProperties['service'] = true; + $this->service = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function baseDn($value): static + { + $this->_usedProperties['baseDn'] = true; + $this->baseDn = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchDn($value): static + { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function searchPassword($value): static + { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list $value + * + * @return $this + */ + public function extraFields(ParamConfigurator|array $value): static + { + $this->_usedProperties['extraFields'] = true; + $this->extraFields = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function defaultRoles(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['defaultRoles'] = true; + $this->defaultRoles = $value; + + return $this; + } + + /** + * @default 'sAMAccountName' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function uidKey($value): static + { + $this->_usedProperties['uidKey'] = true; + $this->uidKey = $value; + + return $this; + } + + /** + * @default '({uid_key}={user_identifier})' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function filter($value): static + { + $this->_usedProperties['filter'] = true; + $this->filter = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function passwordAttribute($value): static + { + $this->_usedProperties['passwordAttribute'] = true; + $this->passwordAttribute = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('service', $value)) { + $this->_usedProperties['service'] = true; + $this->service = $value['service']; + unset($value['service']); + } + + if (array_key_exists('base_dn', $value)) { + $this->_usedProperties['baseDn'] = true; + $this->baseDn = $value['base_dn']; + unset($value['base_dn']); + } + + if (array_key_exists('search_dn', $value)) { + $this->_usedProperties['searchDn'] = true; + $this->searchDn = $value['search_dn']; + unset($value['search_dn']); + } + + if (array_key_exists('search_password', $value)) { + $this->_usedProperties['searchPassword'] = true; + $this->searchPassword = $value['search_password']; + unset($value['search_password']); + } + + if (array_key_exists('extra_fields', $value)) { + $this->_usedProperties['extraFields'] = true; + $this->extraFields = $value['extra_fields']; + unset($value['extra_fields']); + } + + if (array_key_exists('default_roles', $value)) { + $this->_usedProperties['defaultRoles'] = true; + $this->defaultRoles = $value['default_roles']; + unset($value['default_roles']); + } + + if (array_key_exists('uid_key', $value)) { + $this->_usedProperties['uidKey'] = true; + $this->uidKey = $value['uid_key']; + unset($value['uid_key']); + } + + if (array_key_exists('filter', $value)) { + $this->_usedProperties['filter'] = true; + $this->filter = $value['filter']; + unset($value['filter']); + } + + if (array_key_exists('password_attribute', $value)) { + $this->_usedProperties['passwordAttribute'] = true; + $this->passwordAttribute = $value['password_attribute']; + unset($value['password_attribute']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['service'])) { + $output['service'] = $this->service; + } + if (isset($this->_usedProperties['baseDn'])) { + $output['base_dn'] = $this->baseDn; + } + if (isset($this->_usedProperties['searchDn'])) { + $output['search_dn'] = $this->searchDn; + } + if (isset($this->_usedProperties['searchPassword'])) { + $output['search_password'] = $this->searchPassword; + } + if (isset($this->_usedProperties['extraFields'])) { + $output['extra_fields'] = $this->extraFields; + } + if (isset($this->_usedProperties['defaultRoles'])) { + $output['default_roles'] = $this->defaultRoles; + } + if (isset($this->_usedProperties['uidKey'])) { + $output['uid_key'] = $this->uidKey; + } + if (isset($this->_usedProperties['filter'])) { + $output['filter'] = $this->filter; + } + if (isset($this->_usedProperties['passwordAttribute'])) { + $output['password_attribute'] = $this->passwordAttribute; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/ProviderConfig/LexikJwtConfig.php b/var/cache/dev/Symfony/Config/Security/ProviderConfig/LexikJwtConfig.php new file mode 100644 index 0000000..9cbfa54 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/ProviderConfig/LexikJwtConfig.php @@ -0,0 +1,52 @@ +_usedProperties['class'] = true; + $this->class = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('class', $value)) { + $this->_usedProperties['class'] = true; + $this->class = $value['class']; + unset($value['class']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['class'])) { + $output['class'] = $this->class; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/ProviderConfig/Memory/UserConfig.php b/var/cache/dev/Symfony/Config/Security/ProviderConfig/Memory/UserConfig.php new file mode 100644 index 0000000..ff4a54d --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/ProviderConfig/Memory/UserConfig.php @@ -0,0 +1,75 @@ +_usedProperties['password'] = true; + $this->password = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function roles(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['roles'] = true; + $this->roles = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('password', $value)) { + $this->_usedProperties['password'] = true; + $this->password = $value['password']; + unset($value['password']); + } + + if (array_key_exists('roles', $value)) { + $this->_usedProperties['roles'] = true; + $this->roles = $value['roles']; + unset($value['roles']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['password'])) { + $output['password'] = $this->password; + } + if (isset($this->_usedProperties['roles'])) { + $output['roles'] = $this->roles; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Security/ProviderConfig/MemoryConfig.php b/var/cache/dev/Symfony/Config/Security/ProviderConfig/MemoryConfig.php new file mode 100644 index 0000000..8600c2a --- /dev/null +++ b/var/cache/dev/Symfony/Config/Security/ProviderConfig/MemoryConfig.php @@ -0,0 +1,52 @@ +users[$identifier])) { + $this->_usedProperties['users'] = true; + $this->users[$identifier] = new \Symfony\Config\Security\ProviderConfig\Memory\UserConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "user()" has already been initialized. You cannot pass values the second time you call user().'); + } + + return $this->users[$identifier]; + } + + public function __construct(array $value = []) + { + if (array_key_exists('users', $value)) { + $this->_usedProperties['users'] = true; + $this->users = array_map(fn ($v) => new \Symfony\Config\Security\ProviderConfig\Memory\UserConfig($v), $value['users']); + unset($value['users']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['users'])) { + $output['users'] = array_map(fn ($v) => $v->toArray(), $this->users); + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/SecurityConfig.php b/var/cache/dev/Symfony/Config/SecurityConfig.php new file mode 100644 index 0000000..964c9d0 --- /dev/null +++ b/var/cache/dev/Symfony/Config/SecurityConfig.php @@ -0,0 +1,281 @@ +_usedProperties['accessDeniedUrl'] = true; + $this->accessDeniedUrl = $value; + + return $this; + } + + /** + * @default 'migrate' + * @param ParamConfigurator|'none'|'migrate'|'invalidate' $value + * @return $this + */ + public function sessionFixationStrategy($value): static + { + $this->_usedProperties['sessionFixationStrategy'] = true; + $this->sessionFixationStrategy = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function hideUserNotFound($value): static + { + $this->_usedProperties['hideUserNotFound'] = true; + $this->hideUserNotFound = $value; + + return $this; + } + + /** + * @default true + * @param ParamConfigurator|bool $value + * @return $this + */ + public function eraseCredentials($value): static + { + $this->_usedProperties['eraseCredentials'] = true; + $this->eraseCredentials = $value; + + return $this; + } + + /** + * @default {"allow_if_all_abstain":false,"allow_if_equal_granted_denied":true} + */ + public function accessDecisionManager(array $value = []): \Symfony\Config\Security\AccessDecisionManagerConfig + { + if (null === $this->accessDecisionManager) { + $this->_usedProperties['accessDecisionManager'] = true; + $this->accessDecisionManager = new \Symfony\Config\Security\AccessDecisionManagerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "accessDecisionManager()" has already been initialized. You cannot pass values the second time you call accessDecisionManager().'); + } + + return $this->accessDecisionManager; + } + + /** + * @template TValue + * @param TValue $value + * @example "auto" + * @example {"algorithm":"auto","time_cost":8,"cost":13} + * @return \Symfony\Config\Security\PasswordHasherConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Security\PasswordHasherConfig : static) + */ + public function passwordHasher(string $class, string|array $value = []): \Symfony\Config\Security\PasswordHasherConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['passwordHashers'] = true; + $this->passwordHashers[$class] = $value; + + return $this; + } + + if (!isset($this->passwordHashers[$class]) || !$this->passwordHashers[$class] instanceof \Symfony\Config\Security\PasswordHasherConfig) { + $this->_usedProperties['passwordHashers'] = true; + $this->passwordHashers[$class] = new \Symfony\Config\Security\PasswordHasherConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "passwordHasher()" has already been initialized. You cannot pass values the second time you call passwordHasher().'); + } + + return $this->passwordHashers[$class]; + } + + /** + * @example {"memory":{"users":{"foo":{"password":"foo","roles":"ROLE_USER"},"bar":{"password":"bar","roles":"[ROLE_USER, ROLE_ADMIN]"}}}} + * @example {"entity":{"class":"SecurityBundle:User","property":"username"}} + */ + public function provider(string $name, array $value = []): \Symfony\Config\Security\ProviderConfig + { + if (!isset($this->providers[$name])) { + $this->_usedProperties['providers'] = true; + $this->providers[$name] = new \Symfony\Config\Security\ProviderConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "provider()" has already been initialized. You cannot pass values the second time you call provider().'); + } + + return $this->providers[$name]; + } + + public function firewall(string $name, array $value = []): \Symfony\Config\Security\FirewallConfig + { + if (!isset($this->firewalls[$name])) { + $this->_usedProperties['firewalls'] = true; + $this->firewalls[$name] = new \Symfony\Config\Security\FirewallConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "firewall()" has already been initialized. You cannot pass values the second time you call firewall().'); + } + + return $this->firewalls[$name]; + } + + public function accessControl(array $value = []): \Symfony\Config\Security\AccessControlConfig + { + $this->_usedProperties['accessControl'] = true; + + return $this->accessControl[] = new \Symfony\Config\Security\AccessControlConfig($value); + } + + /** + * @return $this + */ + public function roleHierarchy(string $id, mixed $value): static + { + $this->_usedProperties['roleHierarchy'] = true; + $this->roleHierarchy[$id] = $value; + + return $this; + } + + public function getExtensionAlias(): string + { + return 'security'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('access_denied_url', $value)) { + $this->_usedProperties['accessDeniedUrl'] = true; + $this->accessDeniedUrl = $value['access_denied_url']; + unset($value['access_denied_url']); + } + + if (array_key_exists('session_fixation_strategy', $value)) { + $this->_usedProperties['sessionFixationStrategy'] = true; + $this->sessionFixationStrategy = $value['session_fixation_strategy']; + unset($value['session_fixation_strategy']); + } + + if (array_key_exists('hide_user_not_found', $value)) { + $this->_usedProperties['hideUserNotFound'] = true; + $this->hideUserNotFound = $value['hide_user_not_found']; + unset($value['hide_user_not_found']); + } + + if (array_key_exists('erase_credentials', $value)) { + $this->_usedProperties['eraseCredentials'] = true; + $this->eraseCredentials = $value['erase_credentials']; + unset($value['erase_credentials']); + } + + if (array_key_exists('access_decision_manager', $value)) { + $this->_usedProperties['accessDecisionManager'] = true; + $this->accessDecisionManager = new \Symfony\Config\Security\AccessDecisionManagerConfig($value['access_decision_manager']); + unset($value['access_decision_manager']); + } + + if (array_key_exists('password_hashers', $value)) { + $this->_usedProperties['passwordHashers'] = true; + $this->passwordHashers = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Security\PasswordHasherConfig($v) : $v, $value['password_hashers']); + unset($value['password_hashers']); + } + + if (array_key_exists('providers', $value)) { + $this->_usedProperties['providers'] = true; + $this->providers = array_map(fn ($v) => new \Symfony\Config\Security\ProviderConfig($v), $value['providers']); + unset($value['providers']); + } + + if (array_key_exists('firewalls', $value)) { + $this->_usedProperties['firewalls'] = true; + $this->firewalls = array_map(fn ($v) => new \Symfony\Config\Security\FirewallConfig($v), $value['firewalls']); + unset($value['firewalls']); + } + + if (array_key_exists('access_control', $value)) { + $this->_usedProperties['accessControl'] = true; + $this->accessControl = array_map(fn ($v) => new \Symfony\Config\Security\AccessControlConfig($v), $value['access_control']); + unset($value['access_control']); + } + + if (array_key_exists('role_hierarchy', $value)) { + $this->_usedProperties['roleHierarchy'] = true; + $this->roleHierarchy = $value['role_hierarchy']; + unset($value['role_hierarchy']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['accessDeniedUrl'])) { + $output['access_denied_url'] = $this->accessDeniedUrl; + } + if (isset($this->_usedProperties['sessionFixationStrategy'])) { + $output['session_fixation_strategy'] = $this->sessionFixationStrategy; + } + if (isset($this->_usedProperties['hideUserNotFound'])) { + $output['hide_user_not_found'] = $this->hideUserNotFound; + } + if (isset($this->_usedProperties['eraseCredentials'])) { + $output['erase_credentials'] = $this->eraseCredentials; + } + if (isset($this->_usedProperties['accessDecisionManager'])) { + $output['access_decision_manager'] = $this->accessDecisionManager->toArray(); + } + if (isset($this->_usedProperties['passwordHashers'])) { + $output['password_hashers'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Security\PasswordHasherConfig ? $v->toArray() : $v, $this->passwordHashers); + } + if (isset($this->_usedProperties['providers'])) { + $output['providers'] = array_map(fn ($v) => $v->toArray(), $this->providers); + } + if (isset($this->_usedProperties['firewalls'])) { + $output['firewalls'] = array_map(fn ($v) => $v->toArray(), $this->firewalls); + } + if (isset($this->_usedProperties['accessControl'])) { + $output['access_control'] = array_map(fn ($v) => $v->toArray(), $this->accessControl); + } + if (isset($this->_usedProperties['roleHierarchy'])) { + $output['role_hierarchy'] = $this->roleHierarchy; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Twig/DateConfig.php b/var/cache/dev/Symfony/Config/Twig/DateConfig.php new file mode 100644 index 0000000..8559522 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Twig/DateConfig.php @@ -0,0 +1,99 @@ +_usedProperties['format'] = true; + $this->format = $value; + + return $this; + } + + /** + * @default '%d days' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function intervalFormat($value): static + { + $this->_usedProperties['intervalFormat'] = true; + $this->intervalFormat = $value; + + return $this; + } + + /** + * The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function timezone($value): static + { + $this->_usedProperties['timezone'] = true; + $this->timezone = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('format', $value)) { + $this->_usedProperties['format'] = true; + $this->format = $value['format']; + unset($value['format']); + } + + if (array_key_exists('interval_format', $value)) { + $this->_usedProperties['intervalFormat'] = true; + $this->intervalFormat = $value['interval_format']; + unset($value['interval_format']); + } + + if (array_key_exists('timezone', $value)) { + $this->_usedProperties['timezone'] = true; + $this->timezone = $value['timezone']; + unset($value['timezone']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['format'])) { + $output['format'] = $this->format; + } + if (isset($this->_usedProperties['intervalFormat'])) { + $output['interval_format'] = $this->intervalFormat; + } + if (isset($this->_usedProperties['timezone'])) { + $output['timezone'] = $this->timezone; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Twig/GlobalConfig.php b/var/cache/dev/Symfony/Config/Twig/GlobalConfig.php new file mode 100644 index 0000000..fbf9a1c --- /dev/null +++ b/var/cache/dev/Symfony/Config/Twig/GlobalConfig.php @@ -0,0 +1,99 @@ +_usedProperties['id'] = true; + $this->id = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function type($value): static + { + $this->_usedProperties['type'] = true; + $this->type = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * + * @return $this + */ + public function value(mixed $value): static + { + $this->_usedProperties['value'] = true; + $this->value = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('id', $value)) { + $this->_usedProperties['id'] = true; + $this->id = $value['id']; + unset($value['id']); + } + + if (array_key_exists('type', $value)) { + $this->_usedProperties['type'] = true; + $this->type = $value['type']; + unset($value['type']); + } + + if (array_key_exists('value', $value)) { + $this->_usedProperties['value'] = true; + $this->value = $value['value']; + unset($value['value']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['id'])) { + $output['id'] = $this->id; + } + if (isset($this->_usedProperties['type'])) { + $output['type'] = $this->type; + } + if (isset($this->_usedProperties['value'])) { + $output['value'] = $this->value; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Twig/MailerConfig.php b/var/cache/dev/Symfony/Config/Twig/MailerConfig.php new file mode 100644 index 0000000..734223e --- /dev/null +++ b/var/cache/dev/Symfony/Config/Twig/MailerConfig.php @@ -0,0 +1,53 @@ +_usedProperties['htmlToTextConverter'] = true; + $this->htmlToTextConverter = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('html_to_text_converter', $value)) { + $this->_usedProperties['htmlToTextConverter'] = true; + $this->htmlToTextConverter = $value['html_to_text_converter']; + unset($value['html_to_text_converter']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['htmlToTextConverter'])) { + $output['html_to_text_converter'] = $this->htmlToTextConverter; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/Twig/NumberFormatConfig.php b/var/cache/dev/Symfony/Config/Twig/NumberFormatConfig.php new file mode 100644 index 0000000..6577021 --- /dev/null +++ b/var/cache/dev/Symfony/Config/Twig/NumberFormatConfig.php @@ -0,0 +1,98 @@ +_usedProperties['decimals'] = true; + $this->decimals = $value; + + return $this; + } + + /** + * @default '.' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function decimalPoint($value): static + { + $this->_usedProperties['decimalPoint'] = true; + $this->decimalPoint = $value; + + return $this; + } + + /** + * @default ',' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function thousandsSeparator($value): static + { + $this->_usedProperties['thousandsSeparator'] = true; + $this->thousandsSeparator = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('decimals', $value)) { + $this->_usedProperties['decimals'] = true; + $this->decimals = $value['decimals']; + unset($value['decimals']); + } + + if (array_key_exists('decimal_point', $value)) { + $this->_usedProperties['decimalPoint'] = true; + $this->decimalPoint = $value['decimal_point']; + unset($value['decimal_point']); + } + + if (array_key_exists('thousands_separator', $value)) { + $this->_usedProperties['thousandsSeparator'] = true; + $this->thousandsSeparator = $value['thousands_separator']; + unset($value['thousands_separator']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['decimals'])) { + $output['decimals'] = $this->decimals; + } + if (isset($this->_usedProperties['decimalPoint'])) { + $output['decimal_point'] = $this->decimalPoint; + } + if (isset($this->_usedProperties['thousandsSeparator'])) { + $output['thousands_separator'] = $this->thousandsSeparator; + } + + return $output; + } + +} diff --git a/var/cache/dev/Symfony/Config/TwigConfig.php b/var/cache/dev/Symfony/Config/TwigConfig.php new file mode 100644 index 0000000..6127c0d --- /dev/null +++ b/var/cache/dev/Symfony/Config/TwigConfig.php @@ -0,0 +1,450 @@ + $value + * + * @return $this + */ + public function formThemes(ParamConfigurator|array $value): static + { + $this->_usedProperties['formThemes'] = true; + $this->formThemes = $value; + + return $this; + } + + /** + * @template TValue + * @param TValue $value + * @example "@bar" + * @example 3.14 + * @return \Symfony\Config\Twig\GlobalConfig|$this + * @psalm-return (TValue is array ? \Symfony\Config\Twig\GlobalConfig : static) + */ + public function global(string $key, mixed $value = []): \Symfony\Config\Twig\GlobalConfig|static + { + if (!\is_array($value)) { + $this->_usedProperties['globals'] = true; + $this->globals[$key] = $value; + + return $this; + } + + if (!isset($this->globals[$key]) || !$this->globals[$key] instanceof \Symfony\Config\Twig\GlobalConfig) { + $this->_usedProperties['globals'] = true; + $this->globals[$key] = new \Symfony\Config\Twig\GlobalConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "global()" has already been initialized. You cannot pass values the second time you call global().'); + } + + return $this->globals[$key]; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function autoescapeService($value): static + { + $this->_usedProperties['autoescapeService'] = true; + $this->autoescapeService = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function autoescapeServiceMethod($value): static + { + $this->_usedProperties['autoescapeServiceMethod'] = true; + $this->autoescapeServiceMethod = $value; + + return $this; + } + + /** + * @example Twig\Template + * @default null + * @param ParamConfigurator|mixed $value + * @deprecated The child node "base_template_class" at path "twig" is deprecated. + * @return $this + */ + public function baseTemplateClass($value): static + { + $this->_usedProperties['baseTemplateClass'] = true; + $this->baseTemplateClass = $value; + + return $this; + } + + /** + * @default '%kernel.cache_dir%/twig' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function cache($value): static + { + $this->_usedProperties['cache'] = true; + $this->cache = $value; + + return $this; + } + + /** + * @default '%kernel.charset%' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function charset($value): static + { + $this->_usedProperties['charset'] = true; + $this->charset = $value; + + return $this; + } + + /** + * @default '%kernel.debug%' + * @param ParamConfigurator|bool $value + * @return $this + */ + public function debug($value): static + { + $this->_usedProperties['debug'] = true; + $this->debug = $value; + + return $this; + } + + /** + * @default '%kernel.debug%' + * @param ParamConfigurator|bool $value + * @return $this + */ + public function strictVariables($value): static + { + $this->_usedProperties['strictVariables'] = true; + $this->strictVariables = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function autoReload($value): static + { + $this->_usedProperties['autoReload'] = true; + $this->autoReload = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|int $value + * @return $this + */ + public function optimizations($value): static + { + $this->_usedProperties['optimizations'] = true; + $this->optimizations = $value; + + return $this; + } + + /** + * The default path used to load templates + * @default '%kernel.project_dir%/templates' + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function defaultPath($value): static + { + $this->_usedProperties['defaultPath'] = true; + $this->defaultPath = $value; + + return $this; + } + + /** + * @param ParamConfigurator|list|string $value + * + * @return $this + */ + public function fileNamePattern(ParamConfigurator|string|array $value): static + { + $this->_usedProperties['fileNamePattern'] = true; + $this->fileNamePattern = $value; + + return $this; + } + + /** + * @return $this + */ + public function path(string $paths, mixed $value): static + { + $this->_usedProperties['paths'] = true; + $this->paths[$paths] = $value; + + return $this; + } + + /** + * The default format options used by the date filter + * @default {"format":"F j, Y H:i","interval_format":"%d days","timezone":null} + */ + public function date(array $value = []): \Symfony\Config\Twig\DateConfig + { + if (null === $this->date) { + $this->_usedProperties['date'] = true; + $this->date = new \Symfony\Config\Twig\DateConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "date()" has already been initialized. You cannot pass values the second time you call date().'); + } + + return $this->date; + } + + /** + * The default format options for the number_format filter + * @default {"decimals":0,"decimal_point":".","thousands_separator":","} + */ + public function numberFormat(array $value = []): \Symfony\Config\Twig\NumberFormatConfig + { + if (null === $this->numberFormat) { + $this->_usedProperties['numberFormat'] = true; + $this->numberFormat = new \Symfony\Config\Twig\NumberFormatConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "numberFormat()" has already been initialized. You cannot pass values the second time you call numberFormat().'); + } + + return $this->numberFormat; + } + + public function mailer(array $value = []): \Symfony\Config\Twig\MailerConfig + { + if (null === $this->mailer) { + $this->_usedProperties['mailer'] = true; + $this->mailer = new \Symfony\Config\Twig\MailerConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "mailer()" has already been initialized. You cannot pass values the second time you call mailer().'); + } + + return $this->mailer; + } + + public function getExtensionAlias(): string + { + return 'twig'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('form_themes', $value)) { + $this->_usedProperties['formThemes'] = true; + $this->formThemes = $value['form_themes']; + unset($value['form_themes']); + } + + if (array_key_exists('globals', $value)) { + $this->_usedProperties['globals'] = true; + $this->globals = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\Twig\GlobalConfig($v) : $v, $value['globals']); + unset($value['globals']); + } + + if (array_key_exists('autoescape_service', $value)) { + $this->_usedProperties['autoescapeService'] = true; + $this->autoescapeService = $value['autoescape_service']; + unset($value['autoescape_service']); + } + + if (array_key_exists('autoescape_service_method', $value)) { + $this->_usedProperties['autoescapeServiceMethod'] = true; + $this->autoescapeServiceMethod = $value['autoescape_service_method']; + unset($value['autoescape_service_method']); + } + + if (array_key_exists('base_template_class', $value)) { + $this->_usedProperties['baseTemplateClass'] = true; + $this->baseTemplateClass = $value['base_template_class']; + unset($value['base_template_class']); + } + + if (array_key_exists('cache', $value)) { + $this->_usedProperties['cache'] = true; + $this->cache = $value['cache']; + unset($value['cache']); + } + + if (array_key_exists('charset', $value)) { + $this->_usedProperties['charset'] = true; + $this->charset = $value['charset']; + unset($value['charset']); + } + + if (array_key_exists('debug', $value)) { + $this->_usedProperties['debug'] = true; + $this->debug = $value['debug']; + unset($value['debug']); + } + + if (array_key_exists('strict_variables', $value)) { + $this->_usedProperties['strictVariables'] = true; + $this->strictVariables = $value['strict_variables']; + unset($value['strict_variables']); + } + + if (array_key_exists('auto_reload', $value)) { + $this->_usedProperties['autoReload'] = true; + $this->autoReload = $value['auto_reload']; + unset($value['auto_reload']); + } + + if (array_key_exists('optimizations', $value)) { + $this->_usedProperties['optimizations'] = true; + $this->optimizations = $value['optimizations']; + unset($value['optimizations']); + } + + if (array_key_exists('default_path', $value)) { + $this->_usedProperties['defaultPath'] = true; + $this->defaultPath = $value['default_path']; + unset($value['default_path']); + } + + if (array_key_exists('file_name_pattern', $value)) { + $this->_usedProperties['fileNamePattern'] = true; + $this->fileNamePattern = $value['file_name_pattern']; + unset($value['file_name_pattern']); + } + + if (array_key_exists('paths', $value)) { + $this->_usedProperties['paths'] = true; + $this->paths = $value['paths']; + unset($value['paths']); + } + + if (array_key_exists('date', $value)) { + $this->_usedProperties['date'] = true; + $this->date = new \Symfony\Config\Twig\DateConfig($value['date']); + unset($value['date']); + } + + if (array_key_exists('number_format', $value)) { + $this->_usedProperties['numberFormat'] = true; + $this->numberFormat = new \Symfony\Config\Twig\NumberFormatConfig($value['number_format']); + unset($value['number_format']); + } + + if (array_key_exists('mailer', $value)) { + $this->_usedProperties['mailer'] = true; + $this->mailer = new \Symfony\Config\Twig\MailerConfig($value['mailer']); + unset($value['mailer']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['formThemes'])) { + $output['form_themes'] = $this->formThemes; + } + if (isset($this->_usedProperties['globals'])) { + $output['globals'] = array_map(fn ($v) => $v instanceof \Symfony\Config\Twig\GlobalConfig ? $v->toArray() : $v, $this->globals); + } + if (isset($this->_usedProperties['autoescapeService'])) { + $output['autoescape_service'] = $this->autoescapeService; + } + if (isset($this->_usedProperties['autoescapeServiceMethod'])) { + $output['autoescape_service_method'] = $this->autoescapeServiceMethod; + } + if (isset($this->_usedProperties['baseTemplateClass'])) { + $output['base_template_class'] = $this->baseTemplateClass; + } + if (isset($this->_usedProperties['cache'])) { + $output['cache'] = $this->cache; + } + if (isset($this->_usedProperties['charset'])) { + $output['charset'] = $this->charset; + } + if (isset($this->_usedProperties['debug'])) { + $output['debug'] = $this->debug; + } + if (isset($this->_usedProperties['strictVariables'])) { + $output['strict_variables'] = $this->strictVariables; + } + if (isset($this->_usedProperties['autoReload'])) { + $output['auto_reload'] = $this->autoReload; + } + if (isset($this->_usedProperties['optimizations'])) { + $output['optimizations'] = $this->optimizations; + } + if (isset($this->_usedProperties['defaultPath'])) { + $output['default_path'] = $this->defaultPath; + } + if (isset($this->_usedProperties['fileNamePattern'])) { + $output['file_name_pattern'] = $this->fileNamePattern; + } + if (isset($this->_usedProperties['paths'])) { + $output['paths'] = $this->paths; + } + if (isset($this->_usedProperties['date'])) { + $output['date'] = $this->date->toArray(); + } + if (isset($this->_usedProperties['numberFormat'])) { + $output['number_format'] = $this->numberFormat->toArray(); + } + if (isset($this->_usedProperties['mailer'])) { + $output['mailer'] = $this->mailer->toArray(); + } + + return $output; + } + +} diff --git a/var/cache/dev/twig/00/0012721bfa7ee82eeda552c26d3c49be.php b/var/cache/dev/twig/00/0012721bfa7ee82eeda552c26d3c49be.php new file mode 100644 index 0000000..14f1394 --- /dev/null +++ b/var/cache/dev/twig/00/0012721bfa7ee82eeda552c26d3c49be.php @@ -0,0 +1,185 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@Doctrine/Collector/explain.html.twig")); + + // line 1 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, (isset($context["data"]) || array_key_exists("data", $context) ? $context["data"] : (function () { throw new RuntimeError('Variable "data" does not exist.', 1, $this->source); })()), 0, [], "array", false, false, false, 1)) > 1)) { + // line 2 + yield " "; + // line 3 + yield " + + + "; + // line 6 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable(Twig\Extension\CoreExtension::keys(CoreExtension::getAttribute($this->env, $this->source, (isset($context["data"]) || array_key_exists("data", $context) ? $context["data"] : (function () { throw new RuntimeError('Variable "data" does not exist.', 6, $this->source); })()), 0, [], "array", false, false, false, 6))); + foreach ($context['_seq'] as $context["_key"] => $context["label"]) { + // line 7 + yield " + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['label'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 9 + yield " + + + "; + // line 12 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["data"]) || array_key_exists("data", $context) ? $context["data"] : (function () { throw new RuntimeError('Variable "data" does not exist.', 12, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["row"]) { + // line 13 + yield " + "; + // line 14 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable($context["row"]); + foreach ($context['_seq'] as $context["key"] => $context["item"]) { + // line 15 + yield " + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['key'], $context['item'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 17 + yield " + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['row'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 19 + yield " +
"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["label"], "html", null, true); + yield "
"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::replace($context["item"], ["," => ", "]), "html", null, true); + yield "
+"; + } else { + // line 22 + yield " "; + // line 23 + yield "
";
+            // line 24
+            $context['_parent'] = $context;
+            $context['_seq'] = CoreExtension::ensureTraversable((isset($context["data"]) || array_key_exists("data", $context) ? $context["data"] : (function () { throw new RuntimeError('Variable "data" does not exist.', 24, $this->source); })()));
+            foreach ($context['_seq'] as $context["_key"] => $context["row"]) {
+                // line 25
+                yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::first($this->env->getCharset(), $context["row"]), "html", null, true);
+                yield "
+";
+            }
+            $_parent = $context['_parent'];
+            unset($context['_seq'], $context['_key'], $context['row'], $context['_parent']);
+            $context = array_intersect_key($context, $_parent) + $_parent;
+            // line 27
+            yield "
+"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@Doctrine/Collector/explain.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 120 => 27, 112 => 25, 108 => 24, 106 => 23, 104 => 22, 99 => 19, 92 => 17, 83 => 15, 79 => 14, 76 => 13, 72 => 12, 67 => 9, 58 => 7, 54 => 6, 49 => 3, 47 => 2, 45 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% if data[0]|length > 1 %} + {# The platform returns a table for the explanation (e.g. MySQL), display all columns #} + + + + {% for label in data[0]|keys %} + + {% endfor %} + + + + {% for row in data %} + + {% for key, item in row %} + + {% endfor %} + + {% endfor %} + +
{{ label }}
{{ item|replace({',': ', '}) }}
+{% else %} + {# The Platform returns a single column for a textual explanation (e.g. PostgreSQL), display all lines #} +
+        {%- for row in data -%}
+            {{ row|first }}{{ \"\\n\" }}
+        {%- endfor -%}
+    
+{% endif %} +", "@Doctrine/Collector/explain.html.twig", "/home/skylar/Projects/mycomments-api/vendor/doctrine/doctrine-bundle/templates/Collector/explain.html.twig"); + } +} diff --git a/var/cache/dev/twig/04/044aa988150b14296583c13f3782cac0.php b/var/cache/dev/twig/04/044aa988150b14296583c13f3782cac0.php new file mode 100644 index 0000000..bf262e1 --- /dev/null +++ b/var/cache/dev/twig/04/044aa988150b14296583c13f3782cac0.php @@ -0,0 +1,1118 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("bootstrap_base_layout.html.twig", "bootstrap_3_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."bootstrap_base_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'form_widget_simple' => [$this, 'block_form_widget_simple'], + 'button_widget' => [$this, 'block_button_widget'], + 'money_widget' => [$this, 'block_money_widget'], + 'checkbox_widget' => [$this, 'block_checkbox_widget'], + 'radio_widget' => [$this, 'block_radio_widget'], + 'choice_widget_collapsed' => [$this, 'block_choice_widget_collapsed'], + 'form_label' => [$this, 'block_form_label'], + 'choice_label' => [$this, 'block_choice_label'], + 'checkbox_label' => [$this, 'block_checkbox_label'], + 'radio_label' => [$this, 'block_radio_label'], + 'checkbox_radio_label' => [$this, 'block_checkbox_radio_label'], + 'form_row' => [$this, 'block_form_row'], + 'button_row' => [$this, 'block_button_row'], + 'choice_row' => [$this, 'block_choice_row'], + 'date_row' => [$this, 'block_date_row'], + 'time_row' => [$this, 'block_time_row'], + 'datetime_row' => [$this, 'block_datetime_row'], + 'checkbox_row' => [$this, 'block_checkbox_row'], + 'radio_row' => [$this, 'block_radio_row'], + 'form_errors' => [$this, 'block_form_errors'], + 'form_help' => [$this, 'block_form_help'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "bootstrap_3_layout.html.twig")); + + // line 2 + yield " +"; + // line 4 + yield " +"; + // line 5 + yield from $this->unwrap()->yieldBlock('form_widget_simple', $context, $blocks); + // line 11 + yield " +"; + // line 12 + yield from $this->unwrap()->yieldBlock('button_widget', $context, $blocks); + // line 16 + yield " +"; + // line 17 + yield from $this->unwrap()->yieldBlock('money_widget', $context, $blocks); + // line 34 + yield " +"; + // line 35 + yield from $this->unwrap()->yieldBlock('checkbox_widget', $context, $blocks); + // line 45 + yield " +"; + // line 46 + yield from $this->unwrap()->yieldBlock('radio_widget', $context, $blocks); + // line 56 + yield " +"; + // line 57 + yield from $this->unwrap()->yieldBlock('choice_widget_collapsed', $context, $blocks); + // line 61 + yield " +"; + // line 63 + yield " +"; + // line 64 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 68 + yield " +"; + // line 69 + yield from $this->unwrap()->yieldBlock('choice_label', $context, $blocks); + // line 74 + yield " +"; + // line 75 + yield from $this->unwrap()->yieldBlock('checkbox_label', $context, $blocks); + // line 80 + yield " +"; + // line 81 + yield from $this->unwrap()->yieldBlock('radio_label', $context, $blocks); + // line 86 + yield " +"; + // line 87 + yield from $this->unwrap()->yieldBlock('checkbox_radio_label', $context, $blocks); + // line 127 + yield " +"; + // line 129 + yield " +"; + // line 130 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 142 + yield " +"; + // line 143 + yield from $this->unwrap()->yieldBlock('button_row', $context, $blocks); + // line 148 + yield " +"; + // line 149 + yield from $this->unwrap()->yieldBlock('choice_row', $context, $blocks); + // line 153 + yield " +"; + // line 154 + yield from $this->unwrap()->yieldBlock('date_row', $context, $blocks); + // line 158 + yield " +"; + // line 159 + yield from $this->unwrap()->yieldBlock('time_row', $context, $blocks); + // line 163 + yield " +"; + // line 164 + yield from $this->unwrap()->yieldBlock('datetime_row', $context, $blocks); + // line 168 + yield " +"; + // line 169 + yield from $this->unwrap()->yieldBlock('checkbox_row', $context, $blocks); + // line 176 + yield " +"; + // line 177 + yield from $this->unwrap()->yieldBlock('radio_row', $context, $blocks); + // line 184 + yield " +"; + // line 186 + yield " +"; + // line 187 + yield from $this->unwrap()->yieldBlock('form_errors', $context, $blocks); + // line 198 + yield " +"; + // line 200 + yield " +"; + // line 201 + yield from $this->unwrap()->yieldBlock('form_help', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_form_widget_simple(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget_simple")); + + // line 6 + if (( !array_key_exists("type", $context) || !CoreExtension::inFilter((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 6, $this->source); })()), ["file", "hidden"]))) { + // line 7 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 7, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 7)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 7), "")) : ("")) . " form-control"))]); + } + // line 9 + yield from $this->yieldParentBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 12 + /** + * @return iterable + */ + public function block_button_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_widget")); + + // line 13 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 13, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 13)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 13), "btn-default")) : ("btn-default")) . " btn"))]); + // line 14 + yield from $this->yieldParentBlock("button_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 17 + /** + * @return iterable + */ + public function block_money_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "money_widget")); + + // line 18 + $context["prepend"] = !(is_string($__internal_compile_0 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 18, $this->source); })())) && is_string($__internal_compile_1 = "{{") && str_starts_with($__internal_compile_0, $__internal_compile_1)); + // line 19 + yield " "; + $context["append"] = !(is_string($__internal_compile_2 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 19, $this->source); })())) && is_string($__internal_compile_3 = "}}") && str_ends_with($__internal_compile_2, $__internal_compile_3)); + // line 20 + yield " "; + if (((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 20, $this->source); })()) || (isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 20, $this->source); })()))) { + // line 21 + yield "
+ "; + // line 22 + if ((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 22, $this->source); })())) { + // line 23 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 23, $this->source); })())); + yield " + "; + } + // line 25 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 26 + if ((isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 26, $this->source); })())) { + // line 27 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 27, $this->source); })())); + yield " + "; + } + // line 29 + yield "
+ "; + } else { + // line 31 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 35 + /** + * @return iterable + */ + public function block_checkbox_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_widget")); + + // line 36 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 36, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 36)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 36), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 36)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 36), "")) : ("")))); + // line 37 + if (CoreExtension::inFilter("checkbox-inline", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 37, $this->source); })()))) { + // line 38 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 38, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + } else { + // line 40 + yield "
"; + // line 41 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 41, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + // line 42 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 46 + /** + * @return iterable + */ + public function block_radio_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_widget")); + + // line 47 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 47, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 47)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 47), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 47)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 47), "")) : ("")))); + // line 48 + if (CoreExtension::inFilter("radio-inline", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 48, $this->source); })()))) { + // line 49 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 49, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("radio_widget", $context, $blocks)]); + } else { + // line 51 + yield "
"; + // line 52 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 52, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("radio_widget", $context, $blocks)]); + // line 53 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 57 + /** + * @return iterable + */ + public function block_choice_widget_collapsed(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_collapsed")); + + // line 58 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 58, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 58)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 58), "")) : ("")) . " form-control"))]); + // line 59 + yield from $this->yieldParentBlock("choice_widget_collapsed", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 64 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 65 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 65, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 65)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 65), "")) : ("")) . " control-label"))]); + // line 66 + yield from $this->yieldParentBlock("form_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 69 + /** + * @return iterable + */ + public function block_choice_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_label")); + + // line 71 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 71, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(Twig\Extension\CoreExtension::replace(((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 71)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 71), "")) : ("")), ["checkbox-inline" => "", "radio-inline" => ""]))]); + // line 72 + yield from $this->unwrap()->yieldBlock("form_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 75 + /** + * @return iterable + */ + public function block_checkbox_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_label")); + + // line 76 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 76, $this->source); })()), ["for" => (isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 76, $this->source); })())]); + // line 78 + yield from $this->unwrap()->yieldBlock("checkbox_radio_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 81 + /** + * @return iterable + */ + public function block_radio_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_label")); + + // line 82 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 82, $this->source); })()), ["for" => (isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 82, $this->source); })())]); + // line 84 + yield from $this->unwrap()->yieldBlock("checkbox_radio_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 87 + /** + * @return iterable + */ + public function block_checkbox_radio_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_radio_label")); + + // line 89 + if (array_key_exists("widget", $context)) { + // line 90 + if ((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 90, $this->source); })())) { + // line 91 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 91, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 91)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 91), "")) : ("")) . " required"))]); + } + // line 93 + if (array_key_exists("parent_label_class", $context)) { + // line 94 + $context["embed_label_classes"] = Twig\Extension\CoreExtension::filter($this->env, Twig\Extension\CoreExtension::split($this->env->getCharset(), (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 94, $this->source); })()), " "), function ($__class__) use ($context, $macros) { $context["class"] = $__class__; return CoreExtension::inFilter((isset($context["class"]) || array_key_exists("class", $context) ? $context["class"] : (function () { throw new RuntimeError('Variable "class" does not exist.', 94, $this->source); })()), ["checkbox-inline", "radio-inline"]); }); + // line 95 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 95, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 95)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 95), "")) : ("")) . " ") . Twig\Extension\CoreExtension::join((isset($context["embed_label_classes"]) || array_key_exists("embed_label_classes", $context) ? $context["embed_label_classes"] : (function () { throw new RuntimeError('Variable "embed_label_classes" does not exist.', 95, $this->source); })()), " ")))]); + } + // line 97 + if (( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 97, $this->source); })()) === false) && Twig\Extension\CoreExtension::testEmpty((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 97, $this->source); })())))) { + // line 98 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 98, $this->source); })()))) { + // line 99 + $context["label"] = Twig\Extension\CoreExtension::replace((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 99, $this->source); })()), ["%name%" => // line 100 +(isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 100, $this->source); })()), "%id%" => // line 101 +(isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 101, $this->source); })())]); + } else { + // line 104 + $context["label"] = $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->humanize((isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 104, $this->source); })())); + } + } + // line 107 + yield " (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 107, $this->source); })())]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 107, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield ">"; + // line 109 + yield (isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 109, $this->source); })()); + yield " "; + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 109, $this->source); })()) === false)) { + // line 110 + if (((isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 110, $this->source); })()) === false)) { + // line 111 + if (((isset($context["label_html"]) || array_key_exists("label_html", $context) ? $context["label_html"] : (function () { throw new RuntimeError('Variable "label_html" does not exist.', 111, $this->source); })()) === false)) { + // line 112 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 112, $this->source); })()), "html", null, true); + } else { + // line 114 + yield (isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 114, $this->source); })()); + } + } else { + // line 117 + if (((isset($context["label_html"]) || array_key_exists("label_html", $context) ? $context["label_html"] : (function () { throw new RuntimeError('Variable "label_html" does not exist.', 117, $this->source); })()) === false)) { + // line 118 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 118, $this->source); })()), (isset($context["label_translation_parameters"]) || array_key_exists("label_translation_parameters", $context) ? $context["label_translation_parameters"] : (function () { throw new RuntimeError('Variable "label_translation_parameters" does not exist.', 118, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 118, $this->source); })())), "html", null, true); + } else { + // line 120 + yield $this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 120, $this->source); })()), (isset($context["label_translation_parameters"]) || array_key_exists("label_translation_parameters", $context) ? $context["label_translation_parameters"] : (function () { throw new RuntimeError('Variable "label_translation_parameters" does not exist.', 120, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 120, $this->source); })())); + } + } + } + // line 124 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 130 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 131 + $context["widget_attr"] = []; + // line 132 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 132, $this->source); })()))) { + // line 133 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 133, $this->source); })()) . "_help")]]; + } + // line 135 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 135, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 135)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 135), "")) : ("")) . " form-group") . (((( !(isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 135, $this->source); })()) || ((array_key_exists("force_error", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["force_error"]) || array_key_exists("force_error", $context) ? $context["force_error"] : (function () { throw new RuntimeError('Variable "force_error" does not exist.', 135, $this->source); })()), false)) : (false))) && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 135, $this->source); })()))) ? (" has-error") : (""))))])]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 135, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + yield ">"; + // line 136 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 136, $this->source); })()), 'label'); + yield " "; + // line 137 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 137, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 137, $this->source); })())); + yield " "; + // line 138 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 138, $this->source); })()), 'help'); + // line 139 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 139, $this->source); })()), 'errors'); + yield " "; + // line 140 + yield " "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 143 + /** + * @return iterable + */ + public function block_button_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_row")); + + // line 144 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 144, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 144)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 144), "")) : ("")) . " form-group"))])]; + if (!is_iterable($__internal_compile_9)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 144, $this->getSourceContext()); + } + $__internal_compile_9 = CoreExtension::toArray($__internal_compile_9); + $context = $__internal_compile_9 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_8; + yield ">"; + // line 145 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 145, $this->source); })()), 'widget'); + // line 146 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 149 + /** + * @return iterable + */ + public function block_choice_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_row")); + + // line 150 + $context["force_error"] = true; + // line 151 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 154 + /** + * @return iterable + */ + public function block_date_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_row")); + + // line 155 + $context["force_error"] = true; + // line 156 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 159 + /** + * @return iterable + */ + public function block_time_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_row")); + + // line 160 + $context["force_error"] = true; + // line 161 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 164 + /** + * @return iterable + */ + public function block_datetime_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_row")); + + // line 165 + $context["force_error"] = true; + // line 166 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 169 + /** + * @return iterable + */ + public function block_checkbox_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_row")); + + // line 170 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 170, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 170)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 170), "")) : ("")) . " form-group") . (( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 170, $this->source); })())) ? (" has-error") : (""))))])]; + if (!is_iterable($__internal_compile_11)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 170, $this->getSourceContext()); + } + $__internal_compile_11 = CoreExtension::toArray($__internal_compile_11); + $context = $__internal_compile_11 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_10; + yield ">"; + // line 171 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 171, $this->source); })()), 'widget'); + // line 172 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 172, $this->source); })()), 'help'); + // line 173 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 173, $this->source); })()), 'errors'); + // line 174 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 177 + /** + * @return iterable + */ + public function block_radio_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_row")); + + // line 178 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 178, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 178)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 178), "")) : ("")) . " form-group") . (( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 178, $this->source); })())) ? (" has-error") : (""))))])]; + if (!is_iterable($__internal_compile_13)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 178, $this->getSourceContext()); + } + $__internal_compile_13 = CoreExtension::toArray($__internal_compile_13); + $context = $__internal_compile_13 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_12; + yield ">"; + // line 179 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 179, $this->source); })()), 'widget'); + // line 180 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 180, $this->source); })()), 'help'); + // line 181 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 181, $this->source); })()), 'errors'); + // line 182 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 187 + /** + * @return iterable + */ + public function block_form_errors(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_errors")); + + // line 188 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 188, $this->source); })())) > 0)) { + // line 189 + if ( !Symfony\Bridge\Twig\Extension\twig_is_root_form((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 189, $this->source); })()))) { + yield ""; + } else { + yield "
"; + } + // line 190 + yield "
    "; + // line 191 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 191, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 192 + yield "
  • "; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["error"], "message", [], "any", false, false, false, 192), "html", null, true); + yield "
  • "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['error'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 194 + yield "
+ "; + // line 195 + if ( !Symfony\Bridge\Twig\Extension\twig_is_root_form((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 195, $this->source); })()))) { + yield ""; + } else { + yield "
"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 201 + /** + * @return iterable + */ + public function block_form_help(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_help")); + + // line 202 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 202, $this->source); })()))) { + // line 203 + $context["help_attr"] = Twig\Extension\CoreExtension::merge((isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 203, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", true, true, false, 203)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", false, false, false, 203), "")) : ("")) . " help-block"))]); + // line 204 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 204, $this->source); })()), "html", null, true); + yield "_help\""; + $__internal_compile_14 = $context; + $__internal_compile_15 = ["attr" => (isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 204, $this->source); })())]; + if (!is_iterable($__internal_compile_15)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 204, $this->getSourceContext()); + } + $__internal_compile_15 = CoreExtension::toArray($__internal_compile_15); + $context = $__internal_compile_15 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_14; + yield ">"; + // line 205 + if (((isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 205, $this->source); })()) === false)) { + // line 206 + if (((isset($context["help_html"]) || array_key_exists("help_html", $context) ? $context["help_html"] : (function () { throw new RuntimeError('Variable "help_html" does not exist.', 206, $this->source); })()) === false)) { + // line 207 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 207, $this->source); })()), "html", null, true); + } else { + // line 209 + yield (isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 209, $this->source); })()); + } + } else { + // line 212 + if (((isset($context["help_html"]) || array_key_exists("help_html", $context) ? $context["help_html"] : (function () { throw new RuntimeError('Variable "help_html" does not exist.', 212, $this->source); })()) === false)) { + // line 213 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 213, $this->source); })()), (isset($context["help_translation_parameters"]) || array_key_exists("help_translation_parameters", $context) ? $context["help_translation_parameters"] : (function () { throw new RuntimeError('Variable "help_translation_parameters" does not exist.', 213, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 213, $this->source); })())), "html", null, true); + } else { + // line 215 + yield $this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 215, $this->source); })()), (isset($context["help_translation_parameters"]) || array_key_exists("help_translation_parameters", $context) ? $context["help_translation_parameters"] : (function () { throw new RuntimeError('Variable "help_translation_parameters" does not exist.', 215, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 215, $this->source); })())); + } + } + // line 218 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "bootstrap_3_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 870 => 218, 866 => 215, 863 => 213, 861 => 212, 857 => 209, 854 => 207, 852 => 206, 850 => 205, 836 => 204, 834 => 203, 832 => 202, 822 => 201, 809 => 195, 806 => 194, 798 => 192, 794 => 191, 792 => 190, 786 => 189, 784 => 188, 774 => 187, 766 => 182, 764 => 181, 762 => 180, 760 => 179, 748 => 178, 738 => 177, 730 => 174, 728 => 173, 726 => 172, 724 => 171, 712 => 170, 702 => 169, 694 => 166, 692 => 165, 682 => 164, 674 => 161, 672 => 160, 662 => 159, 654 => 156, 652 => 155, 642 => 154, 634 => 151, 632 => 150, 622 => 149, 614 => 146, 612 => 145, 600 => 144, 590 => 143, 582 => 140, 579 => 139, 577 => 138, 574 => 137, 571 => 136, 559 => 135, 556 => 133, 554 => 132, 552 => 131, 542 => 130, 533 => 124, 528 => 120, 525 => 118, 523 => 117, 519 => 114, 516 => 112, 514 => 111, 512 => 110, 508 => 109, 496 => 107, 492 => 104, 489 => 101, 488 => 100, 487 => 99, 485 => 98, 483 => 97, 480 => 95, 478 => 94, 476 => 93, 473 => 91, 471 => 90, 469 => 89, 459 => 87, 451 => 84, 449 => 82, 439 => 81, 431 => 78, 429 => 76, 419 => 75, 411 => 72, 409 => 71, 399 => 69, 391 => 66, 389 => 65, 379 => 64, 371 => 59, 369 => 58, 359 => 57, 350 => 53, 348 => 52, 346 => 51, 343 => 49, 341 => 48, 339 => 47, 329 => 46, 320 => 42, 318 => 41, 316 => 40, 313 => 38, 311 => 37, 309 => 36, 299 => 35, 290 => 31, 286 => 29, 280 => 27, 278 => 26, 276 => 25, 270 => 23, 268 => 22, 265 => 21, 262 => 20, 259 => 19, 257 => 18, 247 => 17, 239 => 14, 237 => 13, 227 => 12, 219 => 9, 216 => 7, 214 => 6, 204 => 5, 196 => 201, 193 => 200, 190 => 198, 188 => 187, 185 => 186, 182 => 184, 180 => 177, 177 => 176, 175 => 169, 172 => 168, 170 => 164, 167 => 163, 165 => 159, 162 => 158, 160 => 154, 157 => 153, 155 => 149, 152 => 148, 150 => 143, 147 => 142, 145 => 130, 142 => 129, 139 => 127, 137 => 87, 134 => 86, 132 => 81, 129 => 80, 127 => 75, 124 => 74, 122 => 69, 119 => 68, 117 => 64, 114 => 63, 111 => 61, 109 => 57, 106 => 56, 104 => 46, 101 => 45, 99 => 35, 96 => 34, 94 => 17, 91 => 16, 89 => 12, 86 => 11, 84 => 5, 81 => 4, 78 => 2, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"bootstrap_base_layout.html.twig\" %} + +{# Widgets #} + +{% block form_widget_simple -%} + {% if type is not defined or type not in ['file', 'hidden'] %} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block button_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) -%} + {{- parent() -}} +{%- endblock button_widget %} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
+ {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
+ {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block checkbox_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {% if 'checkbox-inline' in parent_label_class %} + {{- form_label(form, null, { widget: parent() }) -}} + {% else -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
+ {%- endif -%} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'radio-inline' in parent_label_class -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- else -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
+ {%- endif -%} +{%- endblock radio_widget %} + +{% block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed %} + +{# Labels #} + +{% block form_label -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' control-label')|trim}) -%} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} + {{- block('form_label') -}} +{% endblock %} + +{% block checkbox_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label -%} + {# Do not display the label if widget is not defined in order to prevent double label rendering #} + {%- if widget is defined -%} + {%- if required -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} + {%- endif -%} + {%- if parent_label_class is defined -%} + {% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%} + {% endif %} + {%- if label is not same as(false) and label is empty -%} + {%- if label_format is not empty -%} + {%- set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) -%} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + + {#- if statement must be kept on the same line, to force the space between widget and label -#} + {{- widget|raw }} {% if label is not same as(false) -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{ label -}} + {%- else -%} + {{ label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{ label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{ label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {%- endif -%} + + {%- endif -%} +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + + {{- form_label(form) }} {# -#} + {{ form_widget(form, widget_attr) }} {# -#} + {{- form_help(form) -}} + {{ form_errors(form) }} {# -#} + {# -#} +{%- endblock form_row %} + +{% block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} + + {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} + +{%- endblock checkbox_row %} + +{% block radio_row -%} + + {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} + +{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form is not rootform %}{% else %}
{% endif %} +
    + {%- for error in errors -%} +
  • {{ error.message }}
  • + {%- endfor -%} +
+ {% if form is not rootform %}{% else %}
{% endif %} + {%- endif %} +{%- endblock form_errors %} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-block')|trim}) -%} + + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + + {%- endif -%} +{%- endblock form_help %} +", "bootstrap_3_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/15/15b09f3738b1f2f572677a57110f542f.php b/var/cache/dev/twig/15/15b09f3738b1f2f572677a57110f542f.php new file mode 100644 index 0000000..67318d0 --- /dev/null +++ b/var/cache/dev/twig/15/15b09f3738b1f2f572677a57110f542f.php @@ -0,0 +1,1039 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("form_div_layout.html.twig", "bootstrap_base_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."form_div_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'textarea_widget' => [$this, 'block_textarea_widget'], + 'money_widget' => [$this, 'block_money_widget'], + 'percent_widget' => [$this, 'block_percent_widget'], + 'datetime_widget' => [$this, 'block_datetime_widget'], + 'date_widget' => [$this, 'block_date_widget'], + 'time_widget' => [$this, 'block_time_widget'], + 'dateinterval_widget' => [$this, 'block_dateinterval_widget'], + 'choice_widget_expanded' => [$this, 'block_choice_widget_expanded'], + 'choice_label' => [$this, 'block_choice_label'], + 'checkbox_label' => [$this, 'block_checkbox_label'], + 'radio_label' => [$this, 'block_radio_label'], + 'button_row' => [$this, 'block_button_row'], + 'choice_row' => [$this, 'block_choice_row'], + 'date_row' => [$this, 'block_date_row'], + 'time_row' => [$this, 'block_time_row'], + 'datetime_row' => [$this, 'block_datetime_row'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "bootstrap_base_layout.html.twig")); + + // line 2 + yield " +"; + // line 4 + yield " +"; + // line 5 + yield from $this->unwrap()->yieldBlock('textarea_widget', $context, $blocks); + // line 9 + yield " +"; + // line 10 + yield from $this->unwrap()->yieldBlock('money_widget', $context, $blocks); + // line 27 + yield " +"; + // line 28 + yield from $this->unwrap()->yieldBlock('percent_widget', $context, $blocks); + // line 38 + yield " +"; + // line 39 + yield from $this->unwrap()->yieldBlock('datetime_widget', $context, $blocks); + // line 62 + yield " +"; + // line 63 + yield from $this->unwrap()->yieldBlock('date_widget', $context, $blocks); + // line 89 + yield " +"; + // line 90 + yield from $this->unwrap()->yieldBlock('time_widget', $context, $blocks); + // line 108 + yield from $this->unwrap()->yieldBlock('dateinterval_widget', $context, $blocks); + // line 146 + yield from $this->unwrap()->yieldBlock('choice_widget_expanded', $context, $blocks); + // line 165 + yield " +"; + // line 167 + yield " +"; + // line 168 + yield from $this->unwrap()->yieldBlock('choice_label', $context, $blocks); + // line 173 + yield " +"; + // line 174 + yield from $this->unwrap()->yieldBlock('checkbox_label', $context, $blocks); + // line 177 + yield " +"; + // line 178 + yield from $this->unwrap()->yieldBlock('radio_label', $context, $blocks); + // line 181 + yield " +"; + // line 183 + yield " +"; + // line 184 + yield from $this->unwrap()->yieldBlock('button_row', $context, $blocks); + // line 189 + yield " +"; + // line 190 + yield from $this->unwrap()->yieldBlock('choice_row', $context, $blocks); + // line 194 + yield " +"; + // line 195 + yield from $this->unwrap()->yieldBlock('date_row', $context, $blocks); + // line 199 + yield " +"; + // line 200 + yield from $this->unwrap()->yieldBlock('time_row', $context, $blocks); + // line 204 + yield " +"; + // line 205 + yield from $this->unwrap()->yieldBlock('datetime_row', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_textarea_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "textarea_widget")); + + // line 6 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 6, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 6)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 6), "")) : ("")) . " form-control"))]); + // line 7 + yield from $this->yieldParentBlock("textarea_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 10 + /** + * @return iterable + */ + public function block_money_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "money_widget")); + + // line 11 + $context["prepend"] = !(is_string($__internal_compile_0 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 11, $this->source); })())) && is_string($__internal_compile_1 = "{{") && str_starts_with($__internal_compile_0, $__internal_compile_1)); + // line 12 + yield " "; + $context["append"] = !(is_string($__internal_compile_2 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 12, $this->source); })())) && is_string($__internal_compile_3 = "}}") && str_ends_with($__internal_compile_2, $__internal_compile_3)); + // line 13 + yield " "; + if (((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 13, $this->source); })()) || (isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 13, $this->source); })()))) { + // line 14 + yield "
env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("group_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["group_class"]) || array_key_exists("group_class", $context) ? $context["group_class"] : (function () { throw new RuntimeError('Variable "group_class" does not exist.', 14, $this->source); })()), "")) : ("")), "html", null, true); + yield "\"> + "; + // line 15 + if ((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 15, $this->source); })())) { + // line 16 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 16, $this->source); })())); + yield " + "; + } + // line 18 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 19 + if ((isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 19, $this->source); })())) { + // line 20 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 20, $this->source); })())); + yield " + "; + } + // line 22 + yield "
+ "; + } else { + // line 24 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 28 + /** + * @return iterable + */ + public function block_percent_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "percent_widget")); + + // line 29 + if ((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 29, $this->source); })())) { + // line 30 + yield "
"; + // line 31 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 32 + yield ""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("symbol", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 32, $this->source); })()), "%")) : ("%")), "html", null, true); + yield " +
"; + } else { + // line 35 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 39 + /** + * @return iterable + */ + public function block_datetime_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_widget")); + + // line 40 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 40, $this->source); })()) == "single_text")) { + // line 41 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 43 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 43, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 43)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 43), "")) : ("")) . " form-inline"))]); + // line 44 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 45 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 45, $this->source); })()), "date", [], "any", false, false, false, 45), 'errors'); + // line 46 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 46, $this->source); })()), "time", [], "any", false, false, false, 46), 'errors'); + // line 48 + yield "
"; + // line 49 + if (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, ($context["form"] ?? null), "date", [], "any", false, true, false, 49), "year", [], "any", true, true, false, 49)) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 49, $this->source); })()), "date", [], "any", false, false, false, 49), "year", [], "any", false, false, false, 49), 'label'); + } + // line 50 + if (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, ($context["form"] ?? null), "date", [], "any", false, true, false, 50), "month", [], "any", true, true, false, 50)) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 50, $this->source); })()), "date", [], "any", false, false, false, 50), "month", [], "any", false, false, false, 50), 'label'); + } + // line 51 + if (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, ($context["form"] ?? null), "date", [], "any", false, true, false, 51), "day", [], "any", true, true, false, 51)) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 51, $this->source); })()), "date", [], "any", false, false, false, 51), "day", [], "any", false, false, false, 51), 'label'); + } + // line 52 + if (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, ($context["form"] ?? null), "time", [], "any", false, true, false, 52), "hour", [], "any", true, true, false, 52)) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 52, $this->source); })()), "time", [], "any", false, false, false, 52), "hour", [], "any", false, false, false, 52), 'label'); + } + // line 53 + if (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, ($context["form"] ?? null), "time", [], "any", false, true, false, 53), "minute", [], "any", true, true, false, 53)) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 53, $this->source); })()), "time", [], "any", false, false, false, 53), "minute", [], "any", false, false, false, 53), 'label'); + } + // line 54 + if (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, ($context["form"] ?? null), "time", [], "any", false, true, false, 54), "second", [], "any", true, true, false, 54)) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 54, $this->source); })()), "time", [], "any", false, false, false, 54), "second", [], "any", false, false, false, 54), 'label'); + } + // line 55 + yield "
"; + // line 57 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 57, $this->source); })()), "date", [], "any", false, false, false, 57), 'widget', ["datetime" => true]); + // line 58 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 58, $this->source); })()), "time", [], "any", false, false, false, 58), 'widget', ["datetime" => true]); + // line 59 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 63 + /** + * @return iterable + */ + public function block_date_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_widget")); + + // line 64 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 64, $this->source); })()) == "single_text")) { + // line 65 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 67 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 67, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 67)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 67), "")) : ("")) . " form-inline"))]); + // line 68 + if (( !array_key_exists("datetime", $context) || !(isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 68, $this->source); })()))) { + // line 69 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + } + // line 71 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 71, $this->source); })()) === false)) { + // line 72 + yield "
+ "; + // line 73 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 73, $this->source); })()), "year", [], "any", false, false, false, 73), 'label'); + yield " + "; + // line 74 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 74, $this->source); })()), "month", [], "any", false, false, false, 74), 'label'); + yield " + "; + // line 75 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 75, $this->source); })()), "day", [], "any", false, false, false, 75), 'label'); + yield " +
"; + } + // line 79 + yield Twig\Extension\CoreExtension::replace((isset($context["date_pattern"]) || array_key_exists("date_pattern", $context) ? $context["date_pattern"] : (function () { throw new RuntimeError('Variable "date_pattern" does not exist.', 79, $this->source); })()), ["{{ year }}" => // line 80 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 80, $this->source); })()), "year", [], "any", false, false, false, 80), 'widget'), "{{ month }}" => // line 81 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 81, $this->source); })()), "month", [], "any", false, false, false, 81), 'widget'), "{{ day }}" => // line 82 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 82, $this->source); })()), "day", [], "any", false, false, false, 82), 'widget')]); + // line 84 + if (( !array_key_exists("datetime", $context) || !(isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 84, $this->source); })()))) { + // line 85 + yield "
"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 90 + /** + * @return iterable + */ + public function block_time_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_widget")); + + // line 91 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 91, $this->source); })()) == "single_text")) { + // line 92 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 94 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 94, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 94)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 94), "")) : ("")) . " form-inline"))]); + // line 95 + if (( !array_key_exists("datetime", $context) || (false == (isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 95, $this->source); })())))) { + // line 96 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + } + // line 98 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 98, $this->source); })()) === false)) { + yield "
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 98, $this->source); })()), "hour", [], "any", false, false, false, 98), 'label'); + yield "
"; + } + // line 99 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 99, $this->source); })()), "hour", [], "any", false, false, false, 99), 'widget'); + // line 100 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 100, $this->source); })())) { + yield ":"; + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 100, $this->source); })()) === false)) { + yield "
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 100, $this->source); })()), "minute", [], "any", false, false, false, 100), 'label'); + yield "
"; + } + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 100, $this->source); })()), "minute", [], "any", false, false, false, 100), 'widget'); + } + // line 101 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 101, $this->source); })())) { + yield ":"; + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 101, $this->source); })()) === false)) { + yield "
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 101, $this->source); })()), "second", [], "any", false, false, false, 101), 'label'); + yield "
"; + } + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 101, $this->source); })()), "second", [], "any", false, false, false, 101), 'widget'); + } + // line 102 + if (( !array_key_exists("datetime", $context) || (false == (isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 102, $this->source); })())))) { + // line 103 + yield "
"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 108 + /** + * @return iterable + */ + public function block_dateinterval_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "dateinterval_widget")); + + // line 109 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 109, $this->source); })()) == "single_text")) { + // line 110 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 112 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 112, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 112)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 112), "")) : ("")) . " form-inline"))]); + // line 113 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 114 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 114, $this->source); })()), 'errors'); + // line 115 + yield "
+ env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("table_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["table_class"]) || array_key_exists("table_class", $context) ? $context["table_class"] : (function () { throw new RuntimeError('Variable "table_class" does not exist.', 116, $this->source); })()), "table-bordered table-condensed table-striped")) : ("table-bordered table-condensed table-striped")), "html", null, true); + yield "\" role=\"presentation\"> + + "; + // line 119 + if ((isset($context["with_years"]) || array_key_exists("with_years", $context) ? $context["with_years"] : (function () { throw new RuntimeError('Variable "with_years" does not exist.', 119, $this->source); })())) { + yield ""; + } + // line 120 + if ((isset($context["with_months"]) || array_key_exists("with_months", $context) ? $context["with_months"] : (function () { throw new RuntimeError('Variable "with_months" does not exist.', 120, $this->source); })())) { + yield ""; + } + // line 121 + if ((isset($context["with_weeks"]) || array_key_exists("with_weeks", $context) ? $context["with_weeks"] : (function () { throw new RuntimeError('Variable "with_weeks" does not exist.', 121, $this->source); })())) { + yield ""; + } + // line 122 + if ((isset($context["with_days"]) || array_key_exists("with_days", $context) ? $context["with_days"] : (function () { throw new RuntimeError('Variable "with_days" does not exist.', 122, $this->source); })())) { + yield ""; + } + // line 123 + if ((isset($context["with_hours"]) || array_key_exists("with_hours", $context) ? $context["with_hours"] : (function () { throw new RuntimeError('Variable "with_hours" does not exist.', 123, $this->source); })())) { + yield ""; + } + // line 124 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 124, $this->source); })())) { + yield ""; + } + // line 125 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 125, $this->source); })())) { + yield ""; + } + // line 126 + yield " + + + "; + // line 130 + if ((isset($context["with_years"]) || array_key_exists("with_years", $context) ? $context["with_years"] : (function () { throw new RuntimeError('Variable "with_years" does not exist.', 130, $this->source); })())) { + yield ""; + } + // line 131 + if ((isset($context["with_months"]) || array_key_exists("with_months", $context) ? $context["with_months"] : (function () { throw new RuntimeError('Variable "with_months" does not exist.', 131, $this->source); })())) { + yield ""; + } + // line 132 + if ((isset($context["with_weeks"]) || array_key_exists("with_weeks", $context) ? $context["with_weeks"] : (function () { throw new RuntimeError('Variable "with_weeks" does not exist.', 132, $this->source); })())) { + yield ""; + } + // line 133 + if ((isset($context["with_days"]) || array_key_exists("with_days", $context) ? $context["with_days"] : (function () { throw new RuntimeError('Variable "with_days" does not exist.', 133, $this->source); })())) { + yield ""; + } + // line 134 + if ((isset($context["with_hours"]) || array_key_exists("with_hours", $context) ? $context["with_hours"] : (function () { throw new RuntimeError('Variable "with_hours" does not exist.', 134, $this->source); })())) { + yield ""; + } + // line 135 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 135, $this->source); })())) { + yield ""; + } + // line 136 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 136, $this->source); })())) { + yield ""; + } + // line 137 + yield " + +
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 119, $this->source); })()), "years", [], "any", false, false, false, 119), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 120, $this->source); })()), "months", [], "any", false, false, false, 120), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 121, $this->source); })()), "weeks", [], "any", false, false, false, 121), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 122, $this->source); })()), "days", [], "any", false, false, false, 122), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 123, $this->source); })()), "hours", [], "any", false, false, false, 123), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 124, $this->source); })()), "minutes", [], "any", false, false, false, 124), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 125, $this->source); })()), "seconds", [], "any", false, false, false, 125), 'label'); + yield "
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 130, $this->source); })()), "years", [], "any", false, false, false, 130), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 131, $this->source); })()), "months", [], "any", false, false, false, 131), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 132, $this->source); })()), "weeks", [], "any", false, false, false, 132), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 133, $this->source); })()), "days", [], "any", false, false, false, 133), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 134, $this->source); })()), "hours", [], "any", false, false, false, 134), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 135, $this->source); })()), "minutes", [], "any", false, false, false, 135), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 136, $this->source); })()), "seconds", [], "any", false, false, false, 136), 'widget'); + yield "
+
"; + // line 141 + if ((isset($context["with_invert"]) || array_key_exists("with_invert", $context) ? $context["with_invert"] : (function () { throw new RuntimeError('Variable "with_invert" does not exist.', 141, $this->source); })())) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 141, $this->source); })()), "invert", [], "any", false, false, false, 141), 'widget'); + } + // line 142 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 146 + /** + * @return iterable + */ + public function block_choice_widget_expanded(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_expanded")); + + // line 147 + if (CoreExtension::inFilter("-inline", ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 147)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 147), "")) : ("")))) { + // line 148 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 148, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 149 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget', ["parent_label_class" => ((CoreExtension::getAttribute($this->env, $this->source, // line 150 +($context["label_attr"] ?? null), "class", [], "any", true, true, false, 150)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 150), "")) : ("")), "translation_domain" => // line 151 +(isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 151, $this->source); })())]); + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + } else { + // line 155 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 156 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 156, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 157 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget', ["parent_label_class" => ((CoreExtension::getAttribute($this->env, $this->source, // line 158 +($context["label_attr"] ?? null), "class", [], "any", true, true, false, 158)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 158), "")) : ("")), "translation_domain" => // line 159 +(isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 159, $this->source); })())]); + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 162 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 168 + /** + * @return iterable + */ + public function block_choice_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_label")); + + // line 170 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 170, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(Twig\Extension\CoreExtension::replace(((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 170)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 170), "")) : ("")), ["checkbox-inline" => "", "radio-inline" => "", "checkbox-custom" => "", "radio-custom" => "", "switch-custom" => ""]))]); + // line 171 + yield from $this->unwrap()->yieldBlock("form_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 174 + /** + * @return iterable + */ + public function block_checkbox_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_label")); + + // line 175 + yield from $this->unwrap()->yieldBlock("checkbox_radio_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 178 + /** + * @return iterable + */ + public function block_radio_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_label")); + + // line 179 + yield from $this->unwrap()->yieldBlock("checkbox_radio_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 184 + /** + * @return iterable + */ + public function block_button_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_row")); + + // line 185 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 185, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 185)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 185), "")) : ("")) . " form-group"))])]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 185, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield ">"; + // line 186 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 186, $this->source); })()), 'widget'); + // line 187 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 190 + /** + * @return iterable + */ + public function block_choice_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_row")); + + // line 191 + $context["force_error"] = true; + // line 192 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 195 + /** + * @return iterable + */ + public function block_date_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_row")); + + // line 196 + $context["force_error"] = true; + // line 197 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 200 + /** + * @return iterable + */ + public function block_time_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_row")); + + // line 201 + $context["force_error"] = true; + // line 202 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 205 + /** + * @return iterable + */ + public function block_datetime_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_row")); + + // line 206 + $context["force_error"] = true; + // line 207 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "bootstrap_base_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 804 => 207, 802 => 206, 792 => 205, 784 => 202, 782 => 201, 772 => 200, 764 => 197, 762 => 196, 752 => 195, 744 => 192, 742 => 191, 732 => 190, 724 => 187, 722 => 186, 710 => 185, 700 => 184, 692 => 179, 682 => 178, 674 => 175, 664 => 174, 656 => 171, 654 => 170, 644 => 168, 635 => 162, 629 => 159, 628 => 158, 627 => 157, 623 => 156, 619 => 155, 612 => 151, 611 => 150, 610 => 149, 606 => 148, 604 => 147, 594 => 146, 585 => 142, 581 => 141, 576 => 137, 570 => 136, 564 => 135, 558 => 134, 552 => 133, 546 => 132, 540 => 131, 534 => 130, 529 => 126, 523 => 125, 517 => 124, 511 => 123, 505 => 122, 499 => 121, 493 => 120, 487 => 119, 482 => 116, 479 => 115, 477 => 114, 473 => 113, 471 => 112, 468 => 110, 466 => 109, 456 => 108, 446 => 103, 444 => 102, 434 => 101, 424 => 100, 422 => 99, 416 => 98, 411 => 96, 409 => 95, 407 => 94, 404 => 92, 402 => 91, 392 => 90, 382 => 85, 380 => 84, 378 => 82, 377 => 81, 376 => 80, 375 => 79, 370 => 75, 366 => 74, 362 => 73, 359 => 72, 357 => 71, 352 => 69, 350 => 68, 348 => 67, 345 => 65, 343 => 64, 333 => 63, 324 => 59, 322 => 58, 320 => 57, 318 => 55, 314 => 54, 310 => 53, 306 => 52, 302 => 51, 298 => 50, 294 => 49, 292 => 48, 290 => 46, 288 => 45, 284 => 44, 282 => 43, 279 => 41, 277 => 40, 267 => 39, 258 => 35, 252 => 32, 250 => 31, 248 => 30, 246 => 29, 236 => 28, 227 => 24, 223 => 22, 217 => 20, 215 => 19, 213 => 18, 207 => 16, 205 => 15, 200 => 14, 197 => 13, 194 => 12, 192 => 11, 182 => 10, 174 => 7, 172 => 6, 162 => 5, 154 => 205, 151 => 204, 149 => 200, 146 => 199, 144 => 195, 141 => 194, 139 => 190, 136 => 189, 134 => 184, 131 => 183, 128 => 181, 126 => 178, 123 => 177, 121 => 174, 118 => 173, 116 => 168, 113 => 167, 110 => 165, 108 => 146, 106 => 108, 104 => 90, 101 => 89, 99 => 63, 96 => 62, 94 => 39, 91 => 38, 89 => 28, 86 => 27, 84 => 10, 81 => 9, 79 => 5, 76 => 4, 73 => 2, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"form_div_layout.html.twig\" %} + +{# Widgets #} + +{% block textarea_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
+ {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
+ {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
+ {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block datetime_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
+ {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + +
+ {%- if form.date.year is defined %}{{ form_label(form.date.year) }}{% endif -%} + {%- if form.date.month is defined %}{{ form_label(form.date.month) }}{% endif -%} + {%- if form.date.day is defined %}{{ form_label(form.date.day) }}{% endif -%} + {%- if form.time.hour is defined %}{{ form_label(form.time.hour) }}{% endif -%} + {%- if form.time.minute is defined %}{{ form_label(form.time.minute) }}{% endif -%} + {%- if form.time.second is defined %}{{ form_label(form.time.second) }}{% endif -%} +
+ + {{- form_widget(form.date, { datetime: true } ) -}} + {{- form_widget(form.time, { datetime: true } ) -}} +
+ {%- endif -%} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} + {%- if datetime is not defined or not datetime -%} +
+ {%- endif %} + {%- if label is not same as(false) -%} +
+ {{ form_label(form.year) }} + {{ form_label(form.month) }} + {{ form_label(form.day) }} +
+ {%- endif -%} + + {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} + {%- if datetime is not defined or not datetime -%} +
+ {%- endif -%} + {%- endif -%} +{%- endblock date_widget %} + +{% block time_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} + {%- if datetime is not defined or false == datetime -%} +
+ {%- endif -%} + {%- if label is not same as(false) -%}
{{ form_label(form.hour) }}
{%- endif -%} + {{- form_widget(form.hour) -}} + {%- if with_minutes -%}:{%- if label is not same as(false) -%}
{{ form_label(form.minute) }}
{%- endif -%}{{ form_widget(form.minute) }}{%- endif -%} + {%- if with_seconds -%}:{%- if label is not same as(false) -%}
{{ form_label(form.second) }}
{%- endif -%}{{ form_widget(form.second) }}{%- endif -%} + {%- if datetime is not defined or false == datetime -%} +
+ {%- endif -%} + {%- endif -%} +{%- endblock time_widget %} + +{%- block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
+ {{- form_errors(form) -}} +
+ + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + +
{{ form_label(form.years) }}{{ form_label(form.months) }}{{ form_label(form.weeks) }}{{ form_label(form.days) }}{{ form_label(form.hours) }}{{ form_label(form.minutes) }}{{ form_label(form.seconds) }}
{{ form_widget(form.years) }}{{ form_widget(form.months) }}{{ form_widget(form.weeks) }}{{ form_widget(form.days) }}{{ form_widget(form.hours) }}{{ form_widget(form.minutes) }}{{ form_widget(form.seconds) }}
+
+ {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
+ {%- endif -%} +{%- endblock dateinterval_widget -%} + +{% block choice_widget_expanded -%} + {%- if '-inline' in label_attr.class|default('') -%} + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + }) -}} + {% endfor -%} + {%- else -%} +
+ {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + }) -}} + {%- endfor -%} +
+ {%- endif -%} +{%- endblock choice_widget_expanded %} + +{# Labels #} + +{% block choice_label -%} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': '', 'switch-custom': ''})|trim}) -%} + {{- block('form_label') -}} +{% endblock choice_label %} + +{% block checkbox_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{# Rows #} + +{% block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row %} + +{% block choice_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock choice_row %} + +{% block date_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock date_row %} + +{% block time_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock time_row %} + +{% block datetime_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock datetime_row %} +", "bootstrap_base_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/24/243112b501083e91db58ed8879a469ec.php b/var/cache/dev/twig/24/243112b501083e91db58ed8879a469ec.php new file mode 100644 index 0000000..2314767 --- /dev/null +++ b/var/cache/dev/twig/24/243112b501083e91db58ed8879a469ec.php @@ -0,0 +1,302 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("form_div_layout.html.twig", "form_table_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."form_div_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'form_row' => [$this, 'block_form_row'], + 'button_row' => [$this, 'block_button_row'], + 'hidden_row' => [$this, 'block_hidden_row'], + 'form_widget_compound' => [$this, 'block_form_widget_compound'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "form_table_layout.html.twig")); + + // line 3 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 20 + yield from $this->unwrap()->yieldBlock('button_row', $context, $blocks); + // line 29 + yield from $this->unwrap()->yieldBlock('hidden_row', $context, $blocks); + // line 38 + yield from $this->unwrap()->yieldBlock('form_widget_compound', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 3 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 4 + $context["widget_attr"] = []; + // line 5 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 5, $this->source); })()))) { + // line 6 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 6, $this->source); })()) . "_help")]]; + } + // line 8 + yield " (isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 8, $this->source); })())]; + if (!is_iterable($__internal_compile_1)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 8, $this->getSourceContext()); + } + $__internal_compile_1 = CoreExtension::toArray($__internal_compile_1); + $context = $__internal_compile_1 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_0; + yield "> + "; + // line 10 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 10, $this->source); })()), 'label'); + // line 11 + yield " + "; + // line 13 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 13, $this->source); })()), 'errors'); + // line 14 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 14, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 14, $this->source); })())); + // line 15 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 15, $this->source); })()), 'help'); + // line 16 + yield " + "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 20 + /** + * @return iterable + */ + public function block_button_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_row")); + + // line 21 + yield " (isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 21, $this->source); })())]; + if (!is_iterable($__internal_compile_3)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 21, $this->getSourceContext()); + } + $__internal_compile_3 = CoreExtension::toArray($__internal_compile_3); + $context = $__internal_compile_3 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_2; + yield "> + + "; + // line 24 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 24, $this->source); })()), 'widget'); + // line 25 + yield " + "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 29 + /** + * @return iterable + */ + public function block_hidden_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hidden_row")); + + // line 30 + $context["style"] = ((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "style", [], "any", true, true, false, 30)) ? ((CoreExtension::getAttribute($this->env, $this->source, (isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 30, $this->source); })()), "style", [], "any", false, false, false, 30) . (((Twig\Extension\CoreExtension::last($this->env->getCharset(), Twig\Extension\CoreExtension::trim(CoreExtension::getAttribute($this->env, $this->source, (isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 30, $this->source); })()), "style", [], "any", false, false, false, 30))) != ";")) ? ("; ") : ("")))) : ("")); + // line 31 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 31, $this->source); })()), ["style" => Twig\Extension\CoreExtension::trim(((isset($context["style"]) || array_key_exists("style", $context) ? $context["style"] : (function () { throw new RuntimeError('Variable "style" does not exist.', 31, $this->source); })()) . " display: none"))])]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 31, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield "> + "; + // line 33 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 33, $this->source); })()), 'widget'); + // line 34 + yield " + "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 38 + /** + * @return iterable + */ + public function block_form_widget_compound(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget_compound")); + + // line 39 + yield "unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 40 + if ((Symfony\Bridge\Twig\Extension\twig_is_root_form((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 40, $this->source); })())) && (Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 40, $this->source); })())) > 0))) { + // line 41 + yield " + + "; + } + // line 47 + yield from $this->unwrap()->yieldBlock("form_rows", $context, $blocks); + // line 48 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 48, $this->source); })()), 'rest'); + // line 49 + yield "
"; + // line 43 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 43, $this->source); })()), 'errors'); + // line 44 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "form_table_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 225 => 49, 223 => 48, 221 => 47, 217 => 44, 215 => 43, 212 => 41, 210 => 40, 206 => 39, 196 => 38, 187 => 34, 185 => 33, 172 => 31, 170 => 30, 160 => 29, 151 => 25, 149 => 24, 135 => 21, 125 => 20, 116 => 16, 114 => 15, 112 => 14, 110 => 13, 107 => 11, 105 => 10, 92 => 8, 89 => 6, 87 => 5, 85 => 4, 75 => 3, 67 => 38, 65 => 29, 63 => 20, 61 => 3, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"form_div_layout.html.twig\" %} + +{%- block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + + + {{- form_label(form) -}} + + + {{- form_errors(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + + +{%- endblock form_row -%} + +{%- block button_row -%} + + + + {{- form_widget(form) -}} + + +{%- endblock button_row -%} + +{%- block hidden_row -%} + {%- set style = row_attr.style is defined ? (row_attr.style ~ (row_attr.style|trim|last != ';' ? '; ')) -%} + + + {{- form_widget(form) -}} + + +{%- endblock hidden_row -%} + +{%- block form_widget_compound -%} + + {%- if form is rootform and errors|length > 0 -%} + + + + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
+ {{- form_errors(form) -}} +
+{%- endblock form_widget_compound -%} +", "form_table_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/27/27365893a1a1c3bec388e1fb02f888dd.php b/var/cache/dev/twig/27/27365893a1a1c3bec388e1fb02f888dd.php new file mode 100644 index 0000000..f3d67a4 --- /dev/null +++ b/var/cache/dev/twig/27/27365893a1a1c3bec388e1fb02f888dd.php @@ -0,0 +1,473 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("bootstrap_3_layout.html.twig", "bootstrap_3_horizontal_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."bootstrap_3_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'form_start' => [$this, 'block_form_start'], + 'form_label' => [$this, 'block_form_label'], + 'form_label_class' => [$this, 'block_form_label_class'], + 'form_row' => [$this, 'block_form_row'], + 'submit_row' => [$this, 'block_submit_row'], + 'reset_row' => [$this, 'block_reset_row'], + 'form_group_class' => [$this, 'block_form_group_class'], + 'checkbox_row' => [$this, 'block_checkbox_row'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "bootstrap_3_horizontal_layout.html.twig")); + + // line 2 + yield " +"; + // line 3 + yield from $this->unwrap()->yieldBlock('form_start', $context, $blocks); + // line 7 + yield " +"; + // line 9 + yield " +"; + // line 10 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 18 + yield " +"; + // line 19 + yield from $this->unwrap()->yieldBlock('form_label_class', $context, $blocks); + // line 22 + yield " +"; + // line 24 + yield " +"; + // line 25 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 39 + yield " +"; + // line 40 + yield from $this->unwrap()->yieldBlock('submit_row', $context, $blocks); + // line 48 + yield " +"; + // line 49 + yield from $this->unwrap()->yieldBlock('reset_row', $context, $blocks); + // line 57 + yield " +"; + // line 58 + yield from $this->unwrap()->yieldBlock('form_group_class', $context, $blocks); + // line 61 + yield " +"; + // line 62 + yield from $this->unwrap()->yieldBlock('checkbox_row', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 3 + /** + * @return iterable + */ + public function block_form_start(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_start")); + + // line 4 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 4, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 4)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 4), "")) : ("")) . " form-horizontal"))]); + // line 5 + yield from $this->yieldParentBlock("form_start", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 10 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 11 + if (((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 11, $this->source); })()) === false)) { + // line 12 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + } else { + // line 14 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 14, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 14)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 14), "")) : ("")) . " ") . $this->unwrap()->renderBlock("form_label_class", $context, $blocks)))]); + // line 15 + yield from $this->yieldParentBlock("form_label", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 19 + /** + * @return iterable + */ + public function block_form_label_class(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label_class")); + + // line 20 + yield "col-sm-2"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 25 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 26 + $context["widget_attr"] = []; + // line 27 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 27, $this->source); })()))) { + // line 28 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 28, $this->source); })()) . "_help")]]; + } + // line 30 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 30, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 30)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 30), "")) : ("")) . " form-group") . (((( !(isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 30, $this->source); })()) || ((array_key_exists("force_error", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["force_error"]) || array_key_exists("force_error", $context) ? $context["force_error"] : (function () { throw new RuntimeError('Variable "force_error" does not exist.', 30, $this->source); })()), false)) : (false))) && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 30, $this->source); })()))) ? (" has-error") : (""))))])]; + if (!is_iterable($__internal_compile_1)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 30, $this->getSourceContext()); + } + $__internal_compile_1 = CoreExtension::toArray($__internal_compile_1); + $context = $__internal_compile_1 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_0; + yield ">"; + // line 31 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 31, $this->source); })()), 'label'); + // line 32 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 33 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 33, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 33, $this->source); })())); + // line 34 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 34, $this->source); })()), 'help'); + // line 35 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 35, $this->source); })()), 'errors'); + // line 36 + yield "
+"; + // line 37 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 40 + /** + * @return iterable + */ + public function block_submit_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "submit_row")); + + // line 41 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 41, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 41)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 41), "")) : ("")) . " form-group"))])]; + if (!is_iterable($__internal_compile_3)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 41, $this->getSourceContext()); + } + $__internal_compile_3 = CoreExtension::toArray($__internal_compile_3); + $context = $__internal_compile_3 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_2; + yield ">"; + // line 42 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 43 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 44 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 44, $this->source); })()), 'widget'); + // line 45 + yield "
"; + // line 46 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 49 + /** + * @return iterable + */ + public function block_reset_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "reset_row")); + + // line 50 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 50, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 50)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 50), "")) : ("")) . " form-group"))])]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 50, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield ">"; + // line 51 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 52 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 53 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 53, $this->source); })()), 'widget'); + // line 54 + yield "
"; + // line 55 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 58 + /** + * @return iterable + */ + public function block_form_group_class(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_group_class")); + + // line 59 + yield "col-sm-10"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 62 + /** + * @return iterable + */ + public function block_checkbox_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_row")); + + // line 63 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 63, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 63)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 63), "")) : ("")) . " form-group") . (( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 63, $this->source); })())) ? (" has-error") : (""))))])]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 63, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + yield ">"; + // line 64 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 65 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 66 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 66, $this->source); })()), 'widget'); + // line 67 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 67, $this->source); })()), 'help'); + // line 68 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 68, $this->source); })()), 'errors'); + // line 69 + yield "
"; + // line 70 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "bootstrap_3_horizontal_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 375 => 70, 373 => 69, 371 => 68, 369 => 67, 367 => 66, 363 => 65, 359 => 64, 347 => 63, 337 => 62, 329 => 59, 319 => 58, 311 => 55, 309 => 54, 307 => 53, 303 => 52, 299 => 51, 287 => 50, 277 => 49, 269 => 46, 267 => 45, 265 => 44, 261 => 43, 257 => 42, 245 => 41, 235 => 40, 227 => 37, 224 => 36, 222 => 35, 220 => 34, 218 => 33, 214 => 32, 212 => 31, 200 => 30, 197 => 28, 195 => 27, 193 => 26, 183 => 25, 175 => 20, 165 => 19, 156 => 15, 154 => 14, 149 => 12, 147 => 11, 137 => 10, 129 => 5, 127 => 4, 117 => 3, 109 => 62, 106 => 61, 104 => 58, 101 => 57, 99 => 49, 96 => 48, 94 => 40, 91 => 39, 89 => 25, 86 => 24, 83 => 22, 81 => 19, 78 => 18, 76 => 10, 73 => 9, 70 => 7, 68 => 3, 65 => 2, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"bootstrap_3_layout.html.twig\" %} + +{% block form_start -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-horizontal')|trim}) %} + {{- parent() -}} +{%- endblock form_start %} + +{# Labels #} + +{% block form_label -%} + {%- if label is same as(false) -%} +
+ {%- else -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%} + {{- parent() -}} + {%- endif -%} +{%- endblock form_label %} + +{% block form_label_class -%} +col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + + {{- form_label(form) -}} +
+ {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
+{##} +{%- endblock form_row %} + +{% block submit_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} +
{#--#} + +{%- endblock submit_row %} + +{% block reset_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} +
{#--#} + +{%- endblock reset_row %} + +{% block form_group_class -%} +col-sm-10 +{%- endblock form_group_class %} + +{% block checkbox_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
{#--#} + +{%- endblock checkbox_row %} +", "bootstrap_3_horizontal_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/28/280e979c4f4f23027535cf63599a52bd.php b/var/cache/dev/twig/28/280e979c4f4f23027535cf63599a52bd.php new file mode 100644 index 0000000..f776b04 --- /dev/null +++ b/var/cache/dev/twig/28/280e979c4f4f23027535cf63599a52bd.php @@ -0,0 +1,187 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + 'title' => [$this, 'block_title'], + 'stylesheets' => [$this, 'block_stylesheets'], + 'javascripts' => [$this, 'block_javascripts'], + 'body' => [$this, 'block_body'], + ]; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "base.html.twig")); + + // line 1 + yield " + + + + "; + // line 5 + yield from $this->unwrap()->yieldBlock('title', $context, $blocks); + yield " + âš«ï¸sf\"> + "; + // line 7 + yield from $this->unwrap()->yieldBlock('stylesheets', $context, $blocks); + // line 9 + yield " + "; + // line 10 + yield from $this->unwrap()->yieldBlock('javascripts', $context, $blocks); + // line 12 + yield " + + "; + // line 14 + yield from $this->unwrap()->yieldBlock('body', $context, $blocks); + // line 15 + yield " + +"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_title(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "title")); + + yield "Welcome!"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 7 + /** + * @return iterable + */ + public function block_stylesheets(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "stylesheets")); + + // line 8 + yield " "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 10 + /** + * @return iterable + */ + public function block_javascripts(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "javascripts")); + + // line 11 + yield " "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 14 + /** + * @return iterable + */ + public function block_body(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "body")); + + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "base.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 136 => 14, 128 => 11, 118 => 10, 110 => 8, 100 => 7, 83 => 5, 73 => 15, 71 => 14, 67 => 12, 65 => 10, 62 => 9, 60 => 7, 55 => 5, 49 => 1,); + } + + public function getSourceContext(): Source + { + return new Source(" + + + + {% block title %}Welcome!{% endblock %} + âš«ï¸sf\"> + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + +", "base.html.twig", "/home/skylar/Projects/mycomments-api/templates/base.html.twig"); + } +} diff --git a/var/cache/dev/twig/39/39d35b7efa0cbb82158a1e5d00f4b700.php b/var/cache/dev/twig/39/39d35b7efa0cbb82158a1e5d00f4b700.php new file mode 100644 index 0000000..1be485c --- /dev/null +++ b/var/cache/dev/twig/39/39d35b7efa0cbb82158a1e5d00f4b700.php @@ -0,0 +1,84 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "@email/zurb_2/notification/body.txt.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@email/default/notification/body.txt.twig")); + + $this->parent = $this->loadTemplate("@email/zurb_2/notification/body.txt.twig", "@email/default/notification/body.txt.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@email/default/notification/body.txt.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 39 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends \"@email/zurb_2/notification/body.txt.twig\" %} +", "@email/default/notification/body.txt.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.txt.twig"); + } +} diff --git a/var/cache/dev/twig/53/535f0392bf22d2a23c79b71e5d399ccd.php b/var/cache/dev/twig/53/535f0392bf22d2a23c79b71e5d399ccd.php new file mode 100644 index 0000000..bd2a3da --- /dev/null +++ b/var/cache/dev/twig/53/535f0392bf22d2a23c79b71e5d399ccd.php @@ -0,0 +1,175 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + 'hwi_oauth_content' => [$this, 'block_hwi_oauth_content'], + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "@HWIOAuth/layout.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@HWIOAuth/Connect/connect_confirm.html.twig")); + + $this->parent = $this->loadTemplate("@HWIOAuth/layout.html.twig", "@HWIOAuth/Connect/connect_confirm.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + // line 3 + /** + * @return iterable + */ + public function block_hwi_oauth_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hwi_oauth_content")); + + // line 4 + yield "

"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("header.connecting", [], "HWIOAuthBundle"), "html", null, true); + yield "

+
+
+

"; + // line 7 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("connect.confirm.text", ["%service%" => $this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["service"]) || array_key_exists("service", $context) ? $context["service"] : (function () { throw new RuntimeError('Variable "service" does not exist.', 7, $this->source); })()), [], "HWIOAuthBundle"), "%name%" => CoreExtension::getAttribute($this->env, $this->source, (isset($context["userInformation"]) || array_key_exists("userInformation", $context) ? $context["userInformation"] : (function () { throw new RuntimeError('Variable "userInformation" does not exist.', 7, $this->source); })()), "realName", [], "any", false, false, false, 7)], "HWIOAuthBundle"), "html", null, true); + yield "

+

+ "; + // line 9 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 9, $this->source); })()), 'form_start', ["action" => $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("hwi_oauth_connect_service", ["service" => (isset($context["service"]) || array_key_exists("service", $context) ? $context["service"] : (function () { throw new RuntimeError('Variable "service" does not exist.', 9, $this->source); })()), "key" => (isset($context["key"]) || array_key_exists("key", $context) ? $context["key"] : (function () { throw new RuntimeError('Variable "key" does not exist.', 9, $this->source); })())]), "attr" => ["class" => "registration_register"]]); + yield " + "; + // line 10 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 10, $this->source); })()), 'widget'); + yield " +

+ + extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("hwi_oauth_connect"); + yield "\" class=\"btn\">"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("connect.confirm.cancel", [], "HWIOAuthBundle"), "html", null, true); + yield " +
+ "; + // line 15 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 15, $this->source); })()), 'form_end'); + yield " +

+
+
+ "; + // line 19 + if ((CoreExtension::getAttribute($this->env, $this->source, ($context["userInformation"] ?? null), "profilePicture", [], "any", true, true, false, 19) && !Twig\Extension\CoreExtension::testEmpty(CoreExtension::getAttribute($this->env, $this->source, (isset($context["userInformation"]) || array_key_exists("userInformation", $context) ? $context["userInformation"] : (function () { throw new RuntimeError('Variable "userInformation" does not exist.', 19, $this->source); })()), "profilePicture", [], "any", false, false, false, 19)))) { + // line 20 + yield " env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, (isset($context["userInformation"]) || array_key_exists("userInformation", $context) ? $context["userInformation"] : (function () { throw new RuntimeError('Variable "userInformation" does not exist.', 20, $this->source); })()), "profilePicture", [], "any", false, false, false, 20), "html", null, true); + yield "\" /> + "; + } + // line 22 + yield "
+
+"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@HWIOAuth/Connect/connect_confirm.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 114 => 22, 108 => 20, 106 => 19, 99 => 15, 92 => 13, 88 => 12, 83 => 10, 79 => 9, 74 => 7, 67 => 4, 57 => 3, 40 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} +

{{ 'header.connecting' | trans({}, 'HWIOAuthBundle')}}

+
+
+

{{ 'connect.confirm.text' | trans({'%service%': service | trans({}, 'HWIOAuthBundle'), '%name%': userInformation.realName}, 'HWIOAuthBundle') }}

+

+ {{ form_start(form, {'action': path('hwi_oauth_connect_service', {'service': service, 'key': key}), 'attr': {'class': 'registration_register'}}) }} + {{ form_widget(form) }} +

+ + {{ 'connect.confirm.cancel' | trans({}, 'HWIOAuthBundle') }} +
+ {{ form_end(form) }} +

+
+
+ {% if userInformation.profilePicture is defined and userInformation.profilePicture is not empty %} + + {% endif %} +
+
+{% endblock hwi_oauth_content %} +", "@HWIOAuth/Connect/connect_confirm.html.twig", "/home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_confirm.html.twig"); + } +} diff --git a/var/cache/dev/twig/65/652f25cb019391e28d041585cb3fadfb.php b/var/cache/dev/twig/65/652f25cb019391e28d041585cb3fadfb.php new file mode 100644 index 0000000..89f3855 --- /dev/null +++ b/var/cache/dev/twig/65/652f25cb019391e28d041585cb3fadfb.php @@ -0,0 +1,282 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + 'checkbox_row' => [$this, 'block_checkbox_row'], + 'money_widget' => [$this, 'block_money_widget'], + 'percent_widget' => [$this, 'block_percent_widget'], + 'button_widget' => [$this, 'block_button_widget'], + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "form_div_layout.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "foundation_6_layout.html.twig")); + + $this->parent = $this->loadTemplate("form_div_layout.html.twig", "foundation_6_layout.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + // line 3 + /** + * @return iterable + */ + public function block_checkbox_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_row")); + + // line 4 + $context["parent_class"] = ((array_key_exists("parent_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_class"]) || array_key_exists("parent_class", $context) ? $context["parent_class"] : (function () { throw new RuntimeError('Variable "parent_class" does not exist.', 4, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 4)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 4), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 4)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 4), "")) : ("")))); + // line 5 + if (CoreExtension::inFilter("switch-input", (isset($context["parent_class"]) || array_key_exists("parent_class", $context) ? $context["parent_class"] : (function () { throw new RuntimeError('Variable "parent_class" does not exist.', 5, $this->source); })()))) { + // line 6 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 6, $this->source); })()), 'label'); + // line 7 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 7, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 7)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 7), "")) : ("")) . " switch-input"))]); + // line 8 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 8, $this->source); })()), 'widget'); + // line 9 + yield ""; + // line 10 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 10, $this->source); })()), 'errors'); + } else { + // line 12 + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 16 + /** + * @return iterable + */ + public function block_money_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "money_widget")); + + // line 17 + $context["prepend"] = !(is_string($__internal_compile_0 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 17, $this->source); })())) && is_string($__internal_compile_1 = "{{") && str_starts_with($__internal_compile_0, $__internal_compile_1)); + // line 18 + yield " "; + $context["append"] = !(is_string($__internal_compile_2 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 18, $this->source); })())) && is_string($__internal_compile_3 = "}}") && str_ends_with($__internal_compile_2, $__internal_compile_3)); + // line 19 + yield " "; + if (((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 19, $this->source); })()) || (isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 19, $this->source); })()))) { + // line 20 + yield "
+ "; + // line 21 + if ((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 21, $this->source); })())) { + // line 22 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 22, $this->source); })())); + yield " + "; + } + // line 24 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 24, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 24)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 24), "")) : ("")) . " input-group-field"))]); + // line 25 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 26 + if ((isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 26, $this->source); })())) { + // line 27 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 27, $this->source); })())); + yield " + "; + } + // line 29 + yield "
+ "; + } else { + // line 31 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 35 + /** + * @return iterable + */ + public function block_percent_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "percent_widget")); + + // line 36 + if ((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 36, $this->source); })())) { + // line 37 + yield "
+ "; + // line 38 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 38, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 38)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 38), "")) : ("")) . " input-group-field"))]); + // line 39 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 40 + yield ""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("symbol", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 40, $this->source); })()), "%")) : ("%")), "html", null, true); + yield " +
"; + } else { + // line 43 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 47 + /** + * @return iterable + */ + public function block_button_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_widget")); + + // line 48 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 48, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 48)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 48), "")) : ("")) . " button"))]); + // line 49 + yield from $this->yieldParentBlock("button_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "foundation_6_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 197 => 49, 195 => 48, 185 => 47, 176 => 43, 170 => 40, 168 => 39, 166 => 38, 163 => 37, 161 => 36, 151 => 35, 142 => 31, 138 => 29, 132 => 27, 130 => 26, 128 => 25, 125 => 24, 119 => 22, 117 => 21, 114 => 20, 111 => 19, 108 => 18, 106 => 17, 96 => 16, 87 => 12, 84 => 10, 80 => 9, 78 => 8, 76 => 7, 74 => 6, 72 => 5, 70 => 4, 60 => 3, 43 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends \"form_div_layout.html.twig\" %} + +{%- block checkbox_row -%} + {%- set parent_class = parent_class|default(attr.class|default('')) -%} + {%- if 'switch-input' in parent_class -%} + {{- form_label(form) -}} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' switch-input')|trim}) -%} + {{- form_widget(form) -}} + + {{- form_errors(form) -}} + {%- else -%} + {{- block('form_row') -}} + {%- endif -%} +{%- endblock checkbox_row -%} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
+ {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group-field')|trim}) %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
+ {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
+ {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group-field')|trim}) %} + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock button_widget %} +", "foundation_6_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/foundation_6_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/6a/6af0be323cab927f1bd22a258a1ea0e0.php b/var/cache/dev/twig/6a/6af0be323cab927f1bd22a258a1ea0e0.php new file mode 100644 index 0000000..4a0a46f --- /dev/null +++ b/var/cache/dev/twig/6a/6af0be323cab927f1bd22a258a1ea0e0.php @@ -0,0 +1,536 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("bootstrap_4_layout.html.twig", "bootstrap_4_horizontal_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."bootstrap_4_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'form_label' => [$this, 'block_form_label'], + 'form_label_class' => [$this, 'block_form_label_class'], + 'form_row' => [$this, 'block_form_row'], + 'fieldset_form_row' => [$this, 'block_fieldset_form_row'], + 'submit_row' => [$this, 'block_submit_row'], + 'reset_row' => [$this, 'block_reset_row'], + 'form_group_class' => [$this, 'block_form_group_class'], + 'checkbox_row' => [$this, 'block_checkbox_row'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "bootstrap_4_horizontal_layout.html.twig")); + + // line 2 + yield " +"; + // line 4 + yield " +"; + // line 5 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 16 + yield " +"; + // line 17 + yield from $this->unwrap()->yieldBlock('form_label_class', $context, $blocks); + // line 20 + yield " +"; + // line 22 + yield " +"; + // line 23 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 40 + yield " +"; + // line 41 + yield from $this->unwrap()->yieldBlock('fieldset_form_row', $context, $blocks); + // line 57 + yield " +"; + // line 58 + yield from $this->unwrap()->yieldBlock('submit_row', $context, $blocks); + // line 66 + yield " +"; + // line 67 + yield from $this->unwrap()->yieldBlock('reset_row', $context, $blocks); + // line 75 + yield " +"; + // line 76 + yield from $this->unwrap()->yieldBlock('form_group_class', $context, $blocks); + // line 79 + yield " +"; + // line 80 + yield from $this->unwrap()->yieldBlock('checkbox_row', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 6 + if (((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 6, $this->source); })()) === false)) { + // line 7 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + } else { + // line 9 + if (( !array_key_exists("expanded", $context) || !(isset($context["expanded"]) || array_key_exists("expanded", $context) ? $context["expanded"] : (function () { throw new RuntimeError('Variable "expanded" does not exist.', 9, $this->source); })()))) { + // line 10 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 10, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 10)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 10), "")) : ("")) . " col-form-label"))]); + } + // line 12 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 12, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 12)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 12), "")) : ("")) . " ") . $this->unwrap()->renderBlock("form_label_class", $context, $blocks)))]); + // line 13 + yield from $this->yieldParentBlock("form_label", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 17 + /** + * @return iterable + */ + public function block_form_label_class(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label_class")); + + // line 18 + yield "col-sm-2"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 23 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 24 + if ((array_key_exists("expanded", $context) && (isset($context["expanded"]) || array_key_exists("expanded", $context) ? $context["expanded"] : (function () { throw new RuntimeError('Variable "expanded" does not exist.', 24, $this->source); })()))) { + // line 25 + yield from $this->unwrap()->yieldBlock("fieldset_form_row", $context, $blocks); + } else { + // line 27 + $context["widget_attr"] = []; + // line 28 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 28, $this->source); })()))) { + // line 29 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 29, $this->source); })()) . "_help")]]; + } + // line 31 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 31, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 31)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 31), "")) : ("")) . " form-group row") . (((( !(isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 31, $this->source); })()) || ((array_key_exists("force_error", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["force_error"]) || array_key_exists("force_error", $context) ? $context["force_error"] : (function () { throw new RuntimeError('Variable "force_error" does not exist.', 31, $this->source); })()), false)) : (false))) && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 31, $this->source); })()))) ? (" is-invalid") : (""))))])]; + if (!is_iterable($__internal_compile_1)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 31, $this->getSourceContext()); + } + $__internal_compile_1 = CoreExtension::toArray($__internal_compile_1); + $context = $__internal_compile_1 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_0; + yield ">"; + // line 32 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 32, $this->source); })()), 'label'); + // line 33 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 34 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 34, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 34, $this->source); })())); + // line 35 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 35, $this->source); })()), 'help'); + // line 36 + yield "
+ "; + // line 37 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 41 + /** + * @return iterable + */ + public function block_fieldset_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "fieldset_form_row")); + + // line 42 + $context["widget_attr"] = []; + // line 43 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 43, $this->source); })()))) { + // line 44 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 44, $this->source); })()) . "_help")]]; + } + // line 46 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 46, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 46)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 46), "")) : ("")) . " form-group"))])]; + if (!is_iterable($__internal_compile_3)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 46, $this->getSourceContext()); + } + $__internal_compile_3 = CoreExtension::toArray($__internal_compile_3); + $context = $__internal_compile_3 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_2; + yield "> +
source); })()) || ((array_key_exists("force_error", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["force_error"]) || array_key_exists("force_error", $context) ? $context["force_error"] : (function () { throw new RuntimeError('Variable "force_error" does not exist.', 47, $this->source); })()), false)) : (false))) && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 47, $this->source); })()))) { + yield " is-invalid"; + } + yield "\">"; + // line 48 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 48, $this->source); })()), 'label'); + // line 49 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 50 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 50, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 50, $this->source); })())); + // line 51 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 51, $this->source); })()), 'help'); + // line 52 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 52, $this->source); })()), 'errors'); + // line 53 + yield "
+
+"; + // line 55 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 58 + /** + * @return iterable + */ + public function block_submit_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "submit_row")); + + // line 59 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 59, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 59)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 59), "")) : ("")) . " form-group row"))])]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 59, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield ">"; + // line 60 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 61 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 62 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 62, $this->source); })()), 'widget'); + // line 63 + yield "
"; + // line 64 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 67 + /** + * @return iterable + */ + public function block_reset_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "reset_row")); + + // line 68 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 68, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 68)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 68), "")) : ("")) . " form-group row"))])]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 68, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + yield ">"; + // line 69 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 70 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 71 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 71, $this->source); })()), 'widget'); + // line 72 + yield "
"; + // line 73 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 76 + /** + * @return iterable + */ + public function block_form_group_class(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_group_class")); + + // line 77 + yield "col-sm-10"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 80 + /** + * @return iterable + */ + public function block_checkbox_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_row")); + + // line 81 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 81, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 81)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 81), "")) : ("")) . " form-group row"))])]; + if (!is_iterable($__internal_compile_9)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 81, $this->getSourceContext()); + } + $__internal_compile_9 = CoreExtension::toArray($__internal_compile_9); + $context = $__internal_compile_9 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_8; + yield ">"; + // line 82 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 83 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 84 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 84, $this->source); })()), 'widget'); + // line 85 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 85, $this->source); })()), 'help'); + // line 86 + yield "
"; + // line 87 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "bootstrap_4_horizontal_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 421 => 87, 419 => 86, 417 => 85, 415 => 84, 411 => 83, 407 => 82, 395 => 81, 385 => 80, 377 => 77, 367 => 76, 359 => 73, 357 => 72, 355 => 71, 351 => 70, 347 => 69, 335 => 68, 325 => 67, 317 => 64, 315 => 63, 313 => 62, 309 => 61, 305 => 60, 293 => 59, 283 => 58, 275 => 55, 271 => 53, 269 => 52, 267 => 51, 265 => 50, 261 => 49, 259 => 48, 254 => 47, 241 => 46, 238 => 44, 236 => 43, 234 => 42, 224 => 41, 215 => 37, 212 => 36, 210 => 35, 208 => 34, 204 => 33, 202 => 32, 190 => 31, 187 => 29, 185 => 28, 183 => 27, 180 => 25, 178 => 24, 168 => 23, 160 => 18, 150 => 17, 141 => 13, 139 => 12, 136 => 10, 134 => 9, 129 => 7, 127 => 6, 117 => 5, 109 => 80, 106 => 79, 104 => 76, 101 => 75, 99 => 67, 96 => 66, 94 => 58, 91 => 57, 89 => 41, 86 => 40, 84 => 23, 81 => 22, 78 => 20, 76 => 17, 73 => 16, 71 => 5, 68 => 4, 65 => 2, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"bootstrap_4_layout.html.twig\" %} + +{# Labels #} + +{% block form_label -%} + {%- if label is same as(false) -%} +
+ {%- else -%} + {%- if expanded is not defined or not expanded -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%} + {{- parent() -}} + {%- endif -%} +{%- endblock form_label %} + +{% block form_label_class -%} +col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} + {%- if expanded is defined and expanded -%} + {{ block('fieldset_form_row') }} + {%- else -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + + {{- form_label(form) -}} +
+ {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} +
+ {##} + {%- endif -%} +{%- endblock form_row %} + +{% block fieldset_form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + +
+ {{- form_label(form) -}} +
+ {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
+
+{##} +{%- endblock fieldset_form_row %} + +{% block submit_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} +
{#--#} + +{%- endblock submit_row %} + +{% block reset_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} +
{#--#} + +{%- endblock reset_row %} + +{% block form_group_class -%} +col-sm-10 +{%- endblock form_group_class %} + +{% block checkbox_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} + {{- form_help(form) -}} +
{#--#} + +{%- endblock checkbox_row %} +", "bootstrap_4_horizontal_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/77/77f8f8a912e0d2a58da34414f6278929.php b/var/cache/dev/twig/77/77f8f8a912e0d2a58da34414f6278929.php new file mode 100644 index 0000000..b6c79ef --- /dev/null +++ b/var/cache/dev/twig/77/77f8f8a912e0d2a58da34414f6278929.php @@ -0,0 +1,135 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + 'hwi_oauth_content' => [$this, 'block_hwi_oauth_content'], + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "@HWIOAuth/layout.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@HWIOAuth/Connect/login.html.twig")); + + $this->parent = $this->loadTemplate("@HWIOAuth/layout.html.twig", "@HWIOAuth/Connect/login.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + // line 3 + /** + * @return iterable + */ + public function block_hwi_oauth_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hwi_oauth_content")); + + // line 4 + yield " "; + if ((array_key_exists("error", $context) && (isset($context["error"]) || array_key_exists("error", $context) ? $context["error"] : (function () { throw new RuntimeError('Variable "error" does not exist.', 4, $this->source); })()))) { + // line 5 + yield " "; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["error"]) || array_key_exists("error", $context) ? $context["error"] : (function () { throw new RuntimeError('Variable "error" does not exist.', 5, $this->source); })()), "html", null, true); + yield " + "; + } + // line 7 + yield " "; + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable($this->env->getRuntime('HWI\Bundle\OAuthBundle\Twig\Extension\OAuthRuntime')->getResourceOwners()); + foreach ($context['_seq'] as $context["_key"] => $context["owner"]) { + // line 8 + yield " env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('HWI\Bundle\OAuthBundle\Twig\Extension\OAuthRuntime')->getLoginUrl($context["owner"]), "html", null, true); + yield "\">"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans($context["owner"], [], "HWIOAuthBundle"), "html", null, true); + yield "
+ "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['owner'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@HWIOAuth/Connect/login.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 81 => 8, 76 => 7, 70 => 5, 67 => 4, 57 => 3, 40 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} + {% if error is defined and error %} + {{ error }} + {% endif %} + {% for owner in hwi_oauth_resource_owners() %} + {{ owner | trans({}, 'HWIOAuthBundle') }}
+ {% endfor %} +{% endblock hwi_oauth_content %} +", "@HWIOAuth/Connect/login.html.twig", "/home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views/Connect/login.html.twig"); + } +} diff --git a/var/cache/dev/twig/88/8897378e586e288d1cb621e2055ca003.php b/var/cache/dev/twig/88/8897378e586e288d1cb621e2055ca003.php new file mode 100644 index 0000000..3b37371 --- /dev/null +++ b/var/cache/dev/twig/88/8897378e586e288d1cb621e2055ca003.php @@ -0,0 +1,168 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + 'hwi_oauth_content' => [$this, 'block_hwi_oauth_content'], + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "@HWIOAuth/layout.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@HWIOAuth/Connect/registration.html.twig")); + + $this->parent = $this->loadTemplate("@HWIOAuth/layout.html.twig", "@HWIOAuth/Connect/registration.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + // line 3 + /** + * @return iterable + */ + public function block_hwi_oauth_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hwi_oauth_content")); + + // line 4 + yield "

"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("header.register", ["%name%" => CoreExtension::getAttribute($this->env, $this->source, (isset($context["userInformation"]) || array_key_exists("userInformation", $context) ? $context["userInformation"] : (function () { throw new RuntimeError('Variable "userInformation" does not exist.', 4, $this->source); })()), "realName", [], "any", false, false, false, 4)], "HWIOAuthBundle"), "html", null, true); + yield "

+
+
+ "; + // line 7 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 7, $this->source); })()), 'form_start', ["action" => $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("hwi_oauth_connect_registration", ["key" => (isset($context["key"]) || array_key_exists("key", $context) ? $context["key"] : (function () { throw new RuntimeError('Variable "key" does not exist.', 7, $this->source); })())]), "attr" => ["class" => "hwi_oauth_registration_register"]]); + yield " + "; + // line 8 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 8, $this->source); })()), 'widget'); + yield " +
+ + extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("hwi_oauth_connect"); + yield "\" class=\"btn\">"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("connect.registration.cancel", [], "HWIOAuthBundle"), "html", null, true); + yield " +
+ "; + // line 13 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 13, $this->source); })()), 'form_end'); + yield " +
+
+ "; + // line 16 + if ( !Twig\Extension\CoreExtension::testEmpty(CoreExtension::getAttribute($this->env, $this->source, (isset($context["userInformation"]) || array_key_exists("userInformation", $context) ? $context["userInformation"] : (function () { throw new RuntimeError('Variable "userInformation" does not exist.', 16, $this->source); })()), "profilePicture", [], "any", false, false, false, 16))) { + // line 17 + yield " env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, (isset($context["userInformation"]) || array_key_exists("userInformation", $context) ? $context["userInformation"] : (function () { throw new RuntimeError('Variable "userInformation" does not exist.', 17, $this->source); })()), "profilePicture", [], "any", false, false, false, 17), "html", null, true); + yield "\" alt=\"\" /> + "; + } + // line 19 + yield "
+
+ +"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@HWIOAuth/Connect/registration.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 108 => 19, 102 => 17, 100 => 16, 94 => 13, 87 => 11, 83 => 10, 78 => 8, 74 => 7, 67 => 4, 57 => 3, 40 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} +

{{ 'header.register' | trans({'%name%': userInformation.realName}, 'HWIOAuthBundle') }}

+
+
+ {{ form_start(form, {'action': path('hwi_oauth_connect_registration', {'key': key}), 'attr': {'class': 'hwi_oauth_registration_register'}}) }} + {{ form_widget(form) }} +
+ + {{ 'connect.registration.cancel' | trans({}, 'HWIOAuthBundle') }} +
+ {{ form_end(form) }} +
+
+ {% if userInformation.profilePicture is not empty %} + \"\" + {% endif %} +
+
+ +{% endblock hwi_oauth_content %} +", "@HWIOAuth/Connect/registration.html.twig", "/home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration.html.twig"); + } +} diff --git a/var/cache/dev/twig/90/9019f0f705f30b8ddbd69ed87b463d2d.php b/var/cache/dev/twig/90/9019f0f705f30b8ddbd69ed87b463d2d.php new file mode 100644 index 0000000..66d4177 --- /dev/null +++ b/var/cache/dev/twig/90/9019f0f705f30b8ddbd69ed87b463d2d.php @@ -0,0 +1,684 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("bootstrap_5_layout.html.twig", "bootstrap_5_horizontal_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."bootstrap_5_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'form_label' => [$this, 'block_form_label'], + 'form_label_class' => [$this, 'block_form_label_class'], + 'form_row' => [$this, 'block_form_row'], + 'fieldset_form_row' => [$this, 'block_fieldset_form_row'], + 'submit_row' => [$this, 'block_submit_row'], + 'reset_row' => [$this, 'block_reset_row'], + 'button_row' => [$this, 'block_button_row'], + 'checkbox_row' => [$this, 'block_checkbox_row'], + 'form_group_class' => [$this, 'block_form_group_class'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "bootstrap_5_horizontal_layout.html.twig")); + + // line 2 + yield " +"; + // line 4 + yield " +"; + // line 5 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 19 + yield " +"; + // line 20 + yield from $this->unwrap()->yieldBlock('form_label_class', $context, $blocks); + // line 23 + yield " +"; + // line 25 + yield " +"; + // line 26 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 72 + yield " +"; + // line 73 + yield from $this->unwrap()->yieldBlock('fieldset_form_row', $context, $blocks); + // line 89 + yield " +"; + // line 90 + yield from $this->unwrap()->yieldBlock('submit_row', $context, $blocks); + // line 98 + yield " +"; + // line 99 + yield from $this->unwrap()->yieldBlock('reset_row', $context, $blocks); + // line 107 + yield " +"; + // line 108 + yield from $this->unwrap()->yieldBlock('button_row', $context, $blocks); + // line 116 + yield " +"; + // line 117 + yield from $this->unwrap()->yieldBlock('checkbox_row', $context, $blocks); + // line 127 + yield " +"; + // line 128 + yield from $this->unwrap()->yieldBlock('form_group_class', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 6 + if (((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 6, $this->source); })()) === false)) { + // line 7 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + } else { + // line 9 + $context["row_class"] = ((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 9, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 9)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 9), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 9)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 9), "")) : ("")))); + // line 10 + if ((!CoreExtension::inFilter("form-floating", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 10, $this->source); })())) && !CoreExtension::inFilter("input-group", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 10, $this->source); })())))) { + // line 11 + if (( !array_key_exists("expanded", $context) || !(isset($context["expanded"]) || array_key_exists("expanded", $context) ? $context["expanded"] : (function () { throw new RuntimeError('Variable "expanded" does not exist.', 11, $this->source); })()))) { + // line 12 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 12, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 12)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 12), "")) : ("")) . " col-form-label"))]); + } + // line 14 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 14, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 14)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 14), "")) : ("")) . " ") . $this->unwrap()->renderBlock("form_label_class", $context, $blocks)))]); + } + // line 16 + yield from $this->yieldParentBlock("form_label", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 20 + /** + * @return iterable + */ + public function block_form_label_class(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label_class")); + + // line 21 + yield "col-sm-2"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 26 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 27 + if ((array_key_exists("expanded", $context) && (isset($context["expanded"]) || array_key_exists("expanded", $context) ? $context["expanded"] : (function () { throw new RuntimeError('Variable "expanded" does not exist.', 27, $this->source); })()))) { + // line 28 + yield from $this->unwrap()->yieldBlock("fieldset_form_row", $context, $blocks); + } else { + // line 30 + $context["widget_attr"] = []; + // line 31 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 31, $this->source); })()))) { + // line 32 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 32, $this->source); })()) . "_help")]]; + } + // line 34 + $context["row_class"] = ((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 34, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 34)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 34), "mb-3")) : ("mb-3")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 34)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 34), "mb-3")) : ("mb-3")))); + // line 35 + $context["is_form_floating"] = ((array_key_exists("is_form_floating", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["is_form_floating"]) || array_key_exists("is_form_floating", $context) ? $context["is_form_floating"] : (function () { throw new RuntimeError('Variable "is_form_floating" does not exist.', 35, $this->source); })()), CoreExtension::inFilter("form-floating", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 35, $this->source); })())))) : (CoreExtension::inFilter("form-floating", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 35, $this->source); })())))); + // line 36 + $context["is_input_group"] = ((array_key_exists("is_input_group", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["is_input_group"]) || array_key_exists("is_input_group", $context) ? $context["is_input_group"] : (function () { throw new RuntimeError('Variable "is_input_group" does not exist.', 36, $this->source); })()), CoreExtension::inFilter("input-group", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 36, $this->source); })())))) : (CoreExtension::inFilter("input-group", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 36, $this->source); })())))); + // line 38 + $context["row_class"] = Twig\Extension\CoreExtension::replace((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 38, $this->source); })()), ["form-floating" => "", "input-group" => ""]); + // line 39 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 39, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 39, $this->source); })()) . " row") . (((( !(isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 39, $this->source); })()) || ((array_key_exists("force_error", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["force_error"]) || array_key_exists("force_error", $context) ? $context["force_error"] : (function () { throw new RuntimeError('Variable "force_error" does not exist.', 39, $this->source); })()), false)) : (false))) && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 39, $this->source); })()))) ? (" is-invalid") : (""))))])]; + if (!is_iterable($__internal_compile_1)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 39, $this->getSourceContext()); + } + $__internal_compile_1 = CoreExtension::toArray($__internal_compile_1); + $context = $__internal_compile_1 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_0; + yield ">"; + // line 40 + if (((isset($context["is_form_floating"]) || array_key_exists("is_form_floating", $context) ? $context["is_form_floating"] : (function () { throw new RuntimeError('Variable "is_form_floating" does not exist.', 40, $this->source); })()) || (isset($context["is_input_group"]) || array_key_exists("is_input_group", $context) ? $context["is_input_group"] : (function () { throw new RuntimeError('Variable "is_input_group" does not exist.', 40, $this->source); })()))) { + // line 41 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
+
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 43 + if ((isset($context["is_form_floating"]) || array_key_exists("is_form_floating", $context) ? $context["is_form_floating"] : (function () { throw new RuntimeError('Variable "is_form_floating" does not exist.', 43, $this->source); })())) { + // line 44 + yield "
"; + // line 45 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 45, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 45, $this->source); })())); + // line 46 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 46, $this->source); })()), 'label'); + // line 47 + yield "
"; + } elseif ( // line 48 +(isset($context["is_input_group"]) || array_key_exists("is_input_group", $context) ? $context["is_input_group"] : (function () { throw new RuntimeError('Variable "is_input_group" does not exist.', 48, $this->source); })())) { + // line 49 + yield "
"; + // line 50 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 50, $this->source); })()), 'label'); + // line 51 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 51, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 51, $this->source); })())); + // line 53 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 53, $this->source); })()), 'help'); + // line 54 + yield "
"; + } + // line 56 + if ( !(isset($context["is_input_group"]) || array_key_exists("is_input_group", $context) ? $context["is_input_group"] : (function () { throw new RuntimeError('Variable "is_input_group" does not exist.', 56, $this->source); })())) { + // line 57 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 57, $this->source); })()), 'help'); + } + // line 59 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 59, $this->source); })()), 'errors'); + // line 60 + yield "
"; + } else { + // line 62 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 62, $this->source); })()), 'label'); + // line 63 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 64 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 64, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 64, $this->source); })())); + // line 65 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 65, $this->source); })()), 'help'); + // line 66 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 66, $this->source); })()), 'errors'); + // line 67 + yield "
"; + } + // line 69 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 73 + /** + * @return iterable + */ + public function block_fieldset_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "fieldset_form_row")); + + // line 74 + $context["widget_attr"] = []; + // line 75 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 75, $this->source); })()))) { + // line 76 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 76, $this->source); })()) . "_help")]]; + } + // line 78 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 78, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 78)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 78), "mb-3")) : ("mb-3")))])]; + if (!is_iterable($__internal_compile_3)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 78, $this->getSourceContext()); + } + $__internal_compile_3 = CoreExtension::toArray($__internal_compile_3); + $context = $__internal_compile_3 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_2; + yield "> +
source); })()) || ((array_key_exists("force_error", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["force_error"]) || array_key_exists("force_error", $context) ? $context["force_error"] : (function () { throw new RuntimeError('Variable "force_error" does not exist.', 79, $this->source); })()), false)) : (false))) && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 79, $this->source); })()))) { + yield " is-invalid"; + } + yield "\">"; + // line 80 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 80, $this->source); })()), 'label'); + // line 81 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 82 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 82, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 82, $this->source); })())); + // line 83 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 83, $this->source); })()), 'help'); + // line 84 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 84, $this->source); })()), 'errors'); + // line 85 + yield "
+
+ "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 90 + /** + * @return iterable + */ + public function block_submit_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "submit_row")); + + // line 91 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 91, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 91)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 91), "mb-3")) : ("mb-3")) . " row"))])]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 91, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield ">"; + // line 92 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 93 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 94 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 94, $this->source); })()), 'widget'); + // line 95 + yield "
"; + // line 96 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 99 + /** + * @return iterable + */ + public function block_reset_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "reset_row")); + + // line 100 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 100, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 100)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 100), "mb-3")) : ("mb-3")) . " row"))])]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 100, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + yield ">"; + // line 101 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 102 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 103 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 103, $this->source); })()), 'widget'); + // line 104 + yield "
"; + // line 105 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 108 + /** + * @return iterable + */ + public function block_button_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_row")); + + // line 109 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 109, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 109)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 109), "mb-3")) : ("mb-3")) . " row"))])]; + if (!is_iterable($__internal_compile_9)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 109, $this->getSourceContext()); + } + $__internal_compile_9 = CoreExtension::toArray($__internal_compile_9); + $context = $__internal_compile_9 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_8; + yield ">"; + // line 110 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 111 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 112 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 112, $this->source); })()), 'widget'); + // line 113 + yield "
"; + // line 114 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 117 + /** + * @return iterable + */ + public function block_checkbox_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_row")); + + // line 118 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 118, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 118)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 118), "mb-3")) : ("mb-3")) . " row"))])]; + if (!is_iterable($__internal_compile_11)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 118, $this->getSourceContext()); + } + $__internal_compile_11 = CoreExtension::toArray($__internal_compile_11); + $context = $__internal_compile_11 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_10; + yield ">"; + // line 119 + yield "
unwrap()->yieldBlock("form_label_class", $context, $blocks); + yield "\">
"; + // line 120 + yield "
unwrap()->yieldBlock("form_group_class", $context, $blocks); + yield "\">"; + // line 121 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 121, $this->source); })()), 'widget'); + // line 122 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 122, $this->source); })()), 'help'); + // line 123 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 123, $this->source); })()), 'errors'); + // line 124 + yield "
"; + // line 125 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 128 + /** + * @return iterable + */ + public function block_form_group_class(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_group_class")); + + // line 129 + yield "col-sm-10"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "bootstrap_5_horizontal_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 527 => 129, 517 => 128, 509 => 125, 507 => 124, 505 => 123, 503 => 122, 501 => 121, 497 => 120, 493 => 119, 481 => 118, 471 => 117, 463 => 114, 461 => 113, 459 => 112, 455 => 111, 451 => 110, 439 => 109, 429 => 108, 421 => 105, 419 => 104, 417 => 103, 413 => 102, 409 => 101, 397 => 100, 387 => 99, 379 => 96, 377 => 95, 375 => 94, 371 => 93, 367 => 92, 355 => 91, 345 => 90, 335 => 85, 333 => 84, 331 => 83, 329 => 82, 325 => 81, 323 => 80, 318 => 79, 305 => 78, 302 => 76, 300 => 75, 298 => 74, 288 => 73, 279 => 69, 276 => 67, 274 => 66, 272 => 65, 270 => 64, 266 => 63, 264 => 62, 261 => 60, 259 => 59, 256 => 57, 254 => 56, 251 => 54, 249 => 53, 247 => 51, 245 => 50, 243 => 49, 241 => 48, 239 => 47, 237 => 46, 235 => 45, 233 => 44, 231 => 43, 228 => 42, 223 => 41, 221 => 40, 209 => 39, 207 => 38, 205 => 36, 203 => 35, 201 => 34, 198 => 32, 196 => 31, 194 => 30, 191 => 28, 189 => 27, 179 => 26, 171 => 21, 161 => 20, 152 => 16, 149 => 14, 146 => 12, 144 => 11, 142 => 10, 140 => 9, 135 => 7, 133 => 6, 123 => 5, 115 => 128, 112 => 127, 110 => 117, 107 => 116, 105 => 108, 102 => 107, 100 => 99, 97 => 98, 95 => 90, 92 => 89, 90 => 73, 87 => 72, 85 => 26, 82 => 25, 79 => 23, 77 => 20, 74 => 19, 72 => 5, 69 => 4, 66 => 2, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"bootstrap_5_layout.html.twig\" %} + +{# Labels #} + +{% block form_label -%} + {%- if label is same as(false) -%} +
+ {%- else -%} + {%- set row_class = row_class|default(row_attr.class|default('')) -%} + {%- if 'form-floating' not in row_class and 'input-group' not in row_class -%} + {%- if expanded is not defined or not expanded -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%} + {%- endif -%} + {{- parent() -}} + {%- endif -%} +{%- endblock form_label %} + +{% block form_label_class -%} + col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} + {%- if expanded is defined and expanded -%} + {{ block('fieldset_form_row') }} + {%- else -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + {%- set row_class = row_class|default(row_attr.class|default('mb-3')) -%} + {%- set is_form_floating = is_form_floating|default('form-floating' in row_class) -%} + {%- set is_input_group = is_input_group|default('input-group' in row_class) -%} + {#- Remove behavior class from the main container -#} + {%- set row_class = row_class|replace({'form-floating': '', 'input-group': ''}) -%} + + {%- if is_form_floating or is_input_group -%} +
+
+ {%- if is_form_floating -%} +
+ {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} +
+ {%- elseif is_input_group -%} +
+ {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {#- Hack to properly display help with input group -#} + {{- form_help(form) -}} +
+ {%- endif -%} + {%- if not is_input_group -%} + {{- form_help(form) -}} + {%- endif -%} + {{- form_errors(form) -}} +
+ {%- else -%} + {{- form_label(form) -}} +
+ {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
+ {%- endif -%} + {##} + {%- endif -%} +{%- endblock form_row %} + +{% block fieldset_form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + +
+ {{- form_label(form) -}} +
+ {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
+
+ +{%- endblock fieldset_form_row %} + +{% block submit_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} +
{#--#} + +{%- endblock submit_row %} + +{% block reset_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} +
{#--#} + +{%- endblock reset_row %} + +{% block button_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} +
{#--#} + +{%- endblock button_row %} + +{% block checkbox_row -%} + {#--#} +
{#--#} +
+ {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
{#--#} + +{%- endblock checkbox_row %} + +{% block form_group_class -%} + col-sm-10 +{%- endblock form_group_class %} +", "bootstrap_5_horizontal_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_horizontal_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/9c/9cdc15e3406fb99652a5992d75385147.php b/var/cache/dev/twig/9c/9cdc15e3406fb99652a5992d75385147.php new file mode 100644 index 0000000..6f3e069 --- /dev/null +++ b/var/cache/dev/twig/9c/9cdc15e3406fb99652a5992d75385147.php @@ -0,0 +1,84 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "@email/zurb_2/notification/body.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@email/default/notification/body.html.twig")); + + $this->parent = $this->loadTemplate("@email/zurb_2/notification/body.html.twig", "@email/default/notification/body.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@email/default/notification/body.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 39 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends \"@email/zurb_2/notification/body.html.twig\" %} +", "@email/default/notification/body.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.html.twig"); + } +} diff --git a/var/cache/dev/twig/a3/a3b588452f9d6be054f3fab828181269.php b/var/cache/dev/twig/a3/a3b588452f9d6be054f3fab828181269.php new file mode 100644 index 0000000..9eb79e5 --- /dev/null +++ b/var/cache/dev/twig/a3/a3b588452f9d6be054f3fab828181269.php @@ -0,0 +1,427 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("form_div_layout.html.twig", "tailwind_2_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."form_div_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'form_row' => [$this, 'block_form_row'], + 'widget_attributes' => [$this, 'block_widget_attributes'], + 'form_label' => [$this, 'block_form_label'], + 'form_help' => [$this, 'block_form_help'], + 'form_errors' => [$this, 'block_form_errors'], + 'choice_widget_expanded' => [$this, 'block_choice_widget_expanded'], + 'checkbox_row' => [$this, 'block_checkbox_row'], + 'checkbox_widget' => [$this, 'block_checkbox_widget'], + 'radio_widget' => [$this, 'block_radio_widget'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "tailwind_2_layout.html.twig")); + + // line 3 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 8 + yield from $this->unwrap()->yieldBlock('widget_attributes', $context, $blocks); + // line 13 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 18 + yield from $this->unwrap()->yieldBlock('form_help', $context, $blocks); + // line 23 + yield from $this->unwrap()->yieldBlock('form_errors', $context, $blocks); + // line 33 + yield from $this->unwrap()->yieldBlock('choice_widget_expanded', $context, $blocks); + // line 45 + yield from $this->unwrap()->yieldBlock('checkbox_row', $context, $blocks); + // line 61 + yield from $this->unwrap()->yieldBlock('checkbox_widget', $context, $blocks); + // line 66 + yield from $this->unwrap()->yieldBlock('radio_widget', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 3 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 4 + $context["row_attr"] = Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 4, $this->source); })()), ["class" => ((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 4)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 4), ((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 4, $this->source); })()), "mb-6")) : ("mb-6")))) : (((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 4, $this->source); })()), "mb-6")) : ("mb-6"))))]); + // line 5 + yield from $this->yieldParentBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 8 + /** + * @return iterable + */ + public function block_widget_attributes(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "widget_attributes")); + + // line 9 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 9, $this->source); })()), ["class" => ((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 9)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 9), ((array_key_exists("widget_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["widget_class"]) || array_key_exists("widget_class", $context) ? $context["widget_class"] : (function () { throw new RuntimeError('Variable "widget_class" does not exist.', 9, $this->source); })()), "mt-1 w-full")) : ("mt-1 w-full")))) : (((array_key_exists("widget_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["widget_class"]) || array_key_exists("widget_class", $context) ? $context["widget_class"] : (function () { throw new RuntimeError('Variable "widget_class" does not exist.', 9, $this->source); })()), "mt-1 w-full")) : ("mt-1 w-full")))) . (((isset($context["disabled"]) || array_key_exists("disabled", $context) ? $context["disabled"] : (function () { throw new RuntimeError('Variable "disabled" does not exist.', 9, $this->source); })())) ? ((" " . ((array_key_exists("widget_disabled_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["widget_disabled_class"]) || array_key_exists("widget_disabled_class", $context) ? $context["widget_disabled_class"] : (function () { throw new RuntimeError('Variable "widget_disabled_class" does not exist.', 9, $this->source); })()), "border-gray-300 text-gray-500")) : ("border-gray-300 text-gray-500")))) : (""))) . ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 9, $this->source); })()))) ? ((" " . ((array_key_exists("widget_errors_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["widget_errors_class"]) || array_key_exists("widget_errors_class", $context) ? $context["widget_errors_class"] : (function () { throw new RuntimeError('Variable "widget_errors_class" does not exist.', 9, $this->source); })()), "border-red-700")) : ("border-red-700")))) : ("")))]); + // line 10 + yield from $this->yieldParentBlock("widget_attributes", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 13 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 14 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 14, $this->source); })()), ["class" => ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 14)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 14), ((array_key_exists("label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["label_class"]) || array_key_exists("label_class", $context) ? $context["label_class"] : (function () { throw new RuntimeError('Variable "label_class" does not exist.', 14, $this->source); })()), "block text-gray-800")) : ("block text-gray-800")))) : (((array_key_exists("label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["label_class"]) || array_key_exists("label_class", $context) ? $context["label_class"] : (function () { throw new RuntimeError('Variable "label_class" does not exist.', 14, $this->source); })()), "block text-gray-800")) : ("block text-gray-800"))))]); + // line 15 + yield from $this->yieldParentBlock("form_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 18 + /** + * @return iterable + */ + public function block_form_help(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_help")); + + // line 19 + $context["help_attr"] = Twig\Extension\CoreExtension::merge((isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 19, $this->source); })()), ["class" => ((CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", true, true, false, 19)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", false, false, false, 19), ((array_key_exists("help_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["help_class"]) || array_key_exists("help_class", $context) ? $context["help_class"] : (function () { throw new RuntimeError('Variable "help_class" does not exist.', 19, $this->source); })()), "mt-1 text-gray-600")) : ("mt-1 text-gray-600")))) : (((array_key_exists("help_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["help_class"]) || array_key_exists("help_class", $context) ? $context["help_class"] : (function () { throw new RuntimeError('Variable "help_class" does not exist.', 19, $this->source); })()), "mt-1 text-gray-600")) : ("mt-1 text-gray-600"))))]); + // line 20 + yield from $this->yieldParentBlock("form_help", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 23 + /** + * @return iterable + */ + public function block_form_errors(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_errors")); + + // line 24 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 24, $this->source); })())) > 0)) { + // line 25 + yield "
    "; + // line 26 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 26, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 27 + yield "
  • env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("error_item_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["error_item_class"]) || array_key_exists("error_item_class", $context) ? $context["error_item_class"] : (function () { throw new RuntimeError('Variable "error_item_class" does not exist.', 27, $this->source); })()), "text-red-700")) : ("text-red-700")), "html", null, true); + yield "\">"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["error"], "message", [], "any", false, false, false, 27), "html", null, true); + yield "
  • "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['error'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 29 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 33 + /** + * @return iterable + */ + public function block_choice_widget_expanded(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_expanded")); + + // line 34 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 34, $this->source); })()), ["class" => ((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 34)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 34), "mt-2")) : ("mt-2"))]); + // line 35 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 36 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 36, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 37 + yield "
"; + // line 38 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget'); + // line 39 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'label', ["translation_domain" => (isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 39, $this->source); })())]); + // line 40 + yield "
+ "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 42 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 45 + /** + * @return iterable + */ + public function block_checkbox_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_row")); + + // line 46 + $context["row_attr"] = Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 46, $this->source); })()), ["class" => ((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 46)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 46), ((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 46, $this->source); })()), "mb-6")) : ("mb-6")))) : (((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 46, $this->source); })()), "mb-6")) : ("mb-6"))))]); + // line 47 + $context["widget_attr"] = []; + // line 48 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 48, $this->source); })()))) { + // line 49 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 49, $this->source); })()) . "_help")]]; + } + // line 51 + yield " (isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 51, $this->source); })())]; + if (!is_iterable($__internal_compile_1)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 51, $this->getSourceContext()); + } + $__internal_compile_1 = CoreExtension::toArray($__internal_compile_1); + $context = $__internal_compile_1 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_0; + yield ">"; + // line 52 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 52, $this->source); })()), 'errors'); + // line 53 + yield "
"; + // line 54 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 54, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 54, $this->source); })())); + // line 55 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 55, $this->source); })()), 'label'); + // line 56 + yield "
"; + // line 57 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 57, $this->source); })()), 'help'); + // line 58 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 61 + /** + * @return iterable + */ + public function block_checkbox_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_widget")); + + // line 62 + $context["widget_class"] = ((array_key_exists("widget_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["widget_class"]) || array_key_exists("widget_class", $context) ? $context["widget_class"] : (function () { throw new RuntimeError('Variable "widget_class" does not exist.', 62, $this->source); })()), "mr-2")) : ("mr-2")); + // line 63 + yield from $this->yieldParentBlock("checkbox_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 66 + /** + * @return iterable + */ + public function block_radio_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_widget")); + + // line 67 + $context["widget_class"] = ((array_key_exists("widget_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["widget_class"]) || array_key_exists("widget_class", $context) ? $context["widget_class"] : (function () { throw new RuntimeError('Variable "widget_class" does not exist.', 67, $this->source); })()), "mr-2")) : ("mr-2")); + // line 68 + yield from $this->yieldParentBlock("radio_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "tailwind_2_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 331 => 68, 329 => 67, 319 => 66, 311 => 63, 309 => 62, 299 => 61, 291 => 58, 289 => 57, 287 => 56, 285 => 55, 283 => 54, 281 => 53, 279 => 52, 267 => 51, 264 => 49, 262 => 48, 260 => 47, 258 => 46, 248 => 45, 240 => 42, 233 => 40, 231 => 39, 229 => 38, 227 => 37, 223 => 36, 219 => 35, 217 => 34, 207 => 33, 198 => 29, 188 => 27, 184 => 26, 182 => 25, 180 => 24, 170 => 23, 162 => 20, 160 => 19, 150 => 18, 142 => 15, 140 => 14, 130 => 13, 122 => 10, 120 => 9, 110 => 8, 102 => 5, 100 => 4, 90 => 3, 82 => 66, 80 => 61, 78 => 45, 76 => 33, 74 => 23, 72 => 18, 70 => 13, 68 => 8, 66 => 3, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use 'form_div_layout.html.twig' %} + +{%- block form_row -%} + {%- set row_attr = row_attr|merge({ class: row_attr.class|default(row_class|default('mb-6')) }) -%} + {{- parent() -}} +{%- endblock form_row -%} + +{%- block widget_attributes -%} + {%- set attr = attr|merge({ class: attr.class|default(widget_class|default('mt-1 w-full')) ~ (disabled ? ' ' ~ widget_disabled_class|default('border-gray-300 text-gray-500')) ~ (errors|length ? ' ' ~ widget_errors_class|default('border-red-700')) }) -%} + {{- parent() -}} +{%- endblock widget_attributes -%} + +{%- block form_label -%} + {%- set label_attr = label_attr|merge({ class: label_attr.class|default(label_class|default('block text-gray-800')) }) -%} + {{- parent() -}} +{%- endblock form_label -%} + +{%- block form_help -%} + {%- set help_attr = help_attr|merge({ class: help_attr.class|default(help_class|default('mt-1 text-gray-600')) }) -%} + {{- parent() -}} +{%- endblock form_help -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} +
    + {%- for error in errors -%} +
  • {{ error.message }}
  • + {%- endfor -%} +
+ {%- endif -%} +{%- endblock form_errors -%} + +{%- block choice_widget_expanded -%} + {%- set attr = attr|merge({ class: attr.class|default('mt-2') }) -%} +
+ {%- for child in form %} +
+ {{- form_widget(child) -}} + {{- form_label(child, null, { translation_domain: choice_translation_domain }) -}} +
+ {% endfor -%} +
+{%- endblock choice_widget_expanded -%} + +{%- block checkbox_row -%} + {%- set row_attr = row_attr|merge({ class: row_attr.class|default(row_class|default('mb-6')) }) -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + + {{- form_errors(form) -}} +
+ {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} +
+ {{- form_help(form) -}} + +{%- endblock checkbox_row -%} + +{%- block checkbox_widget -%} + {%- set widget_class = widget_class|default('mr-2') -%} + {{- parent() -}} +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + {%- set widget_class = widget_class|default('mr-2') -%} + {{- parent() -}} +{%- endblock radio_widget -%} +", "tailwind_2_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/a5/a5cdea2f4fadf3b64f66799e9fb7f01d.php b/var/cache/dev/twig/a5/a5cdea2f4fadf3b64f66799e9fb7f01d.php new file mode 100644 index 0000000..9829707 --- /dev/null +++ b/var/cache/dev/twig/a5/a5cdea2f4fadf3b64f66799e9fb7f01d.php @@ -0,0 +1,1451 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("bootstrap_base_layout.html.twig", "bootstrap_4_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."bootstrap_base_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'money_widget' => [$this, 'block_money_widget'], + 'datetime_widget' => [$this, 'block_datetime_widget'], + 'date_widget' => [$this, 'block_date_widget'], + 'time_widget' => [$this, 'block_time_widget'], + 'dateinterval_widget' => [$this, 'block_dateinterval_widget'], + 'percent_widget' => [$this, 'block_percent_widget'], + 'file_widget' => [$this, 'block_file_widget'], + 'form_widget_simple' => [$this, 'block_form_widget_simple'], + 'widget_attributes' => [$this, 'block_widget_attributes'], + 'button_widget' => [$this, 'block_button_widget'], + 'submit_widget' => [$this, 'block_submit_widget'], + 'checkbox_widget' => [$this, 'block_checkbox_widget'], + 'radio_widget' => [$this, 'block_radio_widget'], + 'choice_widget_collapsed' => [$this, 'block_choice_widget_collapsed'], + 'choice_widget_expanded' => [$this, 'block_choice_widget_expanded'], + 'form_label' => [$this, 'block_form_label'], + 'form_label_errors' => [$this, 'block_form_label_errors'], + 'checkbox_radio_label' => [$this, 'block_checkbox_radio_label'], + 'form_row' => [$this, 'block_form_row'], + 'form_errors' => [$this, 'block_form_errors'], + 'form_help' => [$this, 'block_form_help'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "bootstrap_4_layout.html.twig")); + + // line 2 + yield " +"; + // line 4 + yield " +"; + // line 5 + yield from $this->unwrap()->yieldBlock('money_widget', $context, $blocks); + // line 26 + yield " +"; + // line 27 + yield from $this->unwrap()->yieldBlock('datetime_widget', $context, $blocks); + // line 34 + yield " +"; + // line 35 + yield from $this->unwrap()->yieldBlock('date_widget', $context, $blocks); + // line 42 + yield " +"; + // line 43 + yield from $this->unwrap()->yieldBlock('time_widget', $context, $blocks); + // line 50 + yield " +"; + // line 51 + yield from $this->unwrap()->yieldBlock('dateinterval_widget', $context, $blocks); + // line 107 + yield " +"; + // line 108 + yield from $this->unwrap()->yieldBlock('percent_widget', $context, $blocks); + // line 120 + yield " +"; + // line 121 + yield from $this->unwrap()->yieldBlock('file_widget', $context, $blocks); + // line 136 + yield " +"; + // line 137 + yield from $this->unwrap()->yieldBlock('form_widget_simple', $context, $blocks); + // line 153 + yield " +"; + // line 154 + yield from $this->unwrap()->yieldBlock('widget_attributes', $context, $blocks); + // line 160 + yield " +"; + // line 161 + yield from $this->unwrap()->yieldBlock('button_widget', $context, $blocks); + // line 165 + yield " +"; + // line 166 + yield from $this->unwrap()->yieldBlock('submit_widget', $context, $blocks); + // line 170 + yield " +"; + // line 171 + yield from $this->unwrap()->yieldBlock('checkbox_widget', $context, $blocks); + // line 190 + yield " +"; + // line 191 + yield from $this->unwrap()->yieldBlock('radio_widget', $context, $blocks); + // line 205 + yield " +"; + // line 206 + yield from $this->unwrap()->yieldBlock('choice_widget_collapsed', $context, $blocks); + // line 210 + yield " +"; + // line 211 + yield from $this->unwrap()->yieldBlock('choice_widget_expanded', $context, $blocks); + // line 222 + yield " +"; + // line 224 + yield " +"; + // line 225 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 247 + yield " +"; + // line 248 + yield from $this->unwrap()->yieldBlock('checkbox_radio_label', $context, $blocks); + // line 278 + yield " +"; + // line 280 + yield " +"; + // line 281 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 295 + yield " +"; + // line 297 + yield " +"; + // line 298 + yield from $this->unwrap()->yieldBlock('form_errors', $context, $blocks); + // line 309 + yield " +"; + // line 311 + yield " +"; + // line 312 + yield from $this->unwrap()->yieldBlock('form_help', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_money_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "money_widget")); + + // line 6 + $context["prepend"] = !(is_string($__internal_compile_0 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 6, $this->source); })())) && is_string($__internal_compile_1 = "{{") && str_starts_with($__internal_compile_0, $__internal_compile_1)); + // line 7 + $context["append"] = !(is_string($__internal_compile_2 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 7, $this->source); })())) && is_string($__internal_compile_3 = "}}") && str_ends_with($__internal_compile_2, $__internal_compile_3)); + // line 8 + if (((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 8, $this->source); })()) || (isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 8, $this->source); })()))) { + // line 9 + yield "
env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("group_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["group_class"]) || array_key_exists("group_class", $context) ? $context["group_class"] : (function () { throw new RuntimeError('Variable "group_class" does not exist.', 9, $this->source); })()), "")) : ("")), "html", null, true); + yield "\">"; + // line 10 + if ((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 10, $this->source); })())) { + // line 11 + yield "
+ "; + // line 12 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 12, $this->source); })())); + yield " +
"; + } + // line 15 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 16 + if ((isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 16, $this->source); })())) { + // line 17 + yield "
+ "; + // line 18 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 18, $this->source); })())); + yield " +
"; + } + // line 21 + yield "
"; + } else { + // line 23 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 27 + /** + * @return iterable + */ + public function block_datetime_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_widget")); + + // line 28 + if ((((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 28, $this->source); })()) != "single_text") && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 28, $this->source); })()))) { + // line 29 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 29, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 29)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 29), "")) : ("")) . " form-control is-invalid"))]); + // line 30 + $context["valid"] = true; + } + // line 32 + yield from $this->yieldParentBlock("datetime_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 35 + /** + * @return iterable + */ + public function block_date_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_widget")); + + // line 36 + if ((((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 36, $this->source); })()) != "single_text") && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 36, $this->source); })()))) { + // line 37 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 37, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 37)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 37), "")) : ("")) . " form-control is-invalid"))]); + // line 38 + $context["valid"] = true; + } + // line 40 + yield from $this->yieldParentBlock("date_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 43 + /** + * @return iterable + */ + public function block_time_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_widget")); + + // line 44 + if ((((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 44, $this->source); })()) != "single_text") && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 44, $this->source); })()))) { + // line 45 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 45, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 45)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 45), "")) : ("")) . " form-control is-invalid"))]); + // line 46 + $context["valid"] = true; + } + // line 48 + yield from $this->yieldParentBlock("time_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 51 + /** + * @return iterable + */ + public function block_dateinterval_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "dateinterval_widget")); + + // line 52 + if ((((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 52, $this->source); })()) != "single_text") && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 52, $this->source); })()))) { + // line 53 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 53, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 53)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 53), "")) : ("")) . " form-control is-invalid"))]); + // line 54 + $context["valid"] = true; + } + // line 56 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 56, $this->source); })()) == "single_text")) { + // line 57 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 59 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 59, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 59)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 59), "")) : ("")) . " form-inline"))]); + // line 60 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 61 + if ((isset($context["with_years"]) || array_key_exists("with_years", $context) ? $context["with_years"] : (function () { throw new RuntimeError('Variable "with_years" does not exist.', 61, $this->source); })())) { + // line 62 + yield "
+ "; + // line 63 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 63, $this->source); })()), "years", [], "any", false, false, false, 63), 'label'); + yield " + "; + // line 64 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 64, $this->source); })()), "years", [], "any", false, false, false, 64), 'widget'); + yield " +
"; + } + // line 67 + if ((isset($context["with_months"]) || array_key_exists("with_months", $context) ? $context["with_months"] : (function () { throw new RuntimeError('Variable "with_months" does not exist.', 67, $this->source); })())) { + // line 68 + yield "
+ "; + // line 69 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 69, $this->source); })()), "months", [], "any", false, false, false, 69), 'label'); + yield " + "; + // line 70 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 70, $this->source); })()), "months", [], "any", false, false, false, 70), 'widget'); + yield " +
"; + } + // line 73 + if ((isset($context["with_weeks"]) || array_key_exists("with_weeks", $context) ? $context["with_weeks"] : (function () { throw new RuntimeError('Variable "with_weeks" does not exist.', 73, $this->source); })())) { + // line 74 + yield "
+ "; + // line 75 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 75, $this->source); })()), "weeks", [], "any", false, false, false, 75), 'label'); + yield " + "; + // line 76 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 76, $this->source); })()), "weeks", [], "any", false, false, false, 76), 'widget'); + yield " +
"; + } + // line 79 + if ((isset($context["with_days"]) || array_key_exists("with_days", $context) ? $context["with_days"] : (function () { throw new RuntimeError('Variable "with_days" does not exist.', 79, $this->source); })())) { + // line 80 + yield "
+ "; + // line 81 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 81, $this->source); })()), "days", [], "any", false, false, false, 81), 'label'); + yield " + "; + // line 82 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 82, $this->source); })()), "days", [], "any", false, false, false, 82), 'widget'); + yield " +
"; + } + // line 85 + if ((isset($context["with_hours"]) || array_key_exists("with_hours", $context) ? $context["with_hours"] : (function () { throw new RuntimeError('Variable "with_hours" does not exist.', 85, $this->source); })())) { + // line 86 + yield "
+ "; + // line 87 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 87, $this->source); })()), "hours", [], "any", false, false, false, 87), 'label'); + yield " + "; + // line 88 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 88, $this->source); })()), "hours", [], "any", false, false, false, 88), 'widget'); + yield " +
"; + } + // line 91 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 91, $this->source); })())) { + // line 92 + yield "
+ "; + // line 93 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 93, $this->source); })()), "minutes", [], "any", false, false, false, 93), 'label'); + yield " + "; + // line 94 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 94, $this->source); })()), "minutes", [], "any", false, false, false, 94), 'widget'); + yield " +
"; + } + // line 97 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 97, $this->source); })())) { + // line 98 + yield "
+ "; + // line 99 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 99, $this->source); })()), "seconds", [], "any", false, false, false, 99), 'label'); + yield " + "; + // line 100 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 100, $this->source); })()), "seconds", [], "any", false, false, false, 100), 'widget'); + yield " +
"; + } + // line 103 + if ((isset($context["with_invert"]) || array_key_exists("with_invert", $context) ? $context["with_invert"] : (function () { throw new RuntimeError('Variable "with_invert" does not exist.', 103, $this->source); })())) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 103, $this->source); })()), "invert", [], "any", false, false, false, 103), 'widget'); + } + // line 104 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 108 + /** + * @return iterable + */ + public function block_percent_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "percent_widget")); + + // line 109 + if ((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 109, $this->source); })())) { + // line 110 + yield "
"; + // line 111 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 112 + yield "
+ "; + // line 113 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("symbol", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 113, $this->source); })()), "%")) : ("%")), "html", null, true); + yield " +
+
"; + } else { + // line 117 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 121 + /** + * @return iterable + */ + public function block_file_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "file_widget")); + + // line 122 + yield "<"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 122, $this->source); })()), "div")) : ("div")), "html", null, true); + yield " class=\"custom-file\">"; + // line 123 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 123, $this->source); })()), "file")) : ("file")); + // line 124 + $context["input_lang"] = "en"; + // line 125 + if ((array_key_exists("app", $context) && CoreExtension::getAttribute($this->env, $this->source, ($context["app"] ?? null), "request", [], "any", true, true, false, 125))) { + $context["input_lang"] = CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 125, $this->source); })()), "request", [], "any", false, false, false, 125), "locale", [], "any", false, false, false, 125); + } + // line 126 + $context["attr"] = Twig\Extension\CoreExtension::merge(["lang" => (isset($context["input_lang"]) || array_key_exists("input_lang", $context) ? $context["input_lang"] : (function () { throw new RuntimeError('Variable "input_lang" does not exist.', 126, $this->source); })())], (isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 126, $this->source); })())); + // line 127 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 128 + $context["label_attr"] = Twig\Extension\CoreExtension::filter($this->env, Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 128, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 128)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 128), "")) : ("")) . " custom-file-label"))]), function ($__value__, $__key__) use ($context, $macros) { $context["value"] = $__value__; $context["key"] = $__key__; return ((isset($context["key"]) || array_key_exists("key", $context) ? $context["key"] : (function () { throw new RuntimeError('Variable "key" does not exist.', 128, $this->source); })()) != "id"); }); + // line 129 + yield " + env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 134, $this->source); })()), "div")) : ("div")), "html", null, true); + yield "> +"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 137 + /** + * @return iterable + */ + public function block_form_widget_simple(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget_simple")); + + // line 138 + if (( !array_key_exists("type", $context) || ((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 138, $this->source); })()) != "hidden"))) { + // line 139 + $context["className"] = " form-control"; + // line 140 + if ((((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 140, $this->source); })()), "")) : ("")) == "file")) { + // line 141 + $context["className"] = " custom-file-input"; + } elseif (((( // line 142 +array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 142, $this->source); })()), "")) : ("")) == "range")) { + // line 143 + $context["className"] = " form-control-range"; + } + // line 145 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 145, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 145)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 145), "")) : ("")) . (isset($context["className"]) || array_key_exists("className", $context) ? $context["className"] : (function () { throw new RuntimeError('Variable "className" does not exist.', 145, $this->source); })())))]); + } + // line 147 + if ((array_key_exists("type", $context) && (((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 147, $this->source); })()) == "range") || ((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 147, $this->source); })()) == "color")))) { + // line 148 + yield " "; + // line 149 + $context["required"] = false; + } + // line 151 + yield from $this->yieldParentBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 154 + /** + * @return iterable + */ + public function block_widget_attributes(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "widget_attributes")); + + // line 155 + if ( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 155, $this->source); })())) { + // line 156 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 156, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 156)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 156), "")) : ("")) . " is-invalid"))]); + } + // line 158 + yield from $this->yieldParentBlock("widget_attributes", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 161 + /** + * @return iterable + */ + public function block_button_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_widget")); + + // line 162 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 162, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 162)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 162), "btn-secondary")) : ("btn-secondary")) . " btn"))]); + // line 163 + yield from $this->yieldParentBlock("button_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 166 + /** + * @return iterable + */ + public function block_submit_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "submit_widget")); + + // line 167 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 167, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 167)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 167), "btn-primary")) : ("btn-primary")))]); + // line 168 + yield from $this->yieldParentBlock("submit_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 171 + /** + * @return iterable + */ + public function block_checkbox_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_widget")); + + // line 172 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 172, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 172)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 172), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 172)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 172), "")) : ("")))); + // line 173 + if (CoreExtension::inFilter("checkbox-custom", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 173, $this->source); })()))) { + // line 174 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 174, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 174)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 174), "")) : ("")) . " custom-control-input"))]); + // line 175 + yield "
source); })()))) ? (" custom-control-inline") : ("")); + yield "\">"; + // line 176 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 176, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + // line 177 + yield "
"; + } elseif (CoreExtension::inFilter("switch-custom", // line 178 +(isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 178, $this->source); })()))) { + // line 179 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 179, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 179)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 179), "")) : ("")) . " custom-control-input"))]); + // line 180 + yield "
source); })()))) ? (" custom-control-inline") : ("")); + yield "\">"; + // line 181 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 181, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + // line 182 + yield "
"; + } else { + // line 184 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 184, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 184)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 184), "")) : ("")) . " form-check-input"))]); + // line 185 + yield "
source); })()))) ? (" form-check-inline") : ("")); + yield "\">"; + // line 186 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 186, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + // line 187 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 191 + /** + * @return iterable + */ + public function block_radio_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_widget")); + + // line 192 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 192, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 192)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 192), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 192)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 192), "")) : ("")))); + // line 193 + if (CoreExtension::inFilter("radio-custom", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 193, $this->source); })()))) { + // line 194 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 194, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 194)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 194), "")) : ("")) . " custom-control-input"))]); + // line 195 + yield "
source); })()))) ? (" custom-control-inline") : ("")); + yield "\">"; + // line 196 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 196, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("radio_widget", $context, $blocks)]); + // line 197 + yield "
"; + } else { + // line 199 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 199, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 199)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 199), "")) : ("")) . " form-check-input"))]); + // line 200 + yield "
source); })()))) ? (" form-check-inline") : ("")); + yield "\">"; + // line 201 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 201, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("radio_widget", $context, $blocks)]); + // line 202 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 206 + /** + * @return iterable + */ + public function block_choice_widget_collapsed(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_collapsed")); + + // line 207 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 207, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 207)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 207), "")) : ("")) . " form-control"))]); + // line 208 + yield from $this->yieldParentBlock("choice_widget_collapsed", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 211 + /** + * @return iterable + */ + public function block_choice_widget_expanded(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_expanded")); + + // line 212 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 213 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 213, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 214 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget', ["parent_label_class" => ((CoreExtension::getAttribute($this->env, $this->source, // line 215 +($context["label_attr"] ?? null), "class", [], "any", true, true, false, 215)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 215), "")) : ("")), "translation_domain" => // line 216 +(isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 216, $this->source); })()), "valid" => // line 217 +(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 217, $this->source); })())]); + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 220 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 225 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 226 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 226, $this->source); })()) === false)) { + // line 227 + if ((array_key_exists("compound", $context) && (isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 227, $this->source); })()))) { + // line 228 + $context["element"] = "legend"; + // line 229 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 229, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 229)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 229), "")) : ("")) . " col-form-label"))]); + } else { + // line 231 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 231, $this->source); })()), ["for" => (isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 231, $this->source); })())]); + } + // line 233 + if ((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 233, $this->source); })())) { + // line 234 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 234, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 234)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 234), "")) : ("")) . " required"))]); + } + // line 236 + yield "<"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 236, $this->source); })()), "label")) : ("label")), "html", null, true); + if ((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 236, $this->source); })())) { + $__internal_compile_6 = $context; + $__internal_compile_7 = ["attr" => (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 236, $this->source); })())]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 236, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + } + yield ">"; + // line 237 + yield from $this->unwrap()->yieldBlock("form_label_content", $context, $blocks); + // line 238 + yield from $this->unwrap()->yieldBlock('form_label_errors', $context, $blocks); + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 238, $this->source); })()), "label")) : ("label")), "html", null, true); + yield ">"; + } else { + // line 240 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 240, $this->source); })())) > 0)) { + // line 241 + yield "
env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 241, $this->source); })()), "html", null, true); + yield "_errors\" class=\"mb-2\">"; + // line 242 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 242, $this->source); })()), 'errors'); + // line 243 + yield "
"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 238 + /** + * @return iterable + */ + public function block_form_label_errors(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label_errors")); + + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 238, $this->source); })()), 'errors'); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 248 + /** + * @return iterable + */ + public function block_checkbox_radio_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_radio_label")); + + // line 250 + if (array_key_exists("widget", $context)) { + // line 251 + $context["is_parent_custom"] = (array_key_exists("parent_label_class", $context) && ((CoreExtension::inFilter("checkbox-custom", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 251, $this->source); })())) || CoreExtension::inFilter("radio-custom", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 251, $this->source); })()))) || CoreExtension::inFilter("switch-custom", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 251, $this->source); })())))); + // line 252 + yield " "; + $context["is_custom"] = (CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 252) && ((CoreExtension::inFilter("checkbox-custom", CoreExtension::getAttribute($this->env, $this->source, (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 252, $this->source); })()), "class", [], "any", false, false, false, 252)) || CoreExtension::inFilter("radio-custom", CoreExtension::getAttribute($this->env, $this->source, (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 252, $this->source); })()), "class", [], "any", false, false, false, 252))) || CoreExtension::inFilter("switch-custom", CoreExtension::getAttribute($this->env, $this->source, (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 252, $this->source); })()), "class", [], "any", false, false, false, 252)))); + // line 253 + if (((isset($context["is_parent_custom"]) || array_key_exists("is_parent_custom", $context) ? $context["is_parent_custom"] : (function () { throw new RuntimeError('Variable "is_parent_custom" does not exist.', 253, $this->source); })()) || (isset($context["is_custom"]) || array_key_exists("is_custom", $context) ? $context["is_custom"] : (function () { throw new RuntimeError('Variable "is_custom" does not exist.', 253, $this->source); })()))) { + // line 254 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 254, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 254)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 254), "")) : ("")) . " custom-control-label"))]); + } else { + // line 256 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 256, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 256)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 256), "")) : ("")) . " form-check-label"))]); + } + // line 258 + if ( !(isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 258, $this->source); })())) { + // line 259 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 259, $this->source); })()), ["for" => (isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 259, $this->source); })())]); + } + // line 261 + if ((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 261, $this->source); })())) { + // line 262 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 262, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 262)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 262), "")) : ("")) . " required"))]); + } + // line 264 + if (array_key_exists("parent_label_class", $context)) { + // line 265 + $context["embed_label_classes"] = Twig\Extension\CoreExtension::filter($this->env, Twig\Extension\CoreExtension::split($this->env->getCharset(), (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 265, $this->source); })()), " "), function ($__class__) use ($context, $macros) { $context["class"] = $__class__; return CoreExtension::inFilter((isset($context["class"]) || array_key_exists("class", $context) ? $context["class"] : (function () { throw new RuntimeError('Variable "class" does not exist.', 265, $this->source); })()), ["checkbox-inline", "radio-inline"]); }); + // line 266 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 266, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 266)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 266), "")) : ("")) . " ") . Twig\Extension\CoreExtension::join((isset($context["embed_label_classes"]) || array_key_exists("embed_label_classes", $context) ? $context["embed_label_classes"] : (function () { throw new RuntimeError('Variable "embed_label_classes" does not exist.', 266, $this->source); })()), " ")))]); + } + // line 268 + yield " + "; + // line 269 + yield (isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 269, $this->source); })()); + yield " + (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 270, $this->source); })())]; + if (!is_iterable($__internal_compile_9)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 270, $this->getSourceContext()); + } + $__internal_compile_9 = CoreExtension::toArray($__internal_compile_9); + $context = $__internal_compile_9 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_8; + yield ">"; + // line 271 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 271, $this->source); })()) === false)) { + // line 272 + yield from $this->unwrap()->yieldBlock("form_label_content", $context, $blocks); + } + // line 274 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 274, $this->source); })()), 'errors'); + // line 275 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 281 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 282 + if ((array_key_exists("compound", $context) && (isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 282, $this->source); })()))) { + // line 283 + $context["element"] = "fieldset"; + } + // line 285 + $context["widget_attr"] = []; + // line 286 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 286, $this->source); })()))) { + // line 287 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 287, $this->source); })()) . "_help")]]; + } + // line 289 + yield "<"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 289, $this->source); })()), "div")) : ("div")), "html", null, true); + $__internal_compile_10 = $context; + $__internal_compile_11 = ["attr" => Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 289, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 289)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 289), "")) : ("")) . " form-group"))])]; + if (!is_iterable($__internal_compile_11)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 289, $this->getSourceContext()); + } + $__internal_compile_11 = CoreExtension::toArray($__internal_compile_11); + $context = $__internal_compile_11 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_10; + yield ">"; + // line 290 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 290, $this->source); })()), 'label'); + // line 291 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 291, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 291, $this->source); })())); + // line 292 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 292, $this->source); })()), 'help'); + // line 293 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 293, $this->source); })()), "div")) : ("div")), "html", null, true); + yield ">"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 298 + /** + * @return iterable + */ + public function block_form_errors(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_errors")); + + // line 299 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 299, $this->source); })())) > 0)) { + // line 300 + yield "source); })()))) { + yield "invalid-feedback"; + } else { + yield "alert alert-danger"; + } + yield " d-block\">"; + // line 301 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 301, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 302 + yield " + "; + // line 303 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("Error", [], "validators"), "html", null, true); + yield " "; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["error"], "message", [], "any", false, false, false, 303), "html", null, true); + yield " + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['error'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 306 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 312 + /** + * @return iterable + */ + public function block_form_help(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_help")); + + // line 313 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 313, $this->source); })()))) { + // line 314 + $context["help_attr"] = Twig\Extension\CoreExtension::merge((isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 314, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", true, true, false, 314)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", false, false, false, 314), "")) : ("")) . " form-text text-muted"))]); + // line 315 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 315, $this->source); })()), "html", null, true); + yield "_help\""; + $__internal_compile_12 = $context; + $__internal_compile_13 = ["attr" => (isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 315, $this->source); })())]; + if (!is_iterable($__internal_compile_13)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 315, $this->getSourceContext()); + } + $__internal_compile_13 = CoreExtension::toArray($__internal_compile_13); + $context = $__internal_compile_13 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_12; + yield ">"; + // line 316 + yield from $this->unwrap()->yieldBlock("form_help_content", $context, $blocks); + // line 317 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "bootstrap_4_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 1104 => 317, 1102 => 316, 1088 => 315, 1086 => 314, 1084 => 313, 1074 => 312, 1065 => 306, 1055 => 303, 1052 => 302, 1048 => 301, 1040 => 300, 1038 => 299, 1028 => 298, 1018 => 293, 1016 => 292, 1014 => 291, 1012 => 290, 999 => 289, 996 => 287, 994 => 286, 992 => 285, 989 => 283, 987 => 282, 977 => 281, 968 => 275, 966 => 274, 963 => 272, 961 => 271, 950 => 270, 946 => 269, 943 => 268, 940 => 266, 938 => 265, 936 => 264, 933 => 262, 931 => 261, 928 => 259, 926 => 258, 923 => 256, 920 => 254, 918 => 253, 915 => 252, 913 => 251, 911 => 250, 901 => 248, 884 => 238, 874 => 243, 872 => 242, 868 => 241, 866 => 240, 860 => 238, 858 => 237, 843 => 236, 840 => 234, 838 => 233, 835 => 231, 832 => 229, 830 => 228, 828 => 227, 826 => 226, 816 => 225, 808 => 220, 802 => 217, 801 => 216, 800 => 215, 799 => 214, 795 => 213, 791 => 212, 781 => 211, 773 => 208, 771 => 207, 761 => 206, 752 => 202, 750 => 201, 746 => 200, 744 => 199, 741 => 197, 739 => 196, 735 => 195, 733 => 194, 731 => 193, 729 => 192, 719 => 191, 710 => 187, 708 => 186, 704 => 185, 702 => 184, 699 => 182, 697 => 181, 693 => 180, 691 => 179, 689 => 178, 687 => 177, 685 => 176, 681 => 175, 679 => 174, 677 => 173, 675 => 172, 665 => 171, 657 => 168, 655 => 167, 645 => 166, 637 => 163, 635 => 162, 625 => 161, 617 => 158, 614 => 156, 612 => 155, 602 => 154, 594 => 151, 591 => 149, 589 => 148, 587 => 147, 584 => 145, 581 => 143, 579 => 142, 577 => 141, 575 => 140, 573 => 139, 571 => 138, 561 => 137, 551 => 134, 548 => 133, 545 => 131, 543 => 130, 529 => 129, 527 => 128, 525 => 127, 523 => 126, 519 => 125, 517 => 124, 515 => 123, 511 => 122, 501 => 121, 492 => 117, 486 => 113, 483 => 112, 481 => 111, 479 => 110, 477 => 109, 467 => 108, 458 => 104, 454 => 103, 449 => 100, 445 => 99, 442 => 98, 440 => 97, 435 => 94, 431 => 93, 428 => 92, 426 => 91, 421 => 88, 417 => 87, 414 => 86, 412 => 85, 407 => 82, 403 => 81, 400 => 80, 398 => 79, 393 => 76, 389 => 75, 386 => 74, 384 => 73, 379 => 70, 375 => 69, 372 => 68, 370 => 67, 365 => 64, 361 => 63, 358 => 62, 356 => 61, 352 => 60, 350 => 59, 347 => 57, 345 => 56, 342 => 54, 340 => 53, 338 => 52, 328 => 51, 320 => 48, 317 => 46, 315 => 45, 313 => 44, 303 => 43, 295 => 40, 292 => 38, 290 => 37, 288 => 36, 278 => 35, 270 => 32, 267 => 30, 265 => 29, 263 => 28, 253 => 27, 244 => 23, 241 => 21, 236 => 18, 233 => 17, 231 => 16, 229 => 15, 224 => 12, 221 => 11, 219 => 10, 215 => 9, 213 => 8, 211 => 7, 209 => 6, 199 => 5, 191 => 312, 188 => 311, 185 => 309, 183 => 298, 180 => 297, 177 => 295, 175 => 281, 172 => 280, 169 => 278, 167 => 248, 164 => 247, 162 => 225, 159 => 224, 156 => 222, 154 => 211, 151 => 210, 149 => 206, 146 => 205, 144 => 191, 141 => 190, 139 => 171, 136 => 170, 134 => 166, 131 => 165, 129 => 161, 126 => 160, 124 => 154, 121 => 153, 119 => 137, 116 => 136, 114 => 121, 111 => 120, 109 => 108, 106 => 107, 104 => 51, 101 => 50, 99 => 43, 96 => 42, 94 => 35, 91 => 34, 89 => 27, 86 => 26, 84 => 5, 81 => 4, 78 => 2, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"bootstrap_base_layout.html.twig\" %} + +{# Widgets #} + +{% block money_widget -%} + {%- set prepend = not (money_pattern starts with '{{') -%} + {%- set append = not (money_pattern ends with '}}') -%} + {%- if prepend or append -%} +
+ {%- if prepend -%} +
+ {{ money_pattern|form_encode_currency }} +
+ {%- endif -%} + {{- block('form_widget_simple') -}} + {%- if append -%} +
+ {{ money_pattern|form_encode_currency }} +
+ {%- endif -%} +
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock money_widget %} + +{% block datetime_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {{- parent() -}} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {{- parent() -}} +{%- endblock date_widget %} + +{% block time_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {{- parent() -}} +{%- endblock time_widget %} + +{% block dateinterval_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
+ {%- if with_years -%} +
+ {{ form_label(form.years) }} + {{ form_widget(form.years) }} +
+ {%- endif -%} + {%- if with_months -%} +
+ {{ form_label(form.months) }} + {{ form_widget(form.months) }} +
+ {%- endif -%} + {%- if with_weeks -%} +
+ {{ form_label(form.weeks) }} + {{ form_widget(form.weeks) }} +
+ {%- endif -%} + {%- if with_days -%} +
+ {{ form_label(form.days) }} + {{ form_widget(form.days) }} +
+ {%- endif -%} + {%- if with_hours -%} +
+ {{ form_label(form.hours) }} + {{ form_widget(form.hours) }} +
+ {%- endif -%} + {%- if with_minutes -%} +
+ {{ form_label(form.minutes) }} + {{ form_widget(form.minutes) }} +
+ {%- endif -%} + {%- if with_seconds -%} +
+ {{ form_label(form.seconds) }} + {{ form_widget(form.seconds) }} +
+ {%- endif -%} + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
+ {%- endif -%} +{%- endblock dateinterval_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
+ {{- block('form_widget_simple') -}} +
+ {{ symbol|default('%') }} +
+
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block file_widget -%} + <{{ element|default('div') }} class=\"custom-file\"> + {%- set type = type|default('file') -%} + {%- set input_lang = 'en' -%} + {% if app is defined and app.request is defined %}{%- set input_lang = app.request.locale -%}{%- endif -%} + {%- set attr = {lang: input_lang} | merge(attr) -%} + {{- block('form_widget_simple') -}} + {%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim })|filter((value, key) => key != 'id') -%} + + +{% endblock %} + +{% block form_widget_simple -%} + {%- if type is not defined or type != 'hidden' -%} + {%- set className = ' form-control' -%} + {%- if type|default('') == 'file' -%} + {%- set className = ' custom-file-input' -%} + {%- elseif type|default('') == 'range' -%} + {%- set className = ' form-control-range' -%} + {%- endif -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ className)|trim}) -%} + {%- endif -%} + {%- if type is defined and (type == 'range' or type == 'color') %} + {# Attribute \"required\" is not supported #} + {%- set required = false -%} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block widget_attributes -%} + {%- if not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %} + {%- endif -%} + {{ parent() }} +{%- endblock widget_attributes %} + +{% block button_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%} + {{- parent() -}} +{%- endblock button_widget %} + +{% block submit_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-primary'))|trim}) -%} + {{- parent() -}} +{%- endblock submit_widget %} + +{% block checkbox_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'checkbox-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
+ {%- elseif 'switch-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
+ {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
+ {%- endif -%} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'radio-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
+ {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
+ {%- endif -%} +{%- endblock radio_widget %} + +{% block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed %} + +{% block choice_widget_expanded -%} +
+ {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + valid: valid, + }) -}} + {% endfor -%} +
+{%- endblock choice_widget_expanded %} + +{# Labels #} + +{% block form_label -%} + {% if label is not same as(false) -%} + {%- if compound is defined and compound -%} + {%- set element = 'legend' -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- else -%} + {%- set label_attr = label_attr|merge({for: id}) -%} + {%- endif -%} + {% if required -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {%- endif -%} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> + {{- block('form_label_content') -}} + {% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %} + {%- else -%} + {%- if errors|length > 0 -%} +
+ {{- form_errors(form) -}} +
+ {%- endif -%} + {%- endif -%} +{%- endblock form_label %} + +{% block checkbox_radio_label -%} + {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} + {%- if widget is defined -%} + {% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class or 'switch-custom' in parent_label_class) %} + {% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class or 'switch-custom' in label_attr.class) %} + {%- if is_parent_custom or is_custom -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' custom-control-label')|trim}) -%} + {%- else %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%} + {%- endif %} + {%- if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {%- if required -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} + {%- endif -%} + {%- if parent_label_class is defined -%} + {% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%} + {% endif %} + + {{ widget|raw }} + + {%- if label is not same as(false) -%} + {{- block('form_label_content') -}} + {%- endif -%} + {{- form_errors(form) -}} + + {%- endif -%} +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} + {%- if compound is defined and compound -%} + {%- set element = 'fieldset' -%} + {%- endif -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + <{{ element|default('div') }}{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group')|trim})} %}{{ block('attributes') }}{% endwith %}> + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + +{%- endblock form_row %} + +{# Errors #} + +{% block form_errors -%} + {%- if errors|length > 0 -%} + + {%- for error in errors -%} + + {{ 'Error'|trans({}, 'validators') }} {{ error.message }} + + {%- endfor -%} + + {%- endif %} +{%- endblock form_errors %} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' form-text text-muted')|trim}) -%} + + {{- block('form_help_content') -}} + + {%- endif -%} +{%- endblock form_help %} +", "bootstrap_4_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/a8/a8611b7cea5b010e07b5fc54497d1bc2.php b/var/cache/dev/twig/a8/a8611b7cea5b010e07b5fc54497d1bc2.php new file mode 100644 index 0000000..728aed9 --- /dev/null +++ b/var/cache/dev/twig/a8/a8611b7cea5b010e07b5fc54497d1bc2.php @@ -0,0 +1,202 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + 'lead' => [$this, 'block_lead'], + 'content' => [$this, 'block_content'], + 'action' => [$this, 'block_action'], + 'exception' => [$this, 'block_exception'], + ]; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@email/zurb_2/notification/body.txt.twig")); + + // line 1 + yield from $this->unwrap()->yieldBlock('lead', $context, $blocks); + // line 4 + yield " +"; + // line 5 + yield from $this->unwrap()->yieldBlock('content', $context, $blocks); + // line 8 + yield " +"; + // line 9 + yield from $this->unwrap()->yieldBlock('action', $context, $blocks); + // line 14 + yield " +"; + // line 15 + yield from $this->unwrap()->yieldBlock('exception', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 1 + /** + * @return iterable + */ + public function block_lead(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "lead")); + + // line 2 + yield CoreExtension::getAttribute($this->env, $this->source, (isset($context["email"]) || array_key_exists("email", $context) ? $context["email"] : (function () { throw new RuntimeError('Variable "email" does not exist.', 2, $this->source); })()), "subject", [], "any", false, false, false, 2); + yield " +"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "content")); + + // line 6 + yield (isset($context["content"]) || array_key_exists("content", $context) ? $context["content"] : (function () { throw new RuntimeError('Variable "content" does not exist.', 6, $this->source); })()); + yield " +"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 9 + /** + * @return iterable + */ + public function block_action(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "action")); + + // line 10 + if ((isset($context["action_url"]) || array_key_exists("action_url", $context) ? $context["action_url"] : (function () { throw new RuntimeError('Variable "action_url" does not exist.', 10, $this->source); })())) { + // line 11 + yield (isset($context["action_text"]) || array_key_exists("action_text", $context) ? $context["action_text"] : (function () { throw new RuntimeError('Variable "action_text" does not exist.', 11, $this->source); })()); + yield ": "; + yield (isset($context["action_url"]) || array_key_exists("action_url", $context) ? $context["action_url"] : (function () { throw new RuntimeError('Variable "action_url" does not exist.', 11, $this->source); })()); + yield " +"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 15 + /** + * @return iterable + */ + public function block_exception(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "exception")); + + // line 16 + if ((isset($context["exception"]) || array_key_exists("exception", $context) ? $context["exception"] : (function () { throw new RuntimeError('Variable "exception" does not exist.', 16, $this->source); })())) { + // line 17 + yield "Exception stack trace attached. +"; + // line 18 + yield (isset($context["exception"]) || array_key_exists("exception", $context) ? $context["exception"] : (function () { throw new RuntimeError('Variable "exception" does not exist.', 18, $this->source); })()); + yield " +"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@email/zurb_2/notification/body.txt.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 152 => 18, 149 => 17, 147 => 16, 137 => 15, 124 => 11, 122 => 10, 112 => 9, 102 => 6, 92 => 5, 82 => 2, 72 => 1, 64 => 15, 61 => 14, 59 => 9, 56 => 8, 54 => 5, 51 => 4, 49 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% block lead %} +{{ email.subject }} +{% endblock %} + +{% block content %} +{{ content }} +{% endblock %} + +{% block action %} +{% if action_url %} +{{ action_text }}: {{ action_url }} +{% endif %} +{% endblock %} + +{% block exception %} +{% if exception %} +Exception stack trace attached. +{{ exception }} +{% endif %} +{% endblock %} +", "@email/zurb_2/notification/body.txt.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.txt.twig"); + } +} diff --git a/var/cache/dev/twig/ab/ab8957465ff96bf02f5d139059bbb23c.php b/var/cache/dev/twig/ab/ab8957465ff96bf02f5d139059bbb23c.php new file mode 100644 index 0000000..7118903 --- /dev/null +++ b/var/cache/dev/twig/ab/ab8957465ff96bf02f5d139059bbb23c.php @@ -0,0 +1,1533 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + 'form_widget_simple' => [$this, 'block_form_widget_simple'], + 'textarea_widget' => [$this, 'block_textarea_widget'], + 'button_widget' => [$this, 'block_button_widget'], + 'money_widget' => [$this, 'block_money_widget'], + 'percent_widget' => [$this, 'block_percent_widget'], + 'datetime_widget' => [$this, 'block_datetime_widget'], + 'date_widget' => [$this, 'block_date_widget'], + 'time_widget' => [$this, 'block_time_widget'], + 'choice_widget_collapsed' => [$this, 'block_choice_widget_collapsed'], + 'choice_widget_expanded' => [$this, 'block_choice_widget_expanded'], + 'checkbox_widget' => [$this, 'block_checkbox_widget'], + 'radio_widget' => [$this, 'block_radio_widget'], + 'form_label' => [$this, 'block_form_label'], + 'choice_label' => [$this, 'block_choice_label'], + 'checkbox_label' => [$this, 'block_checkbox_label'], + 'radio_label' => [$this, 'block_radio_label'], + 'checkbox_radio_label' => [$this, 'block_checkbox_radio_label'], + 'form_row' => [$this, 'block_form_row'], + 'choice_row' => [$this, 'block_choice_row'], + 'date_row' => [$this, 'block_date_row'], + 'time_row' => [$this, 'block_time_row'], + 'datetime_row' => [$this, 'block_datetime_row'], + 'checkbox_row' => [$this, 'block_checkbox_row'], + 'radio_row' => [$this, 'block_radio_row'], + 'form_errors' => [$this, 'block_form_errors'], + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "form_div_layout.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "foundation_5_layout.html.twig")); + + $this->parent = $this->loadTemplate("form_div_layout.html.twig", "foundation_5_layout.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + // line 6 + /** + * @return iterable + */ + public function block_form_widget_simple(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget_simple")); + + // line 7 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 7, $this->source); })())) > 0)) { + // line 8 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 8, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 8)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 8), "")) : ("")) . " error"))]); + // line 9 + yield " "; + } + // line 10 + yield from $this->yieldParentBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 13 + /** + * @return iterable + */ + public function block_textarea_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "textarea_widget")); + + // line 14 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 14, $this->source); })())) > 0)) { + // line 15 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 15, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 15)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 15), "")) : ("")) . " error"))]); + // line 16 + yield " "; + } + // line 17 + yield from $this->yieldParentBlock("textarea_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 20 + /** + * @return iterable + */ + public function block_button_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_widget")); + + // line 21 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 21, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 21)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 21), "")) : ("")) . " button"))]); + // line 22 + yield from $this->yieldParentBlock("button_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 25 + /** + * @return iterable + */ + public function block_money_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "money_widget")); + + // line 26 + yield "
+ "; + // line 27 + $context["prepend"] = ("{{" == Twig\Extension\CoreExtension::slice($this->env->getCharset(), (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 27, $this->source); })()), 0, 2)); + // line 28 + yield " "; + if ( !(isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 28, $this->source); })())) { + // line 29 + yield "
+ "; + // line 30 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 30, $this->source); })())); + yield " +
+ "; + } + // line 33 + yield "
"; + // line 34 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 35 + yield "
+ "; + // line 36 + if ((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 36, $this->source); })())) { + // line 37 + yield "
+ "; + // line 38 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 38, $this->source); })())); + yield " +
+ "; + } + // line 41 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 44 + /** + * @return iterable + */ + public function block_percent_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "percent_widget")); + + // line 45 + yield "
"; + // line 46 + if ((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 46, $this->source); })())) { + // line 47 + yield "
"; + // line 48 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 49 + yield "
+
+ "; + // line 51 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("symbol", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 51, $this->source); })()), "%")) : ("%")), "html", null, true); + yield " +
"; + } else { + // line 54 + yield "
"; + // line 55 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 56 + yield "
"; + } + // line 58 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 61 + /** + * @return iterable + */ + public function block_datetime_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_widget")); + + // line 62 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 62, $this->source); })()) == "single_text")) { + // line 63 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 65 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 65, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 65)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 65), "")) : ("")) . " row"))]); + // line 66 + yield "
+
"; + // line 67 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 67, $this->source); })()), "date", [], "any", false, false, false, 67), 'errors'); + yield "
+
"; + // line 68 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 68, $this->source); })()), "time", [], "any", false, false, false, 68), 'errors'); + yield "
+
+
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield "> +
"; + // line 71 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 71, $this->source); })()), "date", [], "any", false, false, false, 71), 'widget', ["datetime" => true]); + yield "
+
"; + // line 72 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 72, $this->source); })()), "time", [], "any", false, false, false, 72), 'widget', ["datetime" => true]); + yield "
+
+ "; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 77 + /** + * @return iterable + */ + public function block_date_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_widget")); + + // line 78 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 78, $this->source); })()) == "single_text")) { + // line 79 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 81 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 81, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 81)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 81), "")) : ("")) . " row"))]); + // line 82 + yield " "; + if (( !array_key_exists("datetime", $context) || !(isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 82, $this->source); })()))) { + // line 83 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield "> + "; + } + // line 85 + yield Twig\Extension\CoreExtension::replace((isset($context["date_pattern"]) || array_key_exists("date_pattern", $context) ? $context["date_pattern"] : (function () { throw new RuntimeError('Variable "date_pattern" does not exist.', 85, $this->source); })()), ["{{ year }}" => (("
" . // line 86 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 86, $this->source); })()), "year", [], "any", false, false, false, 86), 'widget')) . "
"), "{{ month }}" => (("
" . // line 87 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 87, $this->source); })()), "month", [], "any", false, false, false, 87), 'widget')) . "
"), "{{ day }}" => (("
" . // line 88 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 88, $this->source); })()), "day", [], "any", false, false, false, 88), 'widget')) . "
")]); + // line 90 + if (( !array_key_exists("datetime", $context) || !(isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 90, $this->source); })()))) { + // line 91 + yield "
+ "; + } + // line 93 + yield " "; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 96 + /** + * @return iterable + */ + public function block_time_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_widget")); + + // line 97 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 97, $this->source); })()) == "single_text")) { + // line 98 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 100 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 100, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 100)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 100), "")) : ("")) . " row"))]); + // line 101 + yield " "; + if (( !array_key_exists("datetime", $context) || (false == (isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 101, $this->source); })())))) { + // line 102 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield "> + "; + } + // line 104 + yield " "; + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 104, $this->source); })())) { + // line 105 + yield "
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 105, $this->source); })()), "hour", [], "any", false, false, false, 105), 'widget'); + yield "
+
+
+
+ : +
+
+ "; + // line 112 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 112, $this->source); })()), "minute", [], "any", false, false, false, 112), 'widget'); + yield " +
+
+
+
+
+
+ : +
+
+ "; + // line 122 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 122, $this->source); })()), "second", [], "any", false, false, false, 122), 'widget'); + yield " +
+
+
+ "; + } else { + // line 127 + yield "
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 127, $this->source); })()), "hour", [], "any", false, false, false, 127), 'widget'); + yield "
+
+
+
+ : +
+
+ "; + // line 134 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 134, $this->source); })()), "minute", [], "any", false, false, false, 134), 'widget'); + yield " +
+
+
+ "; + } + // line 139 + yield " "; + if (( !array_key_exists("datetime", $context) || (false == (isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 139, $this->source); })())))) { + // line 140 + yield "
+ "; + } + // line 142 + yield " "; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 145 + /** + * @return iterable + */ + public function block_choice_widget_collapsed(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_collapsed")); + + // line 146 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 146, $this->source); })())) > 0)) { + // line 147 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 147, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 147)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 147), "")) : ("")) . " error"))]); + // line 148 + yield " "; + } + // line 149 + yield " + "; + // line 150 + if ((isset($context["multiple"]) || array_key_exists("multiple", $context) ? $context["multiple"] : (function () { throw new RuntimeError('Variable "multiple" does not exist.', 150, $this->source); })())) { + // line 151 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 151, $this->source); })()), ["style" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "style", [], "any", true, true, false, 151)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "style", [], "any", false, false, false, 151), "")) : ("")) . " height: auto; background-image: none;"))]); + // line 152 + yield " "; + } + // line 153 + yield " + "; + // line 154 + if (((((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 154, $this->source); })()) && (null === (isset($context["placeholder"]) || array_key_exists("placeholder", $context) ? $context["placeholder"] : (function () { throw new RuntimeError('Variable "placeholder" does not exist.', 154, $this->source); })()))) && !(isset($context["placeholder_in_choices"]) || array_key_exists("placeholder_in_choices", $context) ? $context["placeholder_in_choices"] : (function () { throw new RuntimeError('Variable "placeholder_in_choices" does not exist.', 154, $this->source); })())) && !(isset($context["multiple"]) || array_key_exists("multiple", $context) ? $context["multiple"] : (function () { throw new RuntimeError('Variable "multiple" does not exist.', 154, $this->source); })()))) { + // line 155 + $context["required"] = false; + } + // line 157 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 179 + /** + * @return iterable + */ + public function block_choice_widget_expanded(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_expanded")); + + // line 180 + if (CoreExtension::inFilter("-inline", ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 180)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 180), "")) : ("")))) { + // line 181 + yield "
    + "; + // line 182 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 182, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 183 + yield "
  • "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget', ["parent_label_class" => ((CoreExtension::getAttribute($this->env, $this->source, // line 184 +($context["label_attr"] ?? null), "class", [], "any", true, true, false, 184)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 184), "")) : (""))]); + // line 185 + yield "
  • + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 187 + yield "
+ "; + } else { + // line 189 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield "> + "; + // line 190 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 190, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 191 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget', ["parent_label_class" => ((CoreExtension::getAttribute($this->env, $this->source, // line 192 +($context["label_attr"] ?? null), "class", [], "any", true, true, false, 192)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 192), "")) : (""))]); + // line 193 + yield " + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 195 + yield "
+ "; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 199 + /** + * @return iterable + */ + public function block_checkbox_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_widget")); + + // line 200 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 200, $this->source); })()), "")) : ("")); + // line 201 + yield " "; + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 201, $this->source); })())) > 0)) { + // line 202 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 202, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 202)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 202), "")) : ("")) . " error"))]); + // line 203 + yield " "; + } + // line 204 + yield " "; + if (CoreExtension::inFilter("checkbox-inline", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 204, $this->source); })()))) { + // line 205 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 205, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + yield " + "; + } else { + // line 207 + yield "
+ "; + // line 208 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 208, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + yield " +
+ "; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 213 + /** + * @return iterable + */ + public function block_radio_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_widget")); + + // line 214 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 214, $this->source); })()), "")) : ("")); + // line 215 + yield " "; + if (CoreExtension::inFilter("radio-inline", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 215, $this->source); })()))) { + // line 216 + yield " "; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 216, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("radio_widget", $context, $blocks)]); + yield " + "; + } else { + // line 218 + yield " "; + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 218, $this->source); })())) > 0)) { + // line 219 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 219, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 219)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 219), "")) : ("")) . " error"))]); + // line 220 + yield " "; + } + // line 221 + yield "
+ "; + // line 222 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 222, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("radio_widget", $context, $blocks)]); + yield " +
+ "; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 229 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 230 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 230, $this->source); })())) > 0)) { + // line 231 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 231, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 231)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 231), "")) : ("")) . " error"))]); + // line 232 + yield " "; + } + // line 233 + yield from $this->yieldParentBlock("form_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 236 + /** + * @return iterable + */ + public function block_choice_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_label")); + + // line 237 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 237, $this->source); })())) > 0)) { + // line 238 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 238, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 238)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 238), "")) : ("")) . " error"))]); + // line 239 + yield " "; + } + // line 240 + yield " "; + // line 241 + yield " "; + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 241, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(Twig\Extension\CoreExtension::replace(((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 241)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 241), "")) : ("")), ["checkbox-inline" => "", "radio-inline" => ""]))]); + // line 242 + yield from $this->unwrap()->yieldBlock("form_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 245 + /** + * @return iterable + */ + public function block_checkbox_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_label")); + + // line 246 + yield from $this->unwrap()->yieldBlock("checkbox_radio_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 249 + /** + * @return iterable + */ + public function block_radio_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_label")); + + // line 250 + yield from $this->unwrap()->yieldBlock("checkbox_radio_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 253 + /** + * @return iterable + */ + public function block_checkbox_radio_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_radio_label")); + + // line 254 + if ((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 254, $this->source); })())) { + // line 255 + yield " "; + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 255, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 255)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 255), "")) : ("")) . " required"))]); + // line 256 + yield " "; + } + // line 257 + yield " "; + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 257, $this->source); })())) > 0)) { + // line 258 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 258, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 258)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 258), "")) : ("")) . " error"))]); + // line 259 + yield " "; + } + // line 260 + if (array_key_exists("parent_label_class", $context)) { + // line 261 + $context["embed_label_classes"] = Twig\Extension\CoreExtension::filter($this->env, Twig\Extension\CoreExtension::split($this->env->getCharset(), (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 261, $this->source); })()), " "), function ($__class__) use ($context, $macros) { $context["class"] = $__class__; return CoreExtension::inFilter((isset($context["class"]) || array_key_exists("class", $context) ? $context["class"] : (function () { throw new RuntimeError('Variable "class" does not exist.', 261, $this->source); })()), ["checkbox-inline", "radio-inline"]); }); + // line 262 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 262, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 262)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 262), "")) : ("")) . " ") . Twig\Extension\CoreExtension::join((isset($context["embed_label_classes"]) || array_key_exists("embed_label_classes", $context) ? $context["embed_label_classes"] : (function () { throw new RuntimeError('Variable "embed_label_classes" does not exist.', 262, $this->source); })()), " ")))]); + } + // line 264 + yield " "; + if (Twig\Extension\CoreExtension::testEmpty((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 264, $this->source); })()))) { + // line 265 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 265, $this->source); })()))) { + // line 266 + $context["label"] = Twig\Extension\CoreExtension::replace((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 266, $this->source); })()), ["%name%" => // line 267 +(isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 267, $this->source); })()), "%id%" => // line 268 +(isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 268, $this->source); })())]); + } else { + // line 271 + $context["label"] = $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->humanize((isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 271, $this->source); })())); + } + } + // line 274 + yield " (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 274, $this->source); })())]; + if (!is_iterable($__internal_compile_3)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 274, $this->getSourceContext()); + } + $__internal_compile_3 = CoreExtension::toArray($__internal_compile_3); + $context = $__internal_compile_3 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_2; + yield "> + "; + // line 275 + yield (isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 275, $this->source); })()); + // line 276 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 276, $this->source); })()) === false)) { + // line 277 + yield from $this->unwrap()->yieldBlock("form_label_content", $context, $blocks); + } + // line 279 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 284 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 285 + $context["widget_attr"] = []; + // line 286 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 286, $this->source); })()))) { + // line 287 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 287, $this->source); })()) . "_help")]]; + } + // line 289 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 289, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 289)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 289), "")) : ("")) . " row"))])]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 289, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield "> +
source); })()) || ((array_key_exists("force_error", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["force_error"]) || array_key_exists("force_error", $context) ? $context["force_error"] : (function () { throw new RuntimeError('Variable "force_error" does not exist.', 290, $this->source); })()), false)) : (false))) && !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 290, $this->source); })()))) { + yield " error"; + } + yield "\">"; + // line 291 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 291, $this->source); })()), 'label'); + // line 292 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 292, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 292, $this->source); })())); + // line 293 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 293, $this->source); })()), 'help'); + // line 294 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 294, $this->source); })()), 'errors'); + // line 295 + yield "
+ "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 299 + /** + * @return iterable + */ + public function block_choice_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_row")); + + // line 300 + $context["force_error"] = true; + // line 301 + yield " "; + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 304 + /** + * @return iterable + */ + public function block_date_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_row")); + + // line 305 + $context["force_error"] = true; + // line 306 + yield " "; + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 309 + /** + * @return iterable + */ + public function block_time_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_row")); + + // line 310 + $context["force_error"] = true; + // line 311 + yield " "; + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 314 + /** + * @return iterable + */ + public function block_datetime_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_row")); + + // line 315 + $context["force_error"] = true; + // line 316 + yield " "; + yield from $this->unwrap()->yieldBlock("form_row", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 319 + /** + * @return iterable + */ + public function block_checkbox_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_row")); + + // line 320 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 320, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 320)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 320), "")) : ("")) . " row"))])]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 320, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + yield "> +
source); })())) { + yield " error"; + } + yield "\"> + "; + // line 322 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 322, $this->source); })()), 'widget'); + // line 323 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 323, $this->source); })()), 'help'); + // line 324 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 324, $this->source); })()), 'errors'); + yield " +
+ "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 329 + /** + * @return iterable + */ + public function block_radio_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_row")); + + // line 330 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 330, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 330)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 330), "")) : ("")) . " row"))])]; + if (!is_iterable($__internal_compile_9)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 330, $this->getSourceContext()); + } + $__internal_compile_9 = CoreExtension::toArray($__internal_compile_9); + $context = $__internal_compile_9 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_8; + yield "> +
source); })())) { + yield " error"; + } + yield "\"> + "; + // line 332 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 332, $this->source); })()), 'widget'); + // line 333 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 333, $this->source); })()), 'help'); + // line 334 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 334, $this->source); })()), 'errors'); + yield " +
+ "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 341 + /** + * @return iterable + */ + public function block_form_errors(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_errors")); + + // line 342 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 342, $this->source); })())) > 0)) { + // line 343 + if ( !Symfony\Bridge\Twig\Extension\twig_is_root_form((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 343, $this->source); })()))) { + yield ""; + } else { + yield "
"; + } + // line 344 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 344, $this->source); })())); + $context['loop'] = [ + 'parent' => $context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, + ]; + if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) { + $length = count($context['_seq']); + $context['loop']['revindex0'] = $length - 1; + $context['loop']['revindex'] = $length; + $context['loop']['length'] = $length; + $context['loop']['last'] = 1 === $length; + } + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 345 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["error"], "message", [], "any", false, false, false, 345), "html", null, true); + yield " + "; + // line 346 + if ( !CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 346)) { + yield ", "; + } + ++$context['loop']['index0']; + ++$context['loop']['index']; + $context['loop']['first'] = false; + if (isset($context['loop']['revindex0'], $context['loop']['revindex'])) { + --$context['loop']['revindex0']; + --$context['loop']['revindex']; + $context['loop']['last'] = 0 === $context['loop']['revindex0']; + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['error'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 348 + if ( !Symfony\Bridge\Twig\Extension\twig_is_root_form((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 348, $this->source); })()))) { + yield ""; + } else { + yield "
"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "foundation_5_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 1143 => 348, 1127 => 346, 1123 => 345, 1106 => 344, 1100 => 343, 1098 => 342, 1088 => 341, 1077 => 334, 1075 => 333, 1073 => 332, 1067 => 331, 1054 => 330, 1044 => 329, 1033 => 324, 1031 => 323, 1029 => 322, 1023 => 321, 1010 => 320, 1000 => 319, 991 => 316, 989 => 315, 979 => 314, 970 => 311, 968 => 310, 958 => 309, 949 => 306, 947 => 305, 937 => 304, 928 => 301, 926 => 300, 916 => 299, 907 => 295, 905 => 294, 903 => 293, 901 => 292, 899 => 291, 894 => 290, 881 => 289, 878 => 287, 876 => 286, 874 => 285, 864 => 284, 856 => 279, 853 => 277, 851 => 276, 849 => 275, 836 => 274, 832 => 271, 829 => 268, 828 => 267, 827 => 266, 825 => 265, 822 => 264, 819 => 262, 817 => 261, 815 => 260, 812 => 259, 810 => 258, 807 => 257, 804 => 256, 801 => 255, 799 => 254, 789 => 253, 781 => 250, 771 => 249, 763 => 246, 753 => 245, 745 => 242, 742 => 241, 740 => 240, 737 => 239, 735 => 238, 733 => 237, 723 => 236, 715 => 233, 712 => 232, 710 => 231, 708 => 230, 698 => 229, 686 => 222, 683 => 221, 680 => 220, 678 => 219, 675 => 218, 669 => 216, 666 => 215, 664 => 214, 654 => 213, 642 => 208, 639 => 207, 633 => 205, 630 => 204, 627 => 203, 625 => 202, 622 => 201, 620 => 200, 610 => 199, 600 => 195, 593 => 193, 591 => 192, 589 => 191, 585 => 190, 580 => 189, 576 => 187, 569 => 185, 567 => 184, 565 => 183, 561 => 182, 558 => 181, 556 => 180, 546 => 179, 538 => 176, 536 => 175, 534 => 174, 532 => 173, 524 => 169, 518 => 167, 516 => 166, 514 => 165, 512 => 164, 509 => 163, 507 => 162, 505 => 161, 485 => 159, 483 => 158, 475 => 157, 472 => 155, 470 => 154, 467 => 153, 464 => 152, 462 => 151, 460 => 150, 457 => 149, 454 => 148, 452 => 147, 450 => 146, 440 => 145, 431 => 142, 427 => 140, 424 => 139, 416 => 134, 405 => 127, 397 => 122, 384 => 112, 373 => 105, 370 => 104, 364 => 102, 361 => 101, 358 => 100, 355 => 98, 353 => 97, 343 => 96, 334 => 93, 330 => 91, 328 => 90, 326 => 88, 325 => 87, 324 => 86, 323 => 85, 317 => 83, 314 => 82, 311 => 81, 308 => 79, 306 => 78, 296 => 77, 284 => 72, 280 => 71, 276 => 70, 271 => 68, 267 => 67, 264 => 66, 261 => 65, 258 => 63, 256 => 62, 246 => 61, 238 => 58, 235 => 56, 233 => 55, 231 => 54, 226 => 51, 222 => 49, 220 => 48, 218 => 47, 216 => 46, 214 => 45, 204 => 44, 196 => 41, 190 => 38, 187 => 37, 185 => 36, 182 => 35, 180 => 34, 178 => 33, 172 => 30, 169 => 29, 166 => 28, 164 => 27, 161 => 26, 151 => 25, 143 => 22, 141 => 21, 131 => 20, 123 => 17, 120 => 16, 118 => 15, 116 => 14, 106 => 13, 98 => 10, 95 => 9, 93 => 8, 91 => 7, 81 => 6, 64 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends \"form_div_layout.html.twig\" %} + +{# Based on Foundation 5 Doc #} +{# Widgets #} + +{% block form_widget_simple -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block textarea_widget -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock button_widget %} + +{% block money_widget -%} +
+ {% set prepend = '{{' == money_pattern[0:2] %} + {% if not prepend %} +
+ {{ money_pattern|form_encode_currency }} +
+ {% endif %} +
+ {{- block('form_widget_simple') -}} +
+ {% if prepend %} +
+ {{ money_pattern|form_encode_currency }} +
+ {% endif %} +
+{%- endblock money_widget %} + +{% block percent_widget -%} +
+ {%- if symbol -%} +
+ {{- block('form_widget_simple') -}} +
+
+ {{ symbol|default('%') }} +
+ {%- else -%} +
+ {{- block('form_widget_simple') -}} +
+ {%- endif -%} +
+{%- endblock percent_widget %} + +{% block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} +
+
{{ form_errors(form.date) }}
+
{{ form_errors(form.time) }}
+
+
+
{{ form_widget(form.date, { datetime: true } ) }}
+
{{ form_widget(form.time, { datetime: true } ) }}
+
+ {% endif %} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or not datetime %} +
+ {% endif %} + {{- date_pattern|replace({ + '{{ year }}': '
' ~ form_widget(form.year) ~ '
', + '{{ month }}': '
' ~ form_widget(form.month) ~ '
', + '{{ day }}': '
' ~ form_widget(form.day) ~ '
', + })|raw -}} + {% if datetime is not defined or not datetime %} +
+ {% endif %} + {% endif %} +{%- endblock date_widget %} + +{% block time_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or false == datetime %} +
+ {% endif %} + {% if with_seconds %} +
{{ form_widget(form.hour) }}
+
+
+
+ : +
+
+ {{ form_widget(form.minute) }} +
+
+
+
+
+
+ : +
+
+ {{ form_widget(form.second) }} +
+
+
+ {% else %} +
{{ form_widget(form.hour) }}
+
+
+
+ : +
+
+ {{ form_widget(form.minute) }} +
+
+
+ {% endif %} + {% if datetime is not defined or false == datetime %} +
+ {% endif %} + {% endif %} +{%- endblock time_widget %} + +{% block choice_widget_collapsed -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + + {% if multiple -%} + {% set attr = attr|merge({style: (attr.style|default('') ~ ' height: auto; background-image: none;')|trim}) %} + {% endif %} + + {% if required and placeholder is none and not placeholder_in_choices and not multiple -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed %} + +{% block choice_widget_expanded -%} + {% if '-inline' in label_attr.class|default('') %} +
    + {% for child in form %} +
  • {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }}
  • + {% endfor %} +
+ {% else %} +
+ {% for child in form %} + {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }} + {% endfor %} +
+ {% endif %} +{%- endblock choice_widget_expanded %} + +{% block checkbox_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if 'checkbox-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} +
+ {{ form_label(form, null, { widget: parent() }) }} +
+ {% endif %} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if 'radio-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} +
+ {{ form_label(form, null, { widget: parent() }) }} +
+ {% endif %} +{%- endblock radio_widget %} + +{# Labels #} + +{% block form_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {% set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) %} + {{- block('form_label') -}} +{%- endblock choice_label %} + +{% block checkbox_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label -%} + {% if required %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {% endif %} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {%- if parent_label_class is defined -%} + {% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%} + {% endif %} + {% if label is empty %} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {% endif %} + + {{ widget|raw }} + {%- if label is not same as(false) -%} + {{- block('form_label_content') -}} + {%- endif -%} + +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + +
+ {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
+ +{%- endblock form_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} + +
+ {{ form_widget(form) }} + {{- form_help(form) -}} + {{ form_errors(form) }} +
+ +{%- endblock checkbox_row %} + +{% block radio_row -%} + +
+ {{ form_widget(form) }} + {{- form_help(form) -}} + {{ form_errors(form) }} +
+ +{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form is not rootform %}{% else %}
{% endif %} + {%- for error in errors -%} + {{ error.message }} + {% if not loop.last %}, {% endif %} + {%- endfor -%} + {% if form is not rootform %}{% else %}
{% endif %} + {%- endif %} +{%- endblock form_errors %} +", "foundation_5_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/ac/ac349bbba86229e76bc91d2012b1268e.php b/var/cache/dev/twig/ac/ac349bbba86229e76bc91d2012b1268e.php new file mode 100644 index 0000000..3e62967 --- /dev/null +++ b/var/cache/dev/twig/ac/ac349bbba86229e76bc91d2012b1268e.php @@ -0,0 +1,117 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + 'hwi_oauth_content' => [$this, 'block_hwi_oauth_content'], + ]; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@HWIOAuth/layout.html.twig")); + + // line 1 + yield " + + + + + +
+ "; + // line 8 + yield from $this->unwrap()->yieldBlock('hwi_oauth_content', $context, $blocks); + // line 10 + yield "
+ + +"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 8 + /** + * @return iterable + */ + public function block_hwi_oauth_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hwi_oauth_content")); + + // line 9 + yield " "; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@HWIOAuth/layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 78 => 9, 68 => 8, 57 => 10, 55 => 8, 46 => 1,); + } + + public function getSourceContext(): Source + { + return new Source(" + + + + + +
+ {% block hwi_oauth_content %} + {% endblock hwi_oauth_content %} +
+ + +", "@HWIOAuth/layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views/layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/b7/b7daa3c2af0b77bf80c23c7136066ce3.php b/var/cache/dev/twig/b7/b7daa3c2af0b77bf80c23c7136066ce3.php new file mode 100644 index 0000000..e77938c --- /dev/null +++ b/var/cache/dev/twig/b7/b7daa3c2af0b77bf80c23c7136066ce3.php @@ -0,0 +1,2403 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + 'form_widget' => [$this, 'block_form_widget'], + 'form_widget_simple' => [$this, 'block_form_widget_simple'], + 'form_widget_compound' => [$this, 'block_form_widget_compound'], + 'collection_widget' => [$this, 'block_collection_widget'], + 'textarea_widget' => [$this, 'block_textarea_widget'], + 'choice_widget' => [$this, 'block_choice_widget'], + 'choice_widget_expanded' => [$this, 'block_choice_widget_expanded'], + 'choice_widget_collapsed' => [$this, 'block_choice_widget_collapsed'], + 'choice_widget_options' => [$this, 'block_choice_widget_options'], + 'checkbox_widget' => [$this, 'block_checkbox_widget'], + 'radio_widget' => [$this, 'block_radio_widget'], + 'datetime_widget' => [$this, 'block_datetime_widget'], + 'date_widget' => [$this, 'block_date_widget'], + 'time_widget' => [$this, 'block_time_widget'], + 'dateinterval_widget' => [$this, 'block_dateinterval_widget'], + 'number_widget' => [$this, 'block_number_widget'], + 'integer_widget' => [$this, 'block_integer_widget'], + 'money_widget' => [$this, 'block_money_widget'], + 'url_widget' => [$this, 'block_url_widget'], + 'search_widget' => [$this, 'block_search_widget'], + 'percent_widget' => [$this, 'block_percent_widget'], + 'password_widget' => [$this, 'block_password_widget'], + 'hidden_widget' => [$this, 'block_hidden_widget'], + 'email_widget' => [$this, 'block_email_widget'], + 'range_widget' => [$this, 'block_range_widget'], + 'button_widget' => [$this, 'block_button_widget'], + 'submit_widget' => [$this, 'block_submit_widget'], + 'reset_widget' => [$this, 'block_reset_widget'], + 'tel_widget' => [$this, 'block_tel_widget'], + 'color_widget' => [$this, 'block_color_widget'], + 'week_widget' => [$this, 'block_week_widget'], + 'form_label' => [$this, 'block_form_label'], + 'form_label_content' => [$this, 'block_form_label_content'], + 'button_label' => [$this, 'block_button_label'], + 'form_help' => [$this, 'block_form_help'], + 'form_help_content' => [$this, 'block_form_help_content'], + 'repeated_row' => [$this, 'block_repeated_row'], + 'form_row' => [$this, 'block_form_row'], + 'button_row' => [$this, 'block_button_row'], + 'hidden_row' => [$this, 'block_hidden_row'], + 'form' => [$this, 'block_form'], + 'form_start' => [$this, 'block_form_start'], + 'form_end' => [$this, 'block_form_end'], + 'form_errors' => [$this, 'block_form_errors'], + 'form_rest' => [$this, 'block_form_rest'], + 'form_rows' => [$this, 'block_form_rows'], + 'widget_attributes' => [$this, 'block_widget_attributes'], + 'widget_container_attributes' => [$this, 'block_widget_container_attributes'], + 'button_attributes' => [$this, 'block_button_attributes'], + 'attributes' => [$this, 'block_attributes'], + ]; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "form_div_layout.html.twig")); + + // line 3 + yield from $this->unwrap()->yieldBlock('form_widget', $context, $blocks); + // line 11 + yield from $this->unwrap()->yieldBlock('form_widget_simple', $context, $blocks); + // line 20 + yield from $this->unwrap()->yieldBlock('form_widget_compound', $context, $blocks); + // line 30 + yield from $this->unwrap()->yieldBlock('collection_widget', $context, $blocks); + // line 37 + yield from $this->unwrap()->yieldBlock('textarea_widget', $context, $blocks); + // line 41 + yield from $this->unwrap()->yieldBlock('choice_widget', $context, $blocks); + // line 49 + yield from $this->unwrap()->yieldBlock('choice_widget_expanded', $context, $blocks); + // line 58 + yield from $this->unwrap()->yieldBlock('choice_widget_collapsed', $context, $blocks); + // line 84 + yield from $this->unwrap()->yieldBlock('choice_widget_options', $context, $blocks); + // line 97 + yield from $this->unwrap()->yieldBlock('checkbox_widget', $context, $blocks); + // line 101 + yield from $this->unwrap()->yieldBlock('radio_widget', $context, $blocks); + // line 105 + yield from $this->unwrap()->yieldBlock('datetime_widget', $context, $blocks); + // line 118 + yield from $this->unwrap()->yieldBlock('date_widget', $context, $blocks); + // line 132 + yield from $this->unwrap()->yieldBlock('time_widget', $context, $blocks); + // line 143 + yield from $this->unwrap()->yieldBlock('dateinterval_widget', $context, $blocks); + // line 178 + yield from $this->unwrap()->yieldBlock('number_widget', $context, $blocks); + // line 184 + yield from $this->unwrap()->yieldBlock('integer_widget', $context, $blocks); + // line 189 + yield from $this->unwrap()->yieldBlock('money_widget', $context, $blocks); + // line 193 + yield from $this->unwrap()->yieldBlock('url_widget', $context, $blocks); + // line 198 + yield from $this->unwrap()->yieldBlock('search_widget', $context, $blocks); + // line 203 + yield from $this->unwrap()->yieldBlock('percent_widget', $context, $blocks); + // line 208 + yield from $this->unwrap()->yieldBlock('password_widget', $context, $blocks); + // line 213 + yield from $this->unwrap()->yieldBlock('hidden_widget', $context, $blocks); + // line 218 + yield from $this->unwrap()->yieldBlock('email_widget', $context, $blocks); + // line 223 + yield from $this->unwrap()->yieldBlock('range_widget', $context, $blocks); + // line 228 + yield from $this->unwrap()->yieldBlock('button_widget', $context, $blocks); + // line 256 + yield from $this->unwrap()->yieldBlock('submit_widget', $context, $blocks); + // line 261 + yield from $this->unwrap()->yieldBlock('reset_widget', $context, $blocks); + // line 266 + yield from $this->unwrap()->yieldBlock('tel_widget', $context, $blocks); + // line 271 + yield from $this->unwrap()->yieldBlock('color_widget', $context, $blocks); + // line 276 + yield from $this->unwrap()->yieldBlock('week_widget', $context, $blocks); + // line 289 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 303 + yield from $this->unwrap()->yieldBlock('form_label_content', $context, $blocks); + // line 329 + yield from $this->unwrap()->yieldBlock('button_label', $context, $blocks); + // line 332 + yield " +"; + // line 333 + yield from $this->unwrap()->yieldBlock('form_help', $context, $blocks); + // line 341 + yield " +"; + // line 342 + yield from $this->unwrap()->yieldBlock('form_help_content', $context, $blocks); + // line 357 + yield " +"; + // line 360 + yield from $this->unwrap()->yieldBlock('repeated_row', $context, $blocks); + // line 368 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 381 + yield from $this->unwrap()->yieldBlock('button_row', $context, $blocks); + // line 387 + yield from $this->unwrap()->yieldBlock('hidden_row', $context, $blocks); + // line 393 + yield from $this->unwrap()->yieldBlock('form', $context, $blocks); + // line 399 + yield from $this->unwrap()->yieldBlock('form_start', $context, $blocks); + // line 413 + yield from $this->unwrap()->yieldBlock('form_end', $context, $blocks); + // line 420 + yield from $this->unwrap()->yieldBlock('form_errors', $context, $blocks); + // line 430 + yield from $this->unwrap()->yieldBlock('form_rest', $context, $blocks); + // line 451 + yield " +"; + // line 454 + yield from $this->unwrap()->yieldBlock('form_rows', $context, $blocks); + // line 460 + yield from $this->unwrap()->yieldBlock('widget_attributes', $context, $blocks); + // line 467 + yield from $this->unwrap()->yieldBlock('widget_container_attributes', $context, $blocks); + // line 472 + yield from $this->unwrap()->yieldBlock('button_attributes', $context, $blocks); + // line 477 + yield from $this->unwrap()->yieldBlock('attributes', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 3 + /** + * @return iterable + */ + public function block_form_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget")); + + // line 4 + if ((isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 4, $this->source); })())) { + // line 5 + yield from $this->unwrap()->yieldBlock("form_widget_compound", $context, $blocks); + } else { + // line 7 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 11 + /** + * @return iterable + */ + public function block_form_widget_simple(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget_simple")); + + // line 12 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 12, $this->source); })()), "text")) : ("text")); + // line 13 + if ((((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 13, $this->source); })()) == "range") || ((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 13, $this->source); })()) == "color"))) { + // line 15 + $context["required"] = false; + } + // line 17 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 17, $this->source); })()), "html", null, true); + yield "\" "; + yield from $this->unwrap()->yieldBlock("widget_attributes", $context, $blocks); + yield " "; + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["value"]) || array_key_exists("value", $context) ? $context["value"] : (function () { throw new RuntimeError('Variable "value" does not exist.', 17, $this->source); })()))) { + yield "value=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["value"]) || array_key_exists("value", $context) ? $context["value"] : (function () { throw new RuntimeError('Variable "value" does not exist.', 17, $this->source); })()), "html", null, true); + yield "\" "; + } + yield "/>"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 20 + /** + * @return iterable + */ + public function block_form_widget_compound(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget_compound")); + + // line 21 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 22 + if (Symfony\Bridge\Twig\Extension\twig_is_root_form((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 22, $this->source); })()))) { + // line 23 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 23, $this->source); })()), 'errors'); + } + // line 25 + yield from $this->unwrap()->yieldBlock("form_rows", $context, $blocks); + // line 26 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 26, $this->source); })()), 'rest'); + // line 27 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 30 + /** + * @return iterable + */ + public function block_collection_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "collection_widget")); + + // line 31 + if ((array_key_exists("prototype", $context) && !CoreExtension::getAttribute($this->env, $this->source, (isset($context["prototype"]) || array_key_exists("prototype", $context) ? $context["prototype"] : (function () { throw new RuntimeError('Variable "prototype" does not exist.', 31, $this->source); })()), "rendered", [], "any", false, false, false, 31))) { + // line 32 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 32, $this->source); })()), ["data-prototype" => $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["prototype"]) || array_key_exists("prototype", $context) ? $context["prototype"] : (function () { throw new RuntimeError('Variable "prototype" does not exist.', 32, $this->source); })()), 'row')]); + } + // line 34 + yield from $this->unwrap()->yieldBlock("form_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 37 + /** + * @return iterable + */ + public function block_textarea_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "textarea_widget")); + + // line 38 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 41 + /** + * @return iterable + */ + public function block_choice_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget")); + + // line 42 + if ((isset($context["expanded"]) || array_key_exists("expanded", $context) ? $context["expanded"] : (function () { throw new RuntimeError('Variable "expanded" does not exist.', 42, $this->source); })())) { + // line 43 + yield from $this->unwrap()->yieldBlock("choice_widget_expanded", $context, $blocks); + } else { + // line 45 + yield from $this->unwrap()->yieldBlock("choice_widget_collapsed", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 49 + /** + * @return iterable + */ + public function block_choice_widget_expanded(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_expanded")); + + // line 50 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 51 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 51, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 52 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget'); + // line 53 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'label', ["translation_domain" => (isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 53, $this->source); })())]); + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 55 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 58 + /** + * @return iterable + */ + public function block_choice_widget_collapsed(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_collapsed")); + + // line 59 + if ((((((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 59, $this->source); })()) && (null === (isset($context["placeholder"]) || array_key_exists("placeholder", $context) ? $context["placeholder"] : (function () { throw new RuntimeError('Variable "placeholder" does not exist.', 59, $this->source); })()))) && !(isset($context["placeholder_in_choices"]) || array_key_exists("placeholder_in_choices", $context) ? $context["placeholder_in_choices"] : (function () { throw new RuntimeError('Variable "placeholder_in_choices" does not exist.', 59, $this->source); })())) && !(isset($context["multiple"]) || array_key_exists("multiple", $context) ? $context["multiple"] : (function () { throw new RuntimeError('Variable "multiple" does not exist.', 59, $this->source); })())) && ( !CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "size", [], "any", true, true, false, 59) || (CoreExtension::getAttribute($this->env, $this->source, (isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 59, $this->source); })()), "size", [], "any", false, false, false, 59) <= 1)))) { + // line 60 + $context["required"] = false; + } + // line 62 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 84 + /** + * @return iterable + */ + public function block_choice_widget_options(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_options")); + + // line 85 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["options"]) || array_key_exists("options", $context) ? $context["options"] : (function () { throw new RuntimeError('Variable "options" does not exist.', 85, $this->source); })())); + $context['loop'] = [ + 'parent' => $context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, + ]; + if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) { + $length = count($context['_seq']); + $context['loop']['revindex0'] = $length - 1; + $context['loop']['revindex'] = $length; + $context['loop']['length'] = $length; + $context['loop']['last'] = 1 === $length; + } + foreach ($context['_seq'] as $context["group_label"] => $context["choice"]) { + // line 86 + if (is_iterable($context["choice"])) { + // line 87 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((((isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 87, $this->source); })()) === false)) ? ($context["group_label"]) : ($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans($context["group_label"], [], (isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 87, $this->source); })())))), "html", null, true); + yield "\"> + "; + // line 88 + $context["options"] = $context["choice"]; + // line 89 + yield from $this->unwrap()->yieldBlock("choice_widget_options", $context, $blocks); + // line 90 + yield ""; + } else { + // line 92 + yield ""; + } + ++$context['loop']['index0']; + ++$context['loop']['index']; + $context['loop']['first'] = false; + if (isset($context['loop']['revindex0'], $context['loop']['revindex'])) { + --$context['loop']['revindex0']; + --$context['loop']['revindex']; + $context['loop']['last'] = 0 === $context['loop']['revindex0']; + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['group_label'], $context['choice'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 97 + /** + * @return iterable + */ + public function block_checkbox_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_widget")); + + // line 98 + yield "unwrap()->yieldBlock("widget_attributes", $context, $blocks); + if (array_key_exists("value", $context)) { + yield " value=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["value"]) || array_key_exists("value", $context) ? $context["value"] : (function () { throw new RuntimeError('Variable "value" does not exist.', 98, $this->source); })()), "html", null, true); + yield "\""; + } + if ((isset($context["checked"]) || array_key_exists("checked", $context) ? $context["checked"] : (function () { throw new RuntimeError('Variable "checked" does not exist.', 98, $this->source); })())) { + yield " checked=\"checked\""; + } + yield " />"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 101 + /** + * @return iterable + */ + public function block_radio_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_widget")); + + // line 102 + yield "unwrap()->yieldBlock("widget_attributes", $context, $blocks); + if (array_key_exists("value", $context)) { + yield " value=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["value"]) || array_key_exists("value", $context) ? $context["value"] : (function () { throw new RuntimeError('Variable "value" does not exist.', 102, $this->source); })()), "html", null, true); + yield "\""; + } + if ((isset($context["checked"]) || array_key_exists("checked", $context) ? $context["checked"] : (function () { throw new RuntimeError('Variable "checked" does not exist.', 102, $this->source); })())) { + yield " checked=\"checked\""; + } + yield " />"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 105 + /** + * @return iterable + */ + public function block_datetime_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_widget")); + + // line 106 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 106, $this->source); })()) == "single_text")) { + // line 107 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 109 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 110 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 110, $this->source); })()), "date", [], "any", false, false, false, 110), 'errors'); + // line 111 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 111, $this->source); })()), "time", [], "any", false, false, false, 111), 'errors'); + // line 112 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 112, $this->source); })()), "date", [], "any", false, false, false, 112), 'widget'); + // line 113 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 113, $this->source); })()), "time", [], "any", false, false, false, 113), 'widget'); + // line 114 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 118 + /** + * @return iterable + */ + public function block_date_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_widget")); + + // line 119 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 119, $this->source); })()) == "single_text")) { + // line 120 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 122 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 123 + yield Twig\Extension\CoreExtension::replace((isset($context["date_pattern"]) || array_key_exists("date_pattern", $context) ? $context["date_pattern"] : (function () { throw new RuntimeError('Variable "date_pattern" does not exist.', 123, $this->source); })()), ["{{ year }}" => // line 124 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 124, $this->source); })()), "year", [], "any", false, false, false, 124), 'widget'), "{{ month }}" => // line 125 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 125, $this->source); })()), "month", [], "any", false, false, false, 125), 'widget'), "{{ day }}" => // line 126 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 126, $this->source); })()), "day", [], "any", false, false, false, 126), 'widget')]); + // line 128 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 132 + /** + * @return iterable + */ + public function block_time_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_widget")); + + // line 133 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 133, $this->source); })()) == "single_text")) { + // line 134 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 136 + $context["vars"] = ((((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 136, $this->source); })()) == "text")) ? (["attr" => ["size" => 1]]) : ([])); + // line 137 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield "> + "; + // line 138 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 138, $this->source); })()), "hour", [], "any", false, false, false, 138), 'widget', (isset($context["vars"]) || array_key_exists("vars", $context) ? $context["vars"] : (function () { throw new RuntimeError('Variable "vars" does not exist.', 138, $this->source); })())); + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 138, $this->source); })())) { + yield ":"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 138, $this->source); })()), "minute", [], "any", false, false, false, 138), 'widget', (isset($context["vars"]) || array_key_exists("vars", $context) ? $context["vars"] : (function () { throw new RuntimeError('Variable "vars" does not exist.', 138, $this->source); })())); + } + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 138, $this->source); })())) { + yield ":"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 138, $this->source); })()), "second", [], "any", false, false, false, 138), 'widget', (isset($context["vars"]) || array_key_exists("vars", $context) ? $context["vars"] : (function () { throw new RuntimeError('Variable "vars" does not exist.', 138, $this->source); })())); + } + // line 139 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 143 + /** + * @return iterable + */ + public function block_dateinterval_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "dateinterval_widget")); + + // line 144 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 144, $this->source); })()) == "single_text")) { + // line 145 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 147 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 148 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 148, $this->source); })()), 'errors'); + // line 149 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("table_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["table_class"]) || array_key_exists("table_class", $context) ? $context["table_class"] : (function () { throw new RuntimeError('Variable "table_class" does not exist.', 149, $this->source); })()), "")) : ("")), "html", null, true); + yield "\" role=\"presentation\"> + + "; + // line 152 + if ((isset($context["with_years"]) || array_key_exists("with_years", $context) ? $context["with_years"] : (function () { throw new RuntimeError('Variable "with_years" does not exist.', 152, $this->source); })())) { + yield ""; + } + // line 153 + if ((isset($context["with_months"]) || array_key_exists("with_months", $context) ? $context["with_months"] : (function () { throw new RuntimeError('Variable "with_months" does not exist.', 153, $this->source); })())) { + yield ""; + } + // line 154 + if ((isset($context["with_weeks"]) || array_key_exists("with_weeks", $context) ? $context["with_weeks"] : (function () { throw new RuntimeError('Variable "with_weeks" does not exist.', 154, $this->source); })())) { + yield ""; + } + // line 155 + if ((isset($context["with_days"]) || array_key_exists("with_days", $context) ? $context["with_days"] : (function () { throw new RuntimeError('Variable "with_days" does not exist.', 155, $this->source); })())) { + yield ""; + } + // line 156 + if ((isset($context["with_hours"]) || array_key_exists("with_hours", $context) ? $context["with_hours"] : (function () { throw new RuntimeError('Variable "with_hours" does not exist.', 156, $this->source); })())) { + yield ""; + } + // line 157 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 157, $this->source); })())) { + yield ""; + } + // line 158 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 158, $this->source); })())) { + yield ""; + } + // line 159 + yield " + + + "; + // line 163 + if ((isset($context["with_years"]) || array_key_exists("with_years", $context) ? $context["with_years"] : (function () { throw new RuntimeError('Variable "with_years" does not exist.', 163, $this->source); })())) { + yield ""; + } + // line 164 + if ((isset($context["with_months"]) || array_key_exists("with_months", $context) ? $context["with_months"] : (function () { throw new RuntimeError('Variable "with_months" does not exist.', 164, $this->source); })())) { + yield ""; + } + // line 165 + if ((isset($context["with_weeks"]) || array_key_exists("with_weeks", $context) ? $context["with_weeks"] : (function () { throw new RuntimeError('Variable "with_weeks" does not exist.', 165, $this->source); })())) { + yield ""; + } + // line 166 + if ((isset($context["with_days"]) || array_key_exists("with_days", $context) ? $context["with_days"] : (function () { throw new RuntimeError('Variable "with_days" does not exist.', 166, $this->source); })())) { + yield ""; + } + // line 167 + if ((isset($context["with_hours"]) || array_key_exists("with_hours", $context) ? $context["with_hours"] : (function () { throw new RuntimeError('Variable "with_hours" does not exist.', 167, $this->source); })())) { + yield ""; + } + // line 168 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 168, $this->source); })())) { + yield ""; + } + // line 169 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 169, $this->source); })())) { + yield ""; + } + // line 170 + yield " + +
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 152, $this->source); })()), "years", [], "any", false, false, false, 152), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 153, $this->source); })()), "months", [], "any", false, false, false, 153), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 154, $this->source); })()), "weeks", [], "any", false, false, false, 154), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 155, $this->source); })()), "days", [], "any", false, false, false, 155), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 156, $this->source); })()), "hours", [], "any", false, false, false, 156), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 157, $this->source); })()), "minutes", [], "any", false, false, false, 157), 'label'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 158, $this->source); })()), "seconds", [], "any", false, false, false, 158), 'label'); + yield "
"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 163, $this->source); })()), "years", [], "any", false, false, false, 163), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 164, $this->source); })()), "months", [], "any", false, false, false, 164), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 165, $this->source); })()), "weeks", [], "any", false, false, false, 165), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 166, $this->source); })()), "days", [], "any", false, false, false, 166), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 167, $this->source); })()), "hours", [], "any", false, false, false, 167), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 168, $this->source); })()), "minutes", [], "any", false, false, false, 168), 'widget'); + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 169, $this->source); })()), "seconds", [], "any", false, false, false, 169), 'widget'); + yield "
"; + // line 173 + if ((isset($context["with_invert"]) || array_key_exists("with_invert", $context) ? $context["with_invert"] : (function () { throw new RuntimeError('Variable "with_invert" does not exist.', 173, $this->source); })())) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 173, $this->source); })()), "invert", [], "any", false, false, false, 173), 'widget'); + } + // line 174 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 178 + /** + * @return iterable + */ + public function block_number_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "number_widget")); + + // line 180 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 180, $this->source); })()), "text")) : ("text")); + // line 181 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 184 + /** + * @return iterable + */ + public function block_integer_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "integer_widget")); + + // line 185 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 185, $this->source); })()), "number")) : ("number")); + // line 186 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 189 + /** + * @return iterable + */ + public function block_money_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "money_widget")); + + // line 190 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 190, $this->source); })()), $this->unwrap()->renderBlock("form_widget_simple", $context, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 193 + /** + * @return iterable + */ + public function block_url_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "url_widget")); + + // line 194 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 194, $this->source); })()), "url")) : ("url")); + // line 195 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 198 + /** + * @return iterable + */ + public function block_search_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "search_widget")); + + // line 199 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 199, $this->source); })()), "search")) : ("search")); + // line 200 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 203 + /** + * @return iterable + */ + public function block_percent_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "percent_widget")); + + // line 204 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 204, $this->source); })()), "text")) : ("text")); + // line 205 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + if ((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 205, $this->source); })())) { + yield " "; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("symbol", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 205, $this->source); })()), "%")) : ("%")), "html", null, true); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 208 + /** + * @return iterable + */ + public function block_password_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "password_widget")); + + // line 209 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 209, $this->source); })()), "password")) : ("password")); + // line 210 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 213 + /** + * @return iterable + */ + public function block_hidden_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hidden_widget")); + + // line 214 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 214, $this->source); })()), "hidden")) : ("hidden")); + // line 215 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 218 + /** + * @return iterable + */ + public function block_email_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "email_widget")); + + // line 219 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 219, $this->source); })()), "email")) : ("email")); + // line 220 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 223 + /** + * @return iterable + */ + public function block_range_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "range_widget")); + + // line 224 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 224, $this->source); })()), "range")) : ("range")); + // line 225 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 228 + /** + * @return iterable + */ + public function block_button_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_widget")); + + // line 229 + if (Twig\Extension\CoreExtension::testEmpty((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 229, $this->source); })()))) { + // line 230 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 230, $this->source); })()))) { + // line 231 + $context["label"] = Twig\Extension\CoreExtension::replace((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 231, $this->source); })()), ["%name%" => // line 232 +(isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 232, $this->source); })()), "%id%" => // line 233 +(isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 233, $this->source); })())]); + } elseif ( !( // line 235 +(isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 235, $this->source); })()) === false)) { + // line 236 + $context["label"] = $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->humanize((isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 236, $this->source); })())); + } + } + // line 239 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 256 + /** + * @return iterable + */ + public function block_submit_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "submit_widget")); + + // line 257 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 257, $this->source); })()), "submit")) : ("submit")); + // line 258 + yield from $this->unwrap()->yieldBlock("button_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 261 + /** + * @return iterable + */ + public function block_reset_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "reset_widget")); + + // line 262 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 262, $this->source); })()), "reset")) : ("reset")); + // line 263 + yield from $this->unwrap()->yieldBlock("button_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 266 + /** + * @return iterable + */ + public function block_tel_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "tel_widget")); + + // line 267 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 267, $this->source); })()), "tel")) : ("tel")); + // line 268 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 271 + /** + * @return iterable + */ + public function block_color_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "color_widget")); + + // line 272 + $context["type"] = ((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 272, $this->source); })()), "color")) : ("color")); + // line 273 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 276 + /** + * @return iterable + */ + public function block_week_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "week_widget")); + + // line 277 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 277, $this->source); })()) == "single_text")) { + // line 278 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 280 + $context["vars"] = ((((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 280, $this->source); })()) == "text")) ? (["attr" => ["size" => 1]]) : ([])); + // line 281 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield "> + "; + // line 282 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 282, $this->source); })()), "year", [], "any", false, false, false, 282), 'widget', (isset($context["vars"]) || array_key_exists("vars", $context) ? $context["vars"] : (function () { throw new RuntimeError('Variable "vars" does not exist.', 282, $this->source); })())); + yield "-"; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 282, $this->source); })()), "week", [], "any", false, false, false, 282), 'widget', (isset($context["vars"]) || array_key_exists("vars", $context) ? $context["vars"] : (function () { throw new RuntimeError('Variable "vars" does not exist.', 282, $this->source); })())); + yield " +
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 289 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 290 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 290, $this->source); })()) === false)) { + // line 291 + if ( !(isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 291, $this->source); })())) { + // line 292 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 292, $this->source); })()), ["for" => (isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 292, $this->source); })())]); + } + // line 294 + if ((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 294, $this->source); })())) { + // line 295 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 295, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 295)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 295), "")) : ("")) . " required"))]); + } + // line 297 + yield "<"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 297, $this->source); })()), "label")) : ("label")), "html", null, true); + if ((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 297, $this->source); })())) { + $__internal_compile_4 = $context; + $__internal_compile_5 = ["attr" => (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 297, $this->source); })())]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 297, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + } + yield ">"; + // line 298 + yield from $this->unwrap()->yieldBlock("form_label_content", $context, $blocks); + // line 299 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 299, $this->source); })()), "label")) : ("label")), "html", null, true); + yield ">"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 303 + /** + * @return iterable + */ + public function block_form_label_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label_content")); + + // line 304 + if (Twig\Extension\CoreExtension::testEmpty((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 304, $this->source); })()))) { + // line 305 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 305, $this->source); })()))) { + // line 306 + $context["label"] = Twig\Extension\CoreExtension::replace((isset($context["label_format"]) || array_key_exists("label_format", $context) ? $context["label_format"] : (function () { throw new RuntimeError('Variable "label_format" does not exist.', 306, $this->source); })()), ["%name%" => // line 307 +(isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 307, $this->source); })()), "%id%" => // line 308 +(isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 308, $this->source); })())]); + } else { + // line 311 + $context["label"] = $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->humanize((isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 311, $this->source); })())); + } + } + // line 314 + if (((isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 314, $this->source); })()) === false)) { + // line 315 + if (((isset($context["label_html"]) || array_key_exists("label_html", $context) ? $context["label_html"] : (function () { throw new RuntimeError('Variable "label_html" does not exist.', 315, $this->source); })()) === false)) { + // line 316 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 316, $this->source); })()), "html", null, true); + } else { + // line 318 + yield (isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 318, $this->source); })()); + } + } else { + // line 321 + if (((isset($context["label_html"]) || array_key_exists("label_html", $context) ? $context["label_html"] : (function () { throw new RuntimeError('Variable "label_html" does not exist.', 321, $this->source); })()) === false)) { + // line 322 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 322, $this->source); })()), (isset($context["label_translation_parameters"]) || array_key_exists("label_translation_parameters", $context) ? $context["label_translation_parameters"] : (function () { throw new RuntimeError('Variable "label_translation_parameters" does not exist.', 322, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 322, $this->source); })())), "html", null, true); + } else { + // line 324 + yield $this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 324, $this->source); })()), (isset($context["label_translation_parameters"]) || array_key_exists("label_translation_parameters", $context) ? $context["label_translation_parameters"] : (function () { throw new RuntimeError('Variable "label_translation_parameters" does not exist.', 324, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 324, $this->source); })())); + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 329 + /** + * @return iterable + */ + public function block_button_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_label")); + + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 333 + /** + * @return iterable + */ + public function block_form_help(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_help")); + + // line 334 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 334, $this->source); })()))) { + // line 335 + $context["help_attr"] = Twig\Extension\CoreExtension::merge((isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 335, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", true, true, false, 335)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", false, false, false, 335), "")) : ("")) . " help-text"))]); + // line 336 + yield "<"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 336, $this->source); })()), "div")) : ("div")), "html", null, true); + yield " id=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 336, $this->source); })()), "html", null, true); + yield "_help\""; + $__internal_compile_6 = $context; + $__internal_compile_7 = ["attr" => (isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 336, $this->source); })())]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 336, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + yield ">"; + // line 337 + yield from $this->unwrap()->yieldBlock("form_help_content", $context, $blocks); + // line 338 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 338, $this->source); })()), "div")) : ("div")), "html", null, true); + yield ">"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 342 + /** + * @return iterable + */ + public function block_form_help_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_help_content")); + + // line 343 + if (((isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 343, $this->source); })()) === false)) { + // line 344 + if (((isset($context["help_html"]) || array_key_exists("help_html", $context) ? $context["help_html"] : (function () { throw new RuntimeError('Variable "help_html" does not exist.', 344, $this->source); })()) === false)) { + // line 345 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 345, $this->source); })()), "html", null, true); + } else { + // line 347 + yield (isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 347, $this->source); })()); + } + } else { + // line 350 + if (((isset($context["help_html"]) || array_key_exists("help_html", $context) ? $context["help_html"] : (function () { throw new RuntimeError('Variable "help_html" does not exist.', 350, $this->source); })()) === false)) { + // line 351 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 351, $this->source); })()), (isset($context["help_translation_parameters"]) || array_key_exists("help_translation_parameters", $context) ? $context["help_translation_parameters"] : (function () { throw new RuntimeError('Variable "help_translation_parameters" does not exist.', 351, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 351, $this->source); })())), "html", null, true); + } else { + // line 353 + yield $this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 353, $this->source); })()), (isset($context["help_translation_parameters"]) || array_key_exists("help_translation_parameters", $context) ? $context["help_translation_parameters"] : (function () { throw new RuntimeError('Variable "help_translation_parameters" does not exist.', 353, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 353, $this->source); })())); + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 360 + /** + * @return iterable + */ + public function block_repeated_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "repeated_row")); + + // line 365 + yield from $this->unwrap()->yieldBlock("form_rows", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 368 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 369 + $context["widget_attr"] = []; + // line 370 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 370, $this->source); })()))) { + // line 371 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 371, $this->source); })()) . "_help")]]; + } + // line 373 + yield " (isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 373, $this->source); })())]; + if (!is_iterable($__internal_compile_9)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 373, $this->getSourceContext()); + } + $__internal_compile_9 = CoreExtension::toArray($__internal_compile_9); + $context = $__internal_compile_9 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_8; + yield ">"; + // line 374 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 374, $this->source); })()), 'label'); + // line 375 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 375, $this->source); })()), 'errors'); + // line 376 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 376, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 376, $this->source); })())); + // line 377 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 377, $this->source); })()), 'help'); + // line 378 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 381 + /** + * @return iterable + */ + public function block_button_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_row")); + + // line 382 + yield " (isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 382, $this->source); })())]; + if (!is_iterable($__internal_compile_11)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 382, $this->getSourceContext()); + } + $__internal_compile_11 = CoreExtension::toArray($__internal_compile_11); + $context = $__internal_compile_11 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_10; + yield ">"; + // line 383 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 383, $this->source); })()), 'widget'); + // line 384 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 387 + /** + * @return iterable + */ + public function block_hidden_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hidden_row")); + + // line 388 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 388, $this->source); })()), 'widget'); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 393 + /** + * @return iterable + */ + public function block_form(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form")); + + // line 394 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 394, $this->source); })()), 'form_start'); + // line 395 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 395, $this->source); })()), 'widget'); + // line 396 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 396, $this->source); })()), 'form_end'); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 399 + /** + * @return iterable + */ + public function block_form_start(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_start")); + + // line 400 + CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 400, $this->source); })()), "setMethodRendered", [], "method", false, false, false, 400); + // line 401 + $context["method"] = Twig\Extension\CoreExtension::upper($this->env->getCharset(), (isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 401, $this->source); })())); + // line 402 + if (CoreExtension::inFilter((isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 402, $this->source); })()), ["GET", "POST"])) { + // line 403 + $context["form_method"] = (isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 403, $this->source); })()); + } else { + // line 405 + $context["form_method"] = "POST"; + } + // line 407 + yield "source); })()) != "")) { + yield " name=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 407, $this->source); })()), "html", null, true); + yield "\""; + } + yield " method=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::lower($this->env->getCharset(), (isset($context["form_method"]) || array_key_exists("form_method", $context) ? $context["form_method"] : (function () { throw new RuntimeError('Variable "form_method" does not exist.', 407, $this->source); })())), "html", null, true); + yield "\""; + if (((isset($context["action"]) || array_key_exists("action", $context) ? $context["action"] : (function () { throw new RuntimeError('Variable "action" does not exist.', 407, $this->source); })()) != "")) { + yield " action=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["action"]) || array_key_exists("action", $context) ? $context["action"] : (function () { throw new RuntimeError('Variable "action" does not exist.', 407, $this->source); })()), "html", null, true); + yield "\""; + } + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + if ((isset($context["multipart"]) || array_key_exists("multipart", $context) ? $context["multipart"] : (function () { throw new RuntimeError('Variable "multipart" does not exist.', 407, $this->source); })())) { + yield " enctype=\"multipart/form-data\""; + } + yield ">"; + // line 408 + if (((isset($context["form_method"]) || array_key_exists("form_method", $context) ? $context["form_method"] : (function () { throw new RuntimeError('Variable "form_method" does not exist.', 408, $this->source); })()) != (isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 408, $this->source); })()))) { + // line 409 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 409, $this->source); })()), "html", null, true); + yield "\" />"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 413 + /** + * @return iterable + */ + public function block_form_end(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_end")); + + // line 414 + if (( !array_key_exists("render_rest", $context) || (isset($context["render_rest"]) || array_key_exists("render_rest", $context) ? $context["render_rest"] : (function () { throw new RuntimeError('Variable "render_rest" does not exist.', 414, $this->source); })()))) { + // line 415 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 415, $this->source); })()), 'rest'); + } + // line 417 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 420 + /** + * @return iterable + */ + public function block_form_errors(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_errors")); + + // line 421 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 421, $this->source); })())) > 0)) { + // line 422 + yield "
    "; + // line 423 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 423, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 424 + yield "
  • "; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["error"], "message", [], "any", false, false, false, 424), "html", null, true); + yield "
  • "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['error'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 426 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 430 + /** + * @return iterable + */ + public function block_form_rest(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_rest")); + + // line 431 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 431, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 432 + if ( !CoreExtension::getAttribute($this->env, $this->source, $context["child"], "rendered", [], "any", false, false, false, 432)) { + // line 433 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'row'); + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 437 + if (( !CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 437, $this->source); })()), "methodRendered", [], "any", false, false, false, 437) && Symfony\Bridge\Twig\Extension\twig_is_root_form((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 437, $this->source); })())))) { + // line 438 + CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 438, $this->source); })()), "setMethodRendered", [], "method", false, false, false, 438); + // line 439 + $context["method"] = Twig\Extension\CoreExtension::upper($this->env->getCharset(), (isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 439, $this->source); })())); + // line 440 + if (CoreExtension::inFilter((isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 440, $this->source); })()), ["GET", "POST"])) { + // line 441 + $context["form_method"] = (isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 441, $this->source); })()); + } else { + // line 443 + $context["form_method"] = "POST"; + } + // line 446 + if (((isset($context["form_method"]) || array_key_exists("form_method", $context) ? $context["form_method"] : (function () { throw new RuntimeError('Variable "form_method" does not exist.', 446, $this->source); })()) != (isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 446, $this->source); })()))) { + // line 447 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["method"]) || array_key_exists("method", $context) ? $context["method"] : (function () { throw new RuntimeError('Variable "method" does not exist.', 447, $this->source); })()), "html", null, true); + yield "\" />"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 454 + /** + * @return iterable + */ + public function block_form_rows(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_rows")); + + // line 455 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable(Twig\Extension\CoreExtension::filter($this->env, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 455, $this->source); })()), function ($__child__) use ($context, $macros) { $context["child"] = $__child__; return !CoreExtension::getAttribute($this->env, $this->source, $context["child"], "rendered", [], "any", false, false, false, 455); })); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 456 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'row'); + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 460 + /** + * @return iterable + */ + public function block_widget_attributes(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "widget_attributes")); + + // line 461 + yield "id=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 461, $this->source); })()), "html", null, true); + yield "\" name=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["full_name"]) || array_key_exists("full_name", $context) ? $context["full_name"] : (function () { throw new RuntimeError('Variable "full_name" does not exist.', 461, $this->source); })()), "html", null, true); + yield "\""; + // line 462 + if ((isset($context["disabled"]) || array_key_exists("disabled", $context) ? $context["disabled"] : (function () { throw new RuntimeError('Variable "disabled" does not exist.', 462, $this->source); })())) { + yield " disabled=\"disabled\""; + } + // line 463 + if ((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 463, $this->source); })())) { + yield " required=\"required\""; + } + // line 464 + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 467 + /** + * @return iterable + */ + public function block_widget_container_attributes(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "widget_container_attributes")); + + // line 468 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 468, $this->source); })()))) { + yield "id=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 468, $this->source); })()), "html", null, true); + yield "\""; + } + // line 469 + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 472 + /** + * @return iterable + */ + public function block_button_attributes(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_attributes")); + + // line 473 + yield "id=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 473, $this->source); })()), "html", null, true); + yield "\" name=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["full_name"]) || array_key_exists("full_name", $context) ? $context["full_name"] : (function () { throw new RuntimeError('Variable "full_name" does not exist.', 473, $this->source); })()), "html", null, true); + yield "\""; + if ((isset($context["disabled"]) || array_key_exists("disabled", $context) ? $context["disabled"] : (function () { throw new RuntimeError('Variable "disabled" does not exist.', 473, $this->source); })())) { + yield " disabled=\"disabled\""; + } + // line 474 + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 477 + /** + * @return iterable + */ + public function block_attributes(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "attributes")); + + // line 478 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 478, $this->source); })())); + foreach ($context['_seq'] as $context["attrname"] => $context["attrvalue"]) { + // line 479 + yield " "; + // line 480 + if (CoreExtension::inFilter($context["attrname"], ["placeholder", "title"])) { + // line 481 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["attrname"], "html", null, true); + yield "=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((((((isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 481, $this->source); })()) === false) || (null === $context["attrvalue"]))) ? ($context["attrvalue"]) : ($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans($context["attrvalue"], (isset($context["attr_translation_parameters"]) || array_key_exists("attr_translation_parameters", $context) ? $context["attr_translation_parameters"] : (function () { throw new RuntimeError('Variable "attr_translation_parameters" does not exist.', 481, $this->source); })()), (isset($context["translation_domain"]) || array_key_exists("translation_domain", $context) ? $context["translation_domain"] : (function () { throw new RuntimeError('Variable "translation_domain" does not exist.', 481, $this->source); })())))), "html", null, true); + yield "\""; + } elseif (( // line 482 +$context["attrvalue"] === true)) { + // line 483 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["attrname"], "html", null, true); + yield "=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["attrname"], "html", null, true); + yield "\""; + } elseif ( !( // line 484 +$context["attrvalue"] === false)) { + // line 485 + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["attrname"], "html", null, true); + yield "=\""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["attrvalue"], "html", null, true); + yield "\""; + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['attrname'], $context['attrvalue'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "form_div_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 1880 => 485, 1878 => 484, 1873 => 483, 1871 => 482, 1866 => 481, 1864 => 480, 1862 => 479, 1858 => 478, 1848 => 477, 1840 => 474, 1831 => 473, 1821 => 472, 1813 => 469, 1807 => 468, 1797 => 467, 1789 => 464, 1785 => 463, 1781 => 462, 1775 => 461, 1765 => 460, 1753 => 456, 1749 => 455, 1739 => 454, 1727 => 447, 1725 => 446, 1722 => 443, 1719 => 441, 1717 => 440, 1715 => 439, 1713 => 438, 1711 => 437, 1704 => 433, 1702 => 432, 1698 => 431, 1688 => 430, 1679 => 426, 1671 => 424, 1667 => 423, 1665 => 422, 1663 => 421, 1653 => 420, 1645 => 417, 1642 => 415, 1640 => 414, 1630 => 413, 1619 => 409, 1617 => 408, 1597 => 407, 1594 => 405, 1591 => 403, 1589 => 402, 1587 => 401, 1585 => 400, 1575 => 399, 1567 => 396, 1565 => 395, 1563 => 394, 1553 => 393, 1545 => 388, 1535 => 387, 1527 => 384, 1525 => 383, 1513 => 382, 1503 => 381, 1495 => 378, 1493 => 377, 1491 => 376, 1489 => 375, 1487 => 374, 1475 => 373, 1472 => 371, 1470 => 370, 1468 => 369, 1458 => 368, 1450 => 365, 1440 => 360, 1430 => 353, 1427 => 351, 1425 => 350, 1421 => 347, 1418 => 345, 1416 => 344, 1414 => 343, 1404 => 342, 1393 => 338, 1391 => 337, 1375 => 336, 1373 => 335, 1371 => 334, 1361 => 333, 1345 => 329, 1335 => 324, 1332 => 322, 1330 => 321, 1326 => 318, 1323 => 316, 1321 => 315, 1319 => 314, 1315 => 311, 1312 => 308, 1311 => 307, 1310 => 306, 1308 => 305, 1306 => 304, 1296 => 303, 1285 => 299, 1283 => 298, 1268 => 297, 1265 => 295, 1263 => 294, 1260 => 292, 1258 => 291, 1256 => 290, 1246 => 289, 1233 => 282, 1228 => 281, 1226 => 280, 1223 => 278, 1221 => 277, 1211 => 276, 1203 => 273, 1201 => 272, 1191 => 271, 1183 => 268, 1181 => 267, 1171 => 266, 1163 => 263, 1161 => 262, 1151 => 261, 1143 => 258, 1141 => 257, 1131 => 256, 1123 => 253, 1119 => 250, 1116 => 248, 1114 => 247, 1110 => 244, 1107 => 242, 1105 => 241, 1103 => 240, 1097 => 239, 1093 => 236, 1091 => 235, 1089 => 233, 1088 => 232, 1087 => 231, 1085 => 230, 1083 => 229, 1073 => 228, 1065 => 225, 1063 => 224, 1053 => 223, 1045 => 220, 1043 => 219, 1033 => 218, 1025 => 215, 1023 => 214, 1013 => 213, 1005 => 210, 1003 => 209, 993 => 208, 981 => 205, 979 => 204, 969 => 203, 961 => 200, 959 => 199, 949 => 198, 941 => 195, 939 => 194, 929 => 193, 921 => 190, 911 => 189, 903 => 186, 901 => 185, 891 => 184, 883 => 181, 881 => 180, 871 => 178, 862 => 174, 858 => 173, 854 => 170, 848 => 169, 842 => 168, 836 => 167, 830 => 166, 824 => 165, 818 => 164, 812 => 163, 807 => 159, 801 => 158, 795 => 157, 789 => 156, 783 => 155, 777 => 154, 771 => 153, 765 => 152, 759 => 149, 757 => 148, 753 => 147, 750 => 145, 748 => 144, 738 => 143, 729 => 139, 719 => 138, 714 => 137, 712 => 136, 709 => 134, 707 => 133, 697 => 132, 688 => 128, 686 => 126, 685 => 125, 684 => 124, 683 => 123, 679 => 122, 676 => 120, 674 => 119, 664 => 118, 655 => 114, 653 => 113, 651 => 112, 649 => 111, 647 => 110, 643 => 109, 640 => 107, 638 => 106, 628 => 105, 610 => 102, 600 => 101, 582 => 98, 572 => 97, 532 => 92, 529 => 90, 527 => 89, 525 => 88, 520 => 87, 518 => 86, 501 => 85, 491 => 84, 483 => 81, 481 => 80, 479 => 79, 477 => 78, 469 => 74, 463 => 72, 461 => 71, 459 => 70, 457 => 69, 454 => 68, 452 => 67, 450 => 66, 430 => 64, 428 => 63, 421 => 62, 418 => 60, 416 => 59, 406 => 58, 398 => 55, 392 => 53, 390 => 52, 386 => 51, 382 => 50, 372 => 49, 363 => 45, 360 => 43, 358 => 42, 348 => 41, 336 => 38, 326 => 37, 318 => 34, 315 => 32, 313 => 31, 303 => 30, 295 => 27, 293 => 26, 291 => 25, 288 => 23, 286 => 22, 282 => 21, 272 => 20, 254 => 17, 251 => 15, 249 => 13, 247 => 12, 237 => 11, 228 => 7, 225 => 5, 223 => 4, 213 => 3, 205 => 477, 203 => 472, 201 => 467, 199 => 460, 197 => 454, 194 => 451, 192 => 430, 190 => 420, 188 => 413, 186 => 399, 184 => 393, 182 => 387, 180 => 381, 178 => 368, 176 => 360, 173 => 357, 171 => 342, 168 => 341, 166 => 333, 163 => 332, 161 => 329, 159 => 303, 157 => 289, 155 => 276, 153 => 271, 151 => 266, 149 => 261, 147 => 256, 145 => 228, 143 => 223, 141 => 218, 139 => 213, 137 => 208, 135 => 203, 133 => 198, 131 => 193, 129 => 189, 127 => 184, 125 => 178, 123 => 143, 121 => 132, 119 => 118, 117 => 105, 115 => 101, 113 => 97, 111 => 84, 109 => 58, 107 => 49, 105 => 41, 103 => 37, 101 => 30, 99 => 20, 97 => 11, 95 => 3,); + } + + public function getSourceContext(): Source + { + return new Source("{# Widgets #} + +{%- block form_widget -%} + {% if compound %} + {{- block('form_widget_compound') -}} + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock form_widget -%} + +{%- block form_widget_simple -%} + {%- set type = type|default('text') -%} + {%- if type == 'range' or type == 'color' -%} + {# Attribute \"required\" is not supported #} + {%- set required = false -%} + {%- endif -%} + +{%- endblock form_widget_simple -%} + +{%- block form_widget_compound -%} +
+ {%- if form is rootform -%} + {{ form_errors(form) }} + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
+{%- endblock form_widget_compound -%} + +{%- block collection_widget -%} + {% if prototype is defined and not prototype.rendered %} + {%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%} + {% endif %} + {{- block('form_widget') -}} +{%- endblock collection_widget -%} + +{%- block textarea_widget -%} + +{%- endblock textarea_widget -%} + +{%- block choice_widget -%} + {% if expanded %} + {{- block('choice_widget_expanded') -}} + {% else %} + {{- block('choice_widget_collapsed') -}} + {% endif %} +{%- endblock choice_widget -%} + +{%- block choice_widget_expanded -%} +
+ {%- for child in form %} + {{- form_widget(child) -}} + {{- form_label(child, null, {translation_domain: choice_translation_domain}) -}} + {% endfor -%} +
+{%- endblock choice_widget_expanded -%} + +{%- block choice_widget_collapsed -%} + {%- if required and placeholder is none and not placeholder_in_choices and not multiple and (attr.size is not defined or attr.size <= 1) -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_options -%} + {% for group_label, choice in options %} + {%- if choice is iterable -%} + + {% set options = choice %} + {{- block('choice_widget_options') -}} + + {%- else -%} + + {%- endif -%} + {% endfor %} +{%- endblock choice_widget_options -%} + +{%- block checkbox_widget -%} + +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + +{%- endblock radio_widget -%} + +{%- block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {%- else -%} +
+ {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + {{- form_widget(form.date) -}} + {{- form_widget(form.time) -}} +
+ {%- endif -%} +{%- endblock datetime_widget -%} + +{%- block date_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} +
+ {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
+ {%- endif -%} +{%- endblock date_widget -%} + +{%- block time_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
+ {{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %} +
+ {%- endif -%} +{%- endblock time_widget -%} + +{%- block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} +
+ {{- form_errors(form) -}} + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + +
{{ form_label(form.years) }}{{ form_label(form.months) }}{{ form_label(form.weeks) }}{{ form_label(form.days) }}{{ form_label(form.hours) }}{{ form_label(form.minutes) }}{{ form_label(form.seconds) }}
{{ form_widget(form.years) }}{{ form_widget(form.months) }}{{ form_widget(form.weeks) }}{{ form_widget(form.days) }}{{ form_widget(form.hours) }}{{ form_widget(form.minutes) }}{{ form_widget(form.seconds) }}
+ {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
+ {%- endif -%} +{%- endblock dateinterval_widget -%} + +{%- block number_widget -%} + {# type=\"number\" doesn't work with floats in localized formats #} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }} +{%- endblock number_widget -%} + +{%- block integer_widget -%} + {%- set type = type|default('number') -%} + {{ block('form_widget_simple') }} +{%- endblock integer_widget -%} + +{%- block money_widget -%} + {{ money_pattern|form_encode_currency(block('form_widget_simple')) }} +{%- endblock money_widget -%} + +{%- block url_widget -%} + {%- set type = type|default('url') -%} + {{ block('form_widget_simple') }} +{%- endblock url_widget -%} + +{%- block search_widget -%} + {%- set type = type|default('search') -%} + {{ block('form_widget_simple') }} +{%- endblock search_widget -%} + +{%- block percent_widget -%} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }}{% if symbol %} {{ symbol|default('%') }}{% endif %} +{%- endblock percent_widget -%} + +{%- block password_widget -%} + {%- set type = type|default('password') -%} + {{ block('form_widget_simple') }} +{%- endblock password_widget -%} + +{%- block hidden_widget -%} + {%- set type = type|default('hidden') -%} + {{ block('form_widget_simple') }} +{%- endblock hidden_widget -%} + +{%- block email_widget -%} + {%- set type = type|default('email') -%} + {{ block('form_widget_simple') }} +{%- endblock email_widget -%} + +{%- block range_widget -%} + {% set type = type|default('range') %} + {{- block('form_widget_simple') -}} +{%- endblock range_widget %} + +{%- block button_widget -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- elseif label is not same as(false) -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + +{%- endblock button_widget -%} + +{%- block submit_widget -%} + {%- set type = type|default('submit') -%} + {{ block('button_widget') }} +{%- endblock submit_widget -%} + +{%- block reset_widget -%} + {%- set type = type|default('reset') -%} + {{ block('button_widget') }} +{%- endblock reset_widget -%} + +{%- block tel_widget -%} + {%- set type = type|default('tel') -%} + {{ block('form_widget_simple') }} +{%- endblock tel_widget -%} + +{%- block color_widget -%} + {%- set type = type|default('color') -%} + {{ block('form_widget_simple') }} +{%- endblock color_widget -%} + +{%- block week_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
+ {{ form_widget(form.year, vars) }}-{{ form_widget(form.week, vars) }} +
+ {%- endif -%} +{%- endblock week_widget -%} + +{# Labels #} + +{%- block form_label -%} + {% if label is not same as(false) -%} + {% if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {% if required -%} + {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} + {%- endif -%} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> + {{- block('form_label_content') -}} + + {%- endif -%} +{%- endblock form_label -%} + +{%- block form_label_content -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_label_content -%} + +{%- block button_label -%}{%- endblock -%} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%} + <{{ element|default('div') }} id=\"{{ id }}_help\"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}> + {{- block('form_help_content') -}} + + {%- endif -%} +{%- endblock form_help %} + +{% block form_help_content -%} + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_help_content %} + +{# Rows #} + +{%- block repeated_row -%} + {# + No need to render the errors here, as all errors are mapped + to the first child (see RepeatedTypeValidatorExtension). + #} + {{- block('form_rows') -}} +{%- endblock repeated_row -%} + +{%- block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + + {{- form_label(form) -}} + {{- form_errors(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + +{%- endblock form_row -%} + +{%- block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row -%} + +{%- block hidden_row -%} + {{ form_widget(form) }} +{%- endblock hidden_row -%} + +{# Misc #} + +{%- block form -%} + {{ form_start(form) }} + {{- form_widget(form) -}} + {{ form_end(form) }} +{%- endblock form -%} + +{%- block form_start -%} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in [\"GET\", \"POST\"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = \"POST\" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} +{%- endblock form_start -%} + +{%- block form_end -%} + {%- if not render_rest is defined or render_rest -%} + {{ form_rest(form) }} + {%- endif -%} + +{%- endblock form_end -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} +
    + {%- for error in errors -%} +
  • {{ error.message }}
  • + {%- endfor -%} +
+ {%- endif -%} +{%- endblock form_errors -%} + +{%- block form_rest -%} + {% for child in form -%} + {% if not child.rendered %} + {{- form_row(child) -}} + {% endif %} + {%- endfor -%} + + {% if not form.methodRendered and form is rootform %} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in [\"GET\", \"POST\"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = \"POST\" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} + {% endif -%} +{% endblock form_rest %} + +{# Support #} + +{%- block form_rows -%} + {% for child in form|filter(child => not child.rendered) %} + {{- form_row(child) -}} + {% endfor %} +{%- endblock form_rows -%} + +{%- block widget_attributes -%} + id=\"{{ id }}\" name=\"{{ full_name }}\" + {%- if disabled %} disabled=\"disabled\"{% endif -%} + {%- if required %} required=\"required\"{% endif -%} + {{ block('attributes') }} +{%- endblock widget_attributes -%} + +{%- block widget_container_attributes -%} + {%- if id is not empty %}id=\"{{ id }}\"{% endif -%} + {{ block('attributes') }} +{%- endblock widget_container_attributes -%} + +{%- block button_attributes -%} + id=\"{{ id }}\" name=\"{{ full_name }}\"{% if disabled %} disabled=\"disabled\"{% endif -%} + {{ block('attributes') }} +{%- endblock button_attributes -%} + +{% block attributes -%} + {%- for attrname, attrvalue in attr -%} + {{- \" \" -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}=\"{{ translation_domain is same as(false) or attrvalue is null ? attrvalue : attrvalue|trans(attr_translation_parameters, translation_domain) }}\" + {%- elseif attrvalue is same as(true) -%} + {{- attrname }}=\"{{ attrname }}\" + {%- elseif attrvalue is not same as(false) -%} + {{- attrname }}=\"{{ attrvalue }}\" + {%- endif -%} + {%- endfor -%} +{%- endblock attributes -%} +", "form_div_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/e1/e1f0521d2e4ec6856e3c6ef82b666223.php b/var/cache/dev/twig/e1/e1f0521d2e4ec6856e3c6ef82b666223.php new file mode 100644 index 0000000..d2a3f62 --- /dev/null +++ b/var/cache/dev/twig/e1/e1f0521d2e4ec6856e3c6ef82b666223.php @@ -0,0 +1,1514 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->parent = false; + + // line 1 + $_trait_0 = $this->loadTemplate("bootstrap_base_layout.html.twig", "bootstrap_5_layout.html.twig", 1); + if (!$_trait_0->unwrap()->isTraitable()) { + throw new RuntimeError('Template "'."bootstrap_base_layout.html.twig".'" cannot be used as a trait.', 1, $this->source); + } + $_trait_0_blocks = $_trait_0->unwrap()->getBlocks(); + + $this->traits = $_trait_0_blocks; + + $this->blocks = array_merge( + $this->traits, + [ + 'money_widget' => [$this, 'block_money_widget'], + 'date_widget' => [$this, 'block_date_widget'], + 'time_widget' => [$this, 'block_time_widget'], + 'datetime_widget' => [$this, 'block_datetime_widget'], + 'dateinterval_widget' => [$this, 'block_dateinterval_widget'], + 'percent_widget' => [$this, 'block_percent_widget'], + 'form_widget_simple' => [$this, 'block_form_widget_simple'], + 'widget_attributes' => [$this, 'block_widget_attributes'], + 'button_widget' => [$this, 'block_button_widget'], + 'submit_widget' => [$this, 'block_submit_widget'], + 'checkbox_widget' => [$this, 'block_checkbox_widget'], + 'radio_widget' => [$this, 'block_radio_widget'], + 'choice_widget_collapsed' => [$this, 'block_choice_widget_collapsed'], + 'choice_widget_expanded' => [$this, 'block_choice_widget_expanded'], + 'form_label' => [$this, 'block_form_label'], + 'checkbox_radio_label' => [$this, 'block_checkbox_radio_label'], + 'form_row' => [$this, 'block_form_row'], + 'button_row' => [$this, 'block_button_row'], + 'form_errors' => [$this, 'block_form_errors'], + 'form_help' => [$this, 'block_form_help'], + ] + ); + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "bootstrap_5_layout.html.twig")); + + // line 2 + yield " +"; + // line 4 + yield " +"; + // line 5 + yield from $this->unwrap()->yieldBlock('money_widget', $context, $blocks); + // line 22 + yield " +"; + // line 23 + yield from $this->unwrap()->yieldBlock('date_widget', $context, $blocks); + // line 53 + yield " +"; + // line 54 + yield from $this->unwrap()->yieldBlock('time_widget', $context, $blocks); + // line 92 + yield " +"; + // line 93 + yield from $this->unwrap()->yieldBlock('datetime_widget', $context, $blocks); + // line 109 + yield " +"; + // line 110 + yield from $this->unwrap()->yieldBlock('dateinterval_widget', $context, $blocks); + // line 165 + yield " +"; + // line 166 + yield from $this->unwrap()->yieldBlock('percent_widget', $context, $blocks); + // line 176 + yield " +"; + // line 177 + yield from $this->unwrap()->yieldBlock('form_widget_simple', $context, $blocks); + // line 194 + yield from $this->unwrap()->yieldBlock('widget_attributes', $context, $blocks); + // line 201 + yield from $this->unwrap()->yieldBlock('button_widget', $context, $blocks); + // line 206 + yield from $this->unwrap()->yieldBlock('submit_widget', $context, $blocks); + // line 211 + yield from $this->unwrap()->yieldBlock('checkbox_widget', $context, $blocks); + // line 235 + yield from $this->unwrap()->yieldBlock('radio_widget', $context, $blocks); + // line 256 + yield from $this->unwrap()->yieldBlock('choice_widget_collapsed', $context, $blocks); + // line 261 + yield from $this->unwrap()->yieldBlock('choice_widget_expanded', $context, $blocks); + // line 272 + yield " +"; + // line 275 + yield from $this->unwrap()->yieldBlock('form_label', $context, $blocks); + // line 294 + yield from $this->unwrap()->yieldBlock('checkbox_radio_label', $context, $blocks); + // line 320 + yield " +"; + // line 323 + yield from $this->unwrap()->yieldBlock('form_row', $context, $blocks); + // line 345 + yield from $this->unwrap()->yieldBlock('button_row', $context, $blocks); + // line 350 + yield " +"; + // line 353 + yield from $this->unwrap()->yieldBlock('form_errors', $context, $blocks); + // line 360 + yield " +"; + // line 363 + yield from $this->unwrap()->yieldBlock('form_help', $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 5 + /** + * @return iterable + */ + public function block_money_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "money_widget")); + + // line 6 + $context["prepend"] = !(is_string($__internal_compile_0 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 6, $this->source); })())) && is_string($__internal_compile_1 = "{{") && str_starts_with($__internal_compile_0, $__internal_compile_1)); + // line 7 + $context["append"] = !(is_string($__internal_compile_2 = (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 7, $this->source); })())) && is_string($__internal_compile_3 = "}}") && str_ends_with($__internal_compile_2, $__internal_compile_3)); + // line 8 + if (((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 8, $this->source); })()) || (isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 8, $this->source); })()))) { + // line 9 + yield "
env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("group_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["group_class"]) || array_key_exists("group_class", $context) ? $context["group_class"] : (function () { throw new RuntimeError('Variable "group_class" does not exist.', 9, $this->source); })()), "")) : ("")), "html", null, true); + yield "\">"; + // line 10 + if ((isset($context["prepend"]) || array_key_exists("prepend", $context) ? $context["prepend"] : (function () { throw new RuntimeError('Variable "prepend" does not exist.', 10, $this->source); })())) { + // line 11 + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 11, $this->source); })())); + yield ""; + } + // line 13 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 14 + if ((isset($context["append"]) || array_key_exists("append", $context) ? $context["append"] : (function () { throw new RuntimeError('Variable "append" does not exist.', 14, $this->source); })())) { + // line 15 + yield ""; + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->encodeCurrency($this->env, (isset($context["money_pattern"]) || array_key_exists("money_pattern", $context) ? $context["money_pattern"] : (function () { throw new RuntimeError('Variable "money_pattern" does not exist.', 15, $this->source); })())); + yield ""; + } + // line 17 + yield "
"; + } else { + // line 19 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 23 + /** + * @return iterable + */ + public function block_date_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "date_widget")); + + // line 24 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 24, $this->source); })()) == "single_text")) { + // line 25 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 27 + if ( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 27, $this->source); })())) { + // line 28 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 28, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 28)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 28), "")) : ("")) . " is-invalid"))]); + // line 29 + $context["valid"] = true; + // line 30 + yield " "; + } + // line 31 + if (( !array_key_exists("datetime", $context) || !(isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 31, $this->source); })()))) { + // line 32 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + } + // line 34 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 34, $this->source); })()) === false)) { + // line 35 + yield "
"; + // line 36 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 36, $this->source); })()), "year", [], "any", false, false, false, 36), 'label'); + // line 37 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 37, $this->source); })()), "month", [], "any", false, false, false, 37), 'label'); + // line 38 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 38, $this->source); })()), "day", [], "any", false, false, false, 38), 'label'); + // line 39 + yield "
"; + } + // line 41 + yield "
"; + // line 42 + yield Twig\Extension\CoreExtension::replace((isset($context["date_pattern"]) || array_key_exists("date_pattern", $context) ? $context["date_pattern"] : (function () { throw new RuntimeError('Variable "date_pattern" does not exist.', 42, $this->source); })()), ["{{ year }}" => // line 43 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 43, $this->source); })()), "year", [], "any", false, false, false, 43), 'widget'), "{{ month }}" => // line 44 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 44, $this->source); })()), "month", [], "any", false, false, false, 44), 'widget'), "{{ day }}" => // line 45 +$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 45, $this->source); })()), "day", [], "any", false, false, false, 45), 'widget')]); + // line 47 + yield "
"; + // line 48 + if (( !array_key_exists("datetime", $context) || !(isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 48, $this->source); })()))) { + // line 49 + yield "
"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 54 + /** + * @return iterable + */ + public function block_time_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "time_widget")); + + // line 55 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 55, $this->source); })()) == "single_text")) { + // line 56 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 58 + if ( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 58, $this->source); })())) { + // line 59 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 59, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 59)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 59), "")) : ("")) . " is-invalid"))]); + // line 60 + $context["valid"] = true; + // line 61 + yield " "; + } + // line 62 + if (( !array_key_exists("datetime", $context) || (false == (isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 62, $this->source); })())))) { + // line 63 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + } + // line 65 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 65, $this->source); })()) === false)) { + // line 66 + yield "
"; + // line 67 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 67, $this->source); })()), "hour", [], "any", false, false, false, 67), 'label'); + // line 68 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 68, $this->source); })())) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 68, $this->source); })()), "minute", [], "any", false, false, false, 68), 'label'); + } + // line 69 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 69, $this->source); })())) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 69, $this->source); })()), "second", [], "any", false, false, false, 69), 'label'); + } + // line 70 + yield "
"; + } + // line 72 + if (((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 72, $this->source); })()) || (isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 72, $this->source); })()))) { + // line 73 + yield "
+ "; + } + // line 75 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 75, $this->source); })()), "hour", [], "any", false, false, false, 75), 'widget'); + // line 76 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 76, $this->source); })())) { + // line 77 + yield ":"; + // line 78 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 78, $this->source); })()), "minute", [], "any", false, false, false, 78), 'widget'); + } + // line 80 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 80, $this->source); })())) { + // line 81 + yield ":"; + // line 82 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 82, $this->source); })()), "second", [], "any", false, false, false, 82), 'widget'); + } + // line 84 + if (((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 84, $this->source); })()) || (isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 84, $this->source); })()))) { + // line 85 + yield "
+ "; + } + // line 87 + if (( !array_key_exists("datetime", $context) || (false == (isset($context["datetime"]) || array_key_exists("datetime", $context) ? $context["datetime"] : (function () { throw new RuntimeError('Variable "datetime" does not exist.', 87, $this->source); })())))) { + // line 88 + yield "
"; + } + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 93 + /** + * @return iterable + */ + public function block_datetime_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "datetime_widget")); + + // line 94 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 94, $this->source); })()) == "single_text")) { + // line 95 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 97 + if ( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 97, $this->source); })())) { + // line 98 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 98, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 98)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 98), "")) : ("")) . " is-invalid"))]); + // line 99 + $context["valid"] = true; + // line 100 + yield " "; + } + // line 101 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 102 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 102, $this->source); })()), "date", [], "any", false, false, false, 102), 'widget', ["datetime" => true]); + // line 103 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 103, $this->source); })()), "date", [], "any", false, false, false, 103), 'errors'); + // line 104 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 104, $this->source); })()), "time", [], "any", false, false, false, 104), 'widget', ["datetime" => true]); + // line 105 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 105, $this->source); })()), "time", [], "any", false, false, false, 105), 'errors'); + // line 106 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 110 + /** + * @return iterable + */ + public function block_dateinterval_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "dateinterval_widget")); + + // line 111 + if (((isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 111, $this->source); })()) == "single_text")) { + // line 112 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } else { + // line 114 + if ( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 114, $this->source); })())) { + // line 115 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 115, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 115)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 115), "")) : ("")) . " is-invalid"))]); + // line 116 + $context["valid"] = true; + // line 117 + yield " "; + } + // line 118 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 119 + if ((isset($context["with_years"]) || array_key_exists("with_years", $context) ? $context["with_years"] : (function () { throw new RuntimeError('Variable "with_years" does not exist.', 119, $this->source); })())) { + // line 120 + yield "
+ "; + // line 121 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 121, $this->source); })()), "years", [], "any", false, false, false, 121), 'label'); + yield " + "; + // line 122 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 122, $this->source); })()), "years", [], "any", false, false, false, 122), 'widget'); + yield " +
"; + } + // line 125 + if ((isset($context["with_months"]) || array_key_exists("with_months", $context) ? $context["with_months"] : (function () { throw new RuntimeError('Variable "with_months" does not exist.', 125, $this->source); })())) { + // line 126 + yield "
+ "; + // line 127 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 127, $this->source); })()), "months", [], "any", false, false, false, 127), 'label'); + yield " + "; + // line 128 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 128, $this->source); })()), "months", [], "any", false, false, false, 128), 'widget'); + yield " +
"; + } + // line 131 + if ((isset($context["with_weeks"]) || array_key_exists("with_weeks", $context) ? $context["with_weeks"] : (function () { throw new RuntimeError('Variable "with_weeks" does not exist.', 131, $this->source); })())) { + // line 132 + yield "
+ "; + // line 133 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 133, $this->source); })()), "weeks", [], "any", false, false, false, 133), 'label'); + yield " + "; + // line 134 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 134, $this->source); })()), "weeks", [], "any", false, false, false, 134), 'widget'); + yield " +
"; + } + // line 137 + if ((isset($context["with_days"]) || array_key_exists("with_days", $context) ? $context["with_days"] : (function () { throw new RuntimeError('Variable "with_days" does not exist.', 137, $this->source); })())) { + // line 138 + yield "
+ "; + // line 139 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 139, $this->source); })()), "days", [], "any", false, false, false, 139), 'label'); + yield " + "; + // line 140 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 140, $this->source); })()), "days", [], "any", false, false, false, 140), 'widget'); + yield " +
"; + } + // line 143 + if ((isset($context["with_hours"]) || array_key_exists("with_hours", $context) ? $context["with_hours"] : (function () { throw new RuntimeError('Variable "with_hours" does not exist.', 143, $this->source); })())) { + // line 144 + yield "
+ "; + // line 145 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 145, $this->source); })()), "hours", [], "any", false, false, false, 145), 'label'); + yield " + "; + // line 146 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 146, $this->source); })()), "hours", [], "any", false, false, false, 146), 'widget'); + yield " +
"; + } + // line 149 + if ((isset($context["with_minutes"]) || array_key_exists("with_minutes", $context) ? $context["with_minutes"] : (function () { throw new RuntimeError('Variable "with_minutes" does not exist.', 149, $this->source); })())) { + // line 150 + yield "
+ "; + // line 151 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 151, $this->source); })()), "minutes", [], "any", false, false, false, 151), 'label'); + yield " + "; + // line 152 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 152, $this->source); })()), "minutes", [], "any", false, false, false, 152), 'widget'); + yield " +
"; + } + // line 155 + if ((isset($context["with_seconds"]) || array_key_exists("with_seconds", $context) ? $context["with_seconds"] : (function () { throw new RuntimeError('Variable "with_seconds" does not exist.', 155, $this->source); })())) { + // line 156 + yield "
+ "; + // line 157 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 157, $this->source); })()), "seconds", [], "any", false, false, false, 157), 'label'); + yield " + "; + // line 158 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 158, $this->source); })()), "seconds", [], "any", false, false, false, 158), 'widget'); + yield " +
"; + } + // line 161 + if ((isset($context["with_invert"]) || array_key_exists("with_invert", $context) ? $context["with_invert"] : (function () { throw new RuntimeError('Variable "with_invert" does not exist.', 161, $this->source); })())) { + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 161, $this->source); })()), "invert", [], "any", false, false, false, 161), 'widget'); + } + // line 162 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 166 + /** + * @return iterable + */ + public function block_percent_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "percent_widget")); + + // line 167 + if ((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 167, $this->source); })())) { + // line 168 + yield "
"; + // line 169 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + // line 170 + yield ""; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("symbol", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["symbol"]) || array_key_exists("symbol", $context) ? $context["symbol"] : (function () { throw new RuntimeError('Variable "symbol" does not exist.', 170, $this->source); })()), "%")) : ("%")), "html", null, true); + yield " +
"; + } else { + // line 173 + yield from $this->unwrap()->yieldBlock("form_widget_simple", $context, $blocks); + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 177 + /** + * @return iterable + */ + public function block_form_widget_simple(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_widget_simple")); + + // line 178 + if (( !array_key_exists("type", $context) || ((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 178, $this->source); })()) != "hidden"))) { + // line 179 + $context["widget_class"] = " form-control"; + // line 180 + if ((((array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 180, $this->source); })()), "")) : ("")) == "color")) { + // line 181 + $context["widget_class"] = ((isset($context["widget_class"]) || array_key_exists("widget_class", $context) ? $context["widget_class"] : (function () { throw new RuntimeError('Variable "widget_class" does not exist.', 181, $this->source); })()) . " form-control-color"); + } elseif (((( // line 182 +array_key_exists("type", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 182, $this->source); })()), "")) : ("")) == "range")) { + // line 183 + $context["widget_class"] = " form-range"; + } + // line 185 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 185, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 185)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 185), "")) : ("")) . (isset($context["widget_class"]) || array_key_exists("widget_class", $context) ? $context["widget_class"] : (function () { throw new RuntimeError('Variable "widget_class" does not exist.', 185, $this->source); })())))]); + } + // line 187 + if ((array_key_exists("type", $context) && CoreExtension::inFilter((isset($context["type"]) || array_key_exists("type", $context) ? $context["type"] : (function () { throw new RuntimeError('Variable "type" does not exist.', 187, $this->source); })()), ["range", "color"]))) { + // line 188 + yield " "; + // line 189 + yield " "; + $context["required"] = false; + // line 190 + yield " "; + } + // line 191 + yield from $this->yieldParentBlock("form_widget_simple", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 194 + /** + * @return iterable + */ + public function block_widget_attributes(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "widget_attributes")); + + // line 195 + if ( !(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 195, $this->source); })())) { + // line 196 + yield " "; + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 196, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 196)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 196), "")) : ("")) . " is-invalid"))]); + // line 197 + yield " "; + } + // line 198 + yield from $this->yieldParentBlock("widget_attributes", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 201 + /** + * @return iterable + */ + public function block_button_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_widget")); + + // line 202 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 202, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 202)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 202), "btn-secondary")) : ("btn-secondary")) . " btn"))]); + // line 203 + yield from $this->yieldParentBlock("button_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 206 + /** + * @return iterable + */ + public function block_submit_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "submit_widget")); + + // line 207 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 207, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 207)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 207), "btn-primary")) : ("btn-primary")))]); + // line 208 + yield from $this->yieldParentBlock("submit_widget", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 211 + /** + * @return iterable + */ + public function block_checkbox_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_widget")); + + // line 212 + $context["attr_class"] = ((array_key_exists("attr_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 212, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 212)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 212), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 212)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 212), "")) : ("")))); + // line 213 + $context["row_class"] = ""; + // line 214 + if (!CoreExtension::inFilter("btn-check", (isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 214, $this->source); })()))) { + // line 215 + $context["attr_class"] = ((isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 215, $this->source); })()) . " form-check-input"); + // line 216 + $context["row_class"] = "form-check"; + } + // line 218 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 218, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 218, $this->source); })()))]); + // line 219 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 219, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 219)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 219), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 219)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 219), "")) : ("")))); + // line 220 + if (CoreExtension::inFilter("checkbox-inline", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 220, $this->source); })()))) { + // line 221 + $context["row_class"] = ((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 221, $this->source); })()) . " form-check-inline"); + } + // line 223 + if (CoreExtension::inFilter("checkbox-switch", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 223, $this->source); })()))) { + // line 224 + $context["row_class"] = ((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 224, $this->source); })()) . " form-switch"); + } + // line 226 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 226, $this->source); })()))) { + // line 227 + yield "
env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 227, $this->source); })()), "html", null, true); + yield "\">"; + } + // line 229 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 229, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("checkbox_widget", $context, $blocks)]); + // line 230 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 230, $this->source); })()))) { + // line 231 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 235 + /** + * @return iterable + */ + public function block_radio_widget(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "radio_widget")); + + // line 236 + $context["attr_class"] = ((array_key_exists("attr_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 236, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 236)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 236), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 236)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 236), "")) : ("")))); + // line 237 + $context["row_class"] = ""; + // line 238 + if (!CoreExtension::inFilter("btn-check", (isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 238, $this->source); })()))) { + // line 239 + $context["attr_class"] = ((isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 239, $this->source); })()) . " form-check-input"); + // line 240 + $context["row_class"] = "form-check"; + } + // line 242 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 242, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((isset($context["attr_class"]) || array_key_exists("attr_class", $context) ? $context["attr_class"] : (function () { throw new RuntimeError('Variable "attr_class" does not exist.', 242, $this->source); })()))]); + // line 243 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 243, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 243)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 243), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 243)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 243), "")) : ("")))); + // line 244 + if (CoreExtension::inFilter("radio-inline", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 244, $this->source); })()))) { + // line 245 + $context["row_class"] = ((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 245, $this->source); })()) . " form-check-inline"); + } + // line 247 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 247, $this->source); })()))) { + // line 248 + yield "
env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 248, $this->source); })()), "html", null, true); + yield "\">"; + } + // line 250 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 250, $this->source); })()), 'label', ["widget" => $this->renderParentBlock("radio_widget", $context, $blocks)]); + // line 251 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 251, $this->source); })()))) { + // line 252 + yield "
"; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 256 + /** + * @return iterable + */ + public function block_choice_widget_collapsed(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_collapsed")); + + // line 257 + $context["attr"] = Twig\Extension\CoreExtension::merge((isset($context["attr"]) || array_key_exists("attr", $context) ? $context["attr"] : (function () { throw new RuntimeError('Variable "attr" does not exist.', 257, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", true, true, false, 257)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["attr"] ?? null), "class", [], "any", false, false, false, 257), "")) : ("")) . " form-select"))]); + // line 258 + yield from $this->yieldParentBlock("choice_widget_collapsed", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 261 + /** + * @return iterable + */ + public function block_choice_widget_expanded(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "choice_widget_expanded")); + + // line 262 + yield "
unwrap()->yieldBlock("widget_container_attributes", $context, $blocks); + yield ">"; + // line 263 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 263, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["child"]) { + // line 264 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock($context["child"], 'widget', ["parent_label_class" => ((CoreExtension::getAttribute($this->env, $this->source, // line 265 +($context["label_attr"] ?? null), "class", [], "any", true, true, false, 265)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 265), "")) : ("")), "translation_domain" => // line 266 +(isset($context["choice_translation_domain"]) || array_key_exists("choice_translation_domain", $context) ? $context["choice_translation_domain"] : (function () { throw new RuntimeError('Variable "choice_translation_domain" does not exist.', 266, $this->source); })()), "valid" => // line 267 +(isset($context["valid"]) || array_key_exists("valid", $context) ? $context["valid"] : (function () { throw new RuntimeError('Variable "valid" does not exist.', 267, $this->source); })())]); + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['child'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 270 + yield "
"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 275 + /** + * @return iterable + */ + public function block_form_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_label")); + + // line 276 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 276, $this->source); })()) === false)) { + // line 277 + $context["parent_label_class"] = ((array_key_exists("parent_label_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 277, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 277)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 277), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 277)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 277), "")) : ("")))); + // line 278 + if ((array_key_exists("compound", $context) && (isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 278, $this->source); })()))) { + // line 279 + $context["element"] = "legend"; + // line 280 + if (!CoreExtension::inFilter("col-form-label", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 280, $this->source); })()))) { + // line 281 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 281, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 281)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 281), "")) : ("")) . " col-form-label"))]); + } + } else { + // line 284 + $context["row_class"] = ((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 284, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 284)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 284), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 284)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 284), "")) : ("")))); + // line 285 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 285, $this->source); })()), ["for" => (isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 285, $this->source); })())]); + // line 286 + if (!CoreExtension::inFilter("col-form-label", (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 286, $this->source); })()))) { + // line 287 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 287, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 287)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 287), "")) : ("")) . ((CoreExtension::inFilter("input-group", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 287, $this->source); })()))) ? (" input-group-text") : (" form-label"))))]); + } + } + } + // line 291 + yield from $this->yieldParentBlock("form_label", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 294 + /** + * @return iterable + */ + public function block_checkbox_radio_label(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "checkbox_radio_label")); + + // line 296 + if (array_key_exists("widget", $context)) { + // line 297 + $context["label_attr_class"] = ((array_key_exists("label_attr_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["label_attr_class"]) || array_key_exists("label_attr_class", $context) ? $context["label_attr_class"] : (function () { throw new RuntimeError('Variable "label_attr_class" does not exist.', 297, $this->source); })()), ((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 297)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 297), "")) : ("")))) : (((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 297)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 297), "")) : ("")))); + // line 298 + if (!CoreExtension::inFilter("btn", (isset($context["label_attr_class"]) || array_key_exists("label_attr_class", $context) ? $context["label_attr_class"] : (function () { throw new RuntimeError('Variable "label_attr_class" does not exist.', 298, $this->source); })()))) { + // line 299 + $context["label_attr_class"] = ((isset($context["label_attr_class"]) || array_key_exists("label_attr_class", $context) ? $context["label_attr_class"] : (function () { throw new RuntimeError('Variable "label_attr_class" does not exist.', 299, $this->source); })()) . " form-check-label"); + } + // line 301 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 301, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((isset($context["label_attr_class"]) || array_key_exists("label_attr_class", $context) ? $context["label_attr_class"] : (function () { throw new RuntimeError('Variable "label_attr_class" does not exist.', 301, $this->source); })()))]); + // line 302 + if ( !(isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 302, $this->source); })())) { + // line 303 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 303, $this->source); })()), ["for" => (isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 303, $this->source); })())]); + } + // line 305 + if ((isset($context["required"]) || array_key_exists("required", $context) ? $context["required"] : (function () { throw new RuntimeError('Variable "required" does not exist.', 305, $this->source); })())) { + // line 306 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 306, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 306)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 306), "")) : ("")) . " required"))]); + } + // line 308 + if (array_key_exists("parent_label_class", $context)) { + // line 309 + $context["label_attr"] = Twig\Extension\CoreExtension::merge((isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 309, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(Twig\Extension\CoreExtension::replace(((((CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", true, true, false, 309)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["label_attr"] ?? null), "class", [], "any", false, false, false, 309), "")) : ("")) . " ") . (isset($context["parent_label_class"]) || array_key_exists("parent_label_class", $context) ? $context["parent_label_class"] : (function () { throw new RuntimeError('Variable "parent_label_class" does not exist.', 309, $this->source); })())), ["checkbox-inline" => "", "radio-inline" => ""]))]); + } + // line 312 + yield (isset($context["widget"]) || array_key_exists("widget", $context) ? $context["widget"] : (function () { throw new RuntimeError('Variable "widget" does not exist.', 312, $this->source); })()); + yield " + (isset($context["label_attr"]) || array_key_exists("label_attr", $context) ? $context["label_attr"] : (function () { throw new RuntimeError('Variable "label_attr" does not exist.', 313, $this->source); })())]; + if (!is_iterable($__internal_compile_5)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 313, $this->getSourceContext()); + } + $__internal_compile_5 = CoreExtension::toArray($__internal_compile_5); + $context = $__internal_compile_5 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_4; + yield ">"; + // line 314 + if ( !((isset($context["label"]) || array_key_exists("label", $context) ? $context["label"] : (function () { throw new RuntimeError('Variable "label" does not exist.', 314, $this->source); })()) === false)) { + // line 315 + yield from $this->unwrap()->yieldBlock("form_label_content", $context, $blocks); + } + // line 317 + yield ""; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 323 + /** + * @return iterable + */ + public function block_form_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_row")); + + // line 324 + if ((array_key_exists("compound", $context) && (isset($context["compound"]) || array_key_exists("compound", $context) ? $context["compound"] : (function () { throw new RuntimeError('Variable "compound" does not exist.', 324, $this->source); })()))) { + // line 325 + $context["element"] = "fieldset"; + } + // line 327 + $context["widget_attr"] = []; + // line 328 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 328, $this->source); })()))) { + // line 329 + $context["widget_attr"] = ["attr" => ["aria-describedby" => ((isset($context["id"]) || array_key_exists("id", $context) ? $context["id"] : (function () { throw new RuntimeError('Variable "id" does not exist.', 329, $this->source); })()) . "_help")]]; + } + // line 331 + $context["row_class"] = ((array_key_exists("row_class", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 331, $this->source); })()), Twig\Extension\CoreExtension::trim(((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 331)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 331), "mb-3")) : ("mb-3"))))) : (Twig\Extension\CoreExtension::trim(((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 331)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 331), "mb-3")) : ("mb-3"))))); + // line 332 + yield "<"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 332, $this->source); })()), "div")) : ("div")), "html", null, true); + $__internal_compile_6 = $context; + $__internal_compile_7 = ["attr" => Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 332, $this->source); })()), ["class" => (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 332, $this->source); })())])]; + if (!is_iterable($__internal_compile_7)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 332, $this->getSourceContext()); + } + $__internal_compile_7 = CoreExtension::toArray($__internal_compile_7); + $context = $__internal_compile_7 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_6; + yield ">"; + // line 333 + if (CoreExtension::inFilter("form-floating", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 333, $this->source); })()))) { + // line 334 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 334, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 334, $this->source); })())); + // line 335 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 335, $this->source); })()), 'label'); + } else { + // line 337 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 337, $this->source); })()), 'label'); + // line 338 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 338, $this->source); })()), 'widget', (isset($context["widget_attr"]) || array_key_exists("widget_attr", $context) ? $context["widget_attr"] : (function () { throw new RuntimeError('Variable "widget_attr" does not exist.', 338, $this->source); })())); + } + // line 340 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 340, $this->source); })()), 'help'); + // line 341 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 341, $this->source); })()), 'errors'); + // line 342 + yield "env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("element", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["element"]) || array_key_exists("element", $context) ? $context["element"] : (function () { throw new RuntimeError('Variable "element" does not exist.', 342, $this->source); })()), "div")) : ("div")), "html", null, true); + yield ">"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 345 + /** + * @return iterable + */ + public function block_button_row(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "button_row")); + + // line 346 + yield " Twig\Extension\CoreExtension::merge((isset($context["row_attr"]) || array_key_exists("row_attr", $context) ? $context["row_attr"] : (function () { throw new RuntimeError('Variable "row_attr" does not exist.', 346, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 346)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 346), "mb-3")) : ("mb-3")))])]; + if (!is_iterable($__internal_compile_9)) { + throw new RuntimeError('Variables passed to the "with" tag must be a mapping.', 346, $this->getSourceContext()); + } + $__internal_compile_9 = CoreExtension::toArray($__internal_compile_9); + $context = $__internal_compile_9 + $context + $this->env->getGlobals(); + yield from $this->unwrap()->yieldBlock("attributes", $context, $blocks); + $context = $__internal_compile_8; + yield ">"; + // line 347 + yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 347, $this->source); })()), 'widget'); + // line 348 + yield ""; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 353 + /** + * @return iterable + */ + public function block_form_errors(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_errors")); + + // line 354 + if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 354, $this->source); })())) > 0)) { + // line 355 + $context['_parent'] = $context; + $context['_seq'] = CoreExtension::ensureTraversable((isset($context["errors"]) || array_key_exists("errors", $context) ? $context["errors"] : (function () { throw new RuntimeError('Variable "errors" does not exist.', 355, $this->source); })())); + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 356 + yield "
source); })()))) { + yield "invalid-feedback"; + } else { + yield "alert alert-danger"; + } + yield " d-block\">"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["error"], "message", [], "any", false, false, false, 356), "html", null, true); + yield "
"; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_key'], $context['error'], $context['_parent']); + $context = array_intersect_key($context, $_parent) + $_parent; + } + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + // line 363 + /** + * @return iterable + */ + public function block_form_help(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "form_help")); + + // line 364 + $context["row_class"] = ((CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", true, true, false, 364)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["row_attr"] ?? null), "class", [], "any", false, false, false, 364), "")) : ("")); + // line 365 + $context["help_class"] = " form-text"; + // line 366 + if (CoreExtension::inFilter("input-group", (isset($context["row_class"]) || array_key_exists("row_class", $context) ? $context["row_class"] : (function () { throw new RuntimeError('Variable "row_class" does not exist.', 366, $this->source); })()))) { + // line 368 + $context["help_class"] = " input-group-text"; + } + // line 370 + if ( !Twig\Extension\CoreExtension::testEmpty((isset($context["help"]) || array_key_exists("help", $context) ? $context["help"] : (function () { throw new RuntimeError('Variable "help" does not exist.', 370, $this->source); })()))) { + // line 371 + $context["help_attr"] = Twig\Extension\CoreExtension::merge((isset($context["help_attr"]) || array_key_exists("help_attr", $context) ? $context["help_attr"] : (function () { throw new RuntimeError('Variable "help_attr" does not exist.', 371, $this->source); })()), ["class" => Twig\Extension\CoreExtension::trim(((((CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", true, true, false, 371)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, ($context["help_attr"] ?? null), "class", [], "any", false, false, false, 371), "")) : ("")) . (isset($context["help_class"]) || array_key_exists("help_class", $context) ? $context["help_class"] : (function () { throw new RuntimeError('Variable "help_class" does not exist.', 371, $this->source); })())) . " mb-0"))]); + } + // line 373 + yield from $this->yieldParentBlock("form_help", $context, $blocks); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "bootstrap_5_layout.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 1113 => 373, 1110 => 371, 1108 => 370, 1105 => 368, 1103 => 366, 1101 => 365, 1099 => 364, 1089 => 363, 1068 => 356, 1064 => 355, 1062 => 354, 1052 => 353, 1044 => 348, 1042 => 347, 1030 => 346, 1020 => 345, 1010 => 342, 1008 => 341, 1006 => 340, 1003 => 338, 1001 => 337, 998 => 335, 996 => 334, 994 => 333, 981 => 332, 979 => 331, 976 => 329, 974 => 328, 972 => 327, 969 => 325, 967 => 324, 957 => 323, 948 => 317, 945 => 315, 943 => 314, 932 => 313, 928 => 312, 925 => 309, 923 => 308, 920 => 306, 918 => 305, 915 => 303, 913 => 302, 911 => 301, 908 => 299, 906 => 298, 904 => 297, 902 => 296, 892 => 294, 884 => 291, 879 => 287, 877 => 286, 875 => 285, 873 => 284, 869 => 281, 867 => 280, 865 => 279, 863 => 278, 861 => 277, 859 => 276, 849 => 275, 841 => 270, 835 => 267, 834 => 266, 833 => 265, 832 => 264, 828 => 263, 824 => 262, 814 => 261, 806 => 258, 804 => 257, 794 => 256, 785 => 252, 783 => 251, 781 => 250, 776 => 248, 774 => 247, 771 => 245, 769 => 244, 767 => 243, 765 => 242, 762 => 240, 760 => 239, 758 => 238, 756 => 237, 754 => 236, 744 => 235, 735 => 231, 733 => 230, 731 => 229, 726 => 227, 724 => 226, 721 => 224, 719 => 223, 716 => 221, 714 => 220, 712 => 219, 710 => 218, 707 => 216, 705 => 215, 703 => 214, 701 => 213, 699 => 212, 689 => 211, 681 => 208, 679 => 207, 669 => 206, 661 => 203, 659 => 202, 649 => 201, 641 => 198, 638 => 197, 635 => 196, 633 => 195, 623 => 194, 615 => 191, 612 => 190, 609 => 189, 607 => 188, 605 => 187, 602 => 185, 599 => 183, 597 => 182, 595 => 181, 593 => 180, 591 => 179, 589 => 178, 579 => 177, 570 => 173, 564 => 170, 562 => 169, 560 => 168, 558 => 167, 548 => 166, 539 => 162, 535 => 161, 530 => 158, 526 => 157, 523 => 156, 521 => 155, 516 => 152, 512 => 151, 509 => 150, 507 => 149, 502 => 146, 498 => 145, 495 => 144, 493 => 143, 488 => 140, 484 => 139, 481 => 138, 479 => 137, 474 => 134, 470 => 133, 467 => 132, 465 => 131, 460 => 128, 456 => 127, 453 => 126, 451 => 125, 446 => 122, 442 => 121, 439 => 120, 437 => 119, 433 => 118, 430 => 117, 428 => 116, 425 => 115, 423 => 114, 420 => 112, 418 => 111, 408 => 110, 399 => 106, 397 => 105, 395 => 104, 393 => 103, 391 => 102, 387 => 101, 384 => 100, 382 => 99, 379 => 98, 377 => 97, 374 => 95, 372 => 94, 362 => 93, 352 => 88, 350 => 87, 346 => 85, 344 => 84, 341 => 82, 339 => 81, 337 => 80, 334 => 78, 332 => 77, 330 => 76, 328 => 75, 324 => 73, 322 => 72, 319 => 70, 315 => 69, 311 => 68, 309 => 67, 307 => 66, 305 => 65, 300 => 63, 298 => 62, 295 => 61, 293 => 60, 290 => 59, 288 => 58, 285 => 56, 283 => 55, 273 => 54, 263 => 49, 261 => 48, 259 => 47, 257 => 45, 256 => 44, 255 => 43, 254 => 42, 252 => 41, 249 => 39, 247 => 38, 245 => 37, 243 => 36, 241 => 35, 239 => 34, 234 => 32, 232 => 31, 229 => 30, 227 => 29, 224 => 28, 222 => 27, 219 => 25, 217 => 24, 207 => 23, 198 => 19, 195 => 17, 190 => 15, 188 => 14, 186 => 13, 181 => 11, 179 => 10, 175 => 9, 173 => 8, 171 => 7, 169 => 6, 159 => 5, 151 => 363, 148 => 360, 146 => 353, 143 => 350, 141 => 345, 139 => 323, 136 => 320, 134 => 294, 132 => 275, 129 => 272, 127 => 261, 125 => 256, 123 => 235, 121 => 211, 119 => 206, 117 => 201, 115 => 194, 113 => 177, 110 => 176, 108 => 166, 105 => 165, 103 => 110, 100 => 109, 98 => 93, 95 => 92, 93 => 54, 90 => 53, 88 => 23, 85 => 22, 83 => 5, 80 => 4, 77 => 2, 35 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% use \"bootstrap_base_layout.html.twig\" %} + +{# Widgets #} + +{% block money_widget -%} + {%- set prepend = not (money_pattern starts with '{{') -%} + {%- set append = not (money_pattern ends with '}}') -%} + {%- if prepend or append -%} +
+ {%- if prepend -%} + {{ money_pattern|form_encode_currency }} + {%- endif -%} + {{- block('form_widget_simple') -}} + {%- if append -%} + {{ money_pattern|form_encode_currency }} + {%- endif -%} +
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock money_widget %} + +{% block date_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} + {%- if datetime is not defined or not datetime -%} +
+ {%- endif %} + {%- if label is not same as(false) -%} +
+ {{- form_label(form.year) -}} + {{- form_label(form.month) -}} + {{- form_label(form.day) -}} +
+ {%- endif -%} +
+ {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
+ {%- if datetime is not defined or not datetime -%} +
+ {%- endif -%} + {%- endif -%} +{%- endblock date_widget %} + +{% block time_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} + {%- if datetime is not defined or false == datetime -%} +
+ {%- endif -%} + {%- if label is not same as(false) -%} +
+ {{- form_label(form.hour) -}} + {%- if with_minutes -%}{{ form_label(form.minute) }}{%- endif -%} + {%- if with_seconds -%}{{ form_label(form.second) }}{%- endif -%} +
+ {%- endif -%} + {% if with_minutes or with_seconds %} +
+ {% endif %} + {{- form_widget(form.hour) -}} + {%- if with_minutes -%} + : + {{- form_widget(form.minute) -}} + {%- endif -%} + {%- if with_seconds -%} + : + {{- form_widget(form.second) -}} + {%- endif -%} + {% if with_minutes or with_seconds %} +
+ {% endif %} + {%- if datetime is not defined or false == datetime -%} +
+ {%- endif -%} + {%- endif -%} +{%- endblock time_widget %} + +{% block datetime_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} +
+ {{- form_widget(form.date, { datetime: true } ) -}} + {{- form_errors(form.date) -}} + {{- form_widget(form.time, { datetime: true } ) -}} + {{- form_errors(form.time) -}} +
+ {%- endif -%} +{%- endblock datetime_widget %} + +{% block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} +
+ {%- if with_years -%} +
+ {{ form_label(form.years) }} + {{ form_widget(form.years) }} +
+ {%- endif -%} + {%- if with_months -%} +
+ {{ form_label(form.months) }} + {{ form_widget(form.months) }} +
+ {%- endif -%} + {%- if with_weeks -%} +
+ {{ form_label(form.weeks) }} + {{ form_widget(form.weeks) }} +
+ {%- endif -%} + {%- if with_days -%} +
+ {{ form_label(form.days) }} + {{ form_widget(form.days) }} +
+ {%- endif -%} + {%- if with_hours -%} +
+ {{ form_label(form.hours) }} + {{ form_widget(form.hours) }} +
+ {%- endif -%} + {%- if with_minutes -%} +
+ {{ form_label(form.minutes) }} + {{ form_widget(form.minutes) }} +
+ {%- endif -%} + {%- if with_seconds -%} +
+ {{ form_label(form.seconds) }} + {{ form_widget(form.seconds) }} +
+ {%- endif -%} + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
+ {%- endif -%} +{%- endblock dateinterval_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
+ {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block form_widget_simple -%} + {%- if type is not defined or type != 'hidden' %} + {%- set widget_class = ' form-control' %} + {%- if type|default('') == 'color' -%} + {%- set widget_class = widget_class ~ ' form-control-color' -%} + {%- elseif type|default('') == 'range' -%} + {%- set widget_class = ' form-range' -%} + {%- endif -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ widget_class)|trim}) -%} + {% endif -%} + {%- if type is defined and type in ['range', 'color'] %} + {# Attribute \"required\" is not supported #} + {% set required = false %} + {% endif -%} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{%- block widget_attributes -%} + {%- if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %} + {% endif -%} + {{ parent() }} +{%- endblock widget_attributes -%} + +{%- block button_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%} + {{- parent() -}} +{%- endblock button_widget %} + +{%- block submit_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-primary'))|trim}) -%} + {{- parent() -}} +{%- endblock submit_widget %} + +{%- block checkbox_widget -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'checkbox-inline' in parent_label_class %} + {%- set row_class = row_class ~ ' form-check-inline' -%} + {% endif -%} + {%- if 'checkbox-switch' in parent_label_class %} + {%- set row_class = row_class ~ ' form-switch' -%} + {% endif -%} + {%- if row_class is not empty -%} +
+ {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
+ {%- endif -%} +{%- endblock checkbox_widget %} + +{%- block radio_widget -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'radio-inline' in parent_label_class -%} + {%- set row_class = row_class ~ ' form-check-inline' -%} + {%- endif -%} + {%- if row_class is not empty -%} +
+ {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
+ {%- endif -%} +{%- endblock radio_widget %} + +{%- block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-select')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_expanded -%} +
+ {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + valid: valid, + }) -}} + {% endfor -%} +
+{%- endblock choice_widget_expanded %} + +{# Labels #} + +{%- block form_label -%} + {% if label is not same as(false) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if compound is defined and compound -%} + {%- set element = 'legend' -%} + {%- if 'col-form-label' not in parent_label_class -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label' )|trim}) -%} + {%- endif -%} + {%- else -%} + {%- set row_class = row_class|default(row_attr.class|default('')) -%} + {%- set label_attr = label_attr|merge({for: id}) -%} + {%- if 'col-form-label' not in parent_label_class -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ('input-group' in row_class ? ' input-group-text' : ' form-label') )|trim}) -%} + {%- endif -%} + {%- endif -%} + {%- endif -%} + {{- parent() -}} +{%- endblock form_label %} + +{%- block checkbox_radio_label -%} + {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} + {%- if widget is defined -%} + {%- set label_attr_class = label_attr_class|default(label_attr.class|default('')) -%} + {%- if 'btn' not in label_attr_class -%} + {%- set label_attr_class = label_attr_class ~ ' form-check-label' -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: label_attr_class|trim}) -%} + {%- if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {%- if required -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} + {%- endif -%} + {%- if parent_label_class is defined -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} + {%- endif -%} + + {{ widget|raw }} + + {%- if label is not same as(false) -%} + {{- block('form_label_content') -}} + {%- endif -%} + + {%- endif -%} +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{%- block form_row -%} + {%- if compound is defined and compound -%} + {%- set element = 'fieldset' -%} + {%- endif -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~\"_help\"}} -%} + {%- endif -%} + {%- set row_class = row_class|default(row_attr.class|default('mb-3')|trim) -%} + <{{ element|default('div') }}{% with {attr: row_attr|merge({class: row_class})} %}{{ block('attributes') }}{% endwith %}> + {%- if 'form-floating' in row_class -%} + {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} + {%- else -%} + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {%- endif -%} + {{- form_help(form) -}} + {{- form_errors(form) -}} + +{%- endblock form_row %} + +{%- block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row %} + +{# Errors #} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} + {%- for error in errors -%} +
{{ error.message }}
+ {%- endfor -%} + {%- endif %} +{%- endblock form_errors %} + +{# Help #} + +{%- block form_help -%} + {%- set row_class = row_attr.class|default('') -%} + {%- set help_class = ' form-text' -%} + {%- if 'input-group' in row_class -%} + {#- Hack to properly display help with input group -#} + {%- set help_class = ' input-group-text' -%} + {%- endif -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ help_class ~ ' mb-0')|trim}) -%} + {%- endif -%} + {{- parent() -}} +{%- endblock form_help %} +", "bootstrap_5_layout.html.twig", "/home/skylar/Projects/mycomments-api/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_layout.html.twig"); + } +} diff --git a/var/cache/dev/twig/f2/f2dc52e36444af6f0017c9e0e6d293c3.php b/var/cache/dev/twig/f2/f2dc52e36444af6f0017c9e0e6d293c3.php new file mode 100644 index 0000000..f766317 --- /dev/null +++ b/var/cache/dev/twig/f2/f2dc52e36444af6f0017c9e0e6d293c3.php @@ -0,0 +1,110 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + 'hwi_oauth_content' => [$this, 'block_hwi_oauth_content'], + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "@HWIOAuth/layout.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@HWIOAuth/Connect/registration_success.html.twig")); + + $this->parent = $this->loadTemplate("@HWIOAuth/layout.html.twig", "@HWIOAuth/Connect/registration_success.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + // line 3 + /** + * @return iterable + */ + public function block_hwi_oauth_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hwi_oauth_content")); + + // line 4 + yield "

"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("header.registration_success", ["%username%" => CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 4, $this->source); })()), "user", [], "any", false, false, false, 4), "username", [], "any", false, false, false, 4)], "HWIOAuthBundle"), "html", null, true); + yield "

+"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@HWIOAuth/Connect/registration_success.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 67 => 4, 57 => 3, 40 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} +

{{ 'header.registration_success' | trans({'%username%': app.user.username}, 'HWIOAuthBundle') }}

+{% endblock hwi_oauth_content %} +", "@HWIOAuth/Connect/registration_success.html.twig", "/home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration_success.html.twig"); + } +} diff --git a/var/cache/dev/twig/fc/fc2a8a5206984f7ccc5da1c6624e4d5e.php b/var/cache/dev/twig/fc/fc2a8a5206984f7ccc5da1c6624e4d5e.php new file mode 100644 index 0000000..62c795c --- /dev/null +++ b/var/cache/dev/twig/fc/fc2a8a5206984f7ccc5da1c6624e4d5e.php @@ -0,0 +1,110 @@ + + */ + private array $macros = []; + + public function __construct(Environment $env) + { + parent::__construct($env); + + $this->source = $this->getSourceContext(); + + $this->blocks = [ + 'hwi_oauth_content' => [$this, 'block_hwi_oauth_content'], + ]; + } + + protected function doGetParent(array $context): bool|string|Template|TemplateWrapper + { + // line 1 + return "@HWIOAuth/layout.html.twig"; + } + + protected function doDisplay(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@HWIOAuth/Connect/connect_success.html.twig")); + + $this->parent = $this->loadTemplate("@HWIOAuth/layout.html.twig", "@HWIOAuth/Connect/connect_success.html.twig", 1); + yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks)); + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + } + + // line 3 + /** + * @return iterable + */ + public function block_hwi_oauth_content(array $context, array $blocks = []): iterable + { + $macros = $this->macros; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"]; + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "hwi_oauth_content")); + + // line 4 + yield "

"; + yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("header.success", ["%name%" => CoreExtension::getAttribute($this->env, $this->source, (isset($context["userInformation"]) || array_key_exists("userInformation", $context) ? $context["userInformation"] : (function () { throw new RuntimeError('Variable "userInformation" does not exist.', 4, $this->source); })()), "realName", [], "any", false, false, false, 4)], "HWIOAuthBundle"), "html", null, true); + yield "

+"; + + $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof); + + yield from []; + } + + /** + * @codeCoverageIgnore + */ + public function getTemplateName(): string + { + return "@HWIOAuth/Connect/connect_success.html.twig"; + } + + /** + * @codeCoverageIgnore + */ + public function isTraitable(): bool + { + return false; + } + + /** + * @codeCoverageIgnore + */ + public function getDebugInfo(): array + { + return array ( 67 => 4, 57 => 3, 40 => 1,); + } + + public function getSourceContext(): Source + { + return new Source("{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} +

{{ 'header.success' | trans({'%name%': userInformation.realName}, 'HWIOAuthBundle') }}

+{% endblock hwi_oauth_content %} +", "@HWIOAuth/Connect/connect_success.html.twig", "/home/skylar/Projects/mycomments-api/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_success.html.twig"); + } +} diff --git a/var/cache/dev/url_generating_routes.php b/var/cache/dev/url_generating_routes.php new file mode 100644 index 0000000..ba94d47 --- /dev/null +++ b/var/cache/dev/url_generating_routes.php @@ -0,0 +1,11 @@ + [['code', '_format'], ['_controller' => 'error_controller::preview', '_format' => 'html'], ['code' => '\\d+'], [['variable', '.', '[^/]++', '_format', true], ['variable', '/', '\\d+', 'code', true], ['text', '/_error']], [], [], []], + 'hwi_oauth_service_redirect' => [['service'], ['_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\RedirectToServiceController', 'redirectToServiceAction']], [], [['variable', '/', '[^/]++', 'service', true], ['text', '/connect']], [], [], []], + 'hwi_oauth_connect_service' => [['service'], ['_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\ConnectController', 'connectServiceAction']], [], [['variable', '/', '[^/]++', 'service', true], ['text', '/connect/service']], [], [], []], + 'hwi_oauth_connect_registration' => [['key'], ['_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\RegisterController', 'registrationAction']], [], [['variable', '/', '[^/]++', 'key', true], ['text', '/connect/registration']], [], [], []], + 'hwi_oauth_connect' => [[], ['_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\LoginController', 'connectAction']], [], [['text', '/login/']], [], [], []], +]; diff --git a/var/cache/dev/url_generating_routes.php.meta b/var/cache/dev/url_generating_routes.php.meta new file mode 100644 index 0000000..260b684 Binary files /dev/null and b/var/cache/dev/url_generating_routes.php.meta differ diff --git a/var/cache/dev/url_matching_routes.php b/var/cache/dev/url_matching_routes.php new file mode 100644 index 0000000..9215bea --- /dev/null +++ b/var/cache/dev/url_matching_routes.php @@ -0,0 +1,33 @@ + [[['_route' => 'hwi_oauth_connect', '_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\LoginController', 'connectAction']], null, ['GET' => 0], null, true, false, null]], + ], + [ // $regexpList + 0 => '{^(?' + .'|/_error/(\\d+)(?:\\.([^/]++))?(*:35)' + .'|/connect/(?' + .'|([^/]++)(*:62)' + .'|service/([^/]++)(*:85)' + .'|registration/([^/]++)(*:113)' + .')' + .')/?$}sDu', + ], + [ // $dynamicRoutes + 35 => [[['_route' => '_preview_error', '_controller' => 'error_controller::preview', '_format' => 'html'], ['code', '_format'], null, null, false, true, null]], + 62 => [[['_route' => 'hwi_oauth_service_redirect', '_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\RedirectToServiceController', 'redirectToServiceAction']], ['service'], ['GET' => 0], null, false, true, null]], + 85 => [[['_route' => 'hwi_oauth_connect_service', '_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\ConnectController', 'connectServiceAction']], ['service'], ['GET' => 0, 'POST' => 1], null, false, true, null]], + 113 => [ + [['_route' => 'hwi_oauth_connect_registration', '_controller' => ['HWI\\Bundle\\OAuthBundle\\Controller\\Connect\\RegisterController', 'registrationAction']], ['key'], ['GET' => 0, 'POST' => 1], null, false, true, null], + [null, null, null, null, false, false, 0], + ], + ], + null, // $checkCondition +]; diff --git a/var/cache/dev/url_matching_routes.php.meta b/var/cache/dev/url_matching_routes.php.meta new file mode 100644 index 0000000..260b684 Binary files /dev/null and b/var/cache/dev/url_matching_routes.php.meta differ diff --git a/var/data.db b/var/data.db new file mode 100644 index 0000000..e69de29 diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..e631802 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ + dirname(__DIR__, 1), +]); + +[$app, $args] = $runtime + ->getResolver($app) + ->resolve(); + +$app = $app(...$args); + +exit( + $runtime + ->getRunner($app) + ->run() +); diff --git a/vendor/bin/doctrine-dbal b/vendor/bin/doctrine-dbal new file mode 100755 index 0000000..4ed6f70 --- /dev/null +++ b/vendor/bin/doctrine-dbal @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal'); + } +} + +return include __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal'; diff --git a/vendor/bin/doctrine-migrations b/vendor/bin/doctrine-migrations new file mode 100755 index 0000000..9517060 --- /dev/null +++ b/vendor/bin/doctrine-migrations @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/doctrine/migrations/bin/doctrine-migrations'); + } +} + +return include __DIR__ . '/..'.'/doctrine/migrations/bin/doctrine-migrations'; diff --git a/vendor/bin/patch-type-declarations b/vendor/bin/patch-type-declarations new file mode 100755 index 0000000..4e63fef --- /dev/null +++ b/vendor/bin/patch-type-declarations @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations'); + } +} + +return include __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations'; diff --git a/vendor/bin/php-parse b/vendor/bin/php-parse new file mode 100755 index 0000000..61566e6 --- /dev/null +++ b/vendor/bin/php-parse @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'); + } +} + +return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'; diff --git a/vendor/bin/sql-formatter b/vendor/bin/sql-formatter new file mode 100755 index 0000000..ed0a69d --- /dev/null +++ b/vendor/bin/sql-formatter @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/doctrine/sql-formatter/bin/sql-formatter'); + } +} + +return include __DIR__ . '/..'.'/doctrine/sql-formatter/bin/sql-formatter'; diff --git a/vendor/bin/var-dump-server b/vendor/bin/var-dump-server new file mode 100755 index 0000000..18db1c1 --- /dev/null +++ b/vendor/bin/var-dump-server @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); + } +} + +return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'; diff --git a/vendor/bin/yaml-lint b/vendor/bin/yaml-lint new file mode 100755 index 0000000..388092f --- /dev/null +++ b/vendor/bin/yaml-lint @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint'); + } +} + +return include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint'; diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..7824d8f --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..1c09986 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,27 @@ + $vendorDir . '/symfony/polyfill-intl-icu/Resources/stubs/Collator.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'DateError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateError.php', + 'DateException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateException.php', + 'DateInvalidOperationException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php', + 'DateInvalidTimeZoneException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php', + 'DateMalformedIntervalStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php', + 'DateMalformedPeriodStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php', + 'DateMalformedStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php', + 'DateObjectError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php', + 'DateRangeError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php', + 'IntlDateFormatter' => $vendorDir . '/symfony/polyfill-intl-icu/Resources/stubs/IntlDateFormatter.php', + 'Locale' => $vendorDir . '/symfony/polyfill-intl-icu/Resources/stubs/Locale.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'NumberFormatter' => $vendorDir . '/symfony/polyfill-intl-icu/Resources/stubs/NumberFormatter.php', + 'Override' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/Override.php', + 'SQLite3Exception' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php', + '©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..23ec22e --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,23 @@ + $vendorDir . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + '89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php', + 'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php', + 'c7baa00073ee9c61edf148c51917cfb4' => $vendorDir . '/twig/twig/src/Resources/escaper.php', + 'f844ccf1d25df8663951193c3fc307c8' => $vendorDir . '/twig/twig/src/Resources/string_loader.php', + 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', + '2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php', + '6a47392539ca2329373e0d33e1dba053' => $vendorDir . '/symfony/polyfill-intl-icu/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/twig/twig/src'), + 'Symfony\\Runtime\\Symfony\\Component\\' => array($vendorDir . '/symfony/runtime/Internal'), + 'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), + 'Symfony\\Polyfill\\Intl\\Icu\\' => array($vendorDir . '/symfony/polyfill-intl-icu'), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), + 'Symfony\\Flex\\' => array($vendorDir . '/symfony/flex/src'), + '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\\Contracts\\Cache\\' => array($vendorDir . '/symfony/cache-contracts'), + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'), + 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), + 'Symfony\\Component\\TypeInfo\\' => array($vendorDir . '/symfony/type-info'), + 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), + 'Symfony\\Component\\Stopwatch\\' => array($vendorDir . '/symfony/stopwatch'), + 'Symfony\\Component\\Security\\Http\\' => array($vendorDir . '/symfony/security-http'), + 'Symfony\\Component\\Security\\Csrf\\' => array($vendorDir . '/symfony/security-csrf'), + 'Symfony\\Component\\Security\\Core\\' => array($vendorDir . '/symfony/security-core'), + 'Symfony\\Component\\Runtime\\' => array($vendorDir . '/symfony/runtime'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\PropertyInfo\\' => array($vendorDir . '/symfony/property-info'), + 'Symfony\\Component\\PropertyAccess\\' => array($vendorDir . '/symfony/property-access'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\PasswordHasher\\' => array($vendorDir . '/symfony/password-hasher'), + 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'), + 'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'), + 'Symfony\\Component\\Mailer\\' => array($vendorDir . '/symfony/mailer'), + 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\HttpClient\\' => array($vendorDir . '/symfony/http-client'), + 'Symfony\\Component\\Form\\' => array($vendorDir . '/symfony/form'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'), + 'Symfony\\Component\\Dotenv\\' => array($vendorDir . '/symfony/dotenv'), + 'Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'), + 'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'), + 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), + 'Symfony\\Bundle\\TwigBundle\\' => array($vendorDir . '/symfony/twig-bundle'), + 'Symfony\\Bundle\\SecurityBundle\\' => array($vendorDir . '/symfony/security-bundle'), + 'Symfony\\Bundle\\MakerBundle\\' => array($vendorDir . '/symfony/maker-bundle/src'), + 'Symfony\\Bundle\\FrameworkBundle\\' => array($vendorDir . '/symfony/framework-bundle'), + 'Symfony\\Bridge\\Twig\\' => array($vendorDir . '/symfony/twig-bridge'), + 'Symfony\\Bridge\\Doctrine\\' => array($vendorDir . '/symfony/doctrine-bridge'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), + 'Lexik\\Bundle\\JWTAuthenticationBundle\\' => array($vendorDir . '/lexik/jwt-authentication-bundle'), + 'Lcobucci\\JWT\\' => array($vendorDir . '/lcobucci/jwt/src'), + 'Lcobucci\\Clock\\' => array($vendorDir . '/lcobucci/clock/src'), + 'HWI\\Bundle\\OAuthBundle\\' => array($vendorDir . '/hwi/oauth-bundle/src'), + 'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), + 'Doctrine\\SqlFormatter\\' => array($vendorDir . '/doctrine/sql-formatter/src'), + 'Doctrine\\Persistence\\' => array($vendorDir . '/doctrine/persistence/src/Persistence'), + 'Doctrine\\ORM\\' => array($vendorDir . '/doctrine/orm/src'), + 'Doctrine\\Migrations\\' => array($vendorDir . '/doctrine/migrations/src'), + 'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'), + 'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'), + 'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'), + 'Doctrine\\DBAL\\' => array($vendorDir . '/doctrine/dbal/src'), + 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'), + 'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/src'), + 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'), + 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/event-manager/src'), + 'Doctrine\\Bundle\\MigrationsBundle\\' => array($vendorDir . '/doctrine/doctrine-migrations-bundle'), + 'Doctrine\\Bundle\\DoctrineBundle\\' => array($vendorDir . '/doctrine/doctrine-bundle/src'), + 'App\\Tests\\' => array($baseDir . '/tests'), + 'App\\' => array($baseDir . '/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..748a816 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInit5faecc4248439f1e5842b80b0f0be1f9::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..1f97666 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,481 @@ + __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + '89efb1254ef2d1c5d80096acd12c4098' => __DIR__ . '/..' . '/twig/twig/src/Resources/core.php', + 'ffecb95d45175fd40f75be8a23b34f90' => __DIR__ . '/..' . '/twig/twig/src/Resources/debug.php', + 'c7baa00073ee9c61edf148c51917cfb4' => __DIR__ . '/..' . '/twig/twig/src/Resources/escaper.php', + 'f844ccf1d25df8663951193c3fc307c8' => __DIR__ . '/..' . '/twig/twig/src/Resources/string_loader.php', + 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', + '2203a247e6fda86070a5e4e07aed533a' => __DIR__ . '/..' . '/symfony/clock/Resources/now.php', + '6a47392539ca2329373e0d33e1dba053' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'T' => + array ( + 'Twig\\' => 5, + ), + 'S' => + array ( + 'Symfony\\Runtime\\Symfony\\Component\\' => 34, + 'Symfony\\Polyfill\\Php83\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Idn\\' => 26, + 'Symfony\\Polyfill\\Intl\\Icu\\' => 26, + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, + 'Symfony\\Flex\\' => 13, + 'Symfony\\Contracts\\Translation\\' => 30, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\HttpClient\\' => 29, + 'Symfony\\Contracts\\EventDispatcher\\' => 34, + 'Symfony\\Contracts\\Cache\\' => 24, + 'Symfony\\Component\\Yaml\\' => 23, + 'Symfony\\Component\\VarExporter\\' => 30, + 'Symfony\\Component\\VarDumper\\' => 28, + 'Symfony\\Component\\TypeInfo\\' => 27, + 'Symfony\\Component\\String\\' => 25, + 'Symfony\\Component\\Stopwatch\\' => 28, + 'Symfony\\Component\\Security\\Http\\' => 32, + 'Symfony\\Component\\Security\\Csrf\\' => 32, + 'Symfony\\Component\\Security\\Core\\' => 32, + 'Symfony\\Component\\Runtime\\' => 26, + 'Symfony\\Component\\Routing\\' => 26, + 'Symfony\\Component\\PropertyInfo\\' => 31, + 'Symfony\\Component\\PropertyAccess\\' => 33, + 'Symfony\\Component\\Process\\' => 26, + 'Symfony\\Component\\PasswordHasher\\' => 33, + 'Symfony\\Component\\OptionsResolver\\' => 34, + 'Symfony\\Component\\Mime\\' => 23, + 'Symfony\\Component\\Mailer\\' => 25, + 'Symfony\\Component\\HttpKernel\\' => 29, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\HttpClient\\' => 29, + 'Symfony\\Component\\Form\\' => 23, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\ErrorHandler\\' => 31, + 'Symfony\\Component\\Dotenv\\' => 25, + 'Symfony\\Component\\DependencyInjection\\' => 38, + 'Symfony\\Component\\Console\\' => 26, + 'Symfony\\Component\\Config\\' => 25, + 'Symfony\\Component\\Clock\\' => 24, + 'Symfony\\Component\\Cache\\' => 24, + 'Symfony\\Bundle\\TwigBundle\\' => 26, + 'Symfony\\Bundle\\SecurityBundle\\' => 30, + 'Symfony\\Bundle\\MakerBundle\\' => 27, + 'Symfony\\Bundle\\FrameworkBundle\\' => 31, + 'Symfony\\Bridge\\Twig\\' => 20, + 'Symfony\\Bridge\\Doctrine\\' => 24, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\EventDispatcher\\' => 20, + 'Psr\\Container\\' => 14, + 'Psr\\Clock\\' => 10, + 'Psr\\Cache\\' => 10, + 'PhpParser\\' => 10, + ), + 'L' => + array ( + 'Lexik\\Bundle\\JWTAuthenticationBundle\\' => 37, + 'Lcobucci\\JWT\\' => 13, + 'Lcobucci\\Clock\\' => 15, + ), + 'H' => + array ( + 'HWI\\Bundle\\OAuthBundle\\' => 23, + ), + 'E' => + array ( + 'Egulias\\EmailValidator\\' => 23, + ), + 'D' => + array ( + 'Doctrine\\SqlFormatter\\' => 22, + 'Doctrine\\Persistence\\' => 21, + 'Doctrine\\ORM\\' => 13, + 'Doctrine\\Migrations\\' => 20, + 'Doctrine\\Instantiator\\' => 22, + 'Doctrine\\Inflector\\' => 19, + 'Doctrine\\Deprecations\\' => 22, + 'Doctrine\\DBAL\\' => 14, + 'Doctrine\\Common\\Lexer\\' => 22, + 'Doctrine\\Common\\Collections\\' => 28, + 'Doctrine\\Common\\Cache\\' => 22, + 'Doctrine\\Common\\' => 16, + 'Doctrine\\Bundle\\MigrationsBundle\\' => 33, + 'Doctrine\\Bundle\\DoctrineBundle\\' => 31, + ), + 'A' => + array ( + 'App\\Tests\\' => 10, + 'App\\' => 4, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/src', + ), + 'Symfony\\Runtime\\Symfony\\Component\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/runtime/Internal', + ), + 'Symfony\\Polyfill\\Php83\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php83', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Idn\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', + ), + 'Symfony\\Polyfill\\Intl\\Icu\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-icu', + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', + ), + 'Symfony\\Flex\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/flex/src', + ), + '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\\Contracts\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache-contracts', + ), + 'Symfony\\Component\\Yaml\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/yaml', + ), + 'Symfony\\Component\\VarExporter\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-exporter', + ), + 'Symfony\\Component\\VarDumper\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-dumper', + ), + 'Symfony\\Component\\TypeInfo\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/type-info', + ), + 'Symfony\\Component\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/string', + ), + 'Symfony\\Component\\Stopwatch\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/stopwatch', + ), + 'Symfony\\Component\\Security\\Http\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/security-http', + ), + 'Symfony\\Component\\Security\\Csrf\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/security-csrf', + ), + 'Symfony\\Component\\Security\\Core\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/security-core', + ), + 'Symfony\\Component\\Runtime\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/runtime', + ), + 'Symfony\\Component\\Routing\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/routing', + ), + 'Symfony\\Component\\PropertyInfo\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/property-info', + ), + 'Symfony\\Component\\PropertyAccess\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/property-access', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + 'Symfony\\Component\\PasswordHasher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/password-hasher', + ), + 'Symfony\\Component\\OptionsResolver\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/options-resolver', + ), + 'Symfony\\Component\\Mime\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/mime', + ), + 'Symfony\\Component\\Mailer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/mailer', + ), + 'Symfony\\Component\\HttpKernel\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-kernel', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\HttpClient\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-client', + ), + 'Symfony\\Component\\Form\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/form', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\ErrorHandler\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/error-handler', + ), + 'Symfony\\Component\\Dotenv\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/dotenv', + ), + 'Symfony\\Component\\DependencyInjection\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/dependency-injection', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Symfony\\Component\\Config\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/config', + ), + 'Symfony\\Component\\Clock\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/clock', + ), + 'Symfony\\Component\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache', + ), + 'Symfony\\Bundle\\TwigBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/twig-bundle', + ), + 'Symfony\\Bundle\\SecurityBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/security-bundle', + ), + 'Symfony\\Bundle\\MakerBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/maker-bundle/src', + ), + 'Symfony\\Bundle\\FrameworkBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/framework-bundle', + ), + 'Symfony\\Bridge\\Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/twig-bridge', + ), + 'Symfony\\Bridge\\Doctrine\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/doctrine-bridge', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/event-dispatcher/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Clock\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/clock/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'PhpParser\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser', + ), + 'Lexik\\Bundle\\JWTAuthenticationBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/lexik/jwt-authentication-bundle', + ), + 'Lcobucci\\JWT\\' => + array ( + 0 => __DIR__ . '/..' . '/lcobucci/jwt/src', + ), + 'Lcobucci\\Clock\\' => + array ( + 0 => __DIR__ . '/..' . '/lcobucci/clock/src', + ), + 'HWI\\Bundle\\OAuthBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/hwi/oauth-bundle/src', + ), + 'Egulias\\EmailValidator\\' => + array ( + 0 => __DIR__ . '/..' . '/egulias/email-validator/src', + ), + 'Doctrine\\SqlFormatter\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/sql-formatter/src', + ), + 'Doctrine\\Persistence\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/persistence/src/Persistence', + ), + 'Doctrine\\ORM\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/orm/src', + ), + 'Doctrine\\Migrations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/migrations/src', + ), + 'Doctrine\\Instantiator\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator', + ), + 'Doctrine\\Inflector\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector', + ), + 'Doctrine\\Deprecations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations', + ), + 'Doctrine\\DBAL\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/dbal/src', + ), + 'Doctrine\\Common\\Lexer\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/lexer/src', + ), + 'Doctrine\\Common\\Collections\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/collections/src', + ), + 'Doctrine\\Common\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache', + ), + 'Doctrine\\Common\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/event-manager/src', + ), + 'Doctrine\\Bundle\\MigrationsBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/doctrine-migrations-bundle', + ), + 'Doctrine\\Bundle\\DoctrineBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/doctrine-bundle/src', + ), + 'App\\Tests\\' => + array ( + 0 => __DIR__ . '/../..' . '/tests', + ), + 'App\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + ); + + public static $classMap = array ( + 'Collator' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/Resources/stubs/Collator.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'DateError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateError.php', + 'DateException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateException.php', + 'DateInvalidOperationException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php', + 'DateInvalidTimeZoneException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php', + 'DateMalformedIntervalStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php', + 'DateMalformedPeriodStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php', + 'DateMalformedStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php', + 'DateObjectError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php', + 'DateRangeError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php', + 'IntlDateFormatter' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/Resources/stubs/IntlDateFormatter.php', + 'Locale' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/Resources/stubs/Locale.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'NumberFormatter' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/Resources/stubs/NumberFormatter.php', + 'Override' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/Override.php', + 'SQLite3Exception' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php', + '©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit5faecc4248439f1e5842b80b0f0be1f9::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit5faecc4248439f1e5842b80b0f0be1f9::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit5faecc4248439f1e5842b80b0f0be1f9::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..c01149c --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,6536 @@ +{ + "packages": [ + { + "name": "doctrine/cache", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" + }, + "time": "2022-05-20T20:07:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.2.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "install-path": "../doctrine/cache" + }, + { + "name": "doctrine/collections", + "version": "2.2.2", + "version_normalized": "2.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/d8af7f248c74f195f7347424600fd9e17b57af59", + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1", + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "ext-json": "*", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.11" + }, + "time": "2024-04-18T06:56:21+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/2.2.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcollections", + "type": "tidelift" + } + ], + "install-path": "../doctrine/collections" + }, + { + "name": "doctrine/dbal", + "version": "3.9.1", + "version_normalized": "3.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", + "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3|^1", + "doctrine/event-manager": "^1|^2", + "php": "^7.4 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "12.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.12.0", + "phpstan/phpstan-strict-rules": "^1.6", + "phpunit/phpunit": "9.6.20", + "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", + "squizlabs/php_codesniffer": "3.10.2", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0", + "vimeo/psalm": "4.30.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "time": "2024-09-01T13:49:23+00:00", + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.9.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "install-path": "../doctrine/dbal" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.3", + "version_normalized": "1.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "time": "2024-01-30T19:34:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "install-path": "../doctrine/deprecations" + }, + { + "name": "doctrine/doctrine-bundle", + "version": "2.13.0", + "version_normalized": "2.13.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "ca59d84b8e63143ce1aed90cdb333ba329d71563" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/ca59d84b8e63143ce1aed90cdb333ba329d71563", + "reference": "ca59d84b8e63143ce1aed90cdb333ba329d71563", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/persistence": "^2.2 || ^3", + "doctrine/sql-formatter": "^1.0.1", + "php": "^7.4 || ^8.0", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^5.4.19 || ^6.0.7 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" + }, + "conflict": { + "doctrine/annotations": ">=3.0", + "doctrine/orm": "<2.17 || >=4.0", + "twig/twig": "<1.34 || >=2.0 <2.4" + }, + "require-dev": { + "doctrine/annotations": "^1 || ^2", + "doctrine/coding-standard": "^12", + "doctrine/deprecations": "^1.0", + "doctrine/orm": "^2.17 || ^3.0", + "friendsofphp/proxy-manager-lts": "^1.0", + "phpunit/phpunit": "^9.5.26", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^5", + "psr/log": "^1.1.4 || ^2.0 || ^3.0", + "symfony/phpunit-bridge": "^6.1 || ^7.0", + "symfony/property-info": "^5.4 || ^6.0 || ^7.0", + "symfony/proxy-manager-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/security-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^5.4 || ^6.2 || ^7.0", + "symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", + "twig/twig": "^1.34 || ^2.12 || ^3.0", + "vimeo/psalm": "^5.15" + }, + "suggest": { + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "ext-pdo": "*", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "time": "2024-09-01T09:46:40+00:00", + "type": "symfony-bundle", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org/" + } + ], + "description": "Symfony DoctrineBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineBundle/issues", + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "install-path": "../doctrine/doctrine-bundle" + }, + { + "name": "doctrine/doctrine-migrations-bundle", + "version": "3.3.1", + "version_normalized": "3.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", + "reference": "715b62c31a5894afcb2b2cdbbc6607d7dd0580c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/715b62c31a5894afcb2b2cdbbc6607d7dd0580c0", + "reference": "715b62c31a5894afcb2b2cdbbc6607d7dd0580c0", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "^2.4", + "doctrine/migrations": "^3.2", + "php": "^7.2|^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.6 || ^3", + "doctrine/persistence": "^2.0 || ^3 ", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^8.5|^9.5", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^3 || ^5", + "symfony/phpunit-bridge": "^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6 || ^7", + "vimeo/psalm": "^4.30 || ^5.15" + }, + "time": "2024-05-14T20:32:18+00:00", + "type": "symfony-bundle", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\MigrationsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DoctrineMigrationsBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "dbal", + "migrations", + "schema" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.3.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-migrations-bundle", + "type": "tidelift" + } + ], + "install-path": "../doctrine/doctrine-migrations-bundle" + }, + { + "name": "doctrine/event-manager", + "version": "2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "time": "2024-05-22T20:47:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "install-path": "../doctrine/event-manager" + }, + { + "name": "doctrine/inflector", + "version": "2.0.10", + "version_normalized": "2.0.10.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "time": "2024-02-18T20:23:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.10" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "install-path": "../doctrine/inflector" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "time": "2022-12-30T00:23:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "install-path": "../doctrine/instantiator" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "version_normalized": "3.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "time": "2024-02-05T11:56:58+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "install-path": "../doctrine/lexer" + }, + { + "name": "doctrine/migrations", + "version": "3.8.1", + "version_normalized": "3.8.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "7760fbd0b7cb58bfb50415505a7bab821adf0877" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/7760fbd0b7cb58bfb50415505a7bab821adf0877", + "reference": "7760fbd0b7cb58bfb50415505a7bab821adf0877", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/dbal": "^3.6 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2.0", + "php": "^8.1", + "psr/log": "^1.1.3 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" + }, + "conflict": { + "doctrine/orm": "<2.12 || >=4" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3", + "doctrine/sql-formatter": "^1.0", + "ext-pdo_sqlite": "*", + "fig/log-test": "^1", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.4", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^10.3", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "time": "2024-08-28T13:17:28+00:00", + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/3.8.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fmigrations", + "type": "tidelift" + } + ], + "install-path": "../doctrine/migrations" + }, + { + "name": "doctrine/orm", + "version": "3.2.2", + "version_normalized": "3.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "831a1eb7d260925528cdbb49cc1866c0357cf147" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/831a1eb7d260925528cdbb49cc1866c0357cf147", + "reference": "831a1eb7d260925528cdbb49cc1866c0357cf147", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3 || ^2", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1", + "ext-ctype": "*", + "php": "^8.1", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "1.11.1", + "phpunit/phpunit": "^10.4.0", + "psr/log": "^1 || ^2 || ^3", + "squizlabs/php_codesniffer": "3.7.2", + "symfony/cache": "^5.4 || ^6.2 || ^7.0", + "vimeo/psalm": "5.24.0" + }, + "suggest": { + "ext-dom": "Provides support for XSD validation for XML mapping files", + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" + }, + "time": "2024-08-23T10:03:52+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/3.2.2" + }, + "install-path": "../doctrine/orm" + }, + { + "name": "doctrine/persistence", + "version": "3.3.3", + "version_normalized": "3.3.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "b337726451f5d530df338fc7f68dee8781b49779" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/b337726451f5d530df338fc7f68dee8781b49779", + "reference": "b337726451f5d530df338fc7f68dee8781b49779", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1 || ^2", + "php": "^7.2 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/common": "<2.10" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.11.1", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.30.0 || 5.24.0" + }, + "time": "2024-06-20T10:14:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/3.3.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "install-path": "../doctrine/persistence" + }, + { + "name": "doctrine/sql-formatter", + "version": "1.4.1", + "version_normalized": "1.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "7f83911cc5eba870de7ebb11283972483f7e2891" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/7f83911cc5eba870de7ebb11283972483f7e2891", + "reference": "7f83911cc5eba870de7ebb11283972483f7e2891", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "time": "2024-08-05T20:32:22+00:00", + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.4.1" + }, + "install-path": "../doctrine/sql-formatter" + }, + { + "name": "egulias/email-validator", + "version": "4.0.2", + "version_normalized": "4.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "time": "2023-10-06T06:47:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "install-path": "../egulias/email-validator" + }, + { + "name": "hwi/oauth-bundle", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/hwi/HWIOAuthBundle.git", + "reference": "c9cd9f2ffa55a353b6902eb811315ebd3782d3bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hwi/HWIOAuthBundle/zipball/c9cd9f2ffa55a353b6902eb811315ebd3782d3bd", + "reference": "c9cd9f2ffa55a353b6902eb811315ebd3782d3bd", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/deprecation-contracts": "^3.0", + "symfony/form": "^5.4 || ^6.3 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0", + "symfony/http-client": "^5.4 || ^6.3 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.3 || ^7.0", + "symfony/routing": "^5.4 || ^6.3 || ^7.0", + "symfony/security-bundle": "^5.4 || ^6.3 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.3 || ^7.0" + }, + "conflict": { + "symfony/security-bundle": ">=6.0,<6.3.4", + "twig/twig": "<1.43|>=2.0,<2.13" + }, + "require-dev": { + "doctrine/doctrine-bundle": "^2.4", + "doctrine/orm": "^2.9", + "firebase/php-jwt": "^6.8", + "friendsofphp/php-cs-fixer": "^3.23", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^9.6.11", + "symfony/browser-kit": "^5.4 || ^6.3 || ^7.0", + "symfony/css-selector": "^5.4 || ^6.3 || ^7.0", + "symfony/monolog-bundle": "^3.4", + "symfony/phpunit-bridge": "^5.4 || ^6.3 || ^7.0", + "symfony/property-access": "^5.4 || ^6.3 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.3 || ^7.0", + "symfony/translation": "^5.4 || ^6.3 || ^7.0", + "symfony/validator": "^5.4 || ^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.3 || ^7.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "to use Doctrine user provider", + "firebase/php-jwt": "to use JWT utility functions", + "symfony/property-access": "to use FOSUB integration with this bundle", + "symfony/twig-bundle": "to use the Twig hwi_oauth_* functions" + }, + "time": "2024-02-28T13:59:26+00:00", + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "HWI\\Bundle\\OAuthBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander", + "email": "iam.asm89@gmail.com" + }, + { + "name": "Joseph Bielawski", + "email": "stloyd@gmail.com" + }, + { + "name": "Geoffrey Bachelet", + "email": "geoffrey.bachelet@gmail.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/hwi/HWIOAuthBundle/contributors" + } + ], + "description": "Support for authenticating users using both OAuth1.0a and OAuth2 in Symfony.", + "homepage": "https://github.com/hwi/HWIOAuthBundle", + "keywords": [ + "37signals", + "Authentication", + "Deezer", + "EVE Online", + "amazon", + "apple", + "asana", + "auth0", + "azure", + "bitbucket", + "bitly", + "box", + "bufferapp", + "clever", + "dailymotion", + "deviantart", + "discogs", + "disqus", + "dropbox", + "eventbrite", + "facebook", + "firewall", + "fiware", + "flickr", + "foursquare", + "genius", + "github", + "gitlab", + "google", + "hubic", + "instagram", + "jawbone", + "jira", + "linkedin", + "mail.ru", + "oauth", + "oauth1", + "oauth2", + "odnoklassniki", + "paypal", + "qq", + "reddit", + "runkeeper", + "salesforce", + "security", + "sensio connect", + "sina weibo", + "slack", + "sound cloud", + "spotify", + "stack exchange", + "stereomood", + "strava", + "toshl", + "trakt", + "trello", + "twitch", + "twitter", + "vkontakte", + "windows live", + "wordpress", + "xing", + "yahoo", + "yandex", + "youtube" + ], + "support": { + "issues": "https://github.com/hwi/HWIOAuthBundle/issues", + "source": "https://github.com/hwi/HWIOAuthBundle/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/stloyd", + "type": "github" + } + ], + "install-path": "../hwi/oauth-bundle" + }, + { + "name": "lcobucci/clock", + "version": "3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "6f28b826ea01306b07980cb8320ab30b966cd715" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/6f28b826ea01306b07980cb8320ab30b966cd715", + "reference": "6f28b826ea01306b07980cb8320ab30b966cd715", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "infection/infection": "^0.27", + "lcobucci/coding-standard": "^11.0.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.10.25", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^10.2.3" + }, + "time": "2023-11-17T17:00:27+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "install-path": "../lcobucci/clock" + }, + { + "name": "lcobucci/jwt", + "version": "5.3.0", + "version_normalized": "5.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", + "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.27.0", + "lcobucci/clock": "^3.0", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2.9", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^10.2.6" + }, + "suggest": { + "lcobucci/clock": ">= 3.0" + }, + "time": "2024-04-11T23:07:54+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.3.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "install-path": "../lcobucci/jwt" + }, + { + "name": "lexik/jwt-authentication-bundle", + "version": "v3.1.0", + "version_normalized": "3.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/lexik/LexikJWTAuthenticationBundle.git", + "reference": "4f1a638289cf9282bad1b82b8df56d3bd4e0743c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lexik/LexikJWTAuthenticationBundle/zipball/4f1a638289cf9282bad1b82b8df56d3bd4e0743c", + "reference": "4f1a638289cf9282bad1b82b8df56d3bd4e0743c", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "lcobucci/clock": "^3.0", + "lcobucci/jwt": "^5.0", + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.4|^3.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/translation-contracts": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "api-platform/core": "^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "suggest": { + "gesdinet/jwt-refresh-token-bundle": "Implements a refresh token system over Json Web Tokens in Symfony", + "spomky-labs/lexik-jose-bridge": "Provides a JWT Token encoder with encryption support" + }, + "time": "2024-07-03T20:49:59+00:00", + "type": "symfony-bundle", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Lexik\\Bundle\\JWTAuthenticationBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Barthe", + "email": "j.barthe@lexik.fr", + "homepage": "https://github.com/jeremyb" + }, + { + "name": "Nicolas Cabot", + "email": "n.cabot@lexik.fr", + "homepage": "https://github.com/slashfan" + }, + { + "name": "Cedric Girard", + "email": "c.girard@lexik.fr", + "homepage": "https://github.com/cedric-g" + }, + { + "name": "Dev Lexik", + "email": "dev@lexik.fr", + "homepage": "https://github.com/lexik" + }, + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com", + "homepage": "https://github.com/chalasr" + }, + { + "name": "Lexik Community", + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle/graphs/contributors" + } + ], + "description": "This bundle provides JWT authentication for your Symfony REST API", + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle", + "keywords": [ + "Authentication", + "JWS", + "api", + "bundle", + "jwt", + "rest", + "symfony" + ], + "support": { + "issues": "https://github.com/lexik/LexikJWTAuthenticationBundle/issues", + "source": "https://github.com/lexik/LexikJWTAuthenticationBundle/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://github.com/chalasr", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/lexik/jwt-authentication-bundle", + "type": "tidelift" + } + ], + "install-path": "../lexik/jwt-authentication-bundle" + }, + { + "name": "nikic/php-parser", + "version": "v5.1.0", + "version_normalized": "5.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "time": "2024-07-01T20:03:41+00:00", + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + }, + "install-path": "../nikic/php-parser" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-02-03T23:26:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "install-path": "../psr/cache" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "time": "2022-11-25T14:36:26+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "install-path": "../psr/clock" + }, + { + "name": "psr/container", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:47:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "time": "2019-01-08T18:20:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "install-path": "../psr/event-dispatcher" + }, + { + "name": "psr/log", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2024-09-11T13:17:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "install-path": "../psr/log" + }, + { + "name": "symfony/cache", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "b61e464d7687bb7e8f677d5031c632bf3820df18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/b61e464d7687bb7e8f677d5031c632bf3820df18", + "reference": "b61e464d7687bb7e8f677d5031c632bf3820df18", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-08-12T09:59:40+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/cache" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/df6a1a44c890faded49a5fca33c2d5c5fd3c2197", + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "time": "2024-04-18T09:32:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/cache-contracts" + }, + { + "name": "symfony/clock", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/clock" + }, + { + "name": "symfony/config", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/config" + }, + { + "name": "symfony/console", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-08-15T22:48:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/console" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "5320e0bc2c9e2d7450bb4091e497a305a68b28ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5320e0bc2c9e2d7450bb4091e497a305a68b28ed", + "reference": "5320e0bc2c9e2d7450bb4091e497a305a68b28ed", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "time": "2024-08-29T08:16:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/dependency-injection" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2024-04-18T09:32:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/doctrine-bridge", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "5c31b278a52023970f4ef398e42ab9048483abfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/5c31b278a52023970f4ef398e42ab9048483abfa", + "reference": "5c31b278a52023970f4ef398e42ab9048483abfa", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<2.15", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/form": "<6.4.6|>=7,<7.0.6", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/lock": "<6.4", + "symfony/messenger": "<6.4", + "symfony/property-info": "<6.4", + "symfony/security-bundle": "<6.4", + "symfony/security-core": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "doctrine/collections": "^1.0|^2.0", + "doctrine/data-fixtures": "^1.1", + "doctrine/dbal": "^3.6|^4", + "doctrine/orm": "^2.15|^3", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4.6|^7.0.6", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-08-13T10:29:23+00:00", + "type": "symfony-bridge", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Doctrine with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/doctrine-bridge" + }, + { + "name": "symfony/dotenv", + "version": "v7.1.3", + "version_normalized": "7.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "a26be30fd61678dab694a18a85084cea7673bbf3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/a26be30fd61678dab694a18a85084cea7673bbf3", + "reference": "a26be30fd61678dab694a18a85084cea7673bbf3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "time": "2024-07-09T19:36:07+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/dotenv" + }, + { + "name": "symfony/error-handler", + "version": "v7.1.3", + "version_normalized": "7.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "time": "2024-07-26T13:02:51+00:00", + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/error-handler" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/event-dispatcher" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "time": "2024-04-18T09:32:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/event-dispatcher-contracts" + }, + { + "name": "symfony/filesystem", + "version": "v7.1.2", + "version_normalized": "7.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "time": "2024-06-28T10:03:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/filesystem" + }, + { + "name": "symfony/finder", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "time": "2024-08-13T14:28:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/flex", + "version": "v2.4.6", + "version_normalized": "2.4.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "4dc11919791f81d087a12db2ab4c7e044431ef6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/4dc11919791f81d087a12db2ab4c7e044431ef6b", + "reference": "4dc11919791f81d087a12db2ab4c7e044431ef6b", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.1", + "php": ">=8.0" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "time": "2024-04-27T10:22:22+00:00", + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Flex\\Flex" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "support": { + "issues": "https://github.com/symfony/flex/issues", + "source": "https://github.com/symfony/flex/tree/v2.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/flex" + }, + { + "name": "symfony/form", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/form.git", + "reference": "3018ad169ea7532eec19e001f2c9f049ff051bd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/form/zipball/3018ad169ea7532eec19e001f2c9f049ff051bd6", + "reference": "3018ad169ea7532eec19e001f2c9f049ff051bd6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/options-resolver": "^6.4|^7.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/error-handler": "<6.4", + "symfony/framework-bundle": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "doctrine/collections": "^1.0|^2.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-08-12T09:59:40+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Form\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows to easily create, process and reuse HTML forms", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/form/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/form" + }, + { + "name": "symfony/framework-bundle", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "711af4eefcb4054a9c93e44b403626e1826bcddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/711af4eefcb4054a9c93e44b403626e1826bcddd", + "reference": "711af4eefcb4054a9c93e44b403626e1826bcddd", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^7.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^6.4|^7.0" + }, + "conflict": { + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<6.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", + "symfony/security-core": "<6.4", + "symfony/security-csrf": "<6.4", + "symfony/serializer": "<6.4", + "symfony/stopwatch": "<6.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "time": "2024-08-11T16:10:02+00:00", + "type": "symfony-bundle", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/framework-bundle" + }, + { + "name": "symfony/http-client", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "a8f8d60b30b331cf4b743b3632e5acdba3f8285c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/a8f8d60b30b331cf4b743b3632e5acdba3f8285c", + "reference": "a8f8d60b30b331cf4b743b3632e5acdba3f8285c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3.4.1", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "time": "2024-08-26T06:32:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-client" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "20414d96f391677bf80078aa55baece78b82647d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", + "reference": "20414d96f391677bf80078aa55baece78b82647d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2024-04-18T09:32:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-client-contracts" + }, + { + "name": "symfony/http-foundation", + "version": "v7.1.3", + "version_normalized": "7.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "time": "2024-07-26T12:41:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-foundation" + }, + { + "name": "symfony/http-kernel", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747", + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.0.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "time": "2024-08-30T17:02:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-kernel" + }, + { + "name": "symfony/mailer", + "version": "v7.1.2", + "version_normalized": "7.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee", + "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" + }, + "time": "2024-06-28T08:00:31+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/mailer" + }, + { + "name": "symfony/maker-bundle", + "version": "v1.61.0", + "version_normalized": "1.61.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/maker-bundle.git", + "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/a3b7f14d349f8f44ed752d4dde2263f77510cc18", + "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "nikic/php-parser": "^4.18|^5.0", + "php": ">=8.1", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.2|^3", + "symfony/filesystem": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "conflict": { + "doctrine/doctrine-bundle": "<2.10", + "doctrine/orm": "<2.15" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/doctrine-bundle": "^2.5.0", + "doctrine/orm": "^2.15|^3", + "symfony/http-client": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.0|^4.x-dev" + }, + "time": "2024-08-29T22:50:23+00:00", + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MakerBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", + "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", + "keywords": [ + "code generator", + "dev", + "generator", + "scaffold", + "scaffolding" + ], + "support": { + "issues": "https://github.com/symfony/maker-bundle/issues", + "source": "https://github.com/symfony/maker-bundle/tree/v1.61.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/maker-bundle" + }, + { + "name": "symfony/mime", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758", + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "time": "2024-08-13T14:28:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/mime" + }, + { + "name": "symfony/options-resolver", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/options-resolver" + }, + { + "name": "symfony/password-hasher", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/password-hasher.git", + "reference": "4ad96eb7cf9e2f8f133ada95f2b8021769061662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/4ad96eb7cf9e2f8f133ada95f2b8021769061662", + "reference": "4ad96eb7cf9e2f8f133ada95f2b8021769061662", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\PasswordHasher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides password hashing utilities", + "homepage": "https://symfony.com", + "keywords": [ + "hashing", + "password" + ], + "support": { + "source": "https://github.com/symfony/password-hasher/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/password-hasher" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-grapheme" + }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance and support of other locales than \"en\"" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Icu\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-icu" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-idn" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-normalizer" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php83" + }, + { + "name": "symfony/process", + "version": "v7.1.3", + "version_normalized": "7.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "time": "2024-07-26T12:44:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/process" + }, + { + "name": "symfony/property-access", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "6c709f97103355016e5782d0622437ae381012ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/6c709f97103355016e5782d0622437ae381012ad", + "reference": "6c709f97103355016e5782d0622437ae381012ad", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0" + }, + "time": "2024-08-30T16:12:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/property-access" + }, + { + "name": "symfony/property-info", + "version": "v7.1.3", + "version_normalized": "7.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "88a279df2db5b7919cac6f35d6a5d1d7147e6a9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/88a279df2db5b7919cac6f35d6a5d1d7147e6a9b", + "reference": "88a279df2db5b7919cac6f35d6a5d1d7147e6a9b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "^7.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "time": "2024-07-26T07:36:36+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/property-info" + }, + { + "name": "symfony/routing", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "time": "2024-08-29T08:16:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/routing" + }, + { + "name": "symfony/runtime", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "ea34522c447dd91a2b31cb330ee4540a56ba53f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/ea34522c447dd91a2b31cb330ee4540a56ba53f6", + "reference": "ea34522c447dd91a2b31cb330ee4540a56ba53f6", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" + }, + "conflict": { + "symfony/dotenv": "<6.4" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "time": "2024-05-31T14:55:39+00:00", + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/runtime" + }, + { + "name": "symfony/security-bundle", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-bundle.git", + "reference": "5e10107856ff64d477c61fed7bcbb8a16125ea01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/5e10107856ff64d477c61fed7bcbb8a16125ea01", + "reference": "5e10107856ff64d477c61fed7bcbb8a16125ea01", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4.11|^7.1.4", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^7.1", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/console": "<6.4", + "symfony/framework-bundle": "<6.4", + "symfony/http-client": "<6.4", + "symfony/ldap": "<6.4", + "symfony/serializer": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.0.4", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "time": "2024-08-20T11:38:55+00:00", + "type": "symfony-bundle", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\SecurityBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-bundle/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/security-bundle" + }, + { + "name": "symfony/security-core", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-core.git", + "reference": "f5ccd9d005993e5ff7251e57fe4a0615c8535866" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-core/zipball/f5ccd9d005993e5ff7251e57fe4a0615c8535866", + "reference": "f5ccd9d005993e5ff7251e57fe4a0615c8535866", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/validator": "<6.4" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "psr/container": "^1.1|^2.0", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/validator": "^6.4|^7.0" + }, + "time": "2024-08-29T08:16:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Core\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - Core Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-core/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/security-core" + }, + { + "name": "symfony/security-csrf", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-csrf.git", + "reference": "27cd1bce9d7f3457a152a6ca9790712d6954dd21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/27cd1bce9d7f3457a152a6ca9790712d6954dd21", + "reference": "27cd1bce9d7f3457a152a6ca9790712d6954dd21", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/security-core": "^6.4|^7.0" + }, + "conflict": { + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "symfony/http-foundation": "^6.4|^7.0" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Csrf\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - CSRF Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-csrf/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/security-csrf" + }, + { + "name": "symfony/security-http", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-http.git", + "reference": "acd1ecc807b76b9bdefe53168c3a52a11205fc20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-http/zipball/acd1ecc807b76b9bdefe53168c3a52a11205fc20", + "reference": "acd1ecc807b76b9bdefe53168c3a52a11205fc20", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/clock": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-client-contracts": "<3.0", + "symfony/security-bundle": "<6.4", + "symfony/security-csrf": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/http-client-contracts": "^3.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "time": "2024-08-15T22:52:38+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Http\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - HTTP Integration", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-http/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/security-http" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "time": "2024-04-18T09:32:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/stopwatch", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/stopwatch" + }, + { + "name": "symfony/string", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "time": "2024-08-12T09:59:40+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/string" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2024-04-18T09:32:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/translation-contracts" + }, + { + "name": "symfony/twig-bridge", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "2db32cfe8fc57797908ef0bee232b90dbe42af66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/2db32cfe8fc57797908ef0bee232b90dbe42af66", + "reference": "2db32cfe8fc57797908ef0bee232b90dbe42af66", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.9" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "time": "2024-08-29T08:16:25+00:00", + "type": "symfony-bridge", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/twig-bridge" + }, + { + "name": "symfony/twig-bundle", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "d48c2f08c2f315e749f0e18fc4945b7be8afe1e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d48c2f08c2f315e749f0e18fc4945b7be8afe1e5", + "reference": "d48c2f08c2f315e749f0e18fc4945b7be8afe1e5", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "conflict": { + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "time": "2024-05-31T14:57:53+00:00", + "type": "symfony-bundle", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/twig-bundle" + }, + { + "name": "symfony/type-info", + "version": "v7.1.1", + "version_normalized": "7.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/60b28eb733f1453287f1263ed305b96091e0d1dc", + "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-info": "<6.4" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0" + }, + "time": "2024-05-31T14:59:31+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/type-info" + }, + { + "name": "symfony/var-dumper", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "time": "2024-08-30T16:12:47+00:00", + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-dumper" + }, + { + "name": "symfony/var-exporter", + "version": "v7.1.2", + "version_normalized": "7.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-06-28T08:00:31+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-exporter" + }, + { + "name": "symfony/yaml", + "version": "v7.1.4", + "version_normalized": "7.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "92e080b851c1c655c786a2da77f188f2dccd0f4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/92e080b851c1c655c786a2da77f188f2dccd0f4b", + "reference": "92e080b851c1c655c786a2da77f188f2dccd0f4b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "time": "2024-08-12T09:59:40+00:00", + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/yaml" + }, + { + "name": "twig/twig", + "version": "v3.14.0", + "version_normalized": "3.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "time": "2024-09-09T17:55:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "install-path": "../twig/twig" + } + ], + "dev": true, + "dev-package-names": [ + "nikic/php-parser", + "symfony/maker-bundle", + "symfony/process" + ] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..b3f5542 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,842 @@ + array( + 'name' => '__root__', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/cache' => array( + 'pretty_version' => '2.2.0', + 'version' => '2.2.0.0', + 'reference' => '1ca8f21980e770095a31456042471a57bc4c68fb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/collections' => array( + 'pretty_version' => '2.2.2', + 'version' => '2.2.2.0', + 'reference' => 'd8af7f248c74f195f7347424600fd9e17b57af59', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/collections', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/dbal' => array( + 'pretty_version' => '3.9.1', + 'version' => '3.9.1.0', + 'reference' => 'd7dc08f98cba352b2bab5d32c5e58f7e745c11a7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/dbal', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/deprecations' => array( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'reference' => 'dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/deprecations', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/doctrine-bundle' => array( + 'pretty_version' => '2.13.0', + 'version' => '2.13.0.0', + 'reference' => 'ca59d84b8e63143ce1aed90cdb333ba329d71563', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../doctrine/doctrine-bundle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/doctrine-migrations-bundle' => array( + 'pretty_version' => '3.3.1', + 'version' => '3.3.1.0', + 'reference' => '715b62c31a5894afcb2b2cdbbc6607d7dd0580c0', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../doctrine/doctrine-migrations-bundle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/event-manager' => array( + 'pretty_version' => '2.0.1', + 'version' => '2.0.1.0', + 'reference' => 'b680156fa328f1dfd874fd48c7026c41570b9c6e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/event-manager', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/inflector' => array( + 'pretty_version' => '2.0.10', + 'version' => '2.0.10.0', + 'reference' => '5817d0659c5b50c9b950feb9af7b9668e2c436bc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/inflector', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/instantiator' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => 'c6222283fa3f4ac679f8b9ced9a4e23f163e80d0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/instantiator', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/lexer' => array( + 'pretty_version' => '3.0.1', + 'version' => '3.0.1.0', + 'reference' => '31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/lexer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/migrations' => array( + 'pretty_version' => '3.8.1', + 'version' => '3.8.1.0', + 'reference' => '7760fbd0b7cb58bfb50415505a7bab821adf0877', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/migrations', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/orm' => array( + 'pretty_version' => '3.2.2', + 'version' => '3.2.2.0', + 'reference' => '831a1eb7d260925528cdbb49cc1866c0357cf147', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/orm', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/persistence' => array( + 'pretty_version' => '3.3.3', + 'version' => '3.3.3.0', + 'reference' => 'b337726451f5d530df338fc7f68dee8781b49779', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/persistence', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/sql-formatter' => array( + 'pretty_version' => '1.4.1', + 'version' => '1.4.1.0', + 'reference' => '7f83911cc5eba870de7ebb11283972483f7e2891', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/sql-formatter', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'egulias/email-validator' => array( + 'pretty_version' => '4.0.2', + 'version' => '4.0.2.0', + 'reference' => 'ebaaf5be6c0286928352e054f2d5125608e5405e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../egulias/email-validator', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'hwi/oauth-bundle' => array( + 'pretty_version' => '2.2.0', + 'version' => '2.2.0.0', + 'reference' => 'c9cd9f2ffa55a353b6902eb811315ebd3782d3bd', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../hwi/oauth-bundle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'lcobucci/clock' => array( + 'pretty_version' => '3.2.0', + 'version' => '3.2.0.0', + 'reference' => '6f28b826ea01306b07980cb8320ab30b966cd715', + 'type' => 'library', + 'install_path' => __DIR__ . '/../lcobucci/clock', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'lcobucci/jwt' => array( + 'pretty_version' => '5.3.0', + 'version' => '5.3.0.0', + 'reference' => '08071d8d2c7f4b00222cc4b1fb6aa46990a80f83', + 'type' => 'library', + 'install_path' => __DIR__ . '/../lcobucci/jwt', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'lexik/jwt-authentication-bundle' => array( + 'pretty_version' => 'v3.1.0', + 'version' => '3.1.0.0', + 'reference' => '4f1a638289cf9282bad1b82b8df56d3bd4e0743c', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../lexik/jwt-authentication-bundle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nikic/php-parser' => array( + 'pretty_version' => 'v5.1.0', + 'version' => '5.1.0.0', + 'reference' => '683130c2ff8c2739f4822ff7ac5c873ec529abd1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nikic/php-parser', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'php-http/async-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + ), + ), + 'php-http/client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + ), + ), + 'psr/cache' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.0|3.0', + ), + ), + 'psr/clock' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/clock', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/clock-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/container-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.1|2.0', + ), + ), + 'psr/event-dispatcher' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0|3.0', + ), + ), + 'psr/simple-cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0|3.0', + ), + ), + 'symfony/cache' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => 'b61e464d7687bb7e8f677d5031c632bf3820df18', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/cache-contracts' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => 'df6a1a44c890faded49a5fca33c2d5c5fd3c2197', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.1|2.0|3.0', + ), + ), + 'symfony/clock' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '3dfc8b084853586de51dd1441c6242c76a28cbe7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/clock', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/config' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/config', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/console' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '1eed7af6961d763e7832e874d7f9b21c3ea9c111', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/console', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/dependency-injection' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '5320e0bc2c9e2d7450bb4091e497a305a68b28ed', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/dependency-injection', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/doctrine-bridge' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '5c31b278a52023970f4ef398e42ab9048483abfa', + 'type' => 'symfony-bridge', + 'install_path' => __DIR__ . '/../symfony/doctrine-bridge', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/dotenv' => array( + 'pretty_version' => 'v7.1.3', + 'version' => '7.1.3.0', + 'reference' => 'a26be30fd61678dab694a18a85084cea7673bbf3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/dotenv', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/error-handler' => array( + 'pretty_version' => 'v7.1.3', + 'version' => '7.1.3.0', + 'reference' => '432bb369952795c61ca1def65e078c4a80dad13c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/error-handler', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher-contracts' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => '8f93aec25d41b72493c6ddff14e916177c9efc50', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.0|3.0', + ), + ), + 'symfony/filesystem' => array( + 'pretty_version' => 'v7.1.2', + 'version' => '7.1.2.0', + 'reference' => '92a91985250c251de9b947a14bb2c9390b1a562c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/filesystem', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => 'd95bbf319f7d052082fb7af147e0f835a695e823', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/flex' => array( + 'pretty_version' => 'v2.4.6', + 'version' => '2.4.6.0', + 'reference' => '4dc11919791f81d087a12db2ab4c7e044431ef6b', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../symfony/flex', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/form' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '3018ad169ea7532eec19e001f2c9f049ff051bd6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/form', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/framework-bundle' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '711af4eefcb4054a9c93e44b403626e1826bcddd', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../symfony/framework-bundle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => 'a8f8d60b30b331cf4b743b3632e5acdba3f8285c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client-contracts' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => '20414d96f391677bf80078aa55baece78b82647d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-client-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '3.0', + ), + ), + 'symfony/http-foundation' => array( + 'pretty_version' => 'v7.1.3', + 'version' => '7.1.3.0', + 'reference' => 'f602d5c17d1fa02f8019ace2687d9d136b7f4a1a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-foundation', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-kernel' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '6efcbd1b3f444f631c386504fc83eeca25963747', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-kernel', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/mailer' => array( + 'pretty_version' => 'v7.1.2', + 'version' => '7.1.2.0', + 'reference' => '8fcff0af9043c8f8a8e229437cea363e282f9aee', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/mailer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/maker-bundle' => array( + 'pretty_version' => 'v1.61.0', + 'version' => '1.61.0.0', + 'reference' => 'a3b7f14d349f8f44ed752d4dde2263f77510cc18', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../symfony/maker-bundle', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/mime' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => 'ccaa6c2503db867f472a587291e764d6a1e58758', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/mime', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/options-resolver' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '47aa818121ed3950acd2b58d1d37d08a94f9bf55', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/options-resolver', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/password-hasher' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '4ad96eb7cf9e2f8f133ada95f2b8021769061662', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/password-hasher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-iconv' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-intl-grapheme' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => 'b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-icu' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => 'd80a05e9904d2c2b9b95929f3e4b5d3a8f418d78', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-icu', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-idn' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => 'c36586dcf89a12315939e00ec9b4474adcb1d773', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-normalizer' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => '3833d7255cc303546435cb650316bff708a1c75c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => '85181ba99b2345b0ef10ce42ecac37612d9fd341', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php72' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-php73' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-php74' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-php80' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-php81' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-php82' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'symfony/polyfill-php83' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => '2fb86d65e2d424369ad2905e83b236a8805ba491', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php83', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/process' => array( + 'pretty_version' => 'v7.1.3', + 'version' => '7.1.3.0', + 'reference' => '7f2f542c668ad6c313dc4a5e9c3321f733197eca', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/process', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/property-access' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '6c709f97103355016e5782d0622437ae381012ad', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/property-access', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/property-info' => array( + 'pretty_version' => 'v7.1.3', + 'version' => '7.1.3.0', + 'reference' => '88a279df2db5b7919cac6f35d6a5d1d7147e6a9b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/property-info', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/routing' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '1500aee0094a3ce1c92626ed8cf3c2037e86f5a7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/routing', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/runtime' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => 'ea34522c447dd91a2b31cb330ee4540a56ba53f6', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../symfony/runtime', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/security-bundle' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '5e10107856ff64d477c61fed7bcbb8a16125ea01', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../symfony/security-bundle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/security-core' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => 'f5ccd9d005993e5ff7251e57fe4a0615c8535866', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/security-core', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/security-csrf' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '27cd1bce9d7f3457a152a6ca9790712d6954dd21', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/security-csrf', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/security-http' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => 'acd1ecc807b76b9bdefe53168c3a52a11205fc20', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/security-http', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => 'bd1d9e59a81d8fa4acdcea3f617c581f7475a80f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.1|2.0|3.0', + ), + ), + 'symfony/stopwatch' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/stopwatch', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/string' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '6cd670a6d968eaeb1c77c2e76091c45c56bc367b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/string', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation-contracts' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => 'b9d2189887bb6b2e0367a9fc7136c5239ab9b05a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/translation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/twig-bridge' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '2db32cfe8fc57797908ef0bee232b90dbe42af66', + 'type' => 'symfony-bridge', + 'install_path' => __DIR__ . '/../symfony/twig-bridge', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/twig-bundle' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => 'd48c2f08c2f315e749f0e18fc4945b7be8afe1e5', + 'type' => 'symfony-bundle', + 'install_path' => __DIR__ . '/../symfony/twig-bundle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/type-info' => array( + 'pretty_version' => 'v7.1.1', + 'version' => '7.1.1.0', + 'reference' => '60b28eb733f1453287f1263ed305b96091e0d1dc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/type-info', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/var-dumper' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => 'a5fa7481b199090964d6fd5dab6294d5a870c7aa', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-dumper', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/var-exporter' => array( + 'pretty_version' => 'v7.1.2', + 'version' => '7.1.2.0', + 'reference' => 'b80a669a2264609f07f1667f891dbfca25eba44c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-exporter', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/yaml' => array( + 'pretty_version' => 'v7.1.4', + 'version' => '7.1.4.0', + 'reference' => '92e080b851c1c655c786a2da77f188f2dccd0f4b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/yaml', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'twig/twig' => array( + 'pretty_version' => 'v3.14.0', + 'version' => '3.14.0.0', + 'reference' => '126b2c97818dbff0cdf3fbfc881aedb3d40aae72', + 'type' => 'library', + 'install_path' => __DIR__ . '/../twig/twig', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..d32d90c --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.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 + ); +} diff --git a/vendor/doctrine/cache/LICENSE b/vendor/doctrine/cache/LICENSE new file mode 100644 index 0000000..8c38cc1 --- /dev/null +++ b/vendor/doctrine/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +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. diff --git a/vendor/doctrine/cache/README.md b/vendor/doctrine/cache/README.md new file mode 100644 index 0000000..a13196d --- /dev/null +++ b/vendor/doctrine/cache/README.md @@ -0,0 +1,13 @@ +# Doctrine Cache + +[![Build Status](https://github.com/doctrine/cache/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/cache/actions) +[![Code Coverage](https://codecov.io/gh/doctrine/cache/branch/1.10.x/graph/badge.svg)](https://codecov.io/gh/doctrine/cache/branch/1.10.x) + +[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/cache.svg?style=flat-square)](https://packagist.org/packages/doctrine/cache) +[![Total Downloads](https://img.shields.io/packagist/dt/doctrine/cache.svg?style=flat-square)](https://packagist.org/packages/doctrine/cache) + +Cache component extracted from the Doctrine Common project. [Documentation](https://www.doctrine-project.org/projects/doctrine-cache/en/current/index.html) + +This library is deprecated and will no longer receive bug fixes from the +Doctrine Project. Please use a different cache library, preferably PSR-6 or +PSR-16 instead. diff --git a/vendor/doctrine/cache/UPGRADE-1.11.md b/vendor/doctrine/cache/UPGRADE-1.11.md new file mode 100644 index 0000000..6c5ddb5 --- /dev/null +++ b/vendor/doctrine/cache/UPGRADE-1.11.md @@ -0,0 +1,27 @@ +# Upgrade to 1.11 + +doctrine/cache will no longer be maintained and all cache implementations have +been marked as deprecated. These implementations will be removed in 2.0, which +will only contain interfaces to provide a lightweight package for backward +compatibility. + +There are two new classes to use in the `Doctrine\Common\Cache\Psr6` namespace: +* The `CacheAdapter` class allows using any Doctrine Cache as PSR-6 cache. This + is useful to provide a forward compatibility layer in libraries that accept + Doctrine cache implementations and switch to PSR-6. +* The `DoctrineProvider` class allows using any PSR-6 cache as Doctrine cache. + This implementation is designed for libraries that leak the cache and want to + switch to allowing PSR-6 implementations. This class is design to be used + during the transition phase of sunsetting doctrine/cache support. + +A full example to setup a filesystem based PSR-6 cache with symfony/cache +using the `DoctrineProvider` to convert back to Doctrine's `Cache` interface: + +```php +use Doctrine\Common\Cache\Psr6\DoctrineProvider; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; + +$cachePool = new FilesystemAdapter(); +$cache = DoctrineProvider::wrap($cachePool); +// $cache instanceof \Doctrine\Common\Cache\Cache +``` diff --git a/vendor/doctrine/cache/UPGRADE-1.4.md b/vendor/doctrine/cache/UPGRADE-1.4.md new file mode 100644 index 0000000..e1f8a50 --- /dev/null +++ b/vendor/doctrine/cache/UPGRADE-1.4.md @@ -0,0 +1,16 @@ +# Upgrade to 1.4 + +## Minor BC Break: `Doctrine\Common\Cache\FileCache#$extension` is now `private`. + +If you need to override the value of `Doctrine\Common\Cache\FileCache#$extension`, then use the +second parameter of `Doctrine\Common\Cache\FileCache#__construct()` instead of overriding +the property in your own implementation. + +## Minor BC Break: file based caches paths changed + +`Doctrine\Common\Cache\FileCache`, `Doctrine\Common\Cache\PhpFileCache` and +`Doctrine\Common\Cache\FilesystemCache` are using a different cache paths structure. + +If you rely on warmed up caches for deployments, consider that caches generated +with `doctrine/cache` `<1.4` are not compatible with the new directory structure, +and will be ignored. diff --git a/vendor/doctrine/cache/composer.json b/vendor/doctrine/cache/composer.json new file mode 100644 index 0000000..3c8ca97 --- /dev/null +++ b/vendor/doctrine/cache/composer.json @@ -0,0 +1,50 @@ +{ + "name": "doctrine/cache", + "type": "library", + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "keywords": [ + "php", + "cache", + "caching", + "abstraction", + "redis", + "memcached", + "couchdb", + "xcache", + "apcu" + ], + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": "~7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "doctrine/coding-standard": "^9", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "cache/integration-tests": "dev-master", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" } + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php new file mode 100644 index 0000000..4cfab6c --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php @@ -0,0 +1,90 @@ +hits + * Number of keys that have been requested and found present. + * + * - misses + * Number of items that have been requested and not found. + * + * - uptime + * Time that the server is running. + * + * - memory_usage + * Memory used by this server to store items. + * + * - memory_available + * Memory allowed to use for storage. + * + * @return mixed[]|null An associative array with server's statistics if available, NULL otherwise. + */ + public function getStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php new file mode 100644 index 0000000..180482a --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php @@ -0,0 +1,325 @@ +namespace = (string) $namespace; + $this->namespaceVersion = null; + } + + /** + * Retrieves the namespace that prefixes all cache ids. + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * {@inheritdoc} + */ + public function fetch($id) + { + return $this->doFetch($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function fetchMultiple(array $keys) + { + if (empty($keys)) { + return []; + } + + // note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys + $namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys)); + $items = $this->doFetchMultiple($namespacedKeys); + $foundItems = []; + + // no internal array function supports this sort of mapping: needs to be iterative + // this filters and combines keys in one pass + foreach ($namespacedKeys as $requestedKey => $namespacedKey) { + if (! isset($items[$namespacedKey]) && ! array_key_exists($namespacedKey, $items)) { + continue; + } + + $foundItems[$requestedKey] = $items[$namespacedKey]; + } + + return $foundItems; + } + + /** + * {@inheritdoc} + */ + public function saveMultiple(array $keysAndValues, $lifetime = 0) + { + $namespacedKeysAndValues = []; + foreach ($keysAndValues as $key => $value) { + $namespacedKeysAndValues[$this->getNamespacedId($key)] = $value; + } + + return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function contains($id) + { + return $this->doContains($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function save($id, $data, $lifeTime = 0) + { + return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $keys) + { + return $this->doDeleteMultiple(array_map([$this, 'getNamespacedId'], $keys)); + } + + /** + * {@inheritdoc} + */ + public function delete($id) + { + return $this->doDelete($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function getStats() + { + return $this->doGetStats(); + } + + /** + * {@inheritDoc} + */ + public function flushAll() + { + return $this->doFlush(); + } + + /** + * {@inheritDoc} + */ + public function deleteAll() + { + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->getNamespaceVersion() + 1; + + if ($this->doSave($namespaceCacheKey, $namespaceVersion)) { + $this->namespaceVersion = $namespaceVersion; + + return true; + } + + return false; + } + + /** + * Prefixes the passed id with the configured namespace value. + * + * @param string $id The id to namespace. + * + * @return string The namespaced id. + */ + private function getNamespacedId(string $id): string + { + $namespaceVersion = $this->getNamespaceVersion(); + + return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); + } + + /** + * Returns the namespace cache key. + */ + private function getNamespaceCacheKey(): string + { + return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); + } + + /** + * Returns the namespace version. + */ + private function getNamespaceVersion(): int + { + if ($this->namespaceVersion !== null) { + return $this->namespaceVersion; + } + + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1; + + return $this->namespaceVersion; + } + + /** + * Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it. + * + * @param string[] $keys Array of keys to retrieve from cache + * + * @return mixed[] Array of values retrieved for the given keys. + */ + protected function doFetchMultiple(array $keys) + { + $returnValues = []; + + foreach ($keys as $key) { + $item = $this->doFetch($key); + if ($item === false && ! $this->doContains($key)) { + continue; + } + + $returnValues[$key] = $item; + } + + return $returnValues; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id. + */ + abstract protected function doFetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + abstract protected function doContains($id); + + /** + * Default implementation of doSaveMultiple. Each driver that supports multi-put should override it. + * + * @param mixed[] $keysAndValues Array of keys and values to save in cache + * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these + * cache entries (0 => infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $success = true; + + foreach ($keysAndValues as $key => $value) { + if ($this->doSave($key, $value, $lifetime)) { + continue; + } + + $success = false; + } + + return $success; + } + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param string $data The cache entry/data. + * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this + * cache entry (0 => infinite lifeTime). + * + * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + abstract protected function doSave($id, $data, $lifeTime = 0); + + /** + * Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it. + * + * @param string[] $keys Array of keys to delete from cache + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't + */ + protected function doDeleteMultiple(array $keys) + { + $success = true; + + foreach ($keys as $key) { + if ($this->doDelete($key)) { + continue; + } + + $success = false; + } + + return $success; + } + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doDelete($id); + + /** + * Flushes all cache entries. + * + * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + abstract protected function doFlush(); + + /** + * Retrieves cached information from the data store. + * + * @return mixed[]|null An associative array with server's statistics if available, NULL otherwise. + */ + abstract protected function doGetStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php new file mode 100644 index 0000000..b94618e --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php @@ -0,0 +1,21 @@ + infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + public function saveMultiple(array $keysAndValues, $lifetime = 0); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php new file mode 100644 index 0000000..d3693b7 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php @@ -0,0 +1,340 @@ + */ + private $deferredItems = []; + + public static function wrap(Cache $cache): CacheItemPoolInterface + { + if ($cache instanceof DoctrineProvider && ! $cache->getNamespace()) { + return $cache->getPool(); + } + + if ($cache instanceof SymfonyDoctrineProvider && ! $cache->getNamespace()) { + $getPool = function () { + // phpcs:ignore Squiz.Scope.StaticThisUsage.Found + return $this->pool; + }; + + return $getPool->bindTo($cache, SymfonyDoctrineProvider::class)(); + } + + return new self($cache); + } + + private function __construct(Cache $cache) + { + $this->cache = $cache; + } + + /** @internal */ + public function getCache(): Cache + { + return $this->cache; + } + + /** + * {@inheritDoc} + */ + public function getItem($key): CacheItemInterface + { + assert(self::validKey($key)); + + if (isset($this->deferredItems[$key])) { + $this->commit(); + } + + $value = $this->cache->fetch($key); + + if (PHP_VERSION_ID >= 80000) { + if ($value !== false) { + return new TypedCacheItem($key, $value, true); + } + + return new TypedCacheItem($key, null, false); + } + + if ($value !== false) { + return new CacheItem($key, $value, true); + } + + return new CacheItem($key, null, false); + } + + /** + * {@inheritDoc} + */ + public function getItems(array $keys = []): array + { + if ($this->deferredItems) { + $this->commit(); + } + + assert(self::validKeys($keys)); + + $values = $this->doFetchMultiple($keys); + $items = []; + + if (PHP_VERSION_ID >= 80000) { + foreach ($keys as $key) { + if (array_key_exists($key, $values)) { + $items[$key] = new TypedCacheItem($key, $values[$key], true); + } else { + $items[$key] = new TypedCacheItem($key, null, false); + } + } + + return $items; + } + + foreach ($keys as $key) { + if (array_key_exists($key, $values)) { + $items[$key] = new CacheItem($key, $values[$key], true); + } else { + $items[$key] = new CacheItem($key, null, false); + } + } + + return $items; + } + + /** + * {@inheritDoc} + */ + public function hasItem($key): bool + { + assert(self::validKey($key)); + + if (isset($this->deferredItems[$key])) { + $this->commit(); + } + + return $this->cache->contains($key); + } + + public function clear(): bool + { + $this->deferredItems = []; + + if (! $this->cache instanceof ClearableCache) { + return false; + } + + return $this->cache->deleteAll(); + } + + /** + * {@inheritDoc} + */ + public function deleteItem($key): bool + { + assert(self::validKey($key)); + unset($this->deferredItems[$key]); + + return $this->cache->delete($key); + } + + /** + * {@inheritDoc} + */ + public function deleteItems(array $keys): bool + { + foreach ($keys as $key) { + assert(self::validKey($key)); + unset($this->deferredItems[$key]); + } + + return $this->doDeleteMultiple($keys); + } + + public function save(CacheItemInterface $item): bool + { + return $this->saveDeferred($item) && $this->commit(); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + if (! $item instanceof CacheItem && ! $item instanceof TypedCacheItem) { + return false; + } + + $this->deferredItems[$item->getKey()] = $item; + + return true; + } + + public function commit(): bool + { + if (! $this->deferredItems) { + return true; + } + + $now = microtime(true); + $itemsCount = 0; + $byLifetime = []; + $expiredKeys = []; + + foreach ($this->deferredItems as $key => $item) { + $lifetime = ($item->getExpiry() ?? $now) - $now; + + if ($lifetime < 0) { + $expiredKeys[] = $key; + + continue; + } + + ++$itemsCount; + $byLifetime[(int) $lifetime][$key] = $item->get(); + } + + $this->deferredItems = []; + + switch (count($expiredKeys)) { + case 0: + break; + case 1: + $this->cache->delete(current($expiredKeys)); + break; + default: + $this->doDeleteMultiple($expiredKeys); + break; + } + + if ($itemsCount === 1) { + return $this->cache->save($key, $item->get(), (int) $lifetime); + } + + $success = true; + foreach ($byLifetime as $lifetime => $values) { + $success = $this->doSaveMultiple($values, $lifetime) && $success; + } + + return $success; + } + + public function __destruct() + { + $this->commit(); + } + + /** + * @param mixed $key + */ + private static function validKey($key): bool + { + if (! is_string($key)) { + throw new InvalidArgument(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); + } + + if ($key === '') { + throw new InvalidArgument('Cache key length must be greater than zero.'); + } + + if (strpbrk($key, self::RESERVED_CHARACTERS) !== false) { + throw new InvalidArgument(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); + } + + return true; + } + + /** + * @param mixed[] $keys + */ + private static function validKeys(array $keys): bool + { + foreach ($keys as $key) { + self::validKey($key); + } + + return true; + } + + /** + * @param mixed[] $keys + */ + private function doDeleteMultiple(array $keys): bool + { + if ($this->cache instanceof MultiDeleteCache) { + return $this->cache->deleteMultiple($keys); + } + + $success = true; + foreach ($keys as $key) { + $success = $this->cache->delete($key) && $success; + } + + return $success; + } + + /** + * @param mixed[] $keys + * + * @return mixed[] + */ + private function doFetchMultiple(array $keys): array + { + if ($this->cache instanceof MultiGetCache) { + return $this->cache->fetchMultiple($keys); + } + + $values = []; + foreach ($keys as $key) { + $value = $this->cache->fetch($key); + if (! $value) { + continue; + } + + $values[$key] = $value; + } + + return $values; + } + + /** + * @param mixed[] $keysAndValues + */ + private function doSaveMultiple(array $keysAndValues, int $lifetime = 0): bool + { + if ($this->cache instanceof MultiPutCache) { + return $this->cache->saveMultiple($keysAndValues, $lifetime); + } + + $success = true; + foreach ($keysAndValues as $key => $value) { + $success = $this->cache->save($key, $value, $lifetime) && $success; + } + + return $success; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheItem.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheItem.php new file mode 100644 index 0000000..0b6f0a2 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheItem.php @@ -0,0 +1,118 @@ +key = $key; + $this->value = $data; + $this->isHit = $isHit; + } + + public function getKey(): string + { + return $this->key; + } + + /** + * {@inheritDoc} + * + * @return mixed + */ + public function get() + { + return $this->value; + } + + public function isHit(): bool + { + return $this->isHit; + } + + /** + * {@inheritDoc} + */ + public function set($value): self + { + $this->value = $value; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function expiresAt($expiration): self + { + if ($expiration === null) { + $this->expiry = null; + } elseif ($expiration instanceof DateTimeInterface) { + $this->expiry = (float) $expiration->format('U.u'); + } else { + throw new TypeError(sprintf( + 'Expected $expiration to be an instance of DateTimeInterface or null, got %s', + is_object($expiration) ? get_class($expiration) : gettype($expiration) + )); + } + + return $this; + } + + /** + * {@inheritDoc} + */ + public function expiresAfter($time): self + { + if ($time === null) { + $this->expiry = null; + } elseif ($time instanceof DateInterval) { + $this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); + } elseif (is_int($time)) { + $this->expiry = $time + microtime(true); + } else { + throw new TypeError(sprintf( + 'Expected $time to be either an integer, an instance of DateInterval or null, got %s', + is_object($time) ? get_class($time) : gettype($time) + )); + } + + return $this; + } + + /** + * @internal + */ + public function getExpiry(): ?float + { + return $this->expiry; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/DoctrineProvider.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/DoctrineProvider.php new file mode 100644 index 0000000..3b0f416 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/DoctrineProvider.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Doctrine\Common\Cache\Psr6; + +use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Cache\CacheProvider; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\DoctrineAdapter as SymfonyDoctrineAdapter; +use Symfony\Contracts\Service\ResetInterface; + +use function rawurlencode; + +/** + * This class was copied from the Symfony Framework, see the original copyright + * notice above. The code is distributed subject to the license terms in + * https://github.com/symfony/symfony/blob/ff0cf61278982539c49e467db9ab13cbd342f76d/LICENSE + */ +final class DoctrineProvider extends CacheProvider +{ + /** @var CacheItemPoolInterface */ + private $pool; + + public static function wrap(CacheItemPoolInterface $pool): Cache + { + if ($pool instanceof CacheAdapter) { + return $pool->getCache(); + } + + if ($pool instanceof SymfonyDoctrineAdapter) { + $getCache = function () { + // phpcs:ignore Squiz.Scope.StaticThisUsage.Found + return $this->provider; + }; + + return $getCache->bindTo($pool, SymfonyDoctrineAdapter::class)(); + } + + return new self($pool); + } + + private function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + } + + /** @internal */ + public function getPool(): CacheItemPoolInterface + { + return $this->pool; + } + + public function reset(): void + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + + $this->setNamespace($this->getNamespace()); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $item = $this->pool->getItem(rawurlencode($id)); + + return $item->isHit() ? $item->get() : false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doContains($id) + { + return $this->pool->hasItem(rawurlencode($id)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $item = $this->pool->getItem(rawurlencode($id)); + + if (0 < $lifeTime) { + $item->expiresAfter($lifeTime); + } + + return $this->pool->save($item->set($data)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doDelete($id) + { + return $this->pool->deleteItem(rawurlencode($id)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doFlush() + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return array|null + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/InvalidArgument.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/InvalidArgument.php new file mode 100644 index 0000000..196f1bc --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/InvalidArgument.php @@ -0,0 +1,13 @@ +key; + } + + public function get(): mixed + { + return $this->value; + } + + public function isHit(): bool + { + return $this->isHit; + } + + public function set(mixed $value): static + { + $this->value = $value; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function expiresAt($expiration): static + { + if ($expiration === null) { + $this->expiry = null; + } elseif ($expiration instanceof DateTimeInterface) { + $this->expiry = (float) $expiration->format('U.u'); + } else { + throw new TypeError(sprintf( + 'Expected $expiration to be an instance of DateTimeInterface or null, got %s', + get_debug_type($expiration) + )); + } + + return $this; + } + + /** + * {@inheritDoc} + */ + public function expiresAfter($time): static + { + if ($time === null) { + $this->expiry = null; + } elseif ($time instanceof DateInterval) { + $this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); + } elseif (is_int($time)) { + $this->expiry = $time + microtime(true); + } else { + throw new TypeError(sprintf( + 'Expected $time to be either an integer, an instance of DateInterval or null, got %s', + get_debug_type($time) + )); + } + + return $this; + } + + /** + * @internal + */ + public function getExpiry(): ?float + { + return $this->expiry; + } +} diff --git a/vendor/doctrine/collections/CONTRIBUTING.md b/vendor/doctrine/collections/CONTRIBUTING.md new file mode 100644 index 0000000..7a0a8c9 --- /dev/null +++ b/vendor/doctrine/collections/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# Contribute to Doctrine + +Thank you for contributing to Doctrine! + +Before we can merge your Pull-Request here are some guidelines that you need to follow. +These guidelines exist not to annoy you, but to keep the code base clean, +unified and future proof. + +## Coding Standard + +We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard). + +## Unit-Tests + +Please try to add a test for your pull-request. + +* If you want to contribute new functionality add unit- or functional tests + depending on the scope of the feature. + +You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project. +It will run all the project tests. + +In order to do that, you will need a fresh copy of doctrine/collections, and you +will have to run a composer installation in the project: + +```sh +git clone git@github.com:doctrine/collections.git +cd collections +curl -sS https://getcomposer.org/installer | php -- +./composer.phar install +``` + +## Github Actions + +We automatically run your pull request through Github Actions against supported +PHP versions. If you break the tests, we cannot merge your code, so please make +sure that your code is working before opening up a Pull-Request. + +## Getting merged + +Please allow us time to review your pull requests. We will give our best to review +everything as fast as possible, but cannot always live up to our own expectations. + +Thank you very much again for your contribution! diff --git a/vendor/doctrine/collections/LICENSE b/vendor/doctrine/collections/LICENSE new file mode 100644 index 0000000..5e781fc --- /dev/null +++ b/vendor/doctrine/collections/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +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. diff --git a/vendor/doctrine/collections/README.md b/vendor/doctrine/collections/README.md new file mode 100644 index 0000000..048fb61 --- /dev/null +++ b/vendor/doctrine/collections/README.md @@ -0,0 +1,6 @@ +# Doctrine Collections + +[![Build Status](https://github.com/doctrine/collections/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/collections/actions) +[![Code Coverage](https://codecov.io/gh/doctrine/collections/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/collections/branch/2.0.x) + +Collections Abstraction library diff --git a/vendor/doctrine/collections/UPGRADE.md b/vendor/doctrine/collections/UPGRADE.md new file mode 100644 index 0000000..44232f7 --- /dev/null +++ b/vendor/doctrine/collections/UPGRADE.md @@ -0,0 +1,112 @@ +Note about upgrading: Doctrine uses static and runtime mechanisms to raise +awareness about deprecated code. + +- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or + Static Analysis tools (like Psalm, phpstan) +- Use of our low-overhead runtime deprecation API, details: + https://github.com/doctrine/deprecations/ + +# Upgrade to 2.2 + +## Deprecated string representation of sort order + +Criteria orderings direction is now represented by the +`Doctrine\Common\Collection\Order` enum. + +As a consequence: + +- `Criteria::ASC` and `Criteria::DESC` are deprecated in favor of + `Order::Ascending` and `Order::Descending`, respectively. +- `Criteria::getOrderings()` is deprecated in favor of `Criteria::orderings()`, + which returns `array`. +- `Criteria::orderBy()` accepts `array`, but passing + anything other than `array` is deprecated. + +# Upgrade to 2.0 + +## BC breaking changes + +Native parameter types were added. Native return types will be added in 3.0.x +As a consequence, some signatures were changed and will have to be adjusted in sub-classes. + +Note that in order to keep compatibility with both 1.x and 2.x versions, +extending code would have to omit the added parameter types. +This would only work in PHP 7.2+ which is the first version featuring +[parameter widening](https://wiki.php.net/rfc/parameter-no-type-variance). +It is also recommended to add return types according to the tables below + +You can find a list of major changes to public API below. + +### Doctrine\Common\Collections\Collection + +| 1.0.x | 3.0.x | +|---------------------------------:|:-------------------------------------------------| +| `add($element)` | `add(mixed $element): void` | +| `clear()` | `clear(): void` | +| `contains($element)` | `contains(mixed $element): bool` | +| `isEmpty()` | `isEmpty(): bool` | +| `removeElement($element)` | `removeElement(mixed $element): bool` | +| `containsKey($key)` | `containsKey(string\|int $key): bool` | +| `get()` | `get(string\|int $key): mixed` | +| `getKeys()` | `getKeys(): array` | +| `getValues()` | `getValues(): array` | +| `set($key, $value)` | `set(string\|int $key, $value): void` | +| `toArray()` | `toArray(): array` | +| `first()` | `first(): mixed` | +| `last()` | `last(): mixed` | +| `key()` | `key(): int\|string\|null` | +| `current()` | `current(): mixed` | +| `next()` | `next(): mixed` | +| `exists(Closure $p)` | `exists(Closure $p): bool` | +| `filter(Closure $p)` | `filter(Closure $p): self` | +| `forAll(Closure $p)` | `forAll(Closure $p): bool` | +| `map(Closure $func)` | `map(Closure $func): self` | +| `partition(Closure $p)` | `partition(Closure $p): array` | +| `indexOf($element)` | `indexOf(mixed $element): int\|string\|false` | +| `slice($offset, $length = null)` | `slice(int $offset, ?int $length = null): array` | +| `count()` | `count(): int` | +| `getIterator()` | `getIterator(): \Traversable` | +| `offsetSet($offset, $value)` | `offsetSet(mixed $offset, mixed $value): void` | +| `offsetUnset($offset)` | `offsetUnset(mixed $offset): void` | +| `offsetExists($offset)` | `offsetExists(mixed $offset): bool` | + +### Doctrine\Common\Collections\AbstractLazyCollection + +| 1.0.x | 3.0.x | +|------------------:|:------------------------| +| `isInitialized()` | `isInitialized(): bool` | +| `initialize()` | `initialize(): void` | +| `doInitialize()` | `doInitialize(): void` | + +### Doctrine\Common\Collections\ArrayCollection + +| 1.0.x | 3.0.x | +|------------------------------:|:--------------------------------------| +| `createFrom(array $elements)` | `createFrom(array $elements): static` | +| `__toString()` | `__toString(): string` | + +### Doctrine\Common\Collections\Criteria + +| 1.0.x | 3.0.x | +|------------------------------------------:|:--------------------------------------------| +| `where(Expression $expression): self` | `where(Expression $expression): static` | +| `andWhere(Expression $expression): self` | `andWhere(Expression $expression): static` | +| `orWhere(Expression $expression): self` | `orWhere(Expression $expression): static` | +| `orderBy(array $orderings): self` | `orderBy(array $orderings): static` | +| `setFirstResult(?int $firstResult): self` | `setFirstResult(?int $firstResult): static` | +| `setMaxResult(?int $maxResults): self` | `setMaxResults(?int $maxResults): static` | + +### Doctrine\Common\Collections\Selectable + +| 1.0.x | 3.0.x | +|-------------------------------:|:-------------------------------------------| +| `matching(Criteria $criteria)` | `matching(Criteria $criteria): Collection` | + +# Upgrade to 1.7 + +## Deprecated null first result + +Passing null as `$firstResult` to +`Doctrine\Common\Collections\Criteria::__construct()` and to +`Doctrine\Common\Collections\Criteria::setFirstResult()` is deprecated. +Use `0` instead. diff --git a/vendor/doctrine/collections/composer.json b/vendor/doctrine/collections/composer.json new file mode 100644 index 0000000..4c36229 --- /dev/null +++ b/vendor/doctrine/collections/composer.json @@ -0,0 +1,63 @@ +{ + "name": "doctrine/collections", + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "license": "MIT", + "type": "library", + "keywords": [ + "php", + "collections", + "array", + "iterators" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "require": { + "php": "^8.1", + "doctrine/deprecations": "^1" + }, + "require-dev": { + "ext-json": "*", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.11" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\Common\\Collections\\": "tests" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/doctrine/collections/docs/en/derived-collections.rst b/vendor/doctrine/collections/docs/en/derived-collections.rst new file mode 100644 index 0000000..03d9da5 --- /dev/null +++ b/vendor/doctrine/collections/docs/en/derived-collections.rst @@ -0,0 +1,26 @@ +Derived Collections +=================== + +You can create custom collection classes by extending the +``Doctrine\Common\Collections\ArrayCollection`` class. If the +``__construct`` semantics are different from the default ``ArrayCollection`` +you can override the ``createFrom`` method: + +.. code-block:: php + final class DerivedArrayCollection extends ArrayCollection + { + /** @var \stdClass */ + private $foo; + + public function __construct(\stdClass $foo, array $elements = []) + { + $this->foo = $foo; + + parent::__construct($elements); + } + + protected function createFrom(array $elements) : self + { + return new static($this->foo, $elements); + } + } diff --git a/vendor/doctrine/collections/docs/en/expression-builder.rst b/vendor/doctrine/collections/docs/en/expression-builder.rst new file mode 100644 index 0000000..e022441 --- /dev/null +++ b/vendor/doctrine/collections/docs/en/expression-builder.rst @@ -0,0 +1,185 @@ +Expression Builder +================== + +The Expression Builder is a convenient fluent interface for +building expressions to be used with the ``Doctrine\Common\Collections\Criteria`` +class: + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $criteria = new Criteria(); + $criteria->where($expressionBuilder->eq('name', 'jwage')); + $criteria->orWhere($expressionBuilder->eq('name', 'romanb')); + + $collection->matching($criteria); + +The ``ExpressionBuilder`` has the following API: + +andX +---- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->andX( + $expressionBuilder->eq('foo', 1), + $expressionBuilder->eq('bar', 1) + ); + + $collection->matching(new Criteria($expression)); + +orX +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->orX( + $expressionBuilder->eq('foo', 1), + $expressionBuilder->eq('bar', 1) + ); + + $collection->matching(new Criteria($expression)); + +not +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->not( + $expressionBuilder->eq('foo', 1) + ); + + $collection->matching(new Criteria($expression)); + +eq +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->eq('foo', 1); + + $collection->matching(new Criteria($expression)); + +gt +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->gt('foo', 1); + + $collection->matching(new Criteria($expression)); + +lt +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->lt('foo', 1); + + $collection->matching(new Criteria($expression)); + +gte +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->gte('foo', 1); + + $collection->matching(new Criteria($expression)); + +lte +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->lte('foo', 1); + + $collection->matching(new Criteria($expression)); + +neq +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->neq('foo', 1); + + $collection->matching(new Criteria($expression)); + +isNull +------ + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->isNull('foo'); + + $collection->matching(new Criteria($expression)); + +in +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->in('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +notIn +----- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->notIn('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +contains +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->contains('foo', 'value1'); + + $collection->matching(new Criteria($expression)); + +memberOf +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->memberOf('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +startsWith +---------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->startsWith('foo', 'hello'); + + $collection->matching(new Criteria($expression)); + +endsWith +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->endsWith('foo', 'world'); + + $collection->matching(new Criteria($expression)); diff --git a/vendor/doctrine/collections/docs/en/expressions.rst b/vendor/doctrine/collections/docs/en/expressions.rst new file mode 100644 index 0000000..5587bfb --- /dev/null +++ b/vendor/doctrine/collections/docs/en/expressions.rst @@ -0,0 +1,117 @@ +Expressions +=========== + +The ``Doctrine\Common\Collections\Expr\Comparison`` class +can be used to create comparison expressions to be used with the +``Doctrine\Common\Collections\Criteria`` class. It has the +following operator constants: + +- ``Comparison::EQ`` +- ``Comparison::NEQ`` +- ``Comparison::LT`` +- ``Comparison::LTE`` +- ``Comparison::GT`` +- ``Comparison::GTE`` +- ``Comparison::IS`` +- ``Comparison::IN`` +- ``Comparison::NIN`` +- ``Comparison::CONTAINS`` +- ``Comparison::MEMBER_OF`` +- ``Comparison::STARTS_WITH`` +- ``Comparison::ENDS_WITH`` + +The ``Doctrine\Common\Collections\Expr\CompositeExpression`` class +can be used to create composite expressions to be used with the +``Doctrine\Common\Collections\Criteria`` class. It has the +following operator constants: + +- ``CompositeExpression::TYPE_AND`` +- ``CompositeExpression::TYPE_OR`` +- ``CompositeExpression::TYPE_NOT`` + +When using the ``TYPE_OR`` and ``TYPE_AND`` operators the +``CompositeExpression`` accepts multiple expressions as parameter +but only one expression can be provided when using the ``NOT`` operator. + +The ``Doctrine\Common\Collections\Criteria`` class has the following +API to be used with expressions: + +where +----- + +Sets the where expression to evaluate when this Criteria is searched for. + +.. code-block:: php + $expr = new Comparison('key', Comparison::EQ, 'value'); + + $criteria->where($expr); + +andWhere +-------- + +Appends the where expression to evaluate when this Criteria is searched for +using an AND with previous expression. + +.. code-block:: php + $expr = new Comparison('key', Comparison::EQ, 'value'); + + $criteria->andWhere($expr); + +orWhere +------- + +Appends the where expression to evaluate when this Criteria is searched for +using an OR with previous expression. + +.. code-block:: php + $expr1 = new Comparison('key', Comparison::EQ, 'value1'); + $expr2 = new Comparison('key', Comparison::EQ, 'value2'); + + $criteria->where($expr1); + $criteria->orWhere($expr2); + +orderBy +------- + +Sets the ordering of the result of this Criteria. + +.. code-block:: php + use Doctrine\Common\Collections\Order; + + $criteria->orderBy(['name' => Order::Ascending]); + +setFirstResult +-------------- + +Set the number of first result that this Criteria should return. + +.. code-block:: php + $criteria->setFirstResult(0); + +getFirstResult +-------------- + +Gets the current first result option of this Criteria. + +.. code-block:: php + $criteria->setFirstResult(10); + + echo $criteria->getFirstResult(); // 10 + +setMaxResults +------------- + +Sets the max results that this Criteria should return. + +.. code-block:: php + $criteria->setMaxResults(20); + +getMaxResults +------------- + +Gets the current max results option of this Criteria. + +.. code-block:: php + $criteria->setMaxResults(20); + + echo $criteria->getMaxResults(); // 20 diff --git a/vendor/doctrine/collections/docs/en/index.rst b/vendor/doctrine/collections/docs/en/index.rst new file mode 100644 index 0000000..7f2e38c --- /dev/null +++ b/vendor/doctrine/collections/docs/en/index.rst @@ -0,0 +1,357 @@ +Introduction +============ + +Doctrine Collections is a library that contains classes for working with +arrays of data. Here is an example using the simple +``Doctrine\Common\Collections\ArrayCollection`` class: + +.. code-block:: php + filter(function($element) { + return $element > 1; + }); // [2, 3] + +Collection Methods +================== + +Doctrine Collections provides an interface named ``Doctrine\Common\Collections\Collection`` +that resembles the nature of a regular PHP array. That is, +it is essentially an **ordered map** that can also be used +like a list. + +A Collection has an internal iterator just like a PHP array. In addition, +a Collection can be iterated with external iterators, which is preferable. +To use an external iterator simply use the foreach language construct to +iterate over the collection, which calls ``getIterator()`` internally, or +explicitly retrieve an iterator though ``getIterator()`` which can then be +used to iterate over the collection. You can not rely on the internal iterator +of the collection being at a certain position unless you explicitly positioned it before. + +Methods that do not alter the collection or have template types +appearing in invariant or contravariant positions are not directly +defined in ``Doctrine\Common\Collections\Collection``, but are inherited +from the ``Doctrine\Common\Collections\ReadableCollection`` interface. + +The methods available on the interface are: + +add +--- + +Adds an element at the end of the collection. + +.. code-block:: php + $collection->add('test'); + +clear +----- + +Clears the collection, removing all elements. + +.. code-block:: php + $collection->clear(); + +contains +-------- + +Checks whether an element is contained in the collection. This is an O(n) operation, where n is the size of the collection. + +.. code-block:: php + $collection = new Collection(['test']); + + $contains = $collection->contains('test'); // true + +containsKey +----------- + +Checks whether the collection contains an element with the specified key/index. + +.. code-block:: php + $collection = new Collection(['test' => true]); + + $contains = $collection->containsKey('test'); // true + +current +------- + +Gets the element of the collection at the current iterator position. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $current = $collection->current(); // first + +get +--- + +Gets the element at the specified key/index. + +.. code-block:: php + $collection = new Collection([ + 'key' => 'value', + ]); + + $value = $collection->get('key'); // value + +getKeys +------- + +Gets all keys/indices of the collection. + +.. code-block:: php + $collection = new Collection(['a', 'b', 'c']); + + $keys = $collection->getKeys(); // [0, 1, 2] + +getValues +--------- + +Gets all values of the collection. + +.. code-block:: php + $collection = new Collection([ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ]); + + $values = $collection->getValues(); // ['value1', 'value2', 'value3'] + +isEmpty +------- + +Checks whether the collection is empty (contains no elements). + +.. code-block:: php + $collection = new Collection(['a', 'b', 'c']); + + $isEmpty = $collection->isEmpty(); // false + +first +----- + +Sets the internal iterator to the first element in the collection and returns this element. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $first = $collection->first(); // first + +exists +------ + +Tests for the existence of an element that satisfies the given predicate. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $exists = $collection->exists(function($key, $value) { + return $value === 'first'; + }); // true + +findFirst +--------- + +Returns the first element of this collection that satisfies the given predicate. + +.. code-block:: php + $collection = new Collection([1, 2, 3, 2, 1]); + + $one = $collection->findFirst(function(int $key, int $value): bool { + return $value > 2 && $key > 1; + }); // 3 + +filter +------ + +Returns all the elements of this collection for which your callback function returns `true`. +The order and keys of the elements are preserved. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $filteredCollection = $collection->filter(function($element) { + return $element > 1; + }); // [2, 3] + +forAll +------ + +Tests whether the given predicate holds for all elements of this collection. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $forAll = $collection->forAll(function($key, $value) { + return $value > 1; + }); // false + +indexOf +------- + +Gets the index/key of a given element. The comparison of two elements is strict, that means not only the value but also the type must match. For objects this means reference equality. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $indexOf = $collection->indexOf(3); // 2 + +key +--- + +Gets the key/index of the element at the current iterator position. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->next(); + + $key = $collection->key(); // 1 + +last +---- + +Sets the internal iterator to the last element in the collection and returns this element. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $last = $collection->last(); // 3 + +map +--- + +Applies the given function to each element in the collection and returns a new collection with the elements returned by the function. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $mappedCollection = $collection->map(function($value) { + return $value + 1; + }); // [2, 3, 4] + +reduce +------ + +Applies iteratively the given function to each element in the collection, so as to reduce the collection to a single value. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $reduce = $collection->reduce(function(int $accumulator, int $value): int { + return $accumulator + $value; + }, 0); // 6 + +next +---- + +Moves the internal iterator position to the next element and returns this element. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $next = $collection->next(); // 2 + +partition +--------- + +Partitions this collection in two collections according to a predicate. Keys are preserved in the resulting collections. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $mappedCollection = $collection->partition(function($key, $value) { + return $value > 1 + }); // [[2, 3], [1]] + +remove +------ + +Removes the element at the specified index from the collection. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->remove(0); // [2, 3] + +removeElement +------------- + +Removes the specified element from the collection, if it is found. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->removeElement(3); // [1, 2] + +set +--- + +Sets an element in the collection at the specified key/index. + +.. code-block:: php + $collection = new ArrayCollection(); + + $collection->set('name', 'jwage'); + +slice +----- + +Extracts a slice of $length elements starting at position $offset from the Collection. If $length is null it returns all elements from $offset to the end of the Collection. Keys have to be preserved by this method. Calling this method will only return the selected slice and NOT change the elements contained in the collection slice is called on. + +.. code-block:: php + $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); + + $slice = $collection->slice(1, 2); // [1, 2] + +toArray +------- + +Gets a native PHP array representation of the collection. + +.. code-block:: php + $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); + + $array = $collection->toArray(); // [0, 1, 2, 3, 4, 5] + +Selectable Methods +================== + +Some Doctrine Collections, like ``Doctrine\Common\Collections\ArrayCollection``, +implement an interface named ``Doctrine\Common\Collections\Selectable`` +that offers the usage of a powerful expressions API, where conditions +can be applied to a collection to get a result with matching elements +only. + +matching +-------- + +Selects all elements from a selectable that match the expression and +returns a new collection containing these elements and preserved keys. + +.. code-block:: php + use Doctrine\Common\Collections\Criteria; + use Doctrine\Common\Collections\Expr\Comparison; + + $collection = new ArrayCollection([ + 'wage' => [ + 'name' => 'jwage', + ], + 'roman' => [ + 'name' => 'romanb', + ], + ]); + + $expr = new Comparison('name', '=', 'jwage'); + + $criteria = new Criteria(); + + $criteria->where($expr); + + $matchingCollection = $collection->matching($criteria); // [ 'wage' => [ 'name' => 'jwage' ]] + +You can read more about expressions :ref:`here `. diff --git a/vendor/doctrine/collections/docs/en/lazy-collections.rst b/vendor/doctrine/collections/docs/en/lazy-collections.rst new file mode 100644 index 0000000..b9cafb6 --- /dev/null +++ b/vendor/doctrine/collections/docs/en/lazy-collections.rst @@ -0,0 +1,26 @@ +Lazy Collections +================ + +To create a lazy collection you can extend the +``Doctrine\Common\Collections\AbstractLazyCollection`` class +and define the ``doInitialize`` method. Here is an example where +we lazily query the database for a collection of user records: + +.. code-block:: php + use Doctrine\DBAL\Connection; + + class UsersLazyCollection extends AbstractLazyCollection + { + /** @var Connection */ + private $connection; + + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + protected function doInitialize() : void + { + $this->collection = $this->connection->fetchAll('SELECT * FROM users'); + } + } diff --git a/vendor/doctrine/collections/docs/en/serialization.rst b/vendor/doctrine/collections/docs/en/serialization.rst new file mode 100644 index 0000000..be53d6e --- /dev/null +++ b/vendor/doctrine/collections/docs/en/serialization.rst @@ -0,0 +1,29 @@ +Serialization +============= + +Using (un-)serialize() on a collection is not a supported use-case +and may break when changes on the collection's internals happen in the future. +If a collection needs to be serialized, use ``toArray()`` and reconstruct +the collection manually. + +.. code-block:: php + + $collection = new ArrayCollection([1, 2, 3]); + $serialized = serialize($collection->toArray()); + +A reconstruction is also necessary when the collection contains objects with +infinite recursion of dependencies like in this ``json_serialize()`` example: + +.. code-block:: php + + $foo = new Foo(); + $bar = new Bar(); + + $foo->setBar($bar); + $bar->setFoo($foo); + + $collection = new ArrayCollection([$foo]); + $json = json_serialize($collection->toArray()); // recursion detected + +Serializer libraries can be used to create the serialization-output to prevent +errors. diff --git a/vendor/doctrine/collections/docs/en/sidebar.rst b/vendor/doctrine/collections/docs/en/sidebar.rst new file mode 100644 index 0000000..ebfb11c --- /dev/null +++ b/vendor/doctrine/collections/docs/en/sidebar.rst @@ -0,0 +1,11 @@ +:orphan: + +.. toctree:: + :depth: 3 + + index + expressions + expression-builder + derived-collections + lazy-collections + serialization diff --git a/vendor/doctrine/collections/src/AbstractLazyCollection.php b/vendor/doctrine/collections/src/AbstractLazyCollection.php new file mode 100644 index 0000000..56a340b --- /dev/null +++ b/vendor/doctrine/collections/src/AbstractLazyCollection.php @@ -0,0 +1,414 @@ + + */ +abstract class AbstractLazyCollection implements Collection +{ + /** + * The backed collection to use + * + * @psalm-var Collection|null + * @var Collection|null + */ + protected Collection|null $collection; + + protected bool $initialized = false; + + /** + * {@inheritDoc} + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + $this->initialize(); + + return $this->collection->count(); + } + + /** + * {@inheritDoc} + */ + public function add(mixed $element) + { + $this->initialize(); + + $this->collection->add($element); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->initialize(); + $this->collection->clear(); + } + + /** + * {@inheritDoc} + */ + public function contains(mixed $element) + { + $this->initialize(); + + return $this->collection->contains($element); + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + $this->initialize(); + + return $this->collection->isEmpty(); + } + + /** + * {@inheritDoc} + */ + public function remove(string|int $key) + { + $this->initialize(); + + return $this->collection->remove($key); + } + + /** + * {@inheritDoc} + */ + public function removeElement(mixed $element) + { + $this->initialize(); + + return $this->collection->removeElement($element); + } + + /** + * {@inheritDoc} + */ + public function containsKey(string|int $key) + { + $this->initialize(); + + return $this->collection->containsKey($key); + } + + /** + * {@inheritDoc} + */ + public function get(string|int $key) + { + $this->initialize(); + + return $this->collection->get($key); + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + $this->initialize(); + + return $this->collection->getKeys(); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + $this->initialize(); + + return $this->collection->getValues(); + } + + /** + * {@inheritDoc} + */ + public function set(string|int $key, mixed $value) + { + $this->initialize(); + $this->collection->set($key, $value); + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + $this->initialize(); + + return $this->collection->toArray(); + } + + /** + * {@inheritDoc} + */ + public function first() + { + $this->initialize(); + + return $this->collection->first(); + } + + /** + * {@inheritDoc} + */ + public function last() + { + $this->initialize(); + + return $this->collection->last(); + } + + /** + * {@inheritDoc} + */ + public function key() + { + $this->initialize(); + + return $this->collection->key(); + } + + /** + * {@inheritDoc} + */ + public function current() + { + $this->initialize(); + + return $this->collection->current(); + } + + /** + * {@inheritDoc} + */ + public function next() + { + $this->initialize(); + + return $this->collection->next(); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + $this->initialize(); + + return $this->collection->exists($p); + } + + /** + * {@inheritDoc} + */ + public function findFirst(Closure $p) + { + $this->initialize(); + + return $this->collection->findFirst($p); + } + + /** + * {@inheritDoc} + */ + public function filter(Closure $p) + { + $this->initialize(); + + return $this->collection->filter($p); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + $this->initialize(); + + return $this->collection->forAll($p); + } + + /** + * {@inheritDoc} + */ + public function map(Closure $func) + { + $this->initialize(); + + return $this->collection->map($func); + } + + /** + * {@inheritDoc} + */ + public function reduce(Closure $func, mixed $initial = null) + { + $this->initialize(); + + return $this->collection->reduce($func, $initial); + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $this->initialize(); + + return $this->collection->partition($p); + } + + /** + * {@inheritDoc} + * + * @template TMaybeContained + */ + public function indexOf(mixed $element) + { + $this->initialize(); + + return $this->collection->indexOf($element); + } + + /** + * {@inheritDoc} + */ + public function slice(int $offset, int|null $length = null) + { + $this->initialize(); + + return $this->collection->slice($offset, $length); + } + + /** + * {@inheritDoc} + * + * @return Traversable + * @psalm-return Traversable + */ + #[ReturnTypeWillChange] + public function getIterator() + { + $this->initialize(); + + return $this->collection->getIterator(); + } + + /** + * {@inheritDoc} + * + * @param TKey $offset + * + * @return bool + */ + #[ReturnTypeWillChange] + public function offsetExists(mixed $offset) + { + $this->initialize(); + + return $this->collection->offsetExists($offset); + } + + /** + * {@inheritDoc} + * + * @param TKey $offset + * + * @return T|null + */ + #[ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + $this->initialize(); + + return $this->collection->offsetGet($offset); + } + + /** + * {@inheritDoc} + * + * @param TKey|null $offset + * @param T $value + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetSet(mixed $offset, mixed $value) + { + $this->initialize(); + $this->collection->offsetSet($offset, $value); + } + + /** + * @param TKey $offset + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetUnset(mixed $offset) + { + $this->initialize(); + $this->collection->offsetUnset($offset); + } + + /** + * Is the lazy collection already initialized? + * + * @return bool + * + * @psalm-assert-if-true Collection $this->collection + */ + public function isInitialized() + { + return $this->initialized; + } + + /** + * Initialize the collection + * + * @return void + * + * @psalm-assert Collection $this->collection + */ + protected function initialize() + { + if ($this->initialized) { + return; + } + + $this->doInitialize(); + $this->initialized = true; + + if ($this->collection === null) { + throw new LogicException('You must initialize the collection property in the doInitialize() method.'); + } + } + + /** + * Do the initialization logic + * + * @return void + */ + abstract protected function doInitialize(); +} diff --git a/vendor/doctrine/collections/src/ArrayCollection.php b/vendor/doctrine/collections/src/ArrayCollection.php new file mode 100644 index 0000000..2e7d0e3 --- /dev/null +++ b/vendor/doctrine/collections/src/ArrayCollection.php @@ -0,0 +1,490 @@ + + * @template-implements Selectable + * @psalm-consistent-constructor + */ +class ArrayCollection implements Collection, Selectable, Stringable +{ + /** + * An array containing the entries of this collection. + * + * @psalm-var array + * @var mixed[] + */ + private array $elements = []; + + /** + * Initializes a new ArrayCollection. + * + * @psalm-param array $elements + */ + public function __construct(array $elements = []) + { + $this->elements = $elements; + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + return $this->elements; + } + + /** + * {@inheritDoc} + */ + public function first() + { + return reset($this->elements); + } + + /** + * Creates a new instance from the specified elements. + * + * This method is provided for derived classes to specify how a new + * instance should be created when constructor semantics have changed. + * + * @param array $elements Elements. + * @psalm-param array $elements + * + * @return static + * @psalm-return static + * + * @psalm-template K of array-key + * @psalm-template V + */ + protected function createFrom(array $elements) + { + return new static($elements); + } + + /** + * {@inheritDoc} + */ + public function last() + { + return end($this->elements); + } + + /** + * {@inheritDoc} + */ + public function key() + { + return key($this->elements); + } + + /** + * {@inheritDoc} + */ + public function next() + { + return next($this->elements); + } + + /** + * {@inheritDoc} + */ + public function current() + { + return current($this->elements); + } + + /** + * {@inheritDoc} + */ + public function remove(string|int $key) + { + if (! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) { + return null; + } + + $removed = $this->elements[$key]; + unset($this->elements[$key]); + + return $removed; + } + + /** + * {@inheritDoc} + */ + public function removeElement(mixed $element) + { + $key = array_search($element, $this->elements, true); + + if ($key === false) { + return false; + } + + unset($this->elements[$key]); + + return true; + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey $offset + * + * @return bool + */ + #[ReturnTypeWillChange] + public function offsetExists(mixed $offset) + { + return $this->containsKey($offset); + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey $offset + * + * @return T|null + */ + #[ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->get($offset); + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey|null $offset + * @param T $value + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetSet(mixed $offset, mixed $value) + { + if ($offset === null) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey $offset + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetUnset(mixed $offset) + { + $this->remove($offset); + } + + /** + * {@inheritDoc} + */ + public function containsKey(string|int $key) + { + return isset($this->elements[$key]) || array_key_exists($key, $this->elements); + } + + /** + * {@inheritDoc} + */ + public function contains(mixed $element) + { + return in_array($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + * + * @psalm-param TMaybeContained $element + * + * @return int|string|false + * @psalm-return (TMaybeContained is T ? TKey|false : false) + * + * @template TMaybeContained + */ + public function indexOf($element) + { + return array_search($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function get(string|int $key) + { + return $this->elements[$key] ?? null; + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + return array_keys($this->elements); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + return array_values($this->elements); + } + + /** + * {@inheritDoc} + * + * @return int<0, max> + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->elements); + } + + /** + * {@inheritDoc} + */ + public function set(string|int $key, mixed $value) + { + $this->elements[$key] = $value; + } + + /** + * {@inheritDoc} + * + * @psalm-suppress InvalidPropertyAssignmentValue + * + * This breaks assumptions about the template type, but it would + * be a backwards-incompatible change to remove this method + */ + public function add(mixed $element) + { + $this->elements[] = $element; + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + return empty($this->elements); + } + + /** + * {@inheritDoc} + * + * @return Traversable + * @psalm-return Traversable + */ + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->elements); + } + + /** + * {@inheritDoc} + * + * @psalm-param Closure(T):U $func + * + * @return static + * @psalm-return static + * + * @psalm-template U + */ + public function map(Closure $func) + { + return $this->createFrom(array_map($func, $this->elements)); + } + + /** + * {@inheritDoc} + */ + public function reduce(Closure $func, $initial = null) + { + return array_reduce($this->elements, $func, $initial); + } + + /** + * {@inheritDoc} + * + * @psalm-param Closure(T, TKey):bool $p + * + * @return static + * @psalm-return static + */ + public function filter(Closure $p) + { + return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH)); + } + + /** + * {@inheritDoc} + */ + public function findFirst(Closure $p) + { + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + return $element; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + foreach ($this->elements as $key => $element) { + if (! $p($key, $element)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $matches = $noMatches = []; + + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + $matches[$key] = $element; + } else { + $noMatches[$key] = $element; + } + } + + return [$this->createFrom($matches), $this->createFrom($noMatches)]; + } + + /** + * Returns a string representation of this object. + * {@inheritDoc} + * + * @return string + */ + #[ReturnTypeWillChange] + public function __toString() + { + return self::class . '@' . spl_object_hash($this); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->elements = []; + } + + /** + * {@inheritDoc} + */ + public function slice(int $offset, int|null $length = null) + { + return array_slice($this->elements, $offset, $length, true); + } + + /** @psalm-return Collection&Selectable */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + $filtered = $this->elements; + + if ($expr) { + $visitor = new ClosureExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $filtered = array_filter($filtered, $filter); + } + + $orderings = $criteria->orderings(); + + if ($orderings) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ClosureExpressionVisitor::sortByField($field, $ordering === Order::Descending ? -1 : 1, $next); + } + + uasort($filtered, $next); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset !== null && $offset > 0 || $length !== null && $length > 0) { + $filtered = array_slice($filtered, (int) $offset, $length, true); + } + + return $this->createFrom($filtered); + } +} diff --git a/vendor/doctrine/collections/src/Collection.php b/vendor/doctrine/collections/src/Collection.php new file mode 100644 index 0000000..c3c62aa --- /dev/null +++ b/vendor/doctrine/collections/src/Collection.php @@ -0,0 +1,117 @@ +ordered map that can also be used + * like a list. + * + * A Collection has an internal iterator just like a PHP array. In addition, + * a Collection can be iterated with external iterators, which is preferable. + * To use an external iterator simply use the foreach language construct to + * iterate over the collection (which calls {@link getIterator()} internally) or + * explicitly retrieve an iterator though {@link getIterator()} which can then be + * used to iterate over the collection. + * You can not rely on the internal iterator of the collection being at a certain + * position unless you explicitly positioned it before. Prefer iteration with + * external iterators. + * + * @psalm-template TKey of array-key + * @psalm-template T + * @template-extends ReadableCollection + * @template-extends ArrayAccess + */ +interface Collection extends ReadableCollection, ArrayAccess +{ + /** + * Adds an element at the end of the collection. + * + * @param mixed $element The element to add. + * @psalm-param T $element + * + * @return void we will require a native return type declaration in 3.0 + */ + public function add(mixed $element); + + /** + * Clears the collection, removing all elements. + * + * @return void + */ + public function clear(); + + /** + * Removes the element at the specified index from the collection. + * + * @param string|int $key The key/index of the element to remove. + * @psalm-param TKey $key + * + * @return mixed The removed element or NULL, if the collection did not contain the element. + * @psalm-return T|null + */ + public function remove(string|int $key); + + /** + * Removes the specified element from the collection, if it is found. + * + * @param mixed $element The element to remove. + * @psalm-param T $element + * + * @return bool TRUE if this collection contained the specified element, FALSE otherwise. + */ + public function removeElement(mixed $element); + + /** + * Sets an element in the collection at the specified key/index. + * + * @param string|int $key The key/index of the element to set. + * @param mixed $value The element to set. + * @psalm-param TKey $key + * @psalm-param T $value + * + * @return void + */ + public function set(string|int $key, mixed $value); + + /** + * {@inheritDoc} + * + * @psalm-param Closure(T):U $func + * + * @return Collection + * @psalm-return Collection + * + * @psalm-template U + */ + public function map(Closure $func); + + /** + * {@inheritDoc} + * + * @psalm-param Closure(T, TKey):bool $p + * + * @return Collection A collection with the results of the filter operation. + * @psalm-return Collection + */ + public function filter(Closure $p); + + /** + * {@inheritDoc} + * + * @psalm-param Closure(TKey, T):bool $p + * + * @return Collection[] An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + * @psalm-return array{0: Collection, 1: Collection} + */ + public function partition(Closure $p); +} diff --git a/vendor/doctrine/collections/src/Criteria.php b/vendor/doctrine/collections/src/Criteria.php new file mode 100644 index 0000000..4c8a0a7 --- /dev/null +++ b/vendor/doctrine/collections/src/Criteria.php @@ -0,0 +1,285 @@ + */ + private array $orderings = []; + + private int|null $firstResult = null; + private int|null $maxResults = null; + + /** + * Creates an instance of the class. + * + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Returns the expression builder. + * + * @return ExpressionBuilder + */ + public static function expr() + { + if (self::$expressionBuilder === null) { + self::$expressionBuilder = new ExpressionBuilder(); + } + + return self::$expressionBuilder; + } + + /** + * Construct a new Criteria. + * + * @param array|null $orderings + */ + public function __construct( + private Expression|null $expression = null, + array|null $orderings = null, + int|null $firstResult = null, + int|null $maxResults = null, + ) { + $this->expression = $expression; + + if ($firstResult === null && func_num_args() > 2) { + Deprecation::trigger( + 'doctrine/collections', + 'https://github.com/doctrine/collections/pull/311', + 'Passing null as $firstResult to the constructor of %s is deprecated. Pass 0 instead or omit the argument.', + self::class, + ); + } + + $this->setFirstResult($firstResult); + $this->setMaxResults($maxResults); + + if ($orderings === null) { + return; + } + + $this->orderBy($orderings); + } + + /** + * Sets the where expression to evaluate when this Criteria is searched for. + * + * @return $this + */ + public function where(Expression $expression) + { + $this->expression = $expression; + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an AND with previous expression. + * + * @return $this + */ + public function andWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression( + CompositeExpression::TYPE_AND, + [$this->expression, $expression], + ); + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an OR with previous expression. + * + * @return $this + */ + public function orWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression( + CompositeExpression::TYPE_OR, + [$this->expression, $expression], + ); + + return $this; + } + + /** + * Gets the expression attached to this Criteria. + * + * @return Expression|null + */ + public function getWhereExpression() + { + return $this->expression; + } + + /** + * Gets the current orderings of this Criteria. + * + * @deprecated use orderings() instead + * + * @return array + */ + public function getOrderings() + { + Deprecation::trigger( + 'doctrine/collections', + 'https://github.com/doctrine/collections/pull/389', + 'Calling %s() is deprecated. Use %s::orderings() instead.', + __METHOD__, + self::class, + ); + + return array_map( + static fn (Order $ordering): string => $ordering->value, + $this->orderings, + ); + } + + /** + * Gets the current orderings of this Criteria. + * + * @return array + */ + public function orderings(): array + { + return $this->orderings; + } + + /** + * Sets the ordering of the result of this Criteria. + * + * Keys are field and values are the order, being a valid Order enum case. + * + * @see Order::Ascending + * @see Order::Descending + * + * @param array $orderings + * + * @return $this + */ + public function orderBy(array $orderings) + { + $method = __METHOD__; + $this->orderings = array_map( + static function (string|Order $ordering) use ($method): Order { + if ($ordering instanceof Order) { + return $ordering; + } + + static $triggered = false; + + if (! $triggered) { + Deprecation::trigger( + 'doctrine/collections', + 'https://github.com/doctrine/collections/pull/389', + 'Passing non-Order enum values to %s() is deprecated. Pass Order enum values instead.', + $method, + ); + } + + $triggered = true; + + return strtoupper($ordering) === Order::Ascending->value ? Order::Ascending : Order::Descending; + }, + $orderings, + ); + + return $this; + } + + /** + * Gets the current first result option of this Criteria. + * + * @return int|null + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Set the number of first result that this Criteria should return. + * + * @param int|null $firstResult The value to set. + * + * @return $this + */ + public function setFirstResult(int|null $firstResult) + { + if ($firstResult === null) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/collections', + 'https://github.com/doctrine/collections/pull/311', + 'Passing null to %s() is deprecated, pass 0 instead.', + __METHOD__, + ); + } + + $this->firstResult = $firstResult; + + return $this; + } + + /** + * Gets maxResults. + * + * @return int|null + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Sets maxResults. + * + * @param int|null $maxResults The value to set. + * + * @return $this + */ + public function setMaxResults(int|null $maxResults) + { + $this->maxResults = $maxResults; + + return $this; + } +} diff --git a/vendor/doctrine/collections/src/Expr/ClosureExpressionVisitor.php b/vendor/doctrine/collections/src/Expr/ClosureExpressionVisitor.php new file mode 100644 index 0000000..4319b9b --- /dev/null +++ b/vendor/doctrine/collections/src/Expr/ClosureExpressionVisitor.php @@ -0,0 +1,222 @@ +$accessor(); + } + } + + if (preg_match('/^is[A-Z]+/', $field) === 1 && method_exists($object, $field)) { + return $object->$field(); + } + + // __call should be triggered for get. + $accessor = $accessors[0] . $field; + + if (method_exists($object, '__call')) { + return $object->$accessor(); + } + + if ($object instanceof ArrayAccess) { + return $object[$field]; + } + + if (isset($object->$field)) { + return $object->$field; + } + + // camelcase field name to support different variable naming conventions + $ccField = preg_replace_callback('/_(.?)/', static fn ($matches) => strtoupper((string) $matches[1]), $field); + + foreach ($accessors as $accessor) { + $accessor .= $ccField; + + if (method_exists($object, $accessor)) { + return $object->$accessor(); + } + } + + return $object->$field; + } + + /** + * Helper for sorting arrays of objects based on multiple fields + orientations. + * + * @return Closure + */ + public static function sortByField(string $name, int $orientation = 1, Closure|null $next = null) + { + if (! $next) { + $next = static fn (): int => 0; + } + + return static function ($a, $b) use ($name, $next, $orientation): int { + $aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name); + + $bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name); + + if ($aValue === $bValue) { + return $next($a, $b); + } + + return ($aValue > $bValue ? 1 : -1) * $orientation; + }; + } + + /** + * {@inheritDoc} + */ + public function walkComparison(Comparison $comparison) + { + $field = $comparison->getField(); + $value = $comparison->getValue()->getValue(); + + return match ($comparison->getOperator()) { + Comparison::EQ => static fn ($object): bool => self::getObjectFieldValue($object, $field) === $value, + Comparison::NEQ => static fn ($object): bool => self::getObjectFieldValue($object, $field) !== $value, + Comparison::LT => static fn ($object): bool => self::getObjectFieldValue($object, $field) < $value, + Comparison::LTE => static fn ($object): bool => self::getObjectFieldValue($object, $field) <= $value, + Comparison::GT => static fn ($object): bool => self::getObjectFieldValue($object, $field) > $value, + Comparison::GTE => static fn ($object): bool => self::getObjectFieldValue($object, $field) >= $value, + Comparison::IN => static function ($object) use ($field, $value): bool { + $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + return in_array($fieldValue, $value, is_scalar($fieldValue)); + }, + Comparison::NIN => static function ($object) use ($field, $value): bool { + $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + return ! in_array($fieldValue, $value, is_scalar($fieldValue)); + }, + Comparison::CONTAINS => static fn ($object): bool => str_contains((string) self::getObjectFieldValue($object, $field), (string) $value), + Comparison::MEMBER_OF => static function ($object) use ($field, $value): bool { + $fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + if (! is_array($fieldValues)) { + $fieldValues = iterator_to_array($fieldValues); + } + + return in_array($value, $fieldValues, true); + }, + Comparison::STARTS_WITH => static fn ($object): bool => str_starts_with((string) self::getObjectFieldValue($object, $field), (string) $value), + Comparison::ENDS_WITH => static fn ($object): bool => str_ends_with((string) self::getObjectFieldValue($object, $field), (string) $value), + default => throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()), + }; + } + + /** + * {@inheritDoc} + */ + public function walkValue(Value $value) + { + return $value->getValue(); + } + + /** + * {@inheritDoc} + */ + public function walkCompositeExpression(CompositeExpression $expr) + { + $expressionList = []; + + foreach ($expr->getExpressionList() as $child) { + $expressionList[] = $this->dispatch($child); + } + + return match ($expr->getType()) { + CompositeExpression::TYPE_AND => $this->andExpressions($expressionList), + CompositeExpression::TYPE_OR => $this->orExpressions($expressionList), + CompositeExpression::TYPE_NOT => $this->notExpression($expressionList), + default => throw new RuntimeException('Unknown composite ' . $expr->getType()), + }; + } + + /** @param callable[] $expressions */ + private function andExpressions(array $expressions): Closure + { + return static function ($object) use ($expressions): bool { + foreach ($expressions as $expression) { + if (! $expression($object)) { + return false; + } + } + + return true; + }; + } + + /** @param callable[] $expressions */ + private function orExpressions(array $expressions): Closure + { + return static function ($object) use ($expressions): bool { + foreach ($expressions as $expression) { + if ($expression($object)) { + return true; + } + } + + return false; + }; + } + + /** @param callable[] $expressions */ + private function notExpression(array $expressions): Closure + { + return static fn ($object) => ! $expressions[0]($object); + } +} diff --git a/vendor/doctrine/collections/src/Expr/Comparison.php b/vendor/doctrine/collections/src/Expr/Comparison.php new file mode 100644 index 0000000..f1ea07f --- /dev/null +++ b/vendor/doctrine/collections/src/Expr/Comparison.php @@ -0,0 +1,62 @@ +'; + final public const LT = '<'; + final public const LTE = '<='; + final public const GT = '>'; + final public const GTE = '>='; + final public const IS = '='; // no difference with EQ + final public const IN = 'IN'; + final public const NIN = 'NIN'; + final public const CONTAINS = 'CONTAINS'; + final public const MEMBER_OF = 'MEMBER_OF'; + final public const STARTS_WITH = 'STARTS_WITH'; + final public const ENDS_WITH = 'ENDS_WITH'; + + private readonly Value $value; + + public function __construct(private readonly string $field, private readonly string $op, mixed $value) + { + if (! ($value instanceof Value)) { + $value = new Value($value); + } + + $this->value = $value; + } + + /** @return string */ + public function getField() + { + return $this->field; + } + + /** @return Value */ + public function getValue() + { + return $this->value; + } + + /** @return string */ + public function getOperator() + { + return $this->op; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkComparison($this); + } +} diff --git a/vendor/doctrine/collections/src/Expr/CompositeExpression.php b/vendor/doctrine/collections/src/Expr/CompositeExpression.php new file mode 100644 index 0000000..b7b4544 --- /dev/null +++ b/vendor/doctrine/collections/src/Expr/CompositeExpression.php @@ -0,0 +1,70 @@ + */ + private array $expressions = []; + + /** + * @param Expression[] $expressions + * + * @throws RuntimeException + */ + public function __construct(private readonly string $type, array $expressions) + { + foreach ($expressions as $expr) { + if ($expr instanceof Value) { + throw new RuntimeException('Values are not supported expressions as children of and/or expressions.'); + } + + if (! ($expr instanceof Expression)) { + throw new RuntimeException('No expression given to CompositeExpression.'); + } + + $this->expressions[] = $expr; + } + + if ($type === self::TYPE_NOT && count($this->expressions) !== 1) { + throw new RuntimeException('Not expression only allows one expression as child.'); + } + } + + /** + * Returns the list of expressions nested in this composite. + * + * @return list + */ + public function getExpressionList() + { + return $this->expressions; + } + + /** @return string */ + public function getType() + { + return $this->type; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkCompositeExpression($this); + } +} diff --git a/vendor/doctrine/collections/src/Expr/Expression.php b/vendor/doctrine/collections/src/Expr/Expression.php new file mode 100644 index 0000000..70ad45f --- /dev/null +++ b/vendor/doctrine/collections/src/Expr/Expression.php @@ -0,0 +1,14 @@ + $this->walkComparison($expr), + $expr instanceof Value => $this->walkValue($expr), + $expr instanceof CompositeExpression => $this->walkCompositeExpression($expr), + default => throw new RuntimeException('Unknown Expression ' . $expr::class), + }; + } +} diff --git a/vendor/doctrine/collections/src/Expr/Value.php b/vendor/doctrine/collections/src/Expr/Value.php new file mode 100644 index 0000000..776770d --- /dev/null +++ b/vendor/doctrine/collections/src/Expr/Value.php @@ -0,0 +1,26 @@ +value; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkValue($this); + } +} diff --git a/vendor/doctrine/collections/src/ExpressionBuilder.php b/vendor/doctrine/collections/src/ExpressionBuilder.php new file mode 100644 index 0000000..fc25e3a --- /dev/null +++ b/vendor/doctrine/collections/src/ExpressionBuilder.php @@ -0,0 +1,123 @@ + + */ +interface ReadableCollection extends Countable, IteratorAggregate +{ + /** + * Checks whether an element is contained in the collection. + * This is an O(n) operation, where n is the size of the collection. + * + * @param mixed $element The element to search for. + * @psalm-param TMaybeContained $element + * + * @return bool TRUE if the collection contains the element, FALSE otherwise. + * @psalm-return (TMaybeContained is T ? bool : false) + * + * @template TMaybeContained + */ + public function contains(mixed $element); + + /** + * Checks whether the collection is empty (contains no elements). + * + * @return bool TRUE if the collection is empty, FALSE otherwise. + */ + public function isEmpty(); + + /** + * Checks whether the collection contains an element with the specified key/index. + * + * @param string|int $key The key/index to check for. + * @psalm-param TKey $key + * + * @return bool TRUE if the collection contains an element with the specified key/index, + * FALSE otherwise. + */ + public function containsKey(string|int $key); + + /** + * Gets the element at the specified key/index. + * + * @param string|int $key The key/index of the element to retrieve. + * @psalm-param TKey $key + * + * @return mixed + * @psalm-return T|null + */ + public function get(string|int $key); + + /** + * Gets all keys/indices of the collection. + * + * @return int[]|string[] The keys/indices of the collection, in the order of the corresponding + * elements in the collection. + * @psalm-return list + */ + public function getKeys(); + + /** + * Gets all values of the collection. + * + * @return mixed[] The values of all elements in the collection, in the + * order they appear in the collection. + * @psalm-return list + */ + public function getValues(); + + /** + * Gets a native PHP array representation of the collection. + * + * @return mixed[] + * @psalm-return array + */ + public function toArray(); + + /** + * Sets the internal iterator to the first element in the collection and returns this element. + * + * @return mixed + * @psalm-return T|false + */ + public function first(); + + /** + * Sets the internal iterator to the last element in the collection and returns this element. + * + * @return mixed + * @psalm-return T|false + */ + public function last(); + + /** + * Gets the key/index of the element at the current iterator position. + * + * @return int|string|null + * @psalm-return TKey|null + */ + public function key(); + + /** + * Gets the element of the collection at the current iterator position. + * + * @return mixed + * @psalm-return T|false + */ + public function current(); + + /** + * Moves the internal iterator position to the next element and returns this element. + * + * @return mixed + * @psalm-return T|false + */ + public function next(); + + /** + * Extracts a slice of $length elements starting at position $offset from the Collection. + * + * If $length is null it returns all elements from $offset to the end of the Collection. + * Keys have to be preserved by this method. Calling this method will only return the + * selected slice and NOT change the elements contained in the collection slice is called on. + * + * @param int $offset The offset to start from. + * @param int|null $length The maximum number of elements to return, or null for no limit. + * + * @return mixed[] + * @psalm-return array + */ + public function slice(int $offset, int|null $length = null); + + /** + * Tests for the existence of an element that satisfies the given predicate. + * + * @param Closure $p The predicate. + * @psalm-param Closure(TKey, T):bool $p + * + * @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise. + */ + public function exists(Closure $p); + + /** + * Returns all the elements of this collection that satisfy the predicate p. + * The order of the elements is preserved. + * + * @param Closure $p The predicate used for filtering. + * @psalm-param Closure(T, TKey):bool $p + * + * @return ReadableCollection A collection with the results of the filter operation. + * @psalm-return ReadableCollection + */ + public function filter(Closure $p); + + /** + * Applies the given function to each element in the collection and returns + * a new collection with the elements returned by the function. + * + * @psalm-param Closure(T):U $func + * + * @return ReadableCollection + * @psalm-return ReadableCollection + * + * @psalm-template U + */ + public function map(Closure $func); + + /** + * Partitions this collection in two collections according to a predicate. + * Keys are preserved in the resulting collections. + * + * @param Closure $p The predicate on which to partition. + * @psalm-param Closure(TKey, T):bool $p + * + * @return ReadableCollection[] An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + * @psalm-return array{0: ReadableCollection, 1: ReadableCollection} + */ + public function partition(Closure $p); + + /** + * Tests whether the given predicate p holds for all elements of this collection. + * + * @param Closure $p The predicate. + * @psalm-param Closure(TKey, T):bool $p + * + * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. + */ + public function forAll(Closure $p); + + /** + * Gets the index/key of a given element. The comparison of two elements is strict, + * that means not only the value but also the type must match. + * For objects this means reference equality. + * + * @param mixed $element The element to search for. + * @psalm-param TMaybeContained $element + * + * @return int|string|bool The key/index of the element or FALSE if the element was not found. + * @psalm-return (TMaybeContained is T ? TKey|false : false) + * + * @template TMaybeContained + */ + public function indexOf(mixed $element); + + /** + * Returns the first element of this collection that satisfies the predicate p. + * + * @param Closure $p The predicate. + * @psalm-param Closure(TKey, T):bool $p + * + * @return mixed The first element respecting the predicate, + * null if no element respects the predicate. + * @psalm-return T|null + */ + public function findFirst(Closure $p); + + /** + * Applies iteratively the given function to each element in the collection, + * so as to reduce the collection to a single value. + * + * @psalm-param Closure(TReturn|TInitial, T):TReturn $func + * @psalm-param TInitial $initial + * + * @return mixed + * @psalm-return TReturn|TInitial + * + * @psalm-template TReturn + * @psalm-template TInitial + */ + public function reduce(Closure $func, mixed $initial = null); +} diff --git a/vendor/doctrine/collections/src/Selectable.php b/vendor/doctrine/collections/src/Selectable.php new file mode 100644 index 0000000..5fa87cb --- /dev/null +++ b/vendor/doctrine/collections/src/Selectable.php @@ -0,0 +1,32 @@ +&Selectable + * @psalm-return ReadableCollection&Selectable + */ + public function matching(Criteria $criteria); +} diff --git a/vendor/doctrine/dbal/CONTRIBUTING.md b/vendor/doctrine/dbal/CONTRIBUTING.md new file mode 100644 index 0000000..31b6eff --- /dev/null +++ b/vendor/doctrine/dbal/CONTRIBUTING.md @@ -0,0 +1,6 @@ +This repository has [guidelines specific to testing][testing guidelines], and +Doctrine has [general contributing guidelines][contributor workflow], make +sure you follow both. + +[contributor workflow]: https://www.doctrine-project.org/contribute/index.html +[testing guidelines]: https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/testing.html diff --git a/vendor/doctrine/dbal/LICENSE b/vendor/doctrine/dbal/LICENSE new file mode 100644 index 0000000..e8fdec4 --- /dev/null +++ b/vendor/doctrine/dbal/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2018 Doctrine Project + +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. diff --git a/vendor/doctrine/dbal/README.md b/vendor/doctrine/dbal/README.md new file mode 100644 index 0000000..7f2f66c --- /dev/null +++ b/vendor/doctrine/dbal/README.md @@ -0,0 +1,50 @@ +# Doctrine DBAL + +| [5.0-dev][5.0] | [4.2-dev][4.2] | [4.1][4.1] | [3.9][3.9] | +|:---------------------------------------------------:|:---------------------------------------------------:|:---------------------------------------------------:|:---------------------------------------------------:| +| [![GitHub Actions][GA 5.0 image]][GA 5.0] | [![GitHub Actions][GA 4.2 image]][GA 4.2] | [![GitHub Actions][GA 4.1 image]][GA 4.1] | [![GitHub Actions][GA 3.9 image]][GA 3.9] | +| [![AppVeyor][AppVeyor 5.0 image]][AppVeyor 5.0] | [![AppVeyor][AppVeyor 4.2 image]][AppVeyor 4.2] | [![AppVeyor][AppVeyor 4.1 image]][AppVeyor 4.1] | [![AppVeyor][AppVeyor 3.9 image]][AppVeyor 3.9] | +| [![Code Coverage][Coverage 5.0 image]][CodeCov 5.0] | [![Code Coverage][Coverage 4.2 image]][CodeCov 4.2] | [![Code Coverage][Coverage 4.1 image]][CodeCov 4.1] | [![Code Coverage][Coverage 3.9 image]][CodeCov 3.9] | +| N/A | N/A | [![Type Coverage][TypeCov image]][TypeCov] | N/A | + +Powerful ***D***ata***B***ase ***A***bstraction ***L***ayer with many features for database schema introspection and schema management. + +## More resources: + +* [Website](http://www.doctrine-project.org/projects/dbal.html) +* [Documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/) +* [Issue Tracker](https://github.com/doctrine/dbal/issues) + + [Coverage 5.0 image]: https://codecov.io/gh/doctrine/dbal/branch/5.0.x/graph/badge.svg + [5.0]: https://github.com/doctrine/dbal/tree/5.0.x + [CodeCov 5.0]: https://codecov.io/gh/doctrine/dbal/branch/5.0.x + [AppVeyor 5.0]: https://ci.appveyor.com/project/doctrine/dbal/branch/5.0.x + [AppVeyor 5.0 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/5.0.x?svg=true + [GA 5.0]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A5.0.x + [GA 5.0 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=5.0.x + + [Coverage 4.2 image]: https://codecov.io/gh/doctrine/dbal/branch/4.2.x/graph/badge.svg + [4.2]: https://github.com/doctrine/dbal/tree/4.2.x + [CodeCov 4.2]: https://codecov.io/gh/doctrine/dbal/branch/4.2.x + [AppVeyor 4.2]: https://ci.appveyor.com/project/doctrine/dbal/branch/4.2.x + [AppVeyor 4.2 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/4.2.x?svg=true + [GA 4.2]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.2.x + [GA 4.2 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=4.2.x + + [Coverage 4.1 image]: https://codecov.io/gh/doctrine/dbal/branch/4.1.x/graph/badge.svg + [4.1]: https://github.com/doctrine/dbal/tree/4.1.x + [CodeCov 4.1]: https://codecov.io/gh/doctrine/dbal/branch/4.1.x + [AppVeyor 4.1]: https://ci.appveyor.com/project/doctrine/dbal/branch/4.1.x + [AppVeyor 4.1 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/4.1.x?svg=true + [GA 4.1]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.1.x + [GA 4.1 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=4.1.x + [TypeCov]: https://shepherd.dev/github/doctrine/dbal + [TypeCov image]: https://shepherd.dev/github/doctrine/dbal/coverage.svg + + [Coverage 3.9 image]: https://codecov.io/gh/doctrine/dbal/branch/3.9.x/graph/badge.svg + [3.9]: https://github.com/doctrine/dbal/tree/3.9.x + [CodeCov 3.9]: https://codecov.io/gh/doctrine/dbal/branch/3.9.x + [AppVeyor 3.9]: https://ci.appveyor.com/project/doctrine/dbal/branch/3.9.x + [AppVeyor 3.9 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/3.9.x?svg=true + [GA 3.9]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A3.9.x + [GA 3.9 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=3.9.x diff --git a/vendor/doctrine/dbal/bin/doctrine-dbal b/vendor/doctrine/dbal/bin/doctrine-dbal new file mode 100755 index 0000000..0531527 --- /dev/null +++ b/vendor/doctrine/dbal/bin/doctrine-dbal @@ -0,0 +1,4 @@ +#!/usr/bin/env php +> */ + private array $data; + + private int $columnCount = 0; + private int $num = 0; + + /** @param list> $data */ + public function __construct(array $data) + { + $this->data = $data; + if (count($data) === 0) { + return; + } + + $this->columnCount = count($data[0]); + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + $row = $this->fetch(); + + if ($row === false) { + return false; + } + + return array_values($row); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->fetch(); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + $row = $this->fetch(); + + if ($row === false) { + return false; + } + + return reset($row); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + public function rowCount(): int + { + return count($this->data); + } + + public function columnCount(): int + { + return $this->columnCount; + } + + public function free(): void + { + $this->data = []; + } + + /** @return array|false */ + private function fetch() + { + if (! isset($this->data[$this->num])) { + return false; + } + + return $this->data[$this->num++]; + } +} diff --git a/vendor/doctrine/dbal/src/Cache/CacheException.php b/vendor/doctrine/dbal/src/Cache/CacheException.php new file mode 100644 index 0000000..18e95d6 --- /dev/null +++ b/vendor/doctrine/dbal/src/Cache/CacheException.php @@ -0,0 +1,21 @@ +lifetime = $lifetime; + $this->cacheKey = $cacheKey; + if ($resultCache instanceof CacheItemPoolInterface) { + $this->resultCache = $resultCache; + } elseif ($resultCache instanceof Cache) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4620', + 'Passing an instance of %s to %s as $resultCache is deprecated. Pass an instance of %s instead.', + Cache::class, + __METHOD__, + CacheItemPoolInterface::class, + ); + + $this->resultCache = CacheAdapter::wrap($resultCache); + } elseif ($resultCache !== null) { + throw new TypeError(sprintf( + '$resultCache: Expected either null or an instance of %s or %s, got %s.', + CacheItemPoolInterface::class, + Cache::class, + get_class($resultCache), + )); + } + } + + public function getResultCache(): ?CacheItemPoolInterface + { + return $this->resultCache; + } + + /** + * @deprecated Use {@see getResultCache()} instead. + * + * @return Cache|null + */ + public function getResultCacheDriver() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4620', + '%s is deprecated, call getResultCache() instead.', + __METHOD__, + ); + + return $this->resultCache !== null ? DoctrineProvider::wrap($this->resultCache) : null; + } + + /** @return int */ + public function getLifetime() + { + return $this->lifetime; + } + + /** + * @return string + * + * @throws CacheException + */ + public function getCacheKey() + { + if ($this->cacheKey === null) { + throw CacheException::noCacheKey(); + } + + return $this->cacheKey; + } + + /** + * Generates the real cache key from query, params, types and connection parameters. + * + * @param string $sql + * @param list|array $params + * @param array|array $types + * @param array $connectionParams + * + * @return array{string, string} + */ + public function generateCacheKeys($sql, $params, $types, array $connectionParams = []) + { + if (isset($connectionParams['password'])) { + unset($connectionParams['password']); + } + + $realCacheKey = 'query=' . $sql . + '¶ms=' . serialize($params) . + '&types=' . serialize($types) . + '&connectionParams=' . hash('sha256', serialize($connectionParams)); + + // should the key be automatically generated using the inputs or is the cache key set? + $cacheKey = $this->cacheKey ?? sha1($realCacheKey); + + return [$cacheKey, $realCacheKey]; + } + + public function setResultCache(CacheItemPoolInterface $cache): QueryCacheProfile + { + return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache); + } + + /** + * @deprecated Use {@see setResultCache()} instead. + * + * @return QueryCacheProfile + */ + public function setResultCacheDriver(Cache $cache) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4620', + '%s is deprecated, call setResultCache() instead.', + __METHOD__, + ); + + return new QueryCacheProfile($this->lifetime, $this->cacheKey, CacheAdapter::wrap($cache)); + } + + /** + * @param string|null $cacheKey + * + * @return QueryCacheProfile + */ + public function setCacheKey($cacheKey) + { + return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCache); + } + + /** + * @param int $lifetime + * + * @return QueryCacheProfile + */ + public function setLifetime($lifetime) + { + return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCache); + } +} diff --git a/vendor/doctrine/dbal/src/ColumnCase.php b/vendor/doctrine/dbal/src/ColumnCase.php new file mode 100644 index 0000000..cb0dd40 --- /dev/null +++ b/vendor/doctrine/dbal/src/ColumnCase.php @@ -0,0 +1,28 @@ +schemaAssetsFilter = static function (): bool { + return true; + }; + } + + /** + * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. + * + * @deprecated Use {@see setMiddlewares()} and {@see \Doctrine\DBAL\Logging\Middleware} instead. + */ + public function setSQLLogger(?SQLLogger $logger = null): void + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4967', + '%s is deprecated, use setMiddlewares() and Logging\\Middleware instead.', + __METHOD__, + ); + + $this->sqlLogger = $logger; + } + + /** + * Gets the SQL logger that is used. + * + * @deprecated + */ + public function getSQLLogger(): ?SQLLogger + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4967', + '%s is deprecated.', + __METHOD__, + ); + + return $this->sqlLogger; + } + + /** + * Gets the cache driver implementation that is used for query result caching. + */ + public function getResultCache(): ?CacheItemPoolInterface + { + return $this->resultCache; + } + + /** + * Gets the cache driver implementation that is used for query result caching. + * + * @deprecated Use {@see getResultCache()} instead. + */ + public function getResultCacheImpl(): ?Cache + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4620', + '%s is deprecated, call getResultCache() instead.', + __METHOD__, + ); + + return $this->resultCacheImpl; + } + + /** + * Sets the cache driver implementation that is used for query result caching. + */ + public function setResultCache(CacheItemPoolInterface $cache): void + { + $this->resultCacheImpl = DoctrineProvider::wrap($cache); + $this->resultCache = $cache; + } + + /** + * Sets the cache driver implementation that is used for query result caching. + * + * @deprecated Use {@see setResultCache()} instead. + */ + public function setResultCacheImpl(Cache $cacheImpl): void + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4620', + '%s is deprecated, call setResultCache() instead.', + __METHOD__, + ); + + $this->resultCacheImpl = $cacheImpl; + $this->resultCache = CacheAdapter::wrap($cacheImpl); + } + + /** + * Sets the callable to use to filter schema assets. + */ + public function setSchemaAssetsFilter(?callable $callable = null): void + { + if (func_num_args() < 1) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5483', + 'Not passing an argument to %s is deprecated.', + __METHOD__, + ); + } elseif ($callable === null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5483', + 'Using NULL as a schema asset filter is deprecated.' + . ' Use a callable that always returns true instead.', + ); + } + + $this->schemaAssetsFilter = $callable; + } + + /** + * Returns the callable to use to filter schema assets. + */ + public function getSchemaAssetsFilter(): ?callable + { + return $this->schemaAssetsFilter; + } + + /** + * Sets the default auto-commit mode for connections. + * + * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either + * the method commit or the method rollback. By default, new connections are in auto-commit mode. + * + * @see getAutoCommit + * + * @param bool $autoCommit True to enable auto-commit mode; false to disable it + */ + public function setAutoCommit(bool $autoCommit): void + { + $this->autoCommit = $autoCommit; + } + + /** + * Returns the default auto-commit mode for connections. + * + * @see setAutoCommit + * + * @return bool True if auto-commit mode is enabled by default for connections, false otherwise. + */ + public function getAutoCommit(): bool + { + return $this->autoCommit; + } + + /** + * @param Middleware[] $middlewares + * + * @return $this + */ + public function setMiddlewares(array $middlewares): self + { + $this->middlewares = $middlewares; + + return $this; + } + + /** @return Middleware[] */ + public function getMiddlewares(): array + { + return $this->middlewares; + } + + public function getSchemaManagerFactory(): ?SchemaManagerFactory + { + return $this->schemaManagerFactory; + } + + /** @return $this */ + public function setSchemaManagerFactory(SchemaManagerFactory $schemaManagerFactory): self + { + $this->schemaManagerFactory = $schemaManagerFactory; + + return $this; + } + + public function getDisableTypeComments(): bool + { + return $this->disableTypeComments; + } + + /** @return $this */ + public function setDisableTypeComments(bool $disableTypeComments): self + { + $this->disableTypeComments = $disableTypeComments; + + return $this; + } +} diff --git a/vendor/doctrine/dbal/src/Connection.php b/vendor/doctrine/dbal/src/Connection.php new file mode 100644 index 0000000..b975670 --- /dev/null +++ b/vendor/doctrine/dbal/src/Connection.php @@ -0,0 +1,2001 @@ + + * @psalm-var Params + */ + private array $params; + + /** + * The database platform object used by the connection or NULL before it's initialized. + */ + private ?AbstractPlatform $platform = null; + + private ?ExceptionConverter $exceptionConverter = null; + private ?Parser $parser = null; + + /** + * The schema manager. + * + * @deprecated Use {@see createSchemaManager()} instead. + * + * @var AbstractSchemaManager|null + */ + protected $_schemaManager; + + /** + * The used DBAL driver. + * + * @var Driver + */ + protected $_driver; + + /** + * Flag that indicates whether the current transaction is marked for rollback only. + */ + private bool $isRollbackOnly = false; + + private SchemaManagerFactory $schemaManagerFactory; + + /** + * Initializes a new instance of the Connection class. + * + * @internal The connection can be only instantiated by the driver manager. + * + * @param array $params The connection parameters. + * @param Driver $driver The driver to use. + * @param Configuration|null $config The configuration, optional. + * @param EventManager|null $eventManager The event manager, optional. + * @psalm-param Params $params + * + * @throws Exception + */ + public function __construct( + #[SensitiveParameter] + array $params, + Driver $driver, + ?Configuration $config = null, + ?EventManager $eventManager = null + ) { + $this->_driver = $driver; + $this->params = $params; + + // Create default config and event manager if none given + $config ??= new Configuration(); + $eventManager ??= new EventManager(); + + $this->_config = $config; + $this->_eventManager = $eventManager; + + if (isset($params['platform'])) { + if (! $params['platform'] instanceof Platforms\AbstractPlatform) { + throw Exception::invalidPlatformType($params['platform']); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5699', + 'The "platform" connection parameter is deprecated.' + . ' Use a driver middleware that would instantiate the platform instead.', + ); + + $this->platform = $params['platform']; + $this->platform->setEventManager($this->_eventManager); + $this->platform->setDisableTypeComments($config->getDisableTypeComments()); + } + + $this->_expr = $this->createExpressionBuilder(); + + $this->autoCommit = $config->getAutoCommit(); + + $schemaManagerFactory = $config->getSchemaManagerFactory(); + if ($schemaManagerFactory === null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5812', + 'Not configuring a schema manager factory is deprecated.' + . ' Use %s which is going to be the default in DBAL 4.', + DefaultSchemaManagerFactory::class, + ); + + $schemaManagerFactory = new LegacySchemaManagerFactory(); + } + + $this->schemaManagerFactory = $schemaManagerFactory; + } + + /** + * Gets the parameters used during instantiation. + * + * @internal + * + * @return array + * @psalm-return Params + */ + public function getParams() + { + return $this->params; + } + + /** + * Gets the name of the currently selected database. + * + * @return string|null The name of the database or NULL if a database is not selected. + * The platforms which don't support the concept of a database (e.g. embedded databases) + * must always return a string as an indicator of an implicitly selected database. + * + * @throws Exception + */ + public function getDatabase() + { + $platform = $this->getDatabasePlatform(); + $query = $platform->getDummySelectSQL($platform->getCurrentDatabaseExpression()); + $database = $this->fetchOne($query); + + assert(is_string($database) || $database === null); + + return $database; + } + + /** + * Gets the DBAL driver instance. + * + * @return Driver + */ + public function getDriver() + { + return $this->_driver; + } + + /** + * Gets the Configuration used by the Connection. + * + * @return Configuration + */ + public function getConfiguration() + { + return $this->_config; + } + + /** + * Gets the EventManager used by the Connection. + * + * @deprecated + * + * @return EventManager + */ + public function getEventManager() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + '%s is deprecated.', + __METHOD__, + ); + + return $this->_eventManager; + } + + /** + * Gets the DatabasePlatform for the connection. + * + * @return AbstractPlatform + * + * @throws Exception + */ + public function getDatabasePlatform() + { + if ($this->platform === null) { + $this->platform = $this->detectDatabasePlatform(); + $this->platform->setEventManager($this->_eventManager); + $this->platform->setDisableTypeComments($this->_config->getDisableTypeComments()); + } + + return $this->platform; + } + + /** + * Creates an expression builder for the connection. + */ + public function createExpressionBuilder(): ExpressionBuilder + { + return new ExpressionBuilder($this); + } + + /** + * Gets the ExpressionBuilder for the connection. + * + * @deprecated Use {@see createExpressionBuilder()} instead. + * + * @return ExpressionBuilder + */ + public function getExpressionBuilder() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4515', + 'Connection::getExpressionBuilder() is deprecated,' + . ' use Connection::createExpressionBuilder() instead.', + ); + + return $this->_expr; + } + + /** + * Establishes the connection with the database. + * + * @internal This method will be made protected in DBAL 4.0. + * + * @return bool TRUE if the connection was successfully established, FALSE if + * the connection is already open. + * + * @throws Exception + * + * @psalm-assert !null $this->_conn + */ + public function connect() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4966', + 'Public access to Connection::connect() is deprecated.', + ); + + if ($this->_conn !== null) { + return false; + } + + try { + $this->_conn = $this->_driver->connect($this->params); + } catch (Driver\Exception $e) { + throw $this->convertException($e); + } + + if ($this->autoCommit === false) { + $this->beginTransaction(); + } + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated. Implement a middleware instead.', + Events::postConnect, + ); + + $eventArgs = new Event\ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Detects and sets the database platform. + * + * Evaluates custom platform class and version in order to set the correct platform. + * + * @throws Exception If an invalid platform was specified for this connection. + */ + private function detectDatabasePlatform(): AbstractPlatform + { + $version = $this->getDatabasePlatformVersion(); + + if ($version !== null) { + assert($this->_driver instanceof VersionAwarePlatformDriver); + + return $this->_driver->createDatabasePlatformForVersion($version); + } + + return $this->_driver->getDatabasePlatform(); + } + + /** + * Returns the version of the related platform if applicable. + * + * Returns null if either the driver is not capable to create version + * specific platform instances, no explicit server version was specified + * or the underlying driver connection cannot determine the platform + * version without having to query it (performance reasons). + * + * @return string|null + * + * @throws Throwable + */ + private function getDatabasePlatformVersion() + { + // Driver does not support version specific platforms. + if (! $this->_driver instanceof VersionAwarePlatformDriver) { + return null; + } + + // Explicit platform version requested (supersedes auto-detection). + if (isset($this->params['serverVersion'])) { + return $this->params['serverVersion']; + } + + if (isset($this->params['primary']) && isset($this->params['primary']['serverVersion'])) { + return $this->params['primary']['serverVersion']; + } + + // If not connected, we need to connect now to determine the platform version. + if ($this->_conn === null) { + try { + $this->connect(); + } catch (Exception $originalException) { + if (! isset($this->params['dbname'])) { + throw $originalException; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5707', + 'Relying on a fallback connection used to determine the database platform while connecting' + . ' to a non-existing database is deprecated. Either use an existing database name in' + . ' connection parameters or omit the database name if the platform' + . ' and the server configuration allow that.', + ); + + // The database to connect to might not yet exist. + // Retry detection without database name connection parameter. + $params = $this->params; + + unset($this->params['dbname']); + + try { + $this->connect(); + } catch (Exception $fallbackException) { + // Either the platform does not support database-less connections + // or something else went wrong. + throw $originalException; + } finally { + $this->params = $params; + } + + $serverVersion = $this->getServerVersion(); + + // Close "temporary" connection to allow connecting to the real database again. + $this->close(); + + return $serverVersion; + } + } + + return $this->getServerVersion(); + } + + /** + * Returns the database server version if the underlying driver supports it. + * + * @return string|null + * + * @throws Exception + */ + private function getServerVersion() + { + $connection = $this->getWrappedConnection(); + + // Automatic platform version detection. + if ($connection instanceof ServerInfoAwareConnection) { + try { + return $connection->getServerVersion(); + } catch (Driver\Exception $e) { + throw $this->convertException($e); + } + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4750', + 'Not implementing the ServerInfoAwareConnection interface in %s is deprecated', + get_class($connection), + ); + + // Unable to detect platform version. + return null; + } + + /** + * Returns the current auto-commit mode for this connection. + * + * @see setAutoCommit + * + * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise. + */ + public function isAutoCommit() + { + return $this->autoCommit === true; + } + + /** + * Sets auto-commit mode for this connection. + * + * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either + * the method commit or the method rollback. By default, new connections are in auto-commit mode. + * + * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is + * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op. + * + * @see isAutoCommit + * + * @param bool $autoCommit True to enable auto-commit mode; false to disable it. + * + * @return void + */ + public function setAutoCommit($autoCommit) + { + $autoCommit = (bool) $autoCommit; + + // Mode not changed, no-op. + if ($autoCommit === $this->autoCommit) { + return; + } + + $this->autoCommit = $autoCommit; + + // Commit all currently active transactions if any when switching auto-commit mode. + if ($this->_conn === null || $this->transactionNestingLevel === 0) { + return; + } + + $this->commitAll(); + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as an associative array. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return array|false False is returned if no rows are found. + * + * @throws Exception + */ + public function fetchAssociative(string $query, array $params = [], array $types = []) + { + return $this->executeQuery($query, $params, $types)->fetchAssociative(); + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as a numerically indexed array. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return list|false False is returned if no rows are found. + * + * @throws Exception + */ + public function fetchNumeric(string $query, array $params = [], array $types = []) + { + return $this->executeQuery($query, $params, $types)->fetchNumeric(); + } + + /** + * Prepares and executes an SQL query and returns the value of a single column + * of the first row of the result. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return mixed|false False is returned if no rows are found. + * + * @throws Exception + */ + public function fetchOne(string $query, array $params = [], array $types = []) + { + return $this->executeQuery($query, $params, $types)->fetchOne(); + } + + /** + * Whether an actual connection to the database is established. + * + * @return bool + */ + public function isConnected() + { + return $this->_conn !== null; + } + + /** + * Checks whether a transaction is currently active. + * + * @return bool TRUE if a transaction is currently active, FALSE otherwise. + */ + public function isTransactionActive() + { + return $this->transactionNestingLevel > 0; + } + + /** + * Adds condition based on the criteria to the query components + * + * @param array $criteria Map of key columns to their values + * @param string[] $columns Column names + * @param mixed[] $values Column values + * @param string[] $conditions Key conditions + * + * @throws Exception + */ + private function addCriteriaCondition( + array $criteria, + array &$columns, + array &$values, + array &$conditions + ): void { + $platform = $this->getDatabasePlatform(); + + foreach ($criteria as $columnName => $value) { + if ($value === null) { + $conditions[] = $platform->getIsNullExpression($columnName); + continue; + } + + $columns[] = $columnName; + $values[] = $value; + $conditions[] = $columnName . ' = ?'; + } + } + + /** + * Executes an SQL DELETE statement on a table. + * + * Table expression and columns are not escaped and are not safe for user-input. + * + * @param string $table Table name + * @param array $criteria Deletion criteria + * @param array|array $types Parameter types + * + * @return int|string The number of affected rows. + * + * @throws Exception + */ + public function delete($table, array $criteria, array $types = []) + { + if (count($criteria) === 0) { + throw InvalidArgumentException::fromEmptyCriteria(); + } + + $columns = $values = $conditions = []; + + $this->addCriteriaCondition($criteria, $columns, $values, $conditions); + + return $this->executeStatement( + 'DELETE FROM ' . $table . ' WHERE ' . implode(' AND ', $conditions), + $values, + is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types, + ); + } + + /** + * Closes the connection. + * + * @return void + */ + public function close() + { + $this->_conn = null; + $this->transactionNestingLevel = 0; + } + + /** + * Sets the transaction isolation level. + * + * @param TransactionIsolationLevel::* $level The level to set. + * + * @return int|string + * + * @throws Exception + */ + public function setTransactionIsolation($level) + { + $this->transactionIsolationLevel = $level; + + return $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level)); + } + + /** + * Gets the currently active transaction isolation level. + * + * @return TransactionIsolationLevel::* The current transaction isolation level. + * + * @throws Exception + */ + public function getTransactionIsolation() + { + return $this->transactionIsolationLevel ??= $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel(); + } + + /** + * Executes an SQL UPDATE statement on a table. + * + * Table expression and columns are not escaped and are not safe for user-input. + * + * @param string $table Table name + * @param array $data Column-value pairs + * @param array $criteria Update criteria + * @param array|array $types Parameter types + * + * @return int|string The number of affected rows. + * + * @throws Exception + */ + public function update($table, array $data, array $criteria, array $types = []) + { + $columns = $values = $conditions = $set = []; + + foreach ($data as $columnName => $value) { + $columns[] = $columnName; + $values[] = $value; + $set[] = $columnName . ' = ?'; + } + + $this->addCriteriaCondition($criteria, $columns, $values, $conditions); + + if (is_string(key($types))) { + $types = $this->extractTypeValues($columns, $types); + } + + $sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set) + . ' WHERE ' . implode(' AND ', $conditions); + + return $this->executeStatement($sql, $values, $types); + } + + /** + * Inserts a table row with specified data. + * + * Table expression and columns are not escaped and are not safe for user-input. + * + * @param string $table Table name + * @param array $data Column-value pairs + * @param array|array $types Parameter types + * + * @return int|string The number of affected rows. + * + * @throws Exception + */ + public function insert($table, array $data, array $types = []) + { + if (count($data) === 0) { + return $this->executeStatement('INSERT INTO ' . $table . ' () VALUES ()'); + } + + $columns = []; + $values = []; + $set = []; + + foreach ($data as $columnName => $value) { + $columns[] = $columnName; + $values[] = $value; + $set[] = '?'; + } + + return $this->executeStatement( + 'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' . + ' VALUES (' . implode(', ', $set) . ')', + $values, + is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types, + ); + } + + /** + * Extract ordered type list from an ordered column list and type map. + * + * @param array $columnList + * @param array|array $types + * + * @return array|array + */ + private function extractTypeValues(array $columnList, array $types): array + { + $typeValues = []; + + foreach ($columnList as $columnName) { + $typeValues[] = $types[$columnName] ?? ParameterType::STRING; + } + + return $typeValues; + } + + /** + * Quotes a string so it can be safely used as a table or column name, even if + * it is a reserved name. + * + * Delimiting style depends on the underlying database platform that is being used. + * + * NOTE: Just because you CAN use quoted identifiers does not mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * @param string $str The name to be quoted. + * + * @return string The quoted name. + */ + public function quoteIdentifier($str) + { + return $this->getDatabasePlatform()->quoteIdentifier($str); + } + + /** + * The usage of this method is discouraged. Use prepared statements + * or {@see AbstractPlatform::quoteStringLiteral()} instead. + * + * @param mixed $value + * @param int|string|Type|null $type + * + * @return mixed + */ + public function quote($value, $type = ParameterType::STRING) + { + $connection = $this->getWrappedConnection(); + + [$value, $bindingType] = $this->getBindingInfo($value, $type); + + return $connection->quote($value, $bindingType); + } + + /** + * Prepares and executes an SQL query and returns the result as an array of numeric arrays. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return list> + * + * @throws Exception + */ + public function fetchAllNumeric(string $query, array $params = [], array $types = []): array + { + return $this->executeQuery($query, $params, $types)->fetchAllNumeric(); + } + + /** + * Prepares and executes an SQL query and returns the result as an array of associative arrays. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return list> + * + * @throws Exception + */ + public function fetchAllAssociative(string $query, array $params = [], array $types = []): array + { + return $this->executeQuery($query, $params, $types)->fetchAllAssociative(); + } + + /** + * Prepares and executes an SQL query and returns the result as an associative array with the keys + * mapped to the first column and the values mapped to the second column. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return array + * + * @throws Exception + */ + public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array + { + return $this->executeQuery($query, $params, $types)->fetchAllKeyValue(); + } + + /** + * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped + * to the first column and the values being an associative array representing the rest of the columns + * and their values. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return array> + * + * @throws Exception + */ + public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array + { + return $this->executeQuery($query, $params, $types)->fetchAllAssociativeIndexed(); + } + + /** + * Prepares and executes an SQL query and returns the result as an array of the first column values. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return list + * + * @throws Exception + */ + public function fetchFirstColumn(string $query, array $params = [], array $types = []): array + { + return $this->executeQuery($query, $params, $types)->fetchFirstColumn(); + } + + /** + * Prepares and executes an SQL query and returns the result as an iterator over rows represented as numeric arrays. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return Traversable> + * + * @throws Exception + */ + public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable + { + return $this->executeQuery($query, $params, $types)->iterateNumeric(); + } + + /** + * Prepares and executes an SQL query and returns the result as an iterator over rows represented + * as associative arrays. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return Traversable> + * + * @throws Exception + */ + public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable + { + return $this->executeQuery($query, $params, $types)->iterateAssociative(); + } + + /** + * Prepares and executes an SQL query and returns the result as an iterator with the keys + * mapped to the first column and the values mapped to the second column. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return Traversable + * + * @throws Exception + */ + public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable + { + return $this->executeQuery($query, $params, $types)->iterateKeyValue(); + } + + /** + * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped + * to the first column and the values being an associative array representing the rest of the columns + * and their values. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return Traversable> + * + * @throws Exception + */ + public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable + { + return $this->executeQuery($query, $params, $types)->iterateAssociativeIndexed(); + } + + /** + * Prepares and executes an SQL query and returns the result as an iterator over the first column values. + * + * @param string $query SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @return Traversable + * + * @throws Exception + */ + public function iterateColumn(string $query, array $params = [], array $types = []): Traversable + { + return $this->executeQuery($query, $params, $types)->iterateColumn(); + } + + /** + * Prepares an SQL statement. + * + * @param string $sql The SQL statement to prepare. + * + * @throws Exception + */ + public function prepare(string $sql): Statement + { + $connection = $this->getWrappedConnection(); + + try { + $statement = $connection->prepare($sql); + } catch (Driver\Exception $e) { + throw $this->convertExceptionDuringQuery($e, $sql); + } + + return new Statement($this, $statement, $sql); + } + + /** + * Executes an, optionally parameterized, SQL query. + * + * If the query is parametrized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param string $sql SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @throws Exception + */ + public function executeQuery( + string $sql, + array $params = [], + $types = [], + ?QueryCacheProfile $qcp = null + ): Result { + if ($qcp !== null) { + return $this->executeCacheQuery($sql, $params, $types, $qcp); + } + + $connection = $this->getWrappedConnection(); + + $logger = $this->_config->getSQLLogger(); + if ($logger !== null) { + $logger->startQuery($sql, $params, $types); + } + + try { + if (count($params) > 0) { + if ($this->needsArrayParameterConversion($params, $types)) { + [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types); + } + + $stmt = $connection->prepare($sql); + + $this->bindParameters($stmt, $params, $types); + + $result = $stmt->execute(); + } else { + $result = $connection->query($sql); + } + + return new Result($result, $this); + } catch (Driver\Exception $e) { + throw $this->convertExceptionDuringQuery($e, $sql, $params, $types); + } finally { + if ($logger !== null) { + $logger->stopQuery(); + } + } + } + + /** + * Executes a caching query. + * + * @param string $sql SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @throws CacheException + * @throws Exception + */ + public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): Result + { + $resultCache = $qcp->getResultCache() ?? $this->_config->getResultCache(); + + if ($resultCache === null) { + throw CacheException::noResultDriverConfigured(); + } + + $connectionParams = $this->params; + unset($connectionParams['platform'], $connectionParams['password'], $connectionParams['url']); + + [$cacheKey, $realKey] = $qcp->generateCacheKeys($sql, $params, $types, $connectionParams); + + $item = $resultCache->getItem($cacheKey); + + if ($item->isHit()) { + $value = $item->get(); + if (! is_array($value)) { + $value = []; + } + + if (isset($value[$realKey])) { + return new Result(new ArrayResult($value[$realKey]), $this); + } + } else { + $value = []; + } + + $data = $this->fetchAllAssociative($sql, $params, $types); + + $value[$realKey] = $data; + + $item->set($value); + + $lifetime = $qcp->getLifetime(); + if ($lifetime > 0) { + $item->expiresAfter($lifetime); + } + + $resultCache->save($item); + + return new Result(new ArrayResult($data), $this); + } + + /** + * Executes an SQL statement with the given parameters and returns the number of affected rows. + * + * Could be used for: + * - DML statements: INSERT, UPDATE, DELETE, etc. + * - DDL statements: CREATE, DROP, ALTER, etc. + * - DCL statements: GRANT, REVOKE, etc. + * - Session control statements: ALTER SESSION, SET, DECLARE, etc. + * - Other statements that don't yield a row set. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param string $sql SQL statement + * @param list|array $params Statement parameters + * @param array|array $types Parameter types + * + * @return int|string The number of affected rows. + * + * @throws Exception + */ + public function executeStatement($sql, array $params = [], array $types = []) + { + $connection = $this->getWrappedConnection(); + + $logger = $this->_config->getSQLLogger(); + if ($logger !== null) { + $logger->startQuery($sql, $params, $types); + } + + try { + if (count($params) > 0) { + if ($this->needsArrayParameterConversion($params, $types)) { + [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types); + } + + $stmt = $connection->prepare($sql); + + $this->bindParameters($stmt, $params, $types); + + return $stmt->execute() + ->rowCount(); + } + + return $connection->exec($sql); + } catch (Driver\Exception $e) { + throw $this->convertExceptionDuringQuery($e, $sql, $params, $types); + } finally { + if ($logger !== null) { + $logger->stopQuery(); + } + } + } + + /** + * Returns the current transaction nesting level. + * + * @return int The nesting level. A value of 0 means there's no active transaction. + */ + public function getTransactionNestingLevel() + { + return $this->transactionNestingLevel; + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, + * depending on the underlying driver. + * + * Note: This method may not return a meaningful or consistent result across different drivers, + * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY + * columns or sequences. + * + * @param string|null $name Name of the sequence object from which the ID should be returned. + * + * @return string|int|false A string representation of the last inserted ID. + * + * @throws Exception + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + } + + try { + return $this->getWrappedConnection()->lastInsertId($name); + } catch (Driver\Exception $e) { + throw $this->convertException($e); + } + } + + /** + * Executes a function in a transaction. + * + * The function gets passed this Connection instance as an (optional) parameter. + * + * If an exception occurs during execution of the function or transaction commit, + * the transaction is rolled back and the exception re-thrown. + * + * @param Closure(self):T $func The function to execute transactionally. + * + * @return T The value returned by $func + * + * @throws Throwable + * + * @template T + */ + public function transactional(Closure $func) + { + $this->beginTransaction(); + try { + $res = $func($this); + $this->commit(); + + return $res; + } catch (Throwable $e) { + $this->rollBack(); + + throw $e; + } + } + + /** + * Sets if nested transactions should use savepoints. + * + * @param bool $nestTransactionsWithSavepoints + * + * @return void + * + * @throws Exception + */ + public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) + { + if (! $nestTransactionsWithSavepoints) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5383', + <<<'DEPRECATION' + Nesting transactions without enabling savepoints is deprecated. + Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints. + DEPRECATION, + self::class, + ); + } + + if ($this->transactionNestingLevel > 0) { + throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); + } + + $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints; + } + + /** + * Gets if nested transactions should use savepoints. + * + * @return bool + */ + public function getNestTransactionsWithSavepoints() + { + return $this->nestTransactionsWithSavepoints; + } + + /** + * Returns the savepoint name to use for nested transactions. + * + * @return string + */ + protected function _getNestedTransactionSavePointName() + { + return 'DOCTRINE_' . $this->transactionNestingLevel; + } + + /** + * @return bool + * + * @throws Exception + */ + public function beginTransaction() + { + $connection = $this->getWrappedConnection(); + + ++$this->transactionNestingLevel; + + $logger = $this->_config->getSQLLogger(); + + if ($this->transactionNestingLevel === 1) { + if ($logger !== null) { + $logger->startQuery('"START TRANSACTION"'); + } + + $connection->beginTransaction(); + + if ($logger !== null) { + $logger->stopQuery(); + } + } elseif ($this->nestTransactionsWithSavepoints) { + if ($logger !== null) { + $logger->startQuery('"SAVEPOINT"'); + } + + $this->createSavepoint($this->_getNestedTransactionSavePointName()); + if ($logger !== null) { + $logger->stopQuery(); + } + } else { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5383', + <<<'DEPRECATION' + Nesting transactions without enabling savepoints is deprecated. + Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints. + DEPRECATION, + self::class, + ); + } + + $eventManager = $this->getEventManager(); + + if ($eventManager->hasListeners(Events::onTransactionBegin)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onTransactionBegin, + ); + + $eventManager->dispatchEvent(Events::onTransactionBegin, new TransactionBeginEventArgs($this)); + } + + return true; + } + + /** + * @return bool + * + * @throws Exception + */ + public function commit() + { + if ($this->transactionNestingLevel === 0) { + throw ConnectionException::noActiveTransaction(); + } + + if ($this->isRollbackOnly) { + throw ConnectionException::commitFailedRollbackOnly(); + } + + $result = true; + + $connection = $this->getWrappedConnection(); + + if ($this->transactionNestingLevel === 1) { + $result = $this->doCommit($connection); + } elseif ($this->nestTransactionsWithSavepoints) { + $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); + } + + --$this->transactionNestingLevel; + + $eventManager = $this->getEventManager(); + + if ($eventManager->hasListeners(Events::onTransactionCommit)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onTransactionCommit, + ); + + $eventManager->dispatchEvent(Events::onTransactionCommit, new TransactionCommitEventArgs($this)); + } + + if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) { + return $result; + } + + $this->beginTransaction(); + + return $result; + } + + /** + * @return bool + * + * @throws DriverException + */ + private function doCommit(DriverConnection $connection) + { + $logger = $this->_config->getSQLLogger(); + + if ($logger !== null) { + $logger->startQuery('"COMMIT"'); + } + + $result = $connection->commit(); + + if ($logger !== null) { + $logger->stopQuery(); + } + + return $result; + } + + /** + * Commits all current nesting transactions. + * + * @throws Exception + */ + private function commitAll(): void + { + while ($this->transactionNestingLevel !== 0) { + if ($this->autoCommit === false && $this->transactionNestingLevel === 1) { + // When in no auto-commit mode, the last nesting commit immediately starts a new transaction. + // Therefore we need to do the final commit here and then leave to avoid an infinite loop. + $this->commit(); + + return; + } + + $this->commit(); + } + } + + /** + * Cancels any database changes done during the current transaction. + * + * @return bool + * + * @throws Exception + */ + public function rollBack() + { + if ($this->transactionNestingLevel === 0) { + throw ConnectionException::noActiveTransaction(); + } + + $connection = $this->getWrappedConnection(); + + $logger = $this->_config->getSQLLogger(); + + if ($this->transactionNestingLevel === 1) { + if ($logger !== null) { + $logger->startQuery('"ROLLBACK"'); + } + + $this->transactionNestingLevel = 0; + $connection->rollBack(); + $this->isRollbackOnly = false; + if ($logger !== null) { + $logger->stopQuery(); + } + + if ($this->autoCommit === false) { + $this->beginTransaction(); + } + } elseif ($this->nestTransactionsWithSavepoints) { + if ($logger !== null) { + $logger->startQuery('"ROLLBACK TO SAVEPOINT"'); + } + + $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); + --$this->transactionNestingLevel; + if ($logger !== null) { + $logger->stopQuery(); + } + } else { + $this->isRollbackOnly = true; + --$this->transactionNestingLevel; + } + + $eventManager = $this->getEventManager(); + + if ($eventManager->hasListeners(Events::onTransactionRollBack)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onTransactionRollBack, + ); + + $eventManager->dispatchEvent(Events::onTransactionRollBack, new TransactionRollBackEventArgs($this)); + } + + return true; + } + + /** + * Creates a new savepoint. + * + * @param string $savepoint The name of the savepoint to create. + * + * @return void + * + * @throws Exception + */ + public function createSavepoint($savepoint) + { + $platform = $this->getDatabasePlatform(); + + if (! $platform->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->executeStatement($platform->createSavePoint($savepoint)); + } + + /** + * Releases the given savepoint. + * + * @param string $savepoint The name of the savepoint to release. + * + * @return void + * + * @throws Exception + */ + public function releaseSavepoint($savepoint) + { + $logger = $this->_config->getSQLLogger(); + + $platform = $this->getDatabasePlatform(); + + if (! $platform->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + if (! $platform->supportsReleaseSavepoints()) { + if ($logger !== null) { + $logger->stopQuery(); + } + + return; + } + + if ($logger !== null) { + $logger->startQuery('"RELEASE SAVEPOINT"'); + } + + $this->executeStatement($platform->releaseSavePoint($savepoint)); + + if ($logger === null) { + return; + } + + $logger->stopQuery(); + } + + /** + * Rolls back to the given savepoint. + * + * @param string $savepoint The name of the savepoint to rollback to. + * + * @return void + * + * @throws Exception + */ + public function rollbackSavepoint($savepoint) + { + $platform = $this->getDatabasePlatform(); + + if (! $platform->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->executeStatement($platform->rollbackSavePoint($savepoint)); + } + + /** + * Gets the wrapped driver connection. + * + * @deprecated Use {@link getNativeConnection()} to access the native connection. + * + * @return DriverConnection + * + * @throws Exception + */ + public function getWrappedConnection() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4966', + 'Connection::getWrappedConnection() is deprecated.' + . ' Use Connection::getNativeConnection() to access the native connection.', + ); + + $this->connect(); + + return $this->_conn; + } + + /** @return resource|object */ + public function getNativeConnection() + { + $this->connect(); + + if (! method_exists($this->_conn, 'getNativeConnection')) { + throw new LogicException(sprintf( + 'The driver connection %s does not support accessing the native connection.', + get_class($this->_conn), + )); + } + + return $this->_conn->getNativeConnection(); + } + + /** + * Creates a SchemaManager that can be used to inspect or change the + * database schema through the connection. + * + * @throws Exception + */ + public function createSchemaManager(): AbstractSchemaManager + { + return $this->schemaManagerFactory->createSchemaManager($this); + } + + /** + * Gets the SchemaManager that can be used to inspect or change the + * database schema through the connection. + * + * @deprecated Use {@see createSchemaManager()} instead. + * + * @return AbstractSchemaManager + * + * @throws Exception + */ + public function getSchemaManager() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4515', + 'Connection::getSchemaManager() is deprecated, use Connection::createSchemaManager() instead.', + ); + + return $this->_schemaManager ??= $this->createSchemaManager(); + } + + /** + * Marks the current transaction so that the only possible + * outcome for the transaction to be rolled back. + * + * @return void + * + * @throws ConnectionException If no transaction is active. + */ + public function setRollbackOnly() + { + if ($this->transactionNestingLevel === 0) { + throw ConnectionException::noActiveTransaction(); + } + + $this->isRollbackOnly = true; + } + + /** + * Checks whether the current transaction is marked for rollback only. + * + * @return bool + * + * @throws ConnectionException If no transaction is active. + */ + public function isRollbackOnly() + { + if ($this->transactionNestingLevel === 0) { + throw ConnectionException::noActiveTransaction(); + } + + return $this->isRollbackOnly; + } + + /** + * Converts a given value to its database representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * + * @return mixed The converted value. + * + * @throws Exception + */ + public function convertToDatabaseValue($value, $type) + { + return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform()); + } + + /** + * Converts a given value to its PHP representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * + * @return mixed The converted type. + * + * @throws Exception + */ + public function convertToPHPValue($value, $type) + { + return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform()); + } + + /** + * Binds a set of parameters, some or all of which are typed with a PDO binding type + * or DBAL mapping type, to a given statement. + * + * @param DriverStatement $stmt Prepared statement + * @param list|array $params Statement parameters + * @param array|array $types Parameter types + * + * @throws Exception + */ + private function bindParameters(DriverStatement $stmt, array $params, array $types): void + { + // Check whether parameters are positional or named. Mixing is not allowed. + if (is_int(key($params))) { + $bindIndex = 1; + + foreach ($params as $key => $value) { + if (isset($types[$key])) { + $type = $types[$key]; + [$value, $bindingType] = $this->getBindingInfo($value, $type); + } else { + if (array_key_exists($key, $types)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5550', + 'Using NULL as prepared statement parameter type is deprecated.' + . 'Omit or use ParameterType::STRING instead', + ); + } + + $bindingType = ParameterType::STRING; + } + + $stmt->bindValue($bindIndex, $value, $bindingType); + + ++$bindIndex; + } + } else { + // Named parameters + foreach ($params as $name => $value) { + if (isset($types[$name])) { + $type = $types[$name]; + [$value, $bindingType] = $this->getBindingInfo($value, $type); + } else { + if (array_key_exists($name, $types)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5550', + 'Using NULL as prepared statement parameter type is deprecated.' + . 'Omit or use ParameterType::STRING instead', + ); + } + + $bindingType = ParameterType::STRING; + } + + $stmt->bindValue($name, $value, $bindingType); + } + } + } + + /** + * Gets the binding type of a given type. + * + * @param mixed $value The value to bind. + * @param int|string|Type|null $type The type to bind (PDO or DBAL). + * + * @return array{mixed, int} [0] => the (escaped) value, [1] => the binding type. + * + * @throws Exception + */ + private function getBindingInfo($value, $type): array + { + if (is_string($type)) { + $type = Type::getType($type); + } + + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform()); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type ?? ParameterType::STRING; + } + + return [$value, $bindingType]; + } + + /** + * Creates a new instance of a SQL query builder. + * + * @return QueryBuilder + */ + public function createQueryBuilder() + { + return new Query\QueryBuilder($this); + } + + /** + * @internal + * + * @param list|array $params + * @param array|array $types + */ + final public function convertExceptionDuringQuery( + Driver\Exception $e, + string $sql, + array $params = [], + array $types = [] + ): DriverException { + return $this->handleDriverException($e, new Query($sql, $params, $types)); + } + + /** @internal */ + final public function convertException(Driver\Exception $e): DriverException + { + return $this->handleDriverException($e, null); + } + + /** + * @param array|array $params + * @param array|array $types + * + * @return array{string, list, array} + */ + private function expandArrayParameters(string $sql, array $params, array $types): array + { + $this->parser ??= $this->getDatabasePlatform()->createSQLParser(); + $visitor = new ExpandArrayParameters($params, $types); + + $this->parser->parse($sql, $visitor); + + return [ + $visitor->getSQL(), + $visitor->getParameters(), + $visitor->getTypes(), + ]; + } + + /** + * @param array|array $params + * @param array|array $types + */ + private function needsArrayParameterConversion(array $params, array $types): bool + { + if (is_string(key($params))) { + return true; + } + + foreach ($types as $type) { + if ( + $type === ArrayParameterType::INTEGER + || $type === ArrayParameterType::STRING + || $type === ArrayParameterType::ASCII + || $type === ArrayParameterType::BINARY + ) { + return true; + } + } + + return false; + } + + private function handleDriverException( + Driver\Exception $driverException, + ?Query $query + ): DriverException { + $this->exceptionConverter ??= $this->_driver->getExceptionConverter(); + $exception = $this->exceptionConverter->convert($driverException, $query); + + if ($exception instanceof ConnectionLost) { + $this->close(); + } + + return $exception; + } + + /** + * BC layer for a wide-spread use-case of old DBAL APIs + * + * @deprecated Use {@see executeStatement()} instead + * + * @param array $params The query parameters + * @param array $types The parameter types + */ + public function executeUpdate(string $sql, array $params = [], array $types = []): int + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4163', + '%s is deprecated, please use executeStatement() instead.', + __METHOD__, + ); + + return $this->executeStatement($sql, $params, $types); + } + + /** + * BC layer for a wide-spread use-case of old DBAL APIs + * + * @deprecated Use {@see executeQuery()} instead + */ + public function query(string $sql): Result + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4163', + '%s is deprecated, please use executeQuery() instead.', + __METHOD__, + ); + + return $this->executeQuery($sql); + } + + /** + * BC layer for a wide-spread use-case of old DBAL APIs + * + * @deprecated please use {@see executeStatement()} instead + */ + public function exec(string $sql): int + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4163', + '%s is deprecated, please use executeStatement() instead.', + __METHOD__, + ); + + return $this->executeStatement($sql); + } +} diff --git a/vendor/doctrine/dbal/src/ConnectionException.php b/vendor/doctrine/dbal/src/ConnectionException.php new file mode 100644 index 0000000..f1e1898 --- /dev/null +++ b/vendor/doctrine/dbal/src/ConnectionException.php @@ -0,0 +1,31 @@ +executeQuery("DELETE FROM table"); + * + * Be aware that Connection#executeQuery is a method specifically for READ + * operations only. + * + * Use Connection#executeStatement for any SQL statement that changes/updates + * state in the database (UPDATE, INSERT, DELETE or DDL statements). + * + * This connection is limited to replica operations using the + * Connection#executeQuery operation only, because it wouldn't be compatible + * with the ORM or SchemaManager code otherwise. Both use all the other + * operations in a context where writes could happen to a replica, which makes + * this restricted approach necessary. + * + * You can manually connect to the primary at any time by calling: + * + * $conn->ensureConnectedToPrimary(); + * + * Instantiation through the DriverManager looks like: + * + * @psalm-import-type Params from DriverManager + * @example + * + * $conn = DriverManager::getConnection(array( + * 'wrapperClass' => 'Doctrine\DBAL\Connections\PrimaryReadReplicaConnection', + * 'driver' => 'pdo_mysql', + * 'primary' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), + * 'replica' => array( + * array('user' => 'replica1', 'password' => '', 'host' => '', 'dbname' => ''), + * array('user' => 'replica2', 'password' => '', 'host' => '', 'dbname' => ''), + * ) + * )); + * + * You can also pass 'driverOptions' and any other documented option to each of this drivers + * to pass additional information. + */ +class PrimaryReadReplicaConnection extends Connection +{ + /** + * Primary and Replica connection (one of the randomly picked replicas). + * + * @var DriverConnection[]|null[] + */ + protected $connections = ['primary' => null, 'replica' => null]; + + /** + * You can keep the replica connection and then switch back to it + * during the request if you know what you are doing. + * + * @var bool + */ + protected $keepReplica = false; + + /** + * Creates Primary Replica Connection. + * + * @internal The connection can be only instantiated by the driver manager. + * + * @param array $params + * @psalm-param Params $params + * + * @throws Exception + * @throws InvalidArgumentException + */ + public function __construct( + array $params, + Driver $driver, + ?Configuration $config = null, + ?EventManager $eventManager = null + ) { + if (! isset($params['replica'], $params['primary'])) { + throw new InvalidArgumentException('primary or replica configuration missing'); + } + + if (count($params['replica']) === 0) { + throw new InvalidArgumentException('You have to configure at least one replica.'); + } + + if (isset($params['driver'])) { + $params['primary']['driver'] = $params['driver']; + + foreach ($params['replica'] as $replicaKey => $replica) { + $params['replica'][$replicaKey]['driver'] = $params['driver']; + } + } + + $this->keepReplica = (bool) ($params['keepReplica'] ?? false); + + parent::__construct($params, $driver, $config, $eventManager); + } + + /** + * Checks if the connection is currently towards the primary or not. + */ + public function isConnectedToPrimary(): bool + { + return $this->_conn !== null && $this->_conn === $this->connections['primary']; + } + + /** + * @param string|null $connectionName + * + * @return bool + */ + public function connect($connectionName = null) + { + if ($connectionName !== null) { + throw new InvalidArgumentException( + 'Passing a connection name as first argument is not supported anymore.' + . ' Use ensureConnectedToPrimary()/ensureConnectedToReplica() instead.', + ); + } + + return $this->performConnect(); + } + + protected function performConnect(?string $connectionName = null): bool + { + $requestedConnectionChange = ($connectionName !== null); + $connectionName = $connectionName ?? 'replica'; + + if ($connectionName !== 'replica' && $connectionName !== 'primary') { + throw new InvalidArgumentException('Invalid option to connect(), only primary or replica allowed.'); + } + + // If we have a connection open, and this is not an explicit connection + // change request, then abort right here, because we are already done. + // This prevents writes to the replica in case of "keepReplica" option enabled. + if ($this->_conn !== null && ! $requestedConnectionChange) { + return false; + } + + $forcePrimaryAsReplica = false; + + if ($this->getTransactionNestingLevel() > 0) { + $connectionName = 'primary'; + $forcePrimaryAsReplica = true; + } + + if (isset($this->connections[$connectionName])) { + $this->_conn = $this->connections[$connectionName]; + + if ($forcePrimaryAsReplica && ! $this->keepReplica) { + $this->connections['replica'] = $this->_conn; + } + + return false; + } + + if ($connectionName === 'primary') { + $this->connections['primary'] = $this->_conn = $this->connectTo($connectionName); + + // Set replica connection to primary to avoid invalid reads + if (! $this->keepReplica) { + $this->connections['replica'] = $this->connections['primary']; + } + } else { + $this->connections['replica'] = $this->_conn = $this->connectTo($connectionName); + } + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated. Implement a middleware instead.', + Events::postConnect, + ); + + $eventArgs = new ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Connects to the primary node of the database cluster. + * + * All following statements after this will be executed against the primary node. + */ + public function ensureConnectedToPrimary(): bool + { + return $this->performConnect('primary'); + } + + /** + * Connects to a replica node of the database cluster. + * + * All following statements after this will be executed against the replica node, + * unless the keepReplica option is set to false and a primary connection + * was already opened. + */ + public function ensureConnectedToReplica(): bool + { + return $this->performConnect('replica'); + } + + /** + * Connects to a specific connection. + * + * @param string $connectionName + * + * @return DriverConnection + * + * @throws Exception + */ + protected function connectTo($connectionName) + { + $params = $this->getParams(); + + $connectionParams = $this->chooseConnectionConfiguration($connectionName, $params); + + try { + return $this->_driver->connect($connectionParams); + } catch (DriverException $e) { + throw $this->convertException($e); + } + } + + /** + * @param string $connectionName + * @param mixed[] $params + * + * @return mixed + */ + protected function chooseConnectionConfiguration( + $connectionName, + #[SensitiveParameter] + $params + ) { + if ($connectionName === 'primary') { + return $params['primary']; + } + + $config = $params['replica'][array_rand($params['replica'])]; + + if (! isset($config['charset']) && isset($params['primary']['charset'])) { + $config['charset'] = $params['primary']['charset']; + } + + return $config; + } + + /** + * {@inheritDoc} + */ + public function executeStatement($sql, array $params = [], array $types = []) + { + $this->ensureConnectedToPrimary(); + + return parent::executeStatement($sql, $params, $types); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + $this->ensureConnectedToPrimary(); + + return parent::beginTransaction(); + } + + /** + * {@inheritDoc} + */ + public function commit() + { + $this->ensureConnectedToPrimary(); + + return parent::commit(); + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + $this->ensureConnectedToPrimary(); + + return parent::rollBack(); + } + + /** + * {@inheritDoc} + */ + public function close() + { + unset($this->connections['primary'], $this->connections['replica']); + + parent::close(); + + $this->_conn = null; + $this->connections = ['primary' => null, 'replica' => null]; + } + + /** + * {@inheritDoc} + */ + public function createSavepoint($savepoint) + { + $this->ensureConnectedToPrimary(); + + parent::createSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function releaseSavepoint($savepoint) + { + $this->ensureConnectedToPrimary(); + + parent::releaseSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function rollbackSavepoint($savepoint) + { + $this->ensureConnectedToPrimary(); + + parent::rollbackSavepoint($savepoint); + } + + public function prepare(string $sql): Statement + { + $this->ensureConnectedToPrimary(); + + return parent::prepare($sql); + } +} diff --git a/vendor/doctrine/dbal/src/Driver.php b/vendor/doctrine/dbal/src/Driver.php new file mode 100644 index 0000000..46e422b --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver.php @@ -0,0 +1,57 @@ + $params All connection parameters. + * @psalm-param Params $params All connection parameters. + * + * @return DriverConnection The database connection. + * + * @throws Exception + */ + public function connect( + #[SensitiveParameter] + array $params + ); + + /** + * Gets the DatabasePlatform instance that provides all the metadata about + * the platform this driver connects to. + * + * @return AbstractPlatform The database platform. + */ + public function getDatabasePlatform(); + + /** + * Gets the SchemaManager that can be used to inspect and change the underlying + * database schema of the platform this driver connects to. + * + * @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead. + * + * @return AbstractSchemaManager + */ + public function getSchemaManager(Connection $conn, AbstractPlatform $platform); + + /** + * Gets the ExceptionConverter that can be used to convert driver-level exceptions into DBAL exceptions. + */ + public function getExceptionConverter(): ExceptionConverter; +} diff --git a/vendor/doctrine/dbal/src/Driver/API/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/ExceptionConverter.php new file mode 100644 index 0000000..a7bf271 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/API/ExceptionConverter.php @@ -0,0 +1,25 @@ +getCode()) { + case -104: + return new SyntaxErrorException($exception, $query); + + case -203: + return new NonUniqueFieldNameException($exception, $query); + + case -204: + return new TableNotFoundException($exception, $query); + + case -206: + return new InvalidFieldNameException($exception, $query); + + case -407: + return new NotNullConstraintViolationException($exception, $query); + + case -530: + case -531: + case -532: + case -20356: + return new ForeignKeyConstraintViolationException($exception, $query); + + case -601: + return new TableExistsException($exception, $query); + + case -803: + return new UniqueConstraintViolationException($exception, $query); + + case -1336: + case -30082: + return new ConnectionException($exception, $query); + } + + return new DriverException($exception, $query); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php new file mode 100644 index 0000000..fdfc75a --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php @@ -0,0 +1,120 @@ +getCode()) { + case 1008: + return new DatabaseDoesNotExist($exception, $query); + + case 1213: + return new DeadlockException($exception, $query); + + case 1205: + return new LockWaitTimeoutException($exception, $query); + + case 1050: + return new TableExistsException($exception, $query); + + case 1051: + case 1146: + return new TableNotFoundException($exception, $query); + + case 1216: + case 1217: + case 1451: + case 1452: + case 1701: + return new ForeignKeyConstraintViolationException($exception, $query); + + case 1062: + case 1557: + case 1569: + case 1586: + return new UniqueConstraintViolationException($exception, $query); + + case 1054: + case 1166: + case 1611: + return new InvalidFieldNameException($exception, $query); + + case 1052: + case 1060: + case 1110: + return new NonUniqueFieldNameException($exception, $query); + + case 1064: + case 1149: + case 1287: + case 1341: + case 1342: + case 1343: + case 1344: + case 1382: + case 1479: + case 1541: + case 1554: + case 1626: + return new SyntaxErrorException($exception, $query); + + case 1044: + case 1045: + case 1046: + case 1049: + case 1095: + case 1142: + case 1143: + case 1227: + case 1370: + case 1429: + case 2002: + case 2005: + case 2054: + return new ConnectionException($exception, $query); + + case 2006: + case 4031: + return new ConnectionLost($exception, $query); + + case 1048: + case 1121: + case 1138: + case 1171: + case 1252: + case 1263: + case 1364: + case 1566: + return new NotNullConstraintViolationException($exception, $query); + } + + return new DriverException($exception, $query); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/API/OCI/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/OCI/ExceptionConverter.php new file mode 100644 index 0000000..4703a57 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/API/OCI/ExceptionConverter.php @@ -0,0 +1,74 @@ +getCode()) { + case 1: + case 2299: + case 38911: + return new UniqueConstraintViolationException($exception, $query); + + case 904: + return new InvalidFieldNameException($exception, $query); + + case 918: + case 960: + return new NonUniqueFieldNameException($exception, $query); + + case 923: + return new SyntaxErrorException($exception, $query); + + case 942: + return new TableNotFoundException($exception, $query); + + case 955: + return new TableExistsException($exception, $query); + + case 1017: + case 12545: + return new ConnectionException($exception, $query); + + case 1400: + return new NotNullConstraintViolationException($exception, $query); + + case 1918: + return new DatabaseDoesNotExist($exception, $query); + + case 2289: + case 2443: + case 4080: + return new DatabaseObjectNotFoundException($exception, $query); + + case 2266: + case 2291: + case 2292: + return new ForeignKeyConstraintViolationException($exception, $query); + } + + return new DriverException($exception, $query); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php new file mode 100644 index 0000000..2baca1e --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php @@ -0,0 +1,89 @@ +getSQLState()) { + case '40001': + case '40P01': + return new DeadlockException($exception, $query); + + case '0A000': + // Foreign key constraint violations during a TRUNCATE operation + // are considered "feature not supported" in PostgreSQL. + if (strpos($exception->getMessage(), 'truncate') !== false) { + return new ForeignKeyConstraintViolationException($exception, $query); + } + + break; + + case '23502': + return new NotNullConstraintViolationException($exception, $query); + + case '23503': + return new ForeignKeyConstraintViolationException($exception, $query); + + case '23505': + return new UniqueConstraintViolationException($exception, $query); + + case '3D000': + return new DatabaseDoesNotExist($exception, $query); + + case '3F000': + return new SchemaDoesNotExist($exception, $query); + + case '42601': + return new SyntaxErrorException($exception, $query); + + case '42702': + return new NonUniqueFieldNameException($exception, $query); + + case '42703': + return new InvalidFieldNameException($exception, $query); + + case '42P01': + return new TableNotFoundException($exception, $query); + + case '42P07': + return new TableExistsException($exception, $query); + + case '08006': + return new ConnectionException($exception, $query); + } + + // Prior to fixing https://bugs.php.net/bug.php?id=64705 (PHP 7.4.10), + // in some cases (mainly connection errors) the PDO exception wouldn't provide a SQLSTATE via its code. + // We have to match against the SQLSTATE in the error message in these cases. + if ($exception->getCode() === 7 && strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) { + return new ConnectionException($exception, $query); + } + + return new DriverException($exception, $query); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/API/SQLSrv/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/SQLSrv/ExceptionConverter.php new file mode 100644 index 0000000..d0e8e9f --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/API/SQLSrv/ExceptionConverter.php @@ -0,0 +1,69 @@ +getCode()) { + case 102: + return new SyntaxErrorException($exception, $query); + + case 207: + return new InvalidFieldNameException($exception, $query); + + case 208: + return new TableNotFoundException($exception, $query); + + case 209: + return new NonUniqueFieldNameException($exception, $query); + + case 515: + return new NotNullConstraintViolationException($exception, $query); + + case 547: + case 4712: + return new ForeignKeyConstraintViolationException($exception, $query); + + case 2601: + case 2627: + return new UniqueConstraintViolationException($exception, $query); + + case 2714: + return new TableExistsException($exception, $query); + + case 3701: + case 15151: + return new DatabaseObjectNotFoundException($exception, $query); + + case 11001: + case 18456: + return new ConnectionException($exception, $query); + } + + return new DriverException($exception, $query); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/API/SQLite/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/SQLite/ExceptionConverter.php new file mode 100644 index 0000000..9e67155 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/API/SQLite/ExceptionConverter.php @@ -0,0 +1,85 @@ +getMessage(), 'database is locked') !== false) { + return new LockWaitTimeoutException($exception, $query); + } + + if ( + strpos($exception->getMessage(), 'must be unique') !== false || + strpos($exception->getMessage(), 'is not unique') !== false || + strpos($exception->getMessage(), 'are not unique') !== false || + strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false + ) { + return new UniqueConstraintViolationException($exception, $query); + } + + if ( + strpos($exception->getMessage(), 'may not be NULL') !== false || + strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false + ) { + return new NotNullConstraintViolationException($exception, $query); + } + + if (strpos($exception->getMessage(), 'no such table:') !== false) { + return new TableNotFoundException($exception, $query); + } + + if (strpos($exception->getMessage(), 'already exists') !== false) { + return new TableExistsException($exception, $query); + } + + if (strpos($exception->getMessage(), 'has no column named') !== false) { + return new InvalidFieldNameException($exception, $query); + } + + if (strpos($exception->getMessage(), 'ambiguous column name') !== false) { + return new NonUniqueFieldNameException($exception, $query); + } + + if (strpos($exception->getMessage(), 'syntax error') !== false) { + return new SyntaxErrorException($exception, $query); + } + + if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) { + return new ReadOnlyException($exception, $query); + } + + if (strpos($exception->getMessage(), 'unable to open database file') !== false) { + return new ConnectionException($exception, $query); + } + + if (strpos($exception->getMessage(), 'FOREIGN KEY constraint failed') !== false) { + return new ForeignKeyConstraintViolationException($exception, $query); + } + + return new DriverException($exception, $query); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/API/SQLite/UserDefinedFunctions.php b/vendor/doctrine/dbal/src/Driver/API/SQLite/UserDefinedFunctions.php new file mode 100644 index 0000000..3779c8b --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/API/SQLite/UserDefinedFunctions.php @@ -0,0 +1,80 @@ + ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1], + 'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2], + 'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1], + ]; + + /** + * @param callable(string, callable, int): bool $callback + * @param array $additionalFunctions + */ + public static function register(callable $callback, array $additionalFunctions = []): void + { + $userDefinedFunctions = array_merge(self::DEFAULT_FUNCTIONS, $additionalFunctions); + + foreach ($userDefinedFunctions as $function => $data) { + $callback($function, $data['callback'], $data['numArgs']); + } + } + + /** + * User-defined function that implements MOD(). + * + * @param int $a + * @param int $b + */ + public static function mod($a, $b): int + { + return $a % $b; + } + + /** + * User-defined function that implements LOCATE(). + * + * @param string $str + * @param string $substr + * @param int $offset + */ + public static function locate($str, $substr, $offset = 0): int + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5749', + 'Relying on DBAL\'s emulated LOCATE() function is deprecated. ' + . 'Use INSTR() or %s::getLocateExpression() instead.', + AbstractPlatform::class, + ); + + // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions. + // So we have to make them compatible if an offset is given. + if ($offset > 0) { + $offset -= 1; + } + + $pos = strpos($str, $substr, $offset); + + if ($pos !== false) { + return $pos + 1; + } + + return 0; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/AbstractDB2Driver.php b/vendor/doctrine/dbal/src/Driver/AbstractDB2Driver.php new file mode 100644 index 0000000..81d8432 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/AbstractDB2Driver.php @@ -0,0 +1,100 @@ +getVersionNumber($version), '11.1', '>=')) { + return new DB2111Platform(); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5156', + 'IBM DB2 < 11.1 support is deprecated and will be removed in DBAL 4.' + . ' Consider upgrading to IBM DB2 11.1 or later.', + ); + + return $this->getDatabasePlatform(); + } + + /** + * Detects IBM DB2 server version + * + * @param string $versionString Version string as returned by IBM DB2 server, i.e. 'DB2/LINUXX8664 11.5.8.0' + * + * @throws DBALException + */ + private function getVersionNumber(string $versionString): string + { + if ( + preg_match( + '/^(?:[^\s]+\s)?(?P\d+)\.(?P\d+)\.(?P\d+)/i', + $versionString, + $versionParts, + ) !== 1 + ) { + throw DBALException::invalidPlatformVersionSpecified( + $versionString, + '^(?:[^\s]+\s)?..', + ); + } + + return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/AbstractException.php b/vendor/doctrine/dbal/src/Driver/AbstractException.php new file mode 100644 index 0000000..389f82e --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/AbstractException.php @@ -0,0 +1,44 @@ +sqlState = $sqlState; + } + + /** + * {@inheritDoc} + */ + public function getSQLState() + { + return $this->sqlState; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/AbstractMySQLDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractMySQLDriver.php new file mode 100644 index 0000000..f8c3b39 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/AbstractMySQLDriver.php @@ -0,0 +1,231 @@ +getMariaDbMysqlVersionNumber($version); + if (version_compare($mariaDbVersion, '10.10.0', '>=')) { + return new MariaDb1010Platform(); + } + + if (version_compare($mariaDbVersion, '10.6.0', '>=')) { + return new MariaDb1060Platform(); + } + + if (version_compare($mariaDbVersion, '10.5.2', '>=')) { + return new MariaDb1052Platform(); + } + + if (version_compare($mariaDbVersion, '10.4.3', '>=')) { + return new MariaDb1043Platform(); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6110', + 'Support for MariaDB < 10.4 is deprecated and will be removed in DBAL 4.' + . ' Consider upgrading to a more recent version of MariaDB.', + ); + + if (version_compare($mariaDbVersion, '10.2.7', '>=')) { + return new MariaDb1027Platform(); + } + } else { + $oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version); + + if (version_compare($oracleMysqlVersion, '8.4.0', '>=')) { + if (! version_compare($version, '8.4.0', '>=')) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/dbal/pull/5779', + 'Version detection logic for MySQL will change in DBAL 4. ' + . 'Please specify the version as the server reports it, e.g. "8.4.0" instead of "8.4".', + ); + } + + return new MySQL84Platform(); + } + + if (version_compare($oracleMysqlVersion, '8', '>=')) { + if (! version_compare($version, '8.0.0', '>=')) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/dbal/pull/5779', + 'Version detection logic for MySQL will change in DBAL 4. ' + . 'Please specify the version as the server reports it, e.g. "8.0.31" instead of "8".', + ); + } + + return new MySQL80Platform(); + } + + if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) { + if (! version_compare($version, '5.7.9', '>=')) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/dbal/pull/5779', + 'Version detection logic for MySQL will change in DBAL 4. ' + . 'Please specify the version as the server reports it, e.g. "5.7.40" instead of "5.7".', + ); + } + + return new MySQL57Platform(); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5072', + 'MySQL 5.6 support is deprecated and will be removed in DBAL 4.' + . ' Consider upgrading to MySQL 5.7 or later.', + ); + } + + return $this->getDatabasePlatform(); + } + + /** + * Get a normalized 'version number' from the server string + * returned by Oracle MySQL servers. + * + * @param string $versionString Version string returned by the driver, i.e. '5.7.10' + * + * @throws Exception + */ + private function getOracleMysqlVersionNumber(string $versionString): string + { + if ( + preg_match( + '/^(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+))?)?/', + $versionString, + $versionParts, + ) !== 1 + ) { + throw Exception::invalidPlatformVersionSpecified( + $versionString, + '..', + ); + } + + $majorVersion = $versionParts['major']; + $minorVersion = $versionParts['minor'] ?? 0; + $patchVersion = $versionParts['patch'] ?? null; + + if ($majorVersion === '5' && $minorVersion === '7') { + $patchVersion ??= '9'; + } else { + $patchVersion ??= '0'; + } + + return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; + } + + /** + * Detect MariaDB server version, including hack for some mariadb distributions + * that starts with the prefix '5.5.5-' + * + * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial' + * + * @throws Exception + */ + private function getMariaDbMysqlVersionNumber(string $versionString): string + { + if (stripos($versionString, 'MariaDB') === 0) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/dbal/pull/5779', + 'Version detection logic for MySQL will change in DBAL 4. ' + . 'Please specify the version as the server reports it, ' + . 'e.g. "10.9.3-MariaDB" instead of "mariadb-10.9".', + ); + } + + if ( + preg_match( + '/^(?:5\.5\.5-)?(mariadb-)?(?P\d+)\.(?P\d+)\.(?P\d+)/i', + $versionString, + $versionParts, + ) !== 1 + ) { + throw Exception::invalidPlatformVersionSpecified( + $versionString, + '^(?:5\.5\.5-)?(mariadb-)?..', + ); + } + + return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; + } + + /** + * {@inheritDoc} + * + * @return AbstractMySQLPlatform + */ + public function getDatabasePlatform() + { + return new MySQLPlatform(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@link AbstractMySQLPlatform::createSchemaManager()} instead. + * + * @return MySQLSchemaManager + */ + public function getSchemaManager(Connection $conn, AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5458', + 'AbstractMySQLDriver::getSchemaManager() is deprecated.' + . ' Use MySQLPlatform::createSchemaManager() instead.', + ); + + assert($platform instanceof AbstractMySQLPlatform); + + return new MySQLSchemaManager($conn, $platform); + } + + public function getExceptionConverter(): ExceptionConverter + { + return new MySQL\ExceptionConverter(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver.php new file mode 100644 index 0000000..b0f9245 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver.php @@ -0,0 +1,65 @@ + $params The connection parameters to return the Easy Connect String for. + * + * @return string + */ + protected function getEasyConnectString(array $params) + { + return (string) EasyConnectString::fromConnectionParameters($params); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php new file mode 100644 index 0000000..91bc6a7 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php @@ -0,0 +1,116 @@ +string = $string; + } + + public function __toString(): string + { + return $this->string; + } + + /** + * Creates the object from an array representation + * + * @param mixed[] $params + */ + public static function fromArray(array $params): self + { + return new self(self::renderParams($params)); + } + + /** + * Creates the object from the given DBAL connection parameters. + * + * @param mixed[] $params + */ + public static function fromConnectionParameters(array $params): self + { + if (isset($params['connectstring'])) { + return new self($params['connectstring']); + } + + if (! isset($params['host'])) { + return new self($params['dbname'] ?? ''); + } + + $connectData = []; + + if (isset($params['servicename']) || isset($params['dbname'])) { + $serviceKey = 'SID'; + + if (isset($params['service'])) { + $serviceKey = 'SERVICE_NAME'; + } + + $serviceName = $params['servicename'] ?? $params['dbname']; + + $connectData[$serviceKey] = $serviceName; + } + + if (isset($params['instancename'])) { + $connectData['INSTANCE_NAME'] = $params['instancename']; + } + + if (! empty($params['pooled'])) { + $connectData['SERVER'] = 'POOLED'; + } + + return self::fromArray([ + 'DESCRIPTION' => [ + 'ADDRESS' => [ + 'PROTOCOL' => 'TCP', + 'HOST' => $params['host'], + 'PORT' => $params['port'] ?? 1521, + ], + 'CONNECT_DATA' => $connectData, + ], + ]); + } + + /** @param mixed[] $params */ + private static function renderParams(array $params): string + { + $chunks = []; + + foreach ($params as $key => $value) { + $string = self::renderValue($value); + + if ($string === '') { + continue; + } + + $chunks[] = sprintf('(%s=%s)', $key, $string); + } + + return implode('', $chunks); + } + + /** @param mixed $value */ + private static function renderValue($value): string + { + if (is_array($value)) { + return self::renderParams($value); + } + + return (string) $value; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/AbstractPostgreSQLDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractPostgreSQLDriver.php new file mode 100644 index 0000000..eba309d --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/AbstractPostgreSQLDriver.php @@ -0,0 +1,93 @@ +\d+)(?:\.(?P\d+)(?:\.(?P\d+))?)?/', $version, $versionParts) !== 1) { + throw Exception::invalidPlatformVersionSpecified( + $version, + '..', + ); + } + + $majorVersion = $versionParts['major']; + $minorVersion = $versionParts['minor'] ?? 0; + $patchVersion = $versionParts['patch'] ?? 0; + $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion; + + if (version_compare($version, '12.0', '>=')) { + return new PostgreSQL120Platform(); + } + + if (version_compare($version, '10.0', '>=')) { + return new PostgreSQL100Platform(); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5060', + 'PostgreSQL 9 support is deprecated and will be removed in DBAL 4.' + . ' Consider upgrading to Postgres 10 or later.', + ); + + return new PostgreSQL94Platform(); + } + + /** + * {@inheritDoc} + */ + public function getDatabasePlatform() + { + return new PostgreSQL94Platform(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@link PostgreSQLPlatform::createSchemaManager()} instead. + */ + public function getSchemaManager(Connection $conn, AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5458', + 'AbstractPostgreSQLDriver::getSchemaManager() is deprecated.' + . ' Use PostgreSQLPlatform::createSchemaManager() instead.', + ); + + assert($platform instanceof PostgreSQLPlatform); + + return new PostgreSQLSchemaManager($conn, $platform); + } + + public function getExceptionConverter(): ExceptionConverter + { + return new PostgreSQL\ExceptionConverter(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver.php new file mode 100644 index 0000000..b9a9955 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver.php @@ -0,0 +1,53 @@ +exec('PRAGMA foreign_keys=ON'); + + return $connection; + } + }; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Connection.php b/vendor/doctrine/dbal/src/Driver/Connection.php new file mode 100644 index 0000000..2f460fd --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Connection.php @@ -0,0 +1,86 @@ +fetchNumeric(); + + if ($row === false) { + return false; + } + + return $row[0]; + } + + /** + * @return list> + * + * @throws Exception + */ + public static function fetchAllNumeric(Result $result): array + { + $rows = []; + + while (($row = $result->fetchNumeric()) !== false) { + $rows[] = $row; + } + + return $rows; + } + + /** + * @return list> + * + * @throws Exception + */ + public static function fetchAllAssociative(Result $result): array + { + $rows = []; + + while (($row = $result->fetchAssociative()) !== false) { + $rows[] = $row; + } + + return $rows; + } + + /** + * @return list + * + * @throws Exception + */ + public static function fetchFirstColumn(Result $result): array + { + $rows = []; + + while (($row = $result->fetchOne()) !== false) { + $rows[] = $row; + } + + return $rows; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Connection.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Connection.php new file mode 100644 index 0000000..dfb11c2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Connection.php @@ -0,0 +1,141 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function getServerVersion() + { + $serverInfo = db2_server_info($this->connection); + assert($serverInfo instanceof stdClass); + + return $serverInfo->DBMS_VER; + } + + public function prepare(string $sql): DriverStatement + { + $stmt = @db2_prepare($this->connection, $sql); + + if ($stmt === false) { + throw PrepareFailed::new(error_get_last()); + } + + return new Statement($stmt); + } + + public function query(string $sql): ResultInterface + { + return $this->prepare($sql)->execute(); + } + + /** + * {@inheritDoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + $value = db2_escape_string($value); + + if ($type === ParameterType::INTEGER) { + return $value; + } + + return "'" . $value . "'"; + } + + public function exec(string $sql): int + { + $stmt = @db2_exec($this->connection, $sql); + + if ($stmt === false) { + throw StatementError::new(); + } + + return db2_num_rows($stmt); + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + } + + return db2_last_insert_id($this->connection) ?? false; + } + + public function beginTransaction(): bool + { + return db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF); + } + + public function commit(): bool + { + if (! db2_commit($this->connection)) { + throw ConnectionError::new($this->connection); + } + + return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON); + } + + public function rollBack(): bool + { + if (! db2_rollback($this->connection)) { + throw ConnectionError::new($this->connection); + } + + return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON); + } + + /** @return resource */ + public function getNativeConnection() + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/DataSourceName.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/DataSourceName.php new file mode 100644 index 0000000..124a6f6 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/DataSourceName.php @@ -0,0 +1,84 @@ +string = $string; + } + + public function toString(): string + { + return $this->string; + } + + /** + * Creates the object from an array representation + * + * @param array $params + */ + public static function fromArray( + #[SensitiveParameter] + array $params + ): self { + $chunks = []; + + foreach ($params as $key => $value) { + $chunks[] = sprintf('%s=%s', $key, $value); + } + + return new self(implode(';', $chunks)); + } + + /** + * Creates the object from the given DBAL connection parameters. + * + * @param array $params + */ + public static function fromConnectionParameters( + #[SensitiveParameter] + array $params + ): self { + if (isset($params['dbname']) && strpos($params['dbname'], '=') !== false) { + return new self($params['dbname']); + } + + $dsnParams = []; + + foreach ( + [ + 'host' => 'HOSTNAME', + 'port' => 'PORT', + 'protocol' => 'PROTOCOL', + 'dbname' => 'DATABASE', + 'user' => 'UID', + 'password' => 'PWD', + ] as $dbalParam => $dsnParam + ) { + if (! isset($params[$dbalParam])) { + continue; + } + + $dsnParams[$dsnParam] = $params[$dbalParam]; + } + + return self::fromArray($dsnParams); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Driver.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Driver.php new file mode 100644 index 0000000..7650db5 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Driver.php @@ -0,0 +1,41 @@ +toString(); + + $username = $params['user'] ?? ''; + $password = $params['password'] ?? ''; + $driverOptions = $params['driverOptions'] ?? []; + + if (! empty($params['persistent'])) { + $connection = db2_pconnect($dataSourceName, $username, $password, $driverOptions); + } else { + $connection = db2_connect($dataSourceName, $username, $password, $driverOptions); + } + + if ($connection === false) { + throw ConnectionFailed::new(); + } + + return new Connection($connection); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php new file mode 100644 index 0000000..231c9d4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php @@ -0,0 +1,27 @@ +statement = $statement; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + $row = @db2_fetch_array($this->statement); + + if ($row === false && db2_stmt_error($this->statement) !== '02000') { + throw StatementError::new($this->statement); + } + + return $row; + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + $row = @db2_fetch_assoc($this->statement); + + if ($row === false && db2_stmt_error($this->statement) !== '02000') { + throw StatementError::new($this->statement); + } + + return $row; + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + public function rowCount(): int + { + return @db2_num_rows($this->statement); + } + + public function columnCount(): int + { + $count = db2_num_fields($this->statement); + + if ($count !== false) { + return $count; + } + + return 0; + } + + public function free(): void + { + db2_free_result($this->statement); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Statement.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Statement.php new file mode 100644 index 0000000..699e236 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Statement.php @@ -0,0 +1,220 @@ + + */ + private array $lobs = []; + + /** + * @internal The statement can be only instantiated by its driver connection. + * + * @param resource $stmt + */ + public function __construct($stmt) + { + $this->stmt = $stmt; + } + + /** + * {@inheritDoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + assert(is_int($param)); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + return $this->bindParam($param, $value, $type); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see bindValue()} instead. + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + assert(is_int($param)); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + switch ($type) { + case ParameterType::INTEGER: + $this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG); + break; + + case ParameterType::LARGE_OBJECT: + $this->lobs[$param] = &$variable; + break; + + default: + $this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR); + break; + } + + return true; + } + + /** + * @param int $position Parameter position + * @param mixed $variable + * + * @throws Exception + */ + private function bind($position, &$variable, int $parameterType, int $dataType): void + { + $this->parameters[$position] =& $variable; + + if (! db2_bind_param($this->stmt, $position, '', $parameterType, $dataType)) { + throw StatementError::new($this->stmt); + } + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): ResultInterface + { + if ($params !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::execute() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + } + + $handles = $this->bindLobs(); + + $result = @db2_execute($this->stmt, $params ?? $this->parameters); + + foreach ($handles as $handle) { + fclose($handle); + } + + $this->lobs = []; + + if ($result === false) { + throw StatementError::new($this->stmt); + } + + return new Result($this->stmt); + } + + /** + * @return list + * + * @throws Exception + */ + private function bindLobs(): array + { + $handles = []; + + foreach ($this->lobs as $param => $value) { + if (is_resource($value)) { + $handle = $handles[] = $this->createTemporaryFile(); + $path = stream_get_meta_data($handle)['uri']; + + $this->copyStreamToStream($value, $handle); + + $this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY); + } else { + $this->bind($param, $value, DB2_PARAM_IN, DB2_CHAR); + } + + unset($value); + } + + return $handles; + } + + /** + * @return resource + * + * @throws Exception + */ + private function createTemporaryFile() + { + $handle = @tmpfile(); + + if ($handle === false) { + throw CannotCreateTemporaryFile::new(error_get_last()); + } + + return $handle; + } + + /** + * @param resource $source + * @param resource $target + * + * @throws Exception + */ + private function copyStreamToStream($source, $target): void + { + if (@stream_copy_to_stream($source, $target) === false) { + throw CannotCopyStreamToStream::new(error_get_last()); + } + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Middleware.php b/vendor/doctrine/dbal/src/Driver/Middleware.php new file mode 100644 index 0000000..4629d9a --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Middleware.php @@ -0,0 +1,12 @@ +wrappedConnection = $wrappedConnection; + } + + public function prepare(string $sql): Statement + { + return $this->wrappedConnection->prepare($sql); + } + + public function query(string $sql): Result + { + return $this->wrappedConnection->query($sql); + } + + /** + * {@inheritDoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + return $this->wrappedConnection->quote($value, $type); + } + + public function exec(string $sql): int + { + return $this->wrappedConnection->exec($sql); + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + } + + return $this->wrappedConnection->lastInsertId($name); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + return $this->wrappedConnection->beginTransaction(); + } + + /** + * {@inheritDoc} + */ + public function commit() + { + return $this->wrappedConnection->commit(); + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + return $this->wrappedConnection->rollBack(); + } + + /** + * {@inheritDoc} + */ + public function getServerVersion() + { + if (! $this->wrappedConnection instanceof ServerInfoAwareConnection) { + throw new LogicException('The underlying connection is not a ServerInfoAwareConnection'); + } + + return $this->wrappedConnection->getServerVersion(); + } + + /** @return resource|object */ + public function getNativeConnection() + { + if (! method_exists($this->wrappedConnection, 'getNativeConnection')) { + throw new LogicException(sprintf( + 'The driver connection %s does not support accessing the native connection.', + get_class($this->wrappedConnection), + )); + } + + return $this->wrappedConnection->getNativeConnection(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Middleware/AbstractDriverMiddleware.php b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractDriverMiddleware.php new file mode 100644 index 0000000..1c9d430 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractDriverMiddleware.php @@ -0,0 +1,73 @@ +wrappedDriver = $wrappedDriver; + } + + /** + * {@inheritDoc} + */ + public function connect( + #[SensitiveParameter] + array $params + ) { + return $this->wrappedDriver->connect($params); + } + + /** + * {@inheritDoc} + */ + public function getDatabasePlatform() + { + return $this->wrappedDriver->getDatabasePlatform(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead. + */ + public function getSchemaManager(Connection $conn, AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5458', + 'AbstractDriverMiddleware::getSchemaManager() is deprecated.' + . ' Use AbstractPlatform::createSchemaManager() instead.', + ); + + return $this->wrappedDriver->getSchemaManager($conn, $platform); + } + + public function getExceptionConverter(): ExceptionConverter + { + return $this->wrappedDriver->getExceptionConverter(); + } + + /** + * {@inheritDoc} + */ + public function createDatabasePlatformForVersion($version) + { + if ($this->wrappedDriver instanceof VersionAwarePlatformDriver) { + return $this->wrappedDriver->createDatabasePlatformForVersion($version); + } + + return $this->wrappedDriver->getDatabasePlatform(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Middleware/AbstractResultMiddleware.php b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractResultMiddleware.php new file mode 100644 index 0000000..198d39b --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractResultMiddleware.php @@ -0,0 +1,78 @@ +wrappedResult = $result; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + return $this->wrappedResult->fetchNumeric(); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->wrappedResult->fetchAssociative(); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return $this->wrappedResult->fetchOne(); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return $this->wrappedResult->fetchAllNumeric(); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return $this->wrappedResult->fetchAllAssociative(); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return $this->wrappedResult->fetchFirstColumn(); + } + + public function rowCount(): int + { + return $this->wrappedResult->rowCount(); + } + + public function columnCount(): int + { + return $this->wrappedResult->columnCount(); + } + + public function free(): void + { + $this->wrappedResult->free(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Middleware/AbstractStatementMiddleware.php b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractStatementMiddleware.php new file mode 100644 index 0000000..6cd2f8f --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractStatementMiddleware.php @@ -0,0 +1,71 @@ +wrappedStatement = $wrappedStatement; + } + + /** + * {@inheritDoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + return $this->wrappedStatement->bindValue($param, $value, $type); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see bindValue()} instead. + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + return $this->wrappedStatement->bindParam($param, $variable, $type, $length); + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): Result + { + return $this->wrappedStatement->execute($params); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Connection.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Connection.php new file mode 100644 index 0000000..d492684 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Connection.php @@ -0,0 +1,141 @@ +connection = $connection; + } + + /** + * Retrieves mysqli native resource handle. + * + * Could be used if part of your application is not using DBAL. + * + * @deprecated Call {@see getNativeConnection()} instead. + */ + public function getWrappedResourceHandle(): mysqli + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5037', + '%s is deprecated, call getNativeConnection() instead.', + __METHOD__, + ); + + return $this->getNativeConnection(); + } + + public function getServerVersion(): string + { + return $this->connection->get_server_info(); + } + + public function prepare(string $sql): DriverStatement + { + try { + $stmt = $this->connection->prepare($sql); + } catch (mysqli_sql_exception $e) { + throw ConnectionError::upcast($e); + } + + if ($stmt === false) { + throw ConnectionError::new($this->connection); + } + + return new Statement($stmt); + } + + public function query(string $sql): ResultInterface + { + return $this->prepare($sql)->execute(); + } + + /** + * {@inheritDoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + return "'" . $this->connection->escape_string($value) . "'"; + } + + public function exec(string $sql): int + { + try { + $result = $this->connection->query($sql); + } catch (mysqli_sql_exception $e) { + throw ConnectionError::upcast($e); + } + + if ($result === false) { + throw ConnectionError::new($this->connection); + } + + return $this->connection->affected_rows; + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + } + + return $this->connection->insert_id; + } + + public function beginTransaction(): bool + { + $this->connection->begin_transaction(); + + return true; + } + + public function commit(): bool + { + try { + return $this->connection->commit(); + } catch (mysqli_sql_exception $e) { + return false; + } + } + + public function rollBack(): bool + { + try { + return $this->connection->rollback(); + } catch (mysqli_sql_exception $e) { + return false; + } + } + + public function getNativeConnection(): mysqli + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Driver.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Driver.php new file mode 100644 index 0000000..4f51868 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Driver.php @@ -0,0 +1,117 @@ +compilePreInitializers($params) as $initializer) { + $initializer->initialize($connection); + } + + try { + $success = @$connection->real_connect( + $host, + $params['user'] ?? null, + $params['password'] ?? null, + $params['dbname'] ?? null, + $params['port'] ?? null, + $params['unix_socket'] ?? null, + $params['driverOptions'][Connection::OPTION_FLAGS] ?? 0, + ); + } catch (mysqli_sql_exception $e) { + throw ConnectionFailed::upcast($e); + } + + if (! $success) { + throw ConnectionFailed::new($connection); + } + + foreach ($this->compilePostInitializers($params) as $initializer) { + $initializer->initialize($connection); + } + + return new Connection($connection); + } + + /** + * @param array $params + * + * @return Generator + */ + private function compilePreInitializers( + #[SensitiveParameter] + array $params + ): Generator { + unset($params['driverOptions'][Connection::OPTION_FLAGS]); + + if (isset($params['driverOptions']) && $params['driverOptions'] !== []) { + yield new Options($params['driverOptions']); + } + + if ( + ! isset($params['ssl_key']) && + ! isset($params['ssl_cert']) && + ! isset($params['ssl_ca']) && + ! isset($params['ssl_capath']) && + ! isset($params['ssl_cipher']) + ) { + return; + } + + yield new Secure( + $params['ssl_key'] ?? '', + $params['ssl_cert'] ?? '', + $params['ssl_ca'] ?? '', + $params['ssl_capath'] ?? '', + $params['ssl_cipher'] ?? '', + ); + } + + /** + * @param array $params + * + * @return Generator + */ + private function compilePostInitializers( + #[SensitiveParameter] + array $params + ): Generator { + if (! isset($params['charset'])) { + return; + } + + yield new Charset($params['charset']); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionError.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionError.php new file mode 100644 index 0000000..ef5b980 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionError.php @@ -0,0 +1,31 @@ +error, $connection->sqlstate, $connection->errno); + } + + public static function upcast(mysqli_sql_exception $exception): self + { + $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); + $p->setAccessible(true); + + return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php new file mode 100644 index 0000000..44a8cab --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php @@ -0,0 +1,36 @@ +connect_error; + assert($error !== null); + + return new self($error, 'HY000', $connection->connect_errno); + } + + public static function upcast(mysqli_sql_exception $exception): self + { + $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); + $p->setAccessible(true); + + return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php new file mode 100644 index 0000000..6f26dbe --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php @@ -0,0 +1,22 @@ +error), + $connection->sqlstate, + $connection->errno, + ); + } + + public static function upcast(mysqli_sql_exception $exception, string $charset): self + { + $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); + $p->setAccessible(true); + + return new self( + sprintf('Failed to set charset "%s": %s', $charset, $exception->getMessage()), + $p->getValue($exception), + (int) $exception->getCode(), + $exception, + ); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidOption.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidOption.php new file mode 100644 index 0000000..6fb4631 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidOption.php @@ -0,0 +1,25 @@ +error, $statement->sqlstate, $statement->errno); + } + + public static function upcast(mysqli_sql_exception $exception): self + { + $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); + $p->setAccessible(true); + + return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer.php new file mode 100644 index 0000000..efab67e --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer.php @@ -0,0 +1,14 @@ +charset = $charset; + } + + public function initialize(mysqli $connection): void + { + try { + $success = $connection->set_charset($this->charset); + } catch (mysqli_sql_exception $e) { + throw InvalidCharset::upcast($e, $this->charset); + } + + if ($success) { + return; + } + + throw InvalidCharset::fromCharset($connection, $this->charset); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Options.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Options.php new file mode 100644 index 0000000..2e66f8d --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Options.php @@ -0,0 +1,32 @@ + */ + private array $options; + + /** @param array $options */ + public function __construct(array $options) + { + $this->options = $options; + } + + public function initialize(mysqli $connection): void + { + foreach ($this->options as $option => $value) { + if (! mysqli_options($connection, $option, $value)) { + throw InvalidOption::fromOption($option, $value); + } + } + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Secure.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Secure.php new file mode 100644 index 0000000..a25fcfc --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Secure.php @@ -0,0 +1,38 @@ +key = $key; + $this->cert = $cert; + $this->ca = $ca; + $this->capath = $capath; + $this->cipher = $cipher; + } + + public function initialize(mysqli $connection): void + { + $connection->ssl_set($this->key, $this->cert, $this->ca, $this->capath, $this->cipher); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Result.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Result.php new file mode 100644 index 0000000..c7dc65d --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Result.php @@ -0,0 +1,179 @@ + + */ + private array $columnNames = []; + + /** @var mixed[] */ + private array $boundValues = []; + + /** + * @internal The result can be only instantiated by its driver connection or statement. + * + * @throws Exception + */ + public function __construct(mysqli_stmt $statement) + { + $this->statement = $statement; + + $meta = $statement->result_metadata(); + + if ($meta === false) { + return; + } + + $this->hasColumns = true; + + $this->columnNames = array_column($meta->fetch_fields(), 'name'); + + $meta->free(); + + // Store result of every execution which has it. Otherwise it will be impossible + // to execute a new statement in case if the previous one has non-fetched rows + // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html + $this->statement->store_result(); + + // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql, + // it will have to allocate as much memory as it may be needed for the given column type + // (e.g. for a LONGBLOB column it's 4 gigabytes) + // @link https://bugs.php.net/bug.php?id=51386#1270673122 + // + // Make sure that the values are bound after each execution. Otherwise, if free() has been + // previously called on the result, the values are unbound making the statement unusable. + // + // It's also important that row values are bound after _each_ call to store_result(). Otherwise, + // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated + // to the length of the ones fetched during the previous execution. + $this->boundValues = array_fill(0, count($this->columnNames), null); + + // The following is necessary as PHP cannot handle references to properties properly + $refs = &$this->boundValues; + + if (! $this->statement->bind_result(...$refs)) { + throw StatementError::new($this->statement); + } + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + try { + $ret = $this->statement->fetch(); + } catch (mysqli_sql_exception $e) { + throw StatementError::upcast($e); + } + + if ($ret === false) { + throw StatementError::new($this->statement); + } + + if ($ret === null) { + return false; + } + + $values = []; + + foreach ($this->boundValues as $v) { + $values[] = $v; + } + + return $values; + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + $values = $this->fetchNumeric(); + + if ($values === false) { + return false; + } + + return array_combine($this->columnNames, $values); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + public function rowCount(): int + { + if ($this->hasColumns) { + return $this->statement->num_rows; + } + + return $this->statement->affected_rows; + } + + public function columnCount(): int + { + return $this->statement->field_count; + } + + public function free(): void + { + $this->statement->free_result(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php new file mode 100644 index 0000000..fec7c95 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php @@ -0,0 +1,239 @@ + 's', + ParameterType::STRING => 's', + ParameterType::BINARY => 's', + ParameterType::BOOLEAN => 'i', + ParameterType::NULL => 's', + ParameterType::INTEGER => 'i', + ParameterType::LARGE_OBJECT => 'b', + ]; + + private mysqli_stmt $stmt; + + /** @var mixed[] */ + private array $boundValues; + + private string $types; + + /** + * Contains ref values for bindValue(). + * + * @var mixed[] + */ + private array $values = []; + + /** @internal The statement can be only instantiated by its driver connection. */ + public function __construct(mysqli_stmt $stmt) + { + $this->stmt = $stmt; + + $paramCount = $this->stmt->param_count; + $this->types = str_repeat('s', $paramCount); + $this->boundValues = array_fill(1, $paramCount, null); + } + + /** + * @deprecated Use {@see bindValue()} instead. + * + * {@inheritDoc} + * + * @psalm-assert ParameterType::* $type + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + assert(is_int($param)); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + if (! isset(self::PARAM_TYPE_MAP[$type])) { + throw UnknownParameterType::new($type); + } + + $this->boundValues[$param] =& $variable; + $this->types[$param - 1] = self::PARAM_TYPE_MAP[$type]; + + return true; + } + + /** + * {@inheritDoc} + * + * @psalm-assert ParameterType::* $type + */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + assert(is_int($param)); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + if (! isset(self::PARAM_TYPE_MAP[$type])) { + throw UnknownParameterType::new($type); + } + + $this->values[$param] = $value; + $this->boundValues[$param] =& $this->values[$param]; + $this->types[$param - 1] = self::PARAM_TYPE_MAP[$type]; + + return true; + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): ResultInterface + { + if ($params !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::execute() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + } + + if ($params !== null && count($params) > 0) { + if (! $this->bindUntypedValues($params)) { + throw StatementError::new($this->stmt); + } + } elseif (count($this->boundValues) > 0) { + $this->bindTypedParameters(); + } + + try { + $result = $this->stmt->execute(); + } catch (mysqli_sql_exception $e) { + throw StatementError::upcast($e); + } + + if (! $result) { + throw StatementError::new($this->stmt); + } + + return new Result($this->stmt); + } + + /** + * Binds parameters with known types previously bound to the statement + * + * @throws Exception + */ + private function bindTypedParameters(): void + { + $streams = $values = []; + $types = $this->types; + + foreach ($this->boundValues as $parameter => $value) { + assert(is_int($parameter)); + + if (! isset($types[$parameter - 1])) { + $types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING]; + } + + if ($types[$parameter - 1] === self::PARAM_TYPE_MAP[ParameterType::LARGE_OBJECT]) { + if (is_resource($value)) { + if (get_resource_type($value) !== 'stream') { + throw NonStreamResourceUsedAsLargeObject::new($parameter); + } + + $streams[$parameter] = $value; + $values[$parameter] = null; + continue; + } + + $types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING]; + } + + $values[$parameter] = $value; + } + + if (! $this->stmt->bind_param($types, ...$values)) { + throw StatementError::new($this->stmt); + } + + $this->sendLongData($streams); + } + + /** + * Handle $this->_longData after regular query parameters have been bound + * + * @param array $streams + * + * @throws Exception + */ + private function sendLongData(array $streams): void + { + foreach ($streams as $paramNr => $stream) { + while (! feof($stream)) { + $chunk = fread($stream, 8192); + + if ($chunk === false) { + throw FailedReadingStreamOffset::new($paramNr); + } + + if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) { + throw StatementError::new($this->stmt); + } + } + } + } + + /** + * Binds a array of values to bound parameters. + * + * @param mixed[] $values + */ + private function bindUntypedValues(array $values): bool + { + return $this->stmt->bind_param(str_repeat('s', count($values)), ...$values); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Connection.php b/vendor/doctrine/dbal/src/Driver/OCI8/Connection.php new file mode 100644 index 0000000..72353fa --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/OCI8/Connection.php @@ -0,0 +1,170 @@ +connection = $connection; + $this->parser = new Parser(false); + $this->executionMode = new ExecutionMode(); + } + + public function getServerVersion(): string + { + $version = oci_server_version($this->connection); + + if ($version === false) { + throw Error::new($this->connection); + } + + $result = preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches); + assert($result === 1); + + return $matches[1]; + } + + /** @throws Parser\Exception */ + public function prepare(string $sql): DriverStatement + { + $visitor = new ConvertPositionalToNamedPlaceholders(); + + $this->parser->parse($sql, $visitor); + + $statement = oci_parse($this->connection, $visitor->getSQL()); + assert(is_resource($statement)); + + return new Statement($this->connection, $statement, $visitor->getParameterMap(), $this->executionMode); + } + + /** + * @throws Exception + * @throws Parser\Exception + */ + public function query(string $sql): ResultInterface + { + return $this->prepare($sql)->execute(); + } + + /** + * {@inheritDoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + if (is_int($value) || is_float($value)) { + return $value; + } + + $value = str_replace("'", "''", $value); + + return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; + } + + /** + * @throws Exception + * @throws Parser\Exception + */ + public function exec(string $sql): int + { + return $this->prepare($sql)->execute()->rowCount(); + } + + /** + * {@inheritDoc} + * + * @param string|null $name + * + * @return int|false + * + * @throws Parser\Exception + */ + public function lastInsertId($name = null) + { + if ($name === null) { + return false; + } + + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + + $result = $this->query('SELECT ' . $name . '.CURRVAL FROM DUAL')->fetchOne(); + + if ($result === false) { + throw SequenceDoesNotExist::new(); + } + + return (int) $result; + } + + public function beginTransaction(): bool + { + $this->executionMode->disableAutoCommit(); + + return true; + } + + public function commit(): bool + { + if (! oci_commit($this->connection)) { + throw Error::new($this->connection); + } + + $this->executionMode->enableAutoCommit(); + + return true; + } + + public function rollBack(): bool + { + if (! oci_rollback($this->connection)) { + throw Error::new($this->connection); + } + + $this->executionMode->enableAutoCommit(); + + return true; + } + + /** @return resource */ + public function getNativeConnection() + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php b/vendor/doctrine/dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php new file mode 100644 index 0000000..e2a1126 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php @@ -0,0 +1,56 @@ +). + * + * Oracle does not support positional parameters, hence this method converts all + * positional parameters into artificially named parameters. + * + * @internal This class is not covered by the backward compatibility promise + */ +final class ConvertPositionalToNamedPlaceholders implements Visitor +{ + /** @var list */ + private array $buffer = []; + + /** @var array */ + private array $parameterMap = []; + + public function acceptOther(string $sql): void + { + $this->buffer[] = $sql; + } + + public function acceptPositionalParameter(string $sql): void + { + $position = count($this->parameterMap) + 1; + $param = ':param' . $position; + + $this->parameterMap[$position] = $param; + + $this->buffer[] = $param; + } + + public function acceptNamedParameter(string $sql): void + { + $this->buffer[] = $sql; + } + + public function getSQL(): string + { + return implode('', $this->buffer); + } + + /** @return array */ + public function getParameterMap(): array + { + return $this->parameterMap; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Driver.php b/vendor/doctrine/dbal/src/Driver/OCI8/Driver.php new file mode 100644 index 0000000..650a4f9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/OCI8/Driver.php @@ -0,0 +1,58 @@ +getEasyConnectString($params); + + $persistent = ! empty($params['persistent']); + $exclusive = ! empty($params['driverOptions']['exclusive']); + + if ($persistent && $exclusive) { + throw InvalidConfiguration::forPersistentAndExclusive(); + } + + if ($persistent) { + $connection = @oci_pconnect($username, $password, $connectionString, $charset, $sessionMode); + } elseif ($exclusive) { + $connection = @oci_new_connect($username, $password, $connectionString, $charset, $sessionMode); + } else { + $connection = @oci_connect($username, $password, $connectionString, $charset, $sessionMode); + } + + if ($connection === false) { + throw ConnectionFailed::new(); + } + + return new Connection($connection); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Exception/ConnectionFailed.php b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/ConnectionFailed.php new file mode 100644 index 0000000..cefe9a3 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/ConnectionFailed.php @@ -0,0 +1,26 @@ +isAutoCommitEnabled = true; + } + + public function disableAutoCommit(): void + { + $this->isAutoCommitEnabled = false; + } + + public function isAutoCommitEnabled(): bool + { + return $this->isAutoCommitEnabled; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Middleware/InitializeSession.php b/vendor/doctrine/dbal/src/Driver/OCI8/Middleware/InitializeSession.php new file mode 100644 index 0000000..3a356fb --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/OCI8/Middleware/InitializeSession.php @@ -0,0 +1,38 @@ +exec( + 'ALTER SESSION SET' + . " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" + . " NLS_TIME_FORMAT = 'HH24:MI:SS'" + . " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" + . " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'" + . " NLS_NUMERIC_CHARACTERS = '.,'", + ); + + return $connection; + } + }; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Result.php b/vendor/doctrine/dbal/src/Driver/OCI8/Result.php new file mode 100644 index 0000000..08add4f --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/OCI8/Result.php @@ -0,0 +1,145 @@ +statement = $statement; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + return $this->fetch(OCI_NUM); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->fetch(OCI_ASSOC); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0]; + } + + public function rowCount(): int + { + $count = oci_num_rows($this->statement); + + if ($count !== false) { + return $count; + } + + return 0; + } + + public function columnCount(): int + { + $count = oci_num_fields($this->statement); + + if ($count !== false) { + return $count; + } + + return 0; + } + + public function free(): void + { + oci_cancel($this->statement); + } + + /** + * @return mixed|false + * + * @throws Exception + */ + private function fetch(int $mode) + { + $result = oci_fetch_array($this->statement, $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS); + + if ($result === false && oci_error($this->statement) !== false) { + throw Error::new($this->statement); + } + + return $result; + } + + /** @return array */ + private function fetchAll(int $mode, int $fetchStructure): array + { + oci_fetch_all( + $this->statement, + $result, + 0, + -1, + $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS, + ); + + return $result; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Statement.php b/vendor/doctrine/dbal/src/Driver/OCI8/Statement.php new file mode 100644 index 0000000..015a14b --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/OCI8/Statement.php @@ -0,0 +1,174 @@ + */ + private array $parameterMap; + + private ExecutionMode $executionMode; + + /** + * @internal The statement can be only instantiated by its driver connection. + * + * @param resource $connection + * @param resource $statement + * @param array $parameterMap + */ + public function __construct($connection, $statement, array $parameterMap, ExecutionMode $executionMode) + { + $this->connection = $connection; + $this->statement = $statement; + $this->parameterMap = $parameterMap; + $this->executionMode = $executionMode; + } + + /** + * {@inheritDoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + return $this->bindParam($param, $value, $type); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see bindValue()} instead. + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + if (is_int($param)) { + if (! isset($this->parameterMap[$param])) { + throw UnknownParameterIndex::new($param); + } + + $param = $this->parameterMap[$param]; + } + + if ($type === ParameterType::LARGE_OBJECT) { + if ($variable !== null) { + $lob = oci_new_descriptor($this->connection, OCI_D_LOB); + $lob->writeTemporary($variable, OCI_TEMP_BLOB); + + $variable =& $lob; + } else { + $type = ParameterType::STRING; + } + } + + return oci_bind_by_name( + $this->statement, + $param, + $variable, + $length ?? -1, + $this->convertParameterType($type), + ); + } + + /** + * Converts DBAL parameter type to oci8 parameter type + */ + private function convertParameterType(int $type): int + { + switch ($type) { + case ParameterType::BINARY: + return OCI_B_BIN; + + case ParameterType::LARGE_OBJECT: + return OCI_B_BLOB; + + default: + return SQLT_CHR; + } + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): ResultInterface + { + if ($params !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::execute() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + + foreach ($params as $key => $val) { + if (is_int($key)) { + $this->bindValue($key + 1, $val, ParameterType::STRING); + } else { + $this->bindValue($key, $val, ParameterType::STRING); + } + } + } + + if ($this->executionMode->isAutoCommitEnabled()) { + $mode = OCI_COMMIT_ON_SUCCESS; + } else { + $mode = OCI_NO_AUTO_COMMIT; + } + + $ret = @oci_execute($this->statement, $mode); + if (! $ret) { + throw Error::new($this->statement); + } + + return new Result($this->statement); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Connection.php b/vendor/doctrine/dbal/src/Driver/PDO/Connection.php new file mode 100644 index 0000000..290dcc2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/Connection.php @@ -0,0 +1,158 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $this->connection = $connection; + } + + public function exec(string $sql): int + { + try { + $result = $this->connection->exec($sql); + + assert($result !== false); + + return $result; + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritDoc} + */ + public function getServerVersion() + { + return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * {@inheritDoc} + * + * @return Statement + */ + public function prepare(string $sql): StatementInterface + { + try { + $stmt = $this->connection->prepare($sql); + assert($stmt instanceof PDOStatement); + + return new Statement($stmt); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + public function query(string $sql): ResultInterface + { + try { + $stmt = $this->connection->query($sql); + assert($stmt instanceof PDOStatement); + + return new Result($stmt); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritDoc} + * + * @throws UnknownParameterType + * + * @psalm-assert ParameterType::* $type + */ + public function quote($value, $type = ParameterType::STRING) + { + return $this->connection->quote($value, ParameterTypeMap::convertParamType($type)); + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + try { + if ($name === null) { + return $this->connection->lastInsertId(); + } + + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + + return $this->connection->lastInsertId($name); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + public function beginTransaction(): bool + { + try { + return $this->connection->beginTransaction(); + } catch (PDOException $exception) { + throw DriverPDOException::new($exception); + } + } + + public function commit(): bool + { + try { + return $this->connection->commit(); + } catch (PDOException $exception) { + throw DriverPDOException::new($exception); + } + } + + public function rollBack(): bool + { + try { + return $this->connection->rollBack(); + } catch (PDOException $exception) { + throw DriverPDOException::new($exception); + } + } + + public function getNativeConnection(): PDO + { + return $this->connection; + } + + /** @deprecated Call {@see getNativeConnection()} instead. */ + public function getWrappedConnection(): PDO + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5037', + '%s is deprecated, call getNativeConnection() instead.', + __METHOD__, + ); + + return $this->getNativeConnection(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Exception.php b/vendor/doctrine/dbal/src/Driver/PDO/Exception.php new file mode 100644 index 0000000..fbb8125 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/Exception.php @@ -0,0 +1,30 @@ +errorInfo !== null) { + [$sqlState, $code] = $exception->errorInfo; + + $code ??= 0; + } else { + $code = $exception->getCode(); + $sqlState = null; + } + + return new self($exception->getMessage(), $sqlState, $code, $exception); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/MySQL/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/MySQL/Driver.php new file mode 100644 index 0000000..2492698 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/MySQL/Driver.php @@ -0,0 +1,76 @@ +constructPdoDsn($safeParams), + $params['user'] ?? '', + $params['password'] ?? '', + $driverOptions, + ); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + + return new Connection($pdo); + } + + /** + * Constructs the MySQL PDO DSN. + * + * @param mixed[] $params + */ + private function constructPdoDsn(array $params): string + { + $dsn = 'mysql:'; + if (isset($params['host']) && $params['host'] !== '') { + $dsn .= 'host=' . $params['host'] . ';'; + } + + if (isset($params['port'])) { + $dsn .= 'port=' . $params['port'] . ';'; + } + + if (isset($params['dbname'])) { + $dsn .= 'dbname=' . $params['dbname'] . ';'; + } + + if (isset($params['unix_socket'])) { + $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; + } + + if (isset($params['charset'])) { + $dsn .= 'charset=' . $params['charset'] . ';'; + } + + return $dsn; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/OCI/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/OCI/Driver.php new file mode 100644 index 0000000..10ada9f --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/OCI/Driver.php @@ -0,0 +1,61 @@ +constructPdoDsn($params), + $params['user'] ?? '', + $params['password'] ?? '', + $driverOptions, + ); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + + return new Connection($pdo); + } + + /** + * Constructs the Oracle PDO DSN. + * + * @param mixed[] $params + */ + private function constructPdoDsn(array $params): string + { + $dsn = 'oci:dbname=' . $this->getEasyConnectString($params); + + if (isset($params['charset'])) { + $dsn .= ';charset=' . $params['charset']; + } + + return $dsn; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/PDOException.php b/vendor/doctrine/dbal/src/Driver/PDO/PDOException.php new file mode 100644 index 0000000..6eefda4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/PDOException.php @@ -0,0 +1,33 @@ +message, 0, $previous); + + $exception->errorInfo = $previous->errorInfo; + $exception->code = $previous->code; + $exception->sqlState = $previous->errorInfo[0] ?? null; + + return $exception; + } + + public function getSQLState(): ?string + { + return $this->sqlState; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/ParameterTypeMap.php b/vendor/doctrine/dbal/src/Driver/PDO/ParameterTypeMap.php new file mode 100644 index 0000000..f17b585 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/ParameterTypeMap.php @@ -0,0 +1,49 @@ + PDO::PARAM_NULL, + ParameterType::INTEGER => PDO::PARAM_INT, + ParameterType::STRING => PDO::PARAM_STR, + ParameterType::ASCII => PDO::PARAM_STR, + ParameterType::BINARY => PDO::PARAM_LOB, + ParameterType::LARGE_OBJECT => PDO::PARAM_LOB, + ParameterType::BOOLEAN => PDO::PARAM_BOOL, + ]; + + /** + * Converts DBAL parameter type to PDO parameter type + * + * @psalm-return PDO::PARAM_* + * + * @throws UnknownParameterType + * + * @psalm-assert ParameterType::* $type + */ + public static function convertParamType(int $type): int + { + if (! isset(self::PARAM_TYPE_MAP[$type])) { + throw UnknownParameterType::new($type); + } + + return self::PARAM_TYPE_MAP[$type]; + } + + private function __construct() + { + } + + private function __clone() + { + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php new file mode 100644 index 0000000..5bfcd73 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php @@ -0,0 +1,135 @@ +constructPdoDsn($safeParams), + $params['user'] ?? '', + $params['password'] ?? '', + $driverOptions, + ); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + + if ( + ! isset($driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES]) + || $driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES] === true + ) { + $pdo->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); + } + + $connection = new Connection($pdo); + + /* defining client_encoding via SET NAMES to avoid inconsistent DSN support + * - passing client_encoding via the 'options' param breaks pgbouncer support + */ + if (isset($params['charset'])) { + $connection->exec('SET NAMES \'' . $params['charset'] . '\''); + } + + return $connection; + } + + /** + * Constructs the Postgres PDO DSN. + * + * @param array $params + */ + private function constructPdoDsn(array $params): string + { + $dsn = 'pgsql:'; + + if (isset($params['host']) && $params['host'] !== '') { + $dsn .= 'host=' . $params['host'] . ';'; + } + + if (isset($params['port']) && $params['port'] !== '') { + $dsn .= 'port=' . $params['port'] . ';'; + } + + if (isset($params['dbname'])) { + $dsn .= 'dbname=' . $params['dbname'] . ';'; + } elseif (isset($params['default_dbname'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5705', + 'The "default_dbname" connection parameter is deprecated. Use "dbname" instead.', + ); + + $dsn .= 'dbname=' . $params['default_dbname'] . ';'; + } else { + if (isset($params['user']) && $params['user'] !== 'postgres') { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5705', + 'Relying on the DBAL connecting to the "postgres" database by default is deprecated.' + . ' Unless you want to have the server determine the default database for the connection,' + . ' specify the database name explicitly.', + ); + } + + // Used for temporary connections to allow operations like dropping the database currently connected to. + $dsn .= 'dbname=postgres;'; + } + + if (isset($params['sslmode'])) { + $dsn .= 'sslmode=' . $params['sslmode'] . ';'; + } + + if (isset($params['sslrootcert'])) { + $dsn .= 'sslrootcert=' . $params['sslrootcert'] . ';'; + } + + if (isset($params['sslcert'])) { + $dsn .= 'sslcert=' . $params['sslcert'] . ';'; + } + + if (isset($params['sslkey'])) { + $dsn .= 'sslkey=' . $params['sslkey'] . ';'; + } + + if (isset($params['sslcrl'])) { + $dsn .= 'sslcrl=' . $params['sslcrl'] . ';'; + } + + if (isset($params['application_name'])) { + $dsn .= 'application_name=' . $params['application_name'] . ';'; + } + + if (isset($params['gssencmode'])) { + $dsn .= 'gssencmode=' . $params['gssencmode'] . ';'; + } + + return $dsn; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Result.php b/vendor/doctrine/dbal/src/Driver/PDO/Result.php new file mode 100644 index 0000000..67970ac --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/Result.php @@ -0,0 +1,124 @@ +statement = $statement; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + return $this->fetch(PDO::FETCH_NUM); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->fetch(PDO::FETCH_ASSOC); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return $this->fetch(PDO::FETCH_COLUMN); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return $this->fetchAll(PDO::FETCH_NUM); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return $this->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return $this->fetchAll(PDO::FETCH_COLUMN); + } + + public function rowCount(): int + { + try { + return $this->statement->rowCount(); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + public function columnCount(): int + { + try { + return $this->statement->columnCount(); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + public function free(): void + { + $this->statement->closeCursor(); + } + + /** + * @psalm-param PDO::FETCH_* $mode + * + * @return mixed + * + * @throws Exception + */ + private function fetch(int $mode) + { + try { + return $this->statement->fetch($mode); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * @psalm-param PDO::FETCH_* $mode + * + * @return list + * + * @throws Exception + */ + private function fetchAll(int $mode): array + { + try { + return $this->statement->fetchAll($mode); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Connection.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Connection.php new file mode 100644 index 0000000..9015f55 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Connection.php @@ -0,0 +1,70 @@ +connection = $connection; + } + + public function prepare(string $sql): StatementInterface + { + return new Statement( + $this->connection->prepare($sql), + ); + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name === null) { + return parent::lastInsertId($name); + } + + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + + $statement = $this->prepare( + 'SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?', + ); + $statement->bindValue(1, $name); + + return $statement->execute() + ->fetchOne(); + } + + public function getNativeConnection(): PDO + { + return $this->connection->getNativeConnection(); + } + + /** @deprecated Call {@see getNativeConnection()} instead. */ + public function getWrappedConnection(): PDO + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5037', + '%s is deprecated, call getNativeConnection() instead.', + __METHOD__, + ); + + return $this->connection->getWrappedConnection(); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Driver.php new file mode 100644 index 0000000..63eabb7 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Driver.php @@ -0,0 +1,108 @@ + $value) { + if (is_int($option)) { + $driverOptions[$option] = $value; + } else { + $dsnOptions[$option] = $value; + } + } + } + + if (! empty($params['persistent'])) { + $driverOptions[PDO::ATTR_PERSISTENT] = true; + } + + $safeParams = $params; + unset($safeParams['password'], $safeParams['url']); + + try { + $pdo = new PDO( + $this->constructDsn($safeParams, $dsnOptions), + $params['user'] ?? '', + $params['password'] ?? '', + $driverOptions, + ); + } catch (\PDOException $exception) { + throw PDOException::new($exception); + } + + return new Connection(new PDOConnection($pdo)); + } + + /** + * Constructs the Sqlsrv PDO DSN. + * + * @param mixed[] $params + * @param string[] $connectionOptions + * + * @throws Exception + */ + private function constructDsn(array $params, array $connectionOptions): string + { + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + + if (isset($params['port'])) { + $dsn .= ',' . $params['port']; + } + } elseif (isset($params['port'])) { + throw PortWithoutHost::new(); + } + + if (isset($params['dbname'])) { + $connectionOptions['Database'] = $params['dbname']; + } + + if (isset($params['MultipleActiveResultSets'])) { + $connectionOptions['MultipleActiveResultSets'] = $params['MultipleActiveResultSets'] ? 'true' : 'false'; + } + + return $dsn . $this->getConnectionOptionsDsn($connectionOptions); + } + + /** + * Converts a connection options array to the DSN + * + * @param string[] $connectionOptions + */ + private function getConnectionOptionsDsn(array $connectionOptions): string + { + $connectionOptionsDsn = ''; + + foreach ($connectionOptions as $paramName => $paramValue) { + $connectionOptionsDsn .= sprintf(';%s=%s', $paramName, $paramValue); + } + + return $connectionOptionsDsn; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Statement.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Statement.php new file mode 100644 index 0000000..cb2dfae --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Statement.php @@ -0,0 +1,109 @@ +statement = $statement; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see bindValue()} instead. + * + * @param string|int $param + * @param mixed $variable + * @param int $type + * @param int|null $length + * @param mixed $driverOptions The usage of the argument is deprecated. + * + * @throws UnknownParameterType + * + * @psalm-assert ParameterType::* $type + */ + public function bindParam( + $param, + &$variable, + $type = ParameterType::STRING, + $length = null, + $driverOptions = null + ): bool { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + if (func_num_args() > 4) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4533', + 'The $driverOptions argument of Statement::bindParam() is deprecated.', + ); + } + + switch ($type) { + case ParameterType::LARGE_OBJECT: + case ParameterType::BINARY: + $driverOptions ??= PDO::SQLSRV_ENCODING_BINARY; + + break; + + case ParameterType::ASCII: + $type = ParameterType::STRING; + $length = 0; + $driverOptions = PDO::SQLSRV_ENCODING_SYSTEM; + break; + } + + return $this->statement->bindParam($param, $variable, $type, $length ?? 0, $driverOptions); + } + + /** + * @throws UnknownParameterType + * + * {@inheritDoc} + * + * @psalm-assert ParameterType::* $type + */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + return $this->bindParam($param, $value, $type); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLite/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLite/Driver.php new file mode 100644 index 0000000..2e97788 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLite/Driver.php @@ -0,0 +1,77 @@ +constructPdoDsn(array_intersect_key($params, ['path' => true, 'memory' => true])), + $params['user'] ?? '', + $params['password'] ?? '', + $driverOptions, + ); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + + UserDefinedFunctions::register( + [$pdo, 'sqliteCreateFunction'], + $userDefinedFunctions, + ); + + return new Connection($pdo); + } + + /** + * Constructs the Sqlite PDO DSN. + * + * @param array $params + */ + private function constructPdoDsn(array $params): string + { + $dsn = 'sqlite:'; + if (isset($params['path'])) { + $dsn .= $params['path']; + } elseif (isset($params['memory'])) { + $dsn .= ':memory:'; + } + + return $dsn; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Statement.php b/vendor/doctrine/dbal/src/Driver/PDO/Statement.php new file mode 100644 index 0000000..64f318d --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PDO/Statement.php @@ -0,0 +1,137 @@ +stmt = $stmt; + } + + /** + * {@inheritDoc} + * + * @throws UnknownParameterType + * + * @psalm-assert ParameterType::* $type + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + $pdoType = ParameterTypeMap::convertParamType($type); + + try { + return $this->stmt->bindValue($param, $value, $pdoType); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see bindValue()} instead. + * + * @param mixed $param + * @param mixed $variable + * @param int $type + * @param int|null $length + * @param mixed $driverOptions The usage of the argument is deprecated. + * + * @throws UnknownParameterType + * + * @psalm-assert ParameterType::* $type + */ + public function bindParam( + $param, + &$variable, + $type = ParameterType::STRING, + $length = null, + $driverOptions = null + ): bool { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + if (func_num_args() > 4) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4533', + 'The $driverOptions argument of Statement::bindParam() is deprecated.', + ); + } + + $pdoType = ParameterTypeMap::convertParamType($type); + + try { + return $this->stmt->bindParam( + $param, + $variable, + $pdoType, + $length ?? 0, + ...array_slice(func_get_args(), 4), + ); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): ResultInterface + { + if ($params !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::execute() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + } + + try { + $this->stmt->execute($params); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + + return new Result($this->stmt); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php new file mode 100644 index 0000000..378e8ed --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php @@ -0,0 +1,161 @@ +connection = $connection; + $this->parser = new Parser(false); + } + + public function __destruct() + { + if (! isset($this->connection)) { + return; + } + + @pg_close($this->connection); + } + + public function prepare(string $sql): Statement + { + $visitor = new ConvertParameters(); + $this->parser->parse($sql, $visitor); + + $statementName = uniqid('dbal', true); + if (@pg_send_prepare($this->connection, $statementName, $visitor->getSQL()) !== true) { + throw new Exception(pg_last_error($this->connection)); + } + + $result = @pg_get_result($this->connection); + assert($result !== false); + + if ((bool) pg_result_error($result)) { + throw Exception::fromResult($result); + } + + return new Statement($this->connection, $statementName, $visitor->getParameterMap()); + } + + public function query(string $sql): Result + { + if (@pg_send_query($this->connection, $sql) !== true) { + throw new Exception(pg_last_error($this->connection)); + } + + $result = @pg_get_result($this->connection); + assert($result !== false); + + if ((bool) pg_result_error($result)) { + throw Exception::fromResult($result); + } + + return new Result($result); + } + + /** {@inheritDoc} */ + public function quote($value, $type = ParameterType::STRING) + { + if ($type === ParameterType::BINARY || $type === ParameterType::LARGE_OBJECT) { + return sprintf("'%s'", pg_escape_bytea($this->connection, $value)); + } + + return pg_escape_literal($this->connection, $value); + } + + public function exec(string $sql): int + { + return $this->query($sql)->rowCount(); + } + + /** {@inheritDoc} */ + public function lastInsertId($name = null) + { + if ($name !== null) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + + return $this->query(sprintf('SELECT CURRVAL(%s)', $this->quote($name)))->fetchOne(); + } + + return $this->query('SELECT LASTVAL()')->fetchOne(); + } + + /** @return true */ + public function beginTransaction(): bool + { + $this->exec('BEGIN'); + + return true; + } + + /** @return true */ + public function commit(): bool + { + $this->exec('COMMIT'); + + return true; + } + + /** @return true */ + public function rollBack(): bool + { + $this->exec('ROLLBACK'); + + return true; + } + + public function getServerVersion(): string + { + return (string) pg_version($this->connection)['server']; + } + + /** @return PgSqlConnection|resource */ + public function getNativeConnection() + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php b/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php new file mode 100644 index 0000000..795f12d --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php @@ -0,0 +1,49 @@ + */ + private array $buffer = []; + + /** @var array */ + private array $parameterMap = []; + + public function acceptPositionalParameter(string $sql): void + { + $position = count($this->parameterMap) + 1; + $this->parameterMap[$position] = $position; + $this->buffer[] = '$' . $position; + } + + public function acceptNamedParameter(string $sql): void + { + $position = count($this->parameterMap) + 1; + $this->parameterMap[$sql] = $position; + $this->buffer[] = '$' . $position; + } + + public function acceptOther(string $sql): void + { + $this->buffer[] = $sql; + } + + public function getSQL(): string + { + return implode('', $this->buffer); + } + + /** @return array */ + public function getParameterMap(): array + { + return $this->parameterMap; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php new file mode 100644 index 0000000..6377499 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php @@ -0,0 +1,86 @@ +constructConnectionString($params), PGSQL_CONNECT_FORCE_NEW); + } catch (ErrorException $e) { + throw new Exception($e->getMessage(), '08006', 0, $e); + } finally { + restore_error_handler(); + } + + if ($connection === false) { + throw new Exception('Unable to connect to Postgres server.'); + } + + $driverConnection = new Connection($connection); + + if (isset($params['application_name'])) { + $driverConnection->exec('SET application_name = ' . $driverConnection->quote($params['application_name'])); + } + + return $driverConnection; + } + + /** + * Constructs the Postgres connection string + * + * @param array $params + */ + private function constructConnectionString( + #[SensitiveParameter] + array $params + ): string { + $components = array_filter( + [ + 'host' => $params['host'] ?? null, + 'port' => $params['port'] ?? null, + 'dbname' => $params['dbname'] ?? 'postgres', + 'user' => $params['user'] ?? null, + 'password' => $params['password'] ?? null, + 'sslmode' => $params['sslmode'] ?? null, + 'gssencmode' => $params['gssencmode'] ?? null, + ], + static fn ($value) => $value !== '' && $value !== null, + ); + + return implode(' ', array_map( + static fn ($value, string $key) => sprintf("%s='%s'", $key, addslashes($value)), + array_values($components), + array_keys($components), + )); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php new file mode 100644 index 0000000..41e0dff --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php @@ -0,0 +1,30 @@ +result = $result; + } + + public function __destruct() + { + if (! isset($this->result)) { + return; + } + + $this->free(); + } + + /** {@inheritDoc} */ + public function fetchNumeric() + { + if ($this->result === null) { + return false; + } + + $row = pg_fetch_row($this->result); + if ($row === false) { + return false; + } + + return $this->mapNumericRow($row, $this->fetchNumericColumnTypes()); + } + + /** {@inheritDoc} */ + public function fetchAssociative() + { + if ($this->result === null) { + return false; + } + + $row = pg_fetch_assoc($this->result); + if ($row === false) { + return false; + } + + return $this->mapAssociativeRow($row, $this->fetchAssociativeColumnTypes()); + } + + /** {@inheritDoc} */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** {@inheritDoc} */ + public function fetchAllNumeric(): array + { + if ($this->result === null) { + return []; + } + + $resultSet = pg_fetch_all($this->result, PGSQL_NUM); + // On PHP 7.4, pg_fetch_all() might return false for empty result sets. + if ($resultSet === false) { + return []; + } + + $types = $this->fetchNumericColumnTypes(); + + return array_map( + fn (array $row) => $this->mapNumericRow($row, $types), + $resultSet, + ); + } + + /** {@inheritDoc} */ + public function fetchAllAssociative(): array + { + if ($this->result === null) { + return []; + } + + $resultSet = pg_fetch_all($this->result, PGSQL_ASSOC); + // On PHP 7.4, pg_fetch_all() might return false for empty result sets. + if ($resultSet === false) { + return []; + } + + $types = $this->fetchAssociativeColumnTypes(); + + return array_map( + fn (array $row) => $this->mapAssociativeRow($row, $types), + $resultSet, + ); + } + + /** {@inheritDoc} */ + public function fetchFirstColumn(): array + { + if ($this->result === null) { + return []; + } + + $postgresType = pg_field_type($this->result, 0); + + return array_map( + fn ($value) => $this->mapType($postgresType, $value), + pg_fetch_all_columns($this->result), + ); + } + + public function rowCount(): int + { + if ($this->result === null) { + return 0; + } + + return pg_affected_rows($this->result); + } + + public function columnCount(): int + { + if ($this->result === null) { + return 0; + } + + return pg_num_fields($this->result); + } + + public function free(): void + { + if ($this->result === null) { + return; + } + + pg_free_result($this->result); + $this->result = null; + } + + /** @return array */ + private function fetchNumericColumnTypes(): array + { + assert($this->result !== null); + + $types = []; + $numFields = pg_num_fields($this->result); + for ($i = 0; $i < $numFields; ++$i) { + $types[$i] = pg_field_type($this->result, $i); + } + + return $types; + } + + /** @return array */ + private function fetchAssociativeColumnTypes(): array + { + assert($this->result !== null); + + $types = []; + $numFields = pg_num_fields($this->result); + for ($i = 0; $i < $numFields; ++$i) { + $types[pg_field_name($this->result, $i)] = pg_field_type($this->result, $i); + } + + return $types; + } + + /** + * @param list $row + * @param array $types + * + * @return list + */ + private function mapNumericRow(array $row, array $types): array + { + assert($this->result !== null); + + return array_map( + fn ($value, $field) => $this->mapType($types[$field], $value), + $row, + array_keys($row), + ); + } + + /** + * @param array $row + * @param array $types + * + * @return array + */ + private function mapAssociativeRow(array $row, array $types): array + { + assert($this->result !== null); + + $mappedRow = []; + foreach ($row as $field => $value) { + $mappedRow[$field] = $this->mapType($types[$field], $value); + } + + return $mappedRow; + } + + /** @return string|int|float|bool|null */ + private function mapType(string $postgresType, ?string $value) + { + if ($value === null) { + return null; + } + + switch ($postgresType) { + case 'bool': + switch ($value) { + case 't': + return true; + case 'f': + return false; + } + + throw UnexpectedValue::new($value, $postgresType); + + case 'bytea': + return hex2bin(substr($value, 2)); + + case 'float4': + case 'float8': + return (float) $value; + + case 'int2': + case 'int4': + return (int) $value; + + case 'int8': + return PHP_INT_SIZE >= 8 ? (int) $value : $value; + } + + return $value; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php new file mode 100644 index 0000000..75af66f --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php @@ -0,0 +1,177 @@ + */ + private array $parameterMap; + + /** @var array */ + private array $parameters = []; + + /** @psalm-var array */ + private array $parameterTypes = []; + + /** + * @param PgSqlConnection|resource $connection + * @param array $parameterMap + */ + public function __construct($connection, string $name, array $parameterMap) + { + if (! is_resource($connection) && ! $connection instanceof PgSqlConnection) { + throw new TypeError(sprintf( + 'Expected connection to be a resource or an instance of %s, got %s.', + PgSqlConnection::class, + is_object($connection) ? get_class($connection) : gettype($connection), + )); + } + + $this->connection = $connection; + $this->name = $name; + $this->parameterMap = $parameterMap; + } + + public function __destruct() + { + if (! isset($this->connection)) { + return; + } + + @pg_query( + $this->connection, + 'DEALLOCATE ' . pg_escape_identifier($this->connection, $this->name), + ); + } + + /** {@inheritDoc} */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + if (! isset($this->parameterMap[$param])) { + throw UnknownParameter::new((string) $param); + } + + $this->parameters[$this->parameterMap[$param]] = $value; + $this->parameterTypes[$this->parameterMap[$param]] = $type; + + return true; + } + + /** {@inheritDoc} */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + if (func_num_args() > 4) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4533', + 'The $driverOptions argument of Statement::bindParam() is deprecated.', + ); + } + + if (! isset($this->parameterMap[$param])) { + throw UnknownParameter::new((string) $param); + } + + $this->parameters[$this->parameterMap[$param]] = &$variable; + $this->parameterTypes[$this->parameterMap[$param]] = $type; + + return true; + } + + /** {@inheritDoc} */ + public function execute($params = null): Result + { + if ($params !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::execute() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + + foreach ($params as $param => $value) { + if (is_int($param)) { + $this->bindValue($param + 1, $value, ParameterType::STRING); + } else { + $this->bindValue($param, $value, ParameterType::STRING); + } + } + } + + ksort($this->parameters); + + $escapedParameters = []; + foreach ($this->parameters as $parameter => $value) { + switch ($this->parameterTypes[$parameter]) { + case ParameterType::BINARY: + case ParameterType::LARGE_OBJECT: + $escapedParameters[] = $value === null ? null : pg_escape_bytea( + $this->connection, + is_resource($value) ? stream_get_contents($value) : $value, + ); + break; + default: + $escapedParameters[] = $value; + } + } + + if (@pg_send_execute($this->connection, $this->name, $escapedParameters) !== true) { + throw new Exception(pg_last_error($this->connection)); + } + + $result = @pg_get_result($this->connection); + assert($result !== false); + + if ((bool) pg_result_error($result)) { + throw Exception::fromResult($result); + } + + return new Result($result); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/Result.php b/vendor/doctrine/dbal/src/Driver/Result.php new file mode 100644 index 0000000..7843a95 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Result.php @@ -0,0 +1,93 @@ +|false + * + * @throws Exception + */ + public function fetchNumeric(); + + /** + * Returns the next row of the result as an associative array or FALSE if there are no more rows. + * + * @return array|false + * + * @throws Exception + */ + public function fetchAssociative(); + + /** + * Returns the first value of the next row of the result or FALSE if there are no more rows. + * + * @return mixed|false + * + * @throws Exception + */ + public function fetchOne(); + + /** + * Returns an array containing all of the result rows represented as numeric arrays. + * + * @return list> + * + * @throws Exception + */ + public function fetchAllNumeric(): array; + + /** + * Returns an array containing all of the result rows represented as associative arrays. + * + * @return list> + * + * @throws Exception + */ + public function fetchAllAssociative(): array; + + /** + * Returns an array containing the values of the first column of the result. + * + * @return list + * + * @throws Exception + */ + public function fetchFirstColumn(): array; + + /** + * Returns the number of rows affected by the DELETE, INSERT, or UPDATE statement that produced the result. + * + * If the statement executed a SELECT query or a similar platform-specific SQL (e.g. DESCRIBE, SHOW, etc.), + * some database drivers may return the number of rows returned by that query. However, this behaviour + * is not guaranteed for all drivers and should not be relied on in portable applications. + * + * @return int The number of rows. + * + * @throws Exception + */ + public function rowCount(): int; + + /** + * Returns the number of columns in the result + * + * @return int The number of columns in the result. If the columns cannot be counted, + * this method must return 0. + * + * @throws Exception + */ + public function columnCount(): int; + + /** + * Discards the non-fetched portion of the result, enabling the originating statement to be executed again. + */ + public function free(): void; +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Connection.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Connection.php new file mode 100644 index 0000000..16e45d1 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Connection.php @@ -0,0 +1,144 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function getServerVersion() + { + $serverInfo = sqlsrv_server_info($this->connection); + + return $serverInfo['SQLServerVersion']; + } + + public function prepare(string $sql): DriverStatement + { + return new Statement($this->connection, $sql); + } + + public function query(string $sql): ResultInterface + { + return $this->prepare($sql)->execute(); + } + + /** + * {@inheritDoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + if (is_int($value)) { + return $value; + } + + if (is_float($value)) { + return sprintf('%F', $value); + } + + return "'" . str_replace("'", "''", $value) . "'"; + } + + public function exec(string $sql): int + { + $stmt = sqlsrv_query($this->connection, $sql); + + if ($stmt === false) { + throw Error::new(); + } + + $rowsAffected = sqlsrv_rows_affected($stmt); + + if ($rowsAffected === false) { + throw Error::new(); + } + + return $rowsAffected; + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4687', + 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', + ); + + $result = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') + ->execute([$name]); + } else { + $result = $this->query('SELECT @@IDENTITY'); + } + + return $result->fetchOne(); + } + + public function beginTransaction(): bool + { + if (! sqlsrv_begin_transaction($this->connection)) { + throw Error::new(); + } + + return true; + } + + public function commit(): bool + { + if (! sqlsrv_commit($this->connection)) { + throw Error::new(); + } + + return true; + } + + public function rollBack(): bool + { + if (! sqlsrv_rollback($this->connection)) { + throw Error::new(); + } + + return true; + } + + /** @return resource */ + public function getNativeConnection() + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Driver.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Driver.php new file mode 100644 index 0000000..fcbdb77 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Driver.php @@ -0,0 +1,73 @@ +statement = $stmt; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + return $this->fetch(SQLSRV_FETCH_NUMERIC); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->fetch(SQLSRV_FETCH_ASSOC); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + public function rowCount(): int + { + $count = sqlsrv_rows_affected($this->statement); + + if ($count !== false) { + return $count; + } + + return 0; + } + + public function columnCount(): int + { + $count = sqlsrv_num_fields($this->statement); + + if ($count !== false) { + return $count; + } + + return 0; + } + + public function free(): void + { + // emulate it by fetching and discarding rows, similarly to what PDO does in this case + // @link http://php.net/manual/en/pdostatement.closecursor.php + // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075 + // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them + while (sqlsrv_fetch($this->statement)) { + } + } + + /** @return mixed|false */ + private function fetch(int $fetchType) + { + return sqlsrv_fetch_array($this->statement, $fetchType) ?? false; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Statement.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Statement.php new file mode 100644 index 0000000..227c334 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Statement.php @@ -0,0 +1,223 @@ + + */ + private array $variables = []; + + /** + * Bound parameter types. + * + * @var array + */ + private array $types = []; + + /** + * Append to any INSERT query to retrieve the last insert id. + */ + private const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; + + /** + * @internal The statement can be only instantiated by its driver connection. + * + * @param resource $conn + * @param string $sql + */ + public function __construct($conn, $sql) + { + $this->conn = $conn; + $this->sql = $sql; + + if (stripos($sql, 'INSERT INTO ') !== 0) { + return; + } + + $this->sql .= self::LAST_INSERT_ID_SQL; + } + + /** + * {@inheritDoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + assert(is_int($param)); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + $this->variables[$param] = $value; + $this->types[$param] = $type; + + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see bindValue()} instead. + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + assert(is_int($param)); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + $this->variables[$param] =& $variable; + $this->types[$param] = $type; + + // unset the statement resource if it exists as the new one will need to be bound to the new variable + $this->stmt = null; + + return true; + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): ResultInterface + { + if ($params !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::execute() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + + foreach ($params as $key => $val) { + if (is_int($key)) { + $this->bindValue($key + 1, $val, ParameterType::STRING); + } else { + $this->bindValue($key, $val, ParameterType::STRING); + } + } + } + + $this->stmt ??= $this->prepare(); + + if (! sqlsrv_execute($this->stmt)) { + throw Error::new(); + } + + return new Result($this->stmt); + } + + /** + * Prepares SQL Server statement resource + * + * @return resource + * + * @throws Exception + */ + private function prepare() + { + $params = []; + + foreach ($this->variables as $column => &$variable) { + switch ($this->types[$column]) { + case ParameterType::LARGE_OBJECT: + $params[$column - 1] = [ + &$variable, + SQLSRV_PARAM_IN, + SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_VARBINARY('max'), + ]; + break; + + case ParameterType::BINARY: + $params[$column - 1] = [ + &$variable, + SQLSRV_PARAM_IN, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + ]; + break; + + case ParameterType::ASCII: + $params[$column - 1] = [ + &$variable, + SQLSRV_PARAM_IN, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + ]; + break; + + default: + $params[$column - 1] =& $variable; + break; + } + } + + $stmt = sqlsrv_prepare($this->conn, $this->sql, $params); + + if ($stmt === false) { + throw Error::new(); + } + + return $stmt; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Connection.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Connection.php new file mode 100644 index 0000000..91b9b5f --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Connection.php @@ -0,0 +1,107 @@ +connection = $connection; + } + + public function prepare(string $sql): Statement + { + try { + $statement = $this->connection->prepare($sql); + } catch (\Exception $e) { + throw Exception::new($e); + } + + assert($statement !== false); + + return new Statement($this->connection, $statement); + } + + public function query(string $sql): Result + { + try { + $result = $this->connection->query($sql); + } catch (\Exception $e) { + throw Exception::new($e); + } + + assert($result !== false); + + return new Result($result, $this->connection->changes()); + } + + /** @inheritDoc */ + public function quote($value, $type = ParameterType::STRING): string + { + return sprintf('\'%s\'', SQLite3::escapeString($value)); + } + + public function exec(string $sql): int + { + try { + $this->connection->exec($sql); + } catch (\Exception $e) { + throw Exception::new($e); + } + + return $this->connection->changes(); + } + + /** @inheritDoc */ + public function lastInsertId($name = null): int + { + return $this->connection->lastInsertRowID(); + } + + public function beginTransaction(): bool + { + try { + return $this->connection->exec('BEGIN'); + } catch (\Exception $e) { + return false; + } + } + + public function commit(): bool + { + try { + return $this->connection->exec('COMMIT'); + } catch (\Exception $e) { + return false; + } + } + + public function rollBack(): bool + { + try { + return $this->connection->exec('ROLLBACK'); + } catch (\Exception $e) { + return false; + } + } + + public function getNativeConnection(): SQLite3 + { + return $this->connection; + } + + public function getServerVersion(): string + { + return SQLite3::version()['versionString']; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Driver.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Driver.php new file mode 100644 index 0000000..fecc481 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Driver.php @@ -0,0 +1,49 @@ +enableExceptions(true); + + UserDefinedFunctions::register([$connection, 'createFunction']); + + return new Connection($connection); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Exception.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Exception.php new file mode 100644 index 0000000..3ca1190 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Exception.php @@ -0,0 +1,18 @@ +getMessage(), null, (int) $exception->getCode(), $exception); + } +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Result.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Result.php new file mode 100644 index 0000000..3881e18 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Result.php @@ -0,0 +1,91 @@ +result = $result; + $this->changes = $changes; + } + + /** @inheritDoc */ + public function fetchNumeric() + { + if ($this->result === null) { + return false; + } + + return $this->result->fetchArray(SQLITE3_NUM); + } + + /** @inheritDoc */ + public function fetchAssociative() + { + if ($this->result === null) { + return false; + } + + return $this->result->fetchArray(SQLITE3_ASSOC); + } + + /** @inheritDoc */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** @inheritDoc */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** @inheritDoc */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** @inheritDoc */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + public function rowCount(): int + { + return $this->changes; + } + + public function columnCount(): int + { + if ($this->result === null) { + return 0; + } + + return $this->result->numColumns(); + } + + public function free(): void + { + if ($this->result === null) { + return; + } + + $this->result->finalize(); + $this->result = null; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Statement.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Statement.php new file mode 100644 index 0000000..a4166aa --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Statement.php @@ -0,0 +1,136 @@ + SQLITE3_NULL, + ParameterType::INTEGER => SQLITE3_INTEGER, + ParameterType::STRING => SQLITE3_TEXT, + ParameterType::ASCII => SQLITE3_TEXT, + ParameterType::BINARY => SQLITE3_BLOB, + ParameterType::LARGE_OBJECT => SQLITE3_BLOB, + ParameterType::BOOLEAN => SQLITE3_INTEGER, + ]; + + private SQLite3 $connection; + private SQLite3Stmt $statement; + + /** @internal The statement can be only instantiated by its driver connection. */ + public function __construct(SQLite3 $connection, SQLite3Stmt $statement) + { + $this->connection = $connection; + $this->statement = $statement; + } + + /** + * @throws UnknownParameterType + * + * {@inheritDoc} + * + * @psalm-assert ParameterType::* $type + */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + return $this->statement->bindValue($param, $value, $this->convertParamType($type)); + } + + /** + * @throws UnknownParameterType + * + * {@inheritDoc} + * + * @psalm-assert ParameterType::* $type + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + return $this->statement->bindParam($param, $variable, $this->convertParamType($type)); + } + + /** @inheritDoc */ + public function execute($params = null): Result + { + if ($params !== null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::execute() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + + foreach ($params as $param => $value) { + if (is_int($param)) { + $this->bindValue($param + 1, $value, ParameterType::STRING); + } else { + $this->bindValue($param, $value, ParameterType::STRING); + } + } + } + + try { + $result = $this->statement->execute(); + } catch (\Exception $e) { + throw Exception::new($e); + } + + assert($result !== false); + + return new Result($result, $this->connection->changes()); + } + + /** + * @psalm-return value-of + * + * @psalm-assert ParameterType::* $type + */ + private function convertParamType(int $type): int + { + if (! isset(self::PARAM_TYPE_MAP[$type])) { + throw UnknownParameterType::new($type); + } + + return self::PARAM_TYPE_MAP[$type]; + } +} diff --git a/vendor/doctrine/dbal/src/Driver/ServerInfoAwareConnection.php b/vendor/doctrine/dbal/src/Driver/ServerInfoAwareConnection.php new file mode 100644 index 0000000..5687ab0 --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/ServerInfoAwareConnection.php @@ -0,0 +1,21 @@ +execute() is called. + * + * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), + * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. + * + * Most parameters are input parameters, that is, parameters that are + * used in a read-only fashion to build up the query. Some drivers support the invocation + * of stored procedures that return data as output parameters, and some also as input/output + * parameters that both send in data and are updated to receive it. + * + * @deprecated Use {@see bindValue()} instead. + * + * @param string|int $param Parameter identifier. For a prepared statement using named placeholders, + * this will be a parameter name of the form :name. For a prepared statement using + * question mark placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. + * @param int $type Explicit data type for the parameter using the {@see ParameterType} + * constants. + * @param int|null $length You must specify maxlength when using an OUT bind + * so that PHP allocates enough memory to hold the returned value. + * + * @return bool TRUE on success or FALSE on failure. + * + * @throws Exception + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null); + + /** + * Executes a prepared statement + * + * If the prepared statement included parameter markers, you must either: + * call {@see bindParam()} to bind PHP variables to the parameter markers: + * bound variables pass their value as input and receive the output value, + * if any, of their associated parameter markers or pass an array of input-only + * parameter values. + * + * @param mixed[]|null $params A numeric array of values with as many elements as there are + * bound parameters in the SQL statement being executed. + * + * @throws Exception + */ + public function execute($params = null): Result; +} diff --git a/vendor/doctrine/dbal/src/DriverManager.php b/vendor/doctrine/dbal/src/DriverManager.php new file mode 100644 index 0000000..056f420 --- /dev/null +++ b/vendor/doctrine/dbal/src/DriverManager.php @@ -0,0 +1,288 @@ +, + * driverClass?: class-string, + * driverOptions?: array, + * host?: string, + * password?: string, + * path?: string, + * persistent?: bool, + * platform?: Platforms\AbstractPlatform, + * port?: int, + * serverVersion?: string, + * url?: string, + * user?: string, + * unix_socket?: string, + * } + * @psalm-type Params = array{ + * application_name?: string, + * charset?: string, + * dbname?: string, + * defaultTableOptions?: array, + * default_dbname?: string, + * driver?: key-of, + * driverClass?: class-string, + * driverOptions?: array, + * host?: string, + * keepSlave?: bool, + * keepReplica?: bool, + * master?: OverrideParams, + * memory?: bool, + * password?: string, + * path?: string, + * persistent?: bool, + * platform?: Platforms\AbstractPlatform, + * port?: int, + * primary?: OverrideParams, + * replica?: array, + * serverVersion?: string, + * sharding?: array, + * slaves?: array, + * url?: string, + * user?: string, + * wrapperClass?: class-string, + * unix_socket?: string, + * } + */ +final class DriverManager +{ + /** + * List of supported drivers and their mappings to the driver classes. + * + * To add your own driver use the 'driverClass' parameter to {@see DriverManager::getConnection()}. + */ + private const DRIVER_MAP = [ + 'pdo_mysql' => PDO\MySQL\Driver::class, + 'pdo_sqlite' => PDO\SQLite\Driver::class, + 'pdo_pgsql' => PDO\PgSQL\Driver::class, + 'pdo_oci' => PDO\OCI\Driver::class, + 'oci8' => OCI8\Driver::class, + 'ibm_db2' => IBMDB2\Driver::class, + 'pdo_sqlsrv' => PDO\SQLSrv\Driver::class, + 'mysqli' => Mysqli\Driver::class, + 'pgsql' => PgSQL\Driver::class, + 'sqlsrv' => SQLSrv\Driver::class, + 'sqlite3' => SQLite3\Driver::class, + ]; + + /** + * List of URL schemes from a database URL and their mappings to driver. + * + * @deprecated Use actual driver names instead. + * + * @var array + * @psalm-var array> + */ + private static array $driverSchemeAliases = [ + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]; + + /** + * Private constructor. This class cannot be instantiated. + * + * @codeCoverageIgnore + */ + private function __construct() + { + } + + /** + * Creates a connection object based on the specified parameters. + * This method returns a Doctrine\DBAL\Connection which wraps the underlying + * driver connection. + * + * $params must contain at least one of the following. + * + * Either 'driver' with one of the array keys of {@see DRIVER_MAP}, + * OR 'driverClass' that contains the full class name (with namespace) of the + * driver class to instantiate. + * + * Other (optional) parameters: + * + * user (string): + * The username to use when connecting. + * + * password (string): + * The password to use when connecting. + * + * driverOptions (array): + * Any additional driver-specific options for the driver. These are just passed + * through to the driver. + * + * wrapperClass: + * You may specify a custom wrapper class through the 'wrapperClass' + * parameter but this class MUST inherit from Doctrine\DBAL\Connection. + * + * driverClass: + * The driver class to use. + * + * @param Configuration|null $config The configuration to use. + * @param EventManager|null $eventManager The event manager to use. + * @psalm-param Params $params + * + * @psalm-return ($params is array{wrapperClass: class-string} ? T : Connection) + * + * @throws Exception + * + * @template T of Connection + */ + public static function getConnection( + #[SensitiveParameter] + array $params, + ?Configuration $config = null, + ?EventManager $eventManager = null + ): Connection { + // create default config and event manager, if not set + $config ??= new Configuration(); + $eventManager ??= new EventManager(); + $params = self::parseDatabaseUrl($params); + + // URL support for PrimaryReplicaConnection + if (isset($params['primary'])) { + $params['primary'] = self::parseDatabaseUrl($params['primary']); + } + + if (isset($params['replica'])) { + foreach ($params['replica'] as $key => $replicaParams) { + $params['replica'][$key] = self::parseDatabaseUrl($replicaParams); + } + } + + $driver = self::createDriver($params['driver'] ?? null, $params['driverClass'] ?? null); + + foreach ($config->getMiddlewares() as $middleware) { + $driver = $middleware->wrap($driver); + } + + $wrapperClass = $params['wrapperClass'] ?? Connection::class; + if (! is_a($wrapperClass, Connection::class, true)) { + throw Exception::invalidWrapperClass($wrapperClass); + } + + return new $wrapperClass($params, $driver, $config, $eventManager); + } + + /** + * Returns the list of supported drivers. + * + * @return string[] + * @psalm-return list> + */ + public static function getAvailableDrivers(): array + { + return array_keys(self::DRIVER_MAP); + } + + /** + * @throws Exception + * + * @psalm-assert key-of|null $driver + * @psalm-assert class-string|null $driverClass + */ + private static function createDriver(?string $driver, ?string $driverClass): Driver + { + if ($driverClass === null) { + if ($driver === null) { + throw Exception::driverRequired(); + } + + if (! isset(self::DRIVER_MAP[$driver])) { + throw Exception::unknownDriver($driver, array_keys(self::DRIVER_MAP)); + } + + $driverClass = self::DRIVER_MAP[$driver]; + } elseif (! is_a($driverClass, Driver::class, true)) { + throw Exception::invalidDriverClass($driverClass); + } + + return new $driverClass(); + } + + /** + * Extracts parts from a database URL, if present, and returns an + * updated list of parameters. + * + * @param mixed[] $params The list of parameters. + * @psalm-param Params $params + * + * @return mixed[] A modified list of parameters with info from a database + * URL extracted into indidivual parameter parts. + * @psalm-return Params + * + * @throws Exception + */ + private static function parseDatabaseUrl( + #[SensitiveParameter] + array $params + ): array { + if (! isset($params['url'])) { + return $params; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5843', + 'The "url" connection parameter is deprecated. Please use %s to parse a database url before calling %s.', + DsnParser::class, + self::class, + ); + + $parser = new DsnParser(self::$driverSchemeAliases); + try { + $parsedParams = $parser->parse($params['url']); + } catch (MalformedDsnException $e) { + throw new Exception('Malformed parameter "url".', 0, $e); + } + + if (isset($parsedParams['driver'])) { + // The requested driver from the URL scheme takes precedence + // over the default custom driver from the connection parameters (if any). + unset($params['driverClass']); + } + + $params = array_merge($params, $parsedParams); + + // If a schemeless connection URL is given, we require a default driver or default custom driver + // as connection parameter. + if (! isset($params['driverClass']) && ! isset($params['driver'])) { + throw Exception::driverRequired($params['url']); + } + + return $params; + } +} diff --git a/vendor/doctrine/dbal/src/Event/ConnectionEventArgs.php b/vendor/doctrine/dbal/src/Event/ConnectionEventArgs.php new file mode 100644 index 0000000..9a69c25 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/ConnectionEventArgs.php @@ -0,0 +1,27 @@ +connection = $connection; + } + + /** @return Connection */ + public function getConnection() + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Event/Listeners/OracleSessionInit.php b/vendor/doctrine/dbal/src/Event/Listeners/OracleSessionInit.php new file mode 100644 index 0000000..9598f43 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/Listeners/OracleSessionInit.php @@ -0,0 +1,77 @@ + 'HH24:MI:SS', + 'NLS_DATE_FORMAT' => 'YYYY-MM-DD HH24:MI:SS', + 'NLS_TIMESTAMP_FORMAT' => 'YYYY-MM-DD HH24:MI:SS', + 'NLS_TIMESTAMP_TZ_FORMAT' => 'YYYY-MM-DD HH24:MI:SS TZH:TZM', + 'NLS_NUMERIC_CHARACTERS' => '.,', + ]; + + /** @param string[] $oracleSessionVars */ + public function __construct(array $oracleSessionVars = []) + { + $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars); + } + + /** + * @return void + * + * @throws Exception + */ + public function postConnect(ConnectionEventArgs $args) + { + if (count($this->_defaultSessionVars) === 0) { + return; + } + + $vars = []; + foreach (array_change_key_case($this->_defaultSessionVars, CASE_UPPER) as $option => $value) { + if ($option === 'CURRENT_SCHEMA') { + $vars[] = $option . ' = ' . $value; + } else { + $vars[] = $option . " = '" . $value . "'"; + } + } + + $sql = 'ALTER SESSION SET ' . implode(' ', $vars); + $args->getConnection()->executeStatement($sql); + } + + /** + * {@inheritDoc} + */ + public function getSubscribedEvents() + { + return [Events::postConnect]; + } +} diff --git a/vendor/doctrine/dbal/src/Event/Listeners/SQLSessionInit.php b/vendor/doctrine/dbal/src/Event/Listeners/SQLSessionInit.php new file mode 100644 index 0000000..4ce32d6 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/Listeners/SQLSessionInit.php @@ -0,0 +1,43 @@ +sql = $sql; + } + + /** + * @return void + * + * @throws Exception + */ + public function postConnect(ConnectionEventArgs $args) + { + $args->getConnection()->executeStatement($this->sql); + } + + /** + * {@inheritDoc} + */ + public function getSubscribedEvents() + { + return [Events::postConnect]; + } +} diff --git a/vendor/doctrine/dbal/src/Event/Listeners/SQLiteSessionInit.php b/vendor/doctrine/dbal/src/Event/Listeners/SQLiteSessionInit.php new file mode 100644 index 0000000..950f05f --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/Listeners/SQLiteSessionInit.php @@ -0,0 +1,30 @@ +getConnection()->executeStatement('PRAGMA foreign_keys=ON'); + } + + /** + * {@inheritDoc} + */ + public function getSubscribedEvents() + { + return [Events::postConnect]; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaAlterTableAddColumnEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaAlterTableAddColumnEventArgs.php new file mode 100644 index 0000000..9f3ff6e --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaAlterTableAddColumnEventArgs.php @@ -0,0 +1,81 @@ +column = $column; + $this->tableDiff = $tableDiff; + $this->platform = $platform; + } + + /** @return Column */ + public function getColumn() + { + return $this->column; + } + + /** @return TableDiff */ + public function getTableDiff() + { + return $this->tableDiff; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. + * + * @param string|string[] $sql + * + * @return SchemaAlterTableAddColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3580', + 'Passing multiple SQL statements as an array to SchemaAlterTableAddColumnEventaArrgs::addSql() ' . + 'is deprecated. Pass each statement as an individual argument instead.', + ); + } + + $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); + + return $this; + } + + /** @return string[] */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaAlterTableChangeColumnEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaAlterTableChangeColumnEventArgs.php new file mode 100644 index 0000000..9ba37aa --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaAlterTableChangeColumnEventArgs.php @@ -0,0 +1,71 @@ +columnDiff = $columnDiff; + $this->tableDiff = $tableDiff; + $this->platform = $platform; + } + + /** @return ColumnDiff */ + public function getColumnDiff() + { + return $this->columnDiff; + } + + /** @return TableDiff */ + public function getTableDiff() + { + return $this->tableDiff; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. + * + * @param string|string[] $sql + * + * @return SchemaAlterTableChangeColumnEventArgs + */ + public function addSql($sql) + { + $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); + + return $this; + } + + /** @return string[] */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaAlterTableEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaAlterTableEventArgs.php new file mode 100644 index 0000000..07c065a --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaAlterTableEventArgs.php @@ -0,0 +1,62 @@ +tableDiff = $tableDiff; + $this->platform = $platform; + } + + /** @return TableDiff */ + public function getTableDiff() + { + return $this->tableDiff; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. + * + * @param string|string[] $sql + * + * @return SchemaAlterTableEventArgs + */ + public function addSql($sql) + { + $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); + + return $this; + } + + /** @return string[] */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaAlterTableRemoveColumnEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaAlterTableRemoveColumnEventArgs.php new file mode 100644 index 0000000..4122b41 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaAlterTableRemoveColumnEventArgs.php @@ -0,0 +1,71 @@ +column = $column; + $this->tableDiff = $tableDiff; + $this->platform = $platform; + } + + /** @return Column */ + public function getColumn() + { + return $this->column; + } + + /** @return TableDiff */ + public function getTableDiff() + { + return $this->tableDiff; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. + * + * @param string|string[] $sql + * + * @return SchemaAlterTableRemoveColumnEventArgs + */ + public function addSql($sql) + { + $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); + + return $this; + } + + /** @return string[] */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaAlterTableRenameColumnEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaAlterTableRenameColumnEventArgs.php new file mode 100644 index 0000000..21d3c16 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaAlterTableRenameColumnEventArgs.php @@ -0,0 +1,82 @@ +oldColumnName = $oldColumnName; + $this->column = $column; + $this->tableDiff = $tableDiff; + $this->platform = $platform; + } + + /** @return string */ + public function getOldColumnName() + { + return $this->oldColumnName; + } + + /** @return Column */ + public function getColumn() + { + return $this->column; + } + + /** @return TableDiff */ + public function getTableDiff() + { + return $this->tableDiff; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. + * + * @param string|string[] $sql + * + * @return SchemaAlterTableRenameColumnEventArgs + */ + public function addSql($sql) + { + $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); + + return $this; + } + + /** @return string[] */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaColumnDefinitionEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaColumnDefinitionEventArgs.php new file mode 100644 index 0000000..04fcbde --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaColumnDefinitionEventArgs.php @@ -0,0 +1,87 @@ +tableColumn = $tableColumn; + $this->table = $table; + $this->database = $database; + $this->connection = $connection; + } + + /** + * Allows to clear the column which means the column will be excluded from + * tables column list. + * + * @return SchemaColumnDefinitionEventArgs + */ + public function setColumn(?Column $column = null) + { + $this->column = $column; + + return $this; + } + + /** @return Column|null */ + public function getColumn() + { + return $this->column; + } + + /** @return mixed[] */ + public function getTableColumn() + { + return $this->tableColumn; + } + + /** @return string */ + public function getTable() + { + return $this->table; + } + + /** @return string */ + public function getDatabase() + { + return $this->database; + } + + /** @return Connection */ + public function getConnection() + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaCreateTableColumnEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaCreateTableColumnEventArgs.php new file mode 100644 index 0000000..54f134d --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaCreateTableColumnEventArgs.php @@ -0,0 +1,71 @@ +column = $column; + $this->table = $table; + $this->platform = $platform; + } + + /** @return Column */ + public function getColumn() + { + return $this->column; + } + + /** @return Table */ + public function getTable() + { + return $this->table; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. + * + * @param string|string[] $sql + * + * @return SchemaCreateTableColumnEventArgs + */ + public function addSql($sql) + { + $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); + + return $this; + } + + /** @return string[] */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaCreateTableEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaCreateTableEventArgs.php new file mode 100644 index 0000000..a7d548d --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaCreateTableEventArgs.php @@ -0,0 +1,87 @@ +table = $table; + $this->columns = $columns; + $this->options = $options; + $this->platform = $platform; + } + + /** @return Table */ + public function getTable() + { + return $this->table; + } + + /** @return mixed[][] */ + public function getColumns() + { + return $this->columns; + } + + /** @return mixed[] */ + public function getOptions() + { + return $this->options; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. + * + * @param string|string[] $sql + * + * @return SchemaCreateTableEventArgs + */ + public function addSql($sql) + { + $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); + + return $this; + } + + /** @return string[] */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaDropTableEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaDropTableEventArgs.php new file mode 100644 index 0000000..f45e3a1 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaDropTableEventArgs.php @@ -0,0 +1,64 @@ +table = $table; + $this->platform = $platform; + } + + /** @return string|Table */ + public function getTable() + { + return $this->table; + } + + /** @return AbstractPlatform */ + public function getPlatform() + { + return $this->platform; + } + + /** + * @param string $sql + * + * @return SchemaDropTableEventArgs + */ + public function setSql($sql) + { + $this->sql = $sql; + + return $this; + } + + /** @return string|null */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaEventArgs.php new file mode 100644 index 0000000..77d1d39 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaEventArgs.php @@ -0,0 +1,29 @@ +preventDefault = true; + + return $this; + } + + /** @return bool */ + public function isDefaultPrevented() + { + return $this->preventDefault; + } +} diff --git a/vendor/doctrine/dbal/src/Event/SchemaIndexDefinitionEventArgs.php b/vendor/doctrine/dbal/src/Event/SchemaIndexDefinitionEventArgs.php new file mode 100644 index 0000000..dbee55a --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/SchemaIndexDefinitionEventArgs.php @@ -0,0 +1,75 @@ +tableIndex = $tableIndex; + $this->table = $table; + $this->connection = $connection; + } + + /** + * Allows to clear the index which means the index will be excluded from tables index list. + * + * @return SchemaIndexDefinitionEventArgs + */ + public function setIndex(?Index $index = null) + { + $this->index = $index; + + return $this; + } + + /** @return Index|null */ + public function getIndex() + { + return $this->index; + } + + /** @return mixed[] */ + public function getTableIndex() + { + return $this->tableIndex; + } + + /** @return string */ + public function getTable() + { + return $this->table; + } + + /** @return Connection */ + public function getConnection() + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Event/TransactionBeginEventArgs.php b/vendor/doctrine/dbal/src/Event/TransactionBeginEventArgs.php new file mode 100644 index 0000000..be4ccdf --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/TransactionBeginEventArgs.php @@ -0,0 +1,10 @@ +connection = $connection; + } + + public function getConnection(): Connection + { + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Event/TransactionRollBackEventArgs.php b/vendor/doctrine/dbal/src/Event/TransactionRollBackEventArgs.php new file mode 100644 index 0000000..9e6e650 --- /dev/null +++ b/vendor/doctrine/dbal/src/Event/TransactionRollBackEventArgs.php @@ -0,0 +1,10 @@ +getMessage(); + } else { + $message = 'An exception occurred in the driver: ' . $driverException->getMessage(); + } + + parent::__construct($message, $driverException->getCode(), $driverException); + + $this->query = $query; + } + + /** + * {@inheritDoc} + */ + public function getSQLState() + { + $previous = $this->getPrevious(); + assert($previous instanceof TheDriverException); + + return $previous->getSQLState(); + } + + public function getQuery(): ?Query + { + return $this->query; + } +} diff --git a/vendor/doctrine/dbal/src/Exception/ForeignKeyConstraintViolationException.php b/vendor/doctrine/dbal/src/Exception/ForeignKeyConstraintViolationException.php new file mode 100644 index 0000000..f1a612b --- /dev/null +++ b/vendor/doctrine/dbal/src/Exception/ForeignKeyConstraintViolationException.php @@ -0,0 +1,12 @@ +|array */ + private array $originalParameters; + + /** @var array|array */ + private array $originalTypes; + + private int $originalParameterIndex = 0; + + /** @var list */ + private array $convertedSQL = []; + + /** @var list */ + private array $convertedParameters = []; + + /** @var array */ + private array $convertedTypes = []; + + /** + * @param array|array $parameters + * @param array|array $types + */ + public function __construct(array $parameters, array $types) + { + $this->originalParameters = $parameters; + $this->originalTypes = $types; + } + + public function acceptPositionalParameter(string $sql): void + { + $index = $this->originalParameterIndex; + + if (! array_key_exists($index, $this->originalParameters)) { + throw MissingPositionalParameter::new($index); + } + + $this->acceptParameter($index, $this->originalParameters[$index]); + + $this->originalParameterIndex++; + } + + public function acceptNamedParameter(string $sql): void + { + $name = substr($sql, 1); + + if (! array_key_exists($name, $this->originalParameters)) { + throw MissingNamedParameter::new($name); + } + + $this->acceptParameter($name, $this->originalParameters[$name]); + } + + public function acceptOther(string $sql): void + { + $this->convertedSQL[] = $sql; + } + + public function getSQL(): string + { + return implode('', $this->convertedSQL); + } + + /** @return list */ + public function getParameters(): array + { + return $this->convertedParameters; + } + + /** + * @param int|string $key + * @param mixed $value + */ + private function acceptParameter($key, $value): void + { + if (! isset($this->originalTypes[$key])) { + $this->convertedSQL[] = '?'; + $this->convertedParameters[] = $value; + + return; + } + + $type = $this->originalTypes[$key]; + + if ( + $type !== ArrayParameterType::INTEGER + && $type !== ArrayParameterType::STRING + && $type !== ArrayParameterType::ASCII + && $type !== ArrayParameterType::BINARY + ) { + $this->appendTypedParameter([$value], $type); + + return; + } + + if (count($value) === 0) { + $this->convertedSQL[] = 'NULL'; + + return; + } + + $this->appendTypedParameter($value, ArrayParameterType::toElementParameterType($type)); + } + + /** @return array */ + public function getTypes(): array + { + return $this->convertedTypes; + } + + /** + * @param list $values + * @param Type|int|string|null $type + */ + private function appendTypedParameter(array $values, $type): void + { + $this->convertedSQL[] = implode(', ', array_fill(0, count($values), '?')); + + $index = count($this->convertedParameters); + + foreach ($values as $value) { + $this->convertedParameters[] = $value; + $this->convertedTypes[$index] = $type; + + $index++; + } + } +} diff --git a/vendor/doctrine/dbal/src/FetchMode.php b/vendor/doctrine/dbal/src/FetchMode.php new file mode 100644 index 0000000..d80719f --- /dev/null +++ b/vendor/doctrine/dbal/src/FetchMode.php @@ -0,0 +1,20 @@ +getDriver() instanceof Driver\PDO\SQLite\Driver) { + throw new Exception('Cannot use TableGenerator with SQLite.'); + } + + $this->conn = DriverManager::getConnection( + $conn->getParams(), + $conn->getConfiguration(), + $conn->getEventManager(), + ); + + $this->generatorTableName = $generatorTableName; + } + + /** + * Generates the next unused value for the given sequence name. + * + * @param string $sequence + * + * @return int + * + * @throws Exception + */ + public function nextValue($sequence) + { + if (isset($this->sequences[$sequence])) { + $value = $this->sequences[$sequence]['value']; + $this->sequences[$sequence]['value']++; + if ($this->sequences[$sequence]['value'] >= $this->sequences[$sequence]['max']) { + unset($this->sequences[$sequence]); + } + + return $value; + } + + $this->conn->beginTransaction(); + + try { + $row = $this->conn->createQueryBuilder() + ->select('sequence_value', 'sequence_increment_by') + ->from($this->generatorTableName) + ->where('sequence_name = ?') + ->forUpdate() + ->setParameter(1, $sequence) + ->fetchAssociative(); + + if ($row !== false) { + $row = array_change_key_case($row, CASE_LOWER); + + $value = $row['sequence_value']; + $value++; + + assert(is_int($value)); + + if ($row['sequence_increment_by'] > 1) { + $this->sequences[$sequence] = [ + 'value' => $value, + 'max' => $row['sequence_value'] + $row['sequence_increment_by'], + ]; + } + + $sql = 'UPDATE ' . $this->generatorTableName . ' ' . + 'SET sequence_value = sequence_value + sequence_increment_by ' . + 'WHERE sequence_name = ? AND sequence_value = ?'; + $rows = $this->conn->executeStatement($sql, [$sequence, $row['sequence_value']]); + + if ($rows !== 1) { + throw new Exception('Race-condition detected while updating sequence. Aborting generation'); + } + } else { + $this->conn->insert( + $this->generatorTableName, + ['sequence_name' => $sequence, 'sequence_value' => 1, 'sequence_increment_by' => 1], + ); + $value = 1; + } + + $this->conn->commit(); + } catch (Throwable $e) { + $this->conn->rollBack(); + + throw new Exception( + 'Error occurred while generating ID with TableGenerator, aborted generation: ' . $e->getMessage(), + 0, + $e, + ); + } + + return $value; + } +} diff --git a/vendor/doctrine/dbal/src/Id/TableGeneratorSchemaVisitor.php b/vendor/doctrine/dbal/src/Id/TableGeneratorSchemaVisitor.php new file mode 100644 index 0000000..75c9fe9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Id/TableGeneratorSchemaVisitor.php @@ -0,0 +1,77 @@ +generatorTableName = $generatorTableName; + } + + /** + * {@inheritDoc} + */ + public function acceptSchema(Schema $schema) + { + $table = $schema->createTable($this->generatorTableName); + $table->addColumn('sequence_name', 'string'); + $table->addColumn('sequence_value', 'integer', ['default' => 1]); + $table->addColumn('sequence_increment_by', 'integer', ['default' => 1]); + } + + /** + * {@inheritDoc} + */ + public function acceptTable(Table $table) + { + } + + /** + * {@inheritDoc} + */ + public function acceptColumn(Table $table, Column $column) + { + } + + /** + * {@inheritDoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritDoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritDoc} + */ + public function acceptSequence(Sequence $sequence) + { + } +} diff --git a/vendor/doctrine/dbal/src/LockMode.php b/vendor/doctrine/dbal/src/LockMode.php new file mode 100644 index 0000000..1a7ed23 --- /dev/null +++ b/vendor/doctrine/dbal/src/LockMode.php @@ -0,0 +1,23 @@ +logger = $logger; + } + + public function __destruct() + { + $this->logger->info('Disconnecting'); + } + + public function prepare(string $sql): DriverStatement + { + return new Statement( + parent::prepare($sql), + $this->logger, + $sql, + ); + } + + public function query(string $sql): Result + { + $this->logger->debug('Executing query: {sql}', ['sql' => $sql]); + + return parent::query($sql); + } + + public function exec(string $sql): int + { + $this->logger->debug('Executing statement: {sql}', ['sql' => $sql]); + + return parent::exec($sql); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + $this->logger->debug('Beginning transaction'); + + return parent::beginTransaction(); + } + + /** + * {@inheritDoc} + */ + public function commit() + { + $this->logger->debug('Committing transaction'); + + return parent::commit(); + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + $this->logger->debug('Rolling back transaction'); + + return parent::rollBack(); + } +} diff --git a/vendor/doctrine/dbal/src/Logging/DebugStack.php b/vendor/doctrine/dbal/src/Logging/DebugStack.php new file mode 100644 index 0000000..1a970d0 --- /dev/null +++ b/vendor/doctrine/dbal/src/Logging/DebugStack.php @@ -0,0 +1,75 @@ +> + */ + public $queries = []; + + /** + * If Debug Stack is enabled (log queries) or not. + * + * @var bool + */ + public $enabled = true; + + /** @var float|null */ + public $start = null; + + /** @var int */ + public $currentQuery = 0; + + public function __construct() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4967', + 'DebugStack is deprecated.', + ); + } + + /** + * {@inheritDoc} + */ + public function startQuery($sql, ?array $params = null, ?array $types = null) + { + if (! $this->enabled) { + return; + } + + $this->start = microtime(true); + + $this->queries[++$this->currentQuery] = [ + 'sql' => $sql, + 'params' => $params, + 'types' => $types, + 'executionMS' => 0, + ]; + } + + /** + * {@inheritDoc} + */ + public function stopQuery() + { + if (! $this->enabled) { + return; + } + + $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start; + } +} diff --git a/vendor/doctrine/dbal/src/Logging/Driver.php b/vendor/doctrine/dbal/src/Logging/Driver.php new file mode 100644 index 0000000..32a5cd2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Logging/Driver.php @@ -0,0 +1,58 @@ +logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function connect( + #[SensitiveParameter] + array $params + ) { + $this->logger->info('Connecting with parameters {params}', ['params' => $this->maskPassword($params)]); + + return new Connection( + parent::connect($params), + $this->logger, + ); + } + + /** + * @param array $params Connection parameters + * + * @return array + */ + private function maskPassword( + #[SensitiveParameter] + array $params + ): array { + if (isset($params['password'])) { + $params['password'] = ''; + } + + if (isset($params['url'])) { + $params['url'] = ''; + } + + return $params; + } +} diff --git a/vendor/doctrine/dbal/src/Logging/LoggerChain.php b/vendor/doctrine/dbal/src/Logging/LoggerChain.php new file mode 100644 index 0000000..7a4eaa4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Logging/LoggerChain.php @@ -0,0 +1,48 @@ + */ + private iterable $loggers; + + /** @param iterable $loggers */ + public function __construct(iterable $loggers = []) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4967', + 'LoggerChain is deprecated', + ); + + $this->loggers = $loggers; + } + + /** + * {@inheritDoc} + */ + public function startQuery($sql, ?array $params = null, ?array $types = null) + { + foreach ($this->loggers as $logger) { + $logger->startQuery($sql, $params, $types); + } + } + + /** + * {@inheritDoc} + */ + public function stopQuery() + { + foreach ($this->loggers as $logger) { + $logger->stopQuery(); + } + } +} diff --git a/vendor/doctrine/dbal/src/Logging/Middleware.php b/vendor/doctrine/dbal/src/Logging/Middleware.php new file mode 100644 index 0000000..da0c1b9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Logging/Middleware.php @@ -0,0 +1,24 @@ +logger = $logger; + } + + public function wrap(DriverInterface $driver): DriverInterface + { + return new Driver($driver, $this->logger); + } +} diff --git a/vendor/doctrine/dbal/src/Logging/SQLLogger.php b/vendor/doctrine/dbal/src/Logging/SQLLogger.php new file mode 100644 index 0000000..dab4a3a --- /dev/null +++ b/vendor/doctrine/dbal/src/Logging/SQLLogger.php @@ -0,0 +1,32 @@ +|array|null $params Statement parameters + * @param array|array|null $types Parameter types + * + * @return void + */ + public function startQuery($sql, ?array $params = null, ?array $types = null); + + /** + * Marks the last started query as stopped. This can be used for timing of queries. + * + * @return void + */ + public function stopQuery(); +} diff --git a/vendor/doctrine/dbal/src/Logging/Statement.php b/vendor/doctrine/dbal/src/Logging/Statement.php new file mode 100644 index 0000000..039b93b --- /dev/null +++ b/vendor/doctrine/dbal/src/Logging/Statement.php @@ -0,0 +1,100 @@ +|array */ + private array $params = []; + + /** @var array|array */ + private array $types = []; + + /** @internal This statement can be only instantiated by its connection. */ + public function __construct(StatementInterface $statement, LoggerInterface $logger, string $sql) + { + parent::__construct($statement); + + $this->logger = $logger; + $this->sql = $sql; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see bindValue()} instead. + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindParam() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + $this->params[$param] = &$variable; + $this->types[$param] = $type; + + return parent::bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3)); + } + + /** + * {@inheritDoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + if (func_num_args() < 3) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5558', + 'Not passing $type to Statement::bindValue() is deprecated.' + . ' Pass the type corresponding to the parameter being bound.', + ); + } + + $this->params[$param] = $value; + $this->types[$param] = $type; + + return parent::bindValue($param, $value, $type); + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): ResultInterface + { + $this->logger->debug('Executing statement: {sql} (parameters: {params}, types: {types})', [ + 'sql' => $this->sql, + 'params' => $params ?? $this->params, + 'types' => $this->types, + ]); + + return parent::execute($params); + } +} diff --git a/vendor/doctrine/dbal/src/ParameterType.php b/vendor/doctrine/dbal/src/ParameterType.php new file mode 100644 index 0000000..77917e8 --- /dev/null +++ b/vendor/doctrine/dbal/src/ParameterType.php @@ -0,0 +1,57 @@ + 0) { + $query .= sprintf(' OFFSET %d', $offset); + } + } elseif ($offset > 0) { + // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible + $query .= sprintf(' LIMIT 18446744073709551615 OFFSET %d', $offset); + } + + return $query; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see quoteIdentifier()} to quote identifiers instead. + */ + public function getIdentifierQuoteCharacter() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5388', + 'AbstractMySQLPlatform::getIdentifierQuoteCharacter() is deprecated. Use quoteIdentifier() instead.', + ); + + return '`'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'RLIKE'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos === false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + return sprintf('CONCAT(%s)', implode(', ', func_get_args())); + } + + /** + * {@inheritDoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB'; + + return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; + } + + public function getCurrentDatabaseExpression(): string + { + return 'DATABASE()'; + } + + /** + * {@inheritDoc} + */ + public function getLengthExpression($column) + { + return 'CHAR_LENGTH(' . $column . ')'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListDatabasesSQL() + { + return 'SHOW DATABASES'; + } + + /** + * @deprecated + * + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + return 'SHOW INDEX FROM ' . $table; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + * + * Two approaches to listing the table indexes. The information_schema is + * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table". + */ + public function getListTableIndexesSQL($table, $database = null) + { + if ($database !== null) { + return 'SELECT NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name,' . + ' SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type' . + ' FROM information_schema.STATISTICS WHERE TABLE_NAME = ' . $this->quoteStringLiteral($table) . + ' AND TABLE_SCHEMA = ' . $this->quoteStringLiteral($database) . + ' ORDER BY SEQ_IN_INDEX ASC'; + } + + return 'SHOW INDEX FROM ' . $table; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListViewsSQL($database) + { + return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $this->quoteStringLiteral($database); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * @param string $table + * @param string|null $database + * + * @return string + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition + // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions + // caused by https://bugs.mysql.com/bug.php?id=81347 + return 'SELECT k.CONSTRAINT_NAME, k.COLUMN_NAME, k.REFERENCED_TABLE_NAME, ' . + 'k.REFERENCED_COLUMN_NAME /*!50116 , c.UPDATE_RULE, c.DELETE_RULE */ ' . + 'FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k /*!50116 ' . + 'INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS c ON ' . + 'c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND ' . + 'c.TABLE_NAME = k.TABLE_NAME */ ' . + 'WHERE k.TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' ' . + 'AND k.TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' /*!50116 ' . + 'AND c.CONSTRAINT_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' */' . + 'ORDER BY k.ORDINAL_POSITION'; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default string column length on MySQL is deprecated' + . ', specify the length explicitly.', + ); + } + + return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritDoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default binary column length on MySQL is deprecated' + . ', specify the length explicitly.', + ); + } + + return $fixed + ? 'BINARY(' . ($length > 0 ? $length : 255) . ')' + : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')'; + } + + /** + * Gets the SQL snippet used to declare a CLOB column type. + * TINYTEXT : 2 ^ 8 - 1 = 255 + * TEXT : 2 ^ 16 - 1 = 65535 + * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215 + * LONGTEXT : 2 ^ 32 - 1 = 4294967295 + * + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $column) + { + if (! empty($column['length']) && is_numeric($column['length'])) { + $length = $column['length']; + + if ($length <= static::LENGTH_LIMIT_TINYTEXT) { + return 'TINYTEXT'; + } + + if ($length <= static::LENGTH_LIMIT_TEXT) { + return 'TEXT'; + } + + if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) { + return 'MEDIUMTEXT'; + } + } + + return 'LONGTEXT'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + if (isset($column['version']) && $column['version'] === true) { + return 'TIMESTAMP'; + } + + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $column) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $column) + { + return 'TINYINT(1)'; + } + + /** + * {@inheritDoc} + * + * @deprecated + * + * MySQL prefers "autoincrement" identity columns since sequences can only + * be emulated with a table. + */ + public function prefersIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/1519', + 'AbstractMySQLPlatform::prefersIdentityColumns() is deprecated.', + ); + + return true; + } + + /** + * {@inheritDoc} + * + * MySQL supports this through AUTO_INCREMENT columns. + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsInlineColumnComments() + { + return true; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return 'SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ' . + 'COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, ' . + 'CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ' . + 'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) . + ' AND TABLE_NAME = ' . $this->quoteStringLiteral($table) . + ' ORDER BY ORDINAL_POSITION ASC'; + } + + /** + * @deprecated Use {@see getColumnTypeSQLSnippet()} instead. + * + * The SQL snippets required to elucidate a column type + * + * Returns an array of the form [column type SELECT snippet, additional JOIN statement snippet] + * + * @return array{string, string} + */ + public function getColumnTypeSQLSnippets(string $tableAlias = 'c'): array + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6202', + 'AbstractMySQLPlatform::getColumnTypeSQLSnippets() is deprecated. ' + . 'Use AbstractMySQLPlatform::getColumnTypeSQLSnippet() instead.', + ); + + return [$this->getColumnTypeSQLSnippet(...func_get_args()), '']; + } + + /** + * The SQL snippet required to elucidate a column type + * + * Returns a column type SELECT snippet string + */ + public function getColumnTypeSQLSnippet(string $tableAlias = 'c', ?string $databaseName = null): string + { + return $tableAlias . '.COLUMN_TYPE'; + } + + /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ + public function getListTableMetadataSQL(string $table, ?string $database = null): string + { + return sprintf( + <<<'SQL' +SELECT t.ENGINE, + t.AUTO_INCREMENT, + t.TABLE_COMMENT, + t.CREATE_OPTIONS, + t.TABLE_COLLATION, + ccsa.CHARACTER_SET_NAME +FROM information_schema.TABLES t + INNER JOIN information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` ccsa + ON ccsa.COLLATION_NAME = t.TABLE_COLLATION +WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = %s AND TABLE_NAME = %s +SQL + , + $this->getDatabaseNameSQL($database), + $this->quoteStringLiteral($table), + ); + } + + /** + * {@inheritDoc} + */ + public function getCreateTablesSQL(array $tables): array + { + $sql = []; + + foreach ($tables as $table) { + $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table)); + } + + foreach ($tables as $table) { + if (! $table->hasOption('engine') || $this->engineSupportsForeignKeys($table->getOption('engine'))) { + foreach ($table->getForeignKeys() as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL( + $foreignKey, + $table->getQuotedName($this), + ); + } + } elseif (count($table->getForeignKeys()) > 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5414', + 'Relying on the DBAL not generating DDL for foreign keys on MySQL engines' + . ' other than InnoDB is deprecated.' + . ' Define foreign key constraints only if they are necessary.', + ); + } + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $constraintName => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); + } + } + + // add all indexes + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $indexName => $definition) { + $queryFields .= ', ' . $this->getIndexDeclarationSQL($indexName, $definition); + } + } + + // attach all primary keys + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE '; + + if (! empty($options['temporary'])) { + $query .= 'TEMPORARY '; + } + + $query .= 'TABLE ' . $name . ' (' . $queryFields . ') '; + $query .= $this->buildTableOptions($options); + $query .= $this->buildPartitionOptions($options); + + $sql = [$query]; + + // Propagate foreign key constraints only for InnoDB. + if (isset($options['foreignKeys'])) { + if (! isset($options['engine']) || $this->engineSupportsForeignKeys($options['engine'])) { + foreach ($options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $name); + } + } elseif (count($options['foreignKeys']) > 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5414', + 'Relying on the DBAL not generating DDL for foreign keys on MySQL engines' + . ' other than InnoDB is deprecated.' + . ' Define foreign key constraints only if they are necessary.', + ); + } + } + + return $sql; + } + + public function createSelectSQLBuilder(): SelectSQLBuilder + { + return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null); + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getDefaultValueDeclarationSQL($column) + { + // Unset the default value if the given column definition does not allow default values. + if ($column['type'] instanceof TextType || $column['type'] instanceof BlobType) { + $column['default'] = null; + } + + return parent::getDefaultValueDeclarationSQL($column); + } + + /** + * Build SQL for table options + * + * @param mixed[] $options + */ + private function buildTableOptions(array $options): string + { + if (isset($options['table_options'])) { + return $options['table_options']; + } + + $tableOptions = []; + + // Charset + if (! isset($options['charset'])) { + $options['charset'] = 'utf8'; + } + + $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']); + + if (isset($options['collate'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5214', + 'The "collate" option is deprecated in favor of "collation" and will be removed in 4.0.', + ); + $options['collation'] = $options['collate']; + } + + // Collation + if (! isset($options['collation'])) { + $options['collation'] = $options['charset'] . '_unicode_ci'; + } + + $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collation']); + + // Engine + if (! isset($options['engine'])) { + $options['engine'] = 'InnoDB'; + } + + $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); + + // Auto increment + if (isset($options['auto_increment'])) { + $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); + } + + // Comment + if (isset($options['comment'])) { + $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment'])); + } + + // Row format + if (isset($options['row_format'])) { + $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); + } + + return implode(' ', $tableOptions); + } + + /** + * Build SQL for partition options. + * + * @param mixed[] $options + */ + private function buildPartitionOptions(array $options): string + { + return isset($options['partition_options']) + ? ' ' . $options['partition_options'] + : ''; + } + + private function engineSupportsForeignKeys(string $engine): bool + { + return strcasecmp(trim($engine), 'InnoDB') === 0; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $columnSql = []; + $queryParts = []; + $newName = $diff->getNewName(); + + if ($newName !== false) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + 'Generation of SQL that renames a table using %s is deprecated. Use getRenameTableSQL() instead.', + __METHOD__, + ); + + $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this); + } + + foreach ($diff->getAddedColumns() as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnProperties = array_merge($column->toArray(), [ + 'comment' => $this->getColumnComment($column), + ]); + + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL( + $column->getQuotedName($this), + $columnProperties, + ); + } + + foreach ($diff->getDroppedColumns() as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP ' . $column->getQuotedName($this); + } + + foreach ($diff->getModifiedColumns() as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $newColumn = $columnDiff->getNewColumn(); + + $newColumnProperties = array_merge($newColumn->toArray(), [ + 'comment' => $this->getColumnComment($newColumn), + ]); + + $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); + + $queryParts[] = 'CHANGE ' . $oldColumn->getQuotedName($this) . ' ' + . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $newColumnProperties); + } + + foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $columnProperties = array_merge($column->toArray(), [ + 'comment' => $this->getColumnComment($column), + ]); + + $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); + } + + $addedIndexes = $this->indexAssetsByLowerCaseName($diff->getAddedIndexes()); + $modifiedIndexes = $this->indexAssetsByLowerCaseName($diff->getModifiedIndexes()); + $diffModified = false; + + if (isset($addedIndexes['primary'])) { + $keyColumns = array_unique(array_values($addedIndexes['primary']->getColumns())); + $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; + unset($addedIndexes['primary']); + $diffModified = true; + } elseif (isset($modifiedIndexes['primary'])) { + $addedColumns = $this->indexAssetsByLowerCaseName($diff->getAddedColumns()); + + // Necessary in case the new primary key includes a new auto_increment column + foreach ($modifiedIndexes['primary']->getColumns() as $columnName) { + if (isset($addedColumns[$columnName]) && $addedColumns[$columnName]->getAutoincrement()) { + $keyColumns = array_unique(array_values($modifiedIndexes['primary']->getColumns())); + $queryParts[] = 'DROP PRIMARY KEY'; + $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; + unset($modifiedIndexes['primary']); + $diffModified = true; + break; + } + } + } + + if ($diffModified) { + $diff = new TableDiff( + $diff->name, + $diff->getAddedColumns(), + $diff->getModifiedColumns(), + $diff->getDroppedColumns(), + array_values($addedIndexes), + array_values($modifiedIndexes), + $diff->getDroppedIndexes(), + $diff->getOldTable(), + $diff->getAddedForeignKeys(), + $diff->getModifiedForeignKeys(), + $diff->getDroppedForeignKeys(), + $diff->getRenamedColumns(), + $diff->getRenamedIndexes(), + ); + } + + $sql = []; + $tableSql = []; + + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this) . ' ' + . implode(', ', $queryParts); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff), + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $sql = []; + + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + + foreach ($diff->getModifiedIndexes() as $changedIndex) { + $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex)); + } + + foreach ($diff->getDroppedIndexes() as $droppedIndex) { + $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $droppedIndex)); + + foreach ($diff->getAddedIndexes() as $addedIndex) { + if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { + continue; + } + + $indexClause = 'INDEX ' . $addedIndex->getName(); + + if ($addedIndex->isPrimary()) { + $indexClause = 'PRIMARY KEY'; + } elseif ($addedIndex->isUnique()) { + $indexClause = 'UNIQUE INDEX ' . $addedIndex->getName(); + } + + $query = 'ALTER TABLE ' . $tableNameSQL . ' DROP INDEX ' . $droppedIndex->getName() . ', '; + $query .= 'ADD ' . $indexClause; + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addedIndex) . ')'; + + $sql[] = $query; + + $diff->unsetAddedIndex($addedIndex); + $diff->unsetDroppedIndex($droppedIndex); + + break; + } + } + + $engine = 'INNODB'; + + $table = $diff->getOldTable(); + + if ($table !== null && $table->hasOption('engine')) { + $engine = strtoupper(trim($table->getOption('engine'))); + } + + // Suppress foreign key constraint propagation on non-supporting engines. + if ($engine !== 'INNODB') { + $diff->addedForeignKeys = []; + $diff->changedForeignKeys = []; + $diff->removedForeignKeys = []; + } + + $sql = array_merge( + $sql, + $this->getPreAlterTableAlterIndexForeignKeySQL($diff), + parent::getPreAlterTableIndexForeignKeySQL($diff), + $this->getPreAlterTableRenameIndexForeignKeySQL($diff), + ); + + return $sql; + } + + /** + * @return string[] + * + * @throws Exception + */ + private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index): array + { + if (! $index->isPrimary()) { + return []; + } + + $table = $diff->getOldTable(); + + if ($table === null) { + return []; + } + + $sql = []; + + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + + // Dropping primary keys requires to unset autoincrement attribute on the particular column first. + foreach ($index->getColumns() as $columnName) { + if (! $table->hasColumn($columnName)) { + continue; + } + + $column = $table->getColumn($columnName); + + if ($column->getAutoincrement() !== true) { + continue; + } + + $column->setAutoincrement(false); + + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + + // original autoincrement information might be needed later on by other parts of the table alteration + $column->setAutoincrement(true); + } + + return $sql; + } + + /** + * @param TableDiff $diff The table diff to gather the SQL for. + * + * @return string[] + * + * @throws Exception + */ + private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff): array + { + $table = $diff->getOldTable(); + + if ($table === null) { + return []; + } + + $primaryKey = $table->getPrimaryKey(); + + if ($primaryKey === null) { + return []; + } + + $primaryKeyColumns = []; + + foreach ($primaryKey->getColumns() as $columnName) { + if (! $table->hasColumn($columnName)) { + continue; + } + + $primaryKeyColumns[] = $table->getColumn($columnName); + } + + if (count($primaryKeyColumns) === 0) { + return []; + } + + $sql = []; + + $tableNameSQL = $table->getQuotedName($this); + + foreach ($diff->getModifiedIndexes() as $changedIndex) { + // Changed primary key + if (! $changedIndex->isPrimary()) { + continue; + } + + foreach ($primaryKeyColumns as $column) { + // Check if an autoincrement column was dropped from the primary key. + if (! $column->getAutoincrement() || in_array($column->getName(), $changedIndex->getColumns(), true)) { + continue; + } + + // The autoincrement attribute needs to be removed from the dropped column + // before we can drop and recreate the primary key. + $column->setAutoincrement(false); + + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + + // Restore the autoincrement attribute as it might be needed later on + // by other parts of the table alteration. + $column->setAutoincrement(true); + } + } + + return $sql; + } + + /** + * @param TableDiff $diff The table diff to gather the SQL for. + * + * @return string[] + */ + protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) + { + $sql = []; + + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + + foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { + if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) { + continue; + } + + $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); + } + + return $sql; + } + + /** + * Returns the remaining foreign key constraints that require one of the renamed indexes. + * + * "Remaining" here refers to the diff between the foreign keys currently defined in the associated + * table and the foreign keys to be removed. + * + * @param TableDiff $diff The table diff to evaluate. + * + * @return ForeignKeyConstraint[] + */ + private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff): array + { + if (count($diff->getRenamedIndexes()) === 0) { + return []; + } + + $table = $diff->getOldTable(); + + if ($table === null) { + return []; + } + + $foreignKeys = []; + /** @var ForeignKeyConstraint[] $remainingForeignKeys */ + $remainingForeignKeys = array_diff_key( + $table->getForeignKeys(), + $diff->getDroppedForeignKeys(), + ); + + foreach ($remainingForeignKeys as $foreignKey) { + foreach ($diff->getRenamedIndexes() as $index) { + if ($foreignKey->intersectsIndexColumns($index)) { + $foreignKeys[] = $foreignKey; + + break; + } + } + } + + return $foreignKeys; + } + + /** + * {@inheritDoc} + */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + return array_merge( + parent::getPostAlterTableIndexForeignKeySQL($diff), + $this->getPostAlterTableRenameIndexForeignKeySQL($diff), + ); + } + + /** + * @param TableDiff $diff The table diff to gather the SQL for. + * + * @return string[] + */ + protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) + { + $sql = []; + $newName = $diff->getNewName(); + + if ($newName !== false) { + $tableNameSQL = $newName->getQuotedName($this); + } else { + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + } + + foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { + if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) { + continue; + } + + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } elseif ($index->hasFlag('fulltext')) { + $type .= 'FULLTEXT '; + } elseif ($index->hasFlag('spatial')) { + $type .= 'SPATIAL '; + } + + return $type; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $column) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $column) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $column) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getFloatDeclarationSQL(array $column) + { + return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($column); + } + + /** + * {@inheritDoc} + */ + public function getDecimalTypeDeclarationSQL(array $column) + { + return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column); + } + + /** + * Get unsigned declaration for a column. + * + * @param mixed[] $columnDef + */ + private function getUnsignedDeclaration(array $columnDef): string + { + return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : ''; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $column) + { + $autoinc = ''; + if (! empty($column['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT'; + } + + return $this->getUnsignedDeclaration($column) . $autoinc; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getColumnCharsetDeclarationSQL($charset) + { + return 'CHARACTER SET ' . $charset; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + if ($foreignKey->hasOption('match')) { + $query .= ' MATCH ' . $foreignKey->getOption('match'); + } + + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $indexName = $index->getQuotedName($this); + } elseif (is_string($index)) { + $indexName = $index; + } else { + throw new InvalidArgumentException( + __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', + ); + } + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } elseif (! is_string($table)) { + throw new InvalidArgumentException( + __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', + ); + } + + if ($index instanceof Index && $index->isPrimary()) { + // MySQL primary keys are always named "PRIMARY", + // so we cannot use them in statements because of them being keyword. + return $this->getDropPrimaryKeySQL($table); + } + + return 'DROP INDEX ' . $indexName . ' ON ' . $table; + } + + /** + * @param string $table + * + * @return string + */ + protected function getDropPrimaryKeySQL($table) + { + return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; + } + + /** + * The `ALTER TABLE ... DROP CONSTRAINT` syntax is only available as of MySQL 8.0.19. + * + * @link https://dev.mysql.com/doc/refman/8.0/en/alter-table.html + */ + public function getDropUniqueConstraintSQL(string $name, string $tableName): string + { + return $this->getDropIndexSQL($name, $tableName); + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4749', + 'AbstractMySQLPlatform::getName() is deprecated. Identify platforms by their class.', + ); + + return 'mysql'; + } + + /** + * {@inheritDoc} + */ + public function getReadLockSQL() + { + return 'LOCK IN SHARE MODE'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = [ + 'bigint' => Types::BIGINT, + 'binary' => Types::BINARY, + 'blob' => Types::BLOB, + 'char' => Types::STRING, + 'date' => Types::DATE_MUTABLE, + 'datetime' => Types::DATETIME_MUTABLE, + 'decimal' => Types::DECIMAL, + 'double' => Types::FLOAT, + 'float' => Types::FLOAT, + 'int' => Types::INTEGER, + 'integer' => Types::INTEGER, + 'longblob' => Types::BLOB, + 'longtext' => Types::TEXT, + 'mediumblob' => Types::BLOB, + 'mediumint' => Types::INTEGER, + 'mediumtext' => Types::TEXT, + 'numeric' => Types::DECIMAL, + 'real' => Types::FLOAT, + 'set' => Types::SIMPLE_ARRAY, + 'smallint' => Types::SMALLINT, + 'string' => Types::STRING, + 'text' => Types::TEXT, + 'time' => Types::TIME_MUTABLE, + 'timestamp' => Types::DATETIME_MUTABLE, + 'tinyblob' => Types::BLOB, + 'tinyint' => Types::BOOLEAN, + 'tinytext' => Types::TEXT, + 'varbinary' => Types::BINARY, + 'varchar' => Types::STRING, + 'year' => Types::DATE_MUTABLE, + ]; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getVarcharMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'AbstractMySQLPlatform::getVarcharMaxLength() is deprecated.', + ); + + return 65535; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getBinaryMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'AbstractMySQLPlatform::getBinaryMaxLength() is deprecated.', + ); + + return 65535; + } + + /** + * {@inheritDoc} + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'AbstractMySQLPlatform::getReservedKeywordsClass() is deprecated,' + . ' use AbstractMySQLPlatform::createReservedKeywordsList() instead.', + ); + + return Keywords\MySQLKeywords::class; + } + + /** + * {@inheritDoc} + * + * MySQL commits a transaction implicitly when DROP TABLE is executed, however not + * if DROP TEMPORARY TABLE is executed. + */ + public function getDropTemporaryTableSQL($table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } elseif (! is_string($table)) { + throw new InvalidArgumentException( + __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', + ); + } + + return 'DROP TEMPORARY TABLE ' . $table; + } + + /** + * Gets the SQL Snippet used to declare a BLOB column type. + * TINYBLOB : 2 ^ 8 - 1 = 255 + * BLOB : 2 ^ 16 - 1 = 65535 + * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215 + * LONGBLOB : 2 ^ 32 - 1 = 4294967295 + * + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $column) + { + if (! empty($column['length']) && is_numeric($column['length'])) { + $length = $column['length']; + + if ($length <= static::LENGTH_LIMIT_TINYBLOB) { + return 'TINYBLOB'; + } + + if ($length <= static::LENGTH_LIMIT_BLOB) { + return 'BLOB'; + } + + if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) { + return 'MEDIUMBLOB'; + } + } + + return 'LONGBLOB'; + } + + /** + * {@inheritDoc} + */ + public function quoteStringLiteral($str) + { + $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped + + return parent::quoteStringLiteral($str); + } + + /** + * {@inheritDoc} + */ + public function getDefaultTransactionIsolationLevel() + { + return TransactionIsolationLevel::REPEATABLE_READ; + } + + public function supportsColumnLengthIndexes(): bool + { + return true; + } + + /** @deprecated Will be removed without replacement. */ + protected function getDatabaseNameSQL(?string $databaseName): string + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6215', + '%s is deprecated without replacement.', + __METHOD__, + ); + + if ($databaseName !== null) { + return $this->quoteStringLiteral($databaseName); + } + + return $this->getCurrentDatabaseExpression(); + } + + public function createSchemaManager(Connection $connection): MySQLSchemaManager + { + return new MySQLSchemaManager($connection, $this); + } + + /** + * @param list $assets + * + * @return array + * + * @template T of AbstractAsset + */ + private function indexAssetsByLowerCaseName(array $assets): array + { + $result = []; + + foreach ($assets as $asset) { + $result[strtolower($asset->getName())] = $asset; + } + + return $result; + } + + public function fetchTableOptionsByTable(bool $includeTableName): string + { + $sql = <<<'SQL' + SELECT t.TABLE_NAME, + t.ENGINE, + t.AUTO_INCREMENT, + t.TABLE_COMMENT, + t.CREATE_OPTIONS, + t.TABLE_COLLATION, + ccsa.CHARACTER_SET_NAME + FROM information_schema.TABLES t + INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa + ON ccsa.COLLATION_NAME = t.TABLE_COLLATION +SQL; + + $conditions = ['t.TABLE_SCHEMA = ?']; + + if ($includeTableName) { + $conditions[] = 't.TABLE_NAME = ?'; + } + + $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'"; + + return $sql . ' WHERE ' . implode(' AND ', $conditions); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php b/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php new file mode 100644 index 0000000..928a5a0 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php @@ -0,0 +1,4727 @@ +disableTypeComments = $value; + } + + /** + * Sets the EventManager used by the Platform. + * + * @deprecated + * + * @return void + */ + public function setEventManager(EventManager $eventManager) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + '%s is deprecated.', + __METHOD__, + ); + + $this->_eventManager = $eventManager; + } + + /** + * Gets the EventManager used by the Platform. + * + * @deprecated + * + * @return EventManager|null + */ + public function getEventManager() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + '%s is deprecated.', + __METHOD__, + ); + + return $this->_eventManager; + } + + /** + * Returns the SQL snippet that declares a boolean column. + * + * @param mixed[] $column + * + * @return string + */ + abstract public function getBooleanTypeDeclarationSQL(array $column); + + /** + * Returns the SQL snippet that declares a 4 byte integer column. + * + * @param mixed[] $column + * + * @return string + */ + abstract public function getIntegerTypeDeclarationSQL(array $column); + + /** + * Returns the SQL snippet that declares an 8 byte integer column. + * + * @param mixed[] $column + * + * @return string + */ + abstract public function getBigIntTypeDeclarationSQL(array $column); + + /** + * Returns the SQL snippet that declares a 2 byte integer column. + * + * @param mixed[] $column + * + * @return string + */ + abstract public function getSmallIntTypeDeclarationSQL(array $column); + + /** + * Returns the SQL snippet that declares common properties of an integer column. + * + * @param mixed[] $column + * + * @return string + */ + abstract protected function _getCommonIntegerTypeDeclarationSQL(array $column); + + /** + * Lazy load Doctrine Type Mappings. + * + * @return void + */ + abstract protected function initializeDoctrineTypeMappings(); + + /** + * Initializes Doctrine Type Mappings with the platform defaults + * and with all additional type mappings. + */ + private function initializeAllDoctrineTypeMappings(): void + { + $this->initializeDoctrineTypeMappings(); + + foreach (Type::getTypesMap() as $typeName => $className) { + foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { + $dbType = strtolower($dbType); + $this->doctrineTypeMapping[$dbType] = $typeName; + } + } + } + + /** + * Returns the SQL snippet used to declare a column that can + * store characters in the ASCII character set + * + * @param mixed[] $column + */ + public function getAsciiStringTypeDeclarationSQL(array $column): string + { + return $this->getStringTypeDeclarationSQL($column); + } + + /** + * Returns the SQL snippet used to declare a VARCHAR column type. + * + * @deprecated Use {@link getStringTypeDeclarationSQL()} instead. + * + * @param mixed[] $column + * + * @return string + */ + public function getVarcharTypeDeclarationSQL(array $column) + { + if (isset($column['length'])) { + $lengthOmitted = false; + } else { + $column['length'] = $this->getVarcharDefaultLength(); + $lengthOmitted = true; + } + + $fixed = $column['fixed'] ?? false; + + $maxLength = $fixed + ? $this->getCharMaxLength() + : $this->getVarcharMaxLength(); + + if ($column['length'] > $maxLength) { + return $this->getClobTypeDeclarationSQL($column); + } + + return $this->getVarcharTypeDeclarationSQLSnippet($column['length'], $fixed, $lengthOmitted); + } + + /** + * Returns the SQL snippet used to declare a string column type. + * + * @param mixed[] $column + * + * @return string + */ + public function getStringTypeDeclarationSQL(array $column) + { + return $this->getVarcharTypeDeclarationSQL($column); + } + + /** + * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. + * + * @param mixed[] $column The column definition. + * + * @return string + */ + public function getBinaryTypeDeclarationSQL(array $column) + { + if (isset($column['length'])) { + $lengthOmitted = false; + } else { + $column['length'] = $this->getBinaryDefaultLength(); + $lengthOmitted = true; + } + + $fixed = $column['fixed'] ?? false; + + $maxLength = $this->getBinaryMaxLength(); + + if ($column['length'] > $maxLength) { + if ($maxLength > 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3187', + 'Binary column length %d is greater than supported by the platform (%d).' + . ' Reduce the column length or use a BLOB column instead.', + $column['length'], + $maxLength, + ); + } + + return $this->getBlobTypeDeclarationSQL($column); + } + + return $this->getBinaryTypeDeclarationSQLSnippet($column['length'], $fixed, $lengthOmitted); + } + + /** + * Returns the SQL snippet to declare a GUID/UUID column. + * + * By default this maps directly to a CHAR(36) and only maps to more + * special datatypes when the underlying databases support this datatype. + * + * @param mixed[] $column + * + * @return string + */ + public function getGuidTypeDeclarationSQL(array $column) + { + $column['length'] = 36; + $column['fixed'] = true; + + return $this->getStringTypeDeclarationSQL($column); + } + + /** + * Returns the SQL snippet to declare a JSON column. + * + * By default this maps directly to a CLOB and only maps to more + * special datatypes when the underlying databases support this datatype. + * + * @param mixed[] $column + * + * @return string + */ + public function getJsonTypeDeclarationSQL(array $column) + { + return $this->getClobTypeDeclarationSQL($column); + } + + /** + * @param int|false $length + * @param bool $fixed + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + throw Exception::notSupported('VARCHARs not supported by Platform.'); + } + + /** + * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. + * + * @param int|false $length The length of the column. + * @param bool $fixed Whether the column length is fixed. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + throw Exception::notSupported('BINARY/VARBINARY column types are not supported by this platform.'); + } + + /** + * Returns the SQL snippet used to declare a CLOB column type. + * + * @param mixed[] $column + * + * @return string + */ + abstract public function getClobTypeDeclarationSQL(array $column); + + /** + * Returns the SQL Snippet used to declare a BLOB column type. + * + * @param mixed[] $column + * + * @return string + */ + abstract public function getBlobTypeDeclarationSQL(array $column); + + /** + * Gets the name of the platform. + * + * @deprecated Identify platforms by their class. + * + * @return string + */ + abstract public function getName(); + + /** + * Registers a doctrine type to be used in conjunction with a column type of this platform. + * + * @param string $dbType + * @param string $doctrineType + * + * @return void + * + * @throws Exception If the type is not found. + */ + public function registerDoctrineTypeMapping($dbType, $doctrineType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + if (! Types\Type::hasType($doctrineType)) { + throw Exception::typeNotFound($doctrineType); + } + + $dbType = strtolower($dbType); + $this->doctrineTypeMapping[$dbType] = $doctrineType; + + $doctrineType = Type::getType($doctrineType); + + if (! $doctrineType->requiresSQLCommentHint($this)) { + return; + } + + $this->markDoctrineTypeCommented($doctrineType); + } + + /** + * Gets the Doctrine type that is mapped for the given database column type. + * + * @param string $dbType + * + * @return string + * + * @throws Exception + */ + public function getDoctrineTypeMapping($dbType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + $dbType = strtolower($dbType); + + if (! isset($this->doctrineTypeMapping[$dbType])) { + throw new Exception( + 'Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.', + ); + } + + return $this->doctrineTypeMapping[$dbType]; + } + + /** + * Checks if a database type is currently supported by this platform. + * + * @param string $dbType + * + * @return bool + */ + public function hasDoctrineTypeMappingFor($dbType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + $dbType = strtolower($dbType); + + return isset($this->doctrineTypeMapping[$dbType]); + } + + /** + * Initializes the Doctrine Type comments instance variable for in_array() checks. + * + * @deprecated This API will be removed in Doctrine DBAL 4.0. + * + * @return void + */ + protected function initializeCommentedDoctrineTypes() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5058', + '%s is deprecated and will be removed in Doctrine DBAL 4.0.', + __METHOD__, + ); + + $this->doctrineTypeComments = []; + + foreach (Type::getTypesMap() as $typeName => $className) { + $type = Type::getType($typeName); + + if (! $type->requiresSQLCommentHint($this)) { + continue; + } + + $this->doctrineTypeComments[] = $typeName; + } + } + + /** + * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type? + * + * @deprecated Use {@link Type::requiresSQLCommentHint()} instead. + * + * @return bool + */ + public function isCommentedDoctrineType(Type $doctrineType) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5058', + '%s is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', + __METHOD__, + ); + + if ($this->doctrineTypeComments === null) { + $this->initializeCommentedDoctrineTypes(); + } + + return $doctrineType->requiresSQLCommentHint($this); + } + + /** + * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements. + * + * @param string|Type $doctrineType + * + * @return void + */ + public function markDoctrineTypeCommented($doctrineType) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5058', + '%s is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', + __METHOD__, + ); + + if ($this->doctrineTypeComments === null) { + $this->initializeCommentedDoctrineTypes(); + } + + assert(is_array($this->doctrineTypeComments)); + + $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType; + } + + /** + * Gets the comment to append to a column comment that helps parsing this type in reverse engineering. + * + * @deprecated This method will be removed without replacement. + * + * @return string + */ + public function getDoctrineTypeComment(Type $doctrineType) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5107', + '%s is deprecated and will be removed in Doctrine DBAL 4.0.', + __METHOD__, + ); + + return '(DC2Type:' . $doctrineType->getName() . ')'; + } + + /** + * Gets the comment of a passed column modified by potential doctrine type comment hints. + * + * @deprecated This method will be removed without replacement. + * + * @return string|null + */ + protected function getColumnComment(Column $column) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5107', + '%s is deprecated and will be removed in Doctrine DBAL 4.0.', + __METHOD__, + ); + + $comment = $column->getComment(); + + if (! $this->disableTypeComments && $column->getType()->requiresSQLCommentHint($this)) { + $comment .= $this->getDoctrineTypeComment($column->getType()); + } + + return $comment; + } + + /** + * Gets the character used for identifier quoting. + * + * @deprecated Use {@see quoteIdentifier()} to quote identifiers instead. + * + * @return string + */ + public function getIdentifierQuoteCharacter() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5388', + 'AbstractPlatform::getIdentifierQuoteCharacter() is deprecated. Use quoteIdentifier() instead.', + ); + + return '"'; + } + + /** + * Gets the string portion that starts an SQL comment. + * + * @deprecated + * + * @return string + */ + public function getSqlCommentStartString() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getSqlCommentStartString() is deprecated.', + ); + + return '--'; + } + + /** + * Gets the string portion that ends an SQL comment. + * + * @deprecated + * + * @return string + */ + public function getSqlCommentEndString() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getSqlCommentEndString() is deprecated.', + ); + + return "\n"; + } + + /** + * Gets the maximum length of a char column. + * + * @deprecated + */ + public function getCharMaxLength(): int + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'AbstractPlatform::getCharMaxLength() is deprecated.', + ); + + return $this->getVarcharMaxLength(); + } + + /** + * Gets the maximum length of a varchar column. + * + * @deprecated + * + * @return int + */ + public function getVarcharMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'AbstractPlatform::getVarcharMaxLength() is deprecated.', + ); + + return 4000; + } + + /** + * Gets the default length of a varchar column. + * + * @deprecated + * + * @return int + */ + public function getVarcharDefaultLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default varchar column length is deprecated, specify the length explicitly.', + ); + + return 255; + } + + /** + * Gets the maximum length of a binary column. + * + * @deprecated + * + * @return int + */ + public function getBinaryMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'AbstractPlatform::getBinaryMaxLength() is deprecated.', + ); + + return 4000; + } + + /** + * Gets the default length of a binary column. + * + * @deprecated + * + * @return int + */ + public function getBinaryDefaultLength() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default binary column length is deprecated, specify the length explicitly.', + ); + + return 255; + } + + /** + * Gets all SQL wildcard characters of the platform. + * + * @deprecated Use {@see AbstractPlatform::getLikeWildcardCharacters()} instead. + * + * @return string[] + */ + public function getWildcards() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getWildcards() is deprecated.' + . ' Use AbstractPlatform::getLikeWildcardCharacters() instead.', + ); + + return ['%', '_']; + } + + /** + * Returns the regular expression operator. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getRegexpExpression() + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to get the average value of a column. + * + * @deprecated Use AVG() in SQL instead. + * + * @param string $column The column to use. + * + * @return string Generated SQL including an AVG aggregate function. + */ + public function getAvgExpression($column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getAvgExpression() is deprecated. Use AVG() in SQL instead.', + ); + + return 'AVG(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the number of rows (without a NULL value) of a column. + * + * If a '*' is used instead of a column the number of selected rows is returned. + * + * @deprecated Use COUNT() in SQL instead. + * + * @param string|int $column The column to use. + * + * @return string Generated SQL including a COUNT aggregate function. + */ + public function getCountExpression($column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getCountExpression() is deprecated. Use COUNT() in SQL instead.', + ); + + return 'COUNT(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the highest value of a column. + * + * @deprecated Use MAX() in SQL instead. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a MAX aggregate function. + */ + public function getMaxExpression($column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getMaxExpression() is deprecated. Use MAX() in SQL instead.', + ); + + return 'MAX(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the lowest value of a column. + * + * @deprecated Use MIN() in SQL instead. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a MIN aggregate function. + */ + public function getMinExpression($column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getMinExpression() is deprecated. Use MIN() in SQL instead.', + ); + + return 'MIN(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the total sum of a column. + * + * @deprecated Use SUM() in SQL instead. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a SUM aggregate function. + */ + public function getSumExpression($column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getSumExpression() is deprecated. Use SUM() in SQL instead.', + ); + + return 'SUM(' . $column . ')'; + } + + // scalar functions + + /** + * Returns the SQL snippet to get the md5 sum of a column. + * + * Note: Not SQL92, but common functionality. + * + * @deprecated + * + * @param string $column + * + * @return string + */ + public function getMd5Expression($column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getMd5Expression() is deprecated.', + ); + + return 'MD5(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the length of a text column in characters. + * + * @param string $column + * + * @return string + */ + public function getLengthExpression($column) + { + return 'LENGTH(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the squared value of a column. + * + * @deprecated Use SQRT() in SQL instead. + * + * @param string $column The column to use. + * + * @return string Generated SQL including an SQRT aggregate function. + */ + public function getSqrtExpression($column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getSqrtExpression() is deprecated. Use SQRT() in SQL instead.', + ); + + return 'SQRT(' . $column . ')'; + } + + /** + * Returns the SQL snippet to round a numeric column to the number of decimals specified. + * + * @deprecated Use ROUND() in SQL instead. + * + * @param string $column + * @param string|int $decimals + * + * @return string + */ + public function getRoundExpression($column, $decimals = 0) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getRoundExpression() is deprecated. Use ROUND() in SQL instead.', + ); + + return 'ROUND(' . $column . ', ' . $decimals . ')'; + } + + /** + * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2. + * + * @param string $expression1 + * @param string $expression2 + * + * @return string + */ + public function getModExpression($expression1, $expression2) + { + return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; + } + + /** + * Returns the SQL snippet to trim a string. + * + * @param string $str The expression to apply the trim to. + * @param int $mode The position of the trim (leading/trailing/both). + * @param string|bool $char The char to trim, has to be quoted already. Defaults to space. + * + * @return string + */ + public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) + { + $expression = ''; + + switch ($mode) { + case TrimMode::LEADING: + $expression = 'LEADING '; + break; + + case TrimMode::TRAILING: + $expression = 'TRAILING '; + break; + + case TrimMode::BOTH: + $expression = 'BOTH '; + break; + } + + if ($char !== false) { + $expression .= $char . ' '; + } + + if ($mode !== TrimMode::UNSPECIFIED || $char !== false) { + $expression .= 'FROM '; + } + + return 'TRIM(' . $expression . $str . ')'; + } + + /** + * Returns the SQL snippet to trim trailing space characters from the expression. + * + * @deprecated Use RTRIM() in SQL instead. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getRtrimExpression($str) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getRtrimExpression() is deprecated. Use RTRIM() in SQL instead.', + ); + + return 'RTRIM(' . $str . ')'; + } + + /** + * Returns the SQL snippet to trim leading space characters from the expression. + * + * @deprecated Use LTRIM() in SQL instead. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getLtrimExpression($str) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getLtrimExpression() is deprecated. Use LTRIM() in SQL instead.', + ); + + return 'LTRIM(' . $str . ')'; + } + + /** + * Returns the SQL snippet to change all characters from the expression to uppercase, + * according to the current character set mapping. + * + * @deprecated Use UPPER() in SQL instead. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getUpperExpression($str) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getUpperExpression() is deprecated. Use UPPER() in SQL instead.', + ); + + return 'UPPER(' . $str . ')'; + } + + /** + * Returns the SQL snippet to change all characters from the expression to lowercase, + * according to the current character set mapping. + * + * @deprecated Use LOWER() in SQL instead. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getLowerExpression($str) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getLowerExpression() is deprecated. Use LOWER() in SQL instead.', + ); + + return 'LOWER(' . $str . ')'; + } + + /** + * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str. + * + * @param string $str Literal string. + * @param string $substr Literal string to find. + * @param string|int|false $startPos Position to start at, beginning of string by default. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to get the current system date. + * + * @deprecated Generate dates within the application. + * + * @return string + */ + public function getNowExpression() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4753', + 'AbstractPlatform::getNowExpression() is deprecated. Generate dates within the application.', + ); + + return 'NOW()'; + } + + /** + * Returns a SQL snippet to get a substring inside an SQL statement. + * + * Note: Not SQL92, but common functionality. + * + * SQLite only supports the 2 parameter variant of this function. + * + * @param string $string An sql string literal or column name/alias. + * @param string|int $start Where to start the substring portion. + * @param string|int|null $length The substring portion length. + * + * @return string + */ + public function getSubstringExpression($string, $start, $length = null) + { + if ($length === null) { + return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; + } + + return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; + } + + /** + * Returns a SQL snippet to concatenate the given expressions. + * + * Accepts an arbitrary number of string parameters. Each parameter must contain an expression. + * + * @return string + */ + public function getConcatExpression() + { + return implode(' || ', func_get_args()); + } + + /** + * Returns the SQL for a logical not. + * + * Example: + * + * $q = new Doctrine_Query(); + * $e = $q->expr; + * $q->select('*')->from('table') + * ->where($e->eq('id', $e->not('null')); + * + * + * @deprecated Use NOT() in SQL instead. + * + * @param string $expression + * + * @return string The logical expression. + */ + public function getNotExpression($expression) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getNotExpression() is deprecated. Use NOT() in SQL instead.', + ); + + return 'NOT(' . $expression . ')'; + } + + /** + * Returns the SQL that checks if an expression is null. + * + * @deprecated Use IS NULL in SQL instead. + * + * @param string $expression The expression that should be compared to null. + * + * @return string The logical expression. + */ + public function getIsNullExpression($expression) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getIsNullExpression() is deprecated. Use IS NULL in SQL instead.', + ); + + return $expression . ' IS NULL'; + } + + /** + * Returns the SQL that checks if an expression is not null. + * + * @deprecated Use IS NOT NULL in SQL instead. + * + * @param string $expression The expression that should be compared to null. + * + * @return string The logical expression. + */ + public function getIsNotNullExpression($expression) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getIsNotNullExpression() is deprecated. Use IS NOT NULL in SQL instead.', + ); + + return $expression . ' IS NOT NULL'; + } + + /** + * Returns the SQL that checks if an expression evaluates to a value between two values. + * + * The parameter $expression is checked if it is between $value1 and $value2. + * + * Note: There is a slight difference in the way BETWEEN works on some databases. + * http://www.w3schools.com/sql/sql_between.asp. If you want complete database + * independence you should avoid using between(). + * + * @deprecated Use BETWEEN in SQL instead. + * + * @param string $expression The value to compare to. + * @param string $value1 The lower value to compare with. + * @param string $value2 The higher value to compare with. + * + * @return string The logical expression. + */ + public function getBetweenExpression($expression, $value1, $value2) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getBetweenExpression() is deprecated. Use BETWEEN in SQL instead.', + ); + + return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2; + } + + /** + * Returns the SQL to get the arccosine of a value. + * + * @deprecated Use ACOS() in SQL instead. + * + * @param string $value + * + * @return string + */ + public function getAcosExpression($value) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getAcosExpression() is deprecated. Use ACOS() in SQL instead.', + ); + + return 'ACOS(' . $value . ')'; + } + + /** + * Returns the SQL to get the sine of a value. + * + * @deprecated Use SIN() in SQL instead. + * + * @param string $value + * + * @return string + */ + public function getSinExpression($value) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getSinExpression() is deprecated. Use SIN() in SQL instead.', + ); + + return 'SIN(' . $value . ')'; + } + + /** + * Returns the SQL to get the PI value. + * + * @deprecated Use PI() in SQL instead. + * + * @return string + */ + public function getPiExpression() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getPiExpression() is deprecated. Use PI() in SQL instead.', + ); + + return 'PI()'; + } + + /** + * Returns the SQL to get the cosine of a value. + * + * @deprecated Use COS() in SQL instead. + * + * @param string $value + * + * @return string + */ + public function getCosExpression($value) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getCosExpression() is deprecated. Use COS() in SQL instead.', + ); + + return 'COS(' . $value . ')'; + } + + /** + * Returns the SQL to calculate the difference in days between the two passed dates. + * + * Computes diff = date1 - date2. + * + * @param string $date1 + * @param string $date2 + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateDiffExpression($date1, $date2) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL to add the number of given seconds to a date. + * + * @param string $date + * @param int|string $seconds + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddSecondsExpression($date, $seconds) + { + if (is_int($seconds)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $seconds as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND); + } + + /** + * Returns the SQL to subtract the number of given seconds from a date. + * + * @param string $date + * @param int|string $seconds + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubSecondsExpression($date, $seconds) + { + if (is_int($seconds)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $seconds as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND); + } + + /** + * Returns the SQL to add the number of given minutes to a date. + * + * @param string $date + * @param int|string $minutes + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddMinutesExpression($date, $minutes) + { + if (is_int($minutes)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $minutes as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE); + } + + /** + * Returns the SQL to subtract the number of given minutes from a date. + * + * @param string $date + * @param int|string $minutes + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubMinutesExpression($date, $minutes) + { + if (is_int($minutes)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $minutes as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE); + } + + /** + * Returns the SQL to add the number of given hours to a date. + * + * @param string $date + * @param int|string $hours + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddHourExpression($date, $hours) + { + if (is_int($hours)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $hours as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR); + } + + /** + * Returns the SQL to subtract the number of given hours to a date. + * + * @param string $date + * @param int|string $hours + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubHourExpression($date, $hours) + { + if (is_int($hours)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $hours as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR); + } + + /** + * Returns the SQL to add the number of given days to a date. + * + * @param string $date + * @param int|string $days + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddDaysExpression($date, $days) + { + if (is_int($days)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $days as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY); + } + + /** + * Returns the SQL to subtract the number of given days to a date. + * + * @param string $date + * @param int|string $days + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubDaysExpression($date, $days) + { + if (is_int($days)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $days as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY); + } + + /** + * Returns the SQL to add the number of given weeks to a date. + * + * @param string $date + * @param int|string $weeks + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddWeeksExpression($date, $weeks) + { + if (is_int($weeks)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $weeks as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK); + } + + /** + * Returns the SQL to subtract the number of given weeks from a date. + * + * @param string $date + * @param int|string $weeks + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubWeeksExpression($date, $weeks) + { + if (is_int($weeks)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $weeks as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK); + } + + /** + * Returns the SQL to add the number of given months to a date. + * + * @param string $date + * @param int|string $months + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddMonthExpression($date, $months) + { + if (is_int($months)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $months as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH); + } + + /** + * Returns the SQL to subtract the number of given months to a date. + * + * @param string $date + * @param int|string $months + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubMonthExpression($date, $months) + { + if (is_int($months)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $months as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH); + } + + /** + * Returns the SQL to add the number of given quarters to a date. + * + * @param string $date + * @param int|string $quarters + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddQuartersExpression($date, $quarters) + { + if (is_int($quarters)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $quarters as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER); + } + + /** + * Returns the SQL to subtract the number of given quarters from a date. + * + * @param string $date + * @param int|string $quarters + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubQuartersExpression($date, $quarters) + { + if (is_int($quarters)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $quarters as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER); + } + + /** + * Returns the SQL to add the number of given years to a date. + * + * @param string $date + * @param int|string $years + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateAddYearsExpression($date, $years) + { + if (is_int($years)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $years as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR); + } + + /** + * Returns the SQL to subtract the number of given years from a date. + * + * @param string $date + * @param int|string $years + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateSubYearsExpression($date, $years) + { + if (is_int($years)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3498', + 'Passing $years as an integer is deprecated. Pass it as a numeric string instead.', + ); + } + + return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR); + } + + /** + * Returns the SQL for a date arithmetic expression. + * + * @param string $date The column or literal representing a date + * to perform the arithmetic operation on. + * @param string $operator The arithmetic operator (+ or -). + * @param int|string $interval The interval that shall be calculated into the date. + * @param string $unit The unit of the interval that shall be calculated into the date. + * One of the {@see DateIntervalUnit} constants. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Generates the SQL expression which represents the given date interval multiplied by a number + * + * @param string $interval SQL expression describing the interval value + * @param int $multiplier Interval multiplier + */ + protected function multiplyInterval(string $interval, int $multiplier): string + { + return sprintf('(%s * %d)', $interval, $multiplier); + } + + /** + * Returns the SQL bit AND comparison expression. + * + * @param string $value1 + * @param string $value2 + * + * @return string + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return '(' . $value1 . ' & ' . $value2 . ')'; + } + + /** + * Returns the SQL bit OR comparison expression. + * + * @param string $value1 + * @param string $value2 + * + * @return string + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return '(' . $value1 . ' | ' . $value2 . ')'; + } + + /** + * Returns the SQL expression which represents the currently selected database. + */ + abstract public function getCurrentDatabaseExpression(): string; + + /** + * Returns the FOR UPDATE expression. + * + * @deprecated This API is not portable. Use {@link QueryBuilder::forUpdate()}` instead. + * + * @return string + */ + public function getForUpdateSQL() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6191', + '%s is deprecated as non-portable.', + __METHOD__, + ); + + return 'FOR UPDATE'; + } + + /** + * Honors that some SQL vendors such as MsSql use table hints for locking instead of the + * ANSI SQL FOR UPDATE specification. + * + * @param string $fromClause The FROM clause to append the hint for the given lock mode to + * @param int $lockMode One of the Doctrine\DBAL\LockMode::* constants + * @psalm-param LockMode::* $lockMode + */ + public function appendLockHint(string $fromClause, int $lockMode): string + { + switch ($lockMode) { + case LockMode::NONE: + case LockMode::OPTIMISTIC: + case LockMode::PESSIMISTIC_READ: + case LockMode::PESSIMISTIC_WRITE: + return $fromClause; + + default: + throw InvalidLockMode::fromLockMode($lockMode); + } + } + + /** + * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock. + * + * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database + * vendors allow to lighten this constraint up to be a real read lock. + * + * @deprecated This API is not portable. + * + * @return string + */ + public function getReadLockSQL() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6191', + '%s is deprecated as non-portable.', + __METHOD__, + ); + + return $this->getForUpdateSQL(); + } + + /** + * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. + * + * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard. + * + * @deprecated This API is not portable. + * + * @return string + */ + public function getWriteLockSQL() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6191', + '%s is deprecated as non-portable.', + __METHOD__, + ); + + return $this->getForUpdateSQL(); + } + + /** + * Returns the SQL snippet to drop an existing table. + * + * @param Table|string $table + * + * @return string + * + * @throws InvalidArgumentException + */ + public function getDropTableSQL($table) + { + $tableArg = $table; + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + if (! is_string($table)) { + throw new InvalidArgumentException( + __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', + ); + } + + if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaDropTable, + ); + + $eventArgs = new SchemaDropTableEventArgs($tableArg, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs); + + if ($eventArgs->isDefaultPrevented()) { + $sql = $eventArgs->getSql(); + + if ($sql === null) { + throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL'); + } + + return $sql; + } + } + + return 'DROP TABLE ' . $table; + } + + /** + * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. + * + * @param Table|string $table + * + * @return string + */ + public function getDropTemporaryTableSQL($table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + return $this->getDropTableSQL($table); + } + + /** + * Returns the SQL to drop an index from a table. + * + * @param Index|string $index + * @param Table|string|null $table + * + * @return string + * + * @throws InvalidArgumentException + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $index = $index->getQuotedName($this); + } elseif (! is_string($index)) { + throw new InvalidArgumentException( + __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', + ); + } + + return 'DROP INDEX ' . $index; + } + + /** + * Returns the SQL to drop a constraint. + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + * + * @param Constraint|string $constraint + * @param Table|string $table + * + * @return string + */ + public function getDropConstraintSQL($constraint, $table) + { + if ($constraint instanceof Constraint) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $constraint as a Constraint object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + } else { + $constraint = new Identifier($constraint); + } + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + } else { + $table = new Identifier($table); + } + + $constraint = $constraint->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; + } + + /** + * Returns the SQL to drop a foreign key. + * + * @param ForeignKeyConstraint|string $foreignKey + * @param Table|string $table + * + * @return string + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if ($foreignKey instanceof ForeignKeyConstraint) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' + . ' Pass it as a quoted name instead.', + __METHOD__, + ); + } else { + $foreignKey = new Identifier($foreignKey); + } + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + } else { + $table = new Identifier($table); + } + + $foreignKey = $foreignKey->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; + } + + /** + * Returns the SQL to drop a unique constraint. + */ + public function getDropUniqueConstraintSQL(string $name, string $tableName): string + { + return $this->getDropConstraintSQL($name, $tableName); + } + + /** + * Returns the SQL statement(s) to create a table with the specified name, columns and constraints + * on this platform. + * + * @param int $createFlags + * @psalm-param int-mask-of $createFlags + * + * @return list The list of SQL statements. + * + * @throws Exception + * @throws InvalidArgumentException + */ + public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) + { + if (! is_int($createFlags)) { + throw new InvalidArgumentException( + 'Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.', + ); + } + + if (($createFlags & self::CREATE_INDEXES) === 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5416', + 'Unsetting the CREATE_INDEXES flag in AbstractPlatform::getCreateTableSQL() is deprecated.', + ); + } + + if (($createFlags & self::CREATE_FOREIGNKEYS) === 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5416', + 'Not setting the CREATE_FOREIGNKEYS flag in AbstractPlatform::getCreateTableSQL()' + . ' is deprecated. In order to build the statements that create multiple tables' + . ' referencing each other via foreign keys, use AbstractPlatform::getCreateTablesSQL().', + ); + } + + return $this->buildCreateTableSQL( + $table, + ($createFlags & self::CREATE_INDEXES) > 0, + ($createFlags & self::CREATE_FOREIGNKEYS) > 0, + ); + } + + public function createSelectSQLBuilder(): SelectSQLBuilder + { + return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', 'SKIP LOCKED'); + } + + /** + * @internal + * + * @return list + * + * @throws Exception + */ + final protected function getCreateTableWithoutForeignKeysSQL(Table $table): array + { + return $this->buildCreateTableSQL($table, true, false); + } + + /** + * @return list + * + * @throws Exception + */ + private function buildCreateTableSQL(Table $table, bool $createIndexes, bool $createForeignKeys): array + { + if (count($table->getColumns()) === 0) { + throw Exception::noColumnsSpecifiedForTable($table->getName()); + } + + $tableName = $table->getQuotedName($this); + $options = $table->getOptions(); + $options['uniqueConstraints'] = []; + $options['indexes'] = []; + $options['primary'] = []; + + if ($createIndexes) { + foreach ($table->getIndexes() as $index) { + if (! $index->isPrimary()) { + $options['indexes'][$index->getQuotedName($this)] = $index; + + continue; + } + + $options['primary'] = $index->getQuotedColumns($this); + $options['primary_index'] = $index; + } + + foreach ($table->getUniqueConstraints() as $uniqueConstraint) { + $options['uniqueConstraints'][$uniqueConstraint->getQuotedName($this)] = $uniqueConstraint; + } + } + + if ($createForeignKeys) { + $options['foreignKeys'] = []; + + foreach ($table->getForeignKeys() as $fkConstraint) { + $options['foreignKeys'][] = $fkConstraint; + } + } + + $columnSql = []; + $columns = []; + + foreach ($table->getColumns() as $column) { + if ( + $this->_eventManager !== null + && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn) + ) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaCreateTableColumn, + ); + + $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); + + $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + if ($eventArgs->isDefaultPrevented()) { + continue; + } + } + + $columnData = $this->columnToArray($column); + + if (in_array($column->getName(), $options['primary'], true)) { + $columnData['primary'] = true; + } + + $columns[$columnData['name']] = $columnData; + } + + if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaCreateTable, + ); + + $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); + + $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); + + if ($eventArgs->isDefaultPrevented()) { + return array_merge($eventArgs->getSql(), $columnSql); + } + } + + $sql = $this->_getCreateTableSQL($tableName, $columns, $options); + + if ($this->supportsCommentOnStatement()) { + if ($table->hasOption('comment')) { + $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment')); + } + + foreach ($table->getColumns() as $column) { + $comment = $this->getColumnComment($column); + + if ($comment === null || $comment === '') { + continue; + } + + $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment); + } + } + + return array_merge($sql, $columnSql); + } + + /** + * @param list $tables + * + * @return list + * + * @throws Exception + */ + public function getCreateTablesSQL(array $tables): array + { + $sql = []; + + foreach ($tables as $table) { + $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table)); + } + + foreach ($tables as $table) { + foreach ($table->getForeignKeys() as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL( + $foreignKey, + $table->getQuotedName($this), + ); + } + } + + return $sql; + } + + /** + * @param list
$tables + * + * @return list + */ + public function getDropTablesSQL(array $tables): array + { + $sql = []; + + foreach ($tables as $table) { + foreach ($table->getForeignKeys() as $foreignKey) { + $sql[] = $this->getDropForeignKeySQL( + $foreignKey->getQuotedName($this), + $table->getQuotedName($this), + ); + } + } + + foreach ($tables as $table) { + $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); + } + + return $sql; + } + + protected function getCommentOnTableSQL(string $tableName, ?string $comment): string + { + $tableName = new Identifier($tableName); + + return sprintf( + 'COMMENT ON TABLE %s IS %s', + $tableName->getQuotedName($this), + $this->quoteStringLiteral((string) $comment), + ); + } + + /** + * @param string $tableName + * @param string $columnName + * @param string|null $comment + * + * @return string + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $tableName = new Identifier($tableName); + $columnName = new Identifier($columnName); + + return sprintf( + 'COMMENT ON COLUMN %s.%s IS %s', + $tableName->getQuotedName($this), + $columnName->getQuotedName($this), + $this->quoteStringLiteral((string) $comment), + ); + } + + /** + * Returns the SQL to create inline comment on a column. + * + * @param string $comment + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getInlineColumnCommentSQL($comment) + { + if (! $this->supportsInlineColumnComments()) { + throw Exception::notSupported(__METHOD__); + } + + return 'COMMENT ' . $this->quoteStringLiteral($comment); + } + + /** + * Returns the SQL used to create a table. + * + * @param string $name + * @param mixed[][] $columns + * @param mixed[] $options + * + * @return string[] + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $columnListSql = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $index => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); + } + } + + if (isset($options['primary']) && ! empty($options['primary'])) { + $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; + } + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index => $definition) { + $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); + } + } + + $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; + $check = $this->getCheckDeclarationSQL($columns); + + if (! empty($check)) { + $query .= ', ' . $check; + } + + $query .= ')'; + + $sql = [$query]; + + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $name); + } + } + + return $sql; + } + + /** @return string */ + public function getCreateTemporaryTableSnippetSQL() + { + return 'CREATE TEMPORARY TABLE'; + } + + /** + * Generates SQL statements that can be used to apply the diff. + * + * @return list + */ + public function getAlterSchemaSQL(SchemaDiff $diff): array + { + return $diff->toSql($this); + } + + /** + * Returns the SQL to create a sequence on this platform. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL to change a sequence on this platform. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to drop an existing sequence. + * + * @param Sequence|string $sequence + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDropSequenceSQL($sequence) + { + if (! $this->supportsSequences()) { + throw Exception::notSupported(__METHOD__); + } + + if ($sequence instanceof Sequence) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $sequence as a Sequence object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence; + } + + /** + * Returns the SQL to create a constraint on a table on this platform. + * + * @deprecated Use {@see getCreateIndexSQL()}, {@see getCreateForeignKeySQL()} + * or {@see getCreateUniqueConstraintSQL()} instead. + * + * @param Table|string $table + * + * @return string + * + * @throws InvalidArgumentException + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this); + + $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; + + $referencesClause = ''; + if ($constraint instanceof Index) { + if ($constraint->isPrimary()) { + $query .= ' PRIMARY KEY'; + } elseif ($constraint->isUnique()) { + $query .= ' UNIQUE'; + } else { + throw new InvalidArgumentException( + 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().', + ); + } + } elseif ($constraint instanceof UniqueConstraint) { + $query .= ' UNIQUE'; + } elseif ($constraint instanceof ForeignKeyConstraint) { + $query .= ' FOREIGN KEY'; + + $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) . + ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')'; + } + + $query .= ' ' . $columnList . $referencesClause; + + return $query; + } + + /** + * Returns the SQL to create an index on a table on this platform. + * + * @param Table|string $table The name of the table on which the index is to be created. + * + * @return string + * + * @throws InvalidArgumentException + */ + public function getCreateIndexSQL(Index $index, $table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + $name = $index->getQuotedName($this); + $columns = $index->getColumns(); + + if (count($columns) === 0) { + throw new InvalidArgumentException(sprintf( + 'Incomplete or invalid index definition %s on table %s', + $name, + $table, + )); + } + + if ($index->isPrimary()) { + return $this->getCreatePrimaryKeySQL($index, $table); + } + + $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); + + return $query; + } + + /** + * Adds condition for partial index. + * + * @return string + */ + protected function getPartialIndexSQL(Index $index) + { + if ($this->supportsPartialIndexes() && $index->hasOption('where')) { + return ' WHERE ' . $index->getOption('where'); + } + + return ''; + } + + /** + * Adds additional flags for index generation. + * + * @return string + */ + protected function getCreateIndexSQLFlags(Index $index) + { + return $index->isUnique() ? 'UNIQUE ' : ''; + } + + /** + * Returns the SQL to create an unnamed primary key constraint. + * + * @param Table|string $table + * + * @return string + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; + } + + /** + * Returns the SQL to create a named schema. + * + * @param string $schemaName + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getCreateSchemaSQL($schemaName) + { + if (! $this->supportsSchemas()) { + throw Exception::notSupported(__METHOD__); + } + + return 'CREATE SCHEMA ' . $schemaName; + } + + /** + * Returns the SQL to create a unique constraint on a table on this platform. + */ + public function getCreateUniqueConstraintSQL(UniqueConstraint $constraint, string $tableName): string + { + return $this->getCreateConstraintSQL($constraint, $tableName); + } + + /** + * Returns the SQL snippet to drop a schema. + * + * @throws Exception If not supported on this platform. + */ + public function getDropSchemaSQL(string $schemaName): string + { + if (! $this->supportsSchemas()) { + throw Exception::notSupported(__METHOD__); + } + + return 'DROP SCHEMA ' . $schemaName; + } + + /** + * Quotes a string so that it can be safely used as a table or column name, + * even if it is a reserved word of the platform. This also detects identifier + * chains separated by dot and quotes them independently. + * + * NOTE: Just because you CAN use quoted identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * @param string $str The identifier name to be quoted. + * + * @return string The quoted identifier string. + */ + public function quoteIdentifier($str) + { + if (strpos($str, '.') !== false) { + $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str)); + + return implode('.', $parts); + } + + return $this->quoteSingleIdentifier($str); + } + + /** + * Quotes a single identifier (no dot chain separation). + * + * @param string $str The identifier name to be quoted. + * + * @return string The quoted identifier string. + */ + public function quoteSingleIdentifier($str) + { + $c = $this->getIdentifierQuoteCharacter(); + + return $c . str_replace($c, $c . $c, $str) . $c; + } + + /** + * Returns the SQL to create a new foreign key. + * + * @param ForeignKeyConstraint $foreignKey The foreign key constraint. + * @param Table|string $table The name of the table on which the foreign key is to be created. + * + * @return string + */ + public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); + } + + /** + * Gets the SQL statements for altering an existing table. + * + * This method returns an array of SQL statements, since some platforms need several statements. + * + * @return list + * + * @throws Exception If not supported on this platform. + */ + public function getAlterTableSQL(TableDiff $diff) + { + throw Exception::notSupported(__METHOD__); + } + + /** @return list */ + public function getRenameTableSQL(string $oldName, string $newName): array + { + return [ + sprintf('ALTER TABLE %s RENAME TO %s', $oldName, $newName), + ]; + } + + /** + * @param mixed[] $columnSql + * + * @return bool + */ + protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql) + { + if ($this->_eventManager === null) { + return false; + } + + if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) { + return false; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaAlterTableAddColumn, + ); + + $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param string[] $columnSql + * + * @return bool + */ + protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql) + { + if ($this->_eventManager === null) { + return false; + } + + if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) { + return false; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaAlterTableRemoveColumn, + ); + + $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param string[] $columnSql + * + * @return bool + */ + protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql) + { + if ($this->_eventManager === null) { + return false; + } + + if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { + return false; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaAlterTableChangeColumn, + ); + + $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param string $oldColumnName + * @param string[] $columnSql + * + * @return bool + */ + protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql) + { + if ($this->_eventManager === null) { + return false; + } + + if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) { + return false; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaAlterTableRenameColumn, + ); + + $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param string[] $sql + * + * @return bool + */ + protected function onSchemaAlterTable(TableDiff $diff, &$sql) + { + if ($this->_eventManager === null) { + return false; + } + + if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) { + return false; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated.', + Events::onSchemaAlterTable, + ); + + $eventArgs = new SchemaAlterTableEventArgs($diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs); + + $sql = array_merge($sql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** @return string[] */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + + $sql = []; + if ($this->supportsForeignKeyConstraints()) { + foreach ($diff->getDroppedForeignKeys() as $foreignKey) { + if ($foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = $foreignKey->getQuotedName($this); + } + + $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableNameSQL); + } + + foreach ($diff->getModifiedForeignKeys() as $foreignKey) { + $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); + } + } + + foreach ($diff->getDroppedIndexes() as $index) { + $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); + } + + foreach ($diff->getModifiedIndexes() as $index) { + $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); + } + + return $sql; + } + + /** @return string[] */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $sql = []; + $newName = $diff->getNewName(); + + if ($newName !== false) { + $tableNameSQL = $newName->getQuotedName($this); + } else { + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + } + + if ($this->supportsForeignKeyConstraints()) { + foreach ($diff->getAddedForeignKeys() as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); + } + + foreach ($diff->getModifiedForeignKeys() as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); + } + } + + foreach ($diff->getAddedIndexes() as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); + } + + foreach ($diff->getModifiedIndexes() as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); + } + + foreach ($diff->getRenamedIndexes() as $oldIndexName => $index) { + $oldIndexName = new Identifier($oldIndexName); + $sql = array_merge( + $sql, + $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableNameSQL), + ); + } + + return $sql; + } + + /** + * Returns the SQL for renaming an index on a table. + * + * @param string $oldIndexName The name of the index to rename from. + * @param Index $index The definition of the index to rename to. + * @param string $tableName The table to rename the given index on. + * + * @return string[] The sequence of SQL statements for renaming the given index. + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + return [ + $this->getDropIndexSQL($oldIndexName, $tableName), + $this->getCreateIndexSQL($index, $tableName), + ]; + } + + /** + * Gets declaration of a number of columns in bulk. + * + * @param mixed[][] $columns A multidimensional associative array. + * The first dimension determines the column name, while the second + * dimension is keyed with the name of the properties + * of the column being declared as array indexes. Currently, the types + * of supported column properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * column. If this argument is missing the column should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this column. + * + * notnull + * Boolean flag that indicates whether this column is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this column. + * collation + * Text value with the default COLLATION for this column. + * unique + * unique constraint + * + * @return string + */ + public function getColumnDeclarationListSQL(array $columns) + { + $declarations = []; + + foreach ($columns as $name => $column) { + $declarations[] = $this->getColumnDeclarationSQL($name, $column); + } + + return implode(', ', $declarations); + } + + /** + * Obtains DBMS specific SQL code portion needed to declare a generic type + * column to be used in statements like CREATE TABLE. + * + * @param string $name The name the column to be declared. + * @param mixed[] $column An associative array with the name of the properties + * of the column being declared as array indexes. Currently, the types + * of supported column properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * column. If this argument is missing the column should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this column. + * + * notnull + * Boolean flag that indicates whether this column is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this column. + * collation + * Text value with the default COLLATION for this column. + * unique + * unique constraint + * check + * column check constraint + * columnDefinition + * a string that defines the complete column + * + * @return string DBMS specific SQL code portion that should be used to declare the column. + * + * @throws Exception + */ + public function getColumnDeclarationSQL($name, array $column) + { + if (isset($column['columnDefinition'])) { + $declaration = $this->getCustomTypeDeclarationSQL($column); + } else { + $default = $this->getDefaultValueDeclarationSQL($column); + + $charset = ! empty($column['charset']) ? + ' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : ''; + + $collation = ! empty($column['collation']) ? + ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; + + $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; + + if (! empty($column['unique'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5656', + 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', + ); + + $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); + } else { + $unique = ''; + } + + if (! empty($column['check'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5656', + 'The usage of the "check" column property is deprecated.', + ); + + $check = ' ' . $column['check']; + } else { + $check = ''; + } + + $typeDecl = $column['type']->getSQLDeclaration($column, $this); + $declaration = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; + + if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') { + $declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']); + } + } + + return $name . ' ' . $declaration; + } + + /** + * Returns the SQL snippet that declares a floating point column of arbitrary precision. + * + * @param mixed[] $column + * + * @return string + */ + public function getDecimalTypeDeclarationSQL(array $column) + { + if (empty($column['precision'])) { + if (! isset($column['precision'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5637', + 'Relying on the default decimal column precision is deprecated' + . ', specify the precision explicitly.', + ); + } + + $precision = 10; + } else { + $precision = $column['precision']; + } + + if (empty($column['scale'])) { + if (! isset($column['scale'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5637', + 'Relying on the default decimal column scale is deprecated' + . ', specify the scale explicitly.', + ); + } + + $scale = 0; + } else { + $scale = $column['scale']; + } + + return 'NUMERIC(' . $precision . ', ' . $scale . ')'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set a default value + * declaration to be used in statements like CREATE TABLE. + * + * @param mixed[] $column The column definition array. + * + * @return string DBMS specific SQL code portion needed to set a default value. + */ + public function getDefaultValueDeclarationSQL($column) + { + if (! isset($column['default'])) { + return empty($column['notnull']) ? ' DEFAULT NULL' : ''; + } + + $default = $column['default']; + + if (! isset($column['type'])) { + return " DEFAULT '" . $default . "'"; + } + + $type = $column['type']; + + if ($type instanceof Types\PhpIntegerMappingType) { + return ' DEFAULT ' . $default; + } + + if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) { + return ' DEFAULT ' . $this->getCurrentTimestampSQL(); + } + + if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) { + return ' DEFAULT ' . $this->getCurrentTimeSQL(); + } + + if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) { + return ' DEFAULT ' . $this->getCurrentDateSQL(); + } + + if ($type instanceof Types\BooleanType) { + return ' DEFAULT ' . $this->convertBooleans($default); + } + + return ' DEFAULT ' . $this->quoteStringLiteral($default); + } + + /** + * Obtains DBMS specific SQL code portion needed to set a CHECK constraint + * declaration to be used in statements like CREATE TABLE. + * + * @param string[]|mixed[][] $definition The check definition. + * + * @return string DBMS specific SQL code portion needed to set a CHECK constraint. + */ + public function getCheckDeclarationSQL(array $definition) + { + $constraints = []; + foreach ($definition as $column => $def) { + if (is_string($def)) { + $constraints[] = 'CHECK (' . $def . ')'; + } else { + if (isset($def['min'])) { + $constraints[] = 'CHECK (' . $column . ' >= ' . $def['min'] . ')'; + } + + if (isset($def['max'])) { + $constraints[] = 'CHECK (' . $column . ' <= ' . $def['max'] . ')'; + } + } + } + + return implode(', ', $constraints); + } + + /** + * Obtains DBMS specific SQL code portion needed to set a unique + * constraint declaration to be used in statements like CREATE TABLE. + * + * @param string $name The name of the unique constraint. + * @param UniqueConstraint $constraint The unique constraint definition. + * + * @return string DBMS specific SQL code portion needed to set a constraint. + * + * @throws InvalidArgumentException + */ + public function getUniqueConstraintDeclarationSQL($name, UniqueConstraint $constraint) + { + $columns = $constraint->getQuotedColumns($this); + $name = new Identifier($name); + + if (count($columns) === 0) { + throw new InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + $constraintFlags = array_merge(['UNIQUE'], array_map('strtoupper', $constraint->getFlags())); + $constraintName = $name->getQuotedName($this); + $columnListNames = $this->getColumnsFieldDeclarationListSQL($columns); + + return sprintf('CONSTRAINT %s %s (%s)', $constraintName, implode(' ', $constraintFlags), $columnListNames); + } + + /** + * Obtains DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param string $name The name of the index. + * @param Index $index The index definition. + * + * @return string DBMS specific SQL code portion needed to set an index. + * + * @throws InvalidArgumentException + */ + public function getIndexDeclarationSQL($name, Index $index) + { + $columns = $index->getColumns(); + $name = new Identifier($name); + + if (count($columns) === 0) { + throw new InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) + . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); + } + + /** + * Obtains SQL code portion needed to create a custom column, + * e.g. when a column has the "columnDefinition" keyword. + * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. + * + * @deprecated + * + * @param mixed[] $column + * + * @return string + */ + public function getCustomTypeDeclarationSQL(array $column) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5527', + '%s is deprecated.', + __METHOD__, + ); + + return $column['columnDefinition']; + } + + /** + * Obtains DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @deprecated + */ + public function getIndexFieldDeclarationListSQL(Index $index): string + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5527', + '%s is deprecated.', + __METHOD__, + ); + + return implode(', ', $index->getQuotedColumns($this)); + } + + /** + * Obtains DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @deprecated + * + * @param mixed[] $columns + */ + public function getColumnsFieldDeclarationListSQL(array $columns): string + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5527', + '%s is deprecated.', + __METHOD__, + ); + + $ret = []; + + foreach ($columns as $column => $definition) { + if (is_array($definition)) { + $ret[] = $column; + } else { + $ret[] = $definition; + } + } + + return implode(', ', $ret); + } + + /** + * Returns the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @deprecated + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + public function getTemporaryTableSQL() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getTemporaryTableSQL() is deprecated.', + ); + + return 'TEMPORARY'; + } + + /** + * Some vendors require temporary table names to be qualified specially. + * + * @param string $tableName + * + * @return string + */ + public function getTemporaryTableName($tableName) + { + return $tableName; + } + + /** + * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a column declaration to be used in statements like CREATE TABLE. + * + * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a column declaration. + */ + public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); + $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); + + return $sql; + } + + /** + * Returns the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param ForeignKeyConstraint $foreignKey The foreign key definition. + * + * @return string + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + if ($foreignKey->hasOption('onUpdate')) { + $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); + } + + if ($foreignKey->hasOption('onDelete')) { + $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); + } + + return $query; + } + + /** + * Returns the given referential action in uppercase if valid, otherwise throws an exception. + * + * @param string $action The foreign key referential action. + * + * @return string + * + * @throws InvalidArgumentException If unknown referential action given. + */ + public function getForeignKeyReferentialActionSQL($action) + { + $upper = strtoupper($action); + switch ($upper) { + case 'CASCADE': + case 'SET NULL': + case 'NO ACTION': + case 'RESTRICT': + case 'SET DEFAULT': + return $upper; + + default: + throw new InvalidArgumentException('Invalid foreign key action: ' . $upper); + } + } + + /** + * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a column declaration to be used in statements like CREATE TABLE. + * + * @return string + * + * @throws InvalidArgumentException + */ + public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = ''; + if (strlen($foreignKey->getName()) > 0) { + $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; + } + + $sql .= 'FOREIGN KEY ('; + + if (count($foreignKey->getLocalColumns()) === 0) { + throw new InvalidArgumentException("Incomplete definition. 'local' required."); + } + + if (count($foreignKey->getForeignColumns()) === 0) { + throw new InvalidArgumentException("Incomplete definition. 'foreign' required."); + } + + if (strlen($foreignKey->getForeignTableName()) === 0) { + throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required."); + } + + return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this)) + . ') REFERENCES ' + . $foreignKey->getQuotedForeignTableName($this) . ' (' + . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a column declaration to be used in statements like CREATE TABLE. + * + * @deprecated Use UNIQUE in SQL instead. + * + * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a column declaration. + */ + public function getUniqueFieldDeclarationSQL() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getUniqueFieldDeclarationSQL() is deprecated. Use UNIQUE in SQL instead.', + ); + + return 'UNIQUE'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET + * of a column declaration to be used in statements like CREATE TABLE. + * + * @param string $charset The name of the charset. + * + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a column declaration. + */ + public function getColumnCharsetDeclarationSQL($charset) + { + return ''; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the COLLATION + * of a column declaration to be used in statements like CREATE TABLE. + * + * @param string $collation The name of the collation. + * + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a column declaration. + */ + public function getColumnCollationDeclarationSQL($collation) + { + return $this->supportsColumnCollation() ? 'COLLATE ' . $this->quoteSingleIdentifier($collation) : ''; + } + + /** + * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. + * Subclasses should override this method to return TRUE if they prefer identity columns. + * + * @deprecated + * + * @return bool + */ + public function prefersIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/1519', + 'AbstractPlatform::prefersIdentityColumns() is deprecated.', + ); + + return false; + } + + /** + * Some platforms need the boolean values to be converted. + * + * The default conversion in this implementation converts to integers (false => 0, true => 1). + * + * Note: if the input is not a boolean the original input might be returned. + * + * There are two contexts when converting booleans: Literals and Prepared Statements. + * This method should handle the literal case + * + * @param mixed $item A boolean or an array of them. + * + * @return mixed A boolean database value or an array of them. + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $k => $value) { + if (! is_bool($value)) { + continue; + } + + $item[$k] = (int) $value; + } + } elseif (is_bool($item)) { + $item = (int) $item; + } + + return $item; + } + + /** + * Some platforms have boolean literals that needs to be correctly converted + * + * The default conversion tries to convert value into bool "(bool)$item" + * + * @param T $item + * + * @return (T is null ? null : bool) + * + * @template T + */ + public function convertFromBoolean($item) + { + return $item === null ? null : (bool) $item; + } + + /** + * This method should handle the prepared statements case. When there is no + * distinction, it's OK to use the same method. + * + * Note: if the input is not a boolean the original input might be returned. + * + * @param mixed $item A boolean or an array of them. + * + * @return mixed A boolean database value or an array of them. + */ + public function convertBooleansToDatabaseValue($item) + { + return $this->convertBooleans($item); + } + + /** + * Returns the SQL specific for the platform to get the current date. + * + * @return string + */ + public function getCurrentDateSQL() + { + return 'CURRENT_DATE'; + } + + /** + * Returns the SQL specific for the platform to get the current time. + * + * @return string + */ + public function getCurrentTimeSQL() + { + return 'CURRENT_TIME'; + } + + /** + * Returns the SQL specific for the platform to get the current timestamp + * + * @return string + */ + public function getCurrentTimestampSQL() + { + return 'CURRENT_TIMESTAMP'; + } + + /** + * Returns the SQL for a given transaction isolation level Connection constant. + * + * @param int $level + * + * @return string + * + * @throws InvalidArgumentException + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case TransactionIsolationLevel::READ_UNCOMMITTED: + return 'READ UNCOMMITTED'; + + case TransactionIsolationLevel::READ_COMMITTED: + return 'READ COMMITTED'; + + case TransactionIsolationLevel::REPEATABLE_READ: + return 'REPEATABLE READ'; + + case TransactionIsolationLevel::SERIALIZABLE: + return 'SERIALIZABLE'; + + default: + throw new InvalidArgumentException('Invalid isolation level:' . $level); + } + } + + /** + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListDatabasesSQL() + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL statement for retrieving the namespaces defined in the database. + * + * @deprecated Use {@see AbstractSchemaManager::listSchemaNames()} instead. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListNamespacesSQL() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'AbstractPlatform::getListNamespacesSQL() is deprecated,' + . ' use AbstractSchemaManager::listSchemaNames() instead.', + ); + + throw Exception::notSupported(__METHOD__); + } + + /** + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + * + * @param string $database + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListSequencesSQL($database) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @deprecated + * + * @param string $table + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListTableConstraintsSQL($table) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * @param string $table + * @param string $database + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListTableColumnsSQL($table, $database = null) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListTablesSQL() + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @deprecated + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListUsersSQL() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::getListUsersSQL() is deprecated.', + ); + + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL to list all views of a database or user. + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + * + * @param string $database + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListViewsSQL($database) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * Returns the list of indexes for the current database. + * + * The current database parameter is optional but will always be passed + * when using the SchemaManager API and is the database the given table is in. + * + * Attention: Some platforms only support currentDatabase when they + * are connected with that database. Cross-database information schema + * requests may be impossible. + * + * @param string $table + * @param string $database + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListTableIndexesSQL($table, $database = null) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * @param string $table + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getListTableForeignKeysSQL($table) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @param string $name + * @param string $sql + * + * @return string + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * @param string $name + * + * @return string + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW ' . $name; + } + + /** + * @param string $sequence + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getSequenceNextValSQL($sequence) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Returns the SQL to create a new database. + * + * @param string $name The name of the database that should be created. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getCreateDatabaseSQL($name) + { + if (! $this->supportsCreateDropDatabase()) { + throw Exception::notSupported(__METHOD__); + } + + return 'CREATE DATABASE ' . $name; + } + + /** + * Returns the SQL snippet to drop an existing database. + * + * @param string $name The name of the database that should be dropped. + * + * @return string + */ + public function getDropDatabaseSQL($name) + { + if (! $this->supportsCreateDropDatabase()) { + throw Exception::notSupported(__METHOD__); + } + + return 'DROP DATABASE ' . $name; + } + + /** + * Returns the SQL to set the transaction isolation level. + * + * @param int $level + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getSetTransactionIsolationSQL($level) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create datetime columns in + * statements like CREATE TABLE. + * + * @param mixed[] $column + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create datetime with timezone offset columns. + * + * @param mixed[] $column + * + * @return string + */ + public function getDateTimeTzTypeDeclarationSQL(array $column) + { + return $this->getDateTimeTypeDeclarationSQL($column); + } + + /** + * Obtains DBMS specific SQL to be used to create date columns in statements + * like CREATE TABLE. + * + * @param mixed[] $column + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDateTypeDeclarationSQL(array $column) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create time columns in statements + * like CREATE TABLE. + * + * @param mixed[] $column + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getTimeTypeDeclarationSQL(array $column) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * @param mixed[] $column + * + * @return string + */ + public function getFloatDeclarationSQL(array $column) + { + return 'DOUBLE PRECISION'; + } + + /** + * Gets the default transaction isolation level of the platform. + * + * @see TransactionIsolationLevel + * + * @return TransactionIsolationLevel::* The default isolation level. + */ + public function getDefaultTransactionIsolationLevel() + { + return TransactionIsolationLevel::READ_COMMITTED; + } + + /* supports*() methods */ + + /** + * Whether the platform supports sequences. + * + * @return bool + */ + public function supportsSequences() + { + return false; + } + + /** + * Whether the platform supports identity columns. + * + * Identity columns are columns that receive an auto-generated value from the + * database on insert of a row. + * + * @return bool + */ + public function supportsIdentityColumns() + { + return false; + } + + /** + * Whether the platform emulates identity columns through sequences. + * + * Some platforms that do not support identity columns natively + * but support sequences can emulate identity columns by using + * sequences. + * + * @deprecated + * + * @return bool + */ + public function usesSequenceEmulatedIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return false; + } + + /** + * Returns the name of the sequence for a particular identity column in a particular table. + * + * @deprecated + * + * @see usesSequenceEmulatedIdentityColumns + * + * @param string $tableName The name of the table to return the sequence name for. + * @param string $columnName The name of the identity column in the table to return the sequence name for. + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getIdentitySequenceName($tableName, $columnName) + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Whether the platform supports indexes. + * + * @deprecated + * + * @return bool + */ + public function supportsIndexes() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::supportsIndexes() is deprecated.', + ); + + return true; + } + + /** + * Whether the platform supports partial indexes. + * + * @return bool + */ + public function supportsPartialIndexes() + { + return false; + } + + /** + * Whether the platform supports indexes with column length definitions. + */ + public function supportsColumnLengthIndexes(): bool + { + return false; + } + + /** + * Whether the platform supports altering tables. + * + * @deprecated All platforms must implement altering tables. + * + * @return bool + */ + public function supportsAlterTable() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::supportsAlterTable() is deprecated. All platforms must implement altering tables.', + ); + + return true; + } + + /** + * Whether the platform supports transactions. + * + * @deprecated + * + * @return bool + */ + public function supportsTransactions() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::supportsTransactions() is deprecated.', + ); + + return true; + } + + /** + * Whether the platform supports savepoints. + * + * @return bool + */ + public function supportsSavepoints() + { + return true; + } + + /** + * Whether the platform supports releasing savepoints. + * + * @return bool + */ + public function supportsReleaseSavepoints() + { + return $this->supportsSavepoints(); + } + + /** + * Whether the platform supports primary key constraints. + * + * @deprecated + * + * @return bool + */ + public function supportsPrimaryConstraints() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::supportsPrimaryConstraints() is deprecated.', + ); + + return true; + } + + /** + * Whether the platform supports foreign key constraints. + * + * @deprecated All platforms should support foreign key constraints. + * + * @return bool + */ + public function supportsForeignKeyConstraints() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5409', + 'AbstractPlatform::supportsForeignKeyConstraints() is deprecated.', + ); + + return true; + } + + /** + * Whether the platform supports database schemas. + * + * @return bool + */ + public function supportsSchemas() + { + return false; + } + + /** + * Whether this platform can emulate schemas. + * + * @deprecated + * + * Platforms that either support or emulate schemas don't automatically + * filter a schema for the namespaced elements in {@see AbstractManager::introspectSchema()}. + * + * @return bool + */ + public function canEmulateSchemas() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4805', + 'AbstractPlatform::canEmulateSchemas() is deprecated.', + ); + + return false; + } + + /** + * Returns the default schema name. + * + * @deprecated + * + * @return string + * + * @throws Exception If not supported on this platform. + */ + public function getDefaultSchemaName() + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Whether this platform supports create database. + * + * Some databases don't allow to create and drop databases at all or only with certain tools. + * + * @deprecated + * + * @return bool + */ + public function supportsCreateDropDatabase() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } + + /** + * Whether the platform supports getting the affected rows of a recent update/delete type query. + * + * @deprecated + * + * @return bool + */ + public function supportsGettingAffectedRows() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::supportsGettingAffectedRows() is deprecated.', + ); + + return true; + } + + /** + * Whether this platform support to add inline column comments as postfix. + * + * @return bool + */ + public function supportsInlineColumnComments() + { + return false; + } + + /** + * Whether this platform support the proprietary syntax "COMMENT ON asset". + * + * @return bool + */ + public function supportsCommentOnStatement() + { + return false; + } + + /** + * Does this platform have native guid type. + * + * @deprecated + * + * @return bool + */ + public function hasNativeGuidType() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return false; + } + + /** + * Does this platform have native JSON type. + * + * @deprecated + * + * @return bool + */ + public function hasNativeJsonType() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return false; + } + + /** + * Whether this platform supports views. + * + * @deprecated All platforms must implement support for views. + * + * @return bool + */ + public function supportsViews() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::supportsViews() is deprecated. All platforms must implement support for views.', + ); + + return true; + } + + /** + * Does this platform support column collation? + * + * @return bool + */ + public function supportsColumnCollation() + { + return false; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored datetime value of this platform. + * + * @return string The format string. + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored datetime with timezone value of this platform. + * + * @return string The format string. + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored date value of this platform. + * + * @return string The format string. + */ + public function getDateFormatString() + { + return 'Y-m-d'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored time value of this platform. + * + * @return string The format string. + */ + public function getTimeFormatString() + { + return 'H:i:s'; + } + + /** + * Adds an driver-specific LIMIT clause to the query. + * + * @param string $query + * @param int|null $limit + * @param int $offset + * + * @throws Exception + */ + final public function modifyLimitQuery($query, $limit, $offset = 0): string + { + if ($offset < 0) { + throw new Exception(sprintf( + 'Offset must be a positive integer or zero, %d given', + $offset, + )); + } + + if ($offset > 0 && ! $this->supportsLimitOffset()) { + throw new Exception(sprintf( + 'Platform %s does not support offset values in limit queries.', + $this->getName(), + )); + } + + if ($limit !== null) { + $limit = (int) $limit; + } + + return $this->doModifyLimitQuery($query, $limit, (int) $offset); + } + + /** + * Adds an platform-specific LIMIT clause to the query. + * + * @param string $query + * @param int|null $limit + * @param int $offset + * + * @return string + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit !== null) { + $query .= sprintf(' LIMIT %d', $limit); + } + + if ($offset > 0) { + $query .= sprintf(' OFFSET %d', $offset); + } + + return $query; + } + + /** + * Whether the database platform support offsets in modify limit clauses. + * + * @deprecated All platforms must implement support for offsets in modify limit clauses. + * + * @return bool + */ + public function supportsLimitOffset() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4724', + 'AbstractPlatform::supportsViews() is deprecated.' + . ' All platforms must implement support for offsets in modify limit clauses.', + ); + + return true; + } + + /** + * Maximum length of any given database identifier, like tables or column names. + * + * @return int + */ + public function getMaxIdentifierLength() + { + return 63; + } + + /** + * Returns the insert SQL for an empty insert statement. + * + * @param string $quotedTableName + * @param string $quotedIdentifierColumnName + * + * @return string + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (null)'; + } + + /** + * Generates a Truncate Table SQL statement for a given table. + * + * Cascade is not supported on many platforms but would optionally cascade the truncate by + * following the foreign keys. + * + * @param string $tableName + * @param bool $cascade + * + * @return string + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); + } + + /** + * This is for test reasons, many vendors have special requirements for dummy statements. + * + * @return string + */ + public function getDummySelectSQL() + { + $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; + + return sprintf('SELECT %s', $expression); + } + + /** + * Returns the SQL to create a new savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function createSavePoint($savepoint) + { + return 'SAVEPOINT ' . $savepoint; + } + + /** + * Returns the SQL to release a savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function releaseSavePoint($savepoint) + { + return 'RELEASE SAVEPOINT ' . $savepoint; + } + + /** + * Returns the SQL to rollback a savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function rollbackSavePoint($savepoint) + { + return 'ROLLBACK TO SAVEPOINT ' . $savepoint; + } + + /** + * Returns the keyword list instance of this platform. + * + * @throws Exception If no keyword list is specified. + */ + final public function getReservedKeywordsList(): KeywordList + { + // Store the instance so it doesn't need to be generated on every request. + return $this->_keywords ??= $this->createReservedKeywordsList(); + } + + /** + * Creates an instance of the reserved keyword list of this platform. + * + * This method will become @abstract in DBAL 4.0.0. + * + * @throws Exception + */ + protected function createReservedKeywordsList(): KeywordList + { + $class = $this->getReservedKeywordsClass(); + $keywords = new $class(); + if (! $keywords instanceof KeywordList) { + throw Exception::notSupported(__METHOD__); + } + + return $keywords; + } + + /** + * Returns the class name of the reserved keywords list. + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + * + * @return string + * @psalm-return class-string + * + * @throws Exception If not supported on this platform. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'AbstractPlatform::getReservedKeywordsClass() is deprecated,' + . ' use AbstractPlatform::createReservedKeywordsList() instead.', + ); + + throw Exception::notSupported(__METHOD__); + } + + /** + * Quotes a literal string. + * This method is NOT meant to fix SQL injections! + * It is only meant to escape this platform's string literal + * quote character inside the given literal string. + * + * @param string $str The literal string to be quoted. + * + * @return string The quoted literal string. + */ + public function quoteStringLiteral($str) + { + $c = $this->getStringLiteralQuoteCharacter(); + + return $c . str_replace($c, $c . $c, $str) . $c; + } + + /** + * Gets the character used for string literal quoting. + * + * @deprecated Use {@see quoteStringLiteral()} to quote string literals instead. + * + * @return string + */ + public function getStringLiteralQuoteCharacter() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5388', + 'AbstractPlatform::getStringLiteralQuoteCharacter() is deprecated.' + . ' Use quoteStringLiteral() instead.', + ); + + return "'"; + } + + /** + * Escapes metacharacters in a string intended to be used with a LIKE + * operator. + * + * @param string $inputString a literal, unquoted string + * @param string $escapeChar should be reused by the caller in the LIKE + * expression. + */ + final public function escapeStringForLike(string $inputString, string $escapeChar): string + { + return preg_replace( + '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u', + addcslashes($escapeChar, '\\') . '$1', + $inputString, + ); + } + + /** + * @return array An associative array with the name of the properties + * of the column being declared as array indexes. + */ + private function columnToArray(Column $column): array + { + $name = $column->getQuotedName($this); + + return array_merge($column->toArray(), [ + 'name' => $name, + 'version' => $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false, + 'comment' => $this->getColumnComment($column), + ]); + } + + /** @internal */ + public function createSQLParser(): Parser + { + return new Parser(false); + } + + protected function getLikeWildcardCharacters(): string + { + return '%_'; + } + + /** + * Compares the definitions of the given columns in the context of this platform. + * + * @throws Exception + */ + public function columnsEqual(Column $column1, Column $column2): bool + { + $column1Array = $this->columnToArray($column1); + $column2Array = $this->columnToArray($column2); + + // ignore explicit columnDefinition since it's not set on the Column generated by the SchemaManager + unset($column1Array['columnDefinition']); + unset($column2Array['columnDefinition']); + + if ( + $this->getColumnDeclarationSQL('', $column1Array) + !== $this->getColumnDeclarationSQL('', $column2Array) + ) { + return false; + } + + if (! $this->columnDeclarationsMatch($column1, $column2)) { + return false; + } + + // If the platform supports inline comments, all comparison is already done above + if ($this->supportsInlineColumnComments()) { + return true; + } + + if ($column1->getComment() !== $column2->getComment()) { + return false; + } + + // If disableTypeComments is true, we do not need to check types, all comparison is already done above + if ($this->disableTypeComments) { + return true; + } + + return $column1->getType() === $column2->getType(); + } + + /** + * Whether the database data type matches that expected for the doctrine type for the given colunms. + */ + private function columnDeclarationsMatch(Column $column1, Column $column2): bool + { + return ! ( + $column1->hasPlatformOption('declarationMismatch') || + $column2->hasPlatformOption('declarationMismatch') + ); + } + + /** + * Creates the schema manager that can be used to inspect and change the underlying + * database schema according to the dialect of the platform. + * + * @throws Exception + * + * @abstract + */ + public function createSchemaManager(Connection $connection): AbstractSchemaManager + { + throw Exception::notSupported(__METHOD__); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/DB2111Platform.php b/vendor/doctrine/dbal/src/Platforms/DB2111Platform.php new file mode 100644 index 0000000..40ab42f --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/DB2111Platform.php @@ -0,0 +1,40 @@ + 0) { + $query .= sprintf(' OFFSET %u ROWS', $offset); + } + + if ($limit !== null) { + if ($limit < 0) { + throw new Exception(sprintf('Limit must be a positive integer or zero, %d given', $limit)); + } + + $query .= sprintf(' FETCH %s %u ROWS ONLY', $offset === 0 ? 'FIRST' : 'NEXT', $limit); + } + + return $query; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/DB2Platform.php b/vendor/doctrine/dbal/src/Platforms/DB2Platform.php new file mode 100644 index 0000000..69234f4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/DB2Platform.php @@ -0,0 +1,1053 @@ +getCharMaxLength(); + } + + return parent::getVarcharTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $column) + { + // todo blob(n) with $column['length']; + return 'BLOB(1M)'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = [ + 'bigint' => Types::BIGINT, + 'binary' => Types::BINARY, + 'blob' => Types::BLOB, + 'character' => Types::STRING, + 'clob' => Types::TEXT, + 'date' => Types::DATE_MUTABLE, + 'decimal' => Types::DECIMAL, + 'double' => Types::FLOAT, + 'integer' => Types::INTEGER, + 'real' => Types::FLOAT, + 'smallint' => Types::SMALLINT, + 'time' => Types::TIME_MUTABLE, + 'timestamp' => Types::DATETIME_MUTABLE, + 'varbinary' => Types::BINARY, + 'varchar' => Types::STRING, + ]; + } + + /** + * {@inheritDoc} + */ + public function isCommentedDoctrineType(Type $doctrineType) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5058', + '%s() is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', + __METHOD__, + ); + + if ($doctrineType->getName() === Types::BOOLEAN) { + // We require a commented boolean type in order to distinguish between boolean and smallint + // as both (have to) map to the same native type. + return true; + } + + return parent::isCommentedDoctrineType($doctrineType); + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default string column length on IBM DB2 is deprecated' + . ', specify the length explicitly.', + ); + } + + return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(254)') + : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritDoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default binary column length on IBM DB2 is deprecated' + . ', specify the length explicitly.', + ); + } + + return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed) . ' FOR BIT DATA'; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $column) + { + // todo clob(n) with $column['length']; + return 'CLOB(1M)'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4749', + '%s() is deprecated. Identify platforms by their class.', + __METHOD__, + ); + + return 'db2'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $column) + { + return 'SMALLINT'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $column) + { + return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $column) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $column) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $column) + { + $autoinc = ''; + if (! empty($column['autoincrement'])) { + $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; + } + + return $autoinc; + } + + /** + * {@inheritDoc} + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return 'BITAND(' . $value1 . ', ' . $value2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return 'BITOR(' . $value1 . ', ' . $value2 . ')'; + } + + /** + * {@inheritDoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + switch ($unit) { + case DateIntervalUnit::WEEK: + $interval = $this->multiplyInterval((string) $interval, 7); + $unit = DateIntervalUnit::DAY; + break; + + case DateIntervalUnit::QUARTER: + $interval = $this->multiplyInterval((string) $interval, 3); + $unit = DateIntervalUnit::MONTH; + break; + } + + return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + if (isset($column['version']) && $column['version'] === true) { + return 'TIMESTAMP(0) WITH DEFAULT'; + } + + return 'TIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $column) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE'; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited. + * + * @param string $table + * @param string $database + * + * @return string + */ + public function getListTableColumnsSQL($table, $database = null) + { + $table = $this->quoteStringLiteral($table); + + // We do the funky subquery and join syscat.columns.default this crazy way because + // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB, + // it wants shorter stuff like a varchar. + return " + SELECT + cols.default, + subq.* + FROM ( + SELECT DISTINCT + c.tabschema, + c.tabname, + c.colname, + c.colno, + c.typename, + c.codepage, + c.nulls, + c.length, + c.scale, + c.identity, + tc.type AS tabconsttype, + c.remarks AS comment, + k.colseq, + CASE + WHEN c.generated = '" . self::SYSCAT_COLUMNS_GENERATED_DEFAULT . "' THEN 1 + ELSE 0 + END AS autoincrement + FROM syscat.columns c + LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc + ON (k.tabschema = tc.tabschema + AND k.tabname = tc.tabname + AND tc.type = '" . self::SYSCAT_TABCONST_TYPE_PRIMARY_KEY . "')) + ON (c.tabschema = k.tabschema + AND c.tabname = k.tabname + AND c.colname = k.colname) + WHERE UPPER(c.tabname) = UPPER(" . $table . ') + ORDER BY c.colno + ) subq + JOIN syscat.columns cols + ON subq.tabschema = cols.tabschema + AND subq.tabname = cols.tabname + AND subq.colno = cols.colno + ORDER BY subq.colno + '; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = '" . self::SYSIBM_SYSTABLES_TYPE_TABLE . "'" + . ' AND CREATOR = CURRENT_USER'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListViewsSQL($database) + { + return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS'; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $database = null) + { + $table = $this->quoteStringLiteral($table); + + return "SELECT idx.INDNAME AS key_name, + idxcol.COLNAME AS column_name, + CASE + WHEN idx.UNIQUERULE = '" . self::SYSCAT_INDEXES_UNIQUERULE_IMPLEMENTS_PRIMARY_KEY . "' + THEN 1 + ELSE 0 + END AS primary, + CASE + WHEN idx.UNIQUERULE = '" . self::SYSCAT_INDEXES_UNIQUERULE_PERMITS_DUPLICATES . "' + THEN 1 + ELSE 0 + END AS non_unique + FROM SYSCAT.INDEXES AS idx + JOIN SYSCAT.INDEXCOLUSE AS idxcol + ON idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME + WHERE idx.TABNAME = UPPER(" . $table . ') + ORDER BY idxcol.COLSEQ ASC'; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table) + { + $table = $this->quoteStringLiteral($table); + + return "SELECT fkcol.COLNAME AS local_column, + fk.REFTABNAME AS foreign_table, + pkcol.COLNAME AS foreign_column, + fk.CONSTNAME AS index_name, + CASE + WHEN fk.UPDATERULE = '" . self::SYSCAT_REFERENCES_UPDATERULE_RESTRICT . "' THEN 'RESTRICT' + ELSE NULL + END AS on_update, + CASE + WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_CASCADE . "' THEN 'CASCADE' + WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_SET_NULL . "' THEN 'SET NULL' + WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_RESTRICT . "' THEN 'RESTRICT' + ELSE NULL + END AS on_delete + FROM SYSCAT.REFERENCES AS fk + JOIN SYSCAT.KEYCOLUSE AS fkcol + ON fk.CONSTNAME = fkcol.CONSTNAME + AND fk.TABSCHEMA = fkcol.TABSCHEMA + AND fk.TABNAME = fkcol.TABNAME + JOIN SYSCAT.KEYCOLUSE AS pkcol + ON fk.REFKEYNAME = pkcol.CONSTNAME + AND fk.REFTABSCHEMA = pkcol.TABSCHEMA + AND fk.REFTABNAME = pkcol.TABNAME + WHERE fk.TABNAME = UPPER(" . $table . ') + ORDER BY fkcol.COLSEQ ASC'; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function supportsCreateDropDatabase() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s() is deprecated.', + __METHOD__, + ); + + return false; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getCurrentDateSQL() + { + return 'CURRENT DATE'; + } + + /** + * {@inheritDoc} + */ + public function getCurrentTimeSQL() + { + return 'CURRENT TIME'; + } + + /** + * {@inheritDoc} + */ + public function getCurrentTimestampSQL() + { + return 'CURRENT TIMESTAMP'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getIndexDeclarationSQL($name, Index $index) + { + // Index declaration in statements like CREATE TABLE is not supported. + throw Exception::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $indexes = []; + if (isset($options['indexes'])) { + $indexes = $options['indexes']; + } + + $options['indexes'] = []; + + $sqls = parent::_getCreateTableSQL($name, $columns, $options); + + foreach ($indexes as $definition) { + $sqls[] = $this->getCreateIndexSQL($definition, $name); + } + + return $sqls; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = []; + $columnSql = []; + $commentsSQL = []; + + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + + $queryParts = []; + foreach ($diff->getAddedColumns() as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnDef = $column->toArray(); + $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); + + // Adding non-nullable columns to a table requires a default value to be specified. + if ( + ! empty($columnDef['notnull']) && + ! isset($columnDef['default']) && + empty($columnDef['autoincrement']) + ) { + $queryPart .= ' WITH DEFAULT'; + } + + $queryParts[] = $queryPart; + + $comment = $this->getColumnComment($column); + + if ($comment === null || $comment === '') { + continue; + } + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $tableNameSQL, + $column->getQuotedName($this), + $comment, + ); + } + + foreach ($diff->getDroppedColumns() as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); + } + + foreach ($diff->getModifiedColumns() as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if ($columnDiff->hasCommentChanged()) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $tableNameSQL, + $columnDiff->getNewColumn()->getQuotedName($this), + $this->getColumnComment($columnDiff->getNewColumn()), + ); + } + + $this->gatherAlterColumnSQL( + $tableNameSQL, + $columnDiff, + $sql, + $queryParts, + ); + } + + foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . + ' TO ' . $column->getQuotedName($this); + } + + $tableSql = []; + + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . implode(' ', $queryParts); + } + + // Some table alteration operations require a table reorganization. + if (count($diff->getDroppedColumns()) > 0 || count($diff->getModifiedColumns()) > 0) { + $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $tableNameSQL . "')"; + } + + $sql = array_merge($sql, $commentsSQL); + + $newName = $diff->getNewName(); + + if ($newName !== false) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + 'Generation of "rename table" SQL using %s() is deprecated. Use getRenameTableSQL() instead.', + __METHOD__, + ); + + $sql[] = sprintf( + 'RENAME TABLE %s TO %s', + $tableNameSQL, + $newName->getQuotedName($this), + ); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff), + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + public function getRenameTableSQL(string $oldName, string $newName): array + { + return [ + sprintf('RENAME TABLE %s TO %s', $oldName, $newName), + ]; + } + + /** + * Gathers the table alteration SQL for a given column diff. + * + * @param string $table The table to gather the SQL for. + * @param ColumnDiff $columnDiff The column diff to evaluate. + * @param string[] $sql The sequence of table alteration statements to fill. + * @param mixed[] $queryParts The sequence of column alteration clauses to fill. + */ + private function gatherAlterColumnSQL( + string $table, + ColumnDiff $columnDiff, + array &$sql, + array &$queryParts + ): void { + $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff); + + if (empty($alterColumnClauses)) { + return; + } + + // If we have a single column alteration, we can append the clause to the main query. + if (count($alterColumnClauses) === 1) { + $queryParts[] = current($alterColumnClauses); + + return; + } + + // We have multiple alterations for the same column, + // so we need to trigger a complete ALTER TABLE statement + // for each ALTER COLUMN clause. + foreach ($alterColumnClauses as $alterColumnClause) { + $sql[] = 'ALTER TABLE ' . $table . ' ' . $alterColumnClause; + } + } + + /** + * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff. + * + * @return string[] + */ + private function getAlterColumnClausesSQL(ColumnDiff $columnDiff): array + { + $newColumn = $columnDiff->getNewColumn()->toArray(); + + $alterClause = 'ALTER COLUMN ' . $columnDiff->getNewColumn()->getQuotedName($this); + + if ($newColumn['columnDefinition'] !== null) { + return [$alterClause . ' ' . $newColumn['columnDefinition']]; + } + + $clauses = []; + + if ( + $columnDiff->hasTypeChanged() || + $columnDiff->hasLengthChanged() || + $columnDiff->hasPrecisionChanged() || + $columnDiff->hasScaleChanged() || + $columnDiff->hasFixedChanged() + ) { + $clauses[] = $alterClause . ' SET DATA TYPE ' . $newColumn['type']->getSQLDeclaration($newColumn, $this); + } + + if ($columnDiff->hasNotNullChanged()) { + $clauses[] = $newColumn['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL'; + } + + if ($columnDiff->hasDefaultChanged()) { + if (isset($newColumn['default'])) { + $defaultClause = $this->getDefaultValueDeclarationSQL($newColumn); + + if ($defaultClause !== '') { + $clauses[] = $alterClause . ' SET' . $defaultClause; + } + } else { + $clauses[] = $alterClause . ' DROP DEFAULT'; + } + } + + return $clauses; + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $sql = []; + + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + + foreach ($diff->getDroppedIndexes() as $droppedIndex) { + foreach ($diff->getAddedIndexes() as $addedIndex) { + if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { + continue; + } + + if ($droppedIndex->isPrimary()) { + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP PRIMARY KEY'; + } elseif ($droppedIndex->isUnique()) { + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP UNIQUE ' . $droppedIndex->getQuotedName($this); + } else { + $sql[] = $this->getDropIndexSQL($droppedIndex, $tableNameSQL); + } + + $sql[] = $this->getCreateIndexSQL($addedIndex, $tableNameSQL); + + $diff->unsetAddedIndex($addedIndex); + $diff->unsetDroppedIndex($droppedIndex); + + break; + } + } + + return array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); + } + + /** + * {@inheritDoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + if (strpos($tableName, '.') !== false) { + [$schema] = explode('.', $tableName); + $oldIndexName = $schema . '.' . $oldIndexName; + } + + return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getDefaultValueDeclarationSQL($column) + { + if (! empty($column['autoincrement'])) { + return ''; + } + + if (! empty($column['version'])) { + if ((string) $column['type'] !== 'DateTime') { + $column['default'] = '1'; + } + } + + return parent::getDefaultValueDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return 'DECLARE GLOBAL TEMPORARY TABLE'; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + return 'SESSION.' . $tableName; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + $where = []; + + if ($offset > 0) { + $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1); + } + + if ($limit !== null) { + $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit); + } + + if (empty($where)) { + return $query; + } + + // Todo OVER() needs ORDER BY data! + return sprintf( + 'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s', + $query, + implode(' AND ', $where), + ); + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos === false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($string, $start, $length = null) + { + if ($length === null) { + return 'SUBSTR(' . $string . ', ' . $start . ')'; + } + + return 'SUBSTR(' . $string . ', ' . $start . ', ' . $length . ')'; + } + + /** + * {@inheritDoc} + */ + public function getLengthExpression($column) + { + return 'LENGTH(' . $column . ', CODEUNITS32)'; + } + + public function getCurrentDatabaseExpression(): string + { + return 'CURRENT_USER'; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function prefersIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/1519', + '%s() is deprecated.', + __METHOD__, + ); + + return true; + } + + public function createSelectSQLBuilder(): SelectSQLBuilder + { + return new DefaultSelectSQLBuilder($this, 'WITH RR USE AND KEEP UPDATE LOCKS', null); + } + + /** + * {@inheritDoc} + * + * @deprecated This API is not portable. + */ + public function getForUpdateSQL() + { + return ' WITH RR USE AND KEEP UPDATE LOCKS'; + } + + /** + * {@inheritDoc} + */ + public function getDummySelectSQL() + { + $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; + + return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression); + } + + /** + * {@inheritDoc} + * + * DB2 supports savepoints, but they work semantically different than on other vendor platforms. + * + * TODO: We have to investigate how to get DB2 up and running with savepoints. + */ + public function supportsSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + '%s() is deprecated,' + . ' use %s::createReservedKeywordsList() instead.', + __METHOD__, + static::class, + ); + + return Keywords\DB2Keywords::class; + } + + /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ + public function getListTableCommentsSQL(string $table): string + { + return sprintf( + <<<'SQL' +SELECT REMARKS + FROM SYSIBM.SYSTABLES + WHERE NAME = UPPER( %s ) +SQL + , + $this->quoteStringLiteral($table), + ); + } + + public function createSchemaManager(Connection $connection): DB2SchemaManager + { + return new DB2SchemaManager($connection, $this); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php b/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php new file mode 100644 index 0000000..a95c4e2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php @@ -0,0 +1,29 @@ +keywords === null) { + $this->initializeKeywords(); + } + + return isset($this->keywords[strtoupper($word)]); + } + + /** @return void */ + protected function initializeKeywords() + { + $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords())); + } + + /** + * Returns the list of keywords. + * + * @return string[] + */ + abstract protected function getKeywords(); + + /** + * Returns the name of this keyword list. + * + * @deprecated + * + * @return string + */ + abstract public function getName(); +} diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php new file mode 100644 index 0000000..5417c6c --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php @@ -0,0 +1,276 @@ +keywordLists = $keywordLists; + } + + /** @return string[] */ + public function getViolations() + { + return $this->violations; + } + + /** + * @param string $word + * + * @return string[] + */ + private function isReservedWord($word): array + { + if ($word[0] === '`') { + $word = str_replace('`', '', $word); + } + + $keywordLists = []; + foreach ($this->keywordLists as $keywordList) { + if (! $keywordList->isKeyword($word)) { + continue; + } + + $keywordLists[] = $keywordList->getName(); + } + + return $keywordLists; + } + + /** + * @param string $asset + * @param string[] $violatedPlatforms + */ + private function addViolation($asset, $violatedPlatforms): void + { + if (count($violatedPlatforms) === 0) { + return; + } + + $this->violations[] = $asset . ' keyword violations: ' . implode(', ', $violatedPlatforms); + } + + /** + * {@inheritDoc} + */ + public function acceptColumn(Table $table, Column $column) + { + $this->addViolation( + 'Table ' . $table->getName() . ' column ' . $column->getName(), + $this->isReservedWord($column->getName()), + ); + } + + /** + * {@inheritDoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritDoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritDoc} + */ + public function acceptSchema(Schema $schema) + { + } + + /** + * {@inheritDoc} + */ + public function acceptSequence(Sequence $sequence) + { + } + + /** + * {@inheritDoc} + */ + public function acceptTable(Table $table) + { + $this->addViolation( + 'Table ' . $table->getName(), + $this->isReservedWord($table->getName()), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServer2012Keywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServer2012Keywords.php new file mode 100644 index 0000000..ebc45c4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServer2012Keywords.php @@ -0,0 +1,12 @@ +doctrineTypeMapping['json'] = Types::JSON; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDb1010Platform.php b/vendor/doctrine/dbal/src/Platforms/MariaDb1010Platform.php new file mode 100644 index 0000000..6cddbdd --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MariaDb1010Platform.php @@ -0,0 +1,47 @@ +getColumnTypeSQLSnippets('c', $database); + + return sprintf( + <<getDatabaseNameSQL($database), + $this->quoteStringLiteral($table), + ); + } + + /** + * Generate SQL snippets to reverse the aliasing of JSON to LONGTEXT. + * + * MariaDb aliases columns specified as JSON to LONGTEXT and sets a CHECK constraint to ensure the column + * is valid json. This function generates the SQL snippets which reverse this aliasing i.e. report a column + * as JSON where it was originally specified as such instead of LONGTEXT. + * + * The CHECK constraints are stored in information_schema.CHECK_CONSTRAINTS so query that table. + */ + public function getColumnTypeSQLSnippet(string $tableAlias = 'c', ?string $databaseName = null): string + { + if ($this->getJsonTypeDeclarationSQL([]) !== 'JSON') { + return parent::getColumnTypeSQLSnippet($tableAlias, $databaseName); + } + + if ($databaseName === null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6215', + 'Not passing a database name to methods "getColumnTypeSQLSnippet()", ' + . '"getColumnTypeSQLSnippets()", and "getListTableColumnsSQL()" of "%s" is deprecated.', + self::class, + ); + } + + $subQueryAlias = 'i_' . $tableAlias; + + $databaseName = $this->getDatabaseNameSQL($databaseName); + + // The check for `CONSTRAINT_SCHEMA = $databaseName` is mandatory here to prevent performance issues + return <<getJsonTypeDeclarationSQL([]) === 'JSON' && ($column['type'] ?? null) instanceof JsonType) { + unset($column['collation']); + unset($column['charset']); + } + + return parent::getColumnDeclarationSQL($name, $column); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDb1052Platform.php b/vendor/doctrine/dbal/src/Platforms/MariaDb1052Platform.php new file mode 100644 index 0000000..a2199cd --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MariaDb1052Platform.php @@ -0,0 +1,36 @@ +getQuotedName($this)]; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDb1060Platform.php b/vendor/doctrine/dbal/src/Platforms/MariaDb1060Platform.php new file mode 100644 index 0000000..b3ce4eb --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MariaDb1060Platform.php @@ -0,0 +1,16 @@ + */ + private $cache = []; + + public function __construct(CollationMetadataProvider $collationMetadataProvider) + { + $this->collationMetadataProvider = $collationMetadataProvider; + } + + public function getCollationCharset(string $collation): ?string + { + if (array_key_exists($collation, $this->cache)) { + return $this->cache[$collation]; + } + + return $this->cache[$collation] = $this->collationMetadataProvider->getCollationCharset($collation); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php new file mode 100644 index 0000000..8dc2421 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php @@ -0,0 +1,41 @@ +connection = $connection; + } + + /** @throws Exception */ + public function getCollationCharset(string $collation): ?string + { + $charset = $this->connection->fetchOne( + <<<'SQL' +SELECT CHARACTER_SET_NAME +FROM information_schema.COLLATIONS +WHERE COLLATION_NAME = ?; +SQL + , + [$collation], + ); + + if ($charset !== false) { + return $charset; + } + + return null; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php b/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php new file mode 100644 index 0000000..c27fef7 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php @@ -0,0 +1,94 @@ +collationMetadataProvider = $collationMetadataProvider; + } + + public function compareTables(Table $fromTable, Table $toTable): TableDiff + { + return parent::compareTables( + $this->normalizeColumns($fromTable), + $this->normalizeColumns($toTable), + ); + } + + /** + * {@inheritDoc} + */ + public function diffTable(Table $fromTable, Table $toTable) + { + return parent::diffTable( + $this->normalizeColumns($fromTable), + $this->normalizeColumns($toTable), + ); + } + + private function normalizeColumns(Table $table): Table + { + $tableOptions = array_intersect_key($table->getOptions(), [ + 'charset' => null, + 'collation' => null, + ]); + + $table = clone $table; + + foreach ($table->getColumns() as $column) { + $originalOptions = $column->getPlatformOptions(); + $normalizedOptions = $this->normalizeOptions($originalOptions); + + $overrideOptions = array_diff_assoc($normalizedOptions, $tableOptions); + + if ($overrideOptions === $originalOptions) { + continue; + } + + $column->setPlatformOptions($overrideOptions); + } + + return $table; + } + + /** + * @param array $options + * + * @return array + */ + private function normalizeOptions(array $options): array + { + if (isset($options['collation']) && ! isset($options['charset'])) { + $charset = $this->collationMetadataProvider->getCollationCharset($options['collation']); + + if ($charset !== null) { + $options['charset'] = $charset; + } + } + + return $options; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL57Platform.php b/vendor/doctrine/dbal/src/Platforms/MySQL57Platform.php new file mode 100644 index 0000000..d5169bd --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL57Platform.php @@ -0,0 +1,99 @@ +getQuotedName($this)]; + } + + /** + * {@inheritDoc} + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'MySQL57Platform::getReservedKeywordsClass() is deprecated,' + . ' use MySQL57Platform::createReservedKeywordsList() instead.', + ); + + return Keywords\MySQL57Keywords::class; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + parent::initializeDoctrineTypeMappings(); + + $this->doctrineTypeMapping['json'] = Types::JSON; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php b/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php new file mode 100644 index 0000000..d932428 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php @@ -0,0 +1,34 @@ +multiplyInterval((string) $interval, 3); + break; + + case DateIntervalUnit::YEAR: + $interval = $this->multiplyInterval((string) $interval, 12); + break; + } + + return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')'; + + default: + $calculationClause = ''; + + switch ($unit) { + case DateIntervalUnit::SECOND: + $calculationClause = '/24/60/60'; + break; + + case DateIntervalUnit::MINUTE: + $calculationClause = '/24/60'; + break; + + case DateIntervalUnit::HOUR: + $calculationClause = '/24'; + break; + + case DateIntervalUnit::WEEK: + $calculationClause = '*7'; + break; + } + + return '(' . $date . $operator . $interval . $calculationClause . ')'; + } + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2); + } + + /** + * {@inheritDoc} + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return 'BITAND(' . $value1 . ', ' . $value2 . ')'; + } + + public function getCurrentDatabaseExpression(): string + { + return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')"; + } + + /** + * {@inheritDoc} + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return '(' . $value1 . '-' . + $this->getBitAndComparisonExpression($value1, $value2) + . '+' . $value2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table): string + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $index->getQuotedName($this) + . ' PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; + } + + /** + * {@inheritDoc} + * + * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. + * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection + * in {@see listSequences()} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' START WITH ' . $sequence->getInitialValue() . + ' MINVALUE ' . $sequence->getInitialValue() . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * {@inheritDoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() + . $this->getSequenceCacheSQL($sequence); + } + + /** + * Cache definition for sequences + */ + private function getSequenceCacheSQL(Sequence $sequence): string + { + if ($sequence->getCache() === 0) { + return ' NOCACHE'; + } + + if ($sequence->getCache() === 1) { + return ' NOCACHE'; + } + + if ($sequence->getCache() > 1) { + return ' CACHE ' . $sequence->getCache(); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequence) + { + return 'SELECT ' . $sequence . '.nextval FROM DUAL'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case TransactionIsolationLevel::READ_UNCOMMITTED: + return 'READ UNCOMMITTED'; + + case TransactionIsolationLevel::READ_COMMITTED: + return 'READ COMMITTED'; + + case TransactionIsolationLevel::REPEATABLE_READ: + case TransactionIsolationLevel::SERIALIZABLE: + return 'SERIALIZABLE'; + + default: + return parent::_getTransactionIsolationLevelSQL($level); + } + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $column) + { + return 'NUMBER(1)'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $column) + { + return 'NUMBER(10)'; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $column) + { + return 'NUMBER(20)'; + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $column) + { + return 'NUMBER(5)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + return 'TIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $column) + { + return 'TIMESTAMP(0) WITH TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $column) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default string column length on Oracle is deprecated' + . ', specify the length explicitly.', + ); + } + + return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(2000)') + : ($length > 0 ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); + } + + /** + * {@inheritDoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default binary column length on Oracle is deprecated' + . ', specify the length explicitly.', + ); + } + + return 'RAW(' . ($length > 0 ? $length : $this->getBinaryMaxLength()) . ')'; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getBinaryMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'OraclePlatform::getBinaryMaxLength() is deprecated.', + ); + + return 2000; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $column) + { + return 'CLOB'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListDatabasesSQL() + { + return 'SELECT username FROM all_users'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListSequencesSQL($database) + { + $database = $this->normalizeIdentifier($database); + $database = $this->quoteStringLiteral($database->getName()); + + return 'SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ' . + 'WHERE SEQUENCE_OWNER = ' . $database; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $indexes = $options['indexes'] ?? []; + $options['indexes'] = []; + $sql = parent::_getCreateTableSQL($name, $columns, $options); + + foreach ($columns as $columnName => $column) { + if (isset($column['sequence'])) { + $sql[] = $this->getCreateSequenceSQL($column['sequence']); + } + + if ( + ! isset($column['autoincrement']) || ! $column['autoincrement'] && + (! isset($column['autoinc']) || ! $column['autoinc']) + ) { + continue; + } + + $sql = array_merge($sql, $this->getCreateAutoincrementSql($columnName, $name)); + } + + foreach ($indexes as $index) { + $sql[] = $this->getCreateIndexSQL($index, $name); + } + + return $sql; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + * + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html + */ + public function getListTableIndexesSQL($table, $database = null) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return "SELECT uind_col.index_name AS name, + ( + SELECT uind.index_type + FROM user_indexes uind + WHERE uind.index_name = uind_col.index_name + ) AS type, + decode( + ( + SELECT uind.uniqueness + FROM user_indexes uind + WHERE uind.index_name = uind_col.index_name + ), + 'NONUNIQUE', + 0, + 'UNIQUE', + 1 + ) AS is_unique, + uind_col.column_name AS column_name, + uind_col.column_position AS column_pos, + ( + SELECT ucon.constraint_type + FROM user_constraints ucon + WHERE ucon.index_name = uind_col.index_name + AND ucon.table_name = uind_col.table_name + ) AS is_primary + FROM user_ind_columns uind_col + WHERE uind_col.table_name = " . $table . ' + ORDER BY uind_col.column_position ASC'; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return 'SELECT * FROM sys.user_tables'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListViewsSQL($database) + { + return 'SELECT view_name, text FROM sys.user_views'; + } + + /** + * @internal The method should be only used from within the OraclePlatform class hierarchy. + * + * @param string $name + * @param string $table + * @param int $start + * + * @return string[] + */ + public function getCreateAutoincrementSql($name, $table, $start = 1) + { + $tableIdentifier = $this->normalizeIdentifier($table); + $quotedTableName = $tableIdentifier->getQuotedName($this); + $unquotedTableName = $tableIdentifier->getName(); + + $nameIdentifier = $this->normalizeIdentifier($name); + $quotedName = $nameIdentifier->getQuotedName($this); + $unquotedName = $nameIdentifier->getName(); + + $sql = []; + + $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier); + + $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true); + + $sql[] = "DECLARE + constraints_Count NUMBER; +BEGIN + SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count + FROM USER_CONSTRAINTS + WHERE TABLE_NAME = '" . $unquotedTableName . "' + AND CONSTRAINT_TYPE = 'P'; + IF constraints_Count = 0 OR constraints_Count = '' THEN + EXECUTE IMMEDIATE '" . $this->getCreateConstraintSQL($idx, $quotedTableName) . "'; + END IF; +END;"; + + $sequenceName = $this->getIdentitySequenceName( + $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName, + $nameIdentifier->isQuoted() ? $quotedName : $unquotedName, + ); + $sequence = new Sequence($sequenceName, $start); + $sql[] = $this->getCreateSequenceSQL($sequence); + + $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . ' + BEFORE INSERT + ON ' . $quotedTableName . ' + FOR EACH ROW +DECLARE + last_Sequence NUMBER; + last_InsertID NUMBER; +BEGIN + IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN + SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; + ELSE + SELECT NVL(Last_Number, 0) INTO last_Sequence + FROM User_Sequences + WHERE Sequence_Name = \'' . $sequence->getName() . '\'; + SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL; + WHILE (last_InsertID > last_Sequence) LOOP + SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; + END LOOP; + SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; + END IF; +END;'; + + return $sql; + } + + /** + * @internal The method should be only used from within the OracleSchemaManager class hierarchy. + * + * Returns the SQL statements to drop the autoincrement for the given table name. + * + * @param string $table The table name to drop the autoincrement for. + * + * @return string[] + */ + public function getDropAutoincrementSql($table) + { + $table = $this->normalizeIdentifier($table); + $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table); + $identitySequenceName = $this->getIdentitySequenceName( + $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(), + '', + ); + + return [ + 'DROP TRIGGER ' . $autoincrementIdentifierName, + $this->getDropSequenceSQL($identitySequenceName), + $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)), + ]; + } + + /** + * Normalizes the given identifier. + * + * Uppercases the given identifier if it is not quoted by intention + * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers. + * + * @param string $name The identifier to normalize. + */ + private function normalizeIdentifier($name): Identifier + { + $identifier = new Identifier($name); + + return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name)); + } + + /** + * Adds suffix to identifier, + * + * if the new string exceeds max identifier length, + * keeps $suffix, cuts from $identifier as much as the part exceeding. + */ + private function addSuffix(string $identifier, string $suffix): string + { + $maxPossibleLengthWithoutSuffix = $this->getMaxIdentifierLength() - strlen($suffix); + if (strlen($identifier) > $maxPossibleLengthWithoutSuffix) { + $identifier = substr($identifier, 0, $maxPossibleLengthWithoutSuffix); + } + + return $identifier . $suffix; + } + + /** + * Returns the autoincrement primary key identifier name for the given table identifier. + * + * Quotes the autoincrement primary key identifier name + * if the given table name is quoted by intention. + */ + private function getAutoincrementIdentifierName(Identifier $table): string + { + $identifierName = $this->addSuffix($table->getName(), '_AI_PK'); + + return $table->isQuoted() + ? $this->quoteSingleIdentifier($identifierName) + : $identifierName; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return "SELECT alc.constraint_name, + alc.DELETE_RULE, + cols.column_name \"local_column\", + cols.position, + ( + SELECT r_cols.table_name + FROM user_cons_columns r_cols + WHERE alc.r_constraint_name = r_cols.constraint_name + AND r_cols.position = cols.position + ) AS \"references_table\", + ( + SELECT r_cols.column_name + FROM user_cons_columns r_cols + WHERE alc.r_constraint_name = r_cols.constraint_name + AND r_cols.position = cols.position + ) AS \"foreign_column\" + FROM user_cons_columns cols + JOIN user_constraints alc + ON alc.constraint_name = cols.constraint_name + AND alc.constraint_type = 'R' + AND alc.table_name = " . $table . ' + ORDER BY cols.constraint_name ASC, cols.position ASC'; + } + + /** + * @deprecated + * + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return 'SELECT * FROM user_constraints WHERE table_name = ' . $table; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + $tabColumnsTableName = 'user_tab_columns'; + $colCommentsTableName = 'user_col_comments'; + $tabColumnsOwnerCondition = ''; + $colCommentsOwnerCondition = ''; + + if ($database !== null && $database !== '/') { + $database = $this->normalizeIdentifier($database); + $database = $this->quoteStringLiteral($database->getName()); + $tabColumnsTableName = 'all_tab_columns'; + $colCommentsTableName = 'all_col_comments'; + $tabColumnsOwnerCondition = ' AND c.owner = ' . $database; + $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER'; + } + + return sprintf( + <<<'SQL' +SELECT c.*, + ( + SELECT d.comments + FROM %s d + WHERE d.TABLE_NAME = c.TABLE_NAME%s + AND d.COLUMN_NAME = c.COLUMN_NAME + ) AS comments +FROM %s c +WHERE c.table_name = %s%s +ORDER BY c.column_id +SQL + , + $colCommentsTableName, + $colCommentsOwnerCondition, + $tabColumnsTableName, + $table, + $tabColumnsOwnerCondition, + ); + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if ($foreignKey instanceof ForeignKeyConstraint) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' + . ' Pass it as a quoted name instead.', + __METHOD__, + ); + } else { + $foreignKey = new Identifier($foreignKey); + } + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + } else { + $table = new Identifier($table); + } + + $foreignKey = $foreignKey->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $referentialAction = ''; + + if ($foreignKey->hasOption('onDelete')) { + $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); + } + + if ($referentialAction !== '') { + return ' ON DELETE ' . $referentialAction; + } + + return ''; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getForeignKeyReferentialActionSQL($action) + { + $action = strtoupper($action); + + switch ($action) { + case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION. + case 'NO ACTION': + // NO ACTION cannot be declared explicitly, + // therefore returning empty string to indicate to OMIT the referential clause. + return ''; + + case 'CASCADE': + case 'SET NULL': + return $action; + + default: + // SET DEFAULT is not supported, throw exception instead. + throw new InvalidArgumentException('Invalid foreign key action: ' . $action); + } + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE USER ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($name) + { + return 'DROP USER ' . $name . ' CASCADE'; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = []; + $commentsSQL = []; + $columnSql = []; + + $fields = []; + + $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); + + foreach ($diff->getAddedColumns() as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + $comment = $this->getColumnComment($column); + + if ($comment === null || $comment === '') { + continue; + } + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $tableNameSQL, + $column->getQuotedName($this), + $comment, + ); + } + + if (count($fields) > 0) { + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ADD (' . implode(', ', $fields) . ')'; + } + + $fields = []; + foreach ($diff->getModifiedColumns() as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $newColumn = $columnDiff->getNewColumn(); + + // Do not generate column alteration clause if type is binary and only fixed property has changed. + // Oracle only supports binary type columns with variable length. + // Avoids unnecessary table alteration statements. + if ( + $newColumn->getType() instanceof BinaryType && + $columnDiff->hasFixedChanged() && + count($columnDiff->changedProperties) === 1 + ) { + continue; + } + + $columnHasChangedComment = $columnDiff->hasCommentChanged(); + + /** + * Do not add query part if only comment has changed + */ + if (! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) { + $newColumnProperties = $newColumn->toArray(); + + if (! $columnDiff->hasNotNullChanged()) { + unset($newColumnProperties['notnull']); + } + + $fields[] = $newColumn->getQuotedName($this) . $this->getColumnDeclarationSQL('', $newColumnProperties); + } + + if (! $columnHasChangedComment) { + continue; + } + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $tableNameSQL, + $newColumn->getQuotedName($this), + $this->getColumnComment($newColumn), + ); + } + + if (count($fields) > 0) { + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY (' . implode(', ', $fields) . ')'; + } + + foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) + . ' TO ' . $column->getQuotedName($this); + } + + $fields = []; + foreach ($diff->getDroppedColumns() as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $fields[] = $column->getQuotedName($this); + } + + if (count($fields) > 0) { + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP (' . implode(', ', $fields) . ')'; + } + + $tableSql = []; + + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + $sql = array_merge($sql, $commentsSQL); + + $newName = $diff->getNewName(); + + if ($newName !== false) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', + __METHOD__, + ); + + $sql[] = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $tableNameSQL, + $newName->getQuotedName($this), + ); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff), + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getColumnDeclarationSQL($name, array $column) + { + if (isset($column['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($column); + } else { + $default = $this->getDefaultValueDeclarationSQL($column); + + $notnull = ''; + + if (isset($column['notnull'])) { + $notnull = $column['notnull'] ? ' NOT NULL' : ' NULL'; + } + + if (! empty($column['unique'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5656', + 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', + ); + + $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); + } else { + $unique = ''; + } + + if (! empty($column['check'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5656', + 'The usage of the "check" column property is deprecated.', + ); + + $check = ' ' . $column['check']; + } else { + $check = ''; + } + + $typeDecl = $column['type']->getSQLDeclaration($column, $this); + $columnDef = $typeDecl . $default . $notnull . $unique . $check; + } + + return $name . ' ' . $columnDef; + } + + /** + * {@inheritDoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + if (strpos($tableName, '.') !== false) { + [$schema] = explode('.', $tableName); + $oldIndexName = $schema . '.' . $oldIndexName; + } + + return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function usesSequenceEmulatedIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the OraclePlatform class hierarchy. + */ + public function getIdentitySequenceName($tableName, $columnName) + { + $table = new Identifier($tableName); + + // No usage of column name to preserve BC compatibility with <2.5 + $identitySequenceName = $this->addSuffix($table->getName(), '_SEQ'); + + if ($table->isQuoted()) { + $identitySequenceName = '"' . $identitySequenceName . '"'; + } + + $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName); + + return $identitySequenceIdentifier->getQuotedName($this); + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4749', + 'OraclePlatform::getName() is deprecated. Identify platforms by their class.', + ); + + return 'oracle'; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit === null && $offset <= 0) { + return $query; + } + + if (preg_match('/^\s*SELECT/i', $query) === 1) { + if (preg_match('/\sFROM\s/i', $query) === 0) { + $query .= ' FROM dual'; + } + + $columns = ['a.*']; + + if ($offset > 0) { + $columns[] = 'ROWNUM AS doctrine_rownum'; + } + + $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query); + + if ($limit !== null) { + $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit); + } + + if ($offset > 0) { + $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1); + } + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return 'CREATE GLOBAL TEMPORARY TABLE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:sP'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d 00:00:00'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return '1900-01-01 H:i:s'; + } + + /** + * {@inheritDoc} + */ + public function getMaxIdentifierLength() + { + return 30; + } + + /** + * {@inheritDoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); + } + + /** + * {@inheritDoc} + */ + public function getDummySelectSQL() + { + $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; + + return sprintf('SELECT %s FROM DUAL', $expression); + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = [ + 'binary_double' => Types::FLOAT, + 'binary_float' => Types::FLOAT, + 'binary_integer' => Types::BOOLEAN, + 'blob' => Types::BLOB, + 'char' => Types::STRING, + 'clob' => Types::TEXT, + 'date' => Types::DATE_MUTABLE, + 'float' => Types::FLOAT, + 'integer' => Types::INTEGER, + 'long' => Types::STRING, + 'long raw' => Types::BLOB, + 'nchar' => Types::STRING, + 'nclob' => Types::TEXT, + 'number' => Types::INTEGER, + 'nvarchar2' => Types::STRING, + 'pls_integer' => Types::BOOLEAN, + 'raw' => Types::BINARY, + 'rowid' => Types::STRING, + 'timestamp' => Types::DATETIME_MUTABLE, + 'timestamptz' => Types::DATETIMETZ_MUTABLE, + 'urowid' => Types::STRING, + 'varchar' => Types::STRING, + 'varchar2' => Types::STRING, + ]; + } + + /** + * {@inheritDoc} + */ + public function releaseSavePoint($savepoint) + { + return ''; + } + + /** + * {@inheritDoc} + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'OraclePlatform::getReservedKeywordsClass() is deprecated,' + . ' use OraclePlatform::createReservedKeywordsList() instead.', + ); + + return Keywords\OracleKeywords::class; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $column) + { + return 'BLOB'; + } + + /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ + public function getListTableCommentsSQL(string $table, ?string $database = null): string + { + $tableCommentsName = 'user_tab_comments'; + $ownerCondition = ''; + + if ($database !== null && $database !== '/') { + $tableCommentsName = 'all_tab_comments'; + $ownerCondition = ' AND owner = ' . $this->quoteStringLiteral( + $this->normalizeIdentifier($database)->getName(), + ); + } + + return sprintf( + <<<'SQL' +SELECT comments FROM %s WHERE table_name = %s%s +SQL + , + $tableCommentsName, + $this->quoteStringLiteral($this->normalizeIdentifier($table)->getName()), + $ownerCondition, + ); + } + + public function createSchemaManager(Connection $connection): OracleSchemaManager + { + return new OracleSchemaManager($connection, $this); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/PostgreSQL100Platform.php b/vendor/doctrine/dbal/src/Platforms/PostgreSQL100Platform.php new file mode 100644 index 0000000..6ee1f90 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/PostgreSQL100Platform.php @@ -0,0 +1,36 @@ + [ + 't', + 'true', + 'y', + 'yes', + 'on', + '1', + ], + 'false' => [ + 'f', + 'false', + 'n', + 'no', + 'off', + '0', + ], + ]; + + /** + * PostgreSQL has different behavior with some drivers + * with regard to how booleans have to be handled. + * + * Enables use of 'true'/'false' or otherwise 1 and 0 instead. + * + * @param bool $flag + * + * @return void + */ + public function setUseBooleanTrueFalseStrings($flag) + { + $this->useBooleanTrueFalseStrings = (bool) $flag; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($string, $start, $length = null) + { + if ($length === null) { + return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; + } + + return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; + } + + /** + * {@inheritDoc} + * + * @deprecated Generate dates within the application. + */ + public function getNowExpression() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4753', + 'PostgreSQLPlatform::getNowExpression() is deprecated. Generate dates within the application.', + ); + + return 'LOCALTIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'SIMILAR TO'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos !== false) { + $str = $this->getSubstringExpression($str, $startPos); + + return 'CASE WHEN (POSITION(' . $substr . ' IN ' . $str . ') = 0) THEN 0' + . ' ELSE (POSITION(' . $substr . ' IN ' . $str . ') + ' . $startPos . ' - 1) END'; + } + + return 'POSITION(' . $substr . ' IN ' . $str . ')'; + } + + /** + * {@inheritDoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + if ($unit === DateIntervalUnit::QUARTER) { + $interval = $this->multiplyInterval((string) $interval, 3); + $unit = DateIntervalUnit::MONTH; + } + + return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; + } + + public function getCurrentDatabaseExpression(): string + { + return 'CURRENT_DATABASE()'; + } + + /** + * {@inheritDoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getDefaultSchemaName() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return 'public'; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsPartialIndexes() + { + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function usesSequenceEmulatedIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getIdentitySequenceName($tableName, $columnName) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return $tableName . '_' . $columnName . '_seq'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function hasNativeGuidType() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } + + public function createSelectSQLBuilder(): SelectSQLBuilder + { + return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null); + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListDatabasesSQL() + { + return 'SELECT datname FROM pg_database'; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see PostgreSQLSchemaManager::listSchemaNames()} instead. + */ + public function getListNamespacesSQL() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'PostgreSQLPlatform::getListNamespacesSQL() is deprecated,' + . ' use PostgreSQLSchemaManager::listSchemaNames() instead.', + ); + + return "SELECT schema_name AS nspname + FROM information_schema.schemata + WHERE schema_name NOT LIKE 'pg\_%' + AND schema_name != 'information_schema'"; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListSequencesSQL($database) + { + return 'SELECT sequence_name AS relname, + sequence_schema AS schemaname, + minimum_value AS min_value, + increment AS increment_by + FROM information_schema.sequences + WHERE sequence_catalog = ' . $this->quoteStringLiteral($database) . " + AND sequence_schema NOT LIKE 'pg\_%' + AND sequence_schema != 'information_schema'"; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT quote_ident(table_name) AS table_name, + table_schema AS schema_name + FROM information_schema.tables + WHERE table_schema NOT LIKE 'pg\_%' + AND table_schema != 'information_schema' + AND table_name != 'geometry_columns' + AND table_name != 'spatial_ref_sys' + AND table_type != 'VIEW'"; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListViewsSQL($database) + { + return 'SELECT quote_ident(table_name) AS viewname, + table_schema AS schemaname, + view_definition AS definition + FROM information_schema.views + WHERE view_definition IS NOT NULL'; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * @param string $table + * @param string|null $database + * + * @return string + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef + FROM pg_catalog.pg_constraint r + WHERE r.conrelid = + ( + SELECT c.oid + FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n + WHERE ' . $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace + ) + AND r.contype = 'f'"; + } + + /** + * @deprecated + * + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = new Identifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return sprintf( + <<<'SQL' +SELECT + quote_ident(relname) as relname +FROM + pg_class +WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = %s + AND pg_class.oid = pg_index.indrelid + AND (indisunique = 't' OR indisprimary = 't') + ) +SQL + , + $table, + ); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + * + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + public function getListTableIndexesSQL($table, $database = null) + { + return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, + pg_index.indkey, pg_index.indrelid, + pg_get_expr(indpred, indrelid) AS where + FROM pg_class, pg_index + WHERE oid IN ( + SELECT indexrelid + FROM pg_index si, pg_class sc, pg_namespace sn + WHERE ' . $this->getTableWhereClause($table, 'sc', 'sn') . ' + AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid + ) AND pg_index.indexrelid = oid'; + } + + /** + * @param string $table + * @param string $classAlias + * @param string $namespaceAlias + */ + private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n'): string + { + $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table); + $schema = $this->quoteStringLiteral($schema); + } else { + $schema = 'ANY(current_schemas(false))'; + } + + $table = new Identifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return $whereClause . sprintf( + '%s.relname = %s AND %s.nspname = %s', + $classAlias, + $table, + $namespaceAlias, + $schema, + ); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT + a.attnum, + quote_ident(a.attname) AS field, + t.typname AS type, + format_type(a.atttypid, a.atttypmod) AS complete_type, + (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation, + (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, + (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM + pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, + a.attnotnull AS isnotnull, + (SELECT 't' + FROM pg_index + WHERE c.oid = pg_index.indrelid + AND pg_index.indkey[0] = a.attnum + AND pg_index.indisprimary = 't' + ) AS pri, + (SELECT pg_get_expr(adbin, adrelid) + FROM pg_attrdef + WHERE c.oid = pg_attrdef.adrelid + AND pg_attrdef.adnum=a.attnum + ) AS default, + (SELECT pg_description.description + FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid + ) AS comment + FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n + WHERE " . $this->getTableWhereClause($table, 'c', 'n') . ' + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + AND n.oid = c.relnamespace + ORDER BY a.attnum'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + + if ($foreignKey->hasOption('match')) { + $query .= ' MATCH ' . $foreignKey->getOption('match'); + } + + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + + if ( + ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) + || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) + ) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = []; + $commentsSQL = []; + $columnSql = []; + + $table = $diff->getOldTable() ?? $diff->getName($this); + + $tableNameSQL = $table->getQuotedName($this); + + foreach ($diff->getAddedColumns() as $addedColumn) { + if ($this->onSchemaAlterTableAddColumn($addedColumn, $diff, $columnSql)) { + continue; + } + + $query = 'ADD ' . $this->getColumnDeclarationSQL( + $addedColumn->getQuotedName($this), + $addedColumn->toArray(), + ); + + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + + $comment = $this->getColumnComment($addedColumn); + + if ($comment === null || $comment === '') { + continue; + } + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $tableNameSQL, + $addedColumn->getQuotedName($this), + $comment, + ); + } + + foreach ($diff->getDroppedColumns() as $droppedColumn) { + if ($this->onSchemaAlterTableRemoveColumn($droppedColumn, $diff, $columnSql)) { + continue; + } + + $query = 'DROP ' . $droppedColumn->getQuotedName($this); + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + } + + foreach ($diff->getModifiedColumns() as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if ($this->isUnchangedBinaryColumn($columnDiff)) { + continue; + } + + $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); + $newColumn = $columnDiff->getNewColumn(); + + $oldColumnName = $oldColumn->getQuotedName($this); + + if ( + $columnDiff->hasTypeChanged() + || $columnDiff->hasPrecisionChanged() + || $columnDiff->hasScaleChanged() + || $columnDiff->hasFixedChanged() + ) { + $type = $newColumn->getType(); + + // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type + $columnDefinition = $newColumn->toArray(); + $columnDefinition['autoincrement'] = false; + + // here was a server version check before, but DBAL API does not support this anymore. + $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this); + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + } + + if ($columnDiff->hasDefaultChanged()) { + $defaultClause = $newColumn->getDefault() === null + ? ' DROP DEFAULT' + : ' SET' . $this->getDefaultValueDeclarationSQL($newColumn->toArray()); + + $query = 'ALTER ' . $oldColumnName . $defaultClause; + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + } + + if ($columnDiff->hasNotNullChanged()) { + $query = 'ALTER ' . $oldColumnName . ' ' . ($newColumn->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + } + + if ($columnDiff->hasAutoIncrementChanged()) { + if ($newColumn->getAutoincrement()) { + // add autoincrement + $seqName = $this->getIdentitySequenceName( + $table->getName(), + $oldColumnName, + ); + + $sql[] = 'CREATE SEQUENCE ' . $seqName; + $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' + . $tableNameSQL . '))'; + $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; + } else { + // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have + $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT'; + } + + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + } + + $oldComment = $this->getOldColumnComment($columnDiff); + $newComment = $this->getColumnComment($newColumn); + + if ( + $columnDiff->hasCommentChanged() + || ($columnDiff->getOldColumn() !== null && $oldComment !== $newComment) + ) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $tableNameSQL, + $newColumn->getQuotedName($this), + $newComment, + ); + } + + if (! $columnDiff->hasLengthChanged()) { + continue; + } + + $query = 'ALTER ' . $oldColumnName . ' TYPE ' + . $newColumn->getType()->getSQLDeclaration($newColumn->toArray(), $this); + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + } + + foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) + . ' TO ' . $column->getQuotedName($this); + } + + $tableSql = []; + + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + $sql = array_merge($sql, $commentsSQL); + + $newName = $diff->getNewName(); + + if ($newName !== false) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', + __METHOD__, + ); + + $sql[] = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $tableNameSQL, + $newName->getQuotedName($this), + ); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff), + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Checks whether a given column diff is a logically unchanged binary type column. + * + * Used to determine whether a column alteration for a binary type column can be skipped. + * Doctrine's {@see BinaryType} and {@see BlobType} are mapped to the same database column type on this platform + * as this platform does not have a native VARBINARY/BINARY column type. Therefore the comparator + * might detect differences for binary type columns which do not have to be propagated + * to database as there actually is no difference at database level. + */ + private function isUnchangedBinaryColumn(ColumnDiff $columnDiff): bool + { + $newColumnType = $columnDiff->getNewColumn()->getType(); + + if (! $newColumnType instanceof BinaryType && ! $newColumnType instanceof BlobType) { + return false; + } + + $oldColumn = $columnDiff->getOldColumn() instanceof Column ? $columnDiff->getOldColumn() : null; + + if ($oldColumn !== null) { + $oldColumnType = $oldColumn->getType(); + + if (! $oldColumnType instanceof BinaryType && ! $oldColumnType instanceof BlobType) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0; + } + + if ($columnDiff->hasTypeChanged()) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0; + } + + /** + * {@inheritDoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + if (strpos($tableName, '.') !== false) { + [$schema] = explode('.', $tableName); + $oldIndexName = $schema . '.' . $oldIndexName; + } + + return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $tableName = new Identifier($tableName); + $columnName = new Identifier($columnName); + $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); + + return sprintf( + 'COMMENT ON COLUMN %s.%s IS %s', + $tableName->getQuotedName($this), + $columnName->getQuotedName($this), + $comment, + ); + } + + /** + * {@inheritDoc} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' MINVALUE ' . $sequence->getInitialValue() . + ' START ' . $sequence->getInitialValue() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * {@inheritDoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * Cache definition for sequences + */ + private function getSequenceCacheSQL(Sequence $sequence): string + { + if ($sequence->getCache() > 1) { + return ' CACHE ' . $sequence->getCache(); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function getDropSequenceSQL($sequence) + { + return parent::getDropSequenceSQL($sequence) . ' CASCADE'; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + return $this->getDropConstraintSQL($foreignKey, $table); + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index && $index->isPrimary() && $table !== null) { + $constraintName = $index->getName() === 'primary' ? $this->tableName($table) . '_pkey' : $index->getName(); + + return $this->getDropConstraintSQL($constraintName, $table); + } + + if ($index === '"primary"' && $table !== null) { + $constraintName = $this->tableName($table) . '_pkey'; + + return $this->getDropConstraintSQL($constraintName, $table); + } + + return parent::getDropIndexSQL($index, $table); + } + + /** + * @param Table|string|null $table + * + * @return string + */ + private function tableName($table) + { + return $table instanceof Table ? $table->getName() : (string) $table; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $unlogged = isset($options['unlogged']) && $options['unlogged'] === true ? ' UNLOGGED' : ''; + + $query = 'CREATE' . $unlogged . ' TABLE ' . $name . ' (' . $queryFields . ')'; + + $sql = [$query]; + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index) { + $sql[] = $this->getCreateIndexSQL($index, $name); + } + } + + if (isset($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $uniqueConstraint) { + $sql[] = $this->getCreateConstraintSQL($uniqueConstraint, $name); + } + } + + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $name); + } + } + + return $sql; + } + + /** + * Converts a single boolean value. + * + * First converts the value to its native PHP boolean type + * and passes it to the given callback function to be reconverted + * into any custom representation. + * + * @param mixed $value The value to convert. + * @param callable $callback The callback function to use for converting the real boolean value. + * + * @return mixed + * + * @throws UnexpectedValueException + */ + private function convertSingleBooleanValue($value, $callback) + { + if ($value === null) { + return $callback(null); + } + + if (is_bool($value) || is_numeric($value)) { + return $callback((bool) $value); + } + + if (! is_string($value)) { + return $callback(true); + } + + /** + * Better safe than sorry: http://php.net/in_array#106319 + */ + if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) { + return $callback(false); + } + + if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) { + return $callback(true); + } + + throw new UnexpectedValueException(sprintf("Unrecognized boolean literal '%s'", $value)); + } + + /** + * Converts one or multiple boolean values. + * + * First converts the value(s) to their native PHP boolean type + * and passes them to the given callback function to be reconverted + * into any custom representation. + * + * @param mixed $item The value(s) to convert. + * @param callable $callback The callback function to use for converting the real boolean value(s). + * + * @return mixed + */ + private function doConvertBooleans($item, $callback) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + $item[$key] = $this->convertSingleBooleanValue($value, $callback); + } + + return $item; + } + + return $this->convertSingleBooleanValue($item, $callback); + } + + /** + * {@inheritDoc} + * + * Postgres wants boolean values converted to the strings 'true'/'false'. + */ + public function convertBooleans($item) + { + if (! $this->useBooleanTrueFalseStrings) { + return parent::convertBooleans($item); + } + + return $this->doConvertBooleans( + $item, + /** @param mixed $value */ + static function ($value) { + if ($value === null) { + return 'NULL'; + } + + return $value === true ? 'true' : 'false'; + }, + ); + } + + /** + * {@inheritDoc} + */ + public function convertBooleansToDatabaseValue($item) + { + if (! $this->useBooleanTrueFalseStrings) { + return parent::convertBooleansToDatabaseValue($item); + } + + return $this->doConvertBooleans( + $item, + /** @param mixed $value */ + static function ($value): ?int { + return $value === null ? null : (int) $value; + }, + ); + } + + /** + * {@inheritDoc} + * + * @param T $item + * + * @return (T is null ? null : bool) + * + * @template T + */ + public function convertFromBoolean($item) + { + if ($item !== null && in_array(strtolower($item), $this->booleanLiterals['false'], true)) { + return false; + } + + return parent::convertFromBoolean($item); + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequence) + { + return "SELECT NEXTVAL('" . $sequence . "')"; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' + . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $column) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $column) + { + if (! empty($column['autoincrement'])) { + return 'SERIAL'; + } + + return 'INT'; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $column) + { + if (! empty($column['autoincrement'])) { + return 'BIGSERIAL'; + } + + return 'BIGINT'; + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $column) + { + if (! empty($column['autoincrement'])) { + return 'SMALLSERIAL'; + } + + return 'SMALLINT'; + } + + /** + * {@inheritDoc} + */ + public function getGuidTypeDeclarationSQL(array $column) + { + return 'UUID'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + return 'TIMESTAMP(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $column) + { + return 'TIMESTAMP(0) WITH TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $column) + { + return 'TIME(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $column) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritDoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return 'BYTEA'; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $column) + { + return 'TEXT'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4749', + 'PostgreSQLPlatform::getName() is deprecated. Identify platforms by their class.', + ); + + return 'postgresql'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:sO'; + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); + + if ($cascade) { + $sql .= ' CASCADE'; + } + + return $sql; + } + + /** + * Get the snippet used to retrieve the default value for a given column + */ + public function getDefaultColumnValueSQLSnippet(): string + { + return <<<'SQL' + SELECT pg_get_expr(adbin, adrelid) + FROM pg_attrdef + WHERE c.oid = pg_attrdef.adrelid + AND pg_attrdef.adnum=a.attnum + SQL; + } + + /** + * {@inheritDoc} + */ + public function getReadLockSQL() + { + return 'FOR SHARE'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = [ + 'bigint' => Types::BIGINT, + 'bigserial' => Types::BIGINT, + 'bool' => Types::BOOLEAN, + 'boolean' => Types::BOOLEAN, + 'bpchar' => Types::STRING, + 'bytea' => Types::BLOB, + 'char' => Types::STRING, + 'date' => Types::DATE_MUTABLE, + 'datetime' => Types::DATETIME_MUTABLE, + 'decimal' => Types::DECIMAL, + 'double' => Types::FLOAT, + 'double precision' => Types::FLOAT, + 'float' => Types::FLOAT, + 'float4' => Types::FLOAT, + 'float8' => Types::FLOAT, + 'inet' => Types::STRING, + 'int' => Types::INTEGER, + 'int2' => Types::SMALLINT, + 'int4' => Types::INTEGER, + 'int8' => Types::BIGINT, + 'integer' => Types::INTEGER, + 'interval' => Types::STRING, + 'json' => Types::JSON, + 'jsonb' => Types::JSON, + 'money' => Types::DECIMAL, + 'numeric' => Types::DECIMAL, + 'serial' => Types::INTEGER, + 'serial4' => Types::INTEGER, + 'serial8' => Types::BIGINT, + 'real' => Types::FLOAT, + 'smallint' => Types::SMALLINT, + 'text' => Types::TEXT, + 'time' => Types::TIME_MUTABLE, + 'timestamp' => Types::DATETIME_MUTABLE, + 'timestamptz' => Types::DATETIMETZ_MUTABLE, + 'timetz' => Types::TIME_MUTABLE, + 'tsvector' => Types::TEXT, + 'uuid' => Types::GUID, + 'varchar' => Types::STRING, + 'year' => Types::DATE_MUTABLE, + '_varchar' => Types::STRING, + ]; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getVarcharMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'PostgreSQLPlatform::getVarcharMaxLength() is deprecated.', + ); + + return 65535; + } + + /** + * {@inheritDoc} + */ + public function getBinaryMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'PostgreSQLPlatform::getBinaryMaxLength() is deprecated.', + ); + + return 0; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getBinaryDefaultLength() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default binary column length is deprecated, specify the length explicitly.', + ); + + return 0; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function hasNativeJsonType() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'PostgreSQLPlatform::getReservedKeywordsClass() is deprecated,' + . ' use PostgreSQLPlatform::createReservedKeywordsList() instead.', + ); + + return Keywords\PostgreSQL94Keywords::class; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $column) + { + return 'BYTEA'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getDefaultValueDeclarationSQL($column) + { + if (isset($column['autoincrement']) && $column['autoincrement'] === true) { + return ''; + } + + return parent::getDefaultValueDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getJsonTypeDeclarationSQL(array $column) + { + if (! empty($column['jsonb'])) { + return 'JSONB'; + } + + return 'JSON'; + } + + private function getOldColumnComment(ColumnDiff $columnDiff): ?string + { + $oldColumn = $columnDiff->getOldColumn(); + + if ($oldColumn !== null) { + return $this->getColumnComment($oldColumn); + } + + return null; + } + + /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ + public function getListTableMetadataSQL(string $table, ?string $schema = null): string + { + if ($schema !== null) { + $table = $schema . '.' . $table; + } + + return sprintf( + <<<'SQL' +SELECT obj_description(%s::regclass) AS table_comment; +SQL + , + $this->quoteStringLiteral($table), + ); + } + + public function createSchemaManager(Connection $connection): PostgreSQLSchemaManager + { + return new PostgreSQLSchemaManager($connection, $this); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php b/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php new file mode 100644 index 0000000..41ef422 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php @@ -0,0 +1,63 @@ +databaseCollation = $databaseCollation; + } + + public function compareTables(Table $fromTable, Table $toTable): TableDiff + { + return parent::compareTables( + $this->normalizeColumns($fromTable), + $this->normalizeColumns($toTable), + ); + } + + /** + * {@inheritDoc} + */ + public function diffTable(Table $fromTable, Table $toTable) + { + return parent::diffTable( + $this->normalizeColumns($fromTable), + $this->normalizeColumns($toTable), + ); + } + + private function normalizeColumns(Table $table): Table + { + $table = clone $table; + + foreach ($table->getColumns() as $column) { + $options = $column->getPlatformOptions(); + + if (! isset($options['collation']) || $options['collation'] !== $this->databaseCollation) { + continue; + } + + unset($options['collation']); + $column->setPlatformOptions($options); + } + + return $table; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php b/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php new file mode 100644 index 0000000..dff1222 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php @@ -0,0 +1,86 @@ +platform = $platform; + } + + public function buildSQL(SelectQuery $query): string + { + $parts = ['SELECT']; + + if ($query->isDistinct()) { + $parts[] = 'DISTINCT'; + } + + $parts[] = implode(', ', $query->getColumns()); + + $from = $query->getFrom(); + + if (count($from) > 0) { + $parts[] = 'FROM ' . implode(', ', $from); + } + + $forUpdate = $query->getForUpdate(); + + if ($forUpdate !== null) { + $with = ['UPDLOCK', 'ROWLOCK']; + + if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) { + $with[] = 'READPAST'; + } + + $parts[] = 'WITH (' . implode(', ', $with) . ')'; + } + + $where = $query->getWhere(); + + if ($where !== null) { + $parts[] = 'WHERE ' . $where; + } + + $groupBy = $query->getGroupBy(); + + if (count($groupBy) > 0) { + $parts[] = 'GROUP BY ' . implode(', ', $groupBy); + } + + $having = $query->getHaving(); + + if ($having !== null) { + $parts[] = 'HAVING ' . $having; + } + + $orderBy = $query->getOrderBy(); + + if (count($orderBy) > 0) { + $parts[] = 'ORDER BY ' . implode(', ', $orderBy); + } + + $sql = implode(' ', $parts); + $limit = $query->getLimit(); + + if ($limit->isDefined()) { + $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult()); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServer2012Platform.php b/vendor/doctrine/dbal/src/Platforms/SQLServer2012Platform.php new file mode 100644 index 0000000..a8ba2fa --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLServer2012Platform.php @@ -0,0 +1,13 @@ +getConvertExpression('date', 'GETDATE()'); + } + + /** + * {@inheritDoc} + */ + public function getCurrentTimeSQL() + { + return $this->getConvertExpression('time', 'GETDATE()'); + } + + /** + * Returns an expression that converts an expression of one data type to another. + * + * @param string $dataType The target native data type. Alias data types cannot be used. + * @param string $expression The SQL expression to convert. + */ + private function getConvertExpression($dataType, $expression): string + { + return sprintf('CONVERT(%s, %s)', $dataType, $expression); + } + + /** + * {@inheritDoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + $factorClause = ''; + + if ($operator === '-') { + $factorClause = '-1 * '; + } + + return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')'; + } + + /** + * {@inheritDoc} + * + * Microsoft SQL Server prefers "autoincrement" identity columns + * since sequences can only be emulated with a table. + * + * @deprecated + */ + public function prefersIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/1519', + 'SQLServerPlatform::prefersIdentityColumns() is deprecated.', + ); + + return true; + } + + /** + * {@inheritDoc} + * + * Microsoft SQL Server supports this through AUTO_INCREMENT columns. + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getDefaultSchemaName() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return 'dbo'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsColumnCollation() + { + return true; + } + + public function supportsSequences(): bool + { + return true; + } + + public function getAlterSequenceSQL(Sequence $sequence): string + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + public function getCreateSequenceSQL(Sequence $sequence): string + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' START WITH ' . $sequence->getInitialValue() . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' MINVALUE ' . $sequence->getInitialValue(); + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListSequencesSQL($database) + { + return 'SELECT seq.name, + CAST( + seq.increment AS VARCHAR(MAX) + ) AS increment, -- CAST avoids driver error for sql_variant type + CAST( + seq.start_value AS VARCHAR(MAX) + ) AS start_value -- CAST avoids driver error for sql_variant type + FROM sys.sequences AS seq'; + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequence) + { + return 'SELECT NEXT VALUE FOR ' . $sequence; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function hasNativeGuidType() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if (! $foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = new Identifier($foreignKey); + } + + if (! $table instanceof Table) { + $table = new Identifier($table); + } + + $foreignKey = $foreignKey->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $index = $index->getQuotedName($this); + } elseif (! is_string($index)) { + throw new InvalidArgumentException( + __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', + ); + } + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as an Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } elseif (! is_string($table)) { + throw new InvalidArgumentException( + __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', + ); + } + + return 'DROP INDEX ' . $index . ' ON ' . $table; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $defaultConstraintsSql = []; + $commentsSql = []; + + $tableComment = $options['comment'] ?? null; + if ($tableComment !== null) { + $commentsSql[] = $this->getCommentOnTableSQL($name, $tableComment); + } + + // @todo does other code breaks because of this? + // force primary keys to be not null + foreach ($columns as &$column) { + if (! empty($column['primary'])) { + $column['notnull'] = true; + } + + // Build default constraints SQL statements. + if (isset($column['default'])) { + $defaultConstraintsSql[] = 'ALTER TABLE ' . $name . + ' ADD' . $this->getDefaultConstraintDeclarationSQL($name, $column); + } + + if (empty($column['comment']) && ! is_numeric($column['comment'])) { + continue; + } + + $commentsSql[] = $this->getCreateColumnCommentSQL($name, $column['name'], $column['comment']); + } + + $columnListSql = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $constraintName => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); + } + } + + if (isset($options['primary']) && ! empty($options['primary'])) { + $flags = ''; + if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { + $flags = ' NONCLUSTERED'; + } + + $columnListSql .= ', PRIMARY KEY' . $flags + . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')'; + } + + $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; + + $check = $this->getCheckDeclarationSQL($columns); + if (! empty($check)) { + $query .= ', ' . $check; + } + + $query .= ')'; + + $sql = [$query]; + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index) { + $sql[] = $this->getCreateIndexSQL($index, $name); + } + } + + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $name); + } + } + + return array_merge($sql, $commentsSql, $defaultConstraintsSql); + } + + /** + * {@inheritDoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $identifier = $table->getQuotedName($this); + } else { + $identifier = $table; + } + + $sql = 'ALTER TABLE ' . $identifier . ' ADD PRIMARY KEY'; + + if ($index->hasFlag('nonclustered')) { + $sql .= ' NONCLUSTERED'; + } + + return $sql . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; + } + + private function unquoteSingleIdentifier(string $possiblyQuotedName): string + { + return str_starts_with($possiblyQuotedName, '[') && str_ends_with($possiblyQuotedName, ']') + ? substr($possiblyQuotedName, 1, -1) + : $possiblyQuotedName; + } + + /** + * Returns the SQL statement for creating a column comment. + * + * SQL Server does not support native column comments, + * therefore the extended properties functionality is used + * as a workaround to store them. + * The property name used to store column comments is "MS_Description" + * which provides compatibility with SQL Server Management Studio, + * as column comments are stored in the same property there when + * specifying a column's "Description" attribute. + * + * @param string $tableName The quoted table name to which the column belongs. + * @param string $columnName The quoted column name to create the comment for. + * @param string|null $comment The column's comment. + * + * @return string + */ + protected function getCreateColumnCommentSQL($tableName, $columnName, $comment) + { + if (strpos($tableName, '.') !== false) { + [$schemaName, $tableName] = explode('.', $tableName); + } else { + $schemaName = 'dbo'; + } + + return $this->getAddExtendedPropertySQL( + 'MS_Description', + $comment, + 'SCHEMA', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), + 'TABLE', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), + 'COLUMN', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), + ); + } + + /** + * Returns the SQL snippet for declaring a default constraint. + * + * @internal The method should be only used from within the SQLServerPlatform class hierarchy. + * + * @param string $table Name of the table to return the default constraint declaration for. + * @param mixed[] $column Column definition. + * + * @return string + * + * @throws InvalidArgumentException + */ + public function getDefaultConstraintDeclarationSQL($table, array $column) + { + if (! isset($column['default'])) { + throw new InvalidArgumentException("Incomplete column definition. 'default' required."); + } + + $columnName = new Identifier($column['name']); + + return ' CONSTRAINT ' . + $this->generateDefaultConstraintName($table, $column['name']) . + $this->getDefaultValueDeclarationSQL($column) . + ' FOR ' . $columnName->getQuotedName($this); + } + + /** + * {@inheritDoc} + */ + public function getCreateIndexSQL(Index $index, $table) + { + $constraint = parent::getCreateIndexSQL($index, $table); + + if ($index->isUnique() && ! $index->isPrimary()) { + $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); + } + + return $constraint; + } + + /** + * {@inheritDoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } + + if ($index->hasFlag('clustered')) { + $type .= 'CLUSTERED '; + } elseif ($index->hasFlag('nonclustered')) { + $type .= 'NONCLUSTERED '; + } + + return $type; + } + + /** + * Extend unique key constraint with required filters + * + * @param string $sql + */ + private function _appendUniqueConstraintDefinition($sql, Index $index): string + { + $fields = []; + + foreach ($index->getQuotedColumns($this) as $field) { + $fields[] = $field . ' IS NOT NULL'; + } + + return $sql . ' WHERE ' . implode(' AND ', $fields); + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $queryParts = []; + $sql = []; + $columnSql = []; + $commentsSql = []; + + $table = $diff->getOldTable() ?? $diff->getName($this); + + $tableName = $table->getName(); + + foreach ($diff->getAddedColumns() as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnProperties = $column->toArray(); + + $addColumnSql = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); + + if (isset($columnProperties['default'])) { + $addColumnSql .= ' CONSTRAINT ' . $this->generateDefaultConstraintName( + $tableName, + $column->getQuotedName($this), + ) . $this->getDefaultValueDeclarationSQL($columnProperties); + } + + $queryParts[] = $addColumnSql; + + $comment = $this->getColumnComment($column); + + if (empty($comment) && ! is_numeric($comment)) { + continue; + } + + $commentsSql[] = $this->getCreateColumnCommentSQL( + $tableName, + $column->getQuotedName($this), + $comment, + ); + } + + foreach ($diff->getDroppedColumns() as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); + } + + foreach ($diff->getModifiedColumns() as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $newColumn = $columnDiff->getNewColumn(); + $newComment = $this->getColumnComment($newColumn); + $hasNewComment = ! empty($newComment) || is_numeric($newComment); + + $oldColumn = $columnDiff->getOldColumn(); + + if ($oldColumn instanceof Column) { + $oldComment = $this->getColumnComment($oldColumn); + $hasOldComment = ! empty($oldComment) || is_numeric($oldComment); + + if ($hasOldComment && $hasNewComment && $oldComment !== $newComment) { + $commentsSql[] = $this->getAlterColumnCommentSQL( + $tableName, + $newColumn->getQuotedName($this), + $newComment, + ); + } elseif ($hasOldComment && ! $hasNewComment) { + $commentsSql[] = $this->getDropColumnCommentSQL( + $tableName, + $newColumn->getQuotedName($this), + ); + } elseif (! $hasOldComment && $hasNewComment) { + $commentsSql[] = $this->getCreateColumnCommentSQL( + $tableName, + $newColumn->getQuotedName($this), + $newComment, + ); + } + } + + // Do not add query part if only comment has changed. + if ($columnDiff->hasCommentChanged() && count($columnDiff->changedProperties) === 1) { + continue; + } + + $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff); + + if ($requireDropDefaultConstraint) { + $oldColumn = $columnDiff->getOldColumn(); + + if ($oldColumn !== null) { + $oldColumnName = $oldColumn->getName(); + } else { + $oldColumnName = $columnDiff->oldColumnName; + } + + $queryParts[] = $this->getAlterTableDropDefaultConstraintClause($tableName, $oldColumnName); + } + + $columnProperties = $newColumn->toArray(); + + $queryParts[] = 'ALTER COLUMN ' . + $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $columnProperties); + + if ( + ! isset($columnProperties['default']) + || (! $requireDropDefaultConstraint && ! $columnDiff->hasDefaultChanged()) + ) { + continue; + } + + $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn); + } + + $tableNameSQL = $table->getQuotedName($this); + + foreach ($diff->getRenamedColumns() as $oldColumnName => $newColumn) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $newColumn, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = "sp_rename '" . $tableNameSQL . '.' . $oldColumnName->getQuotedName($this) . + "', '" . $newColumn->getQuotedName($this) . "', 'COLUMN'"; + + // Recreate default constraint with new column name if necessary (for future reference). + if ($newColumn->getDefault() === null) { + continue; + } + + $queryParts[] = $this->getAlterTableDropDefaultConstraintClause( + $tableName, + $oldColumnName->getQuotedName($this), + ); + $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn); + } + + $tableSql = []; + + if ($this->onSchemaAlterTable($diff, $tableSql)) { + return array_merge($tableSql, $columnSql); + } + + foreach ($queryParts as $query) { + $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; + } + + $sql = array_merge($sql, $commentsSql); + + $newName = $diff->getNewName(); + + if ($newName !== false) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', + __METHOD__, + ); + + $sql = array_merge($sql, $this->getRenameTableSQL($tableName, $newName->getName())); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff), + ); + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + public function getRenameTableSQL(string $oldName, string $newName): array + { + return [ + sprintf('sp_rename %s, %s', $this->quoteStringLiteral($oldName), $this->quoteStringLiteral($newName)), + + /* Rename table's default constraints names + * to match the new table name. + * This is necessary to ensure that the default + * constraints can be referenced in future table + * alterations as the table name is encoded in + * default constraints' names. */ + sprintf( + <<<'SQL' + DECLARE @sql NVARCHAR(MAX) = N''; + SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' + + REPLACE(dc.name, '%s', '%s') + ''', ''OBJECT'';' + FROM sys.default_constraints dc + JOIN sys.tables tbl + ON dc.parent_object_id = tbl.object_id + WHERE tbl.name = %s; + EXEC sp_executesql @sql + SQL, + $this->generateIdentifierName($oldName), + $this->generateIdentifierName($newName), + $this->quoteStringLiteral($newName), + ), + ]; + } + + /** + * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement. + * + * @param string $tableName The name of the table to generate the clause for. + * @param Column $column The column to generate the clause for. + */ + private function getAlterTableAddDefaultConstraintClause($tableName, Column $column): string + { + $columnDef = $column->toArray(); + $columnDef['name'] = $column->getQuotedName($this); + + return 'ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $columnDef); + } + + /** + * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement. + * + * @param string $tableName The name of the table to generate the clause for. + * @param string $columnName The name of the column to generate the clause for. + */ + private function getAlterTableDropDefaultConstraintClause($tableName, $columnName): string + { + return 'DROP CONSTRAINT ' . $this->generateDefaultConstraintName($tableName, $columnName); + } + + /** + * Checks whether a column alteration requires dropping its default constraint first. + * + * Different to other database vendors SQL Server implements column default values + * as constraints and therefore changes in a column's default value as well as changes + * in a column's type require dropping the default constraint first before being to + * alter the particular column to the new definition. + */ + private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff): bool + { + $oldColumn = $columnDiff->getOldColumn(); + + // We can only decide whether to drop an existing default constraint + // if we know the original default value. + if (! $oldColumn instanceof Column) { + return false; + } + + // We only need to drop an existing default constraint if we know the + // column was defined with a default value before. + if ($oldColumn->getDefault() === null) { + return false; + } + + // We need to drop an existing default constraint if the column was + // defined with a default value before and it has changed. + if ($columnDiff->hasDefaultChanged()) { + return true; + } + + // We need to drop an existing default constraint if the column was + // defined with a default value before and the native column type has changed. + return $columnDiff->hasTypeChanged() || $columnDiff->hasFixedChanged(); + } + + /** + * Returns the SQL statement for altering a column comment. + * + * SQL Server does not support native column comments, + * therefore the extended properties functionality is used + * as a workaround to store them. + * The property name used to store column comments is "MS_Description" + * which provides compatibility with SQL Server Management Studio, + * as column comments are stored in the same property there when + * specifying a column's "Description" attribute. + * + * @param string $tableName The quoted table name to which the column belongs. + * @param string $columnName The quoted column name to alter the comment for. + * @param string|null $comment The column's comment. + * + * @return string + */ + protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) + { + if (strpos($tableName, '.') !== false) { + [$schemaName, $tableName] = explode('.', $tableName); + } else { + $schemaName = 'dbo'; + } + + return $this->getUpdateExtendedPropertySQL( + 'MS_Description', + $comment, + 'SCHEMA', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), + 'TABLE', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), + 'COLUMN', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), + ); + } + + /** + * Returns the SQL statement for dropping a column comment. + * + * SQL Server does not support native column comments, + * therefore the extended properties functionality is used + * as a workaround to store them. + * The property name used to store column comments is "MS_Description" + * which provides compatibility with SQL Server Management Studio, + * as column comments are stored in the same property there when + * specifying a column's "Description" attribute. + * + * @param string $tableName The quoted table name to which the column belongs. + * @param string $columnName The quoted column name to drop the comment for. + * + * @return string + */ + protected function getDropColumnCommentSQL($tableName, $columnName) + { + if (strpos($tableName, '.') !== false) { + [$schemaName, $tableName] = explode('.', $tableName); + } else { + $schemaName = 'dbo'; + } + + return $this->getDropExtendedPropertySQL( + 'MS_Description', + 'SCHEMA', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), + 'TABLE', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), + 'COLUMN', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), + ); + } + + /** + * {@inheritDoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + return [sprintf( + "EXEC sp_rename N'%s.%s', N'%s', N'INDEX'", + $tableName, + $oldIndexName, + $index->getQuotedName($this), + ), + ]; + } + + /** + * Returns the SQL statement for adding an extended property to a database object. + * + * @internal The method should be only used from within the SQLServerPlatform class hierarchy. + * + * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx + * + * @param string $name The name of the property to add. + * @param string|null $value The value of the property to add. + * @param string|null $level0Type The type of the object at level 0 the property belongs to. + * @param string|null $level0Name The name of the object at level 0 the property belongs to. + * @param string|null $level1Type The type of the object at level 1 the property belongs to. + * @param string|null $level1Name The name of the object at level 1 the property belongs to. + * @param string|null $level2Type The type of the object at level 2 the property belongs to. + * @param string|null $level2Name The name of the object at level 2 the property belongs to. + * + * @return string + */ + public function getAddExtendedPropertySQL( + $name, + $value = null, + $level0Type = null, + $level0Name = null, + $level1Type = null, + $level1Name = null, + $level2Type = null, + $level2Name = null + ) { + return 'EXEC sp_addextendedproperty ' . + 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . + 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . + 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . + ($level2Type !== null || $level2Name !== null + ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name + : '' + ); + } + + /** + * Returns the SQL statement for dropping an extended property from a database object. + * + * @internal The method should be only used from within the SQLServerPlatform class hierarchy. + * + * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx + * + * @param string $name The name of the property to drop. + * @param string|null $level0Type The type of the object at level 0 the property belongs to. + * @param string|null $level0Name The name of the object at level 0 the property belongs to. + * @param string|null $level1Type The type of the object at level 1 the property belongs to. + * @param string|null $level1Name The name of the object at level 1 the property belongs to. + * @param string|null $level2Type The type of the object at level 2 the property belongs to. + * @param string|null $level2Name The name of the object at level 2 the property belongs to. + * + * @return string + */ + public function getDropExtendedPropertySQL( + $name, + $level0Type = null, + $level0Name = null, + $level1Type = null, + $level1Name = null, + $level2Type = null, + $level2Name = null + ) { + return 'EXEC sp_dropextendedproperty ' . + 'N' . $this->quoteStringLiteral($name) . ', ' . + 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . + 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . + ($level2Type !== null || $level2Name !== null + ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name + : '' + ); + } + + /** + * Returns the SQL statement for updating an extended property of a database object. + * + * @internal The method should be only used from within the SQLServerPlatform class hierarchy. + * + * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx + * + * @param string $name The name of the property to update. + * @param string|null $value The value of the property to update. + * @param string|null $level0Type The type of the object at level 0 the property belongs to. + * @param string|null $level0Name The name of the object at level 0 the property belongs to. + * @param string|null $level1Type The type of the object at level 1 the property belongs to. + * @param string|null $level1Name The name of the object at level 1 the property belongs to. + * @param string|null $level2Type The type of the object at level 2 the property belongs to. + * @param string|null $level2Name The name of the object at level 2 the property belongs to. + * + * @return string + */ + public function getUpdateExtendedPropertySQL( + $name, + $value = null, + $level0Type = null, + $level0Name = null, + $level1Type = null, + $level1Name = null, + $level2Type = null, + $level2Name = null + ) { + return 'EXEC sp_updateextendedproperty ' . + 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . + 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . + 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . + ($level2Type !== null || $level2Name !== null + ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name + : '' + ); + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTablesSQL() + { + // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams + // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication + return 'SELECT name, SCHEMA_NAME (uid) AS schema_name FROM sysobjects' + . " WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name"; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT col.name, + type.name AS type, + col.max_length AS length, + ~col.is_nullable AS notnull, + def.definition AS [default], + col.scale, + col.precision, + col.is_identity AS autoincrement, + col.collation_name AS collation, + CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type + FROM sys.columns AS col + JOIN sys.types AS type + ON col.user_type_id = type.user_type_id + JOIN sys.objects AS obj + ON col.object_id = obj.object_id + JOIN sys.schemas AS scm + ON obj.schema_id = scm.schema_id + LEFT JOIN sys.default_constraints def + ON col.default_object_id = def.object_id + AND col.object_id = def.parent_object_id + LEFT JOIN sys.extended_properties AS prop + ON obj.object_id = prop.major_id + AND col.column_id = prop.minor_id + AND prop.name = 'MS_Description' + WHERE obj.type = 'U' + AND " . $this->getTableWhereClause($table, 'scm.name', 'obj.name'); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * @param string $table + * @param string|null $database + * + * @return string + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + return 'SELECT f.name AS ForeignKey, + SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, + OBJECT_NAME (f.parent_object_id) AS TableName, + COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, + SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, + OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, + COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, + f.delete_referential_action_desc, + f.update_referential_action_desc + FROM sys.foreign_keys AS f + INNER JOIN sys.foreign_key_columns AS fc + INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id + ON f.OBJECT_ID = fc.constraint_object_id + WHERE ' . + $this->getTableWhereClause($table, 'SCHEMA_NAME (f.schema_id)', 'OBJECT_NAME (f.parent_object_id)') . + ' ORDER BY fc.constraint_column_id'; + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $database = null) + { + return "SELECT idx.name AS key_name, + col.name AS column_name, + ~idx.is_unique AS non_unique, + idx.is_primary_key AS [primary], + CASE idx.type + WHEN '1' THEN 'clustered' + WHEN '2' THEN 'nonclustered' + ELSE NULL + END AS flags + FROM sys.tables AS tbl + JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id + JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id + JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id + JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id + WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . ' + ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListViewsSQL($database) + { + return "SELECT name, definition FROM sysobjects + INNER JOIN sys.sql_modules ON sysobjects.id = sys.sql_modules.object_id + WHERE type = 'V' ORDER BY name"; + } + + /** + * Returns the where clause to filter schema and table name in a query. + * + * @param string $table The full qualified name of the table. + * @param string $schemaColumn The name of the column to compare the schema to in the where clause. + * @param string $tableColumn The name of the column to compare the table to in the where clause. + */ + private function getTableWhereClause($table, $schemaColumn, $tableColumn): string + { + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table); + $schema = $this->quoteStringLiteral($schema); + $table = $this->quoteStringLiteral($table); + } else { + $schema = 'SCHEMA_NAME()'; + $table = $this->quoteStringLiteral($table); + } + + return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos === false) { + return 'CHARINDEX(' . $substr . ', ' . $str . ')'; + } + + return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')'; + } + + /** + * {@inheritDoc} + */ + public function getModExpression($expression1, $expression2) + { + return $expression1 . ' % ' . $expression2; + } + + /** + * {@inheritDoc} + */ + public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) + { + if ($char === false) { + switch ($mode) { + case TrimMode::LEADING: + $trimFn = 'LTRIM'; + break; + + case TrimMode::TRAILING: + $trimFn = 'RTRIM'; + break; + + default: + return 'LTRIM(RTRIM(' . $str . '))'; + } + + return $trimFn . '(' . $str . ')'; + } + + $pattern = "'%[^' + " . $char . " + ']%'"; + + if ($mode === TrimMode::LEADING) { + return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)'; + } + + if ($mode === TrimMode::TRAILING) { + return 'reverse(stuff(reverse(' . $str . '), 1, ' + . 'patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))'; + } + + return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, ' + . 'patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str + . ') - 1, null))) - 1, null))'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + return sprintf('CONCAT(%s)', implode(', ', func_get_args())); + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListDatabasesSQL() + { + return 'SELECT * FROM sys.databases'; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see SQLServerSchemaManager::listSchemaNames()} instead. + */ + public function getListNamespacesSQL() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'SQLServerPlatform::getListNamespacesSQL() is deprecated,' + . ' use SQLServerSchemaManager::listSchemaNames() instead.', + ); + + return "SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')"; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($string, $start, $length = null) + { + if ($length !== null) { + return 'SUBSTRING(' . $string . ', ' . $start . ', ' . $length . ')'; + } + + return 'SUBSTRING(' . $string . ', ' . $start . ', LEN(' . $string . ') - ' . $start . ' + 1)'; + } + + /** + * {@inheritDoc} + */ + public function getLengthExpression($column) + { + return 'LEN(' . $column . ')'; + } + + public function getCurrentDatabaseExpression(): string + { + return 'DB_NAME()'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $column) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $column) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $column) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getGuidTypeDeclarationSQL(array $column) + { + return 'UNIQUEIDENTIFIER'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $column) + { + return 'DATETIMEOFFSET(6)'; + } + + /** + * {@inheritDoc} + */ + public function getAsciiStringTypeDeclarationSQL(array $column): string + { + $length = $column['length'] ?? null; + + if (empty($column['fixed'])) { + return sprintf('VARCHAR(%d)', $length ?? 255); + } + + return sprintf('CHAR(%d)', $length ?? 255); + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default string column length on SQL Server is deprecated' + . ', specify the length explicitly.', + ); + } + + return $fixed + ? 'NCHAR(' . ($length > 0 ? $length : 255) . ')' + : 'NVARCHAR(' . ($length > 0 ? $length : 255) . ')'; + } + + /** + * {@inheritDoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) + { + if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default binary column length on SQL Server is deprecated' + . ', specify the length explicitly.', + ); + } + + return $fixed + ? 'BINARY(' . ($length > 0 ? $length : 255) . ')' + : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')'; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getBinaryMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'SQLServerPlatform::getBinaryMaxLength() is deprecated.', + ); + + return 8000; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $column) + { + return 'VARCHAR(MAX)'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $column) + { + return ! empty($column['autoincrement']) ? ' IDENTITY' : ''; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + // 3 - microseconds precision length + // http://msdn.microsoft.com/en-us/library/ms187819.aspx + return 'DATETIME2(6)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $column) + { + return 'TIME(0)'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $column) + { + return 'BIT'; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit === null && $offset <= 0) { + return $query; + } + + if ($this->shouldAddOrderBy($query)) { + if (preg_match('/^SELECT\s+DISTINCT/im', $query) > 0) { + // SQL Server won't let us order by a non-selected column in a DISTINCT query, + // so we have to do this madness. This says, order by the first column in the + // result. SQL Server's docs say that a nonordered query's result order is non- + // deterministic anyway, so this won't do anything that a bunch of update and + // deletes to the table wouldn't do anyway. + $query .= ' ORDER BY 1'; + } else { + // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you + // use constant expressions in the order by list. + $query .= ' ORDER BY (SELECT 0)'; + } + } + + // This looks somewhat like MYSQL, but limit/offset are in inverse positions + // Supposedly SQL:2008 core standard. + // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS. + $query .= sprintf(' OFFSET %d ROWS', $offset); + + if ($limit !== null) { + $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit); + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + if (! is_bool($value) && ! is_numeric($value)) { + continue; + } + + $item[$key] = (int) (bool) $value; + } + } elseif (is_bool($item) || is_numeric($item)) { + $item = (int) (bool) $item; + } + + return $item; + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return 'CREATE TABLE'; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + return '#' . $tableName; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s.u'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return 'H:i:s'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s.u P'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'mssql'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = [ + 'bigint' => Types::BIGINT, + 'binary' => Types::BINARY, + 'bit' => Types::BOOLEAN, + 'blob' => Types::BLOB, + 'char' => Types::STRING, + 'date' => Types::DATE_MUTABLE, + 'datetime' => Types::DATETIME_MUTABLE, + 'datetime2' => Types::DATETIME_MUTABLE, + 'datetimeoffset' => Types::DATETIMETZ_MUTABLE, + 'decimal' => Types::DECIMAL, + 'double' => Types::FLOAT, + 'double precision' => Types::FLOAT, + 'float' => Types::FLOAT, + 'image' => Types::BLOB, + 'int' => Types::INTEGER, + 'money' => Types::INTEGER, + 'nchar' => Types::STRING, + 'ntext' => Types::TEXT, + 'numeric' => Types::DECIMAL, + 'nvarchar' => Types::STRING, + 'real' => Types::FLOAT, + 'smalldatetime' => Types::DATETIME_MUTABLE, + 'smallint' => Types::SMALLINT, + 'smallmoney' => Types::INTEGER, + 'sysname' => Types::STRING, + 'text' => Types::TEXT, + 'time' => Types::TIME_MUTABLE, + 'tinyint' => Types::SMALLINT, + 'uniqueidentifier' => Types::GUID, + 'varbinary' => Types::BINARY, + 'varchar' => Types::STRING, + 'xml' => Types::TEXT, + ]; + } + + /** + * {@inheritDoc} + */ + public function createSavePoint($savepoint) + { + return 'SAVE TRANSACTION ' . $savepoint; + } + + /** + * {@inheritDoc} + */ + public function releaseSavePoint($savepoint) + { + return ''; + } + + /** + * {@inheritDoc} + */ + public function rollbackSavePoint($savepoint) + { + return 'ROLLBACK TRANSACTION ' . $savepoint; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getForeignKeyReferentialActionSQL($action) + { + // RESTRICT is not supported, therefore falling back to NO ACTION. + if (strtoupper($action) === 'RESTRICT') { + return 'NO ACTION'; + } + + return parent::getForeignKeyReferentialActionSQL($action); + } + + public function appendLockHint(string $fromClause, int $lockMode): string + { + switch ($lockMode) { + case LockMode::NONE: + case LockMode::OPTIMISTIC: + return $fromClause; + + case LockMode::PESSIMISTIC_READ: + return $fromClause . ' WITH (HOLDLOCK, ROWLOCK)'; + + case LockMode::PESSIMISTIC_WRITE: + return $fromClause . ' WITH (UPDLOCK, ROWLOCK)'; + + default: + throw InvalidLockMode::fromLockMode($lockMode); + } + } + + /** + * {@inheritDoc} + * + * @deprecated This API is not portable. + */ + public function getForUpdateSQL() + { + return ' '; + } + + /** + * {@inheritDoc} + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'SQLServerPlatform::getReservedKeywordsClass() is deprecated,' + . ' use SQLServerPlatform::createReservedKeywordsList() instead.', + ); + + return Keywords\SQLServer2012Keywords::class; + } + + /** + * {@inheritDoc} + */ + public function quoteSingleIdentifier($str) + { + return '[' . str_replace(']', ']]', $str) . ']'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $column) + { + return 'VARBINARY(MAX)'; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getColumnDeclarationSQL($name, array $column) + { + if (isset($column['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($column); + } else { + $collation = ! empty($column['collation']) ? + ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; + + $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; + + if (! empty($column['unique'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5656', + 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', + ); + + $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); + } else { + $unique = ''; + } + + if (! empty($column['check'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5656', + 'The usage of the "check" column property is deprecated.', + ); + + $check = ' ' . $column['check']; + } else { + $check = ''; + } + + $typeDecl = $column['type']->getSQLDeclaration($column, $this); + $columnDef = $typeDecl . $collation . $notnull . $unique . $check; + } + + return $name . ' ' . $columnDef; + } + + /** + * {@inheritDoc} + * + * SQL Server does not support quoting collation identifiers. + */ + public function getColumnCollationDeclarationSQL($collation) + { + return 'COLLATE ' . $collation; + } + + public function columnsEqual(Column $column1, Column $column2): bool + { + if (! parent::columnsEqual($column1, $column2)) { + return false; + } + + return $this->getDefaultValueDeclarationSQL($column1->toArray()) + === $this->getDefaultValueDeclarationSQL($column2->toArray()); + } + + protected function getLikeWildcardCharacters(): string + { + return parent::getLikeWildcardCharacters() . '[]^'; + } + + /** + * Returns a unique default constraint name for a table and column. + * + * @param string $table Name of the table to generate the unique default constraint name for. + * @param string $column Name of the column in the table to generate the unique default constraint name for. + */ + private function generateDefaultConstraintName($table, $column): string + { + return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column); + } + + /** + * Returns a hash value for a given identifier. + * + * @param string $identifier Identifier to generate a hash value for. + */ + private function generateIdentifierName($identifier): string + { + // Always generate name for unquoted identifiers to ensure consistency. + $identifier = new Identifier($identifier); + + return strtoupper(dechex(crc32($identifier->getName()))); + } + + protected function getCommentOnTableSQL(string $tableName, ?string $comment): string + { + return $this->getAddExtendedPropertySQL( + 'MS_Description', + $comment, + 'SCHEMA', + $this->quoteStringLiteral('dbo'), + 'TABLE', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), + ); + } + + /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ + public function getListTableMetadataSQL(string $table): string + { + return sprintf( + <<<'SQL' + SELECT + p.value AS [table_comment] + FROM + sys.tables AS tbl + INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 + WHERE + (tbl.name=N%s and SCHEMA_NAME(tbl.schema_id)=N'dbo' and p.name=N'MS_Description') + SQL + , + $this->quoteStringLiteral($table), + ); + } + + /** @param string $query */ + private function shouldAddOrderBy($query): bool + { + // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement + // but can be in a newline + $matches = []; + $matchesCount = preg_match_all('/[\\s]+order\\s+by\\s/im', $query, $matches, PREG_OFFSET_CAPTURE); + if ($matchesCount === 0) { + return true; + } + + // ORDER BY instance may be in a subquery after ORDER BY + // e.g. SELECT col1 FROM test ORDER BY (SELECT col2 from test ORDER BY col2) + // if in the searched query ORDER BY clause was found where + // number of open parentheses after the occurrence of the clause is equal to + // number of closed brackets after the occurrence of the clause, + // it means that ORDER BY is included in the query being checked + while ($matchesCount > 0) { + $orderByPos = $matches[0][--$matchesCount][1]; + $openBracketsCount = substr_count($query, '(', $orderByPos); + $closedBracketsCount = substr_count($query, ')', $orderByPos); + if ($openBracketsCount === $closedBracketsCount) { + return false; + } + } + + return true; + } + + public function createSchemaManager(Connection $connection): SQLServerSchemaManager + { + return new SQLServerSchemaManager($connection, $this); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php b/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php new file mode 100644 index 0000000..5218871 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php @@ -0,0 +1,61 @@ +normalizeColumns($fromTable), + $this->normalizeColumns($toTable), + ); + } + + /** + * {@inheritDoc} + */ + public function diffTable(Table $fromTable, Table $toTable) + { + return parent::diffTable( + $this->normalizeColumns($fromTable), + $this->normalizeColumns($toTable), + ); + } + + private function normalizeColumns(Table $table): Table + { + $table = clone $table; + + foreach ($table->getColumns() as $column) { + $options = $column->getPlatformOptions(); + + if (! isset($options['collation']) || strcasecmp($options['collation'], 'binary') !== 0) { + continue; + } + + unset($options['collation']); + $column->setPlatformOptions($options); + } + + return $table; + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/SqlitePlatform.php b/vendor/doctrine/dbal/src/Platforms/SqlitePlatform.php new file mode 100644 index 0000000..48c692f --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SqlitePlatform.php @@ -0,0 +1,1545 @@ + 0 THEN INSTR(SUBSTR(' . $str . ', ' . $startPos . '), ' . $substr . ') + ' . $startPos + . ' - 1 ELSE 0 END'; + } + + /** + * {@inheritDoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + switch ($unit) { + case DateIntervalUnit::SECOND: + case DateIntervalUnit::MINUTE: + case DateIntervalUnit::HOUR: + return 'DATETIME(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; + } + + switch ($unit) { + case DateIntervalUnit::WEEK: + $interval = $this->multiplyInterval((string) $interval, 7); + $unit = DateIntervalUnit::DAY; + break; + + case DateIntervalUnit::QUARTER: + $interval = $this->multiplyInterval((string) $interval, 3); + $unit = DateIntervalUnit::MONTH; + break; + } + + if (! is_numeric($interval)) { + $interval = "' || " . $interval . " || '"; + } + + return 'DATE(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2); + } + + /** + * {@inheritDoc} + * + * The DBAL doesn't support databases on the SQLite platform. The expression here always returns a fixed string + * as an indicator of an implicitly selected database. + * + * @link https://www.sqlite.org/lang_select.html + * @see Connection::getDatabase() + */ + public function getCurrentDatabaseExpression(): string + { + return "'main'"; + } + + /** @link https://www2.sqlite.org/cvstrac/wiki?p=UnsupportedSql */ + public function createSelectSQLBuilder(): SelectSQLBuilder + { + return new DefaultSelectSQLBuilder($this, null, null); + } + + /** + * {@inheritDoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case TransactionIsolationLevel::READ_UNCOMMITTED: + return '0'; + + case TransactionIsolationLevel::READ_COMMITTED: + case TransactionIsolationLevel::REPEATABLE_READ: + case TransactionIsolationLevel::SERIALIZABLE: + return '1'; + + default: + return parent::_getTransactionIsolationLevelSQL($level); + } + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function prefersIdentityColumns() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/1519', + 'SqlitePlatform::prefersIdentityColumns() is deprecated.', + ); + + return true; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $column) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $column) + { + return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $column) + { + // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT columns + if (! empty($column['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($column); + } + + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * @deprecated Use {@see getSmallIntTypeDeclarationSQL()} instead. + * + * @param array $column + * + * @return string + */ + public function getTinyIntTypeDeclarationSQL(array $column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5511', + '%s is deprecated. Use getSmallIntTypeDeclarationSQL() instead.', + __METHOD__, + ); + + // SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT columns + if (! empty($column['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($column); + } + + return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $column) + { + // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT columns + if (! empty($column['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($column); + } + + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * @deprecated Use {@see getIntegerTypeDeclarationSQL()} instead. + * + * @param array $column + * + * @return string + */ + public function getMediumIntTypeDeclarationSQL(array $column) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5511', + '%s is deprecated. Use getIntegerTypeDeclarationSQL() instead.', + __METHOD__, + ); + + // SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT columns + if (! empty($column['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($column); + } + + return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $column) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $column) + { + // sqlite autoincrement is only possible for the primary key + if (! empty($column['autoincrement'])) { + return ' PRIMARY KEY AUTOINCREMENT'; + } + + return ! empty($column['unsigned']) ? ' UNSIGNED' : ''; + } + + /** + * Disables schema emulation. + * + * Schema emulation is enabled by default to maintain backwards compatibility. + * Disable it to opt-in to the behavior of DBAL 4. + * + * @deprecated Will be removed in DBAL 4.0. + */ + public function disableSchemaEmulation(): void + { + $this->schemaEmulationEnabled = false; + } + + private function emulateSchemaNamespacing(string $tableName): string + { + return $this->schemaEmulationEnabled + ? str_replace('.', '__', $tableName) + : $tableName; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint( + $foreignKey->getQuotedLocalColumns($this), + $this->emulateSchemaNamespacing($foreignKey->getQuotedForeignTableName($this)), + $foreignKey->getQuotedForeignColumns($this), + $foreignKey->getName(), + $foreignKey->getOptions(), + )); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $name = $this->emulateSchemaNamespacing($name); + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $constraintName => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); + } + } + + $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options); + + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $foreignKey) { + $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey); + } + } + + $tableComment = ''; + if (isset($options['comment'])) { + $comment = trim($options['comment'], " '"); + + $tableComment = $this->getInlineTableCommentSQL($comment); + } + + $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')']; + + if (isset($options['alter']) && $options['alter'] === true) { + return $query; + } + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $indexDef) { + $query[] = $this->getCreateIndexSQL($indexDef, $name); + } + } + + if (isset($options['unique']) && ! empty($options['unique'])) { + foreach ($options['unique'] as $indexDef) { + $query[] = $this->getCreateIndexSQL($indexDef, $name); + } + } + + return $query; + } + + /** + * Generate a PRIMARY KEY definition if no autoincrement value is used + * + * @param mixed[][] $columns + * @param mixed[] $options + */ + private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options): string + { + if (empty($options['primary'])) { + return ''; + } + + $keyColumns = array_unique(array_values($options['primary'])); + + foreach ($keyColumns as $keyColumn) { + if (! empty($columns[$keyColumn]['autoincrement'])) { + return ''; + } + } + + return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'TEXT'); + } + + /** + * {@inheritDoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return 'BLOB'; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getBinaryMaxLength() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'SqlitePlatform::getBinaryMaxLength() is deprecated.', + ); + + return 0; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getBinaryDefaultLength() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3263', + 'Relying on the default binary column length is deprecated, specify the length explicitly.', + ); + + return 0; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $column) + { + return 'CLOB'; + } + + /** + * @deprecated + * + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = $this->emulateSchemaNamespacing($table); + + return sprintf( + "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name", + $this->quoteStringLiteral($table), + ); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $table = $this->emulateSchemaNamespacing($table); + + return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table)); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $database = null) + { + $table = $this->emulateSchemaNamespacing($table); + + return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table)); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return 'SELECT name FROM sqlite_master' + . " WHERE type = 'table'" + . " AND name != 'sqlite_sequence'" + . " AND name != 'geometry_columns'" + . " AND name != 'spatial_ref_sys'" + . ' UNION ALL SELECT name FROM sqlite_temp_master' + . " WHERE type = 'table' ORDER BY name"; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. + */ + public function getListViewsSQL($database) + { + return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + if (! $foreignKey->hasOption('deferrable') || $foreignKey->getOption('deferrable') === false) { + $query .= ' NOT'; + } + + $query .= ' DEFERRABLE'; + $query .= ' INITIALLY'; + + if ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) { + $query .= ' DEFERRED'; + } else { + $query .= ' IMMEDIATE'; + } + + return $query; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function supportsCreateDropDatabase() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5513', + '%s is deprecated.', + __METHOD__, + ); + + return false; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function supportsInlineColumnComments() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4749', + 'SqlitePlatform::getName() is deprecated. Identify platforms by their class.', + ); + + return 'sqlite'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + $tableName = $this->emulateSchemaNamespacing($tableIdentifier->getQuotedName($this)); + + return 'DELETE FROM ' . $tableName; + } + + /** + * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction(). + * + * @deprecated The driver will use {@see sqrt()} in the next major release. + * + * @param int|float $value + * + * @return float + */ + public static function udfSqrt($value) + { + return sqrt($value); + } + + /** + * User-defined function for Sqlite that implements MOD(a, b). + * + * @deprecated The driver will use {@see UserDefinedFunctions::mod()} in the next major release. + * + * @param int $a + * @param int $b + * + * @return int + */ + public static function udfMod($a, $b) + { + return UserDefinedFunctions::mod($a, $b); + } + + /** + * @deprecated The driver will use {@see UserDefinedFunctions::locate()} in the next major release. + * + * @param string $str + * @param string $substr + * @param int $offset + * + * @return int + */ + public static function udfLocate($str, $substr, $offset = 0) + { + return UserDefinedFunctions::locate($str, $substr, $offset); + } + + /** + * {@inheritDoc} + * + * @deprecated This API is not portable. + */ + public function getForUpdateSQL() + { + return ''; + } + + /** + * {@inheritDoc} + * + * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. + */ + public function getInlineColumnCommentSQL($comment) + { + return '--' . str_replace("\n", "\n--", $comment) . "\n"; + } + + private function getInlineTableCommentSQL(string $comment): string + { + return $this->getInlineColumnCommentSQL($comment); + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = [ + 'bigint' => Types\Types::BIGINT, + 'bigserial' => Types\Types::BIGINT, + 'blob' => Types\Types::BLOB, + 'boolean' => Types\Types::BOOLEAN, + 'char' => Types\Types::STRING, + 'clob' => Types\Types::TEXT, + 'date' => Types\Types::DATE_MUTABLE, + 'datetime' => Types\Types::DATETIME_MUTABLE, + 'decimal' => Types\Types::DECIMAL, + 'double' => Types\Types::FLOAT, + 'double precision' => Types\Types::FLOAT, + 'float' => Types\Types::FLOAT, + 'image' => Types\Types::STRING, + 'int' => Types\Types::INTEGER, + 'integer' => Types\Types::INTEGER, + 'longtext' => Types\Types::TEXT, + 'longvarchar' => Types\Types::STRING, + 'mediumint' => Types\Types::INTEGER, + 'mediumtext' => Types\Types::TEXT, + 'ntext' => Types\Types::STRING, + 'numeric' => Types\Types::DECIMAL, + 'nvarchar' => Types\Types::STRING, + 'real' => Types\Types::FLOAT, + 'serial' => Types\Types::INTEGER, + 'smallint' => Types\Types::SMALLINT, + 'text' => Types\Types::TEXT, + 'time' => Types\Types::TIME_MUTABLE, + 'timestamp' => Types\Types::DATETIME_MUTABLE, + 'tinyint' => Types\Types::BOOLEAN, + 'tinytext' => Types\Types::TEXT, + 'varchar' => Types\Types::STRING, + 'varchar2' => Types\Types::STRING, + ]; + } + + /** + * {@inheritDoc} + * + * @deprecated Implement {@see createReservedKeywordsList()} instead. + */ + protected function getReservedKeywordsClass() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'SqlitePlatform::getReservedKeywordsClass() is deprecated,' + . ' use SqlitePlatform::createReservedKeywordsList() instead.', + ); + + return Keywords\SQLiteKeywords::class; + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + return []; + } + + /** + * {@inheritDoc} + */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $table = $diff->getOldTable(); + + if (! $table instanceof Table) { + throw new Exception( + 'Sqlite platform requires for alter table the table diff with reference to original table schema', + ); + } + + $sql = []; + $tableName = $diff->getNewName(); + + if ($tableName === false) { + $tableName = $diff->getName($this); + } + + foreach ($this->getIndexesInAlteredTable($diff, $table) as $index) { + if ($index->isPrimary()) { + continue; + } + + $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this)); + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit === null && $offset > 0) { + return sprintf('%s LIMIT -1 OFFSET %d', $query, $offset); + } + + return parent::doModifyLimitQuery($query, $limit, $offset); + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $column) + { + return 'BLOB'; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + $tableName = $this->emulateSchemaNamespacing($tableName); + + return $tableName; + } + + /** + * {@inheritDoc} + * + * @deprecated + * + * Sqlite Platform emulates schema by underscoring each dot and generating tables + * into the default database. + * + * This hack is implemented to be able to use SQLite as testdriver when + * using schema supporting databases. + */ + public function canEmulateSchemas() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4805', + 'SqlitePlatform::canEmulateSchemas() is deprecated.', + ); + + return $this->schemaEmulationEnabled; + } + + /** + * {@inheritDoc} + */ + public function getCreateTablesSQL(array $tables): array + { + $sql = []; + + foreach ($tables as $table) { + $sql = array_merge($sql, $this->getCreateTableSQL($table)); + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getCreateIndexSQL(Index $index, $table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this); + } + + $name = $index->getQuotedName($this); + $columns = $index->getColumns(); + + if (count($columns) === 0) { + throw new InvalidArgumentException(sprintf( + 'Incomplete or invalid index definition %s on table %s', + $name, + $table, + )); + } + + if ($index->isPrimary()) { + return $this->getCreatePrimaryKeySQL($index, $table); + } + + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table, 2); + $name = $schema . '.' . $name; + } + + $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getDropTablesSQL(array $tables): array + { + $sql = []; + + foreach ($tables as $table) { + $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + throw new Exception('Sqlite platform does not support alter primary key.'); + } + + /** + * {@inheritDoc} + */ + public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) + { + throw new Exception('Sqlite platform does not support alter foreign key.'); + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + throw new Exception('Sqlite platform does not support alter foreign key.'); + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + throw new Exception('Sqlite platform does not support alter constraint.'); + } + + /** + * {@inheritDoc} + * + * @param int|null $createFlags + * @psalm-param int-mask-of|null $createFlags + */ + public function getCreateTableSQL(Table $table, $createFlags = null) + { + $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS; + + return parent::getCreateTableSQL($table, $createFlags); + } + + /** + * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. + * + * @param string $table + * @param string|null $database + * + * @return string + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + $table = $this->emulateSchemaNamespacing($table); + + return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table)); + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = $this->getSimpleAlterTableSQL($diff); + if ($sql !== false) { + return $sql; + } + + $table = $diff->getOldTable(); + + if (! $table instanceof Table) { + throw new Exception( + 'Sqlite platform requires for alter table the table diff with reference to original table schema', + ); + } + + $columns = []; + $oldColumnNames = []; + $newColumnNames = []; + $columnSql = []; + + foreach ($table->getColumns() as $columnName => $column) { + $columnName = strtolower($columnName); + $columns[$columnName] = $column; + $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this); + } + + foreach ($diff->getDroppedColumns() as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $columnName = strtolower($column->getName()); + if (! isset($columns[$columnName])) { + continue; + } + + unset( + $columns[$columnName], + $oldColumnNames[$columnName], + $newColumnNames[$columnName], + ); + } + + foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = strtolower($oldColumnName); + + $columns = $this->replaceColumn( + $table->getName(), + $columns, + $oldColumnName, + $column, + ); + + if (! isset($newColumnNames[$oldColumnName])) { + continue; + } + + $newColumnNames[$oldColumnName] = $column->getQuotedName($this); + } + + foreach ($diff->getModifiedColumns() as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); + + $oldColumnName = strtolower($oldColumn->getName()); + + $columns = $this->replaceColumn( + $table->getName(), + $columns, + $oldColumnName, + $columnDiff->getNewColumn(), + ); + + if (! isset($newColumnNames[$oldColumnName])) { + continue; + } + + $newColumnNames[$oldColumnName] = $columnDiff->getNewColumn()->getQuotedName($this); + } + + foreach ($diff->getAddedColumns() as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columns[strtolower($column->getName())] = $column; + } + + $sql = []; + $tableSql = []; + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + $tableName = $table->getName(); + if (strpos($tableName, '.') !== false) { + [, $tableName] = explode('.', $tableName, 2); + } + + $dataTable = new Table('__temp__' . $tableName); + + $newTable = new Table( + $table->getQuotedName($this), + $columns, + $this->getPrimaryIndexInAlteredTable($diff, $table), + [], + $this->getForeignKeysInAlteredTable($diff, $table), + $table->getOptions(), + ); + $newTable->addOption('alter', true); + + $sql = $this->getPreAlterTableIndexForeignKeySQL($diff); + + $sql[] = sprintf( + 'CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', + $dataTable->getQuotedName($this), + implode(', ', $oldColumnNames), + $table->getQuotedName($this), + ); + $sql[] = $this->getDropTableSQL($table); + + $sql = array_merge($sql, $this->getCreateTableSQL($newTable)); + $sql[] = sprintf( + 'INSERT INTO %s (%s) SELECT %s FROM %s', + $newTable->getQuotedName($this), + implode(', ', $newColumnNames), + implode(', ', $oldColumnNames), + $dataTable->getQuotedName($this), + ); + $sql[] = $this->getDropTableSQL($dataTable->getQuotedName($this)); + + $newName = $diff->getNewName(); + + if ($newName !== false) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', + __METHOD__, + ); + + $sql[] = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $newTable->getQuotedName($this), + $newName->getQuotedName($this), + ); + } + + $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff)); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Replace the column with the given name with the new column. + * + * @param string $tableName + * @param array $columns + * @param string $columnName + * + * @return array + * + * @throws Exception + */ + private function replaceColumn($tableName, array $columns, $columnName, Column $column): array + { + $keys = array_keys($columns); + $index = array_search($columnName, $keys, true); + + if ($index === false) { + throw SchemaException::columnDoesNotExist($columnName, $tableName); + } + + $values = array_values($columns); + + $keys[$index] = strtolower($column->getName()); + $values[$index] = $column; + + return array_combine($keys, $values); + } + + /** + * @return string[]|false + * + * @throws Exception + */ + private function getSimpleAlterTableSQL(TableDiff $diff) + { + // Suppress changes on integer type autoincrement columns. + foreach ($diff->getModifiedColumns() as $columnDiff) { + $oldColumn = $columnDiff->getOldColumn(); + + if ($oldColumn === null) { + continue; + } + + $newColumn = $columnDiff->getNewColumn(); + + if (! $newColumn->getAutoincrement() || ! $newColumn->getType() instanceof IntegerType) { + continue; + } + + $oldColumnName = $oldColumn->getName(); + + if (! $columnDiff->hasTypeChanged() && $columnDiff->hasUnsignedChanged()) { + unset($diff->changedColumns[$oldColumnName]); + + continue; + } + + $fromColumnType = $oldColumn->getType(); + + if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) { + continue; + } + + unset($diff->changedColumns[$oldColumnName]); + } + + if ( + count($diff->getModifiedColumns()) > 0 + || count($diff->getDroppedColumns()) > 0 + || count($diff->getRenamedColumns()) > 0 + || count($diff->getAddedIndexes()) > 0 + || count($diff->getModifiedIndexes()) > 0 + || count($diff->getDroppedIndexes()) > 0 + || count($diff->getRenamedIndexes()) > 0 + || count($diff->getAddedForeignKeys()) > 0 + || count($diff->getModifiedForeignKeys()) > 0 + || count($diff->getDroppedForeignKeys()) > 0 + ) { + return false; + } + + $table = $diff->getOldTable() ?? $diff->getName($this); + + $sql = []; + $tableSql = []; + $columnSql = []; + + foreach ($diff->getAddedColumns() as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $definition = array_merge([ + 'unique' => null, + 'autoincrement' => null, + 'default' => null, + ], $column->toArray()); + + $type = $definition['type']; + + switch (true) { + case isset($definition['columnDefinition']) || $definition['autoincrement'] || $definition['unique']: + case $type instanceof Types\DateTimeType && $definition['default'] === $this->getCurrentTimestampSQL(): + case $type instanceof Types\DateType && $definition['default'] === $this->getCurrentDateSQL(): + case $type instanceof Types\TimeType && $definition['default'] === $this->getCurrentTimeSQL(): + return false; + } + + $definition['name'] = $column->getQuotedName($this); + if ($type instanceof Types\StringType) { + $definition['length'] ??= 255; + } + + $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' + . $this->getColumnDeclarationSQL($definition['name'], $definition); + } + + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + if ($diff->newName !== false) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + 'Generation of SQL that renames a table using %s is deprecated.' + . ' Use getRenameTableSQL() instead.', + __METHOD__, + ); + + $newTable = new Identifier($diff->newName); + + $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' + . $newTable->getQuotedName($this); + } + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** @return string[] */ + private function getColumnNamesInAlteredTable(TableDiff $diff, Table $fromTable): array + { + $columns = []; + + foreach ($fromTable->getColumns() as $columnName => $column) { + $columns[strtolower($columnName)] = $column->getName(); + } + + foreach ($diff->getDroppedColumns() as $column) { + $columnName = strtolower($column->getName()); + if (! isset($columns[$columnName])) { + continue; + } + + unset($columns[$columnName]); + } + + foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { + $columnName = $column->getName(); + $columns[strtolower($oldColumnName)] = $columnName; + $columns[strtolower($columnName)] = $columnName; + } + + foreach ($diff->getModifiedColumns() as $columnDiff) { + $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); + + $oldColumnName = $oldColumn->getName(); + $newColumnName = $columnDiff->getNewColumn()->getName(); + $columns[strtolower($oldColumnName)] = $newColumnName; + $columns[strtolower($newColumnName)] = $newColumnName; + } + + foreach ($diff->getAddedColumns() as $column) { + $columnName = $column->getName(); + $columns[strtolower($columnName)] = $columnName; + } + + return $columns; + } + + /** @return Index[] */ + private function getIndexesInAlteredTable(TableDiff $diff, Table $fromTable): array + { + $indexes = $fromTable->getIndexes(); + $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); + + foreach ($indexes as $key => $index) { + foreach ($diff->getRenamedIndexes() as $oldIndexName => $renamedIndex) { + if (strtolower($key) !== strtolower($oldIndexName)) { + continue; + } + + unset($indexes[$key]); + } + + $changed = false; + $indexColumns = []; + foreach ($index->getColumns() as $columnName) { + $normalizedColumnName = strtolower($columnName); + if (! isset($columnNames[$normalizedColumnName])) { + unset($indexes[$key]); + continue 2; + } + + $indexColumns[] = $columnNames[$normalizedColumnName]; + if ($columnName === $columnNames[$normalizedColumnName]) { + continue; + } + + $changed = true; + } + + if (! $changed) { + continue; + } + + $indexes[$key] = new Index( + $index->getName(), + $indexColumns, + $index->isUnique(), + $index->isPrimary(), + $index->getFlags(), + ); + } + + foreach ($diff->getDroppedIndexes() as $index) { + $indexName = strtolower($index->getName()); + if (strlen($indexName) === 0 || ! isset($indexes[$indexName])) { + continue; + } + + unset($indexes[$indexName]); + } + + foreach ( + array_merge( + $diff->getModifiedIndexes(), + $diff->getAddedIndexes(), + $diff->getRenamedIndexes(), + ) as $index + ) { + $indexName = strtolower($index->getName()); + if (strlen($indexName) > 0) { + $indexes[$indexName] = $index; + } else { + $indexes[] = $index; + } + } + + return $indexes; + } + + /** @return ForeignKeyConstraint[] */ + private function getForeignKeysInAlteredTable(TableDiff $diff, Table $fromTable): array + { + $foreignKeys = $fromTable->getForeignKeys(); + $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); + + foreach ($foreignKeys as $key => $constraint) { + $changed = false; + $localColumns = []; + foreach ($constraint->getLocalColumns() as $columnName) { + $normalizedColumnName = strtolower($columnName); + if (! isset($columnNames[$normalizedColumnName])) { + unset($foreignKeys[$key]); + continue 2; + } + + $localColumns[] = $columnNames[$normalizedColumnName]; + if ($columnName === $columnNames[$normalizedColumnName]) { + continue; + } + + $changed = true; + } + + if (! $changed) { + continue; + } + + $foreignKeys[$key] = new ForeignKeyConstraint( + $localColumns, + $constraint->getForeignTableName(), + $constraint->getForeignColumns(), + $constraint->getName(), + $constraint->getOptions(), + ); + } + + foreach ($diff->getDroppedForeignKeys() as $constraint) { + if (! $constraint instanceof ForeignKeyConstraint) { + $constraint = new Identifier($constraint); + } + + $constraintName = strtolower($constraint->getName()); + if (strlen($constraintName) === 0 || ! isset($foreignKeys[$constraintName])) { + continue; + } + + unset($foreignKeys[$constraintName]); + } + + foreach (array_merge($diff->getModifiedForeignKeys(), $diff->getAddedForeignKeys()) as $constraint) { + $constraintName = strtolower($constraint->getName()); + if (strlen($constraintName) > 0) { + $foreignKeys[$constraintName] = $constraint; + } else { + $foreignKeys[] = $constraint; + } + } + + return $foreignKeys; + } + + /** @return Index[] */ + private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $fromTable): array + { + $primaryIndex = []; + + foreach ($this->getIndexesInAlteredTable($diff, $fromTable) as $index) { + if (! $index->isPrimary()) { + continue; + } + + $primaryIndex = [$index->getName() => $index]; + } + + return $primaryIndex; + } + + public function createSchemaManager(Connection $connection): SqliteSchemaManager + { + return new SqliteSchemaManager($connection, $this); + } +} diff --git a/vendor/doctrine/dbal/src/Platforms/TrimMode.php b/vendor/doctrine/dbal/src/Platforms/TrimMode.php new file mode 100644 index 0000000..01356c0 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/TrimMode.php @@ -0,0 +1,21 @@ +converter = $converter; + } + + public function prepare(string $sql): DriverStatement + { + return new Statement( + parent::prepare($sql), + $this->converter, + ); + } + + public function query(string $sql): DriverResult + { + return new Result( + parent::query($sql), + $this->converter, + ); + } +} diff --git a/vendor/doctrine/dbal/src/Portability/Converter.php b/vendor/doctrine/dbal/src/Portability/Converter.php new file mode 100644 index 0000000..d050397 --- /dev/null +++ b/vendor/doctrine/dbal/src/Portability/Converter.php @@ -0,0 +1,300 @@ +createConvertValue($convertEmptyStringToNull, $rightTrimString); + $convertNumeric = $this->createConvertRow($convertValue, null); + $convertAssociative = $this->createConvertRow($convertValue, $case); + + $this->convertNumeric = $this->createConvert($convertNumeric, [self::class, 'id']); + $this->convertAssociative = $this->createConvert($convertAssociative, [self::class, 'id']); + $this->convertOne = $this->createConvert($convertValue, [self::class, 'id']); + + $this->convertAllNumeric = $this->createConvertAll($convertNumeric, [self::class, 'id']); + $this->convertAllAssociative = $this->createConvertAll($convertAssociative, [self::class, 'id']); + $this->convertFirstColumn = $this->createConvertAll($convertValue, [self::class, 'id']); + } + + /** + * @param array|false $row + * + * @return list|false + */ + public function convertNumeric($row) + { + return ($this->convertNumeric)($row); + } + + /** + * @param array|false $row + * + * @return array|false + */ + public function convertAssociative($row) + { + return ($this->convertAssociative)($row); + } + + /** + * @param mixed|false $value + * + * @return mixed|false + */ + public function convertOne($value) + { + return ($this->convertOne)($value); + } + + /** + * @param list> $data + * + * @return list> + */ + public function convertAllNumeric(array $data): array + { + return ($this->convertAllNumeric)($data); + } + + /** + * @param list> $data + * + * @return list> + */ + public function convertAllAssociative(array $data): array + { + return ($this->convertAllAssociative)($data); + } + + /** + * @param list $data + * + * @return list + */ + public function convertFirstColumn(array $data): array + { + return ($this->convertFirstColumn)($data); + } + + /** + * @param T $value + * + * @return T + * + * @template T + */ + private static function id($value) + { + return $value; + } + + /** + * @param T $value + * + * @return T|null + * + * @template T + */ + private static function convertEmptyStringToNull($value) + { + if ($value === '') { + return null; + } + + return $value; + } + + /** + * @param T $value + * + * @return T|string + * @psalm-return (T is string ? string : T) + * + * @template T + */ + private static function rightTrimString($value) + { + if (! is_string($value)) { + return $value; + } + + return rtrim($value); + } + + /** + * Creates a function that will convert each individual value retrieved from the database + * + * @param bool $convertEmptyStringToNull Whether each empty string should be converted to NULL + * @param bool $rightTrimString Whether each string should right-trimmed + * + * @return callable|null The resulting function or NULL if no conversion is needed + */ + private function createConvertValue(bool $convertEmptyStringToNull, bool $rightTrimString): ?callable + { + $functions = []; + + if ($convertEmptyStringToNull) { + $functions[] = [self::class, 'convertEmptyStringToNull']; + } + + if ($rightTrimString) { + $functions[] = [self::class, 'rightTrimString']; + } + + return $this->compose(...$functions); + } + + /** + * Creates a function that will convert each array-row retrieved from the database + * + * @param callable|null $function The function that will convert each value + * @param self::CASE_LOWER|self::CASE_UPPER|null $case Column name case + * + * @return callable|null The resulting function or NULL if no conversion is needed + */ + private function createConvertRow(?callable $function, ?int $case): ?callable + { + $functions = []; + + if ($function !== null) { + $functions[] = $this->createMapper($function); + } + + if ($case !== null) { + $functions[] = static function (array $row) use ($case): array { + return array_change_key_case($row, $case); + }; + } + + return $this->compose(...$functions); + } + + /** + * Creates a function that will be applied to the return value of Statement::fetch*() + * or an identity function if no conversion is needed + * + * @param callable|null $function The function that will convert each tow + * @param callable $id Identity function + */ + private function createConvert(?callable $function, callable $id): callable + { + if ($function === null) { + return $id; + } + + return /** + * @param T $value + * + * @psalm-return (T is false ? false : T) + * + * @template T + */ + static function ($value) use ($function) { + if ($value === false) { + return false; + } + + return $function($value); + }; + } + + /** + * Creates a function that will be applied to the return value of Statement::fetchAll*() + * or an identity function if no transformation is required + * + * @param callable|null $function The function that will transform each value + * @param callable $id Identity function + */ + private function createConvertAll(?callable $function, callable $id): callable + { + if ($function === null) { + return $id; + } + + return $this->createMapper($function); + } + + /** + * Creates a function that maps each value of the array using the given function + * + * @param callable $function The function that maps each value of the array + */ + private function createMapper(callable $function): callable + { + return static function (array $array) use ($function): array { + return array_map($function, $array); + }; + } + + /** + * Creates a composition of the given set of functions + * + * @param callable(T):T ...$functions The functions to compose + * + * @return callable(T):T|null + * + * @template T + */ + private function compose(callable ...$functions): ?callable + { + return array_reduce($functions, static function (?callable $carry, callable $item): callable { + if ($carry === null) { + return $item; + } + + return /** + * @param T $value + * + * @return T + * + * @template T + */ + static function ($value) use ($carry, $item) { + return $item($carry($value)); + }; + }); + } +} diff --git a/vendor/doctrine/dbal/src/Portability/Driver.php b/vendor/doctrine/dbal/src/Portability/Driver.php new file mode 100644 index 0000000..a5ac967 --- /dev/null +++ b/vendor/doctrine/dbal/src/Portability/Driver.php @@ -0,0 +1,83 @@ +mode = $mode; + $this->case = $case; + } + + /** + * {@inheritDoc} + */ + public function connect( + #[SensitiveParameter] + array $params + ) { + $connection = parent::connect($params); + + $portability = (new OptimizeFlags())( + $this->getDatabasePlatform(), + $this->mode, + ); + + $case = null; + + if ($this->case !== 0 && ($portability & Connection::PORTABILITY_FIX_CASE) !== 0) { + $nativeConnection = null; + if (method_exists($connection, 'getNativeConnection')) { + try { + $nativeConnection = $connection->getNativeConnection(); + } catch (LogicException $e) { + } + } + + if ($nativeConnection instanceof PDO) { + $portability &= ~Connection::PORTABILITY_FIX_CASE; + $nativeConnection->setAttribute( + PDO::ATTR_CASE, + $this->case === ColumnCase::LOWER ? PDO::CASE_LOWER : PDO::CASE_UPPER, + ); + } else { + $case = $this->case === ColumnCase::LOWER ? Converter::CASE_LOWER : Converter::CASE_UPPER; + } + } + + $convertEmptyStringToNull = ($portability & Connection::PORTABILITY_EMPTY_TO_NULL) !== 0; + $rightTrimString = ($portability & Connection::PORTABILITY_RTRIM) !== 0; + + if (! $convertEmptyStringToNull && ! $rightTrimString && $case === null) { + return $connection; + } + + return new Connection( + $connection, + new Converter($convertEmptyStringToNull, $rightTrimString, $case), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Portability/Middleware.php b/vendor/doctrine/dbal/src/Portability/Middleware.php new file mode 100644 index 0000000..fae2c55 --- /dev/null +++ b/vendor/doctrine/dbal/src/Portability/Middleware.php @@ -0,0 +1,38 @@ +mode = $mode; + $this->case = $case; + } + + public function wrap(DriverInterface $driver): DriverInterface + { + if ($this->mode !== 0) { + return new Driver($driver, $this->mode, $this->case); + } + + return $driver; + } +} diff --git a/vendor/doctrine/dbal/src/Portability/OptimizeFlags.php b/vendor/doctrine/dbal/src/Portability/OptimizeFlags.php new file mode 100644 index 0000000..884a936 --- /dev/null +++ b/vendor/doctrine/dbal/src/Portability/OptimizeFlags.php @@ -0,0 +1,42 @@ + + */ + private static array $platforms = [ + DB2Platform::class => 0, + OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL, + PostgreSQLPlatform::class => 0, + SqlitePlatform::class => 0, + SQLServerPlatform::class => 0, + ]; + + public function __invoke(AbstractPlatform $platform, int $flags): int + { + foreach (self::$platforms as $class => $mask) { + if ($platform instanceof $class) { + $flags &= ~$mask; + + break; + } + } + + return $flags; + } +} diff --git a/vendor/doctrine/dbal/src/Portability/Result.php b/vendor/doctrine/dbal/src/Portability/Result.php new file mode 100644 index 0000000..da1eca9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Portability/Result.php @@ -0,0 +1,81 @@ +converter = $converter; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + return $this->converter->convertNumeric( + parent::fetchNumeric(), + ); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->converter->convertAssociative( + parent::fetchAssociative(), + ); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return $this->converter->convertOne( + parent::fetchOne(), + ); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return $this->converter->convertAllNumeric( + parent::fetchAllNumeric(), + ); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return $this->converter->convertAllAssociative( + parent::fetchAllAssociative(), + ); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return $this->converter->convertFirstColumn( + parent::fetchFirstColumn(), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Portability/Statement.php b/vendor/doctrine/dbal/src/Portability/Statement.php new file mode 100644 index 0000000..8fcd79d --- /dev/null +++ b/vendor/doctrine/dbal/src/Portability/Statement.php @@ -0,0 +1,36 @@ +Statement and applies portability measures. + */ + public function __construct(DriverStatement $stmt, Converter $converter) + { + parent::__construct($stmt); + + $this->converter = $converter; + } + + /** + * {@inheritDoc} + */ + public function execute($params = null): ResultInterface + { + return new Result( + parent::execute($params), + $this->converter, + ); + } +} diff --git a/vendor/doctrine/dbal/src/Query.php b/vendor/doctrine/dbal/src/Query.php new file mode 100644 index 0000000..bfc9b14 --- /dev/null +++ b/vendor/doctrine/dbal/src/Query.php @@ -0,0 +1,64 @@ + + */ + private array $params; + + /** + * The types of the parameters bound to the query. + * + * @var array + */ + private array $types; + + /** + * @param array $params + * @param array $types + * + * @psalm-suppress ImpurePropertyAssignment + */ + public function __construct(string $sql, array $params, array $types) + { + $this->sql = $sql; + $this->params = $params; + $this->types = $types; + } + + public function getSQL(): string + { + return $this->sql; + } + + /** @return array */ + public function getParams(): array + { + return $this->params; + } + + /** @return array */ + public function getTypes(): array + { + return $this->types; + } +} diff --git a/vendor/doctrine/dbal/src/Query/Expression/CompositeExpression.php b/vendor/doctrine/dbal/src/Query/Expression/CompositeExpression.php new file mode 100644 index 0000000..4cad8ec --- /dev/null +++ b/vendor/doctrine/dbal/src/Query/Expression/CompositeExpression.php @@ -0,0 +1,183 @@ +type = $type; + + $this->addMultiple($parts); + + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3864', + 'Do not use CompositeExpression constructor directly, use static and() and or() factory methods.', + ); + } + + /** + * @param self|string $part + * @param self|string ...$parts + */ + public static function and($part, ...$parts): self + { + return new self(self::TYPE_AND, array_merge([$part], $parts)); + } + + /** + * @param self|string $part + * @param self|string ...$parts + */ + public static function or($part, ...$parts): self + { + return new self(self::TYPE_OR, array_merge([$part], $parts)); + } + + /** + * Adds multiple parts to composite expression. + * + * @deprecated This class will be made immutable. Use with() instead. + * + * @param self[]|string[] $parts + * + * @return CompositeExpression + */ + public function addMultiple(array $parts = []) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3844', + 'CompositeExpression::addMultiple() is deprecated, use CompositeExpression::with() instead.', + ); + + foreach ($parts as $part) { + $this->add($part); + } + + return $this; + } + + /** + * Adds an expression to composite expression. + * + * @deprecated This class will be made immutable. Use with() instead. + * + * @param mixed $part + * + * @return CompositeExpression + */ + public function add($part) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3844', + 'CompositeExpression::add() is deprecated, use CompositeExpression::with() instead.', + ); + + if ($part === null) { + return $this; + } + + if ($part instanceof self && count($part) === 0) { + return $this; + } + + $this->parts[] = $part; + + return $this; + } + + /** + * Returns a new CompositeExpression with the given parts added. + * + * @param self|string $part + * @param self|string ...$parts + */ + public function with($part, ...$parts): self + { + $that = clone $this; + + $that->parts = array_merge($that->parts, [$part], $parts); + + return $that; + } + + /** + * Retrieves the amount of expressions on composite expression. + * + * @return int + * @psalm-return int<0, max> + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->parts); + } + + /** + * Retrieves the string representation of this composite expression. + * + * @return string + */ + public function __toString() + { + if ($this->count() === 1) { + return (string) $this->parts[0]; + } + + return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')'; + } + + /** + * Returns the type of this composite expression (AND/OR). + * + * @return string + */ + public function getType() + { + return $this->type; + } +} diff --git a/vendor/doctrine/dbal/src/Query/Expression/ExpressionBuilder.php b/vendor/doctrine/dbal/src/Query/Expression/ExpressionBuilder.php new file mode 100644 index 0000000..d532b28 --- /dev/null +++ b/vendor/doctrine/dbal/src/Query/Expression/ExpressionBuilder.php @@ -0,0 +1,323 @@ +'; + public const LT = '<'; + public const LTE = '<='; + public const GT = '>'; + public const GTE = '>='; + + /** + * The DBAL Connection. + */ + private Connection $connection; + + /** + * Initializes a new ExpressionBuilder. + * + * @param Connection $connection The DBAL Connection. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Creates a conjunction of the given expressions. + * + * @param string|CompositeExpression $expression + * @param string|CompositeExpression ...$expressions + */ + public function and($expression, ...$expressions): CompositeExpression + { + return CompositeExpression::and($expression, ...$expressions); + } + + /** + * Creates a disjunction of the given expressions. + * + * @param string|CompositeExpression $expression + * @param string|CompositeExpression ...$expressions + */ + public function or($expression, ...$expressions): CompositeExpression + { + return CompositeExpression::or($expression, ...$expressions); + } + + /** + * @deprecated Use `and()` instead. + * + * @param mixed $x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return CompositeExpression + */ + public function andX($x = null) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3851', + 'ExpressionBuilder::andX() is deprecated, use ExpressionBuilder::and() instead.', + ); + + return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + /** + * @deprecated Use `or()` instead. + * + * @param mixed $x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return CompositeExpression + */ + public function orX($x = null) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/3851', + 'ExpressionBuilder::orX() is deprecated, use ExpressionBuilder::or() instead.', + ); + + return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); + } + + /** + * Creates a comparison expression. + * + * @param mixed $x The left expression. + * @param string $operator One of the ExpressionBuilder::* constants. + * @param mixed $y The right expression. + * + * @return string + */ + public function comparison($x, $operator, $y) + { + return $x . ' ' . $operator . ' ' . $y; + } + + /** + * Creates an equality comparison expression with the given arguments. + * + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a = . Example: + * + * [php] + * // u.id = ? + * $expr->eq('u.id', '?'); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function eq($x, $y) + { + return $this->comparison($x, self::EQ, $y); + } + + /** + * Creates a non equality comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <> . Example: + * + * [php] + * // u.id <> 1 + * $q->where($q->expr()->neq('u.id', '1')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function neq($x, $y) + { + return $this->comparison($x, self::NEQ, $y); + } + + /** + * Creates a lower-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a < . Example: + * + * [php] + * // u.id < ? + * $q->where($q->expr()->lt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function lt($x, $y) + { + return $this->comparison($x, self::LT, $y); + } + + /** + * Creates a lower-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <= . Example: + * + * [php] + * // u.id <= ? + * $q->where($q->expr()->lte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function lte($x, $y) + { + return $this->comparison($x, self::LTE, $y); + } + + /** + * Creates a greater-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a > . Example: + * + * [php] + * // u.id > ? + * $q->where($q->expr()->gt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function gt($x, $y) + { + return $this->comparison($x, self::GT, $y); + } + + /** + * Creates a greater-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a >= . Example: + * + * [php] + * // u.id >= ? + * $q->where($q->expr()->gte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function gte($x, $y) + { + return $this->comparison($x, self::GTE, $y); + } + + /** + * Creates an IS NULL expression with the given arguments. + * + * @param string $x The expression to be restricted by IS NULL. + * + * @return string + */ + public function isNull($x) + { + return $x . ' IS NULL'; + } + + /** + * Creates an IS NOT NULL expression with the given arguments. + * + * @param string $x The expression to be restricted by IS NOT NULL. + * + * @return string + */ + public function isNotNull($x) + { + return $x . ' IS NOT NULL'; + } + + /** + * Creates a LIKE() comparison expression with the given arguments. + * + * @param string $x The expression to be inspected by the LIKE comparison + * @param mixed $y The pattern to compare against + * + * @return string + */ + public function like($x, $y/*, ?string $escapeChar = null */) + { + return $this->comparison($x, 'LIKE', $y) . + (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); + } + + /** + * Creates a NOT LIKE() comparison expression with the given arguments. + * + * @param string $x The expression to be inspected by the NOT LIKE comparison + * @param mixed $y The pattern to compare against + * + * @return string + */ + public function notLike($x, $y/*, ?string $escapeChar = null */) + { + return $this->comparison($x, 'NOT LIKE', $y) . + (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); + } + + /** + * Creates an IN () comparison expression with the given arguments. + * + * @param string $x The SQL expression to be matched against the set. + * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set. + * + * @return string + */ + public function in($x, $y) + { + return $this->comparison($x, 'IN', '(' . implode(', ', (array) $y) . ')'); + } + + /** + * Creates a NOT IN () comparison expression with the given arguments. + * + * @param string $x The SQL expression to be matched against the set. + * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set. + * + * @return string + */ + public function notIn($x, $y) + { + return $this->comparison($x, 'NOT IN', '(' . implode(', ', (array) $y) . ')'); + } + + /** + * Builds an SQL literal from a given input parameter. + * + * The usage of this method is discouraged. Use prepared statements + * or {@see AbstractPlatform::quoteStringLiteral()} instead. + * + * @param mixed $input The parameter to be quoted. + * @param int|null $type The type of the parameter. + * + * @return string + */ + public function literal($input, $type = null) + { + return $this->connection->quote($input, $type); + } +} diff --git a/vendor/doctrine/dbal/src/Query/ForUpdate.php b/vendor/doctrine/dbal/src/Query/ForUpdate.php new file mode 100644 index 0000000..fe54df9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Query/ForUpdate.php @@ -0,0 +1,21 @@ +conflictResolutionMode = $conflictResolutionMode; + } + + public function getConflictResolutionMode(): int + { + return $this->conflictResolutionMode; + } +} diff --git a/vendor/doctrine/dbal/src/Query/ForUpdate/ConflictResolutionMode.php b/vendor/doctrine/dbal/src/Query/ForUpdate/ConflictResolutionMode.php new file mode 100644 index 0000000..f968f7b --- /dev/null +++ b/vendor/doctrine/dbal/src/Query/ForUpdate/ConflictResolutionMode.php @@ -0,0 +1,27 @@ +maxResults = $maxResults; + $this->firstResult = $firstResult; + } + + public function isDefined(): bool + { + return $this->maxResults !== null || $this->firstResult !== 0; + } + + public function getMaxResults(): ?int + { + return $this->maxResults; + } + + public function getFirstResult(): int + { + return $this->firstResult; + } +} diff --git a/vendor/doctrine/dbal/src/Query/QueryBuilder.php b/vendor/doctrine/dbal/src/Query/QueryBuilder.php new file mode 100644 index 0000000..4c5d6b8 --- /dev/null +++ b/vendor/doctrine/dbal/src/Query/QueryBuilder.php @@ -0,0 +1,1759 @@ + [], + 'distinct' => false, + 'from' => [], + 'join' => [], + 'set' => [], + 'where' => null, + 'groupBy' => [], + 'having' => null, + 'orderBy' => [], + 'values' => [], + 'for_update' => null, + ]; + + /** + * The array of SQL parts collected. + * + * @var mixed[] + */ + private array $sqlParts = self::SQL_PARTS_DEFAULTS; + + /** + * The complete SQL string for this query. + */ + private ?string $sql = null; + + /** + * The query parameters. + * + * @var list|array + */ + private $params = []; + + /** + * The parameter type map of this query. + * + * @var array|array + */ + private array $paramTypes = []; + + /** + * The type of query this is. Can be select, update or delete. + * + * @psalm-var self::SELECT|self::DELETE|self::UPDATE|self::INSERT + */ + private int $type = self::SELECT; + + /** + * The state of the query object. Can be dirty or clean. + * + * @psalm-var self::STATE_* + */ + private int $state = self::STATE_CLEAN; + + /** + * The index of the first result to retrieve. + */ + private int $firstResult = 0; + + /** + * The maximum number of results to retrieve or NULL to retrieve all results. + */ + private ?int $maxResults = null; + + /** + * The counter of bound parameters used with {@see bindValue). + */ + private int $boundCounter = 0; + + /** + * The query cache profile used for caching results. + */ + private ?QueryCacheProfile $resultCacheProfile = null; + + /** + * Initializes a new QueryBuilder. + * + * @param Connection $connection The DBAL Connection. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Gets an ExpressionBuilder used for object-oriented construction of query expressions. + * This producer method is intended for convenient inline usage. Example: + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where($qb->expr()->eq('u.id', 1)); + * + * + * For more complex expression construction, consider storing the expression + * builder object in a local variable. + * + * @return ExpressionBuilder + */ + public function expr() + { + return $this->connection->getExpressionBuilder(); + } + + /** + * Gets the type of the currently built query. + * + * @deprecated If necessary, track the type of the query being built outside of the builder. + * + * @return int + */ + public function getType() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5551', + 'Relying on the type of the query being built is deprecated.' + . ' If necessary, track the type of the query being built outside of the builder.', + ); + + return $this->type; + } + + /** + * Gets the associated DBAL Connection for this query builder. + * + * @deprecated Use the connection used to instantiate the builder instead. + * + * @return Connection + */ + public function getConnection() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5780', + '%s is deprecated. Use the connection used to instantiate the builder instead.', + __METHOD__, + ); + + return $this->connection; + } + + /** + * Gets the state of this query builder instance. + * + * @deprecated The builder state is an internal concern. + * + * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. + * @psalm-return self::STATE_* + */ + public function getState() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5551', + 'Relying on the query builder state is deprecated as it is an internal concern.', + ); + + return $this->state; + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as an associative array. + * + * @return array|false False is returned if no rows are found. + * + * @throws Exception + */ + public function fetchAssociative() + { + return $this->executeQuery()->fetchAssociative(); + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as a numerically indexed array. + * + * @return array|false False is returned if no rows are found. + * + * @throws Exception + */ + public function fetchNumeric() + { + return $this->executeQuery()->fetchNumeric(); + } + + /** + * Prepares and executes an SQL query and returns the value of a single column + * of the first row of the result. + * + * @return mixed|false False is returned if no rows are found. + * + * @throws Exception + */ + public function fetchOne() + { + return $this->executeQuery()->fetchOne(); + } + + /** + * Prepares and executes an SQL query and returns the result as an array of numeric arrays. + * + * @return array> + * + * @throws Exception + */ + public function fetchAllNumeric(): array + { + return $this->executeQuery()->fetchAllNumeric(); + } + + /** + * Prepares and executes an SQL query and returns the result as an array of associative arrays. + * + * @return array> + * + * @throws Exception + */ + public function fetchAllAssociative(): array + { + return $this->executeQuery()->fetchAllAssociative(); + } + + /** + * Prepares and executes an SQL query and returns the result as an associative array with the keys + * mapped to the first column and the values mapped to the second column. + * + * @return array + * + * @throws Exception + */ + public function fetchAllKeyValue(): array + { + return $this->executeQuery()->fetchAllKeyValue(); + } + + /** + * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped + * to the first column and the values being an associative array representing the rest of the columns + * and their values. + * + * @return array> + * + * @throws Exception + */ + public function fetchAllAssociativeIndexed(): array + { + return $this->executeQuery()->fetchAllAssociativeIndexed(); + } + + /** + * Prepares and executes an SQL query and returns the result as an array of the first column values. + * + * @return array + * + * @throws Exception + */ + public function fetchFirstColumn(): array + { + return $this->executeQuery()->fetchFirstColumn(); + } + + /** + * Executes an SQL query (SELECT) and returns a Result. + * + * @throws Exception + */ + public function executeQuery(): Result + { + return $this->connection->executeQuery( + $this->getSQL(), + $this->params, + $this->paramTypes, + $this->resultCacheProfile, + ); + } + + /** + * Executes an SQL statement and returns the number of affected rows. + * + * Should be used for INSERT, UPDATE and DELETE + * + * @return int The number of affected rows. + * + * @throws Exception + */ + public function executeStatement(): int + { + return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); + } + + /** + * Executes this query using the bound parameters and their types. + * + * @deprecated Use {@see executeQuery()} or {@see executeStatement()} instead. + * + * @return Result|int|string + * + * @throws Exception + */ + public function execute() + { + if ($this->type === self::SELECT) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4578', + 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeQuery() for SQL queries instead.', + ); + + return $this->executeQuery(); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4578', + 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeStatement() for SQL statements instead.', + ); + + return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); + } + + /** + * Gets the complete SQL string formed by the current specifications of this QueryBuilder. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * echo $qb->getSQL(); // SELECT u FROM User u + * + * + * @return string The SQL query string. + */ + public function getSQL() + { + if ($this->sql !== null && $this->state === self::STATE_CLEAN) { + return $this->sql; + } + + switch ($this->type) { + case self::INSERT: + $sql = $this->getSQLForInsert(); + break; + + case self::DELETE: + $sql = $this->getSQLForDelete(); + break; + + case self::UPDATE: + $sql = $this->getSQLForUpdate(); + break; + + case self::SELECT: + $sql = $this->getSQLForSelect(); + break; + } + + $this->state = self::STATE_CLEAN; + $this->sql = $sql; + + return $sql; + } + + /** + * Sets a query parameter for the query being constructed. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id') + * ->setParameter('user_id', 1); + * + * + * @param int|string $key Parameter position or name + * @param mixed $value Parameter value + * @param int|string|Type|null $type Parameter type + * + * @return $this This QueryBuilder instance. + */ + public function setParameter($key, $value, $type = ParameterType::STRING) + { + if ($type !== null) { + $this->paramTypes[$key] = $type; + } else { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5550', + 'Using NULL as prepared statement parameter type is deprecated.' + . 'Omit or use ParameterType::STRING instead', + ); + } + + $this->params[$key] = $value; + + return $this; + } + + /** + * Sets a collection of query parameters for the query being constructed. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id1 OR u.id = :user_id2') + * ->setParameters(array( + * 'user_id1' => 1, + * 'user_id2' => 2 + * )); + * + * + * @param list|array $params Parameters to set + * @param array|array $types Parameter types + * + * @return $this This QueryBuilder instance. + */ + public function setParameters(array $params, array $types = []) + { + $this->paramTypes = $types; + $this->params = $params; + + return $this; + } + + /** + * Gets all defined query parameters for the query being constructed indexed by parameter index or name. + * + * @return list|array The currently defined query parameters + */ + public function getParameters() + { + return $this->params; + } + + /** + * Gets a (previously set) query parameter of the query being constructed. + * + * @param mixed $key The key (index or name) of the bound parameter. + * + * @return mixed The value of the bound parameter. + */ + public function getParameter($key) + { + return $this->params[$key] ?? null; + } + + /** + * Gets all defined query parameter types for the query being constructed indexed by parameter index or name. + * + * @return array|array The currently defined + * query parameter types + */ + public function getParameterTypes() + { + return $this->paramTypes; + } + + /** + * Gets a (previously set) query parameter type of the query being constructed. + * + * @param int|string $key The key of the bound parameter type + * + * @return int|string|Type The value of the bound parameter type + */ + public function getParameterType($key) + { + return $this->paramTypes[$key] ?? ParameterType::STRING; + } + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param int $firstResult The first result to return. + * + * @return $this This QueryBuilder instance. + */ + public function setFirstResult($firstResult) + { + $this->state = self::STATE_DIRTY; + $this->firstResult = $firstResult; + + return $this; + } + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * + * @return int The position of the first result. + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results. + * + * @return $this This QueryBuilder instance. + */ + public function setMaxResults($maxResults) + { + $this->state = self::STATE_DIRTY; + $this->maxResults = $maxResults; + + return $this; + } + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if all results will be returned. + * + * @return int|null The maximum number of results. + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Locks the queried rows for a subsequent update. + * + * @return $this + */ + public function forUpdate(int $conflictResolutionMode = ConflictResolutionMode::ORDINARY): self + { + $this->state = self::STATE_DIRTY; + + $this->sqlParts['for_update'] = new ForUpdate($conflictResolutionMode); + + return $this; + } + + /** + * Either appends to or replaces a single, generic query part. + * + * The available parts are: 'select', 'from', 'set', 'where', + * 'groupBy', 'having' and 'orderBy'. + * + * @param string $sqlPartName + * @param mixed $sqlPart + * @param bool $append + * + * @return $this This QueryBuilder instance. + */ + public function add($sqlPartName, $sqlPart, $append = false) + { + $isArray = is_array($sqlPart); + $isMultiple = is_array($this->sqlParts[$sqlPartName]); + + if ($isMultiple && ! $isArray) { + $sqlPart = [$sqlPart]; + } + + $this->state = self::STATE_DIRTY; + + if ($append) { + if ( + $sqlPartName === 'orderBy' + || $sqlPartName === 'groupBy' + || $sqlPartName === 'select' + || $sqlPartName === 'set' + ) { + foreach ($sqlPart as $part) { + $this->sqlParts[$sqlPartName][] = $part; + } + } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) { + $key = key($sqlPart); + $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key]; + } elseif ($isMultiple) { + $this->sqlParts[$sqlPartName][] = $sqlPart; + } else { + $this->sqlParts[$sqlPartName] = $sqlPart; + } + + return $this; + } + + $this->sqlParts[$sqlPartName] = $sqlPart; + + return $this; + } + + /** + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. + * + * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id', 'p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); + * + * + * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. + * Pass each value as an individual argument. + * + * @return $this This QueryBuilder instance. + */ + public function select($select = null/*, string ...$selects*/) + { + $this->type = self::SELECT; + + if ($select === null) { + return $this; + } + + if (is_array($select)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3837', + 'Passing an array for the first argument to QueryBuilder::select() is deprecated, ' . + 'pass each value as an individual variadic argument instead.', + ); + } + + $selects = is_array($select) ? $select : func_get_args(); + + return $this->add('select', $selects); + } + + /** + * Adds or removes DISTINCT to/from the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id') + * ->distinct() + * ->from('users', 'u') + * + * + * @return $this This QueryBuilder instance. + */ + public function distinct(/* bool $distinct = true */): self + { + $this->sqlParts['distinct'] = func_num_args() < 1 || func_get_arg(0); + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Adds an item that is to be returned in the query result. + * + * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id') + * ->addSelect('p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); + * + * + * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. + * Pass each value as an individual argument. + * + * @return $this This QueryBuilder instance. + */ + public function addSelect($select = null/*, string ...$selects*/) + { + $this->type = self::SELECT; + + if ($select === null) { + return $this; + } + + if (is_array($select)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3837', + 'Passing an array for the first argument to QueryBuilder::addSelect() is deprecated, ' . + 'pass each value as an individual variadic argument instead.', + ); + } + + $selects = is_array($select) ? $select : func_get_args(); + + return $this->add('select', $selects, true); + } + + /** + * Turns the query being built into a bulk delete query that ranges over + * a certain table. + * + * + * $qb = $conn->createQueryBuilder() + * ->delete('users', 'u') + * ->where('u.id = :user_id') + * ->setParameter(':user_id', 1); + * + * + * @param string $delete The table whose rows are subject to the deletion. + * @param string $alias The table alias used in the constructed query. + * + * @return $this This QueryBuilder instance. + */ + public function delete($delete = null, $alias = null) + { + $this->type = self::DELETE; + + if ($delete === null) { + return $this; + } + + return $this->add('from', [ + 'table' => $delete, + 'alias' => $alias, + ]); + } + + /** + * Turns the query being built into a bulk update query that ranges over + * a certain table + * + * + * $qb = $conn->createQueryBuilder() + * ->update('counters', 'c') + * ->set('c.value', 'c.value + 1') + * ->where('c.id = ?'); + * + * + * @param string $update The table whose rows are subject to the update. + * @param string $alias The table alias used in the constructed query. + * + * @return $this This QueryBuilder instance. + */ + public function update($update = null, $alias = null) + { + $this->type = self::UPDATE; + + if ($update === null) { + return $this; + } + + return $this->add('from', [ + 'table' => $update, + 'alias' => $alias, + ]); + } + + /** + * Turns the query being built into an insert query that inserts into + * a certain table + * + * + * $qb = $conn->createQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?', + * 'password' => '?' + * ) + * ); + * + * + * @param string $insert The table into which the rows should be inserted. + * + * @return $this This QueryBuilder instance. + */ + public function insert($insert = null) + { + $this->type = self::INSERT; + + if ($insert === null) { + return $this; + } + + return $this->add('from', ['table' => $insert]); + } + + /** + * Creates and adds a query root corresponding to the table identified by the + * given alias, forming a cartesian product with any existing query roots. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id') + * ->from('users', 'u') + * + * + * @param string $from The table. + * @param string|null $alias The alias of the table. + * + * @return $this This QueryBuilder instance. + */ + public function from($from, $alias = null) + { + return $this->add('from', [ + 'table' => $from, + 'alias' => $alias, + ], true); + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function join($fromAlias, $join, $alias, $condition = null) + { + return $this->innerJoin($fromAlias, $join, $alias, $condition); + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function innerJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', [ + $fromAlias => [ + 'joinType' => 'inner', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition, + ], + ], true); + } + + /** + * Creates and adds a left join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function leftJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', [ + $fromAlias => [ + 'joinType' => 'left', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition, + ], + ], true); + } + + /** + * Creates and adds a right join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function rightJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', [ + $fromAlias => [ + 'joinType' => 'right', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition, + ], + ], true); + } + + /** + * Sets a new value for a column in a bulk update query. + * + * + * $qb = $conn->createQueryBuilder() + * ->update('counters', 'c') + * ->set('c.value', 'c.value + 1') + * ->where('c.id = ?'); + * + * + * @param string $key The column to set. + * @param string $value The value, expression, placeholder, etc. + * + * @return $this This QueryBuilder instance. + */ + public function set($key, $value) + { + return $this->add('set', $key . ' = ' . $value, true); + } + + /** + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('c.value') + * ->from('counters', 'c') + * ->where('c.id = ?'); + * + * // You can optionally programmatically build and/or expressions + * $qb = $conn->createQueryBuilder(); + * + * $or = $qb->expr()->orx(); + * $or->add($qb->expr()->eq('c.id', 1)); + * $or->add($qb->expr()->eq('c.id', 2)); + * + * $qb->update('counters', 'c') + * ->set('c.value', 'c.value + 1') + * ->where($or); + * + * + * @param mixed $predicates The restriction predicates. + * + * @return $this This QueryBuilder instance. + */ + public function where($predicates) + { + if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) { + $predicates = CompositeExpression::and(...func_get_args()); + } + + return $this->add('where', $predicates); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.username LIKE ?') + * ->andWhere('u.is_active = 1'); + * + * + * @see where() + * + * @param mixed $where The query restrictions. + * + * @return $this This QueryBuilder instance. + */ + public function andWhere($where) + { + $args = func_get_args(); + $where = $this->getQueryPart('where'); + + if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) { + $where = $where->with(...$args); + } else { + array_unshift($args, $where); + $where = CompositeExpression::and(...$args); + } + + return $this->add('where', $where, true); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->where('u.id = 1') + * ->orWhere('u.id = 2'); + * + * + * @see where() + * + * @param mixed $where The WHERE statement. + * + * @return $this This QueryBuilder instance. + */ + public function orWhere($where) + { + $args = func_get_args(); + $where = $this->getQueryPart('where'); + + if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) { + $where = $where->with(...$args); + } else { + array_unshift($args, $where); + $where = CompositeExpression::or(...$args); + } + + return $this->add('where', $where, true); + } + + /** + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. + * + * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.id'); + * + * + * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. + * Pass each value as an individual argument. + * + * @return $this This QueryBuilder instance. + */ + public function groupBy($groupBy/*, string ...$groupBys*/) + { + if (is_array($groupBy) && count($groupBy) === 0) { + return $this; + } + + if (is_array($groupBy)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3837', + 'Passing an array for the first argument to QueryBuilder::groupBy() is deprecated, ' . + 'pass each value as an individual variadic argument instead.', + ); + } + + $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); + + return $this->add('groupBy', $groupBy, false); + } + + /** + * Adds a grouping expression to the query. + * + * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.lastLogin') + * ->addGroupBy('u.createdAt'); + * + * + * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. + * Pass each value as an individual argument. + * + * @return $this This QueryBuilder instance. + */ + public function addGroupBy($groupBy/*, string ...$groupBys*/) + { + if (is_array($groupBy) && count($groupBy) === 0) { + return $this; + } + + if (is_array($groupBy)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3837', + 'Passing an array for the first argument to QueryBuilder::addGroupBy() is deprecated, ' . + 'pass each value as an individual variadic argument instead.', + ); + } + + $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); + + return $this->add('groupBy', $groupBy, true); + } + + /** + * Sets a value for a column in an insert query. + * + * + * $qb = $conn->createQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?' + * ) + * ) + * ->setValue('password', '?'); + * + * + * @param string $column The column into which the value should be inserted. + * @param string $value The value that should be inserted into the column. + * + * @return $this This QueryBuilder instance. + */ + public function setValue($column, $value) + { + $this->sqlParts['values'][$column] = $value; + + return $this; + } + + /** + * Specifies values for an insert query indexed by column names. + * Replaces any previous values, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?', + * 'password' => '?' + * ) + * ); + * + * + * @param mixed[] $values The values to specify for the insert query indexed by column names. + * + * @return $this This QueryBuilder instance. + */ + public function values(array $values) + { + return $this->add('values', $values); + } + + /** + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. + * + * @param mixed $having The restriction over the groups. + * + * @return $this This QueryBuilder instance. + */ + public function having($having) + { + if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) { + $having = CompositeExpression::and(...func_get_args()); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. + * + * @param mixed $having The restriction to append. + * + * @return $this This QueryBuilder instance. + */ + public function andHaving($having) + { + $args = func_get_args(); + $having = $this->getQueryPart('having'); + + if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) { + $having = $having->with(...$args); + } else { + array_unshift($args, $having); + $having = CompositeExpression::and(...$args); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. + * + * @param mixed $having The restriction to add. + * + * @return $this This QueryBuilder instance. + */ + public function orHaving($having) + { + $args = func_get_args(); + $having = $this->getQueryPart('having'); + + if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) { + $having = $having->with(...$args); + } else { + array_unshift($args, $having); + $having = CompositeExpression::or(...$args); + } + + return $this->add('having', $having); + } + + /** + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return $this This QueryBuilder instance. + */ + public function orderBy($sort, $order = null) + { + return $this->add('orderBy', $sort . ' ' . ($order ?? 'ASC'), false); + } + + /** + * Adds an ordering to the query results. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return $this This QueryBuilder instance. + */ + public function addOrderBy($sort, $order = null) + { + return $this->add('orderBy', $sort . ' ' . ($order ?? 'ASC'), true); + } + + /** + * Gets a query part by its name. + * + * @deprecated The query parts are implementation details and should not be relied upon. + * + * @param string $queryPartName + * + * @return mixed + */ + public function getQueryPart($queryPartName) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6179', + 'Getting query parts is deprecated as they are implementation details.', + ); + + return $this->sqlParts[$queryPartName]; + } + + /** + * Gets all query parts. + * + * @deprecated The query parts are implementation details and should not be relied upon. + * + * @return mixed[] + */ + public function getQueryParts() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6179', + 'Getting query parts is deprecated as they are implementation details.', + ); + + return $this->sqlParts; + } + + /** + * Resets SQL parts. + * + * @deprecated Use the dedicated reset*() methods instead. + * + * @param string[]|null $queryPartNames + * + * @return $this This QueryBuilder instance. + */ + public function resetQueryParts($queryPartNames = null) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6193', + '%s() is deprecated, instead use dedicated reset methods for the parts that shall be reset.', + __METHOD__, + ); + + $queryPartNames ??= array_keys($this->sqlParts); + + foreach ($queryPartNames as $queryPartName) { + $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName]; + } + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Resets a single SQL part. + * + * @deprecated Use the dedicated reset*() methods instead. + * + * @param string $queryPartName + * + * @return $this This QueryBuilder instance. + */ + public function resetQueryPart($queryPartName) + { + if ($queryPartName === 'distinct') { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6193', + 'Calling %s() with "distinct" is deprecated, call distinct(false) instead.', + __METHOD__, + ); + + return $this->distinct(false); + } + + $newMethodName = 'reset' . ucfirst($queryPartName); + if (array_key_exists($queryPartName, self::SQL_PARTS_DEFAULTS) && method_exists($this, $newMethodName)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6193', + 'Calling %s() with "%s" is deprecated, call %s() instead.', + __METHOD__, + $queryPartName, + $newMethodName, + ); + + return $this->$newMethodName(); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6193', + 'Calling %s() with "%s" is deprecated without replacement.', + __METHOD__, + $queryPartName, + $newMethodName, + ); + + $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName]; + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Resets the WHERE conditions for the query. + * + * @return $this This QueryBuilder instance. + */ + public function resetWhere(): self + { + $this->sqlParts['where'] = self::SQL_PARTS_DEFAULTS['where']; + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Resets the grouping for the query. + * + * @return $this This QueryBuilder instance. + */ + public function resetGroupBy(): self + { + $this->sqlParts['groupBy'] = self::SQL_PARTS_DEFAULTS['groupBy']; + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Resets the HAVING conditions for the query. + * + * @return $this This QueryBuilder instance. + */ + public function resetHaving(): self + { + $this->sqlParts['having'] = self::SQL_PARTS_DEFAULTS['having']; + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Resets the ordering for the query. + * + * @return $this This QueryBuilder instance. + */ + public function resetOrderBy(): self + { + $this->sqlParts['orderBy'] = self::SQL_PARTS_DEFAULTS['orderBy']; + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** @throws Exception */ + private function getSQLForSelect(): string + { + return $this->connection->getDatabasePlatform() + ->createSelectSQLBuilder() + ->buildSQL( + new SelectQuery( + $this->sqlParts['distinct'], + $this->sqlParts['select'], + $this->getFromClauses(), + $this->sqlParts['where'], + $this->sqlParts['groupBy'], + $this->sqlParts['having'], + $this->sqlParts['orderBy'], + new Limit($this->maxResults, $this->firstResult), + $this->sqlParts['for_update'], + ), + ); + } + + /** + * @return string[] + * + * @throws QueryException + */ + private function getFromClauses(): array + { + $fromClauses = []; + $knownAliases = []; + + // Loop through all FROM clauses + foreach ($this->sqlParts['from'] as $from) { + if ($from['alias'] === null) { + $tableSql = $from['table']; + $tableReference = $from['table']; + } else { + $tableSql = $from['table'] . ' ' . $from['alias']; + $tableReference = $from['alias']; + } + + $knownAliases[$tableReference] = true; + + $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases); + } + + $this->verifyAllAliasesAreKnown($knownAliases); + + return $fromClauses; + } + + /** + * @param array $knownAliases + * + * @throws QueryException + */ + private function verifyAllAliasesAreKnown(array $knownAliases): void + { + foreach ($this->sqlParts['join'] as $fromAlias => $joins) { + if (! isset($knownAliases[$fromAlias])) { + throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases)); + } + } + } + + /** + * Converts this instance into an INSERT string in SQL. + */ + private function getSQLForInsert(): string + { + return 'INSERT INTO ' . $this->sqlParts['from']['table'] . + ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' . + ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')'; + } + + /** + * Converts this instance into an UPDATE string in SQL. + */ + private function getSQLForUpdate(): string + { + $table = $this->sqlParts['from']['table'] + . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); + + return 'UPDATE ' . $table + . ' SET ' . implode(', ', $this->sqlParts['set']) + . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); + } + + /** + * Converts this instance into a DELETE string in SQL. + */ + private function getSQLForDelete(): string + { + $table = $this->sqlParts['from']['table'] + . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); + + return 'DELETE FROM ' . $table + . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); + } + + /** + * Gets a string representation of this QueryBuilder which corresponds to + * the final SQL query being constructed. + * + * @return string The string representation of this QueryBuilder. + */ + public function __toString() + { + return $this->getSQL(); + } + + /** + * Creates a new named parameter and bind the value $value to it. + * + * This method provides a shortcut for {@see Statement::bindValue()} + * when using prepared statements. + * + * The parameter $value specifies the value that you want to bind. If + * $placeholder is not provided createNamedParameter() will automatically + * create a placeholder for you. An automatic placeholder will be of the + * name ':dcValue1', ':dcValue2' etc. + * + * Example: + * + * $value = 2; + * $q->eq( 'id', $q->createNamedParameter( $value ) ); + * $stmt = $q->executeQuery(); // executed with 'id = 2' + * + * + * @link http://www.zetacomponents.org + * + * @param mixed $value + * @param int|string|Type|null $type + * @param string $placeHolder The name to bind with. The string must start with a colon ':'. + * + * @return string the placeholder name used. + */ + public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null) + { + if ($placeHolder === null) { + $this->boundCounter++; + $placeHolder = ':dcValue' . $this->boundCounter; + } + + $this->setParameter(substr($placeHolder, 1), $value, $type); + + return $placeHolder; + } + + /** + * Creates a new positional parameter and bind the given value to it. + * + * Attention: If you are using positional parameters with the query builder you have + * to be very careful to bind all parameters in the order they appear in the SQL + * statement , otherwise they get bound in the wrong order which can lead to serious + * bugs in your code. + * + * Example: + * + * $qb = $conn->createQueryBuilder(); + * $qb->select('u.*') + * ->from('users', 'u') + * ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING)) + * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING)) + * + * + * @param mixed $value + * @param int|string|Type|null $type + * + * @return string + */ + public function createPositionalParameter($value, $type = ParameterType::STRING) + { + $this->setParameter($this->boundCounter, $value, $type); + $this->boundCounter++; + + return '?'; + } + + /** + * @param string $fromAlias + * @param array $knownAliases + * + * @throws QueryException + */ + private function getSQLForJoins($fromAlias, array &$knownAliases): string + { + $sql = ''; + + if (isset($this->sqlParts['join'][$fromAlias])) { + foreach ($this->sqlParts['join'][$fromAlias] as $join) { + if (array_key_exists($join['joinAlias'], $knownAliases)) { + throw QueryException::nonUniqueAlias((string) $join['joinAlias'], array_keys($knownAliases)); + } + + $sql .= ' ' . strtoupper($join['joinType']) + . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']; + if ($join['joinCondition'] !== null) { + $sql .= ' ON ' . $join['joinCondition']; + } + + $knownAliases[$join['joinAlias']] = true; + } + + foreach ($this->sqlParts['join'][$fromAlias] as $join) { + $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases); + } + } + + return $sql; + } + + /** + * Deep clone of all expression objects in the SQL parts. + * + * @return void + */ + public function __clone() + { + foreach ($this->sqlParts as $part => $elements) { + if (is_array($this->sqlParts[$part])) { + foreach ($this->sqlParts[$part] as $idx => $element) { + if (! is_object($element)) { + continue; + } + + $this->sqlParts[$part][$idx] = clone $element; + } + } elseif (is_object($elements)) { + $this->sqlParts[$part] = clone $elements; + } + } + + foreach ($this->params as $name => $param) { + if (! is_object($param)) { + continue; + } + + $this->params[$name] = clone $param; + } + } + + /** + * Enables caching of the results of this query, for given amount of seconds + * and optionally specified which key to use for the cache entry. + * + * @return $this + */ + public function enableResultCache(QueryCacheProfile $cacheProfile): self + { + $this->resultCacheProfile = $cacheProfile; + + return $this; + } + + /** + * Disables caching of the results of this query. + * + * @return $this + */ + public function disableResultCache(): self + { + $this->resultCacheProfile = null; + + return $this; + } +} diff --git a/vendor/doctrine/dbal/src/Query/QueryException.php b/vendor/doctrine/dbal/src/Query/QueryException.php new file mode 100644 index 0000000..90d1f47 --- /dev/null +++ b/vendor/doctrine/dbal/src/Query/QueryException.php @@ -0,0 +1,37 @@ +distinct = $distinct; + $this->columns = $columns; + $this->from = $from; + $this->where = $where; + $this->groupBy = $groupBy; + $this->having = $having; + $this->orderBy = $orderBy; + $this->limit = $limit; + $this->forUpdate = $forUpdate; + } + + public function isDistinct(): bool + { + return $this->distinct; + } + + /** @return string[] */ + public function getColumns(): array + { + return $this->columns; + } + + /** @return string[] */ + public function getFrom(): array + { + return $this->from; + } + + public function getWhere(): ?string + { + return $this->where; + } + + /** @return string[] */ + public function getGroupBy(): array + { + return $this->groupBy; + } + + public function getHaving(): ?string + { + return $this->having; + } + + /** @return string[] */ + public function getOrderBy(): array + { + return $this->orderBy; + } + + public function getLimit(): Limit + { + return $this->limit; + } + + public function getForUpdate(): ?ForUpdate + { + return $this->forUpdate; + } +} diff --git a/vendor/doctrine/dbal/src/Result.php b/vendor/doctrine/dbal/src/Result.php new file mode 100644 index 0000000..92235d0 --- /dev/null +++ b/vendor/doctrine/dbal/src/Result.php @@ -0,0 +1,339 @@ +result = $result; + $this->connection = $connection; + } + + /** + * Returns the next row of the result as a numeric array or FALSE if there are no more rows. + * + * @return list|false + * + * @throws Exception + */ + public function fetchNumeric() + { + try { + return $this->result->fetchNumeric(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + /** + * Returns the next row of the result as an associative array or FALSE if there are no more rows. + * + * @return array|false + * + * @throws Exception + */ + public function fetchAssociative() + { + try { + return $this->result->fetchAssociative(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + /** + * Returns the first value of the next row of the result or FALSE if there are no more rows. + * + * @return mixed|false + * + * @throws Exception + */ + public function fetchOne() + { + try { + return $this->result->fetchOne(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + /** + * Returns an array containing all of the result rows represented as numeric arrays. + * + * @return list> + * + * @throws Exception + */ + public function fetchAllNumeric(): array + { + try { + return $this->result->fetchAllNumeric(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + /** + * Returns an array containing all of the result rows represented as associative arrays. + * + * @return list> + * + * @throws Exception + */ + public function fetchAllAssociative(): array + { + try { + return $this->result->fetchAllAssociative(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + /** + * Returns an array containing the values of the first column of the result. + * + * @return array + * + * @throws Exception + */ + public function fetchAllKeyValue(): array + { + $this->ensureHasKeyValue(); + + $data = []; + + foreach ($this->fetchAllNumeric() as [$key, $value]) { + $data[$key] = $value; + } + + return $data; + } + + /** + * Returns an associative array with the keys mapped to the first column and the values being + * an associative array representing the rest of the columns and their values. + * + * @return array> + * + * @throws Exception + */ + public function fetchAllAssociativeIndexed(): array + { + $data = []; + + foreach ($this->fetchAllAssociative() as $row) { + $data[array_shift($row)] = $row; + } + + return $data; + } + + /** + * @return list + * + * @throws Exception + */ + public function fetchFirstColumn(): array + { + try { + return $this->result->fetchFirstColumn(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + /** + * @return Traversable> + * + * @throws Exception + */ + public function iterateNumeric(): Traversable + { + while (($row = $this->fetchNumeric()) !== false) { + yield $row; + } + } + + /** + * @return Traversable> + * + * @throws Exception + */ + public function iterateAssociative(): Traversable + { + while (($row = $this->fetchAssociative()) !== false) { + yield $row; + } + } + + /** + * @return Traversable + * + * @throws Exception + */ + public function iterateKeyValue(): Traversable + { + $this->ensureHasKeyValue(); + + foreach ($this->iterateNumeric() as [$key, $value]) { + yield $key => $value; + } + } + + /** + * Returns an iterator over the result set with the keys mapped to the first column and the values being + * an associative array representing the rest of the columns and their values. + * + * @return Traversable> + * + * @throws Exception + */ + public function iterateAssociativeIndexed(): Traversable + { + foreach ($this->iterateAssociative() as $row) { + yield array_shift($row) => $row; + } + } + + /** + * @return Traversable + * + * @throws Exception + */ + public function iterateColumn(): Traversable + { + while (($value = $this->fetchOne()) !== false) { + yield $value; + } + } + + /** @throws Exception */ + public function rowCount(): int + { + try { + return $this->result->rowCount(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + /** @throws Exception */ + public function columnCount(): int + { + try { + return $this->result->columnCount(); + } catch (DriverException $e) { + throw $this->connection->convertException($e); + } + } + + public function free(): void + { + $this->result->free(); + } + + /** @throws Exception */ + private function ensureHasKeyValue(): void + { + $columnCount = $this->columnCount(); + + if ($columnCount < 2) { + throw NoKeyValue::fromColumnCount($columnCount); + } + } + + /** + * BC layer for a wide-spread use-case of old DBAL APIs + * + * @deprecated Use {@see fetchNumeric()}, {@see fetchAssociative()} or {@see fetchOne()} instead. + * + * @psalm-param FetchMode::* $mode + * + * @return mixed + * + * @throws Exception + */ + public function fetch(int $mode = FetchMode::ASSOCIATIVE) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4007', + '%s is deprecated, please use fetchNumeric(), fetchAssociative() or fetchOne() instead.', + __METHOD__, + ); + + if (func_num_args() > 1) { + throw new LogicException('Only invocations with one argument are still supported by this legacy API.'); + } + + if ($mode === FetchMode::ASSOCIATIVE) { + return $this->fetchAssociative(); + } + + if ($mode === FetchMode::NUMERIC) { + return $this->fetchNumeric(); + } + + if ($mode === FetchMode::COLUMN) { + return $this->fetchOne(); + } + + throw new LogicException('Only fetch modes declared on Doctrine\DBAL\FetchMode are supported by legacy API.'); + } + + /** + * BC layer for a wide-spread use-case of old DBAL APIs + * + * @deprecated Use {@see fetchAllNumeric()}, {@see fetchAllAssociative()} or {@see fetchFirstColumn()} instead. + * + * @psalm-param FetchMode::* $mode + * + * @return list + * + * @throws Exception + */ + public function fetchAll(int $mode = FetchMode::ASSOCIATIVE): array + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4007', + '%s is deprecated, please use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.', + __METHOD__, + ); + + if (func_num_args() > 1) { + throw new LogicException('Only invocations with one argument are still supported by this legacy API.'); + } + + if ($mode === FetchMode::ASSOCIATIVE) { + return $this->fetchAllAssociative(); + } + + if ($mode === FetchMode::NUMERIC) { + return $this->fetchAllNumeric(); + } + + if ($mode === FetchMode::COLUMN) { + return $this->fetchFirstColumn(); + } + + throw new LogicException('Only fetch modes declared on Doctrine\DBAL\FetchMode are supported by legacy API.'); + } +} diff --git a/vendor/doctrine/dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php new file mode 100644 index 0000000..2e392e6 --- /dev/null +++ b/vendor/doctrine/dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php @@ -0,0 +1,85 @@ +platform = $platform; + } + + /** + * @return list + * + * @throws Exception + */ + public function buildSQL(Schema $schema): array + { + return array_merge( + $this->buildNamespaceStatements($schema->getNamespaces()), + $this->buildSequenceStatements($schema->getSequences()), + $this->buildTableStatements($schema->getTables()), + ); + } + + /** + * @param list $namespaces + * + * @return list + * + * @throws Exception + */ + private function buildNamespaceStatements(array $namespaces): array + { + $statements = []; + + if ($this->platform->supportsSchemas()) { + foreach ($namespaces as $namespace) { + $statements[] = $this->platform->getCreateSchemaSQL($namespace); + } + } + + return $statements; + } + + /** + * @param list
$tables + * + * @return list + * + * @throws Exception + */ + private function buildTableStatements(array $tables): array + { + return $this->platform->getCreateTablesSQL($tables); + } + + /** + * @param list $sequences + * + * @return list + * + * @throws Exception + */ + private function buildSequenceStatements(array $sequences): array + { + $statements = []; + + foreach ($sequences as $sequence) { + $statements[] = $this->platform->getCreateSequenceSQL($sequence); + } + + return $statements; + } +} diff --git a/vendor/doctrine/dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php new file mode 100644 index 0000000..9dcb7a1 --- /dev/null +++ b/vendor/doctrine/dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php @@ -0,0 +1,95 @@ +platform = $platform; + $this->forUpdateSQL = $forUpdateSQL; + $this->skipLockedSQL = $skipLockedSQL; + } + + /** @throws Exception */ + public function buildSQL(SelectQuery $query): string + { + $parts = ['SELECT']; + + if ($query->isDistinct()) { + $parts[] = 'DISTINCT'; + } + + $parts[] = implode(', ', $query->getColumns()); + + $from = $query->getFrom(); + + if (count($from) > 0) { + $parts[] = 'FROM ' . implode(', ', $from); + } + + $where = $query->getWhere(); + + if ($where !== null) { + $parts[] = 'WHERE ' . $where; + } + + $groupBy = $query->getGroupBy(); + + if (count($groupBy) > 0) { + $parts[] = 'GROUP BY ' . implode(', ', $groupBy); + } + + $having = $query->getHaving(); + + if ($having !== null) { + $parts[] = 'HAVING ' . $having; + } + + $orderBy = $query->getOrderBy(); + + if (count($orderBy) > 0) { + $parts[] = 'ORDER BY ' . implode(', ', $orderBy); + } + + $sql = implode(' ', $parts); + $limit = $query->getLimit(); + + if ($limit->isDefined()) { + $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult()); + } + + $forUpdate = $query->getForUpdate(); + + if ($forUpdate !== null) { + if ($this->forUpdateSQL === null) { + throw Exception::notSupported('FOR UPDATE'); + } + + $sql .= ' ' . $this->forUpdateSQL; + + if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) { + if ($this->skipLockedSQL === null) { + throw Exception::notSupported('SKIP LOCKED'); + } + + $sql .= ' ' . $this->skipLockedSQL; + } + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php new file mode 100644 index 0000000..8de742a --- /dev/null +++ b/vendor/doctrine/dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php @@ -0,0 +1,62 @@ +platform = $platform; + } + + /** + * @return list + * + * @throws Exception + */ + public function buildSQL(Schema $schema): array + { + return array_merge( + $this->buildSequenceStatements($schema->getSequences()), + $this->buildTableStatements($schema->getTables()), + ); + } + + /** + * @param list
$tables + * + * @return list + */ + private function buildTableStatements(array $tables): array + { + return $this->platform->getDropTablesSQL($tables); + } + + /** + * @param list $sequences + * + * @return list + * + * @throws Exception + */ + private function buildSequenceStatements(array $sequences): array + { + $statements = []; + + foreach ($sequences as $sequence) { + $statements[] = $this->platform->getDropSequenceSQL($sequence); + } + + return $statements; + } +} diff --git a/vendor/doctrine/dbal/src/SQL/Builder/SelectSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/SelectSQLBuilder.php new file mode 100644 index 0000000..ddbf73c --- /dev/null +++ b/vendor/doctrine/dbal/src/SQL/Builder/SelectSQLBuilder.php @@ -0,0 +1,12 @@ +getMySQLStringLiteralPattern("'"), + $this->getMySQLStringLiteralPattern('"'), + ]; + } else { + $patterns = [ + $this->getAnsiSQLStringLiteralPattern("'"), + $this->getAnsiSQLStringLiteralPattern('"'), + ]; + } + + $patterns = array_merge($patterns, [ + self::BACKTICK_IDENTIFIER, + self::BRACKET_IDENTIFIER, + self::MULTICHAR, + self::ONE_LINE_COMMENT, + self::MULTI_LINE_COMMENT, + self::OTHER, + ]); + + $this->sqlPattern = sprintf('(%s)', implode('|', $patterns)); + } + + /** + * Parses the given SQL statement + * + * @throws Exception + */ + public function parse(string $sql, Visitor $visitor): void + { + /** @var array $patterns */ + $patterns = [ + self::NAMED_PARAMETER => static function (string $sql) use ($visitor): void { + $visitor->acceptNamedParameter($sql); + }, + self::POSITIONAL_PARAMETER => static function (string $sql) use ($visitor): void { + $visitor->acceptPositionalParameter($sql); + }, + $this->sqlPattern => static function (string $sql) use ($visitor): void { + $visitor->acceptOther($sql); + }, + self::SPECIAL => static function (string $sql) use ($visitor): void { + $visitor->acceptOther($sql); + }, + ]; + + $offset = 0; + + while (($handler = current($patterns)) !== false) { + if (preg_match('~\G' . key($patterns) . '~s', $sql, $matches, 0, $offset) === 1) { + $handler($matches[0]); + reset($patterns); + + $offset += strlen($matches[0]); + } elseif (preg_last_error() !== PREG_NO_ERROR) { + // @codeCoverageIgnoreStart + throw RegularExpressionError::new(); + // @codeCoverageIgnoreEnd + } else { + next($patterns); + } + } + + assert($offset === strlen($sql)); + } + + private function getMySQLStringLiteralPattern(string $delimiter): string + { + return $delimiter . '((\\\\.)|(?![' . $delimiter . '\\\\]).)*' . $delimiter; + } + + private function getAnsiSQLStringLiteralPattern(string $delimiter): string + { + return $delimiter . '[^' . $delimiter . ']*' . $delimiter; + } +} diff --git a/vendor/doctrine/dbal/src/SQL/Parser/Exception.php b/vendor/doctrine/dbal/src/SQL/Parser/Exception.php new file mode 100644 index 0000000..0c14b35 --- /dev/null +++ b/vendor/doctrine/dbal/src/SQL/Parser/Exception.php @@ -0,0 +1,11 @@ + Table($tableName)); if you want to rename the table, you have to make sure this does not get + * recreated during schema migration. + */ +abstract class AbstractAsset +{ + /** @var string */ + protected $_name = ''; + + /** + * Namespace of the asset. If none isset the default namespace is assumed. + * + * @var string|null + */ + protected $_namespace; + + /** @var bool */ + protected $_quoted = false; + + /** + * Sets the name of this asset. + * + * @param string $name + * + * @return void + */ + protected function _setName($name) + { + if ($this->isIdentifierQuoted($name)) { + $this->_quoted = true; + $name = $this->trimQuotes($name); + } + + if (strpos($name, '.') !== false) { + $parts = explode('.', $name); + $this->_namespace = $parts[0]; + $name = $parts[1]; + } + + $this->_name = $name; + } + + /** + * Is this asset in the default namespace? + * + * @param string $defaultNamespaceName + * + * @return bool + */ + public function isInDefaultNamespace($defaultNamespaceName) + { + return $this->_namespace === $defaultNamespaceName || $this->_namespace === null; + } + + /** + * Gets the namespace name of this asset. + * + * If NULL is returned this means the default namespace is used. + * + * @return string|null + */ + public function getNamespaceName() + { + return $this->_namespace; + } + + /** + * The shortest name is stripped of the default namespace. All other + * namespaced elements are returned as full-qualified names. + * + * @param string|null $defaultNamespaceName + * + * @return string + */ + public function getShortestName($defaultNamespaceName) + { + $shortestName = $this->getName(); + if ($this->_namespace === $defaultNamespaceName) { + $shortestName = $this->_name; + } + + return strtolower($shortestName); + } + + /** + * The normalized name is full-qualified and lower-cased. Lower-casing is + * actually wrong, but we have to do it to keep our sanity. If you are + * using database objects that only differentiate in the casing (FOO vs + * Foo) then you will NOT be able to use Doctrine Schema abstraction. + * + * Every non-namespaced element is prefixed with the default namespace + * name which is passed as argument to this method. + * + * @deprecated Use {@see getNamespaceName()} and {@see getName()} instead. + * + * @param string $defaultNamespaceName + * + * @return string + */ + public function getFullQualifiedName($defaultNamespaceName) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4814', + 'AbstractAsset::getFullQualifiedName() is deprecated.' + . ' Use AbstractAsset::getNamespaceName() and ::getName() instead.', + ); + + $name = $this->getName(); + if ($this->_namespace === null) { + $name = $defaultNamespaceName . '.' . $name; + } + + return strtolower($name); + } + + /** + * Checks if this asset's name is quoted. + * + * @return bool + */ + public function isQuoted() + { + return $this->_quoted; + } + + /** + * Checks if this identifier is quoted. + * + * @param string $identifier + * + * @return bool + */ + protected function isIdentifierQuoted($identifier) + { + return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '['); + } + + /** + * Trim quotes from the identifier. + * + * @param string $identifier + * + * @return string + */ + protected function trimQuotes($identifier) + { + return str_replace(['`', '"', '[', ']'], '', $identifier); + } + + /** + * Returns the name of this schema asset. + * + * @return string + */ + public function getName() + { + if ($this->_namespace !== null) { + return $this->_namespace . '.' . $this->_name; + } + + return $this->_name; + } + + /** + * Gets the quoted representation of this asset but only if it was defined with one. Otherwise + * return the plain unquoted value as inserted. + * + * @return string + */ + public function getQuotedName(AbstractPlatform $platform) + { + $keywords = $platform->getReservedKeywordsList(); + $parts = explode('.', $this->getName()); + foreach ($parts as $k => $v) { + $parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v; + } + + return implode('.', $parts); + } + + /** + * Generates an identifier from a list of column names obeying a certain string length. + * + * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, + * however building idents automatically for foreign keys, composite keys or such can easily create + * very long names. + * + * @param string[] $columnNames + * @param string $prefix + * @param int $maxSize + * + * @return string + */ + protected function _generateIdentifierName($columnNames, $prefix = '', $maxSize = 30) + { + $hash = implode('', array_map(static function ($column): string { + return dechex(crc32($column)); + }, $columnNames)); + + return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize)); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php b/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php new file mode 100644 index 0000000..2e38bb8 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php @@ -0,0 +1,1808 @@ +_conn = $connection; + $this->_platform = $platform; + } + + /** + * Returns the associated platform. + * + * @deprecated Use {@link Connection::getDatabasePlatform()} instead. + * + * @return T + */ + public function getDatabasePlatform() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5387', + 'AbstractSchemaManager::getDatabasePlatform() is deprecated.' + . ' Use Connection::getDatabasePlatform() instead.', + ); + + return $this->_platform; + } + + /** + * Tries any method on the schema manager. Normally a method throws an + * exception when your DBMS doesn't support it or if an error occurs. + * This method allows you to try and method on your SchemaManager + * instance and will return false if it does not work or is not supported. + * + * + * $result = $sm->tryMethod('dropView', 'view_name'); + * + * + * @deprecated + * + * @return mixed + */ + public function tryMethod() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::tryMethod() is deprecated.', + ); + + $args = func_get_args(); + $method = $args[0]; + unset($args[0]); + $args = array_values($args); + + $callback = [$this, $method]; + assert(is_callable($callback)); + + try { + return call_user_func_array($callback, $args); + } catch (Throwable $e) { + return false; + } + } + + /** + * Lists the available databases for this connection. + * + * @return string[] + * + * @throws Exception + */ + public function listDatabases() + { + $sql = $this->_platform->getListDatabasesSQL(); + + $databases = $this->_conn->fetchAllAssociative($sql); + + return $this->_getPortableDatabasesList($databases); + } + + /** + * Returns a list of all namespaces in the current database. + * + * @deprecated Use {@see listSchemaNames()} instead. + * + * @return string[] + * + * @throws Exception + */ + public function listNamespaceNames() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'AbstractSchemaManager::listNamespaceNames() is deprecated,' + . ' use AbstractSchemaManager::listSchemaNames() instead.', + ); + + $sql = $this->_platform->getListNamespacesSQL(); + + $namespaces = $this->_conn->fetchAllAssociative($sql); + + return $this->getPortableNamespacesList($namespaces); + } + + /** + * Returns a list of the names of all schemata in the current database. + * + * @return list + * + * @throws Exception + */ + public function listSchemaNames(): array + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Lists the available sequences for this connection. + * + * @param string|null $database + * + * @return Sequence[] + * + * @throws Exception + */ + public function listSequences($database = null) + { + if ($database === null) { + $database = $this->getDatabase(__METHOD__); + } else { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5284', + 'Passing $database to AbstractSchemaManager::listSequences() is deprecated.', + ); + } + + $sql = $this->_platform->getListSequencesSQL($database); + + $sequences = $this->_conn->fetchAllAssociative($sql); + + return $this->filterAssetNames($this->_getPortableSequencesList($sequences)); + } + + /** + * Lists the columns for a given table. + * + * In contrast to other libraries and to the old version of Doctrine, + * this column definition does try to contain the 'primary' column for + * the reason that it is not portable across different RDBMS. Use + * {@see listTableIndexes($tableName)} to retrieve the primary key + * of a table. Where a RDBMS specifies more details, these are held + * in the platformDetails array. + * + * @param string $table The name of the table. + * @param string|null $database + * + * @return Column[] + * + * @throws Exception + */ + public function listTableColumns($table, $database = null) + { + if ($database === null) { + $database = $this->getDatabase(__METHOD__); + } else { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5284', + 'Passing $database to AbstractSchemaManager::listTableColumns() is deprecated.', + ); + } + + $sql = $this->_platform->getListTableColumnsSQL($table, $database); + + $tableColumns = $this->_conn->fetchAllAssociative($sql); + + return $this->_getPortableTableColumnList($table, $database, $tableColumns); + } + + /** + * @param string $table + * @param string|null $database + * + * @return Column[] + * + * @throws Exception + */ + protected function doListTableColumns($table, $database = null): array + { + if ($database === null) { + $database = $this->getDatabase(__METHOD__); + } else { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5284', + 'Passing $database to AbstractSchemaManager::doListTableColumns() is deprecated.', + ); + } + + return $this->_getPortableTableColumnList( + $table, + $database, + $this->selectTableColumns($database, $this->normalizeName($table)) + ->fetchAllAssociative(), + ); + } + + /** + * Lists the indexes for a given table returning an array of Index instances. + * + * Keys of the portable indexes list are all lower-cased. + * + * @param string $table The name of the table. + * + * @return Index[] + * + * @throws Exception + */ + public function listTableIndexes($table) + { + $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); + + $tableIndexes = $this->_conn->fetchAllAssociative($sql); + + return $this->_getPortableTableIndexesList($tableIndexes, $table); + } + + /** + * @param string $table + * + * @return Index[] + * + * @throws Exception + */ + protected function doListTableIndexes($table): array + { + $database = $this->getDatabase(__METHOD__); + $table = $this->normalizeName($table); + + return $this->_getPortableTableIndexesList( + $this->selectIndexColumns( + $database, + $table, + )->fetchAllAssociative(), + $table, + ); + } + + /** + * Returns true if all the given tables exist. + * + * The usage of a string $tableNames is deprecated. Pass a one-element array instead. + * + * @param string|string[] $names + * + * @return bool + * + * @throws Exception + */ + public function tablesExist($names) + { + if (is_string($names)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/3580', + 'The usage of a string $tableNames in AbstractSchemaManager::tablesExist() is deprecated. ' . + 'Pass a one-element array instead.', + ); + } + + $names = array_map('strtolower', (array) $names); + + return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames()))); + } + + /** + * Returns a list of all tables in the current database. + * + * @return string[] + * + * @throws Exception + */ + public function listTableNames() + { + $sql = $this->_platform->getListTablesSQL(); + + $tables = $this->_conn->fetchAllAssociative($sql); + $tableNames = $this->_getPortableTablesList($tables); + + return $this->filterAssetNames($tableNames); + } + + /** + * @return list + * + * @throws Exception + */ + protected function doListTableNames(): array + { + $database = $this->getDatabase(__METHOD__); + + return $this->filterAssetNames( + $this->_getPortableTablesList( + $this->selectTableNames($database) + ->fetchAllAssociative(), + ), + ); + } + + /** + * Filters asset names if they are configured to return only a subset of all + * the found elements. + * + * @param mixed[] $assetNames + * + * @return mixed[] + */ + protected function filterAssetNames($assetNames) + { + $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter(); + if ($filter === null) { + return $assetNames; + } + + return array_values(array_filter($assetNames, $filter)); + } + + /** + * Lists the tables for this connection. + * + * @return list
+ * + * @throws Exception + */ + public function listTables() + { + $tableNames = $this->listTableNames(); + + $tables = []; + foreach ($tableNames as $tableName) { + $tables[] = $this->introspectTable($tableName); + } + + return $tables; + } + + /** + * @return list
+ * + * @throws Exception + */ + protected function doListTables(): array + { + $database = $this->getDatabase(__METHOD__); + + $tableColumnsByTable = $this->fetchTableColumnsByTable($database); + $indexColumnsByTable = $this->fetchIndexColumnsByTable($database); + $foreignKeyColumnsByTable = $this->fetchForeignKeyColumnsByTable($database); + $tableOptionsByTable = $this->fetchTableOptionsByTable($database); + + $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter(); + $tables = []; + + foreach ($tableColumnsByTable as $tableName => $tableColumns) { + if ($filter !== null && ! $filter($tableName)) { + continue; + } + + $tables[] = new Table( + $tableName, + $this->_getPortableTableColumnList($tableName, $database, $tableColumns), + $this->_getPortableTableIndexesList($indexColumnsByTable[$tableName] ?? [], $tableName), + [], + $this->_getPortableTableForeignKeysList($foreignKeyColumnsByTable[$tableName] ?? []), + $tableOptionsByTable[$tableName] ?? [], + ); + } + + return $tables; + } + + /** + * @deprecated Use {@see introspectTable()} instead. + * + * @param string $name + * + * @return Table + * + * @throws Exception + */ + public function listTableDetails($name) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5595', + '%s is deprecated. Use introspectTable() instead.', + __METHOD__, + ); + + $columns = $this->listTableColumns($name); + $foreignKeys = []; + + if ($this->_platform->supportsForeignKeyConstraints()) { + $foreignKeys = $this->listTableForeignKeys($name); + } + + $indexes = $this->listTableIndexes($name); + + return new Table($name, $columns, $indexes, [], $foreignKeys); + } + + /** + * @param string $name + * + * @throws Exception + */ + protected function doListTableDetails($name): Table + { + $database = $this->getDatabase(__METHOD__); + + $normalizedName = $this->normalizeName($name); + + $tableOptionsByTable = $this->fetchTableOptionsByTable($database, $normalizedName); + + if ($this->_platform->supportsForeignKeyConstraints()) { + $foreignKeys = $this->listTableForeignKeys($name); + } else { + $foreignKeys = []; + } + + return new Table( + $name, + $this->listTableColumns($name, $database), + $this->listTableIndexes($name), + [], + $foreignKeys, + $tableOptionsByTable[$normalizedName] ?? [], + ); + } + + /** + * An extension point for those platforms where case sensitivity of the object name depends on whether it's quoted. + * + * Such platforms should convert a possibly quoted name into a value of the corresponding case. + */ + protected function normalizeName(string $name): string + { + $identifier = new Identifier($name); + + return $identifier->getName(); + } + + /** + * Selects names of tables in the specified database. + * + * @throws Exception + * + * @abstract + */ + protected function selectTableNames(string $databaseName): Result + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Selects definitions of table columns in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + * + * @abstract + */ + protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Selects definitions of index columns in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Selects definitions of foreign key columns in the specified database. If the table name is specified, + * narrows down the selection to this table. + * + * @throws Exception + */ + protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Fetches definitions of table columns in the specified database and returns them grouped by table name. + * + * @return array>> + * + * @throws Exception + */ + protected function fetchTableColumnsByTable(string $databaseName): array + { + return $this->fetchAllAssociativeGrouped($this->selectTableColumns($databaseName)); + } + + /** + * Fetches definitions of index columns in the specified database and returns them grouped by table name. + * + * @return array>> + * + * @throws Exception + */ + protected function fetchIndexColumnsByTable(string $databaseName): array + { + return $this->fetchAllAssociativeGrouped($this->selectIndexColumns($databaseName)); + } + + /** + * Fetches definitions of foreign key columns in the specified database and returns them grouped by table name. + * + * @return array>> + * + * @throws Exception + */ + protected function fetchForeignKeyColumnsByTable(string $databaseName): array + { + if (! $this->_platform->supportsForeignKeyConstraints()) { + return []; + } + + return $this->fetchAllAssociativeGrouped( + $this->selectForeignKeyColumns($databaseName), + ); + } + + /** + * Fetches table options for the tables in the specified database and returns them grouped by table name. + * If the table name is specified, narrows down the selection to this table. + * + * @return array> + * + * @throws Exception + */ + protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array + { + throw Exception::notSupported(__METHOD__); + } + + /** + * Introspects the table with the given name. + * + * @throws Exception + */ + public function introspectTable(string $name): Table + { + $table = $this->listTableDetails($name); + + if ($table->getColumns() === []) { + throw SchemaException::tableDoesNotExist($name); + } + + return $table; + } + + /** + * Lists the views this connection has. + * + * @return View[] + * + * @throws Exception + */ + public function listViews() + { + $database = $this->_conn->getDatabase(); + $sql = $this->_platform->getListViewsSQL($database); + $views = $this->_conn->fetchAllAssociative($sql); + + return $this->_getPortableViewsList($views); + } + + /** + * Lists the foreign keys for the given table. + * + * @param string $table The name of the table. + * @param string|null $database + * + * @return ForeignKeyConstraint[] + * + * @throws Exception + */ + public function listTableForeignKeys($table, $database = null) + { + if ($database === null) { + $database = $this->getDatabase(__METHOD__); + } else { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5284', + 'Passing $database to AbstractSchemaManager::listTableForeignKeys() is deprecated.', + ); + } + + $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); + $tableForeignKeys = $this->_conn->fetchAllAssociative($sql); + + return $this->_getPortableTableForeignKeysList($tableForeignKeys); + } + + /** + * @param string $table + * @param string|null $database + * + * @return ForeignKeyConstraint[] + * + * @throws Exception + */ + protected function doListTableForeignKeys($table, $database = null): array + { + if ($database === null) { + $database = $this->getDatabase(__METHOD__); + } else { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5284', + 'Passing $database to AbstractSchemaManager::listTableForeignKeys() is deprecated.', + ); + } + + return $this->_getPortableTableForeignKeysList( + $this->selectForeignKeyColumns( + $database, + $this->normalizeName($table), + )->fetchAllAssociative(), + ); + } + + /* drop*() Methods */ + + /** + * Drops a database. + * + * NOTE: You can not drop the database this SchemaManager is currently connected to. + * + * @param string $database The name of the database to drop. + * + * @return void + * + * @throws Exception + */ + public function dropDatabase($database) + { + $this->_conn->executeStatement( + $this->_platform->getDropDatabaseSQL($database), + ); + } + + /** + * Drops a schema. + * + * @throws Exception + */ + public function dropSchema(string $schemaName): void + { + $this->_conn->executeStatement( + $this->_platform->getDropSchemaSQL($schemaName), + ); + } + + /** + * Drops the given table. + * + * @param string $name The name of the table to drop. + * + * @return void + * + * @throws Exception + */ + public function dropTable($name) + { + $this->_conn->executeStatement( + $this->_platform->getDropTableSQL($name), + ); + } + + /** + * Drops the index from the given table. + * + * @param Index|string $index The name of the index. + * @param Table|string $table The name of the table. + * + * @return void + * + * @throws Exception + */ + public function dropIndex($index, $table) + { + if ($index instanceof Index) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $index = $index->getQuotedName($this->_platform); + } + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as an Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this->_platform); + } + + $this->_conn->executeStatement( + $this->_platform->getDropIndexSQL($index, $table), + ); + } + + /** + * Drops the constraint from the given table. + * + * @deprecated Use {@see dropIndex()}, {@see dropForeignKey()} or {@see dropUniqueConstraint()} instead. + * + * @param Table|string $table The name of the table. + * + * @return void + * + * @throws Exception + */ + public function dropConstraint(Constraint $constraint, $table) + { + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this->_platform); + } + + $this->_conn->executeStatement($this->_platform->getDropConstraintSQL( + $constraint->getQuotedName($this->_platform), + $table, + )); + } + + /** + * Drops a foreign key from a table. + * + * @param ForeignKeyConstraint|string $foreignKey The name of the foreign key. + * @param Table|string $table The name of the table with the foreign key. + * + * @return void + * + * @throws Exception + */ + public function dropForeignKey($foreignKey, $table) + { + if ($foreignKey instanceof ForeignKeyConstraint) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' + . ' Pass it as a quoted name instead.', + __METHOD__, + ); + + $foreignKey = $foreignKey->getQuotedName($this->_platform); + } + + if ($table instanceof Table) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4798', + 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', + __METHOD__, + ); + + $table = $table->getQuotedName($this->_platform); + } + + $this->_conn->executeStatement( + $this->_platform->getDropForeignKeySQL($foreignKey, $table), + ); + } + + /** + * Drops a sequence with a given name. + * + * @param string $name The name of the sequence to drop. + * + * @return void + * + * @throws Exception + */ + public function dropSequence($name) + { + $this->_conn->executeStatement( + $this->_platform->getDropSequenceSQL($name), + ); + } + + /** + * Drops the unique constraint from the given table. + * + * @throws Exception + */ + public function dropUniqueConstraint(string $name, string $tableName): void + { + $this->_conn->executeStatement( + $this->_platform->getDropUniqueConstraintSQL($name, $tableName), + ); + } + + /** + * Drops a view. + * + * @param string $name The name of the view. + * + * @return void + * + * @throws Exception + */ + public function dropView($name) + { + $this->_conn->executeStatement( + $this->_platform->getDropViewSQL($name), + ); + } + + /* create*() Methods */ + + /** @throws Exception */ + public function createSchemaObjects(Schema $schema): void + { + $this->_execSql($schema->toSql($this->_platform)); + } + + /** + * Creates a new database. + * + * @param string $database The name of the database to create. + * + * @return void + * + * @throws Exception + */ + public function createDatabase($database) + { + $this->_conn->executeStatement( + $this->_platform->getCreateDatabaseSQL($database), + ); + } + + /** + * Creates a new table. + * + * @return void + * + * @throws Exception + */ + public function createTable(Table $table) + { + $createFlags = AbstractPlatform::CREATE_INDEXES | AbstractPlatform::CREATE_FOREIGNKEYS; + $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags)); + } + + /** + * Creates a new sequence. + * + * @param Sequence $sequence + * + * @return void + * + * @throws Exception + */ + public function createSequence($sequence) + { + $this->_conn->executeStatement( + $this->_platform->getCreateSequenceSQL($sequence), + ); + } + + /** + * Creates a constraint on a table. + * + * @deprecated Use {@see createIndex()}, {@see createForeignKey()} or {@see createUniqueConstraint()} instead. + * + * @param Table|string $table + * + * @return void + * + * @throws Exception + */ + public function createConstraint(Constraint $constraint, $table) + { + $this->_conn->executeStatement( + $this->_platform->getCreateConstraintSQL($constraint, $table), + ); + } + + /** + * Creates a new index on a table. + * + * @param Table|string $table The name of the table on which the index is to be created. + * + * @return void + * + * @throws Exception + */ + public function createIndex(Index $index, $table) + { + $this->_conn->executeStatement( + $this->_platform->getCreateIndexSQL($index, $table), + ); + } + + /** + * Creates a new foreign key. + * + * @param ForeignKeyConstraint $foreignKey The ForeignKey instance. + * @param Table|string $table The name of the table on which the foreign key is to be created. + * + * @return void + * + * @throws Exception + */ + public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $this->_conn->executeStatement( + $this->_platform->getCreateForeignKeySQL($foreignKey, $table), + ); + } + + /** + * Creates a unique constraint on a table. + * + * @throws Exception + */ + public function createUniqueConstraint(UniqueConstraint $uniqueConstraint, string $tableName): void + { + $this->_conn->executeStatement( + $this->_platform->getCreateUniqueConstraintSQL($uniqueConstraint, $tableName), + ); + } + + /** + * Creates a new view. + * + * @return void + * + * @throws Exception + */ + public function createView(View $view) + { + $this->_conn->executeStatement( + $this->_platform->getCreateViewSQL( + $view->getQuotedName($this->_platform), + $view->getSql(), + ), + ); + } + + /* dropAndCreate*() Methods */ + + /** @throws Exception */ + public function dropSchemaObjects(Schema $schema): void + { + $this->_execSql($schema->toDropSql($this->_platform)); + } + + /** + * Drops and creates a constraint. + * + * @deprecated Use {@see dropIndex()} and {@see createIndex()}, + * {@see dropForeignKey()} and {@see createForeignKey()} + * or {@see dropUniqueConstraint()} and {@see createUniqueConstraint()} instead. + * + * @see dropConstraint() + * @see createConstraint() + * + * @param Table|string $table + * + * @return void + * + * @throws Exception + */ + public function dropAndCreateConstraint(Constraint $constraint, $table) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::dropAndCreateConstraint() is deprecated.' + . ' Use AbstractSchemaManager::dropIndex() and AbstractSchemaManager::createIndex(),' + . ' AbstractSchemaManager::dropForeignKey() and AbstractSchemaManager::createForeignKey()' + . ' or AbstractSchemaManager::dropUniqueConstraint()' + . ' and AbstractSchemaManager::createUniqueConstraint() instead.', + ); + + $this->tryMethod('dropConstraint', $constraint, $table); + $this->createConstraint($constraint, $table); + } + + /** + * Drops and creates a new index on a table. + * + * @deprecated Use {@see dropIndex()} and {@see createIndex()} instead. + * + * @param Table|string $table The name of the table on which the index is to be created. + * + * @return void + * + * @throws Exception + */ + public function dropAndCreateIndex(Index $index, $table) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::dropAndCreateIndex() is deprecated.' + . ' Use AbstractSchemaManager::dropIndex() and AbstractSchemaManager::createIndex() instead.', + ); + + $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table); + $this->createIndex($index, $table); + } + + /** + * Drops and creates a new foreign key. + * + * @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead. + * + * @param ForeignKeyConstraint $foreignKey An associative array that defines properties + * of the foreign key to be created. + * @param Table|string $table The name of the table on which the foreign key is to be created. + * + * @return void + * + * @throws Exception + */ + public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::dropAndCreateForeignKey() is deprecated.' + . ' Use AbstractSchemaManager::dropForeignKey() and AbstractSchemaManager::createForeignKey() instead.', + ); + + $this->tryMethod('dropForeignKey', $foreignKey, $table); + $this->createForeignKey($foreignKey, $table); + } + + /** + * Drops and create a new sequence. + * + * @deprecated Use {@see dropSequence()} and {@see createSequence()} instead. + * + * @return void + * + * @throws Exception + */ + public function dropAndCreateSequence(Sequence $sequence) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::dropAndCreateSequence() is deprecated.' + . ' Use AbstractSchemaManager::dropSequence() and AbstractSchemaManager::createSequence() instead.', + ); + + $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform)); + $this->createSequence($sequence); + } + + /** + * Drops and creates a new table. + * + * @deprecated Use {@see dropTable()} and {@see createTable()} instead. + * + * @return void + * + * @throws Exception + */ + public function dropAndCreateTable(Table $table) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::dropAndCreateTable() is deprecated.' + . ' Use AbstractSchemaManager::dropTable() and AbstractSchemaManager::createTable() instead.', + ); + + $this->tryMethod('dropTable', $table->getQuotedName($this->_platform)); + $this->createTable($table); + } + + /** + * Drops and creates a new database. + * + * @deprecated Use {@see dropDatabase()} and {@see createDatabase()} instead. + * + * @param string $database The name of the database to create. + * + * @return void + * + * @throws Exception + */ + public function dropAndCreateDatabase($database) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::dropAndCreateDatabase() is deprecated.' + . ' Use AbstractSchemaManager::dropDatabase() and AbstractSchemaManager::createDatabase() instead.', + ); + + $this->tryMethod('dropDatabase', $database); + $this->createDatabase($database); + } + + /** + * Drops and creates a new view. + * + * @deprecated Use {@see dropView()} and {@see createView()} instead. + * + * @return void + * + * @throws Exception + */ + public function dropAndCreateView(View $view) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'AbstractSchemaManager::dropAndCreateView() is deprecated.' + . ' Use AbstractSchemaManager::dropView() and AbstractSchemaManager::createView() instead.', + ); + + $this->tryMethod('dropView', $view->getQuotedName($this->_platform)); + $this->createView($view); + } + + /** + * Alters an existing schema. + * + * @throws Exception + */ + public function alterSchema(SchemaDiff $schemaDiff): void + { + $this->_execSql($this->_platform->getAlterSchemaSQL($schemaDiff)); + } + + /** + * Migrates an existing schema to a new schema. + * + * @throws Exception + */ + public function migrateSchema(Schema $toSchema): void + { + $schemaDiff = $this->createComparator() + ->compareSchemas($this->introspectSchema(), $toSchema); + + $this->alterSchema($schemaDiff); + } + + /* alterTable() Methods */ + + /** + * Alters an existing tables schema. + * + * @return void + * + * @throws Exception + */ + public function alterTable(TableDiff $tableDiff) + { + $this->_execSql($this->_platform->getAlterTableSQL($tableDiff)); + } + + /** + * Renames a given table to another name. + * + * @param string $name The current name of the table. + * @param string $newName The new name of the table. + * + * @return void + * + * @throws Exception + */ + public function renameTable($name, $newName) + { + $this->_execSql($this->_platform->getRenameTableSQL($name, $newName)); + } + + /** + * Methods for filtering return values of list*() methods to convert + * the native DBMS data definition to a portable Doctrine definition + */ + + /** + * @param mixed[] $databases + * + * @return string[] + */ + protected function _getPortableDatabasesList($databases) + { + $list = []; + foreach ($databases as $value) { + $list[] = $this->_getPortableDatabaseDefinition($value); + } + + return $list; + } + + /** + * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition. + * + * @deprecated Use {@see listSchemaNames()} instead. + * + * @param array> $namespaces The list of namespace names + * in the native DBMS data definition. + * + * @return string[] + */ + protected function getPortableNamespacesList(array $namespaces) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'AbstractSchemaManager::getPortableNamespacesList() is deprecated,' + . ' use AbstractSchemaManager::listSchemaNames() instead.', + ); + + $namespacesList = []; + + foreach ($namespaces as $namespace) { + $namespacesList[] = $this->getPortableNamespaceDefinition($namespace); + } + + return $namespacesList; + } + + /** + * @param mixed $database + * + * @return mixed + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database; + } + + /** + * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition. + * + * @deprecated Use {@see listSchemaNames()} instead. + * + * @param array $namespace The native DBMS namespace definition. + * + * @return mixed + */ + protected function getPortableNamespaceDefinition(array $namespace) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'AbstractSchemaManager::getPortableNamespaceDefinition() is deprecated,' + . ' use AbstractSchemaManager::listSchemaNames() instead.', + ); + + return $namespace; + } + + /** + * @param mixed[][] $sequences + * + * @return Sequence[] + * + * @throws Exception + */ + protected function _getPortableSequencesList($sequences) + { + $list = []; + + foreach ($sequences as $value) { + $list[] = $this->_getPortableSequenceDefinition($value); + } + + return $list; + } + + /** + * @param mixed[] $sequence + * + * @return Sequence + * + * @throws Exception + */ + protected function _getPortableSequenceDefinition($sequence) + { + throw Exception::notSupported('Sequences'); + } + + /** + * Independent of the database the keys of the column list result are lowercased. + * + * The name of the created column instance however is kept in its case. + * + * @param string $table The name of the table. + * @param string $database + * @param mixed[][] $tableColumns + * + * @return Column[] + * + * @throws Exception + */ + protected function _getPortableTableColumnList($table, $database, $tableColumns) + { + $eventManager = $this->_platform->getEventManager(); + + $list = []; + foreach ($tableColumns as $tableColumn) { + $column = null; + $defaultPrevented = false; + + if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated. Use a custom schema manager instead.', + Events::onSchemaColumnDefinition, + ); + + $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn); + $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs); + + $defaultPrevented = $eventArgs->isDefaultPrevented(); + $column = $eventArgs->getColumn(); + } + + if (! $defaultPrevented) { + $column = $this->_getPortableTableColumnDefinition($tableColumn); + } + + if ($column === null) { + continue; + } + + $name = strtolower($column->getQuotedName($this->_platform)); + $list[$name] = $column; + } + + return $list; + } + + /** + * Gets Table Column Definition. + * + * @param mixed[] $tableColumn + * + * @return Column + * + * @throws Exception + */ + abstract protected function _getPortableTableColumnDefinition($tableColumn); + + /** + * Aggregates and groups the index results according to the required data result. + * + * @param mixed[][] $tableIndexes + * @param string|null $tableName + * + * @return Index[] + * + * @throws Exception + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + $result = []; + foreach ($tableIndexes as $tableIndex) { + $indexName = $keyName = $tableIndex['key_name']; + if ($tableIndex['primary']) { + $keyName = 'primary'; + } + + $keyName = strtolower($keyName); + + if (! isset($result[$keyName])) { + $options = [ + 'lengths' => [], + ]; + + if (isset($tableIndex['where'])) { + $options['where'] = $tableIndex['where']; + } + + $result[$keyName] = [ + 'name' => $indexName, + 'columns' => [], + 'unique' => ! $tableIndex['non_unique'], + 'primary' => $tableIndex['primary'], + 'flags' => $tableIndex['flags'] ?? [], + 'options' => $options, + ]; + } + + $result[$keyName]['columns'][] = $tableIndex['column_name']; + $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null; + } + + $eventManager = $this->_platform->getEventManager(); + + $indexes = []; + foreach ($result as $indexKey => $data) { + $index = null; + $defaultPrevented = false; + + if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/5784', + 'Subscribing to %s events is deprecated. Use a custom schema manager instead.', + Events::onSchemaColumnDefinition, + ); + + $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn); + $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs); + + $defaultPrevented = $eventArgs->isDefaultPrevented(); + $index = $eventArgs->getIndex(); + } + + if (! $defaultPrevented) { + $index = new Index( + $data['name'], + $data['columns'], + $data['unique'], + $data['primary'], + $data['flags'], + $data['options'], + ); + } + + if ($index === null) { + continue; + } + + $indexes[$indexKey] = $index; + } + + return $indexes; + } + + /** + * @param mixed[][] $tables + * + * @return string[] + */ + protected function _getPortableTablesList($tables) + { + $list = []; + foreach ($tables as $value) { + $list[] = $this->_getPortableTableDefinition($value); + } + + return $list; + } + + /** + * @param mixed $table + * + * @return string + */ + protected function _getPortableTableDefinition($table) + { + return $table; + } + + /** + * @param mixed[][] $views + * + * @return View[] + */ + protected function _getPortableViewsList($views) + { + $list = []; + foreach ($views as $value) { + $view = $this->_getPortableViewDefinition($value); + + if ($view === false) { + continue; + } + + $viewName = strtolower($view->getQuotedName($this->_platform)); + $list[$viewName] = $view; + } + + return $list; + } + + /** + * @param mixed[] $view + * + * @return View|false + */ + protected function _getPortableViewDefinition($view) + { + return false; + } + + /** + * @param mixed[][] $tableForeignKeys + * + * @return ForeignKeyConstraint[] + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = []; + + foreach ($tableForeignKeys as $value) { + $list[] = $this->_getPortableTableForeignKeyDefinition($value); + } + + return $list; + } + + /** + * @param mixed $tableForeignKey + * + * @return ForeignKeyConstraint + * + * @abstract + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return $tableForeignKey; + } + + /** + * @internal + * + * @param string[]|string $sql + * + * @return void + * + * @throws Exception + */ + protected function _execSql($sql) + { + foreach ((array) $sql as $query) { + $this->_conn->executeStatement($query); + } + } + + /** + * Creates a schema instance for the current database. + * + * @deprecated Use {@link introspectSchema()} instead. + * + * @return Schema + * + * @throws Exception + */ + public function createSchema() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5613', + '%s is deprecated. Use introspectSchema() instead.', + __METHOD__, + ); + + $schemaNames = []; + + if ($this->_platform->supportsSchemas()) { + $schemaNames = $this->listNamespaceNames(); + } + + $sequences = []; + + if ($this->_platform->supportsSequences()) { + $sequences = $this->listSequences(); + } + + $tables = $this->listTables(); + + return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames); + } + + /** + * Returns a {@see Schema} instance representing the current database schema. + * + * @throws Exception + */ + public function introspectSchema(): Schema + { + return $this->createSchema(); + } + + /** + * Creates the configuration for this schema. + * + * @return SchemaConfig + * + * @throws Exception + */ + public function createSchemaConfig() + { + $schemaConfig = new SchemaConfig(); + $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength()); + + $searchPaths = $this->getSchemaSearchPaths(); + if (isset($searchPaths[0])) { + $schemaConfig->setName($searchPaths[0]); + } + + $params = $this->_conn->getParams(); + if (! isset($params['defaultTableOptions'])) { + $params['defaultTableOptions'] = []; + } + + if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) { + $params['defaultTableOptions']['charset'] = $params['charset']; + } + + $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']); + + return $schemaConfig; + } + + /** + * The search path for namespaces in the currently connected database. + * + * The first entry is usually the default namespace in the Schema. All + * further namespaces contain tables/sequences which can also be addressed + * with a short, not full-qualified name. + * + * For databases that don't support subschema/namespaces this method + * returns the name of the currently connected database. + * + * @deprecated + * + * @return string[] + * + * @throws Exception + */ + public function getSchemaSearchPaths() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4821', + 'AbstractSchemaManager::getSchemaSearchPaths() is deprecated.', + ); + + $database = $this->_conn->getDatabase(); + + if ($database !== null) { + return [$database]; + } + + return []; + } + + /** + * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns + * the type given as default. + * + * @internal This method should be only used from within the AbstractSchemaManager class hierarchy. + * + * @param string|null $comment + * @param string $currentType + * + * @return string + */ + public function extractDoctrineTypeFromComment($comment, $currentType) + { + if ($this->_conn->getConfiguration()->getDisableTypeComments()) { + return $currentType; + } + + if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match) === 1) { + return $match[1]; + } + + return $currentType; + } + + /** + * @internal This method should be only used from within the AbstractSchemaManager class hierarchy. + * + * @param string|null $comment + * @param string|null $type + * + * @return string|null + */ + public function removeDoctrineTypeFromComment($comment, $type) + { + if ($this->_conn->getConfiguration()->getDisableTypeComments()) { + return $comment; + } + + if ($comment === null) { + return null; + } + + return str_replace('(DC2Type:' . $type . ')', '', $comment); + } + + /** @throws Exception */ + private function getDatabase(string $methodName): string + { + $database = $this->_conn->getDatabase(); + + if ($database === null) { + throw DatabaseRequired::new($methodName); + } + + return $database; + } + + public function createComparator(): Comparator + { + return new Comparator($this->_platform); + } + + /** + * @return array>> + * + * @throws Exception + */ + private function fetchAllAssociativeGrouped(Result $result): array + { + $data = []; + + foreach ($result->fetchAllAssociative() as $row) { + $tableName = $this->_getPortableTableDefinition($row); + $data[$tableName][] = $row; + } + + return $data; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Column.php b/vendor/doctrine/dbal/src/Schema/Column.php new file mode 100644 index 0000000..e158017 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Column.php @@ -0,0 +1,466 @@ +_setName($name); + $this->setType($type); + $this->setOptions($options); + } + + /** + * @param mixed[] $options + * + * @return Column + * + * @throws SchemaException + */ + public function setOptions(array $options) + { + foreach ($options as $name => $value) { + $method = 'set' . $name; + + if (! method_exists($this, $method)) { + throw UnknownColumnOption::new($name); + } + + $this->$method($value); + } + + return $this; + } + + /** @return Column */ + public function setType(Type $type) + { + $this->_type = $type; + + return $this; + } + + /** + * @param int|null $length + * + * @return Column + */ + public function setLength($length) + { + if ($length !== null) { + $this->_length = (int) $length; + } else { + $this->_length = null; + } + + return $this; + } + + /** + * @param int $precision + * + * @return Column + */ + public function setPrecision($precision) + { + if (! is_numeric($precision)) { + $precision = 10; // defaults to 10 when no valid precision is given. + } + + $this->_precision = (int) $precision; + + return $this; + } + + /** + * @param int $scale + * + * @return Column + */ + public function setScale($scale) + { + if (! is_numeric($scale)) { + $scale = 0; + } + + $this->_scale = (int) $scale; + + return $this; + } + + /** + * @param bool $unsigned + * + * @return Column + */ + public function setUnsigned($unsigned) + { + $this->_unsigned = (bool) $unsigned; + + return $this; + } + + /** + * @param bool $fixed + * + * @return Column + */ + public function setFixed($fixed) + { + $this->_fixed = (bool) $fixed; + + return $this; + } + + /** + * @param bool $notnull + * + * @return Column + */ + public function setNotnull($notnull) + { + $this->_notnull = (bool) $notnull; + + return $this; + } + + /** + * @param mixed $default + * + * @return Column + */ + public function setDefault($default) + { + $this->_default = $default; + + return $this; + } + + /** + * @param mixed[] $platformOptions + * + * @return Column + */ + public function setPlatformOptions(array $platformOptions) + { + $this->_platformOptions = $platformOptions; + + return $this; + } + + /** + * @param string $name + * @param mixed $value + * + * @return Column + */ + public function setPlatformOption($name, $value) + { + $this->_platformOptions[$name] = $value; + + return $this; + } + + /** + * @param string|null $value + * + * @return Column + */ + public function setColumnDefinition($value) + { + $this->_columnDefinition = $value; + + return $this; + } + + /** @return Type */ + public function getType() + { + return $this->_type; + } + + /** @return int|null */ + public function getLength() + { + return $this->_length; + } + + /** @return int */ + public function getPrecision() + { + return $this->_precision; + } + + /** @return int */ + public function getScale() + { + return $this->_scale; + } + + /** @return bool */ + public function getUnsigned() + { + return $this->_unsigned; + } + + /** @return bool */ + public function getFixed() + { + return $this->_fixed; + } + + /** @return bool */ + public function getNotnull() + { + return $this->_notnull; + } + + /** @return mixed */ + public function getDefault() + { + return $this->_default; + } + + /** @return mixed[] */ + public function getPlatformOptions() + { + return $this->_platformOptions; + } + + /** + * @param string $name + * + * @return bool + */ + public function hasPlatformOption($name) + { + return isset($this->_platformOptions[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getPlatformOption($name) + { + return $this->_platformOptions[$name]; + } + + /** @return string|null */ + public function getColumnDefinition() + { + return $this->_columnDefinition; + } + + /** @return bool */ + public function getAutoincrement() + { + return $this->_autoincrement; + } + + /** + * @param bool $flag + * + * @return Column + */ + public function setAutoincrement($flag) + { + $this->_autoincrement = $flag; + + return $this; + } + + /** + * @param string|null $comment + * + * @return Column + */ + public function setComment($comment) + { + $this->_comment = $comment; + + return $this; + } + + /** @return string|null */ + public function getComment() + { + return $this->_comment; + } + + /** + * @deprecated Use {@link setPlatformOption()} instead + * + * @param string $name + * @param mixed $value + * + * @return Column + */ + public function setCustomSchemaOption($name, $value) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5476', + 'Column::setCustomSchemaOption() is deprecated. Use setPlatformOption() instead.', + ); + + $this->_customSchemaOptions[$name] = $value; + + return $this; + } + + /** + * @deprecated Use {@link hasPlatformOption()} instead + * + * @param string $name + * + * @return bool + */ + public function hasCustomSchemaOption($name) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5476', + 'Column::hasCustomSchemaOption() is deprecated. Use hasPlatformOption() instead.', + ); + + return isset($this->_customSchemaOptions[$name]); + } + + /** + * @deprecated Use {@link getPlatformOption()} instead + * + * @param string $name + * + * @return mixed + */ + public function getCustomSchemaOption($name) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5476', + 'Column::getCustomSchemaOption() is deprecated. Use getPlatformOption() instead.', + ); + + return $this->_customSchemaOptions[$name]; + } + + /** + * @deprecated Use {@link setPlatformOptions()} instead + * + * @param mixed[] $customSchemaOptions + * + * @return Column + */ + public function setCustomSchemaOptions(array $customSchemaOptions) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5476', + 'Column::setCustomSchemaOptions() is deprecated. Use setPlatformOptions() instead.', + ); + + $this->_customSchemaOptions = $customSchemaOptions; + + return $this; + } + + /** + * @deprecated Use {@link getPlatformOptions()} instead + * + * @return mixed[] + */ + public function getCustomSchemaOptions() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5476', + 'Column::getCustomSchemaOptions() is deprecated. Use getPlatformOptions() instead.', + ); + + return $this->_customSchemaOptions; + } + + /** @return mixed[] */ + public function toArray() + { + return array_merge([ + 'name' => $this->_name, + 'type' => $this->_type, + 'default' => $this->_default, + 'notnull' => $this->_notnull, + 'length' => $this->_length, + 'precision' => $this->_precision, + 'scale' => $this->_scale, + 'fixed' => $this->_fixed, + 'unsigned' => $this->_unsigned, + 'autoincrement' => $this->_autoincrement, + 'columnDefinition' => $this->_columnDefinition, + 'comment' => $this->_comment, + ], $this->_platformOptions, $this->_customSchemaOptions); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/ColumnDiff.php b/vendor/doctrine/dbal/src/Schema/ColumnDiff.php new file mode 100644 index 0000000..bd1b0ee --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/ColumnDiff.php @@ -0,0 +1,169 @@ +oldColumnName = $oldColumnName; + $this->column = $column; + $this->changedProperties = $changedProperties; + $this->fromColumn = $fromColumn; + } + + public function getOldColumn(): ?Column + { + return $this->fromColumn; + } + + public function getNewColumn(): Column + { + return $this->column; + } + + public function hasTypeChanged(): bool + { + return $this->hasChanged('type'); + } + + public function hasLengthChanged(): bool + { + return $this->hasChanged('length'); + } + + public function hasPrecisionChanged(): bool + { + return $this->hasChanged('precision'); + } + + public function hasScaleChanged(): bool + { + return $this->hasChanged('scale'); + } + + public function hasUnsignedChanged(): bool + { + return $this->hasChanged('unsigned'); + } + + public function hasFixedChanged(): bool + { + return $this->hasChanged('fixed'); + } + + public function hasNotNullChanged(): bool + { + return $this->hasChanged('notnull'); + } + + public function hasDefaultChanged(): bool + { + return $this->hasChanged('default'); + } + + public function hasAutoIncrementChanged(): bool + { + return $this->hasChanged('autoincrement'); + } + + public function hasCommentChanged(): bool + { + return $this->hasChanged('comment'); + } + + /** + * @deprecated Use {@see hasTypeChanged()}, {@see hasLengthChanged()}, {@see hasPrecisionChanged()}, + * {@see hasScaleChanged()}, {@see hasUnsignedChanged()}, {@see hasFixedChanged()}, {@see hasNotNullChanged()}, + * {@see hasDefaultChanged()}, {@see hasAutoIncrementChanged()} or {@see hasCommentChanged()} instead. + * + * @param string $propertyName + * + * @return bool + */ + public function hasChanged($propertyName) + { + return in_array($propertyName, $this->changedProperties, true); + } + + /** + * @deprecated Use {@see $fromColumn} instead. + * + * @return Identifier + */ + public function getOldColumnName() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5622', + '%s is deprecated. Use $fromColumn instead.', + __METHOD__, + ); + + if ($this->fromColumn !== null) { + $name = $this->fromColumn->getName(); + $quote = $this->fromColumn->isQuoted(); + } else { + $name = $this->oldColumnName; + $quote = false; + } + + return new Identifier($name, $quote); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Comparator.php b/vendor/doctrine/dbal/src/Schema/Comparator.php new file mode 100644 index 0000000..28e7f2f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Comparator.php @@ -0,0 +1,716 @@ +platform = $platform; + } + + /** @param list $args */ + public function __call(string $method, array $args): SchemaDiff + { + if ($method !== 'compareSchemas') { + throw new BadMethodCallException(sprintf('Unknown method "%s"', $method)); + } + + return $this->doCompareSchemas(...$args); + } + + /** @param list $args */ + public static function __callStatic(string $method, array $args): SchemaDiff + { + if ($method !== 'compareSchemas') { + throw new BadMethodCallException(sprintf('Unknown method "%s"', $method)); + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4707', + 'Calling %s::%s() statically is deprecated.', + self::class, + $method, + ); + + $comparator = new self(); + + return $comparator->doCompareSchemas(...$args); + } + + /** + * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. + * + * This method should be called non-statically since it will be declared as non-static in the next major release. + * + * @return SchemaDiff + * + * @throws SchemaException + */ + private function doCompareSchemas( + Schema $fromSchema, + Schema $toSchema + ) { + $createdSchemas = []; + $droppedSchemas = []; + $createdTables = []; + $alteredTables = []; + $droppedTables = []; + $createdSequences = []; + $alteredSequences = []; + $droppedSequences = []; + + $orphanedForeignKeys = []; + + $foreignKeysToTable = []; + + foreach ($toSchema->getNamespaces() as $namespace) { + if ($fromSchema->hasNamespace($namespace)) { + continue; + } + + $createdSchemas[$namespace] = $namespace; + } + + foreach ($fromSchema->getNamespaces() as $namespace) { + if ($toSchema->hasNamespace($namespace)) { + continue; + } + + $droppedSchemas[$namespace] = $namespace; + } + + foreach ($toSchema->getTables() as $table) { + $tableName = $table->getShortestName($toSchema->getName()); + if (! $fromSchema->hasTable($tableName)) { + $createdTables[$tableName] = $toSchema->getTable($tableName); + } else { + $tableDifferences = $this->diffTable( + $fromSchema->getTable($tableName), + $toSchema->getTable($tableName), + ); + + if ($tableDifferences !== false) { + $alteredTables[$tableName] = $tableDifferences; + } + } + } + + /* Check if there are tables removed */ + foreach ($fromSchema->getTables() as $table) { + $tableName = $table->getShortestName($fromSchema->getName()); + + $table = $fromSchema->getTable($tableName); + if (! $toSchema->hasTable($tableName)) { + $droppedTables[$tableName] = $table; + } + + // also remember all foreign keys that point to a specific table + foreach ($table->getForeignKeys() as $foreignKey) { + $foreignTable = strtolower($foreignKey->getForeignTableName()); + if (! isset($foreignKeysToTable[$foreignTable])) { + $foreignKeysToTable[$foreignTable] = []; + } + + $foreignKeysToTable[$foreignTable][] = $foreignKey; + } + } + + foreach ($droppedTables as $tableName => $table) { + if (! isset($foreignKeysToTable[$tableName])) { + continue; + } + + foreach ($foreignKeysToTable[$tableName] as $foreignKey) { + if (isset($droppedTables[strtolower($foreignKey->getLocalTableName())])) { + continue; + } + + $orphanedForeignKeys[] = $foreignKey; + } + + // deleting duplicated foreign keys present on both on the orphanedForeignKey + // and the removedForeignKeys from changedTables + foreach ($foreignKeysToTable[$tableName] as $foreignKey) { + // strtolower the table name to make if compatible with getShortestName + $localTableName = strtolower($foreignKey->getLocalTableName()); + if (! isset($alteredTables[$localTableName])) { + continue; + } + + foreach ($alteredTables[$localTableName]->getDroppedForeignKeys() as $droppedForeignKey) { + assert($droppedForeignKey instanceof ForeignKeyConstraint); + + // We check if the key is from the removed table if not we skip. + if ($tableName !== strtolower($droppedForeignKey->getForeignTableName())) { + continue; + } + + $alteredTables[$localTableName]->unsetDroppedForeignKey($droppedForeignKey); + } + } + } + + foreach ($toSchema->getSequences() as $sequence) { + $sequenceName = $sequence->getShortestName($toSchema->getName()); + if (! $fromSchema->hasSequence($sequenceName)) { + if (! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) { + $createdSequences[] = $sequence; + } + } else { + if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { + $alteredSequences[] = $toSchema->getSequence($sequenceName); + } + } + } + + foreach ($fromSchema->getSequences() as $sequence) { + if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) { + continue; + } + + $sequenceName = $sequence->getShortestName($fromSchema->getName()); + + if ($toSchema->hasSequence($sequenceName)) { + continue; + } + + $droppedSequences[] = $sequence; + } + + $diff = new SchemaDiff( + $createdTables, + $alteredTables, + $droppedTables, + $fromSchema, + $createdSchemas, + $droppedSchemas, + $createdSequences, + $alteredSequences, + $droppedSequences, + ); + + $diff->orphanedForeignKeys = $orphanedForeignKeys; + + return $diff; + } + + /** + * @deprecated Use non-static call to {@see compareSchemas()} instead. + * + * @return SchemaDiff + * + * @throws SchemaException + */ + public function compare(Schema $fromSchema, Schema $toSchema) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4707', + 'Method compare() is deprecated. Use a non-static call to compareSchemas() instead.', + ); + + return $this->compareSchemas($fromSchema, $toSchema); + } + + /** + * @param Schema $schema + * @param Sequence $sequence + */ + private function isAutoIncrementSequenceInSchema($schema, $sequence): bool + { + foreach ($schema->getTables() as $table) { + if ($sequence->isAutoIncrementsFor($table)) { + return true; + } + } + + return false; + } + + /** @return bool */ + public function diffSequence(Sequence $sequence1, Sequence $sequence2) + { + if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) { + return true; + } + + return $sequence1->getInitialValue() !== $sequence2->getInitialValue(); + } + + /** + * Returns the difference between the tables $fromTable and $toTable. + * + * If there are no differences this method returns the boolean false. + * + * @deprecated Use {@see compareTables()} and, optionally, {@see TableDiff::isEmpty()} instead. + * + * @return TableDiff|false + * + * @throws Exception + */ + public function diffTable(Table $fromTable, Table $toTable) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5770', + '%s is deprecated. Use compareTables() instead.', + __METHOD__, + ); + + $diff = $this->compareTables($fromTable, $toTable); + + if ($diff->isEmpty()) { + return false; + } + + return $diff; + } + + /** + * Compares the tables and returns the difference between them. + * + * @throws Exception + */ + public function compareTables(Table $fromTable, Table $toTable): TableDiff + { + $addedColumns = []; + $modifiedColumns = []; + $droppedColumns = []; + $addedIndexes = []; + $modifiedIndexes = []; + $droppedIndexes = []; + $addedForeignKeys = []; + $modifiedForeignKeys = []; + $droppedForeignKeys = []; + + $fromTableColumns = $fromTable->getColumns(); + $toTableColumns = $toTable->getColumns(); + + /* See if all the columns in "from" table exist in "to" table */ + foreach ($toTableColumns as $columnName => $column) { + if ($fromTable->hasColumn($columnName)) { + continue; + } + + $addedColumns[$columnName] = $column; + } + + /* See if there are any removed columns in "to" table */ + foreach ($fromTableColumns as $columnName => $column) { + // See if column is removed in "to" table. + if (! $toTable->hasColumn($columnName)) { + $droppedColumns[$columnName] = $column; + + continue; + } + + $toColumn = $toTable->getColumn($columnName); + + // See if column has changed properties in "to" table. + $changedProperties = $this->diffColumn($column, $toColumn); + + if ($this->platform !== null) { + if ($this->columnsEqual($column, $toColumn)) { + continue; + } + } elseif (count($changedProperties) === 0) { + continue; + } + + $modifiedColumns[$column->getName()] = new ColumnDiff( + $column->getName(), + $toColumn, + $changedProperties, + $column, + ); + } + + $renamedColumns = $this->detectRenamedColumns($addedColumns, $droppedColumns); + + $fromTableIndexes = $fromTable->getIndexes(); + $toTableIndexes = $toTable->getIndexes(); + + /* See if all the indexes in "from" table exist in "to" table */ + foreach ($toTableIndexes as $indexName => $index) { + if (($index->isPrimary() && $fromTable->getPrimaryKey() !== null) || $fromTable->hasIndex($indexName)) { + continue; + } + + $addedIndexes[$indexName] = $index; + } + + /* See if there are any removed indexes in "to" table */ + foreach ($fromTableIndexes as $indexName => $index) { + // See if index is removed in "to" table. + if ( + ($index->isPrimary() && $toTable->getPrimaryKey() === null) || + ! $index->isPrimary() && ! $toTable->hasIndex($indexName) + ) { + $droppedIndexes[$indexName] = $index; + + continue; + } + + // See if index has changed in "to" table. + $toTableIndex = $index->isPrimary() ? $toTable->getPrimaryKey() : $toTable->getIndex($indexName); + assert($toTableIndex instanceof Index); + + if (! $this->diffIndex($index, $toTableIndex)) { + continue; + } + + $modifiedIndexes[$indexName] = $toTableIndex; + } + + $renamedIndexes = $this->detectRenamedIndexes($addedIndexes, $droppedIndexes); + + $fromForeignKeys = $fromTable->getForeignKeys(); + $toForeignKeys = $toTable->getForeignKeys(); + + foreach ($fromForeignKeys as $fromKey => $fromConstraint) { + foreach ($toForeignKeys as $toKey => $toConstraint) { + if ($this->diffForeignKey($fromConstraint, $toConstraint) === false) { + unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); + } else { + if (strtolower($fromConstraint->getName()) === strtolower($toConstraint->getName())) { + $modifiedForeignKeys[] = $toConstraint; + + unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); + } + } + } + } + + foreach ($fromForeignKeys as $fromConstraint) { + $droppedForeignKeys[] = $fromConstraint; + } + + foreach ($toForeignKeys as $toConstraint) { + $addedForeignKeys[] = $toConstraint; + } + + return new TableDiff( + $toTable->getName(), + $addedColumns, + $modifiedColumns, + $droppedColumns, + $addedIndexes, + $modifiedIndexes, + $droppedIndexes, + $fromTable, + $addedForeignKeys, + $modifiedForeignKeys, + $droppedForeignKeys, + $renamedColumns, + $renamedIndexes, + ); + } + + /** + * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop + * however ambiguities between different possibilities should not lead to renaming at all. + * + * @param array $addedColumns + * @param array $removedColumns + * + * @return array + * + * @throws Exception + */ + private function detectRenamedColumns(array &$addedColumns, array &$removedColumns): array + { + $candidatesByName = []; + + foreach ($addedColumns as $addedColumnName => $addedColumn) { + foreach ($removedColumns as $removedColumn) { + if (! $this->columnsEqual($addedColumn, $removedColumn)) { + continue; + } + + $candidatesByName[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName]; + } + } + + $renamedColumns = []; + + foreach ($candidatesByName as $candidates) { + if (count($candidates) !== 1) { + continue; + } + + [$removedColumn, $addedColumn] = $candidates[0]; + $removedColumnName = $removedColumn->getName(); + $addedColumnName = strtolower($addedColumn->getName()); + + if (isset($renamedColumns[$removedColumnName])) { + continue; + } + + $renamedColumns[$removedColumnName] = $addedColumn; + unset( + $addedColumns[$addedColumnName], + $removedColumns[strtolower($removedColumnName)], + ); + } + + return $renamedColumns; + } + + /** + * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop + * however ambiguities between different possibilities should not lead to renaming at all. + * + * @param array $addedIndexes + * @param array $removedIndexes + * + * @return array + */ + private function detectRenamedIndexes(array &$addedIndexes, array &$removedIndexes): array + { + $candidatesByName = []; + + // Gather possible rename candidates by comparing each added and removed index based on semantics. + foreach ($addedIndexes as $addedIndexName => $addedIndex) { + foreach ($removedIndexes as $removedIndex) { + if ($this->diffIndex($addedIndex, $removedIndex)) { + continue; + } + + $candidatesByName[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName]; + } + } + + $renamedIndexes = []; + + foreach ($candidatesByName as $candidates) { + // If the current rename candidate contains exactly one semantically equal index, + // we can safely rename it. + // Otherwise, it is unclear if a rename action is really intended, + // therefore we let those ambiguous indexes be added/dropped. + if (count($candidates) !== 1) { + continue; + } + + [$removedIndex, $addedIndex] = $candidates[0]; + + $removedIndexName = strtolower($removedIndex->getName()); + $addedIndexName = strtolower($addedIndex->getName()); + + if (isset($renamedIndexes[$removedIndexName])) { + continue; + } + + $renamedIndexes[$removedIndexName] = $addedIndex; + unset( + $addedIndexes[$addedIndexName], + $removedIndexes[$removedIndexName], + ); + } + + return $renamedIndexes; + } + + /** + * @internal The method should be only used from within the {@see Comparator} class hierarchy. + * + * @return bool + */ + public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) + { + if ( + array_map('strtolower', $key1->getUnquotedLocalColumns()) + !== array_map('strtolower', $key2->getUnquotedLocalColumns()) + ) { + return true; + } + + if ( + array_map('strtolower', $key1->getUnquotedForeignColumns()) + !== array_map('strtolower', $key2->getUnquotedForeignColumns()) + ) { + return true; + } + + if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) { + return true; + } + + if ($key1->onUpdate() !== $key2->onUpdate()) { + return true; + } + + return $key1->onDelete() !== $key2->onDelete(); + } + + /** + * Compares the definitions of the given columns + * + * @internal The method should be only used from within the {@see Comparator} class hierarchy. + * + * @throws Exception + */ + public function columnsEqual(Column $column1, Column $column2): bool + { + if ($this->platform === null) { + return $this->diffColumn($column1, $column2) === []; + } + + return $this->platform->columnsEqual($column1, $column2); + } + + /** + * Returns the difference between the columns + * + * If there are differences this method returns the changed properties as a + * string array, otherwise an empty array gets returned. + * + * @deprecated Use {@see columnsEqual()} instead. + * + * @return string[] + */ + public function diffColumn(Column $column1, Column $column2) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5650', + '%s is deprecated. Use diffTable() instead.', + __METHOD__, + ); + + $properties1 = $column1->toArray(); + $properties2 = $column2->toArray(); + + $changedProperties = []; + + if (get_class($properties1['type']) !== get_class($properties2['type'])) { + $changedProperties[] = 'type'; + } + + foreach (['notnull', 'unsigned', 'autoincrement'] as $property) { + if ($properties1[$property] === $properties2[$property]) { + continue; + } + + $changedProperties[] = $property; + } + + // Null values need to be checked additionally as they tell whether to create or drop a default value. + // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation. + if ( + ($properties1['default'] === null) !== ($properties2['default'] === null) + || $properties1['default'] != $properties2['default'] + ) { + $changedProperties[] = 'default'; + } + + if ( + ($properties1['type'] instanceof Types\StringType && ! $properties1['type'] instanceof Types\GuidType) || + $properties1['type'] instanceof Types\BinaryType + ) { + // check if value of length is set at all, default value assumed otherwise. + $length1 = $properties1['length'] ?? 255; + $length2 = $properties2['length'] ?? 255; + if ($length1 !== $length2) { + $changedProperties[] = 'length'; + } + + if ($properties1['fixed'] !== $properties2['fixed']) { + $changedProperties[] = 'fixed'; + } + } elseif ($properties1['type'] instanceof Types\DecimalType) { + if (($properties1['precision'] ?? 10) !== ($properties2['precision'] ?? 10)) { + $changedProperties[] = 'precision'; + } + + if ($properties1['scale'] !== $properties2['scale']) { + $changedProperties[] = 'scale'; + } + } + + // A null value and an empty string are actually equal for a comment so they should not trigger a change. + if ( + $properties1['comment'] !== $properties2['comment'] && + ! ($properties1['comment'] === null && $properties2['comment'] === '') && + ! ($properties2['comment'] === null && $properties1['comment'] === '') + ) { + $changedProperties[] = 'comment'; + } + + $customOptions1 = $column1->getCustomSchemaOptions(); + $customOptions2 = $column2->getCustomSchemaOptions(); + + foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) { + if (! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) { + $changedProperties[] = $key; + } elseif ($properties1[$key] !== $properties2[$key]) { + $changedProperties[] = $key; + } + } + + $platformOptions1 = $column1->getPlatformOptions(); + $platformOptions2 = $column2->getPlatformOptions(); + + foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) { + if ($properties1[$key] === $properties2[$key]) { + continue; + } + + $changedProperties[] = $key; + } + + return array_unique($changedProperties); + } + + /** + * Finds the difference between the indexes $index1 and $index2. + * + * Compares $index1 with $index2 and returns true if there are any + * differences or false in case there are no differences. + * + * @internal The method should be only used from within the {@see Comparator} class hierarchy. + * + * @return bool + */ + public function diffIndex(Index $index1, Index $index2) + { + return ! ($index1->isFulfilledBy($index2) && $index2->isFulfilledBy($index1)); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Constraint.php b/vendor/doctrine/dbal/src/Schema/Constraint.php new file mode 100644 index 0000000..f47ee1f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Constraint.php @@ -0,0 +1,41 @@ + + */ +class DB2SchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritDoc} + */ + public function listTableNames() + { + return $this->doListTableNames(); + } + + /** + * {@inheritDoc} + */ + public function listTables() + { + return $this->doListTables(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see introspectTable()} instead. + */ + public function listTableDetails($name) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5595', + '%s is deprecated. Use introspectTable() instead.', + __METHOD__, + ); + + return $this->doListTableDetails($name); + } + + /** + * {@inheritDoc} + */ + public function listTableColumns($table, $database = null) + { + return $this->doListTableColumns($table, $database); + } + + /** + * {@inheritDoc} + */ + public function listTableIndexes($table) + { + return $this->doListTableIndexes($table); + } + + /** + * {@inheritDoc} + */ + public function listTableForeignKeys($table, $database = null) + { + return $this->doListTableForeignKeys($table, $database); + } + + /** + * {@inheritDoc} + * + * @throws Exception + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + $length = null; + $fixed = null; + $scale = false; + $precision = false; + + $default = null; + + if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') { + $default = $tableColumn['default']; + + if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { + $default = str_replace("''", "'", $matches[1]); + } + } + + $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']); + + if (isset($tableColumn['comment'])) { + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + } + + switch (strtolower($tableColumn['typename'])) { + case 'varchar': + if ($tableColumn['codepage'] === 0) { + $type = Types::BINARY; + } + + $length = $tableColumn['length']; + $fixed = false; + break; + + case 'character': + if ($tableColumn['codepage'] === 0) { + $type = Types::BINARY; + } + + $length = $tableColumn['length']; + $fixed = true; + break; + + case 'clob': + $length = $tableColumn['length']; + break; + + case 'decimal': + case 'double': + case 'real': + $scale = $tableColumn['scale']; + $precision = $tableColumn['length']; + break; + } + + $options = [ + 'length' => $length, + 'fixed' => (bool) $fixed, + 'default' => $default, + 'autoincrement' => (bool) $tableColumn['autoincrement'], + 'notnull' => $tableColumn['nulls'] === 'N', + 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' + ? $tableColumn['comment'] + : null, + ]; + + if ($scale !== null && $precision !== null) { + $options['scale'] = $scale; + $options['precision'] = $precision; + } + + return new Column($tableColumn['colname'], Type::getType($type), $options); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableDefinition($table) + { + $table = array_change_key_case($table, CASE_LOWER); + + return $table['name']; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + foreach ($tableIndexes as &$tableIndexRow) { + $tableIndexRow = array_change_key_case($tableIndexRow, CASE_LOWER); + $tableIndexRow['primary'] = (bool) $tableIndexRow['primary']; + } + + return parent::_getPortableTableIndexesList($tableIndexes, $tableName); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return new ForeignKeyConstraint( + $tableForeignKey['local_columns'], + $tableForeignKey['foreign_table'], + $tableForeignKey['foreign_columns'], + $tableForeignKey['name'], + $tableForeignKey['options'], + ); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $foreignKeys = []; + + foreach ($tableForeignKeys as $tableForeignKey) { + $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); + + if (! isset($foreignKeys[$tableForeignKey['index_name']])) { + $foreignKeys[$tableForeignKey['index_name']] = [ + 'local_columns' => [$tableForeignKey['local_column']], + 'foreign_table' => $tableForeignKey['foreign_table'], + 'foreign_columns' => [$tableForeignKey['foreign_column']], + 'name' => $tableForeignKey['index_name'], + 'options' => [ + 'onUpdate' => $tableForeignKey['on_update'], + 'onDelete' => $tableForeignKey['on_delete'], + ], + ]; + } else { + $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; + $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; + } + } + + return parent::_getPortableTableForeignKeysList($foreignKeys); + } + + /** + * @param string $def + * + * @return string|null + */ + protected function _getPortableForeignKeyRuleDef($def) + { + if ($def === 'C') { + return 'CASCADE'; + } + + if ($def === 'N') { + return 'SET NULL'; + } + + return null; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableViewDefinition($view) + { + $view = array_change_key_case($view, CASE_LOWER); + + $sql = ''; + $pos = strpos($view['text'], ' AS '); + + if ($pos !== false) { + $sql = substr($view['text'], $pos + 4); + } + + return new View($view['name'], $sql); + } + + protected function normalizeName(string $name): string + { + $identifier = new Identifier($name); + + return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); + } + + protected function selectTableNames(string $databaseName): Result + { + $sql = <<<'SQL' +SELECT NAME +FROM SYSIBM.SYSTABLES +WHERE TYPE = 'T' + AND CREATOR = ? +SQL; + + return $this->_conn->executeQuery($sql, [$databaseName]); + } + + protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' C.TABNAME AS NAME,'; + } + + $sql .= <<<'SQL' + C.COLNAME, + C.TYPENAME, + C.CODEPAGE, + C.NULLS, + C.LENGTH, + C.SCALE, + C.REMARKS AS COMMENT, + CASE + WHEN C.GENERATED = 'D' THEN 1 + ELSE 0 + END AS AUTOINCREMENT, + C.DEFAULT +FROM SYSCAT.COLUMNS C + JOIN SYSCAT.TABLES AS T + ON T.TABSCHEMA = C.TABSCHEMA + AND T.TABNAME = C.TABNAME +SQL; + + $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"]; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'C.TABNAME = ?'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' IDX.TABNAME AS NAME,'; + } + + $sql .= <<<'SQL' + IDX.INDNAME AS KEY_NAME, + IDXCOL.COLNAME AS COLUMN_NAME, + CASE + WHEN IDX.UNIQUERULE = 'P' THEN 1 + ELSE 0 + END AS PRIMARY, + CASE + WHEN IDX.UNIQUERULE = 'D' THEN 1 + ELSE 0 + END AS NON_UNIQUE + FROM SYSCAT.INDEXES AS IDX + JOIN SYSCAT.TABLES AS T + ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME + JOIN SYSCAT.INDEXCOLUSE AS IDXCOL + ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME +SQL; + + $conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"]; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'IDX.TABNAME = ?'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' R.TABNAME AS NAME,'; + } + + $sql .= <<<'SQL' + FKCOL.COLNAME AS LOCAL_COLUMN, + R.REFTABNAME AS FOREIGN_TABLE, + PKCOL.COLNAME AS FOREIGN_COLUMN, + R.CONSTNAME AS INDEX_NAME, + CASE + WHEN R.UPDATERULE = 'R' THEN 'RESTRICT' + END AS ON_UPDATE, + CASE + WHEN R.DELETERULE = 'C' THEN 'CASCADE' + WHEN R.DELETERULE = 'N' THEN 'SET NULL' + WHEN R.DELETERULE = 'R' THEN 'RESTRICT' + END AS ON_DELETE + FROM SYSCAT.REFERENCES AS R + JOIN SYSCAT.TABLES AS T + ON T.TABSCHEMA = R.TABSCHEMA + AND T.TABNAME = R.TABNAME + JOIN SYSCAT.KEYCOLUSE AS FKCOL + ON FKCOL.CONSTNAME = R.CONSTNAME + AND FKCOL.TABSCHEMA = R.TABSCHEMA + AND FKCOL.TABNAME = R.TABNAME + JOIN SYSCAT.KEYCOLUSE AS PKCOL + ON PKCOL.CONSTNAME = R.REFKEYNAME + AND PKCOL.TABSCHEMA = R.REFTABSCHEMA + AND PKCOL.TABNAME = R.REFTABNAME + AND PKCOL.COLSEQ = FKCOL.COLSEQ +SQL; + + $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"]; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'R.TABNAME = ?'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * {@inheritDoc} + */ + protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array + { + $sql = 'SELECT NAME, REMARKS'; + + $conditions = []; + $params = []; + + if ($tableName !== null) { + $conditions[] = 'NAME = ?'; + $params[] = $tableName; + } + + $sql .= ' FROM SYSIBM.SYSTABLES'; + + if ($conditions !== []) { + $sql .= ' WHERE ' . implode(' AND ', $conditions); + } + + /** @var array> $metadata */ + $metadata = $this->_conn->executeQuery($sql, $params) + ->fetchAllAssociativeIndexed(); + + $tableOptions = []; + foreach ($metadata as $table => $data) { + $data = array_change_key_case($data, CASE_LOWER); + + $tableOptions[$table] = ['comment' => $data['remarks']]; + } + + return $tableOptions; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php b/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php new file mode 100644 index 0000000..efba87f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php @@ -0,0 +1,20 @@ +getDatabasePlatform()->createSchemaManager($connection); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php new file mode 100644 index 0000000..cc7acea --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php @@ -0,0 +1,21 @@ +getName(), + implode(', ', $foreignKey->getColumns()), + $foreignKey->getForeignTableName(), + implode(', ', $foreignKey->getForeignColumns()), + ), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php new file mode 100644 index 0000000..008bd5f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php @@ -0,0 +1,21 @@ + Identifier) + * + * @var Identifier[] + */ + protected $_localColumnNames; + + /** + * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with. + * + * @var Table|Identifier + */ + protected $_foreignTableName; + + /** + * Asset identifier instances of the referenced table column names the foreign key constraint is associated with. + * array($columnName => Identifier) + * + * @var Identifier[] + */ + protected $_foreignColumnNames; + + /** + * Options associated with the foreign key constraint. + * + * @var mixed[] + */ + protected $_options; + + /** + * Initializes the foreign key constraint. + * + * @param string[] $localColumnNames Names of the referencing table columns. + * @param Table|string $foreignTableName Referenced table. + * @param string[] $foreignColumnNames Names of the referenced table columns. + * @param string|null $name Name of the foreign key constraint. + * @param mixed[] $options Options associated with the foreign key constraint. + */ + public function __construct( + array $localColumnNames, + $foreignTableName, + array $foreignColumnNames, + $name = null, + array $options = [] + ) { + if ($name !== null) { + $this->_setName($name); + } + + $this->_localColumnNames = $this->createIdentifierMap($localColumnNames); + + if ($foreignTableName instanceof Table) { + $this->_foreignTableName = $foreignTableName; + } else { + $this->_foreignTableName = new Identifier($foreignTableName); + } + + $this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames); + $this->_options = $options; + } + + /** + * @param string[] $names + * + * @return Identifier[] + */ + private function createIdentifierMap(array $names): array + { + $identifiers = []; + + foreach ($names as $name) { + $identifiers[$name] = new Identifier($name); + } + + return $identifiers; + } + + /** + * Returns the name of the referencing table + * the foreign key constraint is associated with. + * + * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. + * + * @return string + */ + public function getLocalTableName() + { + return $this->_localTable->getName(); + } + + /** + * Sets the Table instance of the referencing table + * the foreign key constraint is associated with. + * + * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. + * + * @param Table $table Instance of the referencing table. + * + * @return void + */ + public function setLocalTable(Table $table) + { + $this->_localTable = $table; + } + + /** + * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. + * + * @return Table + */ + public function getLocalTable() + { + return $this->_localTable; + } + + /** + * Returns the names of the referencing table columns + * the foreign key constraint is associated with. + * + * @return string[] + */ + public function getLocalColumns() + { + return array_keys($this->_localColumnNames); + } + + /** + * Returns the quoted representation of the referencing table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referencing table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param AbstractPlatform $platform The platform to use for quotation. + * + * @return string[] + */ + public function getQuotedLocalColumns(AbstractPlatform $platform) + { + $columns = []; + + foreach ($this->_localColumnNames as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * Returns unquoted representation of local table column names for comparison with other FK + * + * @return string[] + */ + public function getUnquotedLocalColumns() + { + return array_map([$this, 'trimQuotes'], $this->getLocalColumns()); + } + + /** + * Returns unquoted representation of foreign table column names for comparison with other FK + * + * @return string[] + */ + public function getUnquotedForeignColumns() + { + return array_map([$this, 'trimQuotes'], $this->getForeignColumns()); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see getLocalColumns()} instead. + * + * @see getLocalColumns + */ + public function getColumns() + { + return $this->getLocalColumns(); + } + + /** + * Returns the quoted representation of the referencing table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referencing table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @deprecated Use {@see getQuotedLocalColumns()} instead. + * + * @see getQuotedLocalColumns + * + * @param AbstractPlatform $platform The platform to use for quotation. + * + * @return string[] + */ + public function getQuotedColumns(AbstractPlatform $platform) + { + return $this->getQuotedLocalColumns($platform); + } + + /** + * Returns the name of the referenced table + * the foreign key constraint is associated with. + * + * @return string + */ + public function getForeignTableName() + { + return $this->_foreignTableName->getName(); + } + + /** + * Returns the non-schema qualified foreign table name. + * + * @return string + */ + public function getUnqualifiedForeignTableName() + { + $name = $this->_foreignTableName->getName(); + $position = strrpos($name, '.'); + + if ($position !== false) { + $name = substr($name, $position + 1); + } + + return strtolower($name); + } + + /** + * Returns the quoted representation of the referenced table name + * the foreign key constraint is associated with. + * + * But only if it was defined with one or the referenced table name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param AbstractPlatform $platform The platform to use for quotation. + * + * @return string + */ + public function getQuotedForeignTableName(AbstractPlatform $platform) + { + return $this->_foreignTableName->getQuotedName($platform); + } + + /** + * Returns the names of the referenced table columns + * the foreign key constraint is associated with. + * + * @return string[] + */ + public function getForeignColumns() + { + return array_keys($this->_foreignColumnNames); + } + + /** + * Returns the quoted representation of the referenced table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referenced table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param AbstractPlatform $platform The platform to use for quotation. + * + * @return string[] + */ + public function getQuotedForeignColumns(AbstractPlatform $platform) + { + $columns = []; + + foreach ($this->_foreignColumnNames as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * Returns whether or not a given option + * is associated with the foreign key constraint. + * + * @param string $name Name of the option to check. + * + * @return bool + */ + public function hasOption($name) + { + return isset($this->_options[$name]); + } + + /** + * Returns an option associated with the foreign key constraint. + * + * @param string $name Name of the option the foreign key constraint is associated with. + * + * @return mixed + */ + public function getOption($name) + { + return $this->_options[$name]; + } + + /** + * Returns the options associated with the foreign key constraint. + * + * @return mixed[] + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Returns the referential action for UPDATE operations + * on the referenced table the foreign key constraint is associated with. + * + * @return string|null + */ + public function onUpdate() + { + return $this->onEvent('onUpdate'); + } + + /** + * Returns the referential action for DELETE operations + * on the referenced table the foreign key constraint is associated with. + * + * @return string|null + */ + public function onDelete() + { + return $this->onEvent('onDelete'); + } + + /** + * Returns the referential action for a given database operation + * on the referenced table the foreign key constraint is associated with. + * + * @param string $event Name of the database operation/event to return the referential action for. + */ + private function onEvent($event): ?string + { + if (isset($this->_options[$event])) { + $onEvent = strtoupper($this->_options[$event]); + + if ($onEvent !== 'NO ACTION' && $onEvent !== 'RESTRICT') { + return $onEvent; + } + } + + return null; + } + + /** + * Checks whether this foreign key constraint intersects the given index columns. + * + * Returns `true` if at least one of this foreign key's local columns + * matches one of the given index's columns, `false` otherwise. + * + * @param Index $index The index to be checked against. + * + * @return bool + */ + public function intersectsIndexColumns(Index $index) + { + foreach ($index->getColumns() as $indexColumn) { + foreach ($this->_localColumnNames as $localColumn) { + if (strtolower($indexColumn) === strtolower($localColumn->getName())) { + return true; + } + } + } + + return false; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Identifier.php b/vendor/doctrine/dbal/src/Schema/Identifier.php new file mode 100644 index 0000000..f34465e --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Identifier.php @@ -0,0 +1,27 @@ +_setName($identifier); + + if (! $quote || $this->_quoted) { + return; + } + + $this->_setName('"' . $this->getName() . '"'); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Index.php b/vendor/doctrine/dbal/src/Schema/Index.php new file mode 100644 index 0000000..84fac41 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Index.php @@ -0,0 +1,365 @@ + Identifier) + * + * @var Identifier[] + */ + protected $_columns = []; + + /** @var bool */ + protected $_isUnique = false; + + /** @var bool */ + protected $_isPrimary = false; + + /** + * Platform specific flags for indexes. + * array($flagName => true) + * + * @var true[] + */ + protected $_flags = []; + + /** + * Platform specific options + * + * @todo $_flags should eventually be refactored into options + * @var mixed[] + */ + private array $options = []; + + /** + * @param string $name + * @param string[] $columns + * @param bool $isUnique + * @param bool $isPrimary + * @param string[] $flags + * @param mixed[] $options + */ + public function __construct( + $name, + array $columns, + $isUnique = false, + $isPrimary = false, + array $flags = [], + array $options = [] + ) { + $isUnique = $isUnique || $isPrimary; + + $this->_setName($name); + $this->_isUnique = $isUnique; + $this->_isPrimary = $isPrimary; + $this->options = $options; + + foreach ($columns as $column) { + $this->_addColumn($column); + } + + foreach ($flags as $flag) { + $this->addFlag($flag); + } + } + + /** @throws InvalidArgumentException */ + protected function _addColumn(string $column): void + { + $this->_columns[$column] = new Identifier($column); + } + + /** + * {@inheritDoc} + */ + public function getColumns() + { + return array_keys($this->_columns); + } + + /** + * {@inheritDoc} + */ + public function getQuotedColumns(AbstractPlatform $platform) + { + $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths') + ? $this->getOption('lengths') : []; + + $columns = []; + + foreach ($this->_columns as $column) { + $length = array_shift($subParts); + + $quotedColumn = $column->getQuotedName($platform); + + if ($length !== null) { + $quotedColumn .= '(' . $length . ')'; + } + + $columns[] = $quotedColumn; + } + + return $columns; + } + + /** @return string[] */ + public function getUnquotedColumns() + { + return array_map([$this, 'trimQuotes'], $this->getColumns()); + } + + /** + * Is the index neither unique nor primary key? + * + * @return bool + */ + public function isSimpleIndex() + { + return ! $this->_isPrimary && ! $this->_isUnique; + } + + /** @return bool */ + public function isUnique() + { + return $this->_isUnique; + } + + /** @return bool */ + public function isPrimary() + { + return $this->_isPrimary; + } + + /** + * @param string $name + * @param int $pos + * + * @return bool + */ + public function hasColumnAtPosition($name, $pos = 0) + { + $name = $this->trimQuotes(strtolower($name)); + $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); + + return array_search($name, $indexColumns, true) === $pos; + } + + /** + * Checks if this index exactly spans the given column names in the correct order. + * + * @param string[] $columnNames + * + * @return bool + */ + public function spansColumns(array $columnNames) + { + $columns = $this->getColumns(); + $numberOfColumns = count($columns); + $sameColumns = true; + + for ($i = 0; $i < $numberOfColumns; $i++) { + if ( + isset($columnNames[$i]) + && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i])) + ) { + continue; + } + + $sameColumns = false; + } + + return $sameColumns; + } + + /** + * Keeping misspelled function name for backwards compatibility + * + * @deprecated Use {@see isFulfilledBy()} instead. + * + * @return bool + */ + public function isFullfilledBy(Index $other) + { + return $this->isFulfilledBy($other); + } + + /** + * Checks if the other index already fulfills all the indexing and constraint needs of the current one. + */ + public function isFulfilledBy(Index $other): bool + { + // allow the other index to be equally large only. It being larger is an option + // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) + if (count($other->getColumns()) !== count($this->getColumns())) { + return false; + } + + // Check if columns are the same, and even in the same order + $sameColumns = $this->spansColumns($other->getColumns()); + + if ($sameColumns) { + if (! $this->samePartialIndex($other)) { + return false; + } + + if (! $this->hasSameColumnLengths($other)) { + return false; + } + + if (! $this->isUnique() && ! $this->isPrimary()) { + // this is a special case: If the current key is neither primary or unique, any unique or + // primary key will always have the same effect for the index and there cannot be any constraint + // overlaps. This means a primary or unique index can always fulfill the requirements of just an + // index that has no constraints. + return true; + } + + if ($other->isPrimary() !== $this->isPrimary()) { + return false; + } + + return $other->isUnique() === $this->isUnique(); + } + + return false; + } + + /** + * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. + * + * @return bool + */ + public function overrules(Index $other) + { + if ($other->isPrimary()) { + return false; + } + + if ($this->isSimpleIndex() && $other->isUnique()) { + return false; + } + + return $this->spansColumns($other->getColumns()) + && ($this->isPrimary() || $this->isUnique()) + && $this->samePartialIndex($other); + } + + /** + * Returns platform specific flags for indexes. + * + * @return string[] + */ + public function getFlags() + { + return array_keys($this->_flags); + } + + /** + * Adds Flag for an index that translates to platform specific handling. + * + * @param string $flag + * + * @return Index + * + * @example $index->addFlag('CLUSTERED') + */ + public function addFlag($flag) + { + $this->_flags[strtolower($flag)] = true; + + return $this; + } + + /** + * Does this index have a specific flag? + * + * @param string $flag + * + * @return bool + */ + public function hasFlag($flag) + { + return isset($this->_flags[strtolower($flag)]); + } + + /** + * Removes a flag. + * + * @param string $flag + * + * @return void + */ + public function removeFlag($flag) + { + unset($this->_flags[strtolower($flag)]); + } + + /** + * @param string $name + * + * @return bool + */ + public function hasOption($name) + { + return isset($this->options[strtolower($name)]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getOption($name) + { + return $this->options[strtolower($name)]; + } + + /** @return mixed[] */ + public function getOptions() + { + return $this->options; + } + + /** + * Return whether the two indexes have the same partial index + */ + private function samePartialIndex(Index $other): bool + { + if ( + $this->hasOption('where') + && $other->hasOption('where') + && $this->getOption('where') === $other->getOption('where') + ) { + return true; + } + + return ! $this->hasOption('where') && ! $other->hasOption('where'); + } + + /** + * Returns whether the index has the same column lengths as the other + */ + private function hasSameColumnLengths(self $other): bool + { + $filter = static function (?int $length): bool { + return $length !== null; + }; + + return array_filter($this->options['lengths'] ?? [], $filter) + === array_filter($other->options['lengths'] ?? [], $filter); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/LegacySchemaManagerFactory.php b/vendor/doctrine/dbal/src/Schema/LegacySchemaManagerFactory.php new file mode 100644 index 0000000..01c856b --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/LegacySchemaManagerFactory.php @@ -0,0 +1,19 @@ +getDriver()->getSchemaManager( + $connection, + $connection->getDatabasePlatform(), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php b/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php new file mode 100644 index 0000000..6e444d2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php @@ -0,0 +1,605 @@ + + */ +class MySQLSchemaManager extends AbstractSchemaManager +{ + /** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */ + private const MARIADB_ESCAPE_SEQUENCES = [ + '\\0' => "\0", + "\\'" => "'", + '\\"' => '"', + '\\b' => "\b", + '\\n' => "\n", + '\\r' => "\r", + '\\t' => "\t", + '\\Z' => "\x1a", + '\\\\' => '\\', + '\\%' => '%', + '\\_' => '_', + + // Internally, MariaDB escapes single quotes using the standard syntax + "''" => "'", + ]; + + /** + * {@inheritDoc} + */ + public function listTableNames() + { + return $this->doListTableNames(); + } + + /** + * {@inheritDoc} + */ + public function listTables() + { + return $this->doListTables(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see introspectTable()} instead. + */ + public function listTableDetails($name) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5595', + '%s is deprecated. Use introspectTable() instead.', + __METHOD__, + ); + + return $this->doListTableDetails($name); + } + + /** + * {@inheritDoc} + */ + public function listTableColumns($table, $database = null) + { + return $this->doListTableColumns($table, $database); + } + + /** + * {@inheritDoc} + */ + public function listTableIndexes($table) + { + return $this->doListTableIndexes($table); + } + + /** + * {@inheritDoc} + */ + public function listTableForeignKeys($table, $database = null) + { + return $this->doListTableForeignKeys($table, $database); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableDefinition($table) + { + return array_shift($table); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + foreach ($tableIndexes as $k => $v) { + $v = array_change_key_case($v, CASE_LOWER); + if ($v['key_name'] === 'PRIMARY') { + $v['primary'] = true; + } else { + $v['primary'] = false; + } + + if (strpos($v['index_type'], 'FULLTEXT') !== false) { + $v['flags'] = ['FULLTEXT']; + } elseif (strpos($v['index_type'], 'SPATIAL') !== false) { + $v['flags'] = ['SPATIAL']; + } + + // Ignore prohibited prefix `length` for spatial index + if (strpos($v['index_type'], 'SPATIAL') === false) { + $v['length'] = isset($v['sub_part']) ? (int) $v['sub_part'] : null; + } + + $tableIndexes[$k] = $v; + } + + return parent::_getPortableTableIndexesList($tableIndexes, $tableName); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['Database']; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + $dbType = strtolower($tableColumn['type']); + $dbType = strtok($dbType, '(), '); + assert(is_string($dbType)); + + $length = $tableColumn['length'] ?? strtok('(), '); + + $fixed = null; + + if (! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $scale = null; + $precision = null; + + $type = $origType = $this->_platform->getDoctrineTypeMapping($dbType); + + // In cases where not connected to a database DESCRIBE $table does not return 'Comment' + if (isset($tableColumn['comment'])) { + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + } + + switch ($dbType) { + case 'char': + case 'binary': + $fixed = true; + break; + + case 'float': + case 'double': + case 'real': + case 'numeric': + case 'decimal': + if ( + preg_match( + '([A-Za-z]+\(([0-9]+),([0-9]+)\))', + $tableColumn['type'], + $match, + ) === 1 + ) { + $precision = $match[1]; + $scale = $match[2]; + $length = null; + } + + break; + + case 'tinytext': + $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT; + break; + + case 'text': + $length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT; + break; + + case 'mediumtext': + $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT; + break; + + case 'tinyblob': + $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB; + break; + + case 'blob': + $length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB; + break; + + case 'mediumblob': + $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB; + break; + + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'int': + case 'integer': + case 'bigint': + case 'year': + $length = null; + break; + } + + if ($this->_platform instanceof MariaDb1027Platform) { + $columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']); + } else { + $columnDefault = $tableColumn['default']; + } + + $options = [ + 'length' => $length !== null ? (int) $length : null, + 'unsigned' => strpos($tableColumn['type'], 'unsigned') !== false, + 'fixed' => (bool) $fixed, + 'default' => $columnDefault, + 'notnull' => $tableColumn['null'] !== 'YES', + 'scale' => null, + 'precision' => null, + 'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false, + 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' + ? $tableColumn['comment'] + : null, + ]; + + if ($scale !== null && $precision !== null) { + $options['scale'] = (int) $scale; + $options['precision'] = (int) $precision; + } + + $column = new Column($tableColumn['field'], Type::getType($type), $options); + + if (isset($tableColumn['characterset'])) { + $column->setPlatformOption('charset', $tableColumn['characterset']); + } + + if (isset($tableColumn['collation'])) { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + if (isset($tableColumn['declarationMismatch'])) { + $column->setPlatformOption('declarationMismatch', $tableColumn['declarationMismatch']); + } + + // Check underlying database type where doctrine type is inferred from DC2Type comment + // and set a flag if it is not as expected. + if ($type === 'json' && $origType !== $type && $this->expectedDbType($type, $options) !== $dbType) { + $column->setPlatformOption('declarationMismatch', true); + } + + return $column; + } + + /** + * Returns the database data type for a given doctrine type and column + * + * Note that for data types that depend on length where length is not part of the column definition + * and therefore the $tableColumn['length'] will not be set, for example TEXT (which could be LONGTEXT, + * MEDIUMTEXT) or BLOB (LONGBLOB or TINYBLOB), the expectedDbType cannot be inferred exactly, merely + * the default type. + * + * This method is intended to be used to determine underlying database type where doctrine type is + * inferred from a DC2Type comment. + * + * @param mixed[] $tableColumn + */ + private function expectedDbType(string $type, array $tableColumn): string + { + $_type = Type::getType($type); + $expectedDbType = strtolower($_type->getSQLDeclaration($tableColumn, $this->_platform)); + $expectedDbType = strtok($expectedDbType, '(), '); + + return $expectedDbType === false ? '' : $expectedDbType; + } + + /** + * Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers. + * + * - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted + * to distinguish them from expressions (see MDEV-10134). + * - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema + * as current_timestamp(), currdate(), currtime() + * - Quoted 'NULL' is not enforced by Maria, it is technically possible to have + * null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053) + * - \' is always stored as '' in information_schema (normalized) + * + * @link https://mariadb.com/kb/en/library/information-schema-columns-table/ + * @link https://jira.mariadb.org/browse/MDEV-13132 + * + * @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7 + */ + private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?string $columnDefault): ?string + { + if ($columnDefault === 'NULL' || $columnDefault === null) { + return null; + } + + if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) { + return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES); + } + + switch ($columnDefault) { + case 'current_timestamp()': + return $platform->getCurrentTimestampSQL(); + + case 'curdate()': + return $platform->getCurrentDateSQL(); + + case 'curtime()': + return $platform->getCurrentTimeSQL(); + } + + return $columnDefault; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = []; + foreach ($tableForeignKeys as $value) { + $value = array_change_key_case($value, CASE_LOWER); + if (! isset($list[$value['constraint_name']])) { + if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') { + $value['delete_rule'] = null; + } + + if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') { + $value['update_rule'] = null; + } + + $list[$value['constraint_name']] = [ + 'name' => $value['constraint_name'], + 'local' => [], + 'foreign' => [], + 'foreignTable' => $value['referenced_table_name'], + 'onDelete' => $value['delete_rule'], + 'onUpdate' => $value['update_rule'], + ]; + } + + $list[$value['constraint_name']]['local'][] = $value['column_name']; + $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name']; + } + + return parent::_getPortableTableForeignKeysList($list); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint + { + return new ForeignKeyConstraint( + $tableForeignKey['local'], + $tableForeignKey['foreignTable'], + $tableForeignKey['foreign'], + $tableForeignKey['name'], + [ + 'onDelete' => $tableForeignKey['onDelete'], + 'onUpdate' => $tableForeignKey['onUpdate'], + ], + ); + } + + public function createComparator(): Comparator + { + return new MySQL\Comparator( + $this->_platform, + new CachingCollationMetadataProvider( + new ConnectionCollationMetadataProvider($this->_conn), + ), + ); + } + + protected function selectTableNames(string $databaseName): Result + { + $sql = <<<'SQL' +SELECT TABLE_NAME +FROM information_schema.TABLES +WHERE TABLE_SCHEMA = ? + AND TABLE_TYPE = 'BASE TABLE' +ORDER BY TABLE_NAME +SQL; + + return $this->_conn->executeQuery($sql, [$databaseName]); + } + + protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result + { + // @todo 4.0 - call getColumnTypeSQLSnippet() instead + [$columnTypeSQL, $joinCheckConstraintSQL] = $this->_platform->getColumnTypeSQLSnippets('c', $databaseName); + + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' c.TABLE_NAME,'; + } + + $sql .= <<_conn->executeQuery($sql, $params); + } + + protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' TABLE_NAME,'; + } + + $sql .= <<<'SQL' + NON_UNIQUE AS Non_Unique, + INDEX_NAME AS Key_name, + COLUMN_NAME AS Column_Name, + SUB_PART AS Sub_Part, + INDEX_TYPE AS Index_Type +FROM information_schema.STATISTICS +SQL; + + $conditions = ['TABLE_SCHEMA = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'TABLE_NAME = ?'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT DISTINCT'; + + if ($tableName === null) { + $sql .= ' k.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + k.CONSTRAINT_NAME, + k.COLUMN_NAME, + k.REFERENCED_TABLE_NAME, + k.REFERENCED_COLUMN_NAME, + k.ORDINAL_POSITION /*!50116, + c.UPDATE_RULE, + c.DELETE_RULE */ +FROM information_schema.key_column_usage k /*!50116 +INNER JOIN information_schema.referential_constraints c +ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME +AND c.TABLE_NAME = k.TABLE_NAME */ +SQL; + + $conditions = ['k.TABLE_SCHEMA = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'k.TABLE_NAME = ?'; + $params[] = $tableName; + } + + $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL'; + + $sql .= ' WHERE ' . implode(' AND ', $conditions) + // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition + // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions + // caused by https://bugs.mysql.com/bug.php?id=81347. + // Use a string literal for the database name since the internal PDO SQL parser + // cannot recognize parameter placeholders inside conditional comments + . ' /*!50116 AND c.CONSTRAINT_SCHEMA = ' . $this->_conn->quote($databaseName) . ' */' + . ' ORDER BY k.ORDINAL_POSITION'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * {@inheritDoc} + */ + protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array + { + $sql = $this->_platform->fetchTableOptionsByTable($tableName !== null); + + $params = [$databaseName]; + if ($tableName !== null) { + $params[] = $tableName; + } + + /** @var array> $metadata */ + $metadata = $this->_conn->executeQuery($sql, $params) + ->fetchAllAssociativeIndexed(); + + $tableOptions = []; + foreach ($metadata as $table => $data) { + $data = array_change_key_case($data, CASE_LOWER); + + $tableOptions[$table] = [ + 'engine' => $data['engine'], + 'collation' => $data['table_collation'], + 'charset' => $data['character_set_name'], + 'autoincrement' => $data['auto_increment'], + 'comment' => $data['table_comment'], + 'create_options' => $this->parseCreateOptions($data['create_options']), + ]; + } + + return $tableOptions; + } + + /** @return string[]|true[] */ + private function parseCreateOptions(?string $string): array + { + $options = []; + + if ($string === null || $string === '') { + return $options; + } + + foreach (explode(' ', $string) as $pair) { + $parts = explode('=', $pair, 2); + + $options[$parts[0]] = $parts[1] ?? true; + } + + return $options; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php new file mode 100644 index 0000000..0737522 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php @@ -0,0 +1,539 @@ + + */ +class OracleSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritDoc} + */ + public function listTableNames() + { + return $this->doListTableNames(); + } + + /** + * {@inheritDoc} + */ + public function listTables() + { + return $this->doListTables(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see introspectTable()} instead. + */ + public function listTableDetails($name) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5595', + '%s is deprecated. Use introspectTable() instead.', + __METHOD__, + ); + + return $this->doListTableDetails($name); + } + + /** + * {@inheritDoc} + */ + public function listTableColumns($table, $database = null) + { + return $this->doListTableColumns($table, $database); + } + + /** + * {@inheritDoc} + */ + public function listTableIndexes($table) + { + return $this->doListTableIndexes($table); + } + + /** + * {@inheritDoc} + */ + public function listTableForeignKeys($table, $database = null) + { + return $this->doListTableForeignKeys($table, $database); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableViewDefinition($view) + { + $view = array_change_key_case($view, CASE_LOWER); + + return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableDefinition($table) + { + $table = array_change_key_case($table, CASE_LOWER); + + return $this->getQuotedIdentifierName($table['table_name']); + } + + /** + * {@inheritDoc} + * + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + $indexBuffer = []; + foreach ($tableIndexes as $tableIndex) { + $tableIndex = array_change_key_case($tableIndex, CASE_LOWER); + + $keyName = strtolower($tableIndex['name']); + $buffer = []; + + if ($tableIndex['is_primary'] === 'P') { + $keyName = 'primary'; + $buffer['primary'] = true; + $buffer['non_unique'] = false; + } else { + $buffer['primary'] = false; + $buffer['non_unique'] = ! $tableIndex['is_unique']; + } + + $buffer['key_name'] = $keyName; + $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']); + $indexBuffer[] = $buffer; + } + + return parent::_getPortableTableIndexesList($indexBuffer, $tableName); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + $dbType = strtolower($tableColumn['data_type']); + if (strpos($dbType, 'timestamp(') === 0) { + if (strpos($dbType, 'with time zone') !== false) { + $dbType = 'timestamptz'; + } else { + $dbType = 'timestamp'; + } + } + + $unsigned = $fixed = $precision = $scale = $length = null; + + if (! isset($tableColumn['column_name'])) { + $tableColumn['column_name'] = ''; + } + + // Default values returned from database sometimes have trailing spaces. + if (is_string($tableColumn['data_default'])) { + $tableColumn['data_default'] = trim($tableColumn['data_default']); + } + + if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') { + $tableColumn['data_default'] = null; + } + + if ($tableColumn['data_default'] !== null) { + // Default values returned from database are represented as literal expressions + if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches) === 1) { + $tableColumn['data_default'] = str_replace("''", "'", $matches[1]); + } + } + + if ($tableColumn['data_precision'] !== null) { + $precision = (int) $tableColumn['data_precision']; + } + + if ($tableColumn['data_scale'] !== null) { + $scale = (int) $tableColumn['data_scale']; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type); + $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type); + + switch ($dbType) { + case 'number': + if ($precision === 20 && $scale === 0) { + $type = 'bigint'; + } elseif ($precision === 5 && $scale === 0) { + $type = 'smallint'; + } elseif ($precision === 1 && $scale === 0) { + $type = 'boolean'; + } elseif ($scale > 0) { + $type = 'decimal'; + } + + break; + + case 'varchar': + case 'varchar2': + case 'nvarchar2': + $length = $tableColumn['char_length']; + $fixed = false; + break; + + case 'raw': + $length = $tableColumn['data_length']; + $fixed = true; + break; + + case 'char': + case 'nchar': + $length = $tableColumn['char_length']; + $fixed = true; + break; + } + + $options = [ + 'notnull' => $tableColumn['nullable'] === 'N', + 'fixed' => (bool) $fixed, + 'unsigned' => (bool) $unsigned, + 'default' => $tableColumn['data_default'], + 'length' => $length, + 'precision' => $precision, + 'scale' => $scale, + 'comment' => isset($tableColumn['comments']) && $tableColumn['comments'] !== '' + ? $tableColumn['comments'] + : null, + ]; + + return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = []; + foreach ($tableForeignKeys as $value) { + $value = array_change_key_case($value, CASE_LOWER); + if (! isset($list[$value['constraint_name']])) { + if ($value['delete_rule'] === 'NO ACTION') { + $value['delete_rule'] = null; + } + + $list[$value['constraint_name']] = [ + 'name' => $this->getQuotedIdentifierName($value['constraint_name']), + 'local' => [], + 'foreign' => [], + 'foreignTable' => $value['references_table'], + 'onDelete' => $value['delete_rule'], + ]; + } + + $localColumn = $this->getQuotedIdentifierName($value['local_column']); + $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']); + + $list[$value['constraint_name']]['local'][$value['position']] = $localColumn; + $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn; + } + + return parent::_getPortableTableForeignKeysList($list); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint + { + return new ForeignKeyConstraint( + array_values($tableForeignKey['local']), + $this->getQuotedIdentifierName($tableForeignKey['foreignTable']), + array_values($tableForeignKey['foreign']), + $this->getQuotedIdentifierName($tableForeignKey['name']), + ['onDelete' => $tableForeignKey['onDelete']], + ); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + $sequence = array_change_key_case($sequence, CASE_LOWER); + + return new Sequence( + $this->getQuotedIdentifierName($sequence['sequence_name']), + (int) $sequence['increment_by'], + (int) $sequence['min_value'], + ); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + $database = array_change_key_case($database, CASE_LOWER); + + return $database['username']; + } + + /** + * {@inheritDoc} + */ + public function createDatabase($database) + { + $statement = $this->_platform->getCreateDatabaseSQL($database); + + $params = $this->_conn->getParams(); + + if (isset($params['password'])) { + $statement .= ' IDENTIFIED BY ' . $params['password']; + } + + $this->_conn->executeStatement($statement); + + $statement = 'GRANT DBA TO ' . $database; + $this->_conn->executeStatement($statement); + } + + /** + * @internal The method should be only used from within the OracleSchemaManager class hierarchy. + * + * @param string $table + * + * @return bool + * + * @throws Exception + */ + public function dropAutoincrement($table) + { + $sql = $this->_platform->getDropAutoincrementSql($table); + foreach ($sql as $query) { + $this->_conn->executeStatement($query); + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function dropTable($name) + { + $this->tryMethod('dropAutoincrement', $name); + + parent::dropTable($name); + } + + /** + * Returns the quoted representation of the given identifier name. + * + * Quotes non-uppercase identifiers explicitly to preserve case + * and thus make references to the particular identifier work. + * + * @param string $identifier The identifier to quote. + */ + private function getQuotedIdentifierName($identifier): string + { + if (preg_match('/[a-z]/', $identifier) === 1) { + return $this->_platform->quoteIdentifier($identifier); + } + + return $identifier; + } + + protected function selectTableNames(string $databaseName): Result + { + $sql = <<<'SQL' +SELECT TABLE_NAME +FROM ALL_TABLES +WHERE OWNER = :OWNER +ORDER BY TABLE_NAME +SQL; + + return $this->_conn->executeQuery($sql, ['OWNER' => $databaseName]); + } + + protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' C.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + C.COLUMN_NAME, + C.DATA_TYPE, + C.DATA_DEFAULT, + C.DATA_PRECISION, + C.DATA_SCALE, + C.CHAR_LENGTH, + C.DATA_LENGTH, + C.NULLABLE, + D.COMMENTS + FROM ALL_TAB_COLUMNS C + INNER JOIN ALL_TABLES T + ON T.OWNER = C.OWNER + AND T.TABLE_NAME = C.TABLE_NAME + LEFT JOIN ALL_COL_COMMENTS D + ON D.OWNER = C.OWNER + AND D.TABLE_NAME = C.TABLE_NAME + AND D.COLUMN_NAME = C.COLUMN_NAME +SQL; + + $conditions = ['C.OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; + + if ($tableName !== null) { + $conditions[] = 'C.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' IND_COL.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + IND_COL.INDEX_NAME AS NAME, + IND.INDEX_TYPE AS TYPE, + DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE, + IND_COL.COLUMN_NAME, + IND_COL.COLUMN_POSITION AS COLUMN_POS, + CON.CONSTRAINT_TYPE AS IS_PRIMARY + FROM ALL_IND_COLUMNS IND_COL + LEFT JOIN ALL_INDEXES IND + ON IND.OWNER = IND_COL.INDEX_OWNER + AND IND.INDEX_NAME = IND_COL.INDEX_NAME + LEFT JOIN ALL_CONSTRAINTS CON + ON CON.OWNER = IND_COL.INDEX_OWNER + AND CON.INDEX_NAME = IND_COL.INDEX_NAME +SQL; + + $conditions = ['IND_COL.INDEX_OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; + + if ($tableName !== null) { + $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME' + . ', IND_COL.COLUMN_POSITION'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' COLS.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + ALC.CONSTRAINT_NAME, + ALC.DELETE_RULE, + COLS.COLUMN_NAME LOCAL_COLUMN, + COLS.POSITION, + R_COLS.TABLE_NAME REFERENCES_TABLE, + R_COLS.COLUMN_NAME FOREIGN_COLUMN + FROM ALL_CONS_COLUMNS COLS + LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME + LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND + R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND + R_COLS.POSITION = COLS.POSITION +SQL; + + $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; + + if ($tableName !== null) { + $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME' + . ', COLS.POSITION'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * {@inheritDoc} + */ + protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array + { + $sql = 'SELECT TABLE_NAME, COMMENTS'; + + $conditions = ['OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; + + if ($tableName !== null) { + $conditions[] = 'TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; + } + + $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions); + + /** @var array> $metadata */ + $metadata = $this->_conn->executeQuery($sql, $params) + ->fetchAllAssociativeIndexed(); + + $tableOptions = []; + foreach ($metadata as $table => $data) { + $data = array_change_key_case($data, CASE_LOWER); + + $tableOptions[$table] = [ + 'comment' => $data['comments'], + ]; + } + + return $tableOptions; + } + + protected function normalizeName(string $name): string + { + $identifier = new Identifier($name); + + return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php b/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php new file mode 100644 index 0000000..3ad313e --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php @@ -0,0 +1,771 @@ + + */ +class PostgreSQLSchemaManager extends AbstractSchemaManager +{ + /** @var string[]|null */ + private ?array $existingSchemaPaths = null; + + /** + * {@inheritDoc} + */ + public function listTableNames() + { + return $this->doListTableNames(); + } + + /** + * {@inheritDoc} + */ + public function listTables() + { + return $this->doListTables(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see introspectTable()} instead. + */ + public function listTableDetails($name) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5595', + '%s is deprecated. Use introspectTable() instead.', + __METHOD__, + ); + + return $this->doListTableDetails($name); + } + + /** + * {@inheritDoc} + */ + public function listTableColumns($table, $database = null) + { + return $this->doListTableColumns($table, $database); + } + + /** + * {@inheritDoc} + */ + public function listTableIndexes($table) + { + return $this->doListTableIndexes($table); + } + + /** + * {@inheritDoc} + */ + public function listTableForeignKeys($table, $database = null) + { + return $this->doListTableForeignKeys($table, $database); + } + + /** + * Gets all the existing schema names. + * + * @deprecated Use {@see listSchemaNames()} instead. + * + * @return string[] + * + * @throws Exception + */ + public function getSchemaNames() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'PostgreSQLSchemaManager::getSchemaNames() is deprecated,' + . ' use PostgreSQLSchemaManager::listSchemaNames() instead.', + ); + + return $this->listNamespaceNames(); + } + + /** + * {@inheritDoc} + */ + public function listSchemaNames(): array + { + return $this->_conn->fetchFirstColumn( + <<<'SQL' +SELECT schema_name +FROM information_schema.schemata +WHERE schema_name NOT LIKE 'pg\_%' +AND schema_name != 'information_schema' +SQL, + ); + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getSchemaSearchPaths() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4821', + 'PostgreSQLSchemaManager::getSchemaSearchPaths() is deprecated.', + ); + + $params = $this->_conn->getParams(); + + $searchPaths = $this->_conn->fetchOne('SHOW search_path'); + assert($searchPaths !== false); + + $schema = explode(',', $searchPaths); + + if (isset($params['user'])) { + $schema = str_replace('"$user"', $params['user'], $schema); + } + + return array_map('trim', $schema); + } + + /** + * Gets names of all existing schemas in the current users search path. + * + * This is a PostgreSQL only function. + * + * @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy. + * + * @return string[] + * + * @throws Exception + */ + public function getExistingSchemaSearchPaths() + { + if ($this->existingSchemaPaths === null) { + $this->determineExistingSchemaSearchPaths(); + } + + assert($this->existingSchemaPaths !== null); + + return $this->existingSchemaPaths; + } + + /** + * Returns the name of the current schema. + * + * @return string|null + * + * @throws Exception + */ + protected function getCurrentSchema() + { + $schemas = $this->getExistingSchemaSearchPaths(); + + return array_shift($schemas); + } + + /** + * Sets or resets the order of the existing schemas in the current search path of the user. + * + * This is a PostgreSQL only function. + * + * @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy. + * + * @return void + * + * @throws Exception + */ + public function determineExistingSchemaSearchPaths() + { + $names = $this->listSchemaNames(); + $paths = $this->getSchemaSearchPaths(); + + $this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names): bool { + return in_array($v, $names, true); + }); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $onUpdate = null; + $onDelete = null; + + if ( + preg_match( + '(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', + $tableForeignKey['condef'], + $match, + ) === 1 + ) { + $onUpdate = $match[1]; + } + + if ( + preg_match( + '(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', + $tableForeignKey['condef'], + $match, + ) === 1 + ) { + $onDelete = $match[1]; + } + + $result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values); + assert($result === 1); + + // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get + // the idea to trim them here. + $localColumns = array_map('trim', explode(',', $values[1])); + $foreignColumns = array_map('trim', explode(',', $values[3])); + $foreignTable = $values[2]; + + return new ForeignKeyConstraint( + $localColumns, + $foreignTable, + $foreignColumns, + $tableForeignKey['conname'], + ['onUpdate' => $onUpdate, 'onDelete' => $onDelete], + ); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableDefinition($table) + { + $currentSchema = $this->getCurrentSchema(); + + if ($table['schema_name'] === $currentSchema) { + return $table['table_name']; + } + + return $table['schema_name'] . '.' . $table['table_name']; + } + + /** + * {@inheritDoc} + * + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + $buffer = []; + foreach ($tableIndexes as $row) { + $colNumbers = array_map('intval', explode(' ', $row['indkey'])); + $columnNameSql = sprintf( + 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC', + $row['indrelid'], + implode(' ,', $colNumbers), + ); + + $indexColumns = $this->_conn->fetchAllAssociative($columnNameSql); + + // required for getting the order of the columns right. + foreach ($colNumbers as $colNum) { + foreach ($indexColumns as $colRow) { + if ($colNum !== $colRow['attnum']) { + continue; + } + + $buffer[] = [ + 'key_name' => $row['relname'], + 'column_name' => trim($colRow['attname']), + 'non_unique' => ! $row['indisunique'], + 'primary' => $row['indisprimary'], + 'where' => $row['where'], + ]; + } + } + } + + return parent::_getPortableTableIndexesList($buffer, $tableName); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['datname']; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see listSchemaNames()} instead. + */ + protected function getPortableNamespaceDefinition(array $namespace) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'PostgreSQLSchemaManager::getPortableNamespaceDefinition() is deprecated,' + . ' use PostgreSQLSchemaManager::listSchemaNames() instead.', + ); + + return $namespace['nspname']; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + if ($sequence['schemaname'] !== 'public') { + $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname']; + } else { + $sequenceName = $sequence['relname']; + } + + return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') { + // get length from varchar definition + $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); + $tableColumn['length'] = $length; + } + + $matches = []; + + $autoincrement = false; + + if ( + $tableColumn['default'] !== null + && preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches) === 1 + ) { + $tableColumn['sequence'] = $matches[1]; + $tableColumn['default'] = null; + $autoincrement = true; + } + + if ($tableColumn['default'] !== null) { + if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) { + $tableColumn['default'] = $matches[1]; + } elseif (preg_match('/^NULL::/', $tableColumn['default']) === 1) { + $tableColumn['default'] = null; + } + } + + $length = $tableColumn['length'] ?? null; + if ($length === '-1' && isset($tableColumn['atttypmod'])) { + $length = $tableColumn['atttypmod'] - 4; + } + + if ((int) $length <= 0) { + $length = null; + } + + $fixed = null; + + if (! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $precision = null; + $scale = null; + $jsonb = null; + + $dbType = strtolower($tableColumn['type']); + if ( + $tableColumn['domain_type'] !== null + && $tableColumn['domain_type'] !== '' + && ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type']) + ) { + $dbType = strtolower($tableColumn['domain_type']); + $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + + switch ($dbType) { + case 'smallint': + case 'int2': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + $length = null; + break; + + case 'int': + case 'int4': + case 'integer': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + $length = null; + break; + + case 'bigint': + case 'int8': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + $length = null; + break; + + case 'bool': + case 'boolean': + if ($tableColumn['default'] === 'true') { + $tableColumn['default'] = true; + } + + if ($tableColumn['default'] === 'false') { + $tableColumn['default'] = false; + } + + $length = null; + break; + + case 'json': + case 'text': + case '_varchar': + case 'varchar': + $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']); + $fixed = false; + break; + case 'interval': + $fixed = false; + break; + + case 'char': + case 'bpchar': + $fixed = true; + break; + + case 'float': + case 'float4': + case 'float8': + case 'double': + case 'double precision': + case 'real': + case 'decimal': + case 'money': + case 'numeric': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + + if ( + preg_match( + '([A-Za-z]+\(([0-9]+),([0-9]+)\))', + $tableColumn['complete_type'], + $match, + ) === 1 + ) { + $precision = $match[1]; + $scale = $match[2]; + $length = null; + } + + break; + + case 'year': + $length = null; + break; + + // PostgreSQL 9.4+ only + case 'jsonb': + $jsonb = true; + break; + } + + if ( + $tableColumn['default'] !== null && preg_match( + "('([^']+)'::)", + $tableColumn['default'], + $match, + ) === 1 + ) { + $tableColumn['default'] = $match[1]; + } + + $options = [ + 'length' => $length, + 'notnull' => (bool) $tableColumn['isnotnull'], + 'default' => $tableColumn['default'], + 'precision' => $precision, + 'scale' => $scale, + 'fixed' => $fixed, + 'autoincrement' => $autoincrement, + 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' + ? $tableColumn['comment'] + : null, + ]; + + $column = new Column($tableColumn['field'], Type::getType($type), $options); + + if (! empty($tableColumn['collation'])) { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + if ($column->getType()->getName() === Types::JSON) { + if (! $column->getType() instanceof JsonType) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5049', + <<<'DEPRECATION' + %s not extending %s while being named %s is deprecated, + and will lead to jsonb never to being used in 4.0., + DEPRECATION, + get_class($column->getType()), + JsonType::class, + Types::JSON, + ); + } + + $column->setPlatformOption('jsonb', $jsonb); + } + + return $column; + } + + /** + * PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually. + * + * @param mixed $defaultValue + * + * @return mixed + */ + private function fixVersion94NegativeNumericDefaultValue($defaultValue) + { + if ($defaultValue !== null && strpos($defaultValue, '(') === 0) { + return trim($defaultValue, '()'); + } + + return $defaultValue; + } + + /** + * Parses a default value expression as given by PostgreSQL + */ + private function parseDefaultExpression(?string $default): ?string + { + if ($default === null) { + return $default; + } + + return str_replace("''", "'", $default); + } + + protected function selectTableNames(string $databaseName): Result + { + $sql = <<<'SQL' +SELECT quote_ident(table_name) AS table_name, + table_schema AS schema_name +FROM information_schema.tables +WHERE table_catalog = ? + AND table_schema NOT LIKE 'pg\_%' + AND table_schema != 'information_schema' + AND table_name != 'geometry_columns' + AND table_name != 'spatial_ref_sys' + AND table_type = 'BASE TABLE' +SQL; + + return $this->_conn->executeQuery($sql, [$databaseName]); + } + + protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' c.relname AS table_name, n.nspname AS schema_name,'; + } + + $sql .= sprintf(<<<'SQL' + a.attnum, + quote_ident(a.attname) AS field, + t.typname AS type, + format_type(a.atttypid, a.atttypmod) AS complete_type, + (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation, + (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, + (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM + pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, + a.attnotnull AS isnotnull, + (SELECT 't' + FROM pg_index + WHERE c.oid = pg_index.indrelid + AND pg_index.indkey[0] = a.attnum + AND pg_index.indisprimary = 't' + ) AS pri, + (%s) AS default, + (SELECT pg_description.description + FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid + ) AS comment + FROM pg_attribute a + INNER JOIN pg_class c + ON c.oid = a.attrelid + INNER JOIN pg_type t + ON t.oid = a.atttypid + INNER JOIN pg_namespace n + ON n.oid = c.relnamespace + LEFT JOIN pg_depend d + ON d.objid = c.oid + AND d.deptype = 'e' + AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') +SQL, $this->_platform->getDefaultColumnValueSQLSnippet()); + + $conditions = array_merge([ + 'a.attnum > 0', + "c.relkind = 'r'", + 'd.refobjid IS NULL', + ], $this->buildQueryConditions($tableName)); + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum'; + + return $this->_conn->executeQuery($sql); + } + + protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; + } + + $sql .= <<<'SQL' + quote_ident(ic.relname) AS relname, + i.indisunique, + i.indisprimary, + i.indkey, + i.indrelid, + pg_get_expr(indpred, indrelid) AS "where" + FROM pg_index i + JOIN pg_class AS tc ON tc.oid = i.indrelid + JOIN pg_namespace tn ON tn.oid = tc.relnamespace + JOIN pg_class AS ic ON ic.oid = i.indexrelid + WHERE ic.oid IN ( + SELECT indexrelid + FROM pg_index i, pg_class c, pg_namespace n +SQL; + + $conditions = array_merge([ + 'c.oid = i.indrelid', + 'c.relnamespace = n.oid', + ], $this->buildQueryConditions($tableName)); + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ')'; + + return $this->_conn->executeQuery($sql); + } + + protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; + } + + $sql .= <<<'SQL' + quote_ident(r.conname) as conname, + pg_get_constraintdef(r.oid, true) as condef + FROM pg_constraint r + JOIN pg_class AS tc ON tc.oid = r.conrelid + JOIN pg_namespace tn ON tn.oid = tc.relnamespace + WHERE r.conrelid IN + ( + SELECT c.oid + FROM pg_class c, pg_namespace n +SQL; + + $conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName)); + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'"; + + return $this->_conn->executeQuery($sql); + } + + /** + * {@inheritDoc} + */ + protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array + { + $sql = <<<'SQL' +SELECT c.relname, + CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, + obj_description(c.oid, 'pg_class') AS comment +FROM pg_class c + INNER JOIN pg_namespace n + ON n.oid = c.relnamespace +SQL; + + $conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName)); + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + return $this->_conn->fetchAllAssociativeIndexed($sql); + } + + /** + * @param string|null $tableName + * + * @return list + */ + private function buildQueryConditions($tableName): array + { + $conditions = []; + + if ($tableName !== null) { + if (strpos($tableName, '.') !== false) { + [$schemaName, $tableName] = explode('.', $tableName); + $conditions[] = 'n.nspname = ' . $this->_platform->quoteStringLiteral($schemaName); + } else { + $conditions[] = 'n.nspname = ANY(current_schemas(false))'; + } + + $identifier = new Identifier($tableName); + $conditions[] = 'c.relname = ' . $this->_platform->quoteStringLiteral($identifier->getName()); + } + + $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; + + return $conditions; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php b/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php new file mode 100644 index 0000000..acef511 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php @@ -0,0 +1,611 @@ + + */ +class SQLServerSchemaManager extends AbstractSchemaManager +{ + private ?string $databaseCollation = null; + + /** + * {@inheritDoc} + */ + public function listTableNames() + { + return $this->doListTableNames(); + } + + /** + * {@inheritDoc} + */ + public function listTables() + { + return $this->doListTables(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see introspectTable()} instead. + */ + public function listTableDetails($name) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5595', + '%s is deprecated. Use introspectTable() instead.', + __METHOD__, + ); + + return $this->doListTableDetails($name); + } + + /** + * {@inheritDoc} + */ + public function listTableColumns($table, $database = null) + { + return $this->doListTableColumns($table, $database); + } + + /** + * {@inheritDoc} + */ + public function listTableIndexes($table) + { + return $this->doListTableIndexes($table); + } + + /** + * {@inheritDoc} + */ + public function listTableForeignKeys($table, $database = null) + { + return $this->doListTableForeignKeys($table, $database); + } + + /** + * {@inheritDoc} + */ + public function listSchemaNames(): array + { + return $this->_conn->fetchFirstColumn( + <<<'SQL' +SELECT name +FROM sys.schemas +WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys') +SQL, + ); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + return new Sequence($sequence['name'], (int) $sequence['increment'], (int) $sequence['start_value']); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $dbType = strtok($tableColumn['type'], '(), '); + assert(is_string($dbType)); + + $fixed = null; + $length = (int) $tableColumn['length']; + $default = $tableColumn['default']; + + if (! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + if ($default !== null) { + $default = $this->parseDefaultExpression($default); + } + + switch ($dbType) { + case 'nchar': + case 'ntext': + // Unicode data requires 2 bytes per character + $length /= 2; + break; + + case 'nvarchar': + if ($length === -1) { + break; + } + + // Unicode data requires 2 bytes per character + $length /= 2; + break; + + case 'varchar': + // TEXT type is returned as VARCHAR(MAX) with a length of -1 + if ($length === -1) { + $dbType = 'text'; + } + + break; + + case 'varbinary': + if ($length === -1) { + $dbType = 'blob'; + } + + break; + } + + if ($dbType === 'char' || $dbType === 'nchar' || $dbType === 'binary') { + $fixed = true; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + + $options = [ + 'unsigned' => false, + 'fixed' => (bool) $fixed, + 'default' => $default, + 'notnull' => (bool) $tableColumn['notnull'], + 'scale' => $tableColumn['scale'], + 'precision' => $tableColumn['precision'], + 'autoincrement' => (bool) $tableColumn['autoincrement'], + 'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, + ]; + + if ($length !== 0 && ($type === 'text' || $type === 'string' || $type === 'binary')) { + $options['length'] = $length; + } + + $column = new Column($tableColumn['name'], Type::getType($type), $options); + + if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + return $column; + } + + private function parseDefaultExpression(string $value): ?string + { + while (preg_match('/^\((.*)\)$/s', $value, $matches)) { + $value = $matches[1]; + } + + if ($value === 'NULL') { + return null; + } + + if (preg_match('/^\'(.*)\'$/s', $value, $matches) === 1) { + $value = str_replace("''", "'", $matches[1]); + } + + if ($value === 'getdate()') { + return $this->_platform->getCurrentTimestampSQL(); + } + + return $value; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $foreignKeys = []; + + foreach ($tableForeignKeys as $tableForeignKey) { + $name = $tableForeignKey['ForeignKey']; + + if (! isset($foreignKeys[$name])) { + $foreignKeys[$name] = [ + 'local_columns' => [$tableForeignKey['ColumnName']], + 'foreign_table' => $tableForeignKey['ReferenceTableName'], + 'foreign_columns' => [$tableForeignKey['ReferenceColumnName']], + 'name' => $name, + 'options' => [ + 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), + 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']), + ], + ]; + } else { + $foreignKeys[$name]['local_columns'][] = $tableForeignKey['ColumnName']; + $foreignKeys[$name]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName']; + } + } + + return parent::_getPortableTableForeignKeysList($foreignKeys); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + foreach ($tableIndexes as &$tableIndex) { + $tableIndex['non_unique'] = (bool) $tableIndex['non_unique']; + $tableIndex['primary'] = (bool) $tableIndex['primary']; + $tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null; + } + + return parent::_getPortableTableIndexesList($tableIndexes, $tableName); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return new ForeignKeyConstraint( + $tableForeignKey['local_columns'], + $tableForeignKey['foreign_table'], + $tableForeignKey['foreign_columns'], + $tableForeignKey['name'], + $tableForeignKey['options'], + ); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableDefinition($table) + { + if ($table['schema_name'] !== 'dbo') { + return $table['schema_name'] . '.' . $table['table_name']; + } + + return $table['table_name']; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['name']; + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see listSchemaNames()} instead. + */ + protected function getPortableNamespaceDefinition(array $namespace) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4503', + 'SQLServerSchemaManager::getPortableNamespaceDefinition() is deprecated,' + . ' use SQLServerSchemaManager::listSchemaNames() instead.', + ); + + return $namespace['name']; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableViewDefinition($view) + { + // @todo + return new View($view['name'], $view['definition']); + } + + /** + * {@inheritDoc} + */ + public function alterTable(TableDiff $tableDiff) + { + $droppedColumns = $tableDiff->getDroppedColumns(); + + if (count($droppedColumns) > 0) { + $tableName = ($tableDiff->getOldTable() ?? $tableDiff->getName($this->_platform))->getName(); + + foreach ($droppedColumns as $col) { + foreach ($this->getColumnConstraints($tableName, $col->getName()) as $constraint) { + $this->_conn->executeStatement( + sprintf( + 'ALTER TABLE %s DROP CONSTRAINT %s', + $tableName, + $constraint, + ), + ); + } + } + } + + parent::alterTable($tableDiff); + } + + /** + * Returns the names of the constraints for a given column. + * + * @return iterable + * + * @throws Exception + */ + private function getColumnConstraints(string $table, string $column): iterable + { + return $this->_conn->iterateColumn( + <<<'SQL' +SELECT o.name +FROM sys.objects o + INNER JOIN sys.objects t + ON t.object_id = o.parent_object_id + AND t.type = 'U' + INNER JOIN sys.default_constraints dc + ON dc.object_id = o.object_id + INNER JOIN sys.columns c + ON c.column_id = dc.parent_column_id + AND c.object_id = t.object_id +WHERE t.name = ? + AND c.name = ? +SQL + , + [$table, $column], + ); + } + + /** @throws Exception */ + public function createComparator(): Comparator + { + return new SQLServer\Comparator($this->_platform, $this->getDatabaseCollation()); + } + + /** @throws Exception */ + private function getDatabaseCollation(): string + { + if ($this->databaseCollation === null) { + $databaseCollation = $this->_conn->fetchOne( + 'SELECT collation_name FROM sys.databases WHERE name = ' + . $this->_platform->getCurrentDatabaseExpression(), + ); + + // a database is always selected, even if omitted in the connection parameters + assert(is_string($databaseCollation)); + + $this->databaseCollation = $databaseCollation; + } + + return $this->databaseCollation; + } + + protected function selectTableNames(string $databaseName): Result + { + // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams + $sql = <<<'SQL' +SELECT name AS table_name, + SCHEMA_NAME(schema_id) AS schema_name +FROM sys.objects +WHERE type = 'U' + AND name != 'sysdiagrams' +ORDER BY name +SQL; + + return $this->_conn->executeQuery($sql); + } + + protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' obj.name AS table_name, scm.name AS schema_name,'; + } + + $sql .= <<<'SQL' + col.name, + type.name AS type, + col.max_length AS length, + ~col.is_nullable AS notnull, + def.definition AS [default], + col.scale, + col.precision, + col.is_identity AS autoincrement, + col.collation_name AS collation, + -- CAST avoids driver error for sql_variant type + CAST(prop.value AS NVARCHAR(MAX)) AS comment + FROM sys.columns AS col + JOIN sys.types AS type + ON col.user_type_id = type.user_type_id + JOIN sys.objects AS obj + ON col.object_id = obj.object_id + JOIN sys.schemas AS scm + ON obj.schema_id = scm.schema_id + LEFT JOIN sys.default_constraints def + ON col.default_object_id = def.object_id + AND col.object_id = def.parent_object_id + LEFT JOIN sys.extended_properties AS prop + ON obj.object_id = prop.major_id + AND col.column_id = prop.minor_id + AND prop.name = 'MS_Description' +SQL; + + // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams + $conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"]; + $params = []; + + if ($tableName !== null) { + $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name'); + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; + } + + $sql .= <<<'SQL' + idx.name AS key_name, + col.name AS column_name, + ~idx.is_unique AS non_unique, + idx.is_primary_key AS [primary], + CASE idx.type + WHEN '1' THEN 'clustered' + WHEN '2' THEN 'nonclustered' + ELSE NULL + END AS flags + FROM sys.tables AS tbl + JOIN sys.schemas AS scm + ON tbl.schema_id = scm.schema_id + JOIN sys.indexes AS idx + ON tbl.object_id = idx.object_id + JOIN sys.index_columns AS idxcol + ON idx.object_id = idxcol.object_id + AND idx.index_id = idxcol.index_id + JOIN sys.columns AS col + ON idxcol.object_id = col.object_id + AND idxcol.column_id = col.column_id +SQL; + + $conditions = []; + $params = []; + + if ($tableName !== null) { + $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); + $sql .= ' WHERE ' . implode(' AND ', $conditions); + } + + $sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; + } + + $sql .= <<<'SQL' + f.name AS ForeignKey, + SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, + OBJECT_NAME (f.parent_object_id) AS TableName, + COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, + SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, + OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, + COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, + f.delete_referential_action_desc, + f.update_referential_action_desc + FROM sys.foreign_keys AS f + INNER JOIN sys.foreign_key_columns AS fc + INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id + ON f.OBJECT_ID = fc.constraint_object_id +SQL; + + $conditions = []; + $params = []; + + if ($tableName !== null) { + $conditions[] = $this->getTableWhereClause( + $tableName, + 'SCHEMA_NAME(f.schema_id)', + 'OBJECT_NAME(f.parent_object_id)', + ); + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + } + + $sql .= ' ORDER BY fc.constraint_column_id'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * {@inheritDoc} + */ + protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array + { + $sql = <<<'SQL' + SELECT + tbl.name, + p.value AS [table_comment] + FROM + sys.tables AS tbl + INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 +SQL; + + $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"]; + $params = []; + + if ($tableName !== null) { + $conditions[] = "tbl.name = N'" . $tableName . "'"; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + /** @var array> $metadata */ + $metadata = $this->_conn->executeQuery($sql, $params) + ->fetchAllAssociativeIndexed(); + + $tableOptions = []; + foreach ($metadata as $table => $data) { + $data = array_change_key_case($data, CASE_LOWER); + + $tableOptions[$table] = [ + 'comment' => $data['table_comment'], + ]; + } + + return $tableOptions; + } + + /** + * Returns the where clause to filter schema and table name in a query. + * + * @param string $table The full qualified name of the table. + * @param string $schemaColumn The name of the column to compare the schema to in the where clause. + * @param string $tableColumn The name of the column to compare the table to in the where clause. + */ + private function getTableWhereClause($table, $schemaColumn, $tableColumn): string + { + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table); + $schema = $this->_platform->quoteStringLiteral($schema); + $table = $this->_platform->quoteStringLiteral($table); + } else { + $schema = 'SCHEMA_NAME()'; + $table = $this->_platform->quoteStringLiteral($table); + } + + return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Schema.php b/vendor/doctrine/dbal/src/Schema/Schema.php new file mode 100644 index 0000000..703c83c --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Schema.php @@ -0,0 +1,523 @@ +_schemaConfig = $schemaConfig; + $this->_setName($schemaConfig->getName() ?? 'public'); + + foreach ($namespaces as $namespace) { + $this->createNamespace($namespace); + } + + foreach ($tables as $table) { + $this->_addTable($table); + } + + foreach ($sequences as $sequence) { + $this->_addSequence($sequence); + } + } + + /** + * @deprecated + * + * @return bool + */ + public function hasExplicitForeignKeyIndexes() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4822', + 'Schema::hasExplicitForeignKeyIndexes() is deprecated.', + ); + + return $this->_schemaConfig->hasExplicitForeignKeyIndexes(); + } + + /** + * @return void + * + * @throws SchemaException + */ + protected function _addTable(Table $table) + { + $namespaceName = $table->getNamespaceName(); + $tableName = $this->normalizeName($table); + + if (isset($this->_tables[$tableName])) { + throw SchemaException::tableAlreadyExists($tableName); + } + + if ( + $namespaceName !== null + && ! $table->isInDefaultNamespace($this->getName()) + && ! $this->hasNamespace($namespaceName) + ) { + $this->createNamespace($namespaceName); + } + + $this->_tables[$tableName] = $table; + $table->setSchemaConfig($this->_schemaConfig); + } + + /** + * @return void + * + * @throws SchemaException + */ + protected function _addSequence(Sequence $sequence) + { + $namespaceName = $sequence->getNamespaceName(); + $seqName = $this->normalizeName($sequence); + + if (isset($this->_sequences[$seqName])) { + throw SchemaException::sequenceAlreadyExists($seqName); + } + + if ( + $namespaceName !== null + && ! $sequence->isInDefaultNamespace($this->getName()) + && ! $this->hasNamespace($namespaceName) + ) { + $this->createNamespace($namespaceName); + } + + $this->_sequences[$seqName] = $sequence; + } + + /** + * Returns the namespaces of this schema. + * + * @return string[] A list of namespace names. + */ + public function getNamespaces() + { + return $this->namespaces; + } + + /** + * Gets all tables of this schema. + * + * @return Table[] + */ + public function getTables() + { + return $this->_tables; + } + + /** + * @param string $name + * + * @return Table + * + * @throws SchemaException + */ + public function getTable($name) + { + $name = $this->getFullQualifiedAssetName($name); + if (! isset($this->_tables[$name])) { + throw SchemaException::tableDoesNotExist($name); + } + + return $this->_tables[$name]; + } + + /** @param string $name */ + private function getFullQualifiedAssetName($name): string + { + $name = $this->getUnquotedAssetName($name); + + if (strpos($name, '.') === false) { + $name = $this->getName() . '.' . $name; + } + + return strtolower($name); + } + + private function normalizeName(AbstractAsset $asset): string + { + return $asset->getFullQualifiedName($this->getName()); + } + + /** + * Returns the unquoted representation of a given asset name. + * + * @param string $assetName Quoted or unquoted representation of an asset name. + */ + private function getUnquotedAssetName($assetName): string + { + if ($this->isIdentifierQuoted($assetName)) { + return $this->trimQuotes($assetName); + } + + return $assetName; + } + + /** + * Does this schema have a namespace with the given name? + * + * @param string $name + * + * @return bool + */ + public function hasNamespace($name) + { + $name = strtolower($this->getUnquotedAssetName($name)); + + return isset($this->namespaces[$name]); + } + + /** + * Does this schema have a table with the given name? + * + * @param string $name + * + * @return bool + */ + public function hasTable($name) + { + $name = $this->getFullQualifiedAssetName($name); + + return isset($this->_tables[$name]); + } + + /** + * Gets all table names, prefixed with a schema name, even the default one if present. + * + * @deprecated Use {@see getTables()} and {@see Table::getName()} instead. + * + * @return string[] + */ + public function getTableNames() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4800', + 'Schema::getTableNames() is deprecated.' + . ' Use Schema::getTables() and Table::getName() instead.', + __METHOD__, + ); + + return array_keys($this->_tables); + } + + /** + * @param string $name + * + * @return bool + */ + public function hasSequence($name) + { + $name = $this->getFullQualifiedAssetName($name); + + return isset($this->_sequences[$name]); + } + + /** + * @param string $name + * + * @return Sequence + * + * @throws SchemaException + */ + public function getSequence($name) + { + $name = $this->getFullQualifiedAssetName($name); + if (! $this->hasSequence($name)) { + throw SchemaException::sequenceDoesNotExist($name); + } + + return $this->_sequences[$name]; + } + + /** @return Sequence[] */ + public function getSequences() + { + return $this->_sequences; + } + + /** + * Creates a new namespace. + * + * @param string $name The name of the namespace to create. + * + * @return Schema This schema instance. + * + * @throws SchemaException + */ + public function createNamespace($name) + { + $unquotedName = strtolower($this->getUnquotedAssetName($name)); + + if (isset($this->namespaces[$unquotedName])) { + throw SchemaException::namespaceAlreadyExists($unquotedName); + } + + $this->namespaces[$unquotedName] = $name; + + return $this; + } + + /** + * Creates a new table. + * + * @param string $name + * + * @return Table + * + * @throws SchemaException + */ + public function createTable($name) + { + $table = new Table($name); + $this->_addTable($table); + + foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) { + $table->addOption($option, $value); + } + + return $table; + } + + /** + * Renames a table. + * + * @param string $oldName + * @param string $newName + * + * @return Schema + * + * @throws SchemaException + */ + public function renameTable($oldName, $newName) + { + $table = $this->getTable($oldName); + $table->_setName($newName); + + $this->dropTable($oldName); + $this->_addTable($table); + + return $this; + } + + /** + * Drops a table from the schema. + * + * @param string $name + * + * @return Schema + * + * @throws SchemaException + */ + public function dropTable($name) + { + $name = $this->getFullQualifiedAssetName($name); + $this->getTable($name); + unset($this->_tables[$name]); + + return $this; + } + + /** + * Creates a new sequence. + * + * @param string $name + * @param int $allocationSize + * @param int $initialValue + * + * @return Sequence + * + * @throws SchemaException + */ + public function createSequence($name, $allocationSize = 1, $initialValue = 1) + { + $seq = new Sequence($name, $allocationSize, $initialValue); + $this->_addSequence($seq); + + return $seq; + } + + /** + * @param string $name + * + * @return Schema + */ + public function dropSequence($name) + { + $name = $this->getFullQualifiedAssetName($name); + unset($this->_sequences[$name]); + + return $this; + } + + /** + * Returns an array of necessary SQL queries to create the schema on the given platform. + * + * @return list + * + * @throws Exception + */ + public function toSql(AbstractPlatform $platform) + { + $builder = new CreateSchemaObjectsSQLBuilder($platform); + + return $builder->buildSQL($this); + } + + /** + * Return an array of necessary SQL queries to drop the schema on the given platform. + * + * @return list + * + * @throws Exception + */ + public function toDropSql(AbstractPlatform $platform) + { + $builder = new DropSchemaObjectsSQLBuilder($platform); + + return $builder->buildSQL($this); + } + + /** + * @deprecated + * + * @return string[] + * + * @throws SchemaException + */ + public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) + { + $schemaDiff = (new Comparator())->compareSchemas($this, $toSchema); + + return $schemaDiff->toSql($platform); + } + + /** + * @deprecated + * + * @return string[] + * + * @throws SchemaException + */ + public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) + { + $schemaDiff = (new Comparator())->compareSchemas($fromSchema, $this); + + return $schemaDiff->toSql($platform); + } + + /** + * @deprecated + * + * @return void + */ + public function visit(Visitor $visitor) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5435', + 'Schema::visit() is deprecated.', + ); + + $visitor->acceptSchema($this); + + if ($visitor instanceof NamespaceVisitor) { + foreach ($this->namespaces as $namespace) { + $visitor->acceptNamespace($namespace); + } + } + + foreach ($this->_tables as $table) { + $table->visit($visitor); + } + + foreach ($this->_sequences as $sequence) { + $sequence->visit($visitor); + } + } + + /** + * Cloning a Schema triggers a deep clone of all related assets. + * + * @return void + */ + public function __clone() + { + foreach ($this->_tables as $k => $table) { + $this->_tables[$k] = clone $table; + } + + foreach ($this->_sequences as $k => $sequence) { + $this->_sequences[$k] = clone $sequence; + } + } +} diff --git a/vendor/doctrine/dbal/src/Schema/SchemaConfig.php b/vendor/doctrine/dbal/src/Schema/SchemaConfig.php new file mode 100644 index 0000000..5e39451 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SchemaConfig.php @@ -0,0 +1,120 @@ +hasExplicitForeignKeyIndexes; + } + + /** + * @deprecated + * + * @param bool $flag + * + * @return void + */ + public function setExplicitForeignKeyIndexes($flag) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4822', + 'SchemaConfig::setExplicitForeignKeyIndexes() is deprecated.', + ); + + $this->hasExplicitForeignKeyIndexes = (bool) $flag; + } + + /** + * @param int $length + * + * @return void + */ + public function setMaxIdentifierLength($length) + { + $this->maxIdentifierLength = (int) $length; + } + + /** @return int */ + public function getMaxIdentifierLength() + { + return $this->maxIdentifierLength; + } + + /** + * Gets the default namespace of schema objects. + * + * @return string|null + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the default namespace name of schema objects. + * + * @param string $name The value to set. + * + * @return void + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the default options that are passed to Table instances created with + * Schema#createTable(). + * + * @return mixed[] + */ + public function getDefaultTableOptions() + { + return $this->defaultTableOptions; + } + + /** + * @param mixed[] $defaultTableOptions + * + * @return void + */ + public function setDefaultTableOptions(array $defaultTableOptions) + { + $this->defaultTableOptions = $defaultTableOptions; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/SchemaDiff.php b/vendor/doctrine/dbal/src/Schema/SchemaDiff.php new file mode 100644 index 0000000..a6a8665 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SchemaDiff.php @@ -0,0 +1,294 @@ + $createdSchemas + * @param array $droppedSchemas + * @param array $createdSequences + * @param array $alteredSequences + * @param array $droppedSequences + */ + public function __construct( + $newTables = [], + $changedTables = [], + $removedTables = [], + ?Schema $fromSchema = null, + $createdSchemas = [], + $droppedSchemas = [], + $createdSequences = [], + $alteredSequences = [], + $droppedSequences = [] + ) { + $this->newTables = $newTables; + + $this->changedTables = array_filter($changedTables, static function (TableDiff $diff): bool { + return ! $diff->isEmpty(); + }); + + $this->removedTables = $removedTables; + $this->fromSchema = $fromSchema; + $this->newNamespaces = $createdSchemas; + $this->removedNamespaces = $droppedSchemas; + $this->newSequences = $createdSequences; + $this->changedSequences = $alteredSequences; + $this->removedSequences = $droppedSequences; + } + + /** @return array */ + public function getCreatedSchemas(): array + { + return $this->newNamespaces; + } + + /** @return array */ + public function getDroppedSchemas(): array + { + return $this->removedNamespaces; + } + + /** @return array
*/ + public function getCreatedTables(): array + { + return $this->newTables; + } + + /** @return array */ + public function getAlteredTables(): array + { + return $this->changedTables; + } + + /** @return array
*/ + public function getDroppedTables(): array + { + return $this->removedTables; + } + + /** @return array */ + public function getCreatedSequences(): array + { + return $this->newSequences; + } + + /** @return array */ + public function getAlteredSequences(): array + { + return $this->changedSequences; + } + + /** @return array */ + public function getDroppedSequences(): array + { + return $this->removedSequences; + } + + /** + * Returns whether the diff is empty (contains no changes). + */ + public function isEmpty(): bool + { + return count($this->newNamespaces) === 0 + && count($this->removedNamespaces) === 0 + && count($this->newTables) === 0 + && count($this->changedTables) === 0 + && count($this->removedTables) === 0 + && count($this->newSequences) === 0 + && count($this->changedSequences) === 0 + && count($this->removedSequences) === 0; + } + + /** + * The to save sql mode ensures that the following things don't happen: + * + * 1. Tables are deleted + * 2. Sequences are deleted + * 3. Foreign Keys which reference tables that would otherwise be deleted. + * + * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all. + * + * @deprecated + * + * @return list + */ + public function toSaveSql(AbstractPlatform $platform) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5766', + '%s is deprecated.', + __METHOD__, + ); + + return $this->_toSql($platform, true); + } + + /** + * @deprecated Use {@link AbstractPlatform::getAlterSchemaSQL()} instead. + * + * @return list + */ + public function toSql(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5766', + '%s is deprecated. Use AbstractPlatform::getAlterSchemaSQL() instead.', + __METHOD__, + ); + + return $this->_toSql($platform, false); + } + + /** + * @param bool $saveMode + * + * @return list + */ + protected function _toSql(AbstractPlatform $platform, $saveMode = false) + { + $sql = []; + + if ($platform->supportsSchemas()) { + foreach ($this->getCreatedSchemas() as $schema) { + $sql[] = $platform->getCreateSchemaSQL($schema); + } + } + + if ($platform->supportsForeignKeyConstraints() && $saveMode === false) { + foreach ($this->orphanedForeignKeys as $orphanedForeignKey) { + $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable()); + } + } + + if ($platform->supportsSequences() === true) { + foreach ($this->getAlteredSequences() as $sequence) { + $sql[] = $platform->getAlterSequenceSQL($sequence); + } + + if ($saveMode === false) { + foreach ($this->getDroppedSequences() as $sequence) { + $sql[] = $platform->getDropSequenceSQL($sequence); + } + } + + foreach ($this->getCreatedSequences() as $sequence) { + $sql[] = $platform->getCreateSequenceSQL($sequence); + } + } + + $sql = array_merge($sql, $platform->getCreateTablesSQL($this->getCreatedTables())); + + if ($saveMode === false) { + $sql = array_merge($sql, $platform->getDropTablesSQL($this->getDroppedTables())); + } + + foreach ($this->getAlteredTables() as $tableDiff) { + $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff)); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/SchemaException.php b/vendor/doctrine/dbal/src/Schema/SchemaException.php new file mode 100644 index 0000000..4ec091f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SchemaException.php @@ -0,0 +1,204 @@ +_setName($name); + $this->setAllocationSize($allocationSize); + $this->setInitialValue($initialValue); + $this->cache = $cache; + } + + /** @return int */ + public function getAllocationSize() + { + return $this->allocationSize; + } + + /** @return int */ + public function getInitialValue() + { + return $this->initialValue; + } + + /** @return int|null */ + public function getCache() + { + return $this->cache; + } + + /** + * @param int $allocationSize + * + * @return Sequence + */ + public function setAllocationSize($allocationSize) + { + if ($allocationSize > 0) { + $this->allocationSize = $allocationSize; + } else { + $this->allocationSize = 1; + } + + return $this; + } + + /** + * @param int $initialValue + * + * @return Sequence + */ + public function setInitialValue($initialValue) + { + if ($initialValue > 0) { + $this->initialValue = $initialValue; + } else { + $this->initialValue = 1; + } + + return $this; + } + + /** + * @param int $cache + * + * @return Sequence + */ + public function setCache($cache) + { + $this->cache = $cache; + + return $this; + } + + /** + * Checks if this sequence is an autoincrement sequence for a given table. + * + * This is used inside the comparator to not report sequences as missing, + * when the "from" schema implicitly creates the sequences. + * + * @return bool + */ + public function isAutoIncrementsFor(Table $table) + { + $primaryKey = $table->getPrimaryKey(); + + if ($primaryKey === null) { + return false; + } + + $pkColumns = $primaryKey->getColumns(); + + if (count($pkColumns) !== 1) { + return false; + } + + $column = $table->getColumn($pkColumns[0]); + + if (! $column->getAutoincrement()) { + return false; + } + + $sequenceName = $this->getShortestName($table->getNamespaceName()); + $tableName = $table->getShortestName($table->getNamespaceName()); + $tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($table->getNamespaceName())); + + return $tableSequenceName === $sequenceName; + } + + /** + * @deprecated + * + * @return void + */ + public function visit(Visitor $visitor) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5435', + 'Sequence::visit() is deprecated.', + ); + + $visitor->acceptSequence($this); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/SqliteSchemaManager.php b/vendor/doctrine/dbal/src/Schema/SqliteSchemaManager.php new file mode 100644 index 0000000..0419e93 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SqliteSchemaManager.php @@ -0,0 +1,790 @@ + + */ +class SqliteSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritDoc} + */ + public function listTableNames() + { + return $this->doListTableNames(); + } + + /** + * {@inheritDoc} + */ + public function listTables() + { + return $this->doListTables(); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see introspectTable()} instead. + */ + public function listTableDetails($name) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5595', + '%s is deprecated. Use introspectTable() instead.', + __METHOD__, + ); + + return $this->doListTableDetails($name); + } + + /** + * {@inheritDoc} + */ + public function listTableColumns($table, $database = null) + { + return $this->doListTableColumns($table, $database); + } + + /** + * {@inheritDoc} + */ + public function listTableIndexes($table) + { + return $this->doListTableIndexes($table); + } + + /** + * {@inheritDoc} + */ + protected function fetchForeignKeyColumnsByTable(string $databaseName): array + { + $columnsByTable = parent::fetchForeignKeyColumnsByTable($databaseName); + + if (count($columnsByTable) > 0) { + foreach ($columnsByTable as $table => $columns) { + $columnsByTable[$table] = $this->addDetailsToTableForeignKeyColumns($table, $columns); + } + } + + return $columnsByTable; + } + + /** + * {@inheritDoc} + * + * @deprecated Delete the database file using the filesystem. + */ + public function dropDatabase($database) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4963', + 'SqliteSchemaManager::dropDatabase() is deprecated. Delete the database file using the filesystem.', + ); + + if (! file_exists($database)) { + return; + } + + unlink($database); + } + + /** + * {@inheritDoc} + * + * @deprecated The engine will create the database file automatically. + */ + public function createDatabase($database) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4963', + 'SqliteSchemaManager::createDatabase() is deprecated.' + . ' The engine will create the database file automatically.', + ); + + $params = $this->_conn->getParams(); + + $params['path'] = $database; + unset($params['memory']); + + $conn = DriverManager::getConnection($params); + $conn->connect(); + $conn->close(); + } + + /** + * {@inheritDoc} + */ + public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + if (! $table instanceof Table) { + $table = $this->listTableDetails($table); + } + + $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [$foreignKey])); + } + + /** + * {@inheritDoc} + * + * @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead. + */ + public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4897', + 'SqliteSchemaManager::dropAndCreateForeignKey() is deprecated.' + . ' Use SqliteSchemaManager::dropForeignKey() and SqliteSchemaManager::createForeignKey() instead.', + ); + + if (! $table instanceof Table) { + $table = $this->listTableDetails($table); + } + + $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [$foreignKey])); + } + + /** + * {@inheritDoc} + */ + public function dropForeignKey($foreignKey, $table) + { + if (! $table instanceof Table) { + $table = $this->listTableDetails($table); + } + + $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [], [$foreignKey])); + } + + /** + * {@inheritDoc} + */ + public function listTableForeignKeys($table, $database = null) + { + $table = $this->normalizeName($table); + + $columns = $this->selectForeignKeyColumns($database ?? 'main', $table) + ->fetchAllAssociative(); + + if (count($columns) > 0) { + $columns = $this->addDetailsToTableForeignKeyColumns($table, $columns); + } + + return $this->_getPortableTableForeignKeysList($columns); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['table_name']; + } + + /** + * {@inheritDoc} + * + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + $indexBuffer = []; + + // fetch primary + $indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_TABLE_INFO (?)', [$tableName]); + + usort( + $indexArray, + /** + * @param array $a + * @param array $b + */ + static function (array $a, array $b): int { + if ($a['pk'] === $b['pk']) { + return $a['cid'] - $b['cid']; + } + + return $a['pk'] - $b['pk']; + }, + ); + + foreach ($indexArray as $indexColumnRow) { + if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') { + continue; + } + + $indexBuffer[] = [ + 'key_name' => 'primary', + 'primary' => true, + 'non_unique' => false, + 'column_name' => $indexColumnRow['name'], + ]; + } + + // fetch regular indexes + foreach ($tableIndexes as $tableIndex) { + // Ignore indexes with reserved names, e.g. autoindexes + if (strpos($tableIndex['name'], 'sqlite_') === 0) { + continue; + } + + $keyName = $tableIndex['name']; + $idx = []; + $idx['key_name'] = $keyName; + $idx['primary'] = false; + $idx['non_unique'] = ! $tableIndex['unique']; + + $indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_INDEX_INFO (?)', [$keyName]); + + foreach ($indexArray as $indexColumnRow) { + $idx['column_name'] = $indexColumnRow['name']; + $indexBuffer[] = $idx; + } + } + + return parent::_getPortableTableIndexesList($indexBuffer, $tableName); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableColumnList($table, $database, $tableColumns) + { + $list = parent::_getPortableTableColumnList($table, $database, $tableColumns); + + // find column with autoincrement + $autoincrementColumn = null; + $autoincrementCount = 0; + + foreach ($tableColumns as $tableColumn) { + if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') { + continue; + } + + $autoincrementCount++; + if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') { + continue; + } + + $autoincrementColumn = $tableColumn['name']; + } + + if ($autoincrementCount === 1 && $autoincrementColumn !== null) { + foreach ($list as $column) { + if ($autoincrementColumn !== $column->getName()) { + continue; + } + + $column->setAutoincrement(true); + } + } + + // inspect column collation and comments + $createSql = $this->getCreateTableSQL($table); + + foreach ($list as $columnName => $column) { + $type = $column->getType(); + + if ($type instanceof StringType || $type instanceof TextType) { + $column->setPlatformOption( + 'collation', + $this->parseColumnCollationFromSQL($columnName, $createSql) ?? 'BINARY', + ); + } + + $comment = $this->parseColumnCommentFromSQL($columnName, $createSql); + + if ($comment === null) { + continue; + } + + $type = $this->extractDoctrineTypeFromComment($comment, ''); + + if ($type !== '') { + $column->setType(Type::getType($type)); + + $comment = $this->removeDoctrineTypeFromComment($comment, $type); + } + + $column->setComment($comment); + } + + return $list; + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $parts = explode('(', $tableColumn['type']); + $tableColumn['type'] = trim($parts[0]); + if (isset($parts[1])) { + $length = trim($parts[1], ')'); + $tableColumn['length'] = $length; + } + + $dbType = strtolower($tableColumn['type']); + $length = $tableColumn['length'] ?? null; + $unsigned = false; + + if (strpos($dbType, ' unsigned') !== false) { + $dbType = str_replace(' unsigned', '', $dbType); + $unsigned = true; + } + + $fixed = false; + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $default = $tableColumn['dflt_value']; + if ($default === 'NULL') { + $default = null; + } + + if ($default !== null) { + // SQLite returns the default value as a literal expression, so we need to parse it + if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { + $default = str_replace("''", "'", $matches[1]); + } + } + + $notnull = (bool) $tableColumn['notnull']; + + if (! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $precision = null; + $scale = null; + + switch ($dbType) { + case 'char': + $fixed = true; + break; + case 'float': + case 'double': + case 'real': + case 'decimal': + case 'numeric': + if (isset($tableColumn['length'])) { + if (strpos($tableColumn['length'], ',') === false) { + $tableColumn['length'] .= ',0'; + } + + [$precision, $scale] = array_map('trim', explode(',', $tableColumn['length'])); + } + + $length = null; + break; + } + + $options = [ + 'length' => $length, + 'unsigned' => $unsigned, + 'fixed' => $fixed, + 'notnull' => $notnull, + 'default' => $default, + 'precision' => $precision, + 'scale' => $scale, + ]; + + return new Column($tableColumn['name'], Type::getType($type), $options); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['name'], $view['sql']); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = []; + foreach ($tableForeignKeys as $value) { + $value = array_change_key_case($value, CASE_LOWER); + $id = $value['id']; + if (! isset($list[$id])) { + if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') { + $value['on_delete'] = null; + } + + if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') { + $value['on_update'] = null; + } + + $list[$id] = [ + 'name' => $value['constraint_name'], + 'local' => [], + 'foreign' => [], + 'foreignTable' => $value['table'], + 'onDelete' => $value['on_delete'], + 'onUpdate' => $value['on_update'], + 'deferrable' => $value['deferrable'], + 'deferred' => $value['deferred'], + ]; + } + + $list[$id]['local'][] = $value['from']; + + if ($value['to'] === null) { + // Inferring a shorthand form for the foreign key constraint, where the "to" field is empty. + // @see https://www.sqlite.org/foreignkeys.html#fk_indexes. + $foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['table']); + + if (! isset($foreignTableIndexes['primary'])) { + continue; + } + + $list[$id]['foreign'] = [...$list[$id]['foreign'], ...$foreignTableIndexes['primary']->getColumns()]; + + continue; + } + + $list[$id]['foreign'][] = $value['to']; + } + + return parent::_getPortableTableForeignKeysList($list); + } + + /** + * {@inheritDoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint + { + return new ForeignKeyConstraint( + $tableForeignKey['local'], + $tableForeignKey['foreignTable'], + $tableForeignKey['foreign'], + $tableForeignKey['name'], + [ + 'onDelete' => $tableForeignKey['onDelete'], + 'onUpdate' => $tableForeignKey['onUpdate'], + 'deferrable' => $tableForeignKey['deferrable'], + 'deferred' => $tableForeignKey['deferred'], + ], + ); + } + + private function parseColumnCollationFromSQL(string $column, string $sql): ?string + { + $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' + . preg_quote($this->_platform->quoteSingleIdentifier($column)) + . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is'; + + if (preg_match($pattern, $sql, $match) !== 1) { + return null; + } + + return $match[1]; + } + + private function parseTableCommentFromSQL(string $table, string $sql): ?string + { + $pattern = '/\s* # Allow whitespace characters at start of line +CREATE\sTABLE # Match "CREATE TABLE" +(?:\W"' . preg_quote($this->_platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/') + . '\W) # Match table name (quoted and unquoted) +( # Start capture + (?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s) +)/ix'; + + if (preg_match($pattern, $sql, $match) !== 1) { + return null; + } + + $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); + + return $comment === '' ? null : $comment; + } + + private function parseColumnCommentFromSQL(string $column, string $sql): ?string + { + $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) + . '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i'; + + if (preg_match($pattern, $sql, $match) !== 1) { + return null; + } + + $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); + + return $comment === '' ? null : $comment; + } + + /** @throws Exception */ + private function getCreateTableSQL(string $table): string + { + $sql = $this->_conn->fetchOne( + <<<'SQL' +SELECT sql + FROM ( + SELECT * + FROM sqlite_master + UNION ALL + SELECT * + FROM sqlite_temp_master + ) +WHERE type = 'table' +AND name = ? +SQL + , + [$table], + ); + + if ($sql !== false) { + return $sql; + } + + return ''; + } + + /** + * @param list> $columns + * + * @return list> + * + * @throws Exception + */ + private function addDetailsToTableForeignKeyColumns(string $table, array $columns): array + { + $foreignKeyDetails = $this->getForeignKeyDetails($table); + $foreignKeyCount = count($foreignKeyDetails); + + foreach ($columns as $i => $column) { + // SQLite identifies foreign keys in reverse order of appearance in SQL + $columns[$i] = array_merge($column, $foreignKeyDetails[$foreignKeyCount - $column['id'] - 1]); + } + + return $columns; + } + + /** + * @param string $table + * + * @return list> + * + * @throws Exception + */ + private function getForeignKeyDetails($table) + { + $createSql = $this->getCreateTableSQL($table); + + if ( + preg_match_all( + '# + (?:CONSTRAINT\s+(\S+)\s+)? + (?:FOREIGN\s+KEY[^)]+\)\s*)? + REFERENCES\s+\S+\s*(?:\([^)]+\))? + (?: + [^,]*? + (NOT\s+DEFERRABLE|DEFERRABLE) + (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))? + )?#isx', + $createSql, + $match, + ) === 0 + ) { + return []; + } + + $names = $match[1]; + $deferrable = $match[2]; + $deferred = $match[3]; + $details = []; + + for ($i = 0, $count = count($match[0]); $i < $count; $i++) { + $details[] = [ + 'constraint_name' => isset($names[$i]) && $names[$i] !== '' ? $names[$i] : null, + 'deferrable' => isset($deferrable[$i]) && strcasecmp($deferrable[$i], 'deferrable') === 0, + 'deferred' => isset($deferred[$i]) && strcasecmp($deferred[$i], 'deferred') === 0, + ]; + } + + return $details; + } + + public function createComparator(): Comparator + { + return new SQLite\Comparator($this->_platform); + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function getSchemaSearchPaths() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4821', + 'SqliteSchemaManager::getSchemaSearchPaths() is deprecated.', + ); + + // SQLite does not support schemas or databases + return []; + } + + protected function selectTableNames(string $databaseName): Result + { + $sql = <<<'SQL' +SELECT name AS table_name +FROM sqlite_master +WHERE type = 'table' + AND name != 'sqlite_sequence' + AND name != 'geometry_columns' + AND name != 'spatial_ref_sys' +UNION ALL +SELECT name +FROM sqlite_temp_master +WHERE type = 'table' +ORDER BY name +SQL; + + return $this->_conn->executeQuery($sql); + } + + protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = <<<'SQL' + SELECT t.name AS table_name, + c.* + FROM sqlite_master t + JOIN pragma_table_info(t.name) c +SQL; + + $conditions = [ + "t.type = 'table'", + "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", + ]; + $params = []; + + if ($tableName !== null) { + $conditions[] = 't.name = ?'; + $params[] = str_replace('.', '__', $tableName); + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = <<<'SQL' + SELECT t.name AS table_name, + i.* + FROM sqlite_master t + JOIN pragma_index_list(t.name) i +SQL; + + $conditions = [ + "t.type = 'table'", + "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", + ]; + $params = []; + + if ($tableName !== null) { + $conditions[] = 't.name = ?'; + $params[] = str_replace('.', '__', $tableName); + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq'; + + return $this->_conn->executeQuery($sql, $params); + } + + protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = <<<'SQL' + SELECT t.name AS table_name, + p.* + FROM sqlite_master t + JOIN pragma_foreign_key_list(t.name) p + ON p."seq" != '-1' +SQL; + + $conditions = [ + "t.type = 'table'", + "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", + ]; + $params = []; + + if ($tableName !== null) { + $conditions[] = 't.name = ?'; + $params[] = str_replace('.', '__', $tableName); + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * {@inheritDoc} + */ + protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array + { + if ($tableName === null) { + $tables = $this->listTableNames(); + } else { + $tables = [$tableName]; + } + + $tableOptions = []; + foreach ($tables as $table) { + $comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table)); + + if ($comment === null) { + continue; + } + + $tableOptions[$table]['comment'] = $comment; + } + + return $tableOptions; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Table.php b/vendor/doctrine/dbal/src/Schema/Table.php new file mode 100644 index 0000000..ce4cc32 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Table.php @@ -0,0 +1,1041 @@ + [], + ]; + + /** @var SchemaConfig|null */ + protected $_schemaConfig; + + /** @var Index[] */ + private array $implicitIndexes = []; + + /** + * @param Column[] $columns + * @param Index[] $indexes + * @param UniqueConstraint[] $uniqueConstraints + * @param ForeignKeyConstraint[] $fkConstraints + * @param mixed[] $options + * + * @throws SchemaException + * @throws Exception + */ + public function __construct( + string $name, + array $columns = [], + array $indexes = [], + array $uniqueConstraints = [], + array $fkConstraints = [], + array $options = [] + ) { + if ($name === '') { + throw InvalidTableName::new($name); + } + + $this->_setName($name); + + foreach ($columns as $column) { + $this->_addColumn($column); + } + + foreach ($indexes as $idx) { + $this->_addIndex($idx); + } + + foreach ($uniqueConstraints as $uniqueConstraint) { + $this->_addUniqueConstraint($uniqueConstraint); + } + + foreach ($fkConstraints as $constraint) { + $this->_addForeignKeyConstraint($constraint); + } + + $this->_options = array_merge($this->_options, $options); + } + + /** @return void */ + public function setSchemaConfig(SchemaConfig $schemaConfig) + { + $this->_schemaConfig = $schemaConfig; + } + + /** @return int */ + protected function _getMaxIdentifierLength() + { + if ($this->_schemaConfig instanceof SchemaConfig) { + return $this->_schemaConfig->getMaxIdentifierLength(); + } + + return 63; + } + + /** + * Sets the Primary Key. + * + * @param string[] $columnNames + * @param string|false $indexName + * + * @return self + * + * @throws SchemaException + */ + public function setPrimaryKey(array $columnNames, $indexName = false) + { + if ($indexName === false) { + $indexName = 'primary'; + } + + $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true)); + + foreach ($columnNames as $columnName) { + $column = $this->getColumn($columnName); + $column->setNotnull(true); + } + + return $this; + } + + /** + * @param string[] $columnNames + * @param string[] $flags + * @param mixed[] $options + * + * @return self + * + * @throws SchemaException + */ + public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []) + { + $indexName ??= $this->_generateIdentifierName( + array_merge([$this->getName()], $columnNames), + 'idx', + $this->_getMaxIdentifierLength(), + ); + + return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options)); + } + + /** + * @param string[] $columnNames + * @param string[] $flags + * @param mixed[] $options + * + * @return self + */ + public function addUniqueConstraint( + array $columnNames, + ?string $indexName = null, + array $flags = [], + array $options = [] + ): Table { + $indexName ??= $this->_generateIdentifierName( + array_merge([$this->getName()], $columnNames), + 'uniq', + $this->_getMaxIdentifierLength(), + ); + + return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options)); + } + + /** + * Drops the primary key from this table. + * + * @return void + * + * @throws SchemaException + */ + public function dropPrimaryKey() + { + if ($this->_primaryKeyName === null) { + return; + } + + $this->dropIndex($this->_primaryKeyName); + $this->_primaryKeyName = null; + } + + /** + * Drops an index from this table. + * + * @param string $name The index name. + * + * @return void + * + * @throws SchemaException If the index does not exist. + */ + public function dropIndex($name) + { + $name = $this->normalizeIdentifier($name); + + if (! $this->hasIndex($name)) { + throw SchemaException::indexDoesNotExist($name, $this->_name); + } + + unset($this->_indexes[$name]); + } + + /** + * @param string[] $columnNames + * @param string|null $indexName + * @param mixed[] $options + * + * @return self + * + * @throws SchemaException + */ + public function addUniqueIndex(array $columnNames, $indexName = null, array $options = []) + { + $indexName ??= $this->_generateIdentifierName( + array_merge([$this->getName()], $columnNames), + 'uniq', + $this->_getMaxIdentifierLength(), + ); + + return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options)); + } + + /** + * Renames an index. + * + * @param string $oldName The name of the index to rename from. + * @param string|null $newName The name of the index to rename to. + * If null is given, the index name will be auto-generated. + * + * @return self This table instance. + * + * @throws SchemaException If no index exists for the given current name + * or if an index with the given new name already exists on this table. + */ + public function renameIndex($oldName, $newName = null) + { + $oldName = $this->normalizeIdentifier($oldName); + $normalizedNewName = $this->normalizeIdentifier($newName); + + if ($oldName === $normalizedNewName) { + return $this; + } + + if (! $this->hasIndex($oldName)) { + throw SchemaException::indexDoesNotExist($oldName, $this->_name); + } + + if ($this->hasIndex($normalizedNewName)) { + throw SchemaException::indexAlreadyExists($normalizedNewName, $this->_name); + } + + $oldIndex = $this->_indexes[$oldName]; + + if ($oldIndex->isPrimary()) { + $this->dropPrimaryKey(); + + return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? false); + } + + unset($this->_indexes[$oldName]); + + if ($oldIndex->isUnique()) { + return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions()); + } + + return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions()); + } + + /** + * Checks if an index begins in the order of the given columns. + * + * @param string[] $columnNames + * + * @return bool + */ + public function columnsAreIndexed(array $columnNames) + { + foreach ($this->getIndexes() as $index) { + if ($index->spansColumns($columnNames)) { + return true; + } + } + + return false; + } + + /** + * @param string[] $columnNames + * @param string $indexName + * @param bool $isUnique + * @param bool $isPrimary + * @param string[] $flags + * @param mixed[] $options + * + * @throws SchemaException + */ + private function _createIndex( + array $columnNames, + $indexName, + $isUnique, + $isPrimary, + array $flags = [], + array $options = [] + ): Index { + if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { + throw SchemaException::indexNameInvalid($indexName); + } + + foreach ($columnNames as $columnName) { + if (! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + } + + return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options); + } + + /** + * @param string $name + * @param string $typeName + * @param mixed[] $options + * + * @return Column + * + * @throws SchemaException + */ + public function addColumn($name, $typeName, array $options = []) + { + $column = new Column($name, Type::getType($typeName), $options); + + $this->_addColumn($column); + + return $column; + } + + /** + * Change Column Details. + * + * @deprecated Use {@link modifyColumn()} instead. + * + * @param string $name + * @param mixed[] $options + * + * @return self + * + * @throws SchemaException + */ + public function changeColumn($name, array $options) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5747', + '%s is deprecated. Use modifyColumn() instead.', + __METHOD__, + ); + + return $this->modifyColumn($name, $options); + } + + /** + * @param string $name + * @param mixed[] $options + * + * @return self + * + * @throws SchemaException + */ + public function modifyColumn($name, array $options) + { + $column = $this->getColumn($name); + $column->setOptions($options); + + return $this; + } + + /** + * Drops a Column from the Table. + * + * @param string $name + * + * @return self + */ + public function dropColumn($name) + { + $name = $this->normalizeIdentifier($name); + + unset($this->_columns[$name]); + + return $this; + } + + /** + * Adds a foreign key constraint. + * + * Name is inferred from the local columns. + * + * @param Table|string $foreignTable Table schema instance or table name + * @param string[] $localColumnNames + * @param string[] $foreignColumnNames + * @param mixed[] $options + * @param string|null $name + * + * @return self + * + * @throws SchemaException + */ + public function addForeignKeyConstraint( + $foreignTable, + array $localColumnNames, + array $foreignColumnNames, + array $options = [], + $name = null + ) { + $name ??= $this->_generateIdentifierName( + array_merge([$this->getName()], $localColumnNames), + 'fk', + $this->_getMaxIdentifierLength(), + ); + + if ($foreignTable instanceof Table) { + foreach ($foreignColumnNames as $columnName) { + if (! $foreignTable->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); + } + } + } + + foreach ($localColumnNames as $columnName) { + if (! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + } + + $constraint = new ForeignKeyConstraint( + $localColumnNames, + $foreignTable, + $foreignColumnNames, + $name, + $options, + ); + + return $this->_addForeignKeyConstraint($constraint); + } + + /** + * @param string $name + * @param mixed $value + * + * @return self + */ + public function addOption($name, $value) + { + $this->_options[$name] = $value; + + return $this; + } + + /** + * @return void + * + * @throws SchemaException + */ + protected function _addColumn(Column $column) + { + $columnName = $column->getName(); + $columnName = $this->normalizeIdentifier($columnName); + + if (isset($this->_columns[$columnName])) { + throw SchemaException::columnAlreadyExists($this->getName(), $columnName); + } + + $this->_columns[$columnName] = $column; + } + + /** + * Adds an index to the table. + * + * @return self + * + * @throws SchemaException + */ + protected function _addIndex(Index $indexCandidate) + { + $indexName = $indexCandidate->getName(); + $indexName = $this->normalizeIdentifier($indexName); + $replacedImplicitIndexes = []; + + foreach ($this->implicitIndexes as $name => $implicitIndex) { + if (! $implicitIndex->isFulfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) { + continue; + } + + $replacedImplicitIndexes[] = $name; + } + + if ( + (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) || + ($this->_primaryKeyName !== null && $indexCandidate->isPrimary()) + ) { + throw SchemaException::indexAlreadyExists($indexName, $this->_name); + } + + foreach ($replacedImplicitIndexes as $name) { + unset($this->_indexes[$name], $this->implicitIndexes[$name]); + } + + if ($indexCandidate->isPrimary()) { + $this->_primaryKeyName = $indexName; + } + + $this->_indexes[$indexName] = $indexCandidate; + + return $this; + } + + /** @return self */ + protected function _addUniqueConstraint(UniqueConstraint $constraint): Table + { + $mergedNames = array_merge([$this->getName()], $constraint->getColumns()); + $name = strlen($constraint->getName()) > 0 + ? $constraint->getName() + : $this->_generateIdentifierName($mergedNames, 'fk', $this->_getMaxIdentifierLength()); + + $name = $this->normalizeIdentifier($name); + + $this->uniqueConstraints[$name] = $constraint; + + // If there is already an index that fulfills this requirements drop the request. In the case of __construct + // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates. + // This creates computation overhead in this case, however no duplicate indexes are ever added (column based). + $indexName = $this->_generateIdentifierName($mergedNames, 'idx', $this->_getMaxIdentifierLength()); + + $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false); + + foreach ($this->_indexes as $existingIndex) { + if ($indexCandidate->isFulfilledBy($existingIndex)) { + return $this; + } + } + + $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; + + return $this; + } + + /** @return self */ + protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) + { + $constraint->setLocalTable($this); + + if (strlen($constraint->getName()) > 0) { + $name = $constraint->getName(); + } else { + $name = $this->_generateIdentifierName( + array_merge([$this->getName()], $constraint->getLocalColumns()), + 'fk', + $this->_getMaxIdentifierLength(), + ); + } + + $name = $this->normalizeIdentifier($name); + + $this->_fkConstraints[$name] = $constraint; + + /* Add an implicit index (defined by the DBAL) on the foreign key + columns. If there is already a user-defined index that fulfills these + requirements drop the request. In the case of __construct() calling + this method during hydration from schema-details, all the explicitly + added indexes lead to duplicates. This creates computation overhead in + this case, however no duplicate indexes are ever added (based on + columns). */ + $indexName = $this->_generateIdentifierName( + array_merge([$this->getName()], $constraint->getColumns()), + 'idx', + $this->_getMaxIdentifierLength(), + ); + + $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false); + + foreach ($this->_indexes as $existingIndex) { + if ($indexCandidate->isFulfilledBy($existingIndex)) { + return $this; + } + } + + $this->_addIndex($indexCandidate); + $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; + + return $this; + } + + /** + * Returns whether this table has a foreign key constraint with the given name. + * + * @param string $name + * + * @return bool + */ + public function hasForeignKey($name) + { + $name = $this->normalizeIdentifier($name); + + return isset($this->_fkConstraints[$name]); + } + + /** + * Returns the foreign key constraint with the given name. + * + * @param string $name The constraint name. + * + * @return ForeignKeyConstraint + * + * @throws SchemaException If the foreign key does not exist. + */ + public function getForeignKey($name) + { + $name = $this->normalizeIdentifier($name); + + if (! $this->hasForeignKey($name)) { + throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); + } + + return $this->_fkConstraints[$name]; + } + + /** + * Removes the foreign key constraint with the given name. + * + * @param string $name The constraint name. + * + * @return void + * + * @throws SchemaException + */ + public function removeForeignKey($name) + { + $name = $this->normalizeIdentifier($name); + + if (! $this->hasForeignKey($name)) { + throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); + } + + unset($this->_fkConstraints[$name]); + } + + /** + * Returns whether this table has a unique constraint with the given name. + */ + public function hasUniqueConstraint(string $name): bool + { + $name = $this->normalizeIdentifier($name); + + return isset($this->uniqueConstraints[$name]); + } + + /** + * Returns the unique constraint with the given name. + * + * @throws SchemaException If the unique constraint does not exist. + */ + public function getUniqueConstraint(string $name): UniqueConstraint + { + $name = $this->normalizeIdentifier($name); + + if (! $this->hasUniqueConstraint($name)) { + throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name); + } + + return $this->uniqueConstraints[$name]; + } + + /** + * Removes the unique constraint with the given name. + * + * @throws SchemaException If the unique constraint does not exist. + */ + public function removeUniqueConstraint(string $name): void + { + $name = $this->normalizeIdentifier($name); + + if (! $this->hasUniqueConstraint($name)) { + throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name); + } + + unset($this->uniqueConstraints[$name]); + } + + /** + * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest) + * + * @return Column[] + */ + public function getColumns() + { + $primaryKeyColumns = $this->getPrimaryKey() !== null ? $this->getPrimaryKeyColumns() : []; + $foreignKeyColumns = $this->getForeignKeyColumns(); + $remainderColumns = $this->filterColumns( + array_merge(array_keys($primaryKeyColumns), array_keys($foreignKeyColumns)), + true, + ); + + return array_merge($primaryKeyColumns, $foreignKeyColumns, $remainderColumns); + } + + /** + * Returns the foreign key columns + * + * @deprecated Use {@see getForeignKey()} and {@see ForeignKeyConstraint::getLocalColumns()} instead. + * + * @return Column[] + */ + public function getForeignKeyColumns() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5731', + '%s is deprecated. Use getForeignKey() and ForeignKeyConstraint::getLocalColumns() instead.', + __METHOD__, + ); + + $foreignKeyColumns = []; + + foreach ($this->getForeignKeys() as $foreignKey) { + $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getLocalColumns()); + } + + return $this->filterColumns($foreignKeyColumns); + } + + /** + * Returns only columns that have specified names + * + * @param string[] $columnNames + * + * @return Column[] + */ + private function filterColumns(array $columnNames, bool $reverse = false): array + { + return array_filter($this->_columns, static function (string $columnName) use ($columnNames, $reverse): bool { + return in_array($columnName, $columnNames, true) !== $reverse; + }, ARRAY_FILTER_USE_KEY); + } + + /** + * Returns whether this table has a Column with the given name. + * + * @param string $name The column name. + * + * @return bool + */ + public function hasColumn($name) + { + $name = $this->normalizeIdentifier($name); + + return isset($this->_columns[$name]); + } + + /** + * Returns the Column with the given name. + * + * @param string $name The column name. + * + * @return Column + * + * @throws SchemaException If the column does not exist. + */ + public function getColumn($name) + { + $name = $this->normalizeIdentifier($name); + + if (! $this->hasColumn($name)) { + throw SchemaException::columnDoesNotExist($name, $this->_name); + } + + return $this->_columns[$name]; + } + + /** + * Returns the primary key. + * + * @return Index|null The primary key, or null if this Table has no primary key. + */ + public function getPrimaryKey() + { + if ($this->_primaryKeyName !== null) { + return $this->getIndex($this->_primaryKeyName); + } + + return null; + } + + /** + * Returns the primary key columns. + * + * @deprecated Use {@see getPrimaryKey()} and {@see Index::getColumns()} instead. + * + * @return Column[] + * + * @throws Exception + */ + public function getPrimaryKeyColumns() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5731', + '%s is deprecated. Use getPrimaryKey() and Index::getColumns() instead.', + __METHOD__, + ); + + $primaryKey = $this->getPrimaryKey(); + + if ($primaryKey === null) { + throw new Exception('Table ' . $this->getName() . ' has no primary key.'); + } + + return $this->filterColumns($primaryKey->getColumns()); + } + + /** + * Returns whether this table has a primary key. + * + * @deprecated Use {@see getPrimaryKey()} instead. + * + * @return bool + */ + public function hasPrimaryKey() + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5731', + '%s is deprecated. Use getPrimaryKey() instead.', + __METHOD__, + ); + + return $this->_primaryKeyName !== null && $this->hasIndex($this->_primaryKeyName); + } + + /** + * Returns whether this table has an Index with the given name. + * + * @param string $name The index name. + * + * @return bool + */ + public function hasIndex($name) + { + $name = $this->normalizeIdentifier($name); + + return isset($this->_indexes[$name]); + } + + /** + * Returns the Index with the given name. + * + * @param string $name The index name. + * + * @return Index + * + * @throws SchemaException If the index does not exist. + */ + public function getIndex($name) + { + $name = $this->normalizeIdentifier($name); + if (! $this->hasIndex($name)) { + throw SchemaException::indexDoesNotExist($name, $this->_name); + } + + return $this->_indexes[$name]; + } + + /** @return Index[] */ + public function getIndexes() + { + return $this->_indexes; + } + + /** + * Returns the unique constraints. + * + * @return UniqueConstraint[] + */ + public function getUniqueConstraints(): array + { + return $this->uniqueConstraints; + } + + /** + * Returns the foreign key constraints. + * + * @return ForeignKeyConstraint[] + */ + public function getForeignKeys() + { + return $this->_fkConstraints; + } + + /** + * @param string $name + * + * @return bool + */ + public function hasOption($name) + { + return isset($this->_options[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getOption($name) + { + return $this->_options[$name]; + } + + /** @return mixed[] */ + public function getOptions() + { + return $this->_options; + } + + /** + * @deprecated + * + * @return void + * + * @throws SchemaException + */ + public function visit(Visitor $visitor) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5435', + 'Table::visit() is deprecated.', + ); + + $visitor->acceptTable($this); + + foreach ($this->getColumns() as $column) { + $visitor->acceptColumn($this, $column); + } + + foreach ($this->getIndexes() as $index) { + $visitor->acceptIndex($this, $index); + } + + foreach ($this->getForeignKeys() as $constraint) { + $visitor->acceptForeignKey($this, $constraint); + } + } + + /** + * Clone of a Table triggers a deep clone of all affected assets. + * + * @return void + */ + public function __clone() + { + foreach ($this->_columns as $k => $column) { + $this->_columns[$k] = clone $column; + } + + foreach ($this->_indexes as $k => $index) { + $this->_indexes[$k] = clone $index; + } + + foreach ($this->_fkConstraints as $k => $fk) { + $this->_fkConstraints[$k] = clone $fk; + $this->_fkConstraints[$k]->setLocalTable($this); + } + } + + /** + * @param string[] $columnNames + * @param string[] $flags + * @param mixed[] $options + * + * @throws SchemaException + */ + private function _createUniqueConstraint( + array $columnNames, + string $indexName, + array $flags = [], + array $options = [] + ): UniqueConstraint { + if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { + throw SchemaException::indexNameInvalid($indexName); + } + + foreach ($columnNames as $columnName) { + if (! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + } + + return new UniqueConstraint($indexName, $columnNames, $flags, $options); + } + + /** + * Normalizes a given identifier. + * + * Trims quotes and lowercases the given identifier. + * + * @return string The normalized identifier. + */ + private function normalizeIdentifier(?string $identifier): string + { + if ($identifier === null) { + return ''; + } + + return $this->trimQuotes(strtolower($identifier)); + } + + public function setComment(?string $comment): self + { + // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options. + $this->addOption('comment', $comment); + + return $this; + } + + public function getComment(): ?string + { + return $this->_options['comment'] ?? null; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/TableDiff.php b/vendor/doctrine/dbal/src/Schema/TableDiff.php new file mode 100644 index 0000000..9aaf9e7 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/TableDiff.php @@ -0,0 +1,361 @@ + $addedColumns + * @param array $modifiedColumns + * @param array $droppedColumns + * @param array $addedIndexes + * @param array $changedIndexes + * @param array $removedIndexes + * @param list $addedForeignKeys + * @param list $changedForeignKeys + * @param list $removedForeignKeys + * @param array $renamedColumns + * @param array $renamedIndexes + */ + public function __construct( + $tableName, + $addedColumns = [], + $modifiedColumns = [], + $droppedColumns = [], + $addedIndexes = [], + $changedIndexes = [], + $removedIndexes = [], + ?Table $fromTable = null, + $addedForeignKeys = [], + $changedForeignKeys = [], + $removedForeignKeys = [], + $renamedColumns = [], + $renamedIndexes = [] + ) { + $this->name = $tableName; + $this->addedColumns = $addedColumns; + $this->changedColumns = $modifiedColumns; + $this->renamedColumns = $renamedColumns; + $this->removedColumns = $droppedColumns; + $this->addedIndexes = $addedIndexes; + $this->changedIndexes = $changedIndexes; + $this->renamedIndexes = $renamedIndexes; + $this->removedIndexes = $removedIndexes; + $this->addedForeignKeys = $addedForeignKeys; + $this->changedForeignKeys = $changedForeignKeys; + $this->removedForeignKeys = $removedForeignKeys; + + if ($fromTable === null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5678', + 'Not passing the $fromTable to %s is deprecated.', + __METHOD__, + ); + } + + $this->fromTable = $fromTable; + } + + /** + * @deprecated Use {@see getOldTable()} instead. + * + * @param AbstractPlatform $platform The platform to use for retrieving this table diff's name. + * + * @return Identifier + */ + public function getName(AbstractPlatform $platform) + { + return new Identifier( + $this->fromTable instanceof Table ? $this->fromTable->getQuotedName($platform) : $this->name, + ); + } + + /** + * @deprecated Rename tables via {@link AbstractSchemaManager::renameTable()} instead. + * + * @return Identifier|false + */ + public function getNewName() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5663', + '%s is deprecated. Rename tables via AbstractSchemaManager::renameTable() instead.', + __METHOD__, + ); + + if ($this->newName === false) { + return false; + } + + return new Identifier($this->newName); + } + + public function getOldTable(): ?Table + { + return $this->fromTable; + } + + /** @return list */ + public function getAddedColumns(): array + { + return array_values($this->addedColumns); + } + + /** @return list */ + public function getModifiedColumns(): array + { + return array_values($this->changedColumns); + } + + /** @return list */ + public function getDroppedColumns(): array + { + return array_values($this->removedColumns); + } + + /** @return array */ + public function getRenamedColumns(): array + { + return $this->renamedColumns; + } + + /** @return list */ + public function getAddedIndexes(): array + { + return array_values($this->addedIndexes); + } + + /** + * @internal This method exists only for compatibility with the current implementation of schema managers + * that modify the diff while processing it. + */ + public function unsetAddedIndex(Index $index): void + { + $this->addedIndexes = array_filter( + $this->addedIndexes, + static function (Index $addedIndex) use ($index): bool { + return $addedIndex !== $index; + }, + ); + } + + /** @return array */ + public function getModifiedIndexes(): array + { + return array_values($this->changedIndexes); + } + + /** @return list */ + public function getDroppedIndexes(): array + { + return array_values($this->removedIndexes); + } + + /** + * @internal This method exists only for compatibility with the current implementation of schema managers + * that modify the diff while processing it. + */ + public function unsetDroppedIndex(Index $index): void + { + $this->removedIndexes = array_filter( + $this->removedIndexes, + static function (Index $removedIndex) use ($index): bool { + return $removedIndex !== $index; + }, + ); + } + + /** @return array */ + public function getRenamedIndexes(): array + { + return $this->renamedIndexes; + } + + /** @return list */ + public function getAddedForeignKeys(): array + { + return $this->addedForeignKeys; + } + + /** @return list */ + public function getModifiedForeignKeys(): array + { + return $this->changedForeignKeys; + } + + /** @return list */ + public function getDroppedForeignKeys(): array + { + return $this->removedForeignKeys; + } + + /** + * @internal This method exists only for compatibility with the current implementation of the schema comparator. + * + * @param ForeignKeyConstraint|string $foreignKey + */ + public function unsetDroppedForeignKey($foreignKey): void + { + $this->removedForeignKeys = array_filter( + $this->removedForeignKeys, + static function ($removedForeignKey) use ($foreignKey): bool { + return $removedForeignKey !== $foreignKey; + }, + ); + } + + /** + * Returns whether the diff is empty (contains no changes). + */ + public function isEmpty(): bool + { + return count($this->addedColumns) === 0 + && count($this->changedColumns) === 0 + && count($this->removedColumns) === 0 + && count($this->renamedColumns) === 0 + && count($this->addedIndexes) === 0 + && count($this->changedIndexes) === 0 + && count($this->removedIndexes) === 0 + && count($this->renamedIndexes) === 0 + && count($this->addedForeignKeys) === 0 + && count($this->changedForeignKeys) === 0 + && count($this->removedForeignKeys) === 0; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php b/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php new file mode 100644 index 0000000..f353f30 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php @@ -0,0 +1,154 @@ + Identifier) + * + * @var Identifier[] + */ + protected $columns = []; + + /** + * Platform specific flags. + * array($flagName => true) + * + * @var true[] + */ + protected $flags = []; + + /** + * Platform specific options. + * + * @var mixed[] + */ + private array $options; + + /** + * @param string[] $columns + * @param string[] $flags + * @param mixed[] $options + */ + public function __construct(string $name, array $columns, array $flags = [], array $options = []) + { + $this->_setName($name); + + $this->options = $options; + + foreach ($columns as $column) { + $this->addColumn($column); + } + + foreach ($flags as $flag) { + $this->addFlag($flag); + } + } + + /** + * {@inheritDoc} + */ + public function getColumns() + { + return array_keys($this->columns); + } + + /** + * {@inheritDoc} + */ + public function getQuotedColumns(AbstractPlatform $platform) + { + $columns = []; + + foreach ($this->columns as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** @return string[] */ + public function getUnquotedColumns(): array + { + return array_map([$this, 'trimQuotes'], $this->getColumns()); + } + + /** + * Returns platform specific flags for unique constraint. + * + * @return string[] + */ + public function getFlags(): array + { + return array_keys($this->flags); + } + + /** + * Adds flag for a unique constraint that translates to platform specific handling. + * + * @return $this + * + * @example $uniqueConstraint->addFlag('CLUSTERED') + */ + public function addFlag(string $flag): UniqueConstraint + { + $this->flags[strtolower($flag)] = true; + + return $this; + } + + /** + * Does this unique constraint have a specific flag? + */ + public function hasFlag(string $flag): bool + { + return isset($this->flags[strtolower($flag)]); + } + + /** + * Removes a flag. + */ + public function removeFlag(string $flag): void + { + unset($this->flags[strtolower($flag)]); + } + + /** + * Does this unique constraint have a specific option? + */ + public function hasOption(string $name): bool + { + return isset($this->options[strtolower($name)]); + } + + /** @return mixed */ + public function getOption(string $name) + { + return $this->options[strtolower($name)]; + } + + /** @return mixed[] */ + public function getOptions(): array + { + return $this->options; + } + + /** + * Adds a new column to the unique constraint. + */ + protected function addColumn(string $column): void + { + $this->columns[$column] = new Identifier($column); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/View.php b/vendor/doctrine/dbal/src/Schema/View.php new file mode 100644 index 0000000..b19f2ad --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/View.php @@ -0,0 +1,28 @@ +_setName($name); + $this->sql = $sql; + } + + /** @return string */ + public function getSql() + { + return $this->sql; + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Visitor/AbstractVisitor.php b/vendor/doctrine/dbal/src/Schema/Visitor/AbstractVisitor.php new file mode 100644 index 0000000..f8f3b58 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Visitor/AbstractVisitor.php @@ -0,0 +1,49 @@ +platform = $platform; + } + + /** + * {@inheritDoc} + */ + public function acceptNamespace($namespaceName) + { + if (! $this->platform->supportsSchemas()) { + return; + } + + $this->createNamespaceQueries[] = $this->platform->getCreateSchemaSQL($namespaceName); + } + + /** + * {@inheritDoc} + */ + public function acceptTable(Table $table) + { + $this->createTableQueries = array_merge($this->createTableQueries, $this->platform->getCreateTableSQL($table)); + } + + /** + * {@inheritDoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + if (! $this->platform->supportsForeignKeyConstraints()) { + return; + } + + $this->createFkConstraintQueries[] = $this->platform->getCreateForeignKeySQL($fkConstraint, $localTable); + } + + /** + * {@inheritDoc} + */ + public function acceptSequence(Sequence $sequence) + { + $this->createSequenceQueries[] = $this->platform->getCreateSequenceSQL($sequence); + } + + /** @return void */ + public function resetQueries() + { + $this->createNamespaceQueries = []; + $this->createTableQueries = []; + $this->createSequenceQueries = []; + $this->createFkConstraintQueries = []; + } + + /** + * Gets all queries collected so far. + * + * @return string[] + */ + public function getQueries() + { + return array_merge( + $this->createNamespaceQueries, + $this->createSequenceQueries, + $this->createTableQueries, + $this->createFkConstraintQueries, + ); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Visitor/DropSchemaSqlCollector.php b/vendor/doctrine/dbal/src/Schema/Visitor/DropSchemaSqlCollector.php new file mode 100644 index 0000000..ddec6b4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Visitor/DropSchemaSqlCollector.php @@ -0,0 +1,107 @@ +platform = $platform; + $this->initializeQueries(); + } + + /** + * {@inheritDoc} + */ + public function acceptTable(Table $table) + { + $this->tables->attach($table); + } + + /** + * {@inheritDoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + if (strlen($fkConstraint->getName()) === 0) { + throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint); + } + + $this->constraints->attach($fkConstraint, $localTable); + } + + /** + * {@inheritDoc} + */ + public function acceptSequence(Sequence $sequence) + { + $this->sequences->attach($sequence); + } + + /** @return void */ + public function clearQueries() + { + $this->initializeQueries(); + } + + /** @return string[] */ + public function getQueries() + { + $sql = []; + + foreach ($this->constraints as $fkConstraint) { + assert($fkConstraint instanceof ForeignKeyConstraint); + $localTable = $this->constraints[$fkConstraint]; + $sql[] = $this->platform->getDropForeignKeySQL( + $fkConstraint->getQuotedName($this->platform), + $localTable->getQuotedName($this->platform), + ); + } + + foreach ($this->sequences as $sequence) { + assert($sequence instanceof Sequence); + $sql[] = $this->platform->getDropSequenceSQL($sequence->getQuotedName($this->platform)); + } + + foreach ($this->tables as $table) { + assert($table instanceof Table); + $sql[] = $this->platform->getDropTableSQL($table->getQuotedName($this->platform)); + } + + return $sql; + } + + private function initializeQueries(): void + { + $this->constraints = new SplObjectStorage(); + $this->sequences = new SplObjectStorage(); + $this->tables = new SplObjectStorage(); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Visitor/Graphviz.php b/vendor/doctrine/dbal/src/Schema/Visitor/Graphviz.php new file mode 100644 index 0000000..5eff0d9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Visitor/Graphviz.php @@ -0,0 +1,164 @@ +output .= $this->createNodeRelation( + $fkConstraint->getLocalTableName() . ':col' . current($fkConstraint->getLocalColumns()) . ':se', + $fkConstraint->getForeignTableName() . ':col' . current($fkConstraint->getForeignColumns()) . ':se', + [ + 'dir' => 'back', + 'arrowtail' => 'dot', + 'arrowhead' => 'normal', + ], + ); + } + + /** + * {@inheritDoc} + */ + public function acceptSchema(Schema $schema) + { + $this->output = 'digraph "' . $schema->getName() . '" {' . "\n"; + $this->output .= 'splines = true;' . "\n"; + $this->output .= 'overlap = false;' . "\n"; + $this->output .= 'outputorder=edgesfirst;' . "\n"; + $this->output .= 'mindist = 0.6;' . "\n"; + $this->output .= 'sep = .2;' . "\n"; + } + + /** + * {@inheritDoc} + */ + public function acceptTable(Table $table) + { + $this->output .= $this->createNode( + $table->getName(), + [ + 'label' => $this->createTableLabel($table), + 'shape' => 'plaintext', + ], + ); + } + + private function createTableLabel(Table $table): string + { + // Start the table + $label = '<
'; + + // The title + $label .= ''; + + // The attributes block + foreach ($table->getColumns() as $column) { + $columnLabel = $column->getName(); + + $label .= '' + . '' + . '' + . ''; + } + + // End the table + $label .= '
' + . '' . $table->getName() . '
' + . '' . $columnLabel . '' + . '' + . '' + . strtolower($column->getType()->getName()) + . '' + . ''; + + $primaryKey = $table->getPrimaryKey(); + + if ($primaryKey !== null && in_array($column->getName(), $primaryKey->getColumns(), true)) { + $label .= "\xe2\x9c\xb7"; + } + + $label .= '
>'; + + return $label; + } + + /** + * @param string $name + * @param string[] $options + */ + private function createNode($name, $options): string + { + $node = $name . ' ['; + foreach ($options as $key => $value) { + $node .= $key . '=' . $value . ' '; + } + + $node .= "]\n"; + + return $node; + } + + /** + * @param string $node1 + * @param string $node2 + * @param string[] $options + */ + private function createNodeRelation($node1, $node2, $options): string + { + $relation = $node1 . ' -> ' . $node2 . ' ['; + foreach ($options as $key => $value) { + $relation .= $key . '=' . $value . ' '; + } + + $relation .= "]\n"; + + return $relation; + } + + /** + * Get Graphviz Output + * + * @return string + */ + public function getOutput() + { + return $this->output . '}'; + } + + /** + * Writes dot language output to a file. This should usually be a *.dot file. + * + * You have to convert the output into a viewable format. For example use "neato" on linux systems + * and execute: + * + * neato -Tpng -o er.png er.dot + * + * @param string $filename + * + * @return void + */ + public function write($filename) + { + file_put_contents($filename, $this->getOutput()); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Visitor/NamespaceVisitor.php b/vendor/doctrine/dbal/src/Schema/Visitor/NamespaceVisitor.php new file mode 100644 index 0000000..4438243 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Visitor/NamespaceVisitor.php @@ -0,0 +1,20 @@ +schema = $schema; + } + + /** + * {@inheritDoc} + */ + public function acceptTable(Table $table) + { + if ($this->schema === null) { + return; + } + + if ($table->isInDefaultNamespace($this->schema->getName())) { + return; + } + + $this->schema->dropTable($table->getName()); + } + + /** + * {@inheritDoc} + */ + public function acceptSequence(Sequence $sequence) + { + if ($this->schema === null) { + return; + } + + if ($sequence->isInDefaultNamespace($this->schema->getName())) { + return; + } + + $this->schema->dropSequence($sequence->getName()); + } + + /** + * {@inheritDoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + if ($this->schema === null) { + return; + } + + // The table may already be deleted in a previous + // RemoveNamespacedAssets#acceptTable call. Removing Foreign keys that + // point to nowhere. + if (! $this->schema->hasTable($fkConstraint->getForeignTableName())) { + $localTable->removeForeignKey($fkConstraint->getName()); + + return; + } + + $foreignTable = $this->schema->getTable($fkConstraint->getForeignTableName()); + if ($foreignTable->isInDefaultNamespace($this->schema->getName())) { + return; + } + + $localTable->removeForeignKey($fkConstraint->getName()); + } +} diff --git a/vendor/doctrine/dbal/src/Schema/Visitor/Visitor.php b/vendor/doctrine/dbal/src/Schema/Visitor/Visitor.php new file mode 100644 index 0000000..8b34864 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Visitor/Visitor.php @@ -0,0 +1,45 @@ +Statement for the given SQL and Connection. + * + * @internal The statement can be only instantiated by {@see Connection}. + * + * @param Connection $conn The connection for handling statement errors. + * @param Driver\Statement $statement The underlying driver-level statement. + * @param string $sql The SQL of the statement. + * + * @throws Exception + */ + public function __construct(Connection $conn, Driver\Statement $statement, string $sql) + { + $this->conn = $conn; + $this->stmt = $statement; + $this->sql = $sql; + $this->platform = $conn->getDatabasePlatform(); + } + + /** + * Binds a parameter value to the statement. + * + * The value can optionally be bound with a DBAL mapping type. + * If bound with a DBAL mapping type, the binding type is derived from the mapping + * type and the value undergoes the conversion routines of the mapping type before + * being bound. + * + * @param string|int $param The name or position of the parameter. + * @param mixed $value The value of the parameter. + * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. + * + * @return bool TRUE on success, FALSE on failure. + * + * @throws Exception + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + $this->params[$param] = $value; + $this->types[$param] = $type; + + $bindingType = ParameterType::STRING; + + if ($type !== null) { + if (is_string($type)) { + $type = Type::getType($type); + } + + $bindingType = $type; + + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->platform); + $bindingType = $type->getBindingType(); + } + } + + try { + return $this->stmt->bindValue($param, $value, $bindingType); + } catch (Driver\Exception $e) { + throw $this->conn->convertException($e); + } + } + + /** + * Binds a parameter to a value by reference. + * + * Binding a parameter by reference does not support DBAL mapping types. + * + * @deprecated Use {@see bindValue()} instead. + * + * @param string|int $param The name or position of the parameter. + * @param mixed $variable The reference to the variable to bind. + * @param int $type The binding type. + * @param int|null $length Must be specified when using an OUT bind + * so that PHP allocates enough memory to hold the returned value. + * + * @return bool TRUE on success, FALSE on failure. + * + * @throws Exception + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5563', + '%s is deprecated. Use bindValue() instead.', + __METHOD__, + ); + + $this->params[$param] = $variable; + $this->types[$param] = $type; + + try { + if (func_num_args() > 3) { + return $this->stmt->bindParam($param, $variable, $type, $length); + } + + return $this->stmt->bindParam($param, $variable, $type); + } catch (Driver\Exception $e) { + throw $this->conn->convertException($e); + } + } + + /** + * Executes the statement with the currently bound parameters. + * + * @deprecated Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead + * + * @param mixed[]|null $params + * + * @throws Exception + */ + public function execute($params = null): Result + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4580', + '%s() is deprecated, use Statement::executeQuery() or Statement::executeStatement() instead', + __METHOD__, + ); + + if ($params !== null) { + $this->params = $params; + } + + $logger = $this->conn->getConfiguration()->getSQLLogger(); + if ($logger !== null) { + $logger->startQuery($this->sql, $this->params, $this->types); + } + + try { + return new Result( + $this->stmt->execute($params), + $this->conn, + ); + } catch (Driver\Exception $ex) { + throw $this->conn->convertExceptionDuringQuery($ex, $this->sql, $this->params, $this->types); + } finally { + if ($logger !== null) { + $logger->stopQuery(); + } + } + } + + /** + * Executes the statement with the currently bound parameters and return result. + * + * @param mixed[] $params + * + * @throws Exception + */ + public function executeQuery(array $params = []): Result + { + if (func_num_args() > 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::executeQuery() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + } + + if ($params === []) { + $params = null; // Workaround as long execute() exists and used internally. + } + + return $this->execute($params); + } + + /** + * Executes the statement with the currently bound parameters and return affected rows. + * + * @param mixed[] $params + * + * @throws Exception + */ + public function executeStatement(array $params = []): int + { + if (func_num_args() > 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5556', + 'Passing $params to Statement::executeStatement() is deprecated. Bind parameters using' + . ' Statement::bindParam() or Statement::bindValue() instead.', + ); + } + + if ($params === []) { + $params = null; // Workaround as long execute() exists and used internally. + } + + return $this->execute($params)->rowCount(); + } + + /** + * Gets the wrapped driver statement. + * + * @return Driver\Statement + */ + public function getWrappedStatement() + { + return $this->stmt; + } +} diff --git a/vendor/doctrine/dbal/src/Tools/Console/Command/CommandCompatibility.php b/vendor/doctrine/dbal/src/Tools/Console/Command/CommandCompatibility.php new file mode 100644 index 0000000..562b5ce --- /dev/null +++ b/vendor/doctrine/dbal/src/Tools/Console/Command/CommandCompatibility.php @@ -0,0 +1,35 @@ +hasReturnType()) { + /** @internal */ + trait CommandCompatibility + { + protected function execute(InputInterface $input, OutputInterface $output): int + { + return $this->doExecute($input, $output); + } + } +} else { + /** @internal */ + trait CommandCompatibility + { + /** + * {@inheritDoc} + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + return $this->doExecute($input, $output); + } + } +} diff --git a/vendor/doctrine/dbal/src/Tools/Console/Command/ReservedWordsCommand.php b/vendor/doctrine/dbal/src/Tools/Console/Command/ReservedWordsCommand.php new file mode 100644 index 0000000..2204b2e --- /dev/null +++ b/vendor/doctrine/dbal/src/Tools/Console/Command/ReservedWordsCommand.php @@ -0,0 +1,219 @@ + */ + private array $keywordLists; + + private ConnectionProvider $connectionProvider; + + public function __construct(ConnectionProvider $connectionProvider) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5431', + 'ReservedWordsCommand is deprecated. Use database documentation instead.', + ); + + parent::__construct(); + + $this->connectionProvider = $connectionProvider; + + $this->keywordLists = [ + 'db2' => new DB2Keywords(), + 'mariadb102' => new MariaDb102Keywords(), + 'mysql' => new MySQLKeywords(), + 'mysql57' => new MySQL57Keywords(), + 'mysql80' => new MySQL80Keywords(), + 'mysql84' => new MySQL84Keywords(), + 'oracle' => new OracleKeywords(), + 'pgsql' => new PostgreSQL94Keywords(), + 'pgsql100' => new PostgreSQL100Keywords(), + 'sqlite' => new SQLiteKeywords(), + 'sqlserver' => new SQLServer2012Keywords(), + ]; + } + + /** + * Add or replace a keyword list. + */ + public function setKeywordList(string $name, KeywordList $keywordList): void + { + $this->keywordLists[$name] = $keywordList; + } + + /** + * If you want to add or replace a keywords list use this command. + * + * @param string $name + * @param class-string $class + * + * @return void + */ + public function setKeywordListClass($name, $class) + { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'ReservedWordsCommand::setKeywordListClass() is deprecated,' + . ' use ReservedWordsCommand::setKeywordList() instead.', + ); + + $this->keywordLists[$name] = new $class(); + } + + /** @return void */ + protected function configure() + { + $this + ->setName('dbal:reserved-words') + ->setDescription('Checks if the current database contains identifiers that are reserved.') + ->setDefinition([ + new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'), + new InputOption( + 'list', + 'l', + InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'Keyword-List name.', + ), + ]) + ->setHelp(<<<'EOT' +Checks if the current database contains tables and columns +with names that are identifiers in this dialect or in other SQL dialects. + +By default all supported platform keywords are checked: + + %command.full_name% + +If you want to check against specific dialects you can +pass them to the command: + + %command.full_name% -l mysql -l pgsql + +The following keyword lists are currently shipped with Doctrine: + + * db2 + * mariadb102 + * mysql + * mysql57 + * mysql80 + * mysql84 + * oracle + * pgsql + * pgsql100 + * sqlite + * sqlserver +EOT); + } + + /** @throws Exception */ + private function doExecute(InputInterface $input, OutputInterface $output): int + { + $output->writeln( + 'The dbal:reserved-words command is deprecated.' + . ' Use the documentation on the used database platform(s) instead.', + ); + $output->writeln(''); + + $conn = $this->getConnection($input); + + $keywordLists = $input->getOption('list'); + + if (is_string($keywordLists)) { + $keywordLists = [$keywordLists]; + } elseif (! is_array($keywordLists)) { + $keywordLists = []; + } + + if (count($keywordLists) === 0) { + $keywordLists = array_keys($this->keywordLists); + } + + $keywords = []; + foreach ($keywordLists as $keywordList) { + if (! isset($this->keywordLists[$keywordList])) { + throw new InvalidArgumentException( + "There exists no keyword list with name '" . $keywordList . "'. " . + 'Known lists: ' . implode(', ', array_keys($this->keywordLists)), + ); + } + + $keywords[] = $this->keywordLists[$keywordList]; + } + + $output->write( + 'Checking keyword violations for ' . implode(', ', $keywordLists) . '...', + true, + ); + + $schema = $conn->getSchemaManager()->introspectSchema(); + $visitor = new ReservedKeywordsValidator($keywords); + $schema->visit($visitor); + + $violations = $visitor->getViolations(); + if (count($violations) !== 0) { + $output->write( + 'There are ' . count($violations) . ' reserved keyword violations' + . ' in your database schema:', + true, + ); + + foreach ($violations as $violation) { + $output->write(' - ' . $violation, true); + } + + return 1; + } + + $output->write('No reserved keywords violations have been found!', true); + + return 0; + } + + private function getConnection(InputInterface $input): Connection + { + $connectionName = $input->getOption('connection'); + assert(is_string($connectionName) || $connectionName === null); + + if ($connectionName !== null) { + return $this->connectionProvider->getConnection($connectionName); + } + + return $this->connectionProvider->getDefaultConnection(); + } +} diff --git a/vendor/doctrine/dbal/src/Tools/Console/Command/RunSqlCommand.php b/vendor/doctrine/dbal/src/Tools/Console/Command/RunSqlCommand.php new file mode 100644 index 0000000..4e5681e --- /dev/null +++ b/vendor/doctrine/dbal/src/Tools/Console/Command/RunSqlCommand.php @@ -0,0 +1,120 @@ +connectionProvider = $connectionProvider; + } + + /** @return void */ + protected function configure() + { + $this + ->setName('dbal:run-sql') + ->setDescription('Executes arbitrary SQL directly from the command line.') + ->setDefinition([ + new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'), + new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'), + new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set (deprecated).'), + new InputOption('force-fetch', null, InputOption::VALUE_NONE, 'Forces fetching the result.'), + ]) + ->setHelp(<<<'EOT' +The %command.name% command executes the given SQL query and +outputs the results: + +php %command.full_name% "SELECT * FROM users" +EOT); + } + + /** @throws Exception */ + private function doExecute(InputInterface $input, OutputInterface $output): int + { + $conn = $this->getConnection($input); + $io = new SymfonyStyle($input, $output); + + $sql = $input->getArgument('sql'); + + if ($sql === null) { + throw new RuntimeException("Argument 'SQL' is required in order to execute this command correctly."); + } + + assert(is_string($sql)); + + if ($input->getOption('depth') !== null) { + $io->warning('Parameter "depth" is deprecated and has no effect anymore.'); + } + + $forceFetch = $input->getOption('force-fetch'); + assert(is_bool($forceFetch)); + + if (stripos($sql, 'select') === 0 || $forceFetch) { + $this->runQuery($io, $conn, $sql); + } else { + $this->runStatement($io, $conn, $sql); + } + + return 0; + } + + private function getConnection(InputInterface $input): Connection + { + $connectionName = $input->getOption('connection'); + assert(is_string($connectionName) || $connectionName === null); + + if ($connectionName !== null) { + return $this->connectionProvider->getConnection($connectionName); + } + + return $this->connectionProvider->getDefaultConnection(); + } + + /** @throws Exception */ + private function runQuery(SymfonyStyle $io, Connection $conn, string $sql): void + { + $resultSet = $conn->fetchAllAssociative($sql); + if ($resultSet === []) { + $io->success('The query yielded an empty result set.'); + + return; + } + + $io->table(array_keys($resultSet[0]), $resultSet); + } + + /** @throws Exception */ + private function runStatement(SymfonyStyle $io, Connection $conn, string $sql): void + { + $io->success(sprintf('%d rows affected.', $conn->executeStatement($sql))); + } +} diff --git a/vendor/doctrine/dbal/src/Tools/Console/ConnectionNotFound.php b/vendor/doctrine/dbal/src/Tools/Console/ConnectionNotFound.php new file mode 100644 index 0000000..81ca418 --- /dev/null +++ b/vendor/doctrine/dbal/src/Tools/Console/ConnectionNotFound.php @@ -0,0 +1,9 @@ +connection = $connection; + $this->defaultConnectionName = $defaultConnectionName; + } + + public function getDefaultConnection(): Connection + { + return $this->connection; + } + + public function getConnection(string $name): Connection + { + if ($name !== $this->defaultConnectionName) { + throw new ConnectionNotFound(sprintf('Connection with name "%s" does not exist.', $name)); + } + + return $this->connection; + } +} diff --git a/vendor/doctrine/dbal/src/Tools/Console/ConsoleRunner.php b/vendor/doctrine/dbal/src/Tools/Console/ConsoleRunner.php new file mode 100644 index 0000000..e8fa3c6 --- /dev/null +++ b/vendor/doctrine/dbal/src/Tools/Console/ConsoleRunner.php @@ -0,0 +1,81 @@ +setCatchExceptions(true); + self::addCommands($cli, $connectionProvider); + $cli->addCommands($commands); + $cli->run(); + } + + /** @return void */ + public static function addCommands(Application $cli, ConnectionProvider $connectionProvider) + { + $cli->addCommands([ + new RunSqlCommand($connectionProvider), + new ReservedWordsCommand($connectionProvider), + ]); + } + + /** + * Prints the instructions to create a configuration file + * + * @deprecated This method will be removed without replacement. + * + * @return void + */ + public static function printCliConfigTemplate() + { + echo <<<'HELP' +You are missing a "cli-config.php" or "config/cli-config.php" file in your +project, which is required to get the Doctrine-DBAL Console working. You can use the +following sample as a template: + +> */ + private array $schemeMapping; + + /** @param array> $schemeMapping An array used to map DSN schemes to DBAL drivers */ + public function __construct(array $schemeMapping = []) + { + $this->schemeMapping = $schemeMapping; + } + + /** + * @psalm-return Params + * + * @throws MalformedDsnException + */ + public function parse( + #[SensitiveParameter] + string $dsn + ): array { + // (pdo-)?sqlite3?:///... => (pdo-)?sqlite3?://localhost/... or else the URL will be invalid + $url = preg_replace('#^((?:pdo-)?sqlite3?):///#', '$1://localhost/', $dsn); + assert($url !== null); + + $url = parse_url($url); + + if ($url === false) { + throw MalformedDsnException::new(); + } + + foreach ($url as $param => $value) { + if (! is_string($value)) { + continue; + } + + $url[$param] = rawurldecode($value); + } + + $params = []; + + if (isset($url['scheme'])) { + $params['driver'] = $this->parseDatabaseUrlScheme($url['scheme']); + } + + if (isset($url['host'])) { + $params['host'] = $url['host']; + } + + if (isset($url['port'])) { + $params['port'] = $url['port']; + } + + if (isset($url['user'])) { + $params['user'] = $url['user']; + } + + if (isset($url['pass'])) { + $params['password'] = $url['pass']; + } + + if (isset($params['driver']) && is_a($params['driver'], Driver::class, true)) { + $params['driverClass'] = $params['driver']; + unset($params['driver']); + } + + $params = $this->parseDatabaseUrlPath($url, $params); + $params = $this->parseDatabaseUrlQuery($url, $params); + + return $params; + } + + /** + * Parses the given connection URL and resolves the given connection parameters. + * + * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters + * via {@see parseDatabaseUrlScheme}. + * + * @see parseDatabaseUrlScheme + * + * @param mixed[] $url The URL parts to evaluate. + * @param mixed[] $params The connection parameters to resolve. + * + * @return mixed[] The resolved connection parameters. + */ + private function parseDatabaseUrlPath(array $url, array $params): array + { + if (! isset($url['path'])) { + return $params; + } + + $url['path'] = $this->normalizeDatabaseUrlPath($url['path']); + + // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate + // and therefore treat the path as a regular DBAL connection URL path. + if (! isset($params['driver'])) { + return $this->parseRegularDatabaseUrlPath($url, $params); + } + + if (strpos($params['driver'], 'sqlite') !== false) { + return $this->parseSqliteDatabaseUrlPath($url, $params); + } + + return $this->parseRegularDatabaseUrlPath($url, $params); + } + + /** + * Normalizes the given connection URL path. + * + * @return string The normalized connection URL path + */ + private function normalizeDatabaseUrlPath(string $urlPath): string + { + // Trim leading slash from URL path. + return substr($urlPath, 1); + } + + /** + * Parses the query part of the given connection URL and resolves the given connection parameters. + * + * @param mixed[] $url The connection URL parts to evaluate. + * @param mixed[] $params The connection parameters to resolve. + * + * @return mixed[] The resolved connection parameters. + */ + private function parseDatabaseUrlQuery(array $url, array $params): array + { + if (! isset($url['query'])) { + return $params; + } + + $query = []; + + parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode + + return array_merge($params, $query); // parse_str wipes existing array elements + } + + /** + * Parses the given regular connection URL and resolves the given connection parameters. + * + * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}. + * + * @see normalizeDatabaseUrlPath + * + * @param mixed[] $url The regular connection URL parts to evaluate. + * @param mixed[] $params The connection parameters to resolve. + * + * @return mixed[] The resolved connection parameters. + */ + private function parseRegularDatabaseUrlPath(array $url, array $params): array + { + $params['dbname'] = $url['path']; + + return $params; + } + + /** + * Parses the given SQLite connection URL and resolves the given connection parameters. + * + * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}. + * + * @see normalizeDatabaseUrlPath + * + * @param mixed[] $url The SQLite connection URL parts to evaluate. + * @param mixed[] $params The connection parameters to resolve. + * + * @return mixed[] The resolved connection parameters. + */ + private function parseSqliteDatabaseUrlPath(array $url, array $params): array + { + if ($url['path'] === ':memory:') { + $params['memory'] = true; + + return $params; + } + + $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key + + return $params; + } + + /** + * Parses the scheme part from given connection URL and resolves the given connection parameters. + * + * @return string The resolved driver. + */ + private function parseDatabaseUrlScheme(string $scheme): string + { + // URL schemes must not contain underscores, but dashes are ok + $driver = str_replace('-', '_', $scheme); + + // If the driver is an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql"). + // Otherwise, let checkParams decide later if the driver exists. + return $this->schemeMapping[$driver] ?? $driver; + } +} diff --git a/vendor/doctrine/dbal/src/TransactionIsolationLevel.php b/vendor/doctrine/dbal/src/TransactionIsolationLevel.php new file mode 100644 index 0000000..9020343 --- /dev/null +++ b/vendor/doctrine/dbal/src/TransactionIsolationLevel.php @@ -0,0 +1,33 @@ +getClobTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + // @todo 3.0 - $value === null check to save real NULL in database + return serialize($value); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $value = is_resource($value) ? stream_get_contents($value) : $value; + + set_error_handler(function (int $code, string $message): bool { + if ($code === E_DEPRECATED || $code === E_USER_DEPRECATED) { + return false; + } + + throw ConversionException::conversionFailedUnserialization($this->getName(), $message); + }); + + try { + return unserialize($value); + } finally { + restore_error_handler(); + } + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::ARRAY; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/AsciiStringType.php b/vendor/doctrine/dbal/src/Types/AsciiStringType.php new file mode 100644 index 0000000..4ea92d9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/AsciiStringType.php @@ -0,0 +1,29 @@ +getAsciiStringTypeDeclarationSQL($column); + } + + public function getBindingType(): int + { + return ParameterType::ASCII; + } + + public function getName(): string + { + return Types::ASCII_STRING; + } +} diff --git a/vendor/doctrine/dbal/src/Types/BigIntType.php b/vendor/doctrine/dbal/src/Types/BigIntType.php new file mode 100644 index 0000000..8d57a11 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/BigIntType.php @@ -0,0 +1,50 @@ +getBigIntTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getBindingType() + { + return ParameterType::STRING; + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value === null ? null : (string) $value; + } +} diff --git a/vendor/doctrine/dbal/src/Types/BinaryType.php b/vendor/doctrine/dbal/src/Types/BinaryType.php new file mode 100644 index 0000000..acbbd87 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/BinaryType.php @@ -0,0 +1,67 @@ +getBinaryTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + if (is_string($value)) { + $fp = fopen('php://temp', 'rb+'); + assert(is_resource($fp)); + fwrite($fp, $value); + fseek($fp, 0); + $value = $fp; + } + + if (! is_resource($value)) { + throw ConversionException::conversionFailed($value, Types::BINARY); + } + + return $value; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::BINARY; + } + + /** + * {@inheritDoc} + */ + public function getBindingType() + { + return ParameterType::BINARY; + } +} diff --git a/vendor/doctrine/dbal/src/Types/BlobType.php b/vendor/doctrine/dbal/src/Types/BlobType.php new file mode 100644 index 0000000..cfaabec --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/BlobType.php @@ -0,0 +1,67 @@ +getBlobTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + if (is_string($value)) { + $fp = fopen('php://temp', 'rb+'); + assert(is_resource($fp)); + fwrite($fp, $value); + fseek($fp, 0); + $value = $fp; + } + + if (! is_resource($value)) { + throw ConversionException::conversionFailed($value, Types::BLOB); + } + + return $value; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::BLOB; + } + + /** + * {@inheritDoc} + */ + public function getBindingType() + { + return ParameterType::LARGE_OBJECT; + } +} diff --git a/vendor/doctrine/dbal/src/Types/BooleanType.php b/vendor/doctrine/dbal/src/Types/BooleanType.php new file mode 100644 index 0000000..7dc7f3a --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/BooleanType.php @@ -0,0 +1,79 @@ +getBooleanTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return $platform->convertBooleansToDatabaseValue($value); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : bool) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $platform->convertFromBoolean($value); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::BOOLEAN; + } + + /** + * {@inheritDoc} + */ + public function getBindingType() + { + return ParameterType::BOOLEAN; + } + + /** + * @deprecated + * + * @return bool + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + // We require a commented boolean type in order to distinguish between + // boolean and smallint as both (have to) map to the same native type. + return $platform instanceof DB2Platform; + } +} diff --git a/vendor/doctrine/dbal/src/Types/ConversionException.php b/vendor/doctrine/dbal/src/Types/ConversionException.php new file mode 100644 index 0000000..154b06d --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/ConversionException.php @@ -0,0 +1,123 @@ + 32 ? substr($value, 0, 20) . '...' : $value; + + return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType, 0, $previous); + } + + /** + * Thrown when a Database to Doctrine Type Conversion fails and we can make a statement + * about the expected format. + * + * @param mixed $value + * @param string $toType + * @param string $expectedFormat + * + * @return ConversionException + */ + public static function conversionFailedFormat($value, $toType, $expectedFormat, ?Throwable $previous = null) + { + $value = strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value; + + return new self( + 'Could not convert database value "' . $value . '" to Doctrine Type ' . + $toType . '. Expected format: ' . $expectedFormat, + 0, + $previous, + ); + } + + /** + * Thrown when the PHP value passed to the converter was not of the expected type. + * + * @param mixed $value + * @param string $toType + * @param string[] $possibleTypes + * + * @return ConversionException + */ + public static function conversionFailedInvalidType( + $value, + $toType, + array $possibleTypes, + ?Throwable $previous = null + ) { + if (is_scalar($value) || $value === null) { + return new self(sprintf( + 'Could not convert PHP value %s to type %s. Expected one of the following types: %s', + var_export($value, true), + $toType, + implode(', ', $possibleTypes), + ), 0, $previous); + } + + return new self(sprintf( + 'Could not convert PHP value of type %s to type %s. Expected one of the following types: %s', + is_object($value) ? get_class($value) : gettype($value), + $toType, + implode(', ', $possibleTypes), + ), 0, $previous); + } + + /** + * @param mixed $value + * @param string $format + * @param string $error + * + * @return ConversionException + */ + public static function conversionFailedSerialization($value, $format, $error /*, ?Throwable $previous = null */) + { + $actualType = is_object($value) ? get_class($value) : gettype($value); + + return new self(sprintf( + "Could not convert PHP type '%s' to '%s', as an '%s' error was triggered by the serialization", + $actualType, + $format, + $error, + ), 0, func_num_args() >= 4 ? func_get_arg(3) : null); + } + + public static function conversionFailedUnserialization(string $format, string $error): self + { + return new self(sprintf( + "Could not convert database value to '%s' as an error was triggered by the unserialization: '%s'", + $format, + $error, + )); + } +} diff --git a/vendor/doctrine/dbal/src/Types/DateImmutableType.php b/vendor/doctrine/dbal/src/Types/DateImmutableType.php new file mode 100644 index 0000000..da96b69 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DateImmutableType.php @@ -0,0 +1,92 @@ +format($platform->getDateFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', DateTimeImmutable::class], + ); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeImmutable) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof DateTimeImmutable) { + return $value; + } + + $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getDateFormatString(), $value); + + if ($dateTime === false) { + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getDateFormatString(), + ); + } + + return $dateTime; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/DateIntervalType.php b/vendor/doctrine/dbal/src/Types/DateIntervalType.php new file mode 100644 index 0000000..1630dc5 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DateIntervalType.php @@ -0,0 +1,110 @@ +getStringTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + if ($value instanceof DateInterval) { + return $value->format(self::FORMAT); + } + + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', DateInterval::class]); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateInterval) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof DateInterval) { + return $value; + } + + $negative = false; + + if (isset($value[0]) && ($value[0] === '+' || $value[0] === '-')) { + $negative = $value[0] === '-'; + $value = substr($value, 1); + } + + try { + $interval = new DateInterval($value); + + if ($negative) { + $interval->invert = 1; + } + + return $interval; + } catch (Throwable $exception) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), self::FORMAT, $exception); + } + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/DateTimeImmutableType.php b/vendor/doctrine/dbal/src/Types/DateTimeImmutableType.php new file mode 100644 index 0000000..a8c7fec --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DateTimeImmutableType.php @@ -0,0 +1,98 @@ +format($platform->getDateTimeFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', DateTimeImmutable::class], + ); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeImmutable) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof DateTimeImmutable) { + return $value; + } + + $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeFormatString(), $value); + + if ($dateTime !== false) { + return $dateTime; + } + + try { + return new DateTimeImmutable($value); + } catch (Exception $e) { + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getDateTimeFormatString(), + $e, + ); + } + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/DateTimeType.php b/vendor/doctrine/dbal/src/Types/DateTimeType.php new file mode 100644 index 0000000..3ff592a --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DateTimeType.php @@ -0,0 +1,115 @@ +getDateTimeTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + DateTimeImmutableType::class, + __FUNCTION__, + ); + } + + if ($value instanceof DateTimeInterface) { + return $value->format($platform->getDateTimeFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', DateTime::class, DateTimeImmutable::class], + ); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeInterface) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + DateTimeImmutableType::class, + __FUNCTION__, + ); + } + + if ($value === null || $value instanceof DateTimeInterface) { + return $value; + } + + $dateTime = DateTime::createFromFormat($platform->getDateTimeFormatString(), $value); + + if ($dateTime !== false) { + return $dateTime; + } + + try { + return new DateTime($value); + } catch (Exception $e) { + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getDateTimeFormatString(), + $e, + ); + } + } +} diff --git a/vendor/doctrine/dbal/src/Types/DateTimeTzImmutableType.php b/vendor/doctrine/dbal/src/Types/DateTimeTzImmutableType.php new file mode 100644 index 0000000..e700f68 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DateTimeTzImmutableType.php @@ -0,0 +1,92 @@ +format($platform->getDateTimeTzFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', DateTimeImmutable::class], + ); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeImmutable) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof DateTimeImmutable) { + return $value; + } + + $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeTzFormatString(), $value); + + if ($dateTime !== false) { + return $dateTime; + } + + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getDateTimeTzFormatString(), + ); + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/DateTimeTzType.php b/vendor/doctrine/dbal/src/Types/DateTimeTzType.php new file mode 100644 index 0000000..b3b5db8 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DateTimeTzType.php @@ -0,0 +1,122 @@ +getDateTimeTzTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + DateTimeTzImmutableType::class, + __FUNCTION__, + ); + } + + if ($value instanceof DateTimeInterface) { + return $value->format($platform->getDateTimeTzFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', DateTime::class], + ); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeInterface) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + DateTimeTzImmutableType::class, + __FUNCTION__, + ); + } + + if ($value === null || $value instanceof DateTimeInterface) { + return $value; + } + + $dateTime = DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value); + if ($dateTime !== false) { + return $dateTime; + } + + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getDateTimeTzFormatString(), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Types/DateType.php b/vendor/doctrine/dbal/src/Types/DateType.php new file mode 100644 index 0000000..842e8bd --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DateType.php @@ -0,0 +1,104 @@ +getDateTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @psalm-param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + DateImmutableType::class, + __FUNCTION__, + ); + } + + return $value->format($platform->getDateFormatString()); + } + + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', DateTime::class]); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeInterface) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + DateImmutableType::class, + __FUNCTION__, + ); + } + + if ($value === null || $value instanceof DateTimeInterface) { + return $value; + } + + $dateTime = DateTime::createFromFormat('!' . $platform->getDateFormatString(), $value); + if ($dateTime !== false) { + return $dateTime; + } + + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getDateFormatString(), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Types/DecimalType.php b/vendor/doctrine/dbal/src/Types/DecimalType.php new file mode 100644 index 0000000..308134b --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/DecimalType.php @@ -0,0 +1,47 @@ +getDecimalTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + // Some drivers starting from PHP 8.1 can represent decimals as float/int + // See also: https://github.com/doctrine/dbal/pull/4818 + if ((PHP_VERSION_ID >= 80100 || $platform instanceof SqlitePlatform) && (is_float($value) || is_int($value))) { + return (string) $value; + } + + return $value; + } +} diff --git a/vendor/doctrine/dbal/src/Types/FloatType.php b/vendor/doctrine/dbal/src/Types/FloatType.php new file mode 100644 index 0000000..e01b774 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/FloatType.php @@ -0,0 +1,38 @@ +getFloatDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : float) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value === null ? null : (float) $value; + } +} diff --git a/vendor/doctrine/dbal/src/Types/GuidType.php b/vendor/doctrine/dbal/src/Types/GuidType.php new file mode 100644 index 0000000..3c8b7f4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/GuidType.php @@ -0,0 +1,45 @@ +getGuidTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::GUID; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return ! $platform->hasNativeGuidType(); + } +} diff --git a/vendor/doctrine/dbal/src/Types/IntegerType.php b/vendor/doctrine/dbal/src/Types/IntegerType.php new file mode 100644 index 0000000..7c2d711 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/IntegerType.php @@ -0,0 +1,50 @@ +getIntegerTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : int) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value === null ? null : (int) $value; + } + + /** + * {@inheritDoc} + */ + public function getBindingType() + { + return ParameterType::INTEGER; + } +} diff --git a/vendor/doctrine/dbal/src/Types/JsonType.php b/vendor/doctrine/dbal/src/Types/JsonType.php new file mode 100644 index 0000000..27f872c --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/JsonType.php @@ -0,0 +1,96 @@ +getJsonTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + try { + return json_encode($value, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION); + } catch (JsonException $e) { + throw ConversionException::conversionFailedSerialization($value, 'json', $e->getMessage(), $e); + } + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value === '') { + return null; + } + + if (is_resource($value)) { + $value = stream_get_contents($value); + } + + try { + return json_decode($value, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw ConversionException::conversionFailed($value, $this->getName(), $e); + } + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::JSON; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return ! $platform->hasNativeJsonType(); + } +} diff --git a/vendor/doctrine/dbal/src/Types/ObjectType.php b/vendor/doctrine/dbal/src/Types/ObjectType.php new file mode 100644 index 0000000..497e9c4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/ObjectType.php @@ -0,0 +1,88 @@ +getClobTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param mixed $value + * + * @return string + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return serialize($value); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $value = is_resource($value) ? stream_get_contents($value) : $value; + + set_error_handler(function (int $code, string $message): bool { + throw ConversionException::conversionFailedUnserialization($this->getName(), $message); + }); + + try { + return unserialize($value); + } finally { + restore_error_handler(); + } + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::OBJECT; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/PhpDateTimeMappingType.php b/vendor/doctrine/dbal/src/Types/PhpDateTimeMappingType.php new file mode 100644 index 0000000..4565850 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/PhpDateTimeMappingType.php @@ -0,0 +1,12 @@ +getClobTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param mixed $value + * + * @return string|null + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (! is_array($value) || count($value) === 0) { + return null; + } + + return implode(',', $value); + } + + /** + * {@inheritDoc} + * + * @param mixed $value + * + * @return list + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return []; + } + + $value = is_resource($value) ? stream_get_contents($value) : $value; + + return explode(',', $value); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::SIMPLE_ARRAY; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/SmallIntType.php b/vendor/doctrine/dbal/src/Types/SmallIntType.php new file mode 100644 index 0000000..2c8567a --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/SmallIntType.php @@ -0,0 +1,50 @@ +getSmallIntTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : int) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value === null ? null : (int) $value; + } + + /** + * {@inheritDoc} + */ + public function getBindingType() + { + return ParameterType::INTEGER; + } +} diff --git a/vendor/doctrine/dbal/src/Types/StringType.php b/vendor/doctrine/dbal/src/Types/StringType.php new file mode 100644 index 0000000..1992e8f --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/StringType.php @@ -0,0 +1,27 @@ +getStringTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::STRING; + } +} diff --git a/vendor/doctrine/dbal/src/Types/TextType.php b/vendor/doctrine/dbal/src/Types/TextType.php new file mode 100644 index 0000000..d060bb2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/TextType.php @@ -0,0 +1,38 @@ +getClobTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return is_resource($value) ? stream_get_contents($value) : $value; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return Types::TEXT; + } +} diff --git a/vendor/doctrine/dbal/src/Types/TimeImmutableType.php b/vendor/doctrine/dbal/src/Types/TimeImmutableType.php new file mode 100644 index 0000000..9373f59 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/TimeImmutableType.php @@ -0,0 +1,92 @@ +format($platform->getTimeFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', DateTimeImmutable::class], + ); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeImmutable) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof DateTimeImmutable) { + return $value; + } + + $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getTimeFormatString(), $value); + + if ($dateTime !== false) { + return $dateTime; + } + + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getTimeFormatString(), + ); + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/TimeType.php b/vendor/doctrine/dbal/src/Types/TimeType.php new file mode 100644 index 0000000..7356fc2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/TimeType.php @@ -0,0 +1,104 @@ +getTimeTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + TimeImmutableType::class, + __FUNCTION__, + ); + } + + if ($value instanceof DateTimeInterface) { + return $value->format($platform->getTimeFormatString()); + } + + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', DateTime::class]); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeInterface) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value instanceof DateTimeImmutable) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6017', + 'Passing an instance of %s is deprecated, use %s::%s() instead.', + get_class($value), + TimeImmutableType::class, + __FUNCTION__, + ); + } + + if ($value === null || $value instanceof DateTimeInterface) { + return $value; + } + + $dateTime = DateTime::createFromFormat('!' . $platform->getTimeFormatString(), $value); + if ($dateTime !== false) { + return $dateTime; + } + + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getTimeFormatString(), + ); + } +} diff --git a/vendor/doctrine/dbal/src/Types/Type.php b/vendor/doctrine/dbal/src/Types/Type.php new file mode 100644 index 0000000..7613811 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/Type.php @@ -0,0 +1,296 @@ + ArrayType::class, + Types::ASCII_STRING => AsciiStringType::class, + Types::BIGINT => BigIntType::class, + Types::BINARY => BinaryType::class, + Types::BLOB => BlobType::class, + Types::BOOLEAN => BooleanType::class, + Types::DATE_MUTABLE => DateType::class, + Types::DATE_IMMUTABLE => DateImmutableType::class, + Types::DATEINTERVAL => DateIntervalType::class, + Types::DATETIME_MUTABLE => DateTimeType::class, + Types::DATETIME_IMMUTABLE => DateTimeImmutableType::class, + Types::DATETIMETZ_MUTABLE => DateTimeTzType::class, + Types::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class, + Types::DECIMAL => DecimalType::class, + Types::FLOAT => FloatType::class, + Types::GUID => GuidType::class, + Types::INTEGER => IntegerType::class, + Types::JSON => JsonType::class, + Types::OBJECT => ObjectType::class, + Types::SIMPLE_ARRAY => SimpleArrayType::class, + Types::SMALLINT => SmallIntType::class, + Types::STRING => StringType::class, + Types::TEXT => TextType::class, + Types::TIME_MUTABLE => TimeType::class, + Types::TIME_IMMUTABLE => TimeImmutableType::class, + ]; + + private static ?TypeRegistry $typeRegistry = null; + + /** @internal Do not instantiate directly - use {@see Type::addType()} method instead. */ + final public function __construct() + { + } + + /** + * Converts a value from its PHP representation to its database representation + * of this type. + * + * @param mixed $value The value to convert. + * @param AbstractPlatform $platform The currently used database platform. + * + * @return mixed The database representation of the value. + * + * @throws ConversionException + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return $value; + } + + /** + * Converts a value from its database representation to its PHP representation + * of this type. + * + * @param mixed $value The value to convert. + * @param AbstractPlatform $platform The currently used database platform. + * + * @return mixed The PHP representation of the value. + * + * @throws ConversionException + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value; + } + + /** + * Gets the SQL declaration snippet for a column of this type. + * + * @param mixed[] $column The column definition + * @param AbstractPlatform $platform The currently used database platform. + * + * @return string + */ + abstract public function getSQLDeclaration(array $column, AbstractPlatform $platform); + + /** + * Gets the name of this type. + * + * @deprecated this method will be removed in Doctrine DBAL 4.0, + * use {@see TypeRegistry::lookupName()} instead. + * + * @return string + */ + abstract public function getName(); + + final public static function getTypeRegistry(): TypeRegistry + { + return self::$typeRegistry ??= self::createTypeRegistry(); + } + + private static function createTypeRegistry(): TypeRegistry + { + $instances = []; + + foreach (self::BUILTIN_TYPES_MAP as $name => $class) { + $instances[$name] = new $class(); + } + + return new TypeRegistry($instances); + } + + /** + * Factory method to create type instances. + * Type instances are implemented as flyweights. + * + * @param string $name The name of the type (as returned by getName()). + * + * @return Type + * + * @throws Exception + */ + public static function getType($name) + { + return self::getTypeRegistry()->get($name); + } + + /** + * Finds a name for the given type. + * + * @throws Exception + */ + public static function lookupName(self $type): string + { + return self::getTypeRegistry()->lookupName($type); + } + + /** + * Adds a custom type to the type map. + * + * @param string $name The name of the type. This should correspond to what getName() returns. + * @param class-string $className The class name of the custom type. + * + * @return void + * + * @throws Exception + */ + public static function addType($name, $className) + { + self::getTypeRegistry()->register($name, new $className()); + } + + /** + * Checks if exists support for a type. + * + * @param string $name The name of the type. + * + * @return bool TRUE if type is supported; FALSE otherwise. + */ + public static function hasType($name) + { + return self::getTypeRegistry()->has($name); + } + + /** + * Overrides an already defined type to use a different implementation. + * + * @param string $name + * @param class-string $className + * + * @return void + * + * @throws Exception + */ + public static function overrideType($name, $className) + { + self::getTypeRegistry()->override($name, new $className()); + } + + /** + * Gets the (preferred) binding type for values of this type that + * can be used when binding parameters to prepared statements. + * + * This method should return one of the {@see ParameterType} constants. + * + * @return int + */ + public function getBindingType() + { + return ParameterType::STRING; + } + + /** + * Gets the types array map which holds all registered types and the corresponding + * type class + * + * @return array + */ + public static function getTypesMap() + { + return array_map( + static function (Type $type): string { + return get_class($type); + }, + self::getTypeRegistry()->getMap(), + ); + } + + /** + * Does working with this column require SQL conversion functions? + * + * This is a metadata function that is required for example in the ORM. + * Usage of {@see convertToDatabaseValueSQL} and + * {@see convertToPHPValueSQL} works for any type and mostly + * does nothing. This method can additionally be used for optimization purposes. + * + * @deprecated Consumers should call {@see convertToDatabaseValueSQL} and {@see convertToPHPValueSQL} + * regardless of the type. + * + * @return bool + */ + public function canRequireSQLConversion() + { + return false; + } + + /** + * Modifies the SQL expression (identifier, parameter) to convert to a database value. + * + * @param string $sqlExpr + * + * @return string + */ + public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) + { + return $sqlExpr; + } + + /** + * Modifies the SQL expression (identifier, parameter) to convert to a PHP value. + * + * @param string $sqlExpr + * @param AbstractPlatform $platform + * + * @return string + */ + public function convertToPHPValueSQL($sqlExpr, $platform) + { + return $sqlExpr; + } + + /** + * Gets an array of database types that map to this Doctrine type. + * + * @return string[] + */ + public function getMappedDatabaseTypes(AbstractPlatform $platform) + { + return []; + } + + /** + * If this Doctrine Type maps to an already mapped database type, + * reverse schema engineering can't tell them apart. You need to mark + * one of those types as commented, which will have Doctrine use an SQL + * comment to typehint the actual Doctrine Type. + * + * @deprecated + * + * @return bool + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return false; + } +} diff --git a/vendor/doctrine/dbal/src/Types/TypeRegistry.php b/vendor/doctrine/dbal/src/Types/TypeRegistry.php new file mode 100644 index 0000000..9b64c6f --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/TypeRegistry.php @@ -0,0 +1,127 @@ + Map of type names and their corresponding flyweight objects. */ + private array $instances; + /** @var array */ + private array $instancesReverseIndex; + + /** @param array $instances */ + public function __construct(array $instances = []) + { + $this->instances = []; + $this->instancesReverseIndex = []; + foreach ($instances as $name => $type) { + $this->register($name, $type); + } + } + + /** + * Finds a type by the given name. + * + * @throws Exception + */ + public function get(string $name): Type + { + $type = $this->instances[$name] ?? null; + if ($type === null) { + throw Exception::unknownColumnType($name); + } + + return $type; + } + + /** + * Finds a name for the given type. + * + * @throws Exception + */ + public function lookupName(Type $type): string + { + $name = $this->findTypeName($type); + + if ($name === null) { + throw Exception::typeNotRegistered($type); + } + + return $name; + } + + /** + * Checks if there is a type of the given name. + */ + public function has(string $name): bool + { + return isset($this->instances[$name]); + } + + /** + * Registers a custom type to the type map. + * + * @throws Exception + */ + public function register(string $name, Type $type): void + { + if (isset($this->instances[$name])) { + throw Exception::typeExists($name); + } + + if ($this->findTypeName($type) !== null) { + throw Exception::typeAlreadyRegistered($type); + } + + $this->instances[$name] = $type; + $this->instancesReverseIndex[spl_object_id($type)] = $name; + } + + /** + * Overrides an already defined type to use a different implementation. + * + * @throws Exception + */ + public function override(string $name, Type $type): void + { + $origType = $this->instances[$name] ?? null; + if ($origType === null) { + throw Exception::typeNotFound($name); + } + + if (($this->findTypeName($type) ?? $name) !== $name) { + throw Exception::typeAlreadyRegistered($type); + } + + unset($this->instancesReverseIndex[spl_object_id($origType)]); + $this->instances[$name] = $type; + $this->instancesReverseIndex[spl_object_id($type)] = $name; + } + + /** + * Gets the map of all registered types and their corresponding type instances. + * + * @internal + * + * @return array + */ + public function getMap(): array + { + return $this->instances; + } + + private function findTypeName(Type $type): ?string + { + return $this->instancesReverseIndex[spl_object_id($type)] ?? null; + } +} diff --git a/vendor/doctrine/dbal/src/Types/Types.php b/vendor/doctrine/dbal/src/Types/Types.php new file mode 100644 index 0000000..54b0dfe --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/Types.php @@ -0,0 +1,47 @@ +format($platform->getDateTimeFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', DateTimeImmutable::class], + ); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeImmutable) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof DateTimeImmutable) { + return $value; + } + + try { + $dateTime = new DateTimeImmutable($value); + } catch (Exception $e) { + throw ConversionException::conversionFailed($value, $this->getName(), $e); + } + + return $dateTime; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} diff --git a/vendor/doctrine/dbal/src/Types/VarDateTimeType.php b/vendor/doctrine/dbal/src/Types/VarDateTimeType.php new file mode 100644 index 0000000..35ad403 --- /dev/null +++ b/vendor/doctrine/dbal/src/Types/VarDateTimeType.php @@ -0,0 +1,42 @@ + 0 it is necessary to use this type. + */ +class VarDateTimeType extends DateTimeType +{ + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeInterface) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof DateTime) { + return $value; + } + + try { + $dateTime = new DateTime($value); + } catch (Exception $e) { + throw ConversionException::conversionFailed($value, $this->getName(), $e); + } + + return $dateTime; + } +} diff --git a/vendor/doctrine/dbal/src/VersionAwarePlatformDriver.php b/vendor/doctrine/dbal/src/VersionAwarePlatformDriver.php new file mode 100644 index 0000000..ffcfcd6 --- /dev/null +++ b/vendor/doctrine/dbal/src/VersionAwarePlatformDriver.php @@ -0,0 +1,30 @@ + $count) { + echo $identifier . " was triggered " . $count . " times\n"; +} +``` + +### Suppressing Specific Deprecations + +Disable triggering about specific deprecations: + +```php +\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); +``` + +Disable all deprecations from a package + +```php +\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); +``` + +### Other Operations + +When used within PHPUnit or other tools that could collect multiple instances of the same deprecations +the deduplication can be disabled: + +```php +\Doctrine\Deprecations\Deprecation::withoutDeduplication(); +``` + +Disable deprecation tracking again: + +```php +\Doctrine\Deprecations\Deprecation::disable(); +``` + +## Usage from a library/producer perspective: + +When you want to unconditionally trigger a deprecation even when called +from the library itself then the `trigger` method is the way to go: + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +If variable arguments are provided at the end, they are used with `sprintf` on +the message. + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://github.com/doctrine/orm/issue/1234", + "message %s %d", + "foo", + 1234 +); +``` + +When you want to trigger a deprecation only when it is called by a function +outside of the current package, but not trigger when the package itself is the cause, +then use: + +```php +\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +Based on the issue link each deprecation message is only triggered once per +request. + +A limited stacktrace is included in the deprecation message to find the +offending location. + +Note: A producer/library should never call `Deprecation::enableWith` methods +and leave the decision how to handle deprecations to application and +frameworks. + +## Usage in PHPUnit tests + +There is a `VerifyDeprecations` trait that you can use to make assertions on +the occurrence of deprecations within a test. + +```php +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; + +class MyTest extends TestCase +{ + use VerifyDeprecations; + + public function testSomethingDeprecation() + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithDeprecation(); + } + + public function testSomethingDeprecationFixed() + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithoutDeprecation(); + } +} +``` + +## What is a deprecation identifier? + +An identifier for deprecations is just a link to any resource, most often a +Github Issue or Pull Request explaining the deprecation and potentially its +alternative. diff --git a/vendor/doctrine/deprecations/composer.json b/vendor/doctrine/deprecations/composer.json new file mode 100644 index 0000000..f8319f9 --- /dev/null +++ b/vendor/doctrine/deprecations/composer.json @@ -0,0 +1,38 @@ +{ + "name": "doctrine/deprecations", + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "license": "MIT", + "type": "library", + "homepage": "https://www.doctrine-project.org/", + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "autoload-dev": { + "psr-4": { + "DeprecationTests\\": "test_fixtures/src", + "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php b/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php new file mode 100644 index 0000000..bad5070 --- /dev/null +++ b/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php @@ -0,0 +1,313 @@ +|null */ + private static $type; + + /** @var LoggerInterface|null */ + private static $logger; + + /** @var array */ + private static $ignoredPackages = []; + + /** @var array */ + private static $triggeredDeprecations = []; + + /** @var array */ + private static $ignoredLinks = []; + + /** @var bool */ + private static $deduplication = true; + + /** + * Trigger a deprecation for the given package and identfier. + * + * The link should point to a Github issue or Wiki entry detailing the + * deprecation. It is additionally used to de-duplicate the trigger of the + * same deprecation during a request. + * + * @param float|int|string $args + */ + public static function trigger(string $package, string $link, string $message, ...$args): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if ($type === self::TYPE_NONE) { + return; + } + + if (isset(self::$ignoredLinks[$link])) { + return; + } + + if (array_key_exists($link, self::$triggeredDeprecations)) { + self::$triggeredDeprecations[$link]++; + } else { + self::$triggeredDeprecations[$link] = 1; + } + + if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * Trigger a deprecation for the given package and identifier when called from outside. + * + * "Outside" means we assume that $package is currently installed as a + * dependency and the caller is not a file in that package. When $package + * is installed as a root package then deprecations triggered from the + * tests folder are also considered "outside". + * + * This deprecation method assumes that you are using Composer to install + * the dependency and are using the default /vendor/ folder and not a + * Composer plugin to change the install location. The assumption is also + * that $package is the exact composer packge name. + * + * Compared to {@link trigger()} this method causes some overhead when + * deprecation tracking is enabled even during deduplication, because it + * needs to call {@link debug_backtrace()} + * + * @param float|int|string $args + */ + public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if ($type === self::TYPE_NONE) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + // first check that the caller is not from a tests folder, in which case we always let deprecations pass + if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { + $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $package) . DIRECTORY_SEPARATOR; + + if (strpos($backtrace[0]['file'], $path) === false) { + return; + } + + if (strpos($backtrace[1]['file'], $path) !== false) { + return; + } + } + + if (isset(self::$ignoredLinks[$link])) { + return; + } + + if (array_key_exists($link, self::$triggeredDeprecations)) { + self::$triggeredDeprecations[$link]++; + } else { + self::$triggeredDeprecations[$link] = 1; + } + + if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * @param list $backtrace + */ + private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if (($type & self::TYPE_PSR_LOGGER) > 0) { + $context = [ + 'file' => $backtrace[0]['file'] ?? null, + 'line' => $backtrace[0]['line'] ?? null, + 'package' => $package, + 'link' => $link, + ]; + + assert(self::$logger !== null); + + self::$logger->notice($message, $context); + } + + if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) { + return; + } + + $message .= sprintf( + ' (%s:%d called by %s:%d, %s, package %s)', + self::basename($backtrace[0]['file'] ?? 'native code'), + $backtrace[0]['line'] ?? 0, + self::basename($backtrace[1]['file'] ?? 'native code'), + $backtrace[1]['line'] ?? 0, + $link, + $package + ); + + @trigger_error($message, E_USER_DEPRECATED); + } + + /** + * A non-local-aware version of PHPs basename function. + */ + private static function basename(string $filename): string + { + $pos = strrpos($filename, DIRECTORY_SEPARATOR); + + if ($pos === false) { + return $filename; + } + + return substr($filename, $pos + 1); + } + + public static function enableTrackingDeprecations(): void + { + self::$type = self::$type ?? 0; + self::$type |= self::TYPE_TRACK_DEPRECATIONS; + } + + public static function enableWithTriggerError(): void + { + self::$type = self::$type ?? 0; + self::$type |= self::TYPE_TRIGGER_ERROR; + } + + public static function enableWithPsrLogger(LoggerInterface $logger): void + { + self::$type = self::$type ?? 0; + self::$type |= self::TYPE_PSR_LOGGER; + self::$logger = $logger; + } + + public static function withoutDeduplication(): void + { + self::$deduplication = false; + } + + public static function disable(): void + { + self::$type = self::TYPE_NONE; + self::$logger = null; + self::$deduplication = true; + self::$ignoredLinks = []; + + foreach (self::$triggeredDeprecations as $link => $count) { + self::$triggeredDeprecations[$link] = 0; + } + } + + public static function ignorePackage(string $packageName): void + { + self::$ignoredPackages[$packageName] = true; + } + + public static function ignoreDeprecations(string ...$links): void + { + foreach ($links as $link) { + self::$ignoredLinks[$link] = true; + } + } + + public static function getUniqueTriggeredDeprecationsCount(): int + { + return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) { + return $carry + $count; + }, 0); + } + + /** + * Returns each triggered deprecation link identifier and the amount of occurrences. + * + * @return array + */ + public static function getTriggeredDeprecations(): array + { + return self::$triggeredDeprecations; + } + + /** + * @return int-mask-of + */ + private static function getTypeFromEnv(): int + { + switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) { + case 'trigger': + self::$type = self::TYPE_TRIGGER_ERROR; + break; + + case 'track': + self::$type = self::TYPE_TRACK_DEPRECATIONS; + break; + + default: + self::$type = self::TYPE_NONE; + break; + } + + return self::$type; + } +} diff --git a/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php b/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php new file mode 100644 index 0000000..4c3366a --- /dev/null +++ b/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php @@ -0,0 +1,66 @@ + */ + private $doctrineDeprecationsExpectations = []; + + /** @var array */ + private $doctrineNoDeprecationsExpectations = []; + + public function expectDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + public function expectNoDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + /** + * @before + */ + public function enableDeprecationTracking(): void + { + Deprecation::enableTrackingDeprecations(); + } + + /** + * @after + */ + public function verifyDeprecationsAreTriggered(): void + { + foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount > $expectation, + sprintf( + "Expected deprecation with identifier '%s' was not triggered by code executed in test.", + $identifier + ) + ); + } + + foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount === $expectation, + sprintf( + "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", + $identifier + ) + ); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/LICENSE b/vendor/doctrine/doctrine-bundle/LICENSE new file mode 100644 index 0000000..655a5ce --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2011 Fabien Potencier, Doctrine Project + +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. diff --git a/vendor/doctrine/doctrine-bundle/README.md b/vendor/doctrine/doctrine-bundle/README.md new file mode 100644 index 0000000..16bac93 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/README.md @@ -0,0 +1,24 @@ +# Doctrine Bundle + +Doctrine DBAL & ORM Bundle for the Symfony Framework. + +[![Continuous Integration](https://github.com/doctrine/DoctrineBundle/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/doctrine/DoctrineBundle/actions/workflows/continuous-integration.yml) [![codecov](https://codecov.io/gh/doctrine/DoctrineBundle/branch/master/graph/badge.svg?token=qtm3EQ3WgV)](https://codecov.io/gh/doctrine/DoctrineBundle) + +## What is Doctrine? + +The Doctrine Project is the home of a selected set of PHP libraries primarily focused on providing persistence +services and related functionality. Its prize projects are a Object Relational Mapper and the Database Abstraction +Layer it is built on top of. You can read more about the projects below or view a list of all projects. + +Object relational mapper (ORM) for PHP that sits on top of a powerful database abstraction layer (DBAL). +One of its key features is the option to write database queries in a proprietary object oriented SQL dialect +called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful +alternative to SQL that maintains flexibility without requiring unnecessary code duplication. + +DBAL is a powerful database abstraction layer with many features for database schema introspection, +schema management and PDO abstraction. + +## Documentation + +The documentation is rendered on [the symfony.com website](https://symfony.com/doc/current/reference/configuration/doctrine.html). +The source of the documentation is available in the docs folder. diff --git a/vendor/doctrine/doctrine-bundle/UPGRADE-2.10.md b/vendor/doctrine/doctrine-bundle/UPGRADE-2.10.md new file mode 100644 index 0000000..e9e03c2 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/UPGRADE-2.10.md @@ -0,0 +1,27 @@ +UPGRADE FROM 2.9 to 2.10 +======================== + +Configuration +------------- + +### Preparing for a new `report_fields_where_declared` mapping driver mode + +Doctrine ORM 2.16+ makes a change to how the annotations and attribute mapping drivers report fields inherited from parent classes. For details, see https://github.com/doctrine/orm/pull/10455. It will trigger a deprecation notice unless the new mode is activated. In ORM 3.0, the new mode will be the only one. + +The new mode ~does not~ should not make a difference for regular, valid use cases, but may lead to `MappingException`s for users with certain configurations that were not meant to be supported by the ORM in the first place. To avoid surprising users (even when their configuration is invalid) during a 2.16 _minor_ version upgrade, the transition to this new mode was implemented as an opt-in. This way, you can try and deal with the change any time you see fit. + +In version 2.10+ of this bundle, a new configuration setting `report_fields_where_declared` was added at the entity manager configuration level. Set it to `true` to switch the mapping driver for the corresponding entity manager to the new mode. It is only relevant for mapping configurations using attributes or annotations. + +Unless you set it to `true`, Doctrine ORM will emit deprecation messages mentioning this new setting. + +### Preparing for the XSD validation for XML drivers + +Doctrine ORM 2.14+ adds support for validating the XSD of XML mapping files. In ORM 3.0, this validation will be mandatory. + +As the ecosystem is known to rely on custom elements in the XML mapping files that are forbidden when validating the XSD (for instance when using `gedmo/doctrine-extensions`), this validation is opt-in thanks to a `validate_xml_mapping` setting at the entity manager configuration level. + +Unless you set it to `true`, Doctrine ORM will emit deprecation messages mentioning the XSD validation. + +### Deprecations + +- `Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface` has been deprecated. Use the `#[AsDoctrineListener]` attribute instead. diff --git a/vendor/doctrine/doctrine-bundle/UPGRADE-2.12.md b/vendor/doctrine/doctrine-bundle/UPGRADE-2.12.md new file mode 100644 index 0000000..77e17d2 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/UPGRADE-2.12.md @@ -0,0 +1,13 @@ +UPGRADE FROM 2.11 to 2.12 +======================== + +Configuration +------------- + +### Controller resolver auto mapping default configuration will be changed + +The default value of `doctrine.orm.controller_resolver.auto_mapping` will be changed from `true` to `false` in 3.0. + +Auto mapping uses any route parameter that matches with a field name of the Entity to resolve as criteria in a find by query. + +If you are relying on this functionality, you will need to configure it explicitly to silence the deprecation notice. diff --git a/vendor/doctrine/doctrine-bundle/UPGRADE-2.13.md b/vendor/doctrine/doctrine-bundle/UPGRADE-2.13.md new file mode 100644 index 0000000..2b0f957 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/UPGRADE-2.13.md @@ -0,0 +1,13 @@ +UPGRADE FROM 2.12 to 2.13 +======================== + +Configuration +------------- + +### Controller resolver auto mapping deprecated + +The controller resolver auto mapping functionality has been deprecated with Symfony 7.1, and is replaced with explicit mapped route parameters. Enabling the auto mapper by default using this bundle is now deprecated as well. + +Auto mapping uses any route parameter that matches with a field name of the Entity to resolve as criteria in a find by query. + +If you are relying on this functionality, you can update your code to use explicit mapped route parameters instead. diff --git a/vendor/doctrine/doctrine-bundle/UPGRADE-3.0.md b/vendor/doctrine/doctrine-bundle/UPGRADE-3.0.md new file mode 100644 index 0000000..365cd13 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/UPGRADE-3.0.md @@ -0,0 +1,19 @@ +UPGRADE FROM 2.x to 3.0 +======================= + +Configuration +------------- + +### Controller resolver auto mapping can no longer be configured + +The `doctrine.orm.controller_resolver.auto_mapping` option now only accepts `false` as value, to disallow the usage of the controller resolver auto mapping feature by default. The configuration option will be fully removed in 4.0. + +Auto mapping used any route parameter that matches with a field name of the Entity to resolve as criteria in a find by query. This method has been deprecated in Symfony 7.1 and is replaced with mapped route parameters. + +If you were relying on this functionality, you will need to update your code to use explicit mapped route parameters instead. + +Types +----- + + * The `commented` configuration option for types is no longer supported and + deprecated. diff --git a/vendor/doctrine/doctrine-bundle/composer.json b/vendor/doctrine/doctrine-bundle/composer.json new file mode 100644 index 0000000..7dfce1c --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/composer.json @@ -0,0 +1,107 @@ +{ + "name": "doctrine/doctrine-bundle", + "description": "Symfony DoctrineBundle", + "license": "MIT", + "type": "symfony-bundle", + "keywords": [ + "DBAL", + "ORM", + "Database", + "Persistence" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org/" + } + ], + "homepage": "https://www.doctrine-project.org", + "require": { + "php": "^7.4 || ^8.0", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/persistence": "^2.2 || ^3", + "doctrine/sql-formatter": "^1.0.1", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^5.4.19 || ^6.0.7 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" + }, + "require-dev": { + "doctrine/annotations": "^1 || ^2", + "doctrine/coding-standard": "^12", + "doctrine/deprecations": "^1.0", + "doctrine/orm": "^2.17 || ^3.0", + "friendsofphp/proxy-manager-lts": "^1.0", + "phpunit/phpunit": "^9.5.26", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^5", + "psr/log": "^1.1.4 || ^2.0 || ^3.0", + "symfony/phpunit-bridge": "^6.1 || ^7.0", + "symfony/property-info": "^5.4 || ^6.0 || ^7.0", + "symfony/proxy-manager-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/security-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^5.4 || ^6.2 || ^7.0", + "symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", + "twig/twig": "^1.34 || ^2.12 || ^3.0", + "vimeo/psalm": "^5.15" + }, + "conflict": { + "doctrine/annotations": ">=3.0", + "doctrine/orm": "<2.17 || >=4.0", + "twig/twig": "<1.34 || >=2.0 <2.4" + }, + "suggest": { + "ext-pdo": "*", + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "minimum-stability": "dev", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\Tests\\": "tests", + "Fixtures\\": "tests/DependencyInjection/Fixtures" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "symfony/flex": true + }, + "sort-packages": true + }, + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/config/dbal.xml b/vendor/doctrine/doctrine-bundle/config/dbal.xml new file mode 100644 index 0000000..61fcb5e --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/config/dbal.xml @@ -0,0 +1,114 @@ + + + + + + Doctrine\DBAL\Configuration + Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector + Symfony\Bridge\Doctrine\ContainerAwareEventManager + Doctrine\Bundle\DoctrineBundle\ConnectionFactory + Doctrine\DBAL\Event\Listeners\MysqlSessionInit + Doctrine\DBAL\Event\Listeners\OracleSessionInit + Doctrine\Bundle\DoctrineBundle\Registry + + + + + + + + + + + true + + + + + %doctrine.dbal.connection_factory.types% + + + + + + + + + + + + + + + + + + + + %doctrine.connections% + %doctrine.entity_managers% + %doctrine.default_connection% + %doctrine.default_entity_manager% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/doctrine-bundle/config/messenger.xml b/vendor/doctrine/doctrine-bundle/config/messenger.xml new file mode 100644 index 0000000..1ca9faa --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/config/messenger.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + null + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/doctrine-bundle/config/middlewares.xml b/vendor/doctrine/doctrine-bundle/config/middlewares.xml new file mode 100644 index 0000000..00dad9c --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/config/middlewares.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/doctrine-bundle/config/orm.xml b/vendor/doctrine/doctrine-bundle/config/orm.xml new file mode 100644 index 0000000..b99cd85 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/config/orm.xml @@ -0,0 +1,283 @@ + + + + + + Doctrine\ORM\Configuration + Doctrine\ORM\EntityManager + Doctrine\Bundle\DoctrineBundle\ManagerConfigurator + + + Doctrine\Common\Cache\ArrayCache + Doctrine\Common\Cache\ApcCache + Doctrine\Common\Cache\MemcacheCache + localhost + 11211 + Memcache + Doctrine\Common\Cache\MemcachedCache + localhost + 11211 + Memcached + Doctrine\Common\Cache\RedisCache + localhost + 6379 + Redis + Doctrine\Common\Cache\XcacheCache + Doctrine\Common\Cache\WinCacheCache + Doctrine\Common\Cache\ZendDataCache + + + Doctrine\Persistence\Mapping\Driver\MappingDriverChain + Doctrine\ORM\Mapping\Driver\AnnotationDriver + Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver + Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver + Doctrine\ORM\Mapping\Driver\PHPDriver + Doctrine\ORM\Mapping\Driver\StaticPHPDriver + Doctrine\ORM\Mapping\Driver\AttributeDriver + + + Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer + + + Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser + + + Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator + Symfony\Bridge\Doctrine\Validator\DoctrineInitializer + + + Symfony\Bridge\Doctrine\Security\User\EntityUserProvider + + + Doctrine\ORM\Tools\ResolveTargetEntityListener + Doctrine\ORM\Tools\AttachEntityListenersListener + + + Doctrine\ORM\Mapping\DefaultNamingStrategy + Doctrine\ORM\Mapping\UnderscoreNamingStrategy + + + Doctrine\ORM\Mapping\DefaultQuoteStrategy + Doctrine\ORM\Mapping\AnsiQuoteStrategy + + + Doctrine\ORM\Mapping\DefaultTypedFieldMapper + + + Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver + + + Doctrine\ORM\Cache\DefaultCacheFactory + Doctrine\ORM\Cache\Region\DefaultRegion + Doctrine\ORM\Cache\Region\FileLockRegion + Doctrine\ORM\Cache\Logging\CacheLoggerChain + Doctrine\ORM\Cache\Logging\StatisticsCacheLogger + Doctrine\ORM\Cache\CacheConfiguration + Doctrine\ORM\Cache\RegionsConfiguration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CASE_LOWER + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + controller.argument_value_resolver + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %kernel.bundles% + + + + + diff --git a/vendor/doctrine/doctrine-bundle/config/schema/doctrine-1.0.xsd b/vendor/doctrine/doctrine-bundle/config/schema/doctrine-1.0.xsd new file mode 100644 index 0000000..8e0a2be --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/config/schema/doctrine-1.0.xsd @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/doctrine-bundle/src/Attribute/AsDoctrineListener.php b/vendor/doctrine/doctrine-bundle/src/Attribute/AsDoctrineListener.php new file mode 100644 index 0000000..ed3c7b7 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Attribute/AsDoctrineListener.php @@ -0,0 +1,19 @@ +entityManager = $entityManager; + $this->phpArrayFile = $phpArrayFile; + + parent::__construct($phpArrayFile); + } + + /** + * It must not be optional because it should be called before ProxyCacheWarmer which is not optional. + */ + public function isOptional(): bool + { + return false; + } + + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool + { + // cache already warmed up, no needs to do it again + if (is_file($this->phpArrayFile)) { + return false; + } + + $metadataFactory = $this->entityManager->getMetadataFactory(); + if ($metadataFactory instanceof AbstractClassMetadataFactory && $metadataFactory->getLoadedMetadata()) { + throw new LogicException('DoctrineMetadataCacheWarmer must load metadata first, check priority of your warmers.'); + } + + $metadataFactory->setCache($arrayAdapter); + $metadataFactory->getAllMetadata(); + + return true; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/CreateDatabaseDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/CreateDatabaseDoctrineCommand.php new file mode 100644 index 0000000..678a05c --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/CreateDatabaseDoctrineCommand.php @@ -0,0 +1,100 @@ +setName('doctrine:database:create') + ->setDescription('Creates the configured database') + ->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'The connection to use for this command') + ->addOption('if-not-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database already exists') + ->setHelp(<<<'EOT' +The %command.name% command creates the default connections database: + + php %command.full_name% + +You can also optionally specify the name of a connection to create the database for: + + php %command.full_name% --connection=default +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $connectionName = $input->getOption('connection'); + if (empty($connectionName)) { + $connectionName = $this->getDoctrine()->getDefaultConnectionName(); + } + + $connection = $this->getDoctrineConnection($connectionName); + + $ifNotExists = $input->getOption('if-not-exists'); + + $params = $connection->getParams(); + + if (isset($params['primary'])) { + $params = $params['primary']; + } + + $hasPath = isset($params['path']); + $name = $hasPath ? $params['path'] : ($params['dbname'] ?? false); + if (! $name) { + throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be created."); + } + + // Need to get rid of _every_ occurrence of dbname from connection configuration as we have already extracted all relevant info from url + /** @psalm-suppress InvalidArrayOffset Need to be compatible with DBAL < 4, which still has `$params['url']` */ + unset($params['dbname'], $params['path'], $params['url']); + + if ($connection->getDatabasePlatform() instanceof PostgreSQLPlatform) { + /** @psalm-suppress InvalidArrayOffset It's still available in DBAL 3.x that we need to support */ + $params['dbname'] = $params['default_dbname'] ?? 'postgres'; + } + + $tmpConnection = DriverManager::getConnection($params, $connection->getConfiguration()); + $schemaManager = $tmpConnection->createSchemaManager(); + $shouldNotCreateDatabase = $ifNotExists && in_array($name, $schemaManager->listDatabases()); + + // Only quote if we don't have a path + if (! $hasPath) { + $name = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name); + } + + $error = false; + try { + if ($shouldNotCreateDatabase) { + $output->writeln(sprintf('Database %s for connection named %s already exists. Skipped.', $name, $connectionName)); + } else { + $schemaManager->createDatabase($name); + $output->writeln(sprintf('Created database %s for connection named %s', $name, $connectionName)); + } + } catch (Throwable $e) { + $output->writeln(sprintf('Could not create database %s for connection named %s', $name, $connectionName)); + $output->writeln(sprintf('%s', $e->getMessage())); + $error = true; + } + + $tmpConnection->close(); + + return $error ? 1 : 0; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/DoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/DoctrineCommand.php new file mode 100644 index 0000000..866d695 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/DoctrineCommand.php @@ -0,0 +1,84 @@ +doctrine = $doctrine; + } + + /** + * get a doctrine entity generator + * + * @return EntityGenerator + * + * @psalm-suppress UndefinedDocblockClass ORM < 3 specific + */ + protected function getEntityGenerator() + { + $entityGenerator = new EntityGenerator(); + $entityGenerator->setGenerateAnnotations(false); + $entityGenerator->setGenerateStubMethods(true); + $entityGenerator->setRegenerateEntityIfExists(false); + $entityGenerator->setUpdateEntityIfExists(true); + $entityGenerator->setNumSpaces(4); + $entityGenerator->setAnnotationPrefix('ORM\\'); + + return $entityGenerator; + } + + /** + * Get a doctrine entity manager by symfony name. + * + * @param string $name + * @param int|null $shardId + * + * @return EntityManager + */ + protected function getEntityManager($name, $shardId = null) + { + $manager = $this->getDoctrine()->getManager($name); + + if ($shardId !== null) { + throw new InvalidArgumentException('Shards are not supported anymore using doctrine/dbal >= 3'); + } + + return $manager; + } + + /** + * Get a doctrine dbal connection by symfony name. + * + * @param string $name + * + * @return Connection + */ + protected function getDoctrineConnection($name) + { + return $this->getDoctrine()->getConnection($name); + } + + /** @return ManagerRegistry */ + protected function getDoctrine() + { + return $this->doctrine; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/DropDatabaseDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/DropDatabaseDoctrineCommand.php new file mode 100644 index 0000000..dc85b29 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/DropDatabaseDoctrineCommand.php @@ -0,0 +1,131 @@ +setName('doctrine:database:drop') + ->setDescription('Drops the configured database') + ->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'The connection to use for this command') + ->addOption('if-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database doesn\'t exist') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Set this parameter to execute this action') + ->setHelp(<<<'EOT' +The %command.name% command drops the default connections database: + + php %command.full_name% + +The --force parameter has to be used to actually drop the database. + +You can also optionally specify the name of a connection to drop the database for: + + php %command.full_name% --connection=default + +Be careful: All data in a given database will be lost when executing this command. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $connectionName = $input->getOption('connection'); + if (empty($connectionName)) { + $connectionName = $this->getDoctrine()->getDefaultConnectionName(); + } + + $connection = $this->getDoctrineConnection($connectionName); + + $ifExists = $input->getOption('if-exists'); + + $params = $connection->getParams(); + + if (isset($params['primary'])) { + $params = $params['primary']; + } + + $name = $params['path'] ?? ($params['dbname'] ?? false); + if (! $name) { + throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped."); + } + + /** @psalm-suppress InvalidArrayOffset Need to be compatible with DBAL < 4, which still has `$params['url']` */ + unset($params['dbname'], $params['url']); + + if ($connection->getDatabasePlatform() instanceof PostgreSQLPlatform) { + /** @psalm-suppress InvalidArrayOffset It's still available in DBAL 3.x that we need to support */ + $params['dbname'] = $params['default_dbname'] ?? 'postgres'; + } + + if (! $input->getOption('force')) { + $output->writeln('ATTENTION: This operation should not be executed in a production environment.'); + $output->writeln(''); + $output->writeln(sprintf('Would drop the database %s for connection named %s.', $name, $connectionName)); + $output->writeln('Please run the operation with --force to execute'); + $output->writeln('All data will be lost!'); + + return self::RETURN_CODE_NO_FORCE; + } + + // Reopen connection without database name set + // as some vendors do not allow dropping the database connected to. + $connection->close(); + $connection = DriverManager::getConnection($params, $connection->getConfiguration()); + $schemaManager = $connection->createSchemaManager(); + $shouldDropDatabase = ! $ifExists || in_array($name, $schemaManager->listDatabases()); + + // Only quote if we don't have a path + if (! isset($params['path'])) { + $name = $connection->getDatabasePlatform()->quoteSingleIdentifier($name); + } + + try { + if ($shouldDropDatabase) { + if ($schemaManager instanceof SQLiteSchemaManager) { + // dropDatabase() is deprecated for Sqlite + $connection->close(); + if (file_exists($name)) { + unlink($name); + } + } else { + $schemaManager->dropDatabase($name); + } + + $output->writeln(sprintf('Dropped database %s for connection named %s', $name, $connectionName)); + } else { + $output->writeln(sprintf('Database %s for connection named %s doesn\'t exist. Skipped.', $name, $connectionName)); + } + + return 0; + } catch (Throwable $e) { + $output->writeln(sprintf('Could not drop database %s for connection named %s', $name, $connectionName)); + $output->writeln(sprintf('%s', $e->getMessage())); + + return self::RETURN_CODE_NOT_DROP; + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/ImportMappingDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/ImportMappingDoctrineCommand.php new file mode 100644 index 0000000..951eea6 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/ImportMappingDoctrineCommand.php @@ -0,0 +1,163 @@ +bundles = $bundles; + } + + protected function configure(): void + { + $this + ->setName('doctrine:mapping:import') + ->addArgument('name', InputArgument::REQUIRED, 'The bundle or namespace to import the mapping information to') + ->addArgument('mapping-type', InputArgument::OPTIONAL, 'The mapping type to export the imported mapping information to') + ->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command') + ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be mapped.') + ->addOption('force', null, InputOption::VALUE_NONE, 'Force to overwrite existing mapping files.') + ->addOption('path', null, InputOption::VALUE_REQUIRED, 'The path where the files would be generated (not used when a bundle is passed).') + ->setDescription('Imports mapping information from an existing database') + ->setHelp(<<<'EOT' +The %command.name% command imports mapping information +from an existing database: + +Generate annotation mappings into the src/ directory using App as the namespace: +php %command.full_name% App\\Entity annotation --path=src/Entity + +Generate xml mappings into the config/doctrine/ directory using App as the namespace: +php %command.full_name% App\\Entity xml --path=config/doctrine + +Generate XML mappings into a bundle: +php %command.full_name% "MyCustomBundle" xml + +You can also optionally specify which entity manager to import from with the +--em option: + +php %command.full_name% "MyCustomBundle" xml --em=default + +If you don't want to map every entity that can be found in the database, use the +--filter option. It will try to match the targeted mapped entity with the +provided pattern string. + +php %command.full_name% "MyCustomBundle" xml --filter=MyMatchedEntity + +Use the --force option, if you want to override existing mapping files: + +php %command.full_name% "MyCustomBundle" xml --force +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $type = $input->getArgument('mapping-type') ?: 'xml'; + if ($type === 'yaml') { + $type = 'yml'; + } + + $namespaceOrBundle = $input->getArgument('name'); + if (isset($this->bundles[$namespaceOrBundle])) { + $bundle = $this->getApplication()->getKernel()->getBundle($namespaceOrBundle); + $namespace = $bundle->getNamespace() . '\Entity'; + + $destPath = $bundle->getPath(); + if ($type === 'annotation') { + $destPath .= '/Entity'; + } else { + $destPath .= '/Resources/config/doctrine'; + } + } else { + // assume a namespace has been passed + $namespace = $namespaceOrBundle; + $destPath = $input->getOption('path'); + if ($destPath === null) { + throw new InvalidArgumentException('The --path option is required when passing a namespace (e.g. --path=src). If you intended to pass a bundle name, check your spelling.'); + } + } + + $cme = new ClassMetadataExporter(); + $exporter = $cme->getExporter($type); + $exporter->setOverwriteExistingFiles($input->getOption('force')); + + if ($type === 'annotation') { + $entityGenerator = $this->getEntityGenerator(); + $exporter->setEntityGenerator($entityGenerator); + } + + $em = $this->getEntityManager($input->getOption('em')); + + $databaseDriver = new DatabaseDriver($em->getConnection()->getSchemaManager()); + $em->getConfiguration()->setMetadataDriverImpl($databaseDriver); + + $emName = $input->getOption('em'); + $emName = $emName ? $emName : 'default'; + + $cmf = new DisconnectedClassMetadataFactory(); + $cmf->setEntityManager($em); + $metadata = $cmf->getAllMetadata(); + $metadata = MetadataFilter::filter($metadata, $input->getOption('filter')); + if ($metadata) { + $output->writeln(sprintf('Importing mapping information from "%s" entity manager', $emName)); + foreach ($metadata as $class) { + $className = $class->name; + $class->name = $namespace . '\\' . $className; + if ($type === 'annotation') { + $path = $destPath . '/' . str_replace('\\', '.', $className) . '.php'; + } else { + $path = $destPath . '/' . str_replace('\\', '.', $className) . '.orm.' . $type; + } + + $output->writeln(sprintf(' > writing %s', $path)); + $code = $exporter->exportClassMetadata($class); + $dir = dirname($path); + if (! is_dir($dir)) { + mkdir($dir, 0775, true); + } + + file_put_contents($path, $code); + chmod($path, 0664); + } + + return 0; + } + + $output->writeln('Database does not have any mapping information.'); + $output->writeln(''); + + return 1; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearMetadataCacheDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearMetadataCacheDoctrineCommand.php new file mode 100644 index 0000000..125036c --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearMetadataCacheDoctrineCommand.php @@ -0,0 +1,31 @@ +setName('doctrine:cache:clear-metadata') + ->setDescription('Clears all metadata cache for an entity manager'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearQueryCacheDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearQueryCacheDoctrineCommand.php new file mode 100644 index 0000000..04c6f56 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearQueryCacheDoctrineCommand.php @@ -0,0 +1,31 @@ +setName('doctrine:cache:clear-query') + ->setDescription('Clears all query cache for an entity manager'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearResultCacheDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearResultCacheDoctrineCommand.php new file mode 100644 index 0000000..c813602 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ClearResultCacheDoctrineCommand.php @@ -0,0 +1,31 @@ +setName('doctrine:cache:clear-result') + ->setDescription('Clears result cache for an entity manager'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/CollectionRegionDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/CollectionRegionDoctrineCommand.php new file mode 100644 index 0000000..6189838 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/CollectionRegionDoctrineCommand.php @@ -0,0 +1,30 @@ +setName('doctrine:cache:clear-collection-region'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ConvertMappingDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ConvertMappingDoctrineCommand.php new file mode 100644 index 0000000..bd1df53 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ConvertMappingDoctrineCommand.php @@ -0,0 +1,58 @@ +setName('doctrine:mapping:convert'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } + + /** + * @param string $toType + * @param string $destPath + * + * @return AbstractExporter + */ + protected function getExporter($toType, $destPath) + { + $exporter = parent::getExporter($toType, $destPath); + assert($exporter instanceof AbstractExporter); + if ($exporter instanceof XmlExporter) { + $exporter->setExtension('.orm.xml'); + } elseif ($exporter instanceof YamlExporter) { + $exporter->setExtension('.orm.yml'); + } + + return $exporter; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/CreateSchemaDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/CreateSchemaDoctrineCommand.php new file mode 100644 index 0000000..273ab71 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/CreateSchemaDoctrineCommand.php @@ -0,0 +1,32 @@ +setName('doctrine:schema:create') + ->setDescription('Executes (or dumps) the SQL needed to generate the database schema'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/DoctrineCommandHelper.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/DoctrineCommandHelper.php new file mode 100644 index 0000000..49e0b59 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/DoctrineCommandHelper.php @@ -0,0 +1,42 @@ +getKernel()->getContainer()->get('doctrine')->getManager($emName); + assert($em instanceof EntityManagerInterface); + $helperSet = $application->getHelperSet(); + /** @psalm-suppress InvalidArgument ORM < 3 specific */ + $helperSet->set(new EntityManagerHelper($em), 'em'); + + trigger_deprecation( + 'doctrine/doctrine-bundle', + '2.7', + 'Providing an EntityManager using "%s" is deprecated. Use an instance of "%s" instead.', + EntityManagerHelper::class, + EntityManagerProvider::class, + ); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/DropSchemaDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/DropSchemaDoctrineCommand.php new file mode 100644 index 0000000..953bf7a --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/DropSchemaDoctrineCommand.php @@ -0,0 +1,31 @@ +setName('doctrine:schema:drop') + ->setDescription('Executes (or dumps) the SQL needed to drop the current database schema'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/EnsureProductionSettingsDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/EnsureProductionSettingsDoctrineCommand.php new file mode 100644 index 0000000..6603d4f --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/EnsureProductionSettingsDoctrineCommand.php @@ -0,0 +1,32 @@ +setName('doctrine:ensure-production-settings'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/EntityRegionCacheDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/EntityRegionCacheDoctrineCommand.php new file mode 100644 index 0000000..ef94386 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/EntityRegionCacheDoctrineCommand.php @@ -0,0 +1,30 @@ +setName('doctrine:cache:clear-entity-region'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/InfoDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/InfoDoctrineCommand.php new file mode 100644 index 0000000..b043fc6 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/InfoDoctrineCommand.php @@ -0,0 +1,28 @@ +setName('doctrine:mapping:info'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/OrmProxyCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/OrmProxyCommand.php new file mode 100644 index 0000000..1f3cdb2 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/OrmProxyCommand.php @@ -0,0 +1,42 @@ +entityManagerProvider = $entityManagerProvider; + + trigger_deprecation( + 'doctrine/doctrine-bundle', + '2.8', + 'Class "%s" is deprecated. Use "%s" instead.', + self::class, + parent::class, + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if (! $this->entityManagerProvider) { + DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em')); + } + + return parent::execute($input, $output); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/QueryRegionCacheDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/QueryRegionCacheDoctrineCommand.php new file mode 100644 index 0000000..2e4a9b1 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/QueryRegionCacheDoctrineCommand.php @@ -0,0 +1,30 @@ +setName('doctrine:cache:clear-query-region'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/RunDqlDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/RunDqlDoctrineCommand.php new file mode 100644 index 0000000..8051912 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/RunDqlDoctrineCommand.php @@ -0,0 +1,46 @@ +setName('doctrine:query:dql') + ->setHelp(<<<'EOT' +The %command.name% command executes the given DQL query and +outputs the results: + +php %command.full_name% "SELECT u FROM UserBundle:User u" + +You can also optional specify some additional options like what type of +hydration to use when executing the query: + +php %command.full_name% "SELECT u FROM UserBundle:User u" --hydrate=array + +Additionally you can specify the first result and maximum amount of results to +show: + +php %command.full_name% "SELECT u FROM UserBundle:User u" --first-result=0 --max-result=30 +EOT); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/RunSqlDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/RunSqlDoctrineCommand.php new file mode 100644 index 0000000..7977a4c --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/RunSqlDoctrineCommand.php @@ -0,0 +1,43 @@ +setName('doctrine:query:sql') + ->setHelp(<<<'EOT' +The %command.name% command executes the given SQL query and +outputs the results: + +php %command.full_name% "SELECT * FROM users" +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + trigger_deprecation( + 'doctrine/doctrine-bundle', + '2.2', + 'The "%s" (doctrine:query:sql) is deprecated, use dbal:run-sql command instead.', + self::class, + ); + + return parent::execute($input, $output); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/UpdateSchemaDoctrineCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/UpdateSchemaDoctrineCommand.php new file mode 100644 index 0000000..682a26c --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/UpdateSchemaDoctrineCommand.php @@ -0,0 +1,31 @@ +setName('doctrine:schema:update'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ValidateSchemaCommand.php b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ValidateSchemaCommand.php new file mode 100644 index 0000000..33d19e7 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Command/Proxy/ValidateSchemaCommand.php @@ -0,0 +1,30 @@ +setName('doctrine:schema:validate'); + + if ($this->getDefinition()->hasOption('em')) { + return; + } + + $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/ConnectionFactory.php b/vendor/doctrine/doctrine-bundle/src/ConnectionFactory.php new file mode 100644 index 0000000..2b273bd --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/ConnectionFactory.php @@ -0,0 +1,290 @@ + 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]; + + /** @var mixed[][] */ + private array $typesConfig = []; + + private DsnParser $dsnParser; + + private bool $initialized = false; + + /** @param mixed[][] $typesConfig */ + public function __construct(array $typesConfig, ?DsnParser $dsnParser = null) + { + $this->typesConfig = $typesConfig; + $this->dsnParser = $dsnParser ?? new DsnParser(self::DEFAULT_SCHEME_MAP); + } + + /** + * Create a connection by name. + * + * @param mixed[] $params + * @param array $mappingTypes + * @psalm-param Params $params + * + * @return Connection + */ + public function createConnection(array $params, ?Configuration $config = null, ?EventManager $eventManager = null, array $mappingTypes = []) + { + if (! method_exists(Connection::class, 'getEventManager') && $eventManager !== null) { + throw new InvalidArgumentException('Passing an EventManager instance is not supported with DBAL > 3'); + } + + if (! $this->initialized) { + $this->initializeTypes(); + } + + $overriddenOptions = []; + /** @psalm-suppress InvalidArrayOffset We should adjust when https://github.com/vimeo/psalm/issues/8984 is fixed */ + if (isset($params['connection_override_options'])) { + trigger_deprecation('doctrine/doctrine-bundle', '2.4', 'The "connection_override_options" connection parameter is deprecated'); + $overriddenOptions = $params['connection_override_options']; + unset($params['connection_override_options']); + } + + $params = $this->parseDatabaseUrl($params); + + // URL support for PrimaryReplicaConnection + if (isset($params['primary'])) { + $params['primary'] = $this->parseDatabaseUrl($params['primary']); + } + + if (isset($params['replica'])) { + foreach ($params['replica'] as $key => $replicaParams) { + $params['replica'][$key] = $this->parseDatabaseUrl($replicaParams); + } + } + + /** @psalm-suppress InvalidArrayOffset We should adjust when https://github.com/vimeo/psalm/issues/8984 is fixed */ + if (! isset($params['pdo']) && (! isset($params['charset']) || $overriddenOptions || isset($params['dbname_suffix']))) { + $wrapperClass = null; + + if (isset($params['wrapperClass'])) { + if (! is_subclass_of($params['wrapperClass'], Connection::class)) { + if (class_exists(InvalidWrapperClass::class)) { + throw InvalidWrapperClass::new($params['wrapperClass']); + } + + throw DBALException::invalidWrapperClass($params['wrapperClass']); + } + + $wrapperClass = $params['wrapperClass']; + $params['wrapperClass'] = null; + } + + $connection = DriverManager::getConnection(...array_merge([$params, $config], $eventManager ? [$eventManager] : [])); + $params = $this->addDatabaseSuffix(array_merge($connection->getParams(), $overriddenOptions)); + $driver = $connection->getDriver(); + /** @psalm-suppress InvalidScalarArgument Bogus error, StaticServerVersionProvider implements Doctrine\DBAL\ServerVersionProvider */ + $platform = $driver->getDatabasePlatform( + ...(class_exists(StaticServerVersionProvider::class) + ? [new StaticServerVersionProvider($params['serverVersion'] ?? $params['primary']['serverVersion'] ?? '')] + : [] + ), + ); + + if (! isset($params['charset'])) { + if ($platform instanceof AbstractMySQLPlatform) { + $params['charset'] = 'utf8mb4'; + + if (isset($params['defaultTableOptions']['collate'])) { + Deprecation::trigger( + 'doctrine/doctrine-bundle', + 'https://github.com/doctrine/dbal/issues/5214', + 'The "collate" default table option is deprecated in favor of "collation" and will be removed in doctrine/doctrine-bundle 3.0. ', + ); + $params['defaultTableOptions']['collation'] = $params['defaultTableOptions']['collate']; + unset($params['defaultTableOptions']['collate']); + } + + if (! isset($params['defaultTableOptions']['collation'])) { + $params['defaultTableOptions']['collation'] = 'utf8mb4_unicode_ci'; + } + } else { + $params['charset'] = 'utf8'; + } + } + + if ($wrapperClass !== null) { + $params['wrapperClass'] = $wrapperClass; + } else { + $wrapperClass = Connection::class; + } + + $connection = new $wrapperClass($params, $driver, $config, $eventManager); + } else { + $connection = DriverManager::getConnection(...array_merge([$params, $config], $eventManager ? [$eventManager] : [])); + } + + if (! empty($mappingTypes)) { + $platform = $this->getDatabasePlatform($connection); + foreach ($mappingTypes as $dbType => $doctrineType) { + $platform->registerDoctrineTypeMapping($dbType, $doctrineType); + } + } + + return $connection; + } + + /** + * Try to get the database platform. + * + * This could fail if types should be registered to an predefined/unused connection + * and the platform version is unknown. + * + * @link https://github.com/doctrine/DoctrineBundle/issues/673 + * + * @throws DBALException + */ + private function getDatabasePlatform(Connection $connection): AbstractPlatform + { + try { + return $connection->getDatabasePlatform(); + } catch (DriverException $driverException) { + $class = class_exists(DBALException::class) ? DBALException::class : ConnectionException::class; + + throw new $class( + 'An exception occurred while establishing a connection to figure out your platform version.' . PHP_EOL . + "You can circumvent this by setting a 'server_version' configuration value" . PHP_EOL . PHP_EOL . + 'For further information have a look at:' . PHP_EOL . + 'https://github.com/doctrine/DoctrineBundle/issues/673', + 0, + $driverException, + ); + } + } + + /** + * initialize the types + */ + private function initializeTypes(): void + { + foreach ($this->typesConfig as $typeName => $typeConfig) { + if (Type::hasType($typeName)) { + Type::overrideType($typeName, $typeConfig['class']); + } else { + Type::addType($typeName, $typeConfig['class']); + } + } + + $this->initialized = true; + } + + /** + * @param array $params + * + * @return array + */ + private function addDatabaseSuffix(array $params): array + { + if (isset($params['dbname']) && isset($params['dbname_suffix'])) { + $params['dbname'] .= $params['dbname_suffix']; + } + + foreach ($params['replica'] ?? [] as $key => $replicaParams) { + if (! isset($replicaParams['dbname'], $replicaParams['dbname_suffix'])) { + continue; + } + + $params['replica'][$key]['dbname'] .= $replicaParams['dbname_suffix']; + } + + if (isset($params['primary']['dbname'], $params['primary']['dbname_suffix'])) { + $params['primary']['dbname'] .= $params['primary']['dbname_suffix']; + } + + return $params; + } + + /** + * Extracts parts from a database URL, if present, and returns an + * updated list of parameters. + * + * @param mixed[] $params The list of parameters. + * @psalm-param Params $params + * + * @return mixed[] A modified list of parameters with info from a database + * URL extracted into individual parameter parts. + * @psalm-return Params + * + * @throws DBALException + */ + private function parseDatabaseUrl(array $params): array + { + /** @psalm-suppress InvalidArrayOffset Need to be compatible with DBAL < 4, which still has `$params['url']` */ + if (! isset($params['url'])) { + return $params; + } + + try { + $parsedParams = $this->dsnParser->parse($params['url']); + } catch (MalformedDsnException $e) { + throw new MalformedDsnException('Malformed parameter "url".', 0, $e); + } + + if (isset($parsedParams['driver'])) { + // The requested driver from the URL scheme takes precedence + // over the default custom driver from the connection parameters (if any). + unset($params['driverClass']); + } + + $params = array_merge($params, $parsedParams); + + // If a schemeless connection URL is given, we require a default driver or default custom driver + // as connection parameter. + if (! isset($params['driverClass']) && ! isset($params['driver'])) { + if (class_exists(DriverRequired::class)) { + throw DriverRequired::new($params['url']); + } + + throw DBALException::driverRequired($params['url']); + } + + unset($params['url']); + + return $params; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Controller/ProfilerController.php b/vendor/doctrine/doctrine-bundle/src/Controller/ProfilerController.php new file mode 100644 index 0000000..d85cda0 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Controller/ProfilerController.php @@ -0,0 +1,132 @@ +twig = $twig; + $this->registry = $registry; + $this->profiler = $profiler; + } + + /** + * Renders the profiler panel for the given token. + * + * @param string $token The profiler token + * @param string $connectionName + * @param int $query + * + * @return Response A Response instance + */ + public function explainAction($token, $connectionName, $query) + { + $this->profiler->disable(); + + $profile = $this->profiler->loadProfile($token); + $collector = $profile->getCollector('db'); + + assert($collector instanceof DoctrineDataCollector); + + $queries = $collector->getQueries(); + + if (! isset($queries[$connectionName][$query])) { + return new Response('This query does not exist.'); + } + + $query = $queries[$connectionName][$query]; + if (! $query['explainable']) { + return new Response('This query cannot be explained.'); + } + + $connection = $this->registry->getConnection($connectionName); + assert($connection instanceof Connection); + try { + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof SQLitePlatform) { + $results = $this->explainSQLitePlatform($connection, $query); + } elseif ($platform instanceof SQLServerPlatform) { + throw new Exception('Explain for SQLServerPlatform is currently not supported. Contributions are welcome.'); + } elseif ($platform instanceof OraclePlatform) { + $results = $this->explainOraclePlatform($connection, $query); + } else { + $results = $this->explainOtherPlatform($connection, $query); + } + } catch (Throwable $e) { + return new Response('This query cannot be explained.'); + } + + return new Response($this->twig->render('@Doctrine/Collector/explain.html.twig', [ + 'data' => $results, + 'query' => $query, + ])); + } + + /** + * @param mixed[] $query + * + * @return mixed[] + */ + private function explainSQLitePlatform(Connection $connection, array $query): array + { + $params = $query['params']; + + if ($params instanceof Data) { + $params = $params->getValue(true); + } + + return $connection->executeQuery('EXPLAIN QUERY PLAN ' . $query['sql'], $params, $query['types']) + ->fetchAllAssociative(); + } + + /** + * @param mixed[] $query + * + * @return mixed[] + */ + private function explainOtherPlatform(Connection $connection, array $query): array + { + $params = $query['params']; + + if ($params instanceof Data) { + $params = $params->getValue(true); + } + + return $connection->executeQuery('EXPLAIN ' . $query['sql'], $params, $query['types']) + ->fetchAllAssociative(); + } + + /** + * @param mixed[] $query + * + * @return mixed[] + */ + private function explainOraclePlatform(Connection $connection, array $query): array + { + $connection->executeQuery('EXPLAIN PLAN FOR ' . $query['sql']); + + return $connection->executeQuery('SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY())') + ->fetchAllAssociative(); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DataCollector/DoctrineDataCollector.php b/vendor/doctrine/doctrine-bundle/src/DataCollector/DoctrineDataCollector.php new file mode 100644 index 0000000..2ecd7a4 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DataCollector/DoctrineDataCollector.php @@ -0,0 +1,305 @@ +, + * runnable: bool, + * types: ?array, + * } + * @psalm-type DataType = array{ + * caches: array{ + * enabled: bool, + * counts: array<"puts"|"hits"|"misses", int>, + * log_enabled: bool, + * regions: array<"puts"|"hits"|"misses", array>, + * }, + * connections: list, + * entities: array>, + * errors: array>>, + * managers: list, + * queries: array>, + * } + * @psalm-property DataType $data + */ +class DoctrineDataCollector extends BaseCollector +{ + private ManagerRegistry $registry; + private ?int $invalidEntityCount = null; + + /** + * @var mixed[][]|null + * @psalm-var ?array> + */ + private ?array $groupedQueries = null; + + private bool $shouldValidateSchema; + + public function __construct(ManagerRegistry $registry, bool $shouldValidateSchema = true, ?DebugDataHolder $debugDataHolder = null) + { + $this->registry = $registry; + $this->shouldValidateSchema = $shouldValidateSchema; + + parent::__construct($registry, $debugDataHolder); + } + + public function collect(Request $request, Response $response, ?Throwable $exception = null): void + { + parent::collect($request, $response, $exception); + + $errors = []; + $entities = []; + $caches = [ + 'enabled' => false, + 'log_enabled' => false, + 'counts' => [ + 'puts' => 0, + 'hits' => 0, + 'misses' => 0, + ], + 'regions' => [ + 'puts' => [], + 'hits' => [], + 'misses' => [], + ], + ]; + + foreach ($this->registry->getManagers() as $name => $em) { + assert($em instanceof EntityManagerInterface); + if ($this->shouldValidateSchema) { + $entities[$name] = []; + + $factory = $em->getMetadataFactory(); + $validator = new SchemaValidator($em); + + assert($factory instanceof AbstractClassMetadataFactory); + + foreach ($factory->getLoadedMetadata() as $class) { + assert($class instanceof ClassMetadata); + if (isset($entities[$name][$class->getName()])) { + continue; + } + + $classErrors = $validator->validateClass($class); + $r = $class->getReflectionClass(); + $entities[$name][$class->getName()] = [ + 'class' => $class->getName(), + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + if (empty($classErrors)) { + continue; + } + + $errors[$name][$class->getName()] = $classErrors; + } + } + + $emConfig = $em->getConfiguration(); + $slcEnabled = $emConfig->isSecondLevelCacheEnabled(); + + if (! $slcEnabled) { + continue; + } + + $caches['enabled'] = true; + + $cacheConfiguration = $emConfig->getSecondLevelCacheConfiguration(); + assert($cacheConfiguration instanceof CacheConfiguration); + $cacheLoggerChain = $cacheConfiguration->getCacheLogger(); + assert($cacheLoggerChain instanceof CacheLoggerChain || $cacheLoggerChain === null); + + if (! $cacheLoggerChain || ! $cacheLoggerChain->getLogger('statistics')) { + continue; + } + + $cacheLoggerStats = $cacheLoggerChain->getLogger('statistics'); + assert($cacheLoggerStats instanceof StatisticsCacheLogger); + $caches['log_enabled'] = true; + + $caches['counts']['puts'] += $cacheLoggerStats->getPutCount(); + $caches['counts']['hits'] += $cacheLoggerStats->getHitCount(); + $caches['counts']['misses'] += $cacheLoggerStats->getMissCount(); + + foreach ($cacheLoggerStats->getRegionsPut() as $key => $value) { + if (! isset($caches['regions']['puts'][$key])) { + $caches['regions']['puts'][$key] = 0; + } + + $caches['regions']['puts'][$key] += $value; + } + + foreach ($cacheLoggerStats->getRegionsHit() as $key => $value) { + if (! isset($caches['regions']['hits'][$key])) { + $caches['regions']['hits'][$key] = 0; + } + + $caches['regions']['hits'][$key] += $value; + } + + foreach ($cacheLoggerStats->getRegionsMiss() as $key => $value) { + if (! isset($caches['regions']['misses'][$key])) { + $caches['regions']['misses'][$key] = 0; + } + + $caches['regions']['misses'][$key] += $value; + } + } + + $this->data['entities'] = $entities; + $this->data['errors'] = $errors; + $this->data['caches'] = $caches; + $this->groupedQueries = null; + } + + /** @return array> */ + public function getEntities() + { + return $this->data['entities']; + } + + /** @return array>> */ + public function getMappingErrors() + { + return $this->data['errors']; + } + + /** @return int */ + public function getCacheHitsCount() + { + return $this->data['caches']['counts']['hits']; + } + + /** @return int */ + public function getCachePutsCount() + { + return $this->data['caches']['counts']['puts']; + } + + /** @return int */ + public function getCacheMissesCount() + { + return $this->data['caches']['counts']['misses']; + } + + /** @return bool */ + public function getCacheEnabled() + { + return $this->data['caches']['enabled']; + } + + /** + * @return array> + * @psalm-return array<"puts"|"hits"|"misses", array> + */ + public function getCacheRegions() + { + return $this->data['caches']['regions']; + } + + /** @return array */ + public function getCacheCounts() + { + return $this->data['caches']['counts']; + } + + /** @return int */ + public function getInvalidEntityCount() + { + return $this->invalidEntityCount ??= array_sum(array_map('count', $this->data['errors'])); + } + + /** + * @return string[][] + * @psalm-return array> + */ + public function getGroupedQueries() + { + if ($this->groupedQueries !== null) { + return $this->groupedQueries; + } + + $this->groupedQueries = []; + $totalExecutionMS = 0; + foreach ($this->data['queries'] as $connection => $queries) { + $connectionGroupedQueries = []; + foreach ($queries as $i => $query) { + $key = $query['sql']; + if (! isset($connectionGroupedQueries[$key])) { + $connectionGroupedQueries[$key] = $query; + $connectionGroupedQueries[$key]['executionMS'] = 0; + $connectionGroupedQueries[$key]['count'] = 0; + $connectionGroupedQueries[$key]['index'] = $i; // "Explain query" relies on query index in 'queries'. + } + + $connectionGroupedQueries[$key]['executionMS'] += $query['executionMS']; + $connectionGroupedQueries[$key]['count']++; + $totalExecutionMS += $query['executionMS']; + } + + usort($connectionGroupedQueries, static function ($a, $b) { + if ($a['executionMS'] === $b['executionMS']) { + return 0; + } + + return $a['executionMS'] < $b['executionMS'] ? 1 : -1; + }); + $this->groupedQueries[$connection] = $connectionGroupedQueries; + } + + foreach ($this->groupedQueries as $connection => $queries) { + foreach ($queries as $i => $query) { + $this->groupedQueries[$connection][$i]['executionPercent'] = + $this->executionTimePercentage($query['executionMS'], $totalExecutionMS); + } + } + + return $this->groupedQueries; + } + + private function executionTimePercentage(float $executionTimeMS, float $totalExecutionTimeMS): float + { + if (! $totalExecutionTimeMS) { + return 0; + } + + return $executionTimeMS / $totalExecutionTimeMS * 100; + } + + /** @return int */ + public function getGroupedQueryCount() + { + $count = 0; + foreach ($this->getGroupedQueries() as $connectionGroupedQueries) { + $count += count($connectionGroupedQueries); + } + + return $count; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Dbal/BlacklistSchemaAssetFilter.php b/vendor/doctrine/doctrine-bundle/src/Dbal/BlacklistSchemaAssetFilter.php new file mode 100644 index 0000000..e63a2ec --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Dbal/BlacklistSchemaAssetFilter.php @@ -0,0 +1,30 @@ +blacklist = $blacklist; + } + + /** @param string|AbstractAsset $assetName */ + public function __invoke($assetName): bool + { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return ! in_array($assetName, $this->blacklist, true); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Dbal/ManagerRegistryAwareConnectionProvider.php b/vendor/doctrine/doctrine-bundle/src/Dbal/ManagerRegistryAwareConnectionProvider.php new file mode 100644 index 0000000..7143a66 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Dbal/ManagerRegistryAwareConnectionProvider.php @@ -0,0 +1,27 @@ +managerRegistry = $managerRegistry; + } + + public function getDefaultConnection(): Connection + { + return $this->managerRegistry->getConnection(); + } + + public function getConnection(string $name): Connection + { + return $this->managerRegistry->getConnection($name); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Dbal/RegexSchemaAssetFilter.php b/vendor/doctrine/doctrine-bundle/src/Dbal/RegexSchemaAssetFilter.php new file mode 100644 index 0000000..c60cdea --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Dbal/RegexSchemaAssetFilter.php @@ -0,0 +1,27 @@ +filterExpression = $filterExpression; + } + + /** @param string|AbstractAsset $assetName */ + public function __invoke($assetName): bool + { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return (bool) preg_match($this->filterExpression, $assetName); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Dbal/SchemaAssetsFilterManager.php b/vendor/doctrine/doctrine-bundle/src/Dbal/SchemaAssetsFilterManager.php new file mode 100644 index 0000000..18cc740 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Dbal/SchemaAssetsFilterManager.php @@ -0,0 +1,32 @@ +schemaAssetFilters = $schemaAssetFilters; + } + + /** @param string|AbstractAsset $assetName */ + public function __invoke($assetName): bool + { + foreach ($this->schemaAssetFilters as $schemaAssetFilter) { + if ($schemaAssetFilter($assetName) === false) { + return false; + } + } + + return true; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/CacheCompatibilityPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/CacheCompatibilityPass.php new file mode 100644 index 0000000..cefe51b --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/CacheCompatibilityPass.php @@ -0,0 +1,125 @@ +findTaggedServiceIds(self::CONFIGURATION_TAG)) as $id) { + foreach ($container->getDefinition($id)->getMethodCalls() as $methodCall) { + if ($methodCall[0] === 'setSecondLevelCacheConfiguration') { + $this->updateSecondLevelCache($container, $methodCall[1][0]); + continue; + } + + if (! in_array($methodCall[0], self::CACHE_METHODS_PSR6_SUPPORT, true)) { + continue; + } + + $aliasId = (string) $methodCall[1][0]; + $definitionId = (string) $container->getAlias($aliasId); + + $this->wrapIfNecessary($container, $aliasId, $definitionId); + } + } + } + + private function updateSecondLevelCache(ContainerBuilder $container, Definition $slcConfigDefinition): void + { + foreach ($slcConfigDefinition->getMethodCalls() as $methodCall) { + if ($methodCall[0] !== 'setCacheFactory') { + continue; + } + + $factoryDefinition = $methodCall[1][0]; + assert($factoryDefinition instanceof Definition); + $aliasId = (string) $factoryDefinition->getArgument(1); + $this->wrapIfNecessary($container, $aliasId, (string) $container->getAlias($aliasId)); + foreach ($factoryDefinition->getMethodCalls() as $factoryMethodCall) { + if ($factoryMethodCall[0] !== 'setRegion') { + continue; + } + + $regionDefinition = $container->getDefinition($factoryMethodCall[1][0]); + + // Get inner service for FileLock + if ($regionDefinition->getClass() === '%doctrine.orm.second_level_cache.filelock_region.class%') { + $regionDefinition = $container->getDefinition($regionDefinition->getArgument(0)); + } + + // We don't know how to adjust custom region classes + if ($regionDefinition->getClass() !== '%doctrine.orm.second_level_cache.default_region.class%') { + continue; + } + + $driverId = (string) $regionDefinition->getArgument(1); + if (! $container->hasAlias($driverId)) { + continue; + } + + $this->wrapIfNecessary($container, $driverId, (string) $container->getAlias($driverId)); + } + + break; + } + } + + private function createCompatibilityLayerDefinition(ContainerBuilder $container, string $definitionId): ?Definition + { + $definition = $container->getDefinition($definitionId); + + while (! $definition->getClass() && $definition instanceof ChildDefinition) { + $definition = $container->findDefinition($definition->getParent()); + } + + if (is_a($definition->getClass(), CacheItemPoolInterface::class, true)) { + return null; + } + + trigger_deprecation( + 'doctrine/doctrine-bundle', + '2.4', + 'Configuring doctrine/cache is deprecated. Please update the cache service "%s" to use a PSR-6 cache.', + $definitionId, + ); + + return (new Definition(CacheItemPoolInterface::class)) + ->setFactory([CacheAdapter::class, 'wrap']) + ->addArgument(new Reference($definitionId)); + } + + private function wrapIfNecessary(ContainerBuilder $container, string $aliasId, string $definitionId): void + { + $compatibilityLayer = $this->createCompatibilityLayerDefinition($container, $definitionId); + if ($compatibilityLayer === null) { + return; + } + + $compatibilityLayerId = $definitionId . '.compatibility_layer'; + $container->setAlias($aliasId, $compatibilityLayerId); + $container->setDefinition($compatibilityLayerId, $compatibilityLayer); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/CacheSchemaSubscriberPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/CacheSchemaSubscriberPass.php new file mode 100644 index 0000000..a9b7f9e --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/CacheSchemaSubscriberPass.php @@ -0,0 +1,55 @@ +injectAdapters($container, 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_subscriber', DoctrineDbalAdapter::class); + + $this->injectAdapters($container, 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_listener', DoctrineDbalAdapter::class); + + // available in Symfony 5.1 and up to Symfony 5.4 (deprecated) + $this->injectAdapters($container, 'doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber', PdoAdapter::class); + } + + private function injectAdapters(ContainerBuilder $container, string $subscriberId, string $class) + { + if (! $container->hasDefinition($subscriberId)) { + return; + } + + $subscriber = $container->getDefinition($subscriberId); + + $cacheAdaptersReferences = []; + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isAbstract() || $definition->isSynthetic()) { + continue; + } + + if ($definition->getClass() !== $class) { + continue; + } + + $cacheAdaptersReferences[] = new Reference($id); + } + + $subscriber->replaceArgument(0, $cacheAdaptersReferences); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/DbalSchemaFilterPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/DbalSchemaFilterPass.php new file mode 100644 index 0000000..c1d3c7d --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/DbalSchemaFilterPass.php @@ -0,0 +1,53 @@ +findTaggedServiceIds('doctrine.dbal.schema_filter'); + + $connectionFilters = []; + foreach ($filters as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + $name = $attributes['connection'] ?? $container->getParameter('doctrine.default_connection'); + + if (! isset($connectionFilters[$name])) { + $connectionFilters[$name] = []; + } + + $connectionFilters[$name][] = new Reference($id); + } + } + + foreach ($connectionFilters as $name => $references) { + $configurationId = sprintf('doctrine.dbal.%s_connection.configuration', $name); + + if (! $container->hasDefinition($configurationId)) { + continue; + } + + $definition = new ChildDefinition('doctrine.dbal.schema_asset_filter_manager'); + $definition->setArgument(0, $references); + + $id = sprintf('doctrine.dbal.%s_schema_asset_filter_manager', $name); + $container->setDefinition($id, $definition); + $container->findDefinition($configurationId) + ->addMethodCall('setSchemaAssetsFilter', [new Reference($id)]); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/DoctrineOrmMappingsPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/DoctrineOrmMappingsPass.php new file mode 100644 index 0000000..3a3a3ca --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/DoctrineOrmMappingsPass.php @@ -0,0 +1,183 @@ +findAndSortTaggedServices('doctrine.orm.entity_listener', $container); + + $lazyServiceReferencesByResolver = []; + + foreach ($resolvers as $reference) { + $id = $reference->__toString(); + foreach ($container->getDefinition($id)->getTag('doctrine.orm.entity_listener') as $attributes) { + $name = $attributes['entity_manager'] ?? $container->getParameter('doctrine.default_entity_manager'); + $entityManager = sprintf('doctrine.orm.%s_entity_manager', $name); + + if (! $container->hasDefinition($entityManager)) { + continue; + } + + $resolverId = sprintf('doctrine.orm.%s_entity_listener_resolver', $name); + + if (! $container->has($resolverId)) { + continue; + } + + $resolver = $container->findDefinition($resolverId); + $resolver->setPublic(true); + + if (isset($attributes['entity'])) { + $this->attachToListener($container, $name, $this->getConcreteDefinitionClass($container->findDefinition($id), $container, $id), $attributes); + } + + $resolverClass = $this->getResolverClass($resolver, $container, $resolverId); + $resolverSupportsLazyListeners = is_a($resolverClass, EntityListenerServiceResolver::class, true); + + $lazyByAttribute = isset($attributes['lazy']) && $attributes['lazy']; + if ($lazyByAttribute && ! $resolverSupportsLazyListeners) { + throw new InvalidArgumentException(sprintf( + 'Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.', + EntityListenerServiceResolver::class, + )); + } + + if (! isset($attributes['lazy']) && $resolverSupportsLazyListeners || $lazyByAttribute) { + $listener = $container->findDefinition($id); + + $resolver->addMethodCall('registerService', [$this->getConcreteDefinitionClass($listener, $container, $id), $id]); + + // if the resolver uses the default class we will use a service locator for all listeners + if ($resolverClass === ContainerEntityListenerResolver::class) { + if (! isset($lazyServiceReferencesByResolver[$resolverId])) { + $lazyServiceReferencesByResolver[$resolverId] = []; + } + + $lazyServiceReferencesByResolver[$resolverId][$id] = new Reference($id); + } else { + $listener->setPublic(true); + } + } else { + $resolver->addMethodCall('register', [new Reference($id)]); + } + } + } + + foreach ($lazyServiceReferencesByResolver as $resolverId => $listenerReferences) { + $container->findDefinition($resolverId)->setArgument(0, ServiceLocatorTagPass::register($container, $listenerReferences)); + } + } + + /** @param array{entity: class-string, event?: ?string, method?: string} $attributes */ + private function attachToListener(ContainerBuilder $container, string $name, string $class, array $attributes): void + { + $listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $name); + + if (! $container->has($listenerId)) { + return; + } + + $args = [ + $attributes['entity'], + $class, + $attributes['event'] ?? null, + ]; + + if (isset($attributes['method'])) { + $args[] = $attributes['method']; + } elseif (isset($attributes['event']) && ! method_exists($class, $attributes['event']) && method_exists($class, '__invoke')) { + $args[] = '__invoke'; + } + + $container->findDefinition($listenerId)->addMethodCall('addEntityListener', $args); + } + + private function getResolverClass(Definition $resolver, ContainerBuilder $container, string $id): string + { + $resolverClass = $this->getConcreteDefinitionClass($resolver, $container, $id); + + if (substr($resolverClass, 0, 1) === '%') { + // resolve container parameter first + $resolverClass = $container->getParameterBag()->resolveValue($resolverClass); + } + + return $resolverClass; + } + + private function getConcreteDefinitionClass(Definition $definition, ContainerBuilder $container, string $id): string + { + $class = $definition->getClass(); + if ($class) { + return $class; + } + + while ($definition instanceof ChildDefinition) { + $definition = $container->findDefinition($definition->getParent()); + + $class = $definition->getClass(); + if ($class) { + return $class; + } + } + + throw new InvalidArgumentException(sprintf('The service "%s" must define its class.', $id)); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/IdGeneratorPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/IdGeneratorPass.php new file mode 100644 index 0000000..e2d52c3 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/IdGeneratorPass.php @@ -0,0 +1,80 @@ +findTaggedServiceIds(self::ID_GENERATOR_TAG)); + + // when ORM is not enabled + if (! $container->hasDefinition('doctrine.orm.configuration') || ! $generatorIds) { + return; + } + + $generatorRefs = array_map(static function ($id) { + return new Reference($id); + }, $generatorIds); + + $ref = ServiceLocatorTagPass::register($container, array_combine($generatorIds, $generatorRefs)); + $container->setAlias('doctrine.id_generator_locator', new Alias((string) $ref, false)); + + foreach ($container->findTaggedServiceIds(self::CONFIGURATION_TAG) as $id => $tags) { + $configurationDef = $container->getDefinition($id); + $methodCalls = $configurationDef->getMethodCalls(); + $metadataDriverImpl = null; + + foreach ($methodCalls as $i => [$method, $arguments]) { + if ($method === 'setMetadataDriverImpl') { + $metadataDriverImpl = (string) $arguments[0]; + } + + if ($method !== 'setClassMetadataFactoryName') { + continue; + } + + if ($arguments[0] !== ORMClassMetadataFactory::class && $arguments[0] !== ClassMetadataFactory::class) { + $class = $container->getReflectionClass($arguments[0]); + + if ($class && $class->isSubclassOf(ClassMetadataFactory::class)) { + break; + } + + continue 2; + } + + $methodCalls[$i] = ['setClassMetadataFactoryName', [ClassMetadataFactory::class]]; + } + + if ($metadataDriverImpl === null) { + continue; + } + + $configurationDef->setMethodCalls($methodCalls); + $container->register('.' . $metadataDriverImpl, MappingDriver::class) + ->setDecoratedService($metadataDriverImpl) + ->setArguments([ + new Reference(sprintf('.%s.inner', $metadataDriverImpl)), + new Reference('doctrine.id_generator_locator'), + ]); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/MiddlewaresPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/MiddlewaresPass.php new file mode 100644 index 0000000..734c5b9 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/MiddlewaresPass.php @@ -0,0 +1,88 @@ +hasParameter('doctrine.connections')) { + return; + } + + $middlewareAbstractDefs = []; + $middlewareConnections = []; + $middlewarePriorities = []; + foreach ($container->findTaggedServiceIds('doctrine.middleware') as $id => $tags) { + $middlewareAbstractDefs[$id] = $container->getDefinition($id); + // When a def has doctrine.middleware tags with connection attributes equal to connection names + // registration of this middleware is limited to the connections with these names + foreach ($tags as $tag) { + if (! isset($tag['connection'])) { + if (isset($tag['priority']) && ! isset($middlewarePriorities[$id])) { + $middlewarePriorities[$id] = $tag['priority']; + } + + continue; + } + + $middlewareConnections[$id][$tag['connection']] = $tag['priority'] ?? null; + } + } + + foreach (array_keys($container->getParameter('doctrine.connections')) as $name) { + $middlewareRefs = []; + $i = 0; + foreach ($middlewareAbstractDefs as $id => $abstractDef) { + if (isset($middlewareConnections[$id]) && ! array_key_exists($name, $middlewareConnections[$id])) { + continue; + } + + $childDef = $container->setDefinition( + $childId = sprintf('%s.%s', $id, $name), + (new ChildDefinition($id)) + ->setTags($abstractDef->getTags())->clearTag('doctrine.middleware') + ->setAutoconfigured($abstractDef->isAutoconfigured()) + ->setAutowired($abstractDef->isAutowired()), + ); + $middlewareRefs[$id] = [new Reference($childId), ++$i]; + + if (! is_subclass_of($abstractDef->getClass(), ConnectionNameAwareInterface::class)) { + continue; + } + + $childDef->addMethodCall('setConnectionName', [$name]); + } + + $middlewareRefs = array_map( + static fn (string $id, array $ref) => [ + $middlewareConnections[$id][$name] ?? $middlewarePriorities[$id] ?? 0, + $ref[1], + $ref[0], + ], + array_keys($middlewareRefs), + array_values($middlewareRefs), + ); + uasort($middlewareRefs, static fn (array $a, array $b): int => $b[0] <=> $a[0] ?: $a[1] <=> $b[1]); + $middlewareRefs = array_map(static fn (array $value): Reference => $value[2], $middlewareRefs); + + $container + ->getDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name)) + ->addMethodCall('setMiddlewares', [$middlewareRefs]); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/RemoveLoggingMiddlewarePass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/RemoveLoggingMiddlewarePass.php new file mode 100644 index 0000000..331e0cb --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/RemoveLoggingMiddlewarePass.php @@ -0,0 +1,19 @@ +has('logger')) { + return; + } + + $container->removeDefinition('doctrine.dbal.logging_middleware'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/RemoveProfilerControllerPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/RemoveProfilerControllerPass.php new file mode 100644 index 0000000..e070cec --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/RemoveProfilerControllerPass.php @@ -0,0 +1,20 @@ +has('twig') && $container->has('profiler')) { + return; + } + + $container->removeDefinition(ProfilerController::class); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/ServiceRepositoryCompilerPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/ServiceRepositoryCompilerPass.php new file mode 100644 index 0000000..5ecbdf5 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/ServiceRepositoryCompilerPass.php @@ -0,0 +1,36 @@ +hasDefinition('doctrine.orm.container_repository_factory')) { + return; + } + + $locatorDef = $container->getDefinition('doctrine.orm.container_repository_factory'); + + $repoServiceIds = array_keys($container->findTaggedServiceIds(self::REPOSITORY_SERVICE_TAG)); + + $repoReferences = array_map(static function ($id) { + return new Reference($id); + }, $repoServiceIds); + + $ref = ServiceLocatorTagPass::register($container, array_combine($repoServiceIds, $repoReferences)); + $locatorDef->replaceArgument(0, $ref); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php new file mode 100644 index 0000000..7017b26 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php @@ -0,0 +1,55 @@ +getDefinitions() as $definition) { + if ($definition->isAbstract() || $definition->isSynthetic()) { + continue; + } + + if ($definition->getClass() !== PdoSessionHandler::class) { + continue; + } + + $table = $definition->getArguments()[1]['db_table'] ?? 'sessions'; + + if (! method_exists($definition->getClass(), 'configureSchema')) { + $blacklist[] = $table; + } + + break; + } + + if (! $blacklist) { + return; + } + + $definition = $container->getDefinition('doctrine.dbal.well_known_schema_asset_filter'); + $definition->replaceArgument(0, $blacklist); + + foreach (array_keys($container->getParameter('doctrine.connections')) as $name) { + $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Configuration.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..06cf3db --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/Configuration.php @@ -0,0 +1,893 @@ +debug = $debug; + } + + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('doctrine'); + $rootNode = $treeBuilder->getRootNode(); + + $this->addDbalSection($rootNode); + $this->addOrmSection($rootNode); + + return $treeBuilder; + } + + /** + * Add DBAL section to configuration tree + */ + private function addDbalSection(ArrayNodeDefinition $node): void + { + // Key that should not be rewritten to the connection config + $excludedKeys = ['default_connection' => true, 'driver_schemes' => true, 'driver_scheme' => true, 'types' => true, 'type' => true]; + + $node + ->children() + ->arrayNode('dbal') + ->beforeNormalization() + ->ifTrue(static function ($v) use ($excludedKeys) { + if (! is_array($v)) { + return false; + } + + if (array_key_exists('connections', $v) || array_key_exists('connection', $v)) { + return false; + } + + // Is there actually anything to use once excluded keys are considered? + return (bool) array_diff_key($v, $excludedKeys); + }) + ->then(static function ($v) use ($excludedKeys) { + $connection = []; + foreach ($v as $key => $value) { + if (isset($excludedKeys[$key])) { + continue; + } + + $connection[$key] = $v[$key]; + unset($v[$key]); + } + + $v['connections'] = [($v['default_connection'] ?? 'default') => $connection]; + + return $v; + }) + ->end() + ->children() + ->scalarNode('default_connection')->end() + ->end() + ->fixXmlConfig('type') + ->children() + ->arrayNode('types') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization() + ->ifString() + ->then(static function ($v) { + return ['class' => $v]; + }) + ->end() + ->children() + ->scalarNode('class')->isRequired()->end() + ->booleanNode('commented') + ->setDeprecated( + 'doctrine/doctrine-bundle', + '2.0', + 'The doctrine-bundle type commenting features were removed; the corresponding config parameter was deprecated in 2.0 and will be dropped in 3.0.', + ) + ->end() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('driver_scheme') + ->children() + ->arrayNode('driver_schemes') + ->useAttributeAsKey('scheme') + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->info('Defines a driver for given URL schemes. Schemes being driver names cannot be redefined. However, other default schemes can be overwritten.') + ->validate() + ->always() + ->then(static function (array $value) { + $unsupportedSchemes = []; + + foreach ($value as $scheme => $driver) { + if (! in_array($scheme, ['pdo-mysql', 'pdo-sqlite', 'pdo-pgsql', 'pdo-oci', 'oci8', 'ibm-db2', 'pdo-sqlsrv', 'mysqli', 'pgsql', 'sqlsrv', 'sqlite3'], true)) { + continue; + } + + $unsupportedSchemes[] = $scheme; + } + + if ($unsupportedSchemes) { + throw new InvalidArgumentException(sprintf('Registering a scheme with the name of one of the official drivers is forbidden, as those are defined in DBAL itself. The following schemes are forbidden: %s', implode(', ', $unsupportedSchemes))); + } + + return $value; + }) + ->end() + ->end() + ->end() + ->fixXmlConfig('connection') + ->append($this->getDbalConnectionsNode()) + ->end(); + } + + /** + * Return the dbal connections node + */ + private function getDbalConnectionsNode(): ArrayNodeDefinition + { + $treeBuilder = new TreeBuilder('connections'); + $node = $treeBuilder->getRootNode(); + + $connectionNode = $node + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array'); + assert($connectionNode instanceof ArrayNodeDefinition); + + $this->configureDbalDriverNode($connectionNode); + + $collationKey = defined('Doctrine\DBAL\Connection::PARAM_ASCII_STR_ARRAY') + ? 'collate' + : 'collation'; + + $connectionNode + ->fixXmlConfig('option') + ->fixXmlConfig('mapping_type') + ->fixXmlConfig('slave') + ->fixXmlConfig('replica') + ->fixXmlConfig('default_table_option') + ->children() + ->scalarNode('driver')->defaultValue('pdo_mysql')->end() + ->scalarNode('platform_service') + ->setDeprecated( + 'doctrine/doctrine-bundle', + '2.9', + 'The "platform_service" configuration key is deprecated since doctrine-bundle 2.9. DBAL 4 will not support setting a custom platform via connection params anymore.', + ) + ->end() + ->booleanNode('auto_commit')->end() + ->scalarNode('schema_filter')->end() + ->booleanNode('logging')->defaultValue($this->debug)->end() + ->booleanNode('profiling')->defaultValue($this->debug)->end() + ->booleanNode('profiling_collect_backtrace') + ->defaultValue(false) + ->info('Enables collecting backtraces when profiling is enabled') + ->end() + ->booleanNode('profiling_collect_schema_errors') + ->defaultValue(true) + ->info('Enables collecting schema errors when profiling is enabled') + ->end() + ->booleanNode('disable_type_comments')->end() + ->scalarNode('server_version')->end() + ->integerNode('idle_connection_ttl')->defaultValue(600)->end() + ->scalarNode('driver_class')->end() + ->scalarNode('wrapper_class')->end() + ->booleanNode('keep_slave') + ->setDeprecated( + 'doctrine/doctrine-bundle', + '2.2', + 'The "keep_slave" configuration key is deprecated since doctrine-bundle 2.2. Use the "keep_replica" configuration key instead.', + ) + ->end() + ->booleanNode('keep_replica')->end() + ->arrayNode('options') + ->useAttributeAsKey('key') + ->prototype('variable')->end() + ->end() + ->arrayNode('mapping_types') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->arrayNode('default_table_options') + ->info(sprintf( + "This option is used by the schema-tool and affects generated SQL. Possible keys include 'charset','%s', and 'engine'.", + $collationKey, + )) + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->scalarNode('schema_manager_factory') + ->cannotBeEmpty() + ->defaultValue($this->getDefaultSchemaManagerFactory()) + ->end() + ->scalarNode('result_cache')->end() + ->end(); + + // dbal < 2.11 + $slaveNode = $connectionNode + ->children() + ->arrayNode('slaves') + ->setDeprecated( + 'doctrine/doctrine-bundle', + '2.2', + 'The "slaves" configuration key will be renamed to "replicas" in doctrine-bundle 3.0. "slaves" is deprecated since doctrine-bundle 2.2.', + ) + ->useAttributeAsKey('name') + ->prototype('array'); + $this->configureDbalDriverNode($slaveNode); + + // dbal >= 2.11 + $replicaNode = $connectionNode + ->children() + ->arrayNode('replicas') + ->useAttributeAsKey('name') + ->prototype('array'); + $this->configureDbalDriverNode($replicaNode); + + return $node; + } + + /** + * Adds config keys related to params processed by the DBAL drivers + * + * These keys are available for replica configurations too. + */ + private function configureDbalDriverNode(ArrayNodeDefinition $node): void + { + $node + ->validate() + ->always(static function (array $values) { + if (! isset($values['url'])) { + return $values; + } + + $urlConflictingOptions = ['host' => true, 'port' => true, 'user' => true, 'password' => true, 'path' => true, 'dbname' => true, 'unix_socket' => true, 'memory' => true]; + $urlConflictingValues = array_keys(array_intersect_key($values, $urlConflictingOptions)); + + if ($urlConflictingValues) { + $tail = count($urlConflictingValues) > 1 ? sprintf('or "%s" options', array_pop($urlConflictingValues)) : 'option'; + trigger_deprecation( + 'doctrine/doctrine-bundle', + '2.4', + 'Setting the "doctrine.dbal.%s" %s while the "url" one is defined is deprecated', + implode('", "', $urlConflictingValues), + $tail, + ); + } + + return $values; + }) + ->end() + ->children() + ->scalarNode('url')->info('A URL with connection information; any parameter value parsed from this string will override explicitly set parameters')->end() + ->scalarNode('dbname')->end() + ->scalarNode('host')->info('Defaults to "localhost" at runtime.')->end() + ->scalarNode('port')->info('Defaults to null at runtime.')->end() + ->scalarNode('user')->info('Defaults to "root" at runtime.')->end() + ->scalarNode('password')->info('Defaults to null at runtime.')->end() + ->booleanNode('override_url')->setDeprecated( + 'doctrine/doctrine-bundle', + '2.4', + 'The "doctrine.dbal.override_url" configuration key is deprecated.', + )->end() + ->scalarNode('dbname_suffix')->end() + ->scalarNode('application_name')->end() + ->scalarNode('charset')->end() + ->scalarNode('path')->end() + ->booleanNode('memory')->end() + ->scalarNode('unix_socket')->info('The unix socket to use for MySQL')->end() + ->booleanNode('persistent')->info('True to use as persistent connection for the ibm_db2 driver')->end() + ->scalarNode('protocol')->info('The protocol to use for the ibm_db2 driver (default to TCPIP if omitted)')->end() + ->booleanNode('service') + ->info('True to use SERVICE_NAME as connection parameter instead of SID for Oracle') + ->end() + ->scalarNode('servicename') + ->info( + 'Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter ' . + 'for Oracle depending on the service parameter.', + ) + ->end() + ->scalarNode('sessionMode') + ->info('The session mode to use for the oci8 driver') + ->end() + ->scalarNode('server') + ->info('The name of a running database server to connect to for SQL Anywhere.') + ->end() + ->scalarNode('default_dbname') + ->info( + 'Override the default database (postgres) to connect to for PostgreSQL connexion.', + ) + ->end() + ->scalarNode('sslmode') + ->info( + 'Determines whether or with what priority a SSL TCP/IP connection will be negotiated with ' . + 'the server for PostgreSQL.', + ) + ->end() + ->scalarNode('sslrootcert') + ->info( + 'The name of a file containing SSL certificate authority (CA) certificate(s). ' . + 'If the file exists, the server\'s certificate will be verified to be signed by one of these authorities.', + ) + ->end() + ->scalarNode('sslcert') + ->info( + 'The path to the SSL client certificate file for PostgreSQL.', + ) + ->end() + ->scalarNode('sslkey') + ->info( + 'The path to the SSL client key file for PostgreSQL.', + ) + ->end() + ->scalarNode('sslcrl') + ->info( + 'The file name of the SSL certificate revocation list for PostgreSQL.', + ) + ->end() + ->booleanNode('pooled')->info('True to use a pooled server with the oci8/pdo_oracle driver')->end() + ->booleanNode('MultipleActiveResultSets')->info('Configuring MultipleActiveResultSets for the pdo_sqlsrv driver')->end() + ->booleanNode('use_savepoints')->info('Use savepoints for nested transactions')->end() + ->scalarNode('instancename') + ->info( + 'Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection.' . + ' It is generally used to connect to an Oracle RAC server to select the name' . + ' of a particular instance.', + ) + ->end() + ->scalarNode('connectstring') + ->info( + 'Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.' . + 'When using this option, you will still need to provide the user and password parameters, but the other ' . + 'parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods' . + ' from Doctrine\DBAL\Connection will no longer function as expected.', + ) + ->end() + ->end() + ->beforeNormalization() + ->ifTrue(static function ($v) { + return ! isset($v['sessionMode']) && isset($v['session_mode']); + }) + ->then(static function ($v) { + $v['sessionMode'] = $v['session_mode']; + unset($v['session_mode']); + + return $v; + }) + ->end() + ->beforeNormalization() + ->ifTrue(static function ($v) { + return ! isset($v['MultipleActiveResultSets']) && isset($v['multiple_active_result_sets']); + }) + ->then(static function ($v) { + $v['MultipleActiveResultSets'] = $v['multiple_active_result_sets']; + unset($v['multiple_active_result_sets']); + + return $v; + }) + ->end(); + } + + /** + * Add the ORM section to configuration tree + */ + private function addOrmSection(ArrayNodeDefinition $node): void + { + // Key that should not be rewritten to the entity-manager config + $excludedKeys = [ + 'default_entity_manager' => true, + 'auto_generate_proxy_classes' => true, + 'enable_lazy_ghost_objects' => true, + 'proxy_dir' => true, + 'proxy_namespace' => true, + 'resolve_target_entities' => true, + 'resolve_target_entity' => true, + 'controller_resolver' => true, + ]; + + $node + ->children() + ->arrayNode('orm') + ->beforeNormalization() + ->ifTrue(static function ($v) use ($excludedKeys) { + if (! empty($v) && ! class_exists(EntityManager::class)) { + throw new LogicException('The doctrine/orm package is required when the doctrine.orm config is set.'); + } + + if (! is_array($v)) { + return false; + } + + if (array_key_exists('entity_managers', $v) || array_key_exists('entity_manager', $v)) { + return false; + } + + // Is there actually anything to use once excluded keys are considered? + return (bool) array_diff_key($v, $excludedKeys); + }) + ->then(static function ($v) use ($excludedKeys) { + $entityManager = []; + foreach ($v as $key => $value) { + if (isset($excludedKeys[$key])) { + continue; + } + + $entityManager[$key] = $v[$key]; + unset($v[$key]); + } + + $v['entity_managers'] = [($v['default_entity_manager'] ?? 'default') => $entityManager]; + + return $v; + }) + ->end() + ->children() + ->scalarNode('default_entity_manager')->end() + ->scalarNode('auto_generate_proxy_classes')->defaultValue(false) + ->info('Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL", "FILE_NOT_EXISTS_OR_CHANGED"') + ->validate() + ->ifTrue(function ($v) { + $generationModes = $this->getAutoGenerateModes(); + + if (is_int($v) && in_array($v, $generationModes['values']/*array(0, 1, 2, 3)*/)) { + return false; + } + + if (is_bool($v)) { + return false; + } + + if (is_string($v)) { + if (in_array(strtoupper($v), $generationModes['names']/*array('NEVER', 'ALWAYS', 'FILE_NOT_EXISTS', 'EVAL', 'FILE_NOT_EXISTS_OR_CHANGED')*/)) { + return false; + } + } + + return true; + }) + ->thenInvalid('Invalid auto generate mode value %s') + ->end() + ->validate() + ->ifString() + ->then(static function ($v) { + return constant('Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_' . strtoupper($v)); + }) + ->end() + ->end() + ->booleanNode('enable_lazy_ghost_objects') + ->defaultValue(! method_exists(ProxyFactory::class, 'resetUninitializedProxy')) + ->info('Enables the new implementation of proxies based on lazy ghosts instead of using the legacy implementation') + ->end() + ->scalarNode('proxy_dir')->defaultValue('%kernel.cache_dir%/doctrine/orm/Proxies')->end() + ->scalarNode('proxy_namespace')->defaultValue('Proxies')->end() + ->arrayNode('controller_resolver') + ->canBeDisabled() + ->children() + ->booleanNode('auto_mapping') + ->defaultNull() + ->info('Set to false to disable using route placeholders as lookup criteria when the primary key doesn\'t match the argument name') + ->end() + ->booleanNode('evict_cache') + ->info('Set to true to fetch the entity from the database instead of using the cache, if any') + ->defaultFalse() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('entity_manager') + ->append($this->getOrmEntityManagersNode()) + ->fixXmlConfig('resolve_target_entity', 'resolve_target_entities') + ->append($this->getOrmTargetEntityResolverNode()) + ->end() + ->end(); + } + + /** + * Return ORM target entity resolver node + */ + private function getOrmTargetEntityResolverNode(): NodeDefinition + { + $treeBuilder = new TreeBuilder('resolve_target_entities'); + $node = $treeBuilder->getRootNode(); + + $node + ->useAttributeAsKey('interface') + ->prototype('scalar') + ->cannotBeEmpty() + ->end(); + + return $node; + } + + /** + * Return ORM entity listener node + */ + private function getOrmEntityListenersNode(): NodeDefinition + { + $treeBuilder = new TreeBuilder('entity_listeners'); + $node = $treeBuilder->getRootNode(); + + $normalizer = static function ($mappings) { + $entities = []; + + foreach ($mappings as $entityClass => $mapping) { + $listeners = []; + + foreach ($mapping as $listenerClass => $listenerEvent) { + $events = []; + + foreach ($listenerEvent as $eventType => $eventMapping) { + if ($eventMapping === null) { + $eventMapping = [null]; + } + + foreach ($eventMapping as $method) { + $events[] = [ + 'type' => $eventType, + 'method' => $method, + ]; + } + } + + $listeners[] = [ + 'class' => $listenerClass, + 'event' => $events, + ]; + } + + $entities[] = [ + 'class' => $entityClass, + 'listener' => $listeners, + ]; + } + + return ['entities' => $entities]; + }; + + $node + ->beforeNormalization() + // Yaml normalization + ->ifTrue(static function ($v) { + return is_array(reset($v)) && is_string(key(reset($v))); + }) + ->then($normalizer) + ->end() + ->fixXmlConfig('entity', 'entities') + ->children() + ->arrayNode('entities') + ->useAttributeAsKey('class') + ->prototype('array') + ->fixXmlConfig('listener') + ->children() + ->arrayNode('listeners') + ->useAttributeAsKey('class') + ->prototype('array') + ->fixXmlConfig('event') + ->children() + ->arrayNode('events') + ->prototype('array') + ->children() + ->scalarNode('type')->end() + ->scalarNode('method')->defaultNull()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end(); + + return $node; + } + + /** + * Return ORM entity manager node + */ + private function getOrmEntityManagersNode(): ArrayNodeDefinition + { + $treeBuilder = new TreeBuilder('entity_managers'); + $node = $treeBuilder->getRootNode(); + + $node + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array') + ->addDefaultsIfNotSet() + ->append($this->getOrmCacheDriverNode('query_cache_driver')) + ->append($this->getOrmCacheDriverNode('metadata_cache_driver')) + ->append($this->getOrmCacheDriverNode('result_cache_driver')) + ->append($this->getOrmEntityListenersNode()) + ->fixXmlConfig('schema_ignore_class', 'schema_ignore_classes') + ->children() + ->scalarNode('connection')->end() + ->scalarNode('class_metadata_factory_name')->defaultValue(ClassMetadataFactory::class)->end() + ->scalarNode('default_repository_class')->defaultValue(EntityRepository::class)->end() + ->scalarNode('auto_mapping')->defaultFalse()->end() + ->scalarNode('naming_strategy')->defaultValue('doctrine.orm.naming_strategy.default')->end() + ->scalarNode('quote_strategy')->defaultValue('doctrine.orm.quote_strategy.default')->end() + ->scalarNode('typed_field_mapper')->defaultValue('doctrine.orm.typed_field_mapper.default')->end() + ->scalarNode('entity_listener_resolver')->defaultNull()->end() + ->scalarNode('repository_factory')->defaultValue('doctrine.orm.container_repository_factory')->end() + ->arrayNode('schema_ignore_classes') + ->prototype('scalar')->end() + ->end() + ->booleanNode('report_fields_where_declared') + ->defaultValue(! class_exists(AnnotationDriver::class)) + ->info('Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455.') + ->validate() + ->ifTrue(static fn (bool $v): bool => ! class_exists(AnnotationDriver::class) && ! $v) + ->thenInvalid('The setting "report_fields_where_declared" cannot be disabled for ORM 3.') + ->end() + ->end() + ->booleanNode('validate_xml_mapping')->defaultFalse()->info('Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728.')->end() + ->end() + ->children() + ->arrayNode('second_level_cache') + ->children() + ->append($this->getOrmCacheDriverNode('region_cache_driver')) + ->scalarNode('region_lock_lifetime')->defaultValue(60)->end() + ->booleanNode('log_enabled')->defaultValue($this->debug)->end() + ->scalarNode('region_lifetime')->defaultValue(3600)->end() + ->booleanNode('enabled')->defaultValue(true)->end() + ->scalarNode('factory')->end() + ->end() + ->fixXmlConfig('region') + ->children() + ->arrayNode('regions') + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->append($this->getOrmCacheDriverNode('cache_driver')) + ->scalarNode('lock_path')->defaultValue('%kernel.cache_dir%/doctrine/orm/slc/filelock')->end() + ->scalarNode('lock_lifetime')->defaultValue(60)->end() + ->scalarNode('type')->defaultValue('default')->end() + ->scalarNode('lifetime')->defaultValue(0)->end() + ->scalarNode('service')->end() + ->scalarNode('name')->end() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('logger') + ->children() + ->arrayNode('loggers') + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->scalarNode('name')->end() + ->scalarNode('service')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('hydrator') + ->children() + ->arrayNode('hydrators') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->end() + ->fixXmlConfig('mapping') + ->children() + ->arrayNode('mappings') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization() + ->ifString() + ->then(static function ($v) { + return ['type' => $v]; + }) + ->end() + ->treatNullLike([]) + ->treatFalseLike(['mapping' => false]) + ->performNoDeepMerging() + ->children() + ->scalarNode('mapping')->defaultValue(true)->end() + ->scalarNode('type')->end() + ->scalarNode('dir')->end() + ->scalarNode('alias')->end() + ->scalarNode('prefix')->end() + ->booleanNode('is_bundle')->end() + ->end() + ->end() + ->end() + ->arrayNode('dql') + ->fixXmlConfig('string_function') + ->fixXmlConfig('numeric_function') + ->fixXmlConfig('datetime_function') + ->children() + ->arrayNode('string_functions') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->arrayNode('numeric_functions') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->arrayNode('datetime_functions') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('filter') + ->children() + ->arrayNode('filters') + ->info('Register SQL Filters in the entity manager') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization() + ->ifString() + ->then(static function ($v) { + return ['class' => $v]; + }) + ->end() + ->beforeNormalization() + // The content of the XML node is returned as the "value" key so we need to rename it + ->ifTrue(static function ($v) { + return is_array($v) && isset($v['value']); + }) + ->then(static function ($v) { + $v['class'] = $v['value']; + unset($v['value']); + + return $v; + }) + ->end() + ->fixXmlConfig('parameter') + ->children() + ->scalarNode('class')->isRequired()->end() + ->booleanNode('enabled')->defaultFalse()->end() + ->arrayNode('parameters') + ->useAttributeAsKey('name') + ->prototype('variable')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('identity_generation_preference') + ->children() + ->arrayNode('identity_generation_preferences') + ->info('Configures the preferences for identity generation when using the AUTO strategy. Valid values are "SEQUENCE" or "IDENTITY".') + ->useAttributeAsKey('platform') + ->prototype('scalar') + ->beforeNormalization() + ->ifString() + ->then(static function ($v) { + return constant(ClassMetadata::class . '::GENERATOR_TYPE_' . strtoupper($v)); + }) + ->end() + ->end() + ->end() + ->end() + ->end(); + + return $node; + } + + /** + * Return an ORM cache driver node for a given entity manager + */ + private function getOrmCacheDriverNode(string $name): ArrayNodeDefinition + { + $treeBuilder = new TreeBuilder($name); + $node = $treeBuilder->getRootNode(); + + $node + ->beforeNormalization() + ->ifString() + ->then(static function ($v): array { + return ['type' => $v]; + }) + ->end() + ->children() + ->scalarNode('type')->defaultNull()->end() + ->scalarNode('id')->end() + ->scalarNode('pool')->end() + ->end(); + + if ($name !== 'metadata_cache_driver') { + $node->addDefaultsIfNotSet(); + } + + return $node; + } + + /** + * Find proxy auto generate modes for their names and int values + * + * @return array{names: list, values: list} + */ + private function getAutoGenerateModes(): array + { + $constPrefix = 'AUTOGENERATE_'; + $prefixLen = strlen($constPrefix); + $refClass = new ReflectionClass(ProxyFactory::class); + $constsArray = $refClass->getConstants(); + $namesArray = []; + $valuesArray = []; + + foreach ($constsArray as $key => $value) { + if (strpos($key, $constPrefix) !== 0) { + continue; + } + + $namesArray[] = substr($key, $prefixLen); + $valuesArray[] = (int) $value; + } + + return [ + 'names' => $namesArray, + 'values' => $valuesArray, + ]; + } + + private function getDefaultSchemaManagerFactory(): string + { + if (class_exists(LegacySchemaManagerFactory::class)) { + return 'doctrine.dbal.legacy_schema_manager_factory'; + } + + return 'doctrine.dbal.default_schema_manager_factory'; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DependencyInjection/DoctrineExtension.php b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/DoctrineExtension.php new file mode 100644 index 0000000..30cbfa6 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DependencyInjection/DoctrineExtension.php @@ -0,0 +1,1241 @@ +, + * driver_schemes: array, + * default_connection: string, + * types: array, + * } + */ +class DoctrineExtension extends AbstractDoctrineExtension +{ + private string $defaultConnection; + + /** + * {@inheritDoc} + * + * @return void + */ + public function load(array $configs, ContainerBuilder $container) + { + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfigurationPrependingDefaults($configuration, $configs); + + if (! empty($config['dbal'])) { + $this->dbalLoad($config['dbal'], $container); + + $this->loadMessengerServices($container); + } + + if (empty($config['orm'])) { + return; + } + + if (empty($config['dbal'])) { + throw new LogicException('Configuring the ORM layer requires to configure the DBAL layer as well.'); + } + + $this->ormLoad($config['orm'], $container); + } + + /** + * Process user configuration and adds a default DBAL connection and/or a + * default EM if required, then process again the configuration to get + * default values for each. + * + * @param array> $configs + * + * @return array + */ + private function processConfigurationPrependingDefaults(ConfigurationInterface $configuration, array $configs): array + { + $config = $this->processConfiguration($configuration, $configs); + $configToAdd = []; + + // if no DB connection defined, prepend an empty one for the default + // connection name in order to make Symfony Config resolve the default + // values + if (isset($config['dbal']) && empty($config['dbal']['connections'])) { + $configToAdd['dbal'] = ['connections' => [($config['dbal']['default_connection'] ?? 'default') => []]]; + } + + // if no EM defined, prepend an empty one for the default EM name in + // order to make Symfony Config resolve the default values + if (isset($config['orm']) && empty($config['orm']['entity_managers'])) { + $configToAdd['orm'] = ['entity_managers' => [($config['orm']['default_entity_manager'] ?? 'default') => []]]; + } + + if (! $configToAdd) { + return $config; + } + + return $this->processConfiguration($configuration, array_merge([$configToAdd], $configs)); + } + + /** + * Loads the DBAL configuration. + * + * Usage example: + * + * + * + * @param DBALConfig $config An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function dbalLoad(array $config, ContainerBuilder $container) + { + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader->load('dbal.xml'); + + if (empty($config['default_connection'])) { + $keys = array_keys($config['connections']); + $config['default_connection'] = reset($keys); + } + + $this->defaultConnection = $config['default_connection']; + + $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $this->defaultConnection)); + $container->getAlias('database_connection')->setPublic(true); + $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $this->defaultConnection), false)); + + $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']); + + $container->getDefinition('doctrine.dbal.connection_factory.dsn_parser')->setArgument(0, array_merge(ConnectionFactory::DEFAULT_SCHEME_MAP, $config['driver_schemes'])); + + $connections = []; + + foreach (array_keys($config['connections']) as $name) { + $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name); + } + + $container->setParameter('doctrine.connections', $connections); + $container->setParameter('doctrine.default_connection', $this->defaultConnection); + + $connWithLogging = []; + $connWithProfiling = []; + $connWithBacktrace = []; + $ttlByConnection = []; + + foreach ($config['connections'] as $name => $connection) { + if ($connection['logging']) { + $connWithLogging[] = $name; + } + + if ($connection['profiling']) { + $connWithProfiling[] = $name; + + if ($connection['profiling_collect_backtrace']) { + $connWithBacktrace[] = $name; + } + } + + if ($connection['idle_connection_ttl'] > 0) { + $ttlByConnection[$name] = $connection['idle_connection_ttl']; + } + + $this->loadDbalConnection($name, $connection, $container); + } + + $container->registerForAutoconfiguration(MiddlewareInterface::class)->addTag('doctrine.middleware'); + + $container->registerAttributeForAutoconfiguration(AsMiddleware::class, static function (ChildDefinition $definition, AsMiddleware $attribute) { + $priority = isset($attribute->priority) ? ['priority' => $attribute->priority] : []; + + if ($attribute->connections === []) { + $definition->addTag('doctrine.middleware', $priority); + + return; + } + + foreach ($attribute->connections as $connName) { + $definition->addTag('doctrine.middleware', array_merge($priority, ['connection' => $connName])); + } + }); + + $this->registerDbalMiddlewares($container, $connWithLogging, $connWithProfiling, $connWithBacktrace, array_keys($ttlByConnection)); + + $container->getDefinition('doctrine.dbal.idle_connection_middleware')->setArgument(1, $ttlByConnection); + + if (class_exists(Listener::class)) { + return; + } + + $container->removeDefinition('doctrine.dbal.idle_connection_listener'); + $container->removeDefinition('doctrine.dbal.idle_connection_middleware'); + } + + /** + * Loads a configured DBAL connection. + * + * @param string $name The name of the connection + * @param array $connection A dbal connection configuration. + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function loadDbalConnection($name, array $connection, ContainerBuilder $container) + { + $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration')); + unset($connection['logging']); + + $dataCollectorDefinition = $container->getDefinition('data_collector.doctrine'); + $dataCollectorDefinition->replaceArgument(1, $connection['profiling_collect_schema_errors']); + + unset( + $connection['profiling'], + $connection['profiling_collect_backtrace'], + $connection['profiling_collect_schema_errors'], + ); + + if (isset($connection['auto_commit'])) { + $configuration->addMethodCall('setAutoCommit', [$connection['auto_commit']]); + } + + unset($connection['auto_commit']); + + if (isset($connection['disable_type_comments'])) { + $configuration->addMethodCall('setDisableTypeComments', [$connection['disable_type_comments']]); + } + + unset($connection['disable_type_comments']); + + if (isset($connection['schema_filter']) && $connection['schema_filter']) { + $definition = new Definition(RegexSchemaAssetFilter::class, [$connection['schema_filter']]); + $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]); + $container->setDefinition(sprintf('doctrine.dbal.%s_regex_schema_filter', $name), $definition); + } + + unset($connection['schema_filter']); + + // event manager + $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new ChildDefinition('doctrine.dbal.connection.event_manager')); + + // connection + $options = $this->getConnectionOptions($connection); + + $connectionId = sprintf('doctrine.dbal.%s_connection', $name); + + $def = $container + ->setDefinition($connectionId, new ChildDefinition('doctrine.dbal.connection')) + ->setPublic(true) + ->setArguments([ + $options, + new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)), + // event manager is only supported on DBAL < 4 + method_exists(Connection::class, 'getEventManager') ? new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)) : null, + $connection['mapping_types'], + ]); + + $container + ->registerAliasForArgument($connectionId, Connection::class, sprintf('%sConnection', $name)) + ->setPublic(false); + + // Set class in case "wrapper_class" option was used to assist IDEs + if (isset($options['wrapperClass'])) { + $def->setClass($options['wrapperClass']); + } + + if (isset($connection['use_savepoints'])) { + // DBAL >= 4 always has savepoints enabled. So we only need to call "setNestTransactionsWithSavepoints" for DBAL < 4 + if (method_exists(Connection::class, 'getEventManager')) { + if ($connection['use_savepoints']) { + $def->addMethodCall('setNestTransactionsWithSavepoints', [$connection['use_savepoints']]); + } + } elseif (! $connection['use_savepoints']) { + throw new LogicException('The "use_savepoints" option can only be set to "true" and should ideally not be set when using DBAL >= 4'); + } + } + + $container->setDefinition( + ManagerRegistryAwareConnectionProvider::class, + new Definition(ManagerRegistryAwareConnectionProvider::class, [$container->getDefinition('doctrine')]), + ); + + $configuration->addMethodCall('setSchemaManagerFactory', [new Reference($connection['schema_manager_factory'])]); + + if (isset($connection['result_cache'])) { + $configuration->addMethodCall('setResultCache', [new Reference($connection['result_cache'])]); + } + + if (class_exists(LegacySchemaManagerFactory::class)) { + return; + } + + $container->removeDefinition('doctrine.dbal.legacy_schema_manager_factory'); + } + + /** + * @param array $connection + * + * @return mixed[] + */ + protected function getConnectionOptions(array $connection): array + { + $options = $connection; + + $connectionDefaults = [ + 'host' => 'localhost', + 'port' => null, + 'user' => 'root', + 'password' => null, + ]; + + if ($options['override_url'] ?? false) { + $options['connection_override_options'] = array_intersect_key($options, ['dbname' => null] + $connectionDefaults); + } + + unset($options['override_url']); + unset($options['schema_manager_factory']); + + $options += $connectionDefaults; + + foreach (['replicas', 'slaves'] as $connectionKey) { + foreach (array_keys($options[$connectionKey]) as $name) { + $options[$connectionKey][$name] += $connectionDefaults; + } + } + + if (isset($options['platform_service'])) { + $options['platform'] = new Reference($options['platform_service']); + unset($options['platform_service']); + } + + unset($options['mapping_types']); + + foreach ( + [ + 'options' => 'driverOptions', + 'driver_class' => 'driverClass', + 'wrapper_class' => 'wrapperClass', + 'keep_slave' => 'keepReplica', + 'keep_replica' => 'keepReplica', + 'replicas' => 'replica', + 'server_version' => 'serverVersion', + 'default_table_options' => 'defaultTableOptions', + ] as $old => $new + ) { + if (! isset($options[$old])) { + continue; + } + + $options[$new] = $options[$old]; + unset($options[$old]); + } + + foreach (['replica', 'slaves'] as $connectionKey) { + foreach ($options[$connectionKey] as $name => $value) { + $driverOptions = $value['driverOptions'] ?? []; + $parentDriverOptions = $options['driverOptions'] ?? []; + if ($driverOptions === [] && $parentDriverOptions === []) { + continue; + } + + $options[$connectionKey][$name]['driverOptions'] = $driverOptions + $parentDriverOptions; + } + } + + if (! empty($options['slaves']) || ! empty($options['replica'])) { + $nonRewrittenKeys = [ + 'driver' => true, + 'driverClass' => true, + 'wrapperClass' => true, + 'keepSlave' => true, + 'keepReplica' => true, + 'platform' => true, + 'slaves' => true, + 'primary' => true, + 'replica' => true, + 'serverVersion' => true, + 'defaultTableOptions' => true, + // included by safety but should have been unset already + 'logging' => true, + 'profiling' => true, + 'mapping_types' => true, + 'platform_service' => true, + ]; + foreach ($options as $key => $value) { + if (isset($nonRewrittenKeys[$key])) { + continue; + } + + $options['primary'][$key] = $value; + unset($options[$key]); + } + + if (empty($options['wrapperClass'])) { + // Change the wrapper class only if user did not configure custom one. + $options['wrapperClass'] = PrimaryReadReplicaConnection::class; + } + } else { + unset($options['slaves'], $options['replica']); + } + + return $options; + } + + /** + * Loads the Doctrine ORM configuration. + * + * Usage example: + * + * + * + * @param array $config An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function ormLoad(array $config, ContainerBuilder $container) + { + if (! class_exists(UnitOfWork::class)) { + throw new LogicException('To configure the ORM layer, you must first install the doctrine/orm package.'); + } + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader->load('orm.xml'); + + if (class_exists(AbstractType::class)) { + $container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']); + } + + if (! class_exists(Annotation::class)) { + $container->removeAlias('doctrine.orm.metadata.annotation_reader'); + } + + // available in Symfony 6.3 + $container->removeDefinition('doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_' . (class_exists(DoctrineDbalCacheAdapterSchemaListener::class) ? 'subscriber' : 'listener')); + + // available in Symfony 6.3 + $container->removeDefinition('doctrine.orm.listeners.doctrine_token_provider_schema_' . (class_exists(RememberMeTokenProviderDoctrineSchemaListener::class) ? 'subscriber' : 'listener')); + + // available in Symfony 5.1 and up to Symfony 5.4 (deprecated) + if (! class_exists(PdoCacheAdapterDoctrineSchemaSubscriber::class)) { + $container->removeDefinition('doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber'); + } + + if (! class_exists(PdoSessionHandlerSchemaListener::class)) { + $container->removeDefinition('doctrine.orm.listeners.pdo_session_handler_schema_listener'); + } + + // available in Symfony 6.3 and higher + if (! class_exists(LockStoreSchemaListener::class)) { + $container->removeDefinition('doctrine.orm.listeners.lock_store_schema_listener'); + } + + if (! class_exists(UlidGenerator::class)) { + $container->removeDefinition('doctrine.ulid_generator'); + } + + if (! class_exists(UuidGenerator::class)) { + $container->removeDefinition('doctrine.uuid_generator'); + } + + // available in Symfony 6.2 and higher + if (! class_exists(EntityValueResolver::class)) { + $container->removeDefinition('doctrine.orm.entity_value_resolver'); + $container->removeDefinition('doctrine.orm.entity_value_resolver.expression_language'); + } else { + if (! class_exists(ExpressionLanguage::class)) { + $container->removeDefinition('doctrine.orm.entity_value_resolver.expression_language'); + } + + $controllerResolverDefaults = []; + + if (! $config['controller_resolver']['enabled']) { + $controllerResolverDefaults['disabled'] = true; + } + + if ($config['controller_resolver']['auto_mapping'] === null) { + trigger_deprecation('doctrine/doctrine-bundle', '2.12', 'The default value of "doctrine.orm.controller_resolver.auto_mapping" will be changed from `true` to `false`. Explicitly configure `true` to keep existing behaviour.'); + $config['controller_resolver']['auto_mapping'] = true; + } + + if ($config['controller_resolver']['auto_mapping'] === true) { + trigger_deprecation('doctrine/doctrine-bundle', '2.13', 'Enabling the controller resolver automapping feature has been deprecated. Symfony Mapped Route Parameters should be used as replacement.'); + } + + if (! $config['controller_resolver']['auto_mapping']) { + $controllerResolverDefaults['mapping'] = []; + } + + if ($config['controller_resolver']['evict_cache']) { + $controllerResolverDefaults['evict_cache'] = true; + } + + if ($controllerResolverDefaults) { + $container->getDefinition('doctrine.orm.entity_value_resolver')->setArgument(2, (new Definition(MapEntity::class))->setArguments([ + null, + null, + null, + $controllerResolverDefaults['mapping'] ?? null, + null, + null, + null, + $controllerResolverDefaults['evict_cache'] ?? null, + $controllerResolverDefaults['disabled'] ?? false, + ])); + } + } + + // not available in Doctrine ORM 3.0 and higher + if (! class_exists(ConvertMappingCommand::class)) { + $container->removeDefinition('doctrine.mapping_convert_command'); + } + + if (! class_exists(EnsureProductionSettingsCommand::class)) { + $container->removeDefinition('doctrine.ensure_production_settings_command'); + } + + if (! class_exists(ClassMetadataExporter::class)) { + $container->removeDefinition('doctrine.mapping_import_command'); + } + + $entityManagers = []; + foreach (array_keys($config['entity_managers']) as $name) { + /** @psalm-suppress InvalidArrayOffset */ + $entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name); + } + + $container->setParameter('doctrine.entity_managers', $entityManagers); + + if (empty($config['default_entity_manager'])) { + $tmp = array_keys($entityManagers); + $config['default_entity_manager'] = reset($tmp); + } + + $container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']); + + if ($config['enable_lazy_ghost_objects'] ?? false) { + // available in Symfony 6.2 and higher + if (! trait_exists(LazyGhostTrait::class)) { + throw new LogicException( + 'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library' + . ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".', + ); + } + + if (! class_exists(RuntimeReflectionProperty::class)) { + throw new LogicException( + 'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library' + . ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".', + ); + } + } elseif (! method_exists(ProxyFactory::class, 'resetUninitializedProxy')) { + throw new LogicException( + 'Lazy ghost objects cannot be disabled for ORM 3.', + ); + } elseif (PHP_VERSION_ID >= 80100) { + trigger_deprecation('doctrine/doctrine-bundle', '2.11', 'Not setting "doctrine.orm.enable_lazy_ghost_objects" to true is deprecated.'); + } + + $options = ['auto_generate_proxy_classes', 'enable_lazy_ghost_objects', 'proxy_dir', 'proxy_namespace']; + foreach ($options as $key) { + $container->setParameter('doctrine.orm.' . $key, $config[$key]); + } + + $container->setAlias('doctrine.orm.entity_manager', $defaultEntityManagerDefinitionId = sprintf('doctrine.orm.%s_entity_manager', $config['default_entity_manager'])); + $container->getAlias('doctrine.orm.entity_manager')->setPublic(true); + + $config['entity_managers'] = $this->fixManagersAutoMappings($config['entity_managers'], $container->getParameter('kernel.bundles')); + + foreach ($config['entity_managers'] as $name => $entityManager) { + $entityManager['name'] = $name; + $this->loadOrmEntityManager($entityManager, $container); + + if (interface_exists(PropertyInfoExtractorInterface::class)) { + $this->loadPropertyInfoExtractor($name, $container); + } + + if (! interface_exists(LoaderInterface::class)) { + continue; + } + + $this->loadValidatorLoader($name, $container); + } + + if ($config['resolve_target_entities']) { + $def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity'); + foreach ($config['resolve_target_entities'] as $name => $implementation) { + $def->addMethodCall('addResolveTargetEntity', [ + $name, + $implementation, + [], + ]); + } + + $def + ->addTag('doctrine.event_listener', ['event' => Events::loadClassMetadata]) + ->addTag('doctrine.event_listener', ['event' => Events::onClassMetadataNotFound]); + } + + $container->registerForAutoconfiguration(ServiceEntityRepositoryInterface::class) + ->addTag(ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG); + + $container->registerForAutoconfiguration(EventSubscriberInterface::class) + ->addTag('doctrine.event_subscriber'); + + $container->registerForAutoconfiguration(AbstractIdGenerator::class) + ->addTag(IdGeneratorPass::ID_GENERATOR_TAG); + + $container->registerAttributeForAutoconfiguration(AsEntityListener::class, static function (ChildDefinition $definition, AsEntityListener $attribute) { + $definition->addTag('doctrine.orm.entity_listener', [ + 'event' => $attribute->event, + 'method' => $attribute->method, + 'lazy' => $attribute->lazy, + 'entity_manager' => $attribute->entityManager, + 'entity' => $attribute->entity, + 'priority' => $attribute->priority, + ]); + }); + $container->registerAttributeForAutoconfiguration(AsDoctrineListener::class, static function (ChildDefinition $definition, AsDoctrineListener $attribute) { + $definition->addTag('doctrine.event_listener', [ + 'event' => $attribute->event, + 'priority' => $attribute->priority, + 'connection' => $attribute->connection, + ]); + }); + + /** @see DoctrineBundle::boot() */ + $container->getDefinition($defaultEntityManagerDefinitionId) + ->addTag('container.preload', [ + 'class' => Autoloader::class, + ]); + } + + /** + * Loads a configured ORM entity manager. + * + * @param array $entityManager A configured ORM entity manager. + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container) + { + $ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration')); + $ormConfigDef->addTag(IdGeneratorPass::CONFIGURATION_TAG); + + $this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container); + $this->loadOrmCacheDrivers($entityManager, $container); + + if (isset($entityManager['entity_listener_resolver']) && $entityManager['entity_listener_resolver']) { + $container->setAlias(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $entityManager['entity_listener_resolver']); + } else { + $definition = new Definition('%doctrine.orm.entity_listener_resolver.class%'); + $definition->addArgument(new Reference('service_container')); + $container->setDefinition(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $definition); + } + + $methods = [ + 'setMetadataCache' => new Reference(sprintf('doctrine.orm.%s_metadata_cache', $entityManager['name'])), + 'setQueryCache' => new Reference(sprintf('doctrine.orm.%s_query_cache', $entityManager['name'])), + 'setResultCache' => new Reference(sprintf('doctrine.orm.%s_result_cache', $entityManager['name'])), + 'setMetadataDriverImpl' => new Reference('doctrine.orm.' . $entityManager['name'] . '_metadata_driver'), + 'setProxyDir' => '%doctrine.orm.proxy_dir%', + 'setProxyNamespace' => '%doctrine.orm.proxy_namespace%', + 'setAutoGenerateProxyClasses' => '%doctrine.orm.auto_generate_proxy_classes%', + 'setSchemaIgnoreClasses' => $entityManager['schema_ignore_classes'], + 'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'], + 'setDefaultRepositoryClassName' => $entityManager['default_repository_class'], + 'setNamingStrategy' => new Reference($entityManager['naming_strategy']), + 'setQuoteStrategy' => new Reference($entityManager['quote_strategy']), + 'setTypedFieldMapper' => new Reference($entityManager['typed_field_mapper']), + 'setEntityListenerResolver' => new Reference(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name'])), + 'setLazyGhostObjectEnabled' => '%doctrine.orm.enable_lazy_ghost_objects%', + 'setIdentityGenerationPreferences' => $entityManager['identity_generation_preferences'], + ]; + + if (! method_exists(OrmConfiguration::class, 'setLazyGhostObjectEnabled')) { + unset($methods['setLazyGhostObjectEnabled']); + } + + $listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $entityManager['name']); + $listenerDef = $container->setDefinition($listenerId, new Definition('%doctrine.orm.listeners.attach_entity_listeners.class%')); + $listenerTagParams = ['event' => 'loadClassMetadata']; + if (isset($entityManager['connection'])) { + $listenerTagParams['connection'] = $entityManager['connection']; + } + + $listenerDef->addTag('doctrine.event_listener', $listenerTagParams); + + if (isset($entityManager['second_level_cache'])) { + $this->loadOrmSecondLevelCache($entityManager, $ormConfigDef, $container); + } + + if ($entityManager['repository_factory']) { + $methods['setRepositoryFactory'] = new Reference($entityManager['repository_factory']); + } + + foreach ($methods as $method => $arg) { + $ormConfigDef->addMethodCall($method, [$arg]); + } + + foreach ($entityManager['hydrators'] as $name => $class) { + $ormConfigDef->addMethodCall('addCustomHydrationMode', [$name, $class]); + } + + if (! empty($entityManager['dql'])) { + foreach ($entityManager['dql']['string_functions'] as $name => $function) { + $ormConfigDef->addMethodCall('addCustomStringFunction', [$name, $function]); + } + + foreach ($entityManager['dql']['numeric_functions'] as $name => $function) { + $ormConfigDef->addMethodCall('addCustomNumericFunction', [$name, $function]); + } + + foreach ($entityManager['dql']['datetime_functions'] as $name => $function) { + $ormConfigDef->addMethodCall('addCustomDatetimeFunction', [$name, $function]); + } + } + + $enabledFilters = []; + $filtersParameters = []; + foreach ($entityManager['filters'] as $name => $filter) { + $ormConfigDef->addMethodCall('addFilter', [$name, $filter['class']]); + if ($filter['enabled']) { + $enabledFilters[] = $name; + } + + if (! $filter['parameters']) { + continue; + } + + $filtersParameters[$name] = $filter['parameters']; + } + + $managerConfiguratorName = sprintf('doctrine.orm.%s_manager_configurator', $entityManager['name']); + $container + ->setDefinition($managerConfiguratorName, new ChildDefinition('doctrine.orm.manager_configurator.abstract')) + ->replaceArgument(0, $enabledFilters) + ->replaceArgument(1, $filtersParameters); + + if (! isset($entityManager['connection'])) { + $entityManager['connection'] = $this->defaultConnection; + } + + $entityManagerId = sprintf('doctrine.orm.%s_entity_manager', $entityManager['name']); + + $container + ->setDefinition($entityManagerId, new ChildDefinition('doctrine.orm.entity_manager.abstract')) + ->setPublic(true) + ->setArguments([ + new Reference(sprintf('doctrine.dbal.%s_connection', $entityManager['connection'])), + new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])), + new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection'])), + ]) + ->setConfigurator([new Reference($managerConfiguratorName), 'configure']); + + $container + ->registerAliasForArgument($entityManagerId, EntityManagerInterface::class, sprintf('%sEntityManager', $entityManager['name'])) + ->setPublic(false); + + $container->setAlias( + sprintf('doctrine.orm.%s_entity_manager.event_manager', $entityManager['name']), + new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection']), false), + ); + + if (! isset($entityManager['entity_listeners'])) { + return; + } + + $entities = $entityManager['entity_listeners']['entities']; + + foreach ($entities as $entityListenerClass => $entity) { + foreach ($entity['listeners'] as $listenerClass => $listener) { + foreach ($listener['events'] as $listenerEvent) { + $listenerEventName = $listenerEvent['type']; + $listenerMethod = $listenerEvent['method']; + + $listenerDef->addMethodCall('addEntityListener', [ + $entityListenerClass, + $listenerClass, + $listenerEventName, + $listenerMethod, + ]); + } + } + } + } + + /** + * Loads an ORM entity managers bundle mapping information. + * + * There are two distinct configuration possibilities for mapping information: + * + * 1. Specify a bundle and optionally details where the entity and mapping information reside. + * 2. Specify an arbitrary mapping location. + * + * @param array $entityManager A configured ORM entity manager + * @param Definition $ormConfigDef A Definition instance + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @example + * + * doctrine.orm: + * mappings: + * MyBundle1: ~ + * MyBundle2: yml + * MyBundle3: { type: annotation, dir: Entities/ } + * MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping } + * MyBundle5: { type: attribute, dir: Entities/ } + * MyBundle6: + * type: yml + * dir: bundle-mappings/ + * alias: BundleAlias + * arbitrary_key: + * type: xml + * dir: %kernel.project_dir%/src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entities + * prefix: DoctrineExtensions\Entities\ + * alias: DExt + * + * In the case of bundles everything is really optional (which leads to autodetection for this bundle) but + * in the mappings key everything except alias is a required argument. + */ + protected function loadOrmEntityManagerMappingInformation(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container) + { + // reset state of drivers and alias map. They are only used by this methods and children. + $this->drivers = []; + $this->aliasMap = []; + + $this->loadMappingInformation($entityManager, $container); + $this->registerMappingDrivers($entityManager, $container); + + $container->getDefinition($this->getObjectManagerElementName($entityManager['name'] . '_metadata_driver')); + /** @psalm-suppress NoValue $this->drivers is set by $this->loadMappingInformation() call */ + foreach (array_keys($this->drivers) as $driverType) { + $mappingService = $this->getObjectManagerElementName($entityManager['name'] . '_' . $driverType . '_metadata_driver'); + $mappingDriverDef = $container->getDefinition($mappingService); + $args = $mappingDriverDef->getArguments(); + if ($driverType === 'annotation') { + $args[2] = $entityManager['report_fields_where_declared']; + } elseif ($driverType === 'attribute') { + $args[1] = $entityManager['report_fields_where_declared']; + } elseif ($driverType === 'xml') { + $args[1] ??= SimplifiedXmlDriver::DEFAULT_FILE_EXTENSION; + $args[2] = $entityManager['validate_xml_mapping']; + } else { + continue; + } + + $mappingDriverDef->setArguments($args); + } + + $ormConfigDef->addMethodCall('setEntityNamespaces', [$this->aliasMap]); + } + + /** + * Loads an ORM second level cache bundle mapping information. + * + * @param array $entityManager A configured ORM entity manager + * @param Definition $ormConfigDef A Definition instance + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @example + * entity_managers: + * default: + * second_level_cache: + * region_lifetime: 3600 + * region_lock_lifetime: 60 + * region_cache_driver: apc + * log_enabled: true + * regions: + * my_service_region: + * type: service + * service : "my_service_region" + * + * my_query_region: + * lifetime: 300 + * cache_driver: array + * type: filelock + * + * my_entity_region: + * lifetime: 600 + * cache_driver: + * type: apc + */ + protected function loadOrmSecondLevelCache(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container) + { + $driverId = null; + $enabled = $entityManager['second_level_cache']['enabled']; + + if (isset($entityManager['second_level_cache']['region_cache_driver'])) { + $driverName = 'second_level_cache.region_cache_driver'; + $driverMap = $entityManager['second_level_cache']['region_cache_driver']; + $driverId = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container); + } + + $configId = sprintf('doctrine.orm.%s_second_level_cache.cache_configuration', $entityManager['name']); + $regionsId = sprintf('doctrine.orm.%s_second_level_cache.regions_configuration', $entityManager['name']); + $driverId = $driverId ?: sprintf('doctrine.orm.%s_second_level_cache.region_cache_driver', $entityManager['name']); + $configDef = $container->setDefinition($configId, new Definition('%doctrine.orm.second_level_cache.cache_configuration.class%')); + $regionsDef = $container + ->setDefinition($regionsId, new Definition('%doctrine.orm.second_level_cache.regions_configuration.class%')) + ->setArguments([$entityManager['second_level_cache']['region_lifetime'], $entityManager['second_level_cache']['region_lock_lifetime']]); + + $slcFactoryId = sprintf('doctrine.orm.%s_second_level_cache.default_cache_factory', $entityManager['name']); + $factoryClass = $entityManager['second_level_cache']['factory'] ?? '%doctrine.orm.second_level_cache.default_cache_factory.class%'; + + $definition = new Definition($factoryClass, [new Reference($regionsId), new Reference($driverId)]); + + $slcFactoryDef = $container + ->setDefinition($slcFactoryId, $definition); + + if (isset($entityManager['second_level_cache']['regions'])) { + foreach ($entityManager['second_level_cache']['regions'] as $name => $region) { + $regionRef = null; + $regionType = $region['type']; + + if ($regionType === 'service') { + $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name); + $regionRef = new Reference($region['service']); + + $container->setAlias($regionId, new Alias($region['service'], false)); + } + + if ($regionType === 'default' || $regionType === 'filelock') { + $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name); + $driverName = sprintf('second_level_cache.region.%s_driver', $name); + $driverMap = $region['cache_driver']; + $driverId = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container); + $regionRef = new Reference($regionId); + + $container + ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.default_region.class%')) + ->setArguments([$name, new Reference($driverId), $region['lifetime']]); + } + + if ($regionType === 'filelock') { + $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s_filelock', $entityManager['name'], $name); + + $container + ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.filelock_region.class%')) + ->setArguments([$regionRef, $region['lock_path'], $region['lock_lifetime']]); + + $regionRef = new Reference($regionId); + $regionsDef->addMethodCall('getLockLifetime', [$name, $region['lock_lifetime']]); + } + + $regionsDef->addMethodCall('setLifetime', [$name, $region['lifetime']]); + $slcFactoryDef->addMethodCall('setRegion', [$regionRef]); + } + } + + if ($entityManager['second_level_cache']['log_enabled']) { + $loggerChainId = sprintf('doctrine.orm.%s_second_level_cache.logger_chain', $entityManager['name']); + $loggerStatsId = sprintf('doctrine.orm.%s_second_level_cache.logger_statistics', $entityManager['name']); + $loggerChaingDef = $container->setDefinition($loggerChainId, new Definition('%doctrine.orm.second_level_cache.logger_chain.class%')); + $loggerStatsDef = $container->setDefinition($loggerStatsId, new Definition('%doctrine.orm.second_level_cache.logger_statistics.class%')); + + $loggerChaingDef->addMethodCall('setLogger', ['statistics', $loggerStatsDef]); + $configDef->addMethodCall('setCacheLogger', [$loggerChaingDef]); + + foreach ($entityManager['second_level_cache']['loggers'] as $name => $logger) { + $loggerId = sprintf('doctrine.orm.%s_second_level_cache.logger.%s', $entityManager['name'], $name); + $loggerRef = new Reference($logger['service']); + + $container->setAlias($loggerId, new Alias($logger['service'], false)); + $loggerChaingDef->addMethodCall('setLogger', [$name, $loggerRef]); + } + } + + $configDef->addMethodCall('setCacheFactory', [$slcFactoryDef]); + $configDef->addMethodCall('setRegionsConfiguration', [$regionsDef]); + $ormConfigDef->addMethodCall('setSecondLevelCacheEnabled', [$enabled]); + $ormConfigDef->addMethodCall('setSecondLevelCacheConfiguration', [$configDef]); + } + + /** + * {@inheritDoc} + */ + protected function getObjectManagerElementName($name): string + { + return 'doctrine.orm.' . $name; + } + + protected function getMappingObjectDefaultName(): string + { + return 'Entity'; + } + + protected function getMappingResourceConfigDirectory(?string $bundleDir = null): string + { + if ($bundleDir !== null && is_dir($bundleDir . '/config/doctrine')) { + return 'config/doctrine'; + } + + return 'Resources/config/doctrine'; + } + + protected function getMappingResourceExtension(): string + { + return 'orm'; + } + + /** + * {@inheritDoc} + */ + protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container): string + { + $aliasId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, $cacheName)); + + switch ($cacheDriver['type'] ?? 'pool') { + case 'service': + $serviceId = $cacheDriver['id']; + break; + + case 'pool': + $serviceId = $cacheDriver['pool'] ?? $this->createArrayAdapterCachePool($container, $objectManagerName, $cacheName); + break; + + default: + throw new InvalidArgumentException(sprintf( + 'Unknown cache of type "%s" configured for cache "%s" in entity manager "%s".', + $cacheDriver['type'], + $cacheName, + $objectManagerName, + )); + } + + $container->setAlias($aliasId, new Alias($serviceId, false)); + + return $aliasId; + } + + /** + * Loads a configured entity managers cache drivers. + * + * @param array $entityManager A configured ORM entity manager. + */ + protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container) + { + if (isset($entityManager['metadata_cache_driver'])) { + $this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container); + } else { + $this->createMetadataCache($entityManager['name'], $container); + } + + $this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container); + $this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container); + } + + private function createMetadataCache(string $objectManagerName, ContainerBuilder $container): void + { + $aliasId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, 'metadata_cache')); + $cacheId = sprintf('cache.doctrine.orm.%s.%s', $objectManagerName, 'metadata'); + + $cache = new Definition(ArrayAdapter::class); + + if (! $container->getParameter('kernel.debug')) { + $phpArrayFile = '%kernel.cache_dir%' . sprintf('/doctrine/orm/%s_metadata.php', $objectManagerName); + $cacheWarmerServiceId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, 'metadata_cache_warmer')); + + $container->register($cacheWarmerServiceId, DoctrineMetadataCacheWarmer::class) + ->setArguments([new Reference(sprintf('doctrine.orm.%s_entity_manager', $objectManagerName)), $phpArrayFile]) + ->addTag('kernel.cache_warmer', ['priority' => 1000]); // priority should be higher than ProxyCacheWarmer + + $cache = new Definition(PhpArrayAdapter::class, [$phpArrayFile, $cache]); + } + + $container->setDefinition($cacheId, $cache); + $container->setAlias($aliasId, $cacheId); + } + + /** + * Loads a property info extractor for each defined entity manager. + */ + private function loadPropertyInfoExtractor(string $entityManagerName, ContainerBuilder $container): void + { + $propertyExtractorDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.property_info_extractor', $entityManagerName), DoctrineExtractor::class); + $argumentId = sprintf('doctrine.orm.%s_entity_manager', $entityManagerName); + + $propertyExtractorDefinition->addArgument(new Reference($argumentId)); + + $propertyExtractorDefinition->addTag('property_info.list_extractor', ['priority' => -1001]); + $propertyExtractorDefinition->addTag('property_info.type_extractor', ['priority' => -999]); + $propertyExtractorDefinition->addTag('property_info.access_extractor', ['priority' => -999]); + } + + /** + * Loads a validator loader for each defined entity manager. + */ + private function loadValidatorLoader(string $entityManagerName, ContainerBuilder $container): void + { + $validatorLoaderDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.validator_loader', $entityManagerName), DoctrineLoader::class); + $validatorLoaderDefinition->addArgument(new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName))); + + $validatorLoaderDefinition->addTag('validator.auto_mapper', ['priority' => -100]); + } + + public function getXsdValidationBasePath(): string + { + return __DIR__ . '/../../config/schema'; + } + + public function getNamespace(): string + { + return 'http://symfony.com/schema/dic/doctrine'; + } + + /** + * {@inheritDoc} + */ + public function getConfiguration(array $config, ContainerBuilder $container): Configuration + { + return new Configuration((bool) $container->getParameter('kernel.debug')); + } + + protected function getMetadataDriverClass(string $driverType): string + { + return '%' . $this->getObjectManagerElementName('metadata.' . $driverType . '.class') . '%'; + } + + private function loadMessengerServices(ContainerBuilder $container): void + { + // If the Messenger component is installed, wire it: + + if (! interface_exists(MessageBusInterface::class)) { + return; + } + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader->load('messenger.xml'); + + // available in Symfony 6.3 + $container->removeDefinition('doctrine.orm.messenger.doctrine_schema_' . (class_exists(MessengerTransportDoctrineSchemaListener::class) ? 'subscriber' : 'listener')); + + /** + * The Doctrine transport component (symfony/doctrine-messenger) is optional. + * Remove service definition, if it is not available + */ + if (class_exists(DoctrineTransportFactory::class)) { + return; + } + + $container->removeDefinition('messenger.transport.doctrine.factory'); + $container->removeDefinition('doctrine.orm.messenger.doctrine_schema_subscriber'); + $container->removeDefinition('doctrine.orm.messenger.doctrine_schema_listener'); + } + + private function createArrayAdapterCachePool(ContainerBuilder $container, string $objectManagerName, string $cacheName): string + { + $id = sprintf('cache.doctrine.orm.%s.%s', $objectManagerName, str_replace('_cache', '', $cacheName)); + + $poolDefinition = $container->register($id, ArrayAdapter::class); + $poolDefinition->addTag('cache.pool'); + $container->setDefinition($id, $poolDefinition); + + return $id; + } + + /** + * @param string[] $connWithLogging + * @param string[] $connWithProfiling + * @param string[] $connWithBacktrace + * @param string[] $connWithTtl + */ + private function registerDbalMiddlewares( + ContainerBuilder $container, + array $connWithLogging, + array $connWithProfiling, + array $connWithBacktrace, + array $connWithTtl + ): void { + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader->load('middlewares.xml'); + + $loggingMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.logging_middleware'); + foreach ($connWithLogging as $connName) { + $loggingMiddlewareAbstractDef->addTag('doctrine.middleware', ['connection' => $connName, 'priority' => 10]); + } + + $container->getDefinition('doctrine.debug_data_holder')->replaceArgument(0, $connWithBacktrace); + $debugMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.debug_middleware'); + foreach ($connWithProfiling as $connName) { + $debugMiddlewareAbstractDef + ->addTag('doctrine.middleware', ['connection' => $connName, 'priority' => 10]); + } + + $idleConnectionMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.idle_connection_middleware'); + foreach ($connWithTtl as $connName) { + $idleConnectionMiddlewareAbstractDef + ->addTag('doctrine.middleware', ['connection' => $connName, 'priority' => 10]); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/DoctrineBundle.php b/vendor/doctrine/doctrine-bundle/src/DoctrineBundle.php new file mode 100644 index 0000000..9751cdb --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/DoctrineBundle.php @@ -0,0 +1,177 @@ +addCompilerPass(new class () implements CompilerPassInterface { + public function process(ContainerBuilder $container): void + { + if ($container->has('session.handler')) { + return; + } + + $container->removeDefinition('doctrine.orm.listeners.pdo_session_handler_schema_listener'); + } + }, PassConfig::TYPE_BEFORE_OPTIMIZATION); + + $container->addCompilerPass(new RegisterEventListenersAndSubscribersPass('doctrine.connections', 'doctrine.dbal.%s_connection.event_manager', 'doctrine'), PassConfig::TYPE_BEFORE_OPTIMIZATION); + + if ($container->hasExtension('security')) { + $security = $container->getExtension('security'); + + if ($security instanceof SecurityExtension) { + $security->addUserProviderFactory(new EntityFactory('entity', 'doctrine.orm.security.user.provider')); + } + } + + $container->addCompilerPass(new CacheCompatibilityPass()); + $container->addCompilerPass(new DoctrineValidationPass('orm')); + $container->addCompilerPass(new EntityListenerPass()); + $container->addCompilerPass(new ServiceRepositoryCompilerPass()); + $container->addCompilerPass(new IdGeneratorPass()); + $container->addCompilerPass(new WellKnownSchemaFilterPass()); + $container->addCompilerPass(new DbalSchemaFilterPass()); + $container->addCompilerPass(new CacheSchemaSubscriberPass(), PassConfig::TYPE_BEFORE_REMOVING, -10); + $container->addCompilerPass(new RemoveProfilerControllerPass()); + $container->addCompilerPass(new RemoveLoggingMiddlewarePass()); + $container->addCompilerPass(new MiddlewaresPass()); + + if (! class_exists(RegisterUidTypePass::class)) { + return; + } + + $container->addCompilerPass(new RegisterUidTypePass()); + } + + /** @return void */ + public function boot() + { + // Register an autoloader for proxies to avoid issues when unserializing them + // when the ORM is used. + if (! $this->container->hasParameter('doctrine.orm.proxy_namespace')) { + return; + } + + $namespace = (string) $this->container->getParameter('doctrine.orm.proxy_namespace'); + $dir = (string) $this->container->getParameter('doctrine.orm.proxy_dir'); + $proxyGenerator = null; + + if ($this->container->getParameter('doctrine.orm.auto_generate_proxy_classes')) { + // See https://github.com/symfony/symfony/pull/3419 for usage of references + /** @psalm-suppress UnsupportedPropertyReferenceUsage */ + $container = &$this->container; + + $proxyGenerator = static function ($proxyDir, $proxyNamespace, $class) use (&$container): void { + $originalClassName = (new DefaultProxyClassNameResolver())->resolveClassName($class); + $registry = $container->get('doctrine'); + assert($registry instanceof Registry); + + foreach ($registry->getManagers() as $em) { + assert($em instanceof EntityManagerInterface); + if (! $em->getConfiguration()->getAutoGenerateProxyClasses()) { + continue; + } + + $metadataFactory = $em->getMetadataFactory(); + + if ($metadataFactory->isTransient($originalClassName)) { + continue; + } + + $classMetadata = $metadataFactory->getMetadataFor($originalClassName); + + $em->getProxyFactory()->generateProxyClasses([$classMetadata]); + + clearstatcache(true, Autoloader::resolveFile($proxyDir, $proxyNamespace, $class)); + + break; + } + }; + } + + $this->autoloader = Autoloader::register($dir, $namespace, $proxyGenerator); + } + + /** @return void */ + public function shutdown() + { + if ($this->autoloader !== null) { + spl_autoload_unregister($this->autoloader); + $this->autoloader = null; + } + + // Clear all entity managers to clear references to entities for GC + if ($this->container->hasParameter('doctrine.entity_managers')) { + foreach ($this->container->getParameter('doctrine.entity_managers') as $id) { + if (! $this->container->initialized($id)) { + continue; + } + + $this->container->get($id)->clear(); + } + } + + // Close all connections to avoid reaching too many connections in the process when booting again later (tests) + if (! $this->container->hasParameter('doctrine.connections')) { + return; + } + + foreach ($this->container->getParameter('doctrine.connections') as $id) { + if (! $this->container->initialized($id)) { + continue; + } + + $this->container->get($id)->close(); + } + } + + /** @return void */ + public function registerCommands(Application $application) + { + } + + public function getPath(): string + { + return dirname(__DIR__); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/EventSubscriber/EventSubscriberInterface.php b/vendor/doctrine/doctrine-bundle/src/EventSubscriber/EventSubscriberInterface.php new file mode 100644 index 0000000..5cbae93 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/EventSubscriber/EventSubscriberInterface.php @@ -0,0 +1,10 @@ +> */ + private array $filtersParameters = []; + + /** + * @param string[] $enabledFilters + * @param array> $filtersParameters + */ + public function __construct(array $enabledFilters, array $filtersParameters) + { + $this->enabledFilters = $enabledFilters; + $this->filtersParameters = $filtersParameters; + } + + /** + * Create a connection by name. + */ + public function configure(EntityManagerInterface $entityManager) + { + $this->enableFilters($entityManager); + } + + /** + * Enables filters for a given entity manager + */ + private function enableFilters(EntityManagerInterface $entityManager): void + { + if (empty($this->enabledFilters)) { + return; + } + + $filterCollection = $entityManager->getFilters(); + foreach ($this->enabledFilters as $filter) { + $this->setFilterParameters($filter, $filterCollection->enable($filter)); + } + } + + /** + * Sets default parameters for a given filter + */ + private function setFilterParameters(string $name, SQLFilter $filter): void + { + if (empty($this->filtersParameters[$name])) { + return; + } + + $parameters = $this->filtersParameters[$name]; + foreach ($parameters as $paramName => $paramValue) { + $filter->setParameter($paramName, $paramValue); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Mapping/ClassMetadataCollection.php b/vendor/doctrine/doctrine-bundle/src/Mapping/ClassMetadataCollection.php new file mode 100644 index 0000000..7b3a9f5 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Mapping/ClassMetadataCollection.php @@ -0,0 +1,50 @@ +metadata = $metadata; + } + + /** @return ClassMetadata[] */ + public function getMetadata() + { + return $this->metadata; + } + + /** @param string $path */ + public function setPath($path) + { + $this->path = $path; + } + + /** @return string|null */ + public function getPath() + { + return $this->path; + } + + /** @param string $namespace */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + } + + /** @return string|null */ + public function getNamespace() + { + return $this->namespace; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Mapping/ClassMetadataFactory.php b/vendor/doctrine/doctrine-bundle/src/Mapping/ClassMetadataFactory.php new file mode 100644 index 0000000..a04a16a --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Mapping/ClassMetadataFactory.php @@ -0,0 +1,33 @@ +customGeneratorDefinition; + + if (! isset($customGeneratorDefinition['instance'])) { + return; + } + + assert($customGeneratorDefinition['instance'] instanceof AbstractIdGenerator); + + $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM); + $class->setIdGenerator($customGeneratorDefinition['instance']); + unset($customGeneratorDefinition['instance']); + $class->setCustomGeneratorDefinition($customGeneratorDefinition); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Mapping/ContainerEntityListenerResolver.php b/vendor/doctrine/doctrine-bundle/src/Mapping/ContainerEntityListenerResolver.php new file mode 100644 index 0000000..5bc96d7 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Mapping/ContainerEntityListenerResolver.php @@ -0,0 +1,101 @@ +container = $container; + } + + /** + * {@inheritDoc} + */ + public function clear($className = null): void + { + if ($className === null) { + $this->instances = []; + + return; + } + + $className = $this->normalizeClassName($className); + + unset($this->instances[$className]); + } + + /** + * {@inheritDoc} + */ + public function register($object): void + { + if (! is_object($object)) { + throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object))); + } + + $className = $this->normalizeClassName(get_class($object)); + + $this->instances[$className] = $object; + } + + /** + * {@inheritDoc} + */ + public function registerService($className, $serviceId) + { + $this->serviceIds[$this->normalizeClassName($className)] = $serviceId; + } + + /** + * {@inheritDoc} + */ + public function resolve($className): object + { + $className = $this->normalizeClassName($className); + + if (! isset($this->instances[$className])) { + if (isset($this->serviceIds[$className])) { + $this->instances[$className] = $this->resolveService($this->serviceIds[$className]); + } else { + $this->instances[$className] = new $className(); + } + } + + return $this->instances[$className]; + } + + private function resolveService(string $serviceId): object + { + if (! $this->container->has($serviceId)) { + throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId)); + } + + return $this->container->get($serviceId); + } + + private function normalizeClassName(string $className): string + { + return trim($className, '\\'); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Mapping/DisconnectedMetadataFactory.php b/vendor/doctrine/doctrine-bundle/src/Mapping/DisconnectedMetadataFactory.php new file mode 100644 index 0000000..86cbdfb --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Mapping/DisconnectedMetadataFactory.php @@ -0,0 +1,174 @@ +registry = $registry; + } + + /** + * Gets the metadata of all classes of a bundle. + * + * @param BundleInterface $bundle A BundleInterface instance + * + * @return ClassMetadataCollection A ClassMetadataCollection instance + * + * @throws RuntimeException When bundle does not contain mapped entities. + */ + public function getBundleMetadata(BundleInterface $bundle) + { + $namespace = $bundle->getNamespace(); + $metadata = $this->getMetadataForNamespace($namespace); + if (! $metadata->getMetadata()) { + throw new RuntimeException(sprintf('Bundle "%s" does not contain any mapped entities.', $bundle->getName())); + } + + $path = $this->getBasePathForClass($bundle->getName(), $bundle->getNamespace(), $bundle->getPath()); + + $metadata->setPath($path); + $metadata->setNamespace($bundle->getNamespace()); + + return $metadata; + } + + /** + * Gets the metadata of a class. + * + * @param string $class A class name + * @param string $path The path where the class is stored (if known) + * + * @return ClassMetadataCollection A ClassMetadataCollection instance + * + * @throws MappingException When class is not valid entity or mapped superclass. + */ + public function getClassMetadata($class, $path = null) + { + $metadata = $this->getMetadataForClass($class); + if (! $metadata->getMetadata()) { + throw MappingException::classIsNotAValidEntityOrMappedSuperClass($class); + } + + $this->findNamespaceAndPathForMetadata($metadata, $path); + + return $metadata; + } + + /** + * Gets the metadata of all classes of a namespace. + * + * @param string $namespace A namespace name + * @param string $path The path where the class is stored (if known) + * + * @return ClassMetadataCollection A ClassMetadataCollection instance + * + * @throws RuntimeException When namespace not contain mapped entities. + */ + public function getNamespaceMetadata($namespace, $path = null) + { + $metadata = $this->getMetadataForNamespace($namespace); + if (! $metadata->getMetadata()) { + throw new RuntimeException(sprintf('Namespace "%s" does not contain any mapped entities.', $namespace)); + } + + $this->findNamespaceAndPathForMetadata($metadata, $path); + + return $metadata; + } + + /** + * Find and configure path and namespace for the metadata collection. + * + * @param string|null $path + * + * @throws RuntimeException When unable to determine the path. + */ + public function findNamespaceAndPathForMetadata(ClassMetadataCollection $metadata, $path = null) + { + $r = new ReflectionClass($metadata->getMetadata()[0]->name); + $metadata->setPath($this->getBasePathForClass($r->getName(), $r->getNamespaceName(), dirname($r->getFilename()))); + $metadata->setNamespace($r->getNamespaceName()); + } + + /** + * Get a base path for a class + * + * @throws RuntimeException When base path not found. + */ + private function getBasePathForClass(string $name, string $namespace, string $path): string + { + $namespace = str_replace('\\', '/', $namespace); + $search = str_replace('\\', '/', $path); + $destination = str_replace('/' . $namespace, '', $search, $c); + + if ($c !== 1) { + throw new RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination)); + } + + return $destination; + } + + private function getMetadataForNamespace(string $namespace): ClassMetadataCollection + { + $metadata = []; + foreach ($this->getAllMetadata() as $m) { + if (strpos($m->name, $namespace) !== 0) { + continue; + } + + $metadata[] = $m; + } + + return new ClassMetadataCollection($metadata); + } + + private function getMetadataForClass(string $entity): ClassMetadataCollection + { + foreach ($this->registry->getManagers() as $em) { + $cmf = new DisconnectedClassMetadataFactory(); + $cmf->setEntityManager($em); + + if (! $cmf->isTransient($entity)) { + return new ClassMetadataCollection([$cmf->getMetadataFor($entity)]); + } + } + + return new ClassMetadataCollection([]); + } + + /** @return ClassMetadata[] */ + private function getAllMetadata(): array + { + $metadata = []; + foreach ($this->registry->getManagers() as $em) { + $cmf = new DisconnectedClassMetadataFactory(); + $cmf->setEntityManager($em); + foreach ($cmf->getAllMetadata() as $m) { + $metadata[] = $m; + } + } + + return $metadata; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Mapping/EntityListenerServiceResolver.php b/vendor/doctrine/doctrine-bundle/src/Mapping/EntityListenerServiceResolver.php new file mode 100644 index 0000000..03120ef --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Mapping/EntityListenerServiceResolver.php @@ -0,0 +1,15 @@ +driver = $driver; + $this->idGeneratorLocator = $idGeneratorLocator; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + return $this->driver->getAllClassNames(); + } + + /** + * {@inheritDoc} + */ + public function isTransient($className): bool + { + return $this->driver->isTransient($className); + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata): void + { + $this->driver->loadMetadataForClass($className, $metadata); + + if ( + ! $metadata instanceof OrmClassMetadata + || $metadata->generatorType !== OrmClassMetadata::GENERATOR_TYPE_CUSTOM + || ! isset($metadata->customGeneratorDefinition['class']) + || ! $this->idGeneratorLocator->has($metadata->customGeneratorDefinition['class']) + ) { + return; + } + + $idGenerator = $this->idGeneratorLocator->get($metadata->customGeneratorDefinition['class']); + $metadata->setCustomGeneratorDefinition(['instance' => $idGenerator] + $metadata->customGeneratorDefinition); + $metadata->setIdGeneratorType(OrmClassMetadata::GENERATOR_TYPE_NONE); + } + + /** + * Returns the inner driver + */ + public function getDriver(): MappingDriverInterface + { + return $this->driver; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Middleware/BacktraceDebugDataHolder.php b/vendor/doctrine/doctrine-bundle/src/Middleware/BacktraceDebugDataHolder.php new file mode 100644 index 0000000..8eae911 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Middleware/BacktraceDebugDataHolder.php @@ -0,0 +1,92 @@ +[]> */ + private array $backtraces = []; + + /** @param string[] $connWithBacktraces */ + public function __construct(array $connWithBacktraces) + { + $this->connWithBacktraces = $connWithBacktraces; + } + + public function reset(): void + { + parent::reset(); + + $this->backtraces = []; + } + + public function addQuery(string $connectionName, Query $query): void + { + parent::addQuery($connectionName, $query); + + if (! in_array($connectionName, $this->connWithBacktraces, true)) { + return; + } + + // array_slice to skip middleware calls in the trace + $this->backtraces[$connectionName][] = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 2); + } + + /** @return array[]> */ + public function getData(): array + { + $dataWithBacktraces = []; + + $data = parent::getData(); + foreach ($data as $connectionName => $dataForConn) { + $dataWithBacktraces[$connectionName] = $this->getDataForConnection($connectionName, $dataForConn); + } + + return $dataWithBacktraces; + } + + /** + * @param mixed[][] $dataForConn + * + * @return mixed[][] + */ + private function getDataForConnection(string $connectionName, array $dataForConn): array + { + $data = []; + + foreach ($dataForConn as $idx => $record) { + $data[] = $this->addBacktracesIfAvailable($connectionName, $record, $idx); + } + + return $data; + } + + /** + * @param mixed[] $record + * + * @return mixed[] + */ + private function addBacktracesIfAvailable(string $connectionName, array $record, int $idx): array + { + if (! isset($this->backtraces[$connectionName])) { + return $record; + } + + $record['backtrace'] = $this->backtraces[$connectionName][$idx]; + + return $record; + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Middleware/ConnectionNameAwareInterface.php b/vendor/doctrine/doctrine-bundle/src/Middleware/ConnectionNameAwareInterface.php new file mode 100644 index 0000000..5bb2e41 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Middleware/ConnectionNameAwareInterface.php @@ -0,0 +1,8 @@ +debugDataHolder = $debugDataHolder; + $this->stopwatch = $stopwatch; + } + + public function setConnectionName(string $name): void + { + $this->connectionName = $name; + } + + public function wrap(DriverInterface $driver): DriverInterface + { + /** @psalm-suppress InternalClass,InternalMethod */ + return new Driver($driver, $this->debugDataHolder, $this->stopwatch, $this->connectionName); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Middleware/IdleConnectionMiddleware.php b/vendor/doctrine/doctrine-bundle/src/Middleware/IdleConnectionMiddleware.php new file mode 100644 index 0000000..d64d22f --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Middleware/IdleConnectionMiddleware.php @@ -0,0 +1,36 @@ + */ + private array $ttlByConnection; + private string $connectionName; + + /** + * @param ArrayObject $connectionExpiries + * @param array $ttlByConnection + */ + public function __construct(ArrayObject $connectionExpiries, array $ttlByConnection) + { + $this->connectionExpiries = $connectionExpiries; + $this->ttlByConnection = $ttlByConnection; + } + + public function setConnectionName(string $name): void + { + $this->connectionName = $name; + } + + public function wrap(Driver $driver): IdleConnectionDriver + { + return new IdleConnectionDriver($driver, $this->connectionExpiries, $this->ttlByConnection[$this->connectionName], $this->connectionName); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Orm/ManagerRegistryAwareEntityManagerProvider.php b/vendor/doctrine/doctrine-bundle/src/Orm/ManagerRegistryAwareEntityManagerProvider.php new file mode 100644 index 0000000..4ef72eb --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Orm/ManagerRegistryAwareEntityManagerProvider.php @@ -0,0 +1,43 @@ +managerRegistry = $managerRegistry; + } + + public function getDefaultManager(): EntityManagerInterface + { + return $this->getManager($this->managerRegistry->getDefaultManagerName()); + } + + public function getManager(string $name): EntityManagerInterface + { + $em = $this->managerRegistry->getManager($name); + + if ($em instanceof EntityManagerInterface) { + return $em; + } + + throw new RuntimeException( + sprintf( + 'Only managers of type "%s" are supported. Instance of "%s given.', + EntityManagerInterface::class, + get_class($em), + ), + ); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Registry.php b/vendor/doctrine/doctrine-bundle/src/Registry.php new file mode 100644 index 0000000..aa84319 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Registry.php @@ -0,0 +1,88 @@ +container = $container; + + parent::__construct('ORM', $connections, $entityManagers, $defaultConnection, $defaultEntityManager, Proxy::class); + } + + /** + * Resolves a registered namespace alias to the full namespace. + * + * This method looks for the alias in all registered entity managers. + * + * @see Configuration::getEntityNamespace + * + * @param string $alias The alias + * + * @return string The full namespace + */ + public function getAliasNamespace($alias) + { + foreach (array_keys($this->getManagers()) as $name) { + $objectManager = $this->getManager($name); + + if (! $objectManager instanceof EntityManagerInterface) { + continue; + } + + try { + /** @psalm-suppress UndefinedMethod ORM < 3 specific */ + return $objectManager->getConfiguration()->getEntityNamespace($alias); + } catch (ORMException $e) { + } + } + + throw ORMException::unknownEntityNamespace($alias); + } + + public function reset(): void + { + foreach ($this->getManagerNames() as $managerName => $serviceId) { + $this->resetOrClearManager($managerName, $serviceId); + } + } + + private function resetOrClearManager(string $managerName, string $serviceId): void + { + if (! $this->container->initialized($serviceId)) { + return; + } + + $manager = $this->container->get($serviceId); + + assert($manager instanceof EntityManagerInterface); + + if ((! $manager instanceof LazyLoadingInterface && ! $manager instanceof LazyObjectInterface) || $manager->isOpen()) { + $manager->clear(); + + return; + } + + $this->resetManager($managerName); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Repository/ContainerRepositoryFactory.php b/vendor/doctrine/doctrine-bundle/src/Repository/ContainerRepositoryFactory.php new file mode 100644 index 0000000..9d007c8 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Repository/ContainerRepositoryFactory.php @@ -0,0 +1,111 @@ + */ + private array $managedRepositories = []; + + private ContainerInterface $container; + + /** @param ContainerInterface $container A service locator containing the repositories */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @param class-string $entityName + * + * @return ObjectRepository + * @psalm-return ($strictTypeCheck is true ? EntityRepository : ObjectRepository) + * + * @template T of object + */ + private function doGetRepository(EntityManagerInterface $entityManager, string $entityName, bool $strictTypeCheck): ObjectRepository + { + $metadata = $entityManager->getClassMetadata($entityName); + $repositoryServiceId = $metadata->customRepositoryClassName; + + $customRepositoryName = $metadata->customRepositoryClassName; + if ($customRepositoryName !== null) { + // fetch from the container + if ($this->container->has($customRepositoryName)) { + $repository = $this->container->get($customRepositoryName); + + if (! $repository instanceof EntityRepository && $strictTypeCheck) { + throw new RuntimeException(sprintf('The service "%s" must extend EntityRepository (e.g. by extending ServiceEntityRepository), "%s" given.', $repositoryServiceId, get_debug_type($repository))); + } + + if (! $repository instanceof ObjectRepository) { + throw new RuntimeException(sprintf('The service "%s" must implement ObjectRepository (or extend a base class, like ServiceEntityRepository), "%s" given.', $repositoryServiceId, get_debug_type($repository))); + } + + if (! $repository instanceof EntityRepository) { + trigger_deprecation('doctrine/doctrine-bundle', '2.11', 'The service "%s" of type "%s" should extend "%s", not doing so is deprecated.', $repositoryServiceId, get_debug_type($repository), EntityRepository::class); + } + + /** @psalm-var ObjectRepository */ + return $repository; + } + + // if not in the container but the class/id implements the interface, throw an error + if (is_a($customRepositoryName, ServiceEntityRepositoryInterface::class, true)) { + throw new RuntimeException(sprintf('The "%s" entity repository implements "%s", but its service could not be found. Make sure the service exists and is tagged with "%s".', $customRepositoryName, ServiceEntityRepositoryInterface::class, ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG)); + } + + if (! class_exists($customRepositoryName)) { + throw new RuntimeException(sprintf('The "%s" entity has a repositoryClass set to "%s", but this is not a valid class. Check your class naming. If this is meant to be a service id, make sure this service exists and is tagged with "%s".', $metadata->name, $customRepositoryName, ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG)); + } + + // allow the repository to be created below + } + + return $this->getOrCreateRepository($entityManager, $metadata); + } + + /** + * @param ClassMetadata $metadata + * + * @return ObjectRepository + * + * @template TEntity of object + */ + private function getOrCreateRepository( + EntityManagerInterface $entityManager, + ClassMetadata $metadata + ): ObjectRepository { + $repositoryHash = $metadata->getName() . spl_object_hash($entityManager); + if (isset($this->managedRepositories[$repositoryHash])) { + /** @psalm-var ObjectRepository */ + return $this->managedRepositories[$repositoryHash]; + } + + $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); + + /** @psalm-var ObjectRepository */ + return $this->managedRepositories[$repositoryHash] = new $repositoryClassName($entityManager, $metadata); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Repository/LazyServiceEntityRepository.php b/vendor/doctrine/doctrine-bundle/src/Repository/LazyServiceEntityRepository.php new file mode 100644 index 0000000..c690599 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Repository/LazyServiceEntityRepository.php @@ -0,0 +1,82 @@ + + */ +class LazyServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface +{ + private ManagerRegistry $registry; + private string $entityClass; + + /** + * @param string $entityClass The class name of the entity this repository manages + * @psalm-param class-string $entityClass + */ + public function __construct(ManagerRegistry $registry, string $entityClass) + { + $this->registry = $registry; + $this->entityClass = $entityClass; + + if ($this instanceof LazyObjectInterface) { + $this->initialize(); + + return; + } + + unset($this->_em); + unset($this->_class); + unset($this->_entityName); + } + + /** @return mixed */ + public function __get(string $name) + { + $this->initialize(); + + $scope = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'] ?? null; + + return (function () use ($name) { + return $this->$name; + })->bindTo($this, $scope)(); + } + + public function __isset(string $name): bool + { + $this->initialize(); + + $scope = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'] ?? null; + + return (function () use ($name) { + return isset($this->$name); + })->bindTo($this, $scope)(); + } + + private function initialize(): void + { + $manager = $this->registry->getManagerForClass($this->entityClass); + + if ($manager === null) { + throw new LogicException(sprintf( + 'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.', + $this->entityClass, + )); + } + + parent::__construct($manager, $manager->getClassMetadata($this->entityClass)); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Repository/RepositoryFactoryCompatibility.php b/vendor/doctrine/doctrine-bundle/src/Repository/RepositoryFactoryCompatibility.php new file mode 100644 index 0000000..f50ff76 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Repository/RepositoryFactoryCompatibility.php @@ -0,0 +1,41 @@ +hasReturnType()) { + // ORM >= 3 + /** @internal */ + trait RepositoryFactoryCompatibility + { + /** + * Gets the repository for an entity class. + * + * @param class-string $entityName + * + * @return EntityRepository + * + * @template T of object + */ + public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository + { + return $this->doGetRepository($entityManager, $entityName, true); + } + } +} else { + // ORM 2 + /** @internal */ + trait RepositoryFactoryCompatibility + { + /** {@inheritDoc} */ + public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository + { + return $this->doGetRepository($entityManager, $entityName, false); + } + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Repository/ServiceEntityRepository.php b/vendor/doctrine/doctrine-bundle/src/Repository/ServiceEntityRepository.php new file mode 100644 index 0000000..d4c59e2 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Repository/ServiceEntityRepository.php @@ -0,0 +1,53 @@ + + */ + class ServiceEntityRepository extends LazyServiceEntityRepository + { + } +} else { + // ORM 3 + /** + * Optional EntityRepository base class with a simplified constructor (for autowiring). + * + * To use in your class, inject the "registry" service and call + * the parent constructor. For example: + * + * class YourEntityRepository extends ServiceEntityRepository + * { + * public function __construct(ManagerRegistry $registry) + * { + * parent::__construct($registry, YourEntity::class); + * } + * } + * + * @template T of object + * @template-extends ServiceEntityRepositoryProxy + */ + class ServiceEntityRepository extends ServiceEntityRepositoryProxy + { + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Repository/ServiceEntityRepositoryInterface.php b/vendor/doctrine/doctrine-bundle/src/Repository/ServiceEntityRepositoryInterface.php new file mode 100644 index 0000000..fe9ce4d --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Repository/ServiceEntityRepositoryInterface.php @@ -0,0 +1,10 @@ + + */ +class ServiceEntityRepositoryProxy extends EntityRepository implements ServiceEntityRepositoryInterface +{ + private ?EntityRepository $repository = null; + + /** @param class-string $entityClass The class name of the entity this repository manages */ + public function __construct( + private readonly ManagerRegistry $registry, + private readonly string $entityClass, + ) { + if (! $this instanceof LazyObjectInterface) { + return; + } + + $this->repository = $this->resolveRepository(); + } + + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + { + return ($this->repository ??= $this->resolveRepository()) + ->createQueryBuilder($alias, $indexBy); + } + + public function createResultSetMappingBuilder(string $alias): ResultSetMappingBuilder + { + return ($this->repository ??= $this->resolveRepository()) + ->createResultSetMappingBuilder($alias); + } + + public function find(mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null + { + /** @psalm-suppress InvalidReturnStatement This proxy is used only in combination with newer parent class */ + return ($this->repository ??= $this->resolveRepository()) + ->find($id, $lockMode, $lockVersion); + } + + /** + * {@inheritDoc} + * + * @psalm-suppress InvalidReturnStatement This proxy is used only in combination with newer parent class + * @psalm-suppress InvalidReturnType This proxy is used only in combination with newer parent class + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return ($this->repository ??= $this->resolveRepository()) + ->findBy($criteria, $orderBy, $limit, $offset); + } + + /** {@inheritDoc} */ + public function findOneBy(array $criteria, ?array $orderBy = null): object|null + { + /** @psalm-suppress InvalidReturnStatement This proxy is used only in combination with newer parent class */ + return ($this->repository ??= $this->resolveRepository()) + ->findOneBy($criteria, $orderBy); + } + + /** {@inheritDoc} */ + public function count(array $criteria = []): int + { + return ($this->repository ??= $this->resolveRepository())->count($criteria); + } + + /** + * {@inheritDoc} + */ + public function __call(string $method, array $arguments): mixed + { + return ($this->repository ??= $this->resolveRepository())->$method(...$arguments); + } + + protected function getEntityName(): string + { + return ($this->repository ??= $this->resolveRepository())->getEntityName(); + } + + protected function getEntityManager(): EntityManagerInterface + { + return ($this->repository ??= $this->resolveRepository())->getEntityManager(); + } + + /** @psalm-suppress InvalidReturnType This proxy is used only in combination with newer parent class */ + protected function getClassMetadata(): ClassMetadata + { + /** @psalm-suppress InvalidReturnStatement This proxy is used only in combination with newer parent class */ + return ($this->repository ??= $this->resolveRepository())->getClassMetadata(); + } + + public function matching(Criteria $criteria): AbstractLazyCollection&Selectable + { + return ($this->repository ??= $this->resolveRepository())->matching($criteria); + } + + private function resolveRepository(): EntityRepository + { + $manager = $this->registry->getManagerForClass($this->entityClass); + + if ($manager === null) { + throw new LogicException(sprintf( + 'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.', + $this->entityClass, + )); + } + + return new EntityRepository($manager, $manager->getClassMetadata($this->entityClass)); + } +} diff --git a/vendor/doctrine/doctrine-bundle/src/Twig/DoctrineExtension.php b/vendor/doctrine/doctrine-bundle/src/Twig/DoctrineExtension.php new file mode 100644 index 0000000..7c5f753 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/src/Twig/DoctrineExtension.php @@ -0,0 +1,197 @@ + ['html'], 'deprecated' => true]), + new TwigFilter('doctrine_prettify_sql', [$this, 'prettifySql'], ['is_safe' => ['html']]), + new TwigFilter('doctrine_format_sql', [$this, 'formatSql'], ['is_safe' => ['html']]), + new TwigFilter('doctrine_replace_query_parameters', [$this, 'replaceQueryParameters']), + ]; + } + + /** + * Escape parameters of a SQL query + * DON'T USE THIS FUNCTION OUTSIDE ITS INTENDED SCOPE + * + * @internal + * + * @param mixed $parameter + * + * @return string + */ + public static function escapeFunction($parameter) + { + $result = $parameter; + + switch (true) { + // Check if result is non-unicode string using PCRE_UTF8 modifier + case is_string($result) && ! preg_match('//u', $result): + $result = '0x' . strtoupper(bin2hex($result)); + break; + + case is_string($result): + $result = "'" . addslashes($result) . "'"; + break; + + case is_array($result): + foreach ($result as &$value) { + $value = static::escapeFunction($value); + } + + $result = implode(', ', $result) ?: 'NULL'; + break; + + case is_object($result) && method_exists($result, '__toString'): + $result = addslashes($result->__toString()); + break; + + case $result === null: + $result = 'NULL'; + break; + + case is_bool($result): + $result = $result ? '1' : '0'; + break; + } + + return $result; + } + + /** + * Return a query with the parameters replaced + * + * @param string $query + * @param mixed[]|Data $parameters + * + * @return string + */ + public function replaceQueryParameters($query, $parameters) + { + if ($parameters instanceof Data) { + $parameters = $parameters->getValue(true); + } + + $i = 0; + + if (! array_key_exists(0, $parameters) && array_key_exists(1, $parameters)) { + $i = 1; + } + + return preg_replace_callback( + '/\?|((?setUpSqlFormatter(true, true); + + if ($highlightOnly) { + return $this->sqlFormatter->highlight($sql); + } + + return sprintf( + '
%s
', + $this->sqlFormatter->format($sql), + ); + } + + public function prettifySql(string $sql): string + { + $this->setUpSqlFormatter(); + + return $this->sqlFormatter->highlight($sql); + } + + public function formatSql(string $sql, bool $highlight): string + { + $this->setUpSqlFormatter($highlight); + + return $this->sqlFormatter->format($sql); + } + + private function setUpSqlFormatter(bool $highlight = true, bool $legacy = false): void + { + $this->sqlFormatter = new SqlFormatter($highlight ? new HtmlHighlighter([ + HtmlHighlighter::HIGHLIGHT_PRE => 'class="highlight highlight-sql"', + HtmlHighlighter::HIGHLIGHT_QUOTE => 'class="string"', + HtmlHighlighter::HIGHLIGHT_BACKTICK_QUOTE => 'class="string"', + HtmlHighlighter::HIGHLIGHT_RESERVED => 'class="keyword"', + HtmlHighlighter::HIGHLIGHT_BOUNDARY => 'class="symbol"', + HtmlHighlighter::HIGHLIGHT_NUMBER => 'class="number"', + HtmlHighlighter::HIGHLIGHT_WORD => 'class="word"', + HtmlHighlighter::HIGHLIGHT_ERROR => 'class="error"', + HtmlHighlighter::HIGHLIGHT_COMMENT => 'class="comment"', + HtmlHighlighter::HIGHLIGHT_VARIABLE => 'class="variable"', + ], ! $legacy) : new NullHighlighter()); + } +} diff --git a/vendor/doctrine/doctrine-bundle/templates/Collector/database.svg b/vendor/doctrine/doctrine-bundle/templates/Collector/database.svg new file mode 100644 index 0000000..40a2b45 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/templates/Collector/database.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/vendor/doctrine/doctrine-bundle/templates/Collector/db.html.twig b/vendor/doctrine/doctrine-bundle/templates/Collector/db.html.twig new file mode 100644 index 0000000..90dbc18 --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/templates/Collector/db.html.twig @@ -0,0 +1,533 @@ +{% extends request.isXmlHttpRequest ? '@WebProfiler/Profiler/ajax_layout.html.twig' : '@WebProfiler/Profiler/layout.html.twig' %} + +{% import _self as helper %} + +{% block toolbar %} + {% if collector.querycount > 0 or collector.invalidEntityCount > 0 %} + + {% set icon %} + {% set status = collector.invalidEntityCount > 0 ? 'red' : collector.querycount > 50 ? 'yellow' %} + + {% if profiler_markup_version >= 3 %} + {{ include('@Doctrine/Collector/database.svg') }} + {% else %} + {{ include('@Doctrine/Collector/icon.svg') }} + {% endif %} + + {% if collector.querycount == 0 and collector.invalidEntityCount > 0 %} + {{ collector.invalidEntityCount }} + errors + {% else %} + {{ collector.querycount }} + + in + {{ '%0.2f'|format(collector.time * 1000) }} + ms + + {% endif %} + {% endset %} + + {% set text %} +
+ Database Queries + {{ collector.querycount }} +
+
+ Different statements + {{ collector.groupedQueryCount }} +
+
+ Query time + {{ '%0.2f'|format(collector.time * 1000) }} ms +
+
+ Invalid entities + {{ collector.invalidEntityCount }} +
+ {% if collector.cacheEnabled %} +
+ Cache hits + {{ collector.cacheHitsCount }} +
+
+ Cache misses + {{ collector.cacheMissesCount }} +
+
+ Cache puts + {{ collector.cachePutsCount }} +
+ {% else %} +
+ Second Level Cache + disabled +
+ {% endif %} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status|default('') }) }} + + {% endif %} +{% endblock %} + +{% block menu %} + + {{ include('@Doctrine/Collector/' ~ (profiler_markup_version < 3 ? 'icon' : 'database') ~ '.svg') }} + Doctrine + {% if collector.invalidEntityCount %} + + {{ collector.invalidEntityCount }} + + {% endif %} + +{% endblock %} + +{% block panel %} + {% if 'explain' == page %} + {{ render(controller('Doctrine\\Bundle\\DoctrineBundle\\Controller\\ProfilerController::explainAction', { + token: token, + panel: 'db', + connectionName: request.query.get('connection'), + query: request.query.get('query') + })) }} + {% else %} + {{ block('queries') }} + {% endif %} +{% endblock %} + +{% block queries %} + + +

Query Metrics

+ +
+
+
+ {{ collector.querycount }} + Database Queries +
+ +
+ {{ collector.groupedQueryCount }} + Different statements +
+ +
+ {{ '%0.2f'|format(collector.time * 1000) }} ms + Query time +
+ +
+ {{ collector.invalidEntityCount }} + Invalid entities +
+
+ + {% if collector.cacheEnabled %} +
+
+ {{ collector.cacheHitsCount }} + Cache hits +
+
+ {{ collector.cacheMissesCount }} + Cache misses +
+
+ {{ collector.cachePutsCount }} + Cache puts +
+
+ {% endif %} +
+ +
+
+ {% set group_queries = request.query.getBoolean('group') %} +

+ {% if group_queries %} + Grouped Statements + {% else %} + Queries + {% endif %} +

+ +
+ {% if not collector.queries %} +
+

No executed queries.

+
+ {% else %} + {% if group_queries %} +

Show all queries

+ {% else %} +

Group similar statements

+ {% endif %} + + {% for connection, queries in collector.queries %} + {% if collector.connections|length > 1 %} +

{{ connection }} connection

+ {% endif %} + + {% if queries is empty %} +
+

No database queries were performed.

+
+ {% else %} + {% if group_queries %} + {% set queries = collector.groupedQueries[connection] %} + {% endif %} + + + + {% if group_queries %} + + + {% else %} + + + {% endif %} + + + + + {% for i, query in queries %} + {% set i = group_queries ? query.index : i %} + + {% if group_queries %} + + + {% else %} + + + {% endif %} + + + {% endfor %} + +
TimeCount#TimeInfo
+ + {{ '%0.2f'|format(query.executionMS * 1000) }} ms
({{ '%0.2f'|format(query.executionPercent) }}%)
+
{{ query.count }}{{ loop.index }}{{ '%0.2f'|format(query.executionMS * 1000) }} ms + {{ query.sql|doctrine_prettify_sql }} + +
+ Parameters: {{ profiler_dump(query.params, 2) }} +
+ +
+ View formatted query + + {% if query.runnable %} +    + View runnable query + {% endif %} + + {% if query.explainable %} +    + Explain query + {% endif %} + + {% if query.backtrace is defined %} +    + View query backtrace + {% endif %} +
+ + + + {% if query.runnable %} + + {% endif %} + + {% if query.explainable %} +
+ {% endif %} + + {% if query.backtrace is defined %} + + {% endif %} +
+ {% endif %} + {% endfor %} + {% endif %} +
+
+ +
+

Database Connections

+
+ {% if not collector.connections %} +
+

There are no configured database connections.

+
+ {% else %} + {{ helper.render_simple_table('Name', 'Service', collector.connections) }} + {% endif %} +
+
+ +
+

Entity Managers

+
+ + {% if not collector.managers %} +
+

There are no configured entity managers.

+
+ {% else %} + {{ helper.render_simple_table('Name', 'Service', collector.managers) }} + {% endif %} +
+
+ +
+

Second Level Cache

+
+ + {% if not collector.cacheEnabled %} +
+

Second Level Cache is not enabled.

+
+ {% else %} + {% if not collector.cacheCounts %} +
+

Second level cache information is not available.

+
+ {% else %} +
+
+ {{ collector.cacheCounts.hits }} + Hits +
+ +
+ {{ collector.cacheCounts.misses }} + Misses +
+ +
+ {{ collector.cacheCounts.puts }} + Puts +
+
+ + {% if collector.cacheRegions.hits %} +

Number of cache hits

+ {{ helper.render_simple_table('Region', 'Hits', collector.cacheRegions.hits) }} + {% endif %} + + {% if collector.cacheRegions.misses %} +

Number of cache misses

+ {{ helper.render_simple_table('Region', 'Misses', collector.cacheRegions.misses) }} + {% endif %} + + {% if collector.cacheRegions.puts %} +

Number of cache puts

+ {{ helper.render_simple_table('Region', 'Puts', collector.cacheRegions.puts) }} + {% endif %} + {% endif %} + {% endif %} +
+
+ +
+

Entities Mapping

+
+ + {% if not collector.entities %} +
+

No mapped entities.

+
+ {% else %} + {% for manager, classes in collector.entities %} + {% if collector.managers|length > 1 %} +

{{ manager }} entity manager

+ {% endif %} + + {% if classes is empty %} +
+

No loaded entities.

+
+ {% else %} + + + + + + + + + {% for class in classes %} + {% set contains_errors = collector.mappingErrors[manager] is defined and collector.mappingErrors[manager][class.class] is defined %} + + + + + {% endfor %} + +
ClassMapping errors
+ {{ class. class}} + + {% if contains_errors %} +
    + {% for error in collector.mappingErrors[manager][class.class] %} +
  • {{ error }}
  • + {% endfor %} +
+ {% else %} + No errors. + {% endif %} +
+ {% endif %} + {% endfor %} + {% endif %} +
+
+
+ + +{% endblock %} + +{% macro render_simple_table(label1, label2, data) %} + + + + + + + + + {% for key, value in data %} + + + + + {% endfor %} + +
{{ label1 }}{{ label2 }}
{{ key }}{{ value }}
+{% endmacro %} diff --git a/vendor/doctrine/doctrine-bundle/templates/Collector/explain.html.twig b/vendor/doctrine/doctrine-bundle/templates/Collector/explain.html.twig new file mode 100644 index 0000000..3b2dd7f --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/templates/Collector/explain.html.twig @@ -0,0 +1,28 @@ +{% if data[0]|length > 1 %} + {# The platform returns a table for the explanation (e.g. MySQL), display all columns #} + + + + {% for label in data[0]|keys %} + + {% endfor %} + + + + {% for row in data %} + + {% for key, item in row %} + + {% endfor %} + + {% endfor %} + +
{{ label }}
{{ item|replace({',': ', '}) }}
+{% else %} + {# The Platform returns a single column for a textual explanation (e.g. PostgreSQL), display all lines #} +
+        {%- for row in data -%}
+            {{ row|first }}{{ "\n" }}
+        {%- endfor -%}
+    
+{% endif %} diff --git a/vendor/doctrine/doctrine-bundle/templates/Collector/icon.svg b/vendor/doctrine/doctrine-bundle/templates/Collector/icon.svg new file mode 100644 index 0000000..93998db --- /dev/null +++ b/vendor/doctrine/doctrine-bundle/templates/Collector/icon.svg @@ -0,0 +1,4 @@ + + + diff --git a/vendor/doctrine/doctrine-migrations-bundle/.symfony.bundle.yaml b/vendor/doctrine/doctrine-migrations-bundle/.symfony.bundle.yaml new file mode 100644 index 0000000..86ee4d1 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/.symfony.bundle.yaml @@ -0,0 +1,4 @@ +branches: ["2.2.x", "3.0.x", "3.1.x", "3.2.x", "3.3.x", "3.4.x"] +maintained_branches: ["2.2.x", "3.3.x", "3.4.x"] +doc_dir: {"2.2.x": "Resources/doc/", "3.3.x": "Resources/doc/", "3.4.x": "docs/"} +dev_branch: "3.4.x" diff --git a/vendor/doctrine/doctrine-migrations-bundle/Collector/MigrationsCollector.php b/vendor/doctrine/doctrine-migrations-bundle/Collector/MigrationsCollector.php new file mode 100644 index 0000000..a7ad389 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Collector/MigrationsCollector.php @@ -0,0 +1,95 @@ +dependencyFactory = $dependencyFactory; + $this->flattener = $migrationsFlattener; + } + + /** @return void */ + public function collect(Request $request, Response $response, ?Throwable $exception = null) + { + if (! empty($this->data)) { + return; + } + + $metadataStorage = $this->dependencyFactory->getMetadataStorage(); + $planCalculator = $this->dependencyFactory->getMigrationPlanCalculator(); + + try { + $executedMigrations = $metadataStorage->getExecutedMigrations(); + } catch (Exception $dbalException) { + $this->dependencyFactory->getLogger()->error( + 'error while trying to collect executed migrations', + ['exception' => $dbalException] + ); + + return; + } + + $availableMigrations = $planCalculator->getMigrations(); + + $this->data['available_migrations_count'] = count($availableMigrations); + $unavailableMigrations = $executedMigrations->unavailableSubset($availableMigrations); + $this->data['unavailable_migrations_count'] = count($unavailableMigrations); + + $newMigrations = $availableMigrations->newSubset($executedMigrations); + $this->data['new_migrations'] = $this->flattener->flattenAvailableMigrations($newMigrations); + $this->data['executed_migrations'] = $this->flattener->flattenExecutedMigrations($executedMigrations, $availableMigrations); + + $this->data['storage'] = get_class($metadataStorage); + $configuration = $this->dependencyFactory->getConfiguration(); + $storage = $configuration->getMetadataStorageConfiguration(); + if ($storage instanceof TableMetadataStorageConfiguration) { + $this->data['table'] = $storage->getTableName(); + $this->data['column'] = $storage->getVersionColumnName(); + } + + $connection = $this->dependencyFactory->getConnection(); + $this->data['driver'] = get_class($connection->getDriver()); + $this->data['name'] = $connection->getDatabase(); + + $this->data['namespaces'] = $configuration->getMigrationDirectories(); + } + + /** @return string */ + public function getName() + { + return 'doctrine_migrations'; + } + + /** @return array|Data */ + public function getData() + { + return $this->data; + } + + /** @return void */ + public function reset() + { + $this->data = []; + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/Collector/MigrationsFlattener.php b/vendor/doctrine/doctrine-migrations-bundle/Collector/MigrationsFlattener.php new file mode 100644 index 0000000..f97b580 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Collector/MigrationsFlattener.php @@ -0,0 +1,73 @@ + (string) $migration->getVersion(), + 'is_new' => true, + 'is_unavailable' => false, + 'description' => $migration->getMigration()->getDescription(), + 'executed_at' => null, + 'execution_time' => null, + 'file' => (new ReflectionClass($migration->getMigration()))->getFileName(), + ]; + }, $migrationsList->getItems()); + } + + /** + * @return array{ + * version: string, + * is_new: false, + * is_unavailable: bool, + * description: string|null, + * executed_at: DateTimeImmutable|null, + * execution_time: float|null, + * file: string|false|null, + * }[] + */ + public function flattenExecutedMigrations(ExecutedMigrationsList $migrationsList, AvailableMigrationsList $availableMigrations): array + { + return array_map(static function (ExecutedMigration $migration) use ($availableMigrations) { + $availableMigration = $availableMigrations->hasMigration($migration->getVersion()) + ? $availableMigrations->getMigration($migration->getVersion())->getMigration() + : null; + + return [ + 'version' => (string) $migration->getVersion(), + 'is_new' => false, + 'is_unavailable' => ! $availableMigration, + 'description' => $availableMigration ? $availableMigration->getDescription() : null, + 'executed_at' => $migration->getExecutedAt(), + 'execution_time' => $migration->getExecutionTime(), + 'file' => $availableMigration ? (new ReflectionClass($availableMigration))->getFileName() : null, + ]; + }, $migrationsList->getItems()); + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/CompilerPass/ConfigureDependencyFactoryPass.php b/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/CompilerPass/ConfigureDependencyFactoryPass.php new file mode 100644 index 0000000..5dcdc6b --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/CompilerPass/ConfigureDependencyFactoryPass.php @@ -0,0 +1,115 @@ +has('doctrine')) { + throw new RuntimeException('DoctrineMigrationsBundle requires DoctrineBundle to be enabled.'); + } + + $diDefinition = $container->getDefinition('doctrine.migrations.dependency_factory'); + + $preferredConnection = $container->getParameter('doctrine.migrations.preferred_connection'); + assert(is_string($preferredConnection) || $preferredConnection === null); + // explicitly use configured connection + if ($preferredConnection !== null) { + $this->validatePreferredConnection($container, $preferredConnection); + + $loaderDefinition = $container->getDefinition('doctrine.migrations.connection_registry_loader'); + $loaderDefinition->setArgument(1, $preferredConnection); + + $diDefinition->setFactory([DependencyFactory::class, 'fromConnection']); + $diDefinition->setArgument(1, new Reference('doctrine.migrations.connection_registry_loader')); + + return; + } + + $preferredEm = $container->getParameter('doctrine.migrations.preferred_em'); + assert(is_string($preferredEm) || $preferredEm === null); + // explicitly use configured entity manager + if ($preferredEm !== null) { + $this->validatePreferredEm($container, $preferredEm); + + $loaderDefinition = $container->getDefinition('doctrine.migrations.entity_manager_registry_loader'); + $loaderDefinition->setArgument(1, $preferredEm); + + $diDefinition->setFactory([DependencyFactory::class, 'fromEntityManager']); + $diDefinition->setArgument(1, new Reference('doctrine.migrations.entity_manager_registry_loader')); + + return; + } + + // try to use any/default entity manager + if ( + $container->hasParameter('doctrine.entity_managers') + && is_array($container->getParameter('doctrine.entity_managers')) + && count($container->getParameter('doctrine.entity_managers')) > 0 + ) { + $diDefinition->setFactory([DependencyFactory::class, 'fromEntityManager']); + $diDefinition->setArgument(1, new Reference('doctrine.migrations.entity_manager_registry_loader')); + + return; + } + + // fallback on any/default connection + $diDefinition->setFactory([DependencyFactory::class, 'fromConnection']); + $diDefinition->setArgument(1, new Reference('doctrine.migrations.connection_registry_loader')); + } + + private function validatePreferredConnection(ContainerBuilder $container, string $preferredConnection): void + { + /** @var array $allowedConnections */ + $allowedConnections = $container->getParameter('doctrine.connections'); + if (! isset($allowedConnections[$preferredConnection])) { + throw new InvalidArgumentException(sprintf( + 'The "%s" connection is not defined. Did you mean one of the following: %s', + $preferredConnection, + implode(', ', array_keys($allowedConnections)) + )); + } + } + + private function validatePreferredEm(ContainerBuilder $container, string $preferredEm): void + { + if ( + ! $container->hasParameter('doctrine.entity_managers') + || ! is_array($container->getParameter('doctrine.entity_managers')) + || count($container->getParameter('doctrine.entity_managers')) === 0 + ) { + throw new InvalidArgumentException(sprintf( + 'The "%s" entity manager is not defined. It seems that you do not have configured any entity manager in the DoctrineBundle.', + $preferredEm + )); + } + + /** @var array $allowedEms */ + $allowedEms = $container->getParameter('doctrine.entity_managers'); + if (! isset($allowedEms[$preferredEm])) { + throw new InvalidArgumentException(sprintf( + 'The "%s" entity manager is not defined. Did you mean one of the following: %s', + $preferredEm, + implode(', ', array_keys($allowedEms)) + )); + } + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/Configuration.php b/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000..2c69caa --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/Configuration.php @@ -0,0 +1,180 @@ +getRootNode(); + + $organizeMigrationModes = $this->getOrganizeMigrationsModes(); + + $rootNode + ->fixXmlConfig('migration', 'migrations') + ->fixXmlConfig('migrations_path', 'migrations_paths') + ->children() + ->arrayNode('migrations_paths') + ->info('A list of namespace/path pairs where to look for migrations.') + ->defaultValue([]) + ->useAttributeAsKey('namespace') + ->prototype('scalar')->end() + ->end() + + ->arrayNode('services') + ->info('A set of services to pass to the underlying doctrine/migrations library, allowing to change its behaviour.') + ->useAttributeAsKey('service') + ->defaultValue([]) + ->validate() + ->ifTrue(static function (array $v): bool { + return count(array_filter(array_keys($v), static function (string $doctrineService): bool { + return strpos($doctrineService, 'Doctrine\Migrations\\') !== 0; + })) !== 0; + }) + ->thenInvalid('Valid services for the DoctrineMigrationsBundle must be in the "Doctrine\Migrations" namespace.') + ->end() + ->prototype('scalar')->end() + ->end() + + ->arrayNode('factories') + ->info('A set of callables to pass to the underlying doctrine/migrations library as services, allowing to change its behaviour.') + ->useAttributeAsKey('factory') + ->defaultValue([]) + ->validate() + ->ifTrue(static function (array $v): bool { + return count(array_filter(array_keys($v), static function (string $doctrineService): bool { + return strpos($doctrineService, 'Doctrine\Migrations\\') !== 0; + })) !== 0; + }) + ->thenInvalid('Valid callables for the DoctrineMigrationsBundle must be in the "Doctrine\Migrations" namespace.') + ->end() + ->prototype('scalar')->end() + ->end() + + ->arrayNode('storage') + ->addDefaultsIfNotSet() + ->info('Storage to use for migration status metadata.') + ->children() + ->arrayNode('table_storage') + ->addDefaultsIfNotSet() + ->info('The default metadata storage, implemented as a table in the database.') + ->children() + ->scalarNode('table_name')->defaultValue(null)->cannotBeEmpty()->end() + ->scalarNode('version_column_name')->defaultValue(null)->end() + ->scalarNode('version_column_length')->defaultValue(null)->end() + ->scalarNode('executed_at_column_name')->defaultValue(null)->end() + ->scalarNode('execution_time_column_name')->defaultValue(null)->end() + ->end() + ->end() + ->end() + ->end() + + ->arrayNode('migrations') + ->info('A list of migrations to load in addition to the one discovered via "migrations_paths".') + ->prototype('scalar')->end() + ->defaultValue([]) + ->end() + ->scalarNode('connection') + ->info('Connection name to use for the migrations database.') + ->defaultValue(null) + ->end() + ->scalarNode('em') + ->info('Entity manager name to use for the migrations database (available when doctrine/orm is installed).') + ->defaultValue(null) + ->end() + ->scalarNode('all_or_nothing') + ->info('Run all migrations in a transaction.') + ->defaultValue(false) + ->end() + ->scalarNode('check_database_platform') + ->info('Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on.') + ->defaultValue(true) + ->end() + ->scalarNode('custom_template') + ->info('Custom template path for generated migration classes.') + ->defaultValue(null) + ->end() + ->scalarNode('organize_migrations') + ->defaultValue(false) + ->info('Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false') + ->validate() + ->ifTrue(static function ($v) use ($organizeMigrationModes): bool { + if ($v === false) { + return false; + } + + return ! is_string($v) || ! in_array(strtoupper($v), $organizeMigrationModes, true); + }) + ->thenInvalid('Invalid organize migrations mode value %s') + ->end() + ->validate() + ->ifString() + ->then(static function ($v) { + return constant('Doctrine\Migrations\Configuration\Configuration::VERSIONS_ORGANIZATION_' . strtoupper($v)); + }) + ->end() + ->end() + ->booleanNode('enable_profiler') + ->info('Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead.') + ->defaultFalse() + ->end() + ->booleanNode('transactional') + ->info('Whether or not to wrap migrations in a single transaction.') + ->defaultTrue() + ->end() + ->end(); + + return $treeBuilder; + } + + /** + * Find organize migrations modes for their names + * + * @return string[] + */ + private function getOrganizeMigrationsModes(): array + { + $constPrefix = 'VERSIONS_ORGANIZATION_'; + $prefixLen = strlen($constPrefix); + $refClass = new ReflectionClass('Doctrine\Migrations\Configuration\Configuration'); + $constsArray = array_keys($refClass->getConstants()); + $namesArray = []; + + foreach ($constsArray as $constant) { + if (strpos($constant, $constPrefix) !== 0) { + continue; + } + + $namesArray[] = substr($constant, $prefixLen); + } + + return $namesArray; + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/DoctrineMigrationsExtension.php b/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/DoctrineMigrationsExtension.php new file mode 100644 index 0000000..647d1a2 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/DependencyInjection/DoctrineMigrationsExtension.php @@ -0,0 +1,201 @@ +processConfiguration($configuration, $configs); + + $locator = new FileLocator(__DIR__ . '/../Resources/config/'); + $loader = new XmlFileLoader($container, $locator); + + $loader->load('services.xml'); + + $configurationDefinition = $container->getDefinition('doctrine.migrations.configuration'); + + foreach ($config['migrations_paths'] as $ns => $path) { + $path = $this->checkIfBundleRelativePath($path, $container); + $configurationDefinition->addMethodCall('addMigrationsDirectory', [$ns, $path]); + } + + foreach ($config['migrations'] as $migrationClass) { + $configurationDefinition->addMethodCall('addMigrationClass', [$migrationClass]); + } + + if ($config['organize_migrations'] !== false) { + $configurationDefinition->addMethodCall('setMigrationOrganization', [$config['organize_migrations']]); + } + + if ($config['custom_template'] !== null) { + $configurationDefinition->addMethodCall('setCustomTemplate', [$config['custom_template']]); + } + + $configurationDefinition->addMethodCall('setAllOrNothing', [$config['all_or_nothing']]); + $configurationDefinition->addMethodCall('setCheckDatabasePlatform', [$config['check_database_platform']]); + + if ($config['enable_profiler']) { + $this->registerCollector($container); + } + + $configurationDefinition->addMethodCall('setTransactional', [$config['transactional']]); + + $diDefinition = $container->getDefinition('doctrine.migrations.dependency_factory'); + + if (! isset($config['services'][MigrationFactory::class])) { + $config['services'][MigrationFactory::class] = 'doctrine.migrations.migrations_factory'; + } + + foreach ($config['services'] as $doctrineId => $symfonyId) { + $diDefinition->addMethodCall('setDefinition', [$doctrineId, new ServiceClosureArgument(new Reference($symfonyId))]); + } + + foreach ($config['factories'] as $doctrineId => $symfonyId) { + $diDefinition->addMethodCall('setDefinition', [$doctrineId, new Reference($symfonyId)]); + } + + if (! isset($config['services'][MetadataStorage::class])) { + $storageConfiguration = $config['storage']['table_storage']; + + $storageDefinition = new Definition(TableMetadataStorageConfiguration::class); + $container->setDefinition('doctrine.migrations.storage.table_storage', $storageDefinition); + $container->setAlias('doctrine.migrations.metadata_storage', 'doctrine.migrations.storage.table_storage'); + + if ($storageConfiguration['table_name'] !== null) { + $storageDefinition->addMethodCall('setTableName', [$storageConfiguration['table_name']]); + } + + if ($storageConfiguration['version_column_name'] !== null) { + $storageDefinition->addMethodCall('setVersionColumnName', [$storageConfiguration['version_column_name']]); + } + + if ($storageConfiguration['version_column_length'] !== null) { + $storageDefinition->addMethodCall('setVersionColumnLength', [$storageConfiguration['version_column_length']]); + } + + if ($storageConfiguration['executed_at_column_name'] !== null) { + $storageDefinition->addMethodCall('setExecutedAtColumnName', [$storageConfiguration['executed_at_column_name']]); + } + + if ($storageConfiguration['execution_time_column_name'] !== null) { + $storageDefinition->addMethodCall('setExecutionTimeColumnName', [$storageConfiguration['execution_time_column_name']]); + } + + $configurationDefinition->addMethodCall('setMetadataStorageConfiguration', [new Reference('doctrine.migrations.storage.table_storage')]); + } + + if ($config['em'] !== null && $config['connection'] !== null) { + throw new InvalidArgumentException( + 'You cannot specify both "connection" and "em" in the DoctrineMigrationsBundle configurations.' + ); + } + + $container->setParameter('doctrine.migrations.preferred_em', $config['em']); + $container->setParameter('doctrine.migrations.preferred_connection', $config['connection']); + + if (interface_exists(ContainerAwareInterface::class)) { + return; + } + + $container->removeDefinition('doctrine.migrations.container_aware_migrations_factory'); + } + + private function checkIfBundleRelativePath(string $path, ContainerBuilder $container): string + { + if (isset($path[0]) && $path[0] === '@') { + $pathParts = explode('/', $path); + $bundleName = substr($pathParts[0], 1); + + $bundlePath = $this->getBundlePath($bundleName, $container); + + return $bundlePath . substr($path, strlen('@' . $bundleName)); + } + + return $path; + } + + private function getBundlePath(string $bundleName, ContainerBuilder $container): string + { + $bundleMetadata = $container->getParameter('kernel.bundles_metadata'); + assert(is_array($bundleMetadata)); + + if (! isset($bundleMetadata[$bundleName])) { + throw new RuntimeException(sprintf( + 'The bundle "%s" has not been registered, available bundles: %s', + $bundleName, + implode(', ', array_keys($bundleMetadata)) + )); + } + + return $bundleMetadata[$bundleName]['path']; + } + + private function registerCollector(ContainerBuilder $container): void + { + $flattenerDefinition = new Definition(MigrationsFlattener::class); + $container->setDefinition('doctrine_migrations.migrations_flattener', $flattenerDefinition); + + $collectorDefinition = new Definition(MigrationsCollector::class, [ + new Reference('doctrine.migrations.dependency_factory'), + new Reference('doctrine_migrations.migrations_flattener'), + ]); + $collectorDefinition + ->addTag('data_collector', [ + 'template' => '@DoctrineMigrations/Collector/migrations.html.twig', + 'id' => 'doctrine_migrations', + 'priority' => '249', + ]); + $container->setDefinition('doctrine_migrations.migrations_collector', $collectorDefinition); + } + + /** + * Returns the base path for the XSD files. + * + * @return string The XSD base path + */ + public function getXsdValidationBasePath(): string + { + return __DIR__ . '/../Resources/config/schema'; + } + + public function getNamespace(): string + { + return 'http://symfony.com/schema/dic/doctrine/migrations/3.0'; + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/DoctrineMigrationsBundle.php b/vendor/doctrine/doctrine-migrations-bundle/DoctrineMigrationsBundle.php new file mode 100644 index 0000000..403c7d1 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/DoctrineMigrationsBundle.php @@ -0,0 +1,21 @@ +addCompilerPass(new ConfigureDependencyFactoryPass()); + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/LICENSE b/vendor/doctrine/doctrine-migrations-bundle/LICENSE new file mode 100644 index 0000000..aa7a99a --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +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. diff --git a/vendor/doctrine/doctrine-migrations-bundle/MigrationsFactory/ContainerAwareMigrationFactory.php b/vendor/doctrine/doctrine-migrations-bundle/MigrationsFactory/ContainerAwareMigrationFactory.php new file mode 100644 index 0000000..9513111 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/MigrationsFactory/ContainerAwareMigrationFactory.php @@ -0,0 +1,41 @@ += 7 */ +class ContainerAwareMigrationFactory implements MigrationFactory +{ + /** @var ContainerInterface */ + private $container; + + /** @var MigrationFactory */ + private $migrationFactory; + + public function __construct(MigrationFactory $migrationFactory, ContainerInterface $container) + { + $this->container = $container; + $this->migrationFactory = $migrationFactory; + } + + public function createVersion(string $migrationClassName): AbstractMigration + { + $migration = $this->migrationFactory->createVersion($migrationClassName); + + if ($migration instanceof ContainerAwareInterface) { + trigger_deprecation('doctrine/doctrine-migrations-bundle', '3.3', 'Migration "%s" implements "%s" to gain access to the application\'s service container. This method is deprecated and won\'t work with Symfony 7.', $migrationClassName, ContainerAwareInterface::class); + + $migration->setContainer($this->container); + } + + return $migration; + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/README.markdown b/vendor/doctrine/doctrine-migrations-bundle/README.markdown new file mode 100644 index 0000000..bd0817a --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/README.markdown @@ -0,0 +1,9 @@ +DoctrineMigrationsBundle +======================== + +This bundle integrates the [Doctrine Migrations library](http://www.doctrine-project.org/projects/migrations.html) +into Symfony applications. Database migrations help you version the changes in +your database schema and apply them in a predictable way on every server running +the application. + +[Read the documentation of this bundle](https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html). diff --git a/vendor/doctrine/doctrine-migrations-bundle/Resources/config/schema/doctrine_migrations-3.0.xsd b/vendor/doctrine/doctrine-migrations-bundle/Resources/config/schema/doctrine_migrations-3.0.xsd new file mode 100644 index 0000000..0c5990c --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Resources/config/schema/doctrine_migrations-3.0.xsd @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/doctrine-migrations-bundle/Resources/config/services.xml b/vendor/doctrine/doctrine-migrations-bundle/Resources/config/services.xml new file mode 100644 index 0000000..cb97871 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Resources/config/services.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doctrine:migrations:diff + + + + + + + + doctrine:migrations:sync-metadata-storage + + + + + + + + doctrine:migrations:versions + + + + + + + + doctrine:migrations:current + + + + + + + + doctrine:migrations:dump-schema + + + + + + + doctrine:migrations:execute + + + + + + + doctrine:migrations:generate + + + + + + + doctrine:migrations:latest + + + + + + + doctrine:migrations:migrate + + + + + + + doctrine:migrations:rollup + + + + + + + doctrine:migrations:status + + + + + + + doctrine:migrations:up-to-date + + + + + + + doctrine:migrations:version + + + + + + + diff --git a/vendor/doctrine/doctrine-migrations-bundle/Resources/doc/index.rst b/vendor/doctrine/doctrine-migrations-bundle/Resources/doc/index.rst new file mode 100644 index 0000000..e2fb02b --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Resources/doc/index.rst @@ -0,0 +1,467 @@ +DoctrineMigrationsBundle +======================== + +Database migrations are a way to safely update your database schema both locally +and on production. Instead of running the ``doctrine:schema:update`` command or +applying the database changes manually with SQL statements, migrations allow to +replicate the changes in your database schema in a safe manner. + +Migrations are available in Symfony applications via the `DoctrineMigrationsBundle`_, +which uses the external `Doctrine Database Migrations`_ library. Read the +`documentation`_ of that library if you need a general introduction about migrations. + +Installation +------------ + +Run this command in your terminal: + +.. code-block:: terminal + + $ composer require doctrine/doctrine-migrations-bundle "^3.0" + +If you don't use `Symfony Flex`_, you must enable the bundle manually in the application: + +.. code-block:: php + + // config/bundles.php + // in older Symfony apps, enable the bundle in app/AppKernel.php + return [ + // ... + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], + ]; + +Configuration +------------- + +If you use Symfony Flex, the ``doctrine_migrations.yaml`` config file is created +automatically. Otherwise, create the following file and configure it for your +application: + +.. code-block:: yaml + + # config/packages/doctrine_migrations.yaml + + doctrine_migrations: + # List of namespace/path pairs to search for migrations, at least one required + migrations_paths: + 'App\Migrations': '%kernel.project_dir%/src/App' + 'AnotherApp\Migrations': '/path/to/other/migrations' + 'SomeBundle\Migrations': '@SomeBundle/Migrations' + + # List of additional migration classes to be loaded, optional + migrations: + - 'App\Migrations\Version123' + - 'App\Migrations\Version123' + + # Connection to use for the migrations + connection: default + + # Entity manager to use for migrations. This overrides the "connection" setting. + em: default + + storage: + # Default (SQL table) metadata storage configuration + table_storage: + table_name: 'doctrine_migration_versions' + version_column_name: 'version' + version_column_length: 192 + executed_at_column_name: 'executed_at' + + # Possible values: "BY_YEAR", "BY_YEAR_AND_MONTH", false + organize_migrations: false + + # Path to your custom migrations template + custom_template: ~ + + # Run all migrations in a transaction. + all_or_nothing: false + + # Adds an extra check in the generated migrations to ensure that is executed on the same database type. + check_database_platform: true + + # Whether or not to wrap migrations in a single transaction. + transactional: true + + # Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead. + # enable_profiler: false + + services: + # Custom migration sorting service id + 'Doctrine\Migrations\Version\Comparator': ~ + + # Custom migration classes factory + 'Doctrine\Migrations\Version\MigrationFactory': ~ + + factories: + # Custom migration sorting service id via callables (MyCallableFactory must be a callable) + 'Doctrine\Migrations\Version\Comparator': 'MyCallableFactory' + +- The ``services`` node allows you to provide custom services to the underlying ``DependencyFactory`` part of ``doctrine/migrations``. +- The node ``factories`` is similar to ``services``, with the difference that it accepts only callables. + +The provided callable must return the service to be passed to the ``DependencyFactory``. +The callable will receive as first argument the ``DependencyFactory`` itself, +allowing you to fetch other dependencies from the factory while instantiating your custom dependencies. + +Usage +----- + +All of the migrations functionality is contained in a few console commands: + +.. code-block:: terminal + + doctrine + doctrine:migrations:current [current] Outputs the current version. + doctrine:migrations:diff [diff] Generate a migration by comparing your current database to your mapping information. + doctrine:migrations:dump-schema [dump-schema] Dump the schema for your database to a migration. + doctrine:migrations:execute [execute] Execute a single migration version up or down manually. + doctrine:migrations:generate [generate] Generate a blank migration class. + doctrine:migrations:latest [latest] Outputs the latest version number + doctrine:migrations:migrate [migrate] Execute a migration to a specified version or the latest available version. + doctrine:migrations:rollup [rollup] Roll migrations up by deleting all tracked versions and inserting the one version that exists. + doctrine:migrations:status [status] View the status of a set of migrations. + doctrine:migrations:up-to-date [up-to-date] Tells you if your schema is up-to-date. + doctrine:migrations:version [version] Manually add and delete migration versions from the version table. + doctrine:migrations:sync-metadata-storage [sync-metadata-storage] Ensures that the metadata storage is at the latest version. + doctrine:migrations:list [list-migrations] Display a list of all available migrations and their status. + +Start by getting the status of migrations in your application by running +the ``status`` command: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:status + +This command will show you generic information about the migration status, such as how many migrations have been +already executed, which still need to run, and the database in use. + +Now, you can start working with migrations by generating a new blank migration +class. Later, you'll learn how Doctrine can generate migrations automatically +for you. + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:generate + +Have a look at the newly generated migration class and you will see something +like the following: + +.. code-block:: php + + declare(strict_types=1); + + namespace DoctrineMigrations; + + use Doctrine\DBAL\Schema\Schema; + use Doctrine\Migrations\AbstractMigration; + + /** + * Auto-generated Migration: Please modify to your needs! + */ + final class Version20180605025653 extends AbstractMigration + { + public function getDescription() : string + { + return ''; + } + + public function up(Schema $schema) : void + { + // this up() migration is auto-generated, please modify it to your needs + + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + + } + } + +If you run the ``status`` command again it will now show that you have one new +migration to execute: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:status + +Now you can add some migration code to the ``up()`` and ``down()`` methods and +finally migrate when you're ready: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:migrate 'DoctrineMigrations\Version20180605025653' + +For more information on how to write the migrations themselves (i.e. how to +fill in the ``up()`` and ``down()`` methods), see the official Doctrine Migrations +`documentation`_. + +Running Migrations during Deployment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Of course, the end goal of writing migrations is to be able to use them to +reliably update your database structure when you deploy your application. +By running the migrations locally (or on a beta server), you can ensure that +the migrations work as you expect. + +When you do finally deploy your application, you just need to remember to run +the ``doctrine:migrations:migrate`` command. Internally, Doctrine creates +a ``migration_versions`` table inside your database and tracks which migrations +have been executed there. So, no matter how many migrations you've created +and executed locally, when you run the command during deployment, Doctrine +will know exactly which migrations it hasn't run yet by looking at the ``migration_versions`` +table of your production database. Regardless of what server you're on, you +can always safely run this command to execute only the migrations that haven't +been run yet on *that* particular database. + +Skipping Migrations +~~~~~~~~~~~~~~~~~~~ + +You can skip single migrations by explicitly adding them to the ``migration_versions`` table: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:version 'App\Migrations\Version123' --add + +.. tip:: + + Pay attention to the single quotes (``'``) used in the command above, without them + or with the double quotes (``"``) the command will not work properly. + + +Doctrine will then assume that this migration has already been run and will ignore it. + +Migration Dependencies +---------------------- + +Migrations can have dependencies on external services (such as geolocation, mailer, data processing services...) that +can be used to have more powerful migrations. Those dependencies are not automatically injected into your migrations +but need to be injected using custom migrations factories. + +Here is an example on how to inject the service container into your migrations: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/doctrine_migrations.yaml + doctrine_migrations: + services: + 'Doctrine\Migrations\Version\MigrationFactory': 'App\Migrations\Factory\MigrationFactoryDecorator' + + # config/services.yaml + services: + App\Migrations\Factory\MigrationFactoryDecorator: + decorates: 'doctrine.migrations.migrations_factory' + arguments: ['@.inner', '@service_container'] + + +.. code-block:: php + + declare(strict_types=1); + + namespace App\Migrations\Factory; + + use Doctrine\Migrations\AbstractMigration; + use Doctrine\Migrations\Version\MigrationFactory; + use Symfony\Component\DependencyInjection\ContainerAwareInterface; + use Symfony\Component\DependencyInjection\ContainerInterface; + + class MigrationFactoryDecorator implements MigrationFactory + { + private $migrationFactory; + private $container; + + public function __construct(MigrationFactory $migrationFactory, ContainerInterface $container) + { + $this->migrationFactory = $migrationFactory; + $this->container = $container; + } + + public function createVersion(string $migrationClassName): AbstractMigration + { + $instance = $this->migrationFactory->createVersion($migrationClassName); + + if ($instance instanceof ContainerAwareInterface) { + $instance->setContainer($this->container); + } + + return $instance; + } + } + + +.. tip:: + + If your migration class implements the interface ``Symfony\Component\DependencyInjection\ContainerAwareInterface`` + this bundle will automatically inject the default symfony container into your migration class + (this because the ``MigrationFactoryDecorator`` shown in this example is the default migration factory used by this bundle). + + +Generating Migrations Automatically +----------------------------------- + +In reality, you should rarely need to write migrations manually, as the migrations +library can generate migration classes automatically by comparing your Doctrine +mapping information (i.e. what your database *should* look like) with your +actual current database structure. + +For example, suppose you create a new ``User`` entity and add mapping information +for Doctrine's ORM: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Entity/User.php + namespace App\Entity; + + use Doctrine\ORM\Mapping as ORM; + + /** + * @ORM\Entity + * @ORM\Table(name="hello_user") + */ + class User + { + /** + * @ORM\Id + * @ORM\Column(type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @ORM\Column(type="string", length=255) + */ + private $name; + + .. code-block:: yaml + + # config/doctrine/User.orm.yaml + App\Entity\User: + type: entity + table: user + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + length: 255 + + .. code-block:: xml + + + + + + + + + + + + + +With this information, Doctrine is now ready to help you persist your new +``User`` object to and from the ``user`` table. Of course, this table +doesn't exist yet! Generate a new migration for this table automatically by +running the following command: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:diff + +You should see a message that a new migration class was generated based on +the schema differences. If you open this file, you'll find that it has the +SQL code needed to create the ``user`` table. Next, run the migration +to add the table to your database: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:migrate + +The moral of the story is this: after each change you make to your Doctrine +mapping information, run the ``doctrine:migrations:diff`` command to automatically +generate your migration classes. + +If you do this from the very beginning of your project (i.e. so that even +the first tables were loaded via a migration class), you'll always be able +to create a fresh database and run your migrations in order to get your database +schema fully up to date. In fact, this is an easy and dependable workflow +for your project. + +If you don't want to use this workflow and instead create your schema via +``doctrine:schema:create``, you can tell Doctrine to skip all existing migrations: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:version --add --all + +Otherwise Doctrine will try to run all migrations, which probably will not work. + +Manual Tables +------------- + +It is a common use case, that in addition to your generated database structure +based on your doctrine entities you might need custom tables. By default such +tables will be removed by the ``doctrine:migrations:diff`` command. + +If you follow a specific scheme you can configure doctrine/dbal to ignore those +tables. Let's say all custom tables will be prefixed by ``t_``. In this case you +just have to add the following configuration option to your doctrine configuration: + +.. configuration-block:: + + .. code-block:: yaml + + doctrine: + dbal: + schema_filter: ~^(?!t_)~ + + .. code-block:: xml + + + + + .. code-block:: php + + $container->loadFromExtension('doctrine', array( + 'dbal' => array( + 'schema_filter' => '~^(?!t_)~', + // ... + ), + // ... + )); + +This ignores the tables, and any named objects such as sequences, on the DBAL level and they will be ignored by the diff command. + +Note that if you have multiple connections configured then the ``schema_filter`` configuration +will need to be placed per-connection. + +Troubleshooting out of sync metadata storage issue +-------------------------------------------------- +``doctrine/migrations`` relies on a properly configured Database server version in the connection string to manage the table storing the +migrations, also known as the metadata storage. + +If you encounter the error ``The metadata storage is not up to date, please run the sync-metadata-storage command to fix this issue.`` +when running the command ``doctrine:migrations:migrate`` or the suggested command itself ``doctrine:migrations:sync-metadata-storage`` please +check the database connection string, and make sure that the proper server version is defined. If you are running a MariaDB database, +you should prefix the server version with ``mariadb-`` (ex: ``mariadb-10.2.12``). See the `configuring_database`_ section. + +Example connection string for MariaDB: + +.. code-block:: terminal + DATABASE_URL=mysql://root:@127.0.0.1:3306/testtest?serverVersion=mariadb-10.4.11 + +.. _documentation: https://www.doctrine-project.org/projects/doctrine-migrations/en/current/index.html +.. _configuring_database: https://symfony.com/doc/current/doctrine.html#configuring-the-database +.. _DoctrineMigrationsBundle: https://github.com/doctrine/DoctrineMigrationsBundle +.. _`Doctrine Database Migrations`: https://github.com/doctrine/migrations +.. _`Symfony Flex`: https://symfony.com/doc/current/setup/flex.html diff --git a/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/icon-v3.svg b/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/icon-v3.svg new file mode 100644 index 0000000..0897e4b --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/icon-v3.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/icon.svg b/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/icon.svg new file mode 100644 index 0000000..c291807 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/migrations.html.twig b/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/migrations.html.twig new file mode 100644 index 0000000..a61934e --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/Resources/views/Collector/migrations.html.twig @@ -0,0 +1,252 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% if collector.data.unavailable_migrations_count is defined %} + {% set unavailable_migrations = collector.data.unavailable_migrations_count %} + {% set new_migrations = collector.data.new_migrations|length %} + {% if unavailable_migrations > 0 or new_migrations > 0 %} + {% set executed_migrations = collector.data.executed_migrations|length %} + {% set available_migrations = collector.data.available_migrations_count %} + {% set status_color = unavailable_migrations > 0 ? 'yellow' : '' %} + {% set status_color = new_migrations > 0 ? 'red' : status_color %} + + {% set icon %} + {% if profiler_markup_version < 3 %} + {{ include('@DoctrineMigrations/Collector/icon.svg') }} + {% else %} + {{ include('@DoctrineMigrations/Collector/icon-v3.svg') }} + {% endif %} + + {{ new_migrations + unavailable_migrations }} + {% endset %} + + {% set text %} +
+
+ Current Migration + {{ executed_migrations > 0 ? collector.data.executed_migrations|last.version|split('\\')|last : 'n/a' }} +
+
+ +
+
+ + Database Migrations + +
+
+ Executed + {{ executed_migrations }} +
+
+ Unavailable + {{ unavailable_migrations }} +
+
+ Available + {{ available_migrations }} +
+
+ New + {{ new_migrations }} +
+
+ {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }} + {% endif %} + {% endif %} +{% endblock %} + +{% block menu %} + {% if collector.data.unavailable_migrations_count is defined %} + {% set unavailable_migrations = collector.data.unavailable_migrations_count %} + {% set new_migrations = collector.data.new_migrations|length %} + {% set label = unavailable_migrations > 0 ? 'label-status-warning' : '' %} + {% set label = new_migrations > 0 ? 'label-status-error' : label %} + + + {% if profiler_markup_version < 3 %} + {{ include('@DoctrineMigrations/Collector/icon.svg') }} + {% else %} + {{ include('@DoctrineMigrations/Collector/icon-v3.svg') }} + {% endif %} + + + Migrations + {% if unavailable_migrations > 0 or new_migrations > 0 %} + + {{ new_migrations + unavailable_migrations }} + + {% endif %} + + {% endif %} +{% endblock %} + +{% block panel %} + {% set num_executed_migrations = collector.data.executed_migrations|length %} + {% set num_unavailable_migrations = collector.data.unavailable_migrations_count %} + {% set num_available_migrations = collector.data.available_migrations_count %} + {% set num_new_migrations = collector.data.new_migrations|length %} + +

Doctrine Migrations

+
+
+ {{ num_executed_migrations }} + Executed +
+ + {% if profiler_markup_version >= 3 %} +
+ {% endif %} + +
+ {{ num_unavailable_migrations }} + Unavailable +
+
+ {{ num_available_migrations }} + Available +
+
+ {{ num_new_migrations }} + New +
+ + {% if profiler_markup_version >= 3 %} +
{# closes the
#} + {% endif %} +
+ +
+
+

+ Migrations + + {{ num_new_migrations > 0 ? num_new_migrations : num_unavailable_migrations > 0 ? num_unavailable_migrations : num_executed_migrations }} + +

+ +
+ {{ _self.render_migration_details(collector, profiler_markup_version) }} +
+
+ +
+

Configuration

+ +
+ {{ _self.render_configuration_details(collector, profiler_markup_version) }} +
+
+
+{% endblock %} + +{% macro render_migration_details(collector) %} + + + + + + + + + + + {% for migration in collector.data.new_migrations %} + {{ _self.render_migration(migration) }} + {% endfor %} + + {% for migration in collector.data.executed_migrations|reverse %} + {{ _self.render_migration(migration) }} + {% endfor %} +
VersionDescriptionStatusExecuted atExecution time
+{% endmacro %} + +{% macro render_configuration_details(collector) %} + + + + + + + + + + + {% if collector.data.table is defined %} + + + + + {% endif %} + {% if collector.data.column is defined %} + + + + + {% endif %} +
Storage
Type{{ collector.data.storage }}
Table Name{{ collector.data.table }}
Column Name{{ collector.data.column }}
+ + + + + + + + + + + + + + + +
Database
Driver{{ collector.data.driver }}
Name{{ collector.data.name }}
+ + + + + + + + {% for namespace, directory in collector.data.namespaces %} + + + + + {% endfor %} +
Migration Namespaces
{{ namespace }}{{ directory }}
+{% endmacro %} + +{% macro render_migration(migration, profiler_markup_version) %} + + + {% if migration.file %} + {{ migration.version }} + {% else %} + {{ migration.version }} + {% endif %} + + {{ migration.description }} + + {% if migration.is_new %} + NOT EXECUTED + {% elseif migration.is_unavailable %} + UNAVAILABLE + {% else %} + EXECUTED + {% endif %} + + {{ migration.executed_at ? migration.executed_at|date('M j, Y H:i') : 'n/a' }} + + {% if migration.execution_time is null %} + n/a + {% elseif migration.execution_time < 1 %} + {{ (migration.execution_time * 1000)|number_format(0) }} ms + {% else %} + {{ migration.execution_time|number_format(2) }} seconds + {% endif %} + + +{% endmacro %} diff --git a/vendor/doctrine/doctrine-migrations-bundle/UPGRADE.md b/vendor/doctrine/doctrine-migrations-bundle/UPGRADE.md new file mode 100644 index 0000000..84ffed0 --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/UPGRADE.md @@ -0,0 +1,86 @@ +# Upgrade + +## From 2.x to 3.0.0 + +- The configuration for the migration namespace and directory changed as follows: + +Before + +```yaml +doctrine_migrations: + dir_name: '%kernel.project_dir%/src/Migrations' + namespace: DoctrineMigrations +``` + +After + +```yaml +doctrine_migrations: + migrations_paths: + 'DoctrineMigrations': '%kernel.project_dir%/src/Migrations' +``` + +- The configuration for the metadata table definition changed as follows: + +Before + +```yaml +doctrine_migrations: + table_name: 'migration_versions' + column_name: 'version' + column_length: 14 + executed_at_column_name: 'executed_at' +``` + +After + +```yaml +doctrine_migrations: + storage: + table_storage: + table_name: 'migration_versions' + version_column_name: 'version' + version_column_length: 191 + executed_at_column_name: 'executed_at' +``` +If your project did not originally specify its own table definition configuration, you will need to configure the table name after the upgrade: + +```yaml +doctrine_migrations: + storage: + table_storage: + table_name: 'migration_versions' +``` +and then run the `doctrine:migrations:sync-metadata-storage` command. +- The migration name has been dropped: + +Before + +```yaml +doctrine_migrations: + name: 'Application Migrations' +``` + +After + +The parameter `name` has been dropped. + + +- The default for `table_name` changed from `migration_versions` to `doctrine_migration_versions`. If you did not +specify the `table_name` option, you now need to declare it explicitly to not lose migration data. + +```yaml +doctrine_migrations: + storage: + table_storage: + table_name: 'migration_versions' +``` + +### Underlying doctrine/migrations library + +Upgrading this bundle to `3.0` will also update the `doctrine/migrations` library to the version `3.0`. +Backward incompatible changes in `doctrine/migrations` 3.0 +are documented in the dedicated [UPGRADE](https://github.com/doctrine/migrations/blob/3.0.x/UPGRADE.md) document. + +- The container is not automatically injected anymore when a migration implements `ContainerAwareInterface`. Custom +migration factories should be used to inject additional dependencies into migrations. diff --git a/vendor/doctrine/doctrine-migrations-bundle/composer.json b/vendor/doctrine/doctrine-migrations-bundle/composer.json new file mode 100644 index 0000000..6e1908e --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/composer.json @@ -0,0 +1,60 @@ +{ + "name": "doctrine/doctrine-migrations-bundle", + "type": "symfony-bundle", + "description": "Symfony DoctrineMigrationsBundle", + "keywords": ["DBAL", "Migrations", "Schema"], + "homepage": "https://www.doctrine-project.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2|^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "doctrine/doctrine-bundle": "^2.4", + "doctrine/migrations": "^3.2" + }, + "require-dev": { + "composer/semver": "^3.0", + "phpunit/phpunit": "^8.5|^9.5", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan-symfony": "^1.3", + "doctrine/orm": "^2.6 || ^3", + "doctrine/persistence": "^2.0 || ^3 ", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^3 || ^5", + "symfony/phpunit-bridge": "^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6 || ^7", + "vimeo/psalm": "^4.30 || ^5.15" + }, + "autoload": { + "psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\Tests\\": "Tests" } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/doctrine/doctrine-migrations-bundle/phpstan-baseline.neon b/vendor/doctrine/doctrine-migrations-bundle/phpstan-baseline.neon new file mode 100644 index 0000000..5f0690d --- /dev/null +++ b/vendor/doctrine/doctrine-migrations-bundle/phpstan-baseline.neon @@ -0,0 +1,26 @@ +parameters: + ignoreErrors: + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: Collector/MigrationsCollector.php + + - + message: "#^Only booleans are allowed in a negated boolean, Doctrine\\\\Migrations\\\\AbstractMigration\\|null given\\.$#" + count: 1 + path: Collector/MigrationsFlattener.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, Doctrine\\\\Migrations\\\\AbstractMigration\\|null given\\.$#" + count: 2 + path: Collector/MigrationsFlattener.php + + - + message: "#^Call to method setContainer\\(\\) on an unknown class Symfony\\\\Component\\\\DependencyInjection\\\\ContainerAwareInterface\\.$#" + count: 1 + path: MigrationsFactory/ContainerAwareMigrationFactory.php + + - + message: "#^Class Symfony\\\\Component\\\\DependencyInjection\\\\ContainerAwareInterface not found\\.$#" + count: 2 + path: MigrationsFactory/ContainerAwareMigrationFactory.php diff --git a/vendor/doctrine/event-manager/LICENSE b/vendor/doctrine/event-manager/LICENSE new file mode 100644 index 0000000..8c38cc1 --- /dev/null +++ b/vendor/doctrine/event-manager/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +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. diff --git a/vendor/doctrine/event-manager/README.md b/vendor/doctrine/event-manager/README.md new file mode 100644 index 0000000..5b36f54 --- /dev/null +++ b/vendor/doctrine/event-manager/README.md @@ -0,0 +1,13 @@ +# Doctrine Event Manager + +[![Build Status](https://github.com/doctrine/event-manager/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/event-manager/actions) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/quality-score.png?b=1.2.x)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x) +[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/coverage.png?b=1.2.x)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x) + +The Doctrine Event Manager is a library that provides a simple event system. + +## More resources: + +* [Website](https://www.doctrine-project.org/) +* [Documentation](https://www.doctrine-project.org/projects/doctrine-event-manager/en/latest/) +* [Downloads](https://github.com/doctrine/event-manager/releases) diff --git a/vendor/doctrine/event-manager/UPGRADE.md b/vendor/doctrine/event-manager/UPGRADE.md new file mode 100644 index 0000000..25154ef --- /dev/null +++ b/vendor/doctrine/event-manager/UPGRADE.md @@ -0,0 +1,15 @@ +# Upgrade to 2.0 + +## Made the `$event` parameter of `EventManager::getListeners()` mandatory + +When calling `EventManager::getListeners()` you need to specify the event that +you want to fetch the listeners for. Call `getAllListeners()` instead if you +want to access the listeners of all events. + +# Upgrade to 1.2 + +## Deprecated calling `EventManager::getListeners()` without an event name + +When calling `EventManager::getListeners()` without an event name, all +listeners were returned, keyed by event name. A new method `getAllListeners()` +has been added to provide this functionality. It should be used instead. diff --git a/vendor/doctrine/event-manager/composer.json b/vendor/doctrine/event-manager/composer.json new file mode 100644 index 0000000..4ea788b --- /dev/null +++ b/vendor/doctrine/event-manager/composer.json @@ -0,0 +1,68 @@ +{ + "name": "doctrine/event-manager", + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "license": "MIT", + "type": "library", + "keywords": [ + "events", + "event", + "event dispatcher", + "event manager", + "event system" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\Common\\": "tests" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/vendor/doctrine/event-manager/psalm-baseline.xml b/vendor/doctrine/event-manager/psalm-baseline.xml new file mode 100644 index 0000000..bc48ad8 --- /dev/null +++ b/vendor/doctrine/event-manager/psalm-baseline.xml @@ -0,0 +1,8 @@ + + + + + listeners[$event])]]> + + + diff --git a/vendor/doctrine/event-manager/src/EventArgs.php b/vendor/doctrine/event-manager/src/EventArgs.php new file mode 100644 index 0000000..eea3d8a --- /dev/null +++ b/vendor/doctrine/event-manager/src/EventArgs.php @@ -0,0 +1,37 @@ + => + * + * @var array + */ + private array $listeners = []; + + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of the event is + * the name of the method that is invoked on listeners. + * @param EventArgs|null $eventArgs The event arguments to pass to the event handlers/listeners. + * If not supplied, the single empty EventArgs instance is used. + */ + public function dispatchEvent(string $eventName, EventArgs|null $eventArgs = null): void + { + if (! isset($this->listeners[$eventName])) { + return; + } + + $eventArgs ??= EventArgs::getEmptyInstance(); + + foreach ($this->listeners[$eventName] as $listener) { + $listener->$eventName($eventArgs); + } + } + + /** + * Gets the listeners of a specific event. + * + * @param string $event The name of the event. + * + * @return object[] + */ + public function getListeners(string $event): array + { + return $this->listeners[$event] ?? []; + } + + /** + * Gets all listeners keyed by event name. + * + * @return array The event listeners for the specified event, or all event listeners. + */ + public function getAllListeners(): array + { + return $this->listeners; + } + + /** + * Checks whether an event has any registered listeners. + */ + public function hasListeners(string $event): bool + { + return ! empty($this->listeners[$event]); + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string|string[] $events The event(s) to listen on. + * @param object $listener The listener object. + */ + public function addEventListener(string|array $events, object $listener): void + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Overrides listener if a previous one was associated already + // Prevents duplicate listeners on same event (same instance only) + $this->listeners[$event][$hash] = $listener; + } + } + + /** + * Removes an event listener from the specified events. + * + * @param string|string[] $events + */ + public function removeEventListener(string|array $events, object $listener): void + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + unset($this->listeners[$event][$hash]); + } + } + + /** + * Adds an EventSubscriber. + * + * The subscriber is asked for all the events it is interested in and added + * as a listener for these events. + */ + public function addEventSubscriber(EventSubscriber $subscriber): void + { + $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); + } + + /** + * Removes an EventSubscriber. + * + * The subscriber is asked for all the events it is interested in and removed + * as a listener for these events. + */ + public function removeEventSubscriber(EventSubscriber $subscriber): void + { + $this->removeEventListener($subscriber->getSubscribedEvents(), $subscriber); + } +} diff --git a/vendor/doctrine/event-manager/src/EventSubscriber.php b/vendor/doctrine/event-manager/src/EventSubscriber.php new file mode 100644 index 0000000..89cef55 --- /dev/null +++ b/vendor/doctrine/event-manager/src/EventSubscriber.php @@ -0,0 +1,21 @@ +build(); + +By default it will create an English inflector. If you want to use another language, just pass the language +you want to create an inflector for to the ``createForLanguage()`` method: + +.. code-block:: php + + use Doctrine\Inflector\InflectorFactory; + use Doctrine\Inflector\Language; + + $inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build(); + +The supported languages are as follows: + +- ``Language::ENGLISH`` +- ``Language::FRENCH`` +- ``Language::NORWEGIAN_BOKMAL`` +- ``Language::PORTUGUESE`` +- ``Language::SPANISH`` +- ``Language::TURKISH`` + +If you want to manually construct the inflector instead of using a factory, you can do so like this: + +.. code-block:: php + + use Doctrine\Inflector\CachedWordInflector; + use Doctrine\Inflector\RulesetInflector; + use Doctrine\Inflector\Rules\English; + + $inflector = new Inflector( + new CachedWordInflector(new RulesetInflector( + English\Rules::getSingularRuleset() + )), + new CachedWordInflector(new RulesetInflector( + English\Rules::getPluralRuleset() + )) + ); + +Adding Languages +---------------- + +If you are interested in adding support for your language, take a look at the other languages defined in the +``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy +one of the languages and update the rules for your language. + +Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions. + +Custom Setup +============ + +If you want to setup custom singular and plural rules, you can configure these in the factory: + +.. code-block:: php + + use Doctrine\Inflector\InflectorFactory; + use Doctrine\Inflector\Rules\Pattern; + use Doctrine\Inflector\Rules\Patterns; + use Doctrine\Inflector\Rules\Ruleset; + use Doctrine\Inflector\Rules\Substitution; + use Doctrine\Inflector\Rules\Substitutions; + use Doctrine\Inflector\Rules\Transformation; + use Doctrine\Inflector\Rules\Transformations; + use Doctrine\Inflector\Rules\Word; + + $inflector = InflectorFactory::create() + ->withSingularRules( + new Ruleset( + new Transformations( + new Transformation(new Pattern('/^(bil)er$/i'), '\1'), + new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta') + ), + new Patterns(new Pattern('singulars')), + new Substitutions(new Substitution(new Word('spins'), new Word('spinor'))) + ) + ) + ->withPluralRules( + new Ruleset( + new Transformations( + new Transformation(new Pattern('^(bil)er$'), '\1'), + new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta') + ), + new Patterns(new Pattern('noflect'), new Pattern('abtuse')), + new Substitutions( + new Substitution(new Word('amaze'), new Word('amazable')), + new Substitution(new Word('phone'), new Word('phonezes')) + ) + ) + ) + ->build(); + +No operation inflector +---------------------- + +The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for +pluralization and/or singularization. If will simply return the input as output. + +This is an implementation of the `Null Object design pattern `_. + +.. code-block:: php + + use Doctrine\Inflector\Inflector; + use Doctrine\Inflector\NoopWordInflector; + + $inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector()); + +Tableize +======== + +Converts ``ModelName`` to ``model_name``: + +.. code-block:: php + + echo $inflector->tableize('ModelName'); // model_name + +Classify +======== + +Converts ``model_name`` to ``ModelName``: + +.. code-block:: php + + echo $inflector->classify('model_name'); // ModelName + +Camelize +======== + +This method uses `Classify`_ and then converts the first character to lowercase: + +.. code-block:: php + + echo $inflector->camelize('model_name'); // modelName + +Capitalize +========== + +Takes a string and capitalizes all of the words, like PHP's built-in +``ucwords`` function. This extends that behavior, however, by allowing the +word delimiters to be configured, rather than only separating on +whitespace. + +Here is an example: + +.. code-block:: php + + $string = 'top-o-the-morning to all_of_you!'; + + echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you! + + echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You! + +Pluralize +========= + +Returns a word in plural form. + +.. code-block:: php + + echo $inflector->pluralize('browser'); // browsers + +Singularize +=========== + +Returns a word in singular form. + +.. code-block:: php + + echo $inflector->singularize('browsers'); // browser + +Urlize +====== + +Generate a URL friendly string from a string of text: + +.. code-block:: php + + echo $inflector->urlize('My first blog post'); // my-first-blog-post + +Unaccent +======== + +You can unaccent a string of text using the ``unaccent()`` method: + +.. code-block:: php + + echo $inflector->unaccent('año'); // ano + +Legacy API +========== + +The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0. +Support for languages other than English is available in the 2.0 API only. + +Acknowledgements +================ + +The language rules in this library have been adapted from several different sources, including but not limited to: + +- `Ruby On Rails Inflector `_ +- `ICanBoogie Inflector `_ +- `CakePHP Inflector `_ diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php new file mode 100644 index 0000000..2d52908 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php @@ -0,0 +1,24 @@ +wordInflector = $wordInflector; + } + + public function inflect(string $word): string + { + return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php new file mode 100644 index 0000000..166061d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php @@ -0,0 +1,66 @@ +singularRulesets[] = $this->getSingularRuleset(); + $this->pluralRulesets[] = $this->getPluralRuleset(); + } + + final public function build(): Inflector + { + return new Inflector( + new CachedWordInflector(new RulesetInflector( + ...$this->singularRulesets + )), + new CachedWordInflector(new RulesetInflector( + ...$this->pluralRulesets + )) + ); + } + + final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory + { + if ($reset) { + $this->singularRulesets = []; + } + + if ($singularRules instanceof Ruleset) { + array_unshift($this->singularRulesets, $singularRules); + } + + return $this; + } + + final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory + { + if ($reset) { + $this->pluralRulesets = []; + } + + if ($pluralRules instanceof Ruleset) { + array_unshift($this->pluralRulesets, $pluralRules); + } + + return $this; + } + + abstract protected function getSingularRuleset(): Ruleset; + + abstract protected function getPluralRuleset(): Ruleset; +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php new file mode 100644 index 0000000..610a4cf --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php @@ -0,0 +1,507 @@ + 'A', + 'Ã' => 'A', + 'Â' => 'A', + 'Ã' => 'A', + 'Ä' => 'Ae', + 'Æ' => 'Ae', + 'Ã…' => 'Aa', + 'æ' => 'a', + 'Ç' => 'C', + 'È' => 'E', + 'É' => 'E', + 'Ê' => 'E', + 'Ë' => 'E', + 'ÃŒ' => 'I', + 'Ã' => 'I', + 'ÃŽ' => 'I', + 'Ã' => 'I', + 'Ñ' => 'N', + 'Ã’' => 'O', + 'Ó' => 'O', + 'Ô' => 'O', + 'Õ' => 'O', + 'Ö' => 'Oe', + 'Ù' => 'U', + 'Ú' => 'U', + 'Û' => 'U', + 'Ü' => 'Ue', + 'Ã' => 'Y', + 'ß' => 'ss', + 'à' => 'a', + 'á' => 'a', + 'â' => 'a', + 'ã' => 'a', + 'ä' => 'ae', + 'Ã¥' => 'aa', + 'ç' => 'c', + 'è' => 'e', + 'é' => 'e', + 'ê' => 'e', + 'ë' => 'e', + 'ì' => 'i', + 'í' => 'i', + 'î' => 'i', + 'ï' => 'i', + 'ñ' => 'n', + 'ò' => 'o', + 'ó' => 'o', + 'ô' => 'o', + 'õ' => 'o', + 'ö' => 'oe', + 'ù' => 'u', + 'ú' => 'u', + 'û' => 'u', + 'ü' => 'ue', + 'ý' => 'y', + 'ÿ' => 'y', + 'Ä€' => 'A', + 'Ä' => 'a', + 'Ä‚' => 'A', + 'ă' => 'a', + 'Ä„' => 'A', + 'Ä…' => 'a', + 'Ć' => 'C', + 'ć' => 'c', + 'Ĉ' => 'C', + 'ĉ' => 'c', + 'ÄŠ' => 'C', + 'Ä‹' => 'c', + 'ÄŒ' => 'C', + 'Ä' => 'c', + 'ÄŽ' => 'D', + 'Ä' => 'd', + 'Ä' => 'D', + 'Ä‘' => 'd', + 'Ä’' => 'E', + 'Ä“' => 'e', + 'Ä”' => 'E', + 'Ä•' => 'e', + 'Ä–' => 'E', + 'Ä—' => 'e', + 'Ę' => 'E', + 'Ä™' => 'e', + 'Äš' => 'E', + 'Ä›' => 'e', + 'Äœ' => 'G', + 'Ä' => 'g', + 'Äž' => 'G', + 'ÄŸ' => 'g', + 'Ä ' => 'G', + 'Ä¡' => 'g', + 'Ä¢' => 'G', + 'Ä£' => 'g', + 'Ĥ' => 'H', + 'Ä¥' => 'h', + 'Ħ' => 'H', + 'ħ' => 'h', + 'Ĩ' => 'I', + 'Ä©' => 'i', + 'Ī' => 'I', + 'Ä«' => 'i', + 'Ĭ' => 'I', + 'Ä­' => 'i', + 'Ä®' => 'I', + 'į' => 'i', + 'İ' => 'I', + 'ı' => 'i', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ä´' => 'J', + 'ĵ' => 'j', + 'Ķ' => 'K', + 'Ä·' => 'k', + 'ĸ' => 'k', + 'Ĺ' => 'L', + 'ĺ' => 'l', + 'Ä»' => 'L', + 'ļ' => 'l', + 'Ľ' => 'L', + 'ľ' => 'l', + 'Ä¿' => 'L', + 'Å€' => 'l', + 'Å' => 'L', + 'Å‚' => 'l', + 'Ń' => 'N', + 'Å„' => 'n', + 'Å…' => 'N', + 'ņ' => 'n', + 'Ň' => 'N', + 'ň' => 'n', + 'ʼn' => 'N', + 'ÅŠ' => 'n', + 'Å‹' => 'N', + 'ÅŒ' => 'O', + 'Å' => 'o', + 'ÅŽ' => 'O', + 'Å' => 'o', + 'Å' => 'O', + 'Å‘' => 'o', + 'Å’' => 'OE', + 'Å“' => 'oe', + 'Ø' => 'O', + 'ø' => 'o', + 'Å”' => 'R', + 'Å•' => 'r', + 'Å–' => 'R', + 'Å—' => 'r', + 'Ř' => 'R', + 'Å™' => 'r', + 'Åš' => 'S', + 'Å›' => 's', + 'Åœ' => 'S', + 'Å' => 's', + 'Åž' => 'S', + 'ÅŸ' => 's', + 'Å ' => 'S', + 'Å¡' => 's', + 'Å¢' => 'T', + 'Å£' => 't', + 'Ť' => 'T', + 'Å¥' => 't', + 'Ŧ' => 'T', + 'ŧ' => 't', + 'Ũ' => 'U', + 'Å©' => 'u', + 'Ū' => 'U', + 'Å«' => 'u', + 'Ŭ' => 'U', + 'Å­' => 'u', + 'Å®' => 'U', + 'ů' => 'u', + 'Ű' => 'U', + 'ű' => 'u', + 'Ų' => 'U', + 'ų' => 'u', + 'Å´' => 'W', + 'ŵ' => 'w', + 'Ŷ' => 'Y', + 'Å·' => 'y', + 'Ÿ' => 'Y', + 'Ź' => 'Z', + 'ź' => 'z', + 'Å»' => 'Z', + 'ż' => 'z', + 'Ž' => 'Z', + 'ž' => 'z', + 'Å¿' => 's', + '€' => 'E', + '£' => '', + ]; + + /** @var WordInflector */ + private $singularizer; + + /** @var WordInflector */ + private $pluralizer; + + public function __construct(WordInflector $singularizer, WordInflector $pluralizer) + { + $this->singularizer = $singularizer; + $this->pluralizer = $pluralizer; + } + + /** + * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. + */ + public function tableize(string $word): string + { + $tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word); + + if ($tableized === null) { + throw new RuntimeException(sprintf( + 'preg_replace returned null for value "%s"', + $word + )); + } + + return mb_strtolower($tableized); + } + + /** + * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. + */ + public function classify(string $word): string + { + return str_replace([' ', '_', '-'], '', ucwords($word, ' _-')); + } + + /** + * Camelizes a word. This uses the classify() method and turns the first character to lowercase. + */ + public function camelize(string $word): string + { + return lcfirst($this->classify($word)); + } + + /** + * Uppercases words with configurable delimiters between words. + * + * Takes a string and capitalizes all of the words, like PHP's built-in + * ucwords function. This extends that behavior, however, by allowing the + * word delimiters to be configured, rather than only separating on + * whitespace. + * + * Here is an example: + * + * capitalize($string); + * // Top-O-The-Morning To All_of_you! + * + * echo $inflector->capitalize($string, '-_ '); + * // Top-O-The-Morning To All_Of_You! + * ?> + * + * + * @param string $string The string to operate on. + * @param string $delimiters A list of word separators. + * + * @return string The string with all delimiter-separated words capitalized. + */ + public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-"): string + { + return ucwords($string, $delimiters); + } + + /** + * Checks if the given string seems like it has utf8 characters in it. + * + * @param string $string The string to check for utf8 characters in. + */ + public function seemsUtf8(string $string): bool + { + for ($i = 0; $i < strlen($string); $i++) { + if (ord($string[$i]) < 0x80) { + continue; // 0bbbbbbb + } + + if ((ord($string[$i]) & 0xE0) === 0xC0) { + $n = 1; // 110bbbbb + } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { + $n = 2; // 1110bbbb + } elseif ((ord($string[$i]) & 0xF8) === 0xF0) { + $n = 3; // 11110bbb + } elseif ((ord($string[$i]) & 0xFC) === 0xF8) { + $n = 4; // 111110bb + } elseif ((ord($string[$i]) & 0xFE) === 0xFC) { + $n = 5; // 1111110b + } else { + return false; // Does not match any model + } + + for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ? + if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) { + return false; + } + } + } + + return true; + } + + /** + * Remove any illegal characters, accents, etc. + * + * @param string $string String to unaccent + * + * @return string Unaccented string + */ + public function unaccent(string $string): string + { + if (preg_match('/[\x80-\xff]/', $string) === false) { + return $string; + } + + if ($this->seemsUtf8($string)) { + $string = strtr($string, self::ACCENTED_CHARACTERS); + } else { + $characters = []; + + // Assume ISO-8859-1 if not UTF-8 + $characters['in'] = + chr(128) + . chr(131) + . chr(138) + . chr(142) + . chr(154) + . chr(158) + . chr(159) + . chr(162) + . chr(165) + . chr(181) + . chr(192) + . chr(193) + . chr(194) + . chr(195) + . chr(196) + . chr(197) + . chr(199) + . chr(200) + . chr(201) + . chr(202) + . chr(203) + . chr(204) + . chr(205) + . chr(206) + . chr(207) + . chr(209) + . chr(210) + . chr(211) + . chr(212) + . chr(213) + . chr(214) + . chr(216) + . chr(217) + . chr(218) + . chr(219) + . chr(220) + . chr(221) + . chr(224) + . chr(225) + . chr(226) + . chr(227) + . chr(228) + . chr(229) + . chr(231) + . chr(232) + . chr(233) + . chr(234) + . chr(235) + . chr(236) + . chr(237) + . chr(238) + . chr(239) + . chr(241) + . chr(242) + . chr(243) + . chr(244) + . chr(245) + . chr(246) + . chr(248) + . chr(249) + . chr(250) + . chr(251) + . chr(252) + . chr(253) + . chr(255); + + $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; + + $string = strtr($string, $characters['in'], $characters['out']); + + $doubleChars = []; + + $doubleChars['in'] = [ + chr(140), + chr(156), + chr(198), + chr(208), + chr(222), + chr(223), + chr(230), + chr(240), + chr(254), + ]; + + $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; + + $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); + } + + return $string; + } + + /** + * Convert any passed string to a url friendly string. + * Converts 'My first blog post' to 'my-first-blog-post' + * + * @param string $string String to urlize. + * + * @return string Urlized string. + */ + public function urlize(string $string): string + { + // Remove all non url friendly characters with the unaccent function + $unaccented = $this->unaccent($string); + + if (function_exists('mb_strtolower')) { + $lowered = mb_strtolower($unaccented); + } else { + $lowered = strtolower($unaccented); + } + + $replacements = [ + '/\W/' => ' ', + '/([A-Z]+)([A-Z][a-z])/' => '\1_\2', + '/([a-z\d])([A-Z])/' => '\1_\2', + '/[^A-Z^a-z^0-9^\/]+/' => '-', + ]; + + $urlized = $lowered; + + foreach ($replacements as $pattern => $replacement) { + $replaced = preg_replace($pattern, $replacement, $urlized); + + if ($replaced === null) { + throw new RuntimeException(sprintf( + 'preg_replace returned null for value "%s"', + $urlized + )); + } + + $urlized = $replaced; + } + + return trim($urlized, '-'); + } + + /** + * Returns a word in singular form. + * + * @param string $word The word in plural form. + * + * @return string The word in singular form. + */ + public function singularize(string $word): string + { + return $this->singularizer->inflect($word); + } + + /** + * Returns a word in plural form. + * + * @param string $word The word in singular form. + * + * @return string The word in plural form. + */ + public function pluralize(string $word): string + { + return $this->pluralizer->inflect($word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php new file mode 100644 index 0000000..a0740a7 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php @@ -0,0 +1,52 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php new file mode 100644 index 0000000..02257de --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php @@ -0,0 +1,189 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php new file mode 100644 index 0000000..9747f91 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php @@ -0,0 +1,28 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php new file mode 100644 index 0000000..5d8d3b3 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php @@ -0,0 +1,30 @@ +pattern = $pattern; + + if (isset($this->pattern[0]) && $this->pattern[0] === '/') { + $this->regex = $this->pattern; + } else { + $this->regex = '/' . $this->pattern . '/i'; + } + } + + public function getPattern(): string + { + return $this->pattern; + } + + public function getRegex(): string + { + return $this->regex; + } + + public function matches(string $word): bool + { + return preg_match($this->getRegex(), $word) === 1; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php new file mode 100644 index 0000000..e8d45cb --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php @@ -0,0 +1,34 @@ +patterns = $patterns; + + $patterns = array_map(static function (Pattern $pattern): string { + return $pattern->getPattern(); + }, $this->patterns); + + $this->regex = '/^(?:' . implode('|', $patterns) . ')$/i'; + } + + public function matches(string $word): bool + { + return preg_match($this->regex, $word, $regs) === 1; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php new file mode 100644 index 0000000..0d41fe7 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php @@ -0,0 +1,98 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php new file mode 100644 index 0000000..b8e988f --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php @@ -0,0 +1,32 @@ +regular = $regular; + $this->uninflected = $uninflected; + $this->irregular = $irregular; + } + + public function getRegular(): Transformations + { + return $this->regular; + } + + public function getUninflected(): Patterns + { + return $this->uninflected; + } + + public function getIrregular(): Substitutions + { + return $this->irregular; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php new file mode 100644 index 0000000..9129460 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php @@ -0,0 +1,47 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php new file mode 100644 index 0000000..c26ebe9 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php @@ -0,0 +1,30 @@ +from = $from; + $this->to = $to; + } + + public function getFrom(): Word + { + return $this->from; + } + + public function getTo(): Word + { + return $this->to; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php new file mode 100644 index 0000000..17ee296 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php @@ -0,0 +1,57 @@ +substitutions[$substitution->getFrom()->getWord()] = $substitution; + } + } + + public function getFlippedSubstitutions(): Substitutions + { + $substitutions = []; + + foreach ($this->substitutions as $substitution) { + $substitutions[] = new Substitution( + $substitution->getTo(), + $substitution->getFrom() + ); + } + + return new Substitutions(...$substitutions); + } + + public function inflect(string $word): string + { + $lowerWord = strtolower($word); + + if (isset($this->substitutions[$lowerWord])) { + $firstLetterUppercase = $lowerWord[0] !== $word[0]; + + $toWord = $this->substitutions[$lowerWord]->getTo()->getWord(); + + if ($firstLetterUppercase) { + return strtoupper($toWord[0]) . substr($toWord, 1); + } + + return $toWord; + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php new file mode 100644 index 0000000..30dcd59 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php @@ -0,0 +1,39 @@ +pattern = $pattern; + $this->replacement = $replacement; + } + + public function getPattern(): Pattern + { + return $this->pattern; + } + + public function getReplacement(): string + { + return $this->replacement; + } + + public function inflect(string $word): string + { + return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php new file mode 100644 index 0000000..b6a48fa --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php @@ -0,0 +1,29 @@ +transformations = $transformations; + } + + public function inflect(string $word): string + { + foreach ($this->transformations as $transformation) { + if ($transformation->getPattern()->matches($word)) { + return $transformation->inflect($word); + } + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php new file mode 100644 index 0000000..a2bda0d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php @@ -0,0 +1,34 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php new file mode 100644 index 0000000..ec1c37d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php @@ -0,0 +1,30 @@ +word = $word; + } + + public function getWord(): string + { + return $this->word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php new file mode 100644 index 0000000..12b2ed5 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php @@ -0,0 +1,56 @@ +rulesets = array_merge([$ruleset], $rulesets); + } + + public function inflect(string $word): string + { + if ($word === '') { + return ''; + } + + foreach ($this->rulesets as $ruleset) { + if ($ruleset->getUninflected()->matches($word)) { + return $word; + } + + $inflected = $ruleset->getIrregular()->inflect($word); + + if ($inflected !== $word) { + return $inflected; + } + + $inflected = $ruleset->getRegular()->inflect($word); + + if ($inflected !== $word) { + return $inflected; + } + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php new file mode 100644 index 0000000..b88b1d6 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php @@ -0,0 +1,10 @@ +instantiate(\My\ClassName\Here::class); +``` + +## Contributing + +Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out! + +## Credits + +This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which +has been donated to the doctrine organization, and which is now deprecated in favour of this package. diff --git a/vendor/doctrine/instantiator/composer.json b/vendor/doctrine/instantiator/composer.json new file mode 100644 index 0000000..179145e --- /dev/null +++ b/vendor/doctrine/instantiator/composer.json @@ -0,0 +1,48 @@ +{ + "name": "doctrine/instantiator", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "type": "library", + "license": "MIT", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "instantiate", + "constructor" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "require": { + "php": "^8.1" + }, + "require-dev": { + "ext-phar": "*", + "ext-pdo": "*", + "doctrine/coding-standard": "^11", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "autoload-dev": { + "psr-0": { + "DoctrineTest\\InstantiatorPerformance\\": "tests", + "DoctrineTest\\InstantiatorTest\\": "tests", + "DoctrineTest\\InstantiatorTestAsset\\": "tests" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/doctrine/instantiator/docs/en/index.rst b/vendor/doctrine/instantiator/docs/en/index.rst new file mode 100644 index 0000000..0c85da0 --- /dev/null +++ b/vendor/doctrine/instantiator/docs/en/index.rst @@ -0,0 +1,68 @@ +Introduction +============ + +This library provides a way of avoiding usage of constructors when instantiating PHP classes. + +Installation +============ + +The suggested installation method is via `composer`_: + +.. code-block:: console + + $ composer require doctrine/instantiator + +Usage +===== + +The instantiator is able to create new instances of any class without +using the constructor or any API of the class itself: + +.. code-block:: php + + instantiate(User::class); + +Contributing +============ + +- Follow the `Doctrine Coding Standard`_ +- The project will follow strict `object calisthenics`_ +- Any contribution must provide tests for additional introduced + conditions +- Any un-confirmed issue needs a failing test case before being + accepted +- Pull requests must be sent from a new hotfix/feature branch, not from + ``master``. + +Testing +======= + +The PHPUnit version to be used is the one installed as a dev- dependency +via composer: + +.. code-block:: console + + $ ./vendor/bin/phpunit + +Accepted coverage for new contributions is 80%. Any contribution not +satisfying this requirement won’t be merged. + +Credits +======= + +This library was migrated from `ocramius/instantiator`_, which has been +donated to the doctrine organization, and which is now deprecated in +favour of this package. + +.. _composer: https://getcomposer.org/ +.. _CONTRIBUTING.md: CONTRIBUTING.md +.. _ocramius/instantiator: https://github.com/Ocramius/Instantiator +.. _Doctrine Coding Standard: https://github.com/doctrine/coding-standard +.. _object calisthenics: http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php diff --git a/vendor/doctrine/instantiator/docs/en/sidebar.rst b/vendor/doctrine/instantiator/docs/en/sidebar.rst new file mode 100644 index 0000000..0c36479 --- /dev/null +++ b/vendor/doctrine/instantiator/docs/en/sidebar.rst @@ -0,0 +1,4 @@ +.. toctree:: + :depth: 3 + + index diff --git a/vendor/doctrine/instantiator/psalm.xml b/vendor/doctrine/instantiator/psalm.xml new file mode 100644 index 0000000..e9b622b --- /dev/null +++ b/vendor/doctrine/instantiator/psalm.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php new file mode 100644 index 0000000..1e59192 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php @@ -0,0 +1,14 @@ + $reflectionClass + * + * @template T of object + */ + public static function fromAbstractClass(ReflectionClass $reflectionClass): self + { + return new self(sprintf( + 'The provided class "%s" is abstract, and cannot be instantiated', + $reflectionClass->getName(), + )); + } + + public static function fromEnum(string $className): self + { + return new self(sprintf( + 'The provided class "%s" is an enum, and cannot be instantiated', + $className, + )); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php new file mode 100644 index 0000000..4f70ded --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php @@ -0,0 +1,61 @@ + $reflectionClass + * + * @template T of object + */ + public static function fromSerializationTriggeredException( + ReflectionClass $reflectionClass, + Exception $exception, + ): self { + return new self( + sprintf( + 'An exception was raised while trying to instantiate an instance of "%s" via un-serialization', + $reflectionClass->getName(), + ), + 0, + $exception, + ); + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + public static function fromUncleanUnSerialization( + ReflectionClass $reflectionClass, + string $errorString, + int $errorCode, + string $errorFile, + int $errorLine, + ): self { + return new self( + sprintf( + 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' + . 'in file "%s" at line "%d"', + $reflectionClass->getName(), + $errorFile, + $errorLine, + ), + 0, + new Exception($errorString, $errorCode), + ); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php new file mode 100644 index 0000000..f803f89 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php @@ -0,0 +1,255 @@ + $className + * + * @phpstan-return T + * + * @throws ExceptionInterface + * + * @template T of object + */ + public function instantiate(string $className): object + { + if (isset(self::$cachedCloneables[$className])) { + /** @phpstan-var T */ + $cachedCloneable = self::$cachedCloneables[$className]; + + return clone $cachedCloneable; + } + + if (isset(self::$cachedInstantiators[$className])) { + $factory = self::$cachedInstantiators[$className]; + + return $factory(); + } + + return $this->buildAndCacheFromFactory($className); + } + + /** + * Builds the requested object and caches it in static properties for performance + * + * @phpstan-param class-string $className + * + * @phpstan-return T + * + * @template T of object + */ + private function buildAndCacheFromFactory(string $className): object + { + $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); + $instance = $factory(); + + if ($this->isSafeToClone(new ReflectionClass($instance))) { + self::$cachedCloneables[$className] = clone $instance; + } + + return $instance; + } + + /** + * Builds a callable capable of instantiating the given $className without + * invoking its constructor. + * + * @phpstan-param class-string $className + * + * @phpstan-return callable(): T + * + * @throws InvalidArgumentException + * @throws UnexpectedValueException + * @throws ReflectionException + * + * @template T of object + */ + private function buildFactory(string $className): callable + { + $reflectionClass = $this->getReflectionClass($className); + + if ($this->isInstantiableViaReflection($reflectionClass)) { + return [$reflectionClass, 'newInstanceWithoutConstructor']; + } + + $serializedString = sprintf( + '%s:%d:"%s":0:{}', + is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, + strlen($className), + $className, + ); + + $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); + + return static fn () => unserialize($serializedString); + } + + /** + * @phpstan-param class-string $className + * + * @phpstan-return ReflectionClass + * + * @throws InvalidArgumentException + * @throws ReflectionException + * + * @template T of object + */ + private function getReflectionClass(string $className): ReflectionClass + { + if (! class_exists($className)) { + throw InvalidArgumentException::fromNonExistingClass($className); + } + + if (enum_exists($className, false)) { + throw InvalidArgumentException::fromEnum($className); + } + + $reflection = new ReflectionClass($className); + + if ($reflection->isAbstract()) { + throw InvalidArgumentException::fromAbstractClass($reflection); + } + + return $reflection; + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @throws UnexpectedValueException + * + * @template T of object + */ + private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void + { + set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool { + $error = UnexpectedValueException::fromUncleanUnSerialization( + $reflectionClass, + $message, + $code, + $file, + $line, + ); + + return true; + }); + + try { + $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); + } finally { + restore_error_handler(); + } + + if ($error) { + throw $error; + } + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @throws UnexpectedValueException + * + * @template T of object + */ + private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void + { + try { + unserialize($serializedString); + } catch (Exception $exception) { + throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); + } + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool + { + return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); + } + + /** + * Verifies whether the given class is to be considered internal + * + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + private function hasInternalAncestors(ReflectionClass $reflectionClass): bool + { + do { + if ($reflectionClass->isInternal()) { + return true; + } + + $reflectionClass = $reflectionClass->getParentClass(); + } while ($reflectionClass); + + return false; + } + + /** + * Checks if a class is cloneable + * + * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. + * + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + private function isSafeToClone(ReflectionClass $reflectionClass): bool + { + return $reflectionClass->isCloneable() + && ! $reflectionClass->hasMethod('__clone') + && ! $reflectionClass->isSubclassOf(ArrayIterator::class); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php new file mode 100644 index 0000000..c6ebe35 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php @@ -0,0 +1,24 @@ + $className + * + * @phpstan-return T + * + * @throws ExceptionInterface + * + * @template T of object + */ + public function instantiate(string $className): object; +} diff --git a/vendor/doctrine/lexer/LICENSE b/vendor/doctrine/lexer/LICENSE new file mode 100644 index 0000000..e8fdec4 --- /dev/null +++ b/vendor/doctrine/lexer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2018 Doctrine Project + +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. diff --git a/vendor/doctrine/lexer/README.md b/vendor/doctrine/lexer/README.md new file mode 100644 index 0000000..784f2a2 --- /dev/null +++ b/vendor/doctrine/lexer/README.md @@ -0,0 +1,9 @@ +# Doctrine Lexer + +[![Build Status](https://github.com/doctrine/lexer/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/lexer/actions) + +Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. + +This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). + +https://www.doctrine-project.org/projects/lexer.html diff --git a/vendor/doctrine/lexer/UPGRADE.md b/vendor/doctrine/lexer/UPGRADE.md new file mode 100644 index 0000000..1933fcb --- /dev/null +++ b/vendor/doctrine/lexer/UPGRADE.md @@ -0,0 +1,22 @@ +Note about upgrading: Doctrine uses static and runtime mechanisms to raise +awareness about deprecated code. + +- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or + Static Analysis tools (like Psalm, phpstan) +- Use of our low-overhead runtime deprecation API, details: + https://github.com/doctrine/deprecations/ + +# Upgrade to 3.0.0 + +`Doctrine\Common\Lexer\Token` no longer implements `ArrayAccess`. +Parameter type declarations have been added to +`Doctrine\Common\Lexer\AbstractLexer` and `Doctrine\Common\Lexer\Token`. +You should add both parameter type declarations and return type declarations to +your lexers, based on the `@return` phpdoc. + +# Upgrade to 2.0.0 + +`AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return +instances of `Doctrine\Common\Lexer\Token`, which is an array-like class +Using it as an array is deprecated in favor of using properties of that class. +Using `count()` on it is deprecated with no replacement. diff --git a/vendor/doctrine/lexer/composer.json b/vendor/doctrine/lexer/composer.json new file mode 100644 index 0000000..995dafa --- /dev/null +++ b/vendor/doctrine/lexer/composer.json @@ -0,0 +1,55 @@ +{ + "name": "doctrine/lexer", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "license": "MIT", + "type": "library", + "keywords": [ + "php", + "parser", + "lexer", + "annotations", + "docblock" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\Common\\Lexer\\": "tests" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/vendor/doctrine/lexer/src/AbstractLexer.php b/vendor/doctrine/lexer/src/AbstractLexer.php new file mode 100644 index 0000000..2436885 --- /dev/null +++ b/vendor/doctrine/lexer/src/AbstractLexer.php @@ -0,0 +1,328 @@ +> + */ + private array $tokens = []; + + /** + * Current lexer position in input string. + */ + private int $position = 0; + + /** + * Current peek of current lexer position. + */ + private int $peek = 0; + + /** + * The next token in the input. + * + * @var Token|null + */ + public Token|null $lookahead; + + /** + * The last matched/seen token. + * + * @var Token|null + */ + public Token|null $token; + + /** + * Composed regex for input parsing. + * + * @var non-empty-string|null + */ + private string|null $regex = null; + + /** + * Sets the input data to be tokenized. + * + * The Lexer is immediately reset and the new input tokenized. + * Any unprocessed tokens from any previous input are lost. + * + * @param string $input The input to be tokenized. + * + * @return void + */ + public function setInput(string $input) + { + $this->input = $input; + $this->tokens = []; + + $this->reset(); + $this->scan($input); + } + + /** + * Resets the lexer. + * + * @return void + */ + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->peek = 0; + $this->position = 0; + } + + /** + * Resets the peek pointer to 0. + * + * @return void + */ + public function resetPeek() + { + $this->peek = 0; + } + + /** + * Resets the lexer position on the input to the given position. + * + * @param int $position Position to place the lexical scanner. + * + * @return void + */ + public function resetPosition(int $position = 0) + { + $this->position = $position; + } + + /** + * Retrieve the original lexer's input until a given position. + * + * @return string + */ + public function getInputUntilPosition(int $position) + { + return substr($this->input, 0, $position); + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param T $type + * + * @return bool + * + * @psalm-assert-if-true !=null $this->lookahead + */ + public function isNextToken(int|string|UnitEnum $type) + { + return $this->lookahead !== null && $this->lookahead->isA($type); + } + + /** + * Checks whether any of the given tokens matches the current lookahead. + * + * @param list $types + * + * @return bool + * + * @psalm-assert-if-true !=null $this->lookahead + */ + public function isNextTokenAny(array $types) + { + return $this->lookahead !== null && $this->lookahead->isA(...$types); + } + + /** + * Moves to the next token in the input string. + * + * @return bool + * + * @psalm-assert-if-true !null $this->lookahead + */ + public function moveNext() + { + $this->peek = 0; + $this->token = $this->lookahead; + $this->lookahead = isset($this->tokens[$this->position]) + ? $this->tokens[$this->position++] : null; + + return $this->lookahead !== null; + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param T $type The token type to skip until. + * + * @return void + */ + public function skipUntil(int|string|UnitEnum $type) + { + while ($this->lookahead !== null && ! $this->lookahead->isA($type)) { + $this->moveNext(); + } + } + + /** + * Checks if given value is identical to the given token. + * + * @return bool + */ + public function isA(string $value, int|string|UnitEnum $token) + { + return $this->getType($value) === $token; + } + + /** + * Moves the lookahead token forward. + * + * @return Token|null The next token or NULL if there are no more tokens ahead. + */ + public function peek() + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } + + return null; + } + + /** + * Peeks at the next token, returns it and immediately resets the peek. + * + * @return Token|null The next token or NULL if there are no more tokens ahead. + */ + public function glimpse() + { + $peek = $this->peek(); + $this->peek = 0; + + return $peek; + } + + /** + * Scans the input string for tokens. + * + * @param string $input A query string. + * + * @return void + */ + protected function scan(string $input) + { + if (! isset($this->regex)) { + $this->regex = sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + $this->getModifiers(), + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($this->regex, $input, -1, $flags); + + if ($matches === false) { + // Work around https://bugs.php.net/78122 + $matches = [[$input, 0]]; + } + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $firstMatch = $match[0]; + $type = $this->getType($firstMatch); + + $this->tokens[] = new Token( + $firstMatch, + $type, + $match[1], + ); + } + } + + /** + * Gets the literal for a given token. + * + * @param T $token + * + * @return int|string + */ + public function getLiteral(int|string|UnitEnum $token) + { + if ($token instanceof UnitEnum) { + return $token::class . '::' . $token->name; + } + + $className = static::class; + + $reflClass = new ReflectionClass($className); + $constants = $reflClass->getConstants(); + + foreach ($constants as $name => $value) { + if ($value === $token) { + return $className . '::' . $name; + } + } + + return $token; + } + + /** + * Regex modifiers + * + * @return string + */ + protected function getModifiers() + { + return 'iu'; + } + + /** + * Lexical catchable patterns. + * + * @return string[] + */ + abstract protected function getCatchablePatterns(); + + /** + * Lexical non-catchable patterns. + * + * @return string[] + */ + abstract protected function getNonCatchablePatterns(); + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @return T|null + * + * @param-out V $value + */ + abstract protected function getType(string &$value); +} diff --git a/vendor/doctrine/lexer/src/Token.php b/vendor/doctrine/lexer/src/Token.php new file mode 100644 index 0000000..b6df694 --- /dev/null +++ b/vendor/doctrine/lexer/src/Token.php @@ -0,0 +1,56 @@ +value = $value; + $this->type = $type; + $this->position = $position; + } + + /** @param T ...$types */ + public function isA(...$types): bool + { + return in_array($this->type, $types, true); + } +} diff --git a/vendor/doctrine/migrations/LICENSE b/vendor/doctrine/migrations/LICENSE new file mode 100644 index 0000000..e8fdec4 --- /dev/null +++ b/vendor/doctrine/migrations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2018 Doctrine Project + +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. diff --git a/vendor/doctrine/migrations/README.md b/vendor/doctrine/migrations/README.md new file mode 100644 index 0000000..91a9f65 --- /dev/null +++ b/vendor/doctrine/migrations/README.md @@ -0,0 +1,11 @@ +# Doctrine Migrations + +[![Build Status](https://github.com/doctrine/migrations/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/migrations/actions) +[![Code Coverage](https://codecov.io/gh/doctrine/migrations/branch/3.1.x/graph/badge.svg)](https://codecov.io/gh/doctrine/migrations/branch/3.1.x) +[![Packagist Downloads](https://img.shields.io/packagist/dm/doctrine/migrations)](https://packagist.org/packages/doctrine/migrations) +[![Packagist Version](https://img.shields.io/packagist/v/doctrine/migrations)](https://packagist.org/packages/doctrine/migrations) +[![GitHub license](https://img.shields.io/github/license/doctrine/migrations)](LICENSE) + +## Documentation + +All available documentation can be found [here](https://www.doctrine-project.org/projects/migrations.html). diff --git a/vendor/doctrine/migrations/UPGRADE.md b/vendor/doctrine/migrations/UPGRADE.md new file mode 100644 index 0000000..728c607 --- /dev/null +++ b/vendor/doctrine/migrations/UPGRADE.md @@ -0,0 +1,302 @@ +# Upgrade to 3.6 + +## Console +- The `--all-or-nothing` option for `migrations:migrate` does not accept a value anymore, and passing it a + value will generate a deprecation. Specifying `--all-or-nothing` will wrap all the migrations to be + executed into a single transaction, regardless of the specified configuration. + + + +# Upgrade to 3.1 + +- The "version" is the FQCN of the migration class (existing entries in the migrations table will be automatically updated). +- `MigrationsEventArgs` and `MigrationsVersionEventArgs` expose different API, +please refer to the [Code BC breaks](#code-bc-breaks) section. + +## Console +- Console output changed. The commands use a different output style. If you were relying on specific output, + please update your scripts. + Console output is not covered by the BC promise, so please try not to rely on specific a output. + Different levels of verbosity are available now (`-v`, `-vv` and `-vvv` ). +- The `--show-versions` option from `migrations:status` command has been removed, + use `migrations:list` instead. +- The `--write-sql` option for `migrations:migrate` and `migrations:execute` does not imply dry-run anymore, +use the `--dry-run` parameter instead. +- The `--db` option has been renamed to `--conn`. + +## Migrations table + +- The migrations table now has a new column named `execution_time`. +- Running the `migrations:migrate` or `migrations:execute` command will automatically upgrade the migration +table structure; a dedicated `migrations:sync-metadata-storage` command is available to sync manually the migrations table. + +## Migration template + +- The `` placeholder has been replaced by the `` placeholder. + +## Configuration files + +*migrations.php Before* +```php + 'My Project Migrations', + 'migrations_namespace' => 'MyProject\Migrations', + 'table_name' => 'doctrine_migration_versions', + 'column_name' => 'version', + 'column_length' => 14, + 'executed_at_column_name' => 'executed_at', + 'migrations_directory' => '/data/doctrine/migrations-docs-example/lib/MyProject/Migrations', + 'all_or_nothing' => true, + 'check_database_platform' => true, +]; +``` +*migrations.php After* + +```php + [ + 'table_name' => 'doctrine_migration_versions', + 'version_column_name' => 'version', + 'version_column_length' => 191, + 'executed_at_column_name' => 'executed_at', + 'execution_time_column_name' => 'execution_time', + ], + + 'migrations_paths' => [ + 'MyProject\Migrations' => '/data/doctrine/migrations/lib/MyProject/Migrations', + 'MyProject\Component\Migrations' => './Component/MyProject/Migrations', + ], + + 'all_or_nothing' => true, + 'check_database_platform' => true, +]; +``` + +Files in XML, YAML or JSON also changed in a similar way. Please refer to the official documentation for more details. + +Note: the `name` property has been removed. + +Note: the option in `table_storage` needs to be updated only if you have changed the metadata table settings +by using v2 options such as `table_name`, `column_name`, `column_length` or `executed_at_column_name`. If you did not change +those settings, it is recommended to not provide the options and let doctrine figure out the best settings. + +## Code BC breaks + +Most of the code is protected by the `@internal` declaration and in a very rare cases you might have dealt with the +internals of this library. + +The most important BC breaks are in the `Doctrine\Migrations\Configuration\Configuration` class and in the helper +system that now has been replaced by the `Doctrine\Migrations\DependencyFactory` functionalities. + +Here is a list of the most important changes: + +- Namespace `Doctrine\Migrations\Configuration\Configuration` + - CHANGED: Class `Doctrine\Migrations\Configuration\Configuration` became final + - REMOVED: Constant `Doctrine\Migrations\Configuration\Configuration::VERSION_FORMAT` was removed, there is not more limitation on the version format + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#__construct()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setName()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getName()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getConnection()` was removed, + use `Doctrine\Migrations\DependencyFactory#getConnection()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsTableName()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsTableName()` was removed, + use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsColumnName()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsColumnName()` was removed, + use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getQuotedMigrationsColumnName()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsColumnLength()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsColumnLength()` was removed, + use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsExecutedAtColumnName()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsExecutedAtColumnName()` was removed, + use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getQuotedMigrationsExecutedAtColumnName()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsDirectory()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#addMigrationsDirectory()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsDirectory()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#getMigrationDirectories()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsNamespace()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#addMigrationsDirectory()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsNamespace()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#getMigrationDirectories()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsFinder()` was removed, + use the dependency factory instead + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsFinder()` was removed, + use the dependency factory instead + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#hasVersionMigrated()` was removed, + use the dependency factory instead + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getVersionData()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#resolveVersionAlias()` was removed, + use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#isMigrationTableCreated()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#createMigrationTable()` was removed, + use `Doctrine\Migrations\Metadata\Storage\MetadataStorage#ensureInitialized()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getDateTime()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#generateVersionNumber()` was removed, + use `Doctrine\Migrations\Generator\ClassNameGenerator#generateClassName()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#connect()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#dispatchMigrationEvent()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#dispatchVersionEvent()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#dispatchEvent()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getNumberOfExecutedMigrations()` was removed, + use `Doctrine\Migrations\DependencyFactory#getMetadataStorage()->getExecutedMigrations()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getNumberOfAvailableMigrations()` was removed, + use `Doctrine\Migrations\DependencyFactory#getMigrationRepository()->getMigrations()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getLatestVersion()` was removed, + use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigratedVersions()` was removed, + use `Doctrine\Migrations\DependencyFactory#getMetadataStorage()->getExecutedMigrations()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getAvailableVersions()` was removed + use `Doctrine\Migrations\DependencyFactory#getMigrationRepository()->getMigrations()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getCurrentVersion()` was removed, + use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#registerMigrationsFromDirectory()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#addMigrationsDirectory()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#registerMigration()` was removed, + use `Doctrine\Migrations\Configuration\Configuration#addMigrationClass()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#registerMigrations()` was removed + use `Doctrine\Migrations\Configuration\Configuration#addMigrationClass()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrations()` was removed, + use `Doctrine\Migrations\DependencyFactory#getMigrationRepository()->getMigrations()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getVersion()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsToExecute()` was removed, + use `Doctrine\Migrations\Version\MigrationPlanCalculator#getPlanUntilVersion()` to create a migration plan + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getPrevVersion()` was removed, + use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getNextVersion()` was removed, + use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getRelativeVersion()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getDeltaVersion()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setOutputWriter()` was removed, + set the `Psr\Log\LoggerInterface` service in `Doctrine\Migrations\DependencyFactory` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getOutputWriter()` was removed, + get the `Psr\Log\LoggerInterface` service from `Doctrine\Migrations\DependencyFactory` + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getQueryWriter()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getDependencyFactory()` was removed + - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#validate()` was removed + - Namespace `Doctrine\Migrations\Configuration\Connection\Loader\Exception` + - REMOVED: Class `Doctrine\Migrations\Configuration\Connection\Loader\Exception\LoaderException` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Connection\Loader\Exception\InvalidConfiguration` has been deleted + - Namespace `Doctrine\Migrations\Configuration\Exception` + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\ParameterIncompatibleWithFinder` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\InvalidConfigurationKey` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\MigrationsNamespaceRequired` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\XmlNotValid` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\YamlNotAvailable` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\FileAlreadyLoaded` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\JsonNotValid` has been deleted + - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\YamlNotValid` has been deleted + - CHANGED: The number of required arguments for `Doctrine\Migrations\Configuration\Exception\FileNotFound::new()` increased from 0 to 1 + - Namespace `Doctrine\Migrations\Event\MigrationsEventArgs` + - CHANGED: Class `Doctrine\Migrations\Event\MigrationsEventArgs` became final + - REMOVED: Method `Doctrine\Migrations\Event\MigrationsEventArgs#getConfiguration()` was removed + - REMOVED: Method `Doctrine\Migrations\Event\MigrationsEventArgs#getDirection()` was removed, + use `Doctrine\Migrations\Event\MigrationsEventArgs#getPlan()` + - REMOVED: Method `Doctrine\Migrations\Event\MigrationsEventArgs#isDryRun()` was removed, + use `Doctrine\Migrations\Event\MigrationsEventArgs#getMigratorConfiguration()` + - CHANGED: `Doctrine\Migrations\Event\MigrationsEventArgs#__construct()` arguments have been updated + - Namespace `Doctrine\Migrations\Event\MigrationsVersionEventArgs` + - CHANGED: Class `Doctrine\Migrations\Event\MigrationsVersionEventArgs` became final + - REMOVED: Method `Doctrine\Migrations\Event\MigrationsVersionEventArgs#getVersion()` was removed + use `Doctrine\Migrations\Event\MigrationsEventArgs#getPlan()` + - Namespace `Doctrine\Migrations\Finder` + - REMOVED: These ancestors of `Doctrine\Migrations\Finder\RecursiveRegexFinder` have been removed: ["Doctrine\\Migrations\\Finder\\MigrationDeepFinder"] + - REMOVED: Class `Doctrine\Migrations\Finder\MigrationDeepFinder` has been deleted + - Namespace `Doctrine\Migrations\Tools\Console\Command` + - CHANGED: All non abstract classes in `Doctrine\Migrations\Tools\Console\Command\*` became final + - REMOVED: Class `Doctrine\Migrations\Tools\Console\Command\AbstractCommand` has been renamed into `Doctrine\Migrations\Tools\Console\Command\DoctrineCommand` and has been marked as internal + - CHANGED: Method `Doctrine\Migrations\Tools\Console\Command\*Command#__construct()` changed signature into `(?Doctrine\Migrations\DependencyFactory $di, ?string $name)` + - CHANGED: Method `initialize()` of Class `Doctrine\Migrations\Tools\Console\Command\AbstractCommand` visibility reduced from `public` to `protected` + - CHANGED: Method `execute()` of Class `Doctrine\Migrations\Tools\Console\Command\*Command` visibility reduced from `public` to `protected` + - REMOVED: Method `Doctrine\Migrations\Tools\Console\Command\DiffCommand#createMigrationDiffGenerator()` was removed + - Namespace `Doctrine\Migrations\Tools\Console\Exception` + - CHANGED: The number of required arguments for `Doctrine\Migrations\Tools\Console\Exception\SchemaDumpRequiresNoMigrations::new()` increased from 0 to 1 + - REMOVED: Class `Doctrine\Migrations\Tools\Console\Exception\ConnectionNotSpecified` has been deleted + - Namespace `Migrations\Tools\Console\Helper` + - REMOVED: All classes and namespaces are marked as internal or have been removed, + use `Doctrine\Migrations\DependencyFactory` instead + - Namespace `Doctrine\Migrations\AbstractMigration` + - CHANGED: The method `Doctrine\Migrations\AbstractMigration#__construct()` changed signature into `(Doctrine\DBAL\Connection $conn, PSR\Log\LoggerInterface $logger)` + - CHANGED: The method `Doctrine\Migrations\AbstractMigration#down()` is not abstract anymore, the default implementation will abort the migration process + - REMOVED: Property `Doctrine\Migrations\AbstractMigration#$version` was removed + - Namespace `Doctrine\Migrations\Provider` + - REMOVED: Class `Doctrine\Migrations\Provider\SchemaProviderInterface` has been deleted + - REMOVED: These ancestors of `Doctrine\Migrations\Provider\StubSchemaProvider` have been removed: ["Doctrine\\Migrations\\Provider\\SchemaProviderInterface"] + - Namespace `Doctrine\Migrations\Exception` + - REMOVED: Class `Doctrine\Migrations\Exception\MigrationNotConvertibleToSql` has been deleted + - REMOVED: Class `Doctrine\Migrations\Exception\MigrationsDirectoryRequired` has been deleted + - REMOVED: Class `Doctrine\Migrations\Version\Factory` became the interface `Doctrine\Migrations\Version\MigrationFactory` + - REMOVED: Class `Doctrine\Migrations\OutputWriter` has been deleted, + use `Psr\Log\Loggerinterface` + + + + +# Upgrade to 2.0 + +## BC Break: Moved `Doctrine\DBAL\Migrations` to `Doctrine\Migrations` + +Your migration classes that previously used to extend `Doctrine\DBAL\Migrations\AbstractMigration` now need to extend +`Doctrine\Migrations\AbstractMigration` instead. The `Doctrine\DBAL\Migrations\AbstractMigration` class will be +deprecated in the `1.8.0` release to prepare for the BC break. + +## BC Break: Removed `Doctrine\DBAL\Migrations\MigrationsVersion` + +The `Doctrine\DBAL\Migrations\MigrationsVersion` class is no longer available: please refrain from checking the Migrations version at runtime. + +## BC Break: Moved `Doctrine\Migrations\Migration` to `Doctrine\Migrations\Migrator` + +To make the name more clear and to differentiate from the `AbstractMigration` class, `Migration` was renamed to `Migrator`. + +## BC Break: Moved exception classes from `Doctrine\Migrations\%name%Exception` to `Doctrine\Migrations\Exception\%name%` +doctrine/migrations#636 +Follows concept introduced in ORM (doctrine/orm#6743 + doctrine/orm#7210) and naming follows pattern accepted in Doctrine CS. + +# Upgrade from 1.0-alpha1 to 1.0.0-alpha3 + +## AbstractMigration + +### Before: + +The method `getName()` was defined and it's implementation would change the order in which the migration would be processed. +It would cause discrepancies between the file order in a file browser and the order of execution of the migrations. + +### After: + +The `getName()` method as been removed | set final and new `getDescription()` method has been added. +The goal of this method is to be able to provide context for the migration. +This context is shown for the last migrated migration when the status command is called. + +## --write-sql option from the migrate command + +### Before: + +The `--write-sql` option would only output sql contained in the migration and would not update the table containing the migrated migrations. + +### After: + +That option now also output the sql queries necessary to update the table containing the state of the migrations. +If you want to go back to the previous behavior just make a request on the bug tracker as for now the need for it is not very clear. + +## MigrationsVersion::VERSION + +### Before: + +`MigrationsVersion::VERSION` used to be a property. +The returned value was fanciful. + +### After: + +It is now a a function so that a different value can be automatically send back if it's a modified version that's used. +The returned value is now the git tag. +The tag is in lowercase as the other doctrine projects. diff --git a/vendor/doctrine/migrations/bin/doctrine-migrations b/vendor/doctrine/migrations/bin/doctrine-migrations new file mode 100755 index 0000000..68394e8 --- /dev/null +++ b/vendor/doctrine/migrations/bin/doctrine-migrations @@ -0,0 +1,8 @@ +#!/usr/bin/env php +=4" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Migrations\\Tests\\": "tests" + } + }, + "bin": [ + "bin/doctrine-migrations" + ], + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/vendor/doctrine/migrations/src/AbstractMigration.php b/vendor/doctrine/migrations/src/AbstractMigration.php new file mode 100644 index 0000000..e13045c --- /dev/null +++ b/vendor/doctrine/migrations/src/AbstractMigration.php @@ -0,0 +1,163 @@ + */ + protected $sm; + + /** @var AbstractPlatform */ + protected $platform; + + /** @var Query[] */ + private array $plannedSql = []; + + private bool $frozen = false; + + public function __construct(Connection $connection, private readonly LoggerInterface $logger) + { + $this->connection = $connection; + $this->sm = $this->connection->createSchemaManager(); + $this->platform = $this->connection->getDatabasePlatform(); + } + + /** + * Indicates the transactional mode of this migration. + * + * If this function returns true (default) the migration will be executed + * in one transaction, otherwise non-transactional state will be used to + * execute each of the migration SQLs. + * + * Extending class should override this function to alter the return value. + */ + public function isTransactional(): bool + { + return true; + } + + public function getDescription(): string + { + return ''; + } + + public function warnIf(bool $condition, string $message = 'Unknown Reason'): void + { + if (! $condition) { + return; + } + + $this->logger->warning($message, ['migration' => $this]); + } + + /** @throws AbortMigration */ + public function abortIf(bool $condition, string $message = 'Unknown Reason'): void + { + if ($condition) { + throw new AbortMigration($message); + } + } + + /** @throws SkipMigration */ + public function skipIf(bool $condition, string $message = 'Unknown Reason'): void + { + if ($condition) { + throw new SkipMigration($message); + } + } + + /** @throws MigrationException|DBALException */ + public function preUp(Schema $schema): void + { + } + + /** @throws MigrationException|DBALException */ + public function postUp(Schema $schema): void + { + } + + /** @throws MigrationException|DBALException */ + public function preDown(Schema $schema): void + { + } + + /** @throws MigrationException|DBALException */ + public function postDown(Schema $schema): void + { + } + + /** @throws MigrationException|DBALException */ + abstract public function up(Schema $schema): void; + + /** @throws MigrationException|DBALException */ + public function down(Schema $schema): void + { + $this->abortIf(true, sprintf('No down() migration implemented for "%s"', static::class)); + } + + /** + * @param mixed[] $params + * @param mixed[] $types + */ + protected function addSql( + string $sql, + array $params = [], + array $types = [], + ): void { + if ($this->frozen) { + throw FrozenMigration::new(); + } + + $this->plannedSql[] = new Query($sql, $params, $types); + } + + /** @return Query[] */ + public function getSql(): array + { + return $this->plannedSql; + } + + public function freeze(): void + { + $this->frozen = true; + } + + protected function write(string $message): void + { + $this->logger->notice($message, ['migration' => $this]); + } + + /** @throws IrreversibleMigration */ + protected function throwIrreversibleMigrationException(string|null $message = null): void + { + if ($message === null) { + $message = 'This migration is irreversible and cannot be reverted.'; + } + + throw new IrreversibleMigration($message); + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Configuration.php b/vendor/doctrine/migrations/src/Configuration/Configuration.php new file mode 100644 index 0000000..9e1f1ce --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Configuration.php @@ -0,0 +1,212 @@ + */ + private array $migrationsDirectories = []; + + /** @var string[] */ + private array $migrationClasses = []; + + private bool $migrationsAreOrganizedByYear = false; + + private bool $migrationsAreOrganizedByYearAndMonth = false; + + private string|null $customTemplate = null; + + private bool $isDryRun = false; + + private bool $allOrNothing = false; + + private bool $transactional = true; + + private string|null $connectionName = null; + + private string|null $entityManagerName = null; + + private bool $checkDbPlatform = true; + + private MetadataStorageConfiguration|null $metadataStorageConfiguration = null; + + private bool $frozen = false; + + public function freeze(): void + { + $this->frozen = true; + } + + private function assertNotFrozen(): void + { + if ($this->frozen) { + throw FrozenConfiguration::new(); + } + } + + public function setMetadataStorageConfiguration(MetadataStorageConfiguration $metadataStorageConfiguration): void + { + $this->assertNotFrozen(); + $this->metadataStorageConfiguration = $metadataStorageConfiguration; + } + + /** @return string[] */ + public function getMigrationClasses(): array + { + return $this->migrationClasses; + } + + public function addMigrationClass(string $className): void + { + $this->assertNotFrozen(); + $this->migrationClasses[] = $className; + } + + public function getMetadataStorageConfiguration(): MetadataStorageConfiguration|null + { + return $this->metadataStorageConfiguration; + } + + public function addMigrationsDirectory(string $namespace, string $path): void + { + $this->assertNotFrozen(); + $this->migrationsDirectories[$namespace] = $path; + } + + /** @return array */ + public function getMigrationDirectories(): array + { + return $this->migrationsDirectories; + } + + public function getConnectionName(): string|null + { + return $this->connectionName; + } + + public function setConnectionName(string|null $connectionName): void + { + $this->assertNotFrozen(); + $this->connectionName = $connectionName; + } + + public function getEntityManagerName(): string|null + { + return $this->entityManagerName; + } + + public function setEntityManagerName(string|null $entityManagerName): void + { + $this->assertNotFrozen(); + $this->entityManagerName = $entityManagerName; + } + + public function setCustomTemplate(string|null $customTemplate): void + { + $this->assertNotFrozen(); + $this->customTemplate = $customTemplate; + } + + public function getCustomTemplate(): string|null + { + return $this->customTemplate; + } + + public function areMigrationsOrganizedByYear(): bool + { + return $this->migrationsAreOrganizedByYear; + } + + /** @throws MigrationException */ + public function setMigrationsAreOrganizedByYear( + bool $migrationsAreOrganizedByYear = true, + ): void { + $this->assertNotFrozen(); + $this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYear; + } + + /** @throws MigrationException */ + public function setMigrationsAreOrganizedByYearAndMonth( + bool $migrationsAreOrganizedByYearAndMonth = true, + ): void { + $this->assertNotFrozen(); + $this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYearAndMonth; + $this->migrationsAreOrganizedByYearAndMonth = $migrationsAreOrganizedByYearAndMonth; + } + + public function areMigrationsOrganizedByYearAndMonth(): bool + { + return $this->migrationsAreOrganizedByYearAndMonth; + } + + public function setIsDryRun(bool $isDryRun): void + { + $this->assertNotFrozen(); + $this->isDryRun = $isDryRun; + } + + public function isDryRun(): bool + { + return $this->isDryRun; + } + + public function setAllOrNothing(bool $allOrNothing): void + { + $this->assertNotFrozen(); + $this->allOrNothing = $allOrNothing; + } + + public function isAllOrNothing(): bool + { + return $this->allOrNothing; + } + + public function setTransactional(bool $transactional): void + { + $this->assertNotFrozen(); + $this->transactional = $transactional; + } + + public function isTransactional(): bool + { + return $this->transactional; + } + + public function setCheckDatabasePlatform(bool $checkDbPlatform): void + { + $this->checkDbPlatform = $checkDbPlatform; + } + + public function isDatabasePlatformChecked(): bool + { + return $this->checkDbPlatform; + } + + public function setMigrationOrganization(string $migrationOrganization): void + { + $this->assertNotFrozen(); + + match (strtolower($migrationOrganization)) { + self::VERSIONS_ORGANIZATION_NONE => $this->setMigrationsAreOrganizedByYearAndMonth(false), + self::VERSIONS_ORGANIZATION_BY_YEAR => $this->setMigrationsAreOrganizedByYear(), + self::VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH => $this->setMigrationsAreOrganizedByYearAndMonth(), + default => throw UnknownConfigurationValue::new('organize_migrations', $migrationOrganization), + }; + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Connection/ConfigurationFile.php b/vendor/doctrine/migrations/src/Configuration/Connection/ConfigurationFile.php new file mode 100644 index 0000000..a160216 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Connection/ConfigurationFile.php @@ -0,0 +1,51 @@ +filename)) { + throw FileNotFound::new($this->filename); + } + + $params = include $this->filename; + + if ($params instanceof Connection) { + return $params; + } + + if ($params instanceof ConnectionLoader) { + return $params->getConnection(); + } + + if (is_array($params)) { + return DriverManager::getConnection($params); + } + + throw InvalidConfiguration::invalidArrayConfiguration(); + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Connection/ConnectionLoader.php b/vendor/doctrine/migrations/src/Configuration/Connection/ConnectionLoader.php new file mode 100644 index 0000000..bf9e3f2 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Connection/ConnectionLoader.php @@ -0,0 +1,23 @@ +registry = $registry; + $that->defaultConnectionName = $connectionName; + + return $that; + } + + private function __construct() + { + } + + public function getConnection(string|null $name = null): Connection + { + $connection = $this->registry->getConnection($name ?? $this->defaultConnectionName); + if (! $connection instanceof Connection) { + throw InvalidConfiguration::invalidConnectionType($connection); + } + + return $connection; + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Connection/Exception/ConnectionNotSpecified.php b/vendor/doctrine/migrations/src/Configuration/Connection/Exception/ConnectionNotSpecified.php new file mode 100644 index 0000000..e3b9501 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Connection/Exception/ConnectionNotSpecified.php @@ -0,0 +1,17 @@ +connection; + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/EntityManager/ConfigurationFile.php b/vendor/doctrine/migrations/src/Configuration/EntityManager/ConfigurationFile.php new file mode 100644 index 0000000..9cf4025 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/EntityManager/ConfigurationFile.php @@ -0,0 +1,51 @@ +filename)) { + throw FileNotFound::new($this->filename); + } + + $params = include $this->filename; + + if ($params instanceof EntityManagerInterface) { + return $params; + } + + if ($params instanceof EntityManagerLoader) { + return $params->getEntityManager(); + } + + throw InvalidConfiguration::invalidArrayConfiguration(); + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/EntityManager/EntityManagerLoader.php b/vendor/doctrine/migrations/src/Configuration/EntityManager/EntityManagerLoader.php new file mode 100644 index 0000000..2982c9a --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/EntityManager/EntityManagerLoader.php @@ -0,0 +1,18 @@ +entityManager; + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/EntityManager/ManagerRegistryEntityManager.php b/vendor/doctrine/migrations/src/Configuration/EntityManager/ManagerRegistryEntityManager.php new file mode 100644 index 0000000..3807894 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/EntityManager/ManagerRegistryEntityManager.php @@ -0,0 +1,41 @@ +registry = $registry; + $that->defaultManagerName = $managerName; + + return $that; + } + + private function __construct() + { + } + + public function getEntityManager(string|null $name = null): EntityManagerInterface + { + $managerName = $name ?? $this->defaultManagerName; + + $em = $this->registry->getManager($managerName); + if (! $em instanceof EntityManagerInterface) { + throw InvalidConfiguration::invalidManagerType($em); + } + + return $em; + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Exception/ConfigurationException.php b/vendor/doctrine/migrations/src/Configuration/Exception/ConfigurationException.php new file mode 100644 index 0000000..8db929c --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Exception/ConfigurationException.php @@ -0,0 +1,11 @@ + $configurations */ + public function __construct(private readonly array $configurations) + { + } + + public function getConfiguration(): Configuration + { + $configMap = [ + 'migrations_paths' => static function ($paths, Configuration $configuration): void { + foreach ($paths as $namespace => $path) { + $configuration->addMigrationsDirectory($namespace, $path); + } + }, + 'migrations' => static function ($migrations, Configuration $configuration): void { + foreach ($migrations as $className) { + $configuration->addMigrationClass($className); + } + }, + + 'connection' => 'setConnectionName', + 'em' => 'setEntityManagerName', + + 'table_storage' => [ + 'table_name' => 'setTableName', + 'version_column_name' => 'setVersionColumnName', + 'version_column_length' => static function ($value, TableMetadataStorageConfiguration $configuration): void { + $configuration->setVersionColumnLength((int) $value); + }, + 'executed_at_column_name' => 'setExecutedAtColumnName', + 'execution_time_column_name' => 'setExecutionTimeColumnName', + ], + + 'organize_migrations' => 'setMigrationOrganization', + 'custom_template' => 'setCustomTemplate', + 'all_or_nothing' => static function ($value, Configuration $configuration): void { + $configuration->setAllOrNothing(is_bool($value) ? $value : BooleanStringFormatter::toBoolean($value, false)); + }, + 'transactional' => static function ($value, Configuration $configuration): void { + $configuration->setTransactional(is_bool($value) ? $value : BooleanStringFormatter::toBoolean($value, true)); + }, + 'check_database_platform' => static function ($value, Configuration $configuration): void { + $configuration->setCheckDatabasePlatform(is_bool($value) ? $value : BooleanStringFormatter::toBoolean($value, false)); + }, + ]; + + $object = new Configuration(); + self::applyConfigs($configMap, $object, $this->configurations); + + if ($object->getMetadataStorageConfiguration() === null) { + $object->setMetadataStorageConfiguration(new TableMetadataStorageConfiguration()); + } + + return $object; + } + + /** + * @param mixed[] $configMap + * @param array $data + */ + private static function applyConfigs(array $configMap, Configuration|TableMetadataStorageConfiguration $object, array $data): void + { + foreach ($data as $configurationKey => $configurationValue) { + if (! isset($configMap[$configurationKey])) { + throw InvalidConfigurationKey::new((string) $configurationKey); + } + + if (is_array($configMap[$configurationKey])) { + if ($configurationKey !== 'table_storage') { + throw InvalidConfigurationKey::new((string) $configurationKey); + } + + $storageConfig = new TableMetadataStorageConfiguration(); + assert($object instanceof Configuration); + $object->setMetadataStorageConfiguration($storageConfig); + self::applyConfigs($configMap[$configurationKey], $storageConfig, $configurationValue); + } else { + $callable = $configMap[$configurationKey] instanceof Closure + ? $configMap[$configurationKey] + : [$object, $configMap[$configurationKey]]; + assert(is_callable($callable)); + call_user_func( + $callable, + $configurationValue, + $object, + $data, + ); + } + } + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationFile.php b/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationFile.php new file mode 100644 index 0000000..3ed3ad8 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationFile.php @@ -0,0 +1,35 @@ +file = $file; + } + + /** + * @param array $directories + * + * @return array + */ + final protected function getDirectoriesRelativeToFile(array $directories, string $file): array + { + foreach ($directories as $ns => $dir) { + $path = realpath(dirname($file) . '/' . $dir); + + $directories[$ns] = $path !== false ? $path : $dir; + } + + return $directories; + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationFileWithFallback.php b/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationFileWithFallback.php new file mode 100644 index 0000000..62216c1 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationFileWithFallback.php @@ -0,0 +1,61 @@ +file !== null) { + return $this->loadConfiguration($this->file); + } + + /** + * If no config has been provided, look for default config file in the path. + */ + $defaultFiles = [ + 'migrations.xml', + 'migrations.yml', + 'migrations.yaml', + 'migrations.json', + 'migrations.php', + ]; + + foreach ($defaultFiles as $file) { + if ($this->configurationFileExists($file)) { + return $this->loadConfiguration($file); + } + } + + throw MissingConfigurationFile::new(); + } + + private function configurationFileExists(string $config): bool + { + return file_exists($config); + } + + /** @throws FileTypeNotSupported */ + private function loadConfiguration(string $file): Configuration + { + return (new FormattedFile($file))->getConfiguration(); + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationLoader.php b/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationLoader.php new file mode 100644 index 0000000..cd84a43 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/ConfigurationLoader.php @@ -0,0 +1,12 @@ +configurations; + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/FormattedFile.php b/vendor/doctrine/migrations/src/Configuration/Migration/FormattedFile.php new file mode 100644 index 0000000..45851e8 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/FormattedFile.php @@ -0,0 +1,45 @@ +loaders = [ + 'json' => static fn ($file): ConfigurationLoader => new JsonFile($file), + 'php' => static fn ($file): ConfigurationLoader => new PhpFile($file), + 'xml' => static fn ($file): ConfigurationLoader => new XmlFile($file), + 'yaml' => static fn ($file): ConfigurationLoader => new YamlFile($file), + 'yml' => static fn ($file): ConfigurationLoader => new YamlFile($file), + ]; + } + + public function getConfiguration(): Configuration + { + if (count($this->loaders) === 0) { + $this->setDefaultLoaders(); + } + + $extension = pathinfo($this->file, PATHINFO_EXTENSION); + if (! isset($this->loaders[$extension])) { + throw InvalidConfigurationFormat::new($this->file); + } + + return $this->loaders[$extension]($this->file)->getConfiguration(); + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/JsonFile.php b/vendor/doctrine/migrations/src/Configuration/Migration/JsonFile.php new file mode 100644 index 0000000..13e1ca0 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/JsonFile.php @@ -0,0 +1,46 @@ +file)) { + throw FileNotFound::new($this->file); + } + + $contents = file_get_contents($this->file); + + assert($contents !== false); + + $config = json_decode($contents, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw JsonNotValid::new(); + } + + if (isset($config['migrations_paths'])) { + $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( + $config['migrations_paths'], + $this->file, + ); + } + + return (new ConfigurationArray($config))->getConfiguration(); + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/PhpFile.php b/vendor/doctrine/migrations/src/Configuration/Migration/PhpFile.php new file mode 100644 index 0000000..d6f2888 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/PhpFile.php @@ -0,0 +1,37 @@ +file)) { + throw FileNotFound::new($this->file); + } + + $config = require $this->file; + if ($config instanceof Configuration) { + return $config; + } + + assert(is_array($config)); + if (isset($config['migrations_paths'])) { + $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( + $config['migrations_paths'], + $this->file, + ); + } + + return (new ConfigurationArray($config))->getConfiguration(); + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/XML/configuration.xsd b/vendor/doctrine/migrations/src/Configuration/Migration/XML/configuration.xsd new file mode 100644 index 0000000..c51d478 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/XML/configuration.xsd @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/XmlFile.php b/vendor/doctrine/migrations/src/Configuration/Migration/XmlFile.php new file mode 100644 index 0000000..b2ee835 --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/XmlFile.php @@ -0,0 +1,129 @@ +file)) { + throw FileNotFound::new($this->file); + } + + $this->validateXml($this->file); + + $rawXML = file_get_contents($this->file); + assert($rawXML !== false); + + $root = simplexml_load_string($rawXML, SimpleXMLElement::class, LIBXML_NOCDATA); + assert($root !== false); + + $config = $this->extractParameters($root, true); + + if (isset($config['all_or_nothing'])) { + $config['all_or_nothing'] = BooleanStringFormatter::toBoolean( + $config['all_or_nothing'], + false, + ); + } + + if (isset($config['transactional'])) { + $config['transactional'] = BooleanStringFormatter::toBoolean( + $config['transactional'], + true, + ); + } + + if (isset($config['migrations_paths'])) { + $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( + $config['migrations_paths'], + $this->file, + ); + } + + return (new ConfigurationArray($config))->getConfiguration(); + } + + /** @return mixed[] */ + private function extractParameters(SimpleXMLElement $root, bool $loopOverNodes): array + { + $config = []; + + $itemsToCheck = $loopOverNodes ? $root->children() : $root->attributes(); + + if (! ($itemsToCheck instanceof SimpleXMLElement)) { + return $config; + } + + foreach ($itemsToCheck as $node) { + $nodeName = strtr($node->getName(), '-', '_'); + if ($nodeName === 'migrations_paths') { + $config['migrations_paths'] = []; + foreach ($node->path as $pathNode) { + $config['migrations_paths'][(string) $pathNode['namespace']] = (string) $pathNode; + } + } elseif ($nodeName === 'storage' && $node->{'table-storage'} instanceof SimpleXMLElement) { + $config['table_storage'] = $this->extractParameters($node->{'table-storage'}, false); + } elseif ($nodeName === 'migrations') { + $config['migrations'] = $this->extractMigrations($node); + } else { + $config[$nodeName] = (string) $node; + } + } + + return $config; + } + + /** @return list */ + private function extractMigrations(SimpleXMLElement $node): array + { + $migrations = []; + foreach ($node->migration as $pathNode) { + $migrations[] = (string) $pathNode; + } + + return $migrations; + } + + private function validateXml(string $file): void + { + try { + libxml_use_internal_errors(true); + + $xml = new DOMDocument(); + + if ($xml->load($file) === false) { + throw XmlNotValid::malformed(); + } + + $xsdPath = __DIR__ . DIRECTORY_SEPARATOR . 'XML' . DIRECTORY_SEPARATOR . 'configuration.xsd'; + + if ($xml->schemaValidate($xsdPath) === false) { + throw XmlNotValid::failedValidation(); + } + } finally { + libxml_clear_errors(); + libxml_use_internal_errors(false); + } + } +} diff --git a/vendor/doctrine/migrations/src/Configuration/Migration/YamlFile.php b/vendor/doctrine/migrations/src/Configuration/Migration/YamlFile.php new file mode 100644 index 0000000..f502bbe --- /dev/null +++ b/vendor/doctrine/migrations/src/Configuration/Migration/YamlFile.php @@ -0,0 +1,55 @@ +file)) { + throw FileNotFound::new($this->file); + } + + $content = file_get_contents($this->file); + + assert($content !== false); + + try { + $config = Yaml::parse($content); + } catch (ParseException) { + throw YamlNotValid::malformed(); + } + + if (! is_array($config)) { + throw YamlNotValid::invalid(); + } + + if (isset($config['migrations_paths'])) { + $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( + $config['migrations_paths'], + $this->file, + ); + } + + return (new ConfigurationArray($config))->getConfiguration(); + } +} diff --git a/vendor/doctrine/migrations/src/DbalMigrator.php b/vendor/doctrine/migrations/src/DbalMigrator.php new file mode 100644 index 0000000..ad8e09a --- /dev/null +++ b/vendor/doctrine/migrations/src/DbalMigrator.php @@ -0,0 +1,140 @@ + */ + private function executeMigrations( + MigrationPlanList $migrationsPlan, + MigratorConfiguration $migratorConfiguration, + ): array { + $allOrNothing = $migratorConfiguration->isAllOrNothing(); + + if ($allOrNothing) { + $this->assertAllMigrationsAreTransactional($migrationsPlan); + $this->connection->beginTransaction(); + } + + try { + $this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrating, $migrationsPlan, $migratorConfiguration); + + $sql = $this->executePlan($migrationsPlan, $migratorConfiguration); + + $this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrated, $migrationsPlan, $migratorConfiguration); + } catch (Throwable $e) { + if ($allOrNothing) { + TransactionHelper::rollbackIfInTransaction($this->connection); + } + + throw $e; + } + + if ($allOrNothing) { + TransactionHelper::commitIfInTransaction($this->connection); + } + + return $sql; + } + + private function assertAllMigrationsAreTransactional(MigrationPlanList $migrationsPlan): void + { + foreach ($migrationsPlan->getItems() as $plan) { + if (! $plan->getMigration()->isTransactional()) { + throw MigrationConfigurationConflict::migrationIsNotTransactional($plan->getMigration()); + } + } + } + + /** @return array */ + private function executePlan(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array + { + $sql = []; + + foreach ($migrationsPlan->getItems() as $plan) { + $versionExecutionResult = $this->executor->execute($plan, $migratorConfiguration); + + // capture the to Schema for the migration so we have the ability to use + // it as the from Schema for the next migration when we are running a dry run + // $toSchema may be null in the case of skipped migrations + if (! $versionExecutionResult->isSkipped()) { + $migratorConfiguration->setFromSchema($versionExecutionResult->getToSchema()); + } + + $sql[(string) $plan->getVersion()] = $versionExecutionResult->getSql(); + } + + return $sql; + } + + /** @param array $sql */ + private function endMigrations( + StopwatchEvent $stopwatchEvent, + MigrationPlanList $migrationsPlan, + array $sql, + ): void { + $stopwatchEvent->stop(); + + $this->logger->notice( + 'finished in {duration}ms, used {memory} memory, {migrations_count} migrations executed, {queries_count} sql queries', + [ + 'duration' => $stopwatchEvent->getDuration(), + 'memory' => BytesFormatter::formatBytes($stopwatchEvent->getMemory()), + 'migrations_count' => count($migrationsPlan), + 'queries_count' => count($sql, COUNT_RECURSIVE) - count($sql), + ], + ); + } + + /** + * {@inheritDoc} + */ + public function migrate(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array + { + if (count($migrationsPlan) === 0) { + $this->logger->notice('No migrations to execute.'); + + return []; + } + + $stopwatchEvent = $this->stopwatch->start('migrate'); + + $sql = $this->executeMigrations($migrationsPlan, $migratorConfiguration); + + $this->endMigrations($stopwatchEvent, $migrationsPlan, $sql); + + return $sql; + } +} diff --git a/vendor/doctrine/migrations/src/DependencyFactory.php b/vendor/doctrine/migrations/src/DependencyFactory.php new file mode 100644 index 0000000..fece1f5 --- /dev/null +++ b/vendor/doctrine/migrations/src/DependencyFactory.php @@ -0,0 +1,465 @@ + */ + private array $inResolution = []; + + private Configuration|null $configuration = null; + + /** @var object[]|callable[] */ + private array $dependencies = []; + + private Connection|null $connection = null; + private EntityManagerInterface|null $em = null; + private EventManager|null $eventManager = null; + private bool $frozen = false; + private ConfigurationLoader $configurationLoader; + private ConnectionLoader $connectionLoader; + private EntityManagerLoader|null $emLoader = null; + + /** @var callable[] */ + private array $factories = []; + + public static function fromConnection( + ConfigurationLoader $configurationLoader, + ConnectionLoader $connectionLoader, + LoggerInterface|null $logger = null, + ): self { + $dependencyFactory = new self($logger); + $dependencyFactory->configurationLoader = $configurationLoader; + $dependencyFactory->connectionLoader = $connectionLoader; + + return $dependencyFactory; + } + + public static function fromEntityManager( + ConfigurationLoader $configurationLoader, + EntityManagerLoader $emLoader, + LoggerInterface|null $logger = null, + ): self { + $dependencyFactory = new self($logger); + $dependencyFactory->configurationLoader = $configurationLoader; + $dependencyFactory->emLoader = $emLoader; + + return $dependencyFactory; + } + + private function __construct(LoggerInterface|null $logger) + { + if ($logger === null) { + return; + } + + $this->setDefinition(LoggerInterface::class, static fn (): LoggerInterface => $logger); + } + + public function isFrozen(): bool + { + return $this->frozen; + } + + public function freeze(): void + { + $this->frozen = true; + } + + private function assertNotFrozen(): void + { + if ($this->frozen) { + throw FrozenDependencies::new(); + } + } + + public function hasEntityManager(): bool + { + return $this->emLoader !== null; + } + + public function setConfigurationLoader(ConfigurationLoader $configurationLoader): void + { + $this->assertNotFrozen(); + $this->configurationLoader = $configurationLoader; + } + + public function getConfiguration(): Configuration + { + if ($this->configuration === null) { + $this->configuration = $this->configurationLoader->getConfiguration(); + $this->frozen = true; + } + + return $this->configuration; + } + + public function getConnection(): Connection + { + if ($this->connection === null) { + $this->connection = $this->hasEntityManager() + ? $this->getEntityManager()->getConnection() + : $this->connectionLoader->getConnection($this->getConfiguration()->getConnectionName()); + $this->frozen = true; + } + + return $this->connection; + } + + public function getEntityManager(): EntityManagerInterface + { + if ($this->em === null) { + if ($this->emLoader === null) { + throw MissingDependency::noEntityManager(); + } + + $this->em = $this->emLoader->getEntityManager($this->getConfiguration()->getEntityManagerName()); + $this->frozen = true; + } + + return $this->em; + } + + public function getVersionComparator(): Comparator + { + return $this->getDependency(Comparator::class, static fn (): AlphabeticalComparator => new AlphabeticalComparator()); + } + + public function getLogger(): LoggerInterface + { + return $this->getDependency(LoggerInterface::class, static fn (): LoggerInterface => new NullLogger()); + } + + public function getEventDispatcher(): EventDispatcher + { + return $this->getDependency(EventDispatcher::class, fn (): EventDispatcher => new EventDispatcher( + $this->getConnection(), + $this->getEventManager(), + )); + } + + public function getClassNameGenerator(): ClassNameGenerator + { + return $this->getDependency(ClassNameGenerator::class, static fn (): ClassNameGenerator => new ClassNameGenerator()); + } + + public function getSchemaDumper(): SchemaDumper + { + return $this->getDependency(SchemaDumper::class, function (): SchemaDumper { + $excludedTables = []; + + $metadataConfig = $this->getConfiguration()->getMetadataStorageConfiguration(); + if ($metadataConfig instanceof TableMetadataStorageConfiguration) { + $excludedTables[] = sprintf('/^%s$/', preg_quote($metadataConfig->getTableName(), '/')); + } + + return new SchemaDumper( + $this->getConnection()->getDatabasePlatform(), + $this->getConnection()->createSchemaManager(), + $this->getMigrationGenerator(), + $this->getMigrationSqlGenerator(), + $excludedTables, + ); + }); + } + + private function getEmptySchemaProvider(): SchemaProvider + { + return $this->getDependency(EmptySchemaProvider::class, fn (): SchemaProvider => new EmptySchemaProvider($this->getConnection()->createSchemaManager())); + } + + public function hasSchemaProvider(): bool + { + try { + $this->getSchemaProvider(); + } catch (MissingDependency) { + return false; + } + + return true; + } + + public function getSchemaProvider(): SchemaProvider + { + return $this->getDependency(SchemaProvider::class, function (): SchemaProvider { + if ($this->hasEntityManager()) { + return new OrmSchemaProvider($this->getEntityManager()); + } + + throw MissingDependency::noSchemaProvider(); + }); + } + + public function getDiffGenerator(): DiffGenerator + { + return $this->getDependency(DiffGenerator::class, fn (): DiffGenerator => new DiffGenerator( + $this->getConnection()->getConfiguration(), + $this->getConnection()->createSchemaManager(), + $this->getSchemaProvider(), + $this->getConnection()->getDatabasePlatform(), + $this->getMigrationGenerator(), + $this->getMigrationSqlGenerator(), + $this->getEmptySchemaProvider(), + )); + } + + public function getSchemaDiffProvider(): SchemaDiffProvider + { + return $this->getDependency(SchemaDiffProvider::class, fn (): LazySchemaDiffProvider => new LazySchemaDiffProvider( + new DBALSchemaDiffProvider( + $this->getConnection()->createSchemaManager(), + $this->getConnection()->getDatabasePlatform(), + ), + )); + } + + private function getFileBuilder(): FileBuilder + { + return $this->getDependency(FileBuilder::class, static fn (): FileBuilder => new ConcatenationFileBuilder()); + } + + private function getParameterFormatter(): ParameterFormatter + { + return $this->getDependency(ParameterFormatter::class, fn (): ParameterFormatter => new InlineParameterFormatter($this->getConnection())); + } + + public function getMigrationsFinder(): MigrationFinder + { + return $this->getDependency(MigrationFinder::class, function (): MigrationFinder { + $configs = $this->getConfiguration(); + $needsRecursiveFinder = $configs->areMigrationsOrganizedByYear() || $configs->areMigrationsOrganizedByYearAndMonth(); + + return $needsRecursiveFinder ? new RecursiveRegexFinder() : new GlobFinder(); + }); + } + + public function getMigrationRepository(): MigrationsRepository + { + return $this->getDependency(MigrationsRepository::class, fn (): MigrationsRepository => new FilesystemMigrationsRepository( + $this->getConfiguration()->getMigrationClasses(), + $this->getConfiguration()->getMigrationDirectories(), + $this->getMigrationsFinder(), + $this->getMigrationFactory(), + )); + } + + public function getMigrationFactory(): MigrationFactory + { + return $this->getDependency(MigrationFactory::class, fn (): MigrationFactory => new DbalMigrationFactory($this->getConnection(), $this->getLogger())); + } + + public function setService(string $id, object|callable $service): void + { + $this->assertNotFrozen(); + $this->dependencies[$id] = $service; + } + + public function getMetadataStorage(): MetadataStorage + { + return $this->getDependency(MetadataStorage::class, fn (): MetadataStorage => new TableMetadataStorage( + $this->getConnection(), + $this->getVersionComparator(), + $this->getConfiguration()->getMetadataStorageConfiguration(), + $this->getMigrationRepository(), + )); + } + + private function getVersionExecutor(): Executor + { + return $this->getDependency(Executor::class, fn (): Executor => new DbalExecutor( + $this->getMetadataStorage(), + $this->getEventDispatcher(), + $this->getConnection(), + $this->getSchemaDiffProvider(), + $this->getLogger(), + $this->getParameterFormatter(), + $this->getStopwatch(), + )); + } + + public function getQueryWriter(): QueryWriter + { + return $this->getDependency(QueryWriter::class, fn (): QueryWriter => new FileQueryWriter( + $this->getFileBuilder(), + $this->getLogger(), + )); + } + + public function getVersionAliasResolver(): AliasResolver + { + return $this->getDependency(AliasResolver::class, fn (): AliasResolver => new DefaultAliasResolver( + $this->getMigrationPlanCalculator(), + $this->getMetadataStorage(), + $this->getMigrationStatusCalculator(), + )); + } + + public function getMigrationStatusCalculator(): MigrationStatusCalculator + { + return $this->getDependency(MigrationStatusCalculator::class, fn (): MigrationStatusCalculator => new CurrentMigrationStatusCalculator( + $this->getMigrationPlanCalculator(), + $this->getMetadataStorage(), + )); + } + + public function getMigrationPlanCalculator(): MigrationPlanCalculator + { + return $this->getDependency(MigrationPlanCalculator::class, fn (): MigrationPlanCalculator => new SortedMigrationPlanCalculator( + $this->getMigrationRepository(), + $this->getMetadataStorage(), + $this->getVersionComparator(), + )); + } + + public function getMigrationGenerator(): Generator + { + return $this->getDependency(Generator::class, fn (): Generator => new Generator($this->getConfiguration())); + } + + public function getMigrationSqlGenerator(): SqlGenerator + { + return $this->getDependency(SqlGenerator::class, fn (): SqlGenerator => new SqlGenerator( + $this->getConfiguration(), + $this->getConnection()->getDatabasePlatform(), + )); + } + + public function getConsoleInputMigratorConfigurationFactory(): MigratorConfigurationFactory + { + return $this->getDependency(MigratorConfigurationFactory::class, fn (): MigratorConfigurationFactory => new ConsoleInputMigratorConfigurationFactory( + $this->getConfiguration(), + )); + } + + public function getMigrationStatusInfosHelper(): MigrationStatusInfosHelper + { + return $this->getDependency(MigrationStatusInfosHelper::class, fn (): MigrationStatusInfosHelper => new MigrationStatusInfosHelper( + $this->getConfiguration(), + $this->getConnection(), + $this->getVersionAliasResolver(), + $this->getMigrationPlanCalculator(), + $this->getMigrationStatusCalculator(), + $this->getMetadataStorage(), + )); + } + + public function getMigrator(): Migrator + { + return $this->getDependency(Migrator::class, fn (): Migrator => new DbalMigrator( + $this->getConnection(), + $this->getEventDispatcher(), + $this->getVersionExecutor(), + $this->getLogger(), + $this->getStopwatch(), + )); + } + + public function getStopwatch(): Stopwatch + { + return $this->getDependency(Stopwatch::class, static fn (): Stopwatch => new Stopwatch(true)); + } + + public function getRollup(): Rollup + { + return $this->getDependency(Rollup::class, fn (): Rollup => new Rollup( + $this->getMetadataStorage(), + $this->getMigrationRepository(), + )); + } + + private function getDependency(string $id, callable $callback): mixed + { + if (! isset($this->inResolution[$id]) && array_key_exists($id, $this->factories) && ! array_key_exists($id, $this->dependencies)) { + $this->inResolution[$id] = true; + $this->dependencies[$id] = call_user_func($this->factories[$id], $this); + unset($this->inResolution); + } + + if (! array_key_exists($id, $this->dependencies)) { + $this->dependencies[$id] = $callback(); + } + + return $this->dependencies[$id]; + } + + public function setDefinition(string $id, callable $service): void + { + $this->assertNotFrozen(); + $this->factories[$id] = $service; + } + + private function getEventManager(): EventManager + { + if ($this->eventManager !== null) { + return $this->eventManager; + } + + if ($this->hasEntityManager()) { + return $this->eventManager = $this->getEntityManager()->getEventManager(); + } + + if (method_exists(Connection::class, 'getEventManager')) { + // DBAL < 4 + return $this->eventManager = $this->getConnection()->getEventManager(); + } + + return $this->eventManager = new EventManager(); + } +} diff --git a/vendor/doctrine/migrations/src/Event/Listeners/AutoCommitListener.php b/vendor/doctrine/migrations/src/Event/Listeners/AutoCommitListener.php new file mode 100644 index 0000000..a760acf --- /dev/null +++ b/vendor/doctrine/migrations/src/Event/Listeners/AutoCommitListener.php @@ -0,0 +1,37 @@ +getConnection(); + $conf = $args->getMigratorConfiguration(); + + if ($conf->isDryRun() || $conn->isAutoCommit()) { + return; + } + + TransactionHelper::commitIfInTransaction($conn); + } + + /** {@inheritDoc} */ + public function getSubscribedEvents() + { + return [Events::onMigrationsMigrated]; + } +} diff --git a/vendor/doctrine/migrations/src/Event/MigrationsEventArgs.php b/vendor/doctrine/migrations/src/Event/MigrationsEventArgs.php new file mode 100644 index 0000000..9ef8dee --- /dev/null +++ b/vendor/doctrine/migrations/src/Event/MigrationsEventArgs.php @@ -0,0 +1,38 @@ +connection; + } + + public function getPlan(): MigrationPlanList + { + return $this->plan; + } + + public function getMigratorConfiguration(): MigratorConfiguration + { + return $this->migratorConfiguration; + } +} diff --git a/vendor/doctrine/migrations/src/Event/MigrationsVersionEventArgs.php b/vendor/doctrine/migrations/src/Event/MigrationsVersionEventArgs.php new file mode 100644 index 0000000..f309240 --- /dev/null +++ b/vendor/doctrine/migrations/src/Event/MigrationsVersionEventArgs.php @@ -0,0 +1,38 @@ +connection; + } + + public function getPlan(): MigrationPlan + { + return $this->plan; + } + + public function getMigratorConfiguration(): MigratorConfiguration + { + return $this->migratorConfiguration; + } +} diff --git a/vendor/doctrine/migrations/src/EventDispatcher.php b/vendor/doctrine/migrations/src/EventDispatcher.php new file mode 100644 index 0000000..a71e0c7 --- /dev/null +++ b/vendor/doctrine/migrations/src/EventDispatcher.php @@ -0,0 +1,73 @@ +createMigrationEventArgs($migrationsPlan, $migratorConfiguration); + + $this->dispatchEvent($eventName, $event); + } + + public function dispatchVersionEvent( + string $eventName, + MigrationPlan $plan, + MigratorConfiguration $migratorConfiguration, + ): void { + $event = $this->createMigrationsVersionEventArgs( + $plan, + $migratorConfiguration, + ); + + $this->dispatchEvent($eventName, $event); + } + + private function dispatchEvent(string $eventName, EventArgs|null $args = null): void + { + $this->eventManager->dispatchEvent($eventName, $args); + } + + private function createMigrationEventArgs( + MigrationPlanList $migrationsPlan, + MigratorConfiguration $migratorConfiguration, + ): MigrationsEventArgs { + return new MigrationsEventArgs($this->connection, $migrationsPlan, $migratorConfiguration); + } + + private function createMigrationsVersionEventArgs( + MigrationPlan $plan, + MigratorConfiguration $migratorConfiguration, + ): MigrationsVersionEventArgs { + return new MigrationsVersionEventArgs( + $this->connection, + $plan, + $migratorConfiguration, + ); + } +} diff --git a/vendor/doctrine/migrations/src/Events.php b/vendor/doctrine/migrations/src/Events.php new file mode 100644 index 0000000..0d2feb9 --- /dev/null +++ b/vendor/doctrine/migrations/src/Events.php @@ -0,0 +1,17 @@ + $queriesByVersion */ + public function write( + string $path, + string $direction, + array $queriesByVersion, + DateTimeInterface|null $now = null, + ): bool { + $now ??= new DateTimeImmutable(); + + $string = $this->migrationFileBuilder + ->buildMigrationFile($queriesByVersion, $direction, $now); + + $path = $this->buildMigrationFilePath($path, $now); + + $this->logger->info('Writing migration file to "{path}"', ['path' => $path]); + + return file_put_contents($path, $string) !== false; + } + + private function buildMigrationFilePath(string $path, DateTimeInterface $now): string + { + if (is_dir($path)) { + $path = realpath($path); + $path .= '/doctrine_migration_' . $now->format('YmdHis') . '.sql'; + } + + return $path; + } +} diff --git a/vendor/doctrine/migrations/src/FilesystemMigrationsRepository.php b/vendor/doctrine/migrations/src/FilesystemMigrationsRepository.php new file mode 100644 index 0000000..1d148fe --- /dev/null +++ b/vendor/doctrine/migrations/src/FilesystemMigrationsRepository.php @@ -0,0 +1,139 @@ + $migrationDirectories + */ + public function __construct( + array $classes, + private readonly array $migrationDirectories, + private readonly MigrationFinder $migrationFinder, + private readonly MigrationFactory $versionFactory, + ) { + $this->registerMigrations($classes); + } + + private function registerMigrationInstance(Version $version, AbstractMigration $migration): AvailableMigration + { + if (isset($this->migrations[(string) $version])) { + throw DuplicateMigrationVersion::new( + (string) $version, + (string) $version, + ); + } + + $this->migrations[(string) $version] = new AvailableMigration($version, $migration); + + return $this->migrations[(string) $version]; + } + + /** @throws MigrationException */ + public function registerMigration(string $migrationClassName): AvailableMigration + { + $this->ensureMigrationClassExists($migrationClassName); + + $version = new Version($migrationClassName); + $migration = $this->versionFactory->createVersion($migrationClassName); + + return $this->registerMigrationInstance($version, $migration); + } + + /** + * @param string[] $migrations + * + * @return AvailableMigration[] + */ + private function registerMigrations(array $migrations): array + { + $versions = []; + + foreach ($migrations as $class) { + $versions[] = $this->registerMigration($class); + } + + return $versions; + } + + public function hasMigration(string $version): bool + { + $this->loadMigrationsFromDirectories(); + + return isset($this->migrations[$version]); + } + + public function getMigration(Version $version): AvailableMigration + { + $this->loadMigrationsFromDirectories(); + + if (! isset($this->migrations[(string) $version])) { + throw MigrationClassNotFound::new((string) $version); + } + + return $this->migrations[(string) $version]; + } + + /** + * Returns a non-sorted set of migrations. + */ + public function getMigrations(): AvailableMigrationsSet + { + $this->loadMigrationsFromDirectories(); + + return new AvailableMigrationsSet($this->migrations); + } + + /** @throws MigrationException */ + private function ensureMigrationClassExists(string $class): void + { + if (! class_exists($class)) { + throw MigrationClassNotFound::new($class); + } + } + + private function loadMigrationsFromDirectories(): void + { + $migrationDirectories = $this->migrationDirectories; + + if ($this->migrationsLoaded) { + return; + } + + $this->migrationsLoaded = true; + + foreach ($migrationDirectories as $namespace => $path) { + $migrations = $this->migrationFinder->findMigrations( + $path, + $namespace, + ); + $this->registerMigrations($migrations); + } + } +} diff --git a/vendor/doctrine/migrations/src/Finder/Exception/FinderException.php b/vendor/doctrine/migrations/src/Finder/Exception/FinderException.php new file mode 100644 index 0000000..15da805 --- /dev/null +++ b/vendor/doctrine/migrations/src/Finder/Exception/FinderException.php @@ -0,0 +1,9 @@ +loadMigrationClasses($includedFiles, $namespace); + $versions = []; + foreach ($classes as $class) { + $versions[] = $class->getName(); + } + + return $versions; + } + + /** + * Look up all declared classes and find those classes contained + * in the given `$files` array. + * + * @param string[] $files The set of files that were `required` + * @param string|null $namespace If not null only classes in this namespace will be returned + * + * @return ReflectionClass[] the classes in `$files` + */ + protected function loadMigrationClasses(array $files, string|null $namespace = null): array + { + $classes = []; + foreach (get_declared_classes() as $class) { + $reflectionClass = new ReflectionClass($class); + + if (! in_array($reflectionClass->getFileName(), $files, true)) { + continue; + } + + if ($namespace !== null && ! $this->isReflectionClassInNamespace($reflectionClass, $namespace)) { + continue; + } + + $classes[] = $reflectionClass; + } + + return $classes; + } + + /** @param ReflectionClass $reflectionClass */ + private function isReflectionClassInNamespace(ReflectionClass $reflectionClass, string $namespace): bool + { + return strncmp($reflectionClass->getName(), $namespace . '\\', strlen($namespace) + 1) === 0; + } +} diff --git a/vendor/doctrine/migrations/src/Finder/GlobFinder.php b/vendor/doctrine/migrations/src/Finder/GlobFinder.php new file mode 100644 index 0000000..ba9780d --- /dev/null +++ b/vendor/doctrine/migrations/src/Finder/GlobFinder.php @@ -0,0 +1,29 @@ +getRealPath($directory); + + $files = glob(rtrim($dir, '/') . '/Version*.php'); + if ($files === false) { + $files = []; + } + + return $this->loadMigrations($files, $namespace); + } +} diff --git a/vendor/doctrine/migrations/src/Finder/MigrationFinder.php b/vendor/doctrine/migrations/src/Finder/MigrationFinder.php new file mode 100644 index 0000000..0d13b39 --- /dev/null +++ b/vendor/doctrine/migrations/src/Finder/MigrationFinder.php @@ -0,0 +1,19 @@ +pattern = $pattern ?? sprintf( + '#^.+\\%s[^\\%s]+\\.php$#i', + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + ); + } + + /** @return string[] */ + public function findMigrations(string $directory, string|null $namespace = null): array + { + $dir = $this->getRealPath($directory); + + return $this->loadMigrations( + $this->getMatches($this->createIterator($dir)), + $namespace, + ); + } + + private function createIterator(string $dir): RegexIterator + { + return new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS), + RecursiveIteratorIterator::LEAVES_ONLY, + ), + $this->getPattern(), + RegexIterator::GET_MATCH, + ); + } + + private function getPattern(): string + { + return $this->pattern; + } + + /** @return string[] */ + private function getMatches(RegexIterator $iteratorFilesMatch): array + { + $files = []; + foreach ($iteratorFilesMatch as $file) { + $files[] = $file[0]; + } + + return $files; + } +} diff --git a/vendor/doctrine/migrations/src/Generator/ClassNameGenerator.php b/vendor/doctrine/migrations/src/Generator/ClassNameGenerator.php new file mode 100644 index 0000000..22b6f0d --- /dev/null +++ b/vendor/doctrine/migrations/src/Generator/ClassNameGenerator.php @@ -0,0 +1,25 @@ +generateVersionNumber(); + } + + private function generateVersionNumber(): string + { + $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); + + return $now->format(self::VERSION_FORMAT); + } +} diff --git a/vendor/doctrine/migrations/src/Generator/ConcatenationFileBuilder.php b/vendor/doctrine/migrations/src/Generator/ConcatenationFileBuilder.php new file mode 100644 index 0000000..10ebc19 --- /dev/null +++ b/vendor/doctrine/migrations/src/Generator/ConcatenationFileBuilder.php @@ -0,0 +1,39 @@ + $queriesByVersion */ + public function buildMigrationFile( + array $queriesByVersion, + string $direction, + DateTimeInterface|null $now = null, + ): string { + $now ??= new DateTimeImmutable(); + $string = sprintf("-- Doctrine Migration File Generated on %s\n", $now->format('Y-m-d H:i:s')); + + foreach ($queriesByVersion as $version => $queries) { + $string .= "\n-- Version " . $version . "\n"; + + foreach ($queries as $query) { + $string .= $query->getStatement() . ";\n"; + } + } + + return $string; + } +} diff --git a/vendor/doctrine/migrations/src/Generator/DiffGenerator.php b/vendor/doctrine/migrations/src/Generator/DiffGenerator.php new file mode 100644 index 0000000..19b6f3d --- /dev/null +++ b/vendor/doctrine/migrations/src/Generator/DiffGenerator.php @@ -0,0 +1,140 @@ + $schemaManager */ + public function __construct( + private readonly DBALConfiguration $dbalConfiguration, + private readonly AbstractSchemaManager $schemaManager, + private readonly SchemaProvider $schemaProvider, + private readonly AbstractPlatform $platform, + private readonly Generator $migrationGenerator, + private readonly SqlGenerator $migrationSqlGenerator, + private readonly SchemaProvider $emptySchemaProvider, + ) { + } + + /** @throws NoChangesDetected */ + public function generate( + string $fqcn, + string|null $filterExpression, + bool $formatted = false, + int $lineLength = 120, + bool $checkDbPlatform = true, + bool $fromEmptySchema = false, + ): string { + if ($filterExpression !== null) { + $this->dbalConfiguration->setSchemaAssetsFilter( + static function ($assetName) use ($filterExpression) { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return preg_match($filterExpression, $assetName); + }, + ); + } + + $fromSchema = $fromEmptySchema + ? $this->createEmptySchema() + : $this->createFromSchema(); + + $toSchema = $this->createToSchema(); + + $comparator = $this->schemaManager->createComparator(); + + $upSql = $this->platform->getAlterSchemaSQL($comparator->compareSchemas($fromSchema, $toSchema)); + + $up = $this->migrationSqlGenerator->generate( + $upSql, + $formatted, + $lineLength, + $checkDbPlatform, + ); + + $downSql = $this->platform->getAlterSchemaSQL($comparator->compareSchemas($toSchema, $fromSchema)); + + $down = $this->migrationSqlGenerator->generate( + $downSql, + $formatted, + $lineLength, + $checkDbPlatform, + ); + + if ($up === '' && $down === '') { + throw NoChangesDetected::new(); + } + + return $this->migrationGenerator->generateMigration( + $fqcn, + $up, + $down, + ); + } + + private function createEmptySchema(): Schema + { + return $this->emptySchemaProvider->createSchema(); + } + + private function createFromSchema(): Schema + { + return $this->schemaManager->introspectSchema(); + } + + private function createToSchema(): Schema + { + $toSchema = $this->schemaProvider->createSchema(); + + $schemaAssetsFilter = $this->dbalConfiguration->getSchemaAssetsFilter(); + + if ($schemaAssetsFilter !== null) { + foreach ($toSchema->getTables() as $table) { + $tableName = $table->getName(); + + if ($schemaAssetsFilter($this->resolveTableName($tableName))) { + continue; + } + + $toSchema->dropTable($tableName); + } + } + + return $toSchema; + } + + /** + * Resolve a table name from its fully qualified name. The `$name` argument + * comes from Doctrine\DBAL\Schema\Table#getName which can sometimes return + * a namespaced name with the form `{namespace}.{tableName}`. This extracts + * the table name from that. + */ + private function resolveTableName(string $name): string + { + $pos = strpos($name, '.'); + + return $pos === false ? $name : substr($name, $pos + 1); + } +} diff --git a/vendor/doctrine/migrations/src/Generator/Exception/GeneratorException.php b/vendor/doctrine/migrations/src/Generator/Exception/GeneratorException.php new file mode 100644 index 0000000..5e90fbc --- /dev/null +++ b/vendor/doctrine/migrations/src/Generator/Exception/GeneratorException.php @@ -0,0 +1,11 @@ + $queriesByVersion */ + public function buildMigrationFile(array $queriesByVersion, string $direction, DateTimeInterface|null $now = null): string; +} diff --git a/vendor/doctrine/migrations/src/Generator/Generator.php b/vendor/doctrine/migrations/src/Generator/Generator.php new file mode 100644 index 0000000..7eb5edc --- /dev/null +++ b/vendor/doctrine/migrations/src/Generator/Generator.php @@ -0,0 +1,157 @@ +; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + + } +} + +TEMPLATE; + + private string|null $template = null; + + public function __construct(private readonly Configuration $configuration) + { + } + + public function generateMigration( + string $fqcn, + string|null $up = null, + string|null $down = null, + ): string { + $mch = []; + if (preg_match('~(.*)\\\\([^\\\\]+)~', $fqcn, $mch) !== 1) { + throw new InvalidArgumentException(sprintf('Invalid FQCN')); + } + + [$fqcn, $namespace, $className] = $mch; + + $dirs = $this->configuration->getMigrationDirectories(); + if (! isset($dirs[$namespace])) { + throw new InvalidArgumentException(sprintf('Path not defined for the namespace "%s"', $namespace)); + } + + $dir = $dirs[$namespace]; + + $replacements = [ + '' => $namespace, + '' => $className, + '' => $up !== null ? ' ' . implode("\n ", explode("\n", $up)) : null, + '' => $down !== null ? ' ' . implode("\n ", explode("\n", $down)) : null, + '' => $this->configuration->isTransactional() ? '' : <<<'METHOD' + + + public function isTransactional(): bool + { + return false; + } +METHOD + , + ]; + + $code = strtr($this->getTemplate(), $replacements); + $code = preg_replace('/^ +$/m', '', $code); + + $directoryHelper = new MigrationDirectoryHelper(); + $dir = $directoryHelper->getMigrationDirectory($this->configuration, $dir); + $path = $dir . '/' . $className . '.php'; + + file_put_contents($path, $code); + + return $path; + } + + private function getTemplate(): string + { + if ($this->template === null) { + $this->template = $this->loadCustomTemplate(); + + if ($this->template === null) { + $this->template = self::MIGRATION_TEMPLATE; + } + } + + return $this->template; + } + + /** @throws InvalidTemplateSpecified */ + private function loadCustomTemplate(): string|null + { + $customTemplate = $this->configuration->getCustomTemplate(); + + if ($customTemplate === null) { + return null; + } + + if (! is_file($customTemplate) || ! is_readable($customTemplate)) { + throw InvalidTemplateSpecified::notFoundOrNotReadable($customTemplate); + } + + $content = file_get_contents($customTemplate); + + if ($content === false) { + throw InvalidTemplateSpecified::notReadable($customTemplate); + } + + if (trim($content) === '') { + throw InvalidTemplateSpecified::empty($customTemplate); + } + + return $content; + } +} diff --git a/vendor/doctrine/migrations/src/Generator/SqlGenerator.php b/vendor/doctrine/migrations/src/Generator/SqlGenerator.php new file mode 100644 index 0000000..2a87cbe --- /dev/null +++ b/vendor/doctrine/migrations/src/Generator/SqlGenerator.php @@ -0,0 +1,96 @@ +configuration->getMetadataStorageConfiguration(); + foreach ($sql as $query) { + if ( + $storageConfiguration instanceof TableMetadataStorageConfiguration + && stripos($query, $storageConfiguration->getTableName()) !== false + ) { + continue; + } + + if ($formatted) { + $maxLength = $lineLength - 18 - 8; // max - php code length - indentation + + if (strlen($query) > $maxLength) { + $query = $this->formatQuery($query); + } + } + + $code[] = sprintf('$this->addSql(%s);', var_export($query, true)); + } + + if (count($code) !== 0 && $checkDbPlatform && $this->configuration->isDatabasePlatformChecked()) { + $currentPlatform = '\\' . get_class($this->platform); + + array_unshift( + $code, + sprintf( + <<<'PHP' +$this->abortIf( + !$this->connection->getDatabasePlatform() instanceof %s, + "Migration can only be executed safely on '%s'." +); +PHP + , + $currentPlatform, + $currentPlatform, + ), + '', + ); + } + + return implode("\n", $code); + } + + private function formatQuery(string $query): string + { + $this->formatter ??= new SqlFormatter(new NullHighlighter()); + + return $this->formatter->format($query); + } +} diff --git a/vendor/doctrine/migrations/src/InlineParameterFormatter.php b/vendor/doctrine/migrations/src/InlineParameterFormatter.php new file mode 100644 index 0000000..741a5ca --- /dev/null +++ b/vendor/doctrine/migrations/src/InlineParameterFormatter.php @@ -0,0 +1,83 @@ + $value) { + $type = $types[$key] ?? 'string'; + + $formattedParameter = '[' . $this->formatParameter($value, $type) . ']'; + + $formattedParameters[] = is_string($key) + ? sprintf(':%s => %s', $key, $formattedParameter) + : $formattedParameter; + } + + return sprintf('with parameters (%s)', implode(', ', $formattedParameters)); + } + + private function formatParameter(mixed $value, mixed $type): string|int|bool|float|null + { + if (is_string($type) && Type::hasType($type)) { + return Type::getType($type)->convertToDatabaseValue( + $value, + $this->connection->getDatabasePlatform(), + ); + } + + return $this->parameterToString($value); + } + + /** @param int[]|bool[]|string[]|float[]|array|int|string|float|bool $value */ + private function parameterToString(array|int|string|float|bool $value): string + { + if (is_array($value)) { + return implode(', ', array_map($this->parameterToString(...), $value)); + } + + if (is_int($value) || is_string($value) || is_float($value)) { + return (string) $value; + } + + if (is_bool($value)) { + return $value === true ? 'true' : 'false'; + } + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/AvailableMigration.php b/vendor/doctrine/migrations/src/Metadata/AvailableMigration.php new file mode 100644 index 0000000..6e1dffa --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/AvailableMigration.php @@ -0,0 +1,31 @@ +version; + } + + public function getMigration(): AbstractMigration + { + return $this->migration; + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/AvailableMigrationsList.php b/vendor/doctrine/migrations/src/Metadata/AvailableMigrationsList.php new file mode 100644 index 0000000..94e5625 --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/AvailableMigrationsList.php @@ -0,0 +1,86 @@ +items = array_values($items); + } + + /** @return AvailableMigration[] */ + public function getItems(): array + { + return $this->items; + } + + public function getFirst(int $offset = 0): AvailableMigration + { + if (! isset($this->items[$offset])) { + throw NoMigrationsFoundWithCriteria::new('first' . ($offset > 0 ? '+' . $offset : '')); + } + + return $this->items[$offset]; + } + + public function getLast(int $offset = 0): AvailableMigration + { + $offset = count($this->items) - 1 - (-1 * $offset); + if (! isset($this->items[$offset])) { + throw NoMigrationsFoundWithCriteria::new('last' . ($offset > 0 ? '+' . $offset : '')); + } + + return $this->items[$offset]; + } + + public function count(): int + { + return count($this->items); + } + + public function hasMigration(Version $version): bool + { + foreach ($this->items as $migration) { + if ($migration->getVersion()->equals($version)) { + return true; + } + } + + return false; + } + + public function getMigration(Version $version): AvailableMigration + { + foreach ($this->items as $migration) { + if ($migration->getVersion()->equals($version)) { + return $migration; + } + } + + throw MigrationNotAvailable::forVersion($version); + } + + public function newSubset(ExecutedMigrationsList $executedMigrations): self + { + return new self(array_filter($this->getItems(), static fn (AvailableMigration $migration): bool => ! $executedMigrations->hasMigration($migration->getVersion()))); + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/AvailableMigrationsSet.php b/vendor/doctrine/migrations/src/Metadata/AvailableMigrationsSet.php new file mode 100644 index 0000000..a166e1e --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/AvailableMigrationsSet.php @@ -0,0 +1,60 @@ +items = array_values($items); + } + + /** @return AvailableMigration[] */ + public function getItems(): array + { + return $this->items; + } + + public function count(): int + { + return count($this->items); + } + + public function hasMigration(Version $version): bool + { + foreach ($this->items as $migration) { + if ($migration->getVersion()->equals($version)) { + return true; + } + } + + return false; + } + + public function getMigration(Version $version): AvailableMigration + { + foreach ($this->items as $migration) { + if ($migration->getVersion()->equals($version)) { + return $migration; + } + } + + throw MigrationNotAvailable::forVersion($version); + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/ExecutedMigration.php b/vendor/doctrine/migrations/src/Metadata/ExecutedMigration.php new file mode 100644 index 0000000..418a1b4 --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/ExecutedMigration.php @@ -0,0 +1,37 @@ +executionTime; + } + + public function getExecutedAt(): DateTimeImmutable|null + { + return $this->executedAt; + } + + public function getVersion(): Version + { + return $this->version; + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/ExecutedMigrationsList.php b/vendor/doctrine/migrations/src/Metadata/ExecutedMigrationsList.php new file mode 100644 index 0000000..bfc32c6 --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/ExecutedMigrationsList.php @@ -0,0 +1,87 @@ +items = array_values($items); + } + + /** @return ExecutedMigration[] */ + public function getItems(): array + { + return $this->items; + } + + public function getFirst(int $offset = 0): ExecutedMigration + { + if (! isset($this->items[$offset])) { + throw NoMigrationsFoundWithCriteria::new('first' . ($offset > 0 ? '+' . $offset : '')); + } + + return $this->items[$offset]; + } + + public function getLast(int $offset = 0): ExecutedMigration + { + $offset = count($this->items) - 1 - (-1 * $offset); + if (! isset($this->items[$offset])) { + throw NoMigrationsFoundWithCriteria::new('last' . ($offset > 0 ? '+' . $offset : '')); + } + + return $this->items[$offset]; + } + + public function count(): int + { + return count($this->items); + } + + public function hasMigration(Version $version): bool + { + foreach ($this->items as $migration) { + if ($migration->getVersion()->equals($version)) { + return true; + } + } + + return false; + } + + public function getMigration(Version $version): ExecutedMigration + { + foreach ($this->items as $migration) { + if ($migration->getVersion()->equals($version)) { + return $migration; + } + } + + throw MigrationNotExecuted::new((string) $version); + } + + public function unavailableSubset(AvailableMigrationsList $availableMigrations): self + { + return new self(array_filter($this->getItems(), static fn (ExecutedMigration $migration): bool => ! $availableMigrations->hasMigration($migration->getVersion()))); + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/MigrationPlan.php b/vendor/doctrine/migrations/src/Metadata/MigrationPlan.php new file mode 100644 index 0000000..75eb31e --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/MigrationPlan.php @@ -0,0 +1,54 @@ +version; + } + + public function getResult(): ExecutionResult|null + { + return $this->result; + } + + public function markAsExecuted(ExecutionResult $result): void + { + if ($this->result !== null) { + throw PlanAlreadyExecuted::new(); + } + + $this->result = $result; + } + + public function getMigration(): AbstractMigration + { + return $this->migration; + } + + public function getDirection(): string + { + return $this->direction; + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/MigrationPlanList.php b/vendor/doctrine/migrations/src/Metadata/MigrationPlanList.php new file mode 100644 index 0000000..e515eb1 --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/MigrationPlanList.php @@ -0,0 +1,59 @@ +items); + } + + /** @return MigrationPlan[] */ + public function getItems(): array + { + return $this->items; + } + + public function getDirection(): string + { + return $this->direction; + } + + public function getFirst(): MigrationPlan + { + if (count($this->items) === 0) { + throw NoMigrationsFoundWithCriteria::new('first'); + } + + return reset($this->items); + } + + public function getLast(): MigrationPlan + { + if (count($this->items) === 0) { + throw NoMigrationsFoundWithCriteria::new('last'); + } + + return end($this->items); + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/Storage/MetadataStorage.php b/vendor/doctrine/migrations/src/Metadata/Storage/MetadataStorage.php new file mode 100644 index 0000000..79b1821 --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/Storage/MetadataStorage.php @@ -0,0 +1,21 @@ + getSql(ExecutionResult $result); */ +interface MetadataStorage +{ + public function ensureInitialized(): void; + + public function getExecutedMigrations(): ExecutedMigrationsList; + + public function complete(ExecutionResult $result): void; + + public function reset(): void; +} diff --git a/vendor/doctrine/migrations/src/Metadata/Storage/MetadataStorageConfiguration.php b/vendor/doctrine/migrations/src/Metadata/Storage/MetadataStorageConfiguration.php new file mode 100644 index 0000000..ffbf5e0 --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/Storage/MetadataStorageConfiguration.php @@ -0,0 +1,9 @@ + */ + private readonly AbstractSchemaManager $schemaManager; + + private readonly AbstractPlatform $platform; + private readonly TableMetadataStorageConfiguration $configuration; + + public function __construct( + private readonly Connection $connection, + private readonly MigrationsComparator $comparator, + MetadataStorageConfiguration|null $configuration = null, + private readonly MigrationsRepository|null $migrationRepository = null, + ) { + $this->schemaManager = $connection->createSchemaManager(); + $this->platform = $connection->getDatabasePlatform(); + + if ($configuration !== null && ! ($configuration instanceof TableMetadataStorageConfiguration)) { + throw new InvalidArgumentException(sprintf( + '%s accepts only %s as configuration', + self::class, + TableMetadataStorageConfiguration::class, + )); + } + + $this->configuration = $configuration ?? new TableMetadataStorageConfiguration(); + } + + public function getExecutedMigrations(): ExecutedMigrationsList + { + if (! $this->isInitialized()) { + return new ExecutedMigrationsList([]); + } + + $this->checkInitialization(); + $rows = $this->connection->fetchAllAssociative(sprintf('SELECT * FROM %s', $this->configuration->getTableName())); + + $migrations = []; + foreach ($rows as $row) { + $row = array_change_key_case($row, CASE_LOWER); + + $version = new Version($row[strtolower($this->configuration->getVersionColumnName())]); + + $executedAt = $row[strtolower($this->configuration->getExecutedAtColumnName())] ?? ''; + $executedAt = $executedAt !== '' + ? DateTimeImmutable::createFromFormat($this->platform->getDateTimeFormatString(), $executedAt) + : null; + + $executionTime = isset($row[strtolower($this->configuration->getExecutionTimeColumnName())]) + ? floatval($row[strtolower($this->configuration->getExecutionTimeColumnName())] / 1000) + : null; + + $migration = new ExecutedMigration( + $version, + $executedAt instanceof DateTimeImmutable ? $executedAt : null, + $executionTime, + ); + + $migrations[(string) $version] = $migration; + } + + uasort($migrations, fn (ExecutedMigration $a, ExecutedMigration $b): int => $this->comparator->compare($a->getVersion(), $b->getVersion())); + + return new ExecutedMigrationsList($migrations); + } + + public function reset(): void + { + $this->checkInitialization(); + + $this->connection->executeStatement( + sprintf( + 'DELETE FROM %s WHERE 1 = 1', + $this->platform->quoteIdentifier($this->configuration->getTableName()), + ), + ); + } + + public function complete(ExecutionResult $result): void + { + $this->checkInitialization(); + + if ($result->getDirection() === Direction::DOWN) { + $this->connection->delete($this->configuration->getTableName(), [ + $this->configuration->getVersionColumnName() => (string) $result->getVersion(), + ]); + } else { + $this->connection->insert($this->configuration->getTableName(), [ + $this->configuration->getVersionColumnName() => (string) $result->getVersion(), + $this->configuration->getExecutedAtColumnName() => $result->getExecutedAt(), + $this->configuration->getExecutionTimeColumnName() => $result->getTime() === null ? null : (int) round($result->getTime() * 1000), + ], [ + Types::STRING, + Types::DATETIME_IMMUTABLE, + Types::INTEGER, + ]); + } + } + + /** @return iterable */ + public function getSql(ExecutionResult $result): iterable + { + yield new Query('-- Version ' . (string) $result->getVersion() . ' update table metadata'); + + if ($result->getDirection() === Direction::DOWN) { + yield new Query(sprintf( + 'DELETE FROM %s WHERE %s = %s', + $this->configuration->getTableName(), + $this->configuration->getVersionColumnName(), + $this->connection->quote((string) $result->getVersion()), + )); + + return; + } + + yield new Query(sprintf( + 'INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, 0)', + $this->configuration->getTableName(), + $this->configuration->getVersionColumnName(), + $this->configuration->getExecutedAtColumnName(), + $this->configuration->getExecutionTimeColumnName(), + $this->connection->quote((string) $result->getVersion()), + $this->connection->quote(($result->getExecutedAt() ?? new DateTimeImmutable())->format('Y-m-d H:i:s')), + )); + } + + public function ensureInitialized(): void + { + if (! $this->isInitialized()) { + $expectedSchemaChangelog = $this->getExpectedTable(); + $this->schemaManager->createTable($expectedSchemaChangelog); + $this->schemaUpToDate = true; + $this->isInitialized = true; + + return; + } + + $this->isInitialized = true; + $expectedSchemaChangelog = $this->getExpectedTable(); + $diff = $this->needsUpdate($expectedSchemaChangelog); + if ($diff === null) { + $this->schemaUpToDate = true; + + return; + } + + $this->schemaUpToDate = true; + $this->schemaManager->alterTable($diff); + $this->updateMigratedVersionsFromV1orV2toV3(); + } + + private function needsUpdate(Table $expectedTable): TableDiff|null + { + if ($this->schemaUpToDate) { + return null; + } + + $currentTable = $this->schemaManager->introspectTable($this->configuration->getTableName()); + $diff = $this->schemaManager->createComparator()->compareTables($currentTable, $expectedTable); + + return $diff->isEmpty() ? null : $diff; + } + + private function isInitialized(): bool + { + if ($this->isInitialized) { + return $this->isInitialized; + } + + if ($this->connection instanceof PrimaryReadReplicaConnection) { + $this->connection->ensureConnectedToPrimary(); + } + + return $this->schemaManager->tablesExist([$this->configuration->getTableName()]); + } + + private function checkInitialization(): void + { + if (! $this->isInitialized()) { + throw MetadataStorageError::notInitialized(); + } + + $expectedTable = $this->getExpectedTable(); + + if ($this->needsUpdate($expectedTable) !== null) { + throw MetadataStorageError::notUpToDate(); + } + } + + private function getExpectedTable(): Table + { + $schemaChangelog = new Table($this->configuration->getTableName()); + + $schemaChangelog->addColumn( + $this->configuration->getVersionColumnName(), + 'string', + ['notnull' => true, 'length' => $this->configuration->getVersionColumnLength()], + ); + $schemaChangelog->addColumn($this->configuration->getExecutedAtColumnName(), 'datetime', ['notnull' => false]); + $schemaChangelog->addColumn($this->configuration->getExecutionTimeColumnName(), 'integer', ['notnull' => false]); + + $schemaChangelog->setPrimaryKey([$this->configuration->getVersionColumnName()]); + + return $schemaChangelog; + } + + private function updateMigratedVersionsFromV1orV2toV3(): void + { + if ($this->migrationRepository === null) { + return; + } + + $availableMigrations = $this->migrationRepository->getMigrations()->getItems(); + $executedMigrations = $this->getExecutedMigrations()->getItems(); + + foreach ($availableMigrations as $availableMigration) { + foreach ($executedMigrations as $k => $executedMigration) { + if ($this->isAlreadyV3Format($availableMigration, $executedMigration)) { + continue; + } + + $this->connection->update( + $this->configuration->getTableName(), + [ + $this->configuration->getVersionColumnName() => (string) $availableMigration->getVersion(), + ], + [ + $this->configuration->getVersionColumnName() => (string) $executedMigration->getVersion(), + ], + ); + unset($executedMigrations[$k]); + } + } + } + + private function isAlreadyV3Format(AvailableMigration $availableMigration, ExecutedMigration $executedMigration): bool + { + return strpos( + (string) $availableMigration->getVersion(), + (string) $executedMigration->getVersion(), + ) !== strlen((string) $availableMigration->getVersion()) - + strlen((string) $executedMigration->getVersion()); + } +} diff --git a/vendor/doctrine/migrations/src/Metadata/Storage/TableMetadataStorageConfiguration.php b/vendor/doctrine/migrations/src/Metadata/Storage/TableMetadataStorageConfiguration.php new file mode 100644 index 0000000..44eb5cd --- /dev/null +++ b/vendor/doctrine/migrations/src/Metadata/Storage/TableMetadataStorageConfiguration.php @@ -0,0 +1,68 @@ +tableName; + } + + public function setTableName(string $tableName): void + { + $this->tableName = $tableName; + } + + public function getVersionColumnName(): string + { + return $this->versionColumnName; + } + + public function setVersionColumnName(string $versionColumnName): void + { + $this->versionColumnName = $versionColumnName; + } + + public function getVersionColumnLength(): int + { + return $this->versionColumnLength; + } + + public function setVersionColumnLength(int $versionColumnLength): void + { + $this->versionColumnLength = $versionColumnLength; + } + + public function getExecutedAtColumnName(): string + { + return $this->executedAtColumnName; + } + + public function setExecutedAtColumnName(string $executedAtColumnName): void + { + $this->executedAtColumnName = $executedAtColumnName; + } + + public function getExecutionTimeColumnName(): string + { + return $this->executionTimeColumnName; + } + + public function setExecutionTimeColumnName(string $executionTimeColumnName): void + { + $this->executionTimeColumnName = $executionTimeColumnName; + } +} diff --git a/vendor/doctrine/migrations/src/MigrationsRepository.php b/vendor/doctrine/migrations/src/MigrationsRepository.php new file mode 100644 index 0000000..56663ae --- /dev/null +++ b/vendor/doctrine/migrations/src/MigrationsRepository.php @@ -0,0 +1,18 @@ + A list of SQL statements executed, grouped by migration version */ + public function migrate(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array; +} diff --git a/vendor/doctrine/migrations/src/MigratorConfiguration.php b/vendor/doctrine/migrations/src/MigratorConfiguration.php new file mode 100644 index 0000000..01e7ee1 --- /dev/null +++ b/vendor/doctrine/migrations/src/MigratorConfiguration.php @@ -0,0 +1,88 @@ +dryRun; + } + + public function setDryRun(bool $dryRun): self + { + $this->dryRun = $dryRun; + + return $this; + } + + public function getTimeAllQueries(): bool + { + return $this->timeAllQueries; + } + + public function setTimeAllQueries(bool $timeAllQueries): self + { + $this->timeAllQueries = $timeAllQueries; + + return $this; + } + + public function getNoMigrationException(): bool + { + return $this->noMigrationException; + } + + public function setNoMigrationException(bool $noMigrationException = false): self + { + $this->noMigrationException = $noMigrationException; + + return $this; + } + + public function isAllOrNothing(): bool + { + return $this->allOrNothing; + } + + public function setAllOrNothing(bool $allOrNothing): self + { + $this->allOrNothing = $allOrNothing; + + return $this; + } + + public function getFromSchema(): Schema|null + { + return $this->fromSchema; + } + + public function setFromSchema(Schema $fromSchema): self + { + $this->fromSchema = $fromSchema; + + return $this; + } +} diff --git a/vendor/doctrine/migrations/src/ParameterFormatter.php b/vendor/doctrine/migrations/src/ParameterFormatter.php new file mode 100644 index 0000000..c6a152a --- /dev/null +++ b/vendor/doctrine/migrations/src/ParameterFormatter.php @@ -0,0 +1,22 @@ + $schemaManager- */ + public function __construct( + private readonly AbstractSchemaManager $schemaManager, + private readonly AbstractPlatform $platform, + ) { + } + + public function createFromSchema(): Schema + { + return $this->schemaManager->introspectSchema(); + } + + public function createToSchema(Schema $fromSchema): Schema + { + return clone $fromSchema; + } + + /** @return string[] */ + public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema): array + { + return $this->platform->getAlterSchemaSQL( + $this->schemaManager->createComparator()->compareSchemas($fromSchema, $toSchema), + ); + } +} diff --git a/vendor/doctrine/migrations/src/Provider/EmptySchemaProvider.php b/vendor/doctrine/migrations/src/Provider/EmptySchemaProvider.php new file mode 100644 index 0000000..3439124 --- /dev/null +++ b/vendor/doctrine/migrations/src/Provider/EmptySchemaProvider.php @@ -0,0 +1,28 @@ + $schemaManager */ + public function __construct(private readonly AbstractSchemaManager $schemaManager) + { + } + + public function createSchema(): Schema + { + return new Schema([], [], $this->schemaManager->createSchemaConfig()); + } +} diff --git a/vendor/doctrine/migrations/src/Provider/Exception/NoMappingFound.php b/vendor/doctrine/migrations/src/Provider/Exception/NoMappingFound.php new file mode 100644 index 0000000..fd5fc51 --- /dev/null +++ b/vendor/doctrine/migrations/src/Provider/Exception/NoMappingFound.php @@ -0,0 +1,15 @@ +originalSchemaManipulator; + + return LazySchema::createLazyProxy(static fn () => $originalSchemaManipulator->createFromSchema()); + } + + public function createToSchema(Schema $fromSchema): Schema + { + $originalSchemaManipulator = $this->originalSchemaManipulator; + + if ($fromSchema instanceof LazySchema && ! $fromSchema->isLazyObjectInitialized()) { + return LazySchema::createLazyProxy(static fn () => $originalSchemaManipulator->createToSchema($fromSchema)); + } + + return $this->originalSchemaManipulator->createToSchema($fromSchema); + } + + /** @return string[] */ + public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema): array + { + if ( + $toSchema instanceof LazySchema + && ! $toSchema->isLazyObjectInitialized() + ) { + return []; + } + + return $this->originalSchemaManipulator->getSqlDiffToMigrate($fromSchema, $toSchema); + } +} diff --git a/vendor/doctrine/migrations/src/Provider/OrmSchemaProvider.php b/vendor/doctrine/migrations/src/Provider/OrmSchemaProvider.php new file mode 100644 index 0000000..750589e --- /dev/null +++ b/vendor/doctrine/migrations/src/Provider/OrmSchemaProvider.php @@ -0,0 +1,41 @@ +entityManager = $em; + } + + public function createSchema(): Schema + { + /** @var array> $metadata */ + $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata(); + + usort($metadata, static fn (ClassMetadata $a, ClassMetadata $b): int => $a->getTableName() <=> $b->getTableName()); + + $tool = new SchemaTool($this->entityManager); + + return $tool->getSchemaFromMetadata($metadata); + } +} diff --git a/vendor/doctrine/migrations/src/Provider/SchemaDiffProvider.php b/vendor/doctrine/migrations/src/Provider/SchemaDiffProvider.php new file mode 100644 index 0000000..3343719 --- /dev/null +++ b/vendor/doctrine/migrations/src/Provider/SchemaDiffProvider.php @@ -0,0 +1,23 @@ +toSchema = $schema; + } + + public function createSchema(): Schema + { + return $this->toSchema; + } +} diff --git a/vendor/doctrine/migrations/src/Query/Exception/InvalidArguments.php b/vendor/doctrine/migrations/src/Query/Exception/InvalidArguments.php new file mode 100644 index 0000000..df73ab6 --- /dev/null +++ b/vendor/doctrine/migrations/src/Query/Exception/InvalidArguments.php @@ -0,0 +1,23 @@ + count($parameters)) { + throw InvalidArguments::wrongTypesArgumentCount($statement, count($parameters), count($types)); + } + } + + public function __toString(): string + { + return $this->statement; + } + + public function getStatement(): string + { + return $this->statement; + } + + /** @return mixed[] */ + public function getParameters(): array + { + return $this->parameters; + } + + /** @return mixed[] */ + public function getTypes(): array + { + return $this->types; + } +} diff --git a/vendor/doctrine/migrations/src/QueryWriter.php b/vendor/doctrine/migrations/src/QueryWriter.php new file mode 100644 index 0000000..9868d7a --- /dev/null +++ b/vendor/doctrine/migrations/src/QueryWriter.php @@ -0,0 +1,22 @@ + $queriesByVersion */ + public function write( + string $path, + string $direction, + array $queriesByVersion, + ): bool; +} diff --git a/vendor/doctrine/migrations/src/Rollup.php b/vendor/doctrine/migrations/src/Rollup.php new file mode 100644 index 0000000..662faaa --- /dev/null +++ b/vendor/doctrine/migrations/src/Rollup.php @@ -0,0 +1,48 @@ +migrationRepository->getMigrations(); + + if (count($versions) === 0) { + throw RollupFailed::noMigrationsFound(); + } + + if (count($versions) > 1) { + throw RollupFailed::tooManyMigrations(); + } + + $this->metadataStorage->reset(); + + $result = new ExecutionResult($versions->getItems()[0]->getVersion()); + $this->metadataStorage->complete($result); + + return $result->getVersion(); + } +} diff --git a/vendor/doctrine/migrations/src/SchemaDumper.php b/vendor/doctrine/migrations/src/SchemaDumper.php new file mode 100644 index 0000000..f1938c9 --- /dev/null +++ b/vendor/doctrine/migrations/src/SchemaDumper.php @@ -0,0 +1,158 @@ + $schemaManager + * @param string[] $excludedTablesRegexes + */ + public function __construct( + private readonly AbstractPlatform $platform, + private readonly AbstractSchemaManager $schemaManager, + private readonly Generator $migrationGenerator, + private readonly SqlGenerator $migrationSqlGenerator, + private readonly array $excludedTablesRegexes = [], + ) { + } + + /** + * @param string[] $excludedTablesRegexes + * + * @throws NoTablesFound + */ + public function dump( + string $fqcn, + array $excludedTablesRegexes = [], + bool $formatted = false, + int $lineLength = 120, + ): string { + $schema = $this->schemaManager->introspectSchema(); + + $up = []; + $down = []; + + foreach ($schema->getTables() as $table) { + if ($this->shouldSkipTable($table, $excludedTablesRegexes)) { + continue; + } + + $upSql = $this->platform->getCreateTableSQL($table); + + $upCode = $this->migrationSqlGenerator->generate( + $upSql, + $formatted, + $lineLength, + ); + + if ($upCode !== '') { + $up[] = $upCode; + } + + $downSql = [$this->platform->getDropTableSQL($table->getQuotedName($this->platform))]; + + $downCode = $this->migrationSqlGenerator->generate( + $downSql, + $formatted, + $lineLength, + ); + + if ($downCode === '') { + continue; + } + + $down[] = $downCode; + } + + if (count($up) === 0) { + throw NoTablesFound::new(); + } + + $up = implode("\n", $up); + $down = implode("\n", $down); + + return $this->migrationGenerator->generateMigration( + $fqcn, + $up, + $down, + ); + } + + /** @param string[] $excludedTablesRegexes */ + private function shouldSkipTable(Table $table, array $excludedTablesRegexes): bool + { + foreach (array_merge($excludedTablesRegexes, $this->excludedTablesRegexes) as $regex) { + if (self::pregMatch($regex, $table->getName()) !== 0) { + return true; + } + } + + return false; + } + + /** + * A local wrapper for "preg_match" which will throw a InvalidArgumentException if there + * is an internal error in the PCRE engine. + * Copied from https://github.com/symfony/symfony/blob/62216ea67762b18982ca3db73c391b0748a49d49/src/Symfony/Component/Yaml/Parser.php#L1072-L1090 + * + * @internal + * + * @param mixed[] $matches + * @param int-mask-of $flags + */ + private static function pregMatch(string $pattern, string $subject, array|null &$matches = null, int $flags = 0, int $offset = 0): int + { + $errorMessages = []; + set_error_handler(static function (int $severity, string $message) use (&$errorMessages): bool { + $errorMessages[] = $message; + + return true; + }); + + try { + $ret = preg_match($pattern, $subject, $matches, $flags, $offset); + } finally { + restore_error_handler(); + } + + if ($ret === false) { + throw new InvalidArgumentException(match (preg_last_error()) { + PREG_INTERNAL_ERROR => sprintf('Internal PCRE error, please check your Regex. Reported errors: %s.', implode(', ', $errorMessages)), + default => preg_last_error_msg(), + }); + } + + return $ret; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/BooleanStringFormatter.php b/vendor/doctrine/migrations/src/Tools/BooleanStringFormatter.php new file mode 100644 index 0000000..187e903 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/BooleanStringFormatter.php @@ -0,0 +1,27 @@ + true, + 'false', '0' => false, + default => $default, + }; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/BytesFormatter.php b/vendor/doctrine/migrations/src/Tools/BytesFormatter.php new file mode 100644 index 0000000..d63062e --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/BytesFormatter.php @@ -0,0 +1,27 @@ +setAliases(['current']) + ->setDescription('Outputs the current version'); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $aliasResolver = $this->getDependencyFactory()->getVersionAliasResolver(); + + $version = $aliasResolver->resolveVersionAlias('current'); + if ((string) $version === '0') { + $description = '(No migration executed yet)'; + } else { + try { + $availableMigration = $this->getDependencyFactory()->getMigrationRepository()->getMigration($version); + $description = $availableMigration->getMigration()->getDescription(); + } catch (MigrationClassNotFound) { + $description = '(Migration info not available)'; + } + } + + $this->io->text(sprintf( + "%s%s\n", + (string) $version, + $description !== '' ? ' - ' . $description : '', + )); + + return 0; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/DiffCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/DiffCommand.php new file mode 100644 index 0000000..3bebfc5 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/DiffCommand.php @@ -0,0 +1,196 @@ +setAliases(['diff']) + ->setDescription('Generate a migration by comparing your current database to your mapping information.') + ->setHelp(<<<'EOT' +The %command.name% command generates a migration by comparing your current database to your mapping information: + + %command.full_name% + +EOT) + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ) + ->addOption( + 'filter-expression', + null, + InputOption::VALUE_REQUIRED, + 'Tables which are filtered by Regular Expression.', + ) + ->addOption( + 'formatted', + null, + InputOption::VALUE_NONE, + 'Format the generated SQL.', + ) + ->addOption( + 'line-length', + null, + InputOption::VALUE_REQUIRED, + 'Max line length of unformatted lines.', + '120', + ) + ->addOption( + 'check-database-platform', + null, + InputOption::VALUE_OPTIONAL, + 'Check Database Platform to the generated code.', + false, + ) + ->addOption( + 'allow-empty-diff', + null, + InputOption::VALUE_NONE, + 'Do not throw an exception when no changes are detected.', + ) + ->addOption( + 'from-empty-schema', + null, + InputOption::VALUE_NONE, + 'Generate a full migration as if the current database was empty.', + ); + } + + /** @throws InvalidOptionUsage */ + protected function execute( + InputInterface $input, + OutputInterface $output, + ): int { + $filterExpression = (string) $input->getOption('filter-expression'); + if ($filterExpression === '') { + $filterExpression = null; + } + + $formatted = filter_var($input->getOption('formatted'), FILTER_VALIDATE_BOOLEAN); + $lineLength = (int) $input->getOption('line-length'); + $allowEmptyDiff = $input->getOption('allow-empty-diff'); + $checkDbPlatform = filter_var($input->getOption('check-database-platform'), FILTER_VALIDATE_BOOLEAN); + $fromEmptySchema = $input->getOption('from-empty-schema'); + + if ($formatted) { + if (! class_exists(SqlFormatter::class)) { + throw InvalidOptionUsage::new( + 'The "--formatted" option can only be used if the sql formatter is installed. Please run "composer require doctrine/sql-formatter".', + ); + } + } + + $namespace = $this->getNamespace($input, $output); + + $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); + $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); + $newMigrations = $statusCalculator->getNewMigrations(); + + if (! $this->checkNewMigrationsOrExecutedUnavailable($newMigrations, $executedUnavailableMigrations, $input)) { + $this->io->error('Migration cancelled!'); + + return 3; + } + + $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); + + $diffGenerator = $this->getDependencyFactory()->getDiffGenerator(); + + try { + $path = $diffGenerator->generate( + $fqcn, + $filterExpression, + $formatted, + $lineLength, + $checkDbPlatform, + $fromEmptySchema, + ); + } catch (NoChangesDetected $exception) { + if ($allowEmptyDiff) { + $this->io->error($exception->getMessage()); + + return 0; + } + + throw $exception; + } + + $this->io->text([ + sprintf('Generated new migration class to "%s"', $path), + '', + sprintf( + 'To run just this migration for testing purposes, you can use migrations:execute --up \'%s\'', + addslashes($fqcn), + ), + '', + sprintf( + 'To revert the migration you can use migrations:execute --down \'%s\'', + addslashes($fqcn), + ), + '', + ]); + + return 0; + } + + private function checkNewMigrationsOrExecutedUnavailable( + AvailableMigrationsList $newMigrations, + ExecutedMigrationsList $executedUnavailableMigrations, + InputInterface $input, + ): bool { + if (count($newMigrations) === 0 && count($executedUnavailableMigrations) === 0) { + return true; + } + + if (count($newMigrations) !== 0) { + $this->io->warning(sprintf( + 'You have %d available migrations to execute.', + count($newMigrations), + )); + } + + if (count($executedUnavailableMigrations) !== 0) { + $this->io->warning(sprintf( + 'You have %d previously executed migrations in the database that are not registered migrations.', + count($executedUnavailableMigrations), + )); + } + + return $this->canExecute('Are you sure you wish to continue?', $input); + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/DoctrineCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/DoctrineCommand.php new file mode 100644 index 0000000..661f30d --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/DoctrineCommand.php @@ -0,0 +1,179 @@ +addOption( + 'configuration', + null, + InputOption::VALUE_REQUIRED, + 'The path to a migrations configuration file. [default: any of migrations.{php,xml,json,yml,yaml}]', + ); + + $this->addOption( + 'em', + null, + InputOption::VALUE_REQUIRED, + 'The name of the entity manager to use.', + ); + + $this->addOption( + 'conn', + null, + InputOption::VALUE_REQUIRED, + 'The name of the connection to use.', + ); + + if ($this->dependencyFactory !== null) { + return; + } + + $this->addOption( + 'db-configuration', + null, + InputOption::VALUE_REQUIRED, + 'The path to a database connection configuration file.', + 'migrations-db.php', + ); + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->io = new SymfonyStyle($input, $output); + + $configurationParameter = $input->getOption('configuration'); + if ($this->dependencyFactory === null) { + $configurationLoader = new ConfigurationFileWithFallback( + is_string($configurationParameter) + ? $configurationParameter + : null, + ); + $connectionLoader = new ConfigurationFile($input->getOption('db-configuration')); + $this->dependencyFactory = DependencyFactory::fromConnection($configurationLoader, $connectionLoader); + } elseif (is_string($configurationParameter)) { + $configurationLoader = new ConfigurationFileWithFallback($configurationParameter); + $this->dependencyFactory->setConfigurationLoader($configurationLoader); + } + + $dependencyFactory = $this->dependencyFactory; + + $this->setNamedEmOrConnection($input); + + if ($dependencyFactory->isFrozen()) { + return; + } + + $logger = new ConsoleLogger($output); + $dependencyFactory->setService(LoggerInterface::class, $logger); + $dependencyFactory->freeze(); + } + + protected function getDependencyFactory(): DependencyFactory + { + if ($this->dependencyFactory === null) { + throw DependenciesNotSatisfied::new(); + } + + return $this->dependencyFactory; + } + + protected function canExecute(string $question, InputInterface $input): bool + { + return ! $input->isInteractive() || $this->io->confirm($question); + } + + private function setNamedEmOrConnection(InputInterface $input): void + { + assert($this->dependencyFactory !== null); + $emName = $input->getOption('em'); + $connName = $input->getOption('conn'); + if ($emName !== null && $connName !== null) { + throw new InvalidOptionUsage('You can specify only one of the --em and --conn options.'); + } + + if ($this->dependencyFactory->hasEntityManager() && $emName !== null) { + $this->dependencyFactory->getConfiguration()->setEntityManagerName($emName); + + return; + } + + if ($connName !== null) { + $this->dependencyFactory->getConfiguration()->setConnectionName($connName); + + return; + } + } + + final protected function getNamespace(InputInterface $input, OutputInterface $output): string + { + $configuration = $this->getDependencyFactory()->getConfiguration(); + + $namespace = $input->getOption('namespace'); + if ($namespace === '') { + $namespace = null; + } + + $dirs = $configuration->getMigrationDirectories(); + if ($namespace === null && count($dirs) === 1) { + $namespace = key($dirs); + } elseif ($namespace === null && count($dirs) > 1) { + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please choose a namespace (defaults to the first one)', + array_keys($dirs), + 0, + ); + $namespace = $helper->ask($input, $output, $question); + $this->io->text(sprintf('You have selected the "%s" namespace', $namespace)); + } + + if (! isset($dirs[$namespace])) { + throw new Exception(sprintf('Path not defined for the namespace "%s"', $namespace)); + } + + assert(is_string($namespace)); + + return $namespace; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/DumpSchemaCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/DumpSchemaCommand.php new file mode 100644 index 0000000..724d0cf --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/DumpSchemaCommand.php @@ -0,0 +1,133 @@ +setAliases(['dump-schema']) + ->setDescription('Dump the schema for your database to a migration.') + ->setHelp(<<<'EOT' +The %command.name% command dumps the schema for your database to a migration: + + %command.full_name% + +After dumping your schema to a migration, you can rollup your migrations using the migrations:rollup command. +EOT) + ->addOption( + 'formatted', + null, + InputOption::VALUE_NONE, + 'Format the generated SQL.', + ) + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'Namespace to use for the generated migrations (defaults to the first namespace definition).', + ) + ->addOption( + 'filter-tables', + null, + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Filter the tables to dump via Regex.', + ) + ->addOption( + 'line-length', + null, + InputOption::VALUE_OPTIONAL, + 'Max line length of unformatted lines.', + '120', + ); + } + + /** @throws SchemaDumpRequiresNoMigrations */ + public function execute( + InputInterface $input, + OutputInterface $output, + ): int { + $formatted = $input->getOption('formatted'); + $lineLength = (int) $input->getOption('line-length'); + + $schemaDumper = $this->getDependencyFactory()->getSchemaDumper(); + + if ($formatted) { + if (! class_exists(SqlFormatter::class)) { + throw InvalidOptionUsage::new( + 'The "--formatted" option can only be used if the sql formatter is installed. Please run "composer require doctrine/sql-formatter".', + ); + } + } + + $namespace = $this->getNamespace($input, $output); + + $this->checkNoPreviousDumpExistsForNamespace($namespace); + + $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); + + $path = $schemaDumper->dump( + $fqcn, + $input->getOption('filter-tables'), + $formatted, + $lineLength, + ); + + $this->io->text([ + sprintf('Dumped your schema to a new migration class at "%s"', $path), + '', + sprintf( + 'To run just this migration for testing purposes, you can use migrations:execute --up \'%s\'', + addslashes($fqcn), + ), + '', + sprintf( + 'To revert the migration you can use migrations:execute --down \'%s\'', + addslashes($fqcn), + ), + '', + 'To use this as a rollup migration you can use the migrations:rollup command.', + '', + ]); + + return 0; + } + + private function checkNoPreviousDumpExistsForNamespace(string $namespace): void + { + $migrations = $this->getDependencyFactory()->getMigrationRepository()->getMigrations(); + foreach ($migrations->getItems() as $migration) { + if (str_contains((string) $migration->getVersion(), $namespace)) { + throw SchemaDumpRequiresNoMigrations::new($namespace); + } + } + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/ExecuteCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/ExecuteCommand.php new file mode 100644 index 0000000..92d4101 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/ExecuteCommand.php @@ -0,0 +1,178 @@ +setAliases(['execute']) + ->setDescription( + 'Execute one or more migration versions up or down manually.', + ) + ->addArgument( + 'versions', + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + 'The versions to execute.', + null, + ) + ->addOption( + 'write-sql', + null, + InputOption::VALUE_OPTIONAL, + 'The path to output the migration SQL file. Defaults to current working directory.', + false, + ) + ->addOption( + 'dry-run', + null, + InputOption::VALUE_NONE, + 'Execute the migration as a dry run.', + ) + ->addOption( + 'up', + null, + InputOption::VALUE_NONE, + 'Execute the migration up.', + ) + ->addOption( + 'down', + null, + InputOption::VALUE_NONE, + 'Execute the migration down.', + ) + ->addOption( + 'query-time', + null, + InputOption::VALUE_NONE, + 'Time all the queries individually.', + ) + ->setHelp(<<<'EOT' +The %command.name% command executes migration versions up or down manually: + + %command.full_name% FQCN + +You can show more information about the process by increasing the verbosity level. To see the +executed queries, set the level to debug with -vv: + + %command.full_name% FQCN -vv + +If no --up or --down option is specified it defaults to up: + + %command.full_name% FQCN --down + +You can also execute the migration as a --dry-run: + + %command.full_name% FQCN --dry-run + +You can output the prepared SQL statements to a file with --write-sql: + + %command.full_name% FQCN --write-sql + +Or you can also execute the migration without a warning message which you need to interact with: + + %command.full_name% FQCN --no-interaction + +All the previous commands accept multiple migration versions, allowing you run execute more than +one migration at once: + + %command.full_name% FQCN-1 FQCN-2 ...FQCN-n + +EOT); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $migratorConfigurationFactory = $this->getDependencyFactory()->getConsoleInputMigratorConfigurationFactory(); + $migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input); + + $databaseName = (string) $this->getDependencyFactory()->getConnection()->getDatabase(); + $question = sprintf( + 'WARNING! You are about to execute a migration in database "%s" that could result in schema changes and data loss. Are you sure you wish to continue?', + $databaseName === '' ? '' : $databaseName, + ); + if (! $migratorConfiguration->isDryRun() && ! $this->canExecute($question, $input)) { + $this->io->error('Migration cancelled!'); + + return 1; + } + + $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); + + $versions = $input->getArgument('versions'); + $direction = $input->getOption('down') !== false + ? Direction::DOWN + : Direction::UP; + + $path = $input->getOption('write-sql') ?? getcwd(); + + if (is_string($path) && ! $this->isPathWritable($path)) { + $this->io->error(sprintf('The path "%s" not writeable!', $path)); + + return 1; + } + + $planCalculator = $this->getDependencyFactory()->getMigrationPlanCalculator(); + $plan = $planCalculator->getPlanForVersions(array_map(static fn (string $version): Version => new Version($version), $versions), $direction); + + $this->getDependencyFactory()->getLogger()->notice( + 'Executing' . ($migratorConfiguration->isDryRun() ? ' (dry-run)' : '') . ' {versions} {direction}', + [ + 'direction' => $plan->getDirection(), + 'versions' => implode(', ', $versions), + ], + ); + + $migrator = $this->getDependencyFactory()->getMigrator(); + $sql = $migrator->migrate($plan, $migratorConfiguration); + + if (is_string($path)) { + $writer = $this->getDependencyFactory()->getQueryWriter(); + $writer->write($path, $direction, $sql); + } + + $this->io->success(sprintf( + 'Successfully migrated version(s): %s: [%s]', + implode(', ', $versions), + strtoupper($plan->getDirection()), + )); + $this->io->newLine(); + + return 0; + } + + private function isPathWritable(string $path): bool + { + return is_writable($path) || is_dir($path) || is_writable(dirname($path)); + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/GenerateCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/GenerateCommand.php new file mode 100644 index 0000000..043d648 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/GenerateCommand.php @@ -0,0 +1,71 @@ +setAliases(['generate']) + ->setDescription('Generate a blank migration class.') + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ) + ->setHelp(<<<'EOT' +The %command.name% command generates a blank migration class: + + %command.full_name% + +EOT); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $migrationGenerator = $this->getDependencyFactory()->getMigrationGenerator(); + + $namespace = $this->getNamespace($input, $output); + + $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); + + $path = $migrationGenerator->generateMigration($fqcn); + + $this->io->text([ + sprintf('Generated new migration class to "%s"', $path), + '', + sprintf( + 'To run just this migration for testing purposes, you can use migrations:execute --up \'%s\'', + $fqcn, + ), + '', + sprintf( + 'To revert the migration you can use migrations:execute --down \'%s\'', + $fqcn, + ), + '', + ]); + + return 0; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/LatestCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/LatestCommand.php new file mode 100644 index 0000000..5ce6af0 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/LatestCommand.php @@ -0,0 +1,53 @@ +setAliases(['latest']) + ->setDescription('Outputs the latest version'); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $aliasResolver = $this->getDependencyFactory()->getVersionAliasResolver(); + + try { + $version = $aliasResolver->resolveVersionAlias('latest'); + $availableMigration = $this->getDependencyFactory()->getMigrationRepository()->getMigration($version); + $description = $availableMigration->getMigration()->getDescription(); + } catch (NoMigrationsToExecute) { + $version = '0'; + $description = ''; + } + + $this->io->text(sprintf( + "%s%s\n", + $version, + $description !== '' ? ' - ' . $description : '', + )); + + return 0; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/ListCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/ListCommand.php new file mode 100644 index 0000000..86aa661 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/ListCommand.php @@ -0,0 +1,70 @@ +setAliases(['list-migrations']) + ->setDescription('Display a list of all available migrations and their status.') + ->setHelp(<<<'EOT' +The %command.name% command outputs a list of all available migrations and their status: + + %command.full_name% +EOT); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $versions = $this->getSortedVersions( + $this->getDependencyFactory()->getMigrationPlanCalculator()->getMigrations(), // available migrations + $this->getDependencyFactory()->getMetadataStorage()->getExecutedMigrations(), // executed migrations + ); + + $this->getDependencyFactory()->getMigrationStatusInfosHelper()->listVersions($versions, $output); + + return 0; + } + + /** @return Version[] */ + private function getSortedVersions(AvailableMigrationsList $availableMigrations, ExecutedMigrationsList $executedMigrations): array + { + $availableVersions = array_map(static fn (AvailableMigration $availableMigration): Version => $availableMigration->getVersion(), $availableMigrations->getItems()); + + $executedVersions = array_map(static fn (ExecutedMigration $executedMigration): Version => $executedMigration->getVersion(), $executedMigrations->getItems()); + + $versions = array_unique(array_merge($availableVersions, $executedVersions)); + + $comparator = $this->getDependencyFactory()->getVersionComparator(); + uasort($versions, $comparator->compare(...)); + + return $versions; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/MigrateCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/MigrateCommand.php new file mode 100644 index 0000000..956a2fb --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/MigrateCommand.php @@ -0,0 +1,308 @@ +setAliases(['migrate']) + ->setDescription( + 'Execute a migration to a specified version or the latest available version.', + ) + ->addArgument( + 'version', + InputArgument::OPTIONAL, + 'The version FQCN or alias (first, prev, next, latest) to migrate to.', + 'latest', + ) + ->addOption( + 'write-sql', + null, + InputOption::VALUE_OPTIONAL, + 'The path to output the migration SQL file. Defaults to current working directory.', + false, + ) + ->addOption( + 'dry-run', + null, + InputOption::VALUE_NONE, + 'Execute the migration as a dry run.', + ) + ->addOption( + 'query-time', + null, + InputOption::VALUE_NONE, + 'Time all the queries individually.', + ) + ->addOption( + 'allow-no-migration', + null, + InputOption::VALUE_NONE, + 'Do not throw an exception if no migration is available.', + ) + ->addOption( + 'all-or-nothing', + null, + InputOption::VALUE_OPTIONAL, + 'Wrap the entire migration in a transaction.', + ConsoleInputMigratorConfigurationFactory::ABSENT_CONFIG_VALUE, + ) + ->addOption( + 'no-all-or-nothing', + null, + InputOption::VALUE_NONE, + 'Disable wrapping the entire migration in a transaction.', + ) + ->setHelp(<<<'EOT' +The %command.name% command executes a migration to a specified version or the latest available version: + + %command.full_name% + +You can show more information about the process by increasing the verbosity level. To see the +executed queries, set the level to debug with -vv: + + %command.full_name% -vv + +You can optionally manually specify the version you wish to migrate to: + + %command.full_name% FQCN + +You can specify the version you wish to migrate to using an alias: + + %command.full_name% prev + These alias are defined: first, latest, prev, current and next + +You can specify the version you wish to migrate to using a number against the current version: + + %command.full_name% current+3 + +You can also execute the migration as a --dry-run: + + %command.full_name% FQCN --dry-run + +You can output the prepared SQL statements to a file with --write-sql: + + %command.full_name% FQCN --write-sql + +Or you can also execute the migration without a warning message which you need to interact with --no-interaction: + + %command.full_name% --no-interaction + +You can also time all the different queries if you want to know which one is taking so long with --query-time: + + %command.full_name% --query-time + +You can skip throwing an exception if no migration is available with --allow-no-migration: + + %command.full_name% --allow-no-migration + +You can wrap the entire migration in a transaction with --all-or-nothing: + + %command.full_name% --all-or-nothing + +EOT); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $migratorConfigurationFactory = $this->getDependencyFactory()->getConsoleInputMigratorConfigurationFactory(); + $migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input); + + $databaseName = (string) $this->getDependencyFactory()->getConnection()->getDatabase(); + $question = sprintf( + 'WARNING! You are about to execute a migration in database "%s" that could result in schema changes and data loss. Are you sure you wish to continue?', + $databaseName === '' ? '' : $databaseName, + ); + if (! $migratorConfiguration->isDryRun() && ! $this->canExecute($question, $input)) { + $this->io->error('Migration cancelled!'); + + return 3; + } + + $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); + + $allowNoMigration = $input->getOption('allow-no-migration'); + $versionAlias = $input->getArgument('version'); + + $path = $input->getOption('write-sql') ?? getcwd(); + + if (is_string($path) && ! $this->isPathWritable($path)) { + $this->io->error(sprintf('The path "%s" not writeable!', $path)); + + return 1; + } + + $migrationRepository = $this->getDependencyFactory()->getMigrationRepository(); + if (count($migrationRepository->getMigrations()) === 0) { + $message = sprintf( + 'The version "%s" couldn\'t be reached, there are no registered migrations.', + $versionAlias, + ); + + if ($allowNoMigration) { + $this->io->warning($message); + + return 0; + } + + $this->io->error($message); + + return 1; + } + + try { + $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias($versionAlias); + } catch (UnknownMigrationVersion) { + $this->io->error(sprintf( + 'Unknown version: %s', + OutputFormatter::escape($versionAlias), + )); + + return 1; + } catch (NoMigrationsToExecute | NoMigrationsFoundWithCriteria) { + return $this->exitForAlias($versionAlias); + } + + $planCalculator = $this->getDependencyFactory()->getMigrationPlanCalculator(); + $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); + $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); + + if ($this->checkExecutedUnavailableMigrations($executedUnavailableMigrations, $input) === false) { + return 3; + } + + $plan = $planCalculator->getPlanUntilVersion($version); + + if (count($plan) === 0) { + return $this->exitForAlias($versionAlias); + } + + $this->getDependencyFactory()->getLogger()->notice( + 'Migrating' . ($migratorConfiguration->isDryRun() ? ' (dry-run)' : '') . ' {direction} to {to}', + [ + 'direction' => $plan->getDirection(), + 'to' => (string) $version, + ], + ); + + $migrator = $this->getDependencyFactory()->getMigrator(); + $sql = $migrator->migrate($plan, $migratorConfiguration); + + if (is_string($path)) { + $writer = $this->getDependencyFactory()->getQueryWriter(); + $writer->write($path, $plan->getDirection(), $sql); + } + + $this->io->success(sprintf( + 'Successfully migrated to version: %s', + $version, + )); + $this->io->newLine(); + + return 0; + } + + private function checkExecutedUnavailableMigrations( + ExecutedMigrationsList $executedUnavailableMigrations, + InputInterface $input, + ): bool { + if (count($executedUnavailableMigrations) !== 0) { + $this->io->warning(sprintf( + 'You have %s previously executed migrations in the database that are not registered migrations.', + count($executedUnavailableMigrations), + )); + + foreach ($executedUnavailableMigrations->getItems() as $executedUnavailableMigration) { + $this->io->text(sprintf( + '>> %s (%s)', + $executedUnavailableMigration->getExecutedAt()?->format('Y-m-d H:i:s'), + $executedUnavailableMigration->getVersion(), + )); + } + + $question = 'Are you sure you wish to continue?'; + + if (! $this->canExecute($question, $input)) { + $this->io->error('Migration cancelled!'); + + return false; + } + } + + return true; + } + + private function exitForAlias(string $versionAlias): int + { + $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias('current'); + + // Allow meaningful message when latest version already reached. + if (in_array($versionAlias, ['current', 'latest', 'first'], true)) { + $message = sprintf( + 'Already at the %s version ("%s")', + $versionAlias, + (string) $version, + ); + + $this->io->success($message); + } elseif (in_array($versionAlias, ['next', 'prev'], true) || str_starts_with($versionAlias, 'current')) { + $message = sprintf( + 'The version "%s" couldn\'t be reached, you are at version "%s"', + $versionAlias, + (string) $version, + ); + + $this->io->error($message); + } else { + $message = sprintf( + 'You are already at version "%s"', + (string) $version, + ); + + $this->io->success($message); + } + + return 0; + } + + private function isPathWritable(string $path): bool + { + return is_writable($path) || is_dir($path) || is_writable(dirname($path)); + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/RollupCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/RollupCommand.php new file mode 100644 index 0000000..1c97985 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/RollupCommand.php @@ -0,0 +1,63 @@ +setAliases(['rollup']) + ->setDescription('Rollup migrations by deleting all tracked versions and insert the one version that exists.') + ->setHelp(<<<'EOT' +The %command.name% command rolls up migrations by deleting all tracked versions and +inserts the one version that exists that was created with the migrations:dump-schema command. + + %command.full_name% + +To dump your schema to a migration version you can use the migrations:dump-schema command. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $question = sprintf( + 'WARNING! You are about to execute a migration in database "%s" that could result in schema changes and data loss. Are you sure you wish to continue?', + $this->getDependencyFactory()->getConnection()->getDatabase() ?? '', + ); + + if (! $this->canExecute($question, $input)) { + $this->io->error('Migration cancelled!'); + + return 3; + } + + $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); + $version = $this->getDependencyFactory()->getRollup()->rollup(); + + $this->io->success(sprintf( + 'Rolled up migrations to version %s', + (string) $version, + )); + + return 0; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/StatusCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/StatusCommand.php new file mode 100644 index 0000000..ce710dc --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/StatusCommand.php @@ -0,0 +1,42 @@ +setAliases(['status']) + ->setDescription('View the status of a set of migrations.') + ->setHelp(<<<'EOT' +The %command.name% command outputs the status of a set of migrations: + + %command.full_name% +EOT); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $infosHelper = $this->getDependencyFactory()->getMigrationStatusInfosHelper(); + $infosHelper->showMigrationsInfo($output); + + return 0; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/SyncMetadataCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/SyncMetadataCommand.php new file mode 100644 index 0000000..3bb68ed --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/SyncMetadataCommand.php @@ -0,0 +1,44 @@ +setAliases(['sync-metadata-storage']) + ->setDescription('Ensures that the metadata storage is at the latest version.') + ->setHelp(<<<'EOT' +The way metadata is stored in the database can change between releases. +The %command.name% command updates metadata storage to the latest version, +ensuring it is ready to receive migrations generated by the current version of Doctrine Migrations. + + + %command.full_name% +EOT); + } + + public function execute( + InputInterface $input, + OutputInterface $output, + ): int { + $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); + + $this->io->success('Metadata storage synchronized'); + + return 0; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/UpToDateCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/UpToDateCommand.php new file mode 100644 index 0000000..dfcf078 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/UpToDateCommand.php @@ -0,0 +1,111 @@ +setAliases(['up-to-date']) + ->setDescription('Tells you if your schema is up-to-date.') + ->addOption('fail-on-unregistered', 'u', InputOption::VALUE_NONE, 'Whether to fail when there are unregistered extra migrations found') + ->addOption('list-migrations', 'l', InputOption::VALUE_NONE, 'Show a list of missing or not migrated versions.') + ->setHelp(<<<'EOT' +The %command.name% command tells you if your schema is up-to-date: + + %command.full_name% +EOT); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); + + $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); + $newMigrations = $statusCalculator->getNewMigrations(); + $newMigrationsCount = count($newMigrations); + $executedUnavailableMigrationsCount = count($executedUnavailableMigrations); + + if ($newMigrationsCount === 0 && $executedUnavailableMigrationsCount === 0) { + $this->io->success('Up-to-date! No migrations to execute.'); + + return 0; + } + + $exitCode = 0; + if ($newMigrationsCount > 0) { + $this->io->error(sprintf( + 'Out-of-date! %u migration%s available to execute.', + $newMigrationsCount, + $newMigrationsCount > 1 ? 's are' : ' is', + )); + $exitCode = 1; + } + + if ($executedUnavailableMigrationsCount > 0) { + $this->io->error(sprintf( + 'You have %1$u previously executed migration%3$s in the database that %2$s registered migration%3$s.', + $executedUnavailableMigrationsCount, + $executedUnavailableMigrationsCount > 1 ? 'are not' : 'is not a', + $executedUnavailableMigrationsCount > 1 ? 's' : '', + )); + if ($input->getOption('fail-on-unregistered')) { + $exitCode = 2; + } + } + + if ($input->getOption('list-migrations')) { + $versions = $this->getSortedVersions($newMigrations, $executedUnavailableMigrations); + $this->getDependencyFactory()->getMigrationStatusInfosHelper()->listVersions($versions, $output); + + $this->io->newLine(); + } + + return $exitCode; + } + + /** @return Version[] */ + private function getSortedVersions(AvailableMigrationsList $newMigrations, ExecutedMigrationsList $executedUnavailableMigrations): array + { + $executedUnavailableVersion = array_map(static fn (ExecutedMigration $executedMigration): Version => $executedMigration->getVersion(), $executedUnavailableMigrations->getItems()); + + $newVersions = array_map(static fn (AvailableMigration $availableMigration): Version => $availableMigration->getVersion(), $newMigrations->getItems()); + + $versions = array_unique(array_merge($executedUnavailableVersion, $newVersions)); + + $comparator = $this->getDependencyFactory()->getVersionComparator(); + uasort($versions, $comparator->compare(...)); + + return $versions; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Command/VersionCommand.php b/vendor/doctrine/migrations/src/Tools/Console/Command/VersionCommand.php new file mode 100644 index 0000000..7e40536 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Command/VersionCommand.php @@ -0,0 +1,260 @@ +setAliases(['version']) + ->setDescription('Manually add and delete migration versions from the version table.') + ->addArgument( + 'version', + InputArgument::OPTIONAL, + 'The version to add or delete.', + null, + ) + ->addOption( + 'add', + null, + InputOption::VALUE_NONE, + 'Add the specified version.', + ) + ->addOption( + 'delete', + null, + InputOption::VALUE_NONE, + 'Delete the specified version.', + ) + ->addOption( + 'all', + null, + InputOption::VALUE_NONE, + 'Apply to all the versions.', + ) + ->addOption( + 'range-from', + null, + InputOption::VALUE_OPTIONAL, + 'Apply from specified version.', + ) + ->addOption( + 'range-to', + null, + InputOption::VALUE_OPTIONAL, + 'Apply to specified version.', + ) + ->setHelp(<<<'EOT' +The %command.name% command allows you to manually add, delete or synchronize migration versions from the version table: + + %command.full_name% MIGRATION-FQCN --add + +If you want to delete a version you can use the --delete option: + + %command.full_name% MIGRATION-FQCN --delete + +If you want to synchronize by adding or deleting all migration versions available in the version table you can use the --all option: + + %command.full_name% --add --all + %command.full_name% --delete --all + +If you want to synchronize by adding or deleting some range of migration versions available in the version table you can use the --range-from/--range-to option: + + %command.full_name% --add --range-from=MIGRATION-FQCN --range-to=MIGRATION-FQCN + %command.full_name% --delete --range-from=MIGRATION-FQCN --range-to=MIGRATION-FQCN + +You can also execute this command without a warning message which you need to interact with: + + %command.full_name% --no-interaction +EOT); + + parent::configure(); + } + + /** @throws InvalidOptionUsage */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($input->getOption('add') === false && $input->getOption('delete') === false) { + throw InvalidOptionUsage::new('You must specify whether you want to --add or --delete the specified version.'); + } + + $this->markMigrated = $input->getOption('add'); + + if ($input->isInteractive()) { + $question = 'WARNING! You are about to add, delete or synchronize migration versions from the version table that could result in data lost. Are you sure you wish to continue?'; + + $confirmation = $this->io->confirm($question); + + if ($confirmation) { + $this->markVersions($input); + } else { + $this->io->error('Migration cancelled!'); + } + } else { + $this->markVersions($input); + } + + return 0; + } + + /** @throws InvalidOptionUsage */ + private function markVersions(InputInterface $input): void + { + $affectedVersion = $input->getArgument('version'); + $allOption = $input->getOption('all'); + $rangeFromOption = $input->getOption('range-from'); + $rangeToOption = $input->getOption('range-to'); + + if ($allOption === true && ($rangeFromOption !== null || $rangeToOption !== null)) { + throw InvalidOptionUsage::new( + 'Options --all and --range-to/--range-from both used. You should use only one of them.', + ); + } + + if ($rangeFromOption !== null xor $rangeToOption !== null) { + throw InvalidOptionUsage::new( + 'Options --range-to and --range-from should be used together.', + ); + } + + $executedMigrations = $this->getDependencyFactory()->getMetadataStorage()->getExecutedMigrations(); + $availableVersions = $this->getDependencyFactory()->getMigrationPlanCalculator()->getMigrations(); + if ($allOption === true) { + if ($input->getOption('delete') === true) { + foreach ($executedMigrations->getItems() as $availableMigration) { + $this->mark($input, $availableMigration->getVersion(), false, $executedMigrations); + } + } + + foreach ($availableVersions->getItems() as $availableMigration) { + $this->mark($input, $availableMigration->getVersion(), true, $executedMigrations); + } + } elseif ($affectedVersion !== null) { + $this->mark($input, new Version($affectedVersion), false, $executedMigrations); + } elseif ($rangeFromOption !== null && $rangeToOption !== null) { + $migrate = false; + foreach ($availableVersions->getItems() as $availableMigration) { + if ((string) $availableMigration->getVersion() === $rangeFromOption) { + $migrate = true; + } + + if ($migrate) { + $this->mark($input, $availableMigration->getVersion(), true, $executedMigrations); + } + + if ((string) $availableMigration->getVersion() === $rangeToOption) { + break; + } + } + } else { + throw InvalidOptionUsage::new('You must specify the version or use the --all argument.'); + } + } + + /** + * @throws VersionAlreadyExists + * @throws VersionDoesNotExist + * @throws UnknownMigrationVersion + */ + private function mark(InputInterface $input, Version $version, bool $all, ExecutedMigrationsList $executedMigrations): void + { + try { + $availableMigration = $this->getDependencyFactory()->getMigrationRepository()->getMigration($version); + } catch (MigrationClassNotFound) { + $availableMigration = null; + } + + $storage = $this->getDependencyFactory()->getMetadataStorage(); + if ($availableMigration === null) { + if ($input->getOption('delete') === false) { + throw UnknownMigrationVersion::new((string) $version); + } + + $question = + 'WARNING! You are about to delete a migration version from the version table that has no corresponding migration file.' . + 'Do you want to delete this migration from the migrations table?'; + + $confirmation = $this->io->confirm($question); + + if ($confirmation) { + $migrationResult = new ExecutionResult($version, Direction::DOWN); + $storage->complete($migrationResult); + $this->io->text(sprintf( + "%s deleted from the version table.\n", + (string) $version, + )); + + return; + } + } + + $marked = false; + + if ($this->markMigrated && $executedMigrations->hasMigration($version)) { + if (! $all) { + throw VersionAlreadyExists::new($version); + } + + $marked = true; + } + + if (! $this->markMigrated && ! $executedMigrations->hasMigration($version)) { + if (! $all) { + throw VersionDoesNotExist::new($version); + } + + $marked = true; + } + + if ($marked === true) { + return; + } + + if ($this->markMigrated) { + $migrationResult = new ExecutionResult($version, Direction::UP); + $storage->complete($migrationResult); + + $this->io->text(sprintf( + "%s added to the version table.\n", + (string) $version, + )); + } else { + $migrationResult = new ExecutionResult($version, Direction::DOWN); + $storage->complete($migrationResult); + + $this->io->text(sprintf( + "%s deleted from the version table.\n", + (string) $version, + )); + } + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/ConsoleInputMigratorConfigurationFactory.php b/vendor/doctrine/migrations/src/Tools/Console/ConsoleInputMigratorConfigurationFactory.php new file mode 100644 index 0000000..a9be446 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/ConsoleInputMigratorConfigurationFactory.php @@ -0,0 +1,85 @@ +hasOption('query-time') ? (bool) $input->getOption('query-time') : false; + $dryRun = $input->hasOption('dry-run') ? (bool) $input->getOption('dry-run') : false; + $allOrNothing = $this->determineAllOrNothingValueFrom($input) ?? $this->configuration->isAllOrNothing(); + + return (new MigratorConfiguration()) + ->setDryRun($dryRun) + ->setTimeAllQueries($timeAllQueries) + ->setAllOrNothing($allOrNothing); + } + + private function determineAllOrNothingValueFrom(InputInterface $input): bool|null + { + $enableAllOrNothingOption = self::ABSENT_CONFIG_VALUE; + $disableAllOrNothingOption = null; + + if ($input->hasOption('no-all-or-nothing')) { + $disableAllOrNothingOption = $input->getOption('no-all-or-nothing'); + } + + $wasOptionExplicitlyPassed = $input->hasOption('all-or-nothing'); + + if ($wasOptionExplicitlyPassed) { + /** + * Due to this option being able to receive optional values, its behavior is tricky: + * - when `--all-or-nothing` option is not provided, the default is set to self::ABSENT_CONFIG_VALUE + * - when `--all-or-nothing` option is provided without values, this will be `null` + * - when `--all-or-nothing` option is provided with a value, we get the provided value + */ + $enableAllOrNothingOption = $input->getOption('all-or-nothing'); + } + + $enableAllOrNothingDeprecation = match ($enableAllOrNothingOption) { + self::ABSENT_CONFIG_VALUE, null => false, + default => true, + }; + + if ($enableAllOrNothingOption !== self::ABSENT_CONFIG_VALUE && $disableAllOrNothingOption === true) { + throw InvalidAllOrNothingConfiguration::new(); + } + + if ($disableAllOrNothingOption === true) { + return false; + } + + if ($enableAllOrNothingDeprecation) { + Deprecation::trigger( + 'doctrine/migrations', + 'https://github.com/doctrine/migrations/issues/1304', + <<<'DEPRECATION' + Context: Passing values to option `--all-or-nothing` + Problem: Passing values is deprecated + Solution: If you need to disable the behavior, use --no-all-or-nothing, + otherwise, pass the option without a value + DEPRECATION, + ); + } + + return match ($enableAllOrNothingOption) { + self::ABSENT_CONFIG_VALUE => null, + null => true, + default => (bool) $enableAllOrNothingOption, + }; + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/ConsoleLogger.php b/vendor/doctrine/migrations/src/Tools/Console/ConsoleLogger.php new file mode 100644 index 0000000..800c20c --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/ConsoleLogger.php @@ -0,0 +1,134 @@ + */ + private array $verbosityLevelMap = [ + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, + LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_VERY_VERBOSE, + ]; + /** @var array */ + private array $formatLevelMap = [ + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ]; + + /** + * @param array $verbosityLevelMap + * @param array $formatLevelMap + */ + public function __construct( + private readonly OutputInterface $output, + array $verbosityLevelMap = [], + array $formatLevelMap = [], + ) { + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritDoc} + * + * @param string|Stringable $message + * @param mixed[] $context + */ + public function log($level, $message, array $context = []): void + { + if (! isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + $output = $this->output; + + // Write to the error output if necessary and available + if ($this->formatLevelMap[$level] === self::ERROR) { + if ($this->output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + } + + // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. + // We only do it for efficiency here as the message formatting is relatively expensive. + if ($output->getVerbosity() < $this->verbosityLevelMap[$level]) { + return; + } + + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); + } + + /** + * Interpolates context values into the message placeholders. + * + * @param mixed[] $context + */ + private function interpolate(string|Stringable $message, array $context): string + { + $message = (string) $message; + if (! str_contains($message, '{')) { + return $message; + } + + $replacements = []; + foreach ($context as $key => $val) { + if ($val === null || is_scalar($val) || $val instanceof Stringable) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(DateTime::RFC3339); + } elseif (is_object($val)) { + $replacements["{{$key}}"] = '[object ' . $val::class . ']'; + } else { + $replacements["{{$key}}"] = '[' . gettype($val) . ']'; + } + + if (! isset($replacements["{{$key}}"])) { + continue; + } + + $replacements["{{$key}}"] = '' . $replacements["{{$key}}"] . ''; + } + + return strtr($message, $replacements); + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/ConsoleRunner.php b/vendor/doctrine/migrations/src/Tools/Console/ConsoleRunner.php new file mode 100644 index 0000000..51c784e --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/ConsoleRunner.php @@ -0,0 +1,154 @@ +run(); + } + + /** @param DoctrineCommand[] $commands */ + public static function createApplication(array $commands = [], DependencyFactory|null $dependencyFactory = null): Application + { + $version = InstalledVersions::getVersion('doctrine/migrations'); + assert($version !== null); + $cli = new Application('Doctrine Migrations', $version); + $cli->setCatchExceptions(true); + self::addCommands($cli, $dependencyFactory); + $cli->addCommands($commands); + + return $cli; + } + + public static function addCommands(Application $cli, DependencyFactory|null $dependencyFactory = null): void + { + $cli->addCommands([ + new CurrentCommand($dependencyFactory), + new DumpSchemaCommand($dependencyFactory), + new ExecuteCommand($dependencyFactory), + new GenerateCommand($dependencyFactory), + new LatestCommand($dependencyFactory), + new MigrateCommand($dependencyFactory), + new RollupCommand($dependencyFactory), + new StatusCommand($dependencyFactory), + new VersionCommand($dependencyFactory), + new UpToDateCommand($dependencyFactory), + new SyncMetadataCommand($dependencyFactory), + new ListCommand($dependencyFactory), + ]); + + if ($dependencyFactory === null || ! $dependencyFactory->hasSchemaProvider()) { + return; + } + + $cli->add(new DiffCommand($dependencyFactory)); + } + + private static function checkLegacyConfiguration(mixed $dependencyFactory, string $configurationFile): mixed + { + if (! ($dependencyFactory instanceof HelperSet)) { + return $dependencyFactory; + } + + $configurations = new ConfigurationFileWithFallback(); + if ($dependencyFactory->has('em') && $dependencyFactory->get('em') instanceof EntityManagerHelper) { + return DependencyFactory::fromEntityManager( + $configurations, + new ExistingEntityManager($dependencyFactory->get('em')->getEntityManager()), + ); + } + + throw new RuntimeException(sprintf( + 'Configuration HelperSet returned by "%s" does not have a valid "em" or the "db" helper.', + $configurationFile, + )); + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Exception/ConsoleException.php b/vendor/doctrine/migrations/src/Tools/Console/Exception/ConsoleException.php new file mode 100644 index 0000000..9ee7a93 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Exception/ConsoleException.php @@ -0,0 +1,11 @@ +areMigrationsOrganizedByYear()) { + $dir .= $this->appendDir(date('Y')); + } + + if ($configuration->areMigrationsOrganizedByYearAndMonth()) { + $dir .= $this->appendDir(date('m')); + } + + $this->createDirIfNotExists($dir); + + return $dir; + } + + private function appendDir(string $dir): string + { + return DIRECTORY_SEPARATOR . $dir; + } + + private function createDirIfNotExists(string $dir): void + { + if (file_exists($dir)) { + return; + } + + mkdir($dir, 0755, true); + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/Helper/MigrationStatusInfosHelper.php b/vendor/doctrine/migrations/src/Tools/Console/Helper/MigrationStatusInfosHelper.php new file mode 100644 index 0000000..d07224d --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/Helper/MigrationStatusInfosHelper.php @@ -0,0 +1,205 @@ +setHeaders( + [ + [new TableCell('Migration Versions', ['colspan' => 4])], + ['Migration', 'Status', 'Migrated At', 'Execution Time', 'Description'], + ], + ); + $executedMigrations = $this->metadataStorage->getExecutedMigrations(); + $availableMigrations = $this->migrationPlanCalculator->getMigrations(); + + foreach ($versions as $version) { + $description = null; + $executedAt = null; + $executionTime = null; + + if ($executedMigrations->hasMigration($version)) { + $executedMigration = $executedMigrations->getMigration($version); + $executionTime = $executedMigration->getExecutionTime(); + $executedAt = $executedMigration->getExecutedAt() instanceof DateTimeInterface + ? $executedMigration->getExecutedAt()->format('Y-m-d H:i:s') + : null; + } + + if ($availableMigrations->hasMigration($version)) { + $description = $availableMigrations->getMigration($version)->getMigration()->getDescription(); + } + + if ($executedMigrations->hasMigration($version) && $availableMigrations->hasMigration($version)) { + $status = 'migrated'; + } elseif ($executedMigrations->hasMigration($version)) { + $status = 'migrated, not available'; + } else { + $status = 'not migrated'; + } + + $table->addRow([ + (string) $version, + $status, + (string) $executedAt, + $executionTime !== null ? $executionTime . 's' : '', + $description, + ]); + } + + $table->render(); + } + + public function showMigrationsInfo(OutputInterface $output): void + { + $executedMigrations = $this->metadataStorage->getExecutedMigrations(); + $availableMigrations = $this->migrationPlanCalculator->getMigrations(); + + $newMigrations = $this->statusCalculator->getNewMigrations(); + $executedUnavailableMigrations = $this->statusCalculator->getExecutedUnavailableMigrations(); + + $storage = $this->configuration->getMetadataStorageConfiguration(); + + $table = new Table($output); + $table->setHeaders( + [ + [new TableCell('Configuration', ['colspan' => 3])], + ], + ); + + $dataGroup = [ + 'Storage' => [ + 'Type' => $storage !== null ? $storage::class : null, + ], + 'Database' => [ + 'Driver' => get_class($this->connection->getDriver()), + 'Name' => $this->connection->getDatabase(), + ], + 'Versions' => [ + 'Previous' => $this->getFormattedVersionAlias('prev', $executedMigrations), + 'Current' => $this->getFormattedVersionAlias('current', $executedMigrations), + 'Next' => $this->getFormattedVersionAlias('next', $executedMigrations), + 'Latest' => $this->getFormattedVersionAlias('latest', $executedMigrations), + ], + + 'Migrations' => [ + 'Executed' => count($executedMigrations), + 'Executed Unavailable' => count($executedUnavailableMigrations) > 0 ? ('' . count($executedUnavailableMigrations) . '') : '0', + 'Available' => count($availableMigrations), + 'New' => count($newMigrations) > 0 ? ('' . count($newMigrations) . '') : '0', + ], + 'Migration Namespaces' => $this->configuration->getMigrationDirectories(), + + ]; + if ($storage instanceof TableMetadataStorageConfiguration) { + $dataGroup['Storage'] += [ + 'Table Name' => $storage->getTableName(), + 'Column Name' => $storage->getVersionColumnName(), + ]; + } + + $first = true; + foreach ($dataGroup as $group => $dataValues) { + $nsRows = []; + foreach ($dataValues as $k => $v) { + $nsRows[] = [ + $k, + $v, + ]; + } + + if (count($nsRows) <= 0) { + continue; + } + + if (! $first) { + $table->addRow([new TableSeparator(['colspan' => 3])]); + } + + $first = false; + array_unshift( + $nsRows[0], + new TableCell('' . $group . '', ['rowspan' => count($dataValues)]), + ); + $table->addRows($nsRows); + } + + $table->render(); + } + + private function getFormattedVersionAlias(string $alias, ExecutedMigrationsList $executedMigrations): string + { + try { + $version = $this->aliasResolver->resolveVersionAlias($alias); + } catch (Throwable) { + $version = null; + } + + // No version found + if ($version === null) { + if ($alias === 'next') { + return 'Already at latest version'; + } + + if ($alias === 'prev') { + return 'Already at first version'; + } + } + + // Before first version "virtual" version number + if ((string) $version === '0') { + return '0'; + } + + // Show normal version number + return sprintf( + '%s ', + (string) $version, + ); + } +} diff --git a/vendor/doctrine/migrations/src/Tools/Console/InvalidAllOrNothingConfiguration.php b/vendor/doctrine/migrations/src/Tools/Console/InvalidAllOrNothingConfiguration.php new file mode 100644 index 0000000..742b6e4 --- /dev/null +++ b/vendor/doctrine/migrations/src/Tools/Console/InvalidAllOrNothingConfiguration.php @@ -0,0 +1,16 @@ +commit(); + } + + public static function rollbackIfInTransaction(Connection $connection): void + { + if (! self::inTransaction($connection)) { + Deprecation::trigger( + 'doctrine/migrations', + 'https://github.com/doctrine/migrations/issues/1169', + <<<'DEPRECATION' +Context: trying to rollback a transaction +Problem: the transaction is already rolled back, relying on silencing is deprecated. +Solution: override `AbstractMigration::isTransactional()` so that it returns false. +Automate that by setting `transactional` to false in the configuration. +More details at https://www.doctrine-project.org/projects/doctrine-migrations/en/stable/explanation/implicit-commits.html +DEPRECATION, + ); + + return; + } + + $connection->rollBack(); + } + + private static function inTransaction(Connection $connection): bool + { + $innermostConnection = self::getInnerConnection($connection); + + /* Attempt to commit or rollback while no transaction is running + results in an exception since PHP 8 + pdo_mysql combination */ + return ! $innermostConnection instanceof PDO || $innermostConnection->inTransaction(); + } + + /** @return object|resource|null */ + private static function getInnerConnection(Connection $connection) + { + try { + return $connection->getNativeConnection(); + } catch (LogicException) { + } + + $innermostConnection = $connection; + while (method_exists($innermostConnection, 'getWrappedConnection')) { + $innermostConnection = $innermostConnection->getWrappedConnection(); + } + + return $innermostConnection; + } +} diff --git a/vendor/doctrine/migrations/src/Version/AliasResolver.php b/vendor/doctrine/migrations/src/Version/AliasResolver.php new file mode 100644 index 0000000..7905803 --- /dev/null +++ b/vendor/doctrine/migrations/src/Version/AliasResolver.php @@ -0,0 +1,28 @@ +metadataStorage->getExecutedMigrations(); + $availableMigration = $this->migrationPlanCalculator->getMigrations(); + + return $executedMigrations->unavailableSubset($availableMigration); + } + + public function getNewMigrations(): AvailableMigrationsList + { + $executedMigrations = $this->metadataStorage->getExecutedMigrations(); + $availableMigration = $this->migrationPlanCalculator->getMigrations(); + + return $availableMigration->newSubset($executedMigrations); + } +} diff --git a/vendor/doctrine/migrations/src/Version/DbalExecutor.php b/vendor/doctrine/migrations/src/Version/DbalExecutor.php new file mode 100644 index 0000000..57712e4 --- /dev/null +++ b/vendor/doctrine/migrations/src/Version/DbalExecutor.php @@ -0,0 +1,324 @@ +sql; + } + + public function addSql(Query $sqlQuery): void + { + $this->sql[] = $sqlQuery; + } + + public function execute( + MigrationPlan $plan, + MigratorConfiguration $configuration, + ): ExecutionResult { + $result = new ExecutionResult($plan->getVersion(), $plan->getDirection(), new DateTimeImmutable()); + + $this->startMigration($plan, $configuration); + + try { + $this->executeMigration( + $plan, + $result, + $configuration, + ); + + $result->setSql($this->sql); + } catch (SkipMigration $e) { + $result->setSkipped(true); + + $this->migrationEnd($e, $plan, $result, $configuration); + } catch (Throwable $e) { + $result->setError(true, $e); + + $this->migrationEnd($e, $plan, $result, $configuration); + + throw $e; + } + + return $result; + } + + private function startMigration( + MigrationPlan $plan, + MigratorConfiguration $configuration, + ): void { + $this->sql = []; + + $this->dispatcher->dispatchVersionEvent( + Events::onMigrationsVersionExecuting, + $plan, + $configuration, + ); + + if (! $plan->getMigration()->isTransactional()) { + return; + } + + // only start transaction if in transactional mode + $this->connection->beginTransaction(); + } + + private function executeMigration( + MigrationPlan $plan, + ExecutionResult $result, + MigratorConfiguration $configuration, + ): ExecutionResult { + $stopwatchEvent = $this->stopwatch->start('execute'); + + $migration = $plan->getMigration(); + $direction = $plan->getDirection(); + + $result->setState(State::PRE); + + $fromSchema = $this->getFromSchema($configuration); + + $migration->{'pre' . ucfirst($direction)}($fromSchema); + + $this->logger->info(...$this->getMigrationHeader($plan, $migration, $direction)); + + $result->setState(State::EXEC); + + $toSchema = $this->schemaProvider->createToSchema($fromSchema); + + $result->setToSchema($toSchema); + + $migration->$direction($toSchema); + + foreach ($migration->getSql() as $sqlQuery) { + $this->addSql($sqlQuery); + } + + foreach ($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema) as $sql) { + $this->addSql(new Query($sql)); + } + + $migration->freeze(); + + if (count($this->sql) !== 0) { + if (! $configuration->isDryRun()) { + $this->executeResult($configuration); + } else { + foreach ($this->sql as $query) { + $this->outputSqlQuery($query, $configuration); + } + } + } else { + $this->logger->warning('Migration {version} was executed but did not result in any SQL statements.', [ + 'version' => (string) $plan->getVersion(), + ]); + } + + $result->setState(State::POST); + + $migration->{'post' . ucfirst($direction)}($toSchema); + + $stopwatchEvent->stop(); + $periods = $stopwatchEvent->getPeriods(); + $lastPeriod = $periods[count($periods) - 1]; + + $result->setTime((float) $lastPeriod->getDuration() / 1000); + $result->setMemory($lastPeriod->getMemory()); + + $params = [ + 'version' => (string) $plan->getVersion(), + 'time' => $lastPeriod->getDuration(), + 'memory' => BytesFormatter::formatBytes($lastPeriod->getMemory()), + 'direction' => $direction === Direction::UP ? 'migrated' : 'reverted', + ]; + + $this->logger->info('Migration {version} {direction} (took {time}ms, used {memory} memory)', $params); + + if (! $configuration->isDryRun()) { + $this->metadataStorage->complete($result); + } elseif (method_exists($this->metadataStorage, 'getSql')) { + foreach ($this->metadataStorage->getSql($result) as $sqlQuery) { + $this->addSql($sqlQuery); + } + } + + if ($migration->isTransactional()) { + TransactionHelper::commitIfInTransaction($this->connection); + } + + $plan->markAsExecuted($result); + $result->setState(State::NONE); + + $this->dispatcher->dispatchVersionEvent( + Events::onMigrationsVersionExecuted, + $plan, + $configuration, + ); + + return $result; + } + + /** @return mixed[] */ + private function getMigrationHeader(MigrationPlan $planItem, AbstractMigration $migration, string $direction): array + { + $versionInfo = (string) $planItem->getVersion(); + $description = $migration->getDescription(); + + if ($description !== '') { + $versionInfo .= ' (' . $description . ')'; + } + + $params = ['version_name' => $versionInfo]; + + if ($direction === Direction::UP) { + return ['++ migrating {version_name}', $params]; + } + + return ['++ reverting {version_name}', $params]; + } + + private function migrationEnd(Throwable $e, MigrationPlan $plan, ExecutionResult $result, MigratorConfiguration $configuration): void + { + $migration = $plan->getMigration(); + if ($migration->isTransactional()) { + //only rollback transaction if in transactional mode + TransactionHelper::rollbackIfInTransaction($this->connection); + } + + $plan->markAsExecuted($result); + $this->logResult($e, $result, $plan); + + $this->dispatcher->dispatchVersionEvent( + Events::onMigrationsVersionSkipped, + $plan, + $configuration, + ); + } + + private function logResult(Throwable $e, ExecutionResult $result, MigrationPlan $plan): void + { + if ($result->isSkipped()) { + $this->logger->notice( + 'Migration {version} skipped during {state}. Reason: "{reason}"', + [ + 'version' => (string) $plan->getVersion(), + 'reason' => $e->getMessage(), + 'state' => $this->getExecutionStateAsString($result->getState()), + ], + ); + } elseif ($result->hasError()) { + $this->logger->error( + 'Migration {version} failed during {state}. Error: "{error}"', + [ + 'version' => (string) $plan->getVersion(), + 'error' => $e->getMessage(), + 'state' => $this->getExecutionStateAsString($result->getState()), + ], + ); + } + } + + private function executeResult(MigratorConfiguration $configuration): void + { + foreach ($this->sql as $key => $query) { + $this->outputSqlQuery($query, $configuration); + + $stopwatchEvent = $this->stopwatch->start('query'); + // executeQuery() must be used here because $query might return a result set, for instance REPAIR does + $this->connection->executeQuery($query->getStatement(), $query->getParameters(), $query->getTypes()); + $stopwatchEvent->stop(); + + if (! $configuration->getTimeAllQueries()) { + continue; + } + + $this->logger->notice('Query took {duration}ms', [ + 'duration' => $stopwatchEvent->getDuration(), + ]); + } + } + + private function outputSqlQuery(Query $query, MigratorConfiguration $configuration): void + { + $params = $this->parameterFormatter->formatParameters( + $query->getParameters(), + $query->getTypes(), + ); + + $this->logger->log( + $configuration->getTimeAllQueries() ? LogLevel::NOTICE : LogLevel::DEBUG, + '{query} {params}', + [ + 'query' => $query->getStatement(), + 'params' => $params, + ], + ); + } + + private function getFromSchema(MigratorConfiguration $configuration): Schema + { + // if we're in a dry run, use the from Schema instead of reading the schema from the database + if ($configuration->isDryRun() && $configuration->getFromSchema() !== null) { + return $configuration->getFromSchema(); + } + + return $this->schemaProvider->createFromSchema(); + } + + private function getExecutionStateAsString(int $state): string + { + return match ($state) { + State::PRE => 'Pre-Checks', + State::POST => 'Post-Checks', + State::EXEC => 'Execution', + default => 'No State', + }; + } +} diff --git a/vendor/doctrine/migrations/src/Version/DbalMigrationFactory.php b/vendor/doctrine/migrations/src/Version/DbalMigrationFactory.php new file mode 100644 index 0000000..b1d1851 --- /dev/null +++ b/vendor/doctrine/migrations/src/Version/DbalMigrationFactory.php @@ -0,0 +1,31 @@ +connection, + $this->logger, + ); + } +} diff --git a/vendor/doctrine/migrations/src/Version/DefaultAliasResolver.php b/vendor/doctrine/migrations/src/Version/DefaultAliasResolver.php new file mode 100644 index 0000000..427227e --- /dev/null +++ b/vendor/doctrine/migrations/src/Version/DefaultAliasResolver.php @@ -0,0 +1,115 @@ +migrationPlanCalculator->getMigrations(); + $executedMigrations = $this->metadataStorage->getExecutedMigrations(); + + switch ($alias) { + case self::ALIAS_FIRST: + case '0': + return new Version('0'); + + case self::ALIAS_CURRENT: + try { + return $executedMigrations->getLast()->getVersion(); + } catch (NoMigrationsFoundWithCriteria) { + return new Version('0'); + } + + // no break because of return + case self::ALIAS_PREV: + try { + return $executedMigrations->getLast(-1)->getVersion(); + } catch (NoMigrationsFoundWithCriteria) { + return new Version('0'); + } + + // no break because of return + case self::ALIAS_NEXT: + $newMigrations = $this->migrationStatusCalculator->getNewMigrations(); + + try { + return $newMigrations->getFirst()->getVersion(); + } catch (NoMigrationsFoundWithCriteria $e) { + throw NoMigrationsToExecute::new($e); + } + + // no break because of return + case self::ALIAS_LATEST: + try { + return $availableMigrations->getLast()->getVersion(); + } catch (NoMigrationsFoundWithCriteria) { + return $this->resolveVersionAlias(self::ALIAS_CURRENT); + } + + // no break because of return + default: + if ($availableMigrations->hasMigration(new Version($alias))) { + return $availableMigrations->getMigration(new Version($alias))->getVersion(); + } + + if (substr($alias, 0, 7) === self::ALIAS_CURRENT) { + $val = (int) substr($alias, 7); + $targetMigration = null; + if ($val > 0) { + $newMigrations = $this->migrationStatusCalculator->getNewMigrations(); + + return $newMigrations->getFirst($val - 1)->getVersion(); + } + + return $executedMigrations->getLast($val)->getVersion(); + } + } + + throw UnknownMigrationVersion::new($alias); + } +} diff --git a/vendor/doctrine/migrations/src/Version/Direction.php b/vendor/doctrine/migrations/src/Version/Direction.php new file mode 100644 index 0000000..6fd2dbd --- /dev/null +++ b/vendor/doctrine/migrations/src/Version/Direction.php @@ -0,0 +1,23 @@ +direction; + } + + public function getExecutedAt(): DateTimeImmutable|null + { + return $this->executedAt; + } + + public function setExecutedAt(DateTimeImmutable $executedAt): void + { + $this->executedAt = $executedAt; + } + + public function getVersion(): Version + { + return $this->version; + } + + public function hasSql(): bool + { + return count($this->sql) !== 0; + } + + /** @return Query[] */ + public function getSql(): array + { + return $this->sql; + } + + /** @param Query[] $sql */ + public function setSql(array $sql): void + { + $this->sql = $sql; + } + + public function getTime(): float|null + { + return $this->time; + } + + public function setTime(float $time): void + { + $this->time = $time; + } + + public function getMemory(): float|null + { + return $this->memory; + } + + public function setMemory(float $memory): void + { + $this->memory = $memory; + } + + public function setSkipped(bool $skipped): void + { + $this->skipped = $skipped; + } + + public function isSkipped(): bool + { + return $this->skipped; + } + + public function setError(bool $error, Throwable|null $exception = null): void + { + $this->error = $error; + $this->exception = $exception; + } + + public function hasError(): bool + { + return $this->error; + } + + public function getException(): Throwable|null + { + return $this->exception; + } + + public function setToSchema(Schema $toSchema): void + { + $this->toSchema = $toSchema; + } + + public function getToSchema(): Schema + { + if ($this->toSchema === null) { + throw new RuntimeException('Cannot call getToSchema() when toSchema is null.'); + } + + return $this->toSchema; + } + + public function getState(): int + { + return $this->state; + } + + public function setState(int $state): void + { + $this->state = $state; + } +} diff --git a/vendor/doctrine/migrations/src/Version/Executor.php b/vendor/doctrine/migrations/src/Version/Executor.php new file mode 100644 index 0000000..646eef1 --- /dev/null +++ b/vendor/doctrine/migrations/src/Version/Executor.php @@ -0,0 +1,21 @@ +arrangeMigrationsForDirection($direction, $this->getMigrations()); + $availableMigrations = array_filter( + $migrationsToCheck, + // in_array third parameter is intentionally false to force object to string casting + static fn (AvailableMigration $availableMigration): bool => in_array($availableMigration->getVersion(), $versions, false), + ); + + $planItems = array_map(static fn (AvailableMigration $availableMigration): MigrationPlan => new MigrationPlan($availableMigration->getVersion(), $availableMigration->getMigration(), $direction), $availableMigrations); + + if (count($planItems) !== count($versions)) { + $plannedVersions = array_map(static fn (MigrationPlan $migrationPlan): Version => $migrationPlan->getVersion(), $planItems); + $diff = array_diff($versions, $plannedVersions); + + throw MigrationClassNotFound::new((string) reset($diff)); + } + + return new MigrationPlanList($planItems, $direction); + } + + public function getPlanUntilVersion(Version $to): MigrationPlanList + { + if ((string) $to !== '0' && ! $this->migrationRepository->hasMigration((string) $to)) { + throw MigrationClassNotFound::new((string) $to); + } + + $availableMigrations = $this->getMigrations(); // migrations are sorted at this point + $executedMigrations = $this->metadataStorage->getExecutedMigrations(); + + $direction = $this->findDirection($to, $executedMigrations, $availableMigrations); + + $migrationsToCheck = $this->arrangeMigrationsForDirection($direction, $availableMigrations); + + $toExecute = $this->findMigrationsToExecute($to, $migrationsToCheck, $direction, $executedMigrations); + + return new MigrationPlanList(array_map(static fn (AvailableMigration $migration): MigrationPlan => new MigrationPlan($migration->getVersion(), $migration->getMigration(), $direction), $toExecute), $direction); + } + + public function getMigrations(): AvailableMigrationsList + { + $availableMigrations = $this->migrationRepository->getMigrations()->getItems(); + uasort($availableMigrations, fn (AvailableMigration $a, AvailableMigration $b): int => $this->sorter->compare($a->getVersion(), $b->getVersion())); + + return new AvailableMigrationsList($availableMigrations); + } + + private function findDirection(Version $to, ExecutedMigrationsList $executedMigrations, AvailableMigrationsList $availableMigrations): string + { + if ((string) $to === '0') { + return Direction::DOWN; + } + + foreach ($availableMigrations->getItems() as $availableMigration) { + if ($availableMigration->getVersion()->equals($to)) { + break; + } + + if (! $executedMigrations->hasMigration($availableMigration->getVersion())) { + return Direction::UP; + } + } + + if ($executedMigrations->hasMigration($to) && ! $executedMigrations->getLast()->getVersion()->equals($to)) { + return Direction::DOWN; + } + + return Direction::UP; + } + + /** @return AvailableMigration[] */ + private function arrangeMigrationsForDirection(string $direction, Metadata\AvailableMigrationsList $availableMigrations): array + { + return $direction === Direction::UP ? $availableMigrations->getItems() : array_reverse($availableMigrations->getItems()); + } + + /** + * @param AvailableMigration[] $migrationsToCheck + * + * @return AvailableMigration[] + */ + private function findMigrationsToExecute(Version $to, array $migrationsToCheck, string $direction, ExecutedMigrationsList $executedMigrations): array + { + $toExecute = []; + foreach ($migrationsToCheck as $availableMigration) { + if ($direction === Direction::DOWN && $availableMigration->getVersion()->equals($to)) { + break; + } + + if ($direction === Direction::UP && ! $executedMigrations->hasMigration($availableMigration->getVersion())) { + $toExecute[] = $availableMigration; + } elseif ($direction === Direction::DOWN && $executedMigrations->hasMigration($availableMigration->getVersion())) { + $toExecute[] = $availableMigration; + } + + if ($direction === Direction::UP && $availableMigration->getVersion()->equals($to)) { + break; + } + } + + return $toExecute; + } +} diff --git a/vendor/doctrine/migrations/src/Version/State.php b/vendor/doctrine/migrations/src/Version/State.php new file mode 100644 index 0000000..4adfa0d --- /dev/null +++ b/vendor/doctrine/migrations/src/Version/State.php @@ -0,0 +1,35 @@ +version; + } + + public function equals(mixed $object): bool + { + return $object instanceof self && $object->version === $this->version; + } +} diff --git a/vendor/doctrine/orm/LICENSE b/vendor/doctrine/orm/LICENSE new file mode 100644 index 0000000..f988839 --- /dev/null +++ b/vendor/doctrine/orm/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Doctrine Project + +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. diff --git a/vendor/doctrine/orm/README.md b/vendor/doctrine/orm/README.md new file mode 100644 index 0000000..1df322c --- /dev/null +++ b/vendor/doctrine/orm/README.md @@ -0,0 +1,40 @@ +| [4.0.x][4.0] | [3.3.x][3.3] | [3.2.x][3.2] | [2.20.x][2.20] | [2.19.x][2.19] | +|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:| +| [![Build status][4.0 image]][4.0] | [![Build status][3.3 image]][3.3] | [![Build status][3.2 image]][3.2] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] | +| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] | + +[

🇺🇦 UKRAINE NEEDS YOUR HELP NOW!

](https://www.doctrine-project.org/stop-war.html) + +Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence +for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features +is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), +inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility +without requiring unnecessary code duplication. + + +## More resources: + +* [Website](http://www.doctrine-project.org) +* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html) + + + [4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x + [4.0]: https://github.com/doctrine/orm/tree/4.0.x + [4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg + [4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x + [3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x + [3.3]: https://github.com/doctrine/orm/tree/3.3.x + [3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg + [3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x + [3.2 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.2.x + [3.2]: https://github.com/doctrine/orm/tree/3.2.x + [3.2 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.2.x/graph/badge.svg + [3.2 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.2.x + [2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x + [2.20]: https://github.com/doctrine/orm/tree/2.20.x + [2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg + [2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x + [2.19 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.19.x + [2.19]: https://github.com/doctrine/orm/tree/2.19.x + [2.19 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.19.x/graph/badge.svg + [2.19 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.19.x diff --git a/vendor/doctrine/orm/SECURITY.md b/vendor/doctrine/orm/SECURITY.md new file mode 100644 index 0000000..b0e7293 --- /dev/null +++ b/vendor/doctrine/orm/SECURITY.md @@ -0,0 +1,17 @@ +Security +======== + +The Doctrine library is operating very close to your database and as such needs +to handle and make assumptions about SQL injection vulnerabilities. + +It is vital that you understand how Doctrine approaches security, because +we cannot protect you from SQL injection. + +Please read the documentation chapter on Security in Doctrine DBAL and ORM to +understand the assumptions we make. + +- [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html) +- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html) + +If you find a Security bug in Doctrine, please follow our +[Security reporting guidelines](https://www.doctrine-project.org/policies/security.html#reporting). diff --git a/vendor/doctrine/orm/UPGRADE.md b/vendor/doctrine/orm/UPGRADE.md new file mode 100644 index 0000000..1869e9f --- /dev/null +++ b/vendor/doctrine/orm/UPGRADE.md @@ -0,0 +1,2303 @@ +# Upgrade to 3.2 + +## Deprecate the `NotSupported` exception + +The class `Doctrine\ORM\Exception\NotSupported` is deprecated without replacement. + +## Deprecate remaining `Serializable` implementation + +Relying on `SequenceGenerator` implementing the `Serializable` is deprecated +because that interface won't be implemented in ORM 4 anymore. + +The following methods are deprecated: + +* `SequenceGenerator::serialize()` +* `SequenceGenerator::unserialize()` + +## `orm:schema-tool:update` option `--complete` is deprecated + +That option behaves as a no-op, and is deprecated. It will be removed in 4.0. + +## Deprecate properties `$indexes` and `$uniqueConstraints` of `Doctrine\ORM\Mapping\Table` + +The properties `$indexes` and `$uniqueConstraints` have been deprecated since they had no effect at all. +The preferred way of defining indices and unique constraints is by +using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\Index` attributes. + +# Upgrade to 3.1 + +## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty` + +This class is deprecated and will be removed in 4.0. +Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from +`doctrine/persistence`. + +## Deprecate passing null to `ClassMetadata::fullyQualifiedClassName()` + +Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is +deprecated and will no longer be possible in 4.0. + +## Deprecate array access + +Using array access on instances of the following classes is deprecated: + +- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping` +- `Doctrine\ORM\Mapping\EmbedClassMapping` +- `Doctrine\ORM\Mapping\FieldMapping` +- `Doctrine\ORM\Mapping\JoinColumnMapping` +- `Doctrine\ORM\Mapping\JoinTableMapping` + +# Upgrade to 3.0 + +## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception + +Previously, calling +`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with +the owning side of an association returned `null`, which was undocumented, and +wrong according to the phpdoc of the parent method. + +If you do not know whether you are on the owning or inverse side of an association, +you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()` +to find out. + +## BC BREAK: `Doctrine\ORM\Proxy\Autoloader` no longer extends `Doctrine\Common\Proxy\Autoloader` + +Make sure to use the former when writing a type declaration or an `instanceof` check. + +## Minor BC BREAK: Changed order of arguments passed to `OneToOne`, `ManyToOne` and `Index` mapping PHP attributes + +To keep PHP mapping attributes consistent, order of arguments passed to above attributes has been changed +so `$targetEntity` is a first argument now. This change affects only non-named arguments usage. + +## BC BREAK: AUTO keyword for identity generation defaults to IDENTITY for PostgreSQL when using `doctrine/dbal` 4 + +When using the `AUTO` strategy to let Doctrine determine the identity generation mechanism for +an entity, and when using `doctrine/dbal` 4, PostgreSQL now uses `IDENTITY` +instead of `SEQUENCE` or `SERIAL`. +* If you want to upgrade your existing tables to identity columns, you will need to follow [migration to identity columns on PostgreSQL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/how-to/postgresql-identity-migration.html) +* If you want to keep using SQL sequences, you need to configure the ORM this way: +```php +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\ORM\Configuration; +use Doctrine\ORM\Mapping\ClassMetadata; + +assert($configuration instanceof Configuration); +$configuration->setIdentityGenerationPreferences([ + PostgreSQLPlatform::CLASS => ClassMetadata::GENERATOR_TYPE_SEQUENCE, +]); +``` + +## BC BREAK: Throw exceptions when using illegal attributes on Embeddable + +There are only a few attributes allowed on an embeddable such as `#[Column]` or +`#[Embedded]`. Previously all others that target entity classes where ignored, +now they throw an exception. + +## BC BREAK: Partial objects are removed + +- The `PARTIAL` keyword in DQL no longer exists. +- `Doctrine\ORM\Query\AST\PartialObjectExpression`is removed. +- `Doctrine\ORM\Query\SqlWalker::HINT_PARTIAL` and + `Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD` are removed. +- `Doctrine\ORM\EntityManager*::getPartialReference()` is removed. + +## BC BREAK: `Doctrine\ORM\Persister\Entity\EntityPersister::executeInserts()` return type changed to `void` + +Implementors should adapt to the new signature, and should call +`UnitOfWork::assignPostInsertId()` for each entry in the previously returned +array. + +## BC BREAK: `Doctrine\ORM\Proxy\ProxyFactory` no longer extends abstract factory from `doctrine/common` + +It is no longer possible to call methods, constants or properties inherited +from that class on a `ProxyFactory` instance. + +`Doctrine\ORM\Proxy\ProxyFactory::createProxyDefinition()` and +`Doctrine\ORM\Proxy\ProxyFactory::resetUninitializedProxy()` are removed as well. + +## BC BREAK: lazy ghosts are enabled unconditionally + +`Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()` and +`Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()` are now no-ops and +will be deprecated in 3.1.0 + +## BC BREAK: collisions in identity map are unconditionally rejected + +`Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` and +`Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()` are now +no-ops and will be deprecated in 3.1.0. + +## BC BREAK: Lifecycle callback mapping on embedded classes is now explicitly forbidden + +Lifecycle callback mapping on embedded classes produced no effect, and is now +explicitly forbidden to point out mistakes. + +## BC BREAK: The `NOTIFY` change tracking policy is removed + +You should use `DEFERRED_EXPLICIT` instead. + +## BC BREAK: `Mapping\Driver\XmlDriver::__construct()` third argument is now enabled by default + +The third argument to +`Doctrine\ORM\Mapping\Driver\XmlDriver::__construct()` was introduced to +let users opt-in to XML validation, that is now always enabled by default. + +As a consequence, the same goes for +`Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver`, and for +`Doctrine\ORM\ORMSetup::createXMLMetadataConfiguration()`. + +## BC BREAK: `Mapping\Driver\AttributeDriver::__construct()` second argument is now a no-op + +The second argument to +`Doctrine\ORM\Mapping\Driver\AttributeDriver::__construct()` was introduced to +let users opt-in to a new behavior, that is now always enforced, regardless of +the value of that argument. + +## BC BREAK: `Query::setDQL()` and `Query::setFirstResult()` no longer accept `null` + +The `$dqlQuery` argument of `Doctrine\ORM\Query::setDQL()` must always be a +string. + +The `$firstResult` argument of `Doctrine\ORM\Query::setFirstResult()` must +always be an integer. + +## BC BREAK: `orm:schema-tool:update` option `--complete` is now a no-op + +`orm:schema-tool:update` now behaves as if `--complete` was provided, +regardless of whether it is provided or not. + +## BC BREAK: Removed `Doctrine\ORM\Proxy\Proxy` interface. + +Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized. + +## BC BREAK: Overriding fields or associations declared in other than mapped superclasses + +As stated in the documentation, fields and associations may only be overridden when being inherited +from mapped superclasses. Overriding them for parent entity classes now throws a `MappingException`. + +## BC BREAK: Undeclared entity inheritance now throws a `MappingException` + +As soon as an entity class inherits from another entity class, inheritance has to +be declared by adding the appropriate configuration for the root entity. + +## Removed `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs` + +Use `getObjectManager()` instead. + +## BC BREAK: Removed `Doctrine\ORM\Mapping\ClassMetadataInfo` class + +Use `Doctrine\ORM\Mapping\ClassMetadata` instead. + +## BC BREAK: Removed `Doctrine\ORM\Event\LifecycleEventArgs` class. + +Use one of the dedicated event classes instead: + +* `Doctrine\ORM\Event\PrePersistEventArgs` +* `Doctrine\ORM\Event\PreUpdateEventArgs` +* `Doctrine\ORM\Event\PreRemoveEventArgs` +* `Doctrine\ORM\Event\PostPersistEventArgs` +* `Doctrine\ORM\Event\PostUpdateEventArgs` +* `Doctrine\ORM\Event\PostRemoveEventArgs` +* `Doctrine\ORM\Event\PostLoadEventArgs` + +## BC BREAK: Removed `AttributeDriver::$entityAnnotationClasses` and `AttributeDriver::getReader()` + +* If you need to change the behavior of `AttributeDriver::isTransient()`, + override that method instead. +* The attribute reader is internal to the driver and should not be accessed from outside. + +## BC BREAK: Removed `Doctrine\ORM\Query\AST\InExpression` + +The AST parser will create a `InListExpression` or a `InSubselectExpression` when +encountering an `IN ()` DQL expression instead of a generic `InExpression`. + +As a consequence, `SqlWalker::walkInExpression()` has been replaced by +`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`. + +## BC BREAK: Changed `EntityManagerInterface#refresh($entity)`, `EntityManagerDecorator#refresh($entity)` and `UnitOfWork#refresh($entity)` signatures + +The new signatures of these methods add an optional `LockMode|int|null $lockMode` +param with default `null` value (no lock). + +## BC Break: Removed AnnotationDriver + +The annotation driver and anything related to annotation has been removed. +Please migrate to another mapping driver. + +The `Doctrine\ORM\Mapping\Annotation` maker interface has been removed in favor of the new +`Doctrine\ORM\Mapping\MappingAttribute` interface. + +## BC BREAK: Removed `EntityManager::create()` + +The constructor of `EntityManager` is now public and must be used instead of the `create()` method. +However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters. +You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the +connection. + +## BC BREAK: Removed `QueryBuilder` methods and constants. + +The following `QueryBuilder` constants and methods have been removed: + +1. `SELECT`, +2. `DELETE`, +3. `UPDATE`, +4. `STATE_DIRTY`, +5. `STATE_CLEAN`, +6. `getState()`, +7. `getType()`. + +## BC BREAK: Omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete` is not supported anymore + +When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted. + +### Before + +```php +$qb = $em->createQueryBuilder() + ->delete('User u') + ->where('u.id = :user_id') + ->setParameter('user_id', 1); +``` + +### After + +```php +$qb = $em->createQueryBuilder() + ->delete('User', 'u') + ->where('u.id = :user_id') + ->setParameter('user_id', 1); +``` + +## BC BREAK: Split output walkers and tree walkers + +`SqlWalker` and its child classes don't implement the `TreeWalker` interface +anymore. + +The following methods have been removed from the `TreeWalker` interface and +from the `TreeWalkerAdapter` and `TreeWalkerChain` classes: + +* `setQueryComponent()` +* `walkSelectClause()` +* `walkFromClause()` +* `walkFunction()` +* `walkOrderByClause()` +* `walkOrderByItem()` +* `walkHavingClause()` +* `walkJoin()` +* `walkSelectExpression()` +* `walkQuantifiedExpression()` +* `walkSubselect()` +* `walkSubselectFromClause()` +* `walkSimpleSelectClause()` +* `walkSimpleSelectExpression()` +* `walkAggregateExpression()` +* `walkGroupByClause()` +* `walkGroupByItem()` +* `walkDeleteClause()` +* `walkUpdateClause()` +* `walkUpdateItem()` +* `walkWhereClause()` +* `walkConditionalExpression()` +* `walkConditionalTerm()` +* `walkConditionalFactor()` +* `walkConditionalPrimary()` +* `walkExistsExpression()` +* `walkCollectionMemberExpression()` +* `walkEmptyCollectionComparisonExpression()` +* `walkNullComparisonExpression()` +* `walkInExpression()` +* `walkInstanceOfExpression()` +* `walkLiteral()` +* `walkBetweenExpression()` +* `walkLikeExpression()` +* `walkStateFieldPathExpression()` +* `walkComparisonExpression()` +* `walkInputParameter()` +* `walkArithmeticExpression()` +* `walkArithmeticTerm()` +* `walkStringPrimary()` +* `walkArithmeticFactor()` +* `walkSimpleArithmeticExpression()` +* `walkPathExpression()` +* `walkResultVariable()` +* `getExecutor()` + +The following changes have been made to the abstract `TreeWalkerAdapter` class: + +* The method `setQueryComponent()` is now protected. +* The method `_getQueryComponents()` has been removed in favor of + `getQueryComponents()`. + +## BC BREAK: Removed identity columns emulation through sequences + +If the platform you are using does not support identity columns, you should +switch to the `SEQUENCE` strategy. + +## BC BREAK: Made setters parameters mandatory + +The following methods require an argument when being called. Pass `null` +instead of omitting the argument. + +* `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()` +* `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()` +* `Doctrine\ORM\AbstractQuery::setResultCache()` +* `Doctrine\ORM\AbstractQuery::setResultCacheProfile()` + +## BC BREAK: New argument to `NamingStrategy::joinColumnName()` + +### Before + +```php + `Exception\MissingMappingDriverImplementation::create()` + * `unrecognizedField()` => `Persisters\Exception\UnrecognizedField::byName()` + * `unexpectedAssociationValue()` => `Exception\UnexpectedAssociationValue::create()` + * `invalidOrientation()` => `Persisters\Exception\InvalidOrientation::fromClassNameAndField()` + * `entityManagerClosed()` => `Exception\EntityManagerClosed::create()` + * `invalidHydrationMode()` => `Exception\InvalidHydrationMode::fromMode()` + * `mismatchedEventManager()` => `Exception\MismatchedEventManager::create()` + * `findByRequiresParameter()` => `Repository\Exception\InvalidMagicMethodCall::onMissingParameter()` + * `invalidMagicCall()` => `Repository\Exception\InvalidMagicMethodCall::becauseFieldNotFoundIn()` + * `invalidFindByInverseAssociation()` => `Repository\Exception\InvalidFindByCall::fromInverseSideUsage()` + * `invalidResultCacheDriver()` => `Cache\Exception\InvalidResultCacheDriver::create()` + * `notSupported()` => `Exception\NotSupported::create()` + * `queryCacheNotConfigured()` => `QueryCacheNotConfigured::create()` + * `metadataCacheNotConfigured()` => `Cache\Exception\MetadataCacheNotConfigured::create()` + * `queryCacheUsesNonPersistentCache()` => `Cache\Exception\QueryCacheUsesNonPersistentCache::fromDriver()` + * `metadataCacheUsesNonPersistentCache()` => `Cache\Exception\MetadataCacheUsesNonPersistentCache::fromDriver()` + * `proxyClassesAlwaysRegenerating()` => `Exception\ProxyClassesAlwaysRegenerating::create()` + * `invalidEntityRepository()` => `Exception\InvalidEntityRepository::fromClassName()` + * `missingIdentifierField()` => `Exception\MissingIdentifierField::fromFieldAndClass()` + * `unrecognizedIdentifierFields()` => `Exception\UnrecognizedIdentifierFields::fromClassAndFieldNames()` + * `cantUseInOperatorOnCompositeKeys()` => `Persisters\Exception\CantUseInOperatorOnCompositeKeys::create()` + +## BC Break: `CacheException` is no longer a class, but an interface + +All methods in `Doctrine\ORM\Cache\CacheException` have been extracted to dedicated exceptions. + + * `updateReadOnlyCollection()` => `Cache\Exception\CannotUpdateReadOnlyCollection::fromEntityAndField()` + * `updateReadOnlyEntity()` => `Cache\Exception\CannotUpdateReadOnlyEntity::fromEntity()` + * `nonCacheableEntity()` => `Cache\Exception\NonCacheableEntity::fromEntity()` + * `nonCacheableEntityAssociation()` => `Cache\Exception\NonCacheableEntityAssociation::fromEntityAndField()` + + +## BC Break: Missing type declaration added for identifier generators + +Although undocumented, it was possible to configure a custom repository +class that implements `ObjectRepository` but does not extend the +`EntityRepository` base class. Repository classes have to extend +`EntityRepository` now. + +## BC BREAK: Removed support for entity namespace alias + +- `EntityManager::getRepository()` no longer accepts the entity namespace alias + notation. +- `Configuration::addEntityNamespace()` and + `Configuration::getEntityNamespace()` have been removed. + +## BC BREAK: Remove helper methods from `AbstractCollectionPersister` + +The following protected methods of +`Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister` +have been removed. + +* `evictCollectionCache()` +* `evictElementCache()` + +## BC BREAK: `Doctrine\ORM\Query\TreeWalkerChainIterator` + +This class has been removed without replacement. + +## BC BREAK: Remove quoting methods from `ClassMetadata` + +The following methods have been removed from the class metadata because +quoting is handled by implementations of `Doctrine\ORM\Mapping\QuoteStrategy`: + +* `getQuotedIdentifierColumnNames()` +* `getQuotedColumnName()` +* `getQuotedTableName()` +* `getQuotedJoinTableName()` + +## BC BREAK: Remove ability to merge detached entities + +Merge semantics was a poor fit for the PHP "share-nothing" architecture. +In addition to that, merging caused multiple issues with data integrity +in the managed entity graph, which was constantly spawning more edge-case +bugs/scenarios. + +The method `UnitOfWork::merge()` has been removed. The method +`EntityManager::merge()` will throw an exception on each call. + +## BC BREAK: Removed ability to partially flush/commit entity manager and unit of work + +The following methods don't accept a single entity or an array of entities anymore: + +* `Doctrine\ORM\EntityManager::flush()` +* `Doctrine\ORM\Decorator\EntityManagerDecorator::flush()` +* `Doctrine\ORM\UnitOfWork::commit()` + +The semantics of `flush()` and `commit()` will remain the same, but the change +tracking will be performed on all entities managed by the unit of work, and not +just on the provided entities, as the parameter is now completely ignored. + +## BC BREAK: Removed ability to partially clear entity manager and unit of work + +* Passing an argument other than `null` to `EntityManager::clear()` will raise + an exception. +* The unit of work cannot be cleared partially anymore. Passing an argument to + `UnitOfWork::clear()` does not have any effect anymore; the unit of work is + cleared completely. +* The method `EntityRepository::clear()` has been removed. +* The methods `getEntityClass()` and `clearsAllEntities()` have been removed + from `OnClearEventArgs`. + +## BC BREAK: Remove support for Doctrine Cache + +The Doctrine Cache library is not supported anymore. The following methods +have been removed from `Doctrine\ORM\Configuration`: + +* `getQueryCacheImpl()` +* `setQueryCacheImpl()` +* `getHydrationCacheImpl()` +* `setHydrationCacheImpl()` +* `getMetadataCacheImpl()` +* `setMetadataCacheImpl()` + +The methods have been replaced by PSR-6 compatible counterparts +(just strip the `Impl` suffix from the old name to get the new one). + +## BC BREAK: Remove `Doctrine\ORM\Configuration::newDefaultAnnotationDriver` + +This functionality has been moved to the new `ORMSetup` class. Call +`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create +a new annotation driver. + +## BC BREAK: Remove `Doctrine\ORM\Tools\Setup` + +In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which +accepted a Doctrine Cache instance in each method has been removed. + +The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6 +cache instead. + +## BC BREAK: Removed named queries + +All APIs related to named queries have been removed. + +## BC BREAK: Remove old cache accessors and mutators from query classes + +The following methods have been removed from `AbstractQuery`: + +* `setResultCacheDriver()` +* `getResultCacheDriver()` +* `useResultCache()` +* `getResultCacheLifetime()` +* `getResultCacheId()` + +The following methods have been removed from `Query`: + +* `setQueryCacheDriver()` +* `getQueryCacheDriver()` + +## BC BREAK: Remove `Doctrine\ORM\Cache\MultiGetRegion` + +The interface has been merged into `Doctrine\ORM\Cache\Region`. + +## BC BREAK: Rename `AbstractIdGenerator::generate()` to `generateId()` + +* Implementations of `AbstractIdGenerator` have to implement the method + `generateId()`. +* The method `generate()` has been removed from `AbstractIdGenerator`. + +## BC BREAK: Remove cache settings inspection + +Doctrine does not provide its own cache implementation anymore and relies on +the PSR-6 standard instead. As a consequence, we cannot determine anymore +whether a given cache adapter is suitable for a production environment. +Because of that, functionality that aims to do so has been removed: + +* `Configuration::ensureProductionSettings()` +* the `orm:ensure-production-settings` console command + +## BC BREAK: PSR-6-based second level cache + +The second level cache has been reworked to consume a PSR-6 cache. Using a +Doctrine Cache instance is not supported anymore. + +* `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as + second argument now. +* `DefaultMultiGetRegion`: This class has been removed. +* `DefaultRegion`: + * The constructor expects a PSR-6 cache item pool as second argument now. + * The protected `$cache` property is removed. + * The properties `$name` and `$lifetime` as well as the constant + `REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are + `private` now. + * The method `getCache()` has been removed. + + +## BC Break: Remove `Doctrine\ORM\Mapping\Driver\PHPDriver` + +Use `StaticPHPDriver` instead when you want to programmatically configure +entity metadata. + +## BC BREAK: Remove `Doctrine\ORM\EntityManagerInterface#transactional()` + +This method has been replaced by `Doctrine\ORM\EntityManagerInterface#wrapInTransaction()`. + +## BC BREAK: Removed support for schema emulation. + +The ORM no longer attempts to emulate schemas on SQLite. + +## BC BREAK: Remove `Setup::registerAutoloadDirectory()` + +Use Composer's autoloader instead. + +## BC BREAK: Remove YAML mapping drivers. + +If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** migrate to +attribute, annotation or XML drivers instead. + +You can use the `orm:convert-mapping` command to convert your metadata mapping to XML +_before_ upgrading to 3.0: + +```sh +php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml +``` + +## BC BREAK: Remove code generators and related console commands + +These console commands have been removed: + +* `orm:convert-d1-schema` +* `orm:convert-mapping` +* `orm:generate:entities` +* `orm:generate-repositories` + +These classes have been deprecated: + +* `Doctrine\ORM\Tools\ConvertDoctrine1Schema` +* `Doctrine\ORM\Tools\EntityGenerator` +* `Doctrine\ORM\Tools\EntityRepositoryGenerator` + +The entire `Doctrine\ORM\Tools\Export` namespace has been removed as well. + +## BC BREAK: Removed `Doctrine\ORM\Version` + +Use Composer's runtime API if you _really_ need to check the version of the ORM package at runtime. + +## BC BREAK: EntityRepository::count() signature change + +The argument `$criteria` of `Doctrine\ORM\EntityRepository::count()` is now +optional. Overrides in child classes should be made compatible. + +## BC BREAK: changes in exception hierarchy + +- `Doctrine\ORM\ORMException` has been removed +- `Doctrine\ORM\Exception\ORMException` is now an interface + +## Variadic methods now use native variadics +The following methods were using `func_get_args()` to simulate a variadic argument: +- `Doctrine\ORM\Query\Expr#andX()` +- `Doctrine\ORM\Query\Expr#orX()` +- `Doctrine\ORM\QueryBuilder#select()` +- `Doctrine\ORM\QueryBuilder#addSelect()` +- `Doctrine\ORM\QueryBuilder#where()` +- `Doctrine\ORM\QueryBuilder#andWhere()` +- `Doctrine\ORM\QueryBuilder#orWhere()` +- `Doctrine\ORM\QueryBuilder#groupBy()` +- `Doctrine\ORM\QueryBuilder#andGroupBy()` +- `Doctrine\ORM\QueryBuilder#having()` +- `Doctrine\ORM\QueryBuilder#andHaving()` +- `Doctrine\ORM\QueryBuilder#orHaving()` +A variadic argument is now actually used in their signatures signature (`...$x`). +Signatures of overridden methods should be changed accordingly + +## Minor BC BREAK: removed `Doctrine\ORM\EntityManagerInterface#copy()` + +Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is removed in 3.0. + +## BC BREAK: Removed classes related to UUID and TABLE generator strategies + +The following classes have been removed: +- `Doctrine\ORM\Id\TableGenerator` +- `Doctrine\ORM\Id\UuidGenerator` + +Using the `UUID` strategy for generating identifiers is not supported anymore. + +## BC BREAK: Removed `Query::iterate()` + +The deprecated method `Query::iterate()` has been removed along with the +following classes and methods: + +- `AbstractHydrator::iterate()` +- `AbstractHydrator::hydrateRow()` +- `IterableResult` + +Use `toIterable()` instead. + +# Upgrade to 2.19 + +## Deprecate calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association + +Calling +`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with +the owning side of an association returns `null`, which is undocumented, and +wrong according to the phpdoc of the parent method. + +If you do not know whether you are on the owning or inverse side of an association, +you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()` +to find out. + +## Deprecate `Doctrine\ORM\Query\Lexer::T_*` constants + +Use `Doctrine\ORM\Query\TokenType::T_*` instead. + +# Upgrade to 2.17 + +## Deprecate annotations classes for named queries + +The following classes have been deprecated: + +* `Doctrine\ORM\Mapping\NamedNativeQueries` +* `Doctrine\ORM\Mapping\NamedNativeQuery` +* `Doctrine\ORM\Mapping\NamedQueries` +* `Doctrine\ORM\Mapping\NamedQuery` + +## Deprecate `Doctrine\ORM\Query\Exec\AbstractSqlExecutor::_sqlStatements` + +Use `Doctrine\ORM\Query\Exec\AbstractSqlExecutor::sqlStatements` instead. + +## Undeprecate `Doctrine\ORM\Proxy\Autoloader` + +It will be a full-fledged class, no longer extending +`Doctrine\Common\Proxy\Autoloader` in 3.0.x. + +## Deprecated: reliance on the non-optimal defaults that come with the `AUTO` identifier generation strategy + +When the `AUTO` identifier generation strategy was introduced, the best +strategy at the time was selected for each database platform. +A lot of time has passed since then, and with ORM 3.0.0 and DBAL 4.0.0, support +for better strategies will be added. + +Because of that, it is now deprecated to rely on the historical defaults when +they differ from what we will be recommended in the future. + +Instead, you should pick a strategy for each database platform you use, and it +will be used when using `AUTO`. As of now, only PostgreSQL is affected by this. + +It is recommended that PostgreSQL users configure their existing and new +applications to use `SEQUENCE` until `doctrine/dbal` 4.0.0 is released: + +```php +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\ORM\Configuration; + +assert($configuration instanceof Configuration); +$configuration->setIdentityGenerationPreferences([ + PostgreSQLPlatform::CLASS => ClassMetadata::GENERATOR_TYPE_SEQUENCE, +]); +``` + +When DBAL 4 is released, `AUTO` will result in `IDENTITY`, and the above +configuration should be removed to migrate to it. + +## Deprecate `EntityManagerInterface::getPartialReference()` + +This method does not have a replacement and will be removed in 3.0. + +## Deprecate not-enabling lazy-ghosts + +Not enabling lazy ghost objects is deprecated. In ORM 3.0, they will be always enabled. +Ensure `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled(true)` is called to enable them. + +# Upgrade to 2.16 + +## Deprecated accepting duplicate IDs in the identity map + +For any given entity class and ID value, there should be only one object instance +representing the entity. + +In https://github.com/doctrine/orm/pull/10785, a check was added that will guard this +in the identity map. The most probable cause for violations of this rule are collisions +of application-provided IDs. + +In ORM 2.16.0, the check was added by throwing an exception. In ORM 2.16.1, this will be +changed to a deprecation notice. ORM 3.0 will make it an exception again. Use +`\Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` if you want to opt-in +to the new mode. + +## Potential changes to the order in which `INSERT`s are executed + +In https://github.com/doctrine/orm/pull/10547, the commit order computation was improved +to fix a series of bugs where a correct (working) commit order was previously not found. +Also, the new computation may get away with fewer queries being executed: By inserting +referred-to entities first and using their ID values for foreign key fields in subsequent +`INSERT` statements, additional `UPDATE` statements that were previously necessary can be +avoided. + +When using database-provided, auto-incrementing IDs, this may lead to IDs being assigned +to entities in a different order than it was previously the case. + +## Deprecated returning post insert IDs from `EntityPersister::executeInserts()` + +Persisters implementing `\Doctrine\ORM\Persisters\Entity\EntityPersister` should no longer +return an array of post insert IDs from their `::executeInserts()` method. Make the +persister call `Doctrine\ORM\UnitOfWork::assignPostInsertId()` instead. + +## Changing the way how reflection-based mapping drivers report fields, deprecated the "old" mode + +In ORM 3.0, a change will be made regarding how the `AttributeDriver` reports field mappings. +This change is necessary to be able to detect (and reject) some invalid mapping configurations. + +To avoid surprises during 2.x upgrades, the new mode is opt-in. It can be activated on the +`AttributeDriver` and `AnnotationDriver` by setting the `$reportFieldsWhereDeclared` +constructor parameter to `true`. It will cause `MappingException`s to be thrown when invalid +configurations are detected. + +Not enabling the new mode will cause a deprecation notice to be raised. In ORM 3.0, +only the new mode will be available. + +# Upgrade to 2.15 + +## Deprecated configuring `JoinColumn` on the inverse side of one-to-one associations + +For one-to-one associations, the side using the `mappedBy` attribute is the inverse side. +The owning side is the entity with the table containing the foreign key. Using `JoinColumn` +configuration on the _inverse_ side now triggers a deprecation notice and will be an error +in 3.0. + +## Deprecated overriding fields or associations not declared in mapped superclasses + +As stated in the documentation, fields and associations may only be overridden when being inherited +from mapped superclasses. Overriding them for parent entity classes now triggers a deprecation notice +and will be an error in 3.0. + +## Deprecated undeclared entity inheritance + +As soon as an entity class inherits from another entity class, inheritance has to +be declared by adding the appropriate configuration for the root entity. + +## Deprecated stubs for "concrete table inheritance" + +This third way of mapping class inheritance was never implemented. Code stubs are +now deprecated and will be removed in 3.0. + +* `\Doctrine\ORM\Mapping\ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS` constant +* `\Doctrine\ORM\Mapping\ClassMetadataInfo::isInheritanceTypeTablePerClass()` method +* Using `TABLE_PER_CLASS` as the value for the `InheritanceType` attribute or annotation + or in XML configuration files. + +# Upgrade to 2.14 + +## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method. + +Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead. + +## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator` + +The following public constants have been deprecated: + +* `CommitOrderCalculator::NOT_VISITED` +* `CommitOrderCalculator::IN_PROGRESS` +* `CommitOrderCalculator::VISITED` + +These constants were used for internal purposes. Relying on them is discouraged. + +## Deprecated `Doctrine\ORM\Query\AST\InExpression` + +The AST parser will create a `InListExpression` or a `InSubselectExpression` when +encountering an `IN ()` DQL expression instead of a generic `InExpression`. + +As a consequence, `SqlWalker::walkInExpression()` has been deprecated in favor of +`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`. + +## Deprecated constructing a `CacheKey` without `$hash` + +The `Doctrine\ORM\Cache\CacheKey` class has an explicit constructor now with +an optional parameter `$hash`. That parameter will become mandatory in 3.0. + +## Deprecated `AttributeDriver::$entityAnnotationClasses` + +If you need to change the behavior of `AttributeDriver::isTransient()`, +override that method instead. + +## Deprecated incomplete schema updates + +Using `orm:schema-tool:update` without passing the `--complete` flag is +deprecated. Use schema asset filtering if you need to preserve assets not +managed by DBAL. + +Likewise, calling `SchemaTool::updateSchema()` or +`SchemaTool::getUpdateSchemaSql()` with a second argument is deprecated. + +## Deprecated annotation mapping driver. + +Please switch to one of the other mapping drivers. Native attributes which PHP +supports since version 8.0 are probably your best option. + +As a consequence, the following methods are deprecated: +- `ORMSetup::createAnnotationMetadataConfiguration` +- `ORMSetup::createDefaultAnnotationDriver` + +The marker interface `Doctrine\ORM\Mapping\Annotation` is deprecated as well. +All annotation/attribute classes implement +`Doctrine\ORM\Mapping\MappingAttribute` now. + +## Deprecated `Doctrine\ORM\Proxy\Proxy` interface. + +Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized. + +## Deprecated `Doctrine\ORM\Event\LifecycleEventArgs` class. + +It will be removed in 3.0. Use one of the dedicated event classes instead: + +* `Doctrine\ORM\Event\PrePersistEventArgs` +* `Doctrine\ORM\Event\PreUpdateEventArgs` +* `Doctrine\ORM\Event\PreRemoveEventArgs` +* `Doctrine\ORM\Event\PostPersistEventArgs` +* `Doctrine\ORM\Event\PostUpdateEventArgs` +* `Doctrine\ORM\Event\PostRemoveEventArgs` +* `Doctrine\ORM\Event\PostLoadEventArgs` + +# Upgrade to 2.13 + +## Deprecated `EntityManager::create()` + +The constructor of `EntityManager` is now public and should be used instead of the `create()` method. +However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters. +You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the +connection. + +## Deprecated `QueryBuilder` methods and constants. + +1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern. +2. Relying on the type of the query being built by using `QueryBuilder::getType()` has been deprecated. + If necessary, track the type of the query being built outside of the builder. + +The following `QueryBuilder` constants related to the above methods have been deprecated: + +1. `SELECT`, +2. `DELETE`, +3. `UPDATE`, +4. `STATE_DIRTY`, +5. `STATE_CLEAN`. + +## Deprecated omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete` + +When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted. + +### Before + +```php +$qb = $em->createQueryBuilder() + ->delete('User u') + ->where('u.id = :user_id') + ->setParameter('user_id', 1); +``` + +### After + +```php +$qb = $em->createQueryBuilder() + ->delete('User', 'u') + ->where('u.id = :user_id') + ->setParameter('user_id', 1); +``` + +## Deprecated using the `IDENTITY` identifier strategy on platform that do not support identity columns + +If identity columns are emulated with sequences on the platform you are using, +you should switch to the `SEQUENCE` strategy. + +## Deprecated passing `null` to `Doctrine\ORM\Query::setFirstResult()` + +`$query->setFirstResult(null);` is equivalent to `$query->setFirstResult(0)`. + +## Deprecated calling setters without arguments + +The following methods will require an argument in 3.0. Pass `null` instead of +omitting the argument. + +* `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()` +* `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()` +* `Doctrine\ORM\AbstractQuery::setResultCache()` +* `Doctrine\ORM\AbstractQuery::setResultCacheProfile()` + +## Deprecated passing invalid fetch modes to `AbstractQuery::setFetchMode()` + +Calling `AbstractQuery::setFetchMode()` with anything else than +`Doctrine\ORM\Mapping::FETCH_EAGER` results in +`Doctrine\ORM\Mapping::FETCH_LAZY` being used. Relying on that behavior is +deprecated and will result in an exception in 3.0. + +## Deprecated `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs` + +This method has been deprecated in: + +* `Doctrine\ORM\Event\OnClearEventArgs` +* `Doctrine\ORM\Event\OnFlushEventArgs` +* `Doctrine\ORM\Event\PostFlushEventArgs` +* `Doctrine\ORM\Event\PreFlushEventArgs` + +It will be removed in 3.0. Use `getObjectManager()` instead. + +## Prepare split of output walkers and tree walkers + +In 3.0, `SqlWalker` and its child classes won't implement the `TreeWalker` +interface anymore. Relying on that inheritance is deprecated. + +The following methods of the `TreeWalker` interface have been deprecated: + +* `setQueryComponent()` +* `walkSelectClause()` +* `walkFromClause()` +* `walkFunction()` +* `walkOrderByClause()` +* `walkOrderByItem()` +* `walkHavingClause()` +* `walkJoin()` +* `walkSelectExpression()` +* `walkQuantifiedExpression()` +* `walkSubselect()` +* `walkSubselectFromClause()` +* `walkSimpleSelectClause()` +* `walkSimpleSelectExpression()` +* `walkAggregateExpression()` +* `walkGroupByClause()` +* `walkGroupByItem()` +* `walkDeleteClause()` +* `walkUpdateClause()` +* `walkUpdateItem()` +* `walkWhereClause()` +* `walkConditionalExpression()` +* `walkConditionalTerm()` +* `walkConditionalFactor()` +* `walkConditionalPrimary()` +* `walkExistsExpression()` +* `walkCollectionMemberExpression()` +* `walkEmptyCollectionComparisonExpression()` +* `walkNullComparisonExpression()` +* `walkInExpression()` +* `walkInstanceOfExpression()` +* `walkLiteral()` +* `walkBetweenExpression()` +* `walkLikeExpression()` +* `walkStateFieldPathExpression()` +* `walkComparisonExpression()` +* `walkInputParameter()` +* `walkArithmeticExpression()` +* `walkArithmeticTerm()` +* `walkStringPrimary()` +* `walkArithmeticFactor()` +* `walkSimpleArithmeticExpression()` +* `walkPathExpression()` +* `walkResultVariable()` +* `getExecutor()` + +The following changes have been made to the abstract `TreeWalkerAdapter` class: + +* All implementations of now-deprecated `TreeWalker` methods have been + deprecated as well. +* The method `setQueryComponent()` will become protected in 3.0. Calling it + publicly is deprecated. +* The method `_getQueryComponents()` is deprecated, call `getQueryComponents()` + instead. + +On the `TreeWalkerChain` class, all implementations of now-deprecated +`TreeWalker` methods have been deprecated as well. However, `SqlWalker` is +unaffected by those deprecations and will continue to implement all of those +methods. + +## Deprecated passing `null` to `Doctrine\ORM\Query::setDQL()` + +Doing `$query->setDQL(null);` achieves nothing. + +## Deprecated omitting second argument to `NamingStrategy::joinColumnName` + +When implementing `NamingStrategy`, it is deprecated to implement +`joinColumnName()` with only one argument. + +### Before + +```php +getConfiguration(); +-$config->addEntityNamespace('CMS', 'My\App\Cms'); ++use My\App\Cms\CmsUser; + +-$entityManager->getRepository('CMS:CmsUser'); ++$entityManager->getRepository(CmsUser::class); +``` + +## Deprecate `AttributeDriver::getReader()` and `AnnotationDriver::getReader()` + +That method was inherited from the abstract `AnnotationDriver` class of +`doctrine/persistence`, and does not seem to serve any purpose. + +## Un-deprecate `Doctrine\ORM\Proxy\Proxy` + +Because no forward-compatible new proxy solution had been implemented yet, the +current proxy mechanism is not considered deprecated anymore for the time +being. This applies to the following interfaces/classes: + +* `Doctrine\ORM\Proxy\Proxy` +* `Doctrine\ORM\Proxy\ProxyFactory` + +These methods have been un-deprecated: + +* `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()` +* `Doctrine\ORM\Configuration::getProxyDir()` +* `Doctrine\ORM\Configuration::getProxyNamespace()` + +Note that the `Doctrine\ORM\Proxy\Autoloader` remains deprecated and will be removed in 3.0. + +## Deprecate helper methods from `AbstractCollectionPersister` + +The following protected methods of +`Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister` +are not in use anymore and will be removed. + +* `evictCollectionCache()` +* `evictElementCache()` + +## Deprecate `Doctrine\ORM\Query\TreeWalkerChainIterator` + +This class won't have a replacement. + +## Deprecate `OnClearEventArgs::getEntityClass()` and `OnClearEventArgs::clearsAllEntities()` + +These methods will be removed in 3.0 along with the ability to partially clear +the entity manager. + +## Deprecate `Doctrine\ORM\Configuration::newDefaultAnnotationDriver` + +This functionality has been moved to the new `ORMSetup` class. Call +`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create +a new annotation driver. + +## Deprecate `Doctrine\ORM\Tools\Setup` + +In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which +accepted a Doctrine Cache instance in each method has been deprecated. + +The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6 +cache instead. + +## Deprecate `Doctrine\ORM\Cache\MultiGetRegion` + +The interface will be merged with `Doctrine\ORM\Cache\Region` in 3.0. + +# Upgrade to 2.11 + +## Rename `AbstractIdGenerator::generate()` to `generateId()` + +Implementations of `AbstractIdGenerator` have to override the method +`generateId()` without calling the parent implementation. Not doing so is +deprecated. Calling `generate()` on any `AbstractIdGenerator` implementation +is deprecated. + +## PSR-6-based second level cache + +The second level cache has been reworked to consume a PSR-6 cache. Using a +Doctrine Cache instance is deprecated. + +* `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as + second argument now. +* `DefaultMultiGetRegion`: This class is deprecated in favor of `DefaultRegion`. +* `DefaultRegion`: + * The constructor expects a PSR-6 cache item pool as second argument now. + * The protected `$cache` property is deprecated. + * The properties `$name` and `$lifetime` as well as the constant + `REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are flagged as + `@internal` now. They all will become `private` in 3.0. + * The method `getCache()` is deprecated without replacement. + +## Deprecated: `Doctrine\ORM\Mapping\Driver\PHPDriver` + +Use `StaticPHPDriver` instead when you want to programmatically configure +entity metadata. + +You can convert mappings with the `orm:convert-mapping` command or more simply +in this case, `include` the metadata file from the `loadMetadata` static method +used by the `StaticPHPDriver`. + +## Deprecated: `Setup::registerAutoloadDirectory()` + +Use Composer's autoloader instead. + +## Deprecated: `AbstractHydrator::hydrateRow()` + +Following the deprecation of the method `AbstractHydrator::iterate()`, the +method `hydrateRow()` has been deprecated as well. + +## Deprecate cache settings inspection + +Doctrine does not provide its own cache implementation anymore and relies on +the PSR-6 standard instead. As a consequence, we cannot determine anymore +whether a given cache adapter is suitable for a production environment. +Because of that, functionality that aims to do so has been deprecated: + +* `Configuration::ensureProductionSettings()` +* the `orm:ensure-production-settings` console command + +# Upgrade to 2.10 + +## BC Break: `UnitOfWork` now relies on SPL object IDs, not hashes + +When calling the following methods, you are now supposed to use the result of +`spl_object_id()`, and not `spl_object_hash()`: +- `UnitOfWork::clearEntityChangeSet()` +- `UnitOfWork::setOriginalEntityProperty()` + +## BC Break: Removed `TABLE` id generator strategy + +The implementation was unfinished for 14 years. +It is now deprecated to rely on: +- `Doctrine\ORM\Id\TableGenerator`; +- `Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_TABLE`; +- `Doctrine\ORM\Mapping\ClassMetadata::$tableGeneratorDefinition`; +- or `Doctrine\ORM\Mapping\ClassMetadata::isIdGeneratorTable()`. + +## New method `Doctrine\ORM\EntityManagerInterface#wrapInTransaction($func)` + +Works the same as `Doctrine\ORM\EntityManagerInterface#transactional()` but returns any value returned from `$func` closure rather than just _non-empty value returned from the closure or true_. + +Because of BC policy, the method does not exist on the interface yet. This is the example of safe usage: + +```php +function foo(EntityManagerInterface $entityManager, callable $func) { + if (method_exists($entityManager, 'wrapInTransaction')) { + return $entityManager->wrapInTransaction($func); + } + + return $entityManager->transactional($func); +} +``` + +`Doctrine\ORM\EntityManagerInterface#transactional()` has been deprecated. + +## Minor BC BREAK: some exception methods have been removed + +The following methods were not in use and are very unlikely to be used by +downstream packages or applications, and were consequently removed: + +- `ORMException::entityMissingForeignAssignedId` +- `ORMException::entityMissingAssignedIdForField` +- `ORMException::invalidFlushMode` + +## Deprecated: database-side UUID generation + +[DB-generated UUIDs are deprecated as of `doctrine/dbal` 2.8][DBAL deprecation]. +As a consequence, using the `UUID` strategy for generating identifiers is deprecated as well. +Furthermore, relying on the following classes and methods is deprecated: + +- `Doctrine\ORM\Id\UuidGenerator` +- `Doctrine\ORM\Mapping\ClassMetadataInfo::isIdentifierUuid()` + +[DBAL deprecation]: https://github.com/doctrine/dbal/pull/3212 + +## Minor BC BREAK: Custom hydrators and `toIterable()` + +The type declaration of the `$stmt` parameter of `AbstractHydrator::toIterable()` has been removed. This change might +break custom hydrator implementations that override this very method. + +Overriding this method is not recommended, which is why the method is documented as `@final` now. + +```diff +- public function toIterable(ResultStatement $stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable ++ public function toIterable($stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable +``` + +## Deprecated: Entity Namespace Aliases + +Entity namespace aliases are deprecated, use the magic ::class constant to abbreviate full class names +in EntityManager, EntityRepository and DQL. + +```diff +- $entityManager->find('MyBundle:User', $id); ++ $entityManager->find(User::class, $id); +``` + +# Upgrade to 2.9 + +## Minor BC BREAK: Setup tool needs cache implementation + +With the deprecation of doctrine/cache, the setup tool might no longer work as expected without a different cache +implementation. To work around this: +* Install symfony/cache: `composer require symfony/cache`. This will keep previous behaviour without any changes +* Instantiate caches yourself: to use a different cache implementation, pass a cache instance when calling any + configuration factory in the setup tool: + ```diff + - $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir); + + $cache = \Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap($anyPsr6Implementation); + + $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir, $cache); + ``` +* As a quick workaround, you can lock the doctrine/cache dependency to work around this: `composer require doctrine/cache ^1.11`. + Note that this is only recommended as a bandaid fix, as future versions of ORM will no longer work with doctrine/cache + 1.11. + +## Deprecated: doctrine/cache for metadata caching + +The `Doctrine\ORM\Configuration#setMetadataCacheImpl()` method is deprecated and should no longer be used. Please use +`Doctrine\ORM\Configuration#setMetadataCache()` with any PSR-6 cache adapter instead. + +## Removed: flushing metadata cache + +To support PSR-6 caches, the `--flush` option for the `orm:clear-cache:metadata` command is ignored. Metadata cache is +now always cleared regardless of the cache adapter being used. + +# Upgrade to 2.8 + +## Minor BC BREAK: Failed commit now throw OptimisticLockException + +Method `Doctrine\ORM\UnitOfWork#commit()` can throw an OptimisticLockException when a commit silently fails and returns false +since `Doctrine\DBAL\Connection#commit()` signature changed from returning void to boolean + +## Deprecated: `Doctrine\ORM\AbstractQuery#iterate()` + +The method `Doctrine\ORM\AbstractQuery#iterate()` is deprecated in favor of `Doctrine\ORM\AbstractQuery#toIterable()`. +Note that `toIterable()` yields results of the query, unlike `iterate()` which yielded each result wrapped into an array. + +# Upgrade to 2.7 + +## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods + +Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache +(depending on passed flag) was split into two. + +## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results + +To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to +perform the pagination with join collections when max results isn't set in the query. + +## Minor BC BREAK: tables filtered with `schema_filter` are no longer created + +When generating schema diffs, if a source table is filtered out by a `schema_filter` expression, then a `CREATE TABLE` was +always generated, even if the table already existed. This has been changed in this release and the table will no longer +be created. + +## Deprecated number unaware `Doctrine\ORM\Mapping\UnderscoreNamingStrategy` + +In the last patch of the `v2.6.x` series, we fixed a bug that was not converting names properly when they had numbers +(e.g.: `base64Encoded` was wrongly converted to `base64encoded` instead of `base64_encoded`). + +In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This +argument will be removed in 3.0 and the default behavior will be the fixed one. + +## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()` + +Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()` +and `disableResultCache()`. It will be removed in 3.0. + +## Deprecated code generators and related console commands + +These console commands have been deprecated: + + * `orm:convert-mapping` + * `orm:generate:entities` + * `orm:generate-repositories` + +These classes have been deprecated: + + * `Doctrine\ORM\Tools\EntityGenerator` + * `Doctrine\ORM\Tools\EntityRepositoryGenerator` + +Whole Doctrine\ORM\Tools\Export namespace with all its members have been deprecated as well. + +## Deprecated `Doctrine\ORM\Proxy\Proxy` marker interface + +Proxy objects in Doctrine ORM 3.0 will no longer implement `Doctrine\ORM\Proxy\Proxy` nor +`Doctrine\Persistence\Proxy`: instead, they implement +`ProxyManager\Proxy\GhostObjectInterface`. + +These related classes have been deprecated: + + * `Doctrine\ORM\Proxy\ProxyFactory` + * `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead + +These methods have been deprecated: + + * `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()` + * `Doctrine\ORM\Configuration#getProxyDir()` + * `Doctrine\ORM\Configuration#getProxyNamespace()` + +## Deprecated `Doctrine\ORM\Version` + +The `Doctrine\ORM\Version` class is now deprecated and will be removed in Doctrine ORM 3.0: +please refrain from checking the ORM version at runtime or use Composer's [runtime API](https://getcomposer.org/doc/07-runtime.md#knowing-whether-package-x-is-installed-in-version-y). + +## Deprecated `EntityManager#merge()` method + +Merge semantics was a poor fit for the PHP "share-nothing" architecture. +In addition to that, merging caused multiple issues with data integrity +in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios. + +The following API methods were therefore deprecated: + +* `EntityManager#merge()` +* `UnitOfWork#merge()` + +An alternative to `EntityManager#merge()` will not be provided by ORM 3.0, since the merging +semantics should be part of the business domain rather than the persistence domain of an +application. If your application relies heavily on CRUD-alike interactions and/or `PATCH` +restful operations, you should look at alternatives such as [JMSSerializer](https://github.com/schmittjoh/serializer). + +## Extending `EntityManager` is deprecated + +Final keyword will be added to the `EntityManager::class` in Doctrine ORM 3.0 in order to ensure that EntityManager + is not used as valid extension point. Valid extension point should be EntityManagerInterface. + +## Deprecated `EntityManager#clear($entityName)` + +If your code relies on clearing a single entity type via `EntityManager#clear($entityName)`, +the signature has been changed to `EntityManager#clear()`. + +The main reason is that partial clears caused multiple issues with data integrity +in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios. + +## Deprecated `EntityManager#flush($entity)` and `EntityManager#flush($entities)` + +If your code relies on single entity flushing optimisations via +`EntityManager#flush($entity)`, the signature has been changed to +`EntityManager#flush()`. + +Said API was affected by multiple data integrity bugs due to the fact +that change tracking was being restricted upon a subset of the managed +entities. The ORM cannot support committing subsets of the managed +entities while also guaranteeing data integrity, therefore this +utility was removed. + +The `flush()` semantics will remain the same, but the change tracking will be performed +on all entities managed by the unit of work, and not just on the provided +`$entity` or `$entities`, as the parameter is now completely ignored. + +The same applies to `UnitOfWork#commit($entity)`, which will simply be +`UnitOfWork#commit()`. + +If you would still like to perform batching operations over small `UnitOfWork` +instances, it is suggested to follow these paths instead: + + * eagerly use `EntityManager#clear()` in conjunction with a specific second level + cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/second-level-cache.html) + * use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/change-tracking-policies.html) + +## Deprecated `YAML` mapping drivers. + +If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** change to +annotation or XML drivers instead. + +## Deprecated: `Doctrine\ORM\EntityManagerInterface#copy()` + +Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is deprecated. +It will be removed in 3.0. + +# Upgrade to 2.6 + +## Added `Doctrine\ORM\EntityRepository::count()` method + +`Doctrine\ORM\EntityRepository::count()` has been added. This new method has different +signature than `Countable::count()` (required parameter) and therefore are not compatible. +If your repository implemented the `Countable` interface, you will have to use +`$repository->count([])` instead and not implement `Countable` interface anymore. + +## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final + +Since it's just an utilitarian class and should not be inherited. + +## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()` + +Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()` +now has a required parameter `$pathExpr`. + +## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()` + +Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because +the distinction between internal function and user defined DQL was removed. +[#6500](https://github.com/doctrine/orm/pull/6500) + +## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()` + +Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was +removed because of the choice to allow users to overwrite internal functions, ie +`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500) + +## PHP 7.1 is now required + +Doctrine 2.6 now requires PHP 7.1 or newer. + +As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed: +- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc). +- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache). +- XCache support was dropped as it doesn't work with PHP 7. + +# Upgrade to 2.5 + +## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` + +Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part +of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600). + +## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()` + +As `$className` parameter was not used in the method, it was safely removed. + +## Minor BC BREAK: query cache key time is now a float + +As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value +instead of an integer in order to have more precision and also to be consistent +with the `TimestampCacheEntry#time`. + +## Minor BC BREAK: discriminator map must now include all non-transient classes + +It is now required that you declare the root of an inheritance in the +discriminator map. + +When declaring an inheritance map, it was previously possible to skip the root +of the inheritance in the discriminator map. This was actually a validation +mistake by Doctrine2 and led to problems when trying to persist instances of +that class. + +If you don't plan to persist instances some classes in your inheritance, then +either: + + - make those classes `abstract` + - map those classes as `MappedSuperclass` + +## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints + +As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require +an ``EntityManagerInterface`` instead. +If you are extending any of the following classes, then you need to check following +signatures: + +- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)`` +- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)`` + +## Minor BC BREAK: Custom Hydrators API change + +As of 2.5, `AbstractHydrator` does not enforce the usage of cache as part of +API, and now provides you a clean API for column information through the method +`hydrateColumnInfo($column)`. +Cache variable being passed around by reference is no longer needed since +Hydrators are per query instantiated since Doctrine 2.4. + +## Minor BC BREAK: Entity based ``EntityManager#clear()`` calls follow cascade detach + +Whenever ``EntityManager#clear()`` method gets called with a given entity class +name, until 2.4, it was only detaching the specific requested entity. +As of 2.5, ``EntityManager`` will follow configured cascades, providing a better +memory management since associations will be garbage collected, optimizing +resources consumption on long running jobs. + +## BC BREAK: NamingStrategy interface changes + +1. A new method ``embeddedFieldToColumnName($propertyName, $embeddedColumnName)`` + +This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you +now also need to implement this new method. + +2. A change to method ``joinColumnName()`` to include the $className + +## Updates on entities scheduled for deletion are no longer processed + +In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would +produce an UPDATE statement to be executed right before the DELETE statement. The entity in question +was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate`` +listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo +the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both +``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset +calculation logic is optimized away. + +## Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures + +A misconception concerning default lock mode values in method signatures lead to unexpected behaviour +in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the +method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related +queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)`` +table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED +instead of the default READ COMMITTED transaction isolation level. +Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell +Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following +method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``: + +- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()`` +- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()`` +- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()`` +- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()`` +- ``Doctrine\ORM\EntityManager#find()`` +- ``Doctrine\ORM\EntityRepository#find()`` +- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()`` +- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()`` +- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()`` +- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()`` +- ``Doctrine\ORM\Persisters\EntityPersister#load()`` +- ``Doctrine\ORM\Persisters\EntityPersister#refresh()`` +- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()`` + +You should update signatures for these methods if you have subclassed one of the above classes. +Please also check the calling code of these methods in your application and update if necessary. + +**Note:** +This in fact is really a minor BC BREAK and should not have any affect on database vendors +other than SQL Server because it is the only one that supports and therefore cares about +``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM. + +## Minor BC BREAK: `__clone` method not called anymore when entities are instantiated via metadata API + +As of PHP 5.6, instantiation of new entities is deferred to the +[`doctrine/instantiator`](https://github.com/doctrine/instantiator) library, which will avoid calling `__clone` +or any public API on instantiated objects. + +## BC BREAK: `Doctrine\ORM\Repository\DefaultRepositoryFactory` is now `final` + +Please implement the `Doctrine\ORM\Repository\RepositoryFactory` interface instead of extending +the `Doctrine\ORM\Repository\DefaultRepositoryFactory`. + +## BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields + +When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query: + + SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true + +Previously, your result would be similar to this: + + array( + 0=>array( + 0=>{UserDTO object}, + 1=>{AddressDTO object}, + 2=>{u.id scalar}, + 3=>{u.name scalar}, + 4=>{a.street scalar}, + 5=>{a.postalCode scalar}, + 'addressId'=>{a.id scalar}, + ), + ... + ) + +From now on, the resultset will look like this: + + array( + 0=>array( + 'user'=>{UserDTO object}, + 'address'=>{AddressDTO object}, + 'addressId'=>{a.id scalar} + ), + ... + ) + +## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature + +Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder() + +# Upgrade to 2.4 + +## BC BREAK: Compatibility Bugfix in PersistentCollection#matching() + +In Doctrine 2.3 it was possible to use the new ``matching($criteria)`` +functionality by adding constraints for assocations based on ID: + + Criteria::expr()->eq('association', $assocation->getId()); + +This functionality does not work on InMemory collections however, because +in memory criteria compares object values based on reference. +As of 2.4 the above code will throw an exception. You need to change +offending code to pass the ``$assocation`` reference directly: + + Criteria::expr()->eq('association', $assocation); + +## Composer is now the default autoloader + +The test suite now runs with composer autoloading. Support for PEAR, and tarball autoloading is deprecated. +Support for GIT submodules is removed. + +## OnFlush and PostFlush event always called + +Before 2.4 the postFlush and onFlush events were only called when there were +actually entities that changed. Now these events are called no matter if there +are entities in the UoW or changes are found. + +## Parenthesis are now considered in arithmetic expression + +Before 2.4 parenthesis are not considered in arithmetic primary expression. +That's conceptually wrong, since it might result in wrong values. For example: + +The DQL: + + SELECT 100 / ( 2 * 2 ) FROM MyEntity + +Before 2.4 it generates the SQL: + + SELECT 100 / 2 * 2 FROM my_entity + +Now parenthesis are considered, the previous DQL will generate: + + SELECT 100 / (2 * 2) FROM my_entity + +# Upgrade to 2.3 + +## Auto Discriminator Map breaks userland implementations with Listener + +The new feature to detect discriminator maps automatically when none +are provided breaks userland implementations doing this with a +listener in ``loadClassMetadata`` event. + +## EntityManager#find() not calls EntityRepository#find() anymore + +Previous to 2.3, calling ``EntityManager#find()`` would be delegated to +``EntityRepository#find()``. This has lead to some unexpected behavior in the +core of Doctrine when people have overwritten the find method in their +repositories. That is why this behavior has been reversed in 2.3, and +``EntityRepository#find()`` calls ``EntityManager#find()`` instead. + +## EntityGenerator add*() method generation + +When generating an add*() method for a collection the EntityGenerator will now not +use the Type-Hint to get the singular for the collection name, but use the field-name +and strip a trailing "s" character if there is one. + +## Merge copies non persisted properties too + +When merging an entity in UoW not only mapped properties are copied, but also others. + +## Query, QueryBuilder and NativeQuery parameters *BC break* + +From now on, parameters in queries is an ArrayCollection instead of a simple array. +This affects heavily the usage of setParameters(), because it will not append anymore +parameters to query, but will actually override the already defined ones. +Whenever you are retrieving a parameter (ie. $query->getParameter(1)), you will +receive an instance of Query\Parameter, which contains the methods "getName", +"getValue" and "getType". Parameters are also only converted to when necessary, and +not when they are set. + +Also, related functions were affected: + +* execute($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance +* iterate($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance +* setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance +* getParameters() now returns ArrayCollection instead of array +* getParameter($key) now returns Parameter instance instead of parameter value + +## Query TreeWalker method renamed + +Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker, +you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin. + +## New methods in TreeWalker interface *BC break* + +Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations +including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the +above you must implement these new methods. + +## Metadata Drivers + +Metadata drivers have been rewritten to reuse code from `Doctrine\Persistence`. Anyone who is using the +`Doctrine\ORM\Mapping\Driver\Driver` interface should instead refer to +`Doctrine\Persistence\Mapping\Driver\MappingDriver`. Same applies to +`Doctrine\ORM\Mapping\Driver\AbstractFileDriver`: you should now refer to +`Doctrine\Persistence\Mapping\Driver\FileDriver`. + +Also, following mapping drivers have been deprecated, please use their replacements in Doctrine\Common as listed: + + * `Doctrine\ORM\Mapping\Driver\DriverChain` => `Doctrine\Persistence\Mapping\Driver\MappingDriverChain` + * `Doctrine\ORM\Mapping\Driver\PHPDriver` => `Doctrine\Persistence\Mapping\Driver\PHPDriver` + * `Doctrine\ORM\Mapping\Driver\StaticPHPDriver` => `Doctrine\Persistence\Mapping\Driver\StaticPHPDriver` + +# Upgrade to 2.2 + +## ResultCache implementation rewritten + +The result cache is completely rewritten and now works on the database result level, not inside the ORM AbstractQuery +anymore. This means that for result cached queries the hydration will now always be performed again, regardless of +the hydration mode. Affected areas are: + +1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork + leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore. +2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result. + +The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now +deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` +instance with access to result cache driver, lifetime and cache key. + + +## EntityManager#getPartialReference() creates read-only entity + +Entities returned from EntityManager#getPartialReference() are now marked as read-only if they +haven't been in the identity map before. This means objects of this kind never lead to changes +in the UnitOfWork. + + +## Fields omitted in a partial DQL query or a native query are never updated + +Fields of an entity that are not returned from a partial DQL Query or native SQL query +will never be updated through an UPDATE statement. + + +## Removed support for onUpdate in @JoinColumn + +The onUpdate foreign key handling makes absolutely no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. + + +## Changes in Annotation Handling + +There have been some changes to the annotation handling in Common 2.2 again, that affect how people with old configurations +from 2.0 have to configure the annotation driver if they don't use `Configuration::newDefaultAnnotationDriver()`: + + // Register the ORM Annotations in the AnnotationRegistry + AnnotationRegistry::registerFile('path/to/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php'); + + $reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader(); + $reader->addNamespace('Doctrine\ORM\Mapping'); + $reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache()); + + $driver = new AnnotationDriver($reader, (array)$paths); + + $config->setMetadataDriverImpl($driver); + + +## Scalar mappings can now be omitted from DQL result + +You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore. +Example: + +SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10 + +Your result will be a collection of Users, and not an array with key 0 as User object instance and "numArticles" as the number of articles per user + + +## Map entities as scalars in DQL result + +When hydrating to array or even a mixed result in object hydrator, previously you had the 0 index holding you entity instance. +You are now allowed to alias this, providing more flexibility for you code. +Example: + +SELECT u AS user FROM User u + +Will now return a collection of arrays with index "user" pointing to the User object instance. + + +## Performance optimizations + +Thousands of lines were completely reviewed and optimized for best performance. +Removed redundancy and improved code readability made now internal Doctrine code easier to understand. +Also, Doctrine 2.2 now is around 10-15% faster than 2.1. + +## EntityManager#find(null) + +Previously EntityManager#find(null) returned null. It now throws an exception. + +# Upgrade to 2.1 + +## Interface for EntityRepository + +The EntityRepository now has an interface Doctrine\Persistence\ObjectRepository. This means that your classes that override EntityRepository and extend find(), findOneBy() or findBy() must be adjusted to follow this interface. + +## AnnotationReader changes + +The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way: + + // new call to the AnnotationRegistry + \Doctrine\Common\Annotations\AnnotationRegistry::registerFile('/doctrine-src/src/Mapping/Driver/DoctrineAnnotations.php'); + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); + // new code necessary starting here + $reader->setIgnoreNotImportedAnnotations(true); + $reader->setEnableParsePhpImports(false); + $reader = new \Doctrine\Common\Annotations\CachedReader( + new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache() + ); + +This is already done inside the ``$config->newDefaultAnnotationDriver``, so everything should automatically work if you are using this method. You can verify if everything still works by executing a console command such as schema-validate that loads all metadata into memory. + +# Update from 2.0-BETA3 to 2.0-BETA4 + +## XML Driver element demoted to attribute + +We changed how the XML Driver allows to define the change-tracking-policy. The working case is now: + + + +# Update from 2.0-BETA2 to 2.0-BETA3 + +## Serialization of Uninitialized Proxies + +As of Beta3 you can now serialize uninitialized proxies, an exception will only be thrown when +trying to access methods on the unserialized proxy as long as it has not been re-attached to the +EntityManager using `EntityManager#merge()`. See this example: + + $proxy = $em->getReference('User', 1); + + $serializedProxy = serialize($proxy); + $detachedProxy = unserialized($serializedProxy); + + echo $em->contains($detachedProxy); // FALSE + + try { + $detachedProxy->getId(); // uninitialized detached proxy + } catch(Exception $e) { + + } + $attachedProxy = $em->merge($detachedProxy); + echo $attackedProxy->getId(); // works! + +## Changed SQL implementation of Postgres and Oracle DateTime types + +The DBAL Type "datetime" included the Timezone Offset in both Postgres and Oracle. As of this version they are now +generated without Timezone (TIMESTAMP WITHOUT TIME ZONE instead of TIMESTAMP WITH TIME ZONE). +See [this comment to Ticket DBAL-22](http://www.doctrine-project.org/jira/browse/DBAL-22?focusedCommentId=13396&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_13396) +for more details as well as migration issues for PostgreSQL and Oracle. + +Both Postgres and Oracle will throw Exceptions during hydration of Objects with "DateTime" fields unless migration steps are taken! + +## Removed multi-dot/deep-path expressions in DQL + +The support for implicit joins in DQL through the multi-dot/Deep Path Expressions +was dropped. For example: + + SELECT u FROM User u WHERE u.group.name = ?1 + +See the "u.group.id" here is using multi dots (deep expression) to walk +through the graph of objects and properties. Internally the DQL parser +would rewrite these queries to: + + SELECT u FROM User u JOIN u.group g WHERE g.name = ?1 + +This explicit notation will be the only supported notation as of now. The internal +handling of multi-dots in the DQL Parser was very complex, error prone in edge cases +and required special treatment for several features we added. Additionally +it had edge cases that could not be solved without making the DQL Parser +even much more complex. For this reason we will drop the support for the +deep path expressions to increase maintainability and overall performance +of the DQL parsing process. This will benefit any DQL query being parsed, +even those not using deep path expressions. + +Note that the generated SQL of both notations is exactly the same! You +don't loose anything through this. + +## Default Allocation Size for Sequences + +The default allocation size for sequences has been changed from 10 to 1. This step was made +to not cause confusion with users and also because it is partly some kind of premature optimization. + +# Update from 2.0-BETA1 to 2.0-BETA2 + +There are no backwards incompatible changes in this release. + +# Upgrade from 2.0-ALPHA4 to 2.0-BETA1 + +## EntityRepository deprecates access to protected variables + +Instead of accessing protected variables for the EntityManager in +a custom EntityRepository it is now required to use the getter methods +for all the three instance variables: + +* `$this->_em` now accessible through `$this->getEntityManager()` +* `$this->_class` now accessible through `$this->getClassMetadata()` +* `$this->_entityName` now accessible through `$this->getEntityName()` + +Important: For Beta 2 the protected visibility of these three properties will be +changed to private! + +## Console migrated to Symfony Console + +The Doctrine CLI has been replaced by Symfony Console Configuration + +Instead of having to specify: + + [php] + $cliConfig = new CliConfiguration(); + $cliConfig->setAttribute('em', $entityManager); + +You now have to configure the script like: + + [php] + $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), + 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) + )); + +## Console: No need for Mapping Paths anymore + +In previous versions you had to specify the --from and --from-path options +to show where your mapping paths are from the console. However this information +is already known from the Mapping Driver configuration, so the requirement +for this options were dropped. + +Instead for each console command all the entities are loaded and to +restrict the operation to one or more sub-groups you can use the --filter flag. + +## AnnotationDriver is not a default mapping driver anymore + +In conjunction with the recent changes to Console we realized that the +annotations driver being a default metadata driver lead to lots of glue +code in the console components to detect where entities lie and how to load +them for batch updates like SchemaTool and other commands. However the +annotations driver being a default driver does not really help that much +anyways. + +Therefore we decided to break backwards compatibility in this issue and drop +the support for Annotations as Default Driver and require our users to +specify the driver explicitly (which allows us to ask for the path to all +entities). + +If you are using the annotations metadata driver as default driver, you +have to add the following lines to your bootstrap code: + + $driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Entities")); + $config->setMetadataDriverImpl($driverImpl); + +You have to specify the path to your entities as either string of a single +path or array of multiple paths +to your entities. This information will be used by all console commands to +access all entities. + +Xml and Yaml Drivers work as before! + + +## New inversedBy attribute + +It is now *mandatory* that the owning side of a bidirectional association specifies the +'inversedBy' attribute that points to the name of the field on the inverse side that completes +the association. Example: + + [php] + // BEFORE (ALPHA4 AND EARLIER) + class User + { + //... + /** @OneToOne(targetEntity="Address", mappedBy="user") */ + private $address; + //... + } + class Address + { + //... + /** @OneToOne(targetEntity="User") */ + private $user; + //... + } + + // SINCE BETA1 + // User class DOES NOT CHANGE + class Address + { + //... + /** @OneToOne(targetEntity="User", inversedBy="address") */ + private $user; + //... + } + +Thus, the inversedBy attribute is the counterpart to the mappedBy attribute. This change +was necessary to enable some simplifications and further performance improvements. We +apologize for the inconvenience. + +## Default Property for Field Mappings + +The "default" option for database column defaults has been removed. If desired, database column defaults can +be implemented by using the columnDefinition attribute of the @Column annotation (or the appropriate XML and YAML equivalents). +Prefer PHP default values, if possible. + +## Selecting Partial Objects + +Querying for partial objects now has a new syntax. The old syntax to query for partial objects +now has a different meaning. This is best illustrated by an example. If you previously +had a DQL query like this: + + [sql] + SELECT u.id, u.name FROM User u + +Since BETA1, simple state field path expressions in the select clause are used to select +object fields as plain scalar values (something that was not possible before). +To achieve the same result as previously (that is, a partial object with only id and name populated) +you need to use the following, explicit syntax: + + [sql] + SELECT PARTIAL u.{id,name} FROM User u + +## XML Mapping Driver + +The 'inheritance-type' attribute changed to take last bit of ClassMetadata constant names, i.e. +NONE, SINGLE_TABLE, INHERITANCE_TYPE_JOINED + +## YAML Mapping Driver + +The way to specify lifecycle callbacks in YAML Mapping driver was changed to allow for multiple callbacks +per event. The Old syntax ways: + + [yaml] + lifecycleCallbacks: + doStuffOnPrePersist: prePersist + doStuffOnPostPersist: postPersist + +The new syntax is: + + [yaml] + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] + +## PreUpdate Event Listeners + +Event Listeners listening to the 'preUpdate' event can only affect the primitive values of entity changesets +by using the API on the `PreUpdateEventArgs` instance passed to the preUpdate listener method. Any changes +to the state of the entitys properties won't affect the database UPDATE statement anymore. This gives drastic +performance benefits for the preUpdate event. + +## Collection API + +The Collection interface in the Common package has been updated with some missing methods +that were present only on the default implementation, ArrayCollection. Custom collection +implementations need to be updated to adhere to the updated interface. + +# Upgrade from 2.0-ALPHA3 to 2.0-ALPHA4 + +## CLI Controller changes + +CLI main object changed its name and namespace. Renamed from Doctrine\ORM\Tools\Cli to Doctrine\Common\Cli\CliController. +Doctrine\Common\Cli\CliController now only deals with namespaces. Ready to go, Core, Dbal and Orm are available and you can subscribe new tasks by retrieving the namespace and including new task. Example: + + [php] + $cli->getNamespace('Core')->addTask('my-example', '\MyProject\Tools\Cli\Tasks\MyExampleTask'); + + +## CLI Tasks documentation + +Tasks have implemented a new way to build documentation. Although it is still possible to define the help manually by extending the basicHelp and extendedHelp, they are now optional. +With new required method AbstractTask::buildDocumentation, its implementation defines the TaskDocumentation instance (accessible through AbstractTask::getDocumentation()), basicHelp and extendedHelp are now not necessary to be implemented. + +## Changes in Method Signatures + + * A bunch of Methods on both Doctrine\DBAL\Platforms\AbstractPlatform and Doctrine\DBAL\Schema\AbstractSchemaManager + have changed quite significantly by adopting the new Schema instance objects. + +## Renamed Methods + + * Doctrine\ORM\AbstractQuery::setExpireResultCache() -> expireResultCache() + * Doctrine\ORM\Query::setExpireQueryCache() -> expireQueryCache() + +## SchemaTool Changes + + * "doctrine schema-tool --drop" now always drops the complete database instead of + only those tables defined by the current database model. The previous method had + problems when foreign keys of orphaned tables pointed to tables that were scheduled + for deletion. + * Use "doctrine schema-tool --update" to get a save incremental update for your + database schema without deleting any unused tables, sequences or foreign keys. + * Use "doctrine schema-tool --complete-update" to do a full incremental update of + your schema. +# Upgrade from 2.0-ALPHA2 to 2.0-ALPHA3 + +This section details the changes made to Doctrine 2.0-ALPHA3 to make it easier for you +to upgrade your projects to use this version. + +## CLI Changes + +The $args variable used in the cli-config.php for configuring the Doctrine CLI has been renamed to $globalArguments. + +## Proxy class changes + +You are now required to make supply some minimalist configuration with regards to proxy objects. That involves 2 new configuration options. First, the directory where generated proxy classes should be placed needs to be specified. Secondly, you need to configure the namespace used for proxy classes. The following snippet shows an example: + + [php] + // step 1: configure directory for proxy classes + // $config instanceof Doctrine\ORM\Configuration + $config->setProxyDir('/path/to/myproject/lib/MyProject/Generated/Proxies'); + $config->setProxyNamespace('MyProject\Generated\Proxies'); + +Note that proxy classes behave exactly like any other classes when it comes to class loading. Therefore you need to make sure the proxy classes can be loaded by some class loader. If you place the generated proxy classes in a namespace and directory under your projects class files, like in the example above, it would be sufficient to register the MyProject namespace on a class loader. Since the proxy classes are contained in that namespace and adhere to the standards for class loading, no additional work is required. +Generating the proxy classes into a namespace within your class library is the recommended setup. + +Entities with initialized proxy objects can now be serialized and unserialized properly from within the same application. + +For more details refer to the Configuration section of the manual. + +## Removed allowPartialObjects configuration option + +The allowPartialObjects configuration option together with the `Configuration#getAllowPartialObjects` and `Configuration#setAllowPartialObjects` methods have been removed. +The new behavior is as if the option were set to FALSE all the time, basically disallowing partial objects globally. However, you can still use the `Query::HINT_FORCE_PARTIAL_LOAD` query hint to force a query to return partial objects for optimization purposes. + +## Renamed Methods + +* Doctrine\ORM\Configuration#getCacheDir() to getProxyDir() +* Doctrine\ORM\Configuration#setCacheDir($dir) to setProxyDir($dir) diff --git a/vendor/doctrine/orm/composer.json b/vendor/doctrine/orm/composer.json new file mode 100644 index 0000000..2a75126 --- /dev/null +++ b/vendor/doctrine/orm/composer.json @@ -0,0 +1,65 @@ +{ + "name": "doctrine/orm", + "type": "library", + "description": "Object-Relational-Mapper for PHP", + "keywords": ["orm", "database"], + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Marco Pivetta", "email": "ocramius@gmail.com"} + ], + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + }, + "require": { + "php": "^8.1", + "composer-runtime-api": "^2", + "ext-ctype": "*", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3 || ^2", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "1.11.1", + "phpunit/phpunit": "^10.4.0", + "psr/log": "^1 || ^2 || ^3", + "squizlabs/php_codesniffer": "3.7.2", + "symfony/cache": "^5.4 || ^6.2 || ^7.0", + "vimeo/psalm": "5.24.0" + }, + "suggest": { + "ext-dom": "Provides support for XSD validation for XML mapping files", + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" + }, + "autoload": { + "psr-4": { "Doctrine\\ORM\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests/Tests", + "Doctrine\\StaticAnalysis\\": "tests/StaticAnalysis", + "Doctrine\\Performance\\": "tests/Performance" + } + }, + "archive": { + "exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"] + } +} diff --git a/vendor/doctrine/orm/doctrine-mapping.xsd b/vendor/doctrine/orm/doctrine-mapping.xsd new file mode 100644 index 0000000..58175a2 --- /dev/null +++ b/vendor/doctrine/orm/doctrine-mapping.xsd @@ -0,0 +1,589 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/orm/phpstan-dbal3.neon b/vendor/doctrine/orm/phpstan-dbal3.neon new file mode 100644 index 0000000..b9ef942 --- /dev/null +++ b/vendor/doctrine/orm/phpstan-dbal3.neon @@ -0,0 +1,29 @@ +includes: + - phpstan-baseline.neon + - phpstan-params.neon + +parameters: + ignoreErrors: + # Symfony cache supports passing a key prefix to the clear method. + - '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/' + + # We can be certain that those values are not matched. + - + message: '~^Match expression does not handle remaining values:~' + path: src/Persisters/Entity/BasicEntityPersister.php + + # DBAL 4 compatibility + - + message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~' + path: src/Query/AST/Functions/TrimFunction.php + - + message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~' + path: src/Persisters/Entity/BasicEntityPersister.php + + - '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~' + + # To be removed in 4.0 + - + message: '#Negated boolean expression is always false\.#' + paths: + - src/Mapping/Driver/AttributeDriver.php diff --git a/vendor/doctrine/orm/src/AbstractQuery.php b/vendor/doctrine/orm/src/AbstractQuery.php new file mode 100644 index 0000000..0ff92c3 --- /dev/null +++ b/vendor/doctrine/orm/src/AbstractQuery.php @@ -0,0 +1,1116 @@ + + */ + protected ArrayCollection $parameters; + + /** + * The user-specified ResultSetMapping to use. + */ + protected ResultSetMapping|null $resultSetMapping = null; + + /** + * The map of query hints. + * + * @psalm-var array + */ + protected array $hints = []; + + /** + * The hydration mode. + * + * @psalm-var string|AbstractQuery::HYDRATE_* + */ + protected string|int $hydrationMode = self::HYDRATE_OBJECT; + + protected QueryCacheProfile|null $queryCacheProfile = null; + + /** + * Whether or not expire the result cache. + */ + protected bool $expireResultCache = false; + + protected QueryCacheProfile|null $hydrationCacheProfile = null; + + /** + * Whether to use second level cache, if available. + */ + protected bool $cacheable = false; + + protected bool $hasCache = false; + + /** + * Second level cache region name. + */ + protected string|null $cacheRegion = null; + + /** + * Second level query cache mode. + * + * @psalm-var Cache::MODE_*|null + */ + protected int|null $cacheMode = null; + + protected CacheLogger|null $cacheLogger = null; + + protected int $lifetime = 0; + + /** + * Initializes a new instance of a class derived from AbstractQuery. + */ + public function __construct( + /** + * The entity manager used by this query object. + */ + protected EntityManagerInterface $em, + ) { + $this->parameters = new ArrayCollection(); + $this->hints = $em->getConfiguration()->getDefaultQueryHints(); + $this->hasCache = $this->em->getConfiguration()->isSecondLevelCacheEnabled(); + + if ($this->hasCache) { + $this->cacheLogger = $em->getConfiguration() + ->getSecondLevelCacheConfiguration() + ->getCacheLogger(); + } + } + + /** + * Enable/disable second level query (result) caching for this query. + * + * @return $this + */ + public function setCacheable(bool $cacheable): static + { + $this->cacheable = $cacheable; + + return $this; + } + + /** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */ + public function isCacheable(): bool + { + return $this->cacheable; + } + + /** @return $this */ + public function setCacheRegion(string $cacheRegion): static + { + $this->cacheRegion = $cacheRegion; + + return $this; + } + + /** + * Obtain the name of the second level query cache region in which query results will be stored + * + * @return string|null The cache region name; NULL indicates the default region. + */ + public function getCacheRegion(): string|null + { + return $this->cacheRegion; + } + + /** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */ + protected function isCacheEnabled(): bool + { + return $this->cacheable && $this->hasCache; + } + + public function getLifetime(): int + { + return $this->lifetime; + } + + /** + * Sets the life-time for this query into second level cache. + * + * @return $this + */ + public function setLifetime(int $lifetime): static + { + $this->lifetime = $lifetime; + + return $this; + } + + /** @psalm-return Cache::MODE_*|null */ + public function getCacheMode(): int|null + { + return $this->cacheMode; + } + + /** + * @psalm-param Cache::MODE_* $cacheMode + * + * @return $this + */ + public function setCacheMode(int $cacheMode): static + { + $this->cacheMode = $cacheMode; + + return $this; + } + + /** + * Gets the SQL query that corresponds to this query object. + * The returned SQL syntax depends on the connection driver that is used + * by this query object at the time of this method call. + * + * @return list|string SQL query + */ + abstract public function getSQL(): string|array; + + /** + * Retrieves the associated EntityManager of this Query instance. + */ + public function getEntityManager(): EntityManagerInterface + { + return $this->em; + } + + /** + * Frees the resources used by the query object. + * + * Resets Parameters, Parameter Types and Query Hints. + */ + public function free(): void + { + $this->parameters = new ArrayCollection(); + + $this->hints = $this->em->getConfiguration()->getDefaultQueryHints(); + } + + /** + * Get all defined parameters. + * + * @psalm-return ArrayCollection + */ + public function getParameters(): ArrayCollection + { + return $this->parameters; + } + + /** + * Gets a query parameter. + * + * @param int|string $key The key (index or name) of the bound parameter. + * + * @return Parameter|null The value of the bound parameter, or NULL if not available. + */ + public function getParameter(int|string $key): Parameter|null + { + $key = Parameter::normalizeName($key); + + $filteredParameters = $this->parameters->filter( + static fn (Parameter $parameter): bool => $parameter->getName() === $key + ); + + return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null; + } + + /** + * Sets a collection of query parameters. + * + * @param ArrayCollection|mixed[] $parameters + * @psalm-param ArrayCollection|mixed[] $parameters + * + * @return $this + */ + public function setParameters(ArrayCollection|array $parameters): static + { + if (is_array($parameters)) { + /** @psalm-var ArrayCollection $parameterCollection */ + $parameterCollection = new ArrayCollection(); + + foreach ($parameters as $key => $value) { + $parameterCollection->add(new Parameter($key, $value)); + } + + $parameters = $parameterCollection; + } + + $this->parameters = $parameters; + + return $this; + } + + /** + * Sets a query parameter. + * + * @param string|int $key The parameter position or name. + * @param mixed $value The parameter value. + * @param ParameterType|ArrayParameterType|string|int|null $type The parameter type. If specified, the given value + * will be run through the type conversion of this + * type. This is usually not needed for strings and + * numeric types. + * + * @return $this + */ + public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static + { + $existingParameter = $this->getParameter($key); + + if ($existingParameter !== null) { + $existingParameter->setValue($value, $type); + + return $this; + } + + $this->parameters->add(new Parameter($key, $value, $type)); + + return $this; + } + + /** + * Processes an individual parameter value. + * + * @throws ORMInvalidArgumentException + */ + public function processParameterValue(mixed $value): mixed + { + if (is_scalar($value)) { + return $value; + } + + if ($value instanceof Collection) { + $value = iterator_to_array($value); + } + + if (is_array($value)) { + $value = $this->processArrayParameterValue($value); + + return $value; + } + + if ($value instanceof ClassMetadata) { + return $value->name; + } + + if ($value instanceof BackedEnum) { + return $value->value; + } + + if (! is_object($value)) { + return $value; + } + + try { + $class = DefaultProxyClassNameResolver::getClass($value); + $value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value); + + if ($value === null) { + throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class); + } + } catch (MappingException | ORMMappingException) { + /* Silence any mapping exceptions. These can occur if the object in + question is not a mapped entity, in which case we just don't do + any preparation on the value. + Depending on MappingDriver, either MappingException or + ORMMappingException is thrown. */ + + $value = $this->potentiallyProcessIterable($value); + } + + return $value; + } + + /** + * If no mapping is detected, trying to resolve the value as a Traversable + */ + private function potentiallyProcessIterable(mixed $value): mixed + { + if ($value instanceof Traversable) { + $value = iterator_to_array($value); + $value = $this->processArrayParameterValue($value); + } + + return $value; + } + + /** + * Process a parameter value which was previously identified as an array + * + * @param mixed[] $value + * + * @return mixed[] + */ + private function processArrayParameterValue(array $value): array + { + foreach ($value as $key => $paramValue) { + $paramValue = $this->processParameterValue($paramValue); + $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue; + } + + return $value; + } + + /** + * Sets the ResultSetMapping that should be used for hydration. + * + * @return $this + */ + public function setResultSetMapping(ResultSetMapping $rsm): static + { + $this->translateNamespaces($rsm); + $this->resultSetMapping = $rsm; + + return $this; + } + + /** + * Gets the ResultSetMapping used for hydration. + */ + protected function getResultSetMapping(): ResultSetMapping|null + { + return $this->resultSetMapping; + } + + /** + * Allows to translate entity namespaces to full qualified names. + */ + private function translateNamespaces(ResultSetMapping $rsm): void + { + $translate = fn ($alias): string => $this->em->getClassMetadata($alias)->getName(); + + $rsm->aliasMap = array_map($translate, $rsm->aliasMap); + $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses); + } + + /** + * Set a cache profile for hydration caching. + * + * If no result cache driver is set in the QueryCacheProfile, the default + * result cache driver is used from the configuration. + * + * Important: Hydration caching does NOT register entities in the + * UnitOfWork when retrieved from the cache. Never use result cached + * entities for requests that also flush the EntityManager. If you want + * some form of caching with UnitOfWork registration you should use + * {@see AbstractQuery::setResultCacheProfile()}. + * + * @return $this + * + * @example + * $lifetime = 100; + * $resultKey = "abc"; + * $query->setHydrationCacheProfile(new QueryCacheProfile()); + * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey)); + */ + public function setHydrationCacheProfile(QueryCacheProfile|null $profile): static + { + if ($profile === null) { + $this->hydrationCacheProfile = null; + + return $this; + } + + if (! $profile->getResultCache()) { + $defaultHydrationCacheImpl = $this->em->getConfiguration()->getHydrationCache(); + if ($defaultHydrationCacheImpl) { + $profile = $profile->setResultCache($defaultHydrationCacheImpl); + } + } + + $this->hydrationCacheProfile = $profile; + + return $this; + } + + public function getHydrationCacheProfile(): QueryCacheProfile|null + { + return $this->hydrationCacheProfile; + } + + /** + * Set a cache profile for the result cache. + * + * If no result cache driver is set in the QueryCacheProfile, the default + * result cache driver is used from the configuration. + * + * @return $this + */ + public function setResultCacheProfile(QueryCacheProfile|null $profile): static + { + if ($profile === null) { + $this->queryCacheProfile = null; + + return $this; + } + + if (! $profile->getResultCache()) { + $defaultResultCache = $this->em->getConfiguration()->getResultCache(); + if ($defaultResultCache) { + $profile = $profile->setResultCache($defaultResultCache); + } + } + + $this->queryCacheProfile = $profile; + + return $this; + } + + /** + * Defines a cache driver to be used for caching result sets and implicitly enables caching. + */ + public function setResultCache(CacheItemPoolInterface|null $resultCache): static + { + if ($resultCache === null) { + if ($this->queryCacheProfile) { + $this->queryCacheProfile = new QueryCacheProfile($this->queryCacheProfile->getLifetime(), $this->queryCacheProfile->getCacheKey()); + } + + return $this; + } + + $this->queryCacheProfile = $this->queryCacheProfile + ? $this->queryCacheProfile->setResultCache($resultCache) + : new QueryCacheProfile(0, null, $resultCache); + + return $this; + } + + /** + * Enables caching of the results of this query, for given or default amount of seconds + * and optionally specifies which ID to use for the cache entry. + * + * @param int|null $lifetime How long the cache entry is valid, in seconds. + * @param string|null $resultCacheId ID to use for the cache entry. + * + * @return $this + */ + public function enableResultCache(int|null $lifetime = null, string|null $resultCacheId = null): static + { + $this->setResultCacheLifetime($lifetime); + $this->setResultCacheId($resultCacheId); + + return $this; + } + + /** + * Disables caching of the results of this query. + * + * @return $this + */ + public function disableResultCache(): static + { + $this->queryCacheProfile = null; + + return $this; + } + + /** + * Defines how long the result cache will be active before expire. + * + * @param int|null $lifetime How long the cache entry is valid, in seconds. + * + * @return $this + */ + public function setResultCacheLifetime(int|null $lifetime): static + { + $lifetime = (int) $lifetime; + + if ($this->queryCacheProfile) { + $this->queryCacheProfile = $this->queryCacheProfile->setLifetime($lifetime); + + return $this; + } + + $this->queryCacheProfile = new QueryCacheProfile($lifetime); + + $cache = $this->em->getConfiguration()->getResultCache(); + if (! $cache) { + return $this; + } + + $this->queryCacheProfile = $this->queryCacheProfile->setResultCache($cache); + + return $this; + } + + /** + * Defines if the result cache is active or not. + * + * @param bool $expire Whether or not to force resultset cache expiration. + * + * @return $this + */ + public function expireResultCache(bool $expire = true): static + { + $this->expireResultCache = $expire; + + return $this; + } + + /** + * Retrieves if the resultset cache is active or not. + */ + public function getExpireResultCache(): bool + { + return $this->expireResultCache; + } + + public function getQueryCacheProfile(): QueryCacheProfile|null + { + return $this->queryCacheProfile; + } + + /** + * Change the default fetch mode of an association for this query. + * + * @param class-string $class + * @psalm-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode + */ + public function setFetchMode(string $class, string $assocName, int $fetchMode): static + { + $this->hints['fetchMode'][$class][$assocName] = $fetchMode; + + return $this; + } + + /** + * Defines the processing mode to be used during hydration / result set transformation. + * + * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process. + * One of the Query::HYDRATE_* constants. + * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode + * + * @return $this + */ + public function setHydrationMode(string|int $hydrationMode): static + { + $this->hydrationMode = $hydrationMode; + + return $this; + } + + /** + * Gets the hydration mode currently used by the query. + * + * @psalm-return string|AbstractQuery::HYDRATE_* + */ + public function getHydrationMode(): string|int + { + return $this->hydrationMode; + } + + /** + * Gets the list of results for the query. + * + * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT). + * + * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode + */ + public function getResult(string|int $hydrationMode = self::HYDRATE_OBJECT): mixed + { + return $this->execute(null, $hydrationMode); + } + + /** + * Gets the array of results for the query. + * + * Alias for execute(null, HYDRATE_ARRAY). + * + * @return mixed[] + */ + public function getArrayResult(): array + { + return $this->execute(null, self::HYDRATE_ARRAY); + } + + /** + * Gets one-dimensional array of results for the query. + * + * Alias for execute(null, HYDRATE_SCALAR_COLUMN). + * + * @return mixed[] + */ + public function getSingleColumnResult(): array + { + return $this->execute(null, self::HYDRATE_SCALAR_COLUMN); + } + + /** + * Gets the scalar results for the query. + * + * Alias for execute(null, HYDRATE_SCALAR). + * + * @return mixed[] + */ + public function getScalarResult(): array + { + return $this->execute(null, self::HYDRATE_SCALAR); + } + + /** + * Get exactly one result or null. + * + * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode + * + * @throws NonUniqueResultException + */ + public function getOneOrNullResult(string|int|null $hydrationMode = null): mixed + { + try { + $result = $this->execute(null, $hydrationMode); + } catch (NoResultException) { + return null; + } + + if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { + return null; + } + + if (! is_array($result)) { + return $result; + } + + if (count($result) > 1) { + throw new NonUniqueResultException(); + } + + return array_shift($result); + } + + /** + * Gets the single result of the query. + * + * Enforces the presence as well as the uniqueness of the result. + * + * If the result is not unique, a NonUniqueResultException is thrown. + * If there is no result, a NoResultException is thrown. + * + * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode + * + * @throws NonUniqueResultException If the query result is not unique. + * @throws NoResultException If the query returned no result. + */ + public function getSingleResult(string|int|null $hydrationMode = null): mixed + { + $result = $this->execute(null, $hydrationMode); + + if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { + throw new NoResultException(); + } + + if (! is_array($result)) { + return $result; + } + + if (count($result) > 1) { + throw new NonUniqueResultException(); + } + + return array_shift($result); + } + + /** + * Gets the single scalar result of the query. + * + * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR). + * + * @return bool|float|int|string|null The scalar result. + * + * @throws NoResultException If the query returned no result. + * @throws NonUniqueResultException If the query result is not unique. + */ + public function getSingleScalarResult(): mixed + { + return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR); + } + + /** + * Sets a query hint. If the hint name is not recognized, it is silently ignored. + * + * @return $this + */ + public function setHint(string $name, mixed $value): static + { + $this->hints[$name] = $value; + + return $this; + } + + /** + * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned. + * + * @return mixed The value of the hint or FALSE, if the hint name is not recognized. + */ + public function getHint(string $name): mixed + { + return $this->hints[$name] ?? false; + } + + public function hasHint(string $name): bool + { + return isset($this->hints[$name]); + } + + /** + * Return the key value map of query hints that are currently set. + * + * @return array + */ + public function getHints(): array + { + return $this->hints; + } + + /** + * Executes the query and returns an iterable that can be used to incrementally + * iterate over the result. + * + * @psalm-param ArrayCollection|mixed[] $parameters + * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode + * + * @return iterable + */ + public function toIterable( + ArrayCollection|array $parameters = [], + string|int|null $hydrationMode = null, + ): iterable { + if ($hydrationMode !== null) { + $this->setHydrationMode($hydrationMode); + } + + if (count($parameters) !== 0) { + $this->setParameters($parameters); + } + + $rsm = $this->getResultSetMapping(); + if ($rsm === null) { + throw new LogicException('Uninitialized result set mapping.'); + } + + if ($rsm->isMixed && count($rsm->scalarMappings) > 0) { + throw QueryException::iterateWithMixedResultNotAllowed(); + } + + $stmt = $this->_doExecute(); + + return $this->em->newHydrator($this->hydrationMode)->toIterable($stmt, $rsm, $this->hints); + } + + /** + * Executes the query. + * + * @psalm-param ArrayCollection|mixed[]|null $parameters + * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode + */ + public function execute( + ArrayCollection|array|null $parameters = null, + string|int|null $hydrationMode = null, + ): mixed { + if ($this->cacheable && $this->isCacheEnabled()) { + return $this->executeUsingQueryCache($parameters, $hydrationMode); + } + + return $this->executeIgnoreQueryCache($parameters, $hydrationMode); + } + + /** + * Execute query ignoring second level cache. + * + * @psalm-param ArrayCollection|mixed[]|null $parameters + * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode + */ + private function executeIgnoreQueryCache( + ArrayCollection|array|null $parameters = null, + string|int|null $hydrationMode = null, + ): mixed { + if ($hydrationMode !== null) { + $this->setHydrationMode($hydrationMode); + } + + if (! empty($parameters)) { + $this->setParameters($parameters); + } + + $setCacheEntry = static function ($data): void { + }; + + if ($this->hydrationCacheProfile !== null) { + [$cacheKey, $realCacheKey] = $this->getHydrationCacheId(); + + $cache = $this->getHydrationCache(); + $cacheItem = $cache->getItem($cacheKey); + $result = $cacheItem->isHit() ? $cacheItem->get() : []; + + if (isset($result[$realCacheKey])) { + return $result[$realCacheKey]; + } + + if (! $result) { + $result = []; + } + + $setCacheEntry = static function ($data) use ($cache, $result, $cacheItem, $realCacheKey): void { + $cache->save($cacheItem->set($result + [$realCacheKey => $data])); + }; + } + + $stmt = $this->_doExecute(); + + if (is_numeric($stmt)) { + $setCacheEntry($stmt); + + return $stmt; + } + + $rsm = $this->getResultSetMapping(); + if ($rsm === null) { + throw new LogicException('Uninitialized result set mapping.'); + } + + $data = $this->em->newHydrator($this->hydrationMode)->hydrateAll($stmt, $rsm, $this->hints); + + $setCacheEntry($data); + + return $data; + } + + private function getHydrationCache(): CacheItemPoolInterface + { + assert($this->hydrationCacheProfile !== null); + + $cache = $this->hydrationCacheProfile->getResultCache(); + assert($cache !== null); + + return $cache; + } + + /** + * Load from second level cache or executes the query and put into cache. + * + * @psalm-param ArrayCollection|mixed[]|null $parameters + * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode + */ + private function executeUsingQueryCache( + ArrayCollection|array|null $parameters = null, + string|int|null $hydrationMode = null, + ): mixed { + $rsm = $this->getResultSetMapping(); + if ($rsm === null) { + throw new LogicException('Uninitialized result set mapping.'); + } + + $queryCache = $this->em->getCache()->getQueryCache($this->cacheRegion); + $queryKey = new QueryCacheKey( + $this->getHash(), + $this->lifetime, + $this->cacheMode ?: Cache::MODE_NORMAL, + $this->getTimestampKey(), + ); + + $result = $queryCache->get($queryKey, $rsm, $this->hints); + + if ($result !== null) { + if ($this->cacheLogger) { + $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey); + } + + return $result; + } + + $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode); + $cached = $queryCache->put($queryKey, $rsm, $result, $this->hints); + + if ($this->cacheLogger) { + $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey); + + if ($cached) { + $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey); + } + } + + return $result; + } + + private function getTimestampKey(): TimestampCacheKey|null + { + assert($this->resultSetMapping !== null); + $entityName = reset($this->resultSetMapping->aliasMap); + + if (empty($entityName)) { + return null; + } + + $metadata = $this->em->getClassMetadata($entityName); + + return new TimestampCacheKey($metadata->rootEntityName); + } + + /** + * Get the result cache id to use to store the result set cache entry. + * Will return the configured id if it exists otherwise a hash will be + * automatically generated for you. + * + * @return string[] ($key, $hash) + * @psalm-return array{string, string} ($key, $hash) + */ + protected function getHydrationCacheId(): array + { + $parameters = []; + $types = []; + + foreach ($this->getParameters() as $parameter) { + $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); + $types[$parameter->getName()] = $parameter->getType(); + } + + $sql = $this->getSQL(); + assert(is_string($sql)); + $queryCacheProfile = $this->getHydrationCacheProfile(); + $hints = $this->getHints(); + $hints['hydrationMode'] = $this->getHydrationMode(); + + ksort($hints); + assert($queryCacheProfile !== null); + + return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints); + } + + /** + * Set the result cache id to use to store the result set cache entry. + * If this is not explicitly set by the developer then a hash is automatically + * generated for you. + */ + public function setResultCacheId(string|null $id): static + { + if (! $this->queryCacheProfile) { + return $this->setResultCacheProfile(new QueryCacheProfile(0, $id)); + } + + $this->queryCacheProfile = $this->queryCacheProfile->setCacheKey($id); + + return $this; + } + + /** + * Executes the query and returns a the resulting Statement object. + * + * @return Result|int The executed database statement that holds + * the results, or an integer indicating how + * many rows were affected. + */ + abstract protected function _doExecute(): Result|int; + + /** + * Cleanup Query resource when clone is called. + */ + public function __clone() + { + $this->parameters = new ArrayCollection(); + + $this->hints = []; + $this->hints = $this->em->getConfiguration()->getDefaultQueryHints(); + } + + /** + * Generates a string of currently query to use for the cache second level cache. + */ + protected function getHash(): string + { + $query = $this->getSQL(); + assert(is_string($query)); + $hints = $this->getHints(); + $params = array_map(function (Parameter $parameter) { + $value = $parameter->getValue(); + + // Small optimization + // Does not invoke processParameterValue for scalar value + if (is_scalar($value)) { + return $value; + } + + return $this->processParameterValue($value); + }, $this->parameters->getValues()); + + ksort($hints); + + return sha1($query . '-' . serialize($params) . '-' . serialize($hints)); + } +} diff --git a/vendor/doctrine/orm/src/Cache.php b/vendor/doctrine/orm/src/Cache.php new file mode 100644 index 0000000..8020b27 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache.php @@ -0,0 +1,106 @@ + $identifier The entity identifier. + * @param class-string $class The entity class name + */ + public function __construct( + public readonly string $class, + public readonly array $identifier, + ) { + } + + /** + * Creates a new AssociationCacheEntry + * + * This method allow Doctrine\Common\Cache\PhpFileCache compatibility + * + * @param array $values array containing property values + */ + public static function __set_state(array $values): self + { + return new self($values['class'], $values['identifier']); + } +} diff --git a/vendor/doctrine/orm/src/Cache/CacheConfiguration.php b/vendor/doctrine/orm/src/Cache/CacheConfiguration.php new file mode 100644 index 0000000..0f8dea7 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/CacheConfiguration.php @@ -0,0 +1,60 @@ +cacheFactory; + } + + public function setCacheFactory(CacheFactory $factory): void + { + $this->cacheFactory = $factory; + } + + public function getCacheLogger(): CacheLogger|null + { + return $this->cacheLogger; + } + + public function setCacheLogger(CacheLogger $logger): void + { + $this->cacheLogger = $logger; + } + + public function getRegionsConfiguration(): RegionsConfiguration + { + return $this->regionsConfig ??= new RegionsConfiguration(); + } + + public function setRegionsConfiguration(RegionsConfiguration $regionsConfig): void + { + $this->regionsConfig = $regionsConfig; + } + + public function getQueryValidator(): QueryCacheValidator + { + return $this->queryValidator ??= new TimestampQueryCacheValidator( + $this->cacheFactory->getTimestampRegion(), + ); + } + + public function setQueryValidator(QueryCacheValidator $validator): void + { + $this->queryValidator = $validator; + } +} diff --git a/vendor/doctrine/orm/src/Cache/CacheEntry.php b/vendor/doctrine/orm/src/Cache/CacheEntry.php new file mode 100644 index 0000000..6e12de1 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/CacheEntry.php @@ -0,0 +1,16 @@ +IMPORTANT NOTE: + * + * Fields of classes that implement CacheEntry are public for performance reason. + */ +interface CacheEntry +{ +} diff --git a/vendor/doctrine/orm/src/Cache/CacheException.php b/vendor/doctrine/orm/src/Cache/CacheException.php new file mode 100644 index 0000000..b422095 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/CacheException.php @@ -0,0 +1,26 @@ + $cache The cache configuration. + */ + public function getRegion(array $cache): Region; + + /** + * Build timestamp cache region + */ + public function getTimestampRegion(): TimestampRegion; + + /** + * Build \Doctrine\ORM\Cache + */ + public function createCache(EntityManagerInterface $entityManager): Cache; +} diff --git a/vendor/doctrine/orm/src/Cache/CacheKey.php b/vendor/doctrine/orm/src/Cache/CacheKey.php new file mode 100644 index 0000000..970702c --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/CacheKey.php @@ -0,0 +1,16 @@ + $values array containing property values + */ + public static function __set_state(array $values): CollectionCacheEntry + { + return new self($values['identifiers']); + } +} diff --git a/vendor/doctrine/orm/src/Cache/CollectionCacheKey.php b/vendor/doctrine/orm/src/Cache/CollectionCacheKey.php new file mode 100644 index 0000000..51b408f --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/CollectionCacheKey.php @@ -0,0 +1,39 @@ + + */ + public readonly array $ownerIdentifier; + + /** + * @param array $ownerIdentifier The identifier of the owning entity. + * @param class-string $entityClass The owner entity class + */ + public function __construct( + public readonly string $entityClass, + public readonly string $association, + array $ownerIdentifier, + ) { + ksort($ownerIdentifier); + + $this->ownerIdentifier = $ownerIdentifier; + + parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association); + } +} diff --git a/vendor/doctrine/orm/src/Cache/CollectionHydrator.php b/vendor/doctrine/orm/src/Cache/CollectionHydrator.php new file mode 100644 index 0000000..16a6572 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/CollectionHydrator.php @@ -0,0 +1,21 @@ + + */ + private array $queryCaches = []; + + private QueryCache|null $defaultQueryCache = null; + + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->uow = $em->getUnitOfWork(); + $this->cacheFactory = $em->getConfiguration() + ->getSecondLevelCacheConfiguration() + ->getCacheFactory(); + } + + public function getEntityCacheRegion(string $className): Region|null + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return null; + } + + return $persister->getCacheRegion(); + } + + public function getCollectionCacheRegion(string $className, string $association): Region|null + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return null; + } + + return $persister->getCacheRegion(); + } + + public function containsEntity(string $className, mixed $identifier): bool + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return false; + } + + return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier)); + } + + public function evictEntity(string $className, mixed $identifier): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier)); + } + + public function evictEntityRegion(string $className): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evictAll(); + } + + public function evictEntityRegions(): void + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if (! ($persister instanceof CachedPersister)) { + continue; + } + + $persister->getCacheRegion()->evictAll(); + } + } + + public function containsCollection(string $className, string $association, mixed $ownerIdentifier): bool + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return false; + } + + return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier)); + } + + public function evictCollection(string $className, string $association, mixed $ownerIdentifier): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier)); + } + + public function evictCollectionRegion(string $className, string $association): void + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if (! ($persister instanceof CachedPersister)) { + return; + } + + $persister->getCacheRegion()->evictAll(); + } + + public function evictCollectionRegions(): void + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + foreach ($metadata->associationMappings as $association) { + if (! $association->isToMany()) { + continue; + } + + $persister = $this->uow->getCollectionPersister($association); + + if (! ($persister instanceof CachedPersister)) { + continue; + } + + $persister->getCacheRegion()->evictAll(); + } + } + } + + public function containsQuery(string $regionName): bool + { + return isset($this->queryCaches[$regionName]); + } + + public function evictQueryRegion(string|null $regionName = null): void + { + if ($regionName === null && $this->defaultQueryCache !== null) { + $this->defaultQueryCache->clear(); + + return; + } + + if (isset($this->queryCaches[$regionName])) { + $this->queryCaches[$regionName]->clear(); + } + } + + public function evictQueryRegions(): void + { + $this->getQueryCache()->clear(); + + foreach ($this->queryCaches as $queryCache) { + $queryCache->clear(); + } + } + + public function getQueryCache(string|null $regionName = null): QueryCache + { + if ($regionName === null) { + return $this->defaultQueryCache ??= $this->cacheFactory->buildQueryCache($this->em); + } + + return $this->queryCaches[$regionName] ??= $this->cacheFactory->buildQueryCache($this->em, $regionName); + } + + private function buildEntityCacheKey(ClassMetadata $metadata, mixed $identifier): EntityCacheKey + { + if (! is_array($identifier)) { + $identifier = $this->toIdentifierArray($metadata, $identifier); + } + + return new EntityCacheKey($metadata->rootEntityName, $identifier); + } + + private function buildCollectionCacheKey( + ClassMetadata $metadata, + string $association, + mixed $ownerIdentifier, + ): CollectionCacheKey { + if (! is_array($ownerIdentifier)) { + $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier); + } + + return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); + } + + /** @return array */ + private function toIdentifierArray(ClassMetadata $metadata, mixed $identifier): array + { + if (is_object($identifier)) { + $class = DefaultProxyClassNameResolver::getClass($identifier); + if ($this->em->getMetadataFactory()->hasMetadataFor($class)) { + $identifier = $this->uow->getSingleIdentifierValue($identifier) + ?? throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class); + } + } + + return [$metadata->identifier[0] => $identifier]; + } +} diff --git a/vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php b/vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php new file mode 100644 index 0000000..84ea490 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/DefaultCacheFactory.php @@ -0,0 +1,189 @@ +fileLockRegionDirectory = $fileLockRegionDirectory; + } + + public function getFileLockRegionDirectory(): string|null + { + return $this->fileLockRegionDirectory; + } + + public function setRegion(Region $region): void + { + $this->regions[$region->getName()] = $region; + } + + public function setTimestampRegion(TimestampRegion $region): void + { + $this->timestampRegion = $region; + } + + public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata): CachedEntityPersister + { + assert($metadata->cache !== null); + $region = $this->getRegion($metadata->cache); + $usage = $metadata->cache['usage']; + + if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { + return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata); + } + + if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata); + } + + if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { + if (! $region instanceof ConcurrentRegion) { + throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage)); + } + + return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata); + } + + throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage)); + } + + public function buildCachedCollectionPersister( + EntityManagerInterface $em, + CollectionPersister $persister, + AssociationMapping $mapping, + ): CachedCollectionPersister { + assert(isset($mapping->cache)); + $usage = $mapping->cache['usage']; + $region = $this->getRegion($mapping->cache); + + if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { + return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping); + } + + if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); + } + + if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { + if (! $region instanceof ConcurrentRegion) { + throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage)); + } + + return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); + } + + throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage)); + } + + public function buildQueryCache(EntityManagerInterface $em, string|null $regionName = null): QueryCache + { + return new DefaultQueryCache( + $em, + $this->getRegion( + [ + 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME, + 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE, + ], + ), + ); + } + + public function buildCollectionHydrator(EntityManagerInterface $em, AssociationMapping $mapping): CollectionHydrator + { + return new DefaultCollectionHydrator($em); + } + + public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata): EntityHydrator + { + return new DefaultEntityHydrator($em); + } + + /** + * {@inheritDoc} + */ + public function getRegion(array $cache): Region + { + if (isset($this->regions[$cache['region']])) { + return $this->regions[$cache['region']]; + } + + $name = $cache['region']; + $lifetime = $this->regionsConfig->getLifetime($cache['region']); + $region = new DefaultRegion($name, $this->cacheItemPool, $lifetime); + + if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) { + if ( + $this->fileLockRegionDirectory === '' || + $this->fileLockRegionDirectory === null + ) { + throw new LogicException( + 'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' . + 'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). ', + ); + } + + $directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region']; + $region = new FileLockRegion($region, $directory, (string) $this->regionsConfig->getLockLifetime($cache['region'])); + } + + return $this->regions[$cache['region']] = $region; + } + + public function getTimestampRegion(): TimestampRegion + { + if ($this->timestampRegion === null) { + $name = Cache::DEFAULT_TIMESTAMP_REGION_NAME; + $lifetime = $this->regionsConfig->getLifetime($name); + + $this->timestampRegion = new UpdateTimestampCache($name, $this->cacheItemPool, $lifetime); + } + + return $this->timestampRegion; + } + + public function createCache(EntityManagerInterface $entityManager): Cache + { + return new DefaultCache($entityManager); + } +} diff --git a/vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php b/vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php new file mode 100644 index 0000000..249d48f --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/DefaultCollectionHydrator.php @@ -0,0 +1,75 @@ + */ + private static array $hints = [Query::HINT_CACHE_ENABLED => true]; + + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->uow = $em->getUnitOfWork(); + } + + public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array|Collection $collection): CollectionCacheEntry + { + $data = []; + + foreach ($collection as $index => $entity) { + $data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity)); + } + + return new CollectionCacheEntry($data); + } + + public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection): array|null + { + $assoc = $metadata->associationMappings[$key->association]; + $targetPersister = $this->uow->getEntityPersister($assoc->targetEntity); + assert($targetPersister instanceof CachedPersister); + $targetRegion = $targetPersister->getCacheRegion(); + $list = []; + + /** @var EntityCacheEntry[]|null $entityEntries */ + $entityEntries = $targetRegion->getMultiple($entry); + + if ($entityEntries === null) { + return null; + } + + foreach ($entityEntries as $index => $entityEntry) { + $entity = $this->uow->createEntity( + $entityEntry->class, + $entityEntry->resolveAssociationEntries($this->em), + self::$hints, + ); + + $collection->hydrateSet($index, $entity); + + $list[$index] = $entity; + } + + $this->uow->hydrationComplete(); + + return $list; + } +} diff --git a/vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php b/vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php new file mode 100644 index 0000000..6bd1524 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/DefaultEntityHydrator.php @@ -0,0 +1,176 @@ + */ + private static array $hints = [Query::HINT_CACHE_ENABLED => true]; + + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->uow = $em->getUnitOfWork(); + $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); + } + + public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, object $entity): EntityCacheEntry + { + $data = $this->uow->getOriginalEntityData($entity); + $data = [...$data, ...$metadata->getIdentifierValues($entity)]; // why update has no identifier values ? + + if ($metadata->requiresFetchAfterChange) { + if ($metadata->isVersioned) { + assert($metadata->versionField !== null); + $data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField); + } + + foreach ($metadata->fieldMappings as $name => $fieldMapping) { + if (isset($fieldMapping->generated)) { + $data[$name] = $metadata->getFieldValue($entity, $name); + } + } + } + + foreach ($metadata->associationMappings as $name => $assoc) { + if (! isset($data[$name])) { + continue; + } + + if (! $assoc->isToOne()) { + unset($data[$name]); + + continue; + } + + if (! isset($assoc->cache)) { + $targetClassMetadata = $this->em->getClassMetadata($assoc->targetEntity); + $owningAssociation = $this->em->getMetadataFactory()->getOwningSide($assoc); + $associationIds = $this->identifierFlattener->flattenIdentifier( + $targetClassMetadata, + $targetClassMetadata->getIdentifierValues($data[$name]), + ); + + unset($data[$name]); + + foreach ($associationIds as $fieldName => $fieldValue) { + if (isset($targetClassMetadata->fieldMappings[$fieldName])) { + assert($owningAssociation->isToOneOwningSide()); + $fieldMapping = $targetClassMetadata->fieldMappings[$fieldName]; + + $data[$owningAssociation->targetToSourceKeyColumns[$fieldMapping->columnName]] = $fieldValue; + + continue; + } + + $targetAssoc = $targetClassMetadata->associationMappings[$fieldName]; + + assert($assoc->isToOneOwningSide()); + foreach ($assoc->targetToSourceKeyColumns as $referencedColumn => $localColumn) { + if (isset($targetAssoc->sourceToTargetKeyColumns[$referencedColumn])) { + $data[$localColumn] = $fieldValue; + } + } + } + + continue; + } + + if (! isset($assoc->id)) { + $targetClass = DefaultProxyClassNameResolver::getClass($data[$name]); + $targetId = $this->uow->getEntityIdentifier($data[$name]); + $data[$name] = new AssociationCacheEntry($targetClass, $targetId); + + continue; + } + + // handle association identifier + $targetId = is_object($data[$name]) && $this->uow->isInIdentityMap($data[$name]) + ? $this->uow->getEntityIdentifier($data[$name]) + : $data[$name]; + + // @TODO - fix it ! + // handle UnitOfWork#createEntity hash generation + if (! is_array($targetId)) { + assert($assoc->isToOneOwningSide()); + $data[reset($assoc->joinColumnFieldNames)] = $targetId; + + $targetEntity = $this->em->getClassMetadata($assoc->targetEntity); + $targetId = [$targetEntity->identifier[0] => $targetId]; + } + + $data[$name] = new AssociationCacheEntry($assoc->targetEntity, $targetId); + } + + return new EntityCacheEntry($metadata->name, $data); + } + + public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, object|null $entity = null): object|null + { + $data = $entry->data; + $hints = self::$hints; + + if ($entity !== null) { + $hints[Query::HINT_REFRESH] = true; + $hints[Query::HINT_REFRESH_ENTITY] = $entity; + } + + foreach ($metadata->associationMappings as $name => $assoc) { + if (! isset($assoc->cache) || ! isset($data[$name])) { + continue; + } + + $assocClass = $data[$name]->class; + $assocId = $data[$name]->identifier; + $isEagerLoad = ($assoc->fetch === ClassMetadata::FETCH_EAGER || ($assoc->isOneToOne() && ! $assoc->isOwningSide())); + + if (! $isEagerLoad) { + $data[$name] = $this->em->getReference($assocClass, $assocId); + + continue; + } + + $assocMetadata = $this->em->getClassMetadata($assoc->targetEntity); + $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId); + $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity); + $assocRegion = $assocPersister->getCacheRegion(); + $assocEntry = $assocRegion->get($assocKey); + + if ($assocEntry === null) { + return null; + } + + $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), $hints); + } + + if ($entity !== null) { + $this->uow->registerManaged($entity, $key->identifier, $data); + } + + $result = $this->uow->createEntity($entry->class, $data, $hints); + + $this->uow->hydrationComplete(); + + return $result; + } +} diff --git a/vendor/doctrine/orm/src/Cache/DefaultQueryCache.php b/vendor/doctrine/orm/src/Cache/DefaultQueryCache.php new file mode 100644 index 0000000..f3bb8ac --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/DefaultQueryCache.php @@ -0,0 +1,414 @@ + */ + private static array $hints = [Query::HINT_CACHE_ENABLED => true]; + + public function __construct( + private readonly EntityManagerInterface $em, + private readonly Region $region, + ) { + $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration(); + + $this->uow = $em->getUnitOfWork(); + $this->cacheLogger = $cacheConfig->getCacheLogger(); + $this->validator = $cacheConfig->getQueryValidator(); + } + + /** + * {@inheritDoc} + */ + public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []): array|null + { + if (! ($key->cacheMode & Cache::MODE_GET)) { + return null; + } + + $cacheEntry = $this->region->get($key); + + if (! $cacheEntry instanceof QueryCacheEntry) { + return null; + } + + if (! $this->validator->isValid($key, $cacheEntry)) { + $this->region->evict($key); + + return null; + } + + $result = []; + $entityName = reset($rsm->aliasMap); + $hasRelation = ! empty($rsm->relationMap); + $persister = $this->uow->getEntityPersister($entityName); + assert($persister instanceof CachedEntityPersister); + + $region = $persister->getCacheRegion(); + $regionName = $region->getName(); + + $cm = $this->em->getClassMetadata($entityName); + + $generateKeys = static fn (array $entry): EntityCacheKey => new EntityCacheKey($cm->rootEntityName, $entry['identifier']); + + $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result)); + $entries = $region->getMultiple($cacheKeys) ?? []; + + // @TODO - move to cache hydration component + foreach ($cacheEntry->result as $index => $entry) { + $entityEntry = $entries[$index] ?? null; + + if (! $entityEntry instanceof EntityCacheEntry) { + $this->cacheLogger?->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]); + + return null; + } + + $this->cacheLogger?->entityCacheHit($regionName, $cacheKeys->identifiers[$index]); + + if (! $hasRelation) { + $result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints); + + continue; + } + + $data = $entityEntry->data; + + foreach ($entry['associations'] as $name => $assoc) { + $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); + assert($assocPersister instanceof CachedEntityPersister); + + $assocRegion = $assocPersister->getCacheRegion(); + $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); + + if ($assoc['type'] & ClassMetadata::TO_ONE) { + $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']); + $assocEntry = $assocRegion->get($assocKey); + + if ($assocEntry === null) { + $this->cacheLogger?->entityCacheMiss($assocRegion->getName(), $assocKey); + + $this->uow->hydrationComplete(); + + return null; + } + + $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); + + $this->cacheLogger?->entityCacheHit($assocRegion->getName(), $assocKey); + + continue; + } + + if (! isset($assoc['list']) || empty($assoc['list'])) { + continue; + } + + $generateKeys = static fn (array $id): EntityCacheKey => new EntityCacheKey($assocMetadata->rootEntityName, $id); + + $collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection()); + $assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list'])); + $assocEntries = $assocRegion->getMultiple($assocKeys); + + foreach ($assoc['list'] as $assocIndex => $assocId) { + $assocEntry = is_array($assocEntries) ? ($assocEntries[$assocIndex] ?? null) : null; + + if ($assocEntry === null) { + $this->cacheLogger?->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); + + $this->uow->hydrationComplete(); + + return null; + } + + $element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); + + $collection->hydrateSet($assocIndex, $element); + + $this->cacheLogger?->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); + } + + $data[$name] = $collection; + + $collection->setInitialized(true); + } + + foreach ($data as $fieldName => $unCachedAssociationData) { + // In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the + // cache key information in `$cacheEntry` will not contain details + // for fields that are associations. + // + // This means that `$data` keys for some associations that may + // actually not be cached will not be converted to actual association + // data, yet they contain L2 cache AssociationCacheEntry objects. + // + // We need to unwrap those associations into proxy references, + // since we don't have actual data for them except for identifiers. + if ($unCachedAssociationData instanceof AssociationCacheEntry) { + $data[$fieldName] = $this->em->getReference( + $unCachedAssociationData->class, + $unCachedAssociationData->identifier, + ); + } + } + + $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints); + } + + $this->uow->hydrationComplete(); + + return $result; + } + + /** + * {@inheritDoc} + */ + public function put(QueryCacheKey $key, ResultSetMapping $rsm, mixed $result, array $hints = []): bool + { + if ($rsm->scalarMappings) { + throw FeatureNotImplemented::scalarResults(); + } + + if (count($rsm->entityMappings) > 1) { + throw FeatureNotImplemented::multipleRootEntities(); + } + + if (! $rsm->isSelect) { + throw FeatureNotImplemented::nonSelectStatements(); + } + + if (! ($key->cacheMode & Cache::MODE_PUT)) { + return false; + } + + $data = []; + $entityName = reset($rsm->aliasMap); + $rootAlias = key($rsm->aliasMap); + $persister = $this->uow->getEntityPersister($entityName); + + if (! $persister instanceof CachedEntityPersister) { + throw NonCacheableEntity::fromEntity($entityName); + } + + $region = $persister->getCacheRegion(); + + $cm = $this->em->getClassMetadata($entityName); + assert($cm instanceof ClassMetadata); + + foreach ($result as $index => $entity) { + $identifier = $this->uow->getEntityIdentifier($entity); + $entityKey = new EntityCacheKey($cm->rootEntityName, $identifier); + + if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) { + // Cancel put result if entity put fail + if (! $persister->storeEntityCache($entity, $entityKey)) { + return false; + } + } + + $data[$index]['identifier'] = $identifier; + $data[$index]['associations'] = []; + + // @TODO - move to cache hydration components + foreach ($rsm->relationMap as $alias => $name) { + $parentAlias = $rsm->parentAliasMap[$alias]; + $parentClass = $rsm->aliasMap[$parentAlias]; + $metadata = $this->em->getClassMetadata($parentClass); + $assoc = $metadata->associationMappings[$name]; + $assocValue = $this->getAssociationValue($rsm, $alias, $entity); + + if ($assocValue === null) { + continue; + } + + // root entity association + if ($rootAlias === $parentAlias) { + // Cancel put result if association put fail + $assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue); + if ($assocInfo === null) { + return false; + } + + $data[$index]['associations'][$name] = $assocInfo; + + continue; + } + + // store single nested association + if (! is_array($assocValue)) { + // Cancel put result if association put fail + if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) { + return false; + } + + continue; + } + + // store array of nested association + foreach ($assocValue as $aVal) { + // Cancel put result if association put fail + if ($this->storeAssociationCache($key, $assoc, $aVal) === null) { + return false; + } + } + } + } + + return $this->region->put($key, new QueryCacheEntry($data)); + } + + /** + * @return mixed[]|null + * @psalm-return array{targetEntity: class-string, type: mixed, list?: array[], identifier?: array}|null + */ + private function storeAssociationCache(QueryCacheKey $key, AssociationMapping $assoc, mixed $assocValue): array|null + { + $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity); + $assocMetadata = $assocPersister->getClassMetadata(); + $assocRegion = $assocPersister->getCacheRegion(); + + // Handle *-to-one associations + if ($assoc->isToOne()) { + $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); + $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); + + if (! $this->uow->isUninitializedObject($assocValue) && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { + // Entity put fail + if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) { + return null; + } + } + + return [ + 'targetEntity' => $assocMetadata->rootEntityName, + 'identifier' => $assocIdentifier, + 'type' => $assoc->type(), + ]; + } + + // Handle *-to-many associations + $list = []; + + foreach ($assocValue as $assocItemIndex => $assocItem) { + $assocIdentifier = $this->uow->getEntityIdentifier($assocItem); + $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); + + if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { + // Entity put fail + if (! $assocPersister->storeEntityCache($assocItem, $entityKey)) { + return null; + } + } + + $list[$assocItemIndex] = $assocIdentifier; + } + + return [ + 'targetEntity' => $assocMetadata->rootEntityName, + 'type' => $assoc->type(), + 'list' => $list, + ]; + } + + /** @psalm-return list|object|null */ + private function getAssociationValue( + ResultSetMapping $rsm, + string $assocAlias, + object $entity, + ): array|object|null { + $path = []; + $alias = $assocAlias; + + while (isset($rsm->parentAliasMap[$alias])) { + $parent = $rsm->parentAliasMap[$alias]; + $field = $rsm->relationMap[$alias]; + $class = $rsm->aliasMap[$parent]; + + array_unshift($path, [ + 'field' => $field, + 'class' => $class, + ]); + + $alias = $parent; + } + + return $this->getAssociationPathValue($entity, $path); + } + + /** + * @psalm-param array $path + * + * @psalm-return list|object|null + */ + private function getAssociationPathValue(mixed $value, array $path): array|object|null + { + $mapping = array_shift($path); + $metadata = $this->em->getClassMetadata($mapping['class']); + $assoc = $metadata->associationMappings[$mapping['field']]; + $value = $metadata->getFieldValue($value, $mapping['field']); + + if ($value === null) { + return null; + } + + if ($path === []) { + return $value; + } + + // Handle *-to-one associations + if ($assoc->isToOne()) { + return $this->getAssociationPathValue($value, $path); + } + + $values = []; + + foreach ($value as $item) { + $values[] = $this->getAssociationPathValue($item, $path); + } + + return $values; + } + + public function clear(): bool + { + return $this->region->evictAll(); + } + + public function getRegion(): Region + { + return $this->region; + } +} diff --git a/vendor/doctrine/orm/src/Cache/EntityCacheEntry.php b/vendor/doctrine/orm/src/Cache/EntityCacheEntry.php new file mode 100644 index 0000000..69bc813 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/EntityCacheEntry.php @@ -0,0 +1,50 @@ + $data The entity map data + * @psalm-param class-string $class The entity class name + */ + public function __construct( + public readonly string $class, + public readonly array $data, + ) { + } + + /** + * Creates a new EntityCacheEntry + * + * This method allows Doctrine\Common\Cache\PhpFileCache compatibility + * + * @param array $values array containing property values + */ + public static function __set_state(array $values): self + { + return new self($values['class'], $values['data']); + } + + /** + * Retrieves the entity data resolving cache entries + * + * @return array + */ + public function resolveAssociationEntries(EntityManagerInterface $em): array + { + return array_map(static function ($value) use ($em) { + if (! ($value instanceof AssociationCacheEntry)) { + return $value; + } + + return $em->getReference($value->class, $value->identifier); + }, $this->data); + } +} diff --git a/vendor/doctrine/orm/src/Cache/EntityCacheKey.php b/vendor/doctrine/orm/src/Cache/EntityCacheKey.php new file mode 100644 index 0000000..095ddaa --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/EntityCacheKey.php @@ -0,0 +1,38 @@ + + */ + public readonly array $identifier; + + /** + * @param class-string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class. + * @param array $identifier The entity identifier + */ + public function __construct( + public readonly string $entityClass, + array $identifier, + ) { + ksort($identifier); + + $this->identifier = $identifier; + + parent::__construct(str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier))); + } +} diff --git a/vendor/doctrine/orm/src/Cache/EntityHydrator.php b/vendor/doctrine/orm/src/Cache/EntityHydrator.php new file mode 100644 index 0000000..13cd21f --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/EntityHydrator.php @@ -0,0 +1,28 @@ +time = $time ?? time(); + } + + public static function createLockRead(): Lock + { + return new self(uniqid((string) time(), true)); + } +} diff --git a/vendor/doctrine/orm/src/Cache/LockException.php b/vendor/doctrine/orm/src/Cache/LockException.php new file mode 100644 index 0000000..bb2d4ec --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/LockException.php @@ -0,0 +1,14 @@ + */ + private array $loggers = []; + + public function setLogger(string $name, CacheLogger $logger): void + { + $this->loggers[$name] = $logger; + } + + public function getLogger(string $name): CacheLogger|null + { + return $this->loggers[$name] ?? null; + } + + /** @return array */ + public function getLoggers(): array + { + return $this->loggers; + } + + public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->collectionCacheHit($regionName, $key); + } + } + + public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->collectionCacheMiss($regionName, $key); + } + } + + public function collectionCachePut(string $regionName, CollectionCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->collectionCachePut($regionName, $key); + } + } + + public function entityCacheHit(string $regionName, EntityCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->entityCacheHit($regionName, $key); + } + } + + public function entityCacheMiss(string $regionName, EntityCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->entityCacheMiss($regionName, $key); + } + } + + public function entityCachePut(string $regionName, EntityCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->entityCachePut($regionName, $key); + } + } + + public function queryCacheHit(string $regionName, QueryCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->queryCacheHit($regionName, $key); + } + } + + public function queryCacheMiss(string $regionName, QueryCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->queryCacheMiss($regionName, $key); + } + } + + public function queryCachePut(string $regionName, QueryCacheKey $key): void + { + foreach ($this->loggers as $logger) { + $logger->queryCachePut($regionName, $key); + } + } +} diff --git a/vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php b/vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php new file mode 100644 index 0000000..092104e --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Logging/StatisticsCacheLogger.php @@ -0,0 +1,174 @@ + */ + private array $cacheMissCountMap = []; + + /** @var array */ + private array $cacheHitCountMap = []; + + /** @var array */ + private array $cachePutCountMap = []; + + public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void + { + $this->cacheMissCountMap[$regionName] + = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; + } + + public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void + { + $this->cacheHitCountMap[$regionName] + = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; + } + + public function collectionCachePut(string $regionName, CollectionCacheKey $key): void + { + $this->cachePutCountMap[$regionName] + = ($this->cachePutCountMap[$regionName] ?? 0) + 1; + } + + public function entityCacheMiss(string $regionName, EntityCacheKey $key): void + { + $this->cacheMissCountMap[$regionName] + = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; + } + + public function entityCacheHit(string $regionName, EntityCacheKey $key): void + { + $this->cacheHitCountMap[$regionName] + = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; + } + + public function entityCachePut(string $regionName, EntityCacheKey $key): void + { + $this->cachePutCountMap[$regionName] + = ($this->cachePutCountMap[$regionName] ?? 0) + 1; + } + + public function queryCacheHit(string $regionName, QueryCacheKey $key): void + { + $this->cacheHitCountMap[$regionName] + = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; + } + + public function queryCacheMiss(string $regionName, QueryCacheKey $key): void + { + $this->cacheMissCountMap[$regionName] + = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; + } + + public function queryCachePut(string $regionName, QueryCacheKey $key): void + { + $this->cachePutCountMap[$regionName] + = ($this->cachePutCountMap[$regionName] ?? 0) + 1; + } + + /** + * Get the number of entries successfully retrieved from cache. + * + * @param string $regionName The name of the cache region. + */ + public function getRegionHitCount(string $regionName): int + { + return $this->cacheHitCountMap[$regionName] ?? 0; + } + + /** + * Get the number of cached entries *not* found in cache. + * + * @param string $regionName The name of the cache region. + */ + public function getRegionMissCount(string $regionName): int + { + return $this->cacheMissCountMap[$regionName] ?? 0; + } + + /** + * Get the number of cacheable entries put in cache. + * + * @param string $regionName The name of the cache region. + */ + public function getRegionPutCount(string $regionName): int + { + return $this->cachePutCountMap[$regionName] ?? 0; + } + + /** @return array */ + public function getRegionsMiss(): array + { + return $this->cacheMissCountMap; + } + + /** @return array */ + public function getRegionsHit(): array + { + return $this->cacheHitCountMap; + } + + /** @return array */ + public function getRegionsPut(): array + { + return $this->cachePutCountMap; + } + + /** + * Clear region statistics + * + * @param string $regionName The name of the cache region. + */ + public function clearRegionStats(string $regionName): void + { + $this->cachePutCountMap[$regionName] = 0; + $this->cacheHitCountMap[$regionName] = 0; + $this->cacheMissCountMap[$regionName] = 0; + } + + /** + * Clear all statistics + */ + public function clearStats(): void + { + $this->cachePutCountMap = []; + $this->cacheHitCountMap = []; + $this->cacheMissCountMap = []; + } + + /** + * Get the total number of put in cache. + */ + public function getPutCount(): int + { + return array_sum($this->cachePutCountMap); + } + + /** + * Get the total number of entries successfully retrieved from cache. + */ + public function getHitCount(): int + { + return array_sum($this->cacheHitCountMap); + } + + /** + * Get the total number of cached entries *not* found in cache. + */ + public function getMissCount(): int + { + return array_sum($this->cacheMissCountMap); + } +} diff --git a/vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php b/vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php new file mode 100644 index 0000000..223692c --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Persister/CachedPersister.php @@ -0,0 +1,25 @@ +getConfiguration(); + $cacheConfig = $configuration->getSecondLevelCacheConfiguration(); + $cacheFactory = $cacheConfig->getCacheFactory(); + + $this->regionName = $region->getName(); + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->cacheLogger = $cacheConfig->getCacheLogger(); + $this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association); + $this->sourceEntity = $em->getClassMetadata($association->sourceEntity); + $this->targetEntity = $em->getClassMetadata($association->targetEntity); + } + + public function getCacheRegion(): Region + { + return $this->region; + } + + public function getSourceEntityMetadata(): ClassMetadata + { + return $this->sourceEntity; + } + + public function getTargetEntityMetadata(): ClassMetadata + { + return $this->targetEntity; + } + + public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key): array|null + { + $cache = $this->region->get($key); + + if ($cache === null) { + return null; + } + + return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection); + } + + public function storeCollectionCache(CollectionCacheKey $key, Collection|array $elements): void + { + $associationMapping = $this->sourceEntity->associationMappings[$key->association]; + $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); + assert($targetPersister instanceof CachedEntityPersister); + $targetRegion = $targetPersister->getCacheRegion(); + $targetHydrator = $targetPersister->getEntityHydrator(); + + // Only preserve ordering if association configured it + if (! $associationMapping->isIndexed()) { + // Elements may be an array or a Collection + $elements = array_values($elements instanceof Collection ? $elements->getValues() : $elements); + } + + $entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements); + + foreach ($entry->identifiers as $index => $entityKey) { + if ($targetRegion->contains($entityKey)) { + continue; + } + + $class = $this->targetEntity; + $className = DefaultProxyClassNameResolver::getClass($elements[$index]); + + if ($className !== $this->targetEntity->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $entity = $elements[$index]; + $entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity); + + $targetRegion->put($entityKey, $entityEntry); + } + + if ($this->region->put($key, $entry)) { + $this->cacheLogger?->collectionCachePut($this->regionName, $key); + } + } + + public function contains(PersistentCollection $collection, object $element): bool + { + return $this->persister->contains($collection, $element); + } + + public function containsKey(PersistentCollection $collection, mixed $key): bool + { + return $this->persister->containsKey($collection, $key); + } + + public function count(PersistentCollection $collection): int + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + $entry = $this->region->get($key); + + if ($entry !== null) { + return count($entry->identifiers); + } + + return $this->persister->count($collection); + } + + public function get(PersistentCollection $collection, mixed $index): mixed + { + return $this->persister->get($collection, $index); + } + + /** + * {@inheritDoc} + */ + public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array + { + return $this->persister->slice($collection, $offset, $length); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array + { + return $this->persister->loadCriteria($collection, $criteria); + } +} diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php new file mode 100644 index 0000000..6b10c80 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/CachedCollectionPersister.php @@ -0,0 +1,36 @@ +queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->storeCollectionCache($item['key'], $item['list']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $key) { + $this->region->evict($key); + } + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + $this->queuedCache = []; + } + + public function delete(PersistentCollection $collection): void + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + + $this->persister->delete($collection); + + $this->queuedCache['delete'][spl_object_id($collection)] = $key; + } + + public function update(PersistentCollection $collection): void + { + $isInitialized = $collection->isInitialized(); + $isDirty = $collection->isDirty(); + + if (! $isInitialized && ! $isDirty) { + return; + } + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + + // Invalidate non initialized collections OR ordered collection + if ($isDirty && ! $isInitialized || $this->association->isOrdered()) { + $this->persister->update($collection); + + $this->queuedCache['delete'][spl_object_id($collection)] = $key; + + return; + } + + $this->persister->update($collection); + + $this->queuedCache['update'][spl_object_id($collection)] = [ + 'key' => $key, + 'list' => $collection, + ]; + } +} diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php new file mode 100644 index 0000000..96e0a4b --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php @@ -0,0 +1,24 @@ +isDirty() && $collection->getSnapshot()) { + throw CannotUpdateReadOnlyCollection::fromEntityAndField( + DefaultProxyClassNameResolver::getClass($collection->getOwner()), + $this->association->fieldName, + ); + } + + parent::update($collection); + } +} diff --git a/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php new file mode 100644 index 0000000..347a065 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php @@ -0,0 +1,103 @@ +queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = []; + } + + public function delete(PersistentCollection $collection): void + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + $lock = $this->region->lock($key); + + $this->persister->delete($collection); + + if ($lock === null) { + return; + } + + $this->queuedCache['delete'][spl_object_id($collection)] = [ + 'key' => $key, + 'lock' => $lock, + ]; + } + + public function update(PersistentCollection $collection): void + { + $isInitialized = $collection->isInitialized(); + $isDirty = $collection->isDirty(); + + if (! $isInitialized && ! $isDirty) { + return; + } + + $this->persister->update($collection); + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId); + $lock = $this->region->lock($key); + + if ($lock === null) { + return; + } + + $this->queuedCache['update'][spl_object_id($collection)] = [ + 'key' => $key, + 'lock' => $lock, + ]; + } +} diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php new file mode 100644 index 0000000..9f371d8 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/AbstractEntityPersister.php @@ -0,0 +1,557 @@ +|null + */ + protected array|null $joinedAssociations = null; + + public function __construct( + protected EntityPersister $persister, + protected Region $region, + EntityManagerInterface $em, + protected ClassMetadata $class, + ) { + $configuration = $em->getConfiguration(); + $cacheConfig = $configuration->getSecondLevelCacheConfiguration(); + $cacheFactory = $cacheConfig->getCacheFactory(); + + $this->cache = $em->getCache(); + $this->regionName = $region->getName(); + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->cacheLogger = $cacheConfig->getCacheLogger(); + $this->timestampRegion = $cacheFactory->getTimestampRegion(); + $this->hydrator = $cacheFactory->buildEntityHydrator($em, $class); + $this->timestampKey = new TimestampCacheKey($this->class->rootEntityName); + } + + public function addInsert(object $entity): void + { + $this->persister->addInsert($entity); + } + + /** + * {@inheritDoc} + */ + public function getInserts(): array + { + return $this->persister->getInserts(); + } + + public function getSelectSQL( + array|Criteria $criteria, + AssociationMapping|null $assoc = null, + LockMode|int|null $lockMode = null, + int|null $limit = null, + int|null $offset = null, + array|null $orderBy = null, + ): string { + return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy); + } + + public function getCountSQL(array|Criteria $criteria = []): string + { + return $this->persister->getCountSQL($criteria); + } + + public function getInsertSQL(): string + { + return $this->persister->getInsertSQL(); + } + + public function getResultSetMapping(): ResultSetMapping + { + return $this->persister->getResultSetMapping(); + } + + public function getSelectConditionStatementSQL( + string $field, + mixed $value, + AssociationMapping|null $assoc = null, + string|null $comparison = null, + ): string { + return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison); + } + + public function exists(object $entity, Criteria|null $extraConditions = null): bool + { + if ($extraConditions === null) { + $key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity)); + + if ($this->region->contains($key)) { + return true; + } + } + + return $this->persister->exists($entity, $extraConditions); + } + + public function getCacheRegion(): Region + { + return $this->region; + } + + public function getEntityHydrator(): EntityHydrator + { + return $this->hydrator; + } + + public function storeEntityCache(object $entity, EntityCacheKey $key): bool + { + $class = $this->class; + $className = DefaultProxyClassNameResolver::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $entry = $this->hydrator->buildCacheEntry($class, $key, $entity); + $cached = $this->region->put($key, $entry); + + if ($cached) { + $this->cacheLogger?->entityCachePut($this->regionName, $key); + } + + return $cached; + } + + private function storeJoinedAssociations(object $entity): void + { + if ($this->joinedAssociations === null) { + $associations = []; + + foreach ($this->class->associationMappings as $name => $assoc) { + if ( + isset($assoc->cache) && + ($assoc->isToOne()) && + ($assoc->fetch === ClassMetadata::FETCH_EAGER || ! $assoc->isOwningSide()) + ) { + $associations[] = $name; + } + } + + $this->joinedAssociations = $associations; + } + + foreach ($this->joinedAssociations as $name) { + $assoc = $this->class->associationMappings[$name]; + $assocEntity = $this->class->getFieldValue($entity, $name); + + if ($assocEntity === null) { + continue; + } + + $assocId = $this->uow->getEntityIdentifier($assocEntity); + $assocMetadata = $this->metadataFactory->getMetadataFor($assoc->targetEntity); + $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId); + $assocPersister = $this->uow->getEntityPersister($assoc->targetEntity); + + $assocPersister->storeEntityCache($assocEntity, $assocKey); + } + } + + /** + * Generates a string of currently query + * + * @param string[]|Criteria $criteria + * @param array|null $orderBy + */ + protected function getHash( + string $query, + array|Criteria $criteria, + array|null $orderBy = null, + int|null $limit = null, + int|null $offset = null, + ): string { + [$params] = $criteria instanceof Criteria + ? $this->persister->expandCriteriaParameters($criteria) + : $this->persister->expandParameters($criteria); + + return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset); + } + + /** + * {@inheritDoc} + */ + public function expandParameters(array $criteria): array + { + return $this->persister->expandParameters($criteria); + } + + /** + * {@inheritDoc} + */ + public function expandCriteriaParameters(Criteria $criteria): array + { + return $this->persister->expandCriteriaParameters($criteria); + } + + public function getClassMetadata(): ClassMetadata + { + return $this->persister->getClassMetadata(); + } + + /** + * {@inheritDoc} + */ + public function getManyToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array { + return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit); + } + + /** + * {@inheritDoc} + */ + public function getOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array { + return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit); + } + + public function getOwningTable(string $fieldName): string + { + return $this->persister->getOwningTable($fieldName); + } + + public function executeInserts(): void + { + // The commit order/foreign key relationships may make it necessary that multiple calls to executeInsert() + // are performed, so collect all the new entities. + $newInserts = $this->persister->getInserts(); + + if ($newInserts) { + $this->queuedCache['insert'] = array_merge($this->queuedCache['insert'] ?? [], $newInserts); + } + + $this->persister->executeInserts(); + } + + /** + * {@inheritDoc} + */ + public function load( + array $criteria, + object|null $entity = null, + AssociationMapping|null $assoc = null, + array $hints = [], + LockMode|int|null $lockMode = null, + int|null $limit = null, + array|null $orderBy = null, + ): object|null { + if ($entity !== null || $assoc !== null || $hints !== [] || $lockMode !== null) { + return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + } + + //handle only EntityRepository#findOneBy + $query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy); + $hash = $this->getHash($query, $criteria); + $rsm = $this->getResultSetMapping(); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); + $queryCache = $this->cache->getQueryCache($this->regionName); + $result = $queryCache->get($queryKey, $rsm); + + if ($result !== null) { + $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey); + + return $result[0]; + } + + $result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + + if ($result === null) { + return null; + } + + $cached = $queryCache->put($queryKey, $rsm, [$result]); + + $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey); + + if ($cached) { + $this->cacheLogger?->queryCachePut($this->regionName, $queryKey); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function loadAll( + array $criteria = [], + array|null $orderBy = null, + int|null $limit = null, + int|null $offset = null, + ): array { + $query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); + $hash = $this->getHash($query, $criteria); + $rsm = $this->getResultSetMapping(); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); + $queryCache = $this->cache->getQueryCache($this->regionName); + $result = $queryCache->get($queryKey, $rsm); + + if ($result !== null) { + $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey); + + return $result; + } + + $result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset); + $cached = $queryCache->put($queryKey, $rsm, $result); + + if ($result) { + $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey); + } + + if ($cached) { + $this->cacheLogger?->queryCachePut($this->regionName, $queryKey); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function loadById(array $identifier, object|null $entity = null): object|null + { + $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); + $cacheEntry = $this->region->get($cacheKey); + $class = $this->class; + + if ($cacheEntry !== null) { + if ($cacheEntry->class !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($cacheEntry->class); + } + + $cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity); + + if ($cachedEntity !== null) { + $this->cacheLogger?->entityCacheHit($this->regionName, $cacheKey); + + return $cachedEntity; + } + } + + $entity = $this->persister->loadById($identifier, $entity); + + if ($entity === null) { + return null; + } + + $class = $this->class; + $className = DefaultProxyClassNameResolver::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity); + $cached = $this->region->put($cacheKey, $cacheEntry); + + if ($cached && ($this->joinedAssociations === null || $this->joinedAssociations)) { + $this->storeJoinedAssociations($entity); + } + + if ($cached) { + $this->cacheLogger?->entityCachePut($this->regionName, $cacheKey); + } + + $this->cacheLogger?->entityCacheMiss($this->regionName, $cacheKey); + + return $entity; + } + + public function count(array|Criteria $criteria = []): int + { + return $this->persister->count($criteria); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(Criteria $criteria): array + { + $orderBy = $criteria->orderings(); + $limit = $criteria->getMaxResults(); + $offset = $criteria->getFirstResult(); + $query = $this->persister->getSelectSQL($criteria); + $hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset); + $rsm = $this->getResultSetMapping(); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); + $queryCache = $this->cache->getQueryCache($this->regionName); + $cacheResult = $queryCache->get($queryKey, $rsm); + + if ($cacheResult !== null) { + $this->cacheLogger?->queryCacheHit($this->regionName, $queryKey); + + return $cacheResult; + } + + $result = $this->persister->loadCriteria($criteria); + $cached = $queryCache->put($queryKey, $rsm, $result); + + if ($result) { + $this->cacheLogger?->queryCacheMiss($this->regionName, $queryKey); + } + + if ($cached) { + $this->cacheLogger?->queryCachePut($this->regionName, $queryKey); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function loadManyToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + PersistentCollection $collection, + ): array { + $persister = $this->uow->getCollectionPersister($assoc); + $hasCache = ($persister instanceof CachedPersister); + + if (! $hasCache) { + return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection); + } + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = $this->buildCollectionCacheKey($assoc, $ownerId); + $list = $persister->loadCollectionCache($collection, $key); + + if ($list !== null) { + $this->cacheLogger?->collectionCacheHit($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + $list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection); + + $persister->storeCollectionCache($key, $list); + + $this->cacheLogger?->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + public function loadOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + PersistentCollection $collection, + ): mixed { + $persister = $this->uow->getCollectionPersister($assoc); + $hasCache = ($persister instanceof CachedPersister); + + if (! $hasCache) { + return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection); + } + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = $this->buildCollectionCacheKey($assoc, $ownerId); + $list = $persister->loadCollectionCache($collection, $key); + + if ($list !== null) { + $this->cacheLogger?->collectionCacheHit($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + $list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection); + + $persister->storeCollectionCache($key, $list); + + $this->cacheLogger?->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); + + return $list; + } + + /** + * {@inheritDoc} + */ + public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null + { + return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier); + } + + /** + * {@inheritDoc} + */ + public function lock(array $criteria, LockMode|int $lockMode): void + { + $this->persister->lock($criteria, $lockMode); + } + + /** + * {@inheritDoc} + */ + public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void + { + $this->persister->refresh($id, $entity, $lockMode); + } + + /** @param array $ownerId */ + protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId): CollectionCacheKey + { + $metadata = $this->metadataFactory->getMetadataFor($association->sourceEntity); + assert($metadata instanceof ClassMetadata); + + return new CollectionCacheKey($metadata->rootEntityName, $association->fieldName, $ownerId); + } +} diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php new file mode 100644 index 0000000..5fba56f --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/CachedEntityPersister.php @@ -0,0 +1,20 @@ +queuedCache['insert'])) { + foreach ($this->queuedCache['insert'] as $entity) { + $isChanged = $this->updateCache($entity, $isChanged); + } + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $entity) { + $isChanged = $this->updateCache($entity, $isChanged); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $key) { + $this->region->evict($key); + + $isChanged = true; + } + } + + if ($isChanged) { + $this->timestampRegion->update($this->timestampKey); + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + $this->queuedCache = []; + } + + public function delete(object $entity): bool + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $deleted = $this->persister->delete($entity); + + if ($deleted) { + $this->region->evict($key); + } + + $this->queuedCache['delete'][] = $key; + + return $deleted; + } + + public function update(object $entity): void + { + $this->persister->update($entity); + + $this->queuedCache['update'][] = $entity; + } + + private function updateCache(object $entity, bool $isChanged): bool + { + $class = $this->metadataFactory->getMetadataFor($entity::class); + $key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $entry = $this->hydrator->buildCacheEntry($class, $key, $entity); + $cached = $this->region->put($key, $entry); + $isChanged = $isChanged || $cached; + + if ($cached) { + $this->cacheLogger?->entityCachePut($this->regionName, $key); + } + + return $isChanged; + } +} diff --git a/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php b/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php new file mode 100644 index 0000000..4cd1784 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php @@ -0,0 +1,19 @@ +queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + + $isChanged = true; + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + + $isChanged = true; + } + } + + if ($isChanged) { + $this->timestampRegion->update($this->timestampKey); + } + + $this->queuedCache = []; + } + + public function afterTransactionRolledBack(): void + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = []; + } + + public function delete(object $entity): bool + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = $this->region->lock($key); + $deleted = $this->persister->delete($entity); + + if ($deleted) { + $this->region->evict($key); + } + + if ($lock === null) { + return $deleted; + } + + $this->queuedCache['delete'][] = [ + 'lock' => $lock, + 'key' => $key, + ]; + + return $deleted; + } + + public function update(object $entity): void + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = $this->region->lock($key); + + $this->persister->update($entity); + + if ($lock === null) { + return; + } + + $this->queuedCache['update'][] = [ + 'lock' => $lock, + 'key' => $key, + ]; + } +} diff --git a/vendor/doctrine/orm/src/Cache/QueryCache.php b/vendor/doctrine/orm/src/Cache/QueryCache.php new file mode 100644 index 0000000..e697680 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/QueryCache.php @@ -0,0 +1,28 @@ + $result List of entity identifiers */ + public function __construct( + public readonly array $result, + float|null $time = null, + ) { + $this->time = $time ?: microtime(true); + } + + /** @param array $values */ + public static function __set_state(array $values): self + { + return new self($values['result'], $values['time']); + } +} diff --git a/vendor/doctrine/orm/src/Cache/QueryCacheKey.php b/vendor/doctrine/orm/src/Cache/QueryCacheKey.php new file mode 100644 index 0000000..2372e5a --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/QueryCacheKey.php @@ -0,0 +1,23 @@ +name; + } + + public function contains(CacheKey $key): bool + { + return $this->cacheItemPool->hasItem($this->getCacheEntryKey($key)); + } + + public function get(CacheKey $key): CacheEntry|null + { + $item = $this->cacheItemPool->getItem($this->getCacheEntryKey($key)); + $entry = $item->isHit() ? $item->get() : null; + + if (! $entry instanceof CacheEntry) { + return null; + } + + return $entry; + } + + public function getMultiple(CollectionCacheEntry $collection): array|null + { + $keys = array_map( + $this->getCacheEntryKey(...), + $collection->identifiers, + ); + /** @var iterable $items */ + $items = $this->cacheItemPool->getItems($keys); + if ($items instanceof Traversable) { + $items = iterator_to_array($items); + } + + $result = []; + foreach ($keys as $arrayKey => $cacheKey) { + if (! isset($items[$cacheKey]) || ! $items[$cacheKey]->isHit()) { + return null; + } + + $entry = $items[$cacheKey]->get(); + if (! $entry instanceof CacheEntry) { + return null; + } + + $result[$arrayKey] = $entry; + } + + return $result; + } + + public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool + { + $item = $this->cacheItemPool + ->getItem($this->getCacheEntryKey($key)) + ->set($entry); + + if ($this->lifetime > 0) { + $item->expiresAfter($this->lifetime); + } + + return $this->cacheItemPool->save($item); + } + + public function evict(CacheKey $key): bool + { + return $this->cacheItemPool->deleteItem($this->getCacheEntryKey($key)); + } + + public function evictAll(): bool + { + return $this->cacheItemPool->clear(self::REGION_PREFIX . $this->name); + } + + private function getCacheEntryKey(CacheKey $key): string + { + return self::REGION_PREFIX . $this->name . self::REGION_KEY_SEPARATOR . strtr($key->hash, '{}()/\@:', '________'); + } +} diff --git a/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php b/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php new file mode 100644 index 0000000..bedd6a6 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php @@ -0,0 +1,194 @@ +getLockFileName($key); + + if (! is_file($filename)) { + return false; + } + + $time = $this->getLockTime($filename); + $content = $this->getLockContent($filename); + + if ($content === false || $time === false) { + @unlink($filename); + + return false; + } + + if ($lock && $content === $lock->value) { + return false; + } + + // outdated lock + if ($time + $this->lockLifetime <= time()) { + @unlink($filename); + + return false; + } + + return true; + } + + private function getLockFileName(CacheKey $key): string + { + return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION; + } + + private function getLockContent(string $filename): string|false + { + return @file_get_contents($filename); + } + + private function getLockTime(string $filename): int|false + { + return @fileatime($filename); + } + + public function getName(): string + { + return $this->region->getName(); + } + + public function contains(CacheKey $key): bool + { + if ($this->isLocked($key)) { + return false; + } + + return $this->region->contains($key); + } + + public function get(CacheKey $key): CacheEntry|null + { + if ($this->isLocked($key)) { + return null; + } + + return $this->region->get($key); + } + + public function getMultiple(CollectionCacheEntry $collection): array|null + { + if (array_filter(array_map($this->isLocked(...), $collection->identifiers))) { + return null; + } + + return $this->region->getMultiple($collection); + } + + public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool + { + if ($this->isLocked($key, $lock)) { + return false; + } + + return $this->region->put($key, $entry); + } + + public function evict(CacheKey $key): bool + { + if ($this->isLocked($key)) { + @unlink($this->getLockFileName($key)); + } + + return $this->region->evict($key); + } + + public function evictAll(): bool + { + // The check below is necessary because on some platforms glob returns false + // when nothing matched (even though no errors occurred) + $filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)) ?: []; + + foreach ($filenames as $filename) { + @unlink($filename); + } + + return $this->region->evictAll(); + } + + public function lock(CacheKey $key): Lock|null + { + if ($this->isLocked($key)) { + return null; + } + + $lock = Lock::createLockRead(); + $filename = $this->getLockFileName($key); + + if (@file_put_contents($filename, $lock->value, LOCK_EX) === false) { + return null; + } + + chmod($filename, 0664); + + return $lock; + } + + public function unlock(CacheKey $key, Lock $lock): bool + { + if ($this->isLocked($key, $lock)) { + return false; + } + + return @unlink($this->getLockFileName($key)); + } +} diff --git a/vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php b/vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php new file mode 100644 index 0000000..aa75a90 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/Region/UpdateTimestampCache.php @@ -0,0 +1,20 @@ +put($key, new TimestampCacheEntry()); + } +} diff --git a/vendor/doctrine/orm/src/Cache/RegionsConfiguration.php b/vendor/doctrine/orm/src/Cache/RegionsConfiguration.php new file mode 100644 index 0000000..a852831 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/RegionsConfiguration.php @@ -0,0 +1,63 @@ + */ + private array $lifetimes = []; + + /** @var array */ + private array $lockLifetimes = []; + + public function __construct( + private int $defaultLifetime = 3600, + private int $defaultLockLifetime = 60, + ) { + } + + public function getDefaultLifetime(): int + { + return $this->defaultLifetime; + } + + public function setDefaultLifetime(int $defaultLifetime): void + { + $this->defaultLifetime = $defaultLifetime; + } + + public function getDefaultLockLifetime(): int + { + return $this->defaultLockLifetime; + } + + public function setDefaultLockLifetime(int $defaultLockLifetime): void + { + $this->defaultLockLifetime = $defaultLockLifetime; + } + + public function getLifetime(string $regionName): int + { + return $this->lifetimes[$regionName] ?? $this->defaultLifetime; + } + + public function setLifetime(string $name, int $lifetime): void + { + $this->lifetimes[$name] = $lifetime; + } + + public function getLockLifetime(string $regionName): int + { + return $this->lockLifetimes[$regionName] ?? $this->defaultLockLifetime; + } + + public function setLockLifetime(string $name, int $lifetime): void + { + $this->lockLifetimes[$name] = $lifetime; + } +} diff --git a/vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php b/vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php new file mode 100644 index 0000000..60c9175 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/TimestampCacheEntry.php @@ -0,0 +1,29 @@ +time = $time ?? microtime(true); + } + + /** + * Creates a new TimestampCacheEntry + * + * This method allow Doctrine\Common\Cache\PhpFileCache compatibility + * + * @param array $values array containing property values + */ + public static function __set_state(array $values): TimestampCacheEntry + { + return new self($values['time']); + } +} diff --git a/vendor/doctrine/orm/src/Cache/TimestampCacheKey.php b/vendor/doctrine/orm/src/Cache/TimestampCacheKey.php new file mode 100644 index 0000000..5aef4c5 --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/TimestampCacheKey.php @@ -0,0 +1,17 @@ +regionUpdated($key, $entry)) { + return false; + } + + if ($key->lifetime === 0) { + return true; + } + + return $entry->time + $key->lifetime > microtime(true); + } + + private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry): bool + { + if ($key->timestampKey === null) { + return false; + } + + $timestamp = $this->timestampRegion->get($key->timestampKey); + + return $timestamp && $timestamp->time > $entry->time; + } +} diff --git a/vendor/doctrine/orm/src/Cache/TimestampRegion.php b/vendor/doctrine/orm/src/Cache/TimestampRegion.php new file mode 100644 index 0000000..b74fa8d --- /dev/null +++ b/vendor/doctrine/orm/src/Cache/TimestampRegion.php @@ -0,0 +1,18 @@ +, ClassMetadata::GENERATOR_TYPE_*> */ + private $identityGenerationPreferences = []; + + /** @psalm-param array, ClassMetadata::GENERATOR_TYPE_*> $value */ + public function setIdentityGenerationPreferences(array $value): void + { + $this->identityGenerationPreferences = $value; + } + + /** @psalm-return array, ClassMetadata::GENERATOR_TYPE_*> $value */ + public function getIdentityGenerationPreferences(): array + { + return $this->identityGenerationPreferences; + } + + /** + * Sets the directory where Doctrine generates any necessary proxy class files. + */ + public function setProxyDir(string $dir): void + { + $this->attributes['proxyDir'] = $dir; + } + + /** + * Gets the directory where Doctrine generates any necessary proxy class files. + */ + public function getProxyDir(): string|null + { + return $this->attributes['proxyDir'] ?? null; + } + + /** + * Gets the strategy for automatically generating proxy classes. + * + * @return ProxyFactory::AUTOGENERATE_* + */ + public function getAutoGenerateProxyClasses(): int + { + return $this->attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS; + } + + /** + * Sets the strategy for automatically generating proxy classes. + * + * @param bool|ProxyFactory::AUTOGENERATE_* $autoGenerate True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER. + */ + public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void + { + $this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate; + } + + /** + * Gets the namespace where proxy classes reside. + */ + public function getProxyNamespace(): string|null + { + return $this->attributes['proxyNamespace'] ?? null; + } + + /** + * Sets the namespace where proxy classes reside. + */ + public function setProxyNamespace(string $ns): void + { + $this->attributes['proxyNamespace'] = $ns; + } + + /** + * Sets the cache driver implementation that is used for metadata caching. + * + * @todo Force parameter to be a Closure to ensure lazy evaluation + * (as soon as a metadata cache is in effect, the driver never needs to initialize). + */ + public function setMetadataDriverImpl(MappingDriver $driverImpl): void + { + $this->attributes['metadataDriverImpl'] = $driverImpl; + } + + /** + * Sets the entity alias map. + * + * @psalm-param array $entityNamespaces + */ + public function setEntityNamespaces(array $entityNamespaces): void + { + $this->attributes['entityNamespaces'] = $entityNamespaces; + } + + /** + * Retrieves the list of registered entity namespace aliases. + * + * @psalm-return array + */ + public function getEntityNamespaces(): array + { + return $this->attributes['entityNamespaces']; + } + + /** + * Gets the cache driver implementation that is used for the mapping metadata. + */ + public function getMetadataDriverImpl(): MappingDriver|null + { + return $this->attributes['metadataDriverImpl'] ?? null; + } + + /** + * Gets the cache driver implementation that is used for the query cache (SQL cache). + */ + public function getQueryCache(): CacheItemPoolInterface|null + { + return $this->attributes['queryCache'] ?? null; + } + + /** + * Sets the cache driver implementation that is used for the query cache (SQL cache). + */ + public function setQueryCache(CacheItemPoolInterface $cache): void + { + $this->attributes['queryCache'] = $cache; + } + + public function getHydrationCache(): CacheItemPoolInterface|null + { + return $this->attributes['hydrationCache'] ?? null; + } + + public function setHydrationCache(CacheItemPoolInterface $cache): void + { + $this->attributes['hydrationCache'] = $cache; + } + + public function getMetadataCache(): CacheItemPoolInterface|null + { + return $this->attributes['metadataCache'] ?? null; + } + + public function setMetadataCache(CacheItemPoolInterface $cache): void + { + $this->attributes['metadataCache'] = $cache; + } + + /** + * Registers a custom DQL function that produces a string value. + * Such a function can then be used in any DQL statement in any place where string + * functions are allowed. + * + * DQL function names are case-insensitive. + * + * @param class-string|callable $className Class name or a callable that returns the function. + * @psalm-param class-string|callable(string):FunctionNode $className + */ + public function addCustomStringFunction(string $name, string|callable $className): void + { + $this->attributes['customStringFunctions'][strtolower($name)] = $className; + } + + /** + * Gets the implementation class name of a registered custom string DQL function. + * + * @psalm-return class-string|callable(string):FunctionNode|null + */ + public function getCustomStringFunction(string $name): string|callable|null + { + $name = strtolower($name); + + return $this->attributes['customStringFunctions'][$name] ?? null; + } + + /** + * Sets a map of custom DQL string functions. + * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added string functions are discarded. + * + * @psalm-param array|callable(string):FunctionNode> $functions The map of custom + * DQL string functions. + */ + public function setCustomStringFunctions(array $functions): void + { + foreach ($functions as $name => $className) { + $this->addCustomStringFunction($name, $className); + } + } + + /** + * Registers a custom DQL function that produces a numeric value. + * Such a function can then be used in any DQL statement in any place where numeric + * functions are allowed. + * + * DQL function names are case-insensitive. + * + * @param class-string|callable $className Class name or a callable that returns the function. + * @psalm-param class-string|callable(string):FunctionNode $className + */ + public function addCustomNumericFunction(string $name, string|callable $className): void + { + $this->attributes['customNumericFunctions'][strtolower($name)] = $className; + } + + /** + * Gets the implementation class name of a registered custom numeric DQL function. + * + * @psalm-return ?class-string|callable(string):FunctionNode + */ + public function getCustomNumericFunction(string $name): string|callable|null + { + $name = strtolower($name); + + return $this->attributes['customNumericFunctions'][$name] ?? null; + } + + /** + * Sets a map of custom DQL numeric functions. + * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added numeric functions are discarded. + * + * @psalm-param array $functions The map of custom + * DQL numeric functions. + */ + public function setCustomNumericFunctions(array $functions): void + { + foreach ($functions as $name => $className) { + $this->addCustomNumericFunction($name, $className); + } + } + + /** + * Registers a custom DQL function that produces a date/time value. + * Such a function can then be used in any DQL statement in any place where date/time + * functions are allowed. + * + * DQL function names are case-insensitive. + * + * @param string|callable $className Class name or a callable that returns the function. + * @psalm-param class-string|callable(string):FunctionNode $className + */ + public function addCustomDatetimeFunction(string $name, string|callable $className): void + { + $this->attributes['customDatetimeFunctions'][strtolower($name)] = $className; + } + + /** + * Gets the implementation class name of a registered custom date/time DQL function. + * + * @psalm-return class-string|callable|null + */ + public function getCustomDatetimeFunction(string $name): string|callable|null + { + $name = strtolower($name); + + return $this->attributes['customDatetimeFunctions'][$name] ?? null; + } + + /** + * Sets a map of custom DQL date/time functions. + * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added date/time functions are discarded. + * + * @param array $functions The map of custom DQL date/time functions. + * @psalm-param array|callable(string):FunctionNode> $functions + */ + public function setCustomDatetimeFunctions(array $functions): void + { + foreach ($functions as $name => $className) { + $this->addCustomDatetimeFunction($name, $className); + } + } + + /** + * Sets a TypedFieldMapper for php typed fields to DBAL types auto-completion. + */ + public function setTypedFieldMapper(TypedFieldMapper|null $typedFieldMapper): void + { + $this->attributes['typedFieldMapper'] = $typedFieldMapper; + } + + /** + * Gets a TypedFieldMapper for php typed fields to DBAL types auto-completion. + */ + public function getTypedFieldMapper(): TypedFieldMapper|null + { + return $this->attributes['typedFieldMapper'] ?? null; + } + + /** + * Sets the custom hydrator modes in one pass. + * + * @param array> $modes An array of ($modeName => $hydrator). + */ + public function setCustomHydrationModes(array $modes): void + { + $this->attributes['customHydrationModes'] = []; + + foreach ($modes as $modeName => $hydrator) { + $this->addCustomHydrationMode($modeName, $hydrator); + } + } + + /** + * Gets the hydrator class for the given hydration mode name. + * + * @psalm-return class-string|null + */ + public function getCustomHydrationMode(string $modeName): string|null + { + return $this->attributes['customHydrationModes'][$modeName] ?? null; + } + + /** + * Adds a custom hydration mode. + * + * @psalm-param class-string $hydrator + */ + public function addCustomHydrationMode(string $modeName, string $hydrator): void + { + $this->attributes['customHydrationModes'][$modeName] = $hydrator; + } + + /** + * Sets a class metadata factory. + * + * @psalm-param class-string $cmfName + */ + public function setClassMetadataFactoryName(string $cmfName): void + { + $this->attributes['classMetadataFactoryName'] = $cmfName; + } + + /** @psalm-return class-string */ + public function getClassMetadataFactoryName(): string + { + if (! isset($this->attributes['classMetadataFactoryName'])) { + $this->attributes['classMetadataFactoryName'] = ClassMetadataFactory::class; + } + + return $this->attributes['classMetadataFactoryName']; + } + + /** + * Adds a filter to the list of possible filters. + * + * @param string $className The class name of the filter. + * @psalm-param class-string $className + */ + public function addFilter(string $name, string $className): void + { + $this->attributes['filters'][$name] = $className; + } + + /** + * Gets the class name for a given filter name. + * + * @return string|null The class name of the filter, or null if it is not + * defined. + * @psalm-return class-string|null + */ + public function getFilterClassName(string $name): string|null + { + return $this->attributes['filters'][$name] ?? null; + } + + /** + * Sets default repository class. + * + * @psalm-param class-string $className + * + * @throws InvalidEntityRepository If $classname is not an ObjectRepository. + */ + public function setDefaultRepositoryClassName(string $className): void + { + if (! class_exists($className) || ! is_a($className, EntityRepository::class, true)) { + throw InvalidEntityRepository::fromClassName($className); + } + + $this->attributes['defaultRepositoryClassName'] = $className; + } + + /** + * Get default repository class. + * + * @psalm-return class-string + */ + public function getDefaultRepositoryClassName(): string + { + return $this->attributes['defaultRepositoryClassName'] ?? EntityRepository::class; + } + + /** + * Sets naming strategy. + */ + public function setNamingStrategy(NamingStrategy $namingStrategy): void + { + $this->attributes['namingStrategy'] = $namingStrategy; + } + + /** + * Gets naming strategy.. + */ + public function getNamingStrategy(): NamingStrategy + { + if (! isset($this->attributes['namingStrategy'])) { + $this->attributes['namingStrategy'] = new DefaultNamingStrategy(); + } + + return $this->attributes['namingStrategy']; + } + + /** + * Sets quote strategy. + */ + public function setQuoteStrategy(QuoteStrategy $quoteStrategy): void + { + $this->attributes['quoteStrategy'] = $quoteStrategy; + } + + /** + * Gets quote strategy. + */ + public function getQuoteStrategy(): QuoteStrategy + { + if (! isset($this->attributes['quoteStrategy'])) { + $this->attributes['quoteStrategy'] = new DefaultQuoteStrategy(); + } + + return $this->attributes['quoteStrategy']; + } + + /** + * Set the entity listener resolver. + */ + public function setEntityListenerResolver(EntityListenerResolver $resolver): void + { + $this->attributes['entityListenerResolver'] = $resolver; + } + + /** + * Get the entity listener resolver. + */ + public function getEntityListenerResolver(): EntityListenerResolver + { + if (! isset($this->attributes['entityListenerResolver'])) { + $this->attributes['entityListenerResolver'] = new DefaultEntityListenerResolver(); + } + + return $this->attributes['entityListenerResolver']; + } + + /** + * Set the entity repository factory. + */ + public function setRepositoryFactory(RepositoryFactory $repositoryFactory): void + { + $this->attributes['repositoryFactory'] = $repositoryFactory; + } + + /** + * Get the entity repository factory. + */ + public function getRepositoryFactory(): RepositoryFactory + { + return $this->attributes['repositoryFactory'] ?? new DefaultRepositoryFactory(); + } + + public function isSecondLevelCacheEnabled(): bool + { + return $this->attributes['isSecondLevelCacheEnabled'] ?? false; + } + + public function setSecondLevelCacheEnabled(bool $flag = true): void + { + $this->attributes['isSecondLevelCacheEnabled'] = $flag; + } + + public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig): void + { + $this->attributes['secondLevelCacheConfiguration'] = $cacheConfig; + } + + public function getSecondLevelCacheConfiguration(): CacheConfiguration|null + { + if (! isset($this->attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) { + $this->attributes['secondLevelCacheConfiguration'] = new CacheConfiguration(); + } + + return $this->attributes['secondLevelCacheConfiguration'] ?? null; + } + + /** + * Returns query hints, which will be applied to every query in application + * + * @psalm-return array + */ + public function getDefaultQueryHints(): array + { + return $this->attributes['defaultQueryHints'] ?? []; + } + + /** + * Sets array of query hints, which will be applied to every query in application + * + * @psalm-param array $defaultQueryHints + */ + public function setDefaultQueryHints(array $defaultQueryHints): void + { + $this->attributes['defaultQueryHints'] = $defaultQueryHints; + } + + /** + * Gets the value of a default query hint. If the hint name is not recognized, FALSE is returned. + * + * @return mixed The value of the hint or FALSE, if the hint name is not recognized. + */ + public function getDefaultQueryHint(string $name): mixed + { + return $this->attributes['defaultQueryHints'][$name] ?? false; + } + + /** + * Sets a default query hint. If the hint name is not recognized, it is silently ignored. + */ + public function setDefaultQueryHint(string $name, mixed $value): void + { + $this->attributes['defaultQueryHints'][$name] = $value; + } + + /** + * Gets a list of entity class names to be ignored by the SchemaTool + * + * @return list + */ + public function getSchemaIgnoreClasses(): array + { + return $this->attributes['schemaIgnoreClasses'] ?? []; + } + + /** + * Sets a list of entity class names to be ignored by the SchemaTool + * + * @param list $schemaIgnoreClasses List of entity class names + */ + public function setSchemaIgnoreClasses(array $schemaIgnoreClasses): void + { + $this->attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses; + } + + /** + * To be deprecated in 3.1.0 + * + * @return true + */ + public function isLazyGhostObjectEnabled(): bool + { + return true; + } + + /** To be deprecated in 3.1.0 */ + public function setLazyGhostObjectEnabled(bool $flag): void + { + if (! $flag) { + throw new LogicException(<<<'EXCEPTION' + The lazy ghost object feature cannot be disabled anymore. + Please remove the call to setLazyGhostObjectEnabled(false). + EXCEPTION); + } + } + + /** To be deprecated in 3.1.0 */ + public function setRejectIdCollisionInIdentityMap(bool $flag): void + { + if (! $flag) { + throw new LogicException(<<<'EXCEPTION' + Rejecting ID collisions in the identity map cannot be disabled anymore. + Please remove the call to setRejectIdCollisionInIdentityMap(false). + EXCEPTION); + } + } + + /** + * To be deprecated in 3.1.0 + * + * @return true + */ + public function isRejectIdCollisionInIdentityMapEnabled(): bool + { + return true; + } + + public function setEagerFetchBatchSize(int $batchSize = 100): void + { + $this->attributes['fetchModeSubselectBatchSize'] = $batchSize; + } + + public function getEagerFetchBatchSize(): int + { + return $this->attributes['fetchModeSubselectBatchSize'] ?? 100; + } +} diff --git a/vendor/doctrine/orm/src/Decorator/EntityManagerDecorator.php b/vendor/doctrine/orm/src/Decorator/EntityManagerDecorator.php new file mode 100644 index 0000000..6f1b041 --- /dev/null +++ b/vendor/doctrine/orm/src/Decorator/EntityManagerDecorator.php @@ -0,0 +1,174 @@ + + */ +abstract class EntityManagerDecorator extends ObjectManagerDecorator implements EntityManagerInterface +{ + public function __construct(EntityManagerInterface $wrapped) + { + $this->wrapped = $wrapped; + } + + public function getRepository(string $className): EntityRepository + { + return $this->wrapped->getRepository($className); + } + + public function getMetadataFactory(): ClassMetadataFactory + { + return $this->wrapped->getMetadataFactory(); + } + + public function getClassMetadata(string $className): ClassMetadata + { + return $this->wrapped->getClassMetadata($className); + } + + public function getConnection(): Connection + { + return $this->wrapped->getConnection(); + } + + public function getExpressionBuilder(): Expr + { + return $this->wrapped->getExpressionBuilder(); + } + + public function beginTransaction(): void + { + $this->wrapped->beginTransaction(); + } + + public function wrapInTransaction(callable $func): mixed + { + return $this->wrapped->wrapInTransaction($func); + } + + public function commit(): void + { + $this->wrapped->commit(); + } + + public function rollback(): void + { + $this->wrapped->rollback(); + } + + public function createQuery(string $dql = ''): Query + { + return $this->wrapped->createQuery($dql); + } + + public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery + { + return $this->wrapped->createNativeQuery($sql, $rsm); + } + + public function createQueryBuilder(): QueryBuilder + { + return $this->wrapped->createQueryBuilder(); + } + + public function getReference(string $entityName, mixed $id): object|null + { + return $this->wrapped->getReference($entityName, $id); + } + + public function close(): void + { + $this->wrapped->close(); + } + + public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void + { + $this->wrapped->lock($entity, $lockMode, $lockVersion); + } + + public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null + { + return $this->wrapped->find($className, $id, $lockMode, $lockVersion); + } + + public function refresh(object $object, LockMode|int|null $lockMode = null): void + { + $this->wrapped->refresh($object, $lockMode); + } + + public function getEventManager(): EventManager + { + return $this->wrapped->getEventManager(); + } + + public function getConfiguration(): Configuration + { + return $this->wrapped->getConfiguration(); + } + + public function isOpen(): bool + { + return $this->wrapped->isOpen(); + } + + public function getUnitOfWork(): UnitOfWork + { + return $this->wrapped->getUnitOfWork(); + } + + public function newHydrator(string|int $hydrationMode): AbstractHydrator + { + return $this->wrapped->newHydrator($hydrationMode); + } + + public function getProxyFactory(): ProxyFactory + { + return $this->wrapped->getProxyFactory(); + } + + public function getFilters(): FilterCollection + { + return $this->wrapped->getFilters(); + } + + public function isFiltersStateClean(): bool + { + return $this->wrapped->isFiltersStateClean(); + } + + public function hasFilters(): bool + { + return $this->wrapped->hasFilters(); + } + + public function getCache(): Cache|null + { + return $this->wrapped->getCache(); + } +} diff --git a/vendor/doctrine/orm/src/EntityManager.php b/vendor/doctrine/orm/src/EntityManager.php new file mode 100644 index 0000000..8045ac2 --- /dev/null +++ b/vendor/doctrine/orm/src/EntityManager.php @@ -0,0 +1,626 @@ + 'pdo_sqlite', 'memory' => true], $config); + * $entityManager = new EntityManager($connection, $config); + * + * For more information see + * {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/configuration.html} + * + * You should never attempt to inherit from the EntityManager: Inheritance + * is not a valid extension point for the EntityManager. Instead you + * should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator} + * and wrap your entity manager in a decorator. + * + * @final + */ +class EntityManager implements EntityManagerInterface +{ + /** + * The metadata factory, used to retrieve the ORM metadata of entity classes. + */ + private ClassMetadataFactory $metadataFactory; + + /** + * The UnitOfWork used to coordinate object-level transactions. + */ + private UnitOfWork $unitOfWork; + + /** + * The event manager that is the central point of the event system. + */ + private EventManager $eventManager; + + /** + * The proxy factory used to create dynamic proxies. + */ + private ProxyFactory $proxyFactory; + + /** + * The repository factory used to create dynamic repositories. + */ + private RepositoryFactory $repositoryFactory; + + /** + * The expression builder instance used to generate query expressions. + */ + private Expr|null $expressionBuilder = null; + + /** + * Whether the EntityManager is closed or not. + */ + private bool $closed = false; + + /** + * Collection of query filters. + */ + private FilterCollection|null $filterCollection = null; + + /** + * The second level cache regions API. + */ + private Cache|null $cache = null; + + /** + * Creates a new EntityManager that operates on the given database connection + * and uses the given Configuration and EventManager implementations. + * + * @param Connection $conn The database connection used by the EntityManager. + */ + public function __construct( + private Connection $conn, + private Configuration $config, + EventManager|null $eventManager = null, + ) { + if (! $config->getMetadataDriverImpl()) { + throw MissingMappingDriverImplementation::create(); + } + + $this->eventManager = $eventManager + ?? (method_exists($conn, 'getEventManager') + ? $conn->getEventManager() + : new EventManager() + ); + + $metadataFactoryClassName = $config->getClassMetadataFactoryName(); + + $this->metadataFactory = new $metadataFactoryClassName(); + $this->metadataFactory->setEntityManager($this); + + $this->configureMetadataCache(); + + $this->repositoryFactory = $config->getRepositoryFactory(); + $this->unitOfWork = new UnitOfWork($this); + $this->proxyFactory = new ProxyFactory( + $this, + $config->getProxyDir(), + $config->getProxyNamespace(), + $config->getAutoGenerateProxyClasses(), + ); + + if ($config->isSecondLevelCacheEnabled()) { + $cacheConfig = $config->getSecondLevelCacheConfiguration(); + $cacheFactory = $cacheConfig->getCacheFactory(); + $this->cache = $cacheFactory->createCache($this); + } + } + + public function getConnection(): Connection + { + return $this->conn; + } + + public function getMetadataFactory(): ClassMetadataFactory + { + return $this->metadataFactory; + } + + public function getExpressionBuilder(): Expr + { + return $this->expressionBuilder ??= new Expr(); + } + + public function beginTransaction(): void + { + $this->conn->beginTransaction(); + } + + public function getCache(): Cache|null + { + return $this->cache; + } + + public function wrapInTransaction(callable $func): mixed + { + $this->conn->beginTransaction(); + + try { + $return = $func($this); + + $this->flush(); + $this->conn->commit(); + + return $return; + } catch (Throwable $e) { + $this->close(); + $this->conn->rollBack(); + + throw $e; + } + } + + public function commit(): void + { + $this->conn->commit(); + } + + public function rollback(): void + { + $this->conn->rollBack(); + } + + /** + * Returns the ORM metadata descriptor for a class. + * + * Internal note: Performance-sensitive method. + * + * {@inheritDoc} + */ + public function getClassMetadata(string $className): Mapping\ClassMetadata + { + return $this->metadataFactory->getMetadataFor($className); + } + + public function createQuery(string $dql = ''): Query + { + $query = new Query($this); + + if (! empty($dql)) { + $query->setDQL($dql); + } + + return $query; + } + + public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery + { + $query = new NativeQuery($this); + + $query->setSQL($sql); + $query->setResultSetMapping($rsm); + + return $query; + } + + public function createQueryBuilder(): QueryBuilder + { + return new QueryBuilder($this); + } + + /** + * Flushes all changes to objects that have been queued up to now to the database. + * This effectively synchronizes the in-memory state of managed objects with the + * database. + * + * If an entity is explicitly passed to this method only this entity and + * the cascade-persist semantics + scheduled inserts/removals are synchronized. + * + * @throws OptimisticLockException If a version check on an entity that + * makes use of optimistic locking fails. + * @throws ORMException + */ + public function flush(): void + { + $this->errorIfClosed(); + $this->unitOfWork->commit(); + } + + /** + * {@inheritDoc} + */ + public function find($className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null + { + $class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\')); + + if ($lockMode !== null) { + $this->checkLockRequirements($lockMode, $class); + } + + if (! is_array($id)) { + if ($class->isIdentifierComposite) { + throw ORMInvalidArgumentException::invalidCompositeIdentifier(); + } + + $id = [$class->identifier[0] => $id]; + } + + foreach ($id as $i => $value) { + if (is_object($value)) { + $className = DefaultProxyClassNameResolver::getClass($value); + if ($this->metadataFactory->hasMetadataFor($className)) { + $id[$i] = $this->unitOfWork->getSingleIdentifierValue($value); + + if ($id[$i] === null) { + throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($className); + } + } + } + } + + $sortedId = []; + + foreach ($class->identifier as $identifier) { + if (! isset($id[$identifier])) { + throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name); + } + + if ($id[$identifier] instanceof BackedEnum) { + $sortedId[$identifier] = $id[$identifier]->value; + } else { + $sortedId[$identifier] = $id[$identifier]; + } + + unset($id[$identifier]); + } + + if ($id) { + throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id)); + } + + $unitOfWork = $this->getUnitOfWork(); + + $entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName); + + // Check identity map first + if ($entity !== false) { + if (! ($entity instanceof $class->name)) { + return null; + } + + switch (true) { + case $lockMode === LockMode::OPTIMISTIC: + $this->lock($entity, $lockMode, $lockVersion); + break; + + case $lockMode === LockMode::NONE: + case $lockMode === LockMode::PESSIMISTIC_READ: + case $lockMode === LockMode::PESSIMISTIC_WRITE: + $persister = $unitOfWork->getEntityPersister($class->name); + $persister->refresh($sortedId, $entity, $lockMode); + break; + } + + return $entity; // Hit! + } + + $persister = $unitOfWork->getEntityPersister($class->name); + + switch (true) { + case $lockMode === LockMode::OPTIMISTIC: + $entity = $persister->load($sortedId); + + if ($entity !== null) { + $unitOfWork->lock($entity, $lockMode, $lockVersion); + } + + return $entity; + + case $lockMode === LockMode::PESSIMISTIC_READ: + case $lockMode === LockMode::PESSIMISTIC_WRITE: + return $persister->load($sortedId, null, null, [], $lockMode); + + default: + return $persister->loadById($sortedId); + } + } + + public function getReference(string $entityName, mixed $id): object|null + { + $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); + + if (! is_array($id)) { + $id = [$class->identifier[0] => $id]; + } + + $sortedId = []; + + foreach ($class->identifier as $identifier) { + if (! isset($id[$identifier])) { + throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name); + } + + $sortedId[$identifier] = $id[$identifier]; + unset($id[$identifier]); + } + + if ($id) { + throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id)); + } + + $entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName); + + // Check identity map first, if its already in there just return it. + if ($entity !== false) { + return $entity instanceof $class->name ? $entity : null; + } + + if ($class->subClasses) { + return $this->find($entityName, $sortedId); + } + + $entity = $this->proxyFactory->getProxy($class->name, $sortedId); + + $this->unitOfWork->registerManaged($entity, $sortedId, []); + + return $entity; + } + + /** + * Clears the EntityManager. All entities that are currently managed + * by this EntityManager become detached. + */ + public function clear(): void + { + $this->unitOfWork->clear(); + } + + public function close(): void + { + $this->clear(); + + $this->closed = true; + } + + /** + * Tells the EntityManager to make an instance managed and persistent. + * + * The entity will be entered into the database at or before transaction + * commit or as a result of the flush operation. + * + * NOTE: The persist operation always considers entities that are not yet known to + * this EntityManager as NEW. Do not pass detached entities to the persist operation. + * + * @throws ORMInvalidArgumentException + * @throws ORMException + */ + public function persist(object $object): void + { + $this->errorIfClosed(); + + $this->unitOfWork->persist($object); + } + + /** + * Removes an entity instance. + * + * A removed entity will be removed from the database at or before transaction commit + * or as a result of the flush operation. + * + * @throws ORMInvalidArgumentException + * @throws ORMException + */ + public function remove(object $object): void + { + $this->errorIfClosed(); + + $this->unitOfWork->remove($object); + } + + public function refresh(object $object, LockMode|int|null $lockMode = null): void + { + $this->errorIfClosed(); + + $this->unitOfWork->refresh($object, $lockMode); + } + + /** + * Detaches an entity from the EntityManager, causing a managed entity to + * become detached. Unflushed changes made to the entity if any + * (including removal of the entity), will not be synchronized to the database. + * Entities which previously referenced the detached entity will continue to + * reference it. + * + * @throws ORMInvalidArgumentException + */ + public function detach(object $object): void + { + $this->unitOfWork->detach($object); + } + + public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void + { + $this->unitOfWork->lock($entity, $lockMode, $lockVersion); + } + + /** + * Gets the repository for an entity class. + * + * @psalm-param class-string $className + * + * @psalm-return EntityRepository + * + * @template T of object + */ + public function getRepository(string $className): EntityRepository + { + return $this->repositoryFactory->getRepository($this, $className); + } + + /** + * Determines whether an entity instance is managed in this EntityManager. + * + * @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise. + */ + public function contains(object $object): bool + { + return $this->unitOfWork->isScheduledForInsert($object) + || $this->unitOfWork->isInIdentityMap($object) + && ! $this->unitOfWork->isScheduledForDelete($object); + } + + public function getEventManager(): EventManager + { + return $this->eventManager; + } + + public function getConfiguration(): Configuration + { + return $this->config; + } + + /** + * Throws an exception if the EntityManager is closed or currently not active. + * + * @throws EntityManagerClosed If the EntityManager is closed. + */ + private function errorIfClosed(): void + { + if ($this->closed) { + throw EntityManagerClosed::create(); + } + } + + public function isOpen(): bool + { + return ! $this->closed; + } + + public function getUnitOfWork(): UnitOfWork + { + return $this->unitOfWork; + } + + public function newHydrator(string|int $hydrationMode): AbstractHydrator + { + return match ($hydrationMode) { + Query::HYDRATE_OBJECT => new Internal\Hydration\ObjectHydrator($this), + Query::HYDRATE_ARRAY => new Internal\Hydration\ArrayHydrator($this), + Query::HYDRATE_SCALAR => new Internal\Hydration\ScalarHydrator($this), + Query::HYDRATE_SINGLE_SCALAR => new Internal\Hydration\SingleScalarHydrator($this), + Query::HYDRATE_SIMPLEOBJECT => new Internal\Hydration\SimpleObjectHydrator($this), + Query::HYDRATE_SCALAR_COLUMN => new Internal\Hydration\ScalarColumnHydrator($this), + default => $this->createCustomHydrator((string) $hydrationMode), + }; + } + + public function getProxyFactory(): ProxyFactory + { + return $this->proxyFactory; + } + + public function initializeObject(object $obj): void + { + $this->unitOfWork->initializeObject($obj); + } + + /** + * {@inheritDoc} + */ + public function isUninitializedObject($obj): bool + { + return $this->unitOfWork->isUninitializedObject($obj); + } + + public function getFilters(): FilterCollection + { + return $this->filterCollection ??= new FilterCollection($this); + } + + public function isFiltersStateClean(): bool + { + return $this->filterCollection === null || $this->filterCollection->isClean(); + } + + public function hasFilters(): bool + { + return $this->filterCollection !== null; + } + + /** + * @psalm-param LockMode::* $lockMode + * + * @throws OptimisticLockException + * @throws TransactionRequiredException + */ + private function checkLockRequirements(LockMode|int $lockMode, ClassMetadata $class): void + { + switch ($lockMode) { + case LockMode::OPTIMISTIC: + if (! $class->isVersioned) { + throw OptimisticLockException::notVersioned($class->name); + } + + break; + case LockMode::PESSIMISTIC_READ: + case LockMode::PESSIMISTIC_WRITE: + if (! $this->getConnection()->isTransactionActive()) { + throw TransactionRequiredException::transactionRequired(); + } + } + } + + private function configureMetadataCache(): void + { + $metadataCache = $this->config->getMetadataCache(); + if (! $metadataCache) { + return; + } + + $this->metadataFactory->setCache($metadataCache); + } + + private function createCustomHydrator(string $hydrationMode): AbstractHydrator + { + $class = $this->config->getCustomHydrationMode($hydrationMode); + + if ($class !== null) { + return new $class($this); + } + + throw InvalidHydrationMode::fromMode($hydrationMode); + } +} diff --git a/vendor/doctrine/orm/src/EntityManagerInterface.php b/vendor/doctrine/orm/src/EntityManagerInterface.php new file mode 100644 index 0000000..cf3102b --- /dev/null +++ b/vendor/doctrine/orm/src/EntityManagerInterface.php @@ -0,0 +1,242 @@ + $className + * + * @psalm-return EntityRepository + * + * @template T of object + */ + public function getRepository(string $className): EntityRepository; + + /** + * Returns the cache API for managing the second level cache regions or NULL if the cache is not enabled. + */ + public function getCache(): Cache|null; + + /** + * Gets the database connection object used by the EntityManager. + */ + public function getConnection(): Connection; + + public function getMetadataFactory(): ClassMetadataFactory; + + /** + * Gets an ExpressionBuilder used for object-oriented construction of query expressions. + * + * Example: + * + * + * $qb = $em->createQueryBuilder(); + * $expr = $em->getExpressionBuilder(); + * $qb->select('u')->from('User', 'u') + * ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2))); + * + */ + public function getExpressionBuilder(): Expr; + + /** + * Starts a transaction on the underlying database connection. + */ + public function beginTransaction(): void; + + /** + * Executes a function in a transaction. + * + * The function gets passed this EntityManager instance as an (optional) parameter. + * + * {@link flush} is invoked prior to transaction commit. + * + * If an exception occurs during execution of the function or flushing or transaction commit, + * the transaction is rolled back, the EntityManager closed and the exception re-thrown. + * + * @psalm-param callable(self): T $func The function to execute transactionally. + * + * @return mixed The value returned from the closure. + * @psalm-return T + * + * @template T + */ + public function wrapInTransaction(callable $func): mixed; + + /** + * Commits a transaction on the underlying database connection. + */ + public function commit(): void; + + /** + * Performs a rollback on the underlying database connection. + */ + public function rollback(): void; + + /** + * Creates a new Query object. + * + * @param string $dql The DQL string. + */ + public function createQuery(string $dql = ''): Query; + + /** + * Creates a native SQL query. + */ + public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery; + + /** + * Create a QueryBuilder instance + */ + public function createQueryBuilder(): QueryBuilder; + + /** + * Finds an Entity by its identifier. + * + * @param string $className The class name of the entity to find. + * @param mixed $id The identity of the entity to find. + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * during the search. + * @param int|null $lockVersion The version of the entity to find when using + * optimistic locking. + * @psalm-param class-string $className + * @psalm-param LockMode::*|null $lockMode + * + * @return object|null The entity instance or NULL if the entity can not be found. + * @psalm-return T|null + * + * @throws OptimisticLockException + * @throws ORMInvalidArgumentException + * @throws TransactionRequiredException + * @throws ORMException + * + * @template T of object + */ + public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null; + + /** + * Refreshes the persistent state of an object from the database, + * overriding any local changes that have not yet been persisted. + * + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * during the search. + * @psalm-param LockMode::*|null $lockMode + * + * @throws ORMInvalidArgumentException + * @throws ORMException + * @throws TransactionRequiredException + */ + public function refresh(object $object, LockMode|int|null $lockMode = null): void; + + /** + * Gets a reference to the entity identified by the given type and identifier + * without actually loading it, if the entity is not yet loaded. + * + * @param string $entityName The name of the entity type. + * @param mixed $id The entity identifier. + * @psalm-param class-string $entityName + * + * @psalm-return T|null + * + * @throws ORMException + * + * @template T of object + */ + public function getReference(string $entityName, mixed $id): object|null; + + /** + * Closes the EntityManager. All entities that are currently managed + * by this EntityManager become detached. The EntityManager may no longer + * be used after it is closed. + */ + public function close(): void; + + /** + * Acquire a lock on the given entity. + * + * @psalm-param LockMode::* $lockMode + * + * @throws OptimisticLockException + * @throws PessimisticLockException + */ + public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void; + + /** + * Gets the EventManager used by the EntityManager. + */ + public function getEventManager(): EventManager; + + /** + * Gets the Configuration used by the EntityManager. + */ + public function getConfiguration(): Configuration; + + /** + * Check if the Entity manager is open or closed. + */ + public function isOpen(): bool; + + /** + * Gets the UnitOfWork used by the EntityManager to coordinate operations. + */ + public function getUnitOfWork(): UnitOfWork; + + /** + * Create a new instance for the given hydration mode. + * + * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode + * + * @throws ORMException + */ + public function newHydrator(string|int $hydrationMode): AbstractHydrator; + + /** + * Gets the proxy factory used by the EntityManager to create entity proxies. + */ + public function getProxyFactory(): ProxyFactory; + + /** + * Gets the enabled filters. + */ + public function getFilters(): FilterCollection; + + /** + * Checks whether the state of the filter collection is clean. + */ + public function isFiltersStateClean(): bool; + + /** + * Checks whether the Entity Manager has filters. + */ + public function hasFilters(): bool; + + /** + * {@inheritDoc} + * + * @psalm-param string|class-string $className + * + * @psalm-return ($className is class-string ? Mapping\ClassMetadata : Mapping\ClassMetadata) + * + * @psalm-template T of object + */ + public function getClassMetadata(string $className): Mapping\ClassMetadata; +} diff --git a/vendor/doctrine/orm/src/EntityNotFoundException.php b/vendor/doctrine/orm/src/EntityNotFoundException.php new file mode 100644 index 0000000..142dc8a --- /dev/null +++ b/vendor/doctrine/orm/src/EntityNotFoundException.php @@ -0,0 +1,46 @@ + $value) { + $ids[] = $key . '(' . $value . ')'; + } + + return new self( + 'Entity of type \'' . $className . '\'' . ($ids ? ' for IDs ' . implode(', ', $ids) : '') . ' was not found', + ); + } + + /** + * Instance for which no identifier can be found + */ + public static function noIdentifierFound(string $className): self + { + return new self(sprintf( + 'Unable to find "%s" entity identifier associated with the UnitOfWork', + $className, + )); + } +} diff --git a/vendor/doctrine/orm/src/EntityRepository.php b/vendor/doctrine/orm/src/EntityRepository.php new file mode 100644 index 0000000..a53c528 --- /dev/null +++ b/vendor/doctrine/orm/src/EntityRepository.php @@ -0,0 +1,236 @@ + + * @template-implements ObjectRepository + */ +class EntityRepository implements ObjectRepository, Selectable +{ + /** @psalm-var class-string */ + private readonly string $entityName; + private static Inflector|null $inflector = null; + + /** @psalm-param ClassMetadata $class */ + public function __construct( + private readonly EntityManagerInterface $em, + private readonly ClassMetadata $class, + ) { + $this->entityName = $class->name; + } + + /** + * Creates a new QueryBuilder instance that is prepopulated for this entity name. + */ + public function createQueryBuilder(string $alias, string|null $indexBy = null): QueryBuilder + { + return $this->em->createQueryBuilder() + ->select($alias) + ->from($this->entityName, $alias, $indexBy); + } + + /** + * Creates a new result set mapping builder for this entity. + * + * The column naming strategy is "INCREMENT". + */ + public function createResultSetMappingBuilder(string $alias): ResultSetMappingBuilder + { + $rsm = new ResultSetMappingBuilder($this->em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT); + $rsm->addRootEntityFromClassMetadata($this->entityName, $alias); + + return $rsm; + } + + /** + * Finds an entity by its primary key / identifier. + * + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * during the search. + * @psalm-param LockMode::*|null $lockMode + * + * @return object|null The entity instance or NULL if the entity can not be found. + * @psalm-return ?T + */ + public function find(mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null + { + return $this->em->find($this->entityName, $id, $lockMode, $lockVersion); + } + + /** + * Finds all entities in the repository. + * + * @psalm-return list The entities. + */ + public function findAll(): array + { + return $this->findBy([]); + } + + /** + * Finds entities by a set of criteria. + * + * {@inheritDoc} + * + * @psalm-return list + */ + public function findBy(array $criteria, array|null $orderBy = null, int|null $limit = null, int|null $offset = null): array + { + $persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName); + + return $persister->loadAll($criteria, $orderBy, $limit, $offset); + } + + /** + * Finds a single entity by a set of criteria. + * + * @psalm-param array $criteria + * @psalm-param array|null $orderBy + * + * @psalm-return T|null + */ + public function findOneBy(array $criteria, array|null $orderBy = null): object|null + { + $persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName); + + return $persister->load($criteria, null, null, [], null, 1, $orderBy); + } + + /** + * Counts entities by a set of criteria. + * + * @psalm-param array $criteria + * + * @return int The cardinality of the objects that match the given criteria. + * + * @todo Add this method to `ObjectRepository` interface in the next major release + */ + public function count(array $criteria = []): int + { + return $this->em->getUnitOfWork()->getEntityPersister($this->entityName)->count($criteria); + } + + /** + * Adds support for magic method calls. + * + * @param mixed[] $arguments + * @psalm-param list $arguments + * + * @throws BadMethodCallException If the method called is invalid. + */ + public function __call(string $method, array $arguments): mixed + { + if (str_starts_with($method, 'findBy')) { + return $this->resolveMagicCall('findBy', substr($method, 6), $arguments); + } + + if (str_starts_with($method, 'findOneBy')) { + return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments); + } + + if (str_starts_with($method, 'countBy')) { + return $this->resolveMagicCall('count', substr($method, 7), $arguments); + } + + throw new BadMethodCallException(sprintf( + 'Undefined method "%s". The method name must start with ' . + 'either findBy, findOneBy or countBy!', + $method, + )); + } + + /** @psalm-return class-string */ + protected function getEntityName(): string + { + return $this->entityName; + } + + public function getClassName(): string + { + return $this->getEntityName(); + } + + protected function getEntityManager(): EntityManagerInterface + { + return $this->em; + } + + /** @psalm-return ClassMetadata */ + protected function getClassMetadata(): ClassMetadata + { + return $this->class; + } + + /** + * Select all elements from a selectable that match the expression and + * return a new collection containing these elements. + * + * @psalm-return AbstractLazyCollection&Selectable + */ + public function matching(Criteria $criteria): AbstractLazyCollection&Selectable + { + $persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName); + + return new LazyCriteriaCollection($persister, $criteria); + } + + /** + * Resolves a magic method call to the proper existent method at `EntityRepository`. + * + * @param string $method The method to call + * @param string $by The property name used as condition + * @psalm-param list $arguments The arguments to pass at method call + * + * @throws InvalidMagicMethodCall If the method called is invalid or the + * requested field/association does not exist. + */ + private function resolveMagicCall(string $method, string $by, array $arguments): mixed + { + if (! $arguments) { + throw InvalidMagicMethodCall::onMissingParameter($method . $by); + } + + self::$inflector ??= InflectorFactory::create()->build(); + + $fieldName = lcfirst(self::$inflector->classify($by)); + + if (! ($this->class->hasField($fieldName) || $this->class->hasAssociation($fieldName))) { + throw InvalidMagicMethodCall::becauseFieldNotFoundIn( + $this->entityName, + $fieldName, + $method . $by, + ); + } + + return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1)); + } +} diff --git a/vendor/doctrine/orm/src/Event/ListenersInvoker.php b/vendor/doctrine/orm/src/Event/ListenersInvoker.php new file mode 100644 index 0000000..c0c327e --- /dev/null +++ b/vendor/doctrine/orm/src/Event/ListenersInvoker.php @@ -0,0 +1,98 @@ +eventManager = $em->getEventManager(); + $this->resolver = $em->getConfiguration()->getEntityListenerResolver(); + } + + /** + * Get the subscribed event systems + * + * @param ClassMetadata $metadata The entity metadata. + * @param string $eventName The entity lifecycle event. + * + * @psalm-return int-mask-of Bitmask of subscribed event systems. + */ + public function getSubscribedSystems(ClassMetadata $metadata, string $eventName): int + { + $invoke = self::INVOKE_NONE; + + if (isset($metadata->lifecycleCallbacks[$eventName])) { + $invoke |= self::INVOKE_CALLBACKS; + } + + if (isset($metadata->entityListeners[$eventName])) { + $invoke |= self::INVOKE_LISTENERS; + } + + if ($this->eventManager->hasListeners($eventName)) { + $invoke |= self::INVOKE_MANAGER; + } + + return $invoke; + } + + /** + * Dispatches the lifecycle event of the given entity. + * + * @param ClassMetadata $metadata The entity metadata. + * @param string $eventName The entity lifecycle event. + * @param object $entity The Entity on which the event occurred. + * @param EventArgs $event The Event args. + * @psalm-param int-mask-of $invoke Bitmask to invoke listeners. + */ + public function invoke( + ClassMetadata $metadata, + string $eventName, + object $entity, + EventArgs $event, + int $invoke, + ): void { + if ($invoke & self::INVOKE_CALLBACKS) { + foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) { + $entity->$callback($event); + } + } + + if ($invoke & self::INVOKE_LISTENERS) { + foreach ($metadata->entityListeners[$eventName] as $listener) { + $class = $listener['class']; + $method = $listener['method']; + $instance = $this->resolver->resolve($class); + + $instance->$method($entity, $event); + } + } + + if ($invoke & self::INVOKE_MANAGER) { + $this->eventManager->dispatchEvent($eventName, $event); + } + } +} diff --git a/vendor/doctrine/orm/src/Event/LoadClassMetadataEventArgs.php b/vendor/doctrine/orm/src/Event/LoadClassMetadataEventArgs.php new file mode 100644 index 0000000..b450616 --- /dev/null +++ b/vendor/doctrine/orm/src/Event/LoadClassMetadataEventArgs.php @@ -0,0 +1,25 @@ +, EntityManagerInterface> + */ +class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs +{ + /** + * Retrieve associated EntityManager. + */ + public function getEntityManager(): EntityManagerInterface + { + return $this->getObjectManager(); + } +} diff --git a/vendor/doctrine/orm/src/Event/OnClassMetadataNotFoundEventArgs.php b/vendor/doctrine/orm/src/Event/OnClassMetadataNotFoundEventArgs.php new file mode 100644 index 0000000..762c083 --- /dev/null +++ b/vendor/doctrine/orm/src/Event/OnClassMetadataNotFoundEventArgs.php @@ -0,0 +1,49 @@ + + */ +class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs +{ + private ClassMetadata|null $foundMetadata = null; + + /** @param EntityManagerInterface $objectManager */ + public function __construct( + private readonly string $className, + ObjectManager $objectManager, + ) { + parent::__construct($objectManager); + } + + public function setFoundMetadata(ClassMetadata|null $classMetadata): void + { + $this->foundMetadata = $classMetadata; + } + + public function getFoundMetadata(): ClassMetadata|null + { + return $this->foundMetadata; + } + + /** + * Retrieve class name for which a failed metadata fetch attempt was executed + */ + public function getClassName(): string + { + return $this->className; + } +} diff --git a/vendor/doctrine/orm/src/Event/OnClearEventArgs.php b/vendor/doctrine/orm/src/Event/OnClearEventArgs.php new file mode 100644 index 0000000..29a42f2 --- /dev/null +++ b/vendor/doctrine/orm/src/Event/OnClearEventArgs.php @@ -0,0 +1,19 @@ + + */ +class OnClearEventArgs extends BaseOnClearEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/OnFlushEventArgs.php b/vendor/doctrine/orm/src/Event/OnFlushEventArgs.php new file mode 100644 index 0000000..b0594ca --- /dev/null +++ b/vendor/doctrine/orm/src/Event/OnFlushEventArgs.php @@ -0,0 +1,19 @@ + + */ +class OnFlushEventArgs extends ManagerEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PostFlushEventArgs.php b/vendor/doctrine/orm/src/Event/PostFlushEventArgs.php new file mode 100644 index 0000000..ca41ba8 --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PostFlushEventArgs.php @@ -0,0 +1,19 @@ + + */ +class PostFlushEventArgs extends ManagerEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PostLoadEventArgs.php b/vendor/doctrine/orm/src/Event/PostLoadEventArgs.php new file mode 100644 index 0000000..8344e68 --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PostLoadEventArgs.php @@ -0,0 +1,13 @@ + */ +final class PostLoadEventArgs extends LifecycleEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PostPersistEventArgs.php b/vendor/doctrine/orm/src/Event/PostPersistEventArgs.php new file mode 100644 index 0000000..926ac1c --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PostPersistEventArgs.php @@ -0,0 +1,13 @@ + */ +final class PostPersistEventArgs extends LifecycleEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PostRemoveEventArgs.php b/vendor/doctrine/orm/src/Event/PostRemoveEventArgs.php new file mode 100644 index 0000000..8bf857e --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PostRemoveEventArgs.php @@ -0,0 +1,13 @@ + */ +final class PostRemoveEventArgs extends LifecycleEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PostUpdateEventArgs.php b/vendor/doctrine/orm/src/Event/PostUpdateEventArgs.php new file mode 100644 index 0000000..c9ff004 --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PostUpdateEventArgs.php @@ -0,0 +1,13 @@ + */ +final class PostUpdateEventArgs extends LifecycleEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PreFlushEventArgs.php b/vendor/doctrine/orm/src/Event/PreFlushEventArgs.php new file mode 100644 index 0000000..671535c --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PreFlushEventArgs.php @@ -0,0 +1,19 @@ + + */ +class PreFlushEventArgs extends ManagerEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PrePersistEventArgs.php b/vendor/doctrine/orm/src/Event/PrePersistEventArgs.php new file mode 100644 index 0000000..e70c3cf --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PrePersistEventArgs.php @@ -0,0 +1,13 @@ + */ +final class PrePersistEventArgs extends LifecycleEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PreRemoveEventArgs.php b/vendor/doctrine/orm/src/Event/PreRemoveEventArgs.php new file mode 100644 index 0000000..3af0d02 --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PreRemoveEventArgs.php @@ -0,0 +1,13 @@ + */ +final class PreRemoveEventArgs extends LifecycleEventArgs +{ +} diff --git a/vendor/doctrine/orm/src/Event/PreUpdateEventArgs.php b/vendor/doctrine/orm/src/Event/PreUpdateEventArgs.php new file mode 100644 index 0000000..090487b --- /dev/null +++ b/vendor/doctrine/orm/src/Event/PreUpdateEventArgs.php @@ -0,0 +1,100 @@ + + */ +class PreUpdateEventArgs extends LifecycleEventArgs +{ + /** @var array */ + private array $entityChangeSet; + + /** + * @param mixed[][] $changeSet + * @psalm-param array $changeSet + */ + public function __construct(object $entity, EntityManagerInterface $em, array &$changeSet) + { + parent::__construct($entity, $em); + + $this->entityChangeSet = &$changeSet; + } + + /** + * Retrieves entity changeset. + * + * @return mixed[][] + * @psalm-return array + */ + public function getEntityChangeSet(): array + { + return $this->entityChangeSet; + } + + /** + * Checks if field has a changeset. + */ + public function hasChangedField(string $field): bool + { + return isset($this->entityChangeSet[$field]); + } + + /** + * Gets the old value of the changeset of the changed field. + */ + public function getOldValue(string $field): mixed + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][0]; + } + + /** + * Gets the new value of the changeset of the changed field. + */ + public function getNewValue(string $field): mixed + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][1]; + } + + /** + * Sets the new value of this field. + */ + public function setNewValue(string $field, mixed $value): void + { + $this->assertValidField($field); + + $this->entityChangeSet[$field][1] = $value; + } + + /** + * Asserts the field exists in changeset. + * + * @throws InvalidArgumentException + */ + private function assertValidField(string $field): void + { + if (! isset($this->entityChangeSet[$field])) { + throw new InvalidArgumentException(sprintf( + 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', + $field, + get_debug_type($this->getObject()), + )); + } + } +} diff --git a/vendor/doctrine/orm/src/Events.php b/vendor/doctrine/orm/src/Events.php new file mode 100644 index 0000000..517917c --- /dev/null +++ b/vendor/doctrine/orm/src/Events.php @@ -0,0 +1,125 @@ +getClassMetadata($entity::class); + $idFields = $class->getIdentifierFieldNames(); + $identifier = []; + + foreach ($idFields as $idField) { + $value = $class->getFieldValue($entity, $idField); + + if (! isset($value)) { + throw EntityMissingAssignedId::forField($entity, $idField); + } + + if (isset($class->associationMappings[$idField])) { + // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. + $value = $em->getUnitOfWork()->getSingleIdentifierValue($value); + } + + $identifier[$idField] = $value; + } + + return $identifier; + } +} diff --git a/vendor/doctrine/orm/src/Id/BigIntegerIdentityGenerator.php b/vendor/doctrine/orm/src/Id/BigIntegerIdentityGenerator.php new file mode 100644 index 0000000..762a7cb --- /dev/null +++ b/vendor/doctrine/orm/src/Id/BigIntegerIdentityGenerator.php @@ -0,0 +1,25 @@ +getConnection()->lastInsertId(); + } + + public function isPostInsertGenerator(): bool + { + return true; + } +} diff --git a/vendor/doctrine/orm/src/Id/IdentityGenerator.php b/vendor/doctrine/orm/src/Id/IdentityGenerator.php new file mode 100644 index 0000000..4610f66 --- /dev/null +++ b/vendor/doctrine/orm/src/Id/IdentityGenerator.php @@ -0,0 +1,25 @@ +getConnection()->lastInsertId(); + } + + public function isPostInsertGenerator(): bool + { + return true; + } +} diff --git a/vendor/doctrine/orm/src/Id/SequenceGenerator.php b/vendor/doctrine/orm/src/Id/SequenceGenerator.php new file mode 100644 index 0000000..659bb58 --- /dev/null +++ b/vendor/doctrine/orm/src/Id/SequenceGenerator.php @@ -0,0 +1,112 @@ +maxValue === null || $this->nextValue === $this->maxValue) { + // Allocate new values + $connection = $em->getConnection(); + $sql = $connection->getDatabasePlatform()->getSequenceNextValSQL($this->sequenceName); + + if ($connection instanceof PrimaryReadReplicaConnection) { + $connection->ensureConnectedToPrimary(); + } + + $this->nextValue = (int) $connection->fetchOne($sql); + $this->maxValue = $this->nextValue + $this->allocationSize; + } + + return $this->nextValue++; + } + + /** + * Gets the maximum value of the currently allocated bag of values. + */ + public function getCurrentMaxValue(): int|null + { + return $this->maxValue; + } + + /** + * Gets the next value that will be returned by generate(). + */ + public function getNextValue(): int + { + return $this->nextValue; + } + + /** @deprecated without replacement. */ + final public function serialize(): string + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11468', + '%s() is deprecated, use __serialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.', + __METHOD__, + self::class, + ); + + return serialize($this->__serialize()); + } + + /** @return array */ + public function __serialize(): array + { + return [ + 'allocationSize' => $this->allocationSize, + 'sequenceName' => $this->sequenceName, + ]; + } + + /** @deprecated without replacement. */ + final public function unserialize(string $serialized): void + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11468', + '%s() is deprecated, use __unserialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.', + __METHOD__, + self::class, + ); + + $this->__unserialize(unserialize($serialized)); + } + + /** @param array $data */ + public function __unserialize(array $data): void + { + $this->sequenceName = $data['sequenceName']; + $this->allocationSize = $data['allocationSize']; + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/AbstractHydrator.php b/vendor/doctrine/orm/src/Internal/Hydration/AbstractHydrator.php new file mode 100644 index 0000000..d8bffe4 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/AbstractHydrator.php @@ -0,0 +1,556 @@ +> + */ + protected array $metadataCache = []; + + /** + * The cache used during row-by-row hydration. + * + * @var array + */ + protected array $cache = []; + + /** + * The statement that provides the data to hydrate. + */ + protected Result|null $stmt = null; + + /** + * The query hints. + * + * @var array + */ + protected array $hints = []; + + /** + * Initializes a new instance of a class derived from AbstractHydrator. + */ + public function __construct(protected EntityManagerInterface $em) + { + $this->platform = $em->getConnection()->getDatabasePlatform(); + $this->uow = $em->getUnitOfWork(); + } + + /** + * Initiates a row-by-row hydration. + * + * @psalm-param array $hints + * + * @return Generator + * + * @final + */ + final public function toIterable(Result $stmt, ResultSetMapping $resultSetMapping, array $hints = []): Generator + { + $this->stmt = $stmt; + $this->rsm = $resultSetMapping; + $this->hints = $hints; + + $evm = $this->em->getEventManager(); + + $evm->addEventListener([Events::onClear], $this); + + $this->prepare(); + + try { + while (true) { + $row = $this->statement()->fetchAssociative(); + + if ($row === false) { + break; + } + + $result = []; + + $this->hydrateRowData($row, $result); + + $this->cleanupAfterRowIteration(); + if (count($result) === 1) { + if (count($resultSetMapping->indexByMap) === 0) { + yield end($result); + } else { + yield from $result; + } + } else { + yield $result; + } + } + } finally { + $this->cleanup(); + } + } + + final protected function statement(): Result + { + if ($this->stmt === null) { + throw new LogicException('Uninitialized _stmt property'); + } + + return $this->stmt; + } + + final protected function resultSetMapping(): ResultSetMapping + { + if ($this->rsm === null) { + throw new LogicException('Uninitialized _rsm property'); + } + + return $this->rsm; + } + + /** + * Hydrates all rows returned by the passed statement instance at once. + * + * @psalm-param array $hints + */ + public function hydrateAll(Result $stmt, ResultSetMapping $resultSetMapping, array $hints = []): mixed + { + $this->stmt = $stmt; + $this->rsm = $resultSetMapping; + $this->hints = $hints; + + $this->em->getEventManager()->addEventListener([Events::onClear], $this); + $this->prepare(); + + try { + $result = $this->hydrateAllData(); + } finally { + $this->cleanup(); + } + + return $result; + } + + /** + * When executed in a hydrate() loop we have to clear internal state to + * decrease memory consumption. + */ + public function onClear(mixed $eventArgs): void + { + } + + /** + * Executes one-time preparation tasks, once each time hydration is started + * through {@link hydrateAll} or {@link toIterable()}. + */ + protected function prepare(): void + { + } + + /** + * Executes one-time cleanup tasks at the end of a hydration that was initiated + * through {@link hydrateAll} or {@link toIterable()}. + */ + protected function cleanup(): void + { + $this->statement()->free(); + + $this->stmt = null; + $this->rsm = null; + $this->cache = []; + $this->metadataCache = []; + + $this + ->em + ->getEventManager() + ->removeEventListener([Events::onClear], $this); + } + + protected function cleanupAfterRowIteration(): void + { + } + + /** + * Hydrates a single row from the current statement instance. + * + * Template method. + * + * @param mixed[] $row The row data. + * @param mixed[] $result The result to fill. + * + * @throws HydrationException + */ + protected function hydrateRowData(array $row, array &$result): void + { + throw new HydrationException('hydrateRowData() not implemented by this hydrator.'); + } + + /** + * Hydrates all rows from the current statement instance at once. + */ + abstract protected function hydrateAllData(): mixed; + + /** + * Processes a row of the result set. + * + * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). + * Puts the elements of a result row into a new array, grouped by the dql alias + * they belong to. The column names in the result set are mapped to their + * field names during this procedure as well as any necessary conversions on + * the values applied. Scalar values are kept in a specific key 'scalars'. + * + * @param mixed[] $data SQL Result Row. + * @psalm-param array $id Dql-Alias => ID-Hash. + * @psalm-param array $nonemptyComponents Does this DQL-Alias has at least one non NULL value? + * + * @return array> An array with all the fields + * (name => value) of the data + * row, grouped by their + * component alias. + * @psalm-return array{ + * data: array, + * newObjects?: array, + * scalars?: array + * } + */ + protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents): array + { + $rowData = ['data' => []]; + + foreach ($data as $key => $value) { + $cacheKeyInfo = $this->hydrateColumnInfo($key); + if ($cacheKeyInfo === null) { + continue; + } + + $fieldName = $cacheKeyInfo['fieldName']; + + switch (true) { + case isset($cacheKeyInfo['isNewObjectParameter']): + $argIndex = $cacheKeyInfo['argIndex']; + $objIndex = $cacheKeyInfo['objIndex']; + $type = $cacheKeyInfo['type']; + $value = $type->convertToPHPValue($value, $this->platform); + + if ($value !== null && isset($cacheKeyInfo['enumType'])) { + $value = $this->buildEnum($value, $cacheKeyInfo['enumType']); + } + + $rowData['newObjects'][$objIndex]['class'] = $cacheKeyInfo['class']; + $rowData['newObjects'][$objIndex]['args'][$argIndex] = $value; + break; + + case isset($cacheKeyInfo['isScalar']): + $type = $cacheKeyInfo['type']; + $value = $type->convertToPHPValue($value, $this->platform); + + if ($value !== null && isset($cacheKeyInfo['enumType'])) { + $value = $this->buildEnum($value, $cacheKeyInfo['enumType']); + } + + $rowData['scalars'][$fieldName] = $value; + + break; + + //case (isset($cacheKeyInfo['isMetaColumn'])): + default: + $dqlAlias = $cacheKeyInfo['dqlAlias']; + $type = $cacheKeyInfo['type']; + + // If there are field name collisions in the child class, then we need + // to only hydrate if we are looking at the correct discriminator value + if ( + isset($cacheKeyInfo['discriminatorColumn'], $data[$cacheKeyInfo['discriminatorColumn']]) + && ! in_array((string) $data[$cacheKeyInfo['discriminatorColumn']], $cacheKeyInfo['discriminatorValues'], true) + ) { + break; + } + + // in an inheritance hierarchy the same field could be defined several times. + // We overwrite this value so long we don't have a non-null value, that value we keep. + // Per definition it cannot be that a field is defined several times and has several values. + if (isset($rowData['data'][$dqlAlias][$fieldName])) { + break; + } + + $rowData['data'][$dqlAlias][$fieldName] = $type + ? $type->convertToPHPValue($value, $this->platform) + : $value; + + if ($rowData['data'][$dqlAlias][$fieldName] !== null && isset($cacheKeyInfo['enumType'])) { + $rowData['data'][$dqlAlias][$fieldName] = $this->buildEnum($rowData['data'][$dqlAlias][$fieldName], $cacheKeyInfo['enumType']); + } + + if ($cacheKeyInfo['isIdentifier'] && $value !== null) { + $id[$dqlAlias] .= '|' . $value; + $nonemptyComponents[$dqlAlias] = true; + } + + break; + } + } + + return $rowData; + } + + /** + * Processes a row of the result set. + * + * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that + * simply converts column names to field names and properly converts the + * values according to their types. The resulting row has the same number + * of elements as before. + * + * @param mixed[] $data + * @psalm-param array $data + * + * @return mixed[] The processed row. + * @psalm-return array + */ + protected function gatherScalarRowData(array &$data): array + { + $rowData = []; + + foreach ($data as $key => $value) { + $cacheKeyInfo = $this->hydrateColumnInfo($key); + if ($cacheKeyInfo === null) { + continue; + } + + $fieldName = $cacheKeyInfo['fieldName']; + + // WARNING: BC break! We know this is the desired behavior to type convert values, but this + // erroneous behavior exists since 2.0 and we're forced to keep compatibility. + if (! isset($cacheKeyInfo['isScalar'])) { + $type = $cacheKeyInfo['type']; + $value = $type ? $type->convertToPHPValue($value, $this->platform) : $value; + + $fieldName = $cacheKeyInfo['dqlAlias'] . '_' . $fieldName; + } + + $rowData[$fieldName] = $value; + } + + return $rowData; + } + + /** + * Retrieve column information from ResultSetMapping. + * + * @param string $key Column name + * + * @return mixed[]|null + * @psalm-return array|null + */ + protected function hydrateColumnInfo(string $key): array|null + { + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + + switch (true) { + // NOTE: Most of the times it's a field mapping, so keep it first!!! + case isset($this->rsm->fieldMappings[$key]): + $classMetadata = $this->getClassMetadata($this->rsm->declaringClasses[$key]); + $fieldName = $this->rsm->fieldMappings[$key]; + $fieldMapping = $classMetadata->fieldMappings[$fieldName]; + $ownerMap = $this->rsm->columnOwnerMap[$key]; + $columnInfo = [ + 'isIdentifier' => in_array($fieldName, $classMetadata->identifier, true), + 'fieldName' => $fieldName, + 'type' => Type::getType($fieldMapping->type), + 'dqlAlias' => $ownerMap, + 'enumType' => $this->rsm->enumMappings[$key] ?? null, + ]; + + // the current discriminator value must be saved in order to disambiguate fields hydration, + // should there be field name collisions + if ($classMetadata->parentClasses && isset($this->rsm->discriminatorColumns[$ownerMap])) { + return $this->cache[$key] = array_merge( + $columnInfo, + [ + 'discriminatorColumn' => $this->rsm->discriminatorColumns[$ownerMap], + 'discriminatorValue' => $classMetadata->discriminatorValue, + 'discriminatorValues' => $this->getDiscriminatorValues($classMetadata), + ], + ); + } + + return $this->cache[$key] = $columnInfo; + + case isset($this->rsm->newObjectMappings[$key]): + // WARNING: A NEW object is also a scalar, so it must be declared before! + $mapping = $this->rsm->newObjectMappings[$key]; + + return $this->cache[$key] = [ + 'isScalar' => true, + 'isNewObjectParameter' => true, + 'fieldName' => $this->rsm->scalarMappings[$key], + 'type' => Type::getType($this->rsm->typeMappings[$key]), + 'argIndex' => $mapping['argIndex'], + 'objIndex' => $mapping['objIndex'], + 'class' => new ReflectionClass($mapping['className']), + 'enumType' => $this->rsm->enumMappings[$key] ?? null, + ]; + + case isset($this->rsm->scalarMappings[$key], $this->hints[LimitSubqueryWalker::FORCE_DBAL_TYPE_CONVERSION]): + return $this->cache[$key] = [ + 'fieldName' => $this->rsm->scalarMappings[$key], + 'type' => Type::getType($this->rsm->typeMappings[$key]), + 'dqlAlias' => '', + 'enumType' => $this->rsm->enumMappings[$key] ?? null, + ]; + + case isset($this->rsm->scalarMappings[$key]): + return $this->cache[$key] = [ + 'isScalar' => true, + 'fieldName' => $this->rsm->scalarMappings[$key], + 'type' => Type::getType($this->rsm->typeMappings[$key]), + 'enumType' => $this->rsm->enumMappings[$key] ?? null, + ]; + + case isset($this->rsm->metaMappings[$key]): + // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). + $fieldName = $this->rsm->metaMappings[$key]; + $dqlAlias = $this->rsm->columnOwnerMap[$key]; + $type = isset($this->rsm->typeMappings[$key]) + ? Type::getType($this->rsm->typeMappings[$key]) + : null; + + // Cache metadata fetch + $this->getClassMetadata($this->rsm->aliasMap[$dqlAlias]); + + return $this->cache[$key] = [ + 'isIdentifier' => isset($this->rsm->isIdentifierColumn[$dqlAlias][$key]), + 'isMetaColumn' => true, + 'fieldName' => $fieldName, + 'type' => $type, + 'dqlAlias' => $dqlAlias, + 'enumType' => $this->rsm->enumMappings[$key] ?? null, + ]; + } + + // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 + // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. + return null; + } + + /** + * @return string[] + * @psalm-return non-empty-list + */ + private function getDiscriminatorValues(ClassMetadata $classMetadata): array + { + $values = array_map( + fn (string $subClass): string => (string) $this->getClassMetadata($subClass)->discriminatorValue, + $classMetadata->subClasses, + ); + + $values[] = (string) $classMetadata->discriminatorValue; + + return $values; + } + + /** + * Retrieve ClassMetadata associated to entity class name. + */ + protected function getClassMetadata(string $className): ClassMetadata + { + if (! isset($this->metadataCache[$className])) { + $this->metadataCache[$className] = $this->em->getClassMetadata($className); + } + + return $this->metadataCache[$className]; + } + + /** + * Register entity as managed in UnitOfWork. + * + * @param mixed[] $data + * + * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow + */ + protected function registerManaged(ClassMetadata $class, object $entity, array $data): void + { + if ($class->isIdentifierComposite) { + $id = []; + + foreach ($class->identifier as $fieldName) { + $id[$fieldName] = isset($class->associationMappings[$fieldName]) && $class->associationMappings[$fieldName]->isToOneOwningSide() + ? $data[$class->associationMappings[$fieldName]->joinColumns[0]->name] + : $data[$fieldName]; + } + } else { + $fieldName = $class->identifier[0]; + $id = [ + $fieldName => isset($class->associationMappings[$fieldName]) && $class->associationMappings[$fieldName]->isToOneOwningSide() + ? $data[$class->associationMappings[$fieldName]->joinColumns[0]->name] + : $data[$fieldName], + ]; + } + + $this->em->getUnitOfWork()->registerManaged($entity, $id, $data); + } + + /** + * @param class-string $enumType + * + * @return BackedEnum|array + */ + final protected function buildEnum(mixed $value, string $enumType): BackedEnum|array + { + if (is_array($value)) { + return array_map( + static fn ($value) => $enumType::from($value), + $value, + ); + } + + return $enumType::from($value); + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/ArrayHydrator.php b/vendor/doctrine/orm/src/Internal/Hydration/ArrayHydrator.php new file mode 100644 index 0000000..7115c16 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/ArrayHydrator.php @@ -0,0 +1,270 @@ + */ + private array $rootAliases = []; + + private bool $isSimpleQuery = false; + + /** @var mixed[] */ + private array $identifierMap = []; + + /** @var mixed[] */ + private array $resultPointers = []; + + /** @var array */ + private array $idTemplate = []; + + private int $resultCounter = 0; + + protected function prepare(): void + { + $this->isSimpleQuery = count($this->resultSetMapping()->aliasMap) <= 1; + + foreach ($this->resultSetMapping()->aliasMap as $dqlAlias => $className) { + $this->identifierMap[$dqlAlias] = []; + $this->resultPointers[$dqlAlias] = []; + $this->idTemplate[$dqlAlias] = ''; + } + } + + /** + * {@inheritDoc} + */ + protected function hydrateAllData(): array + { + $result = []; + + while ($data = $this->statement()->fetchAssociative()) { + $this->hydrateRowData($data, $result); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + protected function hydrateRowData(array $row, array &$result): void + { + // 1) Initialize + $id = $this->idTemplate; // initialize the id-memory + $nonemptyComponents = []; + $rowData = $this->gatherRowData($row, $id, $nonemptyComponents); + + // 2) Now hydrate the data found in the current row. + foreach ($rowData['data'] as $dqlAlias => $data) { + $index = false; + + if (isset($this->resultSetMapping()->parentAliasMap[$dqlAlias])) { + // It's a joined result + + $parent = $this->resultSetMapping()->parentAliasMap[$dqlAlias]; + $path = $parent . '.' . $dqlAlias; + + // missing parent data, skipping as RIGHT JOIN hydration is not supported. + if (! isset($nonemptyComponents[$parent])) { + continue; + } + + // Get a reference to the right element in the result tree. + // This element will get the associated element attached. + if ($this->resultSetMapping()->isMixed && isset($this->rootAliases[$parent])) { + $first = reset($this->resultPointers); + // TODO: Exception if $key === null ? + $baseElement =& $this->resultPointers[$parent][key($first)]; + } elseif (isset($this->resultPointers[$parent])) { + $baseElement =& $this->resultPointers[$parent]; + } else { + unset($this->resultPointers[$dqlAlias]); // Ticket #1228 + + continue; + } + + $relationAlias = $this->resultSetMapping()->relationMap[$dqlAlias]; + $parentClass = $this->metadataCache[$this->resultSetMapping()->aliasMap[$parent]]; + $relation = $parentClass->associationMappings[$relationAlias]; + + // Check the type of the relation (many or single-valued) + if (! $relation->isToOne()) { + $oneToOne = false; + + if (! isset($baseElement[$relationAlias])) { + $baseElement[$relationAlias] = []; + } + + if (isset($nonemptyComponents[$dqlAlias])) { + $indexExists = isset($this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); + $index = $indexExists ? $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; + $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; + + if (! $indexExists || ! $indexIsValid) { + $element = $data; + + if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { + $baseElement[$relationAlias][$row[$this->resultSetMapping()->indexByMap[$dqlAlias]]] = $element; + } else { + $baseElement[$relationAlias][] = $element; + } + + $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = array_key_last($baseElement[$relationAlias]); + } + } + } else { + $oneToOne = true; + + if ( + ! isset($nonemptyComponents[$dqlAlias]) && + ( ! isset($baseElement[$relationAlias])) + ) { + $baseElement[$relationAlias] = null; + } elseif (! isset($baseElement[$relationAlias])) { + $baseElement[$relationAlias] = $data; + } + } + + $coll =& $baseElement[$relationAlias]; + + if (is_array($coll)) { + $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne); + } + } else { + // It's a root result element + + $this->rootAliases[$dqlAlias] = true; // Mark as root + $entityKey = $this->resultSetMapping()->entityMappings[$dqlAlias] ?: 0; + + // if this row has a NULL value for the root result id then make it a null result. + if (! isset($nonemptyComponents[$dqlAlias])) { + $result[] = $this->resultSetMapping()->isMixed + ? [$entityKey => null] + : null; + + $resultKey = $this->resultCounter; + ++$this->resultCounter; + + continue; + } + + // Check for an existing element + if ($this->isSimpleQuery || ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) { + $element = $this->resultSetMapping()->isMixed + ? [$entityKey => $data] + : $data; + + if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { + $resultKey = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]]; + $result[$resultKey] = $element; + } else { + $resultKey = $this->resultCounter; + $result[] = $element; + + ++$this->resultCounter; + } + + $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; + } else { + $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]]; + $resultKey = $index; + } + + $this->updateResultPointer($result, $index, $dqlAlias, false); + } + } + + if (! isset($resultKey)) { + $this->resultCounter++; + } + + // Append scalar values to mixed result sets + if (isset($rowData['scalars'])) { + if (! isset($resultKey)) { + // this only ever happens when no object is fetched (scalar result only) + $resultKey = isset($this->resultSetMapping()->indexByMap['scalars']) + ? $row[$this->resultSetMapping()->indexByMap['scalars']] + : $this->resultCounter - 1; + } + + foreach ($rowData['scalars'] as $name => $value) { + $result[$resultKey][$name] = $value; + } + } + + // Append new object to mixed result sets + if (isset($rowData['newObjects'])) { + if (! isset($resultKey)) { + $resultKey = $this->resultCounter - 1; + } + + $scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0); + + foreach ($rowData['newObjects'] as $objIndex => $newObject) { + $class = $newObject['class']; + $args = $newObject['args']; + $obj = $class->newInstanceArgs($args); + + if (count($args) === $scalarCount || ($scalarCount === 0 && count($rowData['newObjects']) === 1)) { + $result[$resultKey] = $obj; + + continue; + } + + $result[$resultKey][$objIndex] = $obj; + } + } + } + + /** + * Updates the result pointer for an Entity. The result pointers point to the + * last seen instance of each Entity type. This is used for graph construction. + * + * @param mixed[]|null $coll The element. + * @param string|int|false $index Index of the element in the collection. + * @param bool $oneToOne Whether it is a single-valued association or not. + */ + private function updateResultPointer( + array|null &$coll, + string|int|false $index, + string $dqlAlias, + bool $oneToOne, + ): void { + if ($coll === null) { + unset($this->resultPointers[$dqlAlias]); // Ticket #1228 + + return; + } + + if ($oneToOne) { + $this->resultPointers[$dqlAlias] =& $coll; + + return; + } + + if ($index !== false) { + $this->resultPointers[$dqlAlias] =& $coll[$index]; + + return; + } + + if (! $coll) { + return; + } + + $this->resultPointers[$dqlAlias] =& $coll[array_key_last($coll)]; + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/HydrationException.php b/vendor/doctrine/orm/src/Internal/Hydration/HydrationException.php new file mode 100644 index 0000000..710114f --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/HydrationException.php @@ -0,0 +1,67 @@ + $discrValues */ + public static function invalidDiscriminatorValue(string $discrValue, array $discrValues): self + { + return new self(sprintf( + 'The discriminator value "%s" is invalid. It must be one of "%s".', + $discrValue, + implode('", "', $discrValues), + )); + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php b/vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php new file mode 100644 index 0000000..d0fc101 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php @@ -0,0 +1,586 @@ + */ + private array $uninitializedCollections = []; + + /** @var mixed[] */ + private array $existingCollections = []; + + protected function prepare(): void + { + if (! isset($this->hints[UnitOfWork::HINT_DEFEREAGERLOAD])) { + $this->hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true; + } + + foreach ($this->resultSetMapping()->aliasMap as $dqlAlias => $className) { + $this->identifierMap[$dqlAlias] = []; + $this->idTemplate[$dqlAlias] = ''; + + // Remember which associations are "fetch joined", so that we know where to inject + // collection stubs or proxies and where not. + if (! isset($this->resultSetMapping()->relationMap[$dqlAlias])) { + continue; + } + + $parent = $this->resultSetMapping()->parentAliasMap[$dqlAlias]; + + if (! isset($this->resultSetMapping()->aliasMap[$parent])) { + throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent); + } + + $sourceClassName = $this->resultSetMapping()->aliasMap[$parent]; + $sourceClass = $this->getClassMetadata($sourceClassName); + $assoc = $sourceClass->associationMappings[$this->resultSetMapping()->relationMap[$dqlAlias]]; + + $this->hints['fetched'][$parent][$assoc->fieldName] = true; + + if ($assoc->isManyToMany()) { + continue; + } + + // Mark any non-collection opposite sides as fetched, too. + if (! $assoc->isOwningSide()) { + $this->hints['fetched'][$dqlAlias][$assoc->mappedBy] = true; + + continue; + } + + // handle fetch-joined owning side bi-directional one-to-one associations + if ($assoc->inversedBy !== null) { + $class = $this->getClassMetadata($className); + $inverseAssoc = $class->associationMappings[$assoc->inversedBy]; + + if (! $inverseAssoc->isToOne()) { + continue; + } + + $this->hints['fetched'][$dqlAlias][$inverseAssoc->fieldName] = true; + } + } + } + + protected function cleanup(): void + { + $eagerLoad = isset($this->hints[UnitOfWork::HINT_DEFEREAGERLOAD]) && $this->hints[UnitOfWork::HINT_DEFEREAGERLOAD] === true; + + parent::cleanup(); + + $this->identifierMap = + $this->initializedCollections = + $this->uninitializedCollections = + $this->existingCollections = + $this->resultPointers = []; + + if ($eagerLoad) { + $this->uow->triggerEagerLoads(); + } + + $this->uow->hydrationComplete(); + } + + protected function cleanupAfterRowIteration(): void + { + $this->identifierMap = + $this->initializedCollections = + $this->uninitializedCollections = + $this->existingCollections = + $this->resultPointers = []; + } + + /** + * {@inheritDoc} + */ + protected function hydrateAllData(): array + { + $result = []; + + while ($row = $this->statement()->fetchAssociative()) { + $this->hydrateRowData($row, $result); + } + + // Take snapshots from all newly initialized collections + foreach ($this->initializedCollections as $coll) { + $coll->takeSnapshot(); + } + + foreach ($this->uninitializedCollections as $coll) { + if (! $coll->isInitialized()) { + $coll->setInitialized(true); + } + } + + return $result; + } + + /** + * Initializes a related collection. + * + * @param string $fieldName The name of the field on the entity that holds the collection. + * @param string $parentDqlAlias Alias of the parent fetch joining this collection. + */ + private function initRelatedCollection( + object $entity, + ClassMetadata $class, + string $fieldName, + string $parentDqlAlias, + ): PersistentCollection { + $oid = spl_object_id($entity); + $relation = $class->associationMappings[$fieldName]; + $value = $class->reflFields[$fieldName]->getValue($entity); + + if ($value === null || is_array($value)) { + $value = new ArrayCollection((array) $value); + } + + if (! $value instanceof PersistentCollection) { + assert($relation->isToMany()); + $value = new PersistentCollection( + $this->em, + $this->metadataCache[$relation->targetEntity], + $value, + ); + $value->setOwner($entity, $relation); + + $class->reflFields[$fieldName]->setValue($entity, $value); + $this->uow->setOriginalEntityProperty($oid, $fieldName, $value); + + $this->initializedCollections[$oid . $fieldName] = $value; + } elseif ( + isset($this->hints[Query::HINT_REFRESH]) || + isset($this->hints['fetched'][$parentDqlAlias][$fieldName]) && + ! $value->isInitialized() + ) { + // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! + $value->setDirty(false); + $value->setInitialized(true); + $value->unwrap()->clear(); + + $this->initializedCollections[$oid . $fieldName] = $value; + } else { + // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! + $this->existingCollections[$oid . $fieldName] = $value; + } + + return $value; + } + + /** + * Gets an entity instance. + * + * @param string $dqlAlias The DQL alias of the entity's class. + * @psalm-param array $data The instance data. + * + * @throws HydrationException + */ + private function getEntity(array $data, string $dqlAlias): object + { + $className = $this->resultSetMapping()->aliasMap[$dqlAlias]; + + if (isset($this->resultSetMapping()->discriminatorColumns[$dqlAlias])) { + $fieldName = $this->resultSetMapping()->discriminatorColumns[$dqlAlias]; + + if (! isset($this->resultSetMapping()->metaMappings[$fieldName])) { + throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $fieldName, $dqlAlias); + } + + $discrColumn = $this->resultSetMapping()->metaMappings[$fieldName]; + + if (! isset($data[$discrColumn])) { + throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias); + } + + if ($data[$discrColumn] === '') { + throw HydrationException::emptyDiscriminatorValue($dqlAlias); + } + + $discrMap = $this->metadataCache[$className]->discriminatorMap; + $discriminatorValue = $data[$discrColumn]; + if ($discriminatorValue instanceof BackedEnum) { + $discriminatorValue = $discriminatorValue->value; + } + + $discriminatorValue = (string) $discriminatorValue; + + if (! isset($discrMap[$discriminatorValue])) { + throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap)); + } + + $className = $discrMap[$discriminatorValue]; + + unset($data[$discrColumn]); + } + + if (isset($this->hints[Query::HINT_REFRESH_ENTITY], $this->rootAliases[$dqlAlias])) { + $this->registerManaged($this->metadataCache[$className], $this->hints[Query::HINT_REFRESH_ENTITY], $data); + } + + $this->hints['fetchAlias'] = $dqlAlias; + + return $this->uow->createEntity($className, $data, $this->hints); + } + + /** + * @psalm-param class-string $className + * @psalm-param array $data + */ + private function getEntityFromIdentityMap(string $className, array $data): object|bool + { + // TODO: Abstract this code and UnitOfWork::createEntity() equivalent? + $class = $this->metadataCache[$className]; + + if ($class->isIdentifierComposite) { + $idHash = UnitOfWork::getIdHashByIdentifier( + array_map( + /** @return mixed */ + static fn (string $fieldName) => isset($class->associationMappings[$fieldName]) && assert($class->associationMappings[$fieldName]->isToOneOwningSide()) + ? $data[$class->associationMappings[$fieldName]->joinColumns[0]->name] + : $data[$fieldName], + $class->identifier, + ), + ); + + return $this->uow->tryGetByIdHash(ltrim($idHash), $class->rootEntityName); + } elseif (isset($class->associationMappings[$class->identifier[0]])) { + $association = $class->associationMappings[$class->identifier[0]]; + assert($association->isToOneOwningSide()); + + return $this->uow->tryGetByIdHash($data[$association->joinColumns[0]->name], $class->rootEntityName); + } + + return $this->uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName); + } + + /** + * Hydrates a single row in an SQL result set. + * + * @internal + * First, the data of the row is split into chunks where each chunk contains data + * that belongs to a particular component/class. Afterwards, all these chunks + * are processed, one after the other. For each chunk of class data only one of the + * following code paths is executed: + * Path A: The data chunk belongs to a joined/associated object and the association + * is collection-valued. + * Path B: The data chunk belongs to a joined/associated object and the association + * is single-valued. + * Path C: The data chunk belongs to a root result element/object that appears in the topmost + * level of the hydrated result. A typical example are the objects of the type + * specified by the FROM clause in a DQL query. + * + * @param mixed[] $row The data of the row to process. + * @param mixed[] $result The result array to fill. + */ + protected function hydrateRowData(array $row, array &$result): void + { + // Initialize + $id = $this->idTemplate; // initialize the id-memory + $nonemptyComponents = []; + // Split the row data into chunks of class data. + $rowData = $this->gatherRowData($row, $id, $nonemptyComponents); + + // reset result pointers for each data row + $this->resultPointers = []; + + // Hydrate the data chunks + foreach ($rowData['data'] as $dqlAlias => $data) { + $entityName = $this->resultSetMapping()->aliasMap[$dqlAlias]; + + if (isset($this->resultSetMapping()->parentAliasMap[$dqlAlias])) { + // It's a joined result + + $parentAlias = $this->resultSetMapping()->parentAliasMap[$dqlAlias]; + // we need the $path to save into the identifier map which entities were already + // seen for this parent-child relationship + $path = $parentAlias . '.' . $dqlAlias; + + // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs + if (! isset($nonemptyComponents[$parentAlias])) { + // TODO: Add special case code where we hydrate the right join objects into identity map at least + continue; + } + + $parentClass = $this->metadataCache[$this->resultSetMapping()->aliasMap[$parentAlias]]; + $relationField = $this->resultSetMapping()->relationMap[$dqlAlias]; + $relation = $parentClass->associationMappings[$relationField]; + $reflField = $parentClass->reflFields[$relationField]; + + // Get a reference to the parent object to which the joined element belongs. + if ($this->resultSetMapping()->isMixed && isset($this->rootAliases[$parentAlias])) { + $objectClass = $this->resultPointers[$parentAlias]; + $parentObject = $objectClass[key($objectClass)]; + } elseif (isset($this->resultPointers[$parentAlias])) { + $parentObject = $this->resultPointers[$parentAlias]; + } else { + // Parent object of relation not found, mark as not-fetched again + if (isset($nonemptyComponents[$dqlAlias])) { + $element = $this->getEntity($data, $dqlAlias); + + // Update result pointer and provide initial fetch data for parent + $this->resultPointers[$dqlAlias] = $element; + $rowData['data'][$parentAlias][$relationField] = $element; + } else { + $element = null; + } + + // Mark as not-fetched again + unset($this->hints['fetched'][$parentAlias][$relationField]); + continue; + } + + $oid = spl_object_id($parentObject); + + // Check the type of the relation (many or single-valued) + if (! $relation->isToOne()) { + // PATH A: Collection-valued association + $reflFieldValue = $reflField->getValue($parentObject); + + if (isset($nonemptyComponents[$dqlAlias])) { + $collKey = $oid . $relationField; + if (isset($this->initializedCollections[$collKey])) { + $reflFieldValue = $this->initializedCollections[$collKey]; + } elseif (! isset($this->existingCollections[$collKey])) { + $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); + } + + $indexExists = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); + $index = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; + $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; + + if (! $indexExists || ! $indexIsValid) { + if (isset($this->existingCollections[$collKey])) { + // Collection exists, only look for the element in the identity map. + $element = $this->getEntityFromIdentityMap($entityName, $data); + if ($element) { + $this->resultPointers[$dqlAlias] = $element; + } else { + unset($this->resultPointers[$dqlAlias]); + } + } else { + $element = $this->getEntity($data, $dqlAlias); + + if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { + $indexValue = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]]; + $reflFieldValue->hydrateSet($indexValue, $element); + $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; + } else { + if (! $reflFieldValue->contains($element)) { + $reflFieldValue->hydrateAdd($element); + $reflFieldValue->last(); + } + + $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key(); + } + + // Update result pointer + $this->resultPointers[$dqlAlias] = $element; + } + } else { + // Update result pointer + $this->resultPointers[$dqlAlias] = $reflFieldValue[$index]; + } + } elseif (! $reflFieldValue) { + $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); + } elseif ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false && ! isset($this->uninitializedCollections[$oid . $relationField])) { + $this->uninitializedCollections[$oid . $relationField] = $reflFieldValue; + } + } else { + // PATH B: Single-valued association + $reflFieldValue = $reflField->getValue($parentObject); + + if (! $reflFieldValue || isset($this->hints[Query::HINT_REFRESH]) || $this->uow->isUninitializedObject($reflFieldValue)) { + // we only need to take action if this value is null, + // we refresh the entity or its an uninitialized proxy. + if (isset($nonemptyComponents[$dqlAlias])) { + $element = $this->getEntity($data, $dqlAlias); + $reflField->setValue($parentObject, $element); + $this->uow->setOriginalEntityProperty($oid, $relationField, $element); + $targetClass = $this->metadataCache[$relation->targetEntity]; + + if ($relation->isOwningSide()) { + // TODO: Just check hints['fetched'] here? + // If there is an inverse mapping on the target class its bidirectional + if ($relation->inversedBy !== null) { + $inverseAssoc = $targetClass->associationMappings[$relation->inversedBy]; + if ($inverseAssoc->isToOne()) { + $targetClass->reflFields[$inverseAssoc->fieldName]->setValue($element, $parentObject); + $this->uow->setOriginalEntityProperty(spl_object_id($element), $inverseAssoc->fieldName, $parentObject); + } + } + } else { + // For sure bidirectional, as there is no inverse side in unidirectional mappings + $targetClass->reflFields[$relation->mappedBy]->setValue($element, $parentObject); + $this->uow->setOriginalEntityProperty(spl_object_id($element), $relation->mappedBy, $parentObject); + } + + // Update result pointer + $this->resultPointers[$dqlAlias] = $element; + } else { + $this->uow->setOriginalEntityProperty($oid, $relationField, null); + $reflField->setValue($parentObject, null); + } + // else leave $reflFieldValue null for single-valued associations + } else { + // Update result pointer + $this->resultPointers[$dqlAlias] = $reflFieldValue; + } + } + } else { + // PATH C: Its a root result element + $this->rootAliases[$dqlAlias] = true; // Mark as root alias + $entityKey = $this->resultSetMapping()->entityMappings[$dqlAlias] ?: 0; + + // if this row has a NULL value for the root result id then make it a null result. + if (! isset($nonemptyComponents[$dqlAlias])) { + if ($this->resultSetMapping()->isMixed) { + $result[] = [$entityKey => null]; + } else { + $result[] = null; + } + + $resultKey = $this->resultCounter; + ++$this->resultCounter; + continue; + } + + // check for existing result from the iterations before + if (! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) { + $element = $this->getEntity($data, $dqlAlias); + + if ($this->resultSetMapping()->isMixed) { + $element = [$entityKey => $element]; + } + + if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { + $resultKey = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]]; + + if (isset($this->hints['collection'])) { + $this->hints['collection']->hydrateSet($resultKey, $element); + } + + $result[$resultKey] = $element; + } else { + $resultKey = $this->resultCounter; + ++$this->resultCounter; + + if (isset($this->hints['collection'])) { + $this->hints['collection']->hydrateAdd($element); + } + + $result[] = $element; + } + + $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; + + // Update result pointer + $this->resultPointers[$dqlAlias] = $element; + } else { + // Update result pointer + $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]]; + $this->resultPointers[$dqlAlias] = $result[$index]; + $resultKey = $index; + } + } + + if (isset($this->hints[Query::HINT_INTERNAL_ITERATION]) && $this->hints[Query::HINT_INTERNAL_ITERATION]) { + $this->uow->hydrationComplete(); + } + } + + if (! isset($resultKey)) { + $this->resultCounter++; + } + + // Append scalar values to mixed result sets + if (isset($rowData['scalars'])) { + if (! isset($resultKey)) { + $resultKey = isset($this->resultSetMapping()->indexByMap['scalars']) + ? $row[$this->resultSetMapping()->indexByMap['scalars']] + : $this->resultCounter - 1; + } + + foreach ($rowData['scalars'] as $name => $value) { + $result[$resultKey][$name] = $value; + } + } + + // Append new object to mixed result sets + if (isset($rowData['newObjects'])) { + if (! isset($resultKey)) { + $resultKey = $this->resultCounter - 1; + } + + $scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0); + + foreach ($rowData['newObjects'] as $objIndex => $newObject) { + $class = $newObject['class']; + $args = $newObject['args']; + $obj = $class->newInstanceArgs($args); + + if ($scalarCount === 0 && count($rowData['newObjects']) === 1) { + $result[$resultKey] = $obj; + + continue; + } + + $result[$resultKey][$objIndex] = $obj; + } + } + } + + /** + * When executed in a hydrate() loop we may have to clear internal state to + * decrease memory consumption. + */ + public function onClear(mixed $eventArgs): void + { + parent::onClear($eventArgs); + + $aliases = array_keys($this->identifierMap); + + $this->identifierMap = array_fill_keys($aliases, []); + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/ScalarColumnHydrator.php b/vendor/doctrine/orm/src/Internal/Hydration/ScalarColumnHydrator.php new file mode 100644 index 0000000..0f10fb4 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/ScalarColumnHydrator.php @@ -0,0 +1,34 @@ +resultSetMapping()->fieldMappings) > 1) { + throw MultipleSelectorsFoundException::create($this->resultSetMapping()->fieldMappings); + } + + $result = $this->statement()->fetchAllNumeric(); + + return array_column($result, 0); + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/ScalarHydrator.php b/vendor/doctrine/orm/src/Internal/Hydration/ScalarHydrator.php new file mode 100644 index 0000000..15f3e7e --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/ScalarHydrator.php @@ -0,0 +1,35 @@ +statement()->fetchAssociative()) { + $this->hydrateRowData($data, $result); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + protected function hydrateRowData(array $row, array &$result): void + { + $result[] = $this->gatherScalarRowData($row); + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/SimpleObjectHydrator.php b/vendor/doctrine/orm/src/Internal/Hydration/SimpleObjectHydrator.php new file mode 100644 index 0000000..eab7b9b --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/SimpleObjectHydrator.php @@ -0,0 +1,176 @@ +resultSetMapping()->aliasMap) !== 1) { + throw new RuntimeException('Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result.'); + } + + if ($this->resultSetMapping()->scalarMappings) { + throw new RuntimeException('Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.'); + } + + $this->class = $this->getClassMetadata(reset($this->resultSetMapping()->aliasMap)); + } + + protected function cleanup(): void + { + parent::cleanup(); + + $this->uow->triggerEagerLoads(); + $this->uow->hydrationComplete(); + } + + /** + * {@inheritDoc} + */ + protected function hydrateAllData(): array + { + $result = []; + + while ($row = $this->statement()->fetchAssociative()) { + $this->hydrateRowData($row, $result); + } + + $this->em->getUnitOfWork()->triggerEagerLoads(); + + return $result; + } + + /** + * {@inheritDoc} + */ + protected function hydrateRowData(array $row, array &$result): void + { + assert($this->class !== null); + $entityName = $this->class->name; + $data = []; + $discrColumnValue = null; + + // We need to find the correct entity class name if we have inheritance in resultset + if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { + $discrColumn = $this->class->getDiscriminatorColumn(); + $discrColumnName = $this->getSQLResultCasing($this->platform, $discrColumn->name); + + // Find mapped discriminator column from the result set. + $metaMappingDiscrColumnName = array_search($discrColumnName, $this->resultSetMapping()->metaMappings, true); + if ($metaMappingDiscrColumnName) { + $discrColumnName = $metaMappingDiscrColumnName; + } + + if (! isset($row[$discrColumnName])) { + throw HydrationException::missingDiscriminatorColumn( + $entityName, + $discrColumnName, + key($this->resultSetMapping()->aliasMap), + ); + } + + if ($row[$discrColumnName] === '') { + throw HydrationException::emptyDiscriminatorValue(key( + $this->resultSetMapping()->aliasMap, + )); + } + + $discrMap = $this->class->discriminatorMap; + + if (! isset($discrMap[$row[$discrColumnName]])) { + throw HydrationException::invalidDiscriminatorValue($row[$discrColumnName], array_keys($discrMap)); + } + + $entityName = $discrMap[$row[$discrColumnName]]; + $discrColumnValue = $row[$discrColumnName]; + + unset($row[$discrColumnName]); + } + + foreach ($row as $column => $value) { + // An ObjectHydrator should be used instead of SimpleObjectHydrator + if (isset($this->resultSetMapping()->relationMap[$column])) { + throw new Exception(sprintf('Unable to retrieve association information for column "%s"', $column)); + } + + $cacheKeyInfo = $this->hydrateColumnInfo($column); + + if (! $cacheKeyInfo) { + continue; + } + + // If we have inheritance in resultset, make sure the field belongs to the correct class + if (isset($cacheKeyInfo['discriminatorValues']) && ! in_array((string) $discrColumnValue, $cacheKeyInfo['discriminatorValues'], true)) { + continue; + } + + // Check if value is null before conversion (because some types convert null to something else) + $valueIsNull = $value === null; + + // Convert field to a valid PHP value + if (isset($cacheKeyInfo['type'])) { + $type = $cacheKeyInfo['type']; + $value = $type->convertToPHPValue($value, $this->platform); + } + + if ($value !== null && isset($cacheKeyInfo['enumType'])) { + $originalValue = $value; + try { + $value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']); + } catch (ValueError $e) { + throw MappingException::invalidEnumValue( + $entityName, + $cacheKeyInfo['fieldName'], + (string) $originalValue, + $cacheKeyInfo['enumType'], + $e, + ); + } + } + + $fieldName = $cacheKeyInfo['fieldName']; + + // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator) + if (! isset($data[$fieldName]) || ! $valueIsNull) { + $data[$fieldName] = $value; + } + } + + if (isset($this->hints[Query::HINT_REFRESH_ENTITY])) { + $this->registerManaged($this->class, $this->hints[Query::HINT_REFRESH_ENTITY], $data); + } + + $uow = $this->em->getUnitOfWork(); + $entity = $uow->createEntity($entityName, $data, $this->hints); + + $result[] = $entity; + + if (isset($this->hints[Query::HINT_INTERNAL_ITERATION]) && $this->hints[Query::HINT_INTERNAL_ITERATION]) { + $this->uow->hydrationComplete(); + } + } +} diff --git a/vendor/doctrine/orm/src/Internal/Hydration/SingleScalarHydrator.php b/vendor/doctrine/orm/src/Internal/Hydration/SingleScalarHydrator.php new file mode 100644 index 0000000..2787bbc --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/Hydration/SingleScalarHydrator.php @@ -0,0 +1,40 @@ +statement()->fetchAllAssociative(); + $numRows = count($data); + + if ($numRows === 0) { + throw new NoResultException(); + } + + if ($numRows > 1) { + throw new NonUniqueResultException('The query returned multiple rows. Change the query or use a different result function like getScalarResult().'); + } + + $result = $this->gatherScalarRowData($data[key($data)]); + + if (count($result) > 1) { + throw new NonUniqueResultException('The query returned a row containing multiple columns. Change the query or use a different result function like getScalarResult().'); + } + + return array_shift($result); + } +} diff --git a/vendor/doctrine/orm/src/Internal/HydrationCompleteHandler.php b/vendor/doctrine/orm/src/Internal/HydrationCompleteHandler.php new file mode 100644 index 0000000..e0fe342 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/HydrationCompleteHandler.php @@ -0,0 +1,64 @@ +listenersInvoker->getSubscribedSystems($class, Events::postLoad); + + if ($invoke === ListenersInvoker::INVOKE_NONE) { + return; + } + + $this->deferredPostLoadInvocations[] = [$class, $invoke, $entity]; + } + + /** + * This method should be called after any hydration cycle completed. + * + * Method fires all deferred invocations of postLoad events + */ + public function hydrationComplete(): void + { + $toInvoke = $this->deferredPostLoadInvocations; + $this->deferredPostLoadInvocations = []; + + foreach ($toInvoke as $classAndEntity) { + [$class, $invoke, $entity] = $classAndEntity; + + $this->listenersInvoker->invoke( + $class, + Events::postLoad, + $entity, + new PostLoadEventArgs($entity, $this->em), + $invoke, + ); + } + } +} diff --git a/vendor/doctrine/orm/src/Internal/NoUnknownNamedArguments.php b/vendor/doctrine/orm/src/Internal/NoUnknownNamedArguments.php new file mode 100644 index 0000000..7584744 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/NoUnknownNamedArguments.php @@ -0,0 +1,55 @@ + $parameter + */ + private static function validateVariadicParameter(array $parameter): void + { + if (array_is_list($parameter)) { + return; + } + + [, $trace] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + assert(isset($trace['class'])); + + $additionalArguments = array_values(array_filter( + array_keys($parameter), + is_string(...), + )); + + throw new BadMethodCallException(sprintf( + 'Invalid call to %s::%s(), unknown named arguments: %s', + $trace['class'], + $trace['function'], + implode(', ', $additionalArguments), + )); + } +} diff --git a/vendor/doctrine/orm/src/Internal/QueryType.php b/vendor/doctrine/orm/src/Internal/QueryType.php new file mode 100644 index 0000000..b5e60c7 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/QueryType.php @@ -0,0 +1,13 @@ + + */ + private array $nodes = []; + + /** + * DFS state for the different nodes, indexed by node object id and using one of + * this class' constants as value. + * + * @var array + */ + private array $states = []; + + /** + * Edges between the nodes. The first-level key is the object id of the outgoing + * node; the second array maps the destination node by object id as key. + * + * @var array> + */ + private array $edges = []; + + /** + * DFS numbers, by object ID + * + * @var array + */ + private array $dfs = []; + + /** + * lowlink numbers, by object ID + * + * @var array + */ + private array $lowlink = []; + + private int $maxdfs = 0; + + /** + * Nodes representing the SCC another node is in, indexed by lookup-node object ID + * + * @var array + */ + private array $representingNodes = []; + + /** + * Stack with OIDs of nodes visited in the current state of the DFS + * + * @var list + */ + private array $stack = []; + + public function addNode(object $node): void + { + $id = spl_object_id($node); + $this->nodes[$id] = $node; + $this->states[$id] = self::NOT_VISITED; + $this->edges[$id] = []; + } + + public function hasNode(object $node): bool + { + return isset($this->nodes[spl_object_id($node)]); + } + + /** + * Adds a new edge between two nodes to the graph + */ + public function addEdge(object $from, object $to): void + { + $fromId = spl_object_id($from); + $toId = spl_object_id($to); + + $this->edges[$fromId][$toId] = true; + } + + public function findStronglyConnectedComponents(): void + { + foreach (array_keys($this->nodes) as $oid) { + if ($this->states[$oid] === self::NOT_VISITED) { + $this->tarjan($oid); + } + } + } + + private function tarjan(int $oid): void + { + $this->dfs[$oid] = $this->lowlink[$oid] = $this->maxdfs++; + $this->states[$oid] = self::IN_PROGRESS; + array_push($this->stack, $oid); + + foreach ($this->edges[$oid] as $adjacentId => $ignored) { + if ($this->states[$adjacentId] === self::NOT_VISITED) { + $this->tarjan($adjacentId); + $this->lowlink[$oid] = min($this->lowlink[$oid], $this->lowlink[$adjacentId]); + } elseif ($this->states[$adjacentId] === self::IN_PROGRESS) { + $this->lowlink[$oid] = min($this->lowlink[$oid], $this->dfs[$adjacentId]); + } + } + + $lowlink = $this->lowlink[$oid]; + if ($lowlink === $this->dfs[$oid]) { + $representingNode = null; + do { + $unwindOid = array_pop($this->stack); + + if (! $representingNode) { + $representingNode = $this->nodes[$unwindOid]; + } + + $this->representingNodes[$unwindOid] = $representingNode; + $this->states[$unwindOid] = self::VISITED; + } while ($unwindOid !== $oid); + } + } + + public function getNodeRepresentingStronglyConnectedComponent(object $node): object + { + $oid = spl_object_id($node); + + if (! isset($this->representingNodes[$oid])) { + throw new InvalidArgumentException('unknown node'); + } + + return $this->representingNodes[$oid]; + } +} diff --git a/vendor/doctrine/orm/src/Internal/TopologicalSort.php b/vendor/doctrine/orm/src/Internal/TopologicalSort.php new file mode 100644 index 0000000..808bc0f --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/TopologicalSort.php @@ -0,0 +1,155 @@ + + */ + private array $nodes = []; + + /** + * DFS state for the different nodes, indexed by node object id and using one of + * this class' constants as value. + * + * @var array + */ + private array $states = []; + + /** + * Edges between the nodes. The first-level key is the object id of the outgoing + * node; the second array maps the destination node by object id as key. The final + * boolean value indicates whether the edge is optional or not. + * + * @var array> + */ + private array $edges = []; + + /** + * Builds up the result during the DFS. + * + * @var list + */ + private array $sortResult = []; + + public function addNode(object $node): void + { + $id = spl_object_id($node); + $this->nodes[$id] = $node; + $this->states[$id] = self::NOT_VISITED; + $this->edges[$id] = []; + } + + public function hasNode(object $node): bool + { + return isset($this->nodes[spl_object_id($node)]); + } + + /** + * Adds a new edge between two nodes to the graph + * + * @param bool $optional This indicates whether the edge may be ignored during the topological sort if it is necessary to break cycles. + */ + public function addEdge(object $from, object $to, bool $optional): void + { + $fromId = spl_object_id($from); + $toId = spl_object_id($to); + + if (isset($this->edges[$fromId][$toId]) && $this->edges[$fromId][$toId] === false) { + return; // we already know about this dependency, and it is not optional + } + + $this->edges[$fromId][$toId] = $optional; + } + + /** + * Returns a topological sort of all nodes. When we have an edge A->B between two nodes + * A and B, then B will be listed before A in the result. Visually speaking, when ordering + * the nodes in the result order from left to right, all edges point to the left. + * + * @return list + */ + public function sort(): array + { + foreach (array_keys($this->nodes) as $oid) { + if ($this->states[$oid] === self::NOT_VISITED) { + $this->visit($oid); + } + } + + return $this->sortResult; + } + + private function visit(int $oid): void + { + if ($this->states[$oid] === self::IN_PROGRESS) { + // This node is already on the current DFS stack. We've found a cycle! + throw new CycleDetectedException($this->nodes[$oid]); + } + + if ($this->states[$oid] === self::VISITED) { + // We've reached a node that we've already seen, including all + // other nodes that are reachable from here. We're done here, return. + return; + } + + $this->states[$oid] = self::IN_PROGRESS; + + // Continue the DFS downwards the edge list + foreach ($this->edges[$oid] as $adjacentId => $optional) { + try { + $this->visit($adjacentId); + } catch (CycleDetectedException $exception) { + if ($exception->isCycleCollected()) { + // There is a complete cycle downstream of the current node. We cannot + // do anything about that anymore. + throw $exception; + } + + if ($optional) { + // The current edge is part of a cycle, but it is optional and the closest + // such edge while backtracking. Break the cycle here by skipping the edge + // and continuing with the next one. + continue; + } + + // We have found a cycle and cannot break it at $edge. Best we can do + // is to backtrack from the current vertex, hoping that somewhere up the + // stack this can be salvaged. + $this->states[$oid] = self::NOT_VISITED; + $exception->addToCycle($this->nodes[$oid]); + + throw $exception; + } + } + + // We have traversed all edges and visited all other nodes reachable from here. + // So we're done with this vertex as well. + + $this->states[$oid] = self::VISITED; + $this->sortResult[] = $this->nodes[$oid]; + } +} diff --git a/vendor/doctrine/orm/src/Internal/TopologicalSort/CycleDetectedException.php b/vendor/doctrine/orm/src/Internal/TopologicalSort/CycleDetectedException.php new file mode 100644 index 0000000..3af5329 --- /dev/null +++ b/vendor/doctrine/orm/src/Internal/TopologicalSort/CycleDetectedException.php @@ -0,0 +1,47 @@ + */ + private array $cycle; + + /** + * Do we have the complete cycle collected? + */ + private bool $cycleCollected = false; + + public function __construct(private readonly object $startNode) + { + parent::__construct('A cycle has been detected, so a topological sort is not possible. The getCycle() method provides the list of nodes that form the cycle.'); + + $this->cycle = [$startNode]; + } + + /** @return list */ + public function getCycle(): array + { + return $this->cycle; + } + + public function addToCycle(object $node): void + { + array_unshift($this->cycle, $node); + + if ($node === $this->startNode) { + $this->cycleCollected = true; + } + } + + public function isCycleCollected(): bool + { + return $this->cycleCollected; + } +} diff --git a/vendor/doctrine/orm/src/LazyCriteriaCollection.php b/vendor/doctrine/orm/src/LazyCriteriaCollection.php new file mode 100644 index 0000000..ca67914 --- /dev/null +++ b/vendor/doctrine/orm/src/LazyCriteriaCollection.php @@ -0,0 +1,96 @@ + + * @implements Selectable + */ +class LazyCriteriaCollection extends AbstractLazyCollection implements Selectable +{ + private int|null $count = null; + + public function __construct( + protected EntityPersister $entityPersister, + protected Criteria $criteria, + ) { + } + + /** + * Do an efficient count on the collection + */ + public function count(): int + { + if ($this->isInitialized()) { + return $this->collection->count(); + } + + // Return cached result in case count query was already executed + if ($this->count !== null) { + return $this->count; + } + + return $this->count = $this->entityPersister->count($this->criteria); + } + + /** + * check if collection is empty without loading it + */ + public function isEmpty(): bool + { + if ($this->isInitialized()) { + return $this->collection->isEmpty(); + } + + return ! $this->count(); + } + + /** + * Do an optimized search of an element + * + * @param mixed $element The element to search for. + * + * @return bool TRUE if the collection contains $element, FALSE otherwise. + */ + public function contains(mixed $element): bool + { + if ($this->isInitialized()) { + return $this->collection->contains($element); + } + + return $this->entityPersister->exists($element, $this->criteria); + } + + /** @return ReadableCollection&Selectable */ + public function matching(Criteria $criteria): ReadableCollection&Selectable + { + $this->initialize(); + assert($this->collection instanceof Selectable); + + return $this->collection->matching($criteria); + } + + protected function doInitialize(): void + { + $elements = $this->entityPersister->loadCriteria($this->criteria); + $this->collection = new ArrayCollection($elements); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AnsiQuoteStrategy.php b/vendor/doctrine/orm/src/Mapping/AnsiQuoteStrategy.php new file mode 100644 index 0000000..872d4d6 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AnsiQuoteStrategy.php @@ -0,0 +1,76 @@ +fieldMappings[$fieldName]->columnName; + } + + public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string + { + return $class->table['name']; + } + + /** + * {@inheritDoc} + */ + public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string + { + return $definition['sequenceName']; + } + + public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string + { + return $joinColumn->name; + } + + public function getReferencedJoinColumnName( + JoinColumnMapping $joinColumn, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + return $joinColumn->referencedColumnName; + } + + public function getJoinTableName( + ManyToManyOwningSideMapping $association, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + return $association->joinTable->name; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array + { + return $class->identifier; + } + + public function getColumnAlias( + string $columnName, + int $counter, + AbstractPlatform $platform, + ClassMetadata|null $class = null, + ): string { + return $this->getSQLResultCasing($platform, $columnName . '_' . $counter); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ArrayAccessImplementation.php b/vendor/doctrine/orm/src/Mapping/ArrayAccessImplementation.php new file mode 100644 index 0000000..3fd0988 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ArrayAccessImplementation.php @@ -0,0 +1,70 @@ +$offset); + } + + /** @param string $offset */ + public function offsetGet(mixed $offset): mixed + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11211', + 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', + static::class, + ); + + if (! property_exists($this, $offset)) { + throw new InvalidArgumentException('Undefined property: ' . $offset); + } + + return $this->$offset; + } + + /** @param string $offset */ + public function offsetSet(mixed $offset, mixed $value): void + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11211', + 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', + static::class, + ); + + $this->$offset = $value; + } + + /** @param string $offset */ + public function offsetUnset(mixed $offset): void + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11211', + 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', + static::class, + ); + + $this->$offset = null; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AssociationMapping.php b/vendor/doctrine/orm/src/Mapping/AssociationMapping.php new file mode 100644 index 0000000..ce7bdb4 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AssociationMapping.php @@ -0,0 +1,359 @@ + */ +abstract class AssociationMapping implements ArrayAccess +{ + /** + * The names of persistence operations to cascade on the association. + * + * @var list<'persist'|'remove'|'detach'|'refresh'|'all'> + */ + public array $cascade = []; + + /** + * The fetching strategy to use for the association, usually defaults to FETCH_LAZY. + * + * @var ClassMetadata::FETCH_*|null + */ + public int|null $fetch = null; + + /** + * This is set when the association is inherited by this class from another + * (inheritance) parent entity class. The value is the FQCN of the + * topmost entity class that contains this association. (If there are + * transient classes in the class hierarchy, these are ignored, so the + * class property may in fact come from a class further up in the PHP class + * hierarchy.) To-many associations initially declared in mapped + * superclasses are not considered 'inherited' in the nearest + * entity subclasses. + * + * @var class-string|null + */ + public string|null $inherited = null; + + /** + * This is set when the association does not appear in the current class + * for the first time, but is initially declared in another parent + * entity or mapped superclass. The value is the FQCN of the + * topmost non-transient class that contains association information for + * this relationship. + * + * @var class-string|null + */ + public string|null $declared = null; + + public array|null $cache = null; + + public bool|null $id = null; + + public bool|null $isOnDeleteCascade = null; + + /** @var class-string|null */ + public string|null $originalClass = null; + + public string|null $originalField = null; + + public bool $orphanRemoval = false; + + public bool|null $unique = null; + + /** + * @param string $fieldName The name of the field in the entity + * the association is mapped to. + * @param class-string $sourceEntity The class name of the source entity. + * In the case of to-many associations + * initially present in mapped + * superclasses, the nearest + * entity subclasses will be + * considered the respective source + * entities. + * @param class-string $targetEntity The class name of the target entity. + * If it is fully-qualified it is used as + * is. If it is a simple, unqualified + * class name the namespace is assumed to + * be the same as the namespace of the + * source entity. + */ + final public function __construct( + public readonly string $fieldName, + public string $sourceEntity, + public readonly string $targetEntity, + ) { + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): static + { + unset($mappingArray['isOwningSide'], $mappingArray['type']); + $mapping = new static( + $mappingArray['fieldName'], + $mappingArray['sourceEntity'], + $mappingArray['targetEntity'], + ); + unset($mappingArray['fieldName'], $mappingArray['sourceEntity'], $mappingArray['targetEntity']); + + foreach ($mappingArray as $key => $value) { + if ($key === 'joinTable') { + assert($mapping instanceof ManyToManyAssociationMapping); + + if ($value === [] || $value === null) { + continue; + } + + assert($mapping instanceof ManyToManyOwningSideMapping); + + $mapping->joinTable = JoinTableMapping::fromMappingArray($value); + + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value; + } else { + throw new OutOfRangeException('Unknown property ' . $key . ' on class ' . static::class); + } + } + + return $mapping; + } + + /** + * @psalm-assert-if-true OwningSideMapping $this + * @psalm-assert-if-false InverseSideMapping $this + */ + final public function isOwningSide(): bool + { + return $this instanceof OwningSideMapping; + } + + /** @psalm-assert-if-true ToOneAssociationMapping $this */ + final public function isToOne(): bool + { + return $this instanceof ToOneAssociationMapping; + } + + /** @psalm-assert-if-true ToManyAssociationMapping $this */ + final public function isToMany(): bool + { + return $this instanceof ToManyAssociationMapping; + } + + /** @psalm-assert-if-true OneToOneOwningSideMapping $this */ + final public function isOneToOneOwningSide(): bool + { + return $this->isOneToOne() && $this->isOwningSide(); + } + + /** @psalm-assert-if-true OneToOneOwningSideMapping|ManyToOneAssociationMapping $this */ + final public function isToOneOwningSide(): bool + { + return $this->isToOne() && $this->isOwningSide(); + } + + /** @psalm-assert-if-true ManyToManyOwningSideMapping $this */ + final public function isManyToManyOwningSide(): bool + { + return $this instanceof ManyToManyOwningSideMapping; + } + + /** @psalm-assert-if-true OneToOneAssociationMapping $this */ + final public function isOneToOne(): bool + { + return $this instanceof OneToOneAssociationMapping; + } + + /** @psalm-assert-if-true OneToManyAssociationMapping $this */ + final public function isOneToMany(): bool + { + return $this instanceof OneToManyAssociationMapping; + } + + /** @psalm-assert-if-true ManyToOneAssociationMapping $this */ + final public function isManyToOne(): bool + { + return $this instanceof ManyToOneAssociationMapping; + } + + /** @psalm-assert-if-true ManyToManyAssociationMapping $this */ + final public function isManyToMany(): bool + { + return $this instanceof ManyToManyAssociationMapping; + } + + /** @psalm-assert-if-true ToManyAssociationMapping $this */ + final public function isOrdered(): bool + { + return $this->isToMany() && $this->orderBy() !== []; + } + + /** @psalm-assert-if-true ToManyAssociationMapping $this */ + public function isIndexed(): bool + { + return false; + } + + final public function type(): int + { + return match (true) { + $this instanceof OneToOneAssociationMapping => ClassMetadata::ONE_TO_ONE, + $this instanceof OneToManyAssociationMapping => ClassMetadata::ONE_TO_MANY, + $this instanceof ManyToOneAssociationMapping => ClassMetadata::MANY_TO_ONE, + $this instanceof ManyToManyAssociationMapping => ClassMetadata::MANY_TO_MANY, + default => throw new Exception('Cannot determine type for ' . static::class), + }; + } + + /** @param string $offset */ + public function offsetExists(mixed $offset): bool + { + return isset($this->$offset) || in_array($offset, ['isOwningSide', 'type'], true); + } + + final public function offsetGet(mixed $offset): mixed + { + return match ($offset) { + 'isOwningSide' => $this->isOwningSide(), + 'type' => $this->type(), + 'isCascadeRemove' => $this->isCascadeRemove(), + 'isCascadePersist' => $this->isCascadePersist(), + 'isCascadeRefresh' => $this->isCascadeRefresh(), + 'isCascadeDetach' => $this->isCascadeDetach(), + default => property_exists($this, $offset) ? $this->$offset : throw new OutOfRangeException(sprintf( + 'Unknown property "%s" on class %s', + $offset, + static::class, + )), + }; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + assert($offset !== null); + if (! property_exists($this, $offset)) { + throw new OutOfRangeException(sprintf( + 'Unknown property "%s" on class %s', + $offset, + static::class, + )); + } + + if ($offset === 'joinTable') { + $value = JoinTableMapping::fromMappingArray($value); + } + + $this->$offset = $value; + } + + /** @param string $offset */ + public function offsetUnset(mixed $offset): void + { + if (! property_exists($this, $offset)) { + throw new OutOfRangeException(sprintf( + 'Unknown property "%s" on class %s', + $offset, + static::class, + )); + } + + $this->$offset = null; + } + + final public function isCascadeRemove(): bool + { + return in_array('remove', $this->cascade, true); + } + + final public function isCascadePersist(): bool + { + return in_array('persist', $this->cascade, true); + } + + final public function isCascadeRefresh(): bool + { + return in_array('refresh', $this->cascade, true); + } + + final public function isCascadeDetach(): bool + { + return in_array('detach', $this->cascade, true); + } + + /** @return array */ + public function toArray(): array + { + $array = (array) $this; + + $array['isOwningSide'] = $this->isOwningSide(); + $array['type'] = $this->type(); + + return $array; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['fieldName', 'sourceEntity', 'targetEntity']; + + if (count($this->cascade) > 0) { + $serialized[] = 'cascade'; + } + + foreach ( + [ + 'fetch', + 'inherited', + 'declared', + 'cache', + 'originalClass', + 'originalField', + ] as $stringOrArrayProperty + ) { + if ($this->$stringOrArrayProperty !== null) { + $serialized[] = $stringOrArrayProperty; + } + } + + foreach (['id', 'orphanRemoval', 'isOnDeleteCascade', 'unique'] as $boolProperty) { + if ($this->$boolProperty) { + $serialized[] = $boolProperty; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AssociationOverride.php b/vendor/doctrine/orm/src/Mapping/AssociationOverride.php new file mode 100644 index 0000000..e0ebc07 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AssociationOverride.php @@ -0,0 +1,51 @@ +|null + */ + public readonly array|null $joinColumns; + + /** + * The join column that is being mapped to the persistent attribute. + * + * @var array|null + */ + public readonly array|null $inverseJoinColumns; + + /** + * @param string $name The name of the relationship property whose mapping is being overridden. + * @param JoinColumn|array $joinColumns + * @param JoinColumn|array $inverseJoinColumns + * @param JoinTable|null $joinTable The join table that maps the relationship. + * @param string|null $inversedBy The name of the association-field on the inverse-side. + * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY'|null $fetch + */ + public function __construct( + public readonly string $name, + array|JoinColumn|null $joinColumns = null, + array|JoinColumn|null $inverseJoinColumns = null, + public readonly JoinTable|null $joinTable = null, + public readonly string|null $inversedBy = null, + public readonly string|null $fetch = null, + ) { + if ($joinColumns instanceof JoinColumn) { + $joinColumns = [$joinColumns]; + } + + if ($inverseJoinColumns instanceof JoinColumn) { + $inverseJoinColumns = [$inverseJoinColumns]; + } + + $this->joinColumns = $joinColumns; + $this->inverseJoinColumns = $inverseJoinColumns; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AssociationOverrides.php b/vendor/doctrine/orm/src/Mapping/AssociationOverrides.php new file mode 100644 index 0000000..9fc6807 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AssociationOverrides.php @@ -0,0 +1,38 @@ + + */ + public readonly array $overrides; + + /** @param array|AssociationOverride $overrides */ + public function __construct(array|AssociationOverride $overrides) + { + if (! is_array($overrides)) { + $overrides = [$overrides]; + } + + foreach ($overrides as $override) { + if (! ($override instanceof AssociationOverride)) { + throw MappingException::invalidOverrideType('AssociationOverride', $override); + } + } + + $this->overrides = array_values($overrides); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AttributeOverride.php b/vendor/doctrine/orm/src/Mapping/AttributeOverride.php new file mode 100644 index 0000000..8f0e70c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AttributeOverride.php @@ -0,0 +1,15 @@ + + */ + public readonly array $overrides; + + /** @param array|AttributeOverride $overrides */ + public function __construct(array|AttributeOverride $overrides) + { + if (! is_array($overrides)) { + $overrides = [$overrides]; + } + + foreach ($overrides as $override) { + if (! ($override instanceof AttributeOverride)) { + throw MappingException::invalidOverrideType('AttributeOverride', $override); + } + } + + $this->overrides = array_values($overrides); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/AssociationBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/AssociationBuilder.php new file mode 100644 index 0000000..ea9e13c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/AssociationBuilder.php @@ -0,0 +1,171 @@ +mapping['mappedBy'] = $fieldName; + + return $this; + } + + /** @return $this */ + public function inversedBy(string $fieldName): static + { + $this->mapping['inversedBy'] = $fieldName; + + return $this; + } + + /** @return $this */ + public function cascadeAll(): static + { + $this->mapping['cascade'] = ['ALL']; + + return $this; + } + + /** @return $this */ + public function cascadePersist(): static + { + $this->mapping['cascade'][] = 'persist'; + + return $this; + } + + /** @return $this */ + public function cascadeRemove(): static + { + $this->mapping['cascade'][] = 'remove'; + + return $this; + } + + /** @return $this */ + public function cascadeDetach(): static + { + $this->mapping['cascade'][] = 'detach'; + + return $this; + } + + /** @return $this */ + public function cascadeRefresh(): static + { + $this->mapping['cascade'][] = 'refresh'; + + return $this; + } + + /** @return $this */ + public function fetchExtraLazy(): static + { + $this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; + + return $this; + } + + /** @return $this */ + public function fetchEager(): static + { + $this->mapping['fetch'] = ClassMetadata::FETCH_EAGER; + + return $this; + } + + /** @return $this */ + public function fetchLazy(): static + { + $this->mapping['fetch'] = ClassMetadata::FETCH_LAZY; + + return $this; + } + + /** + * Add Join Columns. + * + * @return $this + */ + public function addJoinColumn( + string $columnName, + string $referencedColumnName, + bool $nullable = true, + bool $unique = false, + string|null $onDelete = null, + string|null $columnDef = null, + ): static { + $this->joinColumns[] = [ + 'name' => $columnName, + 'referencedColumnName' => $referencedColumnName, + 'nullable' => $nullable, + 'unique' => $unique, + 'onDelete' => $onDelete, + 'columnDefinition' => $columnDef, + ]; + + return $this; + } + + /** + * Sets field as primary key. + * + * @return $this + */ + public function makePrimaryKey(): static + { + $this->mapping['id'] = true; + + return $this; + } + + /** + * Removes orphan entities when detached from their parent. + * + * @return $this + */ + public function orphanRemoval(): static + { + $this->mapping['orphanRemoval'] = true; + + return $this; + } + + /** @throws InvalidArgumentException */ + public function build(): ClassMetadataBuilder + { + $mapping = $this->mapping; + if ($this->joinColumns) { + $mapping['joinColumns'] = $this->joinColumns; + } + + $cm = $this->builder->getClassMetadata(); + if ($this->type === ClassMetadata::MANY_TO_ONE) { + $cm->mapManyToOne($mapping); + } elseif ($this->type === ClassMetadata::ONE_TO_ONE) { + $cm->mapOneToOne($mapping); + } else { + throw new InvalidArgumentException('Type should be a ToOne Association here'); + } + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/ClassMetadataBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/ClassMetadataBuilder.php new file mode 100644 index 0000000..b9d3cc8 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/ClassMetadataBuilder.php @@ -0,0 +1,426 @@ +cm; + } + + /** + * Marks the class as mapped superclass. + * + * @return $this + */ + public function setMappedSuperClass(): static + { + $this->cm->isMappedSuperclass = true; + $this->cm->isEmbeddedClass = false; + + return $this; + } + + /** + * Marks the class as embeddable. + * + * @return $this + */ + public function setEmbeddable(): static + { + $this->cm->isEmbeddedClass = true; + $this->cm->isMappedSuperclass = false; + + return $this; + } + + /** + * Adds and embedded class + * + * @param class-string $class + * + * @return $this + */ + public function addEmbedded(string $fieldName, string $class, string|false|null $columnPrefix = null): static + { + $this->cm->mapEmbedded( + [ + 'fieldName' => $fieldName, + 'class' => $class, + 'columnPrefix' => $columnPrefix, + ], + ); + + return $this; + } + + /** + * Sets custom Repository class name. + * + * @return $this + */ + public function setCustomRepositoryClass(string $repositoryClassName): static + { + $this->cm->setCustomRepositoryClass($repositoryClassName); + + return $this; + } + + /** + * Marks class read only. + * + * @return $this + */ + public function setReadOnly(): static + { + $this->cm->markReadOnly(); + + return $this; + } + + /** + * Sets the table name. + * + * @return $this + */ + public function setTable(string $name): static + { + $this->cm->setPrimaryTable(['name' => $name]); + + return $this; + } + + /** + * Adds Index. + * + * @psalm-param list $columns + * + * @return $this + */ + public function addIndex(array $columns, string $name): static + { + if (! isset($this->cm->table['indexes'])) { + $this->cm->table['indexes'] = []; + } + + $this->cm->table['indexes'][$name] = ['columns' => $columns]; + + return $this; + } + + /** + * Adds Unique Constraint. + * + * @psalm-param list $columns + * + * @return $this + */ + public function addUniqueConstraint(array $columns, string $name): static + { + if (! isset($this->cm->table['uniqueConstraints'])) { + $this->cm->table['uniqueConstraints'] = []; + } + + $this->cm->table['uniqueConstraints'][$name] = ['columns' => $columns]; + + return $this; + } + + /** + * Sets class as root of a joined table inheritance hierarchy. + * + * @return $this + */ + public function setJoinedTableInheritance(): static + { + $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED); + + return $this; + } + + /** + * Sets class as root of a single table inheritance hierarchy. + * + * @return $this + */ + public function setSingleTableInheritance(): static + { + $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); + + return $this; + } + + /** + * Sets the discriminator column details. + * + * @psalm-param class-string|null $enumType + * @psalm-param array $options + * + * @return $this + */ + public function setDiscriminatorColumn( + string $name, + string $type = 'string', + int $length = 255, + string|null $columnDefinition = null, + string|null $enumType = null, + array $options = [], + ): static { + $this->cm->setDiscriminatorColumn( + [ + 'name' => $name, + 'type' => $type, + 'length' => $length, + 'columnDefinition' => $columnDefinition, + 'enumType' => $enumType, + 'options' => $options, + ], + ); + + return $this; + } + + /** + * Adds a subclass to this inheritance hierarchy. + * + * @return $this + */ + public function addDiscriminatorMapClass(string $name, string $class): static + { + $this->cm->addDiscriminatorMapClass($name, $class); + + return $this; + } + + /** + * Sets deferred explicit change tracking policy. + * + * @return $this + */ + public function setChangeTrackingPolicyDeferredExplicit(): static + { + $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT); + + return $this; + } + + /** + * Adds lifecycle event. + * + * @return $this + */ + public function addLifecycleEvent(string $methodName, string $event): static + { + $this->cm->addLifecycleCallback($methodName, $event); + + return $this; + } + + /** + * Adds Field. + * + * @psalm-param array $mapping + * + * @return $this + */ + public function addField(string $name, string $type, array $mapping = []): static + { + $mapping['fieldName'] = $name; + $mapping['type'] = $type; + + $this->cm->mapField($mapping); + + return $this; + } + + /** + * Creates a field builder. + */ + public function createField(string $name, string $type): FieldBuilder + { + return new FieldBuilder( + $this, + [ + 'fieldName' => $name, + 'type' => $type, + ], + ); + } + + /** + * Creates an embedded builder. + */ + public function createEmbedded(string $fieldName, string $class): EmbeddedBuilder + { + return new EmbeddedBuilder( + $this, + [ + 'fieldName' => $fieldName, + 'class' => $class, + 'columnPrefix' => null, + ], + ); + } + + /** + * Adds a simple many to one association, optionally with the inversed by field. + */ + public function addManyToOne( + string $name, + string $targetEntity, + string|null $inversedBy = null, + ): ClassMetadataBuilder { + $builder = $this->createManyToOne($name, $targetEntity); + + if ($inversedBy !== null) { + $builder->inversedBy($inversedBy); + } + + return $builder->build(); + } + + /** + * Creates a ManyToOne Association Builder. + * + * Note: This method does not add the association, you have to call build() on the AssociationBuilder. + */ + public function createManyToOne(string $name, string $targetEntity): AssociationBuilder + { + return new AssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::MANY_TO_ONE, + ); + } + + /** + * Creates a OneToOne Association Builder. + */ + public function createOneToOne(string $name, string $targetEntity): AssociationBuilder + { + return new AssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::ONE_TO_ONE, + ); + } + + /** + * Adds simple inverse one-to-one association. + */ + public function addInverseOneToOne(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder + { + $builder = $this->createOneToOne($name, $targetEntity); + $builder->mappedBy($mappedBy); + + return $builder->build(); + } + + /** + * Adds simple owning one-to-one association. + */ + public function addOwningOneToOne( + string $name, + string $targetEntity, + string|null $inversedBy = null, + ): ClassMetadataBuilder { + $builder = $this->createOneToOne($name, $targetEntity); + + if ($inversedBy !== null) { + $builder->inversedBy($inversedBy); + } + + return $builder->build(); + } + + /** + * Creates a ManyToMany Association Builder. + */ + public function createManyToMany(string $name, string $targetEntity): ManyToManyAssociationBuilder + { + return new ManyToManyAssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::MANY_TO_MANY, + ); + } + + /** + * Adds a simple owning many to many association. + */ + public function addOwningManyToMany( + string $name, + string $targetEntity, + string|null $inversedBy = null, + ): ClassMetadataBuilder { + $builder = $this->createManyToMany($name, $targetEntity); + + if ($inversedBy !== null) { + $builder->inversedBy($inversedBy); + } + + return $builder->build(); + } + + /** + * Adds a simple inverse many to many association. + */ + public function addInverseManyToMany(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder + { + $builder = $this->createManyToMany($name, $targetEntity); + $builder->mappedBy($mappedBy); + + return $builder->build(); + } + + /** + * Creates a one to many association builder. + */ + public function createOneToMany(string $name, string $targetEntity): OneToManyAssociationBuilder + { + return new OneToManyAssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::ONE_TO_MANY, + ); + } + + /** + * Adds simple OneToMany association. + */ + public function addOneToMany(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder + { + $builder = $this->createOneToMany($name, $targetEntity); + $builder->mappedBy($mappedBy); + + return $builder->build(); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/EmbeddedBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/EmbeddedBuilder.php new file mode 100644 index 0000000..b9d2127 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/EmbeddedBuilder.php @@ -0,0 +1,46 @@ +mapping['columnPrefix'] = $columnPrefix; + + return $this; + } + + /** + * Finalizes this embeddable and attach it to the ClassMetadata. + * + * Without this call an EmbeddedBuilder has no effect on the ClassMetadata. + */ + public function build(): ClassMetadataBuilder + { + $cm = $this->builder->getClassMetadata(); + + $cm->mapEmbedded($this->mapping); + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/EntityListenerBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/EntityListenerBuilder.php new file mode 100644 index 0000000..a0b14b9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/EntityListenerBuilder.php @@ -0,0 +1,55 @@ + true, + Events::postRemove => true, + Events::prePersist => true, + Events::postPersist => true, + Events::preUpdate => true, + Events::postUpdate => true, + Events::postLoad => true, + Events::preFlush => true, + ]; + + /** + * Lookup the entity class to find methods that match to event lifecycle names + * + * @param ClassMetadata $metadata The entity metadata. + * @param string $className The listener class name. + * + * @throws MappingException When the listener class not found. + */ + public static function bindEntityListener(ClassMetadata $metadata, string $className): void + { + $class = $metadata->fullyQualifiedClassName($className); + + if (! class_exists($class)) { + throw MappingException::entityListenerClassNotFound($class, $className); + } + + foreach (get_class_methods($class) as $method) { + if (! isset(self::EVENTS[$method])) { + continue; + } + + $metadata->addEntityListener($method, $class, $method); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/FieldBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/FieldBuilder.php new file mode 100644 index 0000000..8326ff5 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/FieldBuilder.php @@ -0,0 +1,243 @@ +mapping['length'] = $length; + + return $this; + } + + /** + * Sets nullable. + * + * @return $this + */ + public function nullable(bool $flag = true): static + { + $this->mapping['nullable'] = $flag; + + return $this; + } + + /** + * Sets Unique. + * + * @return $this + */ + public function unique(bool $flag = true): static + { + $this->mapping['unique'] = $flag; + + return $this; + } + + /** + * Sets column name. + * + * @return $this + */ + public function columnName(string $name): static + { + $this->mapping['columnName'] = $name; + + return $this; + } + + /** + * Sets Precision. + * + * @return $this + */ + public function precision(int $p): static + { + $this->mapping['precision'] = $p; + + return $this; + } + + /** + * Sets insertable. + * + * @return $this + */ + public function insertable(bool $flag = true): self + { + if (! $flag) { + $this->mapping['notInsertable'] = true; + } + + return $this; + } + + /** + * Sets updatable. + * + * @return $this + */ + public function updatable(bool $flag = true): self + { + if (! $flag) { + $this->mapping['notUpdatable'] = true; + } + + return $this; + } + + /** + * Sets scale. + * + * @return $this + */ + public function scale(int $s): static + { + $this->mapping['scale'] = $s; + + return $this; + } + + /** + * Sets field as primary key. + * + * @return $this + */ + public function makePrimaryKey(): static + { + $this->mapping['id'] = true; + + return $this; + } + + /** + * Sets an option. + * + * @return $this + */ + public function option(string $name, mixed $value): static + { + $this->mapping['options'][$name] = $value; + + return $this; + } + + /** @return $this */ + public function generatedValue(string $strategy = 'AUTO'): static + { + $this->generatedValue = $strategy; + + return $this; + } + + /** + * Sets field versioned. + * + * @return $this + */ + public function isVersionField(): static + { + $this->version = true; + + return $this; + } + + /** + * Sets Sequence Generator. + * + * @return $this + */ + public function setSequenceGenerator(string $sequenceName, int $allocationSize = 1, int $initialValue = 1): static + { + $this->sequenceDef = [ + 'sequenceName' => $sequenceName, + 'allocationSize' => $allocationSize, + 'initialValue' => $initialValue, + ]; + + return $this; + } + + /** + * Sets column definition. + * + * @return $this + */ + public function columnDefinition(string $def): static + { + $this->mapping['columnDefinition'] = $def; + + return $this; + } + + /** + * Set the FQCN of the custom ID generator. + * This class must extend \Doctrine\ORM\Id\AbstractIdGenerator. + * + * @return $this + */ + public function setCustomIdGenerator(string $customIdGenerator): static + { + $this->customIdGenerator = $customIdGenerator; + + return $this; + } + + /** + * Finalizes this field and attach it to the ClassMetadata. + * + * Without this call a FieldBuilder has no effect on the ClassMetadata. + */ + public function build(): ClassMetadataBuilder + { + $cm = $this->builder->getClassMetadata(); + if ($this->generatedValue) { + $cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue)); + } + + if ($this->version) { + $cm->setVersionMapping($this->mapping); + } + + $cm->mapField($this->mapping); + if ($this->sequenceDef) { + $cm->setSequenceGeneratorDefinition($this->sequenceDef); + } + + if ($this->customIdGenerator) { + $cm->setCustomGeneratorDefinition(['class' => $this->customIdGenerator]); + } + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/ManyToManyAssociationBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/ManyToManyAssociationBuilder.php new file mode 100644 index 0000000..b83a8ba --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/ManyToManyAssociationBuilder.php @@ -0,0 +1,73 @@ +joinTableName = $name; + + return $this; + } + + /** + * Adds Inverse Join Columns. + * + * @return $this + */ + public function addInverseJoinColumn( + string $columnName, + string $referencedColumnName, + bool $nullable = true, + bool $unique = false, + string|null $onDelete = null, + string|null $columnDef = null, + ): static { + $this->inverseJoinColumns[] = [ + 'name' => $columnName, + 'referencedColumnName' => $referencedColumnName, + 'nullable' => $nullable, + 'unique' => $unique, + 'onDelete' => $onDelete, + 'columnDefinition' => $columnDef, + ]; + + return $this; + } + + public function build(): ClassMetadataBuilder + { + $mapping = $this->mapping; + $mapping['joinTable'] = []; + if ($this->joinColumns) { + $mapping['joinTable']['joinColumns'] = $this->joinColumns; + } + + if ($this->inverseJoinColumns) { + $mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns; + } + + if ($this->joinTableName) { + $mapping['joinTable']['name'] = $this->joinTableName; + } + + $cm = $this->builder->getClassMetadata(); + $cm->mapManyToMany($mapping); + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/OneToManyAssociationBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/OneToManyAssociationBuilder.php new file mode 100644 index 0000000..077c558 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/OneToManyAssociationBuilder.php @@ -0,0 +1,46 @@ + $fieldNames + * + * @return $this + */ + public function setOrderBy(array $fieldNames): static + { + $this->mapping['orderBy'] = $fieldNames; + + return $this; + } + + /** @return $this */ + public function setIndexBy(string $fieldName): static + { + $this->mapping['indexBy'] = $fieldName; + + return $this; + } + + public function build(): ClassMetadataBuilder + { + $mapping = $this->mapping; + if ($this->joinColumns) { + $mapping['joinColumns'] = $this->joinColumns; + } + + $cm = $this->builder->getClassMetadata(); + $cm->mapOneToMany($mapping); + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Cache.php b/vendor/doctrine/orm/src/Mapping/Cache.php new file mode 100644 index 0000000..3161ab3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Cache.php @@ -0,0 +1,19 @@ + $typedFieldMappers */ + private readonly array $typedFieldMappers; + + public function __construct(TypedFieldMapper ...$typedFieldMappers) + { + self::validateVariadicParameter($typedFieldMappers); + + $this->typedFieldMappers = $typedFieldMappers; + } + + /** + * {@inheritDoc} + */ + public function validateAndComplete(array $mapping, ReflectionProperty $field): array + { + foreach ($this->typedFieldMappers as $typedFieldMapper) { + $mapping = $typedFieldMapper->validateAndComplete($mapping, $field); + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ChangeTrackingPolicy.php b/vendor/doctrine/orm/src/Mapping/ChangeTrackingPolicy.php new file mode 100644 index 0000000..7181d9f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ChangeTrackingPolicy.php @@ -0,0 +1,17 @@ +ClassMetadata instance holds all the object-relational mapping metadata + * of an entity and its associations. + * + * Once populated, ClassMetadata instances are usually cached in a serialized form. + * + * IMPORTANT NOTE: + * + * The fields of this class are only public for 2 reasons: + * 1) To allow fast READ access. + * 2) To drastically reduce the size of a serialized instance (private/protected members + * get the whole class name, namespace inclusive, prepended to every property in + * the serialized representation). + * + * @psalm-type ConcreteAssociationMapping = OneToOneOwningSideMapping|OneToOneInverseSideMapping|ManyToOneAssociationMapping|OneToManyAssociationMapping|ManyToManyOwningSideMapping|ManyToManyInverseSideMapping + * @template-covariant T of object + * @template-implements PersistenceClassMetadata + */ +class ClassMetadata implements PersistenceClassMetadata, Stringable +{ + /* The inheritance mapping types */ + /** + * NONE means the class does not participate in an inheritance hierarchy + * and therefore does not need an inheritance mapping type. + */ + public const INHERITANCE_TYPE_NONE = 1; + + /** + * JOINED means the class will be persisted according to the rules of + * Class Table Inheritance. + */ + public const INHERITANCE_TYPE_JOINED = 2; + + /** + * SINGLE_TABLE means the class will be persisted according to the rules of + * Single Table Inheritance. + */ + public const INHERITANCE_TYPE_SINGLE_TABLE = 3; + + /* The Id generator types. */ + /** + * AUTO means the generator type will depend on what the used platform prefers. + * Offers full portability. + */ + public const GENERATOR_TYPE_AUTO = 1; + + /** + * SEQUENCE means a separate sequence object will be used. Platforms that do + * not have native sequence support may emulate it. Full portability is currently + * not guaranteed. + */ + public const GENERATOR_TYPE_SEQUENCE = 2; + + /** + * IDENTITY means an identity column is used for id generation. The database + * will fill in the id column on insertion. Platforms that do not support + * native identity columns may emulate them. Full portability is currently + * not guaranteed. + */ + public const GENERATOR_TYPE_IDENTITY = 4; + + /** + * NONE means the class does not have a generated id. That means the class + * must have a natural, manually assigned id. + */ + public const GENERATOR_TYPE_NONE = 5; + + /** + * CUSTOM means that customer will use own ID generator that supposedly work + */ + public const GENERATOR_TYPE_CUSTOM = 7; + + /** + * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time + * by doing a property-by-property comparison with the original data. This will + * be done for all entities that are in MANAGED state at commit-time. + * + * This is the default change tracking policy. + */ + public const CHANGETRACKING_DEFERRED_IMPLICIT = 1; + + /** + * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time + * by doing a property-by-property comparison with the original data. This will + * be done only for entities that were explicitly saved (through persist() or a cascade). + */ + public const CHANGETRACKING_DEFERRED_EXPLICIT = 2; + + /** + * Specifies that an association is to be fetched when it is first accessed. + */ + public const FETCH_LAZY = 2; + + /** + * Specifies that an association is to be fetched when the owner of the + * association is fetched. + */ + public const FETCH_EAGER = 3; + + /** + * Specifies that an association is to be fetched lazy (on first access) and that + * commands such as Collection#count, Collection#slice are issued directly against + * the database if the collection is not yet initialized. + */ + public const FETCH_EXTRA_LAZY = 4; + + /** + * Identifies a one-to-one association. + */ + public const ONE_TO_ONE = 1; + + /** + * Identifies a many-to-one association. + */ + public const MANY_TO_ONE = 2; + + /** + * Identifies a one-to-many association. + */ + public const ONE_TO_MANY = 4; + + /** + * Identifies a many-to-many association. + */ + public const MANY_TO_MANY = 8; + + /** + * Combined bitmask for to-one (single-valued) associations. + */ + public const TO_ONE = 3; + + /** + * Combined bitmask for to-many (collection-valued) associations. + */ + public const TO_MANY = 12; + + /** + * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks, + */ + public const CACHE_USAGE_READ_ONLY = 1; + + /** + * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes. + */ + public const CACHE_USAGE_NONSTRICT_READ_WRITE = 2; + + /** + * Read Write Attempts to lock the entity before update/delete. + */ + public const CACHE_USAGE_READ_WRITE = 3; + + /** + * The value of this column is never generated by the database. + */ + public const GENERATED_NEVER = 0; + + /** + * The value of this column is generated by the database on INSERT, but not on UPDATE. + */ + public const GENERATED_INSERT = 1; + + /** + * The value of this column is generated by the database on both INSERT and UDPATE statements. + */ + public const GENERATED_ALWAYS = 2; + + /** + * READ-ONLY: The namespace the entity class is contained in. + * + * @todo Not really needed. Usage could be localized. + */ + public string|null $namespace = null; + + /** + * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance + * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same + * as {@link $name}. + * + * @psalm-var class-string + */ + public string $rootEntityName; + + /** + * READ-ONLY: The definition of custom generator. Only used for CUSTOM + * generator type + * + * The definition has the following structure: + * + * array( + * 'class' => 'ClassName', + * ) + * + * + * @todo Merge with tableGeneratorDefinition into generic generatorDefinition + * @var array|null + */ + public array|null $customGeneratorDefinition = null; + + /** + * The name of the custom repository class used for the entity class. + * (Optional). + * + * @psalm-var ?class-string + */ + public string|null $customRepositoryClassName = null; + + /** + * READ-ONLY: Whether this class describes the mapping of a mapped superclass. + */ + public bool $isMappedSuperclass = false; + + /** + * READ-ONLY: Whether this class describes the mapping of an embeddable class. + */ + public bool $isEmbeddedClass = false; + + /** + * READ-ONLY: The names of the parent entity classes (ancestors), starting with the + * nearest one and ending with the root entity class. + * + * @psalm-var list + */ + public array $parentClasses = []; + + /** + * READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all + * entity subclasses of this class. These may also be abstract classes. + * + * This list is used, for example, to enumerate all necessary tables in JTI when querying for root + * or subclass entities, or to gather all fields comprised in an entity inheritance tree. + * + * For classes that do not use STI/JTI, this list is empty. + * + * Implementation note: + * + * In PHP, there is no general way to discover all subclasses of a given class at runtime. For that + * reason, the list of classes given in the discriminator map at the root entity is considered + * authoritative. The discriminator map must contain all concrete classes that can + * appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract + * entity classes, users are not required to list such classes with a discriminator value. + * + * The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the + * root entity has been loaded. + * + * For subclasses of such root entities, the list can be reused/passed downwards, it only needs to + * be filtered accordingly (only keep remaining subclasses) + * + * @psalm-var list + */ + public array $subClasses = []; + + /** + * READ-ONLY: The names of all embedded classes based on properties. + * + * @psalm-var array + */ + public array $embeddedClasses = []; + + /** + * READ-ONLY: The field names of all fields that are part of the identifier/primary key + * of the mapped entity class. + * + * @psalm-var list + */ + public array $identifier = []; + + /** + * READ-ONLY: The inheritance mapping type used by the class. + * + * @psalm-var self::INHERITANCE_TYPE_* + */ + public int $inheritanceType = self::INHERITANCE_TYPE_NONE; + + /** + * READ-ONLY: The Id generator type used by the class. + * + * @psalm-var self::GENERATOR_TYPE_* + */ + public int $generatorType = self::GENERATOR_TYPE_NONE; + + /** + * READ-ONLY: The field mappings of the class. + * Keys are field names and values are FieldMapping instances + * + * @var array + */ + public array $fieldMappings = []; + + /** + * READ-ONLY: An array of field names. Used to look up field names from column names. + * Keys are column names and values are field names. + * + * @psalm-var array + */ + public array $fieldNames = []; + + /** + * READ-ONLY: A map of field names to column names. Keys are field names and values column names. + * Used to look up column names from field names. + * This is the reverse lookup map of $_fieldNames. + * + * @deprecated 3.0 Remove this. + * + * @var mixed[] + */ + public array $columnNames = []; + + /** + * READ-ONLY: The discriminator value of this class. + * + * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies + * where a discriminator column is used. + * + * @see discriminatorColumn + */ + public mixed $discriminatorValue = null; + + /** + * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. + * + * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies + * where a discriminator column is used. + * + * @see discriminatorColumn + * + * @var array + * + * @psalm-var array + */ + public array $discriminatorMap = []; + + /** + * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE + * inheritance mappings. + */ + public DiscriminatorColumnMapping|null $discriminatorColumn = null; + + /** + * READ-ONLY: The primary table definition. The definition is an array with the + * following entries: + * + * name => + * schema => + * indexes => array + * uniqueConstraints => array + * + * @var mixed[] + * @psalm-var array{ + * name: string, + * schema?: string, + * indexes?: array, + * uniqueConstraints?: array, + * options?: array, + * quoted?: bool + * } + */ + public array $table; + + /** + * READ-ONLY: The registered lifecycle callbacks for entities of this class. + * + * @psalm-var array> + */ + public array $lifecycleCallbacks = []; + + /** + * READ-ONLY: The registered entity listeners. + * + * @psalm-var array> + */ + public array $entityListeners = []; + + /** + * READ-ONLY: The association mappings of this class. + * + * A join table definition has the following structure: + *
+     * array(
+     *     'name' => ,
+     *      'joinColumns' => array(),
+     *      'inverseJoinColumns' => array()
+     * )
+     * 
+ * + * @psalm-var array + */ + public array $associationMappings = []; + + /** + * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite. + */ + public bool $isIdentifierComposite = false; + + /** + * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association. + * + * This flag is necessary because some code blocks require special treatment of this cases. + */ + public bool $containsForeignIdentifier = false; + + /** + * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one ENUM type. + * + * This flag is necessary because some code blocks require special treatment of this cases. + */ + public bool $containsEnumIdentifier = false; + + /** + * READ-ONLY: The ID generator used for generating IDs for this class. + * + * @todo Remove! + */ + public AbstractIdGenerator $idGenerator; + + /** + * READ-ONLY: The definition of the sequence generator of this class. Only used for the + * SEQUENCE generation strategy. + * + * The definition has the following structure: + * + * array( + * 'sequenceName' => 'name', + * 'allocationSize' => '20', + * 'initialValue' => '1' + * ) + * + * + * @var array|null + * @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}|null + * @todo Merge with tableGeneratorDefinition into generic generatorDefinition + */ + public array|null $sequenceGeneratorDefinition = null; + + /** + * READ-ONLY: The policy used for change-tracking on entities of this class. + */ + public int $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; + + /** + * READ-ONLY: A Flag indicating whether one or more columns of this class + * have to be reloaded after insert / update operations. + */ + public bool $requiresFetchAfterChange = false; + + /** + * READ-ONLY: A flag for whether or not instances of this class are to be versioned + * with optimistic locking. + */ + public bool $isVersioned = false; + + /** + * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any). + */ + public string|null $versionField = null; + + /** @var mixed[]|null */ + public array|null $cache = null; + + /** + * The ReflectionClass instance of the mapped class. + * + * @var ReflectionClass|null + */ + public ReflectionClass|null $reflClass = null; + + /** + * Is this entity marked as "read-only"? + * + * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance + * optimization for entities that are immutable, either in your domain or through the relation database + * (coming from a view, or a history table for example). + */ + public bool $isReadOnly = false; + + /** + * NamingStrategy determining the default column and table names. + */ + protected NamingStrategy $namingStrategy; + + /** + * The ReflectionProperty instances of the mapped class. + * + * @var array + */ + public array $reflFields = []; + + private InstantiatorInterface|null $instantiator = null; + + private readonly TypedFieldMapper $typedFieldMapper; + + /** + * Initializes a new ClassMetadata instance that will hold the object-relational mapping + * metadata of the class with the given name. + * + * @param string $name The name of the entity class the new instance is used for. + * @psalm-param class-string $name + */ + public function __construct(public string $name, NamingStrategy|null $namingStrategy = null, TypedFieldMapper|null $typedFieldMapper = null) + { + $this->rootEntityName = $name; + $this->namingStrategy = $namingStrategy ?? new DefaultNamingStrategy(); + $this->instantiator = new Instantiator(); + $this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper(); + } + + /** + * Gets the ReflectionProperties of the mapped class. + * + * @return ReflectionProperty[]|null[] An array of ReflectionProperty instances. + * @psalm-return array + */ + public function getReflectionProperties(): array + { + return $this->reflFields; + } + + /** + * Gets a ReflectionProperty for a specific field of the mapped class. + */ + public function getReflectionProperty(string $name): ReflectionProperty|null + { + return $this->reflFields[$name]; + } + + /** + * Gets the ReflectionProperty for the single identifier field. + * + * @throws BadMethodCallException If the class has a composite identifier. + */ + public function getSingleIdReflectionProperty(): ReflectionProperty|null + { + if ($this->isIdentifierComposite) { + throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.'); + } + + return $this->reflFields[$this->identifier[0]]; + } + + /** + * Extracts the identifier values of an entity of this class. + * + * For composite identifiers, the identifier values are returned as an array + * with the same order as the field order in {@link identifier}. + * + * @return array + */ + public function getIdentifierValues(object $entity): array + { + if ($this->isIdentifierComposite) { + $id = []; + + foreach ($this->identifier as $idField) { + $value = $this->reflFields[$idField]->getValue($entity); + + if ($value !== null) { + $id[$idField] = $value; + } + } + + return $id; + } + + $id = $this->identifier[0]; + $value = $this->reflFields[$id]->getValue($entity); + + if ($value === null) { + return []; + } + + return [$id => $value]; + } + + /** + * Populates the entity identifier of an entity. + * + * @psalm-param array $id + * + * @todo Rename to assignIdentifier() + */ + public function setIdentifierValues(object $entity, array $id): void + { + foreach ($id as $idField => $idValue) { + $this->reflFields[$idField]->setValue($entity, $idValue); + } + } + + /** + * Sets the specified field to the specified value on the given entity. + */ + public function setFieldValue(object $entity, string $field, mixed $value): void + { + $this->reflFields[$field]->setValue($entity, $value); + } + + /** + * Gets the specified field's value off the given entity. + */ + public function getFieldValue(object $entity, string $field): mixed + { + return $this->reflFields[$field]->getValue($entity); + } + + /** + * Creates a string representation of this instance. + * + * @return string The string representation of this instance. + * + * @todo Construct meaningful string representation. + */ + public function __toString(): string + { + return self::class . '@' . spl_object_id($this); + } + + /** + * Determines which fields get serialized. + * + * It is only serialized what is necessary for best unserialization performance. + * That means any metadata properties that are not set or empty or simply have + * their default value are NOT serialized. + * + * Parts that are also NOT serialized because they can not be properly unserialized: + * - reflClass (ReflectionClass) + * - reflFields (ReflectionProperty array) + * + * @return string[] The names of all the fields that should be serialized. + */ + public function __sleep(): array + { + // This metadata is always serialized/cached. + $serialized = [ + 'associationMappings', + 'columnNames', //TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName'] + 'fieldMappings', + 'fieldNames', + 'embeddedClasses', + 'identifier', + 'isIdentifierComposite', // TODO: REMOVE + 'name', + 'namespace', // TODO: REMOVE + 'table', + 'rootEntityName', + 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. + ]; + + // The rest of the metadata is only serialized if necessary. + if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) { + $serialized[] = 'changeTrackingPolicy'; + } + + if ($this->customRepositoryClassName) { + $serialized[] = 'customRepositoryClassName'; + } + + if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE) { + $serialized[] = 'inheritanceType'; + $serialized[] = 'discriminatorColumn'; + $serialized[] = 'discriminatorValue'; + $serialized[] = 'discriminatorMap'; + $serialized[] = 'parentClasses'; + $serialized[] = 'subClasses'; + } + + if ($this->generatorType !== self::GENERATOR_TYPE_NONE) { + $serialized[] = 'generatorType'; + if ($this->generatorType === self::GENERATOR_TYPE_SEQUENCE) { + $serialized[] = 'sequenceGeneratorDefinition'; + } + } + + if ($this->isMappedSuperclass) { + $serialized[] = 'isMappedSuperclass'; + } + + if ($this->isEmbeddedClass) { + $serialized[] = 'isEmbeddedClass'; + } + + if ($this->containsForeignIdentifier) { + $serialized[] = 'containsForeignIdentifier'; + } + + if ($this->containsEnumIdentifier) { + $serialized[] = 'containsEnumIdentifier'; + } + + if ($this->isVersioned) { + $serialized[] = 'isVersioned'; + $serialized[] = 'versionField'; + } + + if ($this->lifecycleCallbacks) { + $serialized[] = 'lifecycleCallbacks'; + } + + if ($this->entityListeners) { + $serialized[] = 'entityListeners'; + } + + if ($this->isReadOnly) { + $serialized[] = 'isReadOnly'; + } + + if ($this->customGeneratorDefinition) { + $serialized[] = 'customGeneratorDefinition'; + } + + if ($this->cache) { + $serialized[] = 'cache'; + } + + if ($this->requiresFetchAfterChange) { + $serialized[] = 'requiresFetchAfterChange'; + } + + return $serialized; + } + + /** + * Creates a new instance of the mapped class, without invoking the constructor. + */ + public function newInstance(): object + { + return $this->instantiator->instantiate($this->name); + } + + /** + * Restores some state that can not be serialized/unserialized. + */ + public function wakeupReflection(ReflectionService $reflService): void + { + // Restore ReflectionClass and properties + $this->reflClass = $reflService->getClass($this->name); + $this->instantiator = $this->instantiator ?: new Instantiator(); + + $parentReflFields = []; + + foreach ($this->embeddedClasses as $property => $embeddedClass) { + if (isset($embeddedClass->declaredField)) { + assert($embeddedClass->originalField !== null); + $childProperty = $this->getAccessibleProperty( + $reflService, + $this->embeddedClasses[$embeddedClass->declaredField]->class, + $embeddedClass->originalField, + ); + assert($childProperty !== null); + $parentReflFields[$property] = new ReflectionEmbeddedProperty( + $parentReflFields[$embeddedClass->declaredField], + $childProperty, + $this->embeddedClasses[$embeddedClass->declaredField]->class, + ); + + continue; + } + + $fieldRefl = $this->getAccessibleProperty( + $reflService, + $embeddedClass->declared ?? $this->name, + $property, + ); + + $parentReflFields[$property] = $fieldRefl; + $this->reflFields[$property] = $fieldRefl; + } + + foreach ($this->fieldMappings as $field => $mapping) { + if (isset($mapping->declaredField) && isset($parentReflFields[$mapping->declaredField])) { + assert($mapping->originalField !== null); + assert($mapping->originalClass !== null); + $childProperty = $this->getAccessibleProperty($reflService, $mapping->originalClass, $mapping->originalField); + assert($childProperty !== null); + + if (isset($mapping->enumType)) { + $childProperty = new EnumReflectionProperty( + $childProperty, + $mapping->enumType, + ); + } + + $this->reflFields[$field] = new ReflectionEmbeddedProperty( + $parentReflFields[$mapping->declaredField], + $childProperty, + $mapping->originalClass, + ); + continue; + } + + $this->reflFields[$field] = isset($mapping->declared) + ? $this->getAccessibleProperty($reflService, $mapping->declared, $field) + : $this->getAccessibleProperty($reflService, $this->name, $field); + + if (isset($mapping->enumType) && $this->reflFields[$field] !== null) { + $this->reflFields[$field] = new EnumReflectionProperty( + $this->reflFields[$field], + $mapping->enumType, + ); + } + } + + foreach ($this->associationMappings as $field => $mapping) { + $this->reflFields[$field] = isset($mapping->declared) + ? $this->getAccessibleProperty($reflService, $mapping->declared, $field) + : $this->getAccessibleProperty($reflService, $this->name, $field); + } + } + + /** + * Initializes a new ClassMetadata instance that will hold the object-relational mapping + * metadata of the class with the given name. + * + * @param ReflectionService $reflService The reflection service. + */ + public function initializeReflection(ReflectionService $reflService): void + { + $this->reflClass = $reflService->getClass($this->name); + $this->namespace = $reflService->getClassNamespace($this->name); + + if ($this->reflClass) { + $this->name = $this->rootEntityName = $this->reflClass->name; + } + + $this->table['name'] = $this->namingStrategy->classToTableName($this->name); + } + + /** + * Validates Identifier. + * + * @throws MappingException + */ + public function validateIdentifier(): void + { + if ($this->isMappedSuperclass || $this->isEmbeddedClass) { + return; + } + + // Verify & complete identifier mapping + if (! $this->identifier) { + throw MappingException::identifierRequired($this->name); + } + + if ($this->usesIdGenerator() && $this->isIdentifierComposite) { + throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name); + } + } + + /** + * Validates association targets actually exist. + * + * @throws MappingException + */ + public function validateAssociations(): void + { + foreach ($this->associationMappings as $mapping) { + if ( + ! class_exists($mapping->targetEntity) + && ! interface_exists($mapping->targetEntity) + && ! trait_exists($mapping->targetEntity) + ) { + throw MappingException::invalidTargetEntityClass($mapping->targetEntity, $this->name, $mapping->fieldName); + } + } + } + + /** + * Validates lifecycle callbacks. + * + * @throws MappingException + */ + public function validateLifecycleCallbacks(ReflectionService $reflService): void + { + foreach ($this->lifecycleCallbacks as $callbacks) { + foreach ($callbacks as $callbackFuncName) { + if (! $reflService->hasPublicMethod($this->name, $callbackFuncName)) { + throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName); + } + } + } + } + + /** + * {@inheritDoc} + * + * Can return null when using static reflection, in violation of the LSP + */ + public function getReflectionClass(): ReflectionClass|null + { + return $this->reflClass; + } + + /** @psalm-param array{usage?: mixed, region?: mixed} $cache */ + public function enableCache(array $cache): void + { + if (! isset($cache['usage'])) { + $cache['usage'] = self::CACHE_USAGE_READ_ONLY; + } + + if (! isset($cache['region'])) { + $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)); + } + + $this->cache = $cache; + } + + /** @psalm-param array{usage?: int, region?: string} $cache */ + public function enableAssociationCache(string $fieldName, array $cache): void + { + $this->associationMappings[$fieldName]->cache = $this->getAssociationCacheDefaults($fieldName, $cache); + } + + /** + * @psalm-param array{usage?: int, region?: string|null} $cache + * + * @return int[]|string[] + * @psalm-return array{usage: int, region: string|null} + */ + public function getAssociationCacheDefaults(string $fieldName, array $cache): array + { + if (! isset($cache['usage'])) { + $cache['usage'] = $this->cache['usage'] ?? self::CACHE_USAGE_READ_ONLY; + } + + if (! isset($cache['region'])) { + $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName; + } + + return $cache; + } + + /** + * Sets the change tracking policy used by this class. + */ + public function setChangeTrackingPolicy(int $policy): void + { + $this->changeTrackingPolicy = $policy; + } + + /** + * Whether the change tracking policy of this class is "deferred explicit". + */ + public function isChangeTrackingDeferredExplicit(): bool + { + return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT; + } + + /** + * Whether the change tracking policy of this class is "deferred implicit". + */ + public function isChangeTrackingDeferredImplicit(): bool + { + return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT; + } + + /** + * Checks whether a field is part of the identifier/primary key field(s). + */ + public function isIdentifier(string $fieldName): bool + { + if (! $this->identifier) { + return false; + } + + if (! $this->isIdentifierComposite) { + return $fieldName === $this->identifier[0]; + } + + return in_array($fieldName, $this->identifier, true); + } + + public function isUniqueField(string $fieldName): bool + { + $mapping = $this->getFieldMapping($fieldName); + + return $mapping !== false && isset($mapping->unique) && $mapping->unique; + } + + public function isNullable(string $fieldName): bool + { + $mapping = $this->getFieldMapping($fieldName); + + return $mapping !== false && isset($mapping->nullable) && $mapping->nullable; + } + + /** + * Gets a column name for a field name. + * If the column name for the field cannot be found, the given field name + * is returned. + */ + public function getColumnName(string $fieldName): string + { + return $this->columnNames[$fieldName] ?? $fieldName; + } + + /** + * Gets the mapping of a (regular) field that holds some data but not a + * reference to another object. + * + * @throws MappingException + */ + public function getFieldMapping(string $fieldName): FieldMapping + { + if (! isset($this->fieldMappings[$fieldName])) { + throw MappingException::mappingNotFound($this->name, $fieldName); + } + + return $this->fieldMappings[$fieldName]; + } + + /** + * Gets the mapping of an association. + * + * @see ClassMetadata::$associationMappings + * + * @param string $fieldName The field name that represents the association in + * the object model. + * + * @throws MappingException + */ + public function getAssociationMapping(string $fieldName): AssociationMapping + { + if (! isset($this->associationMappings[$fieldName])) { + throw MappingException::mappingNotFound($this->name, $fieldName); + } + + return $this->associationMappings[$fieldName]; + } + + /** + * Gets all association mappings of the class. + * + * @psalm-return array + */ + public function getAssociationMappings(): array + { + return $this->associationMappings; + } + + /** + * Gets the field name for a column name. + * If no field name can be found the column name is returned. + * + * @return string The column alias. + */ + public function getFieldName(string $columnName): string + { + return $this->fieldNames[$columnName] ?? $columnName; + } + + /** + * Checks whether given property has type + */ + private function isTypedProperty(string $name): bool + { + return isset($this->reflClass) + && $this->reflClass->hasProperty($name) + && $this->reflClass->getProperty($name)->hasType(); + } + + /** + * Validates & completes the given field mapping based on typed property. + * + * @param array{fieldName: string, type?: string} $mapping The field mapping to validate & complete. + * + * @return array{fieldName: string, enumType?: class-string, type?: string} The updated mapping. + */ + private function validateAndCompleteTypedFieldMapping(array $mapping): array + { + $field = $this->reflClass->getProperty($mapping['fieldName']); + + $mapping = $this->typedFieldMapper->validateAndComplete($mapping, $field); + + return $mapping; + } + + /** + * Validates & completes the basic mapping information based on typed property. + * + * @param array{type: self::ONE_TO_ONE|self::MANY_TO_ONE|self::ONE_TO_MANY|self::MANY_TO_MANY, fieldName: string, targetEntity?: class-string} $mapping The mapping. + * + * @return mixed[] The updated mapping. + */ + private function validateAndCompleteTypedAssociationMapping(array $mapping): array + { + $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); + + if ($type === null || ($mapping['type'] & self::TO_ONE) === 0) { + return $mapping; + } + + if (! isset($mapping['targetEntity']) && $type instanceof ReflectionNamedType) { + $mapping['targetEntity'] = $type->getName(); + } + + return $mapping; + } + + /** + * Validates & completes the given field mapping. + * + * @psalm-param array{ + * fieldName?: string, + * columnName?: string, + * id?: bool, + * generated?: self::GENERATED_*, + * enumType?: class-string, + * } $mapping The field mapping to validate & complete. + * + * @return FieldMapping The updated mapping. + * + * @throws MappingException + */ + protected function validateAndCompleteFieldMapping(array $mapping): FieldMapping + { + // Check mandatory fields + if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { + throw MappingException::missingFieldName($this->name); + } + + if ($this->isTypedProperty($mapping['fieldName'])) { + $mapping = $this->validateAndCompleteTypedFieldMapping($mapping); + } + + if (! isset($mapping['type'])) { + // Default to string + $mapping['type'] = 'string'; + } + + // Complete fieldName and columnName mapping + if (! isset($mapping['columnName'])) { + $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name); + } + + $mapping = FieldMapping::fromMappingArray($mapping); + + if ($mapping->columnName[0] === '`') { + $mapping->columnName = trim($mapping->columnName, '`'); + $mapping->quoted = true; + } + + $this->columnNames[$mapping->fieldName] = $mapping->columnName; + + if (isset($this->fieldNames[$mapping->columnName]) || ($this->discriminatorColumn && $this->discriminatorColumn->name === $mapping->columnName)) { + throw MappingException::duplicateColumnName($this->name, $mapping->columnName); + } + + $this->fieldNames[$mapping->columnName] = $mapping->fieldName; + + // Complete id mapping + if (isset($mapping->id) && $mapping->id === true) { + if ($this->versionField === $mapping->fieldName) { + throw MappingException::cannotVersionIdField($this->name, $mapping->fieldName); + } + + if (! in_array($mapping->fieldName, $this->identifier, true)) { + $this->identifier[] = $mapping->fieldName; + } + + // Check for composite key + if (! $this->isIdentifierComposite && count($this->identifier) > 1) { + $this->isIdentifierComposite = true; + } + } + + if (isset($mapping->generated)) { + if (! in_array($mapping->generated, [self::GENERATED_NEVER, self::GENERATED_INSERT, self::GENERATED_ALWAYS])) { + throw MappingException::invalidGeneratedMode($mapping->generated); + } + + if ($mapping->generated === self::GENERATED_NEVER) { + unset($mapping->generated); + } + } + + if (isset($mapping->enumType)) { + if (! enum_exists($mapping->enumType)) { + throw MappingException::nonEnumTypeMapped($this->name, $mapping->fieldName, $mapping->enumType); + } + + if (! empty($mapping->id)) { + $this->containsEnumIdentifier = true; + } + } + + return $mapping; + } + + /** + * Validates & completes the basic mapping information that is common to all + * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). + * + * @psalm-param array $mapping The mapping. + * + * @return ConcreteAssociationMapping + * + * @throws MappingException If something is wrong with the mapping. + */ + protected function _validateAndCompleteAssociationMapping(array $mapping): AssociationMapping + { + if (array_key_exists('mappedBy', $mapping) && $mapping['mappedBy'] === null) { + unset($mapping['mappedBy']); + } + + if (array_key_exists('inversedBy', $mapping) && $mapping['inversedBy'] === null) { + unset($mapping['inversedBy']); + } + + if (array_key_exists('joinColumns', $mapping) && in_array($mapping['joinColumns'], [null, []], true)) { + unset($mapping['joinColumns']); + } + + $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy + + if (empty($mapping['indexBy'])) { + unset($mapping['indexBy']); + } + + // If targetEntity is unqualified, assume it is in the same namespace as + // the sourceEntity. + $mapping['sourceEntity'] = $this->name; + + if ($this->isTypedProperty($mapping['fieldName'])) { + $mapping = $this->validateAndCompleteTypedAssociationMapping($mapping); + } + + if (isset($mapping['targetEntity'])) { + $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']); + $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); + } + + if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { + throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']); + } + + // Complete id mapping + if (isset($mapping['id']) && $mapping['id'] === true) { + if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { + throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']); + } + + if (! in_array($mapping['fieldName'], $this->identifier, true)) { + if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) { + throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId( + $mapping['targetEntity'], + $this->name, + $mapping['fieldName'], + ); + } + + assert(is_string($mapping['fieldName'])); + $this->identifier[] = $mapping['fieldName']; + $this->containsForeignIdentifier = true; + } + + // Check for composite key + if (! $this->isIdentifierComposite && count($this->identifier) > 1) { + $this->isIdentifierComposite = true; + } + + if ($this->cache && ! isset($mapping['cache'])) { + throw NonCacheableEntityAssociation::fromEntityAndField( + $this->name, + $mapping['fieldName'], + ); + } + } + + // Mandatory attributes for both sides + // Mandatory: fieldName, targetEntity + if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { + throw MappingException::missingFieldName($this->name); + } + + if (! isset($mapping['targetEntity'])) { + throw MappingException::missingTargetEntity($mapping['fieldName']); + } + + // Mandatory and optional attributes for either side + if (! isset($mapping['mappedBy'])) { + if (isset($mapping['joinTable'])) { + if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') { + $mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`'); + $mapping['joinTable']['quoted'] = true; + } + } + } else { + $mapping['isOwningSide'] = false; + } + + if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { + throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']); + } + + // Fetch mode. Default fetch mode to LAZY, if not set. + if (! isset($mapping['fetch'])) { + $mapping['fetch'] = self::FETCH_LAZY; + } + + // Cascades + $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : []; + + $allCascades = ['remove', 'persist', 'refresh', 'detach']; + if (in_array('all', $cascades, true)) { + $cascades = $allCascades; + } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) { + throw MappingException::invalidCascadeOption( + array_diff($cascades, $allCascades), + $this->name, + $mapping['fieldName'], + ); + } + + $mapping['cascade'] = $cascades; + + switch ($mapping['type']) { + case self::ONE_TO_ONE: + if (isset($mapping['joinColumns']) && $mapping['joinColumns'] && ! $mapping['isOwningSide']) { + throw MappingException::joinColumnNotAllowedOnOneToOneInverseSide( + $this->name, + $mapping['fieldName'], + ); + } + + return $mapping['isOwningSide'] ? + OneToOneOwningSideMapping::fromMappingArrayAndName( + $mapping, + $this->namingStrategy, + $this->name, + $this->table ?? null, + $this->isInheritanceTypeSingleTable(), + ) : + OneToOneInverseSideMapping::fromMappingArrayAndName($mapping, $this->name); + + case self::MANY_TO_ONE: + return ManyToOneAssociationMapping::fromMappingArrayAndName( + $mapping, + $this->namingStrategy, + $this->name, + $this->table ?? null, + $this->isInheritanceTypeSingleTable(), + ); + + case self::ONE_TO_MANY: + return OneToManyAssociationMapping::fromMappingArrayAndName($mapping, $this->name); + + case self::MANY_TO_MANY: + if (isset($mapping['joinColumns'])) { + unset($mapping['joinColumns']); + } + + return $mapping['isOwningSide'] ? + ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy($mapping, $this->namingStrategy) : + ManyToManyInverseSideMapping::fromMappingArray($mapping); + + default: + throw MappingException::invalidAssociationType( + $this->name, + $mapping['fieldName'], + $mapping['type'], + ); + } + } + + /** + * {@inheritDoc} + */ + public function getIdentifierFieldNames(): array + { + return $this->identifier; + } + + /** + * Gets the name of the single id field. Note that this only works on + * entity classes that have a single-field pk. + * + * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. + */ + public function getSingleIdentifierFieldName(): string + { + if ($this->isIdentifierComposite) { + throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name); + } + + if (! isset($this->identifier[0])) { + throw MappingException::noIdDefined($this->name); + } + + return $this->identifier[0]; + } + + /** + * Gets the column name of the single id column. Note that this only works on + * entity classes that have a single-field pk. + * + * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. + */ + public function getSingleIdentifierColumnName(): string + { + return $this->getColumnName($this->getSingleIdentifierFieldName()); + } + + /** + * INTERNAL: + * Sets the mapped identifier/primary key fields of this class. + * Mainly used by the ClassMetadataFactory to assign inherited identifiers. + * + * @psalm-param list $identifier + */ + public function setIdentifier(array $identifier): void + { + $this->identifier = $identifier; + $this->isIdentifierComposite = (count($this->identifier) > 1); + } + + /** + * {@inheritDoc} + */ + public function getIdentifier(): array + { + return $this->identifier; + } + + public function hasField(string $fieldName): bool + { + return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]); + } + + /** + * Gets an array containing all the column names. + * + * @psalm-param list|null $fieldNames + * + * @return mixed[] + * @psalm-return list + */ + public function getColumnNames(array|null $fieldNames = null): array + { + if ($fieldNames === null) { + return array_keys($this->fieldNames); + } + + return array_values(array_map($this->getColumnName(...), $fieldNames)); + } + + /** + * Returns an array with all the identifier column names. + * + * @psalm-return list + */ + public function getIdentifierColumnNames(): array + { + $columnNames = []; + + foreach ($this->identifier as $idProperty) { + if (isset($this->fieldMappings[$idProperty])) { + $columnNames[] = $this->fieldMappings[$idProperty]->columnName; + + continue; + } + + // Association defined as Id field + assert($this->associationMappings[$idProperty]->isToOneOwningSide()); + $joinColumns = $this->associationMappings[$idProperty]->joinColumns; + $assocColumnNames = array_map(static fn (JoinColumnMapping $joinColumn): string => $joinColumn->name, $joinColumns); + + $columnNames = array_merge($columnNames, $assocColumnNames); + } + + return $columnNames; + } + + /** + * Sets the type of Id generator to use for the mapped class. + * + * @psalm-param self::GENERATOR_TYPE_* $generatorType + */ + public function setIdGeneratorType(int $generatorType): void + { + $this->generatorType = $generatorType; + } + + /** + * Checks whether the mapped class uses an Id generator. + */ + public function usesIdGenerator(): bool + { + return $this->generatorType !== self::GENERATOR_TYPE_NONE; + } + + public function isInheritanceTypeNone(): bool + { + return $this->inheritanceType === self::INHERITANCE_TYPE_NONE; + } + + /** + * Checks whether the mapped class uses the JOINED inheritance mapping strategy. + * + * @return bool TRUE if the class participates in a JOINED inheritance mapping, + * FALSE otherwise. + */ + public function isInheritanceTypeJoined(): bool + { + return $this->inheritanceType === self::INHERITANCE_TYPE_JOINED; + } + + /** + * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy. + * + * @return bool TRUE if the class participates in a SINGLE_TABLE inheritance mapping, + * FALSE otherwise. + */ + public function isInheritanceTypeSingleTable(): bool + { + return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_TABLE; + } + + /** + * Checks whether the class uses an identity column for the Id generation. + */ + public function isIdGeneratorIdentity(): bool + { + return $this->generatorType === self::GENERATOR_TYPE_IDENTITY; + } + + /** + * Checks whether the class uses a sequence for id generation. + * + * @psalm-assert-if-true !null $this->sequenceGeneratorDefinition + */ + public function isIdGeneratorSequence(): bool + { + return $this->generatorType === self::GENERATOR_TYPE_SEQUENCE; + } + + /** + * Checks whether the class has a natural identifier/pk (which means it does + * not use any Id generator. + */ + public function isIdentifierNatural(): bool + { + return $this->generatorType === self::GENERATOR_TYPE_NONE; + } + + /** + * Gets the type of a field. + * + * @todo 3.0 Remove this. PersisterHelper should fix it somehow + */ + public function getTypeOfField(string $fieldName): string|null + { + return isset($this->fieldMappings[$fieldName]) + ? $this->fieldMappings[$fieldName]->type + : null; + } + + /** + * Gets the name of the primary table. + */ + public function getTableName(): string + { + return $this->table['name']; + } + + /** + * Gets primary table's schema name. + */ + public function getSchemaName(): string|null + { + return $this->table['schema'] ?? null; + } + + /** + * Gets the table name to use for temporary identifier tables of this class. + */ + public function getTemporaryIdTableName(): string + { + // replace dots with underscores because PostgreSQL creates temporary tables in a special schema + return str_replace('.', '_', $this->getTableName() . '_id_tmp'); + } + + /** + * Sets the mapped subclasses of this class. + * + * @psalm-param list $subclasses The names of all mapped subclasses. + */ + public function setSubclasses(array $subclasses): void + { + foreach ($subclasses as $subclass) { + $this->subClasses[] = $this->fullyQualifiedClassName($subclass); + } + } + + /** + * Sets the parent class names. Only entity classes may be given. + * + * Assumes that the class names in the passed array are in the order: + * directParent -> directParentParent -> directParentParentParent ... -> root. + * + * @psalm-param list $classNames + */ + public function setParentClasses(array $classNames): void + { + $this->parentClasses = $classNames; + + if (count($classNames) > 0) { + $this->rootEntityName = array_pop($classNames); + } + } + + /** + * Sets the inheritance type used by the class and its subclasses. + * + * @psalm-param self::INHERITANCE_TYPE_* $type + * + * @throws MappingException + */ + public function setInheritanceType(int $type): void + { + if (! $this->isInheritanceType($type)) { + throw MappingException::invalidInheritanceType($this->name, $type); + } + + $this->inheritanceType = $type; + } + + /** + * Sets the association to override association mapping of property for an entity relationship. + * + * @psalm-param array $overrideMapping + * + * @throws MappingException + */ + public function setAssociationOverride(string $fieldName, array $overrideMapping): void + { + if (! isset($this->associationMappings[$fieldName])) { + throw MappingException::invalidOverrideFieldName($this->name, $fieldName); + } + + $mapping = $this->associationMappings[$fieldName]->toArray(); + + if (isset($mapping['inherited'])) { + throw MappingException::illegalOverrideOfInheritedProperty( + $this->name, + $fieldName, + $mapping['inherited'], + ); + } + + if (isset($overrideMapping['joinColumns'])) { + $mapping['joinColumns'] = $overrideMapping['joinColumns']; + } + + if (isset($overrideMapping['inversedBy'])) { + $mapping['inversedBy'] = $overrideMapping['inversedBy']; + } + + if (isset($overrideMapping['joinTable'])) { + $mapping['joinTable'] = $overrideMapping['joinTable']; + } + + if (isset($overrideMapping['fetch'])) { + $mapping['fetch'] = $overrideMapping['fetch']; + } + + switch ($mapping['type']) { + case self::ONE_TO_ONE: + case self::MANY_TO_ONE: + $mapping['joinColumnFieldNames'] = []; + $mapping['sourceToTargetKeyColumns'] = []; + break; + case self::MANY_TO_MANY: + $mapping['relationToSourceKeyColumns'] = []; + $mapping['relationToTargetKeyColumns'] = []; + break; + } + + $this->associationMappings[$fieldName] = $this->_validateAndCompleteAssociationMapping($mapping); + } + + /** + * Sets the override for a mapped field. + * + * @psalm-param array $overrideMapping + * + * @throws MappingException + */ + public function setAttributeOverride(string $fieldName, array $overrideMapping): void + { + if (! isset($this->fieldMappings[$fieldName])) { + throw MappingException::invalidOverrideFieldName($this->name, $fieldName); + } + + $mapping = $this->fieldMappings[$fieldName]; + + if (isset($mapping->inherited)) { + throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName, $mapping->inherited); + } + + if (isset($mapping->id)) { + $overrideMapping['id'] = $mapping->id; + } + + if (isset($mapping->declared)) { + $overrideMapping['declared'] = $mapping->declared; + } + + if (! isset($overrideMapping['type'])) { + $overrideMapping['type'] = $mapping->type; + } + + if (! isset($overrideMapping['fieldName'])) { + $overrideMapping['fieldName'] = $mapping->fieldName; + } + + if ($overrideMapping['type'] !== $mapping->type) { + throw MappingException::invalidOverrideFieldType($this->name, $fieldName); + } + + unset($this->fieldMappings[$fieldName]); + unset($this->fieldNames[$mapping->columnName]); + unset($this->columnNames[$mapping->fieldName]); + + $overrideMapping = $this->validateAndCompleteFieldMapping($overrideMapping); + + $this->fieldMappings[$fieldName] = $overrideMapping; + } + + /** + * Checks whether a mapped field is inherited from an entity superclass. + */ + public function isInheritedField(string $fieldName): bool + { + return isset($this->fieldMappings[$fieldName]->inherited); + } + + /** + * Checks if this entity is the root in any entity-inheritance-hierarchy. + */ + public function isRootEntity(): bool + { + return $this->name === $this->rootEntityName; + } + + /** + * Checks whether a mapped association field is inherited from a superclass. + */ + public function isInheritedAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]->inherited); + } + + public function isInheritedEmbeddedClass(string $fieldName): bool + { + return isset($this->embeddedClasses[$fieldName]->inherited); + } + + /** + * Sets the name of the primary table the class is mapped to. + * + * @deprecated Use {@link setPrimaryTable}. + */ + public function setTableName(string $tableName): void + { + $this->table['name'] = $tableName; + } + + /** + * Sets the primary table definition. The provided array supports the + * following structure: + * + * name => (optional, defaults to class name) + * indexes => array of indexes (optional) + * uniqueConstraints => array of constraints (optional) + * + * If a key is omitted, the current value is kept. + * + * @psalm-param array $table The table description. + */ + public function setPrimaryTable(array $table): void + { + if (isset($table['name'])) { + // Split schema and table name from a table name like "myschema.mytable" + if (str_contains($table['name'], '.')) { + [$this->table['schema'], $table['name']] = explode('.', $table['name'], 2); + } + + if ($table['name'][0] === '`') { + $table['name'] = trim($table['name'], '`'); + $this->table['quoted'] = true; + } + + $this->table['name'] = $table['name']; + } + + if (isset($table['quoted'])) { + $this->table['quoted'] = $table['quoted']; + } + + if (isset($table['schema'])) { + $this->table['schema'] = $table['schema']; + } + + if (isset($table['indexes'])) { + $this->table['indexes'] = $table['indexes']; + } + + if (isset($table['uniqueConstraints'])) { + $this->table['uniqueConstraints'] = $table['uniqueConstraints']; + } + + if (isset($table['options'])) { + $this->table['options'] = $table['options']; + } + } + + /** + * Checks whether the given type identifies an inheritance type. + */ + private function isInheritanceType(int $type): bool + { + return $type === self::INHERITANCE_TYPE_NONE || + $type === self::INHERITANCE_TYPE_SINGLE_TABLE || + $type === self::INHERITANCE_TYPE_JOINED; + } + + /** + * Adds a mapped field to the class. + * + * @psalm-param array $mapping The field mapping. + * + * @throws MappingException + */ + public function mapField(array $mapping): void + { + $mapping = $this->validateAndCompleteFieldMapping($mapping); + $this->assertFieldNotMapped($mapping->fieldName); + + if (isset($mapping->generated)) { + $this->requiresFetchAfterChange = true; + } + + $this->fieldMappings[$mapping->fieldName] = $mapping; + } + + /** + * INTERNAL: + * Adds an association mapping without completing/validating it. + * This is mainly used to add inherited association mappings to derived classes. + * + * @param ConcreteAssociationMapping $mapping + * + * @throws MappingException + */ + public function addInheritedAssociationMapping(AssociationMapping $mapping/*, $owningClassName = null*/): void + { + if (isset($this->associationMappings[$mapping->fieldName])) { + throw MappingException::duplicateAssociationMapping($this->name, $mapping->fieldName); + } + + $this->associationMappings[$mapping->fieldName] = $mapping; + } + + /** + * INTERNAL: + * Adds a field mapping without completing/validating it. + * This is mainly used to add inherited field mappings to derived classes. + */ + public function addInheritedFieldMapping(FieldMapping $fieldMapping): void + { + $this->fieldMappings[$fieldMapping->fieldName] = $fieldMapping; + $this->columnNames[$fieldMapping->fieldName] = $fieldMapping->columnName; + $this->fieldNames[$fieldMapping->columnName] = $fieldMapping->fieldName; + + if (isset($fieldMapping->generated)) { + $this->requiresFetchAfterChange = true; + } + } + + /** + * Adds a one-to-one mapping. + * + * @param array $mapping The mapping. + */ + public function mapOneToOne(array $mapping): void + { + $mapping['type'] = self::ONE_TO_ONE; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Adds a one-to-many mapping. + * + * @psalm-param array $mapping The mapping. + */ + public function mapOneToMany(array $mapping): void + { + $mapping['type'] = self::ONE_TO_MANY; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Adds a many-to-one mapping. + * + * @psalm-param array $mapping The mapping. + */ + public function mapManyToOne(array $mapping): void + { + $mapping['type'] = self::MANY_TO_ONE; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Adds a many-to-many mapping. + * + * @psalm-param array $mapping The mapping. + */ + public function mapManyToMany(array $mapping): void + { + $mapping['type'] = self::MANY_TO_MANY; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Stores the association mapping. + * + * @param ConcreteAssociationMapping $assocMapping + * + * @throws MappingException + */ + protected function _storeAssociationMapping(AssociationMapping $assocMapping): void + { + $sourceFieldName = $assocMapping->fieldName; + + $this->assertFieldNotMapped($sourceFieldName); + + $this->associationMappings[$sourceFieldName] = $assocMapping; + } + + /** + * Registers a custom repository class for the entity class. + * + * @param string|null $repositoryClassName The class name of the custom mapper. + * @psalm-param class-string|null $repositoryClassName + */ + public function setCustomRepositoryClass(string|null $repositoryClassName): void + { + if ($repositoryClassName === null) { + $this->customRepositoryClassName = null; + + return; + } + + $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName); + } + + /** + * Dispatches the lifecycle event of the given entity to the registered + * lifecycle callbacks and lifecycle listeners. + * + * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker + * + * @param string $lifecycleEvent The lifecycle event. + */ + public function invokeLifecycleCallbacks(string $lifecycleEvent, object $entity): void + { + foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { + $entity->$callback(); + } + } + + /** + * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. + */ + public function hasLifecycleCallbacks(string $lifecycleEvent): bool + { + return isset($this->lifecycleCallbacks[$lifecycleEvent]); + } + + /** + * Gets the registered lifecycle callbacks for an event. + * + * @return string[] + * @psalm-return list + */ + public function getLifecycleCallbacks(string $event): array + { + return $this->lifecycleCallbacks[$event] ?? []; + } + + /** + * Adds a lifecycle callback for entities of this class. + */ + public function addLifecycleCallback(string $callback, string $event): void + { + if ($this->isEmbeddedClass) { + throw MappingException::illegalLifecycleCallbackOnEmbeddedClass($callback, $this->name); + } + + if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) { + return; + } + + $this->lifecycleCallbacks[$event][] = $callback; + } + + /** + * Sets the lifecycle callbacks for entities of this class. + * Any previously registered callbacks are overwritten. + * + * @psalm-param array> $callbacks + */ + public function setLifecycleCallbacks(array $callbacks): void + { + $this->lifecycleCallbacks = $callbacks; + } + + /** + * Adds a entity listener for entities of this class. + * + * @param string $eventName The entity lifecycle event. + * @param string $class The listener class. + * @param string $method The listener callback method. + * + * @throws MappingException + */ + public function addEntityListener(string $eventName, string $class, string $method): void + { + $class = $this->fullyQualifiedClassName($class); + + $listener = [ + 'class' => $class, + 'method' => $method, + ]; + + if (! class_exists($class)) { + throw MappingException::entityListenerClassNotFound($class, $this->name); + } + + if (! method_exists($class, $method)) { + throw MappingException::entityListenerMethodNotFound($class, $method, $this->name); + } + + if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) { + throw MappingException::duplicateEntityListener($class, $method, $this->name); + } + + $this->entityListeners[$eventName][] = $listener; + } + + /** + * Sets the discriminator column definition. + * + * @see getDiscriminatorColumn() + * + * @param DiscriminatorColumnMapping|mixed[]|null $columnDef + * @psalm-param DiscriminatorColumnMapping|array{ + * name: string|null, + * fieldName?: string|null, + * type?: string|null, + * length?: int|null, + * columnDefinition?: string|null, + * enumType?: class-string|null, + * options?: array|null + * }|null $columnDef + * + * @throws MappingException + */ + public function setDiscriminatorColumn(DiscriminatorColumnMapping|array|null $columnDef): void + { + if ($columnDef instanceof DiscriminatorColumnMapping) { + $this->discriminatorColumn = $columnDef; + + return; + } + + if ($columnDef !== null) { + if (! isset($columnDef['name'])) { + throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); + } + + if (isset($this->fieldNames[$columnDef['name']])) { + throw MappingException::duplicateColumnName($this->name, $columnDef['name']); + } + + $columnDef['fieldName'] ??= $columnDef['name']; + $columnDef['type'] ??= 'string'; + $columnDef['options'] ??= []; + + if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) { + throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); + } + + $this->discriminatorColumn = DiscriminatorColumnMapping::fromMappingArray($columnDef); + } + } + + final public function getDiscriminatorColumn(): DiscriminatorColumnMapping + { + if ($this->discriminatorColumn === null) { + throw new LogicException('The discriminator column was not set.'); + } + + return $this->discriminatorColumn; + } + + /** + * Sets the discriminator values used by this class. + * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. + * + * @param array $map + */ + public function setDiscriminatorMap(array $map): void + { + foreach ($map as $value => $className) { + $this->addDiscriminatorMapClass($value, $className); + } + } + + /** + * Adds one entry of the discriminator map with a new class and corresponding name. + * + * @throws MappingException + */ + public function addDiscriminatorMapClass(int|string $name, string $className): void + { + $className = $this->fullyQualifiedClassName($className); + $className = ltrim($className, '\\'); + + $this->discriminatorMap[$name] = $className; + + if ($this->name === $className) { + $this->discriminatorValue = $name; + + return; + } + + if (! (class_exists($className) || interface_exists($className))) { + throw MappingException::invalidClassInDiscriminatorMap($className, $this->name); + } + + $this->addSubClass($className); + } + + /** @param array $classes */ + public function addSubClasses(array $classes): void + { + foreach ($classes as $className) { + $this->addSubClass($className); + } + } + + public function addSubClass(string $className): void + { + // By ignoring classes that are not subclasses of the current class, we simplify inheriting + // the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata. + + if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) { + $this->subClasses[] = $className; + } + } + + public function hasAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]); + } + + public function isSingleValuedAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]) + && ($this->associationMappings[$fieldName]->isToOne()); + } + + public function isCollectionValuedAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]) + && ! $this->associationMappings[$fieldName]->isToOne(); + } + + /** + * Is this an association that only has a single join column? + */ + public function isAssociationWithSingleJoinColumn(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]) + && isset($this->associationMappings[$fieldName]->joinColumns[0]) + && ! isset($this->associationMappings[$fieldName]->joinColumns[1]); + } + + /** + * Returns the single association join column (if any). + * + * @throws MappingException + */ + public function getSingleAssociationJoinColumnName(string $fieldName): string + { + if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { + throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); + } + + $assoc = $this->associationMappings[$fieldName]; + + assert($assoc->isToOneOwningSide()); + + return $assoc->joinColumns[0]->name; + } + + /** + * Returns the single association referenced join column name (if any). + * + * @throws MappingException + */ + public function getSingleAssociationReferencedJoinColumnName(string $fieldName): string + { + if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { + throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); + } + + $assoc = $this->associationMappings[$fieldName]; + + assert($assoc->isToOneOwningSide()); + + return $assoc->joinColumns[0]->referencedColumnName; + } + + /** + * Used to retrieve a fieldname for either field or association from a given column. + * + * This method is used in foreign-key as primary-key contexts. + * + * @throws MappingException + */ + public function getFieldForColumn(string $columnName): string + { + if (isset($this->fieldNames[$columnName])) { + return $this->fieldNames[$columnName]; + } + + foreach ($this->associationMappings as $assocName => $mapping) { + if ( + $this->isAssociationWithSingleJoinColumn($assocName) && + assert($this->associationMappings[$assocName]->isToOneOwningSide()) && + $this->associationMappings[$assocName]->joinColumns[0]->name === $columnName + ) { + return $assocName; + } + } + + throw MappingException::noFieldNameFoundForColumn($this->name, $columnName); + } + + /** + * Sets the ID generator used to generate IDs for instances of this class. + */ + public function setIdGenerator(AbstractIdGenerator $generator): void + { + $this->idGenerator = $generator; + } + + /** + * Sets definition. + * + * @psalm-param array $definition + */ + public function setCustomGeneratorDefinition(array $definition): void + { + $this->customGeneratorDefinition = $definition; + } + + /** + * Sets the definition of the sequence ID generator for this class. + * + * The definition must have the following structure: + * + * array( + * 'sequenceName' => 'name', + * 'allocationSize' => 20, + * 'initialValue' => 1 + * 'quoted' => 1 + * ) + * + * + * @psalm-param array{sequenceName?: string, allocationSize?: int|string, initialValue?: int|string, quoted?: mixed} $definition + * + * @throws MappingException + */ + public function setSequenceGeneratorDefinition(array $definition): void + { + if (! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') { + throw MappingException::missingSequenceName($this->name); + } + + if ($definition['sequenceName'][0] === '`') { + $definition['sequenceName'] = trim($definition['sequenceName'], '`'); + $definition['quoted'] = true; + } + + if (! isset($definition['allocationSize']) || trim((string) $definition['allocationSize']) === '') { + $definition['allocationSize'] = '1'; + } + + if (! isset($definition['initialValue']) || trim((string) $definition['initialValue']) === '') { + $definition['initialValue'] = '1'; + } + + $definition['allocationSize'] = (string) $definition['allocationSize']; + $definition['initialValue'] = (string) $definition['initialValue']; + + $this->sequenceGeneratorDefinition = $definition; + } + + /** + * Sets the version field mapping used for versioning. Sets the default + * value to use depending on the column type. + * + * @psalm-param array $mapping The version field mapping array. + * + * @throws MappingException + */ + public function setVersionMapping(array &$mapping): void + { + $this->isVersioned = true; + $this->versionField = $mapping['fieldName']; + $this->requiresFetchAfterChange = true; + + if (! isset($mapping['default'])) { + if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) { + $mapping['default'] = 1; + } elseif ($mapping['type'] === 'datetime') { + $mapping['default'] = 'CURRENT_TIMESTAMP'; + } else { + throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']); + } + } + } + + /** + * Sets whether this class is to be versioned for optimistic locking. + */ + public function setVersioned(bool $bool): void + { + $this->isVersioned = $bool; + + if ($bool) { + $this->requiresFetchAfterChange = true; + } + } + + /** + * Sets the name of the field that is to be used for versioning if this class is + * versioned for optimistic locking. + */ + public function setVersionField(string|null $versionField): void + { + $this->versionField = $versionField; + } + + /** + * Marks this class as read only, no change tracking is applied to it. + */ + public function markReadOnly(): void + { + $this->isReadOnly = true; + } + + /** + * {@inheritDoc} + */ + public function getFieldNames(): array + { + return array_keys($this->fieldMappings); + } + + /** + * {@inheritDoc} + */ + public function getAssociationNames(): array + { + return array_keys($this->associationMappings); + } + + /** + * {@inheritDoc} + * + * @psalm-return class-string + * + * @throws InvalidArgumentException + */ + public function getAssociationTargetClass(string $assocName): string + { + return $this->associationMappings[$assocName]->targetEntity + ?? throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association."); + } + + public function getName(): string + { + return $this->name; + } + + public function isAssociationInverseSide(string $assocName): bool + { + return isset($this->associationMappings[$assocName]) + && ! $this->associationMappings[$assocName]->isOwningSide(); + } + + public function getAssociationMappedByTargetField(string $assocName): string + { + $assoc = $this->getAssociationMapping($assocName); + + if (! $assoc instanceof InverseSideMapping) { + throw new LogicException(sprintf( + <<<'EXCEPTION' + Context: Calling %s() with "%s", which is the owning side of an association. + Problem: The owning side of an association has no "mappedBy" field. + Solution: Call %s::isAssociationInverseSide() to check first. + EXCEPTION, + __METHOD__, + $assocName, + self::class, + )); + } + + return $assoc->mappedBy; + } + + /** + * @param C $className + * + * @return string|null null if and only if the input value is null + * @psalm-return (C is class-string ? class-string : (C is string ? string : null)) + * + * @template C of string|null + */ + public function fullyQualifiedClassName(string|null $className): string|null + { + if ($className === null) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11294', + 'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0', + __METHOD__, + ); + + return null; + } + + if (! str_contains($className, '\\') && $this->namespace) { + return $this->namespace . '\\' . $className; + } + + return $className; + } + + public function getMetadataValue(string $name): mixed + { + return $this->$name ?? null; + } + + /** + * Map Embedded Class + * + * @psalm-param array{ + * fieldName: string, + * class?: class-string, + * declaredField?: string, + * columnPrefix?: string|false|null, + * originalField?: string + * } $mapping + * + * @throws MappingException + */ + public function mapEmbedded(array $mapping): void + { + $this->assertFieldNotMapped($mapping['fieldName']); + + if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) { + $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); + if ($type instanceof ReflectionNamedType) { + $mapping['class'] = $type->getName(); + } + } + + if (! (isset($mapping['class']) && $mapping['class'])) { + throw MappingException::missingEmbeddedClass($mapping['fieldName']); + } + + $this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([ + 'class' => $this->fullyQualifiedClassName($mapping['class']), + 'columnPrefix' => $mapping['columnPrefix'] ?? null, + 'declaredField' => $mapping['declaredField'] ?? null, + 'originalField' => $mapping['originalField'] ?? null, + ]); + } + + /** + * Inline the embeddable class + */ + public function inlineEmbeddable(string $property, ClassMetadata $embeddable): void + { + foreach ($embeddable->fieldMappings as $originalFieldMapping) { + $fieldMapping = (array) $originalFieldMapping; + $fieldMapping['originalClass'] ??= $embeddable->name; + $fieldMapping['declaredField'] = isset($fieldMapping['declaredField']) + ? $property . '.' . $fieldMapping['declaredField'] + : $property; + $fieldMapping['originalField'] ??= $fieldMapping['fieldName']; + $fieldMapping['fieldName'] = $property . '.' . $fieldMapping['fieldName']; + + if (! empty($this->embeddedClasses[$property]->columnPrefix)) { + $fieldMapping['columnName'] = $this->embeddedClasses[$property]->columnPrefix . $fieldMapping['columnName']; + } elseif ($this->embeddedClasses[$property]->columnPrefix !== false) { + assert($this->reflClass !== null); + assert($embeddable->reflClass !== null); + $fieldMapping['columnName'] = $this->namingStrategy + ->embeddedFieldToColumnName( + $property, + $fieldMapping['columnName'], + $this->reflClass->name, + $embeddable->reflClass->name, + ); + } + + $this->mapField($fieldMapping); + } + } + + /** @throws MappingException */ + private function assertFieldNotMapped(string $fieldName): void + { + if ( + isset($this->fieldMappings[$fieldName]) || + isset($this->associationMappings[$fieldName]) || + isset($this->embeddedClasses[$fieldName]) + ) { + throw MappingException::duplicateFieldMapping($this->name, $fieldName); + } + } + + /** + * Gets the sequence name based on class metadata. + * + * @todo Sequence names should be computed in DBAL depending on the platform + */ + public function getSequenceName(AbstractPlatform $platform): string + { + $sequencePrefix = $this->getSequencePrefix($platform); + $columnName = $this->getSingleIdentifierColumnName(); + + return $sequencePrefix . '_' . $columnName . '_seq'; + } + + /** + * Gets the sequence name prefix based on class metadata. + * + * @todo Sequence names should be computed in DBAL depending on the platform + */ + public function getSequencePrefix(AbstractPlatform $platform): string + { + $tableName = $this->getTableName(); + $sequencePrefix = $tableName; + + // Prepend the schema name to the table name if there is one + $schemaName = $this->getSchemaName(); + if ($schemaName) { + $sequencePrefix = $schemaName . '.' . $tableName; + } + + return $sequencePrefix; + } + + /** @psalm-param class-string $class */ + private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ReflectionProperty|null + { + $reflectionProperty = $reflService->getAccessibleProperty($class, $field); + if ($reflectionProperty?->isReadOnly()) { + $declaringClass = $reflectionProperty->class; + if ($declaringClass !== $class) { + $reflectionProperty = $reflService->getAccessibleProperty($declaringClass, $field); + } + + if ($reflectionProperty !== null) { + $reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty); + } + } + + return $reflectionProperty; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php b/vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php new file mode 100644 index 0000000..a3a4701 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php @@ -0,0 +1,729 @@ + + */ +class ClassMetadataFactory extends AbstractClassMetadataFactory +{ + private EntityManagerInterface|null $em = null; + private AbstractPlatform|null $targetPlatform = null; + private MappingDriver|null $driver = null; + private EventManager|null $evm = null; + + /** @var mixed[] */ + private array $embeddablesActiveNesting = []; + + private const NON_IDENTITY_DEFAULT_STRATEGY = [ + Platforms\OraclePlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE, + ]; + + public function setEntityManager(EntityManagerInterface $em): void + { + parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver()); + + $this->em = $em; + } + + /** + * @param A $maybeOwningSide + * + * @return (A is ManyToManyAssociationMapping ? ManyToManyOwningSideMapping : ( + * A is OneToOneAssociationMapping ? OneToOneOwningSideMapping : ( + * A is OneToManyAssociationMapping ? ManyToOneAssociationMapping : ( + * A is ManyToOneAssociationMapping ? ManyToOneAssociationMapping : + * ManyToManyOwningSideMapping|OneToOneOwningSideMapping|ManyToOneAssociationMapping + * )))) + * + * @template A of AssociationMapping + */ + final public function getOwningSide(AssociationMapping $maybeOwningSide): OwningSideMapping + { + if ($maybeOwningSide instanceof OwningSideMapping) { + assert($maybeOwningSide instanceof ManyToManyOwningSideMapping || + $maybeOwningSide instanceof OneToOneOwningSideMapping || + $maybeOwningSide instanceof ManyToOneAssociationMapping); + + return $maybeOwningSide; + } + + assert($maybeOwningSide instanceof InverseSideMapping); + + $owningSide = $this->getMetadataFor($maybeOwningSide->targetEntity) + ->associationMappings[$maybeOwningSide->mappedBy]; + + assert($owningSide instanceof ManyToManyOwningSideMapping || + $owningSide instanceof OneToOneOwningSideMapping || + $owningSide instanceof ManyToOneAssociationMapping); + + return $owningSide; + } + + protected function initialize(): void + { + $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); + $this->evm = $this->em->getEventManager(); + $this->initialized = true; + } + + protected function onNotFoundMetadata(string $className): ClassMetadata|null + { + if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) { + return null; + } + + $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em); + + $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs); + $classMetadata = $eventArgs->getFoundMetadata(); + assert($classMetadata instanceof ClassMetadata || $classMetadata === null); + + return $classMetadata; + } + + /** + * {@inheritDoc} + */ + protected function doLoadMetadata( + ClassMetadataInterface $class, + ClassMetadataInterface|null $parent, + bool $rootEntityFound, + array $nonSuperclassParents, + ): void { + if ($parent) { + $class->setInheritanceType($parent->inheritanceType); + $class->setDiscriminatorColumn($parent->discriminatorColumn === null ? null : clone $parent->discriminatorColumn); + $class->setIdGeneratorType($parent->generatorType); + $this->addInheritedFields($class, $parent); + $this->addInheritedRelations($class, $parent); + $this->addInheritedEmbeddedClasses($class, $parent); + $class->setIdentifier($parent->identifier); + $class->setVersioned($parent->isVersioned); + $class->setVersionField($parent->versionField); + $class->setDiscriminatorMap($parent->discriminatorMap); + $class->addSubClasses($parent->subClasses); + $class->setLifecycleCallbacks($parent->lifecycleCallbacks); + $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); + + if (! empty($parent->customGeneratorDefinition)) { + $class->setCustomGeneratorDefinition($parent->customGeneratorDefinition); + } + + if ($parent->isMappedSuperclass) { + $class->setCustomRepositoryClass($parent->customRepositoryClassName); + } + } + + // Invoke driver + try { + $this->driver->loadMetadataForClass($class->getName(), $class); + } catch (ReflectionException $e) { + throw MappingException::reflectionFailure($class->getName(), $e); + } + + // If this class has a parent the id generator strategy is inherited. + // However this is only true if the hierarchy of parents contains the root entity, + // if it consists of mapped superclasses these don't necessarily include the id field. + if ($parent && $rootEntityFound) { + $this->inheritIdGeneratorMapping($class, $parent); + } else { + $this->completeIdGeneratorMapping($class); + } + + if (! $class->isMappedSuperclass) { + if ($rootEntityFound && $class->isInheritanceTypeNone()) { + throw MappingException::missingInheritanceTypeDeclaration(end($nonSuperclassParents), $class->name); + } + + foreach ($class->embeddedClasses as $property => $embeddableClass) { + if (isset($embeddableClass->inherited)) { + continue; + } + + if (isset($this->embeddablesActiveNesting[$embeddableClass->class])) { + throw MappingException::infiniteEmbeddableNesting($class->name, $property); + } + + $this->embeddablesActiveNesting[$class->name] = true; + + $embeddableMetadata = $this->getMetadataFor($embeddableClass->class); + + if ($embeddableMetadata->isEmbeddedClass) { + $this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property); + } + + $identifier = $embeddableMetadata->getIdentifier(); + + if (! empty($identifier)) { + $this->inheritIdGeneratorMapping($class, $embeddableMetadata); + } + + $class->inlineEmbeddable($property, $embeddableMetadata); + + unset($this->embeddablesActiveNesting[$class->name]); + } + } + + if ($parent) { + if ($parent->isInheritanceTypeSingleTable()) { + $class->setPrimaryTable($parent->table); + } + + $this->addInheritedIndexes($class, $parent); + + if ($parent->cache) { + $class->cache = $parent->cache; + } + + if ($parent->containsForeignIdentifier) { + $class->containsForeignIdentifier = true; + } + + if ($parent->containsEnumIdentifier) { + $class->containsEnumIdentifier = true; + } + + if (! empty($parent->entityListeners) && empty($class->entityListeners)) { + $class->entityListeners = $parent->entityListeners; + } + } + + $class->setParentClasses($nonSuperclassParents); + + if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { + $this->addDefaultDiscriminatorMap($class); + } + + // During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402. + // So, we must not discover the missing subclasses before that. + + if ($this->evm->hasListeners(Events::loadClassMetadata)) { + $eventArgs = new LoadClassMetadataEventArgs($class, $this->em); + $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); + } + + $this->findAbstractEntityClassesNotListedInDiscriminatorMap($class); + + $this->validateRuntimeMetadata($class, $parent); + } + + /** + * Validate runtime metadata is correctly defined. + * + * @throws MappingException + */ + protected function validateRuntimeMetadata(ClassMetadata $class, ClassMetadataInterface|null $parent): void + { + if (! $class->reflClass) { + // only validate if there is a reflection class instance + return; + } + + $class->validateIdentifier(); + $class->validateAssociations(); + $class->validateLifecycleCallbacks($this->getReflectionService()); + + // verify inheritance + if (! $class->isMappedSuperclass && ! $class->isInheritanceTypeNone()) { + if (! $parent) { + if (count($class->discriminatorMap) === 0) { + throw MappingException::missingDiscriminatorMap($class->name); + } + + if (! $class->discriminatorColumn) { + throw MappingException::missingDiscriminatorColumn($class->name); + } + + foreach ($class->subClasses as $subClass) { + if ((new ReflectionClass($subClass))->name !== $subClass) { + throw MappingException::invalidClassInDiscriminatorMap($subClass, $class->name); + } + } + } else { + assert($parent instanceof ClassMetadata); // https://github.com/doctrine/orm/issues/8746 + if ( + ! $class->reflClass->isAbstract() + && ! in_array($class->name, $class->discriminatorMap, true) + ) { + throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName); + } + } + } elseif ($class->isMappedSuperclass && $class->name === $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) { + // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy + throw MappingException::noInheritanceOnMappedSuperClass($class->name); + } + } + + protected function newClassMetadataInstance(string $className): ClassMetadata + { + return new ClassMetadata( + $className, + $this->em->getConfiguration()->getNamingStrategy(), + $this->em->getConfiguration()->getTypedFieldMapper(), + ); + } + + /** + * Adds a default discriminator map if no one is given + * + * If an entity is of any inheritance type and does not contain a + * discriminator map, then the map is generated automatically. This process + * is expensive computation wise. + * + * The automatically generated discriminator map contains the lowercase short name of + * each class as key. + * + * @throws MappingException + */ + private function addDefaultDiscriminatorMap(ClassMetadata $class): void + { + $allClasses = $this->driver->getAllClassNames(); + $fqcn = $class->getName(); + $map = [$this->getShortName($class->name) => $fqcn]; + + $duplicates = []; + foreach ($allClasses as $subClassCandidate) { + if (is_subclass_of($subClassCandidate, $fqcn)) { + $shortName = $this->getShortName($subClassCandidate); + + if (isset($map[$shortName])) { + $duplicates[] = $shortName; + } + + $map[$shortName] = $subClassCandidate; + } + } + + if ($duplicates) { + throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map); + } + + $class->setDiscriminatorMap($map); + } + + private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void + { + // Only root classes in inheritance hierarchies need contain a discriminator map, + // so skip for other classes. + if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) { + return; + } + + $processedClasses = [$rootEntityClass->name => true]; + foreach ($rootEntityClass->subClasses as $knownSubClass) { + $processedClasses[$knownSubClass] = true; + } + + foreach ($rootEntityClass->discriminatorMap as $declaredClassName) { + // This fetches non-transient parent classes only + $parentClasses = $this->getParentClasses($declaredClassName); + + foreach ($parentClasses as $parentClass) { + if (isset($processedClasses[$parentClass])) { + continue; + } + + $processedClasses[$parentClass] = true; + + // All non-abstract entity classes must be listed in the discriminator map, and + // this will be validated/enforced at runtime (possibly at a later time, when the + // subclass is loaded, but anyways). Also, subclasses is about entity classes only. + // That means we can ignore non-abstract classes here. The (expensive) driver + // check for mapped superclasses need only be run for abstract candidate classes. + if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) { + continue; + } + + // We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter) + $rootEntityClass->addSubClass($parentClass); + } + } + } + + /** @param class-string $className */ + private function peekIfIsMappedSuperclass(string $className): bool + { + $reflService = $this->getReflectionService(); + $class = $this->newClassMetadataInstance($className); + $this->initializeReflection($class, $reflService); + + $this->getDriver()->loadMetadataForClass($className, $class); + + return $class->isMappedSuperclass; + } + + /** + * Gets the lower-case short name of a class. + * + * @psalm-param class-string $className + */ + private function getShortName(string $className): string + { + if (! str_contains($className, '\\')) { + return strtolower($className); + } + + $parts = explode('\\', $className); + + return strtolower(end($parts)); + } + + /** + * Puts the `inherited` and `declared` values into mapping information for fields, associations + * and embedded classes. + */ + private function addMappingInheritanceInformation( + AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping, + ClassMetadata $parentClass, + ): void { + if (! isset($mapping->inherited) && ! $parentClass->isMappedSuperclass) { + $mapping->inherited = $parentClass->name; + } + + if (! isset($mapping->declared)) { + $mapping->declared = $parentClass->name; + } + } + + /** + * Adds inherited fields to the subclass mapping. + */ + private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + foreach ($parentClass->fieldMappings as $mapping) { + $subClassMapping = clone $mapping; + $this->addMappingInheritanceInformation($subClassMapping, $parentClass); + $subClass->addInheritedFieldMapping($subClassMapping); + } + + foreach ($parentClass->reflFields as $name => $field) { + $subClass->reflFields[$name] = $field; + } + } + + /** + * Adds inherited association mappings to the subclass mapping. + * + * @throws MappingException + */ + private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + foreach ($parentClass->associationMappings as $field => $mapping) { + $subClassMapping = clone $mapping; + $this->addMappingInheritanceInformation($subClassMapping, $parentClass); + // When the class inheriting the relation ($subClass) is the first entity class since the + // relation has been defined in a mapped superclass (or in a chain + // of mapped superclasses) above, then declare this current entity class as the source of + // the relationship. + // According to the definitions given in https://github.com/doctrine/orm/pull/10396/, + // this is the case <=> ! isset($mapping['inherited']). + if (! isset($subClassMapping->inherited)) { + $subClassMapping->sourceEntity = $subClass->name; + } + + $subClass->addInheritedAssociationMapping($subClassMapping); + } + } + + private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + foreach ($parentClass->embeddedClasses as $field => $embeddedClass) { + $subClassMapping = clone $embeddedClass; + $this->addMappingInheritanceInformation($subClassMapping, $parentClass); + $subClass->embeddedClasses[$field] = $subClassMapping; + } + } + + /** + * Adds nested embedded classes metadata to a parent class. + * + * @param ClassMetadata $subClass Sub embedded class metadata to add nested embedded classes metadata from. + * @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to. + * @param string $prefix Embedded classes' prefix to use for nested embedded classes field names. + */ + private function addNestedEmbeddedClasses( + ClassMetadata $subClass, + ClassMetadata $parentClass, + string $prefix, + ): void { + foreach ($subClass->embeddedClasses as $property => $embeddableClass) { + if (isset($embeddableClass->inherited)) { + continue; + } + + $embeddableMetadata = $this->getMetadataFor($embeddableClass->class); + + $parentClass->mapEmbedded( + [ + 'fieldName' => $prefix . '.' . $property, + 'class' => $embeddableMetadata->name, + 'columnPrefix' => $embeddableClass->columnPrefix, + 'declaredField' => $embeddableClass->declaredField + ? $prefix . '.' . $embeddableClass->declaredField + : $prefix, + 'originalField' => $embeddableClass->originalField ?: $property, + ], + ); + } + } + + /** + * Copy the table indices from the parent class superclass to the child class + */ + private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + if (! $parentClass->isMappedSuperclass) { + return; + } + + foreach (['uniqueConstraints', 'indexes'] as $indexType) { + if (isset($parentClass->table[$indexType])) { + foreach ($parentClass->table[$indexType] as $indexName => $index) { + if (isset($subClass->table[$indexType][$indexName])) { + continue; // Let the inheriting table override indices + } + + $subClass->table[$indexType][$indexName] = $index; + } + } + } + } + + /** + * Completes the ID generator mapping. If "auto" is specified we choose the generator + * most appropriate for the targeted database platform. + * + * @throws ORMException + */ + private function completeIdGeneratorMapping(ClassMetadata $class): void + { + $idGenType = $class->generatorType; + if ($idGenType === ClassMetadata::GENERATOR_TYPE_AUTO) { + $class->setIdGeneratorType($this->determineIdGeneratorStrategy($this->getTargetPlatform())); + } + + // Create & assign an appropriate ID generator instance + switch ($class->generatorType) { + case ClassMetadata::GENERATOR_TYPE_IDENTITY: + $sequenceName = null; + $fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null; + $platform = $this->getTargetPlatform(); + + $generator = $fieldName && $class->fieldMappings[$fieldName]->type === 'bigint' + ? new BigIntegerIdentityGenerator() + : new IdentityGenerator(); + + $class->setIdGenerator($generator); + + break; + + case ClassMetadata::GENERATOR_TYPE_SEQUENCE: + // If there is no sequence definition yet, create a default definition + $definition = $class->sequenceGeneratorDefinition; + + if (! $definition) { + $fieldName = $class->getSingleIdentifierFieldName(); + $sequenceName = $class->getSequenceName($this->getTargetPlatform()); + $quoted = isset($class->fieldMappings[$fieldName]->quoted) || isset($class->table['quoted']); + + $definition = [ + 'sequenceName' => $this->truncateSequenceName($sequenceName), + 'allocationSize' => 1, + 'initialValue' => 1, + ]; + + if ($quoted) { + $definition['quoted'] = true; + } + + $class->setSequenceGeneratorDefinition($definition); + } + + $sequenceGenerator = new SequenceGenerator( + $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()), + (int) $definition['allocationSize'], + ); + $class->setIdGenerator($sequenceGenerator); + break; + + case ClassMetadata::GENERATOR_TYPE_NONE: + $class->setIdGenerator(new AssignedGenerator()); + break; + + case ClassMetadata::GENERATOR_TYPE_CUSTOM: + $definition = $class->customGeneratorDefinition; + if ($definition === null) { + throw InvalidCustomGenerator::onClassNotConfigured(); + } + + if (! class_exists($definition['class'])) { + throw InvalidCustomGenerator::onMissingClass($definition); + } + + $class->setIdGenerator(new $definition['class']()); + break; + + default: + throw UnknownGeneratorType::create($class->generatorType); + } + } + + /** @psalm-return ClassMetadata::GENERATOR_TYPE_* */ + private function determineIdGeneratorStrategy(AbstractPlatform $platform): int + { + assert($this->em !== null); + foreach ($this->em->getConfiguration()->getIdentityGenerationPreferences() as $platformFamily => $strategy) { + if (is_a($platform, $platformFamily)) { + return $strategy; + } + } + + $nonIdentityDefaultStrategy = self::NON_IDENTITY_DEFAULT_STRATEGY; + + // DBAL 3 + if (method_exists($platform, 'getIdentitySequenceName')) { + $nonIdentityDefaultStrategy[Platforms\PostgreSQLPlatform::class] = ClassMetadata::GENERATOR_TYPE_SEQUENCE; + } + + foreach ($nonIdentityDefaultStrategy as $platformFamily => $strategy) { + if (is_a($platform, $platformFamily)) { + if ($platform instanceof Platforms\PostgreSQLPlatform) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/issues/8893', + <<<'DEPRECATION' + Relying on non-optimal defaults for ID generation is deprecated, and IDENTITY + results in SERIAL, which is not recommended. + Instead, configure identifier generation strategies explicitly through + configuration. + We currently recommend "SEQUENCE" for "%s", when using DBAL 3, + and "IDENTITY" when using DBAL 4, + so you should probably use the following configuration before upgrading to DBAL 4, + and remove it after deploying that upgrade: + + $configuration->setIdentityGenerationPreferences([ + "%s" => ClassMetadata::GENERATOR_TYPE_SEQUENCE, + ]); + + DEPRECATION, + $platformFamily, + $platformFamily, + ); + } + + return $strategy; + } + } + + return ClassMetadata::GENERATOR_TYPE_IDENTITY; + } + + private function truncateSequenceName(string $schemaElementName): string + { + $platform = $this->getTargetPlatform(); + if (! $platform instanceof Platforms\OraclePlatform) { + return $schemaElementName; + } + + $maxIdentifierLength = $platform->getMaxIdentifierLength(); + + if (strlen($schemaElementName) > $maxIdentifierLength) { + return substr($schemaElementName, 0, $maxIdentifierLength); + } + + return $schemaElementName; + } + + /** + * Inherits the ID generator mapping from a parent class. + */ + private function inheritIdGeneratorMapping(ClassMetadata $class, ClassMetadata $parent): void + { + if ($parent->isIdGeneratorSequence()) { + $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition); + } + + if ($parent->generatorType) { + $class->setIdGeneratorType($parent->generatorType); + } + + if ($parent->idGenerator ?? null) { + $class->setIdGenerator($parent->idGenerator); + } + } + + protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService): void + { + $class->wakeupReflection($reflService); + } + + protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService): void + { + $class->initializeReflection($reflService); + } + + protected function getDriver(): MappingDriver + { + assert($this->driver !== null); + + return $this->driver; + } + + protected function isEntity(ClassMetadataInterface $class): bool + { + return ! $class->isMappedSuperclass; + } + + private function getTargetPlatform(): Platforms\AbstractPlatform + { + if (! $this->targetPlatform) { + $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); + } + + return $this->targetPlatform; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Column.php b/vendor/doctrine/orm/src/Mapping/Column.php new file mode 100644 index 0000000..68121e6 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Column.php @@ -0,0 +1,36 @@ +|null $enumType + * @param array $options + * @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated + */ + public function __construct( + public readonly string|null $name = null, + public readonly string|null $type = null, + public readonly int|null $length = null, + public readonly int|null $precision = null, + public readonly int|null $scale = null, + public readonly bool $unique = false, + public readonly bool $nullable = false, + public readonly bool $insertable = true, + public readonly bool $updatable = true, + public readonly string|null $enumType = null, + public readonly array $options = [], + public readonly string|null $columnDefinition = null, + public readonly string|null $generated = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/CustomIdGenerator.php b/vendor/doctrine/orm/src/Mapping/CustomIdGenerator.php new file mode 100644 index 0000000..7b31dc3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/CustomIdGenerator.php @@ -0,0 +1,16 @@ + Map to store entity listener instances. */ + private array $instances = []; + + public function clear(string|null $className = null): void + { + if ($className === null) { + $this->instances = []; + + return; + } + + $className = trim($className, '\\'); + unset($this->instances[$className]); + } + + public function register(object $object): void + { + $this->instances[$object::class] = $object; + } + + public function resolve(string $className): object + { + $className = trim($className, '\\'); + + return $this->instances[$className] ??= new $className(); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DefaultNamingStrategy.php b/vendor/doctrine/orm/src/Mapping/DefaultNamingStrategy.php new file mode 100644 index 0000000..15218f9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DefaultNamingStrategy.php @@ -0,0 +1,68 @@ +referenceColumnName(); + } + + public function joinTableName( + string $sourceEntity, + string $targetEntity, + string $propertyName, + ): string { + return strtolower($this->classToTableName($sourceEntity) . '_' . + $this->classToTableName($targetEntity)); + } + + public function joinKeyColumnName( + string $entityName, + string|null $referencedColumnName, + ): string { + return strtolower($this->classToTableName($entityName) . '_' . + ($referencedColumnName ?: $this->referenceColumnName())); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php b/vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php new file mode 100644 index 0000000..6260336 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php @@ -0,0 +1,145 @@ +fieldMappings[$fieldName]->quoted) + ? $platform->quoteIdentifier($class->fieldMappings[$fieldName]->columnName) + : $class->fieldMappings[$fieldName]->columnName; + } + + /** + * {@inheritDoc} + * + * @todo Table names should be computed in DBAL depending on the platform + */ + public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string + { + $tableName = $class->table['name']; + + if (! empty($class->table['schema'])) { + $tableName = $class->table['schema'] . '.' . $class->table['name']; + } + + return isset($class->table['quoted']) + ? $platform->quoteIdentifier($tableName) + : $tableName; + } + + /** + * {@inheritDoc} + */ + public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string + { + return isset($definition['quoted']) + ? $platform->quoteIdentifier($definition['sequenceName']) + : $definition['sequenceName']; + } + + public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string + { + return isset($joinColumn->quoted) + ? $platform->quoteIdentifier($joinColumn->name) + : $joinColumn->name; + } + + public function getReferencedJoinColumnName( + JoinColumnMapping $joinColumn, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + return isset($joinColumn->quoted) + ? $platform->quoteIdentifier($joinColumn->referencedColumnName) + : $joinColumn->referencedColumnName; + } + + public function getJoinTableName( + ManyToManyOwningSideMapping $association, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + $schema = ''; + + if (isset($association->joinTable->schema)) { + $schema = $association->joinTable->schema . '.'; + } + + $tableName = $association->joinTable->name; + + if (isset($association->joinTable->quoted)) { + $tableName = $platform->quoteIdentifier($tableName); + } + + return $schema . $tableName; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array + { + $quotedColumnNames = []; + + foreach ($class->identifier as $fieldName) { + if (isset($class->fieldMappings[$fieldName])) { + $quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform); + + continue; + } + + // Association defined as Id field + $assoc = $class->associationMappings[$fieldName]; + assert($assoc->isToOneOwningSide()); + $joinColumns = $assoc->joinColumns; + $assocQuotedColumnNames = array_map( + static fn (JoinColumnMapping $joinColumn) => isset($joinColumn->quoted) + ? $platform->quoteIdentifier($joinColumn->name) + : $joinColumn->name, + $joinColumns, + ); + + $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); + } + + return $quotedColumnNames; + } + + public function getColumnAlias( + string $columnName, + int $counter, + AbstractPlatform $platform, + ClassMetadata|null $class = null, + ): string { + // 1 ) Concatenate column name and counter + // 2 ) Trim the column alias to the maximum identifier length of the platform. + // If the alias is to long, characters are cut off from the beginning. + // 3 ) Strip non alphanumeric characters + // 4 ) Prefix with "_" if the result its numeric + $columnName .= '_' . $counter; + $columnName = substr($columnName, -$platform->getMaxIdentifierLength()); + $columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName); + $columnName = is_numeric($columnName) ? '_' . $columnName : $columnName; + + return $this->getSQLResultCasing($platform, $columnName); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php b/vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php new file mode 100644 index 0000000..49144b8 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php @@ -0,0 +1,80 @@ +|string> $typedFieldMappings */ + private array $typedFieldMappings; + + private const DEFAULT_TYPED_FIELD_MAPPINGS = [ + DateInterval::class => Types::DATEINTERVAL, + DateTime::class => Types::DATETIME_MUTABLE, + DateTimeImmutable::class => Types::DATETIME_IMMUTABLE, + 'array' => Types::JSON, + 'bool' => Types::BOOLEAN, + 'float' => Types::FLOAT, + 'int' => Types::INTEGER, + 'string' => Types::STRING, + ]; + + /** @param array|string> $typedFieldMappings */ + public function __construct(array $typedFieldMappings = []) + { + $this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings); + } + + /** + * {@inheritDoc} + */ + public function validateAndComplete(array $mapping, ReflectionProperty $field): array + { + $type = $field->getType(); + + if ( + ! isset($mapping['type']) + && ($type instanceof ReflectionNamedType) + ) { + if (! $type->isBuiltin() && enum_exists($type->getName())) { + $reflection = new ReflectionEnum($type->getName()); + if (! $reflection->isBacked()) { + throw MappingException::backedEnumTypeRequired( + $field->class, + $mapping['fieldName'], + $type->getName(), + ); + } + + assert(is_a($type->getName(), BackedEnum::class, true)); + $mapping['enumType'] = $type->getName(); + $type = $reflection->getBackingType(); + + assert($type instanceof ReflectionNamedType); + } + + if (isset($this->typedFieldMappings[$type->getName()])) { + $mapping['type'] = $this->typedFieldMappings[$type->getName()]; + } + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DiscriminatorColumn.php b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumn.php new file mode 100644 index 0000000..fb9c7d3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumn.php @@ -0,0 +1,24 @@ +|null */ + public readonly string|null $enumType = null, + /** @var array */ + public readonly array $options = [], + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DiscriminatorColumnMapping.php b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumnMapping.php new file mode 100644 index 0000000..4ccb71c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumnMapping.php @@ -0,0 +1,83 @@ + */ +final class DiscriminatorColumnMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + /** The database length of the column. Optional. Default value taken from the type. */ + public int|null $length = null; + + public string|null $columnDefinition = null; + + /** @var class-string|null */ + public string|null $enumType = null; + + /** @var array */ + public array $options = []; + + public function __construct( + public string $type, + public string $fieldName, + public string $name, + ) { + } + + /** + * @psalm-param array{ + * type: string, + * fieldName: string, + * name: string, + * length?: int|null, + * columnDefinition?: string|null, + * enumType?: class-string|null, + * options?: array|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self( + $mappingArray['type'], + $mappingArray['fieldName'], + $mappingArray['name'], + ); + foreach ($mappingArray as $key => $value) { + if (in_array($key, ['type', 'fieldName', 'name'])) { + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value ?? $mapping->$key; + } else { + throw new Exception('Unknown property ' . $key . ' on class ' . static::class); + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['type', 'fieldName', 'name']; + + foreach (['length', 'columnDefinition', 'enumType', 'options'] as $stringOrArrayKey) { + if ($this->$stringOrArrayKey !== null) { + $serialized[] = $stringOrArrayKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DiscriminatorMap.php b/vendor/doctrine/orm/src/Mapping/DiscriminatorMap.php new file mode 100644 index 0000000..2b204a9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DiscriminatorMap.php @@ -0,0 +1,17 @@ + $value */ + public function __construct( + public readonly array $value, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php new file mode 100644 index 0000000..9ba3481 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php @@ -0,0 +1,768 @@ + 1, + Mapping\MappedSuperclass::class => 2, + ]; + + private readonly AttributeReader $reader; + + /** + * @param array $paths + * @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0 + */ + public function __construct(array $paths, bool $reportFieldsWhereDeclared = true) + { + if (! $reportFieldsWhereDeclared) { + throw new InvalidArgumentException(sprintf( + 'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s.', + __METHOD__, + )); + } + + $this->reader = new AttributeReader(); + $this->addPaths($paths); + } + + public function isTransient(string $className): bool + { + $classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className)); + + foreach ($classAttributes as $a) { + $attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a; + if (isset(self::ENTITY_ATTRIBUTE_CLASSES[$attr::class])) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + * + * @psalm-param class-string $className + * @psalm-param ClassMetadata $metadata + * + * @template T of object + */ + public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void + { + $reflectionClass = $metadata->getReflectionClass() + // this happens when running attribute driver in combination with + // static reflection services. This is not the nicest fix + ?? new ReflectionClass($metadata->name); + + $classAttributes = $this->reader->getClassAttributes($reflectionClass); + + // Evaluate Entity attribute + if (isset($classAttributes[Mapping\Entity::class])) { + $entityAttribute = $classAttributes[Mapping\Entity::class]; + if ($entityAttribute->repositoryClass !== null) { + $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass); + } + + if ($entityAttribute->readOnly) { + $metadata->markReadOnly(); + } + } elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) { + $mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class]; + + $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass); + $metadata->isMappedSuperclass = true; + } elseif (isset($classAttributes[Mapping\Embeddable::class])) { + $metadata->isEmbeddedClass = true; + } else { + throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); + } + + $primaryTable = []; + + if (isset($classAttributes[Mapping\Table::class])) { + $tableAnnot = $classAttributes[Mapping\Table::class]; + $primaryTable['name'] = $tableAnnot->name; + $primaryTable['schema'] = $tableAnnot->schema; + + if ($tableAnnot->options) { + $primaryTable['options'] = $tableAnnot->options; + } + } + + if (isset($classAttributes[Mapping\Index::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Index::class); + } + + foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) { + $index = []; + + if (! empty($indexAnnot->columns)) { + $index['columns'] = $indexAnnot->columns; + } + + if (! empty($indexAnnot->fields)) { + $index['fields'] = $indexAnnot->fields; + } + + if ( + isset($index['columns'], $index['fields']) + || ( + ! isset($index['columns']) + && ! isset($index['fields']) + ) + ) { + throw MappingException::invalidIndexConfiguration( + $className, + (string) ($indexAnnot->name ?? $idx), + ); + } + + if (! empty($indexAnnot->flags)) { + $index['flags'] = $indexAnnot->flags; + } + + if (! empty($indexAnnot->options)) { + $index['options'] = $indexAnnot->options; + } + + if (! empty($indexAnnot->name)) { + $primaryTable['indexes'][$indexAnnot->name] = $index; + } else { + $primaryTable['indexes'][] = $index; + } + } + } + + if (isset($classAttributes[Mapping\UniqueConstraint::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\UniqueConstraint::class); + } + + foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) { + $uniqueConstraint = []; + + if (! empty($uniqueConstraintAnnot->columns)) { + $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns; + } + + if (! empty($uniqueConstraintAnnot->fields)) { + $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields; + } + + if ( + isset($uniqueConstraint['columns'], $uniqueConstraint['fields']) + || ( + ! isset($uniqueConstraint['columns']) + && ! isset($uniqueConstraint['fields']) + ) + ) { + throw MappingException::invalidUniqueConstraintConfiguration( + $className, + (string) ($uniqueConstraintAnnot->name ?? $idx), + ); + } + + if (! empty($uniqueConstraintAnnot->options)) { + $uniqueConstraint['options'] = $uniqueConstraintAnnot->options; + } + + if (! empty($uniqueConstraintAnnot->name)) { + $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; + } else { + $primaryTable['uniqueConstraints'][] = $uniqueConstraint; + } + } + } + + $metadata->setPrimaryTable($primaryTable); + + // Evaluate #[Cache] attribute + if (isset($classAttributes[Mapping\Cache::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Cache::class); + } + + $cacheAttribute = $classAttributes[Mapping\Cache::class]; + $cacheMap = [ + 'region' => $cacheAttribute->region, + 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), + ]; + + $metadata->enableCache($cacheMap); + } + + // Evaluate InheritanceType attribute + if (isset($classAttributes[Mapping\InheritanceType::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\InheritanceType::class); + } + + $inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class]; + + $metadata->setInheritanceType( + constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value), + ); + + if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { + // Evaluate DiscriminatorColumn attribute + if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) { + $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class]; + assert($discrColumnAttribute instanceof Mapping\DiscriminatorColumn); + + $columnDef = [ + 'name' => $discrColumnAttribute->name, + 'type' => $discrColumnAttribute->type ?? 'string', + 'length' => $discrColumnAttribute->length ?? 255, + 'columnDefinition' => $discrColumnAttribute->columnDefinition, + 'enumType' => $discrColumnAttribute->enumType, + ]; + + if ($discrColumnAttribute->options) { + $columnDef['options'] = $discrColumnAttribute->options; + } + + $metadata->setDiscriminatorColumn($columnDef); + } else { + $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); + } + + // Evaluate DiscriminatorMap attribute + if (isset($classAttributes[Mapping\DiscriminatorMap::class])) { + $discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class]; + $metadata->setDiscriminatorMap($discrMapAttribute->value); + } + } + } + + // Evaluate DoctrineChangeTrackingPolicy attribute + if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ChangeTrackingPolicy::class); + } + + $changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class]; + $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value)); + } + + foreach ($reflectionClass->getProperties() as $property) { + assert($property instanceof ReflectionProperty); + + if ($this->isRepeatedPropertyDeclaration($property, $metadata)) { + continue; + } + + $mapping = []; + $mapping['fieldName'] = $property->name; + + // Evaluate #[Cache] attribute + $cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class); + if ($cacheAttribute !== null) { + assert($cacheAttribute instanceof Mapping\Cache); + + $mapping['cache'] = $metadata->getAssociationCacheDefaults( + $mapping['fieldName'], + [ + 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), + 'region' => $cacheAttribute->region, + ], + ); + } + + // Check for JoinColumn/JoinColumns attributes + $joinColumns = []; + + $joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class); + + foreach ($joinColumnAttributes as $joinColumnAttribute) { + $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute); + } + + // Field can only be attributed with one of: + // Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded + $columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class); + $oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class); + $oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class); + $manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class); + $manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class); + $embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class); + + if ($columnAttribute !== null) { + $mapping = $this->columnToArray($property->name, $columnAttribute); + + if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { + $mapping['id'] = true; + } + + $generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class); + + if ($generatedValueAttribute !== null) { + $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy)); + } + + if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) { + $metadata->setVersionMapping($mapping); + } + + $metadata->mapField($mapping); + + // Check for SequenceGenerator/TableGenerator definition + $seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class); + $customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class); + + if ($seqGeneratorAttribute !== null) { + $metadata->setSequenceGeneratorDefinition( + [ + 'sequenceName' => $seqGeneratorAttribute->sequenceName, + 'allocationSize' => $seqGeneratorAttribute->allocationSize, + 'initialValue' => $seqGeneratorAttribute->initialValue, + ], + ); + } elseif ($customGeneratorAttribute !== null) { + $metadata->setCustomGeneratorDefinition( + [ + 'class' => $customGeneratorAttribute->class, + ], + ); + } + } elseif ($oneToOneAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToOne::class); + } + + if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { + $mapping['id'] = true; + } + + $mapping['targetEntity'] = $oneToOneAttribute->targetEntity; + $mapping['joinColumns'] = $joinColumns; + $mapping['mappedBy'] = $oneToOneAttribute->mappedBy; + $mapping['inversedBy'] = $oneToOneAttribute->inversedBy; + $mapping['cascade'] = $oneToOneAttribute->cascade; + $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch); + $metadata->mapOneToOne($mapping); + } elseif ($oneToManyAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class); + } + + $mapping['mappedBy'] = $oneToManyAttribute->mappedBy; + $mapping['targetEntity'] = $oneToManyAttribute->targetEntity; + $mapping['cascade'] = $oneToManyAttribute->cascade; + $mapping['indexBy'] = $oneToManyAttribute->indexBy; + $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch); + + $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); + + if ($orderByAttribute !== null) { + $mapping['orderBy'] = $orderByAttribute->value; + } + + $metadata->mapOneToMany($mapping); + } elseif ($manyToOneAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToOne::class); + } + + $idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class); + + if ($idAttribute !== null) { + $mapping['id'] = true; + } + + $mapping['joinColumns'] = $joinColumns; + $mapping['cascade'] = $manyToOneAttribute->cascade; + $mapping['inversedBy'] = $manyToOneAttribute->inversedBy; + $mapping['targetEntity'] = $manyToOneAttribute->targetEntity; + $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch); + $metadata->mapManyToOne($mapping); + } elseif ($manyToManyAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToMany::class); + } + + $joinTable = []; + $joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class); + + if ($joinTableAttribute !== null) { + $joinTable = [ + 'name' => $joinTableAttribute->name, + 'schema' => $joinTableAttribute->schema, + ]; + + if ($joinTableAttribute->options) { + $joinTable['options'] = $joinTableAttribute->options; + } + + foreach ($joinTableAttribute->joinColumns as $joinColumn) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); + } + + foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); + } + } + + foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); + } + + foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); + } + + $mapping['joinTable'] = $joinTable; + $mapping['targetEntity'] = $manyToManyAttribute->targetEntity; + $mapping['mappedBy'] = $manyToManyAttribute->mappedBy; + $mapping['inversedBy'] = $manyToManyAttribute->inversedBy; + $mapping['cascade'] = $manyToManyAttribute->cascade; + $mapping['indexBy'] = $manyToManyAttribute->indexBy; + $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch); + + $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); + + if ($orderByAttribute !== null) { + $mapping['orderBy'] = $orderByAttribute->value; + } + + $metadata->mapManyToMany($mapping); + } elseif ($embeddedAttribute !== null) { + $mapping['class'] = $embeddedAttribute->class; + $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix; + + $metadata->mapEmbedded($mapping); + } + } + + // Evaluate AssociationOverrides attribute + if (isset($classAttributes[Mapping\AssociationOverrides::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AssociationOverride::class); + } + + $associationOverride = $classAttributes[Mapping\AssociationOverrides::class]; + + foreach ($associationOverride->overrides as $associationOverride) { + $override = []; + $fieldName = $associationOverride->name; + + // Check for JoinColumn/JoinColumns attributes + if ($associationOverride->joinColumns) { + $joinColumns = []; + + foreach ($associationOverride->joinColumns as $joinColumn) { + $joinColumns[] = $this->joinColumnToArray($joinColumn); + } + + $override['joinColumns'] = $joinColumns; + } + + if ($associationOverride->inverseJoinColumns) { + $joinColumns = []; + + foreach ($associationOverride->inverseJoinColumns as $joinColumn) { + $joinColumns[] = $this->joinColumnToArray($joinColumn); + } + + $override['inverseJoinColumns'] = $joinColumns; + } + + // Check for JoinTable attributes + if ($associationOverride->joinTable) { + $joinTableAnnot = $associationOverride->joinTable; + $joinTable = [ + 'name' => $joinTableAnnot->name, + 'schema' => $joinTableAnnot->schema, + 'joinColumns' => $override['joinColumns'] ?? [], + 'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [], + ]; + + unset($override['joinColumns'], $override['inverseJoinColumns']); + + $override['joinTable'] = $joinTable; + } + + // Check for inversedBy + if ($associationOverride->inversedBy) { + $override['inversedBy'] = $associationOverride->inversedBy; + } + + // Check for `fetch` + if ($associationOverride->fetch) { + $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch); + } + + $metadata->setAssociationOverride($fieldName, $override); + } + } + + // Evaluate AttributeOverrides attribute + if (isset($classAttributes[Mapping\AttributeOverrides::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AttributeOverrides::class); + } + + $attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class]; + + foreach ($attributeOverridesAnnot->overrides as $attributeOverride) { + $mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column); + + $metadata->setAttributeOverride($attributeOverride->name, $mapping); + } + } + + // Evaluate EntityListeners attribute + if (isset($classAttributes[Mapping\EntityListeners::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\EntityListeners::class); + } + + $entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class]; + + foreach ($entityListenersAttribute->value as $item) { + $listenerClassName = $metadata->fullyQualifiedClassName($item); + + if (! class_exists($listenerClassName)) { + throw MappingException::entityListenerClassNotFound($listenerClassName, $className); + } + + $hasMapping = false; + $listenerClass = new ReflectionClass($listenerClassName); + + foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + assert($method instanceof ReflectionMethod); + // find method callbacks. + $callbacks = $this->getMethodCallbacks($method); + $hasMapping = $hasMapping ?: ! empty($callbacks); + + foreach ($callbacks as $value) { + $metadata->addEntityListener($value[1], $listenerClassName, $value[0]); + } + } + + // Evaluate the listener using naming convention. + if (! $hasMapping) { + EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName); + } + } + } + + // Evaluate #[HasLifecycleCallbacks] attribute + if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\HasLifecycleCallbacks::class); + } + + foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + assert($method instanceof ReflectionMethod); + foreach ($this->getMethodCallbacks($method) as $value) { + $metadata->addLifecycleCallback($value[0], $value[1]); + } + } + } + } + + /** + * Attempts to resolve the fetch mode. + * + * @param class-string $className The class name. + * @param string $fetchMode The fetch mode. + * + * @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata. + * + * @throws MappingException If the fetch mode is not valid. + */ + private function getFetchMode(string $className, string $fetchMode): int + { + if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { + throw MappingException::invalidFetchMode($className, $fetchMode); + } + + return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); + } + + /** + * Attempts to resolve the generated mode. + * + * @throws MappingException If the fetch mode is not valid. + */ + private function getGeneratedMode(string $generatedMode): int + { + if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) { + throw MappingException::invalidGeneratedMode($generatedMode); + } + + return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode); + } + + /** + * Parses the given method. + * + * @return list + * @psalm-return list + */ + private function getMethodCallbacks(ReflectionMethod $method): array + { + $callbacks = []; + $attributes = $this->reader->getMethodAttributes($method); + + foreach ($attributes as $attribute) { + if ($attribute instanceof Mapping\PrePersist) { + $callbacks[] = [$method->name, Events::prePersist]; + } + + if ($attribute instanceof Mapping\PostPersist) { + $callbacks[] = [$method->name, Events::postPersist]; + } + + if ($attribute instanceof Mapping\PreUpdate) { + $callbacks[] = [$method->name, Events::preUpdate]; + } + + if ($attribute instanceof Mapping\PostUpdate) { + $callbacks[] = [$method->name, Events::postUpdate]; + } + + if ($attribute instanceof Mapping\PreRemove) { + $callbacks[] = [$method->name, Events::preRemove]; + } + + if ($attribute instanceof Mapping\PostRemove) { + $callbacks[] = [$method->name, Events::postRemove]; + } + + if ($attribute instanceof Mapping\PostLoad) { + $callbacks[] = [$method->name, Events::postLoad]; + } + + if ($attribute instanceof Mapping\PreFlush) { + $callbacks[] = [$method->name, Events::preFlush]; + } + } + + return $callbacks; + } + + /** + * Parse the given JoinColumn as array + * + * @return mixed[] + * @psalm-return array{ + * name: string|null, + * unique: bool, + * nullable: bool, + * onDelete: mixed, + * columnDefinition: string|null, + * referencedColumnName: string, + * options?: array + * } + */ + private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn): array + { + $mapping = [ + 'name' => $joinColumn->name, + 'unique' => $joinColumn->unique, + 'nullable' => $joinColumn->nullable, + 'onDelete' => $joinColumn->onDelete, + 'columnDefinition' => $joinColumn->columnDefinition, + 'referencedColumnName' => $joinColumn->referencedColumnName, + ]; + + if ($joinColumn->options) { + $mapping['options'] = $joinColumn->options; + } + + return $mapping; + } + + /** + * Parse the given Column as array + * + * @return mixed[] + * @psalm-return array{ + * fieldName: string, + * type: mixed, + * scale: int, + * length: int, + * unique: bool, + * nullable: bool, + * precision: int, + * enumType?: class-string, + * options?: mixed[], + * columnName?: string, + * columnDefinition?: string + * } + */ + private function columnToArray(string $fieldName, Mapping\Column $column): array + { + $mapping = [ + 'fieldName' => $fieldName, + 'type' => $column->type, + 'scale' => $column->scale, + 'length' => $column->length, + 'unique' => $column->unique, + 'nullable' => $column->nullable, + 'precision' => $column->precision, + ]; + + if ($column->options) { + $mapping['options'] = $column->options; + } + + if (isset($column->name)) { + $mapping['columnName'] = $column->name; + } + + if (isset($column->columnDefinition)) { + $mapping['columnDefinition'] = $column->columnDefinition; + } + + if ($column->updatable === false) { + $mapping['notUpdatable'] = true; + } + + if ($column->insertable === false) { + $mapping['notInsertable'] = true; + } + + if ($column->generated !== null) { + $mapping['generated'] = $this->getGeneratedMode($column->generated); + } + + if ($column->enumType) { + $mapping['enumType'] = $column->enumType; + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php b/vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php new file mode 100644 index 0000000..2de622a --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php @@ -0,0 +1,146 @@ +, bool> */ + private array $isRepeatableAttribute = []; + + /** + * @psalm-return class-string-map> + * + * @template T of MappingAttribute + */ + public function getClassAttributes(ReflectionClass $class): array + { + return $this->convertToAttributeInstances($class->getAttributes()); + } + + /** + * @return class-string-map> + * + * @template T of MappingAttribute + */ + public function getMethodAttributes(ReflectionMethod $method): array + { + return $this->convertToAttributeInstances($method->getAttributes()); + } + + /** + * @return class-string-map> + * + * @template T of MappingAttribute + */ + public function getPropertyAttributes(ReflectionProperty $property): array + { + return $this->convertToAttributeInstances($property->getAttributes()); + } + + /** + * @param class-string $attributeName The name of the annotation. + * + * @return T|null + * + * @template T of MappingAttribute + */ + public function getPropertyAttribute(ReflectionProperty $property, string $attributeName) + { + if ($this->isRepeatable($attributeName)) { + throw new LogicException(sprintf( + 'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.', + $attributeName, + )); + } + + return $this->getPropertyAttributes($property)[$attributeName] ?? null; + } + + /** + * @param class-string $attributeName The name of the annotation. + * + * @return RepeatableAttributeCollection + * + * @template T of MappingAttribute + */ + public function getPropertyAttributeCollection( + ReflectionProperty $property, + string $attributeName, + ): RepeatableAttributeCollection { + if (! $this->isRepeatable($attributeName)) { + throw new LogicException(sprintf( + 'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.', + $attributeName, + )); + } + + return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection(); + } + + /** + * @param array $attributes + * + * @return class-string-map> + * + * @template T of MappingAttribute + */ + private function convertToAttributeInstances(array $attributes): array + { + $instances = []; + + foreach ($attributes as $attribute) { + $attributeName = $attribute->getName(); + assert(is_string($attributeName)); + // Make sure we only get Doctrine Attributes + if (! is_subclass_of($attributeName, MappingAttribute::class)) { + continue; + } + + $instance = $attribute->newInstance(); + assert($instance instanceof MappingAttribute); + + if ($this->isRepeatable($attributeName)) { + if (! isset($instances[$attributeName])) { + $instances[$attributeName] = new RepeatableAttributeCollection(); + } + + $collection = $instances[$attributeName]; + assert($collection instanceof RepeatableAttributeCollection); + $collection[] = $instance; + } else { + $instances[$attributeName] = $instance; + } + } + + return $instances; + } + + /** @param class-string $attributeClassName */ + private function isRepeatable(string $attributeClassName): bool + { + if (isset($this->isRepeatableAttribute[$attributeClassName])) { + return $this->isRepeatableAttribute[$attributeClassName]; + } + + $reflectionClass = new ReflectionClass($attributeClassName); + $attribute = $reflectionClass->getAttributes()[0]->newInstance(); + + return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php new file mode 100644 index 0000000..49e2e93 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php @@ -0,0 +1,528 @@ +|null */ + private array|null $tables = null; + + /** @var array */ + private array $classToTableNames = []; + + /** @psalm-var array */ + private array $manyToManyTables = []; + + /** @var mixed[] */ + private array $classNamesForTables = []; + + /** @var mixed[] */ + private array $fieldNamesForColumns = []; + + /** + * The namespace for the generated entities. + */ + private string|null $namespace = null; + + private Inflector $inflector; + + public function __construct(private readonly AbstractSchemaManager $sm) + { + $this->inflector = InflectorFactory::create()->build(); + } + + /** + * Set the namespace for the generated entities. + */ + public function setNamespace(string $namespace): void + { + $this->namespace = $namespace; + } + + public function isTransient(string $className): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames(): array + { + $this->reverseEngineerMappingFromDatabase(); + + return array_keys($this->classToTableNames); + } + + /** + * Sets class name for a table. + */ + public function setClassNameForTable(string $tableName, string $className): void + { + $this->classNamesForTables[$tableName] = $className; + } + + /** + * Sets field name for a column on a specific table. + */ + public function setFieldNameForColumn(string $tableName, string $columnName, string $fieldName): void + { + $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName; + } + + /** + * Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager. + * + * @param Table[] $entityTables + * @param Table[] $manyToManyTables + * @psalm-param list $entityTables + * @psalm-param list
$manyToManyTables + */ + public function setTables(array $entityTables, array $manyToManyTables): void + { + $this->tables = $this->manyToManyTables = $this->classToTableNames = []; + + foreach ($entityTables as $table) { + $className = $this->getClassNameForTable($table->getName()); + + $this->classToTableNames[$className] = $table->getName(); + $this->tables[$table->getName()] = $table; + } + + foreach ($manyToManyTables as $table) { + $this->manyToManyTables[$table->getName()] = $table; + } + } + + public function setInflector(Inflector $inflector): void + { + $this->inflector = $inflector; + } + + /** + * {@inheritDoc} + * + * @psalm-param class-string $className + * @psalm-param ClassMetadata $metadata + * + * @template T of object + */ + public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void + { + if (! $metadata instanceof ClassMetadata) { + throw new TypeError(sprintf( + 'Argument #2 passed to %s() must be an instance of %s, %s given.', + __METHOD__, + ClassMetadata::class, + get_debug_type($metadata), + )); + } + + $this->reverseEngineerMappingFromDatabase(); + + if (! isset($this->classToTableNames[$className])) { + throw new InvalidArgumentException('Unknown class ' . $className); + } + + $tableName = $this->classToTableNames[$className]; + + $metadata->name = $className; + $metadata->table['name'] = $tableName; + + $this->buildIndexes($metadata); + $this->buildFieldMappings($metadata); + $this->buildToOneAssociationMappings($metadata); + + foreach ($this->manyToManyTables as $manyTable) { + foreach ($manyTable->getForeignKeys() as $foreignKey) { + // foreign key maps to the table of the current entity, many to many association probably exists + if (! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) { + continue; + } + + $myFk = $foreignKey; + $otherFk = null; + + foreach ($manyTable->getForeignKeys() as $foreignKey) { + if ($foreignKey !== $myFk) { + $otherFk = $foreignKey; + break; + } + } + + if (! $otherFk) { + // the definition of this many to many table does not contain + // enough foreign key information to continue reverse engineering. + continue; + } + + $localColumn = current($myFk->getLocalColumns()); + + $associationMapping = []; + $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getLocalColumns()), true); + $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); + + if (current($manyTable->getColumns())->getName() === $localColumn) { + $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); + $associationMapping['joinTable'] = [ + 'name' => strtolower($manyTable->getName()), + 'joinColumns' => [], + 'inverseJoinColumns' => [], + ]; + + $fkCols = $myFk->getForeignColumns(); + $cols = $myFk->getLocalColumns(); + + for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { + $associationMapping['joinTable']['joinColumns'][] = [ + 'name' => $cols[$i], + 'referencedColumnName' => $fkCols[$i], + ]; + } + + $fkCols = $otherFk->getForeignColumns(); + $cols = $otherFk->getLocalColumns(); + + for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { + $associationMapping['joinTable']['inverseJoinColumns'][] = [ + 'name' => $cols[$i], + 'referencedColumnName' => $fkCols[$i], + ]; + } + } else { + $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); + } + + $metadata->mapManyToMany($associationMapping); + + break; + } + } + } + + /** @throws MappingException */ + private function reverseEngineerMappingFromDatabase(): void + { + if ($this->tables !== null) { + return; + } + + $this->tables = $this->manyToManyTables = $this->classToTableNames = []; + + foreach ($this->sm->listTables() as $table) { + $tableName = $table->getName(); + $foreignKeys = $table->getForeignKeys(); + + $allForeignKeyColumns = []; + + foreach ($foreignKeys as $foreignKey) { + $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); + } + + $primaryKey = $table->getPrimaryKey(); + if ($primaryKey === null) { + throw new MappingException( + 'Table ' . $tableName . ' has no primary key. Doctrine does not ' . + "support reverse engineering from tables that don't have a primary key.", + ); + } + + $pkColumns = $primaryKey->getColumns(); + + sort($pkColumns); + sort($allForeignKeyColumns); + + if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) { + $this->manyToManyTables[$tableName] = $table; + } else { + // lower-casing is necessary because of Oracle Uppercase Tablenames, + // assumption is lower-case + underscore separated. + $className = $this->getClassNameForTable($tableName); + + $this->tables[$tableName] = $table; + $this->classToTableNames[$className] = $tableName; + } + } + } + + /** + * Build indexes from a class metadata. + */ + private function buildIndexes(ClassMetadata $metadata): void + { + $tableName = $metadata->table['name']; + $indexes = $this->tables[$tableName]->getIndexes(); + + foreach ($indexes as $index) { + if ($index->isPrimary()) { + continue; + } + + $indexName = $index->getName(); + $indexColumns = $index->getColumns(); + $constraintType = $index->isUnique() + ? 'uniqueConstraints' + : 'indexes'; + + $metadata->table[$constraintType][$indexName]['columns'] = $indexColumns; + } + } + + /** + * Build field mapping from class metadata. + */ + private function buildFieldMappings(ClassMetadata $metadata): void + { + $tableName = $metadata->table['name']; + $columns = $this->tables[$tableName]->getColumns(); + $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); + $foreignKeys = $this->tables[$tableName]->getForeignKeys(); + $allForeignKeys = []; + + foreach ($foreignKeys as $foreignKey) { + $allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns()); + } + + $ids = []; + $fieldMappings = []; + + foreach ($columns as $column) { + if (in_array($column->getName(), $allForeignKeys, true)) { + continue; + } + + $fieldMapping = $this->buildFieldMapping($tableName, $column); + + if ($primaryKeys && in_array($column->getName(), $primaryKeys, true)) { + $fieldMapping['id'] = true; + $ids[] = $fieldMapping; + } + + $fieldMappings[] = $fieldMapping; + } + + // We need to check for the columns here, because we might have associations as id as well. + if ($ids && count($primaryKeys) === 1) { + $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); + } + + foreach ($fieldMappings as $fieldMapping) { + $metadata->mapField($fieldMapping); + } + } + + /** + * Build field mapping from a schema column definition + * + * @return mixed[] + * @psalm-return array{ + * fieldName: string, + * columnName: string, + * type: string, + * nullable: bool, + * options: array{ + * unsigned?: bool, + * fixed?: bool, + * comment: string|null, + * default?: mixed + * }, + * precision?: int, + * scale?: int, + * length?: int|null + * } + */ + private function buildFieldMapping(string $tableName, Column $column): array + { + $fieldMapping = [ + 'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false), + 'columnName' => $column->getName(), + 'type' => Type::getTypeRegistry()->lookupName($column->getType()), + 'nullable' => ! $column->getNotnull(), + 'options' => [ + 'comment' => $column->getComment(), + ], + ]; + + // Type specific elements + switch ($fieldMapping['type']) { + case self::ARRAY: + case Types::BLOB: + case Types::GUID: + case self::OBJECT: + case Types::SIMPLE_ARRAY: + case Types::STRING: + case Types::TEXT: + $fieldMapping['length'] = $column->getLength(); + $fieldMapping['options']['fixed'] = $column->getFixed(); + break; + + case Types::DECIMAL: + case Types::FLOAT: + $fieldMapping['precision'] = $column->getPrecision(); + $fieldMapping['scale'] = $column->getScale(); + break; + + case Types::INTEGER: + case Types::BIGINT: + case Types::SMALLINT: + $fieldMapping['options']['unsigned'] = $column->getUnsigned(); + break; + } + + // Default + $default = $column->getDefault(); + if ($default !== null) { + $fieldMapping['options']['default'] = $default; + } + + return $fieldMapping; + } + + /** + * Build to one (one to one, many to one) association mapping from class metadata. + */ + private function buildToOneAssociationMappings(ClassMetadata $metadata): void + { + assert($this->tables !== null); + + $tableName = $metadata->table['name']; + $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); + $foreignKeys = $this->tables[$tableName]->getForeignKeys(); + + foreach ($foreignKeys as $foreignKey) { + $foreignTableName = $foreignKey->getForeignTableName(); + $fkColumns = $foreignKey->getLocalColumns(); + $fkForeignColumns = $foreignKey->getForeignColumns(); + $localColumn = current($fkColumns); + $associationMapping = [ + 'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true), + 'targetEntity' => $this->getClassNameForTable($foreignTableName), + ]; + + if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) { + $associationMapping['fieldName'] .= '2'; // "foo" => "foo2" + } + + if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) { + $associationMapping['id'] = true; + } + + for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) { + $associationMapping['joinColumns'][] = [ + 'name' => $fkColumns[$i], + 'referencedColumnName' => $fkForeignColumns[$i], + ]; + } + + // Here we need to check if $fkColumns are the same as $primaryKeys + if (! array_diff($fkColumns, $primaryKeys)) { + $metadata->mapOneToOne($associationMapping); + } else { + $metadata->mapManyToOne($associationMapping); + } + } + } + + /** + * Retrieve schema table definition primary keys. + * + * @return string[] + */ + private function getTablePrimaryKeys(Table $table): array + { + try { + return $table->getPrimaryKey()->getColumns(); + } catch (SchemaException) { + // Do nothing + } + + return []; + } + + /** + * Returns the mapped class name for a table if it exists. Otherwise return "classified" version. + * + * @psalm-return class-string + */ + private function getClassNameForTable(string $tableName): string + { + if (isset($this->classNamesForTables[$tableName])) { + return $this->namespace . $this->classNamesForTables[$tableName]; + } + + return $this->namespace . $this->inflector->classify(strtolower($tableName)); + } + + /** + * Return the mapped field name for a column, if it exists. Otherwise return camelized version. + * + * @param bool $fk Whether the column is a foreignkey or not. + */ + private function getFieldNameForColumn( + string $tableName, + string $columnName, + bool $fk = false, + ): string { + if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) { + return $this->fieldNamesForColumns[$tableName][$columnName]; + } + + $columnName = strtolower($columnName); + + // Replace _id if it is a foreignkey column + if ($fk) { + $columnName = preg_replace('/_id$/', '', $columnName); + } + + return $this->inflector->camelize($columnName); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php new file mode 100644 index 0000000..7d85471 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php @@ -0,0 +1,44 @@ +class; + + if ( + isset($metadata->fieldMappings[$property->name]->declared) + && $metadata->fieldMappings[$property->name]->declared === $declaringClass + ) { + return true; + } + + if ( + isset($metadata->associationMappings[$property->name]->declared) + && $metadata->associationMappings[$property->name]->declared === $declaringClass + ) { + return true; + } + + return isset($metadata->embeddedClasses[$property->name]->declared) + && $metadata->embeddedClasses[$property->name]->declared === $declaringClass; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php b/vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php new file mode 100644 index 0000000..2f6ae93 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php @@ -0,0 +1,16 @@ + + * @template T of MappingAttribute + */ +final class RepeatableAttributeCollection extends ArrayObject +{ +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php new file mode 100644 index 0000000..486185f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php @@ -0,0 +1,25 @@ + + */ +class XmlDriver extends FileDriver +{ + public const DEFAULT_FILE_EXTENSION = '.dcm.xml'; + + /** + * {@inheritDoc} + */ + public function __construct( + string|array|FileLocator $locator, + string $fileExtension = self::DEFAULT_FILE_EXTENSION, + private readonly bool $isXsdValidationEnabled = true, + ) { + if (! extension_loaded('simplexml')) { + throw new LogicException( + 'The XML metadata driver cannot be enabled because the SimpleXML PHP extension is missing.' + . ' Please configure PHP with SimpleXML or choose a different metadata driver.', + ); + } + + if ($isXsdValidationEnabled && ! extension_loaded('dom')) { + throw new LogicException( + 'XSD validation cannot be enabled because the DOM extension is missing.', + ); + } + + parent::__construct($locator, $fileExtension); + } + + /** + * {@inheritDoc} + * + * @psalm-param class-string $className + * @psalm-param ClassMetadata $metadata + * + * @template T of object + */ + public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void + { + $xmlRoot = $this->getElement($className); + + if ($xmlRoot->getName() === 'entity') { + if (isset($xmlRoot['repository-class'])) { + $metadata->setCustomRepositoryClass((string) $xmlRoot['repository-class']); + } + + if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) { + $metadata->markReadOnly(); + } + } elseif ($xmlRoot->getName() === 'mapped-superclass') { + $metadata->setCustomRepositoryClass( + isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null, + ); + $metadata->isMappedSuperclass = true; + } elseif ($xmlRoot->getName() === 'embeddable') { + $metadata->isEmbeddedClass = true; + } else { + throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); + } + + // Evaluate attributes + $primaryTable = []; + + if (isset($xmlRoot['table'])) { + $primaryTable['name'] = (string) $xmlRoot['table']; + } + + if (isset($xmlRoot['schema'])) { + $primaryTable['schema'] = (string) $xmlRoot['schema']; + } + + $metadata->setPrimaryTable($primaryTable); + + // Evaluate second level cache + if (isset($xmlRoot->cache)) { + $metadata->enableCache($this->cacheToArray($xmlRoot->cache)); + } + + if (isset($xmlRoot['inheritance-type'])) { + $inheritanceType = (string) $xmlRoot['inheritance-type']; + $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); + + if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { + // Evaluate + if (isset($xmlRoot->{'discriminator-column'})) { + $discrColumn = $xmlRoot->{'discriminator-column'}; + $columnDef = [ + 'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null, + 'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string', + 'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255, + 'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null, + 'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null, + ]; + + if (isset($discrColumn['options'])) { + assert($discrColumn['options'] instanceof SimpleXMLElement); + $columnDef['options'] = $this->parseOptions($discrColumn['options']->children()); + } + + $metadata->setDiscriminatorColumn($columnDef); + } else { + $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); + } + + // Evaluate + if (isset($xmlRoot->{'discriminator-map'})) { + $map = []; + assert($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} instanceof SimpleXMLElement); + foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) { + $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class']; + } + + $metadata->setDiscriminatorMap($map); + } + } + } + + // Evaluate + if (isset($xmlRoot['change-tracking-policy'])) { + $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' + . strtoupper((string) $xmlRoot['change-tracking-policy']))); + } + + // Evaluate + if (isset($xmlRoot->indexes)) { + $metadata->table['indexes'] = []; + foreach ($xmlRoot->indexes->index ?? [] as $indexXml) { + $index = []; + + if (isset($indexXml['columns']) && ! empty($indexXml['columns'])) { + $index['columns'] = explode(',', (string) $indexXml['columns']); + } + + if (isset($indexXml['fields'])) { + $index['fields'] = explode(',', (string) $indexXml['fields']); + } + + if ( + isset($index['columns'], $index['fields']) + || ( + ! isset($index['columns']) + && ! isset($index['fields']) + ) + ) { + throw MappingException::invalidIndexConfiguration( + $className, + (string) ($indexXml['name'] ?? count($metadata->table['indexes'])), + ); + } + + if (isset($indexXml['flags'])) { + $index['flags'] = explode(',', (string) $indexXml['flags']); + } + + if (isset($indexXml->options)) { + $index['options'] = $this->parseOptions($indexXml->options->children()); + } + + if (isset($indexXml['name'])) { + $metadata->table['indexes'][(string) $indexXml['name']] = $index; + } else { + $metadata->table['indexes'][] = $index; + } + } + } + + // Evaluate + if (isset($xmlRoot->{'unique-constraints'})) { + $metadata->table['uniqueConstraints'] = []; + foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} ?? [] as $uniqueXml) { + $unique = []; + + if (isset($uniqueXml['columns']) && ! empty($uniqueXml['columns'])) { + $unique['columns'] = explode(',', (string) $uniqueXml['columns']); + } + + if (isset($uniqueXml['fields'])) { + $unique['fields'] = explode(',', (string) $uniqueXml['fields']); + } + + if ( + isset($unique['columns'], $unique['fields']) + || ( + ! isset($unique['columns']) + && ! isset($unique['fields']) + ) + ) { + throw MappingException::invalidUniqueConstraintConfiguration( + $className, + (string) ($uniqueXml['name'] ?? count($metadata->table['uniqueConstraints'])), + ); + } + + if (isset($uniqueXml->options)) { + $unique['options'] = $this->parseOptions($uniqueXml->options->children()); + } + + if (isset($uniqueXml['name'])) { + $metadata->table['uniqueConstraints'][(string) $uniqueXml['name']] = $unique; + } else { + $metadata->table['uniqueConstraints'][] = $unique; + } + } + } + + if (isset($xmlRoot->options)) { + $metadata->table['options'] = $this->parseOptions($xmlRoot->options->children()); + } + + // The mapping assignment is done in 2 times as a bug might occurs on some php/xml lib versions + // The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception + // Evaluate mappings + if (isset($xmlRoot->field)) { + foreach ($xmlRoot->field as $fieldMapping) { + $mapping = $this->columnToArray($fieldMapping); + + if (isset($mapping['version'])) { + $metadata->setVersionMapping($mapping); + unset($mapping['version']); + } + + $metadata->mapField($mapping); + } + } + + if (isset($xmlRoot->embedded)) { + foreach ($xmlRoot->embedded as $embeddedMapping) { + $columnPrefix = isset($embeddedMapping['column-prefix']) + ? (string) $embeddedMapping['column-prefix'] + : null; + + $useColumnPrefix = isset($embeddedMapping['use-column-prefix']) + ? $this->evaluateBoolean($embeddedMapping['use-column-prefix']) + : true; + + $mapping = [ + 'fieldName' => (string) $embeddedMapping['name'], + 'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null, + 'columnPrefix' => $useColumnPrefix ? $columnPrefix : false, + ]; + + $metadata->mapEmbedded($mapping); + } + } + + // Evaluate mappings + $associationIds = []; + foreach ($xmlRoot->id ?? [] as $idElement) { + if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) { + $associationIds[(string) $idElement['name']] = true; + continue; + } + + $mapping = $this->columnToArray($idElement); + $mapping['id'] = true; + + $metadata->mapField($mapping); + + if (isset($idElement->generator)) { + $strategy = isset($idElement->generator['strategy']) ? + (string) $idElement->generator['strategy'] : 'AUTO'; + $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' + . $strategy)); + } + + // Check for SequenceGenerator/TableGenerator definition + if (isset($idElement->{'sequence-generator'})) { + $seqGenerator = $idElement->{'sequence-generator'}; + $metadata->setSequenceGeneratorDefinition( + [ + 'sequenceName' => (string) $seqGenerator['sequence-name'], + 'allocationSize' => (string) $seqGenerator['allocation-size'], + 'initialValue' => (string) $seqGenerator['initial-value'], + ], + ); + } elseif (isset($idElement->{'custom-id-generator'})) { + $customGenerator = $idElement->{'custom-id-generator'}; + $metadata->setCustomGeneratorDefinition( + [ + 'class' => (string) $customGenerator['class'], + ], + ); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'one-to-one'})) { + foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) { + $mapping = [ + 'fieldName' => (string) $oneToOneElement['field'], + ]; + + if (isset($oneToOneElement['target-entity'])) { + $mapping['targetEntity'] = (string) $oneToOneElement['target-entity']; + } + + if (isset($associationIds[$mapping['fieldName']])) { + $mapping['id'] = true; + } + + if (isset($oneToOneElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToOneElement['fetch']); + } + + if (isset($oneToOneElement['mapped-by'])) { + $mapping['mappedBy'] = (string) $oneToOneElement['mapped-by']; + } else { + if (isset($oneToOneElement['inversed-by'])) { + $mapping['inversedBy'] = (string) $oneToOneElement['inversed-by']; + } + + $joinColumns = []; + + if (isset($oneToOneElement->{'join-column'})) { + $joinColumns[] = $this->joinColumnToArray($oneToOneElement->{'join-column'}); + } elseif (isset($oneToOneElement->{'join-columns'})) { + foreach ($oneToOneElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinColumns[] = $this->joinColumnToArray($joinColumnElement); + } + } + + $mapping['joinColumns'] = $joinColumns; + } + + if (isset($oneToOneElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($oneToOneElement->cascade); + } + + if (isset($oneToOneElement['orphan-removal'])) { + $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']); + } + + // Evaluate second level cache + if (isset($oneToOneElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache)); + } + + $metadata->mapOneToOne($mapping); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'one-to-many'})) { + foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) { + $mapping = [ + 'fieldName' => (string) $oneToManyElement['field'], + 'mappedBy' => (string) $oneToManyElement['mapped-by'], + ]; + + if (isset($oneToManyElement['target-entity'])) { + $mapping['targetEntity'] = (string) $oneToManyElement['target-entity']; + } + + if (isset($oneToManyElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToManyElement['fetch']); + } + + if (isset($oneToManyElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($oneToManyElement->cascade); + } + + if (isset($oneToManyElement['orphan-removal'])) { + $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToManyElement['orphan-removal']); + } + + if (isset($oneToManyElement->{'order-by'})) { + $orderBy = []; + foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) { + /** @psalm-suppress DeprecatedConstant */ + $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) + ? (string) $orderByField['direction'] + : (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC); + } + + $mapping['orderBy'] = $orderBy; + } + + if (isset($oneToManyElement['index-by'])) { + $mapping['indexBy'] = (string) $oneToManyElement['index-by']; + } elseif (isset($oneToManyElement->{'index-by'})) { + throw new InvalidArgumentException(' is not a valid tag'); + } + + // Evaluate second level cache + if (isset($oneToManyElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache)); + } + + $metadata->mapOneToMany($mapping); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'many-to-one'})) { + foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) { + $mapping = [ + 'fieldName' => (string) $manyToOneElement['field'], + ]; + + if (isset($manyToOneElement['target-entity'])) { + $mapping['targetEntity'] = (string) $manyToOneElement['target-entity']; + } + + if (isset($associationIds[$mapping['fieldName']])) { + $mapping['id'] = true; + } + + if (isset($manyToOneElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToOneElement['fetch']); + } + + if (isset($manyToOneElement['inversed-by'])) { + $mapping['inversedBy'] = (string) $manyToOneElement['inversed-by']; + } + + $joinColumns = []; + + if (isset($manyToOneElement->{'join-column'})) { + $joinColumns[] = $this->joinColumnToArray($manyToOneElement->{'join-column'}); + } elseif (isset($manyToOneElement->{'join-columns'})) { + foreach ($manyToOneElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinColumns[] = $this->joinColumnToArray($joinColumnElement); + } + } + + $mapping['joinColumns'] = $joinColumns; + + if (isset($manyToOneElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($manyToOneElement->cascade); + } + + // Evaluate second level cache + if (isset($manyToOneElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache)); + } + + $metadata->mapManyToOne($mapping); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'many-to-many'})) { + foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) { + $mapping = [ + 'fieldName' => (string) $manyToManyElement['field'], + ]; + + if (isset($manyToManyElement['target-entity'])) { + $mapping['targetEntity'] = (string) $manyToManyElement['target-entity']; + } + + if (isset($manyToManyElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToManyElement['fetch']); + } + + if (isset($manyToManyElement['orphan-removal'])) { + $mapping['orphanRemoval'] = $this->evaluateBoolean($manyToManyElement['orphan-removal']); + } + + if (isset($manyToManyElement['mapped-by'])) { + $mapping['mappedBy'] = (string) $manyToManyElement['mapped-by']; + } elseif (isset($manyToManyElement->{'join-table'})) { + if (isset($manyToManyElement['inversed-by'])) { + $mapping['inversedBy'] = (string) $manyToManyElement['inversed-by']; + } + + $joinTableElement = $manyToManyElement->{'join-table'}; + $joinTable = [ + 'name' => (string) $joinTableElement['name'], + ]; + + if (isset($joinTableElement['schema'])) { + $joinTable['schema'] = (string) $joinTableElement['schema']; + } + + if (isset($joinTableElement->options)) { + $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); + } + + foreach ($joinTableElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + + foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + + $mapping['joinTable'] = $joinTable; + } + + if (isset($manyToManyElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($manyToManyElement->cascade); + } + + if (isset($manyToManyElement->{'order-by'})) { + $orderBy = []; + foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) { + /** @psalm-suppress DeprecatedConstant */ + $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) + ? (string) $orderByField['direction'] + : (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC); + } + + $mapping['orderBy'] = $orderBy; + } + + if (isset($manyToManyElement['index-by'])) { + $mapping['indexBy'] = (string) $manyToManyElement['index-by']; + } elseif (isset($manyToManyElement->{'index-by'})) { + throw new InvalidArgumentException(' is not a valid tag'); + } + + // Evaluate second level cache + if (isset($manyToManyElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache)); + } + + $metadata->mapManyToMany($mapping); + } + } + + // Evaluate association-overrides + if (isset($xmlRoot->{'attribute-overrides'})) { + foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} ?? [] as $overrideElement) { + $fieldName = (string) $overrideElement['name']; + foreach ($overrideElement->field ?? [] as $field) { + $mapping = $this->columnToArray($field); + $mapping['fieldName'] = $fieldName; + $metadata->setAttributeOverride($fieldName, $mapping); + } + } + } + + // Evaluate association-overrides + if (isset($xmlRoot->{'association-overrides'})) { + foreach ($xmlRoot->{'association-overrides'}->{'association-override'} ?? [] as $overrideElement) { + $fieldName = (string) $overrideElement['name']; + $override = []; + + // Check for join-columns + if (isset($overrideElement->{'join-columns'})) { + $joinColumns = []; + foreach ($overrideElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinColumns[] = $this->joinColumnToArray($joinColumnElement); + } + + $override['joinColumns'] = $joinColumns; + } + + // Check for join-table + if ($overrideElement->{'join-table'}) { + $joinTable = null; + $joinTableElement = $overrideElement->{'join-table'}; + + $joinTable = [ + 'name' => (string) $joinTableElement['name'], + 'schema' => (string) $joinTableElement['schema'], + ]; + + if (isset($joinTableElement->options)) { + $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); + } + + if (isset($joinTableElement->{'join-columns'})) { + foreach ($joinTableElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + } + + if (isset($joinTableElement->{'inverse-join-columns'})) { + foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + } + + $override['joinTable'] = $joinTable; + } + + // Check for inversed-by + if (isset($overrideElement->{'inversed-by'})) { + $override['inversedBy'] = (string) $overrideElement->{'inversed-by'}['name']; + } + + // Check for `fetch` + if (isset($overrideElement['fetch'])) { + $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . (string) $overrideElement['fetch']); + } + + $metadata->setAssociationOverride($fieldName, $override); + } + } + + // Evaluate + if (isset($xmlRoot->{'lifecycle-callbacks'})) { + foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} ?? [] as $lifecycleCallback) { + $metadata->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string) $lifecycleCallback['type'])); + } + } + + // Evaluate entity listener + if (isset($xmlRoot->{'entity-listeners'})) { + foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} ?? [] as $listenerElement) { + $className = (string) $listenerElement['class']; + // Evaluate the listener using naming convention. + if ($listenerElement->count() === 0) { + EntityListenerBuilder::bindEntityListener($metadata, $className); + + continue; + } + + foreach ($listenerElement as $callbackElement) { + $eventName = (string) $callbackElement['type']; + $methodName = (string) $callbackElement['method']; + + $metadata->addEntityListener($eventName, $className, $methodName); + } + } + } + } + + /** + * Parses (nested) option elements. + * + * @return mixed[] The options array. + * @psalm-return array|bool|string> + */ + private function parseOptions(SimpleXMLElement|null $options): array + { + $array = []; + + foreach ($options ?? [] as $option) { + if ($option->count()) { + $value = $this->parseOptions($option->children()); + } else { + $value = (string) $option; + } + + $attributes = $option->attributes(); + + if (isset($attributes->name)) { + $nameAttribute = (string) $attributes->name; + $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true) + ? $this->evaluateBoolean($value) + : $value; + } else { + $array[] = $value; + } + } + + return $array; + } + + /** + * Constructs a joinColumn mapping array based on the information + * found in the given SimpleXMLElement. + * + * @param SimpleXMLElement $joinColumnElement The XML element. + * + * @return mixed[] The mapping array. + * @psalm-return array{ + * name: string, + * referencedColumnName: string, + * unique?: bool, + * nullable?: bool, + * onDelete?: string, + * columnDefinition?: string, + * options?: mixed[] + * } + */ + private function joinColumnToArray(SimpleXMLElement $joinColumnElement): array + { + $joinColumn = [ + 'name' => (string) $joinColumnElement['name'], + 'referencedColumnName' => (string) $joinColumnElement['referenced-column-name'], + ]; + + if (isset($joinColumnElement['unique'])) { + $joinColumn['unique'] = $this->evaluateBoolean($joinColumnElement['unique']); + } + + if (isset($joinColumnElement['nullable'])) { + $joinColumn['nullable'] = $this->evaluateBoolean($joinColumnElement['nullable']); + } + + if (isset($joinColumnElement['on-delete'])) { + $joinColumn['onDelete'] = (string) $joinColumnElement['on-delete']; + } + + if (isset($joinColumnElement['column-definition'])) { + $joinColumn['columnDefinition'] = (string) $joinColumnElement['column-definition']; + } + + if (isset($joinColumnElement['options'])) { + $joinColumn['options'] = $this->parseOptions($joinColumnElement['options'] ? $joinColumnElement['options']->children() : null); + } + + return $joinColumn; + } + + /** + * Parses the given field as array. + * + * @return mixed[] + * @psalm-return array{ + * fieldName: string, + * type?: string, + * columnName?: string, + * length?: int, + * precision?: int, + * scale?: int, + * unique?: bool, + * nullable?: bool, + * notInsertable?: bool, + * notUpdatable?: bool, + * enumType?: string, + * version?: bool, + * columnDefinition?: string, + * options?: array + * } + */ + private function columnToArray(SimpleXMLElement $fieldMapping): array + { + $mapping = [ + 'fieldName' => (string) $fieldMapping['name'], + ]; + + if (isset($fieldMapping['type'])) { + $mapping['type'] = (string) $fieldMapping['type']; + } + + if (isset($fieldMapping['column'])) { + $mapping['columnName'] = (string) $fieldMapping['column']; + } + + if (isset($fieldMapping['length'])) { + $mapping['length'] = (int) $fieldMapping['length']; + } + + if (isset($fieldMapping['precision'])) { + $mapping['precision'] = (int) $fieldMapping['precision']; + } + + if (isset($fieldMapping['scale'])) { + $mapping['scale'] = (int) $fieldMapping['scale']; + } + + if (isset($fieldMapping['unique'])) { + $mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']); + } + + if (isset($fieldMapping['nullable'])) { + $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']); + } + + if (isset($fieldMapping['insertable']) && ! $this->evaluateBoolean($fieldMapping['insertable'])) { + $mapping['notInsertable'] = true; + } + + if (isset($fieldMapping['updatable']) && ! $this->evaluateBoolean($fieldMapping['updatable'])) { + $mapping['notUpdatable'] = true; + } + + if (isset($fieldMapping['generated'])) { + $mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . (string) $fieldMapping['generated']); + } + + if (isset($fieldMapping['version']) && $fieldMapping['version']) { + $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']); + } + + if (isset($fieldMapping['column-definition'])) { + $mapping['columnDefinition'] = (string) $fieldMapping['column-definition']; + } + + if (isset($fieldMapping['enum-type'])) { + $mapping['enumType'] = (string) $fieldMapping['enum-type']; + } + + if (isset($fieldMapping->options)) { + $mapping['options'] = $this->parseOptions($fieldMapping->options->children()); + } + + return $mapping; + } + + /** + * Parse / Normalize the cache configuration + * + * @return mixed[] + * @psalm-return array{usage: int|null, region?: string} + */ + private function cacheToArray(SimpleXMLElement $cacheMapping): array + { + $region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null; + $usage = isset($cacheMapping['usage']) ? strtoupper((string) $cacheMapping['usage']) : null; + + if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) { + throw new InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage)); + } + + if ($usage) { + $usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage); + } + + return [ + 'usage' => $usage, + 'region' => $region, + ]; + } + + /** + * Gathers a list of cascade options found in the given cascade element. + * + * @param SimpleXMLElement $cascadeElement The cascade element. + * + * @return string[] The list of cascade options. + * @psalm-return list + */ + private function getCascadeMappings(SimpleXMLElement $cascadeElement): array + { + $cascades = []; + $children = $cascadeElement->children(); + assert($children !== null); + + foreach ($children as $action) { + // According to the JPA specifications, XML uses "cascade-persist" + // instead of "persist". Here, both variations + // are supported because Attribute uses "persist" + // and we want to make sure that this driver doesn't need to know + // anything about the supported cascading actions + $cascades[] = str_replace('cascade-', '', $action->getName()); + } + + return $cascades; + } + + /** + * {@inheritDoc} + */ + protected function loadMappingFile($file) + { + $this->validateMapping($file); + $result = []; + // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577 + $xmlElement = simplexml_load_string(file_get_contents($file)); + assert($xmlElement !== false); + + if (isset($xmlElement->entity)) { + foreach ($xmlElement->entity as $entityElement) { + /** @psalm-var class-string $entityName */ + $entityName = (string) $entityElement['name']; + $result[$entityName] = $entityElement; + } + } elseif (isset($xmlElement->{'mapped-superclass'})) { + foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) { + /** @psalm-var class-string $className */ + $className = (string) $mappedSuperClass['name']; + $result[$className] = $mappedSuperClass; + } + } elseif (isset($xmlElement->embeddable)) { + foreach ($xmlElement->embeddable as $embeddableElement) { + /** @psalm-var class-string $embeddableName */ + $embeddableName = (string) $embeddableElement['name']; + $result[$embeddableName] = $embeddableElement; + } + } + + return $result; + } + + private function validateMapping(string $file): void + { + if (! $this->isXsdValidationEnabled) { + return; + } + + $backedUpErrorSetting = libxml_use_internal_errors(true); + + try { + $document = new DOMDocument(); + $document->load($file); + + if (! $document->schemaValidate(__DIR__ . '/../../../doctrine-mapping.xsd')) { + throw MappingException::fromLibXmlErrors(libxml_get_errors()); + } + } finally { + libxml_clear_errors(); + libxml_use_internal_errors($backedUpErrorSetting); + } + } + + protected function evaluateBoolean(mixed $element): bool + { + $flag = (string) $element; + + return $flag === 'true' || $flag === '1'; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Embeddable.php b/vendor/doctrine/orm/src/Mapping/Embeddable.php new file mode 100644 index 0000000..b8dfea0 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Embeddable.php @@ -0,0 +1,12 @@ + */ +final class EmbeddedClassMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + public string|false|null $columnPrefix = null; + public string|null $declaredField = null; + public string|null $originalField = null; + + /** + * This is set when this embedded-class field is inherited by this class + * from another (inheritance) parent entity class. The value is + * the FQCN of the topmost entity class that contains mapping information + * for this field. (If there are transient classes in the class hierarchy, + * these are ignored, so the class property may in fact come from a class + * further up in the PHP class hierarchy.) Fields initially declared in + * mapped superclasses are not considered 'inherited' in the + * nearest entity subclasses. + * + * @var class-string|null + */ + public string|null $inherited = null; + + /** + * This is set when the embedded-class field does not appear for the first + * time in this class, but is originally declared in another parent + * entity or mapped superclass. The value is the FQCN of the + * topmost non-transient class that contains mapping information for this + * field. + * + * @var class-string|null + */ + public string|null $declared = null; + + /** @param class-string $class */ + public function __construct(public string $class) + { + } + + /** + * @psalm-param array{ + * class: class-string, + * columnPrefix?: false|string|null, + * declaredField?: string|null, + * originalField?: string|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self($mappingArray['class']); + foreach ($mappingArray as $key => $value) { + if ($key === 'class') { + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value; + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['class']; + + if ($this->columnPrefix) { + $serialized[] = 'columnPrefix'; + } + + foreach (['declaredField', 'originalField', 'inherited', 'declared'] as $property) { + if ($this->$property !== null) { + $serialized[] = $property; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Entity.php b/vendor/doctrine/orm/src/Mapping/Entity.php new file mode 100644 index 0000000..0e27913 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Entity.php @@ -0,0 +1,20 @@ +>|null $repositoryClass */ + public function __construct( + public readonly string|null $repositoryClass = null, + public readonly bool $readOnly = false, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php b/vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php new file mode 100644 index 0000000..eabc217 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php @@ -0,0 +1,30 @@ + $value */ + public function __construct( + public readonly array $value = [], + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Exception/InvalidCustomGenerator.php b/vendor/doctrine/orm/src/Mapping/Exception/InvalidCustomGenerator.php new file mode 100644 index 0000000..b9e10bf --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Exception/InvalidCustomGenerator.php @@ -0,0 +1,28 @@ + */ +final class FieldMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + /** The database length of the column. Optional. Default value taken from the type. */ + public int|null $length = null; + /** + * Marks the field as the primary key of the entity. Multiple + * fields of an entity can have the id attribute, forming a composite key. + */ + public bool|null $id = null; + public bool|null $nullable = null; + public bool|null $notInsertable = null; + public bool|null $notUpdatable = null; + public string|null $columnDefinition = null; + /** @psalm-var ClassMetadata::GENERATED_*|null */ + public int|null $generated = null; + /** @var class-string|null */ + public string|null $enumType = null; + /** + * The precision of a decimal column. + * Only valid if the column type is decimal + */ + public int|null $precision = null; + /** + * The scale of a decimal column. + * Only valid if the column type is decimal + */ + public int|null $scale = null; + /** Whether a unique constraint should be generated for the column. */ + public bool|null $unique = null; + /** + * @var class-string|null This is set when the field is inherited by this + * class from another (inheritance) parent entity class. The value + * is the FQCN of the topmost entity class that contains mapping information + * for this field. (If there are transient classes in the class hierarchy, + * these are ignored, so the class property may in fact come from a class + * further up in the PHP class hierarchy.) + * Fields initially declared in mapped superclasses are + * not considered 'inherited' in the nearest entity subclasses. + */ + public string|null $inherited = null; + + public string|null $originalClass = null; + public string|null $originalField = null; + public bool|null $quoted = null; + /** + * @var class-string|null This is set when the field does not appear for + * the first time in this class, but is originally declared in another + * parent entity or mapped superclass. The value is the FQCN of + * the topmost non-transient class that contains mapping information for + * this field. + */ + public string|null $declared = null; + public string|null $declaredField = null; + public array|null $options = null; + public bool|null $version = null; + public string|int|null $default = null; + + /** + * @param string $type The type name of the mapped field. Can be one of + * Doctrine's mapping types or a custom mapping type. + * @param string $fieldName The name of the field in the Entity. + * @param string $columnName The column name. Optional. Defaults to the field name. + */ + public function __construct( + public string $type, + public string $fieldName, + public string $columnName, + ) { + } + + /** + * @param array $mappingArray + * @psalm-param array{ + * type: string, + * fieldName: string, + * columnName: string, + * length?: int|null, + * id?: bool|null, + * nullable?: bool|null, + * notInsertable?: bool|null, + * notUpdatable?: bool|null, + * columnDefinition?: string|null, + * generated?: ClassMetadata::GENERATED_*|null, + * enumType?: string|null, + * precision?: int|null, + * scale?: int|null, + * unique?: bool|null, + * inherited?: string|null, + * originalClass?: string|null, + * originalField?: string|null, + * quoted?: bool|null, + * declared?: string|null, + * declaredField?: string|null, + * options?: array|null, + * version?: bool|null, + * default?: string|int|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self( + $mappingArray['type'], + $mappingArray['fieldName'], + $mappingArray['columnName'], + ); + foreach ($mappingArray as $key => $value) { + if (in_array($key, ['type', 'fieldName', 'columnName'])) { + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value; + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['type', 'fieldName', 'columnName']; + + foreach (['nullable', 'notInsertable', 'notUpdatable', 'id', 'unique', 'version', 'quoted'] as $boolKey) { + if ($this->$boolKey) { + $serialized[] = $boolKey; + } + } + + foreach ( + [ + 'length', + 'columnDefinition', + 'generated', + 'enumType', + 'precision', + 'scale', + 'inherited', + 'originalClass', + 'originalField', + 'declared', + 'declaredField', + 'options', + 'default', + ] as $key + ) { + if ($this->$key !== null) { + $serialized[] = $key; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/GeneratedValue.php b/vendor/doctrine/orm/src/Mapping/GeneratedValue.php new file mode 100644 index 0000000..aca5f4b --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/GeneratedValue.php @@ -0,0 +1,17 @@ +|null $columns + * @param array|null $fields + * @param array|null $flags + * @param array|null $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly array|null $columns = null, + public readonly array|null $fields = null, + public readonly array|null $flags = null, + public readonly array|null $options = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/InheritanceType.php b/vendor/doctrine/orm/src/Mapping/InheritanceType.php new file mode 100644 index 0000000..c042ee7 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/InheritanceType.php @@ -0,0 +1,17 @@ +mappedBy; + } + + /** @return list */ + public function __sleep(): array + { + return [ + ...parent::__sleep(), + 'mappedBy', + ]; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinColumn.php b/vendor/doctrine/orm/src/Mapping/JoinColumn.php new file mode 100644 index 0000000..9a049fb --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinColumn.php @@ -0,0 +1,13 @@ + */ +final class JoinColumnMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + public bool|null $unique = null; + public bool|null $quoted = null; + public string|null $fieldName = null; + public string|null $onDelete = null; + public string|null $columnDefinition = null; + public bool|null $nullable = null; + + /** @var array|null */ + public array|null $options = null; + + public function __construct( + public string $name, + public string $referencedColumnName, + ) { + } + + /** + * @param array $mappingArray + * @psalm-param array{ + * name: string, + * referencedColumnName: string, + * unique?: bool|null, + * quoted?: bool|null, + * fieldName?: string|null, + * onDelete?: string|null, + * columnDefinition?: string|null, + * nullable?: bool|null, + * options?: array|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self($mappingArray['name'], $mappingArray['referencedColumnName']); + foreach ($mappingArray as $key => $value) { + if (property_exists($mapping, $key) && $value !== null) { + $mapping->$key = $value; + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = []; + + foreach (['name', 'fieldName', 'onDelete', 'columnDefinition', 'referencedColumnName', 'options'] as $stringOrArrayKey) { + if ($this->$stringOrArrayKey !== null) { + $serialized[] = $stringOrArrayKey; + } + } + + foreach (['unique', 'quoted', 'nullable'] as $boolKey) { + if ($this->$boolKey !== null) { + $serialized[] = $boolKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinColumnProperties.php b/vendor/doctrine/orm/src/Mapping/JoinColumnProperties.php new file mode 100644 index 0000000..7d13295 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinColumnProperties.php @@ -0,0 +1,21 @@ + $options */ + public function __construct( + public readonly string|null $name = null, + public readonly string $referencedColumnName = 'id', + public readonly bool $unique = false, + public readonly bool $nullable = true, + public readonly mixed $onDelete = null, + public readonly string|null $columnDefinition = null, + public readonly string|null $fieldName = null, + public readonly array $options = [], + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinColumns.php b/vendor/doctrine/orm/src/Mapping/JoinColumns.php new file mode 100644 index 0000000..f166b76 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinColumns.php @@ -0,0 +1,14 @@ + $value */ + public function __construct( + public readonly array $value, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinTable.php b/vendor/doctrine/orm/src/Mapping/JoinTable.php new file mode 100644 index 0000000..0558761 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinTable.php @@ -0,0 +1,35 @@ + */ + public readonly array $joinColumns; + + /** @var array */ + public readonly array $inverseJoinColumns; + + /** + * @param array|JoinColumn $joinColumns + * @param array|JoinColumn $inverseJoinColumns + * @param array $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly string|null $schema = null, + array|JoinColumn $joinColumns = [], + array|JoinColumn $inverseJoinColumns = [], + public readonly array $options = [], + ) { + $this->joinColumns = $joinColumns instanceof JoinColumn ? [$joinColumns] : $joinColumns; + $this->inverseJoinColumns = $inverseJoinColumns instanceof JoinColumn + ? [$inverseJoinColumns] + : $inverseJoinColumns; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinTableMapping.php b/vendor/doctrine/orm/src/Mapping/JoinTableMapping.php new file mode 100644 index 0000000..c8b4968 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinTableMapping.php @@ -0,0 +1,115 @@ + */ +final class JoinTableMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + public bool|null $quoted = null; + + /** @var list */ + public array $joinColumns = []; + + /** @var list */ + public array $inverseJoinColumns = []; + + /** @var array */ + public array $options = []; + + public string|null $schema = null; + + public function __construct(public string $name) + { + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * name: string, + * quoted?: bool|null, + * joinColumns?: mixed[], + * inverseJoinColumns?: mixed[], + * schema?: string|null, + * options?: array + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self($mappingArray['name']); + + foreach (['quoted', 'schema', 'options'] as $key) { + if (isset($mappingArray[$key])) { + $mapping->$key = $mappingArray[$key]; + } + } + + if (isset($mappingArray['joinColumns'])) { + foreach ($mappingArray['joinColumns'] as $column) { + $mapping->joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + } + + if (isset($mappingArray['inverseJoinColumns'])) { + foreach ($mappingArray['inverseJoinColumns'] as $column) { + $mapping->inverseJoinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + } + + return $mapping; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if (in_array($offset, ['joinColumns', 'inverseJoinColumns'], true)) { + $joinColumns = []; + foreach ($value as $column) { + $joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + + $value = $joinColumns; + } + + $this->$offset = $value; + } + + /** @return mixed[] */ + public function toArray(): array + { + $array = (array) $this; + + $toArray = static fn (JoinColumnMapping $column): array => (array) $column; + $array['joinColumns'] = array_map($toArray, $array['joinColumns']); + $array['inverseJoinColumns'] = array_map($toArray, $array['inverseJoinColumns']); + + return $array; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = []; + + foreach (['joinColumns', 'inverseJoinColumns', 'name', 'schema', 'options'] as $stringOrArrayKey) { + if ($this->$stringOrArrayKey !== null) { + $serialized[] = $stringOrArrayKey; + } + } + + foreach (['quoted'] as $boolKey) { + if ($this->$boolKey) { + $serialized[] = $boolKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ManyToMany.php b/vendor/doctrine/orm/src/Mapping/ManyToMany.php new file mode 100644 index 0000000..d90a762 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToMany.php @@ -0,0 +1,27 @@ + */ + public array $joinTableColumns = []; + + /** @var array */ + public array $relationToSourceKeyColumns = []; + /** @var array */ + public array $relationToTargetKeyColumns = []; + + /** @return array */ + public function toArray(): array + { + $array = parent::toArray(); + + $array['joinTable'] = $this->joinTable->toArray(); + + return $array; + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArrayAndNamingStrategy(array $mappingArray, NamingStrategy $namingStrategy): self + { + if (isset($mappingArray['joinTable']['joinColumns'])) { + foreach ($mappingArray['joinTable']['joinColumns'] as $key => $joinColumn) { + if (empty($joinColumn['name'])) { + $mappingArray['joinTable']['joinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( + $mappingArray['sourceEntity'], + $joinColumn['referencedColumnName'] ?? null, + ); + } + } + } + + if (isset($mappingArray['joinTable']['inverseJoinColumns'])) { + foreach ($mappingArray['joinTable']['inverseJoinColumns'] as $key => $joinColumn) { + if (empty($joinColumn['name'])) { + $mappingArray['joinTable']['inverseJoinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( + $mappingArray['targetEntity'], + $joinColumn['referencedColumnName'] ?? null, + ); + } + } + } + + // owning side MUST have a join table + if (! isset($mappingArray['joinTable']) || ! isset($mappingArray['joinTable']['name'])) { + $mappingArray['joinTable']['name'] = $namingStrategy->joinTableName( + $mappingArray['sourceEntity'], + $mappingArray['targetEntity'], + $mappingArray['fieldName'], + ); + } + + $mapping = parent::fromMappingArray($mappingArray); + + $selfReferencingEntityWithoutJoinColumns = $mapping->sourceEntity === $mapping->targetEntity + && $mapping->joinTable->joinColumns === [] + && $mapping->joinTable->inverseJoinColumns === []; + + if ($mapping->joinTable->joinColumns === []) { + $mapping->joinTable->joinColumns = [ + JoinColumnMapping::fromMappingArray([ + 'name' => $namingStrategy->joinKeyColumnName($mapping->sourceEntity, $selfReferencingEntityWithoutJoinColumns ? 'source' : null), + 'referencedColumnName' => $namingStrategy->referenceColumnName(), + 'onDelete' => 'CASCADE', + ]), + ]; + } + + if ($mapping->joinTable->inverseJoinColumns === []) { + $mapping->joinTable->inverseJoinColumns = [ + JoinColumnMapping::fromMappingArray([ + 'name' => $namingStrategy->joinKeyColumnName($mapping->targetEntity, $selfReferencingEntityWithoutJoinColumns ? 'target' : null), + 'referencedColumnName' => $namingStrategy->referenceColumnName(), + 'onDelete' => 'CASCADE', + ]), + ]; + } + + $mapping->joinTableColumns = []; + + foreach ($mapping->joinTable->joinColumns as $joinColumn) { + if (empty($joinColumn->referencedColumnName)) { + $joinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); + } + + if ($joinColumn->name[0] === '`') { + $joinColumn->name = trim($joinColumn->name, '`'); + $joinColumn->quoted = true; + } + + if ($joinColumn->referencedColumnName[0] === '`') { + $joinColumn->referencedColumnName = trim($joinColumn->referencedColumnName, '`'); + $joinColumn->quoted = true; + } + + if (isset($joinColumn->onDelete) && strtolower($joinColumn->onDelete) === 'cascade') { + $mapping->isOnDeleteCascade = true; + } + + $mapping->relationToSourceKeyColumns[$joinColumn->name] = $joinColumn->referencedColumnName; + $mapping->joinTableColumns[] = $joinColumn->name; + } + + foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) { + if (empty($inverseJoinColumn->referencedColumnName)) { + $inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); + } + + if ($inverseJoinColumn->name[0] === '`') { + $inverseJoinColumn->name = trim($inverseJoinColumn->name, '`'); + $inverseJoinColumn->quoted = true; + } + + if ($inverseJoinColumn->referencedColumnName[0] === '`') { + $inverseJoinColumn->referencedColumnName = trim($inverseJoinColumn->referencedColumnName, '`'); + $inverseJoinColumn->quoted = true; + } + + if (isset($inverseJoinColumn->onDelete) && strtolower($inverseJoinColumn->onDelete) === 'cascade') { + $mapping->isOnDeleteCascade = true; + } + + $mapping->relationToTargetKeyColumns[$inverseJoinColumn->name] = $inverseJoinColumn->referencedColumnName; + $mapping->joinTableColumns[] = $inverseJoinColumn->name; + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = parent::__sleep(); + $serialized[] = 'joinTable'; + $serialized[] = 'joinTableColumns'; + + foreach (['relationToSourceKeyColumns', 'relationToTargetKeyColumns'] as $arrayKey) { + if ($this->$arrayKey !== null) { + $serialized[] = $arrayKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ManyToOne.php b/vendor/doctrine/orm/src/Mapping/ManyToOne.php new file mode 100644 index 0000000..8fccff3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToOne.php @@ -0,0 +1,24 @@ +|null $repositoryClass */ + public function __construct( + public readonly string|null $repositoryClass = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/MappingAttribute.php b/vendor/doctrine/orm/src/Mapping/MappingAttribute.php new file mode 100644 index 0000000..61091a9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/MappingAttribute.php @@ -0,0 +1,10 @@ + $map + */ + public static function duplicateDiscriminatorEntry(string $className, array $entries, array $map): self + { + return new self( + 'The entries ' . implode(', ', $entries) . " in discriminator map of class '" . $className . "' is duplicated. " . + 'If the discriminator map is automatically generated you have to convert it to an explicit discriminator map now. ' . + 'The entries of the current map are: @DiscriminatorMap({' . implode(', ', array_map( + static fn ($a, $b) => sprintf("'%s': '%s'", $a, $b), + array_keys($map), + array_values($map), + )) . '})', + ); + } + + /** + * @param class-string $rootEntityClass + * @param class-string $childEntityClass + */ + public static function missingInheritanceTypeDeclaration(string $rootEntityClass, string $childEntityClass): self + { + return new self(sprintf( + "Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared.", + $childEntityClass, + $rootEntityClass, + )); + } + + public static function missingDiscriminatorMap(string $className): self + { + return new self(sprintf( + "Entity class '%s' is using inheritance but no discriminator map was defined.", + $className, + )); + } + + public static function missingDiscriminatorColumn(string $className): self + { + return new self(sprintf( + "Entity class '%s' is using inheritance but no discriminator column was defined.", + $className, + )); + } + + public static function invalidDiscriminatorColumnType(string $className, string $type): self + { + return new self(sprintf( + "Discriminator column type on entity class '%s' is not allowed to be '%s'. 'string' or 'integer' type variables are suggested!", + $className, + $type, + )); + } + + public static function nameIsMandatoryForDiscriminatorColumns(string $className): self + { + return new self(sprintf("Discriminator column name on entity class '%s' is not defined.", $className)); + } + + public static function cannotVersionIdField(string $className, string $fieldName): self + { + return new self(sprintf( + "Setting Id field '%s' as versionable in entity class '%s' is not supported.", + $fieldName, + $className, + )); + } + + public static function duplicateColumnName(string $className, string $columnName): self + { + return new self("Duplicate definition of column '" . $columnName . "' on entity '" . $className . "' in a field or discriminator column mapping."); + } + + public static function illegalToManyAssociationOnMappedSuperclass(string $className, string $field): self + { + return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '" . $className . '#' . $field . "'."); + } + + public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId(string $className, string $targetEntity, string $targetField): self + { + return new self("It is not possible to map entity '" . $className . "' with a composite primary key " . + "as part of the primary key of another entity '" . $targetEntity . '#' . $targetField . "'."); + } + + public static function noSingleAssociationJoinColumnFound(string $className, string $field): self + { + return new self(sprintf("'%s#%s' is not an association with a single join column.", $className, $field)); + } + + public static function noFieldNameFoundForColumn(string $className, string $column): self + { + return new self(sprintf( + "Cannot find a field on '%s' that is mapped to column '%s'. Either the " . + 'field does not exist or an association exists but it has multiple join columns.', + $className, + $column, + )); + } + + public static function illegalOrphanRemovalOnIdentifierAssociation(string $className, string $field): self + { + return new self(sprintf( + "The orphan removal option is not allowed on an association that is part of the identifier in '%s#%s'.", + $className, + $field, + )); + } + + public static function illegalOrphanRemoval(string $className, string $field): self + { + return new self('Orphan removal is only allowed on one-to-one and one-to-many ' . + 'associations, but ' . $className . '#' . $field . ' is not.'); + } + + public static function illegalInverseIdentifierAssociation(string $className, string $field): self + { + return new self(sprintf( + "An inverse association is not allowed to be identifier in '%s#%s'.", + $className, + $field, + )); + } + + public static function illegalToManyIdentifierAssociation(string $className, string $field): self + { + return new self(sprintf( + "Many-to-many or one-to-many associations are not allowed to be identifier in '%s#%s'.", + $className, + $field, + )); + } + + public static function noInheritanceOnMappedSuperClass(string $className): self + { + return new self("It is not supported to define inheritance information on a mapped superclass '" . $className . "'."); + } + + public static function mappedClassNotPartOfDiscriminatorMap(string $className, string $rootClassName): self + { + return new self( + "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . + "to be properly mapped in the inheritance hierarchy. Alternatively you can make '" . $className . "' an abstract class " . + 'to avoid this exception from occurring.', + ); + } + + public static function lifecycleCallbackMethodNotFound(string $className, string $methodName): self + { + return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); + } + + /** @param class-string $className */ + public static function illegalLifecycleCallbackOnEmbeddedClass(string $event, string $className): self + { + return new self(sprintf( + <<<'EXCEPTION' + Context: Attempt to register lifecycle callback "%s" on embedded class "%s". + Problem: Registering lifecycle callbacks on embedded classes is not allowed. + EXCEPTION, + $event, + $className, + )); + } + + public static function entityListenerClassNotFound(string $listenerName, string $className): self + { + return new self(sprintf('Entity Listener "%s" declared on "%s" not found.', $listenerName, $className)); + } + + public static function entityListenerMethodNotFound(string $listenerName, string $methodName, string $className): self + { + return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName)); + } + + public static function duplicateEntityListener(string $listenerName, string $methodName, string $className): self + { + return new self(sprintf('Entity Listener "%s#%s()" in "%s" was already declared, but it must be declared only once.', $listenerName, $methodName, $className)); + } + + /** @param class-string $className */ + public static function invalidFetchMode(string $className, string $fetchMode): self + { + return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $fetchMode . "'"); + } + + public static function invalidGeneratedMode(int|string $generatedMode): self + { + return new self("Invalid generated mode '" . $generatedMode . "'"); + } + + public static function compositeKeyAssignedIdGeneratorRequired(string $className): self + { + return new self("Entity '" . $className . "' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported."); + } + + public static function invalidTargetEntityClass(string $targetEntity, string $sourceEntity, string $associationName): self + { + return new self('The target-entity ' . $targetEntity . " cannot be found in '" . $sourceEntity . '#' . $associationName . "'."); + } + + /** @param string[] $cascades */ + public static function invalidCascadeOption(array $cascades, string $className, string $propertyName): self + { + $cascades = implode(', ', array_map(static fn (string $e): string => "'" . $e . "'", $cascades)); + + return new self(sprintf( + "You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', and 'detach'", + $className, + $propertyName, + $cascades, + )); + } + + public static function missingSequenceName(string $className): self + { + return new self( + sprintf('Missing "sequenceName" attribute for sequence id generator definition on class "%s".', $className), + ); + } + + public static function infiniteEmbeddableNesting(string $className, string $propertyName): self + { + return new self( + sprintf( + 'Infinite nesting detected for embedded property %s::%s. ' . + 'You cannot embed an embeddable from the same type inside an embeddable.', + $className, + $propertyName, + ), + ); + } + + public static function illegalOverrideOfInheritedProperty(string $className, string $propertyName, string $inheritFromClass): self + { + return new self( + sprintf( + 'Overrides are only allowed for fields or associations declared in mapped superclasses or traits. This is not the case for %s::%s, which was inherited from %s.', + $className, + $propertyName, + $inheritFromClass, + ), + ); + } + + public static function invalidIndexConfiguration(string $className, string $indexName): self + { + return new self( + sprintf( + 'Index %s for entity %s should contain columns or fields values, but not both.', + $indexName, + $className, + ), + ); + } + + public static function invalidUniqueConstraintConfiguration(string $className, string $indexName): self + { + return new self( + sprintf( + 'Unique constraint %s for entity %s should contain columns or fields values, but not both.', + $indexName, + $className, + ), + ); + } + + public static function invalidOverrideType(string $expectdType, mixed $givenValue): self + { + return new self(sprintf( + 'Expected %s, but %s was given.', + $expectdType, + get_debug_type($givenValue), + )); + } + + public static function backedEnumTypeRequired(string $className, string $fieldName, string $enumType): self + { + return new self(sprintf( + 'Attempting to map a non-backed enum type %s in entity %s::$%s. Please use backed enums only', + $enumType, + $className, + $fieldName, + )); + } + + public static function nonEnumTypeMapped(string $className, string $fieldName, string $enumType): self + { + return new self(sprintf( + 'Attempting to map non-enum type %s as enum in entity %s::$%s', + $enumType, + $className, + $fieldName, + )); + } + + /** + * @param class-string $className + * @param class-string $enumType + */ + public static function invalidEnumValue( + string $className, + string $fieldName, + string $value, + string $enumType, + ValueError $previous, + ): self { + return new self(sprintf( + <<<'EXCEPTION' +Context: Trying to hydrate enum property "%s::$%s" +Problem: Case "%s" is not listed in enum "%s" +Solution: Either add the case to the enum type or migrate the database column to use another case of the enum +EXCEPTION + , + $className, + $fieldName, + $value, + $enumType, + ), 0, $previous); + } + + /** @param LibXMLError[] $errors */ + public static function fromLibXmlErrors(array $errors): self + { + $formatter = static fn (LibXMLError $error): string => sprintf( + 'libxml error: %s in %s at line %d', + $error->message, + $error->file, + $error->line, + ); + + return new self(implode(PHP_EOL, array_map($formatter, $errors))); + } + + public static function invalidAttributeOnEmbeddable(string $entityName, string $attributeName): self + { + return new self(sprintf( + 'Attribute "%s" on embeddable "%s" is not allowed.', + $attributeName, + $entityName, + )); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/NamingStrategy.php b/vendor/doctrine/orm/src/Mapping/NamingStrategy.php new file mode 100644 index 0000000..afedebe --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/NamingStrategy.php @@ -0,0 +1,71 @@ +, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): static + { + $mapping = parent::fromMappingArray($mappingArray); + + if ($mapping->orphanRemoval && ! $mapping->isCascadeRemove()) { + $mapping->cascade[] = 'remove'; + } + + return $mapping; + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArrayAndName(array $mappingArray, string $name): static + { + $mapping = self::fromMappingArray($mappingArray); + + // OneToMany-side MUST be inverse (must have mappedBy) + if (! isset($mapping->mappedBy)) { + throw MappingException::oneToManyRequiresMappedBy($name, $mapping->fieldName); + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/OneToOne.php b/vendor/doctrine/orm/src/Mapping/OneToOne.php new file mode 100644 index 0000000..1ddf21c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToOne.php @@ -0,0 +1,26 @@ +|null $cascade + * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch + */ + public function __construct( + public readonly string|null $targetEntity = null, + public readonly string|null $mappedBy = null, + public readonly string|null $inversedBy = null, + public readonly array|null $cascade = null, + public readonly string $fetch = 'LAZY', + public readonly bool $orphanRemoval = false, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/OneToOneAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/OneToOneAssociationMapping.php new file mode 100644 index 0000000..89c6483 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToOneAssociationMapping.php @@ -0,0 +1,9 @@ + $value */ + public function __construct( + public readonly array $value, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/OwningSideMapping.php b/vendor/doctrine/orm/src/Mapping/OwningSideMapping.php new file mode 100644 index 0000000..ab8b7b2 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OwningSideMapping.php @@ -0,0 +1,28 @@ + */ + public function __sleep(): array + { + $serialized = parent::__sleep(); + + if ($this->inversedBy !== null) { + $serialized[] = 'inversedBy'; + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/PostLoad.php b/vendor/doctrine/orm/src/Mapping/PostLoad.php new file mode 100644 index 0000000..9ce1e5c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PostLoad.php @@ -0,0 +1,12 @@ + + */ + public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array; + + /** + * Gets the column alias. + */ + public function getColumnAlias( + string $columnName, + int $counter, + AbstractPlatform $platform, + ClassMetadata|null $class = null, + ): string; +} diff --git a/vendor/doctrine/orm/src/Mapping/ReflectionEmbeddedProperty.php b/vendor/doctrine/orm/src/Mapping/ReflectionEmbeddedProperty.php new file mode 100644 index 0000000..da3d097 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ReflectionEmbeddedProperty.php @@ -0,0 +1,61 @@ +getDeclaringClass()->name, $childProperty->getName()); + } + + public function getValue(object|null $object = null): mixed + { + $embeddedObject = $this->parentProperty->getValue($object); + + if ($embeddedObject === null) { + return null; + } + + return $this->childProperty->getValue($embeddedObject); + } + + public function setValue(mixed $object, mixed $value = null): void + { + $embeddedObject = $this->parentProperty->getValue($object); + + if ($embeddedObject === null) { + $this->instantiator ??= new Instantiator(); + + $embeddedObject = $this->instantiator->instantiate($this->embeddedClass); + + $this->parentProperty->setValue($object, $embeddedObject); + } + + $this->childProperty->setValue($embeddedObject, $value); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ReflectionEnumProperty.php b/vendor/doctrine/orm/src/Mapping/ReflectionEnumProperty.php new file mode 100644 index 0000000..0ebd978 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ReflectionEnumProperty.php @@ -0,0 +1,87 @@ + $enumType */ + public function __construct( + private readonly ReflectionProperty $originalReflectionProperty, + private readonly string $enumType, + ) { + parent::__construct( + $originalReflectionProperty->class, + $originalReflectionProperty->name, + ); + } + + public function getValue(object|null $object = null): int|string|array|null + { + if ($object === null) { + return null; + } + + $enum = $this->originalReflectionProperty->getValue($object); + + if ($enum === null) { + return null; + } + + if (is_array($enum)) { + return array_map( + static fn (BackedEnum $item): int|string => $item->value, + $enum, + ); + } + + return $enum->value; + } + + /** + * @param object $object + * @param int|string|int[]|string[]|BackedEnum|BackedEnum[]|null $value + */ + public function setValue(mixed $object, mixed $value = null): void + { + if ($value !== null) { + if (is_array($value)) { + $value = array_map(fn (int|string|BackedEnum $item): BackedEnum => $this->initializeEnumValue($object, $item), $value); + } else { + $value = $this->initializeEnumValue($object, $value); + } + } + + $this->originalReflectionProperty->setValue($object, $value); + } + + private function initializeEnumValue(object $object, int|string|BackedEnum $value): BackedEnum + { + if ($value instanceof BackedEnum) { + return $value; + } + + $enumType = $this->enumType; + + try { + return $enumType::from($value); + } catch (ValueError $e) { + throw MappingException::invalidEnumValue( + $object::class, + $this->originalReflectionProperty->name, + (string) $value, + $enumType, + $e, + ); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ReflectionReadonlyProperty.php b/vendor/doctrine/orm/src/Mapping/ReflectionReadonlyProperty.php new file mode 100644 index 0000000..13e9f6d --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ReflectionReadonlyProperty.php @@ -0,0 +1,49 @@ +isReadOnly()) { + throw new InvalidArgumentException('Given property is not readonly.'); + } + + parent::__construct($wrappedProperty->class, $wrappedProperty->name); + } + + public function getValue(object|null $object = null): mixed + { + return $this->wrappedProperty->getValue(...func_get_args()); + } + + public function setValue(mixed $objectOrValue, mixed $value = null): void + { + if (func_num_args() < 2 || $objectOrValue === null || ! $this->isInitialized($objectOrValue)) { + $this->wrappedProperty->setValue(...func_get_args()); + + return; + } + + assert(is_object($objectOrValue)); + + if (parent::getValue($objectOrValue) !== $value) { + throw new LogicException(sprintf('Attempting to change readonly property %s::$%s.', $this->class, $this->name)); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/SequenceGenerator.php b/vendor/doctrine/orm/src/Mapping/SequenceGenerator.php new file mode 100644 index 0000000..6c06e84 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/SequenceGenerator.php @@ -0,0 +1,18 @@ +|null $indexes + * @param array|null $uniqueConstraints + * @param array $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly string|null $schema = null, + public readonly array|null $indexes = null, + public readonly array|null $uniqueConstraints = null, + public readonly array $options = [], + ) { + if ($this->indexes !== null) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11357', + 'Providing the property $indexes on %s does not have any effect and will be removed in Doctrine ORM 4.0. Please use the %s attribute instead.', + self::class, + Index::class, + ); + } + + if ($this->uniqueConstraints !== null) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11357', + 'Providing the property $uniqueConstraints on %s does not have any effect and will be removed in Doctrine ORM 4.0. Please use the %s attribute instead.', + self::class, + UniqueConstraint::class, + ); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ToManyAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMapping.php new file mode 100644 index 0000000..2e4969c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMapping.php @@ -0,0 +1,16 @@ +indexBy() */ + public function isIndexed(): bool; + + public function indexBy(): string; + + /** @return array */ + public function orderBy(): array; +} diff --git a/vendor/doctrine/orm/src/Mapping/ToManyAssociationMappingImplementation.php b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMappingImplementation.php new file mode 100644 index 0000000..306880d --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMappingImplementation.php @@ -0,0 +1,69 @@ + + */ + public array $orderBy = []; + + /** @return array */ + final public function orderBy(): array + { + return $this->orderBy; + } + + /** @psalm-assert-if-true !null $this->indexBy */ + final public function isIndexed(): bool + { + return $this->indexBy !== null; + } + + final public function indexBy(): string + { + if (! $this->isIndexed()) { + throw new LogicException(sprintf( + 'This mapping is not indexed. Use %s::isIndexed() to check that before calling %s.', + self::class, + __METHOD__, + )); + } + + return $this->indexBy; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = parent::__sleep(); + + if ($this->indexBy !== null) { + $serialized[] = 'indexBy'; + } + + if ($this->orderBy !== []) { + $serialized[] = 'orderBy'; + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ToManyInverseSideMapping.php b/vendor/doctrine/orm/src/Mapping/ToManyInverseSideMapping.php new file mode 100644 index 0000000..a092ebe --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToManyInverseSideMapping.php @@ -0,0 +1,10 @@ +, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArrayAndName( + array $mappingArray, + string $name, + ): static { + $mapping = static::fromMappingArray($mappingArray); + + if (isset($mapping->id) && $mapping->id === true) { + throw MappingException::illegalInverseIdentifierAssociation($name, $mapping->fieldName); + } + + if ($mapping->orphanRemoval) { + if (! $mapping->isCascadeRemove()) { + $mapping->cascade[] = 'remove'; + } + + $mapping->unique = null; + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ToOneOwningSideMapping.php b/vendor/doctrine/orm/src/Mapping/ToOneOwningSideMapping.php new file mode 100644 index 0000000..cb85afb --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToOneOwningSideMapping.php @@ -0,0 +1,212 @@ + */ + public array $sourceToTargetKeyColumns = []; + + /** @var array */ + public array $targetToSourceKeyColumns = []; + + /** @var list */ + public array $joinColumns = []; + + /** @var array */ + public array $joinColumnFieldNames = []; + + /** + * @param array $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * joinColumns?: mixed[]|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): static + { + $joinColumns = $mappingArray['joinColumns'] ?? []; + unset($mappingArray['joinColumns']); + + $instance = parent::fromMappingArray($mappingArray); + assert($instance->isToOneOwningSide()); + + foreach ($joinColumns as $column) { + $instance->joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + + if ($instance->orphanRemoval) { + if (! $instance->isCascadeRemove()) { + $instance->cascade[] = 'remove'; + } + + $instance->unique = null; + } + + return $instance; + } + + /** + * @param mixed[] $mappingArray + * @param class-string $name + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * joinColumns?: mixed[]|null, + * } $mappingArray + */ + public static function fromMappingArrayAndName( + array $mappingArray, + NamingStrategy $namingStrategy, + string $name, + array|null $table, + bool $isInheritanceTypeSingleTable, + ): static { + if (isset($mappingArray['joinColumns'])) { + foreach ($mappingArray['joinColumns'] as $index => $joinColumn) { + if (empty($joinColumn['name'])) { + $mappingArray['joinColumns'][$index]['name'] = $namingStrategy->joinColumnName($mappingArray['fieldName'], $name); + } + } + } + + $mapping = static::fromMappingArray($mappingArray); + + assert($mapping->isToOneOwningSide()); + if (empty($mapping->joinColumns)) { + // Apply default join column + $mapping->joinColumns = [ + JoinColumnMapping::fromMappingArray([ + 'name' => $namingStrategy->joinColumnName($mapping->fieldName, $name), + 'referencedColumnName' => $namingStrategy->referenceColumnName(), + ]), + ]; + } + + $uniqueConstraintColumns = []; + + foreach ($mapping->joinColumns as $joinColumn) { + if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) { + if (count($mapping->joinColumns) === 1) { + if (empty($mapping->id)) { + $joinColumn->unique = true; + } + } else { + $uniqueConstraintColumns[] = $joinColumn->name; + } + } + + if (empty($joinColumn->referencedColumnName)) { + $joinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); + } + + if ($joinColumn->name[0] === '`') { + $joinColumn->name = trim($joinColumn->name, '`'); + $joinColumn->quoted = true; + } + + if ($joinColumn->referencedColumnName[0] === '`') { + $joinColumn->referencedColumnName = trim($joinColumn->referencedColumnName, '`'); + $joinColumn->quoted = true; + } + + $mapping->sourceToTargetKeyColumns[$joinColumn->name] = $joinColumn->referencedColumnName; + $mapping->joinColumnFieldNames[$joinColumn->name] = $joinColumn->fieldName ?? $joinColumn->name; + } + + if ($uniqueConstraintColumns) { + if (! $table) { + throw new RuntimeException('ClassMetadata::setTable() has to be called before defining a one to one relationship.'); + } + + $table['uniqueConstraints'][$mapping->fieldName . '_uniq'] = ['columns' => $uniqueConstraintColumns]; + } + + $mapping->targetToSourceKeyColumns = array_flip($mapping->sourceToTargetKeyColumns); + + return $mapping; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if ($offset === 'joinColumns') { + $joinColumns = []; + foreach ($value as $column) { + $joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + + $this->joinColumns = $joinColumns; + + return; + } + + parent::offsetSet($offset, $value); + } + + /** @return array */ + public function toArray(): array + { + $array = parent::toArray(); + + $joinColumns = []; + foreach ($array['joinColumns'] as $column) { + $joinColumns[] = (array) $column; + } + + $array['joinColumns'] = $joinColumns; + + return $array; + } + + /** @return list */ + public function __sleep(): array + { + return [ + ...parent::__sleep(), + 'joinColumns', + 'joinColumnFieldNames', + 'sourceToTargetKeyColumns', + 'targetToSourceKeyColumns', + ]; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php b/vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php new file mode 100644 index 0000000..2db9e90 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php @@ -0,0 +1,20 @@ +, type?: string} $mapping The field mapping to validate & complete. + * + * @return array{fieldName: string, enumType?: class-string, type?: string} The updated mapping. + */ + public function validateAndComplete(array $mapping, ReflectionProperty $field): array; +} diff --git a/vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php b/vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php new file mode 100644 index 0000000..cedc150 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php @@ -0,0 +1,108 @@ +case; + } + + /** + * Sets string case CASE_LOWER | CASE_UPPER. + * Alphabetic characters converted to lowercase or uppercase. + */ + public function setCase(int $case): void + { + $this->case = $case; + } + + public function classToTableName(string $className): string + { + if (str_contains($className, '\\')) { + $className = substr($className, strrpos($className, '\\') + 1); + } + + return $this->underscore($className); + } + + public function propertyToColumnName(string $propertyName, string $className): string + { + return $this->underscore($propertyName); + } + + public function embeddedFieldToColumnName( + string $propertyName, + string $embeddedColumnName, + string $className, + string $embeddedClassName, + ): string { + return $this->underscore($propertyName) . '_' . $embeddedColumnName; + } + + public function referenceColumnName(): string + { + return $this->case === CASE_UPPER ? 'ID' : 'id'; + } + + public function joinColumnName(string $propertyName, string $className): string + { + return $this->underscore($propertyName) . '_' . $this->referenceColumnName(); + } + + public function joinTableName( + string $sourceEntity, + string $targetEntity, + string $propertyName, + ): string { + return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity); + } + + public function joinKeyColumnName( + string $entityName, + string|null $referencedColumnName, + ): string { + return $this->classToTableName($entityName) . '_' . + ($referencedColumnName ?: $this->referenceColumnName()); + } + + private function underscore(string $string): string + { + $string = preg_replace('/(?<=[a-z0-9])([A-Z])/', '_$1', $string); + + if ($this->case === CASE_UPPER) { + return strtoupper($string); + } + + return strtolower($string); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/UniqueConstraint.php b/vendor/doctrine/orm/src/Mapping/UniqueConstraint.php new file mode 100644 index 0000000..3180be0 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/UniqueConstraint.php @@ -0,0 +1,24 @@ +|null $columns + * @param array|null $fields + * @param array|null $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly array|null $columns = null, + public readonly array|null $fields = null, + public readonly array|null $options = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Version.php b/vendor/doctrine/orm/src/Mapping/Version.php new file mode 100644 index 0000000..7252e05 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Version.php @@ -0,0 +1,12 @@ +sql = $sql; + + return $this; + } + + public function getSQL(): string + { + return $this->sql; + } + + protected function _doExecute(): Result|int + { + $parameters = []; + $types = []; + + foreach ($this->getParameters() as $parameter) { + $name = $parameter->getName(); + + if ($parameter->typeWasSpecified()) { + $parameters[$name] = $parameter->getValue(); + $types[$name] = $parameter->getType(); + + continue; + } + + $value = $this->processParameterValue($parameter->getValue()); + $type = $parameter->getValue() === $value + ? $parameter->getType() + : ParameterTypeInferer::inferType($value); + + $parameters[$name] = $value; + $types[$name] = $type; + } + + if ($parameters && is_int(key($parameters))) { + ksort($parameters); + ksort($types); + + $parameters = array_values($parameters); + $types = array_values($types); + } + + return $this->em->getConnection()->executeQuery( + $this->sql, + $parameters, + $types, + $this->queryCacheProfile, + ); + } +} diff --git a/vendor/doctrine/orm/src/NoResultException.php b/vendor/doctrine/orm/src/NoResultException.php new file mode 100644 index 0000000..6ecabae --- /dev/null +++ b/vendor/doctrine/orm/src/NoResultException.php @@ -0,0 +1,16 @@ + $newEntitiesWithAssociations */ + public static function newEntitiesFoundThroughRelationships(array $newEntitiesWithAssociations): self + { + $errorMessages = array_map( + static function (array $newEntityWithAssociation): string { + [$associationMapping, $entity] = $newEntityWithAssociation; + + return self::newEntityFoundThroughRelationshipMessage($associationMapping, $entity); + }, + $newEntitiesWithAssociations, + ); + + if (count($errorMessages) === 1) { + return new self(reset($errorMessages)); + } + + return new self( + 'Multiple non-persisted new entities were found through the given association graph:' + . "\n\n * " + . implode("\n * ", $errorMessages), + ); + } + + public static function newEntityFoundThroughRelationship(AssociationMapping $associationMapping, object $entry): self + { + return new self(self::newEntityFoundThroughRelationshipMessage($associationMapping, $entry)); + } + + public static function detachedEntityFoundThroughRelationship(AssociationMapping $assoc, object $entry): self + { + return new self('A detached entity of type ' . $assoc->targetEntity . ' (' . self::objToStr($entry) . ') ' + . " was found through the relationship '" . $assoc->sourceEntity . '#' . $assoc->fieldName . "' " + . 'during cascading a persist operation.'); + } + + public static function entityNotManaged(object $entity): self + { + return new self('Entity ' . self::objToStr($entity) . ' is not managed. An entity is managed if its fetched ' . + 'from the database or registered as new through EntityManager#persist'); + } + + public static function entityHasNoIdentity(object $entity, string $operation): self + { + return new self('Entity has no identity, therefore ' . $operation . ' cannot be performed. ' . self::objToStr($entity)); + } + + public static function entityIsRemoved(object $entity, string $operation): self + { + return new self('Entity is removed, therefore ' . $operation . ' cannot be performed. ' . self::objToStr($entity)); + } + + public static function detachedEntityCannot(object $entity, string $operation): self + { + return new self('Detached entity ' . self::objToStr($entity) . ' cannot be ' . $operation); + } + + public static function invalidObject(string $context, mixed $given, int $parameterIndex = 1): self + { + return new self($context . ' expects parameter ' . $parameterIndex . + ' to be an entity object, ' . gettype($given) . ' given.'); + } + + public static function invalidCompositeIdentifier(): self + { + return new self('Binding an entity with a composite primary key to a query is not supported. ' . + 'You should split the parameter into the explicit fields and bind them separately.'); + } + + public static function invalidIdentifierBindingEntity(string $class): self + { + return new self(sprintf( + <<<'EXCEPTION' +Binding entities to query parameters only allowed for entities that have an identifier. +Class "%s" does not have an identifier. +EXCEPTION + , + $class, + )); + } + + public static function invalidAssociation(ClassMetadata $targetClass, AssociationMapping $assoc, mixed $actualValue): self + { + $expectedType = $targetClass->getName(); + + return new self(sprintf( + 'Expected value of type "%s" for association field "%s#$%s", got "%s" instead.', + $expectedType, + $assoc->sourceEntity, + $assoc->fieldName, + get_debug_type($actualValue), + )); + } + + public static function invalidAutoGenerateMode(mixed $value): self + { + return new self(sprintf('Invalid auto generate mode "%s" given.', is_scalar($value) ? (string) $value : get_debug_type($value))); + } + + public static function missingPrimaryKeyValue(string $className, string $idField): self + { + return new self(sprintf('Missing value for primary key %s on %s', $idField, $className)); + } + + public static function proxyDirectoryRequired(): self + { + return new self('You must configure a proxy directory. See docs for details'); + } + + public static function proxyNamespaceRequired(): self + { + return new self('You must configure a proxy namespace'); + } + + public static function proxyDirectoryNotWritable(string $proxyDirectory): self + { + return new self(sprintf('Your proxy directory "%s" must be writable', $proxyDirectory)); + } + + /** + * Helper method to show an object as string. + */ + private static function objToStr(object $obj): string + { + return $obj instanceof Stringable ? (string) $obj : get_debug_type($obj) . '@' . spl_object_id($obj); + } + + private static function newEntityFoundThroughRelationshipMessage(AssociationMapping $associationMapping, object $entity): string + { + return 'A new entity was found through the relationship \'' + . $associationMapping->sourceEntity . '#' . $associationMapping->fieldName . '\' that was not' + . ' configured to cascade persist operations for entity: ' . self::objToStr($entity) . '.' + . ' To solve this issue: Either explicitly call EntityManager#persist()' + . ' on this unknown entity or configure cascade persist' + . ' this association in the mapping for example @ManyToOne(..,cascade={"persist"}).' + . ($entity instanceof Stringable + ? '' + : ' If you cannot find out which entity causes the problem implement \'' + . $associationMapping->targetEntity . '#__toString()\' to get a clue.' + ); + } +} diff --git a/vendor/doctrine/orm/src/ORMSetup.php b/vendor/doctrine/orm/src/ORMSetup.php new file mode 100644 index 0000000..7354c71 --- /dev/null +++ b/vendor/doctrine/orm/src/ORMSetup.php @@ -0,0 +1,127 @@ +setMetadataDriverImpl(new AttributeDriver($paths)); + + return $config; + } + + /** + * Creates a configuration with an XML metadata driver. + * + * @param string[] $paths + */ + public static function createXMLMetadataConfiguration( + array $paths, + bool $isDevMode = false, + string|null $proxyDir = null, + CacheItemPoolInterface|null $cache = null, + bool $isXsdValidationEnabled = true, + ): Configuration { + $config = self::createConfiguration($isDevMode, $proxyDir, $cache); + $config->setMetadataDriverImpl(new XmlDriver($paths, XmlDriver::DEFAULT_FILE_EXTENSION, $isXsdValidationEnabled)); + + return $config; + } + + /** + * Creates a configuration without a metadata driver. + */ + public static function createConfiguration( + bool $isDevMode = false, + string|null $proxyDir = null, + CacheItemPoolInterface|null $cache = null, + ): Configuration { + $proxyDir = $proxyDir ?: sys_get_temp_dir(); + + $cache = self::createCacheInstance($isDevMode, $proxyDir, $cache); + + $config = new Configuration(); + + $config->setMetadataCache($cache); + $config->setQueryCache($cache); + $config->setResultCache($cache); + $config->setProxyDir($proxyDir); + $config->setProxyNamespace('DoctrineProxies'); + $config->setAutoGenerateProxyClasses($isDevMode); + + return $config; + } + + private static function createCacheInstance( + bool $isDevMode, + string $proxyDir, + CacheItemPoolInterface|null $cache, + ): CacheItemPoolInterface { + if ($cache !== null) { + return $cache; + } + + if (! class_exists(ArrayAdapter::class)) { + throw new RuntimeException( + 'The Doctrine setup tool cannot configure caches without symfony/cache.' + . ' Please add symfony/cache as explicit dependency or pass your own cache implementation.', + ); + } + + if ($isDevMode) { + return new ArrayAdapter(); + } + + $namespace = 'dc2_' . md5($proxyDir); + + if (extension_loaded('apcu') && apcu_enabled()) { + return new ApcuAdapter($namespace); + } + + if (MemcachedAdapter::isSupported()) { + return new MemcachedAdapter(MemcachedAdapter::createConnection('memcached://127.0.0.1'), $namespace); + } + + if (extension_loaded('redis')) { + $redis = new Redis(); + $redis->connect('127.0.0.1'); + + return new RedisAdapter($redis, $namespace); + } + + return new ArrayAdapter(); + } + + private function __construct() + { + } +} diff --git a/vendor/doctrine/orm/src/OptimisticLockException.php b/vendor/doctrine/orm/src/OptimisticLockException.php new file mode 100644 index 0000000..f84e134 --- /dev/null +++ b/vendor/doctrine/orm/src/OptimisticLockException.php @@ -0,0 +1,55 @@ +entity; + } + + /** @param object|class-string $entity */ + public static function lockFailed(object|string $entity): self + { + return new self('The optimistic lock on an entity failed.', $entity); + } + + public static function lockFailedVersionMismatch( + object $entity, + int|string|DateTimeInterface $expectedLockVersion, + int|string|DateTimeInterface $actualLockVersion, + ): self { + $expectedLockVersion = $expectedLockVersion instanceof DateTimeInterface ? $expectedLockVersion->getTimestamp() : $expectedLockVersion; + $actualLockVersion = $actualLockVersion instanceof DateTimeInterface ? $actualLockVersion->getTimestamp() : $actualLockVersion; + + return new self('The optimistic lock failed, version ' . $expectedLockVersion . ' was expected, but is actually ' . $actualLockVersion, $entity); + } + + public static function notVersioned(string $entityName): self + { + return new self('Cannot obtain optimistic lock on unversioned entity ' . $entityName, null); + } +} diff --git a/vendor/doctrine/orm/src/PersistentCollection.php b/vendor/doctrine/orm/src/PersistentCollection.php new file mode 100644 index 0000000..d54d3d1 --- /dev/null +++ b/vendor/doctrine/orm/src/PersistentCollection.php @@ -0,0 +1,652 @@ + + * @template-implements Selectable + */ +final class PersistentCollection extends AbstractLazyCollection implements Selectable +{ + /** + * A snapshot of the collection at the moment it was fetched from the database. + * This is used to create a diff of the collection at commit time. + * + * @psalm-var array + */ + private array $snapshot = []; + + /** + * The entity that owns this collection. + */ + private object|null $owner = null; + + /** + * The association mapping the collection belongs to. + * This is currently either a OneToManyMapping or a ManyToManyMapping. + * + * @var (AssociationMapping&ToManyAssociationMapping)|null + */ + private AssociationMapping|null $association = null; + + /** + * The name of the field on the target entities that points to the owner + * of the collection. This is only set if the association is bi-directional. + */ + private string|null $backRefFieldName = null; + + /** + * Whether the collection is dirty and needs to be synchronized with the database + * when the UnitOfWork that manages its persistent state commits. + */ + private bool $isDirty = false; + + /** + * Creates a new persistent collection. + * + * @param EntityManagerInterface $em The EntityManager the collection will be associated with. + * @param ClassMetadata $typeClass The class descriptor of the entity type of this collection. + * @psalm-param Collection&Selectable $collection The collection elements. + */ + public function __construct( + private EntityManagerInterface|null $em, + private readonly ClassMetadata|null $typeClass, + Collection $collection, + ) { + $this->collection = $collection; + $this->initialized = true; + } + + /** + * INTERNAL: + * Sets the collection's owning entity together with the AssociationMapping that + * describes the association between the owner and the elements of the collection. + */ + public function setOwner(object $entity, AssociationMapping&ToManyAssociationMapping $assoc): void + { + $this->owner = $entity; + $this->association = $assoc; + $this->backRefFieldName = $assoc->isOwningSide() ? $assoc->inversedBy : $assoc->mappedBy; + } + + /** + * INTERNAL: + * Gets the collection owner. + */ + public function getOwner(): object|null + { + return $this->owner; + } + + public function getTypeClass(): ClassMetadata + { + assert($this->typeClass !== null); + + return $this->typeClass; + } + + private function getUnitOfWork(): UnitOfWork + { + assert($this->em !== null); + + return $this->em->getUnitOfWork(); + } + + /** + * INTERNAL: + * Adds an element to a collection during hydration. This will automatically + * complete bidirectional associations in the case of a one-to-many association. + */ + public function hydrateAdd(mixed $element): void + { + $this->unwrap()->add($element); + + // If _backRefFieldName is set and its a one-to-many association, + // we need to set the back reference. + if ($this->backRefFieldName && $this->getMapping()->isOneToMany()) { + assert($this->typeClass !== null); + // Set back reference to owner + $this->typeClass->reflFields[$this->backRefFieldName]->setValue( + $element, + $this->owner, + ); + + $this->getUnitOfWork()->setOriginalEntityProperty( + spl_object_id($element), + $this->backRefFieldName, + $this->owner, + ); + } + } + + /** + * INTERNAL: + * Sets a keyed element in the collection during hydration. + */ + public function hydrateSet(mixed $key, mixed $element): void + { + $this->unwrap()->set($key, $element); + + // If _backRefFieldName is set, then the association is bidirectional + // and we need to set the back reference. + if ($this->backRefFieldName && $this->getMapping()->isOneToMany()) { + assert($this->typeClass !== null); + // Set back reference to owner + $this->typeClass->reflFields[$this->backRefFieldName]->setValue( + $element, + $this->owner, + ); + } + } + + /** + * Initializes the collection by loading its contents from the database + * if the collection is not yet initialized. + */ + public function initialize(): void + { + if ($this->initialized || ! $this->association) { + return; + } + + $this->doInitialize(); + + $this->initialized = true; + } + + /** + * INTERNAL: + * Tells this collection to take a snapshot of its current state. + */ + public function takeSnapshot(): void + { + $this->snapshot = $this->unwrap()->toArray(); + $this->isDirty = false; + } + + /** + * INTERNAL: + * Returns the last snapshot of the elements in the collection. + * + * @psalm-return array The last snapshot of the elements. + */ + public function getSnapshot(): array + { + return $this->snapshot; + } + + /** + * INTERNAL: + * getDeleteDiff + * + * @return mixed[] + */ + public function getDeleteDiff(): array + { + $collectionItems = $this->unwrap()->toArray(); + + return array_values(array_diff_key( + array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot), + array_combine(array_map('spl_object_id', $collectionItems), $collectionItems), + )); + } + + /** + * INTERNAL: + * getInsertDiff + * + * @return mixed[] + */ + public function getInsertDiff(): array + { + $collectionItems = $this->unwrap()->toArray(); + + return array_values(array_diff_key( + array_combine(array_map('spl_object_id', $collectionItems), $collectionItems), + array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot), + )); + } + + /** INTERNAL: Gets the association mapping of the collection. */ + public function getMapping(): AssociationMapping&ToManyAssociationMapping + { + if ($this->association === null) { + throw new UnexpectedValueException('The underlying association mapping is null although it should not be'); + } + + return $this->association; + } + + /** + * Marks this collection as changed/dirty. + */ + private function changed(): void + { + if ($this->isDirty) { + return; + } + + $this->isDirty = true; + } + + /** + * Gets a boolean flag indicating whether this collection is dirty which means + * its state needs to be synchronized with the database. + */ + public function isDirty(): bool + { + return $this->isDirty; + } + + /** + * Sets a boolean flag, indicating whether this collection is dirty. + */ + public function setDirty(bool $dirty): void + { + $this->isDirty = $dirty; + } + + /** + * Sets the initialized flag of the collection, forcing it into that state. + */ + public function setInitialized(bool $bool): void + { + $this->initialized = $bool; + } + + public function remove(string|int $key): mixed + { + // TODO: If the keys are persistent as well (not yet implemented) + // and the collection is not initialized and orphanRemoval is + // not used we can issue a straight SQL delete/update on the + // association (table). Without initializing the collection. + $removed = parent::remove($key); + + if (! $removed) { + return $removed; + } + + $this->changed(); + + if ( + $this->association !== null && + $this->association->isToMany() && + $this->owner && + $this->getMapping()->orphanRemoval + ) { + $this->getUnitOfWork()->scheduleOrphanRemoval($removed); + } + + return $removed; + } + + public function removeElement(mixed $element): bool + { + $removed = parent::removeElement($element); + + if (! $removed) { + return $removed; + } + + $this->changed(); + + if ( + $this->association !== null && + $this->association->isToMany() && + $this->owner && + $this->getMapping()->orphanRemoval + ) { + $this->getUnitOfWork()->scheduleOrphanRemoval($element); + } + + return $removed; + } + + public function containsKey(mixed $key): bool + { + if ( + ! $this->initialized && $this->getMapping()->fetch === ClassMetadata::FETCH_EXTRA_LAZY + && isset($this->getMapping()->indexBy) + ) { + $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping()); + + return $this->unwrap()->containsKey($key) || $persister->containsKey($this, $key); + } + + return parent::containsKey($key); + } + + public function contains(mixed $element): bool + { + if (! $this->initialized && $this->getMapping()->fetch === ClassMetadata::FETCH_EXTRA_LAZY) { + $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping()); + + return $this->unwrap()->contains($element) || $persister->contains($this, $element); + } + + return parent::contains($element); + } + + public function get(string|int $key): mixed + { + if ( + ! $this->initialized + && $this->getMapping()->fetch === ClassMetadata::FETCH_EXTRA_LAZY + && isset($this->getMapping()->indexBy) + ) { + assert($this->em !== null); + assert($this->typeClass !== null); + if (! $this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->getMapping()->indexBy)) { + return $this->em->find($this->typeClass->name, $key); + } + + return $this->getUnitOfWork()->getCollectionPersister($this->getMapping())->get($this, $key); + } + + return parent::get($key); + } + + public function count(): int + { + if (! $this->initialized && $this->association !== null && $this->getMapping()->fetch === ClassMetadata::FETCH_EXTRA_LAZY) { + $persister = $this->getUnitOfWork()->getCollectionPersister($this->association); + + return $persister->count($this) + ($this->isDirty ? $this->unwrap()->count() : 0); + } + + return parent::count(); + } + + public function set(string|int $key, mixed $value): void + { + parent::set($key, $value); + + $this->changed(); + + if (is_object($value) && $this->em) { + $this->getUnitOfWork()->cancelOrphanRemoval($value); + } + } + + public function add(mixed $value): bool + { + $this->unwrap()->add($value); + + $this->changed(); + + if (is_object($value) && $this->em) { + $this->getUnitOfWork()->cancelOrphanRemoval($value); + } + + return true; + } + + public function offsetExists(mixed $offset): bool + { + return $this->containsKey($offset); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->get($offset); + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if (! isset($offset)) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + public function offsetUnset(mixed $offset): void + { + $this->remove($offset); + } + + public function isEmpty(): bool + { + return $this->unwrap()->isEmpty() && $this->count() === 0; + } + + public function clear(): void + { + if ($this->initialized && $this->isEmpty()) { + $this->unwrap()->clear(); + + return; + } + + $uow = $this->getUnitOfWork(); + $association = $this->getMapping(); + + if ( + $association->isToMany() && + $association->orphanRemoval && + $this->owner + ) { + // we need to initialize here, as orphan removal acts like implicit cascadeRemove, + // hence for event listeners we need the objects in memory. + $this->initialize(); + + foreach ($this->unwrap() as $element) { + $uow->scheduleOrphanRemoval($element); + } + } + + $this->unwrap()->clear(); + + $this->initialized = true; // direct call, {@link initialize()} is too expensive + + if ($association->isOwningSide() && $this->owner) { + $this->changed(); + + $uow->scheduleCollectionDeletion($this); + + $this->takeSnapshot(); + } + } + + /** + * Called by PHP when this collection is serialized. Ensures that only the + * elements are properly serialized. + * + * Internal note: Tried to implement Serializable first but that did not work well + * with circular references. This solution seems simpler and works well. + * + * @return string[] + * @psalm-return array{0: string, 1: string} + */ + public function __sleep(): array + { + return ['collection', 'initialized']; + } + + public function __wakeup(): void + { + $this->em = null; + } + + /** + * Extracts a slice of $length elements starting at position $offset from the Collection. + * + * If $length is null it returns all elements from $offset to the end of the Collection. + * Keys have to be preserved by this method. Calling this method will only return the + * selected slice and NOT change the elements contained in the collection slice is called on. + * + * @return mixed[] + * @psalm-return array + */ + public function slice(int $offset, int|null $length = null): array + { + if (! $this->initialized && ! $this->isDirty && $this->getMapping()->fetch === ClassMetadata::FETCH_EXTRA_LAZY) { + $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping()); + + return $persister->slice($this, $offset, $length); + } + + return parent::slice($offset, $length); + } + + /** + * Cleans up internal state of cloned persistent collection. + * + * The following problems have to be prevented: + * 1. Added entities are added to old PC + * 2. New collection is not dirty, if reused on other entity nothing + * changes. + * 3. Snapshot leads to invalid diffs being generated. + * 4. Lazy loading grabs entities from old owner object. + * 5. New collection is connected to old owner and leads to duplicate keys. + */ + public function __clone() + { + if (is_object($this->collection)) { + $this->collection = clone $this->collection; + } + + $this->initialize(); + + $this->owner = null; + $this->snapshot = []; + + $this->changed(); + } + + /** + * Selects all elements from a selectable that match the expression and + * return a new collection containing these elements. + * + * @psalm-return Collection + * + * @throws RuntimeException + */ + public function matching(Criteria $criteria): Collection + { + if ($this->isDirty) { + $this->initialize(); + } + + if ($this->initialized) { + return $this->unwrap()->matching($criteria); + } + + $association = $this->getMapping(); + if ($association->isManyToMany()) { + $persister = $this->getUnitOfWork()->getCollectionPersister($association); + + return new ArrayCollection($persister->loadCriteria($this, $criteria)); + } + + $builder = Criteria::expr(); + $ownerExpression = $builder->eq($this->backRefFieldName, $this->owner); + $expression = $criteria->getWhereExpression(); + $expression = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression; + + $criteria = clone $criteria; + $criteria->where($expression); + $criteria->orderBy( + $criteria->orderings() ?: array_map( + static fn (string $order): Order => Order::from(strtoupper($order)), + $association->orderBy(), + ), + ); + + $persister = $this->getUnitOfWork()->getEntityPersister($association->targetEntity); + + return $association->fetch === ClassMetadata::FETCH_EXTRA_LAZY + ? new LazyCriteriaCollection($persister, $criteria) + : new ArrayCollection($persister->loadCriteria($criteria)); + } + + /** + * Retrieves the wrapped Collection instance. + * + * @return Collection&Selectable + */ + public function unwrap(): Selectable&Collection + { + assert($this->collection instanceof Collection); + assert($this->collection instanceof Selectable); + + return $this->collection; + } + + protected function doInitialize(): void + { + // Has NEW objects added through add(). Remember them. + $newlyAddedDirtyObjects = []; + + if ($this->isDirty) { + $newlyAddedDirtyObjects = $this->unwrap()->toArray(); + } + + $this->unwrap()->clear(); + $this->getUnitOfWork()->loadCollection($this); + $this->takeSnapshot(); + + if ($newlyAddedDirtyObjects) { + $this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects); + } + } + + /** + * @param object[] $newObjects + * + * Note: the only reason why this entire looping/complexity is performed via `spl_object_id` + * is because we want to prevent using `array_udiff()`, which is likely to cause very + * high overhead (complexity of O(n^2)). `array_diff_key()` performs the operation in + * core, which is faster than using a callback for comparisons + */ + private function restoreNewObjectsInDirtyCollection(array $newObjects): void + { + $loadedObjects = $this->unwrap()->toArray(); + $newObjectsByOid = array_combine(array_map('spl_object_id', $newObjects), $newObjects); + $loadedObjectsByOid = array_combine(array_map('spl_object_id', $loadedObjects), $loadedObjects); + $newObjectsThatWereNotLoaded = array_diff_key($newObjectsByOid, $loadedObjectsByOid); + + if ($newObjectsThatWereNotLoaded) { + // Reattach NEW objects added through add(), if any. + array_walk($newObjectsThatWereNotLoaded, [$this->unwrap(), 'add']); + + $this->isDirty = true; + } + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Collection/AbstractCollectionPersister.php b/vendor/doctrine/orm/src/Persisters/Collection/AbstractCollectionPersister.php new file mode 100644 index 0000000..26f0b9e --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Collection/AbstractCollectionPersister.php @@ -0,0 +1,50 @@ +uow = $em->getUnitOfWork(); + $this->conn = $em->getConnection(); + $this->platform = $this->conn->getDatabasePlatform(); + $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + } + + /** + * Check if entity is in a valid state for operations. + */ + protected function isValidEntityState(object $entity): bool + { + $entityState = $this->uow->getEntityState($entity, UnitOfWork::STATE_NEW); + + if ($entityState === UnitOfWork::STATE_NEW) { + return false; + } + + // If Entity is scheduled for inclusion, it is not in this collection. + // We can assure that because it would have return true before on array check + return ! ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity)); + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Collection/CollectionPersister.php b/vendor/doctrine/orm/src/Persisters/Collection/CollectionPersister.php new file mode 100644 index 0000000..07c4eaf --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Collection/CollectionPersister.php @@ -0,0 +1,59 @@ +getMapping($collection); + + if (! $mapping->isOwningSide()) { + return; // ignore inverse side + } + + assert($mapping->isManyToManyOwningSide()); + + $types = []; + $class = $this->em->getClassMetadata($mapping->sourceEntity); + + foreach ($mapping->joinTable->joinColumns as $joinColumn) { + $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em); + } + + $this->conn->executeStatement($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types); + } + + public function update(PersistentCollection $collection): void + { + $mapping = $this->getMapping($collection); + + if (! $mapping->isOwningSide()) { + return; // ignore inverse side + } + + [$deleteSql, $deleteTypes] = $this->getDeleteRowSQL($collection); + [$insertSql, $insertTypes] = $this->getInsertRowSQL($collection); + + foreach ($collection->getDeleteDiff() as $element) { + $this->conn->executeStatement( + $deleteSql, + $this->getDeleteRowSQLParameters($collection, $element), + $deleteTypes, + ); + } + + foreach ($collection->getInsertDiff() as $element) { + $this->conn->executeStatement( + $insertSql, + $this->getInsertRowSQLParameters($collection, $element), + $insertTypes, + ); + } + } + + public function get(PersistentCollection $collection, mixed $index): object|null + { + $mapping = $this->getMapping($collection); + + if (! $mapping->isIndexed()) { + throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); + } + + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + $mappedKey = $mapping->isOwningSide() + ? $mapping->inversedBy + : $mapping->mappedBy; + + assert($mappedKey !== null); + + return $persister->load( + [$mappedKey => $collection->getOwner(), $mapping->indexBy() => $index], + null, + $mapping, + [], + LockMode::NONE, + 1, + ); + } + + public function count(PersistentCollection $collection): int + { + $conditions = []; + $params = []; + $types = []; + $mapping = $this->getMapping($collection); + $id = $this->uow->getEntityIdentifier($collection->getOwner()); + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $association = $this->em->getMetadataFactory()->getOwningSide($mapping); + + $joinTableName = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform); + $joinColumns = ! $mapping->isOwningSide() + ? $association->joinTable->inverseJoinColumns + : $association->joinTable->joinColumns; + + foreach ($joinColumns as $joinColumn) { + $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform); + $referencedName = $joinColumn->referencedColumnName; + $conditions[] = 't.' . $columnName . ' = ?'; + $params[] = $id[$sourceClass->getFieldForColumn($referencedName)]; + $types[] = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em); + } + + [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($mapping); + + if ($filterSql) { + $conditions[] = $filterSql; + } + + // If there is a provided criteria, make part of conditions + // @todo Fix this. Current SQL returns something like: + /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) { + // A join is needed on the target entity + $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); + $targetJoinSql = ' JOIN ' . $targetTableName . ' te' + . ' ON' . implode(' AND ', $this->getOnConditionSQL($association)); + + // And criteria conditions needs to be added + $persister = $this->uow->getEntityPersister($targetClass->name); + $visitor = new SqlExpressionVisitor($persister, $targetClass); + $conditions[] = $visitor->dispatch($expression); + + $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL; + }*/ + + $sql = 'SELECT COUNT(*)' + . ' FROM ' . $joinTableName . ' t' + . $joinTargetEntitySQL + . ' WHERE ' . implode(' AND ', $conditions); + + return (int) $this->conn->fetchOne($sql, $params, $types); + } + + /** + * {@inheritDoc} + */ + public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array + { + $mapping = $this->getMapping($collection); + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length); + } + + public function containsKey(PersistentCollection $collection, mixed $key): bool + { + $mapping = $this->getMapping($collection); + + if (! $mapping->isIndexed()) { + throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); + } + + [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictionsWithKey( + $collection, + (string) $key, + true, + ); + + $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); + + return (bool) $this->conn->fetchOne($sql, $params, $types); + } + + public function contains(PersistentCollection $collection, object $element): bool + { + if (! $this->isValidEntityState($element)) { + return false; + } + + [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions( + $collection, + $element, + true, + ); + + $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); + + return (bool) $this->conn->fetchOne($sql, $params, $types); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array + { + $mapping = $this->getMapping($collection); + $owner = $collection->getOwner(); + $ownerMetadata = $this->em->getClassMetadata($owner::class); + $id = $this->uow->getEntityIdentifier($owner); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $onConditions = $this->getOnConditionSQL($mapping); + $whereClauses = $params = []; + $paramTypes = []; + + if (! $mapping->isOwningSide()) { + assert($mapping instanceof InverseSideMapping); + $associationSourceClass = $targetClass; + $sourceRelationMode = 'relationToTargetKeyColumns'; + } else { + $associationSourceClass = $ownerMetadata; + $sourceRelationMode = 'relationToSourceKeyColumns'; + } + + $mapping = $this->em->getMetadataFactory()->getOwningSide($mapping); + + foreach ($mapping->$sourceRelationMode as $key => $value) { + $whereClauses[] = sprintf('t.%s = ?', $key); + $params[] = $ownerMetadata->containsForeignIdentifier + ? $id[$ownerMetadata->getFieldForColumn($value)] + : $id[$ownerMetadata->fieldNames[$value]]; + $paramTypes[] = PersisterHelper::getTypeOfColumn($value, $ownerMetadata, $this->em); + } + + $parameters = $this->expandCriteriaParameters($criteria); + + foreach ($parameters as $parameter) { + [$name, $value, $operator] = $parameter; + + $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform); + + if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) { + $whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT'); + } else { + $whereClauses[] = sprintf('te.%s %s ?', $field, $operator); + $params[] = $value; + $paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0]; + } + } + + $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); + $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform); + + $rsm = new Query\ResultSetMappingBuilder($this->em); + $rsm->addRootEntityFromClassMetadata($targetClass->name, 'te'); + + $sql = 'SELECT ' . $rsm->generateSelectClause() + . ' FROM ' . $tableName . ' te' + . ' JOIN ' . $joinTable . ' t ON' + . implode(' AND ', $onConditions) + . ' WHERE ' . implode(' AND ', $whereClauses); + + $sql .= $this->getOrderingSql($criteria, $targetClass); + + $sql .= $this->getLimitSql($criteria); + + $stmt = $this->conn->executeQuery($sql, $params, $paramTypes); + + return $this + ->em + ->newHydrator(Query::HYDRATE_OBJECT) + ->hydrateAll($stmt, $rsm); + } + + /** + * Generates the filter SQL for a given mapping. + * + * This method is not used for actually grabbing the related entities + * but when the extra-lazy collection methods are called on a filtered + * association. This is why besides the many to many table we also + * have to join in the actual entities table leading to additional + * JOIN. + * + * @param AssociationMapping $mapping Array containing mapping information. + * + * @return string[] ordered tuple: + * - JOIN condition to add to the SQL + * - WHERE condition to add to the SQL + * @psalm-return array{0: string, 1: string} + */ + public function getFilterSql(AssociationMapping $mapping): array + { + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); + $filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); + + if ($filterSql === '') { + return ['', '']; + } + + // A join is needed if there is filtering on the target entity + $tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform); + $joinSql = ' JOIN ' . $tableName . ' te' + . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping)); + + return [$joinSql, $filterSql]; + } + + /** + * Generates the filter SQL for a given entity and table alias. + * + * @param ClassMetadata $targetEntity Metadata of the target entity. + * @param string $targetTableAlias The table alias of the joined/selected table. + * + * @return string The SQL query part to add to a query. + */ + protected function generateFilterConditionSQL(ClassMetadata $targetEntity, string $targetTableAlias): string + { + $filterClauses = []; + + foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { + $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); + if ($filterExpr) { + $filterClauses[] = '(' . $filterExpr . ')'; + } + } + + return $filterClauses + ? '(' . implode(' AND ', $filterClauses) . ')' + : ''; + } + + /** + * Generate ON condition + * + * @return string[] + * @psalm-return list + */ + protected function getOnConditionSQL(AssociationMapping $mapping): array + { + $association = $this->em->getMetadataFactory()->getOwningSide($mapping); + $joinColumns = $mapping->isOwningSide() + ? $association->joinTable->inverseJoinColumns + : $association->joinTable->joinColumns; + + $conditions = []; + + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + foreach ($joinColumns as $joinColumn) { + $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); + + $conditions[] = ' t.' . $joinColumnName . ' = te.' . $refColumnName; + } + + return $conditions; + } + + protected function getDeleteSQL(PersistentCollection $collection): string + { + $columns = []; + $mapping = $this->getMapping($collection); + assert($mapping->isManyToManyOwningSide()); + $class = $this->em->getClassMetadata($collection->getOwner()::class); + $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); + + foreach ($mapping->joinTable->joinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + } + + return 'DELETE FROM ' . $joinTable + . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; + } + + /** + * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql. + * + * @return list + */ + protected function getDeleteSQLParameters(PersistentCollection $collection): array + { + $mapping = $this->getMapping($collection); + assert($mapping->isManyToManyOwningSide()); + $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); + + // Optimization for single column identifier + if (count($mapping->relationToSourceKeyColumns) === 1) { + return [reset($identifier)]; + } + + // Composite identifier + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $params = []; + + foreach ($mapping->relationToSourceKeyColumns as $columnName => $refColumnName) { + $params[] = isset($sourceClass->fieldNames[$refColumnName]) + ? $identifier[$sourceClass->fieldNames[$refColumnName]] + : $identifier[$sourceClass->getFieldForColumn($refColumnName)]; + } + + return $params; + } + + /** + * Gets the SQL statement used for deleting a row from the collection. + * + * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array + * of types for bound parameters + * @psalm-return array{0: string, 1: list} + */ + protected function getDeleteRowSQL(PersistentCollection $collection): array + { + $mapping = $this->getMapping($collection); + assert($mapping->isManyToManyOwningSide()); + $class = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $columns = []; + $types = []; + + foreach ($mapping->joinTable->joinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em); + } + + foreach ($mapping->joinTable->inverseJoinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); + } + + return [ + 'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) + . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?', + $types, + ]; + } + + /** + * Gets the SQL parameters for the corresponding SQL statement to delete the given + * element from the given collection. + * + * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql. + * + * @return mixed[] + * @psalm-return list + */ + protected function getDeleteRowSQLParameters(PersistentCollection $collection, object $element): array + { + return $this->collectJoinTableColumnParameters($collection, $element); + } + + /** + * Gets the SQL statement used for inserting a row in the collection. + * + * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array + * of types for bound parameters + * @psalm-return array{0: string, 1: list} + */ + protected function getInsertRowSQL(PersistentCollection $collection): array + { + $columns = []; + $types = []; + $mapping = $this->getMapping($collection); + assert($mapping->isManyToManyOwningSide()); + $class = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + + foreach ($mapping->joinTable->joinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em); + } + + foreach ($mapping->joinTable->inverseJoinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); + } + + return [ + 'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) + . ' (' . implode(', ', $columns) . ')' + . ' VALUES' + . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')', + $types, + ]; + } + + /** + * Gets the SQL parameters for the corresponding SQL statement to insert the given + * element of the given collection into the database. + * + * Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql. + * + * @return mixed[] + * @psalm-return list + */ + protected function getInsertRowSQLParameters(PersistentCollection $collection, object $element): array + { + return $this->collectJoinTableColumnParameters($collection, $element); + } + + /** + * Collects the parameters for inserting/deleting on the join table in the order + * of the join table columns as specified in ManyToManyMapping#joinTableColumns. + * + * @return mixed[] + * @psalm-return list + */ + private function collectJoinTableColumnParameters( + PersistentCollection $collection, + object $element, + ): array { + $params = []; + $mapping = $this->getMapping($collection); + assert($mapping->isManyToManyOwningSide()); + $isComposite = count($mapping->joinTableColumns) > 2; + + $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner()); + $identifier2 = $this->uow->getEntityIdentifier($element); + + $class1 = $class2 = null; + if ($isComposite) { + $class1 = $this->em->getClassMetadata($collection->getOwner()::class); + $class2 = $collection->getTypeClass(); + } + + foreach ($mapping->joinTableColumns as $joinTableColumn) { + $isRelationToSource = isset($mapping->relationToSourceKeyColumns[$joinTableColumn]); + + if (! $isComposite) { + $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); + + continue; + } + + if ($isRelationToSource) { + $params[] = $identifier1[$class1->getFieldForColumn($mapping->relationToSourceKeyColumns[$joinTableColumn])]; + + continue; + } + + $params[] = $identifier2[$class2->getFieldForColumn($mapping->relationToTargetKeyColumns[$joinTableColumn])]; + } + + return $params; + } + + /** + * @param bool $addFilters Whether the filter SQL should be included or not. + * + * @return mixed[] ordered vector: + * - quoted join table name + * - where clauses to be added for filtering + * - parameters to be bound for filtering + * - types of the parameters to be bound for filtering + * @psalm-return array{0: string, 1: list, 2: list, 3: list} + */ + private function getJoinTableRestrictionsWithKey( + PersistentCollection $collection, + string $key, + bool $addFilters, + ): array { + $filterMapping = $this->getMapping($collection); + $mapping = $filterMapping; + $indexBy = $mapping->indexBy(); + $id = $this->uow->getEntityIdentifier($collection->getOwner()); + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + + if (! $mapping->isOwningSide()) { + assert($mapping instanceof InverseSideMapping); + $associationSourceClass = $this->em->getClassMetadata($mapping->targetEntity); + $mapping = $associationSourceClass->associationMappings[$mapping->mappedBy]; + assert($mapping->isManyToManyOwningSide()); + $joinColumns = $mapping->joinTable->joinColumns; + $sourceRelationMode = 'relationToTargetKeyColumns'; + $targetRelationMode = 'relationToSourceKeyColumns'; + } else { + assert($mapping->isManyToManyOwningSide()); + $associationSourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $joinColumns = $mapping->joinTable->inverseJoinColumns; + $sourceRelationMode = 'relationToSourceKeyColumns'; + $targetRelationMode = 'relationToTargetKeyColumns'; + } + + $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform) . ' t'; + $whereClauses = []; + $params = []; + $types = []; + + $joinNeeded = ! in_array($indexBy, $targetClass->identifier, true); + + if ($joinNeeded) { // extra join needed if indexBy is not a @id + $joinConditions = []; + + foreach ($joinColumns as $joinTableColumn) { + $joinConditions[] = 't.' . $joinTableColumn->name . ' = tr.' . $joinTableColumn->referencedColumnName; + } + + $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); + $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions); + $columnName = $targetClass->getColumnName($indexBy); + + $whereClauses[] = 'tr.' . $columnName . ' = ?'; + $params[] = $key; + $types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em); + } + + foreach ($mapping->joinTableColumns as $joinTableColumn) { + if (isset($mapping->{$sourceRelationMode}[$joinTableColumn])) { + $column = $mapping->{$sourceRelationMode}[$joinTableColumn]; + $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; + $params[] = $sourceClass->containsForeignIdentifier + ? $id[$sourceClass->getFieldForColumn($column)] + : $id[$sourceClass->fieldNames[$column]]; + $types[] = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em); + } elseif (! $joinNeeded) { + $column = $mapping->{$targetRelationMode}[$joinTableColumn]; + + $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; + $params[] = $key; + $types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em); + } + } + + if ($addFilters) { + [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping); + + if ($filterSql) { + $quotedJoinTable .= ' ' . $joinTargetEntitySQL; + $whereClauses[] = $filterSql; + } + } + + return [$quotedJoinTable, $whereClauses, $params, $types]; + } + + /** + * @param bool $addFilters Whether the filter SQL should be included or not. + * + * @return mixed[] ordered vector: + * - quoted join table name + * - where clauses to be added for filtering + * - parameters to be bound for filtering + * - types of the parameters to be bound for filtering + * @psalm-return array{0: string, 1: list, 2: list, 3: list} + */ + private function getJoinTableRestrictions( + PersistentCollection $collection, + object $element, + bool $addFilters, + ): array { + $filterMapping = $this->getMapping($collection); + $mapping = $filterMapping; + + if (! $mapping->isOwningSide()) { + $sourceClass = $this->em->getClassMetadata($mapping->targetEntity); + $targetClass = $this->em->getClassMetadata($mapping->sourceEntity); + $sourceId = $this->uow->getEntityIdentifier($element); + $targetId = $this->uow->getEntityIdentifier($collection->getOwner()); + } else { + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $sourceId = $this->uow->getEntityIdentifier($collection->getOwner()); + $targetId = $this->uow->getEntityIdentifier($element); + } + + $mapping = $this->em->getMetadataFactory()->getOwningSide($mapping); + + $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); + $whereClauses = []; + $params = []; + $types = []; + + foreach ($mapping->joinTableColumns as $joinTableColumn) { + $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?'; + + if (isset($mapping->relationToTargetKeyColumns[$joinTableColumn])) { + $targetColumn = $mapping->relationToTargetKeyColumns[$joinTableColumn]; + $params[] = $targetId[$targetClass->getFieldForColumn($targetColumn)]; + $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); + + continue; + } + + // relationToSourceKeyColumns + $targetColumn = $mapping->relationToSourceKeyColumns[$joinTableColumn]; + $params[] = $sourceId[$sourceClass->getFieldForColumn($targetColumn)]; + $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em); + } + + if ($addFilters) { + $quotedJoinTable .= ' t'; + + [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping); + + if ($filterSql) { + $quotedJoinTable .= ' ' . $joinTargetEntitySQL; + $whereClauses[] = $filterSql; + } + } + + return [$quotedJoinTable, $whereClauses, $params, $types]; + } + + /** + * Expands Criteria Parameters by walking the expressions and grabbing all + * parameters and types from it. + * + * @return mixed[][] + */ + private function expandCriteriaParameters(Criteria $criteria): array + { + $expression = $criteria->getWhereExpression(); + + if ($expression === null) { + return []; + } + + $valueVisitor = new SqlValueVisitor(); + + $valueVisitor->dispatch($expression); + + [, $types] = $valueVisitor->getParamsAndTypes(); + + return $types; + } + + private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string + { + $orderings = $criteria->orderings(); + if ($orderings) { + $orderBy = []; + foreach ($orderings as $name => $direction) { + $field = $this->quoteStrategy->getColumnName( + $name, + $targetClass, + $this->platform, + ); + $orderBy[] = $field . ' ' . $direction->value; + } + + return ' ORDER BY ' . implode(', ', $orderBy); + } + + return ''; + } + + /** @throws DBALException */ + private function getLimitSql(Criteria $criteria): string + { + $limit = $criteria->getMaxResults(); + $offset = $criteria->getFirstResult(); + + return $this->platform->modifyLimitQuery('', $limit, $offset ?? 0); + } + + private function getMapping(PersistentCollection $collection): AssociationMapping&ManyToManyAssociationMapping + { + $mapping = $collection->getMapping(); + + assert($mapping instanceof ManyToManyAssociationMapping); + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php b/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php new file mode 100644 index 0000000..d96be8d --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php @@ -0,0 +1,270 @@ +getMapping($collection); + + if (! $mapping->orphanRemoval) { + // Handling non-orphan removal should never happen, as @OneToMany + // can only be inverse side. For owning side one to many, it is + // required to have a join table, which would classify as a ManyToManyPersister. + return; + } + + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + + $targetClass->isInheritanceTypeJoined() + ? $this->deleteJoinedEntityCollection($collection) + : $this->deleteEntityCollection($collection); + } + + public function update(PersistentCollection $collection): void + { + // This can never happen. One to many can only be inverse side. + // For owning side one to many, it is required to have a join table, + // then classifying it as a ManyToManyPersister. + return; + } + + public function get(PersistentCollection $collection, mixed $index): object|null + { + $mapping = $this->getMapping($collection); + + if (! $mapping->isIndexed()) { + throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); + } + + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + return $persister->load( + [ + $mapping->mappedBy => $collection->getOwner(), + $mapping->indexBy() => $index, + ], + null, + $mapping, + [], + null, + 1, + ); + } + + public function count(PersistentCollection $collection): int + { + $mapping = $this->getMapping($collection); + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + // only works with single id identifier entities. Will throw an + // exception in Entity Persisters if that is not the case for the + // 'mappedBy' field. + $criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); + + return $persister->count($criteria); + } + + /** + * {@inheritDoc} + */ + public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array + { + $mapping = $this->getMapping($collection); + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + return $persister->getOneToManyCollection($mapping, $collection->getOwner(), $offset, $length); + } + + public function containsKey(PersistentCollection $collection, mixed $key): bool + { + $mapping = $this->getMapping($collection); + + if (! $mapping->isIndexed()) { + throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); + } + + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + // only works with single id identifier entities. Will throw an + // exception in Entity Persisters if that is not the case for the + // 'mappedBy' field. + $criteria = new Criteria(); + + $criteria->andWhere(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); + $criteria->andWhere(Criteria::expr()->eq($mapping->indexBy(), $key)); + + return (bool) $persister->count($criteria); + } + + public function contains(PersistentCollection $collection, object $element): bool + { + if (! $this->isValidEntityState($element)) { + return false; + } + + $mapping = $this->getMapping($collection); + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + // only works with single id identifier entities. Will throw an + // exception in Entity Persisters if that is not the case for the + // 'mappedBy' field. + $criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); + + return $persister->exists($element, $criteria); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array + { + throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.'); + } + + /** + * @throws DBALException + * @throws EntityNotFoundException + * @throws MappingException + */ + private function deleteEntityCollection(PersistentCollection $collection): int + { + $mapping = $this->getMapping($collection); + $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $columns = []; + $parameters = []; + $types = []; + + foreach ($this->em->getMetadataFactory()->getOwningSide($mapping)->joinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn->referencedColumnName)]; + $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $sourceClass, $this->em); + } + + $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) + . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; + + if ($targetClass->isInheritanceTypeSingleTable()) { + $discriminatorColumn = $targetClass->getDiscriminatorColumn(); + $discriminatorValues = $targetClass->discriminatorValue ? [$targetClass->discriminatorValue] : array_keys($targetClass->discriminatorMap); + $statement .= ' AND ' . $discriminatorColumn->name . ' IN (' . implode(', ', array_fill(0, count($discriminatorValues), '?')) . ')'; + foreach ($discriminatorValues as $discriminatorValue) { + $parameters[] = $discriminatorValue; + $types[] = $discriminatorColumn->type; + } + } + + $numAffected = $this->conn->executeStatement($statement, $parameters, $types); + + assert(is_int($numAffected)); + + return $numAffected; + } + + /** + * Delete Class Table Inheritance entities. + * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables. + * + * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =) + * + * @throws DBALException + */ + private function deleteJoinedEntityCollection(PersistentCollection $collection): int + { + $mapping = $this->getMapping($collection); + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); + + // 1) Build temporary table DDL + $tempTable = $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); + $idColumnNames = $rootClass->getIdentifierColumnNames(); + $idColumnList = implode(', ', $idColumnNames); + $columnDefinitions = []; + + foreach ($idColumnNames as $idColumnName) { + $columnDefinitions[$idColumnName] = [ + 'name' => $idColumnName, + 'notnull' => true, + 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em)), + ]; + } + + $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable + . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; + + $this->conn->executeStatement($statement); + + // 2) Build insert table records into temporary table + $query = $this->em->createQuery( + ' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames()) + . ' FROM ' . $targetClass->name . ' t0 WHERE t0.' . $mapping->mappedBy . ' = :owner', + )->setParameter('owner', $collection->getOwner()); + + $sql = $query->getSQL(); + assert(is_string($sql)); + $statement = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . $sql; + $parameters = array_values($sourceClass->getIdentifierValues($collection->getOwner())); + $numDeleted = $this->conn->executeStatement($statement, $parameters); + + // 3) Delete records on each table in the hierarchy + $classNames = [...$targetClass->parentClasses, ...[$targetClass->name], ...$targetClass->subClasses]; + + foreach (array_reverse($classNames) as $className) { + $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform); + $statement = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ')' + . ' IN (SELECT ' . $idColumnList . ' FROM ' . $tempTable . ')'; + + $this->conn->executeStatement($statement); + } + + // 4) Drop temporary table + $statement = $this->platform->getDropTemporaryTableSQL($tempTable); + + $this->conn->executeStatement($statement); + + assert(is_int($numDeleted)); + + return $numDeleted; + } + + private function getMapping(PersistentCollection $collection): OneToManyAssociationMapping + { + $mapping = $collection->getMapping(); + + assert($mapping->isOneToMany()); + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/AbstractEntityInheritancePersister.php b/vendor/doctrine/orm/src/Persisters/Entity/AbstractEntityInheritancePersister.php new file mode 100644 index 0000000..cf8a74e --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Entity/AbstractEntityInheritancePersister.php @@ -0,0 +1,66 @@ +class->getDiscriminatorColumn(); + $this->columnTypes[$discColumn->name] = $discColumn->type; + $data[$this->getDiscriminatorColumnTableName()][$discColumn->name] = $this->class->discriminatorValue; + + return $data; + } + + /** + * Gets the name of the table that contains the discriminator column. + */ + abstract protected function getDiscriminatorColumnTableName(): string; + + protected function getSelectColumnSQL(string $field, ClassMetadata $class, string $alias = 'r'): string + { + $tableAlias = $alias === 'r' ? '' : $alias; + $fieldMapping = $class->fieldMappings[$field]; + $columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName); + $sql = sprintf( + '%s.%s', + $this->getSQLTableAlias($class->name, $tableAlias), + $this->quoteStrategy->getColumnName($field, $class, $this->platform), + ); + + $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field, $class->name); + + $type = Type::getType($fieldMapping->type); + $sql = $type->convertToPHPValueSQL($sql, $this->platform); + + return $sql . ' AS ' . $columnAlias; + } + + protected function getSelectJoinColumnSQL(string $tableAlias, string $joinColumnName, string $quotedColumnName, string $type): string + { + $columnAlias = $this->getSQLColumnAlias($joinColumnName); + + $this->currentPersisterContext->rsm->addMetaResult('r', $columnAlias, $joinColumnName, false, $type); + + return $tableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php b/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php new file mode 100644 index 0000000..abaf8f4 --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php @@ -0,0 +1,2110 @@ + */ + private static array $comparisonMap = [ + Comparison::EQ => '= %s', + Comparison::NEQ => '!= %s', + Comparison::GT => '> %s', + Comparison::GTE => '>= %s', + Comparison::LT => '< %s', + Comparison::LTE => '<= %s', + Comparison::IN => 'IN (%s)', + Comparison::NIN => 'NOT IN (%s)', + Comparison::CONTAINS => 'LIKE %s', + Comparison::STARTS_WITH => 'LIKE %s', + Comparison::ENDS_WITH => 'LIKE %s', + ]; + + /** + * The underlying DBAL Connection of the used EntityManager. + */ + protected Connection $conn; + + /** + * The database platform. + */ + protected AbstractPlatform $platform; + + /** + * Queued inserts. + * + * @psalm-var array + */ + protected array $queuedInserts = []; + + /** + * The map of column names to DBAL mapping types of all prepared columns used + * when INSERTing or UPDATEing an entity. + * + * @see prepareInsertData($entity) + * @see prepareUpdateData($entity) + * + * @var mixed[] + */ + protected array $columnTypes = []; + + /** + * The map of quoted column names. + * + * @see prepareInsertData($entity) + * @see prepareUpdateData($entity) + * + * @var mixed[] + */ + protected array $quotedColumns = []; + + /** + * The INSERT SQL statement used for entities handled by this persister. + * This SQL is only generated once per request, if at all. + */ + private string|null $insertSql = null; + + /** + * The quote strategy. + */ + protected QuoteStrategy $quoteStrategy; + + /** + * The IdentifierFlattener used for manipulating identifiers + */ + protected readonly IdentifierFlattener $identifierFlattener; + + protected CachedPersisterContext $currentPersisterContext; + private readonly CachedPersisterContext $limitsHandlingContext; + private readonly CachedPersisterContext $noLimitsContext; + + /** + * Initializes a new BasicEntityPersister that uses the given EntityManager + * and persists instances of the class described by the given ClassMetadata descriptor. + * + * @param ClassMetadata $class Metadata object that describes the mapping of the mapped entity class. + */ + public function __construct( + protected EntityManagerInterface $em, + protected ClassMetadata $class, + ) { + $this->conn = $em->getConnection(); + $this->platform = $this->conn->getDatabasePlatform(); + $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); + $this->noLimitsContext = $this->currentPersisterContext = new CachedPersisterContext( + $class, + new Query\ResultSetMapping(), + false, + ); + $this->limitsHandlingContext = new CachedPersisterContext( + $class, + new Query\ResultSetMapping(), + true, + ); + } + + public function getClassMetadata(): ClassMetadata + { + return $this->class; + } + + public function getResultSetMapping(): ResultSetMapping + { + return $this->currentPersisterContext->rsm; + } + + public function addInsert(object $entity): void + { + $this->queuedInserts[spl_object_id($entity)] = $entity; + } + + /** + * {@inheritDoc} + */ + public function getInserts(): array + { + return $this->queuedInserts; + } + + public function executeInserts(): void + { + if (! $this->queuedInserts) { + return; + } + + $uow = $this->em->getUnitOfWork(); + $idGenerator = $this->class->idGenerator; + $isPostInsertId = $idGenerator->isPostInsertGenerator(); + + $stmt = $this->conn->prepare($this->getInsertSQL()); + $tableName = $this->class->getTableName(); + + foreach ($this->queuedInserts as $key => $entity) { + $insertData = $this->prepareInsertData($entity); + + if (isset($insertData[$tableName])) { + $paramIndex = 1; + + foreach ($insertData[$tableName] as $column => $value) { + $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$column]); + } + } + + $stmt->executeStatement(); + + if ($isPostInsertId) { + $generatedId = $idGenerator->generateId($this->em, $entity); + $id = [$this->class->identifier[0] => $generatedId]; + + $uow->assignPostInsertId($entity, $generatedId); + } else { + $id = $this->class->getIdentifierValues($entity); + } + + if ($this->class->requiresFetchAfterChange) { + $this->assignDefaultVersionAndUpsertableValues($entity, $id); + } + + // Unset this queued insert, so that the prepareUpdateData() method knows right away + // (for the next entity already) that the current entity has been written to the database + // and no extra updates need to be scheduled to refer to it. + // + // In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities + // from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they + // were given to our addInsert() method. + unset($this->queuedInserts[$key]); + } + } + + /** + * Retrieves the default version value which was created + * by the preceding INSERT statement and assigns it back in to the + * entities version field if the given entity is versioned. + * Also retrieves values of columns marked as 'non insertable' and / or + * 'not updatable' and assigns them back to the entities corresponding fields. + * + * @param mixed[] $id + */ + protected function assignDefaultVersionAndUpsertableValues(object $entity, array $id): void + { + $values = $this->fetchVersionAndNotUpsertableValues($this->class, $id); + + foreach ($values as $field => $value) { + $value = Type::getType($this->class->fieldMappings[$field]->type)->convertToPHPValue($value, $this->platform); + + $this->class->setFieldValue($entity, $field, $value); + } + } + + /** + * Fetches the current version value of a versioned entity and / or the values of fields + * marked as 'not insertable' and / or 'not updatable'. + * + * @param mixed[] $id + */ + protected function fetchVersionAndNotUpsertableValues(ClassMetadata $versionedClass, array $id): mixed + { + $columnNames = []; + foreach ($this->class->fieldMappings as $key => $column) { + if (isset($column->generated) || ($this->class->isVersioned && $key === $versionedClass->versionField)) { + $columnNames[$key] = $this->quoteStrategy->getColumnName($key, $versionedClass, $this->platform); + } + } + + $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); + $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform); + + // FIXME: Order with composite keys might not be correct + $sql = 'SELECT ' . implode(', ', $columnNames) + . ' FROM ' . $tableName + . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; + + $flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id); + + $values = $this->conn->fetchNumeric( + $sql, + array_values($flatId), + $this->extractIdentifierTypes($id, $versionedClass), + ); + + if ($values === false) { + throw new LengthException('Unexpected empty result for database query.'); + } + + $values = array_combine(array_keys($columnNames), $values); + + if (! $values) { + throw new LengthException('Unexpected number of database columns.'); + } + + return $values; + } + + /** + * @param mixed[] $id + * + * @return list + * @psalm-return list + */ + final protected function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array + { + $types = []; + + foreach ($id as $field => $value) { + $types = [...$types, ...$this->getTypes($field, $value, $versionedClass)]; + } + + return $types; + } + + public function update(object $entity): void + { + $tableName = $this->class->getTableName(); + $updateData = $this->prepareUpdateData($entity); + + if (! isset($updateData[$tableName])) { + return; + } + + $data = $updateData[$tableName]; + + if (! $data) { + return; + } + + $isVersioned = $this->class->isVersioned; + $quotedTableName = $this->quoteStrategy->getTableName($this->class, $this->platform); + + $this->updateTable($entity, $quotedTableName, $data, $isVersioned); + + if ($this->class->requiresFetchAfterChange) { + $id = $this->class->getIdentifierValues($entity); + + $this->assignDefaultVersionAndUpsertableValues($entity, $id); + } + } + + /** + * Performs an UPDATE statement for an entity on a specific table. + * The UPDATE can optionally be versioned, which requires the entity to have a version field. + * + * @param object $entity The entity object being updated. + * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. + * @param mixed[] $updateData The map of columns to update (column => value). + * @param bool $versioned Whether the UPDATE should be versioned. + * + * @throws UnrecognizedField + * @throws OptimisticLockException + */ + final protected function updateTable( + object $entity, + string $quotedTableName, + array $updateData, + bool $versioned = false, + ): void { + $set = []; + $types = []; + $params = []; + + foreach ($updateData as $columnName => $value) { + $placeholder = '?'; + $column = $columnName; + + switch (true) { + case isset($this->class->fieldNames[$columnName]): + $fieldName = $this->class->fieldNames[$columnName]; + $column = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); + + if (isset($this->class->fieldMappings[$fieldName])) { + $type = Type::getType($this->columnTypes[$columnName]); + $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); + } + + break; + + case isset($this->quotedColumns[$columnName]): + $column = $this->quotedColumns[$columnName]; + + break; + } + + $params[] = $value; + $set[] = $column . ' = ' . $placeholder; + $types[] = $this->columnTypes[$columnName]; + } + + $where = []; + $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); + + foreach ($this->class->identifier as $idField) { + if (! isset($this->class->associationMappings[$idField])) { + $params[] = $identifier[$idField]; + $types[] = $this->class->fieldMappings[$idField]->type; + $where[] = $this->quoteStrategy->getColumnName($idField, $this->class, $this->platform); + + continue; + } + + assert($this->class->associationMappings[$idField]->isToOneOwningSide()); + + $params[] = $identifier[$idField]; + $where[] = $this->quoteStrategy->getJoinColumnName( + $this->class->associationMappings[$idField]->joinColumns[0], + $this->class, + $this->platform, + ); + + $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]->targetEntity); + $targetType = PersisterHelper::getTypeOfField($targetMapping->identifier[0], $targetMapping, $this->em); + + if ($targetType === []) { + throw UnrecognizedField::byFullyQualifiedName($this->class->name, $targetMapping->identifier[0]); + } + + $types[] = reset($targetType); + } + + if ($versioned) { + $versionField = $this->class->versionField; + assert($versionField !== null); + $versionFieldType = $this->class->fieldMappings[$versionField]->type; + $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->class, $this->platform); + + $where[] = $versionColumn; + $types[] = $this->class->fieldMappings[$versionField]->type; + $params[] = $this->class->reflFields[$versionField]->getValue($entity); + + switch ($versionFieldType) { + case Types::SMALLINT: + case Types::INTEGER: + case Types::BIGINT: + $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; + break; + + case Types::DATETIME_MUTABLE: + $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; + break; + } + } + + $sql = 'UPDATE ' . $quotedTableName + . ' SET ' . implode(', ', $set) + . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; + + $result = $this->conn->executeStatement($sql, $params, $types); + + if ($versioned && ! $result) { + throw OptimisticLockException::lockFailed($entity); + } + } + + /** + * @param array $identifier + * @param string[] $types + * + * @todo Add check for platform if it supports foreign keys/cascading. + */ + protected function deleteJoinTableRecords(array $identifier, array $types): void + { + foreach ($this->class->associationMappings as $mapping) { + if (! $mapping->isManyToMany() || $mapping->isOnDeleteCascade) { + continue; + } + + // @Todo this only covers scenarios with no inheritance or of the same level. Is there something + // like self-referential relationship between different levels of an inheritance hierarchy? I hope not! + $selfReferential = ($mapping->targetEntity === $mapping->sourceEntity); + $class = $this->class; + $association = $mapping; + $otherColumns = []; + $otherKeys = []; + $keys = []; + + if (! $mapping->isOwningSide()) { + $class = $this->em->getClassMetadata($mapping->targetEntity); + } + + $association = $this->em->getMetadataFactory()->getOwningSide($association); + $joinColumns = $mapping->isOwningSide() + ? $association->joinTable->joinColumns + : $association->joinTable->inverseJoinColumns; + + if ($selfReferential) { + $otherColumns = ! $mapping->isOwningSide() + ? $association->joinTable->joinColumns + : $association->joinTable->inverseJoinColumns; + } + + foreach ($joinColumns as $joinColumn) { + $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + } + + foreach ($otherColumns as $joinColumn) { + $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + } + + $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); + + $this->conn->delete($joinTableName, array_combine($keys, $identifier), $types); + + if ($selfReferential) { + $this->conn->delete($joinTableName, array_combine($otherKeys, $identifier), $types); + } + } + } + + public function delete(object $entity): bool + { + $class = $this->class; + $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); + $tableName = $this->quoteStrategy->getTableName($class, $this->platform); + $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); + $id = array_combine($idColumns, $identifier); + $types = $this->getClassIdentifiersTypes($class); + + $this->deleteJoinTableRecords($identifier, $types); + + return (bool) $this->conn->delete($tableName, $id, $types); + } + + /** + * Prepares the changeset of an entity for database insertion (UPDATE). + * + * The changeset is obtained from the currently running UnitOfWork. + * + * During this preparation the array that is passed as the second parameter is filled with + * => pairs, grouped by table name. + * + * Example: + * + * array( + * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), + * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), + * ... + * ) + * + * + * @param object $entity The entity for which to prepare the data. + * @param bool $isInsert Whether the data to be prepared refers to an insert statement. + * + * @return mixed[][] The prepared data. + * @psalm-return array> + */ + protected function prepareUpdateData(object $entity, bool $isInsert = false): array + { + $versionField = null; + $result = []; + $uow = $this->em->getUnitOfWork(); + + $versioned = $this->class->isVersioned; + if ($versioned !== false) { + $versionField = $this->class->versionField; + } + + foreach ($uow->getEntityChangeSet($entity) as $field => $change) { + if (isset($versionField) && $versionField === $field) { + continue; + } + + if (isset($this->class->embeddedClasses[$field])) { + continue; + } + + $newVal = $change[1]; + + if (! isset($this->class->associationMappings[$field])) { + $fieldMapping = $this->class->fieldMappings[$field]; + $columnName = $fieldMapping->columnName; + + if (! $isInsert && isset($fieldMapping->notUpdatable)) { + continue; + } + + if ($isInsert && isset($fieldMapping->notInsertable)) { + continue; + } + + $this->columnTypes[$columnName] = $fieldMapping->type; + + $result[$this->getOwningTable($field)][$columnName] = $newVal; + + continue; + } + + $assoc = $this->class->associationMappings[$field]; + + // Only owning side of x-1 associations can have a FK column. + if (! $assoc->isToOneOwningSide()) { + continue; + } + + if ($newVal !== null) { + $oid = spl_object_id($newVal); + + // If the associated entity $newVal is not yet persisted and/or does not yet have + // an ID assigned, we must set $newVal = null. This will insert a null value and + // schedule an extra update on the UnitOfWork. + // + // This gives us extra time to a) possibly obtain a database-generated identifier + // value for $newVal, and b) insert $newVal into the database before the foreign + // key reference is being made. + // + // When looking at $this->queuedInserts and $uow->isScheduledForInsert, be aware + // of the implementation details that our own executeInserts() method will remove + // entities from the former as soon as the insert statement has been executed and + // a post-insert ID has been assigned (if necessary), and that the UnitOfWork has + // already removed entities from its own list at the time they were passed to our + // addInsert() method. + // + // Then, there is one extra exception we can make: An entity that references back to itself + // _and_ uses an application-provided ID (the "NONE" generator strategy) also does not + // need the extra update, although it is still in the list of insertions itself. + // This looks like a minor optimization at first, but is the capstone for being able to + // use non-NULLable, self-referencing associations in applications that provide IDs (like UUIDs). + if ( + (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) + && ! ($newVal === $entity && $this->class->isIdentifierNatural()) + ) { + $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]); + + $newVal = null; + } + } + + $newValId = null; + + if ($newVal !== null) { + $newValId = $uow->getEntityIdentifier($newVal); + } + + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + $owningTable = $this->getOwningTable($field); + + foreach ($assoc->joinColumns as $joinColumn) { + $sourceColumn = $joinColumn->name; + $targetColumn = $joinColumn->referencedColumnName; + $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + + $this->quotedColumns[$sourceColumn] = $quotedColumn; + $this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); + $result[$owningTable][$sourceColumn] = $newValId + ? $newValId[$targetClass->getFieldForColumn($targetColumn)] + : null; + } + } + + return $result; + } + + /** + * Prepares the data changeset of a managed entity for database insertion (initial INSERT). + * The changeset of the entity is obtained from the currently running UnitOfWork. + * + * The default insert data preparation is the same as for updates. + * + * @see prepareUpdateData + * + * @param object $entity The entity for which to prepare the data. + * + * @return mixed[][] The prepared data for the tables to update. + * @psalm-return array + */ + protected function prepareInsertData(object $entity): array + { + return $this->prepareUpdateData($entity, true); + } + + public function getOwningTable(string $fieldName): string + { + return $this->class->getTableName(); + } + + /** + * {@inheritDoc} + */ + public function load( + array $criteria, + object|null $entity = null, + AssociationMapping|null $assoc = null, + array $hints = [], + LockMode|int|null $lockMode = null, + int|null $limit = null, + array|null $orderBy = null, + ): object|null { + $this->switchPersisterContext(null, $limit); + + $sql = $this->getSelectSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); + [$params, $types] = $this->expandParameters($criteria); + $stmt = $this->conn->executeQuery($sql, $params, $types); + + if ($entity !== null) { + $hints[Query::HINT_REFRESH] = true; + $hints[Query::HINT_REFRESH_ENTITY] = $entity; + } + + $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); + $entities = $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, $hints); + + return $entities ? $entities[0] : null; + } + + /** + * {@inheritDoc} + */ + public function loadById(array $identifier, object|null $entity = null): object|null + { + return $this->load($identifier, $entity); + } + + /** + * {@inheritDoc} + */ + public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null + { + $foundEntity = $this->em->getUnitOfWork()->tryGetById($identifier, $assoc->targetEntity); + if ($foundEntity !== false) { + return $foundEntity; + } + + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + + if ($assoc->isOwningSide()) { + $isInverseSingleValued = $assoc->inversedBy !== null && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy); + + // Mark inverse side as fetched in the hints, otherwise the UoW would + // try to load it in a separate query (remember: to-one inverse sides can not be lazy). + $hints = []; + + if ($isInverseSingleValued) { + $hints['fetched']['r'][$assoc->inversedBy] = true; + } + + $targetEntity = $this->load($identifier, null, $assoc, $hints); + + // Complete bidirectional association, if necessary + if ($targetEntity !== null && $isInverseSingleValued) { + $targetClass->reflFields[$assoc->inversedBy]->setValue($targetEntity, $sourceEntity); + } + + return $targetEntity; + } + + assert(isset($assoc->mappedBy)); + $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); + $owningAssoc = $targetClass->getAssociationMapping($assoc->mappedBy); + assert($owningAssoc->isOneToOneOwningSide()); + + $computedIdentifier = []; + + /** @var array|null $sourceEntityData */ + $sourceEntityData = null; + + // TRICKY: since the association is specular source and target are flipped + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { + if (! isset($sourceClass->fieldNames[$sourceKeyColumn])) { + // The likely case here is that the column is a join column + // in an association mapping. However, there is no guarantee + // at this point that a corresponding (generally identifying) + // association has been mapped in the source entity. To handle + // this case we directly reference the column-keyed data used + // to initialize the source entity before throwing an exception. + $resolvedSourceData = false; + if (! isset($sourceEntityData)) { + $sourceEntityData = $this->em->getUnitOfWork()->getOriginalEntityData($sourceEntity); + } + + if (isset($sourceEntityData[$sourceKeyColumn])) { + $dataValue = $sourceEntityData[$sourceKeyColumn]; + if ($dataValue !== null) { + $resolvedSourceData = true; + $computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] = + $dataValue; + } + } + + if (! $resolvedSourceData) { + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, + $sourceKeyColumn, + ); + } + } else { + $computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] = + $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } + } + + $targetEntity = $this->load($computedIdentifier, null, $assoc); + + if ($targetEntity !== null) { + $targetClass->setFieldValue($targetEntity, $assoc->mappedBy, $sourceEntity); + } + + return $targetEntity; + } + + /** + * {@inheritDoc} + */ + public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void + { + $sql = $this->getSelectSQL($id, null, $lockMode); + [$params, $types] = $this->expandParameters($id); + $stmt = $this->conn->executeQuery($sql, $params, $types); + + $hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT); + $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [Query::HINT_REFRESH => true]); + } + + public function count(array|Criteria $criteria = []): int + { + $sql = $this->getCountSQL($criteria); + + [$params, $types] = $criteria instanceof Criteria + ? $this->expandCriteriaParameters($criteria) + : $this->expandParameters($criteria); + + return (int) $this->conn->executeQuery($sql, $params, $types)->fetchOne(); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(Criteria $criteria): array + { + $orderBy = array_map( + static fn (Order $order): string => $order->value, + $criteria->orderings(), + ); + $limit = $criteria->getMaxResults(); + $offset = $criteria->getFirstResult(); + $query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); + + [$params, $types] = $this->expandCriteriaParameters($criteria); + + $stmt = $this->conn->executeQuery($query, $params, $types); + $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); + + return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true]); + } + + /** + * {@inheritDoc} + */ + public function expandCriteriaParameters(Criteria $criteria): array + { + $expression = $criteria->getWhereExpression(); + $sqlParams = []; + $sqlTypes = []; + + if ($expression === null) { + return [$sqlParams, $sqlTypes]; + } + + $valueVisitor = new SqlValueVisitor(); + + $valueVisitor->dispatch($expression); + + [, $types] = $valueVisitor->getParamsAndTypes(); + + foreach ($types as $type) { + [$field, $value, $operator] = $type; + + if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) { + continue; + } + + $sqlParams = [...$sqlParams, ...$this->getValues($value)]; + $sqlTypes = [...$sqlTypes, ...$this->getTypes($field, $value, $this->class)]; + } + + return [$sqlParams, $sqlTypes]; + } + + /** + * {@inheritDoc} + */ + public function loadAll( + array $criteria = [], + array|null $orderBy = null, + int|null $limit = null, + int|null $offset = null, + ): array { + $this->switchPersisterContext($offset, $limit); + + $sql = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); + [$params, $types] = $this->expandParameters($criteria); + $stmt = $this->conn->executeQuery($sql, $params, $types); + + $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); + + return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true]); + } + + /** + * {@inheritDoc} + */ + public function getManyToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array { + assert($assoc->isManyToMany()); + $this->switchPersisterContext($offset, $limit); + + $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); + + return $this->loadArrayFromResult($assoc, $stmt); + } + + /** + * Loads an array of entities from a given DBAL statement. + * + * @return mixed[] + */ + private function loadArrayFromResult(AssociationMapping $assoc, Result $stmt): array + { + $rsm = $this->currentPersisterContext->rsm; + $hints = [UnitOfWork::HINT_DEFEREAGERLOAD => true]; + + if ($assoc->isIndexed()) { + $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. + $rsm->addIndexBy('r', $assoc->indexBy()); + } + + return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); + } + + /** + * Hydrates a collection from a given DBAL statement. + * + * @return mixed[] + */ + private function loadCollectionFromStatement( + AssociationMapping $assoc, + Result $stmt, + PersistentCollection $coll, + ): array { + $rsm = $this->currentPersisterContext->rsm; + $hints = [ + UnitOfWork::HINT_DEFEREAGERLOAD => true, + 'collection' => $coll, + ]; + + if ($assoc->isIndexed()) { + $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. + $rsm->addIndexBy('r', $assoc->indexBy()); + } + + return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); + } + + /** + * {@inheritDoc} + */ + public function loadManyToManyCollection(AssociationMapping $assoc, object $sourceEntity, PersistentCollection $collection): array + { + assert($assoc->isManyToMany()); + $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); + + return $this->loadCollectionFromStatement($assoc, $stmt, $collection); + } + + /** @throws MappingException */ + private function getManyToManyStatement( + AssociationMapping&ManyToManyAssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): Result { + $this->switchPersisterContext($offset, $limit); + + $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); + $class = $sourceClass; + $association = $assoc; + $criteria = []; + $parameters = []; + + if (! $assoc->isOwningSide()) { + $class = $this->em->getClassMetadata($assoc->targetEntity); + } + + $association = $this->em->getMetadataFactory()->getOwningSide($assoc); + $joinColumns = $assoc->isOwningSide() + ? $association->joinTable->joinColumns + : $association->joinTable->inverseJoinColumns; + + $quotedJoinTable = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); + + foreach ($joinColumns as $joinColumn) { + $sourceKeyColumn = $joinColumn->referencedColumnName; + $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + + switch (true) { + case $sourceClass->containsForeignIdentifier: + $field = $sourceClass->getFieldForColumn($sourceKeyColumn); + $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); + + if (isset($sourceClass->associationMappings[$field])) { + $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); + $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]->targetEntity)->identifier[0]]; + } + + break; + + case isset($sourceClass->fieldNames[$sourceKeyColumn]): + $field = $sourceClass->fieldNames[$sourceKeyColumn]; + $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); + + break; + + default: + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, + $sourceKeyColumn, + ); + } + + $criteria[$quotedJoinTable . '.' . $quotedKeyColumn] = $value; + $parameters[] = [ + 'value' => $value, + 'field' => $field, + 'class' => $sourceClass, + ]; + } + + $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); + [$params, $types] = $this->expandToManyParameters($parameters); + + return $this->conn->executeQuery($sql, $params, $types); + } + + public function getSelectSQL( + array|Criteria $criteria, + AssociationMapping|null $assoc = null, + LockMode|int|null $lockMode = null, + int|null $limit = null, + int|null $offset = null, + array|null $orderBy = null, + ): string { + $this->switchPersisterContext($offset, $limit); + + $joinSql = ''; + $orderBySql = ''; + + if ($assoc !== null && $assoc->isManyToMany()) { + $joinSql = $this->getSelectManyToManyJoinSQL($assoc); + } + + if ($assoc !== null && $assoc->isOrdered()) { + $orderBy = $assoc->orderBy(); + } + + if ($orderBy) { + $orderBySql = $this->getOrderBySQL($orderBy, $this->getSQLTableAlias($this->class->name)); + } + + $conditionSql = $criteria instanceof Criteria + ? $this->getSelectConditionCriteriaSQL($criteria) + : $this->getSelectConditionSQL($criteria, $assoc); + + $lockSql = match ($lockMode) { + LockMode::PESSIMISTIC_READ => ' ' . $this->getReadLockSQL($this->platform), + LockMode::PESSIMISTIC_WRITE => ' ' . $this->getWriteLockSQL($this->platform), + default => '', + }; + + $columnList = $this->getSelectColumnsSQL(); + $tableAlias = $this->getSQLTableAlias($this->class->name); + $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); + + if ($filterSql !== '') { + $conditionSql = $conditionSql + ? $conditionSql . ' AND ' . $filterSql + : $filterSql; + } + + $select = 'SELECT ' . $columnList; + $from = ' FROM ' . $tableName . ' ' . $tableAlias; + $join = $this->currentPersisterContext->selectJoinSql . $joinSql; + $where = ($conditionSql ? ' WHERE ' . $conditionSql : ''); + $lock = $this->platform->appendLockHint($from, $lockMode ?? LockMode::NONE); + $query = $select + . $lock + . $join + . $where + . $orderBySql; + + return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql; + } + + public function getCountSQL(array|Criteria $criteria = []): string + { + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); + $tableAlias = $this->getSQLTableAlias($this->class->name); + + $conditionSql = $criteria instanceof Criteria + ? $this->getSelectConditionCriteriaSQL($criteria) + : $this->getSelectConditionSQL($criteria); + + $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); + + if ($filterSql !== '') { + $conditionSql = $conditionSql + ? $conditionSql . ' AND ' . $filterSql + : $filterSql; + } + + return 'SELECT COUNT(*) ' + . 'FROM ' . $tableName . ' ' . $tableAlias + . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); + } + + /** + * Gets the ORDER BY SQL snippet for ordered collections. + * + * @psalm-param array $orderBy + * + * @throws InvalidOrientation + * @throws InvalidFindByCall + * @throws UnrecognizedField + */ + final protected function getOrderBySQL(array $orderBy, string $baseTableAlias): string + { + $orderByList = []; + + foreach ($orderBy as $fieldName => $orientation) { + $orientation = strtoupper(trim($orientation)); + + if ($orientation !== 'ASC' && $orientation !== 'DESC') { + throw InvalidOrientation::fromClassNameAndField($this->class->name, $fieldName); + } + + if (isset($this->class->fieldMappings[$fieldName])) { + $tableAlias = isset($this->class->fieldMappings[$fieldName]->inherited) + ? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]->inherited) + : $baseTableAlias; + + $columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); + $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; + + continue; + } + + if (isset($this->class->associationMappings[$fieldName])) { + $association = $this->class->associationMappings[$fieldName]; + if (! $association->isOwningSide()) { + throw InvalidFindByCall::fromInverseSideUsage($this->class->name, $fieldName); + } + + assert($association->isToOneOwningSide()); + + $tableAlias = isset($association->inherited) + ? $this->getSQLTableAlias($association->inherited) + : $baseTableAlias; + + foreach ($association->joinColumns as $joinColumn) { + $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; + } + + continue; + } + + throw UnrecognizedField::byFullyQualifiedName($this->class->name, $fieldName); + } + + return ' ORDER BY ' . implode(', ', $orderByList); + } + + /** + * Gets the SQL fragment with the list of columns to select when querying for + * an entity in this persister. + * + * Subclasses should override this method to alter or change the select column + * list SQL fragment. Note that in the implementation of BasicEntityPersister + * the resulting SQL fragment is generated only once and cached in {@link selectColumnListSql}. + * Subclasses may or may not do the same. + */ + protected function getSelectColumnsSQL(): string + { + if ($this->currentPersisterContext->selectColumnListSql !== null) { + return $this->currentPersisterContext->selectColumnListSql; + } + + $columnList = []; + $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); // r for root + + // Add regular columns to select list + foreach ($this->class->fieldNames as $field) { + $columnList[] = $this->getSelectColumnSQL($field, $this->class); + } + + $this->currentPersisterContext->selectJoinSql = ''; + $eagerAliasCounter = 0; + + foreach ($this->class->associationMappings as $assocField => $assoc) { + $assocColumnSQL = $this->getSelectColumnAssociationSQL($assocField, $assoc, $this->class); + + if ($assocColumnSQL) { + $columnList[] = $assocColumnSQL; + } + + $isAssocToOneInverseSide = $assoc->isToOne() && ! $assoc->isOwningSide(); + $isAssocFromOneEager = $assoc->isToOne() && $assoc->fetch === ClassMetadata::FETCH_EAGER; + + if (! ($isAssocFromOneEager || $isAssocToOneInverseSide)) { + continue; + } + + if ($assoc->isToMany() && $this->currentPersisterContext->handlesLimits) { + continue; + } + + $eagerEntity = $this->em->getClassMetadata($assoc->targetEntity); + + if ($eagerEntity->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { + continue; // now this is why you shouldn't use inheritance + } + + $assocAlias = 'e' . ($eagerAliasCounter++); + $this->currentPersisterContext->rsm->addJoinedEntityResult($assoc->targetEntity, $assocAlias, 'r', $assocField); + + foreach ($eagerEntity->fieldNames as $field) { + $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias); + } + + foreach ($eagerEntity->associationMappings as $eagerAssocField => $eagerAssoc) { + $eagerAssocColumnSQL = $this->getSelectColumnAssociationSQL( + $eagerAssocField, + $eagerAssoc, + $eagerEntity, + $assocAlias, + ); + + if ($eagerAssocColumnSQL) { + $columnList[] = $eagerAssocColumnSQL; + } + } + + $association = $assoc; + $joinCondition = []; + + if ($assoc->isIndexed()) { + assert($assoc->isToMany()); + $this->currentPersisterContext->rsm->addIndexBy($assocAlias, $assoc->indexBy()); + } + + if (! $assoc->isOwningSide()) { + $eagerEntity = $this->em->getClassMetadata($assoc->targetEntity); + $association = $eagerEntity->getAssociationMapping($assoc->mappedBy); + } + + assert($association->isToOneOwningSide()); + + $joinTableAlias = $this->getSQLTableAlias($eagerEntity->name, $assocAlias); + $joinTableName = $this->quoteStrategy->getTableName($eagerEntity, $this->platform); + + if ($assoc->isOwningSide()) { + $tableAlias = $this->getSQLTableAlias($association->targetEntity, $assocAlias); + $this->currentPersisterContext->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association->joinColumns); + + foreach ($association->joinColumns as $joinColumn) { + $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); + $joinCondition[] = $this->getSQLTableAlias($association->sourceEntity) + . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol; + } + + // Add filter SQL + $filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias); + if ($filterSql) { + $joinCondition[] = $filterSql; + } + } else { + $this->currentPersisterContext->selectJoinSql .= ' LEFT JOIN'; + + foreach ($association->joinColumns as $joinColumn) { + $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); + + $joinCondition[] = $this->getSQLTableAlias($association->sourceEntity, $assocAlias) . '.' . $sourceCol . ' = ' + . $this->getSQLTableAlias($association->targetEntity) . '.' . $targetCol; + } + } + + $this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON '; + $this->currentPersisterContext->selectJoinSql .= implode(' AND ', $joinCondition); + } + + $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); + + return $this->currentPersisterContext->selectColumnListSql; + } + + /** Gets the SQL join fragment used when selecting entities from an association. */ + protected function getSelectColumnAssociationSQL( + string $field, + AssociationMapping $assoc, + ClassMetadata $class, + string $alias = 'r', + ): string { + if (! $assoc->isToOneOwningSide()) { + return ''; + } + + $columnList = []; + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + $isIdentifier = isset($assoc->id) && $assoc->id === true; + $sqlTableAlias = $this->getSQLTableAlias($class->name, ($alias === 'r' ? '' : $alias)); + + foreach ($assoc->joinColumns as $joinColumn) { + $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + $resultColumnName = $this->getSQLColumnAlias($joinColumn->name); + $type = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); + + $this->currentPersisterContext->rsm->addMetaResult($alias, $resultColumnName, $joinColumn->name, $isIdentifier, $type); + + $columnList[] = sprintf('%s.%s AS %s', $sqlTableAlias, $quotedColumn, $resultColumnName); + } + + return implode(', ', $columnList); + } + + /** + * Gets the SQL join fragment used when selecting entities from a + * many-to-many association. + */ + protected function getSelectManyToManyJoinSQL(AssociationMapping&ManyToManyAssociationMapping $manyToMany): string + { + $conditions = []; + $association = $manyToMany; + $sourceTableAlias = $this->getSQLTableAlias($this->class->name); + + $association = $this->em->getMetadataFactory()->getOwningSide($manyToMany); + $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); + $joinColumns = $manyToMany->isOwningSide() + ? $association->joinTable->inverseJoinColumns + : $association->joinTable->joinColumns; + + foreach ($joinColumns as $joinColumn) { + $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); + $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn; + } + + return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions); + } + + public function getInsertSQL(): string + { + if ($this->insertSql !== null) { + return $this->insertSql; + } + + $columns = $this->getInsertColumnList(); + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); + + if (empty($columns)) { + $identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform); + $this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn); + + return $this->insertSql; + } + + $values = []; + $columns = array_unique($columns); + + foreach ($columns as $column) { + $placeholder = '?'; + + if ( + isset($this->class->fieldNames[$column]) + && isset($this->columnTypes[$this->class->fieldNames[$column]]) + && isset($this->class->fieldMappings[$this->class->fieldNames[$column]]) + ) { + $type = Type::getType($this->columnTypes[$this->class->fieldNames[$column]]); + $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); + } + + $values[] = $placeholder; + } + + $columns = implode(', ', $columns); + $values = implode(', ', $values); + + $this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values); + + return $this->insertSql; + } + + /** + * Gets the list of columns to put in the INSERT SQL statement. + * + * Subclasses should override this method to alter or change the list of + * columns placed in the INSERT statements used by the persister. + * + * @psalm-return list + */ + protected function getInsertColumnList(): array + { + $columns = []; + + foreach ($this->class->reflFields as $name => $field) { + if ($this->class->isVersioned && $this->class->versionField === $name) { + continue; + } + + if (isset($this->class->embeddedClasses[$name])) { + continue; + } + + if (isset($this->class->associationMappings[$name])) { + $assoc = $this->class->associationMappings[$name]; + + if ($assoc->isToOneOwningSide()) { + foreach ($assoc->joinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + } + } + + continue; + } + + if (! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name) { + if (isset($this->class->fieldMappings[$name]->notInsertable)) { + continue; + } + + $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); + $this->columnTypes[$name] = $this->class->fieldMappings[$name]->type; + } + } + + return $columns; + } + + /** + * Gets the SQL snippet of a qualified column name for the given field name. + * + * @param ClassMetadata $class The class that declares this field. The table this class is + * mapped to must own the column for the given field. + */ + protected function getSelectColumnSQL(string $field, ClassMetadata $class, string $alias = 'r'): string + { + $root = $alias === 'r' ? '' : $alias; + $tableAlias = $this->getSQLTableAlias($class->name, $root); + $fieldMapping = $class->fieldMappings[$field]; + $sql = sprintf('%s.%s', $tableAlias, $this->quoteStrategy->getColumnName($field, $class, $this->platform)); + $columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName); + + $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field); + if (! empty($fieldMapping->enumType)) { + $this->currentPersisterContext->rsm->addEnumResult($columnAlias, $fieldMapping->enumType); + } + + $type = Type::getType($fieldMapping->type); + $sql = $type->convertToPHPValueSQL($sql, $this->platform); + + return $sql . ' AS ' . $columnAlias; + } + + /** + * Gets the SQL table alias for the given class name. + * + * @todo Reconsider. Binding table aliases to class names is not such a good idea. + */ + protected function getSQLTableAlias(string $className, string $assocName = ''): string + { + if ($assocName) { + $className .= '#' . $assocName; + } + + if (isset($this->currentPersisterContext->sqlTableAliases[$className])) { + return $this->currentPersisterContext->sqlTableAliases[$className]; + } + + $tableAlias = 't' . $this->currentPersisterContext->sqlAliasCounter++; + + $this->currentPersisterContext->sqlTableAliases[$className] = $tableAlias; + + return $tableAlias; + } + + /** + * {@inheritDoc} + */ + public function lock(array $criteria, LockMode|int $lockMode): void + { + $conditionSql = $this->getSelectConditionSQL($criteria); + + $lockSql = match ($lockMode) { + LockMode::PESSIMISTIC_READ => $this->getReadLockSQL($this->platform), + LockMode::PESSIMISTIC_WRITE => $this->getWriteLockSQL($this->platform), + default => '', + }; + + $lock = $this->getLockTablesSql($lockMode); + $where = ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' '; + $sql = 'SELECT 1 ' + . $lock + . $where + . $lockSql; + + [$params, $types] = $this->expandParameters($criteria); + + $this->conn->executeQuery($sql, $params, $types); + } + + /** + * Gets the FROM and optionally JOIN conditions to lock the entity managed by this persister. + * + * @psalm-param LockMode::* $lockMode + */ + protected function getLockTablesSql(LockMode|int $lockMode): string + { + return $this->platform->appendLockHint( + 'FROM ' + . $this->quoteStrategy->getTableName($this->class, $this->platform) . ' ' + . $this->getSQLTableAlias($this->class->name), + $lockMode, + ); + } + + /** + * Gets the Select Where Condition from a Criteria object. + */ + protected function getSelectConditionCriteriaSQL(Criteria $criteria): string + { + $expression = $criteria->getWhereExpression(); + + if ($expression === null) { + return ''; + } + + $visitor = new SqlExpressionVisitor($this, $this->class); + + return $visitor->dispatch($expression); + } + + public function getSelectConditionStatementSQL( + string $field, + mixed $value, + AssociationMapping|null $assoc = null, + string|null $comparison = null, + ): string { + $selectedColumns = []; + $columns = $this->getSelectConditionStatementColumnSQL($field, $assoc); + + if (count($columns) > 1 && $comparison === Comparison::IN) { + /* + * @todo try to support multi-column IN expressions. + * Example: (col1, col2) IN (('val1A', 'val2A'), ('val1B', 'val2B')) + */ + throw CantUseInOperatorOnCompositeKeys::create(); + } + + foreach ($columns as $column) { + $placeholder = '?'; + + if (isset($this->class->fieldMappings[$field])) { + $type = Type::getType($this->class->fieldMappings[$field]->type); + $placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->platform); + } + + if ($comparison !== null) { + // special case null value handling + if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) { + $selectedColumns[] = $column . ' IS NULL'; + + continue; + } + + if ($comparison === Comparison::NEQ && $value === null) { + $selectedColumns[] = $column . ' IS NOT NULL'; + + continue; + } + + $selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder); + + continue; + } + + if (is_array($value)) { + $in = sprintf('%s IN (%s)', $column, $placeholder); + + if (array_search(null, $value, true) !== false) { + $selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column); + + continue; + } + + $selectedColumns[] = $in; + + continue; + } + + if ($value === null) { + $selectedColumns[] = sprintf('%s IS NULL', $column); + + continue; + } + + $selectedColumns[] = sprintf('%s = %s', $column, $placeholder); + } + + return implode(' AND ', $selectedColumns); + } + + /** + * Builds the left-hand-side of a where condition statement. + * + * @return string[] + * @psalm-return list + * + * @throws InvalidFindByCall + * @throws UnrecognizedField + */ + private function getSelectConditionStatementColumnSQL( + string $field, + AssociationMapping|null $assoc = null, + ): array { + if (isset($this->class->fieldMappings[$field])) { + $className = $this->class->fieldMappings[$field]->inherited ?? $this->class->name; + + return [$this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform)]; + } + + if (isset($this->class->associationMappings[$field])) { + $association = $this->class->associationMappings[$field]; + // Many-To-Many requires join table check for joinColumn + $columns = []; + $class = $this->class; + + if ($association->isManyToMany()) { + assert($assoc !== null); + if (! $association->isOwningSide()) { + $association = $assoc; + } + + assert($association->isManyToManyOwningSide()); + + $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); + $joinColumns = $assoc->isOwningSide() + ? $association->joinTable->joinColumns + : $association->joinTable->inverseJoinColumns; + + foreach ($joinColumns as $joinColumn) { + $columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + } + } else { + if (! $association->isOwningSide()) { + throw InvalidFindByCall::fromInverseSideUsage( + $this->class->name, + $field, + ); + } + + assert($association->isToOneOwningSide()); + + $className = $association->inherited ?? $this->class->name; + + foreach ($association->joinColumns as $joinColumn) { + $columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); + } + } + + return $columns; + } + + if ($assoc !== null && ! str_contains($field, ' ') && ! str_contains($field, '(')) { + // very careless developers could potentially open up this normally hidden api for userland attacks, + // therefore checking for spaces and function calls which are not allowed. + + // found a join column condition, not really a "field" + return [$field]; + } + + throw UnrecognizedField::byFullyQualifiedName($this->class->name, $field); + } + + /** + * Gets the conditional SQL fragment used in the WHERE clause when selecting + * entities in this persister. + * + * Subclasses are supposed to override this method if they intend to change + * or alter the criteria by which entities are selected. + * + * @psalm-param array $criteria + */ + protected function getSelectConditionSQL(array $criteria, AssociationMapping|null $assoc = null): string + { + $conditions = []; + + foreach ($criteria as $field => $value) { + $conditions[] = $this->getSelectConditionStatementSQL($field, $value, $assoc); + } + + return implode(' AND ', $conditions); + } + + /** + * {@inheritDoc} + */ + public function getOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array { + assert($assoc instanceof OneToManyAssociationMapping); + $this->switchPersisterContext($offset, $limit); + + $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); + + return $this->loadArrayFromResult($assoc, $stmt); + } + + public function loadOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + PersistentCollection $collection, + ): mixed { + assert($assoc instanceof OneToManyAssociationMapping); + $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); + + return $this->loadCollectionFromStatement($assoc, $stmt, $collection); + } + + /** Builds criteria and execute SQL statement to fetch the one to many entities from. */ + private function getOneToManyStatement( + OneToManyAssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): Result { + $this->switchPersisterContext($offset, $limit); + + $criteria = []; + $parameters = []; + $owningAssoc = $this->class->associationMappings[$assoc->mappedBy]; + $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); + $tableAlias = $this->getSQLTableAlias($owningAssoc->inherited ?? $this->class->name); + assert($owningAssoc->isManyToOne()); + + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { + if ($sourceClass->containsForeignIdentifier) { + $field = $sourceClass->getFieldForColumn($sourceKeyColumn); + $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); + + if (isset($sourceClass->associationMappings[$field])) { + $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); + $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]->targetEntity)->identifier[0]]; + } + + $criteria[$tableAlias . '.' . $targetKeyColumn] = $value; + $parameters[] = [ + 'value' => $value, + 'field' => $field, + 'class' => $sourceClass, + ]; + + continue; + } + + $field = $sourceClass->fieldNames[$sourceKeyColumn]; + $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); + + $criteria[$tableAlias . '.' . $targetKeyColumn] = $value; + $parameters[] = [ + 'value' => $value, + 'field' => $field, + 'class' => $sourceClass, + ]; + } + + $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); + [$params, $types] = $this->expandToManyParameters($parameters); + + return $this->conn->executeQuery($sql, $params, $types); + } + + /** + * {@inheritDoc} + */ + public function expandParameters(array $criteria): array + { + $params = []; + $types = []; + + foreach ($criteria as $field => $value) { + if ($value === null) { + continue; // skip null values. + } + + $types = [...$types, ...$this->getTypes($field, $value, $this->class)]; + $params = array_merge($params, $this->getValues($value)); + } + + return [$params, $types]; + } + + /** + * Expands the parameters from the given criteria and use the correct binding types if found, + * specialized for OneToMany or ManyToMany associations. + * + * @param mixed[][] $criteria an array of arrays containing following: + * - field to which each criterion will be bound + * - value to be bound + * - class to which the field belongs to + * + * @return mixed[][] + * @psalm-return array{0: array, 1: list} + */ + private function expandToManyParameters(array $criteria): array + { + $params = []; + $types = []; + + foreach ($criteria as $criterion) { + if ($criterion['value'] === null) { + continue; // skip null values. + } + + $types = [...$types, ...$this->getTypes($criterion['field'], $criterion['value'], $criterion['class'])]; + $params = array_merge($params, $this->getValues($criterion['value'])); + } + + return [$params, $types]; + } + + /** + * Infers field types to be used by parameter type casting. + * + * @return list + * @psalm-return list + * + * @throws QueryException + */ + private function getTypes(string $field, mixed $value, ClassMetadata $class): array + { + $types = []; + + switch (true) { + case isset($class->fieldMappings[$field]): + $types = array_merge($types, [$class->fieldMappings[$field]->type]); + break; + + case isset($class->associationMappings[$field]): + $assoc = $this->em->getMetadataFactory()->getOwningSide($class->associationMappings[$field]); + $class = $this->em->getClassMetadata($assoc->targetEntity); + + if ($assoc->isManyToManyOwningSide()) { + $columns = $assoc->relationToTargetKeyColumns; + } else { + assert($assoc->isToOneOwningSide()); + $columns = $assoc->sourceToTargetKeyColumns; + } + + foreach ($columns as $column) { + $types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em); + } + + break; + + default: + $types[] = ParameterType::STRING; + break; + } + + if (is_array($value)) { + return array_map($this->getArrayBindingType(...), $types); + } + + return $types; + } + + /** @psalm-return ArrayParameterType::* */ + private function getArrayBindingType(ParameterType|int|string $type): ArrayParameterType|int + { + if (! $type instanceof ParameterType) { + $type = Type::getType((string) $type)->getBindingType(); + } + + return match ($type) { + ParameterType::STRING => ArrayParameterType::STRING, + ParameterType::INTEGER => ArrayParameterType::INTEGER, + ParameterType::ASCII => ArrayParameterType::ASCII, + }; + } + + /** + * Retrieves the parameters that identifies a value. + * + * @return mixed[] + */ + private function getValues(mixed $value): array + { + if (is_array($value)) { + $newValue = []; + + foreach ($value as $itemValue) { + $newValue = array_merge($newValue, $this->getValues($itemValue)); + } + + return [$newValue]; + } + + return $this->getIndividualValue($value); + } + + /** + * Retrieves an individual parameter value. + * + * @psalm-return list + */ + private function getIndividualValue(mixed $value): array + { + if (! is_object($value)) { + return [$value]; + } + + if ($value instanceof BackedEnum) { + return [$value->value]; + } + + $valueClass = DefaultProxyClassNameResolver::getClass($value); + + if ($this->em->getMetadataFactory()->isTransient($valueClass)) { + return [$value]; + } + + $class = $this->em->getClassMetadata($valueClass); + + if ($class->isIdentifierComposite) { + $newValue = []; + + foreach ($class->getIdentifierValues($value) as $innerValue) { + $newValue = array_merge($newValue, $this->getValues($innerValue)); + } + + return $newValue; + } + + return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)]; + } + + public function exists(object $entity, Criteria|null $extraConditions = null): bool + { + $criteria = $this->class->getIdentifierValues($entity); + + if (! $criteria) { + return false; + } + + $alias = $this->getSQLTableAlias($this->class->name); + + $sql = 'SELECT 1 ' + . $this->getLockTablesSql(LockMode::NONE) + . ' WHERE ' . $this->getSelectConditionSQL($criteria); + + [$params, $types] = $this->expandParameters($criteria); + + if ($extraConditions !== null) { + $sql .= ' AND ' . $this->getSelectConditionCriteriaSQL($extraConditions); + [$criteriaParams, $criteriaTypes] = $this->expandCriteriaParameters($extraConditions); + + $params = [...$params, ...$criteriaParams]; + $types = [...$types, ...$criteriaTypes]; + } + + $filterSql = $this->generateFilterConditionSQL($this->class, $alias); + if ($filterSql) { + $sql .= ' AND ' . $filterSql; + } + + return (bool) $this->conn->fetchOne($sql, $params, $types); + } + + /** + * Generates the appropriate join SQL for the given join column. + * + * @param list $joinColumns The join columns definition of an association. + * + * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. + */ + protected function getJoinSQLForJoinColumns(array $joinColumns): string + { + // if one of the join columns is nullable, return left join + foreach ($joinColumns as $joinColumn) { + if (! isset($joinColumn->nullable) || $joinColumn->nullable) { + return 'LEFT JOIN'; + } + } + + return 'INNER JOIN'; + } + + public function getSQLColumnAlias(string $columnName): string + { + return $this->quoteStrategy->getColumnAlias($columnName, $this->currentPersisterContext->sqlAliasCounter++, $this->platform); + } + + /** + * Generates the filter SQL for a given entity and table alias. + * + * @param ClassMetadata $targetEntity Metadata of the target entity. + * @param string $targetTableAlias The table alias of the joined/selected table. + * + * @return string The SQL query part to add to a query. + */ + protected function generateFilterConditionSQL(ClassMetadata $targetEntity, string $targetTableAlias): string + { + $filterClauses = []; + + foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { + $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); + if ($filterExpr !== '') { + $filterClauses[] = '(' . $filterExpr . ')'; + } + } + + $sql = implode(' AND ', $filterClauses); + + return $sql ? '(' . $sql . ')' : ''; // Wrap again to avoid "X or Y and FilterConditionSQL" + } + + /** + * Switches persister context according to current query offset/limits + * + * This is due to the fact that to-many associations cannot be fetch-joined when a limit is involved + */ + protected function switchPersisterContext(int|null $offset, int|null $limit): void + { + if ($offset === null && $limit === null) { + $this->currentPersisterContext = $this->noLimitsContext; + + return; + } + + $this->currentPersisterContext = $this->limitsHandlingContext; + } + + /** + * @return string[] + * @psalm-return list + */ + protected function getClassIdentifiersTypes(ClassMetadata $class): array + { + $entityManager = $this->em; + + return array_map( + static function ($fieldName) use ($class, $entityManager): string { + $types = PersisterHelper::getTypeOfField($fieldName, $class, $entityManager); + assert(isset($types[0])); + + return $types[0]; + }, + $class->identifier, + ); + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/CachedPersisterContext.php b/vendor/doctrine/orm/src/Persisters/Entity/CachedPersisterContext.php new file mode 100644 index 0000000..03d053b --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Entity/CachedPersisterContext.php @@ -0,0 +1,60 @@ + + */ + public array $sqlTableAliases = []; + + public function __construct( + /** + * Metadata object that describes the mapping of the mapped entity class. + */ + public ClassMetadata $class, + /** + * ResultSetMapping that is used for all queries. Is generated lazily once per request. + */ + public ResultSetMapping $rsm, + /** + * Whether this persistent context is considering limit operations applied to the selection queries + */ + public bool $handlesLimits, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/EntityPersister.php b/vendor/doctrine/orm/src/Persisters/Entity/EntityPersister.php new file mode 100644 index 0000000..6b278a7 --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Entity/EntityPersister.php @@ -0,0 +1,298 @@ +, list} + */ + public function expandParameters(array $criteria): array; + + /** + * Expands Criteria Parameters by walking the expressions and grabbing all parameters and types from it. + * + * @psalm-return array{list, list} + */ + public function expandCriteriaParameters(Criteria $criteria): array; + + /** Gets the SQL WHERE condition for matching a field with a given value. */ + public function getSelectConditionStatementSQL( + string $field, + mixed $value, + AssociationMapping|null $assoc = null, + string|null $comparison = null, + ): string; + + /** + * Adds an entity to the queued insertions. + * The entity remains queued until {@link executeInserts} is invoked. + */ + public function addInsert(object $entity): void; + + /** + * Executes all queued entity insertions. + * + * If no inserts are queued, invoking this method is a NOOP. + */ + public function executeInserts(): void; + + /** + * Updates a managed entity. The entity is updated according to its current changeset + * in the running UnitOfWork. If there is no changeset, nothing is updated. + */ + public function update(object $entity): void; + + /** + * Deletes a managed entity. + * + * The entity to delete must be managed and have a persistent identifier. + * The deletion happens instantaneously. + * + * Subclasses may override this method to customize the semantics of entity deletion. + * + * @return bool TRUE if the entity got deleted in the database, FALSE otherwise. + */ + public function delete(object $entity): bool; + + /** + * Count entities (optionally filtered by a criteria) + * + * @param mixed[]|Criteria $criteria + */ + public function count(array|Criteria $criteria = []): int; + + /** + * Gets the name of the table that owns the column the given field is mapped to. + * + * The default implementation in BasicEntityPersister always returns the name + * of the table the entity type of this persister is mapped to, since an entity + * is always persisted to a single table with a BasicEntityPersister. + */ + public function getOwningTable(string $fieldName): string; + + /** + * Loads an entity by a list of field criteria. + * + * @param mixed[] $criteria The criteria by which to load the entity. + * @param object|null $entity The entity to load the data into. If not specified, + * a new entity is created. + * @param AssociationMapping|null $assoc The association that connects the entity + * to load to another entity, if any. + * @param mixed[] $hints Hints for entity creation. + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * for loading the entity. + * @param int|null $limit Limit number of results. + * @param string[]|null $orderBy Criteria to order by. + * @psalm-param array $criteria + * @psalm-param array $hints + * @psalm-param LockMode::*|null $lockMode + * @psalm-param array|null $orderBy + * + * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. + * + * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? + */ + public function load( + array $criteria, + object|null $entity = null, + AssociationMapping|null $assoc = null, + array $hints = [], + LockMode|int|null $lockMode = null, + int|null $limit = null, + array|null $orderBy = null, + ): object|null; + + /** + * Loads an entity by identifier. + * + * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. + * @psalm-param array $identifier The entity identifier. + * + * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. + * + * @todo Check parameters + */ + public function loadById(array $identifier, object|null $entity = null): object|null; + + /** + * Loads an entity of this persister's mapped class as part of a single-valued + * association from another entity. + * + * @param AssociationMapping $assoc The association to load. + * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). + * @psalm-param array $identifier The identifier of the entity to load. Must be provided if + * the association to load represents the owning side, otherwise + * the identifier is derived from the $sourceEntity. + * + * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. + * + * @throws MappingException + */ + public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null; + + /** + * Refreshes a managed entity. + * + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * for refreshing the managed entity. + * @psalm-param array $id The identifier of the entity as an + * associative array from column or + * field names to values. + * @psalm-param LockMode::*|null $lockMode + */ + public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void; + + /** + * Loads Entities matching the given Criteria object. + * + * @return mixed[] + */ + public function loadCriteria(Criteria $criteria): array; + + /** + * Loads a list of entities by a list of field criteria. + * + * @psalm-param array|null $orderBy + * @psalm-param array $criteria + * + * @return mixed[] + */ + public function loadAll( + array $criteria = [], + array|null $orderBy = null, + int|null $limit = null, + int|null $offset = null, + ): array; + + /** + * Gets (sliced or full) elements of the given collection. + * + * @return mixed[] + */ + public function getManyToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array; + + /** + * Loads a collection of entities of a many-to-many association. + * + * @param AssociationMapping $assoc The association mapping of the association being loaded. + * @param object $sourceEntity The entity that owns the collection. + * @param PersistentCollection $collection The collection to fill. + * + * @return mixed[] + */ + public function loadManyToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + PersistentCollection $collection, + ): array; + + /** + * Loads a collection of entities in a one-to-many association. + * + * @param PersistentCollection $collection The collection to load/fill. + */ + public function loadOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + PersistentCollection $collection, + ): mixed; + + /** + * Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode. + * + * @psalm-param array $criteria + * @psalm-param LockMode::* $lockMode + */ + public function lock(array $criteria, LockMode|int $lockMode): void; + + /** + * Returns an array with (sliced or full list) of elements in the specified collection. + * + * @return mixed[] + */ + public function getOneToManyCollection( + AssociationMapping $assoc, + object $sourceEntity, + int|null $offset = null, + int|null $limit = null, + ): array; + + /** + * Checks whether the given managed entity exists in the database. + */ + public function exists(object $entity, Criteria|null $extraConditions = null): bool; +} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/JoinedSubclassPersister.php b/vendor/doctrine/orm/src/Persisters/Entity/JoinedSubclassPersister.php new file mode 100644 index 0000000..76719a2 --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Entity/JoinedSubclassPersister.php @@ -0,0 +1,601 @@ +Class Table Inheritance strategy. + * + * @see https://martinfowler.com/eaaCatalog/classTableInheritance.html + */ +class JoinedSubclassPersister extends AbstractEntityInheritancePersister +{ + use LockSqlHelper; + use SQLResultCasing; + + /** + * Map that maps column names to the table names that own them. + * This is mainly a temporary cache, used during a single request. + * + * @psalm-var array + */ + private array $owningTableMap = []; + + /** + * Map of table to quoted table names. + * + * @psalm-var array + */ + private array $quotedTableMap = []; + + protected function getDiscriminatorColumnTableName(): string + { + $class = $this->class->name !== $this->class->rootEntityName + ? $this->em->getClassMetadata($this->class->rootEntityName) + : $this->class; + + return $class->getTableName(); + } + + /** + * This function finds the ClassMetadata instance in an inheritance hierarchy + * that is responsible for enabling versioning. + */ + private function getVersionedClassMetadata(): ClassMetadata + { + if (isset($this->class->fieldMappings[$this->class->versionField]->inherited)) { + $definingClassName = $this->class->fieldMappings[$this->class->versionField]->inherited; + + return $this->em->getClassMetadata($definingClassName); + } + + return $this->class; + } + + /** + * Gets the name of the table that owns the column the given field is mapped to. + */ + public function getOwningTable(string $fieldName): string + { + if (isset($this->owningTableMap[$fieldName])) { + return $this->owningTableMap[$fieldName]; + } + + $cm = match (true) { + isset($this->class->associationMappings[$fieldName]->inherited) + => $this->em->getClassMetadata($this->class->associationMappings[$fieldName]->inherited), + isset($this->class->fieldMappings[$fieldName]->inherited) + => $this->em->getClassMetadata($this->class->fieldMappings[$fieldName]->inherited), + default => $this->class, + }; + + $tableName = $cm->getTableName(); + $quotedTableName = $this->quoteStrategy->getTableName($cm, $this->platform); + + $this->owningTableMap[$fieldName] = $tableName; + $this->quotedTableMap[$tableName] = $quotedTableName; + + return $tableName; + } + + public function executeInserts(): void + { + if (! $this->queuedInserts) { + return; + } + + $uow = $this->em->getUnitOfWork(); + $idGenerator = $this->class->idGenerator; + $isPostInsertId = $idGenerator->isPostInsertGenerator(); + $rootClass = $this->class->name !== $this->class->rootEntityName + ? $this->em->getClassMetadata($this->class->rootEntityName) + : $this->class; + + // Prepare statement for the root table + $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->name); + $rootTableName = $rootClass->getTableName(); + $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL()); + + // Prepare statements for sub tables. + $subTableStmts = []; + + if ($rootClass !== $this->class) { + $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL()); + } + + foreach ($this->class->parentClasses as $parentClassName) { + $parentClass = $this->em->getClassMetadata($parentClassName); + $parentTableName = $parentClass->getTableName(); + + if ($parentClass !== $rootClass) { + $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClassName); + $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL()); + } + } + + // Execute all inserts. For each entity: + // 1) Insert on root table + // 2) Insert on sub tables + foreach ($this->queuedInserts as $entity) { + $insertData = $this->prepareInsertData($entity); + + // Execute insert on root table + $paramIndex = 1; + + foreach ($insertData[$rootTableName] as $columnName => $value) { + $rootTableStmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); + } + + $rootTableStmt->executeStatement(); + + if ($isPostInsertId) { + $generatedId = $idGenerator->generateId($this->em, $entity); + $id = [$this->class->identifier[0] => $generatedId]; + + $uow->assignPostInsertId($entity, $generatedId); + } else { + $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity); + } + + // Execute inserts on subtables. + // The order doesn't matter because all child tables link to the root table via FK. + foreach ($subTableStmts as $tableName => $stmt) { + $paramIndex = 1; + $data = $insertData[$tableName] ?? []; + + foreach ($id as $idName => $idVal) { + $type = $this->columnTypes[$idName] ?? Types::STRING; + + $stmt->bindValue($paramIndex++, $idVal, $type); + } + + foreach ($data as $columnName => $value) { + if (! isset($id[$columnName])) { + $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); + } + } + + $stmt->executeStatement(); + } + + if ($this->class->requiresFetchAfterChange) { + $this->assignDefaultVersionAndUpsertableValues($entity, $id); + } + } + + $this->queuedInserts = []; + } + + public function update(object $entity): void + { + $updateData = $this->prepareUpdateData($entity); + + if (! $updateData) { + return; + } + + $isVersioned = $this->class->isVersioned; + + $versionedClass = $this->getVersionedClassMetadata(); + $versionedTable = $versionedClass->getTableName(); + + foreach ($updateData as $tableName => $data) { + $tableName = $this->quotedTableMap[$tableName]; + $versioned = $isVersioned && $versionedTable === $tableName; + + $this->updateTable($entity, $tableName, $data, $versioned); + } + + if ($this->class->requiresFetchAfterChange) { + // Make sure the table with the version column is updated even if no columns on that + // table were affected. + if ($isVersioned && ! isset($updateData[$versionedTable])) { + $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); + + $this->updateTable($entity, $tableName, [], true); + } + + $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity); + + $this->assignDefaultVersionAndUpsertableValues($entity, $identifiers); + } + } + + public function delete(object $entity): bool + { + $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); + $id = array_combine($this->class->getIdentifierColumnNames(), $identifier); + $types = $this->getClassIdentifiersTypes($this->class); + + $this->deleteJoinTableRecords($identifier, $types); + + // Delete the row from the root table. Cascades do the rest. + $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); + $rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform); + $rootTypes = $this->getClassIdentifiersTypes($rootClass); + + return (bool) $this->conn->delete($rootTable, $id, $rootTypes); + } + + public function getSelectSQL( + array|Criteria $criteria, + AssociationMapping|null $assoc = null, + LockMode|int|null $lockMode = null, + int|null $limit = null, + int|null $offset = null, + array|null $orderBy = null, + ): string { + $this->switchPersisterContext($offset, $limit); + + $baseTableAlias = $this->getSQLTableAlias($this->class->name); + $joinSql = $this->getJoinSql($baseTableAlias); + + if ($assoc !== null && $assoc->isManyToMany()) { + $joinSql .= $this->getSelectManyToManyJoinSQL($assoc); + } + + $conditionSql = $criteria instanceof Criteria + ? $this->getSelectConditionCriteriaSQL($criteria) + : $this->getSelectConditionSQL($criteria, $assoc); + + $filterSql = $this->generateFilterConditionSQL( + $this->em->getClassMetadata($this->class->rootEntityName), + $this->getSQLTableAlias($this->class->rootEntityName), + ); + // If the current class in the root entity, add the filters + if ($filterSql) { + $conditionSql .= $conditionSql + ? ' AND ' . $filterSql + : $filterSql; + } + + $orderBySql = ''; + + if ($assoc !== null && $assoc->isOrdered()) { + $orderBy = $assoc->orderBy(); + } + + if ($orderBy) { + $orderBySql = $this->getOrderBySQL($orderBy, $baseTableAlias); + } + + $lockSql = ''; + + switch ($lockMode) { + case LockMode::PESSIMISTIC_READ: + $lockSql = ' ' . $this->getReadLockSQL($this->platform); + + break; + + case LockMode::PESSIMISTIC_WRITE: + $lockSql = ' ' . $this->getWriteLockSQL($this->platform); + + break; + } + + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); + $from = ' FROM ' . $tableName . ' ' . $baseTableAlias; + $where = $conditionSql !== '' ? ' WHERE ' . $conditionSql : ''; + $lock = $this->platform->appendLockHint($from, $lockMode ?? LockMode::NONE); + $columnList = $this->getSelectColumnsSQL(); + $query = 'SELECT ' . $columnList + . $lock + . $joinSql + . $where + . $orderBySql; + + return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql; + } + + public function getCountSQL(array|Criteria $criteria = []): string + { + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); + $baseTableAlias = $this->getSQLTableAlias($this->class->name); + $joinSql = $this->getJoinSql($baseTableAlias); + + $conditionSql = $criteria instanceof Criteria + ? $this->getSelectConditionCriteriaSQL($criteria) + : $this->getSelectConditionSQL($criteria); + + $filterSql = $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName)); + + if ($filterSql !== '') { + $conditionSql = $conditionSql + ? $conditionSql . ' AND ' . $filterSql + : $filterSql; + } + + return 'SELECT COUNT(*) ' + . 'FROM ' . $tableName . ' ' . $baseTableAlias + . $joinSql + . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); + } + + protected function getLockTablesSql(LockMode|int $lockMode): string + { + $joinSql = ''; + $identifierColumns = $this->class->getIdentifierColumnNames(); + $baseTableAlias = $this->getSQLTableAlias($this->class->name); + + // INNER JOIN parent tables + foreach ($this->class->parentClasses as $parentClassName) { + $conditions = []; + $tableAlias = $this->getSQLTableAlias($parentClassName); + $parentClass = $this->em->getClassMetadata($parentClassName); + $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; + + foreach ($identifierColumns as $idColumn) { + $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + + $joinSql .= implode(' AND ', $conditions); + } + + return parent::getLockTablesSql($lockMode) . $joinSql; + } + + /** + * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly. + */ + protected function getSelectColumnsSQL(): string + { + // Create the column list fragment only once + if ($this->currentPersisterContext->selectColumnListSql !== null) { + return $this->currentPersisterContext->selectColumnListSql; + } + + $columnList = []; + $discrColumn = $this->class->getDiscriminatorColumn(); + $discrColumnName = $discrColumn->name; + $discrColumnType = $discrColumn->type; + $baseTableAlias = $this->getSQLTableAlias($this->class->name); + $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); + + $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); + $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); + $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); + + // Add regular columns + foreach ($this->class->fieldMappings as $fieldName => $mapping) { + $class = isset($mapping->inherited) + ? $this->em->getClassMetadata($mapping->inherited) + : $this->class; + + $columnList[] = $this->getSelectColumnSQL($fieldName, $class); + } + + // Add foreign key columns + foreach ($this->class->associationMappings as $mapping) { + if (! $mapping->isToOneOwningSide()) { + continue; + } + + $tableAlias = isset($mapping->inherited) + ? $this->getSQLTableAlias($mapping->inherited) + : $baseTableAlias; + + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + + foreach ($mapping->joinColumns as $joinColumn) { + $columnList[] = $this->getSelectJoinColumnSQL( + $tableAlias, + $joinColumn->name, + $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform), + PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em), + ); + } + } + + // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult). + $tableAlias = $this->class->rootEntityName === $this->class->name + ? $baseTableAlias + : $this->getSQLTableAlias($this->class->rootEntityName); + + $columnList[] = $tableAlias . '.' . $discrColumnName; + + // sub tables + foreach ($this->class->subClasses as $subClassName) { + $subClass = $this->em->getClassMetadata($subClassName); + $tableAlias = $this->getSQLTableAlias($subClassName); + + // Add subclass columns + foreach ($subClass->fieldMappings as $fieldName => $mapping) { + if (isset($mapping->inherited)) { + continue; + } + + $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); + } + + // Add join columns (foreign keys) + foreach ($subClass->associationMappings as $mapping) { + if (! $mapping->isToOneOwningSide() || isset($mapping->inherited)) { + continue; + } + + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + + foreach ($mapping->joinColumns as $joinColumn) { + $columnList[] = $this->getSelectJoinColumnSQL( + $tableAlias, + $joinColumn->name, + $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform), + PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em), + ); + } + } + } + + $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); + + return $this->currentPersisterContext->selectColumnListSql; + } + + /** + * {@inheritDoc} + */ + protected function getInsertColumnList(): array + { + // Identifier columns must always come first in the column list of subclasses. + $columns = $this->class->parentClasses + ? $this->class->getIdentifierColumnNames() + : []; + + foreach ($this->class->reflFields as $name => $field) { + if ( + isset($this->class->fieldMappings[$name]->inherited) + && ! isset($this->class->fieldMappings[$name]->id) + || isset($this->class->associationMappings[$name]->inherited) + || ($this->class->isVersioned && $this->class->versionField === $name) + || isset($this->class->embeddedClasses[$name]) + || isset($this->class->fieldMappings[$name]->notInsertable) + ) { + continue; + } + + if (isset($this->class->associationMappings[$name])) { + $assoc = $this->class->associationMappings[$name]; + if ($assoc->isToOneOwningSide()) { + foreach ($assoc->targetToSourceKeyColumns as $sourceCol) { + $columns[] = $sourceCol; + } + } + } elseif ( + $this->class->name !== $this->class->rootEntityName || + ! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name + ) { + $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); + $this->columnTypes[$name] = $this->class->fieldMappings[$name]->type; + } + } + + // Add discriminator column if it is the topmost class. + if ($this->class->name === $this->class->rootEntityName) { + $columns[] = $this->class->getDiscriminatorColumn()->name; + } + + return $columns; + } + + /** + * {@inheritDoc} + */ + protected function assignDefaultVersionAndUpsertableValues(object $entity, array $id): void + { + $values = $this->fetchVersionAndNotUpsertableValues($this->getVersionedClassMetadata(), $id); + + foreach ($values as $field => $value) { + $value = Type::getType($this->class->fieldMappings[$field]->type)->convertToPHPValue($value, $this->platform); + + $this->class->setFieldValue($entity, $field, $value); + } + } + + /** + * {@inheritDoc} + */ + protected function fetchVersionAndNotUpsertableValues(ClassMetadata $versionedClass, array $id): mixed + { + $columnNames = []; + foreach ($this->class->fieldMappings as $key => $column) { + $class = null; + if ($this->class->isVersioned && $key === $versionedClass->versionField) { + $class = $versionedClass; + } elseif (isset($column->generated)) { + $class = isset($column->inherited) + ? $this->em->getClassMetadata($column->inherited) + : $this->class; + } else { + continue; + } + + $columnNames[$key] = $this->getSelectColumnSQL($key, $class); + } + + $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); + $baseTableAlias = $this->getSQLTableAlias($this->class->name); + $joinSql = $this->getJoinSql($baseTableAlias); + $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform); + foreach ($identifier as $i => $idValue) { + $identifier[$i] = $baseTableAlias . '.' . $idValue; + } + + $sql = 'SELECT ' . implode(', ', $columnNames) + . ' FROM ' . $tableName . ' ' . $baseTableAlias + . $joinSql + . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; + + $flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id); + $values = $this->conn->fetchNumeric( + $sql, + array_values($flatId), + $this->extractIdentifierTypes($id, $versionedClass), + ); + + if ($values === false) { + throw new LengthException('Unexpected empty result for database query.'); + } + + $values = array_combine(array_keys($columnNames), $values); + + if (! $values) { + throw new LengthException('Unexpected number of database columns.'); + } + + return $values; + } + + private function getJoinSql(string $baseTableAlias): string + { + $joinSql = ''; + $identifierColumn = $this->class->getIdentifierColumnNames(); + + // INNER JOIN parent tables + foreach ($this->class->parentClasses as $parentClassName) { + $conditions = []; + $parentClass = $this->em->getClassMetadata($parentClassName); + $tableAlias = $this->getSQLTableAlias($parentClassName); + $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; + + foreach ($identifierColumn as $idColumn) { + $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + + $joinSql .= implode(' AND ', $conditions); + } + + // OUTER JOIN sub tables + foreach ($this->class->subClasses as $subClassName) { + $conditions = []; + $subClass = $this->em->getClassMetadata($subClassName); + $tableAlias = $this->getSQLTableAlias($subClassName); + $joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; + + foreach ($identifierColumn as $idColumn) { + $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + + $joinSql .= implode(' AND ', $conditions); + } + + return $joinSql; + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/SingleTablePersister.php b/vendor/doctrine/orm/src/Persisters/Entity/SingleTablePersister.php new file mode 100644 index 0000000..4a4d999 --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Entity/SingleTablePersister.php @@ -0,0 +1,166 @@ +class->getTableName(); + } + + protected function getSelectColumnsSQL(): string + { + $columnList = []; + if ($this->currentPersisterContext->selectColumnListSql !== null) { + return $this->currentPersisterContext->selectColumnListSql; + } + + $columnList[] = parent::getSelectColumnsSQL(); + + $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); + $tableAlias = $this->getSQLTableAlias($rootClass->name); + + // Append discriminator column + $discrColumn = $this->class->getDiscriminatorColumn(); + $discrColumnName = $discrColumn->name; + $discrColumnType = $discrColumn->type; + + $columnList[] = $tableAlias . '.' . $discrColumnName; + + $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); + + $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); + $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); + + // Append subclass columns + foreach ($this->class->subClasses as $subClassName) { + $subClass = $this->em->getClassMetadata($subClassName); + + // Regular columns + foreach ($subClass->fieldMappings as $fieldName => $mapping) { + if (isset($mapping->inherited)) { + continue; + } + + $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); + } + + // Foreign key columns + foreach ($subClass->associationMappings as $assoc) { + if (! $assoc->isToOneOwningSide() || isset($assoc->inherited)) { + continue; + } + + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + + foreach ($assoc->joinColumns as $joinColumn) { + $columnList[] = $this->getSelectJoinColumnSQL( + $tableAlias, + $joinColumn->name, + $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform), + PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em), + ); + } + } + } + + $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); + + return $this->currentPersisterContext->selectColumnListSql; + } + + /** + * {@inheritDoc} + */ + protected function getInsertColumnList(): array + { + $columns = parent::getInsertColumnList(); + + // Add discriminator column to the INSERT SQL + $columns[] = $this->class->getDiscriminatorColumn()->name; + + return $columns; + } + + protected function getSQLTableAlias(string $className, string $assocName = ''): string + { + return parent::getSQLTableAlias($this->class->rootEntityName, $assocName); + } + + /** + * {@inheritDoc} + */ + protected function getSelectConditionSQL(array $criteria, AssociationMapping|null $assoc = null): string + { + $conditionSql = parent::getSelectConditionSQL($criteria, $assoc); + + if ($conditionSql) { + $conditionSql .= ' AND '; + } + + return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); + } + + protected function getSelectConditionCriteriaSQL(Criteria $criteria): string + { + $conditionSql = parent::getSelectConditionCriteriaSQL($criteria); + + if ($conditionSql) { + $conditionSql .= ' AND '; + } + + return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); + } + + protected function getSelectConditionDiscriminatorValueSQL(): string + { + $values = array_map($this->conn->quote(...), array_map( + strval(...), + array_flip(array_intersect($this->class->discriminatorMap, $this->class->subClasses)), + )); + + if ($this->class->discriminatorValue !== null) { // discriminators can be 0 + array_unshift($values, $this->conn->quote((string) $this->class->discriminatorValue)); + } + + $discColumnName = $this->class->getDiscriminatorColumn()->name; + + $values = implode(', ', $values); + $tableAlias = $this->getSQLTableAlias($this->class->name); + + return $tableAlias . '.' . $discColumnName . ' IN (' . $values . ')'; + } + + protected function generateFilterConditionSQL(ClassMetadata $targetEntity, string $targetTableAlias): string + { + // Ensure that the filters are applied to the root entity of the inheritance tree + $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); + // we don't care about the $targetTableAlias, in a STI there is only one table. + + return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias); + } +} diff --git a/vendor/doctrine/orm/src/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php b/vendor/doctrine/orm/src/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php new file mode 100644 index 0000000..5c91312 --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php @@ -0,0 +1,15 @@ +getField(); + $value = $comparison->getValue()->getValue(); // shortcut for walkValue() + + if ( + isset($this->classMetadata->associationMappings[$field]) && + $value !== null && + ! is_object($value) && + ! in_array($comparison->getOperator(), [Comparison::IN, Comparison::NIN], true) + ) { + throw MatchingAssociationFieldRequiresObject::fromClassAndAssociation( + $this->classMetadata->name, + $field, + ); + } + + return $this->persister->getSelectConditionStatementSQL($field, $value, null, $comparison->getOperator()); + } + + /** + * Converts a composite expression into the target query language output. + * + * @throws RuntimeException + */ + public function walkCompositeExpression(CompositeExpression $expr): string + { + $expressionList = []; + + foreach ($expr->getExpressionList() as $child) { + $expressionList[] = $this->dispatch($child); + } + + return match ($expr->getType()) { + CompositeExpression::TYPE_AND => '(' . implode(' AND ', $expressionList) . ')', + CompositeExpression::TYPE_OR => '(' . implode(' OR ', $expressionList) . ')', + CompositeExpression::TYPE_NOT => 'NOT (' . $expressionList[0] . ')', + default => throw new RuntimeException('Unknown composite ' . $expr->getType()), + }; + } + + /** + * Converts a value expression into the target query language part. + */ + public function walkValue(Value $value): string + { + return '?'; + } +} diff --git a/vendor/doctrine/orm/src/Persisters/SqlValueVisitor.php b/vendor/doctrine/orm/src/Persisters/SqlValueVisitor.php new file mode 100644 index 0000000..7f987ad --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/SqlValueVisitor.php @@ -0,0 +1,88 @@ +getValueFromComparison($comparison); + + $this->values[] = $value; + $this->types[] = [$comparison->getField(), $value, $comparison->getOperator()]; + + return null; + } + + /** + * Converts a composite expression into the target query language output. + * + * {@inheritDoc} + */ + public function walkCompositeExpression(CompositeExpression $expr) + { + foreach ($expr->getExpressionList() as $child) { + $this->dispatch($child); + } + + return null; + } + + /** + * Converts a value expression into the target query language part. + * + * {@inheritDoc} + */ + public function walkValue(Value $value) + { + return null; + } + + /** + * Returns the Parameters and Types necessary for matching the last visited expression. + * + * @return mixed[][] + * @psalm-return array{0: array, 1: array>} + */ + public function getParamsAndTypes(): array + { + return [$this->values, $this->types]; + } + + /** + * Returns the value from a Comparison. In case of a CONTAINS comparison, + * the value is wrapped in %-signs, because it will be used in a LIKE clause. + */ + protected function getValueFromComparison(Comparison $comparison): mixed + { + $value = $comparison->getValue()->getValue(); + + return match ($comparison->getOperator()) { + Comparison::CONTAINS => '%' . $value . '%', + Comparison::STARTS_WITH => $value . '%', + Comparison::ENDS_WITH => '%' . $value, + default => $value, + }; + } +} diff --git a/vendor/doctrine/orm/src/PessimisticLockException.php b/vendor/doctrine/orm/src/PessimisticLockException.php new file mode 100644 index 0000000..c71560f --- /dev/null +++ b/vendor/doctrine/orm/src/PessimisticLockException.php @@ -0,0 +1,16 @@ +resolveClassName($object::class); + } +} diff --git a/vendor/doctrine/orm/src/Proxy/InternalProxy.php b/vendor/doctrine/orm/src/Proxy/InternalProxy.php new file mode 100644 index 0000000..7c1d833 --- /dev/null +++ b/vendor/doctrine/orm/src/Proxy/InternalProxy.php @@ -0,0 +1,18 @@ + + */ +interface InternalProxy extends Proxy +{ + public function __setInitialized(bool $initialized): void; +} diff --git a/vendor/doctrine/orm/src/Proxy/NotAProxyClass.php b/vendor/doctrine/orm/src/Proxy/NotAProxyClass.php new file mode 100644 index 0000000..689cc3e --- /dev/null +++ b/vendor/doctrine/orm/src/Proxy/NotAProxyClass.php @@ -0,0 +1,22 @@ +; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class extends \ implements \ +{ + + + public function __isInitialized(): bool + { + return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); + } + + public function __serialize(): array + { + + } +} + +EOPHP; + + /** The UnitOfWork this factory uses to retrieve persisters */ + private readonly UnitOfWork $uow; + + /** @var self::AUTOGENERATE_* */ + private $autoGenerate; + + /** The IdentifierFlattener used for manipulating identifiers */ + private readonly IdentifierFlattener $identifierFlattener; + + /** @var array */ + private array $proxyFactories = []; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param EntityManagerInterface $em The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param bool|self::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes. + */ + public function __construct( + private readonly EntityManagerInterface $em, + private readonly string $proxyDir, + private readonly string $proxyNs, + bool|int $autoGenerate = self::AUTOGENERATE_NEVER, + ) { + if (! $proxyDir) { + throw ORMInvalidArgumentException::proxyDirectoryRequired(); + } + + if (! $proxyNs) { + throw ORMInvalidArgumentException::proxyNamespaceRequired(); + } + + if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) { + throw ORMInvalidArgumentException::invalidAutoGenerateMode($autoGenerate); + } + + $this->uow = $em->getUnitOfWork(); + $this->autoGenerate = (int) $autoGenerate; + $this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory()); + } + + /** + * @param class-string $className + * @param array $identifier + */ + public function getProxy(string $className, array $identifier): InternalProxy + { + $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); + + return $proxyFactory($identifier); + } + + /** + * Generates proxy classes for all given classes. + * + * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. + * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the EntityManager used + * by this factory is used. + * + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, string|null $proxyDir = null): int + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->proxyDir); + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); + + $this->generateProxyClass($class, $proxyFileName, $proxyClassName); + + ++$generated; + } + + return $generated; + } + + protected function skipClass(ClassMetadata $metadata): bool + { + return $metadata->isMappedSuperclass + || $metadata->isEmbeddedClass + || $metadata->getReflectionClass()->isAbstract(); + } + + /** + * Creates a closure capable of initializing a proxy + * + * @return Closure(InternalProxy, array):void + * + * @throws EntityNotFoundException + */ + private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure + { + return static function (InternalProxy $proxy, array $identifier) use ($entityPersister, $classMetadata, $identifierFlattener): void { + $original = $entityPersister->loadById($identifier); + + if ($original === null) { + throw EntityNotFoundException::fromClassNameAndIdentifier( + $classMetadata->getName(), + $identifierFlattener->flattenIdentifier($classMetadata, $identifier), + ); + } + + if ($proxy === $original) { + return; + } + + $class = $entityPersister->getClassMetadata(); + + foreach ($class->getReflectionProperties() as $property) { + if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) { + continue; + } + + $property->setValue($proxy, $property->getValue($original)); + } + }; + } + + private function getProxyFileName(string $className, string $baseDirectory): string + { + $baseDirectory = $baseDirectory ?: $this->proxyDir; + + return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . InternalProxy::MARKER + . str_replace('\\', '', $className) . '.php'; + } + + private function getProxyFactory(string $className): Closure + { + $skippedProperties = []; + $class = $this->em->getClassMetadata($className); + $identifiers = array_flip($class->getIdentifierFieldNames()); + $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; + $reflector = $class->getReflectionClass(); + + while ($reflector) { + foreach ($reflector->getProperties($filter) as $property) { + $name = $property->name; + + if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) { + continue; + } + + $prefix = $property->isPrivate() ? "\0" . $property->class . "\0" : ($property->isProtected() ? "\0*\0" : ''); + + $skippedProperties[$prefix . $name] = true; + } + + $filter = ReflectionProperty::IS_PRIVATE; + $reflector = $reflector->getParentClass(); + } + + $className = $class->getName(); // aliases and case sensitivity + $entityPersister = $this->uow->getEntityPersister($className); + $initializer = $this->createLazyInitializer($class, $entityPersister, $this->identifierFlattener); + $proxyClassName = $this->loadProxyClass($class); + $identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers); + + $proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy { + $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void { + $initializer($object, $identifier); + }, $skippedProperties); + + foreach ($identifierFields as $idField => $reflector) { + if (! isset($identifier[$idField])) { + throw ORMInvalidArgumentException::missingPrimaryKeyValue($className, $idField); + } + + assert($reflector !== null); + $reflector->setValue($proxy, $identifier[$idField]); + } + + return $proxy; + }, null, $proxyClassName); + + return $this->proxyFactories[$className] = $proxyFactory; + } + + private function loadProxyClass(ClassMetadata $class): string + { + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); + + if (class_exists($proxyClassName, false)) { + return $proxyClassName; + } + + if ($this->autoGenerate === self::AUTOGENERATE_EVAL) { + $this->generateProxyClass($class, null, $proxyClassName); + + return $proxyClassName; + } + + $fileName = $this->getProxyFileName($class->getName(), $this->proxyDir); + + switch ($this->autoGenerate) { + case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: + if (file_exists($fileName) && filemtime($fileName) >= filemtime($class->getReflectionClass()->getFileName())) { + break; + } + // no break + case self::AUTOGENERATE_FILE_NOT_EXISTS: + if (file_exists($fileName)) { + break; + } + // no break + case self::AUTOGENERATE_ALWAYS: + $this->generateProxyClass($class, $fileName, $proxyClassName); + break; + } + + require $fileName; + + return $proxyClassName; + } + + private function generateProxyClass(ClassMetadata $class, string|null $fileName, string $proxyClassName): void + { + $i = strrpos($proxyClassName, '\\'); + $placeholders = [ + '' => $class->getName(), + '' => substr($proxyClassName, 0, $i), + '' => substr($proxyClassName, 1 + $i), + '' => InternalProxy::class, + ]; + + preg_match_all('(<([a-zA-Z]+)>)', self::PROXY_CLASS_TEMPLATE, $placeholderMatches); + + foreach (array_combine($placeholderMatches[0], $placeholderMatches[1]) as $placeholder => $name) { + $placeholders[$placeholder] ?? $placeholders[$placeholder] = $this->{'generate' . ucfirst($name)}($class); + } + + $proxyCode = strtr(self::PROXY_CLASS_TEMPLATE, $placeholders); + + if (! $fileName) { + if (! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true)) { + throw ORMInvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); + } + + if (! is_writable($parentDirectory)) { + throw ORMInvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); + } + + $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); + + file_put_contents($tmpFileName, $proxyCode); + @chmod($tmpFileName, 0664); + rename($tmpFileName, $fileName); + } + + private function generateUseLazyGhostTrait(ClassMetadata $class): string + { + $code = ProxyHelper::generateLazyGhost($class->getReflectionClass()); + $code = substr($code, 7 + (int) strpos($code, "\n{")); + $code = substr($code, 0, (int) strpos($code, "\n}")); + $code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait { + initializeLazyObject as private; + setLazyObjectAsInitialized as public __setInitialized; + isLazyObjectInitialized as private; + createLazyGhost as private; + resetLazyObject as private; + } + + public function __load(): void + { + $this->initializeLazyObject(); + } + '), $code); + + return $code; + } + + private function generateSerializeImpl(ClassMetadata $class): string + { + $reflector = $class->getReflectionClass(); + $properties = $reflector->hasMethod('__serialize') ? 'parent::__serialize()' : '(array) $this'; + + $code = '$properties = ' . $properties . '; + unset($properties["\0" . self::class . "\0lazyObjectState"]); + + '; + + if ($reflector->hasMethod('__serialize') || ! $reflector->hasMethod('__sleep')) { + return $code . 'return $properties;'; + } + + return $code . '$data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->name . '\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(sprintf(\'serialize(): "%s" returned as member variable from __sleep() but does not exist\', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data;'; + } + + private static function generateProxyClassName(string $className, string $proxyNamespace): string + { + return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); + } +} diff --git a/vendor/doctrine/orm/src/Query.php b/vendor/doctrine/orm/src/Query.php new file mode 100644 index 0000000..a869316 --- /dev/null +++ b/vendor/doctrine/orm/src/Query.php @@ -0,0 +1,682 @@ + + */ + private array $parsedTypes = []; + + /** + * Cached DQL query. + */ + private string|null $dql = null; + + /** + * The parser result that holds DQL => SQL information. + */ + private ParserResult $parserResult; + + /** + * The first result to return (the "offset"). + */ + private int $firstResult = 0; + + /** + * The maximum number of results to return (the "limit"). + */ + private int|null $maxResults = null; + + /** + * The cache driver used for caching queries. + */ + private CacheItemPoolInterface|null $queryCache = null; + + /** + * Whether or not expire the query cache. + */ + private bool $expireQueryCache = false; + + /** + * The query cache lifetime. + */ + private int|null $queryCacheTTL = null; + + /** + * Whether to use a query cache, if available. Defaults to TRUE. + */ + private bool $useQueryCache = true; + + /** + * Gets the SQL query/queries that correspond to this DQL query. + * + * @return list|string The built sql query or an array of all sql queries. + */ + public function getSQL(): string|array + { + return $this->parse()->getSqlExecutor()->getSqlStatements(); + } + + /** + * Returns the corresponding AST for this DQL query. + */ + public function getAST(): SelectStatement|UpdateStatement|DeleteStatement + { + $parser = new Parser($this); + + return $parser->getAST(); + } + + protected function getResultSetMapping(): ResultSetMapping + { + // parse query or load from cache + if ($this->resultSetMapping === null) { + $this->resultSetMapping = $this->parse()->getResultSetMapping(); + } + + return $this->resultSetMapping; + } + + /** + * Parses the DQL query, if necessary, and stores the parser result. + * + * Note: Populates $this->_parserResult as a side-effect. + */ + private function parse(): ParserResult + { + $types = []; + + foreach ($this->parameters as $parameter) { + /** @var Query\Parameter $parameter */ + $types[$parameter->getName()] = $parameter->getType(); + } + + // Return previous parser result if the query and the filter collection are both clean + if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->em->isFiltersStateClean()) { + return $this->parserResult; + } + + $this->state = self::STATE_CLEAN; + $this->parsedTypes = $types; + + $queryCache = $this->queryCache ?? $this->em->getConfiguration()->getQueryCache(); + // Check query cache. + if (! ($this->useQueryCache && $queryCache)) { + $parser = new Parser($this); + + $this->parserResult = $parser->parse(); + + return $this->parserResult; + } + + $cacheItem = $queryCache->getItem($this->getQueryCacheId()); + + if (! $this->expireQueryCache && $cacheItem->isHit()) { + $cached = $cacheItem->get(); + if ($cached instanceof ParserResult) { + // Cache hit. + $this->parserResult = $cached; + + return $this->parserResult; + } + } + + // Cache miss. + $parser = new Parser($this); + + $this->parserResult = $parser->parse(); + + $queryCache->save($cacheItem->set($this->parserResult)->expiresAfter($this->queryCacheTTL)); + + return $this->parserResult; + } + + protected function _doExecute(): Result|int + { + $executor = $this->parse()->getSqlExecutor(); + + if ($this->queryCacheProfile) { + $executor->setQueryCacheProfile($this->queryCacheProfile); + } else { + $executor->removeQueryCacheProfile(); + } + + if ($this->resultSetMapping === null) { + $this->resultSetMapping = $this->parserResult->getResultSetMapping(); + } + + // Prepare parameters + $paramMappings = $this->parserResult->getParameterMappings(); + $paramCount = count($this->parameters); + $mappingCount = count($paramMappings); + + if ($paramCount > $mappingCount) { + throw QueryException::tooManyParameters($mappingCount, $paramCount); + } + + if ($paramCount < $mappingCount) { + throw QueryException::tooFewParameters($mappingCount, $paramCount); + } + + // evict all cache for the entity region + if ($this->hasCache && isset($this->hints[self::HINT_CACHE_EVICT]) && $this->hints[self::HINT_CACHE_EVICT]) { + $this->evictEntityCacheRegion(); + } + + [$sqlParams, $types] = $this->processParameterMappings($paramMappings); + + $this->evictResultSetCache( + $executor, + $sqlParams, + $types, + $this->em->getConnection()->getParams(), + ); + + return $executor->execute($this->em->getConnection(), $sqlParams, $types); + } + + /** + * @param array $sqlParams + * @param array $types + * @param array $connectionParams + */ + private function evictResultSetCache( + AbstractSqlExecutor $executor, + array $sqlParams, + array $types, + array $connectionParams, + ): void { + if ($this->queryCacheProfile === null || ! $this->getExpireResultCache()) { + return; + } + + $cache = $this->queryCacheProfile->getResultCache(); + + assert($cache !== null); + + $statements = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array + + foreach ($statements as $statement) { + $cacheKeys = $this->queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams); + $cache->deleteItem(reset($cacheKeys)); + } + } + + /** + * Evict entity cache region + */ + private function evictEntityCacheRegion(): void + { + $AST = $this->getAST(); + + if ($AST instanceof SelectStatement) { + throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.'); + } + + $className = $AST instanceof DeleteStatement + ? $AST->deleteClause->abstractSchemaName + : $AST->updateClause->abstractSchemaName; + + $this->em->getCache()->evictEntityRegion($className); + } + + /** + * Processes query parameter mappings. + * + * @param array> $paramMappings + * + * @return mixed[][] + * @psalm-return array{0: list, 1: array} + * + * @throws Query\QueryException + */ + private function processParameterMappings(array $paramMappings): array + { + $sqlParams = []; + $types = []; + + foreach ($this->parameters as $parameter) { + $key = $parameter->getName(); + + if (! isset($paramMappings[$key])) { + throw QueryException::unknownParameter($key); + } + + [$value, $type] = $this->resolveParameterValue($parameter); + + foreach ($paramMappings[$key] as $position) { + $types[$position] = $type; + } + + $sqlPositions = $paramMappings[$key]; + + // optimized multi value sql positions away for now, + // they are not allowed in DQL anyways. + $value = [$value]; + $countValue = count($value); + + for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { + $sqlParams[$sqlPositions[$i]] = $value[$i % $countValue]; + } + } + + if (count($sqlParams) !== count($types)) { + throw QueryException::parameterTypeMismatch(); + } + + if ($sqlParams) { + ksort($sqlParams); + $sqlParams = array_values($sqlParams); + + ksort($types); + $types = array_values($types); + } + + return [$sqlParams, $types]; + } + + /** + * @return mixed[] tuple of (value, type) + * @psalm-return array{0: mixed, 1: mixed} + */ + private function resolveParameterValue(Parameter $parameter): array + { + if ($parameter->typeWasSpecified()) { + return [$parameter->getValue(), $parameter->getType()]; + } + + $key = $parameter->getName(); + $originalValue = $parameter->getValue(); + $value = $originalValue; + $rsm = $this->getResultSetMapping(); + + if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) { + $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); + } + + if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) { + $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->em)); + } + + $processedValue = $this->processParameterValue($value); + + return [ + $processedValue, + $originalValue === $processedValue + ? $parameter->getType() + : ParameterTypeInferer::inferType($processedValue), + ]; + } + + /** + * Defines a cache driver to be used for caching queries. + * + * @return $this + */ + public function setQueryCache(CacheItemPoolInterface|null $queryCache): self + { + $this->queryCache = $queryCache; + + return $this; + } + + /** + * Defines whether the query should make use of a query cache, if available. + * + * @return $this + */ + public function useQueryCache(bool $bool): self + { + $this->useQueryCache = $bool; + + return $this; + } + + /** + * Defines how long the query cache will be active before expire. + * + * @param int|null $timeToLive How long the cache entry is valid. + * + * @return $this + */ + public function setQueryCacheLifetime(int|null $timeToLive): self + { + $this->queryCacheTTL = $timeToLive; + + return $this; + } + + /** + * Retrieves the lifetime of resultset cache. + */ + public function getQueryCacheLifetime(): int|null + { + return $this->queryCacheTTL; + } + + /** + * Defines if the query cache is active or not. + * + * @return $this + */ + public function expireQueryCache(bool $expire = true): self + { + $this->expireQueryCache = $expire; + + return $this; + } + + /** + * Retrieves if the query cache is active or not. + */ + public function getExpireQueryCache(): bool + { + return $this->expireQueryCache; + } + + public function free(): void + { + parent::free(); + + $this->dql = null; + $this->state = self::STATE_CLEAN; + } + + /** + * Sets a DQL query string. + */ + public function setDQL(string $dqlQuery): self + { + $this->dql = $dqlQuery; + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Returns the DQL query that is represented by this query object. + */ + public function getDQL(): string|null + { + return $this->dql; + } + + /** + * Returns the state of this query object + * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL + * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY. + * + * @see AbstractQuery::STATE_CLEAN + * @see AbstractQuery::STATE_DIRTY + * + * @return int The query state. + * @psalm-return self::STATE_* The query state. + */ + public function getState(): int + { + return $this->state; + } + + /** + * Method to check if an arbitrary piece of DQL exists + * + * @param string $dql Arbitrary piece of DQL to check for. + */ + public function contains(string $dql): bool + { + return stripos($this->getDQL(), $dql) !== false; + } + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param int $firstResult The first result to return. + * + * @return $this + */ + public function setFirstResult(int $firstResult): self + { + $this->firstResult = $firstResult; + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * Returns 0 if {@link setFirstResult} was not applied to this query. + * + * @return int The position of the first result. + */ + public function getFirstResult(): int + { + return $this->firstResult; + } + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @return $this + */ + public function setMaxResults(int|null $maxResults): self + { + $this->maxResults = $maxResults; + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query. + * + * @return int|null Maximum number of results. + */ + public function getMaxResults(): int|null + { + return $this->maxResults; + } + + /** {@inheritDoc} */ + public function toIterable(iterable $parameters = [], $hydrationMode = self::HYDRATE_OBJECT): iterable + { + $this->setHint(self::HINT_INTERNAL_ITERATION, true); + + return parent::toIterable($parameters, $hydrationMode); + } + + public function setHint(string $name, mixed $value): static + { + $this->state = self::STATE_DIRTY; + + return parent::setHint($name, $value); + } + + public function setHydrationMode(string|int $hydrationMode): static + { + $this->state = self::STATE_DIRTY; + + return parent::setHydrationMode($hydrationMode); + } + + /** + * Set the lock mode for this Query. + * + * @see \Doctrine\DBAL\LockMode + * + * @psalm-param LockMode::* $lockMode + * + * @return $this + * + * @throws TransactionRequiredException + */ + public function setLockMode(LockMode|int $lockMode): self + { + if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) { + if (! $this->em->getConnection()->isTransactionActive()) { + throw TransactionRequiredException::transactionRequired(); + } + } + + $this->setHint(self::HINT_LOCK_MODE, $lockMode); + + return $this; + } + + /** + * Get the current lock mode for this query. + * + * @return LockMode|int|null The current lock mode of this query or NULL if no specific lock mode is set. + * @psalm-return LockMode::*|null + */ + public function getLockMode(): LockMode|int|null + { + $lockMode = $this->getHint(self::HINT_LOCK_MODE); + + if ($lockMode === false) { + return null; + } + + return $lockMode; + } + + /** + * Generate a cache id for the query cache - reusing the Result-Cache-Id generator. + */ + protected function getQueryCacheId(): string + { + ksort($this->hints); + + return md5( + $this->getDQL() . serialize($this->hints) . + '&platform=' . get_debug_type($this->getEntityManager()->getConnection()->getDatabasePlatform()) . + ($this->em->hasFilters() ? $this->em->getFilters()->getHash() : '') . + '&firstResult=' . $this->firstResult . '&maxResult=' . $this->maxResults . + '&hydrationMode=' . $this->hydrationMode . '&types=' . serialize($this->parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT', + ); + } + + protected function getHash(): string + { + return sha1(parent::getHash() . '-' . $this->firstResult . '-' . $this->maxResults); + } + + /** + * Cleanup Query resource when clone is called. + */ + public function __clone() + { + parent::__clone(); + + $this->state = self::STATE_DIRTY; + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ASTException.php b/vendor/doctrine/orm/src/Query/AST/ASTException.php new file mode 100644 index 0000000..1ef890a --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ASTException.php @@ -0,0 +1,20 @@ +walkAggregateExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ArithmeticExpression.php b/vendor/doctrine/orm/src/Query/AST/ArithmeticExpression.php new file mode 100644 index 0000000..a819e05 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ArithmeticExpression.php @@ -0,0 +1,34 @@ +simpleArithmeticExpression; + } + + public function isSubselect(): bool + { + return (bool) $this->subselect; + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkArithmeticExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ArithmeticFactor.php b/vendor/doctrine/orm/src/Query/AST/ArithmeticFactor.php new file mode 100644 index 0000000..278a921 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ArithmeticFactor.php @@ -0,0 +1,36 @@ +sign === true; + } + + public function isNegativeSigned(): bool + { + return $this->sign === false; + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkArithmeticFactor($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ArithmeticTerm.php b/vendor/doctrine/orm/src/Query/AST/ArithmeticTerm.php new file mode 100644 index 0000000..b233612 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ArithmeticTerm.php @@ -0,0 +1,25 @@ +walkArithmeticTerm($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/BetweenExpression.php b/vendor/doctrine/orm/src/Query/AST/BetweenExpression.php new file mode 100644 index 0000000..c13292b --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/BetweenExpression.php @@ -0,0 +1,23 @@ +walkBetweenExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/CoalesceExpression.php b/vendor/doctrine/orm/src/Query/AST/CoalesceExpression.php new file mode 100644 index 0000000..89f025f --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/CoalesceExpression.php @@ -0,0 +1,25 @@ +walkCoalesceExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/CollectionMemberExpression.php b/vendor/doctrine/orm/src/Query/AST/CollectionMemberExpression.php new file mode 100644 index 0000000..a62a191 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/CollectionMemberExpression.php @@ -0,0 +1,27 @@ +walkCollectionMemberExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ComparisonExpression.php b/vendor/doctrine/orm/src/Query/AST/ComparisonExpression.php new file mode 100644 index 0000000..a7d91f9 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ComparisonExpression.php @@ -0,0 +1,32 @@ +" | "!=") (BooleanExpression | QuantifiedExpression) | + * EnumExpression ("=" | "<>" | "!=") (EnumExpression | QuantifiedExpression) | + * DatetimeExpression ComparisonOperator (DatetimeExpression | QuantifiedExpression) | + * EntityExpression ("=" | "<>") (EntityExpression | QuantifiedExpression) + * + * @link www.doctrine-project.org + */ +class ComparisonExpression extends Node +{ + public function __construct( + public Node|string $leftExpression, + public string $operator, + public Node|string $rightExpression, + ) { + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkComparisonExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalExpression.php b/vendor/doctrine/orm/src/Query/AST/ConditionalExpression.php new file mode 100644 index 0000000..26a98e5 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ConditionalExpression.php @@ -0,0 +1,25 @@ +walkConditionalExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalFactor.php b/vendor/doctrine/orm/src/Query/AST/ConditionalFactor.php new file mode 100644 index 0000000..7881743 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ConditionalFactor.php @@ -0,0 +1,26 @@ +walkConditionalFactor($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalPrimary.php b/vendor/doctrine/orm/src/Query/AST/ConditionalPrimary.php new file mode 100644 index 0000000..9344cd9 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ConditionalPrimary.php @@ -0,0 +1,34 @@ +simpleConditionalExpression; + } + + public function isConditionalExpression(): bool + { + return (bool) $this->conditionalExpression; + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkConditionalPrimary($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalTerm.php b/vendor/doctrine/orm/src/Query/AST/ConditionalTerm.php new file mode 100644 index 0000000..dcea50b --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ConditionalTerm.php @@ -0,0 +1,25 @@ +walkConditionalTerm($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/DeleteClause.php b/vendor/doctrine/orm/src/Query/AST/DeleteClause.php new file mode 100644 index 0000000..25e9085 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/DeleteClause.php @@ -0,0 +1,26 @@ +walkDeleteClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/DeleteStatement.php b/vendor/doctrine/orm/src/Query/AST/DeleteStatement.php new file mode 100644 index 0000000..f367d09 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/DeleteStatement.php @@ -0,0 +1,26 @@ +walkDeleteStatement($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/EmptyCollectionComparisonExpression.php b/vendor/doctrine/orm/src/Query/AST/EmptyCollectionComparisonExpression.php new file mode 100644 index 0000000..9978800 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/EmptyCollectionComparisonExpression.php @@ -0,0 +1,26 @@ +walkEmptyCollectionComparisonExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ExistsExpression.php b/vendor/doctrine/orm/src/Query/AST/ExistsExpression.php new file mode 100644 index 0000000..72757f4 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ExistsExpression.php @@ -0,0 +1,26 @@ +walkExistsExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/FromClause.php b/vendor/doctrine/orm/src/Query/AST/FromClause.php new file mode 100644 index 0000000..0b74393 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/FromClause.php @@ -0,0 +1,25 @@ +walkFromClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/AbsFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/AbsFunction.php new file mode 100644 index 0000000..4edff06 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/AbsFunction.php @@ -0,0 +1,37 @@ +walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression, + ) . ')'; + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/AvgFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/AvgFunction.php new file mode 100644 index 0000000..ba7b7f3 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/AvgFunction.php @@ -0,0 +1,27 @@ +aggregateExpression->dispatch($sqlWalker); + } + + public function parse(Parser $parser): void + { + $this->aggregateExpression = $parser->AggregateExpression(); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/BitAndFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/BitAndFunction.php new file mode 100644 index 0000000..f2d3146 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/BitAndFunction.php @@ -0,0 +1,43 @@ +getConnection()->getDatabasePlatform(); + + return $platform->getBitAndComparisonExpression( + $this->firstArithmetic->dispatch($sqlWalker), + $this->secondArithmetic->dispatch($sqlWalker), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->firstArithmetic = $parser->ArithmeticPrimary(); + $parser->match(TokenType::T_COMMA); + $this->secondArithmetic = $parser->ArithmeticPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/BitOrFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/BitOrFunction.php new file mode 100644 index 0000000..f3f84da --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/BitOrFunction.php @@ -0,0 +1,43 @@ +getConnection()->getDatabasePlatform(); + + return $platform->getBitOrComparisonExpression( + $this->firstArithmetic->dispatch($sqlWalker), + $this->secondArithmetic->dispatch($sqlWalker), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->firstArithmetic = $parser->ArithmeticPrimary(); + $parser->match(TokenType::T_COMMA); + $this->secondArithmetic = $parser->ArithmeticPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/ConcatFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/ConcatFunction.php new file mode 100644 index 0000000..5b8d696 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/ConcatFunction.php @@ -0,0 +1,58 @@ + */ + public array $concatExpressions = []; + + public function getSql(SqlWalker $sqlWalker): string + { + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + $args = []; + + foreach ($this->concatExpressions as $expression) { + $args[] = $sqlWalker->walkStringPrimary($expression); + } + + return $platform->getConcatExpression(...$args); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->firstStringPrimary = $parser->StringPrimary(); + $this->concatExpressions[] = $this->firstStringPrimary; + + $parser->match(TokenType::T_COMMA); + + $this->secondStringPrimary = $parser->StringPrimary(); + $this->concatExpressions[] = $this->secondStringPrimary; + + while ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) { + $parser->match(TokenType::T_COMMA); + $this->concatExpressions[] = $parser->StringPrimary(); + } + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CountFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CountFunction.php new file mode 100644 index 0000000..dc926a5 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/CountFunction.php @@ -0,0 +1,35 @@ +aggregateExpression->dispatch($sqlWalker); + } + + public function parse(Parser $parser): void + { + $this->aggregateExpression = $parser->AggregateExpression(); + } + + public function getReturnType(): Type + { + return Type::getType(Types::INTEGER); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CurrentDateFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentDateFunction.php new file mode 100644 index 0000000..cec9632 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentDateFunction.php @@ -0,0 +1,29 @@ +getConnection()->getDatabasePlatform()->getCurrentDateSQL(); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimeFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimeFunction.php new file mode 100644 index 0000000..6473fce --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimeFunction.php @@ -0,0 +1,29 @@ +getConnection()->getDatabasePlatform()->getCurrentTimeSQL(); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimestampFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimestampFunction.php new file mode 100644 index 0000000..edcd27c --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimestampFunction.php @@ -0,0 +1,29 @@ +getConnection()->getDatabasePlatform()->getCurrentTimestampSQL(); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/DateAddFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/DateAddFunction.php new file mode 100644 index 0000000..12920dc --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/DateAddFunction.php @@ -0,0 +1,83 @@ +unit->value)) { + 'second' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddSecondsExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'minute' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMinutesExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'hour' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddHourExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'day' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddDaysExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'week' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddWeeksExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'month' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMonthExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'year' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddYearsExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + default => throw QueryException::semanticalError( + 'DATE_ADD() only supports units of type second, minute, hour, day, week, month and year.', + ), + }; + } + + /** @throws ASTException */ + private function dispatchIntervalExpression(SqlWalker $sqlWalker): string + { + return $this->intervalExpression->dispatch($sqlWalker); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->firstDateExpression = $parser->ArithmeticPrimary(); + $parser->match(TokenType::T_COMMA); + $this->intervalExpression = $parser->ArithmeticPrimary(); + $parser->match(TokenType::T_COMMA); + $this->unit = $parser->StringPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/DateDiffFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/DateDiffFunction.php new file mode 100644 index 0000000..55598c0 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/DateDiffFunction.php @@ -0,0 +1,41 @@ +getConnection()->getDatabasePlatform()->getDateDiffExpression( + $this->date1->dispatch($sqlWalker), + $this->date2->dispatch($sqlWalker), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->date1 = $parser->ArithmeticPrimary(); + $parser->match(TokenType::T_COMMA); + $this->date2 = $parser->ArithmeticPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/DateSubFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/DateSubFunction.php new file mode 100644 index 0000000..5363680 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/DateSubFunction.php @@ -0,0 +1,62 @@ +unit->value)) { + 'second' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubSecondsExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'minute' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMinutesExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'hour' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubHourExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'day' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubDaysExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'week' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubWeeksExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'month' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMonthExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + 'year' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubYearsExpression( + $this->firstDateExpression->dispatch($sqlWalker), + $this->dispatchIntervalExpression($sqlWalker), + ), + default => throw QueryException::semanticalError( + 'DATE_SUB() only supports units of type second, minute, hour, day, week, month and year.', + ), + }; + } + + /** @throws ASTException */ + private function dispatchIntervalExpression(SqlWalker $sqlWalker): string + { + return $this->intervalExpression->dispatch($sqlWalker); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/FunctionNode.php b/vendor/doctrine/orm/src/Query/AST/Functions/FunctionNode.php new file mode 100644 index 0000000..4cc549e --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/FunctionNode.php @@ -0,0 +1,32 @@ +walkFunction($this); + } + + abstract public function parse(Parser $parser): void; +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/IdentityFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/IdentityFunction.php new file mode 100644 index 0000000..1dd1bf5 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/IdentityFunction.php @@ -0,0 +1,90 @@ +pathExpression->field !== null); + $entityManager = $sqlWalker->getEntityManager(); + $platform = $entityManager->getConnection()->getDatabasePlatform(); + $quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy(); + $dqlAlias = $this->pathExpression->identificationVariable; + $assocField = $this->pathExpression->field; + $assoc = $sqlWalker->getMetadataForDqlAlias($dqlAlias)->associationMappings[$assocField]; + $targetEntity = $entityManager->getClassMetadata($assoc->targetEntity); + + assert($assoc->isToOneOwningSide()); + $joinColumn = reset($assoc->joinColumns); + + if ($this->fieldMapping !== null) { + if (! isset($targetEntity->fieldMappings[$this->fieldMapping])) { + throw new QueryException(sprintf('Undefined reference field mapping "%s"', $this->fieldMapping)); + } + + $field = $targetEntity->fieldMappings[$this->fieldMapping]; + $joinColumn = null; + + foreach ($assoc->joinColumns as $mapping) { + if ($mapping->referencedColumnName === $field->columnName) { + $joinColumn = $mapping; + + break; + } + } + + if ($joinColumn === null) { + throw new QueryException(sprintf('Unable to resolve the reference field mapping "%s"', $this->fieldMapping)); + } + } + + // The table with the relation may be a subclass, so get the table name from the association definition + $tableName = $entityManager->getClassMetadata($assoc->sourceEntity)->getTableName(); + + $tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias); + $columnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $platform); + + return $tableAlias . '.' . $columnName; + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->pathExpression = $parser->SingleValuedAssociationPathExpression(); + + if ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) { + $parser->match(TokenType::T_COMMA); + $parser->match(TokenType::T_STRING); + + $token = $parser->getLexer()->token; + assert($token !== null); + $this->fieldMapping = $token->value; + } + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/LengthFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/LengthFunction.php new file mode 100644 index 0000000..3994918 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/LengthFunction.php @@ -0,0 +1,45 @@ +getConnection()->getDatabasePlatform()->getLengthExpression( + $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->stringPrimary = $parser->StringPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getReturnType(): Type + { + return Type::getType(Types::INTEGER); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/LocateFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/LocateFunction.php new file mode 100644 index 0000000..c0d3b4a --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/LocateFunction.php @@ -0,0 +1,62 @@ +getConnection()->getDatabasePlatform(); + + $firstString = $sqlWalker->walkStringPrimary($this->firstStringPrimary); + $secondString = $sqlWalker->walkStringPrimary($this->secondStringPrimary); + + if ($this->simpleArithmeticExpression) { + return $platform->getLocateExpression( + $secondString, + $firstString, + $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression), + ); + } + + return $platform->getLocateExpression($secondString, $firstString); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->firstStringPrimary = $parser->StringPrimary(); + + $parser->match(TokenType::T_COMMA); + + $this->secondStringPrimary = $parser->StringPrimary(); + + $lexer = $parser->getLexer(); + if ($lexer->isNextToken(TokenType::T_COMMA)) { + $parser->match(TokenType::T_COMMA); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + } + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/LowerFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/LowerFunction.php new file mode 100644 index 0000000..8ae337a --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/LowerFunction.php @@ -0,0 +1,40 @@ +walkSimpleArithmeticExpression($this->stringPrimary), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->stringPrimary = $parser->StringPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/MaxFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/MaxFunction.php new file mode 100644 index 0000000..8a6eecf --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/MaxFunction.php @@ -0,0 +1,27 @@ +aggregateExpression->dispatch($sqlWalker); + } + + public function parse(Parser $parser): void + { + $this->aggregateExpression = $parser->AggregateExpression(); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/MinFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/MinFunction.php new file mode 100644 index 0000000..98d73a2 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/MinFunction.php @@ -0,0 +1,27 @@ +aggregateExpression->dispatch($sqlWalker); + } + + public function parse(Parser $parser): void + { + $this->aggregateExpression = $parser->AggregateExpression(); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/ModFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/ModFunction.php new file mode 100644 index 0000000..7c1af0b --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/ModFunction.php @@ -0,0 +1,43 @@ +getConnection()->getDatabasePlatform()->getModExpression( + $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), + $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(TokenType::T_COMMA); + + $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SizeFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SizeFunction.php new file mode 100644 index 0000000..87ee713 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/SizeFunction.php @@ -0,0 +1,113 @@ +collectionPathExpression->field !== null); + $entityManager = $sqlWalker->getEntityManager(); + $platform = $entityManager->getConnection()->getDatabasePlatform(); + $quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy(); + $dqlAlias = $this->collectionPathExpression->identificationVariable; + $assocField = $this->collectionPathExpression->field; + + $class = $sqlWalker->getMetadataForDqlAlias($dqlAlias); + $assoc = $class->associationMappings[$assocField]; + $sql = 'SELECT COUNT(*) FROM '; + + if ($assoc->isOneToMany()) { + $targetClass = $entityManager->getClassMetadata($assoc->targetEntity); + $targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName()); + $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); + + $sql .= $quoteStrategy->getTableName($targetClass, $platform) . ' ' . $targetTableAlias . ' WHERE '; + + $owningAssoc = $targetClass->associationMappings[$assoc->mappedBy]; + assert($owningAssoc->isManyToOne()); + + $first = true; + + foreach ($owningAssoc->targetToSourceKeyColumns as $targetColumn => $sourceColumn) { + if ($first) { + $first = false; + } else { + $sql .= ' AND '; + } + + $sql .= $targetTableAlias . '.' . $sourceColumn + . ' = ' + . $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform); + } + } else { // many-to-many + assert($assoc->isManyToMany()); + $owningAssoc = $entityManager->getMetadataFactory()->getOwningSide($assoc); + $joinTable = $owningAssoc->joinTable; + + // SQL table aliases + $joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable->name); + $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); + + // join to target table + $targetClass = $entityManager->getClassMetadata($assoc->targetEntity); + $sql .= $quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $platform) . ' ' . $joinTableAlias . ' WHERE '; + + $joinColumns = $assoc->isOwningSide() + ? $joinTable->joinColumns + : $joinTable->inverseJoinColumns; + + $first = true; + + foreach ($joinColumns as $joinColumn) { + if ($first) { + $first = false; + } else { + $sql .= ' AND '; + } + + $sourceColumnName = $quoteStrategy->getColumnName( + $class->fieldNames[$joinColumn->referencedColumnName], + $class, + $platform, + ); + + $sql .= $joinTableAlias . '.' . $joinColumn->name + . ' = ' + . $sourceTableAlias . '.' . $sourceColumnName; + } + } + + return '(' . $sql . ')'; + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->collectionPathExpression = $parser->CollectionValuedPathExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SqrtFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SqrtFunction.php new file mode 100644 index 0000000..e643663 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/SqrtFunction.php @@ -0,0 +1,40 @@ +walkSimpleArithmeticExpression($this->simpleArithmeticExpression), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SubstringFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SubstringFunction.php new file mode 100644 index 0000000..5744f08 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/SubstringFunction.php @@ -0,0 +1,58 @@ +secondSimpleArithmeticExpression !== null) { + $optionalSecondSimpleArithmeticExpression = $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression); + } + + return $sqlWalker->getConnection()->getDatabasePlatform()->getSubstringExpression( + $sqlWalker->walkStringPrimary($this->stringPrimary), + $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), + $optionalSecondSimpleArithmeticExpression, + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->stringPrimary = $parser->StringPrimary(); + + $parser->match(TokenType::T_COMMA); + + $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $lexer = $parser->getLexer(); + if ($lexer->isNextToken(TokenType::T_COMMA)) { + $parser->match(TokenType::T_COMMA); + + $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + } + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SumFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SumFunction.php new file mode 100644 index 0000000..588dce9 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/SumFunction.php @@ -0,0 +1,27 @@ +aggregateExpression->dispatch($sqlWalker); + } + + public function parse(Parser $parser): void + { + $this->aggregateExpression = $parser->AggregateExpression(); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/TrimFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/TrimFunction.php new file mode 100644 index 0000000..e0a3e99 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/TrimFunction.php @@ -0,0 +1,119 @@ +walkStringPrimary($this->stringPrimary); + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + $trimMode = $this->getTrimMode(); + + if ($this->trimChar !== false) { + return $platform->getTrimExpression( + $stringPrimary, + $trimMode, + $platform->quoteStringLiteral($this->trimChar), + ); + } + + return $platform->getTrimExpression($stringPrimary, $trimMode); + } + + public function parse(Parser $parser): void + { + $lexer = $parser->getLexer(); + + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->parseTrimMode($parser); + + if ($lexer->isNextToken(TokenType::T_STRING)) { + $parser->match(TokenType::T_STRING); + + assert($lexer->token !== null); + $this->trimChar = $lexer->token->value; + } + + if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) { + $parser->match(TokenType::T_FROM); + } + + $this->stringPrimary = $parser->StringPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + /** @psalm-return TrimMode::* */ + private function getTrimMode(): TrimMode|int + { + if ($this->leading) { + return TrimMode::LEADING; + } + + if ($this->trailing) { + return TrimMode::TRAILING; + } + + if ($this->both) { + return TrimMode::BOTH; + } + + return TrimMode::UNSPECIFIED; + } + + private function parseTrimMode(Parser $parser): void + { + $lexer = $parser->getLexer(); + assert($lexer->lookahead !== null); + $value = $lexer->lookahead->value; + + if (strcasecmp('leading', $value) === 0) { + $parser->match(TokenType::T_LEADING); + + $this->leading = true; + + return; + } + + if (strcasecmp('trailing', $value) === 0) { + $parser->match(TokenType::T_TRAILING); + + $this->trailing = true; + + return; + } + + if (strcasecmp('both', $value) === 0) { + $parser->match(TokenType::T_BOTH); + + $this->both = true; + + return; + } + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/UpperFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/UpperFunction.php new file mode 100644 index 0000000..1ecef66 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Functions/UpperFunction.php @@ -0,0 +1,40 @@ +walkSimpleArithmeticExpression($this->stringPrimary), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->stringPrimary = $parser->StringPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/GeneralCaseExpression.php b/vendor/doctrine/orm/src/Query/AST/GeneralCaseExpression.php new file mode 100644 index 0000000..39d760a --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/GeneralCaseExpression.php @@ -0,0 +1,27 @@ +walkGeneralCaseExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/GroupByClause.php b/vendor/doctrine/orm/src/Query/AST/GroupByClause.php new file mode 100644 index 0000000..eb0f1b9 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/GroupByClause.php @@ -0,0 +1,20 @@ +walkGroupByClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/HavingClause.php b/vendor/doctrine/orm/src/Query/AST/HavingClause.php new file mode 100644 index 0000000..0d4d821 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/HavingClause.php @@ -0,0 +1,19 @@ +walkHavingClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/IdentificationVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/IdentificationVariableDeclaration.php new file mode 100644 index 0000000..c4c7cca --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/IdentificationVariableDeclaration.php @@ -0,0 +1,28 @@ +walkIdentificationVariableDeclaration($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/InListExpression.php b/vendor/doctrine/orm/src/Query/AST/InListExpression.php new file mode 100644 index 0000000..dc0f32b --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/InListExpression.php @@ -0,0 +1,23 @@ + $literals */ + public function __construct( + public ArithmeticExpression $expression, + public array $literals, + public bool $not = false, + ) { + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkInListExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/InSubselectExpression.php b/vendor/doctrine/orm/src/Query/AST/InSubselectExpression.php new file mode 100644 index 0000000..1128285 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/InSubselectExpression.php @@ -0,0 +1,22 @@ +walkInSubselectExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/IndexBy.php b/vendor/doctrine/orm/src/Query/AST/IndexBy.php new file mode 100644 index 0000000..3d90265 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/IndexBy.php @@ -0,0 +1,26 @@ +walkIndexBy($this); + + return ''; + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/InputParameter.php b/vendor/doctrine/orm/src/Query/AST/InputParameter.php new file mode 100644 index 0000000..a8e0a3b --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/InputParameter.php @@ -0,0 +1,35 @@ +isNamed = ! is_numeric($param); + $this->name = $param; + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkInputParameter($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/InstanceOfExpression.php b/vendor/doctrine/orm/src/Query/AST/InstanceOfExpression.php new file mode 100644 index 0000000..3a4e75f --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/InstanceOfExpression.php @@ -0,0 +1,29 @@ + $value */ + public function __construct( + public string $identificationVariable, + public array $value, + public bool $not = false, + ) { + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkInstanceOfExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Join.php b/vendor/doctrine/orm/src/Query/AST/Join.php new file mode 100644 index 0000000..34ce830 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Join.php @@ -0,0 +1,34 @@ +walkJoin($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/JoinAssociationDeclaration.php b/vendor/doctrine/orm/src/Query/AST/JoinAssociationDeclaration.php new file mode 100644 index 0000000..e08d7f5 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/JoinAssociationDeclaration.php @@ -0,0 +1,27 @@ +walkJoinAssociationDeclaration($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/JoinAssociationPathExpression.php b/vendor/doctrine/orm/src/Query/AST/JoinAssociationPathExpression.php new file mode 100644 index 0000000..230be36 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/JoinAssociationPathExpression.php @@ -0,0 +1,19 @@ +walkJoinPathExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/JoinVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/JoinVariableDeclaration.php new file mode 100644 index 0000000..bf76695 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/JoinVariableDeclaration.php @@ -0,0 +1,24 @@ +walkJoinVariableDeclaration($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/LikeExpression.php b/vendor/doctrine/orm/src/Query/AST/LikeExpression.php new file mode 100644 index 0000000..e3f67f8 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/LikeExpression.php @@ -0,0 +1,29 @@ +walkLikeExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Literal.php b/vendor/doctrine/orm/src/Query/AST/Literal.php new file mode 100644 index 0000000..9ec2036 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Literal.php @@ -0,0 +1,26 @@ +walkLiteral($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/NewObjectExpression.php b/vendor/doctrine/orm/src/Query/AST/NewObjectExpression.php new file mode 100644 index 0000000..7383c48 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/NewObjectExpression.php @@ -0,0 +1,25 @@ +walkNewObject($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Node.php b/vendor/doctrine/orm/src/Query/AST/Node.php new file mode 100644 index 0000000..cdb5855 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Node.php @@ -0,0 +1,85 @@ +dump($this); + } + + public function dump(mixed $value): string + { + static $ident = 0; + + $str = ''; + + if ($value instanceof Node) { + $str .= get_debug_type($value) . '(' . PHP_EOL; + $props = get_object_vars($value); + + foreach ($props as $name => $prop) { + $ident += 4; + $str .= str_repeat(' ', $ident) . '"' . $name . '": ' + . $this->dump($prop) . ',' . PHP_EOL; + $ident -= 4; + } + + $str .= str_repeat(' ', $ident) . ')'; + } elseif (is_array($value)) { + $ident += 4; + $str .= 'array('; + $some = false; + + foreach ($value as $k => $v) { + $str .= PHP_EOL . str_repeat(' ', $ident) . '"' + . $k . '" => ' . $this->dump($v) . ','; + $some = true; + } + + $ident -= 4; + $str .= ($some ? PHP_EOL . str_repeat(' ', $ident) : '') . ')'; + } elseif (is_object($value)) { + $str .= 'instanceof(' . get_debug_type($value) . ')'; + } else { + $str .= var_export($value, true); + } + + return $str; + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/NullComparisonExpression.php b/vendor/doctrine/orm/src/Query/AST/NullComparisonExpression.php new file mode 100644 index 0000000..e60cb04 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/NullComparisonExpression.php @@ -0,0 +1,26 @@ +walkNullComparisonExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/NullIfExpression.php b/vendor/doctrine/orm/src/Query/AST/NullIfExpression.php new file mode 100644 index 0000000..6fffeeb --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/NullIfExpression.php @@ -0,0 +1,24 @@ +walkNullIfExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/OrderByClause.php b/vendor/doctrine/orm/src/Query/AST/OrderByClause.php new file mode 100644 index 0000000..f6d7a67 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/OrderByClause.php @@ -0,0 +1,25 @@ +walkOrderByClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/OrderByItem.php b/vendor/doctrine/orm/src/Query/AST/OrderByItem.php new file mode 100644 index 0000000..64b3f40 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/OrderByItem.php @@ -0,0 +1,38 @@ +type) === 'ASC'; + } + + public function isDesc(): bool + { + return strtoupper($this->type) === 'DESC'; + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkOrderByItem($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/ParenthesisExpression.php b/vendor/doctrine/orm/src/Query/AST/ParenthesisExpression.php new file mode 100644 index 0000000..cda6d19 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/ParenthesisExpression.php @@ -0,0 +1,22 @@ +walkParenthesisExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/PathExpression.php b/vendor/doctrine/orm/src/Query/AST/PathExpression.php new file mode 100644 index 0000000..4a56fcd --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/PathExpression.php @@ -0,0 +1,39 @@ + $expectedType */ + public function __construct( + public int $expectedType, + public string $identificationVariable, + public string|null $field = null, + ) { + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkPathExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Phase2OptimizableConditional.php b/vendor/doctrine/orm/src/Query/AST/Phase2OptimizableConditional.php new file mode 100644 index 0000000..276f8f8 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Phase2OptimizableConditional.php @@ -0,0 +1,17 @@ +type) === 'ALL'; + } + + public function isAny(): bool + { + return strtoupper($this->type) === 'ANY'; + } + + public function isSome(): bool + { + return strtoupper($this->type) === 'SOME'; + } + + public function dispatch(SqlWalker $walker): string + { + return $walker->walkQuantifiedExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/RangeVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/RangeVariableDeclaration.php new file mode 100644 index 0000000..59bd5c8 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/RangeVariableDeclaration.php @@ -0,0 +1,27 @@ +walkRangeVariableDeclaration($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SelectClause.php b/vendor/doctrine/orm/src/Query/AST/SelectClause.php new file mode 100644 index 0000000..ad50e67 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SelectClause.php @@ -0,0 +1,27 @@ +walkSelectClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SelectExpression.php b/vendor/doctrine/orm/src/Query/AST/SelectExpression.php new file mode 100644 index 0000000..f09f3cd --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SelectExpression.php @@ -0,0 +1,28 @@ +walkSelectExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SelectStatement.php b/vendor/doctrine/orm/src/Query/AST/SelectStatement.php new file mode 100644 index 0000000..399462f --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SelectStatement.php @@ -0,0 +1,32 @@ +walkSelectStatement($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleArithmeticExpression.php b/vendor/doctrine/orm/src/Query/AST/SimpleArithmeticExpression.php new file mode 100644 index 0000000..ae7ca44 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SimpleArithmeticExpression.php @@ -0,0 +1,25 @@ +walkSimpleArithmeticExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleCaseExpression.php b/vendor/doctrine/orm/src/Query/AST/SimpleCaseExpression.php new file mode 100644 index 0000000..b3764ba --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SimpleCaseExpression.php @@ -0,0 +1,28 @@ +walkSimpleCaseExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleSelectClause.php b/vendor/doctrine/orm/src/Query/AST/SimpleSelectClause.php new file mode 100644 index 0000000..0259e3b --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SimpleSelectClause.php @@ -0,0 +1,26 @@ +walkSimpleSelectClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleSelectExpression.php b/vendor/doctrine/orm/src/Query/AST/SimpleSelectExpression.php new file mode 100644 index 0000000..97e8f08 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SimpleSelectExpression.php @@ -0,0 +1,27 @@ +walkSimpleSelectExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleWhenClause.php b/vendor/doctrine/orm/src/Query/AST/SimpleWhenClause.php new file mode 100644 index 0000000..892165a --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SimpleWhenClause.php @@ -0,0 +1,26 @@ +walkWhenClauseExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/Subselect.php b/vendor/doctrine/orm/src/Query/AST/Subselect.php new file mode 100644 index 0000000..8ff8595 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/Subselect.php @@ -0,0 +1,32 @@ +walkSubselect($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SubselectFromClause.php b/vendor/doctrine/orm/src/Query/AST/SubselectFromClause.php new file mode 100644 index 0000000..7cf01e2 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SubselectFromClause.php @@ -0,0 +1,25 @@ +walkSubselectFromClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/SubselectIdentificationVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/SubselectIdentificationVariableDeclaration.php new file mode 100644 index 0000000..eadf6bc --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/SubselectIdentificationVariableDeclaration.php @@ -0,0 +1,19 @@ +walkUpdateClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/UpdateItem.php b/vendor/doctrine/orm/src/Query/AST/UpdateItem.php new file mode 100644 index 0000000..b540593 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/UpdateItem.php @@ -0,0 +1,26 @@ +walkUpdateItem($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/UpdateStatement.php b/vendor/doctrine/orm/src/Query/AST/UpdateStatement.php new file mode 100644 index 0000000..7ea5076 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/UpdateStatement.php @@ -0,0 +1,26 @@ +walkUpdateStatement($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/WhenClause.php b/vendor/doctrine/orm/src/Query/AST/WhenClause.php new file mode 100644 index 0000000..9bf194e --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/WhenClause.php @@ -0,0 +1,26 @@ +walkWhenClauseExpression($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/AST/WhereClause.php b/vendor/doctrine/orm/src/Query/AST/WhereClause.php new file mode 100644 index 0000000..e4d7b66 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/AST/WhereClause.php @@ -0,0 +1,24 @@ +walkWhereClause($this); + } +} diff --git a/vendor/doctrine/orm/src/Query/Exec/AbstractSqlExecutor.php b/vendor/doctrine/orm/src/Query/Exec/AbstractSqlExecutor.php new file mode 100644 index 0000000..101bf26 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Exec/AbstractSqlExecutor.php @@ -0,0 +1,61 @@ +, WrapperParameterType>|array + */ +abstract class AbstractSqlExecutor +{ + /** @var list|string */ + protected array|string $sqlStatements; + + protected QueryCacheProfile|null $queryCacheProfile = null; + + /** + * Gets the SQL statements that are executed by the executor. + * + * @return list|string All the SQL update statements. + */ + public function getSqlStatements(): array|string + { + return $this->sqlStatements; + } + + public function setQueryCacheProfile(QueryCacheProfile $qcp): void + { + $this->queryCacheProfile = $qcp; + } + + /** + * Do not use query cache + */ + public function removeQueryCacheProfile(): void + { + $this->queryCacheProfile = null; + } + + /** + * Executes all sql statements. + * + * @param Connection $conn The database connection that is used to execute the queries. + * @param list|array $params The parameters. + * @psalm-param WrapperParameterTypeArray $types The parameter types. + */ + abstract public function execute(Connection $conn, array $params, array $types): Result|int; +} diff --git a/vendor/doctrine/orm/src/Query/Exec/MultiTableDeleteExecutor.php b/vendor/doctrine/orm/src/Query/Exec/MultiTableDeleteExecutor.php new file mode 100644 index 0000000..6096462 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Exec/MultiTableDeleteExecutor.php @@ -0,0 +1,131 @@ +MultiTableDeleteExecutor. + * + * Internal note: Any SQL construction and preparation takes place in the constructor for + * best performance. With a query cache the executor will be cached. + * + * @param DeleteStatement $AST The root AST node of the DQL query. + * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST. + */ + public function __construct(AST\Node $AST, SqlWalker $sqlWalker) + { + $em = $sqlWalker->getEntityManager(); + $conn = $em->getConnection(); + $platform = $conn->getDatabasePlatform(); + $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + + if ($conn instanceof PrimaryReadReplicaConnection) { + $conn->ensureConnectedToPrimary(); + } + + $primaryClass = $em->getClassMetadata($AST->deleteClause->abstractSchemaName); + $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable; + $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); + + $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); + $idColumnNames = $rootClass->getIdentifierColumnNames(); + $idColumnList = implode(', ', $idColumnNames); + + // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() + $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias); + + $insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' + . ' SELECT t0.' . implode(', t0.', $idColumnNames); + + $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias); + $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]); + $insertSql .= $sqlWalker->walkFromClause($fromClause); + + // Append WHERE clause, if there is one. + if ($AST->whereClause) { + $insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); + } + + $this->insertSql = $insertSql; + + // 2. Create ID subselect statement used in DELETE ... WHERE ... IN (subselect) + $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; + + // 3. Create and store DELETE statements + $classNames = [...$primaryClass->parentClasses, ...[$primaryClass->name], ...$primaryClass->subClasses]; + foreach (array_reverse($classNames) as $className) { + $tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform); + $this->sqlStatements[] = 'DELETE FROM ' . $tableName + . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; + } + + // 4. Store DDL for temporary identifier table. + $columnDefinitions = []; + foreach ($idColumnNames as $idColumnName) { + $columnDefinitions[$idColumnName] = [ + 'name' => $idColumnName, + 'notnull' => true, + 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $em)), + ]; + } + + $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ', PRIMARY KEY(' . implode(',', $idColumnNames) . '))'; + $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); + } + + /** + * {@inheritDoc} + */ + public function execute(Connection $conn, array $params, array $types): int + { + // Create temporary id table + $conn->executeStatement($this->createTempTableSql); + + try { + // Insert identifiers + $numDeleted = $conn->executeStatement($this->insertSql, $params, $types); + + // Execute DELETE statements + foreach ($this->sqlStatements as $sql) { + $conn->executeStatement($sql); + } + } catch (Throwable $exception) { + // FAILURE! Drop temporary table to avoid possible collisions + $conn->executeStatement($this->dropTempTableSql); + + // Re-throw exception + throw $exception; + } + + // Drop temporary table + $conn->executeStatement($this->dropTempTableSql); + + return $numDeleted; + } +} diff --git a/vendor/doctrine/orm/src/Query/Exec/MultiTableUpdateExecutor.php b/vendor/doctrine/orm/src/Query/Exec/MultiTableUpdateExecutor.php new file mode 100644 index 0000000..dab1b61 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Exec/MultiTableUpdateExecutor.php @@ -0,0 +1,180 @@ +MultiTableUpdateExecutor. + * + * Internal note: Any SQL construction and preparation takes place in the constructor for + * best performance. With a query cache the executor will be cached. + * + * @param UpdateStatement $AST The root AST node of the DQL query. + * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST. + */ + public function __construct(AST\Node $AST, SqlWalker $sqlWalker) + { + $em = $sqlWalker->getEntityManager(); + $conn = $em->getConnection(); + $platform = $conn->getDatabasePlatform(); + $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + $this->sqlStatements = []; + + if ($conn instanceof PrimaryReadReplicaConnection) { + $conn->ensureConnectedToPrimary(); + } + + $updateClause = $AST->updateClause; + $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName); + $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); + + $updateItems = $updateClause->updateItems; + + $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); + $idColumnNames = $rootClass->getIdentifierColumnNames(); + $idColumnList = implode(', ', $idColumnNames); + + // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() + $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable); + + $insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' + . ' SELECT t0.' . implode(', t0.', $idColumnNames); + + $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable); + $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]); + + $insertSql .= $sqlWalker->walkFromClause($fromClause); + + // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect) + $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; + + // 3. Create and store UPDATE statements + $classNames = [...$primaryClass->parentClasses, ...[$primaryClass->name], ...$primaryClass->subClasses]; + + foreach (array_reverse($classNames) as $className) { + $affected = false; + $class = $em->getClassMetadata($className); + $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET '; + + $sqlParameters = []; + foreach ($updateItems as $updateItem) { + $field = $updateItem->pathExpression->field; + + if ( + (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]->inherited)) || + (isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]->inherited)) + ) { + $newValue = $updateItem->newValue; + + if (! $affected) { + $affected = true; + } else { + $updateSql .= ', '; + } + + $updateSql .= $sqlWalker->walkUpdateItem($updateItem); + + if ($newValue instanceof AST\InputParameter) { + $sqlParameters[] = $newValue->name; + + ++$this->numParametersInUpdateClause; + } + } + } + + if ($affected) { + $this->sqlParameters[] = $sqlParameters; + $this->sqlStatements[] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; + } + } + + // Append WHERE clause to insertSql, if there is one. + if ($AST->whereClause) { + $insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); + } + + $this->insertSql = $insertSql; + + // 4. Store DDL for temporary identifier table. + $columnDefinitions = []; + + foreach ($idColumnNames as $idColumnName) { + $columnDefinitions[$idColumnName] = [ + 'name' => $idColumnName, + 'notnull' => true, + 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $em)), + ]; + } + + $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ', PRIMARY KEY(' . implode(',', $idColumnNames) . '))'; + + $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); + } + + /** + * {@inheritDoc} + */ + public function execute(Connection $conn, array $params, array $types): int + { + // Create temporary id table + $conn->executeStatement($this->createTempTableSql); + + try { + // Insert identifiers. Parameters from the update clause are cut off. + $numUpdated = $conn->executeStatement( + $this->insertSql, + array_slice($params, $this->numParametersInUpdateClause), + array_slice($types, $this->numParametersInUpdateClause), + ); + + // Execute UPDATE statements + foreach ($this->sqlStatements as $key => $statement) { + $paramValues = []; + $paramTypes = []; + + if (isset($this->sqlParameters[$key])) { + foreach ($this->sqlParameters[$key] as $parameterKey => $parameterName) { + $paramValues[] = $params[$parameterKey]; + $paramTypes[] = $types[$parameterKey] ?? ParameterTypeInferer::inferType($params[$parameterKey]); + } + } + + $conn->executeStatement($statement, $paramValues, $paramTypes); + } + } finally { + // Drop temporary table + $conn->executeStatement($this->dropTempTableSql); + } + + return $numUpdated; + } +} diff --git a/vendor/doctrine/orm/src/Query/Exec/SingleSelectExecutor.php b/vendor/doctrine/orm/src/Query/Exec/SingleSelectExecutor.php new file mode 100644 index 0000000..5445edb --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Exec/SingleSelectExecutor.php @@ -0,0 +1,31 @@ +sqlStatements = $sqlWalker->walkSelectStatement($AST); + } + + /** + * {@inheritDoc} + */ + public function execute(Connection $conn, array $params, array $types): Result + { + return $conn->executeQuery($this->sqlStatements, $params, $types, $this->queryCacheProfile); + } +} diff --git a/vendor/doctrine/orm/src/Query/Exec/SingleTableDeleteUpdateExecutor.php b/vendor/doctrine/orm/src/Query/Exec/SingleTableDeleteUpdateExecutor.php new file mode 100644 index 0000000..66696db --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Exec/SingleTableDeleteUpdateExecutor.php @@ -0,0 +1,42 @@ +sqlStatements = $sqlWalker->walkUpdateStatement($AST); + } elseif ($AST instanceof AST\DeleteStatement) { + $this->sqlStatements = $sqlWalker->walkDeleteStatement($AST); + } + } + + /** + * {@inheritDoc} + */ + public function execute(Connection $conn, array $params, array $types): int + { + if ($conn instanceof PrimaryReadReplicaConnection) { + $conn->ensureConnectedToPrimary(); + } + + return $conn->executeStatement($this->sqlStatements, $params, $types); + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr.php b/vendor/doctrine/orm/src/Query/Expr.php new file mode 100644 index 0000000..65f3082 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr.php @@ -0,0 +1,615 @@ +andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2')); + * + * @param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x Optional clause. Defaults to null, + * but requires at least one defined + * when converting to string. + */ + public function andX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Andx + { + self::validateVariadicParameter($x); + + return new Expr\Andx($x); + } + + /** + * Creates a disjunction of the given boolean expressions. + * + * Example: + * + * [php] + * // (u.type = ?1) OR (u.role = ?2) + * $q->where($q->expr()->orX('u.type = ?1', 'u.role = ?2')); + * + * @param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x Optional clause. Defaults to null, + * but requires at least one defined + * when converting to string. + */ + public function orX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Orx + { + self::validateVariadicParameter($x); + + return new Expr\Orx($x); + } + + /** + * Creates an ASCending order expression. + */ + public function asc(mixed $expr): Expr\OrderBy + { + return new Expr\OrderBy($expr, 'ASC'); + } + + /** + * Creates a DESCending order expression. + */ + public function desc(mixed $expr): Expr\OrderBy + { + return new Expr\OrderBy($expr, 'DESC'); + } + + /** + * Creates an equality comparison expression with the given arguments. + * + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a = . Example: + * + * [php] + * // u.id = ?1 + * $expr->eq('u.id', '?1'); + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function eq(mixed $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, Expr\Comparison::EQ, $y); + } + + /** + * Creates an instance of Expr\Comparison, with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <> . Example: + * + * [php] + * // u.id <> ?1 + * $q->where($q->expr()->neq('u.id', '?1')); + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function neq(mixed $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, Expr\Comparison::NEQ, $y); + } + + /** + * Creates an instance of Expr\Comparison, with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a < . Example: + * + * [php] + * // u.id < ?1 + * $q->where($q->expr()->lt('u.id', '?1')); + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function lt(mixed $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, Expr\Comparison::LT, $y); + } + + /** + * Creates an instance of Expr\Comparison, with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <= . Example: + * + * [php] + * // u.id <= ?1 + * $q->where($q->expr()->lte('u.id', '?1')); + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function lte(mixed $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, Expr\Comparison::LTE, $y); + } + + /** + * Creates an instance of Expr\Comparison, with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a > . Example: + * + * [php] + * // u.id > ?1 + * $q->where($q->expr()->gt('u.id', '?1')); + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function gt(mixed $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, Expr\Comparison::GT, $y); + } + + /** + * Creates an instance of Expr\Comparison, with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a >= . Example: + * + * [php] + * // u.id >= ?1 + * $q->where($q->expr()->gte('u.id', '?1')); + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function gte(mixed $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, Expr\Comparison::GTE, $y); + } + + /** + * Creates an instance of AVG() function, with the given argument. + * + * @param mixed $x Argument to be used in AVG() function. + */ + public function avg(mixed $x): Expr\Func + { + return new Expr\Func('AVG', [$x]); + } + + /** + * Creates an instance of MAX() function, with the given argument. + * + * @param mixed $x Argument to be used in MAX() function. + */ + public function max(mixed $x): Expr\Func + { + return new Expr\Func('MAX', [$x]); + } + + /** + * Creates an instance of MIN() function, with the given argument. + * + * @param mixed $x Argument to be used in MIN() function. + */ + public function min(mixed $x): Expr\Func + { + return new Expr\Func('MIN', [$x]); + } + + /** + * Creates an instance of COUNT() function, with the given argument. + * + * @param mixed $x Argument to be used in COUNT() function. + */ + public function count(mixed $x): Expr\Func + { + return new Expr\Func('COUNT', [$x]); + } + + /** + * Creates an instance of COUNT(DISTINCT) function, with the given argument. + * + * @param mixed ...$x Argument to be used in COUNT(DISTINCT) function. + */ + public function countDistinct(mixed ...$x): string + { + self::validateVariadicParameter($x); + + return 'COUNT(DISTINCT ' . implode(', ', $x) . ')'; + } + + /** + * Creates an instance of EXISTS() function, with the given DQL Subquery. + * + * @param mixed $subquery DQL Subquery to be used in EXISTS() function. + */ + public function exists(mixed $subquery): Expr\Func + { + return new Expr\Func('EXISTS', [$subquery]); + } + + /** + * Creates an instance of ALL() function, with the given DQL Subquery. + * + * @param mixed $subquery DQL Subquery to be used in ALL() function. + */ + public function all(mixed $subquery): Expr\Func + { + return new Expr\Func('ALL', [$subquery]); + } + + /** + * Creates a SOME() function expression with the given DQL subquery. + * + * @param mixed $subquery DQL Subquery to be used in SOME() function. + */ + public function some(mixed $subquery): Expr\Func + { + return new Expr\Func('SOME', [$subquery]); + } + + /** + * Creates an ANY() function expression with the given DQL subquery. + * + * @param mixed $subquery DQL Subquery to be used in ANY() function. + */ + public function any(mixed $subquery): Expr\Func + { + return new Expr\Func('ANY', [$subquery]); + } + + /** + * Creates a negation expression of the given restriction. + * + * @param mixed $restriction Restriction to be used in NOT() function. + */ + public function not(mixed $restriction): Expr\Func + { + return new Expr\Func('NOT', [$restriction]); + } + + /** + * Creates an ABS() function expression with the given argument. + * + * @param mixed $x Argument to be used in ABS() function. + */ + public function abs(mixed $x): Expr\Func + { + return new Expr\Func('ABS', [$x]); + } + + /** + * Creates a MOD($x, $y) function expression to return the remainder of $x divided by $y. + */ + public function mod(mixed $x, mixed $y): Expr\Func + { + return new Expr\Func('MOD', [$x, $y]); + } + + /** + * Creates a product mathematical expression with the given arguments. + * + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a * . Example: + * + * [php] + * // u.salary * u.percentAnnualSalaryIncrease + * $q->expr()->prod('u.salary', 'u.percentAnnualSalaryIncrease') + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function prod(mixed $x, mixed $y): Expr\Math + { + return new Expr\Math($x, '*', $y); + } + + /** + * Creates a difference mathematical expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a - . Example: + * + * [php] + * // u.monthlySubscriptionCount - 1 + * $q->expr()->diff('u.monthlySubscriptionCount', '1') + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function diff(mixed $x, mixed $y): Expr\Math + { + return new Expr\Math($x, '-', $y); + } + + /** + * Creates a sum mathematical expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a + . Example: + * + * [php] + * // u.numChildren + 1 + * $q->expr()->sum('u.numChildren', '1') + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function sum(mixed $x, mixed $y): Expr\Math + { + return new Expr\Math($x, '+', $y); + } + + /** + * Creates a quotient mathematical expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a / . Example: + * + * [php] + * // u.total / u.period + * $expr->quot('u.total', 'u.period') + * + * @param mixed $x Left expression. + * @param mixed $y Right expression. + */ + public function quot(mixed $x, mixed $y): Expr\Math + { + return new Expr\Math($x, '/', $y); + } + + /** + * Creates a SQRT() function expression with the given argument. + * + * @param mixed $x Argument to be used in SQRT() function. + */ + public function sqrt(mixed $x): Expr\Func + { + return new Expr\Func('SQRT', [$x]); + } + + /** + * Creates an IN() expression with the given arguments. + * + * @param string $x Field in string format to be restricted by IN() function. + * @param mixed $y Argument to be used in IN() function. + */ + public function in(string $x, mixed $y): Expr\Func + { + if (is_iterable($y)) { + if ($y instanceof Traversable) { + $y = iterator_to_array($y); + } + + foreach ($y as &$literal) { + if (! ($literal instanceof Expr\Literal)) { + $literal = $this->quoteLiteral($literal); + } + } + } + + return new Expr\Func($x . ' IN', (array) $y); + } + + /** + * Creates a NOT IN() expression with the given arguments. + * + * @param string $x Field in string format to be restricted by NOT IN() function. + * @param mixed $y Argument to be used in NOT IN() function. + */ + public function notIn(string $x, mixed $y): Expr\Func + { + if (is_iterable($y)) { + if ($y instanceof Traversable) { + $y = iterator_to_array($y); + } + + foreach ($y as &$literal) { + if (! ($literal instanceof Expr\Literal)) { + $literal = $this->quoteLiteral($literal); + } + } + } + + return new Expr\Func($x . ' NOT IN', (array) $y); + } + + /** + * Creates an IS NULL expression with the given arguments. + * + * @param string $x Field in string format to be restricted by IS NULL. + */ + public function isNull(string $x): string + { + return $x . ' IS NULL'; + } + + /** + * Creates an IS NOT NULL expression with the given arguments. + * + * @param string $x Field in string format to be restricted by IS NOT NULL. + */ + public function isNotNull(string $x): string + { + return $x . ' IS NOT NULL'; + } + + /** + * Creates a LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by LIKE() comparison. + * @param mixed $y Argument to be used in LIKE() comparison. + */ + public function like(string $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, 'LIKE', $y); + } + + /** + * Creates a NOT LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by LIKE() comparison. + * @param mixed $y Argument to be used in LIKE() comparison. + */ + public function notLike(string $x, mixed $y): Expr\Comparison + { + return new Expr\Comparison($x, 'NOT LIKE', $y); + } + + /** + * Creates a CONCAT() function expression with the given arguments. + * + * @param mixed ...$x Arguments to be used in CONCAT() function. + */ + public function concat(mixed ...$x): Expr\Func + { + self::validateVariadicParameter($x); + + return new Expr\Func('CONCAT', $x); + } + + /** + * Creates a SUBSTRING() function expression with the given arguments. + * + * @param mixed $x Argument to be used as string to be cropped by SUBSTRING() function. + * @param int $from Initial offset to start cropping string. May accept negative values. + * @param int|null $len Length of crop. May accept negative values. + */ + public function substring(mixed $x, int $from, int|null $len = null): Expr\Func + { + $args = [$x, $from]; + if ($len !== null) { + $args[] = $len; + } + + return new Expr\Func('SUBSTRING', $args); + } + + /** + * Creates a LOWER() function expression with the given argument. + * + * @param mixed $x Argument to be used in LOWER() function. + * + * @return Expr\Func A LOWER function expression. + */ + public function lower(mixed $x): Expr\Func + { + return new Expr\Func('LOWER', [$x]); + } + + /** + * Creates an UPPER() function expression with the given argument. + * + * @param mixed $x Argument to be used in UPPER() function. + * + * @return Expr\Func An UPPER function expression. + */ + public function upper(mixed $x): Expr\Func + { + return new Expr\Func('UPPER', [$x]); + } + + /** + * Creates a LENGTH() function expression with the given argument. + * + * @param mixed $x Argument to be used as argument of LENGTH() function. + * + * @return Expr\Func A LENGTH function expression. + */ + public function length(mixed $x): Expr\Func + { + return new Expr\Func('LENGTH', [$x]); + } + + /** + * Creates a literal expression of the given argument. + * + * @param scalar $literal Argument to be converted to literal. + */ + public function literal(bool|string|int|float $literal): Expr\Literal + { + return new Expr\Literal($this->quoteLiteral($literal)); + } + + /** + * Quotes a literal value, if necessary, according to the DQL syntax. + * + * @param scalar $literal The literal value. + */ + private function quoteLiteral(bool|string|int|float $literal): string + { + if (is_int($literal) || is_float($literal)) { + return (string) $literal; + } + + if (is_bool($literal)) { + return $literal ? 'true' : 'false'; + } + + return "'" . str_replace("'", "''", $literal) . "'"; + } + + /** + * Creates an instance of BETWEEN() function, with the given argument. + * + * @param mixed $val Valued to be inspected by range values. + * @param int|string $x Starting range value to be used in BETWEEN() function. + * @param int|string $y End point value to be used in BETWEEN() function. + * + * @return string A BETWEEN expression. + */ + public function between(mixed $val, int|string $x, int|string $y): string + { + return $val . ' BETWEEN ' . $x . ' AND ' . $y; + } + + /** + * Creates an instance of TRIM() function, with the given argument. + * + * @param mixed $x Argument to be used as argument of TRIM() function. + * + * @return Expr\Func a TRIM expression. + */ + public function trim(mixed $x): Expr\Func + { + return new Expr\Func('TRIM', $x); + } + + /** + * Creates an instance of MEMBER OF function, with the given arguments. + * + * @param string $x Value to be checked + * @param string $y Value to be checked against + */ + public function isMemberOf(string $x, string $y): Expr\Comparison + { + return new Expr\Comparison($x, 'MEMBER OF', $y); + } + + /** + * Creates an instance of INSTANCE OF function, with the given arguments. + * + * @param string $x Value to be checked + * @param string $y Value to be checked against + */ + public function isInstanceOf(string $x, string $y): Expr\Comparison + { + return new Expr\Comparison($x, 'INSTANCE OF', $y); + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Andx.php b/vendor/doctrine/orm/src/Query/Expr/Andx.php new file mode 100644 index 0000000..a20bcef --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Andx.php @@ -0,0 +1,32 @@ + */ + protected array $parts = []; + + /** @psalm-return list */ + public function getParts(): array + { + return $this->parts; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Base.php b/vendor/doctrine/orm/src/Query/Expr/Base.php new file mode 100644 index 0000000..e0f2572 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Base.php @@ -0,0 +1,96 @@ + */ + protected array $allowedClasses = []; + + /** @var list */ + protected array $parts = []; + + public function __construct(mixed $args = []) + { + if (is_array($args) && array_key_exists(0, $args) && is_array($args[0])) { + $args = $args[0]; + } + + $this->addMultiple($args); + } + + /** + * @param string[]|object[]|string|object $args + * @psalm-param list|string|object $args + * + * @return $this + */ + public function addMultiple(array|string|object $args = []): static + { + foreach ((array) $args as $arg) { + $this->add($arg); + } + + return $this; + } + + /** + * @return $this + * + * @throws InvalidArgumentException + */ + public function add(mixed $arg): static + { + if ($arg !== null && (! $arg instanceof self || $arg->count() > 0)) { + // If we decide to keep Expr\Base instances, we can use this check + if (! is_string($arg) && ! in_array($arg::class, $this->allowedClasses, true)) { + throw new InvalidArgumentException(sprintf( + "Expression of type '%s' not allowed in this context.", + get_debug_type($arg), + )); + } + + $this->parts[] = $arg; + } + + return $this; + } + + /** @psalm-return 0|positive-int */ + public function count(): int + { + return count($this->parts); + } + + public function __toString(): string + { + if ($this->count() === 1) { + return (string) $this->parts[0]; + } + + return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Comparison.php b/vendor/doctrine/orm/src/Query/Expr/Comparison.php new file mode 100644 index 0000000..ec8ef21 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Comparison.php @@ -0,0 +1,47 @@ +'; + final public const LT = '<'; + final public const LTE = '<='; + final public const GT = '>'; + final public const GTE = '>='; + + /** Creates a comparison expression with the given arguments. */ + public function __construct(protected mixed $leftExpr, protected string $operator, protected mixed $rightExpr) + { + } + + public function getLeftExpr(): mixed + { + return $this->leftExpr; + } + + public function getOperator(): string + { + return $this->operator; + } + + public function getRightExpr(): mixed + { + return $this->rightExpr; + } + + public function __toString(): string + { + return $this->leftExpr . ' ' . $this->operator . ' ' . $this->rightExpr; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Composite.php b/vendor/doctrine/orm/src/Query/Expr/Composite.php new file mode 100644 index 0000000..f3007a7 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Composite.php @@ -0,0 +1,50 @@ +count() === 1) { + return (string) $this->parts[0]; + } + + $components = []; + + foreach ($this->parts as $part) { + $components[] = $this->processQueryPart($part); + } + + return implode($this->separator, $components); + } + + private function processQueryPart(string|Stringable $part): string + { + $queryPart = (string) $part; + + if (is_object($part) && $part instanceof self && $part->count() > 1) { + return $this->preSeparator . $queryPart . $this->postSeparator; + } + + // Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND") + if (preg_match('/\s(OR|AND)\s/i', $queryPart)) { + return $this->preSeparator . $queryPart . $this->postSeparator; + } + + return $queryPart; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/From.php b/vendor/doctrine/orm/src/Query/Expr/From.php new file mode 100644 index 0000000..21af078 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/From.php @@ -0,0 +1,48 @@ +from; + } + + public function getAlias(): string + { + return $this->alias; + } + + public function getIndexBy(): string|null + { + return $this->indexBy; + } + + public function __toString(): string + { + return $this->from . ' ' . $this->alias . + ($this->indexBy ? ' INDEX BY ' . $this->indexBy : ''); + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Func.php b/vendor/doctrine/orm/src/Query/Expr/Func.php new file mode 100644 index 0000000..cd9e8e0 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Func.php @@ -0,0 +1,48 @@ +|mixed $arguments + */ + public function __construct( + protected string $name, + mixed $arguments, + ) { + $this->arguments = (array) $arguments; + } + + public function getName(): string + { + return $this->name; + } + + /** @psalm-return list */ + public function getArguments(): array + { + return $this->arguments; + } + + public function __toString(): string + { + return $this->name . '(' . implode(', ', $this->arguments) . ')'; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/GroupBy.php b/vendor/doctrine/orm/src/Query/Expr/GroupBy.php new file mode 100644 index 0000000..fa4625a --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/GroupBy.php @@ -0,0 +1,25 @@ + */ + protected array $parts = []; + + /** @psalm-return list */ + public function getParts(): array + { + return $this->parts; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Join.php b/vendor/doctrine/orm/src/Query/Expr/Join.php new file mode 100644 index 0000000..c3b6dc9 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Join.php @@ -0,0 +1,77 @@ +joinType; + } + + public function getJoin(): string + { + return $this->join; + } + + public function getAlias(): string|null + { + return $this->alias; + } + + /** @psalm-return self::ON|self::WITH|null */ + public function getConditionType(): string|null + { + return $this->conditionType; + } + + public function getCondition(): string|Comparison|Composite|Func|null + { + return $this->condition; + } + + public function getIndexBy(): string|null + { + return $this->indexBy; + } + + public function __toString(): string + { + return strtoupper($this->joinType) . ' JOIN ' . $this->join + . ($this->alias ? ' ' . $this->alias : '') + . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : '') + . ($this->condition ? ' ' . strtoupper($this->conditionType) . ' ' . $this->condition : ''); + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Literal.php b/vendor/doctrine/orm/src/Query/Expr/Literal.php new file mode 100644 index 0000000..0c13030 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Literal.php @@ -0,0 +1,25 @@ + */ + protected array $parts = []; + + /** @psalm-return list */ + public function getParts(): array + { + return $this->parts; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Math.php b/vendor/doctrine/orm/src/Query/Expr/Math.php new file mode 100644 index 0000000..05e0b39 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Math.php @@ -0,0 +1,59 @@ +leftExpr; + } + + public function getOperator(): string + { + return $this->operator; + } + + public function getRightExpr(): mixed + { + return $this->rightExpr; + } + + public function __toString(): string + { + // Adjusting Left Expression + $leftExpr = (string) $this->leftExpr; + + if ($this->leftExpr instanceof Math) { + $leftExpr = '(' . $leftExpr . ')'; + } + + // Adjusting Right Expression + $rightExpr = (string) $this->rightExpr; + + if ($this->rightExpr instanceof Math) { + $rightExpr = '(' . $rightExpr . ')'; + } + + return $leftExpr . ' ' . $this->operator . ' ' . $rightExpr; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/OrderBy.php b/vendor/doctrine/orm/src/Query/Expr/OrderBy.php new file mode 100644 index 0000000..ac9e160 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/OrderBy.php @@ -0,0 +1,60 @@ + */ + protected array $parts = []; + + public function __construct( + string|null $sort = null, + string|null $order = null, + ) { + if ($sort) { + $this->add($sort, $order); + } + } + + public function add(string $sort, string|null $order = null): void + { + $order = ! $order ? 'ASC' : $order; + $this->parts[] = $sort . ' ' . $order; + } + + /** @psalm-return 0|positive-int */ + public function count(): int + { + return count($this->parts); + } + + /** @psalm-return list */ + public function getParts(): array + { + return $this->parts; + } + + public function __toString(): string + { + return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Orx.php b/vendor/doctrine/orm/src/Query/Expr/Orx.php new file mode 100644 index 0000000..2ae2332 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Orx.php @@ -0,0 +1,32 @@ + */ + protected array $parts = []; + + /** @psalm-return list */ + public function getParts(): array + { + return $this->parts; + } +} diff --git a/vendor/doctrine/orm/src/Query/Expr/Select.php b/vendor/doctrine/orm/src/Query/Expr/Select.php new file mode 100644 index 0000000..91b0b60 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Expr/Select.php @@ -0,0 +1,28 @@ + */ + protected array $parts = []; + + /** @psalm-return list */ + public function getParts(): array + { + return $this->parts; + } +} diff --git a/vendor/doctrine/orm/src/Query/Filter/FilterException.php b/vendor/doctrine/orm/src/Query/Filter/FilterException.php new file mode 100644 index 0000000..37f12bc --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Filter/FilterException.php @@ -0,0 +1,23 @@ + + */ + private array $parameters = []; + + final public function __construct( + private readonly EntityManagerInterface $em, + ) { + } + + /** + * Sets a parameter list that can be used by the filter. + * + * @param array $values List of parameter values. + * @param string $type The parameter type. If specified, the given value will be run through + * the type conversion of this type. + * + * @return $this + */ + final public function setParameterList(string $name, array $values, string $type = Types::STRING): static + { + $this->parameters[$name] = ['value' => $values, 'type' => $type, 'is_list' => true]; + + // Keep the parameters sorted for the hash + ksort($this->parameters); + + // The filter collection of the EM is now dirty + $this->em->getFilters()->setFiltersStateDirty(); + + return $this; + } + + /** + * Sets a parameter that can be used by the filter. + * + * @param string|null $type The parameter type. If specified, the given value will be run through + * the type conversion of this type. This is usually not needed for + * strings and numeric types. + * + * @return $this + */ + final public function setParameter(string $name, mixed $value, string|null $type = null): static + { + if ($type === null) { + $type = ParameterTypeInferer::inferType($value); + } + + $this->parameters[$name] = ['value' => $value, 'type' => $type, 'is_list' => false]; + + // Keep the parameters sorted for the hash + ksort($this->parameters); + + // The filter collection of the EM is now dirty + $this->em->getFilters()->setFiltersStateDirty(); + + return $this; + } + + /** + * Gets a parameter to use in a query. + * + * The function is responsible for the right output escaping to use the + * value in a query. + * + * @return string The SQL escaped parameter to use in a query. + * + * @throws InvalidArgumentException + */ + final public function getParameter(string $name): string + { + if (! isset($this->parameters[$name])) { + throw new InvalidArgumentException("Parameter '" . $name . "' does not exist."); + } + + if ($this->parameters[$name]['is_list']) { + throw FilterException::cannotConvertListParameterIntoSingleValue($name); + } + + return $this->em->getConnection()->quote((string) $this->parameters[$name]['value']); + } + + /** + * Gets a parameter to use in a query assuming it's a list of entries. + * + * The function is responsible for the right output escaping to use the + * value in a query, separating each entry by comma to inline it into + * an IN() query part. + * + * @throws InvalidArgumentException + */ + final public function getParameterList(string $name): string + { + if (! isset($this->parameters[$name])) { + throw new InvalidArgumentException("Parameter '" . $name . "' does not exist."); + } + + if ($this->parameters[$name]['is_list'] === false) { + throw FilterException::cannotConvertSingleParameterIntoListValue($name); + } + + $param = $this->parameters[$name]; + $connection = $this->em->getConnection(); + + $quoted = array_map( + static fn (mixed $value): string => $connection->quote((string) $value), + $param['value'], + ); + + return implode(',', $quoted); + } + + /** + * Checks if a parameter was set for the filter. + */ + final public function hasParameter(string $name): bool + { + return isset($this->parameters[$name]); + } + + /** + * Returns as string representation of the SQLFilter parameters (the state). + */ + final public function __toString(): string + { + return serialize($this->parameters); + } + + /** + * Returns the database connection used by the entity manager + */ + final protected function getConnection(): Connection + { + return $this->em->getConnection(); + } + + /** + * Gets the SQL query part to add to a query. + * + * @psalm-param ClassMetadata $targetEntity + * + * @return string The constraint SQL if there is available, empty string otherwise. + */ + abstract public function addFilterConstraint(ClassMetadata $targetEntity, string $targetTableAlias): string; +} diff --git a/vendor/doctrine/orm/src/Query/FilterCollection.php b/vendor/doctrine/orm/src/Query/FilterCollection.php new file mode 100644 index 0000000..3d3c576 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/FilterCollection.php @@ -0,0 +1,260 @@ + + */ + private array $enabledFilters = []; + + /** The filter hash from the last time the query was parsed. */ + private string $filterHash = ''; + + /** + * Instances of suspended filters. + * + * @var SQLFilter[] + * @psalm-var array + */ + private array $suspendedFilters = []; + + /** + * The current state of this filter. + * + * @psalm-var self::FILTERS_STATE_* + */ + private int $filtersState = self::FILTERS_STATE_CLEAN; + + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->config = $em->getConfiguration(); + } + + /** + * Gets all the enabled filters. + * + * @return array The enabled filters. + */ + public function getEnabledFilters(): array + { + return $this->enabledFilters; + } + + /** + * Gets all the suspended filters. + * + * @return SQLFilter[] The suspended filters. + * @psalm-return array + */ + public function getSuspendedFilters(): array + { + return $this->suspendedFilters; + } + + /** + * Enables a filter from the collection. + * + * @throws InvalidArgumentException If the filter does not exist. + */ + public function enable(string $name): SQLFilter + { + if (! $this->has($name)) { + throw new InvalidArgumentException("Filter '" . $name . "' does not exist."); + } + + if (! $this->isEnabled($name)) { + $filterClass = $this->config->getFilterClassName($name); + + assert($filterClass !== null); + + $this->enabledFilters[$name] = new $filterClass($this->em); + + // In case a suspended filter with the same name was forgotten + unset($this->suspendedFilters[$name]); + + // Keep the enabled filters sorted for the hash + ksort($this->enabledFilters); + + $this->setFiltersStateDirty(); + } + + return $this->enabledFilters[$name]; + } + + /** + * Disables a filter. + * + * @throws InvalidArgumentException If the filter does not exist. + */ + public function disable(string $name): SQLFilter + { + // Get the filter to return it + $filter = $this->getFilter($name); + + unset($this->enabledFilters[$name]); + + $this->setFiltersStateDirty(); + + return $filter; + } + + /** + * Suspend a filter. + * + * @param string $name Name of the filter. + * + * @return SQLFilter The suspended filter. + * + * @throws InvalidArgumentException If the filter does not exist. + */ + public function suspend(string $name): SQLFilter + { + // Get the filter to return it + $filter = $this->getFilter($name); + + $this->suspendedFilters[$name] = $filter; + unset($this->enabledFilters[$name]); + + $this->setFiltersStateDirty(); + + return $filter; + } + + /** + * Restore a disabled filter from the collection. + * + * @param string $name Name of the filter. + * + * @return SQLFilter The restored filter. + * + * @throws InvalidArgumentException If the filter does not exist. + */ + public function restore(string $name): SQLFilter + { + if (! $this->isSuspended($name)) { + throw new InvalidArgumentException("Filter '" . $name . "' is not suspended."); + } + + $this->enabledFilters[$name] = $this->suspendedFilters[$name]; + unset($this->suspendedFilters[$name]); + + // Keep the enabled filters sorted for the hash + ksort($this->enabledFilters); + + $this->setFiltersStateDirty(); + + return $this->enabledFilters[$name]; + } + + /** + * Gets an enabled filter from the collection. + * + * @throws InvalidArgumentException If the filter is not enabled. + */ + public function getFilter(string $name): SQLFilter + { + if (! $this->isEnabled($name)) { + throw new InvalidArgumentException("Filter '" . $name . "' is not enabled."); + } + + return $this->enabledFilters[$name]; + } + + /** + * Checks whether filter with given name is defined. + */ + public function has(string $name): bool + { + return $this->config->getFilterClassName($name) !== null; + } + + /** + * Checks if a filter is enabled. + */ + public function isEnabled(string $name): bool + { + return isset($this->enabledFilters[$name]); + } + + /** + * Checks if a filter is suspended. + * + * @param string $name Name of the filter. + * + * @return bool True if the filter is suspended, false otherwise. + */ + public function isSuspended(string $name): bool + { + return isset($this->suspendedFilters[$name]); + } + + /** + * Checks if the filter collection is clean. + */ + public function isClean(): bool + { + return $this->filtersState === self::FILTERS_STATE_CLEAN; + } + + /** + * Generates a string of currently enabled filters to use for the cache id. + */ + public function getHash(): string + { + // If there are only clean filters, the previous hash can be returned + if ($this->filtersState === self::FILTERS_STATE_CLEAN) { + return $this->filterHash; + } + + $filterHash = ''; + + foreach ($this->enabledFilters as $name => $filter) { + $filterHash .= $name . $filter; + } + + $this->filterHash = $filterHash; + $this->filtersState = self::FILTERS_STATE_CLEAN; + + return $filterHash; + } + + /** + * Sets the filter state to dirty. + */ + public function setFiltersStateDirty(): void + { + $this->filtersState = self::FILTERS_STATE_DIRTY; + } +} diff --git a/vendor/doctrine/orm/src/Query/Lexer.php b/vendor/doctrine/orm/src/Query/Lexer.php new file mode 100644 index 0000000..c446675 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Lexer.php @@ -0,0 +1,150 @@ + + */ +class Lexer extends AbstractLexer +{ + /** + * Creates a new query scanner object. + * + * @param string $input A query string. + */ + public function __construct(string $input) + { + $this->setInput($input); + } + + /** + * {@inheritDoc} + */ + protected function getCatchablePatterns(): array + { + return [ + '[a-z_][a-z0-9_]*\:[a-z_][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // aliased name + '[a-z_\\\][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // identifier or qualified name + '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers + "'(?:[^']|'')*'", // quoted strings + '\?[0-9]*|:[a-z_][a-z0-9_]*', // parameters + ]; + } + + /** + * {@inheritDoc} + */ + protected function getNonCatchablePatterns(): array + { + return ['\s+', '--.*', '(.)']; + } + + protected function getType(string &$value): TokenType + { + $type = TokenType::T_NONE; + + switch (true) { + // Recognize numeric values + case is_numeric($value): + if (str_contains($value, '.') || stripos($value, 'e') !== false) { + return TokenType::T_FLOAT; + } + + return TokenType::T_INTEGER; + + // Recognize quoted strings + case $value[0] === "'": + $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); + + return TokenType::T_STRING; + + // Recognize identifiers, aliased or qualified names + case ctype_alpha($value[0]) || $value[0] === '_' || $value[0] === '\\': + $name = 'Doctrine\ORM\Query\TokenType::T_' . strtoupper($value); + + if (defined($name)) { + $type = constant($name); + + if ($type->value > 100) { + return $type; + } + } + + if (str_contains($value, '\\')) { + return TokenType::T_FULLY_QUALIFIED_NAME; + } + + return TokenType::T_IDENTIFIER; + + // Recognize input parameters + case $value[0] === '?' || $value[0] === ':': + return TokenType::T_INPUT_PARAMETER; + + // Recognize symbols + case $value === '.': + return TokenType::T_DOT; + + case $value === ',': + return TokenType::T_COMMA; + + case $value === '(': + return TokenType::T_OPEN_PARENTHESIS; + + case $value === ')': + return TokenType::T_CLOSE_PARENTHESIS; + + case $value === '=': + return TokenType::T_EQUALS; + + case $value === '>': + return TokenType::T_GREATER_THAN; + + case $value === '<': + return TokenType::T_LOWER_THAN; + + case $value === '+': + return TokenType::T_PLUS; + + case $value === '-': + return TokenType::T_MINUS; + + case $value === '*': + return TokenType::T_MULTIPLY; + + case $value === '/': + return TokenType::T_DIVIDE; + + case $value === '!': + return TokenType::T_NEGATE; + + case $value === '{': + return TokenType::T_OPEN_CURLY_BRACE; + + case $value === '}': + return TokenType::T_CLOSE_CURLY_BRACE; + + // Default + default: + // Do nothing + } + + return $type; + } +} diff --git a/vendor/doctrine/orm/src/Query/Parameter.php b/vendor/doctrine/orm/src/Query/Parameter.php new file mode 100644 index 0000000..43eb7a4 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Parameter.php @@ -0,0 +1,89 @@ +name = self::normalizeName($name); + $this->typeSpecified = $type !== null; + + $this->setValue($value, $type); + } + + /** + * Retrieves the Parameter name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Retrieves the Parameter value. + */ + public function getValue(): mixed + { + return $this->value; + } + + /** + * Retrieves the Parameter type. + */ + public function getType(): mixed + { + return $this->type; + } + + /** + * Defines the Parameter value. + */ + public function setValue(mixed $value, mixed $type = null): void + { + $this->value = $value; + $this->type = $type ?: ParameterTypeInferer::inferType($value); + } + + public function typeWasSpecified(): bool + { + return $this->typeSpecified; + } +} diff --git a/vendor/doctrine/orm/src/Query/ParameterTypeInferer.php b/vendor/doctrine/orm/src/Query/ParameterTypeInferer.php new file mode 100644 index 0000000..dae28fa --- /dev/null +++ b/vendor/doctrine/orm/src/Query/ParameterTypeInferer.php @@ -0,0 +1,77 @@ +value) + ? Types::INTEGER + : Types::STRING; + } + + if (is_array($value)) { + $firstValue = current($value); + if ($firstValue instanceof BackedEnum) { + $firstValue = $firstValue->value; + } + + return is_int($firstValue) + ? ArrayParameterType::INTEGER + : ArrayParameterType::STRING; + } + + return ParameterType::STRING; + } + + private function __construct() + { + } +} diff --git a/vendor/doctrine/orm/src/Query/Parser.php b/vendor/doctrine/orm/src/Query/Parser.php new file mode 100644 index 0000000..e948f2c --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Parser.php @@ -0,0 +1,3269 @@ + + * @psalm-type QueryComponent = array{ + * metadata?: ClassMetadata, + * parent?: string|null, + * relation?: AssociationMapping|null, + * map?: string|null, + * resultVariable?: AST\Node|string, + * nestingLevel: int, + * token: DqlToken, + * } + */ +final class Parser +{ + /** + * @readonly Maps BUILT-IN string function names to AST class names. + * @psalm-var array> + */ + private static array $stringFunctions = [ + 'concat' => Functions\ConcatFunction::class, + 'substring' => Functions\SubstringFunction::class, + 'trim' => Functions\TrimFunction::class, + 'lower' => Functions\LowerFunction::class, + 'upper' => Functions\UpperFunction::class, + 'identity' => Functions\IdentityFunction::class, + ]; + + /** + * @readonly Maps BUILT-IN numeric function names to AST class names. + * @psalm-var array> + */ + private static array $numericFunctions = [ + 'length' => Functions\LengthFunction::class, + 'locate' => Functions\LocateFunction::class, + 'abs' => Functions\AbsFunction::class, + 'sqrt' => Functions\SqrtFunction::class, + 'mod' => Functions\ModFunction::class, + 'size' => Functions\SizeFunction::class, + 'date_diff' => Functions\DateDiffFunction::class, + 'bit_and' => Functions\BitAndFunction::class, + 'bit_or' => Functions\BitOrFunction::class, + + // Aggregate functions + 'min' => Functions\MinFunction::class, + 'max' => Functions\MaxFunction::class, + 'avg' => Functions\AvgFunction::class, + 'sum' => Functions\SumFunction::class, + 'count' => Functions\CountFunction::class, + ]; + + /** + * @readonly Maps BUILT-IN datetime function names to AST class names. + * @psalm-var array> + */ + private static array $datetimeFunctions = [ + 'current_date' => Functions\CurrentDateFunction::class, + 'current_time' => Functions\CurrentTimeFunction::class, + 'current_timestamp' => Functions\CurrentTimestampFunction::class, + 'date_add' => Functions\DateAddFunction::class, + 'date_sub' => Functions\DateSubFunction::class, + ]; + + /* + * Expressions that were encountered during parsing of identifiers and expressions + * and still need to be validated. + */ + + /** @psalm-var list */ + private array $deferredIdentificationVariables = []; + + /** @psalm-var list */ + private array $deferredPathExpressions = []; + + /** @psalm-var list */ + private array $deferredResultVariables = []; + + /** @psalm-var list */ + private array $deferredNewObjectExpressions = []; + + /** + * The lexer. + */ + private readonly Lexer $lexer; + + /** + * The parser result. + */ + private readonly ParserResult $parserResult; + + /** + * The EntityManager. + */ + private readonly EntityManagerInterface $em; + + /** + * Map of declared query components in the parsed query. + * + * @psalm-var array + */ + private array $queryComponents = []; + + /** + * Keeps the nesting level of defined ResultVariables. + */ + private int $nestingLevel = 0; + + /** + * Any additional custom tree walkers that modify the AST. + * + * @psalm-var list> + */ + private array $customTreeWalkers = []; + + /** + * The custom last tree walker, if any, that is responsible for producing the output. + * + * @var class-string|null + */ + private $customOutputWalker; + + /** @psalm-var array */ + private array $identVariableExpressions = []; + + /** + * Creates a new query parser object. + * + * @param Query $query The Query to parse. + */ + public function __construct(private readonly Query $query) + { + $this->em = $query->getEntityManager(); + $this->lexer = new Lexer((string) $query->getDQL()); + $this->parserResult = new ParserResult(); + } + + /** + * Sets a custom tree walker that produces output. + * This tree walker will be run last over the AST, after any other walkers. + * + * @psalm-param class-string $className + */ + public function setCustomOutputTreeWalker(string $className): void + { + $this->customOutputWalker = $className; + } + + /** + * Adds a custom tree walker for modifying the AST. + * + * @psalm-param class-string $className + */ + public function addCustomTreeWalker(string $className): void + { + $this->customTreeWalkers[] = $className; + } + + /** + * Gets the lexer used by the parser. + */ + public function getLexer(): Lexer + { + return $this->lexer; + } + + /** + * Gets the ParserResult that is being filled with information during parsing. + */ + public function getParserResult(): ParserResult + { + return $this->parserResult; + } + + /** + * Gets the EntityManager used by the parser. + */ + public function getEntityManager(): EntityManagerInterface + { + return $this->em; + } + + /** + * Parses and builds AST for the given Query. + */ + public function getAST(): AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement + { + // Parse & build AST + $AST = $this->QueryLanguage(); + + // Process any deferred validations of some nodes in the AST. + // This also allows post-processing of the AST for modification purposes. + $this->processDeferredIdentificationVariables(); + + if ($this->deferredPathExpressions) { + $this->processDeferredPathExpressions(); + } + + if ($this->deferredResultVariables) { + $this->processDeferredResultVariables(); + } + + if ($this->deferredNewObjectExpressions) { + $this->processDeferredNewObjectExpressions($AST); + } + + $this->processRootEntityAliasSelected(); + + // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot! + $this->fixIdentificationVariableOrder($AST); + + return $AST; + } + + /** + * Attempts to match the given token with the current lookahead token. + * + * If they match, updates the lookahead token; otherwise raises a syntax + * error. + * + * @throws QueryException If the tokens don't match. + */ + public function match(TokenType $token): void + { + $lookaheadType = $this->lexer->lookahead->type ?? null; + + // Short-circuit on first condition, usually types match + if ($lookaheadType === $token) { + $this->lexer->moveNext(); + + return; + } + + // If parameter is not identifier (1-99) must be exact match + if ($token->value < TokenType::T_IDENTIFIER->value) { + $this->syntaxError($this->lexer->getLiteral($token)); + } + + // If parameter is keyword (200+) must be exact match + if ($token->value > TokenType::T_IDENTIFIER->value) { + $this->syntaxError($this->lexer->getLiteral($token)); + } + + // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+) + if ($token->value === TokenType::T_IDENTIFIER->value && $lookaheadType->value < TokenType::T_IDENTIFIER->value) { + $this->syntaxError($this->lexer->getLiteral($token)); + } + + $this->lexer->moveNext(); + } + + /** + * Frees this parser, enabling it to be reused. + * + * @param bool $deep Whether to clean peek and reset errors. + * @param int $position Position to reset. + */ + public function free(bool $deep = false, int $position = 0): void + { + // WARNING! Use this method with care. It resets the scanner! + $this->lexer->resetPosition($position); + + // Deep = true cleans peek and also any previously defined errors + if ($deep) { + $this->lexer->resetPeek(); + } + + $this->lexer->token = null; + $this->lexer->lookahead = null; + } + + /** + * Parses a query string. + */ + public function parse(): ParserResult + { + $AST = $this->getAST(); + + $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS); + if ($customWalkers !== false) { + $this->customTreeWalkers = $customWalkers; + } + + $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER); + if ($customOutputWalker !== false) { + $this->customOutputWalker = $customOutputWalker; + } + + // Run any custom tree walkers over the AST + if ($this->customTreeWalkers) { + $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents); + + foreach ($this->customTreeWalkers as $walker) { + $treeWalkerChain->addTreeWalker($walker); + } + + match (true) { + $AST instanceof AST\UpdateStatement => $treeWalkerChain->walkUpdateStatement($AST), + $AST instanceof AST\DeleteStatement => $treeWalkerChain->walkDeleteStatement($AST), + $AST instanceof AST\SelectStatement => $treeWalkerChain->walkSelectStatement($AST), + }; + + $this->queryComponents = $treeWalkerChain->getQueryComponents(); + } + + $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class; + $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents); + + // Assign an SQL executor to the parser result + $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); + + return $this->parserResult; + } + + /** + * Fixes order of identification variables. + * + * They have to appear in the select clause in the same order as the + * declarations (from ... x join ... y join ... z ...) appear in the query + * as the hydration process relies on that order for proper operation. + */ + private function fixIdentificationVariableOrder(AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST): void + { + if (count($this->identVariableExpressions) <= 1) { + return; + } + + assert($AST instanceof AST\SelectStatement); + + foreach ($this->queryComponents as $dqlAlias => $qComp) { + if (! isset($this->identVariableExpressions[$dqlAlias])) { + continue; + } + + $expr = $this->identVariableExpressions[$dqlAlias]; + $key = array_search($expr, $AST->selectClause->selectExpressions, true); + + unset($AST->selectClause->selectExpressions[$key]); + + $AST->selectClause->selectExpressions[] = $expr; + } + } + + /** + * Generates a new syntax error. + * + * @param string $expected Expected string. + * @param DqlToken|null $token Got token. + * + * @throws QueryException + */ + public function syntaxError(string $expected = '', Token|null $token = null): never + { + if ($token === null) { + $token = $this->lexer->lookahead; + } + + $tokenPos = $token->position ?? '-1'; + + $message = sprintf('line 0, col %d: Error: ', $tokenPos); + $message .= $expected !== '' ? sprintf('Expected %s, got ', $expected) : 'Unexpected '; + $message .= $this->lexer->lookahead === null ? 'end of string.' : sprintf("'%s'", $token->value); + + throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL() ?? '')); + } + + /** + * Generates a new semantical error. + * + * @param string $message Optional message. + * @psalm-param DqlToken|null $token + * + * @throws QueryException + */ + public function semanticalError(string $message = '', Token|null $token = null): never + { + if ($token === null) { + $token = $this->lexer->lookahead ?? new Token('fake token', 42, 0); + } + + // Minimum exposed chars ahead of token + $distance = 12; + + // Find a position of a final word to display in error string + $dql = $this->query->getDQL(); + $length = strlen($dql); + $pos = $token->position + $distance; + $pos = strpos($dql, ' ', $length > $pos ? $pos : $length); + $length = $pos !== false ? $pos - $token->position : $distance; + + $tokenPos = $token->position > 0 ? $token->position : '-1'; + $tokenStr = substr($dql, $token->position, $length); + + // Building informative message + $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message; + + throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL())); + } + + /** + * Peeks beyond the matched closing parenthesis and returns the first token after that one. + * + * @param bool $resetPeek Reset peek after finding the closing parenthesis. + * + * @psalm-return DqlToken|null + */ + private function peekBeyondClosingParenthesis(bool $resetPeek = true): Token|null + { + $token = $this->lexer->peek(); + $numUnmatched = 1; + + while ($numUnmatched > 0 && $token !== null) { + switch ($token->type) { + case TokenType::T_OPEN_PARENTHESIS: + ++$numUnmatched; + break; + + case TokenType::T_CLOSE_PARENTHESIS: + --$numUnmatched; + break; + + default: + // Do nothing + } + + $token = $this->lexer->peek(); + } + + if ($resetPeek) { + $this->lexer->resetPeek(); + } + + return $token; + } + + /** + * Checks if the given token indicates a mathematical operator. + * + * @psalm-param DqlToken|null $token + */ + private function isMathOperator(Token|null $token): bool + { + return $token !== null && in_array($token->type, [TokenType::T_PLUS, TokenType::T_MINUS, TokenType::T_DIVIDE, TokenType::T_MULTIPLY], true); + } + + /** + * Checks if the next-next (after lookahead) token starts a function. + * + * @return bool TRUE if the next-next tokens start a function, FALSE otherwise. + */ + private function isFunction(): bool + { + assert($this->lexer->lookahead !== null); + $lookaheadType = $this->lexer->lookahead->type; + $peek = $this->lexer->peek(); + + $this->lexer->resetPeek(); + + return $lookaheadType->value >= TokenType::T_IDENTIFIER->value && $peek !== null && $peek->type === TokenType::T_OPEN_PARENTHESIS; + } + + /** + * Checks whether the given token type indicates an aggregate function. + * + * @return bool TRUE if the token type is an aggregate function, FALSE otherwise. + */ + private function isAggregateFunction(TokenType $tokenType): bool + { + return in_array( + $tokenType, + [TokenType::T_AVG, TokenType::T_MIN, TokenType::T_MAX, TokenType::T_SUM, TokenType::T_COUNT], + true, + ); + } + + /** + * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME. + */ + private function isNextAllAnySome(): bool + { + assert($this->lexer->lookahead !== null); + + return in_array( + $this->lexer->lookahead->type, + [TokenType::T_ALL, TokenType::T_ANY, TokenType::T_SOME], + true, + ); + } + + /** + * Validates that the given IdentificationVariable is semantically correct. + * It must exist in query components list. + */ + private function processDeferredIdentificationVariables(): void + { + foreach ($this->deferredIdentificationVariables as $deferredItem) { + $identVariable = $deferredItem['expression']; + + // Check if IdentificationVariable exists in queryComponents + if (! isset($this->queryComponents[$identVariable])) { + $this->semanticalError( + sprintf("'%s' is not defined.", $identVariable), + $deferredItem['token'], + ); + } + + $qComp = $this->queryComponents[$identVariable]; + + // Check if queryComponent points to an AbstractSchemaName or a ResultVariable + if (! isset($qComp['metadata'])) { + $this->semanticalError( + sprintf("'%s' does not point to a Class.", $identVariable), + $deferredItem['token'], + ); + } + + // Validate if identification variable nesting level is lower or equal than the current one + if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { + $this->semanticalError( + sprintf("'%s' is used outside the scope of its declaration.", $identVariable), + $deferredItem['token'], + ); + } + } + } + + /** + * Validates that the given NewObjectExpression. + */ + private function processDeferredNewObjectExpressions(AST\SelectStatement $AST): void + { + foreach ($this->deferredNewObjectExpressions as $deferredItem) { + $expression = $deferredItem['expression']; + $token = $deferredItem['token']; + $className = $expression->className; + $args = $expression->args; + $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null; + + // If the namespace is not given then assumes the first FROM entity namespace + if (! str_contains($className, '\\') && ! class_exists($className) && is_string($fromClassName) && str_contains($fromClassName, '\\')) { + $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\')); + $fqcn = $namespace . '\\' . $className; + + if (class_exists($fqcn)) { + $expression->className = $fqcn; + $className = $fqcn; + } + } + + if (! class_exists($className)) { + $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token); + } + + $class = new ReflectionClass($className); + + if (! $class->isInstantiable()) { + $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token); + } + + if ($class->getConstructor() === null) { + $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token); + } + + if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) { + $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token); + } + } + } + + /** + * Validates that the given ResultVariable is semantically correct. + * It must exist in query components list. + */ + private function processDeferredResultVariables(): void + { + foreach ($this->deferredResultVariables as $deferredItem) { + $resultVariable = $deferredItem['expression']; + + // Check if ResultVariable exists in queryComponents + if (! isset($this->queryComponents[$resultVariable])) { + $this->semanticalError( + sprintf("'%s' is not defined.", $resultVariable), + $deferredItem['token'], + ); + } + + $qComp = $this->queryComponents[$resultVariable]; + + // Check if queryComponent points to an AbstractSchemaName or a ResultVariable + if (! isset($qComp['resultVariable'])) { + $this->semanticalError( + sprintf("'%s' does not point to a ResultVariable.", $resultVariable), + $deferredItem['token'], + ); + } + + // Validate if identification variable nesting level is lower or equal than the current one + if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { + $this->semanticalError( + sprintf("'%s' is used outside the scope of its declaration.", $resultVariable), + $deferredItem['token'], + ); + } + } + } + + /** + * Validates that the given PathExpression is semantically correct for grammar rules: + * + * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + * StateFieldPathExpression ::= IdentificationVariable "." StateField + * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField + * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField + */ + private function processDeferredPathExpressions(): void + { + foreach ($this->deferredPathExpressions as $deferredItem) { + $pathExpression = $deferredItem['expression']; + + $class = $this->getMetadataForDqlAlias($pathExpression->identificationVariable); + + $field = $pathExpression->field; + if ($field === null) { + $field = $pathExpression->field = $class->identifier[0]; + } + + // Check if field or association exists + if (! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { + $this->semanticalError( + 'Class ' . $class->name . ' has no field or association named ' . $field, + $deferredItem['token'], + ); + } + + $fieldType = AST\PathExpression::TYPE_STATE_FIELD; + + if (isset($class->associationMappings[$field])) { + $assoc = $class->associationMappings[$field]; + + $fieldType = $assoc->isToOne() + ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION + : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION; + } + + // Validate if PathExpression is one of the expected types + $expectedType = $pathExpression->expectedType; + + if (! ($expectedType & $fieldType)) { + // We need to recognize which was expected type(s) + $expectedStringTypes = []; + + // Validate state field type + if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { + $expectedStringTypes[] = 'StateFieldPathExpression'; + } + + // Validate single valued association (*-to-one) + if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) { + $expectedStringTypes[] = 'SingleValuedAssociationField'; + } + + // Validate single valued association (*-to-many) + if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { + $expectedStringTypes[] = 'CollectionValuedAssociationField'; + } + + // Build the error message + $semanticalError = 'Invalid PathExpression. '; + $semanticalError .= count($expectedStringTypes) === 1 + ? 'Must be a ' . $expectedStringTypes[0] . '.' + : implode(' or ', $expectedStringTypes) . ' expected.'; + + $this->semanticalError($semanticalError, $deferredItem['token']); + } + + // We need to force the type in PathExpression + $pathExpression->type = $fieldType; + } + } + + private function processRootEntityAliasSelected(): void + { + if (! count($this->identVariableExpressions)) { + return; + } + + foreach ($this->identVariableExpressions as $dqlAlias => $expr) { + if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) { + return; + } + } + + $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.'); + } + + /** + * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + */ + public function QueryLanguage(): AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement + { + $statement = null; + + $this->lexer->moveNext(); + + switch ($this->lexer->lookahead->type ?? null) { + case TokenType::T_SELECT: + $statement = $this->SelectStatement(); + break; + + case TokenType::T_UPDATE: + $statement = $this->UpdateStatement(); + break; + + case TokenType::T_DELETE: + $statement = $this->DeleteStatement(); + break; + + default: + $this->syntaxError('SELECT, UPDATE or DELETE'); + break; + } + + // Check for end of string + if ($this->lexer->lookahead !== null) { + $this->syntaxError('end of string'); + } + + return $statement; + } + + /** + * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + */ + public function SelectStatement(): AST\SelectStatement + { + $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); + + $selectStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null; + $selectStatement->groupByClause = $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null; + $selectStatement->havingClause = $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null; + $selectStatement->orderByClause = $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null; + + return $selectStatement; + } + + /** + * UpdateStatement ::= UpdateClause [WhereClause] + */ + public function UpdateStatement(): AST\UpdateStatement + { + $updateStatement = new AST\UpdateStatement($this->UpdateClause()); + + $updateStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null; + + return $updateStatement; + } + + /** + * DeleteStatement ::= DeleteClause [WhereClause] + */ + public function DeleteStatement(): AST\DeleteStatement + { + $deleteStatement = new AST\DeleteStatement($this->DeleteClause()); + + $deleteStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null; + + return $deleteStatement; + } + + /** + * IdentificationVariable ::= identifier + */ + public function IdentificationVariable(): string + { + $this->match(TokenType::T_IDENTIFIER); + + assert($this->lexer->token !== null); + $identVariable = $this->lexer->token->value; + + $this->deferredIdentificationVariables[] = [ + 'expression' => $identVariable, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->token, + ]; + + return $identVariable; + } + + /** + * AliasIdentificationVariable = identifier + */ + public function AliasIdentificationVariable(): string + { + $this->match(TokenType::T_IDENTIFIER); + + assert($this->lexer->token !== null); + $aliasIdentVariable = $this->lexer->token->value; + $exists = isset($this->queryComponents[$aliasIdentVariable]); + + if ($exists) { + $this->semanticalError( + sprintf("'%s' is already defined.", $aliasIdentVariable), + $this->lexer->token, + ); + } + + return $aliasIdentVariable; + } + + /** + * AbstractSchemaName ::= fully_qualified_name | identifier + */ + public function AbstractSchemaName(): string + { + if ($this->lexer->isNextToken(TokenType::T_FULLY_QUALIFIED_NAME)) { + $this->match(TokenType::T_FULLY_QUALIFIED_NAME); + assert($this->lexer->token !== null); + + return $this->lexer->token->value; + } + + $this->match(TokenType::T_IDENTIFIER); + assert($this->lexer->token !== null); + + return $this->lexer->token->value; + } + + /** + * Validates an AbstractSchemaName, making sure the class exists. + * + * @param string $schemaName The name to validate. + * + * @throws QueryException if the name does not exist. + */ + private function validateAbstractSchemaName(string $schemaName): void + { + assert($this->lexer->token !== null); + if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) { + $this->semanticalError( + sprintf("Class '%s' is not defined.", $schemaName), + $this->lexer->token, + ); + } + } + + /** + * AliasResultVariable ::= identifier + */ + public function AliasResultVariable(): string + { + $this->match(TokenType::T_IDENTIFIER); + + assert($this->lexer->token !== null); + $resultVariable = $this->lexer->token->value; + $exists = isset($this->queryComponents[$resultVariable]); + + if ($exists) { + $this->semanticalError( + sprintf("'%s' is already defined.", $resultVariable), + $this->lexer->token, + ); + } + + return $resultVariable; + } + + /** + * ResultVariable ::= identifier + */ + public function ResultVariable(): string + { + $this->match(TokenType::T_IDENTIFIER); + + assert($this->lexer->token !== null); + $resultVariable = $this->lexer->token->value; + + // Defer ResultVariable validation + $this->deferredResultVariables[] = [ + 'expression' => $resultVariable, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->token, + ]; + + return $resultVariable; + } + + /** + * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + */ + public function JoinAssociationPathExpression(): AST\JoinAssociationPathExpression + { + $identVariable = $this->IdentificationVariable(); + + if (! isset($this->queryComponents[$identVariable])) { + $this->semanticalError( + 'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.', + ); + } + + $this->match(TokenType::T_DOT); + $this->match(TokenType::T_IDENTIFIER); + + assert($this->lexer->token !== null); + $field = $this->lexer->token->value; + + // Validate association field + $class = $this->getMetadataForDqlAlias($identVariable); + + if (! $class->hasAssociation($field)) { + $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field); + } + + return new AST\JoinAssociationPathExpression($identVariable, $field); + } + + /** + * Parses an arbitrary path expression and defers semantical validation + * based on expected types. + * + * PathExpression ::= IdentificationVariable {"." identifier}* + * + * @psalm-param int-mask-of $expectedTypes + */ + public function PathExpression(int $expectedTypes): AST\PathExpression + { + $identVariable = $this->IdentificationVariable(); + $field = null; + + assert($this->lexer->token !== null); + if ($this->lexer->isNextToken(TokenType::T_DOT)) { + $this->match(TokenType::T_DOT); + $this->match(TokenType::T_IDENTIFIER); + + $field = $this->lexer->token->value; + + while ($this->lexer->isNextToken(TokenType::T_DOT)) { + $this->match(TokenType::T_DOT); + $this->match(TokenType::T_IDENTIFIER); + $field .= '.' . $this->lexer->token->value; + } + } + + // Creating AST node + $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field); + + // Defer PathExpression validation if requested to be deferred + $this->deferredPathExpressions[] = [ + 'expression' => $pathExpr, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->token, + ]; + + return $pathExpr; + } + + /** + * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + */ + public function AssociationPathExpression(): AST\PathExpression + { + return $this->PathExpression( + AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION | + AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION, + ); + } + + /** + * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + */ + public function SingleValuedPathExpression(): AST\PathExpression + { + return $this->PathExpression( + AST\PathExpression::TYPE_STATE_FIELD | + AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, + ); + } + + /** + * StateFieldPathExpression ::= IdentificationVariable "." StateField + */ + public function StateFieldPathExpression(): AST\PathExpression + { + return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); + } + + /** + * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField + */ + public function SingleValuedAssociationPathExpression(): AST\PathExpression + { + return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION); + } + + /** + * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField + */ + public function CollectionValuedPathExpression(): AST\PathExpression + { + return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION); + } + + /** + * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} + */ + public function SelectClause(): AST\SelectClause + { + $isDistinct = false; + $this->match(TokenType::T_SELECT); + + // Check for DISTINCT + if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) { + $this->match(TokenType::T_DISTINCT); + + $isDistinct = true; + } + + // Process SelectExpressions (1..N) + $selectExpressions = []; + $selectExpressions[] = $this->SelectExpression(); + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $selectExpressions[] = $this->SelectExpression(); + } + + return new AST\SelectClause($selectExpressions, $isDistinct); + } + + /** + * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression + */ + public function SimpleSelectClause(): AST\SimpleSelectClause + { + $isDistinct = false; + $this->match(TokenType::T_SELECT); + + if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) { + $this->match(TokenType::T_DISTINCT); + + $isDistinct = true; + } + + return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct); + } + + /** + * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* + */ + public function UpdateClause(): AST\UpdateClause + { + $this->match(TokenType::T_UPDATE); + assert($this->lexer->lookahead !== null); + + $token = $this->lexer->lookahead; + $abstractSchemaName = $this->AbstractSchemaName(); + + $this->validateAbstractSchemaName($abstractSchemaName); + + if ($this->lexer->isNextToken(TokenType::T_AS)) { + $this->match(TokenType::T_AS); + } + + $aliasIdentificationVariable = $this->AliasIdentificationVariable(); + + $class = $this->em->getClassMetadata($abstractSchemaName); + + // Building queryComponent + $queryComponent = [ + 'metadata' => $class, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'nestingLevel' => $this->nestingLevel, + 'token' => $token, + ]; + + $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; + + $this->match(TokenType::T_SET); + + $updateItems = []; + $updateItems[] = $this->UpdateItem(); + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $updateItems[] = $this->UpdateItem(); + } + + $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems); + $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable; + + return $updateClause; + } + + /** + * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable + */ + public function DeleteClause(): AST\DeleteClause + { + $this->match(TokenType::T_DELETE); + + if ($this->lexer->isNextToken(TokenType::T_FROM)) { + $this->match(TokenType::T_FROM); + } + + assert($this->lexer->lookahead !== null); + $token = $this->lexer->lookahead; + $abstractSchemaName = $this->AbstractSchemaName(); + + $this->validateAbstractSchemaName($abstractSchemaName); + + $deleteClause = new AST\DeleteClause($abstractSchemaName); + + if ($this->lexer->isNextToken(TokenType::T_AS)) { + $this->match(TokenType::T_AS); + } + + $aliasIdentificationVariable = $this->lexer->isNextToken(TokenType::T_IDENTIFIER) + ? $this->AliasIdentificationVariable() + : 'alias_should_have_been_set'; + + $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; + $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); + + // Building queryComponent + $queryComponent = [ + 'metadata' => $class, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'nestingLevel' => $this->nestingLevel, + 'token' => $token, + ]; + + $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; + + return $deleteClause; + } + + /** + * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* + */ + public function FromClause(): AST\FromClause + { + $this->match(TokenType::T_FROM); + + $identificationVariableDeclarations = []; + $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); + } + + return new AST\FromClause($identificationVariableDeclarations); + } + + /** + * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* + */ + public function SubselectFromClause(): AST\SubselectFromClause + { + $this->match(TokenType::T_FROM); + + $identificationVariables = []; + $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); + } + + return new AST\SubselectFromClause($identificationVariables); + } + + /** + * WhereClause ::= "WHERE" ConditionalExpression + */ + public function WhereClause(): AST\WhereClause + { + $this->match(TokenType::T_WHERE); + + return new AST\WhereClause($this->ConditionalExpression()); + } + + /** + * HavingClause ::= "HAVING" ConditionalExpression + */ + public function HavingClause(): AST\HavingClause + { + $this->match(TokenType::T_HAVING); + + return new AST\HavingClause($this->ConditionalExpression()); + } + + /** + * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* + */ + public function GroupByClause(): AST\GroupByClause + { + $this->match(TokenType::T_GROUP); + $this->match(TokenType::T_BY); + + $groupByItems = [$this->GroupByItem()]; + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $groupByItems[] = $this->GroupByItem(); + } + + return new AST\GroupByClause($groupByItems); + } + + /** + * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* + */ + public function OrderByClause(): AST\OrderByClause + { + $this->match(TokenType::T_ORDER); + $this->match(TokenType::T_BY); + + $orderByItems = []; + $orderByItems[] = $this->OrderByItem(); + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $orderByItems[] = $this->OrderByItem(); + } + + return new AST\OrderByClause($orderByItems); + } + + /** + * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + */ + public function Subselect(): AST\Subselect + { + // Increase query nesting level + $this->nestingLevel++; + + $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); + + $subselect->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null; + $subselect->groupByClause = $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null; + $subselect->havingClause = $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null; + $subselect->orderByClause = $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null; + + // Decrease query nesting level + $this->nestingLevel--; + + return $subselect; + } + + /** + * UpdateItem ::= SingleValuedPathExpression "=" NewValue + */ + public function UpdateItem(): AST\UpdateItem + { + $pathExpr = $this->SingleValuedPathExpression(); + + $this->match(TokenType::T_EQUALS); + + return new AST\UpdateItem($pathExpr, $this->NewValue()); + } + + /** + * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression + */ + public function GroupByItem(): string|AST\PathExpression + { + // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression + $glimpse = $this->lexer->glimpse(); + + if ($glimpse !== null && $glimpse->type === TokenType::T_DOT) { + return $this->SingleValuedPathExpression(); + } + + assert($this->lexer->lookahead !== null); + // Still need to decide between IdentificationVariable or ResultVariable + $lookaheadValue = $this->lexer->lookahead->value; + + if (! isset($this->queryComponents[$lookaheadValue])) { + $this->semanticalError('Cannot group by undefined identification or result variable.'); + } + + return isset($this->queryComponents[$lookaheadValue]['metadata']) + ? $this->IdentificationVariable() + : $this->ResultVariable(); + } + + /** + * OrderByItem ::= ( + * SimpleArithmeticExpression | SingleValuedPathExpression | CaseExpression | + * ScalarExpression | ResultVariable | FunctionDeclaration + * ) ["ASC" | "DESC"] + */ + public function OrderByItem(): AST\OrderByItem + { + $this->lexer->peek(); // lookahead => '.' + $this->lexer->peek(); // lookahead => token after '.' + + $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' + + $this->lexer->resetPeek(); + + $glimpse = $this->lexer->glimpse(); + + assert($this->lexer->lookahead !== null); + $expr = match (true) { + $this->isMathOperator($peek) => $this->SimpleArithmeticExpression(), + $glimpse !== null && $glimpse->type === TokenType::T_DOT => $this->SingleValuedPathExpression(), + $this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->ScalarExpression(), + $this->lexer->lookahead->type === TokenType::T_CASE => $this->CaseExpression(), + $this->isFunction() => $this->FunctionDeclaration(), + default => $this->ResultVariable(), + }; + + $type = 'ASC'; + $item = new AST\OrderByItem($expr); + + switch (true) { + case $this->lexer->isNextToken(TokenType::T_DESC): + $this->match(TokenType::T_DESC); + $type = 'DESC'; + break; + + case $this->lexer->isNextToken(TokenType::T_ASC): + $this->match(TokenType::T_ASC); + break; + + default: + // Do nothing + } + + $item->type = $type; + + return $item; + } + + /** + * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | + * EnumPrimary | SimpleEntityExpression | "NULL" + * + * NOTE: Since it is not possible to correctly recognize individual types, here is the full + * grammar that needs to be supported: + * + * NewValue ::= SimpleArithmeticExpression | "NULL" + * + * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression + */ + public function NewValue(): AST\ArithmeticExpression|AST\InputParameter|null + { + if ($this->lexer->isNextToken(TokenType::T_NULL)) { + $this->match(TokenType::T_NULL); + + return null; + } + + if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) { + $this->match(TokenType::T_INPUT_PARAMETER); + assert($this->lexer->token !== null); + + return new AST\InputParameter($this->lexer->token->value); + } + + return $this->ArithmeticExpression(); + } + + /** + * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}* + */ + public function IdentificationVariableDeclaration(): AST\IdentificationVariableDeclaration + { + $joins = []; + $rangeVariableDeclaration = $this->RangeVariableDeclaration(); + $indexBy = $this->lexer->isNextToken(TokenType::T_INDEX) + ? $this->IndexBy() + : null; + + $rangeVariableDeclaration->isRoot = true; + + while ( + $this->lexer->isNextToken(TokenType::T_LEFT) || + $this->lexer->isNextToken(TokenType::T_INNER) || + $this->lexer->isNextToken(TokenType::T_JOIN) + ) { + $joins[] = $this->Join(); + } + + return new AST\IdentificationVariableDeclaration( + $rangeVariableDeclaration, + $indexBy, + $joins, + ); + } + + /** + * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration + * + * {Internal note: WARNING: Solution is harder than a bare implementation. + * Desired EBNF support: + * + * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) + * + * It demands that entire SQL generation to become programmatical. This is + * needed because association based subselect requires "WHERE" conditional + * expressions to be injected, but there is no scope to do that. Only scope + * accessible is "FROM", prohibiting an easy implementation without larger + * changes.} + */ + public function SubselectIdentificationVariableDeclaration(): AST\IdentificationVariableDeclaration + { + /* + NOT YET IMPLEMENTED! + + $glimpse = $this->lexer->glimpse(); + + if ($glimpse->type == TokenType::T_DOT) { + $associationPathExpression = $this->AssociationPathExpression(); + + if ($this->lexer->isNextToken(TokenType::T_AS)) { + $this->match(TokenType::T_AS); + } + + $aliasIdentificationVariable = $this->AliasIdentificationVariable(); + $identificationVariable = $associationPathExpression->identificationVariable; + $field = $associationPathExpression->associationField; + + $class = $this->queryComponents[$identificationVariable]['metadata']; + $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']); + + // Building queryComponent + $joinQueryComponent = array( + 'metadata' => $targetClass, + 'parent' => $identificationVariable, + 'relation' => $class->getAssociationMapping($field), + 'map' => null, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->lookahead + ); + + $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; + + return new AST\SubselectIdentificationVariableDeclaration( + $associationPathExpression, $aliasIdentificationVariable + ); + } + */ + + return $this->IdentificationVariableDeclaration(); + } + + /** + * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" + * (JoinAssociationDeclaration | RangeVariableDeclaration) + * ["WITH" ConditionalExpression] + */ + public function Join(): AST\Join + { + // Check Join type + $joinType = AST\Join::JOIN_TYPE_INNER; + + switch (true) { + case $this->lexer->isNextToken(TokenType::T_LEFT): + $this->match(TokenType::T_LEFT); + + $joinType = AST\Join::JOIN_TYPE_LEFT; + + // Possible LEFT OUTER join + if ($this->lexer->isNextToken(TokenType::T_OUTER)) { + $this->match(TokenType::T_OUTER); + + $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; + } + + break; + + case $this->lexer->isNextToken(TokenType::T_INNER): + $this->match(TokenType::T_INNER); + break; + + default: + // Do nothing + } + + $this->match(TokenType::T_JOIN); + + $next = $this->lexer->glimpse(); + assert($next !== null); + $joinDeclaration = $next->type === TokenType::T_DOT ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration(); + $adhocConditions = $this->lexer->isNextToken(TokenType::T_WITH); + $join = new AST\Join($joinType, $joinDeclaration); + + // Describe non-root join declaration + if ($joinDeclaration instanceof AST\RangeVariableDeclaration) { + $joinDeclaration->isRoot = false; + } + + // Check for ad-hoc Join conditions + if ($adhocConditions) { + $this->match(TokenType::T_WITH); + + $join->conditionalExpression = $this->ConditionalExpression(); + } + + return $join; + } + + /** + * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + * + * @throws QueryException + */ + public function RangeVariableDeclaration(): AST\RangeVariableDeclaration + { + if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()->type === TokenType::T_SELECT) { + $this->semanticalError('Subquery is not supported here', $this->lexer->token); + } + + $abstractSchemaName = $this->AbstractSchemaName(); + + $this->validateAbstractSchemaName($abstractSchemaName); + + if ($this->lexer->isNextToken(TokenType::T_AS)) { + $this->match(TokenType::T_AS); + } + + assert($this->lexer->lookahead !== null); + $token = $this->lexer->lookahead; + $aliasIdentificationVariable = $this->AliasIdentificationVariable(); + $classMetadata = $this->em->getClassMetadata($abstractSchemaName); + + // Building queryComponent + $queryComponent = [ + 'metadata' => $classMetadata, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'nestingLevel' => $this->nestingLevel, + 'token' => $token, + ]; + + $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; + + return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); + } + + /** + * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy] + */ + public function JoinAssociationDeclaration(): AST\JoinAssociationDeclaration + { + $joinAssociationPathExpression = $this->JoinAssociationPathExpression(); + + if ($this->lexer->isNextToken(TokenType::T_AS)) { + $this->match(TokenType::T_AS); + } + + assert($this->lexer->lookahead !== null); + + $aliasIdentificationVariable = $this->AliasIdentificationVariable(); + $indexBy = $this->lexer->isNextToken(TokenType::T_INDEX) ? $this->IndexBy() : null; + + $identificationVariable = $joinAssociationPathExpression->identificationVariable; + $field = $joinAssociationPathExpression->associationField; + + $class = $this->getMetadataForDqlAlias($identificationVariable); + $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]->targetEntity); + + // Building queryComponent + $joinQueryComponent = [ + 'metadata' => $targetClass, + 'parent' => $joinAssociationPathExpression->identificationVariable, + 'relation' => $class->getAssociationMapping($field), + 'map' => null, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->lookahead, + ]; + + $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; + + return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy); + } + + /** + * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")" + */ + public function NewObjectExpression(): AST\NewObjectExpression + { + $args = []; + $this->match(TokenType::T_NEW); + + $className = $this->AbstractSchemaName(); // note that this is not yet validated + $token = $this->lexer->token; + + $this->match(TokenType::T_OPEN_PARENTHESIS); + + $args[] = $this->NewObjectArg(); + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $args[] = $this->NewObjectArg(); + } + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + $expression = new AST\NewObjectExpression($className, $args); + + // Defer NewObjectExpression validation + $this->deferredNewObjectExpressions[] = [ + 'token' => $token, + 'expression' => $expression, + 'nestingLevel' => $this->nestingLevel, + ]; + + return $expression; + } + + /** + * NewObjectArg ::= ScalarExpression | "(" Subselect ")" + */ + public function NewObjectArg(): mixed + { + assert($this->lexer->lookahead !== null); + $token = $this->lexer->lookahead; + $peek = $this->lexer->glimpse(); + + assert($peek !== null); + if ($token->type === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT) { + $this->match(TokenType::T_OPEN_PARENTHESIS); + $expression = $this->Subselect(); + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return $expression; + } + + return $this->ScalarExpression(); + } + + /** + * IndexBy ::= "INDEX" "BY" SingleValuedPathExpression + */ + public function IndexBy(): AST\IndexBy + { + $this->match(TokenType::T_INDEX); + $this->match(TokenType::T_BY); + $pathExpr = $this->SingleValuedPathExpression(); + + // Add the INDEX BY info to the query component + $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field; + + return new AST\IndexBy($pathExpr); + } + + /** + * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | + * StateFieldPathExpression | BooleanPrimary | CaseExpression | + * InstanceOfExpression + * + * @return mixed One of the possible expressions or subexpressions. + */ + public function ScalarExpression(): mixed + { + assert($this->lexer->token !== null); + assert($this->lexer->lookahead !== null); + $lookahead = $this->lexer->lookahead->type; + $peek = $this->lexer->glimpse(); + + switch (true) { + case $lookahead === TokenType::T_INTEGER: + case $lookahead === TokenType::T_FLOAT: + // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) or ( - 1 ) or ( + 1 ) + case $lookahead === TokenType::T_MINUS: + case $lookahead === TokenType::T_PLUS: + return $this->SimpleArithmeticExpression(); + + case $lookahead === TokenType::T_STRING: + return $this->StringPrimary(); + + case $lookahead === TokenType::T_TRUE: + case $lookahead === TokenType::T_FALSE: + $this->match($lookahead); + + return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value); + + case $lookahead === TokenType::T_INPUT_PARAMETER: + return match (true) { + $this->isMathOperator($peek) => $this->SimpleArithmeticExpression(), + default => $this->InputParameter(), + }; + + case $lookahead === TokenType::T_CASE: + case $lookahead === TokenType::T_COALESCE: + case $lookahead === TokenType::T_NULLIF: + // Since NULLIF and COALESCE can be identified as a function, + // we need to check these before checking for FunctionDeclaration + return $this->CaseExpression(); + + case $lookahead === TokenType::T_OPEN_PARENTHESIS: + return $this->SimpleArithmeticExpression(); + + // this check must be done before checking for a filed path expression + case $this->isFunction(): + $this->lexer->peek(); + + return match (true) { + $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->SimpleArithmeticExpression(), + default => $this->FunctionDeclaration(), + }; + + // it is no function, so it must be a field path + case $lookahead === TokenType::T_IDENTIFIER: + $this->lexer->peek(); // lookahead => '.' + $this->lexer->peek(); // lookahead => token after '.' + $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' + $this->lexer->resetPeek(); + + if ($this->isMathOperator($peek)) { + return $this->SimpleArithmeticExpression(); + } + + return $this->StateFieldPathExpression(); + + default: + $this->syntaxError(); + } + } + + /** + * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression + * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + * + * @return mixed One of the possible expressions or subexpressions. + */ + public function CaseExpression(): mixed + { + assert($this->lexer->lookahead !== null); + $lookahead = $this->lexer->lookahead->type; + + switch ($lookahead) { + case TokenType::T_NULLIF: + return $this->NullIfExpression(); + + case TokenType::T_COALESCE: + return $this->CoalesceExpression(); + + case TokenType::T_CASE: + $this->lexer->resetPeek(); + $peek = $this->lexer->peek(); + + assert($peek !== null); + if ($peek->type === TokenType::T_WHEN) { + return $this->GeneralCaseExpression(); + } + + return $this->SimpleCaseExpression(); + + default: + // Do nothing + break; + } + + $this->syntaxError(); + } + + /** + * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + */ + public function CoalesceExpression(): AST\CoalesceExpression + { + $this->match(TokenType::T_COALESCE); + $this->match(TokenType::T_OPEN_PARENTHESIS); + + // Process ScalarExpressions (1..N) + $scalarExpressions = []; + $scalarExpressions[] = $this->ScalarExpression(); + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $scalarExpressions[] = $this->ScalarExpression(); + } + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return new AST\CoalesceExpression($scalarExpressions); + } + + /** + * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + */ + public function NullIfExpression(): AST\NullIfExpression + { + $this->match(TokenType::T_NULLIF); + $this->match(TokenType::T_OPEN_PARENTHESIS); + + $firstExpression = $this->ScalarExpression(); + $this->match(TokenType::T_COMMA); + $secondExpression = $this->ScalarExpression(); + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return new AST\NullIfExpression($firstExpression, $secondExpression); + } + + /** + * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + */ + public function GeneralCaseExpression(): AST\GeneralCaseExpression + { + $this->match(TokenType::T_CASE); + + // Process WhenClause (1..N) + $whenClauses = []; + + do { + $whenClauses[] = $this->WhenClause(); + } while ($this->lexer->isNextToken(TokenType::T_WHEN)); + + $this->match(TokenType::T_ELSE); + $scalarExpression = $this->ScalarExpression(); + $this->match(TokenType::T_END); + + return new AST\GeneralCaseExpression($whenClauses, $scalarExpression); + } + + /** + * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + */ + public function SimpleCaseExpression(): AST\SimpleCaseExpression + { + $this->match(TokenType::T_CASE); + $caseOperand = $this->StateFieldPathExpression(); + + // Process SimpleWhenClause (1..N) + $simpleWhenClauses = []; + + do { + $simpleWhenClauses[] = $this->SimpleWhenClause(); + } while ($this->lexer->isNextToken(TokenType::T_WHEN)); + + $this->match(TokenType::T_ELSE); + $scalarExpression = $this->ScalarExpression(); + $this->match(TokenType::T_END); + + return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression); + } + + /** + * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + */ + public function WhenClause(): AST\WhenClause + { + $this->match(TokenType::T_WHEN); + $conditionalExpression = $this->ConditionalExpression(); + $this->match(TokenType::T_THEN); + + return new AST\WhenClause($conditionalExpression, $this->ScalarExpression()); + } + + /** + * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + */ + public function SimpleWhenClause(): AST\SimpleWhenClause + { + $this->match(TokenType::T_WHEN); + $conditionalExpression = $this->ScalarExpression(); + $this->match(TokenType::T_THEN); + + return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression()); + } + + /** + * SelectExpression ::= ( + * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | + * "(" Subselect ")" | CaseExpression | NewObjectExpression + * ) [["AS"] ["HIDDEN"] AliasResultVariable] + */ + public function SelectExpression(): AST\SelectExpression + { + assert($this->lexer->lookahead !== null); + $expression = null; + $identVariable = null; + $peek = $this->lexer->glimpse(); + $lookaheadType = $this->lexer->lookahead->type; + assert($peek !== null); + + switch (true) { + // ScalarExpression (u.name) + case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type === TokenType::T_DOT: + $expression = $this->ScalarExpression(); + break; + + // IdentificationVariable (u) + case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type !== TokenType::T_OPEN_PARENTHESIS: + $expression = $identVariable = $this->IdentificationVariable(); + break; + + // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) + case $lookaheadType === TokenType::T_CASE: + case $lookaheadType === TokenType::T_COALESCE: + case $lookaheadType === TokenType::T_NULLIF: + $expression = $this->CaseExpression(); + break; + + // DQL Function (SUM(u.value) or SUM(u.value) + 1) + case $this->isFunction(): + $this->lexer->peek(); // "(" + + $expression = match (true) { + $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->ScalarExpression(), + default => $this->FunctionDeclaration(), + }; + + break; + + // Subselect + case $lookaheadType === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT: + $this->match(TokenType::T_OPEN_PARENTHESIS); + $expression = $this->Subselect(); + $this->match(TokenType::T_CLOSE_PARENTHESIS); + break; + + // Shortcut: ScalarExpression => SimpleArithmeticExpression + case $lookaheadType === TokenType::T_OPEN_PARENTHESIS: + case $lookaheadType === TokenType::T_INTEGER: + case $lookaheadType === TokenType::T_STRING: + case $lookaheadType === TokenType::T_FLOAT: + // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) + case $lookaheadType === TokenType::T_MINUS: + case $lookaheadType === TokenType::T_PLUS: + $expression = $this->SimpleArithmeticExpression(); + break; + + // NewObjectExpression (New ClassName(id, name)) + case $lookaheadType === TokenType::T_NEW: + $expression = $this->NewObjectExpression(); + break; + + default: + $this->syntaxError( + 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | "(" Subselect ")" | CaseExpression', + $this->lexer->lookahead, + ); + } + + // [["AS"] ["HIDDEN"] AliasResultVariable] + $mustHaveAliasResultVariable = false; + + if ($this->lexer->isNextToken(TokenType::T_AS)) { + $this->match(TokenType::T_AS); + + $mustHaveAliasResultVariable = true; + } + + $hiddenAliasResultVariable = false; + + if ($this->lexer->isNextToken(TokenType::T_HIDDEN)) { + $this->match(TokenType::T_HIDDEN); + + $hiddenAliasResultVariable = true; + } + + $aliasResultVariable = null; + + if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(TokenType::T_IDENTIFIER)) { + assert($expression instanceof AST\Node || is_string($expression)); + $token = $this->lexer->lookahead; + $aliasResultVariable = $this->AliasResultVariable(); + + // Include AliasResultVariable in query components. + $this->queryComponents[$aliasResultVariable] = [ + 'resultVariable' => $expression, + 'nestingLevel' => $this->nestingLevel, + 'token' => $token, + ]; + } + + // AST + + $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); + + if ($identVariable) { + $this->identVariableExpressions[$identVariable] = $expr; + } + + return $expr; + } + + /** + * SimpleSelectExpression ::= ( + * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | + * AggregateExpression | "(" Subselect ")" | ScalarExpression + * ) [["AS"] AliasResultVariable] + */ + public function SimpleSelectExpression(): AST\SimpleSelectExpression + { + assert($this->lexer->lookahead !== null); + $peek = $this->lexer->glimpse(); + assert($peek !== null); + + switch ($this->lexer->lookahead->type) { + case TokenType::T_IDENTIFIER: + switch (true) { + case $peek->type === TokenType::T_DOT: + $expression = $this->StateFieldPathExpression(); + + return new AST\SimpleSelectExpression($expression); + + case $peek->type !== TokenType::T_OPEN_PARENTHESIS: + $expression = $this->IdentificationVariable(); + + return new AST\SimpleSelectExpression($expression); + + case $this->isFunction(): + // SUM(u.id) + COUNT(u.id) + if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) { + return new AST\SimpleSelectExpression($this->ScalarExpression()); + } + + // COUNT(u.id) + if ($this->isAggregateFunction($this->lexer->lookahead->type)) { + return new AST\SimpleSelectExpression($this->AggregateExpression()); + } + + // IDENTITY(u) + return new AST\SimpleSelectExpression($this->FunctionDeclaration()); + + default: + // Do nothing + } + + break; + + case TokenType::T_OPEN_PARENTHESIS: + if ($peek->type !== TokenType::T_SELECT) { + // Shortcut: ScalarExpression => SimpleArithmeticExpression + $expression = $this->SimpleArithmeticExpression(); + + return new AST\SimpleSelectExpression($expression); + } + + // Subselect + $this->match(TokenType::T_OPEN_PARENTHESIS); + $expression = $this->Subselect(); + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return new AST\SimpleSelectExpression($expression); + + default: + // Do nothing + } + + $this->lexer->peek(); + + $expression = $this->ScalarExpression(); + $expr = new AST\SimpleSelectExpression($expression); + + if ($this->lexer->isNextToken(TokenType::T_AS)) { + $this->match(TokenType::T_AS); + } + + if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER)) { + $token = $this->lexer->lookahead; + $resultVariable = $this->AliasResultVariable(); + $expr->fieldIdentificationVariable = $resultVariable; + + // Include AliasResultVariable in query components. + $this->queryComponents[$resultVariable] = [ + 'resultvariable' => $expr, + 'nestingLevel' => $this->nestingLevel, + 'token' => $token, + ]; + } + + return $expr; + } + + /** + * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* + */ + public function ConditionalExpression(): AST\ConditionalExpression|AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm + { + $conditionalTerms = []; + $conditionalTerms[] = $this->ConditionalTerm(); + + while ($this->lexer->isNextToken(TokenType::T_OR)) { + $this->match(TokenType::T_OR); + + $conditionalTerms[] = $this->ConditionalTerm(); + } + + // Phase 1 AST optimization: Prevent AST\ConditionalExpression + // if only one AST\ConditionalTerm is defined + if (count($conditionalTerms) === 1) { + return $conditionalTerms[0]; + } + + return new AST\ConditionalExpression($conditionalTerms); + } + + /** + * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* + */ + public function ConditionalTerm(): AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm + { + $conditionalFactors = []; + $conditionalFactors[] = $this->ConditionalFactor(); + + while ($this->lexer->isNextToken(TokenType::T_AND)) { + $this->match(TokenType::T_AND); + + $conditionalFactors[] = $this->ConditionalFactor(); + } + + // Phase 1 AST optimization: Prevent AST\ConditionalTerm + // if only one AST\ConditionalFactor is defined + if (count($conditionalFactors) === 1) { + return $conditionalFactors[0]; + } + + return new AST\ConditionalTerm($conditionalFactors); + } + + /** + * ConditionalFactor ::= ["NOT"] ConditionalPrimary + */ + public function ConditionalFactor(): AST\ConditionalFactor|AST\ConditionalPrimary + { + $not = false; + + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + + $not = true; + } + + $conditionalPrimary = $this->ConditionalPrimary(); + + // Phase 1 AST optimization: Prevent AST\ConditionalFactor + // if only one AST\ConditionalPrimary is defined + if (! $not) { + return $conditionalPrimary; + } + + return new AST\ConditionalFactor($conditionalPrimary, $not); + } + + /** + * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" + */ + public function ConditionalPrimary(): AST\ConditionalPrimary + { + $condPrimary = new AST\ConditionalPrimary(); + + if (! $this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) { + $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); + + return $condPrimary; + } + + // Peek beyond the matching closing parenthesis ')' + $peek = $this->peekBeyondClosingParenthesis(); + + if ( + $peek !== null && ( + in_array($peek->value, ['=', '<', '<=', '<>', '>', '>=', '!='], true) || + in_array($peek->type, [TokenType::T_NOT, TokenType::T_BETWEEN, TokenType::T_LIKE, TokenType::T_IN, TokenType::T_IS, TokenType::T_EXISTS], true) || + $this->isMathOperator($peek) + ) + ) { + $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); + + return $condPrimary; + } + + $this->match(TokenType::T_OPEN_PARENTHESIS); + $condPrimary->conditionalExpression = $this->ConditionalExpression(); + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return $condPrimary; + } + + /** + * SimpleConditionalExpression ::= + * ComparisonExpression | BetweenExpression | LikeExpression | + * InExpression | NullComparisonExpression | ExistsExpression | + * EmptyCollectionComparisonExpression | CollectionMemberExpression | + * InstanceOfExpression + */ + public function SimpleConditionalExpression(): AST\ExistsExpression|AST\BetweenExpression|AST\LikeExpression|AST\InListExpression|AST\InSubselectExpression|AST\InstanceOfExpression|AST\CollectionMemberExpression|AST\NullComparisonExpression|AST\EmptyCollectionComparisonExpression|AST\ComparisonExpression + { + assert($this->lexer->lookahead !== null); + if ($this->lexer->isNextToken(TokenType::T_EXISTS)) { + return $this->ExistsExpression(); + } + + $token = $this->lexer->lookahead; + $peek = $this->lexer->glimpse(); + $lookahead = $token; + + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $token = $this->lexer->glimpse(); + } + + assert($token !== null); + assert($peek !== null); + if ($token->type === TokenType::T_IDENTIFIER || $token->type === TokenType::T_INPUT_PARAMETER || $this->isFunction()) { + // Peek beyond the matching closing parenthesis. + $beyond = $this->lexer->peek(); + + switch ($peek->value) { + case '(': + // Peeks beyond the matched closing parenthesis. + $token = $this->peekBeyondClosingParenthesis(false); + assert($token !== null); + + if ($token->type === TokenType::T_NOT) { + $token = $this->lexer->peek(); + assert($token !== null); + } + + if ($token->type === TokenType::T_IS) { + $lookahead = $this->lexer->peek(); + } + + break; + + default: + // Peek beyond the PathExpression or InputParameter. + $token = $beyond; + + while ($token->value === '.') { + $this->lexer->peek(); + + $token = $this->lexer->peek(); + assert($token !== null); + } + + // Also peek beyond a NOT if there is one. + assert($token !== null); + if ($token->type === TokenType::T_NOT) { + $token = $this->lexer->peek(); + assert($token !== null); + } + + // We need to go even further in case of IS (differentiate between NULL and EMPTY) + $lookahead = $this->lexer->peek(); + } + + assert($lookahead !== null); + // Also peek beyond a NOT if there is one. + if ($lookahead->type === TokenType::T_NOT) { + $lookahead = $this->lexer->peek(); + } + + $this->lexer->resetPeek(); + } + + if ($token->type === TokenType::T_BETWEEN) { + return $this->BetweenExpression(); + } + + if ($token->type === TokenType::T_LIKE) { + return $this->LikeExpression(); + } + + if ($token->type === TokenType::T_IN) { + return $this->InExpression(); + } + + if ($token->type === TokenType::T_INSTANCE) { + return $this->InstanceOfExpression(); + } + + if ($token->type === TokenType::T_MEMBER) { + return $this->CollectionMemberExpression(); + } + + assert($lookahead !== null); + if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_NULL) { + return $this->NullComparisonExpression(); + } + + if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_EMPTY) { + return $this->EmptyCollectionComparisonExpression(); + } + + return $this->ComparisonExpression(); + } + + /** + * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" + */ + public function EmptyCollectionComparisonExpression(): AST\EmptyCollectionComparisonExpression + { + $pathExpression = $this->CollectionValuedPathExpression(); + $this->match(TokenType::T_IS); + + $not = false; + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + $not = true; + } + + $this->match(TokenType::T_EMPTY); + + return new AST\EmptyCollectionComparisonExpression( + $pathExpression, + $not, + ); + } + + /** + * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression + * + * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression + * SimpleEntityExpression ::= IdentificationVariable | InputParameter + */ + public function CollectionMemberExpression(): AST\CollectionMemberExpression + { + $not = false; + $entityExpr = $this->EntityExpression(); + + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + + $not = true; + } + + $this->match(TokenType::T_MEMBER); + + if ($this->lexer->isNextToken(TokenType::T_OF)) { + $this->match(TokenType::T_OF); + } + + return new AST\CollectionMemberExpression( + $entityExpr, + $this->CollectionValuedPathExpression(), + $not, + ); + } + + /** + * Literal ::= string | char | integer | float | boolean + */ + public function Literal(): AST\Literal + { + assert($this->lexer->lookahead !== null); + assert($this->lexer->token !== null); + switch ($this->lexer->lookahead->type) { + case TokenType::T_STRING: + $this->match(TokenType::T_STRING); + + return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value); + + case TokenType::T_INTEGER: + case TokenType::T_FLOAT: + $this->match( + $this->lexer->isNextToken(TokenType::T_INTEGER) ? TokenType::T_INTEGER : TokenType::T_FLOAT, + ); + + return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token->value); + + case TokenType::T_TRUE: + case TokenType::T_FALSE: + $this->match( + $this->lexer->isNextToken(TokenType::T_TRUE) ? TokenType::T_TRUE : TokenType::T_FALSE, + ); + + return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value); + + default: + $this->syntaxError('Literal'); + } + } + + /** + * InParameter ::= ArithmeticExpression | InputParameter + */ + public function InParameter(): AST\InputParameter|AST\ArithmeticExpression + { + assert($this->lexer->lookahead !== null); + if ($this->lexer->lookahead->type === TokenType::T_INPUT_PARAMETER) { + return $this->InputParameter(); + } + + return $this->ArithmeticExpression(); + } + + /** + * InputParameter ::= PositionalParameter | NamedParameter + */ + public function InputParameter(): AST\InputParameter + { + $this->match(TokenType::T_INPUT_PARAMETER); + assert($this->lexer->token !== null); + + return new AST\InputParameter($this->lexer->token->value); + } + + /** + * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" + */ + public function ArithmeticExpression(): AST\ArithmeticExpression + { + $expr = new AST\ArithmeticExpression(); + + if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) { + $peek = $this->lexer->glimpse(); + assert($peek !== null); + + if ($peek->type === TokenType::T_SELECT) { + $this->match(TokenType::T_OPEN_PARENTHESIS); + $expr->subselect = $this->Subselect(); + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return $expr; + } + } + + $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression(); + + return $expr; + } + + /** + * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* + */ + public function SimpleArithmeticExpression(): AST\Node|string + { + $terms = []; + $terms[] = $this->ArithmeticTerm(); + + while (($isPlus = $this->lexer->isNextToken(TokenType::T_PLUS)) || $this->lexer->isNextToken(TokenType::T_MINUS)) { + $this->match($isPlus ? TokenType::T_PLUS : TokenType::T_MINUS); + + assert($this->lexer->token !== null); + $terms[] = $this->lexer->token->value; + $terms[] = $this->ArithmeticTerm(); + } + + // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression + // if only one AST\ArithmeticTerm is defined + if (count($terms) === 1) { + return $terms[0]; + } + + return new AST\SimpleArithmeticExpression($terms); + } + + /** + * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* + */ + public function ArithmeticTerm(): AST\Node|string + { + $factors = []; + $factors[] = $this->ArithmeticFactor(); + + while (($isMult = $this->lexer->isNextToken(TokenType::T_MULTIPLY)) || $this->lexer->isNextToken(TokenType::T_DIVIDE)) { + $this->match($isMult ? TokenType::T_MULTIPLY : TokenType::T_DIVIDE); + + assert($this->lexer->token !== null); + $factors[] = $this->lexer->token->value; + $factors[] = $this->ArithmeticFactor(); + } + + // Phase 1 AST optimization: Prevent AST\ArithmeticTerm + // if only one AST\ArithmeticFactor is defined + if (count($factors) === 1) { + return $factors[0]; + } + + return new AST\ArithmeticTerm($factors); + } + + /** + * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary + */ + public function ArithmeticFactor(): AST\Node|string|AST\ArithmeticFactor + { + $sign = null; + + $isPlus = $this->lexer->isNextToken(TokenType::T_PLUS); + if ($isPlus || $this->lexer->isNextToken(TokenType::T_MINUS)) { + $this->match($isPlus ? TokenType::T_PLUS : TokenType::T_MINUS); + $sign = $isPlus; + } + + $primary = $this->ArithmeticPrimary(); + + // Phase 1 AST optimization: Prevent AST\ArithmeticFactor + // if only one AST\ArithmeticPrimary is defined + if ($sign === null) { + return $primary; + } + + return new AST\ArithmeticFactor($primary, $sign); + } + + /** + * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression + * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings + * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable + * | InputParameter | CaseExpression + */ + public function ArithmeticPrimary(): AST\Node|string + { + if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) { + $this->match(TokenType::T_OPEN_PARENTHESIS); + + $expr = $this->SimpleArithmeticExpression(); + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return new AST\ParenthesisExpression($expr); + } + + if ($this->lexer->lookahead === null) { + $this->syntaxError('ArithmeticPrimary'); + } + + switch ($this->lexer->lookahead->type) { + case TokenType::T_COALESCE: + case TokenType::T_NULLIF: + case TokenType::T_CASE: + return $this->CaseExpression(); + + case TokenType::T_IDENTIFIER: + $peek = $this->lexer->glimpse(); + + if ($peek !== null && $peek->value === '(') { + return $this->FunctionDeclaration(); + } + + if ($peek !== null && $peek->value === '.') { + return $this->SingleValuedPathExpression(); + } + + if (isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])) { + return $this->ResultVariable(); + } + + return $this->StateFieldPathExpression(); + + case TokenType::T_INPUT_PARAMETER: + return $this->InputParameter(); + + default: + $peek = $this->lexer->glimpse(); + + if ($peek !== null && $peek->value === '(') { + return $this->FunctionDeclaration(); + } + + return $this->Literal(); + } + } + + /** + * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")" + */ + public function StringExpression(): AST\Subselect|AST\Node|string + { + $peek = $this->lexer->glimpse(); + assert($peek !== null); + + // Subselect + if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $peek->type === TokenType::T_SELECT) { + $this->match(TokenType::T_OPEN_PARENTHESIS); + $expr = $this->Subselect(); + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return $expr; + } + + assert($this->lexer->lookahead !== null); + // ResultVariable (string) + if ( + $this->lexer->isNextToken(TokenType::T_IDENTIFIER) && + isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable']) + ) { + return $this->ResultVariable(); + } + + return $this->StringPrimary(); + } + + /** + * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression + */ + public function StringPrimary(): AST\Node + { + assert($this->lexer->lookahead !== null); + $lookaheadType = $this->lexer->lookahead->type; + + switch ($lookaheadType) { + case TokenType::T_IDENTIFIER: + $peek = $this->lexer->glimpse(); + assert($peek !== null); + + if ($peek->value === '.') { + return $this->StateFieldPathExpression(); + } + + if ($peek->value === '(') { + // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions. + return $this->FunctionDeclaration(); + } + + $this->syntaxError("'.' or '('"); + break; + + case TokenType::T_STRING: + $this->match(TokenType::T_STRING); + assert($this->lexer->token !== null); + + return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value); + + case TokenType::T_INPUT_PARAMETER: + return $this->InputParameter(); + + case TokenType::T_CASE: + case TokenType::T_COALESCE: + case TokenType::T_NULLIF: + return $this->CaseExpression(); + + default: + assert($lookaheadType !== null); + if ($this->isAggregateFunction($lookaheadType)) { + return $this->AggregateExpression(); + } + } + + $this->syntaxError( + 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression', + ); + } + + /** + * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression + */ + public function EntityExpression(): AST\InputParameter|AST\PathExpression + { + $glimpse = $this->lexer->glimpse(); + assert($glimpse !== null); + + if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER) && $glimpse->value === '.') { + return $this->SingleValuedAssociationPathExpression(); + } + + return $this->SimpleEntityExpression(); + } + + /** + * SimpleEntityExpression ::= IdentificationVariable | InputParameter + */ + public function SimpleEntityExpression(): AST\InputParameter|AST\PathExpression + { + if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) { + return $this->InputParameter(); + } + + return $this->StateFieldPathExpression(); + } + + /** + * AggregateExpression ::= + * ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")" + */ + public function AggregateExpression(): AST\AggregateExpression + { + assert($this->lexer->lookahead !== null); + $lookaheadType = $this->lexer->lookahead->type; + $isDistinct = false; + + if (! in_array($lookaheadType, [TokenType::T_COUNT, TokenType::T_AVG, TokenType::T_MAX, TokenType::T_MIN, TokenType::T_SUM], true)) { + $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); + } + + $this->match($lookaheadType); + assert($this->lexer->token !== null); + $functionName = $this->lexer->token->value; + $this->match(TokenType::T_OPEN_PARENTHESIS); + + if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) { + $this->match(TokenType::T_DISTINCT); + $isDistinct = true; + } + + $pathExp = $this->SimpleArithmeticExpression(); + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); + } + + /** + * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" + */ + public function QuantifiedExpression(): AST\QuantifiedExpression + { + assert($this->lexer->lookahead !== null); + $lookaheadType = $this->lexer->lookahead->type; + $value = $this->lexer->lookahead->value; + + if (! in_array($lookaheadType, [TokenType::T_ALL, TokenType::T_ANY, TokenType::T_SOME], true)) { + $this->syntaxError('ALL, ANY or SOME'); + } + + $this->match($lookaheadType); + $this->match(TokenType::T_OPEN_PARENTHESIS); + + $qExpr = new AST\QuantifiedExpression($this->Subselect()); + $qExpr->type = $value; + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return $qExpr; + } + + /** + * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression + */ + public function BetweenExpression(): AST\BetweenExpression + { + $not = false; + $arithExpr1 = $this->ArithmeticExpression(); + + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + $not = true; + } + + $this->match(TokenType::T_BETWEEN); + $arithExpr2 = $this->ArithmeticExpression(); + $this->match(TokenType::T_AND); + $arithExpr3 = $this->ArithmeticExpression(); + + return new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3, $not); + } + + /** + * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) + */ + public function ComparisonExpression(): AST\ComparisonExpression + { + $this->lexer->glimpse(); + + $leftExpr = $this->ArithmeticExpression(); + $operator = $this->ComparisonOperator(); + $rightExpr = $this->isNextAllAnySome() + ? $this->QuantifiedExpression() + : $this->ArithmeticExpression(); + + return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); + } + + /** + * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" + */ + public function InExpression(): AST\InListExpression|AST\InSubselectExpression + { + $expression = $this->ArithmeticExpression(); + + $not = false; + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + $not = true; + } + + $this->match(TokenType::T_IN); + $this->match(TokenType::T_OPEN_PARENTHESIS); + + if ($this->lexer->isNextToken(TokenType::T_SELECT)) { + $inExpression = new AST\InSubselectExpression( + $expression, + $this->Subselect(), + $not, + ); + } else { + $literals = [$this->InParameter()]; + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + $literals[] = $this->InParameter(); + } + + $inExpression = new AST\InListExpression( + $expression, + $literals, + $not, + ); + } + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return $inExpression; + } + + /** + * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") + */ + public function InstanceOfExpression(): AST\InstanceOfExpression + { + $identificationVariable = $this->IdentificationVariable(); + + $not = false; + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + $not = true; + } + + $this->match(TokenType::T_INSTANCE); + $this->match(TokenType::T_OF); + + $exprValues = $this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) + ? $this->InstanceOfParameterList() + : [$this->InstanceOfParameter()]; + + return new AST\InstanceOfExpression( + $identificationVariable, + $exprValues, + $not, + ); + } + + /** @return non-empty-list */ + public function InstanceOfParameterList(): array + { + $this->match(TokenType::T_OPEN_PARENTHESIS); + + $exprValues = [$this->InstanceOfParameter()]; + + while ($this->lexer->isNextToken(TokenType::T_COMMA)) { + $this->match(TokenType::T_COMMA); + + $exprValues[] = $this->InstanceOfParameter(); + } + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return $exprValues; + } + + /** + * InstanceOfParameter ::= AbstractSchemaName | InputParameter + */ + public function InstanceOfParameter(): AST\InputParameter|string + { + if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) { + $this->match(TokenType::T_INPUT_PARAMETER); + assert($this->lexer->token !== null); + + return new AST\InputParameter($this->lexer->token->value); + } + + $abstractSchemaName = $this->AbstractSchemaName(); + + $this->validateAbstractSchemaName($abstractSchemaName); + + return $abstractSchemaName; + } + + /** + * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] + */ + public function LikeExpression(): AST\LikeExpression + { + $stringExpr = $this->StringExpression(); + $not = false; + + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + $not = true; + } + + $this->match(TokenType::T_LIKE); + + if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) { + $this->match(TokenType::T_INPUT_PARAMETER); + assert($this->lexer->token !== null); + $stringPattern = new AST\InputParameter($this->lexer->token->value); + } else { + $stringPattern = $this->StringPrimary(); + } + + $escapeChar = null; + + if ($this->lexer->lookahead !== null && $this->lexer->lookahead->type === TokenType::T_ESCAPE) { + $this->match(TokenType::T_ESCAPE); + $this->match(TokenType::T_STRING); + assert($this->lexer->token !== null); + + $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token->value); + } + + return new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar, $not); + } + + /** + * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL" + */ + public function NullComparisonExpression(): AST\NullComparisonExpression + { + switch (true) { + case $this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER): + $this->match(TokenType::T_INPUT_PARAMETER); + assert($this->lexer->token !== null); + + $expr = new AST\InputParameter($this->lexer->token->value); + break; + + case $this->lexer->isNextToken(TokenType::T_NULLIF): + $expr = $this->NullIfExpression(); + break; + + case $this->lexer->isNextToken(TokenType::T_COALESCE): + $expr = $this->CoalesceExpression(); + break; + + case $this->isFunction(): + $expr = $this->FunctionDeclaration(); + break; + + default: + // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression + $glimpse = $this->lexer->glimpse(); + assert($glimpse !== null); + + if ($glimpse->type === TokenType::T_DOT) { + $expr = $this->SingleValuedPathExpression(); + + // Leave switch statement + break; + } + + assert($this->lexer->lookahead !== null); + $lookaheadValue = $this->lexer->lookahead->value; + + // Validate existing component + if (! isset($this->queryComponents[$lookaheadValue])) { + $this->semanticalError('Cannot add having condition on undefined result variable.'); + } + + // Validate SingleValuedPathExpression (ie.: "product") + if (isset($this->queryComponents[$lookaheadValue]['metadata'])) { + $expr = $this->SingleValuedPathExpression(); + break; + } + + // Validating ResultVariable + if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) { + $this->semanticalError('Cannot add having condition on a non result variable.'); + } + + $expr = $this->ResultVariable(); + break; + } + + $this->match(TokenType::T_IS); + + $not = false; + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + + $not = true; + } + + $this->match(TokenType::T_NULL); + + return new AST\NullComparisonExpression($expr, $not); + } + + /** + * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" + */ + public function ExistsExpression(): AST\ExistsExpression + { + $not = false; + + if ($this->lexer->isNextToken(TokenType::T_NOT)) { + $this->match(TokenType::T_NOT); + $not = true; + } + + $this->match(TokenType::T_EXISTS); + $this->match(TokenType::T_OPEN_PARENTHESIS); + + $subselect = $this->Subselect(); + + $this->match(TokenType::T_CLOSE_PARENTHESIS); + + return new AST\ExistsExpression($subselect, $not); + } + + /** + * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" + */ + public function ComparisonOperator(): string + { + assert($this->lexer->lookahead !== null); + switch ($this->lexer->lookahead->value) { + case '=': + $this->match(TokenType::T_EQUALS); + + return '='; + + case '<': + $this->match(TokenType::T_LOWER_THAN); + $operator = '<'; + + if ($this->lexer->isNextToken(TokenType::T_EQUALS)) { + $this->match(TokenType::T_EQUALS); + $operator .= '='; + } elseif ($this->lexer->isNextToken(TokenType::T_GREATER_THAN)) { + $this->match(TokenType::T_GREATER_THAN); + $operator .= '>'; + } + + return $operator; + + case '>': + $this->match(TokenType::T_GREATER_THAN); + $operator = '>'; + + if ($this->lexer->isNextToken(TokenType::T_EQUALS)) { + $this->match(TokenType::T_EQUALS); + $operator .= '='; + } + + return $operator; + + case '!': + $this->match(TokenType::T_NEGATE); + $this->match(TokenType::T_EQUALS); + + return '<>'; + + default: + $this->syntaxError('=, <, <=, <>, >, >=, !='); + } + } + + /** + * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime + */ + public function FunctionDeclaration(): Functions\FunctionNode + { + assert($this->lexer->lookahead !== null); + $token = $this->lexer->lookahead; + $funcName = strtolower($token->value); + + $customFunctionDeclaration = $this->CustomFunctionDeclaration(); + + // Check for custom functions functions first! + switch (true) { + case $customFunctionDeclaration !== null: + return $customFunctionDeclaration; + + case isset(self::$stringFunctions[$funcName]): + return $this->FunctionsReturningStrings(); + + case isset(self::$numericFunctions[$funcName]): + return $this->FunctionsReturningNumerics(); + + case isset(self::$datetimeFunctions[$funcName]): + return $this->FunctionsReturningDatetime(); + + default: + $this->syntaxError('known function', $token); + } + } + + /** + * Helper function for FunctionDeclaration grammar rule. + */ + private function CustomFunctionDeclaration(): Functions\FunctionNode|null + { + assert($this->lexer->lookahead !== null); + $token = $this->lexer->lookahead; + $funcName = strtolower($token->value); + + // Check for custom functions afterwards + $config = $this->em->getConfiguration(); + + return match (true) { + $config->getCustomStringFunction($funcName) !== null => $this->CustomFunctionsReturningStrings(), + $config->getCustomNumericFunction($funcName) !== null => $this->CustomFunctionsReturningNumerics(), + $config->getCustomDatetimeFunction($funcName) !== null => $this->CustomFunctionsReturningDatetime(), + default => null, + }; + } + + /** + * FunctionsReturningNumerics ::= + * "LENGTH" "(" StringPrimary ")" | + * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | + * "ABS" "(" SimpleArithmeticExpression ")" | + * "SQRT" "(" SimpleArithmeticExpression ")" | + * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + * "SIZE" "(" CollectionValuedPathExpression ")" | + * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | + * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | + * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" + */ + public function FunctionsReturningNumerics(): AST\Functions\FunctionNode + { + assert($this->lexer->lookahead !== null); + $funcNameLower = strtolower($this->lexer->lookahead->value); + $funcClass = self::$numericFunctions[$funcNameLower]; + + $function = new $funcClass($funcNameLower); + $function->parse($this); + + return $function; + } + + public function CustomFunctionsReturningNumerics(): AST\Functions\FunctionNode + { + assert($this->lexer->lookahead !== null); + // getCustomNumericFunction is case-insensitive + $functionName = strtolower($this->lexer->lookahead->value); + $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName); + + assert($functionClass !== null); + + $function = is_string($functionClass) + ? new $functionClass($functionName) + : $functionClass($functionName); + + $function->parse($this); + + return $function; + } + + /** + * FunctionsReturningDateTime ::= + * "CURRENT_DATE" | + * "CURRENT_TIME" | + * "CURRENT_TIMESTAMP" | + * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | + * "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" + */ + public function FunctionsReturningDatetime(): AST\Functions\FunctionNode + { + assert($this->lexer->lookahead !== null); + $funcNameLower = strtolower($this->lexer->lookahead->value); + $funcClass = self::$datetimeFunctions[$funcNameLower]; + + $function = new $funcClass($funcNameLower); + $function->parse($this); + + return $function; + } + + public function CustomFunctionsReturningDatetime(): AST\Functions\FunctionNode + { + assert($this->lexer->lookahead !== null); + // getCustomDatetimeFunction is case-insensitive + $functionName = $this->lexer->lookahead->value; + $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName); + + assert($functionClass !== null); + + $function = is_string($functionClass) + ? new $functionClass($functionName) + : $functionClass($functionName); + + $function->parse($this); + + return $function; + } + + /** + * FunctionsReturningStrings ::= + * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" | + * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | + * "LOWER" "(" StringPrimary ")" | + * "UPPER" "(" StringPrimary ")" | + * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" + */ + public function FunctionsReturningStrings(): AST\Functions\FunctionNode + { + assert($this->lexer->lookahead !== null); + $funcNameLower = strtolower($this->lexer->lookahead->value); + $funcClass = self::$stringFunctions[$funcNameLower]; + + $function = new $funcClass($funcNameLower); + $function->parse($this); + + return $function; + } + + public function CustomFunctionsReturningStrings(): Functions\FunctionNode + { + assert($this->lexer->lookahead !== null); + // getCustomStringFunction is case-insensitive + $functionName = $this->lexer->lookahead->value; + $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName); + + assert($functionClass !== null); + + $function = is_string($functionClass) + ? new $functionClass($functionName) + : $functionClass($functionName); + + $function->parse($this); + + return $function; + } + + private function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata + { + if (! isset($this->queryComponents[$dqlAlias]['metadata'])) { + throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias)); + } + + return $this->queryComponents[$dqlAlias]['metadata']; + } +} diff --git a/vendor/doctrine/orm/src/Query/ParserResult.php b/vendor/doctrine/orm/src/Query/ParserResult.php new file mode 100644 index 0000000..8b5ee1f --- /dev/null +++ b/vendor/doctrine/orm/src/Query/ParserResult.php @@ -0,0 +1,118 @@ +> + */ + private array $parameterMappings = []; + + /** + * Initializes a new instance of the ParserResult class. + * The new instance is initialized with an empty ResultSetMapping. + */ + public function __construct() + { + $this->resultSetMapping = new ResultSetMapping(); + } + + /** + * Gets the ResultSetMapping for the parsed query. + * + * @return ResultSetMapping The result set mapping of the parsed query + */ + public function getResultSetMapping(): ResultSetMapping + { + return $this->resultSetMapping; + } + + /** + * Sets the ResultSetMapping of the parsed query. + */ + public function setResultSetMapping(ResultSetMapping $rsm): void + { + $this->resultSetMapping = $rsm; + } + + /** + * Sets the SQL executor that should be used for this ParserResult. + */ + public function setSqlExecutor(AbstractSqlExecutor $executor): void + { + $this->sqlExecutor = $executor; + } + + /** + * Gets the SQL executor used by this ParserResult. + */ + public function getSqlExecutor(): AbstractSqlExecutor + { + if ($this->sqlExecutor === null) { + throw new LogicException(sprintf( + 'Executor not set yet. Call %s::setSqlExecutor() first.', + self::class, + )); + } + + return $this->sqlExecutor; + } + + /** + * Adds a DQL to SQL parameter mapping. One DQL parameter name/position can map to + * several SQL parameter positions. + */ + public function addParameterMapping(string|int $dqlPosition, int $sqlPosition): void + { + $this->parameterMappings[$dqlPosition][] = $sqlPosition; + } + + /** + * Gets all DQL to SQL parameter mappings. + * + * @psalm-return array> The parameter mappings. + */ + public function getParameterMappings(): array + { + return $this->parameterMappings; + } + + /** + * Gets the SQL parameter positions for a DQL parameter name/position. + * + * @param string|int $dqlPosition The name or position of the DQL parameter. + * + * @return int[] The positions of the corresponding SQL parameters. + * @psalm-return list + */ + public function getSqlParameterPositions(string|int $dqlPosition): array + { + return $this->parameterMappings[$dqlPosition]; + } +} diff --git a/vendor/doctrine/orm/src/Query/Printer.php b/vendor/doctrine/orm/src/Query/Printer.php new file mode 100644 index 0000000..db1f159 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/Printer.php @@ -0,0 +1,64 @@ +println('(' . $name); + $this->indent++; + } + + /** + * Decreases indentation level by one and prints a closing parenthesis. + * + * This method is called after executing a production. + */ + public function endProduction(): void + { + $this->indent--; + $this->println(')'); + } + + /** + * Prints text indented with spaces depending on current indentation level. + * + * @param string $str The text. + */ + public function println(string $str): void + { + if (! $this->silent) { + echo str_repeat(' ', $this->indent), $str, "\n"; + } + } +} diff --git a/vendor/doctrine/orm/src/Query/QueryException.php b/vendor/doctrine/orm/src/Query/QueryException.php new file mode 100644 index 0000000..ae945b1 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/QueryException.php @@ -0,0 +1,155 @@ + or ? expected.'); + } + + public static function unknownParameter(string $key): self + { + return new self('Invalid parameter: token ' . $key . ' is not defined in the query.'); + } + + public static function parameterTypeMismatch(): self + { + return new self('DQL Query parameter and type numbers mismatch, but have to be exactly equal.'); + } + + public static function invalidPathExpression(PathExpression $pathExpr): self + { + return new self( + "Invalid PathExpression '" . $pathExpr->identificationVariable . '.' . $pathExpr->field . "'.", + ); + } + + public static function invalidLiteral(string|Stringable $literal): self + { + return new self("Invalid literal '" . $literal . "'"); + } + + public static function iterateWithFetchJoinCollectionNotAllowed(AssociationMapping $assoc): self + { + return new self( + 'Invalid query operation: Not allowed to iterate over fetch join collections ' . + 'in class ' . $assoc->sourceEntity . ' association ' . $assoc->fieldName, + ); + } + + /** + * @param string[] $assoc + * @psalm-param array $assoc + */ + public static function overwritingJoinConditionsNotYetSupported(array $assoc): self + { + return new self( + 'Unsupported query operation: It is not yet possible to overwrite the join ' . + 'conditions in class ' . $assoc['sourceEntityName'] . ' association ' . $assoc['fieldName'] . '. ' . + 'Use WITH to append additional join conditions to the association.', + ); + } + + public static function associationPathInverseSideNotSupported(PathExpression $pathExpr): self + { + return new self( + 'A single-valued association path expression to an inverse side is not supported in DQL queries. ' . + 'Instead of "' . $pathExpr->identificationVariable . '.' . $pathExpr->field . '" use an explicit join.', + ); + } + + public static function iterateWithFetchJoinNotAllowed(AssociationMapping $assoc): self + { + return new self( + 'Iterate with fetch join in class ' . $assoc->sourceEntity . + ' using association ' . $assoc->fieldName . ' not allowed.', + ); + } + + public static function eagerFetchJoinWithNotAllowed(string $sourceEntity, string $fieldName): self + { + return new self( + 'Associations with fetch-mode=EAGER may not be using WITH conditions in + "' . $sourceEntity . '#' . $fieldName . '".', + ); + } + + public static function iterateWithMixedResultNotAllowed(): self + { + return new self('Iterating a query with mixed results (using scalars) is not supported.'); + } + + public static function associationPathCompositeKeyNotSupported(): self + { + return new self( + 'A single-valued association path expression to an entity with a composite primary ' . + 'key is not supported. Explicitly name the components of the composite primary key ' . + 'in the query.', + ); + } + + public static function instanceOfUnrelatedClass(string $className, string $rootClass): self + { + return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " . + 'inheritance hierarchy does not exists between these two classes.'); + } + + public static function invalidQueryComponent(string $dqlAlias): self + { + return new self( + "Invalid query component given for DQL alias '" . $dqlAlias . "', " . + "requires 'metadata', 'parent', 'relation', 'map', 'nestingLevel' and 'token' keys.", + ); + } +} diff --git a/vendor/doctrine/orm/src/Query/QueryExpressionVisitor.php b/vendor/doctrine/orm/src/Query/QueryExpressionVisitor.php new file mode 100644 index 0000000..3e0ec65 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/QueryExpressionVisitor.php @@ -0,0 +1,180 @@ + Expr\Comparison::GT, + Comparison::GTE => Expr\Comparison::GTE, + Comparison::LT => Expr\Comparison::LT, + Comparison::LTE => Expr\Comparison::LTE, + ]; + + private readonly Expr $expr; + + /** @var list */ + private array $parameters = []; + + /** @param mixed[] $queryAliases */ + public function __construct( + private readonly array $queryAliases, + ) { + $this->expr = new Expr(); + } + + /** + * Gets bound parameters. + * Filled after {@link dispach()}. + * + * @return ArrayCollection + */ + public function getParameters(): ArrayCollection + { + return new ArrayCollection($this->parameters); + } + + public function clearParameters(): void + { + $this->parameters = []; + } + + /** + * Converts Criteria expression to Query one based on static map. + */ + private static function convertComparisonOperator(string $criteriaOperator): string|null + { + return self::OPERATOR_MAP[$criteriaOperator] ?? null; + } + + public function walkCompositeExpression(CompositeExpression $expr): mixed + { + $expressionList = []; + + foreach ($expr->getExpressionList() as $child) { + $expressionList[] = $this->dispatch($child); + } + + return match ($expr->getType()) { + CompositeExpression::TYPE_AND => new Expr\Andx($expressionList), + CompositeExpression::TYPE_OR => new Expr\Orx($expressionList), + CompositeExpression::TYPE_NOT => $this->expr->not($expressionList[0]), + default => throw new RuntimeException('Unknown composite ' . $expr->getType()), + }; + } + + public function walkComparison(Comparison $comparison): mixed + { + if (! isset($this->queryAliases[0])) { + throw new QueryException('No aliases are set before invoking walkComparison().'); + } + + $field = $this->queryAliases[0] . '.' . $comparison->getField(); + + foreach ($this->queryAliases as $alias) { + if (str_starts_with($comparison->getField() . '.', $alias . '.')) { + $field = $comparison->getField(); + break; + } + } + + $parameterName = str_replace('.', '_', $comparison->getField()); + + foreach ($this->parameters as $parameter) { + if ($parameter->getName() === $parameterName) { + $parameterName .= '_' . count($this->parameters); + break; + } + } + + $parameter = new Parameter($parameterName, $this->walkValue($comparison->getValue())); + $placeholder = ':' . $parameterName; + + switch ($comparison->getOperator()) { + case Comparison::IN: + $this->parameters[] = $parameter; + + return $this->expr->in($field, $placeholder); + + case Comparison::NIN: + $this->parameters[] = $parameter; + + return $this->expr->notIn($field, $placeholder); + + case Comparison::EQ: + case Comparison::IS: + if ($this->walkValue($comparison->getValue()) === null) { + return $this->expr->isNull($field); + } + + $this->parameters[] = $parameter; + + return $this->expr->eq($field, $placeholder); + + case Comparison::NEQ: + if ($this->walkValue($comparison->getValue()) === null) { + return $this->expr->isNotNull($field); + } + + $this->parameters[] = $parameter; + + return $this->expr->neq($field, $placeholder); + + case Comparison::CONTAINS: + $parameter->setValue('%' . $parameter->getValue() . '%', $parameter->getType()); + $this->parameters[] = $parameter; + + return $this->expr->like($field, $placeholder); + + case Comparison::MEMBER_OF: + return $this->expr->isMemberOf($comparison->getField(), $comparison->getValue()->getValue()); + + case Comparison::STARTS_WITH: + $parameter->setValue($parameter->getValue() . '%', $parameter->getType()); + $this->parameters[] = $parameter; + + return $this->expr->like($field, $placeholder); + + case Comparison::ENDS_WITH: + $parameter->setValue('%' . $parameter->getValue(), $parameter->getType()); + $this->parameters[] = $parameter; + + return $this->expr->like($field, $placeholder); + + default: + $operator = self::convertComparisonOperator($comparison->getOperator()); + if ($operator) { + $this->parameters[] = $parameter; + + return new Expr\Comparison( + $field, + $operator, + $placeholder, + ); + } + + throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()); + } + } + + public function walkValue(Value $value): mixed + { + return $value->getValue(); + } +} diff --git a/vendor/doctrine/orm/src/Query/ResultSetMapping.php b/vendor/doctrine/orm/src/Query/ResultSetMapping.php new file mode 100644 index 0000000..612474d --- /dev/null +++ b/vendor/doctrine/orm/src/Query/ResultSetMapping.php @@ -0,0 +1,547 @@ +Users should use the public methods. + * + * @todo Think about whether the number of lookup maps can be reduced. + */ +class ResultSetMapping +{ + /** + * Whether the result is mixed (contains scalar values together with field values). + * + * @ignore + */ + public bool $isMixed = false; + + /** + * Whether the result is a select statement. + * + * @ignore + */ + public bool $isSelect = true; + + /** + * Maps alias names to class names. + * + * @ignore + * @psalm-var array + */ + public array $aliasMap = []; + + /** + * Maps alias names to related association field names. + * + * @ignore + * @psalm-var array + */ + public array $relationMap = []; + + /** + * Maps alias names to parent alias names. + * + * @ignore + * @psalm-var array + */ + public array $parentAliasMap = []; + + /** + * Maps column names in the result set to field names for each class. + * + * @ignore + * @psalm-var array + */ + public array $fieldMappings = []; + + /** + * Maps column names in the result set to the alias/field name to use in the mapped result. + * + * @ignore + * @psalm-var array + */ + public array $scalarMappings = []; + + /** + * Maps scalar columns to enums + * + * @ignore + * @psalm-var array + */ + public $enumMappings = []; + + /** + * Maps column names in the result set to the alias/field type to use in the mapped result. + * + * @ignore + * @psalm-var array + */ + public array $typeMappings = []; + + /** + * Maps entities in the result set to the alias name to use in the mapped result. + * + * @ignore + * @psalm-var array + */ + public array $entityMappings = []; + + /** + * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names. + * + * @ignore + * @psalm-var array + */ + public array $metaMappings = []; + + /** + * Maps column names in the result set to the alias they belong to. + * + * @ignore + * @psalm-var array + */ + public array $columnOwnerMap = []; + + /** + * List of columns in the result set that are used as discriminator columns. + * + * @ignore + * @psalm-var array + */ + public array $discriminatorColumns = []; + + /** + * Maps alias names to field names that should be used for indexing. + * + * @ignore + * @psalm-var array + */ + public array $indexByMap = []; + + /** + * Map from column names to class names that declare the field the column is mapped to. + * + * @ignore + * @psalm-var array + */ + public array $declaringClasses = []; + + /** + * This is necessary to hydrate derivate foreign keys correctly. + * + * @psalm-var array> + */ + public array $isIdentifierColumn = []; + + /** + * Maps column names in the result set to field names for each new object expression. + * + * @psalm-var array> + */ + public array $newObjectMappings = []; + + /** + * Maps metadata parameter names to the metadata attribute. + * + * @psalm-var array + */ + public array $metadataParameterMapping = []; + + /** + * Contains query parameter names to be resolved as discriminator values + * + * @psalm-var array + */ + public array $discriminatorParameters = []; + + /** + * Adds an entity result to this ResultSetMapping. + * + * @param string $class The class name of the entity. + * @param string $alias The alias for the class. The alias must be unique among all entity + * results or joined entity results within this ResultSetMapping. + * @param string|null $resultAlias The result alias with which the entity result should be + * placed in the result structure. + * @psalm-param class-string $class + * + * @return $this + * + * @todo Rename: addRootEntity + */ + public function addEntityResult(string $class, string $alias, string|null $resultAlias = null): static + { + $this->aliasMap[$alias] = $class; + $this->entityMappings[$alias] = $resultAlias; + + if ($resultAlias !== null) { + $this->isMixed = true; + } + + return $this; + } + + /** + * Sets a discriminator column for an entity result or joined entity result. + * The discriminator column will be used to determine the concrete class name to + * instantiate. + * + * @param string $alias The alias of the entity result or joined entity result the discriminator + * column should be used for. + * @param string $discrColumn The name of the discriminator column in the SQL result set. + * + * @return $this + * + * @todo Rename: addDiscriminatorColumn + */ + public function setDiscriminatorColumn(string $alias, string $discrColumn): static + { + $this->discriminatorColumns[$alias] = $discrColumn; + $this->columnOwnerMap[$discrColumn] = $alias; + + return $this; + } + + /** + * Sets a field to use for indexing an entity result or joined entity result. + * + * @param string $alias The alias of an entity result or joined entity result. + * @param string $fieldName The name of the field to use for indexing. + * + * @return $this + */ + public function addIndexBy(string $alias, string $fieldName): static + { + $found = false; + + foreach ([...$this->metaMappings, ...$this->fieldMappings] as $columnName => $columnFieldName) { + if (! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) { + continue; + } + + $this->addIndexByColumn($alias, $columnName); + $found = true; + + break; + } + + /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals + if ( ! $found) { + $message = sprintf( + 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.', + $alias, + $fieldName + ); + + throw new \LogicException($message); + } + */ + + return $this; + } + + /** + * Sets to index by a scalar result column name. + * + * @return $this + */ + public function addIndexByScalar(string $resultColumnName): static + { + $this->indexByMap['scalars'] = $resultColumnName; + + return $this; + } + + /** + * Sets a column to use for indexing an entity or joined entity result by the given alias name. + * + * @return $this + */ + public function addIndexByColumn(string $alias, string $resultColumnName): static + { + $this->indexByMap[$alias] = $resultColumnName; + + return $this; + } + + /** + * Checks whether an entity result or joined entity result with a given alias has + * a field set for indexing. + * + * @todo Rename: isIndexed($alias) + */ + public function hasIndexBy(string $alias): bool + { + return isset($this->indexByMap[$alias]); + } + + /** + * Checks whether the column with the given name is mapped as a field result + * as part of an entity result or joined entity result. + * + * @param string $columnName The name of the column in the SQL result set. + * + * @todo Rename: isField + */ + public function isFieldResult(string $columnName): bool + { + return isset($this->fieldMappings[$columnName]); + } + + /** + * Adds a field to the result that belongs to an entity or joined entity. + * + * @param string $alias The alias of the root entity or joined entity to which the field belongs. + * @param string $columnName The name of the column in the SQL result set. + * @param string $fieldName The name of the field on the declaring class. + * @param string|null $declaringClass The name of the class that declares/owns the specified field. + * When $alias refers to a superclass in a mapped hierarchy but + * the field $fieldName is defined on a subclass, specify that here. + * If not specified, the field is assumed to belong to the class + * designated by $alias. + * @psalm-param class-string|null $declaringClass + * + * @return $this + * + * @todo Rename: addField + */ + public function addFieldResult(string $alias, string $columnName, string $fieldName, string|null $declaringClass = null): static + { + // column name (in result set) => field name + $this->fieldMappings[$columnName] = $fieldName; + // column name => alias of owner + $this->columnOwnerMap[$columnName] = $alias; + // field name => class name of declaring class + $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; + + if (! $this->isMixed && $this->scalarMappings) { + $this->isMixed = true; + } + + return $this; + } + + /** + * Adds a joined entity result. + * + * @param string $class The class name of the joined entity. + * @param string $alias The unique alias to use for the joined entity. + * @param string $parentAlias The alias of the entity result that is the parent of this joined result. + * @param string $relation The association field that connects the parent entity result + * with the joined entity result. + * @psalm-param class-string $class + * + * @return $this + * + * @todo Rename: addJoinedEntity + */ + public function addJoinedEntityResult(string $class, string $alias, string $parentAlias, string $relation): static + { + $this->aliasMap[$alias] = $class; + $this->parentAliasMap[$alias] = $parentAlias; + $this->relationMap[$alias] = $relation; + + return $this; + } + + /** + * Adds a scalar result mapping. + * + * @param string $columnName The name of the column in the SQL result set. + * @param string|int $alias The result alias with which the scalar result should be placed in the result structure. + * @param string $type The column type + * + * @return $this + * + * @todo Rename: addScalar + */ + public function addScalarResult(string $columnName, string|int $alias, string $type = 'string'): static + { + $this->scalarMappings[$columnName] = $alias; + $this->typeMappings[$columnName] = $type; + + if (! $this->isMixed && $this->fieldMappings) { + $this->isMixed = true; + } + + return $this; + } + + /** + * Adds a scalar result mapping. + * + * @param string $columnName The name of the column in the SQL result set. + * @param string $enumType The enum type + * + * @return $this + */ + public function addEnumResult(string $columnName, string $enumType): static + { + $this->enumMappings[$columnName] = $enumType; + + return $this; + } + + /** + * Adds a metadata parameter mappings. + */ + public function addMetadataParameterMapping(string|int $parameter, string $attribute): void + { + $this->metadataParameterMapping[$parameter] = $attribute; + } + + /** + * Checks whether a column with a given name is mapped as a scalar result. + * + * @todo Rename: isScalar + */ + public function isScalarResult(string $columnName): bool + { + return isset($this->scalarMappings[$columnName]); + } + + /** + * Gets the name of the class of an entity result or joined entity result, + * identified by the given unique alias. + * + * @psalm-return class-string + */ + public function getClassName(string $alias): string + { + return $this->aliasMap[$alias]; + } + + /** + * Gets the field alias for a column that is mapped as a scalar value. + * + * @param string $columnName The name of the column in the SQL result set. + */ + public function getScalarAlias(string $columnName): string|int + { + return $this->scalarMappings[$columnName]; + } + + /** + * Gets the name of the class that owns a field mapping for the specified column. + * + * @psalm-return class-string + */ + public function getDeclaringClass(string $columnName): string + { + return $this->declaringClasses[$columnName]; + } + + public function getRelation(string $alias): string + { + return $this->relationMap[$alias]; + } + + public function isRelation(string $alias): bool + { + return isset($this->relationMap[$alias]); + } + + /** + * Gets the alias of the class that owns a field mapping for the specified column. + */ + public function getEntityAlias(string $columnName): string + { + return $this->columnOwnerMap[$columnName]; + } + + /** + * Gets the parent alias of the given alias. + */ + public function getParentAlias(string $alias): string + { + return $this->parentAliasMap[$alias]; + } + + /** + * Checks whether the given alias has a parent alias. + */ + public function hasParentAlias(string $alias): bool + { + return isset($this->parentAliasMap[$alias]); + } + + /** + * Gets the field name for a column name. + */ + public function getFieldName(string $columnName): string + { + return $this->fieldMappings[$columnName]; + } + + /** @psalm-return array */ + public function getAliasMap(): array + { + return $this->aliasMap; + } + + /** + * Gets the number of different entities that appear in the mapped result. + * + * @psalm-return 0|positive-int + */ + public function getEntityResultCount(): int + { + return count($this->aliasMap); + } + + /** + * Checks whether this ResultSetMapping defines a mixed result. + * + * Mixed results can only occur in object and array (graph) hydration. In such a + * case a mixed result means that scalar values are mixed with objects/array in + * the result. + */ + public function isMixedResult(): bool + { + return $this->isMixed; + } + + /** + * Adds a meta column (foreign key or discriminator column) to the result set. + * + * @param string $alias The result alias with which the meta result should be placed in the result structure. + * @param string $columnName The name of the column in the SQL result set. + * @param string $fieldName The name of the field on the declaring class. + * @param string|null $type The column type + * + * @return $this + * + * @todo Make all methods of this class require all parameters and not infer anything + */ + public function addMetaResult( + string $alias, + string $columnName, + string $fieldName, + bool $isIdentifierColumn = false, + string|null $type = null, + ): static { + $this->metaMappings[$columnName] = $fieldName; + $this->columnOwnerMap[$columnName] = $alias; + + if ($isIdentifierColumn) { + $this->isIdentifierColumn[$alias][$columnName] = true; + } + + if ($type) { + $this->typeMappings[$columnName] = $type; + } + + return $this; + } +} diff --git a/vendor/doctrine/orm/src/Query/ResultSetMappingBuilder.php b/vendor/doctrine/orm/src/Query/ResultSetMappingBuilder.php new file mode 100644 index 0000000..f28f3a9 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/ResultSetMappingBuilder.php @@ -0,0 +1,281 @@ + queryColumnName). + * @psalm-param class-string $class + * @psalm-param array $renamedColumns + * @psalm-param self::COLUMN_RENAMING_*|null $renameMode + */ + public function addRootEntityFromClassMetadata( + string $class, + string $alias, + array $renamedColumns = [], + int|null $renameMode = null, + ): void { + $renameMode = $renameMode ?: $this->defaultRenameMode; + $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); + + $this->addEntityResult($class, $alias); + $this->addAllClassFields($class, $alias, $columnAliasMap); + } + + /** + * Adds a joined entity and all of its fields to the result set. + * + * @param string $class The class name of the joined entity. + * @param string $alias The unique alias to use for the joined entity. + * @param string $parentAlias The alias of the entity result that is the parent of this joined result. + * @param string $relation The association field that connects the parent entity result + * with the joined entity result. + * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). + * @psalm-param class-string $class + * @psalm-param array $renamedColumns + * @psalm-param self::COLUMN_RENAMING_*|null $renameMode + */ + public function addJoinedEntityFromClassMetadata( + string $class, + string $alias, + string $parentAlias, + string $relation, + array $renamedColumns = [], + int|null $renameMode = null, + ): void { + $renameMode = $renameMode ?: $this->defaultRenameMode; + $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); + + $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); + $this->addAllClassFields($class, $alias, $columnAliasMap); + } + + /** + * Adds all fields of the given class to the result set mapping (columns and meta fields). + * + * @param string[] $columnAliasMap + * @psalm-param array $columnAliasMap + * + * @throws InvalidArgumentException + */ + protected function addAllClassFields(string $class, string $alias, array $columnAliasMap = []): void + { + $classMetadata = $this->em->getClassMetadata($class); + $platform = $this->em->getConnection()->getDatabasePlatform(); + + if (! $this->isInheritanceSupported($classMetadata)) { + throw new InvalidArgumentException('ResultSetMapping builder does not currently support your inheritance scheme.'); + } + + foreach ($classMetadata->getColumnNames() as $columnName) { + $propertyName = $classMetadata->getFieldName($columnName); + $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]); + + if (isset($this->fieldMappings[$columnAlias])) { + throw new InvalidArgumentException(sprintf( + "The column '%s' conflicts with another column in the mapper.", + $columnName, + )); + } + + $this->addFieldResult($alias, $columnAlias, $propertyName); + + $enumType = $classMetadata->getFieldMapping($propertyName)->enumType ?? null; + if (! empty($enumType)) { + $this->addEnumResult($columnAlias, $enumType); + } + } + + foreach ($classMetadata->associationMappings as $associationMapping) { + if ($associationMapping->isToOneOwningSide()) { + $targetClass = $this->em->getClassMetadata($associationMapping->targetEntity); + $isIdentifier = isset($associationMapping->id) && $associationMapping->id === true; + + foreach ($associationMapping->joinColumns as $joinColumn) { + $columnName = $joinColumn->name; + $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]); + $columnType = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); + + if (isset($this->metaMappings[$columnAlias])) { + throw new InvalidArgumentException(sprintf( + "The column '%s' conflicts with another column in the mapper.", + $columnAlias, + )); + } + + $this->addMetaResult($alias, $columnAlias, $columnName, $isIdentifier, $columnType); + } + } + } + } + + private function isInheritanceSupported(ClassMetadata $classMetadata): bool + { + if ( + $classMetadata->isInheritanceTypeSingleTable() + && in_array($classMetadata->name, $classMetadata->discriminatorMap, true) + ) { + return true; + } + + return ! ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()); + } + + /** + * Gets column alias for a given column. + * + * @psalm-param array $customRenameColumns + * + * @psalm-assert self::COLUMN_RENAMING_* $mode + */ + private function getColumnAlias(string $columnName, int $mode, array $customRenameColumns): string + { + return match ($mode) { + self::COLUMN_RENAMING_INCREMENT => $columnName . $this->sqlCounter++, + self::COLUMN_RENAMING_CUSTOM => $customRenameColumns[$columnName] ?? $columnName, + self::COLUMN_RENAMING_NONE => $columnName, + default => throw new InvalidArgumentException(sprintf('%d is not a valid value for $mode', $mode)), + }; + } + + /** + * Retrieves a class columns and join columns aliases that are used in the SELECT clause. + * + * This depends on the renaming mode selected by the user. + * + * @psalm-param class-string $className + * @psalm-param self::COLUMN_RENAMING_* $mode + * @psalm-param array $customRenameColumns + * + * @return string[] + * @psalm-return array + */ + private function getColumnAliasMap( + string $className, + int $mode, + array $customRenameColumns, + ): array { + if ($customRenameColumns) { // for BC with 2.2-2.3 API + $mode = self::COLUMN_RENAMING_CUSTOM; + } + + $columnAlias = []; + $class = $this->em->getClassMetadata($className); + + foreach ($class->getColumnNames() as $columnName) { + $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); + } + + foreach ($class->associationMappings as $associationMapping) { + if ($associationMapping->isToOneOwningSide()) { + foreach ($associationMapping->joinColumns as $joinColumn) { + $columnName = $joinColumn->name; + $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); + } + } + } + + return $columnAlias; + } + + /** + * Generates the Select clause from this ResultSetMappingBuilder. + * + * Works only for all the entity results. The select parts for scalar + * expressions have to be written manually. + * + * @param string[] $tableAliases + * @psalm-param array $tableAliases + */ + public function generateSelectClause(array $tableAliases = []): string + { + $sql = ''; + + foreach ($this->columnOwnerMap as $columnName => $dqlAlias) { + $tableAlias = $tableAliases[$dqlAlias] ?? $dqlAlias; + + if ($sql !== '') { + $sql .= ', '; + } + + if (isset($this->fieldMappings[$columnName])) { + $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]); + $fieldName = $this->fieldMappings[$columnName]; + $classFieldMapping = $class->fieldMappings[$fieldName]; + $columnSql = $tableAlias . '.' . $classFieldMapping->columnName; + + $type = Type::getType($classFieldMapping->type); + $columnSql = $type->convertToPHPValueSQL($columnSql, $this->em->getConnection()->getDatabasePlatform()); + + $sql .= $columnSql; + } elseif (isset($this->metaMappings[$columnName])) { + $sql .= $tableAlias . '.' . $this->metaMappings[$columnName]; + } elseif (isset($this->discriminatorColumns[$dqlAlias])) { + $sql .= $tableAlias . '.' . $this->discriminatorColumns[$dqlAlias]; + } + + $sql .= ' AS ' . $columnName; + } + + return $sql; + } + + public function __toString(): string + { + return $this->generateSelectClause([]); + } +} diff --git a/vendor/doctrine/orm/src/Query/SqlWalker.php b/vendor/doctrine/orm/src/Query/SqlWalker.php new file mode 100644 index 0000000..c6f98c1 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/SqlWalker.php @@ -0,0 +1,2264 @@ +> + */ + private array $scalarResultAliasMap = []; + + /** + * Map from Table-Alias + Column-Name to OrderBy-Direction. + * + * @var array + */ + private array $orderedColumnsMap = []; + + /** + * Map from DQL-Alias + Field-Name to SQL Column Alias. + * + * @var array> + */ + private array $scalarFields = []; + + /** + * A list of classes that appear in non-scalar SelectExpressions. + * + * @psalm-var array + */ + private array $selectedClasses = []; + + /** + * The DQL alias of the root class of the currently traversed query. + * + * @psalm-var list + */ + private array $rootAliases = []; + + /** + * Flag that indicates whether to generate SQL table aliases in the SQL. + * These should only be generated for SELECT queries, not for UPDATE/DELETE. + */ + private bool $useSqlTableAliases = true; + + /** + * The database platform abstraction. + */ + private readonly AbstractPlatform $platform; + + /** + * The quote strategy. + */ + private readonly QuoteStrategy $quoteStrategy; + + /** @psalm-param array $queryComponents The query components (symbol table). */ + public function __construct( + private readonly Query $query, + private readonly ParserResult $parserResult, + private array $queryComponents, + ) { + $this->rsm = $parserResult->getResultSetMapping(); + $this->em = $query->getEntityManager(); + $this->conn = $this->em->getConnection(); + $this->platform = $this->conn->getDatabasePlatform(); + $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy(); + } + + /** + * Gets the Query instance used by the walker. + */ + public function getQuery(): Query + { + return $this->query; + } + + /** + * Gets the Connection used by the walker. + */ + public function getConnection(): Connection + { + return $this->conn; + } + + /** + * Gets the EntityManager used by the walker. + */ + public function getEntityManager(): EntityManagerInterface + { + return $this->em; + } + + /** + * Gets the information about a single query component. + * + * @param string $dqlAlias The DQL alias. + * + * @return mixed[] + * @psalm-return QueryComponent + */ + public function getQueryComponent(string $dqlAlias): array + { + return $this->queryComponents[$dqlAlias]; + } + + public function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata + { + return $this->queryComponents[$dqlAlias]['metadata'] + ?? throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias)); + } + + /** + * Returns internal queryComponents array. + * + * @return array + */ + public function getQueryComponents(): array + { + return $this->queryComponents; + } + + /** + * Sets or overrides a query component for a given dql alias. + * + * @psalm-param QueryComponent $queryComponent + */ + public function setQueryComponent(string $dqlAlias, array $queryComponent): void + { + $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token']; + + if (array_diff($requiredKeys, array_keys($queryComponent))) { + throw QueryException::invalidQueryComponent($dqlAlias); + } + + $this->queryComponents[$dqlAlias] = $queryComponent; + } + + /** + * Gets an executor that can be used to execute the result of this walker. + */ + public function getExecutor(AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement $statement): Exec\AbstractSqlExecutor + { + return match (true) { + $statement instanceof AST\SelectStatement + => new Exec\SingleSelectExecutor($statement, $this), + $statement instanceof AST\UpdateStatement + => $this->em->getClassMetadata($statement->updateClause->abstractSchemaName)->isInheritanceTypeJoined() + ? new Exec\MultiTableUpdateExecutor($statement, $this) + : new Exec\SingleTableDeleteUpdateExecutor($statement, $this), + $statement instanceof AST\DeleteStatement + => $this->em->getClassMetadata($statement->deleteClause->abstractSchemaName)->isInheritanceTypeJoined() + ? new Exec\MultiTableDeleteExecutor($statement, $this) + : new Exec\SingleTableDeleteUpdateExecutor($statement, $this), + }; + } + + /** + * Generates a unique, short SQL table alias. + */ + public function getSQLTableAlias(string $tableName, string $dqlAlias = ''): string + { + $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : ''; + + if (! isset($this->tableAliasMap[$tableName])) { + $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't') + . $this->tableAliasCounter++ . '_'; + } + + return $this->tableAliasMap[$tableName]; + } + + /** + * Forces the SqlWalker to use a specific alias for a table name, rather than + * generating an alias on its own. + */ + public function setSQLTableAlias(string $tableName, string $alias, string $dqlAlias = ''): string + { + $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : ''; + + $this->tableAliasMap[$tableName] = $alias; + + return $alias; + } + + /** + * Gets an SQL column alias for a column name. + */ + public function getSQLColumnAlias(string $columnName): string + { + return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform); + } + + /** + * Generates the SQL JOINs that are necessary for Class Table Inheritance + * for the given class. + */ + private function generateClassTableInheritanceJoins( + ClassMetadata $class, + string $dqlAlias, + ): string { + $sql = ''; + + $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); + + // INNER JOIN parent class tables + foreach ($class->parentClasses as $parentClassName) { + $parentClass = $this->em->getClassMetadata($parentClassName); + $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias); + + // If this is a joined association we must use left joins to preserve the correct result. + $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; + $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; + + $sqlParts = []; + + foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { + $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; + } + + // Add filters on the root class + $sqlParts[] = $this->generateFilterConditionSQL($parentClass, $tableAlias); + + $sql .= implode(' AND ', array_filter($sqlParts)); + } + + // LEFT JOIN child class tables + foreach ($class->subClasses as $subClassName) { + $subClass = $this->em->getClassMetadata($subClassName); + $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); + + $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; + + $sqlParts = []; + + foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) { + $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; + } + + $sql .= implode(' AND ', $sqlParts); + } + + return $sql; + } + + private function generateOrderedCollectionOrderByItems(): string + { + $orderedColumns = []; + + foreach ($this->selectedClasses as $selectedClass) { + $dqlAlias = $selectedClass['dqlAlias']; + $qComp = $this->queryComponents[$dqlAlias]; + + if (! isset($qComp['relation']->orderBy)) { + continue; + } + + assert(isset($qComp['metadata'])); + $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name); + + foreach ($qComp['relation']->orderBy as $fieldName => $orientation) { + $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform); + $tableName = $qComp['metadata']->isInheritanceTypeJoined() + ? $persister->getOwningTable($fieldName) + : $qComp['metadata']->getTableName(); + + $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName; + + // OrderByClause should replace an ordered relation. see - DDC-2475 + if (isset($this->orderedColumnsMap[$orderedColumn])) { + continue; + } + + $this->orderedColumnsMap[$orderedColumn] = $orientation; + $orderedColumns[] = $orderedColumn . ' ' . $orientation; + } + } + + return implode(', ', $orderedColumns); + } + + /** + * Generates a discriminator column SQL condition for the class with the given DQL alias. + * + * @psalm-param list $dqlAliases List of root DQL aliases to inspect for discriminator restrictions. + */ + private function generateDiscriminatorColumnConditionSQL(array $dqlAliases): string + { + $sqlParts = []; + + foreach ($dqlAliases as $dqlAlias) { + $class = $this->getMetadataForDqlAlias($dqlAlias); + + if (! $class->isInheritanceTypeSingleTable()) { + continue; + } + + $sqlTableAlias = $this->useSqlTableAliases + ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' + : ''; + + $conn = $this->em->getConnection(); + $values = []; + + if ($class->discriminatorValue !== null) { // discriminators can be 0 + $values[] = $class->getDiscriminatorColumn()->type === 'integer' && is_int($class->discriminatorValue) + ? $class->discriminatorValue + : $conn->quote((string) $class->discriminatorValue); + } + + foreach ($class->subClasses as $subclassName) { + $subclassMetadata = $this->em->getClassMetadata($subclassName); + + // Abstract entity classes show up in the list of subClasses, but may be omitted + // from the discriminator map. In that case, they have a null discriminator value. + if ($subclassMetadata->discriminatorValue === null) { + continue; + } + + $values[] = $subclassMetadata->getDiscriminatorColumn()->type === 'integer' && is_int($subclassMetadata->discriminatorValue) + ? $subclassMetadata->discriminatorValue + : $conn->quote((string) $subclassMetadata->discriminatorValue); + } + + if ($values !== []) { + $sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')'; + } else { + $sqlParts[] = '1=0'; // impossible condition + } + } + + $sql = implode(' AND ', $sqlParts); + + return count($sqlParts) > 1 ? '(' . $sql . ')' : $sql; + } + + /** + * Generates the filter SQL for a given entity and table alias. + */ + private function generateFilterConditionSQL( + ClassMetadata $targetEntity, + string $targetTableAlias, + ): string { + if (! $this->em->hasFilters()) { + return ''; + } + + switch ($targetEntity->inheritanceType) { + case ClassMetadata::INHERITANCE_TYPE_NONE: + break; + case ClassMetadata::INHERITANCE_TYPE_JOINED: + // The classes in the inheritance will be added to the query one by one, + // but only the root node is getting filtered + if ($targetEntity->name !== $targetEntity->rootEntityName) { + return ''; + } + + break; + case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE: + // With STI the table will only be queried once, make sure that the filters + // are added to the root entity + $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); + break; + default: + //@todo: throw exception? + return ''; + } + + $filterClauses = []; + foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { + $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); + if ($filterExpr !== '') { + $filterClauses[] = '(' . $filterExpr . ')'; + } + } + + return implode(' AND ', $filterClauses); + } + + /** + * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. + */ + public function walkSelectStatement(AST\SelectStatement $selectStatement): string + { + $limit = $this->query->getMaxResults(); + $offset = $this->query->getFirstResult(); + $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE; + $sql = $this->walkSelectClause($selectStatement->selectClause) + . $this->walkFromClause($selectStatement->fromClause) + . $this->walkWhereClause($selectStatement->whereClause); + + if ($selectStatement->groupByClause) { + $sql .= $this->walkGroupByClause($selectStatement->groupByClause); + } + + if ($selectStatement->havingClause) { + $sql .= $this->walkHavingClause($selectStatement->havingClause); + } + + if ($selectStatement->orderByClause) { + $sql .= $this->walkOrderByClause($selectStatement->orderByClause); + } + + $orderBySql = $this->generateOrderedCollectionOrderByItems(); + if (! $selectStatement->orderByClause && $orderBySql) { + $sql .= ' ORDER BY ' . $orderBySql; + } + + $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset); + + if ($lockMode === LockMode::NONE) { + return $sql; + } + + if ($lockMode === LockMode::PESSIMISTIC_READ) { + return $sql . ' ' . $this->getReadLockSQL($this->platform); + } + + if ($lockMode === LockMode::PESSIMISTIC_WRITE) { + return $sql . ' ' . $this->getWriteLockSQL($this->platform); + } + + if ($lockMode !== LockMode::OPTIMISTIC) { + throw QueryException::invalidLockMode(); + } + + foreach ($this->selectedClasses as $selectedClass) { + if (! $selectedClass['class']->isVersioned) { + throw OptimisticLockException::lockFailed($selectedClass['class']->name); + } + } + + return $sql; + } + + /** + * Walks down a UpdateStatement AST node, thereby generating the appropriate SQL. + */ + public function walkUpdateStatement(AST\UpdateStatement $updateStatement): string + { + $this->useSqlTableAliases = false; + $this->rsm->isSelect = false; + + return $this->walkUpdateClause($updateStatement->updateClause) + . $this->walkWhereClause($updateStatement->whereClause); + } + + /** + * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. + */ + public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): string + { + $this->useSqlTableAliases = false; + $this->rsm->isSelect = false; + + return $this->walkDeleteClause($deleteStatement->deleteClause) + . $this->walkWhereClause($deleteStatement->whereClause); + } + + /** + * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL. + * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers. + */ + public function walkEntityIdentificationVariable(string $identVariable): string + { + $class = $this->getMetadataForDqlAlias($identVariable); + $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable); + $sqlParts = []; + + foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { + $sqlParts[] = $tableAlias . '.' . $columnName; + } + + return implode(', ', $sqlParts); + } + + /** + * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. + */ + public function walkIdentificationVariable(string $identificationVariable, string|null $fieldName = null): string + { + $class = $this->getMetadataForDqlAlias($identificationVariable); + + if ( + $fieldName !== null && $class->isInheritanceTypeJoined() && + isset($class->fieldMappings[$fieldName]->inherited) + ) { + $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]->inherited); + } + + return $this->getSQLTableAlias($class->getTableName(), $identificationVariable); + } + + /** + * Walks down a PathExpression AST node, thereby generating the appropriate SQL. + */ + public function walkPathExpression(AST\PathExpression $pathExpr): string + { + $sql = ''; + assert($pathExpr->field !== null); + + switch ($pathExpr->type) { + case AST\PathExpression::TYPE_STATE_FIELD: + $fieldName = $pathExpr->field; + $dqlAlias = $pathExpr->identificationVariable; + $class = $this->getMetadataForDqlAlias($dqlAlias); + + if ($this->useSqlTableAliases) { + $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.'; + } + + $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); + break; + + case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: + // 1- the owning side: + // Just use the foreign key, i.e. u.group_id + $fieldName = $pathExpr->field; + $dqlAlias = $pathExpr->identificationVariable; + $class = $this->getMetadataForDqlAlias($dqlAlias); + + if (isset($class->associationMappings[$fieldName]->inherited)) { + $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]->inherited); + } + + $assoc = $class->associationMappings[$fieldName]; + + if (! $assoc->isOwningSide()) { + throw QueryException::associationPathInverseSideNotSupported($pathExpr); + } + + assert($assoc->isToOneOwningSide()); + + // COMPOSITE KEYS NOT (YET?) SUPPORTED + if (count($assoc->sourceToTargetKeyColumns) > 1) { + throw QueryException::associationPathCompositeKeyNotSupported(); + } + + if ($this->useSqlTableAliases) { + $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'; + } + + $sql .= reset($assoc->targetToSourceKeyColumns); + break; + + default: + throw QueryException::invalidPathExpression($pathExpr); + } + + return $sql; + } + + /** + * Walks down a SelectClause AST node, thereby generating the appropriate SQL. + */ + public function walkSelectClause(AST\SelectClause $selectClause): string + { + $sql = 'SELECT ' . ($selectClause->isDistinct ? 'DISTINCT ' : ''); + $sqlSelectExpressions = array_filter(array_map($this->walkSelectExpression(...), $selectClause->selectExpressions)); + + if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && $selectClause->isDistinct) { + $this->query->setHint(self::HINT_DISTINCT, true); + } + + $addMetaColumns = $this->query->getHydrationMode() === Query::HYDRATE_OBJECT + || $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS); + + foreach ($this->selectedClasses as $selectedClass) { + $class = $selectedClass['class']; + $dqlAlias = $selectedClass['dqlAlias']; + $resultAlias = $selectedClass['resultAlias']; + + // Register as entity or joined entity result + if (! isset($this->queryComponents[$dqlAlias]['relation'])) { + $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias); + } else { + assert(isset($this->queryComponents[$dqlAlias]['parent'])); + + $this->rsm->addJoinedEntityResult( + $class->name, + $dqlAlias, + $this->queryComponents[$dqlAlias]['parent'], + $this->queryComponents[$dqlAlias]['relation']->fieldName, + ); + } + + if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { + // Add discriminator columns to SQL + $rootClass = $this->em->getClassMetadata($class->rootEntityName); + $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); + $discrColumn = $rootClass->getDiscriminatorColumn(); + $columnAlias = $this->getSQLColumnAlias($discrColumn->name); + + $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn->name . ' AS ' . $columnAlias; + + $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); + $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn->fieldName, false, $discrColumn->type); + if (! empty($discrColumn->enumType)) { + $this->rsm->addEnumResult($columnAlias, $discrColumn->enumType); + } + } + + // Add foreign key columns to SQL, if necessary + if (! $addMetaColumns && ! $class->containsForeignIdentifier) { + continue; + } + + // Add foreign key columns of class and also parent classes + foreach ($class->associationMappings as $assoc) { + if ( + ! $assoc->isToOneOwningSide() + || ( ! $addMetaColumns && ! isset($assoc->id)) + ) { + continue; + } + + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + $isIdentifier = (isset($assoc->id) && $assoc->id === true); + $owningClass = isset($assoc->inherited) ? $this->em->getClassMetadata($assoc->inherited) : $class; + $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias); + + foreach ($assoc->joinColumns as $joinColumn) { + $columnName = $joinColumn->name; + $columnAlias = $this->getSQLColumnAlias($columnName); + $columnType = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); + + $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; + + $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType); + } + } + + // Add foreign key columns to SQL, if necessary + if (! $addMetaColumns) { + continue; + } + + // Add foreign key columns of subclasses + foreach ($class->subClasses as $subClassName) { + $subClass = $this->em->getClassMetadata($subClassName); + $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); + + foreach ($subClass->associationMappings as $assoc) { + // Skip if association is inherited + if (isset($assoc->inherited)) { + continue; + } + + if ($assoc->isToOneOwningSide()) { + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + + foreach ($assoc->joinColumns as $joinColumn) { + $columnName = $joinColumn->name; + $columnAlias = $this->getSQLColumnAlias($columnName); + $columnType = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); + + $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform); + $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; + + $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType); + } + } + } + } + } + + return $sql . implode(', ', $sqlSelectExpressions); + } + + /** + * Walks down a FromClause AST node, thereby generating the appropriate SQL. + */ + public function walkFromClause(AST\FromClause $fromClause): string + { + $identificationVarDecls = $fromClause->identificationVariableDeclarations; + $sqlParts = []; + + foreach ($identificationVarDecls as $identificationVariableDecl) { + $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl); + } + + return ' FROM ' . implode(', ', $sqlParts); + } + + /** + * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL. + */ + public function walkIdentificationVariableDeclaration(AST\IdentificationVariableDeclaration $identificationVariableDecl): string + { + $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration); + + if ($identificationVariableDecl->indexBy) { + $this->walkIndexBy($identificationVariableDecl->indexBy); + } + + foreach ($identificationVariableDecl->joins as $join) { + $sql .= $this->walkJoin($join); + } + + return $sql; + } + + /** + * Walks down a IndexBy AST node. + */ + public function walkIndexBy(AST\IndexBy $indexBy): void + { + $pathExpression = $indexBy->singleValuedPathExpression; + $alias = $pathExpression->identificationVariable; + assert($pathExpression->field !== null); + + switch ($pathExpression->type) { + case AST\PathExpression::TYPE_STATE_FIELD: + $field = $pathExpression->field; + break; + + case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: + // Just use the foreign key, i.e. u.group_id + $fieldName = $pathExpression->field; + $class = $this->getMetadataForDqlAlias($alias); + + if (isset($class->associationMappings[$fieldName]->inherited)) { + $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]->inherited); + } + + $association = $class->associationMappings[$fieldName]; + + if (! $association->isOwningSide()) { + throw QueryException::associationPathInverseSideNotSupported($pathExpression); + } + + assert($association->isToOneOwningSide()); + + if (count($association->sourceToTargetKeyColumns) > 1) { + throw QueryException::associationPathCompositeKeyNotSupported(); + } + + $field = reset($association->targetToSourceKeyColumns); + break; + + default: + throw QueryException::invalidPathExpression($pathExpression); + } + + if (isset($this->scalarFields[$alias][$field])) { + $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]); + + return; + } + + $this->rsm->addIndexBy($alias, $field); + } + + /** + * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL. + */ + public function walkRangeVariableDeclaration(AST\RangeVariableDeclaration $rangeVariableDeclaration): string + { + return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false); + } + + /** + * Generate appropriate SQL for RangeVariableDeclaration AST node + */ + private function generateRangeVariableDeclarationSQL( + AST\RangeVariableDeclaration $rangeVariableDeclaration, + bool $buildNestedJoins, + ): string { + $class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName); + $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable; + + if ($rangeVariableDeclaration->isRoot) { + $this->rootAliases[] = $dqlAlias; + } + + $sql = $this->platform->appendLockHint( + $this->quoteStrategy->getTableName($class, $this->platform) . ' ' . + $this->getSQLTableAlias($class->getTableName(), $dqlAlias), + $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE, + ); + + if (! $class->isInheritanceTypeJoined()) { + return $sql; + } + + $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias); + + if (! $buildNestedJoins) { + return $sql . $classTableInheritanceJoins; + } + + return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')'; + } + + /** + * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL. + * + * @psalm-param AST\Join::JOIN_TYPE_* $joinType + * + * @throws QueryException + */ + public function walkJoinAssociationDeclaration( + AST\JoinAssociationDeclaration $joinAssociationDeclaration, + int $joinType = AST\Join::JOIN_TYPE_INNER, + AST\ConditionalExpression|AST\Phase2OptimizableConditional|null $condExpr = null, + ): string { + $sql = ''; + + $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression; + $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable; + $indexBy = $joinAssociationDeclaration->indexBy; + + $relation = $this->queryComponents[$joinedDqlAlias]['relation'] ?? null; + assert($relation !== null); + $targetClass = $this->em->getClassMetadata($relation->targetEntity); + $sourceClass = $this->em->getClassMetadata($relation->sourceEntity); + $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); + + $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias); + $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable); + + // Ensure we got the owning side, since it has all mapping info + $assoc = $this->em->getMetadataFactory()->getOwningSide($relation); + + if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && (! $this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) { + if ($relation->isToMany()) { + throw QueryException::iterateWithFetchJoinNotAllowed($assoc); + } + } + + $fetchMode = $this->query->getHint('fetchMode')[$assoc->sourceEntity][$assoc->fieldName] ?? $relation->fetch; + + if ($fetchMode === ClassMetadata::FETCH_EAGER && $condExpr !== null) { + throw QueryException::eagerFetchJoinWithNotAllowed($assoc->sourceEntity, $assoc->fieldName); + } + + // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot + // be the owning side and previously we ensured that $assoc is always the owning side of the associations. + // The owning side is necessary at this point because only it contains the JoinColumn information. + switch (true) { + case $assoc->isToOne(): + assert($assoc->isToOneOwningSide()); + $conditions = []; + + foreach ($assoc->joinColumns as $joinColumn) { + $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); + + if ($relation->isOwningSide()) { + $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; + + continue; + } + + $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn; + } + + // Apply remaining inheritance restrictions + $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]); + + if ($discrSql) { + $conditions[] = $discrSql; + } + + // Apply the filters + $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); + + if ($filterExpr) { + $conditions[] = $filterExpr; + } + + $targetTableJoin = [ + 'table' => $targetTableName . ' ' . $targetTableAlias, + 'condition' => implode(' AND ', $conditions), + ]; + break; + + case $assoc->isManyToMany(): + // Join relation table + $joinTable = $assoc->joinTable; + $joinTableAlias = $this->getSQLTableAlias($joinTable->name, $joinedDqlAlias); + $joinTableName = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform); + + $conditions = []; + $relationColumns = $relation->isOwningSide() + ? $assoc->joinTable->joinColumns + : $assoc->joinTable->inverseJoinColumns; + + foreach ($relationColumns as $joinColumn) { + $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); + + $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; + } + + $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions); + + // Join target table + $sql .= $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN '; + + $conditions = []; + $relationColumns = $relation->isOwningSide() + ? $assoc->joinTable->inverseJoinColumns + : $assoc->joinTable->joinColumns; + + foreach ($relationColumns as $joinColumn) { + $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); + + $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; + } + + // Apply remaining inheritance restrictions + $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]); + + if ($discrSql) { + $conditions[] = $discrSql; + } + + // Apply the filters + $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); + + if ($filterExpr) { + $conditions[] = $filterExpr; + } + + $targetTableJoin = [ + 'table' => $targetTableName . ' ' . $targetTableAlias, + 'condition' => implode(' AND ', $conditions), + ]; + break; + + default: + throw new BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY'); + } + + // Handle WITH clause + $withCondition = $condExpr === null ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')'); + + if ($targetClass->isInheritanceTypeJoined()) { + $ctiJoins = $this->generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); + // If we have WITH condition, we need to build nested joins for target class table and cti joins + if ($withCondition && $ctiJoins) { + $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition']; + } else { + $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins; + } + } else { + $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition']; + } + + if ($withCondition) { + $sql .= ' AND ' . $withCondition; + } + + // Apply the indexes + if ($indexBy) { + // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. + $this->walkIndexBy($indexBy); + } elseif ($relation->isIndexed()) { + $this->rsm->addIndexBy($joinedDqlAlias, $relation->indexBy()); + } + + return $sql; + } + + /** + * Walks down a FunctionNode AST node, thereby generating the appropriate SQL. + */ + public function walkFunction(AST\Functions\FunctionNode $function): string + { + return $function->getSql($this); + } + + /** + * Walks down an OrderByClause AST node, thereby generating the appropriate SQL. + */ + public function walkOrderByClause(AST\OrderByClause $orderByClause): string + { + $orderByItems = array_map($this->walkOrderByItem(...), $orderByClause->orderByItems); + + $collectionOrderByItems = $this->generateOrderedCollectionOrderByItems(); + if ($collectionOrderByItems !== '') { + $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems); + } + + return ' ORDER BY ' . implode(', ', $orderByItems); + } + + /** + * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. + */ + public function walkOrderByItem(AST\OrderByItem $orderByItem): string + { + $type = strtoupper($orderByItem->type); + $expr = $orderByItem->expression; + $sql = $expr instanceof AST\Node + ? $expr->dispatch($this) + : $this->walkResultVariable($this->queryComponents[$expr]['token']->value); + + $this->orderedColumnsMap[$sql] = $type; + + if ($expr instanceof AST\Subselect) { + return '(' . $sql . ') ' . $type; + } + + return $sql . ' ' . $type; + } + + /** + * Walks down a HavingClause AST node, thereby generating the appropriate SQL. + */ + public function walkHavingClause(AST\HavingClause $havingClause): string + { + return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression); + } + + /** + * Walks down a Join AST node and creates the corresponding SQL. + */ + public function walkJoin(AST\Join $join): string + { + $joinType = $join->joinType; + $joinDeclaration = $join->joinAssociationDeclaration; + + $sql = $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER + ? ' LEFT JOIN ' + : ' INNER JOIN '; + + switch (true) { + case $joinDeclaration instanceof AST\RangeVariableDeclaration: + $class = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName); + $dqlAlias = $joinDeclaration->aliasIdentificationVariable; + $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); + $conditions = []; + + if ($join->conditionalExpression) { + $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')'; + } + + $isUnconditionalJoin = $conditions === []; + $condExprConjunction = $class->isInheritanceTypeJoined() && $joinType !== AST\Join::JOIN_TYPE_LEFT && $joinType !== AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin + ? ' AND ' + : ' ON '; + + $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, ! $isUnconditionalJoin); + + // Apply remaining inheritance restrictions + $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]); + + if ($discrSql) { + $conditions[] = $discrSql; + } + + // Apply the filters + $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias); + + if ($filterExpr) { + $conditions[] = $filterExpr; + } + + if ($conditions) { + $sql .= $condExprConjunction . implode(' AND ', $conditions); + } + + break; + + case $joinDeclaration instanceof AST\JoinAssociationDeclaration: + $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression); + break; + } + + return $sql; + } + + /** + * Walks down a CoalesceExpression AST node and generates the corresponding SQL. + */ + public function walkCoalesceExpression(AST\CoalesceExpression $coalesceExpression): string + { + $sql = 'COALESCE('; + + $scalarExpressions = []; + + foreach ($coalesceExpression->scalarExpressions as $scalarExpression) { + $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression); + } + + return $sql . implode(', ', $scalarExpressions) . ')'; + } + + /** + * Walks down a NullIfExpression AST node and generates the corresponding SQL. + */ + public function walkNullIfExpression(AST\NullIfExpression $nullIfExpression): string + { + $firstExpression = is_string($nullIfExpression->firstExpression) + ? $this->conn->quote($nullIfExpression->firstExpression) + : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression); + + $secondExpression = is_string($nullIfExpression->secondExpression) + ? $this->conn->quote($nullIfExpression->secondExpression) + : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression); + + return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')'; + } + + /** + * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL. + */ + public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression): string + { + $sql = 'CASE'; + + foreach ($generalCaseExpression->whenClauses as $whenClause) { + $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression); + $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression); + } + + $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END'; + + return $sql; + } + + /** + * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL. + */ + public function walkSimpleCaseExpression(AST\SimpleCaseExpression $simpleCaseExpression): string + { + $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand); + + foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) { + $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression); + $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression); + } + + $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END'; + + return $sql; + } + + /** + * Walks down a SelectExpression AST node and generates the corresponding SQL. + */ + public function walkSelectExpression(AST\SelectExpression $selectExpression): string + { + $sql = ''; + $expr = $selectExpression->expression; + $hidden = $selectExpression->hiddenAliasResultVariable; + + switch (true) { + case $expr instanceof AST\PathExpression: + if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { + throw QueryException::invalidPathExpression($expr); + } + + assert($expr->field !== null); + $fieldName = $expr->field; + $dqlAlias = $expr->identificationVariable; + $class = $this->getMetadataForDqlAlias($dqlAlias); + + $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName; + $tableName = $class->isInheritanceTypeJoined() + ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) + : $class->getTableName(); + + $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); + $fieldMapping = $class->fieldMappings[$fieldName]; + $columnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); + $columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName); + $col = $sqlTableAlias . '.' . $columnName; + + $type = Type::getType($fieldMapping->type); + $col = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform()); + + $sql .= $col . ' AS ' . $columnAlias; + + $this->scalarResultAliasMap[$resultAlias] = $columnAlias; + + if (! $hidden) { + $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping->type); + $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias; + + if (! empty($fieldMapping->enumType)) { + $this->rsm->addEnumResult($columnAlias, $fieldMapping->enumType); + } + } + + break; + + case $expr instanceof AST\AggregateExpression: + case $expr instanceof AST\Functions\FunctionNode: + case $expr instanceof AST\SimpleArithmeticExpression: + case $expr instanceof AST\ArithmeticTerm: + case $expr instanceof AST\ArithmeticFactor: + case $expr instanceof AST\ParenthesisExpression: + case $expr instanceof AST\Literal: + case $expr instanceof AST\NullIfExpression: + case $expr instanceof AST\CoalesceExpression: + case $expr instanceof AST\GeneralCaseExpression: + case $expr instanceof AST\SimpleCaseExpression: + $columnAlias = $this->getSQLColumnAlias('sclr'); + $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; + + $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; + + $this->scalarResultAliasMap[$resultAlias] = $columnAlias; + + if ($hidden) { + break; + } + + if (! $expr instanceof Query\AST\TypedExpression) { + // Conceptually we could resolve field type here by traverse through AST to retrieve field type, + // but this is not a feasible solution; assume 'string'. + $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); + + break; + } + + $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getTypeRegistry()->lookupName($expr->getReturnType())); + + break; + + case $expr instanceof AST\Subselect: + $columnAlias = $this->getSQLColumnAlias('sclr'); + $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; + + $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; + + $this->scalarResultAliasMap[$resultAlias] = $columnAlias; + + if (! $hidden) { + // We cannot resolve field type here; assume 'string'. + $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); + } + + break; + + case $expr instanceof AST\NewObjectExpression: + $sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable); + break; + + default: + $dqlAlias = $expr; + $class = $this->getMetadataForDqlAlias($dqlAlias); + $resultAlias = $selectExpression->fieldIdentificationVariable ?: null; + + if (! isset($this->selectedClasses[$dqlAlias])) { + $this->selectedClasses[$dqlAlias] = [ + 'class' => $class, + 'dqlAlias' => $dqlAlias, + 'resultAlias' => $resultAlias, + ]; + } + + $sqlParts = []; + + // Select all fields from the queried class + foreach ($class->fieldMappings as $fieldName => $mapping) { + $tableName = isset($mapping->inherited) + ? $this->em->getClassMetadata($mapping->inherited)->getTableName() + : $class->getTableName(); + + $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); + $columnAlias = $this->getSQLColumnAlias($mapping->columnName); + $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); + + $col = $sqlTableAlias . '.' . $quotedColumnName; + + $type = Type::getType($mapping->type); + $col = $type->convertToPHPValueSQL($col, $this->platform); + + $sqlParts[] = $col . ' AS ' . $columnAlias; + + $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; + + $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); + + if (! empty($mapping->enumType)) { + $this->rsm->addEnumResult($columnAlias, $mapping->enumType); + } + } + + // Add any additional fields of subclasses (excluding inherited fields) + // 1) on Single Table Inheritance: always, since its marginal overhead + // 2) on Class Table Inheritance + foreach ($class->subClasses as $subClassName) { + $subClass = $this->em->getClassMetadata($subClassName); + $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); + + foreach ($subClass->fieldMappings as $fieldName => $mapping) { + if (isset($mapping->inherited)) { + continue; + } + + $columnAlias = $this->getSQLColumnAlias($mapping->columnName); + $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform); + + $col = $sqlTableAlias . '.' . $quotedColumnName; + + $type = Type::getType($mapping->type); + $col = $type->convertToPHPValueSQL($col, $this->platform); + + $sqlParts[] = $col . ' AS ' . $columnAlias; + + $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; + + $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); + } + } + + $sql .= implode(', ', $sqlParts); + } + + return $sql; + } + + public function walkQuantifiedExpression(AST\QuantifiedExpression $qExpr): string + { + return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')'; + } + + /** + * Walks down a Subselect AST node, thereby generating the appropriate SQL. + */ + public function walkSubselect(AST\Subselect $subselect): string + { + $useAliasesBefore = $this->useSqlTableAliases; + $rootAliasesBefore = $this->rootAliases; + + $this->rootAliases = []; // reset the rootAliases for the subselect + $this->useSqlTableAliases = true; + + $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); + $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); + $sql .= $this->walkWhereClause($subselect->whereClause); + + $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; + $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; + $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; + + $this->rootAliases = $rootAliasesBefore; // put the main aliases back + $this->useSqlTableAliases = $useAliasesBefore; + + return $sql; + } + + /** + * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. + */ + public function walkSubselectFromClause(AST\SubselectFromClause $subselectFromClause): string + { + $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations; + $sqlParts = []; + + foreach ($identificationVarDecls as $subselectIdVarDecl) { + $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl); + } + + return ' FROM ' . implode(', ', $sqlParts); + } + + /** + * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. + */ + public function walkSimpleSelectClause(AST\SimpleSelectClause $simpleSelectClause): string + { + return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '') + . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression); + } + + public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression): string + { + return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this)); + } + + public function walkNewObject(AST\NewObjectExpression $newObjectExpression, string|null $newObjectResultAlias = null): string + { + $sqlSelectExpressions = []; + $objIndex = $newObjectResultAlias ?: $this->newObjectCounter++; + + foreach ($newObjectExpression->args as $argIndex => $e) { + $resultAlias = $this->scalarResultCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); + $fieldType = 'string'; + + switch (true) { + case $e instanceof AST\NewObjectExpression: + $sqlSelectExpressions[] = $e->dispatch($this); + break; + + case $e instanceof AST\Subselect: + $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias; + break; + + case $e instanceof AST\PathExpression: + assert($e->field !== null); + $dqlAlias = $e->identificationVariable; + $class = $this->getMetadataForDqlAlias($dqlAlias); + $fieldName = $e->field; + $fieldMapping = $class->fieldMappings[$fieldName]; + $fieldType = $fieldMapping->type; + $col = trim($e->dispatch($this)); + + $type = Type::getType($fieldType); + $col = $type->convertToPHPValueSQL($col, $this->platform); + + $sqlSelectExpressions[] = $col . ' AS ' . $columnAlias; + + if (! empty($fieldMapping->enumType)) { + $this->rsm->addEnumResult($columnAlias, $fieldMapping->enumType); + } + + break; + + case $e instanceof AST\Literal: + switch ($e->type) { + case AST\Literal::BOOLEAN: + $fieldType = 'boolean'; + break; + + case AST\Literal::NUMERIC: + $fieldType = is_float($e->value) ? 'float' : 'integer'; + break; + } + + $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias; + break; + + default: + $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias; + break; + } + + $this->scalarResultAliasMap[$resultAlias] = $columnAlias; + $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType); + + $this->rsm->newObjectMappings[$columnAlias] = [ + 'className' => $newObjectExpression->className, + 'objIndex' => $objIndex, + 'argIndex' => $argIndex, + ]; + } + + return implode(', ', $sqlSelectExpressions); + } + + /** + * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. + */ + public function walkSimpleSelectExpression(AST\SimpleSelectExpression $simpleSelectExpression): string + { + $expr = $simpleSelectExpression->expression; + $sql = ' '; + + switch (true) { + case $expr instanceof AST\PathExpression: + $sql .= $this->walkPathExpression($expr); + break; + + case $expr instanceof AST\Subselect: + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; + + $columnAlias = 'sclr' . $this->aliasCounter++; + $this->scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; + break; + + case $expr instanceof AST\Functions\FunctionNode: + case $expr instanceof AST\SimpleArithmeticExpression: + case $expr instanceof AST\ArithmeticTerm: + case $expr instanceof AST\ArithmeticFactor: + case $expr instanceof AST\Literal: + case $expr instanceof AST\NullIfExpression: + case $expr instanceof AST\CoalesceExpression: + case $expr instanceof AST\GeneralCaseExpression: + case $expr instanceof AST\SimpleCaseExpression: + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; + + $columnAlias = $this->getSQLColumnAlias('sclr'); + $this->scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; + break; + + case $expr instanceof AST\ParenthesisExpression: + $sql .= $this->walkParenthesisExpression($expr); + break; + + default: // IdentificationVariable + $sql .= $this->walkEntityIdentificationVariable($expr); + break; + } + + return $sql; + } + + /** + * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. + */ + public function walkAggregateExpression(AST\AggregateExpression $aggExpression): string + { + return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '') + . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')'; + } + + /** + * Walks down a GroupByClause AST node, thereby generating the appropriate SQL. + */ + public function walkGroupByClause(AST\GroupByClause $groupByClause): string + { + $sqlParts = []; + + foreach ($groupByClause->groupByItems as $groupByItem) { + $sqlParts[] = $this->walkGroupByItem($groupByItem); + } + + return ' GROUP BY ' . implode(', ', $sqlParts); + } + + /** + * Walks down a GroupByItem AST node, thereby generating the appropriate SQL. + */ + public function walkGroupByItem(AST\PathExpression|string $groupByItem): string + { + // StateFieldPathExpression + if (! is_string($groupByItem)) { + return $this->walkPathExpression($groupByItem); + } + + // ResultVariable + if (isset($this->queryComponents[$groupByItem]['resultVariable'])) { + $resultVariable = $this->queryComponents[$groupByItem]['resultVariable']; + + if ($resultVariable instanceof AST\PathExpression) { + return $this->walkPathExpression($resultVariable); + } + + if ($resultVariable instanceof AST\Node && isset($resultVariable->pathExpression)) { + return $this->walkPathExpression($resultVariable->pathExpression); + } + + return $this->walkResultVariable($groupByItem); + } + + // IdentificationVariable + $sqlParts = []; + + foreach ($this->getMetadataForDqlAlias($groupByItem)->fieldNames as $field) { + $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field); + $item->type = AST\PathExpression::TYPE_STATE_FIELD; + + $sqlParts[] = $this->walkPathExpression($item); + } + + foreach ($this->getMetadataForDqlAlias($groupByItem)->associationMappings as $mapping) { + if ($mapping->isToOneOwningSide()) { + $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping->fieldName); + $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; + + $sqlParts[] = $this->walkPathExpression($item); + } + } + + return implode(', ', $sqlParts); + } + + /** + * Walks down a DeleteClause AST node, thereby generating the appropriate SQL. + */ + public function walkDeleteClause(AST\DeleteClause $deleteClause): string + { + $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); + $tableName = $class->getTableName(); + $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform); + + $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable); + $this->rootAliases[] = $deleteClause->aliasIdentificationVariable; + + return $sql; + } + + /** + * Walks down an UpdateClause AST node, thereby generating the appropriate SQL. + */ + public function walkUpdateClause(AST\UpdateClause $updateClause): string + { + $class = $this->em->getClassMetadata($updateClause->abstractSchemaName); + $tableName = $class->getTableName(); + $sql = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform); + + $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable); + $this->rootAliases[] = $updateClause->aliasIdentificationVariable; + + return $sql . ' SET ' . implode(', ', array_map($this->walkUpdateItem(...), $updateClause->updateItems)); + } + + /** + * Walks down an UpdateItem AST node, thereby generating the appropriate SQL. + */ + public function walkUpdateItem(AST\UpdateItem $updateItem): string + { + $useTableAliasesBefore = $this->useSqlTableAliases; + $this->useSqlTableAliases = false; + + $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; + $newValue = $updateItem->newValue; + + $sql .= match (true) { + $newValue instanceof AST\Node => $newValue->dispatch($this), + $newValue === null => 'NULL', + }; + + $this->useSqlTableAliases = $useTableAliasesBefore; + + return $sql; + } + + /** + * Walks down a WhereClause AST node, thereby generating the appropriate SQL. + * + * WhereClause or not, the appropriate discriminator sql is added. + */ + public function walkWhereClause(AST\WhereClause|null $whereClause): string + { + $condSql = $whereClause !== null ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; + $discrSql = $this->generateDiscriminatorColumnConditionSQL($this->rootAliases); + + if ($this->em->hasFilters()) { + $filterClauses = []; + foreach ($this->rootAliases as $dqlAlias) { + $class = $this->getMetadataForDqlAlias($dqlAlias); + $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); + + $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias); + if ($filterExpr) { + $filterClauses[] = $filterExpr; + } + } + + if (count($filterClauses)) { + if ($condSql) { + $condSql = '(' . $condSql . ') AND '; + } + + $condSql .= implode(' AND ', $filterClauses); + } + } + + if ($condSql) { + return ' WHERE ' . (! $discrSql ? $condSql : '(' . $condSql . ') AND ' . $discrSql); + } + + if ($discrSql) { + return ' WHERE ' . $discrSql; + } + + return ''; + } + + /** + * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL. + */ + public function walkConditionalExpression( + AST\ConditionalExpression|AST\Phase2OptimizableConditional $condExpr, + ): string { + // Phase 2 AST optimization: Skip processing of ConditionalExpression + // if only one ConditionalTerm is defined + if (! ($condExpr instanceof AST\ConditionalExpression)) { + return $this->walkConditionalTerm($condExpr); + } + + return implode(' OR ', array_map($this->walkConditionalTerm(...), $condExpr->conditionalTerms)); + } + + /** + * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. + */ + public function walkConditionalTerm( + AST\ConditionalTerm|AST\ConditionalPrimary|AST\ConditionalFactor $condTerm, + ): string { + // Phase 2 AST optimization: Skip processing of ConditionalTerm + // if only one ConditionalFactor is defined + if (! ($condTerm instanceof AST\ConditionalTerm)) { + return $this->walkConditionalFactor($condTerm); + } + + return implode(' AND ', array_map($this->walkConditionalFactor(...), $condTerm->conditionalFactors)); + } + + /** + * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. + */ + public function walkConditionalFactor( + AST\ConditionalFactor|AST\ConditionalPrimary $factor, + ): string { + // Phase 2 AST optimization: Skip processing of ConditionalFactor + // if only one ConditionalPrimary is defined + return ! ($factor instanceof AST\ConditionalFactor) + ? $this->walkConditionalPrimary($factor) + : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary); + } + + /** + * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. + */ + public function walkConditionalPrimary(AST\ConditionalPrimary $primary): string + { + if ($primary->isSimpleConditionalExpression()) { + return $primary->simpleConditionalExpression->dispatch($this); + } + + if ($primary->isConditionalExpression()) { + $condExpr = $primary->conditionalExpression; + + return '(' . $this->walkConditionalExpression($condExpr) . ')'; + } + + throw new LogicException('Unexpected state of ConditionalPrimary node.'); + } + + /** + * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. + */ + public function walkExistsExpression(AST\ExistsExpression $existsExpr): string + { + $sql = $existsExpr->not ? 'NOT ' : ''; + + $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')'; + + return $sql; + } + + /** + * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. + */ + public function walkCollectionMemberExpression(AST\CollectionMemberExpression $collMemberExpr): string + { + $sql = $collMemberExpr->not ? 'NOT ' : ''; + $sql .= 'EXISTS (SELECT 1 FROM '; + + $entityExpr = $collMemberExpr->entityExpression; + $collPathExpr = $collMemberExpr->collectionValuedPathExpression; + assert($collPathExpr->field !== null); + + $fieldName = $collPathExpr->field; + $dqlAlias = $collPathExpr->identificationVariable; + + $class = $this->getMetadataForDqlAlias($dqlAlias); + + switch (true) { + // InputParameter + case $entityExpr instanceof AST\InputParameter: + $dqlParamKey = $entityExpr->name; + $entitySql = '?'; + break; + + // SingleValuedAssociationPathExpression | IdentificationVariable + case $entityExpr instanceof AST\PathExpression: + $entitySql = $this->walkPathExpression($entityExpr); + break; + + default: + throw new BadMethodCallException('Not implemented'); + } + + $assoc = $class->associationMappings[$fieldName]; + + if ($assoc->isOneToMany()) { + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); + $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); + + $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE '; + + $owningAssoc = $targetClass->associationMappings[$assoc->mappedBy]; + assert($owningAssoc->isManyToOne()); + $sqlParts = []; + + foreach ($owningAssoc->targetToSourceKeyColumns as $targetColumn => $sourceColumn) { + $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform); + + $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; + } + + foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { + if (isset($dqlParamKey)) { + $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); + } + + $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; + } + + $sql .= implode(' AND ', $sqlParts); + } else { // many-to-many + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + + $owningAssoc = $this->em->getMetadataFactory()->getOwningSide($assoc); + assert($owningAssoc->isManyToManyOwningSide()); + $joinTable = $owningAssoc->joinTable; + + // SQL table aliases + $joinTableAlias = $this->getSQLTableAlias($joinTable->name); + $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); + + $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias . ' WHERE '; + + $joinColumns = $assoc->isOwningSide() ? $joinTable->joinColumns : $joinTable->inverseJoinColumns; + $sqlParts = []; + + foreach ($joinColumns as $joinColumn) { + $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn->referencedColumnName], $class, $this->platform); + + $sqlParts[] = $joinTableAlias . '.' . $joinColumn->name . ' = ' . $sourceTableAlias . '.' . $targetColumn; + } + + $joinColumns = $assoc->isOwningSide() ? $joinTable->inverseJoinColumns : $joinTable->joinColumns; + + foreach ($joinColumns as $joinColumn) { + if (isset($dqlParamKey)) { + $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); + } + + $sqlParts[] = $joinTableAlias . '.' . $joinColumn->name . ' IN (' . $entitySql . ')'; + } + + $sql .= implode(' AND ', $sqlParts); + } + + return $sql . ')'; + } + + /** + * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. + */ + public function walkEmptyCollectionComparisonExpression(AST\EmptyCollectionComparisonExpression $emptyCollCompExpr): string + { + $sizeFunc = new AST\Functions\SizeFunction('size'); + $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression; + + return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0'); + } + + /** + * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. + */ + public function walkNullComparisonExpression(AST\NullComparisonExpression $nullCompExpr): string + { + $expression = $nullCompExpr->expression; + $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL'; + + // Handle ResultVariable + if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) { + return $this->walkResultVariable($expression) . $comparison; + } + + // Handle InputParameter mapping inclusion to ParserResult + if ($expression instanceof AST\InputParameter) { + return $this->walkInputParameter($expression) . $comparison; + } + + assert(! is_string($expression)); + + return $expression->dispatch($this) . $comparison; + } + + /** + * Walks down an InExpression AST node, thereby generating the appropriate SQL. + */ + public function walkInListExpression(AST\InListExpression $inExpr): string + { + return $this->walkArithmeticExpression($inExpr->expression) + . ($inExpr->not ? ' NOT' : '') . ' IN (' + . implode(', ', array_map($this->walkInParameter(...), $inExpr->literals)) + . ')'; + } + + /** + * Walks down an InExpression AST node, thereby generating the appropriate SQL. + */ + public function walkInSubselectExpression(AST\InSubselectExpression $inExpr): string + { + return $this->walkArithmeticExpression($inExpr->expression) + . ($inExpr->not ? ' NOT' : '') . ' IN (' + . $this->walkSubselect($inExpr->subselect) + . ')'; + } + + /** + * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. + * + * @throws QueryException + */ + public function walkInstanceOfExpression(AST\InstanceOfExpression $instanceOfExpr): string + { + $sql = ''; + + $dqlAlias = $instanceOfExpr->identificationVariable; + $discrClass = $class = $this->getMetadataForDqlAlias($dqlAlias); + + if ($class->discriminatorColumn) { + $discrClass = $this->em->getClassMetadata($class->rootEntityName); + } + + if ($this->useSqlTableAliases) { + $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.'; + } + + $sql .= $class->getDiscriminatorColumn()->name . ($instanceOfExpr->not ? ' NOT IN ' : ' IN '); + $sql .= $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr); + + return $sql; + } + + public function walkInParameter(mixed $inParam): string + { + return $inParam instanceof AST\InputParameter + ? $this->walkInputParameter($inParam) + : $this->walkArithmeticExpression($inParam); + } + + /** + * Walks down a literal that represents an AST node, thereby generating the appropriate SQL. + */ + public function walkLiteral(AST\Literal $literal): string + { + return match ($literal->type) { + AST\Literal::STRING => $this->conn->quote($literal->value), + AST\Literal::BOOLEAN => (string) $this->conn->getDatabasePlatform()->convertBooleans(strtolower($literal->value) === 'true'), + AST\Literal::NUMERIC => (string) $literal->value, + default => throw QueryException::invalidLiteral($literal), + }; + } + + /** + * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. + */ + public function walkBetweenExpression(AST\BetweenExpression $betweenExpr): string + { + $sql = $this->walkArithmeticExpression($betweenExpr->expression); + + if ($betweenExpr->not) { + $sql .= ' NOT'; + } + + $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression) + . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression); + + return $sql; + } + + /** + * Walks down a LikeExpression AST node, thereby generating the appropriate SQL. + */ + public function walkLikeExpression(AST\LikeExpression $likeExpr): string + { + $stringExpr = $likeExpr->stringExpression; + if (is_string($stringExpr)) { + if (! isset($this->queryComponents[$stringExpr]['resultVariable'])) { + throw new LogicException(sprintf('No result variable found for string expression "%s".', $stringExpr)); + } + + $leftExpr = $this->walkResultVariable($stringExpr); + } else { + $leftExpr = $stringExpr->dispatch($this); + } + + $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE '; + + if ($likeExpr->stringPattern instanceof AST\InputParameter) { + $sql .= $this->walkInputParameter($likeExpr->stringPattern); + } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) { + $sql .= $this->walkFunction($likeExpr->stringPattern); + } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) { + $sql .= $this->walkPathExpression($likeExpr->stringPattern); + } else { + $sql .= $this->walkLiteral($likeExpr->stringPattern); + } + + if ($likeExpr->escapeChar) { + $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar); + } + + return $sql; + } + + /** + * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. + */ + public function walkStateFieldPathExpression(AST\PathExpression $stateFieldPathExpression): string + { + return $this->walkPathExpression($stateFieldPathExpression); + } + + /** + * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. + */ + public function walkComparisonExpression(AST\ComparisonExpression $compExpr): string + { + $leftExpr = $compExpr->leftExpression; + $rightExpr = $compExpr->rightExpression; + $sql = ''; + + $sql .= $leftExpr instanceof AST\Node + ? $leftExpr->dispatch($this) + : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr)); + + $sql .= ' ' . $compExpr->operator . ' '; + + $sql .= $rightExpr instanceof AST\Node + ? $rightExpr->dispatch($this) + : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr)); + + return $sql; + } + + /** + * Walks down an InputParameter AST node, thereby generating the appropriate SQL. + */ + public function walkInputParameter(AST\InputParameter $inputParam): string + { + $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++); + + $parameter = $this->query->getParameter($inputParam->name); + + if ($parameter) { + $type = $parameter->getType(); + if (is_string($type) && Type::hasType($type)) { + return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform); + } + } + + return '?'; + } + + /** + * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. + */ + public function walkArithmeticExpression(AST\ArithmeticExpression $arithmeticExpr): string + { + return $arithmeticExpr->isSimpleArithmeticExpression() + ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression) + : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')'; + } + + /** + * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. + */ + public function walkSimpleArithmeticExpression(AST\Node|string $simpleArithmeticExpr): string + { + if (! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) { + return $this->walkArithmeticTerm($simpleArithmeticExpr); + } + + return implode(' ', array_map($this->walkArithmeticTerm(...), $simpleArithmeticExpr->arithmeticTerms)); + } + + /** + * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. + */ + public function walkArithmeticTerm(AST\Node|string $term): string + { + if (is_string($term)) { + return isset($this->queryComponents[$term]) + ? $this->walkResultVariable($this->queryComponents[$term]['token']->value) + : $term; + } + + // Phase 2 AST optimization: Skip processing of ArithmeticTerm + // if only one ArithmeticFactor is defined + if (! ($term instanceof AST\ArithmeticTerm)) { + return $this->walkArithmeticFactor($term); + } + + return implode(' ', array_map($this->walkArithmeticFactor(...), $term->arithmeticFactors)); + } + + /** + * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. + */ + public function walkArithmeticFactor(AST\Node|string $factor): string + { + if (is_string($factor)) { + return isset($this->queryComponents[$factor]) + ? $this->walkResultVariable($this->queryComponents[$factor]['token']->value) + : $factor; + } + + // Phase 2 AST optimization: Skip processing of ArithmeticFactor + // if only one ArithmeticPrimary is defined + if (! ($factor instanceof AST\ArithmeticFactor)) { + return $this->walkArithmeticPrimary($factor); + } + + $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''); + + return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary); + } + + /** + * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL. + */ + public function walkArithmeticPrimary(AST\Node|string $primary): string + { + if ($primary instanceof AST\SimpleArithmeticExpression) { + return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; + } + + if ($primary instanceof AST\Node) { + return $primary->dispatch($this); + } + + return $this->walkEntityIdentificationVariable($primary); + } + + /** + * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL. + */ + public function walkStringPrimary(AST\Node|string $stringPrimary): string + { + return is_string($stringPrimary) + ? $this->conn->quote($stringPrimary) + : $stringPrimary->dispatch($this); + } + + /** + * Walks down a ResultVariable that represents an AST node, thereby generating the appropriate SQL. + */ + public function walkResultVariable(string $resultVariable): string + { + if (! isset($this->scalarResultAliasMap[$resultVariable])) { + throw new InvalidArgumentException(sprintf('Unknown result variable: %s', $resultVariable)); + } + + $resultAlias = $this->scalarResultAliasMap[$resultVariable]; + + if (is_array($resultAlias)) { + return implode(', ', $resultAlias); + } + + return $resultAlias; + } + + /** + * @return string The list in parentheses of valid child discriminators from the given class + * + * @throws QueryException + */ + private function getChildDiscriminatorsFromClassMetadata( + ClassMetadata $rootClass, + AST\InstanceOfExpression $instanceOfExpr, + ): string { + $sqlParameterList = []; + $discriminators = []; + foreach ($instanceOfExpr->value as $parameter) { + if ($parameter instanceof AST\InputParameter) { + $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name; + $sqlParameterList[] = $this->walkInParameter($parameter); + continue; + } + + $metadata = $this->em->getClassMetadata($parameter); + + if ($metadata->getName() !== $rootClass->name && ! $metadata->getReflectionClass()->isSubclassOf($rootClass->name)) { + throw QueryException::instanceOfUnrelatedClass($parameter, $rootClass->name); + } + + $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em); + } + + foreach (array_keys($discriminators) as $discriminatorValue) { + $sqlParameterList[] = $rootClass->getDiscriminatorColumn()->type === 'integer' && is_int($discriminatorValue) + ? $discriminatorValue + : $this->conn->quote((string) $discriminatorValue); + } + + return '(' . implode(', ', $sqlParameterList) . ')'; + } +} diff --git a/vendor/doctrine/orm/src/Query/TokenType.php b/vendor/doctrine/orm/src/Query/TokenType.php new file mode 100644 index 0000000..e745e4a --- /dev/null +++ b/vendor/doctrine/orm/src/Query/TokenType.php @@ -0,0 +1,91 @@ += 100 + case T_FULLY_QUALIFIED_NAME = 101; + case T_IDENTIFIER = 102; + + // All keyword tokens should be >= 200 + case T_ALL = 200; + case T_AND = 201; + case T_ANY = 202; + case T_AS = 203; + case T_ASC = 204; + case T_AVG = 205; + case T_BETWEEN = 206; + case T_BOTH = 207; + case T_BY = 208; + case T_CASE = 209; + case T_COALESCE = 210; + case T_COUNT = 211; + case T_DELETE = 212; + case T_DESC = 213; + case T_DISTINCT = 214; + case T_ELSE = 215; + case T_EMPTY = 216; + case T_END = 217; + case T_ESCAPE = 218; + case T_EXISTS = 219; + case T_FALSE = 220; + case T_FROM = 221; + case T_GROUP = 222; + case T_HAVING = 223; + case T_HIDDEN = 224; + case T_IN = 225; + case T_INDEX = 226; + case T_INNER = 227; + case T_INSTANCE = 228; + case T_IS = 229; + case T_JOIN = 230; + case T_LEADING = 231; + case T_LEFT = 232; + case T_LIKE = 233; + case T_MAX = 234; + case T_MEMBER = 235; + case T_MIN = 236; + case T_NEW = 237; + case T_NOT = 238; + case T_NULL = 239; + case T_NULLIF = 240; + case T_OF = 241; + case T_OR = 242; + case T_ORDER = 243; + case T_OUTER = 244; + case T_SELECT = 246; + case T_SET = 247; + case T_SOME = 248; + case T_SUM = 249; + case T_THEN = 250; + case T_TRAILING = 251; + case T_TRUE = 252; + case T_UPDATE = 253; + case T_WHEN = 254; + case T_WHERE = 255; + case T_WITH = 256; +} diff --git a/vendor/doctrine/orm/src/Query/TreeWalker.php b/vendor/doctrine/orm/src/Query/TreeWalker.php new file mode 100644 index 0000000..6c21577 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/TreeWalker.php @@ -0,0 +1,44 @@ + $queryComponents The query components (symbol table). + */ + public function __construct(AbstractQuery $query, ParserResult $parserResult, array $queryComponents); + + /** + * Returns internal queryComponents array. + * + * @psalm-return array + */ + public function getQueryComponents(): array; + + /** + * Walks down a SelectStatement AST node. + */ + public function walkSelectStatement(AST\SelectStatement $selectStatement): void; + + /** + * Walks down an UpdateStatement AST node. + */ + public function walkUpdateStatement(AST\UpdateStatement $updateStatement): void; + + /** + * Walks down a DeleteStatement AST node. + */ + public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): void; +} diff --git a/vendor/doctrine/orm/src/Query/TreeWalkerAdapter.php b/vendor/doctrine/orm/src/Query/TreeWalkerAdapter.php new file mode 100644 index 0000000..a7948db --- /dev/null +++ b/vendor/doctrine/orm/src/Query/TreeWalkerAdapter.php @@ -0,0 +1,90 @@ +queryComponents; + } + + public function walkSelectStatement(AST\SelectStatement $selectStatement): void + { + } + + public function walkUpdateStatement(AST\UpdateStatement $updateStatement): void + { + } + + public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): void + { + } + + /** + * Sets or overrides a query component for a given dql alias. + * + * @psalm-param QueryComponent $queryComponent + */ + protected function setQueryComponent(string $dqlAlias, array $queryComponent): void + { + $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token']; + + if (array_diff($requiredKeys, array_keys($queryComponent))) { + throw QueryException::invalidQueryComponent($dqlAlias); + } + + $this->queryComponents[$dqlAlias] = $queryComponent; + } + + /** + * Retrieves the Query Instance responsible for the current walkers execution. + */ + protected function _getQuery(): AbstractQuery + { + return $this->query; + } + + /** + * Retrieves the ParserResult. + */ + protected function _getParserResult(): ParserResult + { + return $this->parserResult; + } + + protected function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata + { + return $this->queryComponents[$dqlAlias]['metadata'] + ?? throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias)); + } +} diff --git a/vendor/doctrine/orm/src/Query/TreeWalkerChain.php b/vendor/doctrine/orm/src/Query/TreeWalkerChain.php new file mode 100644 index 0000000..7bb3051 --- /dev/null +++ b/vendor/doctrine/orm/src/Query/TreeWalkerChain.php @@ -0,0 +1,88 @@ +> + */ + private array $walkers = []; + + /** + * {@inheritDoc} + */ + public function __construct( + private readonly AbstractQuery $query, + private readonly ParserResult $parserResult, + private array $queryComponents, + ) { + } + + /** + * Returns the internal queryComponents array. + * + * {@inheritDoc} + */ + public function getQueryComponents(): array + { + return $this->queryComponents; + } + + /** + * Adds a tree walker to the chain. + * + * @param string $walkerClass The class of the walker to instantiate. + * @psalm-param class-string $walkerClass + */ + public function addTreeWalker(string $walkerClass): void + { + $this->walkers[] = $walkerClass; + } + + public function walkSelectStatement(AST\SelectStatement $selectStatement): void + { + foreach ($this->getWalkers() as $walker) { + $walker->walkSelectStatement($selectStatement); + + $this->queryComponents = $walker->getQueryComponents(); + } + } + + public function walkUpdateStatement(AST\UpdateStatement $updateStatement): void + { + foreach ($this->getWalkers() as $walker) { + $walker->walkUpdateStatement($updateStatement); + } + } + + public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): void + { + foreach ($this->getWalkers() as $walker) { + $walker->walkDeleteStatement($deleteStatement); + } + } + + /** @psalm-return Generator */ + private function getWalkers(): Generator + { + foreach ($this->walkers as $walkerClass) { + yield new $walkerClass($this->query, $this->parserResult, $this->queryComponents); + } + } +} diff --git a/vendor/doctrine/orm/src/QueryBuilder.php b/vendor/doctrine/orm/src/QueryBuilder.php new file mode 100644 index 0000000..a6a39a9 --- /dev/null +++ b/vendor/doctrine/orm/src/QueryBuilder.php @@ -0,0 +1,1375 @@ + + */ + private array $dqlParts = [ + 'distinct' => false, + 'select' => [], + 'from' => [], + 'join' => [], + 'set' => [], + 'where' => null, + 'groupBy' => [], + 'having' => null, + 'orderBy' => [], + ]; + + private QueryType $type = QueryType::Select; + + /** + * The complete DQL string for this query. + */ + private string|null $dql = null; + + /** + * The query parameters. + * + * @psalm-var ArrayCollection + */ + private ArrayCollection $parameters; + + /** + * The index of the first result to retrieve. + */ + private int $firstResult = 0; + + /** + * The maximum number of results to retrieve. + */ + private int|null $maxResults = null; + + /** + * Keeps root entity alias names for join entities. + * + * @psalm-var array + */ + private array $joinRootAliases = []; + + /** + * Whether to use second level cache, if available. + */ + protected bool $cacheable = false; + + /** + * Second level cache region name. + */ + protected string|null $cacheRegion = null; + + /** + * Second level query cache mode. + * + * @psalm-var Cache::MODE_*|null + */ + protected int|null $cacheMode = null; + + protected int $lifetime = 0; + + /** + * Initializes a new QueryBuilder that uses the given EntityManager. + * + * @param EntityManagerInterface $em The EntityManager to use. + */ + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->parameters = new ArrayCollection(); + } + + /** + * Gets an ExpressionBuilder used for object-oriented construction of query expressions. + * This producer method is intended for convenient inline usage. Example: + * + * + * $qb = $em->createQueryBuilder(); + * $qb + * ->select('u') + * ->from('User', 'u') + * ->where($qb->expr()->eq('u.id', 1)); + * + * + * For more complex expression construction, consider storing the expression + * builder object in a local variable. + */ + public function expr(): Expr + { + return $this->em->getExpressionBuilder(); + } + + /** + * Enable/disable second level query (result) caching for this query. + * + * @return $this + */ + public function setCacheable(bool $cacheable): static + { + $this->cacheable = $cacheable; + + return $this; + } + + /** + * Are the query results enabled for second level cache? + */ + public function isCacheable(): bool + { + return $this->cacheable; + } + + /** @return $this */ + public function setCacheRegion(string $cacheRegion): static + { + $this->cacheRegion = $cacheRegion; + + return $this; + } + + /** + * Obtain the name of the second level query cache region in which query results will be stored + * + * @return string|null The cache region name; NULL indicates the default region. + */ + public function getCacheRegion(): string|null + { + return $this->cacheRegion; + } + + public function getLifetime(): int + { + return $this->lifetime; + } + + /** + * Sets the life-time for this query into second level cache. + * + * @return $this + */ + public function setLifetime(int $lifetime): static + { + $this->lifetime = $lifetime; + + return $this; + } + + /** @psalm-return Cache::MODE_*|null */ + public function getCacheMode(): int|null + { + return $this->cacheMode; + } + + /** + * @psalm-param Cache::MODE_* $cacheMode + * + * @return $this + */ + public function setCacheMode(int $cacheMode): static + { + $this->cacheMode = $cacheMode; + + return $this; + } + + /** + * Gets the associated EntityManager for this query builder. + */ + public function getEntityManager(): EntityManagerInterface + { + return $this->em; + } + + /** + * Gets the complete DQL string formed by the current specifications of this QueryBuilder. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * echo $qb->getDql(); // SELECT u FROM User u + * + */ + public function getDQL(): string + { + return $this->dql ??= match ($this->type) { + QueryType::Select => $this->getDQLForSelect(), + QueryType::Delete => $this->getDQLForDelete(), + QueryType::Update => $this->getDQLForUpdate(), + }; + } + + /** + * Constructs a Query instance from the current specifications of the builder. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * $q = $qb->getQuery(); + * $results = $q->execute(); + * + */ + public function getQuery(): Query + { + $parameters = clone $this->parameters; + $query = $this->em->createQuery($this->getDQL()) + ->setParameters($parameters) + ->setFirstResult($this->firstResult) + ->setMaxResults($this->maxResults); + + if ($this->lifetime) { + $query->setLifetime($this->lifetime); + } + + if ($this->cacheMode) { + $query->setCacheMode($this->cacheMode); + } + + if ($this->cacheable) { + $query->setCacheable($this->cacheable); + } + + if ($this->cacheRegion) { + $query->setCacheRegion($this->cacheRegion); + } + + return $query; + } + + /** + * Finds the root entity alias of the joined entity. + * + * @param string $alias The alias of the new join entity + * @param string $parentAlias The parent entity alias of the join relationship + */ + private function findRootAlias(string $alias, string $parentAlias): string + { + if (in_array($parentAlias, $this->getRootAliases(), true)) { + $rootAlias = $parentAlias; + } elseif (isset($this->joinRootAliases[$parentAlias])) { + $rootAlias = $this->joinRootAliases[$parentAlias]; + } else { + // Should never happen with correct joining order. Might be + // thoughtful to throw exception instead. + $rootAlias = $this->getRootAlias(); + } + + $this->joinRootAliases[$alias] = $rootAlias; + + return $rootAlias; + } + + /** + * Gets the FIRST root alias of the query. This is the first entity alias involved + * in the construction of the query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * + * echo $qb->getRootAlias(); // u + * + * + * @deprecated Please use $qb->getRootAliases() instead. + * + * @throws RuntimeException + */ + public function getRootAlias(): string + { + $aliases = $this->getRootAliases(); + + if (! isset($aliases[0])) { + throw new RuntimeException('No alias was set before invoking getRootAlias().'); + } + + return $aliases[0]; + } + + /** + * Gets the root aliases of the query. This is the entity aliases involved + * in the construction of the query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * + * $qb->getRootAliases(); // array('u') + * + * + * @return string[] + * @psalm-return list + */ + public function getRootAliases(): array + { + $aliases = []; + + foreach ($this->dqlParts['from'] as &$fromClause) { + if (is_string($fromClause)) { + $spacePos = strrpos($fromClause, ' '); + + /** @psalm-var class-string $from */ + $from = substr($fromClause, 0, $spacePos); + $alias = substr($fromClause, $spacePos + 1); + + $fromClause = new Query\Expr\From($from, $alias); + } + + $aliases[] = $fromClause->getAlias(); + } + + return $aliases; + } + + /** + * Gets all the aliases that have been used in the query. + * Including all select root aliases and join aliases + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->join('u.articles','a'); + * + * $qb->getAllAliases(); // array('u','a') + * + * + * @return string[] + * @psalm-return list + */ + public function getAllAliases(): array + { + return [...$this->getRootAliases(), ...array_keys($this->joinRootAliases)]; + } + + /** + * Gets the root entities of the query. This is the entity classes involved + * in the construction of the query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * + * $qb->getRootEntities(); // array('User') + * + * + * @return string[] + * @psalm-return list + */ + public function getRootEntities(): array + { + $entities = []; + + foreach ($this->dqlParts['from'] as &$fromClause) { + if (is_string($fromClause)) { + $spacePos = strrpos($fromClause, ' '); + + /** @psalm-var class-string $from */ + $from = substr($fromClause, 0, $spacePos); + $alias = substr($fromClause, $spacePos + 1); + + $fromClause = new Query\Expr\From($from, $alias); + } + + $entities[] = $fromClause->getFrom(); + } + + return $entities; + } + + /** + * Sets a query parameter for the query being constructed. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = :user_id') + * ->setParameter('user_id', 1); + * + * + * @param string|int $key The parameter position or name. + * @param ParameterType|ArrayParameterType|string|int|null $type ParameterType::*, ArrayParameterType::* or \Doctrine\DBAL\Types\Type::* constant + * + * @return $this + */ + public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static + { + $existingParameter = $this->getParameter($key); + + if ($existingParameter !== null) { + $existingParameter->setValue($value, $type); + + return $this; + } + + $this->parameters->add(new Parameter($key, $value, $type)); + + return $this; + } + + /** + * Sets a collection of query parameters for the query being constructed. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = :user_id1 OR u.id = :user_id2') + * ->setParameters(new ArrayCollection(array( + * new Parameter('user_id1', 1), + * new Parameter('user_id2', 2) + * ))); + * + * + * @psalm-param ArrayCollection $parameters + * + * @return $this + */ + public function setParameters(ArrayCollection $parameters): static + { + $this->parameters = $parameters; + + return $this; + } + + /** + * Gets all defined query parameters for the query being constructed. + * + * @psalm-return ArrayCollection + */ + public function getParameters(): ArrayCollection + { + return $this->parameters; + } + + /** + * Gets a (previously set) query parameter of the query being constructed. + */ + public function getParameter(string|int $key): Parameter|null + { + $key = Parameter::normalizeName($key); + + $filteredParameters = $this->parameters->filter( + static fn (Parameter $parameter): bool => $key === $parameter->getName() + ); + + return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null; + } + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @return $this + */ + public function setFirstResult(int|null $firstResult): static + { + $this->firstResult = (int) $firstResult; + + return $this; + } + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + */ + public function getFirstResult(): int + { + return $this->firstResult; + } + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @return $this + */ + public function setMaxResults(int|null $maxResults): static + { + $this->maxResults = $maxResults; + + return $this; + } + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query builder. + */ + public function getMaxResults(): int|null + { + return $this->maxResults; + } + + /** + * Either appends to or replaces a single, generic query part. + * + * The available parts are: 'select', 'from', 'join', 'set', 'where', + * 'groupBy', 'having' and 'orderBy'. + * + * @psalm-param string|object|list|array{join: array} $dqlPart + * + * @return $this + */ + public function add(string $dqlPartName, string|object|array $dqlPart, bool $append = false): static + { + if ($append && ($dqlPartName === 'where' || $dqlPartName === 'having')) { + throw new InvalidArgumentException( + "Using \$append = true does not have an effect with 'where' or 'having' " . + 'parts. See QueryBuilder#andWhere() for an example for correct usage.', + ); + } + + $isMultiple = is_array($this->dqlParts[$dqlPartName]) + && ! ($dqlPartName === 'join' && ! $append); + + // Allow adding any part retrieved from self::getDQLParts(). + if (is_array($dqlPart) && $dqlPartName !== 'join') { + $dqlPart = reset($dqlPart); + } + + // This is introduced for backwards compatibility reasons. + // TODO: Remove for 3.0 + if ($dqlPartName === 'join') { + $newDqlPart = []; + + foreach ($dqlPart as $k => $v) { + $k = is_numeric($k) ? $this->getRootAlias() : $k; + + $newDqlPart[$k] = $v; + } + + $dqlPart = $newDqlPart; + } + + if ($append && $isMultiple) { + if (is_array($dqlPart)) { + $key = key($dqlPart); + + $this->dqlParts[$dqlPartName][$key][] = $dqlPart[$key]; + } else { + $this->dqlParts[$dqlPartName][] = $dqlPart; + } + } else { + $this->dqlParts[$dqlPartName] = $isMultiple ? [$dqlPart] : $dqlPart; + } + + $this->dql = null; + + return $this; + } + + /** + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u', 'p') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p'); + * + * + * @return $this + */ + public function select(mixed ...$select): static + { + self::validateVariadicParameter($select); + + $this->type = QueryType::Select; + + if ($select === []) { + return $this; + } + + return $this->add('select', new Expr\Select($select), false); + } + + /** + * Adds a DISTINCT flag to this query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->distinct() + * ->from('User', 'u'); + * + * + * @return $this + */ + public function distinct(bool $flag = true): static + { + if ($this->dqlParts['distinct'] !== $flag) { + $this->dqlParts['distinct'] = $flag; + $this->dql = null; + } + + return $this; + } + + /** + * Adds an item that is to be returned in the query result. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->addSelect('p') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p'); + * + * + * @return $this + */ + public function addSelect(mixed ...$select): static + { + self::validateVariadicParameter($select); + + $this->type = QueryType::Select; + + if ($select === []) { + return $this; + } + + return $this->add('select', new Expr\Select($select), true); + } + + /** + * Turns the query being built into a bulk delete query that ranges over + * a certain entity type. + * + * + * $qb = $em->createQueryBuilder() + * ->delete('User', 'u') + * ->where('u.id = :user_id') + * ->setParameter('user_id', 1); + * + * + * @param class-string|null $delete The class/type whose instances are subject to the deletion. + * @param string|null $alias The class/type alias used in the constructed query. + * + * @return $this + */ + public function delete(string|null $delete = null, string|null $alias = null): static + { + $this->type = QueryType::Delete; + + if (! $delete) { + return $this; + } + + if (! $alias) { + throw new InvalidArgumentException(sprintf( + '%s(): The alias for entity %s must not be omitted.', + __METHOD__, + $delete, + )); + } + + return $this->add('from', new Expr\From($delete, $alias)); + } + + /** + * Turns the query being built into a bulk update query that ranges over + * a certain entity type. + * + * + * $qb = $em->createQueryBuilder() + * ->update('User', 'u') + * ->set('u.password', '?1') + * ->where('u.id = ?2'); + * + * + * @param class-string|null $update The class/type whose instances are subject to the update. + * @param string|null $alias The class/type alias used in the constructed query. + * + * @return $this + */ + public function update(string|null $update = null, string|null $alias = null): static + { + $this->type = QueryType::Update; + + if (! $update) { + return $this; + } + + if (! $alias) { + throw new InvalidArgumentException(sprintf( + '%s(): The alias for entity %s must not be omitted.', + __METHOD__, + $update, + )); + } + + return $this->add('from', new Expr\From($update, $alias)); + } + + /** + * Creates and adds a query root corresponding to the entity identified by the given alias, + * forming a cartesian product with any existing query roots. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * + * + * @param class-string $from The class name. + * @param string $alias The alias of the class. + * @param string|null $indexBy The index for the from. + * + * @return $this + */ + public function from(string $from, string $alias, string|null $indexBy = null): static + { + return $this->add('from', new Expr\From($from, $alias, $indexBy), true); + } + + /** + * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with + * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it + * setting an index by. + * + * + * $qb = $userRepository->createQueryBuilder('u') + * ->indexBy('u', 'u.id'); + * + * // Is equivalent to... + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u', 'u.id'); + * + * + * @return $this + * + * @throws Query\QueryException + */ + public function indexBy(string $alias, string $indexBy): static + { + $rootAliases = $this->getRootAliases(); + + if (! in_array($alias, $rootAliases, true)) { + throw new Query\QueryException( + sprintf('Specified root alias %s must be set before invoking indexBy().', $alias), + ); + } + + foreach ($this->dqlParts['from'] as &$fromClause) { + assert($fromClause instanceof Expr\From); + if ($fromClause->getAlias() !== $alias) { + continue; + } + + $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy); + } + + return $this; + } + + /** + * Creates and adds a join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * + * @return $this + */ + public function join( + string $join, + string $alias, + string|null $conditionType = null, + string|Expr\Composite|Expr\Comparison|Expr\Func|null $condition = null, + string|null $indexBy = null, + ): static { + return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy); + } + + /** + * Creates and adds a join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * [php] + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * + * @return $this + */ + public function innerJoin( + string $join, + string $alias, + string|null $conditionType = null, + string|Expr\Composite|Expr\Comparison|Expr\Func|null $condition = null, + string|null $indexBy = null, + ): static { + $parentAlias = substr($join, 0, (int) strpos($join, '.')); + + $rootAlias = $this->findRootAlias($alias, $parentAlias); + + $join = new Expr\Join( + Expr\Join::INNER_JOIN, + $join, + $alias, + $conditionType, + $condition, + $indexBy, + ); + + return $this->add('join', [$rootAlias => $join], true); + } + + /** + * Creates and adds a left join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * + * @return $this + */ + public function leftJoin( + string $join, + string $alias, + string|null $conditionType = null, + string|Expr\Composite|Expr\Comparison|Expr\Func|null $condition = null, + string|null $indexBy = null, + ): static { + $parentAlias = substr($join, 0, (int) strpos($join, '.')); + + $rootAlias = $this->findRootAlias($alias, $parentAlias); + + $join = new Expr\Join( + Expr\Join::LEFT_JOIN, + $join, + $alias, + $conditionType, + $condition, + $indexBy, + ); + + return $this->add('join', [$rootAlias => $join], true); + } + + /** + * Sets a new value for a field in a bulk update query. + * + * + * $qb = $em->createQueryBuilder() + * ->update('User', 'u') + * ->set('u.password', '?1') + * ->where('u.id = ?2'); + * + * + * @return $this + */ + public function set(string $key, mixed $value): static + { + return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true); + } + + /** + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = ?'); + * + * // You can optionally programmatically build and/or expressions + * $qb = $em->createQueryBuilder(); + * + * $or = $qb->expr()->orX(); + * $or->add($qb->expr()->eq('u.id', 1)); + * $or->add($qb->expr()->eq('u.id', 2)); + * + * $qb->update('User', 'u') + * ->set('u.password', '?') + * ->where($or); + * + * + * @return $this + */ + public function where(mixed ...$predicates): static + { + self::validateVariadicParameter($predicates); + + if (! (count($predicates) === 1 && $predicates[0] instanceof Expr\Composite)) { + $predicates = new Expr\Andx($predicates); + } + + return $this->add('where', $predicates); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.username LIKE ?') + * ->andWhere('u.is_active = 1'); + * + * + * @see where() + * + * @return $this + */ + public function andWhere(mixed ...$where): static + { + self::validateVariadicParameter($where); + + $dql = $this->getDQLPart('where'); + + if ($dql instanceof Expr\Andx) { + $dql->addMultiple($where); + } else { + array_unshift($where, $dql); + $dql = new Expr\Andx($where); + } + + return $this->add('where', $dql); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = 1') + * ->orWhere('u.id = 2'); + * + * + * @see where() + * + * @return $this + */ + public function orWhere(mixed ...$where): static + { + self::validateVariadicParameter($where); + + $dql = $this->getDQLPart('where'); + + if ($dql instanceof Expr\Orx) { + $dql->addMultiple($where); + } else { + array_unshift($where, $dql); + $dql = new Expr\Orx($where); + } + + return $this->add('where', $dql); + } + + /** + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->groupBy('u.id'); + * + * + * @return $this + */ + public function groupBy(string ...$groupBy): static + { + self::validateVariadicParameter($groupBy); + + return $this->add('groupBy', new Expr\GroupBy($groupBy)); + } + + /** + * Adds a grouping expression to the query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->groupBy('u.lastLogin') + * ->addGroupBy('u.createdAt'); + * + * + * @return $this + */ + public function addGroupBy(string ...$groupBy): static + { + self::validateVariadicParameter($groupBy); + + return $this->add('groupBy', new Expr\GroupBy($groupBy), true); + } + + /** + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. + * + * @return $this + */ + public function having(mixed ...$having): static + { + self::validateVariadicParameter($having); + + if (! (count($having) === 1 && ($having[0] instanceof Expr\Andx || $having[0] instanceof Expr\Orx))) { + $having = new Expr\Andx($having); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. + * + * @return $this + */ + public function andHaving(mixed ...$having): static + { + self::validateVariadicParameter($having); + + $dql = $this->getDQLPart('having'); + + if ($dql instanceof Expr\Andx) { + $dql->addMultiple($having); + } else { + array_unshift($having, $dql); + $dql = new Expr\Andx($having); + } + + return $this->add('having', $dql); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. + * + * @return $this + */ + public function orHaving(mixed ...$having): static + { + self::validateVariadicParameter($having); + + $dql = $this->getDQLPart('having'); + + if ($dql instanceof Expr\Orx) { + $dql->addMultiple($having); + } else { + array_unshift($having, $dql); + $dql = new Expr\Orx($having); + } + + return $this->add('having', $dql); + } + + /** + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. + * + * @return $this + */ + public function orderBy(string|Expr\OrderBy $sort, string|null $order = null): static + { + $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); + + return $this->add('orderBy', $orderBy); + } + + /** + * Adds an ordering to the query results. + * + * @return $this + */ + public function addOrderBy(string|Expr\OrderBy $sort, string|null $order = null): static + { + $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); + + return $this->add('orderBy', $orderBy, true); + } + + /** + * Adds criteria to the query. + * + * Adds where expressions with AND operator. + * Adds orderings. + * Overrides firstResult and maxResults if they're set. + * + * @return $this + * + * @throws Query\QueryException + */ + public function addCriteria(Criteria $criteria): static + { + $allAliases = $this->getAllAliases(); + if (! isset($allAliases[0])) { + throw new Query\QueryException('No aliases are set before invoking addCriteria().'); + } + + $visitor = new QueryExpressionVisitor($this->getAllAliases()); + + $whereExpression = $criteria->getWhereExpression(); + if ($whereExpression) { + $this->andWhere($visitor->dispatch($whereExpression)); + foreach ($visitor->getParameters() as $parameter) { + $this->parameters->add($parameter); + } + } + + foreach ($criteria->orderings() as $sort => $order) { + $hasValidAlias = false; + foreach ($allAliases as $alias) { + if (str_starts_with($sort . '.', $alias . '.')) { + $hasValidAlias = true; + break; + } + } + + if (! $hasValidAlias) { + $sort = $allAliases[0] . '.' . $sort; + } + + $this->addOrderBy($sort, $order->value); + } + + // Overwrite limits only if they was set in criteria + $firstResult = $criteria->getFirstResult(); + if ($firstResult > 0) { + $this->setFirstResult($firstResult); + } + + $maxResults = $criteria->getMaxResults(); + if ($maxResults !== null) { + $this->setMaxResults($maxResults); + } + + return $this; + } + + /** + * Gets a query part by its name. + */ + public function getDQLPart(string $queryPartName): mixed + { + return $this->dqlParts[$queryPartName]; + } + + /** + * Gets all query parts. + * + * @psalm-return array $dqlParts + */ + public function getDQLParts(): array + { + return $this->dqlParts; + } + + private function getDQLForDelete(): string + { + return 'DELETE' + . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', ']) + . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE ']) + . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']); + } + + private function getDQLForUpdate(): string + { + return 'UPDATE' + . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', ']) + . $this->getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', ']) + . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE ']) + . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']); + } + + private function getDQLForSelect(): string + { + $dql = 'SELECT' + . ($this->dqlParts['distinct'] === true ? ' DISTINCT' : '') + . $this->getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']); + + $fromParts = $this->getDQLPart('from'); + $joinParts = $this->getDQLPart('join'); + $fromClauses = []; + + // Loop through all FROM clauses + if (! empty($fromParts)) { + $dql .= ' FROM '; + + foreach ($fromParts as $from) { + $fromClause = (string) $from; + + if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) { + foreach ($joinParts[$from->getAlias()] as $join) { + $fromClause .= ' ' . ((string) $join); + } + } + + $fromClauses[] = $fromClause; + } + } + + $dql .= implode(', ', $fromClauses) + . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE ']) + . $this->getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', ']) + . $this->getReducedDQLQueryPart('having', ['pre' => ' HAVING ']) + . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']); + + return $dql; + } + + /** @psalm-param array $options */ + private function getReducedDQLQueryPart(string $queryPartName, array $options = []): string + { + $queryPart = $this->getDQLPart($queryPartName); + + if (empty($queryPart)) { + return $options['empty'] ?? ''; + } + + return ($options['pre'] ?? '') + . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart) + . ($options['post'] ?? ''); + } + + /** + * Resets DQL parts. + * + * @param string[]|null $parts + * @psalm-param list|null $parts + * + * @return $this + */ + public function resetDQLParts(array|null $parts = null): static + { + if ($parts === null) { + $parts = array_keys($this->dqlParts); + } + + foreach ($parts as $part) { + $this->resetDQLPart($part); + } + + return $this; + } + + /** + * Resets single DQL part. + * + * @return $this + */ + public function resetDQLPart(string $part): static + { + $this->dqlParts[$part] = is_array($this->dqlParts[$part]) ? [] : null; + $this->dql = null; + + return $this; + } + + /** + * Gets a string representation of this QueryBuilder which corresponds to + * the final DQL query being constructed. + */ + public function __toString(): string + { + return $this->getDQL(); + } + + /** + * Deep clones all expression objects in the DQL parts. + * + * @return void + */ + public function __clone() + { + foreach ($this->dqlParts as $part => $elements) { + if (is_array($this->dqlParts[$part])) { + foreach ($this->dqlParts[$part] as $idx => $element) { + if (is_object($element)) { + $this->dqlParts[$part][$idx] = clone $element; + } + } + } elseif (is_object($elements)) { + $this->dqlParts[$part] = clone $elements; + } + } + + $parameters = []; + + foreach ($this->parameters as $parameter) { + $parameters[] = clone $parameter; + } + + $this->parameters = new ArrayCollection($parameters); + } +} diff --git a/vendor/doctrine/orm/src/Repository/DefaultRepositoryFactory.php b/vendor/doctrine/orm/src/Repository/DefaultRepositoryFactory.php new file mode 100644 index 0000000..5c408fb --- /dev/null +++ b/vendor/doctrine/orm/src/Repository/DefaultRepositoryFactory.php @@ -0,0 +1,49 @@ + + */ + private array $repositoryList = []; + + public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository + { + $repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_id($entityManager); + + return $this->repositoryList[$repositoryHash] ??= $this->createRepository($entityManager, $entityName); + } + + /** + * Create a new repository instance for an entity class. + * + * @param EntityManagerInterface $entityManager The EntityManager instance. + * @param string $entityName The name of the entity. + */ + private function createRepository( + EntityManagerInterface $entityManager, + string $entityName, + ): EntityRepository { + $metadata = $entityManager->getClassMetadata($entityName); + $repositoryClassName = $metadata->customRepositoryClassName + ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); + + return new $repositoryClassName($entityManager, $metadata); + } +} diff --git a/vendor/doctrine/orm/src/Repository/Exception/InvalidFindByCall.php b/vendor/doctrine/orm/src/Repository/Exception/InvalidFindByCall.php new file mode 100644 index 0000000..c5dd015 --- /dev/null +++ b/vendor/doctrine/orm/src/Repository/Exception/InvalidFindByCall.php @@ -0,0 +1,21 @@ + $entityName The name of the entity. + * + * @return EntityRepository + * + * @template T of object + */ + public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository; +} diff --git a/vendor/doctrine/orm/src/Tools/AttachEntityListenersListener.php b/vendor/doctrine/orm/src/Tools/AttachEntityListenersListener.php new file mode 100644 index 0000000..9203cfe --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/AttachEntityListenersListener.php @@ -0,0 +1,69 @@ +> + */ + private array $entityListeners = []; + + /** + * Adds an entity listener for a specific entity. + * + * @param class-string $entityClass The entity to attach the listener. + * @param class-string $listenerClass The listener class. + * @param Events::*|null $eventName The entity lifecycle event. + * @param non-falsy-string|null $listenerCallback The listener callback method or NULL to use $eventName. + */ + public function addEntityListener( + string $entityClass, + string $listenerClass, + string|null $eventName = null, + string|null $listenerCallback = null, + ): void { + $this->entityListeners[ltrim($entityClass, '\\')][] = [ + 'event' => $eventName, + 'class' => $listenerClass, + 'method' => $listenerCallback ?? $eventName, + ]; + } + + /** + * Processes event and attach the entity listener. + */ + public function loadClassMetadata(LoadClassMetadataEventArgs $event): void + { + $metadata = $event->getClassMetadata(); + + if (! isset($this->entityListeners[$metadata->name])) { + return; + } + + foreach ($this->entityListeners[$metadata->name] as $listener) { + if ($listener['event'] === null) { + EntityListenerBuilder::bindEntityListener($metadata, $listener['class']); + } else { + assert($listener['method'] !== null); + $metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']); + } + } + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/AbstractEntityManagerCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/AbstractEntityManagerCommand.php new file mode 100644 index 0000000..370f4fb --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/AbstractEntityManagerCommand.php @@ -0,0 +1,25 @@ +getOption('em') === null + ? $this->entityManagerProvider->getDefaultManager() + : $this->entityManagerProvider->getManager($input->getOption('em')); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/CollectionRegionCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/CollectionRegionCommand.php new file mode 100644 index 0000000..b4c6efa --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/CollectionRegionCommand.php @@ -0,0 +1,119 @@ +setName('orm:clear-cache:region:collection') + ->setDescription('Clear a second-level cache collection region') + ->addArgument('owner-class', InputArgument::OPTIONAL, 'The owner entity name.') + ->addArgument('association', InputArgument::OPTIONAL, 'The association collection name.') + ->addArgument('owner-id', InputArgument::OPTIONAL, 'The owner identifier.') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all entity regions will be deleted/invalidated.') + ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, all cache entries will be flushed.') + ->setHelp(<<<'EOT' +The %command.name% command is meant to clear a second-level cache collection regions for an associated Entity Manager. +It is possible to delete/invalidate all collection region, a specific collection region or flushes the cache provider. + +The execution type differ on how you execute the command. +If you want to invalidate all entries for an collection region this command would do the work: + +%command.name% 'Entities\MyEntity' 'collectionName' + +To invalidate a specific entry you should use : + +%command.name% 'Entities\MyEntity' 'collectionName' 1 + +If you want to invalidate all entries for the all collection regions: + +%command.name% --all + +Alternatively, if you want to flush the configured cache provider for an collection region use this command: + +%command.name% 'Entities\MyEntity' 'collectionName' --flush + +Finally, be aware that if --flush option is passed, +not all cache providers are able to flush entries, because of a limitation of its execution nature. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + $ownerClass = $input->getArgument('owner-class'); + $assoc = $input->getArgument('association'); + $ownerId = $input->getArgument('owner-id'); + $cache = $em->getCache(); + + if (! $cache instanceof Cache) { + throw new InvalidArgumentException('No second-level cache is configured on the given EntityManager.'); + } + + if (( ! $ownerClass || ! $assoc) && ! $input->getOption('all')) { + throw new InvalidArgumentException('Missing arguments "--owner-class" "--association"'); + } + + if ($input->getOption('flush')) { + $cache->getCollectionCacheRegion($ownerClass, $assoc) + ->evictAll(); + + $ui->comment( + sprintf( + 'Flushing cache provider configured for "%s#%s"', + $ownerClass, + $assoc, + ), + ); + + return 0; + } + + if ($input->getOption('all')) { + $ui->comment('Clearing all second-level cache collection regions'); + + $cache->evictEntityRegions(); + + return 0; + } + + if ($ownerId) { + $ui->comment( + sprintf( + 'Clearing second-level cache entry for collection "%s#%s" owner entity identified by "%s"', + $ownerClass, + $assoc, + $ownerId, + ), + ); + $cache->evictCollection($ownerClass, $assoc, $ownerId); + + return 0; + } + + $ui->comment(sprintf('Clearing second-level cache for collection "%s#%s"', $ownerClass, $assoc)); + $cache->evictCollectionRegion($ownerClass, $assoc); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/EntityRegionCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/EntityRegionCommand.php new file mode 100644 index 0000000..c5f2d65 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/EntityRegionCommand.php @@ -0,0 +1,110 @@ +setName('orm:clear-cache:region:entity') + ->setDescription('Clear a second-level cache entity region') + ->addArgument('entity-class', InputArgument::OPTIONAL, 'The entity name.') + ->addArgument('entity-id', InputArgument::OPTIONAL, 'The entity identifier.') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all entity regions will be deleted/invalidated.') + ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, all cache entries will be flushed.') + ->setHelp(<<<'EOT' +The %command.name% command is meant to clear a second-level cache entity region for an associated Entity Manager. +It is possible to delete/invalidate all entity region, a specific entity region or flushes the cache provider. + +The execution type differ on how you execute the command. +If you want to invalidate all entries for an entity region this command would do the work: + +%command.name% 'Entities\MyEntity' + +To invalidate a specific entry you should use : + +%command.name% 'Entities\MyEntity' 1 + +If you want to invalidate all entries for the all entity regions: + +%command.name% --all + +Alternatively, if you want to flush the configured cache provider for an entity region use this command: + +%command.name% 'Entities\MyEntity' --flush + +Finally, be aware that if --flush option is passed, +not all cache providers are able to flush entries, because of a limitation of its execution nature. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + $entityClass = $input->getArgument('entity-class'); + $entityId = $input->getArgument('entity-id'); + $cache = $em->getCache(); + + if (! $cache instanceof Cache) { + throw new InvalidArgumentException('No second-level cache is configured on the given EntityManager.'); + } + + if (! $entityClass && ! $input->getOption('all')) { + throw new InvalidArgumentException('Invalid argument "--entity-class"'); + } + + if ($input->getOption('flush')) { + $cache->getEntityCacheRegion($entityClass) + ->evictAll(); + + $ui->comment(sprintf('Flushing cache provider configured for entity named "%s"', $entityClass)); + + return 0; + } + + if ($input->getOption('all')) { + $ui->comment('Clearing all second-level cache entity regions'); + + $cache->evictEntityRegions(); + + return 0; + } + + if ($entityId) { + $ui->comment( + sprintf( + 'Clearing second-level cache entry for entity "%s" identified by "%s"', + $entityClass, + $entityId, + ), + ); + $cache->evictEntity($entityClass, $entityId); + + return 0; + } + + $ui->comment(sprintf('Clearing second-level cache for entity "%s"', $entityClass)); + $cache->evictEntityRegion($entityClass); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/MetadataCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/MetadataCommand.php new file mode 100644 index 0000000..147795b --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/MetadataCommand.php @@ -0,0 +1,52 @@ +setName('orm:clear-cache:metadata') + ->setDescription('Clear all metadata cache of the various cache drivers') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.') + ->setHelp(<<<'EOT' +The %command.name% command is meant to clear the metadata cache of associated Entity Manager. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + $cacheDriver = $em->getConfiguration()->getMetadataCache(); + + if (! $cacheDriver) { + throw new InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.'); + } + + $ui->comment('Clearing all Metadata cache entries'); + + $result = $cacheDriver->clear(); + $message = $result ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; + + $ui->success($message); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/QueryCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/QueryCommand.php new file mode 100644 index 0000000..83edd7a --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/QueryCommand.php @@ -0,0 +1,54 @@ +setName('orm:clear-cache:query') + ->setDescription('Clear all query cache of the various cache drivers') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->setHelp('The %command.name% command is meant to clear the query cache of associated Entity Manager.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + $cache = $em->getConfiguration()->getQueryCache(); + + if (! $cache) { + throw new InvalidArgumentException('No Query cache driver is configured on given EntityManager.'); + } + + if ($cache instanceof ApcuAdapter) { + throw new LogicException('Cannot clear APCu Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.'); + } + + $ui->comment('Clearing all Query cache entries'); + + $message = $cache->clear() ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; + + $ui->success($message); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/QueryRegionCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/QueryRegionCommand.php new file mode 100644 index 0000000..e80fb90 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/QueryRegionCommand.php @@ -0,0 +1,101 @@ +setName('orm:clear-cache:region:query') + ->setDescription('Clear a second-level cache query region') + ->addArgument('region-name', InputArgument::OPTIONAL, 'The query region to clear.') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all query regions will be deleted/invalidated.') + ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, all cache entries will be flushed.') + ->setHelp(<<<'EOT' +The %command.name% command is meant to clear a second-level cache query region for an associated Entity Manager. +It is possible to delete/invalidate all query region, a specific query region or flushes the cache provider. + +The execution type differ on how you execute the command. +If you want to invalidate all entries for the default query region this command would do the work: + +%command.name% + +To invalidate entries for a specific query region you should use : + +%command.name% my_region_name + +If you want to invalidate all entries for the all query region: + +%command.name% --all + +Alternatively, if you want to flush the configured cache provider use this command: + +%command.name% my_region_name --flush + +Finally, be aware that if --flush option is passed, +not all cache providers are able to flush entries, because of a limitation of its execution nature. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + $name = $input->getArgument('region-name'); + $cache = $em->getCache(); + + if ($name === null) { + $name = Cache::DEFAULT_QUERY_REGION_NAME; + } + + if (! $cache instanceof Cache) { + throw new InvalidArgumentException('No second-level cache is configured on the given EntityManager.'); + } + + if ($input->getOption('flush')) { + $cache->getQueryCache($name) + ->getRegion() + ->evictAll(); + + $ui->comment( + sprintf( + 'Flushing cache provider configured for second-level cache query region named "%s"', + $name, + ), + ); + + return 0; + } + + if ($input->getOption('all')) { + $ui->comment('Clearing all second-level cache query regions'); + + $cache->evictQueryRegions(); + + return 0; + } + + $ui->comment(sprintf('Clearing second-level cache query region named "%s"', $name)); + $cache->evictQueryRegion($name); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/ResultCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/ResultCommand.php new file mode 100644 index 0000000..4f84e0b --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/ClearCache/ResultCommand.php @@ -0,0 +1,65 @@ +setName('orm:clear-cache:result') + ->setDescription('Clear all result cache of the various cache drivers') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.') + ->setHelp(<<<'EOT' +The %command.name% command is meant to clear the result cache of associated Entity Manager. +It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider +instance completely. + +The execution type differ on how you execute the command. +If you want to invalidate the entries (and not delete from cache instance), this command would do the work: + +%command.name% + +Alternatively, if you want to flush the cache provider using this command: + +%command.name% --flush + +Finally, be aware that if --flush option is passed, not all cache providers are able to flush entries, +because of a limitation of its execution nature. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + $cache = $em->getConfiguration()->getResultCache(); + + if (! $cache) { + throw new InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); + } + + $ui->comment('Clearing all Result cache entries'); + + $message = $cache->clear() ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; + + $ui->success($message); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/GenerateProxiesCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/GenerateProxiesCommand.php new file mode 100644 index 0000000..5a407de --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/GenerateProxiesCommand.php @@ -0,0 +1,96 @@ +setName('orm:generate-proxies') + ->setAliases(['orm:generate:proxies']) + ->setDescription('Generates proxy classes for entity classes') + ->addArgument('dest-path', InputArgument::OPTIONAL, 'The path to generate your proxy classes. If none is provided, it will attempt to grab from configuration.') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.') + ->setHelp('Generates proxy classes for entity classes.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + + $metadatas = $em->getMetadataFactory()->getAllMetadata(); + $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); + + // Process destination directory + $destPath = $input->getArgument('dest-path'); + if ($destPath === null) { + $destPath = $em->getConfiguration()->getProxyDir(); + + if ($destPath === null) { + throw new InvalidArgumentException('Proxy directory cannot be null'); + } + } + + if (! is_dir($destPath)) { + mkdir($destPath, 0775, true); + } + + $destPath = realpath($destPath); + + if (! file_exists($destPath)) { + throw new InvalidArgumentException( + sprintf("Proxies destination directory '%s' does not exist.", $em->getConfiguration()->getProxyDir()), + ); + } + + if (! is_writable($destPath)) { + throw new InvalidArgumentException( + sprintf("Proxies destination directory '%s' does not have write permissions.", $destPath), + ); + } + + if (empty($metadatas)) { + $ui->success('No Metadata Classes to process.'); + + return 0; + } + + foreach ($metadatas as $metadata) { + $ui->text(sprintf('Processing entity "%s"', $metadata->name)); + } + + // Generating Proxies + $em->getProxyFactory()->generateProxyClasses($metadatas, $destPath); + + // Outputting information message + $ui->newLine(); + $ui->text(sprintf('Proxy classes generated to "%s"', $destPath)); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/InfoCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/InfoCommand.php new file mode 100644 index 0000000..deebb58 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/InfoCommand.php @@ -0,0 +1,80 @@ +setName('orm:info') + ->setDescription('Show basic information about all mapped entities') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->setHelp(<<<'EOT' +The %command.name% shows basic information about which +entities exist and possibly if their mapping information contains errors or +not. +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $entityManager = $this->getEntityManager($input); + + $entityClassNames = $entityManager->getConfiguration() + ->getMetadataDriverImpl() + ->getAllClassNames(); + + if (! $entityClassNames) { + $ui->caution( + [ + 'You do not have any mapped Doctrine ORM entities according to the current configuration.', + 'If you have entities or mapping files you should check your mapping configuration for errors.', + ], + ); + + return 1; + } + + $ui->text(sprintf('Found %d mapped entities:', count($entityClassNames))); + $ui->newLine(); + + $failure = false; + + foreach ($entityClassNames as $entityClassName) { + try { + $entityManager->getClassMetadata($entityClassName); + $ui->text(sprintf('[OK] %s', $entityClassName)); + } catch (MappingException $e) { + $ui->text( + [ + sprintf('[FAIL] %s', $entityClassName), + sprintf('%s', $e->getMessage()), + '', + ], + ); + + $failure = true; + } + } + + return $failure ? 1 : 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/MappingDescribeCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/MappingDescribeCommand.php new file mode 100644 index 0000000..41a177d --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/MappingDescribeCommand.php @@ -0,0 +1,279 @@ +setName('orm:mapping:describe') + ->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity') + ->setDescription('Display information about mapped objects') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->setHelp(<<<'EOT' +The %command.full_name% command describes the metadata for the given full or partial entity class name. + + %command.full_name% My\Namespace\Entity\MyEntity + +Or: + + %command.full_name% MyEntity +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $entityManager = $this->getEntityManager($input); + + $this->displayEntity($input->getArgument('entityName'), $entityManager, $ui); + + return 0; + } + + /** + * Display all the mapping information for a single Entity. + * + * @param string $entityName Full or partial entity class name + */ + private function displayEntity( + string $entityName, + EntityManagerInterface $entityManager, + SymfonyStyle $ui, + ): void { + $metadata = $this->getClassMetadata($entityName, $entityManager); + + $ui->table( + ['Field', 'Value'], + array_merge( + [ + $this->formatField('Name', $metadata->name), + $this->formatField('Root entity name', $metadata->rootEntityName), + $this->formatField('Custom generator definition', $metadata->customGeneratorDefinition), + $this->formatField('Custom repository class', $metadata->customRepositoryClassName), + $this->formatField('Mapped super class?', $metadata->isMappedSuperclass), + $this->formatField('Embedded class?', $metadata->isEmbeddedClass), + $this->formatField('Parent classes', $metadata->parentClasses), + $this->formatField('Sub classes', $metadata->subClasses), + $this->formatField('Embedded classes', $metadata->subClasses), + $this->formatField('Identifier', $metadata->identifier), + $this->formatField('Inheritance type', $metadata->inheritanceType), + $this->formatField('Discriminator column', $metadata->discriminatorColumn), + $this->formatField('Discriminator value', $metadata->discriminatorValue), + $this->formatField('Discriminator map', $metadata->discriminatorMap), + $this->formatField('Generator type', $metadata->generatorType), + $this->formatField('Table', $metadata->table), + $this->formatField('Composite identifier?', $metadata->isIdentifierComposite), + $this->formatField('Foreign identifier?', $metadata->containsForeignIdentifier), + $this->formatField('Enum identifier?', $metadata->containsEnumIdentifier), + $this->formatField('Sequence generator definition', $metadata->sequenceGeneratorDefinition), + $this->formatField('Change tracking policy', $metadata->changeTrackingPolicy), + $this->formatField('Versioned?', $metadata->isVersioned), + $this->formatField('Version field', $metadata->versionField), + $this->formatField('Read only?', $metadata->isReadOnly), + + $this->formatEntityListeners($metadata->entityListeners), + ], + [$this->formatField('Association mappings:', '')], + $this->formatMappings($metadata->associationMappings), + [$this->formatField('Field mappings:', '')], + $this->formatMappings($metadata->fieldMappings), + ), + ); + } + + /** + * Return all mapped entity class names + * + * @return string[] + * @psalm-return class-string[] + */ + private function getMappedEntities(EntityManagerInterface $entityManager): array + { + $entityClassNames = $entityManager->getConfiguration() + ->getMetadataDriverImpl() + ->getAllClassNames(); + + if (! $entityClassNames) { + throw new InvalidArgumentException( + 'You do not have any mapped Doctrine ORM entities according to the current configuration. ' . + 'If you have entities or mapping files you should check your mapping configuration for errors.', + ); + } + + return $entityClassNames; + } + + /** + * Return the class metadata for the given entity + * name + * + * @param string $entityName Full or partial entity name + */ + private function getClassMetadata( + string $entityName, + EntityManagerInterface $entityManager, + ): ClassMetadata { + try { + return $entityManager->getClassMetadata($entityName); + } catch (MappingException) { + } + + $matches = array_filter( + $this->getMappedEntities($entityManager), + static fn ($mappedEntity) => preg_match('{' . preg_quote($entityName) . '}', $mappedEntity) + ); + + if (! $matches) { + throw new InvalidArgumentException(sprintf( + 'Could not find any mapped Entity classes matching "%s"', + $entityName, + )); + } + + if (count($matches) > 1) { + throw new InvalidArgumentException(sprintf( + 'Entity name "%s" is ambiguous, possible matches: "%s"', + $entityName, + implode(', ', $matches), + )); + } + + return $entityManager->getClassMetadata(current($matches)); + } + + /** + * Format the given value for console output + */ + private function formatValue(mixed $value): string + { + if ($value === '') { + return ''; + } + + if ($value === null) { + return 'Null'; + } + + if (is_bool($value)) { + return '' . ($value ? 'True' : 'False') . ''; + } + + if (empty($value)) { + return 'Empty'; + } + + if (is_array($value)) { + return json_encode( + $value, + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR, + ); + } + + if (is_object($value)) { + return sprintf('<%s>', get_debug_type($value)); + } + + if (is_scalar($value)) { + return (string) $value; + } + + throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true))); + } + + /** + * Add the given label and value to the two column table output + * + * @param string $label Label for the value + * @param mixed $value A Value to show + * + * @return string[] + * @psalm-return array{0: string, 1: string} + */ + private function formatField(string $label, mixed $value): array + { + if ($value === null) { + $value = 'None'; + } + + return [sprintf('%s', $label), $this->formatValue($value)]; + } + + /** + * Format the association mappings + * + * @psalm-param array $propertyMappings + * + * @return string[][] + * @psalm-return list + */ + private function formatMappings(array $propertyMappings): array + { + $output = []; + + foreach ($propertyMappings as $propertyName => $mapping) { + $output[] = $this->formatField(sprintf(' %s', $propertyName), ''); + + foreach ((array) $mapping as $field => $value) { + $output[] = $this->formatField(sprintf(' %s', $field), $this->formatValue($value)); + } + } + + return $output; + } + + /** + * Format the entity listeners + * + * @psalm-param list $entityListeners + * + * @return string[] + * @psalm-return array{0: string, 1: string} + */ + private function formatEntityListeners(array $entityListeners): array + { + return $this->formatField('Entity listeners', array_map('get_class', $entityListeners)); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/RunDqlCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/RunDqlCommand.php new file mode 100644 index 0000000..252151e --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/RunDqlCommand.php @@ -0,0 +1,118 @@ +setName('orm:run-dql') + ->setDescription('Executes arbitrary DQL directly from the command line') + ->addArgument('dql', InputArgument::REQUIRED, 'The DQL to execute.') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('hydrate', null, InputOption::VALUE_REQUIRED, 'Hydration mode of result set. Should be either: object, array, scalar or single-scalar.', 'object') + ->addOption('first-result', null, InputOption::VALUE_REQUIRED, 'The first result in the result set.') + ->addOption('max-result', null, InputOption::VALUE_REQUIRED, 'The maximum number of results in the result set.') + ->addOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of Entity graph.', 7) + ->addOption('show-sql', null, InputOption::VALUE_NONE, 'Dump generated SQL instead of executing query') + ->setHelp(<<<'EOT' + The %command.name% command executes the given DQL query and + outputs the results: + + php %command.full_name% "SELECT u FROM App\Entity\User u" + + You can also optionally specify some additional options like what type of + hydration to use when executing the query: + + php %command.full_name% "SELECT u FROM App\Entity\User u" --hydrate=array + + Additionally you can specify the first result and maximum amount of results to + show: + + php %command.full_name% "SELECT u FROM App\Entity\User u" --first-result=0 --max-result=30 + EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = new SymfonyStyle($input, $output); + + $em = $this->getEntityManager($input); + + $dql = $input->getArgument('dql'); + if ($dql === null) { + throw new RuntimeException("Argument 'dql' is required in order to execute this command correctly."); + } + + $depth = $input->getOption('depth'); + + if (! is_numeric($depth)) { + throw new LogicException("Option 'depth' must contain an integer value"); + } + + $hydrationModeName = (string) $input->getOption('hydrate'); + $hydrationMode = 'Doctrine\ORM\Query::HYDRATE_' . strtoupper(str_replace('-', '_', $hydrationModeName)); + + if (! defined($hydrationMode)) { + throw new RuntimeException(sprintf( + "Hydration mode '%s' does not exist. It should be either: object. array, scalar or single-scalar.", + $hydrationModeName, + )); + } + + $query = $em->createQuery($dql); + + $firstResult = $input->getOption('first-result'); + if ($firstResult !== null) { + if (! is_numeric($firstResult)) { + throw new LogicException("Option 'first-result' must contain an integer value"); + } + + $query->setFirstResult((int) $firstResult); + } + + $maxResult = $input->getOption('max-result'); + if ($maxResult !== null) { + if (! is_numeric($maxResult)) { + throw new LogicException("Option 'max-result' must contain an integer value"); + } + + $query->setMaxResults((int) $maxResult); + } + + if ($input->getOption('show-sql')) { + $ui->text($query->getSQL()); + + return 0; + } + + $resultSet = $query->execute([], constant($hydrationMode)); + + $ui->text(Debug::dump($resultSet, (int) $input->getOption('depth'))); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/AbstractCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/AbstractCommand.php new file mode 100644 index 0000000..b1e4460 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/AbstractCommand.php @@ -0,0 +1,39 @@ +getEntityManager($input); + + $metadatas = $em->getMetadataFactory()->getAllMetadata(); + + if (empty($metadatas)) { + $ui->getErrorStyle()->success('No Metadata Classes to process.'); + + return 0; + } + + return $this->executeSchemaCommand($input, $output, new SchemaTool($em), $metadatas, $ui); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/CreateCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/CreateCommand.php new file mode 100644 index 0000000..69e20c6 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/CreateCommand.php @@ -0,0 +1,75 @@ +setName('orm:schema-tool:create') + ->setDescription('Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Instead of trying to apply generated SQLs into EntityManager Storage Connection, output them.') + ->setHelp(<<<'EOT' +Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. + +Hint: If you have a database with tables that should not be managed +by the ORM, you can use a DBAL functionality to filter the tables and sequences down +on a global level: + + $config->setSchemaAssetsFilter(function (string|AbstractAsset $assetName): bool { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return !str_starts_with($assetName, 'audit_'); + }); +EOT); + } + + /** + * {@inheritDoc} + */ + protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui): int + { + $dumpSql = $input->getOption('dump-sql') === true; + + if ($dumpSql) { + $sqls = $schemaTool->getCreateSchemaSql($metadatas); + + foreach ($sqls as $sql) { + $ui->writeln(sprintf('%s;', $sql)); + } + + return 0; + } + + $notificationUi = $ui->getErrorStyle(); + + $notificationUi->caution('This operation should not be executed in a production environment!'); + + $notificationUi->text('Creating database schema...'); + $notificationUi->newLine(); + + $schemaTool->createSchema($metadatas); + + $notificationUi->success('Database schema created successfully!'); + + return 0; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/DropCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/DropCommand.php new file mode 100644 index 0000000..5c8253b --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/DropCommand.php @@ -0,0 +1,116 @@ +setName('orm:schema-tool:drop') + ->setDescription('Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Instead of trying to apply generated SQLs into EntityManager Storage Connection, output them.') + ->addOption('force', 'f', InputOption::VALUE_NONE, "Don't ask for the deletion of the database, but force the operation to run.") + ->addOption('full-database', null, InputOption::VALUE_NONE, 'Instead of using the Class Metadata to detect the database table schema, drop ALL assets that the database contains.') + ->setHelp(<<<'EOT' +Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. +Beware that the complete database is dropped by this command, even tables that are not relevant to your metadata model. + +Hint: If you have a database with tables that should not be managed +by the ORM, you can use a DBAL functionality to filter the tables and sequences down +on a global level: + + $config->setSchemaAssetsFilter(function (string|AbstractAsset $assetName): bool { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return !str_starts_with($assetName, 'audit_'); + }); +EOT); + } + + /** + * {@inheritDoc} + */ + protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui): int + { + $isFullDatabaseDrop = $input->getOption('full-database'); + $dumpSql = $input->getOption('dump-sql') === true; + $force = $input->getOption('force') === true; + + if ($dumpSql) { + if ($isFullDatabaseDrop) { + $sqls = $schemaTool->getDropDatabaseSQL(); + } else { + $sqls = $schemaTool->getDropSchemaSQL($metadatas); + } + + foreach ($sqls as $sql) { + $ui->writeln(sprintf('%s;', $sql)); + } + + return 0; + } + + $notificationUi = $ui->getErrorStyle(); + + if ($force) { + $notificationUi->text('Dropping database schema...'); + $notificationUi->newLine(); + + if ($isFullDatabaseDrop) { + $schemaTool->dropDatabase(); + } else { + $schemaTool->dropSchema($metadatas); + } + + $notificationUi->success('Database schema dropped successfully!'); + + return 0; + } + + $notificationUi->caution('This operation should not be executed in a production environment!'); + + if ($isFullDatabaseDrop) { + $sqls = $schemaTool->getDropDatabaseSQL(); + } else { + $sqls = $schemaTool->getDropSchemaSQL($metadatas); + } + + if (empty($sqls)) { + $notificationUi->success('Nothing to drop. The database is empty!'); + + return 0; + } + + $notificationUi->text( + [ + sprintf('The Schema-Tool would execute "%s" queries to update the database.', count($sqls)), + '', + 'Please run the operation by passing one - or both - of the following options:', + '', + sprintf(' %s --force to execute the command', $this->getName()), + sprintf(' %s --dump-sql to dump the SQL statements to the screen', $this->getName()), + ], + ); + + return 1; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/UpdateCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/UpdateCommand.php new file mode 100644 index 0000000..f35fc38 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/SchemaTool/UpdateCommand.php @@ -0,0 +1,147 @@ +setName($this->name) + ->setDescription('Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('complete', null, InputOption::VALUE_NONE, 'This option is a no-op, is deprecated and will be removed in 4.0') + ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Dumps the generated SQL statements to the screen (does not execute them).') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Causes the generated SQL statements to be physically executed against your database.') + ->setHelp(<<<'EOT' +The %command.name% command generates the SQL needed to +synchronize the database schema with the current mapping metadata of the +default entity manager. + +For example, if you add metadata for a new column to an entity, this command +would generate and output the SQL needed to add the new column to the database: + +%command.name% --dump-sql + +Alternatively, you can execute the generated queries: + +%command.name% --force + +If both options are specified, the queries are output and then executed: + +%command.name% --dump-sql --force + +Finally, be aware that this task will drop all database assets (e.g. tables, +etc) that are *not* described by the current metadata. In other words, without +this option, this task leaves untouched any "extra" tables that exist in the +database, but which aren't described by any metadata. + +Hint: If you have a database with tables that should not be managed +by the ORM, you can use a DBAL functionality to filter the tables and sequences down +on a global level: + + $config->setSchemaAssetsFilter(function (string|AbstractAsset $assetName): bool { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return !str_starts_with($assetName, 'audit_'); + }); +EOT); + } + + /** + * {@inheritDoc} + */ + protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui): int + { + $notificationUi = $ui->getErrorStyle(); + + if ($input->getOption('complete') === true) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11354', + 'The --complete option is a no-op, is deprecated and will be removed in Doctrine ORM 4.0.', + ); + $notificationUi->warning('The --complete option is a no-op, is deprecated and will be removed in Doctrine ORM 4.0.'); + } + + $sqls = $schemaTool->getUpdateSchemaSql($metadatas); + + if (empty($sqls)) { + $notificationUi->success('Nothing to update - your database is already in sync with the current entity metadata.'); + + return 0; + } + + $dumpSql = $input->getOption('dump-sql') === true; + $force = $input->getOption('force') === true; + + if ($dumpSql) { + foreach ($sqls as $sql) { + $ui->writeln(sprintf('%s;', $sql)); + } + } + + if ($force) { + if ($dumpSql) { + $notificationUi->newLine(); + } + + $notificationUi->text('Updating database schema...'); + $notificationUi->newLine(); + + $schemaTool->updateSchema($metadatas); + + $pluralization = count($sqls) === 1 ? 'query was' : 'queries were'; + + $notificationUi->text(sprintf(' %s %s executed', count($sqls), $pluralization)); + $notificationUi->success('Database schema updated successfully!'); + } + + if ($dumpSql || $force) { + return 0; + } + + $notificationUi->caution( + [ + 'This operation should not be executed in a production environment!', + '', + 'Use the incremental update to detect changes during development and use', + 'the SQL DDL provided to manually update your database in production.', + ], + ); + + $notificationUi->text( + [ + sprintf('The Schema-Tool would execute "%s" queries to update the database.', count($sqls)), + '', + 'Please run the operation by passing one - or both - of the following options:', + '', + sprintf(' %s --force to execute the command', $this->getName()), + sprintf(' %s --dump-sql to dump the SQL statements to the screen', $this->getName()), + ], + ); + + return 1; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/Command/ValidateSchemaCommand.php b/vendor/doctrine/orm/src/Tools/Console/Command/ValidateSchemaCommand.php new file mode 100644 index 0000000..cffb4ce --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/Command/ValidateSchemaCommand.php @@ -0,0 +1,89 @@ +setName('orm:validate-schema') + ->setDescription('Validate the mapping files') + ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') + ->addOption('skip-mapping', null, InputOption::VALUE_NONE, 'Skip the mapping validation check') + ->addOption('skip-sync', null, InputOption::VALUE_NONE, 'Skip checking if the mapping is in sync with the database') + ->addOption('skip-property-types', null, InputOption::VALUE_NONE, 'Skip checking if property types match the Doctrine types') + ->setHelp('Validate that the mapping files are correct and in sync with the database.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + $em = $this->getEntityManager($input); + $validator = new SchemaValidator($em, ! $input->getOption('skip-property-types')); + $exit = 0; + + $ui->section('Mapping'); + + if ($input->getOption('skip-mapping')) { + $ui->text('[SKIPPED] The mapping was not checked.'); + } else { + $errors = $validator->validateMapping(); + if ($errors) { + foreach ($errors as $className => $errorMessages) { + $ui->text( + sprintf( + '[FAIL] The entity-class %s mapping is invalid:', + $className, + ), + ); + + $ui->listing($errorMessages); + $ui->newLine(); + } + + ++$exit; + } else { + $ui->success('The mapping files are correct.'); + } + } + + $ui->section('Database'); + + if ($input->getOption('skip-sync')) { + $ui->text('[SKIPPED] The database was not checked for synchronicity.'); + } elseif (! $validator->schemaInSyncWithMetadata()) { + $ui->error('The database schema is not in sync with the current mapping file.'); + + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $sqls = $validator->getUpdateSchemaList(); + $ui->comment(sprintf('%d schema diff(s) detected:', count($sqls))); + foreach ($sqls as $sql) { + $ui->text(sprintf(' %s;', $sql)); + } + } + + $exit += 2; + } else { + $ui->success('The database schema is in sync with the mapping files.'); + } + + return $exit; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/ConsoleRunner.php b/vendor/doctrine/orm/src/Tools/Console/ConsoleRunner.php new file mode 100644 index 0000000..0a00483 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/ConsoleRunner.php @@ -0,0 +1,88 @@ +run(); + } + + /** + * Creates a console application with the given helperset and + * optional commands. + * + * @param SymfonyCommand[] $commands + * + * @throws OutOfBoundsException + */ + public static function createApplication( + EntityManagerProvider $entityManagerProvider, + array $commands = [], + ): Application { + $version = InstalledVersions::getVersion('doctrine/orm'); + assert($version !== null); + + $cli = new Application('Doctrine Command Line Interface', $version); + $cli->setCatchExceptions(true); + + self::addCommands($cli, $entityManagerProvider); + $cli->addCommands($commands); + + return $cli; + } + + public static function addCommands(Application $cli, EntityManagerProvider $entityManagerProvider): void + { + $connectionProvider = new ConnectionFromManagerProvider($entityManagerProvider); + + if (class_exists(DBALConsole\Command\ReservedWordsCommand::class)) { + $cli->add(new DBALConsole\Command\ReservedWordsCommand($connectionProvider)); + } + + $cli->addCommands( + [ + // DBAL Commands + new DBALConsole\Command\RunSqlCommand($connectionProvider), + + // ORM Commands + new Command\ClearCache\CollectionRegionCommand($entityManagerProvider), + new Command\ClearCache\EntityRegionCommand($entityManagerProvider), + new Command\ClearCache\MetadataCommand($entityManagerProvider), + new Command\ClearCache\QueryCommand($entityManagerProvider), + new Command\ClearCache\QueryRegionCommand($entityManagerProvider), + new Command\ClearCache\ResultCommand($entityManagerProvider), + new Command\SchemaTool\CreateCommand($entityManagerProvider), + new Command\SchemaTool\UpdateCommand($entityManagerProvider), + new Command\SchemaTool\DropCommand($entityManagerProvider), + new Command\GenerateProxiesCommand($entityManagerProvider), + new Command\RunDqlCommand($entityManagerProvider), + new Command\ValidateSchemaCommand($entityManagerProvider), + new Command\InfoCommand($entityManagerProvider), + new Command\MappingDescribeCommand($entityManagerProvider), + ], + ); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider.php b/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider.php new file mode 100644 index 0000000..866589b --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider.php @@ -0,0 +1,14 @@ +entityManagerProvider->getDefaultManager()->getConnection(); + } + + public function getConnection(string $name): Connection + { + return $this->entityManagerProvider->getManager($name)->getConnection(); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider/SingleManagerProvider.php b/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider/SingleManagerProvider.php new file mode 100644 index 0000000..ebe60c9 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider/SingleManagerProvider.php @@ -0,0 +1,31 @@ +entityManager; + } + + public function getManager(string $name): EntityManagerInterface + { + if ($name !== $this->defaultManagerName) { + throw UnknownManagerException::unknownManager($name, [$this->defaultManagerName]); + } + + return $this->entityManager; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider/UnknownManagerException.php b/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider/UnknownManagerException.php new file mode 100644 index 0000000..583d909 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/EntityManagerProvider/UnknownManagerException.php @@ -0,0 +1,23 @@ + $knownManagers */ + public static function unknownManager(string $unknownManager, array $knownManagers = []): self + { + return new self(sprintf( + 'Requested unknown entity manager: %s, known managers: %s', + $unknownManager, + implode(', ', $knownManagers), + )); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Console/MetadataFilter.php b/vendor/doctrine/orm/src/Tools/Console/MetadataFilter.php new file mode 100644 index 0000000..05e248c --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Console/MetadataFilter.php @@ -0,0 +1,92 @@ +filter = (array) $filter; + + parent::__construct($metadata); + } + + public function accept(): bool + { + if (count($this->filter) === 0) { + return true; + } + + $it = $this->getInnerIterator(); + $metadata = $it->current(); + + foreach ($this->filter as $filter) { + $pregResult = preg_match('/' . $filter . '/', $metadata->getName()); + + if ($pregResult === false) { + throw new RuntimeException( + sprintf("Error while evaluating regex '/%s/'.", $filter), + ); + } + + if ($pregResult) { + return true; + } + } + + return false; + } + + /** @return ArrayIterator */ + public function getInnerIterator(): ArrayIterator + { + $innerIterator = parent::getInnerIterator(); + + assert($innerIterator instanceof ArrayIterator); + + return $innerIterator; + } + + public function count(): int + { + return count($this->getInnerIterator()); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Debug.php b/vendor/doctrine/orm/src/Tools/Debug.php new file mode 100644 index 0000000..8521e53 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Debug.php @@ -0,0 +1,158 @@ +toArray(); + } + + if (! $maxDepth) { + return is_object($var) ? $var::class + : (is_array($var) ? 'Array(' . count($var) . ')' : $var); + } + + if (is_array($var)) { + $return = []; + + foreach ($var as $k => $v) { + $return[$k] = self::export($v, $maxDepth - 1); + } + + return $return; + } + + if (! is_object($var)) { + return $var; + } + + $return = new stdClass(); + if ($var instanceof DateTimeInterface) { + $return->__CLASS__ = $var::class; + $return->date = $var->format('c'); + $return->timezone = $var->getTimezone()->getName(); + + return $return; + } + + $return->__CLASS__ = DefaultProxyClassNameResolver::getClass($var); + + if ($var instanceof Proxy) { + $return->__IS_PROXY__ = true; + $return->__PROXY_INITIALIZED__ = $var->__isInitialized(); + } + + if ($var instanceof ArrayObject || $var instanceof ArrayIterator) { + $return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1); + } + + return self::fillReturnWithClassAttributes($var, $return, $maxDepth); + } + + /** + * Fill the $return variable with class attributes + * Based on obj2array function from {@see https://secure.php.net/manual/en/function.get-object-vars.php#47075} + */ + private static function fillReturnWithClassAttributes(object $var, stdClass $return, int $maxDepth): stdClass + { + $clone = (array) $var; + + foreach (array_keys($clone) as $key) { + $aux = explode("\0", (string) $key); + $name = end($aux); + if ($aux[0] === '') { + $name .= ':' . ($aux[1] === '*' ? 'protected' : $aux[1] . ':private'); + } + + $return->$name = self::export($clone[$key], $maxDepth - 1); + } + + return $return; + } +} diff --git a/vendor/doctrine/orm/src/Tools/DebugUnitOfWorkListener.php b/vendor/doctrine/orm/src/Tools/DebugUnitOfWorkListener.php new file mode 100644 index 0000000..71059f7 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/DebugUnitOfWorkListener.php @@ -0,0 +1,144 @@ +dumpIdentityMap($args->getObjectManager()); + } + + /** + * Dumps the contents of the identity map into a stream. + */ + public function dumpIdentityMap(EntityManagerInterface $em): void + { + $uow = $em->getUnitOfWork(); + $identityMap = $uow->getIdentityMap(); + + $fh = fopen($this->file, 'xb+'); + if (count($identityMap) === 0) { + fwrite($fh, 'Flush Operation [' . $this->context . "] - Empty identity map.\n"); + + return; + } + + fwrite($fh, 'Flush Operation [' . $this->context . "] - Dumping identity map:\n"); + foreach ($identityMap as $className => $map) { + fwrite($fh, 'Class: ' . $className . "\n"); + + foreach ($map as $entity) { + fwrite($fh, ' Entity: ' . $this->getIdString($entity, $uow) . ' ' . spl_object_id($entity) . "\n"); + fwrite($fh, " Associations:\n"); + + $cm = $em->getClassMetadata($className); + + foreach ($cm->associationMappings as $field => $assoc) { + fwrite($fh, ' ' . $field . ' '); + $value = $cm->getFieldValue($entity, $field); + + if ($assoc->isToOne()) { + if ($value === null) { + fwrite($fh, " NULL\n"); + } else { + if ($uow->isUninitializedObject($value)) { + fwrite($fh, '[PROXY] '); + } + + fwrite($fh, $this->getIdString($value, $uow) . ' ' . spl_object_id($value) . "\n"); + } + } else { + $initialized = ! ($value instanceof PersistentCollection) || $value->isInitialized(); + if ($value === null) { + fwrite($fh, " NULL\n"); + } elseif ($initialized) { + fwrite($fh, '[INITIALIZED] ' . $this->getType($value) . ' ' . count($value) . " elements\n"); + + foreach ($value as $obj) { + fwrite($fh, ' ' . $this->getIdString($obj, $uow) . ' ' . spl_object_id($obj) . "\n"); + } + } else { + fwrite($fh, '[PROXY] ' . $this->getType($value) . " unknown element size\n"); + foreach ($value->unwrap() as $obj) { + fwrite($fh, ' ' . $this->getIdString($obj, $uow) . ' ' . spl_object_id($obj) . "\n"); + } + } + } + } + } + } + + fclose($fh); + } + + private function getType(mixed $var): string + { + if (is_object($var)) { + $refl = new ReflectionObject($var); + + return $refl->getShortName(); + } + + return gettype($var); + } + + private function getIdString(object $entity, UnitOfWork $uow): string + { + if ($uow->isInIdentityMap($entity)) { + $ids = $uow->getEntityIdentifier($entity); + $idstring = ''; + + foreach ($ids as $k => $v) { + $idstring .= $k . '=' . $v; + } + } else { + $idstring = 'NEWOBJECT '; + } + + $state = $uow->getEntityState($entity); + + if ($state === UnitOfWork::STATE_NEW) { + $idstring .= ' [NEW]'; + } elseif ($state === UnitOfWork::STATE_REMOVED) { + $idstring .= ' [REMOVED]'; + } elseif ($state === UnitOfWork::STATE_MANAGED) { + $idstring .= ' [MANAGED]'; + } elseif ($state === UnitOfWork::STATE_DETACHED) { + $idstring .= ' [DETACHED]'; + } + + return $idstring; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Event/GenerateSchemaEventArgs.php b/vendor/doctrine/orm/src/Tools/Event/GenerateSchemaEventArgs.php new file mode 100644 index 0000000..3b0993e --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Event/GenerateSchemaEventArgs.php @@ -0,0 +1,33 @@ +em; + } + + public function getSchema(): Schema + { + return $this->schema; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Event/GenerateSchemaTableEventArgs.php b/vendor/doctrine/orm/src/Tools/Event/GenerateSchemaTableEventArgs.php new file mode 100644 index 0000000..a09aaae --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Event/GenerateSchemaTableEventArgs.php @@ -0,0 +1,40 @@ +classMetadata; + } + + public function getSchema(): Schema + { + return $this->schema; + } + + public function getClassTable(): Table + { + return $this->classTable; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Exception/MissingColumnException.php b/vendor/doctrine/orm/src/Tools/Exception/MissingColumnException.php new file mode 100644 index 0000000..764721e --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Exception/MissingColumnException.php @@ -0,0 +1,23 @@ + FROM ()) + * + * Works with composite keys but cannot deal with queries that have multiple + * root entities (e.g. `SELECT f, b from Foo, Bar`) + * + * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL) + * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery + * that will most likely be executed next can be read from the native SQL cache. + * + * @psalm-import-type QueryComponent from Parser + */ +class CountOutputWalker extends SqlWalker +{ + private readonly AbstractPlatform $platform; + private readonly ResultSetMapping $rsm; + + /** + * {@inheritDoc} + */ + public function __construct(Query $query, ParserResult $parserResult, array $queryComponents) + { + $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); + $this->rsm = $parserResult->getResultSetMapping(); + + parent::__construct($query, $parserResult, $queryComponents); + } + + public function walkSelectStatement(SelectStatement $selectStatement): string + { + if ($this->platform instanceof SQLServerPlatform) { + $selectStatement->orderByClause = null; + } + + $sql = parent::walkSelectStatement($selectStatement); + + if ($selectStatement->groupByClause) { + return sprintf( + 'SELECT COUNT(*) AS dctrn_count FROM (%s) dctrn_table', + $sql, + ); + } + + // Find out the SQL alias of the identifier column of the root entity + // It may be possible to make this work with multiple root entities but that + // would probably require issuing multiple queries or doing a UNION SELECT + // so for now, It's not supported. + + // Get the root entity and alias from the AST fromClause + $from = $selectStatement->fromClause->identificationVariableDeclarations; + if (count($from) > 1) { + throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); + } + + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + $rootIdentifier = $rootClass->identifier; + + // For every identifier, find out the SQL alias by combing through the ResultSetMapping + $sqlIdentifier = []; + foreach ($rootIdentifier as $property) { + if (isset($rootClass->fieldMappings[$property])) { + foreach (array_keys($this->rsm->fieldMappings, $property, true) as $alias) { + if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { + $sqlIdentifier[$property] = $alias; + } + } + } + + if (isset($rootClass->associationMappings[$property])) { + $association = $rootClass->associationMappings[$property]; + assert($association->isToOneOwningSide()); + $joinColumn = $association->joinColumns[0]->name; + + foreach (array_keys($this->rsm->metaMappings, $joinColumn, true) as $alias) { + if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { + $sqlIdentifier[$property] = $alias; + } + } + } + } + + if (count($rootIdentifier) !== count($sqlIdentifier)) { + throw new RuntimeException(sprintf( + 'Not all identifier properties can be found in the ResultSetMapping: %s', + implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))), + )); + } + + // Build the counter query + return sprintf( + 'SELECT COUNT(*) AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table', + implode(', ', $sqlIdentifier), + $sql, + ); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Pagination/CountWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/CountWalker.php new file mode 100644 index 0000000..d212943 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Pagination/CountWalker.php @@ -0,0 +1,68 @@ +havingClause) { + throw new RuntimeException('Cannot count query that uses a HAVING clause. Use the output walkers for pagination'); + } + + // Get the root entity and alias from the AST fromClause + $from = $selectStatement->fromClause->identificationVariableDeclarations; + + if (count($from) > 1) { + throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); + } + + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + $identifierFieldName = $rootClass->getSingleIdentifierFieldName(); + + $pathType = PathExpression::TYPE_STATE_FIELD; + if (isset($rootClass->associationMappings[$identifierFieldName])) { + $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; + } + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, + $rootAlias, + $identifierFieldName, + ); + $pathExpression->type = $pathType; + + $distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT); + $selectStatement->selectClause->selectExpressions = [ + new SelectExpression( + new AggregateExpression('count', $pathExpression, $distinct), + null, + ), + ]; + + // ORDER BY is not needed, only increases query execution through unnecessary sorting. + $selectStatement->orderByClause = null; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php b/vendor/doctrine/orm/src/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php new file mode 100644 index 0000000..0e3da93 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php @@ -0,0 +1,16 @@ + FROM () LIMIT x OFFSET y + * + * Works with composite keys but cannot deal with queries that have multiple + * root entities (e.g. `SELECT f, b from Foo, Bar`) + * + * @psalm-import-type QueryComponent from Parser + */ +class LimitSubqueryOutputWalker extends SqlWalker +{ + private const ORDER_BY_PATH_EXPRESSION = '/(? */ + private array $orderByPathExpressions = []; + + /** + * We don't want to add path expressions from sub-selects into the select clause of the containing query. + * This state flag simply keeps track on whether we are walking on a subquery or not + */ + private bool $inSubSelect = false; + + /** + * Stores various parameters that are otherwise unavailable + * because Doctrine\ORM\Query\SqlWalker keeps everything private without + * accessors. + * + * {@inheritDoc} + */ + public function __construct( + Query $query, + ParserResult $parserResult, + array $queryComponents, + ) { + $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); + $this->rsm = $parserResult->getResultSetMapping(); + + // Reset limit and offset + $this->firstResult = $query->getFirstResult(); + $this->maxResults = $query->getMaxResults(); + $query->setFirstResult(0)->setMaxResults(null); + + $this->em = $query->getEntityManager(); + $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy(); + + parent::__construct($query, $parserResult, $queryComponents); + } + + /** + * Check if the platform supports the ROW_NUMBER window function. + */ + private function platformSupportsRowNumber(): bool + { + return $this->platform instanceof PostgreSQLPlatform + || $this->platform instanceof SQLServerPlatform + || $this->platform instanceof OraclePlatform + || $this->platform instanceof DB2Platform + || (method_exists($this->platform, 'supportsRowNumberFunction') + && $this->platform->supportsRowNumberFunction()); + } + + /** + * Rebuilds a select statement's order by clause for use in a + * ROW_NUMBER() OVER() expression. + */ + private function rebuildOrderByForRowNumber(SelectStatement $AST): void + { + $orderByClause = $AST->orderByClause; + $selectAliasToExpressionMap = []; + // Get any aliases that are available for select expressions. + foreach ($AST->selectClause->selectExpressions as $selectExpression) { + $selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression; + } + + // Rebuild string orderby expressions to use the select expression they're referencing + foreach ($orderByClause->orderByItems as $orderByItem) { + if (is_string($orderByItem->expression) && isset($selectAliasToExpressionMap[$orderByItem->expression])) { + $orderByItem->expression = $selectAliasToExpressionMap[$orderByItem->expression]; + } + } + + $func = new RowNumberOverFunction('dctrn_rownum'); + $func->orderByClause = $AST->orderByClause; + $AST->selectClause->selectExpressions[] = new SelectExpression($func, 'dctrn_rownum', true); + + // No need for an order by clause, we'll order by rownum in the outer query. + $AST->orderByClause = null; + } + + public function walkSelectStatement(SelectStatement $selectStatement): string + { + if ($this->platformSupportsRowNumber()) { + return $this->walkSelectStatementWithRowNumber($selectStatement); + } + + return $this->walkSelectStatementWithoutRowNumber($selectStatement); + } + + /** + * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT. + * This method is for use with platforms which support ROW_NUMBER. + * + * @throws RuntimeException + */ + public function walkSelectStatementWithRowNumber(SelectStatement $AST): string + { + $hasOrderBy = false; + $outerOrderBy = ' ORDER BY dctrn_minrownum ASC'; + $orderGroupBy = ''; + if ($AST->orderByClause instanceof OrderByClause) { + $hasOrderBy = true; + $this->rebuildOrderByForRowNumber($AST); + } + + $innerSql = $this->getInnerSQL($AST); + + $sqlIdentifier = $this->getSQLIdentifier($AST); + + if ($hasOrderBy) { + $orderGroupBy = ' GROUP BY ' . implode(', ', $sqlIdentifier); + $sqlIdentifier[] = 'MIN(' . $this->walkResultVariable('dctrn_rownum') . ') AS dctrn_minrownum'; + } + + // Build the counter query + $sql = sprintf( + 'SELECT DISTINCT %s FROM (%s) dctrn_result', + implode(', ', $sqlIdentifier), + $innerSql, + ); + + if ($hasOrderBy) { + $sql .= $orderGroupBy . $outerOrderBy; + } + + // Apply the limit and offset. + $sql = $this->platform->modifyLimitQuery( + $sql, + $this->maxResults, + $this->firstResult, + ); + + // Add the columns to the ResultSetMapping. It's not really nice but + // it works. Preferably I'd clear the RSM or simply create a new one + // but that is not possible from inside the output walker, so we dirty + // up the one we have. + foreach ($sqlIdentifier as $property => $alias) { + $this->rsm->addScalarResult($alias, $property); + } + + return $sql; + } + + /** + * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT. + * This method is for platforms which DO NOT support ROW_NUMBER. + * + * @throws RuntimeException + */ + public function walkSelectStatementWithoutRowNumber(SelectStatement $AST, bool $addMissingItemsFromOrderByToSelect = true): string + { + // We don't want to call this recursively! + if ($AST->orderByClause instanceof OrderByClause && $addMissingItemsFromOrderByToSelect) { + // In the case of ordering a query by columns from joined tables, we + // must add those columns to the select clause of the query BEFORE + // the SQL is generated. + $this->addMissingItemsFromOrderByToSelect($AST); + } + + // Remove order by clause from the inner query + // It will be re-appended in the outer select generated by this method + $orderByClause = $AST->orderByClause; + $AST->orderByClause = null; + + $innerSql = $this->getInnerSQL($AST); + + $sqlIdentifier = $this->getSQLIdentifier($AST); + + // Build the counter query + $sql = sprintf( + 'SELECT DISTINCT %s FROM (%s) dctrn_result', + implode(', ', $sqlIdentifier), + $innerSql, + ); + + // https://github.com/doctrine/orm/issues/2630 + $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause); + + // Apply the limit and offset. + $sql = $this->platform->modifyLimitQuery( + $sql, + $this->maxResults, + $this->firstResult, + ); + + // Add the columns to the ResultSetMapping. It's not really nice but + // it works. Preferably I'd clear the RSM or simply create a new one + // but that is not possible from inside the output walker, so we dirty + // up the one we have. + foreach ($sqlIdentifier as $property => $alias) { + $this->rsm->addScalarResult($alias, $property); + } + + // Restore orderByClause + $AST->orderByClause = $orderByClause; + + return $sql; + } + + /** + * Finds all PathExpressions in an AST's OrderByClause, and ensures that + * the referenced fields are present in the SelectClause of the passed AST. + */ + private function addMissingItemsFromOrderByToSelect(SelectStatement $AST): void + { + $this->orderByPathExpressions = []; + + // We need to do this in another walker because otherwise we'll end up + // polluting the state of this one. + $walker = clone $this; + + // This will populate $orderByPathExpressions via + // LimitSubqueryOutputWalker::walkPathExpression, which will be called + // as the select statement is walked. We'll end up with an array of all + // path expressions referenced in the query. + $walker->walkSelectStatementWithoutRowNumber($AST, false); + $orderByPathExpressions = $walker->getOrderByPathExpressions(); + + // Get a map of referenced identifiers to field names. + $selects = []; + foreach ($orderByPathExpressions as $pathExpression) { + assert($pathExpression->field !== null); + $idVar = $pathExpression->identificationVariable; + $field = $pathExpression->field; + if (! isset($selects[$idVar])) { + $selects[$idVar] = []; + } + + $selects[$idVar][$field] = true; + } + + // Loop the select clause of the AST and exclude items from $select + // that are already being selected in the query. + foreach ($AST->selectClause->selectExpressions as $selectExpression) { + if ($selectExpression instanceof SelectExpression) { + $idVar = $selectExpression->expression; + if (! is_string($idVar)) { + continue; + } + + $field = $selectExpression->fieldIdentificationVariable; + if ($field === null) { + // No need to add this select, as we're already fetching the whole object. + unset($selects[$idVar]); + } else { + unset($selects[$idVar][$field]); + } + } + } + + // Add select items which were not excluded to the AST's select clause. + foreach ($selects as $idVar => $fields) { + $AST->selectClause->selectExpressions[] = new SelectExpression($idVar, null, true); + } + } + + /** + * Generates new SQL for statements with an order by clause + * + * @param mixed[] $sqlIdentifier + */ + private function preserveSqlOrdering( + array $sqlIdentifier, + string $innerSql, + string $sql, + OrderByClause|null $orderByClause, + ): string { + // If the sql statement has an order by clause, we need to wrap it in a new select distinct statement + if (! $orderByClause) { + return $sql; + } + + // now only select distinct identifier + return sprintf( + 'SELECT DISTINCT %s FROM (%s) dctrn_result', + implode(', ', $sqlIdentifier), + $this->recreateInnerSql($orderByClause, $sqlIdentifier, $innerSql), + ); + } + + /** + * Generates a new SQL statement for the inner query to keep the correct sorting + * + * @param mixed[] $identifiers + */ + private function recreateInnerSql( + OrderByClause $orderByClause, + array $identifiers, + string $innerSql, + ): string { + [$searchPatterns, $replacements] = $this->generateSqlAliasReplacements(); + $orderByItems = []; + + foreach ($orderByClause->orderByItems as $orderByItem) { + // Walk order by item to get string representation of it and + // replace path expressions in the order by clause with their column alias + $orderByItemString = preg_replace( + $searchPatterns, + $replacements, + $this->walkOrderByItem($orderByItem), + ); + + $orderByItems[] = $orderByItemString; + $identifier = substr($orderByItemString, 0, strrpos($orderByItemString, ' ')); + + if (! in_array($identifier, $identifiers, true)) { + $identifiers[] = $identifier; + } + } + + return $sql = sprintf( + 'SELECT DISTINCT %s FROM (%s) dctrn_result_inner ORDER BY %s', + implode(', ', $identifiers), + $innerSql, + implode(', ', $orderByItems), + ); + } + + /** + * @return string[][] + * @psalm-return array{0: list, 1: list} + */ + private function generateSqlAliasReplacements(): array + { + $aliasMap = $searchPatterns = $replacements = $metadataList = []; + + // Generate DQL alias -> SQL table alias mapping + foreach (array_keys($this->rsm->aliasMap) as $dqlAlias) { + $metadataList[$dqlAlias] = $class = $this->getMetadataForDqlAlias($dqlAlias); + $aliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); + } + + // Generate search patterns for each field's path expression in the order by clause + foreach ($this->rsm->fieldMappings as $fieldAlias => $fieldName) { + $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias]; + $class = $metadataList[$dqlAliasForFieldAlias]; + + // If the field is from a joined child table, we won't be ordering on it. + if (! isset($class->fieldMappings[$fieldName])) { + continue; + } + + $fieldMapping = $class->fieldMappings[$fieldName]; + + // Get the proper column name as will appear in the select list + $columnName = $this->quoteStrategy->getColumnName( + $fieldName, + $metadataList[$dqlAliasForFieldAlias], + $this->em->getConnection()->getDatabasePlatform(), + ); + + // Get the SQL table alias for the entity and field + $sqlTableAliasForFieldAlias = $aliasMap[$dqlAliasForFieldAlias]; + + if (isset($fieldMapping->declared) && $fieldMapping->declared !== $class->name) { + // Field was declared in a parent class, so we need to get the proper SQL table alias + // for the joined parent table. + $otherClassMetadata = $this->em->getClassMetadata($fieldMapping->declared); + + if (! $otherClassMetadata->isMappedSuperclass) { + $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias); + } + } + + // Compose search and replace patterns + $searchPatterns[] = sprintf(self::ORDER_BY_PATH_EXPRESSION, $sqlTableAliasForFieldAlias, $columnName); + $replacements[] = $fieldAlias; + } + + return [$searchPatterns, $replacements]; + } + + /** + * getter for $orderByPathExpressions + * + * @return list + */ + public function getOrderByPathExpressions(): array + { + return $this->orderByPathExpressions; + } + + /** + * @throws OptimisticLockException + * @throws QueryException + */ + private function getInnerSQL(SelectStatement $AST): string + { + // Set every select expression as visible(hidden = false) to + // make $AST have scalar mappings properly - this is relevant for referencing selected + // fields from outside the subquery, for example in the ORDER BY segment + $hiddens = []; + + foreach ($AST->selectClause->selectExpressions as $idx => $expr) { + $hiddens[$idx] = $expr->hiddenAliasResultVariable; + $expr->hiddenAliasResultVariable = false; + } + + $innerSql = parent::walkSelectStatement($AST); + + // Restore hiddens + foreach ($AST->selectClause->selectExpressions as $idx => $expr) { + $expr->hiddenAliasResultVariable = $hiddens[$idx]; + } + + return $innerSql; + } + + /** @return string[] */ + private function getSQLIdentifier(SelectStatement $AST): array + { + // Find out the SQL alias of the identifier column of the root entity. + // It may be possible to make this work with multiple root entities but that + // would probably require issuing multiple queries or doing a UNION SELECT. + // So for now, it's not supported. + + // Get the root entity and alias from the AST fromClause. + $from = $AST->fromClause->identificationVariableDeclarations; + if (count($from) !== 1) { + throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); + } + + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + $rootIdentifier = $rootClass->identifier; + + // For every identifier, find out the SQL alias by combing through the ResultSetMapping + $sqlIdentifier = []; + foreach ($rootIdentifier as $property) { + if (isset($rootClass->fieldMappings[$property])) { + foreach (array_keys($this->rsm->fieldMappings, $property, true) as $alias) { + if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { + $sqlIdentifier[$property] = $alias; + } + } + } + + if (isset($rootClass->associationMappings[$property])) { + $association = $rootClass->associationMappings[$property]; + assert($association->isToOneOwningSide()); + $joinColumn = $association->joinColumns[0]->name; + + foreach (array_keys($this->rsm->metaMappings, $joinColumn, true) as $alias) { + if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { + $sqlIdentifier[$property] = $alias; + } + } + } + } + + if (count($sqlIdentifier) === 0) { + throw new RuntimeException('The Paginator does not support Queries which only yield ScalarResults.'); + } + + if (count($rootIdentifier) !== count($sqlIdentifier)) { + throw new RuntimeException(sprintf( + 'Not all identifier properties can be found in the ResultSetMapping: %s', + implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))), + )); + } + + return $sqlIdentifier; + } + + public function walkPathExpression(PathExpression $pathExpr): string + { + if (! $this->inSubSelect && ! $this->platformSupportsRowNumber() && ! in_array($pathExpr, $this->orderByPathExpressions, true)) { + $this->orderByPathExpressions[] = $pathExpr; + } + + return parent::walkPathExpression($pathExpr); + } + + public function walkSubSelect(Subselect $subselect): string + { + $this->inSubSelect = true; + + $sql = parent::walkSubselect($subselect); + + $this->inSubSelect = false; + + return $sql; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryWalker.php new file mode 100644 index 0000000..3fb0eee --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryWalker.php @@ -0,0 +1,155 @@ +fromClause->identificationVariableDeclarations; + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + + $this->validate($selectStatement); + $identifier = $rootClass->getSingleIdentifierFieldName(); + + if (isset($rootClass->associationMappings[$identifier])) { + throw new RuntimeException('Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.'); + } + + $query = $this->_getQuery(); + + $query->setHint( + self::IDENTIFIER_TYPE, + Type::getType($rootClass->fieldMappings[$identifier]->type), + ); + + $query->setHint(self::FORCE_DBAL_TYPE_CONVERSION, true); + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, + $rootAlias, + $identifier, + ); + + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $selectStatement->selectClause->selectExpressions = [new SelectExpression($pathExpression, '_dctrn_id')]; + $selectStatement->selectClause->isDistinct = ($query->getHints()[Paginator::HINT_ENABLE_DISTINCT] ?? true) === true; + + if (! isset($selectStatement->orderByClause)) { + return; + } + + $queryComponents = $this->getQueryComponents(); + foreach ($selectStatement->orderByClause->orderByItems as $item) { + if ($item->expression instanceof PathExpression) { + $selectStatement->selectClause->selectExpressions[] = new SelectExpression( + $this->createSelectExpressionItem($item->expression), + '_dctrn_ord' . $this->aliasCounter++, + ); + + continue; + } + + if (is_string($item->expression) && isset($queryComponents[$item->expression])) { + $qComp = $queryComponents[$item->expression]; + + if (isset($qComp['resultVariable'])) { + $selectStatement->selectClause->selectExpressions[] = new SelectExpression( + $qComp['resultVariable'], + $item->expression, + ); + } + } + } + } + + /** + * Validate the AST to ensure that this walker is able to properly manipulate it. + */ + private function validate(SelectStatement $AST): void + { + // Prevent LimitSubqueryWalker from being used with queries that include + // a limit, a fetched to-many join, and an order by condition that + // references a column from the fetch joined table. + $queryComponents = $this->getQueryComponents(); + $query = $this->_getQuery(); + $from = $AST->fromClause->identificationVariableDeclarations; + $fromRoot = reset($from); + + if ( + $query instanceof Query + && $query->getMaxResults() !== null + && $AST->orderByClause + && count($fromRoot->joins) + ) { + // Check each orderby item. + // TODO: check complex orderby items too... + foreach ($AST->orderByClause->orderByItems as $orderByItem) { + $expression = $orderByItem->expression; + if ( + $orderByItem->expression instanceof PathExpression + && isset($queryComponents[$expression->identificationVariable]) + ) { + $queryComponent = $queryComponents[$expression->identificationVariable]; + if ( + isset($queryComponent['parent']) + && isset($queryComponent['relation']) + && $queryComponent['relation']->isToMany() + ) { + throw new RuntimeException('Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers.'); + } + } + } + } + } + + /** + * Retrieve either an IdentityFunction (IDENTITY(u.assoc)) or a state field (u.name). + * + * @return IdentityFunction|PathExpression + */ + private function createSelectExpressionItem(PathExpression $pathExpression): Node + { + if ($pathExpression->type === PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) { + $identity = new IdentityFunction('identity'); + + $identity->pathExpression = clone $pathExpression; + + return $identity; + } + + return clone $pathExpression; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Pagination/Paginator.php b/vendor/doctrine/orm/src/Tools/Pagination/Paginator.php new file mode 100644 index 0000000..db1b34d --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Pagination/Paginator.php @@ -0,0 +1,263 @@ + + */ +class Paginator implements Countable, IteratorAggregate +{ + use SQLResultCasing; + + public const HINT_ENABLE_DISTINCT = 'paginator.distinct.enable'; + + private readonly Query $query; + private bool|null $useOutputWalkers = null; + private int|null $count = null; + + /** @param bool $fetchJoinCollection Whether the query joins a collection (true by default). */ + public function __construct( + Query|QueryBuilder $query, + private readonly bool $fetchJoinCollection = true, + ) { + if ($query instanceof QueryBuilder) { + $query = $query->getQuery(); + } + + $this->query = $query; + } + + /** + * Returns the query. + */ + public function getQuery(): Query + { + return $this->query; + } + + /** + * Returns whether the query joins a collection. + * + * @return bool Whether the query joins a collection. + */ + public function getFetchJoinCollection(): bool + { + return $this->fetchJoinCollection; + } + + /** + * Returns whether the paginator will use an output walker. + */ + public function getUseOutputWalkers(): bool|null + { + return $this->useOutputWalkers; + } + + /** + * Sets whether the paginator will use an output walker. + * + * @return $this + */ + public function setUseOutputWalkers(bool|null $useOutputWalkers): static + { + $this->useOutputWalkers = $useOutputWalkers; + + return $this; + } + + public function count(): int + { + if ($this->count === null) { + try { + $this->count = (int) array_sum(array_map('current', $this->getCountQuery()->getScalarResult())); + } catch (NoResultException) { + $this->count = 0; + } + } + + return $this->count; + } + + /** + * {@inheritDoc} + * + * @psalm-return Traversable + */ + public function getIterator(): Traversable + { + $offset = $this->query->getFirstResult(); + $length = $this->query->getMaxResults(); + + if ($this->fetchJoinCollection && $length !== null) { + $subQuery = $this->cloneQuery($this->query); + + if ($this->useOutputWalker($subQuery)) { + $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class); + } else { + $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class); + $this->unbindUnusedQueryParams($subQuery); + } + + $subQuery->setFirstResult($offset)->setMaxResults($length); + + $foundIdRows = $subQuery->getScalarResult(); + + // don't do this for an empty id array + if ($foundIdRows === []) { + return new ArrayIterator([]); + } + + $whereInQuery = $this->cloneQuery($this->query); + $ids = array_map('current', $foundIdRows); + + $this->appendTreeWalker($whereInQuery, WhereInWalker::class); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_HAS_IDS, true); + $whereInQuery->setFirstResult(0)->setMaxResults(null); + $whereInQuery->setCacheable($this->query->isCacheable()); + + $databaseIds = $this->convertWhereInIdentifiersToDatabaseValues($ids); + $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $databaseIds); + + $result = $whereInQuery->getResult($this->query->getHydrationMode()); + } else { + $result = $this->cloneQuery($this->query) + ->setMaxResults($length) + ->setFirstResult($offset) + ->setCacheable($this->query->isCacheable()) + ->getResult($this->query->getHydrationMode()); + } + + return new ArrayIterator($result); + } + + private function cloneQuery(Query $query): Query + { + $cloneQuery = clone $query; + + $cloneQuery->setParameters(clone $query->getParameters()); + $cloneQuery->setCacheable(false); + + foreach ($query->getHints() as $name => $value) { + $cloneQuery->setHint($name, $value); + } + + return $cloneQuery; + } + + /** + * Determines whether to use an output walker for the query. + */ + private function useOutputWalker(Query $query): bool + { + if ($this->useOutputWalkers === null) { + return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false; + } + + return $this->useOutputWalkers; + } + + /** + * Appends a custom tree walker to the tree walkers hint. + * + * @psalm-param class-string $walkerClass + */ + private function appendTreeWalker(Query $query, string $walkerClass): void + { + $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS); + + if ($hints === false) { + $hints = []; + } + + $hints[] = $walkerClass; + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints); + } + + /** + * Returns Query prepared to count. + */ + private function getCountQuery(): Query + { + $countQuery = $this->cloneQuery($this->query); + + if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) { + $countQuery->setHint(CountWalker::HINT_DISTINCT, true); + } + + if ($this->useOutputWalker($countQuery)) { + $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win + + $rsm = new ResultSetMapping(); + $rsm->addScalarResult($this->getSQLResultCasing($platform, 'dctrn_count'), 'count'); + + $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class); + $countQuery->setResultSetMapping($rsm); + } else { + $this->appendTreeWalker($countQuery, CountWalker::class); + $this->unbindUnusedQueryParams($countQuery); + } + + $countQuery->setFirstResult(0)->setMaxResults(null); + + return $countQuery; + } + + private function unbindUnusedQueryParams(Query $query): void + { + $parser = new Parser($query); + $parameterMappings = $parser->parse()->getParameterMappings(); + /** @var Collection|Parameter[] $parameters */ + $parameters = $query->getParameters(); + + foreach ($parameters as $key => $parameter) { + $parameterName = $parameter->getName(); + + if (! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) { + unset($parameters[$key]); + } + } + + $query->setParameters($parameters); + } + + /** + * @param mixed[] $identifiers + * + * @return mixed[] + */ + private function convertWhereInIdentifiersToDatabaseValues(array $identifiers): array + { + $query = $this->cloneQuery($this->query); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class); + + $connection = $this->query->getEntityManager()->getConnection(); + $type = $query->getSQL(); + assert(is_string($type)); + + return array_map(static fn ($id): mixed => $connection->convertToDatabaseValue($id, $type), $identifiers); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Pagination/RootTypeWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/RootTypeWalker.php new file mode 100644 index 0000000..f630ee1 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Pagination/RootTypeWalker.php @@ -0,0 +1,48 @@ + root entity id type resolution can be cached in the query cache. + */ +final class RootTypeWalker extends SqlWalker +{ + public function walkSelectStatement(AST\SelectStatement $selectStatement): string + { + // Get the root entity and alias from the AST fromClause + $from = $selectStatement->fromClause->identificationVariableDeclarations; + + if (count($from) > 1) { + throw new RuntimeException('Can only process queries that select only one FROM component'); + } + + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + $identifierFieldName = $rootClass->getSingleIdentifierFieldName(); + + return PersisterHelper::getTypeOfField( + $identifierFieldName, + $rootClass, + $this->getQuery() + ->getEntityManager(), + )[0]; + } +} diff --git a/vendor/doctrine/orm/src/Tools/Pagination/RowNumberOverFunction.php b/vendor/doctrine/orm/src/Tools/Pagination/RowNumberOverFunction.php new file mode 100644 index 0000000..a0fdd01 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Pagination/RowNumberOverFunction.php @@ -0,0 +1,40 @@ +walkOrderByClause( + $this->orderByClause, + )) . ')'; + } + + /** + * @throws RowNumberOverFunctionNotEnabled + * + * @inheritdoc + */ + public function parse(Parser $parser): void + { + throw RowNumberOverFunctionNotEnabled::create(); + } +} diff --git a/vendor/doctrine/orm/src/Tools/Pagination/WhereInWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/WhereInWalker.php new file mode 100644 index 0000000..01741ca --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/Pagination/WhereInWalker.php @@ -0,0 +1,116 @@ +fromClause->identificationVariableDeclarations; + + if (count($from) > 1) { + throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); + } + + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + $identifierFieldName = $rootClass->getSingleIdentifierFieldName(); + + $pathType = PathExpression::TYPE_STATE_FIELD; + if (isset($rootClass->associationMappings[$identifierFieldName])) { + $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; + } + + $pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $rootAlias, $identifierFieldName); + $pathExpression->type = $pathType; + + $hasIds = $this->_getQuery()->getHint(self::HINT_PAGINATOR_HAS_IDS); + + if ($hasIds) { + $arithmeticExpression = new ArithmeticExpression(); + $arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression( + [$pathExpression], + ); + $expression = new InListExpression( + $arithmeticExpression, + [new InputParameter(':' . self::PAGINATOR_ID_ALIAS)], + ); + } else { + $expression = new NullComparisonExpression($pathExpression); + } + + $conditionalPrimary = new ConditionalPrimary(); + $conditionalPrimary->simpleConditionalExpression = $expression; + if ($selectStatement->whereClause) { + if ($selectStatement->whereClause->conditionalExpression instanceof ConditionalTerm) { + $selectStatement->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary; + } elseif ($selectStatement->whereClause->conditionalExpression instanceof ConditionalPrimary) { + $selectStatement->whereClause->conditionalExpression = new ConditionalExpression( + [ + new ConditionalTerm( + [ + $selectStatement->whereClause->conditionalExpression, + $conditionalPrimary, + ], + ), + ], + ); + } else { + $tmpPrimary = new ConditionalPrimary(); + $tmpPrimary->conditionalExpression = $selectStatement->whereClause->conditionalExpression; + $selectStatement->whereClause->conditionalExpression = new ConditionalTerm( + [ + $tmpPrimary, + $conditionalPrimary, + ], + ); + } + } else { + $selectStatement->whereClause = new WhereClause( + new ConditionalExpression( + [new ConditionalTerm([$conditionalPrimary])], + ), + ); + } + } +} diff --git a/vendor/doctrine/orm/src/Tools/ResolveTargetEntityListener.php b/vendor/doctrine/orm/src/Tools/ResolveTargetEntityListener.php new file mode 100644 index 0000000..9e48521 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/ResolveTargetEntityListener.php @@ -0,0 +1,117 @@ + $mapping + */ + public function addResolveTargetEntity(string $originalEntity, string $newEntity, array $mapping): void + { + $mapping['targetEntity'] = ltrim($newEntity, '\\'); + $this->resolveTargetEntities[ltrim($originalEntity, '\\')] = $mapping; + } + + /** @internal this is an event callback, and should not be called directly */ + public function onClassMetadataNotFound(OnClassMetadataNotFoundEventArgs $args): void + { + if (array_key_exists($args->getClassName(), $this->resolveTargetEntities)) { + $args->setFoundMetadata( + $args + ->getObjectManager() + ->getClassMetadata($this->resolveTargetEntities[$args->getClassName()]['targetEntity']), + ); + } + } + + /** + * Processes event and resolves new target entity names. + * + * @internal this is an event callback, and should not be called directly + */ + public function loadClassMetadata(LoadClassMetadataEventArgs $args): void + { + $cm = $args->getClassMetadata(); + + foreach ($cm->associationMappings as $mapping) { + if (isset($this->resolveTargetEntities[$mapping->targetEntity])) { + $this->remapAssociation($cm, $mapping); + } + } + + foreach ($this->resolveTargetEntities as $interface => $data) { + if ($data['targetEntity'] === $cm->getName()) { + $args->getEntityManager()->getMetadataFactory()->setMetadataFor($interface, $cm); + } + } + + foreach ($cm->discriminatorMap as $value => $class) { + if (isset($this->resolveTargetEntities[$class])) { + $cm->addDiscriminatorMapClass($value, $this->resolveTargetEntities[$class]['targetEntity']); + } + } + } + + private function remapAssociation(ClassMetadata $classMetadata, AssociationMapping $mapping): void + { + $newMapping = $this->resolveTargetEntities[$mapping->targetEntity]; + $newMapping = array_replace_recursive( + $mapping->toArray(), + $newMapping, + ); + $newMapping['fieldName'] = $mapping->fieldName; + + unset($classMetadata->associationMappings[$mapping->fieldName]); + + switch ($mapping->type()) { + case ClassMetadata::MANY_TO_MANY: + $classMetadata->mapManyToMany($newMapping); + break; + case ClassMetadata::MANY_TO_ONE: + $classMetadata->mapManyToOne($newMapping); + break; + case ClassMetadata::ONE_TO_MANY: + $classMetadata->mapOneToMany($newMapping); + break; + case ClassMetadata::ONE_TO_ONE: + $classMetadata->mapOneToOne($newMapping); + break; + } + } +} diff --git a/vendor/doctrine/orm/src/Tools/SchemaTool.php b/vendor/doctrine/orm/src/Tools/SchemaTool.php new file mode 100644 index 0000000..42b52df --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/SchemaTool.php @@ -0,0 +1,932 @@ +ClassMetadata class descriptors. + * + * @link www.doctrine-project.org + */ +class SchemaTool +{ + private const KNOWN_COLUMN_OPTIONS = ['comment', 'unsigned', 'fixed', 'default']; + + private readonly AbstractPlatform $platform; + private readonly QuoteStrategy $quoteStrategy; + private readonly AbstractSchemaManager $schemaManager; + + /** + * Initializes a new SchemaTool instance that uses the connection of the + * provided EntityManager. + */ + public function __construct(private readonly EntityManagerInterface $em) + { + $this->platform = $em->getConnection()->getDatabasePlatform(); + $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + $this->schemaManager = $em->getConnection()->createSchemaManager(); + } + + /** + * Creates the database schema for the given array of ClassMetadata instances. + * + * @psalm-param list $classes + * + * @throws ToolsException + */ + public function createSchema(array $classes): void + { + $createSchemaSql = $this->getCreateSchemaSql($classes); + $conn = $this->em->getConnection(); + + foreach ($createSchemaSql as $sql) { + try { + $conn->executeStatement($sql); + } catch (Throwable $e) { + throw ToolsException::schemaToolFailure($sql, $e); + } + } + } + + /** + * Gets the list of DDL statements that are required to create the database schema for + * the given list of ClassMetadata instances. + * + * @psalm-param list $classes + * + * @return list The SQL statements needed to create the schema for the classes. + */ + public function getCreateSchemaSql(array $classes): array + { + $schema = $this->getSchemaFromMetadata($classes); + + return $schema->toSql($this->platform); + } + + /** + * Detects instances of ClassMetadata that don't need to be processed in the SchemaTool context. + * + * @psalm-param array $processedClasses + */ + private function processingNotRequired( + ClassMetadata $class, + array $processedClasses, + ): bool { + return isset($processedClasses[$class->name]) || + $class->isMappedSuperclass || + $class->isEmbeddedClass || + ($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName) || + in_array($class->name, $this->em->getConfiguration()->getSchemaIgnoreClasses()); + } + + /** + * Resolves fields in index mapping to column names + * + * @param mixed[] $indexData index or unique constraint data + * + * @return list Column names from combined fields and columns mappings + */ + private function getIndexColumns(ClassMetadata $class, array $indexData): array + { + $columns = []; + + if ( + isset($indexData['columns'], $indexData['fields']) + || ( + ! isset($indexData['columns']) + && ! isset($indexData['fields']) + ) + ) { + throw MappingException::invalidIndexConfiguration( + (string) $class, + $indexData['name'] ?? 'unnamed', + ); + } + + if (isset($indexData['columns'])) { + $columns = $indexData['columns']; + } + + if (isset($indexData['fields'])) { + foreach ($indexData['fields'] as $fieldName) { + if ($class->hasField($fieldName)) { + $columns[] = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); + } elseif ($class->hasAssociation($fieldName)) { + $assoc = $class->getAssociationMapping($fieldName); + assert($assoc->isToOneOwningSide()); + foreach ($assoc->joinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + } + } + } + } + + return $columns; + } + + /** + * Creates a Schema instance from a given set of metadata classes. + * + * @psalm-param list $classes + * + * @throws NotSupported + */ + public function getSchemaFromMetadata(array $classes): Schema + { + // Reminder for processed classes, used for hierarchies + $processedClasses = []; + $eventManager = $this->em->getEventManager(); + $metadataSchemaConfig = $this->schemaManager->createSchemaConfig(); + + $schema = new Schema([], [], $metadataSchemaConfig); + + $addedFks = []; + $blacklistedFks = []; + + foreach ($classes as $class) { + if ($this->processingNotRequired($class, $processedClasses)) { + continue; + } + + $table = $schema->createTable($this->quoteStrategy->getTableName($class, $this->platform)); + + if ($class->isInheritanceTypeSingleTable()) { + $this->gatherColumns($class, $table); + $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); + + // Add the discriminator column + $this->addDiscriminatorColumnDefinition($class, $table); + + // Aggregate all the information from all classes in the hierarchy + foreach ($class->parentClasses as $parentClassName) { + // Parent class information is already contained in this class + $processedClasses[$parentClassName] = true; + } + + foreach ($class->subClasses as $subClassName) { + $subClass = $this->em->getClassMetadata($subClassName); + $this->gatherColumns($subClass, $table); + $this->gatherRelationsSql($subClass, $table, $schema, $addedFks, $blacklistedFks); + $processedClasses[$subClassName] = true; + } + } elseif ($class->isInheritanceTypeJoined()) { + // Add all non-inherited fields as columns + foreach ($class->fieldMappings as $fieldName => $mapping) { + if (! isset($mapping->inherited)) { + $this->gatherColumn($class, $mapping, $table); + } + } + + $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); + + // Add the discriminator column only to the root table + if ($class->name === $class->rootEntityName) { + $this->addDiscriminatorColumnDefinition($class, $table); + } else { + // Add an ID FK column to child tables + $pkColumns = []; + $inheritedKeyColumns = []; + + foreach ($class->identifier as $identifierField) { + if (isset($class->fieldMappings[$identifierField]->inherited)) { + $idMapping = $class->fieldMappings[$identifierField]; + $this->gatherColumn($class, $idMapping, $table); + $columnName = $this->quoteStrategy->getColumnName( + $identifierField, + $class, + $this->platform, + ); + // TODO: This seems rather hackish, can we optimize it? + $table->getColumn($columnName)->setAutoincrement(false); + + $pkColumns[] = $columnName; + $inheritedKeyColumns[] = $columnName; + + continue; + } + + if (isset($class->associationMappings[$identifierField]->inherited)) { + $idMapping = $class->associationMappings[$identifierField]; + assert($idMapping->isToOneOwningSide()); + + $targetEntity = current( + array_filter( + $classes, + static fn (ClassMetadata $class): bool => $class->name === $idMapping->targetEntity, + ), + ); + + foreach ($idMapping->joinColumns as $joinColumn) { + if (isset($targetEntity->fieldMappings[$joinColumn->referencedColumnName])) { + $columnName = $this->quoteStrategy->getJoinColumnName( + $joinColumn, + $class, + $this->platform, + ); + + $pkColumns[] = $columnName; + $inheritedKeyColumns[] = $columnName; + } + } + } + } + + if ($inheritedKeyColumns !== []) { + // Add a FK constraint on the ID column + $table->addForeignKeyConstraint( + $this->quoteStrategy->getTableName( + $this->em->getClassMetadata($class->rootEntityName), + $this->platform, + ), + $inheritedKeyColumns, + $inheritedKeyColumns, + ['onDelete' => 'CASCADE'], + ); + } + + if ($pkColumns !== []) { + $table->setPrimaryKey($pkColumns); + } + } + } else { + $this->gatherColumns($class, $table); + $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); + } + + $pkColumns = []; + + foreach ($class->identifier as $identifierField) { + if (isset($class->fieldMappings[$identifierField])) { + $pkColumns[] = $this->quoteStrategy->getColumnName($identifierField, $class, $this->platform); + } elseif (isset($class->associationMappings[$identifierField])) { + $assoc = $class->associationMappings[$identifierField]; + assert($assoc->isToOneOwningSide()); + + foreach ($assoc->joinColumns as $joinColumn) { + $pkColumns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + } + } + } + + if (! $table->hasIndex('primary')) { + $table->setPrimaryKey($pkColumns); + } + + // there can be unique indexes automatically created for join column + // if join column is also primary key we should keep only primary key on this column + // so, remove indexes overruled by primary key + $primaryKey = $table->getIndex('primary'); + + foreach ($table->getIndexes() as $idxKey => $existingIndex) { + if ($primaryKey->overrules($existingIndex)) { + $table->dropIndex($idxKey); + } + } + + if (isset($class->table['indexes'])) { + foreach ($class->table['indexes'] as $indexName => $indexData) { + if (! isset($indexData['flags'])) { + $indexData['flags'] = []; + } + + $table->addIndex( + $this->getIndexColumns($class, $indexData), + is_numeric($indexName) ? null : $indexName, + (array) $indexData['flags'], + $indexData['options'] ?? [], + ); + } + } + + if (isset($class->table['uniqueConstraints'])) { + foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) { + $uniqIndex = new Index('tmp__' . $indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []); + + foreach ($table->getIndexes() as $tableIndexName => $tableIndex) { + if ($tableIndex->isFulfilledBy($uniqIndex)) { + $table->dropIndex($tableIndexName); + break; + } + } + + $table->addUniqueIndex($uniqIndex->getColumns(), is_numeric($indexName) ? null : $indexName, $indexData['options'] ?? []); + } + } + + if (isset($class->table['options'])) { + foreach ($class->table['options'] as $key => $val) { + $table->addOption($key, $val); + } + } + + $processedClasses[$class->name] = true; + + if ($class->isIdGeneratorSequence() && $class->name === $class->rootEntityName) { + $seqDef = $class->sequenceGeneratorDefinition; + $quotedName = $this->quoteStrategy->getSequenceName($seqDef, $class, $this->platform); + if (! $schema->hasSequence($quotedName)) { + $schema->createSequence( + $quotedName, + (int) $seqDef['allocationSize'], + (int) $seqDef['initialValue'], + ); + } + } + + if ($eventManager->hasListeners(ToolEvents::postGenerateSchemaTable)) { + $eventManager->dispatchEvent( + ToolEvents::postGenerateSchemaTable, + new GenerateSchemaTableEventArgs($class, $schema, $table), + ); + } + } + + if ($eventManager->hasListeners(ToolEvents::postGenerateSchema)) { + $eventManager->dispatchEvent( + ToolEvents::postGenerateSchema, + new GenerateSchemaEventArgs($this->em, $schema), + ); + } + + return $schema; + } + + /** + * Gets a portable column definition as required by the DBAL for the discriminator + * column of a class. + */ + private function addDiscriminatorColumnDefinition(ClassMetadata $class, Table $table): void + { + $discrColumn = $class->discriminatorColumn; + assert($discrColumn !== null); + + if (strtolower($discrColumn->type) === 'string' && ! isset($discrColumn->length)) { + $discrColumn->type = 'string'; + $discrColumn->length = 255; + } + + $options = [ + 'length' => $discrColumn->length ?? null, + 'notnull' => true, + ]; + + if (isset($discrColumn->columnDefinition)) { + $options['columnDefinition'] = $discrColumn->columnDefinition; + } + + $options = $this->gatherColumnOptions($discrColumn) + $options; + $table->addColumn($discrColumn->name, $discrColumn->type, $options); + } + + /** + * Gathers the column definitions as required by the DBAL of all field mappings + * found in the given class. + */ + private function gatherColumns(ClassMetadata $class, Table $table): void + { + $pkColumns = []; + + foreach ($class->fieldMappings as $mapping) { + if ($class->isInheritanceTypeSingleTable() && isset($mapping->inherited)) { + continue; + } + + $this->gatherColumn($class, $mapping, $table); + + if ($class->isIdentifier($mapping->fieldName)) { + $pkColumns[] = $this->quoteStrategy->getColumnName($mapping->fieldName, $class, $this->platform); + } + } + } + + /** + * Creates a column definition as required by the DBAL from an ORM field mapping definition. + * + * @param ClassMetadata $class The class that owns the field mapping. + * @psalm-param FieldMapping $mapping The field mapping. + */ + private function gatherColumn( + ClassMetadata $class, + FieldMapping $mapping, + Table $table, + ): void { + $columnName = $this->quoteStrategy->getColumnName($mapping->fieldName, $class, $this->platform); + $columnType = $mapping->type; + + $options = []; + $options['length'] = $mapping->length ?? null; + $options['notnull'] = isset($mapping->nullable) ? ! $mapping->nullable : true; + if ($class->isInheritanceTypeSingleTable() && $class->parentClasses) { + $options['notnull'] = false; + } + + $options['platformOptions'] = []; + $options['platformOptions']['version'] = $class->isVersioned && $class->versionField === $mapping->fieldName; + + if (strtolower($columnType) === 'string' && $options['length'] === null) { + $options['length'] = 255; + } + + if (isset($mapping->precision)) { + $options['precision'] = $mapping->precision; + } + + if (isset($mapping->scale)) { + $options['scale'] = $mapping->scale; + } + + if (isset($mapping->default)) { + $options['default'] = $mapping->default; + } + + if (isset($mapping->columnDefinition)) { + $options['columnDefinition'] = $mapping->columnDefinition; + } + + // the 'default' option can be overwritten here + $options = $this->gatherColumnOptions($mapping) + $options; + + if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() === [$mapping->fieldName]) { + $options['autoincrement'] = true; + } + + if ($class->isInheritanceTypeJoined() && $class->name !== $class->rootEntityName) { + $options['autoincrement'] = false; + } + + if ($table->hasColumn($columnName)) { + // required in some inheritance scenarios + $table->modifyColumn($columnName, $options); + } else { + $table->addColumn($columnName, $columnType, $options); + } + + $isUnique = $mapping->unique ?? false; + if ($isUnique) { + $table->addUniqueIndex([$columnName]); + } + } + + /** + * Gathers the SQL for properly setting up the relations of the given class. + * This includes the SQL for foreign key constraints and join tables. + * + * @psalm-param array + * }> $addedFks + * @psalm-param array $blacklistedFks + * + * @throws NotSupported + */ + private function gatherRelationsSql( + ClassMetadata $class, + Table $table, + Schema $schema, + array &$addedFks, + array &$blacklistedFks, + ): void { + foreach ($class->associationMappings as $id => $mapping) { + if (isset($mapping->inherited) && ! in_array($id, $class->identifier, true)) { + continue; + } + + $foreignClass = $this->em->getClassMetadata($mapping->targetEntity); + + if ($mapping->isToOneOwningSide()) { + $primaryKeyColumns = []; // PK is unnecessary for this relation-type + + $this->gatherRelationJoinColumns( + $mapping->joinColumns, + $table, + $foreignClass, + $mapping, + $primaryKeyColumns, + $addedFks, + $blacklistedFks, + ); + } elseif ($mapping instanceof ManyToManyOwningSideMapping) { + // create join table + $joinTable = $mapping->joinTable; + + $theJoinTable = $schema->createTable( + $this->quoteStrategy->getJoinTableName($mapping, $foreignClass, $this->platform), + ); + + foreach ($joinTable->options as $key => $val) { + $theJoinTable->addOption($key, $val); + } + + $primaryKeyColumns = []; + + // Build first FK constraint (relation table => source table) + $this->gatherRelationJoinColumns( + $joinTable->joinColumns, + $theJoinTable, + $class, + $mapping, + $primaryKeyColumns, + $addedFks, + $blacklistedFks, + ); + + // Build second FK constraint (relation table => target table) + $this->gatherRelationJoinColumns( + $joinTable->inverseJoinColumns, + $theJoinTable, + $foreignClass, + $mapping, + $primaryKeyColumns, + $addedFks, + $blacklistedFks, + ); + + $theJoinTable->setPrimaryKey($primaryKeyColumns); + } + } + } + + /** + * Gets the class metadata that is responsible for the definition of the referenced column name. + * + * Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its + * not a simple field, go through all identifier field names that are associations recursively and + * find that referenced column name. + * + * TODO: Is there any way to make this code more pleasing? + * + * @psalm-return array{ClassMetadata, string}|null + */ + private function getDefiningClass(ClassMetadata $class, string $referencedColumnName): array|null + { + $referencedFieldName = $class->getFieldName($referencedColumnName); + + if ($class->hasField($referencedFieldName)) { + return [$class, $referencedFieldName]; + } + + if (in_array($referencedColumnName, $class->getIdentifierColumnNames(), true)) { + // it seems to be an entity as foreign key + foreach ($class->getIdentifierFieldNames() as $fieldName) { + if ( + $class->hasAssociation($fieldName) + && $class->getSingleAssociationJoinColumnName($fieldName) === $referencedColumnName + ) { + return $this->getDefiningClass( + $this->em->getClassMetadata($class->associationMappings[$fieldName]->targetEntity), + $class->getSingleAssociationReferencedJoinColumnName($fieldName), + ); + } + } + } + + return null; + } + + /** + * Gathers columns and fk constraints that are required for one part of relationship. + * + * @psalm-param list $joinColumns + * @psalm-param list $primaryKeyColumns + * @psalm-param array + * }> $addedFks + * @psalm-param array $blacklistedFks + * + * @throws MissingColumnException + */ + private function gatherRelationJoinColumns( + array $joinColumns, + Table $theJoinTable, + ClassMetadata $class, + AssociationMapping $mapping, + array &$primaryKeyColumns, + array &$addedFks, + array &$blacklistedFks, + ): void { + $localColumns = []; + $foreignColumns = []; + $fkOptions = []; + $foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform); + $uniqueConstraints = []; + + foreach ($joinColumns as $joinColumn) { + [$definingClass, $referencedFieldName] = $this->getDefiningClass( + $class, + $joinColumn->referencedColumnName, + ); + + if (! $definingClass) { + throw MissingColumnException::fromColumnSourceAndTarget( + $joinColumn->referencedColumnName, + $mapping->sourceEntity, + $mapping->targetEntity, + ); + } + + $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + $quotedRefColumnName = $this->quoteStrategy->getReferencedJoinColumnName( + $joinColumn, + $class, + $this->platform, + ); + + $primaryKeyColumns[] = $quotedColumnName; + $localColumns[] = $quotedColumnName; + $foreignColumns[] = $quotedRefColumnName; + + if (! $theJoinTable->hasColumn($quotedColumnName)) { + // Only add the column to the table if it does not exist already. + // It might exist already if the foreign key is mapped into a regular + // property as well. + + $fieldMapping = $definingClass->getFieldMapping($referencedFieldName); + + $columnOptions = ['notnull' => false]; + + if (isset($joinColumn->columnDefinition)) { + $columnOptions['columnDefinition'] = $joinColumn->columnDefinition; + } elseif (isset($fieldMapping->columnDefinition)) { + $columnOptions['columnDefinition'] = $fieldMapping->columnDefinition; + } + + if (isset($joinColumn->nullable)) { + $columnOptions['notnull'] = ! $joinColumn->nullable; + } + + $columnOptions += $this->gatherColumnOptions($fieldMapping); + + if (isset($fieldMapping->length)) { + $columnOptions['length'] = $fieldMapping->length; + } + + if ($fieldMapping->type === 'decimal') { + $columnOptions['scale'] = $fieldMapping->scale; + $columnOptions['precision'] = $fieldMapping->precision; + } + + $columnOptions = $this->gatherColumnOptions($joinColumn) + $columnOptions; + + $theJoinTable->addColumn($quotedColumnName, $fieldMapping->type, $columnOptions); + } + + if (isset($joinColumn->unique) && $joinColumn->unique === true) { + $uniqueConstraints[] = ['columns' => [$quotedColumnName]]; + } + + if (isset($joinColumn->onDelete)) { + $fkOptions['onDelete'] = $joinColumn->onDelete; + } + } + + // Prefer unique constraints over implicit simple indexes created for foreign keys. + // Also avoids index duplication. + foreach ($uniqueConstraints as $indexName => $unique) { + $theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); + } + + $compositeName = $theJoinTable->getName() . '.' . implode('', $localColumns); + if ( + isset($addedFks[$compositeName]) + && ($foreignTableName !== $addedFks[$compositeName]['foreignTableName'] + || 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns']))) + ) { + foreach ($theJoinTable->getForeignKeys() as $fkName => $key) { + if ( + count(array_diff($key->getLocalColumns(), $localColumns)) === 0 + && (($key->getForeignTableName() !== $foreignTableName) + || 0 < count(array_diff($key->getForeignColumns(), $foreignColumns))) + ) { + $theJoinTable->removeForeignKey($fkName); + break; + } + } + + $blacklistedFks[$compositeName] = true; + } elseif (! isset($blacklistedFks[$compositeName])) { + $addedFks[$compositeName] = ['foreignTableName' => $foreignTableName, 'foreignColumns' => $foreignColumns]; + $theJoinTable->addForeignKeyConstraint( + $foreignTableName, + $localColumns, + $foreignColumns, + $fkOptions, + ); + } + } + + /** @return mixed[] */ + private function gatherColumnOptions(JoinColumnMapping|FieldMapping|DiscriminatorColumnMapping $mapping): array + { + $mappingOptions = $mapping->options ?? []; + + if (isset($mapping->enumType)) { + $mappingOptions['enumType'] = $mapping->enumType; + } + + if (($mappingOptions['default'] ?? null) instanceof BackedEnum) { + $mappingOptions['default'] = $mappingOptions['default']->value; + } + + if (empty($mappingOptions)) { + return []; + } + + $options = array_intersect_key($mappingOptions, array_flip(self::KNOWN_COLUMN_OPTIONS)); + $options['platformOptions'] = array_diff_key($mappingOptions, $options); + + return $options; + } + + /** + * Drops the database schema for the given classes. + * + * In any way when an exception is thrown it is suppressed since drop was + * issued for all classes of the schema and some probably just don't exist. + * + * @psalm-param list $classes + */ + public function dropSchema(array $classes): void + { + $dropSchemaSql = $this->getDropSchemaSQL($classes); + $conn = $this->em->getConnection(); + + foreach ($dropSchemaSql as $sql) { + try { + $conn->executeStatement($sql); + } catch (Throwable) { + // ignored + } + } + } + + /** + * Drops all elements in the database of the current connection. + */ + public function dropDatabase(): void + { + $dropSchemaSql = $this->getDropDatabaseSQL(); + $conn = $this->em->getConnection(); + + foreach ($dropSchemaSql as $sql) { + $conn->executeStatement($sql); + } + } + + /** + * Gets the SQL needed to drop the database schema for the connections database. + * + * @return list + */ + public function getDropDatabaseSQL(): array + { + return $this->schemaManager + ->introspectSchema() + ->toDropSql($this->platform); + } + + /** + * Gets SQL to drop the tables defined by the passed classes. + * + * @psalm-param list $classes + * + * @return list + */ + public function getDropSchemaSQL(array $classes): array + { + $schema = $this->getSchemaFromMetadata($classes); + + $deployedSchema = $this->schemaManager->introspectSchema(); + + foreach ($schema->getTables() as $table) { + if (! $deployedSchema->hasTable($table->getName())) { + $schema->dropTable($table->getName()); + } + } + + if ($this->platform->supportsSequences()) { + foreach ($schema->getSequences() as $sequence) { + if (! $deployedSchema->hasSequence($sequence->getName())) { + $schema->dropSequence($sequence->getName()); + } + } + + foreach ($schema->getTables() as $table) { + $primaryKey = $table->getPrimaryKey(); + if ($primaryKey === null) { + continue; + } + + $columns = $primaryKey->getColumns(); + if (count($columns) === 1) { + $checkSequence = $table->getName() . '_' . $columns[0] . '_seq'; + if ($deployedSchema->hasSequence($checkSequence) && ! $schema->hasSequence($checkSequence)) { + $schema->createSequence($checkSequence); + } + } + } + } + + return $schema->toDropSql($this->platform); + } + + /** + * Updates the database schema of the given classes by comparing the ClassMetadata + * instances to the current database schema that is inspected. + * + * @param mixed[] $classes + */ + public function updateSchema(array $classes): void + { + $conn = $this->em->getConnection(); + + foreach ($this->getUpdateSchemaSql($classes) as $sql) { + $conn->executeStatement($sql); + } + } + + /** + * Gets the sequence of SQL statements that need to be performed in order + * to bring the given class mappings in-synch with the relational schema. + * + * @param list $classes The classes to consider. + * + * @return list The sequence of SQL statements. + */ + public function getUpdateSchemaSql(array $classes): array + { + $toSchema = $this->getSchemaFromMetadata($classes); + $fromSchema = $this->createSchemaForComparison($toSchema); + $comparator = $this->schemaManager->createComparator(); + $schemaDiff = $comparator->compareSchemas($fromSchema, $toSchema); + + return $this->platform->getAlterSchemaSQL($schemaDiff); + } + + /** + * Creates the schema from the database, ensuring tables from the target schema are whitelisted for comparison. + */ + private function createSchemaForComparison(Schema $toSchema): Schema + { + $connection = $this->em->getConnection(); + + // backup schema assets filter + $config = $connection->getConfiguration(); + $previousFilter = $config->getSchemaAssetsFilter(); + + if ($previousFilter === null) { + return $this->schemaManager->introspectSchema(); + } + + // whitelist assets we already know about in $toSchema, use the existing filter otherwise + $config->setSchemaAssetsFilter(static function ($asset) use ($previousFilter, $toSchema): bool { + $assetName = $asset instanceof AbstractAsset ? $asset->getName() : $asset; + + return $toSchema->hasTable($assetName) || $toSchema->hasSequence($assetName) || $previousFilter($asset); + }); + + try { + return $this->schemaManager->introspectSchema(); + } finally { + // restore schema assets filter + $config->setSchemaAssetsFilter($previousFilter); + } + } +} diff --git a/vendor/doctrine/orm/src/Tools/SchemaValidator.php b/vendor/doctrine/orm/src/Tools/SchemaValidator.php new file mode 100644 index 0000000..fdfc003 --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/SchemaValidator.php @@ -0,0 +1,443 @@ + ['string'], + BigIntType::class => ['int', 'string'], + BooleanType::class => ['bool'], + DecimalType::class => ['string'], + FloatType::class => ['float'], + GuidType::class => ['string'], + IntegerType::class => ['int'], + JsonType::class => ['array'], + SimpleArrayType::class => ['array'], + SmallIntType::class => ['int'], + StringType::class => ['string'], + TextType::class => ['string'], + ]; + + public function __construct( + private readonly EntityManagerInterface $em, + private readonly bool $validatePropertyTypes = true, + ) { + } + + /** + * Checks the internal consistency of all mapping files. + * + * There are several checks that can't be done at runtime or are too expensive, which can be verified + * with this command. For example: + * + * 1. Check if a relation with "mappedBy" is actually connected to that specified field. + * 2. Check if "mappedBy" and "inversedBy" are consistent to each other. + * 3. Check if "referencedColumnName" attributes are really pointing to primary key columns. + * + * @psalm-return array> + */ + public function validateMapping(): array + { + $errors = []; + $cmf = $this->em->getMetadataFactory(); + $classes = $cmf->getAllMetadata(); + + foreach ($classes as $class) { + $ce = $this->validateClass($class); + if ($ce) { + $errors[$class->name] = $ce; + } + } + + return $errors; + } + + /** + * Validates a single class of the current. + * + * @return string[] + * @psalm-return list + */ + public function validateClass(ClassMetadata $class): array + { + $ce = []; + $cmf = $this->em->getMetadataFactory(); + + foreach ($class->fieldMappings as $fieldName => $mapping) { + if (! Type::hasType($mapping->type)) { + $ce[] = "The field '" . $class->name . '#' . $fieldName . "' uses a non-existent type '" . $mapping->type . "'."; + } + } + + if ($this->validatePropertyTypes) { + array_push($ce, ...$this->validatePropertiesTypes($class)); + } + + foreach ($class->associationMappings as $fieldName => $assoc) { + if (! class_exists($assoc->targetEntity) || $cmf->isTransient($assoc->targetEntity)) { + $ce[] = "The target entity '" . $assoc->targetEntity . "' specified on " . $class->name . '#' . $fieldName . ' is unknown or not an entity.'; + + return $ce; + } + + $targetMetadata = $cmf->getMetadataFor($assoc->targetEntity); + + if ($targetMetadata->isMappedSuperclass) { + $ce[] = "The target entity '" . $assoc->targetEntity . "' specified on " . $class->name . '#' . $fieldName . ' is a mapped superclass. This is not possible since there is no table that a foreign key could refer to.'; + + return $ce; + } + + if (isset($assoc->id) && $targetMetadata->containsForeignIdentifier) { + $ce[] = "Cannot map association '" . $class->name . '#' . $fieldName . ' as identifier, because ' . + "the target entity '" . $targetMetadata->name . "' also maps an association as identifier."; + } + + if (! $assoc->isOwningSide()) { + if ($targetMetadata->hasField($assoc->mappedBy)) { + $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the owning side ' . + 'field ' . $assoc->targetEntity . '#' . $assoc->mappedBy . ' which is not defined as association, but as field.'; + } + + if (! $targetMetadata->hasAssociation($assoc->mappedBy)) { + $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the owning side ' . + 'field ' . $assoc->targetEntity . '#' . $assoc->mappedBy . ' which does not exist.'; + } elseif ($targetMetadata->associationMappings[$assoc->mappedBy]->inversedBy === null) { + $ce[] = 'The field ' . $class->name . '#' . $fieldName . ' is on the inverse side of a ' . + 'bi-directional relationship, but the specified mappedBy association on the target-entity ' . + $assoc->targetEntity . '#' . $assoc->mappedBy . ' does not contain the required ' . + "'inversedBy=\"" . $fieldName . "\"' attribute."; + } elseif ($targetMetadata->associationMappings[$assoc->mappedBy]->inversedBy !== $fieldName) { + $ce[] = 'The mappings ' . $class->name . '#' . $fieldName . ' and ' . + $assoc->targetEntity . '#' . $assoc->mappedBy . ' are ' . + 'inconsistent with each other.'; + } + } + + if ($assoc->isOwningSide() && $assoc->inversedBy !== null) { + if ($targetMetadata->hasField($assoc->inversedBy)) { + $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' . + 'field ' . $assoc->targetEntity . '#' . $assoc->inversedBy . ' which is not defined as association.'; + } + + if (! $targetMetadata->hasAssociation($assoc->inversedBy)) { + $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' . + 'field ' . $assoc->targetEntity . '#' . $assoc->inversedBy . ' which does not exist.'; + } elseif ($targetMetadata->associationMappings[$assoc->inversedBy]->isOwningSide()) { + $ce[] = 'The field ' . $class->name . '#' . $fieldName . ' is on the owning side of a ' . + 'bi-directional relationship, but the specified inversedBy association on the target-entity ' . + $assoc->targetEntity . '#' . $assoc->inversedBy . ' does not contain the required ' . + "'mappedBy=\"" . $fieldName . "\"' attribute."; + } elseif ($targetMetadata->associationMappings[$assoc->inversedBy]->mappedBy !== $fieldName) { + $ce[] = 'The mappings ' . $class->name . '#' . $fieldName . ' and ' . + $assoc->targetEntity . '#' . $assoc->inversedBy . ' are ' . + 'inconsistent with each other.'; + } + + // Verify inverse side/owning side match each other + if (array_key_exists($assoc->inversedBy, $targetMetadata->associationMappings)) { + $targetAssoc = $targetMetadata->associationMappings[$assoc->inversedBy]; + if ($assoc->isOneToOne() && ! $targetAssoc->isOneToOne()) { + $ce[] = 'If association ' . $class->name . '#' . $fieldName . ' is one-to-one, then the inversed ' . + 'side ' . $targetMetadata->name . '#' . $assoc->inversedBy . ' has to be one-to-one as well.'; + } elseif ($assoc->isManyToOne() && ! $targetAssoc->isOneToMany()) { + $ce[] = 'If association ' . $class->name . '#' . $fieldName . ' is many-to-one, then the inversed ' . + 'side ' . $targetMetadata->name . '#' . $assoc->inversedBy . ' has to be one-to-many.'; + } elseif ($assoc->isManyToMany() && ! $targetAssoc->isManyToMany()) { + $ce[] = 'If association ' . $class->name . '#' . $fieldName . ' is many-to-many, then the inversed ' . + 'side ' . $targetMetadata->name . '#' . $assoc->inversedBy . ' has to be many-to-many as well.'; + } + } + } + + if ($assoc->isOwningSide()) { + if ($assoc->isManyToManyOwningSide()) { + $identifierColumns = $class->getIdentifierColumnNames(); + foreach ($assoc->joinTable->joinColumns as $joinColumn) { + if (! in_array($joinColumn->referencedColumnName, $identifierColumns, true)) { + $ce[] = "The referenced column name '" . $joinColumn->referencedColumnName . "' " . + "has to be a primary key column on the target entity class '" . $class->name . "'."; + break; + } + } + + $identifierColumns = $targetMetadata->getIdentifierColumnNames(); + foreach ($assoc->joinTable->inverseJoinColumns as $inverseJoinColumn) { + if (! in_array($inverseJoinColumn->referencedColumnName, $identifierColumns, true)) { + $ce[] = "The referenced column name '" . $inverseJoinColumn->referencedColumnName . "' " . + "has to be a primary key column on the target entity class '" . $targetMetadata->name . "'."; + break; + } + } + + if (count($targetMetadata->getIdentifierColumnNames()) !== count($assoc->joinTable->inverseJoinColumns)) { + $ce[] = "The inverse join columns of the many-to-many table '" . $assoc->joinTable->name . "' " . + "have to contain to ALL identifier columns of the target entity '" . $targetMetadata->name . "', " . + "however '" . implode(', ', array_diff($targetMetadata->getIdentifierColumnNames(), array_values($assoc->relationToTargetKeyColumns))) . + "' are missing."; + } + + if (count($class->getIdentifierColumnNames()) !== count($assoc->joinTable->joinColumns)) { + $ce[] = "The join columns of the many-to-many table '" . $assoc->joinTable->name . "' " . + "have to contain to ALL identifier columns of the source entity '" . $class->name . "', " . + "however '" . implode(', ', array_diff($class->getIdentifierColumnNames(), array_values($assoc->relationToSourceKeyColumns))) . + "' are missing."; + } + } elseif ($assoc->isToOneOwningSide()) { + $identifierColumns = $targetMetadata->getIdentifierColumnNames(); + foreach ($assoc->joinColumns as $joinColumn) { + if (! in_array($joinColumn->referencedColumnName, $identifierColumns, true)) { + $ce[] = "The referenced column name '" . $joinColumn->referencedColumnName . "' " . + "has to be a primary key column on the target entity class '" . $targetMetadata->name . "'."; + } + } + + if (count($identifierColumns) !== count($assoc->joinColumns)) { + $ids = []; + + foreach ($assoc->joinColumns as $joinColumn) { + $ids[] = $joinColumn->name; + } + + $ce[] = "The join columns of the association '" . $assoc->fieldName . "' " . + "have to match to ALL identifier columns of the target entity '" . $targetMetadata->name . "', " . + "however '" . implode(', ', array_diff($targetMetadata->getIdentifierColumnNames(), $ids)) . + "' are missing."; + } + } + } + + if ($assoc->isOrdered()) { + foreach ($assoc->orderBy() as $orderField => $orientation) { + if (! $targetMetadata->hasField($orderField) && ! $targetMetadata->hasAssociation($orderField)) { + $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' is ordered by a foreign field ' . + $orderField . ' that is not a field on the target entity ' . $targetMetadata->name . '.'; + continue; + } + + if ($targetMetadata->isCollectionValuedAssociation($orderField)) { + $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' is ordered by a field ' . + $orderField . ' on ' . $targetMetadata->name . ' that is a collection-valued association.'; + continue; + } + + if ($targetMetadata->isAssociationInverseSide($orderField)) { + $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' is ordered by a field ' . + $orderField . ' on ' . $targetMetadata->name . ' that is the inverse side of an association.'; + continue; + } + } + } + } + + if ( + ! $class->isInheritanceTypeNone() + && ! $class->isRootEntity() + && ($class->reflClass !== null && ! $class->reflClass->isAbstract()) + && ! $class->isMappedSuperclass + && array_search($class->name, $class->discriminatorMap, true) === false + ) { + $ce[] = "Entity class '" . $class->name . "' is part of inheritance hierarchy, but is " . + "not mapped in the root entity '" . $class->rootEntityName . "' discriminator map. " . + 'All subclasses must be listed in the discriminator map.'; + } + + foreach ($class->subClasses as $subClass) { + if (! in_array($class->name, class_parents($subClass), true)) { + $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child " . + "of '" . $class->name . "' but these entities are not related through inheritance."; + } + } + + return $ce; + } + + /** + * Checks if the Database Schema is in sync with the current metadata state. + */ + public function schemaInSyncWithMetadata(): bool + { + return count($this->getUpdateSchemaList()) === 0; + } + + /** + * Returns the list of missing Database Schema updates. + * + * @return array + */ + public function getUpdateSchemaList(): array + { + $schemaTool = new SchemaTool($this->em); + + $allMetadata = $this->em->getMetadataFactory()->getAllMetadata(); + + return $schemaTool->getUpdateSchemaSql($allMetadata); + } + + /** @return list containing the found issues */ + private function validatePropertiesTypes(ClassMetadata $class): array + { + return array_values( + array_filter( + array_map( + function (FieldMapping $fieldMapping) use ($class): string|null { + $fieldName = $fieldMapping->fieldName; + assert(isset($class->reflFields[$fieldName])); + $propertyType = $class->reflFields[$fieldName]->getType(); + + // If the field type is not a built-in type, we cannot check it + if (! Type::hasType($fieldMapping->type)) { + return null; + } + + // If the property type is not a named type, we cannot check it + if (! ($propertyType instanceof ReflectionNamedType) || $propertyType->getName() === 'mixed') { + return null; + } + + $metadataFieldType = $this->findBuiltInType(Type::getType($fieldMapping->type)); + + //If the metadata field type is not a mapped built-in type, we cannot check it + if ($metadataFieldType === null) { + return null; + } + + $propertyType = $propertyType->getName(); + + // If the property type is the same as the metadata field type, we are ok + if (in_array($propertyType, $metadataFieldType, true)) { + return null; + } + + if (is_a($propertyType, BackedEnum::class, true)) { + $backingType = (string) (new ReflectionEnum($propertyType))->getBackingType(); + + if (! in_array($backingType, $metadataFieldType, true)) { + return sprintf( + "The field '%s#%s' has the property type '%s' with a backing type of '%s' that differs from the metadata field type '%s'.", + $class->name, + $fieldName, + $propertyType, + $backingType, + implode('|', $metadataFieldType), + ); + } + + if (! isset($fieldMapping->enumType) || $propertyType === $fieldMapping->enumType) { + return null; + } + + return sprintf( + "The field '%s#%s' has the property type '%s' that differs from the metadata enumType '%s'.", + $class->name, + $fieldName, + $propertyType, + $fieldMapping->enumType, + ); + } + + if ( + isset($fieldMapping->enumType) + && $propertyType !== $fieldMapping->enumType + && interface_exists($propertyType) + && is_a($fieldMapping->enumType, $propertyType, true) + ) { + $backingType = (string) (new ReflectionEnum($fieldMapping->enumType))->getBackingType(); + + if (in_array($backingType, $metadataFieldType, true)) { + return null; + } + + return sprintf( + "The field '%s#%s' has the metadata enumType '%s' with a backing type of '%s' that differs from the metadata field type '%s'.", + $class->name, + $fieldName, + $fieldMapping->enumType, + $backingType, + implode('|', $metadataFieldType), + ); + } + + if ( + $fieldMapping->type === 'json' + && in_array($propertyType, ['string', 'int', 'float', 'bool', 'true', 'false', 'null'], true) + ) { + return null; + } + + return sprintf( + "The field '%s#%s' has the property type '%s' that differs from the metadata field type '%s' returned by the '%s' DBAL type.", + $class->name, + $fieldName, + $propertyType, + implode('|', $metadataFieldType), + $fieldMapping->type, + ); + }, + $class->fieldMappings, + ), + ), + ); + } + + /** + * The exact DBAL type must be used (no subclasses), since consumers of doctrine/orm may have their own + * customization around field types. + * + * @return list|null + */ + private function findBuiltInType(Type $type): array|null + { + $typeName = $type::class; + + return self::BUILTIN_TYPES_MAP[$typeName] ?? null; + } +} diff --git a/vendor/doctrine/orm/src/Tools/ToolEvents.php b/vendor/doctrine/orm/src/Tools/ToolEvents.php new file mode 100644 index 0000000..fac37fa --- /dev/null +++ b/vendor/doctrine/orm/src/Tools/ToolEvents.php @@ -0,0 +1,23 @@ +getMessage() . "' while executing DDL: " . $sql, + 0, + $e, + ); + } +} diff --git a/vendor/doctrine/orm/src/TransactionRequiredException.php b/vendor/doctrine/orm/src/TransactionRequiredException.php new file mode 100644 index 0000000..6114544 --- /dev/null +++ b/vendor/doctrine/orm/src/TransactionRequiredException.php @@ -0,0 +1,21 @@ +> + */ + private array $identityMap = []; + + /** + * Map of all identifiers of managed entities. + * Keys are object ids (spl_object_id). + * + * @psalm-var array> + */ + private array $entityIdentifiers = []; + + /** + * Map of the original entity data of managed entities. + * Keys are object ids (spl_object_id). This is used for calculating changesets + * at commit time. + * + * Internal note: Note that PHPs "copy-on-write" behavior helps a lot with memory usage. + * A value will only really be copied if the value in the entity is modified + * by the user. + * + * @psalm-var array> + */ + private array $originalEntityData = []; + + /** + * Map of entity changes. Keys are object ids (spl_object_id). + * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. + * + * @psalm-var array> + */ + private array $entityChangeSets = []; + + /** + * The (cached) states of any known entities. + * Keys are object ids (spl_object_id). + * + * @psalm-var array + */ + private array $entityStates = []; + + /** + * Map of entities that are scheduled for dirty checking at commit time. + * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT. + * Keys are object ids (spl_object_id). + * + * @psalm-var array> + */ + private array $scheduledForSynchronization = []; + + /** + * A list of all pending entity insertions. + * + * @psalm-var array + */ + private array $entityInsertions = []; + + /** + * A list of all pending entity updates. + * + * @psalm-var array + */ + private array $entityUpdates = []; + + /** + * Any pending extra updates that have been scheduled by persisters. + * + * @psalm-var array}> + */ + private array $extraUpdates = []; + + /** + * A list of all pending entity deletions. + * + * @psalm-var array + */ + private array $entityDeletions = []; + + /** + * New entities that were discovered through relationships that were not + * marked as cascade-persist. During flush, this array is populated and + * then pruned of any entities that were discovered through a valid + * cascade-persist path. (Leftovers cause an error.) + * + * Keys are OIDs, payload is a two-item array describing the association + * and the entity. + * + * @var array indexed by respective object spl_object_id() + */ + private array $nonCascadedNewDetectedEntities = []; + + /** + * All pending collection deletions. + * + * @psalm-var array> + */ + private array $collectionDeletions = []; + + /** + * All pending collection updates. + * + * @psalm-var array> + */ + private array $collectionUpdates = []; + + /** + * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork. + * At the end of the UnitOfWork all these collections will make new snapshots + * of their data. + * + * @psalm-var array> + */ + private array $visitedCollections = []; + + /** + * List of collections visited during the changeset calculation that contain to-be-removed + * entities and need to have keys removed post commit. + * + * Indexed by Collection object ID, which also serves as the key in self::$visitedCollections; + * values are the key names that need to be removed. + * + * @psalm-var array> + */ + private array $pendingCollectionElementRemovals = []; + + /** + * The entity persister instances used to persist entity instances. + * + * @psalm-var array + */ + private array $persisters = []; + + /** + * The collection persister instances used to persist collections. + * + * @psalm-var array + */ + private array $collectionPersisters = []; + + /** + * The EventManager used for dispatching events. + */ + private readonly EventManager $evm; + + /** + * The ListenersInvoker used for dispatching events. + */ + private readonly ListenersInvoker $listenersInvoker; + + /** + * The IdentifierFlattener used for manipulating identifiers + */ + private readonly IdentifierFlattener $identifierFlattener; + + /** + * Orphaned entities that are scheduled for removal. + * + * @psalm-var array + */ + private array $orphanRemovals = []; + + /** + * Read-Only objects are never evaluated + * + * @var array + */ + private array $readOnlyObjects = []; + + /** + * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. + * + * @psalm-var array> + */ + private array $eagerLoadingEntities = []; + + /** @var array> */ + private array $eagerLoadingCollections = []; + + protected bool $hasCache = false; + + /** + * Helper for handling completion of hydration + */ + private readonly HydrationCompleteHandler $hydrationCompleteHandler; + + /** + * Initializes a new UnitOfWork instance, bound to the given EntityManager. + * + * @param EntityManagerInterface $em The EntityManager that "owns" this UnitOfWork instance. + */ + public function __construct( + private readonly EntityManagerInterface $em, + ) { + $this->evm = $em->getEventManager(); + $this->listenersInvoker = new ListenersInvoker($em); + $this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled(); + $this->identifierFlattener = new IdentifierFlattener($this, $em->getMetadataFactory()); + $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em); + } + + /** + * Commits the UnitOfWork, executing all operations that have been postponed + * up to this point. The state of all managed entities will be synchronized with + * the database. + * + * The operations are executed in the following order: + * + * 1) All entity insertions + * 2) All entity updates + * 3) All collection deletions + * 4) All collection updates + * 5) All entity deletions + * + * @throws Exception + */ + public function commit(): void + { + $connection = $this->em->getConnection(); + + if ($connection instanceof PrimaryReadReplicaConnection) { + $connection->ensureConnectedToPrimary(); + } + + // Raise preFlush + if ($this->evm->hasListeners(Events::preFlush)) { + $this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em)); + } + + // Compute changes done since last commit. + $this->computeChangeSets(); + + if ( + ! ($this->entityInsertions || + $this->entityDeletions || + $this->entityUpdates || + $this->collectionUpdates || + $this->collectionDeletions || + $this->orphanRemovals) + ) { + $this->dispatchOnFlushEvent(); + $this->dispatchPostFlushEvent(); + + $this->postCommitCleanup(); + + return; // Nothing to do. + } + + $this->assertThatThereAreNoUnintentionallyNonPersistedAssociations(); + + if ($this->orphanRemovals) { + foreach ($this->orphanRemovals as $orphan) { + $this->remove($orphan); + } + } + + $this->dispatchOnFlushEvent(); + + $conn = $this->em->getConnection(); + $conn->beginTransaction(); + + try { + // Collection deletions (deletions of complete collections) + foreach ($this->collectionDeletions as $collectionToDelete) { + // Deferred explicit tracked collections can be removed only when owning relation was persisted + $owner = $collectionToDelete->getOwner(); + + if ($this->em->getClassMetadata($owner::class)->isChangeTrackingDeferredImplicit() || $this->isScheduledForDirtyCheck($owner)) { + $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); + } + } + + if ($this->entityInsertions) { + // Perform entity insertions first, so that all new entities have their rows in the database + // and can be referred to by foreign keys. The commit order only needs to take new entities + // into account (new entities referring to other new entities), since all other types (entities + // with updates or scheduled deletions) are currently not a problem, since they are already + // in the database. + $this->executeInserts(); + } + + if ($this->entityUpdates) { + // Updates do not need to follow a particular order + $this->executeUpdates(); + } + + // Extra updates that were requested by persisters. + // This may include foreign keys that could not be set when an entity was inserted, + // which may happen in the case of circular foreign key relationships. + if ($this->extraUpdates) { + $this->executeExtraUpdates(); + } + + // Collection updates (deleteRows, updateRows, insertRows) + // No particular order is necessary, since all entities themselves are already + // in the database + foreach ($this->collectionUpdates as $collectionToUpdate) { + $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); + } + + // Entity deletions come last. Their order only needs to take care of other deletions + // (first delete entities depending upon others, before deleting depended-upon entities). + if ($this->entityDeletions) { + $this->executeDeletions(); + } + + $commitFailed = false; + try { + if ($conn->commit() === false) { + $commitFailed = true; + } + } catch (DBAL\Exception $e) { + $commitFailed = true; + } + + if ($commitFailed) { + throw new OptimisticLockException('Commit failed', null, $e ?? null); + } + } catch (Throwable $e) { + $this->em->close(); + + if ($conn->isTransactionActive()) { + $conn->rollBack(); + } + + $this->afterTransactionRolledBack(); + + throw $e; + } + + $this->afterTransactionComplete(); + + // Unset removed entities from collections, and take new snapshots from + // all visited collections. + foreach ($this->visitedCollections as $coid => $coll) { + if (isset($this->pendingCollectionElementRemovals[$coid])) { + foreach ($this->pendingCollectionElementRemovals[$coid] as $key => $valueIgnored) { + unset($coll[$key]); + } + } + + $coll->takeSnapshot(); + } + + $this->dispatchPostFlushEvent(); + + $this->postCommitCleanup(); + } + + private function postCommitCleanup(): void + { + $this->entityInsertions = + $this->entityUpdates = + $this->entityDeletions = + $this->extraUpdates = + $this->collectionUpdates = + $this->nonCascadedNewDetectedEntities = + $this->collectionDeletions = + $this->pendingCollectionElementRemovals = + $this->visitedCollections = + $this->orphanRemovals = + $this->entityChangeSets = + $this->scheduledForSynchronization = []; + } + + /** + * Computes the changesets of all entities scheduled for insertion. + */ + private function computeScheduleInsertsChangeSets(): void + { + foreach ($this->entityInsertions as $entity) { + $class = $this->em->getClassMetadata($entity::class); + + $this->computeChangeSet($class, $entity); + } + } + + /** + * Executes any extra updates that have been scheduled. + */ + private function executeExtraUpdates(): void + { + foreach ($this->extraUpdates as $oid => $update) { + [$entity, $changeset] = $update; + + $this->entityChangeSets[$oid] = $changeset; + $this->getEntityPersister($entity::class)->update($entity); + } + + $this->extraUpdates = []; + } + + /** + * Gets the changeset for an entity. + * + * @return mixed[][] + * @psalm-return array + */ + public function & getEntityChangeSet(object $entity): array + { + $oid = spl_object_id($entity); + $data = []; + + if (! isset($this->entityChangeSets[$oid])) { + return $data; + } + + return $this->entityChangeSets[$oid]; + } + + /** + * Computes the changes that happened to a single entity. + * + * Modifies/populates the following properties: + * + * {@link _originalEntityData} + * If the entity is NEW or MANAGED but not yet fully persisted (only has an id) + * then it was not fetched from the database and therefore we have no original + * entity data yet. All of the current entity data is stored as the original entity data. + * + * {@link _entityChangeSets} + * The changes detected on all properties of the entity are stored there. + * A change is a tuple array where the first entry is the old value and the second + * entry is the new value of the property. Changesets are used by persisters + * to INSERT/UPDATE the persistent entity state. + * + * {@link _entityUpdates} + * If the entity is already fully MANAGED (has been fetched from the database before) + * and any changes to its properties are detected, then a reference to the entity is stored + * there to mark it for an update. + * + * {@link _collectionDeletions} + * If a PersistentCollection has been de-referenced in a fully MANAGED entity, + * then this collection is marked for deletion. + * + * @param ClassMetadata $class The class descriptor of the entity. + * @param object $entity The entity for which to compute the changes. + * @psalm-param ClassMetadata $class + * @psalm-param T $entity + * + * @template T of object + * + * @ignore + */ + public function computeChangeSet(ClassMetadata $class, object $entity): void + { + $oid = spl_object_id($entity); + + if (isset($this->readOnlyObjects[$oid])) { + return; + } + + if (! $class->isInheritanceTypeNone()) { + $class = $this->em->getClassMetadata($entity::class); + } + + $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER; + + if ($invoke !== ListenersInvoker::INVOKE_NONE) { + $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke); + } + + $actualData = []; + + foreach ($class->reflFields as $name => $refProp) { + $value = $refProp->getValue($entity); + + if ($class->isCollectionValuedAssociation($name) && $value !== null) { + if ($value instanceof PersistentCollection) { + if ($value->getOwner() === $entity) { + $actualData[$name] = $value; + continue; + } + + $value = new ArrayCollection($value->getValues()); + } + + // If $value is not a Collection then use an ArrayCollection. + if (! $value instanceof Collection) { + $value = new ArrayCollection($value); + } + + $assoc = $class->associationMappings[$name]; + assert($assoc->isToMany()); + + // Inject PersistentCollection + $value = new PersistentCollection( + $this->em, + $this->em->getClassMetadata($assoc->targetEntity), + $value, + ); + $value->setOwner($entity, $assoc); + $value->setDirty(! $value->isEmpty()); + + $refProp->setValue($entity, $value); + + $actualData[$name] = $value; + + continue; + } + + if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) { + $actualData[$name] = $value; + } + } + + if (! isset($this->originalEntityData[$oid])) { + // Entity is either NEW or MANAGED but not yet fully persisted (only has an id). + // These result in an INSERT. + $this->originalEntityData[$oid] = $actualData; + $changeSet = []; + + foreach ($actualData as $propName => $actualValue) { + if (! isset($class->associationMappings[$propName])) { + $changeSet[$propName] = [null, $actualValue]; + + continue; + } + + $assoc = $class->associationMappings[$propName]; + + if ($assoc->isToOneOwningSide()) { + $changeSet[$propName] = [null, $actualValue]; + } + } + + $this->entityChangeSets[$oid] = $changeSet; + } else { + // Entity is "fully" MANAGED: it was already fully persisted before + // and we have a copy of the original data + $originalData = $this->originalEntityData[$oid]; + $changeSet = []; + + foreach ($actualData as $propName => $actualValue) { + // skip field, its a partially omitted one! + if (! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { + continue; + } + + $orgValue = $originalData[$propName]; + + if (! empty($class->fieldMappings[$propName]->enumType)) { + if (is_array($orgValue)) { + foreach ($orgValue as $id => $val) { + if ($val instanceof BackedEnum) { + $orgValue[$id] = $val->value; + } + } + } else { + if ($orgValue instanceof BackedEnum) { + $orgValue = $orgValue->value; + } + } + } + + // skip if value haven't changed + if ($orgValue === $actualValue) { + continue; + } + + // if regular field + if (! isset($class->associationMappings[$propName])) { + $changeSet[$propName] = [$orgValue, $actualValue]; + + continue; + } + + $assoc = $class->associationMappings[$propName]; + + // Persistent collection was exchanged with the "originally" + // created one. This can only mean it was cloned and replaced + // on another entity. + if ($actualValue instanceof PersistentCollection) { + assert($assoc->isToMany()); + $owner = $actualValue->getOwner(); + if ($owner === null) { // cloned + $actualValue->setOwner($entity, $assoc); + } elseif ($owner !== $entity) { // no clone, we have to fix + if (! $actualValue->isInitialized()) { + $actualValue->initialize(); // we have to do this otherwise the cols share state + } + + $newValue = clone $actualValue; + $newValue->setOwner($entity, $assoc); + $class->reflFields[$propName]->setValue($entity, $newValue); + } + } + + if ($orgValue instanceof PersistentCollection) { + // A PersistentCollection was de-referenced, so delete it. + $coid = spl_object_id($orgValue); + + if (isset($this->collectionDeletions[$coid])) { + continue; + } + + $this->collectionDeletions[$coid] = $orgValue; + $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. + + continue; + } + + if ($assoc->isToOne()) { + if ($assoc->isOwningSide()) { + $changeSet[$propName] = [$orgValue, $actualValue]; + } + + if ($orgValue !== null && $assoc->orphanRemoval) { + assert(is_object($orgValue)); + $this->scheduleOrphanRemoval($orgValue); + } + } + } + + if ($changeSet) { + $this->entityChangeSets[$oid] = $changeSet; + $this->originalEntityData[$oid] = $actualData; + $this->entityUpdates[$oid] = $entity; + } + } + + // Look for changes in associations of the entity + foreach ($class->associationMappings as $field => $assoc) { + $val = $class->reflFields[$field]->getValue($entity); + if ($val === null) { + continue; + } + + $this->computeAssociationChanges($assoc, $val); + + if ( + ! isset($this->entityChangeSets[$oid]) && + $assoc->isManyToManyOwningSide() && + $val instanceof PersistentCollection && + $val->isDirty() + ) { + $this->entityChangeSets[$oid] = []; + $this->originalEntityData[$oid] = $actualData; + $this->entityUpdates[$oid] = $entity; + } + } + } + + /** + * Computes all the changes that have been done to entities and collections + * since the last commit and stores these changes in the _entityChangeSet map + * temporarily for access by the persisters, until the UoW commit is finished. + */ + public function computeChangeSets(): void + { + // Compute changes for INSERTed entities first. This must always happen. + $this->computeScheduleInsertsChangeSets(); + + // Compute changes for other MANAGED entities. Change tracking policies take effect here. + foreach ($this->identityMap as $className => $entities) { + $class = $this->em->getClassMetadata($className); + + // Skip class if instances are read-only + if ($class->isReadOnly) { + continue; + } + + $entitiesToProcess = match (true) { + $class->isChangeTrackingDeferredImplicit() => $entities, + isset($this->scheduledForSynchronization[$className]) => $this->scheduledForSynchronization[$className], + default => [], + }; + + foreach ($entitiesToProcess as $entity) { + // Ignore uninitialized proxy objects + if ($this->isUninitializedObject($entity)) { + continue; + } + + // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here. + $oid = spl_object_id($entity); + + if (! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) { + $this->computeChangeSet($class, $entity); + } + } + } + } + + /** + * Computes the changes of an association. + * + * @param mixed $value The value of the association. + * + * @throws ORMInvalidArgumentException + * @throws ORMException + */ + private function computeAssociationChanges(AssociationMapping $assoc, mixed $value): void + { + if ($this->isUninitializedObject($value)) { + return; + } + + // If this collection is dirty, schedule it for updates + if ($value instanceof PersistentCollection && $value->isDirty()) { + $coid = spl_object_id($value); + + $this->collectionUpdates[$coid] = $value; + $this->visitedCollections[$coid] = $value; + } + + // Look through the entities, and in any of their associations, + // for transient (new) entities, recursively. ("Persistence by reachability") + // Unwrap. Uninitialized collections will simply be empty. + $unwrappedValue = $assoc->isToOne() ? [$value] : $value->unwrap(); + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + + foreach ($unwrappedValue as $key => $entry) { + if (! ($entry instanceof $targetClass->name)) { + throw ORMInvalidArgumentException::invalidAssociation($targetClass, $assoc, $entry); + } + + $state = $this->getEntityState($entry, self::STATE_NEW); + + if (! ($entry instanceof $assoc->targetEntity)) { + throw UnexpectedAssociationValue::create( + $assoc->sourceEntity, + $assoc->fieldName, + get_debug_type($entry), + $assoc->targetEntity, + ); + } + + switch ($state) { + case self::STATE_NEW: + if (! $assoc->isCascadePersist()) { + /* + * For now just record the details, because this may + * not be an issue if we later discover another pathway + * through the object-graph where cascade-persistence + * is enabled for this object. + */ + $this->nonCascadedNewDetectedEntities[spl_object_id($entry)] = [$assoc, $entry]; + + break; + } + + $this->persistNew($targetClass, $entry); + $this->computeChangeSet($targetClass, $entry); + + break; + + case self::STATE_REMOVED: + // Consume the $value as array (it's either an array or an ArrayAccess) + // and remove the element from Collection. + if (! $assoc->isToMany()) { + break; + } + + $coid = spl_object_id($value); + $this->visitedCollections[$coid] = $value; + + if (! isset($this->pendingCollectionElementRemovals[$coid])) { + $this->pendingCollectionElementRemovals[$coid] = []; + } + + $this->pendingCollectionElementRemovals[$coid][$key] = true; + break; + + case self::STATE_DETACHED: + // Can actually not happen right now as we assume STATE_NEW, + // so the exception will be raised from the DBAL layer (constraint violation). + throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($assoc, $entry); + + default: + // MANAGED associated entities are already taken into account + // during changeset calculation anyway, since they are in the identity map. + } + } + } + + /** + * @psalm-param ClassMetadata $class + * @psalm-param T $entity + * + * @template T of object + */ + private function persistNew(ClassMetadata $class, object $entity): void + { + $oid = spl_object_id($entity); + $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist); + + if ($invoke !== ListenersInvoker::INVOKE_NONE) { + $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new PrePersistEventArgs($entity, $this->em), $invoke); + } + + $idGen = $class->idGenerator; + + if (! $idGen->isPostInsertGenerator()) { + $idValue = $idGen->generateId($this->em, $entity); + + if (! $idGen instanceof AssignedGenerator) { + $idValue = [$class->getSingleIdentifierFieldName() => $this->convertSingleFieldIdentifierToPHPValue($class, $idValue)]; + + $class->setIdentifierValues($entity, $idValue); + } + + // Some identifiers may be foreign keys to new entities. + // In this case, we don't have the value yet and should treat it as if we have a post-insert generator + if (! $this->hasMissingIdsWhichAreForeignKeys($class, $idValue)) { + $this->entityIdentifiers[$oid] = $idValue; + } + } + + $this->entityStates[$oid] = self::STATE_MANAGED; + + $this->scheduleForInsert($entity); + } + + /** @param mixed[] $idValue */ + private function hasMissingIdsWhichAreForeignKeys(ClassMetadata $class, array $idValue): bool + { + foreach ($idValue as $idField => $idFieldValue) { + if ($idFieldValue === null && isset($class->associationMappings[$idField])) { + return true; + } + } + + return false; + } + + /** + * INTERNAL: + * Computes the changeset of an individual entity, independently of the + * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). + * + * The passed entity must be a managed entity. If the entity already has a change set + * because this method is invoked during a commit cycle then the change sets are added. + * whereby changes detected in this method prevail. + * + * @param ClassMetadata $class The class descriptor of the entity. + * @param object $entity The entity for which to (re)calculate the change set. + * @psalm-param ClassMetadata $class + * @psalm-param T $entity + * + * @throws ORMInvalidArgumentException If the passed entity is not MANAGED. + * + * @template T of object + * @ignore + */ + public function recomputeSingleEntityChangeSet(ClassMetadata $class, object $entity): void + { + $oid = spl_object_id($entity); + + if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) { + throw ORMInvalidArgumentException::entityNotManaged($entity); + } + + if (! $class->isInheritanceTypeNone()) { + $class = $this->em->getClassMetadata($entity::class); + } + + $actualData = []; + + foreach ($class->reflFields as $name => $refProp) { + if ( + ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) + && ($name !== $class->versionField) + && ! $class->isCollectionValuedAssociation($name) + ) { + $actualData[$name] = $refProp->getValue($entity); + } + } + + if (! isset($this->originalEntityData[$oid])) { + throw new RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.'); + } + + $originalData = $this->originalEntityData[$oid]; + $changeSet = []; + + foreach ($actualData as $propName => $actualValue) { + $orgValue = $originalData[$propName] ?? null; + + if (isset($class->fieldMappings[$propName]->enumType)) { + if (is_array($orgValue)) { + foreach ($orgValue as $id => $val) { + if ($val instanceof BackedEnum) { + $orgValue[$id] = $val->value; + } + } + } else { + if ($orgValue instanceof BackedEnum) { + $orgValue = $orgValue->value; + } + } + } + + if ($orgValue !== $actualValue) { + $changeSet[$propName] = [$orgValue, $actualValue]; + } + } + + if ($changeSet) { + if (isset($this->entityChangeSets[$oid])) { + $this->entityChangeSets[$oid] = [...$this->entityChangeSets[$oid], ...$changeSet]; + } elseif (! isset($this->entityInsertions[$oid])) { + $this->entityChangeSets[$oid] = $changeSet; + $this->entityUpdates[$oid] = $entity; + } + + $this->originalEntityData[$oid] = $actualData; + } + } + + /** + * Executes entity insertions + */ + private function executeInserts(): void + { + $entities = $this->computeInsertExecutionOrder(); + $eventsToDispatch = []; + + foreach ($entities as $entity) { + $oid = spl_object_id($entity); + $class = $this->em->getClassMetadata($entity::class); + $persister = $this->getEntityPersister($class->name); + + $persister->addInsert($entity); + + unset($this->entityInsertions[$oid]); + + $persister->executeInserts(); + + if (! isset($this->entityIdentifiers[$oid])) { + //entity was not added to identity map because some identifiers are foreign keys to new entities. + //add it now + $this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity); + } + + $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist); + + if ($invoke !== ListenersInvoker::INVOKE_NONE) { + $eventsToDispatch[] = ['class' => $class, 'entity' => $entity, 'invoke' => $invoke]; + } + } + + // Defer dispatching `postPersist` events to until all entities have been inserted and post-insert + // IDs have been assigned. + foreach ($eventsToDispatch as $event) { + $this->listenersInvoker->invoke( + $event['class'], + Events::postPersist, + $event['entity'], + new PostPersistEventArgs($event['entity'], $this->em), + $event['invoke'], + ); + } + } + + /** + * @psalm-param ClassMetadata $class + * @psalm-param T $entity + * + * @template T of object + */ + private function addToEntityIdentifiersAndEntityMap( + ClassMetadata $class, + int $oid, + object $entity, + ): void { + $identifier = []; + + foreach ($class->getIdentifierFieldNames() as $idField) { + $origValue = $class->getFieldValue($entity, $idField); + + $value = null; + if (isset($class->associationMappings[$idField])) { + // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. + $value = $this->getSingleIdentifierValue($origValue); + } + + $identifier[$idField] = $value ?? $origValue; + $this->originalEntityData[$oid][$idField] = $origValue; + } + + $this->entityStates[$oid] = self::STATE_MANAGED; + $this->entityIdentifiers[$oid] = $identifier; + + $this->addToIdentityMap($entity); + } + + /** + * Executes all entity updates + */ + private function executeUpdates(): void + { + foreach ($this->entityUpdates as $oid => $entity) { + $class = $this->em->getClassMetadata($entity::class); + $persister = $this->getEntityPersister($class->name); + $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate); + $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate); + + if ($preUpdateInvoke !== ListenersInvoker::INVOKE_NONE) { + $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke); + + $this->recomputeSingleEntityChangeSet($class, $entity); + } + + if (! empty($this->entityChangeSets[$oid])) { + $persister->update($entity); + } + + unset($this->entityUpdates[$oid]); + + if ($postUpdateInvoke !== ListenersInvoker::INVOKE_NONE) { + $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new PostUpdateEventArgs($entity, $this->em), $postUpdateInvoke); + } + } + } + + /** + * Executes all entity deletions + */ + private function executeDeletions(): void + { + $entities = $this->computeDeleteExecutionOrder(); + $eventsToDispatch = []; + + foreach ($entities as $entity) { + $this->removeFromIdentityMap($entity); + + $oid = spl_object_id($entity); + $class = $this->em->getClassMetadata($entity::class); + $persister = $this->getEntityPersister($class->name); + $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove); + + $persister->delete($entity); + + unset( + $this->entityDeletions[$oid], + $this->entityIdentifiers[$oid], + $this->originalEntityData[$oid], + $this->entityStates[$oid], + ); + + // Entity with this $oid after deletion treated as NEW, even if the $oid + // is obtained by a new entity because the old one went out of scope. + //$this->entityStates[$oid] = self::STATE_NEW; + if (! $class->isIdentifierNatural()) { + $class->reflFields[$class->identifier[0]]->setValue($entity, null); + } + + if ($invoke !== ListenersInvoker::INVOKE_NONE) { + $eventsToDispatch[] = ['class' => $class, 'entity' => $entity, 'invoke' => $invoke]; + } + } + + // Defer dispatching `postRemove` events to until all entities have been removed. + foreach ($eventsToDispatch as $event) { + $this->listenersInvoker->invoke( + $event['class'], + Events::postRemove, + $event['entity'], + new PostRemoveEventArgs($event['entity'], $this->em), + $event['invoke'], + ); + } + } + + /** @return list */ + private function computeInsertExecutionOrder(): array + { + $sort = new TopologicalSort(); + + // First make sure we have all the nodes + foreach ($this->entityInsertions as $entity) { + $sort->addNode($entity); + } + + // Now add edges + foreach ($this->entityInsertions as $entity) { + $class = $this->em->getClassMetadata($entity::class); + + foreach ($class->associationMappings as $assoc) { + // We only need to consider the owning sides of to-one associations, + // since many-to-many associations are persisted at a later step and + // have no insertion order problems (all entities already in the database + // at that time). + if (! $assoc->isToOneOwningSide()) { + continue; + } + + $targetEntity = $class->getFieldValue($entity, $assoc->fieldName); + + // If there is no entity that we need to refer to, or it is already in the + // database (i. e. does not have to be inserted), no need to consider it. + if ($targetEntity === null || ! $sort->hasNode($targetEntity)) { + continue; + } + + // An entity that references back to itself _and_ uses an application-provided ID + // (the "NONE" generator strategy) can be exempted from commit order computation. + // See https://github.com/doctrine/orm/pull/10735/ for more details on this edge case. + // A non-NULLable self-reference would be a cycle in the graph. + if ($targetEntity === $entity && $class->isIdentifierNatural()) { + continue; + } + + // According to https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/annotations-reference.html#annref_joincolumn, + // the default for "nullable" is true. Unfortunately, it seems this default is not applied at the metadata driver, factory or other + // level, but in fact we may have an undefined 'nullable' key here, so we must assume that default here as well. + // + // Same in \Doctrine\ORM\Tools\EntityGenerator::isAssociationIsNullable or \Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getJoinSQLForJoinColumns, + // to give two examples. + $joinColumns = reset($assoc->joinColumns); + $isNullable = ! isset($joinColumns->nullable) || $joinColumns->nullable; + + // Add dependency. The dependency direction implies that "$entity depends on $targetEntity". The + // topological sort result will output the depended-upon nodes first, which means we can insert + // entities in that order. + $sort->addEdge($entity, $targetEntity, $isNullable); + } + } + + return $sort->sort(); + } + + /** @return list */ + private function computeDeleteExecutionOrder(): array + { + $stronglyConnectedComponents = new StronglyConnectedComponents(); + $sort = new TopologicalSort(); + + foreach ($this->entityDeletions as $entity) { + $stronglyConnectedComponents->addNode($entity); + $sort->addNode($entity); + } + + // First, consider only "on delete cascade" associations between entities + // and find strongly connected groups. Once we delete any one of the entities + // in such a group, _all_ of the other entities will be removed as well. So, + // we need to treat those groups like a single entity when performing delete + // order topological sorting. + foreach ($this->entityDeletions as $entity) { + $class = $this->em->getClassMetadata($entity::class); + + foreach ($class->associationMappings as $assoc) { + // We only need to consider the owning sides of to-one associations, + // since many-to-many associations can always be (and have already been) + // deleted in a preceding step. + if (! $assoc->isToOneOwningSide()) { + continue; + } + + $joinColumns = reset($assoc->joinColumns); + if (! isset($joinColumns->onDelete)) { + continue; + } + + $onDeleteOption = strtolower($joinColumns->onDelete); + if ($onDeleteOption !== 'cascade') { + continue; + } + + $targetEntity = $class->getFieldValue($entity, $assoc->fieldName); + + // If the association does not refer to another entity or that entity + // is not to be deleted, there is no ordering problem and we can + // skip this particular association. + if ($targetEntity === null || ! $stronglyConnectedComponents->hasNode($targetEntity)) { + continue; + } + + $stronglyConnectedComponents->addEdge($entity, $targetEntity); + } + } + + $stronglyConnectedComponents->findStronglyConnectedComponents(); + + // Now do the actual topological sorting to find the delete order. + foreach ($this->entityDeletions as $entity) { + $class = $this->em->getClassMetadata($entity::class); + + // Get the entities representing the SCC + $entityComponent = $stronglyConnectedComponents->getNodeRepresentingStronglyConnectedComponent($entity); + + // When $entity is part of a non-trivial strongly connected component group + // (a group containing not only those entities alone), make sure we process it _after_ the + // entity representing the group. + // The dependency direction implies that "$entity depends on $entityComponent + // being deleted first". The topological sort will output the depended-upon nodes first. + if ($entityComponent !== $entity) { + $sort->addEdge($entity, $entityComponent, false); + } + + foreach ($class->associationMappings as $assoc) { + // We only need to consider the owning sides of to-one associations, + // since many-to-many associations can always be (and have already been) + // deleted in a preceding step. + if (! $assoc->isToOneOwningSide()) { + continue; + } + + // For associations that implement a database-level set null operation, + // we do not have to follow a particular order: If the referred-to entity is + // deleted first, the DBMS will temporarily set the foreign key to NULL (SET NULL). + // So, we can skip it in the computation. + $joinColumns = reset($assoc->joinColumns); + if (isset($joinColumns->onDelete)) { + $onDeleteOption = strtolower($joinColumns->onDelete); + if ($onDeleteOption === 'set null') { + continue; + } + } + + $targetEntity = $class->getFieldValue($entity, $assoc->fieldName); + + // If the association does not refer to another entity or that entity + // is not to be deleted, there is no ordering problem and we can + // skip this particular association. + if ($targetEntity === null || ! $sort->hasNode($targetEntity)) { + continue; + } + + // Get the entities representing the SCC + $targetEntityComponent = $stronglyConnectedComponents->getNodeRepresentingStronglyConnectedComponent($targetEntity); + + // When we have a dependency between two different groups of strongly connected nodes, + // add it to the computation. + // The dependency direction implies that "$targetEntityComponent depends on $entityComponent + // being deleted first". The topological sort will output the depended-upon nodes first, + // so we can work through the result in the returned order. + if ($targetEntityComponent !== $entityComponent) { + $sort->addEdge($targetEntityComponent, $entityComponent, false); + } + } + } + + return $sort->sort(); + } + + /** + * Schedules an entity for insertion into the database. + * If the entity already has an identifier, it will be added to the identity map. + * + * @throws ORMInvalidArgumentException + * @throws InvalidArgumentException + */ + public function scheduleForInsert(object $entity): void + { + $oid = spl_object_id($entity); + + if (isset($this->entityUpdates[$oid])) { + throw new InvalidArgumentException('Dirty entity can not be scheduled for insertion.'); + } + + if (isset($this->entityDeletions[$oid])) { + throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity); + } + + if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) { + throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity); + } + + if (isset($this->entityInsertions[$oid])) { + throw ORMInvalidArgumentException::scheduleInsertTwice($entity); + } + + $this->entityInsertions[$oid] = $entity; + + if (isset($this->entityIdentifiers[$oid])) { + $this->addToIdentityMap($entity); + } + } + + /** + * Checks whether an entity is scheduled for insertion. + */ + public function isScheduledForInsert(object $entity): bool + { + return isset($this->entityInsertions[spl_object_id($entity)]); + } + + /** + * Schedules an entity for being updated. + * + * @throws ORMInvalidArgumentException + */ + public function scheduleForUpdate(object $entity): void + { + $oid = spl_object_id($entity); + + if (! isset($this->entityIdentifiers[$oid])) { + throw ORMInvalidArgumentException::entityHasNoIdentity($entity, 'scheduling for update'); + } + + if (isset($this->entityDeletions[$oid])) { + throw ORMInvalidArgumentException::entityIsRemoved($entity, 'schedule for update'); + } + + if (! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) { + $this->entityUpdates[$oid] = $entity; + } + } + + /** + * INTERNAL: + * Schedules an extra update that will be executed immediately after the + * regular entity updates within the currently running commit cycle. + * + * Extra updates for entities are stored as (entity, changeset) tuples. + * + * @psalm-param array $changeset The changeset of the entity (what to update). + * + * @ignore + */ + public function scheduleExtraUpdate(object $entity, array $changeset): void + { + $oid = spl_object_id($entity); + $extraUpdate = [$entity, $changeset]; + + if (isset($this->extraUpdates[$oid])) { + [, $changeset2] = $this->extraUpdates[$oid]; + + $extraUpdate = [$entity, $changeset + $changeset2]; + } + + $this->extraUpdates[$oid] = $extraUpdate; + } + + /** + * Checks whether an entity is registered as dirty in the unit of work. + * Note: Is not very useful currently as dirty entities are only registered + * at commit time. + */ + public function isScheduledForUpdate(object $entity): bool + { + return isset($this->entityUpdates[spl_object_id($entity)]); + } + + /** + * Checks whether an entity is registered to be checked in the unit of work. + */ + public function isScheduledForDirtyCheck(object $entity): bool + { + $rootEntityName = $this->em->getClassMetadata($entity::class)->rootEntityName; + + return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_id($entity)]); + } + + /** + * INTERNAL: + * Schedules an entity for deletion. + */ + public function scheduleForDelete(object $entity): void + { + $oid = spl_object_id($entity); + + if (isset($this->entityInsertions[$oid])) { + if ($this->isInIdentityMap($entity)) { + $this->removeFromIdentityMap($entity); + } + + unset($this->entityInsertions[$oid], $this->entityStates[$oid]); + + return; // entity has not been persisted yet, so nothing more to do. + } + + if (! $this->isInIdentityMap($entity)) { + return; + } + + unset($this->entityUpdates[$oid]); + + if (! isset($this->entityDeletions[$oid])) { + $this->entityDeletions[$oid] = $entity; + $this->entityStates[$oid] = self::STATE_REMOVED; + } + } + + /** + * Checks whether an entity is registered as removed/deleted with the unit + * of work. + */ + public function isScheduledForDelete(object $entity): bool + { + return isset($this->entityDeletions[spl_object_id($entity)]); + } + + /** + * Checks whether an entity is scheduled for insertion, update or deletion. + */ + public function isEntityScheduled(object $entity): bool + { + $oid = spl_object_id($entity); + + return isset($this->entityInsertions[$oid]) + || isset($this->entityUpdates[$oid]) + || isset($this->entityDeletions[$oid]); + } + + /** + * INTERNAL: + * Registers an entity in the identity map. + * Note that entities in a hierarchy are registered with the class name of + * the root entity. + * + * @return bool TRUE if the registration was successful, FALSE if the identity of + * the entity in question is already managed. + * + * @throws ORMInvalidArgumentException + * @throws EntityIdentityCollisionException + * + * @ignore + */ + public function addToIdentityMap(object $entity): bool + { + $classMetadata = $this->em->getClassMetadata($entity::class); + $idHash = $this->getIdHashByEntity($entity); + $className = $classMetadata->rootEntityName; + + if (isset($this->identityMap[$className][$idHash])) { + if ($this->identityMap[$className][$idHash] !== $entity) { + throw EntityIdentityCollisionException::create($this->identityMap[$className][$idHash], $entity, $idHash); + } + + return false; + } + + $this->identityMap[$className][$idHash] = $entity; + + return true; + } + + /** + * Gets the id hash of an entity by its identifier. + * + * @param array $identifier The identifier of an entity + * + * @return string The entity id hash. + */ + final public static function getIdHashByIdentifier(array $identifier): string + { + foreach ($identifier as $k => $value) { + if ($value instanceof BackedEnum) { + $identifier[$k] = $value->value; + } + } + + return implode( + ' ', + $identifier, + ); + } + + /** + * Gets the id hash of an entity. + * + * @param object $entity The entity managed by Unit Of Work + * + * @return string The entity id hash. + */ + public function getIdHashByEntity(object $entity): string + { + $identifier = $this->entityIdentifiers[spl_object_id($entity)]; + + if (empty($identifier) || in_array(null, $identifier, true)) { + $classMetadata = $this->em->getClassMetadata($entity::class); + + throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity); + } + + return self::getIdHashByIdentifier($identifier); + } + + /** + * Gets the state of an entity with regard to the current unit of work. + * + * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED). + * This parameter can be set to improve performance of entity state detection + * by potentially avoiding a database lookup if the distinction between NEW and DETACHED + * is either known or does not matter for the caller of the method. + * @psalm-param self::STATE_*|null $assume + * + * @psalm-return self::STATE_* + */ + public function getEntityState(object $entity, int|null $assume = null): int + { + $oid = spl_object_id($entity); + + if (isset($this->entityStates[$oid])) { + return $this->entityStates[$oid]; + } + + if ($assume !== null) { + return $assume; + } + + // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. + // Note that you can not remember the NEW or DETACHED state in _entityStates since + // the UoW does not hold references to such objects and the object hash can be reused. + // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. + $class = $this->em->getClassMetadata($entity::class); + $id = $class->getIdentifierValues($entity); + + if (! $id) { + return self::STATE_NEW; + } + + if ($class->containsForeignIdentifier || $class->containsEnumIdentifier) { + $id = $this->identifierFlattener->flattenIdentifier($class, $id); + } + + switch (true) { + case $class->isIdentifierNatural(): + // Check for a version field, if available, to avoid a db lookup. + if ($class->isVersioned) { + assert($class->versionField !== null); + + return $class->getFieldValue($entity, $class->versionField) + ? self::STATE_DETACHED + : self::STATE_NEW; + } + + // Last try before db lookup: check the identity map. + if ($this->tryGetById($id, $class->rootEntityName)) { + return self::STATE_DETACHED; + } + + // db lookup + if ($this->getEntityPersister($class->name)->exists($entity)) { + return self::STATE_DETACHED; + } + + return self::STATE_NEW; + + case ! $class->idGenerator->isPostInsertGenerator(): + // if we have a pre insert generator we can't be sure that having an id + // really means that the entity exists. We have to verify this through + // the last resort: a db lookup + + // Last try before db lookup: check the identity map. + if ($this->tryGetById($id, $class->rootEntityName)) { + return self::STATE_DETACHED; + } + + // db lookup + if ($this->getEntityPersister($class->name)->exists($entity)) { + return self::STATE_DETACHED; + } + + return self::STATE_NEW; + + default: + return self::STATE_DETACHED; + } + } + + /** + * INTERNAL: + * Removes an entity from the identity map. This effectively detaches the + * entity from the persistence management of Doctrine. + * + * @throws ORMInvalidArgumentException + * + * @ignore + */ + public function removeFromIdentityMap(object $entity): bool + { + $oid = spl_object_id($entity); + $classMetadata = $this->em->getClassMetadata($entity::class); + $idHash = self::getIdHashByIdentifier($this->entityIdentifiers[$oid]); + + if ($idHash === '') { + throw ORMInvalidArgumentException::entityHasNoIdentity($entity, 'remove from identity map'); + } + + $className = $classMetadata->rootEntityName; + + if (isset($this->identityMap[$className][$idHash])) { + unset($this->identityMap[$className][$idHash], $this->readOnlyObjects[$oid]); + + //$this->entityStates[$oid] = self::STATE_DETACHED; + + return true; + } + + return false; + } + + /** + * INTERNAL: + * Gets an entity in the identity map by its identifier hash. + * + * @ignore + */ + public function getByIdHash(string $idHash, string $rootClassName): object|null + { + return $this->identityMap[$rootClassName][$idHash]; + } + + /** + * INTERNAL: + * Tries to get an entity by its identifier hash. If no entity is found for + * the given hash, FALSE is returned. + * + * @param mixed $idHash (must be possible to cast it to string) + * + * @return false|object The found entity or FALSE. + * + * @ignore + */ + public function tryGetByIdHash(mixed $idHash, string $rootClassName): object|false + { + $stringIdHash = (string) $idHash; + + return $this->identityMap[$rootClassName][$stringIdHash] ?? false; + } + + /** + * Checks whether an entity is registered in the identity map of this UnitOfWork. + */ + public function isInIdentityMap(object $entity): bool + { + $oid = spl_object_id($entity); + + if (empty($this->entityIdentifiers[$oid])) { + return false; + } + + $classMetadata = $this->em->getClassMetadata($entity::class); + $idHash = self::getIdHashByIdentifier($this->entityIdentifiers[$oid]); + + return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); + } + + /** + * Persists an entity as part of the current unit of work. + */ + public function persist(object $entity): void + { + $visited = []; + + $this->doPersist($entity, $visited); + } + + /** + * Persists an entity as part of the current unit of work. + * + * This method is internally called during persist() cascades as it tracks + * the already visited entities to prevent infinite recursions. + * + * @psalm-param array $visited The already visited entities. + * + * @throws ORMInvalidArgumentException + * @throws UnexpectedValueException + */ + private function doPersist(object $entity, array &$visited): void + { + $oid = spl_object_id($entity); + + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $entity; // Mark visited + + $class = $this->em->getClassMetadata($entity::class); + + // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation). + // If we would detect DETACHED here we would throw an exception anyway with the same + // consequences (not recoverable/programming error), so just assuming NEW here + // lets us avoid some database lookups for entities with natural identifiers. + $entityState = $this->getEntityState($entity, self::STATE_NEW); + + switch ($entityState) { + case self::STATE_MANAGED: + // Nothing to do, except if policy is "deferred explicit" + if ($class->isChangeTrackingDeferredExplicit()) { + $this->scheduleForDirtyCheck($entity); + } + + break; + + case self::STATE_NEW: + $this->persistNew($class, $entity); + break; + + case self::STATE_REMOVED: + // Entity becomes managed again + unset($this->entityDeletions[$oid]); + $this->addToIdentityMap($entity); + + $this->entityStates[$oid] = self::STATE_MANAGED; + + if ($class->isChangeTrackingDeferredExplicit()) { + $this->scheduleForDirtyCheck($entity); + } + + break; + + case self::STATE_DETACHED: + // Can actually not happen right now since we assume STATE_NEW. + throw ORMInvalidArgumentException::detachedEntityCannot($entity, 'persisted'); + + default: + throw new UnexpectedValueException(sprintf( + 'Unexpected entity state: %s. %s', + $entityState, + self::objToStr($entity), + )); + } + + $this->cascadePersist($entity, $visited); + } + + /** + * Deletes an entity as part of the current unit of work. + */ + public function remove(object $entity): void + { + $visited = []; + + $this->doRemove($entity, $visited); + } + + /** + * Deletes an entity as part of the current unit of work. + * + * This method is internally called during delete() cascades as it tracks + * the already visited entities to prevent infinite recursions. + * + * @psalm-param array $visited The map of the already visited entities. + * + * @throws ORMInvalidArgumentException If the instance is a detached entity. + * @throws UnexpectedValueException + */ + private function doRemove(object $entity, array &$visited): void + { + $oid = spl_object_id($entity); + + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $entity; // mark visited + + // Cascade first, because scheduleForDelete() removes the entity from the identity map, which + // can cause problems when a lazy proxy has to be initialized for the cascade operation. + $this->cascadeRemove($entity, $visited); + + $class = $this->em->getClassMetadata($entity::class); + $entityState = $this->getEntityState($entity); + + switch ($entityState) { + case self::STATE_NEW: + case self::STATE_REMOVED: + // nothing to do + break; + + case self::STATE_MANAGED: + $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove); + + if ($invoke !== ListenersInvoker::INVOKE_NONE) { + $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new PreRemoveEventArgs($entity, $this->em), $invoke); + } + + $this->scheduleForDelete($entity); + break; + + case self::STATE_DETACHED: + throw ORMInvalidArgumentException::detachedEntityCannot($entity, 'removed'); + + default: + throw new UnexpectedValueException(sprintf( + 'Unexpected entity state: %s. %s', + $entityState, + self::objToStr($entity), + )); + } + } + + /** + * Detaches an entity from the persistence management. It's persistence will + * no longer be managed by Doctrine. + */ + public function detach(object $entity): void + { + $visited = []; + + $this->doDetach($entity, $visited); + } + + /** + * Executes a detach operation on the given entity. + * + * @param mixed[] $visited + * @param bool $noCascade if true, don't cascade detach operation. + */ + private function doDetach( + object $entity, + array &$visited, + bool $noCascade = false, + ): void { + $oid = spl_object_id($entity); + + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $entity; // mark visited + + switch ($this->getEntityState($entity, self::STATE_DETACHED)) { + case self::STATE_MANAGED: + if ($this->isInIdentityMap($entity)) { + $this->removeFromIdentityMap($entity); + } + + unset( + $this->entityInsertions[$oid], + $this->entityUpdates[$oid], + $this->entityDeletions[$oid], + $this->entityIdentifiers[$oid], + $this->entityStates[$oid], + $this->originalEntityData[$oid], + ); + break; + case self::STATE_NEW: + case self::STATE_DETACHED: + return; + } + + if (! $noCascade) { + $this->cascadeDetach($entity, $visited); + } + } + + /** + * Refreshes the state of the given entity from the database, overwriting + * any local, unpersisted changes. + * + * @psalm-param LockMode::*|null $lockMode + * + * @throws InvalidArgumentException If the entity is not MANAGED. + * @throws TransactionRequiredException + */ + public function refresh(object $entity, LockMode|int|null $lockMode = null): void + { + $visited = []; + + $this->doRefresh($entity, $visited, $lockMode); + } + + /** + * Executes a refresh operation on an entity. + * + * @psalm-param array $visited The already visited entities during cascades. + * @psalm-param LockMode::*|null $lockMode + * + * @throws ORMInvalidArgumentException If the entity is not MANAGED. + * @throws TransactionRequiredException + */ + private function doRefresh(object $entity, array &$visited, LockMode|int|null $lockMode = null): void + { + switch (true) { + case $lockMode === LockMode::PESSIMISTIC_READ: + case $lockMode === LockMode::PESSIMISTIC_WRITE: + if (! $this->em->getConnection()->isTransactionActive()) { + throw TransactionRequiredException::transactionRequired(); + } + } + + $oid = spl_object_id($entity); + + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $entity; // mark visited + + $class = $this->em->getClassMetadata($entity::class); + + if ($this->getEntityState($entity) !== self::STATE_MANAGED) { + throw ORMInvalidArgumentException::entityNotManaged($entity); + } + + $this->getEntityPersister($class->name)->refresh( + array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), + $entity, + $lockMode, + ); + + $this->cascadeRefresh($entity, $visited, $lockMode); + } + + /** + * Cascades a refresh operation to associated entities. + * + * @psalm-param array $visited + * @psalm-param LockMode::*|null $lockMode + */ + private function cascadeRefresh(object $entity, array &$visited, LockMode|int|null $lockMode = null): void + { + $class = $this->em->getClassMetadata($entity::class); + + $associationMappings = array_filter( + $class->associationMappings, + static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRefresh() + ); + + foreach ($associationMappings as $assoc) { + $relatedEntities = $class->reflFields[$assoc->fieldName]->getValue($entity); + + switch (true) { + case $relatedEntities instanceof PersistentCollection: + // Unwrap so that foreach() does not initialize + $relatedEntities = $relatedEntities->unwrap(); + // break; is commented intentionally! + + case $relatedEntities instanceof Collection: + case is_array($relatedEntities): + foreach ($relatedEntities as $relatedEntity) { + $this->doRefresh($relatedEntity, $visited, $lockMode); + } + + break; + + case $relatedEntities !== null: + $this->doRefresh($relatedEntities, $visited, $lockMode); + break; + + default: + // Do nothing + } + } + } + + /** + * Cascades a detach operation to associated entities. + * + * @param array $visited + */ + private function cascadeDetach(object $entity, array &$visited): void + { + $class = $this->em->getClassMetadata($entity::class); + + $associationMappings = array_filter( + $class->associationMappings, + static fn (AssociationMapping $assoc): bool => $assoc->isCascadeDetach() + ); + + foreach ($associationMappings as $assoc) { + $relatedEntities = $class->reflFields[$assoc->fieldName]->getValue($entity); + + switch (true) { + case $relatedEntities instanceof PersistentCollection: + // Unwrap so that foreach() does not initialize + $relatedEntities = $relatedEntities->unwrap(); + // break; is commented intentionally! + + case $relatedEntities instanceof Collection: + case is_array($relatedEntities): + foreach ($relatedEntities as $relatedEntity) { + $this->doDetach($relatedEntity, $visited); + } + + break; + + case $relatedEntities !== null: + $this->doDetach($relatedEntities, $visited); + break; + + default: + // Do nothing + } + } + } + + /** + * Cascades the save operation to associated entities. + * + * @psalm-param array $visited + */ + private function cascadePersist(object $entity, array &$visited): void + { + if ($this->isUninitializedObject($entity)) { + // nothing to do - proxy is not initialized, therefore we don't do anything with it + return; + } + + $class = $this->em->getClassMetadata($entity::class); + + $associationMappings = array_filter( + $class->associationMappings, + static fn (AssociationMapping $assoc): bool => $assoc->isCascadePersist() + ); + + foreach ($associationMappings as $assoc) { + $relatedEntities = $class->reflFields[$assoc->fieldName]->getValue($entity); + + switch (true) { + case $relatedEntities instanceof PersistentCollection: + // Unwrap so that foreach() does not initialize + $relatedEntities = $relatedEntities->unwrap(); + // break; is commented intentionally! + + case $relatedEntities instanceof Collection: + case is_array($relatedEntities): + if ($assoc->isToMany() <= 0) { + throw ORMInvalidArgumentException::invalidAssociation( + $this->em->getClassMetadata($assoc->targetEntity), + $assoc, + $relatedEntities, + ); + } + + foreach ($relatedEntities as $relatedEntity) { + $this->doPersist($relatedEntity, $visited); + } + + break; + + case $relatedEntities !== null: + if (! $relatedEntities instanceof $assoc->targetEntity) { + throw ORMInvalidArgumentException::invalidAssociation( + $this->em->getClassMetadata($assoc->targetEntity), + $assoc, + $relatedEntities, + ); + } + + $this->doPersist($relatedEntities, $visited); + break; + + default: + // Do nothing + } + } + } + + /** + * Cascades the delete operation to associated entities. + * + * @psalm-param array $visited + */ + private function cascadeRemove(object $entity, array &$visited): void + { + $class = $this->em->getClassMetadata($entity::class); + + $associationMappings = array_filter( + $class->associationMappings, + static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRemove() + ); + + if ($associationMappings) { + $this->initializeObject($entity); + } + + $entitiesToCascade = []; + + foreach ($associationMappings as $assoc) { + $relatedEntities = $class->reflFields[$assoc->fieldName]->getValue($entity); + + switch (true) { + case $relatedEntities instanceof Collection: + case is_array($relatedEntities): + // If its a PersistentCollection initialization is intended! No unwrap! + foreach ($relatedEntities as $relatedEntity) { + $entitiesToCascade[] = $relatedEntity; + } + + break; + + case $relatedEntities !== null: + $entitiesToCascade[] = $relatedEntities; + break; + + default: + // Do nothing + } + } + + foreach ($entitiesToCascade as $relatedEntity) { + $this->doRemove($relatedEntity, $visited); + } + } + + /** + * Acquire a lock on the given entity. + * + * @psalm-param LockMode::* $lockMode + * + * @throws ORMInvalidArgumentException + * @throws TransactionRequiredException + * @throws OptimisticLockException + */ + public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void + { + if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { + throw ORMInvalidArgumentException::entityNotManaged($entity); + } + + $class = $this->em->getClassMetadata($entity::class); + + switch (true) { + case $lockMode === LockMode::OPTIMISTIC: + if (! $class->isVersioned) { + throw OptimisticLockException::notVersioned($class->name); + } + + if ($lockVersion === null) { + return; + } + + $this->initializeObject($entity); + + assert($class->versionField !== null); + $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); + + // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator + if ($entityVersion != $lockVersion) { + throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion); + } + + break; + + case $lockMode === LockMode::NONE: + case $lockMode === LockMode::PESSIMISTIC_READ: + case $lockMode === LockMode::PESSIMISTIC_WRITE: + if (! $this->em->getConnection()->isTransactionActive()) { + throw TransactionRequiredException::transactionRequired(); + } + + $oid = spl_object_id($entity); + + $this->getEntityPersister($class->name)->lock( + array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), + $lockMode, + ); + break; + + default: + // Do nothing + } + } + + /** + * Clears the UnitOfWork. + */ + public function clear(): void + { + $this->identityMap = + $this->entityIdentifiers = + $this->originalEntityData = + $this->entityChangeSets = + $this->entityStates = + $this->scheduledForSynchronization = + $this->entityInsertions = + $this->entityUpdates = + $this->entityDeletions = + $this->nonCascadedNewDetectedEntities = + $this->collectionDeletions = + $this->collectionUpdates = + $this->extraUpdates = + $this->readOnlyObjects = + $this->pendingCollectionElementRemovals = + $this->visitedCollections = + $this->eagerLoadingEntities = + $this->eagerLoadingCollections = + $this->orphanRemovals = []; + + if ($this->evm->hasListeners(Events::onClear)) { + $this->evm->dispatchEvent(Events::onClear, new OnClearEventArgs($this->em)); + } + } + + /** + * INTERNAL: + * Schedules an orphaned entity for removal. The remove() operation will be + * invoked on that entity at the beginning of the next commit of this + * UnitOfWork. + * + * @ignore + */ + public function scheduleOrphanRemoval(object $entity): void + { + $this->orphanRemovals[spl_object_id($entity)] = $entity; + } + + /** + * INTERNAL: + * Cancels a previously scheduled orphan removal. + * + * @ignore + */ + public function cancelOrphanRemoval(object $entity): void + { + unset($this->orphanRemovals[spl_object_id($entity)]); + } + + /** + * INTERNAL: + * Schedules a complete collection for removal when this UnitOfWork commits. + */ + public function scheduleCollectionDeletion(PersistentCollection $coll): void + { + $coid = spl_object_id($coll); + + // TODO: if $coll is already scheduled for recreation ... what to do? + // Just remove $coll from the scheduled recreations? + unset($this->collectionUpdates[$coid]); + + $this->collectionDeletions[$coid] = $coll; + } + + public function isCollectionScheduledForDeletion(PersistentCollection $coll): bool + { + return isset($this->collectionDeletions[spl_object_id($coll)]); + } + + /** + * INTERNAL: + * Creates an entity. Used for reconstitution of persistent entities. + * + * Internal note: Highly performance-sensitive method. + * + * @param string $className The name of the entity class. + * @param mixed[] $data The data for the entity. + * @param mixed[] $hints Any hints to account for during reconstitution/lookup of the entity. + * @psalm-param class-string $className + * @psalm-param array $hints + * + * @return object The managed entity instance. + * + * @ignore + * @todo Rename: getOrCreateEntity + */ + public function createEntity(string $className, array $data, array &$hints = []): object + { + $class = $this->em->getClassMetadata($className); + + $id = $this->identifierFlattener->flattenIdentifier($class, $data); + $idHash = self::getIdHashByIdentifier($id); + + if (isset($this->identityMap[$class->rootEntityName][$idHash])) { + $entity = $this->identityMap[$class->rootEntityName][$idHash]; + $oid = spl_object_id($entity); + + if ( + isset($hints[Query::HINT_REFRESH], $hints[Query::HINT_REFRESH_ENTITY]) + ) { + $unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]; + if ( + $unmanagedProxy !== $entity + && $this->isIdentifierEquals($unmanagedProxy, $entity) + ) { + // We will hydrate the given un-managed proxy anyway: + // continue work, but consider it the entity from now on + $entity = $unmanagedProxy; + } + } + + if ($this->isUninitializedObject($entity)) { + $entity->__setInitialized(true); + } else { + if ( + ! isset($hints[Query::HINT_REFRESH]) + || (isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity) + ) { + return $entity; + } + } + + $this->originalEntityData[$oid] = $data; + } else { + $entity = $class->newInstance(); + $oid = spl_object_id($entity); + $this->registerManaged($entity, $id, $data); + + if (isset($hints[Query::HINT_READ_ONLY])) { + $this->readOnlyObjects[$oid] = true; + } + } + + foreach ($data as $field => $value) { + if (isset($class->fieldMappings[$field])) { + $class->reflFields[$field]->setValue($entity, $value); + } + } + + // Loading the entity right here, if its in the eager loading map get rid of it there. + unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); + + if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) { + unset($this->eagerLoadingEntities[$class->rootEntityName]); + } + + foreach ($class->associationMappings as $field => $assoc) { + // Check if the association is not among the fetch-joined associations already. + if (isset($hints['fetchAlias'], $hints['fetched'][$hints['fetchAlias']][$field])) { + continue; + } + + if (! isset($hints['fetchMode'][$class->name][$field])) { + $hints['fetchMode'][$class->name][$field] = $assoc->fetch; + } + + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + + switch (true) { + case $assoc->isToOne(): + if (! $assoc->isOwningSide()) { + // use the given entity association + if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_id($data[$field])])) { + $this->originalEntityData[$oid][$field] = $data[$field]; + + $class->reflFields[$field]->setValue($entity, $data[$field]); + $targetClass->reflFields[$assoc->mappedBy]->setValue($data[$field], $entity); + + continue 2; + } + + // Inverse side of x-to-one can never be lazy + $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc->targetEntity)->loadOneToOneEntity($assoc, $entity)); + + continue 2; + } + + // use the entity association + if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_id($data[$field])])) { + $class->reflFields[$field]->setValue($entity, $data[$field]); + $this->originalEntityData[$oid][$field] = $data[$field]; + + break; + } + + $associatedId = []; + + assert($assoc->isToOneOwningSide()); + // TODO: Is this even computed right in all cases of composite keys? + foreach ($assoc->targetToSourceKeyColumns as $targetColumn => $srcColumn) { + $joinColumnValue = $data[$srcColumn] ?? null; + + if ($joinColumnValue !== null) { + if ($joinColumnValue instanceof BackedEnum) { + $joinColumnValue = $joinColumnValue->value; + } + + if ($targetClass->containsForeignIdentifier) { + $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; + } else { + $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; + } + } elseif (in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true)) { + // the missing key is part of target's entity primary key + $associatedId = []; + break; + } + } + + if (! $associatedId) { + // Foreign key is NULL + $class->reflFields[$field]->setValue($entity, null); + $this->originalEntityData[$oid][$field] = null; + + break; + } + + // Foreign key is set + // Check identity map first + // FIXME: Can break easily with composite keys if join column values are in + // wrong order. The correct order is the one in ClassMetadata#identifier. + $relatedIdHash = self::getIdHashByIdentifier($associatedId); + + switch (true) { + case isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash]): + $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; + + // If this is an uninitialized proxy, we are deferring eager loads, + // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) + // then we can append this entity for eager loading! + if ( + $hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER && + isset($hints[self::HINT_DEFEREAGERLOAD]) && + ! $targetClass->isIdentifierComposite && + $this->isUninitializedObject($newValue) + ) { + $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); + } + + break; + + case $targetClass->subClasses: + // If it might be a subtype, it can not be lazy. There isn't even + // a way to solve this with deferred eager loading, which means putting + // an entity with subclasses at a *-to-one location is really bad! (performance-wise) + $newValue = $this->getEntityPersister($assoc->targetEntity)->loadOneToOneEntity($assoc, $entity, $associatedId); + break; + + default: + $normalizedAssociatedId = $this->normalizeIdentifier($targetClass, $associatedId); + + switch (true) { + // We are negating the condition here. Other cases will assume it is valid! + case $hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER: + $newValue = $this->em->getProxyFactory()->getProxy($assoc->targetEntity, $normalizedAssociatedId); + $this->registerManaged($newValue, $associatedId, []); + break; + + // Deferred eager load only works for single identifier classes + case isset($hints[self::HINT_DEFEREAGERLOAD]) && + $hints[self::HINT_DEFEREAGERLOAD] && + ! $targetClass->isIdentifierComposite: + // TODO: Is there a faster approach? + $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($normalizedAssociatedId); + + $newValue = $this->em->getProxyFactory()->getProxy($assoc->targetEntity, $normalizedAssociatedId); + $this->registerManaged($newValue, $associatedId, []); + break; + + default: + // TODO: This is very imperformant, ignore it? + $newValue = $this->em->find($assoc->targetEntity, $normalizedAssociatedId); + break; + } + } + + $this->originalEntityData[$oid][$field] = $newValue; + $class->reflFields[$field]->setValue($entity, $newValue); + + if ($assoc->inversedBy !== null && $assoc->isOneToOne() && $newValue !== null) { + $inverseAssoc = $targetClass->associationMappings[$assoc->inversedBy]; + $targetClass->reflFields[$inverseAssoc->fieldName]->setValue($newValue, $entity); + } + + break; + + default: + assert($assoc->isToMany()); + // Ignore if its a cached collection + if (isset($hints[Query::HINT_CACHE_ENABLED]) && $class->getFieldValue($entity, $field) instanceof PersistentCollection) { + break; + } + + // use the given collection + if (isset($data[$field]) && $data[$field] instanceof PersistentCollection) { + $data[$field]->setOwner($entity, $assoc); + + $class->reflFields[$field]->setValue($entity, $data[$field]); + $this->originalEntityData[$oid][$field] = $data[$field]; + + break; + } + + // Inject collection + $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); + $pColl->setOwner($entity, $assoc); + $pColl->setInitialized(false); + + $reflField = $class->reflFields[$field]; + $reflField->setValue($entity, $pColl); + + if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) { + $isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION]; + if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) { + $this->scheduleCollectionForBatchLoading($pColl, $class); + } else { + $this->loadCollection($pColl); + $pColl->takeSnapshot(); + } + } + + $this->originalEntityData[$oid][$field] = $pColl; + break; + } + } + + // defer invoking of postLoad event to hydration complete step + $this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity); + + return $entity; + } + + public function triggerEagerLoads(): void + { + if (! $this->eagerLoadingEntities && ! $this->eagerLoadingCollections) { + return; + } + + // avoid infinite recursion + $eagerLoadingEntities = $this->eagerLoadingEntities; + $this->eagerLoadingEntities = []; + + foreach ($eagerLoadingEntities as $entityName => $ids) { + if (! $ids) { + continue; + } + + $class = $this->em->getClassMetadata($entityName); + $batches = array_chunk($ids, $this->em->getConfiguration()->getEagerFetchBatchSize()); + + foreach ($batches as $batchedIds) { + $this->getEntityPersister($entityName)->loadAll( + array_combine($class->identifier, [$batchedIds]), + ); + } + } + + $eagerLoadingCollections = $this->eagerLoadingCollections; // avoid recursion + $this->eagerLoadingCollections = []; + + foreach ($eagerLoadingCollections as $group) { + $this->eagerLoadCollections($group['items'], $group['mapping']); + } + } + + /** + * Load all data into the given collections, according to the specified mapping + * + * @param PersistentCollection[] $collections + */ + private function eagerLoadCollections(array $collections, ToManyInverseSideMapping $mapping): void + { + $targetEntity = $mapping->targetEntity; + $class = $this->em->getClassMetadata($mapping->sourceEntity); + $mappedBy = $mapping->mappedBy; + + $batches = array_chunk($collections, $this->em->getConfiguration()->getEagerFetchBatchSize(), true); + + foreach ($batches as $collectionBatch) { + $entities = []; + + foreach ($collectionBatch as $collection) { + $entities[] = $collection->getOwner(); + } + + $found = $this->getEntityPersister($targetEntity)->loadAll([$mappedBy => $entities], $mapping->orderBy); + + $targetClass = $this->em->getClassMetadata($targetEntity); + $targetProperty = $targetClass->getReflectionProperty($mappedBy); + assert($targetProperty !== null); + + foreach ($found as $targetValue) { + $sourceEntity = $targetProperty->getValue($targetValue); + + if ($sourceEntity === null && isset($targetClass->associationMappings[$mappedBy]->joinColumns)) { + // case where the hydration $targetValue itself has not yet fully completed, for example + // in case a bi-directional association is being hydrated and deferring eager loading is + // not possible due to subclassing. + $data = $this->getOriginalEntityData($targetValue); + $id = []; + foreach ($targetClass->associationMappings[$mappedBy]->joinColumns as $joinColumn) { + $id[] = $data[$joinColumn->name]; + } + } else { + $id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity)); + } + + $idHash = implode(' ', $id); + + if ($mapping->indexBy !== null) { + $indexByProperty = $targetClass->getReflectionProperty($mapping->indexBy); + assert($indexByProperty !== null); + $collectionBatch[$idHash]->hydrateSet($indexByProperty->getValue($targetValue), $targetValue); + } else { + $collectionBatch[$idHash]->add($targetValue); + } + } + } + + foreach ($collections as $association) { + $association->setInitialized(true); + $association->takeSnapshot(); + } + } + + /** + * Initializes (loads) an uninitialized persistent collection of an entity. + * + * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733. + */ + public function loadCollection(PersistentCollection $collection): void + { + $assoc = $collection->getMapping(); + $persister = $this->getEntityPersister($assoc->targetEntity); + + switch ($assoc->type()) { + case ClassMetadata::ONE_TO_MANY: + $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); + break; + + case ClassMetadata::MANY_TO_MANY: + $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); + break; + } + + $collection->setInitialized(true); + } + + /** + * Schedule this collection for batch loading at the end of the UnitOfWork + */ + private function scheduleCollectionForBatchLoading(PersistentCollection $collection, ClassMetadata $sourceClass): void + { + $mapping = $collection->getMapping(); + $name = $mapping->sourceEntity . '#' . $mapping->fieldName; + + if (! isset($this->eagerLoadingCollections[$name])) { + $this->eagerLoadingCollections[$name] = [ + 'items' => [], + 'mapping' => $mapping, + ]; + } + + $owner = $collection->getOwner(); + assert($owner !== null); + + $id = $this->identifierFlattener->flattenIdentifier( + $sourceClass, + $sourceClass->getIdentifierValues($owner), + ); + $idHash = implode(' ', $id); + + $this->eagerLoadingCollections[$name]['items'][$idHash] = $collection; + } + + /** + * Gets the identity map of the UnitOfWork. + * + * @psalm-return array> + */ + public function getIdentityMap(): array + { + return $this->identityMap; + } + + /** + * Gets the original data of an entity. The original data is the data that was + * present at the time the entity was reconstituted from the database. + * + * @psalm-return array + */ + public function getOriginalEntityData(object $entity): array + { + $oid = spl_object_id($entity); + + return $this->originalEntityData[$oid] ?? []; + } + + /** + * @param mixed[] $data + * + * @ignore + */ + public function setOriginalEntityData(object $entity, array $data): void + { + $this->originalEntityData[spl_object_id($entity)] = $data; + } + + /** + * INTERNAL: + * Sets a property value of the original data array of an entity. + * + * @ignore + */ + public function setOriginalEntityProperty(int $oid, string $property, mixed $value): void + { + $this->originalEntityData[$oid][$property] = $value; + } + + /** + * Gets the identifier of an entity. + * The returned value is always an array of identifier values. If the entity + * has a composite identifier then the identifier values are in the same + * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames(). + * + * @return mixed[] The identifier values. + */ + public function getEntityIdentifier(object $entity): array + { + return $this->entityIdentifiers[spl_object_id($entity)] + ?? throw EntityNotFoundException::noIdentifierFound(get_debug_type($entity)); + } + + /** + * Processes an entity instance to extract their identifier values. + * + * @return mixed A scalar value. + * + * @throws ORMInvalidArgumentException + */ + public function getSingleIdentifierValue(object $entity): mixed + { + $class = $this->em->getClassMetadata($entity::class); + + if ($class->isIdentifierComposite) { + throw ORMInvalidArgumentException::invalidCompositeIdentifier(); + } + + $values = $this->isInIdentityMap($entity) + ? $this->getEntityIdentifier($entity) + : $class->getIdentifierValues($entity); + + return $values[$class->identifier[0]] ?? null; + } + + /** + * Tries to find an entity with the given identifier in the identity map of + * this UnitOfWork. + * + * @param mixed $id The entity identifier to look for. + * @param string $rootClassName The name of the root class of the mapped entity hierarchy. + * @psalm-param class-string $rootClassName + * + * @return object|false Returns the entity with the specified identifier if it exists in + * this UnitOfWork, FALSE otherwise. + */ + public function tryGetById(mixed $id, string $rootClassName): object|false + { + $idHash = self::getIdHashByIdentifier((array) $id); + + return $this->identityMap[$rootClassName][$idHash] ?? false; + } + + /** + * Schedules an entity for dirty-checking at commit-time. + * + * @todo Rename: scheduleForSynchronization + */ + public function scheduleForDirtyCheck(object $entity): void + { + $rootClassName = $this->em->getClassMetadata($entity::class)->rootEntityName; + + $this->scheduledForSynchronization[$rootClassName][spl_object_id($entity)] = $entity; + } + + /** + * Checks whether the UnitOfWork has any pending insertions. + */ + public function hasPendingInsertions(): bool + { + return ! empty($this->entityInsertions); + } + + /** + * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the + * number of entities in the identity map. + */ + public function size(): int + { + return array_sum(array_map('count', $this->identityMap)); + } + + /** + * Gets the EntityPersister for an Entity. + * + * @psalm-param class-string $entityName + */ + public function getEntityPersister(string $entityName): EntityPersister + { + if (isset($this->persisters[$entityName])) { + return $this->persisters[$entityName]; + } + + $class = $this->em->getClassMetadata($entityName); + + $persister = match (true) { + $class->isInheritanceTypeNone() => new BasicEntityPersister($this->em, $class), + $class->isInheritanceTypeSingleTable() => new SingleTablePersister($this->em, $class), + $class->isInheritanceTypeJoined() => new JoinedSubclassPersister($this->em, $class), + default => throw new RuntimeException('No persister found for entity.'), + }; + + if ($this->hasCache && $class->cache !== null) { + $persister = $this->em->getConfiguration() + ->getSecondLevelCacheConfiguration() + ->getCacheFactory() + ->buildCachedEntityPersister($this->em, $persister, $class); + } + + $this->persisters[$entityName] = $persister; + + return $this->persisters[$entityName]; + } + + /** Gets a collection persister for a collection-valued association. */ + public function getCollectionPersister(AssociationMapping $association): CollectionPersister + { + $role = isset($association->cache) + ? $association->sourceEntity . '::' . $association->fieldName + : $association->type(); + + if (isset($this->collectionPersisters[$role])) { + return $this->collectionPersisters[$role]; + } + + $persister = $association->type() === ClassMetadata::ONE_TO_MANY + ? new OneToManyPersister($this->em) + : new ManyToManyPersister($this->em); + + if ($this->hasCache && isset($association->cache)) { + $persister = $this->em->getConfiguration() + ->getSecondLevelCacheConfiguration() + ->getCacheFactory() + ->buildCachedCollectionPersister($this->em, $persister, $association); + } + + $this->collectionPersisters[$role] = $persister; + + return $this->collectionPersisters[$role]; + } + + /** + * INTERNAL: + * Registers an entity as managed. + * + * @param mixed[] $id The identifier values. + * @param mixed[] $data The original entity data. + */ + public function registerManaged(object $entity, array $id, array $data): void + { + $oid = spl_object_id($entity); + + $this->entityIdentifiers[$oid] = $id; + $this->entityStates[$oid] = self::STATE_MANAGED; + $this->originalEntityData[$oid] = $data; + + $this->addToIdentityMap($entity); + } + + /* PropertyChangedListener implementation */ + + /** + * Notifies this UnitOfWork of a property change in an entity. + * + * {@inheritDoc} + */ + public function propertyChanged(object $sender, string $propertyName, mixed $oldValue, mixed $newValue): void + { + $oid = spl_object_id($sender); + $class = $this->em->getClassMetadata($sender::class); + + $isAssocField = isset($class->associationMappings[$propertyName]); + + if (! $isAssocField && ! isset($class->fieldMappings[$propertyName])) { + return; // ignore non-persistent fields + } + + // Update changeset and mark entity for synchronization + $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue]; + + if (! isset($this->scheduledForSynchronization[$class->rootEntityName][$oid])) { + $this->scheduleForDirtyCheck($sender); + } + } + + /** + * Gets the currently scheduled entity insertions in this UnitOfWork. + * + * @psalm-return array + */ + public function getScheduledEntityInsertions(): array + { + return $this->entityInsertions; + } + + /** + * Gets the currently scheduled entity updates in this UnitOfWork. + * + * @psalm-return array + */ + public function getScheduledEntityUpdates(): array + { + return $this->entityUpdates; + } + + /** + * Gets the currently scheduled entity deletions in this UnitOfWork. + * + * @psalm-return array + */ + public function getScheduledEntityDeletions(): array + { + return $this->entityDeletions; + } + + /** + * Gets the currently scheduled complete collection deletions + * + * @psalm-return array> + */ + public function getScheduledCollectionDeletions(): array + { + return $this->collectionDeletions; + } + + /** + * Gets the currently scheduled collection inserts, updates and deletes. + * + * @psalm-return array> + */ + public function getScheduledCollectionUpdates(): array + { + return $this->collectionUpdates; + } + + /** + * Helper method to initialize a lazy loading proxy or persistent collection. + */ + public function initializeObject(object $obj): void + { + if ($obj instanceof InternalProxy) { + $obj->__load(); + + return; + } + + if ($obj instanceof PersistentCollection) { + $obj->initialize(); + } + } + + /** + * Tests if a value is an uninitialized entity. + * + * @psalm-assert-if-true InternalProxy $obj + */ + public function isUninitializedObject(mixed $obj): bool + { + return $obj instanceof InternalProxy && ! $obj->__isInitialized(); + } + + /** + * Helper method to show an object as string. + */ + private static function objToStr(object $obj): string + { + return $obj instanceof Stringable ? (string) $obj : get_debug_type($obj) . '@' . spl_object_id($obj); + } + + /** + * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit(). + * + * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information + * on this object that might be necessary to perform a correct update. + * + * @throws ORMInvalidArgumentException + */ + public function markReadOnly(object $object): void + { + if (! $this->isInIdentityMap($object)) { + throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); + } + + $this->readOnlyObjects[spl_object_id($object)] = true; + } + + /** + * Is this entity read only? + * + * @throws ORMInvalidArgumentException + */ + public function isReadOnly(object $object): bool + { + return isset($this->readOnlyObjects[spl_object_id($object)]); + } + + /** + * Perform whatever processing is encapsulated here after completion of the transaction. + */ + private function afterTransactionComplete(): void + { + $this->performCallbackOnCachedPersister(static function (CachedPersister $persister): void { + $persister->afterTransactionComplete(); + }); + } + + /** + * Perform whatever processing is encapsulated here after completion of the rolled-back. + */ + private function afterTransactionRolledBack(): void + { + $this->performCallbackOnCachedPersister(static function (CachedPersister $persister): void { + $persister->afterTransactionRolledBack(); + }); + } + + /** + * Performs an action after the transaction. + */ + private function performCallbackOnCachedPersister(callable $callback): void + { + if (! $this->hasCache) { + return; + } + + foreach ([...$this->persisters, ...$this->collectionPersisters] as $persister) { + if ($persister instanceof CachedPersister) { + $callback($persister); + } + } + } + + private function dispatchOnFlushEvent(): void + { + if ($this->evm->hasListeners(Events::onFlush)) { + $this->evm->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em)); + } + } + + private function dispatchPostFlushEvent(): void + { + if ($this->evm->hasListeners(Events::postFlush)) { + $this->evm->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em)); + } + } + + /** + * Verifies if two given entities actually are the same based on identifier comparison + */ + private function isIdentifierEquals(object $entity1, object $entity2): bool + { + if ($entity1 === $entity2) { + return true; + } + + $class = $this->em->getClassMetadata($entity1::class); + + if ($class !== $this->em->getClassMetadata($entity2::class)) { + return false; + } + + $oid1 = spl_object_id($entity1); + $oid2 = spl_object_id($entity2); + + $id1 = $this->entityIdentifiers[$oid1] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity1)); + $id2 = $this->entityIdentifiers[$oid2] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity2)); + + return $id1 === $id2 || self::getIdHashByIdentifier($id1) === self::getIdHashByIdentifier($id2); + } + + /** @throws ORMInvalidArgumentException */ + private function assertThatThereAreNoUnintentionallyNonPersistedAssociations(): void + { + $entitiesNeedingCascadePersist = array_diff_key($this->nonCascadedNewDetectedEntities, $this->entityInsertions); + + $this->nonCascadedNewDetectedEntities = []; + + if ($entitiesNeedingCascadePersist) { + throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships( + array_values($entitiesNeedingCascadePersist), + ); + } + } + + /** + * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle. + * Unit of work able to fire deferred events, related to loading events here. + * + * @internal should be called internally from object hydrators + */ + public function hydrationComplete(): void + { + $this->hydrationCompleteHandler->hydrationComplete(); + } + + /** @throws MappingException if the entity has more than a single identifier. */ + private function convertSingleFieldIdentifierToPHPValue(ClassMetadata $class, mixed $identifierValue): mixed + { + return $this->em->getConnection()->convertToPHPValue( + $identifierValue, + $class->getTypeOfField($class->getSingleIdentifierFieldName()), + ); + } + + /** + * Given a flat identifier, this method will produce another flat identifier, but with all + * association fields that are mapped as identifiers replaced by entity references, recursively. + * + * @param mixed[] $flatIdentifier + * + * @return array + */ + private function normalizeIdentifier(ClassMetadata $targetClass, array $flatIdentifier): array + { + $normalizedAssociatedId = []; + + foreach ($targetClass->getIdentifierFieldNames() as $name) { + if (! array_key_exists($name, $flatIdentifier)) { + continue; + } + + if (! $targetClass->isSingleValuedAssociation($name)) { + $normalizedAssociatedId[$name] = $flatIdentifier[$name]; + continue; + } + + $targetIdMetadata = $this->em->getClassMetadata($targetClass->getAssociationTargetClass($name)); + + // Note: the ORM prevents using an entity with a composite identifier as an identifier association + // therefore, reset($targetIdMetadata->identifier) is always correct + $normalizedAssociatedId[$name] = $this->em->getReference( + $targetIdMetadata->getName(), + $this->normalizeIdentifier( + $targetIdMetadata, + [(string) reset($targetIdMetadata->identifier) => $flatIdentifier[$name]], + ), + ); + } + + return $normalizedAssociatedId; + } + + /** + * Assign a post-insert generated ID to an entity + * + * This is used by EntityPersisters after they inserted entities into the database. + * It will place the assigned ID values in the entity's fields and start tracking + * the entity in the identity map. + */ + final public function assignPostInsertId(object $entity, mixed $generatedId): void + { + $class = $this->em->getClassMetadata($entity::class); + $idField = $class->getSingleIdentifierFieldName(); + $idValue = $this->convertSingleFieldIdentifierToPHPValue($class, $generatedId); + $oid = spl_object_id($entity); + + $class->reflFields[$idField]->setValue($entity, $idValue); + + $this->entityIdentifiers[$oid] = [$idField => $idValue]; + $this->entityStates[$oid] = self::STATE_MANAGED; + $this->originalEntityData[$oid][$idField] = $idValue; + + $this->addToIdentityMap($entity); + } +} diff --git a/vendor/doctrine/orm/src/Utility/HierarchyDiscriminatorResolver.php b/vendor/doctrine/orm/src/Utility/HierarchyDiscriminatorResolver.php new file mode 100644 index 0000000..b682125 --- /dev/null +++ b/vendor/doctrine/orm/src/Utility/HierarchyDiscriminatorResolver.php @@ -0,0 +1,44 @@ + + */ + public static function resolveDiscriminatorsForClass( + ClassMetadata $rootClassMetadata, + EntityManagerInterface $entityManager, + ): array { + $hierarchyClasses = $rootClassMetadata->subClasses; + $hierarchyClasses[] = $rootClassMetadata->name; + + $discriminators = []; + + foreach ($hierarchyClasses as $class) { + $currentMetadata = $entityManager->getClassMetadata($class); + $currentDiscriminator = $currentMetadata->discriminatorValue; + + if ($currentDiscriminator !== null) { + $discriminators[$currentDiscriminator] = null; + } + } + + return $discriminators; + } +} diff --git a/vendor/doctrine/orm/src/Utility/IdentifierFlattener.php b/vendor/doctrine/orm/src/Utility/IdentifierFlattener.php new file mode 100644 index 0000000..3792d33 --- /dev/null +++ b/vendor/doctrine/orm/src/Utility/IdentifierFlattener.php @@ -0,0 +1,83 @@ + + */ + public function flattenIdentifier(ClassMetadata $class, array $id): array + { + $flatId = []; + + foreach ($class->identifier as $field) { + if (isset($class->associationMappings[$field]) && isset($id[$field]) && is_a($id[$field], $class->associationMappings[$field]->targetEntity)) { + $targetClassMetadata = $this->metadataFactory->getMetadataFor( + $class->associationMappings[$field]->targetEntity, + ); + assert($targetClassMetadata instanceof ClassMetadata); + + if ($this->unitOfWork->isInIdentityMap($id[$field])) { + $associatedId = $this->flattenIdentifier($targetClassMetadata, $this->unitOfWork->getEntityIdentifier($id[$field])); + } else { + $associatedId = $this->flattenIdentifier($targetClassMetadata, $targetClassMetadata->getIdentifierValues($id[$field])); + } + + $flatId[$field] = implode(' ', $associatedId); + } elseif (isset($class->associationMappings[$field])) { + assert($class->associationMappings[$field]->isToOneOwningSide()); + $associatedId = []; + + foreach ($class->associationMappings[$field]->joinColumns as $joinColumn) { + $associatedId[] = $id[$joinColumn->name]; + } + + $flatId[$field] = implode(' ', $associatedId); + } else { + if ($id[$field] instanceof BackedEnum) { + $flatId[$field] = $id[$field]->value; + } else { + $flatId[$field] = $id[$field]; + } + } + } + + return $flatId; + } +} diff --git a/vendor/doctrine/orm/src/Utility/LockSqlHelper.php b/vendor/doctrine/orm/src/Utility/LockSqlHelper.php new file mode 100644 index 0000000..7d135eb --- /dev/null +++ b/vendor/doctrine/orm/src/Utility/LockSqlHelper.php @@ -0,0 +1,35 @@ + 'LOCK IN SHARE MODE', + $platform instanceof PostgreSQLPlatform => 'FOR SHARE', + default => $this->getWriteLockSQL($platform), + }; + } + + private function getWriteLockSQL(AbstractPlatform $platform): string + { + return match (true) { + $platform instanceof DB2Platform => 'WITH RR USE AND KEEP UPDATE LOCKS', + $platform instanceof SQLitePlatform, + $platform instanceof SQLServerPlatform => '', + default => 'FOR UPDATE', + }; + } +} diff --git a/vendor/doctrine/orm/src/Utility/PersisterHelper.php b/vendor/doctrine/orm/src/Utility/PersisterHelper.php new file mode 100644 index 0000000..76e9242 --- /dev/null +++ b/vendor/doctrine/orm/src/Utility/PersisterHelper.php @@ -0,0 +1,108 @@ + + * + * @throws QueryException + */ + public static function getTypeOfField(string $fieldName, ClassMetadata $class, EntityManagerInterface $em): array + { + if (isset($class->fieldMappings[$fieldName])) { + return [$class->fieldMappings[$fieldName]->type]; + } + + if (! isset($class->associationMappings[$fieldName])) { + return []; + } + + $assoc = $class->associationMappings[$fieldName]; + + if (! $assoc->isOwningSide()) { + return self::getTypeOfField($assoc->mappedBy, $em->getClassMetadata($assoc->targetEntity), $em); + } + + if ($assoc->isManyToManyOwningSide()) { + $joinData = $assoc->joinTable; + } else { + $joinData = $assoc; + } + + $types = []; + $targetClass = $em->getClassMetadata($assoc->targetEntity); + + foreach ($joinData->joinColumns as $joinColumn) { + $types[] = self::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $em); + } + + return $types; + } + + /** @throws RuntimeException */ + public static function getTypeOfColumn(string $columnName, ClassMetadata $class, EntityManagerInterface $em): string + { + if (isset($class->fieldNames[$columnName])) { + $fieldName = $class->fieldNames[$columnName]; + + if (isset($class->fieldMappings[$fieldName])) { + return $class->fieldMappings[$fieldName]->type; + } + } + + // iterate over to-one association mappings + foreach ($class->associationMappings as $assoc) { + if (! $assoc->isToOneOwningSide()) { + continue; + } + + foreach ($assoc->joinColumns as $joinColumn) { + if ($joinColumn->name === $columnName) { + $targetColumnName = $joinColumn->referencedColumnName; + $targetClass = $em->getClassMetadata($assoc->targetEntity); + + return self::getTypeOfColumn($targetColumnName, $targetClass, $em); + } + } + } + + // iterate over to-many association mappings + foreach ($class->associationMappings as $assoc) { + if (! $assoc->isManyToManyOwningSide()) { + continue; + } + + foreach ($assoc->joinTable->joinColumns as $joinColumn) { + if ($joinColumn->name === $columnName) { + $targetColumnName = $joinColumn->referencedColumnName; + $targetClass = $em->getClassMetadata($assoc->targetEntity); + + return self::getTypeOfColumn($targetColumnName, $targetClass, $em); + } + } + } + + throw new RuntimeException(sprintf( + 'Could not resolve type of column "%s" of class "%s"', + $columnName, + $class->getName(), + )); + } +} diff --git a/vendor/doctrine/persistence/CONTRIBUTING.md b/vendor/doctrine/persistence/CONTRIBUTING.md new file mode 100644 index 0000000..268200a --- /dev/null +++ b/vendor/doctrine/persistence/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Circular dependency + +This package has a development dependency on `doctrine/common`, which has a +regular dependency on this package (`^2.0` at the time of writing). + +To be able to use Composer, one has to let it understand that this is version 2 +(even when developing on 3.0.x), as follows: + +```shell +COMPOSER_ROOT_VERSION=2.0 composer update -v +``` diff --git a/vendor/doctrine/persistence/LICENSE b/vendor/doctrine/persistence/LICENSE new file mode 100644 index 0000000..8c38cc1 --- /dev/null +++ b/vendor/doctrine/persistence/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +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. diff --git a/vendor/doctrine/persistence/README.md b/vendor/doctrine/persistence/README.md new file mode 100644 index 0000000..aeaaad3 --- /dev/null +++ b/vendor/doctrine/persistence/README.md @@ -0,0 +1,17 @@ +# Doctrine Persistence + +[![GitHub Actions][GA 3.3 image]][GA 3.3] +[![Code Coverage][Coverage 3.3 image]][CodeCov 3.3] + +The Doctrine Persistence project is a library that provides common abstractions for object mapper persistence. + +## More resources: + +* [Website](https://www.doctrine-project.org/) +* [Documentation](https://www.doctrine-project.org/projects/doctrine-persistence/en/latest/index.html) +* [Downloads](https://github.com/doctrine/persistence/releases) + + [Coverage 3.3 image]: https://codecov.io/gh/doctrine/persistence/branch/3.3.x/graph/badge.svg + [CodeCov 3.3]: https://codecov.io/gh/doctrine/persistence/branch/3.3.x + [GA 3.3 image]: https://github.com/doctrine/persistence/workflows/Continuous%20Integration/badge.svg?branch=3.3.x + [GA 3.3]: https://github.com/doctrine/persistence/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A3.3.x diff --git a/vendor/doctrine/persistence/UPGRADE.md b/vendor/doctrine/persistence/UPGRADE.md new file mode 100644 index 0000000..d7f5b04 --- /dev/null +++ b/vendor/doctrine/persistence/UPGRADE.md @@ -0,0 +1,154 @@ +Note about upgrading: Doctrine uses static and runtime mechanisms to raise +awareness about deprecated code. + +- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or + Static Analysis tools (like Psalm, phpstan) +- Use of our low-overhead runtime deprecation API, details: + https://github.com/doctrine/deprecations/ + +# Upgrade to 3.3 + +## Added method `ObjectManager::isUninitializedObject()` + +Classes implementing `Doctrine\Persistence\ObjectManager` should implement the new +method. This method will be added to the interface in 4.0. + +# Upgrade to 3.1 + +## Deprecated `RuntimePublicReflectionProperty` + +Use `RuntimeReflectionProperty` instead. + +# Upgrade to 3.0 + +## Removed `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()` + +These methods only make sense when partially clearing the object manager, which +is no longer possible. +The second argument of the constructor of `OnClearEventArgs` is removed as well. + +## BC Break: removed `ObjectManagerAware` + +Implement active record style functionality directly in your application, by +using a `postLoad` event. + +## BC Break: removed `AnnotationDriver` + +Use `ColocatedMappingDriver` instead. + +## BC Break: Removed `MappingException::pathRequired()` + +Use `MappingException::pathRequiredForDriver()` instead. + +## BC Break: removed `LifecycleEventArgs::getEntity()` + +Use `LifecycleEventArgs::getObject()` instead. + +## BC Break: removed support for short namespace aliases + +- `AbstractClassMetadataFactory::getFqcnFromAlias()` is removed. +- `ClassMetadataFactory` methods now require their `$className` argument to be an +actual FQCN. + +## BC Break: removed `ObjectManager::merge()` + +`ObjectManagerDecorator::merge()` is removed without replacement. + +## BC Break: removed support for `doctrine/cache` + +Removed support for using doctrine/cache for metadata caching. The +`setCacheDriver` and `getCacheDriver` methods have been removed from +`Doctrine\Persistence\Mapping\AbstractMetadata`. Please use `getCache` and +`setCache` with a PSR-6 implementation instead. + +## BC Break: changed signatures + +`$objectName` has been dropped from the signature of `ObjectManager::clear()`. + +```diff +- public function clear($objectName = null) ++ public function clear(): void +``` + +Also, native parameter type declarations have been added on all public APIs. +Native return type declarations have not been added so that it is possible to +implement types compatible with both 2.x and 3.x. + +## BC Break: Removed `PersistentObject` + +Please implement this functionality directly in your application if you want +ActiveRecord style functionality. + +# Upgrade to 2.5 + +## Deprecated `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()` + +These methods only make sense when partially clearing the object manager, which +is deprecated. +Passing a second argument to the constructor of `OnClearEventArgs` is +deprecated as well. + +## Deprecated `ObjectManagerAware` + +Along with deprecating `PersistentObject`, deprecating `ObjectManagerAware` +means deprecating support for active record, which already came with a word of +warning. Please implement this directly in your application with a `postLoad` +event if you need active record style functionality. + +## Deprecated `MappingException::pathRequired()` + +`MappingException::pathRequiredForDriver()` should be used instead. + +# Upgrade to 2.4 + +## Deprecated `AnnotationDriver` + +Since attributes were introduced in PHP 8.0, annotations are deprecated. +`AnnotationDriver` is an abstract class that is used when implementing concrete +annotation drivers in dependent packages. It is deprecated in favor of using +`ColocatedMappingDriver` to implement both annotation and attribute based +drivers. This will involve implementing `isTransient()` as well as +`__construct()` and `getReader()` to retain backward compatibility. + +# Upgrade to 2.3 + +## Deprecated using short namespace alias syntax in favor of `::class` syntax. + +Before: + +```php +$objectManager->find('MyPackage:MyClass', $id); +$objectManager->createQuery('SELECT u FROM MyPackage:MyClass'); +``` + +After: + +```php +$objectManager->find(MyClass::class, $id); +$objectManager->createQuery('SELECT u FROM '. MyClass::class); +``` + +# Upgrade to 2.2 + +## Deprecated `doctrine/cache` usage for metadata caching + +The `setCacheDriver` and `getCacheDriver` methods in +`Doctrine\Persistence\Mapping\AbstractMetadata` have been deprecated. Please +use `getCache` and `setCache` with a PSR-6 implementation instead. Note that +even after switching to PSR-6, `getCacheDriver` will return a cache instance +that wraps the PSR-6 cache. Note that if you use a custom implementation of +doctrine/cache, the library may not be able to provide a forward compatibility +layer. The cache implementation MUST extend the +`Doctrine\Common\Cache\CacheProvider` class. + +# Upgrade to 1.2 + +## Deprecated `ObjectManager::merge()` and `ObjectManager::detach()` + +Please handle merge operations in your application, and use +`ObjectManager::clear()` instead. + +## Deprecated `PersistentObject` + +Please implement this functionality directly in your application if you want +ActiveRecord style functionality. diff --git a/vendor/doctrine/persistence/composer.json b/vendor/doctrine/persistence/composer.json new file mode 100644 index 0000000..03d832f --- /dev/null +++ b/vendor/doctrine/persistence/composer.json @@ -0,0 +1,58 @@ +{ + "name": "doctrine/persistence", + "type": "library", + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "keywords": [ + "persistence", + "object", + "mapper", + "orm", + "odm" + ], + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}, + {"name": "Marco Pivetta", "email": "ocramius@gmail.com"} + ], + "require": { + "php": "^7.2 || ^8.0", + "doctrine/event-manager": "^1 || ^2", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "phpstan/phpstan": "1.11.1", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "doctrine/coding-standard": "^12", + "doctrine/common": "^3.0", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.30.0 || 5.24.0" + }, + "conflict": { + "doctrine/common": "<2.10" + }, + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests", + "Doctrine\\Tests_PHP74\\": "tests_php74", + "Doctrine\\Tests_PHP81\\": "tests_php81" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "composer/package-versions-deprecated": true + } + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/AbstractManagerRegistry.php b/vendor/doctrine/persistence/src/Persistence/AbstractManagerRegistry.php new file mode 100644 index 0000000..cc245ba --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/AbstractManagerRegistry.php @@ -0,0 +1,269 @@ + */ + private $connections; + + /** @var array */ + private $managers; + + /** @var string */ + private $defaultConnection; + + /** @var string */ + private $defaultManager; + + /** + * @var string + * @psalm-var class-string + */ + private $proxyInterfaceName; + + /** + * @param array $connections + * @param array $managers + * @psalm-param class-string $proxyInterfaceName + */ + public function __construct( + string $name, + array $connections, + array $managers, + string $defaultConnection, + string $defaultManager, + string $proxyInterfaceName + ) { + $this->name = $name; + $this->connections = $connections; + $this->managers = $managers; + $this->defaultConnection = $defaultConnection; + $this->defaultManager = $defaultManager; + $this->proxyInterfaceName = $proxyInterfaceName; + } + + /** + * Fetches/creates the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return object The instance of the given service. + */ + abstract protected function getService(string $name); + + /** + * Resets the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return void + */ + abstract protected function resetService(string $name); + + /** + * Gets the name of the registry. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritDoc} + */ + public function getConnection(?string $name = null) + { + if ($name === null) { + $name = $this->defaultConnection; + } + + if (! isset($this->connections[$name])) { + throw new InvalidArgumentException( + sprintf('Doctrine %s Connection named "%s" does not exist.', $this->name, $name) + ); + } + + return $this->getService($this->connections[$name]); + } + + /** + * {@inheritDoc} + */ + public function getConnectionNames() + { + return $this->connections; + } + + /** + * {@inheritDoc} + */ + public function getConnections() + { + $connections = []; + foreach ($this->connections as $name => $id) { + $connections[$name] = $this->getService($id); + } + + return $connections; + } + + /** + * {@inheritDoc} + */ + public function getDefaultConnectionName() + { + return $this->defaultConnection; + } + + /** + * {@inheritDoc} + */ + public function getDefaultManagerName() + { + return $this->defaultManager; + } + + /** + * {@inheritDoc} + * + * @throws InvalidArgumentException + */ + public function getManager(?string $name = null) + { + if ($name === null) { + $name = $this->defaultManager; + } + + if (! isset($this->managers[$name])) { + throw new InvalidArgumentException( + sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name) + ); + } + + $service = $this->getService($this->managers[$name]); + assert($service instanceof ObjectManager); + + return $service; + } + + /** + * {@inheritDoc} + */ + public function getManagerForClass(string $class) + { + $proxyClass = new ReflectionClass($class); + if ($proxyClass->isAnonymous()) { + return null; + } + + if ($proxyClass->implementsInterface($this->proxyInterfaceName)) { + $parentClass = $proxyClass->getParentClass(); + + if ($parentClass === false) { + return null; + } + + $class = $parentClass->getName(); + } + + foreach ($this->managers as $id) { + $manager = $this->getService($id); + assert($manager instanceof ObjectManager); + + if (! $manager->getMetadataFactory()->isTransient($class)) { + return $manager; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getManagerNames() + { + return $this->managers; + } + + /** + * {@inheritDoc} + */ + public function getManagers() + { + $managers = []; + + foreach ($this->managers as $name => $id) { + $manager = $this->getService($id); + assert($manager instanceof ObjectManager); + $managers[$name] = $manager; + } + + return $managers; + } + + /** + * {@inheritDoc} + */ + public function getRepository( + string $persistentObject, + ?string $persistentManagerName = null + ) { + return $this + ->selectManager($persistentObject, $persistentManagerName) + ->getRepository($persistentObject); + } + + /** + * {@inheritDoc} + */ + public function resetManager(?string $name = null) + { + if ($name === null) { + $name = $this->defaultManager; + } + + if (! isset($this->managers[$name])) { + throw new InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); + } + + // force the creation of a new document manager + // if the current one is closed + $this->resetService($this->managers[$name]); + + return $this->getManager($name); + } + + /** @psalm-param class-string $persistentObject */ + private function selectManager( + string $persistentObject, + ?string $persistentManagerName = null + ): ObjectManager { + if ($persistentManagerName !== null) { + return $this->getManager($persistentManagerName); + } + + return $this->getManagerForClass($persistentObject) ?? $this->getManager(); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/ConnectionRegistry.php b/vendor/doctrine/persistence/src/Persistence/ConnectionRegistry.php new file mode 100644 index 0000000..59d9a74 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/ConnectionRegistry.php @@ -0,0 +1,41 @@ + An array of Connection instances. + */ + public function getConnections(); + + /** + * Gets all connection names. + * + * @return array An array of connection names. + */ + public function getConnectionNames(); +} diff --git a/vendor/doctrine/persistence/src/Persistence/Event/LifecycleEventArgs.php b/vendor/doctrine/persistence/src/Persistence/Event/LifecycleEventArgs.php new file mode 100644 index 0000000..1654de4 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Event/LifecycleEventArgs.php @@ -0,0 +1,54 @@ +object = $object; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated object. + * + * @return object + */ + public function getObject() + { + return $this->object; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + * @psalm-return TObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Event/LoadClassMetadataEventArgs.php b/vendor/doctrine/persistence/src/Persistence/Event/LoadClassMetadataEventArgs.php new file mode 100644 index 0000000..aa92d5d --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Event/LoadClassMetadataEventArgs.php @@ -0,0 +1,61 @@ + + * @template-covariant TObjectManager of ObjectManager + */ +class LoadClassMetadataEventArgs extends EventArgs +{ + /** + * @var ClassMetadata + * @psalm-var TClassMetadata + */ + private $classMetadata; + + /** + * @var ObjectManager + * @psalm-var TObjectManager + */ + private $objectManager; + + /** + * @psalm-param TClassMetadata $classMetadata + * @psalm-param TObjectManager $objectManager + */ + public function __construct(ClassMetadata $classMetadata, ObjectManager $objectManager) + { + $this->classMetadata = $classMetadata; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated ClassMetadata. + * + * @return ClassMetadata + * @psalm-return TClassMetadata + */ + public function getClassMetadata() + { + return $this->classMetadata; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return TObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Event/ManagerEventArgs.php b/vendor/doctrine/persistence/src/Persistence/Event/ManagerEventArgs.php new file mode 100644 index 0000000..5156013 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Event/ManagerEventArgs.php @@ -0,0 +1,39 @@ +objectManager = $objectManager; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + * @psalm-return TObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Event/OnClearEventArgs.php b/vendor/doctrine/persistence/src/Persistence/Event/OnClearEventArgs.php new file mode 100644 index 0000000..519a887 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Event/OnClearEventArgs.php @@ -0,0 +1,42 @@ +objectManager = $objectManager; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + * @psalm-return TObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Event/PreUpdateEventArgs.php b/vendor/doctrine/persistence/src/Persistence/Event/PreUpdateEventArgs.php new file mode 100644 index 0000000..95ecbd4 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Event/PreUpdateEventArgs.php @@ -0,0 +1,110 @@ + + */ +class PreUpdateEventArgs extends LifecycleEventArgs +{ + /** @var array> */ + private $entityChangeSet; + + /** + * @param array> $changeSet + * @psalm-param TObjectManager $objectManager + */ + public function __construct(object $entity, ObjectManager $objectManager, array &$changeSet) + { + parent::__construct($entity, $objectManager); + + $this->entityChangeSet = &$changeSet; + } + + /** + * Retrieves the entity changeset. + * + * @return array> + */ + public function getEntityChangeSet() + { + return $this->entityChangeSet; + } + + /** + * Checks if field has a changeset. + * + * @return bool + */ + public function hasChangedField(string $field) + { + return isset($this->entityChangeSet[$field]); + } + + /** + * Gets the old value of the changeset of the changed field. + * + * @return mixed + */ + public function getOldValue(string $field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][0]; + } + + /** + * Gets the new value of the changeset of the changed field. + * + * @return mixed + */ + public function getNewValue(string $field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][1]; + } + + /** + * Sets the new value of this field. + * + * @param mixed $value + * + * @return void + */ + public function setNewValue(string $field, $value) + { + $this->assertValidField($field); + + $this->entityChangeSet[$field][1] = $value; + } + + /** + * Asserts the field exists in changeset. + * + * @return void + * + * @throws InvalidArgumentException + */ + private function assertValidField(string $field) + { + if (! isset($this->entityChangeSet[$field])) { + throw new InvalidArgumentException(sprintf( + 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', + $field, + get_class($this->getObject()) + )); + } + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/ManagerRegistry.php b/vendor/doctrine/persistence/src/Persistence/ManagerRegistry.php new file mode 100644 index 0000000..46599a5 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/ManagerRegistry.php @@ -0,0 +1,89 @@ + An array of ObjectManager instances + */ + public function getManagers(); + + /** + * Resets a named object manager. + * + * This method is useful when an object manager has been closed + * because of a rollbacked transaction AND when you think that + * it makes sense to get a new one to replace the closed one. + * + * Be warned that you will get a brand new object manager as + * the existing one is not useable anymore. This means that any + * other object with a dependency on this object manager will + * hold an obsolete reference. You can inject the registry instead + * to avoid this problem. + * + * @param string|null $name The object manager name (null for the default one). + * + * @return ObjectManager + */ + public function resetManager(?string $name = null); + + /** + * Gets all object manager names and associated service IDs. A service ID + * is a string that allows to obtain an object manager, typically from a + * PSR-11 container. + * + * @return array An array with object manager names as keys, + * and service IDs as values. + */ + public function getManagerNames(); + + /** + * Gets the ObjectRepository for a persistent object. + * + * @param string $persistentObject The name of the persistent object. + * @param string|null $persistentManagerName The object manager name (null for the default one). + * @psalm-param class-string $persistentObject + * + * @return ObjectRepository + * @psalm-return ObjectRepository + * + * @template T of object + */ + public function getRepository( + string $persistentObject, + ?string $persistentManagerName = null + ); + + /** + * Gets the object manager associated with a given class. + * + * @param class-string $class A persistent object class name. + * + * @return ObjectManager|null + */ + public function getManagerForClass(string $class); +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php b/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php new file mode 100644 index 0000000..e8f6aca --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php @@ -0,0 +1,499 @@ + + */ +abstract class AbstractClassMetadataFactory implements ClassMetadataFactory +{ + /** + * Salt used by specific Object Manager implementation. + * + * @var string + */ + protected $cacheSalt = '__CLASSMETADATA__'; + + /** @var CacheItemPoolInterface|null */ + private $cache; + + /** + * @var array + * @psalm-var CMTemplate[] + */ + private $loadedMetadata = []; + + /** @var bool */ + protected $initialized = false; + + /** @var ReflectionService|null */ + private $reflectionService = null; + + /** @var ProxyClassNameResolver|null */ + private $proxyClassNameResolver = null; + + public function setCache(CacheItemPoolInterface $cache): void + { + $this->cache = $cache; + } + + final protected function getCache(): ?CacheItemPoolInterface + { + return $this->cache; + } + + /** + * Returns an array of all the loaded metadata currently in memory. + * + * @return ClassMetadata[] + * @psalm-return CMTemplate[] + */ + public function getLoadedMetadata() + { + return $this->loadedMetadata; + } + + /** + * {@inheritDoc} + */ + public function getAllMetadata() + { + if (! $this->initialized) { + $this->initialize(); + } + + $driver = $this->getDriver(); + $metadata = []; + foreach ($driver->getAllClassNames() as $className) { + $metadata[] = $this->getMetadataFor($className); + } + + return $metadata; + } + + public function setProxyClassNameResolver(ProxyClassNameResolver $resolver): void + { + $this->proxyClassNameResolver = $resolver; + } + + /** + * Lazy initialization of this stuff, especially the metadata driver, + * since these are not needed at all when a metadata cache is active. + * + * @return void + */ + abstract protected function initialize(); + + /** + * Returns the mapping driver implementation. + * + * @return MappingDriver + */ + abstract protected function getDriver(); + + /** + * Wakes up reflection after ClassMetadata gets unserialized from cache. + * + * @psalm-param CMTemplate $class + * + * @return void + */ + abstract protected function wakeupReflection( + ClassMetadata $class, + ReflectionService $reflService + ); + + /** + * Initializes Reflection after ClassMetadata was constructed. + * + * @psalm-param CMTemplate $class + * + * @return void + */ + abstract protected function initializeReflection( + ClassMetadata $class, + ReflectionService $reflService + ); + + /** + * Checks whether the class metadata is an entity. + * + * This method should return false for mapped superclasses or embedded classes. + * + * @psalm-param CMTemplate $class + * + * @return bool + */ + abstract protected function isEntity(ClassMetadata $class); + + /** + * Removes the prepended backslash of a class string to conform with how php outputs class names + * + * @psalm-param class-string $className + * + * @psalm-return class-string + */ + private function normalizeClassName(string $className): string + { + return ltrim($className, '\\'); + } + + /** + * {@inheritDoc} + * + * @throws ReflectionException + * @throws MappingException + */ + public function getMetadataFor(string $className) + { + $className = $this->normalizeClassName($className); + + if (isset($this->loadedMetadata[$className])) { + return $this->loadedMetadata[$className]; + } + + if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) { + throw MappingException::classIsAnonymous($className); + } + + if (! class_exists($className, false) && strpos($className, ':') !== false) { + throw MappingException::nonExistingClass($className); + } + + $realClassName = $this->getRealClass($className); + + if (isset($this->loadedMetadata[$realClassName])) { + // We do not have the alias name in the map, include it + return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + + try { + if ($this->cache !== null) { + $cached = $this->cache->getItem($this->getCacheKey($realClassName))->get(); + if ($cached instanceof ClassMetadata) { + /** @psalm-var CMTemplate $cached */ + $this->loadedMetadata[$realClassName] = $cached; + + $this->wakeupReflection($cached, $this->getReflectionService()); + } else { + $loadedMetadata = $this->loadMetadata($realClassName); + $classNames = array_combine( + array_map([$this, 'getCacheKey'], $loadedMetadata), + $loadedMetadata + ); + + foreach ($this->cache->getItems(array_keys($classNames)) as $item) { + if (! isset($classNames[$item->getKey()])) { + continue; + } + + $item->set($this->loadedMetadata[$classNames[$item->getKey()]]); + $this->cache->saveDeferred($item); + } + + $this->cache->commit(); + } + } else { + $this->loadMetadata($realClassName); + } + } catch (MappingException $loadingException) { + $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName); + + if ($fallbackMetadataResponse === null) { + throw $loadingException; + } + + $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse; + } + + if ($className !== $realClassName) { + // We do not have the alias name in the map, include it + $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + + return $this->loadedMetadata[$className]; + } + + /** + * {@inheritDoc} + */ + public function hasMetadataFor(string $className) + { + $className = $this->normalizeClassName($className); + + return isset($this->loadedMetadata[$className]); + } + + /** + * Sets the metadata descriptor for a specific class. + * + * NOTE: This is only useful in very special cases, like when generating proxy classes. + * + * @psalm-param class-string $className + * @psalm-param CMTemplate $class + * + * @return void + */ + public function setMetadataFor(string $className, ClassMetadata $class) + { + $this->loadedMetadata[$this->normalizeClassName($className)] = $class; + } + + /** + * Gets an array of parent classes for the given entity class. + * + * @psalm-param class-string $name + * + * @return string[] + * @psalm-return list + */ + protected function getParentClasses(string $name) + { + // Collect parent classes, ignoring transient (not-mapped) classes. + $parentClasses = []; + + foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) { + if ($this->getDriver()->isTransient($parentClass)) { + continue; + } + + $parentClasses[] = $parentClass; + } + + return $parentClasses; + } + + /** + * Loads the metadata of the class in question and all it's ancestors whose metadata + * is still not loaded. + * + * Important: The class $name does not necessarily exist at this point here. + * Scenarios in a code-generation setup might have access to XML/YAML + * Mapping files without the actual PHP code existing here. That is why the + * {@see \Doctrine\Persistence\Mapping\ReflectionService} interface + * should be used for reflection. + * + * @param string $name The name of the class for which the metadata should get loaded. + * @psalm-param class-string $name + * + * @return array + * @psalm-return list + */ + protected function loadMetadata(string $name) + { + if (! $this->initialized) { + $this->initialize(); + } + + $loaded = []; + + $parentClasses = $this->getParentClasses($name); + $parentClasses[] = $name; + + // Move down the hierarchy of parent classes, starting from the topmost class + $parent = null; + $rootEntityFound = false; + $visited = []; + $reflService = $this->getReflectionService(); + + foreach ($parentClasses as $className) { + if (isset($this->loadedMetadata[$className])) { + $parent = $this->loadedMetadata[$className]; + + if ($this->isEntity($parent)) { + $rootEntityFound = true; + + array_unshift($visited, $className); + } + + continue; + } + + $class = $this->newClassMetadataInstance($className); + $this->initializeReflection($class, $reflService); + + $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited); + + $this->loadedMetadata[$className] = $class; + + $parent = $class; + + if ($this->isEntity($class)) { + $rootEntityFound = true; + + array_unshift($visited, $className); + } + + $this->wakeupReflection($class, $reflService); + + $loaded[] = $className; + } + + return $loaded; + } + + /** + * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions + * + * Override this method to implement a fallback strategy for failed metadata loading + * + * @return ClassMetadata|null + * @psalm-return CMTemplate|null + */ + protected function onNotFoundMetadata(string $className) + { + return null; + } + + /** + * Actually loads the metadata from the underlying metadata. + * + * @param bool $rootEntityFound True when there is another entity (non-mapped superclass) class above the current class in the PHP class hierarchy. + * @param list $nonSuperclassParents All parent class names that are not marked as mapped superclasses, with the direct parent class being the first and the root entity class the last element. + * @psalm-param CMTemplate $class + * @psalm-param CMTemplate|null $parent + * + * @return void + */ + abstract protected function doLoadMetadata( + ClassMetadata $class, + ?ClassMetadata $parent, + bool $rootEntityFound, + array $nonSuperclassParents + ); + + /** + * Creates a new ClassMetadata instance for the given class name. + * + * @psalm-param class-string $className + * + * @return ClassMetadata + * @psalm-return CMTemplate + * + * @template T of object + */ + abstract protected function newClassMetadataInstance(string $className); + + /** + * {@inheritDoc} + */ + public function isTransient(string $className) + { + if (! $this->initialized) { + $this->initialize(); + } + + if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) { + return false; + } + + if (! class_exists($className, false) && strpos($className, ':') !== false) { + throw MappingException::nonExistingClass($className); + } + + /** @psalm-var class-string $className */ + return $this->getDriver()->isTransient($className); + } + + /** + * Sets the reflectionService. + * + * @return void + */ + public function setReflectionService(ReflectionService $reflectionService) + { + $this->reflectionService = $reflectionService; + } + + /** + * Gets the reflection service associated with this metadata factory. + * + * @return ReflectionService + */ + public function getReflectionService() + { + if ($this->reflectionService === null) { + $this->reflectionService = new RuntimeReflectionService(); + } + + return $this->reflectionService; + } + + protected function getCacheKey(string $realClassName): string + { + return str_replace('\\', '__', $realClassName) . $this->cacheSalt; + } + + /** + * Gets the real class name of a class name that could be a proxy. + * + * @psalm-param class-string>|class-string $class + * + * @psalm-return class-string + * + * @template T of object + */ + private function getRealClass(string $class): string + { + if ($this->proxyClassNameResolver === null) { + $this->createDefaultProxyClassNameResolver(); + } + + assert($this->proxyClassNameResolver !== null); + + return $this->proxyClassNameResolver->resolveClassName($class); + } + + private function createDefaultProxyClassNameResolver(): void + { + $this->proxyClassNameResolver = new class implements ProxyClassNameResolver { + /** + * @psalm-param class-string>|class-string $className + * + * @psalm-return class-string + * + * @template T of object + */ + public function resolveClassName(string $className): string + { + $pos = strrpos($className, '\\' . Proxy::MARKER . '\\'); + + if ($pos === false) { + /** @psalm-var class-string */ + return $className; + } + + /** @psalm-var class-string */ + return substr($className, $pos + Proxy::MARKER_LENGTH + 2); + } + }; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/ClassMetadata.php b/vendor/doctrine/persistence/src/Persistence/Mapping/ClassMetadata.php new file mode 100644 index 0000000..f407ba3 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/ClassMetadata.php @@ -0,0 +1,141 @@ + + */ + public function getName(); + + /** + * Gets the mapped identifier field name. + * + * The returned structure is an array of the identifier field names. + * + * @return array + * @psalm-return list + */ + public function getIdentifier(); + + /** + * Gets the ReflectionClass instance for this mapped class. + * + * @return ReflectionClass + */ + public function getReflectionClass(); + + /** + * Checks if the given field name is a mapped identifier for this class. + * + * @return bool + */ + public function isIdentifier(string $fieldName); + + /** + * Checks if the given field is a mapped property for this class. + * + * @return bool + */ + public function hasField(string $fieldName); + + /** + * Checks if the given field is a mapped association for this class. + * + * @return bool + */ + public function hasAssociation(string $fieldName); + + /** + * Checks if the given field is a mapped single valued association for this class. + * + * @return bool + */ + public function isSingleValuedAssociation(string $fieldName); + + /** + * Checks if the given field is a mapped collection valued association for this class. + * + * @return bool + */ + public function isCollectionValuedAssociation(string $fieldName); + + /** + * A numerically indexed list of field names of this persistent class. + * + * This array includes identifier fields if present on this class. + * + * @return array + */ + public function getFieldNames(); + + /** + * Returns an array of identifier field names numerically indexed. + * + * @return array + */ + public function getIdentifierFieldNames(); + + /** + * Returns a numerically indexed list of association names of this persistent class. + * + * This array includes identifier associations if present on this class. + * + * @return array + */ + public function getAssociationNames(); + + /** + * Returns a type name of this field. + * + * This type names can be implementation specific but should at least include the php types: + * integer, string, boolean, float/double, datetime. + * + * @return string|null + */ + public function getTypeOfField(string $fieldName); + + /** + * Returns the target class name of the given association. + * + * @return string|null + * @psalm-return class-string|null + */ + public function getAssociationTargetClass(string $assocName); + + /** + * Checks if the association is the inverse side of a bidirectional association. + * + * @return bool + */ + public function isAssociationInverseSide(string $assocName); + + /** + * Returns the target field of the owning side of the association. + * + * @return string + */ + public function getAssociationMappedByTargetField(string $assocName); + + /** + * Returns the identifier of this object as an array with field name as key. + * + * Has to return an empty array if no identifier isset. + * + * @return array + */ + public function getIdentifierValues(object $object); +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/ClassMetadataFactory.php b/vendor/doctrine/persistence/src/Persistence/Mapping/ClassMetadataFactory.php new file mode 100644 index 0000000..28b8303 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/ClassMetadataFactory.php @@ -0,0 +1,61 @@ + + */ + public function getAllMetadata(); + + /** + * Gets the class metadata descriptor for a class. + * + * @param class-string $className The name of the class. + * + * @return ClassMetadata + * @psalm-return T + */ + public function getMetadataFor(string $className); + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param class-string $className + * + * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor(string $className); + + /** + * Sets the metadata descriptor for a specific class. + * + * @param class-string $className + * @psalm-param T $class + * + * @return void + */ + public function setMetadataFor(string $className, ClassMetadata $class); + + /** + * Returns whether the class with the specified name should have its metadata loaded. + * This is only the case if it is either mapped directly or as a MappedSuperclass. + * + * @psalm-param class-string $className + * + * @return bool + */ + public function isTransient(string $className); +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/ColocatedMappingDriver.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/ColocatedMappingDriver.php new file mode 100644 index 0000000..e85ba70 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/ColocatedMappingDriver.php @@ -0,0 +1,212 @@ + + */ + protected $paths = []; + + /** + * The paths excluded from path where to look for mapping files. + * + * @var array + */ + protected $excludePaths = []; + + /** + * The file extension of mapping documents. + * + * @var string + */ + protected $fileExtension = '.php'; + + /** + * Cache for getAllClassNames(). + * + * @var array|null + * @psalm-var list|null + */ + protected $classNames; + + /** + * Appends lookup paths to metadata driver. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Append exclude lookup paths to metadata driver. + * + * @param string[] $paths + * + * @return void + */ + public function addExcludePaths(array $paths) + { + $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); + } + + /** + * Retrieve the defined metadata lookup exclude paths. + * + * @return array + */ + public function getExcludePaths() + { + return $this->excludePaths; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @return void + */ + public function setFileExtension(string $fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + * + * Returns whether the class with the specified name is transient. Only non-transient + * classes, that is entities and mapped superclasses, should have their metadata loaded. + * + * @psalm-param class-string $className + * + * @return bool + */ + abstract public function isTransient(string $className); + + /** + * Gets the names of all mapped classes known to this driver. + * + * @return string[] The names of all mapped classes known to this driver. + * @psalm-return list + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if ($this->paths === []) { + throw MappingException::pathRequiredForDriver(static::class); + } + + $classes = []; + $includedFiles = []; + + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+' . preg_quote($this->fileExtension) . '$/i', + RecursiveRegexIterator::GET_MATCH + ); + + foreach ($iterator as $file) { + $sourceFile = $file[0]; + + if (preg_match('(^phar:)i', $sourceFile) === 0) { + $sourceFile = realpath($sourceFile); + } + + foreach ($this->excludePaths as $excludePath) { + $realExcludePath = realpath($excludePath); + assert($realExcludePath !== false); + $exclude = str_replace('\\', '/', $realExcludePath); + $current = str_replace('\\', '/', $sourceFile); + + if (strpos($current, $exclude) !== false) { + continue 2; + } + } + + require_once $sourceFile; + + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new ReflectionClass($className); + + $sourceFile = $rc->getFileName(); + + if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) { + continue; + } + + $classes[] = $className; + } + + $this->classNames = $classes; + + return $classes; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/DefaultFileLocator.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/DefaultFileLocator.php new file mode 100644 index 0000000..9b00e74 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/DefaultFileLocator.php @@ -0,0 +1,175 @@ + + */ + protected $paths = []; + + /** + * The file extension of mapping documents. + * + * @var string|null + */ + protected $fileExtension; + + /** + * Initializes a new FileDriver that looks in the given path(s) for mapping + * documents and operates in the specified operating mode. + * + * @param string|array $paths One or multiple paths where mapping documents + * can be found. + * @param string|null $fileExtension The file extension of mapping documents, + * usually prefixed with a dot. + */ + public function __construct($paths, ?string $fileExtension = null) + { + $this->addPaths((array) $paths); + $this->fileExtension = $fileExtension; + } + + /** + * Appends lookup paths to metadata driver. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string|null + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string|null $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension(?string $fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile(string $className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ($this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return $path . DIRECTORY_SEPARATOR . $fileName; + } + } + + throw MappingException::mappingFileNotFound($className, $fileName); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames(string $globalBasename) + { + if ($this->paths === []) { + return []; + } + + $classes = []; + + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName === $file->getBasename() || $fileName === $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + + assert(is_string($fileName)); + /** @psalm-var class-string */ + $class = str_replace('.', '\\', $fileName); + $classes[] = $class; + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function fileExists(string $className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ($this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/FileDriver.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/FileDriver.php new file mode 100644 index 0000000..c116233 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/FileDriver.php @@ -0,0 +1,213 @@ +|null + */ + protected $classCache; + + /** @var string */ + protected $globalBasename = ''; + + /** + * Initializes a new FileDriver that looks in the given path(s) for mapping + * documents and operates in the specified operating mode. + * + * @param string|array|FileLocator $locator A FileLocator or one/multiple paths + * where mapping documents can be found. + */ + public function __construct($locator, ?string $fileExtension = null) + { + if ($locator instanceof FileLocator) { + $this->locator = $locator; + } else { + $this->locator = new DefaultFileLocator((array) $locator, $fileExtension); + } + } + + /** + * Sets the global basename. + * + * @return void + */ + public function setGlobalBasename(string $file) + { + $this->globalBasename = $file; + } + + /** + * Retrieves the global basename. + * + * @return string|null + */ + public function getGlobalBasename() + { + return $this->globalBasename; + } + + /** + * Gets the element of schema meta data for the class from the mapping file. + * This will lazily load the mapping file if it is not loaded yet. + * + * @psalm-param class-string $className + * + * @return T The element of schema meta data. + * + * @throws MappingException + */ + public function getElement(string $className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return $this->classCache[$className]; + } + + $result = $this->loadMappingFile($this->locator->findMappingFile($className)); + + if (! isset($result[$className])) { + throw MappingException::invalidMappingFile( + $className, + str_replace('\\', '.', $className) . $this->locator->getFileExtension() + ); + } + + $this->classCache[$className] = $result[$className]; + + return $result[$className]; + } + + /** + * {@inheritDoc} + */ + public function isTransient(string $className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return false; + } + + return ! $this->locator->fileExists($className); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if ($this->classCache === null) { + $this->initialize(); + } + + if ($this->classCache === []) { + return $this->locator->getAllClassNames($this->globalBasename); + } + + /** @psalm-var array> $classCache */ + $classCache = $this->classCache; + + /** @var list $keys */ + $keys = array_keys($classCache); + + return array_values(array_unique(array_merge( + $keys, + $this->locator->getAllClassNames($this->globalBasename) + ))); + } + + /** + * Loads a mapping file with the given name and returns a map + * from class/entity names to their corresponding file driver elements. + * + * @param string $file The mapping file to load. + * + * @return mixed[] + * @psalm-return array + */ + abstract protected function loadMappingFile(string $file); + + /** + * Initializes the class cache from all the global files. + * + * Using this feature adds a substantial performance hit to file drivers as + * more metadata has to be loaded into memory than might actually be + * necessary. This may not be relevant to scenarios where caching of + * metadata is in place, however hits very hard in scenarios where no + * caching is used. + * + * @return void + */ + protected function initialize() + { + $this->classCache = []; + if ($this->globalBasename === '') { + return; + } + + foreach ($this->locator->getPaths() as $path) { + $file = $path . '/' . $this->globalBasename . $this->locator->getFileExtension(); + if (! is_file($file)) { + continue; + } + + $this->classCache = array_merge( + $this->classCache, + $this->loadMappingFile($file) + ); + } + } + + /** + * Retrieves the locator used to discover mapping files by className. + * + * @return FileLocator + */ + public function getLocator() + { + return $this->locator; + } + + /** + * Sets the locator used to discover mapping files by className. + * + * @return void + */ + public function setLocator(FileLocator $locator) + { + $this->locator = $locator; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/FileLocator.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/FileLocator.php new file mode 100644 index 0000000..e57d539 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/FileLocator.php @@ -0,0 +1,52 @@ + + * @psalm-return list + */ + public function getAllClassNames(string $globalBasename); + + /** + * Checks if a file can be found for this class name. + * + * @return bool + */ + public function fileExists(string $className); + + /** + * Gets all the paths that this file locator looks for mapping files. + * + * @return array + */ + public function getPaths(); + + /** + * Gets the file extension that mapping files are suffixed with. + * + * @return string|null + */ + public function getFileExtension(); +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriver.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriver.php new file mode 100644 index 0000000..9b6f0c8 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriver.php @@ -0,0 +1,43 @@ + $className + * @psalm-param ClassMetadata $metadata + * + * @return void + * + * @template T of object + */ + public function loadMetadataForClass(string $className, ClassMetadata $metadata); + + /** + * Gets the names of all mapped classes known to this driver. + * + * @return array The names of all mapped classes known to this driver. + * @psalm-return list + */ + public function getAllClassNames(); + + /** + * Returns whether the class with the specified name should have its metadata loaded. + * This is only the case if it is either mapped as an Entity or a MappedSuperclass. + * + * @psalm-param class-string $className + * + * @return bool + */ + public function isTransient(string $className); +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriverChain.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriverChain.php new file mode 100644 index 0000000..8563dd2 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriverChain.php @@ -0,0 +1,142 @@ + */ + private $drivers = []; + + /** + * Gets the default driver. + * + * @return MappingDriver|null + */ + public function getDefaultDriver() + { + return $this->defaultDriver; + } + + /** + * Set the default driver. + * + * @return void + */ + public function setDefaultDriver(MappingDriver $driver) + { + $this->defaultDriver = $driver; + } + + /** + * Adds a nested driver. + * + * @return void + */ + public function addDriver(MappingDriver $nestedDriver, string $namespace) + { + $this->drivers[$namespace] = $nestedDriver; + } + + /** + * Gets the array of nested drivers. + * + * @return array $drivers + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass(string $className, ClassMetadata $metadata) + { + foreach ($this->drivers as $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + $driver->loadMetadataForClass($className, $metadata); + + return; + } + } + + if ($this->defaultDriver !== null) { + $this->defaultDriver->loadMetadataForClass($className, $metadata); + + return; + } + + throw MappingException::classNotFoundInNamespaces($className, array_keys($this->drivers)); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + $classNames = []; + $driverClasses = []; + + foreach ($this->drivers as $namespace => $driver) { + $oid = spl_object_hash($driver); + + if (! isset($driverClasses[$oid])) { + $driverClasses[$oid] = $driver->getAllClassNames(); + } + + foreach ($driverClasses[$oid] as $className) { + if (strpos($className, $namespace) !== 0) { + continue; + } + + $classNames[$className] = true; + } + } + + if ($this->defaultDriver !== null) { + foreach ($this->defaultDriver->getAllClassNames() as $className) { + $classNames[$className] = true; + } + } + + return array_keys($classNames); + } + + /** + * {@inheritDoc} + */ + public function isTransient(string $className) + { + foreach ($this->drivers as $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + return $driver->isTransient($className); + } + } + + if ($this->defaultDriver !== null) { + return $this->defaultDriver->isTransient($className); + } + + return true; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/PHPDriver.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/PHPDriver.php new file mode 100644 index 0000000..1c1ab9c --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/PHPDriver.php @@ -0,0 +1,49 @@ +> + */ +class PHPDriver extends FileDriver +{ + /** + * @var ClassMetadata + * @psalm-var ClassMetadata + */ + protected $metadata; + + /** @param string|array|FileLocator $locator */ + public function __construct($locator) + { + parent::__construct($locator, '.php'); + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass(string $className, ClassMetadata $metadata) + { + $this->metadata = $metadata; + + $this->loadMappingFile($this->locator->findMappingFile($className)); + } + + /** + * {@inheritDoc} + */ + protected function loadMappingFile(string $file) + { + $metadata = $this->metadata; + include $file; + + return [$metadata->getName() => $metadata]; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/StaticPHPDriver.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/StaticPHPDriver.php new file mode 100644 index 0000000..bbcff75 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/StaticPHPDriver.php @@ -0,0 +1,132 @@ + + */ + private $paths = []; + + /** + * Map of all class names. + * + * @var array + * @psalm-var list + */ + private $classNames; + + /** @param array|string $paths */ + public function __construct($paths) + { + $this->addPaths((array) $paths); + } + + /** + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass(string $className, ClassMetadata $metadata) + { + $className::loadMetadata($metadata); + } + + /** + * {@inheritDoc} + * + * @todo Same code exists in ColocatedMappingDriver, should we re-use it + * somehow or not worry about it? + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if ($this->paths === []) { + throw MappingException::pathRequiredForDriver(static::class); + } + + $classes = []; + $includedFiles = []; + + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + if ($file->getBasename('.php') === $file->getBasename()) { + continue; + } + + $sourceFile = realpath($file->getPathName()); + require_once $sourceFile; + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new ReflectionClass($className); + + $sourceFile = $rc->getFileName(); + + if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) { + continue; + } + + $classes[] = $className; + } + + $this->classNames = $classes; + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function isTransient(string $className) + { + return ! method_exists($className, 'loadMetadata'); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/SymfonyFileLocator.php b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/SymfonyFileLocator.php new file mode 100644 index 0000000..428d5fb --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/SymfonyFileLocator.php @@ -0,0 +1,265 @@ + + */ + protected $paths = []; + + /** + * A map of mapping directory path to namespace prefix used to expand class shortnames. + * + * @var array + */ + protected $prefixes = []; + + /** + * File extension that is searched for. + * + * @var string|null + */ + protected $fileExtension; + + /** + * Represents PHP namespace delimiters when looking for files + * + * @var string + */ + private $nsSeparator; + + /** + * @param array $prefixes + * @param string $nsSeparator String which would be used when converting FQCN + * to filename and vice versa. Should not be empty + */ + public function __construct( + array $prefixes, + string $fileExtension = '', + string $nsSeparator = '.' + ) { + $this->addNamespacePrefixes($prefixes); + $this->fileExtension = $fileExtension; + + if ($nsSeparator === '') { + throw new InvalidArgumentException('Namespace separator should not be empty'); + } + + $this->nsSeparator = $nsSeparator; + } + + /** + * Adds Namespace Prefixes. + * + * @param array $prefixes + * + * @return void + */ + public function addNamespacePrefixes(array $prefixes) + { + $this->prefixes = array_merge($this->prefixes, $prefixes); + $this->paths = array_merge($this->paths, array_keys($prefixes)); + } + + /** + * Gets Namespace Prefixes. + * + * @return string[] + */ + public function getNamespacePrefixes() + { + return $this->prefixes; + } + + /** + * {@inheritDoc} + */ + public function getPaths() + { + return $this->paths; + } + + /** + * {@inheritDoc} + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension(string $fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function fileExists(string $className) + { + $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; + foreach ($this->paths as $path) { + if (! isset($this->prefixes[$path])) { + // global namespace class + if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { + return true; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (strpos($className, $prefix . '\\') !== 0) { + continue; + } + + $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; + + if (is_file($filename)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames(?string $globalBasename = null) + { + if ($this->paths === []) { + return []; + } + + $classes = []; + + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName === $file->getBasename() || $fileName === $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->prefixes[$path])) { + // Calculate namespace suffix for given prefix as a relative path from basepath to file path + $nsSuffix = strtr( + substr($this->realpath($file->getPath()), strlen($this->realpath($path))), + $this->nsSeparator, + '\\' + ); + + /** @psalm-var class-string */ + $class = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName); + } else { + /** @psalm-var class-string */ + $class = str_replace($this->nsSeparator, '\\', $fileName); + } + + $classes[] = $class; + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile(string $className) + { + $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; + foreach ($this->paths as $path) { + if (! isset($this->prefixes[$path])) { + if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { + return $path . DIRECTORY_SEPARATOR . $defaultFileName; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (strpos($className, $prefix . '\\') !== 0) { + continue; + } + + $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; + if (is_file($filename)) { + return $filename; + } + } + + $pos = strrpos($className, '\\'); + assert(is_int($pos)); + + throw MappingException::mappingFileNotFound( + $className, + substr($className, $pos + 1) . $this->fileExtension + ); + } + + private function realpath(string $path): string + { + $realpath = realpath($path); + + if ($realpath === false) { + throw new RuntimeException(sprintf('Could not get realpath for %s', $path)); + } + + return $realpath; + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/MappingException.php b/vendor/doctrine/persistence/src/Persistence/Mapping/MappingException.php new file mode 100644 index 0000000..f0bc4eb --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/MappingException.php @@ -0,0 +1,88 @@ + $namespaces + * + * @return self + */ + public static function classNotFoundInNamespaces( + string $className, + array $namespaces + ) { + return new self(sprintf( + "The class '%s' was not found in the chain configured namespaces %s", + $className, + implode(', ', $namespaces) + )); + } + + /** @param class-string $driverClassName */ + public static function pathRequiredForDriver(string $driverClassName): self + { + return new self(sprintf( + 'Specifying the paths to your entities is required when using %s to retrieve all class names.', + $driverClassName + )); + } + + /** @return self */ + public static function fileMappingDriversRequireConfiguredDirectoryPath( + ?string $path = null + ) { + if ($path !== null) { + $path = '[' . $path . ']'; + } + + return new self(sprintf( + 'File mapping drivers must have a valid directory path, ' . + 'however the given path %s seems to be incorrect!', + (string) $path + )); + } + + /** @return self */ + public static function mappingFileNotFound(string $entityName, string $fileName) + { + return new self(sprintf( + "No mapping file found named '%s' for class '%s'.", + $fileName, + $entityName + )); + } + + /** @return self */ + public static function invalidMappingFile(string $entityName, string $fileName) + { + return new self(sprintf( + "Invalid mapping file '%s' for class '%s'.", + $fileName, + $entityName + )); + } + + /** @return self */ + public static function nonExistingClass(string $className) + { + return new self(sprintf("Class '%s' does not exist", $className)); + } + + /** @param class-string $className */ + public static function classIsAnonymous(string $className): self + { + return new self(sprintf('Class "%s" is anonymous', $className)); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/ProxyClassNameResolver.php b/vendor/doctrine/persistence/src/Persistence/Mapping/ProxyClassNameResolver.php new file mode 100644 index 0000000..2801d90 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/ProxyClassNameResolver.php @@ -0,0 +1,19 @@ +>|class-string $className + * + * @psalm-return class-string + * + * @template T of object + */ + public function resolveClassName(string $className): string; +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/ReflectionService.php b/vendor/doctrine/persistence/src/Persistence/Mapping/ReflectionService.php new file mode 100644 index 0000000..9484e1f --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/ReflectionService.php @@ -0,0 +1,75 @@ + $class + * + * @return ReflectionClass|null + * @psalm-return ReflectionClass|null + * + * @template T of object + */ + public function getClass(string $class); + + /** + * Returns an accessible property (setAccessible(true)) or null. + * + * @psalm-param class-string $class + * + * @return ReflectionProperty|null + */ + public function getAccessibleProperty(string $class, string $property); + + /** + * Checks if the class have a public method with the given name. + * + * @psalm-param class-string $class + * + * @return bool + */ + public function hasPublicMethod(string $class, string $method); +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/RuntimeReflectionService.php b/vendor/doctrine/persistence/src/Persistence/Mapping/RuntimeReflectionService.php new file mode 100644 index 0000000..399e057 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/RuntimeReflectionService.php @@ -0,0 +1,111 @@ +supportsTypedPropertiesWorkaround = version_compare(phpversion(), '7.4.0') >= 0; + } + + /** + * {@inheritDoc} + */ + public function getParentClasses(string $class) + { + if (! class_exists($class)) { + throw MappingException::nonExistingClass($class); + } + + $parents = class_parents($class); + + assert($parents !== false); + + return $parents; + } + + /** + * {@inheritDoc} + */ + public function getClassShortName(string $class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getShortName(); + } + + /** + * {@inheritDoc} + */ + public function getClassNamespace(string $class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getNamespaceName(); + } + + /** + * @psalm-param class-string $class + * + * @return ReflectionClass + * @psalm-return ReflectionClass + * + * @template T of object + */ + public function getClass(string $class) + { + return new ReflectionClass($class); + } + + /** + * {@inheritDoc} + */ + public function getAccessibleProperty(string $class, string $property) + { + $reflectionProperty = new RuntimeReflectionProperty($class, $property); + + if ($this->supportsTypedPropertiesWorkaround && ! array_key_exists($property, $this->getClass($class)->getDefaultProperties())) { + $reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property); + } + + $reflectionProperty->setAccessible(true); + + return $reflectionProperty; + } + + /** + * {@inheritDoc} + */ + public function hasPublicMethod(string $class, string $method) + { + try { + $reflectionMethod = new ReflectionMethod($class, $method); + } catch (ReflectionException $e) { + return false; + } + + return $reflectionMethod->isPublic(); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/StaticReflectionService.php b/vendor/doctrine/persistence/src/Persistence/Mapping/StaticReflectionService.php new file mode 100644 index 0000000..c9f2147 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/StaticReflectionService.php @@ -0,0 +1,78 @@ +find($id). + * + * @param string $className The class name of the object to find. + * @param mixed $id The identity of the object to find. + * @psalm-param class-string $className + * + * @return object|null The found object. + * @psalm-return T|null + * + * @template T of object + */ + public function find(string $className, $id); + + /** + * Tells the ObjectManager to make an instance managed and persistent. + * + * The object will be entered into the database as a result of the flush operation. + * + * NOTE: The persist operation always considers objects that are not yet known to + * this ObjectManager as NEW. Do not pass detached objects to the persist operation. + * + * @param object $object The instance to make managed and persistent. + * + * @return void + */ + public function persist(object $object); + + /** + * Removes an object instance. + * + * A removed object will be removed from the database as a result of the flush operation. + * + * @param object $object The object instance to remove. + * + * @return void + */ + public function remove(object $object); + + /** + * Clears the ObjectManager. All objects that are currently managed + * by this ObjectManager become detached. + * + * @return void + */ + public function clear(); + + /** + * Detaches an object from the ObjectManager, causing a managed object to + * become detached. Unflushed changes made to the object if any + * (including removal of the object), will not be synchronized to the database. + * Objects which previously referenced the detached object will continue to + * reference it. + * + * @param object $object The object to detach. + * + * @return void + */ + public function detach(object $object); + + /** + * Refreshes the persistent state of an object from the database, + * overriding any local changes that have not yet been persisted. + * + * @param object $object The object to refresh. + * + * @return void + */ + public function refresh(object $object); + + /** + * Flushes all changes to objects that have been queued up to now to the database. + * This effectively synchronizes the in-memory state of managed objects with the + * database. + * + * @return void + */ + public function flush(); + + /** + * Gets the repository for a class. + * + * @psalm-param class-string $className + * + * @psalm-return ObjectRepository + * + * @template T of object + */ + public function getRepository(string $className); + + /** + * Returns the ClassMetadata descriptor for a class. + * + * The class name must be the fully-qualified class name without a leading backslash + * (as it is returned by get_class($obj)). + * + * @psalm-param class-string $className + * + * @psalm-return ClassMetadata + * + * @template T of object + */ + public function getClassMetadata(string $className); + + /** + * Gets the metadata factory used to gather the metadata of classes. + * + * @psalm-return ClassMetadataFactory> + */ + public function getMetadataFactory(); + + /** + * Helper method to initialize a lazy loading proxy or persistent collection. + * + * This method is a no-op for other objects. + * + * @return void + */ + public function initializeObject(object $obj); + + /** + * Checks if the object is part of the current UnitOfWork and therefore managed. + * + * @return bool + */ + public function contains(object $object); +} diff --git a/vendor/doctrine/persistence/src/Persistence/ObjectManagerDecorator.php b/vendor/doctrine/persistence/src/Persistence/ObjectManagerDecorator.php new file mode 100644 index 0000000..8c038b8 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/ObjectManagerDecorator.php @@ -0,0 +1,92 @@ +wrapped->find($className, $id); + } + + public function persist(object $object) + { + $this->wrapped->persist($object); + } + + public function remove(object $object) + { + $this->wrapped->remove($object); + } + + public function clear(): void + { + $this->wrapped->clear(); + } + + public function detach(object $object) + { + $this->wrapped->detach($object); + } + + public function refresh(object $object) + { + $this->wrapped->refresh($object); + } + + public function flush() + { + $this->wrapped->flush(); + } + + /** + * {@inheritDoc} + */ + public function getRepository(string $className) + { + return $this->wrapped->getRepository($className); + } + + /** + * {@inheritDoc} + */ + public function getClassMetadata(string $className) + { + return $this->wrapped->getClassMetadata($className); + } + + /** @psalm-return ClassMetadataFactory> */ + public function getMetadataFactory() + { + return $this->wrapped->getMetadataFactory(); + } + + public function initializeObject(object $obj) + { + $this->wrapped->initializeObject($obj); + } + + /** + * {@inheritDoc} + */ + public function contains(object $object) + { + return $this->wrapped->contains($object); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/ObjectRepository.php b/vendor/doctrine/persistence/src/Persistence/ObjectRepository.php new file mode 100644 index 0000000..a714731 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/ObjectRepository.php @@ -0,0 +1,73 @@ + The objects. + * @psalm-return T[] + */ + public function findAll(); + + /** + * Finds objects by a set of criteria. + * + * Optionally sorting and limiting details can be passed. An implementation may throw + * an UnexpectedValueException if certain values of the sorting or limiting details are + * not supported. + * + * @param array $criteria + * @param array|null $orderBy + * @psalm-param array|null $orderBy + * + * @return array The objects. + * @psalm-return T[] + * + * @throws UnexpectedValueException + */ + public function findBy( + array $criteria, + ?array $orderBy = null, + ?int $limit = null, + ?int $offset = null + ); + + /** + * Finds a single object by a set of criteria. + * + * @param array $criteria The criteria. + * + * @return object|null The object. + * @psalm-return T|null + */ + public function findOneBy(array $criteria); + + /** + * Returns the class name of the object managed by the repository. + * + * @psalm-return class-string + */ + public function getClassName(); +} diff --git a/vendor/doctrine/persistence/src/Persistence/PropertyChangedListener.php b/vendor/doctrine/persistence/src/Persistence/PropertyChangedListener.php new file mode 100644 index 0000000..6b50707 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/PropertyChangedListener.php @@ -0,0 +1,24 @@ + */ + private $enumType; + + /** @param class-string $enumType */ + public function __construct(ReflectionProperty $originalReflectionProperty, string $enumType) + { + $this->originalReflectionProperty = $originalReflectionProperty; + $this->enumType = $enumType; + } + + /** + * {@inheritDoc} + * + * @psalm-external-mutation-free + */ + public function getDeclaringClass(): ReflectionClass + { + return $this->originalReflectionProperty->getDeclaringClass(); + } + + /** + * {@inheritDoc} + * + * @psalm-external-mutation-free + */ + public function getName(): string + { + return $this->originalReflectionProperty->getName(); + } + + /** + * {@inheritDoc} + * + * @psalm-external-mutation-free + */ + public function getType(): ?ReflectionType + { + return $this->originalReflectionProperty->getType(); + } + + /** + * {@inheritDoc} + */ + public function getAttributes(?string $name = null, int $flags = 0): array + { + return $this->originalReflectionProperty->getAttributes($name, $flags); + } + + /** + * {@inheritDoc} + * + * Converts enum instance to its value. + * + * @param object|null $object + * + * @return int|string|int[]|string[]|null + */ + #[ReturnTypeWillChange] + public function getValue($object = null) + { + if ($object === null) { + return null; + } + + $enum = $this->originalReflectionProperty->getValue($object); + + if ($enum === null) { + return null; + } + + return $this->fromEnum($enum); + } + + /** + * Converts enum value to enum instance. + * + * @param object $object + * @param mixed $value + */ + public function setValue($object, $value = null): void + { + if ($value !== null) { + $value = $this->toEnum($value); + } + + $this->originalReflectionProperty->setValue($object, $value); + } + + /** + * @param BackedEnum|BackedEnum[] $enum + * + * @return ($enum is BackedEnum ? (string|int) : (string[]|int[])) + */ + private function fromEnum($enum) + { + if (is_array($enum)) { + return array_map(static function (BackedEnum $enum) { + return $enum->value; + }, $enum); + } + + return $enum->value; + } + + /** + * @param int|string|int[]|string[]|BackedEnum|BackedEnum[] $value + * + * @return ($value is int|string|BackedEnum ? BackedEnum : BackedEnum[]) + */ + private function toEnum($value) + { + if ($value instanceof BackedEnum) { + return $value; + } + + if (is_array($value)) { + $v = reset($value); + if ($v instanceof BackedEnum) { + return $value; + } + + return array_map([$this->enumType, 'from'], $value); + } + + return $this->enumType::from($value); + } + + /** + * {@inheritDoc} + * + * @psalm-external-mutation-free + */ + public function getModifiers(): int + { + return $this->originalReflectionProperty->getModifiers(); + } + + /** + * {@inheritDoc} + * + * @psalm-external-mutation-free + */ + public function getDocComment(): string|false + { + return $this->originalReflectionProperty->getDocComment(); + } + + /** + * {@inheritDoc} + * + * @psalm-external-mutation-free + */ + public function isPrivate(): bool + { + return $this->originalReflectionProperty->isPrivate(); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php b/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php new file mode 100644 index 0000000..e2367ec --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php @@ -0,0 +1,61 @@ +getName()] ?? null : parent::getValue(); + } + + /** + * {@inheritDoc} + * + * Avoids triggering lazy loading via `__set` if the provided object + * is a {@see \Doctrine\Common\Proxy\Proxy}. + * + * @link https://bugs.php.net/bug.php?id=63463 + * + * @param object|null $object + * @param mixed $value + * + * @return void + */ + #[ReturnTypeWillChange] + public function setValue($object, $value = null) + { + if (! ($object instanceof Proxy && ! $object->__isInitialized())) { + parent::setValue($object, $value); + + return; + } + + $originalInitializer = $object->__getInitializer(); + $object->__setInitializer(null); + + parent::setValue($object, $value); + + $object->__setInitializer($originalInitializer); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimeReflectionProperty.php b/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimeReflectionProperty.php new file mode 100644 index 0000000..5f52056 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimeReflectionProperty.php @@ -0,0 +1,87 @@ +key = $this->isPrivate() ? "\0" . ltrim($class, '\\') . "\0" . $name : ($this->isProtected() ? "\0*\0" . $name : $name); + } + + /** + * {@inheritDoc} + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function getValue($object = null) + { + if ($object === null) { + return parent::getValue($object); + } + + return ((array) $object)[$this->key] ?? null; + } + + /** + * {@inheritDoc} + * + * @param object|null $object + * @param mixed $value + * + * @return void + */ + #[ReturnTypeWillChange] + public function setValue($object, $value = null) + { + if (! ($object instanceof Proxy && ! $object->__isInitialized())) { + parent::setValue($object, $value); + + return; + } + + if ($object instanceof CommonProxy) { + $originalInitializer = $object->__getInitializer(); + $object->__setInitializer(null); + + parent::setValue($object, $value); + + $object->__setInitializer($originalInitializer); + + return; + } + + if (! method_exists($object, '__setInitialized')) { + return; + } + + $object->__setInitialized(true); + + parent::setValue($object, $value); + + $object->__setInitialized(false); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultReflectionProperty.php b/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultReflectionProperty.php new file mode 100644 index 0000000..5e70620 --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultReflectionProperty.php @@ -0,0 +1,13 @@ +isInitialized($object) ? parent::getValue($object) : null; + } + + /** + * {@inheritDoc} + * + * Works around the problem with setting typed no default properties to + * NULL which is not supported, instead unset() to uninitialize. + * + * @link https://github.com/doctrine/orm/issues/7999 + * + * @param object|null $object + * + * @return void + */ + #[ReturnTypeWillChange] + public function setValue($object, $value = null) + { + if ($value === null && $this->hasType() && ! $this->getType()->allowsNull()) { + $propertyName = $this->getName(); + + $unsetter = function () use ($propertyName): void { + unset($this->$propertyName); + }; + $unsetter = $unsetter->bindTo($object, $this->getDeclaringClass()->getName()); + + assert($unsetter instanceof Closure); + + $unsetter(); + + return; + } + + parent::setValue($object, $value); + } +} diff --git a/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultRuntimePublicReflectionProperty.php b/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultRuntimePublicReflectionProperty.php new file mode 100644 index 0000000..181172f --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultRuntimePublicReflectionProperty.php @@ -0,0 +1,15 @@ + + +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. diff --git a/vendor/doctrine/sql-formatter/README.md b/vendor/doctrine/sql-formatter/README.md new file mode 100644 index 0000000..62b42e4 --- /dev/null +++ b/vendor/doctrine/sql-formatter/README.md @@ -0,0 +1,137 @@ +# SqlFormatter + +A lightweight php package for formatting sql statements. + +It can automatically indent and add line breaks in addition to syntax +highlighting. + +## History + +This package is a fork from https://github.com/jdorn/sql-formatter +Here is what the original History section says: + +> I found myself having to debug auto-generated SQL statements all the time and +> wanted some way to easily output formatted HTML without having to include a +> huge library or copy and paste into online formatters. + +> I was originally planning to extract the formatting code from PhpMyAdmin, +> but that was 10,000+ lines of code and used global variables. + +> I saw that other people had the same problem and used Stack Overflow user +> losif's answer as a starting point. http://stackoverflow.com/a/3924147 + +― @jdorn + +## Usage + +The `SqlFormatter` class has a method `format` which takes an SQL string as +input and returns a formatted block. + +Sample usage: + +```php += NOW()) ) + GROUP BY Column1 ORDER BY Column3 DESC LIMIT 5,10"; + +echo (new SqlFormatter())->format($query); +``` + +Output: + +formatted output with HTML Highlight + +When you run php under cli and instantiated `SqlFormatter` without argument, highlighted with `CliHighlighter`. + +SqlFormatter constructor takes `Highlighter` implementations. `HtmlHighlighter` etc. + + +### Formatting Only + +If you don't want syntax highlighting and only want the indentations and +line breaks, pass in a `NullHighlighter` instance as the second parameter. + +This is useful for outputting to error logs or other non-html formats. + +```php +format($query); +``` + +Output: + +``` +SELECT + count(*), + `Column1`, + `Testing`, + `Testing Three` +FROM + `Table1` +WHERE + Column1 = 'testing' + AND ( + ( + `Column2` = `Column3` + OR Column4 >= NOW() + ) + ) +GROUP BY + Column1 +ORDER BY + Column3 DESC +LIMIT + 5, 10 +``` + +### Syntax Highlighting Only + +There is a separate method `highlight` that preserves all original whitespace +and just adds syntax highlighting. + +This is useful for sql that is already well formatted and just needs to be a +little easier to read. + +```php +highlight($query); +``` + +Output: + +HTML Highlight output + +### Compress Query + +The `compress` method removes all comments and compresses whitespace. + +This is useful for outputting queries that can be copy pasted to the command +line easily. + +```sql +-- This is a comment + SELECT + /* This is another comment + On more than one line */ + Id #This is one final comment + as temp, DateCreated as Created FROM MyTable; +``` + +```php +echo (new SqlFormatter())->compress($query); +``` + +Output: + +```sql +SELECT Id as temp, DateCreated as Created FROM MyTable; +``` diff --git a/vendor/doctrine/sql-formatter/bin/sql-formatter b/vendor/doctrine/sql-formatter/bin/sql-formatter new file mode 100755 index 0000000..d3079ef --- /dev/null +++ b/vendor/doctrine/sql-formatter/bin/sql-formatter @@ -0,0 +1,30 @@ +#!/usr/bin/env php +Run this PHP script from the command line to see CLI syntax highlighting and formatting. It supports Unix pipes or command line argument style.

"; + echo "
php bin/sql-formatter \"SELECT * FROM MyTable WHERE (id>5 AND \\`name\\` LIKE \\"testing\\");\"
"; + echo "
echo \"SELECT * FROM MyTable WHERE (id>5 AND \\`name\\` LIKE \\"testing\\");\" | php bin/sql-formatter
"; + exit; +} + +if(isset($argv[1])) { + $sql = $argv[1]; +} +else { + $sql = stream_get_contents(fopen('php://stdin', 'r')); +} + +$autoloadFiles = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php' +]; + +foreach ($autoloadFiles as $autoloadFile) { + if (file_exists($autoloadFile)) { + require_once $autoloadFile; + break; + } +} + +echo (new \Doctrine\SqlFormatter\SqlFormatter())->format($sql); diff --git a/vendor/doctrine/sql-formatter/composer.json b/vendor/doctrine/sql-formatter/composer.json new file mode 100644 index 0000000..9f8b3e8 --- /dev/null +++ b/vendor/doctrine/sql-formatter/composer.json @@ -0,0 +1,41 @@ +{ + "name": "doctrine/sql-formatter", + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": ["sql", "highlight"], + "license": "MIT", + "type": "library", + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\SqlFormatter\\Tests\\": "tests" + } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "bin": ["bin/sql-formatter"] +} diff --git a/vendor/doctrine/sql-formatter/src/CliHighlighter.php b/vendor/doctrine/sql-formatter/src/CliHighlighter.php new file mode 100644 index 0000000..31f84ba --- /dev/null +++ b/vendor/doctrine/sql-formatter/src/CliHighlighter.php @@ -0,0 +1,76 @@ + */ + private array $escapeSequences; + + /** @param array $escapeSequences */ + public function __construct(array $escapeSequences = []) + { + $this->escapeSequences = $escapeSequences + [ + self::HIGHLIGHT_QUOTE => "\x1b[34;1m", + self::HIGHLIGHT_BACKTICK_QUOTE => "\x1b[35;1m", + self::HIGHLIGHT_RESERVED => "\x1b[37m", + self::HIGHLIGHT_BOUNDARY => '', + self::HIGHLIGHT_NUMBER => "\x1b[32;1m", + self::HIGHLIGHT_WORD => '', + self::HIGHLIGHT_ERROR => "\x1b[31;1;7m", + self::HIGHLIGHT_COMMENT => "\x1b[30;1m", + self::HIGHLIGHT_VARIABLE => "\x1b[36;1m", + self::HIGHLIGHT_FUNCTIONS => "\x1b[37m", + ]; + } + + public function highlightToken(int $type, string $value): string + { + if ($type === Token::TOKEN_TYPE_BOUNDARY && ($value === '(' || $value === ')')) { + return $value; + } + + $prefix = $this->prefix($type); + if ($prefix === null) { + return $value; + } + + return $prefix . $value . "\x1b[0m"; + } + + private function prefix(int $type): string|null + { + if (! isset(self::TOKEN_TYPE_TO_HIGHLIGHT[$type])) { + return null; + } + + return $this->escapeSequences[self::TOKEN_TYPE_TO_HIGHLIGHT[$type]]; + } + + public function highlightError(string $value): string + { + return sprintf( + '%s%s%s%s', + "\n", + $this->escapeSequences[self::HIGHLIGHT_ERROR], + $value, + "\x1b[0m", + ); + } + + public function highlightErrorMessage(string $value): string + { + return $this->highlightError($value); + } + + public function output(string $string): string + { + return $string . "\n"; + } +} diff --git a/vendor/doctrine/sql-formatter/src/Cursor.php b/vendor/doctrine/sql-formatter/src/Cursor.php new file mode 100644 index 0000000..5f70013 --- /dev/null +++ b/vendor/doctrine/sql-formatter/src/Cursor.php @@ -0,0 +1,50 @@ +tokens[++$this->position] ?? null) { + if ($exceptTokenType !== null && $token->isOfType($exceptTokenType)) { + continue; + } + + return $token; + } + + return null; + } + + public function previous(int|null $exceptTokenType = null): Token|null + { + while ($token = $this->tokens[--$this->position] ?? null) { + if ($exceptTokenType !== null && $token->isOfType($exceptTokenType)) { + continue; + } + + return $token; + } + + return null; + } + + public function subCursor(): self + { + $cursor = new self($this->tokens); + $cursor->position = $this->position; + + return $cursor; + } +} diff --git a/vendor/doctrine/sql-formatter/src/Highlighter.php b/vendor/doctrine/sql-formatter/src/Highlighter.php new file mode 100644 index 0000000..02c5c97 --- /dev/null +++ b/vendor/doctrine/sql-formatter/src/Highlighter.php @@ -0,0 +1,56 @@ + self::HIGHLIGHT_BOUNDARY, + Token::TOKEN_TYPE_WORD => self::HIGHLIGHT_WORD, + Token::TOKEN_TYPE_BACKTICK_QUOTE => self::HIGHLIGHT_BACKTICK_QUOTE, + Token::TOKEN_TYPE_QUOTE => self::HIGHLIGHT_QUOTE, + Token::TOKEN_TYPE_RESERVED => self::HIGHLIGHT_RESERVED, + Token::TOKEN_TYPE_RESERVED_TOPLEVEL => self::HIGHLIGHT_RESERVED, + Token::TOKEN_TYPE_RESERVED_NEWLINE => self::HIGHLIGHT_RESERVED, + Token::TOKEN_TYPE_NUMBER => self::HIGHLIGHT_NUMBER, + Token::TOKEN_TYPE_VARIABLE => self::HIGHLIGHT_VARIABLE, + Token::TOKEN_TYPE_COMMENT => self::HIGHLIGHT_COMMENT, + Token::TOKEN_TYPE_BLOCK_COMMENT => self::HIGHLIGHT_COMMENT, + ]; + + public const HIGHLIGHT_BOUNDARY = 'boundary'; + public const HIGHLIGHT_WORD = 'word'; + public const HIGHLIGHT_BACKTICK_QUOTE = 'backtickQuote'; + public const HIGHLIGHT_QUOTE = 'quote'; + public const HIGHLIGHT_RESERVED = 'reserved'; + public const HIGHLIGHT_NUMBER = 'number'; + public const HIGHLIGHT_VARIABLE = 'variable'; + public const HIGHLIGHT_COMMENT = 'comment'; + public const HIGHLIGHT_ERROR = 'error'; + + /** + * Highlights a token depending on its type. + */ + public function highlightToken(int $type, string $value): string; + + /** + * Highlights a token which causes an issue + */ + public function highlightError(string $value): string; + + /** + * Highlights an error message + */ + public function highlightErrorMessage(string $value): string; + + /** + * Helper function for building string output + * + * @param string $string The string to be quoted + * + * @return string The quoted string + */ + public function output(string $string): string; +} diff --git a/vendor/doctrine/sql-formatter/src/HtmlHighlighter.php b/vendor/doctrine/sql-formatter/src/HtmlHighlighter.php new file mode 100644 index 0000000..595e0e4 --- /dev/null +++ b/vendor/doctrine/sql-formatter/src/HtmlHighlighter.php @@ -0,0 +1,92 @@ + */ + private readonly array $htmlAttributes; + + /** + * @param array $htmlAttributes + * @param bool $usePre This flag tells us if queries need to be enclosed in
 tags
+     */
+    public function __construct(
+        array $htmlAttributes = [],
+        private readonly bool $usePre = true,
+    ) {
+        $this->htmlAttributes = $htmlAttributes + [
+            self::HIGHLIGHT_QUOTE => 'style="color: blue;"',
+            self::HIGHLIGHT_BACKTICK_QUOTE => 'style="color: purple;"',
+            self::HIGHLIGHT_RESERVED => 'style="font-weight:bold;"',
+            self::HIGHLIGHT_BOUNDARY => '',
+            self::HIGHLIGHT_NUMBER => 'style="color: green;"',
+            self::HIGHLIGHT_WORD => 'style="color: #333;"',
+            self::HIGHLIGHT_ERROR => 'style="background-color: red;"',
+            self::HIGHLIGHT_COMMENT => 'style="color: #aaa;"',
+            self::HIGHLIGHT_VARIABLE => 'style="color: orange;"',
+            self::HIGHLIGHT_PRE => 'style="color: black; background-color: white;"',
+        ];
+    }
+
+    public function highlightToken(int $type, string $value): string
+    {
+        $value = htmlentities($value, ENT_COMPAT | ENT_IGNORE, 'UTF-8');
+
+        if ($type === Token::TOKEN_TYPE_BOUNDARY && ($value === '(' || $value === ')')) {
+            return $value;
+        }
+
+        $attributes = $this->attributes($type);
+        if ($attributes === null) {
+            return $value;
+        }
+
+        return '' . $value . '';
+    }
+
+    public function attributes(int $type): string|null
+    {
+        if (! isset(self::TOKEN_TYPE_TO_HIGHLIGHT[$type])) {
+            return null;
+        }
+
+        return $this->htmlAttributes[self::TOKEN_TYPE_TO_HIGHLIGHT[$type]];
+    }
+
+    public function highlightError(string $value): string
+    {
+        return sprintf(
+            '%s%s',
+            "\n",
+            $this->htmlAttributes[self::HIGHLIGHT_ERROR],
+            $value,
+        );
+    }
+
+    public function highlightErrorMessage(string $value): string
+    {
+        return $this->highlightError($value);
+    }
+
+    public function output(string $string): string
+    {
+        $string = trim($string);
+        if (! $this->usePre) {
+            return $string;
+        }
+
+        return '
htmlAttributes[self::HIGHLIGHT_PRE] . '>' . $string . '
'; + } +} diff --git a/vendor/doctrine/sql-formatter/src/NullHighlighter.php b/vendor/doctrine/sql-formatter/src/NullHighlighter.php new file mode 100644 index 0000000..b62c998 --- /dev/null +++ b/vendor/doctrine/sql-formatter/src/NullHighlighter.php @@ -0,0 +1,28 @@ +tokenizer = new Tokenizer(); + $this->highlighter = $highlighter ?? (PHP_SAPI === 'cli' ? new CliHighlighter() : new HtmlHighlighter()); + } + + /** + * Format the whitespace in a SQL string to make it easier to read. + * + * @param string $string The SQL string + * + * @return string The SQL string with HTML styles and formatting wrapped in a
 tag
+     */
+    public function format(string $string, string $indentString = '  '): string
+    {
+        // This variable will be populated with formatted html
+        $return = '';
+
+        // Use an actual tab while formatting and then switch out with $indentString at the end
+        $tab = "\t";
+
+        $indentLevel           = 0;
+        $newline               = false;
+        $inlineParentheses     = false;
+        $increaseSpecialIndent = false;
+        $increaseBlockIndent   = false;
+        $indentTypes           = [];
+        $addedNewline          = false;
+        $inlineCount           = 0;
+        $inlineIndented        = false;
+        $clauseLimit           = false;
+
+        // Tokenize String
+        $cursor = $this->tokenizer->tokenize($string);
+
+        // Format token by token
+        while ($token = $cursor->next(Token::TOKEN_TYPE_WHITESPACE)) {
+            $highlighted = $this->highlighter->highlightToken(
+                $token->type(),
+                $token->value(),
+            );
+
+            // If we are increasing the special indent level now
+            if ($increaseSpecialIndent) {
+                $indentLevel++;
+                $increaseSpecialIndent = false;
+                array_unshift($indentTypes, 'special');
+            }
+
+            // If we are increasing the block indent level now
+            if ($increaseBlockIndent) {
+                $indentLevel++;
+                $increaseBlockIndent = false;
+                array_unshift($indentTypes, 'block');
+            }
+
+            // If we need a new line before the token
+            if ($newline) {
+                $return       = rtrim($return, ' ');
+                $return      .= "\n" . str_repeat($tab, $indentLevel);
+                $newline      = false;
+                $addedNewline = true;
+            } else {
+                $addedNewline = false;
+            }
+
+            // Display comments directly where they appear in the source
+            if ($token->isOfType(Token::TOKEN_TYPE_COMMENT, Token::TOKEN_TYPE_BLOCK_COMMENT)) {
+                if ($token->isOfType(Token::TOKEN_TYPE_BLOCK_COMMENT)) {
+                    $indent      = str_repeat($tab, $indentLevel);
+                    $return      = rtrim($return, " \t");
+                    $return     .= "\n" . $indent;
+                    $highlighted = str_replace("\n", "\n" . $indent, $highlighted);
+                }
+
+                $return .= $highlighted;
+                $newline = true;
+                continue;
+            }
+
+            if ($inlineParentheses) {
+                // End of inline parentheses
+                if ($token->value() === ')') {
+                    $return = rtrim($return, ' ');
+
+                    if ($inlineIndented) {
+                        array_shift($indentTypes);
+                        $indentLevel--;
+                        $return  = rtrim($return, ' ');
+                        $return .= "\n" . str_repeat($tab, $indentLevel);
+                    }
+
+                    $inlineParentheses = false;
+
+                    $return .= $highlighted . ' ';
+                    continue;
+                }
+
+                if ($token->value() === ',') {
+                    if ($inlineCount >= 30) {
+                        $inlineCount = 0;
+                        $newline     = true;
+                    }
+                }
+
+                $inlineCount += strlen($token->value());
+            }
+
+            // Opening parentheses increase the block indent level and start a new line
+            if ($token->value() === '(') {
+                // First check if this should be an inline parentheses block
+                // Examples are "NOW()", "COUNT(*)", "int(10)", key(`somecolumn`), DECIMAL(7,2)
+                // Allow up to 3 non-whitespace tokens inside inline parentheses
+                $length    = 0;
+                $subCursor = $cursor->subCursor();
+                for ($j = 1; $j <= 250; $j++) {
+                    // Reached end of string
+                    $next = $subCursor->next(Token::TOKEN_TYPE_WHITESPACE);
+                    if (! $next) {
+                        break;
+                    }
+
+                    // Reached closing parentheses, able to inline it
+                    if ($next->value() === ')') {
+                        $inlineParentheses = true;
+                        $inlineCount       = 0;
+                        $inlineIndented    = false;
+                        break;
+                    }
+
+                    // Reached an invalid token for inline parentheses
+                    if ($next->value() === ';' || $next->value() === '(') {
+                        break;
+                    }
+
+                    // Reached an invalid token type for inline parentheses
+                    if (
+                        $next->isOfType(
+                            Token::TOKEN_TYPE_RESERVED_TOPLEVEL,
+                            Token::TOKEN_TYPE_RESERVED_NEWLINE,
+                            Token::TOKEN_TYPE_COMMENT,
+                            Token::TOKEN_TYPE_BLOCK_COMMENT,
+                        )
+                    ) {
+                        break;
+                    }
+
+                    $length += strlen($next->value());
+                }
+
+                if ($inlineParentheses && $length > 30) {
+                    $increaseBlockIndent = true;
+                    $inlineIndented      = true;
+                    $newline             = true;
+                }
+
+                // Take out the preceding space unless there was whitespace there in the original query
+                $prevToken = $cursor->subCursor()->previous();
+                if ($prevToken && ! $prevToken->isOfType(Token::TOKEN_TYPE_WHITESPACE)) {
+                    $return = rtrim($return, ' ');
+                }
+
+                if (! $inlineParentheses) {
+                    $increaseBlockIndent = true;
+                    // Add a newline after the parentheses
+                    $newline = true;
+                }
+            } elseif ($token->value() === ')') {
+                // Closing parentheses decrease the block indent level
+                // Remove whitespace before the closing parentheses
+                $return = rtrim($return, ' ');
+
+                $indentLevel--;
+
+                // Reset indent level
+                while ($j = array_shift($indentTypes)) {
+                    if ($j !== 'special') {
+                        break;
+                    }
+
+                    $indentLevel--;
+                }
+
+                if ($indentLevel < 0) {
+                    // This is an error
+                    $indentLevel = 0;
+
+                    $return .= $this->highlighter->highlightError($token->value());
+                    continue;
+                }
+
+                // Add a newline before the closing parentheses (if not already added)
+                if (! $addedNewline) {
+                    $return .= "\n" . str_repeat($tab, $indentLevel);
+                }
+            } elseif ($token->isOfType(Token::TOKEN_TYPE_RESERVED_TOPLEVEL)) {
+                // Top level reserved words start a new line and increase the special indent level
+                $increaseSpecialIndent = true;
+
+                // If the last indent type was 'special', decrease the special indent for this round
+                reset($indentTypes);
+                if (current($indentTypes) === 'special') {
+                    $indentLevel--;
+                    array_shift($indentTypes);
+                }
+
+                // Add a newline after the top level reserved word
+                $newline = true;
+                // Add a newline before the top level reserved word (if not already added)
+                if (! $addedNewline) {
+                    $return  = rtrim($return, ' ');
+                    $return .= "\n" . str_repeat($tab, $indentLevel);
+                } else {
+                    // If we already added a newline, redo the indentation since it may be different now
+                    $return = rtrim($return, $tab) . str_repeat($tab, $indentLevel);
+                }
+
+                if ($token->hasExtraWhitespace()) {
+                    $highlighted = preg_replace('/\s+/', ' ', $highlighted);
+                }
+
+                //if SQL 'LIMIT' clause, start variable to reset newline
+                if ($token->value() === 'LIMIT' && ! $inlineParentheses) {
+                    $clauseLimit = true;
+                }
+            } elseif ($token->value() === ';') {
+                // If the last indent type was 'special', decrease the special indent for this round
+                reset($indentTypes);
+                if (current($indentTypes) === 'special') {
+                    $indentLevel--;
+                    array_shift($indentTypes);
+                }
+
+                $newline = true;
+            } elseif (
+                $clauseLimit &&
+                $token->value() !== ',' &&
+                ! $token->isOfType(Token::TOKEN_TYPE_NUMBER, Token::TOKEN_TYPE_WHITESPACE)
+            ) {
+                // Checks if we are out of the limit clause
+                $clauseLimit = false;
+            } elseif ($token->value() === ',' && ! $inlineParentheses) {
+                // Commas start a new line (unless within inline parentheses or SQL 'LIMIT' clause)
+                //If the previous TOKEN_VALUE is 'LIMIT', resets new line
+                if ($clauseLimit === true) {
+                    $newline     = false;
+                    $clauseLimit = false;
+                } else {
+                    // All other cases of commas
+                    $newline = true;
+                }
+            } elseif ($token->isOfType(Token::TOKEN_TYPE_RESERVED_NEWLINE)) {
+            // Newline reserved words start a new line
+                // Add a newline before the reserved word (if not already added)
+                if (! $addedNewline) {
+                    $return  = rtrim($return, ' ');
+                    $return .= "\n" . str_repeat($tab, $indentLevel);
+                }
+
+                if ($token->hasExtraWhitespace()) {
+                    $highlighted = preg_replace('/\s+/', ' ', $highlighted);
+                }
+            } elseif ($token->isOfType(Token::TOKEN_TYPE_BOUNDARY)) {
+                // Multiple boundary characters in a row should not have spaces between them (not including parentheses)
+                $prevNotWhitespaceToken = $cursor->subCursor()->previous(Token::TOKEN_TYPE_WHITESPACE);
+                if ($prevNotWhitespaceToken && $prevNotWhitespaceToken->isOfType(Token::TOKEN_TYPE_BOUNDARY)) {
+                    $prevToken = $cursor->subCursor()->previous();
+                    if ($prevToken && ! $prevToken->isOfType(Token::TOKEN_TYPE_WHITESPACE)) {
+                        $return = rtrim($return, ' ');
+                    }
+                }
+            }
+
+            // If the token shouldn't have a space before it
+            if (
+                $token->value() === '.' ||
+                $token->value() === ',' ||
+                $token->value() === ';'
+            ) {
+                $return = rtrim($return, ' ');
+            }
+
+            $return .= $highlighted . ' ';
+
+            // If the token shouldn't have a space after it
+            if ($token->value() === '(' || $token->value() === '.') {
+                $return = rtrim($return, ' ');
+            }
+
+            // If this is the "-" of a negative number, it shouldn't have a space after it
+            if ($token->value() !== '-') {
+                continue;
+            }
+
+            $nextNotWhitespace = $cursor->subCursor()->next(Token::TOKEN_TYPE_WHITESPACE);
+            if (! $nextNotWhitespace || ! $nextNotWhitespace->isOfType(Token::TOKEN_TYPE_NUMBER)) {
+                continue;
+            }
+
+            $prev = $cursor->subCursor()->previous(Token::TOKEN_TYPE_WHITESPACE);
+            if (! $prev) {
+                continue;
+            }
+
+            if (
+                $prev->isOfType(
+                    Token::TOKEN_TYPE_QUOTE,
+                    Token::TOKEN_TYPE_BACKTICK_QUOTE,
+                    Token::TOKEN_TYPE_WORD,
+                    Token::TOKEN_TYPE_NUMBER,
+                )
+            ) {
+                continue;
+            }
+
+            $return = rtrim($return, ' ');
+        }
+
+        // If there are unmatched parentheses
+        if (array_search('block', $indentTypes) !== false) {
+            $return  = rtrim($return, ' ');
+            $return .= $this->highlighter->highlightErrorMessage(
+                'WARNING: unclosed parentheses or section',
+            );
+        }
+
+        // Replace tab characters with the configuration tab character
+        $return = trim(str_replace("\t", $indentString, $return));
+
+        return $this->highlighter->output($return);
+    }
+
+    /**
+     * Add syntax highlighting to a SQL string
+     *
+     * @param string $string The SQL string
+     *
+     * @return string The SQL string with HTML styles applied
+     */
+    public function highlight(string $string): string
+    {
+        $cursor = $this->tokenizer->tokenize($string);
+
+        $return = '';
+
+        while ($token = $cursor->next()) {
+            $return .= $this->highlighter->highlightToken(
+                $token->type(),
+                $token->value(),
+            );
+        }
+
+        return $this->highlighter->output($return);
+    }
+
+    /**
+     * Compress a query by collapsing white space and removing comments
+     *
+     * @param string $string The SQL string
+     *
+     * @return string The SQL string without comments
+     */
+    public function compress(string $string): string
+    {
+        $result = '';
+        $cursor = $this->tokenizer->tokenize($string);
+
+        $whitespace = true;
+        while ($token = $cursor->next()) {
+            // Skip comment tokens
+            if ($token->isOfType(Token::TOKEN_TYPE_COMMENT, Token::TOKEN_TYPE_BLOCK_COMMENT)) {
+                continue;
+            }
+
+            // Remove extra whitespace in reserved words (e.g "OUTER     JOIN" becomes "OUTER JOIN")
+
+            if (
+                $token->isOfType(
+                    Token::TOKEN_TYPE_RESERVED,
+                    Token::TOKEN_TYPE_RESERVED_NEWLINE,
+                    Token::TOKEN_TYPE_RESERVED_TOPLEVEL,
+                )
+            ) {
+                $newValue = preg_replace('/\s+/', ' ', $token->value());
+                assert($newValue !== null);
+                $token = $token->withValue($newValue);
+            }
+
+            if ($token->isOfType(Token::TOKEN_TYPE_WHITESPACE)) {
+                // If the last token was whitespace, don't add another one
+                if ($whitespace) {
+                    continue;
+                }
+
+                $whitespace = true;
+                // Convert all whitespace to a single space
+                $token = $token->withValue(' ');
+            } else {
+                $whitespace = false;
+            }
+
+            $result .= $token->value();
+        }
+
+        return rtrim($result);
+    }
+}
diff --git a/vendor/doctrine/sql-formatter/src/Token.php b/vendor/doctrine/sql-formatter/src/Token.php
new file mode 100644
index 0000000..0d13dc6
--- /dev/null
+++ b/vendor/doctrine/sql-formatter/src/Token.php
@@ -0,0 +1,63 @@
+value;
+    }
+
+    public function type(): int
+    {
+        return $this->type;
+    }
+
+    public function isOfType(int ...$types): bool
+    {
+        return in_array($this->type, $types, true);
+    }
+
+    public function hasExtraWhitespace(): bool
+    {
+        return str_contains($this->value(), ' ') ||
+            str_contains($this->value(), "\n") ||
+            str_contains($this->value(), "\t");
+    }
+
+    public function withValue(string $value): self
+    {
+        return new self($this->type(), $value);
+    }
+}
diff --git a/vendor/doctrine/sql-formatter/src/Tokenizer.php b/vendor/doctrine/sql-formatter/src/Tokenizer.php
new file mode 100644
index 0000000..d0fdf74
--- /dev/null
+++ b/vendor/doctrine/sql-formatter/src/Tokenizer.php
@@ -0,0 +1,1027 @@
+
+     */
+    private array $reserved = [
+        'ACCESSIBLE',
+        'ACTION',
+        'ADD',
+        'AFTER',
+        'AGAINST',
+        'AGGREGATE',
+        'ALGORITHM',
+        'ALL',
+        'ALTER',
+        'ANALYSE',
+        'ANALYZE',
+        'AND',
+        'AS',
+        'ASC',
+        'AUTOCOMMIT',
+        'AUTO_INCREMENT',
+        'BACKUP',
+        'BEGIN',
+        'BETWEEN',
+        'BIGINT',
+        'BINARY',
+        'BINLOG',
+        'BLOB',
+        'BOTH',
+        'BY',
+        'CASCADE',
+        'CASE',
+        'CHANGE',
+        'CHANGED',
+        'CHAR',
+        'CHARACTER',
+        'CHARSET',
+        'CHECK',
+        'CHECKSUM',
+        'COLLATE',
+        'COLLATION',
+        'COLUMN',
+        'COLUMNS',
+        'COMMENT',
+        'COMMIT',
+        'COMMITTED',
+        'COMPRESSED',
+        'CONCURRENT',
+        'CONSTRAINT',
+        'CONTAINS',
+        'CONVERT',
+        'CREATE',
+        'CROSS',
+        'CURRENT',
+        'CURRENT_TIMESTAMP',
+        'DATABASE',
+        'DATABASES',
+        'DAY',
+        'DAY_HOUR',
+        'DAY_MINUTE',
+        'DAY_SECOND',
+        'DECIMAL',
+        'DEFAULT',
+        'DEFINER',
+        'DELAYED',
+        'DELETE',
+        'DESC',
+        'DESCRIBE',
+        'DETERMINISTIC',
+        'DISTINCT',
+        'DISTINCTROW',
+        'DIV',
+        'DO',
+        'DOUBLE',
+        'DROP',
+        'DUMPFILE',
+        'DUPLICATE',
+        'DYNAMIC',
+        'ELSE',
+        'ENCLOSED',
+        'END',
+        'ENGINE',
+        'ENGINES',
+        'ENGINE_TYPE',
+        'ESCAPE',
+        'ESCAPED',
+        'EVENTS',
+        'EXCEPT',
+        'EXCLUDE',
+        'EXEC',
+        'EXECUTE',
+        'EXISTS',
+        'EXPLAIN',
+        'EXTENDED',
+        'FALSE',
+        'FAST',
+        'FIELDS',
+        'FILE',
+        'FILTER',
+        'FIRST',
+        'FIXED',
+        'FLOAT',
+        'FLOAT4',
+        'FLOAT8',
+        'FLUSH',
+        'FOLLOWING',
+        'FOR',
+        'FORCE',
+        'FOREIGN',
+        'FROM',
+        'FULL',
+        'FULLTEXT',
+        'FUNCTION',
+        'GLOBAL',
+        'GRANT',
+        'GRANTS',
+        'GROUP',
+        'GROUPS',
+        'HAVING',
+        'HEAP',
+        'HIGH_PRIORITY',
+        'HOSTS',
+        'HOUR',
+        'HOUR_MINUTE',
+        'HOUR_SECOND',
+        'IDENTIFIED',
+        'IF',
+        'IFNULL',
+        'IGNORE',
+        'IN',
+        'INDEX',
+        'INDEXES',
+        'INFILE',
+        'INNER',
+        'INSERT',
+        'INSERT_ID',
+        'INSERT_METHOD',
+        'INT',
+        'INT1',
+        'INT2',
+        'INT3',
+        'INT4',
+        'INT8',
+        'INTEGER',
+        'INTERSECT',
+        'INTERVAL',
+        'INTO',
+        'INVOKER',
+        'IS',
+        'ISOLATION',
+        'JOIN',
+        'KEY',
+        'KEYS',
+        'KILL',
+        'LAST_INSERT_ID',
+        'LEADING',
+        'LEFT',
+        'LEVEL',
+        'LIKE',
+        'LIMIT',
+        'LINEAR',
+        'LINES',
+        'LOAD',
+        'LOCAL',
+        'LOCK',
+        'LOCKS',
+        'LOGS',
+        'LONG',
+        'LONGBLOB',
+        'LONGTEXT',
+        'LOW_PRIORITY',
+        'MARIA',
+        'MASTER',
+        'MASTER_CONNECT_RETRY',
+        'MASTER_HOST',
+        'MASTER_LOG_FILE',
+        'MATCH',
+        'MAX_CONNECTIONS_PER_HOUR',
+        'MAX_QUERIES_PER_HOUR',
+        'MAX_ROWS',
+        'MAX_UPDATES_PER_HOUR',
+        'MAX_USER_CONNECTIONS',
+        'MEDIUM',
+        'MEDIUMBLOB',
+        'MEDIUMINT',
+        'MEDIUMTEXT',
+        'MERGE',
+        'MINUTE',
+        'MINUTE_SECOND',
+        'MIN_ROWS',
+        'MODE',
+        'MODIFY',
+        'MONTH',
+        'MRG_MYISAM',
+        'MYISAM',
+        'NAMES',
+        'NATURAL',
+        'NOT',
+        'NULL',
+        'NUMERIC',
+        'OFFSET',
+        'ON',
+        'OPEN',
+        'OPTIMIZE',
+        'OPTION',
+        'OPTIONALLY',
+        'OR',
+        'ORDER',
+        'OUTER',
+        'OUTFILE',
+        'OVER',
+        'PACK_KEYS',
+        'PAGE',
+        'PARTIAL',
+        'PARTITION',
+        'PARTITIONS',
+        'PASSWORD',
+        'PRECEDING',
+        'PRIMARY',
+        'PRIVILEGES',
+        'PROCEDURE',
+        'PROCESS',
+        'PROCESSLIST',
+        'PURGE',
+        'QUICK',
+        'RAID0',
+        'RAID_CHUNKS',
+        'RAID_CHUNKSIZE',
+        'RAID_TYPE',
+        'RANGE',
+        'READ',
+        'READ_ONLY',
+        'READ_WRITE',
+        'REAL',
+        'RECURSIVE',
+        'REFERENCES',
+        'REGEXP',
+        'RELOAD',
+        'RENAME',
+        'REPAIR',
+        'REPEATABLE',
+        'REPLACE',
+        'REPLICATION',
+        'RESET',
+        'RESTORE',
+        'RESTRICT',
+        'RETURN',
+        'RETURNS',
+        'REVOKE',
+        'RIGHT',
+        'RLIKE',
+        'ROLLBACK',
+        'ROW',
+        'ROWS',
+        'ROW_FORMAT',
+        'SECOND',
+        'SECURITY',
+        'SELECT',
+        'SEPARATOR',
+        'SERIALIZABLE',
+        'SESSION',
+        'SET',
+        'SHARE',
+        'SHOW',
+        'SHUTDOWN',
+        'SLAVE',
+        'SMALLINT',
+        'SONAME',
+        'SOUNDS',
+        'SQL',
+        'SQL_AUTO_IS_NULL',
+        'SQL_BIG_RESULT',
+        'SQL_BIG_SELECTS',
+        'SQL_BIG_TABLES',
+        'SQL_BUFFER_RESULT',
+        'SQL_CACHE',
+        'SQL_CALC_FOUND_ROWS',
+        'SQL_LOG_BIN',
+        'SQL_LOG_OFF',
+        'SQL_LOG_UPDATE',
+        'SQL_LOW_PRIORITY_UPDATES',
+        'SQL_MAX_JOIN_SIZE',
+        'SQL_NO_CACHE',
+        'SQL_QUOTE_SHOW_CREATE',
+        'SQL_SAFE_UPDATES',
+        'SQL_SELECT_LIMIT',
+        'SQL_SLAVE_SKIP_COUNTER',
+        'SQL_SMALL_RESULT',
+        'SQL_WARNINGS',
+        'START',
+        'STARTING',
+        'STATUS',
+        'STOP',
+        'STORAGE',
+        'STRAIGHT_JOIN',
+        'STRING',
+        'STRIPED',
+        'SUPER',
+        'TABLE',
+        'TABLES',
+        'TEMPORARY',
+        'TERMINATED',
+        'THEN',
+        'TIES',
+        'TINYBLOB',
+        'TINYINT',
+        'TINYTEXT',
+        'TO',
+        'TRAILING',
+        'TRANSACTIONAL',
+        'TRUE',
+        'TRUNCATE',
+        'TYPE',
+        'TYPES',
+        'UNBOUNDED',
+        'UNCOMMITTED',
+        'UNION',
+        'UNIQUE',
+        'UNLOCK',
+        'UNSIGNED',
+        'UPDATE',
+        'USAGE',
+        'USE',
+        'USING',
+        'VALUES',
+        'VARBINARY',
+        'VARCHAR',
+        'VARCHARACTER',
+        'VARIABLES',
+        'VIEW',
+        'WHEN',
+        'WHERE',
+        'WINDOW',
+        'WITH',
+        'WORK',
+        'WRITE',
+        'XOR',
+        'YEAR_MONTH',
+    ];
+
+    /**
+     * For SQL formatting
+     * These keywords will all be on their own line
+     *
+     * @var list
+     */
+    private array $reservedToplevel = [
+        'ADD',
+        'ALTER TABLE',
+        'CHANGE',
+        'DELETE FROM',
+        'DROP',
+        'EXCEPT',
+        'FROM',
+        'GROUP BY',
+        'GROUPS',
+        'HAVING',
+        'INTERSECT',
+        'LIMIT',
+        'MODIFY',
+        'ORDER BY',
+        'PARTITION BY',
+        'RANGE',
+        'ROWS',
+        'SELECT',
+        'SET',
+        'UNION',
+        'UNION ALL',
+        'UPDATE',
+        'VALUES',
+        'WHERE',
+        'WINDOW',
+        'WITH',
+    ];
+
+    /** @var list */
+    private array $reservedNewline = [
+        'AND',
+        'EXCLUDE',
+        'INNER JOIN',
+        'JOIN',
+        'LEFT JOIN',
+        'LEFT OUTER JOIN',
+        'OR',
+        'OUTER JOIN',
+        'RIGHT JOIN',
+        'RIGHT OUTER JOIN',
+        'STRAIGHT_JOIN',
+        'XOR',
+    ];
+
+    /** @var list */
+    private array $functions = [
+        'ABS',
+        'ACOS',
+        'ADDDATE',
+        'ADDTIME',
+        'AES_DECRYPT',
+        'AES_ENCRYPT',
+        'APPROX_COUNT_DISTINCT',
+        'AREA',
+        'ASBINARY',
+        'ASCII',
+        'ASIN',
+        'ASTEXT',
+        'ATAN',
+        'ATAN2',
+        'AVG',
+        'BDMPOLYFROMTEXT',
+        'BDMPOLYFROMWKB',
+        'BDPOLYFROMTEXT',
+        'BDPOLYFROMWKB',
+        'BENCHMARK',
+        'BIN',
+        'BIT_AND',
+        'BIT_COUNT',
+        'BIT_LENGTH',
+        'BIT_OR',
+        'BIT_XOR',
+        'BOUNDARY',
+        'BUFFER',
+        'CAST',
+        'CEIL',
+        'CEILING',
+        'CENTROID',
+        'CHARACTER_LENGTH',
+        'CHAR_LENGTH',
+        'CHECKSUM_AGG',
+        'COALESCE',
+        'COERCIBILITY',
+        'COMPRESS',
+        'CONCAT',
+        'CONCAT_WS',
+        'CONNECTION_ID',
+        'CONV',
+        'CONVERT_TZ',
+        'CONVEXHULL',
+        'COS',
+        'COT',
+        'COUNT',
+        'COUNT_BIG',
+        'CRC32',
+        'CROSSES',
+        'CUME_DIST',
+        'CURDATE',
+        'CURRENT_DATE',
+        'CURRENT_TIME',
+        'CURRENT_USER',
+        'CURTIME',
+        'DATE',
+        'DATEDIFF',
+        'DATE_ADD',
+        'DATE_DIFF',
+        'DATE_FORMAT',
+        'DATE_SUB',
+        'DAYNAME',
+        'DAYOFMONTH',
+        'DAYOFWEEK',
+        'DAYOFYEAR',
+        'DECODE',
+        'DEGREES',
+        'DENSE_RANK',
+        'DES_DECRYPT',
+        'DES_ENCRYPT',
+        'DIFFERENCE',
+        'DIMENSION',
+        'DISJOINT',
+        'DISTANCE',
+        'ELT',
+        'ENCODE',
+        'ENCRYPT',
+        'ENDPOINT',
+        'ENVELOPE',
+        'EQUALS',
+        'EXP',
+        'EXPORT_SET',
+        'EXTERIORRING',
+        'EXTRACT',
+        'EXTRACTVALUE',
+        'FIELD',
+        'FIND_IN_SET',
+        'FIRST_VALUE',
+        'FLOOR',
+        'FORMAT',
+        'FOUND_ROWS',
+        'FROM_DAYS',
+        'FROM_UNIXTIME',
+        'GEOMCOLLFROMTEXT',
+        'GEOMCOLLFROMWKB',
+        'GEOMETRYCOLLECTION',
+        'GEOMETRYCOLLECTIONFROMTEXT',
+        'GEOMETRYCOLLECTIONFROMWKB',
+        'GEOMETRYFROMTEXT',
+        'GEOMETRYFROMWKB',
+        'GEOMETRYN',
+        'GEOMETRYTYPE',
+        'GEOMFROMTEXT',
+        'GEOMFROMWKB',
+        'GET_FORMAT',
+        'GET_LOCK',
+        'GLENGTH',
+        'GREATEST',
+        'GROUPING',
+        'GROUPING_ID',
+        'GROUP_CONCAT',
+        'GROUP_UNIQUE_USERS',
+        'HEX',
+        'INET_ATON',
+        'INET_NTOA',
+        'INSTR',
+        'INTERIORRINGN',
+        'INTERSECTION',
+        'INTERSECTS',
+        'ISCLOSED',
+        'ISEMPTY',
+        'ISNULL',
+        'ISRING',
+        'ISSIMPLE',
+        'IS_FREE_LOCK',
+        'IS_USED_LOCK',
+        'LAG',
+        'LAST_DAY',
+        'LAST_VALUE',
+        'LCASE',
+        'LEAD',
+        'LEAST',
+        'LENGTH',
+        'LINEFROMTEXT',
+        'LINEFROMWKB',
+        'LINESTRING',
+        'LINESTRINGFROMTEXT',
+        'LINESTRINGFROMWKB',
+        'LISTAGG',
+        'LN',
+        'LOAD_FILE',
+        'LOCALTIME',
+        'LOCALTIMESTAMP',
+        'LOCATE',
+        'LOG',
+        'LOG10',
+        'LOG2',
+        'LOWER',
+        'LPAD',
+        'LTRIM',
+        'MAKEDATE',
+        'MAKETIME',
+        'MAKE_SET',
+        'MASTER_POS_WAIT',
+        'MAX',
+        'MBRCONTAINS',
+        'MBRDISJOINT',
+        'MBREQUAL',
+        'MBRINTERSECTS',
+        'MBROVERLAPS',
+        'MBRTOUCHES',
+        'MBRWITHIN',
+        'MD5',
+        'MICROSECOND',
+        'MID',
+        'MIN',
+        'MLINEFROMTEXT',
+        'MLINEFROMWKB',
+        'MOD',
+        'MONTHNAME',
+        'MPOINTFROMTEXT',
+        'MPOINTFROMWKB',
+        'MPOLYFROMTEXT',
+        'MPOLYFROMWKB',
+        'MULTILINESTRING',
+        'MULTILINESTRINGFROMTEXT',
+        'MULTILINESTRINGFROMWKB',
+        'MULTIPOINT',
+        'MULTIPOINTFROMTEXT',
+        'MULTIPOINTFROMWKB',
+        'MULTIPOLYGON',
+        'MULTIPOLYGONFROMTEXT',
+        'MULTIPOLYGONFROMWKB',
+        'NAME_CONST',
+        'NOW',
+        'NTH_VALUE',
+        'NTILE',
+        'NULLIF',
+        'NUMGEOMETRIES',
+        'NUMINTERIORRINGS',
+        'NUMPOINTS',
+        'OCT',
+        'OCTET_LENGTH',
+        'OLD_PASSWORD',
+        'ORD',
+        'OVERLAPS',
+        'PERCENTILE_CONT',
+        'PERCENTILE_DISC',
+        'PERCENT_RANK',
+        'PERIOD_ADD',
+        'PERIOD_DIFF',
+        'PI',
+        'POINT',
+        'POINTFROMTEXT',
+        'POINTFROMWKB',
+        'POINTN',
+        'POINTONSURFACE',
+        'POLYFROMTEXT',
+        'POLYFROMWKB',
+        'POLYGON',
+        'POLYGONFROMTEXT',
+        'POLYGONFROMWKB',
+        'POSITION',
+        'POW',
+        'POWER',
+        'QUARTER',
+        'QUOTE',
+        'RADIANS',
+        'RAND',
+        'RANK',
+        'RELATED',
+        'RELEASE_LOCK',
+        'REPEAT',
+        'REVERSE',
+        'ROUND',
+        'ROW_COUNT',
+        'ROW_NUMBER',
+        'RPAD',
+        'RTRIM',
+        'SCHEMA',
+        'SEC_TO_TIME',
+        'SESSION_USER',
+        'SHA',
+        'SHA1',
+        'SIGN',
+        'SIN',
+        'SLEEP',
+        'SOUNDEX',
+        'SPACE',
+        'SQRT',
+        'SRID',
+        'STARTPOINT',
+        'STD',
+        'STDDEV',
+        'STDDEV_POP',
+        'STDDEV_SAMP',
+        'STDEV',
+        'STDEVP',
+        'STRCMP',
+        'STRING_AGG',
+        'STR_TO_DATE',
+        'SUBDATE',
+        'SUBSTR',
+        'SUBSTRING',
+        'SUBSTRING_INDEX',
+        'SUBTIME',
+        'SUM',
+        'SYMDIFFERENCE',
+        'SYSDATE',
+        'SYSTEM_USER',
+        'TAN',
+        'TIME',
+        'TIMEDIFF',
+        'TIMESTAMP',
+        'TIMESTAMPADD',
+        'TIMESTAMPDIFF',
+        'TIME_FORMAT',
+        'TIME_TO_SEC',
+        'TOUCHES',
+        'TO_DAYS',
+        'TRIM',
+        'UCASE',
+        'UNCOMPRESS',
+        'UNCOMPRESSED_LENGTH',
+        'UNHEX',
+        'UNIQUE_USERS',
+        'UNIX_TIMESTAMP',
+        'UPDATEXML',
+        'UPPER',
+        'USER',
+        'UTC_DATE',
+        'UTC_TIME',
+        'UTC_TIMESTAMP',
+        'UUID',
+        'VAR',
+        'VARIANCE',
+        'VARP',
+        'VAR_POP',
+        'VAR_SAMP',
+        'VERSION',
+        'WEEK',
+        'WEEKDAY',
+        'WEEKOFYEAR',
+        'WITHIN',
+        'X',
+        'Y',
+        'YEAR',
+        'YEARWEEK',
+    ];
+
+    // Regular expressions for tokenizing
+
+    private readonly string $regexBoundaries;
+    private readonly string $regexReserved;
+    private readonly string $regexReservedNewline;
+    private readonly string $regexReservedToplevel;
+    private readonly string $regexFunction;
+
+    /**
+     * Punctuation that can be used as a boundary between other tokens
+     *
+     * @var list
+     */
+    private array $boundaries = [
+        ',',
+        ';',
+        '::', // PostgreSQL cast operator
+        ':',
+        ')',
+        '(',
+        '.',
+        '=',
+        '<',
+        '>',
+        '+',
+        '-',
+        '*',
+        '/',
+        '!',
+        '^',
+        '%',
+        '|',
+        '&',
+        '#',
+    ];
+
+    /**
+     * Stuff that only needs to be done once. Builds regular expressions and
+     * sorts the reserved words.
+     */
+    public function __construct()
+    {
+        // Sort list from longest word to shortest, 3x faster than usort
+        $sortByLengthFx = static function ($values) {
+            $valuesMap = array_combine($values, array_map(strlen(...), $values));
+            assert($valuesMap !== false);
+            arsort($valuesMap);
+
+            return array_keys($valuesMap);
+        };
+
+        // Set up regular expressions
+        $this->regexBoundaries       = '(' . implode(
+            '|',
+            $this->quoteRegex($this->boundaries),
+        ) . ')';
+        $this->regexReserved         = '(' . implode(
+            '|',
+            $this->quoteRegex($sortByLengthFx($this->reserved)),
+        ) . ')';
+        $this->regexReservedToplevel = str_replace(' ', '\\s+', '(' . implode(
+            '|',
+            $this->quoteRegex($sortByLengthFx($this->reservedToplevel)),
+        ) . ')');
+        $this->regexReservedNewline  = str_replace(' ', '\\s+', '(' . implode(
+            '|',
+            $this->quoteRegex($sortByLengthFx($this->reservedNewline)),
+        ) . ')');
+
+        $this->regexFunction = '(' . implode('|', $this->quoteRegex($sortByLengthFx($this->functions))) . ')';
+    }
+
+    /**
+     * Takes a SQL string and breaks it into tokens.
+     * Each token is an associative array with type and value.
+     *
+     * @param string $string The SQL string
+     */
+    public function tokenize(string $string): Cursor
+    {
+        $tokens = [];
+
+        // Used to make sure the string keeps shrinking on each iteration
+        $oldStringLen = strlen($string) + 1;
+
+        $token = null;
+
+        $currentLength = strlen($string);
+
+        // Keep processing the string until it is empty
+        while ($currentLength) {
+            // If the string stopped shrinking, there was a problem
+            if ($oldStringLen <= $currentLength) {
+                $tokens[] = new Token(Token::TOKEN_TYPE_ERROR, $string);
+
+                return new Cursor($tokens);
+            }
+
+            $oldStringLen =  $currentLength;
+
+            // Get the next token and the token type
+            $token       = $this->createNextToken($string, $token);
+            $tokenLength = strlen($token->value());
+
+            $tokens[] = $token;
+
+            // Advance the string
+            $string = substr($string, $tokenLength);
+
+            $currentLength -= $tokenLength;
+        }
+
+        return new Cursor($tokens);
+    }
+
+    /**
+     * Return the next token and token type in a SQL string.
+     * Quoted strings, comments, reserved words, whitespace, and punctuation
+     * are all their own tokens.
+     *
+     * @param string     $string   The SQL string
+     * @param Token|null $previous The result of the previous createNextToken() call
+     *
+     * @return Token An associative array containing the type and value of the token.
+     */
+    private function createNextToken(string $string, Token|null $previous = null): Token
+    {
+        $matches = [];
+        // Whitespace
+        if (preg_match('/^\s+/', $string, $matches)) {
+            return new Token(Token::TOKEN_TYPE_WHITESPACE, $matches[0]);
+        }
+
+        // Comment
+        if (
+            $string[0] === '#' ||
+            (isset($string[1]) && ($string[0] === '-' && $string[1] === '-') ||
+            (isset($string[1]) && $string[0] === '/' && $string[1] === '*'))
+        ) {
+            // Comment until end of line
+            if ($string[0] === '-' || $string[0] === '#') {
+                $last = strpos($string, "\n");
+                $type = Token::TOKEN_TYPE_COMMENT;
+            } else { // Comment until closing comment tag
+                $pos  = strpos($string, '*/', 2);
+                $last = $pos !== false
+                    ? $pos + 2
+                    : false;
+                $type = Token::TOKEN_TYPE_BLOCK_COMMENT;
+            }
+
+            if ($last === false) {
+                $last = strlen($string);
+            }
+
+            return new Token($type, substr($string, 0, $last));
+        }
+
+        // Quoted String
+        if ($string[0] === '"' || $string[0] === '\'' || $string[0] === '`' || $string[0] === '[') {
+            return new Token(
+                ($string[0] === '`' || $string[0] === '['
+                    ? Token::TOKEN_TYPE_BACKTICK_QUOTE
+                    : Token::TOKEN_TYPE_QUOTE),
+                $this->getQuotedString($string),
+            );
+        }
+
+        // User-defined Variable
+        if (($string[0] === '@' || $string[0] === ':') && isset($string[1])) {
+            $value = null;
+            $type  = Token::TOKEN_TYPE_VARIABLE;
+
+            // If the variable name is quoted
+            if ($string[1] === '"' || $string[1] === '\'' || $string[1] === '`') {
+                $value = $string[0] . $this->getQuotedString(substr($string, 1));
+            } else {
+                // Non-quoted variable name
+                preg_match('/^(' . $string[0] . '[a-zA-Z0-9\._\$]+)/', $string, $matches);
+                if ($matches) {
+                    $value = $matches[1];
+                }
+            }
+
+            if ($value !== null) {
+                return new Token($type, $value);
+            }
+        }
+
+        // Number (decimal, binary, or hex)
+        if (
+            preg_match(
+                '/^([0-9]+(\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)($|\s|"\'`|' . $this->regexBoundaries . ')/',
+                $string,
+                $matches,
+            )
+        ) {
+            return new Token(Token::TOKEN_TYPE_NUMBER, $matches[1]);
+        }
+
+        // Boundary Character (punctuation and symbols)
+        if (preg_match('/^(' . $this->regexBoundaries . ')/', $string, $matches)) {
+            return new Token(Token::TOKEN_TYPE_BOUNDARY, $matches[1]);
+        }
+
+        // A reserved word cannot be preceded by a '.'
+        // this makes it so in "mytable.from", "from" is not considered a reserved word
+        if (! $previous || $previous->value() !== '.') {
+            $upper = strtoupper($string);
+            // Top Level Reserved Word
+            if (
+                preg_match(
+                    '/^(' . $this->regexReservedToplevel . ')($|\s|' . $this->regexBoundaries . ')/',
+                    $upper,
+                    $matches,
+                )
+            ) {
+                return new Token(
+                    Token::TOKEN_TYPE_RESERVED_TOPLEVEL,
+                    substr($string, 0, strlen($matches[1])),
+                );
+            }
+
+            // Newline Reserved Word
+            if (
+                preg_match(
+                    '/^(' . $this->regexReservedNewline . ')($|\s|' . $this->regexBoundaries . ')/',
+                    $upper,
+                    $matches,
+                )
+            ) {
+                return new Token(
+                    Token::TOKEN_TYPE_RESERVED_NEWLINE,
+                    substr($string, 0, strlen($matches[1])),
+                );
+            }
+
+            // Other Reserved Word
+            if (
+                preg_match(
+                    '/^(' . $this->regexReserved . ')($|\s|' . $this->regexBoundaries . ')/',
+                    $upper,
+                    $matches,
+                )
+            ) {
+                return new Token(
+                    Token::TOKEN_TYPE_RESERVED,
+                    substr($string, 0, strlen($matches[1])),
+                );
+            }
+        }
+
+        // A function must be succeeded by '('
+        // this makes it so "count(" is considered a function, but "count" alone is not
+        $upper = strtoupper($string);
+        // function
+        if (preg_match('/^(' . $this->regexFunction . '[(]|\s|[)])/', $upper, $matches)) {
+            return new Token(
+                Token::TOKEN_TYPE_RESERVED,
+                substr($string, 0, strlen($matches[1]) - 1),
+            );
+        }
+
+        // Non reserved word
+        preg_match('/^(.*?)($|\s|["\'`]|' . $this->regexBoundaries . ')/', $string, $matches);
+
+        return new Token(Token::TOKEN_TYPE_WORD, $matches[1]);
+    }
+
+    /**
+     * Helper function for building regular expressions for reserved words and boundary characters
+     *
+     * @param string[] $strings The strings to be quoted
+     *
+     * @return string[] The quoted strings
+     */
+    private function quoteRegex(array $strings): array
+    {
+        return array_map(
+            static fn (string $string): string => preg_quote($string, '/'),
+            $strings,
+        );
+    }
+
+    private function getQuotedString(string $string): string
+    {
+        $ret = '';
+
+        // This checks for the following patterns:
+        // 1. backtick quoted string using `` to escape
+        // 2. square bracket quoted string (SQL Server) using ]] to escape
+        // 3. double quoted string using "" or \" to escape
+        // 4. single quoted string using '' or \' to escape
+        if (
+            preg_match(
+                '/^(((`[^`]*($|`))+)|
+            ((\[[^\]]*($|\]))(\][^\]]*($|\]))*)|
+            (("[^"\\\\]*(?:\\\\.[^"\\\\]*)*("|$))+)|
+            ((\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(\'|$))+))/sx',
+                $string,
+                $matches,
+            )
+        ) {
+            $ret = $matches[1];
+        }
+
+        return $ret;
+    }
+}
diff --git a/vendor/egulias/email-validator/CHANGELOG.md b/vendor/egulias/email-validator/CHANGELOG.md
new file mode 100644
index 0000000..39f5a8a
--- /dev/null
+++ b/vendor/egulias/email-validator/CHANGELOG.md
@@ -0,0 +1,32 @@
+# EmailValidator Changelog
+
+## New Features
+
+* Access to local part and domain part from EmailParser
+* Validations outside of the scope of the RFC will be considered "extra" validations, thus opening the door for adding new; will live in their own folder "extra" (as requested in #248, #195, #183). 
+
+## Breaking changes
+
+* PHP version upgraded to match Symfony's (as of 12/2020).
+* DNSCheckValidation now fails for missing MX records. While the RFC argues that the existence of only A records to be valid, starting in v3 they will be considered invalid.
+* Emails domain part are now intenteded to be RFC 1035 compliant, rendering previous valid emails (e.g example@examp&) invalid.
+
+## PHP versions upgrade policy
+PHP version upgrade requirement will happen via MINOR (3.x) version upgrades of the library, following the adoption level by major frameworks.
+
+## Changes
+* #235
+* #215
+* #130
+* #258
+* #188
+* #181
+* #217
+* #214
+* #249
+* #236
+* #257
+* #210
+
+## Thanks
+To contributors, be it with PRs, reporting issues or supporting otherwise.
diff --git a/vendor/egulias/email-validator/CONTRIBUTING.md b/vendor/egulias/email-validator/CONTRIBUTING.md
new file mode 100644
index 0000000..907bc2c
--- /dev/null
+++ b/vendor/egulias/email-validator/CONTRIBUTING.md
@@ -0,0 +1,153 @@
+# Contributing
+
+When contributing to this repository make sure to follow the Pull request process below.
+Reduce to the minimum 3rd party dependencies.
+
+Please note we have a [code of conduct](#Code of Conduct), please follow it in all your interactions with the project.
+
+## Pull Request Process
+
+When doing a PR to v2 remember that you also have to do the PR port to v3, or tests confirming the bug is not reproducible.
+
+1. Supported version is v3. If you are fixing a bug in v2, please port to v3
+2. Use the title as a brief description of the changes
+3. Describe the changes you are proposing
+    1. If adding an extra validation state the benefits of adding it and the problem is solving
+    2. Document in the readme, by adding it to the list
+4. Provide appropriate tests for the code you are submitting: aim to keep the existing coverage percentage.
+5. Add your Twitter handle (if you have) so we can thank you there.
+
+## License
+By contributing, you agree that your contributions will be licensed under its MIT License.
+
+## Code of Conduct
+
+### Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+### Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+  overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+### Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+### Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+### Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at .
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+#### Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+#### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+#### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+#### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+#### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+### Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
+at [https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/vendor/egulias/email-validator/LICENSE b/vendor/egulias/email-validator/LICENSE
new file mode 100644
index 0000000..b1902a4
--- /dev/null
+++ b/vendor/egulias/email-validator/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013-2023 Eduardo Gulias Davis
+
+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.
diff --git a/vendor/egulias/email-validator/composer.json b/vendor/egulias/email-validator/composer.json
new file mode 100644
index 0000000..bf1b3f4
--- /dev/null
+++ b/vendor/egulias/email-validator/composer.json
@@ -0,0 +1,37 @@
+{
+  "name":        "egulias/email-validator",
+  "description": "A library for validating emails against several RFCs",
+  "homepage":    "https://github.com/egulias/EmailValidator",
+  "keywords":    ["email", "validation", "validator", "emailvalidation", "emailvalidator"],
+  "license":     "MIT",
+  "authors": [
+    {"name": "Eduardo Gulias Davis"}
+  ],
+  "extra": {
+    "branch-alias": {
+      "dev-master": "4.0.x-dev"
+    }
+  },
+  "require": {
+    "php": ">=8.1",
+    "doctrine/lexer": "^2.0 || ^3.0",
+    "symfony/polyfill-intl-idn": "^1.26"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "^10.2",
+    "vimeo/psalm": "^5.12"
+  },
+  "suggest": {
+    "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+  },
+  "autoload": {
+    "psr-4": {
+      "Egulias\\EmailValidator\\": "src"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Egulias\\EmailValidator\\Tests\\": "tests"
+    }
+  }
+}
diff --git a/vendor/egulias/email-validator/src/EmailLexer.php b/vendor/egulias/email-validator/src/EmailLexer.php
new file mode 100644
index 0000000..969c049
--- /dev/null
+++ b/vendor/egulias/email-validator/src/EmailLexer.php
@@ -0,0 +1,330 @@
+ */
+class EmailLexer extends AbstractLexer
+{
+    //ASCII values
+    public const S_EMPTY            = -1;
+    public const C_NUL              = 0;
+    public const S_HTAB             = 9;
+    public const S_LF               = 10;
+    public const S_CR               = 13;
+    public const S_SP               = 32;
+    public const EXCLAMATION        = 33;
+    public const S_DQUOTE           = 34;
+    public const NUMBER_SIGN        = 35;
+    public const DOLLAR             = 36;
+    public const PERCENTAGE         = 37;
+    public const AMPERSAND          = 38;
+    public const S_SQUOTE           = 39;
+    public const S_OPENPARENTHESIS  = 40;
+    public const S_CLOSEPARENTHESIS = 41;
+    public const ASTERISK           = 42;
+    public const S_PLUS             = 43;
+    public const S_COMMA            = 44;
+    public const S_HYPHEN           = 45;
+    public const S_DOT              = 46;
+    public const S_SLASH            = 47;
+    public const S_COLON            = 58;
+    public const S_SEMICOLON        = 59;
+    public const S_LOWERTHAN        = 60;
+    public const S_EQUAL            = 61;
+    public const S_GREATERTHAN      = 62;
+    public const QUESTIONMARK       = 63;
+    public const S_AT               = 64;
+    public const S_OPENBRACKET      = 91;
+    public const S_BACKSLASH        = 92;
+    public const S_CLOSEBRACKET     = 93;
+    public const CARET              = 94;
+    public const S_UNDERSCORE       = 95;
+    public const S_BACKTICK         = 96;
+    public const S_OPENCURLYBRACES  = 123;
+    public const S_PIPE             = 124;
+    public const S_CLOSECURLYBRACES = 125;
+    public const S_TILDE            = 126;
+    public const C_DEL              = 127;
+    public const INVERT_QUESTIONMARK = 168;
+    public const INVERT_EXCLAMATION = 173;
+    public const GENERIC            = 300;
+    public const S_IPV6TAG          = 301;
+    public const INVALID            = 302;
+    public const CRLF               = 1310;
+    public const S_DOUBLECOLON      = 5858;
+    public const ASCII_INVALID_FROM = 127;
+    public const ASCII_INVALID_TO   = 199;
+
+    /**
+     * US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
+     *
+     * @var array
+     */
+    protected $charValue = [
+        '{'    => self::S_OPENCURLYBRACES,
+        '}'    => self::S_CLOSECURLYBRACES,
+        '('    => self::S_OPENPARENTHESIS,
+        ')'    => self::S_CLOSEPARENTHESIS,
+        '<'    => self::S_LOWERTHAN,
+        '>'    => self::S_GREATERTHAN,
+        '['    => self::S_OPENBRACKET,
+        ']'    => self::S_CLOSEBRACKET,
+        ':'    => self::S_COLON,
+        ';'    => self::S_SEMICOLON,
+        '@'    => self::S_AT,
+        '\\'   => self::S_BACKSLASH,
+        '/'    => self::S_SLASH,
+        ','    => self::S_COMMA,
+        '.'    => self::S_DOT,
+        "'"    => self::S_SQUOTE,
+        "`"    => self::S_BACKTICK,
+        '"'    => self::S_DQUOTE,
+        '-'    => self::S_HYPHEN,
+        '::'   => self::S_DOUBLECOLON,
+        ' '    => self::S_SP,
+        "\t"   => self::S_HTAB,
+        "\r"   => self::S_CR,
+        "\n"   => self::S_LF,
+        "\r\n" => self::CRLF,
+        'IPv6' => self::S_IPV6TAG,
+        ''     => self::S_EMPTY,
+        '\0'   => self::C_NUL,
+        '*'    => self::ASTERISK,
+        '!'    => self::EXCLAMATION,
+        '&'    => self::AMPERSAND,
+        '^'    => self::CARET,
+        '$'    => self::DOLLAR,
+        '%'    => self::PERCENTAGE,
+        '~'    => self::S_TILDE,
+        '|'    => self::S_PIPE,
+        '_'    => self::S_UNDERSCORE,
+        '='    => self::S_EQUAL,
+        '+'    => self::S_PLUS,
+        '¿'    => self::INVERT_QUESTIONMARK,
+        '?'    => self::QUESTIONMARK,
+        '#'    => self::NUMBER_SIGN,
+        '¡'    => self::INVERT_EXCLAMATION,
+    ];
+
+    public const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu";
+
+    public const VALID_UTF8_REGEX = '/\p{Cc}+/u';
+
+    public const CATCHABLE_PATTERNS = [
+        '[a-zA-Z]+[46]?', //ASCII and domain literal
+        '[^\x00-\x7F]',  //UTF-8
+        '[0-9]+',
+        '\r\n',
+        '::',
+        '\s+?',
+        '.',
+    ];
+
+    public const NON_CATCHABLE_PATTERNS = [
+        '[\xA0-\xff]+',
+    ];
+
+    public const MODIFIERS = 'iu';
+
+    /** @var bool */
+    protected $hasInvalidTokens = false;
+
+    /**
+     * @var Token
+     */
+    protected Token $previous;
+
+    /**
+     * The last matched/seen token.
+     *
+     * @var Token
+     */
+    public Token $current;
+
+    /**
+     * @var Token
+     */
+    private Token $nullToken;
+
+    /** @var string */
+    private $accumulator = '';
+
+    /** @var bool */
+    private $hasToRecord = false;
+
+    public function __construct()
+    {
+        /** @var Token $nullToken */
+        $nullToken = new Token('', self::S_EMPTY, 0);
+        $this->nullToken = $nullToken;
+
+        $this->current = $this->previous = $this->nullToken;
+        $this->lookahead = null;
+    }
+
+    public function reset(): void
+    {
+        $this->hasInvalidTokens = false;
+        parent::reset();
+        $this->current = $this->previous = $this->nullToken;
+    }
+
+    /**
+     * @param int $type
+     * @throws \UnexpectedValueException
+     * @return boolean
+     *
+     * @psalm-suppress InvalidScalarArgument
+     */
+    public function find($type): bool
+    {
+        $search = clone $this;
+        $search->skipUntil($type);
+
+        if (!$search->lookahead) {
+            throw new \UnexpectedValueException($type . ' not found');
+        }
+        return true;
+    }
+
+    /**
+     * moveNext
+     *
+     * @return boolean
+     */
+    public function moveNext(): bool
+    {
+        if ($this->hasToRecord && $this->previous === $this->nullToken) {
+            $this->accumulator .= $this->current->value;
+        }
+
+        $this->previous = $this->current;
+
+        if ($this->lookahead === null) {
+            $this->lookahead = $this->nullToken;
+        }
+
+        $hasNext = parent::moveNext();
+        $this->current = $this->token ?? $this->nullToken;
+
+        if ($this->hasToRecord) {
+            $this->accumulator .= $this->current->value;
+        }
+
+        return $hasNext;
+    }
+
+    /**
+     * Retrieve token type. Also processes the token value if necessary.
+     *
+     * @param string $value
+     * @throws \InvalidArgumentException
+     * @return integer
+     */
+    protected function getType(&$value): int
+    {
+        $encoded = $value;
+
+        if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
+            $encoded = mb_convert_encoding($value, 'UTF-8', 'Windows-1252');
+        }
+
+        if ($this->isValid($encoded)) {
+            return $this->charValue[$encoded];
+        }
+
+        if ($this->isNullType($encoded)) {
+            return self::C_NUL;
+        }
+
+        if ($this->isInvalidChar($encoded)) {
+            $this->hasInvalidTokens = true;
+            return self::INVALID;
+        }
+
+        return self::GENERIC;
+    }
+
+    protected function isValid(string $value): bool
+    {
+        return isset($this->charValue[$value]);
+    }
+
+    protected function isNullType(string $value): bool
+    {
+        return $value === "\0";
+    }
+
+    protected function isInvalidChar(string $value): bool
+    {
+        return !preg_match(self::INVALID_CHARS_REGEX, $value);
+    }
+
+    protected function isUTF8Invalid(string $value): bool
+    {
+        return preg_match(self::VALID_UTF8_REGEX, $value) !== false;
+    }
+
+    public function hasInvalidTokens(): bool
+    {
+        return $this->hasInvalidTokens;
+    }
+
+    /**
+     * getPrevious
+     *
+     * @return Token
+     */
+    public function getPrevious(): Token
+    {
+        return $this->previous;
+    }
+
+    /**
+     * Lexical catchable patterns.
+     *
+     * @return string[]
+     */
+    protected function getCatchablePatterns(): array
+    {
+        return self::CATCHABLE_PATTERNS;
+    }
+
+    /**
+     * Lexical non-catchable patterns.
+     *
+     * @return string[]
+     */
+    protected function getNonCatchablePatterns(): array
+    {
+        return self::NON_CATCHABLE_PATTERNS;
+    }
+
+    protected function getModifiers(): string
+    {
+        return self::MODIFIERS;
+    }
+
+    public function getAccumulatedValues(): string
+    {
+        return $this->accumulator;
+    }
+
+    public function startRecording(): void
+    {
+        $this->hasToRecord = true;
+    }
+
+    public function stopRecording(): void
+    {
+        $this->hasToRecord = false;
+    }
+
+    public function clearRecorded(): void
+    {
+        $this->accumulator = '';
+    }
+}
diff --git a/vendor/egulias/email-validator/src/EmailParser.php b/vendor/egulias/email-validator/src/EmailParser.php
new file mode 100644
index 0000000..fc449c7
--- /dev/null
+++ b/vendor/egulias/email-validator/src/EmailParser.php
@@ -0,0 +1,90 @@
+addLongEmailWarning($this->localPart, $this->domainPart);
+
+        return $result;
+    }
+
+    protected function preLeftParsing(): Result
+    {
+        if (!$this->hasAtToken()) {
+            return new InvalidEmail(new NoLocalPart(), $this->lexer->current->value);
+        }
+        return new ValidEmail();
+    }
+
+    protected function parseLeftFromAt(): Result
+    {
+        return $this->processLocalPart();
+    }
+
+    protected function parseRightFromAt(): Result
+    {
+        return $this->processDomainPart();
+    }
+
+    private function processLocalPart(): Result
+    {
+        $localPartParser = new LocalPart($this->lexer);
+        $localPartResult = $localPartParser->parse();
+        $this->localPart = $localPartParser->localPart();
+        $this->warnings = [...$localPartParser->getWarnings(), ...$this->warnings];
+
+        return $localPartResult;
+    }
+
+    private function processDomainPart(): Result
+    {
+        $domainPartParser = new DomainPart($this->lexer);
+        $domainPartResult = $domainPartParser->parse();
+        $this->domainPart = $domainPartParser->domainPart();
+        $this->warnings = [...$domainPartParser->getWarnings(), ...$this->warnings];
+
+        return $domainPartResult;
+    }
+
+    public function getDomainPart(): string
+    {
+        return $this->domainPart;
+    }
+
+    public function getLocalPart(): string
+    {
+        return $this->localPart;
+    }
+
+    private function addLongEmailWarning(string $localPart, string $parsedDomainPart): void
+    {
+        if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
+            $this->warnings[EmailTooLong::CODE] = new EmailTooLong();
+        }
+    }
+}
diff --git a/vendor/egulias/email-validator/src/EmailValidator.php b/vendor/egulias/email-validator/src/EmailValidator.php
new file mode 100644
index 0000000..5a2e5c8
--- /dev/null
+++ b/vendor/egulias/email-validator/src/EmailValidator.php
@@ -0,0 +1,67 @@
+lexer = new EmailLexer();
+    }
+
+    /**
+     * @param string          $email
+     * @param EmailValidation $emailValidation
+     * @return bool
+     */
+    public function isValid(string $email, EmailValidation $emailValidation)
+    {
+        $isValid = $emailValidation->isValid($email, $this->lexer);
+        $this->warnings = $emailValidation->getWarnings();
+        $this->error = $emailValidation->getError();
+
+        return $isValid;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function hasWarnings()
+    {
+        return !empty($this->warnings);
+    }
+
+    /**
+     * @return array
+     */
+    public function getWarnings()
+    {
+        return $this->warnings;
+    }
+
+    /**
+     * @return InvalidEmail|null
+     */
+    public function getError()
+    {
+        return $this->error;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/MessageIDParser.php b/vendor/egulias/email-validator/src/MessageIDParser.php
new file mode 100644
index 0000000..35bd0a7
--- /dev/null
+++ b/vendor/egulias/email-validator/src/MessageIDParser.php
@@ -0,0 +1,91 @@
+addLongEmailWarning($this->idLeft, $this->idRight);
+
+        return $result;
+    }
+
+    protected function preLeftParsing(): Result
+    {
+        if (!$this->hasAtToken()) {
+            return new InvalidEmail(new NoLocalPart(), $this->lexer->current->value);
+        }
+        return new ValidEmail();
+    }
+
+    protected function parseLeftFromAt(): Result
+    {
+        return $this->processIDLeft();
+    }
+
+    protected function parseRightFromAt(): Result
+    {
+        return $this->processIDRight();
+    }
+
+    private function processIDLeft(): Result
+    {
+        $localPartParser = new IDLeftPart($this->lexer);
+        $localPartResult = $localPartParser->parse();
+        $this->idLeft = $localPartParser->localPart();
+        $this->warnings = [...$localPartParser->getWarnings(), ...$this->warnings];
+
+        return $localPartResult;
+    }
+
+    private function processIDRight(): Result
+    {
+        $domainPartParser = new IDRightPart($this->lexer);
+        $domainPartResult = $domainPartParser->parse();
+        $this->idRight = $domainPartParser->domainPart();
+        $this->warnings = [...$domainPartParser->getWarnings(), ...$this->warnings];
+
+        return $domainPartResult;
+    }
+
+    public function getLeftPart(): string
+    {
+        return $this->idLeft;
+    }
+
+    public function getRightPart(): string
+    {
+        return $this->idRight;
+    }
+
+    private function addLongEmailWarning(string $localPart, string $parsedDomainPart): void
+    {
+        if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAILID_MAX_LENGTH) {
+            $this->warnings[EmailTooLong::CODE] = new EmailTooLong();
+        }
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser.php b/vendor/egulias/email-validator/src/Parser.php
new file mode 100644
index 0000000..d577e3e
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser.php
@@ -0,0 +1,78 @@
+lexer = $lexer;
+    }
+
+    public function parse(string $str): Result
+    {
+        $this->lexer->setInput($str);
+
+        if ($this->lexer->hasInvalidTokens()) {
+            return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->current->value);
+        }
+
+        $preParsingResult = $this->preLeftParsing();
+        if ($preParsingResult->isInvalid()) {
+            return $preParsingResult;
+        }
+
+        $localPartResult = $this->parseLeftFromAt();
+
+        if ($localPartResult->isInvalid()) {
+            return $localPartResult;
+        }
+
+        $domainPartResult = $this->parseRightFromAt();
+
+        if ($domainPartResult->isInvalid()) {
+            return $domainPartResult;
+        }
+
+        return new ValidEmail();
+    }
+
+    /**
+     * @return Warning\Warning[]
+     */
+    public function getWarnings(): array
+    {
+        return $this->warnings;
+    }
+
+    protected function hasAtToken(): bool
+    {
+        $this->lexer->moveNext();
+        $this->lexer->moveNext();
+
+        return !$this->lexer->current->isA(EmailLexer::S_AT);
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/Comment.php b/vendor/egulias/email-validator/src/Parser/Comment.php
new file mode 100644
index 0000000..7b5b47e
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/Comment.php
@@ -0,0 +1,102 @@
+lexer = $lexer;
+        $this->commentStrategy = $commentStrategy;
+    }
+
+    public function parse(): Result
+    {
+        if ($this->lexer->current->isA(EmailLexer::S_OPENPARENTHESIS)) {
+            $this->openedParenthesis++;
+            if ($this->noClosingParenthesis()) {
+                return new InvalidEmail(new UnclosedComment(), $this->lexer->current->value);
+            }
+        }
+
+        if ($this->lexer->current->isA(EmailLexer::S_CLOSEPARENTHESIS)) {
+            return new InvalidEmail(new UnOpenedComment(), $this->lexer->current->value);
+        }
+
+        $this->warnings[WarningComment::CODE] = new WarningComment();
+
+        $moreTokens = true;
+        while ($this->commentStrategy->exitCondition($this->lexer, $this->openedParenthesis) && $moreTokens) {
+
+            if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) {
+                $this->openedParenthesis++;
+            }
+            $this->warnEscaping();
+            if ($this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
+                $this->openedParenthesis--;
+            }
+            $moreTokens = $this->lexer->moveNext();
+        }
+
+        if ($this->openedParenthesis >= 1) {
+            return new InvalidEmail(new UnclosedComment(), $this->lexer->current->value);
+        }
+        if ($this->openedParenthesis < 0) {
+            return new InvalidEmail(new UnOpenedComment(), $this->lexer->current->value);
+        }
+
+        $finalValidations = $this->commentStrategy->endOfLoopValidations($this->lexer);
+
+        $this->warnings = [...$this->warnings, ...$this->commentStrategy->getWarnings()];
+
+        return $finalValidations;
+    }
+
+
+    /**
+     * @return void
+     */
+    private function warnEscaping(): void
+    {
+        //Backslash found
+        if (!$this->lexer->current->isA(EmailLexer::S_BACKSLASH)) {
+            return;
+        }
+
+        if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
+            return;
+        }
+
+        $this->warnings[QuotedPart::CODE] =
+            new QuotedPart($this->lexer->getPrevious()->type, $this->lexer->current->type);
+    }
+
+    private function noClosingParenthesis(): bool
+    {
+        try {
+            $this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
+            return false;
+        } catch (\RuntimeException $e) {
+            return true;
+        }
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php b/vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php
new file mode 100644
index 0000000..8834db0
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php
@@ -0,0 +1,22 @@
+isNextToken(EmailLexer::S_DOT));
+    }
+
+    public function endOfLoopValidations(EmailLexer $lexer): Result
+    {
+        //test for end of string
+        if (!$lexer->isNextToken(EmailLexer::S_DOT)) {
+            return new InvalidEmail(new ExpectingATEXT('DOT not found near CLOSEPARENTHESIS'), $lexer->current->value);
+        }
+        //add warning
+        //Address is valid within the message but cannot be used unmodified for the envelope
+        return new ValidEmail();
+    }
+
+    public function getWarnings(): array
+    {
+        return [];
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php b/vendor/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php
new file mode 100644
index 0000000..5c18b44
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php
@@ -0,0 +1,37 @@
+isNextToken(EmailLexer::S_AT);
+    }
+
+    public function endOfLoopValidations(EmailLexer $lexer): Result
+    {
+        if (!$lexer->isNextToken(EmailLexer::S_AT)) {
+            return new InvalidEmail(new ExpectingATEXT('ATEX is not expected after closing comments'), $lexer->current->value);
+        }
+        $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
+        return new ValidEmail();
+    }
+
+    public function getWarnings(): array
+    {
+        return $this->warnings;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/DomainLiteral.php b/vendor/egulias/email-validator/src/Parser/DomainLiteral.php
new file mode 100644
index 0000000..5093e50
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/DomainLiteral.php
@@ -0,0 +1,210 @@
+addTagWarnings();
+
+        $IPv6TAG = false;
+        $addressLiteral = '';
+
+        do {
+            if ($this->lexer->current->isA(EmailLexer::C_NUL)) {
+                return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->current->value);
+            }
+
+            $this->addObsoleteWarnings();
+
+            if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENBRACKET, EmailLexer::S_OPENBRACKET))) {
+                return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->current->value);
+            }
+
+            if ($this->lexer->isNextTokenAny(
+                array(EmailLexer::S_HTAB, EmailLexer::S_SP, EmailLexer::CRLF)
+            )) {
+                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
+                $this->parseFWS();
+            }
+
+            if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
+                return new InvalidEmail(new CRNoLF(), $this->lexer->current->value);
+            }
+
+            if ($this->lexer->current->isA(EmailLexer::S_BACKSLASH)) {
+                return new InvalidEmail(new UnusualElements($this->lexer->current->value), $this->lexer->current->value);
+            }
+            if ($this->lexer->current->isA(EmailLexer::S_IPV6TAG)) {
+                $IPv6TAG = true;
+            }
+
+            if ($this->lexer->current->isA(EmailLexer::S_CLOSEBRACKET)) {
+                break;
+            }
+
+            $addressLiteral .= $this->lexer->current->value;
+        } while ($this->lexer->moveNext());
+
+
+        //Encapsulate
+        $addressLiteral = str_replace('[', '', $addressLiteral);
+        $isAddressLiteralIPv4 = $this->checkIPV4Tag($addressLiteral);
+
+        if (!$isAddressLiteralIPv4) {
+            return new ValidEmail();
+        }
+
+        $addressLiteral = $this->convertIPv4ToIPv6($addressLiteral);
+
+        if (!$IPv6TAG) {
+            $this->warnings[WarningDomainLiteral::CODE] = new WarningDomainLiteral();
+            return new ValidEmail();
+        }
+
+        $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
+
+        $this->checkIPV6Tag($addressLiteral);
+
+        return new ValidEmail();
+    }
+
+    /**
+     * @param string $addressLiteral
+     * @param int $maxGroups
+     */
+    public function checkIPV6Tag($addressLiteral, $maxGroups = 8): void
+    {
+        $prev = $this->lexer->getPrevious();
+        if ($prev->isA(EmailLexer::S_COLON)) {
+            $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
+        }
+
+        $IPv6       = substr($addressLiteral, 5);
+        //Daniel Marschall's new IPv6 testing strategy
+        $matchesIP  = explode(':', $IPv6);
+        $groupCount = count($matchesIP);
+        $colons     = strpos($IPv6, '::');
+
+        if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
+            $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
+        }
+
+        if ($colons === false) {
+            // We need exactly the right number of groups
+            if ($groupCount !== $maxGroups) {
+                $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
+            }
+            return;
+        }
+
+        if ($colons !== strrpos($IPv6, '::')) {
+            $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
+            return;
+        }
+
+        if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
+            // RFC 4291 allows :: at the start or end of an address
+            //with 7 other groups in addition
+            ++$maxGroups;
+        }
+
+        if ($groupCount > $maxGroups) {
+            $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
+        } elseif ($groupCount === $maxGroups) {
+            $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
+        }
+    }
+
+    public function convertIPv4ToIPv6(string $addressLiteralIPv4): string
+    {
+        $matchesIP  = [];
+        $IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteralIPv4, $matchesIP);
+
+        // Extract IPv4 part from the end of the address-literal (if there is one)
+        if ($IPv4Match > 0) {
+            $index = (int) strrpos($addressLiteralIPv4, $matchesIP[0]);
+            //There's a match but it is at the start
+            if ($index > 0) {
+                // Convert IPv4 part to IPv6 format for further testing
+                return substr($addressLiteralIPv4, 0, $index) . '0:0';
+            }
+        }
+
+        return $addressLiteralIPv4;
+    }
+
+    /**
+     * @param string $addressLiteral
+     *
+     * @return bool
+     */
+    protected function checkIPV4Tag($addressLiteral): bool
+    {
+        $matchesIP  = [];
+        $IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteral, $matchesIP);
+
+        // Extract IPv4 part from the end of the address-literal (if there is one)
+
+        if ($IPv4Match > 0) {
+            $index = strrpos($addressLiteral, $matchesIP[0]);
+            //There's a match but it is at the start
+            if ($index === 0) {
+                $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private function addObsoleteWarnings(): void
+    {
+        if (in_array($this->lexer->current->type, self::OBSOLETE_WARNINGS)) {
+            $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
+        }
+    }
+
+    private function addTagWarnings(): void
+    {
+        if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
+            $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
+        }
+        if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
+            $lexer = clone $this->lexer;
+            $lexer->moveNext();
+            if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
+                $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
+            }
+        }
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/DomainPart.php b/vendor/egulias/email-validator/src/Parser/DomainPart.php
new file mode 100644
index 0000000..a1a56cf
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/DomainPart.php
@@ -0,0 +1,326 @@
+lexer->clearRecorded();
+        $this->lexer->startRecording();
+
+        $this->lexer->moveNext();
+
+        $domainChecks = $this->performDomainStartChecks();
+        if ($domainChecks->isInvalid()) {
+            return $domainChecks;
+        }
+
+        if ($this->lexer->current->isA(EmailLexer::S_AT)) {
+            return new InvalidEmail(new ConsecutiveAt(), $this->lexer->current->value);
+        }
+
+        $result = $this->doParseDomainPart();
+        if ($result->isInvalid()) {
+            return $result;
+        }
+
+        $end = $this->checkEndOfDomain();
+        if ($end->isInvalid()) {
+            return $end;
+        }
+
+        $this->lexer->stopRecording();
+        $this->domainPart = $this->lexer->getAccumulatedValues();
+
+        $length = strlen($this->domainPart);
+        if ($length > self::DOMAIN_MAX_LENGTH) {
+            return new InvalidEmail(new DomainTooLong(), $this->lexer->current->value);
+        }
+
+        return new ValidEmail();
+    }
+
+    private function checkEndOfDomain(): Result
+    {
+        $prev = $this->lexer->getPrevious();
+        if ($prev->isA(EmailLexer::S_DOT)) {
+            return new InvalidEmail(new DotAtEnd(), $this->lexer->current->value);
+        }
+        if ($prev->isA(EmailLexer::S_HYPHEN)) {
+            return new InvalidEmail(new DomainHyphened('Hypen found at the end of the domain'), $prev->value);
+        }
+
+        if ($this->lexer->current->isA(EmailLexer::S_SP)) {
+            return new InvalidEmail(new CRLFAtTheEnd(), $prev->value);
+        }
+        return new ValidEmail();
+    }
+
+    private function performDomainStartChecks(): Result
+    {
+        $invalidTokens = $this->checkInvalidTokensAfterAT();
+        if ($invalidTokens->isInvalid()) {
+            return $invalidTokens;
+        }
+
+        $missingDomain = $this->checkEmptyDomain();
+        if ($missingDomain->isInvalid()) {
+            return $missingDomain;
+        }
+
+        if ($this->lexer->current->isA(EmailLexer::S_OPENPARENTHESIS)) {
+            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
+        }
+        return new ValidEmail();
+    }
+
+    private function checkEmptyDomain(): Result
+    {
+        $thereIsNoDomain = $this->lexer->current->isA(EmailLexer::S_EMPTY) ||
+            ($this->lexer->current->isA(EmailLexer::S_SP) &&
+                !$this->lexer->isNextToken(EmailLexer::GENERIC));
+
+        if ($thereIsNoDomain) {
+            return new InvalidEmail(new NoDomainPart(), $this->lexer->current->value);
+        }
+
+        return new ValidEmail();
+    }
+
+    private function checkInvalidTokensAfterAT(): Result
+    {
+        if ($this->lexer->current->isA(EmailLexer::S_DOT)) {
+            return new InvalidEmail(new DotAtStart(), $this->lexer->current->value);
+        }
+        if ($this->lexer->current->isA(EmailLexer::S_HYPHEN)) {
+            return new InvalidEmail(new DomainHyphened('After AT'), $this->lexer->current->value);
+        }
+        return new ValidEmail();
+    }
+
+    protected function parseComments(): Result
+    {
+        $commentParser = new Comment($this->lexer, new DomainComment());
+        $result = $commentParser->parse();
+        $this->warnings = [...$this->warnings, ...$commentParser->getWarnings()];
+
+        return $result;
+    }
+
+    protected function doParseDomainPart(): Result
+    {
+        $tldMissing = true;
+        $hasComments = false;
+        $domain = '';
+        do {
+            $prev = $this->lexer->getPrevious();
+
+            $notAllowedChars = $this->checkNotAllowedChars($this->lexer->current);
+            if ($notAllowedChars->isInvalid()) {
+                return $notAllowedChars;
+            }
+
+            if (
+                $this->lexer->current->isA(EmailLexer::S_OPENPARENTHESIS) ||
+                $this->lexer->current->isA(EmailLexer::S_CLOSEPARENTHESIS)
+            ) {
+                $hasComments = true;
+                $commentsResult = $this->parseComments();
+
+                //Invalid comment parsing
+                if ($commentsResult->isInvalid()) {
+                    return $commentsResult;
+                }
+            }
+
+            $dotsResult = $this->checkConsecutiveDots();
+            if ($dotsResult->isInvalid()) {
+                return $dotsResult;
+            }
+
+            if ($this->lexer->current->isA(EmailLexer::S_OPENBRACKET)) {
+                $literalResult = $this->parseDomainLiteral();
+
+                $this->addTLDWarnings($tldMissing);
+                return $literalResult;
+            }
+
+            $labelCheck = $this->checkLabelLength();
+            if ($labelCheck->isInvalid()) {
+                return $labelCheck;
+            }
+
+            $FwsResult = $this->parseFWS();
+            if ($FwsResult->isInvalid()) {
+                return $FwsResult;
+            }
+
+            $domain .= $this->lexer->current->value;
+
+            if ($this->lexer->current->isA(EmailLexer::S_DOT) && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
+                $tldMissing = false;
+            }
+
+            $exceptionsResult = $this->checkDomainPartExceptions($prev, $hasComments);
+            if ($exceptionsResult->isInvalid()) {
+                return $exceptionsResult;
+            }
+            $this->lexer->moveNext();
+        } while (!$this->lexer->current->isA(EmailLexer::S_EMPTY));
+
+        $labelCheck = $this->checkLabelLength(true);
+        if ($labelCheck->isInvalid()) {
+            return $labelCheck;
+        }
+        $this->addTLDWarnings($tldMissing);
+
+        $this->domainPart = $domain;
+        return new ValidEmail();
+    }
+
+     /**
+     * @param Token $token
+     *
+     * @return Result
+     */
+    private function checkNotAllowedChars(Token $token): Result
+    {
+        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH => true];
+        if (isset($notAllowed[$token->type])) {
+            return new InvalidEmail(new CharNotAllowed(), $token->value);
+        }
+        return new ValidEmail();
+    }
+
+    /**
+     * @return Result
+     */
+    protected function parseDomainLiteral(): Result
+    {
+        try {
+            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
+        } catch (\RuntimeException $e) {
+            return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->current->value);
+        }
+
+        $domainLiteralParser = new DomainLiteralParser($this->lexer);
+        $result = $domainLiteralParser->parse();
+        $this->warnings = [...$this->warnings, ...$domainLiteralParser->getWarnings()];
+        return $result;
+    }
+
+    /**
+     * @param Token $prev
+     * @param bool $hasComments
+     *
+     * @return Result
+     */
+    protected function checkDomainPartExceptions(Token $prev, bool $hasComments): Result
+    {
+        if ($this->lexer->current->isA(EmailLexer::S_OPENBRACKET) && $prev->type !== EmailLexer::S_AT) {
+            return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->current->value);
+        }
+
+        if ($this->lexer->current->isA(EmailLexer::S_HYPHEN) && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
+            return new InvalidEmail(new DomainHyphened('Hypen found near DOT'), $this->lexer->current->value);
+        }
+
+        if (
+            $this->lexer->current->isA(EmailLexer::S_BACKSLASH)
+            && $this->lexer->isNextToken(EmailLexer::GENERIC)
+        ) {
+            return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->current->value);
+        }
+
+        return $this->validateTokens($hasComments);
+    }
+
+    protected function validateTokens(bool $hasComments): Result
+    {
+        $validDomainTokens = array(
+            EmailLexer::GENERIC => true,
+            EmailLexer::S_HYPHEN => true,
+            EmailLexer::S_DOT => true,
+        );
+
+        if ($hasComments) {
+            $validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true;
+            $validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true;
+        }
+
+        if (!isset($validDomainTokens[$this->lexer->current->type])) {
+            return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->current->value), $this->lexer->current->value);
+        }
+
+        return new ValidEmail();
+    }
+
+    private function checkLabelLength(bool $isEndOfDomain = false): Result
+    {
+        if ($this->lexer->current->isA(EmailLexer::S_DOT) || $isEndOfDomain) {
+            if ($this->isLabelTooLong($this->label)) {
+                return new InvalidEmail(new LabelTooLong(), $this->lexer->current->value);
+            }
+            $this->label = '';
+        }
+        $this->label .= $this->lexer->current->value;
+        return new ValidEmail();
+    }
+
+
+    private function isLabelTooLong(string $label): bool
+    {
+        if (preg_match('/[^\x00-\x7F]/', $label)) {
+            idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
+            return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
+        }
+        return strlen($label) > self::LABEL_MAX_LENGTH;
+    }
+
+    private function addTLDWarnings(bool $isTLDMissing): void
+    {
+        if ($isTLDMissing) {
+            $this->warnings[TLD::CODE] = new TLD();
+        }
+    }
+
+    public function domainPart(): string
+    {
+        return $this->domainPart;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/DoubleQuote.php b/vendor/egulias/email-validator/src/Parser/DoubleQuote.php
new file mode 100644
index 0000000..b5335d3
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/DoubleQuote.php
@@ -0,0 +1,91 @@
+checkDQUOTE();
+        if ($validQuotedString->isInvalid()) {
+            return $validQuotedString;
+        }
+
+        $special = [
+            EmailLexer::S_CR => true,
+            EmailLexer::S_HTAB => true,
+            EmailLexer::S_LF => true
+        ];
+
+        $invalid = [
+            EmailLexer::C_NUL => true,
+            EmailLexer::S_HTAB => true,
+            EmailLexer::S_CR => true,
+            EmailLexer::S_LF => true
+        ];
+
+        $setSpecialsWarning = true;
+
+        $this->lexer->moveNext();
+
+        while (!$this->lexer->current->isA(EmailLexer::S_DQUOTE) && !$this->lexer->current->isA(EmailLexer::S_EMPTY)) {
+            if (isset($special[$this->lexer->current->type]) && $setSpecialsWarning) {
+                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
+                $setSpecialsWarning = false;
+            }
+            if ($this->lexer->current->isA(EmailLexer::S_BACKSLASH) && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) {
+                $this->lexer->moveNext();
+            }
+
+            $this->lexer->moveNext();
+
+            if (!$this->escaped() && isset($invalid[$this->lexer->current->type])) {
+                return new InvalidEmail(new ExpectingATEXT("Expecting ATEXT between DQUOTE"), $this->lexer->current->value);
+            }
+        }
+
+        $prev = $this->lexer->getPrevious();
+
+        if ($prev->isA(EmailLexer::S_BACKSLASH)) {
+            $validQuotedString = $this->checkDQUOTE();
+            if ($validQuotedString->isInvalid()) {
+                return $validQuotedString;
+            }
+        }
+
+        if (!$this->lexer->isNextToken(EmailLexer::S_AT) && !$prev->isA(EmailLexer::S_BACKSLASH)) {
+            return new InvalidEmail(new ExpectingATEXT("Expecting ATEXT between DQUOTE"), $this->lexer->current->value);
+        }
+
+        return new ValidEmail();
+    }
+
+    protected function checkDQUOTE(): Result
+    {
+        $previous = $this->lexer->getPrevious();
+
+        if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous->isA(EmailLexer::GENERIC)) {
+            $description = 'https://tools.ietf.org/html/rfc5322#section-3.2.4 - quoted string should be a unit';
+            return new InvalidEmail(new ExpectingATEXT($description), $this->lexer->current->value);
+        }
+
+        try {
+            $this->lexer->find(EmailLexer::S_DQUOTE);
+        } catch (\Exception $e) {
+            return new InvalidEmail(new UnclosedQuotedString(), $this->lexer->current->value);
+        }
+        $this->warnings[QuotedString::CODE] = new QuotedString($previous->value, $this->lexer->current->value);
+
+        return new ValidEmail();
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/FoldingWhiteSpace.php b/vendor/egulias/email-validator/src/Parser/FoldingWhiteSpace.php
new file mode 100644
index 0000000..348a7af
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/FoldingWhiteSpace.php
@@ -0,0 +1,87 @@
+isFWS()) {
+            return new ValidEmail();
+        }
+
+        $previous = $this->lexer->getPrevious();
+
+        $resultCRLF = $this->checkCRLFInFWS();
+        if ($resultCRLF->isInvalid()) {
+            return $resultCRLF;
+        }
+
+        if ($this->lexer->current->isA(EmailLexer::S_CR)) {
+            return new InvalidEmail(new CRNoLF(), $this->lexer->current->value);
+        }
+
+        if ($this->lexer->isNextToken(EmailLexer::GENERIC) && !$previous->isA(EmailLexer::S_AT)) {
+            return new InvalidEmail(new AtextAfterCFWS(), $this->lexer->current->value);
+        }
+
+        if ($this->lexer->current->isA(EmailLexer::S_LF) || $this->lexer->current->isA(EmailLexer::C_NUL)) {
+            return new InvalidEmail(new ExpectingCTEXT(), $this->lexer->current->value);
+        }
+
+        if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous->isA(EmailLexer::S_AT)) {
+            $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
+        } else {
+            $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
+        }
+
+        return new ValidEmail();
+    }
+
+    protected function checkCRLFInFWS(): Result
+    {
+        if (!$this->lexer->current->isA(EmailLexer::CRLF)) {
+            return new ValidEmail();
+        }
+
+        if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
+            return new InvalidEmail(new CRLFX2(), $this->lexer->current->value);
+        }
+
+        //this has no coverage. Condition is repeated from above one
+        if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
+            return new InvalidEmail(new CRLFAtTheEnd(), $this->lexer->current->value);
+        }
+
+        return new ValidEmail();
+    }
+
+    protected function isFWS(): bool
+    {
+        if ($this->escaped()) {
+            return false;
+        }
+
+        return in_array($this->lexer->current->type, self::FWS_TYPES);
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/IDLeftPart.php b/vendor/egulias/email-validator/src/Parser/IDLeftPart.php
new file mode 100644
index 0000000..bedcf7b
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/IDLeftPart.php
@@ -0,0 +1,15 @@
+lexer->current->value);
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/IDRightPart.php b/vendor/egulias/email-validator/src/Parser/IDRightPart.php
new file mode 100644
index 0000000..d2fc1d7
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/IDRightPart.php
@@ -0,0 +1,29 @@
+ true,
+            EmailLexer::S_SQUOTE => true,
+            EmailLexer::S_BACKTICK => true,
+            EmailLexer::S_SEMICOLON => true,
+            EmailLexer::S_GREATERTHAN => true,
+            EmailLexer::S_LOWERTHAN => true,
+        ];
+
+        if (isset($invalidDomainTokens[$this->lexer->current->type])) {
+            return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->current->value), $this->lexer->current->value);
+        }
+        return new ValidEmail();
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/LocalPart.php b/vendor/egulias/email-validator/src/Parser/LocalPart.php
new file mode 100644
index 0000000..5ed29d6
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/LocalPart.php
@@ -0,0 +1,162 @@
+ EmailLexer::S_COMMA,
+        EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
+        EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
+        EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
+        EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
+        EmailLexer::S_COLON => EmailLexer::S_COLON,
+        EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
+        EmailLexer::INVALID => EmailLexer::INVALID
+    ];
+
+    /**
+     * @var string
+     */
+    private $localPart = '';
+
+
+    public function parse(): Result
+    {
+        $this->lexer->startRecording();
+
+        while (!$this->lexer->current->isA(EmailLexer::S_AT) && !$this->lexer->current->isA(EmailLexer::S_EMPTY)) {
+            if ($this->hasDotAtStart()) {
+                return new InvalidEmail(new DotAtStart(), $this->lexer->current->value);
+            }
+
+            if ($this->lexer->current->isA(EmailLexer::S_DQUOTE)) {
+                $dquoteParsingResult = $this->parseDoubleQuote();
+
+                //Invalid double quote parsing
+                if ($dquoteParsingResult->isInvalid()) {
+                    return $dquoteParsingResult;
+                }
+            }
+
+            if (
+                $this->lexer->current->isA(EmailLexer::S_OPENPARENTHESIS) ||
+                $this->lexer->current->isA(EmailLexer::S_CLOSEPARENTHESIS)
+            ) {
+                $commentsResult = $this->parseComments();
+
+                //Invalid comment parsing
+                if ($commentsResult->isInvalid()) {
+                    return $commentsResult;
+                }
+            }
+
+            if ($this->lexer->current->isA(EmailLexer::S_DOT) && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
+                return new InvalidEmail(new ConsecutiveDot(), $this->lexer->current->value);
+            }
+
+            if (
+                $this->lexer->current->isA(EmailLexer::S_DOT) &&
+                $this->lexer->isNextToken(EmailLexer::S_AT)
+            ) {
+                return new InvalidEmail(new DotAtEnd(), $this->lexer->current->value);
+            }
+
+            $resultEscaping = $this->validateEscaping();
+            if ($resultEscaping->isInvalid()) {
+                return $resultEscaping;
+            }
+
+            $resultToken = $this->validateTokens(false);
+            if ($resultToken->isInvalid()) {
+                return $resultToken;
+            }
+
+            $resultFWS = $this->parseLocalFWS();
+            if ($resultFWS->isInvalid()) {
+                return $resultFWS;
+            }
+
+            $this->lexer->moveNext();
+        }
+
+        $this->lexer->stopRecording();
+        $this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@');
+        if (strlen($this->localPart) > LocalTooLong::LOCAL_PART_LENGTH) {
+            $this->warnings[LocalTooLong::CODE] = new LocalTooLong();
+        }
+
+        return new ValidEmail();
+    }
+
+    protected function validateTokens(bool $hasComments): Result
+    {
+        if (isset(self::INVALID_TOKENS[$this->lexer->current->type])) {
+            return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->current->value);
+        }
+        return new ValidEmail();
+    }
+
+    public function localPart(): string
+    {
+        return $this->localPart;
+    }
+
+    private function parseLocalFWS(): Result
+    {
+        $foldingWS = new FoldingWhiteSpace($this->lexer);
+        $resultFWS = $foldingWS->parse();
+        if ($resultFWS->isValid()) {
+            $this->warnings = [...$this->warnings, ...$foldingWS->getWarnings()];
+        }
+        return $resultFWS;
+    }
+
+    private function hasDotAtStart(): bool
+    {
+        return $this->lexer->current->isA(EmailLexer::S_DOT) && $this->lexer->getPrevious()->isA(EmailLexer::S_EMPTY);
+    }
+
+    private function parseDoubleQuote(): Result
+    {
+        $dquoteParser = new DoubleQuote($this->lexer);
+        $parseAgain = $dquoteParser->parse();
+        $this->warnings = [...$this->warnings, ...$dquoteParser->getWarnings()];
+
+        return $parseAgain;
+    }
+
+    protected function parseComments(): Result
+    {
+        $commentParser = new Comment($this->lexer, new LocalComment());
+        $result = $commentParser->parse();
+        $this->warnings = [...$this->warnings, ...$commentParser->getWarnings()];
+
+        return $result;
+    }
+
+    private function validateEscaping(): Result
+    {
+        //Backslash found
+        if (!$this->lexer->current->isA(EmailLexer::S_BACKSLASH)) {
+            return new ValidEmail();
+        }
+
+        if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
+            return new InvalidEmail(new ExpectingATEXT('Found ATOM after escaping'), $this->lexer->current->value);
+        }
+
+        return new ValidEmail();
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Parser/PartParser.php b/vendor/egulias/email-validator/src/Parser/PartParser.php
new file mode 100644
index 0000000..53afb25
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Parser/PartParser.php
@@ -0,0 +1,63 @@
+lexer = $lexer;
+    }
+
+    abstract public function parse(): Result;
+
+    /**
+     * @return Warning[]
+     */
+    public function getWarnings()
+    {
+        return $this->warnings;
+    }
+
+    protected function parseFWS(): Result
+    {
+        $foldingWS = new FoldingWhiteSpace($this->lexer);
+        $resultFWS = $foldingWS->parse();
+        $this->warnings = [...$this->warnings, ...$foldingWS->getWarnings()];
+        return $resultFWS;
+    }
+
+    protected function checkConsecutiveDots(): Result
+    {
+        if ($this->lexer->current->isA(EmailLexer::S_DOT) && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
+            return new InvalidEmail(new ConsecutiveDot(), $this->lexer->current->value);
+        }
+
+        return new ValidEmail();
+    }
+
+    protected function escaped(): bool
+    {
+        $previous = $this->lexer->getPrevious();
+
+        return $previous->isA(EmailLexer::S_BACKSLASH)
+            && !$this->lexer->current->isA(EmailLexer::GENERIC);
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/InvalidEmail.php b/vendor/egulias/email-validator/src/Result/InvalidEmail.php
new file mode 100644
index 0000000..82699ac
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/InvalidEmail.php
@@ -0,0 +1,49 @@
+token = $token;
+        $this->reason = $reason;
+    }
+
+    public function isValid(): bool
+    {
+        return false;
+    }
+
+    public function isInvalid(): bool
+    {
+        return true;
+    }
+
+    public function description(): string
+    {
+        return $this->reason->description() . " in char " . $this->token;
+    }
+
+    public function code(): int
+    {
+        return $this->reason->code();
+    }
+
+    public function reason(): Reason
+    {
+        return $this->reason;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/MultipleErrors.php b/vendor/egulias/email-validator/src/Result/MultipleErrors.php
new file mode 100644
index 0000000..5fa85af
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/MultipleErrors.php
@@ -0,0 +1,56 @@
+reasons[$reason->code()] = $reason;
+    }
+
+    /**
+     * @return Reason[]
+     */
+    public function getReasons() : array
+    {
+        return $this->reasons;
+    }
+
+    public function reason() : Reason
+    {
+        return 0 !== count($this->reasons)
+            ? current($this->reasons)
+            : new EmptyReason();
+    }
+
+    public function description() : string
+    {
+        $description = '';
+        foreach($this->reasons as $reason) {
+            $description .= $reason->description() . PHP_EOL;
+        }
+
+        return $description;
+    }
+
+    public function code() : int
+    {
+        return 0;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php b/vendor/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php
new file mode 100644
index 0000000..96e2284
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php
@@ -0,0 +1,16 @@
+detailedDescription = $details;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php b/vendor/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php
new file mode 100644
index 0000000..bcaefb6
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php
@@ -0,0 +1,16 @@
+exception = $exception;
+        
+    }
+    public function code() : int
+    {
+        return 999;
+    }
+
+    public function description() : string
+    {
+        return $this->exception->getMessage();
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/ExpectingATEXT.php b/vendor/egulias/email-validator/src/Result/Reason/ExpectingATEXT.php
new file mode 100644
index 0000000..07ea8d2
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/Reason/ExpectingATEXT.php
@@ -0,0 +1,16 @@
+detailedDescription;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/ExpectingCTEXT.php b/vendor/egulias/email-validator/src/Result/Reason/ExpectingCTEXT.php
new file mode 100644
index 0000000..64f5f7c
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/Reason/ExpectingCTEXT.php
@@ -0,0 +1,16 @@
+element = $element;
+    }
+
+    public function code() : int
+    {
+        return 201;
+    }
+
+    public function description() : string
+    {
+        return 'Unusual element found, wourld render invalid in majority of cases. Element found: ' . $this->element;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/Result.php b/vendor/egulias/email-validator/src/Result/Result.php
new file mode 100644
index 0000000..fd13e6c
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/Result.php
@@ -0,0 +1,27 @@
+reason = new ReasonSpoofEmail();
+        parent::__construct($this->reason, '');
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Result/ValidEmail.php b/vendor/egulias/email-validator/src/Result/ValidEmail.php
new file mode 100644
index 0000000..fdc882f
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Result/ValidEmail.php
@@ -0,0 +1,27 @@
+dnsGetRecord = $dnsGetRecord;
+    }
+
+    public function isValid(string $email, EmailLexer $emailLexer): bool
+    {
+        // use the input to check DNS if we cannot extract something similar to a domain
+        $host = $email;
+
+        // Arguable pattern to extract the domain. Not aiming to validate the domain nor the email
+        if (false !== $lastAtPos = strrpos($email, '@')) {
+            $host = substr($email, $lastAtPos + 1);
+        }
+
+        // Get the domain parts
+        $hostParts = explode('.', $host);
+
+        $isLocalDomain = count($hostParts) <= 1;
+        $isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], self::RESERVED_DNS_TOP_LEVEL_NAMES, true);
+
+        // Exclude reserved top level DNS names
+        if ($isLocalDomain || $isReservedTopLevel) {
+            $this->error = new InvalidEmail(new LocalOrReservedDomain(), $host);
+            return false;
+        }
+
+        return $this->checkDns($host);
+    }
+
+    public function getError(): ?InvalidEmail
+    {
+        return $this->error;
+    }
+
+    /**
+     * @return Warning[]
+     */
+    public function getWarnings(): array
+    {
+        return $this->warnings;
+    }
+
+    /**
+     * @param string $host
+     *
+     * @return bool
+     */
+    protected function checkDns($host)
+    {
+        $variant = INTL_IDNA_VARIANT_UTS46;
+
+        $host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.');
+
+        $hostParts = explode('.', $host);
+        $host = array_pop($hostParts);
+
+        while (count($hostParts) > 0) {
+            $host = array_pop($hostParts) . '.' . $host;
+
+            if ($this->validateDnsRecords($host)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Validate the DNS records for given host.
+     *
+     * @param string $host A set of DNS records in the format returned by dns_get_record.
+     *
+     * @return bool True on success.
+     */
+    private function validateDnsRecords($host): bool
+    {
+        $dnsRecordsResult = $this->dnsGetRecord->getRecords($host, DNS_A + DNS_MX);
+
+        if ($dnsRecordsResult->withError()) {
+            $this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
+            return false;
+        }
+
+        $dnsRecords = $dnsRecordsResult->getRecords();
+
+        // Combined check for A+MX+AAAA can fail with SERVFAIL, even in the presence of valid A/MX records
+        $aaaaRecordsResult = $this->dnsGetRecord->getRecords($host, DNS_AAAA);
+
+        if (! $aaaaRecordsResult->withError()) {
+            $dnsRecords = array_merge($dnsRecords, $aaaaRecordsResult->getRecords());
+        }
+
+        // No MX, A or AAAA DNS records
+        if ($dnsRecords === []) {
+            $this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
+            return false;
+        }
+
+        // For each DNS record
+        foreach ($dnsRecords as $dnsRecord) {
+            if (!$this->validateMXRecord($dnsRecord)) {
+                // No MX records (fallback to A or AAAA records)
+                if (empty($this->mxRecords)) {
+                    $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Validate an MX record
+     *
+     * @param array $dnsRecord Given DNS record.
+     *
+     * @return bool True if valid.
+     */
+    private function validateMxRecord($dnsRecord): bool
+    {
+        if (!isset($dnsRecord['type'])) {
+            $this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
+            return false;
+        }
+
+        if ($dnsRecord['type'] !== 'MX') {
+            return true;
+        }
+
+        // "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505)
+        if (empty($dnsRecord['target']) || $dnsRecord['target'] === '.') {
+            $this->error = new InvalidEmail(new DomainAcceptsNoMail(), "");
+            return false;
+        }
+
+        $this->mxRecords[] = $dnsRecord;
+
+        return true;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Validation/DNSGetRecordWrapper.php b/vendor/egulias/email-validator/src/Validation/DNSGetRecordWrapper.php
new file mode 100644
index 0000000..25e2fa0
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Validation/DNSGetRecordWrapper.php
@@ -0,0 +1,31 @@
+records;
+    }
+
+    public function withError(): bool
+    {
+        return $this->error;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Validation/EmailValidation.php b/vendor/egulias/email-validator/src/Validation/EmailValidation.php
new file mode 100644
index 0000000..1bcc0a7
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Validation/EmailValidation.php
@@ -0,0 +1,34 @@
+setChecks(Spoofchecker::SINGLE_SCRIPT);
+
+        if ($checker->isSuspicious($email)) {
+            $this->error = new SpoofEmail();
+        }
+
+        return $this->error === null;
+    }
+
+    public function getError() : ?InvalidEmail
+    {
+        return $this->error;
+    }
+
+    public function getWarnings() : array
+    {
+        return [];
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Validation/MessageIDValidation.php b/vendor/egulias/email-validator/src/Validation/MessageIDValidation.php
new file mode 100644
index 0000000..97d1ea7
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Validation/MessageIDValidation.php
@@ -0,0 +1,55 @@
+parse($email);
+            $this->warnings = $parser->getWarnings();
+            if ($result->isInvalid()) {
+                /** @psalm-suppress PropertyTypeCoercion */
+                $this->error = $result;
+                return false;
+            }
+        } catch (\Exception $invalid) {
+            $this->error = new InvalidEmail(new ExceptionFound($invalid), '');
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @return Warning[]
+     */
+    public function getWarnings(): array
+    {
+        return $this->warnings;
+    }
+
+    public function getError(): ?InvalidEmail
+    {
+        return $this->error;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php b/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php
new file mode 100644
index 0000000..c908053
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php
@@ -0,0 +1,105 @@
+validations as $validation) {
+            $emailLexer->reset();
+            $validationResult = $validation->isValid($email, $emailLexer);
+            $result = $result && $validationResult;
+            $this->warnings = [...$this->warnings, ...$validation->getWarnings()];
+            if (!$validationResult) {
+                $this->processError($validation);
+            }
+
+            if ($this->shouldStop($result)) {
+                break;
+            }
+        }
+
+        return $result;
+    }
+
+    private function initErrorStorage(): void
+    {
+        if (null === $this->error) {
+            $this->error = new MultipleErrors();
+        }
+    }
+
+    private function processError(EmailValidation $validation): void
+    {
+        if (null !== $validation->getError()) {
+            $this->initErrorStorage();
+            /** @psalm-suppress PossiblyNullReference */
+            $this->error->addReason($validation->getError()->reason());
+        }
+    }
+
+    private function shouldStop(bool $result): bool
+    {
+        return !$result && $this->mode === self::STOP_ON_ERROR;
+    }
+
+    /**
+     * Returns the validation errors.
+     */
+    public function getError(): ?InvalidEmail
+    {
+        return $this->error;
+    }
+
+    /**
+     * @return Warning[]
+     */
+    public function getWarnings(): array
+    {
+        return $this->warnings;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php b/vendor/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php
new file mode 100644
index 0000000..06885ed
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php
@@ -0,0 +1,41 @@
+getWarnings())) {
+            return true;
+        }
+
+        $this->error = new InvalidEmail(new RFCWarnings(), '');
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getError() : ?InvalidEmail
+    {
+        return $this->error ?: parent::getError();
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Validation/RFCValidation.php b/vendor/egulias/email-validator/src/Validation/RFCValidation.php
new file mode 100644
index 0000000..f59cbfc
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Validation/RFCValidation.php
@@ -0,0 +1,54 @@
+parse($email);
+            $this->warnings = $parser->getWarnings();
+            if ($result->isInvalid()) {
+                /** @psalm-suppress PropertyTypeCoercion */
+                $this->error = $result;
+                return false;
+            }
+        } catch (\Exception $invalid) {
+            $this->error = new InvalidEmail(new ExceptionFound($invalid), '');
+            return false;
+        }
+
+        return true;
+    }
+
+    public function getError(): ?InvalidEmail
+    {
+        return $this->error;
+    }
+
+    /**
+     * @return Warning[]
+     */
+    public function getWarnings(): array
+    {
+        return $this->warnings;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/AddressLiteral.php b/vendor/egulias/email-validator/src/Warning/AddressLiteral.php
new file mode 100644
index 0000000..474ff0e
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/AddressLiteral.php
@@ -0,0 +1,14 @@
+message = 'Address literal in domain part';
+        $this->rfcNumber = 5321;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php b/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php
new file mode 100644
index 0000000..8bac12b
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php
@@ -0,0 +1,13 @@
+message = "Deprecated folding white space near @";
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php b/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php
new file mode 100644
index 0000000..ba57601
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php
@@ -0,0 +1,13 @@
+message = 'Folding whites space followed by folding white space';
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/Comment.php b/vendor/egulias/email-validator/src/Warning/Comment.php
new file mode 100644
index 0000000..6508295
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/Comment.php
@@ -0,0 +1,13 @@
+message = "Comments found in this email";
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php b/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php
new file mode 100644
index 0000000..a257807
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php
@@ -0,0 +1,13 @@
+message = 'Deprecated comments';
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/DomainLiteral.php b/vendor/egulias/email-validator/src/Warning/DomainLiteral.php
new file mode 100644
index 0000000..034388c
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/DomainLiteral.php
@@ -0,0 +1,14 @@
+message = 'Domain Literal';
+        $this->rfcNumber = 5322;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/EmailTooLong.php b/vendor/egulias/email-validator/src/Warning/EmailTooLong.php
new file mode 100644
index 0000000..d25ad12
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/EmailTooLong.php
@@ -0,0 +1,15 @@
+message = 'Email is too long, exceeds ' . EmailParser::EMAIL_MAX_LENGTH;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php b/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php
new file mode 100644
index 0000000..3ecd5bc
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php
@@ -0,0 +1,14 @@
+message = 'Bad char in IPV6 domain literal';
+        $this->rfcNumber = 5322;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php b/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php
new file mode 100644
index 0000000..3f0c2f2
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php
@@ -0,0 +1,14 @@
+message = ':: found at the end of the domain literal';
+        $this->rfcNumber = 5322;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php b/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php
new file mode 100644
index 0000000..742fb3b
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php
@@ -0,0 +1,14 @@
+message = ':: found at the start of the domain literal';
+        $this->rfcNumber = 5322;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php b/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php
new file mode 100644
index 0000000..59c3037
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php
@@ -0,0 +1,14 @@
+message = 'Deprecated form of IPV6';
+        $this->rfcNumber = 5321;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php b/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php
new file mode 100644
index 0000000..d406602
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php
@@ -0,0 +1,14 @@
+message = 'Double colon found after IPV6 tag';
+        $this->rfcNumber = 5322;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php b/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php
new file mode 100644
index 0000000..551bc3a
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php
@@ -0,0 +1,14 @@
+message = 'Group count is not IPV6 valid';
+        $this->rfcNumber = 5322;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php b/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php
new file mode 100644
index 0000000..7f8a410
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php
@@ -0,0 +1,14 @@
+message = 'Reached the maximum number of IPV6 groups allowed';
+        $this->rfcNumber = 5321;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/LocalTooLong.php b/vendor/egulias/email-validator/src/Warning/LocalTooLong.php
new file mode 100644
index 0000000..b46b874
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/LocalTooLong.php
@@ -0,0 +1,15 @@
+message = 'Local part is too long, exceeds 64 chars (octets)';
+        $this->rfcNumber = 5322;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php b/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php
new file mode 100644
index 0000000..bddb96b
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php
@@ -0,0 +1,14 @@
+message = 'No MX DSN record was found for this email';
+        $this->rfcNumber = 5321;
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php b/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php
new file mode 100644
index 0000000..412fd49
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php
@@ -0,0 +1,14 @@
+rfcNumber = 5322;
+        $this->message = 'Obsolete DTEXT in domain literal';
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/QuotedPart.php b/vendor/egulias/email-validator/src/Warning/QuotedPart.php
new file mode 100644
index 0000000..c3e017d
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/QuotedPart.php
@@ -0,0 +1,17 @@
+message = "Deprecated Quoted String found between $prevToken and $postToken";
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/QuotedString.php b/vendor/egulias/email-validator/src/Warning/QuotedString.php
new file mode 100644
index 0000000..c152ec2
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/QuotedString.php
@@ -0,0 +1,17 @@
+message = "Quoted String found between $prevToken and $postToken";
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/TLD.php b/vendor/egulias/email-validator/src/Warning/TLD.php
new file mode 100644
index 0000000..10cec28
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/TLD.php
@@ -0,0 +1,13 @@
+message = "RFC5321, TLD";
+    }
+}
diff --git a/vendor/egulias/email-validator/src/Warning/Warning.php b/vendor/egulias/email-validator/src/Warning/Warning.php
new file mode 100644
index 0000000..7adb3b8
--- /dev/null
+++ b/vendor/egulias/email-validator/src/Warning/Warning.php
@@ -0,0 +1,53 @@
+message;
+    }
+
+    /**
+     * @return int
+     */
+    public function code()
+    {
+        return self::CODE;
+    }
+
+    /**
+     * @return int
+     */
+    public function RFCNumber()
+    {
+        return $this->rfcNumber;
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString(): string
+    {
+        return $this->message() . " rfc: " .  $this->rfcNumber . "internal code: " . static::CODE;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/.php-cs-fixer.php b/vendor/hwi/oauth-bundle/.php-cs-fixer.php
new file mode 100644
index 0000000..207678a
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/.php-cs-fixer.php
@@ -0,0 +1,41 @@
+
+
+For the full copyright and license information, please view the LICENSE
+file that was distributed with this source code.
+EOF;
+
+return (new PhpCsFixer\Config())
+    ->setRules(array(
+        '@Symfony' => true,
+        '@Symfony:risky' => true,
+        '@PHP71Migration' => true,
+        '@PHPUnit60Migration:risky' => true,
+        'combine_consecutive_issets' => true,
+        'combine_consecutive_unsets' => true,
+        'heredoc_to_nowdoc' => false,
+        'header_comment' => ['header' => $header],
+        'no_unreachable_default_argument_value' => false,
+        'ordered_class_elements' => true,
+        'ordered_imports' => true,
+        'php_unit_method_casing' => ['case' => 'camel_case'],
+        'php_unit_set_up_tear_down_visibility' => true,
+        'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'],
+        'array_syntax' => ['syntax' => 'short'],
+        'no_superfluous_phpdoc_tags' => [
+            'allow_mixed' => true,
+            'allow_unused_params' => false,
+        ],
+        'phpdoc_types_order' => false,
+    ))
+    ->setRiskyAllowed(true)
+    ->setFinder(
+        PhpCsFixer\Finder::create()
+            ->in(__DIR__.'/src')
+            ->in(__DIR__.'/tests')
+    )
+;
diff --git a/vendor/hwi/oauth-bundle/CHANGELOG.md b/vendor/hwi/oauth-bundle/CHANGELOG.md
new file mode 100644
index 0000000..e07e8d6
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/CHANGELOG.md
@@ -0,0 +1,531 @@
+Changelog
+=========
+## 2.2.0 (2024-02-28)
+* BC Break: Dropped support for PHP 7.4 & 8.0,
+* Added: Telegram resource owner,
+* Bugfix: Allow `use_authorization_to_get_token` to be configured to `false` for generic OAuth2,
+* Bugfix: Update API version for Facebook to latest available
+* Bugfix: Replace custom authenticator passport with custom badge usage,
+* Bugfix: Fix registration of failure handler,
+* Bugfix: Don't miss refresh token in registration controller,
+* Bugfix: Allow `null` as `$registrationForm` in `RegisterController`,
+* Bugfix: Fix connect functionality with authentication managers,
+
+## 2.1.0 (2023-11-30)
+* BC Break: Dropped support for Symfony: `>6.0, <6.3`,
+* Added: New Passage resource owner,
+* Bugfix: Remove deprecations reported by Symfony 6.4,
+* Chore: Added support for Symfony 7,
+
+## 2.0.0 (2023-10-01)
+* Bugfix: Prevent refreshing non-expired tokens
+* Bugfix: Remove deprecations reported by Symfony 6.x
+* Bugfix: Prevent fatal error when token doesn't have resource owner name set
+
+## 2.0.0-BETA3 (2023-08-20)
+* BC Break: Dropped support for Symfony: 6.0.*,
+* BC Break: Class `Templating\Helper\OAuthHelper` was merged into `Twig\Extension\OAuthRuntime`,
+* BC Break: When resource owner class doesn't define `TYPE` constant or is `null`, then key will be calculated by converting its class name without `ResourceOwner` suffix to `snake_case`, if neither is felt, then `\LogicException` will be thrown,
+* Deprecated: method `UserResponseInterface::getUsername()` was deprecated in favour of `UserResponseInterface::getUserIdentifier()` to match changes in Symfony Security component,
+* Enhancement: `@internal` resourceOwner oauth types in Configuration are calculated automatically by scandir. All classes extended from `GenericOAuth[X]ResourceOwner` get `oauth[X]` type. If class only implements ResourceOwnerInterface then its oauth type is `unknown`. ResourceOwner key (parameter `type` in configs) should have defined ResourceOwner::TYPE constant. Each user defined custom ResourceOwner class that implemented `ResourceOwnerInterface` will be registered automatically. If `autoconfigure` option is disabled user have to add the tag `hwi_oauth.resource_owner` to the service definition,
+* Enhancement: Class `ConnectController` was split into two smaller ones, `Connect\ConnectController` & `Connect\RegisterController`,
+* Bugfix: Added `OAuth1ResourceOwner` & `OAuth2ResourceOwner` to cover case of implementing custom oauth resource owners,
+* Bugfix: Fixed Authorization Header in `CleverResourceOwner::doGetRequest`,
+* Bugfix: Catch also the `TransportExceptionInterface` in `AbstractResourceOwner::getResponseContent()` method,
+* Bugfix: Current matched Firewall is respected during generation of resource owner check path links,
+* Bugfix: Prevent fatal error in `OAuthUserProvider::loadUserByOAuthUserResponse()` when `nickname` is not available in OAuth response,
+* Bugfix: Use newer version of `firebase/php-jwt` library,
+* Chore: Removed not used Symfony Templating component
+
+## 2.0.0-BETA2 (2022-01-16)
+* Deprecated: configuration parameter `firewall_names`, firewalls are now computed automatically - all firewalls that have defined `oauth` authenticator/provider will be collected,
+* Added: Ability to automatically refresh expired access tokens (only for derived from `GenericOAuth2ResourceOwner` resource owners), if option `refresh_on_expire` set to `true`,
+* Enhancement: Refresh token listener is disabled by default and will only be enabled if at least one resource owner has option `refresh_on_expure` set to `true`,
+* Enhancement: (`@internal`) Removed/replaced redundant argument `$firewallNames` from controllers. If controller class was copied and replaced, adapt list of arguments: In controller use `$resourceOwnerMapLocator->getFirewallNames()`,
+* Bugfix: `RefreshTokenListener` cannot be lazy. If current firewall is lazy (or anonymous: lazy) then current auth token is often initializing on `kernel.response`. In this case new access token will not be stored in session. Therefore, the expired token will be refreshed on each request,
+* Bugfix: `InteractiveLoginEvent` will be triggered also for `OAuthAuthenticator`,
+* Maintain: Changed config files from `*.xml` to `*.php` (services and routes). Xml routing configs `connect.xml`, `login.xml` and `redirect.xml` are steel present but deprecated. Please use `*.php` variants in your includes instead.
+
+## 2.0.0-BETA1 (2021-12-10)
+* BC Break: Dropped PHP 7.3 support,
+* BC Break: Dropped support for Symfony: >=5.1 & <5.4,
+* BC Break: `OAuthExtension` is now a lazy Twig extension using a Runtime,
+* BC Break: removed support for `FOSUserBundle`,
+* BC Break: changed `process()` argument for `Form/RegistrationFormHandlerInterface`, from `Form $form` to `FormInterface $form`,
+* BC Break: changed form class name in template `Resources/views/Connect/connect_confirm.html.twig` from `fos_user_registration_register` to `registration_register`,
+* BC Break: removed configuration option `fosub` from `oauth_user_provider`,
+* BC Break: removed configuration options `hwi_oauth.fosub`, & all related DI parameters,
+* BC Break: removed DI parameter `hwi_oauth.registration.form.factory` in favour of declaring form class name as DI parameter: `hwi_oauth.connect.registration_form`,
+* BC Break: changed `ResourceOwnerMapInterface::hasResourceOwnerByName` signature, update if you use a custom resource owner,
+* BC Break: changed `ResourceOwnerMapInterface::getResourceOwnerByName` signature, update if you use a custom resource owner,
+* BC Break: changed `ResourceOwnerMapInterface::getResourceOwnerByRequest` signature, update if you use a custom resource owner,
+* BC Break: changed `ResourceOwnerMapInterface::getResourceOwnerCheckPath` signature, update if you use a custom resource owner,
+* BC Break: `ResourceOwnerMap` uses service locator instead of DI container,
+* BC Break: Removed abstract services: `hwi_oauth.abstract_resource_owner.generic`, `hwi_oauth.abstract_resource_owner.oauth1` & `hwi_oauth.abstract_resource_owner.oauth2`,
+* BC Break: Removed `setName()` method from `OAuth/ResourceOwnerInterface`,
+* BC Break: changed `__construct()` argument for `OAuth/ResourceOwner/AbstractResourceOwner`, from `HttpMethodsClient $httpClient` to `HttpClientInterface $httpClient`,
+* BC Break: replaced `php-http/httplug-bundle` with `symfony/http-client`
+* BC Break: removed `hwi_oauth.http` configuration,
+* BC Break: reworked bundles structure to match Symfony best practices:
+  - bundle code moved to: `src/`,
+  - tests moved to: `tests/`,
+  - docs moved from `Resources/doc` into: `docs/`,
+* BC Break: routes provided by bundle now have `methods` requirements:
+  - `hwi_oauth_connect_service`: `GET` & `POST`,
+  - `hwi_oauth_connect_registration`: `GET` & `POST`,
+  - `hwi_oauth_connect`: `GET`,
+  - `hwi_oauth_service_redirect`: `GET`,
+* Added support for PHP 8.1,
+* Added support for Symfony 5.6,
+
+## 1.4.5 (2021-12-08)
+* Bugfix: Fixed: BC break by restoring wrongly moved `AbstractOAuthToken::getCredentials()` method,
+
+## 1.4.3 (2021-12-07)
+* Bugfix: Fixed support for PHP 8.1,
+* Bugfix: Fixed support for Symfony 5.4, 
+* Bugfix: Fixed `VkontakteResourceOwner` option: `api_version` to not point to deprecated one,
+* Bugfix: `RequestStack::getMasterRequest()` is deprecated since Symfony 5.3, use `RequestStack::getMainRequest()` if exists,
+* Maintain: Added `GenericOAuth1ResourceOwnerTestCase`, `GenericOAuth2ResourceOwnerTestCase` & `ResourceOwnerTestCase` test case classes for easier unit testing custom resource owners
+
+## 1.4.2 (2021-08-09)
+* Bugfix: remove `@final` declaration from `OAuthFactory` & `FOSUBUserProvider`,
+* Maintain: added `.gitattributes` to reduce amount of code in archives,
+
+## 1.4.1 (2021-07-28)
+* Bugfix: Define missing `hwi_oauth.connect.confirmation` parameter,
+* Bugfix: Added missing success/failure handlers,
+
+## 1.4.0 (2021-07-26)
+* BC Break: dropped Symfony 5.0 support as it is EOL,
+* BC Break: dropped PHP 7.2 support as it is EOL,
+* BC Break: changed `__construct()` argument for `OAuth/RequestDataStorage/SessionStorage`, from `SessionInterface $session` to `RequestStack $requestStack`,
+* BC Break: all internal classes are "softly" marked as `final`,
+* Added: Symfony 5.1 Security system support,
+* Added: Forward compatibility layer for session service deprecation,
+* Added: state support for service authentication URL's,
+* Added: ability to change the response after `HWIOAuthEvents::CONNECT_COMPLETED` is fired,
+* Added: PHPStan static analyse into CI,
+* Fixed: `OAuthProvide` to properly refresh data inside tokens,
+* Fixed: PHP notice in `AppleResourceOwner`,
+* Fixed: use new GitHub API in `GitHubResourceOwner`,
+* Fixed: functional tests with & without FOSUserBundle,
+* Fixed: controller don't depend on service container if possible,
+* Maintain: removed `Wunderlist` resource owner,
+* Maintain: removed several Symfony BC layers,
+* Maintain: removed Prophecy in favour of PHPUnit mocking,
+
+## 1.3.0 (2021-01-03)
+* BC Break: dropped support for Symfony `<4.4`,
+* BC Break: dropped support for Doctrine Bundle `<2.0`,
+* Added PHP 8 support,
+* Upgraded Facebook API to v8.0,
+* Upgraded Twitch resource owner to incorporate latest Twitch API,
+* Fixed: undefined `id_token` exception in Azure resource owner,
+* Docs: changed firewall name to match flex receipt,
+* Maintain: moved from Travis CI to Github Actions,
+
+## 1.2.0 (2020-10-19)
+* BC Break: dropped Symfony 4.3 support,
+* Added `first_name` & `last_name` in AzureResourceOwner,
+* Added: support for multiple OAuth2 state parameters,
+* Added: Apple resource owner,
+* Fixed: updated Azure `authorization` & `access_token` urls,
+* Fixed: Doctrine persistence deprecation errors,
+* Allow modification of the response in `FilterUserResponseEvent`,
+
+## 1.1.0 (2020-04-06)
+* Added Symfony 5 support,
+* Added domain whitelist service to avoid open redirect on `target_path`,
+* Fixed: session service was not injected in `LoginController`,
+* Fixed: missing `setContainer` call to service configuration for `LoginController`,
+* Fixed: client id and client secret must be set in `Auth0ResourceOwner::doGetTokenRequest`,
+* Fixed: missing client id and client secret in `Auth0ResourceOwner`,
+* Twig dependency on `LoginController` is now optional,
+
+## 1.0.0 (2020-01-17)
+* Dropped support for PHP 5.6, 7.0 and 7.1,
+* Dropped support for FOSUserBundle 1.3,
+* Dropped support for Symfony 2.8,
+* Minimum Symfony 3 requirement is 3.4,
+* Minimum Symfony 4 requirement is 4.3,
+* Fixed: WindowsLive Resource Owner token request,
+* Fixed: Update Facebook API to v3.1,
+* Fixed: Update Linkedin API to v2,
+* Fixed: YahooResourceOwner::doGetUserInformationRequest uses wrong arguments,
+* Fixed: Symfony deprecation warning in `symfony/config`,
+* Fixed: SensioConnect now uses new API URLs,
+* Fixed: Do not add Authorization header if no client_secret is present,
+* Fixed: `LoginController::connectAction` should not fail if no token is available,
+* Added: Genius.com resource owner,
+* Added: HTTPlug 2.0 support,
+* Added: Keycloak resource owner,
+* Added: The controller is now available as a service,
+* Added: Allow to use HTTP Basic auth for token request,
+* [BC break] Class `Configuration` has been marked final,
+* [BC break] Class `ConnectController` has been marked final,
+* [BC break] Class `HWIOAuthExtension` has been marked final,
+* [BC break] Class `OAuthExtension` has been marked final,
+* [BC break] Class `SetResourceOwnerServiceNameCompilerPass` has been marked final,
+* [BC break] Class `ConnectController` extends `AbstractController` instead of `Controller`,
+* [BC break] Service `hwi_oauth.http_client` has been marked private,
+* [BC break] Service `hwi_oauth.security.oauth_utils` has been marked private,
+* [BC break] Several service class parameters have been removed,
+
+## 0.6.3 (2018-07-31)
+* Fixed: Vkontakte profile picture & nickname path,
+* Fixed: `Content-Length` header must be a string,
+* Fixed: Upgraded GitLab end point to v4,
+* Fixed: Resource owner map parameters must be public,
+* Fixed: Azure resource owner `infos_url` should not be empty,
+* Fixed: Don't start sessions twice & don't start sessions if already started,
+* Fixed: Updated BitBucket docs,
+* Added: Further compatibility changes for Symfony 4.1,
+* Added: LinkedIn `first-` & `last-` names,
+* Added: Facebook profile picture
+
+## 0.6.2 (2018-03-28)
+* Fixed: VK requires API version now,
+* Fixed: Updated Slack resource owner to use new Slack API methods,
+* Fixed: Changing authorization and access token to v2 for LinkedIn,
+* Fixed: Fix double call of `getUserInformation()` in `ConnectController`,
+* Fixed: Fix serialization of `AccountNotLinkedException`,
+* Fixed: Check for grant_rule value `IS_AUTHENTICATED_FULLY` in DI configuration,
+* Fixed: Don't execute `OAuthProvider::refreshAccessToken()` when there is no refresh token
+
+## 0.6.1 (2018-01-23)
+* BC BREAK: Replaced `PHPUnit_Framework_TestCase` with `PHPUnit\Framework\TestCase` in tests,
+* Added: Implemented `getUserInformation()` for Dropbox v2,
+* Fixed: Headers passed to `httpRequest()` method in various resource owners,
+* Fixed: Marked some services as `public` to make code compatible with Symfony 4
+
+## 0.6.0 (2017-12-01)
+* BC BREAK: Fully replaced Buzz library with usage of HTTPlug & Guzzle 6,
+* BC BREAK: `hwi.http_client` config options are remove. HTTP configuration must rely on the HTTPlug client,
+* BC BREAK: Template engine other than Twig are no longer supported,
+* BC BREAK: Option `hwi_oauth.templating_engine` was removed,
+* Added: Symfony 4 support,
+* Added: `php-http/httplug-bundle` support, to auto-provide needed HTTPlug services and get full Symfony integration,
+* Added: `hwi.http.client` and `hwi.http.message_factory` config keys to provide your own HTTPlug services,
+* Added: `HWIOAuthEvents`,
+* Added: `ResourceOwnerInterface::addPaths()` method for easier managing paths in resource owners,
+* Fixed: Update Facebook API to v2.8,
+
+## 0.5.3 (2017-01-08)
+* Fixed: Bitbucket2 resource owner,
+* Fixed: GitHub resource owner documentation,
+* Fixed: Don't require any form for the connect feature,
+* Fixed: Uncaught exception with custom error page,
+* Fixed: `php-cs-fixer` updated to latest version & run on base code
+
+## 0.5.2 (2016-12-12)
+* Fixed: Prevent uncaught exception when redirecting to invalid route,
+* Fixed: Add more details too exception when account was not linked,
+* Fixed: Odnoklassinki resource owner,
+* Fixed: Office365 resource owner,
+* Fixed: StackExchange resource owner,
+* Fixed: WeChat resource owner,
+* Fixed: WindowsLive resource owner
+
+## 0.5.1 (2016-10-03)
+* Fixed error that could occur with message "302 Header already sent",
+* Exclude tests from Composer autoloader
+
+## 0.5.0 (2016-09-11)
+* Fixed: `OAuthHelper` should fallback to new `Request` in case of receiving `null`,
+* Fixed: Better `FOSUserBundle` integration,
+* Fixed: Serialization issue in `WechatResourceOwner`,
+* Fixed: Incorrect refresh token in `WechatResourceOwner`,
+* Fixed: Broken `TrelloResourceOwner`,
+* Fixed: Removed dead code in `OAuthProvider`,
+* Fixed: Update Facebook API to v2.7,
+* Added: Symfony 3 support,
+* Added: Redirect to `target_path` after successful registration/connection,
+* Added: Asana resource owner,
+* Added: Bitbucket resource owner,
+* Added: Clever resource owner,
+* Added: Itembase resource owner,
+* Added: Jawbon resource owner,
+* Added: Office365 resource owner,
+* Added: Wunderlist resource owner,
+* Added: Hungarian translation
+
+## 0.4.3 (2016-09-11)
+* Fixed: Request parameters are not copied into new Request on forward,
+* Fixed: Fixed scope deprecating message,
+* Fixed: Resolved deprecated message in ConnectController,
+* Fixed: Removed usage of deprecated code in tests
+
+## 0.4.2 (2016-07-27)
+* Fixed: Change Discogs URL from http to https,
+* Fixed: Update Facebook API URLs to not use outdated ones
+
+## 0.4.1 (2016-03-08)
+* Fixed: Remove usage of deprecated Twig function `form_enctype` & replace with usage of `form_start`/`form_end`,
+* Fixed: Mark as not fully compatible with Symfony `~3.0`,
+* Fixed: Multiple firewalls can now have different resource owners,
+* Fixed: Wrong URL generated for Safesforce resource owner,
+* Added: `include_email` option into Twitter resource owner,
+* Added: Hungarian translation,
+* Added: Documentation about FOSUser integration
+
+## 0.4.0 (2015-12-04)
+* [BC break] Added `UserResponseInterface#getFirstName()` method, also a new default path `firstname`
+  was added, this path holds the first name of user,
+* [BC break] Added `UserResponseInterface#getLastName()` method, also a new default path `lastname`
+  was added, this path holds the last name of user,
+* [BC break] Added `UserResponseInterface::getOAuthToken()` & basic implementation in `AbstractUserResponse`,
+* [BC break] `GenericOAuth1ResourceOwner::getRequestToken()` is now public method (was protected),
+* Added: configuration parameter `firewall_name` (will be removed in next major version)
+  renamed to `firewall_names` to support multiple firewalls,
+* Added: configuration parameter: `failed_auth_path` which contains route name, on which user
+  will be redirected after failure when connecting accounts (i.e. user denies connection),
+* Added: `appsecret_proof` functionality support to the Facebook resource owner,
+* Added: `sandbox` functionality support to the Salesforce resource owner,
+* Added Auth0 resource owner,
+* Added Azure resource owner,
+* Added BufferApp resource owner,
+* Added Deezer resource owner,
+* Added Discogs resource owner,
+* Added EveOnline resource owner,
+* Added Fiware resource owner,
+* Added Hubic resource owner,
+* Added Paypal resource owner,
+* Added Reddit resource owner,
+* Added Runkeeper resource owner,
+* Added Slack resource owner,
+* Added Spotify resource owner,
+* Added Soundcloud resource owner,
+* Added Strava resource owner,
+* Added Toshl resource owner,
+* Added Trakt resource owner,
+* Added Wechat resource owner,
+* Added Wordpress resource owner,
+* Added Xing resource owner,
+* Added Youtube resource owner,
+* Fixed: Revoking tokens for Facebook & Google resource owners,
+* Fixed: Instagram allows only GET calls to fetch user details,
+* Fixed: `ResourceOwnerMap` no longer depends on deprecated `ContainerAware` class,
+* Fixed: Wrong usage of `json_decode` in Mail.ru resource owner,
+* Fixed: Transform storage exceptions in OAuth1 resource owners into `AuthenticationException`
+* Fixed: Default scopes & fields for VKontakte resource owner
+
+## 0.3.9 (2015-08-28)
+* Fix: Remove deprecated Twig features
+* Fix: Undefined variable in `FOSUBUserProvider::refreshUser`
+* Fix: Restore property accessor for Symfony 2.3
+
+## 0.3.8 (2015-05-04)
+* Fix: Remove BC break for Symfony < 2.5,
+* Fix: Compatibility issues with Symfony 2.6+,
+* Fix: Deprecated graph URLs for `FacebookResourceOwner`
+
+## 0.3.7 (2014-11-15)
+* Fix: `SessionStorage::save()` could throw php error,
+* Fix: `OAuthToken::isExpired()` always returned `false`,
+* Fix: `FoursquareResourceOwner`, `TwitchResourceOwner`, `SensioConnectResourceOwner`
+  not working with bearer header,
+* Fix: Don't use deprecated fields in `FacebookResourceOwner`,
+* Fix: `FOSUBUserProvider::refreshUser()` always returning old user,
+
+## 0.3.6 (2014-06-02)
+* Fix: `InstagramResourceOwner` regression while getting user details,
+* Fix: Add smooth migration for session (de)serialization
+
+## 0.3.5 (2014-05-30)
+* Fix: `LinkedinResourceOwner` regression while getting user details,
+* Fix: OAuth `revoke` functionality to be available wider,
+* Fix: Removed undocumented functionality from `SinaWeiboResourceOwner`,
+* Fix: Always remove default ports from URLs to match OAuth 1.0a, Spec: 9.1.2
+
+## 0.3.4 (2014-05-12)
+* Fix: Instagram OAuth redirect to one url,
+* Fix: `FOSUBUserProvider` should also implement `UserProviderInterface`,
+* Fix: `YahooResourceOwner` `infos_url` to use new format,
+* Fix: Send authorization via headers instead of URL parameter,
+* Fix: `GithubResourceOwner` revoke method,
+* Fix: Add login routing documentation note
+
+## 0.3.3 (2014-02-17)
+* Fix: Incorrect redirect URL when no parameters are set,
+* Fix: Add missing parameter `prompt` for `GoogleResourceOwner`,
+* Fix: `WordpressResourceOwner` user details API call,
+* Fix: PHP Notice when `oauth_callback_confirmed` was set too `false`,
+* Fix: PHP Fatal when session returns boolean instead of object,
+* Fix: Add missing query parameters for `FacebookResourceOwner`
+
+## 0.3.2 (2014-02-07)
+* Fix: Prevent `SessionUnavailableException` when returns back from service,
+* Fix: `EntityUserProvider` should implement `UserProviderInterface`,
+* Fix: `createdAt` property was missing when serializing the `OAuthToken`,
+* Added Italian translations
+
+## 0.3.1 (2014-01-17)
+* Fix: Change Twitter API call to use SSL URL,
+* Fix: Problems with options in `VkontakteResourceOwner`,
+* Fix: Problems with OAuth 1.0a token & `YahooResourceOwner`,
+* Fix: Throw exception in `FOSUBUserProvider` when username is missing
+* Added SalesForce resource owner
+
+## 0.3.0 (2013-09-28)
+* [BC break] `AccountConnectorInterface::connect()` method now requires the first
+  parameter to be instance of `Symfony\Component\Security\Core\User\UserInterface`
+* [BC break] `ConnectController::authenticateUser()` method now requires the first
+  parameter to be instance of `Symfony\Component\HttpFoundation\Request`
+* [BC break] Removed `AbstractResourceOwner::addOptions()` method
+* [BC break] `OAuthUtils::getAuthorizationUrl()` & `OAuthUtils::getLoginUrl()` methods
+  now expect first parameter to be instance of `Symfony\Component\HttpFoundation\Request`
+* [BC break] LinkedIn resource owner now uses OAuth2 approach, visit official
+  web page for details how to migrate: https://developer.linkedin.com/documents/authentication#migration
+* [BC break] Dropbox resource owner now uses OAuth2 approach
+* Added ability to merge response parts into single path
+* Added Bitly resource owner
+* Added Box resource owner
+* Added Dailymotion resource owner
+* Added DeviantArt resource owner
+* Added Eventbrite resource owner
+* Added Mail.ru resource owner
+* Added Sina Weibo resource owner
+* Added QQ.com resource owner
+* Added Trello resource owner
+* Added Wordpress resource owner
+
+## 0.3.0-alpha2 (2013-07-29)
+* [BC break] Added `ResourceOwnerInterface::isCsrfTokenValid()` method
+* [BC break] Removed `OAuth1RequestTokenStorageInterface` along with the implementations
+* [BC break] `AbstractResourceOwner::__construct()` now requires `RequestDataStorageInterface`
+  instance as last argument
+* Fix: Yandex resource owner using invalid parameter when requesting user data
+* Fix: To prevent unusual content headers response from resource owners should
+  be first threaten as json and only in case of failure threaten as query text
+* Fix: Instagram resource owner is not able to receive user data more than once
+* Added ability to disable confirmation page when connecting accounts
+* Added CSRF protection for OAuth2 providers (turned off by default)
+* Added `RequestDataStorageInterface` along with implementation
+* Added Stereomood resource owner
+
+## 0.3.0-alpha1 (2013-07-03)
+* [BC break] `GenericOAuth2ResourceOwner::getAccessToken()` now returns an array
+  instead of a string. This array contains the access token and its 'expires_in'
+  value, along with any other parameters returned from the authentication provider
+* [BC break] Added `OAuthAwareExceptionInterface#setToken()`, `OAuthAwareExceptionInterface#getRefreshToken()`,
+  `OAuthAwareExceptionInterface#getRawToken()`, `OAuthAwareExceptionInterface#getExpiresIn()`
+  methods
+* [BC break] Renamed `AbstractResourceOwner::doGetAccessTokenRequest` to `doGetTokenRequest`
+* [BC break] Removed `AdvancedPathUserResponse` & `AdvancedUserResponseInterface`
+* [BC break] Added `UserResponseInterface#getEmail()`, `UserResponseInterface#getProfilePicture()`,
+  `UserResponseInterface#getRefreshToken()`, `UserResponseInterface#getExpiresIn()`,
+  `UserResponseInterface#setOAuthToken()` methods
+* [BC break] Removed `UserResponseInterface::setAccessToken()` method
+* [BC break] Removed `AbstractUserResponse::getOAuthToken()` method because it was ambiguous
+* [BC break] `PathUserResponse#setPaths()` method no longer overwrite default paths
+* [BC break] `PathUserResponse#getPath()` method no longer throws an exception if path
+  not exists
+* [BC break] `PathUserResponse#getValueForPath()` removed second argument from this method,
+  it will not throw exception anymore if response or value is missing, but now will return
+  `null` instead
+* [BC break] Added `ResourceOwnerInterface#getOption($name)` method
+* [BC break] `ResourceOwnerInterface#getUserInformation()` now must receive array (`$accessToken`)
+  as first parameter, also added second parameter (`$extraParameters`) to be consistent
+  along all implementations
+* Added `OAuthToken::getRefreshToken()`, `OAuthToken::setRefreshToken()`, `OAuthToken::getExpiresIn()`,
+  `OAuthToken::setExpiresIn()`, `OAuthToken::getRawToken()`, `OAuthToken::setRawToken()`
+* Added `AbstractResourceOwner#addOptions()` & `ResourceOwnerInterface#setOption($name, $value)`
+  methods which allows easy overwriting resource specific options
+* Added support for options: `access_type`, `request_visible_actions`, `approval_prompt` & `hd`
+  in Google resource owner
+* Added 37signals resource owner
+* Added Amazon resource owner
+* Added Bitbucket resource owner
+* Added Disqus resource owner
+* Added Dropbox resource owner
+* Added Flickr resource owner
+* Added Instagram resource owner
+* Added Odnoklassniki resource owner
+* Added Yandex resource owner
+
+## 0.2.10 (2013-12-09)
+* Fix: use `Symfony\Component\Security\Core\User\UserInterface` in `EntityUserProvider::refreshUser`
+* Fix: made `SessionStorage` compatible with Symfony 2.0
+
+## 0.2.9 (2013-09-25)
+* Fix: Regression done in version `0.2.8` blocking usage without `FOSUserBundle`
+* Fix: `OAuthUtils::getAuthorizationUrl()` ignoring given redirect URL
+
+## 0.2.8 (2013-09-19)
+* Fix: Added missing parts in user providers like: `loadUserByUsername()`
+  or `refreshUser()` methods
+* Fix: Registering of user provider services
+* Fix: Make `OAuthUtils::signRequest()` compatible with OAuth1.0a specification
+
+## 0.2.7 (2013-08-03)
+* Fix: Polish oauth error detection to cover cases from i.e. Facebook resource owner
+* Fix: Changed authorization url for Vkontakte resource owner
+
+## 0.2.6 (2013-06-24)
+* Fix: Use same check for FOSUserBundle compatibility to prevent strange errors
+  with calls of undefined services
+* Fix: User-land aliased (resource owner) services have the appropriate name
+
+## 0.2.5 (2013-05-29)
+* Fix: Use user identifier represented as string for Twitter to prevent issues with
+  losing accuracy for large numbers (i.e. Javascript) or type comparison (i.e. MongoDB)
+* Fix: Don't depend on `arg_separator.output` data for URL generation to prevent issues
+
+## 0.2.4 (2013-05-15)
+* Fix: Throw `Symfony\Component\Security\Core\Exception\AccessDeniedException`
+  & `Symfony\Component\HttpKernel\Exception\NotFoundHttpException` instead of `\Exception`
+  to make cases more clear
+* Fix: Detect `oauth_problem` as authorization error and inform user instead logging error
+  in background
+* Fix: Request extra parameters should have higher priority than default
+* Fix: How urls are build in resource owners
+* Fix: Missing parameter in `YahooResourceOwner`
+
+## 0.2.3 (2013-05-06)
+* Added `AbstractUserResponse::getOAuthToken()` method to allow fetching only OAuth token details
+* Added french translation
+* Fix: FB incompatibility with 'error' field in response
+
+## 0.2.2 (2013-04-15)
+* Fix: FOSUB registration form handler
+* Fix: Use API 1.1 for Twitter, not the deprecated 1.0
+
+## 0.2.1 (2013-03-27)
+* Fixed issue with FOSUserBundle 2.x integration
+
+## 0.2.0 (2013-03-26)
+* Added support for a `target_path_parameter` in order to control the redirect path after login
+* Added `hwi_oauth_authorization_url()` twig helper function
+* Added Jira resource owner
+* Added Yahoo resource owner
+* Added setting `realm` in configuration
+* Added support for FOSUserBundle 2.x integration
+* Added Stack Exchange resource owner
+* Fix: configuration parameter `firewall_name` is required
+* Fix: prevent throwing `AlreadyBoundException` when using FOSUserBundle 1.x integration
+* Fix: check for availability of `profilePicture` in views before calling it
+* Fix: `InMemoryProvider` now shows user nickname as name instead of unique identifier
+* Fix: don't set `realm` option if is empty in request headers
+* Fix: for infinity loop blockade and error token response handling
+
+## 0.1-alpha (2012-08-27)
+* [BC break] Renamed path `username` to `identifier` to make it more clear that this path should
+  hold the unique user identifier (previously `username`)
+* [BC break] Method `UserResponseInterface#getUsername()` now always returns a real
+  unique user identifier, and uses path `identifier`
+* [BC break] `OAuth1RequestTokenStorageInterface#save()` second param `$token` must
+  now be an array
+* [BC break] Configuration type 'generic' is renamed to 'oauth2'
+* [BC break] `redirect.xml` routing has to be imported. See the setup docs
+* Added `UserResponseInterface#getRealName()` method, also a new default path `realname`
+  was added, this path holds the real name of user
+* Added `UserResponseInterface#getNickName()` method, also a new default path `nickname`
+  was added, this path holds the nickname of user
+* Added `UserResponseInterface#getAccessToken()` and `UserResponseInterface#setAccessToken`
+* Added `OAuthToken#getCredentials()` returns an empty string to be consistent with
+  the security component. The access token can still be retrieved from the
+  `getAccessToken()` method
+* Added change that forces all authentication requests are now redirected to the login path
+* Added change that makes `firewall_name` option required setting
+* Added OAuth 1.0a support (linkedin/twitter/generic)
diff --git a/vendor/hwi/oauth-bundle/LICENSE b/vendor/hwi/oauth-bundle/LICENSE
new file mode 100644
index 0000000..e8f2f6f
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012-2019 Hardware Info - https://hardware.info
+
+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.
diff --git a/vendor/hwi/oauth-bundle/README.md b/vendor/hwi/oauth-bundle/README.md
new file mode 100644
index 0000000..7904a10
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/README.md
@@ -0,0 +1,90 @@
+HWIOAuthBundle
+==============
+
+[![Build Status](https://github.com/hwi/HWIOAuthBundle/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/hwi/HWIOAuthBundle/actions/workflows/ci.yaml) [![Latest Stable Version](https://poser.pugx.org/hwi/oauth-bundle/v/stable.svg)](https://packagist.org/packages/hwi/oauth-bundle) [![Total Downloads](https://poser.pugx.org/hwi/oauth-bundle/downloads.svg)](https://packagist.org/packages/hwi/oauth-bundle) [![License](https://poser.pugx.org/hwi/oauth-bundle/license.svg)](https://packagist.org/packages/hwi/oauth-bundle)
+
+The HWIOAuthBundle adds support for authenticating users via OAuth1.0a or OAuth2 in Symfony.
+
+> __Note__: this bundle adds easy way to implement any of OAuth1.0a or OAuth2 provider!
+
+Installation
+------------
+
+All the installation instructions are located in the documentation, check it for a specific version:
+
+* [__2.x__](https://github.com/hwi/HWIOAuthBundle/blob/master/docs/1-setting_up_the_bundle.md) (current) - with support for Symfony: `^5.4`, `^6.3` & `^7.0` (PHP: `^8.1`),
+
+Documentation
+-------------
+
+The bulk of the documentation is stored in the `Resources/doc/index.md`
+file in this bundle. Read the documentation for version:
+
+* [__2.x__](https://github.com/hwi/HWIOAuthBundle/blob/master/docs/index.md)
+
+This bundle contains support for 58 different providers:
+* 37signals,
+* Amazon,
+* Apple,
+* Asana,
+* Auth0,
+* Azure,
+* Bitbucket,
+* Bitly,
+* Box,
+* BufferApp,
+* Clever,
+* Dailymotion,
+* Deezer,
+* DeviantArt,
+* Discogs,
+* Disqus,
+* Dropbox,
+* EVE Online,
+* Facebook,
+* FI-WARE,
+* Flickr,
+* Foursquare,
+* Genius,
+* GitHub,
+* Google,
+* Hubic,
+* Instagram,
+* Itembase,
+* Jawbone,
+* JIRA,
+* Keycloak,
+* LinkedIn,
+* Mail.ru
+* Odnoklassniki,
+* Office365,
+* Passage,
+* PayPal,
+* QQ,
+* RunKeeper,
+* Salesforce,
+* Sensio Connect,
+* Sina Weibo,
+* Slack,
+* Soundcloud,
+* Spotify,
+* Stack Exchange,
+* Stereomood,
+* Strava,
+* Toshl,
+* Trakt,
+* Trello,
+* Twitch,
+* Twitter,
+* VKontakte,
+* Windows Live,
+* Wordpress,
+* XING,
+* Yahoo,
+* Yandex,
+* Youtube
+
+License
+-------
+
+This bundle is under the MIT license. See the complete [license in the bundle](https://github.com/hwi/HWIOAuthBundle/blob/master/LICENSE).
diff --git a/vendor/hwi/oauth-bundle/SECURITY.md b/vendor/hwi/oauth-bundle/SECURITY.md
new file mode 100644
index 0000000..1a8f643
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/SECURITY.md
@@ -0,0 +1,12 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported          |
+| ------- | ------------------ |
+| 2.1  | :white_check_mark: |
+| <2.1 | :x:                |
+
+## Reporting a Vulnerability
+
+If you discover a security vulnerability, please send an email to: stloyd@gmail.com
diff --git a/vendor/hwi/oauth-bundle/composer.json b/vendor/hwi/oauth-bundle/composer.json
new file mode 100644
index 0000000..434e1b4
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/composer.json
@@ -0,0 +1,174 @@
+{
+    "name":        "hwi/oauth-bundle",
+    "type":        "symfony-bundle",
+    "homepage":    "https://github.com/hwi/HWIOAuthBundle",
+    "license":     "MIT",
+    "description": "Support for authenticating users using both OAuth1.0a and OAuth2 in Symfony.",
+    "keywords":    [
+        "authentication",
+        "firewall",
+        "oauth",
+        "oauth1",
+        "oauth2",
+        "security",
+
+        "amazon",
+        "apple",
+        "asana",
+        "auth0",
+        "azure",
+        "bitbucket",
+        "bitly",
+        "box",
+        "bufferapp",
+        "clever",
+        "dailymotion",
+        "deezer",
+        "deviantart",
+        "discogs",
+        "disqus",
+        "dropbox",
+        "eventbrite",
+        "eve online",
+        "facebook",
+        "fiware",
+        "flickr",
+        "foursquare",
+        "genius",
+        "github",
+        "gitlab",
+        "google",
+        "hubic",
+        "instagram",
+        "jawbone",
+        "jira",
+        "linkedin",
+        "mail.ru",
+        "odnoklassniki",
+        "paypal",
+        "qq",
+        "reddit",
+        "runkeeper",
+        "salesforce",
+        "sensio connect",
+        "sina weibo",
+        "slack",
+        "sound cloud",
+        "spotify",
+        "stack exchange",
+        "stereomood",
+        "strava",
+        "toshl",
+        "trakt",
+        "trello",
+        "twitch",
+        "twitter",
+        "vkontakte",
+        "windows live",
+        "wordpress",
+        "xing",
+        "yahoo",
+        "yandex",
+        "youtube",
+        "37signals"
+    ],
+
+    "authors": [
+        {
+            "name": "Alexander",
+            "email": "iam.asm89@gmail.com"
+        },
+        {
+            "name": "Joseph Bielawski",
+            "email": "stloyd@gmail.com"
+        },
+        {
+            "name": "Geoffrey Bachelet",
+            "email": "geoffrey.bachelet@gmail.com"
+        },
+        {
+            "name": "Contributors",
+            "homepage": "https://github.com/hwi/HWIOAuthBundle/contributors"
+        }
+    ],
+
+    "require": {
+        "php":                            "^8.1",
+        "symfony/deprecation-contracts":  "^3.0",
+        "symfony/framework-bundle":       "^5.4 || ^6.3 || ^7.0",
+        "symfony/security-bundle":        "^5.4 || ^6.3 || ^7.0",
+        "symfony/options-resolver":       "^5.4 || ^6.3 || ^7.0",
+        "symfony/form":                   "^5.4 || ^6.3 || ^7.0",
+        "symfony/http-client":            "^5.4 || ^6.3 || ^7.0",
+        "symfony/routing":                "^5.4 || ^6.3 || ^7.0",
+        "symfony/twig-bundle":            "^5.4 || ^6.3 || ^7.0"
+    },
+
+    "require-dev": {
+        "doctrine/doctrine-bundle":     "^2.4",
+        "doctrine/orm":                 "^2.9",
+        "symfony/browser-kit":          "^5.4 || ^6.3 || ^7.0",
+        "symfony/css-selector":         "^5.4 || ^6.3 || ^7.0",
+        "symfony/phpunit-bridge":       "^5.4 || ^6.3 || ^7.0",
+        "symfony/property-access":      "^5.4 || ^6.3 || ^7.0",
+        "symfony/validator":            "^5.4 || ^6.3 || ^7.0",
+        "symfony/stopwatch":            "^5.4 || ^6.3 || ^7.0",
+        "symfony/translation":          "^5.4 || ^6.3 || ^7.0",
+        "symfony/yaml":                 "^5.4 || ^6.3 || ^7.0",
+        "phpunit/phpunit":              "^9.6.11",
+        "friendsofphp/php-cs-fixer":    "^3.23",
+        "symfony/monolog-bundle":       "^3.4",
+        "phpstan/phpstan":              "^1.10",
+        "phpstan/phpstan-symfony":      "^1.3",
+        "phpstan/extension-installer":  "^1.3",
+        "firebase/php-jwt":             "^6.8"
+    },
+
+    "config": {
+        "allow-plugins": {
+            "composer/package-versions-deprecated": true,
+            "phpstan/extension-installer": true
+        }
+    },
+
+    "conflict": {
+        "symfony/security-bundle":      ">=6.0,<6.3.4",
+        "twig/twig":                    "<1.43|>=2.0,<2.13"
+    },
+
+    "scripts": {
+        "csfixer": "vendor/bin/php-cs-fixer fix --verbose --dry-run",
+        "csfixer-fix": "vendor/bin/php-cs-fixer fix --verbose",
+        "phpunit": "vendor/bin/phpunit",
+        "phpstan": "vendor/bin/phpstan"
+    },
+
+    "suggest": {
+        "doctrine/doctrine-bundle":     "to use Doctrine user provider",
+        "firebase/php-jwt":             "to use JWT utility functions",
+        "symfony/property-access":      "to use FOSUB integration with this bundle",
+        "symfony/twig-bundle":          "to use the Twig hwi_oauth_* functions"
+    },
+
+    "autoload": {
+        "psr-4": {
+            "HWI\\Bundle\\OAuthBundle\\": "src/"
+        }
+    },
+
+    "autoload-dev": {
+        "psr-4": {
+            "HWI\\Bundle\\OAuthBundle\\Test\\": "src/Test/",
+            "HWI\\Bundle\\OAuthBundle\\Tests\\": "tests/"
+        }
+    },
+
+    "minimum-stability": "dev",
+    "prefer-stable": true,
+
+    "extra": {
+        "branch-alias": {
+            "dev-master": "2.0-dev"
+        }
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Connect/AccountConnectorInterface.php b/vendor/hwi/oauth-bundle/src/Connect/AccountConnectorInterface.php
new file mode 100644
index 0000000..ac96ade
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Connect/AccountConnectorInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Connect;
+
+use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+/**
+ * Account connector objects are responsible for connecting an OAuth response
+ * to the appropriate fields of the user object.
+ *
+ * @author Alexander 
+ */
+interface AccountConnectorInterface
+{
+    /**
+     * Connects the response to the user object.
+     *
+     * @param UserInterface         $user     The user object
+     * @param UserResponseInterface $response The oauth response
+     */
+    public function connect(UserInterface $user, UserResponseInterface $response);
+}
diff --git a/vendor/hwi/oauth-bundle/src/Controller/Connect/AbstractController.php b/vendor/hwi/oauth-bundle/src/Controller/Connect/AbstractController.php
new file mode 100644
index 0000000..0d4ef15
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Controller/Connect/AbstractController.php
@@ -0,0 +1,204 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Controller\Connect;
+
+use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
+use HWI\Bundle\OAuthBundle\Event\FilterUserResponseEvent;
+use HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent;
+use HWI\Bundle\OAuthBundle\HWIOAuthEvents;
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
+use Symfony\Component\EventDispatcher\Event as DeprecatedEvent;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Exception\AccountStatusException;
+use Symfony\Component\Security\Core\User\UserCheckerInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
+use Symfony\Component\Security\Http\SecurityEvents;
+use Symfony\Contracts\EventDispatcher\Event;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+use Twig\Environment;
+
+/**
+ * @author Alexander 
+ *
+ * @internal
+ */
+abstract class AbstractController
+{
+    protected ResourceOwnerMapLocator $resourceOwnerMapLocator;
+    protected RequestStack $requestStack;
+    protected EventDispatcherInterface $dispatcher;
+    protected TokenStorageInterface $tokenStorage;
+    protected UserCheckerInterface $userChecker;
+    protected Environment $twig;
+    protected ?AccountConnectorInterface $accountConnector;
+
+    public function __construct(
+        ResourceOwnerMapLocator $resourceOwnerMapLocator,
+        RequestStack $requestStack,
+        EventDispatcherInterface $dispatcher,
+        TokenStorageInterface $tokenStorage,
+        UserCheckerInterface $userChecker,
+        Environment $twig,
+        ?AccountConnectorInterface $accountConnector
+    ) {
+        $this->resourceOwnerMapLocator = $resourceOwnerMapLocator;
+        $this->requestStack = $requestStack;
+        $this->dispatcher = $dispatcher;
+        $this->tokenStorage = $tokenStorage;
+        $this->userChecker = $userChecker;
+        $this->twig = $twig;
+        $this->accountConnector = $accountConnector;
+    }
+
+    /**
+     * Get a resource owner by name.
+     *
+     * @throws NotFoundHttpException if there is no resource owner with the given name
+     */
+    protected function getResourceOwnerByName(string $name): ResourceOwnerInterface
+    {
+        foreach ($this->resourceOwnerMapLocator->getResourceOwnerMaps() as $ownerMap) {
+            if ($resourceOwner = $ownerMap->getResourceOwnerByName($name)) {
+                return $resourceOwner;
+            }
+        }
+
+        throw new NotFoundHttpException(sprintf("No resource owner with name '%s'.", $name));
+    }
+
+    /**
+     * Authenticate a user with Symfony Security.
+     *
+     * @param string|array $accessToken
+     */
+    protected function authenticateUser(Request $request, UserInterface $user, string $resourceOwnerName, $accessToken, bool $fakeLogin = true): void
+    {
+        try {
+            $this->userChecker->checkPreAuth($user);
+            $this->userChecker->checkPostAuth($user);
+        } catch (AccountStatusException $e) {
+            // Don't authenticate locked, disabled or expired users
+            return;
+        }
+
+        $token = new OAuthToken($accessToken, $user->getRoles());
+        $token->setResourceOwnerName($resourceOwnerName);
+        $token->setUser($user);
+
+        // required for compatibility with Symfony 5.4
+        if (method_exists($token, 'setAuthenticated')) {
+            $token->setAuthenticated(true, false);
+        }
+
+        $this->tokenStorage->setToken($token);
+
+        if ($fakeLogin) {
+            // Since we're "faking" normal login, we need to throw our INTERACTIVE_LOGIN event manually
+            $this->dispatch(
+                new InteractiveLoginEvent($request, $token),
+                SecurityEvents::INTERACTIVE_LOGIN
+            );
+        }
+    }
+
+    /**
+     * @param string $service name of the resource owner to connect to
+     *
+     * @throws NotFoundHttpException if there is no resource owner with the given name
+     */
+    protected function getConfirmationResponse(Request $request, array $accessToken, string $service): Response
+    {
+        /** @var OAuthToken $currentToken */
+        $currentToken = $this->tokenStorage->getToken();
+        /** @var UserInterface $currentUser */
+        $currentUser = $currentToken->getUser();
+
+        $resourceOwner = $this->getResourceOwnerByName($service);
+        $userInformation = $resourceOwner->getUserInformation($accessToken);
+
+        $event = new GetResponseUserEvent($currentUser, $request);
+        $this->dispatch($event, HWIOAuthEvents::CONNECT_CONFIRMED);
+
+        $this->accountConnector->connect($currentUser, $userInformation);
+
+        if ($currentToken instanceof OAuthToken) {
+            // Update user token with new details
+            $newToken =
+                (isset($accessToken['access_token']) || isset($accessToken['oauth_token'])) ?
+                    $accessToken : $currentToken->getRawToken();
+
+            $this->authenticateUser($request, $currentUser, $service, $newToken, false);
+        }
+
+        if (null === $response = $event->getResponse()) {
+            if ($targetPath = $this->getTargetPath($request->getSession())) {
+                $response = new RedirectResponse($targetPath);
+            } else {
+                $response = new Response($this->twig->render('@HWIOAuth/Connect/connect_success.html.twig', [
+                    'userInformation' => $userInformation,
+                    'service' => $service,
+                ]));
+            }
+        }
+
+        $event = new FilterUserResponseEvent($currentUser, $request, $response);
+        $this->dispatch($event, HWIOAuthEvents::CONNECT_COMPLETED);
+
+        return $event->getResponse();
+    }
+
+    /**
+     * @param Event|DeprecatedEvent $event
+     */
+    protected function dispatch($event, ?string $eventName = null): void
+    {
+        $this->dispatcher->dispatch($event, $eventName);
+    }
+
+    protected function getSession(): ?SessionInterface
+    {
+        if (method_exists($this->requestStack, 'getSession')) {
+            return $this->requestStack->getSession();
+        }
+
+        if ((null !== $request = $this->requestStack->getCurrentRequest()) && $request->hasSession()) {
+            return $request->getSession();
+        }
+
+        return null;
+    }
+
+    protected function getTargetPath(?SessionInterface $session): ?string
+    {
+        if (!$session) {
+            return null;
+        }
+
+        foreach ($this->resourceOwnerMapLocator->getFirewallNames() as $firewallName) {
+            $sessionKey = '_security.'.$firewallName.'.target_path';
+            if ($session->has($sessionKey)) {
+                return $session->get($sessionKey);
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Controller/Connect/ConnectController.php b/vendor/hwi/oauth-bundle/src/Controller/Connect/ConnectController.php
new file mode 100644
index 0000000..481174c
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Controller/Connect/ConnectController.php
@@ -0,0 +1,171 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Controller\Connect;
+
+use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
+use HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent;
+use HWI\Bundle\OAuthBundle\HWIOAuthEvents;
+use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
+use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
+use Symfony\Component\Form\FormFactoryInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
+use Symfony\Component\Security\Core\User\UserCheckerInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+use Twig\Environment;
+
+/**
+ * @author Alexander 
+ *
+ * @internal
+ */
+final class ConnectController extends AbstractController
+{
+    private OAuthUtils $oauthUtils;
+    private AuthorizationCheckerInterface $authorizationChecker;
+    private FormFactoryInterface $formFactory;
+    private RouterInterface $router;
+    private string $grantRule;
+    private bool $failedUseReferer;
+    private string $failedAuthPath;
+    private bool $enableConnectConfirmation;
+
+    public function __construct(
+        OAuthUtils $oauthUtils,
+        ResourceOwnerMapLocator $resourceOwnerMapLocator,
+        RequestStack $requestStack,
+        EventDispatcherInterface $dispatcher,
+        TokenStorageInterface $tokenStorage,
+        UserCheckerInterface $userChecker,
+        AuthorizationCheckerInterface $authorizationChecker,
+        FormFactoryInterface $formFactory,
+        Environment $twig,
+        RouterInterface $router,
+        string $grantRule,
+        bool $failedUseReferer,
+        string $failedAuthPath,
+        bool $enableConnectConfirmation,
+        ?AccountConnectorInterface $accountConnector
+    ) {
+        parent::__construct(
+            $resourceOwnerMapLocator,
+            $requestStack,
+            $dispatcher,
+            $tokenStorage,
+            $userChecker,
+            $twig,
+            $accountConnector
+        );
+
+        $this->oauthUtils = $oauthUtils;
+        $this->grantRule = $grantRule;
+        $this->failedUseReferer = $failedUseReferer;
+        $this->failedAuthPath = $failedAuthPath;
+        $this->enableConnectConfirmation = $enableConnectConfirmation;
+        $this->authorizationChecker = $authorizationChecker;
+        $this->formFactory = $formFactory;
+        $this->router = $router;
+    }
+
+    /**
+     * Connects a user to a given account if the user is logged in and connect is enabled.
+     *
+     * @param string $service name of the resource owner to connect to
+     *
+     * @throws \Exception
+     * @throws NotFoundHttpException if `connect` functionality was not enabled
+     * @throws AccessDeniedException if no user is authenticated
+     */
+    public function connectServiceAction(Request $request, string $service): Response
+    {
+        if (!$this->accountConnector) {
+            throw new NotFoundHttpException();
+        }
+
+        $hasUser = $this->authorizationChecker->isGranted($this->grantRule);
+        if (!$hasUser) {
+            throw new AccessDeniedException('Cannot connect an account.');
+        }
+
+        // Get the data from the resource owner
+        $resourceOwner = $this->getResourceOwnerByName($service);
+
+        $session = $request->hasSession() ? $request->getSession() : $this->getSession();
+        if ($session && !$session->isStarted()) {
+            $session->start();
+        }
+
+        $key = $request->query->get('key', (string) time());
+
+        $accessToken = null;
+        if ($resourceOwner->handles($request)) {
+            $accessToken = $resourceOwner->getAccessToken(
+                $request,
+                $this->oauthUtils->getServiceAuthUrl($request, $resourceOwner)
+            );
+
+            if ($session) {
+                // save in session
+                $session->set('_hwi_oauth.connect_confirmation.'.$key, $accessToken);
+            }
+        } elseif ($session) {
+            $accessToken = $session->get('_hwi_oauth.connect_confirmation.'.$key);
+        }
+
+        // Redirect to the login path if the token is empty (Eg. User cancelled auth)
+        if (null === $accessToken) {
+            if ($this->failedUseReferer && $targetPath = $this->getTargetPath($session)) {
+                return new RedirectResponse($targetPath);
+            }
+
+            return new RedirectResponse($this->router->generate($this->failedAuthPath));
+        }
+
+        // Show confirmation page?
+        if (!$this->enableConnectConfirmation) {
+            return $this->getConfirmationResponse($request, $accessToken, $service);
+        }
+
+        $form = $this->formFactory->create();
+        $form->handleRequest($request);
+
+        if ($form->isSubmitted() && $form->isValid()) {
+            return $this->getConfirmationResponse($request, $accessToken, $service);
+        }
+
+        /** @var TokenInterface $token */
+        $token = $this->tokenStorage->getToken();
+
+        $event = new GetResponseUserEvent($token->getUser(), $request);
+
+        $this->dispatch($event, HWIOAuthEvents::CONNECT_INITIALIZE);
+
+        if ($response = $event->getResponse()) {
+            return $response;
+        }
+
+        return new Response($this->twig->render('@HWIOAuth/Connect/connect_confirm.html.twig', [
+            'key' => $key,
+            'service' => $service,
+            'form' => $form->createView(),
+            'userInformation' => $resourceOwner->getUserInformation($accessToken),
+        ]));
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Controller/Connect/RegisterController.php b/vendor/hwi/oauth-bundle/src/Controller/Connect/RegisterController.php
new file mode 100644
index 0000000..c90e37d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Controller/Connect/RegisterController.php
@@ -0,0 +1,175 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Controller\Connect;
+
+use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
+use HWI\Bundle\OAuthBundle\Event\FilterUserResponseEvent;
+use HWI\Bundle\OAuthBundle\Event\FormEvent;
+use HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent;
+use HWI\Bundle\OAuthBundle\Form\RegistrationFormHandlerInterface;
+use HWI\Bundle\OAuthBundle\HWIOAuthEvents;
+use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException;
+use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
+use Symfony\Component\Form\FormFactoryInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
+use Symfony\Component\Security\Core\User\UserCheckerInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+use Twig\Environment;
+
+/**
+ * @author Alexander 
+ *
+ * @internal
+ */
+final class RegisterController extends AbstractController
+{
+    private AuthorizationCheckerInterface $authorizationChecker;
+    private FormFactoryInterface $formFactory;
+    private ?RegistrationFormHandlerInterface $formHandler;
+    private string $grantRule;
+    private ?string $registrationForm;
+
+    public function __construct(
+        ResourceOwnerMapLocator $resourceOwnerMapLocator,
+        RequestStack $requestStack,
+        EventDispatcherInterface $dispatcher,
+        TokenStorageInterface $tokenStorage,
+        UserCheckerInterface $userChecker,
+        AuthorizationCheckerInterface $authorizationChecker,
+        FormFactoryInterface $formFactory,
+        Environment $twig,
+        string $grantRule,
+        ?string $registrationForm,
+        ?AccountConnectorInterface $accountConnector,
+        ?RegistrationFormHandlerInterface $formHandler
+    ) {
+        parent::__construct(
+            $resourceOwnerMapLocator,
+            $requestStack,
+            $dispatcher,
+            $tokenStorage,
+            $userChecker,
+            $twig,
+            $accountConnector
+        );
+
+        $this->grantRule = $grantRule;
+        $this->registrationForm = $registrationForm;
+        $this->formHandler = $formHandler;
+        $this->authorizationChecker = $authorizationChecker;
+        $this->formFactory = $formFactory;
+    }
+
+    /**
+     * Shows a registration form if there is no user logged in and connecting
+     * is enabled.
+     *
+     * @param string $key key used for retrieving the right information for the registration form
+     *
+     * @throws NotFoundHttpException if `connect` functionality was not enabled
+     * @throws AccessDeniedException if any user is authenticated
+     * @throws \RuntimeException
+     */
+    public function registrationAction(Request $request, string $key): Response
+    {
+        if (!$this->accountConnector || !$this->formHandler) {
+            throw new NotFoundHttpException();
+        }
+
+        $hasUser = $this->authorizationChecker->isGranted($this->grantRule);
+        if ($hasUser) {
+            throw new AccessDeniedException('Cannot connect already registered account.');
+        }
+
+        $error = null;
+        $session = $request->hasSession() ? $request->getSession() : $this->getSession();
+        if ($session) {
+            if (!$session->isStarted()) {
+                $session->start();
+            }
+            $error = $session->get('_hwi_oauth.registration_error.'.$key);
+            $session->remove('_hwi_oauth.registration_error.'.$key);
+        }
+
+        if (!$error instanceof AccountNotLinkedException) {
+            throw new \RuntimeException('Cannot register an account.', 0, $error instanceof \Exception ? $error : null);
+        }
+
+        if (!$this->registrationForm) {
+            throw new \InvalidArgumentException('Registration form class must be set.');
+        }
+
+        $userInformation = $this
+            ->getResourceOwnerByName($error->getResourceOwnerName())
+            ->getUserInformation($error->getRawToken())
+        ;
+
+        $form = $this->formFactory->create($this->registrationForm);
+
+        if ($this->formHandler->process($request, $form, $userInformation)) {
+            $event = new FormEvent($form, $request);
+            $this->dispatch($event, HWIOAuthEvents::REGISTRATION_SUCCESS);
+
+            /** @var UserInterface $user */
+            $user = $form->getData();
+
+            $this->accountConnector->connect($user, $userInformation);
+
+            // Authenticate the user
+            $this->authenticateUser($request, $user, $error->getResourceOwnerName(), $error->getRawToken());
+
+            if (null === $response = $event->getResponse()) {
+                if ($targetPath = $this->getTargetPath($session)) {
+                    $response = new RedirectResponse($targetPath);
+                } else {
+                    $response = new Response($this->twig->render('@HWIOAuth/Connect/registration_success.html.twig', [
+                        'userInformation' => $userInformation,
+                    ]));
+                }
+            }
+
+            $event = new FilterUserResponseEvent($user, $request, $response);
+            $this->dispatch($event, HWIOAuthEvents::REGISTRATION_COMPLETED);
+
+            return $event->getResponse();
+        }
+
+        if ($session) {
+            // reset the error in the session
+            $session->set('_hwi_oauth.registration_error.'.$key, $error);
+        }
+
+        /** @var UserInterface $user */
+        $user = $form->getData();
+
+        $event = new GetResponseUserEvent($user, $request);
+        $this->dispatch($event, HWIOAuthEvents::REGISTRATION_INITIALIZE);
+
+        if ($response = $event->getResponse()) {
+            return $response;
+        }
+
+        return new Response($this->twig->render('@HWIOAuth/Connect/registration.html.twig', [
+            'key' => $key,
+            'form' => $form->createView(),
+            'userInformation' => $userInformation,
+        ]));
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Controller/LoginController.php b/vendor/hwi/oauth-bundle/src/Controller/LoginController.php
new file mode 100644
index 0000000..fc22c7d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Controller/LoginController.php
@@ -0,0 +1,112 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Controller;
+
+use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
+use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
+use Twig\Environment;
+
+/**
+ * @author Alexander 
+ *
+ * @internal
+ */
+final class LoginController
+{
+    private bool $connect;
+    private string $grantRule;
+    private AuthenticationUtils $authenticationUtils;
+    private RouterInterface $router;
+    private AuthorizationCheckerInterface $authorizationChecker;
+    private RequestStack $requestStack;
+    private Environment $twig;
+
+    public function __construct(
+        AuthenticationUtils $authenticationUtils,
+        RouterInterface $router,
+        AuthorizationCheckerInterface $authorizationChecker,
+        RequestStack $requestStack,
+        Environment $twig,
+        bool $connect,
+        string $grantRule
+    ) {
+        $this->authenticationUtils = $authenticationUtils;
+        $this->router = $router;
+        $this->authorizationChecker = $authorizationChecker;
+        $this->requestStack = $requestStack;
+        $this->twig = $twig;
+        $this->connect = $connect;
+        $this->grantRule = $grantRule;
+    }
+
+    /**
+     * Action that handles the login 'form'. If connecting is enabled the
+     * user will be redirected to the appropriate login urls or registration forms.
+     *
+     * @throws \LogicException
+     */
+    public function connectAction(Request $request): Response
+    {
+        try {
+            $hasUser = $this->authorizationChecker->isGranted($this->grantRule);
+        } catch (AuthenticationCredentialsNotFoundException $exception) {
+            $hasUser = false;
+        }
+
+        $error = $this->authenticationUtils->getLastAuthenticationError();
+
+        // if connecting is enabled and there is no user, redirect to the registration form
+        if ($this->connect && !$hasUser && $error instanceof AccountNotLinkedException) {
+            $key = time();
+            $session = $request->hasSession() ? $request->getSession() : $this->getSession();
+            if ($session) {
+                if (!$session->isStarted()) {
+                    $session->start();
+                }
+
+                $session->set('_hwi_oauth.registration_error.'.$key, $error);
+            }
+
+            return new RedirectResponse($this->router->generate('hwi_oauth_connect_registration', ['key' => $key], UrlGeneratorInterface::ABSOLUTE_PATH));
+        }
+
+        if (null !== $error) {
+            $error = $error->getMessageKey();
+        }
+
+        return new Response(
+            $this->twig->render('@HWIOAuth/Connect/login.html.twig', ['error' => $error])
+        );
+    }
+
+    private function getSession(): ?SessionInterface
+    {
+        if (method_exists($this->requestStack, 'getSession')) {
+            return $this->requestStack->getSession();
+        }
+
+        if ((null !== $request = $this->requestStack->getCurrentRequest()) && $request->hasSession()) {
+            return $request->getSession();
+        }
+
+        return null;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Controller/RedirectToServiceController.php b/vendor/hwi/oauth-bundle/src/Controller/RedirectToServiceController.php
new file mode 100644
index 0000000..06585ac
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Controller/RedirectToServiceController.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Controller;
+
+use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
+use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
+use HWI\Bundle\OAuthBundle\Util\DomainWhitelist;
+use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * @author Alexander 
+ *
+ * @internal
+ */
+final class RedirectToServiceController
+{
+    public function __construct(
+        private readonly OAuthUtils $oauthUtils,
+        private readonly DomainWhitelist $domainWhitelist,
+        private readonly ResourceOwnerMapLocator $resourceOwnerMapLocator,
+        private readonly ?string $targetPathParameter,
+        private readonly bool $failedUseReferer,
+        private readonly bool $useReferer
+    ) {
+    }
+
+    /**
+     * @throws NotFoundHttpException
+     */
+    public function redirectToServiceAction(Request $request, string $service): RedirectResponse
+    {
+        try {
+            $authorizationUrl = $this->oauthUtils->getAuthorizationUrl($request, $service);
+        } catch (\RuntimeException $e) {
+            throw new NotFoundHttpException($e->getMessage(), $e);
+        }
+
+        $this->storeReturnPath($request, $authorizationUrl);
+
+        return new RedirectResponse($authorizationUrl);
+    }
+
+    private function storeReturnPath(Request $request, string $authorizationUrl): void
+    {
+        try {
+            $session = $request->getSession();
+        } catch (SessionNotFoundException $e) {
+            return;
+        }
+
+        $param = $this->targetPathParameter;
+
+        foreach ($this->resourceOwnerMapLocator->getFirewallNames() as $firewallName) {
+            $sessionKey = '_security.'.$firewallName.'.target_path';
+            $sessionKeyFailure = '_security.'.$firewallName.'.failed_target_path';
+
+            if (!empty($param) && $targetUrl = $request->get($param)) {
+                if (!$this->domainWhitelist->isValidTargetUrl($targetUrl)) {
+                    throw new AccessDeniedHttpException('Not allowed to redirect to '.$targetUrl);
+                }
+
+                $session->set($sessionKey, $targetUrl);
+            }
+
+            if ($this->failedUseReferer && !$session->has($sessionKeyFailure) && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $authorizationUrl) {
+                $session->set($sessionKeyFailure, $targetUrl);
+            }
+
+            if ($this->useReferer && !$session->has($sessionKey) && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $authorizationUrl) {
+                $session->set($sessionKey, $targetUrl);
+            }
+        }
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/DependencyInjection/CompilerPass/EnableRefreshOAuthTokenListenerCompilerPass.php b/vendor/hwi/oauth-bundle/src/DependencyInjection/CompilerPass/EnableRefreshOAuthTokenListenerCompilerPass.php
new file mode 100644
index 0000000..36aeb6b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/DependencyInjection/CompilerPass/EnableRefreshOAuthTokenListenerCompilerPass.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass;
+
+use HWI\Bundle\OAuthBundle\DependencyInjection\HWIOAuthExtension;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+final class EnableRefreshOAuthTokenListenerCompilerPass implements CompilerPassInterface
+{
+    public function process(ContainerBuilder $container): void
+    {
+        /** @var HWIOAuthExtension $extension */
+        $extension = $container->getExtension('hwi_oauth');
+        if (!$extension->isRefreshTokenListenerEnabled()) {
+            return;
+        }
+
+        foreach ($extension->getFirewallNames() as $firewallName => $_) {
+            $container->findDefinition('hwi_oauth.context_listener.token_refresher.'.$firewallName)
+                ->addMethodCall('enable');
+        }
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/DependencyInjection/CompilerPass/ResourceOwnerCompilerPass.php b/vendor/hwi/oauth-bundle/src/DependencyInjection/CompilerPass/ResourceOwnerCompilerPass.php
new file mode 100644
index 0000000..aa642ed
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/DependencyInjection/CompilerPass/ResourceOwnerCompilerPass.php
@@ -0,0 +1,105 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass;
+
+use HWI\Bundle\OAuthBundle\DependencyInjection\Configuration;
+use HWI\Bundle\OAuthBundle\DependencyInjection\HWIOAuthExtension;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+
+/**
+ * Registers "hwi_oauth.resource_owner.$type.class" Parameters and checks resource owner configurations, whether given
+ * type exists (Apps can add own ResourceOwners).
+ *
+ * Adds resource owner maps to the locator and utils.
+ */
+final class ResourceOwnerCompilerPass implements CompilerPassInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function process(ContainerBuilder $container): void
+    {
+        $this->registerResourceOwnerTypeClassParameters($container);
+        $this->addResourceOwnerMapToLocatorAndUtils($container);
+    }
+
+    private function registerResourceOwnerTypeClassParameters(ContainerBuilder $container): void
+    {
+        foreach ($container->findTaggedServiceIds('hwi_oauth.resource_owner') as $serviceId => $_) {
+            $definition = $container->findDefinition($serviceId);
+            Configuration::registerResourceOwner($definition->getClass());
+        }
+
+        foreach (Configuration::getResourceOwnerTypesClassMap() as $type => $resourceOwnerClass) {
+            $parameterName = "hwi_oauth.resource_owner.$type.class";
+            if (!$container->hasParameter($parameterName)) {
+                $container->setParameter($parameterName, $resourceOwnerClass);
+            }
+        }
+
+        // Check whether resource owner set with parameter '%hwi_oauth.resource_owner.[type].class%' type exists
+        /** @var ServiceLocator $locator */
+        $locator = $container->get('hwi_oauth.resource_owners.locator');
+
+        foreach ($locator->getProvidedServices() as $resourceOwnerName => $_) {
+            try {
+                $definition = $container->findDefinition('hwi_oauth.resource_owner.'.$resourceOwnerName);
+            } catch (ServiceNotFoundException $e) {
+                // Resource owner defined with "options.service"
+                continue;
+            }
+
+            $resourceOwnerClass = $definition->getClass();
+
+            // Check whether a ResourceOwner class exists only if resource owner was set by its "options.type"
+            if (false === preg_match('~^%(?Phwi_oauth.resource_owner.(?P.+).class)%$~', $resourceOwnerClass, $match)) {
+                return;
+            }
+
+            if (!($match['type'] ?? null)) {
+                continue;
+            }
+
+            if (!Configuration::isResourceOwnerSupported($match['type'])) {
+                $e = new \InvalidArgumentException(sprintf('Unknown resource owner type "%s"', $match['type']));
+
+                throw new InvalidConfigurationException(sprintf('Invalid configuration for path "hwi_oauth.resource_owners.%s.type": %s', $resourceOwnerName, $e->getMessage()), $e->getCode(), $e);
+            }
+        }
+    }
+
+    private function addResourceOwnerMapToLocatorAndUtils(ContainerBuilder $container): void
+    {
+        /** @var HWIOAuthExtension $extension */
+        $extension = $container->getExtension('hwi_oauth');
+
+        $locatorDef = $container->findDefinition('hwi_oauth.resource_ownermap_locator');
+        $oauthUtilsDef = $container->findDefinition('hwi_oauth.security.oauth_utils');
+
+        foreach ($extension->getFirewallNames() as $firewallName => $_) {
+            $resourceOwnerMapId = 'hwi_oauth.resource_ownermap.'.$firewallName;
+
+            $container->findDefinition($resourceOwnerMapId)
+                ->setArgument('$locator', new Reference('hwi_oauth.resource_owners.locator'));
+
+            $resourceOwnerMapRef = new Reference($resourceOwnerMapId);
+
+            $locatorDef->addMethodCall('set', [$firewallName, $resourceOwnerMapRef]);
+            $oauthUtilsDef->addMethodCall('addResourceOwnerMap', [$firewallName, $resourceOwnerMapRef]);
+        }
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/DependencyInjection/Configuration.php b/vendor/hwi/oauth-bundle/src/DependencyInjection/Configuration.php
new file mode 100644
index 0000000..dea1033
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/DependencyInjection/Configuration.php
@@ -0,0 +1,441 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\DependencyInjection;
+
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth1ResourceOwner;
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner;
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
+use Symfony\Component\Config\Definition\BaseNode;
+use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Finder\Finder;
+
+/**
+ * Configuration for the extension.
+ *
+ * @author Alexander 
+ */
+final class Configuration implements ConfigurationInterface
+{
+    /**
+     * type => ResourceOwner mapping for hwi_oauth.resource_owner.*.class parameters.
+     *
+     * @var array>
+     */
+    private static array $resourceOwnerTypesClassMap = [];
+
+    /**
+     * Array of supported resource owners.
+     *
+     * @var array
+     */
+    private static array $resourceOwnerTypes = [];
+
+    public function __construct()
+    {
+        if ([] === self::$resourceOwnerTypes) {
+            self::loadResourceOwners();
+        }
+    }
+
+    public static function getResourceOwnerTypesClassMap(): array
+    {
+        return self::$resourceOwnerTypesClassMap;
+    }
+
+    /**
+     * Return the type (oauth1 or oauth2) of given resource owner.
+     */
+    public static function getResourceOwnerType(string $resourceOwner): ?string
+    {
+        $resourceOwner = strtolower($resourceOwner);
+
+        return self::$resourceOwnerTypes[$resourceOwner] ?? null;
+    }
+
+    /**
+     * Checks that given resource owner is supported by this bundle.
+     */
+    public static function isResourceOwnerSupported(string $resourceOwner): bool
+    {
+        return isset(self::$resourceOwnerTypes[strtolower($resourceOwner)]);
+    }
+
+    public static function registerResourceOwner(string $resourceOwnerClass): void
+    {
+        $reflection = new \ReflectionClass($resourceOwnerClass);
+        if (!$reflection->implementsInterface(ResourceOwnerInterface::class)) {
+            throw new \LogicException('Resource owner class should implement "ResourceOwnerInterface", or extended class "GenericOAuth1ResourceOwner"/"GenericOAuth2ResourceOwner".');
+        }
+
+        $type = \defined("$resourceOwnerClass::TYPE") ? $resourceOwnerClass::TYPE : null;
+        if (null === $type) {
+            if (preg_match('~(?P[^\\\\]+)ResourceOwner$~', $resourceOwnerClass, $match)) {
+                $type = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $match['resource_owner']));
+            } else {
+                throw new \LogicException(sprintf('Resource owner class either should have "TYPE" const defined or end with "ResourceOwner" so that type can be calculated by converting its class name without suffix to "snake_case". Given class name is "%s"', $resourceOwnerClass));
+            }
+        }
+
+        $oAuth = 'unknown';
+        if ($reflection->isSubclassOf(GenericOAuth2ResourceOwner::class)) {
+            $oAuth = 'oauth2';
+        } elseif ($reflection->isSubclassOf(GenericOAuth1ResourceOwner::class)) {
+            $oAuth = 'oauth1';
+        }
+
+        self::$resourceOwnerTypes[$type] = $oAuth;
+        self::$resourceOwnerTypesClassMap[$type] = $resourceOwnerClass;
+    }
+
+    /**
+     * Generates the configuration tree builder.
+     */
+    public function getConfigTreeBuilder(): TreeBuilder
+    {
+        $builder = new TreeBuilder('hwi_oauth');
+
+        /** @var ArrayNodeDefinition $rootNode */
+        $rootNode = $builder->getRootNode();
+
+        $rootNode
+            ->fixXmlConfig('firewall_name')
+            ->children()
+                ->arrayNode('firewall_names')
+                    ->setDeprecated(...$this->getDeprecationParams())
+                    ->defaultValue([])
+                    ->prototype('scalar')->end()
+                ->end()
+                ->scalarNode('target_path_parameter')->defaultNull()->end()
+                ->arrayNode('target_path_domains_whitelist')
+                    ->defaultValue([])
+                    ->prototype('scalar')->end()
+                ->end()
+                ->booleanNode('use_referer')->defaultFalse()->end()
+                ->booleanNode('failed_use_referer')->defaultFalse()->end()
+                ->scalarNode('failed_auth_path')->defaultValue('hwi_oauth_connect')->end()
+                ->scalarNode('grant_rule')
+                    ->defaultValue('IS_AUTHENTICATED_REMEMBERED')
+                    ->validate()
+                        ->ifTrue(function ($role) {
+                            return !('IS_AUTHENTICATED_REMEMBERED' === $role || 'IS_AUTHENTICATED_FULLY' === $role);
+                        })
+                        ->thenInvalid('Unknown grant role set "%s".')
+                    ->end()
+                ->end()
+            ->end()
+        ;
+
+        $this->addConnectConfiguration($rootNode);
+        $this->addResourceOwnersConfiguration($rootNode);
+
+        return $builder;
+    }
+
+    private function addResourceOwnersConfiguration(ArrayNodeDefinition $node): void
+    {
+        /* @phpstan-ignore-next-line */
+        $node
+            ->fixXmlConfig('resource_owner')
+            ->children()
+                ->arrayNode('resource_owners')
+                    ->isRequired()
+                    ->useAttributeAsKey('name')
+                    ->prototype('array')
+                        ->ignoreExtraKeys()
+                        ->children()
+                            ->scalarNode('base_url')->end()
+                            ->scalarNode('access_token_url')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('authorization_url')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('request_token_url')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('revoke_token_url')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('infos_url')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('client_id')->cannotBeEmpty()->end()
+                            ->scalarNode('client_secret')->cannotBeEmpty()->end()
+                            ->scalarNode('realm')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('scope')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('user_response_class')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('service')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('class')
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('type')
+                                // will be validated in ResourceOwnerCompilerPass, other apps can register own resource
+                                // owner maps later with tag hwi_oauth.resource_owner
+                                ->validate()
+                                    ->ifEmpty()
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->scalarNode('use_authorization_to_get_token')
+                                ->validate()
+                                    ->ifTrue(function ($v) {
+                                        if (false === $v) {
+                                            return false;
+                                        }
+
+                                        return empty($v);
+                                    })
+                                    ->thenUnset()
+                                ->end()
+                            ->end()
+                            ->arrayNode('paths')
+                                ->useAttributeAsKey('name')
+                                ->prototype('variable')
+                                    ->validate()
+                                        ->ifTrue(function ($v) {
+                                            if (null === $v) {
+                                                return true;
+                                            }
+
+                                            if (\is_array($v)) {
+                                                return 0 === \count($v);
+                                            }
+
+                                            if (\is_string($v)) {
+                                                return empty($v);
+                                            }
+
+                                            return !is_numeric($v);
+                                        })
+                                        ->thenInvalid('Path can be only string or array type.')
+                                    ->end()
+                                ->end()
+                            ->end()
+                            ->arrayNode('options')
+                                ->useAttributeAsKey('name')
+                                ->prototype('scalar')->end()
+                            ->end()
+                        ->end()
+                        ->validate()
+                            ->ifTrue(function ($c) {
+                                // skip if this contains a service
+                                if (isset($c['service'])) {
+                                    return false;
+                                }
+
+                                // for each type at least these have to be set
+                                foreach (['client_id', 'client_secret'] as $child) {
+                                    if (!isset($c[$child])) {
+                                        return true;
+                                    }
+                                }
+
+                                if (!isset($c['type']) && !isset($c['class'])) {
+                                    return true;
+                                }
+
+                                return false;
+                            })
+                            ->thenInvalid("You should set at least the 'type' or 'class' with 'client_id' and the 'client_secret' of a resource owner.")
+                        ->end()
+                        ->validate()
+                            ->ifTrue(function ($c) {
+                                return isset($c['type'], $c['class']);
+                            })
+                            ->then(function ($c) {
+                                trigger_deprecation('hwi/oauth-bundle', '2.0', 'No need to set both "type" and "class" for resource owner.');
+
+                                return $c;
+                            })
+                        ->end()
+                        ->validate()
+                            ->ifTrue(function ($c) {
+                                // Skip if this contains a service or a class
+                                if (isset($c['service']) || isset($c['class'])) {
+                                    return false;
+                                }
+
+                                // Only validate the 'oauth2' and 'oauth1' type
+                                if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
+                                    return false;
+                                }
+
+                                $children = ['authorization_url', 'access_token_url', 'request_token_url', 'infos_url'];
+                                foreach ($children as $child) {
+                                    // This option exists only for OAuth1.0a
+                                    if ('request_token_url' === $child && 'oauth2' === $c['type']) {
+                                        continue;
+                                    }
+
+                                    if (!isset($c[$child])) {
+                                        return true;
+                                    }
+                                }
+
+                                return false;
+                            })
+                            ->thenInvalid("All parameters are mandatory for types 'oauth2' and 'oauth1'. Check if you're missing one of: 'access_token_url', 'authorization_url', 'infos_url' and 'request_token_url' for 'oauth1'.")
+                        ->end()
+                        ->validate()
+                            ->ifTrue(function ($c) {
+                                // skip if this contains a service
+                                if (isset($c['service']) || isset($c['class'])) {
+                                    return false;
+                                }
+
+                                // Only validate the 'oauth2' and 'oauth1' type
+                                if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
+                                    return false;
+                                }
+
+                                // one of this two options must be set
+                                if (0 === \count($c['paths'])) {
+                                    return !isset($c['user_response_class']);
+                                }
+
+                                foreach (['identifier', 'nickname', 'realname'] as $child) {
+                                    if (!isset($c['paths'][$child])) {
+                                        return true;
+                                    }
+                                }
+
+                                return false;
+                            })
+                            ->thenInvalid("At least the 'identifier', 'nickname' and 'realname' paths should be configured for 'oauth2' and 'oauth1' types.")
+                        ->end()
+                        ->validate()
+                            ->ifTrue(function ($c) {
+                                if (isset($c['service'])) {
+                                    // ignore paths & options if none were set
+                                    return 0 !== \count($c['paths']) || 0 !== \count($c['options']) || 3 < \count($c);
+                                }
+
+                                return false;
+                            })
+                            ->thenInvalid("If you're setting a 'service', no other arguments should be set.")
+                        ->end()
+                        ->validate()
+                            ->ifTrue(function ($c) {
+                                return isset($c['class']);
+                            })
+                            ->then(function ($c) {
+                                self::registerResourceOwner($c['class']);
+
+                                return $c;
+                            })
+                        ->end()
+                    ->end()
+                ->end()
+            ->end()
+        ;
+    }
+
+    private function addConnectConfiguration(ArrayNodeDefinition $node): void
+    {
+        $node
+            ->children()
+                ->arrayNode('connect')
+                    ->children()
+                        ->booleanNode('confirmation')->defaultTrue()->end()
+                        ->scalarNode('account_connector')->cannotBeEmpty()->end()
+                        ->scalarNode('registration_form_handler')->cannotBeEmpty()->end()
+                        ->scalarNode('registration_form')->cannotBeEmpty()->end()
+                    ->end()
+                ->end()
+            ->end()
+        ;
+    }
+
+    private static function loadResourceOwners(): void
+    {
+        $files = (new Finder())
+            ->in(__DIR__.'/../OAuth/ResourceOwner')
+            ->name('~^(.+)ResourceOwner\.php$~')
+            ->files();
+
+        foreach ($files as $f) {
+            if (!str_contains($f->getFilename(), 'ResourceOwner')) {
+                continue;
+            }
+
+            // Skip known abstract classes
+            if (\in_array($f->getFilename(), ['AbstractResourceOwner.php', 'GenericOAuth1ResourceOwner.php', 'GenericOAuth2ResourceOwner.php'], true)) {
+                continue;
+            }
+
+            self::registerResourceOwner('HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\\'.str_replace('.php', '', $f->getFilename()));
+        }
+    }
+
+    /**
+     * Returns the correct deprecation params as an array for setDeprecated().
+     *
+     * symfony/config v5.1 introduces a deprecation notice when calling
+     * setDeprecated() with less than 3 args and the getDeprecation() method was
+     * introduced at the same time. By checking if getDeprecation() exists,
+     * we can determine the correct param count to use when calling setDeprecated().
+     *
+     * @return string[]
+     */
+    private function getDeprecationParams(): array
+    {
+        if (method_exists(BaseNode::class, 'getDeprecation')) {
+            return [
+                'hwi/oauth-bundle',
+                '2.0',
+                'option "%path%.%node%" is deprecated. Firewall names are collected automatically.',
+            ];
+        }
+
+        return ['Since hwi/oauth-bundle 2.0: option "hwi_oauth.firewall_names" is deprecated. Firewall names are collected automatically.'];
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/DependencyInjection/HWIOAuthExtension.php b/vendor/hwi/oauth-bundle/src/DependencyInjection/HWIOAuthExtension.php
new file mode 100644
index 0000000..c5a68e6
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/DependencyInjection/HWIOAuthExtension.php
@@ -0,0 +1,199 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\DependencyInjection;
+
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Processor;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\DependencyInjection\Alias;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
+use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ * @author Joseph Bielawski 
+ */
+final class HWIOAuthExtension extends Extension
+{
+    /**
+     * @var \ArrayIterator
+     */
+    private \ArrayIterator $firewallNames;
+
+    private bool $refreshTokenListenerEnabled = false;
+
+    public function __construct()
+    {
+        $this->firewallNames = new \ArrayIterator();
+    }
+
+    public function getConfiguration(array $config, ContainerBuilder $container): Configuration
+    {
+        return new Configuration();
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @throws \Exception
+     * @throws \RuntimeException
+     * @throws InvalidConfigurationException
+     * @throws BadMethodCallException
+     * @throws InvalidArgumentException
+     * @throws OutOfBoundsException
+     * @throws ServiceNotFoundException
+     */
+    public function load(array $configs, ContainerBuilder $container): void
+    {
+        $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/'));
+        $loader->load('controller.php');
+        $loader->load('oauth.php');
+        $loader->load('resource_owners.php');
+        $loader->load('twig.php');
+        $loader->load('util.php');
+
+        $container->registerForAutoconfiguration(ResourceOwnerInterface::class)
+            ->addTag('hwi_oauth.resource_owner');
+
+        $processor = new Processor();
+        $configuration = $this->getConfiguration($configs, $container);
+
+        $config = $processor->processConfiguration($configuration, $configs);
+
+        // set target path parameter
+        $container->setParameter('hwi_oauth.target_path_parameter', $config['target_path_parameter']);
+
+        // set target path domains whitelist parameter
+        $container->setParameter('hwi_oauth.target_path_domains_whitelist', $config['target_path_domains_whitelist']);
+
+        // set use referer parameter
+        $container->setParameter('hwi_oauth.use_referer', $config['use_referer']);
+
+        // set failed use referer parameter
+        $container->setParameter('hwi_oauth.failed_use_referer', $config['failed_use_referer']);
+
+        // set failed auth path
+        $container->setParameter('hwi_oauth.failed_auth_path', $config['failed_auth_path']);
+
+        // set grant rule
+        $container->setParameter('hwi_oauth.grant_rule', $config['grant_rule']);
+
+        // setup services for all configured resource owners
+        $resourceOwners = [];
+        $resourceOwnerReferenceMap = [];
+        foreach ($config['resource_owners'] as $name => $options) {
+            $resourceOwners[$name] = $name;
+            $resourceOwnerReferenceMap[$name] = $this->createResourceOwnerService($container, $name, $options);
+
+            if (!$this->refreshTokenListenerEnabled) {
+                $this->refreshTokenListenerEnabled = $options['options']['refresh_on_expire'] ?? false;
+            }
+        }
+        $container->setParameter('hwi_oauth.resource_owners', $resourceOwners);
+        $container->setAlias(
+            'hwi_oauth.resource_owners.locator',
+            (string) ServiceLocatorTagPass::register($container, $resourceOwnerReferenceMap)
+        );
+
+        $this->createConnectIntegration($container, $config);
+    }
+
+    /**
+     * Creates a resource owner service.
+     *
+     * @param ContainerBuilder $container The container builder
+     * @param string           $name      The name of the service
+     * @param array            $options   Additional options of the service
+     *
+     * @throws InvalidConfigurationException
+     * @throws BadMethodCallException
+     * @throws InvalidArgumentException
+     */
+    public function createResourceOwnerService(ContainerBuilder $container, string $name, array $options): Reference
+    {
+        // alias services
+        if (isset($options['service'])) {
+            return new Reference($options['service']);
+        }
+
+        // handle external resource owners with given class
+        if (isset($options['class'])) {
+            if (!is_subclass_of($options['class'], ResourceOwnerInterface::class, true)) {
+                throw new InvalidConfigurationException(sprintf('Class "%s" must implement interface "HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface".', $options['class']));
+            }
+
+            $definition = new Definition($options['class']);
+        } else {
+            $definition = new Definition("%hwi_oauth.resource_owner.{$options['type']}.class%");
+        }
+        unset($options['class'], $options['type']);
+
+        $definition->setArgument('$httpClient', new Reference('hwi_oauth.http_client'));
+        $definition->setArgument('$httpUtils', new Reference('security.http_utils'));
+        $definition->setArgument('$options', $options);
+        $definition->setArgument('$name', $name);
+        $definition->setArgument('$storage', new Reference('hwi_oauth.storage.session'));
+
+        $container->setDefinition('hwi_oauth.resource_owner.'.$name, $definition);
+
+        return new Reference('hwi_oauth.resource_owner.'.$name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAlias(): string
+    {
+        return 'hwi_oauth';
+    }
+
+    public function getFirewallNames(): \ArrayIterator
+    {
+        return $this->firewallNames;
+    }
+
+    public function isRefreshTokenListenerEnabled(): bool
+    {
+        return $this->refreshTokenListenerEnabled;
+    }
+
+    /**
+     * Check of the connect controllers etc should be enabled.
+     *
+     * @throws BadMethodCallException
+     * @throws InvalidArgumentException
+     */
+    private function createConnectIntegration(ContainerBuilder $container, array $config): void
+    {
+        $container->setParameter('hwi_oauth.connect', isset($config['connect']));
+        $container->setParameter('hwi_oauth.connect.confirmation', $config['connect']['confirmation'] ?? false);
+        $container->setParameter('hwi_oauth.connect.registration_form', $config['connect']['registration_form'] ?? null);
+
+        if (isset($config['connect']['account_connector'])) {
+            $container->setAlias('hwi_oauth.account.connector', new Alias($config['connect']['account_connector'], true));
+        }
+
+        if (isset($config['connect']['registration_form_handler'])) {
+            $container->setAlias('hwi_oauth.registration.form.handler', new Alias($config['connect']['registration_form_handler'], true));
+        }
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/DependencyInjection/Security/Factory/OAuthAuthenticatorFactory.php b/vendor/hwi/oauth-bundle/src/DependencyInjection/Security/Factory/OAuthAuthenticatorFactory.php
new file mode 100644
index 0000000..28a5b14
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/DependencyInjection/Security/Factory/OAuthAuthenticatorFactory.php
@@ -0,0 +1,328 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\DependencyInjection\Security\Factory;
+
+use HWI\Bundle\OAuthBundle\Security\Http\Authenticator\OAuthAuthenticator;
+use HWI\Bundle\OAuthBundle\Security\Http\Firewall\RefreshAccessTokenListener;
+use HWI\Bundle\OAuthBundle\Security\Http\Firewall\RefreshAccessTokenListenerOld;
+use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
+use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
+use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface;
+use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+use Symfony\Component\Config\Definition\Builder\NodeDefinition;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Parameter;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ * @author Vadim Borodavko 
+ */
+final class OAuthAuthenticatorFactory extends AbstractFactory implements AuthenticatorFactoryInterface, FirewallListenerFactoryInterface
+{
+    public function __construct(private \ArrayIterator $firewallNames)
+    {
+    }
+
+    /**
+     * @param ArrayNodeDefinition $node
+     */
+    public function addConfiguration(NodeDefinition $node): void
+    {
+        parent::addConfiguration($node);
+
+        $builder = $node->children();
+        $builder
+            ->scalarNode('login_path')->cannotBeEmpty()->isRequired()->end()
+        ;
+
+        $this->addOAuthProviderConfiguration($node);
+        $this->addResourceOwnersConfiguration($node);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createAuthenticator(
+        ContainerBuilder $container,
+        string $firewallName,
+        array $config,
+        string $userProviderId
+    ): string {
+        $authenticatorId = 'security.authenticator.oauth.'.$firewallName;
+
+        $this->createResourceOwnerMap($container, $firewallName, $config);
+
+        $container
+            ->register($authenticatorId, OAuthAuthenticator::class)
+            ->addArgument(new Reference('security.http_utils'))
+            ->addArgument(
+                $this->createOAuthAwareUserProvider($container, $firewallName, $config['oauth_user_provider'])
+            )
+            ->addArgument($this->createResourceOwnerMapReference($firewallName))
+            ->addArgument($config['resource_owners'])
+            ->addArgument(new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
+            ->addArgument(new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
+            ->addArgument(new Reference('http_kernel'))
+            ->addArgument(array_intersect_key($config, $this->options))
+        ;
+
+        $this->firewallNames[$firewallName] = true;
+
+        return $authenticatorId;
+    }
+
+    public function createListeners(ContainerBuilder $container, string $firewallName, array $config): array
+    {
+        $authenticatorId = 'security.authenticator.oauth.'.$firewallName;
+        $providerId = 'hwi_oauth.authentication.provider.oauth.'.$firewallName;
+
+        $listenerId = 'hwi_oauth.context_listener.token_refresher.'.$firewallName;
+
+        $listenerDef = $container->setDefinition($listenerId, new ChildDefinition('hwi_oauth.context_listener.abstract_token_refresher'));
+
+        $listenerDef->addMethodCall('setResourceOwnerMap', [$this->createResourceOwnerMapReference($firewallName)]);
+
+        if ($container->hasDefinition($authenticatorId)) {
+            // new auth manager
+            $listenerDef
+                ->setClass(RefreshAccessTokenListener::class)
+                ->replaceArgument(0, new Reference($authenticatorId));
+        } else {
+            // old auth manager
+            $listenerDef
+                ->setClass(RefreshAccessTokenListenerOld::class)
+                ->replaceArgument(0, new Reference($providerId));
+        }
+
+        return [$listenerId];
+    }
+
+    public function getPriority(): int
+    {
+        return 0;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getKey(): string
+    {
+        return 'oauth';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getPosition(): string
+    {
+        return 'http';
+    }
+
+    public function getFirewallNames(): \ArrayIterator
+    {
+        return $this->firewallNames;
+    }
+
+    protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config): string
+    {
+        $id = $this->getFailureHandlerId($id);
+        $options = array_intersect_key($config, $this->defaultFailureHandlerOptions);
+
+        $failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.custom_failure_handler'));
+        $failureHandler->replaceArgument(0, new ChildDefinition('hwi_oauth.authentication.failure_handler'));
+        $failureHandler->replaceArgument(1, $options);
+
+        return $id;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
+    {
+        $providerId = 'hwi_oauth.authentication.provider.oauth.'.$id;
+
+        $this->createResourceOwnerMap($container, $id, $config);
+
+        $container
+            ->setDefinition($providerId, new ChildDefinition('hwi_oauth.authentication.provider.oauth'))
+            ->addArgument($this->createOAuthAwareUserProvider($container, $id, $config['oauth_user_provider']))
+            ->addArgument($this->createResourceOwnerMapReference($id))
+            ->addArgument(new Reference('hwi_oauth.user_checker'))
+            ->addArgument(new Reference('security.token_storage'))
+        ;
+
+        $this->firewallNames[$id] = true;
+
+        return $providerId;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function createEntryPoint($container, $id, $config, ?string $defaultEntryPointId): ?string
+    {
+        $entryPointId = 'hwi_oauth.authentication.entry_point.oauth.'.$id;
+
+        $container
+            ->setDefinition($entryPointId, new ChildDefinition('hwi_oauth.authentication.entry_point.oauth'))
+            ->addArgument($config['login_path'])
+            ->addArgument($config['use_forward'])
+        ;
+
+        return $entryPointId;
+    }
+
+    protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider): string
+    {
+        // @phpstan-ignore-next-line Symfony <5.4 BC layer
+        $listenerId = parent::createListener($container, $id, $config, $userProvider);
+
+        $checkPaths = $config['resource_owners'];
+
+        $container
+            ->getDefinition($listenerId)
+            ->addMethodCall('setResourceOwnerMap', [$this->createResourceOwnerMapReference($id)])
+            ->addMethodCall('setCheckPaths', [$checkPaths])
+        ;
+
+        return $listenerId;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getListenerId(): string
+    {
+        return 'hwi_oauth.authentication.listener.oauth';
+    }
+
+    /**
+     * Gets a reference to the resource owner map.
+     */
+    private function createResourceOwnerMapReference(string $firewallName): Reference
+    {
+        return new Reference('hwi_oauth.resource_ownermap.'.$firewallName);
+    }
+
+    /**
+     * Creates a resource owner map for the given configuration.
+     */
+    private function createResourceOwnerMap(ContainerBuilder $container, string $firewallName, array $config): void
+    {
+        $resourceOwnersMap = [];
+        foreach ($config['resource_owners'] as $name => $checkPath) {
+            $resourceOwnersMap[$name] = $checkPath;
+        }
+        $container->setParameter('hwi_oauth.resource_ownermap.configured.'.$firewallName, $resourceOwnersMap);
+
+        $container
+            ->setDefinition(
+                $this->createResourceOwnerMapReference($firewallName),
+                new ChildDefinition('hwi_oauth.abstract_resource_ownermap')
+            )
+            ->replaceArgument('$resourceOwners', new Parameter('hwi_oauth.resource_ownermap.configured.'.$firewallName))
+            ->setPublic(true)
+        ;
+    }
+
+    private function createOAuthAwareUserProvider(ContainerBuilder $container, $id, $config): Reference
+    {
+        $serviceId = 'hwi_oauth.user.provider.entity.'.$id;
+
+        // todo: move this to factories?
+        switch (key($config)) {
+            case 'oauth':
+                $container
+                    ->setDefinition($serviceId, new ChildDefinition('hwi_oauth.user.provider'))
+                ;
+                break;
+            case 'orm':
+                $container
+                    ->setDefinition($serviceId, new ChildDefinition('hwi_oauth.user.provider.entity'))
+                    ->addArgument($config['orm']['class'])
+                    ->addArgument($config['orm']['properties'])
+                    ->addArgument($config['orm']['manager_name'])
+                ;
+                break;
+            case 'service':
+                $container
+                    ->setAlias($serviceId, $config['service']);
+                break;
+        }
+
+        return new Reference($serviceId);
+    }
+
+    private function addOAuthProviderConfiguration(ArrayNodeDefinition $node): void
+    {
+        $builder = $node->children();
+        $builder
+            ->arrayNode('oauth_user_provider')
+                ->isRequired()
+                ->children()
+                    ->arrayNode('orm')
+                        ->children()
+                            ->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
+                            ->scalarNode('manager_name')->defaultNull()->end()
+                            ->arrayNode('properties')
+                                ->isRequired()
+                                ->useAttributeAsKey('name')
+                                    ->prototype('scalar')
+                                ->end()
+                            ->end()
+                        ->end()
+                    ->end()
+                    ->scalarNode('service')->cannotBeEmpty()->end()
+                    ->scalarNode('oauth')->end()
+                ->end()
+                ->validate()
+                    ->ifTrue(function ($c) {
+                        return 1 !== \count($c) || !\in_array(key($c), ['oauth', 'orm', 'service'], true);
+                    })
+                    ->thenInvalid("You should configure (only) one of: 'oauth', 'orm', 'service'.")
+                ->end()
+            ->end()
+        ;
+    }
+
+    private function addResourceOwnersConfiguration(ArrayNodeDefinition $node): void
+    {
+        $builder = $node->children();
+        $builder
+            ->arrayNode('resource_owners')
+                ->isRequired()
+                ->useAttributeAsKey('name')
+                    ->prototype('scalar')
+                ->end()
+                ->validate()
+                    ->ifTrue(function ($c) {
+                        $checkPaths = [];
+                        foreach ($c as $checkPath) {
+                            if (\in_array($checkPath, $checkPaths, true)) {
+                                return true;
+                            }
+
+                            $checkPaths[] = $checkPath;
+                        }
+
+                        return false;
+                    })
+                    ->thenInvalid('Each resource owner should have a unique "check_path".')
+                ->end()
+            ->end()
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Event/AbstractEvent.php b/vendor/hwi/oauth-bundle/src/Event/AbstractEvent.php
new file mode 100644
index 0000000..7637431
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Event/AbstractEvent.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Event;
+
+use Symfony\Contracts\EventDispatcher\Event;
+
+abstract class AbstractEvent extends Event
+{
+}
diff --git a/vendor/hwi/oauth-bundle/src/Event/FilterUserResponseEvent.php b/vendor/hwi/oauth-bundle/src/Event/FilterUserResponseEvent.php
new file mode 100644
index 0000000..1e46df2
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Event/FilterUserResponseEvent.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Event;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+/**
+ * @author Marek Štípek
+ */
+final class FilterUserResponseEvent extends UserEvent
+{
+    private Response $response;
+
+    public function __construct(UserInterface $user, Request $request, Response $response)
+    {
+        parent::__construct($user, $request);
+        $this->response = $response;
+    }
+
+    public function getResponse(): Response
+    {
+        return $this->response;
+    }
+
+    public function setResponse(Response $response): void
+    {
+        $this->response = $response;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Event/FormEvent.php b/vendor/hwi/oauth-bundle/src/Event/FormEvent.php
new file mode 100644
index 0000000..60bd448
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Event/FormEvent.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Event;
+
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @author Marek Štípek
+ */
+final class FormEvent extends AbstractEvent
+{
+    private FormInterface $form;
+    private Request $request;
+    private ?Response $response = null;
+
+    public function __construct(FormInterface $form, Request $request)
+    {
+        $this->form = $form;
+        $this->request = $request;
+    }
+
+    public function getForm(): FormInterface
+    {
+        return $this->form;
+    }
+
+    public function getRequest(): Request
+    {
+        return $this->request;
+    }
+
+    public function setResponse(Response $response): void
+    {
+        $this->response = $response;
+    }
+
+    public function getResponse(): ?Response
+    {
+        return $this->response;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Event/GetResponseUserEvent.php b/vendor/hwi/oauth-bundle/src/Event/GetResponseUserEvent.php
new file mode 100644
index 0000000..dab7e49
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Event/GetResponseUserEvent.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Event;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @author Marek Štípek
+ */
+final class GetResponseUserEvent extends UserEvent
+{
+    private ?Response $response = null;
+
+    public function setResponse(Response $response): void
+    {
+        $this->response = $response;
+    }
+
+    public function getResponse(): ?Response
+    {
+        return $this->response;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Event/UserEvent.php b/vendor/hwi/oauth-bundle/src/Event/UserEvent.php
new file mode 100644
index 0000000..be3b1af
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Event/UserEvent.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Event;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+/**
+ * @author Marek Štípek
+ */
+class UserEvent extends AbstractEvent
+{
+    /**
+     * @var UserInterface
+     */
+    private $user;
+
+    /**
+     * @var Request
+     */
+    private $request;
+
+    public function __construct(UserInterface $user, Request $request)
+    {
+        $this->user = $user;
+        $this->request = $request;
+    }
+
+    /**
+     * @return UserInterface
+     */
+    public function getUser()
+    {
+        return $this->user;
+    }
+
+    /**
+     * @return Request
+     */
+    public function getRequest()
+    {
+        return $this->request;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/Form/RegistrationFormHandlerInterface.php b/vendor/hwi/oauth-bundle/src/Form/RegistrationFormHandlerInterface.php
new file mode 100644
index 0000000..10f8e71
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Form/RegistrationFormHandlerInterface.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\Form;
+
+use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * RegistrationFormHandlerInterface.
+ *
+ * Interface for objects that are able to handle a form.
+ *
+ * @author Alexander 
+ */
+interface RegistrationFormHandlerInterface
+{
+    /**
+     * Processes the form for a given request.
+     *
+     * @return bool True if the processing was successful
+     */
+    public function process(Request $request, FormInterface $form, UserResponseInterface $userInformation);
+}
diff --git a/vendor/hwi/oauth-bundle/src/HWIOAuthBundle.php b/vendor/hwi/oauth-bundle/src/HWIOAuthBundle.php
new file mode 100644
index 0000000..1deae2c
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/HWIOAuthBundle.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle;
+
+use HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass\EnableRefreshOAuthTokenListenerCompilerPass;
+use HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass\ResourceOwnerCompilerPass;
+use HWI\Bundle\OAuthBundle\DependencyInjection\Security\Factory\OAuthAuthenticatorFactory;
+use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ */
+class HWIOAuthBundle extends Bundle
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function build(ContainerBuilder $container): void
+    {
+        parent::build($container);
+
+        /** @var SecurityExtension $extension */
+        $extension = $container->getExtension('security');
+
+        $firewallNames = $this->extension->getFirewallNames();
+
+        if (method_exists($extension, 'addAuthenticatorFactory')) {
+            $extension->addAuthenticatorFactory(new OAuthAuthenticatorFactory($firewallNames));
+        } elseif (method_exists($extension, 'addSecurityListenerFactory')) {
+            // Symfony < 5.4 BC layer
+            $extension->addSecurityListenerFactory(new OAuthAuthenticatorFactory($firewallNames));
+        } else {
+            throw new \RuntimeException('Unsupported Symfony Security component version');
+        }
+
+        $container->addCompilerPass(new ResourceOwnerCompilerPass());
+        $container->addCompilerPass(new EnableRefreshOAuthTokenListenerCompilerPass());
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getContainerExtension(): ?ExtensionInterface
+    {
+        // return the right extension instead of "auto-registering" it. Now the
+        // alias can be hwi_oauth instead of hwi_o_auth.
+        return $this->extension ?: $this->extension = $this->createContainerExtension();
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/HWIOAuthEvents.php b/vendor/hwi/oauth-bundle/src/HWIOAuthEvents.php
new file mode 100644
index 0000000..94a6c01
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/HWIOAuthEvents.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle;
+
+/**
+ * @author Marek Štípek
+ */
+final class HWIOAuthEvents
+{
+    /**
+     * @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
+     */
+    public const REGISTRATION_INITIALIZE = 'hwi_oauth.registration.initialize';
+
+    /**
+     * @Event("HWI\Bundle\OAuthBundle\Event\FormEvent")
+     */
+    public const REGISTRATION_SUCCESS = 'hwi_oauth.registration.success';
+
+    /**
+     * @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
+     */
+    public const REGISTRATION_COMPLETED = 'hwi_oauth.registration.completed';
+
+    /**
+     * @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
+     */
+    public const CONNECT_INITIALIZE = 'hwi_oauth.connect.initialize';
+
+    /**
+     * @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
+     */
+    public const CONNECT_CONFIRMED = 'hwi_oauth.connect.confirmed';
+
+    /**
+     * @Event("HWI\Bundle\OAuthBundle\Event\FilterUserResponseEvent")
+     */
+    public const CONNECT_COMPLETED = 'hwi_oauth.connect.completed';
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/Exception/HttpTransportException.php b/vendor/hwi/oauth-bundle/src/OAuth/Exception/HttpTransportException.php
new file mode 100644
index 0000000..b80348d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/Exception/HttpTransportException.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\Exception;
+
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+final class HttpTransportException extends AuthenticationException
+{
+    private string $ownerName;
+
+    public function __construct(string $message, string $ownerName, int $code = 0, ?\Throwable $previous = null)
+    {
+        parent::__construct($message, $code, $previous);
+        $this->ownerName = $ownerName;
+    }
+
+    public function getOwnerName(): string
+    {
+        return $this->ownerName;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/Exception/StateRetrievalException.php b/vendor/hwi/oauth-bundle/src/OAuth/Exception/StateRetrievalException.php
new file mode 100644
index 0000000..6e9790c
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/Exception/StateRetrievalException.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\Exception;
+
+final class StateRetrievalException extends \InvalidArgumentException
+{
+    /**
+     * @param string $key The provided string key
+     */
+    public static function forKey(string $key): self
+    {
+        return new static(sprintf('No value found in state for key [%s]', $key));
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/RequestDataStorage/SessionStorage.php b/vendor/hwi/oauth-bundle/src/OAuth/RequestDataStorage/SessionStorage.php
new file mode 100644
index 0000000..8e2fb29
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/RequestDataStorage/SessionStorage.php
@@ -0,0 +1,121 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\RequestDataStorage;
+
+use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorageInterface;
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Request token storage implementation using the Symfony session.
+ *
+ * @author Alexander 
+ * @author Francisco Facioni 
+ * @author Joseph Bielawski 
+ */
+final class SessionStorage implements RequestDataStorageInterface
+{
+    private RequestStack $requestStack;
+
+    public function __construct(RequestStack $requestStack)
+    {
+        $this->requestStack = $requestStack;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function fetch(ResourceOwnerInterface $resourceOwner, $key, $type = 'token')
+    {
+        $key = $this->generateKey($resourceOwner, $key, $type);
+        if (null === $data = $this->getSession()->get($key)) {
+            throw new \InvalidArgumentException('No data available in storage.');
+        }
+
+        // Request tokens are one time use only
+        if (\in_array($type, ['token', 'csrf_state'], true)) {
+            $this->getSession()->remove($key);
+        }
+
+        return $data;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(ResourceOwnerInterface $resourceOwner, $value, $type = 'token')
+    {
+        if ('token' === $type) {
+            if (!\is_array($value) || !isset($value['oauth_token'])) {
+                throw new \InvalidArgumentException('Invalid request token.');
+            }
+
+            $key = $this->generateKey($resourceOwner, $value['oauth_token'], 'token');
+        } else {
+            $key = $this->generateKey($resourceOwner, $this->getStorageKey($value), $type);
+        }
+
+        $this->getSession()->set($key, $this->getStorageValue($value));
+    }
+
+    /**
+     * Key to for fetching or saving a token.
+     */
+    private function generateKey(ResourceOwnerInterface $resourceOwner, string $key, string $type): string
+    {
+        return sprintf('_hwi_oauth.%s.%s.%s.%s', $resourceOwner->getName(), $resourceOwner->getOption('client_id'), $type, $key);
+    }
+
+    /**
+     * @param array|string|object $value
+     *
+     * @return array|string
+     */
+    private function getStorageValue($value)
+    {
+        if (\is_object($value)) {
+            $value = serialize($value);
+        }
+
+        return $value;
+    }
+
+    /**
+     * @param array|string|object $value
+     */
+    private function getStorageKey($value): string
+    {
+        if (\is_array($value)) {
+            $storageKey = reset($value);
+        } elseif (\is_object($value)) {
+            $storageKey = $value::class;
+        } else {
+            $storageKey = $value;
+        }
+
+        return (string) $storageKey;
+    }
+
+    private function getSession(): SessionInterface
+    {
+        if (method_exists($this->requestStack, 'getSession')) {
+            return $this->requestStack->getSession();
+        }
+
+        if ((null !== $request = $this->requestStack->getCurrentRequest()) && $request->hasSession()) {
+            return $request->getSession();
+        }
+
+        throw new \LogicException('There is currently no session available.');
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/RequestDataStorageInterface.php b/vendor/hwi/oauth-bundle/src/OAuth/RequestDataStorageInterface.php
new file mode 100644
index 0000000..14a5c30
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/RequestDataStorageInterface.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth;
+
+/**
+ * Interface for classes providing a request tokens storage.
+ *
+ * The storage is needed because the OAuth1.0a authentication flow requires
+ * requests to be signed with the same values in consecutive requests.
+ *
+ * Additionally we require this to provide CSRF protection for all resource
+ * owners.
+ *
+ * @author Alexander 
+ * @author Francisco Facioni 
+ * @author Joseph Bielawski 
+ */
+interface RequestDataStorageInterface
+{
+    /**
+     * Fetch a request data from the storage.
+     *
+     * @param string $key
+     * @param string $type
+     */
+    public function fetch(ResourceOwnerInterface $resourceOwner, $key, $type = 'token');
+
+    /**
+     * Save a request data to the storage.
+     *
+     * @param array|string|object $value
+     * @param string              $type
+     */
+    public function save(ResourceOwnerInterface $resourceOwner, $value, $type = 'token');
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AbstractResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AbstractResourceOwner.php
new file mode 100644
index 0000000..8c085ba
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AbstractResourceOwner.php
@@ -0,0 +1,347 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorageInterface;
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
+use HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse;
+use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
+use HWI\Bundle\OAuthBundle\OAuth\State\State;
+use HWI\Bundle\OAuthBundle\OAuth\StateInterface;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\HttpFoundation\Request as HttpRequest;
+use Symfony\Component\OptionsResolver\Exception\AccessException;
+use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Http\HttpUtils;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+use Symfony\Contracts\HttpClient\ResponseInterface;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ * @author Francisco Facioni 
+ * @author Joseph Bielawski 
+ */
+abstract class AbstractResourceOwner implements ResourceOwnerInterface
+{
+    protected array $options = [];
+
+    /**
+     * @var array|string|null>
+     */
+    protected array $paths = [];
+
+    protected HttpClientInterface $httpClient;
+    protected HttpUtils $httpUtils;
+    protected string $name;
+    protected StateInterface $state;
+    protected RequestDataStorageInterface $storage;
+    private bool $stateLoaded = false;
+
+    /**
+     * @param array  $options Options for the resource owner
+     * @param string $name    Name for the resource owner
+     */
+    public function __construct(
+        HttpClientInterface $httpClient,
+        HttpUtils $httpUtils,
+        array $options,
+        string $name,
+        RequestDataStorageInterface $storage
+    ) {
+        $this->httpClient = $httpClient;
+        $this->httpUtils = $httpUtils;
+        $this->name = $name;
+        $this->storage = $storage;
+
+        if (!empty($options['paths'])) {
+            $this->addPaths($options['paths']);
+        }
+        unset($options['paths']);
+
+        if (!empty($options['options'])) {
+            $options += $options['options'];
+            unset($options['options']);
+        }
+        unset($options['options']);
+
+        // Resolve merged options
+        $resolver = new OptionsResolver();
+        $this->configureOptions($resolver);
+        $this->options = $resolver->resolve($options);
+
+        $this->state = new State($this->options['state'] ?: null);
+
+        $this->configure();
+    }
+
+    /**
+     * Gives a chance for extending providers to customize stuff.
+     */
+    public function configure()
+    {
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getOption($name)
+    {
+        if (!\array_key_exists($name, $this->options)) {
+            throw new \InvalidArgumentException(sprintf('Unknown option "%s"', $name));
+        }
+
+        return $this->options[$name];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addPaths(array $paths)
+    {
+        $this->paths = array_merge($this->paths, $paths);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getState(): StateInterface
+    {
+        if ($this->stateLoaded) {
+            return $this->state;
+        }
+
+        // lazy-loading for stored states
+        try {
+            $storedData = $this->storage->fetch($this, State::class, 'state');
+        } catch (\Throwable $e) {
+            $storedData = null;
+        }
+        if (null !== $storedData && false !== $storedState = unserialize($storedData)) {
+            foreach ($storedState->getAll() as $key => $value) {
+                $this->addStateParameter($key, $value);
+            }
+        }
+        $this->stateLoaded = true;
+
+        return $this->state;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addStateParameter(string $key, string $value): void
+    {
+        if (!$this->state->has($key)) {
+            $this->state->add($key, $value);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function storeState(?StateInterface $state = null): void
+    {
+        if (null === $state || 0 === \count($state->getAll())) {
+            return;
+        }
+
+        $this->storage->save($this, $state, 'state');
+    }
+
+    /**
+     * Retrieve an access token for a given code.
+     *
+     * @param HttpRequest $request         The request object from where the code is going to extracted
+     * @param mixed       $redirectUri     The uri to redirect the client back to
+     * @param array       $extraParameters An array of parameters to add to the url
+     *
+     * @return array array containing the access token and it's 'expires_in' value,
+     *               along with any other parameters returned from the authentication
+     *               provider
+     *
+     * @throws AuthenticationException If an OAuth error occurred or no access token is found
+     * @throws HttpTransportException
+     */
+    abstract public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = []);
+
+    /**
+     * Refresh an access token using a refresh token.
+     *
+     * @param string $refreshToken    Refresh token
+     * @param array  $extraParameters An array of parameters to add to the url
+     *
+     * @return array array containing the access token and it's 'expires_in' value,
+     *               along with any other parameters returned from the authentication
+     *               provider
+     *
+     * @throws AuthenticationException If an OAuth error occurred or no access token is found
+     * @throws HttpTransportException
+     */
+    public function refreshAccessToken($refreshToken, array $extraParameters = [])
+    {
+        throw new AuthenticationException('OAuth error: "Method unsupported."');
+    }
+
+    /**
+     * Revoke an OAuth access token or refresh token.
+     *
+     * @param string $token the token (access token or a refresh token) that should be revoked
+     *
+     * @return bool returns True if the revocation was successful, otherwise False
+     *
+     * @throws AuthenticationException If an OAuth error occurred
+     * @throws HttpTransportException
+     */
+    public function revokeToken($token)
+    {
+        throw new AuthenticationException('OAuth error: "Method unsupported."');
+    }
+
+    /**
+     * Get the response object to return.
+     *
+     * @return UserResponseInterface
+     */
+    protected function getUserResponse()
+    {
+        $response = new $this->options['user_response_class']();
+        if ($response instanceof PathUserResponse) {
+            $response->setPaths($this->paths);
+        }
+
+        return $response;
+    }
+
+    /**
+     * @param string $url
+     *
+     * @return string
+     */
+    protected function normalizeUrl($url, array $parameters = [])
+    {
+        $normalizedUrl = $url;
+        if (!empty($parameters)) {
+            $normalizedUrl .= (str_contains($url, '?') ? '&' : '?').http_build_query($parameters, '', '&');
+        }
+
+        return $normalizedUrl;
+    }
+
+    /**
+     * Performs an HTTP request.
+     *
+     * @param string       $url     The url to fetch
+     * @param string|array $content The content of the request
+     * @param array        $headers The headers of the request
+     * @param string       $method  The HTTP method to use
+     *
+     * @return ResponseInterface The response content
+     *
+     * @throws HttpTransportException
+     */
+    protected function httpRequest($url, $content = null, array $headers = [], $method = null)
+    {
+        if (null === $method) {
+            $method = null === $content || '' === $content ? 'GET' : 'POST';
+        }
+
+        $options = ['headers' => $headers];
+        $options['headers'] += ['User-Agent' => 'HWIOAuthBundle (https://github.com/hwi/HWIOAuthBundle)'];
+        if (\is_string($content)) {
+            if (!isset($options['headers']['Content-Length'])) {
+                $options['headers'] += ['Content-Length' => (string) \strlen($content)];
+            }
+        }
+        $options['body'] = $content;
+
+        try {
+            return $this->httpClient->request(
+                $method,
+                $url,
+                $options
+            );
+        } catch (TransportExceptionInterface $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    protected function getResponseContent(ResponseInterface $rawResponse): array
+    {
+        try {
+            return $rawResponse->toArray(false);
+        } catch (JsonException $e) {
+            parse_str($rawResponse->getContent(false), $response);
+
+            return $response;
+        } catch (TransportExceptionInterface $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * @param string $url
+     *
+     * @return ResponseInterface
+     *
+     * @throws HttpTransportException
+     */
+    abstract protected function doGetTokenRequest($url, array $parameters = []);
+
+    /**
+     * @param string $url
+     *
+     * @return ResponseInterface
+     *
+     * @throws HttpTransportException
+     */
+    abstract protected function doGetUserInformationRequest($url, array $parameters = []);
+
+    /**
+     * Configure the option resolver.
+     *
+     * @throws AccessException
+     * @throws UndefinedOptionsException
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        $resolver->setRequired([
+            'client_id',
+            'client_secret',
+            'authorization_url',
+            'access_token_url',
+            'infos_url',
+        ]);
+
+        $resolver->setDefaults([
+            'scope' => null,
+            'state' => null,
+            'csrf' => false,
+            'user_response_class' => PathUserResponse::class,
+            'auth_with_one_url' => false,
+        ]);
+
+        $resolver->setAllowedValues('csrf', [true, false]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AmazonResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AmazonResourceOwner.php
new file mode 100644
index 0000000..f18f98a
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AmazonResourceOwner.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Fabian Kiss 
+ */
+final class AmazonResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'amazon';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'user_id',
+        'nickname' => 'name',
+        'realname' => 'name',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.amazon.com/ap/oa',
+            'access_token_url' => 'https://api.amazon.com/auth/o2/token',
+            'infos_url' => 'https://api.amazon.com/user/profile',
+
+            'scope' => 'profile',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AppleResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AppleResourceOwner.php
new file mode 100644
index 0000000..940dbb3
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AppleResourceOwner.php
@@ -0,0 +1,196 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Firebase\JWT\JWT;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Josip Letica 
+ * @author Sébastien Alfaiate 
+ */
+final class AppleResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'apple';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'sub',
+        'firstname' => 'firstName',
+        'lastname' => 'lastName',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        return parent::getAuthorizationUrl($redirectUri, array_merge([
+            'response_mode' => $this->options['response_mode'],
+        ], $extraParameters));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        if (!isset($accessToken['id_token'])) {
+            throw new \InvalidArgumentException('Undefined index id_token');
+        }
+
+        $jwt = self::jwtDecode($accessToken['id_token']);
+        $data = $jwt ? json_decode(base64_decode($jwt), true) : [];
+
+        if (isset($accessToken['firstName'], $accessToken['lastName'])) {
+            $data['firstName'] = $accessToken['firstName'];
+            $data['lastName'] = $accessToken['lastName'];
+        }
+
+        $response = $this->getUserResponse();
+        $response->setData(json_encode($data));
+        $response->setResourceOwner($this);
+        $response->setOAuthToken(new OAuthToken($accessToken));
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
+    {
+        OAuthErrorHandler::handleOAuthError($request);
+
+        $parameters = array_merge([
+            'code' => $request->request->get('code'),
+            'grant_type' => 'authorization_code',
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->getClientSecret(),
+            'redirect_uri' => $redirectUri,
+        ], $extraParameters);
+
+        $response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
+        $response = $this->getResponseContent($response);
+
+        $this->validateResponseContent($response);
+
+        $userInfo = $request->request->get('user');
+        if (null !== $userInfo) {
+            $userInfo = json_decode($userInfo, true, 512, \JSON_THROW_ON_ERROR);
+            if (isset($userInfo['name'])) {
+                $response['firstName'] = $userInfo['name']['firstName'];
+                $response['lastName'] = $userInfo['name']['lastName'];
+            }
+        }
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function refreshAccessToken($refreshToken, array $extraParameters = [])
+    {
+        $parameters = [
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->options['client_secret'],
+        ];
+
+        return parent::refreshAccessToken($refreshToken, array_merge($parameters, $extraParameters));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handles(Request $request)
+    {
+        return $request->request->has('code');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://appleid.apple.com/auth/authorize',
+            'access_token_url' => 'https://appleid.apple.com/auth/token',
+            'infos_url' => '',
+            'use_commas_in_scope' => false,
+            'display' => null,
+            'scope' => 'name email',
+            'appsecret_proof' => false,
+            'response_mode' => 'form_post',
+            'auth_key' => null,
+            'key_id' => null,
+            'team_id' => null,
+        ]);
+    }
+
+    private static function jwtDecode(string $idToken)
+    {
+        // from http://stackoverflow.com/a/28748285/624544
+        [, $jwt] = explode('.', $idToken, 3);
+
+        // if the token was urlencoded, do some fixes to ensure that it is valid base64 encoded
+        $jwt = str_replace(['-', '_'], ['+', '/'], $jwt);
+
+        // complete token if needed
+        switch (\strlen($jwt) % 4) {
+            case 0:
+                break;
+            case 2:
+            case 3:
+                $jwt .= '=';
+                break;
+            default:
+                throw new \InvalidArgumentException('Invalid base64 format sent back');
+        }
+
+        return $jwt;
+    }
+
+    private function getClientSecret(): string
+    {
+        if ('auto' !== $this->options['client_secret']) {
+            return $this->options['client_secret'];
+        }
+
+        if (!isset($this->options['auth_key'], $this->options['key_id'], $this->options['team_id'])) {
+            throw new \InvalidArgumentException('Options "auth_key", "key_id" and "team_id" must be defined to use automatic "client_secret" generation.');
+        }
+
+        if (!class_exists(JWT::class)) {
+            throw new \RuntimeException('PHP-JWT library is required to use automatic "client_secret" generation. Please try "composer require firebase/php-jwt".');
+        }
+
+        $payload = [
+            'iss' => $this->options['team_id'],
+            'iat' => time(),
+            'exp' => time() + 600,
+            'aud' => 'https://appleid.apple.com',
+            'sub' => $this->options['client_id'],
+        ];
+
+        return JWT::encode($payload, $this->options['auth_key'], 'ES256', $this->options['key_id']);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AsanaResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AsanaResourceOwner.php
new file mode 100644
index 0000000..554579f
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AsanaResourceOwner.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Guillaume Potier 
+ */
+final class AsanaResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'asana';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'data.id',
+        'nickname' => 'data.name',
+        'realname' => 'data.name',
+        'email' => 'data.email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://app.asana.com/-/oauth_authorize',
+            'access_token_url' => 'https://app.asana.com/-/oauth_token',
+            'infos_url' => 'https://app.asana.com/api/1.0/users/me',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Auth0ResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Auth0ResourceOwner.php
new file mode 100644
index 0000000..233cd68
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Auth0ResourceOwner.php
@@ -0,0 +1,82 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Hernan Rajchert 
+ */
+final class Auth0ResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'auth0';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'sub',
+        'nickname' => 'nickname',
+        'realname' => 'name',
+        'email' => 'email',
+        'profilepicture' => 'picture',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $auth0Client = base64_encode(json_encode([
+            'name' => 'HWIOAuthBundle',
+            'version' => 'unknown',
+            'environment' => [
+                'name' => 'PHP',
+                'version' => \PHP_VERSION,
+            ],
+        ]));
+
+        $resolver->setDefaults([
+            'authorization_url' => '{base_url}/authorize?auth0Client='.$auth0Client,
+            'access_token_url' => '{base_url}/oauth/token',
+            'infos_url' => '{base_url}/userinfo',
+            'auth0_client' => $auth0Client,
+        ]);
+
+        $resolver->setRequired([
+            'base_url',
+        ]);
+
+        $normalizer = function (Options $options, $value) {
+            return str_replace('{base_url}', $options['base_url'], $value);
+        };
+
+        $resolver->setNormalizer('authorization_url', $normalizer);
+        $resolver->setNormalizer('access_token_url', $normalizer);
+        $resolver->setNormalizer('infos_url', $normalizer);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function httpRequest($url, $content = null, array $headers = [], $method = null)
+    {
+        if (isset($this->options['auth0_client'])) {
+            $headers['Auth0-Client'] = $this->options['auth0_client'];
+        }
+
+        return parent::httpRequest($url, $content, $headers, $method);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AzureResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AzureResourceOwner.php
new file mode 100644
index 0000000..077e94d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/AzureResourceOwner.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Baptiste Clavié 
+ */
+final class AzureResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'azure';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'sub',
+        'nickname' => 'unique_name',
+        'lastname' => 'family_name',
+        'firstname' => 'given_name',
+        'realname' => ['given_name', 'family_name'],
+        'email' => ['upn', 'email'],
+        'profilepicture' => null,
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function configure()
+    {
+        $this->options['access_token_url'] = sprintf($this->options['access_token_url'], $this->options['application']);
+        $this->options['authorization_url'] = sprintf($this->options['authorization_url'], $this->options['application']);
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        // from http://stackoverflow.com/a/28748285/624544
+        [, $jwt] = explode('.', \array_key_exists('id_token', $accessToken) ? $accessToken['id_token'] : $accessToken['access_token'], 3);
+
+        // if the token was urlencoded, do some fixes to ensure that it is valid base64 encoded
+        $jwt = str_replace(['-', '_'], ['+', '/'], $jwt);
+
+        // complete token if needed
+        switch (\strlen($jwt) % 4) {
+            case 0:
+                break;
+
+            case 2:
+            case 3:
+                $jwt .= '=';
+                break;
+
+            default:
+                throw new \InvalidArgumentException('Invalid base64 format sent back');
+        }
+
+        $response = parent::getUserInformation($accessToken, $extraParameters);
+        $response->setData(base64_decode($jwt));
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'infos_url' => 'https://graph.microsoft.com/v1.0/me',
+            'authorization_url' => 'https://login.microsoftonline.com/%s/oauth2/v2.0/authorize',
+            'access_token_url' => 'https://login.microsoftonline.com/%s/oauth2/v2.0/token',
+            'application' => 'common',
+            'api_version' => 'v1.0',
+            'csrf' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Bitbucket2ResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Bitbucket2ResourceOwner.php
new file mode 100644
index 0000000..d0cebf8
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Bitbucket2ResourceOwner.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author David Sanchez 
+ */
+final class Bitbucket2ResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'bitbucket2';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'uuid',
+        'nickname' => 'username',
+        'email' => 'email',
+        'realname' => 'display_name',
+        'profilepicture' => 'links.avatar.href',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $response = parent::getUserInformation($accessToken, $extraParameters);
+        $responseData = $response->getData();
+
+        // fetch the email addresses linked to the account
+        if (empty($responseData['email'])) {
+            $content = $this->httpRequest($this->normalizeUrl($this->options['emails_url']), null, ['Authorization' => 'Bearer '.$accessToken['access_token']]);
+            foreach ($this->getResponseContent($content)['values'] as $email) {
+                // we only need the primary email address
+                if (true === $email['is_primary']) {
+                    $responseData['email'] = $email['email'];
+                }
+            }
+
+            $response->setData($responseData);
+        }
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://bitbucket.org/site/oauth2/authorize',
+            'access_token_url' => 'https://bitbucket.org/site/oauth2/access_token',
+            'infos_url' => 'https://api.bitbucket.org/2.0/user',
+            'emails_url' => 'https://api.bitbucket.org/2.0/user/emails',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BitbucketResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BitbucketResourceOwner.php
new file mode 100644
index 0000000..fb0578a
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BitbucketResourceOwner.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class BitbucketResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'bitbucket';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'user.username',
+        'nickname' => 'user.username',
+        'realname' => 'user.display_name',
+        'profilepicture' => 'user.avatar',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://bitbucket.org/api/1.0/oauth/authenticate',
+            'request_token_url' => 'https://bitbucket.org/api/1.0/oauth/request_token',
+            'access_token_url' => 'https://bitbucket.org/api/1.0/oauth/access_token',
+            'infos_url' => 'https://bitbucket.org/api/1.0/user',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BitlyResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BitlyResourceOwner.php
new file mode 100644
index 0000000..4b27dde
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BitlyResourceOwner.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class BitlyResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'bitly';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'data.login',
+        'nickname' => 'data.display_name',
+        'realname' => 'data.full_name',
+        'profilepicture' => 'data.profile_image',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'use_bearer_authorization' => false,
+            'authorization_url' => 'https://bitly.com/oauth/authorize',
+            'access_token_url' => 'https://api-ssl.bitly.com/oauth/access_token',
+            'infos_url' => 'https://api-ssl.bitly.com/v3/user/info?format=json',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BoxResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BoxResourceOwner.php
new file mode 100644
index 0000000..1701ce2
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BoxResourceOwner.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class BoxResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'box';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'name',
+        'realname' => 'name',
+        'email' => 'login',
+        'profilepicture' => 'avatar_url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        $parameters = [
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->options['client_secret'],
+            'token' => $token,
+        ];
+
+        $response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url']), $parameters, [], 'POST');
+
+        return 200 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.box.com/api/oauth2/authorize',
+            'access_token_url' => 'https://www.box.com/api/oauth2/token',
+            'revoke_token_url' => 'https://www.box.com/api/oauth2/revoke',
+            'infos_url' => 'https://api.box.com/2.0/users/me',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BufferAppResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BufferAppResourceOwner.php
new file mode 100644
index 0000000..84ef9a3
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/BufferAppResourceOwner.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author João Paulo Cercal 
+ */
+final class BufferAppResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'bufferapp';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'id',
+        'realname' => 'id',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://bufferapp.com/oauth2/authorize',
+            'access_token_url' => 'https://api.bufferapp.com/1/oauth2/token.json',
+            'infos_url' => 'https://api.bufferapp.com/1/user.json',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/CleverResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/CleverResourceOwner.php
new file mode 100644
index 0000000..7651946
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/CleverResourceOwner.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Matt Farmer 
+ */
+final class CleverResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'clever';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'data.id',
+        'email' => 'data.email',
+        'firstname' => 'data.name.first',
+        'lastname' => 'data.name.last',
+        'realname' => [
+            'data.name.first',
+            'data.name.middle',
+            'data.name.last',
+        ],
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://clever.com/oauth/authorize',
+            'access_token_url' => 'https://clever.com/oauth/tokens',
+            'infos_url' => 'https://api.clever.com/me',
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetTokenRequest($url, array $parameters = [])
+    {
+        $authPreHash = $this->options['client_id'].':'.$this->options['client_secret'];
+
+        return $this->httpRequest(
+            $url,
+            http_build_query($parameters, '', '&'),
+            [
+                'Authorization' => 'Basic '.base64_encode($authPreHash),
+            ]
+        );
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DailymotionResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DailymotionResourceOwner.php
new file mode 100644
index 0000000..1acc623
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DailymotionResourceOwner.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class DailymotionResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'dailymotion';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'screenname',
+        'realname' => 'fullname', // requires 'userinfo' scope
+        'email' => 'email', // requires 'email' scope
+        'profilepicture' => 'avatar_medium_url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        return parent::getAuthorizationUrl($redirectUri, array_merge(['display' => $this->options['display']], $extraParameters));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.dailymotion.com/oauth/authorize',
+            'access_token_url' => 'https://api.dailymotion.com/oauth/token',
+            'infos_url' => 'https://api.dailymotion.com/me',
+
+            'display' => null,
+        ]);
+
+        // @link http://www.dailymotion.com/doc/api/authentication.html#dialog-form-factors
+        $resolver->setAllowedValues('display', ['page', 'popup', 'mobile', null]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DeezerResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DeezerResourceOwner.php
new file mode 100644
index 0000000..a97d833
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DeezerResourceOwner.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Kieu Anh Tuan 
+ */
+final class DeezerResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'deezer';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'name',
+        'realname' => 'firstname',
+        'email' => 'email',
+        'firstname' => 'firstname',
+        'lastname' => 'lastname',
+        'profilepicture' => 'picture',
+        'gender' => 'gender',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://connect.deezer.com/oauth/auth.php',
+            'access_token_url' => 'https://connect.deezer.com/oauth/access_token.php',
+            'infos_url' => 'https://api.deezer.com/user/me',
+            'use_bearer_authorization' => false,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DeviantartResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DeviantartResourceOwner.php
new file mode 100644
index 0000000..6cbf428
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DeviantartResourceOwner.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class DeviantartResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'deviantart';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'username',
+        'nickname' => 'username',
+        'profilepicture' => 'usericonurl',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.deviantart.com/oauth2/draft15/authorize',
+            'access_token_url' => 'https://www.deviantart.com/oauth2/draft15/token',
+            'infos_url' => 'https://www.deviantart.com/api/draft15/user/whoami',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DiscogsResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DiscogsResourceOwner.php
new file mode 100644
index 0000000..46f0082
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DiscogsResourceOwner.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+final class DiscogsResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'discogs';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'username',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.discogs.com/oauth/authorize',
+            'request_token_url' => 'https://api.discogs.com/oauth/request_token',
+            'access_token_url' => 'https://api.discogs.com/oauth/access_token',
+            'infos_url' => 'https://api.discogs.com/oauth/identity',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DisqusResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DisqusResourceOwner.php
new file mode 100644
index 0000000..3526585
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DisqusResourceOwner.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Alexander Müller 
+ */
+final class DisqusResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'disqus';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'response.id',
+        'nickname' => 'response.username',
+        'realname' => 'response.name',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        // Disqus requires api key and secret for user information requests
+        $url = $this->normalizeUrl($url, [
+            'api_key' => $this->options['client_id'],
+            'api_secret' => $this->options['client_secret'],
+        ]);
+
+        return parent::doGetUserInformationRequest($url, $parameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://disqus.com/api/oauth/2.0/authorize/',
+            'access_token_url' => 'https://disqus.com/api/oauth/2.0/access_token/',
+            'infos_url' => 'https://disqus.com/api/3.0/users/details.json',
+
+            'scope' => 'read',
+
+            'use_commas_in_scope' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DropboxResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DropboxResourceOwner.php
new file mode 100644
index 0000000..289034b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/DropboxResourceOwner.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Jamie Sutherland
+ */
+final class DropboxResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'dropbox';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'account_id',
+        'nickname' => 'email',
+        'realname' => 'email',
+        'email' => 'email',
+    ];
+
+    /**
+     * Dropbox API v2 requires a POST request to simply get user info!
+     *
+     * @return UserResponseInterface
+     */
+    public function getUserInformation(array $accessToken,
+        array $extraParameters = []
+    ) {
+        if ($this->options['use_bearer_authorization']) {
+            $content = $this->httpRequest(
+                $this->normalizeUrl($this->options['infos_url'], $extraParameters),
+                'null',
+                [
+                    'Authorization' => 'Bearer '.$accessToken['access_token'],
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json; charset=utf-8',
+                ], 'POST');
+        } else {
+            $content = $this->doGetUserInformationRequest(
+                $this->normalizeUrl(
+                    $this->options['infos_url'],
+                    array_merge([$this->options['attr_name'] => $accessToken['access_token']], $extraParameters)
+                )
+            );
+        }
+
+        try {
+            $response = $this->getUserResponse();
+            $response->setData($content->toArray(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.dropbox.com/oauth2/authorize',
+            'access_token_url' => 'https://api.dropbox.com/oauth2/token',
+            'infos_url' => 'https://api.dropboxapi.com/2/users/get_current_account',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/EveOnlineResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/EveOnlineResourceOwner.php
new file mode 100644
index 0000000..fdc69d3
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/EveOnlineResourceOwner.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Ivan Stankovic 
+ */
+final class EveOnlineResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'eveonline';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'CharacterID',
+        'nickname' => 'CharacterName',
+        'realname' => 'CharacterName',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://login.eveonline.com/oauth/authorize',
+            'access_token_url' => 'https://login.eveonline.com/oauth/token',
+            'infos_url' => 'https://login.eveonline.com/oauth/verify',
+            'use_commas_in_scope' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/EventbriteResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/EventbriteResourceOwner.php
new file mode 100644
index 0000000..e95d7dd
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/EventbriteResourceOwner.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class EventbriteResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'eventbrite';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'user.user_id',
+        'nickname' => 'user.first_name',
+        'firstname' => 'user.first_name',
+        'lastname' => 'user.last_name',
+        'realname' => ['user.first_name', 'user.last_name'],
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetTokenRequest($url, array $parameters = [])
+    {
+        return $this->httpRequest($url, http_build_query($parameters, '', '&'), [], 'POST');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.eventbrite.com/oauth/authorize',
+            'access_token_url' => 'https://www.eventbrite.com/oauth/token',
+            'infos_url' => 'https://www.eventbrite.com/json/user_get',
+
+            'use_bearer_authorization' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FacebookResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FacebookResourceOwner.php
new file mode 100644
index 0000000..99457ea
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FacebookResourceOwner.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Geoffrey Bachelet 
+ */
+final class FacebookResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'facebook';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'name',
+        'firstname' => 'first_name',
+        'lastname' => 'last_name',
+        'realname' => 'name',
+        'email' => 'email',
+        'profilepicture' => 'picture.data.url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        if ($this->options['appsecret_proof']) {
+            $extraParameters['appsecret_proof'] = hash_hmac('sha256', $accessToken['access_token'], $this->options['client_secret']);
+        }
+
+        return parent::getUserInformation($accessToken, $extraParameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        $extraOptions = [];
+        if (isset($this->options['display'])) {
+            $extraOptions['display'] = $this->options['display'];
+        }
+
+        if (isset($this->options['auth_type'])) {
+            $extraOptions['auth_type'] = $this->options['auth_type'];
+        }
+
+        return parent::getAuthorizationUrl($redirectUri, array_merge($extraOptions, $extraParameters));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
+    {
+        $parameters = [];
+        if ($request->query->has('fb_source')) {
+            $parameters['fb_source'] = $request->query->get('fb_source');
+        }
+
+        if ($request->query->has('fb_appcenter')) {
+            $parameters['fb_appcenter'] = $request->query->get('fb_appcenter');
+        }
+
+        return parent::getAccessToken($request, $this->normalizeUrl($redirectUri, $parameters), $extraParameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        $parameters = [
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->options['client_secret'],
+        ];
+
+        $response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url'], ['access_token' => $token]), $parameters, [], 'DELETE');
+
+        return 200 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.facebook.com/v19.0/dialog/oauth',
+            'access_token_url' => 'https://graph.facebook.com/v19.0/oauth/access_token',
+            'revoke_token_url' => 'https://graph.facebook.com/v19.0/me/permissions',
+            'infos_url' => 'https://graph.facebook.com/v19.0/me?fields=id,first_name,last_name,name,email,picture.type(large)',
+            'use_commas_in_scope' => true,
+            'display' => null,
+            'auth_type' => null,
+            'appsecret_proof' => false,
+        ]);
+
+        $resolver
+            ->setAllowedValues('display', ['page', 'popup', 'touch', null]) // @link https://developers.facebook.com/docs/reference/dialogs/#display
+            ->setAllowedValues('auth_type', ['rerequest', null]) // @link https://developers.facebook.com/docs/reference/javascript/FB.login/
+            ->setAllowedTypes('appsecret_proof', 'bool') // @link https://developers.facebook.com/docs/graph-api/securing-requests
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FiwareResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FiwareResourceOwner.php
new file mode 100644
index 0000000..f9aa0f5
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FiwareResourceOwner.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * Resource owner for the fiware keyrock idm oauth 2.0 service
+ * more infos at https://github.com/ging/fi-ware-idm/wiki/Using-the-FIWARE-LAB-instance.
+ *
+ * @author Christian Kaspar 
+ */
+final class FiwareResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'fiware';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'nickName',
+        'realname' => 'displayName',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
+    {
+        $parameters = array_merge([
+            'code' => $request->query->get('code'),
+            'grant_type' => 'authorization_code',
+            'redirect_uri' => $redirectUri,
+        ], $extraParameters);
+
+        $headers = [
+            'Authorization' => 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret']),
+        ];
+
+        $response = $this->httpRequest($this->options['access_token_url'], http_build_query($parameters, '', '&'), $headers, 'POST');
+        $responseContent = $this->getResponseContent($response);
+
+        $this->validateResponseContent($responseContent);
+
+        return $responseContent;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        if ($this->options['use_bearer_authorization']) {
+            $content = $this->httpRequest(
+                $this->normalizeUrl(
+                    $this->options['infos_url'],
+                    ['access_token' => $accessToken['access_token']]
+                ),
+                null,
+                ['Authorization' => 'Bearer']
+            );
+        } else {
+            $content = $this->doGetUserInformationRequest(
+                $this->normalizeUrl(
+                    $this->options['infos_url'],
+                    [$this->options['attr_name'] => $accessToken['access_token']]
+                )
+            );
+        }
+
+        $response = $this->getUserResponse();
+        $response->setData($content->getContent());
+        $response->setResourceOwner($this);
+        $response->setOAuthToken(new OAuthToken($accessToken));
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => '{base_url}/oauth2/authorize',
+            'access_token_url' => '{base_url}/oauth2/token',
+            'revoke_token_url' => '{base_url}/oauth2/revoke',
+            'infos_url' => '{base_url}/user',
+        ]);
+
+        $resolver->setRequired([
+            'base_url',
+        ]);
+
+        $normalizer = function (Options $options, $value) {
+            return str_replace('{base_url}', $options['base_url'], $value);
+        };
+
+        $resolver
+            ->setNormalizer('authorization_url', $normalizer)
+            ->setNormalizer('access_token_url', $normalizer)
+            ->setNormalizer('revoke_token_url', $normalizer)
+            ->setNormalizer('infos_url', $normalizer)
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FlickrResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FlickrResourceOwner.php
new file mode 100644
index 0000000..a415a28
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FlickrResourceOwner.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Dmitri Lakachauskis 
+ */
+final class FlickrResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'flickr';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'user_nsid',
+        'nickname' => 'username',
+        'realname' => 'fullname',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        $token = $this->getRequestToken($redirectUri, $extraParameters);
+
+        return $this->normalizeUrl($this->options['authorization_url'], [
+            'oauth_token' => $token['oauth_token'],
+            'perms' => $this->options['perms'],
+            'nojsoncallback' => 1,
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $response = $this->getUserResponse();
+        $response->setData($accessToken);
+        $response->setResourceOwner($this);
+        $response->setOAuthToken(new OAuthToken($accessToken));
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'http://www.flickr.com/services/oauth/authorize',
+            'request_token_url' => 'http://www.flickr.com/services/oauth/request_token',
+            'access_token_url' => 'http://www.flickr.com/services/oauth/access_token',
+
+            // Flickr don't use `infos_url`
+            'infos_url' => null,
+
+            'perms' => 'read',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FoursquareResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FoursquareResourceOwner.php
new file mode 100644
index 0000000..8934850
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/FoursquareResourceOwner.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\ResponseInterface;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class FoursquareResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'foursquare';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'response.user.id',
+        'firstname' => 'response.user.firstName',
+        'lastname' => 'response.user.lastName',
+        'nickname' => 'response.user.firstName',
+        'realname' => ['response.user.firstName', 'response.user.lastName'],
+        'email' => 'response.user.contact.email',
+        'profilepicture' => 'response.user.photo',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getResponseContent(ResponseInterface $rawResponse): array
+    {
+        $response = parent::getResponseContent($rawResponse);
+
+        // Foursquare use quite custom response structure in case of error
+        if (isset($response['meta']['errorType'])) {
+            // Prevent to mark deprecated calls as errors
+            if (200 === (int) $response['meta']['code']) {
+                $response['error'] = $response['meta']['errorType'];
+                // Try to add some details of error if available
+                if (isset($response['meta']['errorMessage'])) {
+                    $response['error'] .= ' '.$response['meta']['errorMessage'];
+                } elseif (isset($response['meta']['errorDetail'])) {
+                    $response['error'] .= ' '.$response['meta']['errorDetail'];
+                }
+            }
+
+            unset($response['meta']);
+        }
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        // Foursquare require to pass the 'v' ('version' = date in format 'YYYYMMDD') parameter when requesting API
+        $url = $this->normalizeUrl($url, [
+            'v' => $this->options['version'],
+        ]);
+
+        // Foursquare require to pass the OAuth token as 'oauth_token' instead of 'access_token'
+        $url = str_replace('access_token', 'oauth_token', $url);
+
+        return $this->httpRequest($url);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://foursquare.com/oauth2/authenticate',
+            'access_token_url' => 'https://foursquare.com/oauth2/access_token',
+            'infos_url' => 'https://api.foursquare.com/v2/users/self',
+
+            // @link https://developer.foursquare.com/overview/versioning
+            'version' => '20121206',
+
+            'use_bearer_authorization' => false,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GenericOAuth1ResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GenericOAuth1ResourceOwner.php
new file mode 100644
index 0000000..be3d39d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GenericOAuth1ResourceOwner.php
@@ -0,0 +1,240 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use HWI\Bundle\OAuthBundle\Security\Helper\NonceGenerator;
+use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
+use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
+use Symfony\Component\HttpFoundation\Request as HttpRequest;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+/**
+ * @author Francisco Facioni 
+ */
+abstract class GenericOAuth1ResourceOwner extends AbstractResourceOwner
+{
+    public const TYPE = null; // it must be null
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $parameters = array_merge([
+            'oauth_consumer_key' => $this->options['client_id'],
+            'oauth_timestamp' => time(),
+            'oauth_nonce' => NonceGenerator::generate(),
+            'oauth_version' => '1.0',
+            'oauth_signature_method' => $this->options['signature_method'],
+            'oauth_token' => $accessToken['oauth_token'],
+        ], $extraParameters);
+
+        $url = $this->options['infos_url'];
+        $parameters['oauth_signature'] = OAuthUtils::signRequest(
+            'GET',
+            $url,
+            $parameters,
+            $this->options['client_secret'],
+            $accessToken['oauth_token_secret'],
+            $this->options['signature_method']
+        );
+
+        $content = $this->doGetUserInformationRequest($url, $parameters);
+
+        $response = $this->getUserResponse();
+        $response->setData($content->getContent());
+        $response->setResourceOwner($this);
+        $response->setOAuthToken(new OAuthToken($accessToken));
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        $token = $this->getRequestToken($redirectUri, $extraParameters);
+
+        return $this->normalizeUrl($this->options['authorization_url'], ['oauth_token' => $token['oauth_token']]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = [])
+    {
+        OAuthErrorHandler::handleOAuthError($request);
+
+        try {
+            if (null === $requestToken = $this->storage->fetch($this, $request->query->get('oauth_token'))) {
+                throw new \RuntimeException('No request token found in the storage.');
+            }
+        } catch (\InvalidArgumentException $e) {
+            throw new AuthenticationException('Given token is not valid.');
+        }
+
+        $parameters = array_merge([
+            'oauth_consumer_key' => $this->options['client_id'],
+            'oauth_timestamp' => time(),
+            'oauth_nonce' => NonceGenerator::generate(),
+            'oauth_version' => '1.0',
+            'oauth_signature_method' => $this->options['signature_method'],
+            'oauth_token' => $requestToken['oauth_token'],
+            'oauth_verifier' => $request->query->get('oauth_verifier'),
+        ], $extraParameters);
+
+        $url = $this->options['access_token_url'];
+        $parameters['oauth_signature'] = OAuthUtils::signRequest(
+            'POST',
+            $url,
+            $parameters,
+            $this->options['client_secret'],
+            $requestToken['oauth_token_secret'],
+            $this->options['signature_method']
+        );
+
+        $response = $this->doGetTokenRequest($url, $parameters);
+        $response = $this->getResponseContent($response);
+
+        if (isset($response['oauth_problem'])) {
+            throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['oauth_problem']));
+        }
+
+        if (!isset($response['oauth_token'], $response['oauth_token_secret'])) {
+            throw new AuthenticationException('Not a valid request token.');
+        }
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handles(HttpRequest $request)
+    {
+        return $request->query->has('oauth_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isCsrfTokenValid($csrfToken)
+    {
+        // OAuth1.0a passes token with every call
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequestToken($redirectUri, array $extraParameters = [])
+    {
+        $timestamp = time();
+
+        $parameters = array_merge([
+            'oauth_consumer_key' => $this->options['client_id'],
+            'oauth_timestamp' => $timestamp,
+            'oauth_nonce' => NonceGenerator::generate(),
+            'oauth_version' => '1.0',
+            'oauth_callback' => $redirectUri,
+            'oauth_signature_method' => $this->options['signature_method'],
+        ], $extraParameters);
+
+        $url = $this->options['request_token_url'];
+        $parameters['oauth_signature'] = OAuthUtils::signRequest(
+            'POST',
+            $url,
+            $parameters,
+            $this->options['client_secret'],
+            '',
+            $this->options['signature_method']
+        );
+
+        $apiResponse = $this->httpRequest($url, null, [], 'POST', $parameters);
+
+        $response = $this->getResponseContent($apiResponse);
+
+        if (isset($response['oauth_problem'])) {
+            throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['oauth_problem']));
+        }
+
+        if (isset($response['oauth_callback_confirmed']) && 'true' !== $response['oauth_callback_confirmed']) {
+            throw new AuthenticationException('Defined OAuth callback was not confirmed.');
+        }
+
+        if (!isset($response['oauth_token'], $response['oauth_token_secret'])) {
+            throw new AuthenticationException('Not a valid request token.');
+        }
+
+        $response['timestamp'] = $timestamp;
+
+        $this->storage->save($this, $response);
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetTokenRequest($url, array $parameters = [])
+    {
+        return $this->httpRequest($url, null, [], 'POST', $parameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        return $this->httpRequest($url, null, [], null, $parameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function httpRequest($url, $content = null, array $headers = [], $method = null, array $parameters = [])
+    {
+        foreach ($parameters as $key => $value) {
+            $parameters[$key] = $key.'="'.rawurlencode($value ?: '').'"';
+        }
+
+        if (!$this->options['realm']) {
+            array_unshift($parameters, 'realm="'.rawurlencode($this->options['realm'] ?? '').'"');
+        }
+
+        $headers['Authorization'] = 'OAuth '.implode(', ', $parameters);
+
+        return parent::httpRequest($url, $content, $headers, $method);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setRequired([
+            'request_token_url',
+        ]);
+
+        $resolver->setDefaults([
+            'realm' => null,
+            'signature_method' => 'HMAC-SHA1',
+        ]);
+
+        $resolver->setAllowedValues('signature_method', ['HMAC-SHA1', 'RSA-SHA1', 'PLAINTEXT']);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GenericOAuth2ResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GenericOAuth2ResourceOwner.php
new file mode 100644
index 0000000..24bf846
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GenericOAuth2ResourceOwner.php
@@ -0,0 +1,279 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use HWI\Bundle\OAuthBundle\Security\Helper\NonceGenerator;
+use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\HttpFoundation\Request as HttpRequest;
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ */
+abstract class GenericOAuth2ResourceOwner extends AbstractResourceOwner
+{
+    public const TYPE = null; // it must be null
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        if ($this->options['use_bearer_authorization']) {
+            $content = $this->httpRequest(
+                $this->normalizeUrl($this->options['infos_url'], $extraParameters),
+                null,
+                ['Authorization' => 'Bearer '.$accessToken['access_token']]
+            );
+        } else {
+            $content = $this->doGetUserInformationRequest(
+                $this->normalizeUrl(
+                    $this->options['infos_url'],
+                    array_merge([$this->options['attr_name'] => $accessToken['access_token']], $extraParameters)
+                )
+            );
+        }
+
+        try {
+            $response = $this->getUserResponse();
+            $response->setData($content->toArray(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        if ($this->options['csrf']) {
+            $this->handleCsrfToken();
+        }
+
+        $parameters = array_merge([
+            'response_type' => 'code',
+            'client_id' => $this->options['client_id'],
+            'scope' => $this->options['scope'],
+            'state' => $this->state->encode(),
+            'redirect_uri' => $redirectUri,
+        ], $extraParameters);
+
+        return $this->normalizeUrl($this->options['authorization_url'], $parameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = [])
+    {
+        OAuthErrorHandler::handleOAuthError($request);
+
+        $parameters = array_merge([
+            'code' => $request->query->get('code'),
+            'grant_type' => 'authorization_code',
+            'redirect_uri' => $redirectUri,
+        ], $extraParameters);
+
+        $response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
+        $response = $this->getResponseContent($response);
+
+        $this->validateResponseContent($response);
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function refreshAccessToken($refreshToken, array $extraParameters = [])
+    {
+        $parameters = array_merge([
+            'refresh_token' => $refreshToken,
+            'grant_type' => 'refresh_token',
+        ], $extraParameters);
+
+        $response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
+        $response = $this->getResponseContent($response);
+
+        $this->validateResponseContent($response);
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        if (!isset($this->options['revoke_token_url'])) {
+            throw new AuthenticationException('OAuth error: "Method unsupported."');
+        }
+
+        $parameters = [
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->options['client_secret'],
+        ];
+
+        $response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url'], ['token' => $token]), $parameters, [], 'DELETE');
+
+        return 200 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handles(HttpRequest $request)
+    {
+        return $request->query->has('code');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isCsrfTokenValid($csrfToken)
+    {
+        // Mark token valid when validation is disabled
+        if (!$this->options['csrf']) {
+            return true;
+        }
+
+        if (null === $csrfToken) {
+            throw new AuthenticationException('Given CSRF token is not valid.');
+        }
+
+        try {
+            return null !== $this->storage->fetch($this, urldecode($csrfToken), 'csrf_state');
+        } catch (\InvalidArgumentException $e) {
+            throw new AuthenticationException('Given CSRF token is not valid.');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function shouldRefreshOnExpire()
+    {
+        return $this->options['refresh_on_expire'] ?? false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetTokenRequest($url, array $parameters = [])
+    {
+        $headers = [];
+        if ($this->options['use_authorization_to_get_token']) {
+            if ($this->options['client_secret']) {
+                $headers['Authorization'] = 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret']);
+            }
+        } else {
+            $parameters['client_id'] = $this->options['client_id'];
+            $parameters['client_secret'] = $this->options['client_secret'];
+        }
+
+        return $this->httpRequest($url, http_build_query($parameters, '', '&'), $headers);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        return $this->httpRequest($url, http_build_query($parameters, '', '&'));
+    }
+
+    /**
+     * @param mixed $response the 'parsed' content based on the response headers
+     *
+     * @throws AuthenticationException If an OAuth error occurred or no access token is found
+     */
+    protected function validateResponseContent($response)
+    {
+        if (isset($response['error_description'])) {
+            throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['error_description']));
+        }
+
+        if (isset($response['error'])) {
+            throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['error']['message'] ?? $response['error']));
+        }
+
+        if (!isset($response['access_token'])) {
+            throw new AuthenticationException('Not a valid access token.');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'attr_name' => 'access_token',
+            'use_commas_in_scope' => false,
+            'use_bearer_authorization' => true,
+            'use_authorization_to_get_token' => true,
+            'refresh_on_expire' => false,
+        ]);
+
+        $resolver->setDefined('revoke_token_url');
+        $resolver->setAllowedValues('refresh_on_expire', [true, false]);
+
+        // Unfortunately some resource owners break the spec by using commas instead
+        // of spaces to separate scopes (Disqus, Facebook, Github, Vkontante)
+        $scopeNormalizer = function (Options $options, $value) {
+            if (!$value) {
+                return null;
+            }
+
+            if (!$options['use_commas_in_scope']) {
+                return $value;
+            }
+
+            return str_replace(',', ' ', $value);
+        };
+
+        $resolver->setNormalizer('scope', $scopeNormalizer);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function httpRequest($url, $content = null, array $headers = [], $method = null)
+    {
+        $headers += ['Content-Type' => 'application/x-www-form-urlencoded'];
+
+        return parent::httpRequest($url, $content, $headers, $method);
+    }
+
+    private function handleCsrfToken(): void
+    {
+        if (null === $this->state->getCsrfToken()) {
+            $this->state->setCsrfToken(NonceGenerator::generate());
+        }
+
+        $this->storage->save($this, $this->state->getCsrfToken(), 'csrf_state');
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GeniusResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GeniusResourceOwner.php
new file mode 100644
index 0000000..351f54b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GeniusResourceOwner.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Krystian Marcisz 
+ */
+final class GeniusResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'genius';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'response.user.id',
+        'nickname' => 'response.user.name',
+        'realname' => 'response.user.name',
+        'email' => 'response.user.email',
+        'profilepicture' => 'response.user.avatar.medium.url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.genius.com/oauth/authorize',
+            'access_token_url' => 'https://api.genius.com/oauth/token',
+            'infos_url' => 'https://api.genius.com/account',
+            'use_bearer_authorization' => true,
+            'use_commas_in_scope' => true,
+            'scope' => 'me',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GitHubResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GitHubResourceOwner.php
new file mode 100644
index 0000000..83afe3d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GitHubResourceOwner.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ */
+final class GitHubResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'github';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'login',
+        'realname' => 'name',
+        'email' => 'email',
+        'profilepicture' => 'avatar_url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $response = parent::getUserInformation($accessToken, $extraParameters);
+
+        $responseData = $response->getData();
+        if (empty($responseData['email'])) {
+            // fetch the email addresses linked to the account
+            $content = $this->httpRequest(
+                $this->normalizeUrl($this->options['emails_url']), null, ['Authorization' => 'Bearer '.$accessToken['access_token']]
+            );
+
+            foreach ($this->getResponseContent($content) as $email) {
+                if (!empty($email['primary'])) {
+                    // we only need the primary email address
+                    $responseData['email'] = $email['email'];
+                    break;
+                }
+            }
+
+            $response->setData($responseData);
+        }
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        $response = $this->httpRequest(
+            sprintf($this->options['revoke_token_url'], $this->options['client_id']),
+            json_encode(['access_token' => $token]),
+            [
+                'Authorization' => 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret']),
+                'Content-Type' => 'application/json',
+            ],
+            'DELETE'
+        );
+
+        return 204 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://github.com/login/oauth/authorize',
+            'access_token_url' => 'https://github.com/login/oauth/access_token',
+            'revoke_token_url' => 'https://api.github.com/applications/%s/token',
+            'infos_url' => 'https://api.github.com/user',
+            'emails_url' => 'https://api.github.com/user/emails',
+
+            'use_commas_in_scope' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GitLabResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GitLabResourceOwner.php
new file mode 100644
index 0000000..0654da6
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GitLabResourceOwner.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Indra Gunawan 
+ */
+final class GitLabResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'gitlab';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'username',
+        'realname' => 'name',
+        'email' => 'email',
+        'profilepicture' => 'avatar_url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        $parameters = [
+            'token' => $token,
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->options['client_secret'],
+        ];
+
+        $response = $this->httpRequest(
+            $this->options['revoke_token_url'],
+            $parameters,
+            [],
+            'POST'
+        );
+
+        return 200 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://gitlab.com/oauth/authorize',
+            'access_token_url' => 'https://gitlab.com/oauth/token',
+            'revoke_token_url' => 'https://gitlab.com/oauth/revoke',
+            'infos_url' => 'https://gitlab.com/api/v4/user',
+
+            'scope' => 'read_user',
+            'use_commas_in_scope' => false,
+            'use_bearer_authorization' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GoogleResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GoogleResourceOwner.php
new file mode 100644
index 0000000..eec4d4e
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/GoogleResourceOwner.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ */
+final class GoogleResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'google';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'name',
+        'realname' => 'name',
+        'firstname' => 'given_name',
+        'lastname' => 'family_name',
+        'email' => 'email',
+        'profilepicture' => 'picture',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        $url = parent::getAuthorizationUrl($redirectUri, array_merge([
+            'access_type' => $this->options['access_type'],
+            'approval_prompt' => $this->options['approval_prompt'],
+            'request_visible_actions' => $this->options['request_visible_actions'],
+            'prompt' => $this->options['prompt'],
+        ], $extraParameters));
+
+        // This parameter have specific value (uses "&" as a separator of domains)
+        if (null !== $this->options['hd']) {
+            $url .= '&hd='.implode('&', array_map('trim', explode(',', $this->options['hd'])));
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        $response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url'], ['token' => $token]));
+
+        return 200 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://accounts.google.com/o/oauth2/auth',
+            'access_token_url' => 'https://accounts.google.com/o/oauth2/token',
+            'revoke_token_url' => 'https://accounts.google.com/o/oauth2/revoke',
+            'infos_url' => 'https://www.googleapis.com/oauth2/v1/userinfo',
+
+            'scope' => 'https://www.googleapis.com/auth/userinfo.profile',
+
+            'access_type' => null,
+            'approval_prompt' => null,
+            'display' => null,
+            // Identifying a particular hosted domain account to be accessed (for example, 'mycollege.edu')
+            'hd' => null,
+            'login_hint' => null,
+            'prompt' => null,
+            'request_visible_actions' => null,
+        ]);
+
+        $resolver
+            // @link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
+            ->setAllowedValues('access_type', ['online', 'offline', null])
+            // sometimes we need to force for approval prompt (e.g. when we lost refresh token)
+            ->setAllowedValues('approval_prompt', ['force', 'auto', null])
+            // @link https://developers.google.com/accounts/docs/OAuth2Login#authenticationuriparameters
+            ->setAllowedValues('display', ['page', 'popup', 'touch', 'wap', null])
+            ->setAllowedValues('login_hint', ['email address', 'sub', null])
+            ->setAllowedValues('prompt', ['consent', 'select_account', null])
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/HubicResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/HubicResourceOwner.php
new file mode 100644
index 0000000..bc337af
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/HubicResourceOwner.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Vincent Cassé 
+ */
+final class HubicResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'hubic';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'email',
+        'nickname' => 'email',
+        'firstname' => 'firstname',
+        'lastname' => 'lastname',
+        'realname' => 'firstname',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.hubic.com/oauth/auth/',
+            'access_token_url' => 'https://api.hubic.com/oauth/token/',
+            'infos_url' => 'https://api.hubic.com/1.0/account',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/InstagramResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/InstagramResourceOwner.php
new file mode 100644
index 0000000..6685016
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/InstagramResourceOwner.php
@@ -0,0 +1,82 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
+use Symfony\Component\HttpFoundation\Request as HttpRequest;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Jean-Christophe Cuvelier 
+ * @author Fabiano Roberto 
+ */
+final class InstagramResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'instagram';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'username',
+        'realname' => 'username',
+        'email' => 'id',
+        'accounttype' => 'account_type',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = [])
+    {
+        OAuthErrorHandler::handleOAuthError($request);
+
+        $parameters = array_merge([
+            'code' => $request->query->get('code'),
+            'grant_type' => 'authorization_code',
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->options['client_secret'],
+            'redirect_uri' => $redirectUri,
+        ], $extraParameters);
+
+        $response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
+        $response = $this->getResponseContent($response);
+
+        $this->validateResponseContent($response);
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        return $this->httpRequest($this->normalizeUrl($url, $parameters), null, [], 'GET');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.instagram.com/oauth/authorize',
+            'access_token_url' => 'https://api.instagram.com/oauth/access_token',
+            'infos_url' => 'https://api.instagram.com/v1/users/self',
+            'use_bearer_authorization' => false,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ItembaseResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ItembaseResourceOwner.php
new file mode 100644
index 0000000..c3fca6f
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ItembaseResourceOwner.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Thomas Bretzke 
+ */
+final class ItembaseResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'itembase';
+
+    public const ITEMBASE_AUTH_URL = 'https://accounts.itembase.com/oauth/v2/auth';
+    public const ITEMBASE_TOKEN_URL = 'https://accounts.itembase.com/oauth/v2/token';
+    public const ITEMBASE_INFOS_URL = 'https://users.itembase.com/v1/me';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'uuid',
+        'nickname' => 'username',
+        'firstname' => 'first_name',
+        'lastname' => 'last_name',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => self::ITEMBASE_AUTH_URL,
+            'access_token_url' => self::ITEMBASE_TOKEN_URL,
+            'infos_url' => self::ITEMBASE_INFOS_URL,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/JawboneResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/JawboneResourceOwner.php
new file mode 100644
index 0000000..1600b68
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/JawboneResourceOwner.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Dmitry Matora 
+ */
+final class JawboneResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'jawbone';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'xid' => 'data.id',
+        'firstname' => 'data.first',
+        'lastname' => 'data.last',
+        'profilepicture' => 'data.image',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($accessToken)
+    {
+        $response = $this->getInformation($accessToken, 'PartnerAppMembership');
+
+        return 200 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getInformation($accessToken, $type, array $extraParameters = [])
+    {
+        $url = $this->normalizeUrl($this->options['infos_url'].'/'.$type, $extraParameters);
+
+        $headers = [
+            'Authorization' => 'Bearer '.$accessToken['access_token'],
+            'Accept' => 'application/json',
+        ];
+
+        return $this->httpRequest($url, null, $headers);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://jawbone.com/auth/oauth2/auth',
+            'access_token_url' => 'https://jawbone.com/auth/oauth2/token',
+            'infos_url' => 'https://jawbone.com/nudge/api/v.1.0/users/@me',
+            'use_commas_in_scope' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/JiraResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/JiraResourceOwner.php
new file mode 100644
index 0000000..efa4b52
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/JiraResourceOwner.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use HWI\Bundle\OAuthBundle\Security\Helper\NonceGenerator;
+use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Benjamin Eberlei 
+ */
+final class JiraResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'jira';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'name',
+        'nickname' => 'name',
+        'realname' => 'displayName',
+        'email' => 'emailAddress',
+        'profilepicture' => 'avatarUrls.48x48',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $parameters = array_merge([
+            'oauth_consumer_key' => $this->options['client_id'],
+            'oauth_timestamp' => time(),
+            'oauth_nonce' => NonceGenerator::generate(),
+            'oauth_version' => '1.0',
+            'oauth_signature_method' => $this->options['signature_method'],
+            'oauth_token' => $accessToken['oauth_token'],
+        ], $extraParameters);
+
+        $parameters['oauth_signature'] = OAuthUtils::signRequest(
+            'GET',
+            $this->options['infos_session_url'],
+            $parameters,
+            $this->options['client_secret'],
+            $accessToken['oauth_token_secret'],
+            $this->options['signature_method']
+        );
+
+        $content = $this->getResponseContent($this->doGetUserInformationRequest($this->options['infos_session_url'], $parameters));
+        $url = $this->normalizeUrl($this->options['infos_url'], ['username' => $content['name']]);
+
+        // Regenerate nonce & signature as URL was changed
+        $parameters['oauth_nonce'] = NonceGenerator::generate();
+        $parameters['oauth_signature'] = OAuthUtils::signRequest(
+            'GET',
+            $url,
+            $parameters,
+            $this->options['client_secret'],
+            $accessToken['oauth_token_secret'],
+            $this->options['signature_method']
+        );
+
+        try {
+            $content = $this->doGetUserInformationRequest($url, $parameters);
+
+            $response = $this->getUserResponse();
+            $response->setData($content->getContent(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => '{base_url}/plugins/servlet/oauth/authorize',
+            'request_token_url' => '{base_url}/plugins/servlet/oauth/request-token',
+            'access_token_url' => '{base_url}/plugins/servlet/oauth/access-token',
+
+            // JIRA API requires to first know the username to be able to ask for user details
+            'infos_session_url' => '{base_url}/rest/auth/1/session',
+            'infos_url' => '{base_url}/rest/api/2/user',
+
+            'signature_method' => 'RSA-SHA1',
+        ]);
+
+        $resolver->setRequired([
+            'base_url',
+        ]);
+
+        $normalizer = function (Options $options, $value) {
+            return str_replace('{base_url}', $options['base_url'], $value);
+        };
+
+        $resolver
+            ->setNormalizer('authorization_url', $normalizer)
+            ->setNormalizer('request_token_url', $normalizer)
+            ->setNormalizer('access_token_url', $normalizer)
+            ->setNormalizer('infos_session_url', $normalizer)
+            ->setNormalizer('infos_url', $normalizer)
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/KeycloakResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/KeycloakResourceOwner.php
new file mode 100644
index 0000000..148dbb5
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/KeycloakResourceOwner.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Andrea Quintino 
+ */
+final class KeycloakResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'keycloak';
+
+    protected array $paths = [
+        'identifier' => 'sub',
+        'nickname' => 'preferred_username',
+        'firstname' => 'given_name',
+        'lastname' => 'family_name',
+        'realname' => 'name',
+        'email' => 'email',
+        'profilepicture' => 'picture',
+    ];
+
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        return parent::getAuthorizationUrl($redirectUri, array_merge([
+          'approval_prompt' => $this->getOption('approval_prompt'),
+          'kc_idp_hint' => $this->getOption('idp_hint'),
+        ], $extraParameters));
+    }
+
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+          'protocol' => 'openid-connect',
+          'scope' => 'openid email',
+          'response_type' => 'code',
+          'approval_prompt' => 'auto',
+          'authorization_url' => '{keycloak_url}/auth',
+          'access_token_url' => '{keycloak_url}/token',
+          'infos_url' => '{keycloak_url}/userinfo',
+          'idp_hint' => null,
+        ]);
+
+        $resolver->setRequired([
+          'realm',
+          'base_url',
+        ]);
+
+        $normalizer = function (Options $options, $value) {
+            return str_replace(
+                '{keycloak_url}',
+                $options['base_url'].'/realms/'.$options['realm'].'/protocol/'.$options['protocol'],
+                $value
+            );
+        };
+
+        $resolver->setNormalizer('authorization_url', $normalizer);
+        $resolver->setNormalizer('access_token_url', $normalizer);
+        $resolver->setNormalizer('infos_url', $normalizer);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/LinkedinResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/LinkedinResourceOwner.php
new file mode 100644
index 0000000..0e1318c
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/LinkedinResourceOwner.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Response\LinkedinUserResponse;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Francisco Facioni 
+ * @author Joseph Bielawski 
+ */
+final class LinkedinResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'linkedin';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'emailAddress',
+        'firstname' => 'firstName',
+        'lastname' => 'lastName',
+        'email' => 'emailAddress',
+        'profilepicture' => 'profilePicture',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $response = parent::getUserInformation($accessToken, $extraParameters);
+
+        $responseData = $response->getData();
+        // The user info returned by /me doesn't contain the email so we make an extra request to fetch it
+        $content = $this->httpRequest(
+            $this->normalizeUrl($this->options['email_url'], $extraParameters),
+            null,
+            ['Authorization' => 'Bearer '.$accessToken['access_token']]
+        );
+
+        $emailResponse = $this->getResponseContent($content);
+        if (isset($emailResponse['elements']) && \count($emailResponse['elements']) > 0) {
+            $responseData['emailAddress'] = $emailResponse['elements'][0]['handle~']['emailAddress'];
+        }
+        // errors not handled because I don't see any relevant thing to do with them
+
+        $response->setData($responseData);
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetTokenRequest($url, array $parameters = [])
+    {
+        $parameters['client_id'] = $this->options['client_id'];
+        $parameters['client_secret'] = $this->options['client_secret'];
+
+        return $this->httpRequest($this->normalizeUrl($url, $parameters), null, [], 'POST');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function httpRequest($url, $content = null, array $headers = [], $method = null)
+    {
+        // LinkedIn v2 API is supposed to require Content-Type: application/json but it works without
+        // and request to get the access token doesn't seems to work with Content-Type: application/json
+        // so we don't put any Content-Type header.
+        // Skip the Content-Type header in GenericOAuth2ResourceOwner::httpRequest
+        //
+        // LinkedIn API requires to always set Content-Length in POST requests
+        if ('POST' === $method) {
+            $headers['Content-Length'] = \is_string($content) ? (string) \strlen($content) : '0';
+        }
+
+        return AbstractResourceOwner::httpRequest($url, $content, $headers, $method);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'scope' => 'r_liteprofile r_emailaddress',
+            'authorization_url' => 'https://www.linkedin.com/oauth/v2/authorization',
+            'access_token_url' => 'https://www.linkedin.com/oauth/v2/accessToken',
+            'infos_url' => 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))',
+            'email_url' => 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))',
+
+            'user_response_class' => LinkedinUserResponse::class,
+
+            'csrf' => true,
+
+            'use_bearer_authorization' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/MailRuResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/MailRuResourceOwner.php
new file mode 100644
index 0000000..c2d1fa6
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/MailRuResourceOwner.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Gaponov Igor 
+ */
+final class MailRuResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'mailru';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'uid',
+        'nickname' => 'nick',
+        'realname' => 'nick',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $params = [
+            'app_id' => $this->options['client_id'],
+            'method' => 'users.getInfo',
+            'secure' => '1',
+            'session_key' => $accessToken['access_token'],
+        ];
+
+        $params['sig'] = md5(vsprintf('app_id=%smethod=%ssecure=%ssession_key=%s', $params).$this->options['client_secret']);
+
+        $url = $this->normalizeUrl($this->options['infos_url'], $params);
+
+        try {
+            $content = $this->doGetUserInformationRequest($url)->toArray(false);
+            if (isset($content[0])) {
+                $content = (array) $content[0];
+            }
+
+            $response = $this->getUserResponse();
+            $response->setData($content);
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://connect.mail.ru/oauth/authorize',
+            'access_token_url' => 'https://connect.mail.ru/oauth/token',
+            'infos_url' => 'http://www.appsmail.ru/platform/api',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OAuth1ResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OAuth1ResourceOwner.php
new file mode 100644
index 0000000..698b100
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OAuth1ResourceOwner.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+final class OAuth1ResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'oauth1';
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OAuth2ResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OAuth2ResourceOwner.php
new file mode 100644
index 0000000..7d96edd
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OAuth2ResourceOwner.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+final class OAuth2ResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'oauth2';
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OdnoklassnikiResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OdnoklassnikiResourceOwner.php
new file mode 100644
index 0000000..ad5b897
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/OdnoklassnikiResourceOwner.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Sergey Polischook 
+ */
+final class OdnoklassnikiResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'odnoklassniki';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'uid',
+        'nickname' => 'username',
+        'realname' => 'name',
+        'email' => 'email',
+        'firstname' => 'first_name',
+        'lastname' => 'last_name',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $parameters = [
+            'access_token' => $accessToken['access_token'],
+            'application_key' => $this->options['application_key'],
+        ];
+
+        if ($this->options['fields']) {
+            $parameters['fields'] = $this->options['fields'];
+            $parameters['sig'] = md5(sprintf(
+                'application_key=%sfields=%smethod=users.getCurrentUser%s',
+                $this->options['application_key'],
+                $this->options['fields'],
+                md5($accessToken['access_token'].$this->options['client_secret'])
+            ));
+        } else {
+            $parameters['sig'] = md5(sprintf(
+                'application_key=%smethod=users.getCurrentUser%s',
+                $this->options['application_key'],
+                md5($accessToken['access_token'].$this->options['client_secret'])
+            ));
+        }
+
+        $url = $this->normalizeUrl($this->options['infos_url'], $parameters);
+
+        try {
+            $content = $this->doGetUserInformationRequest($url)->toArray(false);
+
+            $response = $this->getUserResponse();
+            $response->setData($content);
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://connect.ok.ru/oauth/authorize',
+            'access_token_url' => 'https://api.ok.ru/oauth/token.do',
+            'infos_url' => 'https://api.ok.ru/fb.do?method=users.getCurrentUser',
+
+            'application_key' => null,
+            'fields' => null,
+        ]);
+
+        $fieldsNormalizer = function (Options $options, $value) {
+            if (!$value) {
+                return null;
+            }
+
+            return \is_array($value) ? implode(',', $value) : $value;
+        };
+
+        $resolver->setNormalizer('fields', $fieldsNormalizer);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Office365ResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Office365ResourceOwner.php
new file mode 100644
index 0000000..eef6ebc
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/Office365ResourceOwner.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+final class Office365ResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'office365';
+
+    protected array $paths = [
+        'identifier' => 'id',
+        'email' => 'mail',
+        'realname' => 'displayName',
+        'firstname' => 'givenName',
+        'lastname' => 'surname',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
+    {
+        $extraParameters = array_merge([
+            'resource' => 'https://graph.microsoft.com',
+        ], $extraParameters);
+
+        return parent::getAccessToken($request, $redirectUri, $extraParameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://login.microsoftonline.com/common/oauth2/authorize',
+            'access_token_url' => 'https://login.microsoftonline.com/common/oauth2/token',
+            'infos_url' => 'https://graph.microsoft.com/v1.0/me',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/PassageResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/PassageResourceOwner.php
new file mode 100644
index 0000000..7ed8a44
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/PassageResourceOwner.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+final class PassageResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'passage';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'sub',
+        'email' => 'email',
+        'phone_number' => 'phone_number',
+        'email_verified' => 'email_verified',
+        'phone_number_verified' => 'phone_number_verified',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        if (!isset($this->options['revoke_token_url'])) {
+            throw new AuthenticationException('OAuth error: "Method unsupported."');
+        }
+
+        $parameters = [
+            'client_id' => $this->options['client_id'],
+            'client_secret' => $this->options['client_secret'],
+            'token' => $token,
+        ];
+
+        $response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url']), $parameters, [], 'POST');
+
+        return 200 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://{sub_domain}.withpassage.com/authorize',
+            'access_token_url' => 'https://{sub_domain}.withpassage.com/token',
+            'revoke_token_url' => 'https://{sub_domain}.withpassage.com/revoke',
+            'infos_url' => 'https://{sub_domain}.withpassage.com/userinfo',
+
+            'use_commas_in_scope' => false,
+            'scope' => 'openid email',
+        ]);
+
+        $resolver->setRequired([
+            'sub_domain',
+        ]);
+
+        $normalizer = function (Options $options, $value) {
+            return str_replace('{sub_domain}', $options['sub_domain'], $value);
+        };
+
+        $resolver
+            ->setNormalizer('authorization_url', $normalizer)
+            ->setNormalizer('access_token_url', $normalizer)
+            ->setNormalizer('revoke_token_url', $normalizer)
+            ->setNormalizer('infos_url', $normalizer)
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/PaypalResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/PaypalResourceOwner.php
new file mode 100644
index 0000000..786d848
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/PaypalResourceOwner.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Berny Cantos 
+ */
+final class PaypalResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'paypal';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'user_id',
+        'nickname' => 'email',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'sandbox' => false,
+            'scope' => 'openid email',
+            'authorization_url' => 'https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize',
+            'access_token_url' => 'https://api.paypal.com/v1/identity/openidconnect/tokenservice',
+            'infos_url' => 'https://api.paypal.com/v1/identity/openidconnect/userinfo/?schema=openid',
+        ]);
+
+        $resolver->addAllowedTypes('sandbox', 'bool');
+
+        $sandboxTransformation = function (Options $options, $value) {
+            if (!$options['sandbox']) {
+                return $value;
+            }
+
+            return preg_replace('~\.paypal\.~', '.sandbox.paypal.', $value, 1);
+        };
+
+        $resolver
+            ->setNormalizer('authorization_url', $sandboxTransformation)
+            ->setNormalizer('access_token_url', $sandboxTransformation)
+            ->setNormalizer('infos_url', $sandboxTransformation)
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/QQResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/QQResourceOwner.php
new file mode 100644
index 0000000..3278e5c
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/QQResourceOwner.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Contracts\HttpClient\ResponseInterface;
+
+final class QQResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'qq';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'openid',
+        'nickname' => 'nickname',
+        'realname' => 'nickname',
+        'profilepicture' => 'figureurl_qq_1',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getResponseContent(ResponseInterface $rawResponse): array
+    {
+        $content = $rawResponse->getContent(false);
+        if (preg_match('/^callback\((.+)\);$/', $content, $matches)) {
+            return json_decode(trim($matches[1]), true) ?: [];
+        }
+
+        return parent::getResponseContent($rawResponse);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(?array $accessToken = null, array $extraParameters = [])
+    {
+        $openid = $extraParameters['openid'] ?? $this->requestUserIdentifier($accessToken);
+
+        $url = $this->normalizeUrl($this->options['infos_url'], [
+            'oauth_consumer_key' => $this->options['client_id'],
+            'access_token' => $accessToken['access_token'],
+            'openid' => $openid,
+            'format' => 'json',
+        ]);
+
+        $response = $this->doGetUserInformationRequest($url);
+        $content = $this->getResponseContent($response);
+
+        // Custom errors:
+        if (isset($content['ret']) && 0 === $content['ret']) {
+            $content['openid'] = $openid;
+        } else {
+            throw new AuthenticationException(sprintf('OAuth error: %s', isset($content['ret']) ? $content['msg'] : 'invalid response'));
+        }
+
+        $response = $this->getUserResponse();
+        $response->setData($content);
+        $response->setResourceOwner($this);
+        $response->setOAuthToken(new OAuthToken($accessToken));
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://graph.qq.com/oauth2.0/authorize?format=json',
+            'access_token_url' => 'https://graph.qq.com/oauth2.0/token',
+            'infos_url' => 'https://graph.qq.com/user/get_user_info',
+            'me_url' => 'https://graph.qq.com/oauth2.0/me',
+        ]);
+    }
+
+    private function requestUserIdentifier(?array $accessToken = null)
+    {
+        $url = $this->normalizeUrl($this->options['me_url'], [
+            'access_token' => $accessToken['access_token'],
+        ]);
+
+        $response = $this->httpRequest($url);
+        $content = $this->getResponseContent($response);
+
+        if (!isset($content['openid'])) {
+            throw new AuthenticationException();
+        }
+
+        return $content['openid'];
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/RedditResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/RedditResourceOwner.php
new file mode 100644
index 0000000..6459a36
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/RedditResourceOwner.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Martin Aarhof 
+ */
+final class RedditResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'reddit';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'name',
+        'realname' => null,
+        'email' => null,
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetTokenRequest($url, array $parameters = [])
+    {
+        return $this->httpRequest(
+            $url,
+            http_build_query($parameters, '', '&'),
+            [
+                'Authorization' => 'Basic '.base64_encode(sprintf('%s:%s', $this->options['client_id'], $this->options['client_secret'])),
+            ],
+            'POST'
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://ssl.reddit.com/api/v1/authorize',
+            'access_token_url' => 'https://ssl.reddit.com/api/v1/access_token',
+            'infos_url' => 'https://oauth.reddit.com/api/v1/me.json',
+
+            'use_bearer_authorization' => true,
+            'use_commas_in_scope' => true,
+            'csrf' => true,
+            'scope' => 'identity',
+
+            'duration' => 'permanent',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/RunKeeperResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/RunKeeperResourceOwner.php
new file mode 100644
index 0000000..a050eda
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/RunKeeperResourceOwner.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Artem Genvald 
+ */
+final class RunKeeperResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'runkeeper';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'realname' => 'name',
+        'profilepicture' => 'medium_picture',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserResource($accessToken)
+    {
+        $response = $this->httpRequest(
+            $this->normalizeUrl($this->options['user_resource_url']),
+            null,
+            ['Authorization' => 'Bearer '.$accessToken]
+        );
+
+        return $this->getResponseContent($response);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://runkeeper.com/apps/authorize',
+            'access_token_url' => 'https://runkeeper.com/apps/token',
+            'infos_url' => 'https://api.runkeeper.com/profile',
+            'user_resource_url' => 'https://api.runkeeper.com/user',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SalesforceResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SalesforceResourceOwner.php
new file mode 100644
index 0000000..6575ef8
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SalesforceResourceOwner.php
@@ -0,0 +1,96 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Tyler Pugh 
+ */
+final class SalesforceResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'salesforce';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'user_id',
+        'nickname' => 'nick_name',
+        'realname' => 'nick_name',
+        'email' => 'email',
+        'profilepicture' => 'photos.picture',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        // SalesForce returns the infos_url in the OAuth Response Token
+        $this->options['infos_url'] = $accessToken['id'];
+
+        return parent::getUserInformation($accessToken, $extraParameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        // Salesforce requires format parameter in order for API to return json response
+        $url = $this->normalizeUrl($url, [
+            'format' => $this->options['format'],
+        ]);
+
+        // Salesforce require to pass the OAuth token as 'oauth_token' instead of 'access_token'
+        $url = str_replace('access_token', 'oauth_token', $url);
+
+        return $this->httpRequest($url);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'sandbox' => false,
+            'authorization_url' => 'https://login.salesforce.com/services/oauth2/authorize',
+            'access_token_url' => 'https://login.salesforce.com/services/oauth2/token',
+
+            // @see SalesforceResourceOwner::getUserInformation()
+            'infos_url' => null,
+
+            // @see SalesforceResourceOwner::doGetUserInformationRequest()
+            'format' => 'json',
+        ]);
+
+        $sandboxTransformation = function (Options $options, $value) {
+            if (!$options['sandbox']) {
+                return $value;
+            }
+
+            return preg_replace('~login\.~', 'test.', $value, 1);
+        };
+
+        $resolver
+            ->setNormalizer('authorization_url', $sandboxTransformation)
+            ->setNormalizer('access_token_url', $sandboxTransformation)
+        ;
+
+        $resolver->addAllowedTypes('sandbox', 'bool');
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SensioConnectResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SensioConnectResourceOwner.php
new file mode 100644
index 0000000..835d5d4
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SensioConnectResourceOwner.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\OAuth\Response\SensioConnectUserResponse;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class SensioConnectResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'sensio_connect';
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $content = $this->doGetUserInformationRequest(
+            $this->normalizeUrl(
+                $this->options['infos_url'],
+                array_merge([$this->options['attr_name'] => $accessToken['access_token']], $extraParameters)
+            )
+        );
+
+        try {
+            $response = $this->getUserResponse();
+            $response->setData($content->getContent(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        return $this->httpRequest($url, null, ['Accept' => 'application/vnd.com.sensiolabs.connect+xml']);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://connect.symfony.com/oauth/authorize',
+            'access_token_url' => 'https://connect.symfony.com/oauth/access_token',
+            'infos_url' => 'https://connect.symfony.com/api',
+
+            'user_response_class' => SensioConnectUserResponse::class,
+
+            'response_type' => 'code',
+
+            'use_bearer_authorization' => false,
+            'csrf' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SinaWeiboResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SinaWeiboResourceOwner.php
new file mode 100644
index 0000000..8739dc9
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SinaWeiboResourceOwner.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+final class SinaWeiboResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'sina_weibo';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'screen_name',
+        'realname' => 'screen_name',
+        'profilepicture' => 'profile_image_url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(?array $accessToken = null, array $extraParameters = [])
+    {
+        $url = $this->normalizeUrl($this->options['infos_url'], [
+            'access_token' => $accessToken['access_token'],
+            'uid' => $accessToken['uid'],
+        ]);
+
+        try {
+            $content = $this->doGetUserInformationRequest($url);
+
+            $response = $this->getUserResponse();
+            $response->setData($content->toArray(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.weibo.com/oauth2/authorize',
+            'access_token_url' => 'https://api.weibo.com/oauth2/access_token',
+            'infos_url' => 'https://api.weibo.com/2/users/show.json',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SlackResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SlackResourceOwner.php
new file mode 100644
index 0000000..aa6ecac
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SlackResourceOwner.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Baptiste Clavié 
+ */
+final class SlackResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'slack';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'user.id',
+        'nickname' => 'user.name',
+        'email' => 'user.email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://slack.com/oauth/authorize',
+            'access_token_url' => 'https://slack.com/api/oauth.access',
+            'infos_url' => 'https://slack.com/api/users.identity',
+
+            'scope' => 'identify',
+
+            'use_bearer_authorization' => false,
+            'attr_name' => 'token',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SoundcloudResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SoundcloudResourceOwner.php
new file mode 100644
index 0000000..42f53a5
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SoundcloudResourceOwner.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Anthony AHMED 
+ */
+final class SoundcloudResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'soundcloud';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'username',
+        'realname' => 'full_name',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'access_token_url' => 'https://api.soundcloud.com/oauth2/token',
+            'attr_name' => 'oauth_token',
+            'authorization_url' => 'https://soundcloud.com/connect',
+            'infos_url' => 'https://api.soundcloud.com/me.json',
+            'scope' => 'non-expiring',
+            'use_bearer_authorization' => false,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SpotifyResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SpotifyResourceOwner.php
new file mode 100644
index 0000000..8118633
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/SpotifyResourceOwner.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Janne Savolainen 
+ */
+final class SpotifyResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'spotify';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'id',
+        'realname' => 'display_name',
+        'email' => 'email',
+        'profilepicture' => 'images.0.url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(?array $accessToken = null, array $extraParameters = [])
+    {
+        $url = $this->normalizeUrl($this->options['infos_url'], [
+            'access_token' => $accessToken['access_token'],
+        ]);
+
+        try {
+            $content = $this->doGetUserInformationRequest($url);
+
+            $response = $this->getUserResponse();
+            $response->setData($content->toArray(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://accounts.spotify.com/authorize',
+            'access_token_url' => 'https://accounts.spotify.com/api/token',
+            'infos_url' => 'https://api.spotify.com/v1/me',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StackExchangeResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StackExchangeResourceOwner.php
new file mode 100644
index 0000000..2664123
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StackExchangeResourceOwner.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class StackExchangeResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'stack_exchange';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'items.0.user_id',
+        'nickname' => 'items.0.display_name',
+        'realname' => 'items.0.display_name',
+        'profilepicture' => 'items.0.profile_image',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $parameters = array_merge(
+            [$this->options['attr_name'] => $accessToken['access_token']],
+            ['site' => $this->options['site'], 'key' => $this->options['key']],
+            $extraParameters
+        );
+
+        try {
+            $content = $this->doGetUserInformationRequest($this->normalizeUrl($this->options['infos_url'], $parameters));
+
+            $response = $this->getUserResponse();
+            $response->setData($content->toArray(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setRequired([
+            'key',
+        ]);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://stackexchange.com/oauth',
+            'access_token_url' => 'https://stackexchange.com/oauth/access_token',
+            'infos_url' => 'https://api.stackexchange.com/2.0/me',
+
+            'scope' => 'no_expiry',
+            'site' => 'stackoverflow',
+            'use_bearer_authorization' => false,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StereomoodResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StereomoodResourceOwner.php
new file mode 100644
index 0000000..3935962
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StereomoodResourceOwner.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Vincenzo Di Biaggio 
+ */
+final class StereomoodResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'stereomood';
+
+    protected array $paths = [
+        'identifier' => 'oauth_token',
+        'nickname' => 'oauth_token',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $response = $this->getUserResponse();
+        $response->setData($accessToken);
+        $response->setResourceOwner($this);
+        $response->setOAuthToken(new OAuthToken($accessToken));
+
+        return $response;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'http://www.stereomood.com/api/oauth/authenticate',
+            'request_token_url' => 'http://www.stereomood.com/api/oauth/request_token',
+            'access_token_url' => 'http://www.stereomood.com/api/oauth/access_token',
+
+            // Stereomood don't use `infos_url`
+            'infos_url' => null,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StravaResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StravaResourceOwner.php
new file mode 100644
index 0000000..861d5ec
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/StravaResourceOwner.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Artem Genvald 
+ */
+final class StravaResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'strava';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'realname' => ['firstname', 'lastname'],
+        'profilepicture' => 'profile_medium',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://www.strava.com/oauth/authorize',
+            'access_token_url' => 'https://www.strava.com/oauth/token',
+            'infos_url' => 'https://www.strava.com/api/v3/athlete',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TelegramResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TelegramResourceOwner.php
new file mode 100644
index 0000000..8404852
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TelegramResourceOwner.php
@@ -0,0 +1,116 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\Exception\LazyResponseException;
+
+/**
+ * @author zorn-v
+ */
+final class TelegramResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'telegram';
+
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'username',
+        'realname' => 'first_name',
+        'firstname' => 'first_name',
+        'lastname' => 'last_name',
+        'profilepicture' => 'photo_url',
+    ];
+
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        [$botId] = explode(':', $this->options['client_secret']);
+        $parameters = array_merge([
+            'bot_id' => $botId,
+            'origin' => $redirectUri,
+            'return_to' => $redirectUri,
+        ], $extraParameters);
+
+        return $this->normalizeUrl($this->options['authorization_url'], $parameters);
+    }
+
+    public function handles(Request $request)
+    {
+        if (!$request->query->has('code')) {
+            $js = '';
+            throw new LazyResponseException(new Response($js));
+        }
+
+        return true;
+    }
+
+    public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
+    {
+        $token = $request->query->get('code', '');
+        $token = str_pad(strtr($token, '-_', '+/'), \strlen($token) % 4, '=', \STR_PAD_RIGHT);
+        $authData = json_decode(base64_decode($token), true);
+        if (empty($authData['hash'])) {
+            throw new AuthenticationException('Invalid Telegram auth data');
+        }
+        if (empty($authData['auth_date']) || (time() - $authData['auth_date']) > 300) {
+            throw new AuthenticationException('Telegram auth data expired');
+        }
+        $botToken = $this->options['client_secret'];
+        $checkHash = $authData['hash'];
+        unset($authData['hash']);
+        ksort($authData);
+        $dataCheckStr = '';
+        foreach ($authData as $k => $v) {
+            $dataCheckStr .= sprintf("\n%s=%s", $k, $v);
+        }
+        $dataCheckStr = substr($dataCheckStr, 1);
+        $secretKey = hash('sha256', $botToken, true);
+        $hash = hash_hmac('sha256', $dataCheckStr, $secretKey);
+        if ($hash !== $checkHash) {
+            throw new AuthenticationException('Telegram auth data check failed');
+        }
+
+        return ['access_token' => $token];
+    }
+
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $data = base64_decode($accessToken['access_token']);
+        $response = $this->getUserResponse();
+        $response->setData($data);
+        $response->setResourceOwner($this);
+        $response->setOAuthToken(new OAuthToken($accessToken));
+
+        return $response;
+    }
+
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        $resolver->setRequired([
+            'client_id',
+            'client_secret',
+            'authorization_url',
+        ]);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://oauth.telegram.org/auth',
+            'auth_with_one_url' => true,
+            'state' => null,
+            'csrf' => false,
+            'user_response_class' => PathUserResponse::class,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ThirtySevenSignalsResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ThirtySevenSignalsResourceOwner.php
new file mode 100644
index 0000000..c891242
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ThirtySevenSignalsResourceOwner.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Richard van den Brand 
+ */
+final class ThirtySevenSignalsResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = '37signals';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'identity.id',
+        'nickname' => 'identity.email_address',
+        'firstname' => 'identity.first_name',
+        'lastname' => 'identity.last_name',
+        'realname' => ['identity.last_name', 'identity.first_name'],
+        'email' => 'identity.email_address',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        return parent::getAuthorizationUrl($redirectUri, array_merge(['type' => 'web_server'], $extraParameters));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
+    {
+        return parent::getAccessToken($request, $redirectUri, array_merge(['type' => 'web_server'], $extraParameters));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://launchpad.37signals.com/authorization/new',
+            'access_token_url' => 'https://launchpad.37signals.com/authorization/token',
+            'infos_url' => 'https://launchpad.37signals.com/authorization.json',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ToshlResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ToshlResourceOwner.php
new file mode 100644
index 0000000..e28fbc5
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/ToshlResourceOwner.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Davide Bellettini 
+ */
+final class ToshlResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'toshl';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'email',
+        'firstname' => 'first_name',
+        'lastname' => 'last_name',
+        'realname' => ['first_name', 'last_name'],
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function revokeToken($token)
+    {
+        $response = $this->httpRequest(
+            $this->options['revoke_token_url'],
+            null,
+            ['Authorization' => 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret'])],
+            'DELETE'
+        );
+
+        return 204 === $response->getStatusCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://toshl.com/oauth2/authorize',
+            'access_token_url' => 'https://toshl.com/oauth2/token',
+            'revoke_token_url' => 'https://toshl.com/oauth2/revoke',
+            'infos_url' => 'https://api.toshl.com/me',
+            'csrf' => true,
+            'use_commas_in_scope' => true,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TraktResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TraktResourceOwner.php
new file mode 100644
index 0000000..9486fce
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TraktResourceOwner.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Julien DIDIER 
+ */
+final class TraktResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'trakt';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'username',
+        'nickname' => 'username',
+        'realname' => 'name',
+        'profilepicture' => 'images.avatar.full',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $content = $this->httpRequest($this->normalizeUrl($this->options['infos_url']), null, [
+            'Authorization' => 'Bearer '.$accessToken['access_token'],
+            'Content-Type' => 'application/json',
+            'trakt-api-key' => $this->options['client_id'],
+            'trakt-api-version' => 2,
+        ]);
+
+        try {
+            $response = $this->getUserResponse();
+            $response->setData($content->toArray(false));
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api-v2launch.trakt.tv/oauth/authorize',
+            'access_token_url' => 'https://api-v2launch.trakt.tv/oauth/token',
+            'infos_url' => 'https://api-v2launch.trakt.tv/users/me?extended=images',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TrelloResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TrelloResourceOwner.php
new file mode 100644
index 0000000..82acfa1
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TrelloResourceOwner.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class TrelloResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'trello';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'username',
+        'realname' => 'fullName',
+        'email' => 'email',
+        'profilepicture' => 'avatarSource',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        $token = $this->getRequestToken($redirectUri, $extraParameters);
+
+        return $this->normalizeUrl($this->options['authorization_url'], [
+            'scope' => $this->options['scopes'],
+            'name' => $this->options['application'],
+            'expiration' => $this->options['expiration'],
+            'oauth_token' => $token['oauth_token'],
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://trello.com/1/OAuthAuthorizeToken',
+            'request_token_url' => 'https://trello.com/1/OAuthGetRequestToken',
+            'access_token_url' => 'https://trello.com/1/OAuthGetAccessToken',
+            'infos_url' => 'https://api.trello.com/1/members/me?fields=username,fullName,avatarSource,email',
+            'realm' => 'trello.com',
+            'application' => null,
+            'scopes' => 'read',
+            'expiration' => null,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TwitchResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TwitchResourceOwner.php
new file mode 100644
index 0000000..77dd34a
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TwitchResourceOwner.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Simon Bräuer 
+ */
+final class TwitchResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'twitch';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'data.0.id',
+        'nickname' => 'data.0.login',
+        'realname' => 'data.0.display_name',
+        'email' => 'data.0.email', // Require scope "user:read:email"
+        'profilepicture' => 'data.0.profile_image_url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function httpRequest($url, $content = null, array $headers = [], $method = null)
+    {
+        // Twitch also require that you provide the client id as a header
+        $headers += ['Client-ID' => $this->options['client_id']];
+
+        return parent::httpRequest($url, $content, $headers, $method);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://id.twitch.tv/oauth2/authorize',
+            'access_token_url' => 'https://id.twitch.tv/oauth2/token',
+            'infos_url' => 'https://api.twitch.tv/helix/users',
+            'use_bearer_authorization' => true,
+            'use_authorization_to_get_token' => false,
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TwitterResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TwitterResourceOwner.php
new file mode 100644
index 0000000..7125f6a
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/TwitterResourceOwner.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Alexander 
+ */
+final class TwitterResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'twitter';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id_str',
+        'nickname' => 'screen_name',
+        'realname' => 'name',
+        'profilepicture' => 'profile_image_url_https',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        if ($this->options['include_email']) {
+            $this->options['infos_url'] = $this->normalizeUrl($this->options['infos_url'], ['include_email' => 'true']);
+        }
+
+        return parent::getUserInformation($accessToken, $extraParameters);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.twitter.com/oauth/authenticate',
+            'request_token_url' => 'https://api.twitter.com/oauth/request_token',
+            'access_token_url' => 'https://api.twitter.com/oauth/access_token',
+            'infos_url' => 'https://api.twitter.com/1.1/account/verify_credentials.json',
+            'include_email' => false,
+        ]);
+
+        $resolver->setDefined('x_auth_access_type');
+        // @link https://dev.twitter.com/oauth/reference/post/oauth/request_token
+        $resolver->setAllowedValues('x_auth_access_type', ['read', 'write']);
+        // @link https://dev.twitter.com/rest/reference/get/account/verify_credentials
+        $resolver->setAllowedTypes('include_email', 'bool');
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/VkontakteResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/VkontakteResourceOwner.php
new file mode 100644
index 0000000..f4726f2
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/VkontakteResourceOwner.php
@@ -0,0 +1,109 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\HttpClient\Exception\JsonException;
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * @author Adrov Igor 
+ * @author Vladislav Vlastovskiy 
+ * @author Alexander Latushkin 
+ */
+final class VkontakteResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'vkontakte';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'response.0.id',
+        'nickname' => 'response.0.nickname',
+        'firstname' => 'response.0.first_name',
+        'lastname' => 'response.0.last_name',
+        'realname' => ['response.0.last_name', 'response.0.first_name'],
+        'profilepicture' => 'response.0.photo_medium',
+        'email' => 'email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = [])
+    {
+        $url = $this->normalizeUrl($this->options['infos_url'], [
+            'access_token' => $accessToken['access_token'],
+            'fields' => $this->options['fields'],
+            'name_case' => $this->options['name_case'],
+            'v' => $this->options['api_version'],
+        ]);
+
+        try {
+            $response = $this->getUserResponse();
+            $response->setResourceOwner($this);
+            $response->setOAuthToken(new OAuthToken($accessToken));
+
+            $content = $this->doGetUserInformationRequest($url)->toArray(false);
+            $content['email'] = $accessToken['email'] ?? null;
+
+            if (isset($content['response'][0]['screen_name'])) {
+                $content['response'][0]['nickname'] = $content['response'][0]['screen_name'];
+            }
+
+            $response->setData($content);
+
+            return $response;
+        } catch (TransportExceptionInterface|JsonException $e) {
+            throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://oauth.vk.com/authorize',
+            'access_token_url' => 'https://oauth.vk.com/access_token',
+            'infos_url' => 'https://api.vk.com/method/users.get',
+            'use_authorization_to_get_token' => false,
+
+            // Based on: https://vk.com/dev/constant_version_updates
+            'api_version' => '5.131',
+
+            'scope' => 'email',
+
+            'use_commas_in_scope' => true,
+
+            'fields' => 'nickname,photo_medium,screen_name,email',
+            'name_case' => null,
+        ]);
+
+        $fieldsNormalizer = function (Options $options, $value) {
+            if (!$value) {
+                return null;
+            }
+
+            return \is_array($value) ? implode(',', $value) : $value;
+        };
+
+        $resolver->setNormalizer('fields', $fieldsNormalizer);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/WindowsLiveResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/WindowsLiveResourceOwner.php
new file mode 100644
index 0000000..179bb8e
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/WindowsLiveResourceOwner.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Alexander 
+ */
+final class WindowsLiveResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'windows_live';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'name',
+        'realname' => 'name',
+        'firstname' => 'first_name',
+        'lastname' => 'last_name',
+        'email' => 'emails.account', // requires 'wl.emails' scope
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetTokenRequest($url, array $parameters = [])
+    {
+        return parent::httpRequest($url, http_build_query($parameters, '', '&'));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function httpRequest($url, $content = null, array $headers = [], $method = null)
+    {
+        // Skip the Content-Type header in GenericOAuth2ResourceOwner::httpRequest
+        return AbstractResourceOwner::httpRequest($url, $content, $headers, $method);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://login.live.com/oauth20_authorize.srf',
+            'access_token_url' => 'https://login.live.com/oauth20_token.srf',
+            'infos_url' => 'https://apis.live.net/v5.0/me',
+
+            'scope' => 'wl.signin',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/WordpressResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/WordpressResourceOwner.php
new file mode 100644
index 0000000..3040df8
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/WordpressResourceOwner.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Joseph Bielawski 
+ */
+final class WordpressResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'wordpress';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'ID',
+        'nickname' => 'username',
+        'realname' => 'display_name',
+        'email' => 'email',
+        'profilepicture' => 'avatar_URL',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://public-api.wordpress.com/oauth2/authorize',
+            'access_token_url' => 'https://public-api.wordpress.com/oauth2/token',
+            'infos_url' => 'https://public-api.wordpress.com/rest/v1/me',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/XingResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/XingResourceOwner.php
new file mode 100644
index 0000000..f57787b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/XingResourceOwner.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author othillo 
+ */
+final class XingResourceOwner extends GenericOAuth1ResourceOwner
+{
+    public const TYPE = 'xing';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'users.0.id',
+        'nickname' => 'users.0.display_name',
+        'firstname' => 'users.0.first_name',
+        'lastname' => 'users.0.last_name',
+        'realname' => ['users.0.first_name', 'users.0.last_name'],
+        'profilepicture' => 'users.0.photo_urls.large',
+        'email' => 'users.0.active_email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.xing.com/v1/authorize',
+            'request_token_url' => 'https://api.xing.com/v1/request_token',
+            'access_token_url' => 'https://api.xing.com/v1/access_token',
+            'infos_url' => 'https://api.xing.com/v1/users/me',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YahooResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YahooResourceOwner.php
new file mode 100644
index 0000000..f587bbf
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YahooResourceOwner.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Tom 
+ * @author Alexander 
+ */
+final class YahooResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'yahoo';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'sub',
+        'nickname' => 'given_name',
+        'realname' => 'name',
+        'email' => 'email',
+        'firstname' => 'given_name',
+        'lastname' => 'family_name',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://api.login.yahoo.com/oauth2/request_auth',
+            'request_token_url' => 'https://api.login.yahoo.com/oauth2/get_token',
+            'access_token_url' => 'https://api.login.yahoo.com/oauth2/get_token',
+            'infos_url' => 'https://api.login.yahoo.com/openid/v1/userinfo',
+
+            'realm' => 'yahooapis.com',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YandexResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YandexResourceOwner.php
new file mode 100644
index 0000000..42d8d6b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YandexResourceOwner.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Anton Kamenschikov 
+ */
+final class YandexResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'yandex';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'id',
+        'nickname' => 'display_name',
+        'realname' => 'real_name',
+        'email' => 'default_email',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetUserInformationRequest($url, array $parameters = [])
+    {
+        // Yandex require to pass the OAuth token as 'oauth_token' instead of 'access_token'
+        return $this->httpRequest(str_replace('access_token', 'oauth_token', $url));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://oauth.yandex.ru/authorize',
+            'access_token_url' => 'https://oauth.yandex.ru/token',
+            'infos_url' => 'https://login.yandex.ru/info?format=json',
+        ]);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YoutubeResourceOwner.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YoutubeResourceOwner.php
new file mode 100644
index 0000000..566877d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwner/YoutubeResourceOwner.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Gennady Telegin 
+ */
+final class YoutubeResourceOwner extends GenericOAuth2ResourceOwner
+{
+    public const TYPE = 'youtube';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected array $paths = [
+        'identifier' => 'items.0.id',
+        'nickname' => 'items.0.snippet.title',
+        'realname' => 'items.0.snippet.title',
+        'email' => 'email',
+        'profilepicture' => 'items.0.snippet.thumbnails.high.url',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
+    {
+        return parent::getAuthorizationUrl($redirectUri, array_merge([
+            'access_type' => $this->options['access_type'],
+            'approval_prompt' => $this->options['approval_prompt'],
+            'request_visible_actions' => $this->options['request_visible_actions'],
+            'hd' => $this->options['hd'],
+            'prompt' => $this->options['prompt'],
+        ], $extraParameters));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureOptions(OptionsResolver $resolver)
+    {
+        parent::configureOptions($resolver);
+
+        $resolver->setDefaults([
+            'authorization_url' => 'https://accounts.google.com/o/oauth2/auth',
+            'access_token_url' => 'https://accounts.google.com/o/oauth2/token',
+            'revoke_token_url' => 'https://accounts.google.com/o/oauth2/revoke',
+            'infos_url' => 'https://www.googleapis.com/youtube/v3/channels?part=id,snippet&mine=true',
+            'scope' => 'https://www.googleapis.com/auth/youtube.readonly',
+
+            'access_type' => null,
+            'approval_prompt' => null,
+            'display' => null,
+            // Identifying a particular hosted domain account to be accessed (for example, 'mycollege.edu')
+            'hd' => null,
+            'login_hint' => null,
+            'prompt' => null,
+            'request_visible_actions' => null,
+        ]);
+
+        $resolver
+            // @link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
+            ->setAllowedValues('access_type', ['online', 'offline', null])
+            // sometimes we need to force for approval prompt (e.g. when we lost refresh token)
+            ->setAllowedValues('approval_prompt', ['force', 'auto', null])
+            // @link https://developers.google.com/accounts/docs/OAuth2Login#authenticationuriparameters
+            ->setAllowedValues('display', ['page', 'popup', 'touch', 'wap', null])
+            ->setAllowedValues('login_hint', ['email address', 'sub', null])
+            ->setAllowedValues('prompt', [null, 'consent', 'select_account', null])
+        ;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwnerInterface.php b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwnerInterface.php
new file mode 100644
index 0000000..475dcd8
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResourceOwnerInterface.php
@@ -0,0 +1,114 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
+use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
+use Symfony\Component\HttpFoundation\Request as HttpRequest;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+/**
+ * ResourceOwnerInterface.
+ *
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ */
+interface ResourceOwnerInterface
+{
+    /**
+     * Retrieves the user's information from an access_token.
+     *
+     * @param array $accessToken     The access token
+     * @param array $extraParameters An array of parameters to add to the url
+     *
+     * @return UserResponseInterface the wrapped response interface
+     *
+     * @throws HttpTransportException
+     */
+    public function getUserInformation(array $accessToken, array $extraParameters = []);
+
+    /**
+     * Returns the provider's authorization url.
+     *
+     * @param string $redirectUri     The uri to redirect the client back to
+     * @param array  $extraParameters An array of parameters to add to the url
+     *
+     * @return string The authorization url
+     */
+    public function getAuthorizationUrl($redirectUri, array $extraParameters = []);
+
+    /**
+     * Retrieve an access token for a given code.
+     *
+     * @param HttpRequest $request         The request object where is going to extract the code from
+     * @param string      $redirectUri     The uri to redirect the client back to
+     * @param array       $extraParameters An array of parameters to add to the url
+     *
+     * @return array The access token
+     *
+     * @throws HttpTransportException
+     */
+    public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = []);
+
+    /**
+     * Check whatever CSRF token from request is valid or not.
+     *
+     * @param string|null $csrfToken
+     *
+     * @return bool True if CSRF token is valid
+     *
+     * @throws AuthenticationException When token is not valid
+     */
+    public function isCsrfTokenValid($csrfToken);
+
+    /**
+     * Return a name for the resource owner.
+     *
+     * @return string
+     */
+    public function getName();
+
+    /**
+     * Retrieve an option by name.
+     *
+     * @param string $name The option name
+     *
+     * @return mixed The option value
+     *
+     * @throws \InvalidArgumentException When the option does not exist
+     */
+    public function getOption($name);
+
+    /**
+     * Checks whether the class can handle the request.
+     *
+     * @return bool
+     */
+    public function handles(HttpRequest $request);
+
+    /**
+     * Add extra paths to the configuration.
+     */
+    public function addPaths(array $paths);
+
+    /**
+     * @param string $refreshToken    Refresh token
+     * @param array  $extraParameters An array of parameters to add to the url
+     */
+    public function refreshAccessToken($refreshToken, array $extraParameters = []);
+
+    public function getState(): StateInterface;
+
+    public function storeState(?StateInterface $state = null);
+
+    public function addStateParameter(string $key, string $value): void;
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/Response/AbstractUserResponse.php b/vendor/hwi/oauth-bundle/src/OAuth/Response/AbstractUserResponse.php
new file mode 100644
index 0000000..cef07ff
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/Response/AbstractUserResponse.php
@@ -0,0 +1,149 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\Response;
+
+use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+/**
+ * @author Alexander 
+ */
+abstract class AbstractUserResponse implements UserResponseInterface
+{
+    /**
+     * @var array
+     */
+    protected $data;
+
+    /**
+     * @var ResourceOwnerInterface
+     */
+    protected $resourceOwner;
+
+    /**
+     * @var OAuthToken
+     */
+    protected $oAuthToken;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getEmail()
+    {
+        return null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getProfilePicture()
+    {
+        return null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessToken()
+    {
+        return $this->oAuthToken->getAccessToken();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRefreshToken()
+    {
+        return $this->oAuthToken->getRefreshToken();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTokenSecret()
+    {
+        return $this->oAuthToken->getTokenSecret();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getExpiresIn()
+    {
+        return $this->oAuthToken->getExpiresIn();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setOAuthToken(OAuthToken $token)
+    {
+        $this->oAuthToken = $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getOAuthToken()
+    {
+        return $this->oAuthToken;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setData($data)
+    {
+        if (\is_array($data)) {
+            $this->data = $data;
+
+            return;
+        }
+
+        if (!$data) {
+            $this->data = [];
+
+            return;
+        }
+
+        try {
+            $this->data = json_decode($data, true, 512, \JSON_THROW_ON_ERROR);
+        } catch (\JsonException $exception) {
+            throw new AuthenticationException('Response is not a valid JSON code.');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getResourceOwner()
+    {
+        return $this->resourceOwner;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setResourceOwner(ResourceOwnerInterface $resourceOwner)
+    {
+        $this->resourceOwner = $resourceOwner;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/Response/LinkedinUserResponse.php b/vendor/hwi/oauth-bundle/src/OAuth/Response/LinkedinUserResponse.php
new file mode 100644
index 0000000..6636e07
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/Response/LinkedinUserResponse.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\Response;
+
+final class LinkedinUserResponse extends PathUserResponse
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getFirstName(): ?string
+    {
+        return $this->getPreferredLocaleValue('firstname');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getLastName(): ?string
+    {
+        return $this->getPreferredLocaleValue('lastname');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getProfilePicture(): ?string
+    {
+        // https://docs.microsoft.com/en-us/linkedin/shared/references/v2/profile/profile-picture
+        /** @var array>>> $profilePicture */
+        $profilePicture = $this->getValueForPath('profilepicture');
+        if (
+            !\is_array($profilePicture)
+            || !isset($profilePicture['displayImage~']['elements'])
+            || 0 === \count($profilePicture['displayImage~']['elements'])
+        ) {
+            return null;
+        }
+
+        $publicElements = array_filter($profilePicture['displayImage~']['elements'], static function ($element) {
+            return 'PUBLIC' === $element['authorizationMethod'];
+        });
+        if (0 === \count($publicElements)) {
+            return null;
+        }
+
+        // the last images seems to always be the one with the best quality, so we take this one
+        $element = array_values(\array_slice($publicElements, -1))[0];
+
+        return $element['identifiers'][0]['identifier'];
+    }
+
+    /**
+     * Helper to extract the preferred locale value from MultiLocaleString
+     * https://docs.microsoft.com/en-us/linkedin/shared/references/v2/object-types#multilocalestring.
+     */
+    private function getPreferredLocaleValue(string $path): ?string
+    {
+        /** @var array> $multiLocaleString */
+        $multiLocaleString = $this->getValueForPath($path);
+
+        $locale = '';
+        if (isset($multiLocaleString['preferredLocale'])) {
+            $locale = $multiLocaleString['preferredLocale']['language'];
+            if (!empty($multiLocaleString['preferredLocale']['country'])) {
+                $locale .= '_'.$multiLocaleString['preferredLocale']['country'];
+            }
+        }
+
+        if (isset($multiLocaleString['localized'][$locale])) {
+            return $multiLocaleString['localized'][$locale];
+        }
+
+        $fallbackLocale = array_keys($multiLocaleString['localized'])[0];
+
+        return $multiLocaleString['localized'][$fallbackLocale];
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/Response/PathUserResponse.php b/vendor/hwi/oauth-bundle/src/OAuth/Response/PathUserResponse.php
new file mode 100644
index 0000000..83f2d88
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/Response/PathUserResponse.php
@@ -0,0 +1,189 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\Response;
+
+/**
+ * Class parsing the properties by given path options.
+ *
+ * @author Geoffrey Bachelet 
+ * @author Alexander 
+ * @author Joseph Bielawski 
+ */
+class PathUserResponse extends AbstractUserResponse
+{
+    /**
+     * @var array
+     */
+    protected $paths = [
+        'identifier' => null,
+        'nickname' => null,
+        'firstname' => null,
+        'lastname' => null,
+        'realname' => null,
+        'email' => null,
+        'profilepicture' => null,
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserIdentifier(): string
+    {
+        $value = $this->getValueForPath('identifier');
+        if (null === $value) {
+            throw new \InvalidArgumentException('User identifier was not found in response.');
+        }
+
+        return (string) $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUsername()
+    {
+        try {
+            return $this->getUserIdentifier();
+        } catch (\InvalidArgumentException $e) {
+            // @phpstan-ignore-next-line BC compatibility
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getNickname()
+    {
+        return $this->getValueForPath('nickname');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getFirstName()
+    {
+        return $this->getValueForPath('firstname');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getLastName()
+    {
+        return $this->getValueForPath('lastname');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRealName()
+    {
+        return $this->getValueForPath('realname');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getEmail()
+    {
+        return $this->getValueForPath('email');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getProfilePicture()
+    {
+        return $this->getValueForPath('profilepicture');
+    }
+
+    /**
+     * Get the configured paths.
+     *
+     * @return array
+     */
+    public function getPaths()
+    {
+        return $this->paths;
+    }
+
+    /**
+     * Configure the paths.
+     */
+    public function setPaths(array $paths)
+    {
+        $this->paths = array_merge($this->paths, $paths);
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return array|string|null
+     */
+    public function getPath($name)
+    {
+        return $this->paths[$name] ?? null;
+    }
+
+    /**
+     * Extracts a value from the response for a given path.
+     *
+     * @param string $path Name of the path to get the value for
+     *
+     * @return string|null
+     */
+    protected function getValueForPath($path)
+    {
+        $data = $this->data;
+        if (!$data) {
+            return null;
+        }
+
+        $steps = $this->getPath($path);
+        if (!$steps) {
+            return null;
+        }
+
+        if (\is_array($steps)) {
+            if (1 === \count($steps)) {
+                return $this->getValue(current($steps), $data);
+            }
+
+            $value = [];
+            foreach ($steps as $step) {
+                $value[] = $this->getValue($step, $data);
+            }
+
+            return trim(implode(' ', $value)) ?: null;
+        }
+
+        return $this->getValue($steps, $data);
+    }
+
+    /**
+     * @return array|string|null
+     */
+    private function getValue(string $steps, array $data)
+    {
+        $value = $data;
+        foreach (explode('.', $steps) as $step) {
+            if (!\array_key_exists($step, $value)) {
+                return null;
+            }
+
+            $value = $value[$step];
+        }
+
+        return $value;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/Response/SensioConnectUserResponse.php b/vendor/hwi/oauth-bundle/src/OAuth/Response/SensioConnectUserResponse.php
new file mode 100644
index 0000000..2bfc43f
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/Response/SensioConnectUserResponse.php
@@ -0,0 +1,189 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\Response;
+
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+/**
+ * @author Joseph Bielawski 
+ * @author SensioLabs 
+ */
+final class SensioConnectUserResponse extends AbstractUserResponse
+{
+    /**
+     * @var \DOMNode
+     */
+    protected $data;
+
+    private ?\DOMXPath $xpath;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUserIdentifier(): string
+    {
+        /** @var \DOMAttr|null $attribute */
+        $attribute = $this->data->attributes->getNamedItem('id');
+        if (null === $attribute) {
+            throw new \InvalidArgumentException('User identifier was not found in response.');
+        }
+
+        return $attribute->value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUsername(): ?string
+    {
+        try {
+            return $this->getUserIdentifier();
+        } catch (\InvalidArgumentException $e) {
+            // @phpstan-ignore-next-line BC compatibility
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getNickname()
+    {
+        $username = null;
+        $accounts = $this->xpath->query('./foaf:account/foaf:OnlineAccount', $this->data);
+        for ($i = 0; $i < $accounts->length; ++$i) {
+            /** @var \DOMNode $account */
+            $account = $accounts->item($i);
+            if ('SensioLabs Connect' === $this->getNodeValue('./foaf:name', $account)) {
+                $username = $this->getNodeValue('foaf:accountName', $account);
+
+                break;
+            }
+        }
+
+        return $username ?: $this->getNodeValue('./foaf:name', $this->data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getFirstName(): ?string
+    {
+        return null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getLastName(): ?string
+    {
+        return null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRealName(): ?string
+    {
+        return $this->getNodeValue('./foaf:name', $this->data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getEmail(): ?string
+    {
+        return $this->getNodeValue('./foaf:mbox', $this->data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getProfilePicture(): ?string
+    {
+        return $this->getNodeValue('./atom:link[@rel="foaf:depiction"]', $this->data, 'link');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setData($data): void
+    {
+        $dom = new \DOMDocument();
+        try {
+            if (!$dom->loadXML($data)) {
+                throw new \ErrorException('Could not transform this xml to a \DOMDocument instance.');
+            }
+        } catch (\Exception $e) {
+            throw new AuthenticationException('Could not retrieve valid user info.');
+        }
+
+        $this->xpath = new \DOMXPath($dom);
+
+        $nodes = $this->xpath->evaluate('/api/root');
+        $user = $this->xpath->query('./foaf:Person', $nodes->item(0));
+        if (1 !== $user->length) {
+            throw new AuthenticationException('Could not retrieve user info.');
+        }
+
+        /** @var \DOMNode $userElement */
+        $userElement = $user->item(0);
+
+        $this->data = $userElement;
+    }
+
+    /**
+     * @return mixed|null
+     */
+    private function getNodeValue(string $query, \DOMNode $element, string $nodeType = 'normal')
+    {
+        $nodeList = $this->xpath->query($query, $element);
+        if ($nodeList && $nodeList->length > 0) {
+            $node = $nodeList->item(0);
+            switch ($nodeType) {
+                case 'link':
+                    /** @var \DOMAttr $attribute */
+                    $attribute = $node->attributes->getNamedItem('href');
+                    $nodeValue = $attribute->value;
+                    break;
+                case 'normal':
+                default:
+                    $nodeValue = $node->nodeValue;
+                    break;
+            }
+
+            return $this->sanitizeValue($nodeValue);
+        }
+
+        return null;
+    }
+
+    /**
+     * @return bool|string|null
+     */
+    private function sanitizeValue(string $value)
+    {
+        if ('true' === $value) {
+            return true;
+        }
+
+        if ('false' === $value) {
+            return false;
+        }
+
+        if (empty($value)) {
+            return null;
+        }
+
+        return $value;
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/Response/UserResponseInterface.php b/vendor/hwi/oauth-bundle/src/OAuth/Response/UserResponseInterface.php
new file mode 100644
index 0000000..c571e7f
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/Response/UserResponseInterface.php
@@ -0,0 +1,120 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\Response;
+
+use HWI\Bundle\OAuthBundle\OAuth\ResponseInterface;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
+
+/**
+ * @author Alexander 
+ * @author Joseph Bielawski 
+ *
+ * @method string getUserIdentifier() Get the unique user identifier.
+ */
+interface UserResponseInterface extends ResponseInterface
+{
+    /**
+     * Get the unique user identifier.
+     *
+     * Note that this is not always common known "username" because of implementation
+     * in Symfony framework. For more details follow link below.
+     *
+     * @see https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Security/Core/User/UserProviderInterface.php#L20-L28
+     *
+     * @return string|null
+     *
+     * @deprecated Please use getUserIdentifier
+     */
+    public function getUsername();
+
+    /**
+     * Get the username to display.
+     *
+     * @return string
+     */
+    public function getNickname();
+
+    /**
+     * Get the first name of user.
+     *
+     * @return string|null
+     */
+    public function getFirstName();
+
+    /**
+     * Get the last name of user.
+     *
+     * @return string|null
+     */
+    public function getLastName();
+
+    /**
+     * Get the real name of user.
+     *
+     * @return string|null
+     */
+    public function getRealName();
+
+    /**
+     * Get the email address.
+     *
+     * @return string|null
+     */
+    public function getEmail();
+
+    /**
+     * Get the url to the profile picture.
+     *
+     * @return string|null
+     */
+    public function getProfilePicture();
+
+    /**
+     * Get the access token used for the request.
+     *
+     * @return string
+     */
+    public function getAccessToken();
+
+    /**
+     * Get the access token used for the request.
+     *
+     * @return string|null
+     */
+    public function getRefreshToken();
+
+    /**
+     * Get oauth token secret used for the request.
+     *
+     * @return string|null
+     */
+    public function getTokenSecret();
+
+    /**
+     * Get the info when token will expire.
+     *
+     * @return int|null
+     */
+    public function getExpiresIn();
+
+    /**
+     * Set the raw token data from the request.
+     */
+    public function setOAuthToken(OAuthToken $token);
+
+    /**
+     * Get the raw token data from the request.
+     *
+     * @return OAuthToken
+     */
+    public function getOAuthToken();
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/ResponseInterface.php b/vendor/hwi/oauth-bundle/src/OAuth/ResponseInterface.php
new file mode 100644
index 0000000..5273c2b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/ResponseInterface.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth;
+
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+/**
+ * @author Alexander 
+ */
+interface ResponseInterface
+{
+    /**
+     * Get the api response data.
+     *
+     * @return array
+     */
+    public function getData();
+
+    /**
+     * Set the raw api response.
+     *
+     * @param string|array $data
+     *
+     * @throws AuthenticationException
+     */
+    public function setData($data);
+
+    /**
+     * Get the resource owner responsible for the response.
+     *
+     * @return ResourceOwnerInterface
+     */
+    public function getResourceOwner();
+
+    /**
+     * Set the resource owner for the response.
+     */
+    public function setResourceOwner(ResourceOwnerInterface $resourceOwner);
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/State/State.php b/vendor/hwi/oauth-bundle/src/OAuth/State/State.php
new file mode 100644
index 0000000..fbd0db8
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/State/State.php
@@ -0,0 +1,170 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth\State;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\StateRetrievalException;
+use HWI\Bundle\OAuthBundle\OAuth\StateInterface;
+use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
+
+final class State implements StateInterface
+{
+    public const DEFAULT_KEY = 'state';
+    public const CSRF_TOKEN_KEY = 'csrf_token';
+
+    /**
+     * @var array
+     */
+    private array $values = [];
+
+    /**
+     * @param string|array|null $parameters The state parameter as a string or assoc array
+     * @param bool                             $keepCsrf   Whether to keep the CSRF token in the state or not
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function __construct($parameters, bool $keepCsrf = true)
+    {
+        if (!\is_array($parameters)) {
+            $parameters = $this->parseStringParameter($parameters);
+        }
+
+        if (null !== $parameters) {
+            if (!$this->isAssociatedArray($parameters)) {
+                throw new \InvalidArgumentException('Constructor argument should be a non-empty, associative array');
+            }
+
+            foreach ($parameters as $key => $value) {
+                if (false === $keepCsrf && self::CSRF_TOKEN_KEY === $key) {
+                    continue;
+                }
+                $this->add($key, $value);
+            }
+        }
+    }
+
+    public function __serialize(): array
+    {
+        return [
+            'values' => $this->values,
+        ];
+    }
+
+    public function __unserialize(array $data): void
+    {
+        $this->values = $data['values'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function add(string $key, string $value): void
+    {
+        if (isset($this->values[$key])) {
+            throw new DuplicateKeyException(sprintf('State key [%s] is already set.', $key));
+        }
+
+        $this->values[$key] = $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key): ?string
+    {
+        if (!isset($this->values[$key])) {
+            throw StateRetrievalException::forKey($key);
+        }
+
+        return $this->values[$key];
+    }
+
+    public function has(string $key): bool
+    {
+        return \array_key_exists($key, $this->values);
+    }
+
+    public function setCsrfToken(string $token): void
+    {
+        $this->values[self::CSRF_TOKEN_KEY] = $token;
+    }
+
+    public function getAll(): array
+    {
+        $values = $this->values;
+        unset($values[self::CSRF_TOKEN_KEY]);
+
+        return $values;
+    }
+
+    public function getCsrfToken(): ?string
+    {
+        return $this->values[self::CSRF_TOKEN_KEY] ?? null;
+    }
+
+    /**
+     * Encodes the array of values to a string so it can be stored in a query parameter.
+     * Returns the plain value if only the default key or CSRF token has been set.
+     */
+    public function encode(): ?string
+    {
+        if (!$this->values) {
+            return null;
+        }
+
+        $encoded = urlencode($this->encodeValues());
+
+        return '' !== $encoded ? $encoded : null;
+    }
+
+    /**
+     * @param string|null $queryParameter The state query parameter string
+     *
+     * @return array|null
+     */
+    private function parseStringParameter(?string $queryParameter = null): ?array
+    {
+        $urlDecoded = $queryParameter ? urldecode($queryParameter) : '';
+
+        try {
+            $values = json_decode(base64_decode($urlDecoded), true, 512, \JSON_THROW_ON_ERROR);
+        } catch (\JsonException $e) {
+            $values = null;
+        }
+
+        if (null === $values && '' !== $urlDecoded) {
+            $values[self::DEFAULT_KEY] = $urlDecoded;
+        }
+
+        return $values;
+    }
+
+    /**
+     * @return string The encoded array
+     */
+    private function encodeValues(): string
+    {
+        try {
+            return base64_encode(json_encode($this->values, \JSON_THROW_ON_ERROR));
+        } catch (\JsonException $e) {
+            return '';
+        }
+    }
+
+    private function isAssociatedArray(?array $array): bool
+    {
+        if ([] === $array) {
+            return false;
+        }
+
+        return array_keys($array) !== range(0, \count($array) - 1);
+    }
+}
diff --git a/vendor/hwi/oauth-bundle/src/OAuth/StateInterface.php b/vendor/hwi/oauth-bundle/src/OAuth/StateInterface.php
new file mode 100644
index 0000000..0acb6cd
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/OAuth/StateInterface.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace HWI\Bundle\OAuthBundle\OAuth;
+
+use HWI\Bundle\OAuthBundle\OAuth\Exception\StateRetrievalException;
+use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
+
+interface StateInterface
+{
+    public function __serialize(): array;
+
+    public function __unserialize(array $data): void;
+
+    /**
+     * @param string $key   The key to store a value to
+     * @param string $value The value to store
+     *
+     * @throws DuplicateKeyException
+     */
+    public function add(string $key, string $value);
+
+    /**
+     * @return string The value set to this key
+     *
+     * @throws StateRetrievalException
+     */
+    public function get(string $key): ?string;
+
+    public function has(string $key): bool;
+
+    /**
+     * @return array
+     */
+    public function getAll(): array;
+
+    public function setCsrfToken(string $token): void;
+
+    public function getCsrfToken(): ?string;
+
+    public function encode(): ?string;
+}
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/controller.php b/vendor/hwi/oauth-bundle/src/Resources/config/controller.php
new file mode 100644
index 0000000..1acab72
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/controller.php
@@ -0,0 +1,82 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+use HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController;
+use HWI\Bundle\OAuthBundle\Controller\Connect\RegisterController;
+use HWI\Bundle\OAuthBundle\Controller\LoginController;
+use HWI\Bundle\OAuthBundle\Controller\RedirectToServiceController;
+
+return static function (ContainerConfigurator $containerConfigurator): void {
+    $services = $containerConfigurator->services();
+
+    $services->set(ConnectController::class)
+        ->public()
+        ->arg('$oauthUtils', service('hwi_oauth.security.oauth_utils'))
+        ->arg('$resourceOwnerMapLocator', service('hwi_oauth.resource_ownermap_locator'))
+        ->arg('$requestStack', service('request_stack'))
+        ->arg('$dispatcher', service('event_dispatcher'))
+        ->arg('$tokenStorage', service('security.token_storage'))
+        ->arg('$userChecker', service('hwi_oauth.user_checker'))
+        ->arg('$authorizationChecker', service('security.authorization_checker'))
+        ->arg('$formFactory', service('form.factory'))
+        ->arg('$twig', service('twig'))
+        ->arg('$router', service('router'))
+        ->arg('$grantRule', '%hwi_oauth.grant_rule%')
+        ->arg('$failedUseReferer', '%hwi_oauth.failed_use_referer%')
+        ->arg('$failedAuthPath', '%hwi_oauth.failed_auth_path%')
+        ->arg('$enableConnectConfirmation', '%hwi_oauth.connect.confirmation%')
+        ->arg('$accountConnector', service('hwi_oauth.account.connector')->nullOnInvalid());
+
+    $services->set(RegisterController::class)
+        ->public()
+        ->arg('$resourceOwnerMapLocator', service('hwi_oauth.resource_ownermap_locator'))
+        ->arg('$requestStack', service('request_stack'))
+        ->arg('$dispatcher', service('event_dispatcher'))
+        ->arg('$tokenStorage', service('security.token_storage'))
+        ->arg('$userChecker', service('hwi_oauth.user_checker'))
+        ->arg('$authorizationChecker', service('security.authorization_checker'))
+        ->arg('$formFactory', service('form.factory'))
+        ->arg('$twig', service('twig'))
+        ->arg('$grantRule', '%hwi_oauth.grant_rule%')
+        ->arg('$registrationForm', '%hwi_oauth.connect.registration_form%')
+        ->arg('$accountConnector', service('hwi_oauth.account.connector')->nullOnInvalid())
+        ->arg('$formHandler', service('hwi_oauth.registration.form.handler')->nullOnInvalid());
+
+    $services->set(LoginController::class)
+        ->public()
+        ->args([
+            service('security.authentication_utils'),
+            service('router'),
+            service('security.authorization_checker'),
+            service('request_stack'),
+            service('twig'),
+            '%hwi_oauth.connect%',
+            '%hwi_oauth.grant_rule%',
+        ]);
+
+    $services->set(RedirectToServiceController::class)
+        ->public()
+        ->args([
+            service('hwi_oauth.security.oauth_utils'),
+            service('hwi_oauth.util.domain_whitelist'),
+            service('hwi_oauth.resource_ownermap_locator'),
+            '%hwi_oauth.target_path_parameter%',
+            '%hwi_oauth.failed_use_referer%',
+            '%hwi_oauth.use_referer%',
+        ]);
+
+    $services->alias('hwi_oauth.user_checker', 'security.user_checker')
+        ->public();
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/oauth.php b/vendor/hwi/oauth-bundle/src/Resources/config/oauth.php
new file mode 100644
index 0000000..596079c
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/oauth.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorage\SessionStorage;
+use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Provider\OAuthProvider;
+use HWI\Bundle\OAuthBundle\Security\Core\User\EntityUserProvider;
+use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider;
+use HWI\Bundle\OAuthBundle\Security\Http\Authentication\AuthenticationFailureHandler;
+use HWI\Bundle\OAuthBundle\Security\Http\EntryPoint\OAuthEntryPoint;
+use HWI\Bundle\OAuthBundle\Security\Http\Firewall\AbstractRefreshAccessTokenListener;
+use HWI\Bundle\OAuthBundle\Security\Http\Firewall\OAuthListener;
+use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
+
+return static function (ContainerConfigurator $containerConfigurator): void {
+    $services = $containerConfigurator->services();
+
+    $services->set('hwi_oauth.authentication.listener.oauth', OAuthListener::class)
+        ->abstract()
+        ->parent('security.authentication.listener.abstract');
+
+    $services->set('hwi_oauth.authentication.provider.oauth', OAuthProvider::class);
+
+    $services->set('hwi_oauth.authentication.entry_point.oauth', OAuthEntryPoint::class)
+        ->args([
+            service('http_kernel'),
+            service('security.http_utils'),
+        ]);
+
+    $services->set('hwi_oauth.user.provider', OAuthUserProvider::class);
+
+    $services->set('hwi_oauth.user.provider.entity', EntityUserProvider::class)
+        ->args([
+            service('doctrine'),
+            abstract_arg('User entity class name'),
+            abstract_arg('an array of properties, where key is resource owner name & value is property name in User entity'),
+        ]);
+
+    $services->set('hwi_oauth.context_listener.abstract_token_refresher', AbstractRefreshAccessTokenListener::class)
+        ->abstract()
+        ->arg(0, abstract_arg('OAuthAuthenticator or AuthenticationProviderInterface'))
+        ->call('setTokenStorage', [service('security.token_storage')]);
+
+    // Session storage
+    $services->set('hwi_oauth.storage.session', SessionStorage::class)
+        ->args([service('request_stack')]);
+
+    $services->set('hwi_oauth.security.oauth_utils', OAuthUtils::class)
+        ->args([
+            service('security.http_utils'),
+            service('security.authorization_checker'),
+            service('security.firewall.map'),
+            '%hwi_oauth.connect%',
+            '%hwi_oauth.grant_rule%',
+        ]);
+
+    $services->set('hwi_oauth.authentication.failure_handler', AuthenticationFailureHandler::class)
+        ->args([
+            service('request_stack'),
+            service('router'),
+            '%hwi_oauth.connect%',
+        ]);
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/resource_owners.php b/vendor/hwi/oauth-bundle/src/Resources/config/resource_owners.php
new file mode 100644
index 0000000..a21eed9
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/resource_owners.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMap;
+use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
+
+return static function (ContainerConfigurator $containerConfigurator): void {
+    $services = $containerConfigurator->services();
+
+    $services->set('hwi_oauth.abstract_resource_ownermap', ResourceOwnerMap::class)
+        ->abstract()
+        ->arg('$httpUtils', service('security.http_utils'))
+        ->arg('$possibleResourceOwners', '%hwi_oauth.resource_owners%')
+        ->arg('$resourceOwners', []);
+
+    $services->set('hwi_oauth.resource_ownermap_locator', ResourceOwnerMapLocator::class);
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/routing/connect.php b/vendor/hwi/oauth-bundle/src/Resources/config/routing/connect.php
new file mode 100644
index 0000000..88d5dea
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/routing/connect.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController;
+use HWI\Bundle\OAuthBundle\Controller\Connect\RegisterController;
+use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+
+return static function (RoutingConfigurator $routes) {
+    $routes->add('hwi_oauth_connect_service', '/service/{service}')
+        ->controller([ConnectController::class, 'connectServiceAction'])
+        ->methods(['GET', 'POST']);
+
+    $routes->add('hwi_oauth_connect_registration', '/registration/{key}')
+        ->controller([RegisterController::class, 'registrationAction'])
+        ->methods(['GET', 'POST']);
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/routing/connect.xml b/vendor/hwi/oauth-bundle/src/Resources/config/routing/connect.xml
new file mode 100644
index 0000000..a119f41
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/routing/connect.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+    
+        HWI\Bundle\OAuthBundle\Controller\Connect\ConnectController::connectServiceAction
+    
+
+    
+        HWI\Bundle\OAuthBundle\Controller\Connect\RegisterController::registrationAction
+    
+
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/routing/login.php b/vendor/hwi/oauth-bundle/src/Resources/config/routing/login.php
new file mode 100644
index 0000000..c7648a4
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/routing/login.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use HWI\Bundle\OAuthBundle\Controller\LoginController;
+use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+
+return function (RoutingConfigurator $routes) {
+    $routes->add('hwi_oauth_connect', '/')
+        ->controller([LoginController::class, 'connectAction'])
+        ->methods(['GET']);
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/routing/login.xml b/vendor/hwi/oauth-bundle/src/Resources/config/routing/login.xml
new file mode 100644
index 0000000..1d29ee4
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/routing/login.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+    
+        HWI\Bundle\OAuthBundle\Controller\LoginController::connectAction
+    
+
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/routing/redirect.php b/vendor/hwi/oauth-bundle/src/Resources/config/routing/redirect.php
new file mode 100644
index 0000000..9f58682
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/routing/redirect.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use HWI\Bundle\OAuthBundle\Controller\RedirectToServiceController;
+use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+
+return function (RoutingConfigurator $routes) {
+    $routes->add('hwi_oauth_service_redirect', '/{service}')
+        ->controller([RedirectToServiceController::class, 'redirectToServiceAction'])
+        ->methods(['GET']);
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/routing/redirect.xml b/vendor/hwi/oauth-bundle/src/Resources/config/routing/redirect.xml
new file mode 100644
index 0000000..a1ca052
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/routing/redirect.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+    
+        HWI\Bundle\OAuthBundle\Controller\RedirectToServiceController::redirectToServiceAction
+    
+
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/twig.php b/vendor/hwi/oauth-bundle/src/Resources/config/twig.php
new file mode 100644
index 0000000..1397380
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/twig.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+use HWI\Bundle\OAuthBundle\Twig\Extension\OAuthExtension;
+use HWI\Bundle\OAuthBundle\Twig\Extension\OAuthRuntime;
+
+return static function (ContainerConfigurator $containerConfigurator): void {
+    $services = $containerConfigurator->services();
+
+    $services->set('hwi_oauth.twig.extension.oauth', OAuthExtension::class)
+        ->tag('twig.extension');
+
+    $services->set('hwi_oauth.twig.extension.oauth.runtime', OAuthRuntime::class)
+        ->args([
+            service('hwi_oauth.security.oauth_utils'),
+            service('request_stack'),
+        ])
+        ->tag('twig.runtime');
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/config/util.php b/vendor/hwi/oauth-bundle/src/Resources/config/util.php
new file mode 100644
index 0000000..9e9287b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/config/util.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+use HWI\Bundle\OAuthBundle\Util\DomainWhitelist;
+use Symfony\Component\HttpClient\HttpClient;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+return static function (ContainerConfigurator $containerConfigurator): void {
+    $services = $containerConfigurator->services();
+
+    $services->set('hwi_oauth.util.domain_whitelist', DomainWhitelist::class)
+        ->args(['%hwi_oauth.target_path_domains_whitelist%']);
+
+    $services->set('hwi_oauth.http_client', HttpClientInterface::class)
+        ->factory([HttpClient::class, 'create'])
+        ->tag('http_client.client');
+};
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.de.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.de.yml
new file mode 100644
index 0000000..21b5004
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.de.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Verbinden'
+    success: '"%name%" erfolgreich mit Benutzeraccount verbunden!'
+    register: 'Neuen Benutzeraccount mit dem Namen "%name%" erstellen'
+    registration_success: 'Benutzeraccount "%username%" erfolgreich angelegt und verknüpft!'
+
+connect:
+    confirm:
+        cancel: 'Abbrechen'
+        submit: 'Benutzeraccount verbinden'
+        text: 'Sicher, dass Sie Ihren %service% Account "%name%" mit Ihrem aktuellen Benutzeraccount verknüpfen möchten?'
+    registration:
+        cancel: 'Abbrechen'
+        submit: 'Benutzeraccount registrieren'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.en.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.en.yml
new file mode 100644
index 0000000..cbfd756
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.en.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Connecting'
+    success: 'Successfully connected the account "%name%"!'
+    register: 'Register with the account "%name%"'
+    registration_success: 'Successfully registered and connected the account "%username%"!'
+
+connect:
+    confirm:
+        cancel: 'Cancel'
+        submit: 'Connect account'
+        text: 'Are you sure that you want to connect your %service% account "%name%" to your current account?'
+    registration:
+        cancel: 'Cancel'
+        submit: 'Register account'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.es.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.es.yml
new file mode 100644
index 0000000..ffdcabe
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.es.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Conectando'
+    success: "Cuenta '%name%' conectada con éxito!"
+    register: "Registrarse con la cuenta '%name%'"
+    registration_success: "Se ha registrado y conectado la cuenta '%username%' con éxito!"
+
+connect:
+    confirm:
+        cancel: 'Cancelar'
+        submit: 'Conectar cuenta'
+        text: "¿Está seguro de que desea conectar su cuenta de %service% '%name%' con su cuenta actual?"
+    registration:
+        cancel: 'Cancelar'
+        submit: 'Registrar cuenta'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.fa.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.fa.yml
new file mode 100644
index 0000000..8720a55
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.fa.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: "در حال برقراری ارتباط"
+    success: "ارتباط با حساب '%name%' با موÙقیت برقرار شد!"
+    register: "با حساب '%name%' ثبت نام کنید"
+    registration_success: "ثبت با با موÙقیت انجام شد Ùˆ به حساب '%username%' متصل شد!"
+
+connect:
+    confirm:
+        cancel: "انصراÙ"
+        submit: "متصل کردن حساب"
+        text: "آیا مطمئن هستید که می‌خواهید حساب %service% با نام '%name%' را به حساب کنونی خود متصل کنید?"
+    registration:
+        cancel: "انصراÙ"
+        submit: "ثبت نام"
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.fr.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.fr.yml
new file mode 100644
index 0000000..d20256d
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.fr.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Connexion en cours'
+    success: "Connexion réussie avec le compte '%name%' !"
+    register: "S'inscrire avec le compte '%name%'"
+    registration_success: "Enregistrement et connexion du compte '%username%' réussis !"
+
+connect:
+    confirm:
+        cancel: 'Annuler'
+        submit: 'Connecter le compte'
+        text: "Êtes-vous sûr de vouloir connecter votre compte %service% avec votre compte '%name%' ?"
+    registration:
+        cancel: 'Annuler'
+        submit: 'Enregistrer le compte'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.hu.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.hu.yml
new file mode 100644
index 0000000..685d65b
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.hu.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Kapcsolódás'
+    success: 'A(z) "%name%" fiók sikeresen csatlakoztatva!'
+    register: 'Regisztrálás a(z) "%name%" fiók névvel'
+    registration_success: 'A(z) "%username%" fiók sikeresen regisztrálva és csatlakoztatva!'
+
+connect:
+    confirm:
+        cancel: 'Mégsem'
+        submit: 'Fiók csatlakoztatása'
+        text: 'Biztos csatlakoztatni szeretné a(z) %service% "%name%" fiókját a jelenlegihez?'
+    registration:
+        cancel: 'Mégsem'
+        submit: 'Fiók regisztrálása'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.it.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.it.yml
new file mode 100644
index 0000000..2a51201
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.it.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Connessione in corso'
+    success: "Connesso con successo all'account '%name%'!"
+    register: "Registrati con l'account '%name%'"
+    registration_success: "Registrato con successo e connesso all'account '%username%'!"
+
+connect:
+    confirm:
+        cancel: 'Annulla'
+        submit: 'Connetti account'
+        text: "Sei sicuro di voler connettere il tuo account %service% '%name%' al tuo account attuale?"
+    registration:
+        cancel: 'Annulla'
+        submit: 'Registra account'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.nl.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.nl.yml
new file mode 100644
index 0000000..b3f2177
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.nl.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Koppelen'
+    success: 'Het account "%name%" is gekoppeld!'
+    register: 'Registreer met het account "%name%"'
+    registration_success: 'Het account "%username%" is succesvol geregistreerd en gekoppeld!'
+
+connect:
+    confirm:
+        cancel: 'Annuleren'
+        submit: 'Account koppelen'
+        text: 'Weet je zeker dat je je %service% account "%name%" aan je huidige account wil koppelen?'
+    registration:
+        cancel: 'Annuleren'
+        submit: 'Registeer account'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.pl.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.pl.yml
new file mode 100644
index 0000000..1337956
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.pl.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'ÅÄ…czenie'
+    success: 'ÅÄ…czenie konta "%name%" zakoÅ„czone sukcesem!'
+    register: 'Zarejestruj siÄ™ korzystajÄ…c z konta "%name%"'
+    registration_success: 'Rejestracja oraz łączenie konta "%username%" zakończone sukcesem!'
+
+connect:
+    confirm:
+        cancel: 'Anuluj'
+        submit: 'Połącz konta'
+        text: 'Czy jesteś pewny, że chcesz połączyć %service% "%name%" ze swoim kontem?'
+    registration:
+        cancel: 'Anuluj'
+        submit: 'Zarejestruj konto'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.ru.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.ru.yml
new file mode 100644
index 0000000..7afae02
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.ru.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'Соединение'
+    success: 'УÑпешное Ñоединение Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼ "%name%"!'
+    register: 'РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼ "%name%"'
+    registration_success: 'УÑÐ¿ÐµÑˆÐ½Ð°Ñ Ñ€ÐµÐ³Ð¸ÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¸ Ñоединение Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼ "%username%"!'
+
+connect:
+    confirm:
+        cancel: 'Отмена'
+        submit: 'Соединить'
+        text: 'Ð’Ñ‹ уверены что хотите Ñоединить "%service%" аккаунт "%name%" Ñ Ñ‚ÐµÐºÑƒÑ‰Ð¸Ð¼ пользователем?'
+    registration:
+        cancel: 'Отмена'
+        submit: 'РегиÑтрациÑ'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.tr.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.tr.yml
new file mode 100644
index 0000000..42b8244
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.tr.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: 'BaÄŸlan'
+    success: 'Başarı ile "%name%" hesabına bağlanıldı!'
+    register: '"%name%" üyeliğim ile kayıt ol'
+    registration_success: '"%username%" başarı ile kayıt oldunuz ve bağlandınız!'
+
+connect:
+    confirm:
+        cancel: 'İptal'
+        submit: 'Üyeliğim ile bağlan'
+        text: '%service% üzerinden "%name%" ile bağlanmak istediğinize emin misiniz?'
+    registration:
+        cancel: 'İptal'
+        submit: 'Hesabımı kaydet'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.uk.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.uk.yml
new file mode 100644
index 0000000..069a3b5
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.uk.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: "З'єднаннÑ"
+    success: "УÑпішне з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· акаунтом '%name%'!"
+    register: "РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð· акаунтом '%name%'"
+    registration_success: "УÑпішна реєÑтраці Ñ– з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· акаунтом '%username%'!"
+
+connect:
+    confirm:
+        cancel: 'Відмінити'
+        submit: "З'єднати"
+        text: "Ви впевнені що бажаєте з'єднати '%service%' акаунт '%name%' з поточним кориÑтувачем?"
+    registration:
+        cancel: 'Відміна'
+        submit: 'РеєÑтраціÑ'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.zh.yml b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.zh.yml
new file mode 100644
index 0000000..2512223
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/translations/HWIOAuthBundle.zh.yml
@@ -0,0 +1,14 @@
+header:
+    connecting: '正在关è”'
+    success: 'æˆåŠŸå…³è”“%name%â€å¸å·ï¼'
+    register: '注册“%name%â€å¸å·'
+    registration_success: 'æˆåŠŸæ³¨å†Œå¹¶å…³è”“%username%â€çš„å¸å·ï¼'
+
+connect:
+    confirm:
+        cancel: 'å–æ¶ˆ'
+        submit: 'å…³è”å¸å·'
+        text: '你确定è¦å°†%service%上的å¸å·â€œ%name%â€ä¸Žå½“å‰å¸å·å…³è”å—?'
+    registration:
+        cancel: 'å–æ¶ˆ'
+        submit: '注册å¸å·'
diff --git a/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_confirm.html.twig b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_confirm.html.twig
new file mode 100644
index 0000000..9119626
--- /dev/null
+++ b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_confirm.html.twig
@@ -0,0 +1,24 @@
+{% extends '@HWIOAuth/layout.html.twig' %}
+
+{% block hwi_oauth_content %}
+    

{{ 'header.connecting' | trans({}, 'HWIOAuthBundle')}}

+
+
+

{{ 'connect.confirm.text' | trans({'%service%': service | trans({}, 'HWIOAuthBundle'), '%name%': userInformation.realName}, 'HWIOAuthBundle') }}

+

+ {{ form_start(form, {'action': path('hwi_oauth_connect_service', {'service': service, 'key': key}), 'attr': {'class': 'registration_register'}}) }} + {{ form_widget(form) }} +

+ + {{ 'connect.confirm.cancel' | trans({}, 'HWIOAuthBundle') }} +
+ {{ form_end(form) }} +

+
+
+ {% if userInformation.profilePicture is defined and userInformation.profilePicture is not empty %} + + {% endif %} +
+
+{% endblock hwi_oauth_content %} diff --git a/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_success.html.twig b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_success.html.twig new file mode 100644 index 0000000..cf2c7b9 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/connect_success.html.twig @@ -0,0 +1,5 @@ +{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} +

{{ 'header.success' | trans({'%name%': userInformation.realName}, 'HWIOAuthBundle') }}

+{% endblock hwi_oauth_content %} diff --git a/vendor/hwi/oauth-bundle/src/Resources/views/Connect/login.html.twig b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/login.html.twig new file mode 100644 index 0000000..b7b1153 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/login.html.twig @@ -0,0 +1,10 @@ +{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} + {% if error is defined and error %} + {{ error }} + {% endif %} + {% for owner in hwi_oauth_resource_owners() %} + {{ owner | trans({}, 'HWIOAuthBundle') }}
+ {% endfor %} +{% endblock hwi_oauth_content %} diff --git a/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration.html.twig b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration.html.twig new file mode 100644 index 0000000..91d3181 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration.html.twig @@ -0,0 +1,22 @@ +{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} +

{{ 'header.register' | trans({'%name%': userInformation.realName}, 'HWIOAuthBundle') }}

+
+
+ {{ form_start(form, {'action': path('hwi_oauth_connect_registration', {'key': key}), 'attr': {'class': 'hwi_oauth_registration_register'}}) }} + {{ form_widget(form) }} +
+ + {{ 'connect.registration.cancel' | trans({}, 'HWIOAuthBundle') }} +
+ {{ form_end(form) }} +
+
+ {% if userInformation.profilePicture is not empty %} + + {% endif %} +
+
+ +{% endblock hwi_oauth_content %} diff --git a/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration_success.html.twig b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration_success.html.twig new file mode 100644 index 0000000..d0fec94 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Resources/views/Connect/registration_success.html.twig @@ -0,0 +1,5 @@ +{% extends '@HWIOAuth/layout.html.twig' %} + +{% block hwi_oauth_content %} +

{{ 'header.registration_success' | trans({'%username%': app.user.username}, 'HWIOAuthBundle') }}

+{% endblock hwi_oauth_content %} diff --git a/vendor/hwi/oauth-bundle/src/Resources/views/layout.html.twig b/vendor/hwi/oauth-bundle/src/Resources/views/layout.html.twig new file mode 100644 index 0000000..d23b61e --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Resources/views/layout.html.twig @@ -0,0 +1,12 @@ + + + + + + +
+ {% block hwi_oauth_content %} + {% endblock hwi_oauth_content %} +
+ + diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Provider/OAuthProvider.php b/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Provider/OAuthProvider.php new file mode 100644 index 0000000..a1874c4 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Provider/OAuthProvider.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\Authentication\Provider; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface; +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; +use HWI\Bundle\OAuthBundle\Security\Core\Exception\OAuthAwareExceptionInterface; +use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface; +use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapInterface; +use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * @author Geoffrey Bachelet + * @author Alexander + */ +final class OAuthProvider implements AuthenticationProviderInterface +{ + private OAuthAwareUserProviderInterface $userProvider; + private ResourceOwnerMapInterface $resourceOwnerMap; + private UserCheckerInterface $userChecker; + private TokenStorageInterface $tokenStorage; + + public function __construct( + OAuthAwareUserProviderInterface $userProvider, + ResourceOwnerMapInterface $resourceOwnerMap, + UserCheckerInterface $userChecker, + TokenStorageInterface $tokenStorage + ) { + $this->userProvider = $userProvider; + $this->resourceOwnerMap = $resourceOwnerMap; + $this->userChecker = $userChecker; + $this->tokenStorage = $tokenStorage; + } + + /** + * {@inheritdoc} + */ + public function supports(TokenInterface $token): bool + { + if (!$token instanceof OAuthToken) { + return false; + } + + return $this->resourceOwnerMap->hasResourceOwnerByName($token->getResourceOwnerName()); + } + + /** + * {@inheritdoc} + */ + public function authenticate(TokenInterface $token): ?TokenInterface + { + if (!$this->supports($token)) { + return null; + } + + // If token is authenticated, re-create it to reload user details + /** @var OAuthToken $token */ + if (!$token->isExpired() && null !== $token->getUser()) { + /** @var UserInterface $user */ + $user = $token->getUser(); + + return $this->createOAuthToken($token->getRawToken(), $token, $user); + } + + /** @var ResourceOwnerInterface $resourceOwner */ + $resourceOwner = $this->resourceOwnerMap->getResourceOwnerByName($token->getResourceOwnerName()); + + $oldToken = $token->isExpired() ? $this->refreshToken($token, $resourceOwner) : $token; + $userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken()); + + try { + $user = $this->userProvider->loadUserByOAuthUserResponse($userResponse); + } catch (OAuthAwareExceptionInterface $e) { + $e->setToken($oldToken); + $e->setResourceOwnerName($oldToken->getResourceOwnerName()); + + throw $e; + } + + if (!$user instanceof UserInterface) { + throw new AuthenticationServiceException('loadUserByOAuthUserResponse() must return a UserInterface.'); + } + + $this->userChecker->checkPreAuth($user); + $this->userChecker->checkPostAuth($user); + + return $this->createOAuthToken($oldToken->getRawToken(), $oldToken, $user); + } + + /** + * @param OAuthToken $expiredToken + */ + private function refreshToken(TokenInterface $expiredToken, ResourceOwnerInterface $resourceOwner): OAuthToken + { + if (!$expiredToken->getRefreshToken()) { + return $expiredToken; + } + + /** @var UserInterface $user */ + $user = $expiredToken->getUser(); + + $token = $this->createOAuthToken( + $resourceOwner->refreshAccessToken($expiredToken->getRefreshToken()), + $expiredToken, + $user + ); + + $this->tokenStorage->setToken($token); + + return $token; + } + + /** + * @template T of OAuthToken + * + * @param string|array $data + * @param T $oldToken + * + * @returns T + */ + private function createOAuthToken( + $data, + OAuthToken $oldToken, + ?UserInterface $user + ): OAuthToken { + $tokenClass = $oldToken::class; + if (null !== $user) { + $token = new $tokenClass($data, $user->getRoles()); + $token->setUser($user); + } else { + $token = new $tokenClass($data); + } + $token->setResourceOwnerName($oldToken->getResourceOwnerName()); + $token->setCreatedAt($oldToken->isExpired() ? time() : $oldToken->getCreatedAt()); + + // required for compatibility with Symfony 5.4 + if (method_exists($token, 'setAuthenticated')) { + $token->setAuthenticated(true, false); + } + + // Don't use old data if newer was already set + if (!$token->getRefreshToken()) { + $token->setRefreshToken($oldToken->getRefreshToken()); + } + + $token->setAttributes($oldToken->getAttributes()); + + $token->copyPersistentDataFrom($oldToken); + + return $token; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Token/AbstractOAuthToken.php b/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Token/AbstractOAuthToken.php new file mode 100644 index 0000000..20fefd0 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Token/AbstractOAuthToken.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; + +/** + * OAuthToken. + * + * @author Geoffrey Bachelet + * @author Alexander + * @author Joseph Bielawski + */ +abstract class AbstractOAuthToken extends AbstractToken +{ + private string $accessToken; + private array $rawToken; + private ?int $expiresIn = null; + private ?int $createdAt = null; + private ?string $resourceOwnerName = null; + private ?string $tokenSecret = null; + private ?string $refreshToken = null; + + /** + * @param string|array $accessToken The OAuth access token + * @param array $roles Roles for the token + */ + public function __construct($accessToken, array $roles = []) + { + parent::__construct($roles); + + $this->setRawToken($accessToken); + + // required for compatibility with Symfony 5.4 + if (method_exists($this, 'setAuthenticated')) { + $this->setAuthenticated(\count($roles) > 0, false); + } + } + + public function __serialize(): array + { + return [ + $this->accessToken, + $this->rawToken, + $this->refreshToken, + $this->expiresIn, + $this->createdAt, + $this->resourceOwnerName, + parent::__serialize(), + ]; + } + + public function __unserialize(array $data): void + { + // add a few extra elements in the array to ensure that we have enough keys when un-serializing + // older data which does not include all properties. + $data = array_merge($data, array_fill(0, 4, null)); + + [ + $this->accessToken, + $this->rawToken, + $this->refreshToken, + $this->expiresIn, + $this->createdAt, + $this->resourceOwnerName, + $parent] = $data; + + if (!$this->tokenSecret && isset($this->rawToken['oauth_token_secret'])) { + $this->tokenSecret = $this->rawToken['oauth_token_secret']; + } + + parent::__unserialize($parent); + } + + public function copyPersistentDataFrom(self $token): void + { + } + + /** + * @return mixed|void + */ + public function getCredentials() + { + return ''; + } + + /** + * @param string $accessToken The OAuth access token + */ + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + } + + /** + * @return string + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * @param array|string $token The OAuth token + * + * @throws \InvalidArgumentException + */ + public function setRawToken($token) + { + if (\is_array($token)) { + if (isset($token['access_token'])) { + $this->accessToken = $token['access_token']; + } elseif (isset($token['oauth_token'])) { + $this->accessToken = $token['oauth_token']; + } else { + throw new \InvalidArgumentException('Access token was not found.'); + } + + if (isset($token['refresh_token'])) { + $this->refreshToken = $token['refresh_token']; + } + + if (isset($token['expires_in'])) { + $this->setExpiresIn($token['expires_in']); + } elseif (isset($token['oauth_expires_in'])) { + $this->setExpiresIn($token['oauth_expires_in']); + } elseif (isset($token['expires'])) { + // Facebook unfortunately breaks the spec by using 'expires' instead of 'expires_in' + $this->setExpiresIn($token['expires']); + } + + if (isset($token['oauth_token_secret'])) { + $this->tokenSecret = $token['oauth_token_secret']; + } + + $this->rawToken = $token; + } else { + $this->accessToken = $token; + $this->rawToken = ['access_token' => $token]; + } + } + + /** + * @return array + */ + public function getRawToken() + { + return $this->rawToken; + } + + /** + * @param string $refreshToken The OAuth refresh token + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + } + + /** + * @return string + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @param int $expiresIn The duration in seconds of the access token lifetime + */ + public function setExpiresIn($expiresIn) + { + $this->createdAt = time(); + $this->expiresIn = $expiresIn; + } + + /** + * @return int + */ + public function getExpiresIn() + { + return $this->expiresIn; + } + + /** + * @param int $createdAt The token creation date in seconds + */ + public function setCreatedAt($createdAt) + { + $this->createdAt = $createdAt; + } + + /** + * @return int + */ + public function getCreatedAt() + { + return $this->createdAt; + } + + /** + * @return int|null + */ + public function getExpiresAt() + { + if (null === $this->expiresIn) { + return null; + } + + return $this->createdAt + $this->expiresIn; + } + + /** + * @param string $tokenSecret + */ + public function setTokenSecret($tokenSecret) + { + $this->tokenSecret = $tokenSecret; + } + + /** + * @return string|null + */ + public function getTokenSecret() + { + return $this->tokenSecret; + } + + /** + * Returns if the `access_token` is expired. + * + * @return bool true if the `access_token` is expired + */ + public function isExpired() + { + if (null === $this->expiresIn) { + return false; + } + + return ($this->createdAt + $this->expiresIn - time()) < 30; + } + + /** + * Get the resource owner name. + * + * @return string|null + */ + public function getResourceOwnerName() + { + return $this->resourceOwnerName; + } + + /** + * Set the resource owner name. + * + * @param string $resourceOwnerName + */ + public function setResourceOwnerName($resourceOwnerName) + { + $this->resourceOwnerName = $resourceOwnerName; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Token/OAuthToken.php b/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Token/OAuthToken.php new file mode 100644 index 0000000..f3d0a76 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/Authentication/Token/OAuthToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token; + +class OAuthToken extends AbstractOAuthToken +{ +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/Exception/AccountNotLinkedException.php b/vendor/hwi/oauth-bundle/src/Security/Core/Exception/AccountNotLinkedException.php new file mode 100644 index 0000000..a1b8708 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/Exception/AccountNotLinkedException.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\Exception; + +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\AbstractOAuthToken; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; + +final class AccountNotLinkedException extends UserNotFoundException implements OAuthAwareExceptionInterface +{ + private ?string $resourceOwnerName = null; + + /** + * {@inheritdoc} + */ + public function __serialize(): array + { + return [ + $this->resourceOwnerName, + parent::__serialize(), + ]; + } + + /** + * {@inheritdoc} + */ + public function __unserialize(array $data): void + { + [ + $this->resourceOwnerName, + $parentData + ] = $data; + + parent::__unserialize($parentData); + } + + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return 'Account could not be linked correctly.'; + } + + /** + * {@inheritdoc} + */ + public function getAccessToken(): string + { + /** @var AbstractOAuthToken $token */ + $token = $this->getToken(); + + return $token->getAccessToken(); + } + + public function getRawToken(): array + { + /** @var AbstractOAuthToken $token */ + $token = $this->getToken(); + + return $token->getRawToken(); + } + + /** + * {@inheritdoc} + */ + public function getRefreshToken(): ?string + { + /** @var AbstractOAuthToken $token */ + $token = $this->getToken(); + + return $token->getRefreshToken(); + } + + /** + * {@inheritdoc} + */ + public function getExpiresIn(): ?int + { + /** @var AbstractOAuthToken $token */ + $token = $this->getToken(); + + return $token->getExpiresIn(); + } + + /** + * {@inheritdoc} + */ + public function getTokenSecret(): ?string + { + /** @var AbstractOAuthToken $token */ + $token = $this->getToken(); + + return $token->getTokenSecret(); + } + + /** + * {@inheritdoc} + */ + public function getResourceOwnerName(): ?string + { + return $this->resourceOwnerName; + } + + /** + * {@inheritdoc} + */ + public function setResourceOwnerName($resourceOwnerName): void + { + $this->resourceOwnerName = $resourceOwnerName; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/Exception/OAuthAwareExceptionInterface.php b/vendor/hwi/oauth-bundle/src/Security/Core/Exception/OAuthAwareExceptionInterface.php new file mode 100644 index 0000000..55f6d09 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/Exception/OAuthAwareExceptionInterface.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\Exception; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * OAuthAwareExceptionInterface. + * + * @author Geoffrey Bachelet + * @author Alexander + */ +interface OAuthAwareExceptionInterface +{ + /** + * Get the access token information. + * + * @return string + */ + public function getAccessToken(); + + /** + * Get the raw version of received token. + * + * @return array + */ + public function getRawToken(); + + /** + * Get the refresh token information. + * + * @return string|null + */ + public function getRefreshToken(); + + /** + * Get the info when token will expire. + * + * @return int|null + */ + public function getExpiresIn(); + + /** + * Get the oauth secret token. + * + * @return string|null + */ + public function getTokenSecret(); + + /** + * Set the token. + */ + public function setToken(TokenInterface $token); + + /** + * Set the name of the resource owner responsible for the oauth authentication. + * + * @param string $resourceOwnerName + */ + public function setResourceOwnerName($resourceOwnerName); + + /** + * Get the name of resource owner. + * + * @return string + */ + public function getResourceOwnerName(); +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/User/EntityUserProvider.php b/vendor/hwi/oauth-bundle/src/Security/Core/User/EntityUserProvider.php new file mode 100644 index 0000000..33b5882 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/User/EntityUserProvider.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\User; + +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; +use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface; +use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * User provider for the ORM that loads users given a mapping between resource + * owner names and the properties of the entities. + * + * @author Alexander + */ +final class EntityUserProvider implements UserProviderInterface, OAuthAwareUserProviderInterface +{ + private ObjectManager $em; + private string $class; + private ?ObjectRepository $repository = null; + + /** + * @var array + */ + private array $properties = [ + 'identifier' => 'id', + ]; + + /** + * @param string $class User entity class to load + * @param array $properties Mapping of resource owners to properties + */ + public function __construct(ManagerRegistry $registry, string $class, array $properties, ?string $managerName = null) + { + $this->em = $registry->getManager($managerName); + $this->class = $class; + $this->properties = array_merge($this->properties, $properties); + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + $user = $this->findUser(['username' => $identifier]); + + if (!$user) { + throw $this->createUserNotFoundException($identifier, sprintf("User '%s' not found.", $identifier)); + } + + return $user; + } + + /** + * Symfony <5.4 BC layer. + * + * @param string $username + * + * @return UserInterface + */ + public function loadUserByUsername($username) + { + return $this->loadUserByIdentifier($username); + } + + public function loadUserByOAuthUserResponse(UserResponseInterface $response): ?UserInterface + { + $resourceOwnerName = $response->getResourceOwner()->getName(); + + if (!isset($this->properties[$resourceOwnerName])) { + throw new \RuntimeException(sprintf("No property defined for entity for resource owner '%s'.", $resourceOwnerName)); + } + + $username = method_exists($response, 'getUserIdentifier') ? $response->getUserIdentifier() : $response->getUsername(); + if (null === $user = $this->findUser([$this->properties[$resourceOwnerName] => $username])) { + throw $this->createUserNotFoundException($username, sprintf("User '%s' not found.", $username)); + } + + return $user; + } + + public function refreshUser(UserInterface $user): UserInterface + { + $accessor = PropertyAccess::createPropertyAccessor(); + $identifier = $this->properties['identifier']; + if (!$accessor->isReadable($user, $identifier) || !$this->supportsClass($user::class)) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $userId = $accessor->getValue($user, $identifier); + + $username = $user->getUserIdentifier(); + + if (null === $user = $this->findUser([$identifier => $userId])) { + throw $this->createUserNotFoundException($username, sprintf('User with ID "%d" could not be reloaded.', $userId)); + } + + return $user; + } + + public function supportsClass($class): bool + { + return $class === $this->class || is_subclass_of($class, $this->class); + } + + private function findUser(array $criteria): ?UserInterface + { + if (null === $this->repository) { + $this->repository = $this->em->getRepository($this->class); + } + + return $this->repository->findOneBy($criteria); + } + + private function createUserNotFoundException(string $username, string $message): UserNotFoundException + { + $exception = new AccountNotLinkedException($message); + $exception->setUserIdentifier($username); + + return $exception; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthAwareUserProviderInterface.php b/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthAwareUserProviderInterface.php new file mode 100644 index 0000000..759722c --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthAwareUserProviderInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\User; + +use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Represents a class that loads UserInterface objects for a given oauth response. + * + * @author Alexander + */ +interface OAuthAwareUserProviderInterface +{ + /** + * Loads the user by a given UserResponseInterface object. + * + * @return UserInterface + * + * @throws UserNotFoundException if the user is not found + */ + public function loadUserByOAuthUserResponse(UserResponseInterface $response); +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthUser.php b/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthUser.php new file mode 100644 index 0000000..cbae2e2 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthUser.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\User; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * @author Geoffrey Bachelet + */ +final class OAuthUser implements UserInterface +{ + private string $username; + + public function __construct(string $username) + { + $this->username = $username; + } + + public function getUserIdentifier(): string + { + return $this->username; + } + + /** + * @return array + */ + public function getRoles(): array + { + return ['ROLE_USER', 'ROLE_OAUTH_USER']; + } + + public function getPassword(): ?string + { + return null; + } + + public function getSalt(): ?string + { + return null; + } + + public function getUsername(): string + { + return $this->getUserIdentifier(); + } + + public function eraseCredentials(): void + { + } + + public function equals(UserInterface $user): bool + { + return $user->getUserIdentifier() === $this->username; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthUserProvider.php b/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthUserProvider.php new file mode 100644 index 0000000..8ae6ca1 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Core/User/OAuthUserProvider.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Core\User; + +use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * @author Geoffrey Bachelet + */ +final class OAuthUserProvider implements UserProviderInterface, OAuthAwareUserProviderInterface +{ + public function loadUserByIdentifier(string $identifier): UserInterface + { + return new OAuthUser($identifier); + } + + /** + * Symfony <5.4 BC layer. + * + * @param string $username + * + * @return UserInterface + */ + public function loadUserByUsername($username) + { + return $this->loadUserByIdentifier($username); + } + + public function loadUserByOAuthUserResponse(UserResponseInterface $response): UserInterface + { + return $this->loadUserByIdentifier($response->getNickname() ?: $response->getUserIdentifier()); + } + + public function refreshUser(UserInterface $user): UserInterface + { + if (!$this->supportsClass($user::class)) { + throw new UnsupportedUserException(sprintf('Unsupported user class "%s"', $user::class)); + } + + // @phpstan-ignore-next-line Symfony <5.4 BC layer + $username = method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); + + return $this->loadUserByUsername($username); + } + + public function supportsClass($class): bool + { + return 'HWI\\Bundle\\OAuthBundle\\Security\\Core\\User\\OAuthUser' === $class; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Helper/NonceGenerator.php b/vendor/hwi/oauth-bundle/src/Security/Helper/NonceGenerator.php new file mode 100644 index 0000000..c655f82 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Helper/NonceGenerator.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Helper; + +final class NonceGenerator +{ + private function __construct() + { + } + + public static function generate(): string + { + return md5(microtime(true).uniqid('', true)); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/Authentication/AuthenticationFailureHandler.php b/vendor/hwi/oauth-bundle/src/Security/Http/Authentication/AuthenticationFailureHandler.php new file mode 100644 index 0000000..73ea0e8 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/Authentication/AuthenticationFailureHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\Authentication; + +use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\ParameterBagUtils; + +final class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface +{ + private array $defaultOptions = [ + 'failure_path' => 'hwi_oauth_connect_registration', + 'failure_forward' => false, + 'login_path' => '/login', + 'failure_path_parameter' => '_failure_path', + ]; + + public function __construct( + private readonly RequestStack $requestStack, + private readonly RouterInterface $router, + private readonly bool $connect, + ) { + } + + public function setOptions(array $options): void + { + $this->defaultOptions = array_merge($this->defaultOptions, $options); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + $options = $this->defaultOptions; + + $failureUrl = ParameterBagUtils::getRequestParameterValue($request, $options['failure_path_parameter']); + + if (\is_string($failureUrl) && (str_starts_with($failureUrl, '/') || str_starts_with($failureUrl, 'http'))) { + $options['failure_path'] = $failureUrl; + } + + $options['failure_path'] ??= $options['login_path']; + + $error = $exception->getPrevious(); + + if ($this->connect && $error instanceof AccountNotLinkedException) { + $key = time(); + $session = $request->hasSession() ? $request->getSession() : $this->getSession(); + if ($session) { + if (!$session->isStarted()) { + $session->start(); + } + + $session->set('_hwi_oauth.registration_error.'.$key, $error); + } + + if ('/' === $options['failure_path'][0]) { + $failurePath = $request->getUriForPath($options['failure_path'][0]); + } else { + $failurePath = $this->router->generate($options['failure_path'], ['key' => $key]); + } + + return new RedirectResponse($failurePath); + } + + if ($error instanceof AuthenticationException) { + $error = $error->getMessageKey(); + } else { + $error = $exception->getMessageKey(); + } + + if ('/' === $options['login_path'][0]) { + $loginPath = $request->getUriForPath($options['login_path'][0]); + } else { + $loginPath = $this->router->generate($options['login_path'], ['error' => $error]); + } + + return new RedirectResponse($loginPath); + } + + private function getSession(): ?SessionInterface + { + if (method_exists($this->requestStack, 'getSession')) { + return $this->requestStack->getSession(); + } + + if ((null !== $request = $this->requestStack->getCurrentRequest()) && $request->hasSession()) { + return $request->getSession(); + } + + return null; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/Authenticator/OAuthAuthenticator.php b/vendor/hwi/oauth-bundle/src/Security/Http/Authenticator/OAuthAuthenticator.php new file mode 100644 index 0000000..8a566bd --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/Authenticator/OAuthAuthenticator.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\Authenticator; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface; +use HWI\Bundle\OAuthBundle\OAuth\State\State; +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; +use HWI\Bundle\OAuthBundle\Security\Core\Exception\OAuthAwareExceptionInterface; +use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface; +use HWI\Bundle\OAuthBundle\Security\Http\Authenticator\Passport\Badge\OAuthTokenBadge; +use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; +use Symfony\Component\Security\Core\Exception\LazyResponseException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\HttpUtils; + +/** + * @author Vadim Borodavko + */ +final class OAuthAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface +{ + /** + * @param string[] $checkPaths + */ + public function __construct( + private readonly HttpUtils $httpUtils, + private readonly OAuthAwareUserProviderInterface $userProvider, + private readonly ResourceOwnerMapInterface $resourceOwnerMap, + private readonly array $checkPaths, + private readonly AuthenticationSuccessHandlerInterface $successHandler, + private readonly AuthenticationFailureHandlerInterface $failureHandler, + private readonly HttpKernelInterface $kernel, + private readonly array $options + ) { + } + + public function supports(Request $request): bool + { + foreach ($this->checkPaths as $checkPath) { + if ($this->httpUtils->checkRequestPath($request, $checkPath)) { + return true; + } + } + + return false; + } + + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + if ($this->options['use_forward'] ?? false) { + $subRequest = $this->httpUtils->createRequest($request, $this->options['login_path']); + + $iterator = $request->query->getIterator(); + $subRequest->query->add(iterator_to_array($iterator)); + + $response = $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + if (200 === $response->getStatusCode()) { + $response->headers->set('X-Status-Code', '401'); + } + + return $response; + } + + return new RedirectResponse($this->httpUtils->generateUri($request, $this->options['login_path'])); + } + + /** + * @throws AuthenticationException + * @throws LazyResponseException + */ + public function authenticate(Request $request): Passport + { + [$resourceOwner, $checkPath] = $this->resourceOwnerMap->getResourceOwnerByRequest($request); + + if (!$resourceOwner instanceof ResourceOwnerInterface) { + throw new AuthenticationException('No resource owner match the request.'); + } + + if (!$resourceOwner->handles($request)) { + throw new AuthenticationException('No oauth code in the request.'); + } + + // If resource owner supports only one url authentication, call redirect + if ($request->query->has('authenticated') && $resourceOwner->getOption('auth_with_one_url')) { + $request->attributes->set('service', $resourceOwner->getName()); + + throw new LazyResponseException(new RedirectResponse(sprintf('%s?code=%s&authenticated=true', $this->httpUtils->generateUri($request, 'hwi_oauth_connect_service'), $request->query->get('code')))); + } + + $resourceOwner->isCsrfTokenValid( + $this->extractCsrfTokenFromState($request->get('state')) + ); + + $accessToken = $resourceOwner->getAccessToken( + $request, + $this->httpUtils->createRequest($request, $checkPath)->getUri() + ); + + $token = new OAuthToken($accessToken); + $token->setResourceOwnerName($resourceOwner->getName()); + $token = $this->refreshToken($token); + + $user = $token->getUser(); + + $userBadge = class_exists(UserBadge::class) + ? new UserBadge( + $user ? method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername() : null, + static function () use ($user) { return $user; } + ) + : $user; + + return new SelfValidatingPassport($userBadge, [new RememberMeBadge(), new OAuthTokenBadge($token)]); + } + + /** + * This function can be used for refreshing an expired token + * or for custom "password grant" authenticator, if site owner also owns oauth instance. + * + * @template T of OAuthToken + * + * @param T $token + * + * @return T + */ + public function refreshToken(OAuthToken $token): OAuthToken + { + if (!$token->isExpired() && null !== $token->getUser()) { + return $this->recreateToken($token, $token->getUser()); + } + + $resourceOwner = $this->resourceOwnerMap->getResourceOwnerByName($token->getResourceOwnerName()); + if (!$resourceOwner) { + throw new AuthenticationServiceException('Unknown resource owner set on token: '.$token->getResourceOwnerName()); + } + + if ($token->isExpired()) { + $expiredToken = $token; + if ($refreshToken = $expiredToken->getRefreshToken()) { + $tokenClass = $expiredToken::class; + $token = new $tokenClass($resourceOwner->refreshAccessToken($refreshToken)); + $token->setResourceOwnerName($expiredToken->getResourceOwnerName()); + if (!$token->getRefreshToken()) { + $token->setRefreshToken($expiredToken->getRefreshToken()); + } + $token->copyPersistentDataFrom($expiredToken); + } else { + // if you cannot refresh token, you do not need to make user_info request to oauth-resource + if (null !== $expiredToken->getUser()) { + return $expiredToken; + } + } + unset($expiredToken); + } + + $userResponse = $resourceOwner->getUserInformation($token->getRawToken()); + + try { + $user = $this->userProvider->loadUserByOAuthUserResponse($userResponse); + } catch (OAuthAwareExceptionInterface $e) { + $e->setToken($token); + $e->setResourceOwnerName($token->getResourceOwnerName()); + + throw $e; + } + + if (!$user instanceof UserInterface) { + throw new AuthenticationServiceException('loadUserByOAuthUserResponse() must return a UserInterface.'); + } + + return $this->recreateToken($token, $user); + } + + /** + * @template T of OAuthToken + * + * @param T $token + * @param ?UserInterface $user + * + * @return T + */ + public function recreateToken(OAuthToken $token, ?UserInterface $user = null): OAuthToken + { + $user = $user instanceof UserInterface ? $user : $token->getUser(); + + $tokenClass = $token::class; + if ($user) { + $newToken = new $tokenClass( + $token->getRawToken(), + method_exists($user, 'getRoles') ? $user->getRoles() : [] + ); + $newToken->setUser($user); + } else { + $newToken = new $tokenClass($token->getRawToken()); + } + + $newToken->setResourceOwnerName($token->getResourceOwnerName()); + $newToken->setRefreshToken($token->getRefreshToken()); + $newToken->setCreatedAt($token->getCreatedAt()); + $newToken->setTokenSecret($token->getTokenSecret()); + $newToken->setAttributes($token->getAttributes()); + + // required for compatibility with Symfony 5.4 + if (method_exists($newToken, 'setAuthenticated')) { + $newToken->setAuthenticated((bool) $user, false); + } + + $newToken->copyPersistentDataFrom($token); + + return $newToken; + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return $this->createAuthenticatedToken($passport, $firewallName); + } + + /** + * @param Passport $passport + */ + public function createAuthenticatedToken($passport, string $firewallName): TokenInterface + { + $badge = $passport->getBadge(OAuthTokenBadge::class); + if ($badge instanceof OAuthTokenBadge) { + return $badge->getToken(); + } + + throw new \LogicException(sprintf('Given passport must contain instance of "%s".', OAuthTokenBadge::class)); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return $this->successHandler->onAuthenticationSuccess($request, $token); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + return $this->failureHandler->onAuthenticationFailure($request, $exception); + } + + public function isInteractive(): bool + { + return true; + } + + private function extractCsrfTokenFromState(?string $stateParameter): ?string + { + $state = new State($stateParameter); + + return $state->getCsrfToken() ?: $stateParameter; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/Authenticator/Passport/Badge/OAuthTokenBadge.php b/vendor/hwi/oauth-bundle/src/Security/Http/Authenticator/Passport/Badge/OAuthTokenBadge.php new file mode 100644 index 0000000..e818759 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/Authenticator/Passport/Badge/OAuthTokenBadge.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\Authenticator\Passport\Badge; + +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; + +final class OAuthTokenBadge implements BadgeInterface +{ + private OAuthToken $token; + + public function __construct(OAuthToken $token) + { + $this->token = $token; + } + + public function isResolved(): bool + { + return true; + } + + public function getToken(): OAuthToken + { + return $this->token; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/EntryPoint/OAuthEntryPoint.php b/vendor/hwi/oauth-bundle/src/Security/Http/EntryPoint/OAuthEntryPoint.php new file mode 100644 index 0000000..296fe5e --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/EntryPoint/OAuthEntryPoint.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\EntryPoint; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\HttpUtils; + +/** + * OAuthEntryPoint redirects the user to the appropriate login url if there is + * only one resource owner. Otherwise the user will be redirected to a login + * page. + * + * @author Geoffrey Bachelet + * @author Alexander + */ +final class OAuthEntryPoint implements AuthenticationEntryPointInterface +{ + public function __construct( + private readonly HttpKernelInterface $kernel, + private readonly HttpUtils $httpUtils, + private readonly string $loginPath, + private readonly bool $useForward = false + ) { + } + + /** + * {@inheritdoc} + */ + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + if ($this->useForward) { + $subRequest = $this->httpUtils->createRequest($request, $this->loginPath); + + /** @var \ArrayIterator $iterator */ + $iterator = $request->query->getIterator(); + $subRequest->query->add($iterator->getArrayCopy()); + + $response = $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + if (200 === $response->getStatusCode()) { + $response->headers->set('X-Status-Code', '401'); + } + + return $response; + } + + return $this->httpUtils->createRedirectResponse($request, $this->loginPath); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/AbstractRefreshAccessTokenListener.php b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/AbstractRefreshAccessTokenListener.php new file mode 100644 index 0000000..8881a27 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/AbstractRefreshAccessTokenListener.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner; +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; +use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Firewall\AbstractListener; + +abstract class AbstractRefreshAccessTokenListener extends AbstractListener +{ + protected TokenStorageInterface $tokenStorage; + + protected ResourceOwnerMap $resourceOwnerMap; + + protected bool $enabled = false; + + public function setTokenStorage(TokenStorageInterface $tokenStorage) + { + $this->tokenStorage = $tokenStorage; + } + + public function setResourceOwnerMap(ResourceOwnerMap $resourceOwnerMap) + { + $this->resourceOwnerMap = $resourceOwnerMap; + } + + public function enable(bool $enabled = true) + { + $this->enabled = $enabled; + } + + public function supports(Request $request): ?bool + { + return $this->enabled; + } + + public function authenticate(RequestEvent $event): void + { + if (null === $token = $this->tokenStorage->getToken()) { + return; + } + + if (!$token instanceof OAuthToken) { + return; + } + + if (false === $token->isExpired()) { + return; + } + + $resourceOwner = $this->resourceOwnerMap->getResourceOwnerByName($token->getResourceOwnerName()); + + if (!$resourceOwner instanceof GenericOAuth2ResourceOwner) { + return; + } + + if (!$resourceOwner->shouldRefreshOnExpire()) { + return; + } + + // here not clear what were better, + // * silent stop or + // * a logger with a notice or + // * force user to logout + if (!$token->getRefreshToken()) { + return; + } + + try { + $newToken = $this->refreshToken($token); + } catch (AuthenticationException $e) { + $newToken = null; + } + + $this->tokenStorage->setToken($newToken); + } + + /** + * @template T of OAuthToken + * + * @param T $token + * + * @return T + */ + abstract protected function refreshToken(OAuthToken $token): OAuthToken; +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/OAuthListener.php b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/OAuthListener.php new file mode 100644 index 0000000..e822b91 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/OAuthListener.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface; +use HWI\Bundle\OAuthBundle\OAuth\State\State; +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; +use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; + +/** + * @author Geoffrey Bachelet + * @author Alexander + * + * @internal + */ +class OAuthListener extends AbstractAuthenticationListener +{ + private ResourceOwnerMapInterface $resourceOwnerMap; + + /** + * @var array + */ + private array $checkPaths; + + public function setResourceOwnerMap(ResourceOwnerMapInterface $resourceOwnerMap): void + { + $this->resourceOwnerMap = $resourceOwnerMap; + } + + public function setCheckPaths(array $checkPaths): void + { + $this->checkPaths = $checkPaths; + } + + /** + * {@inheritdoc} + */ + public function requiresAuthentication(Request $request): bool + { + // Check if the route matches one of the check paths + foreach ($this->checkPaths as $checkPath) { + if ($this->httpUtils->checkRequestPath($request, $checkPath)) { + return true; + } + } + + return false; + } + + /** + * @return TokenInterface|Response|null + */ + protected function attemptAuthentication(Request $request) + { + /* @var ResourceOwnerInterface $resourceOwner */ + [$resourceOwner, $checkPath] = $this->resourceOwnerMap->getResourceOwnerByRequest($request); + + if (!$resourceOwner) { + throw new AuthenticationException('No resource owner match the request.'); + } + + if (!$resourceOwner->handles($request)) { + throw new AuthenticationException('No oauth code in the request.'); + } + + // If resource owner supports only one url authentication, call redirect + if ($request->query->has('authenticated') && $resourceOwner->getOption('auth_with_one_url')) { + $request->attributes->set('service', $resourceOwner->getName()); + + return new RedirectResponse(sprintf('%s?code=%s&authenticated=true', $this->httpUtils->generateUri($request, 'hwi_oauth_connect_service'), $request->query->get('code'))); + } + + $resourceOwner->isCsrfTokenValid( + $this->extractCsrfTokenFromState($request->get('state')) + ); + + $accessToken = $resourceOwner->getAccessToken( + $request, + $this->httpUtils->createRequest($request, $checkPath)->getUri() + ); + + $token = new OAuthToken($accessToken); + $token->setResourceOwnerName($resourceOwner->getName()); + + return $this->authenticationManager->authenticate($token); + } + + private function extractCsrfTokenFromState(?string $stateParameter): ?string + { + $state = new State($stateParameter); + + return $state->getCsrfToken() ?: $stateParameter; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/RefreshAccessTokenListener.php b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/RefreshAccessTokenListener.php new file mode 100644 index 0000000..28a8ec6 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/RefreshAccessTokenListener.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall; + +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; +use HWI\Bundle\OAuthBundle\Security\Http\Authenticator\OAuthAuthenticator; + +class RefreshAccessTokenListener extends AbstractRefreshAccessTokenListener +{ + private OAuthAuthenticator $oAuthAuthenticator; + + public function __construct( + OAuthAuthenticator $oAuthAuthenticator + ) { + $this->oAuthAuthenticator = $oAuthAuthenticator; + } + + /** + * @template T of OAuthToken + * + * @param T $token + * + * @return T + */ + protected function refreshToken(OAuthToken $token): OAuthToken + { + return $this->oAuthAuthenticator->refreshToken($token); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/RefreshAccessTokenListenerOld.php b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/RefreshAccessTokenListenerOld.php new file mode 100644 index 0000000..477fd38 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/Firewall/RefreshAccessTokenListenerOld.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall; + +use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; +use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; + +class RefreshAccessTokenListenerOld extends RefreshAccessTokenListener +{ + /** + * @phpstan-ignore-next-line + */ + private AuthenticationProviderInterface $oAuthProvider; + + public function __construct( + /* @phpstan-ignore-next-line */ + AuthenticationProviderInterface $oAuthProvider + ) { + $this->oAuthProvider = $oAuthProvider; + } + + /** + * @template T of OAuthToken + * + * @param T $token + * + * @return T + */ + protected function refreshToken(OAuthToken $token): OAuthToken + { + // @phpstan-ignore-next-line returns TokenInterface instead of OAuthToken + return $this->oAuthProvider->authenticate($token); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMap.php b/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMap.php new file mode 100644 index 0000000..76154bd --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMap.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface; +use HWI\Bundle\OAuthBundle\OAuth\State\State; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Http\HttpUtils; + +/** + * Holds several resource owners for a firewall. Lazy + * loads the appropriate resource owner when requested. + * + * @author Alexander + */ +final class ResourceOwnerMap implements ResourceOwnerMapInterface +{ + /** + * @param array $possibleResourceOwners array with possible resource owners names + * @param array $resourceOwners array with configured resource owners + */ + public function __construct( + private readonly HttpUtils $httpUtils, + private readonly array $possibleResourceOwners, + private readonly array $resourceOwners, + private readonly ServiceLocator $locator + ) { + } + + /** + * {@inheritdoc} + */ + public function hasResourceOwnerByName(string $name): bool + { + return isset($this->resourceOwners[$name], $this->possibleResourceOwners[$name]); + } + + /** + * {@inheritdoc} + */ + public function getResourceOwnerByName(string $name): ?ResourceOwnerInterface + { + if (!$this->hasResourceOwnerByName($name)) { + return null; + } + + try { + /** @var ResourceOwnerInterface $resourceOwner */ + $resourceOwner = $this->locator->get($name); + } catch (NotFoundExceptionInterface $e) { + return null; + } + + return $resourceOwner; + } + + /** + * {@inheritdoc} + */ + public function getResourceOwnerByRequest(Request $request): ?array + { + foreach ($this->resourceOwners as $name => $checkPath) { + if ($this->httpUtils->checkRequestPath($request, $checkPath)) { + $resourceOwner = $this->getResourceOwnerByName($name); + + // save the round-tripped state to the resource owner + if (null !== $resourceOwner) { + $resourceOwner->storeState(new State($request->get('state'), false)); + } + + return [$resourceOwner, $checkPath]; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getResourceOwnerCheckPath(string $name): ?string + { + return $this->resourceOwners[$name] ?? null; + } + + /** + * {@inheritdoc} + */ + public function getResourceOwners(): array + { + return $this->resourceOwners; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMapInterface.php b/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMapInterface.php new file mode 100644 index 0000000..c8cfc3a --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMapInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Alexander + */ +interface ResourceOwnerMapInterface +{ + /** + * Check that resource owner with given name exists. + */ + public function hasResourceOwnerByName(string $name): bool; + + /** + * Gets the appropriate resource owner given the name. + */ + public function getResourceOwnerByName(string $name): ?ResourceOwnerInterface; + + /** + * Gets the appropriate resource owner for a request. + */ + public function getResourceOwnerByRequest(Request $request): ?array; + + /** + * Gets the check path for given resource name. + */ + public function getResourceOwnerCheckPath(string $name): ?string; + + /** + * Get all the resource owners. + * + * @return array + */ + public function getResourceOwners(): array; +} diff --git a/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMapLocator.php b/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMapLocator.php new file mode 100644 index 0000000..0b0ba9d --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/Http/ResourceOwnerMapLocator.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security\Http; + +/** + * Find resource owner maps based on firewall id. + */ +final class ResourceOwnerMapLocator +{ + /** + * @var array + */ + private array $resourceOwnerMaps = []; + + public function set(string $firewallName, ResourceOwnerMapInterface $resourceOwnerMap): void + { + $this->resourceOwnerMaps[$firewallName] = $resourceOwnerMap; + } + + public function has(string $firewallName): bool + { + return isset($this->resourceOwnerMaps[$firewallName]); + } + + public function get(string $firewallName): ResourceOwnerMapInterface + { + return $this->resourceOwnerMaps[$firewallName]; + } + + /** + * @return array + */ + public function getResourceOwnerMaps(): array + { + return $this->resourceOwnerMaps; + } + + /** + * @return string[] + */ + public function getFirewallNames(): array + { + return array_keys($this->resourceOwnerMaps); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/OAuthErrorHandler.php b/vendor/hwi/oauth-bundle/src/Security/OAuthErrorHandler.php new file mode 100644 index 0000000..ed10b63 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/OAuthErrorHandler.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +final class OAuthErrorHandler +{ + /** + * "translated" OAuth errors to human readable format. + * + * @var array + */ + private static array $translatedOAuthErrors = [ + 'access_denied' => 'You have refused access for this site.', + 'authorization_expired' => 'Authorization expired.', + 'bad_verification_code' => 'Bad verification code.', + 'consumer_key_rejected' => 'You have refused access for this site.', + 'incorrect_client_credentials' => 'Incorrect client credentials.', + 'invalid_assertion' => 'Invalid assertion.', + 'redirect_uri_mismatch' => 'Redirect URI mismatches configured one.', + 'unauthorized_client' => 'Unauthorized client.', + 'unknown_format' => 'Unknown format.', + ]; + + /** + * @throws AuthenticationException + */ + public static function handleOAuthError(Request $request): void + { + $error = null; + + // Try to parse content if error was not in request query + if ($request->query->has('error')) { + $content = json_decode($request->getContent(), true); + if (isset($content['error']) && \JSON_ERROR_NONE === json_last_error()) { + if (isset($content['error']['message'])) { + throw new AuthenticationException($content['error']['message']); + } + + if (isset($content['error']['code'])) { + $error = $content['error']['code']; + } elseif (isset($content['error']['error-code'])) { + $error = $content['error']['error-code']; + } else { + $error = $request->query->get('error'); + } + } + } elseif ($request->query->has('oauth_problem')) { + $error = $request->query->get('oauth_problem'); + } + + if (null !== $error) { + $error = self::$translatedOAuthErrors[$error] ?? sprintf('Unknown OAuth error: "%s".', $error); + + throw new AuthenticationException($error); + } + } +} diff --git a/vendor/hwi/oauth-bundle/src/Security/OAuthUtils.php b/vendor/hwi/oauth-bundle/src/Security/OAuthUtils.php new file mode 100644 index 0000000..28e6aea --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Security/OAuthUtils.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Security; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface; +use HWI\Bundle\OAuthBundle\OAuth\State\State; +use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Http\HttpUtils; + +/** + * @author Alexander + * @author Joseph Bielawski + * @author Francisco Facioni + */ +final class OAuthUtils +{ + public const SIGNATURE_METHOD_HMAC = 'HMAC-SHA1'; + public const SIGNATURE_METHOD_RSA = 'RSA-SHA1'; + public const SIGNATURE_METHOD_PLAINTEXT = 'PLAINTEXT'; + + /** + * @var array + */ + private array $ownerMaps = []; + + public function __construct( + private readonly HttpUtils $httpUtils, + private readonly AuthorizationCheckerInterface $authorizationChecker, + private readonly FirewallMap $firewallMap, + private readonly bool $connect, + private readonly string $grantRule + ) { + } + + public function addResourceOwnerMap($firewallName, ResourceOwnerMapInterface $ownerMap): void + { + $this->ownerMaps[$firewallName] = $ownerMap; + } + + /** + * @return string[] + */ + public function getResourceOwners(): array + { + $resourceOwners = []; + + foreach ($this->ownerMaps as $ownerMap) { + $resourceOwners = array_merge($resourceOwners, $ownerMap->getResourceOwners()); + } + + return array_keys($resourceOwners); + } + + public function getAuthorizationUrl(Request $request, string $name, ?string $redirectUrl = null, array $extraParameters = []): string + { + $resourceOwner = $this->getResourceOwner($name); + + if (null === $redirectUrl) { + if (!$this->connect || !$this->authorizationChecker->isGranted($this->grantRule)) { + $firewallName = $this->getCurrentFirewallName($request); + $redirectUrl = $this->httpUtils->generateUri($request, $this->getResourceOwnerCheckPath($name, $firewallName)); + } else { + $redirectUrl = $this->getServiceAuthUrl($request, $resourceOwner); + } + } + + if ($request->query->has('state')) { + $this->addQueryParameterToState($request->query->get('state'), $resourceOwner); + } + + return $resourceOwner->getAuthorizationUrl($redirectUrl, $extraParameters); + } + + public function getServiceAuthUrl(Request $request, ResourceOwnerInterface $resourceOwner): string + { + if ($resourceOwner->getOption('auth_with_one_url')) { + $firewallName = $this->getCurrentFirewallName($request); + + return $this->httpUtils->generateUri($request, $this->getResourceOwnerCheckPath($resourceOwner->getName(), $firewallName)); + } + + $request->attributes->set('service', $resourceOwner->getName()); + if ($request->query->has('state')) { + $this->addQueryParameterToState($request->query->get('state'), $resourceOwner); + } + + return $this->httpUtils->generateUri($request, 'hwi_oauth_connect_service'); + } + + public function getLoginUrl(Request $request, string $name): string + { + // Just to check that this resource owner exists + $this->getResourceOwner($name); + + $request->attributes->set('service', $name); + + $url = $this->httpUtils->generateUri($request, 'hwi_oauth_service_redirect'); + + if ($request->query->has('state')) { + $data = ['state' => $request->query->all()['state']]; + $url .= '?'.http_build_query($data); + } + + return $url; + } + + /** + * Sign the request parameters. + * + * @param string $method Request method + * @param string $url Request url + * @param array $parameters Parameters for the request + * @param string $clientSecret Client secret to use as key part of signing + * @param string $tokenSecret Optional token secret to use with signing + * @param string $signatureMethod Optional signature method used to sign token + * + * @throws \RuntimeException + */ + public static function signRequest( + string $method, + string $url, + array $parameters, + string $clientSecret, + string $tokenSecret = '', + string $signatureMethod = self::SIGNATURE_METHOD_HMAC + ): string { + // Validate required parameters + foreach (['oauth_consumer_key', 'oauth_timestamp', 'oauth_nonce', 'oauth_version', 'oauth_signature_method'] as $parameter) { + if (!isset($parameters[$parameter])) { + throw new \RuntimeException(sprintf('Parameter "%s" must be set.', $parameter)); + } + } + + // Remove oauth_signature if present + // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") + if (isset($parameters['oauth_signature'])) { + unset($parameters['oauth_signature']); + } + + // Parse & add query params as base string parameters if they exists + $url = parse_url($url); + if (isset($url['query'])) { + parse_str($url['query'], $queryParams); + $parameters += $queryParams; + } + + // Remove default ports + // Ref: Spec: 9.1.2 + $explicitPort = $url['port'] ?? null; + if (('https' === $url['scheme'] && 443 === $explicitPort) || ('http' === $url['scheme'] && 80 === $explicitPort)) { + $explicitPort = null; + } + + // Remove query params from URL + // Ref: Spec: 9.1.2 + $url = sprintf('%s://%s%s%s', $url['scheme'], $url['host'], $explicitPort ? ':'.$explicitPort : '', $url['path'] ?? ''); + + // Parameters are sorted by name, using lexicographical byte value ordering. + // Ref: Spec: 9.1.1 (1) + uksort($parameters, 'strcmp'); + + // http_build_query should use RFC3986 + $parts = [ + // HTTP method name must be uppercase + // Ref: Spec: 9.1.3 (1) + strtoupper($method), + rawurlencode($url), + rawurlencode(str_replace(['%7E', '+'], ['~', '%20'], http_build_query($parameters, '', '&'))), + ]; + + $baseString = implode('&', $parts); + + switch ($signatureMethod) { + case self::SIGNATURE_METHOD_HMAC: + $keyParts = [ + rawurlencode($clientSecret), + rawurlencode($tokenSecret), + ]; + + $signature = hash_hmac('sha1', $baseString, implode('&', $keyParts), true); + break; + + case self::SIGNATURE_METHOD_RSA: + if (!\function_exists('openssl_pkey_get_private')) { + throw new \RuntimeException('RSA-SHA1 signature method requires the OpenSSL extension.'); + } + + if (str_starts_with($clientSecret, '-----BEGIN')) { + $privateKey = openssl_pkey_get_private($clientSecret, $tokenSecret); + } else { + $privateKey = openssl_pkey_get_private(file_get_contents($clientSecret), $tokenSecret); + } + + $signature = false; + + openssl_sign($baseString, $signature, $privateKey); + + break; + + case self::SIGNATURE_METHOD_PLAINTEXT: + $signature = $baseString; + break; + + default: + throw new \RuntimeException(sprintf('Unknown signature method selected %s.', $signatureMethod)); + } + + return base64_encode($signature); + } + + private function getResourceOwner(string $name): ResourceOwnerInterface + { + foreach ($this->ownerMaps as $ownerMap) { + $resourceOwner = $ownerMap->getResourceOwnerByName($name); + if ($resourceOwner instanceof ResourceOwnerInterface) { + return $resourceOwner; + } + } + + throw new \RuntimeException(sprintf("No resource owner with name '%s'.", $name)); + } + + private function getCurrentFirewallName(Request $request): ?string + { + $firewallConfig = $this->firewallMap->getFirewallConfig($request); + + return null === $firewallConfig ? null : $firewallConfig->getName(); + } + + private function getResourceOwnerCheckPath(string $name, ?string $firewallName = null): ?string + { + $resourceOwnerMaps = $this->ownerMaps; + + // if 'oauth' for firewall defined search only in corresponding resourceOwnerMao + if (null !== $firewallName && isset($this->ownerMaps[$firewallName])) { + $resourceOwnerMaps = [$this->ownerMaps[$firewallName]]; + } + + // otherwise get the latest potential checked (basically from main firewall) + $resourceOwnerCheckPath = null; + foreach ($resourceOwnerMaps as $ownerMap) { + if ($potentialResourceOwnerCheckPath = $ownerMap->getResourceOwnerCheckPath($name)) { + $resourceOwnerCheckPath = $potentialResourceOwnerCheckPath; + } + } + + return $resourceOwnerCheckPath; + } + + /** + * @param string|array|null $queryParameter The query parameter to parse and add to the State + * @param ResourceOwnerInterface $resourceOwner The resource owner holding the state to be added to + */ + private function addQueryParameterToState(array|string|null $queryParameter, ResourceOwnerInterface $resourceOwner): void + { + if (null === $queryParameter) { + return; + } + + $additionalState = new State($queryParameter); + foreach ($additionalState->getAll() as $key => $value) { + $resourceOwner->addStateParameter($key, $value); + } + } +} diff --git a/vendor/hwi/oauth-bundle/src/Test/Fixtures/CustomUserResponse.php b/vendor/hwi/oauth-bundle/src/Test/Fixtures/CustomUserResponse.php new file mode 100644 index 0000000..84695e3 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Test/Fixtures/CustomUserResponse.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Test\Fixtures; + +use HWI\Bundle\OAuthBundle\OAuth\Response\AbstractUserResponse; + +/** + * @author Alexander + */ +final class CustomUserResponse extends AbstractUserResponse +{ + public function getUserIdentifier(): string + { + return 'foo666'; + } + + public function getUsername(): string + { + return 'foo666'; + } + + public function getNickname(): string + { + return 'foo'; + } + + public function getFirstName(): string + { + return 'foo'; + } + + public function getLastName(): string + { + return 'BAR'; + } + + public function getRealName(): string + { + return 'foo'; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Test/Fixtures/ResourceOwner/OAuth1ResourceOwnerStub.php b/vendor/hwi/oauth-bundle/src/Test/Fixtures/ResourceOwner/OAuth1ResourceOwnerStub.php new file mode 100644 index 0000000..9bedf4d --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Test/Fixtures/ResourceOwner/OAuth1ResourceOwnerStub.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Test\Fixtures\ResourceOwner; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth1ResourceOwner; + +final class OAuth1ResourceOwnerStub extends GenericOAuth1ResourceOwner +{ +} diff --git a/vendor/hwi/oauth-bundle/src/Test/Fixtures/ResourceOwner/OAuth2ResourceOwnerStub.php b/vendor/hwi/oauth-bundle/src/Test/Fixtures/ResourceOwner/OAuth2ResourceOwnerStub.php new file mode 100644 index 0000000..505fbd7 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Test/Fixtures/ResourceOwner/OAuth2ResourceOwnerStub.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Test\Fixtures\ResourceOwner; + +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner; + +final class OAuth2ResourceOwnerStub extends GenericOAuth2ResourceOwner +{ +} diff --git a/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/GenericOAuth1ResourceOwnerTestCase.php b/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/GenericOAuth1ResourceOwnerTestCase.php new file mode 100644 index 0000000..0768aa5 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/GenericOAuth1ResourceOwnerTestCase.php @@ -0,0 +1,434 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Test\OAuth\ResourceOwner; + +use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorageInterface; +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth1ResourceOwner; +use HWI\Bundle\OAuthBundle\Test\Fixtures\CustomUserResponse; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +abstract class GenericOAuth1ResourceOwnerTestCase extends ResourceOwnerTestCase +{ + protected string $resourceOwnerClass = GenericOAuth1ResourceOwner::class; + + /** @var MockObject&RequestDataStorageInterface */ + protected $storage; + + protected array $options = [ + 'client_id' => 'clientid', + 'client_secret' => 'clientsecret', + + 'infos_url' => 'http://user.info/?test=1', + 'request_token_url' => 'http://user.request/?test=2', + 'authorization_url' => 'http://user.auth/?test=3', + 'access_token_url' => 'http://user.access/?test=4', + ]; + + protected string $userResponse = '{"id": "1", "foo": "bar"}'; + + protected array $paths = [ + 'identifier' => 'id', + 'nickname' => 'foo', + 'realname' => 'foo_disp', + ]; + + public function testUndefinedOptionThrowsException(): void + { + $this->expectException(ExceptionInterface::class); + + $this->createResourceOwner(['non_existing' => null]); + } + + public function testInvalidOptionValueThrowsException(): void + { + $this->expectException(ExceptionInterface::class); + + $this->createResourceOwner(['csrf' => 'invalid']); + } + + public function testHandleRequest(): void + { + $request = new Request(['test' => 'test']); + + $resourceOwner = $this->createResourceOwner(); + + $this->assertFalse($resourceOwner->handles($request)); + + $request = new Request(['oauth_token' => 'test']); + + $this->assertTrue($resourceOwner->handles($request)); + + $request = new Request(['oauth_token' => 'test', 'test' => 'test']); + + $this->assertTrue($resourceOwner->handles($request)); + } + + public function testGetUserInformation(): void + { + $accessToken = ['oauth_token' => 'token', 'oauth_token_secret' => 'secret']; + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse($this->userResponse, 'application/json; charset=utf-8'), + ] + ); + $userResponse = $resourceOwner->getUserInformation($accessToken); + + $this->assertEquals('1', $userResponse->getUsername()); + $this->assertEquals('bar', $userResponse->getNickname()); + $this->assertEquals($accessToken['oauth_token'], $userResponse->getAccessToken()); + $this->assertNull($userResponse->getRefreshToken()); + $this->assertNull($userResponse->getExpiresIn()); + } + + public function testGetAuthorizationUrlContainOAuthTokenAndSecret(): void + { + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('{"oauth_token": "token", "oauth_token_secret": "secret"}', 'application/json; charset=utf-8'), + ] + ); + + $this->storage->expects($this->once()) + ->method('save') + ->with($resourceOwner, ['oauth_token' => 'token', 'oauth_token_secret' => 'secret', 'timestamp' => time()]); + + $this->assertEquals( + $this->options['authorization_url'].'&oauth_token=token', + $resourceOwner->getAuthorizationUrl('http://redirect.to/') + ); + } + + public function testGetAuthorizationUrlFailedResponseContainOnlyOAuthToken(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('{"oauth_token": "token"}', 'application/json; charset=utf-8'), + ] + ); + + $this->storage->expects($this->never()) + ->method('save'); + + $resourceOwner->getAuthorizationUrl('http://redirect.to/'); + } + + public function testGetAuthorizationUrlFailedResponseContainOAuthProblem(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('oauth_problem=message', 'text/plain'), + ] + ); + + $this->storage->expects($this->never()) + ->method('save'); + + $resourceOwner->getAuthorizationUrl('http://redirect.to/'); + } + + public function testGetAuthorizationUrlFailedResponseContainCallbackNotConfirmed(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('oauth_callback_confirmed=false', 'text/plain'), + ] + ); + + $this->storage->expects($this->never()) + ->method('save'); + + $resourceOwner->getAuthorizationUrl('http://redirect.to/'); + } + + public function testGetAuthorizationUrlFailedResponseNotContainOAuthTokenOrSecret(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('invalid', 'text/plain'), + ] + ); + + $this->storage->expects($this->never()) + ->method('save'); + + $resourceOwner->getAuthorizationUrl('http://redirect.to/'); + } + + public function testGetAccessToken(): void + { + $request = new Request(['oauth_verifier' => 'code', 'oauth_token' => 'token']); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('oauth_token=token&oauth_token_secret=secret', 'text/plain'), + ] + ); + + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, 'token') + ->willReturn(['oauth_token' => 'token2', 'oauth_token_secret' => 'secret2']); + + $this->assertEquals( + ['oauth_token' => 'token', 'oauth_token_secret' => 'secret'], + $resourceOwner->getAccessToken($request, 'http://redirect.to/') + ); + } + + public function testGetAccessTokenHtmlResponse(): void + { + $request = new Request(['oauth_verifier' => 'code', 'oauth_token' => 'token']); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('oauth_token=token&oauth_token_secret=secret', 'text/html;charset=utf-8'), + ] + ); + + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, 'token') + ->willReturn(['oauth_token' => 'token2', 'oauth_token_secret' => 'secret2']); + + $this->assertEquals( + ['oauth_token' => 'token', 'oauth_token_secret' => 'secret'], + $resourceOwner->getAccessToken($request, 'http://redirect.to/') + ); + } + + public function testGetAccessTokenUrlEncodedResponse(): void + { + $request = new Request(['oauth_verifier' => 'code', 'oauth_token' => 'token']); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('oauth_token=token&oauth_token_secret=secret', 'application/x-www-form-urlencoded'), + ] + ); + + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, 'token') + ->willReturn(['oauth_token' => 'token2', 'oauth_token_secret' => 'secret2']); + + $this->assertEquals( + ['oauth_token' => 'token', 'oauth_token_secret' => 'secret'], + $resourceOwner->getAccessToken($request, 'http://redirect.to/') + ); + } + + public function testGetAccessTokenJsonResponse(): void + { + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('{"oauth_token": "token", "oauth_token_secret": "secret"}', 'application/json'), + ] + ); + + $request = new Request(['oauth_verifier' => 'code', 'oauth_token' => 'token']); + + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, 'token') + ->willReturn(['oauth_token' => 'token2', 'oauth_token_secret' => 'secret2']); + + $this->assertEquals( + ['oauth_token' => 'token', 'oauth_token_secret' => 'secret'], + $resourceOwner->getAccessToken($request, 'http://redirect.to/') + ); + } + + public function testGetAccessTokenJsonCharsetResponse(): void + { + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('{"oauth_token": "token", "oauth_token_secret": "secret"}', 'application/json; charset=utf-8'), + ] + ); + + $request = new Request(['oauth_verifier' => 'code', 'oauth_token' => 'token']); + + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, 'token') + ->willReturn(['oauth_token' => 'token2', 'oauth_token_secret' => 'secret2']); + + $this->assertEquals( + ['oauth_token' => 'token', 'oauth_token_secret' => 'secret'], + $resourceOwner->getAccessToken($request, 'http://redirect.to/') + ); + } + + public function testGetAccessTokenFailedResponse(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('invalid', 'text/plain'), + ] + ); + + $this->storage->expects($this->once()) + ->method('fetch') + ->willReturn(['oauth_token' => 'token', 'oauth_token_secret' => 'secret']); + + $this->storage->expects($this->never()) + ->method('save'); + + $request = new Request(['oauth_token' => 'token', 'oauth_verifier' => 'code']); + + $resourceOwner->getAccessToken($request, 'http://redirect.to/'); + } + + public function testGetAccessTokenErrorResponse(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('error=foo', 'text/plain'), + ] + ); + + $this->storage->expects($this->once()) + ->method('fetch') + ->willReturn(['oauth_token' => 'token', 'oauth_token_secret' => 'secret']); + + $this->storage->expects($this->never()) + ->method('save'); + + $request = new Request(['oauth_token' => 'token', 'oauth_verifier' => 'code']); + + $resourceOwner->getAccessToken($request, 'http://redirect.to/'); + } + + public function testGetAccessTokenInvalidArgumentException(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner(); + + $this->storage->expects($this->once()) + ->method('fetch') + ->willThrowException(new \InvalidArgumentException()); + + $this->storage->expects($this->never()) + ->method('save'); + + $request = new Request(['oauth_token' => 'token', 'oauth_verifier' => 'code']); + + $resourceOwner->getAccessToken($request, 'http://redirect.to/'); + } + + public function testRefreshAccessToken(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner(); + $resourceOwner->refreshAccessToken('token'); + } + + public function testRevokeToken(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner(); + $resourceOwner->revokeToken('token'); + } + + public function testCsrfTokenIsAlwaysValidForOAuth1(): void + { + $resourceOwner = $this->createResourceOwner(); + + $this->storage->expects($this->never()) + ->method('fetch'); + + $this->assertTrue($resourceOwner->isCsrfTokenValid('valid_token')); + } + + public function testCsrfTokenValid(): void + { + $resourceOwner = $this->createResourceOwner(['csrf' => true]); + + $this->storage->expects($this->never()) + ->method('fetch'); + + $this->assertTrue($resourceOwner->isCsrfTokenValid('valid_token')); + } + + public function testGetSetName(): void + { + $resourceOwner = $this->createResourceOwner(); + $this->assertEquals($this->prepareResourceOwnerName(), $resourceOwner->getName()); + } + + public function testCustomResponseClass(): void + { + $class = CustomUserResponse::class; + $resourceOwner = $this->createResourceOwner( + ['user_response_class' => $class], + [], + [ + $this->createMockResponse($this->userResponse), + ] + ); + + /** @var CustomUserResponse $userResponse */ + $userResponse = $resourceOwner->getUserInformation(['oauth_token' => 'token', 'oauth_token_secret' => 'secret']); + + $this->assertInstanceOf($class, $userResponse); + $this->assertEquals('foo666', $userResponse->getUsername()); + $this->assertEquals('foo', $userResponse->getNickname()); + $this->assertEquals('token', $userResponse->getAccessToken()); + $this->assertNull($userResponse->getRefreshToken()); + $this->assertNull($userResponse->getExpiresIn()); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/GenericOAuth2ResourceOwnerTestCase.php b/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/GenericOAuth2ResourceOwnerTestCase.php new file mode 100644 index 0000000..cae1a42 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/GenericOAuth2ResourceOwnerTestCase.php @@ -0,0 +1,457 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Test\OAuth\ResourceOwner; + +use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException; +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner; +use HWI\Bundle\OAuthBundle\OAuth\Response\AbstractUserResponse; +use HWI\Bundle\OAuthBundle\OAuth\State\State; +use HWI\Bundle\OAuthBundle\OAuth\StateInterface; +use HWI\Bundle\OAuthBundle\Security\Helper\NonceGenerator; +use HWI\Bundle\OAuthBundle\Test\Fixtures\CustomUserResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +abstract class GenericOAuth2ResourceOwnerTestCase extends ResourceOwnerTestCase +{ + protected string $resourceOwnerClass = GenericOAuth2ResourceOwner::class; + protected array $tokenData = ['access_token' => 'token']; + + protected array $options = [ + 'client_id' => 'clientid', + 'client_secret' => 'clientsecret', + + 'infos_url' => 'http://user.info/?test=1', + 'authorization_url' => 'http://user.auth/?test=2', + 'access_token_url' => 'http://user.access/?test=3', + + 'attr_name' => 'access_token', + ]; + + protected string $userResponse = << 'id', + 'nickname' => 'foo', + 'realname' => 'foo_disp', + ]; + + protected string $authorizationUrlBasePart = 'http://user.auth/?test=2&response_type=code&client_id=clientid'; + protected string $redirectUrlPart = '&redirect_uri=http%3A%2F%2Fredirect.to%2F'; + protected array $authorizationUrlParams = []; + + public function testUndefinedOptionThrowsException(): void + { + $this->expectException(ExceptionInterface::class); + + $this->createResourceOwner(['non_existing' => null]); + } + + public function testInvalidOptionValueThrowsException(): void + { + $this->expectException(ExceptionInterface::class); + + $this->createResourceOwner(['csrf' => 'invalid']); + } + + public function testHandleRequest(): void + { + $resourceOwner = $this->createResourceOwner(); + + $request = new Request(['test' => 'test']); + + $this->assertFalse($resourceOwner->handles($request)); + + $request = new Request(['code' => 'test']); + + $this->assertTrue($resourceOwner->handles($request)); + + $request = new Request(['code' => 'test', 'test' => 'test']); + + $this->assertTrue($resourceOwner->handles($request)); + } + + public function testGetUserInformation(): void + { + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse($this->userResponse), + ] + ); + + /** @var AbstractUserResponse $userResponse */ + $userResponse = $resourceOwner->getUserInformation($this->tokenData); + + $this->assertEquals('1', $userResponse->getUsername()); + $this->assertEquals('bar', $userResponse->getNickname()); + $this->assertEquals('token', $userResponse->getAccessToken()); + $this->assertNull($userResponse->getRefreshToken()); + $this->assertNull($userResponse->getExpiresIn()); + } + + public function testGetUserInformationFailure(): void + { + $this->expectException(HttpTransportException::class); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('invalid', null, 401), + ] + ); + $resourceOwner->getUserInformation($this->tokenData); + } + + public function testGetAuthorizationUrl(): void + { + if (!$this->csrf) { + $state = new State(null); + } else { + $state = new State(['csrf_token' => NonceGenerator::generate()]); + } + + $resourceOwner = $this->createResourceOwner([], [], [], $state); + + if (!$this->csrf) { + $this->storage->expects($this->never()) + ->method('save'); + + $expectedUrl = $this->authorizationUrlBasePart.$this->redirectUrlPart; + } else { + $this->storage->expects($this->once()) + ->method('save') + ->with($resourceOwner, $state->getCsrfToken(), 'csrf_state'); + + $expectedUrl = $this->getExpectedAuthorizationUrlWithState($state->encode()); + } + + $this->assertEquals( + $expectedUrl, + $resourceOwner->getAuthorizationUrl('http://redirect.to/', $this->authorizationUrlParams) + ); + } + + public function testGetState(): void + { + $stateParams = ['initial_state_param_1' => 'value']; + if (!$this->csrf) { + $initialState = new State($stateParams); + } else { + $initialState = new State(array_merge($stateParams, ['csrf_token' => NonceGenerator::generate()])); + } + + $resourceOwner = $this->createResourceOwner([], [], [], $initialState); + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, State::class, 'state') + ->willReturn(serialize(new State(['state' => 'some-state']))); + + $state = $resourceOwner->getState(); + self::assertEquals('value', $state->get('initial_state_param_1')); + self::assertEquals('some-state', $state->get('state')); + } + + public function testGetStateWithoutStoredValues(): void + { + $resourceOwner = $this->createResourceOwner([], [], [], new State(null)); + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, State::class, 'state') + ->willThrowException(new \InvalidArgumentException('No data available in storage.')); + + $state = $resourceOwner->getState(); + self::assertEmpty($state->getAll()); + } + + public function testGetAuthorizationUrlWithEnabledCsrf(): void + { + if ($this->csrf) { + $this->markTestSkipped('CSRF is enabled for this Resource Owner.'); + } + + $nonce = NonceGenerator::generate(); + $state = new State(['csrf_token' => $nonce]); + $resourceOwner = $this->createResourceOwner(['csrf' => true], [], [], $state); + + $this->storage->expects($this->once()) + ->method('save') + ->with($resourceOwner, $nonce, 'csrf_state'); + + $this->assertEquals( + $this->getExpectedAuthorizationUrlWithState($state->encode()), + $resourceOwner->getAuthorizationUrl('http://redirect.to/', $this->authorizationUrlParams) + ); + + $this->state = $state->encode(); + } + + /** + * @dataProvider provideAccessTokenData + */ + public function testGetAccessToken(string $response, string $contentType): void + { + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse($response, $contentType), + ] + ); + + $request = new Request(['code' => 'somecode']); + + $this->assertEquals( + ['access_token' => 'code'], + $resourceOwner->getAccessToken($request, 'http://redirect.to/') + ); + } + + public function provideAccessTokenData(): iterable + { + yield 'plain text' => [ + 'access_token=code', + 'text/plain', + ]; + + yield 'html text with charset' => [ + 'access_token=code', + 'text/html;charset=utf-8', + ]; + + yield 'json' => [ + '{"access_token": "code"}', + 'application/json', + ]; + + yield 'json with charset' => [ + '{"access_token": "code"}', + 'application/json; charset=utf-8', + ]; + + yield 'javascript' => [ + '{"access_token": "code"}', + 'text/javascript', + ]; + + yield 'javascript with charset' => [ + '{"access_token": "code"}', + 'text/javascript; charset=utf-8', + ]; + } + + public function testGetAccessTokenFailedResponse(): void + { + $this->expectException(AuthenticationException::class); + + $request = new Request(['code' => 'code']); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('invalid'), + ] + ); + $resourceOwner->getAccessToken($request, 'http://redirect.to/'); + } + + public function testGetAccessTokenErrorResponse(): void + { + $this->expectException(AuthenticationException::class); + + $request = new Request(['code' => 'code']); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse('error=foo'), + ] + ); + $resourceOwner->getAccessToken($request, 'http://redirect.to/'); + } + + /** + * @dataProvider provideRefreshToken + */ + public function testRefreshAccessToken($response, $contentType): void + { + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse($response, $contentType), + ] + ); + + $accessToken = $resourceOwner->refreshAccessToken('foo'); + + $this->assertEquals('bar', $accessToken['access_token']); + $this->assertEquals(3600, $accessToken['expires_in']); + } + + public function provideRefreshToken(): iterable + { + yield 'correct token' => [ + '{"access_token": "bar", "expires_in": 3600}', + 'application/json', + ]; + } + + /** + * @dataProvider provideInvalidRefreshToken + */ + public function testRefreshAccessTokenInvalid(string $response, string $exceptionClass): void + { + $this->expectException($exceptionClass); + + $resourceOwner = $this->createResourceOwner( + [], + [], + [ + $this->createMockResponse($response), + ] + ); + $resourceOwner->refreshAccessToken('foo'); + } + + public function provideInvalidRefreshToken(): iterable + { + yield 'invalid' => [ + 'invalid', + AuthenticationException::class, + ]; + + yield 'invalid json' => [ + '{"error": "invalid"}', + AuthenticationException::class, + ]; + } + + public function testRevokeToken(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner(); + $resourceOwner->revokeToken('token'); + } + + public function testGetSetName(): void + { + $resourceOwner = $this->createResourceOwner(); + $this->assertEquals($this->prepareResourceOwnerName(), $resourceOwner->getName()); + } + + public function testCsrfTokenIsValidWhenDisabled(): void + { + if ($this->csrf) { + $this->markTestSkipped('CSRF is enabled for this Resource Owner.'); + } + + $resourceOwner = $this->createResourceOwner(); + + $this->storage->expects($this->never()) + ->method('fetch'); + + $this->assertTrue($resourceOwner->isCsrfTokenValid('whatever you want')); + } + + public function testCsrfTokenValid(): void + { + $resourceOwner = $this->createResourceOwner(['csrf' => true]); + + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, 'valid_token', 'csrf_state') + ->willReturn('valid_token'); + + $this->assertTrue($resourceOwner->isCsrfTokenValid('valid_token')); + } + + public function testCsrfTokenInvalid(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner(['csrf' => true]); + + $this->storage->expects($this->once()) + ->method('fetch') + ->with($resourceOwner, 'invalid_token', 'csrf_state') + ->will($this->throwException(new InvalidOptionsException('No data available in storage.'))); + + $resourceOwner->isCsrfTokenValid('invalid_token'); + } + + public function testCsrfTokenMissing(): void + { + $this->expectException(AuthenticationException::class); + + $resourceOwner = $this->createResourceOwner(['csrf' => true]); + + $resourceOwner->isCsrfTokenValid(null); + } + + public function testCustomResponseClass(): void + { + $class = CustomUserResponse::class; + + $resourceOwner = $this->createResourceOwner( + ['user_response_class' => $class], + [], + [ + $this->createMockResponse($this->userResponse), + ] + ); + + $userResponse = $resourceOwner->getUserInformation($this->tokenData); + + $this->assertInstanceOf($class, $userResponse); + $this->assertEquals('foo666', $userResponse->getUsername()); + $this->assertEquals('foo', $userResponse->getNickname()); + $this->assertEquals('token', $userResponse->getAccessToken()); + $this->assertNull($userResponse->getRefreshToken()); + $this->assertNull($userResponse->getExpiresIn()); + } + + protected function createResourceOwner( + array $options = [], + array $paths = [], + array $responses = [], + ?StateInterface $state = null + ): GenericOAuth2ResourceOwner { + /** @var GenericOAuth2ResourceOwner $resourceOwner */ + $resourceOwner = parent::createResourceOwner($options, $paths, $responses); + + $reflection = new \ReflectionClass($resourceOwner::class); + $stateProperty = $reflection->getProperty('state'); + $stateProperty->setAccessible(true); + $stateProperty->setValue($resourceOwner, $state ?: new State($this->state)); + + return $resourceOwner; + } + + private function getExpectedAuthorizationUrlWithState($stateParameter): string + { + // urlencode state parameter since AbstractResourceOwner::normalizeUrl() http_build_query method encodes them again + return $this->authorizationUrlBasePart.'&state='.urlencode($stateParameter).$this->redirectUrlPart; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/ResourceOwnerTestCase.php b/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/ResourceOwnerTestCase.php new file mode 100644 index 0000000..a2b8182 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Test/OAuth/ResourceOwner/ResourceOwnerTestCase.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Test\OAuth\ResourceOwner; + +use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorageInterface; +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth1ResourceOwner; +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner; +use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface; +use HWI\Bundle\OAuthBundle\Test\Fixtures\ResourceOwner\OAuth1ResourceOwnerStub; +use HWI\Bundle\OAuthBundle\Test\Fixtures\ResourceOwner\OAuth2ResourceOwnerStub; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\Security\Http\HttpUtils; + +abstract class ResourceOwnerTestCase extends TestCase +{ + protected ?MockHttpClient $httpClient = null; + + /** @var MockObject&RequestDataStorageInterface */ + protected $storage; + + protected string $state = 'eyJzdGF0ZSI6InJhbmRvbSJ9'; + protected bool $csrf = false; + + protected int $httpResponseHttpCode = 200; + protected int $httpClientCalls = 0; + + protected array $options = []; + /** @var array */ + protected array $paths = []; + + /** @var class-string */ + protected string $resourceOwnerClass; + + protected function createMockResponse(?string $response, ?string $contentType = null, ?int $httpCode = null): MockResponse + { + return new MockResponse( + $response ?: '', + [ + 'http_code' => $httpCode ?: 200, + 'response_headers' => [ + 'Content-Type' => $contentType ?: 'application/json', + ], + ] + ); + } + + protected function prepareResourceOwnerName(): string + { + return str_replace(['generic', 'resourceownertest'], '', strtolower(__CLASS__)); + } + + /** + * @return OAuth1ResourceOwnerStub|OAuth2ResourceOwnerStub + */ + protected function createResourceOwner(array $options = [], array $paths = [], array $responses = []): ResourceOwnerInterface + { + $this->storage = $this->createMock(RequestDataStorageInterface::class); + + /** @var HttpUtils $httpUtils */ + $httpUtils = $this->createMock(HttpUtils::class); + + $resourceOwner = $this->setUpResourceOwner( + $this->prepareResourceOwnerName(), + $httpUtils, + array_merge($this->options, $options), + $responses + ); + $resourceOwner->addPaths(array_merge($this->paths, $paths)); + + return $resourceOwner; + } + + /** + * @return OAuth1ResourceOwnerStub|OAuth2ResourceOwnerStub + */ + protected function setUpResourceOwner(string $name, HttpUtils $httpUtils, array $options, array $responses): ResourceOwnerInterface + { + if (!$this->resourceOwnerClass) { + throw new \RuntimeException('Missing resource owner class declaration!'); + } + + if (!\in_array(ResourceOwnerInterface::class, class_implements($this->resourceOwnerClass), true)) { + throw new \RuntimeException('Class is not implementing "ResourceOwnerInterface"!'); + } + + $resourceOwnerClass = $this->resourceOwnerClass; + if (GenericOAuth1ResourceOwner::class === $resourceOwnerClass) { + $resourceOwnerClass = OAuth1ResourceOwnerStub::class; + } elseif (GenericOAuth2ResourceOwner::class === $resourceOwnerClass) { + $resourceOwnerClass = OAuth2ResourceOwnerStub::class; + } + + return new $resourceOwnerClass( + $this->httpClient ?: new MockHttpClient($responses), + $httpUtils, + $options, + $name, + $this->storage + ); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Twig/Extension/OAuthExtension.php b/vendor/hwi/oauth-bundle/src/Twig/Extension/OAuthExtension.php new file mode 100644 index 0000000..dfb844d --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Twig/Extension/OAuthExtension.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * @author Joseph Bielawski + */ +final class OAuthExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getFunctions(): array + { + return [ + new TwigFunction('hwi_oauth_authorization_url', [OAuthRuntime::class, 'getAuthorizationUrl']), + new TwigFunction('hwi_oauth_login_url', [OAuthRuntime::class, 'getLoginUrl']), + new TwigFunction('hwi_oauth_resource_owners', [OAuthRuntime::class, 'getResourceOwners']), + ]; + } + + public function getName(): string + { + return 'hwi_oauth'; + } +} diff --git a/vendor/hwi/oauth-bundle/src/Twig/Extension/OAuthRuntime.php b/vendor/hwi/oauth-bundle/src/Twig/Extension/OAuthRuntime.php new file mode 100644 index 0000000..93125a6 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Twig/Extension/OAuthRuntime.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Twig\Extension; + +use HWI\Bundle\OAuthBundle\Security\OAuthUtils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Twig\Extension\RuntimeExtensionInterface; + +/** + * @author Alexander + * @author Joseph Bielawski + */ +final class OAuthRuntime implements RuntimeExtensionInterface +{ + private OAuthUtils $oauthUtils; + private RequestStack $requestStack; + + public function __construct(OAuthUtils $oauthUtils, RequestStack $requestStack) + { + $this->oauthUtils = $oauthUtils; + $this->requestStack = $requestStack; + } + + /** + * @return string[] + */ + public function getResourceOwners(): array + { + return $this->oauthUtils->getResourceOwners(); + } + + public function getLoginUrl(string $name): string + { + return $this->oauthUtils->getLoginUrl($this->getMainRequest(), $name); + } + + public function getAuthorizationUrl(string $name, ?string $redirectUrl = null, array $extraParameters = []): string + { + return $this->oauthUtils->getAuthorizationUrl($this->getMainRequest(), $name, $redirectUrl, $extraParameters); + } + + private function getMainRequest(): ?Request + { + if (method_exists($this->requestStack, 'getMainRequest')) { + return $this->requestStack->getMainRequest(); // Symfony 5.3+ + } + + // @phpstan-ignore-next-line + return $this->requestStack->getMasterRequest(); + } +} diff --git a/vendor/hwi/oauth-bundle/src/Util/DomainWhitelist.php b/vendor/hwi/oauth-bundle/src/Util/DomainWhitelist.php new file mode 100644 index 0000000..75267f2 --- /dev/null +++ b/vendor/hwi/oauth-bundle/src/Util/DomainWhitelist.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HWI\Bundle\OAuthBundle\Util; + +final class DomainWhitelist +{ + /** + * @var array + */ + private array $targetPathDomainsWhiteList; + + public function __construct(array $targetPathDomainsWhiteList) + { + $this->targetPathDomainsWhiteList = $targetPathDomainsWhiteList; + } + + public function isValidTargetUrl(string $targetUrl): bool + { + if (!$this->targetPathDomainsWhiteList) { + return true; + } + + $urlParts = parse_url($targetUrl); + if (!isset($urlParts['host'])) { + return false; + } + + return \in_array($urlParts['host'], $this->targetPathDomainsWhiteList, true); + } +} diff --git a/vendor/lcobucci/clock/LICENSE b/vendor/lcobucci/clock/LICENSE new file mode 100644 index 0000000..58ea944 --- /dev/null +++ b/vendor/lcobucci/clock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Luís Cobucci + +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. diff --git a/vendor/lcobucci/clock/composer.json b/vendor/lcobucci/clock/composer.json new file mode 100644 index 0000000..f2cd93b --- /dev/null +++ b/vendor/lcobucci/clock/composer.json @@ -0,0 +1,48 @@ +{ + "name": "lcobucci/clock", + "description": "Yet another clock abstraction", + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "require": { + "php": "~8.2.0 || ~8.3.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.27", + "lcobucci/coding-standard": "^11.0.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.10.25", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^10.2.3" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Lcobucci\\Clock\\": "test" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "infection/extension-installer": true, + "phpstan/extension-installer": true + }, + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/lcobucci/clock/src/Clock.php b/vendor/lcobucci/clock/src/Clock.php new file mode 100644 index 0000000..45a033b --- /dev/null +++ b/vendor/lcobucci/clock/src/Clock.php @@ -0,0 +1,12 @@ +now = $now; + } + + public function now(): DateTimeImmutable + { + return $this->now; + } +} diff --git a/vendor/lcobucci/clock/src/SystemClock.php b/vendor/lcobucci/clock/src/SystemClock.php new file mode 100644 index 0000000..6b65dfa --- /dev/null +++ b/vendor/lcobucci/clock/src/SystemClock.php @@ -0,0 +1,32 @@ +timezone); + } +} diff --git a/vendor/lcobucci/jwt/.readthedocs.yaml b/vendor/lcobucci/jwt/.readthedocs.yaml new file mode 100644 index 0000000..aa49c94 --- /dev/null +++ b/vendor/lcobucci/jwt/.readthedocs.yaml @@ -0,0 +1,9 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3" + +mkdocs: + configuration: mkdocs.yml diff --git a/vendor/lcobucci/jwt/LICENSE b/vendor/lcobucci/jwt/LICENSE new file mode 100644 index 0000000..cc7e28f --- /dev/null +++ b/vendor/lcobucci/jwt/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014, Luís Cobucci +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/lcobucci/jwt/composer.json b/vendor/lcobucci/jwt/composer.json new file mode 100644 index 0000000..a618689 --- /dev/null +++ b/vendor/lcobucci/jwt/composer.json @@ -0,0 +1,63 @@ +{ + "name": "lcobucci/jwt", + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "license": [ + "BSD-3-Clause" + ], + "type": "library", + "keywords": [ + "JWT", + "JWS" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "require": { + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", + "ext-openssl": "*", + "ext-sodium": "*", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.27.0", + "lcobucci/clock": "^3.0", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2.9", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^10.2.6" + }, + "suggest": { + "lcobucci/clock": ">= 3.0" + }, + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Lcobucci\\JWT\\Tests\\": "tests" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "infection/extension-installer": true, + "ocramius/package-versions": true, + "phpstan/extension-installer": true + }, + "platform": { + "php": "8.1.99" + }, + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/lcobucci/jwt/renovate.json b/vendor/lcobucci/jwt/renovate.json new file mode 100644 index 0000000..d0adcc3 --- /dev/null +++ b/vendor/lcobucci/jwt/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>lcobucci/.github:renovate-config" + ] +} diff --git a/vendor/lcobucci/jwt/src/Builder.php b/vendor/lcobucci/jwt/src/Builder.php new file mode 100644 index 0000000..4295e3f --- /dev/null +++ b/vendor/lcobucci/jwt/src/Builder.php @@ -0,0 +1,85 @@ + $claims + * + * @return array + */ + public function formatClaims(array $claims): array; +} diff --git a/vendor/lcobucci/jwt/src/Configuration.php b/vendor/lcobucci/jwt/src/Configuration.php new file mode 100644 index 0000000..488ea3e --- /dev/null +++ b/vendor/lcobucci/jwt/src/Configuration.php @@ -0,0 +1,131 @@ +parser = new Token\Parser($decoder); + $this->validator = new Validation\Validator(); + + $this->builderFactory = static function (ClaimsFormatter $claimFormatter) use ($encoder): Builder { + return new Token\Builder($encoder, $claimFormatter); + }; + } + + public static function forAsymmetricSigner( + Signer $signer, + Key $signingKey, + Key $verificationKey, + Encoder $encoder = new JoseEncoder(), + Decoder $decoder = new JoseEncoder(), + ): self { + return new self( + $signer, + $signingKey, + $verificationKey, + $encoder, + $decoder, + ); + } + + public static function forSymmetricSigner( + Signer $signer, + Key $key, + Encoder $encoder = new JoseEncoder(), + Decoder $decoder = new JoseEncoder(), + ): self { + return new self( + $signer, + $key, + $key, + $encoder, + $decoder, + ); + } + + /** @param callable(ClaimsFormatter): Builder $builderFactory */ + public function setBuilderFactory(callable $builderFactory): void + { + $this->builderFactory = $builderFactory(...); + } + + public function builder(?ClaimsFormatter $claimFormatter = null): Builder + { + return ($this->builderFactory)($claimFormatter ?? ChainedFormatter::default()); + } + + public function parser(): Parser + { + return $this->parser; + } + + public function setParser(Parser $parser): void + { + $this->parser = $parser; + } + + public function signer(): Signer + { + return $this->signer; + } + + public function signingKey(): Key + { + return $this->signingKey; + } + + public function verificationKey(): Key + { + return $this->verificationKey; + } + + public function validator(): Validator + { + return $this->validator; + } + + public function setValidator(Validator $validator): void + { + $this->validator = $validator; + } + + /** @return Constraint[] */ + public function validationConstraints(): array + { + return $this->validationConstraints; + } + + public function setValidationConstraints(Constraint ...$validationConstraints): void + { + $this->validationConstraints = $validationConstraints; + } +} diff --git a/vendor/lcobucci/jwt/src/Decoder.php b/vendor/lcobucci/jwt/src/Decoder.php new file mode 100644 index 0000000..6b24b92 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Decoder.php @@ -0,0 +1,29 @@ + */ + private array $formatters; + + public function __construct(ClaimsFormatter ...$formatters) + { + $this->formatters = $formatters; + } + + public static function default(): self + { + return new self(new UnifyAudience(), new MicrosecondBasedDateConversion()); + } + + public static function withUnixTimestampDates(): self + { + return new self(new UnifyAudience(), new UnixTimestampDates()); + } + + /** @inheritdoc */ + public function formatClaims(array $claims): array + { + foreach ($this->formatters as $formatter) { + $claims = $formatter->formatClaims($claims); + } + + return $claims; + } +} diff --git a/vendor/lcobucci/jwt/src/Encoding/JoseEncoder.php b/vendor/lcobucci/jwt/src/Encoding/JoseEncoder.php new file mode 100644 index 0000000..0d90444 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Encoding/JoseEncoder.php @@ -0,0 +1,56 @@ +convertDate($claims[$claim]); + } + + return $claims; + } + + private function convertDate(DateTimeImmutable $date): int|float + { + if ($date->format('u') === '000000') { + return (int) $date->format('U'); + } + + return (float) $date->format('U.u'); + } +} diff --git a/vendor/lcobucci/jwt/src/Encoding/UnifyAudience.php b/vendor/lcobucci/jwt/src/Encoding/UnifyAudience.php new file mode 100644 index 0000000..cf57252 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Encoding/UnifyAudience.php @@ -0,0 +1,29 @@ +convertDate($claims[$claim]); + } + + return $claims; + } + + private function convertDate(DateTimeImmutable $date): int + { + return $date->getTimestamp(); + } +} diff --git a/vendor/lcobucci/jwt/src/Exception.php b/vendor/lcobucci/jwt/src/Exception.php new file mode 100644 index 0000000..4b3916e --- /dev/null +++ b/vendor/lcobucci/jwt/src/Exception.php @@ -0,0 +1,10 @@ +clock = $clock ?? new class implements Clock { + public function now(): DateTimeImmutable + { + return new DateTimeImmutable(); + } + }; + } + + /** @param Closure(Builder, DateTimeImmutable):Builder $customiseBuilder */ + public function issue( + Signer $signer, + Key $signingKey, + Closure $customiseBuilder, + ): UnencryptedToken { + $builder = new Token\Builder(new JoseEncoder(), ChainedFormatter::withUnixTimestampDates()); + + $now = $this->clock->now(); + $builder = $builder + ->issuedAt($now) + ->canOnlyBeUsedAfter($now) + ->expiresAt($now->modify('+5 minutes')); + + return $customiseBuilder($builder, $now)->getToken($signer, $signingKey); + } + + /** @param non-empty-string $jwt */ + public function parse( + string $jwt, + SignedWith $signedWith, + ValidAt $validAt, + Constraint ...$constraints, + ): UnencryptedToken { + $token = $this->parser->parse($jwt); + assert($token instanceof UnencryptedToken); + + (new Validator())->assert( + $token, + $signedWith, + $validAt, + ...$constraints, + ); + + return $token; + } +} diff --git a/vendor/lcobucci/jwt/src/Parser.php b/vendor/lcobucci/jwt/src/Parser.php new file mode 100644 index 0000000..fa77f04 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Parser.php @@ -0,0 +1,22 @@ +contents()); + + if ($actualKeyLength < self::MINIMUM_KEY_LENGTH_IN_BITS) { + throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH_IN_BITS, $actualKeyLength); + } + + return sodium_crypto_generichash($payload, $key->contents()); + } + + public function verify(string $expected, string $payload, Key $key): bool + { + return hash_equals($expected, $this->sign($payload, $key)); + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/CannotSignPayload.php b/vendor/lcobucci/jwt/src/Signer/CannotSignPayload.php new file mode 100644 index 0000000..35cc4d6 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/CannotSignPayload.php @@ -0,0 +1,15 @@ +converter->fromAsn1( + $this->createSignature($key->contents(), $key->passphrase(), $payload), + $this->pointLength(), + ); + } + + final public function verify(string $expected, string $payload, Key $key): bool + { + return $this->verifySignature( + $this->converter->toAsn1($expected, $this->pointLength()), + $payload, + $key->contents(), + ); + } + + /** {@inheritDoc} */ + final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void + { + if ($type !== OPENSSL_KEYTYPE_EC) { + throw InvalidKeyProvided::incompatibleKeyType( + self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_EC], + self::KEY_TYPE_MAP[$type], + ); + } + + $expectedKeyLength = $this->expectedKeyLength(); + + if ($lengthInBits !== $expectedKeyLength) { + throw InvalidKeyProvided::incompatibleKeyLength($expectedKeyLength, $lengthInBits); + } + } + + /** + * @internal + * + * @return positive-int + */ + abstract public function expectedKeyLength(): int; + + /** + * Returns the length of each point in the signature, so that we can calculate and verify R and S points properly + * + * @internal + * + * @return positive-int + */ + abstract public function pointLength(): int; +} diff --git a/vendor/lcobucci/jwt/src/Signer/Ecdsa/ConversionFailed.php b/vendor/lcobucci/jwt/src/Signer/Ecdsa/ConversionFailed.php new file mode 100644 index 0000000..d9ca751 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Ecdsa/ConversionFailed.php @@ -0,0 +1,25 @@ + self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : ''; + + $asn1 = hex2bin( + self::ASN1_SEQUENCE + . $lengthPrefix . dechex($totalLength) + . self::ASN1_INTEGER . dechex($lengthR) . $pointR + . self::ASN1_INTEGER . dechex($lengthS) . $pointS, + ); + assert(is_string($asn1)); + assert($asn1 !== ''); + + return $asn1; + } + + private static function octetLength(string $data): int + { + return (int) (strlen($data) / self::BYTE_SIZE); + } + + private static function preparePositiveInteger(string $data): string + { + if (substr($data, 0, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT) { + return self::ASN1_NEGATIVE_INTEGER . $data; + } + + while ( + substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER + && substr($data, 2, self::BYTE_SIZE) <= self::ASN1_BIG_INTEGER_LIMIT + ) { + $data = substr($data, 2, null); + } + + return $data; + } + + public function fromAsn1(string $signature, int $length): string + { + $message = bin2hex($signature); + $position = 0; + + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) { + throw ConversionFailed::incorrectStartSequence(); + } + + // @phpstan-ignore-next-line + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) { + $position += self::BYTE_SIZE; + } + + $pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); + $pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); + + $points = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT)); + assert(is_string($points)); + assert($points !== ''); + + return $points; + } + + private static function readAsn1Content(string $message, int &$position, int $length): string + { + $content = substr($message, $position, $length); + $position += $length; + + return $content; + } + + private static function readAsn1Integer(string $message, int &$position): string + { + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) { + throw ConversionFailed::integerExpected(); + } + + $length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE)); + + return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE); + } + + private static function retrievePositiveInteger(string $data): string + { + while ( + substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER + && substr($data, 2, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT + ) { + $data = substr($data, 2, null); + } + + return $data; + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha256.php b/vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha256.php new file mode 100644 index 0000000..ff00f4d --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha256.php @@ -0,0 +1,31 @@ +contents()); + } catch (SodiumException $sodiumException) { + throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException); + } + } + + public function verify(string $expected, string $payload, Key $key): bool + { + try { + return sodium_crypto_sign_verify_detached($expected, $payload, $key->contents()); + } catch (SodiumException $sodiumException) { + throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/Hmac.php b/vendor/lcobucci/jwt/src/Signer/Hmac.php new file mode 100644 index 0000000..815f84c --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Hmac.php @@ -0,0 +1,44 @@ +contents()); + $expectedKeyLength = $this->minimumBitsLengthForKey(); + + if ($actualKeyLength < $expectedKeyLength) { + throw InvalidKeyProvided::tooShort($expectedKeyLength, $actualKeyLength); + } + + return hash_hmac($this->algorithm(), $payload, $key->contents(), true); + } + + final public function verify(string $expected, string $payload, Key $key): bool + { + return hash_equals($expected, $this->sign($payload, $key)); + } + + /** + * @internal + * + * @return non-empty-string + */ + abstract public function algorithm(): string; + + /** + * @internal + * + * @return positive-int + */ + abstract public function minimumBitsLengthForKey(): int; +} diff --git a/vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php b/vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php new file mode 100644 index 0000000..e19992e --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php @@ -0,0 +1,24 @@ +getSize(); + $contents = $fileSize > 0 ? $file->fread($file->getSize()) : ''; + assert(is_string($contents)); + + self::guardAgainstEmptyKey($contents); + + return new self($contents, $passphrase); + } + + /** @phpstan-assert non-empty-string $contents */ + private static function guardAgainstEmptyKey(string $contents): void + { + if ($contents === '') { + throw InvalidKeyProvided::cannotBeEmpty(); + } + } + + public function contents(): string + { + return $this->contents; + } + + public function passphrase(): string + { + return $this->passphrase; + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/OpenSSL.php b/vendor/lcobucci/jwt/src/Signer/OpenSSL.php new file mode 100644 index 0000000..bcc7065 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/OpenSSL.php @@ -0,0 +1,126 @@ + 'RSA', + OPENSSL_KEYTYPE_DSA => 'DSA', + OPENSSL_KEYTYPE_DH => 'DH', + OPENSSL_KEYTYPE_EC => 'EC', + ]; + + /** + * @return non-empty-string + * + * @throws CannotSignPayload + * @throws InvalidKeyProvided + */ + final protected function createSignature( + string $pem, + string $passphrase, + string $payload, + ): string { + $key = $this->getPrivateKey($pem, $passphrase); + + $signature = ''; + + if (! openssl_sign($payload, $signature, $key, $this->algorithm())) { + throw CannotSignPayload::errorHappened($this->fullOpenSSLErrorString()); + } + + return $signature; + } + + /** @throws CannotSignPayload */ + private function getPrivateKey(string $pem, string $passphrase): OpenSSLAsymmetricKey + { + return $this->validateKey(openssl_pkey_get_private($pem, $passphrase)); + } + + /** @throws InvalidKeyProvided */ + final protected function verifySignature( + string $expected, + string $payload, + string $pem, + ): bool { + $key = $this->getPublicKey($pem); + $result = openssl_verify($payload, $expected, $key, $this->algorithm()); + + return $result === 1; + } + + /** @throws InvalidKeyProvided */ + private function getPublicKey(string $pem): OpenSSLAsymmetricKey + { + return $this->validateKey(openssl_pkey_get_public($pem)); + } + + /** + * Raises an exception when the key type is not the expected type + * + * @throws InvalidKeyProvided + */ + private function validateKey(OpenSSLAsymmetricKey|bool $key): OpenSSLAsymmetricKey + { + if (is_bool($key)) { + throw InvalidKeyProvided::cannotBeParsed($this->fullOpenSSLErrorString()); + } + + $details = openssl_pkey_get_details($key); + assert(is_array($details)); + + assert(array_key_exists('bits', $details)); + assert(is_int($details['bits'])); + assert(array_key_exists('type', $details)); + assert(is_int($details['type'])); + + $this->guardAgainstIncompatibleKey($details['type'], $details['bits']); + + return $key; + } + + private function fullOpenSSLErrorString(): string + { + $error = ''; + + while ($msg = openssl_error_string()) { + $error .= PHP_EOL . '* ' . $msg; + } + + return $error; + } + + /** @throws InvalidKeyProvided */ + abstract protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void; + + /** + * Returns which algorithm to be used to create/verify the signature (using OpenSSL constants) + * + * @internal + */ + abstract public function algorithm(): int; +} diff --git a/vendor/lcobucci/jwt/src/Signer/Rsa.php b/vendor/lcobucci/jwt/src/Signer/Rsa.php new file mode 100644 index 0000000..ba7d72d --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Rsa.php @@ -0,0 +1,35 @@ +createSignature($key->contents(), $key->passphrase(), $payload); + } + + final public function verify(string $expected, string $payload, Key $key): bool + { + return $this->verifySignature($expected, $payload, $key->contents()); + } + + final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void + { + if ($type !== OPENSSL_KEYTYPE_RSA) { + throw InvalidKeyProvided::incompatibleKeyType( + self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_RSA], + self::KEY_TYPE_MAP[$type], + ); + } + + if ($lengthInBits < self::MINIMUM_KEY_LENGTH) { + throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH, $lengthInBits); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/Rsa/Sha256.php b/vendor/lcobucci/jwt/src/Signer/Rsa/Sha256.php new file mode 100644 index 0000000..9e56c70 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Rsa/Sha256.php @@ -0,0 +1,21 @@ + */ + private array $headers = ['typ' => 'JWT', 'alg' => null]; + + /** @var array */ + private array $claims = []; + + public function __construct(private readonly Encoder $encoder, private readonly ClaimsFormatter $claimFormatter) + { + } + + public function permittedFor(string ...$audiences): BuilderInterface + { + $configured = $this->claims[RegisteredClaims::AUDIENCE] ?? []; + $toAppend = array_diff($audiences, $configured); + + return $this->setClaim(RegisteredClaims::AUDIENCE, array_merge($configured, $toAppend)); + } + + public function expiresAt(DateTimeImmutable $expiration): BuilderInterface + { + return $this->setClaim(RegisteredClaims::EXPIRATION_TIME, $expiration); + } + + public function identifiedBy(string $id): BuilderInterface + { + return $this->setClaim(RegisteredClaims::ID, $id); + } + + public function issuedAt(DateTimeImmutable $issuedAt): BuilderInterface + { + return $this->setClaim(RegisteredClaims::ISSUED_AT, $issuedAt); + } + + public function issuedBy(string $issuer): BuilderInterface + { + return $this->setClaim(RegisteredClaims::ISSUER, $issuer); + } + + public function canOnlyBeUsedAfter(DateTimeImmutable $notBefore): BuilderInterface + { + return $this->setClaim(RegisteredClaims::NOT_BEFORE, $notBefore); + } + + public function relatedTo(string $subject): BuilderInterface + { + return $this->setClaim(RegisteredClaims::SUBJECT, $subject); + } + + public function withHeader(string $name, mixed $value): BuilderInterface + { + $new = clone $this; + $new->headers[$name] = $value; + + return $new; + } + + public function withClaim(string $name, mixed $value): BuilderInterface + { + if (in_array($name, RegisteredClaims::ALL, true)) { + throw RegisteredClaimGiven::forClaim($name); + } + + return $this->setClaim($name, $value); + } + + /** @param non-empty-string $name */ + private function setClaim(string $name, mixed $value): BuilderInterface + { + $new = clone $this; + $new->claims[$name] = $value; + + return $new; + } + + /** + * @param array $items + * + * @throws CannotEncodeContent When data cannot be converted to JSON. + */ + private function encode(array $items): string + { + return $this->encoder->base64UrlEncode( + $this->encoder->jsonEncode($items), + ); + } + + public function getToken(Signer $signer, Key $key): UnencryptedToken + { + $headers = $this->headers; + $headers['alg'] = $signer->algorithmId(); + + $encodedHeaders = $this->encode($headers); + $encodedClaims = $this->encode($this->claimFormatter->formatClaims($this->claims)); + + $signature = $signer->sign($encodedHeaders . '.' . $encodedClaims, $key); + $encodedSignature = $this->encoder->base64UrlEncode($signature); + + return new Plain( + new DataSet($headers, $encodedHeaders), + new DataSet($this->claims, $encodedClaims), + new Signature($signature, $encodedSignature), + ); + } +} diff --git a/vendor/lcobucci/jwt/src/Token/DataSet.php b/vendor/lcobucci/jwt/src/Token/DataSet.php new file mode 100644 index 0000000..6c0b98a --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/DataSet.php @@ -0,0 +1,37 @@ + $data */ + public function __construct(private readonly array $data, private readonly string $encoded) + { + } + + /** @param non-empty-string $name */ + public function get(string $name, mixed $default = null): mixed + { + return $this->data[$name] ?? $default; + } + + /** @param non-empty-string $name */ + public function has(string $name): bool + { + return array_key_exists($name, $this->data); + } + + /** @return array */ + public function all(): array + { + return $this->data; + } + + public function toString(): string + { + return $this->encoded; + } +} diff --git a/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php b/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php new file mode 100644 index 0000000..abba344 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php @@ -0,0 +1,41 @@ +splitJwt($jwt); + + if ($encodedHeaders === '') { + throw InvalidTokenStructure::missingHeaderPart(); + } + + if ($encodedClaims === '') { + throw InvalidTokenStructure::missingClaimsPart(); + } + + if ($encodedSignature === '') { + throw InvalidTokenStructure::missingSignaturePart(); + } + + $header = $this->parseHeader($encodedHeaders); + + return new Plain( + new DataSet($header, $encodedHeaders), + new DataSet($this->parseClaims($encodedClaims), $encodedClaims), + $this->parseSignature($encodedSignature), + ); + } + + /** + * Splits the JWT string into an array + * + * @param non-empty-string $jwt + * + * @return string[] + * + * @throws InvalidTokenStructure When JWT doesn't have all parts. + */ + private function splitJwt(string $jwt): array + { + $data = explode('.', $jwt); + + if (count($data) !== 3) { + throw InvalidTokenStructure::missingOrNotEnoughSeparators(); + } + + return $data; + } + + /** + * Parses the header from a string + * + * @param non-empty-string $data + * + * @return array + * + * @throws UnsupportedHeaderFound When an invalid header is informed. + * @throws InvalidTokenStructure When parsed content isn't an array. + */ + private function parseHeader(string $data): array + { + $header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); + + if (! is_array($header)) { + throw InvalidTokenStructure::arrayExpected('headers'); + } + + $this->guardAgainstEmptyStringKeys($header, 'headers'); + + if (array_key_exists('enc', $header)) { + throw UnsupportedHeaderFound::encryption(); + } + + if (! array_key_exists('typ', $header)) { + $header['typ'] = 'JWT'; + } + + return $header; + } + + /** + * Parses the claim set from a string + * + * @param non-empty-string $data + * + * @return array + * + * @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates. + */ + private function parseClaims(string $data): array + { + $claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); + + if (! is_array($claims)) { + throw InvalidTokenStructure::arrayExpected('claims'); + } + + $this->guardAgainstEmptyStringKeys($claims, 'claims'); + + if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) { + $claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE]; + } + + foreach (RegisteredClaims::DATE_CLAIMS as $claim) { + if (! array_key_exists($claim, $claims)) { + continue; + } + + $claims[$claim] = $this->convertDate($claims[$claim]); + } + + return $claims; + } + + /** + * @param array $array + * @param non-empty-string $part + * + * @phpstan-assert array $array + */ + private function guardAgainstEmptyStringKeys(array $array, string $part): void + { + foreach ($array as $key => $value) { + if ($key === '') { + throw InvalidTokenStructure::arrayExpected($part); + } + } + } + + /** @throws InvalidTokenStructure */ + private function convertDate(int|float|string $timestamp): DateTimeImmutable + { + if (! is_numeric($timestamp)) { + throw InvalidTokenStructure::dateIsNotParseable($timestamp); + } + + $normalizedTimestamp = number_format((float) $timestamp, self::MICROSECOND_PRECISION, '.', ''); + + $date = DateTimeImmutable::createFromFormat('U.u', $normalizedTimestamp); + + if ($date === false) { + throw InvalidTokenStructure::dateIsNotParseable($normalizedTimestamp); + } + + return $date; + } + + /** + * Returns the signature from given data + * + * @param non-empty-string $data + */ + private function parseSignature(string $data): Signature + { + $hash = $this->decoder->base64UrlDecode($data); + + return new Signature($hash, $data); + } +} diff --git a/vendor/lcobucci/jwt/src/Token/Plain.php b/vendor/lcobucci/jwt/src/Token/Plain.php new file mode 100644 index 0000000..6af388d --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/Plain.php @@ -0,0 +1,85 @@ +headers; + } + + public function claims(): DataSet + { + return $this->claims; + } + + public function signature(): Signature + { + return $this->signature; + } + + public function payload(): string + { + return $this->headers->toString() . '.' . $this->claims->toString(); + } + + public function isPermittedFor(string $audience): bool + { + return in_array($audience, $this->claims->get(RegisteredClaims::AUDIENCE, []), true); + } + + public function isIdentifiedBy(string $id): bool + { + return $this->claims->get(RegisteredClaims::ID) === $id; + } + + public function isRelatedTo(string $subject): bool + { + return $this->claims->get(RegisteredClaims::SUBJECT) === $subject; + } + + public function hasBeenIssuedBy(string ...$issuers): bool + { + return in_array($this->claims->get(RegisteredClaims::ISSUER), $issuers, true); + } + + public function hasBeenIssuedBefore(DateTimeInterface $now): bool + { + return $now >= $this->claims->get(RegisteredClaims::ISSUED_AT); + } + + public function isMinimumTimeBefore(DateTimeInterface $now): bool + { + return $now >= $this->claims->get(RegisteredClaims::NOT_BEFORE); + } + + public function isExpired(DateTimeInterface $now): bool + { + if (! $this->claims->has(RegisteredClaims::EXPIRATION_TIME)) { + return false; + } + + return $now >= $this->claims->get(RegisteredClaims::EXPIRATION_TIME); + } + + public function toString(): string + { + return $this->headers->toString() . '.' + . $this->claims->toString() . '.' + . $this->signature->toString(); + } +} diff --git a/vendor/lcobucci/jwt/src/Token/RegisteredClaimGiven.php b/vendor/lcobucci/jwt/src/Token/RegisteredClaimGiven.php new file mode 100644 index 0000000..ce40a6a --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/RegisteredClaimGiven.php @@ -0,0 +1,21 @@ +hash; + } + + /** + * Returns the encoded version of the signature + * + * @return non-empty-string + */ + public function toString(): string + { + return $this->encoded; + } +} diff --git a/vendor/lcobucci/jwt/src/Token/UnsupportedHeaderFound.php b/vendor/lcobucci/jwt/src/Token/UnsupportedHeaderFound.php new file mode 100644 index 0000000..1824078 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/UnsupportedHeaderFound.php @@ -0,0 +1,15 @@ +claims(); + + if (! $claims->has($this->claim)) { + throw ConstraintViolation::error('The token does not have the claim "' . $this->claim . '"', $this); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/HasClaimWithValue.php b/vendor/lcobucci/jwt/src/Validation/Constraint/HasClaimWithValue.php new file mode 100644 index 0000000..d3ba1d6 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/HasClaimWithValue.php @@ -0,0 +1,42 @@ +claims(); + + if (! $claims->has($this->claim)) { + throw ConstraintViolation::error('The token does not have the claim "' . $this->claim . '"', $this); + } + + if ($claims->get($this->claim) !== $this->expectedValue) { + throw ConstraintViolation::error( + 'The claim "' . $this->claim . '" does not have the expected value', + $this, + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/IdentifiedBy.php b/vendor/lcobucci/jwt/src/Validation/Constraint/IdentifiedBy.php new file mode 100644 index 0000000..44541a7 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/IdentifiedBy.php @@ -0,0 +1,26 @@ +isIdentifiedBy($this->id)) { + throw ConstraintViolation::error( + 'The token is not identified with the expected ID', + $this, + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/IssuedBy.php b/vendor/lcobucci/jwt/src/Validation/Constraint/IssuedBy.php new file mode 100644 index 0000000..8ba3890 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/IssuedBy.php @@ -0,0 +1,30 @@ +issuers = $issuers; + } + + public function assert(Token $token): void + { + if (! $token->hasBeenIssuedBy(...$this->issuers)) { + throw ConstraintViolation::error( + 'The token was not issued by the given issuers', + $this, + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/LeewayCannotBeNegative.php b/vendor/lcobucci/jwt/src/Validation/Constraint/LeewayCannotBeNegative.php new file mode 100644 index 0000000..53abc0d --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/LeewayCannotBeNegative.php @@ -0,0 +1,15 @@ +leeway = $this->guardLeeway($leeway); + } + + private function guardLeeway(?DateInterval $leeway): DateInterval + { + if ($leeway === null) { + return new DateInterval('PT0S'); + } + + if ($leeway->invert === 1) { + throw LeewayCannotBeNegative::create(); + } + + return $leeway; + } + + public function assert(Token $token): void + { + $now = $this->clock->now(); + + $this->assertIssueTime($token, $now->add($this->leeway)); + $this->assertMinimumTime($token, $now->add($this->leeway)); + $this->assertExpiration($token, $now->sub($this->leeway)); + } + + /** @throws ConstraintViolation */ + private function assertExpiration(Token $token, DateTimeInterface $now): void + { + if ($token->isExpired($now)) { + throw ConstraintViolation::error('The token is expired', $this); + } + } + + /** @throws ConstraintViolation */ + private function assertMinimumTime(Token $token, DateTimeInterface $now): void + { + if (! $token->isMinimumTimeBefore($now)) { + throw ConstraintViolation::error('The token cannot be used yet', $this); + } + } + + /** @throws ConstraintViolation */ + private function assertIssueTime(Token $token, DateTimeInterface $now): void + { + if (! $token->hasBeenIssuedBefore($now)) { + throw ConstraintViolation::error('The token was issued in the future', $this); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/PermittedFor.php b/vendor/lcobucci/jwt/src/Validation/Constraint/PermittedFor.php new file mode 100644 index 0000000..48544c9 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/PermittedFor.php @@ -0,0 +1,26 @@ +isPermittedFor($this->audience)) { + throw ConstraintViolation::error( + 'The token is not allowed to be used by this audience', + $this, + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/RelatedTo.php b/vendor/lcobucci/jwt/src/Validation/Constraint/RelatedTo.php new file mode 100644 index 0000000..1649362 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/RelatedTo.php @@ -0,0 +1,26 @@ +isRelatedTo($this->subject)) { + throw ConstraintViolation::error( + 'The token is not related to the expected subject', + $this, + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWith.php b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWith.php new file mode 100644 index 0000000..5c8e265 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWith.php @@ -0,0 +1,32 @@ +headers()->get('alg') !== $this->signer->algorithmId()) { + throw ConstraintViolation::error('Token signer mismatch', $this); + } + + if (! $this->signer->verify($token->signature()->hash(), $token->payload(), $this->key)) { + throw ConstraintViolation::error('Token signature mismatch', $this); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithOneInSet.php b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithOneInSet.php new file mode 100644 index 0000000..fb542fb --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithOneInSet.php @@ -0,0 +1,38 @@ + */ + private readonly array $constraints; + + public function __construct(SignedWithUntilDate ...$constraints) + { + $this->constraints = $constraints; + } + + public function assert(Token $token): void + { + $errorMessage = 'It was not possible to verify the signature of the token, reasons:'; + + foreach ($this->constraints as $constraint) { + try { + $constraint->assert($token); + + return; + } catch (ConstraintViolation $violation) { + $errorMessage .= PHP_EOL . '- ' . $violation->getMessage(); + } + } + + throw ConstraintViolation::error($errorMessage, $this); + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithUntilDate.php b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithUntilDate.php new file mode 100644 index 0000000..85429e8 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithUntilDate.php @@ -0,0 +1,47 @@ +verifySignature = new SignedWith($signer, $key); + + $this->clock = $clock ?? new class () implements ClockInterface { + public function now(): DateTimeImmutable + { + return new DateTimeImmutable(); + } + }; + } + + public function assert(Token $token): void + { + if ($this->validUntil < $this->clock->now()) { + throw ConstraintViolation::error( + 'This constraint was only usable until ' + . $this->validUntil->format(DateTimeInterface::RFC3339), + $this, + ); + } + + $this->verifySignature->assert($token); + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/StrictValidAt.php b/vendor/lcobucci/jwt/src/Validation/Constraint/StrictValidAt.php new file mode 100644 index 0000000..93db0a3 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/StrictValidAt.php @@ -0,0 +1,84 @@ +leeway = $this->guardLeeway($leeway); + } + + private function guardLeeway(?DateInterval $leeway): DateInterval + { + if ($leeway === null) { + return new DateInterval('PT0S'); + } + + if ($leeway->invert === 1) { + throw LeewayCannotBeNegative::create(); + } + + return $leeway; + } + + public function assert(Token $token): void + { + if (! $token instanceof UnencryptedToken) { + throw ConstraintViolation::error('You should pass a plain token', $this); + } + + $now = $this->clock->now(); + + $this->assertIssueTime($token, $now->add($this->leeway)); + $this->assertMinimumTime($token, $now->add($this->leeway)); + $this->assertExpiration($token, $now->sub($this->leeway)); + } + + /** @throws ConstraintViolation */ + private function assertExpiration(UnencryptedToken $token, DateTimeInterface $now): void + { + if (! $token->claims()->has(Token\RegisteredClaims::EXPIRATION_TIME)) { + throw ConstraintViolation::error('"Expiration Time" claim missing', $this); + } + + if ($token->isExpired($now)) { + throw ConstraintViolation::error('The token is expired', $this); + } + } + + /** @throws ConstraintViolation */ + private function assertMinimumTime(UnencryptedToken $token, DateTimeInterface $now): void + { + if (! $token->claims()->has(Token\RegisteredClaims::NOT_BEFORE)) { + throw ConstraintViolation::error('"Not Before" claim missing', $this); + } + + if (! $token->isMinimumTimeBefore($now)) { + throw ConstraintViolation::error('The token cannot be used yet', $this); + } + } + + /** @throws ConstraintViolation */ + private function assertIssueTime(UnencryptedToken $token, DateTimeInterface $now): void + { + if (! $token->claims()->has(Token\RegisteredClaims::ISSUED_AT)) { + throw ConstraintViolation::error('"Issued At" claim missing', $this); + } + + if (! $token->hasBeenIssuedBefore($now)) { + throw ConstraintViolation::error('The token was issued in the future', $this); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/ConstraintViolation.php b/vendor/lcobucci/jwt/src/Validation/ConstraintViolation.php new file mode 100644 index 0000000..17c7546 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/ConstraintViolation.php @@ -0,0 +1,24 @@ +|null $constraint */ + public function __construct( + string $message = '', + public readonly ?string $constraint = null, + ) { + parent::__construct($message); + } + + /** @param non-empty-string $message */ + public static function error(string $message, Constraint $constraint): self + { + return new self(message: $message, constraint: $constraint::class); + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/NoConstraintsGiven.php b/vendor/lcobucci/jwt/src/Validation/NoConstraintsGiven.php new file mode 100644 index 0000000..0ef80d2 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/NoConstraintsGiven.php @@ -0,0 +1,11 @@ +getMessage(); + }, + $violations, + ); + + $message = "The token violates some mandatory constraints, details:\n"; + $message .= implode("\n", $violations); + + return $message; + } + + /** @return ConstraintViolation[] */ + public function violations(): array + { + return $this->violations; + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/SignedWith.php b/vendor/lcobucci/jwt/src/Validation/SignedWith.php new file mode 100644 index 0000000..e721095 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/SignedWith.php @@ -0,0 +1,8 @@ +checkConstraint($constraint, $token, $violations); + } + + if ($violations) { + throw RequiredConstraintsViolated::fromViolations(...$violations); + } + } + + /** @param ConstraintViolation[] $violations */ + private function checkConstraint( + Constraint $constraint, + Token $token, + array &$violations, + ): void { + try { + $constraint->assert($token); + } catch (ConstraintViolation $e) { + $violations[] = $e; + } + } + + public function validate(Token $token, Constraint ...$constraints): bool + { + if ($constraints === []) { + throw new NoConstraintsGiven('No constraint given.'); + } + + try { + foreach ($constraints as $constraint) { + $constraint->assert($token); + } + + return true; + } catch (ConstraintViolation) { + return false; + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validator.php b/vendor/lcobucci/jwt/src/Validator.php new file mode 100644 index 0000000..d0ce4b8 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validator.php @@ -0,0 +1,20 @@ + + */ +#[AsCommand(name: 'lexik:jwt:check-config', description: 'Checks that the bundle is properly configured.')] +final class CheckConfigCommand extends Command +{ + private KeyLoaderInterface $keyLoader; + + private string $signatureAlgorithm; + + public function __construct(KeyLoaderInterface $keyLoader, string $signatureAlgorithm) + { + $this->keyLoader = $keyLoader; + $this->signatureAlgorithm = $signatureAlgorithm; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + $this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PRIVATE); + // No public key for HMAC + if (!str_contains($this->signatureAlgorithm, 'HS')) { + $this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PUBLIC); + } + } catch (\RuntimeException $e) { + $output->writeln('' . $e->getMessage() . ''); + + return Command::FAILURE; + } + + $output->writeln('The configuration seems correct.'); + + return Command::SUCCESS; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Command/EnableEncryptionConfigCommand.php b/vendor/lexik/jwt-authentication-bundle/Command/EnableEncryptionConfigCommand.php new file mode 100644 index 0000000..e22fa6a --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Command/EnableEncryptionConfigCommand.php @@ -0,0 +1,349 @@ + + */ +#[AsCommand(name: 'lexik:jwt:enable-encryption', description: 'Enable Web-Token encryption support.')] +final class EnableEncryptionConfigCommand extends AbstractConfigCommand +{ + /** + * @deprecated + */ + protected static $defaultName = 'lexik:jwt:enable-encryption'; + + /** + * @var ?AlgorithmManagerFactory + */ + private $algorithmManagerFactory; + + public function __construct( + ?AlgorithmManagerFactory $algorithmManagerFactory = null + ) { + parent::__construct(); + + $this->algorithmManagerFactory = $algorithmManagerFactory; + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setName(static::$defaultName) + ->setDescription('Enable Web-Token encryption support.') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force the modification of the configuration, even if already set.') + ; + } + + public function isEnabled(): bool + { + return $this->algorithmManagerFactory !== null; + } + + /** + * {@inheritdoc} + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $force = $input->getOption('force'); + $this->checkRequirements(); + $io = new SymfonyStyle($input, $output); + $io->title('Web-Token Encryption support'); + $io->info('This tool will help you enabling the encryption support for Web-Token'); + + $algorithms = $this->algorithmManagerFactory->all(); + $availableKeyEncryptionAlgorithms = array_map( + static function (Algorithm $algorithm): string { + return $algorithm->name(); + }, + array_filter($algorithms, static function (Algorithm $algorithm): bool { + return ($algorithm instanceof KeyEncryptionAlgorithm && $algorithm->name() !== 'dir'); + }) + ); + $availableContentEncryptionAlgorithms = array_map( + static function (Algorithm $algorithm): string { + return $algorithm->name(); + }, + array_filter($algorithms, static function (Algorithm $algorithm): bool { + return $algorithm instanceof ContentEncryptionAlgorithm; + }) + ); + + $keyEncryptionAlgorithmAlias = $io->choice('Key Encryption Algorithm', $availableKeyEncryptionAlgorithms); + $contentEncryptionAlgorithmAlias = $io->choice('Content Encryption Algorithm', $availableContentEncryptionAlgorithms); + $keyEncryptionAlgorithm = $algorithms[$keyEncryptionAlgorithmAlias]; + $contentEncryptionAlgorithm = $algorithms[$contentEncryptionAlgorithmAlias]; + + $continueOnDecryptionFailure = 'yes' === $io->choice('Continue decryption on failure', ['yes', 'no'], 'no'); + + $extension = $this->findExtension('lexik_jwt_authentication'); + $config = $this->getConfiguration($extension); + if (!isset($config['encoder']['service']) || $config['encoder']['service'] !== 'lexik_jwt_authentication.encoder.web_token') { + $io->error('Please migrate to WebToken first.'); + return self::FAILURE; + } + if (!$force && ($config['access_token_issuance']['encryption']['enabled'] || $config['access_token_verification']['encryption']['enabled'])) { + $io->error('Encryption support is already enabled.'); + return self::FAILURE; + } + + $key = $this->generatePrivateKey($keyEncryptionAlgorithm); + $keyset = $this->generatePublicKeyset($key, $keyEncryptionAlgorithm->name()); + + $config['access_token_issuance']['encryption'] = [ + 'enabled' => true, + 'key_encryption_algorithm' => $keyEncryptionAlgorithm->name(), + 'content_encryption_algorithm' => $contentEncryptionAlgorithm->name(), + 'key' => json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), + ]; + $config['access_token_verification']['encryption'] = [ + 'enabled' => true, + 'continue_on_decryption_failure' => $continueOnDecryptionFailure, + 'header_checkers' => ['iat_with_clock_skew', 'nbf_with_clock_skew', 'exp_with_clock_skew'], + 'allowed_key_encryption_algorithms' => [$keyEncryptionAlgorithm->name()], + 'allowed_content_encryption_algorithms' => [$contentEncryptionAlgorithm->name()], + 'keyset' => json_encode($keyset, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), + ]; + + $io->comment('Please replace the current configuration with the following parameters.'); + $io->section('# config/packages/lexik_jwt_authentication.yaml'); + $io->writeln(Yaml::dump([$extension->getAlias() => $config], 10)); + $io->section('# End of file'); + + return self::SUCCESS; + } + + private function generatePublicKeyset(JWK $key, string $algorithm): JWKSet + { + $keyset = new JWKSet([$key->toPublic()]); + switch ($key->get('kty')) { + case 'oct': + return $this->withOctKeys($keyset, $algorithm); + case 'OKP': + return $this->withOkpKeys($keyset, $algorithm, $key->get('crv')); + case 'EC': + return $this->withEcKeys($keyset, $algorithm, $key->get('crv')); + case 'RSA': + return $this->withRsaKeys($keyset, $algorithm); + default: + throw new \InvalidArgumentException('Unsupported key type.'); + } + } + + private function withOctKeys(JWKSet $keyset, string $algorithm): JWKSet + { + $size = $this->getKeySize($algorithm); + + return $keyset + ->with($this->createOctKey($size, $algorithm)->toPublic()) + ->with($this->createOctKey($size, $algorithm)->toPublic()) + ; + } + + private function withRsaKeys(JWKSet $keyset, string $algorithm): JWKSet + { + return $keyset + ->with($this->createRsaKey(2048, $algorithm)->toPublic()) + ->with($this->createRsaKey(2048, $algorithm)->toPublic()) + ; + } + + private function withOkpKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet + { + return $keyset + ->with($this->createOkpKey($curve, $algorithm)->toPublic()) + ->with($this->createOkpKey($curve, $algorithm)->toPublic()) + ; + } + + private function withEcKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet + { + return $keyset + ->with($this->createEcKey($curve, $algorithm)->toPublic()) + ->with($this->createEcKey($curve, $algorithm)->toPublic()) + ; + } + + private function generatePrivateKey(KeyEncryptionAlgorithm $algorithm): JWK + { + $keyType = current($algorithm->allowedKeyTypes()); + switch ($keyType) { + case 'oct': + return $this->createOctKey($this->getKeySize($algorithm->name()), $algorithm->name()); + case 'OKP': + return $this->createOkpKey('X25519', $algorithm->name()); + case 'EC': + return $this->createEcKey('P-256', $algorithm->name()); + case 'RSA': + return $this->createRsaKey($this->getKeySize($algorithm->name()), $algorithm->name()); + default: + throw new \InvalidArgumentException('Unsupported key type.'); + } + } + + private function checkRequirements(): void + { + $requirements = [ + JoseFrameworkBundle::class => 'web-token/jwt-bundle', + JWKFactory::class => 'web-token/jwt-key-mgmt', + ClaimCheckerManager::class => 'web-token/jwt-checker', + JWEBuilder::class => 'web-token/jwt-encryption', + ]; + if ($this->algorithmManagerFactory === null) { + throw new \RuntimeException('The package "web-token/jwt-bundle" is missing. Please install it for using this migration tool.'); + } + foreach (array_keys($requirements) as $requirement) { + if (!class_exists($requirement)) { + throw new \RuntimeException(sprintf('The package "%s" is missing. Please install it for using this migration tool.', $requirement)); + } + } + } + private function getConfiguration(ExtensionInterface $extension): array + { + $container = $this->compileContainer(); + + $config = $this->getConfig($extension, $container); + $uselessParameters = ['secret_key', 'public_key', 'pass_phrase', 'private_key_path', 'public_key_path', 'additional_public_keys']; + foreach ($uselessParameters as $parameter) { + unset($config[$parameter]); + } + + return $config; + } + + private function createOctKey(int $size, string $algorithm): JWK + { + return JWKFactory::createOctKey($size, $this->getOptions($algorithm)); + } + + private function createRsaKey(int $size, string $algorithm): JWK + { + return JWKFactory::createRSAKey($size, $this->getOptions($algorithm)); + } + + private function createOkpKey(string $curve, string $algorithm): JWK + { + return JWKFactory::createOKPKey($curve, $this->getOptions($algorithm)); + } + + private function createEcKey(string $curve, string $algorithm): JWK + { + return JWKFactory::createECKey($curve, $this->getOptions($algorithm)); + } + + private function compileContainer(): ContainerBuilder + { + $kernel = clone $this->getApplication()->getKernel(); + $kernel->boot(); + + $method = new \ReflectionMethod($kernel, 'buildContainer'); + $container = $method->invoke($kernel); + $container->getCompiler()->compile($container); + + return $container; + } + + private function getConfig(ExtensionInterface $extension, ContainerBuilder $container) + { + return $container->resolveEnvPlaceholders( + $container->getParameterBag()->resolveValue( + $this->getConfigForExtension($extension, $container) + ) + ); + } + + private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array + { + $extensionAlias = $extension->getAlias(); + + $extensionConfig = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof ValidateEnvPlaceholdersPass) { + $extensionConfig = $pass->getExtensionConfig(); + break; + } + } + + if (isset($extensionConfig[$extensionAlias])) { + return $extensionConfig[$extensionAlias]; + } + + // Fall back to default config if the extension has one + + if (!$extension instanceof ConfigurationExtensionInterface) { + throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); + } + + $configs = $container->getExtensionConfig($extensionAlias); + $configuration = $extension->getConfiguration($configs, $container); + $this->validateConfiguration($extension, $configuration); + + return (new Processor())->processConfiguration($configuration, $configs); + } + + private function getKeySize(string $algorithm): int + { + switch ($algorithm) { + case 'RSA1_5': + case 'RSA-OAEP': + case 'RSA-OAEP-256': + return 4096; + case 'A128KW': + case 'A128GCMKW': + case 'PBES2-HS256+A128KW': + return 128; + case 'A192KW': + case 'A192GCMKW': + case 'PBES2-HS384+A192KW': + return 192; + case 'A256KW': + case 'A256GCMKW': + case 'PBES2-HS512+A256KW': + return 256; + default: + throw new \LogicException('Unsupported algorithm'); + } + } + + private function getOptions(string $algorithm): array + { + return [ + 'use' => 'enc', + 'alg' => $algorithm, + 'kid' => Base64UrlSafe::encodeUnpadded(random_bytes(16)) + ]; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Command/GenerateKeyPairCommand.php b/vendor/lexik/jwt-authentication-bundle/Command/GenerateKeyPairCommand.php new file mode 100644 index 0000000..65bfbd8 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Command/GenerateKeyPairCommand.php @@ -0,0 +1,217 @@ + + */ +#[AsCommand(name: self::NAME, description: 'Generate public/private keys for use in your application.')] +final class GenerateKeyPairCommand extends Command +{ + private const NAME = 'lexik:jwt:generate-keypair'; + + private const ACCEPTED_ALGORITHMS = [ + 'RS256', + 'RS384', + 'RS512', + 'HS256', + 'HS384', + 'HS512', + 'ES256', + 'ES384', + 'ES512', + ]; + + private Filesystem $filesystem; + + private ?string $secretKey; + + private ?string $publicKey; + + private ?string $passphrase; + + private string $algorithm; + + public function __construct(Filesystem $filesystem, ?string $secretKey, ?string $publicKey, ?string $passphrase, string $algorithm) + { + $this->filesystem = $filesystem; + $this->secretKey = $secretKey; + $this->publicKey = $publicKey; + $this->passphrase = $passphrase; + $this->algorithm = $algorithm; + + parent::__construct(); + } + + protected function configure(): void + { + $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not update key files.'); + $this->addOption('skip-if-exists', null, InputOption::VALUE_NONE, 'Do not update key files if they already exist.'); + $this->addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite key files if they already exist.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + if (!in_array($this->algorithm, self::ACCEPTED_ALGORITHMS, true)) { + $io->error(sprintf('Cannot generate key pair with the provided algorithm `%s`.', $this->algorithm)); + + return Command::FAILURE; + } + + [$secretKey, $publicKey] = $this->generateKeyPair($this->passphrase); + + if (true === $input->getOption('dry-run')) { + $io->success('Your keys have been generated!'); + $io->newLine(); + $io->writeln(sprintf('Update your private key in %s:', $this->secretKey)); + $io->writeln($secretKey); + $io->newLine(); + $io->writeln(sprintf('Update your public key in %s:', $this->publicKey)); + $io->writeln($publicKey); + + return Command::SUCCESS; + } + + if (null === $this->secretKey || null === $this->publicKey) { + throw new LogicException(sprintf('The "lexik_jwt_authentication.secret_key" and "lexik_jwt_authentication.public_key" config options must not be empty for using the "%s" command.', self::NAME)); + } + + $alreadyExists = $this->filesystem->exists($this->secretKey) || $this->filesystem->exists($this->publicKey); + + if ($alreadyExists) { + try { + $this->handleExistingKeys($input); + } catch (\RuntimeException $e) { + if (0 === $e->getCode()) { + $io->comment($e->getMessage()); + + return Command::SUCCESS; + } + + $io->error($e->getMessage()); + + return Command::FAILURE; + } + + if (!$io->confirm('You are about to replace your existing keys. Are you sure you wish to continue?')) { + $io->comment('Your action was canceled.'); + + return Command::SUCCESS; + } + } + + $this->filesystem->dumpFile($this->secretKey, $secretKey); + $this->filesystem->dumpFile($this->publicKey, $publicKey); + + $io->success('Done!'); + + return Command::SUCCESS; + } + + private function handleExistingKeys(InputInterface $input): void + { + if (true === $input->getOption('skip-if-exists') && true === $input->getOption('overwrite')) { + throw new \RuntimeException('Both options `--skip-if-exists` and `--overwrite` cannot be combined.', 1); + } + + if (true === $input->getOption('skip-if-exists')) { + throw new \RuntimeException('Your key files already exist, they won\'t be overriden.', 0); + } + + if (false === $input->getOption('overwrite')) { + throw new \RuntimeException('Your keys already exist. Use the `--overwrite` option to force regeneration.', 1); + } + } + + private function generateKeyPair(?string $passphrase): array + { + $config = $this->buildOpenSSLConfiguration(); + + $resource = \openssl_pkey_new($config); + if (false === $resource) { + throw new \RuntimeException(\openssl_error_string()); + } + + $success = \openssl_pkey_export($resource, $privateKey, $passphrase); + + if (false === $success) { + throw new \RuntimeException(\openssl_error_string()); + } + + $publicKeyData = \openssl_pkey_get_details($resource); + + if (false === $publicKeyData) { + throw new \RuntimeException(\openssl_error_string()); + } + + $publicKey = $publicKeyData['key']; + + return [$privateKey, $publicKey]; + } + + private function buildOpenSSLConfiguration(): array + { + $digestAlgorithms = [ + 'RS256' => 'sha256', + 'RS384' => 'sha384', + 'RS512' => 'sha512', + 'HS256' => 'sha256', + 'HS384' => 'sha384', + 'HS512' => 'sha512', + 'ES256' => 'sha256', + 'ES384' => 'sha384', + 'ES512' => 'sha512', + ]; + $privateKeyBits = [ + 'RS256' => 2048, + 'RS384' => 2048, + 'RS512' => 4096, + 'HS256' => 512, + 'HS384' => 512, + 'HS512' => 512, + 'ES256' => 384, + 'ES384' => 512, + 'ES512' => 1024, + ]; + $privateKeyTypes = [ + 'RS256' => \OPENSSL_KEYTYPE_RSA, + 'RS384' => \OPENSSL_KEYTYPE_RSA, + 'RS512' => \OPENSSL_KEYTYPE_RSA, + 'HS256' => \OPENSSL_KEYTYPE_DH, + 'HS384' => \OPENSSL_KEYTYPE_DH, + 'HS512' => \OPENSSL_KEYTYPE_DH, + 'ES256' => \OPENSSL_KEYTYPE_EC, + 'ES384' => \OPENSSL_KEYTYPE_EC, + 'ES512' => \OPENSSL_KEYTYPE_EC, + ]; + + $curves = [ + 'ES256' => 'secp256k1', + 'ES384' => 'secp384r1', + 'ES512' => 'secp521r1', + ]; + + $config = [ + 'digest_alg' => $digestAlgorithms[$this->algorithm], + 'private_key_type' => $privateKeyTypes[$this->algorithm], + 'private_key_bits' => $privateKeyBits[$this->algorithm], + ]; + + if (isset($curves[$this->algorithm])) { + $config['curve_name'] = $curves[$this->algorithm]; + } + + return $config; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Command/GenerateTokenCommand.php b/vendor/lexik/jwt-authentication-bundle/Command/GenerateTokenCommand.php new file mode 100644 index 0000000..e6ca98d --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Command/GenerateTokenCommand.php @@ -0,0 +1,97 @@ + + */ +#[AsCommand(name: 'lexik:jwt:generate-token', description: 'Generates a JWT token for a given user.')] +class GenerateTokenCommand extends Command +{ + private JWTTokenManagerInterface $tokenManager; + + /** @var \Traversable */ + private \Traversable $userProviders; + + public function __construct(JWTTokenManagerInterface $tokenManager, \Traversable $userProviders) + { + $this->tokenManager = $tokenManager; + $this->userProviders = $userProviders; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->addArgument('username', InputArgument::REQUIRED, 'Username of user to be retreived from user provider') + ->addOption('ttl', 't', InputOption::VALUE_REQUIRED, 'Ttl in seconds to be added to current time. If not provided, the ttl configured in the bundle will be used. Use 0 to generate token without exp') + ->addOption('user-class', 'c', InputOption::VALUE_REQUIRED, 'Userclass is used to determine which user provider to use') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($this->userProviders instanceof \Countable && 0 === \count($this->userProviders)) { + throw new \RuntimeException('You must have at least 1 configured user provider to generate a token.'); + } + + if (!$userClass = $input->getOption('user-class')) { + if (1 < \count($userProviders = iterator_to_array($this->userProviders))) { + throw new \RuntimeException('The "--user-class" option must be passed as there is more than 1 configured user provider.'); + } + + $userProvider = current($userProviders); + } else { + $userProvider = null; + + foreach ($this->userProviders as $provider) { + if ($provider->supportsClass($userClass)) { + $userProvider = $provider; + + break; + } + } + + if (null === $userProvider) { + throw new \RuntimeException(sprintf('There is no configured user provider for class "%s".', $userClass)); + } + } + + $user = $userProvider->loadUserByIdentifier($input->getArgument('username')); + + $payload = []; + if (null !== $input->getOption('ttl') && ((int) $input->getOption('ttl')) == 0) { + $payload['exp'] = 0; + } elseif (null !== $input->getOption('ttl') && ((int) $input->getOption('ttl')) > 0) { + $payload['exp'] = time() + $input->getOption('ttl'); + } + + $token = $this->tokenManager->createFromPayload($user, $payload); + + $output->writeln([ + '', + '' . $token . '', + '', + ]); + + return Command::SUCCESS; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Command/MigrateConfigCommand.php b/vendor/lexik/jwt-authentication-bundle/Command/MigrateConfigCommand.php new file mode 100644 index 0000000..235748f --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Command/MigrateConfigCommand.php @@ -0,0 +1,322 @@ + + */ +#[AsCommand(name: 'lexik:jwt:migrate-config', description: 'Migrate LexikJWTAuthenticationBundle configuration to the Web-Token one.')] +final class MigrateConfigCommand extends AbstractConfigCommand +{ + /** + * @deprecated + */ + protected static $defaultName = 'lexik:jwt:migrate-config'; + + /** + * @var KeyLoaderInterface + */ + private $keyLoader; + + /** + * @var string + */ + private $signatureAlgorithm; + + /** + * @var string + */ + private $passphrase; + + public function __construct( + KeyLoaderInterface $keyLoader, + string $passphrase, + string $signatureAlgorithm + ) { + parent::__construct(); + $this->keyLoader = $keyLoader; + $this->passphrase = $passphrase === '' ? null : $passphrase; + $this->signatureAlgorithm = $signatureAlgorithm; + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setName(static::$defaultName) + ->setDescription('Migrate the configuration to Web-Token') + ; + } + + /** + * {@inheritdoc} + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->checkRequirements(); + $io = new SymfonyStyle($input, $output); + $io->title('Web-Token Migration tool'); + $io->info('This tool will help you converting the current LexikJWTAuthenticationBundle configuration to support Web-Token'); + + try { + $key = $this->getKey(); + $keyset = $this->getKeyset($key, $this->signatureAlgorithm); + } catch (\RuntimeException $e) { + $io->error('An error occurred: ' . $e->getMessage()); + + return self::FAILURE; + } + + $extension = $this->findExtension('lexik_jwt_authentication'); + $config = $this->getConfiguration($extension); + + foreach ($config['set_cookies'] as $cookieConfig) { + if ($cookieConfig['split'] !== []) { + $io->error('Web-Token is not compatible with the cookie split feature. Please disable this option before using this migration tool.'); + + return self::FAILURE; + } + } + + $config['encoder'] = ['service' => 'lexik_jwt_authentication.encoder.web_token']; + $config['access_token_issuance'] = [ + 'enabled' => true, + 'signature' => [ + 'signature_algorithm' => $this->signatureAlgorithm, + 'key' => json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), + ] + ]; + $config['access_token_verification'] = [ + 'enabled' => true, + 'signature' => [ + 'allowed_signature_algorithms' => [$this->signatureAlgorithm], + 'keyset' => json_encode($keyset, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), + ] + ]; + + $io->comment('Please replace the current configuration with the following parameters.'); + $io->section('# config/packages/lexik_jwt_authentication.yaml'); + $io->writeln(Yaml::dump([$extension->getAlias() => $config], 10)); + $io->section('# End of file'); + + return self::SUCCESS; + } + + private function getKeyset(JWK $key, string $algorithm): JWKSet + { + $keyset = new JWKSet([$key->toPublic()]); + switch ($key->get('kty')) { + case 'oct': + return $this->withOctKeys($keyset, $algorithm); + case 'OKP': + return $this->withOkpKeys($keyset, $algorithm, $key->get('crv')); + case 'EC': + return $this->withEcKeys($keyset, $algorithm, $key->get('crv')); + case 'RSA': + return $this->withRsaKeys($keyset, $algorithm); + default: + throw new \InvalidArgumentException('Unsupported key type.'); + } + } + + private function withOctKeys(JWKSet $keyset, string $algorithm): JWKSet + { + $size = $this->getKeySize($algorithm); + + return $keyset + ->with($this->createOctKey($size, $algorithm)->toPublic()) + ->with($this->createOctKey($size, $algorithm)->toPublic()) + ; + } + + private function withRsaKeys(JWKSet $keyset, string $algorithm): JWKSet + { + return $keyset + ->with($this->createRsaKey(2048, $algorithm)->toPublic()) + ->with($this->createRsaKey(2048, $algorithm)->toPublic()) + ; + } + + private function withOkpKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet + { + return $keyset + ->with($this->createOkpKey($curve, $algorithm)->toPublic()) + ->with($this->createOkpKey($curve, $algorithm)->toPublic()) + ; + } + + private function withEcKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet + { + return $keyset + ->with($this->createEcKey($curve, $algorithm)->toPublic()) + ->with($this->createEcKey($curve, $algorithm)->toPublic()) + ; + } + + private function getKey(): JWK + { + $additionalValues = [ + 'use' => 'sig', + 'alg' => $this->signatureAlgorithm, + ]; + // No public key for HMAC + if (false !== strpos($this->signatureAlgorithm, 'HS')) { + return JWKFactory::createFromSecret( + $this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PUBLIC), + $additionalValues + ); + } + return JWKFactory::createFromKey( + $this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PRIVATE), + $this->passphrase, + $additionalValues + ); + } + + private function checkRequirements(): void + { + $requirements = [ + JoseFrameworkBundle::class => 'web-token/jwt-bundle', + JWKFactory::class => 'web-token/jwt-key-mgmt', + ClaimCheckerManager::class => 'web-token/jwt-checker', + JWSBuilder::class => 'web-token/jwt-signature', + ]; + + foreach (array_keys($requirements) as $requirement) { + if (!class_exists($requirement)) { + throw new \RuntimeException(sprintf('The package "%s" is missing. Please install it for using this migration tool.', $requirement)); + } + } + } + private function getConfiguration(ExtensionInterface $extension): array + { + $container = $this->compileContainer(); + + $config = $this->getConfig($extension, $container); + $uselessParameters = ['secret_key', 'public_key', 'pass_phrase', 'private_key_path', 'public_key_path', 'additional_public_keys']; + foreach ($uselessParameters as $parameter) { + unset($config[$parameter]); + } + + return $config; + } + + private function createOctKey(int $size, string $algorithm): JWK + { + return JWKFactory::createOctKey($size, $this->getOptions($algorithm)); + } + + private function createRsaKey(int $size, string $algorithm): JWK + { + return JWKFactory::createRSAKey($size, $this->getOptions($algorithm)); + } + + private function createOkpKey(string $curve, string $algorithm): JWK + { + return JWKFactory::createOKPKey($curve, $this->getOptions($algorithm)); + } + + private function createEcKey(string $curve, string $algorithm): JWK + { + return JWKFactory::createECKey($curve, $this->getOptions($algorithm)); + } + + private function compileContainer(): ContainerBuilder + { + $kernel = clone $this->getApplication()->getKernel(); + $kernel->boot(); + + $method = new \ReflectionMethod($kernel, 'buildContainer'); + $container = $method->invoke($kernel); + $container->getCompiler()->compile($container); + + return $container; + } + + private function getConfig(ExtensionInterface $extension, ContainerBuilder $container) + { + return $container->resolveEnvPlaceholders( + $container->getParameterBag()->resolveValue( + $this->getConfigForExtension($extension, $container) + ) + ); + } + + private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array + { + $extensionAlias = $extension->getAlias(); + + $extensionConfig = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof ValidateEnvPlaceholdersPass) { + $extensionConfig = $pass->getExtensionConfig(); + break; + } + } + + if (isset($extensionConfig[$extensionAlias])) { + return $extensionConfig[$extensionAlias]; + } + + // Fall back to default config if the extension has one + + if (!$extension instanceof ConfigurationExtensionInterface) { + throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); + } + + $configs = $container->getExtensionConfig($extensionAlias); + $configuration = $extension->getConfiguration($configs, $container); + $this->validateConfiguration($extension, $configuration); + + return (new Processor())->processConfiguration($configuration, $configs); + } + + private function getKeySize(string $algorithm): int + { + switch ($algorithm) { + case 'HS256': + case 'HS256/64': + return 256; + case 'HS384': + return 384; + case 'HS512': + return 512; + default: + throw new \LogicException('Unsupported algorithm'); + } + } + + private function getOptions(string $algorithm): array + { + return [ + 'use' => 'sig', + 'alg' => $algorithm, + 'kid' => Base64UrlSafe::encodeUnpadded(random_bytes(16)) + ]; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/ApiPlatformOpenApiPass.php b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/ApiPlatformOpenApiPass.php new file mode 100644 index 0000000..469290c --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/ApiPlatformOpenApiPass.php @@ -0,0 +1,52 @@ +hasDefinition('lexik_jwt_authentication.api_platform.openapi.factory') || !$container->hasParameter('security.firewalls')) { + return; + } + + $checkPath = null; + $usernamePath = null; + $passwordPath = null; + $firewalls = $container->getParameter('security.firewalls'); + foreach ($firewalls as $firewallName) { + if ($container->hasDefinition('security.authenticator.json_login.' . $firewallName)) { + $firewallOptions = $container->getDefinition('security.authenticator.json_login.' . $firewallName)->getArgument(4); + $checkPath = $firewallOptions['check_path']; + $usernamePath = $firewallOptions['username_path']; + $passwordPath = $firewallOptions['password_path']; + + break; + } + } + + $openApiFactoryDefinition = $container->getDefinition('lexik_jwt_authentication.api_platform.openapi.factory'); + $checkPathArg = $openApiFactoryDefinition->getArgument(1); + $usernamePathArg = $openApiFactoryDefinition->getArgument(2); + $passwordPathArg = $openApiFactoryDefinition->getArgument(3); + + if (!$checkPath && !$checkPathArg) { + $container->removeDefinition('lexik_jwt_authentication.api_platform.openapi.factory'); + + return; + } + + if (!$checkPathArg) { + $openApiFactoryDefinition->replaceArgument(1, $checkPath); + } + if (!$usernamePathArg) { + $openApiFactoryDefinition->replaceArgument(2, $usernamePath ?? 'username'); + } + if (!$passwordPathArg) { + $openApiFactoryDefinition->replaceArgument(3, $passwordPath ?? 'password'); + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/CollectPayloadEnrichmentsPass.php b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/CollectPayloadEnrichmentsPass.php new file mode 100644 index 0000000..ffbbb50 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/CollectPayloadEnrichmentsPass.php @@ -0,0 +1,22 @@ +hasDefinition('lexik_jwt_authentication.payload_enrichment')) { + return; + } + + $container->getDefinition('lexik_jwt_authentication.payload_enrichment') + ->replaceArgument(0, $this->findAndSortTaggedServices('lexik_jwt_authentication.payload_enrichment', $container)); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/WireGenerateTokenCommandPass.php b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/WireGenerateTokenCommandPass.php new file mode 100644 index 0000000..173c77f --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/WireGenerateTokenCommandPass.php @@ -0,0 +1,21 @@ +hasDefinition('lexik_jwt_authentication.generate_token_command') || !$container->hasDefinition('security.context_listener')) { + return; + } + + $container + ->getDefinition('lexik_jwt_authentication.generate_token_command') + ->replaceArgument(1, $container->getDefinition('security.context_listener')->getArgument(1)) + ; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Configuration.php b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000..d011c84 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Configuration.php @@ -0,0 +1,294 @@ +getRootNode() + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('public_key') + ->info('The key used to sign tokens (useless for HMAC). If not set, the key will be automatically computed from the secret key.') + ->defaultNull() + ->end() + ->arrayNode('additional_public_keys') + ->info('Multiple public keys to try to verify token signature. If none is given, it will use the key provided in "public_key".') + ->scalarPrototype()->end() + ->end() + ->scalarNode('secret_key') + ->info('The key used to sign tokens. It can be a raw secret (for HMAC), a raw RSA/ECDSA key or the path to a file itself being plaintext or PEM.') + ->defaultNull() + ->end() + ->scalarNode('pass_phrase') + ->info('The key passphrase (useless for HMAC)') + ->defaultValue('') + ->end() + ->scalarNode('token_ttl') + ->defaultValue(3600) + ->end() + ->booleanNode('allow_no_expiration') + ->info('Allow tokens without "exp" claim (i.e. indefinitely valid, no lifetime) to be considered valid. Caution: usage of this should be rare.') + ->defaultFalse() + ->end() + ->scalarNode('clock_skew') + ->defaultValue(0) + ->end() + ->arrayNode('encoder') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('service') + ->defaultValue('lexik_jwt_authentication.encoder.lcobucci') + ->end() + ->scalarNode('signature_algorithm') + ->defaultValue('RS256') + ->cannotBeEmpty() + ->end() + ->end() + ->end() + ->scalarNode('user_id_claim') + ->defaultValue('username') + ->cannotBeEmpty() + ->end() + ->append($this->getTokenExtractorsNode()) + ->scalarNode('remove_token_from_body_when_cookies_used') + ->defaultTrue() + ->end() + ->arrayNode('set_cookies') + ->fixXmlConfig('set_cookie') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->scalarNode('lifetime') + ->defaultNull() + ->info('The cookie lifetime. If null, the "token_ttl" option value will be used') + ->end() + ->enumNode('samesite') + ->values([Cookie::SAMESITE_NONE, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT]) + ->defaultValue(Cookie::SAMESITE_LAX) + ->end() + ->scalarNode('path')->defaultValue('/')->cannotBeEmpty()->end() + ->scalarNode('domain')->defaultNull()->end() + ->scalarNode('secure')->defaultTrue()->end() + ->scalarNode('httpOnly')->defaultTrue()->end() + ->scalarNode('partitioned')->defaultFalse()->end() + ->arrayNode('split') + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('api_platform') + ->canBeEnabled() + ->info('API Platform compatibility: add check_path in OpenAPI documentation.') + ->children() + ->scalarNode('check_path') + ->defaultNull() + ->info('The login check path to add in OpenAPI.') + ->end() + ->scalarNode('username_path') + ->defaultNull() + ->info('The path to the username in the JSON body.') + ->end() + ->scalarNode('password_path') + ->defaultNull() + ->info('The path to the password in the JSON body.') + ->end() + ->end() + ->end() + ->arrayNode('access_token_issuance') + ->fixXmlConfig('access_token_issuance') + ->canBeEnabled() + ->children() + ->arrayNode('signature') + ->fixXmlConfig('signature') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('algorithm') + ->isRequired() + ->info('The algorithm use to sign the access tokens.') + ->end() + ->scalarNode('key') + ->isRequired() + ->info('The signature key. It shall be JWK encoded.') + ->end() + ->end() + ->end() + ->arrayNode('encryption') + ->fixXmlConfig('encryption') + ->canBeEnabled() + ->children() + ->scalarNode('key_encryption_algorithm') + ->isRequired() + ->cannotBeEmpty() + ->info('The key encryption algorithm is used to encrypt the token.') + ->end() + ->scalarNode('content_encryption_algorithm') + ->isRequired() + ->cannotBeEmpty() + ->info('The key encryption algorithm is used to encrypt the token.') + ->end() + ->scalarNode('key') + ->isRequired() + ->info('The encryption key. It shall be JWK encoded.') + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('access_token_verification') + ->fixXmlConfig('access_token_verification') + ->canBeEnabled() + ->children() + ->arrayNode('signature') + ->fixXmlConfig('signature') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('header_checkers') + ->fixXmlConfig('header_checkers') + ->scalarPrototype()->end() + ->defaultValue([]) + ->info('The headers to be checked for validating the JWS.') + ->end() + ->arrayNode('claim_checkers') + ->fixXmlConfig('claim_checkers') + ->scalarPrototype()->end() + ->defaultValue(['exp_with_clock_skew', 'iat_with_clock_skew', 'nbf_with_clock_skew']) + ->info('The claims to be checked for validating the JWS.') + ->end() + ->arrayNode('mandatory_claims') + ->fixXmlConfig('mandatory_claims') + ->scalarPrototype()->end() + ->defaultValue([]) + ->info('The list of claims that shall be present in the JWS.') + ->end() + ->arrayNode('allowed_algorithms') + ->fixXmlConfig('allowed_algorithms') + ->scalarPrototype()->end() + ->requiresAtLeastOneElement() + ->info('The algorithms allowed to be used for token verification.') + ->end() + ->scalarNode('keyset') + ->isRequired() + ->info('The signature keyset. It shall be JWKSet encoded.') + ->end() + ->end() + ->end() + ->arrayNode('encryption') + ->fixXmlConfig('encryption') + ->canBeEnabled() + ->children() + ->booleanNode('continue_on_decryption_failure') + ->defaultFalse() + ->info('If enable, non-encrypted tokens or tokens that failed during decryption or verification processes are accepted.') + ->end() + ->arrayNode('header_checkers') + ->fixXmlConfig('header_checkers') + ->scalarPrototype()->end() + ->defaultValue(['iat_with_clock_skew', 'nbf_with_clock_skew', 'exp_with_clock_skew']) + ->info('The headers to be checked for validating the JWE.') + ->end() + ->arrayNode('allowed_key_encryption_algorithms') + ->fixXmlConfig('allowed_key_encryption_algorithms') + ->scalarPrototype()->end() + ->requiresAtLeastOneElement() + ->info('The key encryption algorithm is used to encrypt the token.') + ->end() + ->arrayNode('allowed_content_encryption_algorithms') + ->fixXmlConfig('allowed_content_encryption_algorithms') + ->scalarPrototype()->end() + ->requiresAtLeastOneElement() + ->info('The key encryption algorithm is used to encrypt the token.') + ->end() + ->scalarNode('keyset') + ->isRequired() + ->info('The encryption keyset. It shall be JWKSet encoded.') + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('blocklist_token') + ->addDefaultsIfNotSet() + ->canBeEnabled() + ->children() + ->scalarNode('cache') + ->defaultValue('cache.app') + ->info('Storage to track blocked tokens') + ->end() + ->end() + ->end() + ->end() + ->end(); + + return $treeBuilder; + } + + private function getTokenExtractorsNode(): ArrayNodeDefinition + { + $builder = new TreeBuilder('token_extractors'); + $node = $builder->getRootNode(); + $node + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('authorization_header') + ->addDefaultsIfNotSet() + ->canBeDisabled() + ->children() + ->scalarNode('prefix') + ->defaultValue('Bearer') + ->end() + ->scalarNode('name') + ->defaultValue('Authorization') + ->end() + ->end() + ->end() + ->arrayNode('cookie') + ->addDefaultsIfNotSet() + ->canBeEnabled() + ->children() + ->scalarNode('name') + ->defaultValue('BEARER') + ->end() + ->end() + ->end() + ->arrayNode('query_parameter') + ->addDefaultsIfNotSet() + ->canBeEnabled() + ->children() + ->scalarNode('name') + ->defaultValue('bearer') + ->end() + ->end() + ->end() + ->arrayNode('split_cookie') + ->canBeEnabled() + ->children() + ->arrayNode('cookies') + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->end() + ; + + return $node; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/DependencyInjection/LexikJWTAuthenticationExtension.php b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/LexikJWTAuthenticationExtension.php new file mode 100644 index 0000000..bc8e631 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/LexikJWTAuthenticationExtension.php @@ -0,0 +1,247 @@ +processConfiguration($configuration, $configs); + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + + $loader->load('jwt_manager.xml'); + $loader->load('key_loader.xml'); + $loader->load('lcobucci.xml'); + $loader->load('response_interceptor.xml'); + $loader->load('token_authenticator.xml'); + $loader->load('token_extractor.xml'); + + if (empty($config['public_key']) && empty($config['secret_key'])) { + $e = new InvalidConfigurationException('You must either configure a "public_key" or a "secret_key".'); + $e->setPath('lexik_jwt_authentication'); + + throw $e; + } + + $container->setParameter('lexik_jwt_authentication.pass_phrase', $config['pass_phrase']); + $container->setParameter('lexik_jwt_authentication.token_ttl', $config['token_ttl']); + $container->setParameter('lexik_jwt_authentication.clock_skew', $config['clock_skew']); + $container->setParameter('lexik_jwt_authentication.allow_no_expiration', $config['allow_no_expiration']); + $container->setParameter('lexik_jwt_authentication.user_id_claim', $config['user_id_claim']); + + $encoderConfig = $config['encoder']; + + $container->setAlias('lexik_jwt_authentication.encoder', new Alias($encoderConfig['service'], true)); + $container->setAlias(JWTEncoderInterface::class, 'lexik_jwt_authentication.encoder'); + $container->setAlias( + 'lexik_jwt_authentication.key_loader', + new Alias('lexik_jwt_authentication.key_loader.raw', true) + ); + + $container + ->findDefinition('lexik_jwt_authentication.key_loader') + ->replaceArgument(0, $config['secret_key']) + ->replaceArgument(1, $config['public_key']); + + if (isset($config['additional_public_keys'])) { + $container + ->findDefinition('lexik_jwt_authentication.key_loader') + ->replaceArgument(3, $config['additional_public_keys']); + } + + $container->setParameter('lexik_jwt_authentication.encoder.signature_algorithm', $encoderConfig['signature_algorithm']); + + $tokenExtractors = $this->createTokenExtractors($container, $config['token_extractors']); + $container + ->getDefinition('lexik_jwt_authentication.extractor.chain_extractor') + ->replaceArgument(0, $tokenExtractors); + + if (isset($config['remove_token_from_body_when_cookies_used'])) { + $container + ->getDefinition('lexik_jwt_authentication.handler.authentication_success') + ->replaceArgument(3, $config['remove_token_from_body_when_cookies_used']); + } + + if ($config['set_cookies']) { + $loader->load('cookie.xml'); + + $cookieProviders = []; + foreach ($config['set_cookies'] as $name => $attributes) { + if ($attributes['partitioned'] && Kernel::VERSION < '6.4') { + throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION)); + } + + $container + ->setDefinition($id = "lexik_jwt_authentication.cookie_provider.$name", new ChildDefinition('lexik_jwt_authentication.cookie_provider')) + ->replaceArgument(0, $name) + ->replaceArgument(1, $attributes['lifetime'] ?? ($config['token_ttl'] ?: 0)) + ->replaceArgument(2, $attributes['samesite']) + ->replaceArgument(3, $attributes['path']) + ->replaceArgument(4, $attributes['domain']) + ->replaceArgument(5, $attributes['secure']) + ->replaceArgument(6, $attributes['httpOnly']) + ->replaceArgument(7, $attributes['split']) + ->replaceArgument(8, $attributes['partitioned']); + $cookieProviders[] = new Reference($id); + } + + $container + ->getDefinition('lexik_jwt_authentication.handler.authentication_success') + ->replaceArgument(2, new IteratorArgument($cookieProviders)); + } + + if (class_exists(Application::class)) { + $loader->load('console.xml'); + + $container + ->getDefinition('lexik_jwt_authentication.generate_keypair_command') + ->replaceArgument(1, $config['secret_key']) + ->replaceArgument(2, $config['public_key']) + ->replaceArgument(3, $config['pass_phrase']) + ->replaceArgument(4, $encoderConfig['signature_algorithm']); + if (!$container->hasParameter('kernel.debug') || !$container->getParameter('kernel.debug')) { + $container->removeDefinition('lexik_jwt_authentication.migrate_config_command'); + } + } + + if ($this->isConfigEnabled($container, $config['api_platform'])) { + if (!class_exists(ApiPlatformBundle::class)) { + throw new LogicException('API Platform cannot be detected. Try running "composer require api-platform/core".'); + } + + $loader->load('api_platform.xml'); + + $container + ->getDefinition('lexik_jwt_authentication.api_platform.openapi.factory') + ->replaceArgument(1, $config['api_platform']['check_path'] ?? null) + ->replaceArgument(2, $config['api_platform']['username_path'] ?? null) + ->replaceArgument(3, $config['api_platform']['password_path'] ?? null); + } + + $this->processWithWebTokenConfig($config, $container, $loader); + + if ($this->isConfigEnabled($container, $config['blocklist_token'])) { + $loader->load('blocklist_token.xml'); + $blockListTokenConfig = $config['blocklist_token']; + $container->setAlias('lexik_jwt_authentication.blocklist_token.cache', $blockListTokenConfig['cache']); + } else { + $container->getDefinition('lexik_jwt_authentication.payload_enrichment.random_jti_enrichment') + ->clearTag('lexik_jwt_authentication.payload_enrichment'); + } + } + + private function createTokenExtractors(ContainerBuilder $container, array $tokenExtractorsConfig): array + { + $map = []; + + if ($this->isConfigEnabled($container, $tokenExtractorsConfig['authorization_header'])) { + $authorizationHeaderExtractorId = 'lexik_jwt_authentication.extractor.authorization_header_extractor'; + $container + ->getDefinition($authorizationHeaderExtractorId) + ->replaceArgument(0, $tokenExtractorsConfig['authorization_header']['prefix']) + ->replaceArgument(1, $tokenExtractorsConfig['authorization_header']['name']); + + $map[] = new Reference($authorizationHeaderExtractorId); + } + + if ($this->isConfigEnabled($container, $tokenExtractorsConfig['query_parameter'])) { + $queryParameterExtractorId = 'lexik_jwt_authentication.extractor.query_parameter_extractor'; + $container + ->getDefinition($queryParameterExtractorId) + ->replaceArgument(0, $tokenExtractorsConfig['query_parameter']['name']); + + $map[] = new Reference($queryParameterExtractorId); + } + + if ($this->isConfigEnabled($container, $tokenExtractorsConfig['cookie'])) { + $cookieExtractorId = 'lexik_jwt_authentication.extractor.cookie_extractor'; + $container + ->getDefinition($cookieExtractorId) + ->replaceArgument(0, $tokenExtractorsConfig['cookie']['name']); + + $map[] = new Reference($cookieExtractorId); + } + + if ($this->isConfigEnabled($container, $tokenExtractorsConfig['split_cookie'])) { + $cookieExtractorId = 'lexik_jwt_authentication.extractor.split_cookie_extractor'; + $container + ->getDefinition($cookieExtractorId) + ->replaceArgument(0, $tokenExtractorsConfig['split_cookie']['cookies']); + + $map[] = new Reference($cookieExtractorId); + } + + return $map; + } + + private function processWithWebTokenConfig(array $config, ContainerBuilder $container, LoaderInterface $loader): void + { + if ($config['access_token_issuance']['enabled'] === false && $config['access_token_verification']['enabled'] === false) { + return; + } + $loader->load('web_token.xml'); + if ($config['access_token_issuance']['enabled'] === true) { + $loader->load('web_token_issuance.xml'); + $accessTokenBuilder = 'lexik_jwt_authentication.access_token_builder'; + $accessTokenBuilderDefinition = $container->getDefinition($accessTokenBuilder); + $accessTokenBuilderDefinition + ->replaceArgument(3, $config['access_token_issuance']['signature']['algorithm']) + ->replaceArgument(4, $config['access_token_issuance']['signature']['key']) + ; + if ($config['access_token_issuance']['encryption']['enabled'] === true) { + $accessTokenBuilderDefinition + ->replaceArgument(5, $config['access_token_issuance']['encryption']['key_encryption_algorithm']) + ->replaceArgument(6, $config['access_token_issuance']['encryption']['content_encryption_algorithm']) + ->replaceArgument(7, $config['access_token_issuance']['encryption']['key']) + ; + } + } + if ($config['access_token_verification']['enabled'] === true) { + $loader->load('web_token_verification.xml'); + $accessTokenLoader = 'lexik_jwt_authentication.access_token_loader'; + $accessTokenLoaderDefinition = $container->getDefinition($accessTokenLoader); + $accessTokenLoaderDefinition + ->replaceArgument(3, $config['access_token_verification']['signature']['claim_checkers']) + ->replaceArgument(4, $config['access_token_verification']['signature']['header_checkers']) + ->replaceArgument(5, $config['access_token_verification']['signature']['mandatory_claims']) + ->replaceArgument(6, $config['access_token_verification']['signature']['allowed_algorithms']) + ->replaceArgument(7, $config['access_token_verification']['signature']['keyset']) + ; + if ($config['access_token_verification']['encryption']['enabled'] === true) { + $accessTokenLoaderDefinition + ->replaceArgument(8, $config['access_token_verification']['encryption']['continue_on_decryption_failure']) + ->replaceArgument(9, $config['access_token_verification']['encryption']['header_checkers']) + ->replaceArgument(10, $config['access_token_verification']['encryption']['allowed_key_encryption_algorithms']) + ->replaceArgument(11, $config['access_token_verification']['encryption']['allowed_content_encryption_algorithms']) + ->replaceArgument(12, $config['access_token_verification']['encryption']['keyset']) + ; + } + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Security/Factory/JWTAuthenticatorFactory.php b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Security/Factory/JWTAuthenticatorFactory.php new file mode 100644 index 0000000..7b8f8e5 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Security/Factory/JWTAuthenticatorFactory.php @@ -0,0 +1,61 @@ + + */ +class JWTAuthenticatorFactory implements AuthenticatorFactoryInterface +{ + /** + * {@inheritdoc} + */ + public function getPriority(): int + { + return -10; + } + + /** + * {@inheritdoc} + */ + public function getKey(): string + { + return 'jwt'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->children() + ->scalarNode('provider') + ->defaultNull() + ->end() + ->scalarNode('authenticator') + ->defaultValue('lexik_jwt_authentication.security.jwt_authenticator') + ->end() + ->end() + ; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $authenticatorId = 'security.authenticator.jwt.' . $firewallName; + + $userProviderId = empty($config['provider']) ? $userProviderId : 'security.user.provider.concrete.' . $config['provider']; + + $container + ->setDefinition($authenticatorId, new ChildDefinition($config['authenticator'])) + ->replaceArgument(3, new Reference($userProviderId)) + ; + + return $authenticatorId; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Security/Factory/JWTUserFactory.php b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Security/Factory/JWTUserFactory.php new file mode 100644 index 0000000..aa9d5d4 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/DependencyInjection/Security/Factory/JWTUserFactory.php @@ -0,0 +1,47 @@ + + */ +final class JWTUserFactory implements UserProviderFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array $config): void + { + $container->setDefinition($id, new ChildDefinition('lexik_jwt_authentication.security.jwt_user_provider')) + ->replaceArgument(0, $config['class']); + } + + public function getKey(): string + { + return 'lexik_jwt'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->children() + ->scalarNode('class') + ->cannotBeEmpty() + ->defaultValue(JWTUser::class) + ->validate() + ->ifTrue(fn ($class) => !is_subclass_of($class, JWTUserInterface::class)) + ->thenInvalid('The %s class must implement ' . JWTUserInterface::class . ' for using the "lexik_jwt" user provider.') + ->end() + ->end() + ->end() + ; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Encoder/HeaderAwareJWTEncoderInterface.php b/vendor/lexik/jwt-authentication-bundle/Encoder/HeaderAwareJWTEncoderInterface.php new file mode 100644 index 0000000..788c395 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Encoder/HeaderAwareJWTEncoderInterface.php @@ -0,0 +1,19 @@ + + */ +interface JWTEncoderInterface +{ + /** + * @return string the encoded token string + * + * @throws JWTEncodeFailureException If an error occurred while trying to create + * the token (invalid crypto key, invalid payload...) + */ + public function encode(array $data); + + /** + * @param string $token + * + * @return array + * + * @throws JWTDecodeFailureException If an error occurred while trying to load the token + * (invalid signature, invalid crypto key, expired token...) + */ + public function decode($token); +} diff --git a/vendor/lexik/jwt-authentication-bundle/Encoder/LcobucciJWTEncoder.php b/vendor/lexik/jwt-authentication-bundle/Encoder/LcobucciJWTEncoder.php new file mode 100644 index 0000000..4089492 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Encoder/LcobucciJWTEncoder.php @@ -0,0 +1,66 @@ + + */ +class LcobucciJWTEncoder implements JWTEncoderInterface, HeaderAwareJWTEncoderInterface +{ + protected JWSProviderInterface $jwsProvider; + + public function __construct(JWSProviderInterface $jwsProvider) + { + $this->jwsProvider = $jwsProvider; + } + + /** + * {@inheritdoc} + */ + public function encode(array $payload, array $header = []) + { + try { + $jws = $this->jwsProvider->create($payload, $header); + } catch (\InvalidArgumentException $e) { + throw new JWTEncodeFailureException(JWTEncodeFailureException::INVALID_CONFIG, 'An error occurred while trying to encode the JWT token. Please verify your configuration (private key/passphrase)', $e, $payload); + } + + if (!$jws->isSigned()) { + throw new JWTEncodeFailureException(JWTEncodeFailureException::UNSIGNED_TOKEN, 'Unable to create a signed JWT from the given configuration.', null, $payload); + } + + return $jws->getToken(); + } + + /** + * {@inheritdoc} + */ + public function decode($token) + { + try { + $jws = $this->jwsProvider->load($token); + } catch (\Exception $e) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid JWT Token', $e); + } + + if ($jws->isInvalid()) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid JWT Token', null, $jws->getPayload()); + } + + if ($jws->isExpired()) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::EXPIRED_TOKEN, 'Expired JWT Token', null, $jws->getPayload()); + } + + if (!$jws->isVerified()) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::UNVERIFIED_TOKEN, 'Unable to verify the given JWT through the given configuration. If the "lexik_jwt_authentication.encoder" encryption options have been changed since your last authentication, please renew the token. If the problem persists, verify that the configured keys/passphrase are valid.', null, $jws->getPayload()); + } + + return $jws->getPayload(); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Encoder/WebTokenEncoder.php b/vendor/lexik/jwt-authentication-bundle/Encoder/WebTokenEncoder.php new file mode 100644 index 0000000..ab0418e --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Encoder/WebTokenEncoder.php @@ -0,0 +1,65 @@ + + */ +final class WebTokenEncoder implements HeaderAwareJWTEncoderInterface +{ + /** + * @var AccessTokenBuilder|null + */ + private $accessTokenBuilder; + + /** + * @var AccessTokenLoader|null + */ + private $accessTokenLoader; + + public function __construct(?AccessTokenBuilder $accessTokenBuilder, ?AccessTokenLoader $accessTokenLoader) + { + $this->accessTokenBuilder = $accessTokenBuilder; + $this->accessTokenLoader = $accessTokenLoader; + } + + /** + * {@inheritdoc} + */ + public function encode(array $payload, array $header = []) + { + if (!$this->accessTokenBuilder) { + throw new \LogicException('The access token issuance features are not enabled.'); + } + + try { + return $this->accessTokenBuilder->build($header, $payload); + } catch (\InvalidArgumentException $e) { + throw new JWTEncodeFailureException(JWTEncodeFailureException::INVALID_CONFIG, 'An error occurred while trying to encode the JWT token. Please verify your configuration (private key/passphrase)', $e, $payload); + } + } + + /** + * {@inheritdoc} + */ + public function decode($token) + { + if (!$this->accessTokenLoader) { + throw new \LogicException('The access token verification features are not enabled.'); + } + + try { + return $this->accessTokenLoader->load($token); + } catch (JWTFailureException $e) { + throw $e; + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/AuthenticationFailureEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/AuthenticationFailureEvent.php new file mode 100644 index 0000000..8888ae9 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/AuthenticationFailureEvent.php @@ -0,0 +1,53 @@ + + * @author Robin Chalas + */ +class AuthenticationFailureEvent extends Event +{ + protected AuthenticationException $exception; + protected ?Response $response; + protected ?Request $request; + + public function __construct(?AuthenticationException $exception, ?Response $response, ?Request $request = null) + { + $this->exception = $exception; + $this->response = $response; + $this->request = $request; + } + + public function getException(): AuthenticationException + { + return $this->exception; + } + + public function getResponse(): ?Response + { + return $this->response; + } + + public function setResponse(Response $response): void + { + $this->response = $response; + } + + public function getRequest(): ?Request + { + return $this->request; + } + + public function setRequest(Request $request) + { + $this->request = $request; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/AuthenticationSuccessEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/AuthenticationSuccessEvent.php new file mode 100644 index 0000000..90c7ff8 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/AuthenticationSuccessEvent.php @@ -0,0 +1,46 @@ + + */ +class AuthenticationSuccessEvent extends Event +{ + protected array $data; + protected UserInterface $user; + protected Response $response; + + public function __construct(array $data, UserInterface $user, Response $response) + { + $this->data = $data; + $this->user = $user; + $this->response = $response; + } + + public function getData(): array + { + return $this->data; + } + + public function setData(array $data): void + { + $this->data = $data; + } + + public function getUser(): UserInterface + { + return $this->user; + } + + public function getResponse(): Response + { + return $this->response; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/BeforeJWEComputationEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/BeforeJWEComputationEvent.php new file mode 100644 index 0000000..12b4c2c --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/BeforeJWEComputationEvent.php @@ -0,0 +1,44 @@ + + */ +class BeforeJWEComputationEvent +{ + private $header; + + /** + * @param array $header + */ + public function __construct(array $header) + { + $this->header = $header; + } + + public function setHeader(string $key, mixed $value): self + { + $this->header[$key] = $value; + + return $this; + } + + public function removeHeader(string $key): self + { + unset($this->header[$key]); + + return $this; + } + + /** + * @return array + */ + public function getHeader(): array + { + return $this->header; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTAuthenticatedEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTAuthenticatedEvent.php new file mode 100644 index 0000000..6977bd0 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTAuthenticatedEvent.php @@ -0,0 +1,36 @@ +payload = $payload; + $this->token = $token; + } + + public function getPayload(): array + { + return $this->payload; + } + + public function setPayload(array $payload) + { + $this->payload = $payload; + } + + public function getToken(): TokenInterface + { + return $this->token; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTCreatedEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTCreatedEvent.php new file mode 100644 index 0000000..4414a94 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTCreatedEvent.php @@ -0,0 +1,48 @@ +data = $data; + $this->user = $user; + $this->header = $header; + } + + public function getHeader(): array + { + return $this->header; + } + + public function setHeader(array $header) + { + $this->header = $header; + } + + public function getData(): array + { + return $this->data; + } + + public function setData(array $data) + { + $this->data = $data; + } + + public function getUser(): UserInterface + { + return $this->user; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTDecodedEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTDecodedEvent.php new file mode 100644 index 0000000..0a9f590 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTDecodedEvent.php @@ -0,0 +1,46 @@ + + */ +class JWTDecodedEvent extends Event +{ + protected array $payload; + protected bool $isValid; + + public function __construct(array $payload) + { + $this->payload = $payload; + $this->isValid = true; + } + + public function getPayload(): array + { + return $this->payload; + } + + public function setPayload(array $payload) + { + $this->payload = $payload; + } + + /** + * Mark payload as invalid. + */ + public function markAsInvalid(): void + { + $this->isValid = false; + $this->stopPropagation(); + } + + public function isValid(): bool + { + return $this->isValid; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTEncodedEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTEncodedEvent.php new file mode 100644 index 0000000..4f79921 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTEncodedEvent.php @@ -0,0 +1,20 @@ +jwtString = $jwtString; + } + + public function getJWTString(): string + { + return $this->jwtString; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTExpiredEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTExpiredEvent.php new file mode 100644 index 0000000..d0129bc --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTExpiredEvent.php @@ -0,0 +1,12 @@ + + */ +class JWTExpiredEvent extends AuthenticationFailureEvent implements JWTFailureEventInterface +{ +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTFailureEventInterface.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTFailureEventInterface.php new file mode 100644 index 0000000..01e78d2 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTFailureEventInterface.php @@ -0,0 +1,35 @@ + + */ +interface JWTFailureEventInterface +{ + /** + * Gets the response that will be returned after dispatching a + * {@link JWTFailureEventInterface} implementation. + * + * @return Response + */ + public function getResponse(); + + /** + * Gets the tied AuthenticationException object. + * + * @return AuthenticationException + */ + public function getException(); + + /** + * Calling this allows to return a custom Response immediately after + * the corresponding implementation of this event is dispatched. + */ + public function setResponse(Response $response); +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTInvalidEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTInvalidEvent.php new file mode 100644 index 0000000..e2d89fa --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTInvalidEvent.php @@ -0,0 +1,12 @@ + + */ +class JWTInvalidEvent extends AuthenticationFailureEvent implements JWTFailureEventInterface +{ +} diff --git a/vendor/lexik/jwt-authentication-bundle/Event/JWTNotFoundEvent.php b/vendor/lexik/jwt-authentication-bundle/Event/JWTNotFoundEvent.php new file mode 100644 index 0000000..5a76c10 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Event/JWTNotFoundEvent.php @@ -0,0 +1,21 @@ + + */ +class JWTNotFoundEvent extends AuthenticationFailureEvent implements JWTFailureEventInterface +{ + public function __construct(AuthenticationException $exception = null, Response $response = null, Request $request = null) + { + parent::__construct($exception, $response, $request); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/EventListener/BlockJWTListener.php b/vendor/lexik/jwt-authentication-bundle/EventListener/BlockJWTListener.php new file mode 100644 index 0000000..ba19790 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/EventListener/BlockJWTListener.php @@ -0,0 +1,67 @@ +blockedTokenManager = $blockedTokenManager; + $this->tokenExtractor = $tokenExtractor; + $this->jwtManager = $jwtManager; + } + + public function onLoginFailure(LoginFailureEvent $event): void + { + $exception = $event->getException(); + if (($exception instanceof DisabledException) || ($exception->getPrevious() instanceof DisabledException)) { + $this->blockTokenFromRequest($event->getRequest()); + } + } + + public function onLogout(LogoutEvent $event): void + { + $this->blockTokenFromRequest($event->getRequest()); + } + + private function blockTokenFromRequest(Request $request): void + { + $token = $this->tokenExtractor->extract($request); + + if ($token === false) { + // There's nothing to block if the token isn't in the request + return; + } + + try { + $payload = $this->jwtManager->parse($token); + } catch (JWTDecodeFailureException $e) { + // Ignore decode failures, this would mean the token is invalid anyway + return; + } + + try { + $this->blockedTokenManager->add($payload); + } catch (MissingClaimException $e) { + // We can't block a token missing the claims our system requires, so silently ignore this one + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/EventListener/RejectBlockedTokenListener.php b/vendor/lexik/jwt-authentication-bundle/EventListener/RejectBlockedTokenListener.php new file mode 100644 index 0000000..2c23946 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/EventListener/RejectBlockedTokenListener.php @@ -0,0 +1,32 @@ +blockedTokenManager = $blockedTokenManager; + } + + /** + * @throws InvalidTokenException If the JWT is blocked + */ + public function __invoke(JWTAuthenticatedEvent $event): void + { + try { + if ($this->blockedTokenManager->has($event->getPayload())) { + throw new InvalidTokenException('JWT blocked'); + } + } catch (MissingClaimException) { + // Do nothing if the required claims do not exist on the payload (older JWTs won't have the "jti" claim the manager requires) + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Events.php b/vendor/lexik/jwt-authentication-bundle/Events.php new file mode 100644 index 0000000..32ed026 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Events.php @@ -0,0 +1,93 @@ + + */ +final class Events +{ + /** + * Dispatched after the token generation to allow sending more data + * on the authentication success response. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent") + */ + public const AUTHENTICATION_SUCCESS = 'lexik_jwt_authentication.on_authentication_success'; + + /** + * Dispatched after an authentication failure. + * Hook into this event to add a custom error message in the response body. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent") + */ + public const AUTHENTICATION_FAILURE = 'lexik_jwt_authentication.on_authentication_failure'; + + /** + * Dispatched before the token payload is encoded by the configured encoder (JWTEncoder by default). + * Hook into this event to add extra fields to the payload. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent") + */ + public const JWT_CREATED = 'lexik_jwt_authentication.on_jwt_created'; + + /** + * Dispatched right after token string is created. + * Hook into this event to get token representation itself. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTEncodedEvent") + */ + public const JWT_ENCODED = 'lexik_jwt_authentication.on_jwt_encoded'; + + /** + * Dispatched after the token payload has been decoded by the configured encoder (JWTEncoder by default). + * Hook into this event to perform additional validation on the received payload. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent") + */ + public const JWT_DECODED = 'lexik_jwt_authentication.on_jwt_decoded'; + + /** + * Dispatched after the token payload has been authenticated by the provider. + * Hook into this event to perform additional modification to the authenticated token using the payload. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent") + */ + public const JWT_AUTHENTICATED = 'lexik_jwt_authentication.on_jwt_authenticated'; + + /** + * Dispatched after the token has been invalidated by the provider. + * Hook into this event to add a custom error message in the response body. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent") + */ + public const JWT_INVALID = 'lexik_jwt_authentication.on_jwt_invalid'; + + /** + * Dispatched when no token can be found in a request. + * Hook into this event to set a custom response. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent") + */ + public const JWT_NOT_FOUND = 'lexik_jwt_authentication.on_jwt_not_found'; + + /** + * Dispatched when the token is expired. + * The expired token's payload can be retrieved by hooking into this event, so you can set a different + * response. + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent") + */ + public const JWT_EXPIRED = 'lexik_jwt_authentication.on_jwt_expired'; + + /** + * Dispatched before the JWE is computed. + * This event allow the JWE header parameters to be changed. + * It is only dispatched when using Web-Token + * + * @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\BeforeJWEComputationEvent") + */ + public const BEFORE_JWE_COMPUTATION = 'lexik_jwt_authentication.before_jwe_computation'; +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/ExpiredTokenException.php b/vendor/lexik/jwt-authentication-bundle/Exception/ExpiredTokenException.php new file mode 100644 index 0000000..b0d0bb7 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/ExpiredTokenException.php @@ -0,0 +1,22 @@ + + */ +class ExpiredTokenException extends AuthenticationException +{ + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return 'Expired JWT Token'; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/InvalidPayloadException.php b/vendor/lexik/jwt-authentication-bundle/Exception/InvalidPayloadException.php new file mode 100644 index 0000000..5f7fe4b --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/InvalidPayloadException.php @@ -0,0 +1,31 @@ + + */ +class InvalidPayloadException extends AuthenticationException +{ + private string $invalidKey; + + /** + * @param string $invalidKey The key that cannot be found in the payload + */ + public function __construct(string $invalidKey) + { + $this->invalidKey = $invalidKey; + } + + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return sprintf('Unable to find key "%s" in the token payload.', $this->invalidKey); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/InvalidTokenException.php b/vendor/lexik/jwt-authentication-bundle/Exception/InvalidTokenException.php new file mode 100644 index 0000000..1366842 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/InvalidTokenException.php @@ -0,0 +1,21 @@ + + */ +class InvalidTokenException extends AuthenticationException +{ + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return 'Invalid JWT Token'; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/JWTDecodeFailureException.php b/vendor/lexik/jwt-authentication-bundle/Exception/JWTDecodeFailureException.php new file mode 100644 index 0000000..dcbd491 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/JWTDecodeFailureException.php @@ -0,0 +1,17 @@ + + */ +class JWTDecodeFailureException extends JWTFailureException +{ + public const INVALID_TOKEN = 'invalid_token'; + + public const UNVERIFIED_TOKEN = 'unverified_token'; + + public const EXPIRED_TOKEN = 'expired_token'; +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/JWTEncodeFailureException.php b/vendor/lexik/jwt-authentication-bundle/Exception/JWTEncodeFailureException.php new file mode 100644 index 0000000..f5ecd92 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/JWTEncodeFailureException.php @@ -0,0 +1,15 @@ + + */ +class JWTEncodeFailureException extends JWTFailureException +{ + public const INVALID_CONFIG = 'invalid_config'; + + public const UNSIGNED_TOKEN = 'unsigned_token'; +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/JWTFailureException.php b/vendor/lexik/jwt-authentication-bundle/Exception/JWTFailureException.php new file mode 100644 index 0000000..c25e463 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/JWTFailureException.php @@ -0,0 +1,32 @@ + + */ +class JWTFailureException extends \Exception +{ + private string $reason; + private ?array $payload; + + public function __construct(string $reason, string $message, \Throwable $previous = null, array $payload = null) + { + $this->reason = $reason; + $this->payload = $payload; + + parent::__construct($message, 0, $previous); + } + + public function getReason(): string + { + return $this->reason; + } + + public function getPayload(): ?array + { + return $this->payload; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/MissingClaimException.php b/vendor/lexik/jwt-authentication-bundle/Exception/MissingClaimException.php new file mode 100644 index 0000000..72fba07 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/MissingClaimException.php @@ -0,0 +1,15 @@ + + */ +class MissingTokenException extends AuthenticationException +{ + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return 'JWT Token not found'; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Exception/UserNotFoundException.php b/vendor/lexik/jwt-authentication-bundle/Exception/UserNotFoundException.php new file mode 100644 index 0000000..f26c920 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Exception/UserNotFoundException.php @@ -0,0 +1,30 @@ + + */ +class UserNotFoundException extends AuthenticationException +{ + private string $userIdentityField; + private string $identity; + + public function __construct(string $userIdentityField, string $identity) + { + $this->userIdentityField = $userIdentityField; + $this->identity = $identity; + } + + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return sprintf('Unable to load an user with property "%s" = "%s". If the user identity has changed, you must renew the token. Otherwise, verify that the "lexik_jwt_authentication.user_identity_field" config option is correctly set.', $this->userIdentityField, $this->identity); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Helper/JWTSplitter.php b/vendor/lexik/jwt-authentication-bundle/Helper/JWTSplitter.php new file mode 100644 index 0000000..e62e857 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Helper/JWTSplitter.php @@ -0,0 +1,37 @@ + + * + * @final + */ +class JWTSplitter +{ + private string $header; + private string $payload; + private string $signature; + + /** + * @var string + */ + private $jwt; + + public function __construct(string $jwt) + { + $this->jwt = $jwt; + [$this->header, $this->payload, $this->signature] = explode('.', $jwt); + } + + public function getParts(array $parts = []): string + { + if (!$parts) { + return $this->jwt; + } + + return implode('.', array_intersect_key(get_object_vars($this), array_flip($parts))); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/LICENSE b/vendor/lexik/jwt-authentication-bundle/LICENSE new file mode 100644 index 0000000..dcee272 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2014-2020 Lexik , Robin Chalas + +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. diff --git a/vendor/lexik/jwt-authentication-bundle/LexikJWTAuthenticationBundle.php b/vendor/lexik/jwt-authentication-bundle/LexikJWTAuthenticationBundle.php new file mode 100644 index 0000000..e72fff8 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/LexikJWTAuthenticationBundle.php @@ -0,0 +1,40 @@ + + */ +class LexikJWTAuthenticationBundle extends Bundle +{ + /** + * {@inheritdoc} + */ + public function build(ContainerBuilder $container): void + { + parent::build($container); + + $container->addCompilerPass(new WireGenerateTokenCommandPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); + $container->addCompilerPass(new ApiPlatformOpenApiPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); + $container->addCompilerPass(new CollectPayloadEnrichmentsPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); + + /** @var SecurityExtension $extension */ + $extension = $container->getExtension('security'); + + $extension->addUserProviderFactory(new JWTUserFactory()); + + $extension->addAuthenticatorFactory(new JWTAuthenticatorFactory()); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/OpenApi/OpenApiFactory.php b/vendor/lexik/jwt-authentication-bundle/OpenApi/OpenApiFactory.php new file mode 100644 index 0000000..56161a2 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/OpenApi/OpenApiFactory.php @@ -0,0 +1,122 @@ + + * + * @final + */ +class OpenApiFactory implements OpenApiFactoryInterface +{ + private OpenApiFactoryInterface $decorated; + private string $checkPath; + private string $usernamePath; + private string $passwordPath; + + public function __construct(OpenApiFactoryInterface $decorated, string $checkPath, string $usernamePath, string $passwordPath) + { + $this->decorated = $decorated; + $this->checkPath = $checkPath; + $this->usernamePath = $usernamePath; + $this->passwordPath = $passwordPath; + } + + /** + * {@inheritdoc} + */ + public function __invoke(array $context = []): OpenApi + { + $openApi = ($this->decorated)($context); + + $openApi + ->getComponents()->getSecuritySchemes()->offsetSet( + 'JWT', + new \ArrayObject( + [ + 'type' => 'http', + 'scheme' => 'bearer', + 'bearerFormat' => 'JWT', + ] + ) + ); + + $openApi + ->getPaths() + ->addPath($this->checkPath, (new PathItem())->withPost( + (new Operation()) + ->withOperationId('login_check_post') + ->withTags(['Login Check']) + ->withResponses([ + Response::HTTP_OK => [ + 'description' => 'User token created', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'token' => [ + 'readOnly' => true, + 'type' => 'string', + 'nullable' => false, + ], + ], + 'required' => ['token'], + ], + ], + ], + ], + ]) + ->withSummary('Creates a user token.') + ->withDescription('Creates a user token.') + ->withRequestBody( + (new RequestBody()) + ->withDescription('The login data') + ->withContent(new \ArrayObject([ + 'application/json' => new MediaType(new \ArrayObject(new \ArrayObject([ + 'type' => 'object', + 'properties' => $properties = array_merge_recursive($this->getJsonSchemaFromPathParts(explode('.', $this->usernamePath)), $this->getJsonSchemaFromPathParts(explode('.', $this->passwordPath))), + 'required' => array_keys($properties), + ]))), + ])) + ->withRequired(true) + ) + )); + + return $openApi; + } + + private function getJsonSchemaFromPathParts(array $pathParts): array + { + $jsonSchema = []; + + if (count($pathParts) === 1) { + $jsonSchema[array_shift($pathParts)] = [ + 'type' => 'string', + 'nullable' => false, + ]; + + return $jsonSchema; + } + + $pathPart = array_shift($pathParts); + $properties = $this->getJsonSchemaFromPathParts($pathParts); + $jsonSchema[$pathPart] = [ + 'type' => 'object', + 'properties' => $properties, + 'required' => array_keys($properties), + ]; + + return $jsonSchema; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/api_platform.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/api_platform.xml new file mode 100644 index 0000000..d3061b3 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/api_platform.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/blocklist_token.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/blocklist_token.xml new file mode 100644 index 0000000..e313a30 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/blocklist_token.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/console.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/console.xml new file mode 100644 index 0000000..57c0abe --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/console.xml @@ -0,0 +1,42 @@ + + + + + + + + %lexik_jwt_authentication.encoder.signature_algorithm% + + + + + + %lexik_jwt_authentication.pass_phrase% + %lexik_jwt_authentication.encoder.signature_algorithm% + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/cookie.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/cookie.xml new file mode 100644 index 0000000..e666c70 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/cookie.xml @@ -0,0 +1,20 @@ + + + + + + + null + null + + + null + + + null + false + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/jwt_manager.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/jwt_manager.xml new file mode 100644 index 0000000..a54621e --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/jwt_manager.xml @@ -0,0 +1,24 @@ + + + + + + + + + %lexik_jwt_authentication.user_id_claim% + + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/key_loader.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/key_loader.xml new file mode 100644 index 0000000..543ceeb --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/key_loader.xml @@ -0,0 +1,16 @@ + + + + + + + + + %lexik_jwt_authentication.pass_phrase% + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/lcobucci.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/lcobucci.xml new file mode 100644 index 0000000..9ad67ba --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/lcobucci.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + %lexik_jwt_authentication.encoder.signature_algorithm% + %lexik_jwt_authentication.token_ttl% + %lexik_jwt_authentication.clock_skew% + %lexik_jwt_authentication.allow_no_expiration% + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/response_interceptor.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/response_interceptor.xml new file mode 100644 index 0000000..0f8ed2e --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/response_interceptor.xml @@ -0,0 +1,24 @@ + + + + + + + + + + true + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/token_authenticator.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/token_authenticator.xml new file mode 100644 index 0000000..1e155d9 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/token_authenticator.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/token_extractor.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/token_extractor.xml new file mode 100644 index 0000000..35389e7 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/token_extractor.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token.xml new file mode 100644 index 0000000..60f0b87 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + %lexik_jwt_authentication.token_ttl% + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token_issuance.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token_issuance.xml new file mode 100644 index 0000000..e2b42af --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token_issuance.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token_verification.xml b/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token_verification.xml new file mode 100644 index 0000000..a84ad5b --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Resources/config/web_token_verification.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + %lexik_jwt_authentication.clock_skew% + true + + + + + %lexik_jwt_authentication.clock_skew% + true + + + + + %lexik_jwt_authentication.clock_skew% + true + + + + + diff --git a/vendor/lexik/jwt-authentication-bundle/Response/JWTAuthenticationFailureResponse.php b/vendor/lexik/jwt-authentication-bundle/Response/JWTAuthenticationFailureResponse.php new file mode 100644 index 0000000..302d8bd --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Response/JWTAuthenticationFailureResponse.php @@ -0,0 +1,53 @@ + + */ +final class JWTAuthenticationFailureResponse extends JsonResponse +{ + private string $message; + + public function __construct(string $message = 'Bad credentials', int $statusCode = Response::HTTP_UNAUTHORIZED) + { + $this->message = $message; + + parent::__construct(null, $statusCode, ['WWW-Authenticate' => 'Bearer']); + } + + /** + * Sets the response data with the statusCode & message included. + */ + public function setData(mixed $data = []): static + { + return parent::setData((array)$data + ["code" => $this->statusCode, "message" => $this->getMessage()]); + } + + /** + * Sets the failure message. + */ + public function setMessage(string $message): JWTAuthenticationFailureResponse + { + $this->message = $message; + + $this->setData(); + + return $this; + } + + /** + * Gets the failure message. + */ + public function getMessage(): string + { + return $this->message; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Response/JWTAuthenticationSuccessResponse.php b/vendor/lexik/jwt-authentication-bundle/Response/JWTAuthenticationSuccessResponse.php new file mode 100644 index 0000000..72a24f2 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Response/JWTAuthenticationSuccessResponse.php @@ -0,0 +1,32 @@ + + */ +final class JWTAuthenticationSuccessResponse extends JsonResponse +{ + /** + * @param string $token Json Web Token + * @param array $data Extra data passed to the response + */ + public function __construct(string $token, array $data = [], array $jwtCookies = []) + { + if (!$jwtCookies) { + parent::__construct(['token' => $token] + $data); + + return; + } + + parent::__construct($data); + + foreach ($jwtCookies as $cookie) { + $this->headers->setCookie($cookie); + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/Authenticator/JWTAuthenticator.php b/vendor/lexik/jwt-authentication-bundle/Security/Authenticator/JWTAuthenticator.php new file mode 100644 index 0000000..9c58902 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/Authenticator/JWTAuthenticator.php @@ -0,0 +1,219 @@ +tokenExtractor = $tokenExtractor; + $this->jwtManager = $jwtManager; + $this->eventDispatcher = $eventDispatcher; + $this->userProvider = $userProvider; + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function start(Request $request, AuthenticationException $authException = null): Response + { + $exception = new MissingTokenException('JWT Token not found', 0, $authException); + $event = new JWTNotFoundEvent($exception, new JWTAuthenticationFailureResponse($exception->getMessageKey()), $request); + + $this->eventDispatcher->dispatch($event, Events::JWT_NOT_FOUND); + + return $event->getResponse(); + } + + public function supports(Request $request): ?bool + { + return false !== $this->getTokenExtractor()->extract($request); + } + + public function authenticate(Request $request): Passport + { + $token = $this->getTokenExtractor()->extract($request); + if ($token === false) { + throw new \LogicException('Unable to extract a JWT token from the request. Also, make sure to call `supports()` before `authenticate()` to get a proper client error.'); + } + + try { + if (!$payload = $this->jwtManager->parse($token)) { + throw new InvalidTokenException('Invalid JWT Token'); + } + } catch (JWTDecodeFailureException $e) { + if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) { + throw new ExpiredTokenException(); + } + + throw new InvalidTokenException('Invalid JWT Token', 0, $e); + } + + $idClaim = $this->jwtManager->getUserIdClaim(); + if (!isset($payload[$idClaim])) { + throw new InvalidPayloadException($idClaim); + } + + $passport = new SelfValidatingPassport( + new UserBadge( + (string) $payload[$idClaim], + fn ($userIdentifier) => $this->loadUser($payload, $userIdentifier) + ) + ); + + $passport->setAttribute('payload', $payload); + $passport->setAttribute('token', $token); + + return $passport; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData()); + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security'); + } + $response = new JWTAuthenticationFailureResponse($errorMessage); + + if ($exception instanceof ExpiredTokenException) { + $event = new JWTExpiredEvent($exception, $response, $request); + $eventName = Events::JWT_EXPIRED; + } else { + $event = new JWTInvalidEvent($exception, $response, $request); + $eventName = Events::JWT_INVALID; + } + + $this->eventDispatcher->dispatch($event, $eventName); + + return $event->getResponse(); + } + + /** + * Gets the token extractor to be used for retrieving a JWT token in the + * current request. + * + * Override this method for adding/removing extractors to the chain one or + * returning a different {@link TokenExtractorInterface} implementation. + */ + protected function getTokenExtractor(): TokenExtractorInterface + { + return $this->tokenExtractor; + } + + /** + * Gets the jwt manager. + */ + protected function getJwtManager(): JWTTokenManagerInterface + { + return $this->jwtManager; + } + + /** + * Gets the event dispatcher. + */ + protected function getEventDispatcher(): EventDispatcherInterface + { + return $this->eventDispatcher; + } + + /** + * Gets the user provider. + */ + protected function getUserProvider(): UserProviderInterface + { + return $this->userProvider; + } + + /** + * Loads the user to authenticate. + * + * @param array $payload The token payload + * @param string $identity The key from which to retrieve the user "identifier" + */ + protected function loadUser(array $payload, string $identity): UserInterface + { + if ($this->userProvider instanceof PayloadAwareUserProviderInterface) { + return $this->userProvider->loadUserByIdentifierAndPayload($identity, $payload); + } + + if ($this->userProvider instanceof ChainUserProvider) { + foreach ($this->userProvider->getProviders() as $provider) { + try { + if ($provider instanceof PayloadAwareUserProviderInterface) { + return $provider->loadUserByIdentifierAndPayload($identity, $payload); + } + + return $provider->loadUserByIdentifier($identity); + } catch (AuthenticationException $e) { + // try next one + } + } + + $ex = new UserNotFoundException(sprintf('There is no user with identifier "%s".', $identity)); + $ex->setUserIdentifier($identity); + + throw $ex; + } + + return $this->userProvider->loadUserByIdentifier($identity); + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + $token = new JWTPostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles(), $passport->getAttribute('token')); + + $this->eventDispatcher->dispatch(new JWTAuthenticatedEvent($passport->getAttribute('payload'), $token), Events::JWT_AUTHENTICATED); + + return $token; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/Authenticator/Token/JWTPostAuthenticationToken.php b/vendor/lexik/jwt-authentication-bundle/Security/Authenticator/Token/JWTPostAuthenticationToken.php new file mode 100644 index 0000000..d7f4d7d --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/Authenticator/Token/JWTPostAuthenticationToken.php @@ -0,0 +1,26 @@ +token = $token; + } + + /** + * {@inheritdoc} + */ + public function getCredentials(): string + { + return $this->token; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationFailureHandler.php b/vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationFailureHandler.php new file mode 100644 index 0000000..524c125 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationFailureHandler.php @@ -0,0 +1,66 @@ + + */ +class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface +{ + protected EventDispatcherInterface $dispatcher; + private ?TranslatorInterface $translator; + + public function __construct(EventDispatcherInterface $dispatcher, TranslatorInterface $translator = null) + { + $this->dispatcher = $dispatcher; + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData()); + $statusCode = self::mapExceptionCodeToStatusCode($exception->getCode()); + if ($this->translator) { + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security'); + } + + $event = new AuthenticationFailureEvent( + $exception, + new JWTAuthenticationFailureResponse($errorMessage, $statusCode), + $request + ); + + $this->dispatcher->dispatch($event, Events::AUTHENTICATION_FAILURE); + + return $event->getResponse(); + } + + /** + * @param string|int $exceptionCode + */ + private static function mapExceptionCodeToStatusCode($exceptionCode): int + { + $canMapToStatusCode = is_int($exceptionCode) + && $exceptionCode >= 400 + && $exceptionCode < 500; + + return $canMapToStatusCode + ? $exceptionCode + : Response::HTTP_UNAUTHORIZED; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationSuccessHandler.php b/vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationSuccessHandler.php new file mode 100644 index 0000000..04be106 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationSuccessHandler.php @@ -0,0 +1,80 @@ + + * @author Robin Chalas + * + * @final + */ +class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface +{ + protected JWTTokenManagerInterface $jwtManager; + protected EventDispatcherInterface $dispatcher; + protected bool $removeTokenFromBodyWhenCookiesUsed; + private iterable $cookieProviders; + + /** + * @param iterable|JWTCookieProvider[] $cookieProviders + */ + public function __construct(JWTTokenManagerInterface $jwtManager, EventDispatcherInterface $dispatcher, iterable $cookieProviders = [], bool $removeTokenFromBodyWhenCookiesUsed = true) + { + $this->jwtManager = $jwtManager; + $this->dispatcher = $dispatcher; + $this->cookieProviders = $cookieProviders; + $this->removeTokenFromBodyWhenCookiesUsed = $removeTokenFromBodyWhenCookiesUsed; + } + + /** + * {@inheritdoc} + */ + public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response + { + return $this->handleAuthenticationSuccess($token->getUser()); + } + + public function handleAuthenticationSuccess(UserInterface $user, $jwt = null): Response + { + if (null === $jwt) { + $jwt = $this->jwtManager->create($user); + } + + $jwtCookies = []; + foreach ($this->cookieProviders as $cookieProvider) { + $jwtCookies[] = $cookieProvider->createCookie($jwt); + } + + $response = new JWTAuthenticationSuccessResponse($jwt, [], $jwtCookies); + $event = new AuthenticationSuccessEvent(['token' => $jwt], $user, $response); + + $this->dispatcher->dispatch($event, Events::AUTHENTICATION_SUCCESS); + $responseData = $event->getData(); + + if ($jwtCookies && $this->removeTokenFromBodyWhenCookiesUsed) { + unset($responseData['token']); + } + + if ($responseData) { + $response->setData($responseData); + } else { + $response->setStatusCode(Response::HTTP_NO_CONTENT); + } + + return $response; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/Http/Cookie/JWTCookieProvider.php b/vendor/lexik/jwt-authentication-bundle/Security/Http/Cookie/JWTCookieProvider.php new file mode 100644 index 0000000..2235556 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/Http/Cookie/JWTCookieProvider.php @@ -0,0 +1,81 @@ +defaultName = $defaultName; + $this->defaultLifetime = $defaultLifetime; + $this->defaultSameSite = $defaultSameSite; + $this->defaultPath = $defaultPath; + $this->defaultDomain = $defaultDomain; + $this->defaultSecure = $defaultSecure; + $this->defaultHttpOnly = $defaultHttpOnly; + $this->defaultSplit = $defaultSplit; + $this->defaultPartitioned = $defaultPartitioned; + + if ($defaultPartitioned && Kernel::VERSION < '6.4') { + throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION)); + } + } + + /** + * Creates a secure cookie containing the passed JWT. + * + * For each argument (all args except $jwt), if omitted or set to null then the + * default value defined via the constructor will be used. + */ + public function createCookie(string $jwt, ?string $name = null, $expiresAt = null, ?string $sameSite = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, array $split = [], ?bool $partitioned = null): Cookie + { + if (!$name && !$this->defaultName) { + throw new \LogicException(sprintf('The cookie name must be provided, either pass it as 2nd argument of %s or set a default name via the constructor.', __METHOD__)); + } + + if (!$expiresAt && null === $this->defaultLifetime) { + throw new \LogicException(sprintf('The cookie expiration time must be provided, either pass it as 3rd argument of %s or set a default lifetime via the constructor.', __METHOD__)); + } + + if ($partitioned && Kernel::VERSION < '6.4') { + throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION)); + } + + $jwtParts = new JWTSplitter($jwt); + $jwt = $jwtParts->getParts($split ?: $this->defaultSplit); + + if (null === $expiresAt) { + $expiresAt = 0 === $this->defaultLifetime ? 0 : (time() + $this->defaultLifetime); + } + + return Cookie::create( + $name ?: $this->defaultName, + $jwt, + $expiresAt, + $path ?: $this->defaultPath, + $domain ?: $this->defaultDomain, + $secure ?: $this->defaultSecure, + $httpOnly ?: $this->defaultHttpOnly, + false, + $sameSite ?: $this->defaultSameSite, + $partitioned ?: $this->defaultPartitioned + ); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/User/JWTUser.php b/vendor/lexik/jwt-authentication-bundle/Security/User/JWTUser.php new file mode 100644 index 0000000..f93cab2 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/User/JWTUser.php @@ -0,0 +1,72 @@ + + */ +class JWTUser implements JWTUserInterface +{ + private string $userIdentifier; + private array $roles; + + public function __construct(string $userIdentifier, array $roles = []) + { + $this->userIdentifier = $userIdentifier; + $this->roles = $roles; + } + + /** + * {@inheritdoc} + */ + public static function createFromPayload($username, array $payload): JWTUserInterface + { + if (isset($payload['roles'])) { + return new static($username, (array) $payload['roles']); + } + + return new static($username); + } + + public function getUsername(): string + { + return $this->getUserIdentifier(); + } + + public function getUserIdentifier(): string + { + return $this->userIdentifier; + } + + /** + * {@inheritdoc} + */ + public function getRoles(): array + { + return $this->roles; + } + + public function getPassword(): ?string + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getSalt(): ?string + { + return null; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials(): void + { + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/User/JWTUserInterface.php b/vendor/lexik/jwt-authentication-bundle/Security/User/JWTUserInterface.php new file mode 100644 index 0000000..0ff8fdd --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/User/JWTUserInterface.php @@ -0,0 +1,17 @@ + + */ +final class JWTUserProvider implements PayloadAwareUserProviderInterface +{ + private string $class; + + private array $cache = []; + + /** + * @param string $class The {@link JWTUserInterface} implementation FQCN for which to provide instances + */ + public function __construct(string $class) + { + $this->class = $class; + } + + /** + * To be removed at the same time as symfony 5.4 support. + */ + public function loadUserByUsername(string $username): UserInterface + { + // to be removed at the same time as symfony 5.4 support + throw new \LogicException('This method is implemented for BC purpose and should never be called.'); + } + + /** + * {@inheritdoc} + * + * @param array $payload The JWT payload from which to create an instance + */ + public function loadUserByIdentifier(string $identifier, array $payload = []): UserInterface + { + return $this->loadUserByIdentifierAndPayload($identifier, $payload); + } + + public function loadUserByIdentifierAndPayload(string $identifier, array $payload): UserInterface + { + if (isset($this->cache[$identifier])) { + return $this->cache[$identifier]; + } + + $class = $this->class; + + return $this->cache[$identifier] = $class::createFromPayload($identifier, $payload); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class): bool + { + return $class === $this->class || (new \ReflectionClass($class))->implementsInterface(JWTUserInterface::class); + } + + public function refreshUser(UserInterface $user): UserInterface + { + return $user; // noop + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Security/User/PayloadAwareUserProviderInterface.php b/vendor/lexik/jwt-authentication-bundle/Security/User/PayloadAwareUserProviderInterface.php new file mode 100644 index 0000000..204d008 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Security/User/PayloadAwareUserProviderInterface.php @@ -0,0 +1,17 @@ +cacheJwt = $cacheJwt; + } + + public function add(array $payload): bool + { + if (!isset($payload['exp'])) { + throw new MissingClaimException('exp'); + } + + $expiration = new DateTimeImmutable('@' . $payload['exp'], new DateTimeZone('UTC')); + $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); + + // If the token is already expired, there's no point in adding it to storage + if ($expiration <= $now) { + return false; + } + + $cacheExpiration = $expiration->add(new DateInterval('PT5M')); + + if (!isset($payload['jti'])) { + throw new MissingClaimException('jti'); + } + + $cacheItem = $this->cacheJwt->getItem($payload['jti']); + $cacheItem->set([]); + $cacheItem->expiresAt($cacheExpiration); + $this->cacheJwt->save($cacheItem); + + return true; + } + + public function has(array $payload): bool + { + if (!isset($payload['jti'])) { + throw new MissingClaimException('jti'); + } + + return $this->cacheJwt->hasItem($payload['jti']); + } + + public function remove(array $payload): void + { + if (!isset($payload['jti'])) { + throw new MissingClaimException('jti'); + } + + $this->cacheJwt->deleteItem($payload['jti']); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/BlockedTokenManagerInterface.php b/vendor/lexik/jwt-authentication-bundle/Services/BlockedTokenManagerInterface.php new file mode 100644 index 0000000..d1b59fb --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/BlockedTokenManagerInterface.php @@ -0,0 +1,23 @@ + + */ +interface JWSProviderInterface +{ + /** + * Creates a new JWS signature from a given payload. + */ + public function create(array $payload, array $header = []): CreatedJWS; + + /** + * Loads an existing JWS signature from a given JWT token. + */ + public function load(string $token): LoadedJWS; +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/JWSProvider/LcobucciJWSProvider.php b/vendor/lexik/jwt-authentication-bundle/Services/JWSProvider/LcobucciJWSProvider.php new file mode 100644 index 0000000..f7d2970 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/JWSProvider/LcobucciJWSProvider.php @@ -0,0 +1,228 @@ + + */ +class LcobucciJWSProvider implements JWSProviderInterface +{ + private KeyLoaderInterface $keyLoader; + private Clock $clock; + private Signer $signer; + private ?int $ttl; + private ?int $clockSkew; + private bool $allowNoExpiration; + + /** + * @throws \InvalidArgumentException If the given crypto engine is not supported + */ + public function __construct(KeyLoaderInterface $keyLoader, string $signatureAlgorithm, ?int $ttl, ?int $clockSkew, bool $allowNoExpiration = false, Clock $clock = null) + { + if (null === $clock) { + $clock = new SystemClock(new \DateTimeZone('UTC')); + } + + $this->keyLoader = $keyLoader; + $this->clock = $clock; + $this->signer = $this->getSignerForAlgorithm($signatureAlgorithm); + $this->ttl = $ttl; + $this->clockSkew = $clockSkew; + $this->allowNoExpiration = $allowNoExpiration; + } + + /** + * {@inheritdoc} + */ + public function create(array $payload, array $header = []): CreatedJWS + { + $jws = new JWTBuilder(new JoseEncoder(), ChainedFormatter::default()); + + foreach ($header as $k => $v) { + $jws = $jws->withHeader($k, $v); + } + + $now = time(); + + $issuedAt = $payload['iat'] ?? $now; + unset($payload['iat']); + + $jws = $jws->issuedAt(!$issuedAt instanceof \DateTimeImmutable ? new \DateTimeImmutable("@{$issuedAt}") : $issuedAt); + + $exp = null; + if (null !== $this->ttl || isset($payload['exp'])) { + $exp = $payload['exp'] ?? $now + $this->ttl; + unset($payload['exp']); + } + + if ($exp) { + $jws = $jws->expiresAt(!$exp instanceof \DateTimeImmutable ? new \DateTimeImmutable("@{$exp}") : $exp); + } + + if (isset($payload['sub'])) { + $jws = $jws->relatedTo($payload['sub']); + unset($payload['sub']); + } + + $jws = $this->addStandardClaims($jws, $payload); + + foreach ($payload as $name => $value) { + $jws = $jws->withClaim($name, $value); + } + + $e = $token = null; + try { + $token = $this->getSignedToken($jws); + } catch (\InvalidArgumentException) { + } + + return new CreatedJWS((string) $token, null === $e); + } + + /** + * {@inheritdoc} + */ + public function load(string $token): LoadedJWS + { + $jws = (new JWTParser(new JoseEncoder()))->parse($token); + + $payload = []; + foreach ($jws->claims()->all() as $name => $value) { + if ($value instanceof \DateTimeInterface) { + $value = $value->getTimestamp(); + } + $payload[$name] = $value; + } + + return new LoadedJWS( + $payload, + $this->verify($jws), + false == $this->allowNoExpiration, + $jws->headers()->all(), + $this->clockSkew + ); + } + + private function getSignerForAlgorithm($signatureAlgorithm): Signer + { + $signerMap = [ + 'HS256' => Sha256::class, + 'HS384' => Sha384::class, + 'HS512' => Sha512::class, + 'RS256' => Signer\Rsa\Sha256::class, + 'RS384' => Signer\Rsa\Sha384::class, + 'RS512' => Signer\Rsa\Sha512::class, + 'ES256' => Signer\Ecdsa\Sha256::class, + 'ES384' => Signer\Ecdsa\Sha384::class, + 'ES512' => Signer\Ecdsa\Sha512::class, + ]; + + if (!isset($signerMap[$signatureAlgorithm])) { + throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported by %s', $signatureAlgorithm, self::class)); + } + + $signerClass = $signerMap[$signatureAlgorithm]; + + if (is_subclass_of($signerClass, Ecdsa::class) && method_exists($signerClass, 'create')) { + return $signerClass::create(); + } + + return new $signerClass(); + } + + private function getSignedToken(Builder $jws): string + { + $key = InMemory::plainText($this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PRIVATE), $this->signer instanceof Hmac ? '' : (string) $this->keyLoader->getPassphrase()); + + $token = $jws->getToken($this->signer, $key); + + return $token->toString(); + } + + private function verify(Token $jwt): bool + { + $key = InMemory::plainText($this->signer instanceof Hmac ? $this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PRIVATE) : $this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PUBLIC)); + $validator = new Validator(); + + $isValid = $validator->validate( + $jwt, + new LooseValidAt($this->clock, new \DateInterval("PT{$this->clockSkew}S")), + new SignedWith($this->signer, $key) + ); + + $publicKeys = $this->keyLoader->getAdditionalPublicKeys(); + if ($isValid || $this->signer instanceof Hmac || empty($publicKeys)) { + return $isValid; + } + + // If the key used to verify the token is invalid, and it's not Hmac algorithm, try with additional public keys + foreach ($publicKeys as $key) { + $isValid = $validator->validate( + $jwt, + new LooseValidAt($this->clock, new \DateInterval("PT{$this->clockSkew}S")), + new SignedWith($this->signer, InMemory::plainText($key)) + ); + + if ($isValid) { + return true; + } + } + + return false; + } + + private function addStandardClaims(Builder $builder, array &$payload): Builder + { + $mutatorMap = [ + RegisteredClaims::AUDIENCE => 'permittedFor', + RegisteredClaims::ID => 'identifiedBy', + RegisteredClaims::ISSUER => 'issuedBy', + RegisteredClaims::NOT_BEFORE => 'canOnlyBeUsedAfter', + ]; + + foreach ($payload as $claim => $value) { + if (!isset($mutatorMap[$claim])) { + continue; + } + + $mutator = $mutatorMap[$claim]; + unset($payload[$claim]); + + if (\is_array($value)) { + $builder = \call_user_func_array([$builder, $mutator], $value); + continue; + } + + $builder = $builder->{$mutator}($value); + } + + return $builder; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/JWTManager.php b/vendor/lexik/jwt-authentication-bundle/Services/JWTManager.php new file mode 100644 index 0000000..0266b58 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/JWTManager.php @@ -0,0 +1,148 @@ + + * @author Robin Chalas + */ +class JWTManager implements JWTTokenManagerInterface +{ + protected JWTEncoderInterface $jwtEncoder; + protected EventDispatcherInterface $dispatcher; + protected string $userIdClaim; + private $payloadEnrichment; + + public function __construct(JWTEncoderInterface $encoder, EventDispatcherInterface $dispatcher, string $userIdClaim, PayloadEnrichmentInterface $payloadEnrichment = null) + { + $this->jwtEncoder = $encoder; + $this->dispatcher = $dispatcher; + $this->userIdClaim = $userIdClaim; + $this->payloadEnrichment = $payloadEnrichment ?? new NullEnrichment(); + } + + /** + * @return string The JWT token + * + * @throws JWTEncodeFailureException + */ + public function create(UserInterface $user): string + { + $payload = ['roles' => $user->getRoles()]; + $this->addUserIdentityToPayload($user, $payload); + + $this->payloadEnrichment->enrich($user, $payload); + + return $this->generateJwtStringAndDispatchEvents($user, $payload); + } + + /** + * @return string The JWT token + * + * @throws JWTEncodeFailureException + */ + public function createFromPayload(UserInterface $user, array $payload = []): string + { + $payload = array_merge(['roles' => $user->getRoles()], $payload); + $this->addUserIdentityToPayload($user, $payload); + + $this->payloadEnrichment->enrich($user, $payload); + + return $this->generateJwtStringAndDispatchEvents($user, $payload); + } + + /** + * @return string The JWT token + * + * @throws JWTEncodeFailureException + */ + private function generateJwtStringAndDispatchEvents(UserInterface $user, array $payload): string + { + $jwtCreatedEvent = new JWTCreatedEvent($payload, $user); + $this->dispatcher->dispatch($jwtCreatedEvent, Events::JWT_CREATED); + + if ($this->jwtEncoder instanceof HeaderAwareJWTEncoderInterface) { + $jwtString = $this->jwtEncoder->encode($jwtCreatedEvent->getData(), $jwtCreatedEvent->getHeader()); + } else { + $jwtString = $this->jwtEncoder->encode($jwtCreatedEvent->getData()); + } + + $jwtEncodedEvent = new JWTEncodedEvent($jwtString); + + $this->dispatcher->dispatch($jwtEncodedEvent, Events::JWT_ENCODED); + + return $jwtString; + } + + /** + * {@inheritdoc} + * + * @throws JWTDecodeFailureException + */ + public function decode(TokenInterface $token): array|bool + { + if (!($payload = $this->jwtEncoder->decode($token->getCredentials()))) { + return false; + } + + $event = new JWTDecodedEvent($payload); + $this->dispatcher->dispatch($event, Events::JWT_DECODED); + + if (!$event->isValid()) { + return false; + } + + return $event->getPayload(); + } + + /** + * {@inheritdoc} + * + * @throws JWTDecodeFailureException + */ + public function parse(string $token): array + { + $payload = $this->jwtEncoder->decode($token); + + $event = new JWTDecodedEvent($payload); + $this->dispatcher->dispatch($event, Events::JWT_DECODED); + + if (!$event->isValid()) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'The token was marked as invalid by an event listener after successful decoding.'); + } + + return $event->getPayload(); + } + + /** + * Add user identity to payload, username by default. + * Override this if you need to identify it by another property. + */ + protected function addUserIdentityToPayload(UserInterface $user, array &$payload): void + { + $accessor = PropertyAccess::createPropertyAccessor(); + $payload[$this->userIdClaim] = $accessor->getValue($user, $accessor->isReadable($user, $this->userIdClaim) ? $this->userIdClaim : 'user_identifier'); + } + + public function getUserIdClaim(): string + { + return $this->userIdClaim; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/JWTTokenManagerInterface.php b/vendor/lexik/jwt-authentication-bundle/Services/JWTTokenManagerInterface.php new file mode 100644 index 0000000..ff07aef --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/JWTTokenManagerInterface.php @@ -0,0 +1,42 @@ + + * @author Eric Lannez + */ +interface JWTTokenManagerInterface +{ + /** + * @return string The JWT token + */ + public function create(UserInterface $user); + + public function createFromPayload(UserInterface $user, array $payload = []): string; + + /** + * @return array|false The JWT token payload or false if an error occurs + * @throws JWTDecodeFailureException + */ + public function decode(TokenInterface $token): array|bool; + + /** + * Parses a raw JWT token and returns its payload + */ + public function parse(string $token): array; + + /** + * Returns the claim used as identifier to load an user from a JWT payload. + * + * @return string + */ + public function getUserIdClaim(); +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/AbstractKeyLoader.php b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/AbstractKeyLoader.php new file mode 100644 index 0000000..e10d593 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/AbstractKeyLoader.php @@ -0,0 +1,90 @@ + + * + * @internal + */ +abstract class AbstractKeyLoader implements KeyLoaderInterface +{ + private ?string $signingKey; + private ?string $publicKey; + private ?string $passphrase; + private array $additionalPublicKeys; + + public function __construct(?string $signingKey = null, ?string $publicKey = null, ?string $passphrase = null, array $additionalPublicKeys = []) + { + $this->signingKey = $signingKey; + $this->publicKey = $publicKey; + $this->passphrase = $passphrase; + $this->additionalPublicKeys = $additionalPublicKeys; + } + + /** + * {@inheritdoc} + */ + public function getPassphrase(): ?string + { + return $this->passphrase; + } + + public function getSigningKey(): ?string + { + return $this->signingKey && is_file($this->signingKey) ? $this->readKey(self::TYPE_PRIVATE) : $this->signingKey; + } + + public function getPublicKey(): ?string + { + return $this->publicKey && is_file($this->publicKey) ? $this->readKey(self::TYPE_PUBLIC) : $this->publicKey; + } + + public function getAdditionalPublicKeys(): array + { + $contents = []; + + foreach ($this->additionalPublicKeys as $key) { + if (!$key || !is_file($key) || !is_readable($key)) { + throw new \RuntimeException(sprintf('Additional public key "%s" does not exist or is not readable. Did you correctly set the "lexik_jwt_authentication.additional_public_keys" configuration key?', $key)); + } + + $rawKey = @file_get_contents($key); + + if (false === $rawKey) { + // Try invalidating the realpath cache + clearstatcache(true, $key); + $rawKey = file_get_contents($key); + } + $contents[] = $rawKey; + } + + return $contents; + } + + private function readKey($type): ?string + { + $isPublic = self::TYPE_PUBLIC === $type; + $key = $isPublic ? $this->publicKey : $this->signingKey; + + if (!$key || !is_file($key) || !is_readable($key)) { + if ($isPublic) { + return null; + } + + throw new \RuntimeException(sprintf('Signature key "%s" does not exist or is not readable. Did you correctly set the "lexik_jwt_authentication.signature_key" configuration key?', $key)); + } + + $rawKey = @file_get_contents($key); + + if (false === $rawKey) { + // Try invalidating the realpath cache + clearstatcache(true, $key); + $rawKey = file_get_contents($key); + } + + return $rawKey; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/KeyDumperInterface.php b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/KeyDumperInterface.php new file mode 100644 index 0000000..edaaf89 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/KeyDumperInterface.php @@ -0,0 +1,16 @@ + + */ +interface KeyDumperInterface +{ + /** + * Dumps a key to be shared between parties. + * + * @return resource|string + */ + public function dumpKey(); +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/KeyLoaderInterface.php b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/KeyLoaderInterface.php new file mode 100644 index 0000000..44b97be --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/KeyLoaderInterface.php @@ -0,0 +1,31 @@ + + */ +interface KeyLoaderInterface +{ + public const TYPE_PUBLIC = 'public'; + public const TYPE_PRIVATE = 'private'; + + /** + * Loads a key from a given type (public or private). + * + * @param resource|string|null $type + * + * @return resource|string|null + */ + public function loadKey($type); + + public function getPassphrase(): ?string; + + public function getSigningKey(): ?string; + + public function getPublicKey(): ?string; + + public function getAdditionalPublicKeys(): array; +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/RawKeyLoader.php b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/RawKeyLoader.php new file mode 100644 index 0000000..06392ed --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/KeyLoader/RawKeyLoader.php @@ -0,0 +1,52 @@ + + */ +class RawKeyLoader extends AbstractKeyLoader implements KeyDumperInterface +{ + /** + * @param string $type + * + * @return string + * + * @throws \RuntimeException If the key cannot be read + */ + public function loadKey($type) + { + if (!in_array($type, [self::TYPE_PUBLIC, self::TYPE_PRIVATE])) { + throw new \InvalidArgumentException(sprintf('The key type must be "public" or "private", "%s" given.', $type)); + } + + if (self::TYPE_PUBLIC === $type) { + return $this->dumpKey(); + } + + return $this->getSigningKey(); + } + + /** + * {@inheritdoc} + */ + public function dumpKey() + { + if ($publicKey = $this->getPublicKey()) { + return $publicKey; + } + + $signingKey = $this->getSigningKey(); + + // no public key provided, compute it from signing key + try { + $publicKey = openssl_pkey_get_details(openssl_pkey_get_private($signingKey, $this->getPassphrase()))['key']; + } catch (\Throwable $e) { + throw new \RuntimeException('Secret key either does not exist, is not readable or is invalid. Did you correctly set the "lexik_jwt_authentication.secret_key" config option?'); + } + + return $publicKey; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/PayloadEnrichment/ChainEnrichment.php b/vendor/lexik/jwt-authentication-bundle/Services/PayloadEnrichment/ChainEnrichment.php new file mode 100644 index 0000000..5dcc4b5 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/PayloadEnrichment/ChainEnrichment.php @@ -0,0 +1,26 @@ +enrichments = $enrichments; + } + + public function enrich(UserInterface $user, array &$payload): void + { + foreach ($this->enrichments as $enrichment) { + $enrichment->enrich($user, $payload); + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/PayloadEnrichment/NullEnrichment.php b/vendor/lexik/jwt-authentication-bundle/Services/PayloadEnrichment/NullEnrichment.php new file mode 100644 index 0000000..f18669f --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/PayloadEnrichment/NullEnrichment.php @@ -0,0 +1,13 @@ +jwsBuilder = $jwsBuilderFactory->create([$signatureAlgorithm]); + if ($jweBuilderFactory !== null && $keyEncryptionAlgorithm !== null && $contentEncryptionAlgorithm !== null) { + $this->jweBuilder = $jweBuilderFactory->create([$keyEncryptionAlgorithm, $contentEncryptionAlgorithm]); + } + $this->signatureKey = JWK::createFromJson($signatureKey); + $this->encryptionKey = $encryptionKey ? JWK::createFromJson($encryptionKey) : null; + $this->signatureAlgorithm = $signatureAlgorithm; + $this->keyEncryptionAlgorithm = $keyEncryptionAlgorithm; + $this->contentEncryptionAlgorithm = $contentEncryptionAlgorithm; + $this->dispatcher = $dispatcher; + } + + public function build(array $header, array $claims): string + { + $token = $this->buildJWS($header, $claims); + + if ($this->jweBuilder !== null) { + $token = $this->buildJWE($claims, $token); + } + + return $token; + } + + /** + * @param array $header + * @param array $claims + */ + private function buildJWS(array $header, array $claims): string + { + $header['alg'] = $this->signatureAlgorithm; + if ($this->signatureKey->has('kid')) { + $header['kid'] = $this->signatureKey->get('kid'); + } + $jws = $this->jwsBuilder + ->create() + ->withPayload(json_encode($claims, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) + ->addSignature($this->signatureKey, $header) + ->build() + ; + $token = (new JwsCompactSerializer())->serialize($jws); + + return $token; + } + + /** + * @param array $header + */ + private function buildJWE(array $claims, string $payload): string + { + $header = [ + 'alg' => $this->keyEncryptionAlgorithm, + 'enc' => $this->contentEncryptionAlgorithm, + 'cty' => 'JWT', + 'typ' => 'JWT', + ]; + if ($this->encryptionKey->has('kid')) { + $header['kid'] = $this->encryptionKey->get('kid'); + } + foreach (['exp', 'iat', 'nbf'] as $claim) { + if (array_key_exists($claim, $claims)) { + $header[$claim] = $claims[$claim]; + } + } + $event = $this->dispatcher->dispatch(new BeforeJWEComputationEvent($header), Events::BEFORE_JWE_COMPUTATION); + $jwe = $this->jweBuilder + ->create() + ->withPayload($payload) + ->withSharedProtectedHeader($event->getHeader()) + ->addRecipient($this->encryptionKey) + ->build() + ; + return (new JweCompactSerializer())->serialize($jwe); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Services/WebToken/AccessTokenLoader.php b/vendor/lexik/jwt-authentication-bundle/Services/WebToken/AccessTokenLoader.php new file mode 100644 index 0000000..8b48a5d --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Services/WebToken/AccessTokenLoader.php @@ -0,0 +1,126 @@ +jwsLoader = $jwsLoaderFactory->create(['jws_compact'], $signatureAlgorithms, $jwsHeaderChecker); + if ($jweLoaderFactory !== null && $keyEncryptionAlgorithms !== null && $contentEncryptionAlgorithms !== null && $jweHeaderChecker !== null) { + $this->jweLoader = $jweLoaderFactory->create(['jwe_compact'], array_merge($keyEncryptionAlgorithms, $contentEncryptionAlgorithms), null, null, $jweHeaderChecker); + $this->continueOnDecryptionFailure = $continueOnDecryptionFailure; + } + $this->signatureKeyset = JWKSet::createFromJson($signatureKeyset); + $this->encryptionKeyset = $encryptionKeyset ? JWKSet::createFromJson($encryptionKeyset) : null; + $this->claimCheckerManager = $claimCheckerManagerFactory->create($claimChecker); + $this->mandatoryClaims = $mandatoryClaims; + } + + public function load(string $token): array + { + $token = $this->loadJWE($token); + $data = $this->loadJWS($token); + try { + $this->claimCheckerManager->check($data, $this->mandatoryClaims); + } catch (MissingMandatoryClaimException|InvalidClaimException $e) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, $e->getMessage(), $e, $data); + } catch (\Throwable $e) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Unable to load the token', $e, $data); + } + + return $data; + } + + /** + * @return array + */ + private function loadJWS(string $token): array + { + $payload = null; + $data = null; + $signature = null; + try { + $jws = $this->jwsLoader->loadAndVerifyWithKeySet($token, $this->signatureKeyset, $signature); + } catch (\Throwable $e) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be loaded or the signature cannot be verified.'); + } + if ($signature !== 0) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token shall contain only one signature.'); + } + + $payload = $jws->getPayload(); + if (!$payload) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.'); + } + + $data = json_decode($payload, true); + if (!is_array($data)) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.'); + } + + return $data; + } + + private function loadJWE(string $token): string + { + if (!$this->jweLoader) { + return $token; + } + + $recipient = null; + try { + $jwe = $this->jweLoader->loadAndDecryptWithKeySet($token, $this->encryptionKeyset, $recipient); + } catch (\Throwable $e) { + if ($this->continueOnDecryptionFailure === true) { + return $token; + } + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be decrypted.', $e); + } + $token = $jwe->getPayload(); + if (!$token || $recipient !== 0) { + throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token has no valid content.'); + } + + return $token; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Signature/CreatedJWS.php b/vendor/lexik/jwt-authentication-bundle/Signature/CreatedJWS.php new file mode 100644 index 0000000..2a26f07 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Signature/CreatedJWS.php @@ -0,0 +1,30 @@ + + */ +final class CreatedJWS +{ + private string $token; + private bool $signed; + + public function __construct(string $token, bool $isSigned) + { + $this->token = $token; + $this->signed = $isSigned; + } + + public function isSigned(): bool + { + return $this->signed; + } + + public function getToken(): string + { + return $this->token; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Signature/LoadedJWS.php b/vendor/lexik/jwt-authentication-bundle/Signature/LoadedJWS.php new file mode 100644 index 0000000..cdacc9a --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Signature/LoadedJWS.php @@ -0,0 +1,91 @@ + + */ +final class LoadedJWS +{ + public const VERIFIED = 'verified'; + public const EXPIRED = 'expired'; + public const INVALID = 'invalid'; + + private array $header; + private array $payload; + private ?string $state = null; + private int $clockSkew; + private bool $shouldCheckExpiration; + + public function __construct(array $payload, bool $isVerified, bool $shouldCheckExpiration = true, array $header = [], int $clockSkew = 0) + { + $this->payload = $payload; + $this->header = $header; + $this->shouldCheckExpiration = $shouldCheckExpiration; + $this->clockSkew = $clockSkew; + + if (true === $isVerified) { + $this->state = self::VERIFIED; + } + + $this->checkIssuedAt(); + $this->checkExpiration(); + } + + public function getHeader(): array + { + return $this->header; + } + + public function getPayload(): array + { + return $this->payload; + } + + public function isVerified(): bool + { + return self::VERIFIED === $this->state; + } + + public function isExpired(): bool + { + $this->checkExpiration(); + + return self::EXPIRED === $this->state; + } + + public function isInvalid(): bool + { + return self::INVALID === $this->state; + } + + private function checkExpiration(): void + { + if (!$this->shouldCheckExpiration) { + return; + } + + if (!isset($this->payload['exp']) || !is_numeric($this->payload['exp'])) { + $this->state = self::INVALID; + + return; + } + + if ($this->clockSkew <= time() - $this->payload['exp']) { + $this->state = self::EXPIRED; + } + } + + /** + * Ensures that the iat claim is not in the future. + */ + private function checkIssuedAt(): void + { + if (isset($this->payload['iat']) && (int) $this->payload['iat'] - $this->clockSkew > time()) { + $this->state = self::INVALID; + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/Subscriber/AdditionalAccessTokenClaimsAndHeaderSubscriber.php b/vendor/lexik/jwt-authentication-bundle/Subscriber/AdditionalAccessTokenClaimsAndHeaderSubscriber.php new file mode 100644 index 0000000..18fdcbc --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/Subscriber/AdditionalAccessTokenClaimsAndHeaderSubscriber.php @@ -0,0 +1,43 @@ +ttl = $ttl; + } + + public static function getSubscribedEvents(): array + { + return [ + Events::JWT_CREATED => [ + ['addClaims'], + ], + ]; + } + + public function addClaims(JWTCreatedEvent $event): void + { + $claims = [ + 'jti' => uniqid('', true), + 'iat' => time(), + 'nbf' => time(), + ]; + $data = $event->getData(); + if (!array_key_exists('exp', $data) && $this->ttl > 0) { + $claims['exp'] = time() + $this->ttl; + } + $event->setData(array_merge($claims, $data)); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/TokenExtractor/AuthorizationHeaderTokenExtractor.php b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/AuthorizationHeaderTokenExtractor.php new file mode 100644 index 0000000..7fcac68 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/AuthorizationHeaderTokenExtractor.php @@ -0,0 +1,47 @@ + + */ +class AuthorizationHeaderTokenExtractor implements TokenExtractorInterface +{ + protected ?string $prefix; + + protected string $name; + + public function __construct(?string $prefix, string $name) + { + $this->prefix = $prefix; + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function extract(Request $request) + { + if (!$request->headers->has($this->name)) { + return false; + } + + $authorizationHeader = $request->headers->get($this->name); + + if (empty($this->prefix)) { + return $authorizationHeader; + } + + $headerParts = explode(' ', (string) $authorizationHeader); + + if (!(2 === count($headerParts) && 0 === strcasecmp($headerParts[0], $this->prefix))) { + return false; + } + + return $headerParts[1]; + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/TokenExtractor/ChainTokenExtractor.php b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/ChainTokenExtractor.php new file mode 100644 index 0000000..4e8b189 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/ChainTokenExtractor.php @@ -0,0 +1,93 @@ + + */ +class ChainTokenExtractor implements \IteratorAggregate, TokenExtractorInterface +{ + private array $map; + + public function __construct(array $map) + { + $this->map = $map; + } + + /** + * Adds a new token extractor to the map. + */ + public function addExtractor(TokenExtractorInterface $extractor) + { + $this->map[] = $extractor; + } + + /** + * Removes a token extractor from the map. + * + * @param \Closure $filter A function taking an extractor as argument, used to find the extractor to remove. + * + * @return bool True in case of success, false otherwise + */ + public function removeExtractor(\Closure $filter) + { + $filtered = array_filter($this->map, $filter); + + if (!$extractorToUnmap = current($filtered)) { + return false; + } + + $key = array_search($extractorToUnmap, $this->map); + unset($this->map[$key]); + + return true; + } + + /** + * Clears the token extractor map. + */ + public function clearMap() + { + $this->map = []; + } + + /** + * Iterates over the token extractors map calling {@see extract()} + * until a token is found. + * + * {@inheritdoc} + */ + public function extract(Request $request) + { + foreach ($this->getIterator() as $extractor) { + if ($token = $extractor->extract($request)) { + return $token; + } + } + + return false; + } + + /** + * Iterates over the mapped token extractors while generating them. + * + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + foreach ($this->map as $extractor) { + if ($extractor instanceof TokenExtractorInterface) { + yield $extractor; + } + } + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/TokenExtractor/CookieTokenExtractor.php b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/CookieTokenExtractor.php new file mode 100644 index 0000000..6293f8e --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/CookieTokenExtractor.php @@ -0,0 +1,28 @@ + + */ +class CookieTokenExtractor implements TokenExtractorInterface +{ + protected string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function extract(Request $request) + { + return $request->cookies->get($this->name, false); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/TokenExtractor/QueryParameterTokenExtractor.php b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/QueryParameterTokenExtractor.php new file mode 100644 index 0000000..a42bde7 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/QueryParameterTokenExtractor.php @@ -0,0 +1,28 @@ + + */ +class QueryParameterTokenExtractor implements TokenExtractorInterface +{ + protected string $parameterName; + + public function __construct(string $parameterName) + { + $this->parameterName = $parameterName; + } + + /** + * {@inheritdoc} + */ + public function extract(Request $request) + { + return $request->query->get($this->parameterName, false); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/TokenExtractor/SplitCookieExtractor.php b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/SplitCookieExtractor.php new file mode 100644 index 0000000..d4d7883 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/SplitCookieExtractor.php @@ -0,0 +1,38 @@ + + */ +class SplitCookieExtractor implements TokenExtractorInterface +{ + private array $cookies; + + public function __construct(array $cookies) + { + $this->cookies = $cookies; + } + + /** + * {@inheritDoc} + */ + public function extract(Request $request) + { + $jwtCookies = []; + + foreach ($this->cookies as $cookie) { + $jwtCookies[] = $request->cookies->get($cookie, false); + } + + if (count($this->cookies) !== count(array_filter($jwtCookies))) { + return false; + } + + return implode('.', $jwtCookies); + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/TokenExtractor/TokenExtractorInterface.php b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/TokenExtractorInterface.php new file mode 100644 index 0000000..861d303 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/TokenExtractor/TokenExtractorInterface.php @@ -0,0 +1,18 @@ + + */ +interface TokenExtractorInterface +{ + /** + * @return string|false + */ + public function extract(Request $request); +} diff --git a/vendor/lexik/jwt-authentication-bundle/composer.json b/vendor/lexik/jwt-authentication-bundle/composer.json new file mode 100644 index 0000000..84fc87e --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/composer.json @@ -0,0 +1,89 @@ +{ + "name": "lexik/jwt-authentication-bundle", + "type": "symfony-bundle", + "description": "This bundle provides JWT authentication for your Symfony REST API", + "keywords": ["Symfony", "bundle", "jwt", "jws", "authentication", "api", "rest"], + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle", + "license": "MIT", + "authors": [ + { + "name": "Jeremy Barthe", + "email": "j.barthe@lexik.fr", + "homepage": "https://github.com/jeremyb" + }, + { + "name": "Nicolas Cabot", + "email": "n.cabot@lexik.fr", + "homepage": "https://github.com/slashfan" + }, + { + "name": "Cedric Girard", + "email": "c.girard@lexik.fr", + "homepage": "https://github.com/cedric-g" + }, + { + "name": "Dev Lexik", + "email": "dev@lexik.fr", + "homepage": "https://github.com/lexik" + }, + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com", + "homepage": "https://github.com/chalasr" + }, + { + "name": "Lexik Community", + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle/graphs/contributors" + } + ], + "require": { + "php": ">=8.2", + "ext-openssl": "*", + "lcobucci/jwt": "^5.0", + "lcobucci/clock": "^3.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.4|^3.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/translation-contracts": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "api-platform/core": "^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "suggest": { + "gesdinet/jwt-refresh-token-bundle": "Implements a refresh token system over Json Web Tokens in Symfony", + "spomky-labs/lexik-jose-bridge": "Provides a JWT Token encoder with encryption support" + }, + "autoload": { + "psr-4": { + "Lexik\\Bundle\\JWTAuthenticationBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "config": { + "sort-packages": true + }, + "scripts": { + "test": [ + "vendor/bin/simple-phpunit --exclude-group web-token", + "ENCODER=lcobucci vendor/bin/simple-phpunit --exclude-group web-token", + "ENCODER=lcobucci ALGORITHM=HS256 vendor/bin/simple-phpunit --exclude-group web-token", + "ENCODER=user_id_claim vendor/bin/simple-phpunit --exclude-group web-token", + "PROVIDER=lexik_jwt vendor/bin/simple-phpunit --exclude-group web-token" + ] + } +} diff --git a/vendor/lexik/jwt-authentication-bundle/ecs.php b/vendor/lexik/jwt-authentication-bundle/ecs.php new file mode 100644 index 0000000..4fe4772 --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/ecs.php @@ -0,0 +1,25 @@ +sets([SetList::PSR_12]); + $config->rule(OrderedImportsFixer::class); + $config->ruleWithConfiguration(ArraySyntaxFixer::class, [ + 'syntax' => 'short', + ]); + + $config->parallel(); + $config->paths([__DIR__]); + $config->skip([ + __DIR__ . '/.github', + __DIR__ . '/vendor', + PhpdocScalarFixer::class + ]); +}; diff --git a/vendor/lexik/jwt-authentication-bundle/rector.php b/vendor/lexik/jwt-authentication-bundle/rector.php new file mode 100644 index 0000000..91efdeb --- /dev/null +++ b/vendor/lexik/jwt-authentication-bundle/rector.php @@ -0,0 +1,39 @@ +sets([ + LevelSetList::UP_TO_PHP_71, + SymfonySetList::SYMFONY_CODE_QUALITY, + SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION, + ]); + $rectorConfig->phpVersion(\Rector\ValueObject\PhpVersion::PHP_71); + $rectorConfig->importShortClasses(false); + $rectorConfig->importNames(); + $rectorConfig->bootstrapFiles([ + __DIR__ . '/vendor/autoload.php', + ]); + $rectorConfig->parallel(); + $rectorConfig->paths([__DIR__]); + $rectorConfig->skip([ + // Path + __DIR__ . '/.github', + __DIR__ . '/DependencyInjection/Configuration.php', + __DIR__ . '/Tests/DependencyInjection/LexikJWTAuthenticationExtensionTest.php', + __DIR__ . '/vendor', + + // Rules + JsonThrowOnErrorRector::class, + ReturnNeverTypeRector::class => [ + __DIR__ . '/Security/User/JWTUserProvider.php', + ], + ]); +}; diff --git a/vendor/nikic/php-parser/LICENSE b/vendor/nikic/php-parser/LICENSE new file mode 100644 index 0000000..2e56718 --- /dev/null +++ b/vendor/nikic/php-parser/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2011, Nikita Popov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/nikic/php-parser/README.md b/vendor/nikic/php-parser/README.md new file mode 100644 index 0000000..7555838 --- /dev/null +++ b/vendor/nikic/php-parser/README.md @@ -0,0 +1,233 @@ +PHP Parser +========== + +[![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master) + +This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and +manipulation. + +[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.3, with limited support for parsing PHP 5.x). + +[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3). + +Features +-------- + +The main features provided by this library are: + + * Parsing PHP 7, and PHP 8 code into an abstract syntax tree (AST). + * Invalid code can be parsed into a partial AST. + * The AST contains accurate location information. + * Dumping the AST in human-readable form. + * Converting an AST back to PHP code. + * Formatting can be preserved for partially changed ASTs. + * Infrastructure to traverse and modify ASTs. + * Resolution of namespaced names. + * Evaluation of constant expressions. + * Builders to simplify AST construction for code generation. + * Converting an AST into JSON and back. + +Quick Start +----------- + +Install the library using [composer](https://getcomposer.org): + + php composer.phar require nikic/php-parser + +Parse some PHP code into an AST and dump the result in human-readable form: + +```php +createForNewestSupportedVersion(); +try { + $ast = $parser->parse($code); +} catch (Error $error) { + echo "Parse error: {$error->getMessage()}\n"; + return; +} + +$dumper = new NodeDumper; +echo $dumper->dump($ast) . "\n"; +``` + +This dumps an AST looking something like this: + +``` +array( + 0: Stmt_Function( + attrGroups: array( + ) + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + attrGroups: array( + ) + flags: 0 + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + 0: Stmt_Expression( + expr: Expr_FuncCall( + name: Name( + name: var_dump + ) + args: array( + 0: Arg( + name: null + value: Expr_Variable( + name: foo + ) + byRef: false + unpack: false + ) + ) + ) + ) + ) + ) +) +``` + +Let's traverse the AST and perform some kind of modification. For example, drop all function bodies: + +```php +use PhpParser\Node; +use PhpParser\Node\Stmt\Function_; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; + +$traverser = new NodeTraverser(); +$traverser->addVisitor(new class extends NodeVisitorAbstract { + public function enterNode(Node $node) { + if ($node instanceof Function_) { + // Clean out the function body + $node->stmts = []; + } + } +}); + +$ast = $traverser->traverse($ast); +echo $dumper->dump($ast) . "\n"; +``` + +This gives us an AST where the `Function_::$stmts` are empty: + +``` +array( + 0: Stmt_Function( + attrGroups: array( + ) + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + attrGroups: array( + ) + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + ) + ) +) +``` + +Finally, we can convert the new AST back to PHP code: + +```php +use PhpParser\PrettyPrinter; + +$prettyPrinter = new PrettyPrinter\Standard; +echo $prettyPrinter->prettyPrintFile($ast); +``` + +This gives us our original code, minus the `var_dump()` call inside the function: + +```php +createForVersion($attributes['version']); +$dumper = new PhpParser\NodeDumper([ + 'dumpComments' => true, + 'dumpPositions' => $attributes['with-positions'], +]); +$prettyPrinter = new PhpParser\PrettyPrinter\Standard; + +$traverser = new PhpParser\NodeTraverser(); +$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + +foreach ($files as $file) { + if ($file === '-') { + $code = file_get_contents('php://stdin'); + fwrite(STDERR, "====> Stdin:\n"); + } else if (strpos($file, ' Code $code\n"); + } else { + if (!file_exists($file)) { + fwrite(STDERR, "File $file does not exist.\n"); + exit(1); + } + + $code = file_get_contents($file); + fwrite(STDERR, "====> File $file:\n"); + } + + if ($attributes['with-recovery']) { + $errorHandler = new PhpParser\ErrorHandler\Collecting; + $stmts = $parser->parse($code, $errorHandler); + foreach ($errorHandler->getErrors() as $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + } + if (null === $stmts) { + continue; + } + } else { + try { + $stmts = $parser->parse($code); + } catch (PhpParser\Error $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + exit(1); + } + } + + foreach ($operations as $operation) { + if ('dump' === $operation) { + fwrite(STDERR, "==> Node dump:\n"); + echo $dumper->dump($stmts, $code), "\n"; + } elseif ('pretty-print' === $operation) { + fwrite(STDERR, "==> Pretty print:\n"); + echo $prettyPrinter->prettyPrintFile($stmts), "\n"; + } elseif ('json-dump' === $operation) { + fwrite(STDERR, "==> JSON dump:\n"); + echo json_encode($stmts, JSON_PRETTY_PRINT), "\n"; + } elseif ('var-dump' === $operation) { + fwrite(STDERR, "==> var_dump():\n"); + var_dump($stmts); + } elseif ('resolve-names' === $operation) { + fwrite(STDERR, "==> Resolved names.\n"); + $stmts = $traverser->traverse($stmts); + } + } +} + +function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) { + if ($withColumnInfo && $e->hasColumnInfo()) { + return $e->getMessageWithColumnInfo($code); + } else { + return $e->getMessage(); + } +} + +function showHelp($error = '') { + if ($error) { + fwrite(STDERR, $error . "\n\n"); + } + fwrite($error ? STDERR : STDOUT, <<<'OUTPUT' +Usage: php-parse [operations] file1.php [file2.php ...] + or: php-parse [operations] " false, + 'with-positions' => false, + 'with-recovery' => false, + 'version' => PhpParser\PhpVersion::getNewestSupported(), + ]; + + array_shift($args); + $parseOptions = true; + foreach ($args as $arg) { + if (!$parseOptions) { + $files[] = $arg; + continue; + } + + switch ($arg) { + case '--dump': + case '-d': + $operations[] = 'dump'; + break; + case '--pretty-print': + case '-p': + $operations[] = 'pretty-print'; + break; + case '--json-dump': + case '-j': + $operations[] = 'json-dump'; + break; + case '--var-dump': + $operations[] = 'var-dump'; + break; + case '--resolve-names': + case '-N'; + $operations[] = 'resolve-names'; + break; + case '--with-column-info': + case '-c'; + $attributes['with-column-info'] = true; + break; + case '--with-positions': + case '-P': + $attributes['with-positions'] = true; + break; + case '--with-recovery': + case '-r': + $attributes['with-recovery'] = true; + break; + case '--help': + case '-h'; + showHelp(); + break; + case '--': + $parseOptions = false; + break; + default: + if (preg_match('/^--version=(.*)$/', $arg, $matches)) { + $attributes['version'] = PhpParser\PhpVersion::fromString($matches[1]); + } elseif ($arg[0] === '-' && \strlen($arg[0]) > 1) { + showHelp("Invalid operation $arg."); + } else { + $files[] = $arg; + } + } + } + + return [$operations, $files, $attributes]; +} diff --git a/vendor/nikic/php-parser/composer.json b/vendor/nikic/php-parser/composer.json new file mode 100644 index 0000000..b52f3ee --- /dev/null +++ b/vendor/nikic/php-parser/composer.json @@ -0,0 +1,43 @@ +{ + "name": "nikic/php-parser", + "type": "library", + "description": "A PHP parser written in PHP", + "keywords": [ + "php", + "parser" + ], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov" + } + ], + "require": { + "php": ">=7.4", + "ext-tokenizer": "*", + "ext-json": "*", + "ext-ctype": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "ircmaxell/php-yacc": "^0.0.7" + }, + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "autoload-dev": { + "psr-4": { + "PhpParser\\": "test/PhpParser/" + } + }, + "bin": [ + "bin/php-parse" + ] +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder.php b/vendor/nikic/php-parser/lib/PhpParser/Builder.php new file mode 100644 index 0000000..d6aa124 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder.php @@ -0,0 +1,12 @@ + */ + protected array $attributes = []; + /** @var list */ + protected array $constants = []; + + /** @var list */ + protected array $attributeGroups = []; + /** @var Identifier|Node\Name|Node\ComplexType|null */ + protected ?Node $type = null; + + /** + * Creates a class constant builder + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + */ + public function __construct($name, $value) { + $this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))]; + } + + /** + * Add another constant to const group + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + * + * @return $this The builder instance (for fluid interface) + */ + public function addConst($name, $value) { + $this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value)); + + return $this; + } + + /** + * Makes the constant public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the constant protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the constant private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the constant final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Sets the constant type. + * + * @param string|Node\Name|Identifier|Node\ComplexType $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\ClassConst The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\ClassConst( + $this->constants, + $this->flags, + $this->attributes, + $this->attributeGroups, + $this->type + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php new file mode 100644 index 0000000..6f39431 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php @@ -0,0 +1,151 @@ + */ + protected array $implements = []; + protected int $flags = 0; + /** @var list */ + protected array $uses = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $properties = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a class builder. + * + * @param string $name Name of the class + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Extends a class. + * + * @param Name|string $class Name of class to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend($class) { + $this->extends = BuilderHelpers::normalizeName($class); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Makes the class abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::ABSTRACT); + + return $this; + } + + /** + * Makes the class final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Makes the class readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Class_ The built class node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Class_($this->name, [ + 'flags' => $this->flags, + 'extends' => $this->extends, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php new file mode 100644 index 0000000..488b721 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php @@ -0,0 +1,50 @@ + */ + protected array $attributes = []; + + /** + * Adds a statement. + * + * @param PhpParser\Node\Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + abstract public function addStmt($stmt); + + /** + * Adds multiple statements. + * + * @param (PhpParser\Node\Stmt|PhpParser\Builder)[] $stmts The statements to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmts(array $stmts) { + foreach ($stmts as $stmt) { + $this->addStmt($stmt); + } + + return $this; + } + + /** + * Sets doc comment for the declaration. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes['comments'] = [ + BuilderHelpers::normalizeDocComment($docComment) + ]; + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php new file mode 100644 index 0000000..04058bf --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php @@ -0,0 +1,87 @@ + */ + protected array $attributes = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + */ + public function __construct($name) { + $this->name = $name; + } + + /** + * Sets the value. + * + * @param Node\Expr|string|int $value + * + * @return $this + */ + public function setValue($value) { + $this->value = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built enum case node. + * + * @return Stmt\EnumCase The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\EnumCase( + $this->name, + $this->value, + $this->attributeGroups, + $this->attributes + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php new file mode 100644 index 0000000..c00df03 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php @@ -0,0 +1,116 @@ + */ + protected array $implements = []; + /** @var list */ + protected array $uses = []; + /** @var list */ + protected array $enumCases = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Sets the scalar type. + * + * @param string|Identifier $scalarType + * + * @return $this + */ + public function setScalarType($scalarType) { + $this->scalarType = BuilderHelpers::normalizeType($scalarType); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\EnumCase) { + $this->enumCases[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Enum_ The built enum node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Enum_($this->name, [ + 'scalarType' => $this->scalarType, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php new file mode 100644 index 0000000..ff79cb6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php @@ -0,0 +1,73 @@ +returnByRef = true; + + return $this; + } + + /** + * Adds a parameter. + * + * @param Node\Param|Param $param The parameter to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParam($param) { + $param = BuilderHelpers::normalizeNode($param); + + if (!$param instanceof Node\Param) { + throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType())); + } + + $this->params[] = $param; + + return $this; + } + + /** + * Adds multiple parameters. + * + * @param (Node\Param|Param)[] $params The parameters to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParams(array $params) { + foreach ($params as $param) { + $this->addParam($param); + } + + return $this; + } + + /** + * Sets the return type for PHP 7. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type + * + * @return $this The builder instance (for fluid interface) + */ + public function setReturnType($type) { + $this->returnType = BuilderHelpers::normalizeType($type); + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php new file mode 100644 index 0000000..48f5f69 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php @@ -0,0 +1,67 @@ + */ + protected array $stmts = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a function builder. + * + * @param string $name Name of the function + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built function node. + * + * @return Stmt\Function_ The built function node + */ + public function getNode(): Node { + return new Stmt\Function_($this->name, [ + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php new file mode 100644 index 0000000..13dd3f7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php @@ -0,0 +1,94 @@ + */ + protected array $extends = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Extends one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend(...$interfaces) { + foreach ($interfaces as $interface) { + $this->extends[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + // we erase all statements in the body of an interface method + $stmt->stmts = null; + $this->methods[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built interface node. + * + * @return Stmt\Interface_ The built interface node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Interface_($this->name, [ + 'extends' => $this->extends, + 'stmts' => array_merge($this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php new file mode 100644 index 0000000..8358dbe --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php @@ -0,0 +1,147 @@ +|null */ + protected ?array $stmts = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a method builder. + * + * @param string $name Name of the method + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Makes the method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the method static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC); + + return $this; + } + + /** + * Makes the method abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + if (!empty($this->stmts)) { + throw new \LogicException('Cannot make method with statements abstract'); + } + + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT); + $this->stmts = null; // abstract methods don't have statements + + return $this; + } + + /** + * Makes the method final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + if (null === $this->stmts) { + throw new \LogicException('Cannot add statements to an abstract method'); + } + + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built method node. + * + * @return Stmt\ClassMethod The built method node + */ + public function getNode(): Node { + return new Stmt\ClassMethod($this->name, [ + 'flags' => $this->flags, + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php new file mode 100644 index 0000000..80fe6f8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php @@ -0,0 +1,45 @@ +name = null !== $name ? BuilderHelpers::normalizeName($name) : null; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Namespace_ The built node + */ + public function getNode(): Node { + return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php new file mode 100644 index 0000000..f439e87 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php @@ -0,0 +1,149 @@ + */ + protected array $attributeGroups = []; + + /** + * Creates a parameter builder. + * + * @param string $name Name of the parameter + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Sets default value for the parameter. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets type for the parameter. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type + * + * @return $this The builder instance (for fluid interface) + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + if ($this->type == 'void') { + throw new \LogicException('Parameter type cannot be void'); + } + + return $this; + } + + /** + * Make the parameter accept the value by reference. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeByRef() { + $this->byRef = true; + + return $this; + } + + /** + * Make the parameter variadic + * + * @return $this The builder instance (for fluid interface) + */ + public function makeVariadic() { + $this->variadic = true; + + return $this; + } + + /** + * Makes the (promoted) parameter public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the (promoted) parameter protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the (promoted) parameter private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the (promoted) parameter readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built parameter node. + * + * @return Node\Param The built parameter node + */ + public function getNode(): Node { + return new Node\Param( + new Node\Expr\Variable($this->name), + $this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php new file mode 100644 index 0000000..3fc96d9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php @@ -0,0 +1,161 @@ + */ + protected array $attributes = []; + /** @var null|Identifier|Name|ComplexType */ + protected ?Node $type = null; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a property builder. + * + * @param string $name Name of the property + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Makes the property public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the property protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the property private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the property static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC); + + return $this; + } + + /** + * Makes the property readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Sets default value for the property. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the property. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Sets the property type for PHP 7.4+. + * + * @param string|Name|Identifier|ComplexType $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Property The built property node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Property( + $this->flags !== 0 ? $this->flags : Modifiers::PUBLIC, + [ + new Node\PropertyItem($this->name, $this->default) + ], + $this->attributes, + $this->type, + $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php new file mode 100644 index 0000000..cf21c82 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php @@ -0,0 +1,65 @@ +and($trait); + } + } + + /** + * Adds used trait. + * + * @param Node\Name|string $trait Trait name + * + * @return $this The builder instance (for fluid interface) + */ + public function and($trait) { + $this->traits[] = BuilderHelpers::normalizeName($trait); + return $this; + } + + /** + * Adds trait adaptation. + * + * @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation + * + * @return $this The builder instance (for fluid interface) + */ + public function with($adaptation) { + $adaptation = BuilderHelpers::normalizeNode($adaptation); + + if (!$adaptation instanceof Stmt\TraitUseAdaptation) { + throw new \LogicException('Adaptation must have type TraitUseAdaptation'); + } + + $this->adaptations[] = $adaptation; + return $this; + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode(): Node { + return new Stmt\TraitUse($this->traits, $this->adaptations); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php new file mode 100644 index 0000000..fee0958 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php @@ -0,0 +1,145 @@ +type = self::TYPE_UNDEFINED; + + $this->trait = is_null($trait) ? null : BuilderHelpers::normalizeName($trait); + $this->method = BuilderHelpers::normalizeIdentifier($method); + } + + /** + * Sets alias of method. + * + * @param Node\Identifier|string $alias Alias for adapted method + * + * @return $this The builder instance (for fluid interface) + */ + public function as($alias) { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set alias for not alias adaptation buider'); + } + + $this->alias = BuilderHelpers::normalizeIdentifier($alias); + return $this; + } + + /** + * Sets adapted method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->setModifier(Modifiers::PUBLIC); + return $this; + } + + /** + * Sets adapted method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->setModifier(Modifiers::PROTECTED); + return $this; + } + + /** + * Sets adapted method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->setModifier(Modifiers::PRIVATE); + return $this; + } + + /** + * Adds overwritten traits. + * + * @param Node\Name|string ...$traits Traits for overwrite + * + * @return $this The builder instance (for fluid interface) + */ + public function insteadof(...$traits) { + if ($this->type === self::TYPE_UNDEFINED) { + if (is_null($this->trait)) { + throw new \LogicException('Precedence adaptation must have trait'); + } + + $this->type = self::TYPE_PRECEDENCE; + } + + if ($this->type !== self::TYPE_PRECEDENCE) { + throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider'); + } + + foreach ($traits as $trait) { + $this->insteadof[] = BuilderHelpers::normalizeName($trait); + } + + return $this; + } + + protected function setModifier(int $modifier): void { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set access modifier for not alias adaptation buider'); + } + + if (is_null($this->modifier)) { + $this->modifier = $modifier; + } else { + throw new \LogicException('Multiple access type modifiers are not allowed'); + } + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode(): Node { + switch ($this->type) { + case self::TYPE_ALIAS: + return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias); + case self::TYPE_PRECEDENCE: + return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof); + default: + throw new \LogicException('Type of adaptation is not defined'); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php new file mode 100644 index 0000000..ffa1bd5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php @@ -0,0 +1,83 @@ + */ + protected array $uses = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $properties = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built trait node. + * + * @return Stmt\Trait_ The built interface node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Trait_( + $this->name, [ + 'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php new file mode 100644 index 0000000..b82cf13 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php @@ -0,0 +1,49 @@ +name = BuilderHelpers::normalizeName($name); + $this->type = $type; + } + + /** + * Sets alias for used name. + * + * @param string $alias Alias to use (last component of full name by default) + * + * @return $this The builder instance (for fluid interface) + */ + public function as(string $alias) { + $this->alias = $alias; + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Use_ The built node + */ + public function getNode(): Node { + return new Stmt\Use_([ + new Node\UseItem($this->name, $this->alias) + ], $this->type); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php new file mode 100644 index 0000000..b7efe5e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php @@ -0,0 +1,375 @@ +args($args) + ); + } + + /** + * Creates a namespace builder. + * + * @param null|string|Node\Name $name Name of the namespace + * + * @return Builder\Namespace_ The created namespace builder + */ + public function namespace($name): Builder\Namespace_ { + return new Builder\Namespace_($name); + } + + /** + * Creates a class builder. + * + * @param string $name Name of the class + * + * @return Builder\Class_ The created class builder + */ + public function class(string $name): Builder\Class_ { + return new Builder\Class_($name); + } + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + * + * @return Builder\Interface_ The created interface builder + */ + public function interface(string $name): Builder\Interface_ { + return new Builder\Interface_($name); + } + + /** + * Creates a trait builder. + * + * @param string $name Name of the trait + * + * @return Builder\Trait_ The created trait builder + */ + public function trait(string $name): Builder\Trait_ { + return new Builder\Trait_($name); + } + + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + * + * @return Builder\Enum_ The created enum builder + */ + public function enum(string $name): Builder\Enum_ { + return new Builder\Enum_($name); + } + + /** + * Creates a trait use builder. + * + * @param Node\Name|string ...$traits Trait names + * + * @return Builder\TraitUse The created trait use builder + */ + public function useTrait(...$traits): Builder\TraitUse { + return new Builder\TraitUse(...$traits); + } + + /** + * Creates a trait use adaptation builder. + * + * @param Node\Name|string|null $trait Trait name + * @param Node\Identifier|string $method Method name + * + * @return Builder\TraitUseAdaptation The created trait use adaptation builder + */ + public function traitUseAdaptation($trait, $method = null): Builder\TraitUseAdaptation { + if ($method === null) { + $method = $trait; + $trait = null; + } + + return new Builder\TraitUseAdaptation($trait, $method); + } + + /** + * Creates a method builder. + * + * @param string $name Name of the method + * + * @return Builder\Method The created method builder + */ + public function method(string $name): Builder\Method { + return new Builder\Method($name); + } + + /** + * Creates a parameter builder. + * + * @param string $name Name of the parameter + * + * @return Builder\Param The created parameter builder + */ + public function param(string $name): Builder\Param { + return new Builder\Param($name); + } + + /** + * Creates a property builder. + * + * @param string $name Name of the property + * + * @return Builder\Property The created property builder + */ + public function property(string $name): Builder\Property { + return new Builder\Property($name); + } + + /** + * Creates a function builder. + * + * @param string $name Name of the function + * + * @return Builder\Function_ The created function builder + */ + public function function(string $name): Builder\Function_ { + return new Builder\Function_($name); + } + + /** + * Creates a namespace/class use builder. + * + * @param Node\Name|string $name Name of the entity (namespace or class) to alias + * + * @return Builder\Use_ The created use builder + */ + public function use($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_NORMAL); + } + + /** + * Creates a function use builder. + * + * @param Node\Name|string $name Name of the function to alias + * + * @return Builder\Use_ The created use function builder + */ + public function useFunction($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_FUNCTION); + } + + /** + * Creates a constant use builder. + * + * @param Node\Name|string $name Name of the const to alias + * + * @return Builder\Use_ The created use const builder + */ + public function useConst($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_CONSTANT); + } + + /** + * Creates a class constant builder. + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + * + * @return Builder\ClassConst The created use const builder + */ + public function classConst($name, $value): Builder\ClassConst { + return new Builder\ClassConst($name, $value); + } + + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + * + * @return Builder\EnumCase The created use const builder + */ + public function enumCase($name): Builder\EnumCase { + return new Builder\EnumCase($name); + } + + /** + * Creates node a for a literal value. + * + * @param Expr|bool|null|int|float|string|array $value $value + */ + public function val($value): Expr { + return BuilderHelpers::normalizeValue($value); + } + + /** + * Creates variable node. + * + * @param string|Expr $name Name + */ + public function var($name): Expr\Variable { + if (!\is_string($name) && !$name instanceof Expr) { + throw new \LogicException('Variable name must be string or Expr'); + } + + return new Expr\Variable($name); + } + + /** + * Normalizes an argument list. + * + * Creates Arg nodes for all arguments and converts literal values to expressions. + * + * @param array $args List of arguments to normalize + * + * @return list + */ + public function args(array $args): array { + $normalizedArgs = []; + foreach ($args as $key => $arg) { + if (!($arg instanceof Arg)) { + $arg = new Arg(BuilderHelpers::normalizeValue($arg)); + } + if (\is_string($key)) { + $arg->name = BuilderHelpers::normalizeIdentifier($key); + } + $normalizedArgs[] = $arg; + } + return $normalizedArgs; + } + + /** + * Creates a function call node. + * + * @param string|Name|Expr $name Function name + * @param array $args Function arguments + */ + public function funcCall($name, array $args = []): Expr\FuncCall { + return new Expr\FuncCall( + BuilderHelpers::normalizeNameOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a method call node. + * + * @param Expr $var Variable the method is called on + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + */ + public function methodCall(Expr $var, $name, array $args = []): Expr\MethodCall { + return new Expr\MethodCall( + $var, + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a static method call node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + */ + public function staticCall($class, $name, array $args = []): Expr\StaticCall { + return new Expr\StaticCall( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates an object creation node. + * + * @param string|Name|Expr $class Class name + * @param array $args Constructor arguments + */ + public function new($class, array $args = []): Expr\New_ { + return new Expr\New_( + BuilderHelpers::normalizeNameOrExpr($class), + $this->args($args) + ); + } + + /** + * Creates a constant fetch node. + * + * @param string|Name $name Constant name + */ + public function constFetch($name): Expr\ConstFetch { + return new Expr\ConstFetch(BuilderHelpers::normalizeName($name)); + } + + /** + * Creates a property fetch node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name + */ + public function propertyFetch(Expr $var, $name): Expr\PropertyFetch { + return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name)); + } + + /** + * Creates a class constant fetch node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Constant name + */ + public function classConstFetch($class, $name): Expr\ClassConstFetch { + return new Expr\ClassConstFetch( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifierOrExpr($name) + ); + } + + /** + * Creates nested Concat nodes from a list of expressions. + * + * @param Expr|string ...$exprs Expressions or literal strings + */ + public function concat(...$exprs): Concat { + $numExprs = count($exprs); + if ($numExprs < 2) { + throw new \LogicException('Expected at least two expressions'); + } + + $lastConcat = $this->normalizeStringExpr($exprs[0]); + for ($i = 1; $i < $numExprs; $i++) { + $lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i])); + } + return $lastConcat; + } + + /** + * @param string|Expr $expr + */ + private function normalizeStringExpr($expr): Expr { + if ($expr instanceof Expr) { + return $expr; + } + + if (\is_string($expr)) { + return new String_($expr); + } + + throw new \LogicException('Expected string or Expr'); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php new file mode 100644 index 0000000..3e41b26 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php @@ -0,0 +1,333 @@ +getNode(); + } + + if ($node instanceof Node) { + return $node; + } + + throw new \LogicException('Expected node or builder object'); + } + + /** + * Normalizes a node to a statement. + * + * Expressions are wrapped in a Stmt\Expression node. + * + * @param Node|Builder $node The node to normalize + * + * @return Stmt The normalized statement node + */ + public static function normalizeStmt($node): Stmt { + $node = self::normalizeNode($node); + if ($node instanceof Stmt) { + return $node; + } + + if ($node instanceof Expr) { + return new Stmt\Expression($node); + } + + throw new \LogicException('Expected statement or expression node'); + } + + /** + * Normalizes strings to Identifier. + * + * @param string|Identifier $name The identifier to normalize + * + * @return Identifier The normalized identifier + */ + public static function normalizeIdentifier($name): Identifier { + if ($name instanceof Identifier) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier'); + } + + /** + * Normalizes strings to Identifier, also allowing expressions. + * + * @param string|Identifier|Expr $name The identifier to normalize + * + * @return Identifier|Expr The normalized identifier or expression + */ + public static function normalizeIdentifierOrExpr($name) { + if ($name instanceof Identifier || $name instanceof Expr) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr'); + } + + /** + * Normalizes a name: Converts string names to Name nodes. + * + * @param Name|string $name The name to normalize + * + * @return Name The normalized name + */ + public static function normalizeName($name): Name { + if ($name instanceof Name) { + return $name; + } + + if (is_string($name)) { + if (!$name) { + throw new \LogicException('Name cannot be empty'); + } + + if ($name[0] === '\\') { + return new Name\FullyQualified(substr($name, 1)); + } + + if (0 === strpos($name, 'namespace\\')) { + return new Name\Relative(substr($name, strlen('namespace\\'))); + } + + return new Name($name); + } + + throw new \LogicException('Name must be a string or an instance of Node\Name'); + } + + /** + * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. + * + * @param Expr|Name|string $name The name to normalize + * + * @return Name|Expr The normalized name or expression + */ + public static function normalizeNameOrExpr($name) { + if ($name instanceof Expr) { + return $name; + } + + if (!is_string($name) && !($name instanceof Name)) { + throw new \LogicException( + 'Name must be a string or an instance of Node\Name or Node\Expr' + ); + } + + return self::normalizeName($name); + } + + /** + * Normalizes a type: Converts plain-text type names into proper AST representation. + * + * In particular, builtin types become Identifiers, custom types become Names and nullables + * are wrapped in NullableType nodes. + * + * @param string|Name|Identifier|ComplexType $type The type to normalize + * + * @return Name|Identifier|ComplexType The normalized type + */ + public static function normalizeType($type) { + if (!is_string($type)) { + if ( + !$type instanceof Name && !$type instanceof Identifier && + !$type instanceof ComplexType + ) { + throw new \LogicException( + 'Type must be a string, or an instance of Name, Identifier or ComplexType' + ); + } + return $type; + } + + $nullable = false; + if (strlen($type) > 0 && $type[0] === '?') { + $nullable = true; + $type = substr($type, 1); + } + + $builtinTypes = [ + 'array', + 'callable', + 'bool', + 'int', + 'float', + 'string', + 'iterable', + 'void', + 'object', + 'null', + 'false', + 'mixed', + 'never', + 'true', + ]; + + $lowerType = strtolower($type); + if (in_array($lowerType, $builtinTypes)) { + $type = new Identifier($lowerType); + } else { + $type = self::normalizeName($type); + } + + $notNullableTypes = [ + 'void', 'mixed', 'never', + ]; + if ($nullable && in_array((string) $type, $notNullableTypes)) { + throw new \LogicException(sprintf('%s type cannot be nullable', $type)); + } + + return $nullable ? new NullableType($type) : $type; + } + + /** + * Normalizes a value: Converts nulls, booleans, integers, + * floats, strings and arrays into their respective nodes + * + * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize + * + * @return Expr The normalized value + */ + public static function normalizeValue($value): Expr { + if ($value instanceof Node\Expr) { + return $value; + } + + if (is_null($value)) { + return new Expr\ConstFetch( + new Name('null') + ); + } + + if (is_bool($value)) { + return new Expr\ConstFetch( + new Name($value ? 'true' : 'false') + ); + } + + if (is_int($value)) { + return new Scalar\Int_($value); + } + + if (is_float($value)) { + return new Scalar\Float_($value); + } + + if (is_string($value)) { + return new Scalar\String_($value); + } + + if (is_array($value)) { + $items = []; + $lastKey = -1; + foreach ($value as $itemKey => $itemValue) { + // for consecutive, numeric keys don't generate keys + if (null !== $lastKey && ++$lastKey === $itemKey) { + $items[] = new Node\ArrayItem( + self::normalizeValue($itemValue) + ); + } else { + $lastKey = null; + $items[] = new Node\ArrayItem( + self::normalizeValue($itemValue), + self::normalizeValue($itemKey) + ); + } + } + + return new Expr\Array_($items); + } + + throw new \LogicException('Invalid value'); + } + + /** + * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. + * + * @param Comment\Doc|string $docComment The doc comment to normalize + * + * @return Comment\Doc The normalized doc comment + */ + public static function normalizeDocComment($docComment): Comment\Doc { + if ($docComment instanceof Comment\Doc) { + return $docComment; + } + + if (is_string($docComment)) { + return new Comment\Doc($docComment); + } + + throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); + } + + /** + * Normalizes a attribute: Converts attribute to the Attribute Group if needed. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return Node\AttributeGroup The Attribute Group + */ + public static function normalizeAttribute($attribute): Node\AttributeGroup { + if ($attribute instanceof Node\AttributeGroup) { + return $attribute; + } + + if (!($attribute instanceof Node\Attribute)) { + throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup'); + } + + return new Node\AttributeGroup([$attribute]); + } + + /** + * Adds a modifier and returns new modifier bitmask. + * + * @param int $modifiers Existing modifiers + * @param int $modifier Modifier to set + * + * @return int New modifiers + */ + public static function addModifier(int $modifiers, int $modifier): int { + Modifiers::verifyModifier($modifiers, $modifier); + return $modifiers | $modifier; + } + + /** + * Adds a modifier and returns new modifier bitmask. + * @return int New modifiers + */ + public static function addClassModifier(int $existingModifiers, int $modifierToSet): int { + Modifiers::verifyClassModifier($existingModifiers, $modifierToSet); + return $existingModifiers | $modifierToSet; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment.php b/vendor/nikic/php-parser/lib/PhpParser/Comment.php new file mode 100644 index 0000000..01b341e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment.php @@ -0,0 +1,209 @@ +text = $text; + $this->startLine = $startLine; + $this->startFilePos = $startFilePos; + $this->startTokenPos = $startTokenPos; + $this->endLine = $endLine; + $this->endFilePos = $endFilePos; + $this->endTokenPos = $endTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function getText(): string { + return $this->text; + } + + /** + * Gets the line number the comment started on. + * + * @return int Line number (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getStartLine(): int { + return $this->startLine; + } + + /** + * Gets the file offset the comment started on. + * + * @return int File offset (or -1 if not available) + */ + public function getStartFilePos(): int { + return $this->startFilePos; + } + + /** + * Gets the token offset the comment started on. + * + * @return int Token offset (or -1 if not available) + */ + public function getStartTokenPos(): int { + return $this->startTokenPos; + } + + /** + * Gets the line number the comment ends on. + * + * @return int Line number (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getEndLine(): int { + return $this->endLine; + } + + /** + * Gets the file offset the comment ends on. + * + * @return int File offset (or -1 if not available) + */ + public function getEndFilePos(): int { + return $this->endFilePos; + } + + /** + * Gets the token offset the comment ends on. + * + * @return int Token offset (or -1 if not available) + */ + public function getEndTokenPos(): int { + return $this->endTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function __toString(): string { + return $this->text; + } + + /** + * Gets the reformatted comment text. + * + * "Reformatted" here means that we try to clean up the whitespace at the + * starts of the lines. This is necessary because we receive the comments + * without leading whitespace on the first line, but with leading whitespace + * on all subsequent lines. + * + * Additionally, this normalizes CRLF newlines to LF newlines. + */ + public function getReformattedText(): string { + $text = str_replace("\r\n", "\n", $this->text); + $newlinePos = strpos($text, "\n"); + if (false === $newlinePos) { + // Single line comments don't need further processing + return $text; + } + if (preg_match('(^.*(?:\n\s+\*.*)+$)', $text)) { + // Multi line comment of the type + // + // /* + // * Some text. + // * Some more text. + // */ + // + // is handled by replacing the whitespace sequences before the * by a single space + return preg_replace('(^\s+\*)m', ' *', $text); + } + if (preg_match('(^/\*\*?\s*\n)', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) { + // Multi line comment of the type + // + // /* + // Some text. + // Some more text. + // */ + // + // is handled by removing the whitespace sequence on the line before the closing + // */ on all lines. So if the last line is " */", then " " is removed at the + // start of all lines. + return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text); + } + if (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) { + // Multi line comment of the type + // + // /* Some text. + // Some more text. + // Indented text. + // Even more text. */ + // + // is handled by removing the difference between the shortest whitespace prefix on all + // lines and the length of the "/* " opening sequence. + $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1)); + $removeLen = $prefixLen - strlen($matches[0]); + return preg_replace('(^\s{' . $removeLen . '})m', '', $text); + } + + // No idea how to format this comment, so simply return as is + return $text; + } + + /** + * Get length of shortest whitespace prefix (at the start of a line). + * + * If there is a line with no prefix whitespace, 0 is a valid return value. + * + * @param string $str String to check + * @return int Length in characters. Tabs count as single characters. + */ + private function getShortestWhitespacePrefixLen(string $str): int { + $lines = explode("\n", $str); + $shortestPrefixLen = \PHP_INT_MAX; + foreach ($lines as $line) { + preg_match('(^\s*)', $line, $matches); + $prefixLen = strlen($matches[0]); + if ($prefixLen < $shortestPrefixLen) { + $shortestPrefixLen = $prefixLen; + } + } + return $shortestPrefixLen; + } + + /** + * @return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} + */ + public function jsonSerialize(): array { + // Technically not a node, but we make it look like one anyway + $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; + return [ + 'nodeType' => $type, + 'text' => $this->text, + // TODO: Rename these to include "start". + 'line' => $this->startLine, + 'filePos' => $this->startFilePos, + 'tokenPos' => $this->startTokenPos, + 'endLine' => $this->endLine, + 'endFilePos' => $this->endFilePos, + 'endTokenPos' => $this->endTokenPos, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php new file mode 100644 index 0000000..bb3e914 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php @@ -0,0 +1,6 @@ +fallbackEvaluator = $fallbackEvaluator ?? function (Expr $expr) { + throw new ConstExprEvaluationException( + "Expression of type {$expr->getType()} cannot be evaluated" + ); + }; + } + + /** + * Silently evaluates a constant expression into a PHP value. + * + * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException. + * The original source of the exception is available through getPrevious(). + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred + */ + public function evaluateSilently(Expr $expr) { + set_error_handler(function ($num, $str, $file, $line) { + throw new \ErrorException($str, 0, $num, $file, $line); + }); + + try { + return $this->evaluate($expr); + } catch (\Throwable $e) { + if (!$e instanceof ConstExprEvaluationException) { + $e = new ConstExprEvaluationException( + "An error occurred during constant expression evaluation", 0, $e); + } + throw $e; + } finally { + restore_error_handler(); + } + } + + /** + * Directly evaluates a constant expression into a PHP value. + * + * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these + * into a ConstExprEvaluationException. + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated + */ + public function evaluateDirectly(Expr $expr) { + return $this->evaluate($expr); + } + + /** @return mixed */ + private function evaluate(Expr $expr) { + if ($expr instanceof Scalar\Int_ + || $expr instanceof Scalar\Float_ + || $expr instanceof Scalar\String_ + ) { + return $expr->value; + } + + if ($expr instanceof Expr\Array_) { + return $this->evaluateArray($expr); + } + + // Unary operators + if ($expr instanceof Expr\UnaryPlus) { + return +$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\UnaryMinus) { + return -$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BooleanNot) { + return !$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BitwiseNot) { + return ~$this->evaluate($expr->expr); + } + + if ($expr instanceof Expr\BinaryOp) { + return $this->evaluateBinaryOp($expr); + } + + if ($expr instanceof Expr\Ternary) { + return $this->evaluateTernary($expr); + } + + if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { + return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; + } + + if ($expr instanceof Expr\ConstFetch) { + return $this->evaluateConstFetch($expr); + } + + return ($this->fallbackEvaluator)($expr); + } + + private function evaluateArray(Expr\Array_ $expr): array { + $array = []; + foreach ($expr->items as $item) { + if (null !== $item->key) { + $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } elseif ($item->unpack) { + $array = array_merge($array, $this->evaluate($item->value)); + } else { + $array[] = $this->evaluate($item->value); + } + } + return $array; + } + + /** @return mixed */ + private function evaluateTernary(Expr\Ternary $expr) { + if (null === $expr->if) { + return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); + } + + return $this->evaluate($expr->cond) + ? $this->evaluate($expr->if) + : $this->evaluate($expr->else); + } + + /** @return mixed */ + private function evaluateBinaryOp(Expr\BinaryOp $expr) { + if ($expr instanceof Expr\BinaryOp\Coalesce + && $expr->left instanceof Expr\ArrayDimFetch + ) { + // This needs to be special cased to respect BP_VAR_IS fetch semantics + return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] + ?? $this->evaluate($expr->right); + } + + // The evaluate() calls are repeated in each branch, because some of the operators are + // short-circuiting and evaluating the RHS in advance may be illegal in that case + $l = $expr->left; + $r = $expr->right; + switch ($expr->getOperatorSigil()) { + case '&': return $this->evaluate($l) & $this->evaluate($r); + case '|': return $this->evaluate($l) | $this->evaluate($r); + case '^': return $this->evaluate($l) ^ $this->evaluate($r); + case '&&': return $this->evaluate($l) && $this->evaluate($r); + case '||': return $this->evaluate($l) || $this->evaluate($r); + case '??': return $this->evaluate($l) ?? $this->evaluate($r); + case '.': return $this->evaluate($l) . $this->evaluate($r); + case '/': return $this->evaluate($l) / $this->evaluate($r); + case '==': return $this->evaluate($l) == $this->evaluate($r); + case '>': return $this->evaluate($l) > $this->evaluate($r); + case '>=': return $this->evaluate($l) >= $this->evaluate($r); + case '===': return $this->evaluate($l) === $this->evaluate($r); + case 'and': return $this->evaluate($l) and $this->evaluate($r); + case 'or': return $this->evaluate($l) or $this->evaluate($r); + case 'xor': return $this->evaluate($l) xor $this->evaluate($r); + case '-': return $this->evaluate($l) - $this->evaluate($r); + case '%': return $this->evaluate($l) % $this->evaluate($r); + case '*': return $this->evaluate($l) * $this->evaluate($r); + case '!=': return $this->evaluate($l) != $this->evaluate($r); + case '!==': return $this->evaluate($l) !== $this->evaluate($r); + case '+': return $this->evaluate($l) + $this->evaluate($r); + case '**': return $this->evaluate($l) ** $this->evaluate($r); + case '<<': return $this->evaluate($l) << $this->evaluate($r); + case '>>': return $this->evaluate($l) >> $this->evaluate($r); + case '<': return $this->evaluate($l) < $this->evaluate($r); + case '<=': return $this->evaluate($l) <= $this->evaluate($r); + case '<=>': return $this->evaluate($l) <=> $this->evaluate($r); + } + + throw new \Exception('Should not happen'); + } + + /** @return mixed */ + private function evaluateConstFetch(Expr\ConstFetch $expr) { + $name = $expr->name->toLowerString(); + switch ($name) { + case 'null': return null; + case 'false': return false; + case 'true': return true; + } + + return ($this->fallbackEvaluator)($expr); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Error.php new file mode 100644 index 0000000..f81f0c4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Error.php @@ -0,0 +1,173 @@ + */ + protected array $attributes; + + /** + * Creates an Exception signifying a parse error. + * + * @param string $message Error message + * @param array $attributes Attributes of node/token where error occurred + */ + public function __construct(string $message, array $attributes = []) { + $this->rawMessage = $message; + $this->attributes = $attributes; + $this->updateMessage(); + } + + /** + * Gets the error message + * + * @return string Error message + */ + public function getRawMessage(): string { + return $this->rawMessage; + } + + /** + * Gets the line the error starts in. + * + * @return int Error start line + * @phpstan-return -1|positive-int + */ + public function getStartLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the error ends in. + * + * @return int Error end line + * @phpstan-return -1|positive-int + */ + public function getEndLine(): int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the attributes of the node/token the error occurred at. + * + * @return array + */ + public function getAttributes(): array { + return $this->attributes; + } + + /** + * Sets the attributes of the node/token the error occurred at. + * + * @param array $attributes + */ + public function setAttributes(array $attributes): void { + $this->attributes = $attributes; + $this->updateMessage(); + } + + /** + * Sets the line of the PHP file the error occurred in. + * + * @param string $message Error message + */ + public function setRawMessage(string $message): void { + $this->rawMessage = $message; + $this->updateMessage(); + } + + /** + * Sets the line the error starts in. + * + * @param int $line Error start line + */ + public function setStartLine(int $line): void { + $this->attributes['startLine'] = $line; + $this->updateMessage(); + } + + /** + * Returns whether the error has start and end column information. + * + * For column information enable the startFilePos and endFilePos in the lexer options. + */ + public function hasColumnInfo(): bool { + return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); + } + + /** + * Gets the start column (1-based) into the line where the error started. + * + * @param string $code Source code of the file + */ + public function getStartColumn(string $code): int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['startFilePos']); + } + + /** + * Gets the end column (1-based) into the line where the error ended. + * + * @param string $code Source code of the file + */ + public function getEndColumn(string $code): int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['endFilePos']); + } + + /** + * Formats message including line and column information. + * + * @param string $code Source code associated with the error, for calculation of the columns + * + * @return string Formatted message + */ + public function getMessageWithColumnInfo(string $code): string { + return sprintf( + '%s from %d:%d to %d:%d', $this->getRawMessage(), + $this->getStartLine(), $this->getStartColumn($code), + $this->getEndLine(), $this->getEndColumn($code) + ); + } + + /** + * Converts a file offset into a column. + * + * @param string $code Source code that $pos indexes into + * @param int $pos 0-based position in $code + * + * @return int 1-based column (relative to start of line) + */ + private function toColumn(string $code, int $pos): int { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } + + /** + * Updates the exception message after a change to rawMessage or rawLine. + */ + protected function updateMessage(): void { + $this->message = $this->rawMessage; + + if (-1 === $this->getStartLine()) { + $this->message .= ' on unknown line'; + } else { + $this->message .= ' on line ' . $this->getStartLine(); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php new file mode 100644 index 0000000..51ad730 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php @@ -0,0 +1,12 @@ +errors[] = $error; + } + + /** + * Get collected errors. + * + * @return Error[] + */ + public function getErrors(): array { + return $this->errors; + } + + /** + * Check whether there are any errors. + */ + public function hasErrors(): bool { + return !empty($this->errors); + } + + /** + * Reset/clear collected errors. + */ + public function clearErrors(): void { + $this->errors = []; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php new file mode 100644 index 0000000..dff33dd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php @@ -0,0 +1,17 @@ +type = $type; + $this->old = $old; + $this->new = $new; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php new file mode 100644 index 0000000..253e175 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php @@ -0,0 +1,178 @@ +isEqual = $isEqual; + } + + /** + * Calculate diff (edit script) from $old to $new. + * + * @param T[] $old Original array + * @param T[] $new New array + * + * @return DiffElem[] Diff (edit script) + */ + public function diff(array $old, array $new): array { + $old = \array_values($old); + $new = \array_values($new); + list($trace, $x, $y) = $this->calculateTrace($old, $new); + return $this->extractDiff($trace, $x, $y, $old, $new); + } + + /** + * Calculate diff, including "replace" operations. + * + * If a sequence of remove operations is followed by the same number of add operations, these + * will be coalesced into replace operations. + * + * @param T[] $old Original array + * @param T[] $new New array + * + * @return DiffElem[] Diff (edit script), including replace operations + */ + public function diffWithReplacements(array $old, array $new): array { + return $this->coalesceReplacements($this->diff($old, $new)); + } + + /** + * @param T[] $old + * @param T[] $new + * @return array{array>, int, int} + */ + private function calculateTrace(array $old, array $new): array { + $n = \count($old); + $m = \count($new); + $max = $n + $m; + $v = [1 => 0]; + $trace = []; + for ($d = 0; $d <= $max; $d++) { + $trace[] = $v; + for ($k = -$d; $k <= $d; $k += 2) { + if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { + $x = $v[$k + 1]; + } else { + $x = $v[$k - 1] + 1; + } + + $y = $x - $k; + while ($x < $n && $y < $m && ($this->isEqual)($old[$x], $new[$y])) { + $x++; + $y++; + } + + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y]; + } + } + } + throw new \Exception('Should not happen'); + } + + /** + * @param array> $trace + * @param T[] $old + * @param T[] $new + * @return DiffElem[] + */ + private function extractDiff(array $trace, int $x, int $y, array $old, array $new): array { + $result = []; + for ($d = \count($trace) - 1; $d >= 0; $d--) { + $v = $trace[$d]; + $k = $x - $y; + + if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { + $prevK = $k + 1; + } else { + $prevK = $k - 1; + } + + $prevX = $v[$prevK]; + $prevY = $prevX - $prevK; + + while ($x > $prevX && $y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_KEEP, $old[$x - 1], $new[$y - 1]); + $x--; + $y--; + } + + if ($d === 0) { + break; + } + + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $old[$x - 1], null); + $x--; + } + + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $new[$y - 1]); + $y--; + } + } + return array_reverse($result); + } + + /** + * Coalesce equal-length sequences of remove+add into a replace operation. + * + * @param DiffElem[] $diff + * @return DiffElem[] + */ + private function coalesceReplacements(array $diff): array { + $newDiff = []; + $c = \count($diff); + for ($i = 0; $i < $c; $i++) { + $diffType = $diff[$i]->type; + if ($diffType !== DiffElem::TYPE_REMOVE) { + $newDiff[] = $diff[$i]; + continue; + } + + $j = $i; + while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { + $j++; + } + + $k = $j; + while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { + $k++; + } + + if ($j - $i === $k - $j) { + $len = $j - $i; + for ($n = 0; $n < $len; $n++) { + $newDiff[] = new DiffElem( + DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new + ); + } + } else { + for (; $i < $k; $i++) { + $newDiff[] = $diff[$i]; + } + } + $i = $k - 1; + } + return $newDiff; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php new file mode 100644 index 0000000..b30a99a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php @@ -0,0 +1,71 @@ + $attributes Attributes + */ + public function __construct( + array $attrGroups, int $flags, array $args, ?Node\Name $extends, array $implements, + array $stmts, array $attributes + ) { + parent::__construct($attributes); + $this->attrGroups = $attrGroups; + $this->flags = $flags; + $this->args = $args; + $this->extends = $extends; + $this->implements = $implements; + $this->stmts = $stmts; + } + + public static function fromNewNode(Expr\New_ $newNode): self { + $class = $newNode->class; + assert($class instanceof Node\Stmt\Class_); + // We don't assert that $class->name is null here, to allow consumers to assign unique names + // to anonymous classes for their own purposes. We simplify ignore the name here. + return new self( + $class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements, + $class->stmts, $newNode->getAttributes() + ); + } + + public function getType(): string { + return 'Expr_PrintableNewAnonClass'; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php new file mode 100644 index 0000000..36022d0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php @@ -0,0 +1,237 @@ += 80000) { + class TokenPolyfill extends \PhpToken { + } + return; +} + +/** + * This is a polyfill for the PhpToken class introduced in PHP 8.0. We do not actually polyfill + * PhpToken, because composer might end up picking a different polyfill implementation, which does + * not meet our requirements. + * + * @internal + */ +class TokenPolyfill { + /** @var int The ID of the token. Either a T_* constant of a character code < 256. */ + public int $id; + /** @var string The textual content of the token. */ + public string $text; + /** @var int The 1-based starting line of the token (or -1 if unknown). */ + public int $line; + /** @var int The 0-based starting position of the token (or -1 if unknown). */ + public int $pos; + + /** @var array Tokens ignored by the PHP parser. */ + private const IGNORABLE_TOKENS = [ + \T_WHITESPACE => true, + \T_COMMENT => true, + \T_DOC_COMMENT => true, + \T_OPEN_TAG => true, + ]; + + /** @var array Tokens that may be part of a T_NAME_* identifier. */ + private static array $identifierTokens; + + /** + * Create a Token with the given ID and text, as well optional line and position information. + */ + final public function __construct(int $id, string $text, int $line = -1, int $pos = -1) { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $pos; + } + + /** + * Get the name of the token. For single-char tokens this will be the token character. + * Otherwise it will be a T_* style name, or null if the token ID is unknown. + */ + public function getTokenName(): ?string { + if ($this->id < 256) { + return \chr($this->id); + } + + $name = token_name($this->id); + return $name === 'UNKNOWN' ? null : $name; + } + + /** + * Check whether the token is of the given kind. The kind may be either an integer that matches + * the token ID, a string that matches the token text, or an array of integers/strings. In the + * latter case, the function returns true if any of the kinds in the array match. + * + * @param int|string|(int|string)[] $kind + */ + public function is($kind): bool { + if (\is_int($kind)) { + return $this->id === $kind; + } + if (\is_string($kind)) { + return $this->text === $kind; + } + if (\is_array($kind)) { + foreach ($kind as $entry) { + if (\is_int($entry)) { + if ($this->id === $entry) { + return true; + } + } elseif (\is_string($entry)) { + if ($this->text === $entry) { + return true; + } + } else { + throw new \TypeError( + 'Argument #1 ($kind) must only have elements of type string|int, ' . + gettype($entry) . ' given'); + } + } + return false; + } + throw new \TypeError( + 'Argument #1 ($kind) must be of type string|int|array, ' .gettype($kind) . ' given'); + } + + /** + * Check whether this token would be ignored by the PHP parser. Returns true for T_WHITESPACE, + * T_COMMENT, T_DOC_COMMENT and T_OPEN_TAG, and false for everything else. + */ + public function isIgnorable(): bool { + return isset(self::IGNORABLE_TOKENS[$this->id]); + } + + /** + * Return the textual content of the token. + */ + public function __toString(): string { + return $this->text; + } + + /** + * Tokenize the given source code and return an array of tokens. + * + * This performs certain canonicalizations to match the PHP 8.0 token format: + * * Bad characters are represented using T_BAD_CHARACTER rather than omitted. + * * T_COMMENT does not include trailing newlines, instead the newline is part of a following + * T_WHITESPACE token. + * * Namespaced names are represented using T_NAME_* tokens. + * + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array { + self::init(); + + $tokens = []; + $line = 1; + $pos = 0; + $origTokens = \token_get_all($code, $flags); + + $numTokens = \count($origTokens); + for ($i = 0; $i < $numTokens; $i++) { + $token = $origTokens[$i]; + if (\is_string($token)) { + if (\strlen($token) === 2) { + // b" and B" are tokenized as single-char tokens, even though they aren't. + $tokens[] = new static(\ord('"'), $token, $line, $pos); + $pos += 2; + } else { + $tokens[] = new static(\ord($token), $token, $line, $pos); + $pos++; + } + } else { + $id = $token[0]; + $text = $token[1]; + + // Emulate PHP 8.0 comment format, which does not include trailing whitespace anymore. + if ($id === \T_COMMENT && \substr($text, 0, 2) !== '/*' && + \preg_match('/(\r\n|\n|\r)$/D', $text, $matches) + ) { + $trailingNewline = $matches[0]; + $text = \substr($text, 0, -\strlen($trailingNewline)); + $tokens[] = new static($id, $text, $line, $pos); + $pos += \strlen($text); + + if ($i + 1 < $numTokens && $origTokens[$i + 1][0] === \T_WHITESPACE) { + // Move trailing newline into following T_WHITESPACE token, if it already exists. + $origTokens[$i + 1][1] = $trailingNewline . $origTokens[$i + 1][1]; + $origTokens[$i + 1][2]--; + } else { + // Otherwise, we need to create a new T_WHITESPACE token. + $tokens[] = new static(\T_WHITESPACE, $trailingNewline, $line, $pos); + $line++; + $pos += \strlen($trailingNewline); + } + continue; + } + + // Emulate PHP 8.0 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and + // T_STRING into a single token. + if (($id === \T_NS_SEPARATOR || isset(self::$identifierTokens[$id]))) { + $newText = $text; + $lastWasSeparator = $id === \T_NS_SEPARATOR; + for ($j = $i + 1; $j < $numTokens; $j++) { + if ($lastWasSeparator) { + if (!isset(self::$identifierTokens[$origTokens[$j][0]])) { + break; + } + $lastWasSeparator = false; + } else { + if ($origTokens[$j][0] !== \T_NS_SEPARATOR) { + break; + } + $lastWasSeparator = true; + } + $newText .= $origTokens[$j][1]; + } + if ($lastWasSeparator) { + // Trailing separator is not part of the name. + $j--; + $newText = \substr($newText, 0, -1); + } + if ($j > $i + 1) { + if ($id === \T_NS_SEPARATOR) { + $id = \T_NAME_FULLY_QUALIFIED; + } elseif ($id === \T_NAMESPACE) { + $id = \T_NAME_RELATIVE; + } else { + $id = \T_NAME_QUALIFIED; + } + $tokens[] = new static($id, $newText, $line, $pos); + $pos += \strlen($newText); + $i = $j - 1; + continue; + } + } + + $tokens[] = new static($id, $text, $line, $pos); + $line += \substr_count($text, "\n"); + $pos += \strlen($text); + } + } + return $tokens; + } + + /** Initialize private static state needed by tokenize(). */ + private static function init(): void { + if (isset(self::$identifierTokens)) { + return; + } + + // Based on semi_reserved production. + self::$identifierTokens = \array_fill_keys([ + \T_STRING, + \T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY, + \T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND, + \T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE, + \T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH, + \T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO, + \T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT, + \T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS, + \T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN, + \T_MATCH, + ], true); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php new file mode 100644 index 0000000..c02844a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php @@ -0,0 +1,275 @@ +tokens = $tokens; + $this->indentMap = $this->calcIndentMap(); + } + + /** + * Whether the given position is immediately surrounded by parenthesis. + * + * @param int $startPos Start position + * @param int $endPos End position + */ + public function haveParens(int $startPos, int $endPos): bool { + return $this->haveTokenImmediatelyBefore($startPos, '(') + && $this->haveTokenImmediatelyAfter($endPos, ')'); + } + + /** + * Whether the given position is immediately surrounded by braces. + * + * @param int $startPos Start position + * @param int $endPos End position + */ + public function haveBraces(int $startPos, int $endPos): bool { + return ($this->haveTokenImmediatelyBefore($startPos, '{') + || $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN)) + && $this->haveTokenImmediatelyAfter($endPos, '}'); + } + + /** + * Check whether the position is directly preceded by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position before which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType): bool { + $tokens = $this->tokens; + $pos--; + for (; $pos >= 0; $pos--) { + $token = $tokens[$pos]; + if ($token->is($expectedTokenType)) { + return true; + } + if (!$token->isIgnorable()) { + break; + } + } + return false; + } + + /** + * Check whether the position is directly followed by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position after which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType): bool { + $tokens = $this->tokens; + $pos++; + for ($c = \count($tokens); $pos < $c; $pos++) { + $token = $tokens[$pos]; + if ($token->is($expectedTokenType)) { + return true; + } + if (!$token->isIgnorable()) { + break; + } + } + return false; + } + + /** @param int|string|(int|string)[] $skipTokenType */ + public function skipLeft(int $pos, $skipTokenType): int { + $tokens = $this->tokens; + + $pos = $this->skipLeftWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if (!$tokens[$pos]->is($skipTokenType)) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos--; + + return $this->skipLeftWhitespace($pos); + } + + /** @param int|string|(int|string)[] $skipTokenType */ + public function skipRight(int $pos, $skipTokenType): int { + $tokens = $this->tokens; + + $pos = $this->skipRightWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if (!$tokens[$pos]->is($skipTokenType)) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos++; + + return $this->skipRightWhitespace($pos); + } + + /** + * Return first non-whitespace token position smaller or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipLeftWhitespace(int $pos): int { + $tokens = $this->tokens; + for (; $pos >= 0; $pos--) { + if (!$tokens[$pos]->isIgnorable()) { + break; + } + } + return $pos; + } + + /** + * Return first non-whitespace position greater or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipRightWhitespace(int $pos): int { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + if (!$tokens[$pos]->isIgnorable()) { + break; + } + } + return $pos; + } + + /** @param int|string|(int|string)[] $findTokenType */ + public function findRight(int $pos, $findTokenType): int { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + if ($tokens[$pos]->is($findTokenType)) { + return $pos; + } + } + return -1; + } + + /** + * Whether the given position range contains a certain token type. + * + * @param int $startPos Starting position (inclusive) + * @param int $endPos Ending position (exclusive) + * @param int|string $tokenType Token type to look for + * @return bool Whether the token occurs in the given range + */ + public function haveTokenInRange(int $startPos, int $endPos, $tokenType): bool { + $tokens = $this->tokens; + for ($pos = $startPos; $pos < $endPos; $pos++) { + if ($tokens[$pos]->is($tokenType)) { + return true; + } + } + return false; + } + + public function haveTagInRange(int $startPos, int $endPos): bool { + return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG) + || $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG); + } + + /** + * Get indentation before token position. + * + * @param int $pos Token position + * + * @return int Indentation depth (in spaces) + */ + public function getIndentationBefore(int $pos): int { + return $this->indentMap[$pos]; + } + + /** + * Get the code corresponding to a token offset range, optionally adjusted for indentation. + * + * @param int $from Token start position (inclusive) + * @param int $to Token end position (exclusive) + * @param int $indent By how much the code should be indented (can be negative as well) + * + * @return string Code corresponding to token range, adjusted for indentation + */ + public function getTokenCode(int $from, int $to, int $indent): string { + $tokens = $this->tokens; + $result = ''; + for ($pos = $from; $pos < $to; $pos++) { + $token = $tokens[$pos]; + $id = $token->id; + $text = $token->text; + if ($id === \T_CONSTANT_ENCAPSED_STRING || $id === \T_ENCAPSED_AND_WHITESPACE) { + $result .= $text; + } else { + // TODO Handle non-space indentation + if ($indent < 0) { + $result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $text); + } elseif ($indent > 0) { + $result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $text); + } else { + $result .= $text; + } + } + } + return $result; + } + + /** + * Precalculate the indentation at every token position. + * + * @return int[] Token position to indentation map + */ + private function calcIndentMap(): array { + $indentMap = []; + $indent = 0; + foreach ($this->tokens as $i => $token) { + $indentMap[] = $indent; + + if ($token->id === \T_WHITESPACE) { + $content = $token->text; + $newlinePos = \strrpos($content, "\n"); + if (false !== $newlinePos) { + $indent = \strlen($content) - $newlinePos - 1; + } elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG && + $this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") { + // Special case: Newline at the end of opening tag followed by whitespace. + $indent = \strlen($content); + } + } + } + + // Add a sentinel for one past end of the file + $indentMap[] = $indent; + + return $indentMap; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php new file mode 100644 index 0000000..7be4142 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php @@ -0,0 +1,108 @@ +[] Node type to reflection class map */ + private array $reflectionClassCache; + + /** @return mixed */ + public function decode(string $json) { + $value = json_decode($json, true); + if (json_last_error()) { + throw new \RuntimeException('JSON decoding error: ' . json_last_error_msg()); + } + + return $this->decodeRecursive($value); + } + + /** + * @param mixed $value + * @return mixed + */ + private function decodeRecursive($value) { + if (\is_array($value)) { + if (isset($value['nodeType'])) { + if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') { + return $this->decodeComment($value); + } + return $this->decodeNode($value); + } + return $this->decodeArray($value); + } + return $value; + } + + private function decodeArray(array $array): array { + $decodedArray = []; + foreach ($array as $key => $value) { + $decodedArray[$key] = $this->decodeRecursive($value); + } + return $decodedArray; + } + + private function decodeNode(array $value): Node { + $nodeType = $value['nodeType']; + if (!\is_string($nodeType)) { + throw new \RuntimeException('Node type must be a string'); + } + + $reflectionClass = $this->reflectionClassFromNodeType($nodeType); + $node = $reflectionClass->newInstanceWithoutConstructor(); + + if (isset($value['attributes'])) { + if (!\is_array($value['attributes'])) { + throw new \RuntimeException('Attributes must be an array'); + } + + $node->setAttributes($this->decodeArray($value['attributes'])); + } + + foreach ($value as $name => $subNode) { + if ($name === 'nodeType' || $name === 'attributes') { + continue; + } + + $node->$name = $this->decodeRecursive($subNode); + } + + return $node; + } + + private function decodeComment(array $value): Comment { + $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class; + if (!isset($value['text'])) { + throw new \RuntimeException('Comment must have text'); + } + + return new $className( + $value['text'], + $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1, + $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1 + ); + } + + /** @return \ReflectionClass */ + private function reflectionClassFromNodeType(string $nodeType): \ReflectionClass { + if (!isset($this->reflectionClassCache[$nodeType])) { + $className = $this->classNameFromNodeType($nodeType); + $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className); + } + return $this->reflectionClassCache[$nodeType]; + } + + /** @return class-string */ + private function classNameFromNodeType(string $nodeType): string { + $className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\'); + if (class_exists($className)) { + return $className; + } + + $className .= '_'; + if (class_exists($className)) { + return $className; + } + + throw new \RuntimeException("Unknown node type \"$nodeType\""); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php new file mode 100644 index 0000000..5e2ece9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php @@ -0,0 +1,116 @@ +postprocessTokens($tokens, $errorHandler); + + if (false !== $scream) { + ini_set('xdebug.scream', $scream); + } + + return $tokens; + } + + private function handleInvalidCharacter(Token $token, ErrorHandler $errorHandler): void { + $chr = $token->text; + if ($chr === "\0") { + // PHP cuts error message after null byte, so need special case + $errorMsg = 'Unexpected null byte'; + } else { + $errorMsg = sprintf( + 'Unexpected character "%s" (ASCII %d)', $chr, ord($chr) + ); + } + + $errorHandler->handleError(new Error($errorMsg, [ + 'startLine' => $token->line, + 'endLine' => $token->line, + 'startFilePos' => $token->pos, + 'endFilePos' => $token->pos, + ])); + } + + private function isUnterminatedComment(Token $token): bool { + return $token->is([\T_COMMENT, \T_DOC_COMMENT]) + && substr($token->text, 0, 2) === '/*' + && substr($token->text, -2) !== '*/'; + } + + /** + * @param list $tokens + */ + protected function postprocessTokens(array &$tokens, ErrorHandler $errorHandler): void { + // This function reports errors (bad characters and unterminated comments) in the token + // array, and performs certain canonicalizations: + // * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and + // T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types. + // * Add a sentinel token with ID 0. + + $numTokens = \count($tokens); + if ($numTokens === 0) { + // Empty input edge case: Just add the sentinel token. + $tokens[] = new Token(0, "\0", 1, 0); + return; + } + + for ($i = 0; $i < $numTokens; $i++) { + $token = $tokens[$i]; + if ($token->id === \T_BAD_CHARACTER) { + $this->handleInvalidCharacter($token, $errorHandler); + } + + if ($token->id === \ord('&')) { + $next = $i + 1; + while (isset($tokens[$next]) && $tokens[$next]->id === \T_WHITESPACE) { + $next++; + } + $followedByVarOrVarArg = isset($tokens[$next]) && + $tokens[$next]->is([\T_VARIABLE, \T_ELLIPSIS]); + $token->id = $followedByVarOrVarArg + ? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + : \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; + } + } + + // Check for unterminated comment + $lastToken = $tokens[$numTokens - 1]; + if ($this->isUnterminatedComment($lastToken)) { + $errorHandler->handleError(new Error('Unterminated comment', [ + 'startLine' => $lastToken->line, + 'endLine' => $lastToken->getEndLine(), + 'startFilePos' => $lastToken->pos, + 'endFilePos' => $lastToken->getEndPos(), + ])); + } + + // Add sentinel token. + $tokens[] = new Token(0, "\0", $lastToken->getEndLine(), $lastToken->getEndPos()); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php new file mode 100644 index 0000000..934954c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php @@ -0,0 +1,226 @@ + */ + private array $emulators = []; + + private PhpVersion $targetPhpVersion; + + private PhpVersion $hostPhpVersion; + + /** + * @param PhpVersion|null $phpVersion PHP version to emulate. Defaults to newest supported. + */ + public function __construct(?PhpVersion $phpVersion = null) { + $this->targetPhpVersion = $phpVersion ?? PhpVersion::getNewestSupported(); + $this->hostPhpVersion = PhpVersion::getHostVersion(); + + $emulators = [ + new MatchTokenEmulator(), + new NullsafeTokenEmulator(), + new AttributeEmulator(), + new EnumTokenEmulator(), + new ReadonlyTokenEmulator(), + new ExplicitOctalEmulator(), + new ReadonlyFunctionTokenEmulator(), + ]; + + // Collect emulators that are relevant for the PHP version we're running + // and the PHP version we're targeting for emulation. + foreach ($emulators as $emulator) { + $emulatorPhpVersion = $emulator->getPhpVersion(); + if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = $emulator; + } elseif ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = new ReverseEmulator($emulator); + } + } + } + + public function tokenize(string $code, ?ErrorHandler $errorHandler = null): array { + $emulators = array_filter($this->emulators, function ($emulator) use ($code) { + return $emulator->isEmulationNeeded($code); + }); + + if (empty($emulators)) { + // Nothing to emulate, yay + return parent::tokenize($code, $errorHandler); + } + + if ($errorHandler === null) { + $errorHandler = new ErrorHandler\Throwing(); + } + + $this->patches = []; + foreach ($emulators as $emulator) { + $code = $emulator->preprocessCode($code, $this->patches); + } + + $collector = new ErrorHandler\Collecting(); + $tokens = parent::tokenize($code, $collector); + $this->sortPatches(); + $tokens = $this->fixupTokens($tokens); + + $errors = $collector->getErrors(); + if (!empty($errors)) { + $this->fixupErrors($errors); + foreach ($errors as $error) { + $errorHandler->handleError($error); + } + } + + foreach ($emulators as $emulator) { + $tokens = $emulator->emulate($code, $tokens); + } + + return $tokens; + } + + private function isForwardEmulationNeeded(PhpVersion $emulatorPhpVersion): bool { + return $this->hostPhpVersion->older($emulatorPhpVersion) + && $this->targetPhpVersion->newerOrEqual($emulatorPhpVersion); + } + + private function isReverseEmulationNeeded(PhpVersion $emulatorPhpVersion): bool { + return $this->hostPhpVersion->newerOrEqual($emulatorPhpVersion) + && $this->targetPhpVersion->older($emulatorPhpVersion); + } + + private function sortPatches(): void { + // Patches may be contributed by different emulators. + // Make sure they are sorted by increasing patch position. + usort($this->patches, function ($p1, $p2) { + return $p1[0] <=> $p2[0]; + }); + } + + /** + * @param list $tokens + * @return list + */ + private function fixupTokens(array $tokens): array { + if (\count($this->patches) === 0) { + return $tokens; + } + + // Load first patch + $patchIdx = 0; + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + + // We use a manual loop over the tokens, because we modify the array on the fly + $posDelta = 0; + $lineDelta = 0; + for ($i = 0, $c = \count($tokens); $i < $c; $i++) { + $token = $tokens[$i]; + $pos = $token->pos; + $token->pos += $posDelta; + $token->line += $lineDelta; + $localPosDelta = 0; + $len = \strlen($token->text); + while ($patchPos >= $pos && $patchPos < $pos + $len) { + $patchTextLen = \strlen($patchText); + if ($patchType === 'remove') { + if ($patchPos === $pos && $patchTextLen === $len) { + // Remove token entirely + array_splice($tokens, $i, 1, []); + $i--; + $c--; + } else { + // Remove from token string + $token->text = substr_replace( + $token->text, '', $patchPos - $pos + $localPosDelta, $patchTextLen + ); + $localPosDelta -= $patchTextLen; + } + $lineDelta -= \substr_count($patchText, "\n"); + } elseif ($patchType === 'add') { + // Insert into the token string + $token->text = substr_replace( + $token->text, $patchText, $patchPos - $pos + $localPosDelta, 0 + ); + $localPosDelta += $patchTextLen; + $lineDelta += \substr_count($patchText, "\n"); + } elseif ($patchType === 'replace') { + // Replace inside the token string + $token->text = substr_replace( + $token->text, $patchText, $patchPos - $pos + $localPosDelta, $patchTextLen + ); + } else { + assert(false); + } + + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches. However, we still need to adjust position. + $patchPos = \PHP_INT_MAX; + break; + } + + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + } + + $posDelta += $localPosDelta; + } + return $tokens; + } + + /** + * Fixup line and position information in errors. + * + * @param Error[] $errors + */ + private function fixupErrors(array $errors): void { + foreach ($errors as $error) { + $attrs = $error->getAttributes(); + + $posDelta = 0; + $lineDelta = 0; + foreach ($this->patches as $patch) { + list($patchPos, $patchType, $patchText) = $patch; + if ($patchPos >= $attrs['startFilePos']) { + // No longer relevant + break; + } + + if ($patchType === 'add') { + $posDelta += strlen($patchText); + $lineDelta += substr_count($patchText, "\n"); + } elseif ($patchType === 'remove') { + $posDelta -= strlen($patchText); + $lineDelta -= substr_count($patchText, "\n"); + } + } + + $attrs['startFilePos'] += $posDelta; + $attrs['endFilePos'] += $posDelta; + $attrs['startLine'] += $lineDelta; + $attrs['endLine'] += $lineDelta; + $error->setAttributes($attrs); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php new file mode 100644 index 0000000..2c12f33 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php @@ -0,0 +1,49 @@ +text === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1]->text === '[') { + array_splice($tokens, $i, 2, [ + new Token(\T_ATTRIBUTE, '#[', $token->line, $token->pos), + ]); + $c--; + continue; + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + // TODO + return $tokens; + } + + public function preprocessCode(string $code, array &$patches): string { + $pos = 0; + while (false !== $pos = strpos($code, '#[', $pos)) { + // Replace #[ with %[ + $code[$pos] = '%'; + $patches[] = [$pos, 'replace', '#']; + $pos += 2; + } + return $code; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php new file mode 100644 index 0000000..5418f52 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php @@ -0,0 +1,26 @@ +id === \T_WHITESPACE + && $tokens[$pos + 2]->id === \T_STRING; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php new file mode 100644 index 0000000..9cadf42 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php @@ -0,0 +1,45 @@ +id == \T_LNUMBER && $token->text === '0' && + isset($tokens[$i + 1]) && $tokens[$i + 1]->id == \T_STRING && + preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1]->text) + ) { + $tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1]->text); + array_splice($tokens, $i, 2, [ + new Token($tokenKind, '0' . $tokens[$i + 1]->text, $token->line, $token->pos), + ]); + $c--; + } + } + return $tokens; + } + + private function resolveIntegerOrFloatToken(string $str): int { + $str = substr($str, 1); + $str = str_replace('_', '', $str); + $num = octdec($str); + return is_float($num) ? \T_DNUMBER : \T_LNUMBER; + } + + public function reverseEmulate(string $code, array $tokens): array { + // Explicit octals were not legal code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php new file mode 100644 index 0000000..9803f99 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php @@ -0,0 +1,56 @@ +getKeywordString()) !== false; + } + + /** @param Token[] $tokens */ + protected function isKeywordContext(array $tokens, int $pos): bool { + $previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos); + return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR; + } + + public function emulate(string $code, array $tokens): array { + $keywordString = $this->getKeywordString(); + foreach ($tokens as $i => $token) { + if ($token->id === T_STRING && strtolower($token->text) === $keywordString + && $this->isKeywordContext($tokens, $i)) { + $token->id = $this->getKeywordToken(); + } + } + + return $tokens; + } + + /** @param Token[] $tokens */ + private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token { + for ($i = $start - 1; $i >= 0; --$i) { + if ($tokens[$i]->id === T_WHITESPACE) { + continue; + } + + return $tokens[$i]; + } + + return null; + } + + public function reverseEmulate(string $code, array $tokens): array { + $keywordToken = $this->getKeywordToken(); + foreach ($tokens as $token) { + if ($token->id === $keywordToken) { + $token->id = \T_STRING; + } + } + + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php new file mode 100644 index 0000000..0fa5fbc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php @@ -0,0 +1,19 @@ +') !== false; + } + + public function emulate(string $code, array $tokens): array { + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if ($token->text === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1]->id === \T_OBJECT_OPERATOR) { + array_splice($tokens, $i, 2, [ + new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), + ]); + $c--; + continue; + } + + // Handle ?-> inside encapsed string. + if ($token->id === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) + && $tokens[$i - 1]->id === \T_VARIABLE + && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $token->text, $matches) + ) { + $replacement = [ + new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), + new Token(\T_STRING, $matches[1], $token->line, $token->pos + 3), + ]; + $matchLen = \strlen($matches[0]); + if ($matchLen !== \strlen($token->text)) { + $replacement[] = new Token( + \T_ENCAPSED_AND_WHITESPACE, + \substr($token->text, $matchLen), + $token->line, $token->pos + $matchLen + ); + } + array_splice($tokens, $i, 1, $replacement); + $c += \count($replacement) - 1; + continue; + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + // ?-> was not valid code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyFunctionTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyFunctionTokenEmulator.php new file mode 100644 index 0000000..5990d7f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyFunctionTokenEmulator.php @@ -0,0 +1,31 @@ +text === '(' || + ($tokens[$pos + 1]->id === \T_WHITESPACE && + isset($tokens[$pos + 2]) && + $tokens[$pos + 2]->text === '('))); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php new file mode 100644 index 0000000..851b5c4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php @@ -0,0 +1,37 @@ +emulator = $emulator; + } + + public function getPhpVersion(): PhpVersion { + return $this->emulator->getPhpVersion(); + } + + public function isEmulationNeeded(string $code): bool { + return $this->emulator->isEmulationNeeded($code); + } + + public function emulate(string $code, array $tokens): array { + return $this->emulator->reverseEmulate($code, $tokens); + } + + public function reverseEmulate(string $code, array $tokens): array { + return $this->emulator->emulate($code, $tokens); + } + + public function preprocessCode(string $code, array &$patches): string { + return $code; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php new file mode 100644 index 0000000..fec2f19 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php @@ -0,0 +1,30 @@ + [aliasName => originalName]] */ + protected array $aliases = []; + + /** @var Name[][] Same as $aliases but preserving original case */ + protected array $origAliases = []; + + /** @var ErrorHandler Error handler */ + protected ErrorHandler $errorHandler; + + /** + * Create a name context. + * + * @param ErrorHandler $errorHandler Error handling used to report errors + */ + public function __construct(ErrorHandler $errorHandler) { + $this->errorHandler = $errorHandler; + } + + /** + * Start a new namespace. + * + * This also resets the alias table. + * + * @param Name|null $namespace Null is the global namespace + */ + public function startNamespace(?Name $namespace = null): void { + $this->namespace = $namespace; + $this->origAliases = $this->aliases = [ + Stmt\Use_::TYPE_NORMAL => [], + Stmt\Use_::TYPE_FUNCTION => [], + Stmt\Use_::TYPE_CONSTANT => [], + ]; + } + + /** + * Add an alias / import. + * + * @param Name $name Original name + * @param string $aliasName Aliased name + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * @param array $errorAttrs Attributes to use to report an error + */ + public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []): void { + // Constant names are case sensitive, everything else case insensitive + if ($type === Stmt\Use_::TYPE_CONSTANT) { + $aliasLookupName = $aliasName; + } else { + $aliasLookupName = strtolower($aliasName); + } + + if (isset($this->aliases[$type][$aliasLookupName])) { + $typeStringMap = [ + Stmt\Use_::TYPE_NORMAL => '', + Stmt\Use_::TYPE_FUNCTION => 'function ', + Stmt\Use_::TYPE_CONSTANT => 'const ', + ]; + + $this->errorHandler->handleError(new Error( + sprintf( + 'Cannot use %s%s as %s because the name is already in use', + $typeStringMap[$type], $name, $aliasName + ), + $errorAttrs + )); + return; + } + + $this->aliases[$type][$aliasLookupName] = $name; + $this->origAliases[$type][$aliasName] = $name; + } + + /** + * Get current namespace. + * + * @return null|Name Namespace (or null if global namespace) + */ + public function getNamespace(): ?Name { + return $this->namespace; + } + + /** + * Get resolved name. + * + * @param Name $name Name to resolve + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} + * + * @return null|Name Resolved name, or null if static resolution is not possible + */ + public function getResolvedName(Name $name, int $type): ?Name { + // don't resolve special class names + if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { + if (!$name->isUnqualified()) { + $this->errorHandler->handleError(new Error( + sprintf("'\\%s' is an invalid class name", $name->toString()), + $name->getAttributes() + )); + } + return $name; + } + + // fully qualified names are already resolved + if ($name->isFullyQualified()) { + return $name; + } + + // Try to resolve aliases + if (null !== $resolvedName = $this->resolveAlias($name, $type)) { + return $resolvedName; + } + + if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { + if (null === $this->namespace) { + // outside of a namespace unaliased unqualified is same as fully qualified + return new FullyQualified($name, $name->getAttributes()); + } + + // Cannot resolve statically + return null; + } + + // if no alias exists prepend current namespace + return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); + } + + /** + * Get resolved class name. + * + * @param Name $name Class ame to resolve + * + * @return Name Resolved name + */ + public function getResolvedClassName(Name $name): Name { + return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); + } + + /** + * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name[] Possible representations of the name + */ + public function getPossibleNames(string $name, int $type): array { + $lcName = strtolower($name); + + if ($type === Stmt\Use_::TYPE_NORMAL) { + // self, parent and static must always be unqualified + if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { + return [new Name($name)]; + } + } + + // Collect possible ways to write this name, starting with the fully-qualified name + $possibleNames = [new FullyQualified($name)]; + + if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) { + // Make sure there is no alias that makes the normally namespace-relative name + // into something else + if (null === $this->resolveAlias($nsRelativeName, $type)) { + $possibleNames[] = $nsRelativeName; + } + } + + // Check for relevant namespace use statements + foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { + $lcOrig = $orig->toLowerString(); + if (0 === strpos($lcName, $lcOrig . '\\')) { + $possibleNames[] = new Name($alias . substr($name, strlen($lcOrig))); + } + } + + // Check for relevant type-specific use statements + foreach ($this->origAliases[$type] as $alias => $orig) { + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // Constants are are complicated-sensitive + $normalizedOrig = $this->normalizeConstName($orig->toString()); + if ($normalizedOrig === $this->normalizeConstName($name)) { + $possibleNames[] = new Name($alias); + } + } else { + // Everything else is case-insensitive + if ($orig->toLowerString() === $lcName) { + $possibleNames[] = new Name($alias); + } + } + } + + return $possibleNames; + } + + /** + * Get shortest representation of this fully-qualified name. + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name Shortest representation + */ + public function getShortName(string $name, int $type): Name { + $possibleNames = $this->getPossibleNames($name, $type); + + // Find shortest name + $shortestName = null; + $shortestLength = \INF; + foreach ($possibleNames as $possibleName) { + $length = strlen($possibleName->toCodeString()); + if ($length < $shortestLength) { + $shortestName = $possibleName; + $shortestLength = $length; + } + } + + return $shortestName; + } + + private function resolveAlias(Name $name, int $type): ?FullyQualified { + $firstPart = $name->getFirst(); + + if ($name->isQualified()) { + // resolve aliases for qualified names, always against class alias table + $checkName = strtolower($firstPart); + if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { + $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; + return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); + } + } elseif ($name->isUnqualified()) { + // constant aliases are case-sensitive, function aliases case-insensitive + $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart); + if (isset($this->aliases[$type][$checkName])) { + // resolve unqualified aliases + return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); + } + } + + // No applicable aliases + return null; + } + + private function getNamespaceRelativeName(string $name, string $lcName, int $type): ?Name { + if (null === $this->namespace) { + return new Name($name); + } + + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // The constants true/false/null always resolve to the global symbols, even inside a + // namespace, so they may be used without qualification + if ($lcName === "true" || $lcName === "false" || $lcName === "null") { + return new Name($name); + } + } + + $namespacePrefix = strtolower($this->namespace . '\\'); + if (0 === strpos($lcName, $namespacePrefix)) { + return new Name(substr($name, strlen($namespacePrefix))); + } + + return null; + } + + private function normalizeConstName(string $name): string { + $nsSep = strrpos($name, '\\'); + if (false === $nsSep) { + return $name; + } + + // Constants have case-insensitive namespace and case-sensitive short-name + $ns = substr($name, 0, $nsSep); + $shortName = substr($name, $nsSep + 1); + return strtolower($ns) . '\\' . $shortName; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node.php b/vendor/nikic/php-parser/lib/PhpParser/Node.php new file mode 100644 index 0000000..fd2a9b7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node.php @@ -0,0 +1,150 @@ + + */ + public function getAttributes(): array; + + /** + * Replaces all the attributes of this node. + * + * @param array $attributes + */ + public function setAttributes(array $attributes): void; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php new file mode 100644 index 0000000..6680efa --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php @@ -0,0 +1,44 @@ + $attributes Additional attributes + * @param Identifier|null $name Parameter name (for named parameters) + */ + public function __construct( + Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [], + ?Identifier $name = null + ) { + $this->attributes = $attributes; + $this->name = $name; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames(): array { + return ['name', 'value', 'byRef', 'unpack']; + } + + public function getType(): string { + return 'Arg'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php new file mode 100644 index 0000000..fa1cff5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php @@ -0,0 +1,43 @@ + $attributes Additional attributes + */ + public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) { + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames(): array { + return ['key', 'value', 'byRef', 'unpack']; + } + + public function getType(): string { + return 'ArrayItem'; + } +} + +// @deprecated compatibility alias +class_alias(ArrayItem::class, Expr\ArrayItem::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php new file mode 100644 index 0000000..9d89243 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php @@ -0,0 +1,33 @@ + Attribute arguments */ + public array $args; + + /** + * @param Node\Name $name Attribute name + * @param list $args Attribute arguments + * @param array $attributes Additional node attributes + */ + public function __construct(Name $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['name', 'args']; + } + + public function getType(): string { + return 'Attribute'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php new file mode 100644 index 0000000..b9eb588 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php @@ -0,0 +1,27 @@ + $attributes Additional node attributes + */ + public function __construct(array $attrs, array $attributes = []) { + $this->attributes = $attributes; + $this->attrs = $attrs; + } + + public function getSubNodeNames(): array { + return ['attrs']; + } + + public function getType(): string { + return 'AttributeGroup'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php new file mode 100644 index 0000000..e313280 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php @@ -0,0 +1,36 @@ + $attributes Additional attributes + */ + public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->byRef = $byRef; + } + + public function getSubNodeNames(): array { + return ['var', 'byRef']; + } + + public function getType(): string { + return 'ClosureUse'; + } +} + +// @deprecated compatibility alias +class_alias(ClosureUse::class, Expr\ClosureUse::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php new file mode 100644 index 0000000..05a5e5e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php @@ -0,0 +1,13 @@ + $attributes Additional attributes + */ + public function __construct($name, Expr $value, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['name', 'value']; + } + + public function getType(): string { + return 'Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php new file mode 100644 index 0000000..55c1fe4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php @@ -0,0 +1,37 @@ +value pair node. + * + * @param string|Node\Identifier $key Key + * @param Node\Expr $value Value + * @param array $attributes Additional attributes + */ + public function __construct($key, Node\Expr $value, array $attributes = []) { + $this->attributes = $attributes; + $this->key = \is_string($key) ? new Node\Identifier($key) : $key; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['key', 'value']; + } + + public function getType(): string { + return 'DeclareItem'; + } +} + +// @deprecated compatibility alias +class_alias(DeclareItem::class, Stmt\DeclareDeclare::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php new file mode 100644 index 0000000..8b7dbb6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php @@ -0,0 +1,8 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, ?Expr $dim = null, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->dim = $dim; + } + + public function getSubNodeNames(): array { + return ['var', 'dim']; + } + + public function getType(): string { + return 'Expr_ArrayDimFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php new file mode 100644 index 0000000..490ac93 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $items = [], array $attributes = []) { + $this->attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames(): array { + return ['items']; + } + + public function getType(): string { + return 'Expr_Array'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php new file mode 100644 index 0000000..0e98ce9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php @@ -0,0 +1,84 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes, array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->expr = $subNodes['expr']; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** + * @return Node\Stmt\Return_[] + */ + public function getStmts(): array { + return [new Node\Stmt\Return_($this->expr)]; + } + + public function getType(): string { + return 'Expr_ArrowFunction'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php new file mode 100644 index 0000000..dcbf84d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } + + public function getType(): string { + return 'Expr_Assign'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php new file mode 100644 index 0000000..5209a64 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php new file mode 100644 index 0000000..4f3623f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php @@ -0,0 +1,11 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } + + public function getType(): string { + return 'Expr_AssignRef'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php new file mode 100644 index 0000000..1b92bd4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(Expr $left, Expr $right, array $attributes = []) { + $this->attributes = $attributes; + $this->left = $left; + $this->right = $right; + } + + public function getSubNodeNames(): array { + return ['left', 'right']; + } + + /** + * Get the operator sigil for this binary operation. + * + * In the case there are multiple possible sigils for an operator, this method does not + * necessarily return the one used in the parsed code. + */ + abstract public function getOperatorSigil(): string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php new file mode 100644 index 0000000..5930c54 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php @@ -0,0 +1,15 @@ +'; + } + + public function getType(): string { + return 'Expr_BinaryOp_Greater'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php new file mode 100644 index 0000000..4d440b1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php @@ -0,0 +1,15 @@ +='; + } + + public function getType(): string { + return 'Expr_BinaryOp_GreaterOrEqual'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php new file mode 100644 index 0000000..e25d17c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php @@ -0,0 +1,15 @@ +>'; + } + + public function getType(): string { + return 'Expr_BinaryOp_ShiftRight'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php new file mode 100644 index 0000000..01e9b23 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php @@ -0,0 +1,15 @@ +'; + } + + public function getType(): string { + return 'Expr_BinaryOp_Spaceship'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php new file mode 100644 index 0000000..b7175a7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_BitwiseNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php new file mode 100644 index 0000000..c66d233 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_BooleanNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php new file mode 100644 index 0000000..2af2245 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php @@ -0,0 +1,35 @@ + + */ + abstract public function getRawArgs(): array; + + /** + * Returns whether this call expression is actually a first class callable. + */ + public function isFirstClassCallable(): bool { + $rawArgs = $this->getRawArgs(); + return count($rawArgs) === 1 && current($rawArgs) instanceof VariadicPlaceholder; + } + + /** + * Assert that this is not a first-class callable and return only ordinary Args. + * + * @return Arg[] + */ + public function getArgs(): array { + assert(!$this->isFirstClassCallable()); + return $this->getRawArgs(); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php new file mode 100644 index 0000000..c2751de --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php @@ -0,0 +1,25 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php new file mode 100644 index 0000000..471cb82 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php @@ -0,0 +1,11 @@ + $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['class', 'name']; + } + + public function getType(): string { + return 'Expr_ClassConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php new file mode 100644 index 0000000..d85bc9a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Clone'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php new file mode 100644 index 0000000..0680446 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php @@ -0,0 +1,86 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array(): Parameters + * 'uses' => array(): use()s + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attributes groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->uses = $subNodes['uses'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + /** @return Node\Stmt[] */ + public function getStmts(): array { + return $this->stmts; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + public function getType(): string { + return 'Expr_Closure'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php new file mode 100644 index 0000000..681ff31 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(Name $name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Expr_ConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php new file mode 100644 index 0000000..d2f3050 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Empty'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php new file mode 100644 index 0000000..43010ac --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return []; + } + + public function getType(): string { + return 'Expr_Error'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php new file mode 100644 index 0000000..32625a2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_ErrorSuppress'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php new file mode 100644 index 0000000..5120b1b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Eval'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php new file mode 100644 index 0000000..cf00246 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Expr $expr = null, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Exit'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php new file mode 100644 index 0000000..0b85840 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php @@ -0,0 +1,38 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr $name Function name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['name', 'args']; + } + + public function getType(): string { + return 'Expr_FuncCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php new file mode 100644 index 0000000..e1187b1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php @@ -0,0 +1,38 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, int $type, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['expr', 'type']; + } + + public function getType(): string { + return 'Expr_Include'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php new file mode 100644 index 0000000..a2783cb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, Node $class, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->class = $class; + } + + public function getSubNodeNames(): array { + return ['expr', 'class']; + } + + public function getType(): string { + return 'Expr_Instanceof'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php new file mode 100644 index 0000000..4f80fff --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Expr_Isset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php new file mode 100644 index 0000000..496b7b3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php @@ -0,0 +1,34 @@ + $attributes Additional attributes + */ + public function __construct(array $items, array $attributes = []) { + $this->attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames(): array { + return ['items']; + } + + public function getType(): string { + return 'Expr_List'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php new file mode 100644 index 0000000..cd028a2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->arms = $arms; + } + + public function getSubNodeNames(): array { + return ['cond', 'arms']; + } + + public function getType(): string { + return 'Expr_Match'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php new file mode 100644 index 0000000..2703c75 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['var', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_MethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php new file mode 100644 index 0000000..eedaaa1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php @@ -0,0 +1,40 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['class', 'args']; + } + + public function getType(): string { + return 'Expr_New'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php new file mode 100644 index 0000000..a151f71 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a nullsafe method call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['var', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_NullsafeMethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php new file mode 100644 index 0000000..6f73a16 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['var', 'name']; + } + + public function getType(): string { + return 'Expr_NullsafePropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php new file mode 100644 index 0000000..3dca8fd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PostDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php new file mode 100644 index 0000000..bc990c3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PostInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php new file mode 100644 index 0000000..2f16873 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PreDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php new file mode 100644 index 0000000..fd455f5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PreInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php new file mode 100644 index 0000000..6057476 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Print'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php new file mode 100644 index 0000000..8c416a8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['var', 'name']; + } + + public function getType(): string { + return 'Expr_PropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php new file mode 100644 index 0000000..e400351 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct(array $parts, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames(): array { + return ['parts']; + } + + public function getType(): string { + return 'Expr_ShellExec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php new file mode 100644 index 0000000..707f34b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a static method call node. + * + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['class', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_StaticCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php new file mode 100644 index 0000000..4836a65 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php @@ -0,0 +1,36 @@ + $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['class', 'name']; + } + + public function getType(): string { + return 'Expr_StaticPropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php new file mode 100644 index 0000000..d4837e6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(Expr $cond, ?Expr $if, Expr $else, array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->if = $if; + $this->else = $else; + } + + public function getSubNodeNames(): array { + return ['cond', 'if', 'else']; + } + + public function getType(): string { + return 'Expr_Ternary'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php new file mode 100644 index 0000000..ee49f83 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Throw'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php new file mode 100644 index 0000000..cd06f74 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_UnaryMinus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php new file mode 100644 index 0000000..1b44f7b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_UnaryPlus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php new file mode 100644 index 0000000..bab7492 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Expr_Variable'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php new file mode 100644 index 0000000..5cff88f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_YieldFrom'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php new file mode 100644 index 0000000..bd81e69 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Expr $value = null, ?Expr $key = null, array $attributes = []) { + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['key', 'value']; + } + + public function getType(): string { + return 'Expr_Yield'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php new file mode 100644 index 0000000..58f653a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php @@ -0,0 +1,40 @@ + */ + private static array $specialClassNames = [ + 'self' => true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs an identifier node. + * + * @param string $name Identifier as string + * @param array $attributes Additional attributes + */ + public function __construct(string $name, array $attributes = []) { + if ($name === '') { + throw new \InvalidArgumentException('Identifier name cannot be empty'); + } + + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + /** + * Get identifier as string. + * + * @psalm-return non-empty-string + * @return string Identifier as string. + */ + public function toString(): string { + return $this->name; + } + + /** + * Get lowercased identifier as string. + * + * @psalm-return non-empty-string + * @return string Lowercased identifier as string + */ + public function toLowerString(): string { + return strtolower($this->name); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName(): bool { + return isset(self::$specialClassNames[strtolower($this->name)]); + } + + /** + * Get identifier as string. + * + * @psalm-return non-empty-string + * @return string Identifier as string + */ + public function __toString(): string { + return $this->name; + } + + public function getType(): string { + return 'Identifier'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php b/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php new file mode 100644 index 0000000..576dac4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + public function getType(): string { + return 'InterpolatedStringPart'; + } +} + +// @deprecated compatibility alias +class_alias(InterpolatedStringPart::class, Scalar\EncapsedStringPart::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php new file mode 100644 index 0000000..3b39cf1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php @@ -0,0 +1,27 @@ + $attributes Additional attributes + */ + public function __construct(array $types, array $attributes = []) { + $this->attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames(): array { + return ['types']; + } + + public function getType(): string { + return 'IntersectionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php new file mode 100644 index 0000000..2927f02 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php @@ -0,0 +1,30 @@ + */ + public ?array $conds; + /** @var Node\Expr */ + public Expr $body; + + /** + * @param null|list $conds + */ + public function __construct(?array $conds, Node\Expr $body, array $attributes = []) { + $this->conds = $conds; + $this->body = $body; + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return ['conds', 'body']; + } + + public function getType(): string { + return 'MatchArm'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php new file mode 100644 index 0000000..aa2b90e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php @@ -0,0 +1,278 @@ + */ + private static array $specialClassNames = [ + 'self' => true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs a name node. + * + * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor) + * @param array $attributes Additional attributes + */ + final public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = self::prepareName($name); + } + + public function getSubNodeNames(): array { + return ['name']; + } + + /** + * Get parts of name (split by the namespace separator). + * + * @psalm-return non-empty-list + * @return string[] Parts of name + */ + public function getParts(): array { + return \explode('\\', $this->name); + } + + /** + * Gets the first part of the name, i.e. everything before the first namespace separator. + * + * @return string First part of the name + */ + public function getFirst(): string { + if (false !== $pos = \strpos($this->name, '\\')) { + return \substr($this->name, 0, $pos); + } + return $this->name; + } + + /** + * Gets the last part of the name, i.e. everything after the last namespace separator. + * + * @return string Last part of the name + */ + public function getLast(): string { + if (false !== $pos = \strrpos($this->name, '\\')) { + return \substr($this->name, $pos + 1); + } + return $this->name; + } + + /** + * Checks whether the name is unqualified. (E.g. Name) + * + * @return bool Whether the name is unqualified + */ + public function isUnqualified(): bool { + return false === \strpos($this->name, '\\'); + } + + /** + * Checks whether the name is qualified. (E.g. Name\Name) + * + * @return bool Whether the name is qualified + */ + public function isQualified(): bool { + return false !== \strpos($this->name, '\\'); + } + + /** + * Checks whether the name is fully qualified. (E.g. \Name) + * + * @return bool Whether the name is fully qualified + */ + public function isFullyQualified(): bool { + return false; + } + + /** + * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) + * + * @return bool Whether the name is relative + */ + public function isRelative(): bool { + return false; + } + + /** + * Returns a string representation of the name itself, without taking the name type into + * account (e.g., not including a leading backslash for fully qualified names). + * + * @psalm-return non-empty-string + * @return string String representation + */ + public function toString(): string { + return $this->name; + } + + /** + * Returns a string representation of the name as it would occur in code (e.g., including + * leading backslash for fully qualified names. + * + * @psalm-return non-empty-string + * @return string String representation + */ + public function toCodeString(): string { + return $this->toString(); + } + + /** + * Returns lowercased string representation of the name, without taking the name type into + * account (e.g., no leading backslash for fully qualified names). + * + * @psalm-return non-empty-string + * @return string Lowercased string representation + */ + public function toLowerString(): string { + return strtolower($this->name); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName(): bool { + return isset(self::$specialClassNames[strtolower($this->name)]); + } + + /** + * Returns a string representation of the name by imploding the namespace parts with the + * namespace separator. + * + * @psalm-return non-empty-string + * @return string String representation + */ + public function __toString(): string { + return $this->name; + } + + /** + * Gets a slice of a name (similar to array_slice). + * + * This method returns a new instance of the same type as the original and with the same + * attributes. + * + * If the slice is empty, null is returned. The null value will be correctly handled in + * concatenations using concat(). + * + * Offset and length have the same meaning as in array_slice(). + * + * @param int $offset Offset to start the slice at (may be negative) + * @param int|null $length Length of the slice (may be negative) + * + * @return static|null Sliced name + */ + public function slice(int $offset, ?int $length = null) { + if ($offset === 1 && $length === null) { + // Short-circuit the common case. + if (false !== $pos = \strpos($this->name, '\\')) { + return new static(\substr($this->name, $pos + 1)); + } + return null; + } + + $parts = \explode('\\', $this->name); + $numParts = \count($parts); + + $realOffset = $offset < 0 ? $offset + $numParts : $offset; + if ($realOffset < 0 || $realOffset > $numParts) { + throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset)); + } + + if (null === $length) { + $realLength = $numParts - $realOffset; + } else { + $realLength = $length < 0 ? $length + $numParts - $realOffset : $length; + if ($realLength < 0 || $realLength > $numParts - $realOffset) { + throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length)); + } + } + + if ($realLength === 0) { + // Empty slice is represented as null + return null; + } + + return new static(array_slice($parts, $realOffset, $realLength), $this->attributes); + } + + /** + * Concatenate two names, yielding a new Name instance. + * + * The type of the generated instance depends on which class this method is called on, for + * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance. + * + * If one of the arguments is null, a new instance of the other name will be returned. If both + * arguments are null, null will be returned. As such, writing + * Name::concat($namespace, $shortName) + * where $namespace is a Name node or null will work as expected. + * + * @param string|string[]|self|null $name1 The first name + * @param string|string[]|self|null $name2 The second name + * @param array $attributes Attributes to assign to concatenated name + * + * @return static|null Concatenated name + */ + public static function concat($name1, $name2, array $attributes = []) { + if (null === $name1 && null === $name2) { + return null; + } + if (null === $name1) { + return new static($name2, $attributes); + } + if (null === $name2) { + return new static($name1, $attributes); + } else { + return new static( + self::prepareName($name1) . '\\' . self::prepareName($name2), $attributes + ); + } + } + + /** + * Prepares a (string, array or Name node) name for use in name changing methods by converting + * it to a string. + * + * @param string|string[]|self $name Name to prepare + * + * @psalm-return non-empty-string + * @return string Prepared name + */ + private static function prepareName($name): string { + if (\is_string($name)) { + if ('' === $name) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return $name; + } + if (\is_array($name)) { + if (empty($name)) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return implode('\\', $name); + } + if ($name instanceof self) { + return $name->name; + } + + throw new \InvalidArgumentException( + 'Expected string, array of parts or Name instance' + ); + } + + public function getType(): string { + return 'Name'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php new file mode 100644 index 0000000..2118378 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php @@ -0,0 +1,49 @@ +toString(); + } + + public function getType(): string { + return 'Name_FullyQualified'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php new file mode 100644 index 0000000..0226a4e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php @@ -0,0 +1,49 @@ +toString(); + } + + public function getType(): string { + return 'Name_Relative'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php new file mode 100644 index 0000000..b99acd1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Node $type, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['type']; + } + + public function getType(): string { + return 'NullableType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php new file mode 100644 index 0000000..0e9ff0e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php @@ -0,0 +1,84 @@ + $attributes Additional attributes + * @param int $flags Optional visibility flags + * @param list $attrGroups PHP attribute groups + */ + public function __construct( + Expr $var, ?Expr $default = null, ?Node $type = null, + bool $byRef = false, bool $variadic = false, + array $attributes = [], + int $flags = 0, + array $attrGroups = [] + ) { + $this->attributes = $attributes; + $this->type = $type; + $this->byRef = $byRef; + $this->variadic = $variadic; + $this->var = $var; + $this->default = $default; + $this->flags = $flags; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default']; + } + + public function getType(): string { + return 'Param'; + } + + /** + * Whether this parameter uses constructor property promotion. + */ + public function isPromoted(): bool { + return $this->flags !== 0; + } + + public function isPublic(): bool { + return (bool) ($this->flags & Modifiers::PUBLIC); + } + + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php new file mode 100644 index 0000000..101611e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct($name, ?Node\Expr $default = null, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name; + $this->default = $default; + } + + public function getSubNodeNames(): array { + return ['name', 'default']; + } + + public function getType(): string { + return 'PropertyItem'; + } +} + +// @deprecated compatibility alias +class_alias(PropertyItem::class, Stmt\PropertyProperty::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php new file mode 100644 index 0000000..3df2572 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php @@ -0,0 +1,6 @@ + $attributes Additional attributes + */ + public function __construct(float $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * @param mixed[] $attributes + */ + public static function fromString(string $str, array $attributes = []): Float_ { + $attributes['rawValue'] = $str; + $float = self::parse($str); + + return new Float_($float, $attributes); + } + + /** + * @internal + * + * Parses a DNUMBER token like PHP would. + * + * @param string $str A string number + * + * @return float The parsed number + */ + public static function parse(string $str): float { + $str = str_replace('_', '', $str); + + // Check whether this is one of the special integer notations. + if ('0' === $str[0]) { + // hex + if ('x' === $str[1] || 'X' === $str[1]) { + return hexdec($str); + } + + // bin + if ('b' === $str[1] || 'B' === $str[1]) { + return bindec($str); + } + + // oct, but only if the string does not contain any of '.eE'. + if (false === strpbrk($str, '.eE')) { + // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit + // (8 or 9) so that only the digits before that are used. + return octdec(substr($str, 0, strcspn($str, '89'))); + } + } + + // dec + return (float) $str; + } + + public function getType(): string { + return 'Scalar_Float'; + } +} + +// @deprecated compatibility alias +class_alias(Float_::class, DNumber::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php new file mode 100644 index 0000000..bcc257a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php @@ -0,0 +1,82 @@ + $attributes Additional attributes + */ + public function __construct(int $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * Constructs an Int node from a string number literal. + * + * @param string $str String number literal (decimal, octal, hex or binary) + * @param array $attributes Additional attributes + * @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5) + * + * @return Int_ The constructed LNumber, including kind attribute + */ + public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false): Int_ { + $attributes['rawValue'] = $str; + + $str = str_replace('_', '', $str); + + if ('0' !== $str[0] || '0' === $str) { + $attributes['kind'] = Int_::KIND_DEC; + return new Int_((int) $str, $attributes); + } + + if ('x' === $str[1] || 'X' === $str[1]) { + $attributes['kind'] = Int_::KIND_HEX; + return new Int_(hexdec($str), $attributes); + } + + if ('b' === $str[1] || 'B' === $str[1]) { + $attributes['kind'] = Int_::KIND_BIN; + return new Int_(bindec($str), $attributes); + } + + if (!$allowInvalidOctal && strpbrk($str, '89')) { + throw new Error('Invalid numeric literal', $attributes); + } + + // Strip optional explicit octal prefix. + if ('o' === $str[1] || 'O' === $str[1]) { + $str = substr($str, 2); + } + + // use intval instead of octdec to get proper cutting behavior with malformed numbers + $attributes['kind'] = Int_::KIND_OCT; + return new Int_(intval($str, 8), $attributes); + } + + public function getType(): string { + return 'Scalar_Int'; + } +} + +// @deprecated compatibility alias +class_alias(Int_::class, LNumber::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php new file mode 100644 index 0000000..9336dfe --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php @@ -0,0 +1,34 @@ + $attributes Additional attributes + */ + public function __construct(array $parts, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames(): array { + return ['parts']; + } + + public function getType(): string { + return 'Scalar_InterpolatedString'; + } +} + +// @deprecated compatibility alias +class_alias(InterpolatedString::class, Encapsed::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php new file mode 100644 index 0000000..cfe8c8c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return []; + } + + /** + * Get name of magic constant. + * + * @return string Name of magic constant + */ + abstract public function getName(): string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php new file mode 100644 index 0000000..732ed14 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php @@ -0,0 +1,15 @@ + Escaped character to its decoded value */ + protected static array $replacements = [ + '\\' => '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Constructs a string scalar node. + * + * @param string $value Value of the string + * @param array $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * @param array $attributes + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + */ + public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self { + $attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B'))) + ? Scalar\String_::KIND_SINGLE_QUOTED + : Scalar\String_::KIND_DOUBLE_QUOTED; + + $attributes['rawValue'] = $str; + + $string = self::parse($str, $parseUnicodeEscape); + + return new self($string, $attributes); + } + + /** + * @internal + * + * Parses a string token. + * + * @param string $str String token content + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string The parsed string + */ + public static function parse(string $str, bool $parseUnicodeEscape = true): string { + $bLength = 0; + if ('b' === $str[0] || 'B' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences( + substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape + ); + } + } + + /** + * @internal + * + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param null|string $quote Quote type + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string String with escape sequences parsed + */ + public static function parseEscapeSequences(string $str, ?string $quote, bool $parseUnicodeEscape = true): string { + if (null !== $quote) { + $str = str_replace('\\' . $quote, $quote, $str); + } + + $extra = ''; + if ($parseUnicodeEscape) { + $extra = '|u\{([0-9a-fA-F]+)\}'; + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', + function ($matches) { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } + if ('x' === $str[0] || 'X' === $str[0]) { + return chr(hexdec(substr($str, 1))); + } + if ('u' === $str[0]) { + $dec = hexdec($matches[2]); + // If it overflowed to float, treat as INT_MAX, it will throw an error anyway. + return self::codePointToUtf8(\is_int($dec) ? $dec : \PHP_INT_MAX); + } else { + return chr(octdec($str)); + } + }, + $str + ); + } + + /** + * Converts a Unicode code point to its UTF-8 encoded representation. + * + * @param int $num Code point + * + * @return string UTF-8 representation of code point + */ + private static function codePointToUtf8(int $num): string { + if ($num <= 0x7F) { + return chr($num); + } + if ($num <= 0x7FF) { + return chr(($num >> 6) + 0xC0) . chr(($num & 0x3F) + 0x80); + } + if ($num <= 0xFFFF) { + return chr(($num >> 12) + 0xE0) . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); + } + if ($num <= 0x1FFFFF) { + return chr(($num >> 18) + 0xF0) . chr((($num >> 12) & 0x3F) + 0x80) + . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); + } + throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); + } + + public function getType(): string { + return 'Scalar_String'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php new file mode 100644 index 0000000..517c0ed --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php @@ -0,0 +1,39 @@ + $attributes Additional attributes + */ + public function __construct( + Expr\Variable $var, ?Node\Expr $default = null, array $attributes = [] + ) { + $this->attributes = $attributes; + $this->var = $var; + $this->default = $default; + } + + public function getSubNodeNames(): array { + return ['var', 'default']; + } + + public function getType(): string { + return 'StaticVar'; + } +} + +// @deprecated compatibility alias +class_alias(StaticVar::class, Stmt\StaticVar::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php new file mode 100644 index 0000000..481d31a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php @@ -0,0 +1,8 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts, array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getType(): string { + return 'Stmt_Block'; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php new file mode 100644 index 0000000..d2bcc5e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $num = null, array $attributes = []) { + $this->attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames(): array { + return ['num']; + } + + public function getType(): string { + return 'Stmt_Break'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php new file mode 100644 index 0000000..a06ca18 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Case'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php new file mode 100644 index 0000000..e8d39c9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php @@ -0,0 +1,40 @@ + $attributes Additional attributes + */ + public function __construct( + array $types, ?Expr\Variable $var = null, array $stmts = [], array $attributes = [] + ) { + $this->attributes = $attributes; + $this->types = $types; + $this->var = $var; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['types', 'var', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Catch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php new file mode 100644 index 0000000..9bdce1f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php @@ -0,0 +1,77 @@ + $attributes Additional attributes + * @param list $attrGroups PHP attribute groups + * @param null|Node\Identifier|Node\Name|Node\ComplexType $type Type declaration + */ + public function __construct( + array $consts, + int $flags = 0, + array $attributes = [], + array $attrGroups = [], + ?Node $type = null + ) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->consts = $consts; + $this->attrGroups = $attrGroups; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'consts']; + } + + /** + * Whether constant is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether constant is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether constant is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether constant is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function getType(): string { + return 'Stmt_ClassConst'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php new file mode 100644 index 0000000..fb9ba4f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php @@ -0,0 +1,109 @@ +stmts as $stmt) { + if ($stmt instanceof TraitUse) { + $traitUses[] = $stmt; + } + } + return $traitUses; + } + + /** + * @return ClassConst[] + */ + public function getConstants(): array { + $constants = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassConst) { + $constants[] = $stmt; + } + } + return $constants; + } + + /** + * @return Property[] + */ + public function getProperties(): array { + $properties = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + $properties[] = $stmt; + } + } + return $properties; + } + + /** + * Gets property with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the property + * + * @return Property|null Property node or null if the property does not exist + */ + public function getProperty(string $name): ?Property { + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + foreach ($stmt->props as $prop) { + if ($prop instanceof PropertyItem && $name === $prop->name->toString()) { + return $stmt; + } + } + } + } + return null; + } + + /** + * Gets all methods defined directly in this class/interface/trait + * + * @return ClassMethod[] + */ + public function getMethods(): array { + $methods = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + $methods[] = $stmt; + } + } + return $methods; + } + + /** + * Gets method with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the method (compared case-insensitively) + * + * @return ClassMethod|null Method node or null if the method does not exist + */ + public function getMethod(string $name): ?ClassMethod { + $lowerName = strtolower($name); + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { + return $stmt; + } + } + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php new file mode 100644 index 0000000..59c0519 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php @@ -0,0 +1,154 @@ + */ + private static array $magicNames = [ + '__construct' => true, + '__destruct' => true, + '__call' => true, + '__callstatic' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + '__unset' => true, + '__sleep' => true, + '__wakeup' => true, + '__tostring' => true, + '__set_state' => true, + '__clone' => true, + '__invoke' => true, + '__debuginfo' => true, + '__serialize' => true, + '__unserialize' => true, + ]; + + /** + * Constructs a class method node. + * + * @param string|Node\Identifier $name Name + * @param array{ + * flags?: int, + * byRef?: bool, + * params?: Node\Param[], + * returnType?: null|Node\Identifier|Node\Name|Node\ComplexType, + * stmts?: Node\Stmt[]|null, + * attrGroups?: Node\AttributeGroup[], + * } $subNodes Array of the following optional subnodes: + * 'flags => 0 : Flags + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getStmts(): ?array { + return $this->stmts; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** + * Whether the method is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether the method is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether the method is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether the method is abstract. + */ + public function isAbstract(): bool { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + + /** + * Whether the method is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + /** + * Whether the method is static. + */ + public function isStatic(): bool { + return (bool) ($this->flags & Modifiers::STATIC); + } + + /** + * Whether the method is magic. + */ + public function isMagic(): bool { + return isset(self::$magicNames[$this->name->toLowerString()]); + } + + public function getType(): string { + return 'Stmt_ClassMethod'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php new file mode 100644 index 0000000..3f492b7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php @@ -0,0 +1,94 @@ + 0 : Flags + * 'extends' => null : Name of extended class + * 'implements' => array(): Names of implemented interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts']; + } + + /** + * Whether the class is explicitly abstract. + */ + public function isAbstract(): bool { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + + /** + * Whether the class is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } + + /** + * Whether the class is anonymous. + */ + public function isAnonymous(): bool { + return null === $this->name; + } + + public function getType(): string { + return 'Stmt_Class'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php new file mode 100644 index 0000000..f1165fd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $consts, array $attributes = []) { + $this->attributes = $attributes; + $this->consts = $consts; + } + + public function getSubNodeNames(): array { + return ['consts']; + } + + public function getType(): string { + return 'Stmt_Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php new file mode 100644 index 0000000..54e979d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $num = null, array $attributes = []) { + $this->attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames(): array { + return ['num']; + } + + public function getType(): string { + return 'Stmt_Continue'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php new file mode 100644 index 0000000..cb9e837 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $declares, ?array $stmts = null, array $attributes = []) { + $this->attributes = $attributes; + $this->declares = $declares; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['declares', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Declare'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php new file mode 100644 index 0000000..6124442 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts', 'cond']; + } + + public function getType(): string { + return 'Stmt_Do'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php new file mode 100644 index 0000000..4d42452 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $exprs, array $attributes = []) { + $this->attributes = $attributes; + $this->exprs = $exprs; + } + + public function getSubNodeNames(): array { + return ['exprs']; + } + + public function getType(): string { + return 'Stmt_Echo'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php new file mode 100644 index 0000000..b26d59c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_ElseIf'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php new file mode 100644 index 0000000..3d2b066 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } + + public function getType(): string { + return 'Stmt_Else'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php new file mode 100644 index 0000000..c071a0a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php @@ -0,0 +1,36 @@ + $attrGroups PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, ?Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) { + parent::__construct($attributes); + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->expr = $expr; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'expr']; + } + + public function getType(): string { + return 'Stmt_EnumCase'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php new file mode 100644 index 0000000..7eea6a6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php @@ -0,0 +1,44 @@ + null : Scalar type + * 'implements' => array() : Names of implemented interfaces + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->scalarType = $subNodes['scalarType'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + + parent::__construct($attributes); + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Enum'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php new file mode 100644 index 0000000..89751fa --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Stmt_Expression'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php new file mode 100644 index 0000000..69ecf25 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } + + public function getType(): string { + return 'Stmt_Finally'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php new file mode 100644 index 0000000..6f2fbb9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php @@ -0,0 +1,47 @@ + array(): Init expressions + * 'cond' => array(): Loop conditions + * 'loop' => array(): Loop expressions + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->init = $subNodes['init'] ?? []; + $this->cond = $subNodes['cond'] ?? []; + $this->loop = $subNodes['loop'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames(): array { + return ['init', 'cond', 'loop', 'stmts']; + } + + public function getType(): string { + return 'Stmt_For'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php new file mode 100644 index 0000000..c5d9a8b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php @@ -0,0 +1,50 @@ + null : Variable to assign key to + * 'byRef' => false : Whether to assign value by reference + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->keyVar = $subNodes['keyVar'] ?? null; + $this->byRef = $subNodes['byRef'] ?? false; + $this->valueVar = $valueVar; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames(): array { + return ['expr', 'keyVar', 'byRef', 'valueVar', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Foreach'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php new file mode 100644 index 0000000..2111bab --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php @@ -0,0 +1,81 @@ + false : Whether to return by reference + * 'params' => array(): Parameters + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** @return Node\Stmt[] */ + public function getStmts(): array { + return $this->stmts; + } + + public function getType(): string { + return 'Stmt_Function'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php new file mode 100644 index 0000000..d3ab12f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Global'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php new file mode 100644 index 0000000..26a0d01 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Stmt_Goto'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php new file mode 100644 index 0000000..0ec8e9d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php @@ -0,0 +1,41 @@ + $attributes Additional attributes + */ + public function __construct(Name $prefix, array $uses, int $type = Use_::TYPE_NORMAL, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->prefix = $prefix; + $this->uses = $uses; + } + + public function getSubNodeNames(): array { + return ['type', 'prefix', 'uses']; + } + + public function getType(): string { + return 'Stmt_GroupUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php new file mode 100644 index 0000000..665bacd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(string $remaining, array $attributes = []) { + $this->attributes = $attributes; + $this->remaining = $remaining; + } + + public function getSubNodeNames(): array { + return ['remaining']; + } + + public function getType(): string { + return 'Stmt_HaltCompiler'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php new file mode 100644 index 0000000..544390f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php @@ -0,0 +1,46 @@ + array(): Statements + * 'elseifs' => array(): Elseif clauses + * 'else' => null : Else clause + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $subNodes['stmts'] ?? []; + $this->elseifs = $subNodes['elseifs'] ?? []; + $this->else = $subNodes['else'] ?? null; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts', 'elseifs', 'else']; + } + + public function getType(): string { + return 'Stmt_If'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php new file mode 100644 index 0000000..0515d02 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + public function getType(): string { + return 'Stmt_InlineHTML'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php new file mode 100644 index 0000000..9359064 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php @@ -0,0 +1,40 @@ + array(): Name of extended interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'extends', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Interface'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php new file mode 100644 index 0000000..658468d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Stmt_Label'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php new file mode 100644 index 0000000..f5b59ad --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Name $name = null, ?array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['name', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Namespace'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php new file mode 100644 index 0000000..3acfa46 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php @@ -0,0 +1,16 @@ + $attributes Additional attributes + * @param null|Identifier|Name|ComplexType $type Type declaration + * @param Node\AttributeGroup[] $attrGroups PHP attribute groups + */ + public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = []) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->props = $props; + $this->type = $type; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'props']; + } + + /** + * Whether the property is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether the property is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether the property is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether the property is static. + */ + public function isStatic(): bool { + return (bool) ($this->flags & Modifiers::STATIC); + } + + /** + * Whether the property is readonly. + */ + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } + + public function getType(): string { + return 'Stmt_Property'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php new file mode 100644 index 0000000..4a21a88 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $expr = null, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Stmt_Return'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php new file mode 100644 index 0000000..88452e7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Static'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php new file mode 100644 index 0000000..21e5efa --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $cases, array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->cases = $cases; + } + + public function getSubNodeNames(): array { + return ['cond', 'cases']; + } + + public function getType(): string { + return 'Stmt_Switch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php new file mode 100644 index 0000000..7705a57 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(array $traits, array $adaptations = [], array $attributes = []) { + $this->attributes = $attributes; + $this->traits = $traits; + $this->adaptations = $adaptations; + } + + public function getSubNodeNames(): array { + return ['traits', 'adaptations']; + } + + public function getType(): string { + return 'Stmt_TraitUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php new file mode 100644 index 0000000..987bc88 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php @@ -0,0 +1,12 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Name $trait, $method, ?int $newModifier, $newName, array $attributes = []) { + $this->attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->newModifier = $newModifier; + $this->newName = \is_string($newName) ? new Node\Identifier($newName) : $newName; + } + + public function getSubNodeNames(): array { + return ['trait', 'method', 'newModifier', 'newName']; + } + + public function getType(): string { + return 'Stmt_TraitUseAdaptation_Alias'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php new file mode 100644 index 0000000..7bc4083 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Name $trait, $method, array $insteadof, array $attributes = []) { + $this->attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->insteadof = $insteadof; + } + + public function getSubNodeNames(): array { + return ['trait', 'method', 'insteadof']; + } + + public function getType(): string { + return 'Stmt_TraitUseAdaptation_Precedence'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php new file mode 100644 index 0000000..5f2b330 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php @@ -0,0 +1,34 @@ + array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Trait'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php new file mode 100644 index 0000000..6414c46 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts, array $catches, ?Finally_ $finally = null, array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + $this->catches = $catches; + $this->finally = $finally; + } + + public function getSubNodeNames(): array { + return ['stmts', 'catches', 'finally']; + } + + public function getType(): string { + return 'Stmt_TryCatch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php new file mode 100644 index 0000000..c211beb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Unset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php new file mode 100644 index 0000000..85830ed --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $uses, int $type = self::TYPE_NORMAL, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->uses = $uses; + } + + public function getSubNodeNames(): array { + return ['type', 'uses']; + } + + public function getType(): string { + return 'Stmt_Use'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php new file mode 100644 index 0000000..2f7aed2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_While'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php new file mode 100644 index 0000000..bad88d2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php @@ -0,0 +1,27 @@ + $attributes Additional attributes + */ + public function __construct(array $types, array $attributes = []) { + $this->attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames(): array { + return ['types']; + } + + public function getType(): string { + return 'UnionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php new file mode 100644 index 0000000..a7d9fc4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php @@ -0,0 +1,55 @@ + $attributes Additional attributes + */ + public function __construct(Node\Name $name, $alias = null, int $type = Use_::TYPE_UNKNOWN, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->name = $name; + $this->alias = \is_string($alias) ? new Identifier($alias) : $alias; + } + + public function getSubNodeNames(): array { + return ['type', 'name', 'alias']; + } + + /** + * Get alias. If not explicitly given this is the last component of the used name. + */ + public function getAlias(): Identifier { + if (null !== $this->alias) { + return $this->alias; + } + + return new Identifier($this->name->getLast()); + } + + public function getType(): string { + return 'UseItem'; + } +} + +// @deprecated compatibility alias +class_alias(UseItem::class, Stmt\UseUse::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php new file mode 100644 index 0000000..9baa6fe --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php @@ -0,0 +1,16 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getType(): string { + return 'VariadicPlaceholder'; + } + + public function getSubNodeNames(): array { + return []; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php new file mode 100644 index 0000000..a6a50ae --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php @@ -0,0 +1,181 @@ + Attributes */ + protected array $attributes; + + /** + * Creates a Node. + * + * @param array $attributes Array of attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + /** + * Gets line the node started in (alias of getStartLine). + * + * @return int Start line (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets line the node started in. + * + * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int Start line (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getStartLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the node ended in. + * + * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int End line (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getEndLine(): int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the token offset of the first token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token start position (or -1 if not available) + */ + public function getStartTokenPos(): int { + return $this->attributes['startTokenPos'] ?? -1; + } + + /** + * Gets the token offset of the last token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token end position (or -1 if not available) + */ + public function getEndTokenPos(): int { + return $this->attributes['endTokenPos'] ?? -1; + } + + /** + * Gets the file offset of the first character that is part of this node. + * + * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File start position (or -1 if not available) + */ + public function getStartFilePos(): int { + return $this->attributes['startFilePos'] ?? -1; + } + + /** + * Gets the file offset of the last character that is part of this node. + * + * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File end position (or -1 if not available) + */ + public function getEndFilePos(): int { + return $this->attributes['endFilePos'] ?? -1; + } + + /** + * Gets all comments directly preceding this node. + * + * The comments are also available through the "comments" attribute. + * + * @return Comment[] + */ + public function getComments(): array { + return $this->attributes['comments'] ?? []; + } + + /** + * Gets the doc comment of the node. + * + * @return null|Comment\Doc Doc comment object or null + */ + public function getDocComment(): ?Comment\Doc { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + $comment = $comments[$i]; + if ($comment instanceof Comment\Doc) { + return $comment; + } + } + + return null; + } + + /** + * Sets the doc comment of the node. + * + * This will either replace an existing doc comment or add it to the comments array. + * + * @param Comment\Doc $docComment Doc comment to set + */ + public function setDocComment(Comment\Doc $docComment): void { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + if ($comments[$i] instanceof Comment\Doc) { + // Replace existing doc comment. + $comments[$i] = $docComment; + $this->setAttribute('comments', $comments); + return; + } + } + + // Append new doc comment. + $comments[] = $docComment; + $this->setAttribute('comments', $comments); + } + + public function setAttribute(string $key, $value): void { + $this->attributes[$key] = $value; + } + + public function hasAttribute(string $key): bool { + return array_key_exists($key, $this->attributes); + } + + public function getAttribute(string $key, $default = null) { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + + return $default; + } + + public function getAttributes(): array { + return $this->attributes; + } + + public function setAttributes(array $attributes): void { + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function jsonSerialize(): array { + return ['nodeType' => $this->getType()] + get_object_vars($this); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php new file mode 100644 index 0000000..a2535de --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php @@ -0,0 +1,290 @@ + true, + 'startLine' => true, + 'endLine' => true, + 'startFilePos' => true, + 'endFilePos' => true, + 'startTokenPos' => true, + 'endTokenPos' => true, + ]; + + /** + * Constructs a NodeDumper. + * + * Supported options: + * * bool dumpComments: Whether comments should be dumped. + * * bool dumpPositions: Whether line/offset information should be dumped. To dump offset + * information, the code needs to be passed to dump(). + * * bool dumpOtherAttributes: Whether non-comment, non-position attributes should be dumped. + * + * @param array $options Options (see description) + */ + public function __construct(array $options = []) { + $this->dumpComments = !empty($options['dumpComments']); + $this->dumpPositions = !empty($options['dumpPositions']); + $this->dumpOtherAttributes = !empty($options['dumpOtherAttributes']); + } + + /** + * Dumps a node or array. + * + * @param array|Node $node Node or array to dump + * @param string|null $code Code corresponding to dumped AST. This only needs to be passed if + * the dumpPositions option is enabled and the dumping of node offsets + * is desired. + * + * @return string Dumped value + */ + public function dump($node, ?string $code = null): string { + $this->code = $code; + $this->res = ''; + $this->nl = "\n"; + $this->dumpRecursive($node, false); + return $this->res; + } + + /** @param mixed $node */ + protected function dumpRecursive($node, bool $indent = true): void { + if ($indent) { + $this->nl .= " "; + } + if ($node instanceof Node) { + $this->res .= $node->getType(); + if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) { + $this->res .= $p; + } + $this->res .= '('; + + foreach ($node->getSubNodeNames() as $key) { + $this->res .= "$this->nl " . $key . ': '; + + $value = $node->$key; + if (\is_int($value)) { + if ('flags' === $key || 'newModifier' === $key) { + $this->res .= $this->dumpFlags($value); + continue; + } + if ('type' === $key && $node instanceof Include_) { + $this->res .= $this->dumpIncludeType($value); + continue; + } + if ('type' === $key + && ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) { + $this->res .= $this->dumpUseType($value); + continue; + } + } + $this->dumpRecursive($value); + } + + if ($this->dumpComments && $comments = $node->getComments()) { + $this->res .= "$this->nl comments: "; + $this->dumpRecursive($comments); + } + + if ($this->dumpOtherAttributes) { + foreach ($node->getAttributes() as $key => $value) { + if (isset(self::IGNORE_ATTRIBUTES[$key])) { + continue; + } + + $this->res .= "$this->nl $key: "; + if (\is_int($value)) { + if ('kind' === $key) { + if ($node instanceof Int_) { + $this->res .= $this->dumpIntKind($value); + continue; + } + if ($node instanceof String_ || $node instanceof InterpolatedString) { + $this->res .= $this->dumpStringKind($value); + continue; + } + if ($node instanceof Array_) { + $this->res .= $this->dumpArrayKind($value); + continue; + } + if ($node instanceof List_) { + $this->res .= $this->dumpListKind($value); + continue; + } + } + } + $this->dumpRecursive($value); + } + } + $this->res .= "$this->nl)"; + } elseif (\is_array($node)) { + $this->res .= 'array('; + foreach ($node as $key => $value) { + $this->res .= "$this->nl " . $key . ': '; + $this->dumpRecursive($value); + } + $this->res .= "$this->nl)"; + } elseif ($node instanceof Comment) { + $this->res .= \str_replace("\n", $this->nl, $node->getReformattedText()); + } elseif (\is_string($node)) { + $this->res .= \str_replace("\n", $this->nl, (string)$node); + } elseif (\is_int($node) || \is_float($node)) { + $this->res .= $node; + } elseif (null === $node) { + $this->res .= 'null'; + } elseif (false === $node) { + $this->res .= 'false'; + } elseif (true === $node) { + $this->res .= 'true'; + } else { + throw new \InvalidArgumentException('Can only dump nodes and arrays.'); + } + if ($indent) { + $this->nl = \substr($this->nl, 0, -4); + } + } + + protected function dumpFlags(int $flags): string { + $strs = []; + if ($flags & Modifiers::PUBLIC) { + $strs[] = 'PUBLIC'; + } + if ($flags & Modifiers::PROTECTED) { + $strs[] = 'PROTECTED'; + } + if ($flags & Modifiers::PRIVATE) { + $strs[] = 'PRIVATE'; + } + if ($flags & Modifiers::ABSTRACT) { + $strs[] = 'ABSTRACT'; + } + if ($flags & Modifiers::STATIC) { + $strs[] = 'STATIC'; + } + if ($flags & Modifiers::FINAL) { + $strs[] = 'FINAL'; + } + if ($flags & Modifiers::READONLY) { + $strs[] = 'READONLY'; + } + + if ($strs) { + return implode(' | ', $strs) . ' (' . $flags . ')'; + } else { + return (string) $flags; + } + } + + /** @param array $map */ + private function dumpEnum(int $value, array $map): string { + if (!isset($map[$value])) { + return (string) $value; + } + return $map[$value] . ' (' . $value . ')'; + } + + private function dumpIncludeType(int $type): string { + return $this->dumpEnum($type, [ + Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', + Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', + Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', + Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE', + ]); + } + + private function dumpUseType(int $type): string { + return $this->dumpEnum($type, [ + Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', + Use_::TYPE_NORMAL => 'TYPE_NORMAL', + Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', + Use_::TYPE_CONSTANT => 'TYPE_CONSTANT', + ]); + } + + private function dumpIntKind(int $kind): string { + return $this->dumpEnum($kind, [ + Int_::KIND_BIN => 'KIND_BIN', + Int_::KIND_OCT => 'KIND_OCT', + Int_::KIND_DEC => 'KIND_DEC', + Int_::KIND_HEX => 'KIND_HEX', + ]); + } + + private function dumpStringKind(int $kind): string { + return $this->dumpEnum($kind, [ + String_::KIND_SINGLE_QUOTED => 'KIND_SINGLE_QUOTED', + String_::KIND_DOUBLE_QUOTED => 'KIND_DOUBLE_QUOTED', + String_::KIND_HEREDOC => 'KIND_HEREDOC', + String_::KIND_NOWDOC => 'KIND_NOWDOC', + ]); + } + + private function dumpArrayKind(int $kind): string { + return $this->dumpEnum($kind, [ + Array_::KIND_LONG => 'KIND_LONG', + Array_::KIND_SHORT => 'KIND_SHORT', + ]); + } + + private function dumpListKind(int $kind): string { + return $this->dumpEnum($kind, [ + List_::KIND_LIST => 'KIND_LIST', + List_::KIND_ARRAY => 'KIND_ARRAY', + ]); + } + + /** + * Dump node position, if possible. + * + * @param Node $node Node for which to dump position + * + * @return string|null Dump of position, or null if position information not available + */ + protected function dumpPosition(Node $node): ?string { + if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) { + return null; + } + + $start = $node->getStartLine(); + $end = $node->getEndLine(); + if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') + && null !== $this->code + ) { + $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); + $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); + } + return "[$start - $end]"; + } + + // Copied from Error class + private function toColumn(string $code, int $pos): int { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php new file mode 100644 index 0000000..96c8452 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php @@ -0,0 +1,90 @@ +traverse($nodes); + + return $visitor->getFoundNodes(); + } + + /** + * Find all nodes that are instances of a certain class. + + * @template TNode as Node + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param class-string $class Class name + * + * @return TNode[] Found nodes (all instances of $class) + */ + public function findInstanceOf($nodes, string $class): array { + return $this->find($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } + + /** + * Find first node satisfying a filter callback. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param callable $filter Filter callback: function(Node $node) : bool + * + * @return null|Node Found node (or null if none found) + */ + public function findFirst($nodes, callable $filter): ?Node { + if ($nodes === []) { + return null; + } + + if (!is_array($nodes)) { + $nodes = [$nodes]; + } + + $visitor = new FirstFindingVisitor($filter); + + $traverser = new NodeTraverser($visitor); + $traverser->traverse($nodes); + + return $visitor->getFoundNode(); + } + + /** + * Find first node that is an instance of a certain class. + * + * @template TNode as Node + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param class-string $class Class name + * + * @return null|TNode Found node, which is an instance of $class (or null if none found) + */ + public function findFirstInstanceOf($nodes, string $class): ?Node { + return $this->findFirst($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php new file mode 100644 index 0000000..f5b868a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php @@ -0,0 +1,278 @@ + Visitors */ + protected array $visitors = []; + + /** @var bool Whether traversal should be stopped */ + protected bool $stopTraversal; + + /** + * Create a traverser with the given visitors. + * + * @param NodeVisitor ...$visitors Node visitors + */ + public function __construct(NodeVisitor ...$visitors) { + $this->visitors = $visitors; + } + + /** + * Adds a visitor. + * + * @param NodeVisitor $visitor Visitor to add + */ + public function addVisitor(NodeVisitor $visitor): void { + $this->visitors[] = $visitor; + } + + /** + * Removes an added visitor. + */ + public function removeVisitor(NodeVisitor $visitor): void { + $index = array_search($visitor, $this->visitors); + if ($index !== false) { + array_splice($this->visitors, $index, 1, []); + } + } + + /** + * Traverses an array of nodes using the registered visitors. + * + * @param Node[] $nodes Array of nodes + * + * @return Node[] Traversed array of nodes + */ + public function traverse(array $nodes): array { + $this->stopTraversal = false; + + foreach ($this->visitors as $visitor) { + if (null !== $return = $visitor->beforeTraverse($nodes)) { + $nodes = $return; + } + } + + $nodes = $this->traverseArray($nodes); + + for ($i = \count($this->visitors) - 1; $i >= 0; --$i) { + $visitor = $this->visitors[$i]; + if (null !== $return = $visitor->afterTraverse($nodes)) { + $nodes = $return; + } + } + + return $nodes; + } + + /** + * Recursively traverse a node. + * + * @param Node $node Node to traverse. + */ + protected function traverseNode(Node $node): void { + foreach ($node->getSubNodeNames() as $name) { + $subNode = $node->$name; + + if (\is_array($subNode)) { + $node->$name = $this->traverseArray($subNode); + if ($this->stopTraversal) { + break; + } + } elseif ($subNode instanceof Node) { + $traverseChildren = true; + $visitorIndex = -1; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $node->$name = $return; + } elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + $node->$name = null; + continue 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $this->traverseNode($subNode); + if ($this->stopTraversal) { + break; + } + } + + for (; $visitorIndex >= 0; --$visitorIndex) { + $visitor = $this->visitors[$visitorIndex]; + $return = $visitor->leaveNode($subNode); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $node->$name = $return; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + $node->$name = null; + break; + } elseif (\is_array($return)) { + throw new \LogicException( + 'leaveNode() may only return an array ' . + 'if the parent structure is an array' + ); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + } + } + } + + /** + * Recursively traverse array (usually of nodes). + * + * @param array $nodes Array to traverse + * + * @return array Result of traversal (may be original array or changed one) + */ + protected function traverseArray(array $nodes): array { + $doNodes = []; + + foreach ($nodes as $i => $node) { + if ($node instanceof Node) { + $traverseChildren = true; + $visitorIndex = -1; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $nodes[$i] = $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + continue 2; + } elseif (NodeVisitor::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + continue 2; + } elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + throw new \LogicException( + 'REPLACE_WITH_NULL can not be used if the parent structure is an array'); + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $this->traverseNode($node); + if ($this->stopTraversal) { + break; + } + } + + for (; $visitorIndex >= 0; --$visitorIndex) { + $visitor = $this->visitors[$visitorIndex]; + $return = $visitor->leaveNode($node); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $nodes[$i] = $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + break; + } elseif (NodeVisitor::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + throw new \LogicException( + 'REPLACE_WITH_NULL can not be used if the parent structure is an array'); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + } elseif (\is_array($node)) { + throw new \LogicException('Invalid node structure: Contains nested arrays'); + } + } + + if (!empty($doNodes)) { + while (list($i, $replace) = array_pop($doNodes)) { + array_splice($nodes, $i, 1, $replace); + } + } + + return $nodes; + } + + private function ensureReplacementReasonable(Node $old, Node $new): void { + if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { + throw new \LogicException( + "Trying to replace statement ({$old->getType()}) " . + "with expression ({$new->getType()}). Are you missing a " . + "Stmt_Expression wrapper?" + ); + } + + if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { + throw new \LogicException( + "Trying to replace expression ({$old->getType()}) " . + "with statement ({$new->getType()})" + ); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php new file mode 100644 index 0000000..c3992b3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php @@ -0,0 +1,26 @@ + $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * NodeVisitor::REMOVE_NODE + * => $node is removed from the parent array + * * NodeVisitor::REPLACE_WITH_NULL + * => $node is replaced with null + * * NodeVisitor::DONT_TRAVERSE_CHILDREN + * => Children of $node are not traversed. $node stays as-is + * * NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN + * => Further visitors for the current node are skipped, and its children are not + * traversed. $node stays as-is. + * * NodeVisitor::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function enterNode(Node $node); + + /** + * Called when leaving a node. + * + * Return value semantics: + * * null + * => $node stays as-is + * * NodeVisitor::REMOVE_NODE + * => $node is removed from the parent array + * * NodeVisitor::REPLACE_WITH_NULL + * => $node is replaced with null + * * NodeVisitor::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function leaveNode(Node $node); + + /** + * Called once after traversal. + * + * Return value semantics: + * * null: $nodes stays as-is + * * otherwise: $nodes is set to the return value + * + * @param Node[] $nodes Array of nodes + * + * @return null|Node[] Array of nodes + */ + public function afterTraverse(array $nodes); +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php new file mode 100644 index 0000000..cba9249 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php @@ -0,0 +1,19 @@ +setAttribute('origNode', $origNode); + return $node; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php new file mode 100644 index 0000000..5e2aed3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php @@ -0,0 +1,82 @@ + Token positions of comments */ + private array $commentPositions = []; + + /** + * Create a comment annotation visitor. + * + * @param Token[] $tokens Token array + */ + public function __construct(array $tokens) { + $this->tokens = $tokens; + + // Collect positions of comments. We use this to avoid traversing parts of the AST where + // there are no comments. + foreach ($tokens as $i => $token) { + if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) { + $this->commentPositions[] = $i; + } + } + } + + public function enterNode(Node $node) { + $nextCommentPos = current($this->commentPositions); + if ($nextCommentPos === false) { + // No more comments. + return self::STOP_TRAVERSAL; + } + + $oldPos = $this->pos; + $this->pos = $pos = $node->getStartTokenPos(); + if ($nextCommentPos > $oldPos && $nextCommentPos < $pos) { + $comments = []; + while (--$pos >= $oldPos) { + $token = $this->tokens[$pos]; + if ($token->id === \T_DOC_COMMENT) { + $comments[] = new Comment\Doc( + $token->text, $token->line, $token->pos, $pos, + $token->getEndLine(), $token->getEndPos() - 1, $pos); + continue; + } + if ($token->id === \T_COMMENT) { + $comments[] = new Comment( + $token->text, $token->line, $token->pos, $pos, + $token->getEndLine(), $token->getEndPos() - 1, $pos); + continue; + } + if ($token->id !== \T_WHITESPACE) { + break; + } + } + if (!empty($comments)) { + $node->setAttribute('comments', array_reverse($comments)); + } + + do { + $nextCommentPos = next($this->commentPositions); + } while ($nextCommentPos !== false && $nextCommentPos < $this->pos); + } + + $endPos = $node->getEndTokenPos(); + if ($nextCommentPos > $endPos) { + // Skip children if there are no comments located inside this node. + $this->pos = $endPos; + return self::DONT_TRAVERSE_CHILDREN; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php new file mode 100644 index 0000000..1f3f4ba --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php @@ -0,0 +1,47 @@ +filterCallback = $filterCallback; + } + + /** + * Get found nodes satisfying the filter callback. + * + * Nodes are returned in pre-order. + * + * @return Node[] Found nodes + */ + public function getFoundNodes(): array { + return $this->foundNodes; + } + + public function beforeTraverse(array $nodes): ?array { + $this->foundNodes = []; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNodes[] = $node; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php new file mode 100644 index 0000000..05deed5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php @@ -0,0 +1,49 @@ +filterCallback = $filterCallback; + } + + /** + * Get found node satisfying the filter callback. + * + * Returns null if no node satisfies the filter callback. + * + * @return null|Node Found node (or null if not found) + */ + public function getFoundNode(): ?Node { + return $this->foundNode; + } + + public function beforeTraverse(array $nodes): ?array { + $this->foundNode = null; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNode = $node; + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php new file mode 100644 index 0000000..ccd014e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php @@ -0,0 +1,262 @@ +nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing()); + $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false; + $this->replaceNodes = $options['replaceNodes'] ?? true; + } + + /** + * Get name resolution context. + */ + public function getNameContext(): NameContext { + return $this->nameContext; + } + + public function beforeTraverse(array $nodes): ?array { + $this->nameContext->startNamespace(); + return null; + } + + public function enterNode(Node $node) { + if ($node instanceof Stmt\Namespace_) { + $this->nameContext->startNamespace($node->name); + } elseif ($node instanceof Stmt\Use_) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, null); + } + } elseif ($node instanceof Stmt\GroupUse) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, $node->prefix); + } + } elseif ($node instanceof Stmt\Class_) { + if (null !== $node->extends) { + $node->extends = $this->resolveClassName($node->extends); + } + + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + if (null !== $node->name) { + $this->addNamespacedName($node); + } else { + $node->namespacedName = null; + } + } elseif ($node instanceof Stmt\Interface_) { + foreach ($node->extends as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Enum_) { + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Trait_) { + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Function_) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\ClassMethod + || $node instanceof Expr\Closure + || $node instanceof Expr\ArrowFunction + ) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Property) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Const_) { + foreach ($node->consts as $const) { + $this->addNamespacedName($const); + } + } elseif ($node instanceof Stmt\ClassConst) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\EnumCase) { + $this->resolveAttrGroups($node); + } elseif ($node instanceof Expr\StaticCall + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\ClassConstFetch + || $node instanceof Expr\New_ + || $node instanceof Expr\Instanceof_ + ) { + if ($node->class instanceof Name) { + $node->class = $this->resolveClassName($node->class); + } + } elseif ($node instanceof Stmt\Catch_) { + foreach ($node->types as &$type) { + $type = $this->resolveClassName($type); + } + } elseif ($node instanceof Expr\FuncCall) { + if ($node->name instanceof Name) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION); + } + } elseif ($node instanceof Expr\ConstFetch) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT); + } elseif ($node instanceof Stmt\TraitUse) { + foreach ($node->traits as &$trait) { + $trait = $this->resolveClassName($trait); + } + + foreach ($node->adaptations as $adaptation) { + if (null !== $adaptation->trait) { + $adaptation->trait = $this->resolveClassName($adaptation->trait); + } + + if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { + foreach ($adaptation->insteadof as &$insteadof) { + $insteadof = $this->resolveClassName($insteadof); + } + } + } + } + + return null; + } + + /** @param Stmt\Use_::TYPE_* $type */ + private function addAlias(Node\UseItem $use, int $type, ?Name $prefix = null): void { + // Add prefix for group uses + $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; + // Type is determined either by individual element or whole use declaration + $type |= $use->type; + + $this->nameContext->addAlias( + $name, (string) $use->getAlias(), $type, $use->getAttributes() + ); + } + + /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure|Expr\ArrowFunction $node */ + private function resolveSignature($node): void { + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + $this->resolveAttrGroups($param); + } + $node->returnType = $this->resolveType($node->returnType); + } + + /** + * @template T of Node\Identifier|Name|Node\ComplexType|null + * @param T $node + * @return T + */ + private function resolveType(?Node $node): ?Node { + if ($node instanceof Name) { + return $this->resolveClassName($node); + } + if ($node instanceof Node\NullableType) { + $node->type = $this->resolveType($node->type); + return $node; + } + if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { + foreach ($node->types as &$type) { + $type = $this->resolveType($type); + } + return $node; + } + return $node; + } + + /** + * Resolve name, according to name resolver options. + * + * @param Name $name Function or constant name to resolve + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name Resolved name, or original name with attribute + */ + protected function resolveName(Name $name, int $type): Name { + if (!$this->replaceNodes) { + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + $name->setAttribute('resolvedName', $resolvedName); + } else { + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + } + return $name; + } + + if ($this->preserveOriginalNames) { + // Save the original name + $originalName = $name; + $name = clone $originalName; + $name->setAttribute('originalName', $originalName); + } + + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + return $resolvedName; + } + + // unqualified names inside a namespace cannot be resolved at compile-time + // add the namespaced version of the name as an attribute + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + return $name; + } + + protected function resolveClassName(Name $name): Name { + return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); + } + + protected function addNamespacedName(Node $node): void { + $node->namespacedName = Name::concat( + $this->nameContext->getNamespace(), (string) $node->name); + } + + protected function resolveAttrGroups(Node $node): void { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attr->name = $this->resolveClassName($attr->name); + } + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php new file mode 100644 index 0000000..38fedfd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php @@ -0,0 +1,51 @@ +$node->getAttribute('parent'), the previous + * node can be accessed through $node->getAttribute('previous'), + * and the next node can be accessed through $node->getAttribute('next'). + */ +final class NodeConnectingVisitor extends NodeVisitorAbstract { + /** + * @var Node[] + */ + private array $stack = []; + + /** + * @var ?Node + */ + private $previous; + + public function beforeTraverse(array $nodes) { + $this->stack = []; + $this->previous = null; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) { + $node->setAttribute('previous', $this->previous); + $this->previous->setAttribute('next', $node); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + $this->previous = $node; + + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php new file mode 100644 index 0000000..1e7e9e8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php @@ -0,0 +1,38 @@ +$node->getAttribute('parent'). + */ +final class ParentConnectingVisitor extends NodeVisitorAbstract { + /** + * @var Node[] + */ + private array $stack = []; + + public function beforeTraverse(array $nodes) { + $this->stack = []; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php new file mode 100644 index 0000000..6fb15cc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php @@ -0,0 +1,24 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'.'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_READONLY", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_ENUM", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "T_ATTRIBUTE", + "';'", + "']'", + "'('", + "')'", + "'{'", + "'}'", + "'`'", + "'\"'", + "'$'" + ); + + protected array $tokenToSymbol = array( + 0, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 56, 166, 168, 167, 55, 168, 168, + 161, 162, 53, 50, 8, 51, 52, 54, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 31, 159, + 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 70, 168, 160, 36, 168, 165, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 163, 35, 164, 58, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, + 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, + 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158 + ); + + protected array $action = array( + 133, 134, 135, 586, 136, 137, 0, 755, 756, 757, + 138, 38,-32766,-32766,-32766, 151,-32766,-32766,-32766,-32767, + -32767,-32767,-32767, 102, 103, 104, 105, 106, 1116, 1117, + 1118, 1115, 1114, 1113, 1119, 749, 748,-32766,-32766,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, + -32767, 1252, 841,-32766, 1331, 758,-32766,-32766,-32766,-32766, + -599,-32766,-32766,-32766, 104, 105, 106, -599, 1315, 265, + 139, 406, 762, 763, 764, 765, 994,-32766, 431,-32766, + -32766, -16,-32766, 242, 1031, 819, 766, 767, 768, 769, + 770, 771, 772, 773, 774, 775, 795, 587, 796, 797, + 798, 799, 787, 788, 347, 348, 790, 791, 776, 777, + 778, 780, 781, 782, 358, 822, 823, 824, 825, 826, + 588, 783, 784, 589, 590,-32766, 807, 805, 806, 818, + 802, 803, 839, 830, 591, 592, 801, 593, 594, 595, + 596, 597, 598, 830, 461, 462, 463, 1040, 804, 599, + 600, 945, 140, 2, 133, 134, 135, 586, 136, 137, + 1064, 755, 756, 757, 138, 38, -328, -110, -110, 1335, + 291, 23, -110,-32766,-32766,-32766, 1334, 35, -110, 1116, + 1117, 1118, 1115, 1114, 1113, 1119, 616,-32766, 129, 749, + 748, 107, 108, 109,-32766, 275,-32766,-32766,-32766,-32766, + -32766,-32766,-32766, 832, 995, -194, 145, 110, 300, 758, + 840, 75,-32766,-32766,-32766, 1360, 142, 328, 1361, -599, + 328, -599, 253, 265, 139, 406, 762, 763, 764, 765, + 82, -272, 431,-32766, 328,-32766,-32766,-32766,-32766, 819, + 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, + 795, 587, 796, 797, 798, 799, 787, 788, 347, 348, + 790, 791, 776, 777, 778, 780, 781, 782, 358, 822, + 823, 824, 825, 826, 588, 783, 784, 589, 590, 834, + 807, 805, 806, 818, 802, 803, 716, 311, 591, 592, + 801, 593, 594, 595, 596, 597, 598, -78, 83, 84, + 85, -85, 804, 599, 600, 313, 149, 779, 750, 751, + 752, 753, 754, 729, 755, 756, 757, 792, 793, 37, + -328, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 325, 275, 485,-32766,-32766, + -32766, -58,-32766,-32766,-32766, 963, 964, 127, 110, -194, + 965, 341, 758,-32766,-32766,-32766, 959, -85, 292,-32766, + 1092,-32766,-32766,-32766,-32766,-32766, 759, 760, 761, 762, + 763, 764, 765, -193,-32766, 828,-32766,-32766,-32766, -367, + 431, -367, 819, 766, 767, 768, 769, 770, 771, 772, + 773, 774, 775, 795, 817, 796, 797, 798, 799, 787, + 788, 789, 816, 790, 791, 776, 777, 778, 780, 781, + 782, 821, 822, 823, 824, 825, 826, 827, 783, 784, + 785, 786, -552, 807, 805, 806, 818, 802, 803, 342, + 329, 794, 800, 801, 808, 809, 811, 810, 812, 813, + 1037, 866, 610, 867,-32766, 804, 815, 814, 50, 51, + 52, 516, 53, 54, 835, 1247, 1246, 1248, 55, 56, + -110, 57, 1040, 924, 1094, -110, 1040, -110, 292, 486, + 749, 748, 307, 384, 383, -110, -110, -110, -110, -110, + -110, -110, -110, 425, 924, 284, -552, -552, 372, 291, + 838, 924, 1252, 719, 470, 471, 58, 59,-32766,-32766, + 21, -550, 60, 560, 61, 247, 248, 62, 63, 64, + 65, 66, 67, 68, 69, -552, 28, 267, 70, 446, + 517, 720, 1108, -342, 1279, 1280, 518, -193, 839, 376, + 836, -548, 1277, 42, 25, 519, 391, 520, 241, 521, + 924, 522, 947, 1245, 523, 524, 914, 660, 26, 44, + 45, 447, 379, 378,-32766, 46, 525, 1027, 1026, 1025, + 1028, 370, 340, 442, 1285, -550, -550, 914, 1238, 947, + 527, 528, 529, 839, 914, 839, 1040, 443, 1350, 1243, + -550, 359, 531, 532, 444, 1266, 1267, 1268, 1269, 1263, + 1264, 299, -556, 445, -550, -548, -548, 1270, 1265, 291, + 1039, 1247, 1246, 1248, 300, 749, 748, 71, 364, 845, + -548, 323, 324, 328, -153, -153, -153, 152, 1247, 1246, + 1248, 926, -555, 914, -548, 714, 1063, 154,-32766, -153, + 1093, -153, 155, -153, 741, -153, 156, -596, 28, 268, + 36, 250, 926,-32766, -596, 377, 714, 679, 680, 926, + 839, 1273, 75, 714, 1277, 288, 963, 964, 328, -547, + 393, 526, 7, 1037, -57, 1040, 900, 959, -110, -110, + -110, 32, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 1040, 158, 382, 383, 866, + 1238, 867, 924, 749, 748, 1252, 33, 425, 926, 150, + 409, 924, 714, -153, 531, 532, -87, 1266, 1267, 1268, + 1269, 1263, 1264, 124, 1154, 1156, -84, -4, 924, 1270, + 1265, 125, 721, -547, -547, -546, 130, 749, 748, 73, + -32766, 724, 839, -78, 324, 328, 1245, 131, -547, 300, + -590, 1037, -590,-32766,-32766,-32766, 144,-32766, 159,-32766, + -554,-32766, -547, 160,-32766, 380, 381, 924, 161,-32766, + -32766,-32766, 162, 1040,-32766,-32766,-32766, 385, 386, 163, + 1245,-32766, 422, 651, 652, 914, 839,-32766,-32766,-32766, + -32766,-32766, -73,-32766, 914,-32766, 284, 731,-32766, -546, + -546, -72, 48,-32766,-32766,-32766, -596, -71, -596,-32766, + -32766, 914, -70, -69, -546,-32766, 422, -68, -67, -66, + 74, -110, -110, 141,-32766, -50, -110, 328, -546, -65, + -46, -18, -110, 377, 148, 438, 274, 285, 730, 733, + 298,-32766, 923, 147, 963, 964, 289, 290, -549, 526, + 914, -302, -298, 280, 530, 959, -110, -110, -110, 132, + 980, 281, 300, 941, 714, 75, 301, 302,-32766, 926, + 286, 328, 287, 714, 1245, 334, 293, 10, 294, 275, + 1362,-32766,-32766,-32766, 110,-32766, 926,-32766, 707,-32766, + 714, -4,-32766, 146, 830, 126, 689,-32766,-32766,-32766, + 705, 20,-32766,-32766,-32766, 924, 839, 682, 1245,-32766, + 422, 1123, -549, -549, 649,-32766,-32766,-32766,-32766,-32766, + 565,-32766, 661,-32766, 467, 926,-32766, -549,-32766, 714, + 666,-32766,-32766,-32766,-32766, 496, 667,-32766,-32766,-32766, + 1245, -549, 683,-32766, 422, 924, 571,-32766,-32766,-32766, + 838,-32766,-32766,-32766, 306,-32766, 735, 1278,-32766, 308, + 0, 960, 491,-32766,-32766,-32766,-32766, 0, 0,-32766, + -32766, 0, 1245, 578, 0,-32766, 422, -546, 305,-32766, + -32766,-32766, 312,-32766,-32766,-32766, 0,-32766, 914, 40, + -32766, 0, 0, 1284, 1286,-32766,-32766,-32766, -511, 0, + -501,-32766,-32766, 8, -250, -250, -250,-32766, 422, 614, + 377, 24, 49, 28, 267, 374,-32766, 943, 41, 300, + -275, 963, 964, 738, 739, 839, 526, 858, 914, 1277, + 905, 900, 959, -110, -110, -110, 1004, 981, 988, 978, + 989, -546, -546, 903, -249, -249, -249, 976, 28, 268, + 377, 1274, 288, 1097, 1100, 1101, -546, 1098, 1099, 1105, + 839, 963, 964, 926, 1277, 1238, 526, 714, -250, 850, + -546, 900, 959, -110, -110, -110, 303, 304, 1301, 1319, + 532, 1353, 1266, 1267, 1268, 1269, 1263, 1264, 654, -273, + -584, 375, -583, -582, 1270, 1265, -556, -555, -554, -553, + 1238, -495, 694, 926, 73, 128, 1, 714, -249, 324, + 328, 29, 30, 39, 43, 532, 47, 1266, 1267, 1268, + 1269, 1263, 1264, 72, 76, 77, 78, 79, 80, 1270, + 1265, 81, 143, 153,-32766, 157, 245, 330, 695, 73, + 1245, 359, 360, 361, 324, 328, 362,-32766,-32766,-32766, + 363,-32766, 364,-32766, 365,-32766, 366, 367,-32766, 696, + 697, 368, 369,-32766,-32766,-32766, 371, 439, 559,-32766, + -32766, -272, 13, 14, 15,-32766, 422, 1247, 1246, 1248, + 16, 18, 408, 284,-32766, 487, 488, 495, 498, 499, + 500, 501, 505, 506, 507, 514, 576, 700, 1256, 1194, + 1275, 1066, 1065, 1046, 1233, 1042, -277, -102, 12, 17, + 27, 297, 407, 607, 611, 640, 706, 1198, 1251, 1195, + 1332, 0, 34, 0, 322, 373, 715, 718, 722, 723, + 725, 726, 727, 728, 732, 717, 0, 901, 1357, 1359, + 861, 860, 869, 953, 996, 868, 1358, 952, 950, 951, + 954, 1226, 934, 944, 932, 986, 987, 638, 1356, 1313, + 1302, 1320, 1329, 0, 1211, 0, 0, 328 + ); + + protected array $actionCheck = array( + 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, + 12, 13, 9, 10, 11, 14, 9, 10, 11, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 116, 117, + 118, 119, 120, 121, 122, 37, 38, 30, 116, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 1, 1, 9, 1, 57, 9, 10, 11, 137, + 1, 9, 10, 11, 50, 51, 52, 8, 1, 71, + 72, 73, 74, 75, 76, 77, 31, 30, 80, 32, + 33, 31, 30, 14, 1, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 116, 128, 129, 130, 131, + 132, 133, 82, 80, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 80, 129, 130, 131, 138, 150, 151, + 152, 1, 154, 8, 2, 3, 4, 5, 6, 7, + 162, 9, 10, 11, 12, 13, 8, 117, 118, 1, + 161, 8, 122, 9, 10, 11, 8, 8, 128, 116, + 117, 118, 119, 120, 121, 122, 51, 137, 8, 37, + 38, 53, 54, 55, 30, 57, 32, 33, 34, 35, + 36, 37, 38, 80, 159, 8, 8, 69, 158, 57, + 159, 161, 9, 10, 11, 80, 163, 167, 83, 160, + 167, 162, 8, 71, 72, 73, 74, 75, 76, 77, + 163, 162, 80, 30, 167, 32, 33, 34, 35, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 156, + 128, 129, 130, 131, 132, 133, 163, 8, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 16, 9, 10, + 11, 31, 150, 151, 152, 8, 154, 2, 3, 4, + 5, 6, 7, 163, 9, 10, 11, 12, 13, 30, + 162, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 8, 57, 31, 9, 10, + 11, 16, 9, 10, 11, 117, 118, 14, 69, 162, + 122, 8, 57, 9, 10, 11, 128, 97, 30, 30, + 1, 32, 33, 34, 35, 36, 71, 72, 73, 74, + 75, 76, 77, 8, 30, 80, 32, 33, 34, 106, + 80, 108, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 70, 128, 129, 130, 131, 132, 133, 8, + 70, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 116, 106, 1, 108, 116, 150, 151, 152, 2, 3, + 4, 5, 6, 7, 80, 155, 156, 157, 12, 13, + 101, 15, 138, 1, 164, 106, 138, 108, 30, 163, + 37, 38, 113, 106, 107, 116, 117, 118, 119, 120, + 121, 122, 123, 116, 1, 161, 134, 135, 8, 161, + 155, 1, 1, 31, 134, 135, 50, 51, 9, 10, + 101, 70, 56, 85, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 163, 70, 71, 72, 73, + 74, 31, 123, 164, 78, 79, 80, 162, 82, 8, + 156, 70, 86, 87, 88, 89, 8, 91, 97, 93, + 1, 95, 122, 80, 98, 99, 84, 75, 76, 103, + 104, 105, 106, 107, 116, 109, 110, 119, 120, 121, + 122, 115, 116, 8, 146, 134, 135, 84, 122, 122, + 124, 125, 126, 82, 84, 82, 138, 8, 85, 116, + 149, 161, 136, 137, 8, 139, 140, 141, 142, 143, + 144, 145, 161, 8, 163, 134, 135, 151, 152, 161, + 137, 155, 156, 157, 158, 37, 38, 161, 161, 8, + 149, 165, 166, 167, 75, 76, 77, 14, 155, 156, + 157, 159, 161, 84, 163, 163, 1, 14, 137, 90, + 159, 92, 14, 94, 163, 96, 14, 1, 70, 71, + 147, 148, 159, 116, 8, 106, 163, 75, 76, 159, + 82, 1, 161, 163, 86, 30, 117, 118, 167, 70, + 106, 122, 108, 116, 16, 138, 127, 128, 129, 130, + 131, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 138, 14, 106, 107, 106, + 122, 108, 1, 37, 38, 1, 14, 116, 159, 101, + 102, 1, 163, 164, 136, 137, 31, 139, 140, 141, + 142, 143, 144, 16, 59, 60, 31, 0, 1, 151, + 152, 16, 31, 134, 135, 70, 16, 37, 38, 161, + 74, 31, 82, 31, 166, 167, 80, 16, 149, 158, + 160, 116, 162, 87, 88, 89, 16, 91, 16, 93, + 161, 95, 163, 16, 98, 106, 107, 1, 16, 103, + 104, 105, 16, 138, 74, 109, 110, 106, 107, 16, + 80, 115, 116, 111, 112, 84, 82, 87, 88, 89, + 124, 91, 31, 93, 84, 95, 161, 31, 98, 134, + 135, 31, 70, 103, 104, 105, 160, 31, 162, 109, + 110, 84, 31, 31, 149, 115, 116, 31, 31, 31, + 154, 117, 118, 163, 124, 31, 122, 167, 163, 31, + 31, 31, 128, 106, 31, 108, 31, 31, 31, 31, + 113, 137, 31, 31, 117, 118, 37, 37, 70, 122, + 84, 35, 35, 35, 127, 128, 129, 130, 131, 31, + 159, 35, 158, 38, 163, 161, 134, 135, 74, 159, + 35, 167, 35, 163, 80, 35, 37, 150, 37, 57, + 83, 87, 88, 89, 69, 91, 159, 93, 92, 95, + 163, 164, 98, 70, 80, 163, 77, 103, 104, 105, + 80, 97, 74, 109, 110, 1, 82, 94, 80, 115, + 116, 82, 134, 135, 113, 87, 88, 89, 124, 91, + 89, 93, 90, 95, 97, 159, 98, 149, 85, 163, + 96, 103, 104, 105, 74, 97, 100, 109, 110, 137, + 80, 163, 100, 115, 116, 1, 153, 87, 88, 89, + 155, 91, 124, 93, 133, 95, 164, 166, 98, 114, + -1, 128, 102, 103, 104, 105, 74, -1, -1, 109, + 110, -1, 80, 81, -1, 115, 116, 70, 132, 87, + 88, 89, 132, 91, 124, 93, -1, 95, 84, 159, + 98, -1, -1, 146, 146, 103, 104, 105, 149, -1, + 149, 109, 110, 149, 100, 101, 102, 115, 116, 153, + 106, 149, 70, 70, 71, 149, 124, 154, 159, 158, + 162, 117, 118, 159, 159, 82, 122, 159, 84, 86, + 159, 127, 128, 129, 130, 131, 159, 159, 159, 159, + 159, 134, 135, 159, 100, 101, 102, 159, 70, 71, + 106, 160, 30, 159, 159, 159, 149, 159, 159, 159, + 82, 117, 118, 159, 86, 122, 122, 163, 164, 160, + 163, 127, 128, 129, 130, 131, 134, 135, 160, 160, + 137, 160, 139, 140, 141, 142, 143, 144, 160, 162, + 161, 149, 161, 161, 151, 152, 161, 161, 161, 161, + 122, 161, 80, 159, 161, 163, 161, 163, 164, 166, + 167, 161, 161, 161, 161, 137, 161, 139, 140, 141, + 142, 143, 144, 161, 161, 161, 161, 161, 161, 151, + 152, 161, 161, 161, 74, 161, 161, 161, 116, 161, + 80, 161, 161, 161, 166, 167, 161, 87, 88, 89, + 161, 91, 161, 93, 161, 95, 161, 161, 98, 137, + 138, 161, 161, 103, 104, 105, 161, 161, 161, 109, + 110, 162, 162, 162, 162, 115, 116, 155, 156, 157, + 162, 162, 162, 161, 124, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, + 162, -1, 163, -1, 163, 163, 163, 163, 163, 163, + 163, 163, 163, 163, 163, 163, -1, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, -1, 165, -1, -1, 167 + ); + + protected array $actionBase = array( + 0, -2, 152, 549, 727, 904, 944, 1022, 660, 310, + 123, 899, 500, 710, 710, 766, 710, 472, 701, 820, + 63, 305, 305, 820, 305, 493, 493, 493, 666, 666, + 666, 666, 700, 700, 860, 860, 892, 828, 794, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 51, 45, 451, 692, 1049, 1055, + 1051, 1056, 1047, 1046, 1050, 1052, 1057, 1094, 1095, 812, + 1096, 1097, 1093, 1098, 1053, 928, 1048, 1054, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 44, 343, 499, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 52, 52, + 52, 578, 578, 47, 354, 978, 943, 978, 978, 978, + 978, 978, 978, 978, 978, 203, 665, 339, 164, 164, + 7, 7, 7, 7, 7, 50, 369, 704, 704, -25, + -25, -25, -25, 448, 635, 501, 409, 283, 338, 591, + 334, 334, 14, 14, 557, 557, 9, 9, 557, 557, + 557, 537, 537, 537, 537, 441, 471, 599, 345, 428, + 802, 53, 53, 53, 53, 802, 802, 802, 802, 848, + 791, 802, 802, 802, 778, 907, 907, 942, 138, 138, + 138, 907, 593, 503, 503, 593, 238, 503, 67, 135, + -78, 833, 377, 590, -78, 362, 732, 646, 59, 795, + 659, 795, 1045, 430, 843, 843, 457, 799, 761, 900, + 1072, 1058, 836, 1091, 842, 1092, 15, 370, 712, 1044, + 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, 1044, + 1100, 443, 1045, 384, 1100, 1100, 1100, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 672, 384, 482, + 582, 384, 840, 443, 51, 851, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 800, 316, 51, 45, + 150, 150, 481, 83, 150, 150, 150, 150, 51, 51, + 51, 51, 659, 822, 793, 671, 856, 375, 822, 822, + 822, 270, 158, 69, 197, 816, 817, 564, 814, 814, + 829, 945, 814, 824, 814, 829, 955, 814, 814, 945, + 945, 861, 945, 180, 565, 353, 531, 579, 945, 279, + 814, 814, 814, 814, 850, 945, 586, 814, 214, 198, + 814, 814, 850, 846, 806, 145, 821, 945, 945, 945, + 850, 490, 821, 821, 821, 864, 865, 801, 805, 337, + 297, 611, 169, 825, 805, 805, 814, 538, 801, 805, + 801, 805, 863, 805, 805, 805, 801, 805, 824, 431, + 805, 742, 595, 163, 805, 6, 962, 963, 685, 964, + 952, 965, 1006, 966, 967, 1063, 940, 975, 953, 970, + 1007, 951, 950, 811, 707, 715, 854, 849, 938, 815, + 815, 815, 935, 936, 815, 815, 815, 815, 815, 815, + 815, 815, 707, 891, 866, 831, 981, 720, 731, 1034, + 847, 1073, 1099, 980, 1036, 971, 830, 740, 1019, 982, + 792, 1061, 985, 989, 1020, 1037, 868, 1038, 1074, 823, + 1075, 1076, 909, 993, 1064, 815, 962, 967, 695, 953, + 970, 951, 950, 798, 788, 786, 787, 782, 781, 770, + 776, 803, 1039, 932, 929, 918, 991, 937, 707, 919, + 1010, 1059, 1023, 1024, 1062, 827, 797, 921, 1077, 995, + 996, 1000, 1065, 1040, 1066, 859, 1011, 858, 1025, 838, + 1078, 1026, 1027, 1028, 1029, 1067, 1079, 1068, 931, 1069, + 871, 832, 927, 834, 1080, 1, 835, 837, 841, 1005, + 613, 976, 1070, 1081, 1082, 1030, 1031, 1032, 1083, 1084, + 972, 877, 1012, 813, 1018, 1009, 878, 879, 623, 839, + 1041, 818, 826, 810, 628, 632, 1085, 1086, 1087, 974, + 807, 819, 880, 881, 1042, 809, 1043, 1088, 682, 884, + 747, 1089, 1035, 752, 756, 281, 658, 335, 763, 796, + 1071, 862, 845, 804, 1001, 756, 808, 888, 1090, 894, + 895, 896, 1033, 898, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 456, 456, 456, 456, 456, 456, + 305, 305, 305, 305, 305, 456, 456, 456, 456, 456, + 456, 456, 305, 305, 0, 0, 305, 0, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 473, 473, 289, 289, 473, 473, + 473, 473, 473, 473, 473, 473, 473, 473, 289, 0, + 289, 289, 289, 289, 289, 289, 289, 289, 473, 861, + 473, 473, 138, 138, 138, 138, 473, 473, 473, -88, + -88, 473, 238, 473, 473, 138, 138, 473, 473, 473, + 473, 473, 473, 473, 473, 473, 473, 473, 0, 0, + 0, 384, 503, 473, 824, 824, 824, 824, 473, 473, + 473, 473, 503, 503, 473, 473, 473, 0, 0, 0, + 0, 0, 0, 0, 0, 384, 0, 0, 384, 0, + 0, 824, 824, 473, 238, 861, 168, 473, 0, 0, + 0, 0, 384, 824, 384, 443, 814, 503, 503, 814, + 443, 443, 150, 51, 168, 608, 608, 608, 608, 0, + 0, 659, 861, 861, 861, 861, 861, 861, 861, 861, + 861, 861, 861, 824, 0, 861, 0, 824, 824, 824, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 824, 0, 0, 945, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 955, + 0, 0, 0, 0, 0, 0, 824, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 815, 827, 0, 827, + 0, 815, 815, 815, 0, 0, 0, 0, 839, 809 + ); + + protected array $actionDefault = array( + 3,32767, 102,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 100,32767,32767,32767,32767, 602, 602, + 602, 602,32767,32767, 254, 102,32767,32767, 470, 387, + 387, 387,32767,32767, 544, 544, 544, 544, 544, 544, + 32767,32767,32767,32767,32767,32767, 470,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 100, + 32767,32767,32767, 36, 7, 8, 10, 11, 49, 17, + 324,32767,32767,32767,32767, 102,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 595,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 474, 453, + 454, 456, 457, 386, 545, 601, 327, 598, 385, 145, + 339, 329, 242, 330, 258, 475, 259, 476, 479, 480, + 215, 287, 382, 149, 150, 417, 471, 419, 469, 473, + 418, 392, 398, 399, 400, 401, 402, 403, 404, 405, + 406, 407, 408, 409, 410, 390, 391, 472, 450, 449, + 448,32767,32767, 415, 416,32767,32767,32767,32767,32767, + 32767,32767,32767, 102,32767, 420, 389, 423, 421, 422, + 439, 440, 437, 438, 441,32767,32767,32767,32767, 442, + 443, 444, 445, 316,32767,32767, 366, 364, 316, 111, + 32767,32767, 430, 431,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 487, 538, 447,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 102,32767, 100, 540, 412, 414, 507, 425, 426, + 424, 393,32767, 514,32767, 102,32767, 516,32767,32767, + 32767,32767,32767,32767,32767, 539,32767, 546, 546,32767, + 500, 100, 195,32767,32767, 515,32767, 195, 195,32767, + 32767,32767,32767,32767,32767,32767,32767, 609, 500, 110, + 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, + 32767, 195, 110,32767,32767,32767, 100, 195, 195, 195, + 195, 195, 195, 195, 195, 195, 195, 190,32767, 268, + 270, 102, 563, 195,32767, 519,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 512,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 500, 435, 138,32767, 138, 546, 427, 428, + 429, 502, 546, 546, 546, 312, 289,32767,32767,32767, + 32767, 517, 100, 100, 100, 100, 512,32767,32767,32767, + 32767, 111, 486, 99, 99, 99, 99, 99, 103, 101, + 32767,32767,32767,32767, 223,32767, 99,32767, 101, 101, + 32767,32767, 223, 225, 212, 101, 227,32767, 567, 568, + 223, 101, 227, 227, 227, 247, 247, 489, 318, 101, + 99, 101, 101, 197, 318, 318,32767, 101, 489, 318, + 489, 318, 199, 318, 318, 318, 489, 318,32767, 101, + 318, 214, 99, 99, 318,32767,32767,32767, 502,32767, + 32767,32767,32767,32767,32767,32767, 222,32767,32767,32767, + 32767,32767,32767,32767,32767, 533,32767, 551, 565, 433, + 434, 436, 550, 548, 458, 459, 460, 461, 462, 463, + 464, 466, 597,32767, 506,32767,32767,32767, 338,32767, + 607,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 608,32767, + 546,32767,32767,32767,32767, 432, 9, 74, 495, 42, + 43, 51, 57, 523, 524, 525, 526, 520, 521, 527, + 522,32767,32767, 528, 573,32767,32767, 547, 600,32767, + 32767,32767,32767,32767,32767, 138,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 533,32767, 136, + 32767,32767,32767,32767,32767,32767,32767,32767, 529,32767, + 32767,32767, 546,32767,32767,32767,32767, 314, 311,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 546,32767,32767,32767,32767, + 32767, 291,32767, 308,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 286,32767,32767, 381, 502, 294, 296, 297,32767,32767, + 32767,32767, 360,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 152, 152, 3, 3, 341, 152, + 152, 152, 341, 341, 152, 341, 341, 341, 152, 152, + 152, 152, 152, 152, 280, 185, 262, 265, 247, 247, + 152, 352, 152 + ); + + protected array $goto = array( + 196, 196, 1038, 1069, 701, 353, 433, 665, 856, 710, + 427, 321, 315, 316, 337, 580, 432, 338, 434, 642, + 658, 659, 857, 676, 677, 678, 979, 167, 167, 167, + 167, 221, 197, 193, 193, 177, 179, 216, 193, 193, + 193, 193, 193, 194, 194, 194, 194, 194, 194, 188, + 189, 190, 191, 192, 218, 216, 219, 539, 540, 423, + 541, 544, 545, 546, 547, 548, 549, 550, 551, 1140, + 168, 169, 170, 195, 171, 172, 173, 166, 174, 175, + 176, 178, 215, 217, 220, 238, 243, 244, 255, 257, + 258, 259, 260, 261, 262, 263, 264, 269, 270, 271, + 272, 282, 283, 318, 319, 320, 428, 429, 430, 585, + 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 180, 237, 181, 198, 199, + 200, 239, 188, 189, 190, 191, 192, 218, 1140, 201, + 182, 183, 184, 202, 198, 185, 240, 203, 201, 165, + 204, 205, 186, 206, 207, 208, 187, 209, 210, 211, + 212, 213, 214, 859, 421, 1041, 1041, 625, 662, 685, + 956, 251, 251, 1033, 1049, 1050, 279, 279, 279, 279, + 344, 831, 852, 627, 627, 890, 604, 1276, 1276, 1276, + 1276, 1276, 1276, 1276, 1276, 1276, 1276, 351, 249, 249, + 249, 249, 246, 252, 345, 344, 577, 864, 460, 913, + 908, 909, 922, 865, 910, 862, 911, 912, 863, 469, + 469, 916, 897, 855, 897, 897, 357, 917, 469, 918, + 1336, 1091, 1086, 1087, 1088, 852, 357, 357, 613, 628, + 631, 632, 633, 634, 655, 656, 657, 712, 396, 698, + 357, 357, 833, 1000, 357, 441, 1363, 354, 355, 872, + 1244, 698, 1244, 1244, 426, 698, 615, 558, 1038, 1038, + 1244, 357, 357, 1038, 884, 1038, 1038, 871, 575, 1038, + 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, 1038, + 1328, 1328, 1328, 1328, 1137, 1244, 356, 356, 356, 356, + 1244, 1244, 1244, 1244, 1111, 1112, 1244, 1244, 1244, 1220, + 948, 563, 556, 1221, 1224, 949, 1225, 1062, 554, 1307, + 554, 554, 482, 603, 1104, 930, 713, 465, 554, 931, + 484, 5, 946, 6, 1189, 946, 511, 704, 664, 1102, + 690, 343, 556, 563, 572, 573, 346, 583, 606, 620, + 621, 1044, 1043, 458, 852, 1047, 1048, 22, 973, 973, + 973, 973, 327, 310, 458, 967, 974, 1295, 1295, 440, + 558, 1295, 1295, 1295, 1295, 1295, 1295, 1295, 1295, 1295, + 1295, 1292, 1292, 837, 686, 1292, 1292, 1292, 1292, 1292, + 1292, 1292, 1292, 1292, 1292, 543, 543, 1323, 1324, 543, + 543, 543, 543, 543, 543, 543, 543, 543, 543, 542, + 542, 254, 254, 542, 670, 542, 542, 542, 542, 542, + 542, 542, 542, 339, 837, 962, 837, 557, 567, 581, + 618, 557, 849, 567, 877, 1237, 399, 464, 451, 451, + 451, 451, 405, 1318, 619, 1318, 1318, 1239, 874, 472, + 584, 473, 474, 1318, 1235, 1075, 882, 570, 1022, 1354, + 1355, 737, 641, 643, 740, 1079, 663, 479, 1321, 1322, + 687, 691, 1014, 699, 708, 1010, 503, 886, 504, 1330, + 1330, 1330, 1330, 1122, 510, 880, 984, 410, 411, 0, + 1346, 1346, 674, 1261, 675, 0, 414, 415, 416, 0, + 688, 1240, 1241, 417, 0, 0, 1314, 349, 1346, 0, + 847, 885, 873, 1074, 1078, 552, 552, 552, 552, 0, + 608, 0, 0, 982, 0, 1349, 1349, 0, 0, 1242, + 1304, 1305, 451, 451, 451, 451, 451, 451, 451, 451, + 451, 451, 451, 935, 1127, 451, 0, 972, 1077, 0, + 623, 0, 1316, 1316, 1077, 0, 1019, 0, 326, 276, + 326, 326, 0, 0, 876, 0, 668, 998, 435, 1120, + 889, 0, 870, 435, 398, 401, 564, 605, 609, 0, + 1003, 1045, 1045, 975, 1234, 736, 669, 1056, 1052, 1053, + 971, 412, 709, 555, 1012, 1007, 635, 637, 639, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1017, 1017 + ); + + protected array $gotoCheck = array( + 42, 42, 73, 127, 73, 97, 66, 66, 26, 9, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 86, 86, 27, 86, 86, 86, 49, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 15, 43, 89, 89, 56, 56, 89, + 89, 5, 5, 89, 89, 89, 23, 23, 23, 23, + 170, 6, 22, 108, 108, 45, 130, 108, 108, 108, + 108, 108, 108, 108, 108, 108, 108, 181, 5, 5, + 5, 5, 5, 5, 170, 170, 174, 15, 83, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 149, + 149, 15, 25, 25, 25, 25, 14, 65, 149, 65, + 183, 15, 15, 15, 15, 22, 14, 14, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 62, 7, + 14, 14, 7, 103, 14, 83, 14, 97, 97, 35, + 73, 7, 73, 73, 13, 7, 13, 14, 73, 73, + 73, 14, 14, 73, 35, 73, 73, 35, 104, 73, + 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, + 9, 9, 9, 9, 150, 73, 24, 24, 24, 24, + 73, 73, 73, 73, 144, 144, 73, 73, 73, 79, + 79, 76, 76, 79, 79, 79, 79, 114, 19, 14, + 19, 19, 84, 8, 8, 73, 8, 151, 19, 73, + 84, 46, 9, 46, 151, 9, 8, 8, 64, 8, + 14, 76, 76, 76, 76, 76, 76, 76, 76, 76, + 76, 118, 118, 19, 22, 119, 119, 76, 19, 19, + 19, 19, 171, 171, 19, 19, 19, 172, 172, 113, + 14, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 173, 173, 12, 116, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 175, 175, 180, 180, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 158, + 158, 5, 5, 158, 120, 158, 158, 158, 158, 158, + 158, 158, 158, 29, 12, 92, 12, 9, 9, 2, + 2, 9, 18, 9, 39, 14, 9, 9, 23, 23, + 23, 23, 28, 130, 80, 130, 130, 20, 37, 9, + 9, 9, 9, 130, 162, 129, 9, 48, 110, 9, + 9, 48, 48, 48, 99, 132, 48, 178, 178, 178, + 48, 48, 48, 48, 48, 48, 155, 41, 155, 130, + 130, 130, 130, 147, 155, 9, 96, 82, 82, -1, + 184, 184, 82, 20, 82, -1, 82, 82, 82, -1, + 82, 20, 20, 82, -1, -1, 130, 82, 184, -1, + 20, 16, 16, 16, 16, 107, 107, 107, 107, -1, + 107, -1, -1, 16, -1, 184, 184, -1, -1, 20, + 20, 20, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 17, 17, 23, -1, 16, 130, -1, + 17, -1, 130, 130, 130, -1, 17, -1, 24, 24, + 24, 24, -1, -1, 17, -1, 17, 17, 117, 16, + 16, -1, 17, 117, 59, 59, 59, 59, 59, -1, + 50, 117, 117, 50, 17, 50, 117, 117, 117, 117, + 93, 93, 93, 50, 50, 50, 85, 85, 85, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 107, 107 + ); + + protected array $gotoBase = array( + 0, 0, -287, 0, 0, 170, 161, 242, 315, -11, + 0, 0, 85, -75, -73, -187, 57, 75, 121, 53, + 52, 0, -97, 173, 293, 219, 4, 18, 103, 125, + 0, 0, 0, 0, 0, -114, 0, 107, 0, 109, + 0, 35, -1, 145, 0, 162, -409, 0, -258, 8, + 568, 0, 0, 0, 0, 0, 127, 0, 0, 529, + 0, 0, 206, 0, 96, 213, -235, 0, 0, 0, + 0, 0, 0, -5, 0, 0, -36, 0, 0, -101, + 98, -122, -7, -71, -150, 114, -702, 0, 0, -115, + 0, 0, 94, 284, 0, 0, 42, -481, 0, 55, + 0, 0, 0, 218, 235, 0, 0, 487, -58, 0, + 86, 0, 0, 91, 43, 0, 100, 295, 71, 69, + 123, 0, 0, 0, 0, 0, 0, 1, 0, 79, + 178, 0, 22, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 28, 0, 0, 38, 0, 185, + 48, 59, 0, 0, 0, -22, 0, 0, 168, 0, + 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, + -119, 39, 126, 140, 177, 154, 0, 0, 165, 0, + 23, 167, 0, 199, 181, 0, 0 + ); + + protected array $gotoDefault = array( + -32768, 515, 744, 4, 745, 939, 820, 829, 601, 533, + 711, 350, 629, 424, 1312, 915, 1126, 582, 848, 1253, + 1227, 459, 851, 332, 734, 927, 898, 899, 402, 388, + 394, 400, 653, 630, 497, 883, 455, 875, 489, 878, + 454, 887, 164, 420, 513, 891, 3, 894, 561, 925, + 977, 389, 902, 390, 681, 904, 566, 906, 907, 397, + 403, 404, 1131, 574, 626, 919, 256, 568, 920, 387, + 921, 929, 392, 395, 692, 468, 508, 502, 413, 1106, + 569, 612, 650, 448, 476, 624, 636, 622, 483, 436, + 418, 331, 961, 969, 490, 466, 983, 352, 991, 742, + 1139, 644, 492, 999, 645, 1006, 1009, 534, 535, 481, + 1021, 273, 1024, 493, 19, 671, 1035, 1036, 672, 646, + 1058, 647, 673, 648, 1060, 475, 602, 1068, 456, 1076, + 1300, 457, 1080, 266, 1083, 278, 419, 437, 1089, 1090, + 9, 1096, 702, 703, 11, 277, 512, 1121, 693, 453, + 1138, 452, 1208, 1210, 562, 494, 1228, 480, 295, 1231, + 684, 509, 1236, 449, 1303, 450, 536, 477, 317, 537, + 1347, 309, 335, 314, 553, 296, 336, 538, 478, 1309, + 1317, 333, 31, 1337, 1348, 579, 617 + ); + + protected array $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, + 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, + 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 29, 29, 30, 30, 32, 34, 34, + 28, 36, 36, 33, 38, 38, 35, 35, 37, 37, + 39, 39, 31, 40, 40, 41, 43, 44, 44, 45, + 45, 46, 46, 48, 47, 47, 47, 47, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 25, 25, 50, 69, 69, 72, 72, 71, + 70, 70, 63, 75, 75, 76, 76, 77, 77, 78, + 78, 79, 79, 80, 80, 26, 26, 27, 27, 27, + 27, 27, 88, 88, 90, 90, 83, 83, 91, 91, + 92, 92, 92, 84, 84, 87, 87, 85, 85, 93, + 94, 94, 57, 57, 65, 65, 68, 68, 68, 67, + 95, 95, 96, 58, 58, 58, 58, 97, 97, 98, + 98, 99, 99, 100, 101, 101, 102, 102, 103, 103, + 55, 55, 51, 51, 105, 53, 53, 106, 52, 52, + 54, 54, 64, 64, 64, 64, 81, 81, 109, 109, + 111, 111, 112, 112, 112, 112, 110, 110, 110, 114, + 114, 114, 114, 89, 89, 117, 117, 117, 118, 118, + 115, 115, 119, 119, 121, 121, 122, 122, 116, 123, + 123, 120, 124, 124, 124, 124, 113, 113, 82, 82, + 82, 20, 20, 20, 126, 125, 125, 127, 127, 127, + 127, 60, 128, 128, 129, 61, 131, 131, 132, 132, + 133, 133, 86, 134, 134, 134, 134, 134, 134, 134, + 139, 139, 140, 140, 141, 141, 141, 141, 141, 142, + 143, 143, 138, 138, 135, 135, 137, 137, 145, 145, + 144, 144, 144, 144, 144, 144, 144, 136, 146, 146, + 148, 147, 147, 62, 104, 149, 149, 56, 56, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 156, 158, 158, 159, 150, 150, 155, + 155, 160, 161, 161, 162, 163, 164, 164, 164, 164, + 19, 19, 73, 73, 73, 73, 151, 151, 151, 151, + 166, 166, 152, 152, 154, 154, 154, 157, 157, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 173, 173, + 173, 108, 175, 175, 175, 175, 153, 153, 153, 153, + 153, 153, 153, 153, 59, 59, 169, 169, 169, 169, + 169, 176, 176, 165, 165, 165, 165, 177, 177, 177, + 177, 177, 177, 74, 74, 66, 66, 66, 66, 130, + 130, 130, 130, 180, 179, 168, 168, 168, 168, 168, + 168, 168, 167, 167, 167, 178, 178, 178, 178, 107, + 174, 182, 182, 181, 181, 183, 183, 183, 183, 183, + 183, 183, 183, 171, 171, 171, 171, 170, 185, 184, + 184, 184, 184, 184, 184, 184, 184, 186, 186, 186, + 186 + ); + + protected array $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, + 0, 1, 1, 1, 1, 4, 3, 5, 4, 3, + 4, 2, 3, 1, 1, 7, 6, 2, 3, 1, + 2, 3, 1, 2, 3, 1, 1, 3, 1, 3, + 1, 2, 2, 3, 1, 3, 2, 3, 1, 3, + 3, 2, 0, 1, 1, 1, 1, 1, 3, 7, + 10, 5, 7, 9, 5, 3, 3, 3, 3, 3, + 3, 1, 2, 5, 7, 9, 6, 5, 6, 3, + 2, 1, 1, 1, 1, 0, 2, 1, 3, 8, + 0, 4, 2, 1, 3, 0, 1, 0, 1, 0, + 1, 3, 1, 1, 1, 8, 9, 7, 8, 7, + 6, 8, 0, 2, 0, 2, 1, 2, 1, 2, + 1, 1, 1, 0, 2, 0, 2, 0, 2, 2, + 1, 3, 1, 4, 1, 4, 1, 1, 4, 2, + 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, + 3, 1, 1, 7, 0, 2, 1, 3, 3, 4, + 1, 4, 0, 2, 5, 0, 2, 6, 0, 2, + 0, 3, 1, 2, 1, 1, 2, 0, 1, 3, + 0, 2, 1, 1, 1, 1, 6, 8, 6, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 3, 3, 1, 3, 3, 3, 3, 3, 1, 3, + 3, 1, 1, 2, 1, 1, 0, 1, 0, 2, + 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, + 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, + 2, 0, 1, 5, 5, 6, 10, 3, 5, 1, + 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, + 1, 1, 3, 2, 2, 3, 1, 0, 1, 1, + 3, 3, 3, 4, 4, 1, 1, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, + 3, 4, 4, 2, 2, 4, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, + 1, 2, 4, 2, 2, 8, 9, 8, 9, 9, + 10, 9, 10, 8, 3, 2, 2, 1, 1, 0, + 4, 2, 1, 3, 2, 1, 2, 2, 2, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, + 1, 1, 0, 3, 0, 1, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, + 3, 3, 4, 1, 1, 3, 1, 1, 1, 1, + 1, 3, 2, 3, 0, 1, 1, 3, 1, 1, + 1, 1, 1, 1, 3, 1, 1, 1, 4, 4, + 1, 4, 4, 0, 1, 1, 1, 3, 3, 1, + 4, 2, 2, 1, 3, 1, 4, 4, 3, 3, + 3, 3, 1, 3, 1, 1, 3, 1, 1, 4, + 1, 1, 1, 3, 1, 1, 2, 1, 3, 4, + 3, 2, 0, 2, 2, 1, 2, 1, 1, 1, + 4, 3, 3, 3, 3, 6, 3, 1, 1, 2, + 1 + ); + + protected function initReduceCallbacks(): void { + $this->reduceCallbacks = [ + 0 => null, + 1 => static function ($self, $stackPos) { + $self->semValue = $self->handleNamespaces($self->semStack[$stackPos-(1-1)]); + }, + 2 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 3 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 4 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 5 => null, + 6 => null, + 7 => null, + 8 => null, + 9 => null, + 10 => null, + 11 => null, + 12 => null, + 13 => null, + 14 => null, + 15 => null, + 16 => null, + 17 => null, + 18 => null, + 19 => null, + 20 => null, + 21 => null, + 22 => null, + 23 => null, + 24 => null, + 25 => null, + 26 => null, + 27 => null, + 28 => null, + 29 => null, + 30 => null, + 31 => null, + 32 => null, + 33 => null, + 34 => null, + 35 => null, + 36 => null, + 37 => null, + 38 => null, + 39 => null, + 40 => null, + 41 => null, + 42 => null, + 43 => null, + 44 => null, + 45 => null, + 46 => null, + 47 => null, + 48 => null, + 49 => null, + 50 => null, + 51 => null, + 52 => null, + 53 => null, + 54 => null, + 55 => null, + 56 => null, + 57 => null, + 58 => null, + 59 => null, + 60 => null, + 61 => null, + 62 => null, + 63 => null, + 64 => null, + 65 => null, + 66 => null, + 67 => null, + 68 => null, + 69 => null, + 70 => null, + 71 => null, + 72 => null, + 73 => null, + 74 => null, + 75 => null, + 76 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; if ($self->semValue === "emitError(new Error('Cannot use "getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 77 => null, + 78 => null, + 79 => null, + 80 => null, + 81 => null, + 82 => null, + 83 => null, + 84 => null, + 85 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 86 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 87 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 88 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 89 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 90 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 91 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 92 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 93 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 94 => null, + 95 => static function ($self, $stackPos) { + $self->semValue = new Name(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 96 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 97 => static function ($self, $stackPos) { + /* nothing */ + }, + 98 => static function ($self, $stackPos) { + /* nothing */ + }, + 99 => static function ($self, $stackPos) { + /* nothing */ + }, + 100 => static function ($self, $stackPos) { + $self->emitError(new Error('A trailing comma is not allowed here', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 101 => null, + 102 => null, + 103 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(1-1)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 104 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 105 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 106 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 107 => static function ($self, $stackPos) { + $self->semValue = new Node\AttributeGroup($self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 108 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 109 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 110 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 111 => null, + 112 => null, + 113 => null, + 114 => null, + 115 => static function ($self, $stackPos) { + $self->semValue = new Stmt\HaltCompiler($self->handleHaltCompiler(), $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 116 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(3-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $self->checkNamespace($self->semValue); + }, + 117 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 118 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_(null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 119 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 120 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 121 => null, + 122 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 123 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 124 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 125 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 126 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 127 => null, + 128 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 129 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 130 => null, + 131 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 132 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 133 => null, + 134 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 135 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 136 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 137 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 138 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 139 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 140 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 141 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; $self->semValue->type = $self->semStack[$stackPos-(2-1)]; + }, + 142 => null, + 143 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 144 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 145 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 146 => null, + 147 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 148 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 149 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 150 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 151 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 152 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 153 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 154 => null, + 155 => null, + 156 => null, + 157 => static function ($self, $stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 158 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Block($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 159 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(7-3)], ['stmts' => $self->semStack[$stackPos-(7-5)], 'elseifs' => $self->semStack[$stackPos-(7-6)], 'else' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 160 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(10-3)], ['stmts' => $self->semStack[$stackPos-(10-6)], 'elseifs' => $self->semStack[$stackPos-(10-7)], 'else' => $self->semStack[$stackPos-(10-8)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 161 => static function ($self, $stackPos) { + $self->semValue = new Stmt\While_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 162 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Do_($self->semStack[$stackPos-(7-5)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 163 => static function ($self, $stackPos) { + $self->semValue = new Stmt\For_(['init' => $self->semStack[$stackPos-(9-3)], 'cond' => $self->semStack[$stackPos-(9-5)], 'loop' => $self->semStack[$stackPos-(9-7)], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 164 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Switch_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 165 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Break_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 166 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Continue_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 167 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Return_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 168 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Global_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 169 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Static_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 170 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Echo_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 171 => static function ($self, $stackPos) { + + $self->semValue = new Stmt\InlineHTML($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('hasLeadingNewline', $self->inlineHtmlHasLeadingNewline($stackPos-(1-1))); + + }, + 172 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Expression($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 173 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Unset_($self->semStack[$stackPos-(5-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 174 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $self->semStack[$stackPos-(7-5)][1], 'stmts' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 175 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-7)][0], ['keyVar' => $self->semStack[$stackPos-(9-5)], 'byRef' => $self->semStack[$stackPos-(9-7)][1], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 176 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(6-3)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-4)], $self->tokenEndStack[$stackPos-(6-4)])), ['stmts' => $self->semStack[$stackPos-(6-6)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 177 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Declare_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 178 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TryCatch($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->checkTryCatch($self->semValue); + }, + 179 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Goto_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 180 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Label($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 181 => static function ($self, $stackPos) { + $self->semValue = null; /* means: no statement */ + }, + 182 => null, + 183 => static function ($self, $stackPos) { + $self->semValue = $self->maybeCreateNop($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); + }, + 184 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 185 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 186 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 187 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 188 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 189 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Catch_($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-7)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 190 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 191 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Finally_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 192 => null, + 193 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 194 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 195 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 196 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 197 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 198 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 199 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 200 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 201 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 202 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 203 => null, + 204 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 205 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(8-3)], ['byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-5)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 206 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(9-4)], ['byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-6)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 207 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(7-2)], ['type' => $self->semStack[$stackPos-(7-1)], 'extends' => $self->semStack[$stackPos-(7-3)], 'implements' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(7-2)); + }, + 208 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(8-3)], ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(8-3)); + }, + 209 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Interface_($self->semStack[$stackPos-(7-3)], ['extends' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => $self->semStack[$stackPos-(7-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkInterface($self->semValue, $stackPos-(7-3)); + }, + 210 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Trait_($self->semStack[$stackPos-(6-3)], ['stmts' => $self->semStack[$stackPos-(6-5)], 'attrGroups' => $self->semStack[$stackPos-(6-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 211 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Enum_($self->semStack[$stackPos-(8-3)], ['scalarType' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkEnum($self->semValue, $stackPos-(8-3)); + }, + 212 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 213 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 214 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 215 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 216 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 217 => null, + 218 => null, + 219 => static function ($self, $stackPos) { + $self->checkClassModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 220 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 221 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 222 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 223 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 224 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 225 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 226 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 227 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 228 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 229 => null, + 230 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 231 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 232 => null, + 233 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 234 => null, + 235 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 236 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 237 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 238 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 239 => null, + 240 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 241 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 242 => static function ($self, $stackPos) { + $self->semValue = new Node\DeclareItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 243 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 244 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 245 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 246 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(5-3)]; + }, + 247 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 248 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 249 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_($self->semStack[$stackPos-(4-2)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 250 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_(null, $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 251 => null, + 252 => null, + 253 => static function ($self, $stackPos) { + $self->semValue = new Expr\Match_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 254 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 255 => null, + 256 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 257 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 258 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 259 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm(null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 260 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 261 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 262 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 263 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 264 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 265 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 266 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 267 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 268 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 269 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 270 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 271 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 272 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 273 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-2)], true); + }, + 274 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 275 => static function ($self, $stackPos) { + $self->semValue = array($self->fixupArrayDestructuring($self->semStack[$stackPos-(1-1)]), false); + }, + 276 => null, + 277 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 278 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 279 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 280 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 281 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 282 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 283 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 284 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 285 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 286 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(6-6)], null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + $self->checkParam($self->semValue); + }, + 287 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(8-6)], $self->semStack[$stackPos-(8-8)], $self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(8-2)], $self->semStack[$stackPos-(8-1)]); + $self->checkParam($self->semValue); + }, + 288 => static function ($self, $stackPos) { + $self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + }, + 289 => null, + 290 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 291 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 292 => null, + 293 => null, + 294 => static function ($self, $stackPos) { + $self->semValue = new Node\Name('static', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 295 => static function ($self, $stackPos) { + $self->semValue = $self->handleBuiltinTypes($self->semStack[$stackPos-(1-1)]); + }, + 296 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('array', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 297 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('callable', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 298 => null, + 299 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 300 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 301 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 302 => null, + 303 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 304 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 305 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 306 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 307 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 308 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 309 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 310 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 311 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 312 => null, + 313 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 314 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 315 => null, + 316 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 317 => null, + 318 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 319 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 320 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 321 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 322 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 323 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 324 => static function ($self, $stackPos) { + $self->semValue = new Node\VariadicPlaceholder($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 325 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 326 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 327 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(1-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 328 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], true, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 329 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], false, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 330 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(3-3)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(3-1)]); + }, + 331 => null, + 332 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 333 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 334 => null, + 335 => null, + 336 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 337 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 338 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 339 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 340 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; } else { $self->semValue = $self->semStack[$stackPos-(2-1)]; } + }, + 341 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 342 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 343 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Property($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-1)]); + $self->checkProperty($self->semValue, $stackPos-(5-2)); + }, + 344 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-1)]); + $self->checkClassConst($self->semValue, $stackPos-(5-2)); + }, + 345 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-1)], $self->semStack[$stackPos-(6-4)]); + $self->checkClassConst($self->semValue, $stackPos-(6-2)); + }, + 346 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassMethod($self->semStack[$stackPos-(10-5)], ['type' => $self->semStack[$stackPos-(10-2)], 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-7)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + $self->checkClassMethod($self->semValue, $stackPos-(10-2)); + }, + 347 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUse($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 348 => static function ($self, $stackPos) { + $self->semValue = new Stmt\EnumCase($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 349 => static function ($self, $stackPos) { + $self->semValue = null; /* will be skipped */ + }, + 350 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 351 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 352 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 353 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 354 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Precedence($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 355 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(5-1)][0], $self->semStack[$stackPos-(5-1)][1], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 356 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 357 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 358 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 359 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 360 => null, + 361 => static function ($self, $stackPos) { + $self->semValue = array(null, $self->semStack[$stackPos-(1-1)]); + }, + 362 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 363 => null, + 364 => null, + 365 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 366 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 367 => null, + 368 => null, + 369 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 370 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 371 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 372 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 373 => static function ($self, $stackPos) { + $self->semValue = Modifiers::STATIC; + }, + 374 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 375 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 376 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 377 => null, + 378 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 379 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 380 => static function ($self, $stackPos) { + $self->semValue = new Node\VarLikeIdentifier(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 381 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 382 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 383 => null, + 384 => null, + 385 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 386 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 387 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 388 => null, + 389 => null, + 390 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 391 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->fixupArrayDestructuring($self->semStack[$stackPos-(3-1)]), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 392 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 393 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 394 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + if (!$self->phpVersion->allowsAssignNewByReference()) { + $self->emitError(new Error('Cannot assign new by reference', $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + } + + }, + 395 => null, + 396 => null, + 397 => static function ($self, $stackPos) { + $self->semValue = new Expr\Clone_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 398 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 399 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 400 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 401 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 402 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 403 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 404 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 405 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 406 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 407 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 408 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 409 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 410 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 411 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostInc($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 412 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreInc($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 413 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostDec($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 414 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreDec($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 415 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 416 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 417 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 418 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 419 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 420 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 421 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 422 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 423 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 424 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 425 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 426 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 427 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 428 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 429 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 430 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 431 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 432 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 433 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryPlus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 434 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryMinus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 435 => static function ($self, $stackPos) { + $self->semValue = new Expr\BooleanNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 436 => static function ($self, $stackPos) { + $self->semValue = new Expr\BitwiseNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 437 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Identical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 438 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotIdentical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 439 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Equal($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 440 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 441 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Spaceship($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 442 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Smaller($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 443 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\SmallerOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 444 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Greater($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 445 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\GreaterOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 446 => static function ($self, $stackPos) { + $self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 447 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 448 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 449 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(4-1)], null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 450 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 451 => static function ($self, $stackPos) { + $self->semValue = new Expr\Isset_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 452 => static function ($self, $stackPos) { + $self->semValue = new Expr\Empty_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 453 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 454 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 455 => static function ($self, $stackPos) { + $self->semValue = new Expr\Eval_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 456 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 457 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 458 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 459 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getFloatCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); + }, + 460 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 461 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 462 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 463 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 464 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 465 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = strtolower($self->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $self->semValue = new Expr\Exit_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 466 => static function ($self, $stackPos) { + $self->semValue = new Expr\ErrorSuppress($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 467 => null, + 468 => static function ($self, $stackPos) { + $self->semValue = new Expr\ShellExec($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 469 => static function ($self, $stackPos) { + $self->semValue = new Expr\Print_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 470 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_(null, null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 471 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(2-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 472 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 473 => static function ($self, $stackPos) { + $self->semValue = new Expr\YieldFrom($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 474 => static function ($self, $stackPos) { + $self->semValue = new Expr\Throw_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 475 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'returnType' => $self->semStack[$stackPos-(8-6)], 'expr' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 476 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 477 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'uses' => $self->semStack[$stackPos-(8-6)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 478 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 479 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 480 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'returnType' => $self->semStack[$stackPos-(10-8)], 'expr' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 481 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 482 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'uses' => $self->semStack[$stackPos-(10-8)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 483 => static function ($self, $stackPos) { + $self->semValue = array(new Stmt\Class_(null, ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])), $self->semStack[$stackPos-(8-3)]); + $self->checkClass($self->semValue[0], -1); + }, + 484 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 485 => static function ($self, $stackPos) { + list($class, $ctorArgs) = $self->semStack[$stackPos-(2-2)]; $self->semValue = new Expr\New_($class, $ctorArgs, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 486 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(2-2)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 487 => null, + 488 => null, + 489 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 490 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 491 => null, + 492 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 493 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 494 => static function ($self, $stackPos) { + $self->semValue = new Node\ClosureUse($self->semStack[$stackPos-(2-2)], $self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 495 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 496 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 497 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 498 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 499 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 500 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 501 => null, + 502 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 503 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 504 => static function ($self, $stackPos) { + $self->semValue = new Name\FullyQualified(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 505 => static function ($self, $stackPos) { + $self->semValue = new Name\Relative(substr($self->semStack[$stackPos-(1-1)], 10), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 506 => null, + 507 => null, + 508 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 509 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 510 => null, + 511 => null, + 512 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 513 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 514 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 515 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); foreach ($self->semValue as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; + }, + 516 => static function ($self, $stackPos) { + foreach ($self->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 517 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 518 => null, + 519 => static function ($self, $stackPos) { + $self->semValue = new Expr\ConstFetch($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 520 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Line($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 521 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\File($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 522 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Dir($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 523 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Class_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 524 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Trait_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 525 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Method($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 526 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Function_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 527 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Namespace_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 528 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 529 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 530 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)])), $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 531 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(3-2)], $attrs); + }, + 532 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); + $self->createdArrays->attach($self->semValue); + }, + 533 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue); + }, + 534 => static function ($self, $stackPos) { + $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); + }, + 535 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($self->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = new Scalar\InterpolatedString($self->semStack[$stackPos-(3-2)], $attrs); + }, + 536 => static function ($self, $stackPos) { + $self->semValue = $self->parseLNumber($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->allowsInvalidOctals()); + }, + 537 => static function ($self, $stackPos) { + $self->semValue = Scalar\Float_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 538 => null, + 539 => null, + 540 => null, + 541 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 542 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(2-1)], '', $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(2-2)], $self->tokenEndStack[$stackPos-(2-2)]), true); + }, + 543 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 544 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 545 => null, + 546 => null, + 547 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 548 => null, + 549 => null, + 550 => null, + 551 => null, + 552 => null, + 553 => null, + 554 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 555 => null, + 556 => null, + 557 => null, + 558 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 559 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 560 => null, + 561 => static function ($self, $stackPos) { + $self->semValue = new Expr\MethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 562 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafeMethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 563 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 564 => null, + 565 => null, + 566 => null, + 567 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 568 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 569 => null, + 570 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 571 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 572 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])), $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 573 => static function ($self, $stackPos) { + $var = $self->semStack[$stackPos-(1-1)]->name; $self->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])) : $var; + }, + 574 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 575 => null, + 576 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 577 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 578 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 579 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 580 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 581 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 582 => null, + 583 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 584 => null, + 585 => null, + 586 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 587 => null, + 588 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 589 => static function ($self, $stackPos) { + $self->semValue = new Expr\List_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); $self->semValue->setAttribute('kind', Expr\List_::KIND_LIST); + $self->postprocessList($self->semValue); + }, + 590 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $end = count($self->semValue)-1; if ($self->semValue[$end]->value instanceof Expr\Error) array_pop($self->semValue); + }, + 591 => null, + 592 => static function ($self, $stackPos) { + /* do nothing -- prevent default action of $$=$self->semStack[$1]. See $551. */ + }, + 593 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 594 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 595 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 596 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 597 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 598 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 599 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-1)], true, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 600 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 601 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), true); + }, + 602 => static function ($self, $stackPos) { + /* Create an Error node now to remember the position. We'll later either report an error, + or convert this into a null element, depending on whether this is a creation or destructuring context. */ + $attrs = $self->createEmptyElemAttributes($self->tokenPos); + $self->semValue = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); + }, + 603 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 604 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 605 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 606 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)]); + }, + 607 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); $attrs['rawValue'] = $self->semStack[$stackPos-(1-1)]; $self->semValue = new Node\InterpolatedStringPart($self->semStack[$stackPos-(1-1)], $attrs); + }, + 608 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 609 => null, + 610 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 611 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 612 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 613 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 614 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 615 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 616 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 617 => static function ($self, $stackPos) { + $self->semValue = new Scalar\String_($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 618 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 619 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString('-' . $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 620 => null, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php new file mode 100644 index 0000000..1317c54 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php @@ -0,0 +1,2737 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "'.'", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_READONLY", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_ENUM", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "T_ATTRIBUTE", + "';'", + "']'", + "'('", + "')'", + "'{'", + "'}'", + "'`'", + "'\"'", + "'$'" + ); + + protected array $tokenToSymbol = array( + 0, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 56, 166, 168, 167, 55, 168, 168, + 161, 162, 53, 51, 8, 52, 48, 54, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 31, 159, + 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 70, 168, 160, 36, 168, 165, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 163, 35, 164, 58, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, + 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, + 41, 42, 43, 45, 47, 49, 50, 57, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158 + ); + + protected array $action = array( + 133, 134, 135, 586, 136, 137, 0, 755, 756, 757, + 138, 38, 329,-32766,-32766,-32766,-32766,-32766,-32766, 841, + 830,-32767,-32767,-32767,-32767, 102, 103, 104, 1116, 1117, + 1118, 1115, 1114, 1113, 1119, 749, 748,-32766, 1031,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, + -32767, 1252,-32766,-32766, 1331, 758, 1116, 1117, 1118, 1115, + 1114, 1113, 1119, 461, 462, 463, 2, 994, 1315, 265, + 139, 406, 762, 763, 764, 765, 470, 471, 431, 839, + 610, -16, 1350, 23, 293, 819, 766, 767, 768, 769, + 770, 771, 772, 773, 774, 775, 795, 587, 796, 797, + 798, 799, 787, 788, 347, 348, 790, 791, 776, 777, + 778, 780, 781, 782, 358, 822, 823, 824, 825, 826, + 588, 783, 784, 589, 590, 945, 807, 805, 806, 818, + 802, 803, 839, 830, 591, 592, 801, 593, 594, 595, + 596, 597, 598, -328, 36, 250, 35, -194, 804, 599, + 600, -193, 140, -85, 133, 134, 135, 586, 136, 137, + 1064, 755, 756, 757, 138, 38, 129, -110, -110, -590, + -32766, -590, -110,-32766,-32766,-32766, 241, 840, -110, 145, + 963, 964,-32766,-32766,-32766, 965, -599,-32766, 485, 749, + 748, 959, 1040, -599,-32766, 995,-32766,-32766,-32766,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766, 301, 758, + 835, 75,-32766,-32766,-32766, 292, 142, 328, 242, -85, + 328, 384, 383, 265, 139, 406, 762, 763, 764, 765, + 82, 425, 431,-32766, 328,-32766,-32766,-32766,-32766, 819, + 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, + 795, 587, 796, 797, 798, 799, 787, 788, 347, 348, + 790, 791, 776, 777, 778, 780, 781, 782, 358, 822, + 823, 824, 825, 826, 588, 783, 784, 589, 590, 253, + 807, 805, 806, 818, 802, 803, 836, 729, 591, 592, + 801, 593, 594, 595, 596, 597, 598, -328, 83, 84, + 85, -194, 804, 599, 600, -193, 149, 779, 750, 751, + 752, 753, 754, 151, 755, 756, 757, 792, 793, 37, + 486, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, -599, 275, -599,-32766,-32766, + -32766,-32766,-32766,-32766, 312, 1093, 127, 314, 110, 741, + 1335, 21, 758,-32766,-32766,-32766, -272, 1334,-32766,-32766, + 1092,-32766,-32766,-32766,-32766,-32766, 759, 760, 761, 762, + 763, 764, 765, 1108,-32766, 828,-32766,-32766, -550, 560, + 1040, 1273, 819, 766, 767, 768, 769, 770, 771, 772, + 773, 774, 775, 795, 817, 796, 797, 798, 799, 787, + 788, 789, 816, 790, 791, 776, 777, 778, 780, 781, + 782, 821, 822, 823, 824, 825, 826, 827, 783, 784, + 785, 786, 1037, 807, 805, 806, 818, 802, 803, 749, + 748, 794, 800, 801, 808, 809, 811, 810, 812, 813, + 1285, 325, -550, -550, 1040, 804, 815, 814, 50, 51, + 52, 516, 53, 54, 866, 341, 867, -550, 55, 56, + -110, 57, 839, 924, -367, -110, -367, -110, 293, -556, + 152, -550, 308, 103, 104, -110, -110, -110, -110, -110, + -110, -110, -110, 105, 106, 107, 108, 109, 947, 275, + 342, 924, 1252, 719,-32766,-32766,-32766, 58, 59, -549, + 372, 110, 60, 838, 61, 247, 248, 62, 63, 64, + 65, 66, 67, 68, 69,-32766, 28, 267, 70, 446, + 517, 720, 376, -342, 1279, 1280, 518, 359, 839, -548, + 391, -546, 1277, 42, 25, 519, 947, 520, 616, 521, + 924, 522, 442, 141, 523, 524, 914, 328, 443, 44, + 45, 447, 379, 378,-32766, 46, 525, 1027, 1026, 1025, + 1028, 370, 340, -549, -549, 444, 1360, 431, 1238, 1361, + 527, 528, 529, 839, 914, 364, 1040, 445, -549,-32766, + -32766,-32766, 531, 532, 845, 1266, 1267, 1268, 1269, 1263, + 1264, 300, -549, -548, -548, -546, -546, 1270, 1265, 292, + -32766, 1247, 1246, 1248, 301, 749, 748, 71, -548, -78, + -546, 323, 324, 328, -153, -153, -153, 393,-32766, 7, + -555, 926, -548, 914, -546, 714, 660, 26,-32766, -153, + 832, -153, 866, -153, 867, -153, 382, 383, 28, 268, + 1040, 154, 1247, 1246, 1248, 377, 425, 155, -596, 926, + 839, 1094, 75, 714, 1277, -596, 963, 964, 328, -547, + 156, 526, 158, 292, 1245, 33, 900, 959, -110, -110, + -110, 32, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 679, 680, -58, 301, -57, + 1238, 124, 924, 749, 748, 1252, 150, 409, 926, 125, + 1243, 924, 714, -153, 531, 532, 834, 1266, 1267, 1268, + 1269, 1263, 1264, 716, 1154, 1156, -87, -4, 924, 1270, + 1265, 1039, 721, -547, -547, -546, 130, 749, 748, 73, + -32766, 724, 131, -552, 324, 328, 1245, 144, -547, 1247, + 1246, 1248, 159,-32766,-32766,-32766, 1037,-32766, 160,-32766, + -554,-32766, -547, 161,-32766, 380, 381, 924, 162,-32766, + -32766,-32766, 163, 49,-32766,-32766,-32766, -84, 1040, -78, + 1245,-32766, 422, 48, 924, 914, 839,-32766,-32766,-32766, + -32766,-32766, -73,-32766, 914,-32766, -72, 731,-32766, -546, + -546, 283, -71,-32766,-32766,-32766, -70, -552, -552,-32766, + -32766, 914, 385, 386, -546,-32766, 422, -596, -69, -596, + 74, -110, -110, -68,-32766, -50, -110, -67, -546, 651, + 652, -66, -110, 377, -65, 438, -552, 304, 305, -46, + 299,-32766, -18, 148, 963, 964, 274, 302, 303, 526, + 914, 284, 375, 730, 530, 959, -110, -110, -110, 132, + 980, 733, 301, 923, 714, 75, 128, 914,-32766, 926, + 147, 328, -302, 714, 1245, -298, 126, 10, 1063, 281, + 282,-32766,-32766,-32766, 285,-32766, 926,-32766, 286,-32766, + 714, -4,-32766, 334, 288, 275, 289,-32766,-32766,-32766, + 294, 295,-32766,-32766,-32766, 924, 941, 287, 1245,-32766, + 422, 110, 689, 146, 830,-32766,-32766,-32766,-32766,-32766, + 565,-32766, 666,-32766, 1362, 926,-32766, 705, 839, 714, + 1123,-32766,-32766,-32766,-32766,-32766, 667,-32766,-32766, 309, + 1245, 661, 926,-32766, 422, 924, 714,-32766,-32766,-32766, + 682,-32766,-32766,-32766, 707,-32766, 306, 960,-32766, 313, + -32766, 683, 491,-32766,-32766,-32766,-32766, 20, 467,-32766, + -32766, 496, 1245, 578, 571,-32766, 422, 301, 649,-32766, + -32766,-32766, -511,-32766,-32766,-32766, 0,-32766, 914, 0, + -32766, 0, 0, 1037, 0,-32766,-32766,-32766, 1284, 307, + 1286,-32766,-32766, 0, -250, -250, -250,-32766, 422, 943, + 377, 0, 0, 28, 267, 1040,-32766, 0, -501, 0, + 614, 963, 964, 0, 8, 839, 526, 24, 914, 1277, + 374, 900, 959, -110, -110, -110, 1274, 838, 283, 40, + -584, 0, 41, 738, -249, -249, -249, 739, 28, 268, + 377, 850, 287, 858, 905, 1004, 981, 988, 978, 989, + 839, 963, 964, 926, 1277, 1238, 526, 714, -250, 903, + 976, 900, 959, -110, -110, -110, 1097, 1100, 1101, 1098, + 532, 1099, 1266, 1267, 1268, 1269, 1263, 1264, 1105, -583, + 1301, 1319, 1353, 654, 1270, 1265, -582, -556, -555, -554, + 1238, -553, 694, 926, 73, 34, -495, 714, -249, 324, + 328, 1, 29, 30, 39, 532, 43, 1266, 1267, 1268, + 1269, 1263, 1264, 47, 72, 76, 77, 78, 79, 1270, + 1265, 80, 81, 143,-32766, 153, 157, 245, 695, 73, + 1245, 330, 359, 360, 324, 328, 361,-32766,-32766,-32766, + 362,-32766, 363,-32766, 364,-32766, 365, 366,-32766, 696, + 697, 367, 368,-32766,-32766,-32766, 369, 371, 439,-32766, + -32766, 559, 322, -275, -273,-32766, 422, 1247, 1246, 1248, + -272, 13, 14, 283,-32766, 15, 16, 18, 408, 487, + 488, 495, 498, 499, 500, 501, 505, 506, 507, 514, + 576, 700, 1256, 1194, 1275, 1066, 1065, 1046, 1233, 1042, + -277, -102, 12, 17, 27, 298, 407, 607, 611, 640, + 706, 1198, 0, 1251, 1195, 1332, 0, 373, 715, 718, + 722, 723, 725, 726, 727, 728, 732, 717, 0, 735, + 901, 1357, 1359, 861, 860, 869, 953, 996, 868, 1358, + 952, 950, 951, 954, 1226, 934, 944, 932, 986, 987, + 638, 1356, 1313, 1302, 1320, 1329, 0, 1211, 0, 1278, + 0, 328 + ); + + protected array $actionCheck = array( + 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, + 12, 13, 70, 9, 10, 11, 9, 10, 11, 1, + 80, 44, 45, 46, 47, 48, 49, 50, 116, 117, + 118, 119, 120, 121, 122, 37, 38, 30, 1, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 1, 9, 10, 1, 57, 116, 117, 118, 119, + 120, 121, 122, 129, 130, 131, 8, 31, 1, 71, + 72, 73, 74, 75, 76, 77, 134, 135, 80, 82, + 1, 31, 85, 8, 30, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 1, 128, 129, 130, 131, + 132, 133, 82, 80, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 8, 147, 148, 8, 8, 150, 151, + 152, 8, 154, 31, 2, 3, 4, 5, 6, 7, + 162, 9, 10, 11, 12, 13, 8, 117, 118, 160, + 116, 162, 122, 9, 10, 11, 97, 159, 128, 8, + 117, 118, 9, 10, 11, 122, 1, 137, 31, 37, + 38, 128, 138, 8, 30, 159, 32, 33, 34, 35, + 36, 37, 38, 30, 9, 32, 33, 34, 158, 57, + 80, 161, 9, 10, 11, 161, 163, 167, 14, 97, + 167, 106, 107, 71, 72, 73, 74, 75, 76, 77, + 163, 116, 80, 30, 167, 32, 33, 34, 35, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 8, + 128, 129, 130, 131, 132, 133, 156, 163, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 162, 9, 10, + 11, 162, 150, 151, 152, 162, 154, 2, 3, 4, + 5, 6, 7, 14, 9, 10, 11, 12, 13, 30, + 163, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 160, 57, 162, 9, 10, + 11, 9, 10, 11, 8, 159, 14, 8, 69, 163, + 1, 101, 57, 9, 10, 11, 162, 8, 116, 30, + 1, 32, 33, 34, 35, 36, 71, 72, 73, 74, + 75, 76, 77, 123, 30, 80, 32, 33, 70, 85, + 138, 1, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 116, 128, 129, 130, 131, 132, 133, 37, + 38, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 146, 8, 134, 135, 138, 150, 151, 152, 2, 3, + 4, 5, 6, 7, 106, 8, 108, 149, 12, 13, + 101, 15, 82, 1, 106, 106, 108, 108, 30, 161, + 14, 163, 113, 49, 50, 116, 117, 118, 119, 120, + 121, 122, 123, 51, 52, 53, 54, 55, 122, 57, + 8, 1, 1, 31, 9, 10, 11, 51, 52, 70, + 8, 69, 56, 155, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 30, 70, 71, 72, 73, + 74, 31, 8, 164, 78, 79, 80, 161, 82, 70, + 8, 70, 86, 87, 88, 89, 122, 91, 52, 93, + 1, 95, 8, 163, 98, 99, 84, 167, 8, 103, + 104, 105, 106, 107, 116, 109, 110, 119, 120, 121, + 122, 115, 116, 134, 135, 8, 80, 80, 122, 83, + 124, 125, 126, 82, 84, 161, 138, 8, 149, 116, + 51, 52, 136, 137, 8, 139, 140, 141, 142, 143, + 144, 145, 163, 134, 135, 134, 135, 151, 152, 161, + 137, 155, 156, 157, 158, 37, 38, 161, 149, 16, + 149, 165, 166, 167, 75, 76, 77, 106, 116, 108, + 161, 159, 163, 84, 163, 163, 75, 76, 137, 90, + 80, 92, 106, 94, 108, 96, 106, 107, 70, 71, + 138, 14, 155, 156, 157, 106, 116, 14, 1, 159, + 82, 164, 161, 163, 86, 8, 117, 118, 167, 70, + 14, 122, 14, 161, 80, 14, 127, 128, 129, 130, + 131, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 75, 76, 16, 158, 16, + 122, 16, 1, 37, 38, 1, 101, 102, 159, 16, + 116, 1, 163, 164, 136, 137, 156, 139, 140, 141, + 142, 143, 144, 163, 59, 60, 31, 0, 1, 151, + 152, 137, 31, 134, 135, 70, 16, 37, 38, 161, + 74, 31, 16, 70, 166, 167, 80, 16, 149, 155, + 156, 157, 16, 87, 88, 89, 116, 91, 16, 93, + 161, 95, 163, 16, 98, 106, 107, 1, 16, 103, + 104, 105, 16, 70, 74, 109, 110, 31, 138, 31, + 80, 115, 116, 70, 1, 84, 82, 87, 88, 89, + 124, 91, 31, 93, 84, 95, 31, 31, 98, 134, + 135, 161, 31, 103, 104, 105, 31, 134, 135, 109, + 110, 84, 106, 107, 149, 115, 116, 160, 31, 162, + 154, 117, 118, 31, 124, 31, 122, 31, 163, 111, + 112, 31, 128, 106, 31, 108, 163, 134, 135, 31, + 113, 137, 31, 31, 117, 118, 31, 134, 135, 122, + 84, 31, 149, 31, 127, 128, 129, 130, 131, 31, + 159, 31, 158, 31, 163, 161, 163, 84, 74, 159, + 31, 167, 35, 163, 80, 35, 163, 150, 1, 35, + 35, 87, 88, 89, 35, 91, 159, 93, 35, 95, + 163, 164, 98, 35, 37, 57, 37, 103, 104, 105, + 37, 37, 74, 109, 110, 1, 38, 30, 80, 115, + 116, 69, 77, 70, 80, 87, 88, 89, 124, 91, + 89, 93, 96, 95, 83, 159, 98, 80, 82, 163, + 82, 103, 104, 105, 74, 85, 100, 109, 110, 114, + 80, 90, 159, 115, 116, 1, 163, 87, 88, 89, + 94, 91, 124, 93, 92, 95, 132, 128, 98, 132, + 137, 100, 102, 103, 104, 105, 74, 97, 97, 109, + 110, 97, 80, 81, 153, 115, 116, 158, 113, 87, + 88, 89, 149, 91, 124, 93, -1, 95, 84, -1, + 98, -1, -1, 116, -1, 103, 104, 105, 146, 133, + 146, 109, 110, -1, 100, 101, 102, 115, 116, 154, + 106, -1, -1, 70, 71, 138, 124, -1, 149, -1, + 153, 117, 118, -1, 149, 82, 122, 149, 84, 86, + 149, 127, 128, 129, 130, 131, 160, 155, 161, 159, + 161, -1, 159, 159, 100, 101, 102, 159, 70, 71, + 106, 160, 30, 159, 159, 159, 159, 159, 159, 159, + 82, 117, 118, 159, 86, 122, 122, 163, 164, 159, + 159, 127, 128, 129, 130, 131, 159, 159, 159, 159, + 137, 159, 139, 140, 141, 142, 143, 144, 159, 161, + 160, 160, 160, 160, 151, 152, 161, 161, 161, 161, + 122, 161, 80, 159, 161, 163, 161, 163, 164, 166, + 167, 161, 161, 161, 161, 137, 161, 139, 140, 141, + 142, 143, 144, 161, 161, 161, 161, 161, 161, 151, + 152, 161, 161, 161, 74, 161, 161, 161, 116, 161, + 80, 161, 161, 161, 166, 167, 161, 87, 88, 89, + 161, 91, 161, 93, 161, 95, 161, 161, 98, 137, + 138, 161, 161, 103, 104, 105, 161, 161, 161, 109, + 110, 161, 163, 162, 162, 115, 116, 155, 156, 157, + 162, 162, 162, 161, 124, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, + 162, 162, -1, 162, 162, 162, -1, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 163, -1, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, -1, 165, -1, 166, + -1, 167 + ); + + protected array $actionBase = array( + 0, -2, 152, 549, 727, 904, 944, 1022, 390, 497, + 560, 922, 500, 710, 710, 766, 710, 472, 701, 847, + -60, 305, 305, 847, 305, 783, 783, 783, 666, 666, + 666, 666, 700, 700, 860, 860, 892, 828, 794, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1060, + 1060, 1060, 1060, 1060, 18, 36, 79, 661, 1053, 1059, + 1055, 1061, 1051, 1050, 1054, 1056, 1062, 1097, 1098, 839, + 1099, 1100, 1096, 1101, 1057, 933, 1052, 1058, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 195, 342, 43, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 495, 495, + 495, 578, 578, 354, 173, 978, 943, 978, 978, 978, + 978, 978, 978, 978, 978, 203, 665, 339, 164, 164, + 7, 7, 7, 7, 7, 50, 369, 704, 704, -23, + -23, -23, -23, 448, 877, 501, 260, 368, 434, 54, + 540, 640, 640, 316, 316, 512, 512, 316, 316, 316, + 442, 442, 252, 252, 252, 252, 318, 469, 599, 358, + 304, 823, 53, 53, 53, 53, 823, 823, 823, 823, + 854, 1103, 823, 823, 823, 439, 471, 471, 703, 539, + 539, 471, 536, -3, -3, 536, 63, -3, 67, 496, + 473, 829, 115, 9, 473, 673, 713, 657, 185, 882, + 659, 882, 1049, 376, 850, 850, 424, 808, 761, 929, + 1074, 1063, 836, 1094, 861, 1095, -66, -58, 748, 1048, + 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, 1048, + 1104, 402, 1049, 130, 1104, 1104, 1104, 402, 402, 402, + 402, 402, 402, 402, 402, 402, 402, 718, 130, 561, + 620, 130, 858, 402, 18, 869, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 811, 157, 18, 36, + 124, 124, 196, 37, 124, 124, 124, 124, 18, 18, + 18, 18, 659, 838, 821, 706, 867, 143, 838, 838, + 838, 122, 135, 204, 139, 837, 840, 521, 834, 834, + 848, 950, 834, 846, 834, 848, 962, 834, 834, 950, + 950, 819, 950, 158, 544, 457, 524, 550, 950, 346, + 834, 834, 834, 834, 827, 950, 567, 834, 271, 171, + 834, 834, 827, 824, 820, 58, 866, 950, 950, 950, + 827, 502, 866, 866, 866, 884, 888, 865, 815, 443, + 349, 586, 138, 868, 815, 815, 834, 532, 865, 815, + 865, 815, 855, 815, 815, 815, 865, 815, 846, 492, + 815, 736, 579, 75, 815, 6, 963, 964, 695, 965, + 953, 966, 1007, 967, 970, 1065, 945, 976, 955, 971, + 1010, 952, 951, 832, 685, 693, 875, 833, 940, 842, + 842, 842, 936, 937, 842, 842, 842, 842, 842, 842, + 842, 842, 685, 876, 881, 831, 982, 720, 726, 1038, + 852, 1076, 1102, 981, 1040, 972, 880, 731, 1025, 985, + 1075, 1009, 989, 991, 1026, 1041, 894, 1042, 1077, 843, + 1078, 1079, 891, 995, 1066, 842, 963, 970, 746, 955, + 971, 952, 951, 803, 800, 792, 796, 787, 775, 765, + 771, 812, 1043, 935, 879, 930, 993, 938, 685, 931, + 1019, 942, 1027, 1028, 1064, 871, 841, 932, 1080, 996, + 1000, 1001, 1067, 1044, 1068, 883, 1020, 1011, 1029, 874, + 1081, 1030, 1031, 1032, 1033, 1069, 1082, 1070, 928, 1071, + 895, 851, 1012, 826, 1083, 299, 849, 853, 864, 1006, + 466, 980, 1072, 1084, 1085, 1034, 1035, 1036, 1086, 1087, + 974, 896, 1023, 856, 1024, 1018, 897, 898, 637, 863, + 1045, 844, 845, 859, 643, 656, 1088, 1089, 1090, 975, + 822, 835, 899, 900, 1046, 857, 1047, 1091, 658, 910, + 742, 1092, 1039, 747, 752, 603, 683, 681, 756, 862, + 1073, 878, 825, 870, 1005, 752, 830, 911, 1093, 917, + 918, 919, 1037, 920, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 456, 456, 456, 456, 456, 456, + 305, 305, 305, 305, 305, 456, 456, 456, 456, 456, + 456, 456, 305, 305, 0, 0, 305, 0, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 594, 594, 289, 289, 594, 594, + 594, 594, 594, 594, 594, 594, 594, 594, 289, 0, + 289, 289, 289, 289, 289, 289, 289, 289, 594, 819, + 594, 594, 442, 442, 442, 442, 594, 594, 594, -88, + -88, 442, 594, 63, 594, 594, 594, 594, 594, 594, + 594, 594, 594, 0, 0, 594, 594, 594, 594, 0, + 0, 0, 130, -3, 594, 846, 846, 846, 846, 594, + 594, 594, 594, -3, -3, 594, 594, 594, 0, 0, + 0, 0, 442, 442, 0, 130, 0, 0, 130, 0, + 0, 846, 846, 594, 63, 819, 359, 594, 0, 0, + 0, 0, 130, 846, 130, 402, 834, -3, -3, 834, + 402, 402, 124, 18, 359, 605, 605, 605, 605, 0, + 0, 659, 819, 819, 819, 819, 819, 819, 819, 819, + 819, 819, 819, 846, 0, 819, 0, 846, 846, 846, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 846, 0, 0, 950, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 962, + 0, 0, 0, 0, 0, 0, 846, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 842, 871, 0, 871, + 0, 842, 842, 842, 0, 0, 0, 0, 863, 857 + ); + + protected array $actionDefault = array( + 3,32767, 102,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 100,32767,32767,32767,32767, 602, 602, + 602, 602,32767,32767, 254, 102,32767,32767, 470, 387, + 387, 387,32767,32767, 544, 544, 544, 544, 544, 544, + 32767,32767,32767,32767,32767,32767, 470,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 100, + 32767,32767,32767, 36, 7, 8, 10, 11, 49, 17, + 324,32767,32767,32767,32767, 102,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 595,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 474, 453, + 454, 456, 457, 386, 545, 601, 327, 598, 385, 145, + 339, 329, 242, 330, 258, 475, 259, 476, 479, 480, + 215, 287, 382, 149, 150, 417, 471, 419, 469, 473, + 418, 392, 398, 399, 400, 401, 402, 403, 404, 405, + 406, 407, 408, 409, 410, 390, 391, 472, 450, 449, + 448,32767,32767, 415, 416,32767,32767,32767,32767,32767, + 32767,32767,32767, 102,32767, 420, 389, 423, 421, 422, + 439, 440, 437, 438, 441,32767,32767,32767,32767, 442, + 443, 444, 445, 316,32767,32767, 366, 364, 424, 316, + 111,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 430, 431,32767,32767,32767,32767, 487, 538, 447,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 102,32767, 100, 540, 412, 414, 507, 425, + 426, 393,32767, 514,32767, 102,32767, 516,32767,32767, + 32767,32767,32767,32767,32767, 539,32767, 546, 546,32767, + 500, 100, 195,32767,32767, 515,32767, 195, 195,32767, + 32767,32767,32767,32767,32767,32767,32767, 609, 500, 110, + 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, + 32767, 195, 110,32767,32767,32767, 100, 195, 195, 195, + 195, 195, 195, 195, 195, 195, 195, 190,32767, 268, + 270, 102, 563, 195,32767, 519,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 512,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 500, 435, 138,32767, 138, 546, 427, 428, + 429, 502, 546, 546, 546, 312, 289,32767,32767,32767, + 32767, 517, 100, 100, 100, 100, 512,32767,32767,32767, + 32767, 111, 486, 99, 99, 99, 99, 99, 103, 101, + 32767,32767,32767,32767, 223,32767, 99,32767, 101, 101, + 32767,32767, 223, 225, 212, 101, 227,32767, 567, 568, + 223, 101, 227, 227, 227, 247, 247, 489, 318, 101, + 99, 101, 101, 197, 318, 318,32767, 101, 489, 318, + 489, 318, 199, 318, 318, 318, 489, 318,32767, 101, + 318, 214, 99, 99, 318,32767,32767,32767, 502,32767, + 32767,32767,32767,32767,32767,32767, 222,32767,32767,32767, + 32767,32767,32767,32767,32767, 533,32767, 551, 565, 433, + 434, 436, 550, 548, 458, 459, 460, 461, 462, 463, + 464, 466, 597,32767, 506,32767,32767,32767, 338,32767, + 607,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 608,32767, + 546,32767,32767,32767,32767, 432, 9, 74, 495, 42, + 43, 51, 57, 523, 524, 525, 526, 520, 521, 527, + 522,32767,32767, 528, 573,32767,32767, 547, 600,32767, + 32767,32767,32767,32767,32767, 138,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 533,32767, 136, + 32767,32767,32767,32767,32767,32767,32767,32767, 529,32767, + 32767,32767, 546,32767,32767,32767,32767, 314, 311,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 546,32767,32767,32767,32767, + 32767, 291,32767, 308,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 286,32767,32767, 381, 502, 294, 296, 297,32767,32767, + 32767,32767, 360,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 152, 152, 3, 3, 341, 152, + 152, 152, 341, 341, 152, 341, 341, 341, 152, 152, + 152, 152, 152, 152, 280, 185, 262, 265, 247, 247, + 152, 352, 152 + ); + + protected array $goto = array( + 196, 196, 1038, 1069, 701, 353, 433, 665, 856, 710, + 427, 321, 316, 317, 337, 580, 432, 338, 434, 642, + 658, 659, 421, 676, 677, 678, 857, 167, 167, 167, + 167, 221, 197, 193, 193, 177, 179, 216, 193, 193, + 193, 193, 193, 194, 194, 194, 194, 194, 194, 188, + 189, 190, 191, 192, 218, 216, 219, 539, 540, 423, + 541, 544, 545, 546, 547, 548, 549, 550, 551, 1140, + 168, 169, 170, 195, 171, 172, 173, 166, 174, 175, + 176, 178, 215, 217, 220, 238, 243, 244, 255, 257, + 258, 259, 260, 261, 262, 263, 264, 269, 270, 271, + 272, 278, 290, 291, 319, 320, 428, 429, 430, 585, + 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 180, 237, 181, 198, 199, + 200, 239, 188, 189, 190, 191, 192, 218, 1140, 201, + 182, 183, 184, 202, 198, 185, 240, 203, 201, 165, + 204, 205, 186, 206, 207, 208, 187, 209, 210, 211, + 212, 213, 214, 859, 613, 628, 631, 632, 633, 634, + 655, 656, 657, 712, 460, 979, 280, 280, 280, 280, + 479, 1321, 1322, 627, 627, 831, 604, 1276, 1276, 1276, + 1276, 1276, 1276, 1276, 1276, 1276, 1276, 398, 401, 564, + 605, 609, 890, 552, 552, 552, 552, 864, 608, 913, + 908, 909, 922, 865, 910, 862, 911, 912, 863, 465, + 441, 916, 1041, 1041, 685, 956, 1189, 357, 1033, 1049, + 1050, 1091, 1086, 1087, 1088, 1295, 1295, 357, 357, 1295, + 1295, 1295, 1295, 1295, 1295, 1295, 1295, 1295, 1295, 698, + 357, 357, 833, 917, 357, 918, 1363, 354, 355, 577, + 1244, 698, 1244, 1244, 426, 698, 615, 558, 1038, 1038, + 1244, 357, 357, 5, 1038, 6, 1038, 1038, 1038, 1038, + 1038, 1038, 1038, 1038, 1038, 625, 662, 1038, 1038, 1038, + 1038, 1328, 1328, 1328, 1328, 351, 1244, 356, 356, 356, + 356, 1244, 1244, 1244, 1244, 1111, 1112, 1244, 1244, 1244, + 344, 563, 556, 897, 855, 897, 897, 1336, 554, 1307, + 554, 554, 482, 603, 1104, 930, 713, 1000, 554, 931, + 484, 396, 946, 345, 344, 946, 511, 704, 872, 1102, + 690, 343, 556, 563, 572, 573, 346, 583, 606, 620, + 621, 575, 852, 884, 458, 664, 871, 22, 1137, 973, + 973, 973, 973, 1044, 1043, 458, 967, 974, 1292, 1292, + 558, 1062, 1292, 1292, 1292, 1292, 1292, 1292, 1292, 1292, + 1292, 1292, 543, 543, 1047, 1048, 543, 543, 543, 543, + 543, 543, 543, 543, 543, 543, 570, 469, 469, 440, + 737, 641, 643, 670, 852, 663, 469, 327, 311, 687, + 691, 1014, 699, 708, 1010, 686, 1017, 1017, 1220, 948, + 1323, 1324, 1221, 1224, 949, 1225, 849, 557, 567, 581, + 618, 557, 339, 567, 877, 1237, 399, 464, 451, 451, + 451, 451, 405, 1318, 837, 1318, 1318, 251, 251, 472, + 584, 473, 474, 1318, 962, 1022, 882, 542, 542, 1354, + 1355, 542, 874, 542, 542, 542, 542, 542, 542, 542, + 542, 971, 412, 709, 249, 249, 249, 249, 246, 252, + 1330, 1330, 1330, 1330, 837, 880, 837, 410, 411, 635, + 637, 639, 674, 619, 675, 1075, 414, 415, 416, 1235, + 688, 740, 886, 417, 1079, 0, 1314, 349, 435, 984, + 885, 873, 1074, 1078, 435, 1122, 503, 0, 504, 1239, + 1045, 1045, 982, 852, 510, 0, 0, 669, 1056, 1052, + 1053, 0, 451, 451, 451, 451, 451, 451, 451, 451, + 451, 451, 451, 935, 1127, 451, 972, 0, 1077, 0, + 623, 0, 1316, 1316, 1077, 0, 1019, 0, 0, 326, + 276, 326, 326, 0, 876, 1261, 668, 998, 1120, 889, + 1346, 1346, 870, 1240, 1241, 1003, 0, 0, 975, 0, + 736, 0, 847, 0, 1234, 0, 0, 1346, 555, 1012, + 1007, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1242, 1304, 1305, 1349, 1349, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 254, 254 + ); + + protected array $gotoCheck = array( + 42, 42, 73, 127, 73, 97, 66, 66, 26, 9, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 86, 86, 43, 86, 86, 86, 27, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 15, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 83, 49, 23, 23, 23, 23, + 178, 178, 178, 108, 108, 6, 130, 108, 108, 108, + 108, 108, 108, 108, 108, 108, 108, 59, 59, 59, + 59, 59, 45, 107, 107, 107, 107, 15, 107, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 151, + 83, 15, 89, 89, 89, 89, 151, 14, 89, 89, + 89, 15, 15, 15, 15, 172, 172, 14, 14, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 7, + 14, 14, 7, 65, 14, 65, 14, 97, 97, 174, + 73, 7, 73, 73, 13, 7, 13, 14, 73, 73, + 73, 14, 14, 46, 73, 46, 73, 73, 73, 73, + 73, 73, 73, 73, 73, 56, 56, 73, 73, 73, + 73, 9, 9, 9, 9, 181, 73, 24, 24, 24, + 24, 73, 73, 73, 73, 144, 144, 73, 73, 73, + 170, 76, 76, 25, 25, 25, 25, 183, 19, 14, + 19, 19, 84, 8, 8, 73, 8, 103, 19, 73, + 84, 62, 9, 170, 170, 9, 8, 8, 35, 8, + 14, 76, 76, 76, 76, 76, 76, 76, 76, 76, + 76, 104, 22, 35, 19, 64, 35, 76, 150, 19, + 19, 19, 19, 118, 118, 19, 19, 19, 173, 173, + 14, 114, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 175, 175, 119, 119, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 48, 149, 149, 113, + 48, 48, 48, 120, 22, 48, 149, 171, 171, 48, + 48, 48, 48, 48, 48, 116, 107, 107, 79, 79, + 180, 180, 79, 79, 79, 79, 18, 9, 9, 2, + 2, 9, 29, 9, 39, 14, 9, 9, 23, 23, + 23, 23, 28, 130, 12, 130, 130, 5, 5, 9, + 9, 9, 9, 130, 92, 110, 9, 158, 158, 9, + 9, 158, 37, 158, 158, 158, 158, 158, 158, 158, + 158, 93, 93, 93, 5, 5, 5, 5, 5, 5, + 130, 130, 130, 130, 12, 9, 12, 82, 82, 85, + 85, 85, 82, 80, 82, 129, 82, 82, 82, 162, + 82, 99, 41, 82, 132, -1, 130, 82, 117, 96, + 16, 16, 16, 16, 117, 147, 155, -1, 155, 20, + 117, 117, 16, 22, 155, -1, -1, 117, 117, 117, + 117, -1, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 17, 17, 23, 16, -1, 130, -1, + 17, -1, 130, 130, 130, -1, 17, -1, -1, 24, + 24, 24, 24, -1, 17, 20, 17, 17, 16, 16, + 184, 184, 17, 20, 20, 50, -1, -1, 50, -1, + 50, -1, 20, -1, 17, -1, -1, 184, 50, 50, + 50, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 20, 20, 20, 184, 184, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 5, 5 + ); + + protected array $gotoBase = array( + 0, 0, -287, 0, 0, 446, 165, 242, 315, -11, + 0, 0, 145, -75, -73, -187, 56, 75, 114, 53, + 124, 0, 72, 173, 294, 310, 4, 22, 103, 133, + 0, 0, 0, 0, 0, -35, 0, 121, 0, 109, + 0, 60, -1, 3, 0, 179, -467, 0, -319, 157, + 563, 0, 0, 0, 0, 0, 245, 0, 0, 152, + 0, 0, 289, 0, 113, 239, -235, 0, 0, 0, + 0, 0, 0, -5, 0, 0, -36, 0, 0, 8, + 147, -196, -7, -106, -150, 7, -702, 0, 0, -59, + 0, 0, 123, 164, 0, 0, 65, -481, 0, 92, + 0, 0, 0, 292, 308, 0, 0, 175, -58, 0, + 83, 0, 0, 120, 97, 0, 132, 235, 82, 99, + 111, 0, 0, 0, 0, 0, 0, 1, 0, 119, + 178, 0, 61, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 29, 0, 0, 70, 0, 363, + 112, -49, 0, 0, 0, 18, 0, 0, 216, 0, + 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, + 10, 84, -6, 127, 230, 141, 0, 0, -123, 0, + 46, 265, 0, 286, 260, 0, 0 + ); + + protected array $gotoDefault = array( + -32768, 515, 744, 4, 745, 939, 820, 829, 601, 533, + 711, 350, 629, 424, 1312, 915, 1126, 582, 848, 1253, + 1227, 459, 851, 332, 734, 927, 898, 899, 402, 388, + 394, 400, 653, 630, 497, 883, 455, 875, 489, 878, + 454, 887, 164, 420, 513, 891, 3, 894, 561, 925, + 977, 389, 902, 390, 681, 904, 566, 906, 907, 397, + 403, 404, 1131, 574, 626, 919, 256, 568, 920, 387, + 921, 929, 392, 395, 692, 468, 508, 502, 413, 1106, + 569, 612, 650, 448, 476, 624, 636, 622, 483, 436, + 418, 331, 961, 969, 490, 466, 983, 352, 991, 742, + 1139, 644, 492, 999, 645, 1006, 1009, 534, 535, 481, + 1021, 273, 1024, 493, 19, 671, 1035, 1036, 672, 646, + 1058, 647, 673, 648, 1060, 475, 602, 1068, 456, 1076, + 1300, 457, 1080, 266, 1083, 279, 419, 437, 1089, 1090, + 9, 1096, 702, 703, 11, 277, 512, 1121, 693, 453, + 1138, 452, 1208, 1210, 562, 494, 1228, 480, 296, 1231, + 684, 509, 1236, 449, 1303, 450, 536, 477, 318, 537, + 1347, 310, 335, 315, 553, 297, 336, 538, 478, 1309, + 1317, 333, 31, 1337, 1348, 579, 617 + ); + + protected array $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, + 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, + 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 29, 29, 30, 30, 32, 34, 34, + 28, 36, 36, 33, 38, 38, 35, 35, 37, 37, + 39, 39, 31, 40, 40, 41, 43, 44, 44, 45, + 45, 46, 46, 48, 47, 47, 47, 47, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 25, 25, 50, 69, 69, 72, 72, 71, + 70, 70, 63, 75, 75, 76, 76, 77, 77, 78, + 78, 79, 79, 80, 80, 26, 26, 27, 27, 27, + 27, 27, 88, 88, 90, 90, 83, 83, 91, 91, + 92, 92, 92, 84, 84, 87, 87, 85, 85, 93, + 94, 94, 57, 57, 65, 65, 68, 68, 68, 67, + 95, 95, 96, 58, 58, 58, 58, 97, 97, 98, + 98, 99, 99, 100, 101, 101, 102, 102, 103, 103, + 55, 55, 51, 51, 105, 53, 53, 106, 52, 52, + 54, 54, 64, 64, 64, 64, 81, 81, 109, 109, + 111, 111, 112, 112, 112, 112, 110, 110, 110, 114, + 114, 114, 114, 89, 89, 117, 117, 117, 118, 118, + 115, 115, 119, 119, 121, 121, 122, 122, 116, 123, + 123, 120, 124, 124, 124, 124, 113, 113, 82, 82, + 82, 20, 20, 20, 126, 125, 125, 127, 127, 127, + 127, 60, 128, 128, 129, 61, 131, 131, 132, 132, + 133, 133, 86, 134, 134, 134, 134, 134, 134, 134, + 139, 139, 140, 140, 141, 141, 141, 141, 141, 142, + 143, 143, 138, 138, 135, 135, 137, 137, 145, 145, + 144, 144, 144, 144, 144, 144, 144, 136, 146, 146, + 148, 147, 147, 62, 104, 149, 149, 56, 56, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 156, 158, 158, 159, 150, 150, 155, + 155, 160, 161, 161, 162, 163, 164, 164, 164, 164, + 19, 19, 73, 73, 73, 73, 151, 151, 151, 151, + 166, 166, 152, 152, 154, 154, 154, 157, 157, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 173, 173, + 173, 108, 175, 175, 175, 175, 153, 153, 153, 153, + 153, 153, 153, 153, 59, 59, 169, 169, 169, 169, + 169, 176, 176, 165, 165, 165, 165, 177, 177, 177, + 177, 177, 177, 74, 74, 66, 66, 66, 66, 130, + 130, 130, 130, 180, 179, 168, 168, 168, 168, 168, + 168, 168, 167, 167, 167, 178, 178, 178, 178, 107, + 174, 182, 182, 181, 181, 183, 183, 183, 183, 183, + 183, 183, 183, 171, 171, 171, 171, 170, 185, 184, + 184, 184, 184, 184, 184, 184, 184, 186, 186, 186, + 186 + ); + + protected array $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, + 0, 1, 1, 1, 1, 4, 3, 5, 4, 3, + 4, 2, 3, 1, 1, 7, 6, 2, 3, 1, + 2, 3, 1, 2, 3, 1, 1, 3, 1, 3, + 1, 2, 2, 3, 1, 3, 2, 3, 1, 3, + 3, 2, 0, 1, 1, 1, 1, 1, 3, 7, + 10, 5, 7, 9, 5, 3, 3, 3, 3, 3, + 3, 1, 2, 5, 7, 9, 6, 5, 6, 3, + 2, 1, 1, 1, 1, 0, 2, 1, 3, 8, + 0, 4, 2, 1, 3, 0, 1, 0, 1, 0, + 1, 3, 1, 1, 1, 8, 9, 7, 8, 7, + 6, 8, 0, 2, 0, 2, 1, 2, 1, 2, + 1, 1, 1, 0, 2, 0, 2, 0, 2, 2, + 1, 3, 1, 4, 1, 4, 1, 1, 4, 2, + 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, + 3, 1, 1, 7, 0, 2, 1, 3, 3, 4, + 1, 4, 0, 2, 5, 0, 2, 6, 0, 2, + 0, 3, 1, 2, 1, 1, 2, 0, 1, 3, + 0, 2, 1, 1, 1, 1, 6, 8, 6, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 3, 3, 1, 3, 3, 3, 3, 3, 1, 3, + 3, 1, 1, 2, 1, 1, 0, 1, 0, 2, + 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, + 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, + 2, 0, 1, 5, 5, 6, 10, 3, 5, 1, + 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, + 1, 1, 3, 2, 2, 3, 1, 0, 1, 1, + 3, 3, 3, 4, 4, 1, 1, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, + 3, 4, 4, 2, 2, 4, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, + 1, 2, 4, 2, 2, 8, 9, 8, 9, 9, + 10, 9, 10, 8, 3, 2, 2, 1, 1, 0, + 4, 2, 1, 3, 2, 1, 2, 2, 2, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, + 1, 1, 0, 3, 0, 1, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, + 3, 3, 4, 1, 1, 3, 1, 1, 1, 1, + 1, 3, 2, 3, 0, 1, 1, 3, 1, 1, + 1, 1, 1, 1, 3, 1, 1, 1, 4, 4, + 1, 4, 4, 0, 1, 1, 1, 3, 3, 1, + 4, 2, 2, 1, 3, 1, 4, 4, 3, 3, + 3, 3, 1, 3, 1, 1, 3, 1, 1, 4, + 1, 1, 1, 3, 1, 1, 2, 1, 3, 4, + 3, 2, 0, 2, 2, 1, 2, 1, 1, 1, + 4, 3, 3, 3, 3, 6, 3, 1, 1, 2, + 1 + ); + + protected function initReduceCallbacks(): void { + $this->reduceCallbacks = [ + 0 => null, + 1 => static function ($self, $stackPos) { + $self->semValue = $self->handleNamespaces($self->semStack[$stackPos-(1-1)]); + }, + 2 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 3 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 4 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 5 => null, + 6 => null, + 7 => null, + 8 => null, + 9 => null, + 10 => null, + 11 => null, + 12 => null, + 13 => null, + 14 => null, + 15 => null, + 16 => null, + 17 => null, + 18 => null, + 19 => null, + 20 => null, + 21 => null, + 22 => null, + 23 => null, + 24 => null, + 25 => null, + 26 => null, + 27 => null, + 28 => null, + 29 => null, + 30 => null, + 31 => null, + 32 => null, + 33 => null, + 34 => null, + 35 => null, + 36 => null, + 37 => null, + 38 => null, + 39 => null, + 40 => null, + 41 => null, + 42 => null, + 43 => null, + 44 => null, + 45 => null, + 46 => null, + 47 => null, + 48 => null, + 49 => null, + 50 => null, + 51 => null, + 52 => null, + 53 => null, + 54 => null, + 55 => null, + 56 => null, + 57 => null, + 58 => null, + 59 => null, + 60 => null, + 61 => null, + 62 => null, + 63 => null, + 64 => null, + 65 => null, + 66 => null, + 67 => null, + 68 => null, + 69 => null, + 70 => null, + 71 => null, + 72 => null, + 73 => null, + 74 => null, + 75 => null, + 76 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; if ($self->semValue === "emitError(new Error('Cannot use "getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 77 => null, + 78 => null, + 79 => null, + 80 => null, + 81 => null, + 82 => null, + 83 => null, + 84 => null, + 85 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 86 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 87 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 88 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 89 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 90 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 91 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 92 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 93 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 94 => null, + 95 => static function ($self, $stackPos) { + $self->semValue = new Name(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 96 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 97 => static function ($self, $stackPos) { + /* nothing */ + }, + 98 => static function ($self, $stackPos) { + /* nothing */ + }, + 99 => static function ($self, $stackPos) { + /* nothing */ + }, + 100 => static function ($self, $stackPos) { + $self->emitError(new Error('A trailing comma is not allowed here', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 101 => null, + 102 => null, + 103 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(1-1)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 104 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 105 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 106 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 107 => static function ($self, $stackPos) { + $self->semValue = new Node\AttributeGroup($self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 108 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 109 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 110 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 111 => null, + 112 => null, + 113 => null, + 114 => null, + 115 => static function ($self, $stackPos) { + $self->semValue = new Stmt\HaltCompiler($self->handleHaltCompiler(), $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 116 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(3-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $self->checkNamespace($self->semValue); + }, + 117 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 118 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_(null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 119 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 120 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 121 => null, + 122 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 123 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 124 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 125 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 126 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 127 => null, + 128 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 129 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 130 => null, + 131 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 132 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 133 => null, + 134 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 135 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 136 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 137 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 138 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 139 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 140 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 141 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; $self->semValue->type = $self->semStack[$stackPos-(2-1)]; + }, + 142 => null, + 143 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 144 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 145 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 146 => null, + 147 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 148 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 149 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 150 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 151 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 152 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 153 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 154 => null, + 155 => null, + 156 => null, + 157 => static function ($self, $stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 158 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Block($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 159 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(7-3)], ['stmts' => $self->semStack[$stackPos-(7-5)], 'elseifs' => $self->semStack[$stackPos-(7-6)], 'else' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 160 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(10-3)], ['stmts' => $self->semStack[$stackPos-(10-6)], 'elseifs' => $self->semStack[$stackPos-(10-7)], 'else' => $self->semStack[$stackPos-(10-8)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 161 => static function ($self, $stackPos) { + $self->semValue = new Stmt\While_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 162 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Do_($self->semStack[$stackPos-(7-5)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 163 => static function ($self, $stackPos) { + $self->semValue = new Stmt\For_(['init' => $self->semStack[$stackPos-(9-3)], 'cond' => $self->semStack[$stackPos-(9-5)], 'loop' => $self->semStack[$stackPos-(9-7)], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 164 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Switch_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 165 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Break_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 166 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Continue_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 167 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Return_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 168 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Global_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 169 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Static_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 170 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Echo_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 171 => static function ($self, $stackPos) { + + $self->semValue = new Stmt\InlineHTML($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('hasLeadingNewline', $self->inlineHtmlHasLeadingNewline($stackPos-(1-1))); + + }, + 172 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Expression($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 173 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Unset_($self->semStack[$stackPos-(5-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 174 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $self->semStack[$stackPos-(7-5)][1], 'stmts' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 175 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-7)][0], ['keyVar' => $self->semStack[$stackPos-(9-5)], 'byRef' => $self->semStack[$stackPos-(9-7)][1], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 176 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(6-3)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-4)], $self->tokenEndStack[$stackPos-(6-4)])), ['stmts' => $self->semStack[$stackPos-(6-6)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 177 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Declare_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 178 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TryCatch($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->checkTryCatch($self->semValue); + }, + 179 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Goto_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 180 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Label($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 181 => static function ($self, $stackPos) { + $self->semValue = null; /* means: no statement */ + }, + 182 => null, + 183 => static function ($self, $stackPos) { + $self->semValue = $self->maybeCreateNop($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); + }, + 184 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 185 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 186 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 187 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 188 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 189 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Catch_($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-7)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 190 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 191 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Finally_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 192 => null, + 193 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 194 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 195 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 196 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 197 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 198 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 199 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 200 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 201 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 202 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 203 => null, + 204 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 205 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(8-3)], ['byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-5)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 206 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(9-4)], ['byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-6)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 207 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(7-2)], ['type' => $self->semStack[$stackPos-(7-1)], 'extends' => $self->semStack[$stackPos-(7-3)], 'implements' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(7-2)); + }, + 208 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(8-3)], ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(8-3)); + }, + 209 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Interface_($self->semStack[$stackPos-(7-3)], ['extends' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => $self->semStack[$stackPos-(7-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkInterface($self->semValue, $stackPos-(7-3)); + }, + 210 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Trait_($self->semStack[$stackPos-(6-3)], ['stmts' => $self->semStack[$stackPos-(6-5)], 'attrGroups' => $self->semStack[$stackPos-(6-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 211 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Enum_($self->semStack[$stackPos-(8-3)], ['scalarType' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkEnum($self->semValue, $stackPos-(8-3)); + }, + 212 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 213 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 214 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 215 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 216 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 217 => null, + 218 => null, + 219 => static function ($self, $stackPos) { + $self->checkClassModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 220 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 221 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 222 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 223 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 224 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 225 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 226 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 227 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 228 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 229 => null, + 230 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 231 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 232 => null, + 233 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 234 => null, + 235 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 236 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 237 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 238 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 239 => null, + 240 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 241 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 242 => static function ($self, $stackPos) { + $self->semValue = new Node\DeclareItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 243 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 244 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 245 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 246 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(5-3)]; + }, + 247 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 248 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 249 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_($self->semStack[$stackPos-(4-2)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 250 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_(null, $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 251 => null, + 252 => null, + 253 => static function ($self, $stackPos) { + $self->semValue = new Expr\Match_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 254 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 255 => null, + 256 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 257 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 258 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 259 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm(null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 260 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 261 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 262 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 263 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 264 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 265 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 266 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 267 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 268 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 269 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 270 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 271 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 272 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 273 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-2)], true); + }, + 274 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 275 => static function ($self, $stackPos) { + $self->semValue = array($self->fixupArrayDestructuring($self->semStack[$stackPos-(1-1)]), false); + }, + 276 => null, + 277 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 278 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 279 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 280 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 281 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 282 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 283 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 284 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 285 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 286 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(6-6)], null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + $self->checkParam($self->semValue); + }, + 287 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(8-6)], $self->semStack[$stackPos-(8-8)], $self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(8-2)], $self->semStack[$stackPos-(8-1)]); + $self->checkParam($self->semValue); + }, + 288 => static function ($self, $stackPos) { + $self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + }, + 289 => null, + 290 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 291 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 292 => null, + 293 => null, + 294 => static function ($self, $stackPos) { + $self->semValue = new Node\Name('static', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 295 => static function ($self, $stackPos) { + $self->semValue = $self->handleBuiltinTypes($self->semStack[$stackPos-(1-1)]); + }, + 296 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('array', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 297 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('callable', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 298 => null, + 299 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 300 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 301 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 302 => null, + 303 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 304 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 305 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 306 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 307 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 308 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 309 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 310 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 311 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 312 => null, + 313 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 314 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 315 => null, + 316 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 317 => null, + 318 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 319 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 320 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 321 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 322 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 323 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 324 => static function ($self, $stackPos) { + $self->semValue = new Node\VariadicPlaceholder($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 325 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 326 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 327 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(1-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 328 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], true, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 329 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], false, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 330 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(3-3)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(3-1)]); + }, + 331 => null, + 332 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 333 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 334 => null, + 335 => null, + 336 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 337 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 338 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 339 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 340 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; } else { $self->semValue = $self->semStack[$stackPos-(2-1)]; } + }, + 341 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 342 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 343 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Property($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-1)]); + $self->checkProperty($self->semValue, $stackPos-(5-2)); + }, + 344 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-1)]); + $self->checkClassConst($self->semValue, $stackPos-(5-2)); + }, + 345 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-1)], $self->semStack[$stackPos-(6-4)]); + $self->checkClassConst($self->semValue, $stackPos-(6-2)); + }, + 346 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassMethod($self->semStack[$stackPos-(10-5)], ['type' => $self->semStack[$stackPos-(10-2)], 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-7)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + $self->checkClassMethod($self->semValue, $stackPos-(10-2)); + }, + 347 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUse($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 348 => static function ($self, $stackPos) { + $self->semValue = new Stmt\EnumCase($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 349 => static function ($self, $stackPos) { + $self->semValue = null; /* will be skipped */ + }, + 350 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 351 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 352 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 353 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 354 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Precedence($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 355 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(5-1)][0], $self->semStack[$stackPos-(5-1)][1], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 356 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 357 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 358 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 359 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 360 => null, + 361 => static function ($self, $stackPos) { + $self->semValue = array(null, $self->semStack[$stackPos-(1-1)]); + }, + 362 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 363 => null, + 364 => null, + 365 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 366 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 367 => null, + 368 => null, + 369 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 370 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 371 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 372 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 373 => static function ($self, $stackPos) { + $self->semValue = Modifiers::STATIC; + }, + 374 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 375 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 376 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 377 => null, + 378 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 379 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 380 => static function ($self, $stackPos) { + $self->semValue = new Node\VarLikeIdentifier(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 381 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 382 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 383 => null, + 384 => null, + 385 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 386 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 387 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 388 => null, + 389 => null, + 390 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 391 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->fixupArrayDestructuring($self->semStack[$stackPos-(3-1)]), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 392 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 393 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 394 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + if (!$self->phpVersion->allowsAssignNewByReference()) { + $self->emitError(new Error('Cannot assign new by reference', $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + } + + }, + 395 => null, + 396 => null, + 397 => static function ($self, $stackPos) { + $self->semValue = new Expr\Clone_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 398 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 399 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 400 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 401 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 402 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 403 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 404 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 405 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 406 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 407 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 408 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 409 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 410 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 411 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostInc($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 412 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreInc($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 413 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostDec($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 414 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreDec($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 415 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 416 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 417 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 418 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 419 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 420 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 421 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 422 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 423 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 424 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 425 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 426 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 427 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 428 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 429 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 430 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 431 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 432 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 433 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryPlus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 434 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryMinus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 435 => static function ($self, $stackPos) { + $self->semValue = new Expr\BooleanNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 436 => static function ($self, $stackPos) { + $self->semValue = new Expr\BitwiseNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 437 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Identical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 438 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotIdentical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 439 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Equal($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 440 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 441 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Spaceship($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 442 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Smaller($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 443 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\SmallerOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 444 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Greater($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 445 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\GreaterOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 446 => static function ($self, $stackPos) { + $self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 447 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 448 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 449 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(4-1)], null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 450 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 451 => static function ($self, $stackPos) { + $self->semValue = new Expr\Isset_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 452 => static function ($self, $stackPos) { + $self->semValue = new Expr\Empty_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 453 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 454 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 455 => static function ($self, $stackPos) { + $self->semValue = new Expr\Eval_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 456 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 457 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 458 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 459 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getFloatCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); + }, + 460 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 461 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 462 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 463 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 464 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 465 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = strtolower($self->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $self->semValue = new Expr\Exit_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 466 => static function ($self, $stackPos) { + $self->semValue = new Expr\ErrorSuppress($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 467 => null, + 468 => static function ($self, $stackPos) { + $self->semValue = new Expr\ShellExec($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 469 => static function ($self, $stackPos) { + $self->semValue = new Expr\Print_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 470 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_(null, null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 471 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(2-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 472 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 473 => static function ($self, $stackPos) { + $self->semValue = new Expr\YieldFrom($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 474 => static function ($self, $stackPos) { + $self->semValue = new Expr\Throw_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 475 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'returnType' => $self->semStack[$stackPos-(8-6)], 'expr' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 476 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 477 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'uses' => $self->semStack[$stackPos-(8-6)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 478 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 479 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 480 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'returnType' => $self->semStack[$stackPos-(10-8)], 'expr' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 481 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 482 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'uses' => $self->semStack[$stackPos-(10-8)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 483 => static function ($self, $stackPos) { + $self->semValue = array(new Stmt\Class_(null, ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])), $self->semStack[$stackPos-(8-3)]); + $self->checkClass($self->semValue[0], -1); + }, + 484 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 485 => static function ($self, $stackPos) { + list($class, $ctorArgs) = $self->semStack[$stackPos-(2-2)]; $self->semValue = new Expr\New_($class, $ctorArgs, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 486 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(2-2)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 487 => null, + 488 => null, + 489 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 490 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 491 => null, + 492 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 493 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 494 => static function ($self, $stackPos) { + $self->semValue = new Node\ClosureUse($self->semStack[$stackPos-(2-2)], $self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 495 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 496 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 497 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 498 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 499 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 500 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 501 => null, + 502 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 503 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 504 => static function ($self, $stackPos) { + $self->semValue = new Name\FullyQualified(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 505 => static function ($self, $stackPos) { + $self->semValue = new Name\Relative(substr($self->semStack[$stackPos-(1-1)], 10), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 506 => null, + 507 => null, + 508 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 509 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 510 => null, + 511 => null, + 512 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 513 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 514 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 515 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); foreach ($self->semValue as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; + }, + 516 => static function ($self, $stackPos) { + foreach ($self->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 517 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 518 => null, + 519 => static function ($self, $stackPos) { + $self->semValue = new Expr\ConstFetch($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 520 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Line($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 521 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\File($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 522 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Dir($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 523 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Class_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 524 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Trait_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 525 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Method($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 526 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Function_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 527 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Namespace_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 528 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 529 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 530 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)])), $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 531 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(3-2)], $attrs); + }, + 532 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); + $self->createdArrays->attach($self->semValue); + }, + 533 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue); + }, + 534 => static function ($self, $stackPos) { + $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); + }, + 535 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($self->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = new Scalar\InterpolatedString($self->semStack[$stackPos-(3-2)], $attrs); + }, + 536 => static function ($self, $stackPos) { + $self->semValue = $self->parseLNumber($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->allowsInvalidOctals()); + }, + 537 => static function ($self, $stackPos) { + $self->semValue = Scalar\Float_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 538 => null, + 539 => null, + 540 => null, + 541 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 542 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(2-1)], '', $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(2-2)], $self->tokenEndStack[$stackPos-(2-2)]), true); + }, + 543 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 544 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 545 => null, + 546 => null, + 547 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 548 => null, + 549 => null, + 550 => null, + 551 => null, + 552 => null, + 553 => null, + 554 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 555 => null, + 556 => null, + 557 => null, + 558 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 559 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 560 => null, + 561 => static function ($self, $stackPos) { + $self->semValue = new Expr\MethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 562 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafeMethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 563 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 564 => null, + 565 => null, + 566 => null, + 567 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 568 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 569 => null, + 570 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 571 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 572 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])), $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 573 => static function ($self, $stackPos) { + $var = $self->semStack[$stackPos-(1-1)]->name; $self->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])) : $var; + }, + 574 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 575 => null, + 576 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 577 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 578 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 579 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 580 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 581 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 582 => null, + 583 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 584 => null, + 585 => null, + 586 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 587 => null, + 588 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 589 => static function ($self, $stackPos) { + $self->semValue = new Expr\List_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); $self->semValue->setAttribute('kind', Expr\List_::KIND_LIST); + $self->postprocessList($self->semValue); + }, + 590 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $end = count($self->semValue)-1; if ($self->semValue[$end]->value instanceof Expr\Error) array_pop($self->semValue); + }, + 591 => null, + 592 => static function ($self, $stackPos) { + /* do nothing -- prevent default action of $$=$self->semStack[$1]. See $551. */ + }, + 593 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 594 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 595 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 596 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 597 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 598 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 599 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-1)], true, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 600 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 601 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), true); + }, + 602 => static function ($self, $stackPos) { + /* Create an Error node now to remember the position. We'll later either report an error, + or convert this into a null element, depending on whether this is a creation or destructuring context. */ + $attrs = $self->createEmptyElemAttributes($self->tokenPos); + $self->semValue = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); + }, + 603 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 604 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 605 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 606 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)]); + }, + 607 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); $attrs['rawValue'] = $self->semStack[$stackPos-(1-1)]; $self->semValue = new Node\InterpolatedStringPart($self->semStack[$stackPos-(1-1)], $attrs); + }, + 608 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 609 => null, + 610 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 611 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 612 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 613 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 614 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 615 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 616 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 617 => static function ($self, $stackPos) { + $self->semValue = new Scalar\String_($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 618 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 619 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString('-' . $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 620 => null, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php new file mode 100644 index 0000000..4272331 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php @@ -0,0 +1,1241 @@ + Map of PHP token IDs to drop */ + protected array $dropTokens; + /** @var int[] Map of external symbols (static::T_*) to internal symbols */ + protected array $tokenToSymbol; + /** @var string[] Map of symbols to their names */ + protected array $symbolToName; + /** @var array Names of the production rules (only necessary for debugging) */ + protected array $productions; + + /** @var int[] Map of states to a displacement into the $action table. The corresponding action for this + * state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the + * action is defaulted, i.e. $actionDefault[$state] should be used instead. */ + protected array $actionBase; + /** @var int[] Table of actions. Indexed according to $actionBase comment. */ + protected array $action; + /** @var int[] Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol + * then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */ + protected array $actionCheck; + /** @var int[] Map of states to their default action */ + protected array $actionDefault; + /** @var callable[] Semantic action callbacks */ + protected array $reduceCallbacks; + + /** @var int[] Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this + * non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */ + protected array $gotoBase; + /** @var int[] Table of states to goto after reduction. Indexed according to $gotoBase comment. */ + protected array $goto; + /** @var int[] Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal + * then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */ + protected array $gotoCheck; + /** @var int[] Map of non-terminals to the default state to goto after their reduction */ + protected array $gotoDefault; + + /** @var int[] Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for + * determining the state to goto after reduction. */ + protected array $ruleToNonTerminal; + /** @var int[] Map of rules to the length of their right-hand side, which is the number of elements that have to + * be popped from the stack(s) on reduction. */ + protected array $ruleToLength; + + /* + * The following members are part of the parser state: + */ + + /** @var mixed Temporary value containing the result of last semantic action (reduction) */ + protected $semValue; + /** @var mixed[] Semantic value stack (contains values of tokens and semantic action results) */ + protected array $semStack; + /** @var int[] Token start position stack */ + protected array $tokenStartStack; + /** @var int[] Token end position stack */ + protected array $tokenEndStack; + + /** @var ErrorHandler Error handler */ + protected ErrorHandler $errorHandler; + /** @var int Error state, used to avoid error floods */ + protected int $errorState; + + /** @var \SplObjectStorage|null Array nodes created during parsing, for postprocessing of empty elements. */ + protected ?\SplObjectStorage $createdArrays; + + /** @var Token[] Tokens for the current parse */ + protected array $tokens; + /** @var int Current position in token array */ + protected int $tokenPos; + + /** + * Initialize $reduceCallbacks map. + */ + abstract protected function initReduceCallbacks(): void; + + /** + * Creates a parser instance. + * + * Options: + * * phpVersion: ?PhpVersion, + * + * @param Lexer $lexer A lexer + * @param PhpVersion $phpVersion PHP version to target, defaults to latest supported. This + * option is best-effort: Even if specified, parsing will generally assume the latest + * supported version and only adjust behavior in minor ways, for example by omitting + * errors in older versions and interpreting type hints as a name or identifier depending + * on version. + */ + public function __construct(Lexer $lexer, ?PhpVersion $phpVersion = null) { + $this->lexer = $lexer; + $this->phpVersion = $phpVersion ?? PhpVersion::getNewestSupported(); + + $this->initReduceCallbacks(); + $this->phpTokenToSymbol = $this->createTokenMap(); + $this->dropTokens = array_fill_keys( + [\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], true + ); + } + + /** + * Parses PHP code into a node tree. + * + * If a non-throwing error handler is used, the parser will continue parsing after an error + * occurred and attempt to build a partial AST. + * + * @param string $code The source code to parse + * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults + * to ErrorHandler\Throwing. + * + * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and + * the parser was unable to recover from an error). + */ + public function parse(string $code, ?ErrorHandler $errorHandler = null): ?array { + $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing(); + $this->createdArrays = new \SplObjectStorage(); + + $this->tokens = $this->lexer->tokenize($code, $this->errorHandler); + $result = $this->doParse(); + + // Report errors for any empty elements used inside arrays. This is delayed until after the main parse, + // because we don't know a priori whether a given array expression will be used in a destructuring context + // or not. + foreach ($this->createdArrays as $node) { + foreach ($node->items as $item) { + if ($item->value instanceof Expr\Error) { + $this->errorHandler->handleError( + new Error('Cannot use empty array elements in arrays', $item->getAttributes())); + } + } + } + + // Clear out some of the interior state, so we don't hold onto unnecessary + // memory between uses of the parser + $this->tokenStartStack = []; + $this->tokenEndStack = []; + $this->semStack = []; + $this->semValue = null; + $this->createdArrays = null; + + if ($result !== null) { + $traverser = new NodeTraverser(new CommentAnnotatingVisitor($this->tokens)); + $traverser->traverse($result); + } + + return $result; + } + + public function getTokens(): array { + return $this->tokens; + } + + /** @return Stmt[]|null */ + protected function doParse(): ?array { + // We start off with no lookahead-token + $symbol = self::SYMBOL_NONE; + $tokenValue = null; + $this->tokenPos = -1; + + // Keep stack of start and end attributes + $this->tokenStartStack = []; + $this->tokenEndStack = [0]; + + // Start off in the initial state and keep a stack of previous states + $state = 0; + $stateStack = [$state]; + + // Semantic value stack (contains values of tokens and semantic action results) + $this->semStack = []; + + // Current position in the stack(s) + $stackPos = 0; + + $this->errorState = 0; + + for (;;) { + //$this->traceNewState($state, $symbol); + + if ($this->actionBase[$state] === 0) { + $rule = $this->actionDefault[$state]; + } else { + if ($symbol === self::SYMBOL_NONE) { + do { + $token = $this->tokens[++$this->tokenPos]; + $tokenId = $token->id; + } while (isset($this->dropTokens[$tokenId])); + + // Map the lexer token id to the internally used symbols. + $tokenValue = $token->text; + if (!isset($this->phpTokenToSymbol[$tokenId])) { + throw new \RangeException(sprintf( + 'The lexer returned an invalid token (id=%d, value=%s)', + $tokenId, $tokenValue + )); + } + $symbol = $this->phpTokenToSymbol[$tokenId]; + + //$this->traceRead($symbol); + } + + $idx = $this->actionBase[$state] + $symbol; + if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)) + && ($action = $this->action[$idx]) !== $this->defaultAction) { + /* + * >= numNonLeafStates: shift and reduce + * > 0: shift + * = 0: accept + * < 0: reduce + * = -YYUNEXPECTED: error + */ + if ($action > 0) { + /* shift */ + //$this->traceShift($symbol); + + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + $this->semStack[$stackPos] = $tokenValue; + $this->tokenStartStack[$stackPos] = $this->tokenPos; + $this->tokenEndStack[$stackPos] = $this->tokenPos; + $symbol = self::SYMBOL_NONE; + + if ($this->errorState) { + --$this->errorState; + } + + if ($action < $this->numNonLeafStates) { + continue; + } + + /* $yyn >= numNonLeafStates means shift-and-reduce */ + $rule = $action - $this->numNonLeafStates; + } else { + $rule = -$action; + } + } else { + $rule = $this->actionDefault[$state]; + } + } + + for (;;) { + if ($rule === 0) { + /* accept */ + //$this->traceAccept(); + return $this->semValue; + } + if ($rule !== $this->unexpectedTokenRule) { + /* reduce */ + //$this->traceReduce($rule); + + $ruleLength = $this->ruleToLength[$rule]; + try { + $callback = $this->reduceCallbacks[$rule]; + if ($callback !== null) { + $callback($this, $stackPos); + } elseif ($ruleLength > 0) { + $this->semValue = $this->semStack[$stackPos - $ruleLength + 1]; + } + } catch (Error $e) { + if (-1 === $e->getStartLine()) { + $e->setStartLine($this->tokens[$this->tokenPos]->line); + } + + $this->emitError($e); + // Can't recover from this type of error + return null; + } + + /* Goto - shift nonterminal */ + $lastTokenEnd = $this->tokenEndStack[$stackPos]; + $stackPos -= $ruleLength; + $nonTerminal = $this->ruleToNonTerminal[$rule]; + $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos]; + if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) { + $state = $this->goto[$idx]; + } else { + $state = $this->gotoDefault[$nonTerminal]; + } + + ++$stackPos; + $stateStack[$stackPos] = $state; + $this->semStack[$stackPos] = $this->semValue; + $this->tokenEndStack[$stackPos] = $lastTokenEnd; + if ($ruleLength === 0) { + // Empty productions use the start attributes of the lookahead token. + $this->tokenStartStack[$stackPos] = $this->tokenPos; + } + } else { + /* error */ + switch ($this->errorState) { + case 0: + $msg = $this->getErrorMessage($symbol, $state); + $this->emitError(new Error($msg, $this->getAttributesForToken($this->tokenPos))); + // Break missing intentionally + // no break + case 1: + case 2: + $this->errorState = 3; + + // Pop until error-expecting state uncovered + while (!( + (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + ) || ($action = $this->action[$idx]) === $this->defaultAction) { // Not totally sure about this + if ($stackPos <= 0) { + // Could not recover from error + return null; + } + $state = $stateStack[--$stackPos]; + //$this->tracePop($state); + } + + //$this->traceShift($this->errorSymbol); + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + + // We treat the error symbol as being empty, so we reset the end attributes + // to the end attributes of the last non-error symbol + $this->tokenStartStack[$stackPos] = $this->tokenPos; + $this->tokenEndStack[$stackPos] = $this->tokenEndStack[$stackPos - 1]; + break; + + case 3: + if ($symbol === 0) { + // Reached EOF without recovering from error + return null; + } + + //$this->traceDiscard($symbol); + $symbol = self::SYMBOL_NONE; + break 2; + } + } + + if ($state < $this->numNonLeafStates) { + break; + } + + /* >= numNonLeafStates means shift-and-reduce */ + $rule = $state - $this->numNonLeafStates; + } + } + + throw new \RuntimeException('Reached end of parser loop'); + } + + protected function emitError(Error $error): void { + $this->errorHandler->handleError($error); + } + + /** + * Format error message including expected tokens. + * + * @param int $symbol Unexpected symbol + * @param int $state State at time of error + * + * @return string Formatted error message + */ + protected function getErrorMessage(int $symbol, int $state): string { + $expectedString = ''; + if ($expected = $this->getExpectedTokens($state)) { + $expectedString = ', expecting ' . implode(' or ', $expected); + } + + return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString; + } + + /** + * Get limited number of expected tokens in given state. + * + * @param int $state State + * + * @return string[] Expected tokens. If too many, an empty array is returned. + */ + protected function getExpectedTokens(int $state): array { + $expected = []; + + $base = $this->actionBase[$state]; + foreach ($this->symbolToName as $symbol => $name) { + $idx = $base + $symbol; + if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + || $state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + ) { + if ($this->action[$idx] !== $this->unexpectedTokenRule + && $this->action[$idx] !== $this->defaultAction + && $symbol !== $this->errorSymbol + ) { + if (count($expected) === 4) { + /* Too many expected tokens */ + return []; + } + + $expected[] = $name; + } + } + } + + return $expected; + } + + /** + * Get attributes for a node with the given start and end token positions. + * + * @param int $tokenStartPos Token position the node starts at + * @param int $tokenEndPos Token position the node ends at + * @return array Attributes + */ + protected function getAttributes(int $tokenStartPos, int $tokenEndPos): array { + $startToken = $this->tokens[$tokenStartPos]; + $afterEndToken = $this->tokens[$tokenEndPos + 1]; + return [ + 'startLine' => $startToken->line, + 'startTokenPos' => $tokenStartPos, + 'startFilePos' => $startToken->pos, + 'endLine' => $afterEndToken->line, + 'endTokenPos' => $tokenEndPos, + 'endFilePos' => $afterEndToken->pos - 1, + ]; + } + + /** + * Get attributes for a single token at the given token position. + * + * @return array Attributes + */ + protected function getAttributesForToken(int $tokenPos): array { + if ($tokenPos < \count($this->tokens) - 1) { + return $this->getAttributes($tokenPos, $tokenPos); + } + + // Get attributes for the sentinel token. + $token = $this->tokens[$tokenPos]; + return [ + 'startLine' => $token->line, + 'startTokenPos' => $tokenPos, + 'startFilePos' => $token->pos, + 'endLine' => $token->line, + 'endTokenPos' => $tokenPos, + 'endFilePos' => $token->pos, + ]; + } + + /* + * Tracing functions used for debugging the parser. + */ + + /* + protected function traceNewState($state, $symbol): void { + echo '% State ' . $state + . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n"; + } + + protected function traceRead($symbol): void { + echo '% Reading ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceShift($symbol): void { + echo '% Shift ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceAccept(): void { + echo "% Accepted.\n"; + } + + protected function traceReduce($n): void { + echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n"; + } + + protected function tracePop($state): void { + echo '% Recovering, uncovered state ' . $state . "\n"; + } + + protected function traceDiscard($symbol): void { + echo '% Discard ' . $this->symbolToName[$symbol] . "\n"; + } + */ + + /* + * Helper functions invoked by semantic actions + */ + + /** + * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions. + * + * @param Node\Stmt[] $stmts + * @return Node\Stmt[] + */ + protected function handleNamespaces(array $stmts): array { + $hasErrored = false; + $style = $this->getNamespacingStyle($stmts); + if (null === $style) { + // not namespaced, nothing to do + return $stmts; + } + if ('brace' === $style) { + // For braced namespaces we only have to check that there are no invalid statements between the namespaces + $afterFirstNamespace = false; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $afterFirstNamespace = true; + } elseif (!$stmt instanceof Node\Stmt\HaltCompiler + && !$stmt instanceof Node\Stmt\Nop + && $afterFirstNamespace && !$hasErrored) { + $this->emitError(new Error( + 'No code may exist outside of namespace {}', $stmt->getAttributes())); + $hasErrored = true; // Avoid one error for every statement + } + } + return $stmts; + } else { + // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts + $resultStmts = []; + $targetStmts = &$resultStmts; + $lastNs = null; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + if ($stmt->stmts === null) { + $stmt->stmts = []; + $targetStmts = &$stmt->stmts; + $resultStmts[] = $stmt; + } else { + // This handles the invalid case of mixed style namespaces + $resultStmts[] = $stmt; + $targetStmts = &$resultStmts; + } + $lastNs = $stmt; + } elseif ($stmt instanceof Node\Stmt\HaltCompiler) { + // __halt_compiler() is not moved into the namespace + $resultStmts[] = $stmt; + } else { + $targetStmts[] = $stmt; + } + } + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + return $resultStmts; + } + } + + private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt): void { + // We moved the statements into the namespace node, as such the end of the namespace node + // needs to be extended to the end of the statements. + if (empty($stmt->stmts)) { + return; + } + + // We only move the builtin end attributes here. This is the best we can do with the + // knowledge we have. + $endAttributes = ['endLine', 'endFilePos', 'endTokenPos']; + $lastStmt = $stmt->stmts[count($stmt->stmts) - 1]; + foreach ($endAttributes as $endAttribute) { + if ($lastStmt->hasAttribute($endAttribute)) { + $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute)); + } + } + } + + /** @return array */ + private function getNamespaceErrorAttributes(Namespace_ $node): array { + $attrs = $node->getAttributes(); + // Adjust end attributes to only cover the "namespace" keyword, not the whole namespace. + if (isset($attrs['startLine'])) { + $attrs['endLine'] = $attrs['startLine']; + } + if (isset($attrs['startTokenPos'])) { + $attrs['endTokenPos'] = $attrs['startTokenPos']; + } + if (isset($attrs['startFilePos'])) { + $attrs['endFilePos'] = $attrs['startFilePos'] + \strlen('namespace') - 1; + } + return $attrs; + } + + /** + * Determine namespacing style (semicolon or brace) + * + * @param Node[] $stmts Top-level statements. + * + * @return null|string One of "semicolon", "brace" or null (no namespaces) + */ + private function getNamespacingStyle(array $stmts): ?string { + $style = null; + $hasNotAllowedStmts = false; + foreach ($stmts as $i => $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace'; + if (null === $style) { + $style = $currentStyle; + if ($hasNotAllowedStmts) { + $this->emitError(new Error( + 'Namespace declaration statement has to be the very first statement in the script', + $this->getNamespaceErrorAttributes($stmt) + )); + } + } elseif ($style !== $currentStyle) { + $this->emitError(new Error( + 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations', + $this->getNamespaceErrorAttributes($stmt) + )); + // Treat like semicolon style for namespace normalization + return 'semicolon'; + } + continue; + } + + /* declare(), __halt_compiler() and nops can be used before a namespace declaration */ + if ($stmt instanceof Node\Stmt\Declare_ + || $stmt instanceof Node\Stmt\HaltCompiler + || $stmt instanceof Node\Stmt\Nop) { + continue; + } + + /* There may be a hashbang line at the very start of the file */ + if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) { + continue; + } + + /* Everything else if forbidden before namespace declarations */ + $hasNotAllowedStmts = true; + } + return $style; + } + + /** @return Name|Identifier */ + protected function handleBuiltinTypes(Name $name) { + if (!$name->isUnqualified()) { + return $name; + } + + $lowerName = $name->toLowerString(); + if (!$this->phpVersion->supportsBuiltinType($lowerName)) { + return $name; + } + + return new Node\Identifier($lowerName, $name->getAttributes()); + } + + /** + * Get combined start and end attributes at a stack location + * + * @param int $stackPos Stack location + * + * @return array Combined start and end attributes + */ + protected function getAttributesAt(int $stackPos): array { + return $this->getAttributes($this->tokenStartStack[$stackPos], $this->tokenEndStack[$stackPos]); + } + + protected function getFloatCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'float') !== false) { + return Double::KIND_FLOAT; + } + + if (strpos($cast, 'real') !== false) { + return Double::KIND_REAL; + } + + return Double::KIND_DOUBLE; + } + + /** @param array $attributes */ + protected function parseLNumber(string $str, array $attributes, bool $allowInvalidOctal = false): Int_ { + try { + return Int_::fromString($str, $attributes, $allowInvalidOctal); + } catch (Error $error) { + $this->emitError($error); + // Use dummy value + return new Int_(0, $attributes); + } + } + + /** + * Parse a T_NUM_STRING token into either an integer or string node. + * + * @param string $str Number string + * @param array $attributes Attributes + * + * @return Int_|String_ Integer or string node. + */ + protected function parseNumString(string $str, array $attributes) { + if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) { + return new String_($str, $attributes); + } + + $num = +$str; + if (!is_int($num)) { + return new String_($str, $attributes); + } + + return new Int_($num, $attributes); + } + + /** @param array $attributes */ + protected function stripIndentation( + string $string, int $indentLen, string $indentChar, + bool $newlineAtStart, bool $newlineAtEnd, array $attributes + ): string { + if ($indentLen === 0) { + return $string; + } + + $start = $newlineAtStart ? '(?:(?<=\n)|\A)' : '(?<=\n)'; + $end = $newlineAtEnd ? '(?:(?=[\r\n])|\z)' : '(?=[\r\n])'; + $regex = '/' . $start . '([ \t]*)(' . $end . ')?/'; + return preg_replace_callback( + $regex, + function ($matches) use ($indentLen, $indentChar, $attributes) { + $prefix = substr($matches[1], 0, $indentLen); + if (false !== strpos($prefix, $indentChar === " " ? "\t" : " ")) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', $attributes + )); + } elseif (strlen($prefix) < $indentLen && !isset($matches[2])) { + $this->emitError(new Error( + 'Invalid body indentation level ' . + '(expecting an indentation level of at least ' . $indentLen . ')', + $attributes + )); + } + return substr($matches[0], strlen($prefix)); + }, + $string + ); + } + + /** + * @param string|(Expr|InterpolatedStringPart)[] $contents + * @param array $attributes + * @param array $endTokenAttributes + */ + protected function parseDocString( + string $startToken, $contents, string $endToken, + array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape + ): Expr { + $kind = strpos($startToken, "'") === false + ? String_::KIND_HEREDOC : String_::KIND_NOWDOC; + + $regex = '/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/'; + $result = preg_match($regex, $startToken, $matches); + assert($result === 1); + $label = $matches[1]; + + $result = preg_match('/\A[ \t]*/', $endToken, $matches); + assert($result === 1); + $indentation = $matches[0]; + + $attributes['kind'] = $kind; + $attributes['docLabel'] = $label; + $attributes['docIndentation'] = $indentation; + + $indentHasSpaces = false !== strpos($indentation, " "); + $indentHasTabs = false !== strpos($indentation, "\t"); + if ($indentHasSpaces && $indentHasTabs) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', + $endTokenAttributes + )); + + // Proceed processing as if this doc string is not indented + $indentation = ''; + } + + $indentLen = \strlen($indentation); + $indentChar = $indentHasSpaces ? " " : "\t"; + + if (\is_string($contents)) { + if ($contents === '') { + $attributes['rawValue'] = $contents; + return new String_('', $attributes); + } + + $contents = $this->stripIndentation( + $contents, $indentLen, $indentChar, true, true, $attributes + ); + $contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents); + $attributes['rawValue'] = $contents; + + if ($kind === String_::KIND_HEREDOC) { + $contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape); + } + + return new String_($contents, $attributes); + } else { + assert(count($contents) > 0); + if (!$contents[0] instanceof Node\InterpolatedStringPart) { + // If there is no leading encapsed string part, pretend there is an empty one + $this->stripIndentation( + '', $indentLen, $indentChar, true, false, $contents[0]->getAttributes() + ); + } + + $newContents = []; + foreach ($contents as $i => $part) { + if ($part instanceof Node\InterpolatedStringPart) { + $isLast = $i === \count($contents) - 1; + $part->value = $this->stripIndentation( + $part->value, $indentLen, $indentChar, + $i === 0, $isLast, $part->getAttributes() + ); + if ($isLast) { + $part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value); + } + $part->setAttribute('rawValue', $part->value); + $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape); + if ('' === $part->value) { + continue; + } + } + $newContents[] = $part; + } + return new InterpolatedString($newContents, $attributes); + } + } + + protected function createCommentFromToken(Token $token, int $tokenPos): Comment { + assert($token->id === \T_COMMENT || $token->id == \T_DOC_COMMENT); + return \T_DOC_COMMENT === $token->id + ? new Comment\Doc($token->text, $token->line, $token->pos, $tokenPos, + $token->getEndLine(), $token->getEndPos() - 1, $tokenPos) + : new Comment($token->text, $token->line, $token->pos, $tokenPos, + $token->getEndLine(), $token->getEndPos() - 1, $tokenPos); + } + + /** + * Get last comment before the given token position, if any + */ + protected function getCommentBeforeToken(int $tokenPos): ?Comment { + while (--$tokenPos >= 0) { + $token = $this->tokens[$tokenPos]; + if (!isset($this->dropTokens[$token->id])) { + break; + } + + if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) { + return $this->createCommentFromToken($token, $tokenPos); + } + } + return null; + } + + /** + * Create a zero-length nop to capture preceding comments, if any. + */ + protected function maybeCreateZeroLengthNop(int $tokenPos): ?Nop { + $comment = $this->getCommentBeforeToken($tokenPos); + if ($comment === null) { + return null; + } + + $commentEndLine = $comment->getEndLine(); + $commentEndFilePos = $comment->getEndFilePos(); + $commentEndTokenPos = $comment->getEndTokenPos(); + $attributes = [ + 'startLine' => $commentEndLine, + 'endLine' => $commentEndLine, + 'startFilePos' => $commentEndFilePos + 1, + 'endFilePos' => $commentEndFilePos, + 'startTokenPos' => $commentEndTokenPos + 1, + 'endTokenPos' => $commentEndTokenPos, + ]; + return new Nop($attributes); + } + + protected function maybeCreateNop(int $tokenStartPos, int $tokenEndPos): ?Nop { + if ($this->getCommentBeforeToken($tokenStartPos) === null) { + return null; + } + return new Nop($this->getAttributes($tokenStartPos, $tokenEndPos)); + } + + protected function handleHaltCompiler(): string { + // Prevent the lexer from returning any further tokens. + $nextToken = $this->tokens[$this->tokenPos + 1]; + $this->tokenPos = \count($this->tokens) - 2; + + // Return text after __halt_compiler. + return $nextToken->id === \T_INLINE_HTML ? $nextToken->text : ''; + } + + protected function inlineHtmlHasLeadingNewline(int $stackPos): bool { + $tokenPos = $this->tokenStartStack[$stackPos]; + $token = $this->tokens[$tokenPos]; + assert($token->id == \T_INLINE_HTML); + if ($tokenPos > 0) { + $prevToken = $this->tokens[$tokenPos - 1]; + assert($prevToken->id == \T_CLOSE_TAG); + return false !== strpos($prevToken->text, "\n") + || false !== strpos($prevToken->text, "\r"); + } + return true; + } + + /** + * @return array + */ + protected function createEmptyElemAttributes(int $tokenPos): array { + return $this->getAttributesForToken($tokenPos); + } + + protected function fixupArrayDestructuring(Array_ $node): Expr\List_ { + $this->createdArrays->detach($node); + return new Expr\List_(array_map(function (Node\ArrayItem $item) { + if ($item->value instanceof Expr\Error) { + // We used Error as a placeholder for empty elements, which are legal for destructuring. + return null; + } + if ($item->value instanceof Array_) { + return new Node\ArrayItem( + $this->fixupArrayDestructuring($item->value), + $item->key, $item->byRef, $item->getAttributes()); + } + return $item; + }, $node->items), ['kind' => Expr\List_::KIND_ARRAY] + $node->getAttributes()); + } + + protected function postprocessList(Expr\List_ $node): void { + foreach ($node->items as $i => $item) { + if ($item->value instanceof Expr\Error) { + // We used Error as a placeholder for empty elements, which are legal for destructuring. + $node->items[$i] = null; + } + } + } + + /** @param ElseIf_|Else_ $node */ + protected function fixupAlternativeElse($node): void { + // Make sure a trailing nop statement carrying comments is part of the node. + $numStmts = \count($node->stmts); + if ($numStmts !== 0 && $node->stmts[$numStmts - 1] instanceof Nop) { + $nopAttrs = $node->stmts[$numStmts - 1]->getAttributes(); + if (isset($nopAttrs['endLine'])) { + $node->setAttribute('endLine', $nopAttrs['endLine']); + } + if (isset($nopAttrs['endFilePos'])) { + $node->setAttribute('endFilePos', $nopAttrs['endFilePos']); + } + if (isset($nopAttrs['endTokenPos'])) { + $node->setAttribute('endTokenPos', $nopAttrs['endTokenPos']); + } + } + } + + protected function checkClassModifier(int $a, int $b, int $modifierPos): void { + try { + Modifiers::verifyClassModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkModifier(int $a, int $b, int $modifierPos): void { + // Jumping through some hoops here because verifyModifier() is also used elsewhere + try { + Modifiers::verifyModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkParam(Param $node): void { + if ($node->variadic && null !== $node->default) { + $this->emitError(new Error( + 'Variadic parameter cannot have a default value', + $node->default->getAttributes() + )); + } + } + + protected function checkTryCatch(TryCatch $node): void { + if (empty($node->catches) && null === $node->finally) { + $this->emitError(new Error( + 'Cannot use try without catch or finally', $node->getAttributes() + )); + } + } + + protected function checkNamespace(Namespace_ $node): void { + if (null !== $node->stmts) { + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Namespace_) { + $this->emitError(new Error( + 'Namespace declarations cannot be nested', $stmt->getAttributes() + )); + } + } + } + } + + private function checkClassName(?Identifier $name, int $namePos): void { + if (null !== $name && $name->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $name), + $this->getAttributesAt($namePos) + )); + } + } + + /** @param Name[] $interfaces */ + private function checkImplementedInterfaces(array $interfaces): void { + foreach ($interfaces as $interface) { + if ($interface->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface), + $interface->getAttributes() + )); + } + } + } + + protected function checkClass(Class_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + + if ($node->extends && $node->extends->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends), + $node->extends->getAttributes() + )); + } + + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkInterface(Interface_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->extends); + } + + protected function checkEnum(Enum_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkClassMethod(ClassMethod $node, int $modifierPos): void { + if ($node->flags & Modifiers::STATIC) { + switch ($node->name->toLowerString()) { + case '__construct': + $this->emitError(new Error( + sprintf('Constructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__destruct': + $this->emitError(new Error( + sprintf('Destructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__clone': + $this->emitError(new Error( + sprintf('Clone method %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + } + } + + if ($node->flags & Modifiers::READONLY) { + $this->emitError(new Error( + sprintf('Method %s() cannot be readonly', $node->name), + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkClassConst(ClassConst $node, int $modifierPos): void { + if ($node->flags & Modifiers::STATIC) { + $this->emitError(new Error( + "Cannot use 'static' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Modifiers::ABSTRACT) { + $this->emitError(new Error( + "Cannot use 'abstract' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Modifiers::READONLY) { + $this->emitError(new Error( + "Cannot use 'readonly' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkProperty(Property $node, int $modifierPos): void { + if ($node->flags & Modifiers::ABSTRACT) { + $this->emitError(new Error('Properties cannot be declared abstract', + $this->getAttributesAt($modifierPos))); + } + + if ($node->flags & Modifiers::FINAL) { + $this->emitError(new Error('Properties cannot be declared final', + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkUseUse(UseItem $node, int $namePos): void { + if ($node->alias && $node->alias->isSpecialClassName()) { + $this->emitError(new Error( + sprintf( + 'Cannot use %s as %s because \'%2$s\' is a special class name', + $node->name, $node->alias + ), + $this->getAttributesAt($namePos) + )); + } + } + + /** + * Creates the token map. + * + * The token map maps the PHP internal token identifiers + * to the identifiers used by the Parser. Additionally it + * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'. + * + * @return array The token map + */ + protected function createTokenMap(): array { + $tokenMap = []; + + for ($i = 0; $i < 1000; ++$i) { + if ($i < 256) { + // Single-char tokens use an identity mapping. + $tokenMap[$i] = $i; + } elseif (\T_DOUBLE_COLON === $i) { + // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM + $tokenMap[$i] = static::T_PAAMAYIM_NEKUDOTAYIM; + } elseif (\T_OPEN_TAG_WITH_ECHO === $i) { + // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO + $tokenMap[$i] = static::T_ECHO; + } elseif (\T_CLOSE_TAG === $i) { + // T_CLOSE_TAG is equivalent to ';' + $tokenMap[$i] = ord(';'); + } elseif ('UNKNOWN' !== $name = token_name($i)) { + if (defined($name = static::class . '::' . $name)) { + // Other tokens can be mapped directly + $tokenMap[$i] = constant($name); + } + } + } + + // Assign tokens for which we define compatibility constants, as token_name() does not know them. + $tokenMap[\T_FN] = static::T_FN; + $tokenMap[\T_COALESCE_EQUAL] = static::T_COALESCE_EQUAL; + $tokenMap[\T_NAME_QUALIFIED] = static::T_NAME_QUALIFIED; + $tokenMap[\T_NAME_FULLY_QUALIFIED] = static::T_NAME_FULLY_QUALIFIED; + $tokenMap[\T_NAME_RELATIVE] = static::T_NAME_RELATIVE; + $tokenMap[\T_MATCH] = static::T_MATCH; + $tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = static::T_NULLSAFE_OBJECT_OPERATOR; + $tokenMap[\T_ATTRIBUTE] = static::T_ATTRIBUTE; + $tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; + $tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG; + $tokenMap[\T_ENUM] = static::T_ENUM; + $tokenMap[\T_READONLY] = static::T_READONLY; + + // We have create a map from PHP token IDs to external symbol IDs. + // Now map them to the internal symbol ID. + $fullTokenMap = []; + foreach ($tokenMap as $phpToken => $extSymbol) { + $intSymbol = $this->tokenToSymbol[$extSymbol]; + if ($intSymbol === $this->invalidSymbol) { + continue; + } + $fullTokenMap[$phpToken] = $intSymbol; + } + + return $fullTokenMap; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php new file mode 100644 index 0000000..3a7586e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php @@ -0,0 +1,42 @@ +isHostVersion()) { + $lexer = new Lexer(); + } else { + $lexer = new Lexer\Emulative($version); + } + if ($version->id >= 80000) { + return new Php8($lexer, $version); + } + return new Php7($lexer, $version); + } + + /** + * Create a parser targeting the newest version supported by this library. Code for older + * versions will be accepted if there have been no relevant backwards-compatibility breaks in + * PHP. + */ + public function createForNewestSupportedVersion(): Parser { + return $this->createForVersion(PhpVersion::getNewestSupported()); + } + + /** + * Create a parser targeting the host PHP version, that is the PHP version we're currently + * running on. This parser will not use any token emulation. + */ + public function createForHostVersion(): Parser { + return $this->createForVersion(PhpVersion::getHostVersion()); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php b/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php new file mode 100644 index 0000000..db85b1e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php @@ -0,0 +1,164 @@ + 50100, + 'callable' => 50400, + 'bool' => 70000, + 'int' => 70000, + 'float' => 70000, + 'string' => 70000, + 'iterable' => 70100, + 'void' => 70100, + 'object' => 70200, + 'null' => 80000, + 'false' => 80000, + 'mixed' => 80000, + 'never' => 80100, + 'true' => 80200, + ]; + + private function __construct(int $id) { + $this->id = $id; + } + + /** + * Create a PhpVersion object from major and minor version components. + */ + public static function fromComponents(int $major, int $minor): self { + return new self($major * 10000 + $minor * 100); + } + + /** + * Get the newest PHP version supported by this library. Support for this version may be partial, + * if it is still under development. + */ + public static function getNewestSupported(): self { + return self::fromComponents(8, 3); + } + + /** + * Get the host PHP version, that is the PHP version we're currently running on. + */ + public static function getHostVersion(): self { + return self::fromComponents(\PHP_MAJOR_VERSION, \PHP_MINOR_VERSION); + } + + /** + * Parse the version from a string like "8.1". + */ + public static function fromString(string $version): self { + if (!preg_match('/^(\d+)\.(\d+)/', $version, $matches)) { + throw new \LogicException("Invalid PHP version \"$version\""); + } + return self::fromComponents((int) $matches[1], (int) $matches[2]); + } + + /** + * Check whether two versions are the same. + */ + public function equals(PhpVersion $other): bool { + return $this->id === $other->id; + } + + /** + * Check whether this version is greater than or equal to the argument. + */ + public function newerOrEqual(PhpVersion $other): bool { + return $this->id >= $other->id; + } + + /** + * Check whether this version is older than the argument. + */ + public function older(PhpVersion $other): bool { + return $this->id < $other->id; + } + + /** + * Check whether this is the host PHP version. + */ + public function isHostVersion(): bool { + return $this->equals(self::getHostVersion()); + } + + /** + * Check whether this PHP version supports the given builtin type. Type name must be lowercase. + */ + public function supportsBuiltinType(string $type): bool { + $minVersion = self::BUILTIN_TYPE_VERSIONS[$type] ?? null; + return $minVersion !== null && $this->id >= $minVersion; + } + + /** + * Whether this version supports [] array literals. + */ + public function supportsShortArraySyntax(): bool { + return $this->id >= 50400; + } + + /** + * Whether this version supports [] for destructuring. + */ + public function supportsShortArrayDestructuring(): bool { + return $this->id >= 70100; + } + + /** + * Whether this version supports flexible heredoc/nowdoc. + */ + public function supportsFlexibleHeredoc(): bool { + return $this->id >= 70300; + } + + /** + * Whether this version supports trailing commas in parameter lists. + */ + public function supportsTrailingCommaInParamList(): bool { + return $this->id >= 80000; + } + + /** + * Whether this version allows "$var =& new Obj". + */ + public function allowsAssignNewByReference(): bool { + return $this->id < 70000; + } + + /** + * Whether this version allows invalid octals like "08". + */ + public function allowsInvalidOctals(): bool { + return $this->id < 70000; + } + + /** + * Whether this version allows DEL (\x7f) to occur in identifiers. + */ + public function allowsDelInIdentifiers(): bool { + return $this->id < 70100; + } + + /** + * Whether this version supports yield in expression context without parentheses. + */ + public function supportsYieldWithoutParentheses(): bool { + return $this->id >= 70000; + } + + /** + * Whether this version supports unicode escape sequences in strings. + */ + public function supportsUnicodeEscapes(): bool { + return $this->id >= 70000; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php new file mode 100644 index 0000000..892c686 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php @@ -0,0 +1,51 @@ +pAttrGroups($node->attrGroups, true) + . $this->pModifiers($node->flags) + . ($node->type ? $this->p($node->type) . ' ' : '') + . ($node->byRef ? '&' : '') + . ($node->variadic ? '...' : '') + . $this->p($node->var) + . ($node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pArg(Node\Arg $node): string { + return ($node->name ? $node->name->toString() . ': ' : '') + . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node): string { + return '...'; + } + + protected function pConst(Node\Const_ $node): string { + return $node->name . ' = ' . $this->p($node->value); + } + + protected function pNullableType(Node\NullableType $node): string { + return '?' . $this->p($node->type); + } + + protected function pUnionType(Node\UnionType $node): string { + $types = []; + foreach ($node->types as $typeNode) { + if ($typeNode instanceof Node\IntersectionType) { + $types[] = '('. $this->p($typeNode) . ')'; + continue; + } + $types[] = $this->p($typeNode); + } + return implode('|', $types); + } + + protected function pIntersectionType(Node\IntersectionType $node): string { + return $this->pImplode($node->types, '&'); + } + + protected function pIdentifier(Node\Identifier $node): string { + return $node->name; + } + + protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node): string { + return '$' . $node->name; + } + + protected function pAttribute(Node\Attribute $node): string { + return $this->p($node->name) + . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); + } + + protected function pAttributeGroup(Node\AttributeGroup $node): string { + return '#[' . $this->pCommaSeparated($node->attrs) . ']'; + } + + // Names + + protected function pName(Name $node): string { + return $node->name; + } + + protected function pName_FullyQualified(Name\FullyQualified $node): string { + return '\\' . $node->name; + } + + protected function pName_Relative(Name\Relative $node): string { + return 'namespace\\' . $node->name; + } + + // Magic Constants + + protected function pScalar_MagicConst_Class(MagicConst\Class_ $node): string { + return '__CLASS__'; + } + + protected function pScalar_MagicConst_Dir(MagicConst\Dir $node): string { + return '__DIR__'; + } + + protected function pScalar_MagicConst_File(MagicConst\File $node): string { + return '__FILE__'; + } + + protected function pScalar_MagicConst_Function(MagicConst\Function_ $node): string { + return '__FUNCTION__'; + } + + protected function pScalar_MagicConst_Line(MagicConst\Line $node): string { + return '__LINE__'; + } + + protected function pScalar_MagicConst_Method(MagicConst\Method $node): string { + return '__METHOD__'; + } + + protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node): string { + return '__NAMESPACE__'; + } + + protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node): string { + return '__TRAIT__'; + } + + // Scalars + + private function indentString(string $str): string { + return str_replace("\n", $this->nl, $str); + } + + protected function pScalar_String(Scalar\String_ $node): string { + $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED); + switch ($kind) { + case Scalar\String_::KIND_NOWDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + $shouldIdent = $this->phpVersion->supportsFlexibleHeredoc(); + $nl = $shouldIdent ? $this->nl : $this->newline; + if ($node->value === '') { + return "<<<'$label'$nl$label{$this->docStringEndToken}"; + } + + // Make sure trailing \r is not combined with following \n into CRLF. + if ($node->value[strlen($node->value) - 1] !== "\r") { + $value = $shouldIdent ? $this->indentString($node->value) : $node->value; + return "<<<'$label'$nl$value$nl$label{$this->docStringEndToken}"; + } + } + /* break missing intentionally */ + // no break + case Scalar\String_::KIND_SINGLE_QUOTED: + return $this->pSingleQuotedString($node->value); + case Scalar\String_::KIND_HEREDOC: + $label = $node->getAttribute('docLabel'); + $escaped = $this->escapeString($node->value, null); + if ($label && !$this->containsEndLabel($escaped, $label)) { + $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; + if ($escaped === '') { + return "<<<$label$nl$label{$this->docStringEndToken}"; + } + + return "<<<$label$nl$escaped$nl$label{$this->docStringEndToken}"; + } + /* break missing intentionally */ + // no break + case Scalar\String_::KIND_DOUBLE_QUOTED: + return '"' . $this->escapeString($node->value, '"') . '"'; + } + throw new \Exception('Invalid string kind'); + } + + protected function pScalar_InterpolatedString(Scalar\InterpolatedString $node): string { + if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { + $label = $node->getAttribute('docLabel'); + if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { + $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; + if (count($node->parts) === 1 + && $node->parts[0] instanceof Node\InterpolatedStringPart + && $node->parts[0]->value === '' + ) { + return "<<<$label$nl$label{$this->docStringEndToken}"; + } + + return "<<<$label$nl" . $this->pEncapsList($node->parts, null) + . "$nl$label{$this->docStringEndToken}"; + } + } + return '"' . $this->pEncapsList($node->parts, '"') . '"'; + } + + protected function pScalar_Int(Scalar\Int_ $node): string { + if ($node->value === -\PHP_INT_MAX - 1) { + // PHP_INT_MIN cannot be represented as a literal, + // because the sign is not part of the literal + return '(-' . \PHP_INT_MAX . '-1)'; + } + + $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); + if (Scalar\Int_::KIND_DEC === $kind) { + return (string) $node->value; + } + + if ($node->value < 0) { + $sign = '-'; + $str = (string) -$node->value; + } else { + $sign = ''; + $str = (string) $node->value; + } + switch ($kind) { + case Scalar\Int_::KIND_BIN: + return $sign . '0b' . base_convert($str, 10, 2); + case Scalar\Int_::KIND_OCT: + return $sign . '0' . base_convert($str, 10, 8); + case Scalar\Int_::KIND_HEX: + return $sign . '0x' . base_convert($str, 10, 16); + } + throw new \Exception('Invalid number kind'); + } + + protected function pScalar_Float(Scalar\Float_ $node): string { + if (!is_finite($node->value)) { + if ($node->value === \INF) { + return '1.0E+1000'; + } + if ($node->value === -\INF) { + return '-1.0E+1000'; + } else { + return '\NAN'; + } + } + + // Try to find a short full-precision representation + $stringValue = sprintf('%.16G', $node->value); + if ($node->value !== (float) $stringValue) { + $stringValue = sprintf('%.17G', $node->value); + } + + // %G is locale dependent and there exists no locale-independent alternative. We don't want + // mess with switching locales here, so let's assume that a comma is the only non-standard + // decimal separator we may encounter... + $stringValue = str_replace(',', '.', $stringValue); + + // ensure that number is really printed as float + return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; + } + + // Assignments + + protected function pExpr_Assign(Expr\Assign $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Assign::class, $this->p($node->var) . ' = ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignRef(Expr\AssignRef $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\AssignRef::class, $this->p($node->var) . ' =& ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Plus(AssignOp\Plus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Plus::class, $this->p($node->var) . ' += ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Minus(AssignOp\Minus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Minus::class, $this->p($node->var) . ' -= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Mul(AssignOp\Mul $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Mul::class, $this->p($node->var) . ' *= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Div(AssignOp\Div $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Div::class, $this->p($node->var) . ' /= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Concat(AssignOp\Concat $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Concat::class, $this->p($node->var) . ' .= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Mod(AssignOp\Mod $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Mod::class, $this->p($node->var) . ' %= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseAnd::class, $this->p($node->var) . ' &= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseOr::class, $this->p($node->var) . ' |= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseXor::class, $this->p($node->var) . ' ^= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\ShiftLeft::class, $this->p($node->var) . ' <<= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\ShiftRight::class, $this->p($node->var) . ' >>= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Pow(AssignOp\Pow $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Pow::class, $this->p($node->var) . ' **= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Coalesce::class, $this->p($node->var) . ' ??= ', $node->expr, $precedence, $lhsPrecedence); + } + + // Binary expressions + + protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Div(BinaryOp\Div $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_Instanceof(Expr\Instanceof_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPostfixOp( + Expr\Instanceof_::class, $node->expr, + ' instanceof ' . $this->pNewOperand($node->class), + $precedence, $lhsPrecedence); + } + + // Unary expressions + + protected function pExpr_BooleanNot(Expr\BooleanNot $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_BitwiseNot(Expr\BitwiseNot $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_UnaryMinus(Expr\UnaryMinus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_UnaryPlus(Expr\UnaryPlus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_PreInc(Expr\PreInc $node): string { + return '++' . $this->p($node->var); + } + + protected function pExpr_PreDec(Expr\PreDec $node): string { + return '--' . $this->p($node->var); + } + + protected function pExpr_PostInc(Expr\PostInc $node): string { + return $this->p($node->var) . '++'; + } + + protected function pExpr_PostDec(Expr\PostDec $node): string { + return $this->p($node->var) . '--'; + } + + protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_YieldFrom(Expr\YieldFrom $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Print(Expr\Print_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr, $precedence, $lhsPrecedence); + } + + // Casts + + protected function pExpr_Cast_Int(Cast\Int_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Double(Cast\Double $node, int $precedence, int $lhsPrecedence): string { + $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); + if ($kind === Cast\Double::KIND_DOUBLE) { + $cast = '(double)'; + } elseif ($kind === Cast\Double::KIND_FLOAT) { + $cast = '(float)'; + } else { + assert($kind === Cast\Double::KIND_REAL); + $cast = '(real)'; + } + return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_String(Cast\String_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Array(Cast\Array_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Object(Cast\Object_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Bool(Cast\Bool_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Unset(Cast\Unset_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr, $precedence, $lhsPrecedence); + } + + // Function calls and similar constructs + + protected function pExpr_FuncCall(Expr\FuncCall $node): string { + return $this->pCallLhs($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_MethodCall(Expr\MethodCall $node): string { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node): string { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_StaticCall(Expr\StaticCall $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::' + . ($node->name instanceof Expr + ? ($node->name instanceof Expr\Variable + ? $this->p($node->name) + : '{' . $this->p($node->name) . '}') + : $node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Empty(Expr\Empty_ $node): string { + return 'empty(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Isset(Expr\Isset_ $node): string { + return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; + } + + protected function pExpr_Eval(Expr\Eval_ $node): string { + return 'eval(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Include(Expr\Include_ $node, int $precedence, int $lhsPrecedence): string { + static $map = [ + Expr\Include_::TYPE_INCLUDE => 'include', + Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once', + Expr\Include_::TYPE_REQUIRE => 'require', + Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once', + ]; + + return $this->pPrefixOp(Expr\Include_::class, $map[$node->type] . ' ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_List(Expr\List_ $node): string { + $syntax = $node->getAttribute('kind', + $this->phpVersion->supportsShortArrayDestructuring() ? Expr\List_::KIND_ARRAY : Expr\List_::KIND_LIST); + if ($syntax === Expr\List_::KIND_ARRAY) { + return '[' . $this->pMaybeMultiline($node->items, true) . ']'; + } else { + return 'list(' . $this->pMaybeMultiline($node->items, true) . ')'; + } + } + + // Other + + protected function pExpr_Error(Expr\Error $node): string { + throw new \LogicException('Cannot pretty-print AST with Error nodes'); + } + + protected function pExpr_Variable(Expr\Variable $node): string { + if ($node->name instanceof Expr) { + return '${' . $this->p($node->name) . '}'; + } else { + return '$' . $node->name; + } + } + + protected function pExpr_Array(Expr\Array_ $node): string { + $syntax = $node->getAttribute('kind', + $this->shortArraySyntax ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); + if ($syntax === Expr\Array_::KIND_SHORT) { + return '[' . $this->pMaybeMultiline($node->items, true) . ']'; + } else { + return 'array(' . $this->pMaybeMultiline($node->items, true) . ')'; + } + } + + protected function pKey(?Node $node): string { + if ($node === null) { + return ''; + } + + // => is not really an operator and does not typically participate in precedence resolution. + // However, there is an exception if yield expressions with keys are involved: + // [yield $a => $b] is interpreted as [(yield $a => $b)], so we need to ensure that + // [(yield $a) => $b] is printed with parentheses. We approximate this by lowering the LHS + // precedence to that of yield (which will also print unnecessary parentheses for rare low + // precedence unary operators like include). + $yieldPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; + return $this->p($node, self::MAX_PRECEDENCE, $yieldPrecedence) . ' => '; + } + + protected function pArrayItem(Node\ArrayItem $node): string { + return $this->pKey($node->key) + . ($node->byRef ? '&' : '') + . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node): string { + return $this->pDereferenceLhs($node->var) + . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; + } + + protected function pExpr_ConstFetch(Expr\ConstFetch $node): string { + return $this->p($node->name); + } + + protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::' . $this->pObjectProperty($node->name); + } + + protected function pExpr_PropertyFetch(Expr\PropertyFetch $node): string { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node): string { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); + } + + protected function pExpr_ShellExec(Expr\ShellExec $node): string { + return '`' . $this->pEncapsList($node->parts, '`') . '`'; + } + + protected function pExpr_Closure(Expr\Closure $node): string { + return $this->pAttrGroups($node->attrGroups, true) + . $this->pStatic($node->static) + . 'function ' . ($node->byRef ? '&' : '') + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '') + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pExpr_Match(Expr\Match_ $node): string { + return 'match (' . $this->p($node->cond) . ') {' + . $this->pCommaSeparatedMultiline($node->arms, true) + . $this->nl + . '}'; + } + + protected function pMatchArm(Node\MatchArm $node): string { + $result = ''; + if ($node->conds) { + for ($i = 0, $c = \count($node->conds); $i + 1 < $c; $i++) { + $result .= $this->p($node->conds[$i]) . ', '; + } + $result .= $this->pKey($node->conds[$i]); + } else { + $result = 'default => '; + } + return $result . $this->p($node->body); + } + + protected function pExpr_ArrowFunction(Expr\ArrowFunction $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp( + Expr\ArrowFunction::class, + $this->pAttrGroups($node->attrGroups, true) + . $this->pStatic($node->static) + . 'fn' . ($node->byRef ? '&' : '') + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' => ', + $node->expr, $precedence, $lhsPrecedence); + } + + protected function pClosureUse(Node\ClosureUse $node): string { + return ($node->byRef ? '&' : '') . $this->p($node->var); + } + + protected function pExpr_New(Expr\New_ $node): string { + if ($node->class instanceof Stmt\Class_) { + $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; + return 'new ' . $this->pClassCommon($node->class, $args); + } + return 'new ' . $this->pNewOperand($node->class) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Clone(Expr\Clone_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Clone_::class, 'clone ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Ternary(Expr\Ternary $node, int $precedence, int $lhsPrecedence): string { + // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator. + // this is okay because the part between ? and : never needs parentheses. + return $this->pInfixOp(Expr\Ternary::class, + $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else, + $precedence, $lhsPrecedence + ); + } + + protected function pExpr_Exit(Expr\Exit_ $node): string { + $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE); + return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die') + . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : ''); + } + + protected function pExpr_Throw(Expr\Throw_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Throw_::class, 'throw ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Yield(Expr\Yield_ $node, int $precedence, int $lhsPrecedence): string { + if ($node->value === null) { + $opPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; + return $opPrecedence >= $lhsPrecedence ? '(yield)' : 'yield'; + } else { + if (!$this->phpVersion->supportsYieldWithoutParentheses()) { + return '(yield ' . $this->pKey($node->key) . $this->p($node->value) . ')'; + } + return $this->pPrefixOp( + Expr\Yield_::class, 'yield ' . $this->pKey($node->key), + $node->value, $precedence, $lhsPrecedence); + } + } + + // Declarations + + protected function pStmt_Namespace(Stmt\Namespace_ $node): string { + if ($this->canUseSemicolonNamespaces) { + return 'namespace ' . $this->p($node->name) . ';' + . $this->nl . $this->pStmts($node->stmts, false); + } else { + return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + } + + protected function pStmt_Use(Stmt\Use_ $node): string { + return 'use ' . $this->pUseType($node->type) + . $this->pCommaSeparated($node->uses) . ';'; + } + + protected function pStmt_GroupUse(Stmt\GroupUse $node): string { + return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) + . '\{' . $this->pCommaSeparated($node->uses) . '};'; + } + + protected function pUseItem(Node\UseItem $node): string { + return $this->pUseType($node->type) . $this->p($node->name) + . (null !== $node->alias ? ' as ' . $node->alias : ''); + } + + protected function pUseType(int $type): string { + return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' + : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); + } + + protected function pStmt_Interface(Stmt\Interface_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'interface ' . $node->name + . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Enum(Stmt\Enum_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'enum ' . $node->name + . ($node->scalarType ? ' : ' . $this->p($node->scalarType) : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Class(Stmt\Class_ $node): string { + return $this->pClassCommon($node, ' ' . $node->name); + } + + protected function pStmt_Trait(Stmt\Trait_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'trait ' . $node->name + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_EnumCase(Stmt\EnumCase $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'case ' . $node->name + . ($node->expr ? ' = ' . $this->p($node->expr) : '') + . ';'; + } + + protected function pStmt_TraitUse(Stmt\TraitUse $node): string { + return 'use ' . $this->pCommaSeparated($node->traits) + . (empty($node->adaptations) + ? ';' + : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); + } + + protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node): string { + return $this->p($node->trait) . '::' . $node->method + . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; + } + + protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node): string { + return (null !== $node->trait ? $this->p($node->trait) . '::' : '') + . $node->method . ' as' + . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '') + . (null !== $node->newName ? ' ' . $node->newName : '') + . ';'; + } + + protected function pStmt_Property(Stmt\Property $node): string { + return $this->pAttrGroups($node->attrGroups) + . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) + . ($node->type ? $this->p($node->type) . ' ' : '') + . $this->pCommaSeparated($node->props) . ';'; + } + + protected function pPropertyItem(Node\PropertyItem $node): string { + return '$' . $node->name + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . (null !== $node->stmts + ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' + : ';'); + } + + protected function pStmt_ClassConst(Stmt\ClassConst $node): string { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'const ' + . (null !== $node->type ? $this->p($node->type) . ' ' : '') + . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Function(Stmt\Function_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Const(Stmt\Const_ $node): string { + return 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Declare(Stmt\Declare_ $node): string { + return 'declare (' . $this->pCommaSeparated($node->declares) . ')' + . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); + } + + protected function pDeclareItem(Node\DeclareItem $node): string { + return $node->key . '=' . $this->p($node->value); + } + + // Control flow + + protected function pStmt_If(Stmt\If_ $node): string { + return 'if (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '') + . (null !== $node->else ? ' ' . $this->p($node->else) : ''); + } + + protected function pStmt_ElseIf(Stmt\ElseIf_ $node): string { + return 'elseif (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Else(Stmt\Else_ $node): string { + if (\count($node->stmts) === 1 && $node->stmts[0] instanceof Stmt\If_) { + // Print as "else if" rather than "else { if }" + return 'else ' . $this->p($node->stmts[0]); + } + return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_For(Stmt\For_ $node): string { + return 'for (' + . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '') + . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '') + . $this->pCommaSeparated($node->loop) + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Foreach(Stmt\Foreach_ $node): string { + return 'foreach (' . $this->p($node->expr) . ' as ' + . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') + . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_While(Stmt\While_ $node): string { + return 'while (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Do(Stmt\Do_ $node): string { + return 'do {' . $this->pStmts($node->stmts) . $this->nl + . '} while (' . $this->p($node->cond) . ');'; + } + + protected function pStmt_Switch(Stmt\Switch_ $node): string { + return 'switch (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->cases) . $this->nl . '}'; + } + + protected function pStmt_Case(Stmt\Case_ $node): string { + return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' + . $this->pStmts($node->stmts); + } + + protected function pStmt_TryCatch(Stmt\TryCatch $node): string { + return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '') + . ($node->finally !== null ? ' ' . $this->p($node->finally) : ''); + } + + protected function pStmt_Catch(Stmt\Catch_ $node): string { + return 'catch (' . $this->pImplode($node->types, '|') + . ($node->var !== null ? ' ' . $this->p($node->var) : '') + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Finally(Stmt\Finally_ $node): string { + return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Break(Stmt\Break_ $node): string { + return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Continue(Stmt\Continue_ $node): string { + return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Return(Stmt\Return_ $node): string { + return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; + } + + protected function pStmt_Label(Stmt\Label $node): string { + return $node->name . ':'; + } + + protected function pStmt_Goto(Stmt\Goto_ $node): string { + return 'goto ' . $node->name . ';'; + } + + // Other + + protected function pStmt_Expression(Stmt\Expression $node): string { + return $this->p($node->expr) . ';'; + } + + protected function pStmt_Echo(Stmt\Echo_ $node): string { + return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; + } + + protected function pStmt_Static(Stmt\Static_ $node): string { + return 'static ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStmt_Global(Stmt\Global_ $node): string { + return 'global ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStaticVar(Node\StaticVar $node): string { + return $this->p($node->var) + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_Unset(Stmt\Unset_ $node): string { + return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; + } + + protected function pStmt_InlineHTML(Stmt\InlineHTML $node): string { + $newline = $node->getAttribute('hasLeadingNewline', true) ? $this->newline : ''; + return '?>' . $newline . $node->value . 'remaining; + } + + protected function pStmt_Nop(Stmt\Nop $node): string { + return ''; + } + + protected function pStmt_Block(Stmt\Block $node): string { + return '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + // Helpers + + protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string { + return $this->pAttrGroups($node->attrGroups, $node->name === null) + . $this->pModifiers($node->flags) + . 'class' . $afterClassToken + . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pObjectProperty(Node $node): string { + if ($node instanceof Expr) { + return '{' . $this->p($node) . '}'; + } else { + assert($node instanceof Node\Identifier); + return $node->name; + } + } + + /** @param (Expr|Node\InterpolatedStringPart)[] $encapsList */ + protected function pEncapsList(array $encapsList, ?string $quote): string { + $return = ''; + foreach ($encapsList as $element) { + if ($element instanceof Node\InterpolatedStringPart) { + $return .= $this->escapeString($element->value, $quote); + } else { + $return .= '{' . $this->p($element) . '}'; + } + } + + return $return; + } + + protected function pSingleQuotedString(string $string): string { + // It is idiomatic to only escape backslashes when necessary, i.e. when followed by ', \ or + // the end of the string ('Foo\Bar' instead of 'Foo\\Bar'). However, we also don't want to + // produce an odd number of backslashes, so '\\\\a' should not get rendered as '\\\a', even + // though that would be legal. + $regex = '/\'|\\\\(?=[\'\\\\]|$)|(?<=\\\\)\\\\/'; + return '\'' . preg_replace($regex, '\\\\$0', $string) . '\''; + } + + protected function escapeString(string $string, ?string $quote): string { + if (null === $quote) { + // For doc strings, don't escape newlines + $escaped = addcslashes($string, "\t\f\v$\\"); + // But do escape isolated \r. Combined with the terminating newline, it might get + // interpreted as \r\n and dropped from the string contents. + $escaped = preg_replace('/\r(?!\n)/', '\\r', $escaped); + if ($this->phpVersion->supportsFlexibleHeredoc()) { + $escaped = $this->indentString($escaped); + } + } else { + $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\"); + } + + // Escape control characters and non-UTF-8 characters. + // Regex based on https://stackoverflow.com/a/11709412/385378. + $regex = '/( + [\x00-\x08\x0E-\x1F] # Control characters + | [\xC0-\xC1] # Invalid UTF-8 Bytes + | [\xF5-\xFF] # Invalid UTF-8 Bytes + | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point + | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point + | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start + | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start + | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start + | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle + | (? $part) { + if ($part instanceof Node\InterpolatedStringPart + && $this->containsEndLabel($this->escapeString($part->value, null), $label, $i === 0) + ) { + return true; + } + } + return false; + } + + protected function pDereferenceLhs(Node $node): string { + if (!$this->dereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pStaticDereferenceLhs(Node $node): string { + if (!$this->staticDereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pCallLhs(Node $node): string { + if (!$this->callLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pNewOperand(Node $node): string { + if (!$this->newOperandRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + /** + * @param Node[] $nodes + */ + protected function hasNodeWithComments(array $nodes): bool { + foreach ($nodes as $node) { + if ($node && $node->getComments()) { + return true; + } + } + return false; + } + + /** @param Node[] $nodes */ + protected function pMaybeMultiline(array $nodes, bool $trailingComma = false): string { + if (!$this->hasNodeWithComments($nodes)) { + return $this->pCommaSeparated($nodes); + } else { + return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; + } + } + + /** @param Node\AttributeGroup[] $nodes */ + protected function pAttrGroups(array $nodes, bool $inline = false): string { + $result = ''; + $sep = $inline ? ' ' : $this->nl; + foreach ($nodes as $node) { + $result .= $this->p($node) . $sep; + } + + return $result; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php new file mode 100644 index 0000000..17f27a1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php @@ -0,0 +1,1655 @@ + */ + protected array $precedenceMap = [ + // [precedence, precedenceLHS, precedenceRHS] + // Where the latter two are the precedences to use for the LHS and RHS of a binary operator, + // where 1 is added to one of the sides depending on associativity. This information is not + // used for unary operators and set to -1. + Expr\Clone_::class => [-10, 0, 1], + BinaryOp\Pow::class => [ 0, 0, 1], + Expr\BitwiseNot::class => [ 10, -1, -1], + Expr\UnaryPlus::class => [ 10, -1, -1], + Expr\UnaryMinus::class => [ 10, -1, -1], + Cast\Int_::class => [ 10, -1, -1], + Cast\Double::class => [ 10, -1, -1], + Cast\String_::class => [ 10, -1, -1], + Cast\Array_::class => [ 10, -1, -1], + Cast\Object_::class => [ 10, -1, -1], + Cast\Bool_::class => [ 10, -1, -1], + Cast\Unset_::class => [ 10, -1, -1], + Expr\ErrorSuppress::class => [ 10, -1, -1], + Expr\Instanceof_::class => [ 20, -1, -1], + Expr\BooleanNot::class => [ 30, -1, -1], + BinaryOp\Mul::class => [ 40, 41, 40], + BinaryOp\Div::class => [ 40, 41, 40], + BinaryOp\Mod::class => [ 40, 41, 40], + BinaryOp\Plus::class => [ 50, 51, 50], + BinaryOp\Minus::class => [ 50, 51, 50], + BinaryOp\Concat::class => [ 50, 51, 50], + BinaryOp\ShiftLeft::class => [ 60, 61, 60], + BinaryOp\ShiftRight::class => [ 60, 61, 60], + BinaryOp\Smaller::class => [ 70, 70, 70], + BinaryOp\SmallerOrEqual::class => [ 70, 70, 70], + BinaryOp\Greater::class => [ 70, 70, 70], + BinaryOp\GreaterOrEqual::class => [ 70, 70, 70], + BinaryOp\Equal::class => [ 80, 80, 80], + BinaryOp\NotEqual::class => [ 80, 80, 80], + BinaryOp\Identical::class => [ 80, 80, 80], + BinaryOp\NotIdentical::class => [ 80, 80, 80], + BinaryOp\Spaceship::class => [ 80, 80, 80], + BinaryOp\BitwiseAnd::class => [ 90, 91, 90], + BinaryOp\BitwiseXor::class => [100, 101, 100], + BinaryOp\BitwiseOr::class => [110, 111, 110], + BinaryOp\BooleanAnd::class => [120, 121, 120], + BinaryOp\BooleanOr::class => [130, 131, 130], + BinaryOp\Coalesce::class => [140, 140, 141], + Expr\Ternary::class => [150, 150, 150], + Expr\Assign::class => [160, -1, -1], + Expr\AssignRef::class => [160, -1, -1], + AssignOp\Plus::class => [160, -1, -1], + AssignOp\Minus::class => [160, -1, -1], + AssignOp\Mul::class => [160, -1, -1], + AssignOp\Div::class => [160, -1, -1], + AssignOp\Concat::class => [160, -1, -1], + AssignOp\Mod::class => [160, -1, -1], + AssignOp\BitwiseAnd::class => [160, -1, -1], + AssignOp\BitwiseOr::class => [160, -1, -1], + AssignOp\BitwiseXor::class => [160, -1, -1], + AssignOp\ShiftLeft::class => [160, -1, -1], + AssignOp\ShiftRight::class => [160, -1, -1], + AssignOp\Pow::class => [160, -1, -1], + AssignOp\Coalesce::class => [160, -1, -1], + Expr\YieldFrom::class => [170, -1, -1], + Expr\Yield_::class => [175, -1, -1], + Expr\Print_::class => [180, -1, -1], + BinaryOp\LogicalAnd::class => [190, 191, 190], + BinaryOp\LogicalXor::class => [200, 201, 200], + BinaryOp\LogicalOr::class => [210, 211, 210], + Expr\Include_::class => [220, -1, -1], + Expr\ArrowFunction::class => [230, -1, -1], + Expr\Throw_::class => [240, -1, -1], + ]; + + /** @var int Current indentation level. */ + protected int $indentLevel; + /** @var string Newline style. Does not include current indentation. */ + protected string $newline; + /** @var string Newline including current indentation. */ + protected string $nl; + /** @var string|null Token placed at end of doc string to ensure it is followed by a newline. + * Null if flexible doc strings are used. */ + protected ?string $docStringEndToken; + /** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */ + protected bool $canUseSemicolonNamespaces; + /** @var bool Whether to use short array syntax if the node specifies no preference */ + protected bool $shortArraySyntax; + /** @var PhpVersion PHP version to target */ + protected PhpVersion $phpVersion; + + /** @var TokenStream|null Original tokens for use in format-preserving pretty print */ + protected ?TokenStream $origTokens; + /** @var Internal\Differ Differ for node lists */ + protected Differ $nodeListDiffer; + /** @var array Map determining whether a certain character is a label character */ + protected array $labelCharMap; + /** + * @var array> Map from token classes and subnode names to FIXUP_* constants. + * This is used during format-preserving prints to place additional parens/braces if necessary. + */ + protected array $fixupMap; + /** + * @var array Map from "{$node->getType()}->{$subNode}" + * to ['left' => $l, 'right' => $r], where $l and $r specify the token type that needs to be stripped + * when removing this node. + */ + protected array $removalMap; + /** + * @var array Map from + * "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight]. + * $find is an optional token after which the insertion occurs. $extraLeft/Right + * are optionally added before/after the main insertions. + */ + protected array $insertionMap; + /** + * @var array Map From "{$class}->{$subNode}" to string that should be inserted + * between elements of this list subnode. + */ + protected array $listInsertionMap; + + /** + * @var array + */ + protected array $emptyListInsertionMap; + /** @var array Map from "{$class}->{$subNode}" to [$printFn, $token] + * where $printFn is the function to print the modifiers and $token is the token before which + * the modifiers should be reprinted. */ + protected array $modifierChangeMap; + + /** + * Creates a pretty printer instance using the given options. + * + * Supported options: + * * PhpVersion $phpVersion: The PHP version to target (default to PHP 7.4). This option + * controls compatibility of the generated code with older PHP + * versions in cases where a simple stylistic choice exists (e.g. + * array() vs []). It is safe to pretty-print an AST for a newer + * PHP version while specifying an older target (but the result will + * of course not be compatible with the older version in that case). + * * string $newline: The newline style to use. Should be "\n" (default) or "\r\n". + * * bool $shortArraySyntax: Whether to use [] instead of array() as the default array + * syntax, if the node does not specify a format. Defaults to whether + * the phpVersion support short array syntax. + * + * @param array{ + * phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool + * } $options Dictionary of formatting options + */ + public function __construct(array $options = []) { + $this->phpVersion = $options['phpVersion'] ?? PhpVersion::fromComponents(7, 4); + + $this->newline = $options['newline'] ?? "\n"; + if ($this->newline !== "\n" && $this->newline != "\r\n") { + throw new \LogicException('Option "newline" must be one of "\n" or "\r\n"'); + } + + $this->shortArraySyntax = + $options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax(); + $this->docStringEndToken = + $this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand(); + } + + /** + * Reset pretty printing state. + */ + protected function resetState(): void { + $this->indentLevel = 0; + $this->nl = $this->newline; + $this->origTokens = null; + } + + /** + * Set indentation level + * + * @param int $level Level in number of spaces + */ + protected function setIndentLevel(int $level): void { + $this->indentLevel = $level; + $this->nl = $this->newline . \str_repeat(' ', $level); + } + + /** + * Increase indentation level. + */ + protected function indent(): void { + $this->indentLevel += 4; + $this->nl .= ' '; + } + + /** + * Decrease indentation level. + */ + protected function outdent(): void { + assert($this->indentLevel >= 4); + $this->indentLevel -= 4; + $this->nl = $this->newline . str_repeat(' ', $this->indentLevel); + } + + /** + * Pretty prints an array of statements. + * + * @param Node[] $stmts Array of statements + * + * @return string Pretty printed statements + */ + public function prettyPrint(array $stmts): string { + $this->resetState(); + $this->preprocessNodes($stmts); + + return ltrim($this->handleMagicTokens($this->pStmts($stmts, false))); + } + + /** + * Pretty prints an expression. + * + * @param Expr $node Expression node + * + * @return string Pretty printed node + */ + public function prettyPrintExpr(Expr $node): string { + $this->resetState(); + return $this->handleMagicTokens($this->p($node)); + } + + /** + * Pretty prints a file of statements (includes the opening newline . $this->newline; + } + + $p = "newline . $this->newline . $this->prettyPrint($stmts); + + if ($stmts[0] instanceof Stmt\InlineHTML) { + $p = preg_replace('/^<\?php\s+\?>\r?\n?/', '', $p); + } + if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) { + $p = preg_replace('/<\?php$/', '', rtrim($p)); + } + + return $p; + } + + /** + * Preprocesses the top-level nodes to initialize pretty printer state. + * + * @param Node[] $nodes Array of nodes + */ + protected function preprocessNodes(array $nodes): void { + /* We can use semicolon-namespaces unless there is a global namespace declaration */ + $this->canUseSemicolonNamespaces = true; + foreach ($nodes as $node) { + if ($node instanceof Stmt\Namespace_ && null === $node->name) { + $this->canUseSemicolonNamespaces = false; + break; + } + } + } + + /** + * Handles (and removes) doc-string-end tokens. + */ + protected function handleMagicTokens(string $str): string { + if ($this->docStringEndToken !== null) { + // Replace doc-string-end tokens with nothing or a newline + $str = str_replace( + $this->docStringEndToken . ';' . $this->newline, + ';' . $this->newline, + $str); + $str = str_replace($this->docStringEndToken, $this->newline, $str); + } + + return $str; + } + + /** + * Pretty prints an array of nodes (statements) and indents them optionally. + * + * @param Node[] $nodes Array of nodes + * @param bool $indent Whether to indent the printed nodes + * + * @return string Pretty printed statements + */ + protected function pStmts(array $nodes, bool $indent = true): string { + if ($indent) { + $this->indent(); + } + + $result = ''; + foreach ($nodes as $node) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + if ($node instanceof Stmt\Nop) { + continue; + } + } + + $result .= $this->nl . $this->p($node); + } + + if ($indent) { + $this->outdent(); + } + + return $result; + } + + /** + * Pretty-print an infix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param Node $leftNode Left-hand side node + * @param string $operatorString String representation of the operator + * @param Node $rightNode Right-hand side node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed infix operation + */ + protected function pInfixOp( + string $class, Node $leftNode, string $operatorString, Node $rightNode, + int $precedence, int $lhsPrecedence + ): string { + list($opPrecedence, $newPrecedenceLHS, $newPrecedenceRHS) = $this->precedenceMap[$class]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $precedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + return $prefix . $this->p($leftNode, $newPrecedenceLHS, $newPrecedenceLHS) + . $operatorString . $this->p($rightNode, $newPrecedenceRHS, $lhsPrecedence) . $suffix; + } + + /** + * Pretty-print a prefix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed prefix operation + */ + protected function pPrefixOp(string $class, string $operatorString, Node $node, int $precedence, int $lhsPrecedence): string { + $opPrecedence = $this->precedenceMap[$class][0]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $lhsPrecedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + $printedArg = $this->p($node, $opPrecedence, $lhsPrecedence); + if (($operatorString === '+' && $printedArg[0] === '+') || + ($operatorString === '-' && $printedArg[0] === '-') + ) { + // Avoid printing +(+$a) as ++$a and similar. + $printedArg = '(' . $printedArg . ')'; + } + return $prefix . $operatorString . $printedArg . $suffix; + } + + /** + * Pretty-print a postfix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed postfix operation + */ + protected function pPostfixOp(string $class, Node $node, string $operatorString, int $precedence, int $lhsPrecedence): string { + $opPrecedence = $this->precedenceMap[$class][0]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $precedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + if ($opPrecedence < $lhsPrecedence) { + $lhsPrecedence = $opPrecedence; + } + return $prefix . $this->p($node, $opPrecedence, $lhsPrecedence) . $operatorString . $suffix; + } + + /** + * Pretty prints an array of nodes and implodes the printed values. + * + * @param Node[] $nodes Array of Nodes to be printed + * @param string $glue Character to implode with + * + * @return string Imploded pretty printed nodes> $pre + */ + protected function pImplode(array $nodes, string $glue = ''): string { + $pNodes = []; + foreach ($nodes as $node) { + if (null === $node) { + $pNodes[] = ''; + } else { + $pNodes[] = $this->p($node); + } + } + + return implode($glue, $pNodes); + } + + /** + * Pretty prints an array of nodes and implodes the printed values with commas. + * + * @param Node[] $nodes Array of Nodes to be printed + * + * @return string Comma separated pretty printed nodes + */ + protected function pCommaSeparated(array $nodes): string { + return $this->pImplode($nodes, ', '); + } + + /** + * Pretty prints a comma-separated list of nodes in multiline style, including comments. + * + * The result includes a leading newline and one level of indentation (same as pStmts). + * + * @param Node[] $nodes Array of Nodes to be printed + * @param bool $trailingComma Whether to use a trailing comma + * + * @return string Comma separated pretty printed nodes in multiline style + */ + protected function pCommaSeparatedMultiline(array $nodes, bool $trailingComma): string { + $this->indent(); + + $result = ''; + $lastIdx = count($nodes) - 1; + foreach ($nodes as $idx => $node) { + if ($node !== null) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + } + + $result .= $this->nl . $this->p($node); + } else { + $result .= $this->nl; + } + if ($trailingComma || $idx !== $lastIdx) { + $result .= ','; + } + } + + $this->outdent(); + return $result; + } + + /** + * Prints reformatted text of the passed comments. + * + * @param Comment[] $comments List of comments + * + * @return string Reformatted text of comments + */ + protected function pComments(array $comments): string { + $formattedComments = []; + + foreach ($comments as $comment) { + $formattedComments[] = str_replace("\n", $this->nl, $comment->getReformattedText()); + } + + return implode($this->nl, $formattedComments); + } + + /** + * Perform a format-preserving pretty print of an AST. + * + * The format preservation is best effort. For some changes to the AST the formatting will not + * be preserved (at least not locally). + * + * In order to use this method a number of prerequisites must be satisfied: + * * The startTokenPos and endTokenPos attributes in the lexer must be enabled. + * * The CloningVisitor must be run on the AST prior to modification. + * * The original tokens must be provided, using the getTokens() method on the lexer. + * + * @param Node[] $stmts Modified AST with links to original AST + * @param Node[] $origStmts Original AST with token offset information + * @param Token[] $origTokens Tokens of the original code + */ + public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens): string { + $this->initializeNodeListDiffer(); + $this->initializeLabelCharMap(); + $this->initializeFixupMap(); + $this->initializeRemovalMap(); + $this->initializeInsertionMap(); + $this->initializeListInsertionMap(); + $this->initializeEmptyListInsertionMap(); + $this->initializeModifierChangeMap(); + + $this->resetState(); + $this->origTokens = new TokenStream($origTokens); + + $this->preprocessNodes($stmts); + + $pos = 0; + $result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null); + if (null !== $result) { + $result .= $this->origTokens->getTokenCode($pos, count($origTokens) - 1, 0); + } else { + // Fallback + // TODO Add newline . $this->pStmts($stmts, false); + } + + return $this->handleMagicTokens($result); + } + + protected function pFallback(Node $node, int $precedence, int $lhsPrecedence): string { + return $this->{'p' . $node->getType()}($node, $precedence, $lhsPrecedence); + } + + /** + * Pretty prints a node. + * + * This method also handles formatting preservation for nodes. + * + * @param Node $node Node to be pretty printed + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * @param bool $parentFormatPreserved Whether parent node has preserved formatting + * + * @return string Pretty printed node + */ + protected function p( + Node $node, int $precedence = self::MAX_PRECEDENCE, int $lhsPrecedence = self::MAX_PRECEDENCE, + bool $parentFormatPreserved = false + ): string { + // No orig tokens means this is a normal pretty print without preservation of formatting + if (!$this->origTokens) { + return $this->{'p' . $node->getType()}($node, $precedence, $lhsPrecedence); + } + + /** @var Node|null $origNode */ + $origNode = $node->getAttribute('origNode'); + if (null === $origNode) { + return $this->pFallback($node, $precedence, $lhsPrecedence); + } + + $class = \get_class($node); + \assert($class === \get_class($origNode)); + + $startPos = $origNode->getStartTokenPos(); + $endPos = $origNode->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + + $fallbackNode = $node; + if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) { + // Normalize node structure of anonymous classes + assert($origNode instanceof Expr\New_); + $node = PrintableNewAnonClassNode::fromNewNode($node); + $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); + $class = PrintableNewAnonClassNode::class; + } + + // InlineHTML node does not contain closing and opening PHP tags. If the parent formatting + // is not preserved, then we need to use the fallback code to make sure the tags are + // printed. + if ($node instanceof Stmt\InlineHTML && !$parentFormatPreserved) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + $indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos); + + $type = $node->getType(); + $fixupInfo = $this->fixupMap[$class] ?? null; + + $result = ''; + $pos = $startPos; + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->$subNodeName; + $origSubNode = $origNode->$subNodeName; + + if ((!$subNode instanceof Node && $subNode !== null) + || (!$origSubNode instanceof Node && $origSubNode !== null) + ) { + if ($subNode === $origSubNode) { + // Unchanged, can reuse old code + continue; + } + + if (is_array($subNode) && is_array($origSubNode)) { + // Array subnode changed, we might be able to reconstruct it + $listResult = $this->pArray( + $subNode, $origSubNode, $pos, $indentAdjustment, $class, $subNodeName, + $fixupInfo[$subNodeName] ?? null + ); + if (null === $listResult) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + $result .= $listResult; + continue; + } + + // Check if this is a modifier change + $key = $class . '->' . $subNodeName; + if (!isset($this->modifierChangeMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + [$printFn, $findToken] = $this->modifierChangeMap[$key]; + $result .= $this->$printFn($subNode); + $pos = $this->origTokens->findRight($pos, $findToken); + continue; + } + + $extraLeft = ''; + $extraRight = ''; + if ($origSubNode !== null) { + $subStartPos = $origSubNode->getStartTokenPos(); + $subEndPos = $origSubNode->getEndTokenPos(); + \assert($subStartPos >= 0 && $subEndPos >= 0); + } else { + if ($subNode === null) { + // Both null, nothing to do + continue; + } + + // A node has been inserted, check if we have insertion information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->insertionMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + list($findToken, $beforeToken, $extraLeft, $extraRight) = $this->insertionMap[$key]; + if (null !== $findToken) { + $subStartPos = $this->origTokens->findRight($pos, $findToken) + + (int) !$beforeToken; + } else { + $subStartPos = $pos; + } + + if (null === $extraLeft && null !== $extraRight) { + // If inserting on the right only, skipping whitespace looks better + $subStartPos = $this->origTokens->skipRightWhitespace($subStartPos); + } + $subEndPos = $subStartPos - 1; + } + + if (null === $subNode) { + // A node has been removed, check if we have removal information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->removalMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + // Adjust positions to account for additional tokens that must be skipped + $removalInfo = $this->removalMap[$key]; + if (isset($removalInfo['left'])) { + $subStartPos = $this->origTokens->skipLeft($subStartPos - 1, $removalInfo['left']) + 1; + } + if (isset($removalInfo['right'])) { + $subEndPos = $this->origTokens->skipRight($subEndPos + 1, $removalInfo['right']) - 1; + } + } + + $result .= $this->origTokens->getTokenCode($pos, $subStartPos, $indentAdjustment); + + if (null !== $subNode) { + $result .= $extraLeft; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment); + + // If it's the same node that was previously in this position, it certainly doesn't + // need fixup. It's important to check this here, because our fixup checks are more + // conservative than strictly necessary. + if (isset($fixupInfo[$subNodeName]) + && $subNode->getAttribute('origNode') !== $origSubNode + ) { + $fixup = $fixupInfo[$subNodeName]; + $res = $this->pFixup($fixup, $subNode, $class, $subStartPos, $subEndPos); + } else { + $res = $this->p($subNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + } + + $this->safeAppend($result, $res); + $this->setIndentLevel($origIndentLevel); + + $result .= $extraRight; + } + + $pos = $subEndPos + 1; + } + + $result .= $this->origTokens->getTokenCode($pos, $endPos + 1, $indentAdjustment); + return $result; + } + + /** + * Perform a format-preserving pretty print of an array. + * + * @param Node[] $nodes New nodes + * @param Node[] $origNodes Original nodes + * @param int $pos Current token position (updated by reference) + * @param int $indentAdjustment Adjustment for indentation + * @param string $parentNodeClass Class of the containing node. + * @param string $subNodeName Name of array subnode. + * @param null|int $fixup Fixup information for array item nodes + * + * @return null|string Result of pretty print or null if cannot preserve formatting + */ + protected function pArray( + array $nodes, array $origNodes, int &$pos, int $indentAdjustment, + string $parentNodeClass, string $subNodeName, ?int $fixup + ): ?string { + $diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes); + + $mapKey = $parentNodeClass . '->' . $subNodeName; + $insertStr = $this->listInsertionMap[$mapKey] ?? null; + $isStmtList = $subNodeName === 'stmts'; + + $beforeFirstKeepOrReplace = true; + $skipRemovedNode = false; + $delayedAdd = []; + $lastElemIndentLevel = $this->indentLevel; + + $insertNewline = false; + if ($insertStr === "\n") { + $insertStr = ''; + $insertNewline = true; + } + + if ($isStmtList && \count($origNodes) === 1 && \count($nodes) !== 1) { + $startPos = $origNodes[0]->getStartTokenPos(); + $endPos = $origNodes[0]->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + if (!$this->origTokens->haveBraces($startPos, $endPos)) { + // This was a single statement without braces, but either additional statements + // have been added, or the single statement has been removed. This requires the + // addition of braces. For now fall back. + // TODO: Try to preserve formatting + return null; + } + } + + $result = ''; + foreach ($diff as $i => $diffElem) { + $diffType = $diffElem->type; + /** @var Node|string|null $arrItem */ + $arrItem = $diffElem->new; + /** @var Node|string|null $origArrItem */ + $origArrItem = $diffElem->old; + + if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) { + $beforeFirstKeepOrReplace = false; + + if ($origArrItem === null || $arrItem === null) { + // We can only handle the case where both are null + if ($origArrItem === $arrItem) { + continue; + } + return null; + } + + if (!$arrItem instanceof Node || !$origArrItem instanceof Node) { + // We can only deal with nodes. This can occur for Names, which use string arrays. + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos); + + $origIndentLevel = $this->indentLevel; + $lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment; + $this->setIndentLevel($lastElemIndentLevel); + + $comments = $arrItem->getComments(); + $origComments = $origArrItem->getComments(); + $commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos; + \assert($commentStartPos >= 0); + + if ($commentStartPos < $pos) { + // Comments may be assigned to multiple nodes if they start at the same position. + // Make sure we don't try to print them multiple times. + $commentStartPos = $itemStartPos; + } + + if ($skipRemovedNode) { + if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) { + // We'd remove an opening/closing PHP tag. + // TODO: Preserve formatting. + $this->setIndentLevel($origIndentLevel); + return null; + } + } else { + $result .= $this->origTokens->getTokenCode( + $pos, $commentStartPos, $indentAdjustment); + } + + if (!empty($delayedAdd)) { + /** @var Node $delayedAddNode */ + foreach ($delayedAdd as $delayedAddNode) { + if ($insertNewline) { + $delayedAddComments = $delayedAddNode->getComments(); + if ($delayedAddComments) { + $result .= $this->pComments($delayedAddComments) . $this->nl; + } + } + + $this->safeAppend($result, $this->p($delayedAddNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true)); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + } else { + $result .= $insertStr; + } + } + + $delayedAdd = []; + } + + if ($comments !== $origComments) { + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $this->origTokens->getTokenCode( + $commentStartPos, $itemStartPos, $indentAdjustment); + } + + // If we had to remove anything, we have done so now. + $skipRemovedNode = false; + } elseif ($diffType === DiffElem::TYPE_ADD) { + if (null === $insertStr) { + // We don't have insertion information for this list type + return null; + } + + if (!$arrItem instanceof Node) { + // We only support list insertion of nodes. + return null; + } + + // We go multiline if the original code was multiline, + // or if it's an array item with a comment above it. + // Match always uses multiline formatting. + if ($insertStr === ', ' && + ($this->isMultiline($origNodes) || $arrItem->getComments() || + $parentNodeClass === Expr\Match_::class) + ) { + $insertStr = ','; + $insertNewline = true; + } + + if ($beforeFirstKeepOrReplace) { + // Will be inserted at the next "replace" or "keep" element + $delayedAdd[] = $arrItem; + continue; + } + + $itemStartPos = $pos; + $itemEndPos = $pos - 1; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($lastElemIndentLevel); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + $comments = $arrItem->getComments(); + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $insertStr; + } + } elseif ($diffType === DiffElem::TYPE_REMOVE) { + if (!$origArrItem instanceof Node) { + // We only support removal for nodes + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0); + + // Consider comments part of the node. + $origComments = $origArrItem->getComments(); + if ($origComments) { + $itemStartPos = $origComments[0]->getStartTokenPos(); + } + + if ($i === 0) { + // If we're removing from the start, keep the tokens before the node and drop those after it, + // instead of the other way around. + $result .= $this->origTokens->getTokenCode( + $pos, $itemStartPos, $indentAdjustment); + $skipRemovedNode = true; + } else { + if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) { + // We'd remove an opening/closing PHP tag. + // TODO: Preserve formatting. + return null; + } + } + + $pos = $itemEndPos + 1; + continue; + } else { + throw new \Exception("Shouldn't happen"); + } + + if (null !== $fixup && $arrItem->getAttribute('origNode') !== $origArrItem) { + $res = $this->pFixup($fixup, $arrItem, null, $itemStartPos, $itemEndPos); + } else { + $res = $this->p($arrItem, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + } + $this->safeAppend($result, $res); + + $this->setIndentLevel($origIndentLevel); + $pos = $itemEndPos + 1; + } + + if ($skipRemovedNode) { + // TODO: Support removing single node. + return null; + } + + if (!empty($delayedAdd)) { + if (!isset($this->emptyListInsertionMap[$mapKey])) { + return null; + } + + list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey]; + if (null !== $findToken) { + $insertPos = $this->origTokens->findRight($pos, $findToken) + 1; + $result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment); + $pos = $insertPos; + } + + $first = true; + $result .= $extraLeft; + foreach ($delayedAdd as $delayedAddNode) { + if (!$first) { + $result .= $insertStr; + if ($insertNewline) { + $result .= $this->nl; + } + } + $result .= $this->p($delayedAddNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + $first = false; + } + $result .= $extraRight === "\n" ? $this->nl : $extraRight; + } + + return $result; + } + + /** + * Print node with fixups. + * + * Fixups here refer to the addition of extra parentheses, braces or other characters, that + * are required to preserve program semantics in a certain context (e.g. to maintain precedence + * or because only certain expressions are allowed in certain places). + * + * @param int $fixup Fixup type + * @param Node $subNode Subnode to print + * @param string|null $parentClass Class of parent node + * @param int $subStartPos Original start pos of subnode + * @param int $subEndPos Original end pos of subnode + * + * @return string Result of fixed-up print of subnode + */ + protected function pFixup(int $fixup, Node $subNode, ?string $parentClass, int $subStartPos, int $subEndPos): string { + switch ($fixup) { + case self::FIXUP_PREC_LEFT: + // We use a conservative approximation where lhsPrecedence == precedence. + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][1]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_PREC_RIGHT: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][2]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_PREC_UNARY: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][0]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_CALL_LHS: + if ($this->callLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_DEREF_LHS: + if ($this->dereferenceLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_STATIC_DEREF_LHS: + if ($this->staticDereferenceLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_NEW: + if ($this->newOperandRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos)) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_BRACED_NAME: + case self::FIXUP_VAR_BRACED_NAME: + if ($subNode instanceof Expr + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return ($fixup === self::FIXUP_VAR_BRACED_NAME ? '$' : '') + . '{' . $this->p($subNode) . '}'; + } + break; + case self::FIXUP_ENCAPSED: + if (!$subNode instanceof Node\InterpolatedStringPart + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return '{' . $this->p($subNode) . '}'; + } + break; + default: + throw new \Exception('Cannot happen'); + } + + // Nothing special to do + return $this->p($subNode); + } + + /** + * Appends to a string, ensuring whitespace between label characters. + * + * Example: "echo" and "$x" result in "echo$x", but "echo" and "x" result in "echo x". + * Without safeAppend the result would be "echox", which does not preserve semantics. + */ + protected function safeAppend(string &$str, string $append): void { + if ($str === "") { + $str = $append; + return; + } + + if ($append === "") { + return; + } + + if (!$this->labelCharMap[$append[0]] + || !$this->labelCharMap[$str[\strlen($str) - 1]]) { + $str .= $append; + } else { + $str .= " " . $append; + } + } + + /** + * Determines whether the LHS of a call must be wrapped in parenthesis. + * + * @param Node $node LHS of a call + * + * @return bool Whether parentheses are required + */ + protected function callLhsRequiresParens(Node $node): bool { + return !($node instanceof Node\Name + || $node instanceof Expr\Variable + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_); + } + + /** + * Determines whether the LHS of an array/object operation must be wrapped in parentheses. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function dereferenceLhsRequiresParens(Node $node): bool { + // A constant can occur on the LHS of an array/object deref, but not a static deref. + return $this->staticDereferenceLhsRequiresParens($node) + && !$node instanceof Expr\ConstFetch; + } + + /** + * Determines whether the LHS of a static operation must be wrapped in parentheses. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function staticDereferenceLhsRequiresParens(Node $node): bool { + return !($node instanceof Expr\Variable + || $node instanceof Node\Name + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\PropertyFetch + || $node instanceof Expr\NullsafePropertyFetch + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_ + || $node instanceof Scalar\String_ + || $node instanceof Expr\ClassConstFetch); + } + + /** + * Determines whether an expression used in "new" or "instanceof" requires parentheses. + * + * @param Node $node New or instanceof operand + * + * @return bool Whether parentheses are required + */ + protected function newOperandRequiresParens(Node $node): bool { + if ($node instanceof Node\Name || $node instanceof Expr\Variable) { + return false; + } + if ($node instanceof Expr\ArrayDimFetch || $node instanceof Expr\PropertyFetch || + $node instanceof Expr\NullsafePropertyFetch + ) { + return $this->newOperandRequiresParens($node->var); + } + if ($node instanceof Expr\StaticPropertyFetch) { + return $this->newOperandRequiresParens($node->class); + } + return true; + } + + /** + * Print modifiers, including trailing whitespace. + * + * @param int $modifiers Modifier mask to print + * + * @return string Printed modifiers + */ + protected function pModifiers(int $modifiers): string { + return ($modifiers & Modifiers::FINAL ? 'final ' : '') + . ($modifiers & Modifiers::ABSTRACT ? 'abstract ' : '') + . ($modifiers & Modifiers::PUBLIC ? 'public ' : '') + . ($modifiers & Modifiers::PROTECTED ? 'protected ' : '') + . ($modifiers & Modifiers::PRIVATE ? 'private ' : '') + . ($modifiers & Modifiers::STATIC ? 'static ' : '') + . ($modifiers & Modifiers::READONLY ? 'readonly ' : ''); + } + + protected function pStatic(bool $static): string { + return $static ? 'static ' : ''; + } + + /** + * Determine whether a list of nodes uses multiline formatting. + * + * @param (Node|null)[] $nodes Node list + * + * @return bool Whether multiline formatting is used + */ + protected function isMultiline(array $nodes): bool { + if (\count($nodes) < 2) { + return false; + } + + $pos = -1; + foreach ($nodes as $node) { + if (null === $node) { + continue; + } + + $endPos = $node->getEndTokenPos() + 1; + if ($pos >= 0) { + $text = $this->origTokens->getTokenCode($pos, $endPos, 0); + if (false === strpos($text, "\n")) { + // We require that a newline is present between *every* item. If the formatting + // is inconsistent, with only some items having newlines, we don't consider it + // as multiline + return false; + } + } + $pos = $endPos; + } + + return true; + } + + /** + * Lazily initializes label char map. + * + * The label char map determines whether a certain character may occur in a label. + */ + protected function initializeLabelCharMap(): void { + if (isset($this->labelCharMap)) { + return; + } + + $this->labelCharMap = []; + for ($i = 0; $i < 256; $i++) { + $chr = chr($i); + $this->labelCharMap[$chr] = $i >= 0x80 || ctype_alnum($chr); + } + + if ($this->phpVersion->allowsDelInIdentifiers()) { + $this->labelCharMap["\x7f"] = true; + } + } + + /** + * Lazily initializes node list differ. + * + * The node list differ is used to determine differences between two array subnodes. + */ + protected function initializeNodeListDiffer(): void { + if (isset($this->nodeListDiffer)) { + return; + } + + $this->nodeListDiffer = new Internal\Differ(function ($a, $b) { + if ($a instanceof Node && $b instanceof Node) { + return $a === $b->getAttribute('origNode'); + } + // Can happen for array destructuring + return $a === null && $b === null; + }); + } + + /** + * Lazily initializes fixup map. + * + * The fixup map is used to determine whether a certain subnode of a certain node may require + * some kind of "fixup" operation, e.g. the addition of parenthesis or braces. + */ + protected function initializeFixupMap(): void { + if (isset($this->fixupMap)) { + return; + } + + $this->fixupMap = [ + Expr\Instanceof_::class => [ + 'expr' => self::FIXUP_PREC_UNARY, + 'class' => self::FIXUP_NEW, + ], + Expr\Ternary::class => [ + 'cond' => self::FIXUP_PREC_LEFT, + 'else' => self::FIXUP_PREC_RIGHT, + ], + Expr\Yield_::class => ['value' => self::FIXUP_PREC_UNARY], + + Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS], + Expr\StaticCall::class => ['class' => self::FIXUP_STATIC_DEREF_LHS], + Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\ClassConstFetch::class => [ + 'class' => self::FIXUP_STATIC_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\New_::class => ['class' => self::FIXUP_NEW], + Expr\MethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafeMethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\StaticPropertyFetch::class => [ + 'class' => self::FIXUP_STATIC_DEREF_LHS, + 'name' => self::FIXUP_VAR_BRACED_NAME, + ], + Expr\PropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafePropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Scalar\InterpolatedString::class => [ + 'parts' => self::FIXUP_ENCAPSED, + ], + ]; + + $binaryOps = [ + BinaryOp\Pow::class, BinaryOp\Mul::class, BinaryOp\Div::class, BinaryOp\Mod::class, + BinaryOp\Plus::class, BinaryOp\Minus::class, BinaryOp\Concat::class, + BinaryOp\ShiftLeft::class, BinaryOp\ShiftRight::class, BinaryOp\Smaller::class, + BinaryOp\SmallerOrEqual::class, BinaryOp\Greater::class, BinaryOp\GreaterOrEqual::class, + BinaryOp\Equal::class, BinaryOp\NotEqual::class, BinaryOp\Identical::class, + BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class, + BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class, + BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class, + BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class, + ]; + foreach ($binaryOps as $binaryOp) { + $this->fixupMap[$binaryOp] = [ + 'left' => self::FIXUP_PREC_LEFT, + 'right' => self::FIXUP_PREC_RIGHT + ]; + } + + $prefixOps = [ + Expr\Clone_::class, Expr\BitwiseNot::class, Expr\BooleanNot::class, Expr\UnaryPlus::class, Expr\UnaryMinus::class, + Cast\Int_::class, Cast\Double::class, Cast\String_::class, Cast\Array_::class, + Cast\Object_::class, Cast\Bool_::class, Cast\Unset_::class, Expr\ErrorSuppress::class, + Expr\YieldFrom::class, Expr\Print_::class, Expr\Include_::class, + Expr\Assign::class, Expr\AssignRef::class, AssignOp\Plus::class, AssignOp\Minus::class, + AssignOp\Mul::class, AssignOp\Div::class, AssignOp\Concat::class, AssignOp\Mod::class, + AssignOp\BitwiseAnd::class, AssignOp\BitwiseOr::class, AssignOp\BitwiseXor::class, + AssignOp\ShiftLeft::class, AssignOp\ShiftRight::class, AssignOp\Pow::class, AssignOp\Coalesce::class, + Expr\ArrowFunction::class, Expr\Throw_::class, + ]; + foreach ($prefixOps as $prefixOp) { + $this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_UNARY]; + } + } + + /** + * Lazily initializes the removal map. + * + * The removal map is used to determine which additional tokens should be removed when a + * certain node is replaced by null. + */ + protected function initializeRemovalMap(): void { + if (isset($this->removalMap)) { + return; + } + + $stripBoth = ['left' => \T_WHITESPACE, 'right' => \T_WHITESPACE]; + $stripLeft = ['left' => \T_WHITESPACE]; + $stripRight = ['right' => \T_WHITESPACE]; + $stripDoubleArrow = ['right' => \T_DOUBLE_ARROW]; + $stripColon = ['left' => ':']; + $stripEquals = ['left' => '=']; + $this->removalMap = [ + 'Expr_ArrayDimFetch->dim' => $stripBoth, + 'ArrayItem->key' => $stripDoubleArrow, + 'Expr_ArrowFunction->returnType' => $stripColon, + 'Expr_Closure->returnType' => $stripColon, + 'Expr_Exit->expr' => $stripBoth, + 'Expr_Ternary->if' => $stripBoth, + 'Expr_Yield->key' => $stripDoubleArrow, + 'Expr_Yield->value' => $stripBoth, + 'Param->type' => $stripRight, + 'Param->default' => $stripEquals, + 'Stmt_Break->num' => $stripBoth, + 'Stmt_Catch->var' => $stripLeft, + 'Stmt_ClassConst->type' => $stripRight, + 'Stmt_ClassMethod->returnType' => $stripColon, + 'Stmt_Class->extends' => ['left' => \T_EXTENDS], + 'Stmt_Enum->scalarType' => $stripColon, + 'Stmt_EnumCase->expr' => $stripEquals, + 'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS], + 'Stmt_Continue->num' => $stripBoth, + 'Stmt_Foreach->keyVar' => $stripDoubleArrow, + 'Stmt_Function->returnType' => $stripColon, + 'Stmt_If->else' => $stripLeft, + 'Stmt_Namespace->name' => $stripLeft, + 'Stmt_Property->type' => $stripRight, + 'PropertyItem->default' => $stripEquals, + 'Stmt_Return->expr' => $stripBoth, + 'Stmt_StaticVar->default' => $stripEquals, + 'Stmt_TraitUseAdaptation_Alias->newName' => $stripLeft, + 'Stmt_TryCatch->finally' => $stripLeft, + // 'Stmt_Case->cond': Replace with "default" + // 'Stmt_Class->name': Unclear what to do + // 'Stmt_Declare->stmts': Not a plain node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a plain node + ]; + } + + protected function initializeInsertionMap(): void { + if (isset($this->insertionMap)) { + return; + } + + // TODO: "yield" where both key and value are inserted doesn't work + // [$find, $beforeToken, $extraLeft, $extraRight] + $this->insertionMap = [ + 'Expr_ArrayDimFetch->dim' => ['[', false, null, null], + 'ArrayItem->key' => [null, false, null, ' => '], + 'Expr_ArrowFunction->returnType' => [')', false, ': ', null], + 'Expr_Closure->returnType' => [')', false, ': ', null], + 'Expr_Ternary->if' => ['?', false, ' ', ' '], + 'Expr_Yield->key' => [\T_YIELD, false, null, ' => '], + 'Expr_Yield->value' => [\T_YIELD, false, ' ', null], + 'Param->type' => [null, false, null, ' '], + 'Param->default' => [null, false, ' = ', null], + 'Stmt_Break->num' => [\T_BREAK, false, ' ', null], + 'Stmt_Catch->var' => [null, false, ' ', null], + 'Stmt_ClassMethod->returnType' => [')', false, ': ', null], + 'Stmt_ClassConst->type' => [\T_CONST, false, ' ', null], + 'Stmt_Class->extends' => [null, false, ' extends ', null], + 'Stmt_Enum->scalarType' => [null, false, ' : ', null], + 'Stmt_EnumCase->expr' => [null, false, ' = ', null], + 'Expr_PrintableNewAnonClass->extends' => [null, false, ' extends ', null], + 'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null], + 'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '], + 'Stmt_Function->returnType' => [')', false, ': ', null], + 'Stmt_If->else' => [null, false, ' ', null], + 'Stmt_Namespace->name' => [\T_NAMESPACE, false, ' ', null], + 'Stmt_Property->type' => [\T_VARIABLE, true, null, ' '], + 'PropertyItem->default' => [null, false, ' = ', null], + 'Stmt_Return->expr' => [\T_RETURN, false, ' ', null], + 'Stmt_StaticVar->default' => [null, false, ' = ', null], + //'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, false, ' ', null], // TODO + 'Stmt_TryCatch->finally' => [null, false, ' ', null], + + // 'Expr_Exit->expr': Complicated due to optional () + // 'Stmt_Case->cond': Conversion from default to case + // 'Stmt_Class->name': Unclear + // 'Stmt_Declare->stmts': Not a proper node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a proper node + ]; + } + + protected function initializeListInsertionMap(): void { + if (isset($this->listInsertionMap)) { + return; + } + + $this->listInsertionMap = [ + // special + //'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully + //'Scalar_InterpolatedString->parts' => '', + Stmt\Catch_::class . '->types' => '|', + UnionType::class . '->types' => '|', + IntersectionType::class . '->types' => '&', + Stmt\If_::class . '->elseifs' => ' ', + Stmt\TryCatch::class . '->catches' => ' ', + + // comma-separated lists + Expr\Array_::class . '->items' => ', ', + Expr\ArrowFunction::class . '->params' => ', ', + Expr\Closure::class . '->params' => ', ', + Expr\Closure::class . '->uses' => ', ', + Expr\FuncCall::class . '->args' => ', ', + Expr\Isset_::class . '->vars' => ', ', + Expr\List_::class . '->items' => ', ', + Expr\MethodCall::class . '->args' => ', ', + Expr\NullsafeMethodCall::class . '->args' => ', ', + Expr\New_::class . '->args' => ', ', + PrintableNewAnonClassNode::class . '->args' => ', ', + Expr\StaticCall::class . '->args' => ', ', + Stmt\ClassConst::class . '->consts' => ', ', + Stmt\ClassMethod::class . '->params' => ', ', + Stmt\Class_::class . '->implements' => ', ', + Stmt\Enum_::class . '->implements' => ', ', + PrintableNewAnonClassNode::class . '->implements' => ', ', + Stmt\Const_::class . '->consts' => ', ', + Stmt\Declare_::class . '->declares' => ', ', + Stmt\Echo_::class . '->exprs' => ', ', + Stmt\For_::class . '->init' => ', ', + Stmt\For_::class . '->cond' => ', ', + Stmt\For_::class . '->loop' => ', ', + Stmt\Function_::class . '->params' => ', ', + Stmt\Global_::class . '->vars' => ', ', + Stmt\GroupUse::class . '->uses' => ', ', + Stmt\Interface_::class . '->extends' => ', ', + Expr\Match_::class . '->arms' => ', ', + Stmt\Property::class . '->props' => ', ', + Stmt\StaticVar::class . '->vars' => ', ', + Stmt\TraitUse::class . '->traits' => ', ', + Stmt\TraitUseAdaptation\Precedence::class . '->insteadof' => ', ', + Stmt\Unset_::class . '->vars' => ', ', + Stmt\UseUse::class . '->uses' => ', ', + MatchArm::class . '->conds' => ', ', + AttributeGroup::class . '->attrs' => ', ', + + // statement lists + Expr\Closure::class . '->stmts' => "\n", + Stmt\Case_::class . '->stmts' => "\n", + Stmt\Catch_::class . '->stmts' => "\n", + Stmt\Class_::class . '->stmts' => "\n", + Stmt\Enum_::class . '->stmts' => "\n", + PrintableNewAnonClassNode::class . '->stmts' => "\n", + Stmt\Interface_::class . '->stmts' => "\n", + Stmt\Trait_::class . '->stmts' => "\n", + Stmt\ClassMethod::class . '->stmts' => "\n", + Stmt\Declare_::class . '->stmts' => "\n", + Stmt\Do_::class . '->stmts' => "\n", + Stmt\ElseIf_::class . '->stmts' => "\n", + Stmt\Else_::class . '->stmts' => "\n", + Stmt\Finally_::class . '->stmts' => "\n", + Stmt\Foreach_::class . '->stmts' => "\n", + Stmt\For_::class . '->stmts' => "\n", + Stmt\Function_::class . '->stmts' => "\n", + Stmt\If_::class . '->stmts' => "\n", + Stmt\Namespace_::class . '->stmts' => "\n", + Stmt\Block::class . '->stmts' => "\n", + + // Attribute groups + Stmt\Class_::class . '->attrGroups' => "\n", + Stmt\Enum_::class . '->attrGroups' => "\n", + Stmt\EnumCase::class . '->attrGroups' => "\n", + Stmt\Interface_::class . '->attrGroups' => "\n", + Stmt\Trait_::class . '->attrGroups' => "\n", + Stmt\Function_::class . '->attrGroups' => "\n", + Stmt\ClassMethod::class . '->attrGroups' => "\n", + Stmt\ClassConst::class . '->attrGroups' => "\n", + Stmt\Property::class . '->attrGroups' => "\n", + PrintableNewAnonClassNode::class . '->attrGroups' => ' ', + Expr\Closure::class . '->attrGroups' => ' ', + Expr\ArrowFunction::class . '->attrGroups' => ' ', + Param::class . '->attrGroups' => ' ', + Stmt\Switch_::class . '->cases' => "\n", + Stmt\TraitUse::class . '->adaptations' => "\n", + Stmt\TryCatch::class . '->stmts' => "\n", + Stmt\While_::class . '->stmts' => "\n", + + // dummy for top-level context + 'File->stmts' => "\n", + ]; + } + + protected function initializeEmptyListInsertionMap(): void { + if (isset($this->emptyListInsertionMap)) { + return; + } + + // TODO Insertion into empty statement lists. + + // [$find, $extraLeft, $extraRight] + $this->emptyListInsertionMap = [ + Expr\ArrowFunction::class . '->params' => ['(', '', ''], + Expr\Closure::class . '->uses' => [')', ' use (', ')'], + Expr\Closure::class . '->params' => ['(', '', ''], + Expr\FuncCall::class . '->args' => ['(', '', ''], + Expr\MethodCall::class . '->args' => ['(', '', ''], + Expr\NullsafeMethodCall::class . '->args' => ['(', '', ''], + Expr\New_::class . '->args' => ['(', '', ''], + PrintableNewAnonClassNode::class . '->args' => ['(', '', ''], + PrintableNewAnonClassNode::class . '->implements' => [null, ' implements ', ''], + Expr\StaticCall::class . '->args' => ['(', '', ''], + Stmt\Class_::class . '->implements' => [null, ' implements ', ''], + Stmt\Enum_::class . '->implements' => [null, ' implements ', ''], + Stmt\ClassMethod::class . '->params' => ['(', '', ''], + Stmt\Interface_::class . '->extends' => [null, ' extends ', ''], + Stmt\Function_::class . '->params' => ['(', '', ''], + Stmt\Interface_::class . '->attrGroups' => [null, '', "\n"], + Stmt\Class_::class . '->attrGroups' => [null, '', "\n"], + Stmt\ClassConst::class . '->attrGroups' => [null, '', "\n"], + Stmt\ClassMethod::class . '->attrGroups' => [null, '', "\n"], + Stmt\Function_::class . '->attrGroups' => [null, '', "\n"], + Stmt\Property::class . '->attrGroups' => [null, '', "\n"], + Stmt\Trait_::class . '->attrGroups' => [null, '', "\n"], + Expr\ArrowFunction::class . '->attrGroups' => [null, '', ' '], + Expr\Closure::class . '->attrGroups' => [null, '', ' '], + PrintableNewAnonClassNode::class . '->attrGroups' => [\T_NEW, ' ', ''], + + /* These cannot be empty to start with: + * Expr_Isset->vars + * Stmt_Catch->types + * Stmt_Const->consts + * Stmt_ClassConst->consts + * Stmt_Declare->declares + * Stmt_Echo->exprs + * Stmt_Global->vars + * Stmt_GroupUse->uses + * Stmt_Property->props + * Stmt_StaticVar->vars + * Stmt_TraitUse->traits + * Stmt_TraitUseAdaptation_Precedence->insteadof + * Stmt_Unset->vars + * Stmt_Use->uses + * UnionType->types + */ + + /* TODO + * Stmt_If->elseifs + * Stmt_TryCatch->catches + * Expr_Array->items + * Expr_List->items + * Stmt_For->init + * Stmt_For->cond + * Stmt_For->loop + */ + ]; + } + + protected function initializeModifierChangeMap(): void { + if (isset($this->modifierChangeMap)) { + return; + } + + $this->modifierChangeMap = [ + Stmt\ClassConst::class . '->flags' => ['pModifiers', \T_CONST], + Stmt\ClassMethod::class . '->flags' => ['pModifiers', \T_FUNCTION], + Stmt\Class_::class . '->flags' => ['pModifiers', \T_CLASS], + Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE], + PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS], + Param::class . '->flags' => ['pModifiers', \T_VARIABLE], + Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION], + Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN], + //Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO + ]; + + // List of integer subnodes that are not modifiers: + // Expr_Include->type + // Stmt_GroupUse->type + // Stmt_Use->type + // UseItem->type + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Token.php b/vendor/nikic/php-parser/lib/PhpParser/Token.php new file mode 100644 index 0000000..6683310 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Token.php @@ -0,0 +1,18 @@ +pos + \strlen($this->text); + } + + /** Get 1-based end line number of the token. */ + public function getEndLine(): int { + return $this->line + \substr_count($this->text, "\n"); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php b/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php new file mode 100644 index 0000000..273271d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php @@ -0,0 +1,63 @@ +=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..bb785f4 --- /dev/null +++ b/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +clock = $clock; + } + + public function doSomething() + { + /** @var DateTimeImmutable $currentDateAndTime */ + $currentDateAndTime = $this->clock->now(); + // do something useful with that information + } +} +``` + +You can then pick one of the [implementations][implementation-url] of the interface to get a clock. + +If you want to implement the interface, you can require this package and +implement `Psr\Clock\ClockInterface` in your code. + +Don't forget to add `psr/clock-implementation` to your `composer.json`s `provides`-section like this: + +```json +{ + "provides": { + "psr/clock-implementation": "1.0" + } +} +``` + +And please read the [specification text][specification-url] for details on the interface. + +[psr-url]: https://www.php-fig.org/psr/psr-20 +[package-url]: https://packagist.org/packages/psr/clock +[implementation-url]: https://packagist.org/providers/psr/clock-implementation +[specification-url]: https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md diff --git a/vendor/psr/clock/composer.json b/vendor/psr/clock/composer.json new file mode 100644 index 0000000..77992ed --- /dev/null +++ b/vendor/psr/clock/composer.json @@ -0,0 +1,21 @@ +{ + "name": "psr/clock", + "description": "Common interface for reading the clock.", + "keywords": ["psr", "psr-20", "time", "clock", "now"], + "homepage": "https://github.com/php-fig/clock", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": "^7.0 || ^8.0" + }, + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + } +} diff --git a/vendor/psr/clock/src/ClockInterface.php b/vendor/psr/clock/src/ClockInterface.php new file mode 100644 index 0000000..7b6d8d8 --- /dev/null +++ b/vendor/psr/clock/src/ClockInterface.php @@ -0,0 +1,13 @@ +=7.4.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..0f213f2 --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ +=7.2.0" + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php new file mode 100644 index 0000000..4306fa9 --- /dev/null +++ b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php @@ -0,0 +1,21 @@ +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. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000..879fc6f --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "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": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/vendor/psr/log/src/AbstractLogger.php b/vendor/psr/log/src/AbstractLogger.php new file mode 100644 index 0000000..d60a091 --- /dev/null +++ b/vendor/psr/log/src/AbstractLogger.php @@ -0,0 +1,15 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/src/LoggerInterface.php b/vendor/psr/log/src/LoggerInterface.php new file mode 100644 index 0000000..cb4cf64 --- /dev/null +++ b/vendor/psr/log/src/LoggerInterface.php @@ -0,0 +1,98 @@ +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. + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + */ + public function error(string|\Stringable $message, array $context = []): void + { + $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. + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + */ + public function debug(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, string|\Stringable $message, array $context = []): void; +} diff --git a/vendor/psr/log/src/NullLogger.php b/vendor/psr/log/src/NullLogger.php new file mode 100644 index 0000000..de0561e --- /dev/null +++ b/vendor/psr/log/src/NullLogger.php @@ -0,0 +1,26 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed[] $context + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + // noop + } +} diff --git a/vendor/symfony/cache-contracts/CHANGELOG.md b/vendor/symfony/cache-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/cache-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/cache-contracts/CacheInterface.php b/vendor/symfony/cache-contracts/CacheInterface.php new file mode 100644 index 0000000..3e4aaf6 --- /dev/null +++ b/vendor/symfony/cache-contracts/CacheInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Covers most simple to advanced caching needs. + * + * @author Nicolas Grekas + */ +interface CacheInterface +{ + /** + * Fetches a value from the pool or computes it if not found. + * + * On cache misses, a callback is called that should return the missing value. + * This callback is given a PSR-6 CacheItemInterface instance corresponding to the + * requested key, that could be used e.g. for expiration control. It could also + * be an ItemInterface instance when its additional features are needed. + * + * @template T + * + * @param string $key The key of the item to retrieve from the cache + * @param (callable(CacheItemInterface,bool):T)|(callable(ItemInterface,bool):T)|CallbackInterface $callback + * @param float|null $beta A float that, as it grows, controls the likeliness of triggering + * early expiration. 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. + * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * + * @return T + * + * @throws InvalidArgumentException When $key is not valid or when $beta is negative + */ + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed; + + /** + * Removes an item from the pool. + * + * @param string $key The key to delete + * + * @return bool True if the item was successfully removed, false if there was any error + * + * @throws InvalidArgumentException When $key is not valid + */ + public function delete(string $key): bool; +} diff --git a/vendor/symfony/cache-contracts/CacheTrait.php b/vendor/symfony/cache-contracts/CacheTrait.php new file mode 100644 index 0000000..c2f6580 --- /dev/null +++ b/vendor/symfony/cache-contracts/CacheTrait.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(InvalidArgumentException::class); + +/** + * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. + * + * @author Nicolas Grekas + */ +trait CacheTrait +{ + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null, ?LoggerInterface $logger = null): mixed + { + if (0 > $beta ??= 1.0) { + throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException {}; + } + + $item = $pool->getItem($key); + $recompute = !$item->isHit() || \INF === $beta; + $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; + + if (!$recompute && $metadata) { + $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false; + $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false; + + if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) { + // force applying defaultLifetime to expiry + $item->expiresAt(null); + $logger?->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ + 'key' => $key, + 'delta' => sprintf('%.1f', $expiry - $now), + ]); + } + } + + if ($recompute) { + $save = true; + $item->set($callback($item, $save)); + if ($save) { + $pool->save($item); + } + } + + return $item->get(); + } +} diff --git a/vendor/symfony/cache-contracts/CallbackInterface.php b/vendor/symfony/cache-contracts/CallbackInterface.php new file mode 100644 index 0000000..15941e9 --- /dev/null +++ b/vendor/symfony/cache-contracts/CallbackInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; + +/** + * Computes and returns the cached value of an item. + * + * @author Nicolas Grekas + * + * @template T + */ +interface CallbackInterface +{ + /** + * @param CacheItemInterface|ItemInterface $item The item to compute the value for + * @param bool &$save Should be set to false when the value should not be saved in the pool + * + * @return T The computed value for the passed item + */ + public function __invoke(CacheItemInterface $item, bool &$save): mixed; +} diff --git a/vendor/symfony/cache-contracts/ItemInterface.php b/vendor/symfony/cache-contracts/ItemInterface.php new file mode 100644 index 0000000..8c4c512 --- /dev/null +++ b/vendor/symfony/cache-contracts/ItemInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Augments PSR-6's CacheItemInterface with support for tags and metadata. + * + * @author Nicolas Grekas + */ +interface ItemInterface extends CacheItemInterface +{ + /** + * References the Unix timestamp stating when the item will expire. + */ + public const METADATA_EXPIRY = 'expiry'; + + /** + * References the time the item took to be created, in milliseconds. + */ + public const METADATA_CTIME = 'ctime'; + + /** + * References the list of tags that were assigned to the item, as string[]. + */ + public const METADATA_TAGS = 'tags'; + + /** + * Reserved characters that cannot be used in a key or tag. + */ + public const RESERVED_CHARACTERS = '{}()/\@:'; + + /** + * Adds a tag to a cache item. + * + * Tags are strings that follow the same validation rules as keys. + * + * @param string|string[] $tags A tag or array of tags + * + * @return $this + * + * @throws InvalidArgumentException When $tag is not valid + * @throws CacheException When the item comes from a pool that is not tag-aware + */ + public function tag(string|iterable $tags): static; + + /** + * Returns a list of metadata info that were saved alongside with the cached value. + * + * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. + */ + public function getMetadata(): array; +} diff --git a/vendor/symfony/cache-contracts/LICENSE b/vendor/symfony/cache-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/cache-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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. diff --git a/vendor/symfony/cache-contracts/README.md b/vendor/symfony/cache-contracts/README.md new file mode 100644 index 0000000..ffe0833 --- /dev/null +++ b/vendor/symfony/cache-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Cache Contracts +======================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/cache-contracts/TagAwareCacheInterface.php b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php new file mode 100644 index 0000000..8e0b6be --- /dev/null +++ b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\InvalidArgumentException; + +/** + * Allows invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareCacheInterface extends CacheInterface +{ + /** + * Invalidates cached items using tags. + * + * When implemented on a PSR-6 pool, invalidation should not apply + * to deferred items. Instead, they should be committed as usual. + * This allows replacing old tagged values by new ones without + * race conditions. + * + * @param string[] $tags An array of tags to invalidate + * + * @return bool True on success + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags): bool; +} diff --git a/vendor/symfony/cache-contracts/composer.json b/vendor/symfony/cache-contracts/composer.json new file mode 100644 index 0000000..fe261d1 --- /dev/null +++ b/vendor/symfony/cache-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/cache-contracts", + "type": "library", + "description": "Generic abstractions related to caching", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Cache\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/cache/Adapter/AbstractAdapter.php b/vendor/symfony/cache/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..5d6336e --- /dev/null +++ b/vendor/symfony/cache/Adapter/AbstractAdapter.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use AbstractAdapterTrait; + use ContractsTrait; + + /** + * @internal + */ + protected const NS_SEPARATOR = ':'; + + private static bool $apcuSupported; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR; + $this->defaultLifetime = $defaultLifetime; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + $item->unpack(); + + return $item; + }, + null, + CacheItem::class + ); + self::$mergeByLifetime ??= \Closure::bind( + static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; + } elseif (!$item->expiry) { + $ttl = 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + $byLifetime[$ttl][$getId($key)] = $item->pack(); + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Returns the best possible adapter that your runtime supports. + * + * Using ApcuAdapter makes system caches compatible with read-only filesystems. + */ + public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, ?LoggerInterface $logger = null): AdapterInterface + { + $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true); + if (null !== $logger) { + $opcache->setLogger($logger); + } + + if (!self::$apcuSupported ??= ApcuAdapter::isSupported()) { + return $opcache; + } + + if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) { + return $opcache; + } + + $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version); + if (null !== $logger) { + $apcu->setLogger($logger); + } + + return new ChainAdapter([$apcu, $opcache]); + } + + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): mixed + { + if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) { + return RedisAdapter::createConnection($dsn, $options); + } + if (str_starts_with($dsn, 'memcached:')) { + return MemcachedAdapter::createConnection($dsn, $options); + } + if (str_starts_with($dsn, 'couchbase:')) { + if (class_exists('CouchbaseBucket') && CouchbaseBucketAdapter::isSupported()) { + return CouchbaseBucketAdapter::createConnection($dsn, $options); + } + + return CouchbaseCollectionAdapter::createConnection($dsn, $options); + } + if (preg_match('/^(mysql|oci|pgsql|sqlsrv|sqlite):/', $dsn)) { + return PdoAdapter::createConnection($dsn, $options); + } + + throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:", "couchbase:", "mysql:", "oci:", "pgsql:", "sqlsrv:" nor "sqlite:".'); + } + + public function commit(): bool + { + $ok = true; + $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, $this->getId(...), $this->defaultLifetime); + $retry = $this->deferred = []; + + if ($expiredIds) { + try { + $this->doDelete($expiredIds); + } catch (\Exception $e) { + $ok = false; + CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + } + foreach ($byLifetime as $lifetime => $values) { + try { + $e = $this->doSave($values, $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $e = $this->doSave([$id => $v], $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + $ok = false; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + return $ok; + } +} diff --git a/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php new file mode 100644 index 0000000..ef62b4f --- /dev/null +++ b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php @@ -0,0 +1,320 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * Abstract for native TagAware adapters. + * + * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids + * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate(). + * + * @author Nicolas Grekas + * @author André Rømcke + * + * @internal + */ +abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface +{ + use AbstractAdapterTrait; + use ContractsTrait; + + private const TAGS_PREFIX = "\1tags\1"; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':'; + $this->defaultLifetime = $defaultLifetime; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->isTaggable = true; + // If structure does not match what we expect return item as is (no value and not a hit) + if (!\is_array($value) || !\array_key_exists('value', $value)) { + return $item; + } + $item->isHit = $isHit; + // Extract value, tags and meta data from the cache value + $item->value = $value['value']; + $item->metadata[CacheItem::METADATA_TAGS] = isset($value['tags']) ? array_combine($value['tags'], $value['tags']) : []; + if (isset($value['meta'])) { + // For compactness these values are packed, & expiry is offset to reduce size + $v = unpack('Ve/Nc', $value['meta']); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } + + return $item; + }, + null, + CacheItem::class + ); + self::$mergeByLifetime ??= \Closure::bind( + static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; + } elseif (!$item->expiry) { + $ttl = 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + // Store Value and Tags on the cache value + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]]; + unset($metadata[CacheItem::METADATA_TAGS]); + } else { + $value = ['value' => $item->value, 'tags' => []]; + } + + if ($metadata) { + // For compactness, expiry and creation duration are packed, using magic numbers as separators + $value['meta'] = pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME]); + } + + // Extract tag changes, these should be removed from values in doSave() + $value['tag-operations'] = ['add' => [], 'remove' => []]; + $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? []; + foreach (array_diff_key($value['tags'], $oldTags) as $addedTag) { + $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag); + } + foreach (array_diff_key($oldTags, $value['tags']) as $removedTag) { + $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag); + } + $value['tags'] = array_keys($value['tags']); + + $byLifetime[$ttl][$getId($key)] = $value; + $item->metadata = $item->newMetadata; + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag + * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag + * + * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array; + + /** + * Removes multiple items from the pool and their corresponding tags. + * + * @param array $ids An array of identifiers that should be removed from the pool + */ + abstract protected function doDelete(array $ids): bool; + + /** + * Removes relations between tags and deleted items. + * + * @param array $tagData Array of tag => key identifiers that should be removed from the pool + */ + abstract protected function doDeleteTagRelations(array $tagData): bool; + + /** + * Invalidates cached items using tags. + * + * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id + */ + abstract protected function doInvalidate(array $tagIds): bool; + + /** + * Delete items and yields the tags they were bound to. + */ + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($this->doFetch($ids) as $id => $value) { + yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : []; + } + + $this->doDelete($ids); + } + + public function commit(): bool + { + $ok = true; + $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, $this->getId(...), self::TAGS_PREFIX, $this->defaultLifetime); + $retry = $this->deferred = []; + + if ($expiredIds) { + // Tags are not cleaned up in this case, however that is done on invalidateTags(). + try { + $this->doDelete($expiredIds); + } catch (\Exception $e) { + $ok = false; + CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + } + foreach ($byLifetime as $lifetime => $values) { + try { + $values = $this->extractTagData($values, $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + $ok = false; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + return $ok; + } + + public function deleteItems(array $keys): bool + { + if (!$keys) { + return true; + } + + $ok = true; + $ids = []; + $tagData = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) { + foreach ($tags as $tag) { + $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id; + } + } + } catch (\Exception) { + $ok = false; + } + + try { + if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) { + return true; + } + } catch (\Exception) { + } + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $ok = false; + } + + return $ok; + } + + public function invalidateTags(array $tags): bool + { + if (!$tags) { + return false; + } + + $tagIds = []; + foreach (array_unique($tags) as $tag) { + $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag); + } + + try { + if ($this->doInvalidate($tagIds)) { + return true; + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to invalidate tags: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + return false; + } + + /** + * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it. + */ + private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array + { + $addTagData = $removeTagData = []; + foreach ($values as $id => $value) { + foreach ($value['tag-operations']['add'] as $tag => $tagId) { + $addTagData[$tagId][] = $id; + } + + foreach ($value['tag-operations']['remove'] as $tag => $tagId) { + $removeTagData[$tagId][] = $id; + } + + unset($values[$id]['tag-operations']); + } + + return $values; + } +} diff --git a/vendor/symfony/cache/Adapter/AdapterInterface.php b/vendor/symfony/cache/Adapter/AdapterInterface.php new file mode 100644 index 0000000..e556720 --- /dev/null +++ b/vendor/symfony/cache/Adapter/AdapterInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; + +// Help opcache.preload discover always-needed symbols +class_exists(CacheItem::class); + +/** + * Interface for adapters managing instances of Symfony's CacheItem. + * + * @author Kévin Dunglas + */ +interface AdapterInterface extends CacheItemPoolInterface +{ + public function getItem(mixed $key): CacheItem; + + /** + * @return iterable + */ + public function getItems(array $keys = []): iterable; + + public function clear(string $prefix = ''): bool; +} diff --git a/vendor/symfony/cache/Adapter/ApcuAdapter.php b/vendor/symfony/cache/Adapter/ApcuAdapter.php new file mode 100644 index 0000000..03b512f --- /dev/null +++ b/vendor/symfony/cache/Adapter/ApcuAdapter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Nicolas Grekas + */ +class ApcuAdapter extends AbstractAdapter +{ + /** + * @throws CacheException if APCu is not enabled + */ + public function __construct( + string $namespace = '', + int $defaultLifetime = 0, + ?string $version = null, + private ?MarshallerInterface $marshaller = null, + ) { + if (!static::isSupported()) { + throw new CacheException('APCu is not enabled.'); + } + if ('cli' === \PHP_SAPI) { + ini_set('apc.use_request_time', 0); + } + parent::__construct($namespace, $defaultLifetime); + + if (null !== $version) { + CacheItem::validateKey($version); + + if (!apcu_exists($version.'@'.$namespace)) { + $this->doClear($namespace); + apcu_add($version.'@'.$namespace, null); + } + } + } + + public static function isSupported(): bool + { + return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL); + } + + protected function doFetch(array $ids): iterable + { + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + $values = []; + foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) { + if (null !== $v || $ok) { + $values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v; + } + } + + return $values; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + protected function doHave(string $id): bool + { + return apcu_exists($id); + } + + protected function doClear(string $namespace): bool + { + return isset($namespace[0]) && class_exists(\APCUIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) + ? apcu_delete(new \APCUIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY)) + : apcu_clear_cache(); + } + + protected function doDelete(array $ids): bool + { + foreach ($ids as $id) { + apcu_delete($id); + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (null !== $this->marshaller && (!$values = $this->marshaller->marshall($values, $failed))) { + return $failed; + } + + try { + if (false === $failures = apcu_store($values, null, $lifetime)) { + $failures = $values; + } + + return array_keys($failures); + } catch (\Throwable $e) { + if (1 === \count($values)) { + // Workaround https://github.com/krakjoe/apcu/issues/170 + apcu_delete(array_key_first($values)); + } + + throw $e; + } + } +} diff --git a/vendor/symfony/cache/Adapter/ArrayAdapter.php b/vendor/symfony/cache/Adapter/ArrayAdapter.php new file mode 100644 index 0000000..0f1c49d --- /dev/null +++ b/vendor/symfony/cache/Adapter/ArrayAdapter.php @@ -0,0 +1,359 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * An in-memory cache storage. + * + * Acts as a least-recently-used (LRU) storage when configured with a maximum number of items. + * + * @author Nicolas Grekas + */ +class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use LoggerAwareTrait; + + private array $values = []; + private array $tags = []; + private array $expiries = []; + + private static \Closure $createCacheItem; + + /** + * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise + */ + public function __construct( + private int $defaultLifetime = 0, + private bool $storeSerialized = true, + private float $maxLifetime = 0, + private int $maxItems = 0, + ) { + if (0 > $maxLifetime) { + throw new InvalidArgumentException(sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime)); + } + + if (0 > $maxItems) { + throw new InvalidArgumentException(sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems)); + } + + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit, $tags) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + if (null !== $tags) { + $item->metadata[CacheItem::METADATA_TAGS] = $tags; + } + + return $item; + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + $item = $this->getItem($key); + $metadata = $item->getMetadata(); + + // ArrayAdapter works in memory, we don't care about stampede protection + if (\INF === $beta || !$item->isHit()) { + $save = true; + $item->set($callback($item, $save)); + if ($save) { + $this->save($item); + } + } + + return $item->get(); + } + + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + public function hasItem(mixed $key): bool + { + if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) { + if ($this->maxItems) { + // Move the item last in the storage + $value = $this->values[$key]; + unset($this->values[$key]); + $this->values[$key] = $value; + } + + return true; + } + \assert('' !== CacheItem::validateKey($key)); + + return isset($this->expiries[$key]) && !$this->deleteItem($key); + } + + public function getItem(mixed $key): CacheItem + { + if (!$isHit = $this->hasItem($key)) { + $value = null; + + if (!$this->maxItems) { + // Track misses in non-LRU mode only + $this->values[$key] = null; + } + } else { + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + + return (self::$createCacheItem)($key, $value, $isHit, $this->tags[$key] ?? null); + } + + public function getItems(array $keys = []): iterable + { + \assert(self::validateKeys($keys)); + + return $this->generateItems($keys, microtime(true), self::$createCacheItem); + } + + public function deleteItem(mixed $key): bool + { + \assert('' !== CacheItem::validateKey($key)); + unset($this->values[$key], $this->tags[$key], $this->expiries[$key]); + + return true; + } + + public function deleteItems(array $keys): bool + { + foreach ($keys as $key) { + $this->deleteItem($key); + } + + return true; + } + + public function save(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $item = (array) $item; + $key = $item["\0*\0key"]; + $value = $item["\0*\0value"]; + $expiry = $item["\0*\0expiry"]; + + $now = microtime(true); + + if (null !== $expiry) { + if (!$expiry) { + $expiry = \PHP_INT_MAX; + } elseif ($expiry <= $now) { + $this->deleteItem($key); + + return true; + } + } + if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) { + return false; + } + if (null === $expiry && 0 < $this->defaultLifetime) { + $expiry = $this->defaultLifetime; + $expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry); + } elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) { + $expiry = $now + $this->maxLifetime; + } + + if ($this->maxItems) { + unset($this->values[$key], $this->tags[$key]); + + // Iterate items and vacuum expired ones while we are at it + foreach ($this->values as $k => $v) { + if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) { + break; + } + + unset($this->values[$k], $this->tags[$k], $this->expiries[$k]); + } + } + + $this->values[$key] = $value; + $this->expiries[$key] = $expiry ?? \PHP_INT_MAX; + + if (null === $this->tags[$key] = $item["\0*\0newMetadata"][CacheItem::METADATA_TAGS] ?? null) { + unset($this->tags[$key]); + } + + return true; + } + + public function saveDeferred(CacheItemInterface $item): bool + { + return $this->save($item); + } + + public function commit(): bool + { + return true; + } + + public function clear(string $prefix = ''): bool + { + if ('' !== $prefix) { + $now = microtime(true); + + foreach ($this->values as $key => $value) { + if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || str_starts_with($key, $prefix)) { + unset($this->values[$key], $this->tags[$key], $this->expiries[$key]); + } + } + + if ($this->values) { + return true; + } + } + + $this->values = $this->tags = $this->expiries = []; + + return true; + } + + /** + * Returns all cached values, with cache miss as null. + */ + public function getValues(): array + { + if (!$this->storeSerialized) { + return $this->values; + } + + $values = $this->values; + foreach ($values as $k => $v) { + if (null === $v || 'N;' === $v) { + continue; + } + if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) { + $values[$k] = serialize($v); + } + } + + return $values; + } + + public function reset(): void + { + $this->clear(); + } + + private function generateItems(array $keys, float $now, \Closure $f): \Generator + { + foreach ($keys as $i => $key) { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) { + $value = null; + + if (!$this->maxItems) { + // Track misses in non-LRU mode only + $this->values[$key] = null; + } + } else { + if ($this->maxItems) { + // Move the item last in the storage + $value = $this->values[$key]; + unset($this->values[$key]); + $this->values[$key] = $value; + } + + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + unset($keys[$i]); + + yield $key => $f($key, $value, $isHit, $this->tags[$key] ?? null); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } + + private function freeze($value, string $key): string|int|float|bool|array|\UnitEnum|null + { + if (null === $value) { + return 'N;'; + } + if (\is_string($value)) { + // Serialize strings if they could be confused with serialized objects or arrays + if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { + return serialize($value); + } + } elseif (!\is_scalar($value)) { + try { + $serialized = serialize($value); + } catch (\Exception $e) { + unset($this->values[$key], $this->tags[$key]); + $type = get_debug_type($value); + $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage()); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return null; + } + // Keep value serialized if it contains any objects or any internal references + if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) { + return $serialized; + } + } + + return $value; + } + + private function unfreeze(string $key, bool &$isHit): mixed + { + if ('N;' === $value = $this->values[$key]) { + return null; + } + if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { + try { + $value = unserialize($value); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $value = false; + } + if (false === $value) { + $value = null; + $isHit = false; + + if (!$this->maxItems) { + $this->values[$key] = null; + } + } + } + + return $value; + } + + private function validateKeys(array $keys): bool + { + foreach ($keys as $key) { + if (!\is_string($key) || !isset($this->expiries[$key])) { + CacheItem::validateKey($key); + } + } + + return true; + } +} diff --git a/vendor/symfony/cache/Adapter/ChainAdapter.php b/vendor/symfony/cache/Adapter/ChainAdapter.php new file mode 100644 index 0000000..1418cff --- /dev/null +++ b/vendor/symfony/cache/Adapter/ChainAdapter.php @@ -0,0 +1,291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Chains several adapters together. + * + * Cached items are fetched from the first adapter having them in its data store. + * They are saved and deleted in all adapters at once. + * + * @author Kévin Dunglas + */ +class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + + private array $adapters = []; + private int $adapterCount; + + private static \Closure $syncItem; + + /** + * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items + * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones + */ + public function __construct( + array $adapters, + private int $defaultLifetime = 0, + ) { + if (!$adapters) { + throw new InvalidArgumentException('At least one adapter must be specified.'); + } + + foreach ($adapters as $adapter) { + if (!$adapter instanceof CacheItemPoolInterface) { + throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class)); + } + if ('cli' === \PHP_SAPI && $adapter instanceof ApcuAdapter && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) { + continue; // skip putting APCu in the chain when the backend is disabled + } + + if ($adapter instanceof AdapterInterface) { + $this->adapters[] = $adapter; + } else { + $this->adapters[] = new ProxyAdapter($adapter); + } + } + $this->adapterCount = \count($this->adapters); + + self::$syncItem ??= \Closure::bind( + static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) { + $sourceItem->isTaggable = false; + $sourceMetadata ??= $sourceItem->metadata; + + $item->value = $sourceItem->value; + $item->isHit = $sourceItem->isHit; + $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata; + + if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) { + $item->expiresAt(\DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY]))); + } elseif (0 < $defaultLifetime) { + $item->expiresAfter($defaultLifetime); + } + + return $item; + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + $doSave = true; + $callback = static function (CacheItem $item, bool &$save) use ($callback, &$doSave) { + $value = $callback($item, $save); + $doSave = $save; + + return $value; + }; + + $wrap = function (?CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$doSave, &$metadata) { + static $lastItem; + static $i = 0; + $adapter = $this->adapters[$i]; + if (isset($this->adapters[++$i])) { + $callback = $wrap; + $beta = \INF === $beta ? \INF : 0; + } + if ($adapter instanceof CacheInterface) { + $value = $adapter->get($key, $callback, $beta, $metadata); + } else { + $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); + } + if (null !== $item) { + (self::$syncItem)($lastItem ??= $item, $item, $this->defaultLifetime, $metadata); + } + $save = $doSave; + + return $value; + }; + + return $wrap(); + } + + public function getItem(mixed $key): CacheItem + { + $syncItem = self::$syncItem; + $misses = []; + + foreach ($this->adapters as $i => $adapter) { + $item = $adapter->getItem($key); + + if ($item->isHit()) { + while (0 <= --$i) { + $this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime)); + } + + return $item; + } + + $misses[$i] = $item; + } + + return $item; + } + + public function getItems(array $keys = []): iterable + { + return $this->generateItems($this->adapters[0]->getItems($keys), 0); + } + + private function generateItems(iterable $items, int $adapterIndex): \Generator + { + $missing = []; + $misses = []; + $nextAdapterIndex = $adapterIndex + 1; + $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null; + + foreach ($items as $k => $item) { + if (!$nextAdapter || $item->isHit()) { + yield $k => $item; + } else { + $missing[] = $k; + $misses[$k] = $item; + } + } + + if ($missing) { + $syncItem = self::$syncItem; + $adapter = $this->adapters[$adapterIndex]; + $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex); + + foreach ($items as $k => $item) { + if ($item->isHit()) { + $adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime)); + } + + yield $k => $item; + } + } + } + + public function hasItem(mixed $key): bool + { + foreach ($this->adapters as $adapter) { + if ($adapter->hasItem($key)) { + return true; + } + } + + return false; + } + + public function clear(string $prefix = ''): bool + { + $cleared = true; + $i = $this->adapterCount; + + while ($i--) { + if ($this->adapters[$i] instanceof AdapterInterface) { + $cleared = $this->adapters[$i]->clear($prefix) && $cleared; + } else { + $cleared = $this->adapters[$i]->clear() && $cleared; + } + } + + return $cleared; + } + + public function deleteItem(mixed $key): bool + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItem($key) && $deleted; + } + + return $deleted; + } + + public function deleteItems(array $keys): bool + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted; + } + + return $deleted; + } + + public function save(CacheItemInterface $item): bool + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->save($item) && $saved; + } + + return $saved; + } + + public function saveDeferred(CacheItemInterface $item): bool + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->saveDeferred($item) && $saved; + } + + return $saved; + } + + public function commit(): bool + { + $committed = true; + $i = $this->adapterCount; + + while ($i--) { + $committed = $this->adapters[$i]->commit() && $committed; + } + + return $committed; + } + + public function prune(): bool + { + $pruned = true; + + foreach ($this->adapters as $adapter) { + if ($adapter instanceof PruneableInterface) { + $pruned = $adapter->prune() && $pruned; + } + } + + return $pruned; + } + + public function reset(): void + { + foreach ($this->adapters as $adapter) { + if ($adapter instanceof ResetInterface) { + $adapter->reset(); + } + } + } +} diff --git a/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php new file mode 100644 index 0000000..106d7fd --- /dev/null +++ b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +trigger_deprecation('symfony/cache', '7.1', 'The "%s" class is deprecated, use "%s" instead.', CouchbaseBucketAdapter::class, CouchbaseCollectionAdapter::class); + +/** + * @author Antonio Jose Cerezo Aranda + * + * @deprecated since Symfony 7.1, use {@see CouchbaseCollectionAdapter} instead + */ +class CouchbaseBucketAdapter extends AbstractAdapter +{ + private const THIRTY_DAYS_IN_SECONDS = 2592000; + private const MAX_KEY_LENGTH = 250; + private const KEY_NOT_FOUND = 13; + private const VALID_DSN_OPTIONS = [ + 'operationTimeout', + 'configTimeout', + 'configNodeTimeout', + 'n1qlTimeout', + 'httpTimeout', + 'configDelay', + 'htconfigIdleTimeout', + 'durabilityInterval', + 'durabilityTimeout', + ]; + + private MarshallerInterface $marshaller; + + public function __construct( + private \CouchbaseBucket $bucket, + string $namespace = '', + int $defaultLifetime = 0, + ?MarshallerInterface $marshaller = null, + ) { + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); + } + + $this->maxIdLength = static::MAX_KEY_LENGTH; + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function createConnection(#[\SensitiveParameter] array|string $servers, array $options = []): \CouchbaseBucket + { + if (\is_string($servers)) { + $servers = [$servers]; + } + + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); + } + + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); + + $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?' + .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\?]+))(?:\?(?.*))?$/i'; + + $newServers = []; + $protocol = 'couchbase'; + try { + $options = self::initOptions($options); + $username = $options['username']; + $password = $options['password']; + + foreach ($servers as $dsn) { + if (!str_starts_with($dsn, 'couchbase:')) { + throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); + } + + preg_match($dsnPattern, $dsn, $matches); + + $username = $matches['username'] ?: $username; + $password = $matches['password'] ?: $password; + $protocol = $matches['protocol'] ?: $protocol; + + if (isset($matches['options'])) { + $optionsInDsn = self::getOptions($matches['options']); + + foreach ($optionsInDsn as $parameter => $value) { + $options[$parameter] = $value; + } + } + + $newServers[] = $matches['host']; + } + + $connectionString = $protocol.'://'.implode(',', $newServers); + + $client = new \CouchbaseCluster($connectionString); + $client->authenticateAs($username, $password); + + $bucket = $client->openBucket($matches['bucketName']); + + unset($options['username'], $options['password']); + foreach ($options as $option => $value) { + if ($value) { + $bucket->$option = $value; + } + } + + return $bucket; + } finally { + restore_error_handler(); + } + } + + public static function isSupported(): bool + { + return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<'); + } + + private static function getOptions(string $options): array + { + $results = []; + $optionsInArray = explode('&', $options); + + foreach ($optionsInArray as $option) { + [$key, $value] = explode('=', $option); + + if (\in_array($key, static::VALID_DSN_OPTIONS, true)) { + $results[$key] = $value; + } + } + + return $results; + } + + private static function initOptions(array $options): array + { + $options['username'] ??= ''; + $options['password'] ??= ''; + $options['operationTimeout'] ??= 0; + $options['configTimeout'] ??= 0; + $options['configNodeTimeout'] ??= 0; + $options['n1qlTimeout'] ??= 0; + $options['httpTimeout'] ??= 0; + $options['configDelay'] ??= 0; + $options['htconfigIdleTimeout'] ??= 0; + $options['durabilityInterval'] ??= 0; + $options['durabilityTimeout'] ??= 0; + + return $options; + } + + protected function doFetch(array $ids): iterable + { + $resultsCouchbase = $this->bucket->get($ids); + + $results = []; + foreach ($resultsCouchbase as $key => $value) { + if (null !== $value->error) { + continue; + } + $results[$key] = $this->marshaller->unmarshall($value->value); + } + + return $results; + } + + protected function doHave(string $id): bool + { + return false !== $this->bucket->get($id); + } + + protected function doClear(string $namespace): bool + { + if ('' === $namespace) { + $this->bucket->manager()->flush(); + + return true; + } + + return false; + } + + protected function doDelete(array $ids): bool + { + $results = $this->bucket->remove(array_values($ids)); + + foreach ($results as $key => $result) { + if (null !== $result->error && static::KEY_NOT_FOUND !== $result->error->getCode()) { + continue; + } + unset($results[$key]); + } + + return 0 === \count($results); + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $lifetime = $this->normalizeExpiry($lifetime); + + $ko = []; + foreach ($values as $key => $value) { + $result = $this->bucket->upsert($key, $value, ['expiry' => $lifetime]); + + if (null !== $result->error) { + $ko[$key] = $result; + } + } + + return [] === $ko ? true : $ko; + } + + private function normalizeExpiry(int $expiry): int + { + if ($expiry && $expiry > static::THIRTY_DAYS_IN_SECONDS) { + $expiry += time(); + } + + return $expiry; + } +} diff --git a/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php new file mode 100644 index 0000000..9646bc3 --- /dev/null +++ b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Couchbase\Bucket; +use Couchbase\Cluster; +use Couchbase\ClusterOptions; +use Couchbase\Collection; +use Couchbase\DocumentNotFoundException; +use Couchbase\UpsertOptions; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Antonio Jose Cerezo Aranda + */ +class CouchbaseCollectionAdapter extends AbstractAdapter +{ + private const MAX_KEY_LENGTH = 250; + + private MarshallerInterface $marshaller; + + public function __construct( + private Collection $connection, + string $namespace = '', + int $defaultLifetime = 0, + ?MarshallerInterface $marshaller = null, + ) { + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 3.0.5 < 4.0.0 is required.'); + } + + $this->maxIdLength = static::MAX_KEY_LENGTH; + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function createConnection(#[\SensitiveParameter] array|string $dsn, array $options = []): Bucket|Collection + { + if (\is_string($dsn)) { + $dsn = [$dsn]; + } + + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 3.0.5 < 4.0.0 is required.'); + } + + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); + + $pathPattern = '/^(?:\/(?[^\/\?]+))(?:(?:\/(?[^\/]+))(?:\/(?[^\/\?]+)))?(?:\/)?$/'; + $newServers = []; + $protocol = 'couchbase'; + try { + $username = $options['username'] ?? ''; + $password = $options['password'] ?? ''; + + foreach ($dsn as $server) { + if (!str_starts_with($server, 'couchbase:')) { + throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); + } + + $params = parse_url($server); + + $username = isset($params['user']) ? rawurldecode($params['user']) : $username; + $password = isset($params['pass']) ? rawurldecode($params['pass']) : $password; + $protocol = $params['scheme'] ?? $protocol; + + if (isset($params['query'])) { + $optionsInDsn = self::getOptions($params['query']); + + foreach ($optionsInDsn as $parameter => $value) { + $options[$parameter] = $value; + } + } + + $newServers[] = $params['host']; + } + + $option = isset($params['query']) ? '?'.$params['query'] : ''; + $connectionString = $protocol.'://'.implode(',', $newServers).$option; + + $clusterOptions = new ClusterOptions(); + $clusterOptions->credentials($username, $password); + + $client = new Cluster($connectionString, $clusterOptions); + + preg_match($pathPattern, $params['path'] ?? '', $matches); + $bucket = $client->bucket($matches['bucketName']); + $collection = $bucket->defaultCollection(); + if (!empty($matches['scopeName'])) { + $scope = $bucket->scope($matches['scopeName']); + $collection = $scope->collection($matches['collectionName']); + } + + return $collection; + } finally { + restore_error_handler(); + } + } + + public static function isSupported(): bool + { + return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '3.0.5', '>=') && version_compare(phpversion('couchbase'), '4.0', '<'); + } + + private static function getOptions(string $options): array + { + $results = []; + $optionsInArray = explode('&', $options); + + foreach ($optionsInArray as $option) { + [$key, $value] = explode('=', $option); + + $results[$key] = $value; + } + + return $results; + } + + protected function doFetch(array $ids): array + { + $results = []; + foreach ($ids as $id) { + try { + $resultCouchbase = $this->connection->get($id); + } catch (DocumentNotFoundException) { + continue; + } + + $content = $resultCouchbase->value ?? $resultCouchbase->content(); + + $results[$id] = $this->marshaller->unmarshall($content); + } + + return $results; + } + + protected function doHave($id): bool + { + return $this->connection->exists($id)->exists(); + } + + protected function doClear($namespace): bool + { + return false; + } + + protected function doDelete(array $ids): bool + { + $idsErrors = []; + foreach ($ids as $id) { + try { + $result = $this->connection->remove($id); + + if (null === $result->mutationToken()) { + $idsErrors[] = $id; + } + } catch (DocumentNotFoundException) { + } + } + + return 0 === \count($idsErrors); + } + + protected function doSave(array $values, $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $upsertOptions = new UpsertOptions(); + $upsertOptions->expiry($lifetime); + + $ko = []; + foreach ($values as $key => $value) { + try { + $this->connection->upsert($key, $value, $upsertOptions); + } catch (\Exception) { + $ko[$key] = ''; + } + } + + return [] === $ko ? true : $ko; + } +} diff --git a/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php b/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php new file mode 100644 index 0000000..ae2bea7 --- /dev/null +++ b/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Tools\DsnParser; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; + +class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface +{ + private const MAX_KEY_LENGTH = 255; + + private MarshallerInterface $marshaller; + private Connection $conn; + private string $platformName; + private string $table = 'cache_items'; + private string $idCol = 'item_id'; + private string $dataCol = 'item_data'; + private string $lifetimeCol = 'item_lifetime'; + private string $timeCol = 'item_time'; + + /** + * You can either pass an existing database Doctrine DBAL Connection or + * a DSN string that will be used to connect to the database. + * + * The cache table is created automatically when possible. + * Otherwise, use the createTable() method. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct( + Connection|string $connOrDsn, + private string $namespace = '', + int $defaultLifetime = 0, + array $options = [], + ?MarshallerInterface $marshaller = null, + ) { + if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($connOrDsn instanceof Connection) { + $this->conn = $connOrDsn; + } else { + if (!class_exists(DriverManager::class)) { + throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".'); + } + $params = (new DsnParser([ + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]))->parse($connOrDsn); + + $config = new Configuration(); + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + + $this->conn = DriverManager::getConnection($params, $config); + } + + $this->maxIdLength = self::MAX_KEY_LENGTH; + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + + parent::__construct($namespace, $defaultLifetime); + } + + /** + * Creates the table to store cache items which can be called once for setup. + * + * Cache ID are saved in a column of maximum length 255. Cache data is + * saved in a BLOB. + * + * @throws DBALException When the table already exists + */ + public function createTable(): void + { + $schema = new Schema(); + $this->addTableToSchema($schema); + + foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { + $this->conn->executeStatement($sql); + } + } + + public function configureSchema(Schema $schema, Connection $forConnection, \Closure $isSameDatabase): void + { + if ($schema->hasTable($this->table)) { + return; + } + + if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) { + return; + } + + $this->addTableToSchema($schema); + } + + public function prune(): bool + { + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ?"; + $params = [time()]; + $paramTypes = [ParameterType::INTEGER]; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE ?"; + $params[] = sprintf('%s%%', $this->namespace); + $paramTypes[] = ParameterType::STRING; + } + + try { + $this->conn->executeStatement($deleteSql, $params, $paramTypes); + } catch (TableNotFoundException) { + } + + return true; + } + + protected function doFetch(array $ids): iterable + { + $now = time(); + $expired = []; + + $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN (?)"; + $result = $this->conn->executeQuery($sql, [ + $now, + $ids, + ], [ + ParameterType::INTEGER, + ArrayParameterType::STRING, + ])->iterateNumeric(); + + foreach ($result as $row) { + if (null === $row[1]) { + $expired[] = $row[0]; + } else { + yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]); + } + } + + if ($expired) { + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN (?)"; + $this->conn->executeStatement($sql, [ + $now, + $expired, + ], [ + ParameterType::INTEGER, + ArrayParameterType::STRING, + ]); + } + } + + protected function doHave(string $id): bool + { + $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = ? AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ?)"; + $result = $this->conn->executeQuery($sql, [ + $id, + time(), + ], [ + ParameterType::STRING, + ParameterType::INTEGER, + ]); + + return (bool) $result->fetchOne(); + } + + protected function doClear(string $namespace): bool + { + if ('' === $namespace) { + $sql = $this->conn->getDatabasePlatform()->getTruncateTableSQL($this->table); + } else { + $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; + } + + try { + $this->conn->executeStatement($sql); + } catch (TableNotFoundException) { + } + + return true; + } + + protected function doDelete(array $ids): bool + { + $sql = "DELETE FROM $this->table WHERE $this->idCol IN (?)"; + try { + $this->conn->executeStatement($sql, [array_values($ids)], [ArrayParameterType::STRING]); + } catch (TableNotFoundException) { + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $platformName = $this->getPlatformName(); + $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?)"; + + switch ($platformName) { + case 'mysql': + $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci': + // DUAL is Oracle specific dummy table + $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv': + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite': + $sql = 'INSERT OR REPLACE'.substr($insertSql, 6); + break; + case 'pgsql': + $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + $platformName = null; + $sql = "UPDATE $this->table SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ? WHERE $this->idCol = ?"; + break; + } + + $now = time(); + $lifetime = $lifetime ?: null; + try { + $stmt = $this->conn->prepare($sql); + } catch (TableNotFoundException) { + if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt = $this->conn->prepare($sql); + } + + if ('sqlsrv' === $platformName || 'oci' === $platformName) { + $bind = static function ($id, $data) use ($stmt) { + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $id); + $stmt->bindValue(3, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(6, $data, ParameterType::LARGE_OBJECT); + }; + $stmt->bindValue(4, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(5, $now, ParameterType::INTEGER); + $stmt->bindValue(7, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(8, $now, ParameterType::INTEGER); + } elseif (null !== $platformName) { + $bind = static function ($id, $data) use ($stmt) { + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); + }; + $stmt->bindValue(3, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(4, $now, ParameterType::INTEGER); + } else { + $stmt->bindValue(2, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(3, $now, ParameterType::INTEGER); + + $insertStmt = $this->conn->prepare($insertSql); + $insertStmt->bindValue(3, $lifetime, ParameterType::INTEGER); + $insertStmt->bindValue(4, $now, ParameterType::INTEGER); + + $bind = static function ($id, $data) use ($stmt, $insertStmt) { + $stmt->bindValue(1, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(4, $id); + $insertStmt->bindValue(1, $id); + $insertStmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); + }; + } + + foreach ($values as $id => $data) { + $bind($id, $data); + try { + $rowCount = $stmt->executeStatement(); + } catch (TableNotFoundException) { + if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $rowCount = $stmt->executeStatement(); + } + if (null === $platformName && 0 === $rowCount) { + try { + $insertStmt->executeStatement(); + } catch (DBALException) { + // A concurrent write won, let it be + } + } + } + + return $failed; + } + + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ('pgsql' !== $this->platformName ??= $this->getPlatformName()) { + return parent::getId($key); + } + + if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { + $key = rawurlencode($key); + } + + return parent::getId($key); + } + + private function getPlatformName(): string + { + if (isset($this->platformName)) { + return $this->platformName; + } + + $platform = $this->conn->getDatabasePlatform(); + + return $this->platformName = match (true) { + $platform instanceof \Doctrine\DBAL\Platforms\AbstractMySQLPlatform => 'mysql', + $platform instanceof \Doctrine\DBAL\Platforms\SqlitePlatform => 'sqlite', + $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform => 'pgsql', + $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform => 'oci', + $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform => 'sqlsrv', + default => $platform::class, + }; + } + + private function addTableToSchema(Schema $schema): void + { + $types = [ + 'mysql' => 'binary', + 'sqlite' => 'text', + ]; + + $table = $schema->createTable($this->table); + $table->addColumn($this->idCol, $types[$this->getPlatformName()] ?? 'string', ['length' => 255]); + $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]); + $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]); + $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]); + $table->setPrimaryKey([$this->idCol]); + } +} diff --git a/vendor/symfony/cache/Adapter/FilesystemAdapter.php b/vendor/symfony/cache/Adapter/FilesystemAdapter.php new file mode 100644 index 0000000..13daa56 --- /dev/null +++ b/vendor/symfony/cache/Adapter/FilesystemAdapter.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +class FilesystemAdapter extends AbstractAdapter implements PruneableInterface +{ + use FilesystemTrait; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } +} diff --git a/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php new file mode 100644 index 0000000..80edee4 --- /dev/null +++ b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +/** + * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls. + * + * @author Nicolas Grekas + * @author André Rømcke + */ +class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface +{ + use FilesystemTrait { + prune as private doPrune; + doClear as private doClearCache; + doSave as private doSaveCache; + } + + /** + * Folder used for tag symlinks. + */ + private const TAG_FOLDER = 'tags'; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) + { + $this->marshaller = new TagAwareMarshaller($marshaller); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } + + public function prune(): bool + { + $ok = $this->doPrune(); + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + $dir .= \DIRECTORY_SEPARATOR; + $keepDir = false; + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' === $link || '..' === $link) { + continue; + } + if ('_' !== $dir[-2] && realpath($d.\DIRECTORY_SEPARATOR.$link)) { + $keepDir = true; + } else { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + $keepDir ?: rmdir($d); + } + $keepDir ?: rmdir($dir.$chars[$i]); + } + $keepDir ?: rmdir($dir); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + + protected function doClear(string $namespace): bool + { + $ok = $this->doClearCache($namespace); + + if ('' !== $namespace) { + return $ok; + } + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + $this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + if (rename($dir, $renamed = substr_replace($dir, $this->tmpSuffix.'_', -9))) { + $dir = $renamed.\DIRECTORY_SEPARATOR; + } else { + $dir .= \DIRECTORY_SEPARATOR; + $renamed = null; + } + + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + null === $renamed ?: rmdir($d); + } + null === $renamed ?: rmdir($dir.$chars[$i]); + } + null === $renamed ?: rmdir($renamed); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + + protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array + { + $failed = $this->doSaveCache($values, $lifetime); + + // Add Tags as symlinks + foreach ($addTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + $file = $this->getFile($id); + + if (!@symlink($file, $tagLink = $this->getFile($id, true, $tagFolder)) && !is_link($tagLink)) { + @unlink($file); + $failed[] = $id; + } + } + } + + // Unlink removed Tags + foreach ($removeTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return $failed; + } + + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!is_file($file) || !$h = @fopen($file, 'r')) { + continue; + } + + if (!@unlink($file)) { + fclose($h); + continue; + } + + $meta = explode("\n", fread($h, 4096), 3)[2] ?? ''; + + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) { + $meta[9] = "\0"; + $tagLen = unpack('Nlen', $meta, 9)['len']; + $meta = substr($meta, 13, $tagLen); + + if (0 < $tagLen -= \strlen($meta)) { + $meta .= fread($h, $tagLen); + } + + try { + yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta); + } catch (\Exception) { + yield $id => []; + } + } + + fclose($h); + } + } + + protected function doDeleteTagRelations(array $tagData): bool + { + foreach ($tagData as $tagId => $idList) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($idList as $id) { + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return true; + } + + protected function doInvalidate(array $tagIds): bool + { + foreach ($tagIds as $tagId) { + if (!is_dir($tagFolder = $this->getTagFolder($tagId))) { + continue; + } + + $this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + + set_error_handler(static function () {}); + + try { + if (rename($tagFolder, $renamed = substr_replace($tagFolder, $this->tmpSuffix.'_', -10))) { + $tagFolder = $renamed.\DIRECTORY_SEPARATOR; + } else { + $renamed = null; + } + + foreach ($this->scanHashDir($tagFolder) as $itemLink) { + unlink(realpath($itemLink) ?: $itemLink); + unlink($itemLink); + } + + if (null === $renamed) { + continue; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + for ($j = 0; $j < 38; ++$j) { + rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]); + } + rmdir($tagFolder.$chars[$i]); + } + rmdir($renamed); + } finally { + restore_error_handler(); + } + } + + return true; + } + + private function getTagFolder(string $tagId): string + { + return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/cache/Adapter/MemcachedAdapter.php b/vendor/symfony/cache/Adapter/MemcachedAdapter.php new file mode 100644 index 0000000..033d987 --- /dev/null +++ b/vendor/symfony/cache/Adapter/MemcachedAdapter.php @@ -0,0 +1,329 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Rob Frawley 2nd + * @author Nicolas Grekas + */ +class MemcachedAdapter extends AbstractAdapter +{ + /** + * We are replacing characters that are illegal in Memcached keys with reserved characters from + * {@see \Symfony\Contracts\Cache\ItemInterface::RESERVED_CHARACTERS} that are legal in Memcached. + * Note: don’t use {@see AbstractAdapter::NS_SEPARATOR}. + */ + private const RESERVED_MEMCACHED = " \n\r\t\v\f\0"; + private const RESERVED_PSR6 = '@()\{}/'; + private const MAX_KEY_LENGTH = 250; + + private MarshallerInterface $marshaller; + private \Memcached $client; + private \Memcached $lazyClient; + + /** + * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged. + * Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that: + * - the Memcached::OPT_BINARY_PROTOCOL must be enabled + * (that's the default when using MemcachedAdapter::createConnection()); + * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation; + * your Memcached memory should be large enough to never trigger LRU. + * + * Using a MemcachedAdapter as a pure items store is fine. + */ + public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) + { + if (!static::isSupported()) { + throw new CacheException('Memcached > 3.1.5 is required.'); + } + $this->maxIdLength = self::MAX_KEY_LENGTH; + + if ('Memcached' === $client::class) { + $opt = $client->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY)); + $this->client = $client; + } else { + $this->lazyClient = $client; + } + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function isSupported(): bool + { + return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '3.1.6', '>='); + } + + /** + * Creates a Memcached instance. + * + * By default, the binary protocol, no block, and libketama compatible options are enabled. + * + * Examples for servers: + * - 'memcached://user:pass@localhost?weight=33' + * - [['localhost', 11211, 33]] + * + * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs + * + * @throws \ErrorException When invalid options or servers are provided + */ + public static function createConnection(#[\SensitiveParameter] array|string $servers, array $options = []): \Memcached + { + if (\is_string($servers)) { + $servers = [$servers]; + } + if (!static::isSupported()) { + throw new CacheException('Memcached > 3.1.5 is required.'); + } + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); + try { + $client = new \Memcached($options['persistent_id'] ?? null); + $username = $options['username'] ?? null; + $password = $options['password'] ?? null; + + // parse any DSN in $servers + foreach ($servers as $i => $dsn) { + if (\is_array($dsn)) { + continue; + } + if (!str_starts_with($dsn, 'memcached:')) { + throw new InvalidArgumentException('Invalid Memcached DSN: it does not start with "memcached:".'); + } + $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { + if (!empty($m[2])) { + [$username, $password] = explode(':', $m[2], 2) + [1 => null]; + $username = rawurldecode($username); + $password = null !== $password ? rawurldecode($password) : null; + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException('Invalid Memcached DSN.'); + } + $query = $hosts = []; + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException('Invalid Memcached DSN: query parameter "host" must be an array.'); + } + foreach ($hosts as $host => $weight) { + if (false === $port = strrpos($host, ':')) { + $hosts[$host] = [$host, 11211, (int) $weight]; + } else { + $hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight]; + } + } + $hosts = array_values($hosts); + unset($query['host']); + } + if ($hosts && !isset($params['host']) && !isset($params['path'])) { + unset($servers[$i]); + $servers = array_merge($servers, $hosts); + continue; + } + } + if (!isset($params['host']) && !isset($params['path'])) { + throw new InvalidArgumentException('Invalid Memcached DSN: missing host or path.'); + } + if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) { + $params['weight'] = $m[1]; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } + $params += [ + 'host' => $params['host'] ?? $params['path'], + 'port' => isset($params['host']) ? 11211 : null, + 'weight' => 0, + ]; + if ($query) { + $params += $query; + $options = $query + $options; + } + + $servers[$i] = [$params['host'], $params['port'], $params['weight']]; + + if ($hosts) { + $servers = array_merge($servers, $hosts); + } + } + + // set client's options + unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']); + $options = array_change_key_case($options, \CASE_UPPER); + $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $client->setOption(\Memcached::OPT_NO_BLOCK, true); + $client->setOption(\Memcached::OPT_TCP_NODELAY, true); + if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) { + $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true); + } + foreach ($options as $name => $value) { + if (\is_int($name)) { + continue; + } + if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) { + $value = \constant('Memcached::'.$name.'_'.strtoupper($value)); + } + unset($options[$name]); + + if (\defined('Memcached::OPT_'.$name)) { + $options[\constant('Memcached::OPT_'.$name)] = $value; + } + } + $client->setOptions($options + [\Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP]); + + // set client's servers, taking care of persistent connections + if (!$client->isPristine()) { + $oldServers = []; + foreach ($client->getServerList() as $server) { + $oldServers[] = [$server['host'], $server['port']]; + } + + $newServers = []; + foreach ($servers as $server) { + if (1 < \count($server)) { + $server = array_values($server); + unset($server[2]); + $server[1] = (int) $server[1]; + } + $newServers[] = $server; + } + + if ($oldServers !== $newServers) { + $client->resetServerList(); + $client->addServers($servers); + } + } else { + $client->addServers($servers); + } + + if (null !== $username || null !== $password) { + if (!method_exists($client, 'setSaslAuthData')) { + trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.'); + } + $client->setSaslAuthData($username, $password); + } + + return $client; + } finally { + restore_error_handler(); + } + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + if ($lifetime && $lifetime > 30 * 86400) { + $lifetime += time(); + } + + $encodedValues = []; + foreach ($values as $key => $value) { + $encodedValues[self::encodeKey($key)] = $value; + } + + return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false; + } + + protected function doFetch(array $ids): iterable + { + try { + $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids); + + $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds)); + + $result = []; + foreach ($encodedResult as $key => $value) { + $result[self::decodeKey($key)] = $this->marshaller->unmarshall($value); + } + + return $result; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } + } + + protected function doHave(string $id): bool + { + return false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); + } + + protected function doDelete(array $ids): bool + { + $ok = true; + $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids); + foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) { + if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) { + $ok = false; + } + } + + return $ok; + } + + protected function doClear(string $namespace): bool + { + return '' === $namespace && $this->getClient()->flush(); + } + + private function checkResultCode(mixed $result): mixed + { + $code = $this->client->getResultCode(); + + if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) { + return $result; + } + + throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage())); + } + + private function getClient(): \Memcached + { + if (isset($this->client)) { + return $this->client; + } + + $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) { + throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix)); + } + + return $this->client = $this->lazyClient; + } + + private static function encodeKey(string $key): string + { + return strtr($key, self::RESERVED_MEMCACHED, self::RESERVED_PSR6); + } + + private static function decodeKey(string $key): string + { + return strtr($key, self::RESERVED_PSR6, self::RESERVED_MEMCACHED); + } +} diff --git a/vendor/symfony/cache/Adapter/NullAdapter.php b/vendor/symfony/cache/Adapter/NullAdapter.php new file mode 100644 index 0000000..d5d2ef6 --- /dev/null +++ b/vendor/symfony/cache/Adapter/NullAdapter.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Titouan Galopin + */ +class NullAdapter implements AdapterInterface, CacheInterface +{ + private static \Closure $createCacheItem; + + public function __construct() + { + self::$createCacheItem ??= \Closure::bind( + static function ($key) { + $item = new CacheItem(); + $item->key = $key; + $item->isHit = false; + + return $item; + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + $save = true; + + return $callback((self::$createCacheItem)($key), $save); + } + + public function getItem(mixed $key): CacheItem + { + return (self::$createCacheItem)($key); + } + + public function getItems(array $keys = []): iterable + { + return $this->generateItems($keys); + } + + public function hasItem(mixed $key): bool + { + return false; + } + + public function clear(string $prefix = ''): bool + { + return true; + } + + public function deleteItem(mixed $key): bool + { + return true; + } + + public function deleteItems(array $keys): bool + { + return true; + } + + public function save(CacheItemInterface $item): bool + { + return true; + } + + public function saveDeferred(CacheItemInterface $item): bool + { + return true; + } + + public function commit(): bool + { + return true; + } + + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function generateItems(array $keys): \Generator + { + $f = self::$createCacheItem; + + foreach ($keys as $key) { + yield $key => $f($key); + } + } +} diff --git a/vendor/symfony/cache/Adapter/ParameterNormalizer.php b/vendor/symfony/cache/Adapter/ParameterNormalizer.php new file mode 100644 index 0000000..a689640 --- /dev/null +++ b/vendor/symfony/cache/Adapter/ParameterNormalizer.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +/** + * @author Lars Strojny + */ +final class ParameterNormalizer +{ + public static function normalizeDuration(string $duration): int + { + if (is_numeric($duration)) { + return $duration; + } + + if (false !== $time = strtotime($duration, 0)) { + return $time; + } + + try { + return \DateTimeImmutable::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp(); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('Cannot parse date interval "%s".', $duration), 0, $e); + } + } +} diff --git a/vendor/symfony/cache/Adapter/PdoAdapter.php b/vendor/symfony/cache/Adapter/PdoAdapter.php new file mode 100644 index 0000000..b18428d --- /dev/null +++ b/vendor/symfony/cache/Adapter/PdoAdapter.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; + +class PdoAdapter extends AbstractAdapter implements PruneableInterface +{ + private const MAX_KEY_LENGTH = 255; + + private MarshallerInterface $marshaller; + private \PDO $conn; + private string $dsn; + private string $driver; + private string $serverVersion; + private string $table = 'cache_items'; + private string $idCol = 'item_id'; + private string $dataCol = 'item_data'; + private string $lifetimeCol = 'item_lifetime'; + private string $timeCol = 'item_time'; + private ?string $username = null; + private ?string $password = null; + private array $connectionOptions = []; + private string $namespace; + + /** + * You can either pass an existing database connection as PDO instance or + * a DSN string that will be used to lazy-connect to the database when the + * cache is actually used. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * + * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string + * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct(#[\SensitiveParameter] \PDO|string $connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], ?MarshallerInterface $marshaller = null) + { + if (\is_string($connOrDsn) && str_contains($connOrDsn, '://')) { + throw new InvalidArgumentException(sprintf('Usage of Doctrine DBAL URL with "%s" is not supported. Use a PDO DSN or "%s" instead.', __CLASS__, DoctrineDbalAdapter::class)); + } + + if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($connOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); + } + + $this->conn = $connOrDsn; + } else { + $this->dsn = $connOrDsn; + } + + $this->maxIdLength = self::MAX_KEY_LENGTH; + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->username = $options['db_username'] ?? $this->username; + $this->password = $options['db_password'] ?? $this->password; + $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; + $this->namespace = $namespace; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + + parent::__construct($namespace, $defaultLifetime); + } + + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \PDO|string + { + if ($options['lazy'] ?? true) { + return $dsn; + } + + $pdo = new \PDO($dsn); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + return $pdo; + } + + /** + * Creates the table to store cache items which can be called once for setup. + * + * Cache ID are saved in a column of maximum length 255. Cache data is + * saved in a BLOB. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable(): void + { + $sql = match ($driver = $this->getDriver()) { + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + 'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB", + 'sqlite' => "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + default => throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $driver)), + }; + + $this->getConnection()->exec($sql); + } + + public function prune(): bool + { + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time"; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE :namespace"; + } + + $connection = $this->getConnection(); + + try { + $delete = $connection->prepare($deleteSql); + } catch (\PDOException) { + return true; + } + $delete->bindValue(':time', time(), \PDO::PARAM_INT); + + if ('' !== $this->namespace) { + $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR); + } + try { + return $delete->execute(); + } catch (\PDOException) { + return true; + } + } + + protected function doFetch(array $ids): iterable + { + $connection = $this->getConnection(); + + $now = time(); + $expired = []; + + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)"; + $stmt = $connection->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($ids as $id) { + $stmt->bindValue(++$i, $id); + } + $result = $stmt->execute(); + + if (\is_object($result)) { + $result = $result->iterateNumeric(); + } else { + $stmt->setFetchMode(\PDO::FETCH_NUM); + $result = $stmt; + } + + foreach ($result as $row) { + if (null === $row[1]) { + $expired[] = $row[0]; + } else { + yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]); + } + } + + if ($expired) { + $sql = str_pad('', (\count($expired) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)"; + $stmt = $connection->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($expired as $id) { + $stmt->bindValue(++$i, $id); + } + $stmt->execute(); + } + } + + protected function doHave(string $id): bool + { + $connection = $this->getConnection(); + + $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)"; + $stmt = $connection->prepare($sql); + + $stmt->bindValue(':id', $id); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + + return (bool) $stmt->fetchColumn(); + } + + protected function doClear(string $namespace): bool + { + $conn = $this->getConnection(); + + if ('' === $namespace) { + if ('sqlite' === $this->getDriver()) { + $sql = "DELETE FROM $this->table"; + } else { + $sql = "TRUNCATE TABLE $this->table"; + } + } else { + $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; + } + + try { + $conn->exec($sql); + } catch (\PDOException) { + } + + return true; + } + + protected function doDelete(array $ids): bool + { + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)"; + try { + $stmt = $this->getConnection()->prepare($sql); + $stmt->execute(array_values($ids)); + } catch (\PDOException) { + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $conn = $this->getConnection(); + + $driver = $this->getDriver(); + $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + + switch (true) { + case 'mysql' === $driver: + $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci' === $driver: + // DUAL is Oracle specific dummy table + $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $driver: + $sql = 'INSERT OR REPLACE'.substr($insertSql, 6); + break; + case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='): + $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + $driver = null; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $now = time(); + $lifetime = $lifetime ?: null; + try { + $stmt = $conn->prepare($sql); + } catch (\PDOException $e) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { + $this->createTable(); + } + $stmt = $conn->prepare($sql); + } + + // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution. + if ('sqlsrv' === $driver || 'oci' === $driver) { + $stmt->bindParam(1, $id); + $stmt->bindParam(2, $id); + $stmt->bindParam(3, $data, \PDO::PARAM_LOB); + $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(5, $now, \PDO::PARAM_INT); + $stmt->bindParam(6, $data, \PDO::PARAM_LOB); + $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(8, $now, \PDO::PARAM_INT); + } else { + $stmt->bindParam(':id', $id); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + if (null === $driver) { + $insertStmt = $conn->prepare($insertSql); + + $insertStmt->bindParam(':id', $id); + $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + + foreach ($values as $id => $data) { + try { + $stmt->execute(); + } catch (\PDOException $e) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { + $this->createTable(); + } + $stmt->execute(); + } + if (null === $driver && !$stmt->rowCount()) { + try { + $insertStmt->execute(); + } catch (\PDOException) { + // A concurrent write won, let it be + } + } + } + + return $failed; + } + + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ('pgsql' !== $this->getDriver()) { + return parent::getId($key); + } + + if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { + $key = rawurlencode($key); + } + + return parent::getId($key); + } + + private function getConnection(): \PDO + { + if (!isset($this->conn)) { + $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions); + $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } + + return $this->conn; + } + + private function getDriver(): string + { + return $this->driver ??= $this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + private function getServerVersion(): string + { + return $this->serverVersion ??= $this->getConnection()->getAttribute(\PDO::ATTR_SERVER_VERSION); + } + + private function isTableMissing(\PDOException $exception): bool + { + $driver = $this->getDriver(); + [$sqlState, $code] = $exception->errorInfo ?? [null, $exception->getCode()]; + + return match ($driver) { + 'pgsql' => '42P01' === $sqlState, + 'sqlite' => str_contains($exception->getMessage(), 'no such table:'), + 'oci' => 942 === $code, + 'sqlsrv' => 208 === $code, + 'mysql' => 1146 === $code, + default => false, + }; + } +} diff --git a/vendor/symfony/cache/Adapter/PhpArrayAdapter.php b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php new file mode 100644 index 0000000..f92a238 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php @@ -0,0 +1,389 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Component\VarExporter\VarExporter; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. + * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter. + * + * @author Titouan Galopin + * @author Nicolas Grekas + */ +class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + use ProxyTrait; + + private array $keys; + private array $values; + + private static \Closure $createCacheItem; + private static array $valuesCache = []; + + /** + * @param string $file The PHP file were values are cached + * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public function __construct( + private string $file, + AdapterInterface $fallbackPool, + ) { + $this->pool = $fallbackPool; + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * This adapter takes advantage of how PHP stores arrays in its latest versions. + * + * @param string $file The PHP file were values are cached + * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public static function create(string $file, CacheItemPoolInterface $fallbackPool): CacheItemPoolInterface + { + if (!$fallbackPool instanceof AdapterInterface) { + $fallbackPool = new ProxyAdapter($fallbackPool); + } + + return new static($file, $fallbackPool); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + if (!isset($this->values)) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + get_from_pool: + if ($this->pool instanceof CacheInterface) { + return $this->pool->get($key, $callback, $beta, $metadata); + } + + return $this->doGet($this->pool, $key, $callback, $beta, $metadata); + } + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + return null; + } + try { + if ($value instanceof \Closure) { + return $value(); + } + } catch (\Throwable) { + unset($this->keys[$key]); + goto get_from_pool; + } + + return $value; + } + + public function getItem(mixed $key): CacheItem + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (!isset($this->values)) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + return $this->pool->getItem($key); + } + + $value = $this->values[$this->keys[$key]]; + $isHit = true; + + if ('N;' === $value) { + $value = null; + } elseif ($value instanceof \Closure) { + try { + $value = $value(); + } catch (\Throwable) { + $value = null; + $isHit = false; + } + } + + return (self::$createCacheItem)($key, $value, $isHit); + } + + public function getItems(array $keys = []): iterable + { + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + } + if (!isset($this->values)) { + $this->initialize(); + } + + return $this->generateItems($keys); + } + + public function hasItem(mixed $key): bool + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (!isset($this->values)) { + $this->initialize(); + } + + return isset($this->keys[$key]) || $this->pool->hasItem($key); + } + + public function deleteItem(mixed $key): bool + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (!isset($this->values)) { + $this->initialize(); + } + + return !isset($this->keys[$key]) && $this->pool->deleteItem($key); + } + + public function deleteItems(array $keys): bool + { + $deleted = true; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + + if (isset($this->keys[$key])) { + $deleted = false; + } else { + $fallbackKeys[] = $key; + } + } + if (!isset($this->values)) { + $this->initialize(); + } + + if ($fallbackKeys) { + $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted; + } + + return $deleted; + } + + public function save(CacheItemInterface $item): bool + { + if (!isset($this->values)) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->save($item); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + if (!isset($this->values)) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); + } + + public function commit(): bool + { + return $this->pool->commit(); + } + + public function clear(string $prefix = ''): bool + { + $this->keys = $this->values = []; + + $cleared = @unlink($this->file) || !file_exists($this->file); + unset(self::$valuesCache[$this->file]); + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix) && $cleared; + } + + return $this->pool->clear() && $cleared; + } + + /** + * Store an array of cached values. + * + * @param array $values The cached values + * + * @return string[] A list of classes to preload on PHP 7.4+ + */ + public function warmUp(array $values): array + { + if (file_exists($this->file)) { + if (!is_file($this->file)) { + throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".', $this->file)); + } + + if (!is_writable($this->file)) { + throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".', $this->file)); + } + } else { + $directory = \dirname($this->file); + + if (!is_dir($directory) && !@mkdir($directory, 0777, true)) { + throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".', $directory)); + } + + if (!is_writable($directory)) { + throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".', $directory)); + } + } + + $preload = []; + $dumpedValues = ''; + $dumpedMap = []; + $dump = <<<'EOF' + $value) { + CacheItem::validateKey(\is_int($key) ? (string) $key : $key); + $isStaticValue = true; + + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue, $preload); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!\is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value))); + } else { + $value = var_export($value, true); + } + + if (!$isStaticValue) { + $value = str_replace("\n", "\n ", $value); + $value = "static function () {\n return {$value};\n}"; + } + $hash = hash('xxh128', $value); + + if (null === $id = $dumpedMap[$hash] ?? null) { + $id = $dumpedMap[$hash] = \count($dumpedMap); + $dumpedValues .= "{$id} => {$value},\n"; + } + + $dump .= var_export($key, true)." => {$id},\n"; + } + + $dump .= "\n], [\n\n{$dumpedValues}\n]];\n"; + + $tmpFile = uniqid($this->file, true); + + file_put_contents($tmpFile, $dump); + @chmod($tmpFile, 0666 & ~umask()); + unset($serialized, $value, $dump); + + @rename($tmpFile, $this->file); + unset(self::$valuesCache[$this->file]); + + $this->initialize(); + + return $preload; + } + + /** + * Load the cache file. + */ + private function initialize(): void + { + if (isset(self::$valuesCache[$this->file])) { + $values = self::$valuesCache[$this->file]; + } elseif (!is_file($this->file)) { + $this->keys = $this->values = []; + + return; + } else { + $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []]; + } + + if (2 !== \count($values) || !isset($values[0], $values[1])) { + $this->keys = $this->values = []; + } else { + [$this->keys, $this->values] = $values; + } + } + + private function generateItems(array $keys): \Generator + { + $f = self::$createCacheItem; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (isset($this->keys[$key])) { + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + yield $key => $f($key, null, true); + } elseif ($value instanceof \Closure) { + try { + yield $key => $f($key, $value(), true); + } catch (\Throwable) { + yield $key => $f($key, null, false); + } + } else { + yield $key => $f($key, $value, true); + } + } else { + $fallbackKeys[] = $key; + } + } + + if ($fallbackKeys) { + yield from $this->pool->getItems($fallbackKeys); + } + } +} diff --git a/vendor/symfony/cache/Adapter/PhpFilesAdapter.php b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php new file mode 100644 index 0000000..917ff16 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php @@ -0,0 +1,314 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemCommonTrait; +use Symfony\Component\VarExporter\VarExporter; + +/** + * @author Piotr Stankowski + * @author Nicolas Grekas + * @author Rob Frawley 2nd + */ +class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface +{ + use FilesystemCommonTrait { + doClear as private doCommonClear; + doDelete as private doCommonDelete; + } + + private \Closure $includeHandler; + private array $values = []; + private array $files = []; + + private static int $startTime; + private static array $valuesCache = []; + + /** + * @param bool $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire. + * Doing so is encouraged because it fits perfectly OPcache's memory model. + * + * @throws CacheException if OPcache is not enabled + */ + public function __construct( + string $namespace = '', + int $defaultLifetime = 0, + ?string $directory = null, + private bool $appendOnly = false, + ) { + self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + $this->includeHandler = static function ($type, $msg, $file, $line) { + throw new \ErrorException($msg, 0, $type, $file, $line); + }; + } + + public static function isSupported(): bool + { + self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); + + return \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL)); + } + + public function prune(): bool + { + $time = time(); + $pruned = true; + $getExpiry = true; + + set_error_handler($this->includeHandler); + try { + foreach ($this->scanHashDir($this->directory) as $file) { + try { + if (\is_array($expiresAt = include $file)) { + $expiresAt = $expiresAt[0]; + } + } catch (\ErrorException $e) { + $expiresAt = $time; + } + + if ($time >= $expiresAt) { + $pruned = ($this->doUnlink($file) || !file_exists($file)) && $pruned; + } + } + } finally { + restore_error_handler(); + } + + return $pruned; + } + + protected function doFetch(array $ids): iterable + { + if ($this->appendOnly) { + $now = 0; + $missingIds = []; + } else { + $now = time(); + $missingIds = $ids; + $ids = []; + } + $values = []; + + begin: + $getExpiry = false; + + foreach ($ids as $id) { + if (null === $value = $this->values[$id] ?? null) { + $missingIds[] = $id; + } elseif ('N;' === $value) { + $values[$id] = null; + } elseif (!\is_object($value)) { + $values[$id] = $value; + } elseif (!$value instanceof LazyValue) { + $values[$id] = $value(); + } elseif (false === $values[$id] = include $value->file) { + unset($values[$id], $this->values[$id]); + $missingIds[] = $id; + } + if (!$this->appendOnly) { + unset($this->values[$id]); + } + } + + if (!$missingIds) { + return $values; + } + + set_error_handler($this->includeHandler); + try { + $getExpiry = true; + + foreach ($missingIds as $k => $id) { + try { + $file = $this->files[$id] ??= $this->getFile($id); + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $this->values[$id]] = $expiresAt; + } elseif ($now < $expiresAt) { + $this->values[$id] = new LazyValue($file); + } + + if ($now >= $expiresAt) { + unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); + } + } catch (\ErrorException $e) { + unset($missingIds[$k]); + } + } + } finally { + restore_error_handler(); + } + + $ids = $missingIds; + $missingIds = []; + goto begin; + } + + protected function doHave(string $id): bool + { + if ($this->appendOnly && isset($this->values[$id])) { + return true; + } + + set_error_handler($this->includeHandler); + try { + $file = $this->files[$id] ??= $this->getFile($id); + $getExpiry = true; + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $value] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $value] = $expiresAt; + } elseif ($this->appendOnly) { + $value = new LazyValue($file); + } + } catch (\ErrorException) { + return false; + } finally { + restore_error_handler(); + } + if ($this->appendOnly) { + $now = 0; + $this->values[$id] = $value; + } else { + $now = time(); + } + + return $now < $expiresAt; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + $ok = true; + $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX'; + $allowCompile = self::isSupported(); + + foreach ($values as $key => $value) { + unset($this->values[$key]); + $isStaticValue = true; + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!\is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value))); + } else { + $value = var_export($value, true); + } + + $encodedKey = rawurlencode($key); + + if ($isStaticValue) { + $value = "return [{$expiry}, {$value}];"; + } elseif ($this->appendOnly) { + $value = "return [{$expiry}, static fn () => {$value}];"; + } else { + // We cannot use a closure here because of https://bugs.php.net/76982 + $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value); + $value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};"; + } + + $file = $this->files[$key] = $this->getFile($key, true); + // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past + $ok = $this->write($file, "directory)) { + throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $ok; + } + + protected function doClear(string $namespace): bool + { + $this->values = []; + + return $this->doCommonClear($namespace); + } + + protected function doDelete(array $ids): bool + { + foreach ($ids as $id) { + unset($this->values[$id]); + } + + return $this->doCommonDelete($ids); + } + + protected function doUnlink(string $file): bool + { + unset(self::$valuesCache[$file]); + + if (self::isSupported()) { + @opcache_invalidate($file, true); + } + + return @unlink($file); + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'r')) { + return ''; + } + + $encodedKey = substr(fgets($h), 8); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} + +/** + * @internal + */ +class LazyValue +{ + public string $file; + + public function __construct(string $file) + { + $this->file = $file; + } +} diff --git a/vendor/symfony/cache/Adapter/ProxyAdapter.php b/vendor/symfony/cache/Adapter/ProxyAdapter.php new file mode 100644 index 0000000..c022dd5 --- /dev/null +++ b/vendor/symfony/cache/Adapter/ProxyAdapter.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + use ProxyTrait; + + private string $namespace = ''; + private int $namespaceLen; + private string $poolHash; + private int $defaultLifetime; + + private static \Closure $createCacheItem; + private static \Closure $setInnerItem; + + public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + $this->pool = $pool; + $this->poolHash = spl_object_hash($pool); + if ('' !== $namespace) { + \assert('' !== CacheItem::validateKey($namespace)); + $this->namespace = $namespace; + } + $this->namespaceLen = \strlen($namespace); + $this->defaultLifetime = $defaultLifetime; + self::$createCacheItem ??= \Closure::bind( + static function ($key, $innerItem, $poolHash) { + $item = new CacheItem(); + $item->key = $key; + + if (null === $innerItem) { + return $item; + } + + $item->value = $innerItem->get(); + $item->isHit = $innerItem->isHit(); + $item->innerItem = $innerItem; + $item->poolHash = $poolHash; + + if (!$item->unpack() && $innerItem instanceof CacheItem) { + $item->metadata = $innerItem->metadata; + } + $innerItem->set(null); + + return $item; + }, + null, + CacheItem::class + ); + self::$setInnerItem ??= \Closure::bind( + static function (CacheItemInterface $innerItem, CacheItem $item, $expiry = null) { + $innerItem->set($item->pack()); + $innerItem->expiresAt(($expiry ?? $item->expiry) ? \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $expiry ?? $item->expiry)) : null); + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + if (!$this->pool instanceof CacheInterface) { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) { + $item = (self::$createCacheItem)($key, $innerItem, $this->poolHash); + $item->set($value = $callback($item, $save)); + (self::$setInnerItem)($innerItem, $item); + + return $value; + }, $beta, $metadata); + } + + public function getItem(mixed $key): CacheItem + { + $item = $this->pool->getItem($this->getId($key)); + + return (self::$createCacheItem)($key, $item, $this->poolHash); + } + + public function getItems(array $keys = []): iterable + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->generateItems($this->pool->getItems($keys)); + } + + public function hasItem(mixed $key): bool + { + return $this->pool->hasItem($this->getId($key)); + } + + public function clear(string $prefix = ''): bool + { + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($this->namespace.$prefix); + } + + return $this->pool->clear(); + } + + public function deleteItem(mixed $key): bool + { + return $this->pool->deleteItem($this->getId($key)); + } + + public function deleteItems(array $keys): bool + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->pool->deleteItems($keys); + } + + public function save(CacheItemInterface $item): bool + { + return $this->doSave($item, __FUNCTION__); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + return $this->doSave($item, __FUNCTION__); + } + + public function commit(): bool + { + return $this->pool->commit(); + } + + private function doSave(CacheItemInterface $item, string $method): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $castItem = (array) $item; + + if (null === $castItem["\0*\0expiry"] && 0 < $this->defaultLifetime) { + $castItem["\0*\0expiry"] = microtime(true) + $this->defaultLifetime; + } + + if ($castItem["\0*\0poolHash"] === $this->poolHash && $castItem["\0*\0innerItem"]) { + $innerItem = $castItem["\0*\0innerItem"]; + } elseif ($this->pool instanceof AdapterInterface) { + // this is an optimization specific for AdapterInterface implementations + // so we can save a round-trip to the backend by just creating a new item + $innerItem = (self::$createCacheItem)($this->namespace.$castItem["\0*\0key"], null, $this->poolHash); + } else { + $innerItem = $this->pool->getItem($this->namespace.$castItem["\0*\0key"]); + } + + (self::$setInnerItem)($innerItem, $item, $castItem["\0*\0expiry"]); + + return $this->pool->$method($innerItem); + } + + private function generateItems(iterable $items): \Generator + { + $f = self::$createCacheItem; + + foreach ($items as $key => $item) { + if ($this->namespaceLen) { + $key = substr($key, $this->namespaceLen); + } + + yield $key => $f($key, $item, $this->poolHash); + } + } + + private function getId(mixed $key): string + { + \assert('' !== CacheItem::validateKey($key)); + + return $this->namespace.$key; + } +} diff --git a/vendor/symfony/cache/Adapter/Psr16Adapter.php b/vendor/symfony/cache/Adapter/Psr16Adapter.php new file mode 100644 index 0000000..a72b037 --- /dev/null +++ b/vendor/symfony/cache/Adapter/Psr16Adapter.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-16 cache into a PSR-6 one. + * + * @author Nicolas Grekas + */ +class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface +{ + use ProxyTrait; + + /** + * @internal + */ + protected const NS_SEPARATOR = '_'; + + private object $miss; + + public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + parent::__construct($namespace, $defaultLifetime); + + $this->pool = $pool; + $this->miss = new \stdClass(); + } + + protected function doFetch(array $ids): iterable + { + foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) { + if ($this->miss !== $value) { + yield $key => $value; + } + } + } + + protected function doHave(string $id): bool + { + return $this->pool->has($id); + } + + protected function doClear(string $namespace): bool + { + return $this->pool->clear(); + } + + protected function doDelete(array $ids): bool + { + return $this->pool->deleteMultiple($ids); + } + + protected function doSave(array $values, int $lifetime): array|bool + { + return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); + } +} diff --git a/vendor/symfony/cache/Adapter/RedisAdapter.php b/vendor/symfony/cache/Adapter/RedisAdapter.php new file mode 100644 index 0000000..e33f2f6 --- /dev/null +++ b/vendor/symfony/cache/Adapter/RedisAdapter.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Traits\RedisTrait; + +class RedisAdapter extends AbstractAdapter +{ + use RedisTrait; + + public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) + { + $this->init($redis, $namespace, $defaultLifetime, $marshaller); + } +} diff --git a/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php new file mode 100644 index 0000000..f71622b --- /dev/null +++ b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\PredisCluster; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Response\ErrorInterface; +use Predis\Response\Status; +use Relay\Relay; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; +use Symfony\Component\Cache\Marshaller\DeflateMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\Traits\RedisTrait; + +/** + * Stores tag id <> cache id relationship as a Redis Set. + * + * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even + * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache + * relationship survives eviction (cache cleanup when Redis runs out of memory). + * + * Redis server 2.8+ with any `volatile-*` eviction policy, OR `noeviction` if you're sure memory will NEVER fill up + * + * Design limitations: + * - Max 4 billion cache keys per cache tag as limited by Redis Set datatype. + * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also. + * + * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies. + * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype. + * + * @author Nicolas Grekas + * @author André Rømcke + */ +class RedisTagAwareAdapter extends AbstractTagAwareAdapter +{ + use RedisTrait; + + /** + * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are + * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements. + */ + private const DEFAULT_CACHE_TTL = 8640000; + + /** + * detected eviction policy used on Redis server. + */ + private string $redisEvictionPolicy; + + public function __construct( + \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private string $namespace = '', + int $defaultLifetime = 0, + ?MarshallerInterface $marshaller = null, + ) { + if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { + throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); + } + + $isRelay = $redis instanceof Relay; + if ($isRelay || \defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { + $compression = $redis->getOption($isRelay ? Relay::OPT_COMPRESSION : \Redis::OPT_COMPRESSION); + + foreach (\is_array($compression) ? $compression : [$compression] as $c) { + if ($isRelay ? Relay::COMPRESSION_NONE : \Redis::COMPRESSION_NONE !== $c) { + throw new InvalidArgumentException(sprintf('redis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); + } + } + } + + $this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller)); + } + + protected function doSave(array $values, int $lifetime, array $addTagData = [], array $delTagData = []): array + { + $eviction = $this->getRedisEvictionPolicy(); + if ('noeviction' !== $eviction && !str_starts_with($eviction, 'volatile-')) { + throw new LogicException(sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction)); + } + + // serialize values + if (!$serialized = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op + $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) { + // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one + foreach ($serialized as $id => $value) { + yield 'setEx' => [ + $id, + 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime, + $value, + ]; + } + + // Add and Remove Tags + foreach ($addTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sAdd' => array_merge([$tagId], $ids); + } + } + + foreach ($delTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sRem' => array_merge([$tagId], $ids); + } + } + }); + + foreach ($results as $id => $result) { + // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not + if (is_numeric($result)) { + continue; + } + // setEx results + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + protected function doDeleteYieldTags(array $ids): iterable + { + $lua = <<<'EOLUA' + local v = redis.call('GET', KEYS[1]) + local e = redis.pcall('UNLINK', KEYS[1]) + + if type(e) ~= 'number' then + redis.call('DEL', KEYS[1]) + end + + if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then + return '' + end + + return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536) +EOLUA; + + $results = $this->pipeline(function () use ($ids, $lua) { + foreach ($ids as $id) { + yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id] : [$lua, [$id], 1]; + } + }); + + foreach ($results as $id => $result) { + if ($result instanceof \RedisException || $result instanceof \Relay\Exception || $result instanceof ErrorInterface) { + CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]); + + continue; + } + + try { + yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result); + } catch (\Exception) { + yield $id => []; + } + } + } + + protected function doDeleteTagRelations(array $tagData): bool + { + $results = $this->pipeline(static function () use ($tagData) { + foreach ($tagData as $tagId => $idList) { + array_unshift($idList, $tagId); + yield 'sRem' => $idList; + } + }); + foreach ($results as $result) { + // no-op + } + + return true; + } + + protected function doInvalidate(array $tagIds): bool + { + // This script scans the set of items linked to tag: it empties the set + // and removes the linked items. When the set is still not empty after + // the scan, it means we're in cluster mode and that the linked items + // are on other nodes: we move the links to a temporary set and we + // garbage collect that set from the client side. + + $lua = <<<'EOLUA' + redis.replicate_commands() + + local cursor = '0' + local id = KEYS[1] + repeat + local result = redis.call('SSCAN', id, cursor, 'COUNT', 5000); + cursor = result[1]; + local rems = {} + + for _, v in ipairs(result[2]) do + local ok, _ = pcall(redis.call, 'DEL', ARGV[1]..v) + if ok then + table.insert(rems, v) + end + end + if 0 < #rems then + redis.call('SREM', id, unpack(rems)) + end + until '0' == cursor; + + redis.call('SUNIONSTORE', '{'..id..'}'..id, id) + redis.call('DEL', id) + + return redis.call('SSCAN', '{'..id..'}'..id, '0', 'COUNT', 5000) +EOLUA; + + $results = $this->pipeline(function () use ($tagIds, $lua) { + if ($this->redis instanceof \Predis\ClientInterface) { + $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; + } elseif (\is_array($prefix = $this->redis->getOption($this->redis instanceof Relay ? Relay::OPT_PREFIX : \Redis::OPT_PREFIX) ?? '')) { + $prefix = current($prefix); + } + + foreach ($tagIds as $id) { + yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id, $prefix] : [$lua, [$id, $prefix], 1]; + } + }); + + $lua = <<<'EOLUA' + redis.replicate_commands() + + local id = KEYS[1] + local cursor = table.remove(ARGV) + redis.call('SREM', '{'..id..'}'..id, unpack(ARGV)) + + return redis.call('SSCAN', '{'..id..'}'..id, cursor, 'COUNT', 5000) +EOLUA; + + $success = true; + foreach ($results as $id => $values) { + if ($values instanceof \RedisException || $values instanceof \Relay\Exception || $values instanceof ErrorInterface) { + CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]); + $success = false; + + continue; + } + + [$cursor, $ids] = $values; + + while ($ids || '0' !== $cursor) { + $this->doDelete($ids); + + $evalArgs = [$id, $cursor]; + array_splice($evalArgs, 1, 0, $ids); + + if ($this->redis instanceof \Predis\ClientInterface) { + array_unshift($evalArgs, $lua, 1); + } else { + $evalArgs = [$lua, $evalArgs, 1]; + } + + $results = $this->pipeline(function () use ($evalArgs) { + yield 'eval' => $evalArgs; + }); + + foreach ($results as [$cursor, $ids]) { + // no-op + } + } + } + + return $success; + } + + private function getRedisEvictionPolicy(): string + { + if (isset($this->redisEvictionPolicy)) { + return $this->redisEvictionPolicy; + } + + $hosts = $this->getHosts(); + $host = reset($hosts); + if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) { + // Predis supports info command only on the master in replication environments + $hosts = [$host->getClientFor('master')]; + } + + foreach ($hosts as $host) { + $info = $host->info('Memory'); + + if (false === $info || null === $info || $info instanceof ErrorInterface) { + continue; + } + + $info = $info['Memory'] ?? $info; + + return $this->redisEvictionPolicy = $info['maxmemory_policy'] ?? ''; + } + + return $this->redisEvictionPolicy = ''; + } +} diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapter.php b/vendor/symfony/cache/Adapter/TagAwareAdapter.php new file mode 100644 index 0000000..34082db --- /dev/null +++ b/vendor/symfony/cache/Adapter/TagAwareAdapter.php @@ -0,0 +1,370 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * Implements simple and robust tag-based invalidation suitable for use with volatile caches. + * + * This adapter works by storing a version for each tags. When saving an item, it is stored together with its tags and + * their corresponding versions. When retrieving an item, those tag versions are compared to the current version of + * each tags. Invalidation is achieved by deleting tags, thereby ensuring that their versions change even when the + * storage is out of space. When versions of non-existing tags are requested for item commits, this adapter assigns a + * new random version to them. + * + * @author Nicolas Grekas + * @author Sergey Belyshkin + */ +class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface +{ + use ContractsTrait; + use LoggerAwareTrait; + + public const TAGS_PREFIX = "\1tags\1"; + + private array $deferred = []; + private AdapterInterface $pool; + private AdapterInterface $tags; + private array $knownTagVersions = []; + + private static \Closure $setCacheItemTags; + private static \Closure $setTagVersions; + private static \Closure $getTagsByKey; + private static \Closure $saveTags; + + public function __construct( + AdapterInterface $itemsPool, + ?AdapterInterface $tagsPool = null, + private float $knownTagVersionsTtl = 0.15, + ) { + $this->pool = $itemsPool; + $this->tags = $tagsPool ?? $itemsPool; + self::$setCacheItemTags ??= \Closure::bind( + static function (array $items, array $itemTags) { + foreach ($items as $key => $item) { + $item->isTaggable = true; + + if (isset($itemTags[$key])) { + $tags = array_keys($itemTags[$key]); + $item->metadata[CacheItem::METADATA_TAGS] = array_combine($tags, $tags); + } else { + $item->value = null; + $item->isHit = false; + $item->metadata = []; + } + } + + return $items; + }, + null, + CacheItem::class + ); + self::$setTagVersions ??= \Closure::bind( + static function (array $items, array $tagVersions) { + foreach ($items as $item) { + $item->newMetadata[CacheItem::METADATA_TAGS] = array_intersect_key($tagVersions, $item->newMetadata[CacheItem::METADATA_TAGS] ?? []); + } + }, + null, + CacheItem::class + ); + self::$getTagsByKey ??= \Closure::bind( + static function ($deferred) { + $tagsByKey = []; + foreach ($deferred as $key => $item) { + $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? []; + $item->metadata = $item->newMetadata; + } + + return $tagsByKey; + }, + null, + CacheItem::class + ); + self::$saveTags ??= \Closure::bind( + static function (AdapterInterface $tagsAdapter, array $tags) { + ksort($tags); + + foreach ($tags as $v) { + $v->expiry = 0; + $tagsAdapter->saveDeferred($v); + } + + return $tagsAdapter->commit(); + }, + null, + CacheItem::class + ); + } + + public function invalidateTags(array $tags): bool + { + $ids = []; + foreach ($tags as $tag) { + \assert('' !== CacheItem::validateKey($tag)); + unset($this->knownTagVersions[$tag]); + $ids[] = $tag.static::TAGS_PREFIX; + } + + return !$tags || $this->tags->deleteItems($ids); + } + + public function hasItem(mixed $key): bool + { + return $this->getItem($key)->isHit(); + } + + public function getItem(mixed $key): CacheItem + { + foreach ($this->getItems([$key]) as $item) { + return $item; + } + } + + public function getItems(array $keys = []): iterable + { + $tagKeys = []; + $commit = false; + + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $commit = $commit || isset($this->deferred[$key]); + } + } + + if ($commit) { + $this->commit(); + } + + try { + $items = $this->pool->getItems($keys); + } catch (InvalidArgumentException $e) { + $this->pool->getItems($keys); // Should throw an exception + + throw $e; + } + + $bufferedItems = $itemTags = []; + + foreach ($items as $key => $item) { + if (null !== $tags = $item->getMetadata()[CacheItem::METADATA_TAGS] ?? null) { + $itemTags[$key] = $tags; + } + + $bufferedItems[$key] = $item; + + if (null === $tags) { + $key = "\0tags\0".$key; + $tagKeys[$key] = $key; // BC with pools populated before v6.1 + } + } + + if ($tagKeys) { + foreach ($this->pool->getItems($tagKeys) as $key => $item) { + if ($item->isHit()) { + $itemTags[substr($key, \strlen("\0tags\0"))] = $item->get() ?: []; + } + } + } + + $tagVersions = $this->getTagVersions($itemTags, false); + foreach ($itemTags as $key => $tags) { + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version) { + unset($itemTags[$key]); + continue 2; + } + } + } + $tagVersions = null; + + return (self::$setCacheItemTags)($bufferedItems, $itemTags); + } + + public function clear(string $prefix = ''): bool + { + if ('' !== $prefix) { + foreach ($this->deferred as $key => $item) { + if (str_starts_with($key, $prefix)) { + unset($this->deferred[$key]); + } + } + } else { + $this->deferred = []; + } + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix); + } + + return $this->pool->clear(); + } + + public function deleteItem(mixed $key): bool + { + return $this->deleteItems([$key]); + } + + public function deleteItems(array $keys): bool + { + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $keys[] = "\0tags\0".$key; // BC with pools populated before v6.1 + } + } + + return $this->pool->deleteItems($keys); + } + + public function save(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + public function commit(): bool + { + if (!$items = $this->deferred) { + return true; + } + + $tagVersions = $this->getTagVersions((self::$getTagsByKey)($items), true); + (self::$setTagVersions)($items, $tagVersions); + + $ok = true; + foreach ($items as $key => $item) { + if ($this->pool->saveDeferred($item)) { + unset($this->deferred[$key]); + } else { + $ok = false; + } + } + $ok = $this->pool->commit() && $ok; + + $tagVersions = array_keys($tagVersions); + (self::$setTagVersions)($items, array_combine($tagVersions, $tagVersions)); + + return $ok; + } + + public function prune(): bool + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + public function reset(): void + { + $this->commit(); + $this->knownTagVersions = []; + $this->pool instanceof ResettableInterface && $this->pool->reset(); + $this->tags instanceof ResettableInterface && $this->tags->reset(); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->commit(); + } + + private function getTagVersions(array $tagsByKey, bool $persistTags): array + { + $tagVersions = []; + $fetchTagVersions = $persistTags; + + foreach ($tagsByKey as $tags) { + $tagVersions += $tags; + if ($fetchTagVersions) { + continue; + } + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version) { + $fetchTagVersions = true; + } + } + } + + if (!$tagVersions) { + return []; + } + + $now = microtime(true); + $tags = []; + foreach ($tagVersions as $tag => $version) { + $tags[$tag.static::TAGS_PREFIX] = $tag; + $knownTagVersion = $this->knownTagVersions[$tag] ?? [0, null]; + if ($fetchTagVersions || $now > $knownTagVersion[0] || $knownTagVersion[1] !== $version) { + // reuse previously fetched tag versions until the expiration + $fetchTagVersions = true; + } + } + + if (!$fetchTagVersions) { + return $tagVersions; + } + + $newTags = []; + $newVersion = null; + $expiration = $now + $this->knownTagVersionsTtl; + foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) { + unset($this->knownTagVersions[$tag = $tags[$tag]]); // update FIFO + if (null !== $tagVersions[$tag] = $version->get()) { + $this->knownTagVersions[$tag] = [$expiration, $tagVersions[$tag]]; + } elseif ($persistTags) { + $newTags[$tag] = $version->set($newVersion ??= random_bytes(6)); + $tagVersions[$tag] = $newVersion; + $this->knownTagVersions[$tag] = [$expiration, $newVersion]; + } + } + + if ($newTags) { + (self::$saveTags)($this->tags, $newTags); + } + + while ($now > ($this->knownTagVersions[$tag = array_key_first($this->knownTagVersions)][0] ?? \INF)) { + unset($this->knownTagVersions[$tag]); + } + + return $tagVersions; + } +} diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php new file mode 100644 index 0000000..9242779 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\InvalidArgumentException; + +/** + * Interface for invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareAdapterInterface extends AdapterInterface +{ + /** + * Invalidates cached items using tags. + * + * @param string[] $tags An array of tags to invalidate + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags): bool; +} diff --git a/vendor/symfony/cache/Adapter/TraceableAdapter.php b/vendor/symfony/cache/Adapter/TraceableAdapter.php new file mode 100644 index 0000000..b5bce14 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TraceableAdapter.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An adapter that collects data about all cache calls. + * + * @author Aaron Scherer + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + protected AdapterInterface $pool; + private array $calls = []; + + public function __construct(AdapterInterface $pool) + { + $this->pool = $pool; + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + if (!$this->pool instanceof CacheInterface) { + throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); + } + + $isHit = true; + $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) { + $isHit = $item->isHit(); + + return $callback($item, $save); + }; + + $event = $this->start(__FUNCTION__); + try { + $value = $this->pool->get($key, $callback, $beta, $metadata); + $event->result[$key] = get_debug_type($value); + } finally { + $event->end = microtime(true); + } + if ($isHit) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $value; + } + + public function getItem(mixed $key): CacheItem + { + $event = $this->start(__FUNCTION__); + try { + $item = $this->pool->getItem($key); + } finally { + $event->end = microtime(true); + } + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $item; + } + + public function hasItem(mixed $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->hasItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function deleteItem(mixed $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function save(CacheItemInterface $item): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->save($item); + } finally { + $event->end = microtime(true); + } + } + + public function saveDeferred(CacheItemInterface $item): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->saveDeferred($item); + } finally { + $event->end = microtime(true); + } + } + + public function getItems(array $keys = []): iterable + { + $event = $this->start(__FUNCTION__); + try { + $result = $this->pool->getItems($keys); + } finally { + $event->end = microtime(true); + } + $f = function () use ($result, $event) { + $event->result = []; + foreach ($result as $key => $item) { + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + yield $key => $item; + } + }; + + return $f(); + } + + public function clear(string $prefix = ''): bool + { + $event = $this->start(__FUNCTION__); + try { + if ($this->pool instanceof AdapterInterface) { + return $event->result = $this->pool->clear($prefix); + } + + return $event->result = $this->pool->clear(); + } finally { + $event->end = microtime(true); + } + } + + public function deleteItems(array $keys): bool + { + $event = $this->start(__FUNCTION__); + $event->result['keys'] = $keys; + try { + return $event->result['result'] = $this->pool->deleteItems($keys); + } finally { + $event->end = microtime(true); + } + } + + public function commit(): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->commit(); + } finally { + $event->end = microtime(true); + } + } + + public function prune(): bool + { + if (!$this->pool instanceof PruneableInterface) { + return false; + } + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->prune(); + } finally { + $event->end = microtime(true); + } + } + + public function reset(): void + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + + $this->clearCalls(); + } + + public function delete(string $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function getCalls(): array + { + return $this->calls; + } + + public function clearCalls(): void + { + $this->calls = []; + } + + public function getPool(): AdapterInterface + { + return $this->pool; + } + + protected function start(string $name): TraceableAdapterEvent + { + $this->calls[] = $event = new TraceableAdapterEvent(); + $event->name = $name; + $event->start = microtime(true); + + return $event; + } +} + +/** + * @internal + */ +class TraceableAdapterEvent +{ + public string $name; + public float $start; + public float $end; + public array|bool $result; + public int $hits = 0; + public int $misses = 0; +} diff --git a/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php new file mode 100644 index 0000000..c85d199 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * @author Robin Chalas + */ +class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface +{ + public function __construct(TagAwareAdapterInterface $pool) + { + parent::__construct($pool); + } + + public function invalidateTags(array $tags): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->invalidateTags($tags); + } finally { + $event->end = microtime(true); + } + } +} diff --git a/vendor/symfony/cache/CHANGELOG.md b/vendor/symfony/cache/CHANGELOG.md new file mode 100644 index 0000000..cab9bf6 --- /dev/null +++ b/vendor/symfony/cache/CHANGELOG.md @@ -0,0 +1,147 @@ +CHANGELOG +========= + +7.1 +--- + + * Add option `sentinel_master` as an alias for `redis_sentinel` + * Deprecate `CouchbaseBucketAdapter`, use `CouchbaseCollectionAdapter` + * Add support for URL encoded characters in Couchbase DSN + * Add support for using DSN with PDOAdapter + * The algorithm for the default cache namespace changed from SHA256 to XXH128 + +7.0 +--- + + * Add parameter `$isSameDatabase` to `DoctrineDbalAdapter::configureSchema()` + * Drop support for Postgres < 9.5 and SQL Server < 2008 in `DoctrineDbalAdapter` + +6.4 +--- + + * `EarlyExpirationHandler` no longer implements `MessageHandlerInterface`, rely on `AsMessageHandler` instead + +6.3 +--- + + * Add support for Relay PHP extension for Redis + * Updates to allow Redis cluster connections using predis/predis:^2.0 + * Add optional parameter `$isSameDatabase` to `DoctrineDbalAdapter::configureSchema()` + +6.1 +--- + + * Add support for ACL auth in RedisAdapter + * Improve reliability and performance of `TagAwareAdapter` by making tag versions an integral part of item value + +6.0 +--- + + * Remove `DoctrineProvider` and `DoctrineAdapter` + * Remove support of Doctrine DBAL in `PdoAdapter` + +5.4 +--- + + * Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package + * Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL + * Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL + +5.3 +--- + + * added support for connecting to Redis Sentinel clusters when using the Redis PHP extension + * add support for a custom serializer to the `ApcuAdapter` class + +5.2.0 +----- + + * added integration with Messenger to allow computing cached values in a worker + * allow ISO 8601 time intervals to specify default lifetime + +5.1.0 +----- + + * added max-items + LRU + max-lifetime capabilities to `ArrayCache` + * added `CouchbaseBucketAdapter` + * added context `cache-adapter` to log messages + +5.0.0 +----- + + * removed all PSR-16 implementations in the `Simple` namespace + * removed `SimpleCacheAdapter` + * removed `AbstractAdapter::unserialize()` + * removed `CacheItem::getPreviousTags()` + +4.4.0 +----- + + * added support for connecting to Redis Sentinel clusters + * added argument `$prefix` to `AdapterInterface::clear()` + * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag + * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter` + * added `DeflateMarshaller` to compress serialized values + * removed support for phpredis 4 `compression` + * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead + * Marked the `CacheDataCollector` class as `@final`. + * added `SodiumMarshaller` to encrypt/decrypt values using libsodium + +4.3.0 +----- + + * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it + * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead + * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead + +4.2.0 +----- + + * added support for connecting to Redis clusters via DSN + * added support for configuring multiple Memcached servers via DSN + * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available + * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache + * added sub-second expiry accuracy for backends that support it + * added support for phpredis 4 `compression` and `tcp_keepalive` options + * added automatic table creation when using Doctrine DBAL with PDO-based backends + * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool + * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead + * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods + * added `CacheCollectorPass` (originally in `FrameworkBundle`) + * added `CachePoolClearerPass` (originally in `FrameworkBundle`) + * added `CachePoolPass` (originally in `FrameworkBundle`) + * added `CachePoolPrunerPass` (originally in `FrameworkBundle`) + +3.4.0 +----- + + * added using options from Memcached DSN + * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning + * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait + * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and + ChainCache implement PruneableInterface and support manual stale cache pruning + +3.3.0 +----- + + * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any + * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters + * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16 + * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16) + * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16) + +3.2.0 +----- + + * added TagAwareAdapter for tags-based invalidation + * added PdoAdapter with PDO and Doctrine DBAL support + * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only) + * added NullAdapter + +3.1.0 +----- + + * added the component with strict PSR-6 implementations + * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter + * added AbstractAdapter, ChainAdapter and ProxyAdapter + * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache diff --git a/vendor/symfony/cache/CacheItem.php b/vendor/symfony/cache/CacheItem.php new file mode 100644 index 0000000..1a81706 --- /dev/null +++ b/vendor/symfony/cache/CacheItem.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + */ +final class CacheItem implements ItemInterface +{ + private const METADATA_EXPIRY_OFFSET = 1527506807; + private const VALUE_WRAPPER = "\xA9"; + + protected string $key; + protected mixed $value = null; + protected bool $isHit = false; + protected float|int|null $expiry = null; + protected array $metadata = []; + protected array $newMetadata = []; + protected ?ItemInterface $innerItem = null; + protected ?string $poolHash = null; + protected bool $isTaggable = false; + + public function getKey(): string + { + return $this->key; + } + + public function get(): mixed + { + return $this->value; + } + + public function isHit(): bool + { + return $this->isHit; + } + + /** + * @return $this + */ + public function set($value): static + { + $this->value = $value; + + return $this; + } + + /** + * @return $this + */ + public function expiresAt(?\DateTimeInterface $expiration): static + { + $this->expiry = null !== $expiration ? (float) $expiration->format('U.u') : null; + + return $this; + } + + /** + * @return $this + */ + public function expiresAfter(mixed $time): static + { + if (null === $time) { + $this->expiry = null; + } elseif ($time instanceof \DateInterval) { + $this->expiry = microtime(true) + \DateTimeImmutable::createFromFormat('U', 0)->add($time)->format('U.u'); + } elseif (\is_int($time)) { + $this->expiry = $time + microtime(true); + } else { + throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', get_debug_type($time))); + } + + return $this; + } + + public function tag(mixed $tags): static + { + if (!$this->isTaggable) { + throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); + } + if (!\is_array($tags) && !$tags instanceof \Traversable) { // don't use is_iterable(), it's slow + $tags = [$tags]; + } + foreach ($tags as $tag) { + if (!\is_string($tag) && !$tag instanceof \Stringable) { + throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', get_debug_type($tag))); + } + $tag = (string) $tag; + if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { + continue; + } + if ('' === $tag) { + throw new InvalidArgumentException('Cache tag length must be greater than zero.'); + } + if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS)); + } + $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; + } + + return $this; + } + + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * Validates a cache key according to PSR-6. + * + * @param mixed $key The key to validate + * + * @throws InvalidArgumentException When $key is not valid + */ + public static function validateKey($key): string + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if ('' === $key) { + throw new InvalidArgumentException('Cache key length must be greater than zero.'); + } + if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); + } + + return $key; + } + + /** + * Internal logging helper. + * + * @internal + */ + public static function log(?LoggerInterface $logger, string $message, array $context = []): void + { + if ($logger) { + $logger->warning($message, $context); + } else { + $replace = []; + foreach ($context as $k => $v) { + if (\is_scalar($v)) { + $replace['{'.$k.'}'] = $v; + } + } + @trigger_error(strtr($message, $replace), \E_USER_WARNING); + } + } + + private function pack(): mixed + { + if (!$m = $this->newMetadata) { + return $this->value; + } + $valueWrapper = self::VALUE_WRAPPER; + + return new $valueWrapper($this->value, $m + ['expiry' => $this->expiry]); + } + + private function unpack(): bool + { + $v = $this->value; + $valueWrapper = self::VALUE_WRAPPER; + + if ($v instanceof $valueWrapper) { + $this->value = $v->value; + $this->metadata = $v->metadata; + + return true; + } + + if (!\is_array($v) || 1 !== \count($v) || 10 !== \strlen($k = (string) array_key_first($v)) || "\x9D" !== $k[0] || "\0" !== $k[5] || "\x5F" !== $k[9]) { + return false; + } + + // BC with pools populated before v6.1 + $this->value = $v[$k]; + $this->metadata = unpack('Vexpiry/Nctime', substr($k, 1, -1)); + $this->metadata['expiry'] += self::METADATA_EXPIRY_OFFSET; + + return true; + } +} diff --git a/vendor/symfony/cache/DataCollector/CacheDataCollector.php b/vendor/symfony/cache/DataCollector/CacheDataCollector.php new file mode 100644 index 0000000..b9bcdaf --- /dev/null +++ b/vendor/symfony/cache/DataCollector/CacheDataCollector.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DataCollector; + +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableAdapterEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + * + * @final + */ +class CacheDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var TraceableAdapter[] + */ + private array $instances = []; + + public function addInstance(string $name, TraceableAdapter $instance): void + { + $this->instances[$name] = $instance; + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $empty = ['calls' => [], 'adapters' => [], 'config' => [], 'options' => [], 'statistics' => []]; + $this->data = ['instances' => $empty, 'total' => $empty]; + foreach ($this->instances as $name => $instance) { + $this->data['instances']['calls'][$name] = $instance->getCalls(); + $this->data['instances']['adapters'][$name] = get_debug_type($instance->getPool()); + } + + $this->data['instances']['statistics'] = $this->calculateStatistics(); + $this->data['total']['statistics'] = $this->calculateTotalStatistics(); + } + + public function reset(): void + { + $this->data = []; + foreach ($this->instances as $instance) { + $instance->clearCalls(); + } + } + + public function lateCollect(): void + { + $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']); + } + + public function getName(): string + { + return 'cache'; + } + + /** + * Method returns amount of logged Cache reads: "get" calls. + */ + public function getStatistics(): array + { + return $this->data['instances']['statistics']; + } + + /** + * Method returns the statistic totals. + */ + public function getTotals(): array + { + return $this->data['total']['statistics']; + } + + /** + * Method returns all logged Cache call objects. + */ + public function getCalls(): mixed + { + return $this->data['instances']['calls']; + } + + /** + * Method returns all logged Cache adapter classes. + */ + public function getAdapters(): array + { + return $this->data['instances']['adapters']; + } + + private function calculateStatistics(): array + { + $statistics = []; + foreach ($this->data['instances']['calls'] as $name => $calls) { + $statistics[$name] = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + /** @var TraceableAdapterEvent $call */ + foreach ($calls as $call) { + ++$statistics[$name]['calls']; + $statistics[$name]['time'] += ($call->end ?? microtime(true)) - $call->start; + if ('get' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + ++$statistics[$name]['writes']; + } + } elseif ('getItem' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + } + } elseif ('getItems' === $call->name) { + $statistics[$name]['reads'] += $call->hits + $call->misses; + $statistics[$name]['hits'] += $call->hits; + $statistics[$name]['misses'] += $call->misses; + } elseif ('hasItem' === $call->name) { + ++$statistics[$name]['reads']; + foreach ($call->result ?? [] as $result) { + ++$statistics[$name][$result ? 'hits' : 'misses']; + } + } elseif ('save' === $call->name) { + ++$statistics[$name]['writes']; + } elseif ('deleteItem' === $call->name) { + ++$statistics[$name]['deletes']; + } + } + if ($statistics[$name]['reads']) { + $statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2); + } else { + $statistics[$name]['hit_read_ratio'] = null; + } + } + + return $statistics; + } + + private function calculateTotalStatistics(): array + { + $statistics = $this->getStatistics(); + $totals = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + foreach ($statistics as $name => $values) { + foreach ($totals as $key => $value) { + $totals[$key] += $statistics[$name][$key]; + } + } + if ($totals['reads']) { + $totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2); + } else { + $totals['hit_read_ratio'] = null; + } + + return $totals; + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php new file mode 100644 index 0000000..ed95740 --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Inject a data collector to all the cache services to be able to get detailed statistics. + * + * @author Tobias Nyholm + */ +class CacheCollectorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('data_collector.cache')) { + return; + } + + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $attributes) { + $poolName = $attributes[0]['name'] ?? $id; + + $this->addToCollector($id, $poolName, $container); + } + } + + private function addToCollector(string $id, string $name, ContainerBuilder $container): void + { + $definition = $container->getDefinition($id); + if ($definition->isAbstract()) { + return; + } + + $collectorDefinition = $container->getDefinition('data_collector.cache'); + $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); + $recorder->setTags($definition->getTags()); + if (!$definition->isPublic() || !$definition->isPrivate()) { + $recorder->setPublic($definition->isPublic()); + } + $recorder->setArguments([new Reference($innerId = $id.'.recorder_inner')]); + + foreach ($definition->getMethodCalls() as [$method, $args]) { + if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) { + continue; + } + if ([new Reference($id), 'setCallbackWrapper'] == $args[0]->getArguments()[2]->getFactory()) { + $args[0]->getArguments()[2]->setFactory([new Reference($innerId), 'setCallbackWrapper']); + } + } + + $definition->setTags([]); + $definition->setPublic(false); + + $container->setDefinition($innerId, $definition); + $container->setDefinition($id, $recorder); + + // Tell the collector to add the new instance + $collectorDefinition->addMethodCall('addInstance', [$name, new Reference($id)]); + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php new file mode 100644 index 0000000..5449720 --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolClearerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $container->getParameterBag()->remove('cache.prefix.seed'); + + foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) { + $clearer = $container->getDefinition($id); + $pools = []; + foreach ($clearer->getArgument(0) as $name => $ref) { + if ($container->hasDefinition($ref)) { + $pools[$name] = new Reference($ref); + } + } + $clearer->replaceArgument(0, $pools); + } + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php new file mode 100644 index 0000000..081d82c --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Cache\Adapter\ParameterNormalizer; +use Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if ($container->hasParameter('cache.prefix.seed')) { + $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); + } else { + $seed = '_'.$container->getParameter('kernel.project_dir'); + $seed .= '.'.$container->getParameter('kernel.container_class'); + } + + $needsMessageHandler = false; + $allPools = []; + $clearers = []; + $attributes = [ + 'provider', + 'name', + 'namespace', + 'default_lifetime', + 'early_expiration_message_bus', + 'reset', + ]; + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) { + $adapter = $pool = $container->getDefinition($id); + if ($pool->isAbstract()) { + continue; + } + $class = $adapter->getClass(); + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $class = $class ?: $adapter->getClass(); + if ($t = $adapter->getTag('cache.pool')) { + $tags[0] += $t[0]; + } + } + $name = $tags[0]['name'] ?? $id; + if (!isset($tags[0]['namespace'])) { + $namespaceSeed = $seed; + if (null !== $class) { + $namespaceSeed .= '.'.$class; + } + + $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name); + } + if (isset($tags[0]['clearer'])) { + $clearer = $tags[0]['clearer']; + while ($container->hasAlias($clearer)) { + $clearer = (string) $container->getAlias($clearer); + } + } else { + $clearer = null; + } + unset($tags[0]['clearer'], $tags[0]['name']); + + if (isset($tags[0]['provider'])) { + $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); + } + + if (ChainAdapter::class === $class) { + $adapters = []; + foreach ($adapter->getArgument(0) as $provider => $adapter) { + if ($adapter instanceof ChildDefinition) { + $chainedPool = $adapter; + } else { + $chainedPool = $adapter = new ChildDefinition($adapter); + } + + $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]]; + $chainedClass = ''; + + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $chainedClass = $chainedClass ?: $adapter->getClass(); + if ($t = $adapter->getTag('cache.pool')) { + $chainedTags[0] += $t[0]; + } + } + + if (ChainAdapter::class === $chainedClass) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent())); + } + + $i = 0; + + if (isset($chainedTags[0]['provider'])) { + $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider']))); + } + + if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], true)) { + $chainedPool->replaceArgument($i++, $tags[0]['namespace']); + } + + if (isset($tags[0]['default_lifetime'])) { + $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']); + } + + $adapters[] = $chainedPool; + } + + $pool->replaceArgument(0, $adapters); + unset($tags[0]['provider'], $tags[0]['namespace']); + $i = 1; + } else { + $i = 0; + } + + foreach ($attributes as $attr) { + if (!isset($tags[0][$attr])) { + // no-op + } elseif ('reset' === $attr) { + if ($tags[0][$attr]) { + $pool->addTag('kernel.reset', ['method' => $tags[0][$attr]]); + } + } elseif ('early_expiration_message_bus' === $attr) { + $needsMessageHandler = true; + $pool->addMethodCall('setCallbackWrapper', [(new Definition(EarlyExpirationDispatcher::class)) + ->addArgument(new Reference($tags[0]['early_expiration_message_bus'])) + ->addArgument(new Reference('reverse_container')) + ->addArgument((new Definition('callable')) + ->setFactory([new Reference($id), 'setCallbackWrapper']) + ->addArgument(null) + ), + ]); + $pool->addTag('container.reversible'); + } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) { + $argument = $tags[0][$attr]; + + if ('default_lifetime' === $attr && !is_numeric($argument)) { + $argument = (new Definition('int', [$argument])) + ->setFactory([ParameterNormalizer::class, 'normalizeDuration']); + } + + $pool->replaceArgument($i++, $argument); + } + unset($tags[0][$attr]); + } + if (!empty($tags[0])) { + throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); + } + + if (null !== $clearer) { + $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + if (!$needsMessageHandler) { + $container->removeDefinition('cache.early_expiration_handler'); + } + + $notAliasedCacheClearerId = 'cache.global_clearer'; + while ($container->hasAlias($notAliasedCacheClearerId)) { + $notAliasedCacheClearerId = (string) $container->getAlias($notAliasedCacheClearerId); + } + if ($container->hasDefinition($notAliasedCacheClearerId)) { + $clearers[$notAliasedCacheClearerId] = $allPools; + } + + foreach ($clearers as $id => $pools) { + $clearer = $container->getDefinition($id); + if ($clearer instanceof ChildDefinition) { + $clearer->replaceArgument(0, $pools); + } else { + $clearer->setArgument(0, $pools); + } + $clearer->addTag('cache.pool.clearer'); + + if ('cache.system_clearer' === $id) { + $clearer->addTag('kernel.cache_clearer'); + } + } + + $allPoolsKeys = array_keys($allPools); + + if ($container->hasDefinition('console.command.cache_pool_list')) { + $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, $allPoolsKeys); + } + + if ($container->hasDefinition('console.command.cache_pool_clear')) { + $container->getDefinition('console.command.cache_pool_clear')->addArgument($allPoolsKeys); + } + + if ($container->hasDefinition('console.command.cache_pool_delete')) { + $container->getDefinition('console.command.cache_pool_delete')->addArgument($allPoolsKeys); + } + } + + private function getNamespace(string $seed, string $id): string + { + return substr(str_replace('/', '-', base64_encode(hash('xxh128', $id.$seed, true))), 0, 10); + } + + /** + * @internal + */ + public static function getServiceProvider(ContainerBuilder $container, string $name): string + { + $container->resolveEnvPlaceholders($name, null, $usedEnvs); + + if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) { + $dsn = $name; + + if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) { + $definition = new Definition(AbstractAdapter::class); + $definition->setFactory([AbstractAdapter::class, 'createConnection']); + $definition->setArguments([$dsn, ['lazy' => true]]); + $container->setDefinition($name, $definition); + } + } + + return $name; + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php new file mode 100644 index 0000000..69b69fb --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Rob Frawley 2nd + */ +class CachePoolPrunerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('console.command.cache_pool_prune')) { + return; + } + + $services = []; + + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) { + $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass()); + + if (!$reflection = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + if ($reflection->implementsInterface(PruneableInterface::class)) { + $services[$id] = new Reference($id); + } + } + + $container->getDefinition('console.command.cache_pool_prune')->replaceArgument(0, new IteratorArgument($services)); + } +} diff --git a/vendor/symfony/cache/Exception/CacheException.php b/vendor/symfony/cache/Exception/CacheException.php new file mode 100644 index 0000000..d2e975b --- /dev/null +++ b/vendor/symfony/cache/Exception/CacheException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class CacheException extends \Exception implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/Exception/InvalidArgumentException.php b/vendor/symfony/cache/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..7f9584a --- /dev/null +++ b/vendor/symfony/cache/Exception/InvalidArgumentException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/Exception/LogicException.php b/vendor/symfony/cache/Exception/LogicException.php new file mode 100644 index 0000000..9ffa7ed --- /dev/null +++ b/vendor/symfony/cache/Exception/LogicException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class LogicException extends \LogicException implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/LICENSE b/vendor/symfony/cache/LICENSE new file mode 100644 index 0000000..0223acd --- /dev/null +++ b/vendor/symfony/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-present Fabien Potencier + +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. diff --git a/vendor/symfony/cache/LockRegistry.php b/vendor/symfony/cache/LockRegistry.php new file mode 100644 index 0000000..c5c5fde --- /dev/null +++ b/vendor/symfony/cache/LockRegistry.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * LockRegistry is used internally by existing adapters to protect against cache stampede. + * + * It does so by wrapping the computation of items in a pool of locks. + * Foreach each apps, there can be at most 20 concurrent processes that + * compute items at the same time and only one per cache-key. + * + * @author Nicolas Grekas + */ +final class LockRegistry +{ + private static array $openedFiles = []; + private static ?array $lockedFiles = null; + private static \Exception $signalingException; + private static \Closure $signalingCallback; + + /** + * The number of items in this list controls the max number of concurrent processes. + */ + private static array $files = [ + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseBucketAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseCollectionAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineDbalAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ParameterNormalizer.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php', + ]; + + /** + * Defines a set of existing files that will be used as keys to acquire locks. + * + * @return array The previously defined set of files + */ + public static function setFiles(array $files): array + { + $previousFiles = self::$files; + self::$files = $files; + + foreach (self::$openedFiles as $file) { + if ($file) { + flock($file, \LOCK_UN); + fclose($file); + } + } + self::$openedFiles = self::$lockedFiles = []; + + return $previousFiles; + } + + public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, ?\Closure $setMetadata = null, ?LoggerInterface $logger = null): mixed + { + if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) { + // disable locking on Windows by default + self::$files = self::$lockedFiles = []; + } + + $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1; + + if ($key < 0 || self::$lockedFiles || !$lock = self::open($key)) { + return $callback($item, $save); + } + + self::$signalingException ??= unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); + self::$signalingCallback ??= fn () => throw self::$signalingException; + + while (true) { + try { + // race to get the lock in non-blocking mode + $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); + + if ($locked || !$wouldBlock) { + $logger?->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); + self::$lockedFiles[$key] = true; + + $value = $callback($item, $save); + + if ($save) { + if ($setMetadata) { + $setMetadata($item); + } + + $pool->save($item->set($value)); + $save = false; + } + + return $value; + } + // if we failed the race, retry locking in blocking mode to wait for the winner + $logger?->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); + flock($lock, \LOCK_SH); + } finally { + flock($lock, \LOCK_UN); + unset(self::$lockedFiles[$key]); + } + + try { + $value = $pool->get($item->getKey(), self::$signalingCallback, 0); + $logger?->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); + $save = false; + + return $value; + } catch (\Exception $e) { + if (self::$signalingException !== $e) { + throw $e; + } + $logger?->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); + } + } + + return null; + } + + /** + * @return resource|false + */ + private static function open(int $key) + { + if (null !== $h = self::$openedFiles[$key] ?? null) { + return $h; + } + set_error_handler(static fn () => null); + try { + $h = fopen(self::$files[$key], 'r+'); + } finally { + restore_error_handler(); + } + + return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r'); + } +} diff --git a/vendor/symfony/cache/Marshaller/DefaultMarshaller.php b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php new file mode 100644 index 0000000..34bbeb8 --- /dev/null +++ b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise. + * + * @author Nicolas Grekas + */ +class DefaultMarshaller implements MarshallerInterface +{ + private bool $useIgbinarySerialize = true; + private bool $throwOnSerializationFailure = false; + + public function __construct(?bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false) + { + if (null === $useIgbinarySerialize) { + $useIgbinarySerialize = \extension_loaded('igbinary') && version_compare('3.1.6', phpversion('igbinary'), '<='); + } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || version_compare('3.1.6', phpversion('igbinary'), '>'))) { + throw new CacheException(\extension_loaded('igbinary') ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.'); + } + $this->useIgbinarySerialize = $useIgbinarySerialize; + $this->throwOnSerializationFailure = $throwOnSerializationFailure; + } + + public function marshall(array $values, ?array &$failed): array + { + $serialized = $failed = []; + + foreach ($values as $id => $value) { + try { + if ($this->useIgbinarySerialize) { + $serialized[$id] = igbinary_serialize($value); + } else { + $serialized[$id] = serialize($value); + } + } catch (\Exception $e) { + if ($this->throwOnSerializationFailure) { + throw new \ValueError($e->getMessage(), 0, $e); + } + $failed[] = $id; + } + } + + return $serialized; + } + + public function unmarshall(string $value): mixed + { + if ('b:0;' === $value) { + return false; + } + if ('N;' === $value) { + return null; + } + static $igbinaryNull; + if ($value === $igbinaryNull ??= \extension_loaded('igbinary') ? igbinary_serialize(null) : false) { + return null; + } + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + if (':' === ($value[1] ?? ':')) { + if (false !== $value = unserialize($value)) { + return $value; + } + } elseif (false === $igbinaryNull) { + throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?'); + } elseif (null !== $value = igbinary_unserialize($value)) { + return $value; + } + + throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.'); + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class): never + { + throw new \DomainException('Class not found: '.$class); + } +} diff --git a/vendor/symfony/cache/Marshaller/DeflateMarshaller.php b/vendor/symfony/cache/Marshaller/DeflateMarshaller.php new file mode 100644 index 0000000..f35241c --- /dev/null +++ b/vendor/symfony/cache/Marshaller/DeflateMarshaller.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Compresses values using gzdeflate(). + * + * @author Nicolas Grekas + */ +class DeflateMarshaller implements MarshallerInterface +{ + public function __construct( + private MarshallerInterface $marshaller, + ) { + if (!\function_exists('gzdeflate')) { + throw new CacheException('The "zlib" PHP extension is not loaded.'); + } + } + + public function marshall(array $values, ?array &$failed): array + { + return array_map('gzdeflate', $this->marshaller->marshall($values, $failed)); + } + + public function unmarshall(string $value): mixed + { + if (false !== $inflatedValue = @gzinflate($value)) { + $value = $inflatedValue; + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/vendor/symfony/cache/Marshaller/MarshallerInterface.php b/vendor/symfony/cache/Marshaller/MarshallerInterface.php new file mode 100644 index 0000000..5b81aad --- /dev/null +++ b/vendor/symfony/cache/Marshaller/MarshallerInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * Serializes/unserializes PHP values. + * + * Implementations of this interface MUST deal with errors carefully. They MUST + * also deal with forward and backward compatibility at the storage format level. + * + * @author Nicolas Grekas + */ +interface MarshallerInterface +{ + /** + * Serializes a list of values. + * + * When serialization fails for a specific value, no exception should be + * thrown. Instead, its key should be listed in $failed. + */ + public function marshall(array $values, ?array &$failed): array; + + /** + * Unserializes a single value and throws an exception if anything goes wrong. + * + * @throws \Exception Whenever unserialization fails + */ + public function unmarshall(string $value): mixed; +} diff --git a/vendor/symfony/cache/Marshaller/SodiumMarshaller.php b/vendor/symfony/cache/Marshaller/SodiumMarshaller.php new file mode 100644 index 0000000..77d16e8 --- /dev/null +++ b/vendor/symfony/cache/Marshaller/SodiumMarshaller.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * Encrypt/decrypt values using Libsodium. + * + * @author Ahmed TAILOULOUTE + */ +class SodiumMarshaller implements MarshallerInterface +{ + private MarshallerInterface $marshaller; + + /** + * @param string[] $decryptionKeys The key at index "0" is required and is used to decrypt and encrypt values; + * more rotating keys can be provided to decrypt values; + * each key must be generated using sodium_crypto_box_keypair() + */ + public function __construct( + private array $decryptionKeys, + ?MarshallerInterface $marshaller = null, + ) { + if (!self::isSupported()) { + throw new CacheException('The "sodium" PHP extension is not loaded.'); + } + + if (!isset($decryptionKeys[0])) { + throw new InvalidArgumentException('At least one decryption key must be provided at index "0".'); + } + + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function isSupported(): bool + { + return \function_exists('sodium_crypto_box_seal'); + } + + public function marshall(array $values, ?array &$failed): array + { + $encryptionKey = sodium_crypto_box_publickey($this->decryptionKeys[0]); + + $encryptedValues = []; + foreach ($this->marshaller->marshall($values, $failed) as $k => $v) { + $encryptedValues[$k] = sodium_crypto_box_seal($v, $encryptionKey); + } + + return $encryptedValues; + } + + public function unmarshall(string $value): mixed + { + foreach ($this->decryptionKeys as $k) { + if (false !== $decryptedValue = @sodium_crypto_box_seal_open($value, $k)) { + $value = $decryptedValue; + break; + } + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php new file mode 100644 index 0000000..825f32c --- /dev/null +++ b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * A marshaller optimized for data structures generated by AbstractTagAwareAdapter. + * + * @author Nicolas Grekas + */ +class TagAwareMarshaller implements MarshallerInterface +{ + private MarshallerInterface $marshaller; + + public function __construct(?MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public function marshall(array $values, ?array &$failed): array + { + $failed = $notSerialized = $serialized = []; + + foreach ($values as $id => $value) { + if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) { + // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall() + + $v = $this->marshaller->marshall($value, $f); + + if ($f) { + $f = []; + $failed[] = $id; + } else { + if ([] === $value['tags']) { + $v['tags'] = ''; + } + + $serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value']; + $serialized[$id][9] = "\x5F"; + } + } else { + // other arbitrary values are serialized using the decorated marshaller below + $notSerialized[$id] = $value; + } + } + + if ($notSerialized) { + $serialized += $this->marshaller->marshall($notSerialized, $f); + $failed = array_merge($failed, $f); + } + + return $serialized; + } + + public function unmarshall(string $value): mixed + { + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) { + return $this->marshaller->unmarshall($value); + } + + // data consists of value, tags and metadata which we need to unpack + $meta = substr($value, 1, 12); + $meta[8] = "\0"; + $tagLen = unpack('Nlen', $meta, 8)['len']; + $meta = substr($meta, 0, 8); + + return [ + 'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)), + 'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [], + 'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta, + ]; + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php b/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php new file mode 100644 index 0000000..d972634 --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Stamp\HandledStamp; + +/** + * Sends the computation of cached values to a message bus. + */ +class EarlyExpirationDispatcher +{ + private ?\Closure $callbackWrapper; + + public function __construct( + private MessageBusInterface $bus, + private ReverseContainer $reverseContainer, + ?callable $callbackWrapper = null, + ) { + $this->callbackWrapper = null === $callbackWrapper ? null : $callbackWrapper(...); + } + + public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger = null): mixed + { + if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) { + // The item is stale or the callback cannot be reversed: we must compute the value now + $logger?->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); + + return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save); + } + + $envelope = $this->bus->dispatch($message); + + if ($logger) { + if ($envelope->last(HandledStamp::class)) { + $logger->info('Item "{key}" was computed online', ['key' => $item->getKey()]); + } else { + $logger->info('Item "{key}" sent for recomputation', ['key' => $item->getKey()]); + } + } + + // The item's value is not stale, no need to write it to the backend + $save = false; + + return $message->getItem()->get() ?? $item->get(); + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php b/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php new file mode 100644 index 0000000..adaeb5b --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; + +/** + * Computes cached values sent to a message bus. + */ +#[AsMessageHandler] +class EarlyExpirationHandler +{ + private array $processedNonces = []; + + public function __construct( + private ReverseContainer $reverseContainer, + ) { + } + + public function __invoke(EarlyExpirationMessage $message): void + { + $item = $message->getItem(); + $metadata = $item->getMetadata(); + $expiry = $metadata[CacheItem::METADATA_EXPIRY] ?? 0; + $ctime = $metadata[CacheItem::METADATA_CTIME] ?? 0; + + if ($expiry && $ctime) { + // skip duplicate or expired messages + + $processingNonce = [$expiry, $ctime]; + $pool = $message->getPool(); + $key = $item->getKey(); + + if (($this->processedNonces[$pool][$key] ?? null) === $processingNonce) { + return; + } + + if (microtime(true) >= $expiry) { + return; + } + + $this->processedNonces[$pool] = [$key => $processingNonce] + ($this->processedNonces[$pool] ?? []); + + if (\count($this->processedNonces[$pool]) > 100) { + array_pop($this->processedNonces[$pool]); + } + } + + static $setMetadata; + + $setMetadata ??= \Closure::bind( + function (CacheItem $item, float $startTime) { + if ($item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); + } + }, + null, + CacheItem::class + ); + + $startTime = microtime(true); + $pool = $message->findPool($this->reverseContainer); + $callback = $message->findCallback($this->reverseContainer); + $save = true; + $value = $callback($item, $save); + $setMetadata($item, $startTime); + $pool->save($item->set($value)); + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php b/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php new file mode 100644 index 0000000..6056eba --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; + +/** + * Conveys a cached value that needs to be computed. + */ +final class EarlyExpirationMessage +{ + private CacheItem $item; + private string $pool; + private string|array $callback; + + public static function create(ReverseContainer $reverseContainer, callable $callback, CacheItem $item, AdapterInterface $pool): ?self + { + try { + $item = clone $item; + $item->set(null); + } catch (\Exception) { + return null; + } + + $pool = $reverseContainer->getId($pool); + + if (\is_object($callback)) { + if (null === $id = $reverseContainer->getId($callback)) { + return null; + } + + $callback = '@'.$id; + } elseif (!\is_array($callback)) { + $callback = (string) $callback; + } elseif (!\is_object($callback[0])) { + $callback = [(string) $callback[0], (string) $callback[1]]; + } else { + if (null === $id = $reverseContainer->getId($callback[0])) { + return null; + } + + $callback = ['@'.$id, (string) $callback[1]]; + } + + return new self($item, $pool, $callback); + } + + public function getItem(): CacheItem + { + return $this->item; + } + + public function getPool(): string + { + return $this->pool; + } + + /** + * @return string|string[] + */ + public function getCallback(): string|array + { + return $this->callback; + } + + public function findPool(ReverseContainer $reverseContainer): AdapterInterface + { + return $reverseContainer->getService($this->pool); + } + + public function findCallback(ReverseContainer $reverseContainer): callable + { + if (\is_string($callback = $this->callback)) { + return '@' === $callback[0] ? $reverseContainer->getService(substr($callback, 1)) : $callback; + } + if ('@' === $callback[0][0]) { + $callback[0] = $reverseContainer->getService(substr($callback[0], 1)); + } + + return $callback; + } + + private function __construct(CacheItem $item, string $pool, string|array $callback) + { + $this->item = $item; + $this->pool = $pool; + $this->callback = $callback; + } +} diff --git a/vendor/symfony/cache/PruneableInterface.php b/vendor/symfony/cache/PruneableInterface.php new file mode 100644 index 0000000..3095c80 --- /dev/null +++ b/vendor/symfony/cache/PruneableInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +/** + * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items. + */ +interface PruneableInterface +{ + public function prune(): bool; +} diff --git a/vendor/symfony/cache/Psr16Cache.php b/vendor/symfony/cache/Psr16Cache.php new file mode 100644 index 0000000..f21384f --- /dev/null +++ b/vendor/symfony/cache/Psr16Cache.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Cache\CacheException as Psr6CacheException; +use Psr\Cache\CacheItemPoolInterface; +use Psr\SimpleCache\CacheException as SimpleCacheException; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-6 cache into a PSR-16 one. + * + * @author Nicolas Grekas + */ +class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface +{ + use ProxyTrait; + + private ?\Closure $createCacheItem = null; + private ?CacheItem $cacheItemPrototype = null; + private static \Closure $packCacheItem; + + public function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + + if (!$pool instanceof AdapterInterface) { + return; + } + $cacheItemPrototype = &$this->cacheItemPrototype; + $createCacheItem = \Closure::bind( + static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) { + $item = clone $cacheItemPrototype; + $item->poolHash = $item->innerItem = null; + if ($allowInt && \is_int($key)) { + $item->key = (string) $key; + } else { + \assert('' !== CacheItem::validateKey($key)); + $item->key = $key; + } + $item->value = $value; + $item->isHit = false; + + return $item; + }, + null, + CacheItem::class + ); + $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) { + if (null === $this->cacheItemPrototype) { + $this->get($allowInt && \is_int($key) ? (string) $key : $key); + } + $this->createCacheItem = $createCacheItem; + + return $createCacheItem($key, null, $allowInt)->set($value); + }; + self::$packCacheItem ??= \Closure::bind( + static function (CacheItem $item) { + $item->newMetadata = $item->metadata; + + return $item->pack(); + }, + null, + CacheItem::class + ); + } + + public function get($key, $default = null): mixed + { + try { + $item = $this->pool->getItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null === $this->cacheItemPrototype) { + $this->cacheItemPrototype = clone $item; + $this->cacheItemPrototype->set(null); + } + + return $item->isHit() ? $item->get() : $default; + } + + public function set($key, $value, $ttl = null): bool + { + try { + if (null !== $f = $this->createCacheItem) { + $item = $f($key, $value); + } else { + $item = $this->pool->getItem($key)->set($value); + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + + return $this->pool->save($item); + } + + public function delete($key): bool + { + try { + return $this->pool->deleteItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + public function clear(): bool + { + return $this->pool->clear(); + } + + public function getMultiple($keys, $default = null): iterable + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys))); + } + + try { + $items = $this->pool->getItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $values = []; + + if (!$this->pool instanceof AdapterInterface) { + foreach ($items as $key => $item) { + $values[$key] = $item->isHit() ? $item->get() : $default; + } + + return $values; + } + + foreach ($items as $key => $item) { + $values[$key] = $item->isHit() ? (self::$packCacheItem)($item) : $default; + } + + return $values; + } + + public function setMultiple($values, $ttl = null): bool + { + $valuesIsArray = \is_array($values); + if (!$valuesIsArray && !$values instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', get_debug_type($values))); + } + $items = []; + + try { + if (null !== $f = $this->createCacheItem) { + $valuesIsArray = false; + foreach ($values as $key => $value) { + $items[$key] = $f($key, $value, true); + } + } elseif ($valuesIsArray) { + $items = []; + foreach ($values as $key => $value) { + $items[] = (string) $key; + } + $items = $this->pool->getItems($items); + } else { + foreach ($values as $key => $value) { + if (\is_int($key)) { + $key = (string) $key; + } + $items[$key] = $this->pool->getItem($key)->set($value); + } + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $ok = true; + + foreach ($items as $key => $item) { + if ($valuesIsArray) { + $item->set($values[$key]); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + $ok = $this->pool->saveDeferred($item) && $ok; + } + + return $this->pool->commit() && $ok; + } + + public function deleteMultiple($keys): bool + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys))); + } + + try { + return $this->pool->deleteItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + public function has($key): bool + { + try { + return $this->pool->hasItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/cache/README.md b/vendor/symfony/cache/README.md new file mode 100644 index 0000000..c466d57 --- /dev/null +++ b/vendor/symfony/cache/README.md @@ -0,0 +1,19 @@ +Symfony PSR-6 implementation for caching +======================================== + +The Cache component provides extended +[PSR-6](https://www.php-fig.org/psr/psr-6/) implementations for adding cache to +your applications. It is designed to have a low overhead so that caching is +fastest. It ships with adapters for the most widespread caching backends. +It also provides a [PSR-16](https://www.php-fig.org/psr/psr-16/) adapter, +and implementations for [symfony/cache-contracts](https://github.com/symfony/cache-contracts)' +`CacheInterface` and `TagAwareCacheInterface`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/cache.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/cache/ResettableInterface.php b/vendor/symfony/cache/ResettableInterface.php new file mode 100644 index 0000000..7b0a853 --- /dev/null +++ b/vendor/symfony/cache/ResettableInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Symfony\Contracts\Service\ResetInterface; + +/** + * Resets a pool's local state. + */ +interface ResettableInterface extends ResetInterface +{ +} diff --git a/vendor/symfony/cache/Traits/AbstractAdapterTrait.php b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php new file mode 100644 index 0000000..222bc54 --- /dev/null +++ b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php @@ -0,0 +1,377 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait AbstractAdapterTrait +{ + use LoggerAwareTrait; + + /** + * needs to be set by class, signature is function(string , mixed , bool ). + */ + private static \Closure $createCacheItem; + + /** + * needs to be set by class, signature is function(array , string , array <&expiredIds>). + */ + private static \Closure $mergeByLifetime; + + private string $namespace = ''; + private int $defaultLifetime; + private string $namespaceVersion = ''; + private bool $versioningIsEnabled = false; + private array $deferred = []; + private array $ids = []; + + /** + * The maximum length to enforce for identifiers or null when no limit applies. + */ + protected ?int $maxIdLength = null; + + /** + * Fetches several cache items. + * + * @param array $ids The cache identifiers to fetch + */ + abstract protected function doFetch(array $ids): iterable; + + /** + * Confirms if the cache contains specified cache item. + * + * @param string $id The identifier for which to check existence + */ + abstract protected function doHave(string $id): bool; + + /** + * Deletes all items in the pool. + * + * @param string $namespace The prefix used for all identifiers managed by this pool + */ + abstract protected function doClear(string $namespace): bool; + + /** + * Removes multiple items from the pool. + * + * @param array $ids An array of identifiers that should be removed from the pool + */ + abstract protected function doDelete(array $ids): bool; + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * + * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, int $lifetime): array|bool; + + public function hasItem(mixed $key): bool + { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $this->commit(); + } + + try { + return $this->doHave($id); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return false; + } + } + + public function clear(string $prefix = ''): bool + { + $this->deferred = []; + if ($cleared = $this->versioningIsEnabled) { + if ('' === $namespaceVersionToClear = $this->namespaceVersion) { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $namespaceVersionToClear = $v; + } + } + $namespaceToClear = $this->namespace.$namespaceVersionToClear; + $namespaceVersion = self::formatNamespaceVersion(mt_rand()); + try { + $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0); + } catch (\Exception $e) { + } + if (true !== $e && [] !== $e) { + $cleared = false; + $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } else { + $this->namespaceVersion = $namespaceVersion; + $this->ids = []; + } + } else { + $namespaceToClear = $this->namespace.$prefix; + } + + try { + return $this->doClear($namespaceToClear) || $cleared; + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return false; + } + } + + public function deleteItem(mixed $key): bool + { + return $this->deleteItems([$key]); + } + + public function deleteItems(array $keys): bool + { + $ids = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + if ($this->doDelete($ids)) { + return true; + } + } catch (\Exception) { + } + + $ok = true; + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $ok = false; + } + + return $ok; + } + + public function getItem(mixed $key): CacheItem + { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $this->commit(); + } + + $isHit = false; + $value = null; + + try { + foreach ($this->doFetch([$id]) as $value) { + $isHit = true; + } + + return (self::$createCacheItem)($key, $value, $isHit); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + return (self::$createCacheItem)($key, null, false); + } + + public function getItems(array $keys = []): iterable + { + $ids = []; + $commit = false; + + foreach ($keys as $key) { + $ids[] = $this->getId($key); + $commit = $commit || isset($this->deferred[$key]); + } + + if ($commit) { + $this->commit(); + } + + try { + $items = $this->doFetch($ids); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $items = []; + } + $ids = array_combine($ids, $keys); + + return $this->generateItems($items, $ids); + } + + public function save(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * Enables/disables versioning of items. + * + * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed, + * but old keys may need garbage collection and extra round-trips to the back-end are required. + * + * Calling this method also clears the memoized namespace version and thus forces a resynchronization of it. + * + * @return bool the previous state of versioning + */ + public function enableVersioning(bool $enable = true): bool + { + $wasEnabled = $this->versioningIsEnabled; + $this->versioningIsEnabled = $enable; + $this->namespaceVersion = ''; + $this->ids = []; + + return $wasEnabled; + } + + public function reset(): void + { + if ($this->deferred) { + $this->commit(); + } + $this->namespaceVersion = ''; + $this->ids = []; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if ($this->deferred) { + $this->commit(); + } + } + + private function generateItems(iterable $items, array &$keys): \Generator + { + $f = self::$createCacheItem; + + try { + foreach ($items as $id => $value) { + if (!isset($keys[$id])) { + throw new InvalidArgumentException(sprintf('Could not match value id "%s" to keys "%s".', $id, implode('", "', $keys))); + } + $key = $keys[$id]; + unset($keys[$id]); + yield $key => $f($key, $value, true); + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } + + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { + $this->ids = []; + $this->namespaceVersion = '1'.static::NS_SEPARATOR; + try { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $this->namespaceVersion = $v; + } + $e = true; + if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) { + $this->namespaceVersion = self::formatNamespaceVersion(time()); + $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0); + } + } catch (\Exception $e) { + } + if (true !== $e && [] !== $e) { + $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + if (\is_string($key) && isset($this->ids[$key])) { + return $this->namespace.$this->namespaceVersion.$this->ids[$key]; + } + \assert('' !== CacheItem::validateKey($key)); + $this->ids[$key] = $key; + + if (\count($this->ids) > 1000) { + $this->ids = \array_slice($this->ids, 500, null, true); // stop memory leak if there are many keys + } + + if (null === $this->maxIdLength) { + return $this->namespace.$this->namespaceVersion.$key; + } + if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { + // Use xxh128 to favor speed over security, which is not an issue here + $this->ids[$key] = $id = substr_replace(base64_encode(hash('xxh128', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); + $id = $this->namespace.$this->namespaceVersion.$id; + } + + return $id; + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class): never + { + throw new \DomainException('Class not found: '.$class); + } + + private static function formatNamespaceVersion(int $value): string + { + return strtr(substr_replace(base64_encode(pack('V', $value)), static::NS_SEPARATOR, 5), '/', '_'); + } +} diff --git a/vendor/symfony/cache/Traits/ContractsTrait.php b/vendor/symfony/cache/Traits/ContractsTrait.php new file mode 100644 index 0000000..8d830f0 --- /dev/null +++ b/vendor/symfony/cache/Traits/ContractsTrait.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\LockRegistry; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\CacheTrait; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ContractsTrait +{ + use CacheTrait { + doGet as private contractsGet; + } + + private \Closure $callbackWrapper; + private array $computing = []; + + /** + * Wraps the callback passed to ->get() in a callable. + * + * @return callable the previous callback wrapper + */ + public function setCallbackWrapper(?callable $callbackWrapper): callable + { + if (!isset($this->callbackWrapper)) { + $this->callbackWrapper = LockRegistry::compute(...); + + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + $this->setCallbackWrapper(null); + } + } + + if (null !== $callbackWrapper && !$callbackWrapper instanceof \Closure) { + $callbackWrapper = $callbackWrapper(...); + } + + $previousWrapper = $this->callbackWrapper; + $this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) => $callback($item, $save); + + return $previousWrapper; + } + + private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null): mixed + { + if (0 > $beta ??= 1.0) { + throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); + } + + static $setMetadata; + + $setMetadata ??= \Closure::bind( + static function (CacheItem $item, float $startTime, ?array &$metadata) { + if ($item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); + } else { + unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME], $metadata[CacheItem::METADATA_TAGS]); + } + }, + null, + CacheItem::class + ); + + $this->callbackWrapper ??= LockRegistry::compute(...); + + return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) { + // don't wrap nor save recursive calls + if (isset($this->computing[$key])) { + $value = $callback($item, $save); + $save = false; + + return $value; + } + + $this->computing[$key] = $key; + $startTime = microtime(true); + + if (!isset($this->callbackWrapper)) { + $this->setCallbackWrapper($this->setCallbackWrapper(null)); + } + + try { + $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) { + $setMetadata($item, $startTime, $metadata); + }, $this->logger ?? null); + $setMetadata($item, $startTime, $metadata); + + return $value; + } finally { + unset($this->computing[$key]); + } + }, $beta, $metadata, $this->logger ?? null); + } +} diff --git a/vendor/symfony/cache/Traits/FilesystemCommonTrait.php b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php new file mode 100644 index 0000000..3e8c3b1 --- /dev/null +++ b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait FilesystemCommonTrait +{ + private string $directory; + private string $tmpSuffix; + + private function init(string $namespace, ?string $directory): void + { + if (!isset($directory[0])) { + $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache'; + } else { + $directory = realpath($directory) ?: $directory; + } + if (isset($namespace[0])) { + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + $directory .= \DIRECTORY_SEPARATOR.$namespace; + } else { + $directory .= \DIRECTORY_SEPARATOR.'@'; + } + if (!is_dir($directory)) { + @mkdir($directory, 0777, true); + } + $directory .= \DIRECTORY_SEPARATOR; + // On Windows the whole path is limited to 258 chars + if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) { + throw new InvalidArgumentException(sprintf('Cache directory too long (%s).', $directory)); + } + + $this->directory = $directory; + } + + protected function doClear(string $namespace): bool + { + $ok = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if ('' !== $namespace && !str_starts_with($this->getFileKey($file), $namespace)) { + continue; + } + + $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + protected function doDelete(array $ids): bool + { + $ok = true; + + foreach ($ids as $id) { + $file = $this->getFile($id); + $ok = (!is_file($file) || $this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + protected function doUnlink(string $file): bool + { + return @unlink($file); + } + + private function write(string $file, string $data, ?int $expiresAt = null): bool + { + $unlink = false; + set_error_handler(static fn ($type, $message, $file, $line) => throw new \ErrorException($message, 0, $type, $file, $line)); + try { + $tmp = $this->directory.$this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + try { + $h = fopen($tmp, 'x'); + } catch (\ErrorException $e) { + if (!str_contains($e->getMessage(), 'File exists')) { + throw $e; + } + + $tmp = $this->directory.$this->tmpSuffix = str_replace('/', '-', base64_encode(random_bytes(6))); + $h = fopen($tmp, 'x'); + } + fwrite($h, $data); + fclose($h); + $unlink = true; + + if (null !== $expiresAt) { + touch($tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $success = copy($tmp, $file); + $unlink = true; + } else { + $success = rename($tmp, $file); + $unlink = !$success; + } + + return $success; + } finally { + restore_error_handler(); + + if ($unlink) { + @unlink($tmp); + } + } + } + + private function getFile(string $id, bool $mkdir = false, ?string $directory = null): string + { + // Use xxh128 to favor speed over security, which is not an issue here + $hash = str_replace('/', '-', base64_encode(hash('xxh128', static::class.$id, true))); + $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR); + + if ($mkdir && !is_dir($dir)) { + @mkdir($dir, 0777, true); + } + + return $dir.substr($hash, 2, 20); + } + + private function getFileKey(string $file): string + { + return ''; + } + + private function scanHashDir(string $directory): \Generator + { + if (!is_dir($directory)) { + return; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($directory.$chars[$i])) { + continue; + } + + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + + foreach (@scandir($dir, \SCANDIR_SORT_NONE) ?: [] as $file) { + if ('.' !== $file && '..' !== $file) { + yield $dir.\DIRECTORY_SEPARATOR.$file; + } + } + } + } + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (method_exists(parent::class, '__destruct')) { + parent::__destruct(); + } + if (isset($this->tmpSuffix) && is_file($this->directory.$this->tmpSuffix)) { + unlink($this->directory.$this->tmpSuffix); + } + } +} diff --git a/vendor/symfony/cache/Traits/FilesystemTrait.php b/vendor/symfony/cache/Traits/FilesystemTrait.php new file mode 100644 index 0000000..47e9b83 --- /dev/null +++ b/vendor/symfony/cache/Traits/FilesystemTrait.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Nicolas Grekas + * @author Rob Frawley 2nd + * + * @internal + */ +trait FilesystemTrait +{ + use FilesystemCommonTrait; + + private MarshallerInterface $marshaller; + + public function prune(): bool + { + $time = time(); + $pruned = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if (!$h = @fopen($file, 'r')) { + continue; + } + + if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) { + fclose($h); + $pruned = (@unlink($file) || !file_exists($file)) && $pruned; + } else { + fclose($h); + } + } + + return $pruned; + } + + protected function doFetch(array $ids): iterable + { + $values = []; + $now = time(); + + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!is_file($file) || !$h = @fopen($file, 'r')) { + continue; + } + if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) { + fclose($h); + @unlink($file); + } else { + $i = rawurldecode(rtrim(fgets($h))); + $value = stream_get_contents($h); + fclose($h); + if ($i === $id) { + $values[$id] = $this->marshaller->unmarshall($value); + } + } + } + + return $values; + } + + protected function doHave(string $id): bool + { + $file = $this->getFile($id); + + return is_file($file) && (@filemtime($file) > time() || $this->doFetch([$id])); + } + + protected function doSave(array $values, int $lifetime): array|bool + { + $expiresAt = $lifetime ? (time() + $lifetime) : 0; + $values = $this->marshaller->marshall($values, $failed); + + foreach ($values as $id => $value) { + if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) { + $failed[] = $id; + } + } + + if ($failed && !is_writable($this->directory)) { + throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $failed; + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'r')) { + return ''; + } + + fgets($h); // expiry + $encodedKey = fgets($h); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} diff --git a/vendor/symfony/cache/Traits/ProxyTrait.php b/vendor/symfony/cache/Traits/ProxyTrait.php new file mode 100644 index 0000000..ba7c11f --- /dev/null +++ b/vendor/symfony/cache/Traits/ProxyTrait.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ProxyTrait +{ + private object $pool; + + public function prune(): bool + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + public function reset(): void + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + } +} diff --git a/vendor/symfony/cache/Traits/Redis5Proxy.php b/vendor/symfony/cache/Traits/Redis5Proxy.php new file mode 100644 index 0000000..0b2794e --- /dev/null +++ b/vendor/symfony/cache/Traits/Redis5Proxy.php @@ -0,0 +1,1228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class Redis5Proxy extends \Redis implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _prefix($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _serialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _pack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function _compress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function acl($subcmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $auth) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->auth(...\func_get_args()); + } + + public function bgSave() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgSave(...\func_get_args()); + } + + public function bgrewriteaof() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bitcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $ret_key, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blPop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blPop(...\func_get_args()); + } + + public function brPop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brPop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function bzPopMax($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMax(...\func_get_args()); + } + + public function bzPopMin($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMin(...\func_get_args()); + } + + public function clearLastError() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearLastError(...\func_get_args()); + } + + public function client($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function command(...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($cmd, $key, $value = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function connect($host, $port = null, $timeout = null, $retry_interval = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->connect(...\func_get_args()); + } + + public function dbSize() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbSize(...\func_get_args()); + } + + public function debug($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->debug(...\func_get_args()); + } + + public function decr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrBy($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrBy(...\func_get_args()); + } + + public function del($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function discard() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function dump($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function echo($msg) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function evalsha($script_sha, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function exec() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function expire($key, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireAt($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireAt(...\func_get_args()); + } + + public function flushAll($async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushAll(...\func_get_args()); + } + + public function flushDB($async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushDB(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function get($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getAuth() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getAuth(...\func_get_args()); + } + + public function getBit($key, $offset) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getBit(...\func_get_args()); + } + + public function getDBNum() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDBNum(...\func_get_args()); + } + + public function getHost() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getHost(...\func_get_args()); + } + + public function getLastError() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getLastError(...\func_get_args()); + } + + public function getMode() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMode(...\func_get_args()); + } + + public function getOption($option) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getOption(...\func_get_args()); + } + + public function getPersistentID() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPersistentID(...\func_get_args()); + } + + public function getPort() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPort(...\func_get_args()); + } + + public function getRange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getRange(...\func_get_args()); + } + + public function getReadTimeout() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getReadTimeout(...\func_get_args()); + } + + public function getSet($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getSet(...\func_get_args()); + } + + public function getTimeout() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTimeout(...\func_get_args()); + } + + public function hDel($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hDel(...\func_get_args()); + } + + public function hExists($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hExists(...\func_get_args()); + } + + public function hGet($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGet(...\func_get_args()); + } + + public function hGetAll($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGetAll(...\func_get_args()); + } + + public function hIncrBy($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrBy(...\func_get_args()); + } + + public function hIncrByFloat($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrByFloat(...\func_get_args()); + } + + public function hKeys($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hKeys(...\func_get_args()); + } + + public function hLen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hLen(...\func_get_args()); + } + + public function hMget($key, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMget(...\func_get_args()); + } + + public function hMset($key, $pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMset(...\func_get_args()); + } + + public function hSet($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSet(...\func_get_args()); + } + + public function hSetNx($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSetNx(...\func_get_args()); + } + + public function hStrLen($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hStrLen(...\func_get_args()); + } + + public function hVals($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hVals(...\func_get_args()); + } + + public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function incr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrBy($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrBy(...\func_get_args()); + } + + public function incrByFloat($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrByFloat(...\func_get_args()); + } + + public function info($option = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function isConnected() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->isConnected(...\func_get_args()); + } + + public function keys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lInsert($key, $position, $pivot, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lInsert(...\func_get_args()); + } + + public function lLen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lLen(...\func_get_args()); + } + + public function lPop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPop(...\func_get_args()); + } + + public function lPush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPush(...\func_get_args()); + } + + public function lPushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPushx(...\func_get_args()); + } + + public function lSet($key, $index, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lSet(...\func_get_args()); + } + + public function lastSave() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastSave(...\func_get_args()); + } + + public function lindex($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function lrange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function ltrim($key, $start, $stop) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function mget($keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function migrate($host, $port, $key, $db, $timeout, $copy = null, $replace = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->migrate(...\func_get_args()); + } + + public function move($key, $dbindex) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->move(...\func_get_args()); + } + + public function mset($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi($mode = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($field, $key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function pconnect($host, $port = null, $timeout = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pconnect(...\func_get_args()); + } + + public function persist($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireAt($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireAt(...\func_get_args()); + } + + public function pfadd($key, $elements) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dstkey, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function pipeline() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pipeline(...\func_get_args()); + } + + public function psetex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function publish($channel, $message) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function rPop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPop(...\func_get_args()); + } + + public function rPush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPush(...\func_get_args()); + } + + public function rPushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPushx(...\func_get_args()); + } + + public function randomKey() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomKey(...\func_get_args()); + } + + public function rawcommand($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renameNx($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renameNx(...\func_get_args()); + } + + public function restore($ttl, $key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpoplpush($src, $dst) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function sAdd($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAdd(...\func_get_args()); + } + + public function sAddArray($key, $options) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAddArray(...\func_get_args()); + } + + public function sDiff($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiff(...\func_get_args()); + } + + public function sDiffStore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiffStore(...\func_get_args()); + } + + public function sInter($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInter(...\func_get_args()); + } + + public function sInterStore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInterStore(...\func_get_args()); + } + + public function sMembers($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMembers(...\func_get_args()); + } + + public function sMisMember($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMisMember(...\func_get_args()); + } + + public function sMove($src, $dst, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMove(...\func_get_args()); + } + + public function sPop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sPop(...\func_get_args()); + } + + public function sRandMember($key, $count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sRandMember(...\func_get_args()); + } + + public function sUnion($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnion(...\func_get_args()); + } + + public function sUnionStore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnionStore(...\func_get_args()); + } + + public function save() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($i_iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function select($dbindex) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->select(...\func_get_args()); + } + + public function set($key, $value, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setBit($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setBit(...\func_get_args()); + } + + public function setOption($option, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setOption(...\func_get_args()); + } + + public function setRange($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setRange(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function sismember($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function slaveof($host = null, $port = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slaveof(...\func_get_args()); + } + + public function slowlog($arg, $option = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function sort($key, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sortAsc($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAsc(...\func_get_args()); + } + + public function sortAscAlpha($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAscAlpha(...\func_get_args()); + } + + public function sortDesc($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDesc(...\func_get_args()); + } + + public function sortDescAlpha($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDescAlpha(...\func_get_args()); + } + + public function srem($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function swapdb($srcdb, $dstdb) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->swapdb(...\func_get_args()); + } + + public function time() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unlink($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unsubscribe($channel, ...$other_channels) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unwatch() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function wait($numslaves, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function xack($str_key, $str_group, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($str_key, $str_id, $arr_fields, $i_maxlen = null, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($str_key, $str_group, $str_consumer, $i_min_idle, $arr_ids, $arr_opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($str_key, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($str_operation, $str_key = null, $str_arg1 = null, $str_arg2 = null, $str_arg3 = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xinfo($str_cmd, $str_key = null, $str_group = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($str_key, $str_group, $str_start = null, $str_end = null, $i_count = null, $str_consumer = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($str_group, $str_consumer, $arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($str_key, $i_maxlen, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zAdd($key, $score, $value, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zAdd(...\func_get_args()); + } + + public function zCard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCard(...\func_get_args()); + } + + public function zCount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCount(...\func_get_args()); + } + + public function zIncrBy($key, $value, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zIncrBy(...\func_get_args()); + } + + public function zLexCount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zLexCount(...\func_get_args()); + } + + public function zPopMax($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMax(...\func_get_args()); + } + + public function zPopMin($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMin(...\func_get_args()); + } + + public function zRange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRange(...\func_get_args()); + } + + public function zRangeByLex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByLex(...\func_get_args()); + } + + public function zRangeByScore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByScore(...\func_get_args()); + } + + public function zRank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRank(...\func_get_args()); + } + + public function zRem($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRem(...\func_get_args()); + } + + public function zRemRangeByLex($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByLex(...\func_get_args()); + } + + public function zRemRangeByRank($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByRank(...\func_get_args()); + } + + public function zRemRangeByScore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByScore(...\func_get_args()); + } + + public function zRevRange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRange(...\func_get_args()); + } + + public function zRevRangeByLex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByLex(...\func_get_args()); + } + + public function zRevRangeByScore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByScore(...\func_get_args()); + } + + public function zRevRank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRank(...\func_get_args()); + } + + public function zScore($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zScore(...\func_get_args()); + } + + public function zinterstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zunionstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } + + public function delete($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->delete(...\func_get_args()); + } + + public function evaluate($script, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evaluate(...\func_get_args()); + } + + public function evaluateSha($script_sha, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evaluateSha(...\func_get_args()); + } + + public function getKeys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getKeys(...\func_get_args()); + } + + public function getMultiple($keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMultiple(...\func_get_args()); + } + + public function lGet($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lGet(...\func_get_args()); + } + + public function lGetRange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lGetRange(...\func_get_args()); + } + + public function lRemove($key, $value, $count) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lRemove(...\func_get_args()); + } + + public function lSize($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lSize(...\func_get_args()); + } + + public function listTrim($key, $start, $stop) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->listTrim(...\func_get_args()); + } + + public function open($host, $port = null, $timeout = null, $retry_interval = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->open(...\func_get_args()); + } + + public function popen($host, $port = null, $timeout = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->popen(...\func_get_args()); + } + + public function renameKey($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renameKey(...\func_get_args()); + } + + public function sContains($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sContains(...\func_get_args()); + } + + public function sGetMembers($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sGetMembers(...\func_get_args()); + } + + public function sRemove($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sRemove(...\func_get_args()); + } + + public function sSize($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sSize(...\func_get_args()); + } + + public function sendEcho($msg) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sendEcho(...\func_get_args()); + } + + public function setTimeout($key, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setTimeout(...\func_get_args()); + } + + public function substr($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->substr(...\func_get_args()); + } + + public function zDelete($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zDelete(...\func_get_args()); + } + + public function zDeleteRangeByRank($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zDeleteRangeByRank(...\func_get_args()); + } + + public function zDeleteRangeByScore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zDeleteRangeByScore(...\func_get_args()); + } + + public function zInter($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zInter(...\func_get_args()); + } + + public function zRemove($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemove(...\func_get_args()); + } + + public function zRemoveRangeByScore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemoveRangeByScore(...\func_get_args()); + } + + public function zReverseRange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zReverseRange(...\func_get_args()); + } + + public function zSize($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zSize(...\func_get_args()); + } + + public function zUnion($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zUnion(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/Redis6Proxy.php b/vendor/symfony/cache/Traits/Redis6Proxy.php new file mode 100644 index 0000000..c841d42 --- /dev/null +++ b/vendor/symfony/cache/Traits/Redis6Proxy.php @@ -0,0 +1,1269 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class Redis6Proxy extends \Redis implements ResetInterface, LazyObjectInterface +{ + use Redis6ProxyTrait; + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _compress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _prefix($key): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _serialize($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _pack($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function acl($subcmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $credentials): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->auth(...\func_get_args()); + } + + public function bgSave(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgSave(...\func_get_args()); + } + + public function bgrewriteaof(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $bybit = false): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $deskey, $srckey, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = 0, $end = -1, $bybit = false): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blPop(...\func_get_args()); + } + + public function brPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brPop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function bzPopMax($key, $timeout_or_key, ...$extra_args): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMax(...\func_get_args()); + } + + public function bzPopMin($key, $timeout_or_key, ...$extra_args): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzPopMin(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Redis|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmpop(...\func_get_args()); + } + + public function clearLastError(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearLastError(...\func_get_args()); + } + + public function client($opt, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function command($opt = null, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($operation, $key_or_settings = null, $value = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->connect(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->copy(...\func_get_args()); + } + + public function dbSize(): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbSize(...\func_get_args()); + } + + public function debug($key): \Redis|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->debug(...\func_get_args()); + } + + public function decr($key, $by = 1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrBy($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrBy(...\func_get_args()); + } + + public function del($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function delete($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->delete(...\func_get_args()); + } + + public function discard(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function echo($str): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function eval_ro($script_sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval_ro(...\func_get_args()); + } + + public function evalsha($sha1, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha1, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha_ro(...\func_get_args()); + } + + public function exec(): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys): \Redis|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function expire($key, $timeout, $mode = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireAt($key, $timestamp, $mode = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireAt(...\func_get_args()); + } + + public function failover($to = null, $abort = false, $timeout = 0): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->failover(...\func_get_args()); + } + + public function expiretime($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expiretime(...\func_get_args()); + } + + public function pexpiretime($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpiretime(...\func_get_args()); + } + + public function fcall($fn, $keys = [], $args = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall(...\func_get_args()); + } + + public function fcall_ro($fn, $keys = [], $args = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall_ro(...\func_get_args()); + } + + public function flushAll($sync = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushAll(...\func_get_args()); + } + + public function flushDB($sync = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushDB(...\func_get_args()); + } + + public function function($operation, ...$args): \Redis|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->function(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Redis|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getAuth(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getAuth(...\func_get_args()); + } + + public function getBit($key, $idx): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getBit(...\func_get_args()); + } + + public function getEx($key, $options = []): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getEx(...\func_get_args()); + } + + public function getDBNum(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDBNum(...\func_get_args()); + } + + public function getDel($key): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDel(...\func_get_args()); + } + + public function getHost(): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getHost(...\func_get_args()); + } + + public function getLastError(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getLastError(...\func_get_args()); + } + + public function getMode(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMode(...\func_get_args()); + } + + public function getOption($option): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getOption(...\func_get_args()); + } + + public function getPersistentID(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPersistentID(...\func_get_args()); + } + + public function getPort(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPort(...\func_get_args()); + } + + public function getRange($key, $start, $end): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getRange(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): \Redis|array|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lcs(...\func_get_args()); + } + + public function getReadTimeout(): float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getReadTimeout(...\func_get_args()); + } + + public function getset($key, $value): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTimeout(...\func_get_args()); + } + + public function getTransferredBytes(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTransferredBytes(...\func_get_args()); + } + + public function clearTransferredBytes(): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearTransferredBytes(...\func_get_args()); + } + + public function hDel($key, $field, ...$other_fields): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hDel(...\func_get_args()); + } + + public function hExists($key, $field): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hExists(...\func_get_args()); + } + + public function hGet($key, $member): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGet(...\func_get_args()); + } + + public function hGetAll($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hGetAll(...\func_get_args()); + } + + public function hIncrBy($key, $field, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrBy(...\func_get_args()); + } + + public function hIncrByFloat($key, $field, $value): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hIncrByFloat(...\func_get_args()); + } + + public function hKeys($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hKeys(...\func_get_args()); + } + + public function hLen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hLen(...\func_get_args()); + } + + public function hMget($key, $fields): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMget(...\func_get_args()); + } + + public function hMset($key, $fieldvals): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hMset(...\func_get_args()); + } + + public function hSetNx($key, $field, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSetNx(...\func_get_args()); + } + + public function hStrLen($key, $field): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hStrLen(...\func_get_args()); + } + + public function hVals($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hVals(...\func_get_args()); + } + + public function hscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function incr($key, $by = 1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrBy($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrBy(...\func_get_args()); + } + + public function incrByFloat($key, $value): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrByFloat(...\func_get_args()); + } + + public function info(...$sections): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function isConnected(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->isConnected(...\func_get_args()); + } + + public function keys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lInsert($key, $pos, $pivot, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lInsert(...\func_get_args()); + } + + public function lLen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lLen(...\func_get_args()); + } + + public function lMove($src, $dst, $wherefrom, $whereto): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lMove(...\func_get_args()); + } + + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmove(...\func_get_args()); + } + + public function lPop($key, $count = 0): \Redis|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPop(...\func_get_args()); + } + + public function lPos($key, $value, $options = null): \Redis|array|bool|int|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPos(...\func_get_args()); + } + + public function lPush($key, ...$elements): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPush(...\func_get_args()); + } + + public function rPush($key, ...$elements): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPush(...\func_get_args()); + } + + public function lPushx($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lPushx(...\func_get_args()); + } + + public function rPushx($key, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPushx(...\func_get_args()); + } + + public function lSet($key, $index, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lSet(...\func_get_args()); + } + + public function lastSave(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastSave(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function lrange($key, $start, $end): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count = 0): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->migrate(...\func_get_args()); + } + + public function move($key, $index): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->move(...\func_get_args()); + } + + public function mset($key_values): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($key_values): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi($value = \Redis::MULTI): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($subcommand, $key): \Redis|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function open($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->open(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pconnect(...\func_get_args()); + } + + public function persist($key): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timeout, $mode = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireAt($key, $timestamp, $mode = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireAt(...\func_get_args()); + } + + public function pfadd($key, $elements): \Redis|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key_or_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping($message = null): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function pipeline(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pipeline(...\func_get_args()); + } + + public function popen($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->popen(...\func_get_args()); + } + + public function psetex($key, $expire, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $cb): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function publish($channel, $message): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($command, $arg = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($patterns): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function rPop($key, $count = 0): \Redis|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rPop(...\func_get_args()); + } + + public function randomKey(): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomKey(...\func_get_args()); + } + + public function rawcommand($command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($old_name, $new_name): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renameNx($key_src, $key_dst): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renameNx(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpoplpush($srckey, $dstkey): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function sAdd($key, $value, ...$other_values): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAdd(...\func_get_args()); + } + + public function sAddArray($key, $values): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sAddArray(...\func_get_args()); + } + + public function sDiff($key, ...$other_keys): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiff(...\func_get_args()); + } + + public function sDiffStore($dst, $key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sDiffStore(...\func_get_args()); + } + + public function sInter($key, ...$other_keys): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sintercard(...\func_get_args()); + } + + public function sInterStore($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sInterStore(...\func_get_args()); + } + + public function sMembers($key): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMembers(...\func_get_args()); + } + + public function sMisMember($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMisMember(...\func_get_args()); + } + + public function sMove($src, $dst, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sMove(...\func_get_args()); + } + + public function sPop($key, $count = 0): \Redis|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sPop(...\func_get_args()); + } + + public function sUnion($key, ...$other_keys): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnion(...\func_get_args()); + } + + public function sUnionStore($dst, $key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sUnionStore(...\func_get_args()); + } + + public function save(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$iterator, $pattern = null, $count = 0, $type = null): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function select($db): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->select(...\func_get_args()); + } + + public function set($key, $value, $options = null): \Redis|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setBit($key, $idx, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setBit(...\func_get_args()); + } + + public function setRange($key, $index, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setRange(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setOption(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function sismember($key, $value): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function slaveof($host = null, $port = 6379): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slaveof(...\func_get_args()); + } + + public function replicaof($host = null, $port = 6379): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->replicaof(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->touch(...\func_get_args()); + } + + public function slowlog($operation, $length = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function sort($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort_ro(...\func_get_args()); + } + + public function sortAsc($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAsc(...\func_get_args()); + } + + public function sortAscAlpha($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortAscAlpha(...\func_get_args()); + } + + public function sortDesc($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDesc(...\func_get_args()); + } + + public function sortDescAlpha($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sortDescAlpha(...\func_get_args()); + } + + public function srem($key, $value, ...$other_values): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($key, &$iterator, $pattern = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function ssubscribe($channels, $cb): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ssubscribe(...\func_get_args()); + } + + public function strlen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $cb): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function sunsubscribe($channels): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunsubscribe(...\func_get_args()); + } + + public function swapdb($src, $dst): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->swapdb(...\func_get_args()); + } + + public function time(): \Redis|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unlink($key, ...$other_keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unsubscribe($channels): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unwatch(): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Redis|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function wait($numreplicas, $timeout): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->wait(...\func_get_args()); + } + + public function xack($key, $group, $ids): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xautoclaim(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($key, $ids): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Redis|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zAdd($key, $score_or_options, ...$more_scores_and_mems): \Redis|false|float|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zAdd(...\func_get_args()); + } + + public function zCard($key): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCard(...\func_get_args()); + } + + public function zCount($key, $start, $end): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zCount(...\func_get_args()); + } + + public function zIncrBy($key, $value, $member): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zIncrBy(...\func_get_args()); + } + + public function zLexCount($key, $min, $max): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zLexCount(...\func_get_args()); + } + + public function zMscore($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zMscore(...\func_get_args()); + } + + public function zPopMax($key, $count = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMax(...\func_get_args()); + } + + public function zPopMin($key, $count = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zPopMin(...\func_get_args()); + } + + public function zRange($key, $start, $end, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRange(...\func_get_args()); + } + + public function zRangeByLex($key, $min, $max, $offset = -1, $count = -1): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByLex(...\func_get_args()); + } + + public function zRangeByScore($key, $start, $end, $options = []): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRangeByScore(...\func_get_args()); + } + + public function zrangestore($dstkey, $srckey, $start, $end, $options = null): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangestore(...\func_get_args()); + } + + public function zRandMember($key, $options = null): \Redis|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRandMember(...\func_get_args()); + } + + public function zRank($key, $member): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRank(...\func_get_args()); + } + + public function zRem($key, $member, ...$other_members): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRem(...\func_get_args()); + } + + public function zRemRangeByLex($key, $min, $max): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByLex(...\func_get_args()); + } + + public function zRemRangeByRank($key, $start, $end): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByRank(...\func_get_args()); + } + + public function zRemRangeByScore($key, $start, $end): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRemRangeByScore(...\func_get_args()); + } + + public function zRevRange($key, $start, $end, $scores = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRange(...\func_get_args()); + } + + public function zRevRangeByLex($key, $max, $min, $offset = -1, $count = -1): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByLex(...\func_get_args()); + } + + public function zRevRangeByScore($key, $max, $min, $options = []): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRangeByScore(...\func_get_args()); + } + + public function zRevRank($key, $member): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zRevRank(...\func_get_args()); + } + + public function zScore($key, $member): \Redis|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zScore(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiffstore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $aggregate = null): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zunion($keys, $weights = null, $options = null): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $aggregate = null): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/Redis6ProxyTrait.php b/vendor/symfony/cache/Traits/Redis6ProxyTrait.php new file mode 100644 index 0000000..d339e56 --- /dev/null +++ b/vendor/symfony/cache/Traits/Redis6ProxyTrait.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +if (version_compare(phpversion('redis'), '6.1.0', '>=')) { + /** + * @internal + */ + trait Redis6ProxyTrait + { + public function dump($key): \Redis|string|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function hRandField($key, $options = null): \Redis|array|string|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hRandField(...\func_get_args()); + } + + public function hSet($key, $fields_and_vals): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSet(...\func_get_args()); + } + + public function mget($keys): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function sRandMember($key, $count = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sRandMember(...\func_get_args()); + } + + public function waitaof($numlocal, $numreplicas, $timeout): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->waitaof(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait Redis6ProxyTrait + { + public function dump($key): \Redis|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function hRandField($key, $options = null): \Redis|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hRandField(...\func_get_args()); + } + + public function hSet($key, $member, $value): \Redis|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hSet(...\func_get_args()); + } + + public function mget($keys): \Redis|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function sRandMember($key, $count = 0): \Redis|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sRandMember(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/RedisCluster5Proxy.php b/vendor/symfony/cache/Traits/RedisCluster5Proxy.php new file mode 100644 index 0000000..511c53d --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisCluster5Proxy.php @@ -0,0 +1,983 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RedisCluster5Proxy extends \RedisCluster implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($name, $seeds = null, $timeout = null, $read_timeout = null, $persistent = null, #[\SensitiveParameter] $auth = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _masters() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_masters(...\func_get_args()); + } + + public function _prefix($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _redir() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_redir(...\func_get_args()); + } + + public function _serialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _compress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _pack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function acl($key_or_address, $subcmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function bgrewriteaof($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bgsave($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgsave(...\func_get_args()); + } + + public function bitcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $ret_key, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function clearlasterror() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearlasterror(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmin(...\func_get_args()); + } + + public function client($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function cluster($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cluster(...\func_get_args()); + } + + public function command(...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function dbsize($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbsize(...\func_get_args()); + } + + public function decr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrby($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrby(...\func_get_args()); + } + + public function del($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function discard() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function dump($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function echo($msg) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function evalsha($script_sha, $args = null, $num_keys = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function exec() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function expire($key, $timeout) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireat($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireat(...\func_get_args()); + } + + public function flushall($key_or_address, $async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushall(...\func_get_args()); + } + + public function flushdb($key_or_address, $async = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushdb(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lan, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function get($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getbit($key, $offset) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getbit(...\func_get_args()); + } + + public function getlasterror() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getlasterror(...\func_get_args()); + } + + public function getmode() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getmode(...\func_get_args()); + } + + public function getoption($option) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getoption(...\func_get_args()); + } + + public function getrange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getrange(...\func_get_args()); + } + + public function getset($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function hdel($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hdel(...\func_get_args()); + } + + public function hexists($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexists(...\func_get_args()); + } + + public function hget($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hget(...\func_get_args()); + } + + public function hgetall($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetall(...\func_get_args()); + } + + public function hincrby($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrbyfloat(...\func_get_args()); + } + + public function hkeys($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hkeys(...\func_get_args()); + } + + public function hlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hlen(...\func_get_args()); + } + + public function hmget($key, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmget(...\func_get_args()); + } + + public function hmset($key, $pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmset(...\func_get_args()); + } + + public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function hset($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hset(...\func_get_args()); + } + + public function hsetnx($key, $member, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hsetnx(...\func_get_args()); + } + + public function hstrlen($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hstrlen(...\func_get_args()); + } + + public function hvals($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hvals(...\func_get_args()); + } + + public function incr($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrby($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrbyfloat(...\func_get_args()); + } + + public function info($key_or_address, $option = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function keys($pattern) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lastsave($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastsave(...\func_get_args()); + } + + public function lget($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lget(...\func_get_args()); + } + + public function lindex($key, $index) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function linsert($key, $position, $pivot, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->linsert(...\func_get_args()); + } + + public function llen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->llen(...\func_get_args()); + } + + public function lpop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpop(...\func_get_args()); + } + + public function lpush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpush(...\func_get_args()); + } + + public function lpushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpushx(...\func_get_args()); + } + + public function lrange($key, $start, $end) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function lset($key, $index, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lset(...\func_get_args()); + } + + public function ltrim($key, $start, $stop) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function mget($keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function mset($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($pairs) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($field, $key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function persist($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireat($key, $timestamp) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireat(...\func_get_args()); + } + + public function pfadd($key, $elements) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dstkey, $keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function psetex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function publish($channel, $message) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function randomkey($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomkey(...\func_get_args()); + } + + public function rawcommand($cmd, ...$args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renamenx(...\func_get_args()); + } + + public function restore($ttl, $key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpop(...\func_get_args()); + } + + public function rpoplpush($src, $dst) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function rpush($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpush(...\func_get_args()); + } + + public function rpushx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpushx(...\func_get_args()); + } + + public function sadd($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sadd(...\func_get_args()); + } + + public function saddarray($key, $options) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->saddarray(...\func_get_args()); + } + + public function save($key_or_address) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$i_iterator, $str_node, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($i_iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiff(...\func_get_args()); + } + + public function sdiffstore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiffstore(...\func_get_args()); + } + + public function set($key, $value, $opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setbit($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setbit(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function setoption($option, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setoption(...\func_get_args()); + } + + public function setrange($key, $offset, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setrange(...\func_get_args()); + } + + public function sinter($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinter(...\func_get_args()); + } + + public function sinterstore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinterstore(...\func_get_args()); + } + + public function sismember($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function slowlog($key_or_address, $arg = null, ...$other_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function smembers($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smembers(...\func_get_args()); + } + + public function smove($src, $dst, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smove(...\func_get_args()); + } + + public function sort($key, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function spop($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spop(...\func_get_args()); + } + + public function srandmember($key, $count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srandmember(...\func_get_args()); + } + + public function srem($key, $value) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $callback) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function sunion($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunion(...\func_get_args()); + } + + public function sunionstore($dst, $key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunionstore(...\func_get_args()); + } + + public function time() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unsubscribe($channel, ...$other_channels) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unlink($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unwatch() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function xack($str_key, $str_group, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($str_key, $str_id, $arr_fields, $i_maxlen = null, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($str_key, $str_group, $str_consumer, $i_min_idle, $arr_ids, $arr_opts = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($str_key, $arr_ids) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($str_operation, $str_key = null, $str_arg1 = null, $str_arg2 = null, $str_arg3 = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xinfo($str_cmd, $str_key = null, $str_group = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($str_key, $str_group, $str_start = null, $str_end = null, $i_count = null, $str_consumer = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($str_group, $str_consumer, $arr_streams, $i_count = null, $i_block = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($str_key, $str_start, $str_end, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($str_key, $i_maxlen, $boo_approximate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zadd($key, $score, $value, ...$extra_args) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zadd(...\func_get_args()); + } + + public function zcard($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcount(...\func_get_args()); + } + + public function zincrby($key, $value, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zincrby(...\func_get_args()); + } + + public function zinterstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zlexcount($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zlexcount(...\func_get_args()); + } + + public function zpopmax($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmax(...\func_get_args()); + } + + public function zpopmin($key) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmin(...\func_get_args()); + } + + public function zrange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrange(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebylex(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebyscore(...\func_get_args()); + } + + public function zrank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrank(...\func_get_args()); + } + + public function zrem($key, $member, ...$other_members) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyscore(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $scores = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrange(...\func_get_args()); + } + + public function zrevrangebylex($key, $min, $max, $offset = null, $limit = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebylex(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebyscore(...\func_get_args()); + } + + public function zrevrank($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrank(...\func_get_args()); + } + + public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscore($key, $member) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscore(...\func_get_args()); + } + + public function zunionstore($key, $keys, $weights = null, $aggregate = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/RedisCluster6Proxy.php b/vendor/symfony/cache/Traits/RedisCluster6Proxy.php new file mode 100644 index 0000000..c19aa16 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisCluster6Proxy.php @@ -0,0 +1,1139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RedisCluster6Proxy extends \RedisCluster implements ResetInterface, LazyObjectInterface +{ + use RedisCluster6ProxyTrait; + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($name, $seeds = null, $timeout = 0, $read_timeout = 0, $persistent = false, #[\SensitiveParameter] $auth = null, $context = null) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function _compress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _serialize($value): bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _pack($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function _prefix($key): bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function _masters(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_masters(...\func_get_args()); + } + + public function _redir(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_redir(...\func_get_args()); + } + + public function acl($key_or_address, $subcmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function bgrewriteaof($key_or_address): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function bgsave($key_or_address): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgsave(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $bybit = false): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitop($operation, $deskey, $srckey, ...$otherkeys): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = 0, $end = -1, $bybit = false): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpop(...\func_get_args()); + } + + public function brpoplpush($srckey, $deskey, $timeout): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function lmove($src, $dst, $wherefrom, $whereto): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmove(...\func_get_args()); + } + + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmove(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmin(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \RedisCluster|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmpop(...\func_get_args()); + } + + public function clearlasterror(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearlasterror(...\func_get_args()); + } + + public function client($key_or_address, $subcommand, $arg = null): array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function close(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function cluster($key_or_address, $command, ...$extra_args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cluster(...\func_get_args()); + } + + public function command(...$extra_args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function config($key_or_address, $subcommand, ...$extra_args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function dbsize($key_or_address): \RedisCluster|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbsize(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->copy(...\func_get_args()); + } + + public function decr($key, $by = 1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function decrby($key, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrby(...\func_get_args()); + } + + public function decrbyfloat($key, $value): float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrbyfloat(...\func_get_args()); + } + + public function del($key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function discard(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function dump($key): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function echo($key_or_address, $msg): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval_ro(...\func_get_args()); + } + + public function evalsha($script_sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function evalsha_ro($script_sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha_ro(...\func_get_args()); + } + + public function exec(): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function touch($key, ...$other_keys): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->touch(...\func_get_args()); + } + + public function expire($key, $timeout, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function expireat($key, $timestamp, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireat(...\func_get_args()); + } + + public function expiretime($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expiretime(...\func_get_args()); + } + + public function pexpiretime($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpiretime(...\func_get_args()); + } + + public function flushall($key_or_address, $async = false): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushall(...\func_get_args()); + } + + public function flushdb($key_or_address, $async = false): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushdb(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dest, $unit = null): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): \RedisCluster|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \RedisCluster|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getbit($key, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getbit(...\func_get_args()); + } + + public function getlasterror(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getlasterror(...\func_get_args()); + } + + public function getmode(): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getmode(...\func_get_args()); + } + + public function getoption($option): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getoption(...\func_get_args()); + } + + public function getrange($key, $start, $end): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getrange(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): \RedisCluster|array|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lcs(...\func_get_args()); + } + + public function getset($key, $value): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function gettransferredbytes(): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->gettransferredbytes(...\func_get_args()); + } + + public function cleartransferredbytes(): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cleartransferredbytes(...\func_get_args()); + } + + public function hdel($key, $member, ...$other_members): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hdel(...\func_get_args()); + } + + public function hexists($key, $member): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexists(...\func_get_args()); + } + + public function hget($key, $member): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hget(...\func_get_args()); + } + + public function hgetall($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetall(...\func_get_args()); + } + + public function hincrby($key, $member, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $member, $value): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrbyfloat(...\func_get_args()); + } + + public function hkeys($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hkeys(...\func_get_args()); + } + + public function hlen($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hlen(...\func_get_args()); + } + + public function hmget($key, $keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmget(...\func_get_args()); + } + + public function hmset($key, $key_values): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmset(...\func_get_args()); + } + + public function hscan($key, &$iterator, $pattern = null, $count = 0): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function hrandfield($key, $options = null): \RedisCluster|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hrandfield(...\func_get_args()); + } + + public function hset($key, $member, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hset(...\func_get_args()); + } + + public function hsetnx($key, $member, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hsetnx(...\func_get_args()); + } + + public function hstrlen($key, $field): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hstrlen(...\func_get_args()); + } + + public function hvals($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hvals(...\func_get_args()); + } + + public function incr($key, $by = 1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function incrby($key, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrbyfloat(...\func_get_args()); + } + + public function info($key_or_address, ...$sections): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function keys($pattern): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function lastsave($key_or_address): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastsave(...\func_get_args()); + } + + public function lget($key, $index): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lget(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function linsert($key, $pos, $pivot, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->linsert(...\func_get_args()); + } + + public function llen($key): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->llen(...\func_get_args()); + } + + public function lpop($key, $count = 0): \RedisCluster|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Redis|array|bool|int|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpos(...\func_get_args()); + } + + public function lpush($key, $value, ...$other_values): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpush(...\func_get_args()); + } + + public function lpushx($key, $value): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpushx(...\func_get_args()); + } + + public function lrange($key, $start, $end): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count = 0): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function lset($key, $index, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lset(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function mget($keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function mset($key_values): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($key_values): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function multi($value = \Redis::MULTI): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function object($subcommand, $key): \RedisCluster|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function persist($key): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function pexpire($key, $timeout, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function pexpireat($key, $timestamp, $mode = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireat(...\func_get_args()); + } + + public function pfadd($key, $elements): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($key, $keys): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function ping($key_or_address, $message = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function psetex($key, $timeout, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function pttl($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function pubsub($key_or_address, ...$values): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function randomkey($key_or_address): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomkey(...\func_get_args()); + } + + public function rawcommand($key_or_address, $command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawcommand(...\func_get_args()); + } + + public function rename($key_src, $key_dst): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renamenx(...\func_get_args()); + } + + public function restore($key, $timeout, $value, $options = null): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function role($key_or_address): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function rpop($key, $count = 0): \RedisCluster|array|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpop(...\func_get_args()); + } + + public function rpoplpush($src, $dst): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function rpush($key, ...$elements): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpush(...\func_get_args()); + } + + public function rpushx($key, $value): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpushx(...\func_get_args()); + } + + public function sadd($key, $value, ...$other_values): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sadd(...\func_get_args()); + } + + public function saddarray($key, $values): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->saddarray(...\func_get_args()); + } + + public function save($key_or_address): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function scan(&$iterator, $key_or_address, $pattern = null, $count = 0): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($key_or_address, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiff(...\func_get_args()); + } + + public function sdiffstore($dst, $key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiffstore(...\func_get_args()); + } + + public function set($key, $value, $options = null): \RedisCluster|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function setbit($key, $offset, $onoff): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setbit(...\func_get_args()); + } + + public function setex($key, $expire, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function setnx($key, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function setoption($option, $value): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setoption(...\func_get_args()); + } + + public function setrange($key, $offset, $value): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setrange(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinterstore(...\func_get_args()); + } + + public function sismember($key, $value): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function smismember($key, $member, ...$other_members): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smismember(...\func_get_args()); + } + + public function slowlog($key_or_address, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function smembers($key): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smembers(...\func_get_args()); + } + + public function smove($src, $dst, $member): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smove(...\func_get_args()); + } + + public function sort($key, $options = null): \RedisCluster|array|bool|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = null): \RedisCluster|array|bool|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort_ro(...\func_get_args()); + } + + public function spop($key, $count = 0): \RedisCluster|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spop(...\func_get_args()); + } + + public function srandmember($key, $count = 0): \RedisCluster|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srandmember(...\func_get_args()); + } + + public function srem($key, $value, ...$other_values): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sscan($key, &$iterator, $pattern = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function subscribe($channels, $cb): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunion(...\func_get_args()); + } + + public function sunionstore($dst, $key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunionstore(...\func_get_args()); + } + + public function time($key_or_address): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function ttl($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function type($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function unsubscribe($channels): array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function unlink($key, ...$other_keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function unwatch(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function xack($key, $group, $ids): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false): \RedisCluster|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_iddle, $ids, $options): \RedisCluster|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xdel($key, $ids): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xautoclaim(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xlen($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xrevrange($key, $start, $end, $count = -1): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xtrim($key, $maxlen, $approx = false, $minid = false, $limit = -1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zadd($key, $score_or_options, ...$more_scores_and_mems): \RedisCluster|false|float|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zadd(...\func_get_args()); + } + + public function zcard($key): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcard(...\func_get_args()); + } + + public function zcount($key, $start, $end): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcount(...\func_get_args()); + } + + public function zincrby($key, $value, $member): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zincrby(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $aggregate = null): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zintercard(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zlexcount(...\func_get_args()); + } + + public function zpopmax($key, $value = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmax(...\func_get_args()); + } + + public function zpopmin($key, $value = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmin(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrange(...\func_get_args()); + } + + public function zrangestore($dstkey, $srckey, $start, $end, $options = null): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangestore(...\func_get_args()); + } + + public function zrandmember($key, $options = null): \RedisCluster|array|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrandmember(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebylex(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = []): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebyscore(...\func_get_args()); + } + + public function zrank($key, $member): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrank(...\func_get_args()); + } + + public function zrem($key, $value, ...$other_values): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyscore(...\func_get_args()); + } + + public function zrevrange($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrange(...\func_get_args()); + } + + public function zrevrangebylex($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebylex(...\func_get_args()); + } + + public function zrevrangebyscore($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebyscore(...\func_get_args()); + } + + public function zrevrank($key, $member): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrank(...\func_get_args()); + } + + public function zscan($key, &$iterator, $pattern = null, $count = 0): \RedisCluster|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscore($key, $member): \RedisCluster|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscore(...\func_get_args()); + } + + public function zmscore($key, $member, ...$other_members): \Redis|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmscore(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $aggregate = null): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinter(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \RedisCluster|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiffstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunion(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiff(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/RedisCluster6ProxyTrait.php b/vendor/symfony/cache/Traits/RedisCluster6ProxyTrait.php new file mode 100644 index 0000000..7addffb --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisCluster6ProxyTrait.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +if (version_compare(phpversion('redis'), '6.1.0', '>')) { + /** + * @internal + */ + trait RedisCluster6ProxyTrait + { + public function getex($key, $options = []): \RedisCluster|string|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getex(...\func_get_args()); + } + + public function publish($channel, $message): \RedisCluster|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function waitaof($key_or_address, $numlocal, $numreplicas, $timeout): \RedisCluster|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->waitaof(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait RedisCluster6ProxyTrait + { + public function publish($channel, $message): \RedisCluster|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php b/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php new file mode 100644 index 0000000..f5c0baa --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * This file acts as a wrapper to the \RedisCluster implementation so it can accept the same type of calls as + * individual \Redis objects. + * + * Calls are made to individual nodes via: RedisCluster->{method}($host, ...args)' + * according to https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#directed-node-commands + * + * @author Jack Thomas + * + * @internal + */ +class RedisClusterNodeProxy +{ + public function __construct( + private array $host, + private \RedisCluster $redis, + ) { + } + + public function __call(string $method, array $args) + { + return $this->redis->{$method}($this->host, ...$args); + } + + public function scan(&$iIterator, $strPattern = null, $iCount = null) + { + return $this->redis->scan($iIterator, $this->host, $strPattern, $iCount); + } + + public function getOption($name) + { + return $this->redis->getOption($name); + } +} diff --git a/vendor/symfony/cache/Traits/RedisClusterProxy.php b/vendor/symfony/cache/Traits/RedisClusterProxy.php new file mode 100644 index 0000000..c67d534 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisClusterProxy.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +class_alias(6.0 <= (float) phpversion('redis') ? RedisCluster6Proxy::class : RedisCluster5Proxy::class, RedisClusterProxy::class); + +if (false) { + /** + * @internal + */ + class RedisClusterProxy extends \RedisCluster + { + } +} diff --git a/vendor/symfony/cache/Traits/RedisProxy.php b/vendor/symfony/cache/Traits/RedisProxy.php new file mode 100644 index 0000000..7f4537b --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisProxy.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +class_alias(6.0 <= (float) phpversion('redis') ? Redis6Proxy::class : Redis5Proxy::class, RedisProxy::class); + +if (false) { + /** + * @internal + */ + class RedisProxy extends \Redis + { + } +} diff --git a/vendor/symfony/cache/Traits/RedisTrait.php b/vendor/symfony/cache/Traits/RedisTrait.php new file mode 100644 index 0000000..a4fdc5d --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisTrait.php @@ -0,0 +1,682 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Predis\Command\Redis\UNLINK; +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\RedisCluster; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Connection\Cluster\ClusterInterface as Predis2ClusterInterface; +use Predis\Connection\Cluster\RedisCluster as Predis2RedisCluster; +use Predis\Response\ErrorInterface; +use Predis\Response\Status; +use Relay\Relay; +use Relay\Sentinel; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Aurimas Niekis + * @author Nicolas Grekas + * + * @internal + */ +trait RedisTrait +{ + private static array $defaultConnectionOptions = [ + 'class' => null, + 'persistent' => 0, + 'persistent_id' => null, + 'timeout' => 30, + 'read_timeout' => 0, + 'retry_interval' => 0, + 'tcp_keepalive' => 0, + 'lazy' => null, + 'redis_cluster' => false, + 'redis_sentinel' => null, + 'dbindex' => 0, + 'failover' => 'none', + 'ssl' => null, // see https://php.net/context.ssl + ]; + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; + private MarshallerInterface $marshaller; + + private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller): void + { + parent::__construct($namespace, $defaultLifetime); + + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($redis instanceof \Predis\ClientInterface && $redis->getOptions()->exceptions) { + $options = clone $redis->getOptions(); + \Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)(); + $redis = new $redis($redis->getConnection(), $options); + } + + $this->redis = $redis; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * Creates a Redis connection using a DSN configuration. + * + * Example DSN: + * - redis://localhost + * - redis://example.com:1234 + * - redis://secret@example.com/13 + * - redis:///var/run/redis.sock + * - redis://secret@/var/run/redis.sock/13 + * + * @param array $options See self::$defaultConnectionOptions + * + * @throws InvalidArgumentException when the DSN is invalid + */ + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay + { + if (str_starts_with($dsn, 'redis:')) { + $scheme = 'redis'; + } elseif (str_starts_with($dsn, 'rediss:')) { + $scheme = 'rediss'; + } else { + throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:".'); + } + + if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) { + throw new CacheException('Cannot find the "redis" extension nor the "predis/predis" package.'); + } + + $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:(?[^:@]*+):)?(?[^@]*+)@)?#', function ($m) use (&$auth) { + if (isset($m['password'])) { + if (\in_array($m['user'], ['', 'default'], true)) { + $auth = rawurldecode($m['password']); + } else { + $auth = [rawurldecode($m['user']), rawurldecode($m['password'])]; + } + + if ('' === $auth) { + $auth = null; + } + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException('Invalid Redis DSN.'); + } + + $query = $hosts = []; + + $tls = 'rediss' === $scheme; + $tcpScheme = $tls ? 'tls' : 'tcp'; + + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException('Invalid Redis DSN: query parameter "host" must be an array.'); + } + foreach ($hosts as $host => $parameters) { + if (\is_string($parameters)) { + parse_str($parameters, $parameters); + } + if (false === $i = strrpos($host, ':')) { + $hosts[$host] = ['scheme' => $tcpScheme, 'host' => $host, 'port' => 6379] + $parameters; + } elseif ($port = (int) substr($host, 1 + $i)) { + $hosts[$host] = ['scheme' => $tcpScheme, 'host' => substr($host, 0, $i), 'port' => $port] + $parameters; + } else { + $hosts[$host] = ['scheme' => 'unix', 'path' => substr($host, 0, $i)] + $parameters; + } + } + $hosts = array_values($hosts); + } + } + + if (isset($params['host']) || isset($params['path'])) { + if (!isset($params['dbindex']) && isset($params['path'])) { + if (preg_match('#/(\d+)?$#', $params['path'], $m)) { + $params['dbindex'] = $m[1] ?? $query['dbindex'] ?? '0'; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } elseif (isset($params['host'])) { + throw new InvalidArgumentException('Invalid Redis DSN: parameter "dbindex" must be a number.'); + } + } + + if (isset($params['host'])) { + array_unshift($hosts, ['scheme' => $tcpScheme, 'host' => $params['host'], 'port' => $params['port'] ?? 6379]); + } else { + array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]); + } + } + + if (!$hosts) { + throw new InvalidArgumentException('Invalid Redis DSN: missing host.'); + } + + if (isset($params['dbindex'], $query['dbindex']) && $params['dbindex'] !== $query['dbindex']) { + throw new InvalidArgumentException('Invalid Redis DSN: path and query "dbindex" parameters mismatch.'); + } + + $params += $query + $options + self::$defaultConnectionOptions; + + if (isset($params['redis_sentinel']) && isset($params['sentinel_master'])) { + throw new InvalidArgumentException('Cannot use both "redis_sentinel" and "sentinel_master" at the same time.'); + } + + $params['redis_sentinel'] ??= $params['sentinel_master'] ?? null; + + if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".'); + } + + if (isset($params['lazy'])) { + $params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); + } + $params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN); + + if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { + throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); + } + + $class = $params['class'] ?? match (true) { + $params['redis_cluster'] => \extension_loaded('redis') ? \RedisCluster::class : \Predis\Client::class, + isset($params['redis_sentinel']) => match (true) { + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }, + 1 < \count($hosts) && \extension_loaded('redis') => 1 < \count($hosts) ? \RedisArray::class : \Redis::class, + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }; + + if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and neither ext-redis >= 5.2 nor ext-relay have been found.', $class)); + } + + $isRedisExt = is_a($class, \Redis::class, true); + $isRelayExt = !$isRedisExt && is_a($class, Relay::class, true); + + if ($isRedisExt || $isRelayExt) { + $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; + + $initializer = static function () use ($class, $isRedisExt, $connect, $params, $auth, $hosts, $tls) { + $sentinelClass = $isRedisExt ? \RedisSentinel::class : Sentinel::class; + $redis = new $class(); + $hostIndex = 0; + do { + $host = $hosts[$hostIndex]['host'] ?? $hosts[$hostIndex]['path']; + $port = $hosts[$hostIndex]['port'] ?? 0; + $passAuth = isset($params['auth']) && (!$isRedisExt || \defined('Redis::OPT_NULL_MULTIBULK_AS_NULL')); + $address = false; + + if (isset($hosts[$hostIndex]['host']) && $tls) { + $host = 'tls://'.$host; + } + + if (!isset($params['redis_sentinel'])) { + break; + } + + try { + if (version_compare(phpversion('redis'), '6.0.0', '>=') && $isRedisExt) { + $options = [ + 'host' => $host, + 'port' => $port, + 'connectTimeout' => $params['timeout'], + 'persistent' => $params['persistent_id'], + 'retryInterval' => $params['retry_interval'], + 'readTimeout' => $params['read_timeout'], + ]; + + if ($passAuth) { + $options['auth'] = $params['auth']; + } + + $sentinel = new \RedisSentinel($options); + } else { + $extra = $passAuth ? [$params['auth']] : []; + + $sentinel = new $sentinelClass($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); + } + + if ($address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { + [$host, $port] = $address; + } + } catch (\RedisException|\Relay\Exception $redisException) { + } + } while (++$hostIndex < \count($hosts) && !$address); + + if (isset($params['redis_sentinel']) && !$address) { + throw new InvalidArgumentException(sprintf('Failed to retrieve master information from sentinel "%s".', $params['redis_sentinel']), previous: $redisException ?? null); + } + + try { + $extra = [ + 'stream' => $params['ssl'] ?? null, + ]; + $booleanStreamOptions = [ + 'allow_self_signed', + 'capture_peer_cert', + 'capture_peer_cert_chain', + 'disable_compression', + 'SNI_enabled', + 'verify_peer', + 'verify_peer_name', + ]; + + foreach ($extra['stream'] ?? [] as $streamOption => $value) { + if (\in_array($streamOption, $booleanStreamOptions, true) && \is_string($value)) { + $extra['stream'][$streamOption] = filter_var($value, \FILTER_VALIDATE_BOOL); + } + } + + if (isset($params['auth'])) { + $extra['auth'] = $params['auth']; + } + @$redis->{$connect}($host, $port, (float) $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') || !$isRedisExt ? [$extra] : []); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $isConnected = $redis->isConnected(); + } finally { + restore_error_handler(); + } + if (!$isConnected) { + $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error ?? $redis->getLastError() ?? '', $error) ? sprintf(' (%s)', $error[1]) : ''; + throw new InvalidArgumentException('Redis connection failed: '.$error.'.'); + } + + if ((null !== $auth && !$redis->auth($auth)) + // Due to a bug in phpredis we must always select the dbindex if persistent pooling is enabled + // @see https://github.com/phpredis/phpredis/issues/1920 + // @see https://github.com/symfony/symfony/issues/51578 + || (($params['dbindex'] || ('pconnect' === $connect && '0' !== \ini_get('redis.pconnect.pooling_enabled'))) && !$redis->select($params['dbindex'])) + ) { + $e = preg_replace('/^ERR /', '', $redis->getLastError()); + throw new InvalidArgumentException('Redis connection failed: '.$e.'.'); + } + + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + } catch (\RedisException|\Relay\Exception $e) { + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); + } + + return $redis; + }; + + if ($params['lazy']) { + $redis = $isRedisExt ? RedisProxy::createLazyProxy($initializer) : RelayProxy::createLazyProxy($initializer); + } else { + $redis = $initializer(); + } + } elseif (is_a($class, \RedisArray::class, true)) { + foreach ($hosts as $i => $host) { + $hosts[$i] = match ($host['scheme']) { + 'tcp' => $host['host'].':'.$host['port'], + 'tls' => 'tls://'.$host['host'].':'.$host['port'], + default => $host['path'], + }; + } + $params['lazy_connect'] = $params['lazy'] ?? true; + $params['connect_timeout'] = $params['timeout']; + + try { + $redis = new $class($hosts, $params); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); + } + + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + } elseif (is_a($class, \RedisCluster::class, true)) { + $initializer = static function () use ($isRedisExt, $class, $params, $hosts) { + foreach ($hosts as $i => $host) { + $hosts[$i] = match ($host['scheme']) { + 'tcp' => $host['host'].':'.$host['port'], + 'tls' => 'tls://'.$host['host'].':'.$host['port'], + default => $host['path'], + }; + } + + try { + $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); + } + + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, match ($params['failover']) { + 'error' => \RedisCluster::FAILOVER_ERROR, + 'distribute' => \RedisCluster::FAILOVER_DISTRIBUTE, + 'slaves' => \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES, + 'none' => \RedisCluster::FAILOVER_NONE, + }); + + return $redis; + }; + + $redis = $params['lazy'] ? RedisClusterProxy::createLazyProxy($initializer) : $initializer(); + } elseif (is_a($class, \Predis\ClientInterface::class, true)) { + if ($params['redis_cluster']) { + $params['cluster'] = 'redis'; + } elseif (isset($params['redis_sentinel'])) { + $params['replication'] = 'sentinel'; + $params['service'] = $params['redis_sentinel']; + } + $params += ['parameters' => []]; + $params['parameters'] += [ + 'persistent' => $params['persistent'], + 'timeout' => $params['timeout'], + 'read_write_timeout' => $params['read_timeout'], + 'tcp_nodelay' => true, + ]; + if ($params['dbindex']) { + $params['parameters']['database'] = $params['dbindex']; + } + if (null !== $auth) { + if (\is_array($auth)) { + // ACL + $params['parameters']['username'] = $auth[0]; + $params['parameters']['password'] = $auth[1]; + } else { + $params['parameters']['password'] = $auth; + } + } + + if (isset($params['ssl'])) { + foreach ($hosts as $i => $host) { + $hosts[$i]['ssl'] ??= $params['ssl']; + } + } + + if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) { + $hosts = $hosts[0]; + } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) { + $params['replication'] = true; + $hosts[0] += ['alias' => 'master']; + } + $params['exceptions'] = false; + + $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions)); + if (isset($params['redis_sentinel'])) { + $redis->getConnection()->setSentinelTimeout($params['timeout']); + } + } elseif (class_exists($class, false)) { + throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster", "Relay\Relay" nor "Predis\ClientInterface".', $class)); + } else { + throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return $redis; + } + + protected function doFetch(array $ids): iterable + { + if (!$ids) { + return []; + } + + $result = []; + + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { + $values = $this->pipeline(function () use ($ids) { + foreach ($ids as $id) { + yield 'get' => [$id]; + } + }); + } else { + $values = $this->redis->mget($ids); + + if (!\is_array($values) || \count($values) !== \count($ids)) { + return []; + } + + $values = array_combine($ids, $values); + } + + foreach ($values as $id => $v) { + if ($v) { + $result[$id] = $this->marshaller->unmarshall($v); + } + } + + return $result; + } + + protected function doHave(string $id): bool + { + return (bool) $this->redis->exists($id); + } + + protected function doClear(string $namespace): bool + { + if ($this->redis instanceof \Predis\ClientInterface) { + $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; + $prefixLen = \strlen($prefix ?? ''); + } + + $cleared = true; + $hosts = $this->getHosts(); + $host = reset($hosts); + if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) { + // Predis supports info command only on the master in replication environments + $hosts = [$host->getClientFor('master')]; + } + + foreach ($hosts as $host) { + if (!isset($namespace[0])) { + $cleared = $host->flushDb() && $cleared; + continue; + } + + $info = $host->info('Server'); + $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0']; + + if ($host instanceof Relay) { + $prefix = Relay::SCAN_PREFIX & $host->getOption(Relay::OPT_SCAN) ? '' : $host->getOption(Relay::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(Relay::OPT_PREFIX) ?? ''); + } elseif (!$host instanceof \Predis\ClientInterface) { + $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); + } + $pattern = $prefix.$namespace.'*'; + + if (!version_compare($info['redis_version'], '2.8', '>=')) { + // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS + // can hang your server when it is executed against large databases (millions of items). + // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above. + $unlink = version_compare($info['redis_version'], '4.0', '>=') ? 'UNLINK' : 'DEL'; + $args = $this->redis instanceof \Predis\ClientInterface ? [0, $pattern] : [[$pattern], 0]; + $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]) for i=1,#keys,5000 do redis.call('$unlink',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $args[0], $args[1]) && $cleared; + continue; + } + + $cursor = null; + do { + $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor, 'MATCH', $pattern, 'COUNT', 1000) : $host->scan($cursor, $pattern, 1000); + if (isset($keys[1]) && \is_array($keys[1])) { + $cursor = $keys[0]; + $keys = $keys[1]; + } + if ($keys) { + if ($prefixLen) { + foreach ($keys as $i => $key) { + $keys[$i] = substr($key, $prefixLen); + } + } + $this->doDelete($keys); + } + } while ($cursor); + } + + return $cleared; + } + + protected function doDelete(array $ids): bool + { + if (!$ids) { + return true; + } + + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { + static $del; + $del ??= (class_exists(UNLINK::class) ? 'unlink' : 'del'); + + $this->pipeline(function () use ($ids, $del) { + foreach ($ids as $id) { + yield $del => [$id]; + } + })->rewind(); + } else { + static $unlink = true; + + if ($unlink) { + try { + $unlink = false !== $this->redis->unlink($ids); + } catch (\Throwable) { + $unlink = false; + } + } + + if (!$unlink) { + $this->redis->del($ids); + } + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $results = $this->pipeline(function () use ($values, $lifetime) { + foreach ($values as $id => $value) { + if (0 >= $lifetime) { + yield 'set' => [$id, $value]; + } else { + yield 'setEx' => [$id, $lifetime, $value]; + } + } + }); + + foreach ($results as $id => $result) { + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + private function pipeline(\Closure $generator, ?object $redis = null): \Generator + { + $ids = []; + $redis ??= $this->redis; + + if ($redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && ($redis->getConnection() instanceof RedisCluster || $redis->getConnection() instanceof Predis2RedisCluster))) { + // phpredis & predis don't support pipelining with RedisCluster + // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining + // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 + $results = []; + foreach ($generator() as $command => $args) { + $results[] = $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0]; + } + } elseif ($redis instanceof \Predis\ClientInterface) { + $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) { + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[2] : $args[0]; + } + }); + } elseif ($redis instanceof \RedisArray) { + $connections = $results = $ids = []; + foreach ($generator() as $command => $args) { + $id = 'eval' === $command ? $args[1][0] : $args[0]; + if (!isset($connections[$h = $redis->_target($id)])) { + $connections[$h] = [$redis->_instance($h), -1]; + $connections[$h][0]->multi(\Redis::PIPELINE); + } + $connections[$h][0]->{$command}(...$args); + $results[] = [$h, ++$connections[$h][1]]; + $ids[] = $id; + } + foreach ($connections as $h => $c) { + $connections[$h] = $c[0]->exec(); + } + foreach ($results as $k => [$h, $c]) { + $results[$k] = $connections[$h][$c]; + } + } else { + $redis->multi($redis instanceof Relay ? Relay::PIPELINE : \Redis::PIPELINE); + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; + } + $results = $redis->exec(); + } + + if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { + $e = $redis instanceof Relay ? new \Relay\Exception($redis->getLastError()) : new \RedisException($redis->getLastError()); + $results = array_map(fn ($v) => false === $v ? $e : $v, (array) $results); + } + + if (\is_bool($results)) { + return; + } + + foreach ($ids as $k => $id) { + yield $id => $results[$k]; + } + } + + private function getHosts(): array + { + $hosts = [$this->redis]; + if ($this->redis instanceof \Predis\ClientInterface) { + $connection = $this->redis->getConnection(); + if (($connection instanceof ClusterInterface || $connection instanceof Predis2ClusterInterface) && $connection instanceof \Traversable) { + $hosts = []; + foreach ($connection as $c) { + $hosts[] = new \Predis\Client($c); + } + } + } elseif ($this->redis instanceof \RedisArray) { + $hosts = []; + foreach ($this->redis->_hosts() as $host) { + $hosts[] = $this->redis->_instance($host); + } + } elseif ($this->redis instanceof \RedisCluster) { + $hosts = []; + foreach ($this->redis->_masters() as $host) { + $hosts[] = new RedisClusterNodeProxy($host, $this->redis); + } + } + + return $hosts; + } +} diff --git a/vendor/symfony/cache/Traits/RelayProxy.php b/vendor/symfony/cache/Traits/RelayProxy.php new file mode 100644 index 0000000..96d7d19 --- /dev/null +++ b/vendor/symfony/cache/Traits/RelayProxy.php @@ -0,0 +1,1319 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RelayProxy extends \Relay\Relay implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + use RelayProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function __construct($host = null, $port = 6379, $connect_timeout = 0.0, $command_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0) + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->__construct(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->connect(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pconnect(...\func_get_args()); + } + + public function close(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->close(...\func_get_args()); + } + + public function pclose(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pclose(...\func_get_args()); + } + + public function listen($callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->listen(...\func_get_args()); + } + + public function onFlushed($callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->onFlushed(...\func_get_args()); + } + + public function onInvalidated($callback, $pattern = null): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->onInvalidated(...\func_get_args()); + } + + public function dispatchEvents(): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dispatchEvents(...\func_get_args()); + } + + public function getOption($option): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getOption(...\func_get_args()); + } + + public function option($option, $value = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->option(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setOption(...\func_get_args()); + } + + public function addIgnorePatterns(...$pattern): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->addIgnorePatterns(...\func_get_args()); + } + + public function addAllowPatterns(...$pattern): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->addAllowPatterns(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getTimeout(...\func_get_args()); + } + + public function timeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->timeout(...\func_get_args()); + } + + public function getReadTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getReadTimeout(...\func_get_args()); + } + + public function readTimeout(): false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->readTimeout(...\func_get_args()); + } + + public function getBytes(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getBytes(...\func_get_args()); + } + + public function bytes(): array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bytes(...\func_get_args()); + } + + public function getHost(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getHost(...\func_get_args()); + } + + public function isConnected(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->isConnected(...\func_get_args()); + } + + public function getPort(): false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPort(...\func_get_args()); + } + + public function getAuth(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getAuth(...\func_get_args()); + } + + public function getDbNum(): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getDbNum(...\func_get_args()); + } + + public function _serialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unserialize(...\func_get_args()); + } + + public function _compress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_uncompress(...\func_get_args()); + } + + public function _pack($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_unpack(...\func_get_args()); + } + + public function _prefix($value): string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_prefix(...\func_get_args()); + } + + public function getLastError(): ?string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getLastError(...\func_get_args()); + } + + public function clearLastError(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearLastError(...\func_get_args()); + } + + public function endpointId(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->endpointId(...\func_get_args()); + } + + public function getPersistentID(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getPersistentID(...\func_get_args()); + } + + public function socketId(): false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->socketId(...\func_get_args()); + } + + public function rawCommand($cmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rawCommand(...\func_get_args()); + } + + public function select($db): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->select(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $auth): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->auth(...\func_get_args()); + } + + public function info(...$sections): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->info(...\func_get_args()); + } + + public function flushdb($sync = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushdb(...\func_get_args()); + } + + public function flushall($sync = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->flushall(...\func_get_args()); + } + + public function fcall($name, $keys = [], $argv = [], $handler = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall(...\func_get_args()); + } + + public function fcall_ro($name, $keys = [], $argv = [], $handler = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->fcall_ro(...\func_get_args()); + } + + public function function($op, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->function(...\func_get_args()); + } + + public function dbsize(): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dbsize(...\func_get_args()); + } + + public function dump($key): \Relay\Relay|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args()); + } + + public function replicaof($host = null, $port = 0): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->replicaof(...\func_get_args()); + } + + public function waitaof($numlocal, $numremote, $timeout): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->waitaof(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->restore(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->migrate(...\func_get_args()); + } + + public function echo($arg): \Relay\Relay|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args()); + } + + public function ping($arg = null): \Relay\Relay|bool|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ping(...\func_get_args()); + } + + public function idleTime(): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->idleTime(...\func_get_args()); + } + + public function randomkey(): \Relay\Relay|bool|null|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->randomkey(...\func_get_args()); + } + + public function time(): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->time(...\func_get_args()); + } + + public function bgrewriteaof(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args()); + } + + public function lastsave(): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lastsave(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lcs(...\func_get_args()); + } + + public function bgsave($schedule = false): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgsave(...\func_get_args()); + } + + public function save(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->save(...\func_get_args()); + } + + public function role(): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->role(...\func_get_args()); + } + + public function ttl($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ttl(...\func_get_args()); + } + + public function pttl($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args()); + } + + public function exists(...$keys): \Relay\Relay|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exists(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->eval_ro(...\func_get_args()); + } + + public function evalsha($sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha, $args = [], $num_keys = 0): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->evalsha_ro(...\func_get_args()); + } + + public function client($operation, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->client(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geohash(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadiusbymember_ro(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->georadius_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): \Relay\Relay|array + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->get(...\func_get_args()); + } + + public function getset($key, $value): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getset(...\func_get_args()); + } + + public function getrange($key, $start, $end): \Relay\Relay|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getrange(...\func_get_args()); + } + + public function setrange($key, $start, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setrange(...\func_get_args()); + } + + public function getbit($key, $pos): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getbit(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $by_bit = false): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args()); + } + + public function bitfield($key, ...$args): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitfield(...\func_get_args()); + } + + public function config($operation, $key = null, $value = null): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->config(...\func_get_args()); + } + + public function command(...$args): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->command(...\func_get_args()); + } + + public function bitop($operation, $dstkey, $srckey, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null, $bybit = false): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitpos(...\func_get_args()); + } + + public function setbit($key, $pos, $val): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setbit(...\func_get_args()); + } + + public function acl($cmd, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->acl(...\func_get_args()); + } + + public function append($key, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->append(...\func_get_args()); + } + + public function set($key, $value, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->set(...\func_get_args()); + } + + public function getex($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getex(...\func_get_args()); + } + + public function getdel($key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getdel(...\func_get_args()); + } + + public function setex($key, $seconds, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setex(...\func_get_args()); + } + + public function pfadd($key, $elements): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfadd(...\func_get_args()); + } + + public function pfcount($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfcount(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pfmerge(...\func_get_args()); + } + + public function psetex($key, $milliseconds, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psetex(...\func_get_args()); + } + + public function publish($channel, $message): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args()); + } + + public function pubsub($operation, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args()); + } + + public function spublish($channel, $message): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spublish(...\func_get_args()); + } + + public function setnx($key, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->setnx(...\func_get_args()); + } + + public function mget($keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args()); + } + + public function move($key, $db): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->move(...\func_get_args()); + } + + public function mset($kvals): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mset(...\func_get_args()); + } + + public function msetnx($kvals): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->msetnx(...\func_get_args()); + } + + public function rename($key, $newkey): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->renamenx(...\func_get_args()); + } + + public function del(...$keys): \Relay\Relay|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->del(...\func_get_args()); + } + + public function unlink(...$keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unlink(...\func_get_args()); + } + + public function expire($key, $seconds, $mode = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expire(...\func_get_args()); + } + + public function pexpire($key, $milliseconds): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpire(...\func_get_args()); + } + + public function expireat($key, $timestamp): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expireat(...\func_get_args()); + } + + public function expiretime($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->expiretime(...\func_get_args()); + } + + public function pexpireat($key, $timestamp_ms): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpireat(...\func_get_args()); + } + + public function pexpiretime($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pexpiretime(...\func_get_args()); + } + + public function persist($key): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->persist(...\func_get_args()); + } + + public function type($key): \Relay\Relay|bool|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->type(...\func_get_args()); + } + + public function lmove($srckey, $dstkey, $srcpos, $dstpos): \Relay\Relay|false|null|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmove(...\func_get_args()); + } + + public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): \Relay\Relay|false|null|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmove(...\func_get_args()); + } + + public function lrange($key, $start, $stop): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrange(...\func_get_args()); + } + + public function lpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpush(...\func_get_args()); + } + + public function rpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpush(...\func_get_args()); + } + + public function lpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpushx(...\func_get_args()); + } + + public function rpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpushx(...\func_get_args()); + } + + public function lset($key, $index, $mem): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lset(...\func_get_args()); + } + + public function lpop($key, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Relay\Relay|array|false|int|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lpos(...\func_get_args()); + } + + public function rpop($key, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpop(...\func_get_args()); + } + + public function rpoplpush($source, $dest): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->rpoplpush(...\func_get_args()); + } + + public function brpoplpush($source, $dest, $timeout): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpoplpush(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->blmpop(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->brpop(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bzpopmin(...\func_get_args()); + } + + public function object($op, $key): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->object(...\func_get_args()); + } + + public function geopos($key, ...$members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->geopos(...\func_get_args()); + } + + public function lrem($key, $mem, $count = 0): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lrem(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->lindex(...\func_get_args()); + } + + public function linsert($key, $op, $pivot, $element): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->linsert(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args()); + } + + public function hget($hash, $member): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hget(...\func_get_args()); + } + + public function hstrlen($hash, $member): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hstrlen(...\func_get_args()); + } + + public function hgetall($hash): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetall(...\func_get_args()); + } + + public function hkeys($hash): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hkeys(...\func_get_args()); + } + + public function hvals($hash): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hvals(...\func_get_args()); + } + + public function hmget($hash, $members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmget(...\func_get_args()); + } + + public function hrandfield($hash, $options = null): \Relay\Relay|array|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hrandfield(...\func_get_args()); + } + + public function hmset($hash, $members): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hmset(...\func_get_args()); + } + + public function hexists($hash, $member): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexists(...\func_get_args()); + } + + public function hsetnx($hash, $member, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hsetnx(...\func_get_args()); + } + + public function hset($key, $mem, $val, ...$kvals): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hset(...\func_get_args()); + } + + public function hdel($key, $mem, ...$mems): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hdel(...\func_get_args()); + } + + public function hincrby($key, $mem, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $mem, $value): \Relay\Relay|bool|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hincrbyfloat(...\func_get_args()); + } + + public function incr($key, $by = 1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incr(...\func_get_args()); + } + + public function decr($key, $by = 1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decr(...\func_get_args()); + } + + public function incrby($key, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrby(...\func_get_args()); + } + + public function decrby($key, $value): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->decrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->incrbyfloat(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiff(...\func_get_args()); + } + + public function sdiffstore($key, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sdiffstore(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sinterstore(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunion(...\func_get_args()); + } + + public function sunionstore($key, ...$other_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunionstore(...\func_get_args()); + } + + public function subscribe($channels, $callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->subscribe(...\func_get_args()); + } + + public function unsubscribe($channels = []): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unsubscribe(...\func_get_args()); + } + + public function psubscribe($patterns, $callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->psubscribe(...\func_get_args()); + } + + public function punsubscribe($patterns = []): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->punsubscribe(...\func_get_args()); + } + + public function ssubscribe($channels, $callback): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ssubscribe(...\func_get_args()); + } + + public function sunsubscribe($channels = []): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sunsubscribe(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->touch(...\func_get_args()); + } + + public function pipeline(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pipeline(...\func_get_args()); + } + + public function multi($mode = 0): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->multi(...\func_get_args()); + } + + public function exec(): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->exec(...\func_get_args()); + } + + public function wait($replicas, $timeout): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->watch(...\func_get_args()); + } + + public function unwatch(): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->unwatch(...\func_get_args()); + } + + public function discard(): bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args()); + } + + public function getMode($masked = false): int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getMode(...\func_get_args()); + } + + public function clearBytes(): void + { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->clearBytes(...\func_get_args()); + } + + public function scan(&$iterator, $match = null, $count = 0, $type = null): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function hscan($key, &$iterator, $match = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function sscan($key, &$iterator, $match = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscan($key, &$iterator, $match = null, $count = 0): array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function keys($pattern): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->keys(...\func_get_args()); + } + + public function slowlog($operation, ...$extra_args): \Relay\Relay|array|bool|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->slowlog(...\func_get_args()); + } + + public function smembers($set): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smembers(...\func_get_args()); + } + + public function sismember($set, $member): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sismember(...\func_get_args()); + } + + public function smismember($set, ...$members): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smismember(...\func_get_args()); + } + + public function srem($set, $member, ...$members): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srem(...\func_get_args()); + } + + public function sadd($set, $member, ...$members): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sadd(...\func_get_args()); + } + + public function sort($key, $options = []): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = []): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->sort_ro(...\func_get_args()); + } + + public function smove($srcset, $dstset, $member): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->smove(...\func_get_args()); + } + + public function spop($set, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->spop(...\func_get_args()); + } + + public function srandmember($set, $count = 1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->srandmember(...\func_get_args()); + } + + public function scard($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->script(...\func_get_args()); + } + + public function strlen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->strlen(...\func_get_args()); + } + + public function hlen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hlen(...\func_get_args()); + } + + public function llen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->llen(...\func_get_args()); + } + + public function xack($key, $group, $ids): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Relay\Relay|false|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xadd(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xclaim(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xautoclaim(...\func_get_args()); + } + + public function xlen($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xlen(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xgroup(...\func_get_args()); + } + + public function xdel($key, $ids): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xdel(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xinfo(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null, $idle = 0): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrange(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Relay\Relay|array|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xrevrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Relay\Relay|array|bool|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Relay\Relay|array|bool|null + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xreadgroup(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->xtrim(...\func_get_args()); + } + + public function zadd($key, ...$args): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zadd(...\func_get_args()); + } + + public function zrandmember($key, $options = null): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrandmember(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrange(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrange(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebyscore(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebyscore(...\func_get_args()); + } + + public function zrangestore($dst, $src, $start, $end, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangestore(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrangebylex(...\func_get_args()); + } + + public function zrevrangebylex($key, $max, $min, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrangebylex(...\func_get_args()); + } + + public function zrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrank(...\func_get_args()); + } + + public function zrevrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrevrank(...\func_get_args()); + } + + public function zrem($key, ...$args): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $start, $end): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zremrangebyscore(...\func_get_args()); + } + + public function zcard($key): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zcount(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zdiffstore(...\func_get_args()); + } + + public function zincrby($key, $score, $mem): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zincrby(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zlexcount(...\func_get_args()); + } + + public function zmscore($key, ...$mems): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zmscore(...\func_get_args()); + } + + public function zscore($key, $member): \Relay\Relay|false|float + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zscore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zinterstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zunionstore(...\func_get_args()); + } + + public function zpopmin($key, $count = 1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmin(...\func_get_args()); + } + + public function zpopmax($key, $count = 1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->zpopmax(...\func_get_args()); + } + + public function _getKeys() + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->_getKeys(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/RelayProxyTrait.php b/vendor/symfony/cache/Traits/RelayProxyTrait.php new file mode 100644 index 0000000..a1d252b --- /dev/null +++ b/vendor/symfony/cache/Traits/RelayProxyTrait.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +if (version_compare(phpversion('relay'), '0.8.1', '>=')) { + /** + * @internal + */ + trait RelayProxyTrait + { + public function copy($src, $dst, $options = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->copy(...\func_get_args()); + } + + public function jsonArrAppend($key, $value_or_array, $path = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonArrAppend(...\func_get_args()); + } + + public function jsonArrIndex($key, $path, $value, $start = 0, $stop = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonArrIndex(...\func_get_args()); + } + + public function jsonArrInsert($key, $path, $index, $value, ...$other_values): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonArrInsert(...\func_get_args()); + } + + public function jsonArrLen($key, $path = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonArrLen(...\func_get_args()); + } + + public function jsonArrPop($key, $path = null, $index = -1): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonArrPop(...\func_get_args()); + } + + public function jsonArrTrim($key, $path, $start, $stop): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonArrTrim(...\func_get_args()); + } + + public function jsonClear($key, $path = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonClear(...\func_get_args()); + } + + public function jsonDebug($command, $key, $path = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonDebug(...\func_get_args()); + } + + public function jsonDel($key, $path = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonDel(...\func_get_args()); + } + + public function jsonForget($key, $path = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonForget(...\func_get_args()); + } + + public function jsonGet($key, $options = [], ...$paths): mixed + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonGet(...\func_get_args()); + } + + public function jsonMerge($key, $path, $value): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonMerge(...\func_get_args()); + } + + public function jsonMget($key_or_array, $path): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonMget(...\func_get_args()); + } + + public function jsonMset($key, $path, $value, ...$other_triples): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonMset(...\func_get_args()); + } + + public function jsonNumIncrBy($key, $path, $value): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonNumIncrBy(...\func_get_args()); + } + + public function jsonNumMultBy($key, $path, $value): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonNumMultBy(...\func_get_args()); + } + + public function jsonObjKeys($key, $path = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonObjKeys(...\func_get_args()); + } + + public function jsonObjLen($key, $path = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonObjLen(...\func_get_args()); + } + + public function jsonResp($key, $path = null): \Relay\Relay|array|false|int|string + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonResp(...\func_get_args()); + } + + public function jsonSet($key, $path, $value, $condition = null): \Relay\Relay|bool + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonSet(...\func_get_args()); + } + + public function jsonStrAppend($key, $value, $path = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonStrAppend(...\func_get_args()); + } + + public function jsonStrLen($key, $path = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonStrLen(...\func_get_args()); + } + + public function jsonToggle($key, $path): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonToggle(...\func_get_args()); + } + + public function jsonType($key, $path = null): \Relay\Relay|array|false + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->jsonType(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait RelayProxyTrait + { + public function copy($src, $dst, $options = null): \Relay\Relay|false|int + { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->copy(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/ValueWrapper.php b/vendor/symfony/cache/Traits/ValueWrapper.php new file mode 100644 index 0000000..718a23d --- /dev/null +++ b/vendor/symfony/cache/Traits/ValueWrapper.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * A short namespace-less class to serialize items with metadata. + * + * @author Nicolas Grekas + * + * @internal + */ +class © +{ + private const EXPIRY_OFFSET = 1648206727; + private const INT32_MAX = 2147483647; + + public readonly mixed $value; + public readonly array $metadata; + + public function __construct(mixed $value, array $metadata) + { + $this->value = $value; + $this->metadata = $metadata; + } + + public function __serialize(): array + { + // pack 31-bits ctime into 14bits + $c = $this->metadata['ctime'] ?? 0; + $c = match (true) { + $c > self::INT32_MAX - 2 => self::INT32_MAX, + $c > 0 => 1 + $c, + default => 1, + }; + $e = 0; + while (!(0x40000000 & $c)) { + $c <<= 1; + ++$e; + } + $c = (0x7FE0 & ($c >> 16)) | $e; + + $pack = pack('Vn', (int) (0.1 + ($this->metadata['expiry'] ?: self::INT32_MAX + self::EXPIRY_OFFSET) - self::EXPIRY_OFFSET), $c); + + if (isset($this->metadata['tags'])) { + $pack[4] = $pack[4] | "\x80"; + } + + return [$pack => $this->value] + ($this->metadata['tags'] ?? []); + } + + public function __unserialize(array $data): void + { + $pack = array_key_first($data); + $this->value = $data[$pack]; + + if ($hasTags = "\x80" === ($pack[4] & "\x80")) { + unset($data[$pack]); + $pack[4] = $pack[4] & "\x7F"; + } + + $metadata = unpack('Vexpiry/nctime', $pack); + $metadata['expiry'] += self::EXPIRY_OFFSET; + + if (!$metadata['ctime'] = ((0x4000 | $metadata['ctime']) << 16 >> (0x1F & $metadata['ctime'])) - 1) { + unset($metadata['ctime']); + } + + if ($hasTags) { + $metadata['tags'] = $data; + } + + $this->metadata = $metadata; + } +} diff --git a/vendor/symfony/cache/composer.json b/vendor/symfony/cache/composer.json new file mode 100644 index 0000000..d537037 --- /dev/null +++ b/vendor/symfony/cache/composer.json @@ -0,0 +1,60 @@ +{ + "name": "symfony/cache", + "type": "library", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "keywords": ["caching", "psr6"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/clock/CHANGELOG.md b/vendor/symfony/clock/CHANGELOG.md new file mode 100644 index 0000000..c71cb5c --- /dev/null +++ b/vendor/symfony/clock/CHANGELOG.md @@ -0,0 +1,25 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `DatePoint::getMicrosecond()` and `DatePoint::setMicrosecond()` + +6.4 +--- + + * Add `DatePoint`: an immutable DateTime implementation with stricter error handling and return types + * Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate + * Add `$modifier` argument to the `now()` helper + +6.3 +--- + + * Add `ClockAwareTrait` to help write time-sensitive classes + * Add `Clock` class and `now()` function + +6.2 +--- + + * Add the component diff --git a/vendor/symfony/clock/Clock.php b/vendor/symfony/clock/Clock.php new file mode 100644 index 0000000..311e8fc --- /dev/null +++ b/vendor/symfony/clock/Clock.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface as PsrClockInterface; + +/** + * A global clock. + * + * @author Nicolas Grekas + */ +final class Clock implements ClockInterface +{ + private static ClockInterface $globalClock; + + public function __construct( + private readonly ?PsrClockInterface $clock = null, + private ?\DateTimeZone $timezone = null, + ) { + } + + /** + * Returns the current global clock. + * + * Note that you should prefer injecting a ClockInterface or using + * ClockAwareTrait when possible instead of using this method. + */ + public static function get(): ClockInterface + { + return self::$globalClock ??= new NativeClock(); + } + + public static function set(PsrClockInterface $clock): void + { + self::$globalClock = $clock instanceof ClockInterface ? $clock : new self($clock); + } + + public function now(): DatePoint + { + $now = ($this->clock ?? self::get())->now(); + + if (!$now instanceof DatePoint) { + $now = DatePoint::createFromInterface($now); + } + + return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now; + } + + public function sleep(float|int $seconds): void + { + $clock = $this->clock ?? self::get(); + + if ($clock instanceof ClockInterface) { + $clock->sleep($seconds); + } else { + (new NativeClock())->sleep($seconds); + } + } + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->timezone = $timezone; + + return $clone; + } +} diff --git a/vendor/symfony/clock/ClockAwareTrait.php b/vendor/symfony/clock/ClockAwareTrait.php new file mode 100644 index 0000000..e723d7f --- /dev/null +++ b/vendor/symfony/clock/ClockAwareTrait.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface; +use Symfony\Contracts\Service\Attribute\Required; + +/** + * A trait to help write time-sensitive classes. + * + * @author Nicolas Grekas + */ +trait ClockAwareTrait +{ + private readonly ClockInterface $clock; + + #[Required] + public function setClock(ClockInterface $clock): void + { + $this->clock = $clock; + } + + protected function now(): DatePoint + { + $now = ($this->clock ??= new Clock())->now(); + + return $now instanceof DatePoint ? $now : DatePoint::createFromInterface($now); + } +} diff --git a/vendor/symfony/clock/ClockInterface.php b/vendor/symfony/clock/ClockInterface.php new file mode 100644 index 0000000..435775a --- /dev/null +++ b/vendor/symfony/clock/ClockInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface as PsrClockInterface; + +/** + * @author Nicolas Grekas + */ +interface ClockInterface extends PsrClockInterface +{ + public function sleep(float|int $seconds): void; + + public function withTimeZone(\DateTimeZone|string $timezone): static; +} diff --git a/vendor/symfony/clock/DatePoint.php b/vendor/symfony/clock/DatePoint.php new file mode 100644 index 0000000..eefb494 --- /dev/null +++ b/vendor/symfony/clock/DatePoint.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * An immmutable DateTime with stricter error handling and return types than the native one. + * + * @author Nicolas Grekas + */ +final class DatePoint extends \DateTimeImmutable +{ + /** + * @throws \DateMalformedStringException When $datetime is invalid + */ + public function __construct(string $datetime = 'now', ?\DateTimeZone $timezone = null, ?parent $reference = null) + { + $now = $reference ?? Clock::get()->now(); + + if ('now' !== $datetime) { + if (!$now instanceof static) { + $now = static::createFromInterface($now); + } + + if (\PHP_VERSION_ID < 80300) { + try { + $builtInDate = new parent($datetime, $timezone ?? $now->getTimezone()); + $timezone = $builtInDate->getTimezone(); + } catch (\Exception $e) { + throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e); + } + } else { + $builtInDate = new parent($datetime, $timezone ?? $now->getTimezone()); + $timezone = $builtInDate->getTimezone(); + } + + $now = $now->setTimezone($timezone)->modify($datetime); + + if ('00:00:00.000000' === $builtInDate->format('H:i:s.u')) { + $now = $now->setTime(0, 0); + } + } elseif (null !== $timezone) { + $now = $now->setTimezone($timezone); + } + + $this->__unserialize((array) $now); + } + + /** + * @throws \DateMalformedStringException When $format or $datetime are invalid + */ + public static function createFromFormat(string $format, string $datetime, ?\DateTimeZone $timezone = null): static + { + return parent::createFromFormat($format, $datetime, $timezone) ?: throw new \DateMalformedStringException(static::getLastErrors()['errors'][0] ?? 'Invalid date string or format.'); + } + + public static function createFromInterface(\DateTimeInterface $object): static + { + return parent::createFromInterface($object); + } + + public static function createFromMutable(\DateTime $object): static + { + return parent::createFromMutable($object); + } + + public static function createFromTimestamp(int|float $timestamp): static + { + if (\PHP_VERSION_ID >= 80400) { + return parent::createFromTimestamp($timestamp); + } + + if (\is_int($timestamp) || !$ms = (int) $timestamp - $timestamp) { + return static::createFromFormat('U', (string) $timestamp); + } + + if (!is_finite($timestamp) || \PHP_INT_MAX + 1.0 <= $timestamp || \PHP_INT_MIN > $timestamp) { + throw new \DateRangeError(sprintf('DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %s and %s.999999, %s given', \PHP_INT_MIN, \PHP_INT_MAX, $timestamp)); + } + + if ($timestamp < 0) { + $timestamp = (int) $timestamp - 2.0 + $ms; + } + + return static::createFromFormat('U.u', sprintf('%.6F', $timestamp)); + } + + public function add(\DateInterval $interval): static + { + return parent::add($interval); + } + + public function sub(\DateInterval $interval): static + { + return parent::sub($interval); + } + + /** + * @throws \DateMalformedStringException When $modifier is invalid + */ + public function modify(string $modifier): static + { + if (\PHP_VERSION_ID < 80300) { + return @parent::modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? sprintf('Invalid modifier: "%s".', $modifier)); + } + + return parent::modify($modifier); + } + + public function setTimestamp(int $value): static + { + return parent::setTimestamp($value); + } + + public function setDate(int $year, int $month, int $day): static + { + return parent::setDate($year, $month, $day); + } + + public function setISODate(int $year, int $week, int $day = 1): static + { + return parent::setISODate($year, $week, $day); + } + + public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static + { + return parent::setTime($hour, $minute, $second, $microsecond); + } + + public function setTimezone(\DateTimeZone $timezone): static + { + return parent::setTimezone($timezone); + } + + public function getTimezone(): \DateTimeZone + { + return parent::getTimezone() ?: throw new \DateInvalidTimeZoneException('The DatePoint object has no timezone.'); + } + + public function setMicrosecond(int $microsecond): static + { + if ($microsecond < 0 || $microsecond > 999999) { + throw new \DateRangeError('DatePoint::setMicrosecond(): Argument #1 ($microsecond) must be between 0 and 999999, '.$microsecond.' given'); + } + + if (\PHP_VERSION_ID < 80400) { + return $this->setTime(...explode('.', $this->format('H.i.s.'.$microsecond))); + } + + return parent::setMicrosecond($microsecond); + } + + public function getMicrosecond(): int + { + if (\PHP_VERSION_ID >= 80400) { + return parent::getMicrosecond(); + } + + return $this->format('u'); + } +} diff --git a/vendor/symfony/clock/LICENSE b/vendor/symfony/clock/LICENSE new file mode 100644 index 0000000..733c826 --- /dev/null +++ b/vendor/symfony/clock/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022-present Fabien Potencier + +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. diff --git a/vendor/symfony/clock/MockClock.php b/vendor/symfony/clock/MockClock.php new file mode 100644 index 0000000..ab64f1c --- /dev/null +++ b/vendor/symfony/clock/MockClock.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * A clock that always returns the same date, suitable for testing time-sensitive logic. + * + * Consider using ClockSensitiveTrait in your test cases instead of using this class directly. + * + * @author Nicolas Grekas + */ +final class MockClock implements ClockInterface +{ + private DatePoint $now; + + /** + * @throws \DateMalformedStringException When $now is invalid + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZone|string|null $timezone = null) + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + if (\is_string($now)) { + $now = new DatePoint($now, $timezone ?? new \DateTimeZone('UTC')); + } elseif (!$now instanceof DatePoint) { + $now = DatePoint::createFromInterface($now); + } + + $this->now = null !== $timezone ? $now->setTimezone($timezone) : $now; + } + + public function now(): DatePoint + { + return clone $this->now; + } + + public function sleep(float|int $seconds): void + { + $now = (float) $this->now->format('Uu') + $seconds * 1e6; + $now = substr_replace(sprintf('@%07.0F', $now), '.', -6, 0); + $timezone = $this->now->getTimezone(); + + $this->now = DatePoint::createFromInterface(new \DateTimeImmutable($now, $timezone))->setTimezone($timezone); + } + + /** + * @throws \DateMalformedStringException When $modifier is invalid + */ + public function modify(string $modifier): void + { + if (\PHP_VERSION_ID < 80300) { + $this->now = @$this->now->modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? sprintf('Invalid modifier: "%s". Could not modify MockClock.', $modifier)); + + return; + } + + $this->now = $this->now->modify($modifier); + } + + /** + * @throws \DateInvalidTimeZoneException When the timezone name is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->now = $clone->now->setTimezone($timezone); + + return $clone; + } +} diff --git a/vendor/symfony/clock/MonotonicClock.php b/vendor/symfony/clock/MonotonicClock.php new file mode 100644 index 0000000..d27bf9c --- /dev/null +++ b/vendor/symfony/clock/MonotonicClock.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * A monotonic clock suitable for performance profiling. + * + * @author Nicolas Grekas + */ +final class MonotonicClock implements ClockInterface +{ + private int $sOffset; + private int $usOffset; + private \DateTimeZone $timezone; + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function __construct(\DateTimeZone|string|null $timezone = null) + { + if (false === $offset = hrtime()) { + throw new \RuntimeException('hrtime() returned false: the runtime environment does not provide access to a monotonic timer.'); + } + + $time = explode(' ', microtime(), 2); + $this->sOffset = $time[1] - $offset[0]; + $this->usOffset = (int) ($time[0] * 1000000) - (int) ($offset[1] / 1000); + + $this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; + } + + public function now(): DatePoint + { + [$s, $us] = hrtime(); + + if (1000000 <= $us = (int) ($us / 1000) + $this->usOffset) { + ++$s; + $us -= 1000000; + } elseif (0 > $us) { + --$s; + $us += 1000000; + } + + if (6 !== \strlen($now = (string) $us)) { + $now = str_pad($now, 6, '0', \STR_PAD_LEFT); + } + + $now = '@'.($s + $this->sOffset).'.'.$now; + + return DatePoint::createFromInterface(new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone); + } + + public function sleep(float|int $seconds): void + { + if (0 < $s = (int) $seconds) { + sleep($s); + } + + if (0 < $us = $seconds - $s) { + usleep((int) ($us * 1E6)); + } + } + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->timezone = $timezone; + + return $clone; + } +} diff --git a/vendor/symfony/clock/NativeClock.php b/vendor/symfony/clock/NativeClock.php new file mode 100644 index 0000000..b580a88 --- /dev/null +++ b/vendor/symfony/clock/NativeClock.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * A clock that relies the system time. + * + * @author Nicolas Grekas + */ +final class NativeClock implements ClockInterface +{ + private \DateTimeZone $timezone; + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function __construct(\DateTimeZone|string|null $timezone = null) + { + $this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; + } + + public function now(): DatePoint + { + return DatePoint::createFromInterface(new \DateTimeImmutable('now', $this->timezone)); + } + + public function sleep(float|int $seconds): void + { + if (0 < $s = (int) $seconds) { + sleep($s); + } + + if (0 < $us = $seconds - $s) { + usleep((int) ($us * 1E6)); + } + } + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->timezone = $timezone; + + return $clone; + } +} diff --git a/vendor/symfony/clock/README.md b/vendor/symfony/clock/README.md new file mode 100644 index 0000000..e80b5d3 --- /dev/null +++ b/vendor/symfony/clock/README.md @@ -0,0 +1,47 @@ +Clock Component +=============== + +Symfony Clock decouples applications from the system clock. + +Getting Started +--------------- + +```bash +composer require symfony/clock +``` + +```php +use Symfony\Component\Clock\NativeClock; +use Symfony\Component\Clock\ClockInterface; + +class MyClockSensitiveClass +{ + public function __construct( + private ClockInterface $clock, + ) { + // Only if you need to force a timezone: + //$this->clock = $clock->withTimeZone('UTC'); + } + + public function doSomething() + { + $now = $this->clock->now(); + // [...] do something with $now, which is a \DateTimeImmutable object + + $this->clock->sleep(2.5); // Pause execution for 2.5 seconds + } +} + +$clock = new NativeClock(); +$service = new MyClockSensitiveClass($clock); +$service->doSomething(); +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/clock.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/clock/Resources/now.php b/vendor/symfony/clock/Resources/now.php new file mode 100644 index 0000000..47d086c --- /dev/null +++ b/vendor/symfony/clock/Resources/now.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +if (!\function_exists(now::class)) { + /** + * @throws \DateMalformedStringException When the modifier is invalid + */ + function now(string $modifier = 'now'): DatePoint + { + if ('now' !== $modifier) { + return new DatePoint($modifier); + } + + $now = Clock::get()->now(); + + return $now instanceof DatePoint ? $now : DatePoint::createFromInterface($now); + } +} diff --git a/vendor/symfony/clock/Test/ClockSensitiveTrait.php b/vendor/symfony/clock/Test/ClockSensitiveTrait.php new file mode 100644 index 0000000..f71f3a1 --- /dev/null +++ b/vendor/symfony/clock/Test/ClockSensitiveTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock\Test; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use Symfony\Component\Clock\Clock; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Clock\MockClock; + +use function Symfony\Component\Clock\now; + +/** + * Helps with mocking the time in your test cases. + * + * This trait provides one self::mockTime() method that freezes the time. + * It restores the global clock after each test case. + * self::mockTime() accepts either a string (eg '+1 days' or '2022-12-22'), + * a DateTimeImmutable, or a boolean (to freeze/restore the global clock). + * + * @author Nicolas Grekas + */ +trait ClockSensitiveTrait +{ + public static function mockTime(string|\DateTimeImmutable|bool $when = true): ClockInterface + { + Clock::set(match (true) { + false === $when => self::saveClockBeforeTest(false), + true === $when => new MockClock(), + $when instanceof \DateTimeImmutable => new MockClock($when), + default => new MockClock(now($when)), + }); + + return Clock::get(); + } + + /** + * @beforeClass + * + * @before + * + * @internal + */ + #[Before] + #[BeforeClass] + public static function saveClockBeforeTest(bool $save = true): ClockInterface + { + static $originalClock; + + if ($save && $originalClock) { + self::restoreClockAfterTest(); + } + + return $save ? $originalClock = Clock::get() : $originalClock; + } + + /** + * @after + * + * @internal + */ + #[After] + protected static function restoreClockAfterTest(): void + { + Clock::set(self::saveClockBeforeTest(false)); + } +} diff --git a/vendor/symfony/clock/composer.json b/vendor/symfony/clock/composer.json new file mode 100644 index 0000000..491215f --- /dev/null +++ b/vendor/symfony/clock/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/clock", + "type": "library", + "description": "Decouples applications from the system clock", + "keywords": ["clock", "time", "psr20"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "psr/clock-implementation": "1.0" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "autoload": { + "files": [ "Resources/now.php" ], + "psr-4": { "Symfony\\Component\\Clock\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/config/Builder/ClassBuilder.php b/vendor/symfony/config/Builder/ClassBuilder.php new file mode 100644 index 0000000..f34ab85 --- /dev/null +++ b/vendor/symfony/config/Builder/ClassBuilder.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Builder; + +/** + * Build PHP classes to generate config. + * + * @internal + * + * @author Tobias Nyholm + */ +class ClassBuilder +{ + private string $name; + + /** @var Property[] */ + private array $properties = []; + + /** @var Method[] */ + private array $methods = []; + private array $require = []; + private array $use = []; + private array $implements = []; + private bool $allowExtraKeys = false; + + public function __construct( + private string $namespace, + string $name, + ) { + $this->name = ucfirst($this->camelCase($name)).'Config'; + } + + public function getDirectory(): string + { + return str_replace('\\', \DIRECTORY_SEPARATOR, $this->namespace); + } + + public function getFilename(): string + { + return $this->name.'.php'; + } + + public function build(): string + { + $rootPath = explode(\DIRECTORY_SEPARATOR, $this->getDirectory()); + $require = ''; + foreach ($this->require as $class) { + // figure out relative path. + $path = explode(\DIRECTORY_SEPARATOR, $class->getDirectory()); + $path[] = $class->getFilename(); + foreach ($rootPath as $key => $value) { + if ($path[$key] !== $value) { + break; + } + unset($path[$key]); + } + $require .= sprintf('require_once __DIR__.\DIRECTORY_SEPARATOR.\'%s\';', implode('\'.\DIRECTORY_SEPARATOR.\'', $path))."\n"; + } + $use = $require ? "\n" : ''; + foreach (array_keys($this->use) as $statement) { + $use .= sprintf('use %s;', $statement)."\n"; + } + + $implements = [] === $this->implements ? '' : 'implements '.implode(', ', $this->implements); + $body = ''; + foreach ($this->properties as $property) { + $body .= ' '.$property->getContent()."\n"; + } + foreach ($this->methods as $method) { + $lines = explode("\n", $method->getContent()); + foreach ($lines as $line) { + $body .= ($line ? ' '.$line : '')."\n"; + } + } + + $content = strtr(' $this->namespace, 'REQUIRE' => $require, 'USE' => $use, 'CLASS' => $this->getName(), 'IMPLEMENTS' => $implements, 'BODY' => $body]); + + return $content; + } + + public function addRequire(self $class): void + { + $this->require[] = $class; + } + + public function addUse(string $class): void + { + $this->use[$class] = true; + } + + public function addImplements(string $interface): void + { + $this->implements[] = '\\'.ltrim($interface, '\\'); + } + + public function addMethod(string $name, string $body, array $params = []): void + { + $this->methods[] = new Method(strtr($body, ['NAME' => $this->camelCase($name)] + $params)); + } + + public function addProperty(string $name, ?string $classType = null, ?string $defaultValue = null): Property + { + $property = new Property($name, '_' !== $name[0] ? $this->camelCase($name) : $name); + if (null !== $classType) { + $property->setType($classType); + } + $this->properties[] = $property; + $defaultValue = null !== $defaultValue ? sprintf(' = %s', $defaultValue) : ''; + $property->setContent(sprintf('private $%s%s;', $property->getName(), $defaultValue)); + + return $property; + } + + public function getProperties(): array + { + return $this->properties; + } + + private function camelCase(string $input): string + { + $output = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $input)))); + + return preg_replace('#\W#', '', $output); + } + + public function getName(): string + { + return $this->name; + } + + public function getNamespace(): string + { + return $this->namespace; + } + + public function getFqcn(): string + { + return '\\'.$this->namespace.'\\'.$this->name; + } + + public function setAllowExtraKeys(bool $allowExtraKeys): void + { + $this->allowExtraKeys = $allowExtraKeys; + } + + public function shouldAllowExtraKeys(): bool + { + return $this->allowExtraKeys; + } +} diff --git a/vendor/symfony/config/Builder/ConfigBuilderGenerator.php b/vendor/symfony/config/Builder/ConfigBuilderGenerator.php new file mode 100644 index 0000000..4923eaf --- /dev/null +++ b/vendor/symfony/config/Builder/ConfigBuilderGenerator.php @@ -0,0 +1,599 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Builder; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\BooleanNode; +use Symfony\Component\Config\Definition\Builder\ExprBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\FloatNode; +use Symfony\Component\Config\Definition\IntegerNode; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Config\Definition\ScalarNode; +use Symfony\Component\Config\Definition\VariableNode; +use Symfony\Component\Config\Loader\ParamConfigurator; + +/** + * Generate ConfigBuilders to help create valid config. + * + * @author Tobias Nyholm + */ +class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface +{ + /** + * @var ClassBuilder[] + */ + private array $classes = []; + + public function __construct( + private string $outputDir, + ) { + } + + /** + * @return \Closure that will return the root config class + */ + public function build(ConfigurationInterface $configuration): \Closure + { + $this->classes = []; + + $rootNode = $configuration->getConfigTreeBuilder()->buildTree(); + $rootClass = new ClassBuilder('Symfony\\Config', $rootNode->getName()); + + $path = $this->getFullPath($rootClass); + if (!is_file($path)) { + // Generate the class if the file not exists + $this->classes[] = $rootClass; + $this->buildNode($rootNode, $rootClass, $this->getSubNamespace($rootClass)); + $rootClass->addImplements(ConfigBuilderInterface::class); + $rootClass->addMethod('getExtensionAlias', ' +public function NAME(): string +{ + return \'ALIAS\'; +}', ['ALIAS' => $rootNode->getPath()]); + + $this->writeClasses(); + } + + return function () use ($path, $rootClass) { + require_once $path; + $className = $rootClass->getFqcn(); + + return new $className(); + }; + } + + private function getFullPath(ClassBuilder $class): string + { + $directory = $this->outputDir.\DIRECTORY_SEPARATOR.$class->getDirectory(); + if (!is_dir($directory)) { + @mkdir($directory, 0777, true); + } + + return $directory.\DIRECTORY_SEPARATOR.$class->getFilename(); + } + + private function writeClasses(): void + { + foreach ($this->classes as $class) { + $this->buildConstructor($class); + $this->buildToArray($class); + if ($class->getProperties()) { + $class->addProperty('_usedProperties', null, '[]'); + } + $this->buildSetExtraKey($class); + + file_put_contents($this->getFullPath($class), $class->build()); + } + + $this->classes = []; + } + + private function buildNode(NodeInterface $node, ClassBuilder $class, string $namespace): void + { + if (!$node instanceof ArrayNode) { + throw new \LogicException('The node was expected to be an ArrayNode. This Configuration includes an edge case not supported yet.'); + } + + foreach ($node->getChildren() as $child) { + match (true) { + $child instanceof ScalarNode => $this->handleScalarNode($child, $class), + $child instanceof PrototypedArrayNode => $this->handlePrototypedArrayNode($child, $class, $namespace), + $child instanceof VariableNode => $this->handleVariableNode($child, $class), + $child instanceof ArrayNode => $this->handleArrayNode($child, $class, $namespace), + default => throw new \RuntimeException(sprintf('Unknown node "%s".', $child::class)), + }; + } + } + + private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $namespace): void + { + $childClass = new ClassBuilder($namespace, $node->getName()); + $childClass->setAllowExtraKeys($node->shouldIgnoreExtraKeys()); + $class->addRequire($childClass); + $this->classes[] = $childClass; + + $hasNormalizationClosures = $this->hasNormalizationClosures($node); + $comment = $this->getComment($node); + if ($hasNormalizationClosures) { + $comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); + $comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); + $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); + } + if ('' !== $comment) { + $comment = "/**\n$comment*/\n"; + } + + $property = $class->addProperty( + $node->getName(), + $this->getType($childClass->getFqcn(), $hasNormalizationClosures) + ); + $nodeTypes = $this->getParameterTypes($node); + $body = $hasNormalizationClosures ? ' +COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static +{ + if (!\is_array($value)) { + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY = $value; + + return $this; + } + + if (!$this->PROPERTY instanceof CLASS) { + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY = new CLASS($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); + } + + return $this->PROPERTY; +}' : ' +COMMENTpublic function NAME(array $value = []): CLASS +{ + if (null === $this->PROPERTY) { + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY = new CLASS($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); + } + + return $this->PROPERTY; +}'; + $class->addUse(InvalidConfigurationException::class); + $class->addMethod($node->getName(), $body, [ + 'COMMENT' => $comment, + 'PROPERTY' => $property->getName(), + 'CLASS' => $childClass->getFqcn(), + 'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes), + ]); + + $this->buildNode($node, $childClass, $this->getSubNamespace($childClass)); + } + + private function handleVariableNode(VariableNode $node, ClassBuilder $class): void + { + $comment = $this->getComment($node); + $property = $class->addProperty($node->getName()); + $class->addUse(ParamConfigurator::class); + + $body = ' +/** +COMMENT * + * @return $this + */ +public function NAME(mixed $valueDEFAULT): static +{ + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY = $value; + + return $this; +}'; + $class->addMethod($node->getName(), $body, [ + 'PROPERTY' => $property->getName(), + 'COMMENT' => $comment, + 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '', + ]); + } + + private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace): void + { + $name = $this->getSingularName($node); + $prototype = $node->getPrototype(); + $methodName = $name; + $hasNormalizationClosures = $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype); + + $nodeParameterTypes = $this->getParameterTypes($node); + $prototypeParameterTypes = $this->getParameterTypes($prototype); + if (!$prototype instanceof ArrayNode || ($prototype instanceof PrototypedArrayNode && $prototype->getPrototype() instanceof ScalarNode)) { + $class->addUse(ParamConfigurator::class); + $property = $class->addProperty($node->getName()); + if (null === $key = $node->getKeyAttribute()) { + // This is an array of values; don't use singular name + $nodeTypesWithoutArray = array_filter($nodeParameterTypes, static fn ($type) => 'array' !== $type); + $body = ' +/** + * @param ParamConfigurator|listEXTRA_TYPE $value + * + * @return $this + */ +public function NAME(PARAM_TYPE $value): static +{ + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY = $value; + + return $this; +}'; + + $class->addMethod($node->getName(), $body, [ + 'PROPERTY' => $property->getName(), + 'PROTOTYPE_TYPE' => implode('|', $prototypeParameterTypes), + 'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '', + 'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $nodeParameterTypes), + ]); + } else { + $body = ' +/** + * @return $this + */ +public function NAME(string $VAR, TYPE $VALUE): static +{ + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY[$VAR] = $VALUE; + + return $this; +}'; + + $class->addMethod($methodName, $body, [ + 'PROPERTY' => $property->getName(), + 'TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $prototypeParameterTypes), + 'VAR' => '' === $key ? 'key' : $key, + 'VALUE' => 'value' === $key ? 'data' : 'value', + ]); + } + + return; + } + + $childClass = new ClassBuilder($namespace, $name); + if ($prototype instanceof ArrayNode) { + $childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys()); + } + $class->addRequire($childClass); + $this->classes[] = $childClass; + + $property = $class->addProperty( + $node->getName(), + $this->getType($childClass->getFqcn().'[]', $hasNormalizationClosures) + ); + + $comment = $this->getComment($node); + if ($hasNormalizationClosures) { + $comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); + $comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); + $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); + } + if ('' !== $comment) { + $comment = "/**\n$comment*/\n"; + } + + if (null === $key = $node->getKeyAttribute()) { + $body = $hasNormalizationClosures ? ' +COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static +{ + $this->_usedProperties[\'PROPERTY\'] = true; + if (!\is_array($value)) { + $this->PROPERTY[] = $value; + + return $this; + } + + return $this->PROPERTY[] = new CLASS($value); +}' : ' +COMMENTpublic function NAME(array $value = []): CLASS +{ + $this->_usedProperties[\'PROPERTY\'] = true; + + return $this->PROPERTY[] = new CLASS($value); +}'; + $class->addMethod($methodName, $body, [ + 'COMMENT' => $comment, + 'PROPERTY' => $property->getName(), + 'CLASS' => $childClass->getFqcn(), + 'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : implode('|', $nodeParameterTypes), + ]); + } else { + $body = $hasNormalizationClosures ? ' +COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static +{ + if (!\is_array($VALUE)) { + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY[$VAR] = $VALUE; + + return $this; + } + + if (!isset($this->PROPERTY[$VAR]) || !$this->PROPERTY[$VAR] instanceof CLASS) { + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY[$VAR] = new CLASS($VALUE); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); + } + + return $this->PROPERTY[$VAR]; +}' : ' +COMMENTpublic function NAME(string $VAR, array $VALUE = []): CLASS +{ + if (!isset($this->PROPERTY[$VAR])) { + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY[$VAR] = new CLASS($VALUE); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); + } + + return $this->PROPERTY[$VAR]; +}'; + $class->addUse(InvalidConfigurationException::class); + $class->addMethod($methodName, str_replace('$value', '$VAR', $body), [ + 'COMMENT' => $comment, 'PROPERTY' => $property->getName(), + 'CLASS' => $childClass->getFqcn(), + 'VAR' => '' === $key ? 'key' : $key, + 'VALUE' => 'value' === $key ? 'data' : 'value', + 'PARAM_TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : implode('|', $prototypeParameterTypes), + ]); + } + + $this->buildNode($prototype, $childClass, $namespace.'\\'.$childClass->getName()); + } + + private function handleScalarNode(ScalarNode $node, ClassBuilder $class): void + { + $comment = $this->getComment($node); + $property = $class->addProperty($node->getName()); + $class->addUse(ParamConfigurator::class); + + $body = ' +/** +COMMENT * @return $this + */ +public function NAME($value): static +{ + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY = $value; + + return $this; +}'; + + $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]); + } + + private function getParameterTypes(NodeInterface $node): array + { + $paramTypes = []; + if ($node instanceof BaseNode) { + $types = $node->getNormalizedTypes(); + if (\in_array(ExprBuilder::TYPE_ANY, $types, true)) { + $paramTypes[] = 'mixed'; + } + if (\in_array(ExprBuilder::TYPE_STRING, $types, true)) { + $paramTypes[] = 'string'; + } + } + if ($node instanceof BooleanNode) { + $paramTypes[] = 'bool'; + } elseif ($node instanceof IntegerNode) { + $paramTypes[] = 'int'; + } elseif ($node instanceof FloatNode) { + $paramTypes[] = 'float'; + } elseif ($node instanceof EnumNode) { + $paramTypes[] = 'mixed'; + } elseif ($node instanceof ArrayNode) { + $paramTypes[] = 'array'; + } elseif ($node instanceof VariableNode) { + $paramTypes[] = 'mixed'; + } + + return array_unique($paramTypes); + } + + private function getComment(BaseNode $node): string + { + $comment = ''; + if ('' !== $info = (string) $node->getInfo()) { + $comment .= ' * '.$info."\n"; + } + + if (!$node instanceof ArrayNode) { + foreach ((array) ($node->getExample() ?? []) as $example) { + $comment .= ' * @example '.$example."\n"; + } + + if ('' !== $default = $node->getDefaultValue()) { + $comment .= ' * @default '.(null === $default ? 'null' : var_export($default, true))."\n"; + } + + if ($node instanceof EnumNode) { + $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => !$a instanceof \UnitEnum ? var_export($a, true) : '\\'.ltrim(var_export($a, true), '\\'), $node->getValues()))))."\n"; + } else { + $parameterTypes = $this->getParameterTypes($node); + $comment .= ' * @param ParamConfigurator|'.implode('|', $parameterTypes).' $value'."\n"; + } + } else { + foreach ((array) ($node->getExample() ?? []) as $example) { + $comment .= ' * @example '.json_encode($example)."\n"; + } + + if ($node->hasDefaultValue() && [] != $default = $node->getDefaultValue()) { + $comment .= ' * @default '.json_encode($default)."\n"; + } + } + + if ($node->isDeprecated()) { + $comment .= ' * @deprecated '.$node->getDeprecation($node->getName(), $node->getParent()->getName())['message']."\n"; + } + + return $comment; + } + + /** + * Pick a good singular name. + */ + private function getSingularName(PrototypedArrayNode $node): string + { + $name = $node->getName(); + if (!str_ends_with($name, 's')) { + return $name; + } + + $parent = $node->getParent(); + $mappings = $parent instanceof ArrayNode ? $parent->getXmlRemappings() : []; + foreach ($mappings as $map) { + if ($map[1] === $name) { + $name = $map[0]; + break; + } + } + + return $name; + } + + private function buildToArray(ClassBuilder $class): void + { + $body = '$output = [];'; + foreach ($class->getProperties() as $p) { + $code = '$this->PROPERTY'; + if (null !== $p->getType()) { + if ($p->isArray()) { + $code = $p->areScalarsAllowed() + ? 'array_map(fn ($v) => $v instanceof CLASS ? $v->toArray() : $v, $this->PROPERTY)' + : 'array_map(fn ($v) => $v->toArray(), $this->PROPERTY)' + ; + } else { + $code = $p->areScalarsAllowed() + ? '$this->PROPERTY instanceof CLASS ? $this->PROPERTY->toArray() : $this->PROPERTY' + : '$this->PROPERTY->toArray()' + ; + } + } + + $body .= strtr(' + if (isset($this->_usedProperties[\'PROPERTY\'])) { + $output[\'ORG_NAME\'] = '.$code.'; + }', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName(), 'CLASS' => $p->getType()]); + } + + $extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : ''; + + $class->addMethod('toArray', ' +public function NAME(): array +{ + '.$body.' + + return $output'.$extraKeys.'; +}'); + } + + private function buildConstructor(ClassBuilder $class): void + { + $body = ''; + foreach ($class->getProperties() as $p) { + $code = '$value[\'ORG_NAME\']'; + if (null !== $p->getType()) { + if ($p->isArray()) { + $code = $p->areScalarsAllowed() + ? 'array_map(fn ($v) => \is_array($v) ? new '.$p->getType().'($v) : $v, $value[\'ORG_NAME\'])' + : 'array_map(fn ($v) => new '.$p->getType().'($v), $value[\'ORG_NAME\'])' + ; + } else { + $code = $p->areScalarsAllowed() + ? '\is_array($value[\'ORG_NAME\']) ? new '.$p->getType().'($value[\'ORG_NAME\']) : $value[\'ORG_NAME\']' + : 'new '.$p->getType().'($value[\'ORG_NAME\'])' + ; + } + } + + $body .= strtr(' + if (array_key_exists(\'ORG_NAME\', $value)) { + $this->_usedProperties[\'PROPERTY\'] = true; + $this->PROPERTY = '.$code.'; + unset($value[\'ORG_NAME\']); + } +', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]); + } + + if ($class->shouldAllowExtraKeys()) { + $body .= ' + $this->_extraKeys = $value; +'; + } else { + $body .= ' + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__).implode(\', \', array_keys($value))); + }'; + + $class->addUse(InvalidConfigurationException::class); + } + + $class->addMethod('__construct', ' +public function __construct(array $value = []) +{'.$body.' +}'); + } + + private function buildSetExtraKey(ClassBuilder $class): void + { + if (!$class->shouldAllowExtraKeys()) { + return; + } + + $class->addUse(ParamConfigurator::class); + + $class->addProperty('_extraKeys'); + + $class->addMethod('set', ' +/** + * @param ParamConfigurator|mixed $value + * + * @return $this + */ +public function NAME(string $key, mixed $value): static +{ + $this->_extraKeys[$key] = $value; + + return $this; +}'); + } + + private function getSubNamespace(ClassBuilder $rootClass): string + { + return sprintf('%s\\%s', $rootClass->getNamespace(), substr($rootClass->getName(), 0, -6)); + } + + private function hasNormalizationClosures(NodeInterface $node): bool + { + try { + $r = new \ReflectionProperty($node, 'normalizationClosures'); + } catch (\ReflectionException) { + return false; + } + + return [] !== $r->getValue($node); + } + + private function getType(string $classType, bool $hasNormalizationClosures): string + { + return $classType.($hasNormalizationClosures ? '|scalar' : ''); + } +} diff --git a/vendor/symfony/config/Builder/ConfigBuilderGeneratorInterface.php b/vendor/symfony/config/Builder/ConfigBuilderGeneratorInterface.php new file mode 100644 index 0000000..c52c9e5 --- /dev/null +++ b/vendor/symfony/config/Builder/ConfigBuilderGeneratorInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Builder; + +use Symfony\Component\Config\Definition\ConfigurationInterface; + +/** + * Generates ConfigBuilders to help create valid config. + * + * @author Tobias Nyholm + */ +interface ConfigBuilderGeneratorInterface +{ + /** + * @return \Closure that will return the root config class + */ + public function build(ConfigurationInterface $configuration): \Closure; +} diff --git a/vendor/symfony/config/Builder/ConfigBuilderInterface.php b/vendor/symfony/config/Builder/ConfigBuilderInterface.php new file mode 100644 index 0000000..fd3129c --- /dev/null +++ b/vendor/symfony/config/Builder/ConfigBuilderInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Builder; + +/** + * A ConfigBuilder provides helper methods to build a large complex array. + * + * @author Tobias Nyholm + */ +interface ConfigBuilderInterface +{ + /** + * Gets all configuration represented as an array. + */ + public function toArray(): array; + + /** + * Gets the alias for the extension which config we are building. + */ + public function getExtensionAlias(): string; +} diff --git a/vendor/symfony/config/Builder/Method.php b/vendor/symfony/config/Builder/Method.php new file mode 100644 index 0000000..8fba068 --- /dev/null +++ b/vendor/symfony/config/Builder/Method.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Builder; + +/** + * Represents a method when building classes. + * + * @internal + * + * @author Tobias Nyholm + */ +class Method +{ + public function __construct( + private string $content, + ) { + } + + public function getContent(): string + { + return $this->content; + } +} diff --git a/vendor/symfony/config/Builder/Property.php b/vendor/symfony/config/Builder/Property.php new file mode 100644 index 0000000..ede5351 --- /dev/null +++ b/vendor/symfony/config/Builder/Property.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Builder; + +/** + * Represents a property when building classes. + * + * @internal + * + * @author Tobias Nyholm + */ +class Property +{ + private bool $array = false; + private bool $scalarsAllowed = false; + private ?string $type = null; + private ?string $content = null; + + public function __construct( + private string $originalName, + private string $name, + ) { + } + + public function getName(): string + { + return $this->name; + } + + public function getOriginalName(): string + { + return $this->originalName; + } + + public function setType(string $type): void + { + $this->array = false; + $this->type = $type; + + if (str_ends_with($type, '|scalar')) { + $this->scalarsAllowed = true; + $this->type = $type = substr($type, 0, -7); + } + + if (str_ends_with($type, '[]')) { + $this->array = true; + $this->type = substr($type, 0, -2); + } + } + + public function getType(): ?string + { + return $this->type; + } + + public function getContent(): ?string + { + return $this->content; + } + + public function setContent(string $content): void + { + $this->content = $content; + } + + public function isArray(): bool + { + return $this->array; + } + + public function areScalarsAllowed(): bool + { + return $this->scalarsAllowed; + } +} diff --git a/vendor/symfony/config/CHANGELOG.md b/vendor/symfony/config/CHANGELOG.md new file mode 100644 index 0000000..1697989 --- /dev/null +++ b/vendor/symfony/config/CHANGELOG.md @@ -0,0 +1,164 @@ +CHANGELOG +========= + +7.1 +--- + + * Allow custom meta location in `ResourceCheckerConfigCache` + * Allow custom meta location in `ConfigCache` + +7.0 +--- + + * Require explicit argument when calling `NodeBuilder::setParent()` + +6.3 +--- + + * Allow enum values in `EnumNode` + +6.2 +--- + + * Deprecate calling `NodeBuilder::setParent()` without any arguments + * Add a more accurate typehint in generated PHP config + +6.1 +--- + + * Allow using environment variables in `EnumNode` + * Add Node's information in generated Config + * Add `DefinitionFileLoader` class to load a TreeBuilder definition from an external file + * Add `DefinitionConfigurator` helper + +6.0 +--- + + * Remove `BaseNode::getDeprecationMessage()` + +5.3.0 +----- + + * Add support for generating `ConfigBuilder` for extensions + +5.1.0 +----- + + * updated the signature of method `NodeDefinition::setDeprecated()` to `NodeDefinition::setDeprecation(string $package, string $version, string $message)` + * updated the signature of method `BaseNode::setDeprecated()` to `BaseNode::setDeprecation(string $package, string $version, string $message)` + * deprecated passing a null message to `BaseNode::setDeprecated()` to un-deprecate a node + * deprecated `BaseNode::getDeprecationMessage()`, use `BaseNode::getDeprecation()` instead + +5.0.0 +----- + + * Dropped support for constructing a `TreeBuilder` without passing root node information. + * Removed the `root()` method in `TreeBuilder`, pass the root node information to the constructor instead + * Added method `getChildNodeDefinitions()` to ParentNodeDefinitionInterface + * Removed `FileLoaderLoadException`, use `LoaderLoadException` instead + +4.4.0 +----- + + * added a way to exclude patterns of resources from being imported by the `import()` method + +4.3.0 +----- + + * deprecated using environment variables with `cannotBeEmpty()` if the value is validated with `validate()` + * made `Resource\*` classes final and not implement `Serializable` anymore + * deprecated the `root()` method in `TreeBuilder`, pass the root node information to the constructor instead + +4.2.0 +----- + + * deprecated constructing a `TreeBuilder` without passing root node information + * renamed `FileLoaderLoadException` to `LoaderLoadException` + +4.1.0 +----- + + * added `setPathSeparator` method to `NodeBuilder` class + * added third `$pathSeparator` constructor argument to `BaseNode` + * the `Processor` class has been made final + +4.0.0 +----- + + * removed `ConfigCachePass` + +3.4.0 +----- + + * added `setDeprecated()` method to indicate a deprecated node + * added `XmlUtils::parse()` method to parse an XML string + * deprecated `ConfigCachePass` + +3.3.0 +----- + + * added `ReflectionClassResource` class + * added second `$exists` constructor argument to `ClassExistenceResource` + * made `ClassExistenceResource` work with interfaces and traits + * added `ConfigCachePass` (originally in FrameworkBundle) + * added `castToArray()` helper to turn any config value into an array + +3.0.0 +----- + + * removed `ReferenceDumper` class + * removed the `ResourceInterface::isFresh()` method + * removed `BCResourceInterfaceChecker` class + * removed `ResourceInterface::getResource()` method + +2.8.0 +----- + +The edge case of defining just one value for nodes of type Enum is now allowed: + +```php +$rootNode + ->children() + ->enumNode('variable') + ->values(['value']) + ->end() + ->end() +; +``` + +Before: `InvalidArgumentException` (variable must contain at least two +distinct elements). +After: the code will work as expected and it will restrict the values of the +`variable` option to just `value`. + + * deprecated the `ResourceInterface::isFresh()` method. If you implement custom resource types and they + can be validated that way, make them implement the new `SelfCheckingResourceInterface`. + * deprecated the getResource() method in ResourceInterface. You can still call this method + on concrete classes implementing the interface, but it does not make sense at the interface + level as you need to know about the particular type of resource at hand to understand the + semantics of the returned value. + +2.7.0 +----- + + * added `ConfigCacheInterface`, `ConfigCacheFactoryInterface` and a basic `ConfigCacheFactory` + implementation to delegate creation of ConfigCache instances + +2.2.0 +----- + + * added `ArrayNodeDefinition::canBeEnabled()` and `ArrayNodeDefinition::canBeDisabled()` + to ease configuration when some sections are respectively disabled / enabled + by default. + * added a `normalizeKeys()` method for array nodes (to avoid key normalization) + * added numerical type handling for config definitions + * added convenience methods for optional configuration sections to `ArrayNodeDefinition` + * added a utils class for XML manipulations + +2.1.0 +----- + + * added a way to add documentation on configuration + * implemented `Serializable` on resources + * `LoaderResolverInterface` is now used instead of `LoaderResolver` for type + hinting diff --git a/vendor/symfony/config/ConfigCache.php b/vendor/symfony/config/ConfigCache.php new file mode 100644 index 0000000..7c87b9b --- /dev/null +++ b/vendor/symfony/config/ConfigCache.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; + +/** + * ConfigCache caches arbitrary content in files on disk. + * + * When in debug mode, those metadata resources that implement + * \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will + * be used to check cache freshness. + * + * @author Fabien Potencier + * @author Matthias Pigulla + */ +class ConfigCache extends ResourceCheckerConfigCache +{ + /** + * @param string $file The absolute cache path + * @param bool $debug Whether debugging is enabled or not + * @param string|null $metaFile The absolute path to the meta file + */ + public function __construct( + string $file, + private bool $debug, + ?string $metaFile = null, + ) { + $checkers = []; + if (true === $this->debug) { + $checkers = [new SelfCheckingResourceChecker()]; + } + + parent::__construct($file, $checkers, $metaFile); + } + + /** + * Checks if the cache is still fresh. + * + * This implementation always returns true when debug is off and the + * cache file exists. + */ + public function isFresh(): bool + { + if (!$this->debug && is_file($this->getPath())) { + return true; + } + + return parent::isFresh(); + } +} diff --git a/vendor/symfony/config/ConfigCacheFactory.php b/vendor/symfony/config/ConfigCacheFactory.php new file mode 100644 index 0000000..27a18e7 --- /dev/null +++ b/vendor/symfony/config/ConfigCacheFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * Basic implementation of ConfigCacheFactoryInterface that + * creates an instance of the default ConfigCache. + * + * This factory and/or cache do not support cache validation + * by means of ResourceChecker instances (that is, service-based). + * + * @author Matthias Pigulla + */ +class ConfigCacheFactory implements ConfigCacheFactoryInterface +{ + /** + * @param bool $debug The debug flag to pass to ConfigCache + */ + public function __construct( + private bool $debug, + ) { + } + + public function cache(string $file, callable $callback): ConfigCacheInterface + { + $cache = new ConfigCache($file, $this->debug); + if (!$cache->isFresh()) { + $callback($cache); + } + + return $cache; + } +} diff --git a/vendor/symfony/config/ConfigCacheFactoryInterface.php b/vendor/symfony/config/ConfigCacheFactoryInterface.php new file mode 100644 index 0000000..01c90d1 --- /dev/null +++ b/vendor/symfony/config/ConfigCacheFactoryInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * Interface for a ConfigCache factory. This factory creates + * an instance of ConfigCacheInterface and initializes the + * cache if necessary. + * + * @author Matthias Pigulla + */ +interface ConfigCacheFactoryInterface +{ + /** + * Creates a cache instance and (re-)initializes it if necessary. + * + * @param string $file The absolute cache file path + * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback + */ + public function cache(string $file, callable $callable): ConfigCacheInterface; +} diff --git a/vendor/symfony/config/ConfigCacheInterface.php b/vendor/symfony/config/ConfigCacheInterface.php new file mode 100644 index 0000000..7b9d388 --- /dev/null +++ b/vendor/symfony/config/ConfigCacheInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Interface for ConfigCache. + * + * @author Matthias Pigulla + */ +interface ConfigCacheInterface +{ + /** + * Gets the cache file path. + */ + public function getPath(): string; + + /** + * Checks if the cache is still fresh. + * + * This check should take the metadata passed to the write() method into consideration. + */ + public function isFresh(): bool; + + /** + * Writes the given content into the cache file. Metadata will be stored + * independently and can be used to check cache freshness at a later time. + * + * @param string $content The content to write into the cache + * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances + * + * @throws \RuntimeException When the cache file cannot be written + */ + public function write(string $content, ?array $metadata = null): void; +} diff --git a/vendor/symfony/config/Definition/ArrayNode.php b/vendor/symfony/config/Definition/ArrayNode.php new file mode 100644 index 0000000..15ad478 --- /dev/null +++ b/vendor/symfony/config/Definition/ArrayNode.php @@ -0,0 +1,379 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * Represents an Array node in the config tree. + * + * @author Johannes M. Schmitt + */ +class ArrayNode extends BaseNode implements PrototypeNodeInterface +{ + protected array $xmlRemappings = []; + protected array $children = []; + protected bool $allowFalse = false; + protected bool $allowNewKeys = true; + protected bool $addIfNotSet = false; + protected bool $performDeepMerging = true; + protected bool $ignoreExtraKeys = false; + protected bool $removeExtraKeys = true; + protected bool $normalizeKeys = true; + + public function setNormalizeKeys(bool $normalizeKeys): void + { + $this->normalizeKeys = $normalizeKeys; + } + + /** + * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML. + * After running this method, all keys are normalized to foo_bar. + * + * If you have a mixed key like foo-bar_moo, it will not be altered. + * The key will also not be altered if the target key already exists. + */ + protected function preNormalize(mixed $value): mixed + { + if (!$this->normalizeKeys || !\is_array($value)) { + return $value; + } + + $normalized = []; + + foreach ($value as $k => $v) { + if (str_contains($k, '-') && !str_contains($k, '_') && !\array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) { + $normalized[$normalizedKey] = $v; + } else { + $normalized[$k] = $v; + } + } + + return $normalized; + } + + /** + * Retrieves the children of this node. + * + * @return array + */ + public function getChildren(): array + { + return $this->children; + } + + /** + * Sets the xml remappings that should be performed. + * + * @param array $remappings An array of the form [[string, string]] + */ + public function setXmlRemappings(array $remappings): void + { + $this->xmlRemappings = $remappings; + } + + /** + * Gets the xml remappings that should be performed. + * + * @return array an array of the form [[string, string]] + */ + public function getXmlRemappings(): array + { + return $this->xmlRemappings; + } + + /** + * Sets whether to add default values for this array if it has not been + * defined in any of the configuration files. + */ + public function setAddIfNotSet(bool $boolean): void + { + $this->addIfNotSet = $boolean; + } + + /** + * Sets whether false is allowed as value indicating that the array should be unset. + */ + public function setAllowFalse(bool $allow): void + { + $this->allowFalse = $allow; + } + + /** + * Sets whether new keys can be defined in subsequent configurations. + */ + public function setAllowNewKeys(bool $allow): void + { + $this->allowNewKeys = $allow; + } + + /** + * Sets if deep merging should occur. + */ + public function setPerformDeepMerging(bool $boolean): void + { + $this->performDeepMerging = $boolean; + } + + /** + * Whether extra keys should just be ignored without an exception. + * + * @param bool $boolean To allow extra keys + * @param bool $remove To remove extra keys + */ + public function setIgnoreExtraKeys(bool $boolean, bool $remove = true): void + { + $this->ignoreExtraKeys = $boolean; + $this->removeExtraKeys = $this->ignoreExtraKeys && $remove; + } + + /** + * Returns true when extra keys should be ignored without an exception. + */ + public function shouldIgnoreExtraKeys(): bool + { + return $this->ignoreExtraKeys; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function hasDefaultValue(): bool + { + return $this->addIfNotSet; + } + + public function getDefaultValue(): mixed + { + if (!$this->hasDefaultValue()) { + throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath())); + } + + $defaults = []; + foreach ($this->children as $name => $child) { + if ($child->hasDefaultValue()) { + $defaults[$name] = $child->getDefaultValue(); + } + } + + return $defaults; + } + + /** + * Adds a child node. + * + * @throws \InvalidArgumentException when the child node has no name + * @throws \InvalidArgumentException when the child node's name is not unique + */ + public function addChild(NodeInterface $node): void + { + $name = $node->getName(); + if ('' === $name) { + throw new \InvalidArgumentException('Child nodes must be named.'); + } + if (isset($this->children[$name])) { + throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name)); + } + + $this->children[$name] = $node; + } + + /** + * @throws UnsetKeyException + * @throws InvalidConfigurationException if the node doesn't have enough children + */ + protected function finalizeValue(mixed $value): mixed + { + if (false === $value) { + throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); + } + + foreach ($this->children as $name => $child) { + if (!\array_key_exists($name, $value)) { + if ($child->isRequired()) { + $message = sprintf('The child config "%s" under "%s" must be configured', $name, $this->getPath()); + if ($child->getInfo()) { + $message .= sprintf(': %s', $child->getInfo()); + } else { + $message .= '.'; + } + $ex = new InvalidConfigurationException($message); + $ex->setPath($this->getPath()); + + throw $ex; + } + + if ($child->hasDefaultValue()) { + $value[$name] = $child->getDefaultValue(); + } + + continue; + } + + if ($child->isDeprecated()) { + $deprecation = $child->getDeprecation($name, $this->getPath()); + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + try { + $value[$name] = $child->finalize($value[$name]); + } catch (UnsetKeyException) { + unset($value[$name]); + } + } + + return $value; + } + + protected function validateType(mixed $value): void + { + if (!\is_array($value) && (!$this->allowFalse || false !== $value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), get_debug_type($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + /** + * @throws InvalidConfigurationException + */ + protected function normalizeValue(mixed $value): mixed + { + if (false === $value) { + return $value; + } + + $value = $this->remapXml($value); + + $normalized = []; + foreach ($value as $name => $val) { + if (isset($this->children[$name])) { + try { + $normalized[$name] = $this->children[$name]->normalize($val); + } catch (UnsetKeyException) { + } + unset($value[$name]); + } elseif (!$this->removeExtraKeys) { + $normalized[$name] = $val; + } + } + + // if extra fields are present, throw exception + if (\count($value) && !$this->ignoreExtraKeys) { + $proposals = array_keys($this->children); + sort($proposals); + $guesses = []; + + foreach (array_keys($value) as $subject) { + $minScore = \INF; + foreach ($proposals as $proposal) { + $distance = levenshtein($subject, $proposal); + if ($distance <= $minScore && $distance < 3) { + $guesses[$proposal] = $distance; + $minScore = $distance; + } + } + } + + $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath()); + + if (\count($guesses)) { + asort($guesses); + $msg .= sprintf('. Did you mean "%s"?', implode('", "', array_keys($guesses))); + } else { + $msg .= sprintf('. Available option%s %s "%s".', 1 === \count($proposals) ? '' : 's', 1 === \count($proposals) ? 'is' : 'are', implode('", "', $proposals)); + } + + $ex = new InvalidConfigurationException($msg); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $normalized; + } + + /** + * Remaps multiple singular values to a single plural value. + */ + protected function remapXml(array $value): array + { + foreach ($this->xmlRemappings as [$singular, $plural]) { + if (!isset($value[$singular])) { + continue; + } + + $value[$plural] = Processor::normalizeConfig($value, $singular, $plural); + unset($value[$singular]); + } + + return $value; + } + + /** + * @throws InvalidConfigurationException + * @throws \RuntimeException + */ + protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed + { + if (false === $rightSide) { + // if this is still false after the last config has been merged the + // finalization pass will take care of removing this key entirely + return false; + } + + if (false === $leftSide || !$this->performDeepMerging) { + return $rightSide; + } + + foreach ($rightSide as $k => $v) { + // no conflict + if (!\array_key_exists($k, $leftSide)) { + if (!$this->allowNewKeys) { + $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + + $leftSide[$k] = $v; + continue; + } + + if (!isset($this->children[$k])) { + if (!$this->ignoreExtraKeys || $this->removeExtraKeys) { + throw new \RuntimeException('merge() expects a normalized config array.'); + } + + $leftSide[$k] = $v; + continue; + } + + $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v); + } + + return $leftSide; + } + + protected function allowPlaceholders(): bool + { + return false; + } +} diff --git a/vendor/symfony/config/Definition/BaseNode.php b/vendor/symfony/config/Definition/BaseNode.php new file mode 100644 index 0000000..180d597 --- /dev/null +++ b/vendor/symfony/config/Definition/BaseNode.php @@ -0,0 +1,512 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * The base node class. + * + * @author Johannes M. Schmitt + */ +abstract class BaseNode implements NodeInterface +{ + public const DEFAULT_PATH_SEPARATOR = '.'; + + private static array $placeholderUniquePrefixes = []; + private static array $placeholders = []; + + protected string $name; + protected array $normalizationClosures = []; + protected array $normalizedTypes = []; + protected array $finalValidationClosures = []; + protected bool $allowOverwrite = true; + protected bool $required = false; + protected array $deprecation = []; + protected array $equivalentValues = []; + protected array $attributes = []; + + private mixed $handlingPlaceholder = null; + + /** + * @throws \InvalidArgumentException if the name contains a period + */ + public function __construct( + ?string $name, + protected ?NodeInterface $parent = null, + protected string $pathSeparator = self::DEFAULT_PATH_SEPARATOR, + ) { + if (str_contains($name = (string) $name, $pathSeparator)) { + throw new \InvalidArgumentException('The name must not contain ".'.$pathSeparator.'".'); + } + + $this->name = $name; + } + + /** + * Register possible (dummy) values for a dynamic placeholder value. + * + * Matching configuration values will be processed with a provided value, one by one. After a provided value is + * successfully processed the configuration value is returned as is, thus preserving the placeholder. + * + * @internal + */ + public static function setPlaceholder(string $placeholder, array $values): void + { + if (!$values) { + throw new \InvalidArgumentException('At least one value must be provided.'); + } + + self::$placeholders[$placeholder] = $values; + } + + /** + * Adds a common prefix for dynamic placeholder values. + * + * Matching configuration values will be skipped from being processed and are returned as is, thus preserving the + * placeholder. An exact match provided by {@see setPlaceholder()} might take precedence. + * + * @internal + */ + public static function setPlaceholderUniquePrefix(string $prefix): void + { + self::$placeholderUniquePrefixes[] = $prefix; + } + + /** + * Resets all current placeholders available. + * + * @internal + */ + public static function resetPlaceholders(): void + { + self::$placeholderUniquePrefixes = []; + self::$placeholders = []; + } + + public function setAttribute(string $key, mixed $value): void + { + $this->attributes[$key] = $value; + } + + public function getAttribute(string $key, mixed $default = null): mixed + { + return $this->attributes[$key] ?? $default; + } + + public function hasAttribute(string $key): bool + { + return isset($this->attributes[$key]); + } + + public function getAttributes(): array + { + return $this->attributes; + } + + public function setAttributes(array $attributes): void + { + $this->attributes = $attributes; + } + + public function removeAttribute(string $key): void + { + unset($this->attributes[$key]); + } + + /** + * Sets an info message. + */ + public function setInfo(string $info): void + { + $this->setAttribute('info', $info); + } + + /** + * Returns info message. + */ + public function getInfo(): ?string + { + return $this->getAttribute('info'); + } + + /** + * Sets the example configuration for this node. + */ + public function setExample(string|array $example): void + { + $this->setAttribute('example', $example); + } + + /** + * Retrieves the example configuration for this node. + */ + public function getExample(): string|array|null + { + return $this->getAttribute('example'); + } + + /** + * Adds an equivalent value. + */ + public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue): void + { + $this->equivalentValues[] = [$originalValue, $equivalentValue]; + } + + /** + * Set this node as required. + */ + public function setRequired(bool $boolean): void + { + $this->required = $boolean; + } + + /** + * Sets this node as deprecated. + * + * You can use %node% and %path% placeholders in your message to display, + * respectively, the node name and its complete path. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message the deprecation message to use + */ + public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.'): void + { + $this->deprecation = [ + 'package' => $package, + 'version' => $version, + 'message' => $message, + ]; + } + + /** + * Sets if this node can be overridden. + */ + public function setAllowOverwrite(bool $allow): void + { + $this->allowOverwrite = $allow; + } + + /** + * Sets the closures used for normalization. + * + * @param \Closure[] $closures An array of Closures used for normalization + */ + public function setNormalizationClosures(array $closures): void + { + $this->normalizationClosures = $closures; + } + + /** + * Sets the list of types supported by normalization. + * + * see ExprBuilder::TYPE_* constants. + */ + public function setNormalizedTypes(array $types): void + { + $this->normalizedTypes = $types; + } + + /** + * Gets the list of types supported by normalization. + * + * see ExprBuilder::TYPE_* constants. + */ + public function getNormalizedTypes(): array + { + return $this->normalizedTypes; + } + + /** + * Sets the closures used for final validation. + * + * @param \Closure[] $closures An array of Closures used for final validation + */ + public function setFinalValidationClosures(array $closures): void + { + $this->finalValidationClosures = $closures; + } + + public function isRequired(): bool + { + return $this->required; + } + + /** + * Checks if this node is deprecated. + */ + public function isDeprecated(): bool + { + return (bool) $this->deprecation; + } + + /** + * @param string $node The configuration node name + * @param string $path The path of the node + */ + public function getDeprecation(string $node, string $path): array + { + return [ + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => strtr($this->deprecation['message'], ['%node%' => $node, '%path%' => $path]), + ]; + } + + public function getName(): string + { + return $this->name; + } + + public function getPath(): string + { + if (null !== $this->parent) { + return $this->parent->getPath().$this->pathSeparator.$this->name; + } + + return $this->name; + } + + final public function merge(mixed $leftSide, mixed $rightSide): mixed + { + if (!$this->allowOverwrite) { + throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath())); + } + + if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) { + foreach ($leftPlaceholders as $leftPlaceholder) { + $this->handlingPlaceholder = $leftSide; + try { + $this->merge($leftPlaceholder, $rightSide); + } finally { + $this->handlingPlaceholder = null; + } + } + + return $rightSide; + } + + if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) { + foreach ($rightPlaceholders as $rightPlaceholder) { + $this->handlingPlaceholder = $rightSide; + try { + $this->merge($leftSide, $rightPlaceholder); + } finally { + $this->handlingPlaceholder = null; + } + } + + return $rightSide; + } + + $this->doValidateType($leftSide); + $this->doValidateType($rightSide); + + return $this->mergeValues($leftSide, $rightSide); + } + + final public function normalize(mixed $value): mixed + { + $value = $this->preNormalize($value); + + // run custom normalization closures + foreach ($this->normalizationClosures as $closure) { + $value = $closure($value); + } + + // resolve placeholder value + if ($value !== $placeholders = self::resolvePlaceholderValue($value)) { + foreach ($placeholders as $placeholder) { + $this->handlingPlaceholder = $value; + try { + $this->normalize($placeholder); + } finally { + $this->handlingPlaceholder = null; + } + } + + return $value; + } + + // replace value with their equivalent + foreach ($this->equivalentValues as $data) { + if ($data[0] === $value) { + $value = $data[1]; + } + } + + // validate type + $this->doValidateType($value); + + // normalize value + return $this->normalizeValue($value); + } + + /** + * Normalizes the value before any other normalization is applied. + */ + protected function preNormalize(mixed $value): mixed + { + return $value; + } + + /** + * Returns parent node for this node. + */ + public function getParent(): ?NodeInterface + { + return $this->parent; + } + + final public function finalize(mixed $value): mixed + { + if ($value !== $placeholders = self::resolvePlaceholderValue($value)) { + foreach ($placeholders as $placeholder) { + $this->handlingPlaceholder = $value; + try { + $this->finalize($placeholder); + } finally { + $this->handlingPlaceholder = null; + } + } + + return $value; + } + + $this->doValidateType($value); + + $value = $this->finalizeValue($value); + + // Perform validation on the final value if a closure has been set. + // The closure is also allowed to return another value. + foreach ($this->finalValidationClosures as $closure) { + try { + $value = $closure($value); + } catch (Exception $e) { + if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) { + continue; + } + + throw $e; + } catch (\Exception $e) { + throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": ', $this->getPath()).$e->getMessage(), $e->getCode(), $e); + } + } + + return $value; + } + + /** + * Validates the type of a Node. + * + * @throws InvalidTypeException when the value is invalid + */ + abstract protected function validateType(mixed $value): void; + + /** + * Normalizes the value. + */ + abstract protected function normalizeValue(mixed $value): mixed; + + /** + * Merges two values together. + */ + abstract protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed; + + /** + * Finalizes a value. + */ + abstract protected function finalizeValue(mixed $value): mixed; + + /** + * Tests if placeholder values are allowed for this node. + */ + protected function allowPlaceholders(): bool + { + return true; + } + + /** + * Tests if a placeholder is being handled currently. + */ + protected function isHandlingPlaceholder(): bool + { + return null !== $this->handlingPlaceholder; + } + + /** + * Gets allowed dynamic types for this node. + */ + protected function getValidPlaceholderTypes(): array + { + return []; + } + + private static function resolvePlaceholderValue(mixed $value): mixed + { + if (\is_string($value)) { + if (isset(self::$placeholders[$value])) { + return self::$placeholders[$value]; + } + + foreach (self::$placeholderUniquePrefixes as $placeholderUniquePrefix) { + if (str_starts_with($value, $placeholderUniquePrefix)) { + return []; + } + } + } + + return $value; + } + + private function doValidateType(mixed $value): void + { + if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) { + $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath())); + $e->setPath($this->getPath()); + + throw $e; + } + + if (null === $this->handlingPlaceholder || null === $value) { + $this->validateType($value); + + return; + } + + $knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]); + $validTypes = $this->getValidPlaceholderTypes(); + + if ($validTypes && array_diff($knownTypes, $validTypes)) { + $e = new InvalidTypeException(sprintf( + 'Invalid type for path "%s". Expected %s, but got %s.', + $this->getPath(), + 1 === \count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"', + 1 === \count($knownTypes) ? '"'.reset($knownTypes).'"' : 'one of "'.implode('", "', $knownTypes).'"' + )); + if ($hint = $this->getInfo()) { + $e->addHint($hint); + } + $e->setPath($this->getPath()); + + throw $e; + } + + $this->validateType($value); + } +} diff --git a/vendor/symfony/config/Definition/BooleanNode.php b/vendor/symfony/config/Definition/BooleanNode.php new file mode 100644 index 0000000..f6ab5bf --- /dev/null +++ b/vendor/symfony/config/Definition/BooleanNode.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a Boolean value in the config tree. + * + * @author Johannes M. Schmitt + */ +class BooleanNode extends ScalarNode +{ + protected function validateType(mixed $value): void + { + if (!\is_bool($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), get_debug_type($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + protected function isValueEmpty(mixed $value): bool + { + // a boolean value cannot be empty + return false; + } + + protected function getValidPlaceholderTypes(): array + { + return ['bool']; + } +} diff --git a/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php new file mode 100644 index 0000000..4596151 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php @@ -0,0 +1,509 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\PrototypedArrayNode; + +/** + * This class provides a fluent interface for defining an array node. + * + * @author Johannes M. Schmitt + */ +class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface +{ + protected bool $performDeepMerging = true; + protected bool $ignoreExtraKeys = false; + protected bool $removeExtraKeys = true; + protected array $children = []; + protected NodeDefinition $prototype; + protected bool $atLeastOne = false; + protected bool $allowNewKeys = true; + protected ?string $key = null; + protected bool $removeKeyItem = false; + protected bool $addDefaults = false; + protected int|string|array|false|null $addDefaultChildren = false; + protected NodeBuilder $nodeBuilder; + protected bool $normalizeKeys = true; + + public function __construct(?string $name, ?NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = []; + $this->trueEquivalent = []; + } + + public function setBuilder(NodeBuilder $builder): void + { + $this->nodeBuilder = $builder; + } + + public function children(): NodeBuilder + { + return $this->getNodeBuilder(); + } + + /** + * Sets a prototype for child nodes. + */ + public function prototype(string $type): NodeDefinition + { + return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this); + } + + public function variablePrototype(): VariableNodeDefinition + { + return $this->prototype('variable'); + } + + public function scalarPrototype(): ScalarNodeDefinition + { + return $this->prototype('scalar'); + } + + public function booleanPrototype(): BooleanNodeDefinition + { + return $this->prototype('boolean'); + } + + public function integerPrototype(): IntegerNodeDefinition + { + return $this->prototype('integer'); + } + + public function floatPrototype(): FloatNodeDefinition + { + return $this->prototype('float'); + } + + public function arrayPrototype(): self + { + return $this->prototype('array'); + } + + public function enumPrototype(): EnumNodeDefinition + { + return $this->prototype('enum'); + } + + /** + * Adds the default value if the node is not set in the configuration. + * + * This method is applicable to concrete nodes only (not to prototype nodes). + * If this function has been called and the node is not set during the finalization + * phase, it's default value will be derived from its children default values. + * + * @return $this + */ + public function addDefaultsIfNotSet(): static + { + $this->addDefaults = true; + + return $this; + } + + /** + * Adds children with a default value when none are defined. + * + * This method is applicable to prototype nodes only. + * + * @param int|string|array|null $children The number of children|The child name|The children names to be added + * + * @return $this + */ + public function addDefaultChildrenIfNoneSet(int|string|array|null $children = null): static + { + $this->addDefaultChildren = $children; + + return $this; + } + + /** + * Requires the node to have at least one element. + * + * This method is applicable to prototype nodes only. + * + * @return $this + */ + public function requiresAtLeastOneElement(): static + { + $this->atLeastOne = true; + + return $this; + } + + /** + * Disallows adding news keys in a subsequent configuration. + * + * If used all keys have to be defined in the same configuration file. + * + * @return $this + */ + public function disallowNewKeysInSubsequentConfigs(): static + { + $this->allowNewKeys = false; + + return $this; + } + + /** + * Sets a normalization rule for XML configurations. + * + * @param string $singular The key to remap + * @param string|null $plural The plural of the key for irregular plurals + * + * @return $this + */ + public function fixXmlConfig(string $singular, ?string $plural = null): static + { + $this->normalization()->remap($singular, $plural); + + return $this; + } + + /** + * Sets the attribute which value is to be used as key. + * + * This is useful when you have an indexed array that should be an + * associative array. You can select an item from within the array + * to be the key of the particular item. For example, if "id" is the + * "key", then: + * + * [ + * ['id' => 'my_name', 'foo' => 'bar'], + * ]; + * + * becomes + * + * [ + * 'my_name' => ['foo' => 'bar'], + * ]; + * + * If you'd like "'id' => 'my_name'" to still be present in the resulting + * array, then you can set the second argument of this method to false. + * + * This method is applicable to prototype nodes only. + * + * @param string $name The name of the key + * @param bool $removeKeyItem Whether or not the key item should be removed + * + * @return $this + */ + public function useAttributeAsKey(string $name, bool $removeKeyItem = true): static + { + $this->key = $name; + $this->removeKeyItem = $removeKeyItem; + + return $this; + } + + /** + * Sets whether the node can be unset. + * + * @return $this + */ + public function canBeUnset(bool $allow = true): static + { + $this->merge()->allowUnset($allow); + + return $this; + } + + /** + * Adds an "enabled" boolean to enable the current section. + * + * By default, the section is disabled. If any configuration is specified then + * the node will be automatically enabled: + * + * enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden + * enableableArrayNode: ~ # The config is enabled & use the default values + * enableableArrayNode: true # The config is enabled & use the default values + * enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden + * enableableArrayNode: {enabled: false, ...} # The config is disabled + * enableableArrayNode: false # The config is disabled + * + * @return $this + */ + public function canBeEnabled(): static + { + $this + ->addDefaultsIfNotSet() + ->treatFalseLike(['enabled' => false]) + ->treatTrueLike(['enabled' => true]) + ->treatNullLike(['enabled' => true]) + ->beforeNormalization() + ->ifArray() + ->then(function (array $v) { + $v['enabled'] ??= true; + + return $v; + }) + ->end() + ->children() + ->booleanNode('enabled') + ->defaultFalse() + ; + + return $this; + } + + /** + * Adds an "enabled" boolean to enable the current section. + * + * By default, the section is enabled. + * + * @return $this + */ + public function canBeDisabled(): static + { + $this + ->addDefaultsIfNotSet() + ->treatFalseLike(['enabled' => false]) + ->treatTrueLike(['enabled' => true]) + ->treatNullLike(['enabled' => true]) + ->children() + ->booleanNode('enabled') + ->defaultTrue() + ; + + return $this; + } + + /** + * Disables the deep merging of the node. + * + * @return $this + */ + public function performNoDeepMerging(): static + { + $this->performDeepMerging = false; + + return $this; + } + + /** + * Allows extra config keys to be specified under an array without + * throwing an exception. + * + * Those config values are ignored and removed from the resulting + * array. This should be used only in special cases where you want + * to send an entire configuration array through a special tree that + * processes only part of the array. + * + * @param bool $remove Whether to remove the extra keys + * + * @return $this + */ + public function ignoreExtraKeys(bool $remove = true): static + { + $this->ignoreExtraKeys = true; + $this->removeExtraKeys = $remove; + + return $this; + } + + /** + * Sets whether to enable key normalization. + * + * @return $this + */ + public function normalizeKeys(bool $bool): static + { + $this->normalizeKeys = $bool; + + return $this; + } + + public function append(NodeDefinition $node): static + { + $this->children[$node->name] = $node->setParent($this); + + return $this; + } + + /** + * Returns a node builder to be used to add children and prototype. + */ + protected function getNodeBuilder(): NodeBuilder + { + $this->nodeBuilder ??= new NodeBuilder(); + + return $this->nodeBuilder->setParent($this); + } + + protected function createNode(): NodeInterface + { + if (!isset($this->prototype)) { + $node = new ArrayNode($this->name, $this->parent, $this->pathSeparator); + + $this->validateConcreteNode($node); + + $node->setAddIfNotSet($this->addDefaults); + + foreach ($this->children as $child) { + $child->parent = $node; + $node->addChild($child->getNode()); + } + } else { + $node = new PrototypedArrayNode($this->name, $this->parent, $this->pathSeparator); + + $this->validatePrototypeNode($node); + + if (null !== $this->key) { + $node->setKeyAttribute($this->key, $this->removeKeyItem); + } + + if (true === $this->atLeastOne || false === $this->allowEmptyValue) { + $node->setMinNumberOfElements(1); + } + + if ($this->default) { + if (!\is_array($this->defaultValue)) { + throw new \InvalidArgumentException(sprintf('%s: the default value of an array node has to be an array.', $node->getPath())); + } + + $node->setDefaultValue($this->defaultValue); + } + + if (false !== $this->addDefaultChildren) { + $node->setAddChildrenIfNoneSet($this->addDefaultChildren); + if ($this->prototype instanceof static && !isset($this->prototype->prototype)) { + $this->prototype->addDefaultsIfNotSet(); + } + } + + $this->prototype->parent = $node; + $node->setPrototype($this->prototype->getNode()); + } + + $node->setAllowNewKeys($this->allowNewKeys); + $node->addEquivalentValue(null, $this->nullEquivalent); + $node->addEquivalentValue(true, $this->trueEquivalent); + $node->addEquivalentValue(false, $this->falseEquivalent); + $node->setPerformDeepMerging($this->performDeepMerging); + $node->setRequired($this->required); + $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys); + $node->setNormalizeKeys($this->normalizeKeys); + + if ($this->deprecation) { + $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']); + } + + if (isset($this->normalization)) { + $node->setNormalizationClosures($this->normalization->before); + $node->setNormalizedTypes($this->normalization->declaredTypes); + $node->setXmlRemappings($this->normalization->remappings); + } + + if (isset($this->merge)) { + $node->setAllowOverwrite($this->merge->allowOverwrite); + $node->setAllowFalse($this->merge->allowFalse); + } + + if (isset($this->validation)) { + $node->setFinalValidationClosures($this->validation->rules); + } + + return $node; + } + + /** + * Validate the configuration of a concrete node. + * + * @throws InvalidDefinitionException + */ + protected function validateConcreteNode(ArrayNode $node): void + { + $path = $node->getPath(); + + if (null !== $this->key) { + throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".', $path)); + } + + if (false === $this->allowEmptyValue) { + throw new InvalidDefinitionException(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".', $path)); + } + + if (true === $this->atLeastOne) { + throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".', $path)); + } + + if ($this->default) { + throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".', $path)); + } + + if (false !== $this->addDefaultChildren) { + throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".', $path)); + } + } + + /** + * Validate the configuration of a prototype node. + * + * @throws InvalidDefinitionException + */ + protected function validatePrototypeNode(PrototypedArrayNode $node): void + { + $path = $node->getPath(); + + if ($this->addDefaults) { + throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".', $path)); + } + + if (false !== $this->addDefaultChildren) { + if ($this->default) { + throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s".', $path)); + } + + if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) { + throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".', $path)); + } + + if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) { + throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".', $path)); + } + } + } + + /** + * @return NodeDefinition[] + */ + public function getChildNodeDefinitions(): array + { + return $this->children; + } + + /** + * Finds a node defined by the given $nodePath. + * + * @param string $nodePath The path of the node to find. e.g "doctrine.orm.mappings" + */ + public function find(string $nodePath): NodeDefinition + { + $firstPathSegment = (false === $pathSeparatorPos = strpos($nodePath, $this->pathSeparator)) + ? $nodePath + : substr($nodePath, 0, $pathSeparatorPos); + + if (null === $node = ($this->children[$firstPathSegment] ?? null)) { + throw new \RuntimeException(sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name)); + } + + if (false === $pathSeparatorPos) { + return $node; + } + + return $node->find(substr($nodePath, $pathSeparatorPos + \strlen($this->pathSeparator))); + } +} diff --git a/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php new file mode 100644 index 0000000..15e6396 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\BooleanNode; +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class BooleanNodeDefinition extends ScalarNodeDefinition +{ + public function __construct(?string $name, ?NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = true; + } + + /** + * Instantiate a Node. + */ + protected function instantiateNode(): BooleanNode + { + return new BooleanNode($this->name, $this->parent, $this->pathSeparator); + } + + /** + * @throws InvalidDefinitionException + */ + public function cannotBeEmpty(): static + { + throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.'); + } +} diff --git a/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php b/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php new file mode 100644 index 0000000..cf646a1 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * An interface that can be implemented by nodes which build other nodes. + * + * @author Roland Franssen + */ +interface BuilderAwareInterface +{ + /** + * Sets a custom children builder. + */ + public function setBuilder(NodeBuilder $builder): void; +} diff --git a/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php new file mode 100644 index 0000000..99f3181 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\EnumNode; + +/** + * Enum Node Definition. + * + * @author Johannes M. Schmitt + */ +class EnumNodeDefinition extends ScalarNodeDefinition +{ + private array $values; + + /** + * @return $this + */ + public function values(array $values): static + { + if (!$values) { + throw new \InvalidArgumentException('->values() must be called with at least one value.'); + } + + $this->values = $values; + + return $this; + } + + /** + * Instantiate a Node. + * + * @throws \RuntimeException + */ + protected function instantiateNode(): EnumNode + { + if (!isset($this->values)) { + throw new \RuntimeException('You must call ->values() on enum nodes.'); + } + + return new EnumNode($this->name, $this->parent, $this->values, $this->pathSeparator); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ExprBuilder.php b/vendor/symfony/config/Definition/Builder/ExprBuilder.php new file mode 100644 index 0000000..d391f85 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ExprBuilder.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * This class builds an if expression. + * + * @author Johannes M. Schmitt + * @author Christophe Coevoet + */ +class ExprBuilder +{ + public const TYPE_ANY = 'any'; + public const TYPE_STRING = 'string'; + public const TYPE_NULL = 'null'; + public const TYPE_ARRAY = 'array'; + + public string $allowedTypes; + public ?\Closure $ifPart = null; + public ?\Closure $thenPart = null; + + public function __construct( + protected NodeDefinition $node, + ) { + } + + /** + * Marks the expression as being always used. + * + * @return $this + */ + public function always(?\Closure $then = null): static + { + $this->ifPart = static fn () => true; + $this->allowedTypes = self::TYPE_ANY; + + if (null !== $then) { + $this->thenPart = $then; + } + + return $this; + } + + /** + * Sets a closure to use as tests. + * + * The default one tests if the value is true. + * + * @return $this + */ + public function ifTrue(?\Closure $closure = null): static + { + $this->ifPart = $closure ?? static fn ($v) => true === $v; + $this->allowedTypes = self::TYPE_ANY; + + return $this; + } + + /** + * Tests if the value is a string. + * + * @return $this + */ + public function ifString(): static + { + $this->ifPart = \is_string(...); + $this->allowedTypes = self::TYPE_STRING; + + return $this; + } + + /** + * Tests if the value is null. + * + * @return $this + */ + public function ifNull(): static + { + $this->ifPart = \is_null(...); + $this->allowedTypes = self::TYPE_NULL; + + return $this; + } + + /** + * Tests if the value is empty. + * + * @return $this + */ + public function ifEmpty(): static + { + $this->ifPart = static fn ($v) => !$v; + $this->allowedTypes = self::TYPE_ANY; + + return $this; + } + + /** + * Tests if the value is an array. + * + * @return $this + */ + public function ifArray(): static + { + $this->ifPart = \is_array(...); + $this->allowedTypes = self::TYPE_ARRAY; + + return $this; + } + + /** + * Tests if the value is in an array. + * + * @return $this + */ + public function ifInArray(array $array): static + { + $this->ifPart = static fn ($v) => \in_array($v, $array, true); + $this->allowedTypes = self::TYPE_ANY; + + return $this; + } + + /** + * Tests if the value is not in an array. + * + * @return $this + */ + public function ifNotInArray(array $array): static + { + $this->ifPart = static fn ($v) => !\in_array($v, $array, true); + $this->allowedTypes = self::TYPE_ANY; + + return $this; + } + + /** + * Transforms variables of any type into an array. + * + * @return $this + */ + public function castToArray(): static + { + $this->ifPart = static fn ($v) => !\is_array($v); + $this->allowedTypes = self::TYPE_ANY; + $this->thenPart = static fn ($v) => [$v]; + + return $this; + } + + /** + * Sets the closure to run if the test pass. + * + * @return $this + */ + public function then(\Closure $closure): static + { + $this->thenPart = $closure; + + return $this; + } + + /** + * Sets a closure returning an empty array. + * + * @return $this + */ + public function thenEmptyArray(): static + { + $this->thenPart = static fn () => []; + + return $this; + } + + /** + * Sets a closure marking the value as invalid at processing time. + * + * if you want to add the value of the node in your message just use a %s placeholder. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function thenInvalid(string $message): static + { + $this->thenPart = static fn ($v) => throw new \InvalidArgumentException(sprintf($message, json_encode($v))); + + return $this; + } + + /** + * Sets a closure unsetting this key of the array at processing time. + * + * @return $this + * + * @throws UnsetKeyException + */ + public function thenUnset(): static + { + $this->thenPart = static fn () => throw new UnsetKeyException('Unsetting key.'); + + return $this; + } + + /** + * Returns the related node. + * + * @throws \RuntimeException + */ + public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition + { + if (null === $this->ifPart) { + throw new \RuntimeException('You must specify an if part.'); + } + if (null === $this->thenPart) { + throw new \RuntimeException('You must specify a then part.'); + } + + return $this->node; + } + + /** + * Builds the expressions. + * + * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build + */ + public static function buildExpressions(array $expressions): array + { + foreach ($expressions as $k => $expr) { + if ($expr instanceof self) { + $if = $expr->ifPart; + $then = $expr->thenPart; + $expressions[$k] = static fn ($v) => $if($v) ? $then($v) : $v; + } + } + + return $expressions; + } +} diff --git a/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php new file mode 100644 index 0000000..337e971 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\FloatNode; + +/** + * This class provides a fluent interface for defining a float node. + * + * @author Jeanmonod David + */ +class FloatNodeDefinition extends NumericNodeDefinition +{ + /** + * Instantiates a Node. + */ + protected function instantiateNode(): FloatNode + { + return new FloatNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator); + } +} diff --git a/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php new file mode 100644 index 0000000..2af81df --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\IntegerNode; + +/** + * This class provides a fluent interface for defining an integer node. + * + * @author Jeanmonod David + */ +class IntegerNodeDefinition extends NumericNodeDefinition +{ + /** + * Instantiates a Node. + */ + protected function instantiateNode(): IntegerNode + { + return new IntegerNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator); + } +} diff --git a/vendor/symfony/config/Definition/Builder/MergeBuilder.php b/vendor/symfony/config/Definition/Builder/MergeBuilder.php new file mode 100644 index 0000000..b90865c --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/MergeBuilder.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class builds merge conditions. + * + * @author Johannes M. Schmitt + */ +class MergeBuilder +{ + public bool $allowFalse = false; + public bool $allowOverwrite = true; + + public function __construct( + protected NodeDefinition $node, + ) { + } + + /** + * Sets whether the node can be unset. + * + * @return $this + */ + public function allowUnset(bool $allow = true): static + { + $this->allowFalse = $allow; + + return $this; + } + + /** + * Sets whether the node can be overwritten. + * + * @return $this + */ + public function denyOverwrite(bool $deny = true): static + { + $this->allowOverwrite = !$deny; + + return $this; + } + + /** + * Returns the related node. + */ + public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition + { + return $this->node; + } +} diff --git a/vendor/symfony/config/Definition/Builder/NodeBuilder.php b/vendor/symfony/config/Definition/Builder/NodeBuilder.php new file mode 100644 index 0000000..d79075a --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeBuilder.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class provides a fluent interface for building a node. + * + * @author Johannes M. Schmitt + */ +class NodeBuilder implements NodeParentInterface +{ + protected (NodeDefinition&ParentNodeDefinitionInterface)|null $parent = null; + protected array $nodeMapping; + + public function __construct() + { + $this->nodeMapping = [ + 'variable' => VariableNodeDefinition::class, + 'scalar' => ScalarNodeDefinition::class, + 'boolean' => BooleanNodeDefinition::class, + 'integer' => IntegerNodeDefinition::class, + 'float' => FloatNodeDefinition::class, + 'array' => ArrayNodeDefinition::class, + 'enum' => EnumNodeDefinition::class, + ]; + } + + /** + * Set the parent node. + * + * @return $this + */ + public function setParent((NodeDefinition&ParentNodeDefinitionInterface)|null $parent): static + { + $this->parent = $parent; + + return $this; + } + + /** + * Creates a child array node. + */ + public function arrayNode(string $name): ArrayNodeDefinition + { + return $this->node($name, 'array'); + } + + /** + * Creates a child scalar node. + */ + public function scalarNode(string $name): ScalarNodeDefinition + { + return $this->node($name, 'scalar'); + } + + /** + * Creates a child Boolean node. + */ + public function booleanNode(string $name): BooleanNodeDefinition + { + return $this->node($name, 'boolean'); + } + + /** + * Creates a child integer node. + */ + public function integerNode(string $name): IntegerNodeDefinition + { + return $this->node($name, 'integer'); + } + + /** + * Creates a child float node. + */ + public function floatNode(string $name): FloatNodeDefinition + { + return $this->node($name, 'float'); + } + + /** + * Creates a child EnumNode. + */ + public function enumNode(string $name): EnumNodeDefinition + { + return $this->node($name, 'enum'); + } + + /** + * Creates a child variable node. + */ + public function variableNode(string $name): VariableNodeDefinition + { + return $this->node($name, 'variable'); + } + + /** + * Returns the parent node. + */ + public function end(): NodeDefinition&ParentNodeDefinitionInterface + { + return $this->parent; + } + + /** + * Creates a child node. + * + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found + */ + public function node(?string $name, string $type): NodeDefinition + { + $class = $this->getNodeClass($type); + + $node = new $class($name); + + $this->append($node); + + return $node; + } + + /** + * Appends a node definition. + * + * Usage: + * + * $node = new ArrayNodeDefinition('name') + * ->children() + * ->scalarNode('foo')->end() + * ->scalarNode('baz')->end() + * ->append($this->getBarNodeDefinition()) + * ->end() + * ; + * + * @return $this + */ + public function append(NodeDefinition $node): static + { + if ($node instanceof BuilderAwareInterface) { + $builder = clone $this; + $builder->setParent(null); + $node->setBuilder($builder); + } + + if (null !== $this->parent) { + $this->parent->append($node); + // Make this builder the node parent to allow for a fluid interface + $node->setParent($this); + } + + return $this; + } + + /** + * Adds or overrides a node Type. + * + * @param string $type The name of the type + * @param string $class The fully qualified name the node definition class + * + * @return $this + */ + public function setNodeClass(string $type, string $class): static + { + $this->nodeMapping[strtolower($type)] = $class; + + return $this; + } + + /** + * Returns the class name of the node definition. + * + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found + */ + protected function getNodeClass(string $type): string + { + $type = strtolower($type); + + if (!isset($this->nodeMapping[$type])) { + throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type)); + } + + $class = $this->nodeMapping[$type]; + + if (!class_exists($class)) { + throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class)); + } + + return $class; + } +} diff --git a/vendor/symfony/config/Definition/Builder/NodeDefinition.php b/vendor/symfony/config/Definition/Builder/NodeDefinition.php new file mode 100644 index 0000000..54e976e --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeDefinition.php @@ -0,0 +1,338 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; +use Symfony\Component\Config\Definition\NodeInterface; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +abstract class NodeDefinition implements NodeParentInterface +{ + protected ?string $name = null; + protected NormalizationBuilder $normalization; + protected ValidationBuilder $validation; + protected mixed $defaultValue; + protected bool $default = false; + protected bool $required = false; + protected array $deprecation = []; + protected MergeBuilder $merge; + protected bool $allowEmptyValue = true; + protected mixed $nullEquivalent = null; + protected mixed $trueEquivalent = true; + protected mixed $falseEquivalent = false; + protected string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR; + protected NodeParentInterface|NodeInterface|null $parent; + protected array $attributes = []; + + public function __construct(?string $name, ?NodeParentInterface $parent = null) + { + $this->parent = $parent; + $this->name = $name; + } + + /** + * Sets the parent node. + * + * @return $this + */ + public function setParent(NodeParentInterface $parent): static + { + $this->parent = $parent; + + return $this; + } + + /** + * Sets info message. + * + * @return $this + */ + public function info(string $info): static + { + return $this->attribute('info', $info); + } + + /** + * Sets example configuration. + * + * @return $this + */ + public function example(string|array $example): static + { + return $this->attribute('example', $example); + } + + /** + * Sets an attribute on the node. + * + * @return $this + */ + public function attribute(string $key, mixed $value): static + { + $this->attributes[$key] = $value; + + return $this; + } + + /** + * Returns the parent node. + * + * @return NodeParentInterface|NodeBuilder|self|ArrayNodeDefinition|VariableNodeDefinition + */ + public function end(): NodeParentInterface + { + return $this->parent; + } + + /** + * Creates the node. + */ + public function getNode(bool $forceRootNode = false): NodeInterface + { + if ($forceRootNode) { + $this->parent = null; + } + + if (isset($this->normalization)) { + $allowedTypes = []; + foreach ($this->normalization->before as $expr) { + $allowedTypes[] = $expr->allowedTypes; + } + $allowedTypes = array_unique($allowedTypes); + $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before); + $this->normalization->declaredTypes = $allowedTypes; + } + + if (isset($this->validation)) { + $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules); + } + + $node = $this->createNode(); + if ($node instanceof BaseNode) { + $node->setAttributes($this->attributes); + } + + return $node; + } + + /** + * Sets the default value. + * + * @return $this + */ + public function defaultValue(mixed $value): static + { + $this->default = true; + $this->defaultValue = $value; + + return $this; + } + + /** + * Sets the node as required. + * + * @return $this + */ + public function isRequired(): static + { + $this->required = true; + + return $this; + } + + /** + * Sets the node as deprecated. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message the deprecation message to use + * + * You can use %node% and %path% placeholders in your message to display, + * respectively, the node name and its complete path + * + * @return $this + */ + public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.'): static + { + $this->deprecation = [ + 'package' => $package, + 'version' => $version, + 'message' => $message, + ]; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains null. + * + * @return $this + */ + public function treatNullLike(mixed $value): static + { + $this->nullEquivalent = $value; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains true. + * + * @return $this + */ + public function treatTrueLike(mixed $value): static + { + $this->trueEquivalent = $value; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains false. + * + * @return $this + */ + public function treatFalseLike(mixed $value): static + { + $this->falseEquivalent = $value; + + return $this; + } + + /** + * Sets null as the default value. + * + * @return $this + */ + public function defaultNull(): static + { + return $this->defaultValue(null); + } + + /** + * Sets true as the default value. + * + * @return $this + */ + public function defaultTrue(): static + { + return $this->defaultValue(true); + } + + /** + * Sets false as the default value. + * + * @return $this + */ + public function defaultFalse(): static + { + return $this->defaultValue(false); + } + + /** + * Sets an expression to run before the normalization. + */ + public function beforeNormalization(): ExprBuilder + { + return $this->normalization()->before(); + } + + /** + * Denies the node value being empty. + * + * @return $this + */ + public function cannotBeEmpty(): static + { + $this->allowEmptyValue = false; + + return $this; + } + + /** + * Sets an expression to run for the validation. + * + * The expression receives the value of the node and must return it. It can + * modify it. + * An exception should be thrown when the node is not valid. + */ + public function validate(): ExprBuilder + { + return $this->validation()->rule(); + } + + /** + * Sets whether the node can be overwritten. + * + * @return $this + */ + public function cannotBeOverwritten(bool $deny = true): static + { + $this->merge()->denyOverwrite($deny); + + return $this; + } + + /** + * Gets the builder for validation rules. + */ + protected function validation(): ValidationBuilder + { + return $this->validation ??= new ValidationBuilder($this); + } + + /** + * Gets the builder for merging rules. + */ + protected function merge(): MergeBuilder + { + return $this->merge ??= new MergeBuilder($this); + } + + /** + * Gets the builder for normalization rules. + */ + protected function normalization(): NormalizationBuilder + { + return $this->normalization ??= new NormalizationBuilder($this); + } + + /** + * Instantiate and configure the node according to this definition. + * + * @throws InvalidDefinitionException When the definition is invalid + */ + abstract protected function createNode(): NodeInterface; + + /** + * Set PathSeparator to use. + * + * @return $this + */ + public function setPathSeparator(string $separator): static + { + if ($this instanceof ParentNodeDefinitionInterface) { + foreach ($this->getChildNodeDefinitions() as $child) { + $child->setPathSeparator($separator); + } + } + + $this->pathSeparator = $separator; + + return $this; + } +} diff --git a/vendor/symfony/config/Definition/Builder/NodeParentInterface.php b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php new file mode 100644 index 0000000..305e993 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * An interface that must be implemented by all node parents. + * + * @author Victor Berchet + */ +interface NodeParentInterface +{ +} diff --git a/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php new file mode 100644 index 0000000..8a8141c --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class builds normalization conditions. + * + * @author Johannes M. Schmitt + */ +class NormalizationBuilder +{ + public array $before = []; + public array $declaredTypes = []; + public array $remappings = []; + + public function __construct( + protected NodeDefinition $node, + ) { + } + + /** + * Registers a key to remap to its plural form. + * + * @param string $key The key to remap + * @param string|null $plural The plural of the key in case of irregular plural + * + * @return $this + */ + public function remap(string $key, ?string $plural = null): static + { + $this->remappings[] = [$key, null === $plural ? $key.'s' : $plural]; + + return $this; + } + + /** + * Registers a closure to run before the normalization or an expression builder to build it if null is provided. + * + * @return ExprBuilder|$this + */ + public function before(?\Closure $closure = null): ExprBuilder|static + { + if (null !== $closure) { + $this->before[] = $closure; + + return $this; + } + + return $this->before[] = new ExprBuilder($this->node); + } +} diff --git a/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php new file mode 100644 index 0000000..41129a2 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; + +/** + * Abstract class that contains common code of integer and float node definitions. + * + * @author David Jeanmonod + */ +abstract class NumericNodeDefinition extends ScalarNodeDefinition +{ + protected int|float|null $min = null; + protected int|float|null $max = null; + + /** + * Ensures that the value is smaller than the given reference. + * + * @return $this + * + * @throws \InvalidArgumentException when the constraint is inconsistent + */ + public function max(int|float $max): static + { + if (isset($this->min) && $this->min > $max) { + throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s).', $max, $this->min)); + } + $this->max = $max; + + return $this; + } + + /** + * Ensures that the value is bigger than the given reference. + * + * @return $this + * + * @throws \InvalidArgumentException when the constraint is inconsistent + */ + public function min(int|float $min): static + { + if (isset($this->max) && $this->max < $min) { + throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s).', $min, $this->max)); + } + $this->min = $min; + + return $this; + } + + /** + * @throws InvalidDefinitionException + */ + public function cannotBeEmpty(): static + { + throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.'); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php new file mode 100644 index 0000000..7b8a7eb --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * An interface that must be implemented by nodes which can have children. + * + * @author Victor Berchet + */ +interface ParentNodeDefinitionInterface extends BuilderAwareInterface +{ + /** + * Returns a builder to add children nodes. + */ + public function children(): NodeBuilder; + + /** + * Appends a node definition. + * + * Usage: + * + * $node = $parentNode + * ->children() + * ->scalarNode('foo')->end() + * ->scalarNode('baz')->end() + * ->append($this->getBarNodeDefinition()) + * ->end() + * ; + * + * @return $this + */ + public function append(NodeDefinition $node): static; + + /** + * Gets the child node definitions. + * + * @return NodeDefinition[] + */ + public function getChildNodeDefinitions(): array; +} diff --git a/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php new file mode 100644 index 0000000..37a0af0 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\ScalarNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class ScalarNodeDefinition extends VariableNodeDefinition +{ + /** + * Instantiate a Node. + */ + protected function instantiateNode(): ScalarNode + { + return new ScalarNode($this->name, $this->parent, $this->pathSeparator); + } +} diff --git a/vendor/symfony/config/Definition/Builder/TreeBuilder.php b/vendor/symfony/config/Definition/Builder/TreeBuilder.php new file mode 100644 index 0000000..5170e19 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/TreeBuilder.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\NodeInterface; + +/** + * This is the entry class for building a config tree. + * + * @author Johannes M. Schmitt + */ +class TreeBuilder implements NodeParentInterface +{ + protected ?NodeInterface $tree = null; + protected ?NodeDefinition $root = null; + + public function __construct(string $name, string $type = 'array', ?NodeBuilder $builder = null) + { + $builder ??= new NodeBuilder(); + $this->root = $builder->node($name, $type)->setParent($this); + } + + /** + * @return NodeDefinition|ArrayNodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array') + */ + public function getRootNode(): NodeDefinition|ArrayNodeDefinition + { + return $this->root; + } + + /** + * Builds the tree. + * + * @throws \RuntimeException + */ + public function buildTree(): NodeInterface + { + return $this->tree ??= $this->root->getNode(true); + } + + public function setPathSeparator(string $separator): void + { + // unset last built as changing path separator changes all nodes + $this->tree = null; + + $this->root->setPathSeparator($separator); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ValidationBuilder.php b/vendor/symfony/config/Definition/Builder/ValidationBuilder.php new file mode 100644 index 0000000..ad22393 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ValidationBuilder.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class builds validation conditions. + * + * @author Christophe Coevoet + */ +class ValidationBuilder +{ + public array $rules = []; + + public function __construct( + protected NodeDefinition $node, + ) { + } + + /** + * Registers a closure to run as normalization or an expression builder to build it if null is provided. + * + * @return ExprBuilder|$this + */ + public function rule(?\Closure $closure = null): ExprBuilder|static + { + if (null !== $closure) { + $this->rules[] = $closure; + + return $this; + } + + return $this->rules[] = new ExprBuilder($this->node); + } +} diff --git a/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php new file mode 100644 index 0000000..a4cc53a --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\VariableNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class VariableNodeDefinition extends NodeDefinition +{ + /** + * Instantiate a Node. + */ + protected function instantiateNode(): VariableNode + { + return new VariableNode($this->name, $this->parent, $this->pathSeparator); + } + + protected function createNode(): NodeInterface + { + $node = $this->instantiateNode(); + + if (isset($this->normalization)) { + $node->setNormalizationClosures($this->normalization->before); + } + + if (isset($this->merge)) { + $node->setAllowOverwrite($this->merge->allowOverwrite); + } + + if (true === $this->default) { + $node->setDefaultValue($this->defaultValue); + } + + $node->setAllowEmptyValue($this->allowEmptyValue); + $node->addEquivalentValue(null, $this->nullEquivalent); + $node->addEquivalentValue(true, $this->trueEquivalent); + $node->addEquivalentValue(false, $this->falseEquivalent); + $node->setRequired($this->required); + + if ($this->deprecation) { + $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']); + } + + if (isset($this->validation)) { + $node->setFinalValidationClosures($this->validation->rules); + } + + return $node; + } +} diff --git a/vendor/symfony/config/Definition/ConfigurableInterface.php b/vendor/symfony/config/Definition/ConfigurableInterface.php new file mode 100644 index 0000000..cd46461 --- /dev/null +++ b/vendor/symfony/config/Definition/ConfigurableInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + +/** + * @author Yonel Ceruto + */ +interface ConfigurableInterface +{ + /** + * Generates the configuration tree builder. + */ + public function configure(DefinitionConfigurator $definition): void; +} diff --git a/vendor/symfony/config/Definition/Configuration.php b/vendor/symfony/config/Definition/Configuration.php new file mode 100644 index 0000000..32954a6 --- /dev/null +++ b/vendor/symfony/config/Definition/Configuration.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\Config\Definition\Loader\DefinitionFileLoader; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Yonel Ceruto + * + * @final + */ +class Configuration implements ConfigurationInterface +{ + public function __construct( + private ConfigurableInterface $subject, + private ?ContainerBuilder $container, + private string $alias, + ) { + } + + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder($this->alias); + $file = (new \ReflectionObject($this->subject))->getFileName(); + $loader = new DefinitionFileLoader($treeBuilder, new FileLocator(\dirname($file)), $this->container); + $configurator = new DefinitionConfigurator($treeBuilder, $loader, $file, $file); + + $this->subject->configure($configurator); + + return $treeBuilder; + } +} diff --git a/vendor/symfony/config/Definition/ConfigurationInterface.php b/vendor/symfony/config/Definition/ConfigurationInterface.php new file mode 100644 index 0000000..97a325b --- /dev/null +++ b/vendor/symfony/config/Definition/ConfigurationInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; + +/** + * Configuration interface. + * + * @author Victor Berchet + */ +interface ConfigurationInterface +{ + /** + * Generates the configuration tree builder. + */ + public function getConfigTreeBuilder(): TreeBuilder; +} diff --git a/vendor/symfony/config/Definition/Configurator/DefinitionConfigurator.php b/vendor/symfony/config/Definition/Configurator/DefinitionConfigurator.php new file mode 100644 index 0000000..13fe45c --- /dev/null +++ b/vendor/symfony/config/Definition/Configurator/DefinitionConfigurator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Configurator; + +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Loader\DefinitionFileLoader; + +/** + * @author Yonel Ceruto + */ +class DefinitionConfigurator +{ + public function __construct( + private TreeBuilder $treeBuilder, + private DefinitionFileLoader $loader, + private string $path, + private string $file, + ) { + } + + public function import(string $resource, ?string $type = null, bool $ignoreErrors = false): void + { + $this->loader->setCurrentDir(\dirname($this->path)); + $this->loader->import($resource, $type, $ignoreErrors, $this->file); + } + + public function rootNode(): NodeDefinition|ArrayNodeDefinition + { + return $this->treeBuilder->getRootNode(); + } + + public function setPathSeparator(string $separator): void + { + $this->treeBuilder->setPathSeparator($separator); + } +} diff --git a/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php new file mode 100644 index 0000000..dd6f76c --- /dev/null +++ b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Dumper; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\BooleanNode; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\FloatNode; +use Symfony\Component\Config\Definition\IntegerNode; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Config\Definition\ScalarNode; + +/** + * Dumps an XML reference configuration for the given configuration/node instance. + * + * @author Wouter J + */ +class XmlReferenceDumper +{ + private ?string $reference = null; + + public function dump(ConfigurationInterface $configuration, ?string $namespace = null): string + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); + } + + public function dumpNode(NodeInterface $node, ?string $namespace = null): string + { + $this->reference = ''; + $this->writeNode($node, 0, true, $namespace); + $ref = $this->reference; + $this->reference = null; + + return $ref; + } + + private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, ?string $namespace = null): void + { + $rootName = ($root ? 'config' : $node->getName()); + $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null)); + + // xml remapping + if ($node->getParent()) { + $remapping = array_filter($node->getParent()->getXmlRemappings(), fn (array $mapping) => $rootName === $mapping[1]); + + if (\count($remapping)) { + [$singular] = current($remapping); + $rootName = $singular; + } + } + $rootName = str_replace('_', '-', $rootName); + + $rootAttributes = []; + $rootAttributeComments = []; + $rootChildren = []; + $rootComments = []; + + if ($node instanceof ArrayNode) { + $children = $node->getChildren(); + + // comments about the root node + if ($rootInfo = $node->getInfo()) { + $rootComments[] = $rootInfo; + } + + if ($rootNamespace) { + $rootComments[] = 'Namespace: '.$rootNamespace; + } + + // render prototyped nodes + if ($node instanceof PrototypedArrayNode) { + $prototype = $node->getPrototype(); + + $info = 'prototype'; + if (null !== $prototype->getInfo()) { + $info .= ': '.$prototype->getInfo(); + } + array_unshift($rootComments, $info); + + if ($key = $node->getKeyAttribute()) { + $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key; + } + + if ($prototype instanceof PrototypedArrayNode) { + $prototype->setName($key ?? ''); + $children = [$key => $prototype]; + } elseif ($prototype instanceof ArrayNode) { + $children = $prototype->getChildren(); + } else { + if ($prototype->hasDefaultValue()) { + $prototypeValue = $prototype->getDefaultValue(); + } else { + $prototypeValue = match ($prototype::class) { + ScalarNode::class => 'scalar value', + FloatNode::class, + IntegerNode::class => 'numeric value', + BooleanNode::class => 'true|false', + EnumNode::class => $prototype->getPermissibleValues('|'), + default => 'value', + }; + } + } + } + + // get attributes and elements + foreach ($children as $child) { + if ($child instanceof ArrayNode) { + // get elements + $rootChildren[] = $child; + + continue; + } + + // get attributes + + // metadata + $name = str_replace('_', '-', $child->getName()); + $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world + + // comments + $comments = []; + if ($child instanceof BaseNode && $info = $child->getInfo()) { + $comments[] = $info; + } + + if ($child instanceof BaseNode && $example = $child->getExample()) { + $comments[] = 'Example: '.(\is_array($example) ? implode(', ', $example) : $example); + } + + if ($child->isRequired()) { + $comments[] = 'Required'; + } + + if ($child instanceof BaseNode && $child->isDeprecated()) { + $deprecation = $child->getDeprecation($child->getName(), $node->getPath()); + $comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); + } + + if ($child instanceof EnumNode) { + $comments[] = 'One of '.$child->getPermissibleValues('; '); + } + + if (\count($comments)) { + $rootAttributeComments[$name] = implode(";\n", $comments); + } + + // default values + if ($child->hasDefaultValue()) { + $value = $child->getDefaultValue(); + } + + // append attribute + $rootAttributes[$name] = $value; + } + } + + // render comments + + // root node comment + if (\count($rootComments)) { + foreach ($rootComments as $comment) { + $this->writeLine('', $depth); + } + } + + // attribute comments + if (\count($rootAttributeComments)) { + foreach ($rootAttributeComments as $attrName => $comment) { + $commentDepth = $depth + 4 + \strlen($attrName) + 2; + $commentLines = explode("\n", $comment); + $multiline = (\count($commentLines) > 1); + $comment = implode(\PHP_EOL.str_repeat(' ', $commentDepth), $commentLines); + + if ($multiline) { + $this->writeLine('', $depth); + } else { + $this->writeLine('', $depth); + } + } + } + + // render start tag + attributes + $rootIsVariablePrototype = isset($prototypeValue); + $rootIsEmptyTag = (0 === \count($rootChildren) && !$rootIsVariablePrototype); + $rootOpenTag = '<'.$rootName; + if (1 >= ($attributesCount = \count($rootAttributes))) { + if (1 === $attributesCount) { + $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes))); + } + + $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>'; + + if ($rootIsVariablePrototype) { + $rootOpenTag .= $prototypeValue.''; + } + + $this->writeLine($rootOpenTag, $depth); + } else { + $this->writeLine($rootOpenTag, $depth); + + $i = 1; + + foreach ($rootAttributes as $attrName => $attrValue) { + $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue)); + + $this->writeLine($attr, $depth + 4); + + if ($attributesCount === $i++) { + $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth); + + if ($rootIsVariablePrototype) { + $rootOpenTag .= $prototypeValue.''; + } + } + } + } + + // render children tags + foreach ($rootChildren as $child) { + $this->writeLine(''); + $this->writeNode($child, $depth + 4); + } + + // render end tag + if (!$rootIsEmptyTag && !$rootIsVariablePrototype) { + $this->writeLine(''); + + $rootEndTag = ''; + $this->writeLine($rootEndTag, $depth); + } + } + + /** + * Outputs a single config reference line. + */ + private function writeLine(string $text, int $indent = 0): void + { + $indent = \strlen($text) + $indent; + $format = '%'.$indent.'s'; + + $this->reference .= sprintf($format, $text).\PHP_EOL; + } + + /** + * Renders the string conversion of the value. + */ + private function writeValue(mixed $value): string + { + if ('%%%%not_defined%%%%' === $value) { + return ''; + } + + if (\is_string($value) || is_numeric($value)) { + return $value; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + if (null === $value) { + return 'null'; + } + + if (!$value) { + return ''; + } + + if (\is_array($value)) { + return implode(',', $value); + } + + return ''; + } +} diff --git a/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php new file mode 100644 index 0000000..e8d7f5c --- /dev/null +++ b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Dumper; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Config\Definition\ScalarNode; +use Symfony\Component\Yaml\Inline; + +/** + * Dumps a Yaml reference configuration for the given configuration/node instance. + * + * @author Kevin Bond + */ +class YamlReferenceDumper +{ + private ?string $reference = null; + + public function dump(ConfigurationInterface $configuration): string + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); + } + + public function dumpAtPath(ConfigurationInterface $configuration, string $path): string + { + $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree(); + + foreach (explode('.', $path) as $step) { + if (!$node instanceof ArrayNode) { + throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); + } + + /** @var NodeInterface[] $children */ + $children = $node instanceof PrototypedArrayNode ? $this->getPrototypeChildren($node) : $node->getChildren(); + + foreach ($children as $child) { + if ($child->getName() === $step) { + $node = $child; + + continue 2; + } + } + + throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); + } + + return $this->dumpNode($node); + } + + public function dumpNode(NodeInterface $node): string + { + $this->reference = ''; + $this->writeNode($node); + $ref = $this->reference; + $this->reference = null; + + return $ref; + } + + private function writeNode(NodeInterface $node, ?NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false): void + { + $comments = []; + $default = ''; + $defaultArray = null; + $children = null; + $example = null; + if ($node instanceof BaseNode) { + $example = $node->getExample(); + } + + // defaults + if ($node instanceof ArrayNode) { + $children = $node->getChildren(); + + if ($node instanceof PrototypedArrayNode) { + $children = $this->getPrototypeChildren($node); + } + + if (!$children && !($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue()))) { + $default = '[]'; + } + } elseif ($node instanceof EnumNode) { + $comments[] = 'One of '.$node->getPermissibleValues('; '); + $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~'; + } else { + $default = '~'; + + if ($node->hasDefaultValue()) { + $default = $node->getDefaultValue(); + + if (\is_array($default)) { + if (\count($defaultArray = $node->getDefaultValue())) { + $default = ''; + } elseif (!\is_array($example)) { + $default = '[]'; + } + } else { + $default = Inline::dump($default); + } + } + } + + // required? + if ($node->isRequired()) { + $comments[] = 'Required'; + } + + // deprecated? + if ($node instanceof BaseNode && $node->isDeprecated()) { + $deprecation = $node->getDeprecation($node->getName(), $parentNode ? $parentNode->getPath() : $node->getPath()); + $comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); + } + + // example + if ($example && !\is_array($example)) { + $comments[] = 'Example: '.Inline::dump($example); + } + + $default = '' != (string) $default ? ' '.$default : ''; + $comments = \count($comments) ? '# '.implode(', ', $comments) : ''; + + $key = $prototypedArray ? '-' : $node->getName().':'; + $text = rtrim(sprintf('%-21s%s %s', $key, $default, $comments), ' '); + + if ($node instanceof BaseNode && $info = $node->getInfo()) { + $this->writeLine(''); + // indenting multi-line info + $info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info); + $this->writeLine('# '.$info, $depth * 4); + } + + $this->writeLine($text, $depth * 4); + + // output defaults + if ($defaultArray) { + $this->writeLine(''); + + $message = \count($defaultArray) > 1 ? 'Defaults' : 'Default'; + + $this->writeLine('# '.$message.':', $depth * 4 + 4); + + $this->writeArray($defaultArray, $depth + 1); + } + + if (\is_array($example)) { + $this->writeLine(''); + + $message = \count($example) > 1 ? 'Examples' : 'Example'; + + $this->writeLine('# '.$message.':', $depth * 4 + 4); + + $this->writeArray(array_map(Inline::dump(...), $example), $depth + 1, true); + } + + if ($children) { + foreach ($children as $childNode) { + $this->writeNode($childNode, $node, $depth + 1, $node instanceof PrototypedArrayNode && !$node->getKeyAttribute()); + } + } + } + + /** + * Outputs a single config reference line. + */ + private function writeLine(string $text, int $indent = 0): void + { + $indent = \strlen($text) + $indent; + $format = '%'.$indent.'s'; + + $this->reference .= sprintf($format, $text)."\n"; + } + + private function writeArray(array $array, int $depth, bool $asComment = false): void + { + $isIndexed = array_is_list($array); + + foreach ($array as $key => $value) { + if (\is_array($value)) { + $val = ''; + } else { + $val = $value; + } + $prefix = $asComment ? '# ' : ''; + + $prefix = $asComment ? '# ' : ''; + + if ($isIndexed) { + $this->writeLine($prefix.'- '.$val, $depth * 4); + } else { + $this->writeLine(sprintf('%s%-20s %s', $prefix, $key.':', $val), $depth * 4); + } + + if (\is_array($value)) { + $this->writeArray($value, $depth + 1, $asComment); + } + } + } + + private function getPrototypeChildren(PrototypedArrayNode $node): array + { + $prototype = $node->getPrototype(); + $key = $node->getKeyAttribute(); + + // Do not expand prototype if it isn't an array node nor uses attribute as key + if (!$key && !$prototype instanceof ArrayNode) { + return $node->getChildren(); + } + + if ($prototype instanceof ArrayNode) { + $keyNode = new ArrayNode($key, $node); + $children = $prototype->getChildren(); + + if ($prototype instanceof PrototypedArrayNode && $prototype->getKeyAttribute()) { + $children = $this->getPrototypeChildren($prototype); + } + + // add children + foreach ($children as $childNode) { + $keyNode->addChild($childNode); + } + } else { + $keyNode = new ScalarNode($key, $node); + } + + $info = 'Prototype'; + if (null !== $prototype->getInfo()) { + $info .= ': '.$prototype->getInfo(); + } + $keyNode->setInfo($info); + + return [$key => $keyNode]; + } +} diff --git a/vendor/symfony/config/Definition/EnumNode.php b/vendor/symfony/config/Definition/EnumNode.php new file mode 100644 index 0000000..29fe0bd --- /dev/null +++ b/vendor/symfony/config/Definition/EnumNode.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * Node which only allows a finite set of values. + * + * @author Johannes M. Schmitt + */ +class EnumNode extends ScalarNode +{ + private array $values; + + public function __construct(?string $name, ?NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) + { + if (!$values) { + throw new \InvalidArgumentException('$values must contain at least one element.'); + } + + foreach ($values as $value) { + if (null === $value || \is_scalar($value)) { + continue; + } + + if (!$value instanceof \UnitEnum) { + throw new \InvalidArgumentException(sprintf('"%s" only supports scalar, enum, or null values, "%s" given.', __CLASS__, get_debug_type($value))); + } + + if ($value::class !== ($enumClass ??= $value::class)) { + throw new \InvalidArgumentException(sprintf('"%s" only supports one type of enum, "%s" and "%s" passed.', __CLASS__, $enumClass, $value::class)); + } + } + + parent::__construct($name, $parent, $pathSeparator); + $this->values = $values; + } + + public function getValues(): array + { + return $this->values; + } + + /** + * @internal + */ + public function getPermissibleValues(string $separator): string + { + return implode($separator, array_unique(array_map(static function (mixed $value): string { + if (!$value instanceof \UnitEnum) { + return json_encode($value); + } + + return ltrim(var_export($value, true), '\\'); + }, $this->values))); + } + + protected function validateType(mixed $value): void + { + if ($value instanceof \UnitEnum) { + return; + } + + parent::validateType($value); + } + + protected function finalizeValue(mixed $value): mixed + { + $value = parent::finalizeValue($value); + + if (!\in_array($value, $this->values, true)) { + $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), $this->getPermissibleValues(', '))); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } +} diff --git a/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php new file mode 100644 index 0000000..48dd932 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is thrown whenever the key of an array is not unique. This can + * only be the case if the configuration is coming from an XML file. + * + * @author Johannes M. Schmitt + */ +class DuplicateKeyException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/Exception.php b/vendor/symfony/config/Definition/Exception/Exception.php new file mode 100644 index 0000000..8933a49 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/Exception.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * Base exception for all configuration exceptions. + * + * @author Johannes M. Schmitt + */ +class Exception extends \RuntimeException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php new file mode 100644 index 0000000..726c07f --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is thrown when a configuration path is overwritten from a + * subsequent configuration file, but the entry node specifically forbids this. + * + * @author Johannes M. Schmitt + */ +class ForbiddenOverwriteException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php new file mode 100644 index 0000000..e3f29f8 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * A very general exception which can be thrown whenever non of the more specific + * exceptions is suitable. + * + * @author Johannes M. Schmitt + */ +class InvalidConfigurationException extends Exception +{ + private ?string $path = null; + private bool $containsHints = false; + + public function setPath(string $path): void + { + $this->path = $path; + } + + public function getPath(): ?string + { + return $this->path; + } + + /** + * Adds extra information that is suffixed to the original exception message. + */ + public function addHint(string $hint): void + { + if (!$this->containsHints) { + $this->message .= "\nHint: ".$hint; + $this->containsHints = true; + } else { + $this->message .= ', '.$hint; + } + } +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php new file mode 100644 index 0000000..98310da --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * Thrown when an error is detected in a node Definition. + * + * @author Victor Berchet + */ +class InvalidDefinitionException extends Exception +{ +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidTypeException.php b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php new file mode 100644 index 0000000..d7ca8c9 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is thrown if an invalid type is encountered. + * + * @author Johannes M. Schmitt + */ +class InvalidTypeException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/UnsetKeyException.php b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php new file mode 100644 index 0000000..863181a --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is usually not encountered by the end-user, but only used + * internally to signal the parent scope to unset a key. + * + * @author Johannes M. Schmitt + */ +class UnsetKeyException extends Exception +{ +} diff --git a/vendor/symfony/config/Definition/FloatNode.php b/vendor/symfony/config/Definition/FloatNode.php new file mode 100644 index 0000000..1023a16 --- /dev/null +++ b/vendor/symfony/config/Definition/FloatNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a float value in the config tree. + * + * @author Jeanmonod David + */ +class FloatNode extends NumericNode +{ + protected function validateType(mixed $value): void + { + // Integers are also accepted, we just cast them + if (\is_int($value)) { + $value = (float) $value; + } + + if (!\is_float($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "float", but got "%s".', $this->getPath(), get_debug_type($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + protected function getValidPlaceholderTypes(): array + { + return ['float']; + } +} diff --git a/vendor/symfony/config/Definition/IntegerNode.php b/vendor/symfony/config/Definition/IntegerNode.php new file mode 100644 index 0000000..3fe70f6 --- /dev/null +++ b/vendor/symfony/config/Definition/IntegerNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents an integer value in the config tree. + * + * @author Jeanmonod David + */ +class IntegerNode extends NumericNode +{ + protected function validateType(mixed $value): void + { + if (!\is_int($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), get_debug_type($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + protected function getValidPlaceholderTypes(): array + { + return ['int']; + } +} diff --git a/vendor/symfony/config/Definition/Loader/DefinitionFileLoader.php b/vendor/symfony/config/Definition/Loader/DefinitionFileLoader.php new file mode 100644 index 0000000..940b894 --- /dev/null +++ b/vendor/symfony/config/Definition/Loader/DefinitionFileLoader.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Loader; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * DefinitionFileLoader loads config definitions from a PHP file. + * + * The PHP file is required. + * + * @author Yonel Ceruto + */ +class DefinitionFileLoader extends FileLoader +{ + public function __construct( + private TreeBuilder $treeBuilder, + FileLocatorInterface $locator, + private ?ContainerBuilder $container = null, + ) { + parent::__construct($locator); + } + + public function load(mixed $resource, ?string $type = null): mixed + { + // the loader variable is exposed to the included file below + $loader = $this; + + $path = $this->locator->locate($resource); + $this->setCurrentDir(\dirname($path)); + $this->container?->fileExists($path); + + // the closure forbids access to the private scope in the included file + $load = \Closure::bind(static function ($file) use ($loader) { + return include $file; + }, null, ProtectedDefinitionFileLoader::class); + + $callback = $load($path); + + if (\is_object($callback) && \is_callable($callback)) { + $this->executeCallback($callback, new DefinitionConfigurator($this->treeBuilder, $this, $path, $resource), $path); + } + + return null; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + if (!\is_string($resource)) { + return false; + } + + if (null === $type && 'php' === pathinfo($resource, \PATHINFO_EXTENSION)) { + return true; + } + + return 'php' === $type; + } + + private function executeCallback(callable $callback, DefinitionConfigurator $configurator, string $path): void + { + $callback = $callback(...); + + $arguments = []; + $r = new \ReflectionFunction($callback); + + foreach ($r->getParameters() as $parameter) { + $reflectionType = $parameter->getType(); + + if (!$reflectionType instanceof \ReflectionNamedType) { + throw new \InvalidArgumentException(sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s").', $parameter->getName(), $path, DefinitionConfigurator::class)); + } + + $arguments[] = match ($reflectionType->getName()) { + DefinitionConfigurator::class => $configurator, + TreeBuilder::class => $this->treeBuilder, + FileLoader::class, self::class => $this, + }; + } + + $callback(...$arguments); + } +} + +/** + * @internal + */ +final class ProtectedDefinitionFileLoader extends DefinitionFileLoader +{ +} diff --git a/vendor/symfony/config/Definition/NodeInterface.php b/vendor/symfony/config/Definition/NodeInterface.php new file mode 100644 index 0000000..d171587 --- /dev/null +++ b/vendor/symfony/config/Definition/NodeInterface.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * Common Interface among all nodes. + * + * In most cases, it is better to inherit from BaseNode instead of implementing + * this interface yourself. + * + * @author Johannes M. Schmitt + */ +interface NodeInterface +{ + /** + * Returns the name of the node. + */ + public function getName(): string; + + /** + * Returns the path of the node. + */ + public function getPath(): string; + + /** + * Returns true when the node is required. + */ + public function isRequired(): bool; + + /** + * Returns true when the node has a default value. + */ + public function hasDefaultValue(): bool; + + /** + * Returns the default value of the node. + * + * @throws \RuntimeException if the node has no default value + */ + public function getDefaultValue(): mixed; + + /** + * Normalizes a value. + * + * @throws InvalidTypeException if the value type is invalid + */ + public function normalize(mixed $value): mixed; + + /** + * Merges two values together. + * + * @throws ForbiddenOverwriteException if the configuration path cannot be overwritten + * @throws InvalidTypeException if the value type is invalid + */ + public function merge(mixed $leftSide, mixed $rightSide): mixed; + + /** + * Finalizes a value. + * + * @throws InvalidTypeException if the value type is invalid + * @throws InvalidConfigurationException if the value is invalid configuration + */ + public function finalize(mixed $value): mixed; +} diff --git a/vendor/symfony/config/Definition/NumericNode.php b/vendor/symfony/config/Definition/NumericNode.php new file mode 100644 index 0000000..b55ee92 --- /dev/null +++ b/vendor/symfony/config/Definition/NumericNode.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * This node represents a numeric value in the config tree. + * + * @author David Jeanmonod + */ +class NumericNode extends ScalarNode +{ + protected int|float|null $min; + protected int|float|null $max; + + public function __construct(?string $name, ?NodeInterface $parent = null, int|float|null $min = null, int|float|null $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) + { + parent::__construct($name, $parent, $pathSeparator); + $this->min = $min; + $this->max = $max; + } + + protected function finalizeValue(mixed $value): mixed + { + $value = parent::finalizeValue($value); + + $errorMsg = null; + if (isset($this->min) && $value < $this->min) { + $errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min); + } + if (isset($this->max) && $value > $this->max) { + $errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max); + } + if (isset($errorMsg)) { + $ex = new InvalidConfigurationException($errorMsg); + $ex->setPath($this->getPath()); + throw $ex; + } + + return $value; + } + + protected function isValueEmpty(mixed $value): bool + { + // a numeric value cannot be empty + return false; + } +} diff --git a/vendor/symfony/config/Definition/Processor.php b/vendor/symfony/config/Definition/Processor.php new file mode 100644 index 0000000..272ddcc --- /dev/null +++ b/vendor/symfony/config/Definition/Processor.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +/** + * This class is the entry point for config normalization/merging/finalization. + * + * @author Johannes M. Schmitt + * + * @final + */ +class Processor +{ + /** + * Processes an array of configurations. + * + * @param array $configs An array of configuration items to process + */ + public function process(NodeInterface $configTree, array $configs): array + { + $currentConfig = []; + foreach ($configs as $config) { + $config = $configTree->normalize($config); + $currentConfig = $configTree->merge($currentConfig, $config); + } + + return $configTree->finalize($currentConfig); + } + + /** + * Processes an array of configurations. + * + * @param array $configs An array of configuration items to process + */ + public function processConfiguration(ConfigurationInterface $configuration, array $configs): array + { + return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs); + } + + /** + * Normalizes a configuration entry. + * + * This method returns a normalize configuration array for a given key + * to remove the differences due to the original format (YAML and XML mainly). + * + * Here is an example. + * + * The configuration in XML: + * + * twig.extension.foo + * twig.extension.bar + * + * And the same configuration in YAML: + * + * extensions: ['twig.extension.foo', 'twig.extension.bar'] + * + * @param array $config A config array + * @param string $key The key to normalize + * @param string|null $plural The plural form of the key if it is irregular + */ + public static function normalizeConfig(array $config, string $key, ?string $plural = null): array + { + $plural ??= $key.'s'; + + if (isset($config[$plural])) { + return $config[$plural]; + } + + if (isset($config[$key])) { + if (\is_string($config[$key]) || !\is_int(key($config[$key]))) { + // only one + return [$config[$key]]; + } + + return $config[$key]; + } + + return []; + } +} diff --git a/vendor/symfony/config/Definition/PrototypeNodeInterface.php b/vendor/symfony/config/Definition/PrototypeNodeInterface.php new file mode 100644 index 0000000..109889f --- /dev/null +++ b/vendor/symfony/config/Definition/PrototypeNodeInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +/** + * This interface must be implemented by nodes which can be used as prototypes. + * + * @author Johannes M. Schmitt + */ +interface PrototypeNodeInterface extends NodeInterface +{ + /** + * Sets the name of the node. + */ + public function setName(string $name): void; +} diff --git a/vendor/symfony/config/Definition/PrototypedArrayNode.php b/vendor/symfony/config/Definition/PrototypedArrayNode.php new file mode 100644 index 0000000..a11e726 --- /dev/null +++ b/vendor/symfony/config/Definition/PrototypedArrayNode.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\DuplicateKeyException; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * Represents a prototyped Array node in the config tree. + * + * @author Johannes M. Schmitt + */ +class PrototypedArrayNode extends ArrayNode +{ + protected PrototypeNodeInterface $prototype; + protected ?string $keyAttribute = null; + protected bool $removeKeyAttribute = false; + protected int $minNumberOfElements = 0; + protected array $defaultValue = []; + protected ?array $defaultChildren = null; + /** + * @var NodeInterface[] An array of the prototypes of the simplified value children + */ + private array $valuePrototypes = []; + + /** + * Sets the minimum number of elements that a prototype based node must + * contain. By default this is zero, meaning no elements. + */ + public function setMinNumberOfElements(int $number): void + { + $this->minNumberOfElements = $number; + } + + /** + * Sets the attribute which value is to be used as key. + * + * This is useful when you have an indexed array that should be an + * associative array. You can select an item from within the array + * to be the key of the particular item. For example, if "id" is the + * "key", then: + * + * [ + * ['id' => 'my_name', 'foo' => 'bar'], + * ]; + * + * becomes + * + * [ + * 'my_name' => ['foo' => 'bar'], + * ]; + * + * If you'd like "'id' => 'my_name'" to still be present in the resulting + * array, then you can set the second argument of this method to false. + * + * @param string $attribute The name of the attribute which value is to be used as a key + * @param bool $remove Whether or not to remove the key + */ + public function setKeyAttribute(string $attribute, bool $remove = true): void + { + $this->keyAttribute = $attribute; + $this->removeKeyAttribute = $remove; + } + + /** + * Retrieves the name of the attribute which value should be used as key. + */ + public function getKeyAttribute(): ?string + { + return $this->keyAttribute; + } + + /** + * Sets the default value of this node. + */ + public function setDefaultValue(array $value): void + { + $this->defaultValue = $value; + } + + public function hasDefaultValue(): bool + { + return true; + } + + /** + * Adds default children when none are set. + * + * @param int|string|array|null $children The number of children|The child name|The children names to be added + */ + public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']): void + { + if (null === $children) { + $this->defaultChildren = ['defaults']; + } else { + $this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children; + } + } + + /** + * The default value could be either explicited or derived from the prototype + * default value. + */ + public function getDefaultValue(): mixed + { + if (null !== $this->defaultChildren) { + $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : []; + $defaults = []; + foreach (array_values($this->defaultChildren) as $i => $name) { + $defaults[null === $this->keyAttribute ? $i : $name] = $default; + } + + return $defaults; + } + + return $this->defaultValue; + } + + /** + * Sets the node prototype. + */ + public function setPrototype(PrototypeNodeInterface $node): void + { + $this->prototype = $node; + } + + /** + * Retrieves the prototype. + */ + public function getPrototype(): PrototypeNodeInterface + { + return $this->prototype; + } + + /** + * Disable adding concrete children for prototyped nodes. + * + * @throws Exception + */ + public function addChild(NodeInterface $node): never + { + throw new Exception('A prototyped array node cannot have concrete children.'); + } + + protected function finalizeValue(mixed $value): mixed + { + if (false === $value) { + throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); + } + + foreach ($value as $k => $v) { + $prototype = $this->getPrototypeForChild($k); + try { + $value[$k] = $prototype->finalize($v); + } catch (UnsetKeyException) { + unset($value[$k]); + } + } + + if (\count($value) < $this->minNumberOfElements) { + $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements)); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } + + /** + * @throws DuplicateKeyException + */ + protected function normalizeValue(mixed $value): mixed + { + if (false === $value) { + return $value; + } + + $value = $this->remapXml($value); + + $isList = array_is_list($value); + $normalized = []; + foreach ($value as $k => $v) { + if (null !== $this->keyAttribute && \is_array($v)) { + if (!isset($v[$this->keyAttribute]) && \is_int($k) && $isList) { + $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } elseif (isset($v[$this->keyAttribute])) { + $k = $v[$this->keyAttribute]; + + if (\is_float($k)) { + $k = var_export($k, true); + } + + // remove the key attribute when required + if ($this->removeKeyAttribute) { + unset($v[$this->keyAttribute]); + } + + // if only "value" is left + if (array_keys($v) === ['value']) { + $v = $v['value']; + if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && \array_key_exists('value', $children)) { + $valuePrototype = current($this->valuePrototypes) ?: clone $children['value']; + $valuePrototype->parent = $this; + $originalClosures = $this->prototype->normalizationClosures; + if (\is_array($originalClosures)) { + $valuePrototypeClosures = $valuePrototype->normalizationClosures; + $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures; + } + $this->valuePrototypes[$k] = $valuePrototype; + } + } + } + + if (\array_key_exists($k, $normalized)) { + $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + $prototype = $this->getPrototypeForChild($k); + if (null !== $this->keyAttribute || !$isList) { + $normalized[$k] = $prototype->normalize($v); + } else { + $normalized[] = $prototype->normalize($v); + } + } + + return $normalized; + } + + protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed + { + if (false === $rightSide) { + // if this is still false after the last config has been merged the + // finalization pass will take care of removing this key entirely + return false; + } + + if (false === $leftSide || !$this->performDeepMerging) { + return $rightSide; + } + + $isList = array_is_list($rightSide); + foreach ($rightSide as $k => $v) { + // prototype, and key is irrelevant there are no named keys, append the element + if (null === $this->keyAttribute && $isList) { + $leftSide[] = $v; + continue; + } + + // no conflict + if (!\array_key_exists($k, $leftSide)) { + if (!$this->allowNewKeys) { + $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + + $leftSide[$k] = $v; + continue; + } + + $prototype = $this->getPrototypeForChild($k); + $leftSide[$k] = $prototype->merge($leftSide[$k], $v); + } + + return $leftSide; + } + + /** + * Returns a prototype for the child node that is associated to $key in the value array. + * For general child nodes, this will be $this->prototype. + * But if $this->removeKeyAttribute is true and there are only two keys in the child node: + * one is same as this->keyAttribute and the other is 'value', then the prototype will be different. + * + * For example, assume $this->keyAttribute is 'name' and the value array is as follows: + * + * [ + * [ + * 'name' => 'name001', + * 'value' => 'value001' + * ] + * ] + * + * Now, the key is 0 and the child node is: + * + * [ + * 'name' => 'name001', + * 'value' => 'value001' + * ] + * + * When normalizing the value array, the 'name' element will removed from the child node + * and its value becomes the new key of the child node: + * + * [ + * 'name001' => ['value' => 'value001'] + * ] + * + * Now only 'value' element is left in the child node which can be further simplified into a string: + * + * ['name001' => 'value001'] + * + * Now, the key becomes 'name001' and the child node becomes 'value001' and + * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance. + */ + private function getPrototypeForChild(string $key): mixed + { + $prototype = $this->valuePrototypes[$key] ?? $this->prototype; + $prototype->setName($key); + + return $prototype; + } +} diff --git a/vendor/symfony/config/Definition/ScalarNode.php b/vendor/symfony/config/Definition/ScalarNode.php new file mode 100644 index 0000000..a7ccb91 --- /dev/null +++ b/vendor/symfony/config/Definition/ScalarNode.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a scalar value in the config tree. + * + * The following values are considered scalars: + * * booleans + * * strings + * * null + * * integers + * * floats + * + * @author Johannes M. Schmitt + */ +class ScalarNode extends VariableNode +{ + protected function validateType(mixed $value): void + { + if (!\is_scalar($value) && null !== $value) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), get_debug_type($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + protected function isValueEmpty(mixed $value): bool + { + // assume environment variables are never empty (which in practice is likely to be true during runtime) + // not doing so breaks many configs that are valid today + if ($this->isHandlingPlaceholder()) { + return false; + } + + return null === $value || '' === $value; + } + + protected function getValidPlaceholderTypes(): array + { + return ['bool', 'int', 'float', 'string']; + } +} diff --git a/vendor/symfony/config/Definition/VariableNode.php b/vendor/symfony/config/Definition/VariableNode.php new file mode 100644 index 0000000..22c2850 --- /dev/null +++ b/vendor/symfony/config/Definition/VariableNode.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * This node represents a value of variable type in the config tree. + * + * This node is intended for values of arbitrary type. + * Any PHP type is accepted as a value. + * + * @author Jeremy Mikola + */ +class VariableNode extends BaseNode implements PrototypeNodeInterface +{ + protected bool $defaultValueSet = false; + protected mixed $defaultValue = null; + protected bool $allowEmptyValue = true; + + public function setDefaultValue(mixed $value): void + { + $this->defaultValueSet = true; + $this->defaultValue = $value; + } + + public function hasDefaultValue(): bool + { + return $this->defaultValueSet; + } + + public function getDefaultValue(): mixed + { + $v = $this->defaultValue; + + return $v instanceof \Closure ? $v() : $v; + } + + /** + * Sets if this node is allowed to have an empty value. + * + * @param bool $boolean True if this entity will accept empty values + */ + public function setAllowEmptyValue(bool $boolean): void + { + $this->allowEmptyValue = $boolean; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + protected function validateType(mixed $value): void + { + } + + protected function finalizeValue(mixed $value): mixed + { + // deny environment variables only when using custom validators + // this avoids ever passing an empty value to final validation closures + if (!$this->allowEmptyValue && $this->isHandlingPlaceholder() && $this->finalValidationClosures) { + $e = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an environment variable when empty values are not allowed by definition and are validated.', $this->getPath())); + if ($hint = $this->getInfo()) { + $e->addHint($hint); + } + $e->setPath($this->getPath()); + + throw $e; + } + + if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { + $ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } + + protected function normalizeValue(mixed $value): mixed + { + return $value; + } + + protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed + { + return $rightSide; + } + + /** + * Evaluates if the given value is to be treated as empty. + * + * By default, PHP's empty() function is used to test for emptiness. This + * method may be overridden by subtypes to better match their understanding + * of empty data. + * + * @see finalizeValue() + */ + protected function isValueEmpty(mixed $value): bool + { + return !$value; + } +} diff --git a/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php new file mode 100644 index 0000000..2d2a4de --- /dev/null +++ b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +/** + * Exception class for when a circular reference is detected when importing resources. + * + * @author Fabien Potencier + */ +class FileLoaderImportCircularReferenceException extends LoaderLoadException +{ + public function __construct(array $resources, int $code = 0, ?\Throwable $previous = null) + { + $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]); + + \Exception::__construct($message, $code, $previous); + } +} diff --git a/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php b/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php new file mode 100644 index 0000000..5641a31 --- /dev/null +++ b/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +/** + * File locator exception if a file does not exist. + * + * @author Leo Feyer + */ +class FileLocatorFileNotFoundException extends \InvalidArgumentException +{ + private array $paths; + + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, array $paths = []) + { + parent::__construct($message, $code, $previous); + + $this->paths = $paths; + } + + public function getPaths(): array + { + return $this->paths; + } +} diff --git a/vendor/symfony/config/Exception/LoaderLoadException.php b/vendor/symfony/config/Exception/LoaderLoadException.php new file mode 100644 index 0000000..a2d5e33 --- /dev/null +++ b/vendor/symfony/config/Exception/LoaderLoadException.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +/** + * Exception class for when a resource cannot be loaded or imported. + * + * @author Ryan Weaver + */ +class LoaderLoadException extends \Exception +{ + /** + * @param mixed $resource The resource that could not be imported + * @param string|null $sourceResource The original resource importing the new resource + * @param int $code The error code + * @param \Throwable|null $previous A previous exception + * @param string|null $type The type of resource + */ + public function __construct(mixed $resource, ?string $sourceResource = null, int $code = 0, ?\Throwable $previous = null, ?string $type = null) + { + if (!\is_string($resource)) { + try { + $resource = json_encode($resource, \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + $resource = sprintf('resource of type "%s"', get_debug_type($resource)); + } + } + + $message = ''; + if ($previous) { + // Include the previous exception, to help the user see what might be the underlying cause + + // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim... + if (str_ends_with($previous->getMessage(), '.')) { + $trimmedMessage = substr($previous->getMessage(), 0, -1); + $message .= sprintf('%s', $trimmedMessage).' in '; + } else { + $message .= sprintf('%s', $previous->getMessage()).' in '; + } + $message .= $resource.' '; + + // show tweaked trace to complete the human readable sentence + if (null === $sourceResource) { + $message .= sprintf('(which is loaded in resource "%s")', $resource); + } else { + $message .= sprintf('(which is being imported from "%s")', $sourceResource); + } + $message .= '.'; + + // if there's no previous message, present it the default way + } elseif (null === $sourceResource) { + $message .= sprintf('Cannot load resource "%s".', $resource); + } else { + $message .= sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource); + } + + // Is the resource located inside a bundle? + if ('@' === $resource[0]) { + $parts = explode(\DIRECTORY_SEPARATOR, $resource); + $bundle = substr($parts[0], 1); + $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle); + $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource); + } elseif (null !== $type) { + $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type); + } + + parent::__construct($message, $code, $previous); + } + + protected function varToString(mixed $var): string + { + if (\is_object($var)) { + return sprintf('Object(%s)', $var::class); + } + + if (\is_array($var)) { + $a = []; + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf('Array(%s)', implode(', ', $a)); + } + + if (\is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/config/Exception/LogicException.php b/vendor/symfony/config/Exception/LogicException.php new file mode 100644 index 0000000..d227aec --- /dev/null +++ b/vendor/symfony/config/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +class LogicException extends \LogicException +{ +} diff --git a/vendor/symfony/config/FileLocator.php b/vendor/symfony/config/FileLocator.php new file mode 100644 index 0000000..bf80069 --- /dev/null +++ b/vendor/symfony/config/FileLocator.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; + +/** + * FileLocator uses an array of pre-defined paths to find files. + * + * @author Fabien Potencier + */ +class FileLocator implements FileLocatorInterface +{ + protected array $paths; + + /** + * @param string|string[] $paths A path or an array of paths where to look for resources + */ + public function __construct(string|array $paths = []) + { + $this->paths = (array) $paths; + } + + /** + * @return string|string[] + * + * @psalm-return ($first is true ? string : string[]) + */ + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array + { + if ('' === $name) { + throw new \InvalidArgumentException('An empty file name is not valid to be located.'); + } + + if ($this->isAbsolutePath($name)) { + if (!file_exists($name)) { + throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist.', $name), 0, null, [$name]); + } + + return $name; + } + + $paths = $this->paths; + + if (null !== $currentPath) { + array_unshift($paths, $currentPath); + } + + $paths = array_unique($paths); + $filepaths = $notfound = []; + + foreach ($paths as $path) { + if (@file_exists($file = $path.\DIRECTORY_SEPARATOR.$name)) { + if (true === $first) { + return $file; + } + $filepaths[] = $file; + } else { + $notfound[] = $file; + } + } + + if (!$filepaths) { + throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist (in: "%s").', $name, implode('", "', $paths)), 0, null, $notfound); + } + + return $filepaths; + } + + /** + * Returns whether the file path is an absolute path. + */ + private function isAbsolutePath(string $file): bool + { + if ('/' === $file[0] || '\\' === $file[0] + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === $file[1] + && ('\\' === $file[2] || '/' === $file[2]) + ) + || null !== parse_url($file, \PHP_URL_SCHEME) + ) { + return true; + } + + return false; + } +} diff --git a/vendor/symfony/config/FileLocatorInterface.php b/vendor/symfony/config/FileLocatorInterface.php new file mode 100644 index 0000000..87cecf4 --- /dev/null +++ b/vendor/symfony/config/FileLocatorInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; + +/** + * @author Fabien Potencier + */ +interface FileLocatorInterface +{ + /** + * Returns a full path for a given file name. + * + * @param string $name The file name to locate + * @param string|null $currentPath The current path + * @param bool $first Whether to return the first occurrence or an array of filenames + * + * @return string|string[] The full path to the file or an array of file paths + * + * @throws \InvalidArgumentException If $name is empty + * @throws FileLocatorFileNotFoundException If a file is not found + * + * @psalm-return ($first is true ? string : string[]) + */ + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array; +} diff --git a/vendor/symfony/config/LICENSE b/vendor/symfony/config/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/config/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/config/Loader/DelegatingLoader.php b/vendor/symfony/config/Loader/DelegatingLoader.php new file mode 100644 index 0000000..045a559 --- /dev/null +++ b/vendor/symfony/config/Loader/DelegatingLoader.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +use Symfony\Component\Config\Exception\LoaderLoadException; + +/** + * DelegatingLoader delegates loading to other loaders using a loader resolver. + * + * This loader acts as an array of LoaderInterface objects - each having + * a chance to load a given resource (handled by the resolver) + * + * @author Fabien Potencier + */ +class DelegatingLoader extends Loader +{ + public function __construct(LoaderResolverInterface $resolver) + { + $this->resolver = $resolver; + } + + public function load(mixed $resource, ?string $type = null): mixed + { + if (false === $loader = $this->resolver->resolve($resource, $type)) { + throw new LoaderLoadException($resource, null, 0, null, $type); + } + + return $loader->load($resource, $type); + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return false !== $this->resolver->resolve($resource, $type); + } +} diff --git a/vendor/symfony/config/Loader/DirectoryAwareLoaderInterface.php b/vendor/symfony/config/Loader/DirectoryAwareLoaderInterface.php new file mode 100644 index 0000000..87559cb --- /dev/null +++ b/vendor/symfony/config/Loader/DirectoryAwareLoaderInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * A loader that can be scoped to a given filesystem directory. + * + * @author Alexander M. Turek + */ +interface DirectoryAwareLoaderInterface +{ + public function forDirectory(string $currentDirectory): static; +} diff --git a/vendor/symfony/config/Loader/FileLoader.php b/vendor/symfony/config/Loader/FileLoader.php new file mode 100644 index 0000000..c217cd8 --- /dev/null +++ b/vendor/symfony/config/Loader/FileLoader.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException; +use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; +use Symfony\Component\Config\Exception\LoaderLoadException; +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Resource\FileExistenceResource; +use Symfony\Component\Config\Resource\GlobResource; + +/** + * FileLoader is the abstract class used by all built-in loaders that are file based. + * + * @author Fabien Potencier + */ +abstract class FileLoader extends Loader +{ + protected static array $loading = []; + + protected FileLocatorInterface $locator; + + private ?string $currentDir = null; + + public function __construct(FileLocatorInterface $locator, ?string $env = null) + { + $this->locator = $locator; + parent::__construct($env); + } + + /** + * Sets the current directory. + */ + public function setCurrentDir(string $dir): void + { + $this->currentDir = $dir; + } + + /** + * Returns the file locator used by this loader. + */ + public function getLocator(): FileLocatorInterface + { + return $this->locator; + } + + /** + * Imports a resource. + * + * @param mixed $resource A Resource + * @param string|null $type The resource type or null if unknown + * @param bool $ignoreErrors Whether to ignore import errors or not + * @param string|null $sourceResource The original resource importing the new resource + * @param string|string[]|null $exclude Glob patterns to exclude from the import + * + * @throws LoaderLoadException + * @throws FileLoaderImportCircularReferenceException + * @throws FileLocatorFileNotFoundException + */ + public function import(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null, string|array|null $exclude = null): mixed + { + if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { + $excluded = []; + foreach ((array) $exclude as $pattern) { + foreach ($this->glob($pattern, true, $_, false, true) as $path => $info) { + // normalize Windows slashes and remove trailing slashes + $excluded[rtrim(str_replace('\\', '/', $path), '/')] = true; + } + } + + $ret = []; + $isSubpath = 0 !== $i && str_contains(substr($resource, 0, $i), '/'); + foreach ($this->glob($resource, false, $_, $ignoreErrors || !$isSubpath, false, $excluded) as $path => $info) { + if (null !== $res = $this->doImport($path, 'glob' === $type ? null : $type, $ignoreErrors, $sourceResource)) { + $ret[] = $res; + } + $isSubpath = true; + } + + if ($isSubpath) { + return isset($ret[1]) ? $ret : ($ret[0] ?? null); + } + } + + return $this->doImport($resource, $type, $ignoreErrors, $sourceResource); + } + + /** + * @internal + */ + protected function glob(string $pattern, bool $recursive, array|GlobResource|null &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = []): iterable + { + if (\strlen($pattern) === $i = strcspn($pattern, '*?{[')) { + $prefix = $pattern; + $pattern = ''; + } elseif (0 === $i || !str_contains(substr($pattern, 0, $i), '/')) { + $prefix = '.'; + $pattern = '/'.$pattern; + } else { + $prefix = \dirname(substr($pattern, 0, 1 + $i)); + $pattern = substr($pattern, \strlen($prefix)); + } + + try { + $prefix = $this->locator->locate($prefix, $this->currentDir, true); + } catch (FileLocatorFileNotFoundException $e) { + if (!$ignoreErrors) { + throw $e; + } + + $resource = []; + foreach ($e->getPaths() as $path) { + $resource[] = new FileExistenceResource($path); + } + + return; + } + $resource = new GlobResource($prefix, $pattern, $recursive, $forExclusion, $excluded); + + yield from $resource; + } + + private function doImport(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null): mixed + { + try { + $loader = $this->resolve($resource, $type); + + if ($loader instanceof DirectoryAwareLoaderInterface) { + $loader = $loader->forDirectory($this->currentDir); + } + + if (!$loader instanceof self) { + return $loader->load($resource, $type); + } + + if (null !== $this->currentDir) { + $resource = $loader->getLocator()->locate($resource, $this->currentDir, false); + } + + $resources = \is_array($resource) ? $resource : [$resource]; + for ($i = 0; $i < $resourcesCount = \count($resources); ++$i) { + if (isset(self::$loading[$resources[$i]])) { + if ($i == $resourcesCount - 1) { + throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading)); + } + } else { + $resource = $resources[$i]; + break; + } + } + self::$loading[$resource] = true; + + try { + $ret = $loader->load($resource, $type); + } finally { + unset(self::$loading[$resource]); + } + + return $ret; + } catch (FileLoaderImportCircularReferenceException $e) { + throw $e; + } catch (\Exception $e) { + if (!$ignoreErrors) { + // prevent embedded imports from nesting multiple exceptions + if ($e instanceof LoaderLoadException) { + throw $e; + } + + throw new LoaderLoadException($resource, $sourceResource, 0, $e, $type); + } + } + + return null; + } +} diff --git a/vendor/symfony/config/Loader/GlobFileLoader.php b/vendor/symfony/config/Loader/GlobFileLoader.php new file mode 100644 index 0000000..31eebf6 --- /dev/null +++ b/vendor/symfony/config/Loader/GlobFileLoader.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * GlobFileLoader loads files from a glob pattern. + * + * @author Fabien Potencier + */ +class GlobFileLoader extends FileLoader +{ + public function load(mixed $resource, ?string $type = null): mixed + { + return $this->import($resource); + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return 'glob' === $type; + } +} diff --git a/vendor/symfony/config/Loader/Loader.php b/vendor/symfony/config/Loader/Loader.php new file mode 100644 index 0000000..28e5877 --- /dev/null +++ b/vendor/symfony/config/Loader/Loader.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +use Symfony\Component\Config\Exception\LoaderLoadException; +use Symfony\Component\Config\Exception\LogicException; + +/** + * Loader is the abstract class used by all built-in loaders. + * + * @author Fabien Potencier + */ +abstract class Loader implements LoaderInterface +{ + protected ?LoaderResolverInterface $resolver = null; + + public function __construct( + protected ?string $env = null, + ) { + } + + public function getResolver(): LoaderResolverInterface + { + if (null === $this->resolver) { + throw new LogicException('Cannot get a resolver if none was set.'); + } + + return $this->resolver; + } + + public function setResolver(LoaderResolverInterface $resolver): void + { + $this->resolver = $resolver; + } + + /** + * Imports a resource. + */ + public function import(mixed $resource, ?string $type = null): mixed + { + return $this->resolve($resource, $type)->load($resource, $type); + } + + /** + * Finds a loader able to load an imported resource. + * + * @throws LoaderLoadException If no loader is found + */ + public function resolve(mixed $resource, ?string $type = null): LoaderInterface + { + if ($this->supports($resource, $type)) { + return $this; + } + + $loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type); + + if (false === $loader) { + throw new LoaderLoadException($resource, null, 0, null, $type); + } + + return $loader; + } +} diff --git a/vendor/symfony/config/Loader/LoaderInterface.php b/vendor/symfony/config/Loader/LoaderInterface.php new file mode 100644 index 0000000..6ed1893 --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * LoaderInterface is the interface implemented by all loader classes. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a resource. + * + * @throws \Exception If something went wrong + */ + public function load(mixed $resource, ?string $type = null): mixed; + + /** + * Returns whether this class supports the given resource. + * + * @param mixed $resource A resource + */ + public function supports(mixed $resource, ?string $type = null): bool; + + /** + * Gets the loader resolver. + */ + public function getResolver(): LoaderResolverInterface; + + /** + * Sets the loader resolver. + */ + public function setResolver(LoaderResolverInterface $resolver): void; +} diff --git a/vendor/symfony/config/Loader/LoaderResolver.php b/vendor/symfony/config/Loader/LoaderResolver.php new file mode 100644 index 0000000..8308d7e --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderResolver.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * LoaderResolver selects a loader for a given resource. + * + * A resource can be anything (e.g. a full path to a config file or a Closure). + * Each loader determines whether it can load a resource and how. + * + * @author Fabien Potencier + */ +class LoaderResolver implements LoaderResolverInterface +{ + /** + * @var LoaderInterface[] An array of LoaderInterface objects + */ + private array $loaders = []; + + /** + * @param LoaderInterface[] $loaders An array of loaders + */ + public function __construct(array $loaders = []) + { + foreach ($loaders as $loader) { + $this->addLoader($loader); + } + } + + public function resolve(mixed $resource, ?string $type = null): LoaderInterface|false + { + foreach ($this->loaders as $loader) { + if ($loader->supports($resource, $type)) { + return $loader; + } + } + + return false; + } + + public function addLoader(LoaderInterface $loader): void + { + $this->loaders[] = $loader; + $loader->setResolver($this); + } + + /** + * Returns the registered loaders. + * + * @return LoaderInterface[] + */ + public function getLoaders(): array + { + return $this->loaders; + } +} diff --git a/vendor/symfony/config/Loader/LoaderResolverInterface.php b/vendor/symfony/config/Loader/LoaderResolverInterface.php new file mode 100644 index 0000000..a8bb3a4 --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderResolverInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * LoaderResolverInterface selects a loader for a given resource. + * + * @author Fabien Potencier + */ +interface LoaderResolverInterface +{ + /** + * Returns a loader able to load the resource. + * + * @param string|null $type The resource type or null if unknown + */ + public function resolve(mixed $resource, ?string $type = null): LoaderInterface|false; +} diff --git a/vendor/symfony/config/Loader/ParamConfigurator.php b/vendor/symfony/config/Loader/ParamConfigurator.php new file mode 100644 index 0000000..ed10452 --- /dev/null +++ b/vendor/symfony/config/Loader/ParamConfigurator.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * Placeholder for a parameter. + * + * @author Tobias Nyholm + */ +class ParamConfigurator +{ + public function __construct( + private string $name, + ) { + } + + public function __toString(): string + { + return '%'.$this->name.'%'; + } +} diff --git a/vendor/symfony/config/README.md b/vendor/symfony/config/README.md new file mode 100644 index 0000000..10c2ddd --- /dev/null +++ b/vendor/symfony/config/README.md @@ -0,0 +1,15 @@ +Config Component +================ + +The Config component helps find, load, combine, autofill and validate +configuration values of any kind, whatever their source may be (YAML, XML, INI +files, or for instance a database). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/config.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/config/Resource/ClassExistenceResource.php b/vendor/symfony/config/Resource/ClassExistenceResource.php new file mode 100644 index 0000000..e2175b9 --- /dev/null +++ b/vendor/symfony/config/Resource/ClassExistenceResource.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * ClassExistenceResource represents a class existence. + * Freshness is only evaluated against resource existence. + * + * The resource must be a fully-qualified class name. + * + * @author Fabien Potencier + * + * @final + */ +class ClassExistenceResource implements SelfCheckingResourceInterface +{ + private ?array $exists = null; + + private static int $autoloadLevel = 0; + private static ?string $autoloadedClass = null; + private static array $existsCache = []; + + /** + * @param string $resource The fully-qualified class name + * @param bool|null $exists Boolean when the existence check has already been done + */ + public function __construct( + private string $resource, + ?bool $exists = null, + ) { + if (null !== $exists) { + $this->exists = [$exists, null]; + } + } + + public function __toString(): string + { + return $this->resource; + } + + public function getResource(): string + { + return $this->resource; + } + + /** + * @throws \ReflectionException when a parent class/interface/trait is not found + */ + public function isFresh(int $timestamp): bool + { + $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false); + + if (null !== $exists = &self::$existsCache[$this->resource]) { + if ($loaded) { + $exists = [true, null]; + } elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) { + throw new \ReflectionException($exists[1]); + } + } elseif ([false, null] === $exists = [$loaded, null]) { + if (!self::$autoloadLevel++) { + spl_autoload_register(__CLASS__.'::throwOnRequiredClass'); + } + $autoloadedClass = self::$autoloadedClass; + self::$autoloadedClass = ltrim($this->resource, '\\'); + + try { + $exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false); + } catch (\Exception $e) { + $exists[1] = $e->getMessage(); + + try { + self::throwOnRequiredClass($this->resource, $e); + } catch (\ReflectionException $e) { + if (0 >= $timestamp) { + throw $e; + } + } + } catch (\Throwable $e) { + $exists[1] = $e->getMessage(); + + throw $e; + } finally { + self::$autoloadedClass = $autoloadedClass; + if (!--self::$autoloadLevel) { + spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass'); + } + } + } + + $this->exists ??= $exists; + + return $this->exists[0] xor !$exists[0]; + } + + /** + * @internal + */ + public function __sleep(): array + { + if (null === $this->exists) { + $this->isFresh(0); + } + + return ['resource', 'exists']; + } + + /** + * @internal + */ + public function __wakeup(): void + { + if (\is_bool($this->exists)) { + $this->exists = [$this->exists, null]; + } + } + + /** + * Throws a reflection exception when the passed class does not exist but is required. + * + * A class is considered "not required" when it's loaded as part of a "class_exists" or similar check. + * + * This function can be used as an autoload function to throw a reflection + * exception if the class was not found by previous autoload functions. + * + * A previous exception can be passed. In this case, the class is considered as being + * required totally, so if it doesn't exist, a reflection exception is always thrown. + * If it exists, the previous exception is rethrown. + * + * @throws \ReflectionException + * + * @internal + */ + public static function throwOnRequiredClass(string $class, ?\Exception $previous = null): void + { + // If the passed class is the resource being checked, we shouldn't throw. + if (null === $previous && self::$autoloadedClass === $class) { + return; + } + + if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) { + if (null !== $previous) { + throw $previous; + } + + return; + } + + if ($previous instanceof \ReflectionException) { + throw $previous; + } + + $message = sprintf('Class "%s" not found.', $class); + + if ($class !== (self::$autoloadedClass ?? $class)) { + $message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0); + } + + if (null !== $previous) { + $message = $previous->getMessage(); + } + + $e = new \ReflectionException($message, 0, $previous); + + if (null !== $previous) { + throw $e; + } + + $trace = debug_backtrace(); + $autoloadFrame = [ + 'function' => 'spl_autoload_call', + 'args' => [$class], + ]; + + if (isset($trace[1])) { + $callerFrame = $trace[1]; + $i = 2; + } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) { + $callerFrame = $trace[++$i]; + } else { + throw $e; + } + + if (isset($callerFrame['function']) && !isset($callerFrame['class'])) { + switch ($callerFrame['function']) { + case 'get_class_methods': + case 'get_class_vars': + case 'get_parent_class': + case 'is_a': + case 'is_subclass_of': + case 'class_exists': + case 'class_implements': + case 'class_parents': + case 'trait_exists': + case 'defined': + case 'interface_exists': + case 'method_exists': + case 'property_exists': + case 'is_callable': + return; + } + + $props = [ + 'file' => $callerFrame['file'] ?? null, + 'line' => $callerFrame['line'] ?? null, + 'trace' => \array_slice($trace, 1 + $i), + ]; + + foreach ($props as $p => $v) { + if (null !== $v) { + $r = new \ReflectionProperty(\Exception::class, $p); + $r->setValue($e, $v); + } + } + } + + throw $e; + } +} diff --git a/vendor/symfony/config/Resource/ComposerResource.php b/vendor/symfony/config/Resource/ComposerResource.php new file mode 100644 index 0000000..8348122 --- /dev/null +++ b/vendor/symfony/config/Resource/ComposerResource.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * ComposerResource tracks the PHP version and Composer dependencies. + * + * @author Nicolas Grekas + * + * @final + */ +class ComposerResource implements SelfCheckingResourceInterface +{ + private array $vendors; + + private static array $runtimeVendors; + + public function __construct() + { + self::refresh(); + $this->vendors = self::$runtimeVendors; + } + + public function getVendors(): array + { + return array_keys($this->vendors); + } + + public function __toString(): string + { + return __CLASS__; + } + + public function isFresh(int $timestamp): bool + { + self::refresh(); + + return array_values(self::$runtimeVendors) === array_values($this->vendors); + } + + private static function refresh(): void + { + self::$runtimeVendors = []; + + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname($r->getFileName(), 2); + if (is_file($v.'/composer/installed.json')) { + self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json'); + } + } + } + } +} diff --git a/vendor/symfony/config/Resource/DirectoryResource.php b/vendor/symfony/config/Resource/DirectoryResource.php new file mode 100644 index 0000000..372a895 --- /dev/null +++ b/vendor/symfony/config/Resource/DirectoryResource.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * DirectoryResource represents a resources stored in a subdirectory tree. + * + * @author Fabien Potencier + * + * @final + */ +class DirectoryResource implements SelfCheckingResourceInterface +{ + private string $resource; + + /** + * @param string $resource The file path to the resource + * @param string|null $pattern A pattern to restrict monitored files + * + * @throws \InvalidArgumentException + */ + public function __construct( + string $resource, + private ?string $pattern = null, + ) { + $resolvedResource = realpath($resource) ?: (file_exists($resource) ? $resource : false); + + if (false === $resolvedResource || !is_dir($resolvedResource)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource)); + } + + $this->resource = $resolvedResource; + } + + public function __toString(): string + { + return hash('xxh128', serialize([$this->resource, $this->pattern])); + } + + public function getResource(): string + { + return $this->resource; + } + + public function getPattern(): ?string + { + return $this->pattern; + } + + public function isFresh(int $timestamp): bool + { + if (!is_dir($this->resource)) { + return false; + } + + if ($timestamp < filemtime($this->resource)) { + return false; + } + + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { + // if regex filtering is enabled only check matching files + if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) { + continue; + } + + // always monitor directories for changes, except the .. entries + // (otherwise deleted files wouldn't get detected) + if ($file->isDir() && str_ends_with($file, '/..')) { + continue; + } + + // for broken links + try { + $fileMTime = $file->getMTime(); + } catch (\RuntimeException) { + continue; + } + + // early return if a file's mtime exceeds the passed timestamp + if ($timestamp < $fileMTime) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/config/Resource/FileExistenceResource.php b/vendor/symfony/config/Resource/FileExistenceResource.php new file mode 100644 index 0000000..4722537 --- /dev/null +++ b/vendor/symfony/config/Resource/FileExistenceResource.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * FileExistenceResource represents a resource stored on the filesystem. + * Freshness is only evaluated against resource creation or deletion. + * + * The resource can be a file or a directory. + * + * @author Charles-Henri Bruyand + * + * @final + */ +class FileExistenceResource implements SelfCheckingResourceInterface +{ + private bool $exists; + + /** + * @param string $resource The file path to the resource + */ + public function __construct( + private string $resource, + ) { + $this->exists = file_exists($resource); + } + + public function __toString(): string + { + return 'existence.'.$this->resource; + } + + public function getResource(): string + { + return $this->resource; + } + + public function isFresh(int $timestamp): bool + { + return file_exists($this->resource) === $this->exists; + } +} diff --git a/vendor/symfony/config/Resource/FileResource.php b/vendor/symfony/config/Resource/FileResource.php new file mode 100644 index 0000000..6e8f9bd --- /dev/null +++ b/vendor/symfony/config/Resource/FileResource.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * FileResource represents a resource stored on the filesystem. + * + * The resource can be a file or a directory. + * + * @author Fabien Potencier + * + * @final + */ +class FileResource implements SelfCheckingResourceInterface +{ + private string $resource; + + /** + * @param string $resource The file path to the resource + * + * @throws \InvalidArgumentException + */ + public function __construct(string $resource) + { + $resolvedResource = realpath($resource) ?: (file_exists($resource) ? $resource : false); + + if (false === $resolvedResource) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource)); + } + + $this->resource = $resolvedResource; + } + + public function __toString(): string + { + return $this->resource; + } + + /** + * Returns the canonicalized, absolute path to the resource. + */ + public function getResource(): string + { + return $this->resource; + } + + public function isFresh(int $timestamp): bool + { + return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp; + } +} diff --git a/vendor/symfony/config/Resource/GlobResource.php b/vendor/symfony/config/Resource/GlobResource.php new file mode 100644 index 0000000..1cc627b --- /dev/null +++ b/vendor/symfony/config/Resource/GlobResource.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\Glob; + +/** + * GlobResource represents a set of resources stored on the filesystem. + * + * Only existence/removal is tracked (not mtimes.) + * + * @author Nicolas Grekas + * + * @final + * + * @implements \IteratorAggregate + */ +class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface +{ + private string $prefix; + private string $hash; + private array $excludedPrefixes; + private int $globBrace; + + /** + * @param string $prefix A directory prefix + * @param string $pattern A glob pattern + * @param bool $recursive Whether directories should be scanned recursively or not + * + * @throws \InvalidArgumentException + */ + public function __construct( + string $prefix, + private string $pattern, + private bool $recursive, + private bool $forExclusion = false, + array $excludedPrefixes = [], + ) { + ksort($excludedPrefixes); + $resolvedPrefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false); + $this->excludedPrefixes = $excludedPrefixes; + $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; + + if (false === $resolvedPrefix) { + throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix)); + } + + $this->prefix = $resolvedPrefix; + } + + public function getPrefix(): string + { + return $this->prefix; + } + + public function __toString(): string + { + return 'glob.'.$this->prefix.(int) $this->recursive.$this->pattern.(int) $this->forExclusion.implode("\0", $this->excludedPrefixes); + } + + public function isFresh(int $timestamp): bool + { + $hash = $this->computeHash(); + $this->hash ??= $hash; + + return $this->hash === $hash; + } + + /** + * @internal + */ + public function __sleep(): array + { + $this->hash ??= $this->computeHash(); + + return ['prefix', 'pattern', 'recursive', 'hash', 'forExclusion', 'excludedPrefixes']; + } + + /** + * @internal + */ + public function __wakeup(): void + { + $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; + } + + public function getIterator(): \Traversable + { + if ((!$this->recursive && '' === $this->pattern) || !file_exists($this->prefix)) { + return; + } + + if (is_file($prefix = str_replace('\\', '/', $this->prefix))) { + $prefix = \dirname($prefix); + $pattern = basename($prefix).$this->pattern; + } else { + $pattern = $this->pattern; + } + + if (class_exists(Finder::class)) { + $regex = Glob::toRegex($pattern); + if ($this->recursive) { + $regex = substr_replace($regex, '(/|$)', -2, 1); + } + } else { + $regex = null; + } + + $prefixLen = \strlen($prefix); + $paths = null; + + if ('' === $this->pattern && is_file($this->prefix)) { + $paths = [$this->prefix => null]; + } elseif (!str_starts_with($this->prefix, 'phar://') && (null !== $regex || !str_contains($this->pattern, '/**/'))) { + if (!str_contains($this->pattern, '/**/') && ($this->globBrace || !str_contains($this->pattern, '{'))) { + $paths = array_fill_keys(glob($this->prefix.$this->pattern, \GLOB_NOSORT | $this->globBrace), null); + } elseif (!str_contains($this->pattern, '\\') || !preg_match('/\\\\[,{}]/', $this->pattern)) { + $paths = []; + foreach ($this->expandGlob($this->pattern) as $p) { + if (false !== $i = strpos($p, '/**/')) { + $p = substr_replace($p, '/*', $i); + } + $paths += array_fill_keys(glob($this->prefix.$p, \GLOB_NOSORT), false !== $i ? $regex : null); + } + } + } + + if (null !== $paths) { + uksort($paths, 'strnatcmp'); + foreach ($paths as $path => $regex) { + if ($this->excludedPrefixes) { + $normalizedPath = str_replace('\\', '/', $path); + do { + if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { + continue 2; + } + } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath)); + } + + if ((null === $regex || preg_match($regex, substr(str_replace('\\', '/', $path), $prefixLen))) && is_file($path)) { + yield $path => new \SplFileInfo($path); + } + if (!is_dir($path)) { + continue; + } + if ($this->forExclusion && (null === $regex || preg_match($regex, substr(str_replace('\\', '/', $path), $prefixLen)))) { + yield $path => new \SplFileInfo($path); + continue; + } + if (!($this->recursive || null !== $regex) || isset($this->excludedPrefixes[str_replace('\\', '/', $path)])) { + continue; + } + $files = iterator_to_array(new \RecursiveIteratorIterator( + new \RecursiveCallbackFilterIterator( + new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + fn (\SplFileInfo $file, $path) => !isset($this->excludedPrefixes[$path = str_replace('\\', '/', $path)]) + && (null === $regex || preg_match($regex, substr($path, $prefixLen)) || $file->isDir()) + && '.' !== $file->getBasename()[0] + ), + \RecursiveIteratorIterator::LEAVES_ONLY + )); + uksort($files, 'strnatcmp'); + + foreach ($files as $path => $info) { + if ($info->isFile()) { + yield $path => $info; + } + } + } + + return; + } + + if (!class_exists(Finder::class)) { + throw new \LogicException('Extended glob patterns cannot be used as the Finder component is not installed. Try running "composer require symfony/finder".'); + } + + yield from (new Finder()) + ->followLinks() + ->filter(function (\SplFileInfo $info) use ($regex, $prefixLen, $prefix) { + $normalizedPath = str_replace('\\', '/', $info->getPathname()); + if (!preg_match($regex, substr($normalizedPath, $prefixLen)) || !$info->isFile()) { + return false; + } + if ($this->excludedPrefixes) { + do { + if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { + return false; + } + } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath)); + } + }) + ->sortByName() + ->in($prefix) + ; + } + + private function computeHash(): string + { + $hash = hash_init('xxh128'); + + foreach ($this->getIterator() as $path => $info) { + hash_update($hash, $path."\n"); + } + + return hash_final($hash); + } + + private function expandGlob(string $pattern): array + { + $segments = preg_split('/\{([^{}]*+)\}/', $pattern, -1, \PREG_SPLIT_DELIM_CAPTURE); + $paths = [$segments[0]]; + $patterns = []; + + for ($i = 1; $i < \count($segments); $i += 2) { + $patterns = []; + + foreach (explode(',', $segments[$i]) as $s) { + foreach ($paths as $p) { + $patterns[] = $p.$s.$segments[1 + $i]; + } + } + + $paths = $patterns; + } + + $j = 0; + foreach ($patterns as $i => $p) { + if (str_contains($p, '{')) { + $p = $this->expandGlob($p); + array_splice($paths, $i + $j, 1, $p); + $j += \count($p) - 1; + } + } + + return $paths; + } +} diff --git a/vendor/symfony/config/Resource/ReflectionClassResource.php b/vendor/symfony/config/Resource/ReflectionClassResource.php new file mode 100644 index 0000000..dbc927b --- /dev/null +++ b/vendor/symfony/config/Resource/ReflectionClassResource.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * @author Nicolas Grekas + * + * @final + */ +class ReflectionClassResource implements SelfCheckingResourceInterface +{ + private array $files = []; + private string $className; + private \ReflectionClass $classReflector; + private array $excludedVendors = []; + private string $hash; + + public function __construct(\ReflectionClass $classReflector, array $excludedVendors = []) + { + $this->className = $classReflector->name; + $this->classReflector = $classReflector; + $this->excludedVendors = $excludedVendors; + } + + public function isFresh(int $timestamp): bool + { + if (!isset($this->hash)) { + $this->hash = $this->computeHash(); + $this->loadFiles($this->classReflector); + } + + foreach ($this->files as $file => $v) { + if (false === $filemtime = @filemtime($file)) { + return false; + } + + if ($filemtime > $timestamp) { + return $this->hash === $this->computeHash(); + } + } + + return true; + } + + public function __toString(): string + { + return 'reflection.'.$this->className; + } + + /** + * @internal + */ + public function __sleep(): array + { + if (!isset($this->hash)) { + $this->hash = $this->computeHash(); + $this->loadFiles($this->classReflector); + } + + return ['files', 'className', 'hash']; + } + + private function loadFiles(\ReflectionClass $class): void + { + foreach ($class->getInterfaces() as $v) { + $this->loadFiles($v); + } + do { + $file = $class->getFileName(); + if (false !== $file && is_file($file)) { + foreach ($this->excludedVendors as $vendor) { + if (str_starts_with($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + $file = false; + break; + } + } + if ($file) { + $this->files[$file] = null; + } + } + foreach ($class->getTraits() as $v) { + $this->loadFiles($v); + } + } while ($class = $class->getParentClass()); + } + + private function computeHash(): string + { + try { + $this->classReflector ??= new \ReflectionClass($this->className); + } catch (\ReflectionException) { + // the class does not exist anymore + return false; + } + $hash = hash_init('xxh128'); + + foreach ($this->generateSignature($this->classReflector) as $info) { + hash_update($hash, $info); + } + + return hash_final($hash); + } + + private function generateSignature(\ReflectionClass $class): iterable + { + $attributes = []; + foreach ($class->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; + } + yield print_r($attributes, true); + $attributes = []; + + yield $class->getDocComment(); + yield (int) $class->isFinal(); + yield (int) $class->isAbstract(); + + if ($class->isTrait()) { + yield print_r(class_uses($class->name), true); + } else { + yield print_r(class_parents($class->name), true); + yield print_r(class_implements($class->name), true); + yield print_r($class->getConstants(), true); + } + + if (!$class->isInterface()) { + $defaults = $class->getDefaultProperties(); + + foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) { + foreach ($p->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; + } + yield print_r($attributes, true); + $attributes = []; + + yield $p->getDocComment(); + yield $p->isDefault() ? '' : ''; + yield $p->isPublic() ? 'public' : 'protected'; + yield $p->isStatic() ? 'static' : ''; + yield '$'.$p->name; + yield print_r(isset($defaults[$p->name]) && !\is_object($defaults[$p->name]) ? $defaults[$p->name] : null, true); + } + } + + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { + foreach ($m->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; + } + yield print_r($attributes, true); + $attributes = []; + + $defaults = []; + foreach ($m->getParameters() as $p) { + foreach ($p->getAttributes() as $a) { + $attributes[] = [$a->getName(), (string) $a]; + } + yield print_r($attributes, true); + $attributes = []; + + if (!$p->isDefaultValueAvailable()) { + $defaults[$p->name] = null; + + continue; + } + + $defaults[$p->name] = (string) $p; + } + + yield preg_replace('/^ @@.*/m', '', $m); + yield print_r($defaults, true); + } + + if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) { + return; + } + + if (interface_exists(EventSubscriberInterface::class, false) && $class->isSubclassOf(EventSubscriberInterface::class)) { + yield EventSubscriberInterface::class; + yield print_r($class->name::getSubscribedEvents(), true); + } + + if (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) { + yield ServiceSubscriberInterface::class; + yield print_r($class->name::getSubscribedServices(), true); + } + } +} diff --git a/vendor/symfony/config/Resource/ResourceInterface.php b/vendor/symfony/config/Resource/ResourceInterface.php new file mode 100644 index 0000000..a97671d --- /dev/null +++ b/vendor/symfony/config/Resource/ResourceInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * ResourceInterface is the interface that must be implemented by all Resource classes. + * + * @author Fabien Potencier + */ +interface ResourceInterface extends \Stringable +{ + /** + * Returns a string representation of the Resource. + * + * This method is necessary to allow for resource de-duplication, for example by means + * of array_unique(). The string returned need not have a particular meaning, but has + * to be identical for different ResourceInterface instances referring to the same + * resource; and it should be unlikely to collide with that of other, unrelated + * resource instances. + */ + public function __toString(): string; +} diff --git a/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php b/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php new file mode 100644 index 0000000..5abdbad --- /dev/null +++ b/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +use Symfony\Component\Config\ResourceCheckerInterface; + +/** + * Resource checker for instances of SelfCheckingResourceInterface. + * + * As these resources perform the actual check themselves, we can provide + * this class as a standard way of validating them. + * + * @author Matthias Pigulla + */ +class SelfCheckingResourceChecker implements ResourceCheckerInterface +{ + // Common shared cache, because this checker can be used in different + // situations. For example, when using the full stack framework, the router + // and the container have their own cache. But they may check the very same + // resources + private static array $cache = []; + + public function supports(ResourceInterface $metadata): bool + { + return $metadata instanceof SelfCheckingResourceInterface; + } + + /** + * @param SelfCheckingResourceInterface $resource + */ + public function isFresh(ResourceInterface $resource, int $timestamp): bool + { + $key = "$resource:$timestamp"; + + return self::$cache[$key] ??= $resource->isFresh($timestamp); + } +} diff --git a/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php b/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php new file mode 100644 index 0000000..197ff1f --- /dev/null +++ b/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * Interface for Resources that can check for freshness autonomously, + * without special support from external services. + * + * @author Matthias Pigulla + */ +interface SelfCheckingResourceInterface extends ResourceInterface +{ + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param int $timestamp The last time the resource was loaded + */ + public function isFresh(int $timestamp): bool; +} diff --git a/vendor/symfony/config/ResourceCheckerConfigCache.php b/vendor/symfony/config/ResourceCheckerConfigCache.php new file mode 100644 index 0000000..b730748 --- /dev/null +++ b/vendor/symfony/config/ResourceCheckerConfigCache.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; + +/** + * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface + * to check whether cached data is still fresh. + * + * @author Matthias Pigulla + */ +class ResourceCheckerConfigCache implements ConfigCacheInterface +{ + private string $metaFile; + + /** + * @param string $file The absolute cache path + * @param iterable $resourceCheckers The ResourceCheckers to use for the freshness check + * @param string|null $metaFile The absolute path to the meta file, defaults to $file.meta if null + */ + public function __construct( + private string $file, + private iterable $resourceCheckers = [], + ?string $metaFile = null, + ) { + $this->metaFile = $metaFile ?? $file.'.meta'; + } + + public function getPath(): string + { + return $this->file; + } + + /** + * Checks if the cache is still fresh. + * + * This implementation will make a decision solely based on the ResourceCheckers + * passed in the constructor. + * + * The first ResourceChecker that supports a given resource is considered authoritative. + * Resources with no matching ResourceChecker will silently be ignored and considered fresh. + */ + public function isFresh(): bool + { + if (!is_file($this->file)) { + return false; + } + + if ($this->resourceCheckers instanceof \Traversable && !$this->resourceCheckers instanceof \Countable) { + $this->resourceCheckers = iterator_to_array($this->resourceCheckers); + } + + if (!\count($this->resourceCheckers)) { + return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all + } + + $metadata = $this->metaFile; + + if (!is_file($metadata)) { + return false; + } + + $meta = $this->safelyUnserialize($metadata); + + if (false === $meta) { + return false; + } + + $time = filemtime($this->file); + + foreach ($meta as $resource) { + foreach ($this->resourceCheckers as $checker) { + if (!$checker->supports($resource)) { + continue; // next checker + } + if ($checker->isFresh($resource, $time)) { + break; // no need to further check this resource + } + + return false; // cache is stale + } + // no suitable checker found, ignore this resource + } + + return true; + } + + /** + * Writes cache. + * + * @param string $content The content to write in the cache + * @param ResourceInterface[] $metadata An array of metadata + * + * @throws \RuntimeException When cache file can't be written + */ + public function write(string $content, ?array $metadata = null): void + { + $mode = 0666; + $umask = umask(); + $filesystem = new Filesystem(); + $filesystem->dumpFile($this->file, $content); + try { + $filesystem->chmod($this->file, $mode, $umask); + } catch (IOException) { + // discard chmod failure (some filesystem may not support it) + } + + if (null !== $metadata) { + $filesystem->dumpFile($this->metaFile, serialize($metadata)); + try { + $filesystem->chmod($this->metaFile, $mode, $umask); + } catch (IOException) { + // discard chmod failure (some filesystem may not support it) + } + } + + if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL)) { + @opcache_invalidate($this->file, true); + } + } + + private function safelyUnserialize(string $file): mixed + { + $meta = false; + $content = (new Filesystem())->readFile($file); + $signalingException = new \UnexpectedValueException(); + $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback'); + $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) { + if (__FILE__ === $file && !\in_array($type, [\E_DEPRECATED, \E_USER_DEPRECATED], true)) { + throw $signalingException; + } + + return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false; + }); + + try { + $meta = unserialize($content); + } catch (\Throwable $e) { + if ($e !== $signalingException) { + throw $e; + } + } finally { + restore_error_handler(); + ini_set('unserialize_callback_func', $prevUnserializeHandler); + } + + return $meta; + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class): void + { + trigger_error('Class not found: '.$class); + } +} diff --git a/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php b/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php new file mode 100644 index 0000000..595855c --- /dev/null +++ b/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * A ConfigCacheFactory implementation that validates the + * cache with an arbitrary set of ResourceCheckers. + * + * @author Matthias Pigulla + */ +class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface +{ + /** + * @param iterable $resourceCheckers + */ + public function __construct( + private iterable $resourceCheckers = [], + ) { + } + + public function cache(string $file, callable $callable): ConfigCacheInterface + { + $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers); + if (!$cache->isFresh()) { + $callable($cache); + } + + return $cache; + } +} diff --git a/vendor/symfony/config/ResourceCheckerInterface.php b/vendor/symfony/config/ResourceCheckerInterface.php new file mode 100644 index 0000000..13ae03f --- /dev/null +++ b/vendor/symfony/config/ResourceCheckerInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Interface for ResourceCheckers. + * + * When a ResourceCheckerConfigCache instance is checked for freshness, all its associated + * metadata resources are passed to ResourceCheckers. The ResourceCheckers + * can then inspect the resources and decide whether the cache can be considered + * fresh or not. + * + * @author Matthias Pigulla + * @author Benjamin Klotz + */ +interface ResourceCheckerInterface +{ + /** + * Queries the ResourceChecker whether it can validate a given + * resource or not. + */ + public function supports(ResourceInterface $metadata): bool; + + /** + * Validates the resource. + * + * @param int $timestamp The timestamp at which the cache associated with this resource was created + */ + public function isFresh(ResourceInterface $resource, int $timestamp): bool; +} diff --git a/vendor/symfony/config/Util/Exception/InvalidXmlException.php b/vendor/symfony/config/Util/Exception/InvalidXmlException.php new file mode 100644 index 0000000..155571c --- /dev/null +++ b/vendor/symfony/config/Util/Exception/InvalidXmlException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Util\Exception; + +/** + * Exception class for when XML parsing with an XSD schema file path or a callable validator produces errors unrelated + * to the actual XML parsing. + * + * @author Ole Rößner + */ +class InvalidXmlException extends XmlParsingException +{ +} diff --git a/vendor/symfony/config/Util/Exception/XmlParsingException.php b/vendor/symfony/config/Util/Exception/XmlParsingException.php new file mode 100644 index 0000000..9bceed6 --- /dev/null +++ b/vendor/symfony/config/Util/Exception/XmlParsingException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Util\Exception; + +/** + * Exception class for when XML cannot be parsed properly. + * + * @author Ole Rößner + */ +class XmlParsingException extends \InvalidArgumentException +{ +} diff --git a/vendor/symfony/config/Util/XmlUtils.php b/vendor/symfony/config/Util/XmlUtils.php new file mode 100644 index 0000000..a7a4355 --- /dev/null +++ b/vendor/symfony/config/Util/XmlUtils.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Util; + +use Symfony\Component\Config\Util\Exception\InvalidXmlException; +use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Filesystem\Filesystem; + +/** + * XMLUtils is a bunch of utility methods to XML operations. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Fabien Potencier + * @author Martin Hasoň + * @author Ole Rößner + */ +class XmlUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Parses an XML string. + * + * @param string $content An XML string + * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation + * + * @throws XmlParsingException When parsing of XML file returns error + * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself + * @throws \RuntimeException When DOM extension is missing + */ + public static function parse(string $content, string|callable|null $schemaOrCallable = null): \DOMDocument + { + if (!\extension_loaded('dom')) { + throw new \LogicException('Extension DOM is required.'); + } + + $internalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + + $dom = new \DOMDocument(); + $dom->validateOnParse = true; + if (!$dom->loadXML($content, \LIBXML_NONET | \LIBXML_COMPACT)) { + throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors))); + } + + $dom->normalizeDocument(); + + libxml_use_internal_errors($internalErrors); + + foreach ($dom->childNodes as $child) { + if (\XML_DOCUMENT_TYPE_NODE === $child->nodeType) { + throw new XmlParsingException('Document types are not allowed.'); + } + } + + if (null !== $schemaOrCallable) { + $internalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + + $e = null; + if (\is_callable($schemaOrCallable)) { + try { + $valid = $schemaOrCallable($dom, $internalErrors); + } catch (\Exception $e) { + $valid = false; + } + } elseif (is_file($schemaOrCallable)) { + $schemaSource = (new Filesystem())->readFile((string) $schemaOrCallable); + $valid = @$dom->schemaValidateSource($schemaSource); + } else { + libxml_use_internal_errors($internalErrors); + + throw new XmlParsingException(sprintf('Invalid XSD file: "%s".', $schemaOrCallable)); + } + + if (!$valid) { + $messages = static::getXmlErrors($internalErrors); + if (!$messages) { + throw new InvalidXmlException('The XML is not valid.', 0, $e); + } + throw new XmlParsingException(implode("\n", $messages), 0, $e); + } + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $dom; + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation + * + * @throws \InvalidArgumentException When loading of XML file returns error + * @throws XmlParsingException When XML parsing returns any errors + * @throws \RuntimeException When DOM extension is missing + */ + public static function loadFile(string $file, string|callable|null $schemaOrCallable = null): \DOMDocument + { + if (!is_file($file)) { + throw new \InvalidArgumentException(sprintf('Resource "%s" is not a file.', $file)); + } + + if (!is_readable($file)) { + throw new \InvalidArgumentException(sprintf('File "%s" is not readable.', $file)); + } + + $content = (new Filesystem())->readFile($file); + + if ('' === trim($content)) { + throw new \InvalidArgumentException(sprintf('File "%s" does not contain valid XML, it is empty.', $file)); + } + + try { + return static::parse($content, $schemaOrCallable); + } catch (InvalidXmlException $e) { + throw new XmlParsingException(sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious()); + } + } + + /** + * Converts a \DOMElement object to a PHP array. + * + * The following rules applies during the conversion: + * + * * Each tag is converted to a key value or an array + * if there is more than one "value" + * + * * The content of a tag is set under a "value" key (bar) + * if the tag also has some nested tags + * + * * The attributes are converted to keys () + * + * * The nested-tags are converted to keys (bar) + * + * @param \DOMElement $element A \DOMElement instance + * @param bool $checkPrefix Check prefix in an element or an attribute name + */ + public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = true): mixed + { + $prefix = (string) $element->prefix; + $empty = true; + $config = []; + foreach ($element->attributes as $name => $node) { + if ($checkPrefix && !\in_array((string) $node->prefix, ['', $prefix], true)) { + continue; + } + $config[$name] = static::phpize($node->value); + $empty = false; + } + + $nodeValue = false; + foreach ($element->childNodes as $node) { + if ($node instanceof \DOMText) { + if ('' !== trim($node->nodeValue)) { + $nodeValue = trim($node->nodeValue); + $empty = false; + } + } elseif ($checkPrefix && $prefix != (string) $node->prefix) { + continue; + } elseif (!$node instanceof \DOMComment) { + $value = static::convertDomElementToArray($node, $checkPrefix); + + $key = $node->localName; + if (isset($config[$key])) { + if (!\is_array($config[$key]) || !\is_int(key($config[$key]))) { + $config[$key] = [$config[$key]]; + } + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + + $empty = false; + } + } + + if (false !== $nodeValue) { + $value = static::phpize($nodeValue); + if (\count($config)) { + $config['value'] = $value; + } else { + $config = $value; + } + } + + return !$empty ? $config : null; + } + + /** + * Converts an xml value to a PHP type. + */ + public static function phpize(string|\Stringable $value): mixed + { + $value = (string) $value; + $lowercaseValue = strtolower($value); + + switch (true) { + case 'null' === $lowercaseValue: + return null; + case ctype_digit($value): + case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)): + $raw = $value; + $cast = (int) $value; + + return self::isOctal($value) ? \intval($value, 8) : (($raw === (string) $cast) ? $cast : $raw); + case 'true' === $lowercaseValue: + return true; + case 'false' === $lowercaseValue: + return false; + case isset($value[1]) && '0b' == $value[0].$value[1] && preg_match('/^0b[01]*$/', $value): + return bindec($value); + case is_numeric($value): + return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value; + case preg_match('/^0x[0-9a-f]++$/i', $value): + return hexdec($value); + case preg_match('/^[+-]?[0-9]+(\.[0-9]+)?$/', $value): + return (float) $value; + default: + return $value; + } + } + + protected static function getXmlErrors(bool $internalErrors): array + { + $errors = []; + foreach (libxml_get_errors() as $error) { + $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', + \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + $error->code, + trim($error->message), + $error->file ?: 'n/a', + $error->line, + $error->column + ); + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $errors; + } + + private static function isOctal(string $str): bool + { + if ('-' === $str[0]) { + $str = substr($str, 1); + } + + return $str === '0'.decoct(\intval($str, 8)); + } +} diff --git a/vendor/symfony/config/composer.json b/vendor/symfony/config/composer.json new file mode 100644 index 0000000..3720604 --- /dev/null +++ b/vendor/symfony/config/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/config", + "type": "library", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Config\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php new file mode 100644 index 0000000..87eb7a6 --- /dev/null +++ b/vendor/symfony/console/Application.php @@ -0,0 +1,1282 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\CompleteCommand; +use Symfony\Component\Console\Command\DumpCompletionCommand; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Command\SignalableCommandInterface; +use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\NamespaceNotFoundException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\SignalRegistry\SignalRegistry; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier + */ +class Application implements ResetInterface +{ + private array $commands = []; + private bool $wantHelps = false; + private ?Command $runningCommand = null; + private ?CommandLoaderInterface $commandLoader = null; + private bool $catchExceptions = true; + private bool $catchErrors = false; + private bool $autoExit = true; + private InputDefinition $definition; + private HelperSet $helperSet; + private ?EventDispatcherInterface $dispatcher = null; + private Terminal $terminal; + private string $defaultCommand; + private bool $singleCommand = false; + private bool $initialized = false; + private ?SignalRegistry $signalRegistry = null; + private array $signalsToDispatchEvent = []; + + public function __construct( + private string $name = 'UNKNOWN', + private string $version = 'UNKNOWN', + ) { + $this->terminal = new Terminal(); + $this->defaultCommand = 'list'; + if (\defined('SIGINT') && SignalRegistry::isSupported()) { + $this->signalRegistry = new SignalRegistry(); + $this->signalsToDispatchEvent = [\SIGINT, \SIGQUIT, \SIGTERM, \SIGUSR1, \SIGUSR2]; + } + } + + /** + * @final + */ + public function setDispatcher(EventDispatcherInterface $dispatcher): void + { + $this->dispatcher = $dispatcher; + } + + public function setCommandLoader(CommandLoaderInterface $commandLoader): void + { + $this->commandLoader = $commandLoader; + } + + public function getSignalRegistry(): SignalRegistry + { + if (!$this->signalRegistry) { + throw new RuntimeException('Signals are not supported. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); + } + + return $this->signalRegistry; + } + + public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void + { + $this->signalsToDispatchEvent = $signalsToDispatchEvent; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + * + * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. + */ + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int + { + if (\function_exists('putenv')) { + @putenv('LINES='.$this->terminal->getHeight()); + @putenv('COLUMNS='.$this->terminal->getWidth()); + } + + $input ??= new ArgvInput(); + $output ??= new ConsoleOutput(); + + $renderException = function (\Throwable $e) use ($output) { + if ($output instanceof ConsoleOutputInterface) { + $this->renderThrowable($e, $output->getErrorOutput()); + } else { + $this->renderThrowable($e, $output); + } + }; + if ($phpHandler = set_exception_handler($renderException)) { + restore_exception_handler(); + if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { + $errorHandler = true; + } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) { + $phpHandler[0]->setExceptionHandler($errorHandler); + } + } + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Throwable $e) { + if ($e instanceof \Exception && !$this->catchExceptions) { + throw $e; + } + if (!$e instanceof \Exception && !$this->catchErrors) { + throw $e; + } + + $renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if ($exitCode <= 0) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } finally { + // if the exception handler changed, keep it + // otherwise, unregister $renderException + if (!$phpHandler) { + if (set_exception_handler($renderException) === $renderException) { + restore_exception_handler(); + } + restore_exception_handler(); + } elseif (!$errorHandler) { + $finalHandler = $phpHandler[0]->setExceptionHandler(null); + if ($finalHandler !== $renderException) { + $phpHandler[0]->setExceptionHandler($finalHandler); + } + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output): int + { + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + try { + // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. + $input->bind($this->getDefinition()); + } catch (ExceptionInterface) { + // Errors must be ignored, full binding/validation happens later when the command is known. + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(['--help', '-h'], true)) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(['command_name' => $this->defaultCommand]); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), + [ + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), + ] + )); + } + + try { + $this->runningCommand = null; + // the command name MUST be the first element of the input + $command = $this->find($name); + } catch (\Throwable $e) { + if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) { + $alternative = $alternatives[0]; + + $style = new SymfonyStyle($input, $output); + $output->writeln(''); + $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); + $output->writeln($formattedBlock); + if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + return $event->getExitCode(); + } + + return 1; + } + + $command = $this->find($alternative); + } else { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + if (0 === $event->getExitCode()) { + return 0; + } + + $e = $event->getError(); + } + + try { + if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) { + $helper = new DescriptorHelper(); + $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [ + 'format' => 'txt', + 'raw_text' => false, + 'namespace' => $namespace, + 'short' => false, + ]); + + return isset($event) ? $event->getExitCode() : 1; + } + + throw $e; + } catch (NamespaceNotFoundException) { + throw $e; + } + } + } + + if ($command instanceof LazyCommand) { + $command = $command->getCommand(); + } + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + public function reset(): void + { + } + + public function setHelperSet(HelperSet $helperSet): void + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + */ + public function getHelperSet(): HelperSet + { + return $this->helperSet ??= $this->getDefaultHelperSet(); + } + + public function setDefinition(InputDefinition $definition): void + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + */ + public function getDefinition(): InputDefinition + { + $this->definition ??= $this->getDefaultInputDefinition(); + + if ($this->singleCommand) { + $inputDefinition = $this->definition; + $inputDefinition->setArguments(); + + return $inputDefinition; + } + + return $this->definition; + } + + /** + * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ( + CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() + && 'command' === $input->getCompletionName() + ) { + foreach ($this->all() as $name => $command) { + // skip hidden commands and aliased commands as they already get added below + if ($command->isHidden() || $command->getName() !== $name) { + continue; + } + $suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription())); + foreach ($command->getAliases() as $name) { + $suggestions->suggestValue(new Suggestion($name, $command->getDescription())); + } + } + + return; + } + + if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) { + $suggestions->suggestOptions($this->getDefinition()->getOptions()); + + return; + } + } + + /** + * Gets the help message. + */ + public function getHelp(): string + { + return $this->getLongVersion(); + } + + /** + * Gets whether to catch exceptions or not during commands execution. + */ + public function areExceptionsCaught(): bool + { + return $this->catchExceptions; + } + + /** + * Sets whether to catch exceptions or not during commands execution. + */ + public function setCatchExceptions(bool $boolean): void + { + $this->catchExceptions = $boolean; + } + + /** + * Sets whether to catch errors or not during commands execution. + */ + public function setCatchErrors(bool $catchErrors = true): void + { + $this->catchErrors = $catchErrors; + } + + /** + * Gets whether to automatically exit after a command execution or not. + */ + public function isAutoExitEnabled(): bool + { + return $this->autoExit; + } + + /** + * Sets whether to automatically exit after a command execution or not. + */ + public function setAutoExit(bool $boolean): void + { + $this->autoExit = $boolean; + } + + /** + * Gets the name of the application. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Sets the application name. + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * Gets the application version. + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * Sets the application version. + */ + public function setVersion(string $version): void + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + */ + public function getLongVersion(): string + { + if ('UNKNOWN' !== $this->getName()) { + if ('UNKNOWN' !== $this->getVersion()) { + return sprintf('%s %s', $this->getName(), $this->getVersion()); + } + + return $this->getName(); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + */ + public function register(string $name): Command + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * If a Command is not enabled it will not be added. + * + * @param Command[] $commands An array of commands + */ + public function addCommands(array $commands): void + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * If the command is not enabled it will not be added. + */ + public function add(Command $command): ?Command + { + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return null; + } + + if (!$command instanceof LazyCommand) { + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + } + + if (!$command->getName()) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @throws CommandNotFoundException When given command name does not exist + */ + public function get(string $name): Command + { + $this->init(); + + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + // When the command has a different name than the one used at the command loader level + if (!isset($this->commands[$name])) { + throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + */ + public function has(string $name): bool + { + $this->init(); + + return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name))); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not return the global namespace which always exists. + * + * @return string[] + */ + public function getNamespaces(): array + { + $namespaces = []; + foreach ($this->all() as $command) { + if ($command->isHidden()) { + continue; + } + + $namespaces[] = $this->extractAllNamespaces($command->getName()); + + foreach ($command->getAliases() as $alias) { + $namespaces[] = $this->extractAllNamespaces($alias); + } + } + + return array_values(array_unique(array_filter(array_merge([], ...$namespaces)))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous + */ + public function findNamespace(string $namespace): string + { + $allNamespaces = $this->getNamespaces(); + $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*'; + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (!$namespaces) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new NamespaceNotFoundException($message, $alternatives); + } + + $exact = \in_array($namespace, $namespaces, true); + if (\count($namespaces) > 1 && !$exact) { + throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ + public function find(string $name): Command + { + $this->init(); + + $aliases = []; + + foreach ($this->commands as $command) { + foreach ($command->getAliases() as $alias) { + if (!$this->has($alias)) { + $this->commands[$alias] = $command; + } + } + } + + if ($this->has($name)) { + return $this->get($name); + } + + $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands); + $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*'; + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (!$commands) { + $commands = preg_grep('{^'.$expr.'}i', $allCommands); + } + + // if no commands matched or we just matched namespaces + if (!$commands || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + // remove hidden commands + $alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden()); + + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, array_values($alternatives)); + } + + // filter out aliases for commands which are already on the list + if (\count($commands) > 1) { + $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands; + $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) { + if (!$commandList[$nameOrAlias] instanceof Command) { + $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias); + } + + $commandName = $commandList[$nameOrAlias]->getName(); + + $aliases[$nameOrAlias] = $commandName; + + return $commandName === $nameOrAlias || !\in_array($commandName, $commands, true); + })); + } + + if (\count($commands) > 1) { + $usableWidth = $this->terminal->getWidth() - 10; + $abbrevs = array_values($commands); + $maxLen = 0; + foreach ($abbrevs as $abbrev) { + $maxLen = max(Helper::width($abbrev), $maxLen); + } + $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) { + if ($commandList[$cmd]->isHidden()) { + unset($commands[array_search($cmd, $commands)]); + + return false; + } + + $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); + + return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; + }, array_values($commands)); + + if (\count($commands) > 1) { + $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs)); + + throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands)); + } + } + + $command = $this->get(reset($commands)); + + if ($command->isHidden()) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + return $command; + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @return Command[] + */ + public function all(?string $namespace = null): array + { + $this->init(); + + if (null === $namespace) { + if (!$this->commandLoader) { + return $this->commands; + } + + $commands = $this->commands; + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + + return $commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + if ($this->commandLoader) { + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @return string[][] + */ + public static function getAbbreviations(array $names): array + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = \strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + public function renderThrowable(\Throwable $e, OutputInterface $output): void + { + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + + $this->doRenderThrowable($e, $output); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET); + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } + + protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void + { + do { + $message = trim($e->getMessage()); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $class = get_debug_type($e); + $title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''); + $len = Helper::width($title); + } else { + $len = 0; + } + + if (str_contains($message, "@anonymous\0")) { + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); + } + + $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; + $lines = []; + foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = Helper::width($line) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = []; + if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('%s', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); + } + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::width($title)))); + } + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); + + // exception related properties + $trace = $e->getTrace(); + + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() ?: 'n/a', + 'line' => $e->getLine() ?: 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = \count($trace); $i < $count; ++$i) { + $class = $trace[$i]['class'] ?? ''; + $type = $trace[$i]['type'] ?? ''; + $function = $trace[$i]['function'] ?? ''; + $file = $trace[$i]['file'] ?? 'n/a'; + $line = $trace[$i]['line'] ?? 'n/a'; + + $output->writeln(sprintf(' %s%s at %s:%s', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); + } + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } while ($e = $e->getPrevious()); + } + + /** + * Configures the input and output instances based on the user arguments and options. + */ + protected function configureIO(InputInterface $input, OutputInterface $output): void + { + if (true === $input->hasParameterOption(['--ansi'], true)) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) { + $input->setInteractive(false); + } + + switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { + case -1: + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + break; + case 1: + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + break; + case 2: + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + break; + case 3: + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + break; + default: + $shellVerbosity = 0; + break; + } + + if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $shellVerbosity = -1; + } else { + if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + $shellVerbosity = 3; + } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + $shellVerbosity = 2; + } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + $shellVerbosity = 1; + } + } + + if (-1 === $shellVerbosity) { + $input->setInteractive(false); + } + + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$shellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @return int 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; + if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) { + if (!$this->signalRegistry) { + throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); + } + + if (Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + foreach ([\SIGINT, \SIGQUIT, \SIGTERM] as $signal) { + $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode)); + } + } + + if ($this->dispatcher) { + // We register application signals, so that we can dispatch the event + foreach ($this->signalsToDispatchEvent as $signal) { + $event = new ConsoleSignalEvent($command, $input, $output, $signal); + + $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) { + $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); + $exitCode = $event->getExitCode(); + + // If the command is signalable, we call the handleSignal() method + if (\in_array($signal, $commandSignals, true)) { + $exitCode = $command->handleSignal($signal, $exitCode); + } + + if (false !== $exitCode) { + $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + exit($event->getExitCode()); + } + }); + } + + // then we register command signals, but not if already handled after the dispatcher + $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent); + } + + foreach ($commandSignals as $signal) { + $this->signalRegistry->register($signal, function (int $signal) use ($command): void { + if (false !== $exitCode = $command->handleSignal($signal)) { + exit($exitCode); + } + }); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + // bind before the console.command event, so the listeners have access to input options/arguments + try { + $command->mergeApplicationDefinition(); + $input->bind($command->getDefinition()); + } catch (ExceptionInterface) { + // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $e = null; + + try { + $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); + + if ($event->commandShouldRun()) { + $exitCode = $command->run($input, $output); + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + } catch (\Throwable $e) { + $event = new ConsoleErrorEvent($input, $output, $e, $command); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + $e = $event->getError(); + + if (0 === $exitCode = $event->getExitCode()) { + $e = null; + } + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + if (null !== $e) { + throw $e; + } + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + */ + protected function getCommandName(InputInterface $input): ?string + { + return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + */ + protected function getDefaultInputDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the '.$this->defaultCommand.' command'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), + new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] + */ + protected function getDefaultCommands(): array + { + return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()]; + } + + /** + * Gets the default helper set with the helpers that should always be available. + */ + protected function getDefaultHelperSet(): HelperSet + { + return new HelperSet([ + new FormatterHelper(), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + ]); + } + + /** + * Returns abbreviated suggestions in string format. + */ + private function getAbbreviationSuggestions(array $abbrevs): string + { + return ' '.implode("\n ", $abbrevs); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + */ + public function extractNamespace(string $name, ?int $limit = null): string + { + $parts = explode(':', $name, -1); + + return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @return string[] + */ + private function findAlternatives(string $name, iterable $collection): array + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @return $this + */ + public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static + { + $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0]; + + if ($isSingleCommand) { + // Ensure the command exist + $this->find($commandName); + + $this->singleCommand = true; + } + + return $this; + } + + /** + * @internal + */ + public function isSingleCommand(): bool + { + return $this->singleCommand; + } + + private function splitStringByWidth(string $string, int $width): array + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + + $offset = 0; + while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) { + $offset += \strlen($m[0]); + + foreach (preg_split('//u', $m[0]) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + } + + $lines[] = \count($lines) ? str_pad($line, $width) : $line; + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @return string[] + */ + private function extractAllNamespaces(string $name): array + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (\count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + private function init(): void + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } +} diff --git a/vendor/symfony/console/Attribute/AsCommand.php b/vendor/symfony/console/Attribute/AsCommand.php new file mode 100644 index 0000000..6066d7c --- /dev/null +++ b/vendor/symfony/console/Attribute/AsCommand.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Attribute; + +/** + * Service tag to autoconfigure commands. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsCommand +{ + /** + * @param string $name The name of the command, used when calling it (i.e. "cache:clear") + * @param string|null $description The description of the command, displayed with the help page + * @param string[] $aliases The list of aliases of the command. The command will be executed when using one of them (i.e. "cache:clean") + * @param bool $hidden If true, the command won't be shown when listing all the available commands, but it can still be run as any other command + */ + public function __construct( + public string $name, + public ?string $description = null, + array $aliases = [], + bool $hidden = false, + ) { + if (!$hidden && !$aliases) { + return; + } + + $name = explode('|', $name); + $name = array_merge($name, $aliases); + + if ($hidden && '' !== $name[0]) { + array_unshift($name, ''); + } + + $this->name = implode('|', $name); + } +} diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md new file mode 100644 index 0000000..25d7f71 --- /dev/null +++ b/vendor/symfony/console/CHANGELOG.md @@ -0,0 +1,274 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `ArgvInput::getRawTokens()` + +7.0 +--- + + * Add method `__toString()` to `InputInterface` + * Remove `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead + * Require explicit argument when calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()` and `Question::setAutocompleterCallback/setValidator()` + * Remove `StringInput::REGEX_STRING` + +6.4 +--- + + * Add `SignalMap` to map signal value to its name + * Multi-line text in vertical tables is aligned properly + * The application can also catch errors with `Application::setCatchErrors(true)` + * Add `RunCommandMessage` and `RunCommandMessageHandler` + * Dispatch `ConsoleTerminateEvent` after an exit on signal handling and add `ConsoleTerminateEvent::getInterruptingSignal()` + +6.3 +--- + + * Add support for choosing exit code while handling signal, or to not exit at all + * Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global. + * Add `ReStructuredTextDescriptor` + +6.2 +--- + + * Improve truecolor terminal detection in some cases + * Add support for 256 color terminals (conversion from Ansi24 to Ansi8 if terminal is capable of it) + * Deprecate calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()`, `Question::setAutocompleterCallback/setValidator()`without any arguments + * Change the signature of `OutputFormatterStyleInterface::setForeground/setBackground()` to `setForeground/setBackground(?string)` + * Change the signature of `HelperInterface::setHelperSet()` to `setHelperSet(?HelperSet)` + +6.1 +--- + + * Add support to display table vertically when calling setVertical() + * Add method `__toString()` to `InputInterface` + * Added `OutputWrapper` to prevent truncated URL in `SymfonyStyle::createBlock`. + * Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead + * Add suggested values for arguments and options in input definition, for input completion + * Add `$resumeAt` parameter to `ProgressBar#start()`, so that one can easily 'resume' progress on longer tasks, and still get accurate `getEstimate()` and `getRemaining()` results. + +6.0 +--- + + * `Command::setHidden()` has a default value (`true`) for `$hidden` parameter and is final + * Remove `Helper::strlen()`, use `Helper::width()` instead + * Remove `Helper::strlenWithoutDecoration()`, use `Helper::removeDecoration()` instead + * `AddConsoleCommandPass` can not be configured anymore + * Remove `HelperSet::setCommand()` and `getCommand()` without replacement + +5.4 +--- + + * Add `TesterTrait::assertCommandIsSuccessful()` to test command + * Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement + +5.3 +--- + + * Add `GithubActionReporter` to render annotations in a Github Action + * Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options + * Add the `Command::$defaultDescription` static property and the `description` attribute + on the `console.command` tag to allow the `list` command to instantiate commands lazily + * Add option `--short` to the `list` command + * Add support for bright colors + * Add `#[AsCommand]` attribute for declaring commands on PHP 8 + * Add `Helper::width()` and `Helper::length()` + * The `--ansi` and `--no-ansi` options now default to `null`. + +5.2.0 +----- + + * Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester` + * added support for multiline responses to questions through `Question::setMultiline()` + and `Question::isMultiline()` + * Added `SignalRegistry` class to stack signals handlers + * Added support for signals: + * Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods + * Added `SignalableCommandInterface` interface + * Added `TableCellStyle` class to customize table cell + * Removed `php ` prefix invocation from help messages. + +5.1.0 +----- + + * `Command::setHidden()` is final since Symfony 5.1 + * Add `SingleCommandApplication` + * Add `Cursor` class + +5.0.0 +----- + + * removed support for finding hidden commands using an abbreviation, use the full name instead + * removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()` + * removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()` + * removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()` + * removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()` + * removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()` + * removed support for returning `null` from `Command::execute()`, return `0` instead + * `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument + * `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` + for its `dispatcher` argument + * renamed `Application::renderException()` and `Application::doRenderException()` + to `renderThrowable()` and `doRenderThrowable()` respectively. + +4.4.0 +----- + + * deprecated finding hidden commands using an abbreviation, use the full name instead + * added `Question::setTrimmable` default to true to allow the answer to be trimmed + * added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar` + * `Application` implements `ResetInterface` + * marked all dispatched event classes as `@final` + * added support for displaying table horizontally + * deprecated returning `null` from `Command::execute()`, return `0` instead + * Deprecated the `Application::renderException()` and `Application::doRenderException()` methods, + use `renderThrowable()` and `doRenderThrowable()` instead. + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added support for hyperlinks + * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating + * added `Question::setAutocompleterCallback()` to provide a callback function + that dynamically generates suggestions as the user types + +4.2.0 +----- + + * allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to + `ProcessHelper::run()` to pass environment variables + * deprecated passing a command as a string to `ProcessHelper::run()`, + pass it the command as an array of its arguments instead + * made the `ProcessHelper` class final + * added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`) + * added `capture_stderr_separately` option to `CommandTester::execute()` + +4.1.0 +----- + + * added option to run suggested command if command is not found and only 1 alternative is available + * added option to modify console output and print multiple modifiable sections + * added support for iterable messages in output `write` and `writeln` methods + +4.0.0 +----- + + * `OutputFormatter` throws an exception when unknown options are used + * removed `QuestionHelper::setInputStream()/getInputStream()` + * removed `Application::getTerminalWidth()/getTerminalHeight()` and + `Application::setTerminalDimensions()/getTerminalDimensions()` + * removed `ConsoleExceptionEvent` + * removed `ConsoleEvents::EXCEPTION` + +3.4.0 +----- + + * added `SHELL_VERBOSITY` env var to control verbosity + * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11 + `ContainerCommandLoader` for commands lazy-loading + * added a case-insensitive command name matching fallback + * added static `Command::$defaultName/getDefaultName()`, allowing for + commands to be registered at compile time in the application command loader. + Setting the `$defaultName` property avoids the need for filling the `command` + attribute on the `console.command` tag when using `AddConsoleCommandPass`. + +3.3.0 +----- + + * added `ExceptionListener` + * added `AddConsoleCommandPass` (originally in FrameworkBundle) + * [BC BREAK] `Input::getOption()` no longer returns the default value for options + with value optional explicitly passed empty + * added console.error event to catch exceptions thrown by other listeners + * deprecated console.exception event in favor of console.error + * added ability to handle `CommandNotFoundException` through the + `console.error` event + * deprecated default validation in `SymfonyQuestionHelper::ask` + +3.2.0 +------ + + * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs + * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) + * added StreamableInputInterface + * added LockableTrait + +3.1.0 +----- + + * added truncate method to FormatterHelper + * added setColumnWidth(s) method to Table + +2.8.3 +----- + + * remove readline support from the question helper as it caused issues + +2.8.0 +----- + + * use readline for user input in the question helper when available to allow + the use of arrow keys + +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + +2.5.0 +----- + + * deprecated the dialog helper (use the question helper instead) + * deprecated TableHelper in favor of Table + * deprecated ProgressHelper in favor of ProgressBar + * added ConsoleLogger + * added a question helper + * added a way to set the process name of a command + * added a way to set a default command instead of `ListCommand` + +2.4.0 +----- + + * added a way to force terminal dimensions + * added a convenient method to detect verbosity level + * [BC BREAK] made descriptors use output instead of returning a string + +2.3.0 +----- + + * added multiselect support to the select dialog helper + * added Table Helper for tabular data rendering + * added support for events in `Application` + * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` + * added a way to set the progress bar progress via the `setCurrent` method + * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` + * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG + +2.2.0 +----- + + * added support for colorization on Windows via ConEmu + * add a method to Dialog Helper to ask for a question and hide the response + * added support for interactive selections in console (DialogHelper::select()) + * added support for autocompletion as you type in Dialog Helper + +2.1.0 +----- + + * added ConsoleOutputInterface + * added the possibility to disable a command (Command::isEnabled()) + * added suggestions when a command does not exist + * added a --raw option to the list command + * added support for STDERR in the console output class (errors are now sent + to STDERR) + * made the defaults (helper set, commands, input definition) in Application + more easily customizable + * added support for the shell even if readline is not available + * added support for process isolation in Symfony shell via + `--process-isolation` switch + * added support for `--`, which disables options parsing after that point + (tokens will be parsed as arguments) diff --git a/vendor/symfony/console/CI/GithubActionReporter.php b/vendor/symfony/console/CI/GithubActionReporter.php new file mode 100644 index 0000000..2cae6fd --- /dev/null +++ b/vendor/symfony/console/CI/GithubActionReporter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CI; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Utility class for Github actions. + * + * @author Maxime Steinhausser + */ +class GithubActionReporter +{ + private OutputInterface $output; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 + */ + private const ESCAPED_DATA = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ]; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94 + */ + private const ESCAPED_PROPERTIES = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ':' => '%3A', + ',' => '%2C', + ]; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + public static function isGithubActionEnvironment(): bool + { + return false !== getenv('GITHUB_ACTIONS'); + } + + /** + * Output an error using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + */ + public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + $this->log('error', $message, $file, $line, $col); + } + + /** + * Output a warning using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message + */ + public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + $this->log('warning', $message, $file, $line, $col); + } + + /** + * Output a debug log using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message + */ + public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + $this->log('debug', $message, $file, $line, $col); + } + + private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + // Some values must be encoded. + $message = strtr($message, self::ESCAPED_DATA); + + if (!$file) { + // No file provided, output the message solely: + $this->output->writeln(sprintf('::%s::%s', $type, $message)); + + return; + } + + $this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message)); + } +} diff --git a/vendor/symfony/console/Color.php b/vendor/symfony/console/Color.php new file mode 100644 index 0000000..60ed046 --- /dev/null +++ b/vendor/symfony/console/Color.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Fabien Potencier + */ +final class Color +{ + private const COLORS = [ + 'black' => 0, + 'red' => 1, + 'green' => 2, + 'yellow' => 3, + 'blue' => 4, + 'magenta' => 5, + 'cyan' => 6, + 'white' => 7, + 'default' => 9, + ]; + + private const BRIGHT_COLORS = [ + 'gray' => 0, + 'bright-red' => 1, + 'bright-green' => 2, + 'bright-yellow' => 3, + 'bright-blue' => 4, + 'bright-magenta' => 5, + 'bright-cyan' => 6, + 'bright-white' => 7, + ]; + + private const AVAILABLE_OPTIONS = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private string $foreground; + private string $background; + private array $options = []; + + public function __construct(string $foreground = '', string $background = '', array $options = []) + { + $this->foreground = $this->parseColor($foreground); + $this->background = $this->parseColor($background, true); + + foreach ($options as $option) { + if (!isset(self::AVAILABLE_OPTIONS[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS)))); + } + + $this->options[$option] = self::AVAILABLE_OPTIONS[$option]; + } + } + + public function apply(string $text): string + { + return $this->set().$text.$this->unset(); + } + + public function set(): string + { + $setCodes = []; + if ('' !== $this->foreground) { + $setCodes[] = $this->foreground; + } + if ('' !== $this->background) { + $setCodes[] = $this->background; + } + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + } + if (0 === \count($setCodes)) { + return ''; + } + + return sprintf("\033[%sm", implode(';', $setCodes)); + } + + public function unset(): string + { + $unsetCodes = []; + if ('' !== $this->foreground) { + $unsetCodes[] = 39; + } + if ('' !== $this->background) { + $unsetCodes[] = 49; + } + foreach ($this->options as $option) { + $unsetCodes[] = $option['unset']; + } + if (0 === \count($unsetCodes)) { + return ''; + } + + return sprintf("\033[%sm", implode(';', $unsetCodes)); + } + + private function parseColor(string $color, bool $background = false): string + { + if ('' === $color) { + return ''; + } + + if ('#' === $color[0]) { + return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color); + } + + if (isset(self::COLORS[$color])) { + return ($background ? '4' : '3').self::COLORS[$color]; + } + + if (isset(self::BRIGHT_COLORS[$color])) { + return ($background ? '10' : '9').self::BRIGHT_COLORS[$color]; + } + + throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS))))); + } +} diff --git a/vendor/symfony/console/Command/Command.php b/vendor/symfony/console/Command/Command.php new file mode 100644 index 0000000..03da6db --- /dev/null +++ b/vendor/symfony/console/Command/Command.php @@ -0,0 +1,664 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + */ +class Command +{ + // see https://tldp.org/LDP/abs/html/exitcodes.html + public const SUCCESS = 0; + public const FAILURE = 1; + public const INVALID = 2; + + private ?Application $application = null; + private ?string $name = null; + private ?string $processTitle = null; + private array $aliases = []; + private InputDefinition $definition; + private bool $hidden = false; + private string $help = ''; + private string $description = ''; + private ?InputDefinition $fullDefinition = null; + private bool $ignoreValidationErrors = false; + private ?\Closure $code = null; + private array $synopsis = []; + private array $usages = []; + private ?HelperSet $helperSet = null; + + public static function getDefaultName(): ?string + { + if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) { + return $attribute[0]->newInstance()->name; + } + + return null; + } + + public static function getDefaultDescription(): ?string + { + if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) { + return $attribute[0]->newInstance()->description; + } + + return null; + } + + /** + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws LogicException When the command name is empty + */ + public function __construct(?string $name = null) + { + $this->definition = new InputDefinition(); + + if (null === $name && null !== $name = static::getDefaultName()) { + $aliases = explode('|', $name); + + if ('' === $name = array_shift($aliases)) { + $this->setHidden(true); + $name = array_shift($aliases); + } + + $this->setAliases($aliases); + } + + if (null !== $name) { + $this->setName($name); + } + + if ('' === $this->description) { + $this->setDescription(static::getDefaultDescription() ?? ''); + } + + $this->configure(); + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors(): void + { + $this->ignoreValidationErrors = true; + } + + public function setApplication(?Application $application): void + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + + $this->fullDefinition = null; + } + + public function setHelperSet(HelperSet $helperSet): void + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + */ + public function getHelperSet(): ?HelperSet + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + */ + public function getApplication(): ?Application + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment. + * + * Override this to check for x or y and return false if the command cannot + * run properly under the current conditions. + */ + public function isEnabled(): bool + { + return true; + } + + /** + * Configures the current command. + * + * @return void + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + throw new LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + * + * @return void + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command after the input has been bound and before the input + * is validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + * + * @see InputInterface::bind() + * @see InputInterface::validate() + * + * @return void + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @return int The command exit code + * + * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output): int + { + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (\function_exists('cli_set_process_title')) { + if (!@cli_set_process_title($this->processTitle)) { + if ('Darwin' === \PHP_OS) { + $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); + } else { + cli_set_process_title($this->processTitle); + } + } + } elseif (\function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + // The command name argument is often omitted when a command is executed directly with its run() method. + // It would fail the validation if we didn't make sure the command argument is present, + // since it's required by the application. + if ($input->hasArgument('command') && null === $input->getArgument('command')) { + $input->setArgument('command', $this->getName()); + } + + $input->validate(); + + if ($this->code) { + $statusCode = ($this->code)($input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Supplies suggestions when resolving possible completion options for input (e.g. option or argument). + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $definition = $this->getDefinition(); + if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) { + $definition->getOption($input->getCompletionName())->complete($input, $suggestions); + } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) { + $definition->getArgument($input->getCompletionName())->complete($input, $suggestions); + } + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return $this + * + * @throws InvalidArgumentException + * + * @see execute() + */ + public function setCode(callable $code): static + { + if ($code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + set_error_handler(static function () {}); + try { + if ($c = \Closure::bind($code, $this)) { + $code = $c; + } + } finally { + restore_error_handler(); + } + } + } else { + $code = $code(...); + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + * + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + if (null === $this->application) { + return; + } + + $this->fullDefinition = new InputDefinition(); + $this->fullDefinition->setOptions($this->definition->getOptions()); + $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions()); + + if ($mergeArgs) { + $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments()); + $this->fullDefinition->addArguments($this->definition->getArguments()); + } else { + $this->fullDefinition->setArguments($this->definition->getArguments()); + } + } + + /** + * Sets an array of argument and option instances. + * + * @return $this + */ + public function setDefinition(array|InputDefinition $definition): static + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->fullDefinition = null; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + */ + public function getDefinition(): InputDefinition + { + return $this->fullDefinition ?? $this->getNativeDefinition(); + } + + /** + * Gets the InputDefinition to be used to create representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + */ + public function getNativeDefinition(): InputDefinition + { + return $this->definition ?? throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); + } + + /** + * Adds an argument. + * + * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param $default The default value (for InputArgument::OPTIONAL mode only) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @return $this + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); + $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); + + return $this; + } + + /** + * Adds an option. + * + * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param $mode The option mode: One of the InputOption::VALUE_* constants + * @param $default The default value (must be null for InputOption::VALUE_NONE) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @return $this + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); + $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @return $this + * + * @throws InvalidArgumentException When the name is invalid + */ + public function setName(string $name): static + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * @return $this + */ + public function setProcessTitle(string $title): static + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param bool $hidden Whether or not the command should be hidden from the list of commands + * + * @return $this + */ + public function setHidden(bool $hidden = true): static + { + $this->hidden = $hidden; + + return $this; + } + + /** + * @return bool whether the command should be publicly shown or not + */ + public function isHidden(): bool + { + return $this->hidden; + } + + /** + * Sets the description for the command. + * + * @return $this + */ + public function setDescription(string $description): static + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @return $this + */ + public function setHelp(string $help): static + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + */ + public function getHelp(): string + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + */ + public function getProcessedHelp(): string + { + $name = $this->name; + $isSingleCommand = $this->application?->isSingleCommand(); + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); + } + + /** + * Sets the aliases for the command. + * + * @param string[] $aliases An array of aliases for the command + * + * @return $this + * + * @throws InvalidArgumentException When an alias is invalid + */ + public function setAliases(iterable $aliases): static + { + $list = []; + + foreach ($aliases as $alias) { + $this->validateName($alias); + $list[] = $alias; + } + + $this->aliases = \is_array($aliases) ? $aliases : $list; + + return $this; + } + + /** + * Returns the aliases for the command. + */ + public function getAliases(): array + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + */ + public function getSynopsis(bool $short = false): string + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example, it'll be prefixed with the command name. + * + * @return $this + */ + public function addUsage(string $usage): static + { + if (!str_starts_with($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + */ + public function getUsages(): array + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @throws LogicException if no HelperSet is defined + * @throws InvalidArgumentException if the helper is not defined + */ + public function getHelper(string $name): HelperInterface + { + if (null === $this->helperSet) { + throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); + } + + return $this->helperSet->get($name); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @throws InvalidArgumentException When the name is invalid + */ + private function validateName(string $name): void + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Command/CompleteCommand.php b/vendor/symfony/console/Command/CompleteCommand.php new file mode 100644 index 0000000..38aa737 --- /dev/null +++ b/vendor/symfony/console/Command/CompleteCommand.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Output\BashCompletionOutput; +use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; +use Symfony\Component\Console\Completion\Output\FishCompletionOutput; +use Symfony\Component\Console\Completion\Output\ZshCompletionOutput; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Responsible for providing the values to the shell completion. + * + * @author Wouter de Jong + */ +#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')] +final class CompleteCommand extends Command +{ + public const COMPLETION_API_VERSION = '1'; + + private array $completionOutputs; + private bool $isDebug = false; + + /** + * @param array> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value + */ + public function __construct(array $completionOutputs = []) + { + // must be set before the parent constructor, as the property value is used in configure() + $this->completionOutputs = $completionOutputs + [ + 'bash' => BashCompletionOutput::class, + 'fish' => FishCompletionOutput::class, + 'zsh' => ZshCompletionOutput::class, + ]; + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")') + ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)') + ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)') + ->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script') + ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated') + ; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + // "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1 + $version = $input->getOption('symfony') ? '1' : $input->getOption('api-version'); + if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) { + $message = sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION); + $this->log($message); + + $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.'); + + return 126; + } + + $shell = $input->getOption('shell'); + if (!$shell) { + throw new \RuntimeException('The "--shell" option must be set.'); + } + + if (!$completionOutput = $this->completionOutputs[$shell] ?? false) { + throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs)))); + } + + $completionInput = $this->createCompletionInput($input); + $suggestions = new CompletionSuggestions(); + + $this->log([ + '', + ''.date('Y-m-d H:i:s').'', + 'Input: ("|" indicates the cursor position)', + ' '.(string) $completionInput, + 'Command:', + ' '.(string) implode(' ', $_SERVER['argv']), + 'Messages:', + ]); + + $command = $this->findCommand($completionInput, $output); + if (null === $command) { + $this->log(' No command found, completing using the Application class.'); + + $this->getApplication()->complete($completionInput, $suggestions); + } elseif ( + $completionInput->mustSuggestArgumentValuesFor('command') + && $command->getName() !== $completionInput->getCompletionValue() + && !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true) + ) { + $this->log(' No command found, completing using the Application class.'); + + // expand shortcut names ("cache:cl") into their full name ("cache:clear") + $suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases()))); + } else { + $command->mergeApplicationDefinition(); + $completionInput->bind($command->getDefinition()); + + if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { + $this->log(' Completing option names for the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' command.'); + + $suggestions->suggestOptions($command->getDefinition()->getOptions()); + } else { + $this->log([ + ' Completing using the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' class.', + ' Completing '.$completionInput->getCompletionType().' for '.$completionInput->getCompletionName().'', + ]); + if (null !== $compval = $completionInput->getCompletionValue()) { + $this->log(' Current value: '.$compval.''); + } + + $command->complete($completionInput, $suggestions); + } + } + + /** @var CompletionOutputInterface $completionOutput */ + $completionOutput = new $completionOutput(); + + $this->log('Suggestions:'); + if ($options = $suggestions->getOptionSuggestions()) { + $this->log(' --'.implode(' --', array_map(fn ($o) => $o->getName(), $options))); + } elseif ($values = $suggestions->getValueSuggestions()) { + $this->log(' '.implode(' ', $values)); + } else { + $this->log(' No suggestions were provided'); + } + + $completionOutput->write($suggestions, $output); + } catch (\Throwable $e) { + $this->log([ + 'Error!', + (string) $e, + ]); + + if ($output->isDebug()) { + throw $e; + } + + return 2; + } + + return 0; + } + + private function createCompletionInput(InputInterface $input): CompletionInput + { + $currentIndex = $input->getOption('current'); + if (!$currentIndex || !ctype_digit($currentIndex)) { + throw new \RuntimeException('The "--current" option must be set and it must be an integer.'); + } + + $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex); + + try { + $completionInput->bind($this->getApplication()->getDefinition()); + } catch (ExceptionInterface) { + } + + return $completionInput; + } + + private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command + { + try { + $inputName = $completionInput->getFirstArgument(); + if (null === $inputName) { + return null; + } + + return $this->getApplication()->find($inputName); + } catch (CommandNotFoundException) { + } + + return null; + } + + private function log($messages): void + { + if (!$this->isDebug) { + return; + } + + $commandName = basename($_SERVER['argv'][0]); + file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND); + } +} diff --git a/vendor/symfony/console/Command/DumpCompletionCommand.php b/vendor/symfony/console/Command/DumpCompletionCommand.php new file mode 100644 index 0000000..be6f545 --- /dev/null +++ b/vendor/symfony/console/Command/DumpCompletionCommand.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; + +/** + * Dumps the completion script for the current shell. + * + * @author Wouter de Jong + */ +#[AsCommand(name: 'completion', description: 'Dump the shell completion script')] +final class DumpCompletionCommand extends Command +{ + private array $supportedShells; + + protected function configure(): void + { + $fullCommand = $_SERVER['PHP_SELF']; + $commandName = basename($fullCommand); + $fullCommand = @realpath($fullCommand) ?: $fullCommand; + + $shell = $this->guessShell(); + [$rcFile, $completionFile] = match ($shell) { + 'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"], + 'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName], + default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"], + }; + + $supportedShells = implode(', ', $this->getSupportedShells()); + + $this + ->setHelp(<<%command.name% command dumps the shell completion script required +to use shell autocompletion (currently, {$supportedShells} completion are supported). + +Static installation +------------------- + +Dump the script to a global completion file and restart your shell: + + %command.full_name% {$shell} | sudo tee {$completionFile} + +Or dump the script to a local file and source it: + + %command.full_name% {$shell} > completion.sh + + # source the file whenever you use the project + source completion.sh + + # or add this line at the end of your "{$rcFile}" file: + source /path/to/completion.sh + +Dynamic installation +-------------------- + +Add this to the end of your shell configuration file (e.g. "{$rcFile}"): + + eval "$({$fullCommand} completion {$shell})" +EOH + ) + ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...)) + ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $commandName = basename($_SERVER['argv'][0]); + + if ($input->getOption('debug')) { + $this->tailDebugLog($commandName, $output); + + return 0; + } + + $shell = $input->getArgument('shell') ?? self::guessShell(); + $completionFile = __DIR__.'/../Resources/completion.'.$shell; + if (!file_exists($completionFile)) { + $supportedShells = $this->getSupportedShells(); + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + if ($shell) { + $output->writeln(sprintf('Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").', $shell, implode('", "', $supportedShells))); + } else { + $output->writeln(sprintf('Shell not detected, Symfony shell completion only supports "%s").', implode('", "', $supportedShells))); + } + + return 2; + } + + $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile))); + + return 0; + } + + private static function guessShell(): string + { + return basename($_SERVER['SHELL'] ?? ''); + } + + private function tailDebugLog(string $commandName, OutputInterface $output): void + { + $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log'; + if (!file_exists($debugFile)) { + touch($debugFile); + } + $process = new Process(['tail', '-f', $debugFile], null, null, null, 0); + $process->run(function (string $type, string $line) use ($output): void { + $output->write($line); + }); + } + + /** + * @return string[] + */ + private function getSupportedShells(): array + { + if (isset($this->supportedShells)) { + return $this->supportedShells; + } + + $shells = []; + + foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) { + if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) { + $shells[] = $file->getExtension(); + } + } + sort($shells); + + return $this->supportedShells = $shells; + } +} diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 0000000..a2a72da --- /dev/null +++ b/vendor/symfony/console/Command/HelpCommand.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Descriptor\ApplicationDescription; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier + */ +class HelpCommand extends Command +{ + private Command $command; + + protected function configure(): void + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ]) + ->setDescription('Display help for a command') + ->setHelp(<<<'EOF' +The %command.name% command displays help for a given command: + + %command.full_name% list + +You can also output the help in other formats by using the --format option: + + %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + public function setCommand(Command $command): void + { + $this->command = $command; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->command ??= $this->getApplication()->find($input->getArgument('command_name')); + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + ]); + + unset($this->command); + + return 0; + } +} diff --git a/vendor/symfony/console/Command/LazyCommand.php b/vendor/symfony/console/Command/LazyCommand.php new file mode 100644 index 0000000..fd2c300 --- /dev/null +++ b/vendor/symfony/console/Command/LazyCommand.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Nicolas Grekas + */ +final class LazyCommand extends Command +{ + private \Closure|Command $command; + + public function __construct( + string $name, + array $aliases, + string $description, + bool $isHidden, + \Closure $commandFactory, + private ?bool $isEnabled = true, + ) { + $this->setName($name) + ->setAliases($aliases) + ->setHidden($isHidden) + ->setDescription($description); + + $this->command = $commandFactory; + } + + public function ignoreValidationErrors(): void + { + $this->getCommand()->ignoreValidationErrors(); + } + + public function setApplication(?Application $application): void + { + if ($this->command instanceof parent) { + $this->command->setApplication($application); + } + + parent::setApplication($application); + } + + public function setHelperSet(HelperSet $helperSet): void + { + if ($this->command instanceof parent) { + $this->command->setHelperSet($helperSet); + } + + parent::setHelperSet($helperSet); + } + + public function isEnabled(): bool + { + return $this->isEnabled ?? $this->getCommand()->isEnabled(); + } + + public function run(InputInterface $input, OutputInterface $output): int + { + return $this->getCommand()->run($input, $output); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $this->getCommand()->complete($input, $suggestions); + } + + public function setCode(callable $code): static + { + $this->getCommand()->setCode($code); + + return $this; + } + + /** + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + $this->getCommand()->mergeApplicationDefinition($mergeArgs); + } + + public function setDefinition(array|InputDefinition $definition): static + { + $this->getCommand()->setDefinition($definition); + + return $this; + } + + public function getDefinition(): InputDefinition + { + return $this->getCommand()->getDefinition(); + } + + public function getNativeDefinition(): InputDefinition + { + return $this->getCommand()->getNativeDefinition(); + } + + /** + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + */ + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues); + + return $this; + } + + /** + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + */ + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); + + return $this; + } + + public function setProcessTitle(string $title): static + { + $this->getCommand()->setProcessTitle($title); + + return $this; + } + + public function setHelp(string $help): static + { + $this->getCommand()->setHelp($help); + + return $this; + } + + public function getHelp(): string + { + return $this->getCommand()->getHelp(); + } + + public function getProcessedHelp(): string + { + return $this->getCommand()->getProcessedHelp(); + } + + public function getSynopsis(bool $short = false): string + { + return $this->getCommand()->getSynopsis($short); + } + + public function addUsage(string $usage): static + { + $this->getCommand()->addUsage($usage); + + return $this; + } + + public function getUsages(): array + { + return $this->getCommand()->getUsages(); + } + + public function getHelper(string $name): HelperInterface + { + return $this->getCommand()->getHelper($name); + } + + public function getCommand(): parent + { + if (!$this->command instanceof \Closure) { + return $this->command; + } + + $command = $this->command = ($this->command)(); + $command->setApplication($this->getApplication()); + + if (null !== $this->getHelperSet()) { + $command->setHelperSet($this->getHelperSet()); + } + + $command->setName($this->getName()) + ->setAliases($this->getAliases()) + ->setHidden($this->isHidden()) + ->setDescription($this->getDescription()); + + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + + return $command; + } +} diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 0000000..61b4b1b --- /dev/null +++ b/vendor/symfony/console/Command/ListCommand.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Descriptor\ApplicationDescription; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + protected function configure(): void + { + $this + ->setName('list') + ->setDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()), + new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), + ]) + ->setDescription('List commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + %command.full_name% + +You can also display the commands for a specific namespace: + + %command.full_name% test + +You can also output the information in other formats by using the --format option: + + %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + %command.full_name% --raw +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + 'short' => $input->getOption('short'), + ]); + + return 0; + } +} diff --git a/vendor/symfony/console/Command/LockableTrait.php b/vendor/symfony/console/Command/LockableTrait.php new file mode 100644 index 0000000..f0001cc --- /dev/null +++ b/vendor/symfony/console/Command/LockableTrait.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\LockInterface; +use Symfony\Component\Lock\Store\FlockStore; +use Symfony\Component\Lock\Store\SemaphoreStore; + +/** + * Basic lock feature for commands. + * + * @author Geoffrey Brier + */ +trait LockableTrait +{ + private ?LockInterface $lock = null; + + private ?LockFactory $lockFactory = null; + + /** + * Locks a command. + */ + private function lock(?string $name = null, bool $blocking = false): bool + { + if (!class_exists(SemaphoreStore::class)) { + throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".'); + } + + if (null !== $this->lock) { + throw new LogicException('A lock is already in place.'); + } + + if (null === $this->lockFactory) { + if (SemaphoreStore::isSupported()) { + $store = new SemaphoreStore(); + } else { + $store = new FlockStore(); + } + + $this->lockFactory = (new LockFactory($store)); + } + + $this->lock = $this->lockFactory->createLock($name ?: $this->getName()); + if (!$this->lock->acquire($blocking)) { + $this->lock = null; + + return false; + } + + return true; + } + + /** + * Releases the command lock if there is one. + */ + private function release(): void + { + if ($this->lock) { + $this->lock->release(); + $this->lock = null; + } + } +} diff --git a/vendor/symfony/console/Command/SignalableCommandInterface.php b/vendor/symfony/console/Command/SignalableCommandInterface.php new file mode 100644 index 0000000..40b301d --- /dev/null +++ b/vendor/symfony/console/Command/SignalableCommandInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +/** + * Interface for command reacting to signal. + * + * @author Grégoire Pineau + */ +interface SignalableCommandInterface +{ + /** + * Returns the list of signals to subscribe. + */ + public function getSubscribedSignals(): array; + + /** + * The method will be called when the application is signaled. + * + * @return int|false The exit code to return or false to continue the normal execution + */ + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false; +} diff --git a/vendor/symfony/console/Command/TraceableCommand.php b/vendor/symfony/console/Command/TraceableCommand.php new file mode 100644 index 0000000..9ffb68d --- /dev/null +++ b/vendor/symfony/console/Command/TraceableCommand.php @@ -0,0 +1,356 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @internal + * + * @author Jules Pietri + */ +final class TraceableCommand extends Command implements SignalableCommandInterface +{ + public readonly Command $command; + public int $exitCode; + public ?int $interruptedBySignal = null; + public bool $ignoreValidation; + public bool $isInteractive = false; + public string $duration = 'n/a'; + public string $maxMemoryUsage = 'n/a'; + public InputInterface $input; + public OutputInterface $output; + /** @var array */ + public array $arguments; + /** @var array */ + public array $options; + /** @var array */ + public array $interactiveInputs = []; + public array $handledSignals = []; + + public function __construct( + Command $command, + private readonly Stopwatch $stopwatch, + ) { + if ($command instanceof LazyCommand) { + $command = $command->getCommand(); + } + + $this->command = $command; + + // prevent call to self::getDefaultDescription() + $this->setDescription($command->getDescription()); + + parent::__construct($command->getName()); + + // init below enables calling {@see parent::run()} + [$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(function () { + return [$this->code, $this->processTitle, $this->ignoreValidationErrors]; + }, $command, Command::class)(); + + if (\is_callable($code)) { + $this->setCode($code); + } + + if ($processTitle) { + parent::setProcessTitle($processTitle); + } + + if ($ignoreValidationErrors) { + parent::ignoreValidationErrors(); + } + + $this->ignoreValidation = $ignoreValidationErrors; + } + + public function __call(string $name, array $arguments): mixed + { + return $this->command->{$name}(...$arguments); + } + + public function getSubscribedSignals(): array + { + return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : []; + } + + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false + { + if (!$this->command instanceof SignalableCommandInterface) { + return false; + } + + $event = $this->stopwatch->start($this->getName().'.handle_signal'); + + $exit = $this->command->handleSignal($signal, $previousExitCode); + + $event->stop(); + + if (!isset($this->handledSignals[$signal])) { + $this->handledSignals[$signal] = [ + 'handled' => 0, + 'duration' => 0, + 'memory' => 0, + ]; + } + + ++$this->handledSignals[$signal]['handled']; + $this->handledSignals[$signal]['duration'] += $event->getDuration(); + $this->handledSignals[$signal]['memory'] = max( + $this->handledSignals[$signal]['memory'], + $event->getMemory() >> 20 + ); + + return $exit; + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function ignoreValidationErrors(): void + { + $this->ignoreValidation = true; + $this->command->ignoreValidationErrors(); + + parent::ignoreValidationErrors(); + } + + public function setApplication(?Application $application = null): void + { + $this->command->setApplication($application); + } + + public function getApplication(): ?Application + { + return $this->command->getApplication(); + } + + public function setHelperSet(HelperSet $helperSet): void + { + $this->command->setHelperSet($helperSet); + } + + public function getHelperSet(): ?HelperSet + { + return $this->command->getHelperSet(); + } + + public function isEnabled(): bool + { + return $this->command->isEnabled(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $this->command->complete($input, $suggestions); + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function setCode(callable $code): static + { + $this->command->setCode($code); + + return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int { + $event = $this->stopwatch->start($this->getName().'.code'); + + $this->exitCode = $code($input, $output); + + $event->stop(); + + return $this->exitCode; + }); + } + + /** + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + $this->command->mergeApplicationDefinition($mergeArgs); + } + + public function setDefinition(array|InputDefinition $definition): static + { + $this->command->setDefinition($definition); + + return $this; + } + + public function getDefinition(): InputDefinition + { + return $this->command->getDefinition(); + } + + public function getNativeDefinition(): InputDefinition + { + return $this->command->getNativeDefinition(); + } + + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->command->addArgument($name, $mode, $description, $default, $suggestedValues); + + return $this; + } + + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); + + return $this; + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function setProcessTitle(string $title): static + { + $this->command->setProcessTitle($title); + + return parent::setProcessTitle($title); + } + + public function setHelp(string $help): static + { + $this->command->setHelp($help); + + return $this; + } + + public function getHelp(): string + { + return $this->command->getHelp(); + } + + public function getProcessedHelp(): string + { + return $this->command->getProcessedHelp(); + } + + public function getSynopsis(bool $short = false): string + { + return $this->command->getSynopsis($short); + } + + public function addUsage(string $usage): static + { + $this->command->addUsage($usage); + + return $this; + } + + public function getUsages(): array + { + return $this->command->getUsages(); + } + + public function getHelper(string $name): HelperInterface + { + return $this->command->getHelper($name); + } + + public function run(InputInterface $input, OutputInterface $output): int + { + $this->input = $input; + $this->output = $output; + $this->arguments = $input->getArguments(); + $this->options = $input->getOptions(); + $event = $this->stopwatch->start($this->getName(), 'command'); + + try { + $this->exitCode = parent::run($input, $output); + } finally { + $event->stop(); + + if ($output instanceof ConsoleOutputInterface && $output->isDebug()) { + $output->getErrorOutput()->writeln((string) $event); + } + + $this->duration = $event->getDuration().' ms'; + $this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB'; + + if ($this->isInteractive) { + $this->extractInteractiveInputs($input->getArguments(), $input->getOptions()); + } + } + + return $this->exitCode; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $event = $this->stopwatch->start($this->getName().'.init', 'command'); + + $this->command->initialize($input, $output); + + $event->stop(); + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) { + return; + } + + $event = $this->stopwatch->start($this->getName().'.interact', 'command'); + + $this->command->interact($input, $output); + + $event->stop(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $event = $this->stopwatch->start($this->getName().'.execute', 'command'); + + $exitCode = $this->command->execute($input, $output); + + $event->stop(); + + return $exitCode; + } + + private function extractInteractiveInputs(array $arguments, array $options): void + { + foreach ($arguments as $argName => $argValue) { + if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) { + continue; + } + + $this->interactiveInputs[$argName] = $argValue; + } + + foreach ($options as $optName => $optValue) { + if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) { + continue; + } + + $this->interactiveInputs['--'.$optName] = $optValue; + } + } +} diff --git a/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php new file mode 100644 index 0000000..b6b637c --- /dev/null +++ b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Robin Chalas + */ +interface CommandLoaderInterface +{ + /** + * Loads a command. + * + * @throws CommandNotFoundException + */ + public function get(string $name): Command; + + /** + * Checks if a command exists. + */ + public function has(string $name): bool; + + /** + * @return string[] + */ + public function getNames(): array; +} diff --git a/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php new file mode 100644 index 0000000..84e25be --- /dev/null +++ b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * Loads commands from a PSR-11 container. + * + * @author Robin Chalas + */ +class ContainerCommandLoader implements CommandLoaderInterface +{ + /** + * @param array $commandMap An array with command names as keys and service ids as values + */ + public function __construct( + private ContainerInterface $container, + private array $commandMap, + ) { + } + + public function get(string $name): Command + { + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->container->get($this->commandMap[$name]); + } + + public function has(string $name): bool + { + return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); + } + + public function getNames(): array + { + return array_keys($this->commandMap); + } +} diff --git a/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php new file mode 100644 index 0000000..ae16bf6 --- /dev/null +++ b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * A simple command loader using factories to instantiate commands lazily. + * + * @author Maxime Steinhausser + */ +class FactoryCommandLoader implements CommandLoaderInterface +{ + /** + * @param callable[] $factories Indexed by command names + */ + public function __construct( + private array $factories, + ) { + } + + public function has(string $name): bool + { + return isset($this->factories[$name]); + } + + public function get(string $name): Command + { + if (!isset($this->factories[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + $factory = $this->factories[$name]; + + return $factory(); + } + + public function getNames(): array + { + return array_keys($this->factories); + } +} diff --git a/vendor/symfony/console/Completion/CompletionInput.php b/vendor/symfony/console/Completion/CompletionInput.php new file mode 100644 index 0000000..79c2f65 --- /dev/null +++ b/vendor/symfony/console/Completion/CompletionInput.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * An input specialized for shell completion. + * + * This input allows unfinished option names or values and exposes what kind of + * completion is expected. + * + * @author Wouter de Jong + */ +final class CompletionInput extends ArgvInput +{ + public const TYPE_ARGUMENT_VALUE = 'argument_value'; + public const TYPE_OPTION_VALUE = 'option_value'; + public const TYPE_OPTION_NAME = 'option_name'; + public const TYPE_NONE = 'none'; + + private array $tokens; + private int $currentIndex; + private string $completionType; + private ?string $completionName = null; + private string $completionValue = ''; + + /** + * Converts a terminal string into tokens. + * + * This is required for shell completions without COMP_WORDS support. + */ + public static function fromString(string $inputStr, int $currentIndex): self + { + preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?tokens = $tokens; + $input->currentIndex = $currentIndex; + + return $input; + } + + public function bind(InputDefinition $definition): void + { + parent::bind($definition); + + $relevantToken = $this->getRelevantToken(); + if ('-' === $relevantToken[0]) { + // the current token is an input option: complete either option name or option value + [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', '']; + + $option = $this->getOptionFromToken($optionToken); + if (null === $option && !$this->isCursorFree()) { + $this->completionType = self::TYPE_OPTION_NAME; + $this->completionValue = $relevantToken; + + return; + } + + if ($option?->acceptValue()) { + $this->completionType = self::TYPE_OPTION_VALUE; + $this->completionName = $option->getName(); + $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); + + return; + } + } + + $previousToken = $this->tokens[$this->currentIndex - 1]; + if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { + // check if previous option accepted a value + $previousOption = $this->getOptionFromToken($previousToken); + if ($previousOption?->acceptValue()) { + $this->completionType = self::TYPE_OPTION_VALUE; + $this->completionName = $previousOption->getName(); + $this->completionValue = $relevantToken; + + return; + } + } + + // complete argument value + $this->completionType = self::TYPE_ARGUMENT_VALUE; + + foreach ($this->definition->getArguments() as $argumentName => $argument) { + if (!isset($this->arguments[$argumentName])) { + break; + } + + $argumentValue = $this->arguments[$argumentName]; + $this->completionName = $argumentName; + if (\is_array($argumentValue)) { + $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null; + } else { + $this->completionValue = $argumentValue; + } + } + + if ($this->currentIndex >= \count($this->tokens)) { + if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { + $this->completionName = $argumentName; + $this->completionValue = ''; + } else { + // we've reached the end + $this->completionType = self::TYPE_NONE; + $this->completionName = null; + $this->completionValue = ''; + } + } + } + + /** + * Returns the type of completion required. + * + * TYPE_ARGUMENT_VALUE when completing the value of an input argument + * TYPE_OPTION_VALUE when completing the value of an input option + * TYPE_OPTION_NAME when completing the name of an input option + * TYPE_NONE when nothing should be completed + * + * TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component. + * + * @return self::TYPE_* + */ + public function getCompletionType(): string + { + return $this->completionType; + } + + /** + * The name of the input option or argument when completing a value. + * + * @return string|null returns null when completing an option name + */ + public function getCompletionName(): ?string + { + return $this->completionName; + } + + /** + * The value already typed by the user (or empty string). + */ + public function getCompletionValue(): string + { + return $this->completionValue; + } + + public function mustSuggestOptionValuesFor(string $optionName): bool + { + return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName(); + } + + public function mustSuggestArgumentValuesFor(string $argumentName): bool + { + return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName(); + } + + protected function parseToken(string $token, bool $parseOptions): bool + { + try { + return parent::parseToken($token, $parseOptions); + } catch (RuntimeException) { + // suppress errors, completed input is almost never valid + } + + return $parseOptions; + } + + private function getOptionFromToken(string $optionToken): ?InputOption + { + $optionName = ltrim($optionToken, '-'); + if (!$optionName) { + return null; + } + + if ('-' === ($optionToken[1] ?? ' ')) { + // long option name + return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null; + } + + // short option name + return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null; + } + + /** + * The token of the cursor, or the last token if the cursor is at the end of the input. + */ + private function getRelevantToken(): string + { + return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex]; + } + + /** + * Whether the cursor is "free" (i.e. at the end of the input preceded by a space). + */ + private function isCursorFree(): bool + { + $nrOfTokens = \count($this->tokens); + if ($this->currentIndex > $nrOfTokens) { + throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.'); + } + + return $this->currentIndex >= $nrOfTokens; + } + + public function __toString() + { + $str = ''; + foreach ($this->tokens as $i => $token) { + $str .= $token; + + if ($this->currentIndex === $i) { + $str .= '|'; + } + + $str .= ' '; + } + + if ($this->currentIndex > $i) { + $str .= '|'; + } + + return rtrim($str); + } +} diff --git a/vendor/symfony/console/Completion/CompletionSuggestions.php b/vendor/symfony/console/Completion/CompletionSuggestions.php new file mode 100644 index 0000000..549bbaf --- /dev/null +++ b/vendor/symfony/console/Completion/CompletionSuggestions.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +use Symfony\Component\Console\Input\InputOption; + +/** + * Stores all completion suggestions for the current input. + * + * @author Wouter de Jong + */ +final class CompletionSuggestions +{ + private array $valueSuggestions = []; + private array $optionSuggestions = []; + + /** + * Add a suggested value for an input option or argument. + * + * @return $this + */ + public function suggestValue(string|Suggestion $value): static + { + $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value; + + return $this; + } + + /** + * Add multiple suggested values at once for an input option or argument. + * + * @param list $values + * + * @return $this + */ + public function suggestValues(array $values): static + { + foreach ($values as $value) { + $this->suggestValue($value); + } + + return $this; + } + + /** + * Add a suggestion for an input option name. + * + * @return $this + */ + public function suggestOption(InputOption $option): static + { + $this->optionSuggestions[] = $option; + + return $this; + } + + /** + * Add multiple suggestions for input option names at once. + * + * @param InputOption[] $options + * + * @return $this + */ + public function suggestOptions(array $options): static + { + foreach ($options as $option) { + $this->suggestOption($option); + } + + return $this; + } + + /** + * @return InputOption[] + */ + public function getOptionSuggestions(): array + { + return $this->optionSuggestions; + } + + /** + * @return Suggestion[] + */ + public function getValueSuggestions(): array + { + return $this->valueSuggestions; + } +} diff --git a/vendor/symfony/console/Completion/Output/BashCompletionOutput.php b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php new file mode 100644 index 0000000..c6f76eb --- /dev/null +++ b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Wouter de Jong + */ +class BashCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = $suggestions->getValueSuggestions(); + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName(); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName(); + } + } + $output->writeln(implode("\n", $values)); + } +} diff --git a/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php new file mode 100644 index 0000000..659e596 --- /dev/null +++ b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion. + * + * @author Wouter de Jong + */ +interface CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void; +} diff --git a/vendor/symfony/console/Completion/Output/FishCompletionOutput.php b/vendor/symfony/console/Completion/Output/FishCompletionOutput.php new file mode 100644 index 0000000..356a974 --- /dev/null +++ b/vendor/symfony/console/Completion/Output/FishCompletionOutput.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Guillaume Aveline + */ +class FishCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = []; + foreach ($suggestions->getValueSuggestions() as $value) { + $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : ''); + } + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + } + } + $output->write(implode("\n", $values)); + } +} diff --git a/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php b/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php new file mode 100644 index 0000000..bb4ce70 --- /dev/null +++ b/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jitendra A + */ +class ZshCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = []; + foreach ($suggestions->getValueSuggestions() as $value) { + $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : ''); + } + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + } + } + $output->write(implode("\n", $values)."\n"); + } +} diff --git a/vendor/symfony/console/Completion/Suggestion.php b/vendor/symfony/console/Completion/Suggestion.php new file mode 100644 index 0000000..3251b07 --- /dev/null +++ b/vendor/symfony/console/Completion/Suggestion.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +/** + * Represents a single suggested value. + * + * @author Wouter de Jong + */ +class Suggestion implements \Stringable +{ + public function __construct( + private readonly string $value, + private readonly string $description = '', + ) { + } + + public function getValue(): string + { + return $this->value; + } + + public function getDescription(): string + { + return $this->description; + } + + public function __toString(): string + { + return $this->getValue(); + } +} diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 0000000..6ae8f32 --- /dev/null +++ b/vendor/symfony/console/ConsoleEvents.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handed to the command. + * + * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") + */ + public const COMMAND = 'console.command'; + + /** + * The SIGNAL event allows you to perform some actions + * after the command execution was interrupted. + * + * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent") + */ + public const SIGNAL = 'console.signal'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") + */ + public const TERMINATE = 'console.terminate'; + + /** + * The ERROR event occurs when an uncaught exception or error appears. + * + * This event allows you to deal with the exception/error or + * to modify the thrown exception. + * + * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") + */ + public const ERROR = 'console.error'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + ConsoleCommandEvent::class => self::COMMAND, + ConsoleErrorEvent::class => self::ERROR, + ConsoleSignalEvent::class => self::SIGNAL, + ConsoleTerminateEvent::class => self::TERMINATE, + ]; +} diff --git a/vendor/symfony/console/Cursor.php b/vendor/symfony/console/Cursor.php new file mode 100644 index 0000000..965f996 --- /dev/null +++ b/vendor/symfony/console/Cursor.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Pierre du Plessis + */ +final class Cursor +{ + /** @var resource */ + private $input; + + /** + * @param resource|null $input + */ + public function __construct( + private OutputInterface $output, + $input = null, + ) { + $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); + } + + /** + * @return $this + */ + public function moveUp(int $lines = 1): static + { + $this->output->write(sprintf("\x1b[%dA", $lines)); + + return $this; + } + + /** + * @return $this + */ + public function moveDown(int $lines = 1): static + { + $this->output->write(sprintf("\x1b[%dB", $lines)); + + return $this; + } + + /** + * @return $this + */ + public function moveRight(int $columns = 1): static + { + $this->output->write(sprintf("\x1b[%dC", $columns)); + + return $this; + } + + /** + * @return $this + */ + public function moveLeft(int $columns = 1): static + { + $this->output->write(sprintf("\x1b[%dD", $columns)); + + return $this; + } + + /** + * @return $this + */ + public function moveToColumn(int $column): static + { + $this->output->write(sprintf("\x1b[%dG", $column)); + + return $this; + } + + /** + * @return $this + */ + public function moveToPosition(int $column, int $row): static + { + $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column)); + + return $this; + } + + /** + * @return $this + */ + public function savePosition(): static + { + $this->output->write("\x1b7"); + + return $this; + } + + /** + * @return $this + */ + public function restorePosition(): static + { + $this->output->write("\x1b8"); + + return $this; + } + + /** + * @return $this + */ + public function hide(): static + { + $this->output->write("\x1b[?25l"); + + return $this; + } + + /** + * @return $this + */ + public function show(): static + { + $this->output->write("\x1b[?25h\x1b[?0c"); + + return $this; + } + + /** + * Clears all the output from the current line. + * + * @return $this + */ + public function clearLine(): static + { + $this->output->write("\x1b[2K"); + + return $this; + } + + /** + * Clears all the output from the current line after the current position. + */ + public function clearLineAfter(): self + { + $this->output->write("\x1b[K"); + + return $this; + } + + /** + * Clears all the output from the cursors' current position to the end of the screen. + * + * @return $this + */ + public function clearOutput(): static + { + $this->output->write("\x1b[0J"); + + return $this; + } + + /** + * Clears the entire screen. + * + * @return $this + */ + public function clearScreen(): static + { + $this->output->write("\x1b[2J"); + + return $this; + } + + /** + * Returns the current cursor position as x,y coordinates. + */ + public function getCurrentPosition(): array + { + static $isTtySupported; + + if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) { + return [1, 1]; + } + + $sttyMode = shell_exec('stty -g'); + shell_exec('stty -icanon -echo'); + + @fwrite($this->input, "\033[6n"); + + $code = trim(fread($this->input, 1024)); + + shell_exec(sprintf('stty %s', $sttyMode)); + + sscanf($code, "\033[%d;%dR", $row, $col); + + return [$col, $row]; + } +} diff --git a/vendor/symfony/console/DataCollector/CommandDataCollector.php b/vendor/symfony/console/DataCollector/CommandDataCollector.php new file mode 100644 index 0000000..45138c7 --- /dev/null +++ b/vendor/symfony/console/DataCollector/CommandDataCollector.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\DataCollector; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\SignalRegistry\SignalMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @internal + * + * @author Jules Pietri + */ +final class CommandDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + if (!$request instanceof CliRequest) { + return; + } + + $command = $request->command; + $application = $command->getApplication(); + + $this->data = [ + 'command' => $this->cloneVar($command->command), + 'exit_code' => $command->exitCode, + 'interrupted_by_signal' => $command->interruptedBySignal, + 'duration' => $command->duration, + 'max_memory_usage' => $command->maxMemoryUsage, + 'verbosity_level' => match ($command->output->getVerbosity()) { + OutputInterface::VERBOSITY_QUIET => 'quiet', + OutputInterface::VERBOSITY_NORMAL => 'normal', + OutputInterface::VERBOSITY_VERBOSE => 'verbose', + OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose', + OutputInterface::VERBOSITY_DEBUG => 'debug', + }, + 'interactive' => $command->isInteractive, + 'validate_input' => !$command->ignoreValidation, + 'enabled' => $command->isEnabled(), + 'visible' => !$command->isHidden(), + 'input' => $this->cloneVar($command->input), + 'output' => $this->cloneVar($command->output), + 'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs), + 'signalable' => $command->getSubscribedSignals(), + 'handled_signals' => $command->handledSignals, + 'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())), + ]; + + $baseDefinition = $application->getDefinition(); + + foreach ($command->arguments as $argName => $argValue) { + if ($baseDefinition->hasArgument($argName)) { + $this->data['application_inputs'][$argName] = $this->cloneVar($argValue); + } else { + $this->data['arguments'][$argName] = $this->cloneVar($argValue); + } + } + + foreach ($command->options as $optName => $optValue) { + if ($baseDefinition->hasOption($optName)) { + $this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue); + } else { + $this->data['options'][$optName] = $this->cloneVar($optValue); + } + } + } + + public function getName(): string + { + return 'command'; + } + + /** + * @return array{ + * class?: class-string, + * executor?: string, + * file: string, + * line: int, + * } + */ + public function getCommand(): array + { + $class = $this->data['command']->getType(); + $r = new \ReflectionMethod($class, 'execute'); + + if (Command::class !== $r->getDeclaringClass()) { + return [ + 'executor' => $class.'::'.$r->name, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + + $r = new \ReflectionClass($class); + + return [ + 'class' => $class, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + + public function getInterruptedBySignal(): ?string + { + if (isset($this->data['interrupted_by_signal'])) { + return sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']); + } + + return null; + } + + public function getDuration(): string + { + return $this->data['duration']; + } + + public function getMaxMemoryUsage(): string + { + return $this->data['max_memory_usage']; + } + + public function getVerbosityLevel(): string + { + return $this->data['verbosity_level']; + } + + public function getInteractive(): bool + { + return $this->data['interactive']; + } + + public function getValidateInput(): bool + { + return $this->data['validate_input']; + } + + public function getEnabled(): bool + { + return $this->data['enabled']; + } + + public function getVisible(): bool + { + return $this->data['visible']; + } + + public function getInput(): Data + { + return $this->data['input']; + } + + public function getOutput(): Data + { + return $this->data['output']; + } + + /** + * @return Data[] + */ + public function getArguments(): array + { + return $this->data['arguments'] ?? []; + } + + /** + * @return Data[] + */ + public function getOptions(): array + { + return $this->data['options'] ?? []; + } + + /** + * @return Data[] + */ + public function getApplicationInputs(): array + { + return $this->data['application_inputs'] ?? []; + } + + /** + * @return Data[] + */ + public function getInteractiveInputs(): array + { + return $this->data['interactive_inputs'] ?? []; + } + + public function getSignalable(): array + { + return array_map( + static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal), + $this->data['signalable'] + ); + } + + public function getHandledSignals(): array + { + $keys = array_map( + static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal), + array_keys($this->data['handled_signals']) + ); + + return array_combine($keys, array_values($this->data['handled_signals'])); + } + + /** + * @return Data[] + */ + public function getHelperSet(): array + { + return $this->data['helper_set'] ?? []; + } + + public function reset(): void + { + $this->data = []; + } +} diff --git a/vendor/symfony/console/Debug/CliRequest.php b/vendor/symfony/console/Debug/CliRequest.php new file mode 100644 index 0000000..b023db0 --- /dev/null +++ b/vendor/symfony/console/Debug/CliRequest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Debug; + +use Symfony\Component\Console\Command\TraceableCommand; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @internal + */ +final class CliRequest extends Request +{ + public function __construct( + public readonly TraceableCommand $command, + ) { + parent::__construct( + attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'], + server: $_SERVER, + ); + } + + // Methods below allow to populate a profile, thus enable search and filtering + public function getUri(): string + { + if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) { + $binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console'; + } else { + $binary = $this->server->get('argv')[0]; + } + + return $binary.' '.$this->command->input; + } + + public function getMethod(): string + { + return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH'; + } + + public function getResponse(): Response + { + return new class($this->command->exitCode) extends Response { + public function __construct(private readonly int $exitCode) + { + parent::__construct(); + } + + public function getStatusCode(): int + { + return $this->exitCode; + } + }; + } + + public function getClientIp(): string + { + $application = $this->command->getApplication(); + + return $application->getName().' '.$application->getVersion(); + } +} diff --git a/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php new file mode 100644 index 0000000..f712c61 --- /dev/null +++ b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\DependencyInjection; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Registers console commands. + * + * @author Grégoire Pineau + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $commandServices = $container->findTaggedServiceIds('console.command', true); + $lazyCommandMap = []; + $lazyCommandRefs = []; + $serviceIds = []; + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + $definition->addTag('container.no_preload'); + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (isset($tags[0]['command'])) { + $aliases = $tags[0]['command']; + } else { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); + } + $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); + } + + $aliases = explode('|', $aliases ?? ''); + $commandName = array_shift($aliases); + + if ($isHidden = '' === $commandName) { + $commandName = array_shift($aliases); + } + + if (null === $commandName) { + if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) { + $commandId = 'console.command.public_alias.'.$id; + $container->setAlias($commandId, $id)->setPublic(true); + $id = $commandId; + } + $serviceIds[] = $id; + + continue; + } + + $description = $tags[0]['description'] ?? null; + + unset($tags[0]); + $lazyCommandMap[$commandName] = $id; + $lazyCommandRefs[$id] = new TypedReference($id, $class); + + foreach ($aliases as $alias) { + $lazyCommandMap[$alias] = $id; + } + + foreach ($tags as $tag) { + if (isset($tag['command'])) { + $aliases[] = $tag['command']; + $lazyCommandMap[$tag['command']] = $id; + } + + $description ??= $tag['description'] ?? null; + } + + $definition->addMethodCall('setName', [$commandName]); + + if ($aliases) { + $definition->addMethodCall('setAliases', [$aliases]); + } + + if ($isHidden) { + $definition->addMethodCall('setHidden', [true]); + } + + if (!$description) { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); + } + $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); + } + + if ($description) { + $definition->addMethodCall('setDescription', [$description]); + + $container->register('.'.$id.'.lazy', LazyCommand::class) + ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); + + $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy'); + } + } + + $container + ->register('console.command_loader', ContainerCommandLoader::class) + ->setPublic(true) + ->addTag('container.no_preload') + ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); + + $container->setParameter('console.command.ids', $serviceIds); + } +} diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 0000000..5149fde --- /dev/null +++ b/vendor/symfony/console/Descriptor/ApplicationDescription.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Jean-François Simon + * + * @internal + */ +class ApplicationDescription +{ + public const GLOBAL_NAMESPACE = '_global'; + + private array $namespaces; + + /** + * @var array + */ + private array $commands; + + /** + * @var array + */ + private array $aliases = []; + + public function __construct( + private Application $application, + private ?string $namespace = null, + private bool $showHidden = false, + ) { + } + + public function getNamespaces(): array + { + if (!isset($this->namespaces)) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (!isset($this->commands)) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @throws CommandNotFoundException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->commands[$name] ?? $this->aliases[$name]; + } + + private function inspectApplication(): void + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + private function sortCommands(array $commands): array + { + $namespacedCommands = []; + $globalCommands = []; + $sortedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) { + $globalCommands[$name] = $command; + } else { + $namespacedCommands[$key][$name] = $command; + } + } + + if ($globalCommands) { + ksort($globalCommands); + $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands; + } + + if ($namespacedCommands) { + ksort($namespacedCommands, \SORT_STRING); + foreach ($namespacedCommands as $key => $commandsSet) { + ksort($commandsSet); + $sortedCommands[$key] = $commandsSet; + } + } + + return $sortedCommands; + } +} diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 0000000..7b2509c --- /dev/null +++ b/vendor/symfony/console/Descriptor/Descriptor.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + protected OutputInterface $output; + + public function describe(OutputInterface $output, object $object, array $options = []): void + { + $this->output = $output; + + match (true) { + $object instanceof InputArgument => $this->describeInputArgument($object, $options), + $object instanceof InputOption => $this->describeInputOption($object, $options), + $object instanceof InputDefinition => $this->describeInputDefinition($object, $options), + $object instanceof Command => $this->describeCommand($object, $options), + $object instanceof Application => $this->describeApplication($object, $options), + default => throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))), + }; + } + + protected function write(string $content, bool $decorated = false): void + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void; + + /** + * Describes an InputOption instance. + */ + abstract protected function describeInputOption(InputOption $option, array $options = []): void; + + /** + * Describes an InputDefinition instance. + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void; + + /** + * Describes a Command instance. + */ + abstract protected function describeCommand(Command $command, array $options = []): void; + + /** + * Describes an Application instance. + */ + abstract protected function describeApplication(Application $application, array $options = []): void; +} diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 0000000..04e5a7c --- /dev/null +++ b/vendor/symfony/console/Descriptor/DescriptorInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + public function describe(OutputInterface $output, object $object, array $options = []): void; +} diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000..9563037 --- /dev/null +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $this->writeData($this->getInputOptionData($option), $options); + if ($option->isNegatable()) { + $this->writeData($this->getInputOptionData($option, true), $options); + } + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + protected function describeCommand(Command $command, array $options = []): void + { + $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options); + } + + protected function describeApplication(Application $application, array $options = []): void + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace, true); + $commands = []; + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command, $options['short'] ?? false); + } + + $data = []; + if ('UNKNOWN' !== $application->getName()) { + $data['application']['name'] = $application->getName(); + if ('UNKNOWN' !== $application->getVersion()) { + $data['application']['version'] = $application->getVersion(); + } + } + + $data['commands'] = $commands; + + if ($describedNamespace) { + $data['namespace'] = $describedNamespace; + } else { + $data['namespaces'] = array_values($description->getNamespaces()); + } + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + */ + private function writeData(array $data, array $options): void + { + $flags = $options['json_encoding'] ?? 0; + + $this->write(json_encode($data, $flags)); + } + + private function getInputArgumentData(InputArgument $argument): array + { + return [ + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), + ]; + } + + private function getInputOptionData(InputOption $option, bool $negated = false): array + { + return $negated ? [ + 'name' => '--no-'.$option->getName(), + 'shortcut' => '', + 'accept_value' => false, + 'is_value_required' => false, + 'is_multiple' => false, + 'description' => 'Negate the "--'.$option->getName().'" option', + 'default' => false, + ] : [ + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), + ]; + } + + private function getInputDefinitionData(InputDefinition $definition): array + { + $inputArguments = []; + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = []; + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + if ($option->isNegatable()) { + $inputOptions['no-'.$name] = $this->getInputOptionData($option, true); + } + } + + return ['arguments' => $inputArguments, 'options' => $inputOptions]; + } + + private function getCommandData(Command $command, bool $short = false): array + { + $data = [ + 'name' => $command->getName(), + 'description' => $command->getDescription(), + ]; + + if ($short) { + $data += [ + 'usage' => $command->getAliases(), + ]; + } else { + $command->mergeApplicationDefinition(false); + + $data += [ + 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getDefinition()), + ]; + } + + $data['hidden'] = $command->isHidden(); + + return $data; + } +} diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000..b3f16ee --- /dev/null +++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + public function describe(OutputInterface $output, object $object, array $options = []): void + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + protected function write(string $content, bool $decorated = true): void + { + parent::write($content, $decorated); + } + + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->write( + '#### `'.($argument->getName() ?: '')."`\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $name = '--'.$option->getName(); + if ($option->isNegatable()) { + $name .= '|--no-'.$option->getName(); + } + if ($option->getShortcut()) { + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; + } + + $this->write( + '#### `'.$name.'`'."\n\n" + .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + if ($showArguments = \count($definition->getArguments()) > 0) { + $this->write('### Arguments'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->describeInputArgument($argument); + } + } + + if (\count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + $this->describeInputOption($option); + } + } + } + + protected function describeCommand(Command $command, array $options = []): void + { + if ($options['short'] ?? false) { + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") + ); + + return; + } + + $command->mergeApplicationDefinition(false); + + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->write("\n\n"); + $this->describeInputDefinition($definition); + } + } + + protected function describeApplication(Application $application, array $options = []): void + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat('=', Helper::width($title))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(fn ($commandName) => sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + $this->describeCommand($command, $options); + } + } + + private function getApplicationTitle(Application $application): string + { + if ('UNKNOWN' !== $application->getName()) { + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + return 'Console Tool'; + } +} diff --git a/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php b/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php new file mode 100644 index 0000000..f12fecb --- /dev/null +++ b/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\String\UnicodeString; + +class ReStructuredTextDescriptor extends Descriptor +{ + //

+ private string $partChar = '='; + //

+ private string $chapterChar = '-'; + //

+ private string $sectionChar = '~'; + //

+ private string $subsectionChar = '.'; + //

+ private string $subsubsectionChar = '^'; + //
+ private string $paragraphsChar = '"'; + + private array $visibleNamespaces = []; + + public function describe(OutputInterface $output, object $object, array $options = []): void + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + /** + * Override parent method to set $decorated = true. + */ + protected function write(string $content, bool $decorated = true): void + { + parent::write($content, $decorated); + } + + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->write( + $argument->getName() ?: ''."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``' + ); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $name = '\-\-'.$option->getName(); + if ($option->isNegatable()) { + $name .= '|\-\-no-'.$option->getName(); + } + if ($option->getShortcut()) { + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()); + } + + $optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : ''; + $optionDescription = (new UnicodeString($optionDescription))->ascii(); + $this->write( + $name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n" + .$optionDescription + .'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n" + .'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n" + .'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n" + ); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + if ($showArguments = ((bool) $definition->getArguments())) { + $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n"; + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->describeInputArgument($argument); + } + } + + if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n"); + foreach ($nonDefaultOptions as $option) { + $this->describeInputOption($option); + $this->write("\n"); + } + } + } + + protected function describeCommand(Command $command, array $options = []): void + { + if ($options['short'] ?? false) { + $this->write( + '``'.$command->getName()."``\n" + .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + ."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n" + .array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n") + ); + + return; + } + + $command->mergeApplicationDefinition(false); + + foreach ($command->getAliases() as $alias) { + $this->write('.. _'.$alias.":\n\n"); + } + $this->write( + $command->getName()."\n" + .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + ."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n") + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->write("\n\n"); + $this->describeInputDefinition($definition); + } + } + + protected function describeApplication(Application $application, array $options = []): void + { + $description = new ApplicationDescription($application, $options['namespace'] ?? null); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat($this->partChar, Helper::width($title))); + $this->createTableOfContents($description, $application); + $this->describeCommands($application, $options); + } + + private function getApplicationTitle(Application $application): string + { + if ('UNKNOWN' === $application->getName()) { + return 'Console Tool'; + } + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + private function describeCommands($application, array $options): void + { + $title = 'Commands'; + $this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n"); + foreach ($this->visibleNamespaces as $namespace) { + if ('_global' === $namespace) { + $commands = $application->all(''); + $this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n"); + } else { + $commands = $application->all($namespace); + $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n"); + } + + foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) { + $this->describeCommand($command, $options); + $this->write("\n\n"); + } + } + } + + private function createTableOfContents(ApplicationDescription $description, Application $application): void + { + $this->setVisibleNamespaces($description); + $chapterTitle = 'Table of Contents'; + $this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n"); + foreach ($this->visibleNamespaces as $namespace) { + if ('_global' === $namespace) { + $commands = $application->all(''); + } else { + $commands = $application->all($namespace); + $this->write("\n\n"); + $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n"); + } + $commands = $this->removeAliasesAndHiddenCommands($commands); + + $this->write("\n\n"); + $this->write(implode("\n", array_map(static fn ($commandName) => sprintf('- `%s`_', $commandName), array_keys($commands)))); + } + } + + private function getNonDefaultOptions(InputDefinition $definition): array + { + $globalOptions = [ + 'help', + 'quiet', + 'verbose', + 'version', + 'ansi', + 'no-interaction', + ]; + $nonDefaultOptions = []; + foreach ($definition->getOptions() as $option) { + // Skip global options. + if (!\in_array($option->getName(), $globalOptions, true)) { + $nonDefaultOptions[] = $option; + } + } + + return $nonDefaultOptions; + } + + private function setVisibleNamespaces(ApplicationDescription $description): void + { + $commands = $description->getCommands(); + foreach ($description->getNamespaces() as $namespace) { + try { + $namespaceCommands = $namespace['commands']; + foreach ($namespaceCommands as $key => $commandName) { + if (!\array_key_exists($commandName, $commands)) { + // If the array key does not exist, then this is an alias. + unset($namespaceCommands[$key]); + } elseif ($commands[$commandName]->isHidden()) { + unset($namespaceCommands[$key]); + } + } + if (!$namespaceCommands) { + // If the namespace contained only aliases or hidden commands, skip the namespace. + continue; + } + } catch (\Exception) { + } + $this->visibleNamespaces[] = $namespace['id']; + } + } + + private function removeAliasesAndHiddenCommands(array $commands): array + { + foreach ($commands as $key => $command) { + if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) { + unset($commands[$key]); + } + } + unset($commands['completion']); + + return $commands; + } +} diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000..d04d102 --- /dev/null +++ b/vendor/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = $options['total_width'] ?? Helper::width($argument->getName()); + $spacingWidth = $totalWidth - \strlen($argument->getName()); + + $this->writeText(sprintf(' %s %s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), + $default + ), $options); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::width($synopsis); + + $this->writeText(sprintf(' %s %s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::width($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (\strlen($option->getShortcut() ?? '') > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + protected function describeCommand(Command $command, array $options = []): void + { + $command->mergeApplicationDefinition(false); + + if ($description = $command->getDescription()) { + $this->writeText('Description:', $options); + $this->writeText("\n"); + $this->writeText(' '.$description); + $this->writeText("\n\n"); + } + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.OutputFormatter::escape($usage), $options); + } + $this->writeText("\n"); + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + $help = $command->getProcessedHelp(); + if ($help && $help !== $description) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + protected function describeApplication(Application $application, array $options = []): void + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $commands = $description->getCommands(); + $namespaces = $description->getNamespaces(); + if ($describedNamespace && $namespaces) { + // make sure all alias commands are included when describing a specific namespace + $describedNamespaceInfo = reset($namespaces); + foreach ($describedNamespaceInfo['commands'] as $name) { + $commands[$name] = $description->getCommand($name); + } + } + + // calculate max. width based on available commands per namespace + $width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces))))); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + foreach ($namespaces as $namespace) { + $namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name])); + + if (!$namespace['commands']) { + continue; + } + + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' '.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - Helper::width($name); + $command = $commands[$name]; + $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + private function writeText(string $content, array $options = []): void + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats command aliases to show them in the command description. + */ + private function getCommandAliasesText(Command $command): string + { + $text = ''; + $aliases = $command->getAliases(); + + if ($aliases) { + $text = '['.implode('|', $aliases).'] '; + } + + return $text; + } + + /** + * Formats input option/argument default value. + */ + private function formatDefaultValue(mixed $default): string + { + if (\INF === $default) { + return 'INF'; + } + + if (\is_string($default)) { + $default = OutputFormatter::escape($default); + } elseif (\is_array($default)) { + foreach ($default as $key => $value) { + if (\is_string($value)) { + $default[$key] = OutputFormatter::escape($value); + } + } + } + + return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + /** + * @param array $commands + */ + private function getColumnWidth(array $commands): int + { + $widths = []; + + foreach ($commands as $command) { + if ($command instanceof Command) { + $widths[] = Helper::width($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::width($alias); + } + } else { + $widths[] = Helper::width($command); + } + } + + return $widths ? max($widths) + 2 : 0; + } + + /** + * @param InputOption[] $options + */ + private function calculateTotalWidthForOptions(array $options): int + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName()); + if ($option->isNegatable()) { + $nameLength += 6 + Helper::width($option->getName()); // |--no- + name + } elseif ($option->acceptValue()) { + $valueLength = 1 + Helper::width($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000..8e44c88 --- /dev/null +++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + public function getCommandDocument(Command $command, bool $short = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + if ($short) { + foreach ($command->getAliases() as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + } else { + $command->mergeApplicationDefinition(false); + + foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + } + + return $dom; + } + + public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ('UNKNOWN' !== $application->getName()) { + $rootXml->setAttribute('name', $application->getName()); + if ('UNKNOWN' !== $application->getVersion()) { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace, true); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + protected function describeCommand(Command $command, array $options = []): void + { + $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false)); + } + + protected function describeApplication(Application $application, array $options = []): void + { + $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false)); + } + + /** + * Appends document children to parent node. + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + */ + private function writeDocument(\DOMDocument $dom): void + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + private function getInputArgumentDocument(InputArgument $argument): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : [])); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $dom; + } + + private function getInputOptionDocument(InputOption $option): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut() ?? '', '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if ($defaults) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + if ($option->isNegatable()) { + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--no-'.$option->getName()); + $objectXML->setAttribute('shortcut', ''); + $objectXML->setAttribute('accept_value', 0); + $objectXML->setAttribute('is_value_required', 0); + $objectXML->setAttribute('is_multiple', 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option')); + } + + return $dom; + } +} diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 0000000..0757a23 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleCommandEvent.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or executing code before the command is + * going to be executed. + * + * Changing the input arguments will have no effect. + * + * @author Fabien Potencier + */ +final class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + public const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + */ + private bool $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + */ + public function disableCommand(): bool + { + return $this->commandShouldRun = false; + } + + public function enableCommand(): bool + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + */ + public function commandShouldRun(): bool + { + return $this->commandShouldRun; + } +} diff --git a/vendor/symfony/console/Event/ConsoleErrorEvent.php b/vendor/symfony/console/Event/ConsoleErrorEvent.php new file mode 100644 index 0000000..1c0d626 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleErrorEvent.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle throwables thrown while running a command. + * + * @author Wouter de Jong + */ +final class ConsoleErrorEvent extends ConsoleEvent +{ + private int $exitCode; + + public function __construct( + InputInterface $input, + OutputInterface $output, + private \Throwable $error, + ?Command $command = null, + ) { + parent::__construct($command, $input, $output); + } + + public function getError(): \Throwable + { + return $this->error; + } + + public function setError(\Throwable $error): void + { + $this->error = $error; + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + + $r = new \ReflectionProperty($this->error, 'code'); + $r->setValue($this->error, $this->exitCode); + } + + public function getExitCode(): int + { + return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); + } +} diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 0000000..2f9f077 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleEvent.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato + */ +class ConsoleEvent extends Event +{ + public function __construct( + protected ?Command $command, + private InputInterface $input, + private OutputInterface $output, + ) { + } + + /** + * Gets the command that is executed. + */ + public function getCommand(): ?Command + { + return $this->command; + } + + /** + * Gets the input instance. + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Gets the output instance. + */ + public function getOutput(): OutputInterface + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Event/ConsoleSignalEvent.php b/vendor/symfony/console/Event/ConsoleSignalEvent.php new file mode 100644 index 0000000..b27f08a --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleSignalEvent.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author marie + */ +final class ConsoleSignalEvent extends ConsoleEvent +{ + public function __construct( + Command $command, + InputInterface $input, + OutputInterface $output, + private int $handlingSignal, + private int|false $exitCode = 0, + ) { + parent::__construct($command, $input, $output); + } + + public function getHandlingSignal(): int + { + return $this->handlingSignal; + } + + public function setExitCode(int $exitCode): void + { + if ($exitCode < 0 || $exitCode > 255) { + throw new \InvalidArgumentException('Exit code must be between 0 and 255.'); + } + + $this->exitCode = $exitCode; + } + + public function abortExit(): void + { + $this->exitCode = false; + } + + public function getExitCode(): int|false + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 0000000..38f7253 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato + * @author Jules Pietri + */ +final class ConsoleTerminateEvent extends ConsoleEvent +{ + public function __construct( + Command $command, + InputInterface $input, + OutputInterface $output, + private int $exitCode, + private readonly ?int $interruptingSignal = null, + ) { + parent::__construct($command, $input, $output); + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + } + + public function getExitCode(): int + { + return $this->exitCode; + } + + public function getInterruptingSignal(): ?int + { + return $this->interruptingSignal; + } +} diff --git a/vendor/symfony/console/EventListener/ErrorListener.php b/vendor/symfony/console/EventListener/ErrorListener.php new file mode 100644 index 0000000..49915a4 --- /dev/null +++ b/vendor/symfony/console/EventListener/ErrorListener.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @author James Halsall + * @author Robin Chalas + */ +class ErrorListener implements EventSubscriberInterface +{ + public function __construct( + private ?LoggerInterface $logger = null, + ) { + } + + public function onConsoleError(ConsoleErrorEvent $event): void + { + if (null === $this->logger) { + return; + } + + $error = $event->getError(); + + if (!$inputString = $this->getInputString($event)) { + $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); + + return; + } + + $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event): void + { + if (null === $this->logger) { + return; + } + + $exitCode = $event->getExitCode(); + + if (0 === $exitCode) { + return; + } + + if (!$inputString = $this->getInputString($event)) { + $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); + + return; + } + + $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', -128], + ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128], + ]; + } + + private static function getInputString(ConsoleEvent $event): ?string + { + $commandName = $event->getCommand()?->getName(); + $input = $event->getInput(); + + if ($input instanceof \Stringable) { + if ($commandName) { + return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); + } + + return (string) $input; + } + + return $commandName; + } +} diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php new file mode 100644 index 0000000..246f04f --- /dev/null +++ b/vendor/symfony/console/Exception/CommandNotFoundException.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect command name typed in the console. + * + * @author Jérôme Tamarelle + */ +class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ + /** + * @param string $message Exception message to throw + * @param string[] $alternatives List of similar defined names + * @param int $code Exception code + * @param \Throwable|null $previous Previous exception used for the exception chaining + */ + public function __construct( + string $message, + private array $alternatives = [], + int $code = 0, + ?\Throwable $previous = null, + ) { + parent::__construct($message, $code, $previous); + } + + /** + * @return string[] + */ + public function getAlternatives(): array + { + return $this->alternatives; + } +} diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php new file mode 100644 index 0000000..1624e13 --- /dev/null +++ b/vendor/symfony/console/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * ExceptionInterface. + * + * @author Jérôme Tamarelle + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..07cc0b6 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php new file mode 100644 index 0000000..5cf6279 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidOptionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect option name or value typed in the console. + * + * @author Jérôme Tamarelle + */ +class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php new file mode 100644 index 0000000..fc37b8d --- /dev/null +++ b/vendor/symfony/console/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/MissingInputException.php b/vendor/symfony/console/Exception/MissingInputException.php new file mode 100644 index 0000000..04f02ad --- /dev/null +++ b/vendor/symfony/console/Exception/MissingInputException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents failure to read input from stdin. + * + * @author Gabriel Ostrolucký + */ +class MissingInputException extends RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/NamespaceNotFoundException.php b/vendor/symfony/console/Exception/NamespaceNotFoundException.php new file mode 100644 index 0000000..dd16e45 --- /dev/null +++ b/vendor/symfony/console/Exception/NamespaceNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect namespace typed in the console. + * + * @author Pierre du Plessis + */ +class NamespaceNotFoundException extends CommandNotFoundException +{ +} diff --git a/vendor/symfony/console/Exception/RunCommandFailedException.php b/vendor/symfony/console/Exception/RunCommandFailedException.php new file mode 100644 index 0000000..5d87ec9 --- /dev/null +++ b/vendor/symfony/console/Exception/RunCommandFailedException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +use Symfony\Component\Console\Messenger\RunCommandContext; + +/** + * @author Kevin Bond + */ +final class RunCommandFailedException extends RuntimeException +{ + public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context) + { + parent::__construct( + $exception instanceof \Throwable ? $exception->getMessage() : $exception, + $exception instanceof \Throwable ? $exception->getCode() : 0, + $exception instanceof \Throwable ? $exception : null, + ); + } +} diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php new file mode 100644 index 0000000..51d7d80 --- /dev/null +++ b/vendor/symfony/console/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Formatter/NullOutputFormatter.php b/vendor/symfony/console/Formatter/NullOutputFormatter.php new file mode 100644 index 0000000..5c11c76 --- /dev/null +++ b/vendor/symfony/console/Formatter/NullOutputFormatter.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatter implements OutputFormatterInterface +{ + private NullOutputFormatterStyle $style; + + public function format(?string $message): ?string + { + return null; + } + + public function getStyle(string $name): OutputFormatterStyleInterface + { + // to comply with the interface we must return a OutputFormatterStyleInterface + return $this->style ??= new NullOutputFormatterStyle(); + } + + public function hasStyle(string $name): bool + { + return false; + } + + public function isDecorated(): bool + { + return false; + } + + public function setDecorated(bool $decorated): void + { + // do nothing + } + + public function setStyle(string $name, OutputFormatterStyleInterface $style): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php new file mode 100644 index 0000000..06fa6e4 --- /dev/null +++ b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatterStyle implements OutputFormatterStyleInterface +{ + public function apply(string $text): string + { + return $text; + } + + public function setBackground(?string $color): void + { + // do nothing + } + + public function setForeground(?string $color): void + { + // do nothing + } + + public function setOption(string $option): void + { + // do nothing + } + + public function setOptions(array $options): void + { + // do nothing + } + + public function unsetOption(string $option): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 0000000..8e81e59 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +use function Symfony\Component\String\b; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + * @author Roland Franssen + */ +class OutputFormatter implements WrappableOutputFormatterInterface +{ + private bool $decorated; + private array $styles = []; + private OutputFormatterStyleStack $styleStack; + + public function __clone() + { + $this->styleStack = clone $this->styleStack; + foreach ($this->styles as $key => $value) { + $this->styles[$key] = clone $value; + } + } + + /** + * Escapes "<" and ">" special chars in given text. + */ + public static function escape(string $text): string + { + $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); + + return self::escapeTrailingBackslash($text); + } + + /** + * Escapes trailing "\" in given text. + * + * @internal + */ + public static function escapeTrailingBackslash(string $text): string + { + if (str_ends_with($text, '\\')) { + $len = \strlen($text); + $text = rtrim($text, '\\'); + $text = str_replace("\0", '', $text); + $text .= str_repeat("\0", $len - \strlen($text)); + } + + return $text; + } + + /** + * Initializes console output formatter. + * + * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances + */ + public function __construct(bool $decorated = false, array $styles = []) + { + $this->decorated = $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + public function setDecorated(bool $decorated): void + { + $this->decorated = $decorated; + } + + public function isDecorated(): bool + { + return $this->decorated; + } + + public function setStyle(string $name, OutputFormatterStyleInterface $style): void + { + $this->styles[strtolower($name)] = $style; + } + + public function hasStyle(string $name): bool + { + return isset($this->styles[strtolower($name)]); + } + + public function getStyle(string $name): OutputFormatterStyleInterface + { + if (!$this->hasStyle($name)) { + throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); + } + + return $this->styles[strtolower($name)]; + } + + public function format(?string $message): ?string + { + return $this->formatAndWrap($message, 0); + } + + public function formatAndWrap(?string $message, int $width): string + { + if (null === $message) { + return ''; + } + + $offset = 0; + $output = ''; + $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + $closeTagRegex = '[a-z][^<>]*+'; + $currentLineLength = 0; + preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); + $offset = $pos + \strlen($text); + + // opening tag? + if ($open = '/' !== $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = $matches[3][$i][0] ?? ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (null === $style = $this->createStyleFromString($tag)) { + $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); + + return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); + } + + public function getStyleStack(): OutputFormatterStyleStack + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + */ + private function createStyleFromString(string $string): ?OutputFormatterStyleInterface + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { + return null; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + $match[0] = strtolower($match[0]); + + if ('fg' == $match[0]) { + $style->setForeground(strtolower($match[1])); + } elseif ('bg' == $match[0]) { + $style->setBackground(strtolower($match[1])); + } elseif ('href' === $match[0]) { + $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); + $style->setHref($url); + } elseif ('options' === $match[0]) { + preg_match_all('([^,;]+)', strtolower($match[1]), $options); + $options = array_shift($options); + foreach ($options as $option) { + $style->setOption($option); + } + } else { + return null; + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + */ + private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string + { + if ('' === $text) { + return ''; + } + + if (!$width) { + return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text; + } + + if (!$currentLineLength && '' !== $current) { + $text = ltrim($text); + } + + if ($currentLineLength) { + $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n"; + $text = substr($text, $i); + } else { + $prefix = ''; + } + + preg_match('~(\\n)$~', $text, $matches); + $text = $prefix.$this->addLineBreaks($text, $width); + $text = rtrim($text, "\n").($matches[1] ?? ''); + + if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) { + $text = "\n".$text; + } + + $lines = explode("\n", $text); + + foreach ($lines as $line) { + $currentLineLength += \strlen($line); + if ($width <= $currentLineLength) { + $currentLineLength = 0; + } + } + + if ($this->isDecorated()) { + foreach ($lines as $i => $line) { + $lines[$i] = $this->styleStack->getCurrent()->apply($line); + } + } + + return implode("\n", $lines); + } + + private function addLineBreaks(string $text, int $width): string + { + $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8'; + + return b($text)->toCodePointString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 0000000..947347f --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated): void; + + /** + * Whether the output will decorate messages. + */ + public function isDecorated(): bool; + + /** + * Sets a new style. + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style): void; + + /** + * Checks if output formatter has style with specified name. + */ + public function hasStyle(string $name): bool; + + /** + * Gets style options from style with specified name. + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle(string $name): OutputFormatterStyleInterface; + + /** + * Formats a message according to the given styles. + */ + public function format(?string $message): ?string; +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 0000000..20a65b5 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Color; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private Color $color; + private string $foreground; + private string $background; + private array $options; + private ?string $href = null; + private bool $handlesHrefGracefully; + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $background The style background color name + */ + public function __construct(?string $foreground = null, ?string $background = null, array $options = []) + { + $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); + } + + public function setForeground(?string $color): void + { + $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); + } + + public function setBackground(?string $color): void + { + $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); + } + + public function setHref(string $url): void + { + $this->href = $url; + } + + public function setOption(string $option): void + { + $this->options[] = $option; + $this->color = new Color($this->foreground, $this->background, $this->options); + } + + public function unsetOption(string $option): void + { + $pos = array_search($option, $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + + $this->color = new Color($this->foreground, $this->background, $this->options); + } + + public function setOptions(array $options): void + { + $this->color = new Color($this->foreground, $this->background, $this->options = $options); + } + + public function apply(string $text): string + { + $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) + && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); + + if (null !== $this->href && $this->handlesHrefGracefully) { + $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; + } + + return $this->color->apply($text); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 0000000..0374192 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + */ + public function setForeground(?string $color): void; + + /** + * Sets style background color. + */ + public function setBackground(?string $color): void; + + /** + * Sets some specific style option. + */ + public function setOption(string $option): void; + + /** + * Unsets some specific style option. + */ + public function unsetOption(string $option): void; + + /** + * Sets multiple style options at once. + */ + public function setOptions(array $options): void; + + /** + * Applies the style to a given text. + */ + public function apply(string $text): string; +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 0000000..4985213 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack implements ResetInterface +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private array $styles = []; + + private OutputFormatterStyleInterface $emptyStyle; + + public function __construct(?OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset(): void + { + $this->styles = []; + } + + /** + * Pushes a style in the stack. + */ + public function push(OutputFormatterStyleInterface $style): void + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @throws InvalidArgumentException When style tags incorrectly nested + */ + public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface + { + if (!$this->styles) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = \array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + */ + public function getCurrent(): OutputFormatterStyleInterface + { + if (!$this->styles) { + return $this->emptyStyle; + } + + return $this->styles[\count($this->styles) - 1]; + } + + /** + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + public function getEmptyStyle(): OutputFormatterStyleInterface + { + return $this->emptyStyle; + } +} diff --git a/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php new file mode 100644 index 0000000..412d997 --- /dev/null +++ b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output that supports word wrapping. + * + * @author Roland Franssen + */ +interface WrappableOutputFormatterInterface extends OutputFormatterInterface +{ + /** + * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). + */ + public function formatAndWrap(?string $message, int $width): string; +} diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 0000000..9ea7fb9 --- /dev/null +++ b/vendor/symfony/console/Helper/DebugFormatterHelper.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier + */ +class DebugFormatterHelper extends Helper +{ + private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default']; + private array $started = []; + private int $count = -1; + + /** + * Starts a debug formatting session. + */ + public function start(string $id, string $message, string $prefix = 'RUN'): string + { + $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + */ + public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + */ + public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + private function getBorder(string $id): string + { + return sprintf(' ', self::COLORS[$this->started[$id]['border']]); + } + + public function getName(): string + { + return 'debug_formatter'; + } +} diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 0000000..300c7b1 --- /dev/null +++ b/vendor/symfony/console/Helper/DescriptorHelper.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private array $descriptors = []; + + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ->register('rst', new ReStructuredTextDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @throws InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, ?object $object, array $options = []): void + { + $options = array_merge([ + 'raw_text' => false, + 'format' => 'txt', + ], $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * Registers a descriptor. + * + * @return $this + */ + public function register(string $format, DescriptorInterface $descriptor): static + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + public function getName(): string + { + return 'descriptor'; + } + + public function getFormats(): array + { + return array_keys($this->descriptors); + } +} diff --git a/vendor/symfony/console/Helper/Dumper.php b/vendor/symfony/console/Helper/Dumper.php new file mode 100644 index 0000000..0cd01e6 --- /dev/null +++ b/vendor/symfony/console/Helper/Dumper.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Roland Franssen + */ +final class Dumper +{ + private \Closure $handler; + + public function __construct( + private OutputInterface $output, + private ?CliDumper $dumper = null, + private ?ClonerInterface $cloner = null, + ) { + if (class_exists(CliDumper::class)) { + $this->handler = function ($var): string { + $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + $dumper->setColors($this->output->isDecorated()); + + return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + }; + } else { + $this->handler = fn ($var): string => match (true) { + null === $var => 'null', + true === $var => 'true', + false === $var => 'false', + \is_string($var) => '"'.$var.'"', + default => rtrim(print_r($var, true)), + }; + } + } + + public function __invoke(mixed $var): string + { + return ($this->handler)($var); + } +} diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 0000000..279e4c7 --- /dev/null +++ b/vendor/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + */ + public function formatSection(string $section, string $message, string $style = 'info'): string + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + */ + public function formatBlock(string|array $messages, string $style, bool $large = false): string + { + if (!\is_array($messages)) { + $messages = [$messages]; + } + + $len = 0; + $lines = []; + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max(self::width($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? [str_repeat(' ', $len)] : []; + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * Truncates a message to the given length. + */ + public function truncate(string $message, int $length, string $suffix = '...'): string + { + $computedLength = $length - self::width($suffix); + + if ($computedLength > self::width($message)) { + return $message; + } + + return self::substr($message, 0, $length).$suffix; + } + + public function getName(): string + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 0000000..de09006 --- /dev/null +++ b/vendor/symfony/console/Helper/Helper.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\String\UnicodeString; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected ?HelperSet $helperSet = null; + + public function setHelperSet(?HelperSet $helperSet): void + { + $this->helperSet = $helperSet; + } + + public function getHelperSet(): ?HelperSet + { + return $this->helperSet; + } + + /** + * Returns the width of a string, using mb_strwidth if it is available. + * The width is how many characters positions the string will use. + */ + public static function width(?string $string): int + { + $string ??= ''; + + if (preg_match('//u', $string)) { + return (new UnicodeString($string))->width(false); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + /** + * Returns the length of a string, using mb_strlen if it is available. + * The length is related to how many bytes the string will use. + */ + public static function length(?string $string): int + { + $string ??= ''; + + if (preg_match('//u', $string)) { + return (new UnicodeString($string))->length(); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strlen($string, $encoding); + } + + /** + * Returns the subset of a string, using mb_substr if it is available. + */ + public static function substr(?string $string, int $from, ?int $length = null): string + { + $string ??= ''; + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return substr($string, $from, $length); + } + + return mb_substr($string, $from, $length, $encoding); + } + + public static function formatTime(int|float $secs, int $precision = 1): string + { + $secs = (int) floor($secs); + + if (0 === $secs) { + return '< 1 sec'; + } + + static $timeFormats = [ + [1, '1 sec', 'secs'], + [60, '1 min', 'mins'], + [3600, '1 hr', 'hrs'], + [86400, '1 day', 'days'], + ]; + + $times = []; + foreach ($timeFormats as $index => $format) { + $seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs; + + if (isset($times[$index - $precision])) { + unset($times[$index - $precision]); + } + + if (0 === $seconds) { + continue; + } + + $unitCount = ($seconds / $format[0]); + $times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2]; + + if ($secs === $seconds) { + break; + } + + $secs -= $seconds; + } + + return implode(', ', array_reverse($times)); + } + + public static function formatMemory(int $memory): string + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string): string + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string ?? ''); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string ?? ''); + // remove terminal hyperlinks + $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? ''); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 0000000..8c4da3c --- /dev/null +++ b/vendor/symfony/console/Helper/HelperInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + */ + public function setHelperSet(?HelperSet $helperSet): void; + + /** + * Gets the helper set associated with this helper. + */ + public function getHelperSet(): ?HelperSet; + + /** + * Returns the canonical name of this helper. + */ + public function getName(): string; +} diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 0000000..30df9f9 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperSet.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class HelperSet implements \IteratorAggregate +{ + /** @var array */ + private array $helpers = []; + + /** + * @param HelperInterface[] $helpers + */ + public function __construct(array $helpers = []) + { + foreach ($helpers as $alias => $helper) { + $this->set($helper, \is_int($alias) ? null : $alias); + } + } + + public function set(HelperInterface $helper, ?string $alias = null): void + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + */ + public function has(string $name): bool + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @throws InvalidArgumentException if the helper is not defined + */ + public function get(string $name): HelperInterface + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 0000000..47126bd --- /dev/null +++ b/vendor/symfony/console/Helper/InputAwareHelper.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected InputInterface $input; + + public function setInput(InputInterface $input): void + { + $this->input = $input; + } +} diff --git a/vendor/symfony/console/Helper/OutputWrapper.php b/vendor/symfony/console/Helper/OutputWrapper.php new file mode 100644 index 0000000..0ea2b70 --- /dev/null +++ b/vendor/symfony/console/Helper/OutputWrapper.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow + * answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN). + * + * (?: + * # -- Words/Characters + * ( # (1 start) + * (?> # Atomic Group - Match words with valid breaks + * .{1,16} # 1-N characters + * # Followed by one of 4 prioritized, non-linebreak whitespace + * (?: # break types: + * (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace + * [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace ) + * | (?= \r? \n ) # 2. - Ahead a linebreak + * | $ # 3. - EOS + * | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace + * ) + * ) # End atomic group + * | + * .{1,16} # No valid word breaks, just break on the N'th character + * ) # (1 end) + * (?: \r? \n )? # Optional linebreak after Words/Characters + * | + * # -- Or, Linebreak + * (?: \r? \n | $ ) # Stand alone linebreak or at EOS + * ) + * + * @author Krisztián Ferenczi + * + * @see https://stackoverflow.com/a/20434776/1476819 + */ +final class OutputWrapper +{ + private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+'; + private const URL_PATTERN = 'https?://\S+'; + + public function __construct( + private bool $allowCutUrls = false, + ) { + } + + public function wrap(string $text, int $width, string $break = "\n"): string + { + if (!$width) { + return $text; + } + + $tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT); + $limitPattern = "{1,$width}"; + $patternBlocks = [$tagPattern]; + if (!$this->allowCutUrls) { + $patternBlocks[] = self::URL_PATTERN; + } + $patternBlocks[] = '.'; + $blocks = implode('|', $patternBlocks); + $rowPattern = "(?:$blocks)$limitPattern"; + $pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern); + $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break); + + return str_replace(' '.$break, $break, $output); + } +} diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 0000000..3ef6f71 --- /dev/null +++ b/vendor/symfony/console/Helper/ProcessHelper.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier + * + * @final + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param array|Process $cmd An instance of Process or an array of the command and arguments + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + */ + public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process + { + if (!class_exists(Process::class)) { + throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); + } + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if ($cmd instanceof Process) { + $cmd = [$cmd]; + } + + if (\is_string($cmd[0] ?? null)) { + $process = new Process($cmd); + $cmd = []; + } elseif (($cmd[0] ?? null) instanceof Process) { + $process = $cmd[0]; + unset($cmd[0]); + } else { + throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__)); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback, $cmd); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('%s', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param array|Process $cmd An instance of Process or a command to run + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + */ + public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + return function ($type, $buffer) use ($output, $process, $callback, $formatter) { + $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + $callback($type, $buffer); + } + }; + } + + private function escapeString(string $str): string + { + return str_replace('<', '\\<', $str); + } + + public function getName(): string + { + return 'process'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 0000000..7c22b7d --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,645 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Cursor; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Terminal; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier + * @author Chris Jones + */ +final class ProgressBar +{ + public const FORMAT_VERBOSE = 'verbose'; + public const FORMAT_VERY_VERBOSE = 'very_verbose'; + public const FORMAT_DEBUG = 'debug'; + public const FORMAT_NORMAL = 'normal'; + + private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax'; + private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax'; + private const FORMAT_DEBUG_NOMAX = 'debug_nomax'; + private const FORMAT_NORMAL_NOMAX = 'normal_nomax'; + + private int $barWidth = 28; + private string $barChar; + private string $emptyBarChar = '-'; + private string $progressChar = '>'; + private ?string $format = null; + private ?string $internalFormat = null; + private ?int $redrawFreq = 1; + private int $writeCount = 0; + private float $lastWriteTime = 0; + private float $minSecondsBetweenRedraws = 0; + private float $maxSecondsBetweenRedraws = 1; + private OutputInterface $output; + private int $step = 0; + private int $startingStep = 0; + private ?int $max = null; + private int $startTime; + private int $stepWidth; + private float $percent = 0.0; + private array $messages = []; + private bool $overwrite = true; + private Terminal $terminal; + private ?string $previousMessage = null; + private Cursor $cursor; + private array $placeholders = []; + + private static array $formatters; + private static array $formats; + + /** + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + $this->terminal = new Terminal(); + + if (0 < $minSecondsBetweenRedraws) { + $this->redrawFreq = null; + $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws; + } + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + // set a reasonable redraw frequency so output isn't flooded + $this->redrawFreq = null; + } + + $this->startTime = time(); + $this->cursor = new Cursor($output); + } + + /** + * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable(ProgressBar):string $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + self::$formatters ??= self::initPlaceholderFormatters(); + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + */ + public static function getPlaceholderFormatterDefinition(string $name): ?callable + { + self::$formatters ??= self::initPlaceholderFormatters(); + + return self::$formatters[$name] ?? null; + } + + /** + * Sets a placeholder formatter for a given name, for this instance only. + * + * @param callable(ProgressBar):string $callable A PHP callable + */ + public function setPlaceholderFormatter(string $name, callable $callable): void + { + $this->placeholders[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + */ + public function getPlaceholderFormatter(string $name): ?callable + { + return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name); + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition(string $name, string $format): void + { + self::$formats ??= self::initFormats(); + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + */ + public static function getFormatDefinition(string $name): ?string + { + self::$formats ??= self::initFormats(); + + return self::$formats[$name] ?? null; + } + + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ + public function setMessage(string $message, string $name = 'message'): void + { + $this->messages[$name] = $message; + } + + public function getMessage(string $name = 'message'): ?string + { + return $this->messages[$name] ?? null; + } + + public function getStartTime(): int + { + return $this->startTime; + } + + public function getMaxSteps(): int + { + return $this->max ?? 0; + } + + public function getProgress(): int + { + return $this->step; + } + + private function getStepWidth(): int + { + return $this->stepWidth; + } + + public function getProgressPercent(): float + { + return $this->percent; + } + + public function getBarOffset(): float + { + return floor(null !== $this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); + } + + public function getEstimated(): float + { + if (0 === $this->step || $this->step === $this->startingStep) { + return 0; + } + + return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max); + } + + public function getRemaining(): float + { + if (!$this->step) { + return 0; + } + + return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step)); + } + + public function setBarWidth(int $size): void + { + $this->barWidth = max(1, $size); + } + + public function getBarWidth(): int + { + return $this->barWidth; + } + + public function setBarCharacter(string $char): void + { + $this->barChar = $char; + } + + public function getBarCharacter(): string + { + return $this->barChar ?? (null !== $this->max ? '=' : $this->emptyBarChar); + } + + public function setEmptyBarCharacter(string $char): void + { + $this->emptyBarChar = $char; + } + + public function getEmptyBarCharacter(): string + { + return $this->emptyBarChar; + } + + public function setProgressCharacter(string $char): void + { + $this->progressChar = $char; + } + + public function getProgressCharacter(): string + { + return $this->progressChar; + } + + public function setFormat(string $format): void + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|null $freq The frequency in steps + */ + public function setRedrawFrequency(?int $freq): void + { + $this->redrawFreq = null !== $freq ? max(1, $freq) : null; + } + + public function minSecondsBetweenRedraws(float $seconds): void + { + $this->minSecondsBetweenRedraws = $seconds; + } + + public function maxSecondsBetweenRedraws(float $seconds): void + { + $this->maxSecondsBetweenRedraws = $seconds; + } + + /** + * Returns an iterator that will automatically update the progress bar when iterated. + * + * @template TKey + * @template TValue + * + * @param iterable $iterable + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + * + * @return iterable + */ + public function iterate(iterable $iterable, ?int $max = null): iterable + { + if (0 === $max) { + $max = null; + } + + $max ??= is_countable($iterable) ? \count($iterable) : null; + + if (0 === $max) { + $this->max = 0; + $this->stepWidth = 2; + $this->finish(); + + return; + } + + $this->start($max); + + foreach ($iterable as $key => $value) { + yield $key => $value; + + $this->advance(); + } + + $this->finish(); + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + * @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar) + */ + public function start(?int $max = null, int $startAt = 0): void + { + $this->startTime = time(); + $this->step = $startAt; + $this->startingStep = $startAt; + + $startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function advance(int $step = 1): void + { + $this->setProgress($this->step + $step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + */ + public function setOverwrite(bool $overwrite): void + { + $this->overwrite = $overwrite; + } + + public function setProgress(int $step): void + { + if ($this->max && $step > $this->max) { + $this->max = $step; + } elseif ($step < 0) { + $step = 0; + } + + $redrawFreq = $this->redrawFreq ?? (($this->max ?? 10) / 10); + $prevPeriod = $redrawFreq ? (int) ($this->step / $redrawFreq) : 0; + $currPeriod = $redrawFreq ? (int) ($step / $redrawFreq) : 0; + $this->step = $step; + $this->percent = match ($this->max) { + null => 0, + 0 => 1, + default => (float) $this->step / $this->max, + }; + $timeInterval = microtime(true) - $this->lastWriteTime; + + // Draw regardless of other limits + if ($this->max === $step) { + $this->display(); + + return; + } + + // Throttling + if ($timeInterval < $this->minSecondsBetweenRedraws) { + return; + } + + // Draw each step period, but not too late + if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) { + $this->display(); + } + } + + public function setMaxSteps(?int $max): void + { + if (0 === $max) { + $max = null; + } + + $this->format = null; + if (null === $max) { + $this->max = null; + $this->stepWidth = 4; + } else { + $this->max = max(0, $max); + $this->stepWidth = Helper::width((string) $this->max); + } + } + + /** + * Finishes the progress output. + */ + public function finish(): void + { + if (null === $this->max) { + $this->max = $this->step; + } + + if (($this->step === $this->max || null === $this->max) && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max ?? $this->step); + } + + /** + * Outputs the current progress string. + */ + public function display(): void + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite($this->buildLine()); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear(): void + { + if (!$this->overwrite) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite(''); + } + + private function setRealFormat(string $format): void + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message): void + { + if ($this->previousMessage === $message) { + return; + } + + $originalMessage = $message; + + if ($this->overwrite) { + if (null !== $this->previousMessage) { + if ($this->output instanceof ConsoleSectionOutput) { + $messageLines = explode("\n", $this->previousMessage); + $lineCount = \count($messageLines); + foreach ($messageLines as $messageLine) { + $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine)); + if ($messageLineLength > $this->terminal->getWidth()) { + $lineCount += floor($messageLineLength / $this->terminal->getWidth()); + } + } + $this->output->clear($lineCount); + } else { + $lineCount = substr_count($this->previousMessage, "\n"); + for ($i = 0; $i < $lineCount; ++$i) { + $this->cursor->moveToColumn(1); + $this->cursor->clearLine(); + $this->cursor->moveUp(); + } + + $this->cursor->moveToColumn(1); + $this->cursor->clearLine(); + } + } + } elseif ($this->step > 0) { + $message = \PHP_EOL.$message; + } + + $this->previousMessage = $originalMessage; + $this->lastWriteTime = microtime(true); + + $this->output->write($message); + ++$this->writeCount; + } + + private function determineBestFormat(): string + { + return match ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX, + OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX, + OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX, + default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX, + }; + } + + private static function initPlaceholderFormatters(): array + { + return [ + 'bar' => function (self $bar, OutputInterface $output) { + $completeBars = $bar->getBarOffset(); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter())); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2), + 'remaining' => function (self $bar) { + if (null === $bar->getMaxSteps()) { + throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + return Helper::formatTime($bar->getRemaining(), 2); + }, + 'estimated' => function (self $bar) { + if (null === $bar->getMaxSteps()) { + throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + return Helper::formatTime($bar->getEstimated(), 2); + }, + 'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)), + 'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT), + 'max' => fn (self $bar) => $bar->getMaxSteps(), + 'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100), + ]; + } + + private static function initFormats(): array + { + return [ + self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%', + self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]', + + self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', + + self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', + + self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ]; + } + + private function buildLine(): string + { + \assert(null !== $this->format); + + $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + $callback = function ($matches) { + if ($formatter = $this->getPlaceholderFormatter($matches[1])) { + $text = $formatter($this, $this->output); + } elseif (isset($this->messages[$matches[1]])) { + $text = $this->messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }; + $line = preg_replace_callback($regex, $callback, $this->format); + + // gets string length for each sub line with multiline format + $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line)); + + $linesWidth = max($linesLength); + + $terminalWidth = $this->terminal->getWidth(); + if ($linesWidth <= $terminalWidth) { + return $line; + } + + $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); + + return preg_replace_callback($regex, $callback, $this->format); + } +} diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php new file mode 100644 index 0000000..969d835 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressIndicator.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Kevin Bond + */ +class ProgressIndicator +{ + private const FORMATS = [ + 'normal' => ' %indicator% %message%', + 'normal_no_ansi' => ' %message%', + + 'verbose' => ' %indicator% %message% (%elapsed:6s%)', + 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', + + 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', + 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', + ]; + + private int $startTime; + private ?string $format = null; + private ?string $message = null; + private array $indicatorValues; + private int $indicatorCurrent; + private float $indicatorUpdateTime; + private bool $started = false; + + /** + * @var array + */ + private static array $formatters; + + /** + * @param int $indicatorChangeInterval Change interval in milliseconds + * @param array|null $indicatorValues Animated indicator characters + */ + public function __construct( + private OutputInterface $output, + ?string $format = null, + private int $indicatorChangeInterval = 100, + ?array $indicatorValues = null, + ) { + + $format ??= $this->determineBestFormat(); + $indicatorValues ??= ['-', '\\', '|', '/']; + $indicatorValues = array_values($indicatorValues); + + if (2 > \count($indicatorValues)) { + throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); + } + + $this->format = self::getFormatDefinition($format); + $this->indicatorValues = $indicatorValues; + $this->startTime = time(); + } + + /** + * Sets the current indicator message. + */ + public function setMessage(?string $message): void + { + $this->message = $message; + + $this->display(); + } + + /** + * Starts the indicator output. + */ + public function start(string $message): void + { + if ($this->started) { + throw new LogicException('Progress indicator already started.'); + } + + $this->message = $message; + $this->started = true; + $this->startTime = time(); + $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; + $this->indicatorCurrent = 0; + + $this->display(); + } + + /** + * Advances the indicator. + */ + public function advance(): void + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + if (!$this->output->isDecorated()) { + return; + } + + $currentTime = $this->getCurrentTimeInMilliseconds(); + + if ($currentTime < $this->indicatorUpdateTime) { + return; + } + + $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; + ++$this->indicatorCurrent; + + $this->display(); + } + + /** + * Finish the indicator with message. + */ + public function finish(string $message): void + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + $this->message = $message; + $this->display(); + $this->output->writeln(''); + $this->started = false; + } + + /** + * Gets the format for a given name. + */ + public static function getFormatDefinition(string $name): ?string + { + return self::FORMATS[$name] ?? null; + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + self::$formatters ??= self::initPlaceholderFormatters(); + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name (including the delimiter char like %). + */ + public static function getPlaceholderFormatterDefinition(string $name): ?callable + { + self::$formatters ??= self::initPlaceholderFormatters(); + + return self::$formatters[$name] ?? null; + } + + private function display(): void + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) { + if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) { + return $formatter($this); + } + + return $matches[0]; + }, $this->format ?? '')); + } + + private function determineBestFormat(): string + { + return match ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi', + OutputInterface::VERBOSITY_VERY_VERBOSE, + OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi', + default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi', + }; + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message): void + { + if ($this->output->isDecorated()) { + $this->output->write("\x0D\x1B[2K"); + $this->output->write($message); + } else { + $this->output->writeln($message); + } + } + + private function getCurrentTimeInMilliseconds(): float + { + return round(microtime(true) * 1000); + } + + /** + * @return array + */ + private static function initPlaceholderFormatters(): array + { + return [ + 'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)], + 'message' => fn (self $indicator) => $indicator->message, + 'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2), + 'memory' => fn () => Helper::formatMemory(memory_get_usage(true)), + ]; + } +} diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 0000000..54825c6 --- /dev/null +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,589 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Cursor; +use Symfony\Component\Console\Exception\MissingInputException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +use function Symfony\Component\String\s; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class QuestionHelper extends Helper +{ + private static bool $stty = true; + private static bool $stdinIsInteractive; + + /** + * Asks a question to the user. + * + * @return mixed The user answer + * + * @throws RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + return $this->getDefaultAnswer($question); + } + + $inputStream = $input instanceof StreamableInputInterface ? $input->getStream() : null; + $inputStream ??= STDIN; + + try { + if (!$question->getValidator()) { + return $this->doAsk($inputStream, $output, $question); + } + + $interviewer = fn () => $this->doAsk($inputStream, $output, $question); + + return $this->validateAttempts($interviewer, $output, $question); + } catch (MissingInputException $exception) { + $input->setInteractive(false); + + if (null === $fallbackOutput = $this->getDefaultAnswer($question)) { + throw $exception; + } + + return $fallbackOutput; + } + } + + public function getName(): string + { + return 'question'; + } + + /** + * Prevents usage of stty. + */ + public static function disableStty(): void + { + self::$stty = false; + } + + /** + * Asks the question to the user. + * + * @param resource $inputStream + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function doAsk($inputStream, OutputInterface $output, Question $question): mixed + { + $this->writePrompt($output, $question); + + $autocomplete = $question->getAutocompleterCallback(); + + if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable()); + $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse; + } catch (RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true; + + if (!$isBlocked) { + stream_set_blocking($inputStream, true); + } + + $ret = $this->readInput($inputStream, $question); + + if (!$isBlocked) { + stream_set_blocking($inputStream, false); + } + + if (false === $ret) { + throw new MissingInputException('Aborted.'); + } + if ($question->isTrimmable()) { + $ret = trim($ret); + } + } + } else { + $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete); + $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete; + } + + if ($output instanceof ConsoleSectionOutput) { + $output->addContent(''); // add EOL to the question + $output->addContent($ret); + } + + $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function getDefaultAnswer(Question $question): mixed + { + $default = $question->getDefault(); + + if (null === $default) { + return $default; + } + + if ($validator = $question->getValidator()) { + return \call_user_func($validator, $default); + } elseif ($question instanceof ChoiceQuestion) { + $choices = $question->getChoices(); + + if (!$question->isMultiselect()) { + return $choices[$default] ?? $default; + } + + $default = explode(',', $default); + foreach ($default as $k => $v) { + $v = $question->isTrimmable() ? trim($v) : $v; + $default[$k] = $choices[$v] ?? $v; + } + } + + return $default; + } + + /** + * Outputs the question prompt. + */ + protected function writePrompt(OutputInterface $output, Question $question): void + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $output->writeln(array_merge([ + $question->getQuestion(), + ], $this->formatChoiceQuestionChoices($question, 'info'))); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * @return string[] + */ + protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array + { + $messages = []; + + $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices()))); + + foreach ($choices as $key => $value) { + $padding = str_repeat(' ', $maxWidth - self::width($key)); + + $messages[] = sprintf(" [<$tag>%s$padding] %s", $key, $value); + } + + return $messages; + } + + /** + * Outputs an error message. + */ + protected function writeError(OutputInterface $output, \Exception $error): void + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = ''.$error->getMessage().''; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param resource $inputStream + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string + { + $cursor = new Cursor($output, $inputStream); + + $fullChoice = ''; + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + + $sttyMode = shell_exec('stty -g'); + $isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null); + $r = [$inputStream]; + $w = []; + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) { + // Give signal handlers a chance to run + $r = [$inputStream]; + } + $c = fread($inputStream, 1); + + // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. + if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { + shell_exec('stty '.$sttyMode); + throw new MissingInputException('Aborted.'); + } elseif ("\177" === $c) { // Backspace Character + if (0 === $numMatches && 0 !== $i) { + --$i; + $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false)); + + $fullChoice = self::substr($fullChoice, 0, $i); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = self::substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (\ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = (string) $matches[$ofs]; + // Echo out remaining chars for current match + $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); + $output->write($remainingCharacters); + $fullChoice .= $remainingCharacters; + $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding); + + $matches = array_filter( + $autocomplete($ret), + fn ($match) => '' === $ret || str_starts_with($match, $ret) + ); + $numMatches = \count($matches); + $ofs = -1; + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + if ("\x80" <= $c) { + $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]); + } + + $output->write($c); + $ret .= $c; + $fullChoice .= $c; + ++$i; + + $tempRet = $ret; + + if ($question instanceof ChoiceQuestion && $question->isMultiselect()) { + $tempRet = $this->mostRecentlyEnteredValue($fullChoice); + } + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete($ret) as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (str_starts_with($value, $tempRet)) { + $matches[$numMatches++] = $value; + } + } + } + + $cursor->clearLineAfter(); + + if ($numMatches > 0 && -1 !== $ofs) { + $cursor->savePosition(); + // Write highlighted text, complete the partially entered response + $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); + $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).''); + $cursor->restorePosition(); + } + } + + // Reset stty so it behaves normally again + shell_exec('stty '.$sttyMode); + + return $fullChoice; + } + + private function mostRecentlyEnteredValue(string $entered): string + { + // Determine the most recent value that the user entered + if (!str_contains($entered, ',')) { + return $entered; + } + + $choices = explode(',', $entered); + if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) { + return $lastChoice; + } + + return $entered; + } + + /** + * Gets a hidden response from user. + * + * @param resource $inputStream The handler resource + * @param bool $trimmable Is the answer trimmable + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if (str_starts_with(__FILE__, 'phar:')) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $sExec = shell_exec('"'.$exe.'"'); + $value = $trimmable ? rtrim($sExec) : $sExec; + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if (self::$stty && Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + shell_exec('stty -echo'); + } elseif ($this->isInteractiveInput($inputStream)) { + throw new RuntimeException('Unable to hide the response.'); + } + + $value = fgets($inputStream, 4096); + + if (4095 === \strlen($value)) { + $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $errOutput->warning('The value was possibly truncated by your shell or terminal emulator'); + } + + if (self::$stty && Terminal::hasSttyAvailable()) { + shell_exec('stty '.$sttyMode); + } + + if (false === $value) { + throw new MissingInputException('Aborted.'); + } + if ($trimmable) { + $value = trim($value); + } + $output->writeln(''); + + return $value; + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed + { + $error = null; + $attempts = $question->getMaxAttempts(); + + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return $question->getValidator()($interviewer()); + } catch (RuntimeException $e) { + throw $e; + } catch (\Exception $error) { + } + } + + throw $error; + } + + private function isInteractiveInput($inputStream): bool + { + if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) { + return false; + } + + if (isset(self::$stdinIsInteractive)) { + return self::$stdinIsInteractive; + } + + return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r')); + } + + /** + * Reads one or more lines of input and returns what is read. + * + * @param resource $inputStream The handler resource + * @param Question $question The question being asked + */ + private function readInput($inputStream, Question $question): string|false + { + if (!$question->isMultiline()) { + $cp = $this->setIOCodepage(); + $ret = fgets($inputStream, 4096); + + return $this->resetIOCodepage($cp, $ret); + } + + $multiLineStreamReader = $this->cloneInputStream($inputStream); + if (null === $multiLineStreamReader) { + return false; + } + + $ret = ''; + $cp = $this->setIOCodepage(); + while (false !== ($char = fgetc($multiLineStreamReader))) { + if (\PHP_EOL === "{$ret}{$char}") { + break; + } + $ret .= $char; + } + + return $this->resetIOCodepage($cp, $ret); + } + + private function setIOCodepage(): int + { + if (\function_exists('sapi_windows_cp_set')) { + $cp = sapi_windows_cp_get(); + sapi_windows_cp_set(sapi_windows_cp_get('oem')); + + return $cp; + } + + return 0; + } + + /** + * Sets console I/O to the specified code page and converts the user input. + */ + private function resetIOCodepage(int $cp, string|false $input): string|false + { + if (0 !== $cp) { + sapi_windows_cp_set($cp); + + if (false !== $input && '' !== $input) { + $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input); + } + } + + return $input; + } + + /** + * Clones an input stream in order to act on one instance of the same + * stream without affecting the other instance. + * + * @param resource $inputStream The handler resource + * + * @return resource|null The cloned resource, null in case it could not be cloned + */ + private function cloneInputStream($inputStream) + { + $streamMetaData = stream_get_meta_data($inputStream); + $seekable = $streamMetaData['seekable'] ?? false; + $mode = $streamMetaData['mode'] ?? 'rb'; + $uri = $streamMetaData['uri'] ?? null; + + if (null === $uri) { + return null; + } + + $cloneStream = fopen($uri, $mode); + + // For seekable and writable streams, add all the same data to the + // cloned stream and then seek to the same offset. + if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) { + $offset = ftell($inputStream); + rewind($inputStream); + stream_copy_to_stream($inputStream, $cloneStream); + fseek($inputStream, $offset); + fseek($cloneStream, $offset); + } + + return $cloneStream; + } +} diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 0000000..48d947b --- /dev/null +++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + protected function writePrompt(OutputInterface $output, Question $question): void + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + if ($question->isMultiline()) { + $text .= sprintf(' (press %s to continue)', $this->getEofShortcut()); + } + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion && $question->isMultiselect(): + $choices = $question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default] ?? $default)); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); + } + + $output->writeln($text); + + $prompt = ' > '; + + if ($question instanceof ChoiceQuestion) { + $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); + + $prompt = $question->getPrompt(); + } + + $output->write($prompt); + } + + protected function writeError(OutputInterface $output, \Exception $error): void + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } + + private function getEofShortcut(): string + { + if ('Windows' === \PHP_OS_FAMILY) { + return 'Ctrl+Z then Enter'; + } + + return 'Ctrl+D'; + } +} diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php new file mode 100644 index 0000000..09709a2 --- /dev/null +++ b/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,924 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Abdellatif Ait boudad + * @author Max Grigorian + * @author Dany Maillard + */ +class Table +{ + private const SEPARATOR_TOP = 0; + private const SEPARATOR_TOP_BOTTOM = 1; + private const SEPARATOR_MID = 2; + private const SEPARATOR_BOTTOM = 3; + private const BORDER_OUTSIDE = 0; + private const BORDER_INSIDE = 1; + private const DISPLAY_ORIENTATION_DEFAULT = 'default'; + private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal'; + private const DISPLAY_ORIENTATION_VERTICAL = 'vertical'; + + private ?string $headerTitle = null; + private ?string $footerTitle = null; + private array $headers = []; + private array $rows = []; + private array $effectiveColumnWidths = []; + private int $numberOfColumns; + private TableStyle $style; + private array $columnStyles = []; + private array $columnWidths = []; + private array $columnMaxWidths = []; + private bool $rendered = false; + private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT; + + private static array $styles; + + public function __construct( + private OutputInterface $output, + ) { + self::$styles ??= self::initStyles(); + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + */ + public static function setStyleDefinition(string $name, TableStyle $style): void + { + self::$styles ??= self::initStyles(); + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + */ + public static function getStyleDefinition(string $name): TableStyle + { + self::$styles ??= self::initStyles(); + + return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @return $this + */ + public function setStyle(TableStyle|string $name): static + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + */ + public function getStyle(): TableStyle + { + return $this->style; + } + + /** + * Sets table column style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setColumnStyle(int $columnIndex, TableStyle|string $name): static + { + $this->columnStyles[$columnIndex] = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current style for a column. + * + * If style was not set, it returns the global table style. + */ + public function getColumnStyle(int $columnIndex): TableStyle + { + return $this->columnStyles[$columnIndex] ?? $this->getStyle(); + } + + /** + * Sets the minimum width of a column. + * + * @return $this + */ + public function setColumnWidth(int $columnIndex, int $width): static + { + $this->columnWidths[$columnIndex] = $width; + + return $this; + } + + /** + * Sets the minimum width of all columns. + * + * @return $this + */ + public function setColumnWidths(array $widths): static + { + $this->columnWidths = []; + foreach ($widths as $index => $width) { + $this->setColumnWidth($index, $width); + } + + return $this; + } + + /** + * Sets the maximum width of a column. + * + * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while + * formatted strings are preserved. + * + * @return $this + */ + public function setColumnMaxWidth(int $columnIndex, int $width): static + { + if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { + throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter()))); + } + + $this->columnMaxWidths[$columnIndex] = $width; + + return $this; + } + + /** + * @return $this + */ + public function setHeaders(array $headers): static + { + $headers = array_values($headers); + if ($headers && !\is_array($headers[0])) { + $headers = [$headers]; + } + + $this->headers = $headers; + + return $this; + } + + /** + * @return $this + */ + public function setRows(array $rows): static + { + $this->rows = []; + + return $this->addRows($rows); + } + + /** + * @return $this + */ + public function addRows(array $rows): static + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + /** + * @return $this + */ + public function addRow(TableSeparator|array $row): static + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + $this->rows[] = array_values($row); + + return $this; + } + + /** + * Adds a row to the table, and re-renders the table. + * + * @return $this + */ + public function appendRow(TableSeparator|array $row): static + { + if (!$this->output instanceof ConsoleSectionOutput) { + throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); + } + + if ($this->rendered) { + $this->output->clear($this->calculateRowCount()); + } + + $this->addRow($row); + $this->render(); + + return $this; + } + + /** + * @return $this + */ + public function setRow(int|string $column, array $row): static + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * @return $this + */ + public function setHeaderTitle(?string $title): static + { + $this->headerTitle = $title; + + return $this; + } + + /** + * @return $this + */ + public function setFooterTitle(?string $title): static + { + $this->footerTitle = $title; + + return $this; + } + + /** + * @return $this + */ + public function setHorizontal(bool $horizontal = true): static + { + $this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT; + + return $this; + } + + /** + * @return $this + */ + public function setVertical(bool $vertical = true): static + { + $this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render(): void + { + $divider = new TableSeparator(); + $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2; + + $horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation; + $vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation; + + $rows = []; + if ($horizontal) { + foreach ($this->headers[0] ?? [] as $i => $header) { + $rows[$i] = [$header]; + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + if (isset($row[$i])) { + $rows[$i][] = $row[$i]; + } elseif ($isCellWithColspan($rows[$i][0])) { + // Noop, there is a "title" + } else { + $rows[$i][] = null; + } + } + } + } elseif ($vertical) { + $formatter = $this->output->getFormatter(); + $maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0); + + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + if ($rows) { + $rows[] = [$divider]; + } + + $containsColspan = false; + foreach ($row as $cell) { + if ($containsColspan = $isCellWithColspan($cell)) { + break; + } + } + + $headers = $this->headers[0] ?? []; + $maxRows = max(\count($headers), \count($row)); + for ($i = 0; $i < $maxRows; ++$i) { + $cell = (string) ($row[$i] ?? ''); + + $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n"; + $parts = explode($eol, $cell); + foreach ($parts as $idx => $part) { + if ($headers && !$containsColspan) { + if (0 === $idx) { + $rows[] = [sprintf( + '%s%s: %s', + str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))), + $headers[$i] ?? '', + $part + )]; + } else { + $rows[] = [sprintf( + '%s %s', + str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT), + $part + )]; + } + } elseif ('' !== $cell) { + $rows[] = [$part]; + } + } + } + } + } else { + $rows = array_merge($this->headers, [$divider], $this->rows); + } + + $this->calculateNumberOfColumns($rows); + + $rowGroups = $this->buildTableRows($rows); + $this->calculateColumnsWidth($rowGroups); + + $isHeader = !$horizontal; + $isFirstRow = $horizontal; + $hasTitle = (bool) $this->headerTitle; + + foreach ($rowGroups as $rowGroup) { + $isHeaderSeparatorRendered = false; + + foreach ($rowGroup as $row) { + if ($divider === $row) { + $isHeader = false; + $isFirstRow = true; + + continue; + } + + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + + continue; + } + + if (!$row) { + continue; + } + + if ($isHeader && !$isHeaderSeparatorRendered) { + $this->renderRowSeparator( + self::SEPARATOR_TOP, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $hasTitle = false; + $isHeaderSeparatorRendered = true; + } + + if ($isFirstRow) { + $this->renderRowSeparator( + $horizontal ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $isFirstRow = false; + $hasTitle = false; + } + + if ($vertical) { + $isHeader = false; + $isFirstRow = false; + } + + if ($horizontal) { + $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); + } else { + $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); + } + } + } + $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); + + $this->cleanup(); + $this->rendered = true; + } + + /** + * Renders horizontal header separator. + * + * Example: + * + * +-----+-----------+-------+ + */ + private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void + { + if (!$count = $this->numberOfColumns) { + return; + } + + $borders = $this->style->getBorderChars(); + if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { + return; + } + + $crossings = $this->style->getCrossingChars(); + if (self::SEPARATOR_MID === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; + } elseif (self::SEPARATOR_TOP === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; + } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; + } else { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; + } + + $markup = $leftChar; + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); + $markup .= $column === $count - 1 ? $rightChar : $midChar; + } + + if (null !== $title) { + $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title))); + $markupLength = Helper::width($markup); + if ($titleLength > $limit = $markupLength - 4) { + $titleLength = $limit; + $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, ''))); + $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); + } + + $titleStart = intdiv($markupLength - $titleLength, 2); + if (false === mb_detect_encoding($markup, null, true)) { + $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); + } else { + $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); + } + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string + { + $borders = $this->style->getBorderChars(); + + return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); + } + + /** + * Renders table row. + * + * Example: + * + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + */ + private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void + { + $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); + $columns = $this->getRowColumns($row); + $last = \count($columns) - 1; + foreach ($columns as $i => $column) { + if ($firstCellFormat && 0 === $i) { + $rowContent .= $this->renderCell($row, $column, $firstCellFormat); + } else { + $rowContent .= $this->renderCell($row, $column, $cellFormat); + } + $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); + } + $this->output->writeln($rowContent); + } + + /** + * Renders table cell with padding. + */ + private function renderCell(array $row, int $column, string $cellFormat): string + { + $cell = $row[$column] ?? ''; + $width = $this->effectiveColumnWidths[$column]; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding($cell, null, true)) { + $width += \strlen($cell) - mb_strwidth($cell, $encoding); + } + + $style = $this->getColumnStyle($column); + + if ($cell instanceof TableSeparator) { + return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); + } + + $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell)); + $content = sprintf($style->getCellRowContentFormat(), $cell); + + $padType = $style->getPadType(); + if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) { + $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell); + if ($isNotStyledByTag) { + $cellFormat = $cell->getStyle()->getCellFormat(); + if (!\is_string($cellFormat)) { + $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';'); + $cellFormat = '<'.$tag.'>%s'; + } + + if (str_contains($content, '')) { + $content = str_replace('', '', $content); + $width -= 3; + } + if (str_contains($content, '')) { + $content = str_replace('', '', $content); + $width -= \strlen(''); + } + } + + $padType = $cell->getStyle()->getPadByAlign(); + } + + return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType)); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns(array $rows): void + { + $columns = [0]; + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + $this->numberOfColumns = max($columns); + } + + private function buildTableRows(array $rows): TableRows + { + /** @var WrappableOutputFormatterInterface $formatter */ + $formatter = $this->output->getFormatter(); + $unmergedRows = []; + for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; + + if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) { + $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); + } + if (!str_contains($cell ?? '', "\n")) { + continue; + } + $eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n"; + $escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell))); + $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; + $lines = explode($eol, str_replace($eol, ''.$eol, $cell)); + foreach ($lines as $lineKey => $line) { + if ($colspan > 1) { + $line = new TableCell($line, ['colspan' => $colspan]); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { + $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); + } + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + return new TableRows(function () use ($rows, $unmergedRows): \Traversable { + foreach ($rows as $rowKey => $row) { + $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; + + if (isset($unmergedRows[$rowKey])) { + foreach ($unmergedRows[$rowKey] as $row) { + $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); + } + } + yield $rowGroup; + } + }); + } + + private function calculateRowCount(): int + { + $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); + + if ($this->headers) { + ++$numberOfRows; // Add row for header separator + } + + if ($this->rows) { + ++$numberOfRows; // Add row for footer separator + } + + return $numberOfRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @throws InvalidArgumentException + */ + private function fillNextRows(array $rows, int $line): array + { + $unmergedRows = []; + foreach ($rows[$line] as $column => $cell) { + if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) { + throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell))); + } + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = [$cell]; + if (str_contains($cell, "\n")) { + $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n"; + $lines = explode($eol, str_replace($eol, ''.$eol.'', $cell)); + $nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = $lines[$unmergedRowKey - $line] ?? ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); + if ($nbLines === $unmergedRowKey - $line) { + break; + } + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRow) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if ($cell) { + $row[$column] = $cell; + } + } + array_splice($rows, $unmergedRowKey, 0, [$row]); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + */ + private function fillCells(iterable $row): iterable + { + $newRow = []; + + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } + } + } + + return $newRow ?: $row; + } + + private function copyRow(array $rows, int $line): array + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + */ + private function getNumberOfColumns(array $row): int + { + $columns = \count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + */ + private function getRowColumns(array $row): array + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Calculates columns widths. + */ + private function calculateColumnsWidth(iterable $groups): void + { + for ($column = 0; $column < $this->numberOfColumns; ++$column) { + $lengths = []; + foreach ($groups as $group) { + foreach ($group as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::width($textContent); + if ($textLength > 0) { + $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } + } + } + } + + $lengths[] = $this->getCellWidth($row, $column); + } + } + + $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2; + } + } + + private function getColumnSeparatorWidth(): int + { + return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); + } + + private function getCellWidth(array $row, int $column): int + { + $cellWidth = 0; + + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell)); + } + + $columnWidth = $this->columnWidths[$column] ?? 0; + $cellWidth = max($cellWidth, $columnWidth); + + return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup(): void + { + $this->effectiveColumnWidths = []; + unset($this->numberOfColumns); + } + + /** + * @return array + */ + private static function initStyles(): array + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChars('=') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChars('') + ->setVerticalBorderChars('') + ->setDefaultCrossingChar('') + ->setCellRowContentFormat('%s ') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChars('-') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + $box = (new TableStyle()) + ->setHorizontalBorderChars('─') + ->setVerticalBorderChars('│') + ->setCrossingChars('┼', '┌', '┬', 'â”', '┤', '┘', 'â”´', 'â””', '├') + ; + + $boxDouble = (new TableStyle()) + ->setHorizontalBorderChars('â•', '─') + ->setVerticalBorderChars('â•‘', '│') + ->setCrossingChars('┼', 'â•”', '╤', 'â•—', 'â•¢', 'â•', 'â•§', '╚', '╟', 'â• ', '╪', 'â•£') + ; + + return [ + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + 'box' => $box, + 'box-double' => $boxDouble, + ]; + } + + private function resolveStyle(TableStyle|string $name): TableStyle + { + if ($name instanceof TableStyle) { + return $name; + } + + return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 0000000..1c4eeea --- /dev/null +++ b/vendor/symfony/console/Helper/TableCell.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + private array $options = [ + 'rowspan' => 1, + 'colspan' => 1, + 'style' => null, + ]; + + public function __construct( + private string $value = '', + array $options = [], + ) { + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) { + throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".'); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + */ + public function __toString(): string + { + return $this->value; + } + + /** + * Gets number of colspan. + */ + public function getColspan(): int + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + */ + public function getRowspan(): int + { + return (int) $this->options['rowspan']; + } + + public function getStyle(): ?TableCellStyle + { + return $this->options['style']; + } +} diff --git a/vendor/symfony/console/Helper/TableCellStyle.php b/vendor/symfony/console/Helper/TableCellStyle.php new file mode 100644 index 0000000..49b97f8 --- /dev/null +++ b/vendor/symfony/console/Helper/TableCellStyle.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Yewhen Khoptynskyi + */ +class TableCellStyle +{ + public const DEFAULT_ALIGN = 'left'; + + private const TAG_OPTIONS = [ + 'fg', + 'bg', + 'options', + ]; + + private const ALIGN_MAP = [ + 'left' => \STR_PAD_RIGHT, + 'center' => \STR_PAD_BOTH, + 'right' => \STR_PAD_LEFT, + ]; + + private array $options = [ + 'fg' => 'default', + 'bg' => 'default', + 'options' => null, + 'align' => self::DEFAULT_ALIGN, + 'cellFormat' => null, + ]; + + public function __construct(array $options = []) + { + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) { + throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP)))); + } + + $this->options = array_merge($this->options, $options); + } + + public function getOptions(): array + { + return $this->options; + } + + /** + * Gets options we need for tag for example fg, bg. + * + * @return string[] + */ + public function getTagOptions(): array + { + return array_filter( + $this->getOptions(), + fn ($key) => \in_array($key, self::TAG_OPTIONS, true) && isset($this->options[$key]), + \ARRAY_FILTER_USE_KEY + ); + } + + public function getPadByAlign(): int + { + return self::ALIGN_MAP[$this->getOptions()['align']]; + } + + public function getCellFormat(): ?string + { + return $this->getOptions()['cellFormat']; + } +} diff --git a/vendor/symfony/console/Helper/TableRows.php b/vendor/symfony/console/Helper/TableRows.php new file mode 100644 index 0000000..fb2dc27 --- /dev/null +++ b/vendor/symfony/console/Helper/TableRows.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * @internal + */ +class TableRows implements \IteratorAggregate +{ + public function __construct( + private \Closure $generator, + ) { + } + + public function getIterator(): \Traversable + { + return ($this->generator)(); + } +} diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 0000000..e541c53 --- /dev/null +++ b/vendor/symfony/console/Helper/TableSeparator.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier + */ +class TableSeparator extends TableCell +{ + public function __construct(array $options = []) + { + parent::__construct('', $options); + } +} diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 0000000..be956c1 --- /dev/null +++ b/vendor/symfony/console/Helper/TableStyle.php @@ -0,0 +1,362 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Dany Maillard + */ +class TableStyle +{ + private string $paddingChar = ' '; + private string $horizontalOutsideBorderChar = '-'; + private string $horizontalInsideBorderChar = '-'; + private string $verticalOutsideBorderChar = '|'; + private string $verticalInsideBorderChar = '|'; + private string $crossingChar = '+'; + private string $crossingTopRightChar = '+'; + private string $crossingTopMidChar = '+'; + private string $crossingTopLeftChar = '+'; + private string $crossingMidRightChar = '+'; + private string $crossingBottomRightChar = '+'; + private string $crossingBottomMidChar = '+'; + private string $crossingBottomLeftChar = '+'; + private string $crossingMidLeftChar = '+'; + private string $crossingTopLeftBottomChar = '+'; + private string $crossingTopMidBottomChar = '+'; + private string $crossingTopRightBottomChar = '+'; + private string $headerTitleFormat = ' %s '; + private string $footerTitleFormat = ' %s '; + private string $cellHeaderFormat = '%s'; + private string $cellRowFormat = '%s'; + private string $cellRowContentFormat = ' %s '; + private string $borderFormat = '%s'; + private int $padType = \STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @return $this + */ + public function setPaddingChar(string $paddingChar): static + { + if (!$paddingChar) { + throw new LogicException('The padding char must not be empty.'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + */ + public function getPaddingChar(): string + { + return $this->paddingChar; + } + + /** + * Sets horizontal border characters. + * + * + * â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•╤â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•╤â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•— + * 1 ISBN 2 Title │ Author â•‘ + * â• â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•╪â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•╪â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•£ + * â•‘ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri â•‘ + * â•‘ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens â•‘ + * â•‘ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien â•‘ + * â•‘ 80-902734-1-6 │ And Then There Were None │ Agatha Christie â•‘ + * ╚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•§â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•§â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + * + * + * @return $this + */ + public function setHorizontalBorderChars(string $outside, ?string $inside = null): static + { + $this->horizontalOutsideBorderChar = $outside; + $this->horizontalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Sets vertical border characters. + * + * + * â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•╤â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•╤â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•— + * â•‘ ISBN │ Title │ Author â•‘ + * â• â•â•â•â•â•â•â•1â•â•â•â•â•â•â•╪â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•╪â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•£ + * â•‘ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri â•‘ + * â•‘ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens â•‘ + * ╟───────2───────┼──────────────────────────┼──────────────────╢ + * â•‘ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien â•‘ + * â•‘ 80-902734-1-6 │ And Then There Were None │ Agatha Christie â•‘ + * ╚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•§â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•§â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + * + * + * @return $this + */ + public function setVerticalBorderChars(string $outside, ?string $inside = null): static + { + $this->verticalOutsideBorderChar = $outside; + $this->verticalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Gets border characters. + * + * @internal + */ + public function getBorderChars(): array + { + return [ + $this->horizontalOutsideBorderChar, + $this->verticalOutsideBorderChar, + $this->horizontalInsideBorderChar, + $this->verticalInsideBorderChar, + ]; + } + + /** + * Sets crossing characters. + * + * Example: + * + * 1â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•2â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•2â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•3 + * â•‘ ISBN │ Title │ Author â•‘ + * 8'â•â•â•â•â•â•â•â•â•â•â•â•â•â•0'â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•0'â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•4' + * â•‘ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri â•‘ + * â•‘ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens â•‘ + * 8───────────────0──────────────────────────0──────────────────4 + * â•‘ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien â•‘ + * â•‘ 80-902734-1-6 │ And Then There Were None │ Agatha Christie â•‘ + * 7â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•6â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•6â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•5 + * + * + * @param string $cross Crossing char (see #0 of example) + * @param string $topLeft Top left char (see #1 of example) + * @param string $topMid Top mid char (see #2 of example) + * @param string $topRight Top right char (see #3 of example) + * @param string $midRight Mid right char (see #4 of example) + * @param string $bottomRight Bottom right char (see #5 of example) + * @param string $bottomMid Bottom mid char (see #6 of example) + * @param string $bottomLeft Bottom left char (see #7 of example) + * @param string $midLeft Mid left char (see #8 of example) + * @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null + * @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null + * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null + * + * @return $this + */ + public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static + { + $this->crossingChar = $cross; + $this->crossingTopLeftChar = $topLeft; + $this->crossingTopMidChar = $topMid; + $this->crossingTopRightChar = $topRight; + $this->crossingMidRightChar = $midRight; + $this->crossingBottomRightChar = $bottomRight; + $this->crossingBottomMidChar = $bottomMid; + $this->crossingBottomLeftChar = $bottomLeft; + $this->crossingMidLeftChar = $midLeft; + $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft; + $this->crossingTopMidBottomChar = $topMidBottom ?? $cross; + $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight; + + return $this; + } + + /** + * Sets default crossing character used for each cross. + * + * @see {@link setCrossingChars()} for setting each crossing individually. + */ + public function setDefaultCrossingChar(string $char): self + { + return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char); + } + + /** + * Gets crossing character. + */ + public function getCrossingChar(): string + { + return $this->crossingChar; + } + + /** + * Gets crossing characters. + * + * @internal + */ + public function getCrossingChars(): array + { + return [ + $this->crossingChar, + $this->crossingTopLeftChar, + $this->crossingTopMidChar, + $this->crossingTopRightChar, + $this->crossingMidRightChar, + $this->crossingBottomRightChar, + $this->crossingBottomMidChar, + $this->crossingBottomLeftChar, + $this->crossingMidLeftChar, + $this->crossingTopLeftBottomChar, + $this->crossingTopMidBottomChar, + $this->crossingTopRightBottomChar, + ]; + } + + /** + * Sets header cell format. + * + * @return $this + */ + public function setCellHeaderFormat(string $cellHeaderFormat): static + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + */ + public function getCellHeaderFormat(): string + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @return $this + */ + public function setCellRowFormat(string $cellRowFormat): static + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + */ + public function getCellRowFormat(): string + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @return $this + */ + public function setCellRowContentFormat(string $cellRowContentFormat): static + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + */ + public function getCellRowContentFormat(): string + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @return $this + */ + public function setBorderFormat(string $borderFormat): static + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + */ + public function getBorderFormat(): string + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @return $this + */ + public function setPadType(int $padType): static + { + if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) { + throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + */ + public function getPadType(): int + { + return $this->padType; + } + + public function getHeaderTitleFormat(): string + { + return $this->headerTitleFormat; + } + + /** + * @return $this + */ + public function setHeaderTitleFormat(string $format): static + { + $this->headerTitleFormat = $format; + + return $this; + } + + public function getFooterTitleFormat(): string + { + return $this->footerTitleFormat; + } + + /** + * @return $this + */ + public function setFooterTitleFormat(string $format): static + { + $this->footerTitleFormat = $format; + + return $this; + } +} diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 0000000..95703ba --- /dev/null +++ b/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,396 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + */ +class ArgvInput extends Input +{ + /** @var list */ + private array $tokens; + private array $parsed; + + /** @param list|null $argv */ + public function __construct(?array $argv = null, ?InputDefinition $definition = null) + { + $argv ??= $_SERVER['argv'] ?? []; + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + /** @param list $tokens */ + protected function setTokens(array $tokens): void + { + $this->tokens = $tokens; + } + + protected function parse(): void + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + $parseOptions = $this->parseToken($token, $parseOptions); + } + } + + protected function parseToken(string $token, bool $parseOptions): bool + { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + return false; + } elseif ($parseOptions && str_starts_with($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + + return $parseOptions; + } + + /** + * Parses a short option. + */ + private function parseShortOption(string $token): void + { + $name = substr($token, 1); + + if (\strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @throws RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet(string $name): void + { + $len = \strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + $encoding = mb_detect_encoding($name, null, true); + throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding))); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + */ + private function parseLongOption(string $token): void + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if ('' === $value = substr($name, $pos + 1)) { + array_unshift($this->parsed, $value); + } + $this->addLongOption(substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @throws RuntimeException When too many arguments are given + */ + private function parseArgument(string $token): void + { + $c = \count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + $all = $this->definition->getArguments(); + $symfonyCommandName = null; + if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) { + $symfonyCommandName = $this->arguments['command'] ?? null; + unset($all[$key]); + } + + if (\count($all)) { + if ($symfonyCommandName) { + $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all))); + } else { + $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))); + } + } elseif ($symfonyCommandName) { + $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token); + } else { + $message = sprintf('No arguments expected, got "%s".', $token); + } + + throw new RuntimeException($message); + } + } + + /** + * Adds a short option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addShortOption(string $shortcut, mixed $value): void + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addLongOption(string $name, mixed $value): void + { + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + if (null !== $value) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); + + if (null !== $value && !$option->acceptValue()) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) { + $value = $next; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray() && !$option->isValueOptional()) { + $value = true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + public function getFirstArgument(): ?string + { + $isOption = false; + foreach ($this->tokens as $i => $token) { + if ($token && '-' === $token[0]) { + if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) { + continue; + } + + // If it's a long option, consider that everything after "--" is the option name. + // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator) + $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); + if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { + // noop + } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { + $isOption = true; + } + + continue; + } + + if ($isOption) { + $isOption = false; + continue; + } + + return $token; + } + + return null; + } + + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + if ($onlyParams && '--' === $token) { + return false; + } + foreach ($values as $value) { + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) { + return true; + } + } + } + + return false; + } + + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < \count($tokens)) { + $token = array_shift($tokens); + if ($onlyParams && '--' === $token) { + return $default; + } + + foreach ($values as $value) { + if ($token === $value) { + return array_shift($tokens); + } + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ('' !== $leading && str_starts_with($token, $leading)) { + return substr($token, \strlen($leading)); + } + } + } + + return $default; + } + + /** + * Returns un-parsed and not validated tokens. + * + * @param bool $strip Whether to return the raw parameters (false) or the values after the command name (true) + * + * @return list + */ + public function getRawTokens(bool $strip = false): array + { + if (!$strip) { + return $this->tokens; + } + + $parameters = []; + $keep = false; + foreach ($this->tokens as $value) { + if (!$keep && $value === $this->getFirstArgument()) { + $keep = true; + + continue; + } + if ($keep) { + $parameters[] = $value; + } + } + + return $parameters; + } + + /** + * Returns a stringified representation of the args passed to the command. + */ + public function __toString(): string + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1].$this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/symfony/console/Input/ArrayInput.php b/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 0000000..d27ff41 --- /dev/null +++ b/vendor/symfony/console/Input/ArrayInput.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']); + * + * @author Fabien Potencier + */ +class ArrayInput extends Input +{ + public function __construct( + private array $parameters, + ?InputDefinition $definition = null, + ) { + parent::__construct($definition); + } + + public function getFirstArgument(): ?string + { + foreach ($this->parameters as $param => $value) { + if ($param && \is_string($param) && '-' === $param[0]) { + continue; + } + + return $value; + } + + return null; + } + + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!\is_int($k)) { + $v = $k; + } + + if ($onlyParams && '--' === $v) { + return false; + } + + if (\in_array($v, $values)) { + return true; + } + } + + return false; + } + + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) { + return $default; + } + + if (\is_int($k)) { + if (\in_array($v, $values)) { + return true; + } + } elseif (\in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + */ + public function __toString(): string + { + $params = []; + foreach ($this->parameters as $param => $val) { + if ($param && \is_string($param) && '-' === $param[0]) { + $glue = ('-' === $param[1]) ? '=' : ' '; + if (\is_array($val)) { + foreach ($val as $v) { + $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : ''); + } + } else { + $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : ''); + } + } else { + $params[] = \is_array($val) ? implode(' ', array_map($this->escapeToken(...), $val)) : $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + protected function parse(): void + { + foreach ($this->parameters as $key => $value) { + if ('--' === $key) { + return; + } + if (str_starts_with($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif (str_starts_with($key, '-')) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @throws InvalidOptionException When option given doesn't exist + */ + private function addShortOption(string $shortcut, mixed $value): void + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws InvalidOptionException When option given doesn't exist + * @throws InvalidOptionException When a required value is missing + */ + private function addLongOption(string $name, mixed $value): void + { + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isValueOptional()) { + $value = true; + } + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + private function addArgument(string|int $name, mixed $value): void + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/vendor/symfony/console/Input/Input.php b/vendor/symfony/console/Input/Input.php new file mode 100644 index 0000000..5a8b9a2 --- /dev/null +++ b/vendor/symfony/console/Input/Input.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier + */ +abstract class Input implements InputInterface, StreamableInputInterface +{ + protected InputDefinition $definition; + /** @var resource */ + protected $stream; + protected array $options = []; + protected array $arguments = []; + protected bool $interactive = true; + + public function __construct(?InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + public function bind(InputDefinition $definition): void + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(): void; + + public function validate(): void + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired()); + + if (\count($missingArguments) > 0) { + throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + public function isInteractive(): bool + { + return $this->interactive; + } + + public function setInteractive(bool $interactive): void + { + $this->interactive = $interactive; + } + + public function getArguments(): array + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + public function getArgument(string $name): mixed + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); + } + + public function setArgument(string $name, mixed $value): void + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + public function hasArgument(string $name): bool + { + return $this->definition->hasArgument($name); + } + + public function getOptions(): array + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + public function getOption(string $name): mixed + { + if ($this->definition->hasNegation($name)) { + if (null === $value = $this->getOption($this->definition->negationToName($name))) { + return $value; + } + + return !$value; + } + + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + public function setOption(string $name, mixed $value): void + { + if ($this->definition->hasNegation($name)) { + $this->options[$this->definition->negationToName($name)] = !$value; + + return; + } elseif (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + public function hasOption(string $name): bool + { + return $this->definition->hasOption($name) || $this->definition->hasNegation($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + */ + public function escapeToken(string $token): string + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * @param resource $stream + */ + public function setStream($stream): void + { + $this->stream = $stream; + } + + /** + * @return resource + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/symfony/console/Input/InputArgument.php b/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 0000000..a5d9492 --- /dev/null +++ b/vendor/symfony/console/Input/InputArgument.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + */ +class InputArgument +{ + /** + * Providing an argument is required (e.g. just 'app:foo' is not allowed). + */ + public const REQUIRED = 1; + + /** + * Providing an argument is optional (e.g. 'app:foo' and 'app:foo bar' are both allowed). This is the default behavior of arguments. + */ + public const OPTIONAL = 2; + + /** + * The argument accepts multiple values and turn them into an array (e.g. 'app:foo bar baz' will result in value ['bar', 'baz']). + */ + public const IS_ARRAY = 4; + + private int $mode; + private string|int|bool|array|float|null $default; + + /** + * @param string $name The argument name + * @param int-mask-of|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY + * @param string $description A description text + * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct( + private string $name, + ?int $mode = null, + private string $description = '', + string|bool|int|float|array|null $default = null, + private \Closure|array $suggestedValues = [], + ) { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif ($mode >= (self::IS_ARRAY << 1) || $mode < 1) { + throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->mode = $mode; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired(): bool + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return bool true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray(): bool + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + */ + public function setDefault(string|bool|int|float|array|null $default): void + { + if ($this->isRequired() && null !== $default) { + throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + */ + public function getDefault(): string|bool|int|float|array|null + { + return $this->default; + } + + /** + * Returns true if the argument has values for input completion. + */ + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; + } + + /** + * Supplies suggestions when command resolves possible completion options for input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input))) { + throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } + + /** + * Returns the description text. + */ + public function getDescription(): string + { + return $this->description; + } +} diff --git a/vendor/symfony/console/Input/InputAwareInterface.php b/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 0000000..ba4664c --- /dev/null +++ b/vendor/symfony/console/Input/InputAwareInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + */ + public function setInput(InputInterface $input): void; +} diff --git a/vendor/symfony/console/Input/InputDefinition.php b/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 0000000..f27e297 --- /dev/null +++ b/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,402 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition([ + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * ]); + * + * @author Fabien Potencier + */ +class InputDefinition +{ + private array $arguments = []; + private int $requiredCount = 0; + private ?InputArgument $lastArrayArgument = null; + private ?InputArgument $lastOptionalArgument = null; + private array $options = []; + private array $negations = []; + private array $shortcuts = []; + + /** + * @param array $definition An array of InputArgument and InputOption instance + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + */ + public function setDefinition(array $definition): void + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function setArguments(array $arguments = []): void + { + $this->arguments = []; + $this->requiredCount = 0; + $this->lastOptionalArgument = null; + $this->lastArrayArgument = null; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments(?array $arguments = []): void + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * @throws LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument): void + { + if (isset($this->arguments[$argument->getName()])) { + throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if (null !== $this->lastArrayArgument) { + throw new LogicException(sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName())); + } + + if ($argument->isRequired() && null !== $this->lastOptionalArgument) { + throw new LogicException(sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName())); + } + + if ($argument->isArray()) { + $this->lastArrayArgument = $argument; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->lastOptionalArgument = $argument; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument(string|int $name): InputArgument + { + if (!$this->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + */ + public function hasArgument(string|int $name): bool + { + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + */ + public function getArgumentCount(): int + { + return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + */ + public function getArgumentRequiredCount(): int + { + return $this->requiredCount; + } + + /** + * @return array + */ + public function getArgumentDefaults(): array + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function setOptions(array $options = []): void + { + $this->options = []; + $this->shortcuts = []; + $this->negations = []; + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions(array $options = []): void + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @throws LogicException When option given already exist + */ + public function addOption(InputOption $option): void + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + if (isset($this->negations[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + + if ($option->isNegatable()) { + $negatedName = 'no-'.$option->getName(); + if (isset($this->options[$negatedName])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName)); + } + $this->negations[$negatedName] = $option->getName(); + } + } + + /** + * Returns an InputOption by name. + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name): InputOption + { + if (!$this->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * This method can't be used to check if the user included the option when + * executing the command (use getOption() instead). + */ + public function hasOption(string $name): bool + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + */ + public function hasShortcut(string $name): bool + { + return isset($this->shortcuts[$name]); + } + + /** + * Returns true if an InputOption object exists by negated name. + */ + public function hasNegation(string $name): bool + { + return isset($this->negations[$name]); + } + + /** + * Gets an InputOption by shortcut. + */ + public function getOptionForShortcut(string $shortcut): InputOption + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * @return array + */ + public function getOptionDefaults(): array + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function shortcutToName(string $shortcut): string + { + if (!isset($this->shortcuts[$shortcut])) { + throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Returns the InputOption name given a negation. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function negationToName(string $negation): string + { + if (!isset($this->negations[$negation])) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation)); + } + + return $this->negations[$negation]; + } + + /** + * Gets the synopsis. + */ + public function getSynopsis(bool $short = false): string + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : ''; + $elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation); + } + } + + if (\count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + $tail = ''; + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if ($argument->isArray()) { + $element .= '...'; + } + + if (!$argument->isRequired()) { + $element = '['.$element; + $tail .= ']'; + } + + $elements[] = $element; + } + + return implode(' ', $elements).$tail; + } +} diff --git a/vendor/symfony/console/Input/InputInterface.php b/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 0000000..c177d96 --- /dev/null +++ b/vendor/symfony/console/Input/InputInterface.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + */ + public function getFirstArgument(): ?string; + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + */ + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool; + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param string|bool|int|float|array|null $default The default value to return if no result is found + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + */ + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed; + + /** + * Binds the current Input instance with the given arguments and options. + * + * @throws RuntimeException + */ + public function bind(InputDefinition $definition): void; + + /** + * Validates the input. + * + * @throws RuntimeException When not enough arguments are given + */ + public function validate(): void; + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(): array; + + /** + * Returns the argument value for a given argument name. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument(string $name): mixed; + + /** + * Sets an argument value by name. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function setArgument(string $name, mixed $value): void; + + /** + * Returns true if an InputArgument object exists by name or position. + */ + public function hasArgument(string $name): bool; + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(): array; + + /** + * Returns the option value for a given option name. + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name): mixed; + + /** + * Sets an option value by name. + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function setOption(string $name, mixed $value): void; + + /** + * Returns true if an InputOption object exists by name. + */ + public function hasOption(string $name): bool; + + /** + * Is this input means interactive? + */ + public function isInteractive(): bool; + + /** + * Sets the input interactivity. + */ + public function setInteractive(bool $interactive): void; + + /** + * Returns a stringified representation of the args passed to the command. + * + * InputArguments MUST be escaped as well as the InputOption values passed to the command. + */ + public function __toString(): string; +} diff --git a/vendor/symfony/console/Input/InputOption.php b/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 0000000..617c348 --- /dev/null +++ b/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class InputOption +{ + /** + * Do not accept input for the option (e.g. --yell). This is the default behavior of options. + */ + public const VALUE_NONE = 1; + + /** + * A value must be passed when the option is used (e.g. --iterations=5 or -i5). + */ + public const VALUE_REQUIRED = 2; + + /** + * The option may or may not have a value (e.g. --yell or --yell=loud). + */ + public const VALUE_OPTIONAL = 4; + + /** + * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). + */ + public const VALUE_IS_ARRAY = 8; + + /** + * The option allows passing a negated variant (e.g. --ansi or --no-ansi). + */ + public const VALUE_NEGATABLE = 16; + + private string $name; + private ?string $shortcut; + private int $mode; + private string|int|bool|array|float|null $default; + + /** + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int-mask-of|null $mode The option mode: One of the VALUE_* constants + * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct( + string $name, + string|array|null $shortcut = null, + ?int $mode = null, + private string $description = '', + string|bool|int|float|array|null $default = null, + private array|\Closure $suggestedValues = [], + ) { + if (str_starts_with($name, '--')) { + $name = substr($name, 2); + } + + if (!$name) { + throw new InvalidArgumentException('An option name cannot be empty.'); + } + + if ('' === $shortcut || [] === $shortcut || false === $shortcut) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (\is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts, 'strlen'); + $shortcut = implode('|', $shortcuts); + + if ('' === $shortcut) { + throw new InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + + if ($suggestedValues && !$this->acceptValue()) { + throw new LogicException('Cannot set suggested values if the option does not accept a value.'); + } + if ($this->isArray() && !$this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + if ($this->isNegatable() && $this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + */ + public function getShortcut(): ?string + { + return $this->shortcut; + } + + /** + * Returns the option name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return bool true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue(): bool + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired(): bool + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional(): bool + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray(): bool + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * Returns true if the option allows passing a negated variant. + * + * @return bool true if mode is self::VALUE_NEGATABLE, false otherwise + */ + public function isNegatable(): bool + { + return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); + } + + /** + * Sets the default value. + */ + public function setDefault(string|bool|int|float|array|null $default): void + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false; + } + + /** + * Returns the default value. + */ + public function getDefault(): string|bool|int|float|array|null + { + return $this->default; + } + + /** + * Returns the description text. + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Returns true if the option has values for input completion. + */ + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; + } + + /** + * Supplies suggestions when command resolves possible completion options for input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input))) { + throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } + + /** + * Checks whether the given option equals this one. + */ + public function equals(self $option): bool + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isNegatable() === $this->isNegatable() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/vendor/symfony/console/Input/StreamableInputInterface.php b/vendor/symfony/console/Input/StreamableInputInterface.php new file mode 100644 index 0000000..4a0dc01 --- /dev/null +++ b/vendor/symfony/console/Input/StreamableInputInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StreamableInputInterface is the interface implemented by all input classes + * that have an input stream. + * + * @author Robin Chalas + */ +interface StreamableInputInterface extends InputInterface +{ + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setStream($stream): void; + + /** + * Returns the input stream. + * + * @return resource|null + */ + public function getStream(); +} diff --git a/vendor/symfony/console/Input/StringInput.php b/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 0000000..8357001 --- /dev/null +++ b/vendor/symfony/console/Input/StringInput.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + */ +class StringInput extends ArgvInput +{ + public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)'; + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; + + /** + * @param string $input A string representing the parameters from the CLI + */ + public function __construct(string $input) + { + parent::__construct([]); + + $this->setTokens($this->tokenize($input)); + } + + /** + * Tokenizes a string. + * + * @return list + * + * @throws InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize(string $input): array + { + $tokens = []; + $length = \strlen($input); + $cursor = 0; + $token = null; + while ($cursor < $length) { + if ('\\' === $input[$cursor]) { + $token .= $input[++$cursor] ?? ''; + ++$cursor; + continue; + } + + if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { + if (null !== $token) { + $tokens[] = $token; + $token = null; + } + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { + $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= stripcslashes(substr($match[0], 1, -1)); + } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= $match[1]; + } else { + // should never happen + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); + } + + $cursor += \strlen($match[0]); + } + + if (null !== $token) { + $tokens[] = $token; + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/LICENSE b/vendor/symfony/console/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/console/Logger/ConsoleLogger.php b/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 0000000..ad6e49c --- /dev/null +++ b/vendor/symfony/console/Logger/ConsoleLogger.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @see https://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + public const INFO = 'info'; + public const ERROR = 'error'; + + private array $verbosityLevelMap = [ + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ]; + private array $formatLevelMap = [ + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ]; + private bool $errored = false; + + public function __construct( + private OutputInterface $output, + array $verbosityLevelMap = [], + array $formatLevelMap = [], + ) { + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + public function log($level, $message, array $context = []): void + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + $output = $this->output; + + // Write to the error output if necessary and available + if (self::ERROR === $this->formatLevelMap[$level]) { + if ($this->output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->errored = true; + } + + // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. + // We only do it for efficiency here as the message formatting is relatively expensive. + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); + } + } + + /** + * Returns true when any messages have been logged at error levels. + */ + public function hasErrored(): bool + { + return $this->errored; + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + */ + private function interpolate(string $message, array $context): string + { + if (!str_contains($message, '{')) { + return $message; + } + + $replacements = []; + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || $val instanceof \Stringable) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object '.$val::class.']'; + } else { + $replacements["{{$key}}"] = '['.\gettype($val).']'; + } + } + + return strtr($message, $replacements); + } +} diff --git a/vendor/symfony/console/Messenger/RunCommandContext.php b/vendor/symfony/console/Messenger/RunCommandContext.php new file mode 100644 index 0000000..2ee5415 --- /dev/null +++ b/vendor/symfony/console/Messenger/RunCommandContext.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +/** + * @author Kevin Bond + */ +final class RunCommandContext +{ + public function __construct( + public readonly RunCommandMessage $message, + public readonly int $exitCode, + public readonly string $output, + ) { + } +} diff --git a/vendor/symfony/console/Messenger/RunCommandMessage.php b/vendor/symfony/console/Messenger/RunCommandMessage.php new file mode 100644 index 0000000..b530c43 --- /dev/null +++ b/vendor/symfony/console/Messenger/RunCommandMessage.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +use Symfony\Component\Console\Exception\RunCommandFailedException; + +/** + * @author Kevin Bond + */ +class RunCommandMessage implements \Stringable +{ + /** + * @param bool $throwOnFailure If the command has a non-zero exit code, throw {@see RunCommandFailedException} + * @param bool $catchExceptions @see Application::setCatchExceptions() + */ + public function __construct( + public readonly string $input, + public readonly bool $throwOnFailure = true, + public readonly bool $catchExceptions = false, + ) { + } + + public function __toString(): string + { + return $this->input; + } +} diff --git a/vendor/symfony/console/Messenger/RunCommandMessageHandler.php b/vendor/symfony/console/Messenger/RunCommandMessageHandler.php new file mode 100644 index 0000000..1bc4994 --- /dev/null +++ b/vendor/symfony/console/Messenger/RunCommandMessageHandler.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RunCommandFailedException; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * @author Kevin Bond + */ +final class RunCommandMessageHandler +{ + public function __construct( + private readonly Application $application, + ) { + } + + public function __invoke(RunCommandMessage $message): RunCommandContext + { + $input = new StringInput($message->input); + $output = new BufferedOutput(); + + $this->application->setCatchExceptions($message->catchExceptions); + + try { + $exitCode = $this->application->run($input, $output); + } catch (\Throwable $e) { + throw new RunCommandFailedException($e, new RunCommandContext($message, Command::FAILURE, $output->fetch())); + } + + if ($message->throwOnFailure && Command::SUCCESS !== $exitCode) { + throw new RunCommandFailedException(sprintf('Command "%s" exited with code "%s".', $message->input, $exitCode), new RunCommandContext($message, $exitCode, $output->fetch())); + } + + return new RunCommandContext($message, $exitCode, $output->fetch()); + } +} diff --git a/vendor/symfony/console/Output/AnsiColorMode.php b/vendor/symfony/console/Output/AnsiColorMode.php new file mode 100644 index 0000000..ca40ffb --- /dev/null +++ b/vendor/symfony/console/Output/AnsiColorMode.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Fabien Potencier + * @author Julien Boudry + */ +enum AnsiColorMode +{ + /* + * Classical 4-bit Ansi colors, including 8 classical colors and 8 bright color. Output syntax is "ESC[${foreGroundColorcode};${backGroundColorcode}m" + * Must be compatible with all terminals and it's the minimal version supported. + */ + case Ansi4; + + /* + * 8-bit Ansi colors (240 different colors + 16 duplicate color codes, ensuring backward compatibility). + * Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m" + * Should be compatible with most terminals. + */ + case Ansi8; + + /* + * 24-bit Ansi colors (RGB). + * Output syntax is: "ESC[38;2;${foreGroundColorcodeRed};${foreGroundColorcodeGreen};${foreGroundColorcodeBlue};48;2;${backGroundColorcodeRed};${backGroundColorcodeGreen};${backGroundColorcodeBlue}m" + * May be compatible with many modern terminals. + */ + case Ansi24; + + /** + * Converts an RGB hexadecimal color to the corresponding Ansi code. + */ + public function convertFromHexToAnsiColorCode(string $hexColor): string + { + $hexColor = str_replace('#', '', $hexColor); + + if (3 === \strlen($hexColor)) { + $hexColor = $hexColor[0].$hexColor[0].$hexColor[1].$hexColor[1].$hexColor[2].$hexColor[2]; + } + + if (6 !== \strlen($hexColor)) { + throw new InvalidArgumentException(sprintf('Invalid "#%s" color.', $hexColor)); + } + + $color = hexdec($hexColor); + + $r = ($color >> 16) & 255; + $g = ($color >> 8) & 255; + $b = $color & 255; + + return match ($this) { + self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b), + self::Ansi8 => '8;5;'.((string) $this->convertFromRGB($r, $g, $b)), + self::Ansi24 => sprintf('8;2;%d;%d;%d', $r, $g, $b), + }; + } + + private function convertFromRGB(int $r, int $g, int $b): int + { + return match ($this) { + self::Ansi4 => $this->degradeHexColorToAnsi4($r, $g, $b), + self::Ansi8 => $this->degradeHexColorToAnsi8($r, $g, $b), + default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}."), + }; + } + + private function degradeHexColorToAnsi4(int $r, int $g, int $b): int + { + return round($b / 255) << 2 | (round($g / 255) << 1) | round($r / 255); + } + + /** + * Inspired from https://github.com/ajalt/colormath/blob/e464e0da1b014976736cf97250063248fc77b8e7/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/Ansi256.kt code (MIT license). + */ + private function degradeHexColorToAnsi8(int $r, int $g, int $b): int + { + if ($r === $g && $g === $b) { + if ($r < 8) { + return 16; + } + + if ($r > 248) { + return 231; + } + + return (int) round(($r - 8) / 247 * 24) + 232; + } else { + return 16 + + (36 * (int) round($r / 255 * 5)) + + (6 * (int) round($g / 255 * 5)) + + (int) round($b / 255 * 5); + } + } +} diff --git a/vendor/symfony/console/Output/BufferedOutput.php b/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 0000000..3c8d390 --- /dev/null +++ b/vendor/symfony/console/Output/BufferedOutput.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + private string $buffer = ''; + + /** + * Empties buffer and returns its content. + */ + public function fetch(): string + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + protected function doWrite(string $message, bool $newline): void + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutput.php b/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 0000000..2ad3dbc --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutput.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. + * + * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); + * + * @author Fabien Potencier + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private OutputInterface $stderr; + private array $consoleSectionOutputs = []; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + if (null === $formatter) { + // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); + + return; + } + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * Creates a new output section. + */ + public function section(): ConsoleSectionOutput + { + return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); + } + + public function setDecorated(bool $decorated): void + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + public function setVerbosity(int $level): void + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + public function getErrorOutput(): OutputInterface + { + return $this->stderr; + } + + public function setErrorOutput(OutputInterface $error): void + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + */ + protected function hasStdoutSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + */ + protected function hasStderrSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + */ + private function isRunningOS400(): bool + { + $checks = [ + \function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + \PHP_OS, + ]; + + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDOUT when possible to prevent from opening too many file descriptors + return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); + } + + /** + * @return resource + */ + private function openErrorStream() + { + if (!$this->hasStderrSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDERR when possible to prevent from opening too many file descriptors + return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 0000000..1f8f147 --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutputInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr and section output stream. + * + * @author Dariusz Górecki + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + */ + public function getErrorOutput(): OutputInterface; + + public function setErrorOutput(OutputInterface $error): void; + + public function section(): ConsoleSectionOutput; +} diff --git a/vendor/symfony/console/Output/ConsoleSectionOutput.php b/vendor/symfony/console/Output/ConsoleSectionOutput.php new file mode 100644 index 0000000..09aa7fe --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleSectionOutput.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Terminal; + +/** + * @author Pierre du Plessis + * @author Gabriel Ostrolucký + */ +class ConsoleSectionOutput extends StreamOutput +{ + private array $content = []; + private int $lines = 0; + private array $sections; + private Terminal $terminal; + private int $maxHeight = 0; + + /** + * @param resource $stream + * @param ConsoleSectionOutput[] $sections + */ + public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) + { + parent::__construct($stream, $verbosity, $decorated, $formatter); + array_unshift($sections, $this); + $this->sections = &$sections; + $this->terminal = new Terminal(); + } + + /** + * Defines a maximum number of lines for this section. + * + * When more lines are added, the section will automatically scroll to the + * end (i.e. remove the first lines to comply with the max height). + */ + public function setMaxHeight(int $maxHeight): void + { + // when changing max height, clear output of current section and redraw again with the new height + $previousMaxHeight = $this->maxHeight; + $this->maxHeight = $maxHeight; + $existingContent = $this->popStreamContentUntilCurrentSection($previousMaxHeight ? min($previousMaxHeight, $this->lines) : $this->lines); + + parent::doWrite($this->getVisibleContent(), false); + parent::doWrite($existingContent, false); + } + + /** + * Clears previous output for this section. + * + * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared + */ + public function clear(?int $lines = null): void + { + if (!$this->content || !$this->isDecorated()) { + return; + } + + if ($lines) { + array_splice($this->content, -$lines); + } else { + $lines = $this->lines; + $this->content = []; + } + + $this->lines -= $lines; + + parent::doWrite($this->popStreamContentUntilCurrentSection($this->maxHeight ? min($this->maxHeight, $lines) : $lines), false); + } + + /** + * Overwrites the previous output with a new message. + */ + public function overwrite(string|iterable $message): void + { + $this->clear(); + $this->writeln($message); + } + + public function getContent(): string + { + return implode('', $this->content); + } + + public function getVisibleContent(): string + { + if (0 === $this->maxHeight) { + return $this->getContent(); + } + + return implode('', \array_slice($this->content, -$this->maxHeight)); + } + + /** + * @internal + */ + public function addContent(string $input, bool $newline = true): int + { + $width = $this->terminal->getWidth(); + $lines = explode(\PHP_EOL, $input); + $linesAdded = 0; + $count = \count($lines) - 1; + foreach ($lines as $i => $lineContent) { + // re-add the line break (that has been removed in the above `explode()` for + // - every line that is not the last line + // - if $newline is required, also add it to the last line + if ($i < $count || $newline) { + $lineContent .= \PHP_EOL; + } + + // skip line if there is no text (or newline for that matter) + if ('' === $lineContent) { + continue; + } + + // For the first line, check if the previous line (last entry of `$this->content`) + // needs to be continued (i.e. does not end with a line break). + if (0 === $i + && (false !== $lastLine = end($this->content)) + && !str_ends_with($lastLine, \PHP_EOL) + ) { + // deduct the line count of the previous line + $this->lines -= (int) ceil($this->getDisplayLength($lastLine) / $width) ?: 1; + // concatenate previous and new line + $lineContent = $lastLine.$lineContent; + // replace last entry of `$this->content` with the new expanded line + array_splice($this->content, -1, 1, $lineContent); + } else { + // otherwise just add the new content + $this->content[] = $lineContent; + } + + $linesAdded += (int) ceil($this->getDisplayLength($lineContent) / $width) ?: 1; + } + + $this->lines += $linesAdded; + + return $linesAdded; + } + + /** + * @internal + */ + public function addNewLineOfInputSubmit(): void + { + $this->content[] = \PHP_EOL; + ++$this->lines; + } + + protected function doWrite(string $message, bool $newline): void + { + // Simulate newline behavior for consistent output formatting, avoiding extra logic + if (!$newline && str_ends_with($message, \PHP_EOL)) { + $message = substr($message, 0, -\strlen(\PHP_EOL)); + $newline = true; + } + + if (!$this->isDecorated()) { + parent::doWrite($message, $newline); + + return; + } + + // Check if the previous line (last entry of `$this->content`) needs to be continued + // (i.e. does not end with a line break). In which case, it needs to be erased first. + $linesToClear = $deleteLastLine = ($lastLine = end($this->content) ?: '') && !str_ends_with($lastLine, \PHP_EOL) ? 1 : 0; + + $linesAdded = $this->addContent($message, $newline); + + if ($lineOverflow = $this->maxHeight > 0 && $this->lines > $this->maxHeight) { + // on overflow, clear the whole section and redraw again (to remove the first lines) + $linesToClear = $this->maxHeight; + } + + $erasedContent = $this->popStreamContentUntilCurrentSection($linesToClear); + + if ($lineOverflow) { + // redraw existing lines of the section + $previousLinesOfSection = \array_slice($this->content, $this->lines - $this->maxHeight, $this->maxHeight - $linesAdded); + parent::doWrite(implode('', $previousLinesOfSection), false); + } + + // if the last line was removed, re-print its content together with the new content. + // otherwise, just print the new content. + parent::doWrite($deleteLastLine ? $lastLine.$message : $message, true); + parent::doWrite($erasedContent, false); + } + + /** + * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits + * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. + */ + private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string + { + $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; + $erasedContent = []; + + foreach ($this->sections as $section) { + if ($section === $this) { + break; + } + + $numberOfLinesToClear += $section->maxHeight ? min($section->lines, $section->maxHeight) : $section->lines; + if ('' !== $sectionContent = $section->getVisibleContent()) { + if (!str_ends_with($sectionContent, \PHP_EOL)) { + $sectionContent .= \PHP_EOL; + } + $erasedContent[] = $sectionContent; + } + } + + if ($numberOfLinesToClear > 0) { + // move cursor up n lines + parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); + // erase to end of screen + parent::doWrite("\x1b[0J", false); + } + + return implode('', array_reverse($erasedContent)); + } + + private function getDisplayLength(string $text): int + { + return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text))); + } +} diff --git a/vendor/symfony/console/Output/NullOutput.php b/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 0000000..40ae332 --- /dev/null +++ b/vendor/symfony/console/Output/NullOutput.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\NullOutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class NullOutput implements OutputInterface +{ + private NullOutputFormatter $formatter; + + public function setFormatter(OutputFormatterInterface $formatter): void + { + // do nothing + } + + public function getFormatter(): OutputFormatterInterface + { + // to comply with the interface we must return a OutputFormatterInterface + return $this->formatter ??= new NullOutputFormatter(); + } + + public function setDecorated(bool $decorated): void + { + // do nothing + } + + public function isDecorated(): bool + { + return false; + } + + public function setVerbosity(int $level): void + { + // do nothing + } + + public function getVerbosity(): int + { + return self::VERBOSITY_QUIET; + } + + public function isQuiet(): bool + { + return true; + } + + public function isVerbose(): bool + { + return false; + } + + public function isVeryVerbose(): bool + { + return false; + } + + public function isDebug(): bool + { + return false; + } + + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void + { + // do nothing + } + + public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Output/Output.php b/vendor/symfony/console/Output/Output.php new file mode 100644 index 0000000..2bb1057 --- /dev/null +++ b/vendor/symfony/console/Output/Output.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier + */ +abstract class Output implements OutputInterface +{ + private int $verbosity; + private OutputFormatterInterface $formatter; + + /** + * @param int|null $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null) + { + $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL; + $this->formatter = $formatter ?? new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + $this->formatter = $formatter; + } + + public function getFormatter(): OutputFormatterInterface + { + return $this->formatter; + } + + public function setDecorated(bool $decorated): void + { + $this->formatter->setDecorated($decorated); + } + + public function isDecorated(): bool + { + return $this->formatter->isDecorated(); + } + + public function setVerbosity(int $level): void + { + $this->verbosity = $level; + } + + public function getVerbosity(): int + { + return $this->verbosity; + } + + public function isQuiet(): bool + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose(): bool + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose(): bool + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug(): bool + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void + { + $this->write($messages, true, $options); + } + + public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; + $type = $types & $options ?: self::OUTPUT_NORMAL; + + $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; + $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; + + if ($verbosity > $this->getVerbosity()) { + return; + } + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + } + + $this->doWrite($message ?? '', $newline); + } + } + + /** + * Writes a message to the output. + */ + abstract protected function doWrite(string $message, bool $newline): void; +} diff --git a/vendor/symfony/console/Output/OutputInterface.php b/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 0000000..41315fb --- /dev/null +++ b/vendor/symfony/console/Output/OutputInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + */ +interface OutputInterface +{ + public const VERBOSITY_QUIET = 16; + public const VERBOSITY_NORMAL = 32; + public const VERBOSITY_VERBOSE = 64; + public const VERBOSITY_VERY_VERBOSE = 128; + public const VERBOSITY_DEBUG = 256; + + public const OUTPUT_NORMAL = 1; + public const OUTPUT_RAW = 2; + public const OUTPUT_PLAIN = 4; + + /** + * Writes a message to the output. + * + * @param bool $newline Whether to add a newline + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), + * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function write(string|iterable $messages, bool $newline = false, int $options = 0): void; + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), + * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function writeln(string|iterable $messages, int $options = 0): void; + + /** + * Sets the verbosity of the output. + * + * @param self::VERBOSITY_* $level + */ + public function setVerbosity(int $level): void; + + /** + * Gets the current verbosity of the output. + * + * @return self::VERBOSITY_* + */ + public function getVerbosity(): int; + + /** + * Returns whether verbosity is quiet (-q). + */ + public function isQuiet(): bool; + + /** + * Returns whether verbosity is verbose (-v). + */ + public function isVerbose(): bool; + + /** + * Returns whether verbosity is very verbose (-vv). + */ + public function isVeryVerbose(): bool; + + /** + * Returns whether verbosity is debug (-vvv). + */ + public function isDebug(): bool; + + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated): void; + + /** + * Gets the decorated flag. + */ + public function isDecorated(): bool; + + public function setFormatter(OutputFormatterInterface $formatter): void; + + /** + * Returns current output formatter instance. + */ + public function getFormatter(): OutputFormatterInterface; +} diff --git a/vendor/symfony/console/Output/StreamOutput.php b/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 0000000..e94a5eb --- /dev/null +++ b/vendor/symfony/console/Output/StreamOutput.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier + */ +class StreamOutput extends Output +{ + /** @var resource */ + private $stream; + + /** + * @param resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws InvalidArgumentException When first argument is not a real stream + */ + public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + $decorated ??= $this->hasColorSupport(); + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + protected function doWrite(string $message, bool $newline): void + { + if ($newline) { + $message .= \PHP_EOL; + } + + @fwrite($this->stream, $message); + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport(): bool + { + // Follow https://no-color.org/ + if ('' !== (($_SERVER['NO_COLOR'] ?? getenv('NO_COLOR'))[0] ?? '')) { + return false; + } + + // Detect msysgit/mingw and assume this is a tty because detection + // does not work correctly, see https://github.com/composer/composer/issues/9690 + if (!@stream_isatty($this->stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR && @sapi_windows_vt100_support($this->stream)) { + return true; + } + + if ('Hyper' === getenv('TERM_PROGRAM') + || false !== getenv('COLORTERM') + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + ) { + return true; + } + + if ('dumb' === $term = (string) getenv('TERM')) { + return false; + } + + // See https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L157 + return preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term); + } +} diff --git a/vendor/symfony/console/Output/TrimmedBufferOutput.php b/vendor/symfony/console/Output/TrimmedBufferOutput.php new file mode 100644 index 0000000..c1862a2 --- /dev/null +++ b/vendor/symfony/console/Output/TrimmedBufferOutput.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * A BufferedOutput that keeps only the last N chars. + * + * @author Jérémy Derussé + */ +class TrimmedBufferOutput extends Output +{ + private int $maxLength; + private string $buffer = ''; + + public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null) + { + if ($maxLength <= 0) { + throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); + } + + parent::__construct($verbosity, $decorated, $formatter); + $this->maxLength = $maxLength; + } + + /** + * Empties buffer and returns its content. + */ + public function fetch(): string + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + protected function doWrite(string $message, bool $newline): void + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + + $this->buffer = substr($this->buffer, 0 - $this->maxLength); + } +} diff --git a/vendor/symfony/console/Question/ChoiceQuestion.php b/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 0000000..d062ce7 --- /dev/null +++ b/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Represents a choice question. + * + * @author Fabien Potencier + */ +class ChoiceQuestion extends Question +{ + private bool $multiselect = false; + private string $prompt = ' > '; + private string $errorMessage = 'Value "%s" is invalid'; + + /** + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param string|bool|int|float|null $default The default answer to return + */ + public function __construct( + string $question, + private array $choices, + string|bool|int|float|null $default = null, + ) { + if (!$choices) { + throw new \LogicException('Choice question must have at least 1 choice available.'); + } + + parent::__construct($question, $default); + + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + */ + public function getChoices(): array + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @return $this + */ + public function setMultiselect(bool $multiselect): static + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + */ + public function isMultiselect(): bool + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + */ + public function getPrompt(): string + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @return $this + */ + public function setPrompt(string $prompt): static + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @return $this + */ + public function setErrorMessage(string $errorMessage): static + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + private function getDefaultValidator(): callable + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $selected)); + } + + $selectedChoices = explode(',', (string) $selected); + } else { + $selectedChoices = [$selected]; + } + + if ($this->isTrimmable()) { + foreach ($selectedChoices as $k => $v) { + $selectedChoices[$k] = trim((string) $v); + } + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (\count($results) > 1) { + throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new InvalidArgumentException(sprintf($errorMessage, $value)); + } + + // For associative choices, consistently return the key as string: + $multiselectChoices[] = $isAssoc ? (string) $result : $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/symfony/console/Question/ConfirmationQuestion.php b/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 0000000..951d681 --- /dev/null +++ b/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier + */ +class ConfirmationQuestion extends Question +{ + /** + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct( + string $question, + bool $default = true, + private string $trueAnswerRegex = '/^y/i', + ) { + parent::__construct($question, $default); + + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + */ + private function getDefaultNormalizer(): callable + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (\is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return '' === $answer || $answerIsTrue; + }; + } +} diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php new file mode 100644 index 0000000..46a60c7 --- /dev/null +++ b/vendor/symfony/console/Question/Question.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a Question. + * + * @author Fabien Potencier + */ +class Question +{ + private ?int $attempts = null; + private bool $hidden = false; + private bool $hiddenFallback = true; + private ?\Closure $autocompleterCallback = null; + private ?\Closure $validator = null; + private ?\Closure $normalizer = null; + private bool $trimmable = true; + private bool $multiline = false; + + /** + * @param string $question The question to ask to the user + * @param string|bool|int|float|null $default The default answer to return if the user enters nothing + */ + public function __construct( + private string $question, + private string|bool|int|float|null $default = null, + ) { + } + + /** + * Returns the question. + */ + public function getQuestion(): string + { + return $this->question; + } + + /** + * Returns the default answer. + */ + public function getDefault(): string|bool|int|float|null + { + return $this->default; + } + + /** + * Returns whether the user response accepts newline characters. + */ + public function isMultiline(): bool + { + return $this->multiline; + } + + /** + * Sets whether the user response should accept newline characters. + * + * @return $this + */ + public function setMultiline(bool $multiline): static + { + $this->multiline = $multiline; + + return $this; + } + + /** + * Returns whether the user response must be hidden. + */ + public function isHidden(): bool + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @return $this + * + * @throws LogicException In case the autocompleter is also used + */ + public function setHidden(bool $hidden): static + { + if ($this->autocompleterCallback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = $hidden; + + return $this; + } + + /** + * In case the response cannot be hidden, whether to fallback on non-hidden question or not. + */ + public function isHiddenFallback(): bool + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response cannot be hidden. + * + * @return $this + */ + public function setHiddenFallback(bool $fallback): static + { + $this->hiddenFallback = $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + */ + public function getAutocompleterValues(): ?iterable + { + $callback = $this->getAutocompleterCallback(); + + return $callback ? $callback('') : null; + } + + /** + * Sets values for the autocompleter. + * + * @return $this + * + * @throws LogicException + */ + public function setAutocompleterValues(?iterable $values): static + { + if (\is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + + $callback = static fn () => $values; + } elseif ($values instanceof \Traversable) { + $callback = static function () use ($values) { + static $valueCache; + + return $valueCache ??= iterator_to_array($values, false); + }; + } else { + $callback = null; + } + + return $this->setAutocompleterCallback($callback); + } + + /** + * Gets the callback function used for the autocompleter. + */ + public function getAutocompleterCallback(): ?callable + { + return $this->autocompleterCallback; + } + + /** + * Sets the callback function used for the autocompleter. + * + * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. + * + * @return $this + */ + public function setAutocompleterCallback(?callable $callback): static + { + if ($this->hidden && null !== $callback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterCallback = null === $callback ? null : $callback(...); + + return $this; + } + + /** + * Sets a validator for the question. + * + * @return $this + */ + public function setValidator(?callable $validator): static + { + $this->validator = null === $validator ? null : $validator(...); + + return $this; + } + + /** + * Gets the validator for the question. + */ + public function getValidator(): ?callable + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return $this + * + * @throws InvalidArgumentException in case the number of attempts is invalid + */ + public function setMaxAttempts(?int $attempts): static + { + if (null !== $attempts && $attempts < 1) { + throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + */ + public function getMaxAttempts(): ?int + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @return $this + */ + public function setNormalizer(callable $normalizer): static + { + $this->normalizer = $normalizer(...); + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + */ + public function getNormalizer(): ?callable + { + return $this->normalizer; + } + + protected function isAssoc(array $array): bool + { + return (bool) \count(array_filter(array_keys($array), 'is_string')); + } + + public function isTrimmable(): bool + { + return $this->trimmable; + } + + /** + * @return $this + */ + public function setTrimmable(bool $trimmable): static + { + $this->trimmable = $trimmable; + + return $this; + } +} diff --git a/vendor/symfony/console/README.md b/vendor/symfony/console/README.md new file mode 100644 index 0000000..92f70e7 --- /dev/null +++ b/vendor/symfony/console/README.md @@ -0,0 +1,27 @@ +Console Component +================= + +The Console component eases the creation of beautiful and testable command line +interfaces. + +Sponsor +------- + +Help Symfony by [sponsoring][1] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/console.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +Credits +------- + +`Resources/bin/hiddeninput.exe` is a third party binary provided within this +component. Find sources and license at https://github.com/Seldaek/hidden-input. + +[1]: https://symfony.com/sponsor diff --git a/vendor/symfony/console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Resources/completion.bash b/vendor/symfony/console/Resources/completion.bash new file mode 100644 index 0000000..64c6a33 --- /dev/null +++ b/vendor/symfony/console/Resources/completion.bash @@ -0,0 +1,94 @@ +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +_sf_{{ COMMAND_NAME }}() { + + # Use the default completion for shell redirect operators. + for w in '>' '>>' '&>' '<'; do + if [[ $w = "${COMP_WORDS[COMP_CWORD-1]}" ]]; then + compopt -o filenames + COMPREPLY=($(compgen -f -- "${COMP_WORDS[COMP_CWORD]}")) + return 0 + fi + done + + # Use newline as only separator to allow space in completion values + local IFS=$'\n' + local sf_cmd="${COMP_WORDS[0]}" + + # for an alias, get the real script behind it + sf_cmd_type=$(type -t $sf_cmd) + if [[ $sf_cmd_type == "alias" ]]; then + sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/") + elif [[ $sf_cmd_type == "file" ]]; then + sf_cmd=$(type -p $sf_cmd) + fi + + if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then + return 1 + fi + + local cur prev words cword + _get_comp_words_by_ref -n := cur prev words cword + + local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a{{ VERSION }}") + for w in ${words[@]}; do + w=$(printf -- '%b' "$w") + # remove quotes from typed values + quote="${w:0:1}" + if [ "$quote" == \' ]; then + w="${w%\'}" + w="${w#\'}" + elif [ "$quote" == \" ]; then + w="${w%\"}" + w="${w#\"}" + fi + # empty values are ignored + if [ ! -z "$w" ]; then + completecmd+=("-i$w") + fi + done + + local sfcomplete + if sfcomplete=$(${completecmd[@]} 2>&1); then + local quote suggestions + quote=${cur:0:1} + + # Use single quotes by default if suggestions contains backslash (FQCN) + if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then + quote=\' + fi + + if [ "$quote" == \' ]; then + # single quotes: no additional escaping (does not accept ' in values) + suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done) + elif [ "$quote" == \" ]; then + # double quotes: double escaping for \ $ ` " + suggestions=$(for s in $sfcomplete; do + s=${s//\\/\\\\} + s=${s//\$/\\\$} + s=${s//\`/\\\`} + s=${s//\"/\\\"} + printf $'%q%q%q\n' "$quote" "$s" "$quote"; + done) + else + # no quotes: double escaping + suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done) + fi + COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur"))) + __ltrim_colon_completions "$cur" + else + if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then + >&2 echo + >&2 echo $sfcomplete + fi + + return 1 + fi +} + +complete -F _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} diff --git a/vendor/symfony/console/Resources/completion.fish b/vendor/symfony/console/Resources/completion.fish new file mode 100644 index 0000000..1853dd8 --- /dev/null +++ b/vendor/symfony/console/Resources/completion.fish @@ -0,0 +1,25 @@ +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +function _sf_{{ COMMAND_NAME }} + set sf_cmd (commandline -o) + set c (count (commandline -oc)) + + set completecmd "$sf_cmd[1]" "_complete" "--no-interaction" "-sfish" "-a{{ VERSION }}" + + for i in $sf_cmd + if [ $i != "" ] + set completecmd $completecmd "-i$i" + end + end + + set completecmd $completecmd "-c$c" + + $completecmd +end + +complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f diff --git a/vendor/symfony/console/Resources/completion.zsh b/vendor/symfony/console/Resources/completion.zsh new file mode 100644 index 0000000..ff76fe5 --- /dev/null +++ b/vendor/symfony/console/Resources/completion.zsh @@ -0,0 +1,82 @@ +#compdef {{ COMMAND_NAME }} + +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +# +# zsh completions for {{ COMMAND_NAME }} +# +# References: +# - https://github.com/spf13/cobra/blob/master/zsh_completions.go +# - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash +# +_sf_{{ COMMAND_NAME }}() { + local lastParam flagPrefix requestComp out comp + local -a completions + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") lastParam=${words[-1]} + + # For zsh, when completing a flag with an = (e.g., {{ COMMAND_NAME }} -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a{{ VERSION }} -c$((CURRENT-1))" i="" + for w in ${words[@]}; do + w=$(printf -- '%b' "$w") + # remove quotes from typed values + quote="${w:0:1}" + if [ "$quote" = \' ]; then + w="${w%\'}" + w="${w#\'}" + elif [ "$quote" = \" ]; then + w="${w%\"}" + w="${w#\"}" + fi + # empty values are ignored + if [ ! -z "$w" ]; then + i="${i}-i${w} " + fi + done + + # Ensure at least 1 input + if [ "${i}" = "" ]; then + requestComp="${requestComp} -i\" \"" + else + requestComp="${requestComp} ${i}" + fi + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + local tab=$(printf '\t') + comp=${comp//$tab/:} + completions+=${comp} + fi + done < <(printf "%s\n" "${out[@]}") + + # Let inbuilt _describe handle completions + eval _describe "completions" completions $flagPrefix + return $? +} + +compdef _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} diff --git a/vendor/symfony/console/SignalRegistry/SignalMap.php b/vendor/symfony/console/SignalRegistry/SignalMap.php new file mode 100644 index 0000000..de419bd --- /dev/null +++ b/vendor/symfony/console/SignalRegistry/SignalMap.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\SignalRegistry; + +/** + * @author Grégoire Pineau + */ +class SignalMap +{ + private static array $map; + + public static function getSignalName(int $signal): ?string + { + if (!\extension_loaded('pcntl')) { + return null; + } + + if (!isset(self::$map)) { + $r = new \ReflectionExtension('pcntl'); + $c = $r->getConstants(); + $map = array_filter($c, fn ($k) => str_starts_with($k, 'SIG') && !str_starts_with($k, 'SIG_'), \ARRAY_FILTER_USE_KEY); + self::$map = array_flip($map); + } + + return self::$map[$signal] ?? null; + } +} diff --git a/vendor/symfony/console/SignalRegistry/SignalRegistry.php b/vendor/symfony/console/SignalRegistry/SignalRegistry.php new file mode 100644 index 0000000..ef2e5f0 --- /dev/null +++ b/vendor/symfony/console/SignalRegistry/SignalRegistry.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\SignalRegistry; + +final class SignalRegistry +{ + private array $signalHandlers = []; + + public function __construct() + { + if (\function_exists('pcntl_async_signals')) { + pcntl_async_signals(true); + } + } + + public function register(int $signal, callable $signalHandler): void + { + if (!isset($this->signalHandlers[$signal])) { + $previousCallback = pcntl_signal_get_handler($signal); + + if (\is_callable($previousCallback)) { + $this->signalHandlers[$signal][] = $previousCallback; + } + } + + $this->signalHandlers[$signal][] = $signalHandler; + + pcntl_signal($signal, $this->handle(...)); + } + + public static function isSupported(): bool + { + return \function_exists('pcntl_signal'); + } + + /** + * @internal + */ + public function handle(int $signal): void + { + $count = \count($this->signalHandlers[$signal]); + + foreach ($this->signalHandlers[$signal] as $i => $signalHandler) { + $hasNext = $i !== $count - 1; + $signalHandler($signal, $hasNext); + } + } +} diff --git a/vendor/symfony/console/SingleCommandApplication.php b/vendor/symfony/console/SingleCommandApplication.php new file mode 100644 index 0000000..ff1c172 --- /dev/null +++ b/vendor/symfony/console/SingleCommandApplication.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Grégoire Pineau + */ +class SingleCommandApplication extends Command +{ + private string $version = 'UNKNOWN'; + private bool $autoExit = true; + private bool $running = false; + + /** + * @return $this + */ + public function setVersion(string $version): static + { + $this->version = $version; + + return $this; + } + + /** + * @final + * + * @return $this + */ + public function setAutoExit(bool $autoExit): static + { + $this->autoExit = $autoExit; + + return $this; + } + + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int + { + if ($this->running) { + return parent::run($input, $output); + } + + // We use the command name as the application name + $application = new Application($this->getName() ?: 'UNKNOWN', $this->version); + $application->setAutoExit($this->autoExit); + // Fix the usage of the command displayed with "--help" + $this->setName($_SERVER['argv'][0]); + $application->add($this); + $application->setDefaultCommand($this->getName(), true); + + $this->running = true; + try { + $ret = $application->run($input, $output); + } finally { + $this->running = false; + } + + return $ret ?? 1; + } +} diff --git a/vendor/symfony/console/Style/OutputStyle.php b/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 0000000..9f62ea3 --- /dev/null +++ b/vendor/symfony/console/Style/OutputStyle.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + public function __construct( + private OutputInterface $output, + ) { + } + + public function newLine(int $count = 1): void + { + $this->output->write(str_repeat(\PHP_EOL, $count)); + } + + public function createProgressBar(int $max = 0): ProgressBar + { + return new ProgressBar($this->output, $max); + } + + public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void + { + $this->output->write($messages, $newline, $type); + } + + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void + { + $this->output->writeln($messages, $type); + } + + public function setVerbosity(int $level): void + { + $this->output->setVerbosity($level); + } + + public function getVerbosity(): int + { + return $this->output->getVerbosity(); + } + + public function setDecorated(bool $decorated): void + { + $this->output->setDecorated($decorated); + } + + public function isDecorated(): bool + { + return $this->output->isDecorated(); + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + $this->output->setFormatter($formatter); + } + + public function getFormatter(): OutputFormatterInterface + { + return $this->output->getFormatter(); + } + + public function isQuiet(): bool + { + return $this->output->isQuiet(); + } + + public function isVerbose(): bool + { + return $this->output->isVerbose(); + } + + public function isVeryVerbose(): bool + { + return $this->output->isVeryVerbose(); + } + + public function isDebug(): bool + { + return $this->output->isDebug(); + } + + protected function getErrorOutput(): OutputInterface + { + if (!$this->output instanceof ConsoleOutputInterface) { + return $this->output; + } + + return $this->output->getErrorOutput(); + } +} diff --git a/vendor/symfony/console/Style/StyleInterface.php b/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 0000000..fcc5bc7 --- /dev/null +++ b/vendor/symfony/console/Style/StyleInterface.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + */ + public function title(string $message): void; + + /** + * Formats a section title. + */ + public function section(string $message): void; + + /** + * Formats a list. + */ + public function listing(array $elements): void; + + /** + * Formats informational text. + */ + public function text(string|array $message): void; + + /** + * Formats a success result bar. + */ + public function success(string|array $message): void; + + /** + * Formats an error result bar. + */ + public function error(string|array $message): void; + + /** + * Formats an warning result bar. + */ + public function warning(string|array $message): void; + + /** + * Formats a note admonition. + */ + public function note(string|array $message): void; + + /** + * Formats a caution admonition. + */ + public function caution(string|array $message): void; + + /** + * Formats a table. + */ + public function table(array $headers, array $rows): void; + + /** + * Asks a question. + */ + public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed; + + /** + * Asks a question with the user input hidden. + */ + public function askHidden(string $question, ?callable $validator = null): mixed; + + /** + * Asks for confirmation. + */ + public function confirm(string $question, bool $default = true): bool; + + /** + * Asks a choice question. + */ + public function choice(string $question, array $choices, mixed $default = null): mixed; + + /** + * Add newline(s). + */ + public function newLine(int $count = 1): void; + + /** + * Starts the progress output. + */ + public function progressStart(int $max = 0): void; + + /** + * Advances the progress output X steps. + */ + public function progressAdvance(int $step = 1): void; + + /** + * Finishes the progress output. + */ + public function progressFinish(): void; +} diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 0000000..19ad892 --- /dev/null +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,455 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\OutputWrapper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\TrimmedBufferOutput; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + public const MAX_LINE_LENGTH = 120; + + private SymfonyQuestionHelper $questionHelper; + private ProgressBar $progressBar; + private int $lineLength; + private TrimmedBufferOutput $bufferedOutput; + + public function __construct( + private InputInterface $input, + private OutputInterface $output, + ) { + $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; + $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($output); + } + + /** + * Formats a message as a block of text. + */ + public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true): void + { + $messages = \is_array($messages) ? array_values($messages) : [$messages]; + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); + $this->newLine(); + } + + public function title(string $message): void + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), + ]); + $this->newLine(); + } + + public function section(string $message): void + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), + ]); + $this->newLine(); + } + + public function listing(array $elements): void + { + $this->autoPrependText(); + $elements = array_map(fn ($element) => sprintf(' * %s', $element), $elements); + + $this->writeln($elements); + $this->newLine(); + } + + public function text(string|array $message): void + { + $this->autoPrependText(); + + $messages = \is_array($message) ? array_values($message) : [$message]; + foreach ($messages as $message) { + $this->writeln(sprintf(' %s', $message)); + } + } + + /** + * Formats a command comment. + */ + public function comment(string|array $message): void + { + $this->block($message, null, null, ' // ', false, false); + } + + public function success(string|array $message): void + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + public function error(string|array $message): void + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + public function warning(string|array $message): void + { + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); + } + + public function note(string|array $message): void + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * Formats an info message. + */ + public function info(string|array $message): void + { + $this->block($message, 'INFO', 'fg=green', ' ', true); + } + + public function caution(string|array $message): void + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + public function table(array $headers, array $rows): void + { + $this->createTable() + ->setHeaders($headers) + ->setRows($rows) + ->render() + ; + + $this->newLine(); + } + + /** + * Formats a horizontal table. + */ + public function horizontalTable(array $headers, array $rows): void + { + $this->createTable() + ->setHorizontal(true) + ->setHeaders($headers) + ->setRows($rows) + ->render() + ; + + $this->newLine(); + } + + /** + * Formats a list of key/value horizontally. + * + * Each row can be one of: + * * 'A title' + * * ['key' => 'value'] + * * new TableSeparator() + */ + public function definitionList(string|array|TableSeparator ...$list): void + { + $headers = []; + $row = []; + foreach ($list as $value) { + if ($value instanceof TableSeparator) { + $headers[] = $value; + $row[] = $value; + continue; + } + if (\is_string($value)) { + $headers[] = new TableCell($value, ['colspan' => 2]); + $row[] = null; + continue; + } + if (!\is_array($value)) { + throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.'); + } + $headers[] = key($value); + $row[] = current($value); + } + + $this->horizontalTable($headers, [$row]); + } + + public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + public function askHidden(string $question, ?callable $validator = null): mixed + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + public function confirm(string $question, bool $default = true): bool + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + public function choice(string $question, array $choices, mixed $default = null, bool $multiSelect = false): mixed + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default] ?? $default; + } + + $questionChoice = new ChoiceQuestion($question, $choices, $default); + $questionChoice->setMultiselect($multiSelect); + + return $this->askQuestion($questionChoice); + } + + public function progressStart(int $max = 0): void + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + public function progressAdvance(int $step = 1): void + { + $this->getProgressBar()->advance($step); + } + + public function progressFinish(): void + { + $this->getProgressBar()->finish(); + $this->newLine(2); + unset($this->progressBar); + } + + public function createProgressBar(int $max = 0): ProgressBar + { + $progressBar = parent::createProgressBar($max); + + if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { + $progressBar->setEmptyBarCharacter('â–‘'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('â–“'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @see ProgressBar::iterate() + * + * @template TKey + * @template TValue + * + * @param iterable $iterable + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + * + * @return iterable + */ + public function progressIterate(iterable $iterable, ?int $max = null): iterable + { + yield from $this->createProgressBar()->iterate($iterable, $max); + + $this->newLine(2); + } + + public function askQuestion(Question $question): mixed + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + $this->questionHelper ??= new SymfonyQuestionHelper(); + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + if ($this->output instanceof ConsoleSectionOutput) { + // add the new line of the `return` to submit the input to ConsoleSectionOutput, because ConsoleSectionOutput is holding all it's lines. + // this is relevant when a `ConsoleSectionOutput::clear` is called. + $this->output->addNewLineOfInputSubmit(); + } + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::writeln($message, $type); + $this->writeBuffer($message, true, $type); + } + } + + public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::write($message, $newline, $type); + $this->writeBuffer($message, $newline, $type); + } + } + + public function newLine(int $count = 1): void + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * Returns a new instance which makes use of stderr if available. + */ + public function getErrorStyle(): self + { + return new self($this->input, $this->getErrorOutput()); + } + + public function createTable(): Table + { + $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output; + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + return (new Table($output))->setStyle($style); + } + + private function getProgressBar(): ProgressBar + { + return $this->progressBar + ?? throw new RuntimeException('The ProgressBar is not started.'); + } + + private function autoPrependBlock(): void + { + $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + $this->newLine(); // empty history, so we should start with a new line. + + return; + } + // Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText(): void + { + $fetched = $this->bufferedOutput->fetch(); + // Prepend new line if last char isn't EOL: + if ($fetched && !str_ends_with($fetched, "\n")) { + $this->newLine(); + } + } + + private function writeBuffer(string $message, bool $newLine, int $type): void + { + // We need to know if the last chars are PHP_EOL + $this->bufferedOutput->write($message, $newLine, $type); + } + + private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array + { + $indentLength = 0; + $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix)); + $lines = []; + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = Helper::width($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + $outputWrapper = new OutputWrapper(); + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $lines = array_merge( + $lines, + explode(\PHP_EOL, $outputWrapper->wrap( + $message, + $this->lineLength - $prefixLength - $indentLength, + \PHP_EOL + )) + ); + + if (\count($messages) > 1 && $key < \count($messages) - 1) { + $lines[] = ''; + } + } + + $firstLineIndex = 0; + if ($padding && $this->isDecorated()) { + $firstLineIndex = 1; + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as $i => &$line) { + if (null !== $type) { + $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + } + + $line = $prefix.$line; + $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + return $lines; + } +} diff --git a/vendor/symfony/console/Terminal.php b/vendor/symfony/console/Terminal.php new file mode 100644 index 0000000..9eb16aa --- /dev/null +++ b/vendor/symfony/console/Terminal.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Output\AnsiColorMode; + +class Terminal +{ + public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4; + + private static ?AnsiColorMode $colorMode = null; + private static ?int $width = null; + private static ?int $height = null; + private static ?bool $stty = null; + + /** + * About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + * For more information about true color support with terminals https://github.com/termstandard/colors/. + */ + public static function getColorMode(): AnsiColorMode + { + // Use Cache from previous run (or user forced mode) + if (null !== self::$colorMode) { + return self::$colorMode; + } + + // Try with $COLORTERM first + if (\is_string($colorterm = getenv('COLORTERM'))) { + $colorterm = strtolower($colorterm); + + if (str_contains($colorterm, 'truecolor')) { + self::setColorMode(AnsiColorMode::Ansi24); + + return self::$colorMode; + } + + if (str_contains($colorterm, '256color')) { + self::setColorMode(AnsiColorMode::Ansi8); + + return self::$colorMode; + } + } + + // Try with $TERM + if (\is_string($term = getenv('TERM'))) { + $term = strtolower($term); + + if (str_contains($term, 'truecolor')) { + self::setColorMode(AnsiColorMode::Ansi24); + + return self::$colorMode; + } + + if (str_contains($term, '256color')) { + self::setColorMode(AnsiColorMode::Ansi8); + + return self::$colorMode; + } + } + + self::setColorMode(self::DEFAULT_COLOR_MODE); + + return self::$colorMode; + } + + /** + * Force a terminal color mode rendering. + */ + public static function setColorMode(?AnsiColorMode $colorMode): void + { + self::$colorMode = $colorMode; + } + + /** + * Gets the terminal width. + */ + public function getWidth(): int + { + $width = getenv('COLUMNS'); + if (false !== $width) { + return (int) trim($width); + } + + if (null === self::$width) { + self::initDimensions(); + } + + return self::$width ?: 80; + } + + /** + * Gets the terminal height. + */ + public function getHeight(): int + { + $height = getenv('LINES'); + if (false !== $height) { + return (int) trim($height); + } + + if (null === self::$height) { + self::initDimensions(); + } + + return self::$height ?: 50; + } + + /** + * @internal + */ + public static function hasSttyAvailable(): bool + { + if (null !== self::$stty) { + return self::$stty; + } + + // skip check if shell_exec function is disabled + if (!\function_exists('shell_exec')) { + return false; + } + + return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); + } + + private static function initDimensions(): void + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $ansicon = getenv('ANSICON'); + if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) { + // extract [w, H] from "wxh (WxH)" + // or [w, h] from "wxh" + self::$width = (int) $matches[1]; + self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; + } elseif (!sapi_windows_vt100_support(fopen('php://stdout', 'w')) && self::hasSttyAvailable()) { + // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash) + // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT + self::initDimensionsUsingStty(); + } elseif (null !== $dimensions = self::getConsoleMode()) { + // extract [w, h] from "wxh" + self::$width = (int) $dimensions[0]; + self::$height = (int) $dimensions[1]; + } + } else { + self::initDimensionsUsingStty(); + } + } + + /** + * Initializes dimensions using the output of an stty columns line. + */ + private static function initDimensionsUsingStty(): void + { + if ($sttyString = self::getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) { + // extract [w, h] from "rows h; columns w;" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) { + // extract [w, h] from "; h rows; w columns" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output. + * + * @return int[]|null An array composed of the width and the height or null if it could not be parsed + */ + private static function getConsoleMode(): ?array + { + $info = self::readFromProcess('mode CON'); + + if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return null; + } + + return [(int) $matches[2], (int) $matches[1]]; + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output. + */ + private static function getSttyColumns(): ?string + { + return self::readFromProcess(['stty', '-a']); + } + + private static function readFromProcess(string|array $command): ?string + { + if (!\function_exists('proc_open')) { + return null; + } + + $descriptorspec = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0; + + $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (!\is_resource($process)) { + return null; + } + + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if ($cp) { + sapi_windows_cp_set($cp); + } + + return $info; + } +} diff --git a/vendor/symfony/console/Tester/ApplicationTester.php b/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 0000000..cebb6f8 --- /dev/null +++ b/vendor/symfony/console/Tester/ApplicationTester.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + use TesterTrait; + + public function __construct( + private Application $application, + ) { + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @return int The command exit code + */ + public function run(array $input, array $options = []): int + { + $prevShellVerbosity = getenv('SHELL_VERBOSITY'); + + try { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } + + $this->initOutput($options); + + return $this->statusCode = $this->application->run($this->input, $this->output); + } finally { + // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it + // to its previous value to avoid one test's verbosity to spread to the following tests + if (false === $prevShellVerbosity) { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY'); + } + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } else { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; + } + } + } +} diff --git a/vendor/symfony/console/Tester/CommandCompletionTester.php b/vendor/symfony/console/Tester/CommandCompletionTester.php new file mode 100644 index 0000000..76cbaf1 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandCompletionTester.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; + +/** + * Eases the testing of command completion. + * + * @author Jérôme Tamarelle + */ +class CommandCompletionTester +{ + public function __construct( + private Command $command, + ) { + } + + /** + * Create completion suggestions from input tokens. + */ + public function complete(array $input): array + { + $currentIndex = \count($input); + if ('' === end($input)) { + array_pop($input); + } + array_unshift($input, $this->command->getName()); + + $completionInput = CompletionInput::fromTokens($input, $currentIndex); + $completionInput->bind($this->command->getDefinition()); + $suggestions = new CompletionSuggestions(); + + $this->command->complete($completionInput, $suggestions); + + $options = []; + foreach ($suggestions->getOptionSuggestions() as $option) { + $options[] = '--'.$option->getName(); + } + + return array_map('strval', array_merge($options, $suggestions->getValueSuggestions())); + } +} diff --git a/vendor/symfony/console/Tester/CommandTester.php b/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 0000000..d39cde7 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandTester.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + * @author Robin Chalas + */ +class CommandTester +{ + use TesterTrait; + + public function __construct( + private Command $command, + ) { + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = []): int + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(['command' => $this->command->getName()], $input); + } + + $this->input = new ArrayInput($input); + // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN. + $this->input->setStream(self::createStream($this->inputs)); + + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if (!isset($options['decorated'])) { + $options['decorated'] = false; + } + + $this->initOutput($options); + + return $this->statusCode = $this->command->run($this->input, $this->output); + } +} diff --git a/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php b/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php new file mode 100644 index 0000000..09c6194 --- /dev/null +++ b/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Console\Command\Command; + +final class CommandIsSuccessful extends Constraint +{ + public function toString(): string + { + return 'is successful'; + } + + protected function matches($other): bool + { + return Command::SUCCESS === $other; + } + + protected function failureDescription($other): string + { + return 'the command '.$this->toString(); + } + + protected function additionalFailureDescription($other): string + { + $mapping = [ + Command::FAILURE => 'Command failed.', + Command::INVALID => 'Command was invalid.', + ]; + + return $mapping[$other] ?? sprintf('Command returned exit status %d.', $other); + } +} diff --git a/vendor/symfony/console/Tester/TesterTrait.php b/vendor/symfony/console/Tester/TesterTrait.php new file mode 100644 index 0000000..1ab7a70 --- /dev/null +++ b/vendor/symfony/console/Tester/TesterTrait.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use PHPUnit\Framework\Assert; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful; + +/** + * @author Amrouche Hamza + */ +trait TesterTrait +{ + private StreamOutput $output; + private array $inputs = []; + private bool $captureStreamsIndependently = false; + private InputInterface $input; + private int $statusCode; + + /** + * Gets the display returned by the last execution of the command or application. + * + * @throws \RuntimeException If it's called before the execute method + */ + public function getDisplay(bool $normalize = false): string + { + if (!isset($this->output)) { + throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); + } + + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the output written to STDERR by the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + */ + public function getErrorOutput(bool $normalize = false): string + { + if (!$this->captureStreamsIndependently) { + throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); + } + + rewind($this->output->getErrorOutput()->getStream()); + + $display = stream_get_contents($this->output->getErrorOutput()->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command or application. + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command or application. + */ + public function getOutput(): OutputInterface + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the command or application. + * + * @throws \RuntimeException If it's called before the execute method + */ + public function getStatusCode(): int + { + return $this->statusCode ?? throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); + } + + public function assertCommandIsSuccessful(string $message = ''): void + { + Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message); + } + + /** + * Sets the user inputs. + * + * @param array $inputs An array of strings representing each input + * passed to the command input stream + * + * @return $this + */ + public function setInputs(array $inputs): static + { + $this->inputs = $inputs; + + return $this; + } + + /** + * Initializes the output property. + * + * Available options: + * + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + */ + private function initOutput(array $options): void + { + $this->captureStreamsIndependently = $options['capture_stderr_separately'] ?? false; + if (!$this->captureStreamsIndependently) { + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + } else { + $this->output = new ConsoleOutput( + $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL, + $options['decorated'] ?? null + ); + + $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); + $errorOutput->setFormatter($this->output->getFormatter()); + $errorOutput->setVerbosity($this->output->getVerbosity()); + $errorOutput->setDecorated($this->output->isDecorated()); + + $reflectedOutput = new \ReflectionObject($this->output); + $strErrProperty = $reflectedOutput->getProperty('stderr'); + $strErrProperty->setValue($this->output, $errorOutput); + + $reflectedParent = $reflectedOutput->getParentClass(); + $streamProperty = $reflectedParent->getProperty('stream'); + $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); + } + } + + /** + * @return resource + */ + private static function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + foreach ($inputs as $input) { + fwrite($stream, $input.\PHP_EOL); + } + + rewind($stream); + + return $stream; + } +} diff --git a/vendor/symfony/console/composer.json b/vendor/symfony/console/composer.json new file mode 100644 index 0000000..0ed1bd9 --- /dev/null +++ b/vendor/symfony/console/composer.json @@ -0,0 +1,54 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Eases the creation of beautiful and testable command line interfaces", + "keywords": ["console", "cli", "command-line", "terminal"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "psr/log": "^1|^2|^3" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Console\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/dependency-injection/Alias.php b/vendor/symfony/dependency-injection/Alias.php new file mode 100644 index 0000000..c5b91ed --- /dev/null +++ b/vendor/symfony/dependency-injection/Alias.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +class Alias +{ + private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%alias_id%" service alias is deprecated. You should stop using it, as it will be removed in the future.'; + + private string $id; + private bool $public; + private array $deprecation = []; + + public function __construct(string $id, bool $public = false) + { + $this->id = $id; + $this->public = $public; + } + + /** + * Checks if this DI Alias should be public or not. + */ + public function isPublic(): bool + { + return $this->public; + } + + /** + * Sets if this Alias is public. + * + * @return $this + */ + public function setPublic(bool $boolean): static + { + $this->public = $boolean; + + return $this; + } + + /** + * Whether this alias is private. + */ + public function isPrivate(): bool + { + return !$this->public; + } + + /** + * Whether this alias is deprecated, that means it should not be referenced + * anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function setDeprecated(string $package, string $version, string $message): static + { + if ('' !== $message) { + if (preg_match('#[\r\n]|\*/#', $message)) { + throw new InvalidArgumentException('Invalid characters found in deprecation template.'); + } + + if (!str_contains($message, '%alias_id%')) { + throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.'); + } + } + + $this->deprecation = ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE]; + + return $this; + } + + public function isDeprecated(): bool + { + return (bool) $this->deprecation; + } + + /** + * @param string $id Service id relying on this definition + */ + public function getDeprecation(string $id): array + { + return [ + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => str_replace('%alias_id%', $id, $this->deprecation['message']), + ]; + } + + public function __toString(): string + { + return $this->id; + } +} diff --git a/vendor/symfony/dependency-injection/Argument/AbstractArgument.php b/vendor/symfony/dependency-injection/Argument/AbstractArgument.php new file mode 100644 index 0000000..b04f9b8 --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/AbstractArgument.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents an abstract service argument, which have to be set by a compiler pass or a DI extension. + */ +final class AbstractArgument +{ + private string $text; + private string $context = ''; + + public function __construct(string $text = '') + { + $this->text = trim($text, '. '); + } + + public function setContext(string $context): void + { + $this->context = $context.' is abstract'.('' === $this->text ? '' : ': '); + } + + public function getText(): string + { + return $this->text; + } + + public function getTextWithContext(): string + { + return $this->context.$this->text.'.'; + } +} diff --git a/vendor/symfony/dependency-injection/Argument/ArgumentInterface.php b/vendor/symfony/dependency-injection/Argument/ArgumentInterface.php new file mode 100644 index 0000000..a160393 --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/ArgumentInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents a complex argument containing nested values. + * + * @author Titouan Galopin + */ +interface ArgumentInterface +{ + public function getValues(): array; + + public function setValues(array $values): void; +} diff --git a/vendor/symfony/dependency-injection/Argument/BoundArgument.php b/vendor/symfony/dependency-injection/Argument/BoundArgument.php new file mode 100644 index 0000000..22d9414 --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/BoundArgument.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * @author Guilhem Niot + */ +final class BoundArgument implements ArgumentInterface +{ + public const SERVICE_BINDING = 0; + public const DEFAULTS_BINDING = 1; + public const INSTANCEOF_BINDING = 2; + + private static int $sequence = 0; + + private mixed $value; + private ?int $identifier = null; + private ?bool $used = null; + private int $type; + private ?string $file; + + public function __construct(mixed $value, bool $trackUsage = true, int $type = 0, ?string $file = null) + { + $this->value = $value; + if ($trackUsage) { + $this->identifier = ++self::$sequence; + } else { + $this->used = true; + } + $this->type = $type; + $this->file = $file; + } + + public function getValues(): array + { + return [$this->value, $this->identifier, $this->used, $this->type, $this->file]; + } + + public function setValues(array $values): void + { + if (5 === \count($values)) { + [$this->value, $this->identifier, $this->used, $this->type, $this->file] = $values; + } else { + [$this->value, $this->identifier, $this->used] = $values; + } + } +} diff --git a/vendor/symfony/dependency-injection/Argument/IteratorArgument.php b/vendor/symfony/dependency-injection/Argument/IteratorArgument.php new file mode 100644 index 0000000..1e2de6d --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/IteratorArgument.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents a collection of values to lazily iterate over. + * + * @author Titouan Galopin + */ +class IteratorArgument implements ArgumentInterface +{ + private array $values; + + public function __construct(array $values) + { + $this->setValues($values); + } + + public function getValues(): array + { + return $this->values; + } + + public function setValues(array $values): void + { + $this->values = $values; + } +} diff --git a/vendor/symfony/dependency-injection/Argument/LazyClosure.php b/vendor/symfony/dependency-injection/Argument/LazyClosure.php new file mode 100644 index 0000000..230363a --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/LazyClosure.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class LazyClosure +{ + public readonly object $service; + + public function __construct( + private \Closure $initializer, + ) { + unset($this->service); + } + + public function __get(mixed $name): mixed + { + if ('service' !== $name) { + throw new InvalidArgumentException(sprintf('Cannot read property "%s" from a lazy closure.', $name)); + } + + if (isset($this->initializer)) { + $this->service = ($this->initializer)(); + unset($this->initializer); + } + + return $this->service; + } + + public static function getCode(string $initializer, array $callable, Definition $definition, ContainerBuilder $container, ?string $id): string + { + $method = $callable[1]; + $asClosure = 'Closure' === ($definition->getClass() ?: 'Closure'); + + if ($asClosure) { + $class = ($callable[0] instanceof Reference ? $container->findDefinition($callable[0]) : $callable[0])->getClass(); + } else { + $class = $definition->getClass(); + } + + $r = $container->getReflectionClass($class); + + if (null !== $id) { + $id = sprintf(' for service "%s"', $id); + } + + if (!$asClosure) { + $id = str_replace('%', '%%', (string) $id); + + if (!$r || !$r->isInterface()) { + throw new RuntimeException(sprintf("Cannot create adapter{$id} because \"%s\" is not an interface.", $class)); + } + if (1 !== \count($method = $r->getMethods())) { + throw new RuntimeException(sprintf("Cannot create adapter{$id} because interface \"%s\" doesn't have exactly one method.", $class)); + } + $method = $method[0]->name; + } elseif (!$r || !$r->hasMethod($method)) { + throw new RuntimeException("Cannot create lazy closure{$id} because its corresponding callable is invalid."); + } + + $methodReflector = $r->getMethod($method); + $code = ProxyHelper::exportSignature($methodReflector, true, $args); + + if ($asClosure) { + $code = ' { '.preg_replace('/: static$/', ': \\'.$r->name, $code); + } else { + $code = ' implements \\'.$r->name.' { '.$code; + } + + $code = 'new class('.$initializer.') extends \\'.self::class + .$code.' { '.($methodReflector->hasReturnType() && 'void' === (string) $methodReflector->getReturnType() ? '' : 'return ').'$this->service->'.$callable[1].'('.$args.'); } ' + .'}'; + + return $asClosure ? '('.$code.')->'.$method.'(...)' : $code; + } +} diff --git a/vendor/symfony/dependency-injection/Argument/RewindableGenerator.php b/vendor/symfony/dependency-injection/Argument/RewindableGenerator.php new file mode 100644 index 0000000..9fee374 --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/RewindableGenerator.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * @internal + */ +class RewindableGenerator implements \IteratorAggregate, \Countable +{ + private \Closure $generator; + private \Closure|int $count; + + public function __construct(callable $generator, int|callable $count) + { + $this->generator = $generator(...); + $this->count = \is_int($count) ? $count : $count(...); + } + + public function getIterator(): \Traversable + { + $g = $this->generator; + + return $g(); + } + + public function count(): int + { + if (!\is_int($count = $this->count)) { + $this->count = $count(); + } + + return $this->count; + } +} diff --git a/vendor/symfony/dependency-injection/Argument/ServiceClosureArgument.php b/vendor/symfony/dependency-injection/Argument/ServiceClosureArgument.php new file mode 100644 index 0000000..3537540 --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/ServiceClosureArgument.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Represents a service wrapped in a memoizing closure. + * + * @author Nicolas Grekas + */ +class ServiceClosureArgument implements ArgumentInterface +{ + private array $values; + + public function __construct(mixed $value) + { + $this->values = [$value]; + } + + public function getValues(): array + { + return $this->values; + } + + public function setValues(array $values): void + { + if ([0] !== array_keys($values)) { + throw new InvalidArgumentException('A ServiceClosureArgument must hold one and only one value.'); + } + + $this->values = $values; + } +} diff --git a/vendor/symfony/dependency-injection/Argument/ServiceLocator.php b/vendor/symfony/dependency-injection/Argument/ServiceLocator.php new file mode 100644 index 0000000..8276f6a --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/ServiceLocator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class ServiceLocator extends BaseServiceLocator +{ + private \Closure $factory; + private array $serviceMap; + private ?array $serviceTypes; + + public function __construct(\Closure $factory, array $serviceMap, ?array $serviceTypes = null) + { + $this->factory = $factory; + $this->serviceMap = $serviceMap; + $this->serviceTypes = $serviceTypes; + parent::__construct($serviceMap); + } + + public function get(string $id): mixed + { + return match (\count($this->serviceMap[$id] ?? [])) { + 0 => parent::get($id), + 1 => $this->serviceMap[$id][0], + default => ($this->factory)(...$this->serviceMap[$id]), + }; + } + + public function getProvidedServices(): array + { + return $this->serviceTypes ??= array_map(fn () => '?', $this->serviceMap); + } +} diff --git a/vendor/symfony/dependency-injection/Argument/ServiceLocatorArgument.php b/vendor/symfony/dependency-injection/Argument/ServiceLocatorArgument.php new file mode 100644 index 0000000..555d146 --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/ServiceLocatorArgument.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents a closure acting as a service locator. + * + * @author Nicolas Grekas + */ +class ServiceLocatorArgument implements ArgumentInterface +{ + private array $values; + private ?TaggedIteratorArgument $taggedIteratorArgument = null; + + public function __construct(array|TaggedIteratorArgument $values = []) + { + if ($values instanceof TaggedIteratorArgument) { + $this->taggedIteratorArgument = $values; + $values = []; + } + + $this->setValues($values); + } + + public function getTaggedIteratorArgument(): ?TaggedIteratorArgument + { + return $this->taggedIteratorArgument; + } + + public function getValues(): array + { + return $this->values; + } + + public function setValues(array $values): void + { + $this->values = $values; + } +} diff --git a/vendor/symfony/dependency-injection/Argument/TaggedIteratorArgument.php b/vendor/symfony/dependency-injection/Argument/TaggedIteratorArgument.php new file mode 100644 index 0000000..2e0a1fe --- /dev/null +++ b/vendor/symfony/dependency-injection/Argument/TaggedIteratorArgument.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents a collection of services found by tag name to lazily iterate over. + * + * @author Roland Franssen + */ +class TaggedIteratorArgument extends IteratorArgument +{ + private string $tag; + private mixed $indexAttribute; + private ?string $defaultIndexMethod; + private ?string $defaultPriorityMethod; + private bool $needsIndexes; + private array $exclude; + private bool $excludeSelf = true; + + /** + * @param string $tag The name of the tag identifying the target services + * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection + * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute + * @param bool $needsIndexes Whether indexes are required and should be generated when computing the map + * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute + * @param array $exclude Services to exclude from the iterator + * @param bool $excludeSelf Whether to automatically exclude the referencing service from the iterator + */ + public function __construct(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, bool $needsIndexes = false, ?string $defaultPriorityMethod = null, array $exclude = [], bool $excludeSelf = true) + { + parent::__construct([]); + + if (null === $indexAttribute && $needsIndexes) { + $indexAttribute = preg_match('/[^.]++$/', $tag, $m) ? $m[0] : $tag; + } + + $this->tag = $tag; + $this->indexAttribute = $indexAttribute; + $this->defaultIndexMethod = $defaultIndexMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Name' : null); + $this->needsIndexes = $needsIndexes; + $this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Priority' : null); + $this->exclude = $exclude; + $this->excludeSelf = $excludeSelf; + } + + public function getTag(): string + { + return $this->tag; + } + + public function getIndexAttribute(): ?string + { + return $this->indexAttribute; + } + + public function getDefaultIndexMethod(): ?string + { + return $this->defaultIndexMethod; + } + + public function needsIndexes(): bool + { + return $this->needsIndexes; + } + + public function getDefaultPriorityMethod(): ?string + { + return $this->defaultPriorityMethod; + } + + public function getExclude(): array + { + return $this->exclude; + } + + public function excludeSelf(): bool + { + return $this->excludeSelf; + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AsAlias.php b/vendor/symfony/dependency-injection/Attribute/AsAlias.php new file mode 100644 index 0000000..2f03e5f --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AsAlias.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell under which alias a service should be registered or to use the implemented interface if no parameter is given. + * + * @author Alan Poulain + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +final class AsAlias +{ + /** + * @param string|null $id The id of the alias + * @param bool $public Whether to declare the alias public + */ + public function __construct( + public ?string $id = null, + public bool $public = false, + ) { + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AsDecorator.php b/vendor/symfony/dependency-injection/Attribute/AsDecorator.php new file mode 100644 index 0000000..b5125c8 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AsDecorator.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Declares a decorating service. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsDecorator +{ + /** + * @param string $decorates The service id to decorate + * @param int $priority The priority of this decoration when multiple decorators are declared for the same service + * @param int $onInvalid The behavior to adopt when the decoration is invalid; must be one of the {@see ContainerInterface} constants + */ + public function __construct( + public string $decorates, + public int $priority = 0, + public int $onInvalid = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, + ) { + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AsTaggedItem.php b/vendor/symfony/dependency-injection/Attribute/AsTaggedItem.php new file mode 100644 index 0000000..2e649bd --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AsTaggedItem.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell under which index and priority a service class should be found in tagged iterators/locators. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsTaggedItem +{ + /** + * @param string|null $index The property or method to use to index the item in the locator + * @param int|null $priority The priority of the item; the higher the number, the earlier the tagged service will be located in the locator + */ + public function __construct( + public ?string $index = null, + public ?int $priority = null, + ) { + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/Autoconfigure.php b/vendor/symfony/dependency-injection/Attribute/Autoconfigure.php new file mode 100644 index 0000000..dc2c84c --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/Autoconfigure.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell how a base type should be autoconfigured. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class Autoconfigure +{ + /** + * @param array>|string[]|null $tags The tags to add to the service + * @param array>|null $calls The calls to be made when instantiating the service + * @param array|null $bind The bindings to declare for the service + * @param bool|string|null $lazy Whether the service is lazy-loaded + * @param bool|null $public Whether to declare the service as public + * @param bool|null $shared Whether to declare the service as shared + * @param bool|null $autowire Whether to declare the service as autowired + * @param array|null $properties The properties to define when creating the service + * @param array|string|null $configurator A PHP function, reference or an array containing a class/Reference and a method to call after the service is fully initialized + * @param string|null $constructor The public static method to use to instantiate the service + */ + public function __construct( + public ?array $tags = null, + public ?array $calls = null, + public ?array $bind = null, + public bool|string|null $lazy = null, + public ?bool $public = null, + public ?bool $shared = null, + public ?bool $autowire = null, + public ?array $properties = null, + public array|string|null $configurator = null, + public ?string $constructor = null, + ) { + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutoconfigureTag.php b/vendor/symfony/dependency-injection/Attribute/AutoconfigureTag.php new file mode 100644 index 0000000..dab5595 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutoconfigureTag.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell how a base type should be tagged. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class AutoconfigureTag extends Autoconfigure +{ + /** + * @param string|null $name The tag name to add + * @param array $attributes The tag attributes to attach to the tag + */ + public function __construct(?string $name = null, array $attributes = []) + { + parent::__construct( + tags: [ + [$name ?? 0 => $attributes], + ] + ); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/Autowire.php b/vendor/symfony/dependency-injection/Attribute/Autowire.php new file mode 100644 index 0000000..8740926 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/Autowire.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * Attribute to tell a parameter how to be autowired. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class Autowire +{ + public readonly string|array|Expression|Reference|ArgumentInterface|null $value; + public readonly bool|array $lazy; + + /** + * Use only ONE of the following. + * + * @param string|array|ArgumentInterface|null $value Value to inject (ie "%kernel.project_dir%/some/path") + * @param string|null $service Service ID (ie "some.service") + * @param string|null $expression Expression (ie 'service("some.service").someMethod()') + * @param string|null $env Environment variable name (ie 'SOME_ENV_VARIABLE') + * @param string|null $param Parameter name (ie 'some.parameter.name') + * @param bool|class-string|class-string[] $lazy Whether to use lazy-loading for this argument + */ + public function __construct( + string|array|ArgumentInterface|null $value = null, + ?string $service = null, + ?string $expression = null, + ?string $env = null, + ?string $param = null, + bool|string|array $lazy = false, + ) { + if ($this->lazy = \is_string($lazy) ? [$lazy] : $lazy) { + if (null !== ($expression ?? $env ?? $param)) { + throw new LogicException('#[Autowire] attribute cannot be $lazy and use $expression, $env, or $param.'); + } + if (null !== $value && null !== $service) { + throw new LogicException('#[Autowire] attribute cannot declare $value and $service at the same time.'); + } + } elseif (!(null !== $value xor null !== $service xor null !== $expression xor null !== $env xor null !== $param)) { + throw new LogicException('#[Autowire] attribute must declare exactly one of $service, $expression, $env, $param or $value.'); + } + + if (\is_string($value) && str_starts_with($value, '@')) { + match (true) { + str_starts_with($value, '@@') => $value = substr($value, 1), + str_starts_with($value, '@=') => $expression = substr($value, 2), + default => $service = substr($value, 1), + }; + } + + $this->value = match (true) { + null !== $service => new Reference($service), + null !== $expression => class_exists(Expression::class) ? new Expression($expression) : throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'), + null !== $env => "%env($env)%", + null !== $param => "%$param%", + default => $value, + }; + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutowireCallable.php b/vendor/symfony/dependency-injection/Attribute/AutowireCallable.php new file mode 100644 index 0000000..bb17be5 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutowireCallable.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Attribute to tell which callable to give to an argument of type Closure. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireCallable extends AutowireInline +{ + /** + * @param string|array|null $callable The callable to autowire + * @param string|null $service The service containing the callable to autowire + * @param string|null $method The method name that will be autowired + * @param bool|class-string $lazy Whether to use lazy-loading for this argument + */ + public function __construct( + string|array|null $callable = null, + ?string $service = null, + ?string $method = null, + bool|string $lazy = false, + ) { + if (!(null !== $callable xor null !== $service)) { + throw new LogicException('#[AutowireCallable] attribute must declare exactly one of $callable or $service.'); + } + if (null === $service && null !== $method) { + throw new LogicException('#[AutowireCallable] attribute cannot have a $method without a $service.'); + } + + Autowire::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy); + } + + public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition + { + return (new Definition($type = \is_array($this->lazy) ? current($this->lazy) : ($type ?: 'Closure'))) + ->setFactory(['Closure', 'fromCallable']) + ->setArguments([\is_array($value) ? $value + [1 => '__invoke'] : $value]) + ->setLazy($this->lazy || 'Closure' !== $type && 'callable' !== (string) $parameter->getType()); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutowireDecorated.php b/vendor/symfony/dependency-injection/Attribute/AutowireDecorated.php new file mode 100644 index 0000000..58f77b8 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutowireDecorated.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * Autowires the inner object of decorating services. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireDecorated +{ +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutowireInline.php b/vendor/symfony/dependency-injection/Attribute/AutowireInline.php new file mode 100644 index 0000000..157df67 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutowireInline.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** + * Allows inline service definition for an argument. + * + * Using this attribute on a class autowires a new instance + * which is not shared between different services. + * + * $class a FQCN, or an array to define a factory. + * Use the "@" prefix to reference a service. + * + * @author Ismail Özgün Turan + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireInline extends Autowire +{ + public function __construct(string|array|null $class = null, array $arguments = [], array $calls = [], array $properties = [], ?string $parent = null, bool|string $lazy = false) + { + if (null === $class && null === $parent) { + throw new LogicException('#[AutowireInline] attribute should declare either $class or $parent.'); + } + + parent::__construct([ + \is_array($class) ? 'factory' : 'class' => $class, + 'arguments' => $arguments, + 'calls' => $calls, + 'properties' => $properties, + 'parent' => $parent, + ], lazy: $lazy); + } + + public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition + { + static $parseDefinition; + static $yamlLoader; + + $parseDefinition ??= new \ReflectionMethod(YamlFileLoader::class, 'parseDefinition'); + $yamlLoader ??= $parseDefinition->getDeclaringClass()->newInstanceWithoutConstructor(); + + if (isset($value['factory'])) { + $value['class'] = $type; + $value['factory'][0] ??= $type; + $value['factory'][1] ??= '__invoke'; + } + $class = $parameter->getDeclaringClass(); + + return $parseDefinition->invoke($yamlLoader, $class->name, $value, $class->getFileName(), ['autowire' => true], true); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutowireIterator.php b/vendor/symfony/dependency-injection/Attribute/AutowireIterator.php new file mode 100644 index 0000000..2f845c8 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutowireIterator.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; + +/** + * Autowires an iterator of services based on a tag name. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireIterator extends Autowire +{ + /** + * @see ServiceSubscriberInterface::getSubscribedServices() + * + * @param string $tag A tag name to search for to populate the iterator + * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection + * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute + * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute + * @param string|array $exclude A service id or a list of service ids to exclude + * @param bool $excludeSelf Whether to automatically exclude the referencing service from the iterator + */ + public function __construct( + string $tag, + ?string $indexAttribute = null, + ?string $defaultIndexMethod = null, + ?string $defaultPriorityMethod = null, + string|array $exclude = [], + bool $excludeSelf = true, + ) { + parent::__construct(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf)); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutowireLocator.php b/vendor/symfony/dependency-injection/Attribute/AutowireLocator.php new file mode 100644 index 0000000..3bf4d57 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutowireLocator.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\SubscribedService; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * Autowires a service locator based on a tag name or an explicit list of key => service-type pairs. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireLocator extends Autowire +{ + /** + * @see ServiceSubscriberInterface::getSubscribedServices() + * + * @param string|array $services A tag name or an explicit list of service ids + * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the locator + * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute + * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute + * @param string|array $exclude A service id or a list of service ids to exclude + * @param bool $excludeSelf Whether to automatically exclude the referencing service from the locator + */ + public function __construct( + string|array $services, + ?string $indexAttribute = null, + ?string $defaultIndexMethod = null, + ?string $defaultPriorityMethod = null, + string|array $exclude = [], + bool $excludeSelf = true, + ) { + if (\is_string($services)) { + parent::__construct(new ServiceLocatorArgument(new TaggedIteratorArgument($services, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf))); + + return; + } + + $references = []; + + foreach ($services as $key => $type) { + $attributes = []; + + if ($type instanceof Autowire) { + $references[$key] = $type; + continue; + } + + if ($type instanceof SubscribedService) { + $key = $type->key ?? $key; + $attributes = $type->attributes; + $type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s" is used, a type must be set.', SubscribedService::class))); + } + + if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { + throw new InvalidArgumentException(sprintf('"%s" is not a PHP type for key "%s".', \is_string($type) ? $type : get_debug_type($type), $key)); + } + $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('?' === $type[0]) { + $type = substr($type, 1); + $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } + if (\is_int($name = $key)) { + $key = $type; + $name = null; + } + + $references[$key] = new TypedReference($type, $type, $optionalBehavior, $name, $attributes); + } + + parent::__construct(new ServiceLocatorArgument($references)); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutowireMethodOf.php b/vendor/symfony/dependency-injection/Attribute/AutowireMethodOf.php new file mode 100644 index 0000000..4edcb8f --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutowireMethodOf.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Tells which method should be turned into a Closure based on the name of the parameter it's attached to. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireMethodOf extends AutowireCallable +{ + /** + * @param string $service The service containing the method to autowire + * @param bool|class-string $lazy Whether to use lazy-loading for this argument + */ + public function __construct(string $service, bool|string $lazy = false) + { + parent::__construct([new Reference($service)], lazy: $lazy); + } + + public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition + { + $value[1] = $parameter->name; + + return parent::buildDefinition($value, $type, $parameter); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/AutowireServiceClosure.php b/vendor/symfony/dependency-injection/Attribute/AutowireServiceClosure.php new file mode 100644 index 0000000..a640a7f --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/AutowireServiceClosure.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Attribute to wrap a service in a closure that returns it. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireServiceClosure extends Autowire +{ + /** + * @param string $service The service id to wrap in the closure + */ + public function __construct(string $service) + { + parent::__construct(new ServiceClosureArgument(new Reference($service))); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/Exclude.php b/vendor/symfony/dependency-injection/Attribute/Exclude.php new file mode 100644 index 0000000..43efdcf --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/Exclude.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell the class should not be registered as service. + * + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class Exclude +{ +} diff --git a/vendor/symfony/dependency-injection/Attribute/Lazy.php b/vendor/symfony/dependency-injection/Attribute/Lazy.php new file mode 100644 index 0000000..54de2fe --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/Lazy.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PARAMETER)] +class Lazy +{ + public function __construct( + public bool|string|null $lazy = true, + ) { + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/TaggedIterator.php b/vendor/symfony/dependency-injection/Attribute/TaggedIterator.php new file mode 100644 index 0000000..cd558de --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/TaggedIterator.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * Autowires an iterator of services based on a tag name. + * + * @deprecated since Symfony 7.1, use {@see AutowireIterator} instead. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class TaggedIterator extends AutowireIterator +{ + /** + * @param string $tag The tag to look for to populate the iterator + * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection + * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute + * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute + * @param string|string[] $exclude A service id or a list of service ids to exclude + * @param bool $excludeSelf Whether to automatically exclude the referencing service from the iterator + */ + public function __construct( + public string $tag, + public ?string $indexAttribute = null, + public ?string $defaultIndexMethod = null, + public ?string $defaultPriorityMethod = null, + public string|array $exclude = [], + public bool $excludeSelf = true, + ) { + trigger_deprecation('symfony/dependency-injection', '7.1', 'The "%s" attribute is deprecated, use "%s" instead.', self::class, AutowireIterator::class); + + parent::__construct($tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/TaggedLocator.php b/vendor/symfony/dependency-injection/Attribute/TaggedLocator.php new file mode 100644 index 0000000..d122930 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/TaggedLocator.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * Autowires a locator of services based on a tag name. + * + * @deprecated since Symfony 7.1, use {@see AutowireLocator} instead. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class TaggedLocator extends AutowireLocator +{ + /** + * @param string $tag The tag to look for to populate the locator + * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection + * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute + * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute + * @param string|string[] $exclude A service id or a list of service ids to exclude + * @param bool $excludeSelf Whether to automatically exclude the referencing service from the locator + */ + public function __construct( + public string $tag, + public ?string $indexAttribute = null, + public ?string $defaultIndexMethod = null, + public ?string $defaultPriorityMethod = null, + public string|array $exclude = [], + public bool $excludeSelf = true, + ) { + trigger_deprecation('symfony/dependency-injection', '7.1', 'The "%s" attribute is deprecated, use "%s" instead.', self::class, AutowireLocator::class); + + parent::__construct($tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf); + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/Target.php b/vendor/symfony/dependency-injection/Attribute/Target.php new file mode 100644 index 0000000..8255315 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/Target.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * An attribute to tell how a dependency is used and hint named autowiring aliases. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +final class Target +{ + /** + * @param string|null $name The name of the target autowiring alias + */ + public function __construct(public ?string $name = null) + { + } + + public function getParsedName(): string + { + if (null === $this->name) { + throw new LogicException(sprintf('Cannot parse the name of a #[Target] attribute that has not been resolved. Did you forget to call "%s::parseName()"?', __CLASS__)); + } + + return lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->name)))); + } + + public static function parseName(\ReflectionParameter $parameter, ?self &$attribute = null, ?string &$parsedName = null): string + { + $attribute = null; + if (!$target = $parameter->getAttributes(self::class)[0] ?? null) { + $parsedName = (new self($parameter->name))->getParsedName(); + + return $parameter->name; + } + + $attribute = $target->newInstance(); + $name = $attribute->name ??= $parameter->name; + $parsedName = $attribute->getParsedName(); + + if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $parsedName)) { + if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { + $function = $function->class.'::'.$function->name; + } else { + $function = $function->name; + } + + throw new InvalidArgumentException(sprintf('Invalid #[Target] name "%s" on parameter "$%s" of "%s()": the first character must be a letter.', $name, $parameter->name, $function)); + } + + return preg_match('/^[a-zA-Z0-9_\x7f-\xff]++$/', $name) ? $name : $parsedName; + } +} diff --git a/vendor/symfony/dependency-injection/Attribute/When.php b/vendor/symfony/dependency-injection/Attribute/When.php new file mode 100644 index 0000000..af36e05 --- /dev/null +++ b/vendor/symfony/dependency-injection/Attribute/When.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell under which environment this class should be registered as a service. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION | \Attribute::IS_REPEATABLE)] +class When +{ + /** + * @param string $env The environment under which the class will be registered as a service (i.e. "dev", "test", "prod") + */ + public function __construct(public string $env) + { + } +} diff --git a/vendor/symfony/dependency-injection/CHANGELOG.md b/vendor/symfony/dependency-injection/CHANGELOG.md new file mode 100644 index 0000000..54095a8 --- /dev/null +++ b/vendor/symfony/dependency-injection/CHANGELOG.md @@ -0,0 +1,401 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `CheckAliasValidityPass` to check service compatibility with aliased interface + * Add argument `$prepend` to `ContainerConfigurator::extension()` to prepend the configuration instead of appending it + * Have `ServiceLocator` implement `ServiceCollectionInterface` + * Add `#[Lazy]` attribute as shortcut for `#[Autowire(lazy: [bool|string])]` and `#[Autoconfigure(lazy: [bool|string])]` + * Add `#[AutowireMethodOf]` attribute to autowire a method of a service as a callable + * Make `ContainerBuilder::registerAttributeForAutoconfiguration()` propagate to attribute classes that extend the registered class + * Add argument `$prepend` to `FileLoader::construct()` to prepend loaded configuration instead of appending it + * [BC BREAK] When used in the `prependExtension()` method, the `ContainerConfigurator::import()` method now prepends the configuration instead of appending it + * Cast env vars to null or bool when referencing them using `#[Autowire(env: '...')]` depending on the signature of the corresponding parameter + * Add `#[AutowireInline]` attribute to allow service definition at the class level + * Add `StaticEnvVarLoader` + +7.0 +--- + + * Remove `#[MapDecorated]`, use `#[AutowireDecorated]` instead + * Remove `ProxyHelper`, use `Symfony\Component\VarExporter\ProxyHelper` instead + * Remove `ReferenceSetArgumentTrait` + * Remove support of `@required` annotation, use the `Symfony\Contracts\Service\Attribute\Required` attribute instead + * Require explicit argument when calling `ContainerAwareTrait::setContainer()` + * Remove `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter`, use options `inline_factories` and `inline_class_loader` instead + * Parameter names of `ParameterBag` cannot be numerics + * Remove `ContainerAwareInterface` and `ContainerAwareTrait`, use dependency injection instead + * Add argument `$id` and `$asGhostObject` to `DumperInterface::isProxyCandidate()` and `getProxyCode()` + * Add argument `$source` to `FileLoader::registerClasses()` + +6.4 +--- + + * Allow using `#[Target]` with no arguments to state that a parameter must match a named autowiring alias + * Deprecate `ContainerAwareInterface` and `ContainerAwareTrait`, use dependency injection instead + * Add `defined` env var processor that returns `true` for defined and neither null nor empty env vars + * Add `#[AutowireLocator]` and `#[AutowireIterator]` attributes + * Add `urlencode` env var processor that url encodes a string value + +6.3 +--- + + * Add options `inline_factories` and `inline_class_loader` to `PhpDumper::dump()` + * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter` + * Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation + * Add support for nesting autowiring-related attributes into `#[Autowire(...)]` + * Deprecate undefined and numeric keys with `service_locator` config + * Fail if Target attribute does not exist during compilation + * Enable deprecating parameters with `ContainerBuilder::deprecateParameter()` + * Add `#[AsAlias]` attribute to tell under which alias a service should be registered or to use the implemented interface if no parameter is given + * Allow to trim XML service parameters value by using `trim="true"` attribute + * Allow extending the `Autowire` attribute + * Add `#[Exclude]` to skip autoregistering a class + * Add support for generating lazy closures + * Add support for autowiring services as closures using `#[AutowireCallable]` or `#[AutowireServiceClosure]` + * Add support for `#[Autowire(lazy: true|class-string)]` + * Make it possible to cast callables into single-method interfaces + * Deprecate `#[MapDecorated]`, use `#[AutowireDecorated]` instead + * Deprecate the `@required` annotation, use the `Symfony\Contracts\Service\Attribute\Required` attribute instead + * Add `constructor` option to services declaration and to `#[Autoconfigure]` + +6.2 +--- + + * Use lazy-loading ghost objects and virtual proxies out of the box + * Add arguments `&$asGhostObject` and `$id` to LazyProxy's `DumperInterface` to allow using ghost objects for lazy loading services + * Add `enum` env var processor + * Add `shuffle` env var processor + * Allow #[When] to be extended + * Change the signature of `ContainerAwareInterface::setContainer()` to `setContainer(?ContainerInterface)` + * Deprecate calling `ContainerAwareTrait::setContainer()` without arguments + * Deprecate using numeric parameter names + * Add support for tagged iterators/locators `exclude` option to the xml and yaml loaders/dumpers + * Allow injecting `string $env` into php config closures + * Add `excludeSelf` parameter to `TaggedIteratorArgument` with default value to `true` + to control whether the referencing service should be automatically excluded from the iterator + +6.1 +--- + + * Add `#[MapDecorated]` attribute telling to which parameter the decorated service should be mapped in a decorator + * Add `#[AsDecorator]` attribute to make a service decorates another + * Add `$exclude` to `TaggedIterator` and `TaggedLocator` attributes + * Add `$exclude` to `tagged_iterator` and `tagged_locator` configurator + * Add an `env` function to the expression language provider + * Add an `Autowire` attribute to tell a parameter how to be autowired + * Allow using expressions as service factories + * Add argument type `closure` to help passing closures to services + * Deprecate `ReferenceSetArgumentTrait` + * Add `AbstractExtension` class for DI configuration/definition on a single file + +6.0 +--- + + * Remove `Definition::setPrivate()` and `Alias::setPrivate()`, use `setPublic()` instead + * Remove `inline()` in favor of `inline_service()` and `ref()` in favor of `service()` when using the PHP-DSL + * Remove `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead + * Remove `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead + * Remove the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service + +5.4 +--- + * Add `$defaultIndexMethod` and `$defaultPriorityMethod` to `TaggedIterator` and `TaggedLocator` attributes + * Add `service_closure()` to the PHP-DSL + * Add support for autoconfigurable attributes on methods, properties and parameters + * Make auto-aliases private by default + * Add support for autowiring union and intersection types + +5.3 +--- + + * Add `ServicesConfigurator::remove()` in the PHP-DSL + * Add `%env(not:...)%` processor to negate boolean values + * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 + * Add `#[AsTaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators + * Add autoconfigurable attributes + * Add support for autowiring tagged iterators and locators via attributes on PHP 8 + * Add support for per-env configuration in XML and Yaml loaders + * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration + * Add support an integer return value for default_index_method + * Add `#[When(env: 'foo')]` to skip autoregistering a class when the env doesn't match + * Add `env()` and `EnvConfigurator` in the PHP-DSL + * Add support for `ConfigBuilder` in the `PhpFileLoader` + * Add `ContainerConfigurator::env()` to get the current environment + * Add `#[Target]` to tell how a dependency is used and hint named autowiring aliases + +5.2.0 +----- + + * added `param()` and `abstract_arg()` in the PHP-DSL + * deprecated `Definition::setPrivate()` and `Alias::setPrivate()`, use `setPublic()` instead + * added support for the `#[Required]` attribute + +5.1.0 +----- + + * deprecated `inline()` in favor of `inline_service()` and `ref()` in favor of `service()` when using the PHP-DSL + * allow decorators to reference their decorated service using the special `.inner` id + * added support to autowire public typed properties in php 7.4 + * added support for defining method calls, a configurator, and property setters in `InlineServiceConfigurator` + * added possibility to define abstract service arguments + * allowed mixing "parent" and instanceof-conditionals/defaults/bindings + * updated the signature of method `Definition::setDeprecated()` to `Definition::setDeprecation(string $package, string $version, string $message)` + * updated the signature of method `Alias::setDeprecated()` to `Alias::setDeprecation(string $package, string $version, string $message)` + * updated the signature of method `DeprecateTrait::deprecate()` to `DeprecateTrait::deprecation(string $package, string $version, string $message)` + * deprecated the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service, + configure them explicitly instead + * added class `Symfony\Component\DependencyInjection\Dumper\Preloader` to help with preloading on PHP 7.4+ + * added tags `container.preload`/`.no_preload` to declare extra classes to preload/services to not preload + * allowed loading and dumping tags with an attribute named "name" + * deprecated `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead + * deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead + * added support of PHP8 static return type for withers + * added `AliasDeprecatedPublicServicesPass` to deprecate public services to private + +5.0.0 +----- + + * removed support for auto-discovered extension configuration class which does not implement `ConfigurationInterface` + * removed support for non-string default env() parameters + * moved `ServiceSubscriberInterface` to the `Symfony\Contracts\Service` namespace + * removed `RepeatedPass` and `RepeatablePassInterface` + * removed support for short factory/configurator syntax from `YamlFileLoader` + * removed `ResettableContainerInterface`, use `ResetInterface` instead + * added argument `$returnsClone` to `Definition::addMethodCall()` + * removed `tagged`, use `tagged_iterator` instead + +4.4.0 +----- + + * added `CheckTypeDeclarationsPass` to check injected parameters type during compilation + * added support for opcache.preload by generating a preloading script in the cache folder + * added support for dumping the container in one file instead of many files + * deprecated support for short factories and short configurators in Yaml + * added `tagged_iterator` alias for `tagged` which might be deprecated in a future version + * deprecated passing an instance of `Symfony\Component\DependencyInjection\Parameter` as class name to `Symfony\Component\DependencyInjection\Definition` + * added support for binding iterable and tagged services + * made singly-implemented interfaces detection be scoped by file + * added ability to define a static priority method for tagged service + * added support for improved syntax to define method calls in Yaml + * made the `%env(base64:...)%` processor able to decode base64url + * added ability to choose behavior of decorations on non existent decorated services + +4.3.0 +----- + + * added `%env(trim:...)%` processor to trim a string value + * added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%` + * added `%env(url:...)%` processor to convert a URL or DNS into an array of components + * added `%env(query_string:...)%` processor to convert a query string into an array of key values + * added support for deprecating aliases + * made `ContainerParametersResource` final and not implement `Serializable` anymore + * added `ReverseContainer`: a container that turns services back to their ids + * added ability to define an index for a tagged collection + * added ability to define an index for services in an injected service locator argument + * made `ServiceLocator` implement `ServiceProviderInterface` + * deprecated support for non-string default env() parameters + * added `%env(require:...)%` processor to `require()` a PHP file and use the value returned from it + +4.2.0 +----- + + * added `ContainerBuilder::registerAliasForArgument()` to support autowiring by type+name + * added support for binding by type+name + * added `ServiceSubscriberTrait` to ease implementing `ServiceSubscriberInterface` using methods' return types + * added `ServiceLocatorArgument` and `!service_locator` config tag for creating optimized service-locators + * added support for autoconfiguring bindings + * added `%env(key:...)%` processor to fetch a specific key from an array + * deprecated `ServiceSubscriberInterface`, use the same interface from the `Symfony\Contracts\Service` namespace instead + * deprecated `ResettableContainerInterface`, use `Symfony\Contracts\Service\ResetInterface` instead + +4.1.0 +----- + + * added support for variadics in named arguments + * added PSR-11 `ContainerBagInterface` and its `ContainerBag` implementation to access parameters as-a-service + * added support for service's decorators autowiring + * deprecated the `TypedReference::canBeAutoregistered()` and `TypedReference::getRequiringClass()` methods + * environment variables are validated when used in extension configuration + * deprecated support for auto-discovered extension configuration class which does not implement `ConfigurationInterface` + +4.0.0 +----- + + * Relying on service auto-registration while autowiring is not supported anymore. + Explicitly inject your dependencies or create services whose ids are + their fully-qualified class name. + + Before: + + ```php + namespace App\Controller; + + use App\Mailer; + + class DefaultController + { + public function __construct(Mailer $mailer) { + // ... + } + + // ... + } + ``` + ```yml + services: + App\Controller\DefaultController: + autowire: true + ``` + + After: + + ```php + // same PHP code + ``` + ```yml + services: + App\Controller\DefaultController: + autowire: true + + # or + # App\Controller\DefaultController: + # arguments: { $mailer: "@App\Mailer" } + + App\Mailer: + autowire: true + ``` + * removed autowiring services based on the types they implement + * added a third `$methodName` argument to the `getProxyFactoryCode()` method + of the `DumperInterface` + * removed support for autowiring types + * removed `Container::isFrozen` + * removed support for dumping an ucompiled container in `PhpDumper` + * removed support for generating a dumped `Container` without populating the method map + * removed support for case insensitive service identifiers + * removed the `DefinitionDecorator` class, replaced by `ChildDefinition` + * removed the `AutowireServiceResource` class and related `AutowirePass::createResourceForClass()` method + * removed `LoggingFormatter`, `Compiler::getLoggingFormatter()` and `addLogMessage()` class and methods, use the `ContainerBuilder::log()` method instead + * removed `FactoryReturnTypePass` + * removed `ContainerBuilder::addClassResource()`, use the `addObjectResource()` or the `getReflectionClass()` method instead. + * removed support for top-level anonymous services + * removed silent behavior for unused attributes and elements + * removed support for setting and accessing private services in `Container` + * removed support for setting pre-defined services in `Container` + * removed support for case insensitivity of parameter names + * removed `AutowireExceptionPass` and `AutowirePass::getAutowiringExceptions()`, use `Definition::addError()` and the `DefinitionErrorExceptionPass` instead + +3.4.0 +----- + + * moved the `ExtensionCompilerPass` to before-optimization passes with priority -1000 + * deprecated "public-by-default" definitions and aliases, the new default will be "private" in 4.0 + * added `EnvVarProcessorInterface` and corresponding "container.env_var_processor" tag for processing env vars + * added support for ignore-on-uninitialized references + * deprecated service auto-registration while autowiring + * deprecated the ability to check for the initialization of a private service with the `Container::initialized()` method + * deprecated support for top-level anonymous services in XML + * deprecated case insensitivity of parameter names + * deprecated the `ResolveDefinitionTemplatesPass` class in favor of `ResolveChildDefinitionsPass` + * added `TaggedIteratorArgument` with YAML (`!tagged foo`) and XML (``) support + * deprecated `AutowireExceptionPass` and `AutowirePass::getAutowiringExceptions()`, use `Definition::addError()` and the `DefinitionErrorExceptionPass` instead + +3.3.0 +----- + + * deprecated autowiring services based on the types they implement; + rename (or alias) your services to their FQCN id to make them autowirable + * added "ServiceSubscriberInterface" - to allow for per-class explicit service-locator definitions + * added "container.service_locator" tag for defining service-locator services + * added anonymous services support in YAML configuration files using the `!service` tag. + * added "TypedReference" and "ServiceClosureArgument" for creating service-locator services + * added `ServiceLocator` - a PSR-11 container holding a set of services to be lazily loaded + * added "instanceof" section for local interface-defined configs + * added prototype services for PSR4-based discovery and registration + * added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info + * deprecated `ContainerBuilder::getClassResource()`, use `ContainerBuilder::getReflectionClass()` or `ContainerBuilder::addObjectResource()` instead + * added `ContainerBuilder::fileExists()` for checking and tracking file or directory existence + * deprecated autowiring-types, use aliases instead + * added support for omitting the factory class name in a service definition if the definition class is set + * deprecated case insensitivity of service identifiers + * added "iterator" argument type for lazy iteration over a set of values and services + * added file-wide configurable defaults for service attributes "public", "tags", + "autowire" and "autoconfigure" + * made the "class" attribute optional, using the "id" as fallback + * using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and + will not be supported anymore in 4.0 + * deprecated the `DefinitionDecorator` class in favor of `ChildDefinition` + * allow config files to be loaded using a glob pattern + * [BC BREAK] the `NullDumper` class is now final + +3.2.0 +----- + + * allowed to prioritize compiler passes by introducing a third argument to `PassConfig::addPass()`, to `Compiler::addPass` and to `ContainerBuilder::addCompilerPass()` + * added support for PHP constants in YAML configuration files + * deprecated the ability to set or unset a private service with the `Container::set()` method + * deprecated the ability to check for the existence of a private service with the `Container::has()` method + * deprecated the ability to request a private service with the `Container::get()` method + * deprecated support for generating a dumped `Container` without populating the method map + +3.0.0 +----- + + * removed all deprecated codes from 2.x versions + +2.8.0 +----- + + * deprecated the abstract ContainerAware class in favor of ContainerAwareTrait + * deprecated IntrospectableContainerInterface, to be merged with ContainerInterface in 3.0 + * allowed specifying a directory to recursively load all configuration files it contains + * deprecated the concept of scopes + * added `Definition::setShared()` and `Definition::isShared()` + * added ResettableContainerInterface to be able to reset the container to release memory on shutdown + * added a way to define the priority of service decoration + * added support for service autowiring + +2.7.0 +----- + + * deprecated synchronized services + +2.6.0 +----- + + * added new factory syntax and deprecated the old one + +2.5.0 +----- + + * added DecoratorServicePass and a way to override a service definition (Definition::setDecoratedService()) + * deprecated SimpleXMLElement class. + +2.4.0 +----- + + * added support for expressions in service definitions + * added ContainerAwareTrait to add default container aware behavior to a class + +2.2.0 +----- + + * added Extension::isConfigEnabled() to ease working with enableable configurations + * added an Extension base class with sensible defaults to be used in conjunction + with the Config component. + * added PrependExtensionInterface (to be able to allow extensions to prepend + application configuration settings for any Bundle) + +2.1.0 +----- + + * added IntrospectableContainerInterface (to be able to check if a service + has been initialized or not) + * added ConfigurationExtensionInterface + * added Definition::clearTag() + * component exceptions that inherit base SPL classes are now used exclusively + (this includes dumped containers) + * [BC BREAK] fixed unescaping of class arguments, method + ParameterBag::unescapeValue() was made public diff --git a/vendor/symfony/dependency-injection/ChildDefinition.php b/vendor/symfony/dependency-injection/ChildDefinition.php new file mode 100644 index 0000000..c5905a4 --- /dev/null +++ b/vendor/symfony/dependency-injection/ChildDefinition.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; + +/** + * This definition extends another definition. + * + * @author Johannes M. Schmitt + */ +class ChildDefinition extends Definition +{ + private string $parent; + + /** + * @param string $parent The id of Definition instance to decorate + */ + public function __construct(string $parent) + { + $this->parent = $parent; + } + + /** + * Returns the Definition to inherit from. + */ + public function getParent(): string + { + return $this->parent; + } + + /** + * Sets the Definition to inherit from. + * + * @return $this + */ + public function setParent(string $parent): static + { + $this->parent = $parent; + + return $this; + } + + /** + * Gets an argument to pass to the service constructor/factory method. + * + * If replaceArgument() has been used to replace an argument, this method + * will return the replacement value. + * + * @throws OutOfBoundsException When the argument does not exist + */ + public function getArgument(int|string $index): mixed + { + if (\array_key_exists('index_'.$index, $this->arguments)) { + return $this->arguments['index_'.$index]; + } + + return parent::getArgument($index); + } + + /** + * You should always use this method when overwriting existing arguments + * of the parent definition. + * + * If you directly call setArguments() keep in mind that you must follow + * certain conventions when you want to overwrite the arguments of the + * parent definition, otherwise your arguments will only be appended. + * + * @return $this + * + * @throws InvalidArgumentException when $index isn't an integer + */ + public function replaceArgument(int|string $index, mixed $value): static + { + if (\is_int($index)) { + $this->arguments['index_'.$index] = $value; + } elseif (str_starts_with($index, '$')) { + $this->arguments[$index] = $value; + } else { + throw new InvalidArgumentException('The argument must be an existing index or the name of a constructor\'s parameter.'); + } + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php b/vendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php new file mode 100644 index 0000000..0e90821 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractRecursivePass implements CompilerPassInterface +{ + protected ?ContainerBuilder $container; + protected ?string $currentId = null; + protected bool $skipScalars = false; + + private bool $processExpressions = false; + private ExpressionLanguage $expressionLanguage; + private bool $inExpression = false; + + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + + try { + $this->processValue($container->getDefinitions(), true); + } finally { + $this->container = null; + } + } + + protected function enableExpressionProcessing(): void + { + $this->processExpressions = true; + } + + protected function inExpression(bool $reset = true): bool + { + $inExpression = $this->inExpression; + if ($reset) { + $this->inExpression = false; + } + + return $inExpression; + } + + /** + * Processes a value found in a definition tree. + * + * @return mixed + */ + protected function processValue(mixed $value, bool $isRoot = false) + { + if (\is_array($value)) { + foreach ($value as $k => $v) { + if ((!$v || \is_scalar($v)) && $this->skipScalars) { + continue; + } + if ($isRoot) { + if ($v instanceof Definition && $v->hasTag('container.excluded')) { + continue; + } + $this->currentId = $k; + } + if ($v !== $processedValue = $this->processValue($v, $isRoot)) { + $value[$k] = $processedValue; + } + } + } elseif ($value instanceof ArgumentInterface) { + $value->setValues($this->processValue($value->getValues())); + } elseif ($value instanceof Expression && $this->processExpressions) { + $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container', 'args' => 'args']); + } elseif ($value instanceof Definition) { + $value->setArguments($this->processValue($value->getArguments())); + $value->setProperties($this->processValue($value->getProperties())); + $value->setMethodCalls($this->processValue($value->getMethodCalls())); + + $changes = $value->getChanges(); + if (isset($changes['factory'])) { + if (\is_string($factory = $value->getFactory()) && str_starts_with($factory, '@=')) { + if (!class_exists(Expression::class)) { + throw new LogicException('Expressions cannot be used in service factories without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + $factory = new Expression(substr($factory, 2)); + } + if (($factory = $this->processValue($factory)) instanceof Expression) { + $factory = '@='.$factory; + } + $value->setFactory($factory); + } + if (isset($changes['configurator'])) { + $value->setConfigurator($this->processValue($value->getConfigurator())); + } + } + + return $value; + } + + /** + * @throws RuntimeException + */ + protected function getConstructor(Definition $definition, bool $required): ?\ReflectionFunctionAbstract + { + if ($definition->isSynthetic()) { + return null; + } + + if (\is_string($factory = $definition->getFactory())) { + if (str_starts_with($factory, '@=')) { + return new \ReflectionFunction(static function (...$args) {}); + } + + if (!\function_exists($factory)) { + throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory)); + } + $r = new \ReflectionFunction($factory); + if (false !== $r->getFileName() && file_exists($r->getFileName())) { + $this->container->fileExists($r->getFileName()); + } + + return $r; + } + + if ($factory) { + [$class, $method] = $factory; + + if ('__construct' === $method) { + throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId)); + } + + if ($class instanceof Reference) { + $factoryDefinition = $this->container->findDefinition((string) $class); + while ((null === $class = $factoryDefinition->getClass()) && $factoryDefinition instanceof ChildDefinition) { + $factoryDefinition = $this->container->findDefinition($factoryDefinition->getParent()); + } + } elseif ($class instanceof Definition) { + $class = $class->getClass(); + } else { + $class ??= $definition->getClass(); + } + + return $this->getReflectionMethod(new Definition($class), $method); + } + + while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) { + $definition = $this->container->findDefinition($definition->getParent()); + } + + try { + if (!$r = $this->container->getReflectionClass($class)) { + if (null === $class) { + throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId)); + } + + throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); + } + } catch (\ReflectionException $e) { + throw new RuntimeException(sprintf('Invalid service "%s": ', $this->currentId).lcfirst($e->getMessage())); + } + if (!$r = $r->getConstructor()) { + if ($required) { + throw new RuntimeException(sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, sprintf($class !== $this->currentId ? ' "%s"' : '', $class))); + } + } elseif (!$r->isPublic()) { + throw new RuntimeException(sprintf('Invalid service "%s": ', $this->currentId).sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class).' must be public.'); + } + + return $r; + } + + /** + * @throws RuntimeException + */ + protected function getReflectionMethod(Definition $definition, string $method): \ReflectionFunctionAbstract + { + if ('__construct' === $method) { + return $this->getConstructor($definition, true); + } + + while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) { + $definition = $this->container->findDefinition($definition->getParent()); + } + + if (null === $class) { + throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId)); + } + + if (!$r = $this->container->getReflectionClass($class)) { + throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); + } + + if (!$r->hasMethod($method)) { + if ($r->hasMethod('__call') && ($r = $r->getMethod('__call')) && $r->isPublic()) { + return new \ReflectionMethod(static function (...$arguments) {}, '__invoke'); + } + + throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); + } + + $r = $r->getMethod($method); + if (!$r->isPublic()) { + throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); + } + + return $r; + } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (!isset($this->expressionLanguage)) { + if (!class_exists(ExpressionLanguage::class)) { + throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + + $providers = $this->container->getExpressionLanguageProviders(); + $this->expressionLanguage = new ExpressionLanguage(null, $providers, function (string $arg): string { + if ('""' === substr_replace($arg, '', 1, -1)) { + $id = stripcslashes(substr($arg, 1, -1)); + $this->inExpression = true; + $arg = $this->processValue(new Reference($id)); + $this->inExpression = false; + if (!$arg instanceof Reference) { + throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, "%s" returned for service("%s").', static::class, get_debug_type($arg), $id)); + } + $arg = sprintf('"%s"', $arg); + } + + return sprintf('$this->get(%s)', $arg); + }); + } + + return $this->expressionLanguage; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php b/vendor/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php new file mode 100644 index 0000000..7aa7ec2 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $aliases = []; + + public function process(ContainerBuilder $container): void + { + foreach ($container->findTaggedServiceIds('container.private') as $id => $tags) { + if (null === $package = $tags[0]['package'] ?? null) { + throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "container.private" tag on the "%s" service.', $id)); + } + + if (null === $version = $tags[0]['version'] ?? null) { + throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "container.private" tag on the "%s" service.', $id)); + } + + $definition = $container->getDefinition($id); + if (!$definition->isPublic() || $definition->isPrivate()) { + continue; + } + + $container + ->setAlias($id, $aliasId = '.container.private.'.$id) + ->setPublic(true) + ->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.'); + + $container->setDefinition($aliasId, $definition); + + $this->aliases[$id] = $aliasId; + } + + parent::process($container); + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) { + return new Reference($this->aliases[$id], $value->getInvalidBehavior()); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php b/vendor/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php new file mode 100644 index 0000000..6b84fc9 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * Run this pass before passes that need to know more about the relation of + * your services. + * + * This class will populate the ServiceReferenceGraph with information. You can + * retrieve the graph in other passes from the compiler. + * + * @author Johannes M. Schmitt + * @author Nicolas Grekas + */ +class AnalyzeServiceReferencesPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private ServiceReferenceGraph $graph; + private ?Definition $currentDefinition = null; + private bool $onlyConstructorArguments; + private bool $hasProxyDumper; + private bool $lazy; + private bool $byConstructor; + private bool $byFactory; + private array $definitions; + private array $aliases; + + /** + * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls + */ + public function __construct(bool $onlyConstructorArguments = false, bool $hasProxyDumper = true) + { + $this->onlyConstructorArguments = $onlyConstructorArguments; + $this->hasProxyDumper = $hasProxyDumper; + $this->enableExpressionProcessing(); + } + + /** + * Processes a ContainerBuilder object to populate the service reference graph. + */ + public function process(ContainerBuilder $container): void + { + $this->container = $container; + $this->graph = $container->getCompiler()->getServiceReferenceGraph(); + $this->graph->clear(); + $this->lazy = false; + $this->byConstructor = false; + $this->byFactory = false; + $this->definitions = $container->getDefinitions(); + $this->aliases = $container->getAliases(); + + foreach ($this->aliases as $id => $alias) { + $targetId = $this->getDefinitionId((string) $alias); + $this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null); + } + + try { + parent::process($container); + } finally { + $this->aliases = $this->definitions = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + $lazy = $this->lazy; + $inExpression = $this->inExpression(); + + if ($value instanceof ArgumentInterface) { + $this->lazy = !$this->byFactory || !$value instanceof IteratorArgument; + parent::processValue($value->getValues()); + $this->lazy = $lazy; + + return $value; + } + if ($value instanceof Reference) { + $targetId = $this->getDefinitionId((string) $value); + $targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null; + + $this->graph->connect( + $this->currentId, + $this->currentDefinition, + $targetId, + $targetDefinition, + $value, + $this->lazy || ($this->hasProxyDumper && $targetDefinition?->isLazy()), + ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(), + $this->byConstructor + ); + + if ($inExpression) { + $this->graph->connect( + '.internal.reference_in_expression', + null, + $targetId, + $targetDefinition, + $value, + $this->lazy || $targetDefinition?->isLazy(), + true + ); + } + + return $value; + } + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + if ($isRoot) { + if ($value->isSynthetic() || $value->isAbstract()) { + return $value; + } + $this->currentDefinition = $value; + } elseif ($this->currentDefinition === $value) { + return $value; + } + $this->lazy = false; + + $byConstructor = $this->byConstructor; + $this->byConstructor = $isRoot || $byConstructor; + + $byFactory = $this->byFactory; + $this->byFactory = true; + if (\is_string($factory = $value->getFactory()) && str_starts_with($factory, '@=')) { + if (!class_exists(Expression::class)) { + throw new LogicException('Expressions cannot be used in service factories without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + + $factory = new Expression(substr($factory, 2)); + } + $this->processValue($factory); + $this->byFactory = $byFactory; + + $this->processValue($value->getArguments()); + + $properties = $value->getProperties(); + $setters = $value->getMethodCalls(); + + // Any references before a "wither" are part of the constructor-instantiation graph + $lastWitherIndex = null; + foreach ($setters as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + + if (null !== $lastWitherIndex) { + $this->processValue($properties); + $setters = $properties = []; + + foreach ($value->getMethodCalls() as $k => $call) { + if (null === $lastWitherIndex) { + $setters[] = $call; + continue; + } + + if ($lastWitherIndex === $k) { + $lastWitherIndex = null; + } + + $this->processValue($call); + } + } + + $this->byConstructor = $byConstructor; + + if (!$this->onlyConstructorArguments) { + $this->processValue($properties); + $this->processValue($setters); + $this->processValue($value->getConfigurator()); + } + $this->lazy = $lazy; + + return $value; + } + + private function getDefinitionId(string $id): ?string + { + while (isset($this->aliases[$id])) { + $id = (string) $this->aliases[$id]; + } + + return isset($this->definitions[$id]) ? $id : null; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php b/vendor/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php new file mode 100644 index 0000000..22aaedd --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * @author Alexander M. Turek + */ +final class AttributeAutoconfigurationPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $classAttributeConfigurators = []; + private array $methodAttributeConfigurators = []; + private array $propertyAttributeConfigurators = []; + private array $parameterAttributeConfigurators = []; + + public function process(ContainerBuilder $container): void + { + if (!$container->getAutoconfiguredAttributes()) { + return; + } + + foreach ($container->getAutoconfiguredAttributes() as $attributeName => $callable) { + $callableReflector = new \ReflectionFunction($callable(...)); + if ($callableReflector->getNumberOfParameters() <= 2) { + $this->classAttributeConfigurators[$attributeName] = $callable; + continue; + } + + $reflectorParameter = $callableReflector->getParameters()[2]; + $parameterType = $reflectorParameter->getType(); + $types = []; + if ($parameterType instanceof \ReflectionUnionType) { + foreach ($parameterType->getTypes() as $type) { + $types[] = $type->getName(); + } + } elseif ($parameterType instanceof \ReflectionNamedType) { + $types[] = $parameterType->getName(); + } else { + throw new LogicException(sprintf('Argument "$%s" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in "%s" on line "%d".', $reflectorParameter->getName(), $callableReflector->getFileName(), $callableReflector->getStartLine())); + } + + try { + $attributeReflector = new \ReflectionClass($attributeName); + } catch (\ReflectionException) { + continue; + } + + $targets = $attributeReflector->getAttributes(\Attribute::class)[0] ?? 0; + $targets = $targets ? $targets->getArguments()[0] ?? -1 : 0; + + foreach (['class', 'method', 'property', 'parameter'] as $symbol) { + if (['Reflector'] !== $types) { + if (!\in_array('Reflection'.ucfirst($symbol), $types, true)) { + continue; + } + if (!($targets & \constant('Attribute::TARGET_'.strtoupper($symbol)))) { + throw new LogicException(sprintf('Invalid type "Reflection%s" on argument "$%s": attribute "%s" cannot target a '.$symbol.' in "%s" on line "%d".', ucfirst($symbol), $reflectorParameter->getName(), $attributeName, $callableReflector->getFileName(), $callableReflector->getStartLine())); + } + } + $this->{$symbol.'AttributeConfigurators'}[$attributeName] = $callable; + } + } + + parent::process($container); + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof Definition + || !$value->isAutoconfigured() + || $value->isAbstract() + || $value->hasTag('container.ignore_attributes') + || !($classReflector = $this->container->getReflectionClass($value->getClass(), false)) + ) { + return parent::processValue($value, $isRoot); + } + + $instanceof = $value->getInstanceofConditionals(); + $conditionals = $instanceof[$classReflector->getName()] ?? new ChildDefinition(''); + + if ($this->classAttributeConfigurators) { + foreach ($classReflector->getAttributes() as $attribute) { + if ($configurator = $this->findConfigurator($this->classAttributeConfigurators, $attribute->getName())) { + $configurator($conditionals, $attribute->newInstance(), $classReflector); + } + } + } + + if ($this->parameterAttributeConfigurators) { + try { + $constructorReflector = $this->getConstructor($value, false); + } catch (RuntimeException) { + $constructorReflector = null; + } + + if ($constructorReflector) { + foreach ($constructorReflector->getParameters() as $parameterReflector) { + foreach ($parameterReflector->getAttributes() as $attribute) { + if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) { + $configurator($conditionals, $attribute->newInstance(), $parameterReflector); + } + } + } + } + } + + if ($this->methodAttributeConfigurators || $this->parameterAttributeConfigurators) { + foreach ($classReflector->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflector) { + if ($methodReflector->isConstructor() || $methodReflector->isDestructor()) { + continue; + } + + if ($this->methodAttributeConfigurators) { + foreach ($methodReflector->getAttributes() as $attribute) { + if ($configurator = $this->findConfigurator($this->methodAttributeConfigurators, $attribute->getName())) { + $configurator($conditionals, $attribute->newInstance(), $methodReflector); + } + } + } + + if ($this->parameterAttributeConfigurators) { + foreach ($methodReflector->getParameters() as $parameterReflector) { + foreach ($parameterReflector->getAttributes() as $attribute) { + if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) { + $configurator($conditionals, $attribute->newInstance(), $parameterReflector); + } + } + } + } + } + } + + if ($this->propertyAttributeConfigurators) { + foreach ($classReflector->getProperties(\ReflectionProperty::IS_PUBLIC) as $propertyReflector) { + if ($propertyReflector->isStatic()) { + continue; + } + + foreach ($propertyReflector->getAttributes() as $attribute) { + if ($configurator = $this->findConfigurator($this->propertyAttributeConfigurators, $attribute->getName())) { + $configurator($conditionals, $attribute->newInstance(), $propertyReflector); + } + } + } + } + + if (!isset($instanceof[$classReflector->getName()]) && new ChildDefinition('') != $conditionals) { + $instanceof[$classReflector->getName()] = $conditionals; + $value->setInstanceofConditionals($instanceof); + } + + return parent::processValue($value, $isRoot); + } + + /** + * Find the first configurator for the given attribute name, looking up the class hierarchy. + */ + private function findConfigurator(array &$configurators, string $attributeName): ?callable + { + if (\array_key_exists($attributeName, $configurators)) { + return $configurators[$attributeName]; + } + + if (class_exists($attributeName) && $parent = get_parent_class($attributeName)) { + return $configurators[$attributeName] = self::findConfigurator($configurators, $parent); + } + + return $configurators[$attributeName] = null; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.php b/vendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.php new file mode 100644 index 0000000..8a95c0e --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Sets a service to be an alias of another one, given a format pattern. + */ +class AutoAliasServicePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->findTaggedServiceIds('auto_alias') as $serviceId => $tags) { + foreach ($tags as $tag) { + if (!isset($tag['format'])) { + throw new InvalidArgumentException(sprintf('Missing tag information "format" on auto_alias service "%s".', $serviceId)); + } + + $aliasId = $container->getParameterBag()->resolveValue($tag['format']); + if ($container->hasDefinition($aliasId) || $container->hasAlias($aliasId)) { + $alias = new Alias($aliasId, $container->getDefinition($serviceId)->isPublic()); + $container->setAlias($serviceId, $alias); + } + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php b/vendor/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php new file mode 100644 index 0000000..1e812c7 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AutowireAsDecoratorPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Reads #[AsDecorator] attributes on definitions that are autowired + * and don't have the "container.ignore_attributes" tag. + */ +final class AutowireAsDecoratorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $definition) { + if ($this->accept($definition) && $reflectionClass = $container->getReflectionClass($definition->getClass(), false)) { + $this->processClass($definition, $reflectionClass); + } + } + } + + private function accept(Definition $definition): bool + { + return !$definition->hasTag('container.ignore_attributes') && $definition->isAutowired(); + } + + private function processClass(Definition $definition, \ReflectionClass $reflectionClass): void + { + foreach ($reflectionClass->getAttributes(AsDecorator::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + $attribute = $attribute->newInstance(); + + $definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AutowirePass.php b/vendor/symfony/dependency-injection/Compiler/AutowirePass.php new file mode 100644 index 0000000..ca1d3e8 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AutowirePass.php @@ -0,0 +1,752 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Symfony\Component\DependencyInjection\Attribute\AutowireInline; +use Symfony\Component\DependencyInjection\Attribute\Lazy; +use Symfony\Component\DependencyInjection\Attribute\Target; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * Inspects existing service definitions and wires the autowired ones using the type hints of their classes. + * + * @author Kévin Dunglas + * @author Nicolas Grekas + */ +class AutowirePass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $types; + private array $ambiguousServiceTypes; + private array $autowiringAliases; + private ?string $lastFailure = null; + private bool $throwOnAutowiringException; + private ?string $decoratedClass = null; + private ?string $decoratedId = null; + private object $defaultArgument; + private ?\Closure $restorePreviousValue = null; + private ?self $typesClone = null; + + public function __construct(bool $throwOnAutowireException = true) + { + $this->throwOnAutowiringException = $throwOnAutowireException; + $this->defaultArgument = new class() { + public $value; + public $names; + public $bag; + + public function withValue(\ReflectionParameter $parameter): self + { + $clone = clone $this; + $clone->value = $this->bag->escapeValue($parameter->getDefaultValue()); + + return $clone; + } + }; + } + + public function process(ContainerBuilder $container): void + { + $this->defaultArgument->bag = $container->getParameterBag(); + + try { + $this->typesClone = clone $this; + parent::process($container); + } finally { + $this->decoratedClass = null; + $this->decoratedId = null; + $this->defaultArgument->bag = null; + $this->defaultArgument->names = null; + $this->restorePreviousValue = null; + $this->typesClone = null; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Autowire) { + return $this->processValue($this->container->getParameterBag()->resolveValue($value->value)); + } + + if ($value instanceof AutowireDecorated) { + $definition = $this->container->getDefinition($this->currentId); + + return new Reference($definition->innerServiceId ?? $this->currentId.'.inner', $definition->decorationOnInvalid ?? ContainerInterface::NULL_ON_INVALID_REFERENCE); + } + + try { + return $this->doProcessValue($value, $isRoot); + } catch (AutowiringFailedException $e) { + if ($this->throwOnAutowiringException) { + throw $e; + } + + $this->container->getDefinition($this->currentId)->addError($e->getMessageCallback() ?? $e->getMessage()); + + return parent::processValue($value, $isRoot); + } + } + + private function doProcessValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof TypedReference) { + foreach ($value->getAttributes() as $attribute) { + if ($attribute === $v = $this->processValue($attribute)) { + continue; + } + if (!$attribute instanceof Autowire || !$v instanceof Reference) { + return $v; + } + + $invalidBehavior = ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE !== $v->getInvalidBehavior() ? $v->getInvalidBehavior() : $value->getInvalidBehavior(); + $value = $v instanceof TypedReference + ? new TypedReference($v, $v->getType(), $invalidBehavior, $v->getName() ?? $value->getName(), array_merge($v->getAttributes(), $value->getAttributes())) + : new TypedReference($v, $value->getType(), $invalidBehavior, $value->getName(), $value->getAttributes()); + break; + } + if ($ref = $this->getAutowiredReference($value, true)) { + return $ref; + } + if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $message = $this->createTypeNotFoundMessageCallback($value, 'it'); + + // since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition + $this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, (string) $value), $value->getType()) + ->addError($message); + + return new TypedReference($id, $value->getType(), $value->getInvalidBehavior(), $value->getName()); + } + } + $value = parent::processValue($value, $isRoot); + + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + return $value; + } + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { + $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass())); + + return $value; + } + + $methodCalls = $value->getMethodCalls(); + + try { + $constructor = $this->getConstructor($value, false); + } catch (RuntimeException $e) { + throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); + } + + if ($constructor) { + array_unshift($methodCalls, [$constructor, $value->getArguments()]); + } + + $checkAttributes = !$value->hasTag('container.ignore_attributes'); + $methodCalls = $this->autowireCalls($methodCalls, $reflectionClass, $isRoot, $checkAttributes); + + if ($constructor) { + [, $arguments] = array_shift($methodCalls); + + if ($arguments !== $value->getArguments()) { + $value->setArguments($arguments); + } + } + + if ($methodCalls !== $value->getMethodCalls()) { + $value->setMethodCalls($methodCalls); + } + + return $value; + } + + private function autowireCalls(array $methodCalls, \ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array + { + if ($isRoot) { + $this->decoratedId = null; + $this->decoratedClass = null; + $this->restorePreviousValue = null; + + if (($definition = $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId = $definition->innerServiceId) && $this->container->has($this->decoratedId)) { + $this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass(); + } + } + + $patchedIndexes = []; + + foreach ($methodCalls as $i => $call) { + [$method, $arguments] = $call; + + if ($method instanceof \ReflectionFunctionAbstract) { + $reflectionMethod = $method; + } else { + $definition = new Definition($reflectionClass->name); + try { + $reflectionMethod = $this->getReflectionMethod($definition, $method); + } catch (RuntimeException $e) { + if ($definition->getFactory()) { + continue; + } + throw $e; + } + } + + $arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes); + + if ($arguments !== $call[1]) { + $methodCalls[$i][1] = $arguments; + $patchedIndexes[] = $i; + } + } + + // use named arguments to skip complex default values + foreach ($patchedIndexes as $i) { + $namedArguments = null; + $arguments = $methodCalls[$i][1]; + + foreach ($arguments as $j => $value) { + if ($namedArguments && !$value instanceof $this->defaultArgument) { + unset($arguments[$j]); + $arguments[$namedArguments[$j]] = $value; + } + if (!$value instanceof $this->defaultArgument) { + continue; + } + + if (\is_array($value->value) ? $value->value : \is_object($value->value)) { + unset($arguments[$j]); + $namedArguments = $value->names; + } + + if ($namedArguments) { + unset($arguments[$j]); + } else { + $arguments[$j] = $value->value; + } + } + + $methodCalls[$i][1] = $arguments; + } + + return $methodCalls; + } + + /** + * Autowires the constructor or a method. + * + * @throws AutowiringFailedException + */ + private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes): array + { + $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId; + $method = $reflectionMethod->name; + $parameters = $reflectionMethod->getParameters(); + if ($reflectionMethod->isVariadic()) { + array_pop($parameters); + } + $defaultArgument = clone $this->defaultArgument; + $defaultArgument->names = new \ArrayObject(); + + foreach ($parameters as $index => $parameter) { + $defaultArgument->names[$index] = $parameter->name; + + if (\array_key_exists($parameter->name, $arguments)) { + $arguments[$index] = $arguments[$parameter->name]; + unset($arguments[$parameter->name]); + } + if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) { + continue; + } + + $type = ProxyHelper::exportType($parameter, true); + $target = null; + $name = Target::parseName($parameter, $target); + $target = $target ? [$target] : []; + $currentId = $this->currentId; + + $getValue = function () use ($type, $parameter, $class, $method, $name, $target, $defaultArgument, $currentId) { + if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target), false)) { + $failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $currentId ? $class.'::'.$method : $method)); + + if ($parameter->isDefaultValueAvailable()) { + $value = $defaultArgument->withValue($parameter); + } elseif (!$parameter->allowsNull()) { + throw new AutowiringFailedException($currentId, $failureMessage); + } + } + + return $value; + }; + + if ($checkAttributes) { + $attributes = array_merge($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF), $parameter->getAttributes(Lazy::class, \ReflectionAttribute::IS_INSTANCEOF)); + + if (1 < \count($attributes)) { + throw new AutowiringFailedException($this->currentId, 'Using both attributes #[Lazy] and #[Autowire] on an argument is not allowed; use the "lazy" parameter of #[Autowire] instead.'); + } + + foreach ($attributes as $attribute) { + $attribute = $attribute->newInstance(); + $value = $attribute instanceof Autowire ? $attribute->value : null; + + if (\is_string($value) && str_starts_with($value, '%env(') && str_ends_with($value, ')%')) { + if ($parameter->getType() instanceof \ReflectionNamedType && 'bool' === $parameter->getType()->getName() && !str_starts_with($value, '%env(bool:')) { + $attribute = new Autowire(substr_replace($value, 'bool:', 5, 0)); + } + if ($parameter->isDefaultValueAvailable() && $parameter->allowsNull() && null === $parameter->getDefaultValue() && !preg_match('/(^|:)default:/', $value)) { + $attribute = new Autowire(substr_replace($value, 'default::', 5, 0)); + } + } + + $invalidBehavior = $parameter->allowsNull() ? ContainerInterface::NULL_ON_INVALID_REFERENCE : ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE; + + try { + $value = $this->processValue(new TypedReference($type ?: '?', $type ?: 'mixed', $invalidBehavior, $name, [$attribute, ...$target])); + } catch (ParameterNotFoundException $e) { + if (!$parameter->isDefaultValueAvailable()) { + throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); + } + $arguments[$index] = clone $defaultArgument; + $arguments[$index]->value = $parameter->getDefaultValue(); + + continue 2; + } + + if ($attribute instanceof AutowireInline) { + $value = $attribute->buildDefinition($value, $type, $parameter); + $value = $this->doProcessValue($value); + } elseif ($lazy = $attribute->lazy) { + $definition = (new Definition($type)) + ->setFactory('current') + ->setArguments([[$value ??= $getValue()]]) + ->setLazy(true); + + if (!\is_array($lazy)) { + if (str_contains($type, '|')) { + throw new AutowiringFailedException($this->currentId, sprintf('Cannot use #[Autowire] with option "lazy: true" on union types for service "%s"; set the option to the interface(s) that should be proxied instead.', $this->currentId)); + } + $lazy = str_contains($type, '&') ? explode('&', $type) : []; + } + + if ($lazy) { + if (!class_exists($type) && !interface_exists($type, false)) { + $definition->setClass('object'); + } + foreach ($lazy as $v) { + $definition->addTag('proxy', ['interface' => $v]); + } + } + + if ($definition->getClass() !== (string) $value || $definition->getTag('proxy')) { + $value .= '.'.$this->container->hash([$definition->getClass(), $definition->getTag('proxy')]); + } + $this->container->setDefinition($value = '.lazy.'.$value, $definition); + $value = new Reference($value); + } + $arguments[$index] = $value; + + continue 2; + } + + foreach ($parameter->getAttributes(AutowireDecorated::class) as $attribute) { + $arguments[$index] = $this->processValue($attribute->newInstance()); + + continue 2; + } + } + + if (!$type) { + if (isset($arguments[$index])) { + continue; + } + + // no default value? Then fail + if (!$parameter->isDefaultValueAvailable()) { + // For core classes, isDefaultValueAvailable() can + // be false when isOptional() returns true. If the + // argument *is* optional, allow it to be missing + if ($parameter->isOptional()) { + --$index; + break; + } + $type = ProxyHelper::exportType($parameter); + $type = $type ? sprintf('is type-hinted "%s"', preg_replace('/(^|[(|&])\\\\|^\?\\\\?/', '\1', $type)) : 'has no type-hint'; + + throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type)); + } + + // specifically pass the default value + $arguments[$index] = $defaultArgument->withValue($parameter); + + continue; + } + + if ($this->decoratedClass && is_a($this->decoratedClass, $type, true)) { + if ($this->restorePreviousValue) { + // The inner service is injected only if there is only 1 argument matching the type of the decorated class + // across all arguments of all autowired methods. + // If a second matching argument is found, the default behavior is restored. + ($this->restorePreviousValue)(); + $this->decoratedClass = $this->restorePreviousValue = null; // Prevent further checks + } else { + $arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass); + $argumentAtIndex = &$arguments[$index]; + $this->restorePreviousValue = static function () use (&$argumentAtIndex, $getValue) { + $argumentAtIndex = $getValue(); + }; + + continue; + } + } + + $arguments[$index] = $getValue(); + } + + if ($parameters && !isset($arguments[++$index])) { + while (0 <= --$index) { + if (!$arguments[$index] instanceof $defaultArgument) { + break; + } + unset($arguments[$index]); + } + } + + // it's possible index 1 was set, then index 0, then 2, etc + // make sure that we re-order so they're injected as expected + ksort($arguments, \SORT_NATURAL); + + return $arguments; + } + + /** + * Returns a reference to the service matching the given type, if any. + */ + private function getAutowiredReference(TypedReference $reference, bool $filterType): ?TypedReference + { + $this->lastFailure = null; + $type = $reference->getType(); + + if ($type !== (string) $reference) { + return $reference; + } + + if ($filterType && false !== $m = strpbrk($type, '&|')) { + $types = array_diff(explode($m[0], $type), ['int', 'string', 'array', 'bool', 'float', 'iterable', 'object', 'callable', 'null']); + + sort($types); + + $type = implode($m[0], $types); + } + + $name = $target = (array_filter($reference->getAttributes(), static fn ($a) => $a instanceof Target)[0] ?? null)?->name; + + if (null !== $name ??= $reference->getName()) { + if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); + } + + if (null !== ($alias = $this->getCombinedAlias($type, $name)) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); + } + + $parsedName = (new Target($name))->getParsedName(); + + if ($this->container->has($alias = $type.' $'.$parsedName) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); + } + + if (null !== ($alias = $this->getCombinedAlias($type, $parsedName)) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); + } + + if (($this->container->has($n = $name) && !$this->container->findDefinition($n)->isAbstract()) + || ($this->container->has($n = $parsedName) && !$this->container->findDefinition($n)->isAbstract()) + ) { + foreach ($this->container->getAliases() as $id => $alias) { + if ($n === (string) $alias && str_starts_with($id, $type.' $')) { + return new TypedReference($n, $type, $reference->getInvalidBehavior()); + } + } + } + + if (null !== $target) { + return null; + } + } + + if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) { + return new TypedReference($type, $type, $reference->getInvalidBehavior()); + } + + if (null !== ($alias = $this->getCombinedAlias($type)) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); + } + + return null; + } + + /** + * Populates the list of available types. + */ + private function populateAvailableTypes(ContainerBuilder $container): void + { + $this->types = []; + $this->ambiguousServiceTypes = []; + $this->autowiringAliases = []; + + foreach ($container->getDefinitions() as $id => $definition) { + $this->populateAvailableType($container, $id, $definition); + } + + $prev = null; + foreach ($container->getAliases() as $id => $alias) { + $this->populateAutowiringAlias($id, $prev); + $prev = $id; + } + } + + /** + * Populates the list of available types for a given definition. + */ + private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition): void + { + // Never use abstract services + if ($definition->isAbstract()) { + return; + } + + if ('' === $id || '.' === $id[0] || $definition->isDeprecated() || !$reflectionClass = $container->getReflectionClass($definition->getClass(), false)) { + return; + } + + foreach ($reflectionClass->getInterfaces() as $reflectionInterface) { + $this->set($reflectionInterface->name, $id); + } + + do { + $this->set($reflectionClass->name, $id); + } while ($reflectionClass = $reflectionClass->getParentClass()); + + $this->populateAutowiringAlias($id); + } + + /** + * Associates a type and a service id if applicable. + */ + private function set(string $type, string $id): void + { + // is this already a type/class that is known to match multiple services? + if (isset($this->ambiguousServiceTypes[$type])) { + $this->ambiguousServiceTypes[$type][] = $id; + + return; + } + + // check to make sure the type doesn't match multiple services + if (!isset($this->types[$type]) || $this->types[$type] === $id) { + $this->types[$type] = $id; + + return; + } + + // keep an array of all services matching this type + if (!isset($this->ambiguousServiceTypes[$type])) { + $this->ambiguousServiceTypes[$type] = [$this->types[$type]]; + unset($this->types[$type]); + } + $this->ambiguousServiceTypes[$type][] = $id; + } + + private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label): \Closure + { + if (!isset($this->typesClone->container)) { + $this->typesClone->container = new ContainerBuilder($this->container->getParameterBag()); + $this->typesClone->container->setAliases($this->container->getAliases()); + $this->typesClone->container->setDefinitions($this->container->getDefinitions()); + $this->typesClone->container->setResourceTracking(false); + } + $currentId = $this->currentId; + + return (fn () => $this->createTypeNotFoundMessage($reference, $label, $currentId))->bindTo($this->typesClone); + } + + private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId): string + { + $type = $reference->getType(); + + $i = null; + $namespace = $type; + do { + $namespace = substr($namespace, 0, $i); + + if ($this->container->hasDefinition($namespace) && $tag = $this->container->getDefinition($namespace)->getTag('container.excluded')) { + return sprintf('Cannot autowire service "%s": %s needs an instance of "%s" but this type has been excluded %s.', $currentId, $label, $type, $tag[0]['source'] ?? 'from autowiring'); + } + } while (false !== $i = strrpos($namespace, '\\')); + + if (!$r = $this->container->getReflectionClass($type, false)) { + // either $type does not exist or a parent class does not exist + try { + if (class_exists(ClassExistenceResource::class)) { + $resource = new ClassExistenceResource($type, false); + // isFresh() will explode ONLY if a parent class/trait does not exist + $resource->isFresh(0); + $parentMsg = false; + } else { + $parentMsg = "couldn't be loaded. Either it was not found or it is missing a parent class or a trait"; + } + } catch (\ReflectionException $e) { + $parentMsg = sprintf('is missing a parent class (%s)', $e->getMessage()); + } + + $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ?: 'was not found'); + } else { + $alternatives = $this->createTypeAlternatives($this->container, $reference); + + if (null !== $target = (array_filter($reference->getAttributes(), static fn ($a) => $a instanceof Target)[0] ?? null)) { + $target = null !== $target->name ? "('{$target->name}')" : ''; + $message = sprintf('has "#[Target%s]" but no such target exists.%s', $target, $alternatives); + } else { + $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; + $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); + } + + if ($r->isInterface() && !$alternatives) { + $message .= ' Did you create a class that implements this interface?'; + } + } + + $message = sprintf('Cannot autowire service "%s": %s %s', $currentId, $label, $message); + + if (null !== $this->lastFailure) { + $message = $this->lastFailure."\n".$message; + $this->lastFailure = null; + } + + return $message; + } + + private function createTypeAlternatives(ContainerBuilder $container, TypedReference $reference): string + { + // try suggesting available aliases first + if ($message = $this->getAliasesSuggestionForType($container, $type = $reference->getType())) { + return ' '.$message; + } + if (!isset($this->ambiguousServiceTypes)) { + $this->populateAvailableTypes($container); + } + + $servicesAndAliases = $container->getServiceIds(); + $autowiringAliases = $this->autowiringAliases[$type] ?? []; + unset($autowiringAliases['']); + + if ($autowiringAliases) { + return sprintf(' Did you mean to target%s "%s" instead?', 1 < \count($autowiringAliases) ? ' one of' : '', implode('", "', $autowiringAliases)); + } + + if (!$container->has($type) && false !== $key = array_search(strtolower($type), array_map('strtolower', $servicesAndAliases))) { + return sprintf(' Did you mean "%s"?', $servicesAndAliases[$key]); + } elseif (isset($this->ambiguousServiceTypes[$type])) { + $message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type])); + } elseif (isset($this->types[$type])) { + $message = sprintf('the existing "%s" service', $this->types[$type]); + } else { + return ''; + } + + return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message); + } + + private function getAliasesSuggestionForType(ContainerBuilder $container, string $type): ?string + { + $aliases = []; + foreach (class_parents($type) + class_implements($type) as $parent) { + if ($container->has($parent) && !$container->findDefinition($parent)->isAbstract()) { + $aliases[] = $parent; + } + } + + if (1 < $len = \count($aliases)) { + $message = 'Try changing the type-hint to one of its parents: '; + for ($i = 0, --$len; $i < $len; ++$i) { + $message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]); + } + $message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]); + + return $message; + } + + if ($aliases) { + return sprintf('Try changing the type-hint to "%s" instead.', $aliases[0]); + } + + return null; + } + + private function populateAutowiringAlias(string $id, ?string $target = null): void + { + if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) { + return; + } + + $type = $m[2]; + $name = $m[3] ?? ''; + + if (class_exists($type, false) || interface_exists($type, false)) { + if (null !== $target && str_starts_with($target, '.'.$type.' $') + && (new Target($target = substr($target, \strlen($type) + 3)))->getParsedName() === $name + ) { + $name = $target; + } + + $this->autowiringAliases[$type][$name] = $name; + } + } + + private function getCombinedAlias(string $type, ?string $name = null): ?string + { + if (str_contains($type, '&')) { + $types = explode('&', $type); + } elseif (str_contains($type, '|')) { + $types = explode('|', $type); + } else { + return null; + } + + $alias = null; + $suffix = $name ? ' $'.$name : ''; + + foreach ($types as $type) { + if (!$this->container->hasAlias($type.$suffix)) { + return null; + } + + if (null === $alias) { + $alias = (string) $this->container->getAlias($type.$suffix); + } elseif ((string) $this->container->getAlias($type.$suffix) !== $alias) { + return null; + } + } + + return $alias; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php b/vendor/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php new file mode 100644 index 0000000..9c42280 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Contracts\Service\Attribute\Required; + +/** + * Looks for definitions with autowiring enabled and registers their corresponding "#[Required]" methods as setters. + * + * @author Nicolas Grekas + */ +class AutowireRequiredMethodsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + $value = parent::processValue($value, $isRoot); + + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + return $value; + } + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { + return $value; + } + + $alreadyCalledMethods = []; + $withers = []; + + foreach ($value->getMethodCalls() as [$method]) { + $alreadyCalledMethods[strtolower($method)] = true; + } + + foreach ($reflectionClass->getMethods() as $reflectionMethod) { + $r = $reflectionMethod; + + if ($r->isConstructor() || isset($alreadyCalledMethods[strtolower($r->name)])) { + continue; + } + + while (true) { + if ($r->getAttributes(Required::class)) { + if ($this->isWither($r, $r->getDocComment() ?: '')) { + $withers[] = [$r->name, [], true]; + } else { + $value->addMethodCall($r->name, []); + } + break; + } + try { + $r = $r->getPrototype(); + } catch (\ReflectionException) { + break; // method has no prototype + } + } + } + + if ($withers) { + // Prepend withers to prevent creating circular loops + $setters = $value->getMethodCalls(); + $value->setMethodCalls($withers); + foreach ($setters as $call) { + $value->addMethodCall($call[0], $call[1], $call[2] ?? false); + } + } + + return $value; + } + + private function isWither(\ReflectionMethod $reflectionMethod, string $doc): bool + { + $match = preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@return\s++(static|\$this)[\s\*]#i', $doc, $matches); + if ($match && 'static' === $matches[1]) { + return true; + } + + if ($match && '$this' === $matches[1]) { + return false; + } + + $reflectionType = $reflectionMethod->hasReturnType() ? $reflectionMethod->getReturnType() : null; + + return $reflectionType instanceof \ReflectionNamedType && 'static' === $reflectionType->getName(); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php b/vendor/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php new file mode 100644 index 0000000..c5f45da --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\Required; + +/** + * Looks for definitions with autowiring enabled and registers their corresponding "#[Required]" properties. + * + * @author Sebastien Morel (Plopix) + * @author Nicolas Grekas + */ +class AutowireRequiredPropertiesPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + $value = parent::processValue($value, $isRoot); + + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + return $value; + } + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { + return $value; + } + + $properties = $value->getProperties(); + foreach ($reflectionClass->getProperties() as $reflectionProperty) { + if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) { + continue; + } + if (!$reflectionProperty->getAttributes(Required::class)) { + continue; + } + if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) { + continue; + } + + $type = $type->getName(); + $value->setProperty($name, new TypedReference($type, $type, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name)); + } + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckAliasValidityPass.php b/vendor/symfony/dependency-injection/Compiler/CheckAliasValidityPass.php new file mode 100644 index 0000000..44a1bdc --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckAliasValidityPass.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * This pass validates aliases, it provides the following checks: + * + * - An alias which happens to be an interface must resolve to a service implementing this interface. This ensures injecting the aliased interface won't cause a type error at runtime. + */ +class CheckAliasValidityPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->getAliases() as $id => $alias) { + try { + if (!$container->hasDefinition((string) $alias)) { + continue; + } + + $target = $container->getDefinition((string) $alias); + if (null === $target->getClass() || null !== $target->getFactory()) { + continue; + } + + $reflection = $container->getReflectionClass($id); + if (null === $reflection || !$reflection->isInterface()) { + continue; + } + + $targetReflection = $container->getReflectionClass($target->getClass()); + if (null !== $targetReflection && !$targetReflection->implementsInterface($id)) { + throw new RuntimeException(sprintf('Invalid alias definition: alias "%s" is referencing class "%s" but this class does not implement "%s". Because this alias is an interface, "%s" must implement "%s".', $id, $target->getClass(), $id, $target->getClass(), $id)); + } + } catch (\ReflectionException) { + continue; + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php b/vendor/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php new file mode 100644 index 0000000..8cbd722 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * Checks if arguments of methods are properly configured. + * + * @author Kévin Dunglas + * @author Nicolas Grekas + */ +class CheckArgumentsValidityPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private bool $throwExceptions; + + public function __construct(bool $throwExceptions = true) + { + $this->throwExceptions = $throwExceptions; + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + + $i = 0; + $hasNamedArgs = false; + foreach ($value->getArguments() as $k => $v) { + if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { + $hasNamedArgs = true; + continue; + } + + if ($k !== $i++) { + if (!\is_int($k)) { + $msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + + break; + } + + $msg = sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + } + + if ($hasNamedArgs) { + $msg = sprintf('Invalid constructor argument for service "%s": cannot use positional argument after named argument. Check your service definition.', $this->currentId); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + + break; + } + } + + foreach ($value->getMethodCalls() as $methodCall) { + $i = 0; + $hasNamedArgs = false; + foreach ($methodCall[1] as $k => $v) { + if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { + $hasNamedArgs = true; + continue; + } + + if ($k !== $i++) { + if (!\is_int($k)) { + $msg = sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + + break; + } + + $msg = sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + } + + if ($hasNamedArgs) { + $msg = sprintf('Invalid argument for method call "%s" of service "%s": cannot use positional argument after named argument. Check your service definition.', $methodCall[0], $this->currentId); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + + break; + } + } + } + + return null; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php b/vendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php new file mode 100644 index 0000000..9b43d6e --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; + +/** + * Checks your services for circular references. + * + * References from method calls are ignored since we might be able to resolve + * these references depending on the order in which services are called. + * + * Circular reference from method calls will only be detected at run-time. + * + * @author Johannes M. Schmitt + */ +class CheckCircularReferencesPass implements CompilerPassInterface +{ + private array $currentPath; + private array $checkedNodes; + + /** + * Checks the ContainerBuilder object for circular references. + */ + public function process(ContainerBuilder $container): void + { + $graph = $container->getCompiler()->getServiceReferenceGraph(); + + $this->checkedNodes = []; + foreach ($graph->getNodes() as $id => $node) { + $this->currentPath = [$id]; + + $this->checkOutEdges($node->getOutEdges()); + } + } + + /** + * Checks for circular references. + * + * @param ServiceReferenceGraphEdge[] $edges An array of Edges + * + * @throws ServiceCircularReferenceException when a circular reference is found + */ + private function checkOutEdges(array $edges): void + { + foreach ($edges as $edge) { + $node = $edge->getDestNode(); + $id = $node->getId(); + + if (empty($this->checkedNodes[$id])) { + // Don't check circular references for lazy edges + if (!$node->getValue() || (!$edge->isLazy() && !$edge->isWeak())) { + $searchKey = array_search($id, $this->currentPath); + $this->currentPath[] = $id; + + if (false !== $searchKey) { + throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey)); + } + + $this->checkOutEdges($node->getOutEdges()); + } + + $this->checkedNodes[$id] = true; + array_pop($this->currentPath); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php b/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php new file mode 100644 index 0000000..3426877 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\EnvParameterException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Loader\FileLoader; + +/** + * This pass validates each definition individually only taking the information + * into account which is contained in the definition itself. + * + * Later passes can rely on the following, and specifically do not need to + * perform these checks themselves: + * + * - non synthetic, non abstract services always have a class set + * - synthetic services are always public + * + * @author Johannes M. Schmitt + */ +class CheckDefinitionValidityPass implements CompilerPassInterface +{ + /** + * Processes the ContainerBuilder to validate the Definition. + * + * @throws RuntimeException When the Definition is invalid + */ + public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $id => $definition) { + // synthetic service is public + if ($definition->isSynthetic() && !$definition->isPublic()) { + throw new RuntimeException(sprintf('A synthetic service ("%s") must be public.', $id)); + } + + // non-synthetic, non-abstract service has class + if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass() && !$definition->hasTag('container.service_locator') && (!$definition->getFactory() || !preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id))) { + if ($definition->getFactory()) { + throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id)); + } + if (class_exists($id) || interface_exists($id, false)) { + if (str_starts_with($id, '\\') && 1 < substr_count($id, '\\')) { + throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "%s" to get rid of this error.', $id, substr($id, 1))); + } + + throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface in the global namespace. Leaving out the "class" attribute is only allowed for namespaced classes. Please specify the class attribute explicitly to get rid of this error.', $id)); + } + + throw new RuntimeException(sprintf('The definition for "%s" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.', $id)); + } + + // tag attribute values must be scalars + foreach ($definition->getTags() as $name => $tags) { + foreach ($tags as $attributes) { + $this->validateAttributes($id, $name, $attributes); + } + } + + if ($definition->isPublic() && !$definition->isPrivate()) { + $resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs); + if (null !== $usedEnvs) { + throw new EnvParameterException([$resolvedId], null, 'A service name ("%s") cannot contain dynamic values.'); + } + } + } + + foreach ($container->getAliases() as $id => $alias) { + if ($alias->isPublic() && !$alias->isPrivate()) { + $resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs); + if (null !== $usedEnvs) { + throw new EnvParameterException([$resolvedId], null, 'An alias name ("%s") cannot contain dynamic values.'); + } + } + } + } + + private function validateAttributes(string $id, string $tag, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($id, $tag, $value, [...$path, $name]); + } elseif (!\is_scalar($value) && null !== $value) { + $name = implode('.', [...$path, $name]); + throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $tag, $name)); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php new file mode 100644 index 0000000..e81db66 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Checks that all references are pointing to a valid service. + * + * @author Johannes M. Schmitt + */ +class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $serviceLocatorContextIds = []; + + public function process(ContainerBuilder $container): void + { + $this->serviceLocatorContextIds = []; + foreach ($container->findTaggedServiceIds('container.service_locator_context') as $id => $tags) { + $this->serviceLocatorContextIds[$id] = $tags[0]['id']; + $container->getDefinition($id)->clearTag('container.service_locator_context'); + } + + try { + parent::process($container); + } finally { + $this->serviceLocatorContextIds = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof Reference) { + return parent::processValue($value, $isRoot); + } + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $value->getInvalidBehavior() || $this->container->has((string) $value)) { + return $value; + } + + $currentId = $this->currentId; + $graph = $this->container->getCompiler()->getServiceReferenceGraph(); + + if (isset($this->serviceLocatorContextIds[$currentId])) { + $currentId = $this->serviceLocatorContextIds[$currentId]; + $locator = $this->container->getDefinition($this->currentId)->getFactory()[0]; + $this->throwServiceNotFoundException($value, $currentId, $locator->getArgument(0)); + } + + if ('.' === $currentId[0] && $graph->hasNode($currentId)) { + foreach ($graph->getNode($currentId)->getInEdges() as $edge) { + if (!$edge->getValue() instanceof Reference || ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $edge->getValue()->getInvalidBehavior()) { + continue; + } + $sourceId = $edge->getSourceNode()->getId(); + + if ('.' !== $sourceId[0]) { + $currentId = $sourceId; + break; + } + + if (isset($this->serviceLocatorContextIds[$sourceId])) { + $currentId = $this->serviceLocatorContextIds[$sourceId]; + $locator = $this->container->getDefinition($this->currentId); + $this->throwServiceNotFoundException($value, $currentId, $locator->getArgument(0)); + } + } + } + + $this->throwServiceNotFoundException($value, $currentId, $value); + } + + private function throwServiceNotFoundException(Reference $ref, string $sourceId, mixed $value): void + { + $id = (string) $ref; + $alternatives = []; + foreach ($this->container->getServiceIds() as $knownId) { + if ('' === $knownId || '.' === $knownId[0] || $knownId === $this->currentId) { + continue; + } + + $lev = levenshtein($id, $knownId); + if ($lev <= \strlen($id) / 3 || str_contains($knownId, $id)) { + $alternatives[] = $knownId; + } + } + + $pass = new class() extends AbstractRecursivePass { + public Reference $ref; + public string $sourceId; + public array $alternatives; + + public function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($this->ref !== $value) { + return parent::processValue($value, $isRoot); + } + $sourceId = $this->sourceId; + if (null !== $this->currentId && $this->currentId !== (string) $value) { + $sourceId = $this->currentId.'" in the container provided to "'.$sourceId; + } + + throw new ServiceNotFoundException((string) $value, $sourceId, null, $this->alternatives); + } + }; + $pass->ref = $ref; + $pass->sourceId = $sourceId; + $pass->alternatives = $alternatives; + + $pass->processValue($value, true); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php b/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php new file mode 100644 index 0000000..5c54a65 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Checks the validity of references. + * + * The following checks are performed by this pass: + * - target definitions are not abstract + * + * @author Johannes M. Schmitt + */ +class CheckReferenceValidityPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) { + return $value; + } + if ($value instanceof Reference && $this->container->hasDefinition((string) $value)) { + $targetDefinition = $this->container->getDefinition((string) $value); + + if ($targetDefinition->isAbstract()) { + throw new RuntimeException(sprintf('The definition "%s" has a reference to an abstract definition "%s". Abstract definitions cannot be the target of references.', $this->currentId, $value)); + } + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php b/vendor/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php new file mode 100644 index 0000000..074d899 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php @@ -0,0 +1,334 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * Checks whether injected parameters are compatible with type declarations. + * + * This pass should be run after all optimization passes. + * + * It can be added either: + * * before removing passes to check all services even if they are not currently used, + * * after removing passes to check only services are used in the app. + * + * @author Nicolas Grekas + * @author Julien Maulny + */ +final class CheckTypeDeclarationsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private const SCALAR_TYPES = [ + 'int' => true, + 'float' => true, + 'bool' => true, + 'string' => true, + ]; + + private const BUILTIN_TYPES = [ + 'array' => true, + 'bool' => true, + 'callable' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'object' => true, + 'string' => true, + ]; + + private bool $autoload; + private array $skippedIds; + + private ExpressionLanguage $expressionLanguage; + + /** + * @param bool $autoload Whether services who's class in not loaded should be checked or not. + * Defaults to false to save loading code during compilation. + * @param array $skippedIds An array indexed by the service ids to skip + */ + public function __construct(bool $autoload = false, array $skippedIds = []) + { + $this->autoload = $autoload; + $this->skippedIds = $skippedIds; + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (isset($this->skippedIds[$this->currentId])) { + return $value; + } + + if (!$value instanceof Definition || $value->hasErrors() || $value->isDeprecated()) { + return parent::processValue($value, $isRoot); + } + + if (!$this->autoload) { + if (!$class = $value->getClass()) { + return parent::processValue($value, $isRoot); + } + if (!class_exists($class, false) && !interface_exists($class, false)) { + return parent::processValue($value, $isRoot); + } + } + + if (ServiceLocator::class === $value->getClass()) { + return parent::processValue($value, $isRoot); + } + + if ($constructor = $this->getConstructor($value, false)) { + $this->checkTypeDeclarations($value, $constructor, $value->getArguments()); + } + + foreach ($value->getMethodCalls() as $methodCall) { + try { + $reflectionMethod = $this->getReflectionMethod($value, $methodCall[0]); + } catch (RuntimeException $e) { + if ($value->getFactory()) { + continue; + } + + throw $e; + } + + $this->checkTypeDeclarations($value, $reflectionMethod, $methodCall[1]); + } + + return parent::processValue($value, $isRoot); + } + + /** + * @throws InvalidArgumentException When not enough parameters are defined for the method + */ + private function checkTypeDeclarations(Definition $checkedDefinition, \ReflectionFunctionAbstract $reflectionFunction, array $values): void + { + $numberOfRequiredParameters = $reflectionFunction->getNumberOfRequiredParameters(); + + if (\count($values) < $numberOfRequiredParameters) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": "%s::%s()" requires %d arguments, %d passed.', $this->currentId, $reflectionFunction->class, $reflectionFunction->name, $numberOfRequiredParameters, \count($values))); + } + + $reflectionParameters = $reflectionFunction->getParameters(); + $checksCount = min($reflectionFunction->getNumberOfParameters(), \count($values)); + + $envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null; + + for ($i = 0; $i < $checksCount; ++$i) { + $p = $reflectionParameters[$i]; + if (!$p->hasType() || $p->isVariadic()) { + continue; + } + if (\array_key_exists($p->name, $values)) { + $i = $p->name; + } elseif (!\array_key_exists($i, $values)) { + continue; + } + + $this->checkType($checkedDefinition, $values[$i], $p, $envPlaceholderUniquePrefix); + } + + if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) { + $variadicParameters = \array_slice($values, $lastParameter->getPosition()); + + foreach ($variadicParameters as $variadicParameter) { + $this->checkType($checkedDefinition, $variadicParameter, $lastParameter, $envPlaceholderUniquePrefix); + } + } + } + + /** + * @throws InvalidParameterTypeException When a parameter is not compatible with the declared type + */ + private function checkType(Definition $checkedDefinition, mixed $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, ?\ReflectionType $reflectionType = null): void + { + $reflectionType ??= $parameter->getType(); + + if ($reflectionType instanceof \ReflectionUnionType) { + foreach ($reflectionType->getTypes() as $t) { + try { + $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); + + return; + } catch (InvalidParameterTypeException $e) { + } + } + + throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter); + } + if ($reflectionType instanceof \ReflectionIntersectionType) { + foreach ($reflectionType->getTypes() as $t) { + $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); + } + + return; + } + if (!$reflectionType instanceof \ReflectionNamedType) { + return; + } + + $type = $reflectionType->getName(); + + if ($value instanceof Reference) { + if (!$this->container->has($value = (string) $value)) { + return; + } + + if ('service_container' === $value && is_a($type, Container::class, true)) { + return; + } + + $value = $this->container->findDefinition($value); + } + + if ('self' === $type) { + $type = $parameter->getDeclaringClass()->getName(); + } + + if ('static' === $type) { + $type = $checkedDefinition->getClass(); + } + + $class = null; + + if ($value instanceof Definition) { + if ($value->hasErrors() || $value->getFactory()) { + return; + } + + $class = $value->getClass(); + + if ($class && isset(self::BUILTIN_TYPES[strtolower($class)])) { + $class = strtolower($class); + } elseif (!$class || (!$this->autoload && !class_exists($class, false) && !interface_exists($class, false))) { + return; + } + } elseif ($value instanceof Parameter) { + $value = $this->container->getParameter($value); + } elseif ($value instanceof Expression) { + try { + $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]); + } catch (\Exception) { + // If a service from the expression cannot be fetched from the container, we skip the validation. + return; + } + } elseif (\is_string($value)) { + if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) { + $value = $this->container->getParameter(substr($value, 1, -1)); + } + + if ($envPlaceholderUniquePrefix && \is_string($value) && str_contains($value, 'env_')) { + // If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it. + // We don't need to change the value because it is already a string. + if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) { + try { + $value = $this->container->resolveEnvPlaceholders($value, true); + } catch (\Exception) { + // If an env placeholder cannot be resolved, we skip the validation. + return; + } + } + } + } + + if (null === $value && $parameter->allowsNull()) { + return; + } + + if (null === $class) { + if ($value instanceof IteratorArgument) { + $class = RewindableGenerator::class; + } elseif ($value instanceof ServiceClosureArgument) { + $class = \Closure::class; + } elseif ($value instanceof ServiceLocatorArgument) { + $class = ServiceLocator::class; + } elseif (\is_object($value)) { + $class = $value::class; + } else { + $class = \gettype($value); + $class = ['integer' => 'int', 'double' => 'float', 'boolean' => 'bool'][$class] ?? $class; + } + } + + if (isset(self::SCALAR_TYPES[$type]) && isset(self::SCALAR_TYPES[$class])) { + return; + } + + if ('string' === $type && method_exists($class, '__toString')) { + return; + } + + if ('callable' === $type && (\Closure::class === $class || method_exists($class, '__invoke'))) { + return; + } + + if ('callable' === $type && \is_array($value) && isset($value[0]) && ($value[0] instanceof Reference || $value[0] instanceof Definition || \is_string($value[0]))) { + return; + } + + if ('iterable' === $type && (\is_array($value) || 'array' === $class || is_subclass_of($class, \Traversable::class))) { + return; + } + + if ($type === $class) { + return; + } + + if ('object' === $type && !isset(self::BUILTIN_TYPES[$class])) { + return; + } + + if ('mixed' === $type) { + return; + } + + if (is_a($class, $type, true)) { + return; + } + + if ('false' === $type) { + if (false === $value) { + return; + } + } elseif ('true' === $type) { + if (true === $value) { + return; + } + } elseif ($reflectionType->isBuiltin()) { + $checkFunction = sprintf('is_%s', $type); + if ($checkFunction($value)) { + return; + } + } + + throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : get_debug_type($value), $parameter); + } + + private function getExpressionLanguage(): ExpressionLanguage + { + return $this->expressionLanguage ??= new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders()); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/Compiler.php b/vendor/symfony/dependency-injection/Compiler/Compiler.php new file mode 100644 index 0000000..cd03dcd --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/Compiler.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\EnvParameterException; + +/** + * This class is used to remove circular dependencies between individual passes. + * + * @author Johannes M. Schmitt + */ +class Compiler +{ + private PassConfig $passConfig; + private array $log = []; + private ServiceReferenceGraph $serviceReferenceGraph; + + public function __construct() + { + $this->passConfig = new PassConfig(); + $this->serviceReferenceGraph = new ServiceReferenceGraph(); + } + + public function getPassConfig(): PassConfig + { + return $this->passConfig; + } + + public function getServiceReferenceGraph(): ServiceReferenceGraph + { + return $this->serviceReferenceGraph; + } + + public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): void + { + $this->passConfig->addPass($pass, $type, $priority); + } + + /** + * @final + */ + public function log(CompilerPassInterface $pass, string $message): void + { + if (str_contains($message, "\n")) { + $message = str_replace("\n", "\n".$pass::class.': ', trim($message)); + } + + $this->log[] = $pass::class.': '.$message; + } + + public function getLog(): array + { + return $this->log; + } + + /** + * Run the Compiler and process all Passes. + */ + public function compile(ContainerBuilder $container): void + { + try { + foreach ($this->passConfig->getPasses() as $pass) { + $pass->process($container); + } + } catch (\Exception $e) { + $usedEnvs = []; + $prev = $e; + + do { + $msg = $prev->getMessage(); + + if ($msg !== $resolvedMsg = $container->resolveEnvPlaceholders($msg, null, $usedEnvs)) { + $r = new \ReflectionProperty($prev, 'message'); + $r->setValue($prev, $resolvedMsg); + } + } while ($prev = $prev->getPrevious()); + + if ($usedEnvs) { + $e = new EnvParameterException($usedEnvs, $e); + } + + throw $e; + } finally { + $this->getServiceReferenceGraph()->clear(); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CompilerPassInterface.php b/vendor/symfony/dependency-injection/Compiler/CompilerPassInterface.php new file mode 100644 index 0000000..2ad4a04 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CompilerPassInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Interface that must be implemented by compilation passes. + * + * @author Johannes M. Schmitt + */ +interface CompilerPassInterface +{ + /** + * You can modify the container here before it is dumped to PHP code. + * + * @return void + */ + public function process(ContainerBuilder $container); +} diff --git a/vendor/symfony/dependency-injection/Compiler/DecoratorServicePass.php b/vendor/symfony/dependency-injection/Compiler/DecoratorServicePass.php new file mode 100644 index 0000000..20b2ac2 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/DecoratorServicePass.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Overwrites a service but keeps the overridden one. + * + * @author Christophe Coevoet + * @author Fabien Potencier + * @author Diego Saint Esteben + */ +class DecoratorServicePass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + public function process(ContainerBuilder $container): void + { + $definitions = new \SplPriorityQueue(); + $order = \PHP_INT_MAX; + + foreach ($container->getDefinitions() as $id => $definition) { + if (!$decorated = $definition->getDecoratedService()) { + continue; + } + $definitions->insert([$id, $definition], [$decorated[2], --$order]); + } + $decoratingDefinitions = []; + $decoratedIds = []; + + $tagsToKeep = $container->hasParameter('container.behavior_describing_tags') + ? $container->getParameter('container.behavior_describing_tags') + : ['proxy', 'container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator']; + + foreach ($definitions as [$id, $definition]) { + $decoratedService = $definition->getDecoratedService(); + [$inner, $renamedId] = $decoratedService; + $invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + + $definition->setDecoratedService(null); + + if (!$renamedId) { + $renamedId = $id.'.inner'; + } + + $decoratedIds[$inner] ??= $renamedId; + $this->currentId = $renamedId; + $this->processValue($definition); + + $definition->innerServiceId = $renamedId; + $definition->decorationOnInvalid = $invalidBehavior; + + // we create a new alias/service for the service we are replacing + // to be able to reference it in the new one + if ($container->hasAlias($inner)) { + $alias = $container->getAlias($inner); + $public = $alias->isPublic(); + $container->setAlias($renamedId, new Alias((string) $alias, false)); + $decoratedDefinition = $container->findDefinition($alias); + } elseif ($container->hasDefinition($inner)) { + $decoratedDefinition = $container->getDefinition($inner); + $public = $decoratedDefinition->isPublic(); + $decoratedDefinition->setPublic(false); + $container->setDefinition($renamedId, $decoratedDefinition); + $decoratingDefinitions[$inner] = $decoratedDefinition; + } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { + $container->removeDefinition($id); + continue; + } elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { + $public = $definition->isPublic(); + $decoratedDefinition = null; + } else { + throw new ServiceNotFoundException($inner, $id); + } + + if ($decoratedDefinition?->isSynthetic()) { + throw new InvalidArgumentException(sprintf('A synthetic service cannot be decorated: service "%s" cannot decorate "%s".', $id, $inner)); + } + + if (isset($decoratingDefinitions[$inner])) { + $decoratingDefinition = $decoratingDefinitions[$inner]; + + $decoratingTags = $decoratingDefinition->getTags(); + $resetTags = []; + + // Behavior-describing tags must not be transferred out to decorators + foreach ($tagsToKeep as $containerTag) { + if (isset($decoratingTags[$containerTag])) { + $resetTags[$containerTag] = $decoratingTags[$containerTag]; + unset($decoratingTags[$containerTag]); + } + } + + $definition->setTags(array_merge($decoratingTags, $definition->getTags())); + $decoratingDefinition->setTags($resetTags); + $decoratingDefinitions[$inner] = $definition; + } + + $container->setAlias($inner, $id)->setPublic($public); + } + + foreach ($decoratingDefinitions as $inner => $definition) { + $definition->addTag('container.decorator', ['id' => $inner, 'inner' => $decoratedIds[$inner]]); + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Reference && '.inner' === (string) $value) { + return new Reference($this->currentId, $value->getInvalidBehavior()); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php b/vendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php new file mode 100644 index 0000000..26ab135 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Throws an exception for any Definitions that have errors and still exist. + * + * @author Ryan Weaver + */ +class DefinitionErrorExceptionPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $erroredDefinitions = []; + private array $sourceReferences = []; + + public function process(ContainerBuilder $container): void + { + try { + parent::process($container); + + $visitedIds = []; + + foreach ($this->erroredDefinitions as $id => $definition) { + if ($this->isErrorForRuntime($id, $visitedIds)) { + continue; + } + + // only show the first error so the user can focus on it + $errors = $definition->getErrors(); + + throw new RuntimeException(reset($errors)); + } + } finally { + $this->erroredDefinitions = []; + $this->sourceReferences = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof ArgumentInterface) { + parent::processValue($value->getValues()); + + return $value; + } + + if ($value instanceof Reference && $this->currentId !== $targetId = (string) $value) { + if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $this->sourceReferences[$targetId][$this->currentId] ??= true; + } else { + $this->sourceReferences[$targetId][$this->currentId] = false; + } + + return $value; + } + + if (!$value instanceof Definition || !$value->hasErrors() || $value->hasTag('container.error')) { + return parent::processValue($value, $isRoot); + } + + $this->erroredDefinitions[$this->currentId] = $value; + + return parent::processValue($value); + } + + private function isErrorForRuntime(string $id, array &$visitedIds): bool + { + if (!isset($this->sourceReferences[$id])) { + return false; + } + + if (isset($visitedIds[$id])) { + return $visitedIds[$id]; + } + + $visitedIds[$id] = true; + + foreach ($this->sourceReferences[$id] as $sourceId => $isRuntime) { + if ($visitedIds[$sourceId] ?? $visitedIds[$sourceId] = $this->isErrorForRuntime($sourceId, $visitedIds)) { + continue; + } + + if (!$isRuntime) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php b/vendor/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php new file mode 100644 index 0000000..f463a46 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * A pass to automatically process extensions if they implement + * CompilerPassInterface. + * + * @author Wouter J + */ +class ExtensionCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->getExtensions() as $extension) { + if (!$extension instanceof CompilerPassInterface) { + continue; + } + + $extension->process($container); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php b/vendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php new file mode 100644 index 0000000..3a99b0a --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Inline service definitions where this is possible. + * + * @author Johannes M. Schmitt + */ +class InlineServiceDefinitionsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private ?AnalyzeServiceReferencesPass $analyzingPass; + private array $cloningIds = []; + private array $connectedIds = []; + private array $notInlinedIds = []; + private array $inlinedIds = []; + private array $notInlinableIds = []; + private ?ServiceReferenceGraph $graph = null; + + public function __construct(?AnalyzeServiceReferencesPass $analyzingPass = null) + { + $this->analyzingPass = $analyzingPass; + } + + public function process(ContainerBuilder $container): void + { + $this->container = $container; + if ($this->analyzingPass) { + $analyzedContainer = new ContainerBuilder(); + $analyzedContainer->setAliases($container->getAliases()); + $analyzedContainer->setDefinitions($container->getDefinitions()); + foreach ($container->getExpressionLanguageProviders() as $provider) { + $analyzedContainer->addExpressionLanguageProvider($provider); + } + } else { + $analyzedContainer = $container; + } + try { + $notInlinableIds = []; + $remainingInlinedIds = []; + $this->connectedIds = $this->notInlinedIds = $container->getDefinitions(); + do { + if ($this->analyzingPass) { + $analyzedContainer->setDefinitions(array_intersect_key($analyzedContainer->getDefinitions(), $this->connectedIds)); + $this->analyzingPass->process($analyzedContainer); + } + $this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph(); + $notInlinedIds = $this->notInlinedIds; + $notInlinableIds += $this->notInlinableIds; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = $this->notInlinableIds = []; + + foreach ($analyzedContainer->getDefinitions() as $id => $definition) { + if (!$this->graph->hasNode($id)) { + continue; + } + foreach ($this->graph->getNode($id)->getOutEdges() as $edge) { + if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) { + $this->currentId = $id; + $this->processValue($definition, true); + break; + } + } + } + + foreach ($this->inlinedIds as $id => $isPublicOrNotShared) { + if ($isPublicOrNotShared) { + $remainingInlinedIds[$id] = $id; + } else { + $container->removeDefinition($id); + $analyzedContainer->removeDefinition($id); + } + } + } while ($this->inlinedIds && $this->analyzingPass); + + foreach ($remainingInlinedIds as $id) { + if (isset($notInlinableIds[$id])) { + continue; + } + + $definition = $container->getDefinition($id); + + if (!$definition->isShared() && !$definition->isPublic()) { + $container->removeDefinition($id); + } + } + } finally { + $this->container = null; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = []; + $this->notInlinableIds = []; + $this->graph = null; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof ArgumentInterface) { + // References found in ArgumentInterface::getValues() are not inlineable + return $value; + } + + if ($value instanceof Definition && $this->cloningIds) { + if ($value->isShared()) { + return $value; + } + $value = clone $value; + } + + if (!$value instanceof Reference) { + return parent::processValue($value, $isRoot); + } elseif (!$this->container->hasDefinition($id = (string) $value)) { + return $value; + } + + $definition = $this->container->getDefinition($id); + + if (isset($this->notInlinableIds[$id]) || !$this->isInlineableDefinition($id, $definition)) { + if ($this->currentId !== $id) { + $this->notInlinableIds[$id] = true; + } + + return $value; + } + + $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); + $this->inlinedIds[$id] = $definition->isPublic() || !$definition->isShared(); + $this->notInlinedIds[$this->currentId] = true; + + if ($definition->isShared()) { + return $definition; + } + + if (isset($this->cloningIds[$id])) { + $ids = array_keys($this->cloningIds); + $ids[] = $id; + + throw new ServiceCircularReferenceException($id, \array_slice($ids, array_search($id, $ids))); + } + + $this->cloningIds[$id] = true; + try { + return $this->processValue($definition); + } finally { + unset($this->cloningIds[$id]); + } + } + + /** + * Checks if the definition is inlineable. + */ + private function isInlineableDefinition(string $id, Definition $definition): bool + { + if (str_starts_with($id, '.autowire_inline.')) { + return true; + } + if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) { + return false; + } + + if (!$definition->isShared()) { + if (!$this->graph->hasNode($id)) { + return true; + } + + foreach ($this->graph->getNode($id)->getInEdges() as $edge) { + $srcId = $edge->getSourceNode()->getId(); + $this->connectedIds[$srcId] = true; + if ($edge->isWeak() || $edge->isLazy()) { + return !$this->connectedIds[$id] = true; + } + } + + return true; + } + + if ($definition->isPublic()) { + return false; + } + + if (!$this->graph->hasNode($id)) { + return true; + } + + if ($this->currentId === $id) { + return false; + } + $this->connectedIds[$id] = true; + + $srcIds = []; + $srcCount = 0; + foreach ($this->graph->getNode($id)->getInEdges() as $edge) { + $srcId = $edge->getSourceNode()->getId(); + $this->connectedIds[$srcId] = true; + if ($edge->isWeak() || $edge->isLazy()) { + return false; + } + $srcIds[$srcId] = true; + ++$srcCount; + } + + if (1 !== \count($srcIds)) { + $this->notInlinedIds[$id] = true; + + return false; + } + + if ($srcCount > 1 && \is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) { + return false; + } + + return $this->container->getDefinition($srcId)->isShared(); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php b/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php new file mode 100644 index 0000000..4c885f6 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; + +/** + * Merges extension configs into the container builder. + * + * @author Fabien Potencier + */ +class MergeExtensionConfigurationPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $parameters = $container->getParameterBag()->all(); + $definitions = $container->getDefinitions(); + $aliases = $container->getAliases(); + $exprLangProviders = $container->getExpressionLanguageProviders(); + $configAvailable = class_exists(BaseNode::class); + + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof PrependExtensionInterface) { + $extension->prepend($container); + } + } + + foreach ($container->getExtensions() as $name => $extension) { + if (!$config = $container->getExtensionConfig($name)) { + // this extension was not called + continue; + } + $resolvingBag = $container->getParameterBag(); + if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) { + // create a dedicated bag so that we can track env vars per-extension + $resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag); + if ($configAvailable) { + BaseNode::setPlaceholderUniquePrefix($resolvingBag->getEnvPlaceholderUniquePrefix()); + } + } + + try { + $config = $resolvingBag->resolveValue($config); + } catch (ParameterNotFoundException $e) { + $e->setSourceExtensionName($name); + + throw $e; + } + + try { + $tmpContainer = new MergeExtensionConfigurationContainerBuilder($extension, $resolvingBag); + $tmpContainer->setResourceTracking($container->isTrackingResources()); + $tmpContainer->addObjectResource($extension); + if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) { + $tmpContainer->addObjectResource($configuration); + } + + foreach ($exprLangProviders as $provider) { + $tmpContainer->addExpressionLanguageProvider($provider); + } + + $extension->load($config, $tmpContainer); + } catch (\Exception $e) { + if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { + $container->getParameterBag()->mergeEnvPlaceholders($resolvingBag); + } + + throw $e; + } + + if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { + // don't keep track of env vars that are *overridden* when configs are merged + $resolvingBag->freezeAfterProcessing($extension, $tmpContainer); + } + + $container->merge($tmpContainer); + $container->getParameterBag()->add($parameters); + } + + $container->addDefinitions($definitions); + $container->addAliases($aliases); + } +} + +/** + * @internal + */ +class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag +{ + private array $processedEnvPlaceholders; + + public function __construct(parent $parameterBag) + { + parent::__construct($parameterBag->all()); + $this->mergeEnvPlaceholders($parameterBag); + } + + public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container): void + { + if (!$config = $extension->getProcessedConfigs()) { + // Extension::processConfiguration() wasn't called, we cannot know how configs were merged + return; + } + $this->processedEnvPlaceholders = []; + + // serialize config and container to catch env vars nested in object graphs + $config = serialize($config).serialize($container->getDefinitions()).serialize($container->getAliases()).serialize($container->getParameterBag()->all()); + + if (false === stripos($config, 'env_')) { + return; + } + + preg_match_all('/env_[a-f0-9]{16}_\w+_[a-f0-9]{32}/Ui', $config, $matches); + $usedPlaceholders = array_flip($matches[0]); + foreach (parent::getEnvPlaceholders() as $env => $placeholders) { + foreach ($placeholders as $placeholder) { + if (isset($usedPlaceholders[$placeholder])) { + $this->processedEnvPlaceholders[$env] = $placeholders; + break; + } + } + } + } + + public function getEnvPlaceholders(): array + { + return $this->processedEnvPlaceholders ?? parent::getEnvPlaceholders(); + } + + public function getUnusedEnvPlaceholders(): array + { + return !isset($this->processedEnvPlaceholders) ? [] : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders); + } +} + +/** + * A container builder preventing using methods that wouldn't have any effect from extensions. + * + * @internal + */ +class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder +{ + private string $extensionClass; + + public function __construct(ExtensionInterface $extension, ?ParameterBagInterface $parameterBag = null) + { + parent::__construct($parameterBag); + + $this->extensionClass = $extension::class; + } + + public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): static + { + throw new LogicException(sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', get_debug_type($pass), $this->extensionClass)); + } + + public function registerExtension(ExtensionInterface $extension): void + { + throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', get_debug_type($extension), $this->extensionClass)); + } + + public function compile(bool $resolveEnvPlaceholders = false): void + { + throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass)); + } + + public function resolveEnvPlaceholders(mixed $value, string|bool|null $format = null, ?array &$usedEnvs = null): mixed + { + if (true !== $format || !\is_string($value)) { + return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); + } + + $bag = $this->getParameterBag(); + $value = $bag->resolveValue($value); + + if (!$bag instanceof EnvPlaceholderParameterBag) { + return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); + } + + foreach ($bag->getEnvPlaceholders() as $env => $placeholders) { + if (!str_contains($env, ':')) { + continue; + } + foreach ($placeholders as $placeholder) { + if (false !== stripos($value, $placeholder)) { + throw new RuntimeException(sprintf('Using a cast in "env(%s)" is incompatible with resolution at compile time in "%s". The logic in the extension should be moved to a compiler pass, or an env parameter with no cast should be used instead.', $env, $this->extensionClass)); + } + } + } + + return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/PassConfig.php b/vendor/symfony/dependency-injection/Compiler/PassConfig.php new file mode 100644 index 0000000..35e2c20 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/PassConfig.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Compiler Pass Configuration. + * + * This class has a default configuration embedded. + * + * @author Johannes M. Schmitt + */ +class PassConfig +{ + public const TYPE_AFTER_REMOVING = 'afterRemoving'; + public const TYPE_BEFORE_OPTIMIZATION = 'beforeOptimization'; + public const TYPE_BEFORE_REMOVING = 'beforeRemoving'; + public const TYPE_OPTIMIZE = 'optimization'; + public const TYPE_REMOVE = 'removing'; + + private MergeExtensionConfigurationPass $mergePass; + private array $afterRemovingPasses; + private array $beforeOptimizationPasses; + private array $beforeRemovingPasses = []; + private array $optimizationPasses; + private array $removingPasses; + + public function __construct() + { + $this->mergePass = new MergeExtensionConfigurationPass(); + + $this->beforeOptimizationPasses = [ + 100 => [ + new ResolveClassPass(), + new RegisterAutoconfigureAttributesPass(), + new AutowireAsDecoratorPass(), + new AttributeAutoconfigurationPass(), + new ResolveInstanceofConditionalsPass(), + new RegisterEnvVarProcessorsPass(), + ], + -1000 => [new ExtensionCompilerPass()], + ]; + + $this->optimizationPasses = [[ + new AutoAliasServicePass(), + new ValidateEnvPlaceholdersPass(), + new ResolveDecoratorStackPass(), + new ResolveAutowireInlineAttributesPass(), + new ResolveChildDefinitionsPass(), + new RegisterServiceSubscribersPass(), + new ResolveParameterPlaceHoldersPass(false, false), + new ResolveFactoryClassPass(), + new ResolveNamedArgumentsPass(), + new AutowireRequiredMethodsPass(), + new AutowireRequiredPropertiesPass(), + new ResolveBindingsPass(), + new ServiceLocatorTagPass(), + new DecoratorServicePass(), + new CheckDefinitionValidityPass(), + new AutowirePass(false), + new ServiceLocatorTagPass(), + new ResolveTaggedIteratorArgumentPass(), + new ResolveServiceSubscribersPass(), + new ResolveReferencesToAliasesPass(), + new ResolveInvalidReferencesPass(), + new AnalyzeServiceReferencesPass(true), + new CheckCircularReferencesPass(), + new CheckReferenceValidityPass(), + new CheckArgumentsValidityPass(false), + ]]; + + $this->removingPasses = [[ + new RemovePrivateAliasesPass(), + new ReplaceAliasByActualDefinitionPass(), + new RemoveAbstractDefinitionsPass(), + new RemoveUnusedDefinitionsPass(), + new AnalyzeServiceReferencesPass(), + new CheckExceptionOnInvalidReferenceBehaviorPass(), + new InlineServiceDefinitionsPass(new AnalyzeServiceReferencesPass()), + new AnalyzeServiceReferencesPass(), + new DefinitionErrorExceptionPass(), + ]]; + + $this->afterRemovingPasses = [ + 0 => [ + new ResolveHotPathPass(), + new ResolveNoPreloadPass(), + new AliasDeprecatedPublicServicesPass(), + ], + // Let build parameters be available as late as possible + -2048 => [new RemoveBuildParametersPass()], + ]; + } + + /** + * Returns all passes in order to be processed. + * + * @return CompilerPassInterface[] + */ + public function getPasses(): array + { + return array_merge( + [$this->mergePass], + $this->getBeforeOptimizationPasses(), + $this->getOptimizationPasses(), + $this->getBeforeRemovingPasses(), + $this->getRemovingPasses(), + $this->getAfterRemovingPasses() + ); + } + + /** + * Adds a pass. + * + * @throws InvalidArgumentException when a pass type doesn't exist + */ + public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): void + { + $property = $type.'Passes'; + if (!isset($this->$property)) { + throw new InvalidArgumentException(sprintf('Invalid type "%s".', $type)); + } + + $passes = &$this->$property; + + if (!isset($passes[$priority])) { + $passes[$priority] = []; + } + $passes[$priority][] = $pass; + } + + /** + * Gets all passes for the AfterRemoving pass. + * + * @return CompilerPassInterface[] + */ + public function getAfterRemovingPasses(): array + { + return $this->sortPasses($this->afterRemovingPasses); + } + + /** + * Gets all passes for the BeforeOptimization pass. + * + * @return CompilerPassInterface[] + */ + public function getBeforeOptimizationPasses(): array + { + return $this->sortPasses($this->beforeOptimizationPasses); + } + + /** + * Gets all passes for the BeforeRemoving pass. + * + * @return CompilerPassInterface[] + */ + public function getBeforeRemovingPasses(): array + { + return $this->sortPasses($this->beforeRemovingPasses); + } + + /** + * Gets all passes for the Optimization pass. + * + * @return CompilerPassInterface[] + */ + public function getOptimizationPasses(): array + { + return $this->sortPasses($this->optimizationPasses); + } + + /** + * Gets all passes for the Removing pass. + * + * @return CompilerPassInterface[] + */ + public function getRemovingPasses(): array + { + return $this->sortPasses($this->removingPasses); + } + + /** + * Gets the Merge pass. + */ + public function getMergePass(): CompilerPassInterface + { + return $this->mergePass; + } + + public function setMergePass(CompilerPassInterface $pass): void + { + $this->mergePass = $pass; + } + + /** + * Sets the AfterRemoving passes. + * + * @param CompilerPassInterface[] $passes + */ + public function setAfterRemovingPasses(array $passes): void + { + $this->afterRemovingPasses = [$passes]; + } + + /** + * Sets the BeforeOptimization passes. + * + * @param CompilerPassInterface[] $passes + */ + public function setBeforeOptimizationPasses(array $passes): void + { + $this->beforeOptimizationPasses = [$passes]; + } + + /** + * Sets the BeforeRemoving passes. + * + * @param CompilerPassInterface[] $passes + */ + public function setBeforeRemovingPasses(array $passes): void + { + $this->beforeRemovingPasses = [$passes]; + } + + /** + * Sets the Optimization passes. + * + * @param CompilerPassInterface[] $passes + */ + public function setOptimizationPasses(array $passes): void + { + $this->optimizationPasses = [$passes]; + } + + /** + * Sets the Removing passes. + * + * @param CompilerPassInterface[] $passes + */ + public function setRemovingPasses(array $passes): void + { + $this->removingPasses = [$passes]; + } + + /** + * Sort passes by priority. + * + * @param array $passes CompilerPassInterface instances with their priority as key + * + * @return CompilerPassInterface[] + */ + private function sortPasses(array $passes): array + { + if (0 === \count($passes)) { + return []; + } + + krsort($passes); + + // Flatten the array + return array_merge(...$passes); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php b/vendor/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php new file mode 100644 index 0000000..5d2110b --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Trait that allows a generic method to find and sort service by priority option in the tag. + * + * @author Iltar van der Berg + */ +trait PriorityTaggedServiceTrait +{ + /** + * Finds all services with the given tag name and order them by their priority. + * + * The order of additions must be respected for services having the same priority, + * and knowing that the \SplPriorityQueue class does not respect the FIFO method, + * we should not use that class. + * + * @see https://bugs.php.net/53710 + * @see https://bugs.php.net/60926 + * + * @return Reference[] + */ + private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container, array $exclude = []): array + { + $indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null; + + if ($tagName instanceof TaggedIteratorArgument) { + $indexAttribute = $tagName->getIndexAttribute(); + $defaultIndexMethod = $tagName->getDefaultIndexMethod(); + $needsIndexes = $tagName->needsIndexes(); + $defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority'; + $exclude = array_merge($exclude, $tagName->getExclude()); + $tagName = $tagName->getTag(); + } + + $i = 0; + $services = []; + + foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { + if (\in_array($serviceId, $exclude, true)) { + continue; + } + + $defaultPriority = null; + $defaultIndex = null; + $definition = $container->getDefinition($serviceId); + $class = $definition->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; + $checkTaggedItem = !$definition->hasTag($definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName); + + foreach ($attributes as $attribute) { + $index = $priority = null; + + if (isset($attribute['priority'])) { + $priority = $attribute['priority']; + } elseif (null === $defaultPriority && $defaultPriorityMethod && $class) { + $defaultPriority = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem); + } + $priority ??= $defaultPriority ??= 0; + + if (null === $indexAttribute && !$defaultIndexMethod && !$needsIndexes) { + $services[] = [$priority, ++$i, null, $serviceId, null]; + continue 2; + } + + if (null !== $indexAttribute && isset($attribute[$indexAttribute])) { + $index = $attribute[$indexAttribute]; + } elseif (null === $defaultIndex && $defaultPriorityMethod && $class) { + $defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem); + } + $decorated = $definition->getTag('container.decorator')[0]['id'] ?? null; + $index = $index ?? $defaultIndex ?? $defaultIndex = $decorated ?? $serviceId; + + $services[] = [$priority, ++$i, $index, $serviceId, $class]; + } + } + + uasort($services, static fn ($a, $b) => $b[0] <=> $a[0] ?: $a[1] <=> $b[1]); + + $refs = []; + foreach ($services as [, , $index, $serviceId, $class]) { + if (!$class) { + $reference = new Reference($serviceId); + } elseif ($index === $serviceId) { + $reference = new TypedReference($serviceId, $class); + } else { + $reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index); + } + + if (null === $index) { + $refs[] = $reference; + } else { + $refs[$index] = $reference; + } + } + + return $refs; + } +} + +/** + * @internal + */ +class PriorityTaggedServiceUtil +{ + public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem): string|int|null + { + if (!($r = $container->getReflectionClass($class)) || (!$checkTaggedItem && !$r->hasMethod($defaultMethod))) { + return null; + } + + if ($checkTaggedItem && !$r->hasMethod($defaultMethod)) { + foreach ($r->getAttributes(AsTaggedItem::class) as $attribute) { + return 'priority' === $indexAttribute ? $attribute->newInstance()->priority : $attribute->newInstance()->index; + } + + return null; + } + + if ($r->isInterface()) { + return null; + } + + if (null !== $indexAttribute) { + $service = $class !== $serviceId ? sprintf('service "%s"', $serviceId) : 'on the corresponding service'; + $message = [sprintf('Either method "%s::%s()" should ', $class, $defaultMethod), sprintf(' or tag "%s" on %s is missing attribute "%s".', $tagName, $service, $indexAttribute)]; + } else { + $message = [sprintf('Method "%s::%s()" should ', $class, $defaultMethod), '.']; + } + + if (!($rm = $r->getMethod($defaultMethod))->isStatic()) { + throw new InvalidArgumentException(implode('be static', $message)); + } + + if (!$rm->isPublic()) { + throw new InvalidArgumentException(implode('be public', $message)); + } + + $default = $rm->invoke(null); + + if ('priority' === $indexAttribute) { + if (!\is_int($default)) { + throw new InvalidArgumentException(implode(sprintf('return int (got "%s")', get_debug_type($default)), $message)); + } + + return $default; + } + + if (\is_int($default)) { + $default = (string) $default; + } + + if (!\is_string($default)) { + throw new InvalidArgumentException(implode(sprintf('return string|int (got "%s")', get_debug_type($default)), $message)); + } + + return $default; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php b/vendor/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php new file mode 100644 index 0000000..ec40eee --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; +use Symfony\Component\DependencyInjection\Attribute\Lazy; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\AutoconfigureFailedException; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** + * Reads #[Autoconfigure] attributes on definitions that are autoconfigured + * and don't have the "container.ignore_attributes" tag. + * + * @author Nicolas Grekas + */ +final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface +{ + private static \Closure $registerForAutoconfiguration; + + public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $id => $definition) { + if ($this->accept($definition) && $class = $container->getReflectionClass($definition->getClass(), false)) { + $this->processClass($container, $class); + } + } + } + + public function accept(Definition $definition): bool + { + return $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes'); + } + + public function processClass(ContainerBuilder $container, \ReflectionClass $class): void + { + $autoconfigure = $class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF); + $lazy = $class->getAttributes(Lazy::class, \ReflectionAttribute::IS_INSTANCEOF); + + if ($autoconfigure && $lazy) { + throw new AutoconfigureFailedException($class->name, 'Using both attributes #[Lazy] and #[Autoconfigure] on an argument is not allowed; use the "lazy" parameter of #[Autoconfigure] instead.'); + } + + $attributes = array_merge($autoconfigure, $lazy); + + foreach ($attributes as $attribute) { + self::registerForAutoconfiguration($container, $class, $attribute); + } + } + + private static function registerForAutoconfiguration(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute): void + { + if (isset(self::$registerForAutoconfiguration)) { + (self::$registerForAutoconfiguration)($container, $class, $attribute); + + return; + } + + $parseDefinitions = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinitions'); + $yamlLoader = $parseDefinitions->getDeclaringClass()->newInstanceWithoutConstructor(); + + self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) { + $attribute = (array) $attribute->newInstance(); + + foreach ($attribute['tags'] ?? [] as $i => $tag) { + if (\is_array($tag) && [0] === array_keys($tag)) { + $attribute['tags'][$i] = [$class->name => $tag[0]]; + } + } + + $parseDefinitions->invoke( + $yamlLoader, + [ + 'services' => [ + '_instanceof' => [ + $class->name => [$container->registerForAutoconfiguration($class->name)] + $attribute, + ], + ], + ], + $class->getFileName(), + false + ); + }; + + (self::$registerForAutoconfiguration)($container, $class, $attribute); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php b/vendor/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php new file mode 100644 index 0000000..4c562fb --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\EnvVarProcessor; +use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Creates the container.env_var_processors_locator service. + * + * @author Nicolas Grekas + */ +class RegisterEnvVarProcessorsPass implements CompilerPassInterface +{ + private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string', \BackedEnum::class]; + + public function process(ContainerBuilder $container): void + { + $bag = $container->getParameterBag(); + $types = []; + $processors = []; + foreach ($container->findTaggedServiceIds('container.env_var_processor') as $id => $tags) { + if (!$r = $container->getReflectionClass($class = $container->getDefinition($id)->getClass())) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } elseif (!$r->isSubclassOf(EnvVarProcessorInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class)); + } + foreach ($class::getProvidedTypes() as $prefix => $type) { + $processors[$prefix] = new Reference($id); + $types[$prefix] = self::validateProvidedTypes($type, $class); + } + } + + if ($bag instanceof EnvPlaceholderParameterBag) { + foreach (EnvVarProcessor::getProvidedTypes() as $prefix => $type) { + if (!isset($types[$prefix])) { + $types[$prefix] = self::validateProvidedTypes($type, EnvVarProcessor::class); + } + } + $bag->setProvidedTypes($types); + } + + if ($processors) { + $container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors)) + ->setPublic(true) + ; + } + } + + private static function validateProvidedTypes(string $types, string $class): array + { + $types = explode('|', $types); + + foreach ($types as $type) { + if (!\in_array($type, self::ALLOWED_TYPES, true)) { + throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::ALLOWED_TYPES))); + } + } + + return $types; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php b/vendor/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php new file mode 100644 index 0000000..4600bf4 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class RegisterReverseContainerPass implements CompilerPassInterface +{ + private bool $beforeRemoving; + + public function __construct(bool $beforeRemoving) + { + $this->beforeRemoving = $beforeRemoving; + } + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('reverse_container')) { + return; + } + + $refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + $services = []; + foreach ($container->findTaggedServiceIds('container.reversible') as $id => $tags) { + $services[$id] = new Reference($id, $refType); + } + + if ($this->beforeRemoving) { + // prevent inlining of the reverse container + $services['reverse_container'] = new Reference('reverse_container', $refType); + } + $locator = $container->getDefinition('reverse_container')->getArgument(1); + + if ($locator instanceof Reference) { + $locator = $container->getDefinition((string) $locator); + } + if ($locator instanceof Definition) { + foreach ($services as $id => $ref) { + $services[$id] = new ServiceClosureArgument($ref); + } + $locator->replaceArgument(0, $services); + } else { + $locator->setValues($services); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php b/vendor/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php new file mode 100644 index 0000000..3f57313 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Psr\Container\ContainerInterface as PsrContainerInterface; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\SubscribedService; +use Symfony\Contracts\Service\ServiceProviderInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * Compiler pass to register tagged services that require a service locator. + * + * @author Nicolas Grekas + */ +class RegisterServiceSubscribersPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) { + return parent::processValue($value, $isRoot); + } + + $serviceMap = []; + $autowire = $value->isAutowired(); + + foreach ($value->getTag('container.service_subscriber') as $attributes) { + if (!$attributes) { + $autowire = true; + continue; + } + ksort($attributes); + if ([] !== array_diff(array_keys($attributes), ['id', 'key'])) { + throw new InvalidArgumentException(sprintf('The "container.service_subscriber" tag accepts only the "key" and "id" attributes, "%s" given for service "%s".', implode('", "', array_keys($attributes)), $this->currentId)); + } + if (!\array_key_exists('id', $attributes)) { + throw new InvalidArgumentException(sprintf('Missing "id" attribute on "container.service_subscriber" tag with key="%s" for service "%s".', $attributes['key'], $this->currentId)); + } + if (!\array_key_exists('key', $attributes)) { + $attributes['key'] = $attributes['id']; + } + if (isset($serviceMap[$attributes['key']])) { + continue; + } + $serviceMap[$attributes['key']] = new Reference($attributes['id']); + } + $class = $value->getClass(); + + if (!$r = $this->container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId)); + } + if (!$r->isSubclassOf(ServiceSubscriberInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class)); + } + $class = $r->name; + $subscriberMap = []; + + foreach ($class::getSubscribedServices() as $key => $type) { + $attributes = []; + + if (!isset($serviceMap[$key]) && $type instanceof Autowire) { + $subscriberMap[$key] = $type; + continue; + } + + if ($type instanceof SubscribedService) { + $key = $type->key ?? $key; + $attributes = $type->attributes; + $type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s::getSubscribedServices()" returns "%s", a type must be set.', $class, SubscribedService::class))); + } + + if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { + throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type))); + } + $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('?' === $type[0]) { + $type = substr($type, 1); + $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } + if (\is_int($name = $key)) { + $key = $type; + $name = null; + } + if (!isset($serviceMap[$key])) { + if (!$autowire) { + throw new InvalidArgumentException(sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $this->currentId, $key, $class)); + } + $serviceMap[$key] = new Reference($type); + } + + if ($name) { + if (false !== $i = strpos($name, '::get')) { + $name = lcfirst(substr($name, 5 + $i)); + } elseif (str_contains($name, '::')) { + $name = null; + } + } + + if (null !== $name && !$this->container->has($name) && !$this->container->has($type.' $'.$name)) { + $camelCaseName = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name)))); + $name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name; + } + + $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes); + unset($serviceMap[$key]); + } + + if ($serviceMap = array_keys($serviceMap)) { + $message = sprintf(1 < \count($serviceMap) ? 'keys "%s" do' : 'key "%s" does', str_replace('%', '%%', implode('", "', $serviceMap))); + throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId)); + } + + $locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId); + + $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]); + + $value->setBindings([ + PsrContainerInterface::class => new BoundArgument($locatorRef, false), + ServiceProviderInterface::class => new BoundArgument($locatorRef, false), + ] + $value->getBindings()); + + return parent::processValue($value); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php b/vendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php new file mode 100644 index 0000000..e21b15d --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes abstract Definitions. + */ +class RemoveAbstractDefinitionsPass implements CompilerPassInterface +{ + /** + * Removes abstract definitions from the ContainerBuilder. + */ + public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isAbstract()) { + $container->removeDefinition($id); + $container->log($this, sprintf('Removed service "%s"; reason: abstract.', $id)); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php b/vendor/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php new file mode 100644 index 0000000..6dfb46d --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RemoveBuildParametersPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class RemoveBuildParametersPass implements CompilerPassInterface +{ + /** + * @var array + */ + private array $removedParameters = []; + + public function process(ContainerBuilder $container): void + { + $parameterBag = $container->getParameterBag(); + $this->removedParameters = []; + + foreach ($parameterBag->all() as $name => $value) { + if ('.' === ($name[0] ?? '')) { + $this->removedParameters[$name] = $value; + + $parameterBag->remove($name); + $container->log($this, sprintf('Removing build parameter "%s".', $name)); + } + } + } + + /** + * @return array + */ + public function getRemovedParameters(): array + { + return $this->removedParameters; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php b/vendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php new file mode 100644 index 0000000..968b165 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Remove private aliases from the container. They were only used to establish + * dependencies between services, and these dependencies have been resolved in + * one of the previous passes. + * + * @author Johannes M. Schmitt + */ +class RemovePrivateAliasesPass implements CompilerPassInterface +{ + /** + * Removes private aliases from the ContainerBuilder. + */ + public function process(ContainerBuilder $container): void + { + foreach ($container->getAliases() as $id => $alias) { + if ($alias->isPublic()) { + continue; + } + + $container->removeAlias($id); + $container->log($this, sprintf('Removed service "%s"; reason: private alias.', $id)); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php b/vendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php new file mode 100644 index 0000000..3c1e390 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Removes unused service definitions from the container. + * + * @author Johannes M. Schmitt + * @author Nicolas Grekas + */ +class RemoveUnusedDefinitionsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $connectedIds = []; + + /** + * Processes the ContainerBuilder to remove unused definitions. + */ + public function process(ContainerBuilder $container): void + { + try { + $this->enableExpressionProcessing(); + $this->container = $container; + $connectedIds = []; + $aliases = $container->getAliases(); + + foreach ($aliases as $id => $alias) { + if ($alias->isPublic()) { + $this->connectedIds[] = (string) $aliases[$id]; + } + } + + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isPublic()) { + $connectedIds[$id] = true; + $this->processValue($definition); + } + } + + while ($this->connectedIds) { + $ids = $this->connectedIds; + $this->connectedIds = []; + foreach ($ids as $id) { + if (!isset($connectedIds[$id]) && $container->hasDefinition($id)) { + $connectedIds[$id] = true; + $this->processValue($container->getDefinition($id)); + } + } + } + + foreach ($container->getDefinitions() as $id => $definition) { + if (!isset($connectedIds[$id])) { + $container->removeDefinition($id); + $container->resolveEnvPlaceholders(!$definition->hasErrors() ? serialize($definition) : $definition); + $container->log($this, sprintf('Removed service "%s"; reason: unused.', $id)); + } + } + } finally { + $this->container = null; + $this->connectedIds = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof Reference) { + return parent::processValue($value, $isRoot); + } + + if (ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) { + $this->connectedIds[] = (string) $value; + } + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php b/vendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php new file mode 100644 index 0000000..9b83323 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Replaces aliases with actual service definitions, effectively removing these + * aliases. + * + * @author Johannes M. Schmitt + */ +class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $replacements; + + /** + * Process the Container to replace aliases with service definitions. + * + * @throws InvalidArgumentException if the service definition does not exist + */ + public function process(ContainerBuilder $container): void + { + // First collect all alias targets that need to be replaced + $seenAliasTargets = []; + $replacements = []; + + foreach ($container->getAliases() as $definitionId => $target) { + $targetId = (string) $target; + // Special case: leave this target alone + if ('service_container' === $targetId) { + continue; + } + // Check if target needs to be replaced + if (isset($replacements[$targetId])) { + $container->setAlias($definitionId, $replacements[$targetId])->setPublic($target->isPublic()); + + if ($target->isDeprecated()) { + $container->getAlias($definitionId)->setDeprecated(...array_values($target->getDeprecation('%alias_id%'))); + } + } + // No need to process the same target twice + if (isset($seenAliasTargets[$targetId])) { + continue; + } + // Process new target + $seenAliasTargets[$targetId] = true; + try { + $definition = $container->getDefinition($targetId); + } catch (ServiceNotFoundException $e) { + if ('' !== $e->getId() && '@' === $e->getId()[0]) { + throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]); + } + + throw $e; + } + if ($definition->isPublic()) { + continue; + } + // Remove private definition and schedule for replacement + $definition->setPublic($target->isPublic()); + $container->setDefinition($definitionId, $definition); + $container->removeDefinition($targetId); + $replacements[$targetId] = $definitionId; + + if ($target->isPublic() && $target->isDeprecated()) { + $definition->addTag('container.private', $target->getDeprecation('%service_id%')); + } + } + $this->replacements = $replacements; + + parent::process($container); + $this->replacements = []; + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) { + // Perform the replacement + $newId = $this->replacements[$referenceId]; + $value = new Reference($newId, $value->getInvalidBehavior()); + $this->container->log($this, sprintf('Changed reference of service "%s" previously pointing to "%s" to "%s".', $this->currentId, $referenceId, $newId)); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveAutowireInlineAttributesPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveAutowireInlineAttributesPass.php new file mode 100644 index 0000000..e2df19d --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveAutowireInlineAttributesPass.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Attribute\AutowireInline; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * Inspects existing autowired services for {@see AutowireInline} attributes and registers the definitions for reuse. + * + * @author Ismail Özgün Turan + */ +class ResolveAutowireInlineAttributesPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + $value = parent::processValue($value, $isRoot); + + if (!$value instanceof Definition || !$value->isAutowired() || !$value->getClass() || $value->hasTag('container.ignore_attributes')) { + return $value; + } + + $isChildDefinition = $value instanceof ChildDefinition; + + try { + $constructor = $this->getConstructor($value, false); + } catch (RuntimeException) { + return $value; + } + + if ($constructor) { + $arguments = $this->registerAutowireInlineAttributes($constructor, $value->getArguments(), $isChildDefinition); + + if ($arguments !== $value->getArguments()) { + $value->setArguments($arguments); + } + } + + $methodCalls = $value->getMethodCalls(); + + foreach ($methodCalls as $i => $call) { + [$method, $arguments] = $call; + + try { + $method = $this->getReflectionMethod($value, $method); + } catch (RuntimeException) { + continue; + } + + $arguments = $this->registerAutowireInlineAttributes($method, $arguments, $isChildDefinition); + + if ($arguments !== $call[1]) { + $methodCalls[$i][1] = $arguments; + } + } + + if ($methodCalls !== $value->getMethodCalls()) { + $value->setMethodCalls($methodCalls); + } + + return $value; + } + + private function registerAutowireInlineAttributes(\ReflectionFunctionAbstract $method, array $arguments, bool $isChildDefinition): array + { + $parameters = $method->getParameters(); + + if ($method->isVariadic()) { + array_pop($parameters); + } + $paramResolverContainer = new ContainerBuilder($this->container->getParameterBag()); + + foreach ($parameters as $index => $parameter) { + if ($isChildDefinition) { + $index = 'index_'.$index; + } + + if (\array_key_exists('$'.$parameter->name, $arguments) || (\array_key_exists($index, $arguments) && '' !== $arguments[$index])) { + continue; + } + if (!$attribute = $parameter->getAttributes(AutowireInline::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) { + continue; + } + + $type = ProxyHelper::exportType($parameter, true); + + if (!$type && isset($arguments[$index])) { + continue; + } + + $attribute = $attribute->newInstance(); + $definition = $attribute->buildDefinition($attribute->value, $type, $parameter); + + $paramResolverContainer->setDefinition('.autowire_inline', $definition); + (new ResolveParameterPlaceHoldersPass(false, false))->process($paramResolverContainer); + + $id = '.autowire_inline.'.ContainerBuilder::hash([$this->currentId, $method->class ?? null, $method->name, (string) $parameter]); + + $this->container->setDefinition($id, $definition); + $arguments[$isChildDefinition ? '$'.$parameter->name : $index] = new Reference($id); + + if ($definition->isAutowired()) { + $currentId = $this->currentId; + try { + $this->currentId = $id; + $this->processValue($definition, true); + } finally { + $this->currentId = $currentId; + } + } + } + + return $arguments; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveBindingsPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveBindingsPass.php new file mode 100644 index 0000000..e00eb44 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveBindingsPass.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\Attribute\Target; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * @author Guilhem Niot + */ +class ResolveBindingsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $usedBindings = []; + private array $unusedBindings = []; + private array $errorMessages = []; + + public function process(ContainerBuilder $container): void + { + $this->usedBindings = $container->getRemovedBindingIds(); + + try { + parent::process($container); + + foreach ($this->unusedBindings as [$key, $serviceId, $bindingType, $file]) { + $argumentType = $argumentName = $message = null; + + if (str_contains($key, ' ')) { + [$argumentType, $argumentName] = explode(' ', $key, 2); + } elseif ('$' === $key[0]) { + $argumentName = $key; + } else { + $argumentType = $key; + } + + if ($argumentType) { + $message .= sprintf('of type "%s" ', $argumentType); + } + + if ($argumentName) { + $message .= sprintf('named "%s" ', $argumentName); + } + + if (BoundArgument::DEFAULTS_BINDING === $bindingType) { + $message .= 'under "_defaults"'; + } elseif (BoundArgument::INSTANCEOF_BINDING === $bindingType) { + $message .= 'under "_instanceof"'; + } else { + $message .= sprintf('for service "%s"', $serviceId); + } + + if ($file) { + $message .= sprintf(' in file "%s"', $file); + } + + $message = sprintf('A binding is configured for an argument %s, but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.', $message); + + if ($this->errorMessages) { + $message .= sprintf("\nCould be related to%s:", 1 < \count($this->errorMessages) ? ' one of' : ''); + } + foreach ($this->errorMessages as $m) { + $message .= "\n - ".$m; + } + throw new InvalidArgumentException($message); + } + } finally { + $this->usedBindings = []; + $this->unusedBindings = []; + $this->errorMessages = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof TypedReference && $value->getType() === (string) $value) { + // Already checked + $bindings = $this->container->getDefinition($this->currentId)->getBindings(); + $name = $value->getName(); + + if (isset($name, $bindings[$name = $value.' $'.$name])) { + return $this->getBindingValue($bindings[$name]); + } + + if (isset($bindings[$value->getType()])) { + return $this->getBindingValue($bindings[$value->getType()]); + } + + return parent::processValue($value, $isRoot); + } + + if (!$value instanceof Definition || !$bindings = $value->getBindings()) { + return parent::processValue($value, $isRoot); + } + + $bindingNames = []; + + foreach ($bindings as $key => $binding) { + [$bindingValue, $bindingId, $used, $bindingType, $file] = $binding->getValues(); + if ($used) { + $this->usedBindings[$bindingId] = true; + unset($this->unusedBindings[$bindingId]); + } elseif (!isset($this->usedBindings[$bindingId])) { + $this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file]; + } + + if (preg_match('/^(?:(?:array|bool|float|int|string|iterable|([^ $]++)) )\$/', $key, $m)) { + $bindingNames[substr($key, \strlen($m[0]))] = $binding; + } + + if (!isset($m[1])) { + continue; + } + + if (is_subclass_of($m[1], \UnitEnum::class)) { + $bindingNames[substr($key, \strlen($m[0]))] = $binding; + continue; + } + + if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition && !$bindingValue instanceof TaggedIteratorArgument && !$bindingValue instanceof ServiceLocatorArgument) { + throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected "%s", "%s", "%s", "%s" or null, "%s" given.', $key, $this->currentId, Reference::class, Definition::class, TaggedIteratorArgument::class, ServiceLocatorArgument::class, get_debug_type($bindingValue))); + } + } + + if ($value->isAbstract()) { + return parent::processValue($value, $isRoot); + } + + $calls = $value->getMethodCalls(); + + try { + if ($constructor = $this->getConstructor($value, false)) { + $calls[] = [$constructor, $value->getArguments()]; + } + } catch (RuntimeException $e) { + $this->errorMessages[] = $e->getMessage(); + $this->container->getDefinition($this->currentId)->addError($e->getMessage()); + + return parent::processValue($value, $isRoot); + } + + foreach ($calls as $i => $call) { + [$method, $arguments] = $call; + + if ($method instanceof \ReflectionFunctionAbstract) { + $reflectionMethod = $method; + } else { + try { + $reflectionMethod = $this->getReflectionMethod($value, $method); + } catch (RuntimeException $e) { + if ($value->getFactory()) { + continue; + } + throw $e; + } + } + + $names = []; + + foreach ($reflectionMethod->getParameters() as $key => $parameter) { + $names[$key] = $parameter->name; + + if (\array_key_exists($key, $arguments) && '' !== $arguments[$key]) { + continue; + } + if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name]) { + continue; + } + if ( + $value->isAutowired() + && !$value->hasTag('container.ignore_attributes') + && $parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF) + ) { + continue; + } + + $typeHint = ltrim(ProxyHelper::exportType($parameter) ?? '', '?'); + + $name = Target::parseName($parameter, parsedName: $parsedName); + + if ($typeHint && ( + \array_key_exists($k = preg_replace('/(^|[(|&])\\\\/', '\1', $typeHint).' $'.$name, $bindings) + || \array_key_exists($k = preg_replace('/(^|[(|&])\\\\/', '\1', $typeHint).' $'.$parsedName, $bindings) + )) { + $arguments[$key] = $this->getBindingValue($bindings[$k]); + + continue; + } + + if (\array_key_exists($k = '$'.$name, $bindings) || \array_key_exists($k = '$'.$parsedName, $bindings)) { + $arguments[$key] = $this->getBindingValue($bindings[$k]); + + continue; + } + + if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = substr($typeHint, 1)])) { + $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); + + continue; + } + + if (isset($bindingNames[$name]) || isset($bindingNames[$parsedName]) || isset($bindingNames[$parameter->name])) { + $bindingKey = array_search($binding, $bindings, true); + $argumentType = substr($bindingKey, 0, strpos($bindingKey, ' ')); + $this->errorMessages[] = sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name); + } + } + + foreach ($names as $key => $name) { + if (\array_key_exists($name, $arguments) && (0 === $key || \array_key_exists($key - 1, $arguments))) { + $arguments[$key] = $arguments[$name]; + unset($arguments[$name]); + } + } + + if ($arguments !== $call[1]) { + ksort($arguments, \SORT_NATURAL); + $calls[$i][1] = $arguments; + } + } + + if ($constructor) { + [, $arguments] = array_pop($calls); + + if ($arguments !== $value->getArguments()) { + $value->setArguments($arguments); + } + } + + if ($calls !== $value->getMethodCalls()) { + $value->setMethodCalls($calls); + } + + return parent::processValue($value, $isRoot); + } + + private function getBindingValue(BoundArgument $binding): mixed + { + [$bindingValue, $bindingId] = $binding->getValues(); + + $this->usedBindings[$bindingId] = true; + unset($this->unusedBindings[$bindingId]); + + return $bindingValue; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php new file mode 100644 index 0000000..bc1eeeb --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ExceptionInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; + +/** + * This replaces all ChildDefinition instances with their equivalent fully + * merged Definition instance. + * + * @author Johannes M. Schmitt + * @author Nicolas Grekas + */ +class ResolveChildDefinitionsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $currentPath; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + if ($isRoot) { + // yes, we are specifically fetching the definition from the + // container to ensure we are not operating on stale data + $value = $this->container->getDefinition($this->currentId); + } + if ($value instanceof ChildDefinition) { + $this->currentPath = []; + $value = $this->resolveDefinition($value); + if ($isRoot) { + $this->container->setDefinition($this->currentId, $value); + } + } + + return parent::processValue($value, $isRoot); + } + + /** + * Resolves the definition. + * + * @throws RuntimeException When the definition is invalid + */ + private function resolveDefinition(ChildDefinition $definition): Definition + { + try { + return $this->doResolveDefinition($definition); + } catch (ServiceCircularReferenceException $e) { + throw $e; + } catch (ExceptionInterface $e) { + $r = new \ReflectionProperty($e, 'message'); + $r->setValue($e, sprintf('Service "%s": %s', $this->currentId, $e->getMessage())); + + throw $e; + } + } + + private function doResolveDefinition(ChildDefinition $definition): Definition + { + if (!$this->container->has($parent = $definition->getParent())) { + throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent)); + } + + $searchKey = array_search($parent, $this->currentPath); + $this->currentPath[] = $parent; + + if (false !== $searchKey) { + throw new ServiceCircularReferenceException($parent, \array_slice($this->currentPath, $searchKey)); + } + + $parentDef = $this->container->findDefinition($parent); + if ($parentDef instanceof ChildDefinition) { + $id = $this->currentId; + $this->currentId = $parent; + $parentDef = $this->resolveDefinition($parentDef); + $this->container->setDefinition($parent, $parentDef); + $this->currentId = $id; + } + + $this->container->log($this, sprintf('Resolving inheritance for "%s" (parent: %s).', $this->currentId, $parent)); + $def = new Definition(); + + // merge in parent definition + // purposely ignored attributes: abstract, shared, tags, autoconfigured + $def->setClass($parentDef->getClass()); + $def->setArguments($parentDef->getArguments()); + $def->setMethodCalls($parentDef->getMethodCalls()); + $def->setProperties($parentDef->getProperties()); + if ($parentDef->isDeprecated()) { + $deprecation = $parentDef->getDeprecation('%service_id%'); + $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + $def->setFactory($parentDef->getFactory()); + $def->setConfigurator($parentDef->getConfigurator()); + $def->setFile($parentDef->getFile()); + $def->setPublic($parentDef->isPublic()); + $def->setLazy($parentDef->isLazy()); + $def->setAutowired($parentDef->isAutowired()); + $def->setChanges($parentDef->getChanges()); + + $def->setBindings($definition->getBindings() + $parentDef->getBindings()); + + $def->setSynthetic($definition->isSynthetic()); + + // overwrite with values specified in the decorator + $changes = $definition->getChanges(); + if (isset($changes['class'])) { + $def->setClass($definition->getClass()); + } + if (isset($changes['factory'])) { + $def->setFactory($definition->getFactory()); + } + if (isset($changes['configurator'])) { + $def->setConfigurator($definition->getConfigurator()); + } + if (isset($changes['file'])) { + $def->setFile($definition->getFile()); + } + if (isset($changes['public'])) { + $def->setPublic($definition->isPublic()); + } else { + $def->setPublic($parentDef->isPublic()); + } + if (isset($changes['lazy'])) { + $def->setLazy($definition->isLazy()); + } + if (isset($changes['deprecated']) && $definition->isDeprecated()) { + $deprecation = $definition->getDeprecation('%service_id%'); + $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + if (isset($changes['autowired'])) { + $def->setAutowired($definition->isAutowired()); + } + if (isset($changes['shared'])) { + $def->setShared($definition->isShared()); + } + if (isset($changes['decorated_service'])) { + $decoratedService = $definition->getDecoratedService(); + if (null === $decoratedService) { + $def->setDecoratedService($decoratedService); + } else { + $def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2], $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); + } + } + + // merge arguments + foreach ($definition->getArguments() as $k => $v) { + if (is_numeric($k)) { + $def->addArgument($v); + } elseif (str_starts_with($k, 'index_')) { + $def->replaceArgument((int) substr($k, \strlen('index_')), $v); + } else { + $def->setArgument($k, $v); + } + } + + // merge properties + foreach ($definition->getProperties() as $k => $v) { + $def->setProperty($k, $v); + } + + // append method calls + if ($calls = $definition->getMethodCalls()) { + $def->setMethodCalls(array_merge($def->getMethodCalls(), $calls)); + } + + $def->addError($parentDef); + $def->addError($definition); + + // these attributes are always taken from the child + $def->setAbstract($definition->isAbstract()); + $def->setTags($definition->getTags()); + // autoconfigure is never taken from parent (on purpose) + // and it's not legal on an instanceof + $def->setAutoconfigured($definition->isAutoconfigured()); + + if (!$def->hasTag('proxy')) { + foreach ($parentDef->getTag('proxy') as $v) { + $def->addTag('proxy', $v); + } + } + + return $def; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveClassPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveClassPass.php new file mode 100644 index 0000000..dd9823f --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveClassPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + */ +class ResolveClassPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isSynthetic() || null !== $definition->getClass()) { + continue; + } + if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $id)) { + if ($definition instanceof ChildDefinition && !class_exists($id)) { + throw new InvalidArgumentException(sprintf('Service definition "%s" has a parent but no class, and its name looks like an FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id)); + } + $definition->setClass($id); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php new file mode 100644 index 0000000..da022a8 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class ResolveDecoratorStackPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $stacks = []; + + foreach ($container->findTaggedServiceIds('container.stack') as $id => $tags) { + $definition = $container->getDefinition($id); + + if (!$definition instanceof ChildDefinition) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "container.stack" tag.', $id)); + } + + if (!$stack = $definition->getArguments()) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id)); + } + + $stacks[$id] = $stack; + } + + if (!$stacks) { + return; + } + + $resolvedDefinitions = []; + + foreach ($container->getDefinitions() as $id => $definition) { + if (!isset($stacks[$id])) { + $resolvedDefinitions[$id] = $definition; + continue; + } + + foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) { + $resolvedDefinitions[$k] = $v; + } + + $alias = $container->setAlias($id, $k); + + if ($definition->getChanges()['public'] ?? false) { + $alias->setPublic($definition->isPublic()); + } + + if ($definition->isDeprecated()) { + $alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%'))); + } + } + + $container->setDefinitions($resolvedDefinitions); + } + + private function resolveStack(array $stacks, array $path): array + { + $definitions = []; + $id = end($path); + $prefix = '.'.$id.'.'; + + if (!isset($stacks[$id])) { + return [$id => new ChildDefinition($id)]; + } + + if (key($path) !== $searchKey = array_search($id, $path)) { + throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey)); + } + + foreach ($stacks[$id] as $k => $definition) { + if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) { + $path[] = $definition->getParent(); + $definition = unserialize(serialize($definition)); // deep clone + } elseif ($definition instanceof Definition) { + $definitions[$decoratedId = $prefix.$k] = $definition; + continue; + } elseif ($definition instanceof Reference || $definition instanceof Alias) { + $path[] = (string) $definition; + } else { + throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition))); + } + + $p = $prefix.$k; + + foreach ($this->resolveStack($stacks, $path) as $k => $v) { + $definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k); + $definition = null; + } + array_pop($path); + } + + if (1 === \count($path)) { + foreach ($definitions as $k => $definition) { + $definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId); + } + $definition->setDecoratedService(null); + } + + return $definitions; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php new file mode 100644 index 0000000..ea077cb --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * Replaces env var placeholders by their current values. + */ +class ResolveEnvPlaceholdersPass extends AbstractRecursivePass +{ + protected bool $skipScalars = false; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (\is_string($value)) { + return $this->container->resolveEnvPlaceholders($value, true); + } + if ($value instanceof Definition) { + $changes = $value->getChanges(); + if (isset($changes['class'])) { + $value->setClass($this->container->resolveEnvPlaceholders($value->getClass(), true)); + } + if (isset($changes['file'])) { + $value->setFile($this->container->resolveEnvPlaceholders($value->getFile(), true)); + } + } + + $value = parent::processValue($value, $isRoot); + + if ($value && \is_array($value) && !$isRoot) { + $value = array_combine($this->container->resolveEnvPlaceholders(array_keys($value), true), $value); + } + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php new file mode 100644 index 0000000..2beaa01 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * @author Maxime Steinhausser + */ +class ResolveFactoryClassPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Definition && \is_array($factory = $value->getFactory()) && null === $factory[0]) { + if (null === $class = $value->getClass()) { + throw new RuntimeException(sprintf('The "%s" service is defined to be created by a factory, but is missing the factory class. Did you forget to define the factory or service class?', $this->currentId)); + } + + $factory[0] = $class; + $value->setFactory($factory); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveHotPathPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveHotPathPass.php new file mode 100644 index 0000000..a3eb1df --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveHotPathPass.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Propagate "container.hot_path" tags to referenced services. + * + * @author Nicolas Grekas + */ +class ResolveHotPathPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private array $resolvedIds = []; + + public function process(ContainerBuilder $container): void + { + try { + parent::process($container); + $container->getDefinition('service_container')->clearTag('container.hot_path'); + } finally { + $this->resolvedIds = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof ArgumentInterface) { + return $value; + } + + if ($value instanceof Definition && $isRoot) { + if ($value->isDeprecated()) { + return $value->clearTag('container.hot_path'); + } + + $this->resolvedIds[$this->currentId] = true; + + if (!$value->hasTag('container.hot_path')) { + return $value; + } + } + + if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { + $definition = $this->container->getDefinition($id); + + if ($definition->isDeprecated() || $definition->hasTag('container.hot_path')) { + return $value; + } + + $definition->addTag('container.hot_path'); + + if (isset($this->resolvedIds[$id])) { + parent::processValue($definition, false); + } + + return $value; + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php new file mode 100644 index 0000000..31d9432 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * Applies instanceof conditionals to definitions. + * + * @author Nicolas Grekas + */ +class ResolveInstanceofConditionalsPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) { + if ($definition->getArguments()) { + throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface)); + } + } + + $tagsToKeep = []; + + if ($container->hasParameter('container.behavior_describing_tags')) { + $tagsToKeep = $container->getParameter('container.behavior_describing_tags'); + } + + foreach ($container->getDefinitions() as $id => $definition) { + $container->setDefinition($id, $this->processDefinition($container, $id, $definition, $tagsToKeep)); + } + + if ($container->hasParameter('container.behavior_describing_tags')) { + $container->getParameterBag()->remove('container.behavior_describing_tags'); + } + } + + private function processDefinition(ContainerBuilder $container, string $id, Definition $definition, array $tagsToKeep): Definition + { + $instanceofConditionals = $definition->getInstanceofConditionals(); + $autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : []; + if (!$instanceofConditionals && !$autoconfiguredInstanceof) { + return $definition; + } + + if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) { + return $definition; + } + + $conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container); + + $definition->setInstanceofConditionals([]); + $shared = null; + $instanceofTags = []; + $instanceofCalls = []; + $instanceofBindings = []; + $reflectionClass = null; + $parent = $definition instanceof ChildDefinition ? $definition->getParent() : null; + + foreach ($conditionals as $interface => $instanceofDefs) { + if ($interface !== $class && !($reflectionClass ??= $container->getReflectionClass($class, false) ?: false)) { + continue; + } + + if ($interface !== $class && !is_subclass_of($class, $interface)) { + continue; + } + + foreach ($instanceofDefs as $key => $instanceofDef) { + /** @var ChildDefinition $instanceofDef */ + $instanceofDef = clone $instanceofDef; + $instanceofDef->setAbstract(true)->setParent($parent ?: '.abstract.instanceof.'.$id); + $parent = '.instanceof.'.$interface.'.'.$key.'.'.$id; + $container->setDefinition($parent, $instanceofDef); + $instanceofTags[] = [$interface, $instanceofDef->getTags()]; + $instanceofBindings = $instanceofDef->getBindings() + $instanceofBindings; + + foreach ($instanceofDef->getMethodCalls() as $methodCall) { + $instanceofCalls[] = $methodCall; + } + + $instanceofDef->setTags([]); + $instanceofDef->setMethodCalls([]); + $instanceofDef->setBindings([]); + + if (isset($instanceofDef->getChanges()['shared'])) { + $shared = $instanceofDef->isShared(); + } + } + } + + if ($parent) { + $bindings = $definition->getBindings(); + $abstract = $container->setDefinition('.abstract.instanceof.'.$id, $definition); + $definition->setBindings([]); + $definition = serialize($definition); + + if (Definition::class === $abstract::class) { + // cast Definition to ChildDefinition + $definition = substr_replace($definition, '53', 2, 2); + $definition = substr_replace($definition, 'Child', 44, 0); + } + /** @var ChildDefinition $definition */ + $definition = unserialize($definition); + $definition->setParent($parent); + + if (null !== $shared && !isset($definition->getChanges()['shared'])) { + $definition->setShared($shared); + } + + // Don't add tags to service decorators + $i = \count($instanceofTags); + while (0 <= --$i) { + [$interface, $tags] = $instanceofTags[$i]; + foreach ($tags as $k => $v) { + if (null === $definition->getDecoratedService() || $interface === $definition->getClass() || \in_array($k, $tagsToKeep, true)) { + foreach ($v as $v) { + if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k), true)) { + continue; + } + $definition->addTag($k, $v); + } + } + } + } + + $definition->setMethodCalls(array_merge($instanceofCalls, $definition->getMethodCalls())); + $definition->setBindings($bindings + $instanceofBindings); + + // reset fields with "merge" behavior + $abstract + ->setBindings([]) + ->setArguments([]) + ->setMethodCalls([]) + ->setDecoratedService(null) + ->setTags([]) + ->setAbstract(true); + } + + return $definition; + } + + private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container): array + { + // make each value an array of ChildDefinition + $conditionals = array_map(fn ($childDef) => [$childDef], $autoconfiguredInstanceof); + + foreach ($instanceofConditionals as $interface => $instanceofDef) { + // make sure the interface/class exists (but don't validate automaticInstanceofConditionals) + if (!$container->getReflectionClass($interface)) { + throw new RuntimeException(sprintf('"%s" is set as an "instanceof" conditional, but it does not exist.', $interface)); + } + + if (!isset($autoconfiguredInstanceof[$interface])) { + $conditionals[$interface] = []; + } + + $conditionals[$interface][] = $instanceofDef; + } + + return $conditionals; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php new file mode 100644 index 0000000..a00cce0 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Emulates the invalid behavior if the reference is not found within the + * container. + * + * @author Johannes M. Schmitt + */ +class ResolveInvalidReferencesPass implements CompilerPassInterface +{ + private ContainerBuilder $container; + private RuntimeException $signalingException; + private string $currentId; + + /** + * Process the ContainerBuilder to resolve invalid references. + */ + public function process(ContainerBuilder $container): void + { + $this->container = $container; + $this->signalingException = new RuntimeException('Invalid reference.'); + + try { + foreach ($container->getDefinitions() as $this->currentId => $definition) { + $this->processValue($definition); + } + } finally { + unset($this->container, $this->signalingException); + } + } + + /** + * Processes arguments to determine invalid references. + * + * @throws RuntimeException When an invalid reference is found + */ + private function processValue(mixed $value, int $rootLevel = 0, int $level = 0): mixed + { + if ($value instanceof ServiceClosureArgument) { + $value->setValues($this->processValue($value->getValues(), 1, 1)); + } elseif ($value instanceof ArgumentInterface) { + $value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level)); + } elseif ($value instanceof Definition) { + if ($value->isSynthetic() || $value->isAbstract()) { + return $value; + } + $value->setArguments($this->processValue($value->getArguments(), 0)); + $value->setProperties($this->processValue($value->getProperties(), 1)); + $value->setMethodCalls($this->processValue($value->getMethodCalls(), 2)); + } elseif (\is_array($value)) { + $i = 0; + + foreach ($value as $k => $v) { + try { + if (false !== $i && $k !== $i++) { + $i = false; + } + if ($v !== $processedValue = $this->processValue($v, $rootLevel, 1 + $level)) { + $value[$k] = $processedValue; + } + } catch (RuntimeException $e) { + if ($rootLevel < $level || ($rootLevel && !$level)) { + unset($value[$k]); + } elseif ($rootLevel) { + throw $e; + } else { + $value[$k] = null; + } + } + } + + // Ensure numerically indexed arguments have sequential numeric keys. + if (false !== $i) { + $value = array_values($value); + } + } elseif ($value instanceof Reference) { + if ($this->container->hasDefinition($id = (string) $value) ? !$this->container->getDefinition($id)->hasTag('container.excluded') : $this->container->hasAlias($id)) { + return $value; + } + + $currentDefinition = $this->container->getDefinition($this->currentId); + + // resolve decorated service behavior depending on decorator service + if ($currentDefinition->innerServiceId === $id && ContainerInterface::NULL_ON_INVALID_REFERENCE === $currentDefinition->decorationOnInvalid) { + return null; + } + + $invalidBehavior = $value->getInvalidBehavior(); + + if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) { + $e = new ServiceNotFoundException($id, $this->currentId); + + // since the error message varies by $id and $this->currentId, so should the id of the dummy errored definition + $this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, $id), $value->getType()) + ->addError($e->getMessage()); + + return new TypedReference($id, $value->getType(), $value->getInvalidBehavior()); + } + + // resolve invalid behavior + if (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { + $value = null; + } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { + if (0 < $level || $rootLevel) { + throw $this->signalingException; + } + $value = null; + } + } + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php new file mode 100644 index 0000000..24fac73 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * Resolves named arguments to their corresponding numeric index. + * + * @author Kévin Dunglas + */ +class ResolveNamedArgumentsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof AbstractArgument && $value->getText().'.' === $value->getTextWithContext()) { + $value->setContext(sprintf('A value found in service "%s"', $this->currentId)); + } + + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + + $calls = $value->getMethodCalls(); + $calls[] = ['__construct', $value->getArguments()]; + + foreach ($calls as $i => $call) { + [$method, $arguments] = $call; + $parameters = null; + $resolvedKeys = []; + $resolvedArguments = []; + + foreach ($arguments as $key => $argument) { + if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) { + $argument->setContext(sprintf('Argument '.(\is_int($key) ? 1 + $key : '"%3$s"').' of '.('__construct' === $method ? 'service "%s"' : 'method call "%s::%s()"'), $this->currentId, $method, $key)); + } + + if (\is_int($key)) { + $resolvedKeys[$key] = $key; + $resolvedArguments[$key] = $argument; + continue; + } + + if (null === $parameters) { + $r = $this->getReflectionMethod($value, $method); + $class = $r instanceof \ReflectionMethod ? $r->class : $this->currentId; + $method = $r->getName(); + $parameters = $r->getParameters(); + } + + if (isset($key[0]) && '$' !== $key[0] && !class_exists($key) && !interface_exists($key, false)) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": did you forget to add the "$" prefix to argument "%s"?', $this->currentId, $key)); + } + + if (isset($key[0]) && '$' === $key[0]) { + foreach ($parameters as $j => $p) { + if ($key === '$'.$p->name) { + if ($p->isVariadic() && \is_array($argument)) { + foreach ($argument as $variadicArgument) { + $resolvedKeys[$j] = $j; + $resolvedArguments[$j++] = $variadicArgument; + } + } else { + $resolvedKeys[$j] = $p->name; + $resolvedArguments[$j] = $argument; + } + + continue 2; + } + } + + throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key)); + } + + if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of "%s" or an instance of "%s", "%s" given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, get_debug_type($argument))); + } + + $typeFound = false; + foreach ($parameters as $j => $p) { + if (!\array_key_exists($j, $resolvedArguments) && ProxyHelper::exportType($p, true) === $key) { + $resolvedKeys[$j] = $p->name; + $resolvedArguments[$j] = $argument; + $typeFound = true; + } + } + + if (!$typeFound) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key)); + } + } + + if ($resolvedArguments !== $call[1]) { + ksort($resolvedArguments); + + if (!$value->isAutowired() && !array_is_list($resolvedArguments)) { + ksort($resolvedKeys); + $resolvedArguments = array_combine($resolvedKeys, $resolvedArguments); + } + + $calls[$i][1] = $resolvedArguments; + } + } + + [, $arguments] = array_pop($calls); + + if ($arguments !== $value->getArguments()) { + $value->setArguments($arguments); + } + if ($calls !== $value->getMethodCalls()) { + $value->setMethodCalls($calls); + } + + foreach ($value->getProperties() as $key => $argument) { + if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) { + $argument->setContext(sprintf('Property "%s" of service "%s"', $key, $this->currentId)); + } + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php new file mode 100644 index 0000000..2c129b3 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Propagate the "container.no_preload" tag. + * + * @author Nicolas Grekas + */ +class ResolveNoPreloadPass extends AbstractRecursivePass +{ + private const DO_PRELOAD_TAG = '.container.do_preload'; + + protected bool $skipScalars = true; + + private array $resolvedIds = []; + + public function process(ContainerBuilder $container): void + { + $this->container = $container; + + try { + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) { + $this->resolvedIds[$id] = true; + $this->processValue($definition, true); + } + } + + foreach ($container->getAliases() as $alias) { + if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->hasDefinition($id)) { + $this->resolvedIds[$id] = true; + $this->processValue($container->getDefinition($id), true); + } + } + } finally { + $this->resolvedIds = []; + $this->container = null; + } + + foreach ($container->getDefinitions() as $definition) { + if ($definition->hasTag(self::DO_PRELOAD_TAG)) { + $definition->clearTag(self::DO_PRELOAD_TAG); + } elseif (!$definition->isDeprecated() && !$definition->hasErrors()) { + $definition->addTag('container.no_preload'); + } + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { + $definition = $this->container->getDefinition($id); + + if (!isset($this->resolvedIds[$id]) && (!$definition->isPublic() || $definition->isPrivate())) { + $this->resolvedIds[$id] = true; + $this->processValue($definition, true); + } + + return $value; + } + + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + + if ($value->hasTag('container.no_preload') || $value->isDeprecated() || $value->hasErrors()) { + return $value; + } + + if ($isRoot) { + $value->addTag(self::DO_PRELOAD_TAG); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php new file mode 100644 index 0000000..8a90d3c --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; + +/** + * Resolves all parameter placeholders "%somevalue%" to their real values. + * + * @author Johannes M. Schmitt + */ +class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass +{ + protected bool $skipScalars = false; + + private ParameterBagInterface $bag; + + public function __construct( + private bool $resolveArrays = true, + private bool $throwOnResolveException = true, + ) { + } + + /** + * @throws ParameterNotFoundException + */ + public function process(ContainerBuilder $container): void + { + $this->bag = $container->getParameterBag(); + + try { + parent::process($container); + + $aliases = []; + foreach ($container->getAliases() as $name => $target) { + $this->currentId = $name; + $aliases[$this->bag->resolveValue($name)] = $target; + } + $container->setAliases($aliases); + } catch (ParameterNotFoundException $e) { + $e->setSourceId($this->currentId); + + throw $e; + } + + $this->bag->resolve(); + unset($this->bag); + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (\is_string($value)) { + try { + $v = $this->bag->resolveValue($value); + } catch (ParameterNotFoundException $e) { + if ($this->throwOnResolveException) { + throw $e; + } + + $v = null; + $this->container->getDefinition($this->currentId)->addError($e->getMessage()); + } + + return $this->resolveArrays || !$v || !\is_array($v) ? $v : $value; + } + if ($value instanceof Definition) { + $value->setBindings($this->processValue($value->getBindings())); + $changes = $value->getChanges(); + if (isset($changes['class'])) { + $value->setClass($this->bag->resolveValue($value->getClass())); + } + if (isset($changes['file'])) { + $value->setFile($this->bag->resolveValue($value->getFile())); + } + $tags = $value->getTags(); + if (isset($tags['proxy'])) { + $tags['proxy'] = $this->bag->resolveValue($tags['proxy']); + $value->setTags($tags); + } + } + + $value = parent::processValue($value, $isRoot); + + if ($value && \is_array($value)) { + $value = array_combine($this->bag->resolveValue(array_keys($value)), $value); + } + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php new file mode 100644 index 0000000..b8923c6 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Replaces all references to aliases with references to the actual service. + * + * @author Johannes M. Schmitt + */ +class ResolveReferencesToAliasesPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + public function process(ContainerBuilder $container): void + { + parent::process($container); + + foreach ($container->getAliases() as $id => $alias) { + $aliasId = (string) $alias; + $this->currentId = $id; + + if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) { + $container->setAlias($id, $defId)->setPublic($alias->isPublic()); + } + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof Reference) { + return parent::processValue($value, $isRoot); + } + + $defId = $this->getDefinitionId($id = (string) $value, $this->container); + + return $defId !== $id ? new Reference($defId, $value->getInvalidBehavior()) : $value; + } + + private function getDefinitionId(string $id, ContainerBuilder $container): string + { + if (!$container->hasAlias($id)) { + return $id; + } + + $alias = $container->getAlias($id); + + if ($alias->isDeprecated()) { + $referencingDefinition = $container->hasDefinition($this->currentId) ? $container->getDefinition($this->currentId) : $container->getAlias($this->currentId); + if (!$referencingDefinition->isDeprecated()) { + $deprecation = $alias->getDeprecation($id); + trigger_deprecation($deprecation['package'], $deprecation['version'], rtrim($deprecation['message'], '. ').'. It is being referenced by the "%s" '.($container->hasDefinition($this->currentId) ? 'service.' : 'alias.'), $this->currentId); + } + } + + $seen = []; + do { + if (isset($seen[$id])) { + throw new ServiceCircularReferenceException($id, array_merge(array_keys($seen), [$id])); + } + + $seen[$id] = true; + $id = (string) $container->getAlias($id); + } while ($container->hasAlias($id)); + + return $id; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php new file mode 100644 index 0000000..9171412 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * Compiler pass to inject their service locator to service subscribers. + * + * @author Nicolas Grekas + */ +class ResolveServiceSubscribersPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private ?string $serviceLocator = null; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], true)) { + return new Reference($this->serviceLocator); + } + + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + + $serviceLocator = $this->serviceLocator; + $this->serviceLocator = null; + + if ($value->hasTag('container.service_subscriber.locator')) { + $this->serviceLocator = $value->getTag('container.service_subscriber.locator')[0]['id']; + $value->clearTag('container.service_subscriber.locator'); + } + + try { + return parent::processValue($value); + } finally { + $this->serviceLocator = $serviceLocator; + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php new file mode 100644 index 0000000..c01e90b --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; + +/** + * Resolves all TaggedIteratorArgument arguments. + * + * @author Roland Franssen + */ +class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass +{ + use PriorityTaggedServiceTrait; + + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (!$value instanceof TaggedIteratorArgument) { + return parent::processValue($value, $isRoot); + } + + $exclude = $value->getExclude(); + if ($value->excludeSelf()) { + $exclude[] = $this->currentId; + } + + $value->setValues($this->findAndSortTaggedServices($value, $this->container, $exclude)); + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php b/vendor/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php new file mode 100644 index 0000000..032e905 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; + +/** + * Applies the "container.service_locator" tag by wrapping references into ServiceClosureArgument instances. + * + * @author Nicolas Grekas + */ +final class ServiceLocatorTagPass extends AbstractRecursivePass +{ + use PriorityTaggedServiceTrait; + + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof ServiceLocatorArgument) { + if ($value->getTaggedIteratorArgument()) { + $value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container)); + } + + return self::register($this->container, $value->getValues()); + } + + if ($value instanceof Definition) { + $value->setBindings(parent::processValue($value->getBindings())); + } + + if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) { + return parent::processValue($value, $isRoot); + } + + if (!$value->getClass()) { + $value->setClass(ServiceLocator::class); + } + + $services = $value->getArguments()[0] ?? null; + + if ($services instanceof TaggedIteratorArgument) { + $services = $this->findAndSortTaggedServices($services, $this->container); + } + + if (!\is_array($services)) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set.', $this->currentId)); + } + + $value->setArgument(0, self::map($services)); + + $id = '.service_locator.'.ContainerBuilder::hash($value); + + if ($isRoot) { + if ($id !== $this->currentId) { + $this->container->setAlias($id, new Alias($this->currentId, false)); + } + + return $value; + } + + $this->container->setDefinition($id, $value->setPublic(false)); + + return new Reference($id); + } + + public static function register(ContainerBuilder $container, array $map, ?string $callerId = null): Reference + { + $locator = (new Definition(ServiceLocator::class)) + ->addArgument(self::map($map)) + ->addTag('container.service_locator'); + + if (null !== $callerId && $container->hasDefinition($callerId)) { + $locator->setBindings($container->getDefinition($callerId)->getBindings()); + } + + if (!$container->hasDefinition($id = '.service_locator.'.ContainerBuilder::hash($locator))) { + $container->setDefinition($id, $locator); + } + + if (null !== $callerId) { + $locatorId = $id; + // Locators are shared when they hold the exact same list of factories; + // to have them specialized per consumer service, we use a cloning factory + // to derivate customized instances from the prototype one. + $container->register($id .= '.'.$callerId, ServiceLocator::class) + ->setFactory([new Reference($locatorId), 'withContext']) + ->addTag('container.service_locator_context', ['id' => $callerId]) + ->addArgument($callerId) + ->addArgument(new Reference('service_container')); + } + + return new Reference($id); + } + + public static function map(array $services): array + { + $i = 0; + + foreach ($services as $k => $v) { + if ($v instanceof ServiceClosureArgument) { + continue; + } + + if ($i === $k) { + if ($v instanceof Reference) { + unset($services[$k]); + $k = (string) $v; + } + ++$i; + } elseif (\is_int($k)) { + $i = null; + } + + $services[$k] = new ServiceClosureArgument($v); + } + + return $services; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php new file mode 100644 index 0000000..8310fb2 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * This is a directed graph of your services. + * + * This information can be used by your compiler passes instead of collecting + * it themselves which improves performance quite a lot. + * + * @author Johannes M. Schmitt + * + * @final + */ +class ServiceReferenceGraph +{ + /** + * @var ServiceReferenceGraphNode[] + */ + private array $nodes = []; + + public function hasNode(string $id): bool + { + return isset($this->nodes[$id]); + } + + /** + * Gets a node by identifier. + * + * @throws InvalidArgumentException if no node matches the supplied identifier + */ + public function getNode(string $id): ServiceReferenceGraphNode + { + if (!isset($this->nodes[$id])) { + throw new InvalidArgumentException(sprintf('There is no node with id "%s".', $id)); + } + + return $this->nodes[$id]; + } + + /** + * Returns all nodes. + * + * @return ServiceReferenceGraphNode[] + */ + public function getNodes(): array + { + return $this->nodes; + } + + /** + * Clears all nodes. + */ + public function clear(): void + { + foreach ($this->nodes as $node) { + $node->clear(); + } + $this->nodes = []; + } + + /** + * Connects 2 nodes together in the Graph. + */ + public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, ?Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void + { + if (null === $sourceId || null === $destId) { + return; + } + + $sourceNode = $this->createNode($sourceId, $sourceValue); + $destNode = $this->createNode($destId, $destValue); + $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor); + + $sourceNode->addOutEdge($edge); + $destNode->addInEdge($edge); + } + + private function createNode(string $id, mixed $value): ServiceReferenceGraphNode + { + if (isset($this->nodes[$id]) && $this->nodes[$id]->getValue() === $value) { + return $this->nodes[$id]; + } + + return $this->nodes[$id] = new ServiceReferenceGraphNode($id, $value); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php new file mode 100644 index 0000000..b607164 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +/** + * Represents an edge in your service graph. + * + * Value is typically a reference. + * + * @author Johannes M. Schmitt + */ +class ServiceReferenceGraphEdge +{ + private ServiceReferenceGraphNode $sourceNode; + private ServiceReferenceGraphNode $destNode; + private mixed $value; + private bool $lazy; + private bool $weak; + private bool $byConstructor; + + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, mixed $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false) + { + $this->sourceNode = $sourceNode; + $this->destNode = $destNode; + $this->value = $value; + $this->lazy = $lazy; + $this->weak = $weak; + $this->byConstructor = $byConstructor; + } + + /** + * Returns the value of the edge. + */ + public function getValue(): mixed + { + return $this->value; + } + + /** + * Returns the source node. + */ + public function getSourceNode(): ServiceReferenceGraphNode + { + return $this->sourceNode; + } + + /** + * Returns the destination node. + */ + public function getDestNode(): ServiceReferenceGraphNode + { + return $this->destNode; + } + + /** + * Returns true if the edge is lazy, meaning it's a dependency not requiring direct instantiation. + */ + public function isLazy(): bool + { + return $this->lazy; + } + + /** + * Returns true if the edge is weak, meaning it shouldn't prevent removing the target service. + */ + public function isWeak(): bool + { + return $this->weak; + } + + /** + * Returns true if the edge links with a constructor argument. + */ + public function isReferencedByConstructor(): bool + { + return $this->byConstructor; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php new file mode 100644 index 0000000..76bddec --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Represents a node in your service graph. + * + * Value is typically a definition, or an alias. + * + * @author Johannes M. Schmitt + */ +class ServiceReferenceGraphNode +{ + private string $id; + private array $inEdges = []; + private array $outEdges = []; + private mixed $value; + + public function __construct(string $id, mixed $value) + { + $this->id = $id; + $this->value = $value; + } + + public function addInEdge(ServiceReferenceGraphEdge $edge): void + { + $this->inEdges[] = $edge; + } + + public function addOutEdge(ServiceReferenceGraphEdge $edge): void + { + $this->outEdges[] = $edge; + } + + /** + * Checks if the value of this node is an Alias. + */ + public function isAlias(): bool + { + return $this->value instanceof Alias; + } + + /** + * Checks if the value of this node is a Definition. + */ + public function isDefinition(): bool + { + return $this->value instanceof Definition; + } + + /** + * Returns the identifier. + */ + public function getId(): string + { + return $this->id; + } + + /** + * Returns the in edges. + * + * @return ServiceReferenceGraphEdge[] + */ + public function getInEdges(): array + { + return $this->inEdges; + } + + /** + * Returns the out edges. + * + * @return ServiceReferenceGraphEdge[] + */ + public function getOutEdges(): array + { + return $this->outEdges; + } + + /** + * Returns the value of this Node. + */ + public function getValue(): mixed + { + return $this->value; + } + + /** + * Clears all edges. + */ + public function clear(): void + { + $this->inEdges = $this->outEdges = []; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php b/vendor/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php new file mode 100644 index 0000000..783080c --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; + +/** + * Validates environment variable placeholders used in extension configuration with dummy values. + * + * @author Roland Franssen + */ +class ValidateEnvPlaceholdersPass implements CompilerPassInterface +{ + private const TYPE_FIXTURES = ['array' => [], 'bool' => false, 'float' => 0.0, 'int' => 0, 'string' => '']; + + private array $extensionConfig = []; + + public function process(ContainerBuilder $container): void + { + $this->extensionConfig = []; + + if (!class_exists(BaseNode::class) || !$extensions = $container->getExtensions()) { + return; + } + + $resolvingBag = $container->getParameterBag(); + if (!$resolvingBag instanceof EnvPlaceholderParameterBag) { + return; + } + + $defaultBag = new ParameterBag($resolvingBag->all()); + $envTypes = $resolvingBag->getProvidedTypes(); + foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) { + $values = []; + if (false === $i = strpos($env, ':')) { + $default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::TYPE_FIXTURES['string']; + $defaultType = null !== $default ? get_debug_type($default) : 'string'; + $values[$defaultType] = $default; + } else { + $prefix = substr($env, 0, $i); + foreach ($envTypes[$prefix] ?? ['string'] as $type) { + $values[$type] = self::TYPE_FIXTURES[$type] ?? null; + } + } + foreach ($placeholders as $placeholder) { + BaseNode::setPlaceholder($placeholder, $values); + } + } + + $processor = new Processor(); + + foreach ($extensions as $name => $extension) { + if (!($extension instanceof ConfigurationExtensionInterface || $extension instanceof ConfigurationInterface) + || !$config = array_filter($container->getExtensionConfig($name)) + ) { + // this extension has no semantic configuration or was not called + continue; + } + + $config = $resolvingBag->resolveValue($config); + + if ($extension instanceof ConfigurationInterface) { + $configuration = $extension; + } elseif (null === $configuration = $extension->getConfiguration($config, $container)) { + continue; + } + + $this->extensionConfig[$name] = $processor->processConfiguration($configuration, $config); + } + + $resolvingBag->clearUnusedEnvPlaceholders(); + } + + /** + * @internal + */ + public function getExtensionConfig(): array + { + try { + return $this->extensionConfig; + } finally { + $this->extensionConfig = []; + } + } +} diff --git a/vendor/symfony/dependency-injection/Config/ContainerParametersResource.php b/vendor/symfony/dependency-injection/Config/ContainerParametersResource.php new file mode 100644 index 0000000..b066b5f --- /dev/null +++ b/vendor/symfony/dependency-injection/Config/ContainerParametersResource.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Tracks container parameters. + * + * @author Maxime Steinhausser + * + * @final + */ +class ContainerParametersResource implements ResourceInterface +{ + private array $parameters; + + /** + * @param array $parameters The container parameters to track + */ + public function __construct(array $parameters) + { + $this->parameters = $parameters; + } + + public function __toString(): string + { + return 'container_parameters_'.hash('xxh128', serialize($this->parameters)); + } + + public function getParameters(): array + { + return $this->parameters; + } +} diff --git a/vendor/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php b/vendor/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php new file mode 100644 index 0000000..619c5e1 --- /dev/null +++ b/vendor/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\ResourceCheckerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * @author Maxime Steinhausser + */ +class ContainerParametersResourceChecker implements ResourceCheckerInterface +{ + private ContainerInterface $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function supports(ResourceInterface $metadata): bool + { + return $metadata instanceof ContainerParametersResource; + } + + public function isFresh(ResourceInterface $resource, int $timestamp): bool + { + foreach ($resource->getParameters() as $key => $value) { + if (!$this->container->hasParameter($key) || $this->container->getParameter($key) !== $value) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/dependency-injection/Container.php b/vendor/symfony/dependency-injection/Container.php new file mode 100644 index 0000000..b0c9710 --- /dev/null +++ b/vendor/symfony/dependency-injection/Container.php @@ -0,0 +1,403 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator; +use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(RewindableGenerator::class); +class_exists(ArgumentServiceLocator::class); + +/** + * Container is a dependency injection container. + * + * It gives access to object instances (services). + * Services and parameters are simple key/pair stores. + * The container can have four possible behaviors when a service + * does not exist (or is not initialized for the last case): + * + * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at compilation time (the default) + * * NULL_ON_INVALID_REFERENCE: Returns null + * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference + * (for instance, ignore a setter if the service does not exist) + * * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references + * * RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at runtime + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class Container implements ContainerInterface, ResetInterface +{ + protected ParameterBagInterface $parameterBag; + protected array $services = []; + protected array $privates = []; + protected array $fileMap = []; + protected array $methodMap = []; + protected array $factories = []; + protected array $aliases = []; + protected array $loading = []; + protected array $resolving = []; + protected array $syntheticIds = []; + + private array $envCache = []; + private bool $compiled = false; + private \Closure $getEnv; + + private static \Closure $make; + + public function __construct(?ParameterBagInterface $parameterBag = null) + { + $this->parameterBag = $parameterBag ?? new EnvPlaceholderParameterBag(); + } + + /** + * Compiles the container. + * + * This method does two things: + * + * * Parameter values are resolved; + * * The parameter bag is frozen. + */ + public function compile(): void + { + $this->parameterBag->resolve(); + + $this->parameterBag = new FrozenParameterBag( + $this->parameterBag->all(), + $this->parameterBag instanceof ParameterBag ? $this->parameterBag->allDeprecated() : [] + ); + + $this->compiled = true; + } + + /** + * Returns true if the container is compiled. + */ + public function isCompiled(): bool + { + return $this->compiled; + } + + /** + * Gets the service container parameter bag. + */ + public function getParameterBag(): ParameterBagInterface + { + return $this->parameterBag; + } + + /** + * Gets a parameter. + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + return $this->parameterBag->get($name); + } + + public function hasParameter(string $name): bool + { + return $this->parameterBag->has($name); + } + + public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value): void + { + $this->parameterBag->set($name, $value); + } + + /** + * Sets a service. + * + * Setting a synthetic service to null resets it: has() returns false and get() + * behaves in the same way as if the service was never created. + */ + public function set(string $id, ?object $service): void + { + // Runs the internal initializer; used by the dumped container to include always-needed files + if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) { + $initialize = $this->privates['service_container']; + unset($this->privates['service_container']); + $initialize($this); + } + + if ('service_container' === $id) { + throw new InvalidArgumentException('You cannot set service "service_container".'); + } + + if (!(isset($this->fileMap[$id]) || isset($this->methodMap[$id]))) { + if (isset($this->syntheticIds[$id]) || !isset($this->getRemovedIds()[$id])) { + // no-op + } elseif (null === $service) { + throw new InvalidArgumentException(sprintf('The "%s" service is private, you cannot unset it.', $id)); + } else { + throw new InvalidArgumentException(sprintf('The "%s" service is private, you cannot replace it.', $id)); + } + } elseif (isset($this->services[$id])) { + throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id)); + } + + if (isset($this->aliases[$id])) { + unset($this->aliases[$id]); + } + + if (null === $service) { + unset($this->services[$id]); + + return; + } + + $this->services[$id] = $service; + } + + public function has(string $id): bool + { + if (isset($this->aliases[$id])) { + $id = $this->aliases[$id]; + } + if (isset($this->services[$id])) { + return true; + } + if ('service_container' === $id) { + return true; + } + + return isset($this->fileMap[$id]) || isset($this->methodMap[$id]); + } + + /** + * Gets a service. + * + * @throws ServiceCircularReferenceException When a circular reference is detected + * @throws ServiceNotFoundException When the service is not defined + * + * @see Reference + */ + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object + { + return $this->services[$id] + ?? $this->services[$id = $this->aliases[$id] ?? $id] + ?? ('service_container' === $id ? $this : ($this->factories[$id] ?? self::$make ??= self::make(...))($this, $id, $invalidBehavior)); + } + + /** + * Creates a service. + * + * As a separate method to allow "get()" to use the really fast `??` operator. + */ + private static function make(self $container, string $id, int $invalidBehavior): ?object + { + if (isset($container->loading[$id])) { + throw new ServiceCircularReferenceException($id, array_merge(array_keys($container->loading), [$id])); + } + + $container->loading[$id] = true; + + try { + if (isset($container->fileMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->load($container->fileMap[$id]); + } elseif (isset($container->methodMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->{$container->methodMap[$id]}($container); + } + } catch (\Exception $e) { + unset($container->services[$id]); + + throw $e; + } finally { + unset($container->loading[$id]); + } + + if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { + if (!$id) { + throw new ServiceNotFoundException($id); + } + if (isset($container->syntheticIds[$id])) { + throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is synthetic, it needs to be set at boot time before it can be used.', $id)); + } + if (isset($container->getRemovedIds()[$id])) { + throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.', $id)); + } + + $alternatives = []; + foreach ($container->getServiceIds() as $knownId) { + if ('' === $knownId || '.' === $knownId[0]) { + continue; + } + $lev = levenshtein($id, $knownId); + if ($lev <= \strlen($id) / 3 || str_contains($knownId, $id)) { + $alternatives[] = $knownId; + } + } + + throw new ServiceNotFoundException($id, null, null, $alternatives); + } + + return null; + } + + /** + * Returns true if the given service has actually been initialized. + */ + public function initialized(string $id): bool + { + if (isset($this->aliases[$id])) { + $id = $this->aliases[$id]; + } + + if ('service_container' === $id) { + return false; + } + + return isset($this->services[$id]); + } + + public function reset(): void + { + $services = $this->services + $this->privates; + $this->services = $this->factories = $this->privates = []; + + foreach ($services as $service) { + try { + if ($service instanceof ResetInterface) { + $service->reset(); + } + } catch (\Throwable) { + continue; + } + } + + $this->envCache = []; + } + + /** + * Gets all service ids. + * + * @return string[] + */ + public function getServiceIds(): array + { + return array_map('strval', array_unique(array_merge(['service_container'], array_keys($this->fileMap), array_keys($this->methodMap), array_keys($this->aliases), array_keys($this->services)))); + } + + /** + * Gets service ids that existed at compile time. + */ + public function getRemovedIds(): array + { + return []; + } + + /** + * Camelizes a string. + */ + public static function camelize(string $id): string + { + return strtr(ucwords(strtr($id, ['_' => ' ', '.' => '_ ', '\\' => '_ '])), [' ' => '']); + } + + /** + * A string to underscore. + */ + public static function underscore(string $id): string + { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], str_replace('_', '.', $id))); + } + + /** + * Creates a service by requiring its factory file. + */ + protected function load(string $file): mixed + { + return require $file; + } + + /** + * Fetches a variable from the environment. + * + * @throws EnvNotFoundException When the environment variable is not found and has no default value + */ + protected function getEnv(string $name): mixed + { + if (isset($this->resolving[$envName = "env($name)"])) { + throw new ParameterCircularReferenceException(array_keys($this->resolving)); + } + if (isset($this->envCache[$name]) || \array_key_exists($name, $this->envCache)) { + return $this->envCache[$name]; + } + if (!$this->has($id = 'container.env_var_processors_locator')) { + $this->set($id, new ServiceLocator([])); + } + $this->getEnv ??= $this->getEnv(...); + $processors = $this->get($id); + + if (false !== $i = strpos($name, ':')) { + $prefix = substr($name, 0, $i); + $localName = substr($name, 1 + $i); + } else { + $prefix = 'string'; + $localName = $name; + } + + $processor = $processors->has($prefix) ? $processors->get($prefix) : new EnvVarProcessor($this); + if (false === $i) { + $prefix = ''; + } + + $this->resolving[$envName] = true; + try { + return $this->envCache[$name] = $processor->getEnv($prefix, $localName, $this->getEnv); + } finally { + unset($this->resolving[$envName]); + } + } + + /** + * @internal + */ + final protected function getService(string|false $registry, string $id, ?string $method, string|bool $load): mixed + { + if ('service_container' === $id) { + return $this; + } + if (\is_string($load)) { + throw new RuntimeException($load); + } + if (null === $method) { + return false !== $registry ? $this->{$registry}[$id] ?? null : null; + } + if (false !== $registry) { + return $this->{$registry}[$id] ??= $load ? $this->load($method) : $this->{$method}($this); + } + if (!$load) { + return $this->{$method}($this); + } + + return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory($this) : $this->load($method); + } + + private function __clone() + { + } +} diff --git a/vendor/symfony/dependency-injection/ContainerBuilder.php b/vendor/symfony/dependency-injection/ContainerBuilder.php new file mode 100644 index 0000000..0689077 --- /dev/null +++ b/vendor/symfony/dependency-injection/ContainerBuilder.php @@ -0,0 +1,1705 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Composer\InstalledVersions; +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\Config\Resource\ComposerResource; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileExistenceResource; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\Config\Resource\ReflectionClassResource; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\LazyClosure; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Attribute\Target; +use Symfony\Component\DependencyInjection\Compiler\Compiler; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass; +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\LazyServiceInstantiator; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * ContainerBuilder is a DI container that provides an API to easily describe services. + * + * @author Fabien Potencier + */ +class ContainerBuilder extends Container implements TaggedContainerInterface +{ + /** + * @var array + */ + private array $extensions = []; + + /** + * @var array + */ + private array $extensionsByNs = []; + + /** + * @var array + */ + private array $definitions = []; + + /** + * @var array + */ + private array $aliasDefinitions = []; + + /** + * @var array + */ + private array $resources = []; + + /** + * @var array>> + */ + private array $extensionConfigs = []; + + private Compiler $compiler; + private bool $trackResources; + private InstantiatorInterface $proxyInstantiator; + private ExpressionLanguage $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private array $expressionLanguageProviders = []; + + /** + * @var string[] with tag names used by findTaggedServiceIds + */ + private array $usedTags = []; + + /** + * @var string[][] a map of env var names to their placeholders + */ + private array $envPlaceholders = []; + + /** + * @var int[] a map of env vars to their resolution counter + */ + private array $envCounters = []; + + /** + * @var string[] the list of vendor directories + */ + private array $vendors; + + /** + * @var array the cache for paths being in vendor directories + */ + private array $pathsInVendor = []; + + /** + * @var array + */ + private array $autoconfiguredInstanceof = []; + + /** + * @var array + */ + private array $autoconfiguredAttributes = []; + + /** + * @var array + */ + private array $removedIds = []; + + /** + * @var array + */ + private array $removedBindingIds = []; + + private const INTERNAL_TYPES = [ + 'int' => true, + 'float' => true, + 'string' => true, + 'bool' => true, + 'resource' => true, + 'object' => true, + 'array' => true, + 'null' => true, + 'callable' => true, + 'iterable' => true, + 'mixed' => true, + ]; + + public function __construct(?ParameterBagInterface $parameterBag = null) + { + parent::__construct($parameterBag); + + $this->trackResources = interface_exists(ResourceInterface::class); + $this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true)->setPublic(true)); + } + + /** + * @var array + */ + private array $classReflectors; + + /** + * Sets the track resources flag. + * + * If you are not using the loaders and therefore don't want + * to depend on the Config component, set this flag to false. + */ + public function setResourceTracking(bool $track): void + { + $this->trackResources = $track; + } + + /** + * Checks if resources are tracked. + */ + public function isTrackingResources(): bool + { + return $this->trackResources; + } + + /** + * Sets the instantiator to be used when fetching proxies. + */ + public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator): void + { + $this->proxyInstantiator = $proxyInstantiator; + } + + public function registerExtension(ExtensionInterface $extension): void + { + $this->extensions[$extension->getAlias()] = $extension; + + if (false !== $extension->getNamespace()) { + $this->extensionsByNs[$extension->getNamespace()] = $extension; + } + } + + /** + * Returns an extension by alias or namespace. + * + * @throws LogicException if the extension is not registered + */ + public function getExtension(string $name): ExtensionInterface + { + if (isset($this->extensions[$name])) { + return $this->extensions[$name]; + } + + if (isset($this->extensionsByNs[$name])) { + return $this->extensionsByNs[$name]; + } + + throw new LogicException(sprintf('Container extension "%s" is not registered.', $name)); + } + + /** + * Returns all registered extensions. + * + * @return array + */ + public function getExtensions(): array + { + return $this->extensions; + } + + /** + * Checks if we have an extension. + */ + public function hasExtension(string $name): bool + { + return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]); + } + + /** + * Returns an array of resources loaded to build this configuration. + * + * @return ResourceInterface[] + */ + public function getResources(): array + { + return array_values($this->resources); + } + + /** + * @return $this + */ + public function addResource(ResourceInterface $resource): static + { + if (!$this->trackResources) { + return $this; + } + + if ($resource instanceof GlobResource && $this->inVendors($resource->getPrefix())) { + return $this; + } + + $this->resources[(string) $resource] = $resource; + + return $this; + } + + /** + * Sets the resources for this configuration. + * + * @param array $resources + * + * @return $this + */ + public function setResources(array $resources): static + { + if (!$this->trackResources) { + return $this; + } + + $this->resources = $resources; + + return $this; + } + + /** + * Adds the object class hierarchy as resources. + * + * @param object|string $object An object instance or class name + * + * @return $this + */ + public function addObjectResource(object|string $object): static + { + if ($this->trackResources) { + if (\is_object($object)) { + $object = $object::class; + } + if (!isset($this->classReflectors[$object])) { + $this->classReflectors[$object] = new \ReflectionClass($object); + } + $class = $this->classReflectors[$object]; + + foreach ($class->getInterfaceNames() as $name) { + if (null === $interface = &$this->classReflectors[$name]) { + $interface = new \ReflectionClass($name); + } + $file = $interface->getFileName(); + if (false !== $file && file_exists($file)) { + $this->fileExists($file); + } + } + do { + $file = $class->getFileName(); + if (false !== $file && file_exists($file)) { + $this->fileExists($file); + } + foreach ($class->getTraitNames() as $name) { + $this->addObjectResource($name); + } + } while ($class = $class->getParentClass()); + } + + return $this; + } + + /** + * Retrieves the requested reflection class and registers it for resource tracking. + * + * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true + * + * @final + */ + public function getReflectionClass(?string $class, bool $throw = true): ?\ReflectionClass + { + if (!$class = $this->getParameterBag()->resolveValue($class)) { + return null; + } + + if (isset(self::INTERNAL_TYPES[$class])) { + return null; + } + + $resource = $classReflector = null; + + try { + if (isset($this->classReflectors[$class])) { + $classReflector = $this->classReflectors[$class]; + } elseif (class_exists(ClassExistenceResource::class)) { + $resource = new ClassExistenceResource($class, false); + $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class); + } else { + $classReflector = class_exists($class) ? new \ReflectionClass($class) : false; + } + } catch (\ReflectionException $e) { + if ($throw) { + throw $e; + } + } + + if ($this->trackResources) { + if (!$classReflector) { + $this->addResource($resource ?? new ClassExistenceResource($class, false)); + } elseif (!$classReflector->isInternal()) { + $path = $classReflector->getFileName(); + + if (!$this->inVendors($path)) { + $this->addResource(new ReflectionClassResource($classReflector, $this->vendors)); + } + } + $this->classReflectors[$class] = $classReflector; + } + + return $classReflector ?: null; + } + + /** + * Checks whether the requested file or directory exists and registers the result for resource tracking. + * + * @param string $path The file or directory path for which to check the existence + * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed, + * it will be used as pattern for tracking contents of the requested directory + * + * @final + */ + public function fileExists(string $path, bool|string $trackContents = true): bool + { + $exists = file_exists($path); + + if (!$this->trackResources || $this->inVendors($path)) { + return $exists; + } + + if (!$exists) { + $this->addResource(new FileExistenceResource($path)); + + return $exists; + } + + if (is_dir($path)) { + if ($trackContents) { + $this->addResource(new DirectoryResource($path, \is_string($trackContents) ? $trackContents : null)); + } else { + $this->addResource(new GlobResource($path, '/*', false)); + } + } elseif ($trackContents) { + $this->addResource(new FileResource($path)); + } + + return $exists; + } + + /** + * Loads the configuration for an extension. + * + * @param string $extension The extension alias or namespace + * @param array|null $values An array of values that customizes the extension + * + * @return $this + * + * @throws BadMethodCallException When this ContainerBuilder is compiled + * @throws \LogicException if the extension is not registered + */ + public function loadFromExtension(string $extension, ?array $values = null): static + { + if ($this->isCompiled()) { + throw new BadMethodCallException('Cannot load from an extension on a compiled container.'); + } + + $namespace = $this->getExtension($extension)->getAlias(); + + $this->extensionConfigs[$namespace][] = $values ?? []; + + return $this; + } + + /** + * Adds a compiler pass. + * + * @param string $type The type of compiler pass + * @param int $priority Used to sort the passes + * + * @return $this + */ + public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): static + { + $this->getCompiler()->addPass($pass, $type, $priority); + + $this->addObjectResource($pass); + + return $this; + } + + /** + * Returns the compiler pass config which can then be modified. + */ + public function getCompilerPassConfig(): PassConfig + { + return $this->getCompiler()->getPassConfig(); + } + + /** + * Returns the compiler. + */ + public function getCompiler(): Compiler + { + return $this->compiler ??= new Compiler(); + } + + /** + * Sets a service. + * + * @throws BadMethodCallException When this ContainerBuilder is compiled + */ + public function set(string $id, ?object $service): void + { + if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { + // setting a synthetic service on a compiled container is alright + throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id)); + } + + unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->removedIds[$id]); + + parent::set($id, $service); + } + + /** + * Removes a service definition. + */ + public function removeDefinition(string $id): void + { + if (isset($this->definitions[$id])) { + unset($this->definitions[$id]); + if ('.' !== ($id[0] ?? '-')) { + $this->removedIds[$id] = true; + } + } + } + + public function has(string $id): bool + { + return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id); + } + + /** + * @throws InvalidArgumentException when no definitions are available + * @throws ServiceCircularReferenceException When a circular reference is detected + * @throws ServiceNotFoundException When the service is not defined + * @throws \Exception + * + * @see Reference + */ + public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): ?object + { + if ($this->isCompiled() && isset($this->removedIds[$id])) { + return ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior ? parent::get($id) : null; + } + + return $this->doGet($id, $invalidBehavior); + } + + private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, ?array &$inlineServices = null, bool $isConstructorArgument = false): mixed + { + if (isset($inlineServices[$id])) { + return $inlineServices[$id]; + } + if (null === $inlineServices) { + $isConstructorArgument = true; + $inlineServices = []; + } + try { + if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { + return $this->privates[$id] ?? parent::get($id, $invalidBehavior); + } + if (null !== $service = $this->privates[$id] ?? parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { + return $service; + } + } catch (ServiceCircularReferenceException $e) { + if ($isConstructorArgument) { + throw $e; + } + } + + if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) { + $alias = $this->aliasDefinitions[$id]; + + if ($alias->isDeprecated()) { + $deprecation = $alias->getDeprecation($id); + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + return $this->doGet((string) $alias, $invalidBehavior, $inlineServices, $isConstructorArgument); + } + + try { + $definition = $this->getDefinition($id); + } catch (ServiceNotFoundException $e) { + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $invalidBehavior) { + return null; + } + + throw $e; + } + + if ($definition->hasErrors() && $e = $definition->getErrors()) { + throw new RuntimeException(reset($e)); + } + + if ($isConstructorArgument) { + $this->loading[$id] = true; + } + + try { + return $this->createService($definition, $inlineServices, $isConstructorArgument, $id); + } finally { + if ($isConstructorArgument) { + unset($this->loading[$id]); + } + } + } + + /** + * Merges a ContainerBuilder with the current ContainerBuilder configuration. + * + * Service definitions overrides the current defined ones. + * + * But for parameters, they are overridden by the current ones. It allows + * the parameters passed to the container constructor to have precedence + * over the loaded ones. + * + * $container = new ContainerBuilder(new ParameterBag(['foo' => 'bar'])); + * $loader = new LoaderXXX($container); + * $loader->load('resource_name'); + * $container->register('foo', 'stdClass'); + * + * In the above example, even if the loaded resource defines a foo + * parameter, the value will still be 'bar' as defined in the ContainerBuilder + * constructor. + * + * @throws BadMethodCallException When this ContainerBuilder is compiled + */ + public function merge(self $container): void + { + if ($this->isCompiled()) { + throw new BadMethodCallException('Cannot merge on a compiled container.'); + } + + foreach ($container->getDefinitions() as $id => $definition) { + if (!$definition->hasTag('container.excluded') || !$this->has($id)) { + $this->setDefinition($id, $definition); + } + } + $this->addAliases($container->getAliases()); + $parameterBag = $this->getParameterBag(); + $otherBag = $container->getParameterBag(); + $parameterBag->add($otherBag->all()); + + if ($parameterBag instanceof ParameterBag && $otherBag instanceof ParameterBag) { + foreach ($otherBag->allDeprecated() as $name => $deprecated) { + $parameterBag->deprecate($name, ...$deprecated); + } + } + + if ($this->trackResources) { + foreach ($container->getResources() as $resource) { + $this->addResource($resource); + } + } + + foreach ($this->extensions as $name => $extension) { + if (!isset($this->extensionConfigs[$name])) { + $this->extensionConfigs[$name] = []; + } + + $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name)); + } + + if ($parameterBag instanceof EnvPlaceholderParameterBag && $otherBag instanceof EnvPlaceholderParameterBag) { + $envPlaceholders = $otherBag->getEnvPlaceholders(); + $parameterBag->mergeEnvPlaceholders($otherBag); + } else { + $envPlaceholders = []; + } + + foreach ($container->envCounters as $env => $count) { + if (!$count && !isset($envPlaceholders[$env])) { + continue; + } + if (!isset($this->envCounters[$env])) { + $this->envCounters[$env] = $count; + } else { + $this->envCounters[$env] += $count; + } + } + + foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) { + if (isset($this->autoconfiguredInstanceof[$interface])) { + throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface)); + } + + $this->autoconfiguredInstanceof[$interface] = $childDefinition; + } + + foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) { + if (isset($this->autoconfiguredAttributes[$attribute])) { + throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute)); + } + + $this->autoconfiguredAttributes[$attribute] = $configurator; + } + } + + /** + * Returns the configuration array for the given extension. + * + * @return array> + */ + public function getExtensionConfig(string $name): array + { + if (!isset($this->extensionConfigs[$name])) { + $this->extensionConfigs[$name] = []; + } + + return $this->extensionConfigs[$name]; + } + + /** + * Prepends a config array to the configs of the given extension. + * + * @param array $config + */ + public function prependExtensionConfig(string $name, array $config): void + { + if (!isset($this->extensionConfigs[$name])) { + $this->extensionConfigs[$name] = []; + } + + array_unshift($this->extensionConfigs[$name], $config); + } + + /** + * Deprecates a service container parameter. + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function deprecateParameter(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): void + { + if (!$this->parameterBag instanceof ParameterBag) { + throw new BadMethodCallException(sprintf('The parameter bag must be an instance of "%s" to call "%s".', ParameterBag::class, __METHOD__)); + } + + $this->parameterBag->deprecate($name, $package, $version, $message); + } + + /** + * Compiles the container. + * + * This method passes the container to compiler + * passes whose job is to manipulate and optimize + * the container. + * + * The main compiler passes roughly do four things: + * + * * The extension configurations are merged; + * * Parameter values are resolved; + * * The parameter bag is frozen; + * * Extension loading is disabled. + * + * @param bool $resolveEnvPlaceholders Whether %env()% parameters should be resolved using the current + * env vars or be replaced by uniquely identifiable placeholders. + * Set to "true" when you want to use the current ContainerBuilder + * directly, keep to "false" when the container is dumped instead. + */ + public function compile(bool $resolveEnvPlaceholders = false): void + { + $compiler = $this->getCompiler(); + + if ($this->trackResources) { + foreach ($compiler->getPassConfig()->getPasses() as $pass) { + $this->addObjectResource($pass); + } + } + $bag = $this->getParameterBag(); + + if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) { + $compiler->addPass(new ResolveEnvPlaceholdersPass(), PassConfig::TYPE_AFTER_REMOVING, -1000); + } + + $compiler->compile($this); + + foreach ($this->definitions as $id => $definition) { + if ($this->trackResources && $definition->isLazy()) { + $this->getReflectionClass($definition->getClass()); + } + } + + $this->extensionConfigs = []; + + if ($bag instanceof EnvPlaceholderParameterBag) { + if ($resolveEnvPlaceholders) { + $this->parameterBag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true)); + } + + $this->envPlaceholders = $bag->getEnvPlaceholders(); + } + + parent::compile(); + + foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) { + if ('.' === ($id[0] ?? '-')) { + continue; + } + if (!$definition->isPublic() || $definition->isPrivate()) { + $this->removedIds[$id] = true; + } + } + } + + public function getServiceIds(): array + { + return array_map('strval', array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds()))); + } + + /** + * Gets removed service or alias ids. + * + * @return array + */ + public function getRemovedIds(): array + { + return $this->removedIds; + } + + /** + * Adds the service aliases. + * + * @param array $aliases + */ + public function addAliases(array $aliases): void + { + foreach ($aliases as $alias => $id) { + $this->setAlias($alias, $id); + } + } + + /** + * Sets the service aliases. + * + * @param array $aliases + */ + public function setAliases(array $aliases): void + { + $this->aliasDefinitions = []; + $this->addAliases($aliases); + } + + /** + * Sets an alias for an existing service. + * + * @throws InvalidArgumentException if the id is not a string or an Alias + * @throws InvalidArgumentException if the alias is for itself + */ + public function setAlias(string $alias, string|Alias $id): Alias + { + if ('' === $alias || '\\' === $alias[-1] || \strlen($alias) !== strcspn($alias, "\0\r\n'")) { + throw new InvalidArgumentException(sprintf('Invalid alias id: "%s".', $alias)); + } + + if (\is_string($id)) { + $id = new Alias($id); + } + + if ($alias === (string) $id) { + throw new InvalidArgumentException(sprintf('An alias cannot reference itself, got a circular reference on "%s".', $alias)); + } + + unset($this->definitions[$alias], $this->removedIds[$alias]); + + return $this->aliasDefinitions[$alias] = $id; + } + + public function removeAlias(string $alias): void + { + if (isset($this->aliasDefinitions[$alias])) { + unset($this->aliasDefinitions[$alias]); + if ('.' !== ($alias[0] ?? '-')) { + $this->removedIds[$alias] = true; + } + } + } + + public function hasAlias(string $id): bool + { + return isset($this->aliasDefinitions[$id]); + } + + /** + * @return array + */ + public function getAliases(): array + { + return $this->aliasDefinitions; + } + + /** + * @throws InvalidArgumentException if the alias does not exist + */ + public function getAlias(string $id): Alias + { + if (!isset($this->aliasDefinitions[$id])) { + throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id)); + } + + return $this->aliasDefinitions[$id]; + } + + /** + * Registers a service definition. + * + * This method allows for simple registration of service definition + * with a fluid interface. + */ + public function register(string $id, ?string $class = null): Definition + { + return $this->setDefinition($id, new Definition($class)); + } + + /** + * Registers an autowired service definition. + * + * This method implements a shortcut for using setDefinition() with + * an autowired definition. + */ + public function autowire(string $id, ?string $class = null): Definition + { + return $this->setDefinition($id, (new Definition($class))->setAutowired(true)); + } + + /** + * Adds the service definitions. + * + * @param array $definitions + */ + public function addDefinitions(array $definitions): void + { + foreach ($definitions as $id => $definition) { + $this->setDefinition($id, $definition); + } + } + + /** + * Sets the service definitions. + * + * @param array $definitions + */ + public function setDefinitions(array $definitions): void + { + $this->definitions = []; + $this->addDefinitions($definitions); + } + + /** + * Gets all service definitions. + * + * @return array + */ + public function getDefinitions(): array + { + return $this->definitions; + } + + /** + * Sets a service definition. + * + * @throws BadMethodCallException When this ContainerBuilder is compiled + */ + public function setDefinition(string $id, Definition $definition): Definition + { + if ($this->isCompiled()) { + throw new BadMethodCallException('Adding definition to a compiled container is not allowed.'); + } + + if ('' === $id || '\\' === $id[-1] || \strlen($id) !== strcspn($id, "\0\r\n'")) { + throw new InvalidArgumentException(sprintf('Invalid service id: "%s".', $id)); + } + + unset($this->aliasDefinitions[$id], $this->removedIds[$id]); + + return $this->definitions[$id] = $definition; + } + + /** + * Returns true if a service definition exists under the given identifier. + */ + public function hasDefinition(string $id): bool + { + return isset($this->definitions[$id]); + } + + /** + * Gets a service definition. + * + * @throws ServiceNotFoundException if the service definition does not exist + */ + public function getDefinition(string $id): Definition + { + if (!isset($this->definitions[$id])) { + throw new ServiceNotFoundException($id); + } + + return $this->definitions[$id]; + } + + /** + * Gets a service definition by id or alias. + * + * The method "unaliases" recursively to return a Definition instance. + * + * @throws ServiceNotFoundException if the service definition does not exist + */ + public function findDefinition(string $id): Definition + { + $seen = []; + while (isset($this->aliasDefinitions[$id])) { + $id = (string) $this->aliasDefinitions[$id]; + + if (isset($seen[$id])) { + $seen = array_values($seen); + $seen = \array_slice($seen, array_search($id, $seen)); + $seen[] = $id; + + throw new ServiceCircularReferenceException($id, $seen); + } + + $seen[$id] = $id; + } + + return $this->getDefinition($id); + } + + /** + * Creates a service for a service definition. + * + * @throws RuntimeException When the factory definition is incomplete + * @throws RuntimeException When the service is a synthetic service + * @throws InvalidArgumentException When configure callable is not callable + */ + private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, ?string $id = null, bool|object $tryProxy = true): mixed + { + if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) { + return $inlineServices[$h]; + } + + if ($definition instanceof ChildDefinition) { + throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id)); + } + + if ($definition->isSynthetic()) { + throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); + } + + if ($definition->isDeprecated()) { + $deprecation = $definition->getDeprecation($id); + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + $parameterBag = $this->getParameterBag(); + $class = $parameterBag->resolveValue($definition->getClass()) ?: (['Closure', 'fromCallable'] === $definition->getFactory() ? 'Closure' : null); + + if (['Closure', 'fromCallable'] === $definition->getFactory() && ('Closure' !== $class || $definition->isLazy())) { + $callable = $parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArgument(0))); + + if ($callable instanceof Reference || $callable instanceof Definition) { + $callable = [$callable, '__invoke']; + } + + if (\is_array($callable) && ( + $callable[0] instanceof Reference + || $callable[0] instanceof Definition && !isset($inlineServices[spl_object_hash($callable[0])]) + )) { + $initializer = function () use ($callable, &$inlineServices) { + return $this->doResolveServices($callable[0], $inlineServices); + }; + + $proxy = eval('return '.LazyClosure::getCode('$initializer', $callable, $definition, $this, $id).';'); + $this->shareService($definition, $proxy, $id, $inlineServices); + + return $proxy; + } + } + + if (true === $tryProxy && $definition->isLazy() && ['Closure', 'fromCallable'] !== $definition->getFactory() + && !$tryProxy = !($proxy = $this->proxyInstantiator ??= new LazyServiceInstantiator()) || $proxy instanceof RealServiceInstantiator + ) { + $proxy = $proxy->instantiateProxy( + $this, + (clone $definition) + ->setClass($class) + ->setTags(($definition->hasTag('proxy') ? ['proxy' => $parameterBag->resolveValue($definition->getTag('proxy'))] : []) + $definition->getTags()), + $id, function ($proxy = false) use ($definition, &$inlineServices, $id) { + return $this->createService($definition, $inlineServices, true, $id, $proxy); + } + ); + $this->shareService($definition, $proxy, $id, $inlineServices); + + return $proxy; + } + + if (null !== $definition->getFile()) { + require_once $parameterBag->resolveValue($definition->getFile()); + } + + $arguments = $definition->getArguments(); + + if (null !== $factory = $definition->getFactory()) { + if (\is_array($factory)) { + $factory = [$this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices, $isConstructorArgument), $factory[1]]; + } elseif (!\is_string($factory)) { + throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory.', $id)); + } elseif (str_starts_with($factory, '@=')) { + $factory = fn (ServiceLocator $arguments) => $this->getExpressionLanguage()->evaluate(substr($factory, 2), ['container' => $this, 'args' => $arguments]); + $arguments = [new ServiceLocatorArgument($arguments)]; + } + } + + $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($arguments)), $inlineServices, $isConstructorArgument); + + if (null !== $id && $definition->isShared() && (isset($this->services[$id]) || isset($this->privates[$id])) && (true === $tryProxy || !$definition->isLazy())) { + return $this->services[$id] ?? $this->privates[$id]; + } + + if (!array_is_list($arguments)) { + $arguments = array_combine(array_map(fn ($k) => preg_replace('/^.*\\$/', '', $k), array_keys($arguments)), $arguments); + } + + if (null !== $factory) { + $service = $factory(...$arguments); + + if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) { + $r = new \ReflectionClass($factory[0]); + + if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name); + } + } + } else { + $r = new \ReflectionClass($class); + + if (\is_object($tryProxy)) { + if ($r->getConstructor()) { + $tryProxy->__construct(...$arguments); + } + + $service = $tryProxy; + } else { + $service = $r->getConstructor() ? $r->newInstanceArgs($arguments) : $r->newInstance(); + } + + if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name); + } + } + + $lastWitherIndex = null; + foreach ($definition->getMethodCalls() as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + + if (null === $lastWitherIndex && (true === $tryProxy || !$definition->isLazy())) { + // share only if proxying failed, or if not a proxy, and if no withers are found + $this->shareService($definition, $service, $id, $inlineServices); + } + + $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices); + foreach ($properties as $name => $value) { + $service->$name = $value; + } + + foreach ($definition->getMethodCalls() as $k => $call) { + $service = $this->callMethod($service, $call, $inlineServices); + + if ($lastWitherIndex === $k && (true === $tryProxy || !$definition->isLazy())) { + // share only if proxying failed, or if not a proxy, and this is the last wither + $this->shareService($definition, $service, $id, $inlineServices); + } + } + + if ($callable = $definition->getConfigurator()) { + if (\is_array($callable)) { + $callable[0] = $parameterBag->resolveValue($callable[0]); + + if ($callable[0] instanceof Reference) { + $callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices); + } elseif ($callable[0] instanceof Definition) { + $callable[0] = $this->createService($callable[0], $inlineServices); + } + } + + if (!\is_callable($callable)) { + throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_debug_type($service))); + } + + $callable($service); + } + + return $service; + } + + /** + * Replaces service references by the real service instance and evaluates expressions. + * + * @return mixed The same value with all service references replaced by + * the real service instances and all expressions evaluated + */ + public function resolveServices(mixed $value): mixed + { + return $this->doResolveServices($value); + } + + private function doResolveServices(mixed $value, array &$inlineServices = [], bool $isConstructorArgument = false): mixed + { + if (\is_array($value)) { + foreach ($value as $k => $v) { + $value[$k] = $this->doResolveServices($v, $inlineServices, $isConstructorArgument); + } + } elseif ($value instanceof ServiceClosureArgument) { + $reference = $value->getValues()[0]; + $value = fn () => $this->resolveServices($reference); + } elseif ($value instanceof IteratorArgument) { + $value = new RewindableGenerator(function () use ($value, &$inlineServices) { + foreach ($value->getValues() as $k => $v) { + foreach (self::getServiceConditionals($v) as $s) { + if (!$this->has($s)) { + continue 2; + } + } + foreach (self::getInitializedConditionals($v) as $s) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { + continue 2; + } + } + + yield $k => $this->doResolveServices($v, $inlineServices); + } + }, function () use ($value): int { + $count = 0; + foreach ($value->getValues() as $v) { + foreach (self::getServiceConditionals($v) as $s) { + if (!$this->has($s)) { + continue 2; + } + } + foreach (self::getInitializedConditionals($v) as $s) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + continue 2; + } + } + + ++$count; + } + + return $count; + }); + } elseif ($value instanceof ServiceLocatorArgument) { + $refs = $types = []; + foreach ($value->getValues() as $k => $v) { + $refs[$k] = [$v, null]; + $types[$k] = $v instanceof TypedReference ? $v->getType() : '?'; + } + $value = new ServiceLocator($this->resolveServices(...), $refs, $types); + } elseif ($value instanceof Reference) { + $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument); + } elseif ($value instanceof Definition) { + $value = $this->createService($value, $inlineServices, $isConstructorArgument); + } elseif ($value instanceof Parameter) { + $value = $this->getParameter((string) $value); + } elseif ($value instanceof Expression) { + $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this]); + } elseif ($value instanceof AbstractArgument) { + throw new RuntimeException($value->getTextWithContext()); + } + + return $value; + } + + /** + * Returns service ids for a given tag. + * + * Example: + * + * $container->register('foo')->addTag('my.tag', ['hello' => 'world']); + * + * $serviceIds = $container->findTaggedServiceIds('my.tag'); + * foreach ($serviceIds as $serviceId => $tags) { + * foreach ($tags as $tag) { + * echo $tag['hello']; + * } + * } + * + * @return array An array of tags with the tagged service as key, holding a list of attribute arrays + */ + public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false): array + { + $this->usedTags[] = $name; + $tags = []; + foreach ($this->getDefinitions() as $id => $definition) { + if ($definition->hasTag($name) && !$definition->hasTag('container.excluded')) { + if ($throwOnAbstract && $definition->isAbstract()) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name)); + } + $tags[$id] = $definition->getTag($name); + } + } + + return $tags; + } + + /** + * Returns all tags the defined services use. + * + * @return string[] + */ + public function findTags(): array + { + $tags = []; + foreach ($this->getDefinitions() as $id => $definition) { + $tags[] = array_keys($definition->getTags()); + } + + return array_unique(array_merge([], ...$tags)); + } + + /** + * Returns all tags not queried by findTaggedServiceIds. + * + * @return string[] + */ + public function findUnusedTags(): array + { + return array_values(array_diff($this->findTags(), $this->usedTags)); + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider): void + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * @return ExpressionFunctionProviderInterface[] + */ + public function getExpressionLanguageProviders(): array + { + return $this->expressionLanguageProviders; + } + + /** + * Returns a ChildDefinition that will be used for autoconfiguring the interface/class. + */ + public function registerForAutoconfiguration(string $interface): ChildDefinition + { + if (!isset($this->autoconfiguredInstanceof[$interface])) { + $this->autoconfiguredInstanceof[$interface] = new ChildDefinition(''); + } + + return $this->autoconfiguredInstanceof[$interface]; + } + + /** + * Registers an attribute that will be used for autoconfiguring annotated classes. + * + * The third argument passed to the callable is the reflector of the + * class/method/property/parameter that the attribute targets. Using one or many of + * \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter as a type-hint + * for this argument allows filtering which attributes should be passed to the callable. + * + * @template T + * + * @param class-string $attributeClass + * @param callable(ChildDefinition, T, \Reflector): void $configurator + */ + public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator): void + { + $this->autoconfiguredAttributes[$attributeClass] = $configurator; + } + + /** + * Registers an autowiring alias that only binds to a specific argument name. + * + * The argument name is derived from $name if provided (from $id otherwise) + * using camel case: "foo.bar" or "foo_bar" creates an alias bound to + * "$fooBar"-named arguments with $type as type-hint. Such arguments will + * receive the service $id when autowiring is used. + */ + public function registerAliasForArgument(string $id, string $type, ?string $name = null): Alias + { + $parsedName = (new Target($name ??= $id))->getParsedName(); + + if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $parsedName)) { + if ($id !== $name) { + $id = sprintf(' for service "%s"', $id); + } + + throw new InvalidArgumentException(sprintf('Invalid argument name "%s"'.$id.': the first character must be a letter.', $name)); + } + + if ($parsedName !== $name) { + $this->setAlias('.'.$type.' $'.$name, $type.' $'.$parsedName); + } + + return $this->setAlias($type.' $'.$parsedName, $id); + } + + /** + * Returns an array of ChildDefinition[] keyed by interface. + * + * @return array + */ + public function getAutoconfiguredInstanceof(): array + { + return $this->autoconfiguredInstanceof; + } + + /** + * @return array + */ + public function getAutoconfiguredAttributes(): array + { + return $this->autoconfiguredAttributes; + } + + /** + * Resolves env parameter placeholders in a string or an array. + * + * @param string|true|null $format A sprintf() format returning the replacement for each env var name or + * null to resolve back to the original "%env(VAR)%" format or + * true to resolve to the actual values of the referenced env vars + * @param array &$usedEnvs Env vars found while resolving are added to this array + * + * @return mixed The value with env parameters resolved if a string or an array is passed + */ + public function resolveEnvPlaceholders(mixed $value, string|bool|null $format = null, ?array &$usedEnvs = null): mixed + { + $bag = $this->getParameterBag(); + if (true === $format ??= '%%env(%s)%%') { + $value = $bag->resolveValue($value); + } + + if ($value instanceof Definition) { + $value = (array) $value; + } + + if (\is_array($value)) { + $result = []; + foreach ($value as $k => $v) { + $result[\is_string($k) ? $this->resolveEnvPlaceholders($k, $format, $usedEnvs) : $k] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs); + } + + return $result; + } + + if (!\is_string($value) || 38 > \strlen($value) || false === stripos($value, 'env_')) { + return $value; + } + $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; + + $completed = false; + preg_match_all('/env_[a-f0-9]{16}_\w+_[a-f0-9]{32}/Ui', $value, $matches); + $usedPlaceholders = array_flip($matches[0]); + foreach ($envPlaceholders as $env => $placeholders) { + foreach ($placeholders as $placeholder) { + if (isset($usedPlaceholders[$placeholder])) { + if (true === $format) { + $resolved = $bag->escapeValue($this->getEnv($env)); + } else { + $resolved = sprintf($format, $env); + } + if ($placeholder === $value) { + $value = $resolved; + $completed = true; + } else { + if (!\is_string($resolved) && !is_numeric($resolved)) { + throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type "%s" inside string value "%s".', $env, get_debug_type($resolved), $this->resolveEnvPlaceholders($value))); + } + $value = str_ireplace($placeholder, $resolved, $value); + } + $usedEnvs[$env] = $env; + $this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1; + + if ($completed) { + break 2; + } + } + } + } + + return $value; + } + + /** + * Get statistics about env usage. + * + * @return int[] The number of time each env vars has been resolved + */ + public function getEnvCounters(): array + { + $bag = $this->getParameterBag(); + $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; + + foreach ($envPlaceholders as $env => $placeholders) { + if (!isset($this->envCounters[$env])) { + $this->envCounters[$env] = 0; + } + } + + return $this->envCounters; + } + + /** + * @final + */ + public function log(CompilerPassInterface $pass, string $message): void + { + $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message)); + } + + /** + * Checks whether a class is available and will remain available in the "no-dev" mode of Composer. + * + * When parent packages are provided and if any of them is in dev-only mode, + * the class will be considered available even if it is also in dev-only mode. + * + * @throws \LogicException If dependencies have been installed with Composer 1 + */ + final public static function willBeAvailable(string $package, string $class, array $parentPackages): bool + { + if (!class_exists(InstalledVersions::class)) { + throw new \LogicException(sprintf('Calling "%s" when dependencies have been installed with Composer 1 is not supported. Consider upgrading to Composer 2.', __METHOD__)); + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + return false; + } + + if (!InstalledVersions::isInstalled($package) || InstalledVersions::isInstalled($package, false)) { + return true; + } + + // the package is installed but in dev-mode only, check if this applies to one of the parent packages too + + $rootPackage = InstalledVersions::getRootPackage()['name'] ?? ''; + + if ('symfony/symfony' === $rootPackage) { + return true; + } + + foreach ($parentPackages as $parentPackage) { + if ($rootPackage === $parentPackage || (InstalledVersions::isInstalled($parentPackage) && !InstalledVersions::isInstalled($parentPackage, false))) { + return true; + } + } + + return false; + } + + /** + * Gets removed binding ids. + * + * @return array + * + * @internal + */ + public function getRemovedBindingIds(): array + { + return $this->removedBindingIds; + } + + /** + * Removes bindings for a service. + * + * @internal + */ + public function removeBindings(string $id): void + { + if ($this->hasDefinition($id)) { + foreach ($this->getDefinition($id)->getBindings() as $key => $binding) { + [, $bindingId] = $binding->getValues(); + $this->removedBindingIds[(int) $bindingId] = true; + } + } + } + + /** + * @return string[] + * + * @internal + */ + public static function getServiceConditionals(mixed $value): array + { + $services = []; + + if (\is_array($value)) { + foreach ($value as $v) { + $services = array_unique(array_merge($services, self::getServiceConditionals($v))); + } + } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $services[] = (string) $value; + } + + return $services; + } + + /** + * @return string[] + * + * @internal + */ + public static function getInitializedConditionals(mixed $value): array + { + $services = []; + + if (\is_array($value)) { + foreach ($value as $v) { + $services = array_unique(array_merge($services, self::getInitializedConditionals($v))); + } + } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()) { + $services[] = (string) $value; + } + + return $services; + } + + /** + * Computes a reasonably unique hash of a serializable value. + */ + public static function hash(mixed $value): string + { + $hash = substr(base64_encode(hash('xxh128', serialize($value), true)), 0, 7); + + return str_replace(['/', '+'], ['.', '_'], $hash); + } + + protected function getEnv(string $name): mixed + { + $value = parent::getEnv($name); + $bag = $this->getParameterBag(); + + if (!\is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) { + return $value; + } + + $envPlaceholders = $bag->getEnvPlaceholders(); + if (isset($envPlaceholders[$name][$value])) { + $bag = new ParameterBag($bag->all()); + + return $bag->unescapeValue($bag->get("env($name)")); + } + foreach ($envPlaceholders as $env => $placeholders) { + if (isset($placeholders[$value])) { + return $this->getEnv($env); + } + } + + $this->resolving["env($name)"] = true; + try { + return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), true)); + } finally { + unset($this->resolving["env($name)"]); + } + } + + private function callMethod(object $service, array $call, array &$inlineServices): mixed + { + foreach (self::getServiceConditionals($call[1]) as $s) { + if (!$this->has($s)) { + return $service; + } + } + foreach (self::getInitializedConditionals($call[1]) as $s) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { + return $service; + } + } + + $result = $service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices)); + + return empty($call[2]) ? $service : $result; + } + + private function shareService(Definition $definition, mixed $service, ?string $id, array &$inlineServices): void + { + $inlineServices[$id ?? spl_object_hash($definition)] = $service; + + if (null !== $id && $definition->isShared()) { + if ($definition->isPrivate() && $this->isCompiled()) { + $this->privates[$id] = $service; + } else { + $this->services[$id] = $service; + } + unset($this->loading[$id]); + } + } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (!isset($this->expressionLanguage)) { + if (!class_exists(Expression::class)) { + throw new LogicException('Expressions cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders, null, $this->getEnv(...)); + } + + return $this->expressionLanguage; + } + + private function inVendors(string $path): bool + { + $path = is_file($path) ? \dirname($path) : $path; + + if (isset($this->pathsInVendor[$path])) { + return $this->pathsInVendor[$path]; + } + + $this->vendors ??= (new ComposerResource())->getVendors(); + $path = realpath($path) ?: $path; + + if (isset($this->pathsInVendor[$path])) { + return $this->pathsInVendor[$path]; + } + + foreach ($this->vendors as $vendor) { + if (str_starts_with($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + $this->addResource(new FileResource($vendor.'/composer/installed.json')); + + return $this->pathsInVendor[$path] = true; + } + } + + return $this->pathsInVendor[$path] = false; + } +} diff --git a/vendor/symfony/dependency-injection/ContainerInterface.php b/vendor/symfony/dependency-injection/ContainerInterface.php new file mode 100644 index 0000000..39fd080 --- /dev/null +++ b/vendor/symfony/dependency-injection/ContainerInterface.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Psr\Container\ContainerInterface as PsrContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +/** + * ContainerInterface is the interface implemented by service container classes. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +interface ContainerInterface extends PsrContainerInterface +{ + public const RUNTIME_EXCEPTION_ON_INVALID_REFERENCE = 0; + public const EXCEPTION_ON_INVALID_REFERENCE = 1; + public const NULL_ON_INVALID_REFERENCE = 2; + public const IGNORE_ON_INVALID_REFERENCE = 3; + public const IGNORE_ON_UNINITIALIZED_REFERENCE = 4; + + public function set(string $id, ?object $service): void; + + /** + * @template B of self::*_REFERENCE + * + * @param B $invalidBehavior + * + * @psalm-return (B is self::EXCEPTION_ON_INVALID_REFERENCE|self::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE ? object : object|null) + * + * @throws ServiceCircularReferenceException When a circular reference is detected + * @throws ServiceNotFoundException When the service is not defined + * + * @see Reference + */ + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object; + + public function has(string $id): bool; + + /** + * Check for whether or not a service has been initialized. + */ + public function initialized(string $id): bool; + + /** + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null; + + public function hasParameter(string $name): bool; + + public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value): void; +} diff --git a/vendor/symfony/dependency-injection/Definition.php b/vendor/symfony/dependency-injection/Definition.php new file mode 100644 index 0000000..c80ee07 --- /dev/null +++ b/vendor/symfony/dependency-injection/Definition.php @@ -0,0 +1,811 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; + +/** + * Definition represents a service definition. + * + * @author Fabien Potencier + */ +class Definition +{ + private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%service_id%" service is deprecated. You should stop using it, as it will be removed in the future.'; + + private ?string $class = null; + private ?string $file = null; + private string|array|null $factory = null; + private bool $shared = true; + private array $deprecation = []; + private array $properties = []; + private array $calls = []; + private array $instanceof = []; + private bool $autoconfigured = false; + private string|array|null $configurator = null; + private array $tags = []; + private bool $public = false; + private bool $synthetic = false; + private bool $abstract = false; + private bool $lazy = false; + private ?array $decoratedService = null; + private bool $autowired = false; + private array $changes = []; + private array $bindings = []; + private array $errors = []; + + protected array $arguments = []; + + /** + * @internal + * + * Used to store the name of the inner id when using service decoration together with autowiring + */ + public ?string $innerServiceId = null; + + /** + * @internal + * + * Used to store the behavior to follow when using service decoration and the decorated service is invalid + */ + public ?int $decorationOnInvalid = null; + + public function __construct(?string $class = null, array $arguments = []) + { + if (null !== $class) { + $this->setClass($class); + } + $this->arguments = $arguments; + } + + /** + * Returns all changes tracked for the Definition object. + */ + public function getChanges(): array + { + return $this->changes; + } + + /** + * Sets the tracked changes for the Definition object. + * + * @param array $changes An array of changes for this Definition + * + * @return $this + */ + public function setChanges(array $changes): static + { + $this->changes = $changes; + + return $this; + } + + /** + * Sets a factory. + * + * @param string|array|Reference|null $factory A PHP function, reference or an array containing a class/Reference and a method to call + * + * @return $this + */ + public function setFactory(string|array|Reference|null $factory): static + { + $this->changes['factory'] = true; + + if (\is_string($factory) && str_contains($factory, '::')) { + $factory = explode('::', $factory, 2); + } elseif ($factory instanceof Reference) { + $factory = [$factory, '__invoke']; + } + + $this->factory = $factory; + + return $this; + } + + /** + * Gets the factory. + * + * @return string|array|null The PHP function or an array containing a class/Reference and a method to call + */ + public function getFactory(): string|array|null + { + return $this->factory; + } + + /** + * Sets the service that this service is decorating. + * + * @param string|null $id The decorated service id, use null to remove decoration + * @param string|null $renamedId The new decorated service id + * + * @return $this + * + * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals + */ + public function setDecoratedService(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static + { + if ($renamedId && $id === $renamedId) { + throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); + } + + $this->changes['decorated_service'] = true; + + if (null === $id) { + $this->decoratedService = null; + } else { + $this->decoratedService = [$id, $renamedId, $priority]; + + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { + $this->decoratedService[] = $invalidBehavior; + } + } + + return $this; + } + + /** + * Gets the service that this service is decorating. + * + * @return array|null An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated + */ + public function getDecoratedService(): ?array + { + return $this->decoratedService; + } + + /** + * Sets the service class. + * + * @return $this + */ + public function setClass(?string $class): static + { + $this->changes['class'] = true; + + $this->class = $class; + + return $this; + } + + /** + * Gets the service class. + * + * @return class-string|null + */ + public function getClass(): ?string + { + return $this->class; + } + + /** + * Sets the arguments to pass to the service constructor/factory method. + * + * @return $this + */ + public function setArguments(array $arguments): static + { + $this->arguments = $arguments; + + return $this; + } + + /** + * Sets the properties to define when creating the service. + * + * @return $this + */ + public function setProperties(array $properties): static + { + $this->properties = $properties; + + return $this; + } + + /** + * Gets the properties to define when creating the service. + */ + public function getProperties(): array + { + return $this->properties; + } + + /** + * Sets a specific property. + * + * @return $this + */ + public function setProperty(string $name, mixed $value): static + { + $this->properties[$name] = $value; + + return $this; + } + + /** + * Adds an argument to pass to the service constructor/factory method. + * + * @return $this + */ + public function addArgument(mixed $argument): static + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * Replaces a specific argument. + * + * @return $this + * + * @throws OutOfBoundsException When the replaced argument does not exist + */ + public function replaceArgument(int|string $index, mixed $argument): static + { + if (0 === \count($this->arguments)) { + throw new OutOfBoundsException(sprintf('Cannot replace arguments for class "%s" if none have been configured yet.', $this->class)); + } + + if (!\array_key_exists($index, $this->arguments)) { + throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); + } + + $this->arguments[$index] = $argument; + + return $this; + } + + /** + * Sets a specific argument. + * + * @return $this + */ + public function setArgument(int|string $key, mixed $value): static + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Gets the arguments to pass to the service constructor/factory method. + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * Gets an argument to pass to the service constructor/factory method. + * + * @throws OutOfBoundsException When the argument does not exist + */ + public function getArgument(int|string $index): mixed + { + if (!\array_key_exists($index, $this->arguments)) { + throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); + } + + return $this->arguments[$index]; + } + + /** + * Sets the methods to call after service initialization. + * + * @return $this + */ + public function setMethodCalls(array $calls = []): static + { + $this->calls = []; + foreach ($calls as $call) { + $this->addMethodCall($call[0], $call[1], $call[2] ?? false); + } + + return $this; + } + + /** + * Adds a method to call after service initialization. + * + * @param string $method The method name to call + * @param array $arguments An array of arguments to pass to the method call + * @param bool $returnsClone Whether the call returns the service instance or not + * + * @return $this + * + * @throws InvalidArgumentException on empty $method param + */ + public function addMethodCall(string $method, array $arguments = [], bool $returnsClone = false): static + { + if (!$method) { + throw new InvalidArgumentException('Method name cannot be empty.'); + } + $this->calls[] = $returnsClone ? [$method, $arguments, true] : [$method, $arguments]; + + return $this; + } + + /** + * Removes a method to call after service initialization. + * + * @return $this + */ + public function removeMethodCall(string $method): static + { + foreach ($this->calls as $i => $call) { + if ($call[0] === $method) { + unset($this->calls[$i]); + } + } + + return $this; + } + + /** + * Check if the current definition has a given method to call after service initialization. + */ + public function hasMethodCall(string $method): bool + { + foreach ($this->calls as $call) { + if ($call[0] === $method) { + return true; + } + } + + return false; + } + + /** + * Gets the methods to call after service initialization. + */ + public function getMethodCalls(): array + { + return $this->calls; + } + + /** + * Sets the definition templates to conditionally apply on the current definition, keyed by parent interface/class. + * + * @param ChildDefinition[] $instanceof + * + * @return $this + */ + public function setInstanceofConditionals(array $instanceof): static + { + $this->instanceof = $instanceof; + + return $this; + } + + /** + * Gets the definition templates to conditionally apply on the current definition, keyed by parent interface/class. + * + * @return ChildDefinition[] + */ + public function getInstanceofConditionals(): array + { + return $this->instanceof; + } + + /** + * Sets whether or not instanceof conditionals should be prepended with a global set. + * + * @return $this + */ + public function setAutoconfigured(bool $autoconfigured): static + { + $this->changes['autoconfigured'] = true; + + $this->autoconfigured = $autoconfigured; + + return $this; + } + + public function isAutoconfigured(): bool + { + return $this->autoconfigured; + } + + /** + * Sets tags for this definition. + * + * @return $this + */ + public function setTags(array $tags): static + { + $this->tags = $tags; + + return $this; + } + + /** + * Returns all tags. + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Gets a tag by name. + */ + public function getTag(string $name): array + { + return $this->tags[$name] ?? []; + } + + /** + * Adds a tag for this definition. + * + * @return $this + */ + public function addTag(string $name, array $attributes = []): static + { + $this->tags[$name][] = $attributes; + + return $this; + } + + /** + * Whether this definition has a tag with the given name. + */ + public function hasTag(string $name): bool + { + return isset($this->tags[$name]); + } + + /** + * Clears all tags for a given name. + * + * @return $this + */ + public function clearTag(string $name): static + { + unset($this->tags[$name]); + + return $this; + } + + /** + * Clears the tags for this definition. + * + * @return $this + */ + public function clearTags(): static + { + $this->tags = []; + + return $this; + } + + /** + * Sets a file to require before creating the service. + * + * @return $this + */ + public function setFile(?string $file): static + { + $this->changes['file'] = true; + + $this->file = $file; + + return $this; + } + + /** + * Gets the file to require before creating the service. + */ + public function getFile(): ?string + { + return $this->file; + } + + /** + * Sets if the service must be shared or not. + * + * @return $this + */ + public function setShared(bool $shared): static + { + $this->changes['shared'] = true; + + $this->shared = $shared; + + return $this; + } + + /** + * Whether this service is shared. + */ + public function isShared(): bool + { + return $this->shared; + } + + /** + * Sets the visibility of this service. + * + * @return $this + */ + public function setPublic(bool $boolean): static + { + $this->changes['public'] = true; + + $this->public = $boolean; + + return $this; + } + + /** + * Whether this service is public facing. + */ + public function isPublic(): bool + { + return $this->public; + } + + /** + * Whether this service is private. + */ + public function isPrivate(): bool + { + return !$this->public; + } + + /** + * Sets the lazy flag of this service. + * + * @return $this + */ + public function setLazy(bool $lazy): static + { + $this->changes['lazy'] = true; + + $this->lazy = $lazy; + + return $this; + } + + /** + * Whether this service is lazy. + */ + public function isLazy(): bool + { + return $this->lazy; + } + + /** + * Sets whether this definition is synthetic, that is not constructed by the + * container, but dynamically injected. + * + * @return $this + */ + public function setSynthetic(bool $boolean): static + { + $this->synthetic = $boolean; + + if (!isset($this->changes['public'])) { + $this->setPublic(true); + } + + return $this; + } + + /** + * Whether this definition is synthetic, that is not constructed by the + * container, but dynamically injected. + */ + public function isSynthetic(): bool + { + return $this->synthetic; + } + + /** + * Whether this definition is abstract, that means it merely serves as a + * template for other definitions. + * + * @return $this + */ + public function setAbstract(bool $boolean): static + { + $this->abstract = $boolean; + + return $this; + } + + /** + * Whether this definition is abstract, that means it merely serves as a + * template for other definitions. + */ + public function isAbstract(): bool + { + return $this->abstract; + } + + /** + * Whether this definition is deprecated, that means it should not be called + * anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function setDeprecated(string $package, string $version, string $message): static + { + if ('' !== $message) { + if (preg_match('#[\r\n]|\*/#', $message)) { + throw new InvalidArgumentException('Invalid characters found in deprecation template.'); + } + + if (!str_contains($message, '%service_id%')) { + throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.'); + } + } + + $this->changes['deprecated'] = true; + $this->deprecation = ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE]; + + return $this; + } + + /** + * Whether this definition is deprecated, that means it should not be called + * anymore. + */ + public function isDeprecated(): bool + { + return (bool) $this->deprecation; + } + + /** + * @param string $id Service id relying on this definition + */ + public function getDeprecation(string $id): array + { + return [ + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => str_replace('%service_id%', $id, $this->deprecation['message']), + ]; + } + + /** + * Sets a configurator to call after the service is fully initialized. + * + * @param string|array|Reference|null $configurator A PHP function, reference or an array containing a class/Reference and a method to call + * + * @return $this + */ + public function setConfigurator(string|array|Reference|null $configurator): static + { + $this->changes['configurator'] = true; + + if (\is_string($configurator) && str_contains($configurator, '::')) { + $configurator = explode('::', $configurator, 2); + } elseif ($configurator instanceof Reference) { + $configurator = [$configurator, '__invoke']; + } + + $this->configurator = $configurator; + + return $this; + } + + /** + * Gets the configurator to call after the service is fully initialized. + */ + public function getConfigurator(): string|array|null + { + return $this->configurator; + } + + /** + * Is the definition autowired? + */ + public function isAutowired(): bool + { + return $this->autowired; + } + + /** + * Enables/disables autowiring. + * + * @return $this + */ + public function setAutowired(bool $autowired): static + { + $this->changes['autowired'] = true; + + $this->autowired = $autowired; + + return $this; + } + + /** + * Gets bindings. + * + * @return BoundArgument[] + */ + public function getBindings(): array + { + return $this->bindings; + } + + /** + * Sets bindings. + * + * Bindings map $named or FQCN arguments to values that should be + * injected in the matching parameters (of the constructor, of methods + * called and of controller actions). + * + * @return $this + */ + public function setBindings(array $bindings): static + { + foreach ($bindings as $key => $binding) { + if (0 < strpos($key, '$') && $key !== $k = preg_replace('/[ \t]*\$/', ' $', $key)) { + unset($bindings[$key]); + $bindings[$key = $k] = $binding; + } + if (!$binding instanceof BoundArgument) { + $bindings[$key] = new BoundArgument($binding); + } + } + + $this->bindings = $bindings; + + return $this; + } + + /** + * Add an error that occurred when building this Definition. + * + * @return $this + */ + public function addError(string|\Closure|self $error): static + { + if ($error instanceof self) { + $this->errors = array_merge($this->errors, $error->errors); + } else { + $this->errors[] = $error; + } + + return $this; + } + + /** + * Returns any errors that occurred while building this Definition. + */ + public function getErrors(): array + { + foreach ($this->errors as $i => $error) { + if ($error instanceof \Closure) { + $this->errors[$i] = (string) $error(); + } elseif (!\is_string($error)) { + $this->errors[$i] = (string) $error; + } + } + + return $this->errors; + } + + public function hasErrors(): bool + { + return (bool) $this->errors; + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/Dumper.php b/vendor/symfony/dependency-injection/Dumper/Dumper.php new file mode 100644 index 0000000..6b9068c --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/Dumper.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Dumper is the abstract class for all built-in dumpers. + * + * @author Fabien Potencier + */ +abstract class Dumper implements DumperInterface +{ + protected ContainerBuilder $container; + + public function __construct(ContainerBuilder $container) + { + $this->container = $container; + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/DumperInterface.php b/vendor/symfony/dependency-injection/Dumper/DumperInterface.php new file mode 100644 index 0000000..616b658 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/DumperInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +/** + * DumperInterface is the interface implemented by service container dumper classes. + * + * @author Fabien Potencier + */ +interface DumperInterface +{ + /** + * Dumps the service container. + */ + public function dump(array $options = []): string|array; +} diff --git a/vendor/symfony/dependency-injection/Dumper/GraphvizDumper.php b/vendor/symfony/dependency-injection/Dumper/GraphvizDumper.php new file mode 100644 index 0000000..1134281 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/GraphvizDumper.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; + +/** + * GraphvizDumper dumps a service container as a graphviz file. + * + * You can convert the generated dot file with the dot utility (http://www.graphviz.org/): + * + * dot -Tpng container.dot > foo.png + * + * @author Fabien Potencier + */ +class GraphvizDumper extends Dumper +{ + private array $nodes; + private array $edges; + // All values should be strings + private array $options = [ + 'graph' => ['ratio' => 'compress'], + 'node' => ['fontsize' => '11', 'fontname' => 'Arial', 'shape' => 'record'], + 'edge' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => '0.5'], + 'node.instance' => ['fillcolor' => '#9999ff', 'style' => 'filled'], + 'node.definition' => ['fillcolor' => '#eeeeee'], + 'node.missing' => ['fillcolor' => '#ff9999', 'style' => 'filled'], + ]; + + /** + * Dumps the service container as a graphviz graph. + * + * Available options: + * + * * graph: The default options for the whole graph + * * node: The default options for nodes + * * edge: The default options for edges + * * node.instance: The default options for services that are defined directly by object instances + * * node.definition: The default options for services that are defined via service definition instances + * * node.missing: The default options for missing services + */ + public function dump(array $options = []): string + { + foreach (['graph', 'node', 'edge', 'node.instance', 'node.definition', 'node.missing'] as $key) { + if (isset($options[$key])) { + $this->options[$key] = array_merge($this->options[$key], $options[$key]); + } + } + + $this->nodes = $this->findNodes(); + + $this->edges = []; + foreach ($this->container->getDefinitions() as $id => $definition) { + $this->edges[$id] = array_merge( + $this->findEdges($id, $definition->getArguments(), true, ''), + $this->findEdges($id, $definition->getProperties(), false, '') + ); + + foreach ($definition->getMethodCalls() as $call) { + $this->edges[$id] = array_merge( + $this->edges[$id], + $this->findEdges($id, $call[1], false, $call[0].'()') + ); + } + } + + return $this->container->resolveEnvPlaceholders($this->startDot().$this->addNodes().$this->addEdges().$this->endDot(), '__ENV_%s__'); + } + + private function addNodes(): string + { + $code = ''; + foreach ($this->nodes as $id => $node) { + $aliases = $this->getAliases($id); + + $code .= sprintf(" node_%s [label=\"%s\\n%s\\n\", shape=%s%s];\n", $this->dotize($id), $id.($aliases ? ' ('.implode(', ', $aliases).')' : ''), $node['class'], $this->options['node']['shape'], $this->addAttributes($node['attributes'])); + } + + return $code; + } + + private function addEdges(): string + { + $code = ''; + foreach ($this->edges as $id => $edges) { + foreach ($edges as $edge) { + $code .= sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"%s];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], $edge['required'] ? 'filled' : 'dashed', $edge['lazy'] ? ' color="#9999ff"' : ''); + } + } + + return $code; + } + + /** + * Finds all edges belonging to a specific service id. + */ + private function findEdges(string $id, array $arguments, bool $required, string $name, bool $lazy = false): array + { + $edges = []; + foreach ($arguments as $argument) { + if ($argument instanceof Parameter) { + $argument = $this->container->hasParameter($argument) ? $this->container->getParameter($argument) : null; + } elseif (\is_string($argument) && preg_match('/^%([^%]+)%$/', $argument, $match)) { + $argument = $this->container->hasParameter($match[1]) ? $this->container->getParameter($match[1]) : null; + } + + if ($argument instanceof Reference) { + $lazyEdge = $lazy; + + if (!$this->container->has((string) $argument)) { + $this->nodes[(string) $argument] = ['name' => $name, 'required' => $required, 'class' => '', 'attributes' => $this->options['node.missing']]; + } elseif ('service_container' !== (string) $argument) { + $lazyEdge = $lazy || $this->container->getDefinition((string) $argument)->isLazy(); + } + + $edges[] = [['name' => $name, 'required' => $required, 'to' => $argument, 'lazy' => $lazyEdge]]; + } elseif ($argument instanceof ArgumentInterface) { + $edges[] = $this->findEdges($id, $argument->getValues(), $required, $name, true); + } elseif ($argument instanceof Definition) { + $edges[] = $this->findEdges($id, $argument->getArguments(), $required, ''); + $edges[] = $this->findEdges($id, $argument->getProperties(), false, ''); + + foreach ($argument->getMethodCalls() as $call) { + $edges[] = $this->findEdges($id, $call[1], false, $call[0].'()'); + } + } elseif (\is_array($argument)) { + $edges[] = $this->findEdges($id, $argument, $required, $name, $lazy); + } + } + + return array_merge([], ...$edges); + } + + private function findNodes(): array + { + $nodes = []; + + $container = $this->cloneContainer(); + + foreach ($container->getDefinitions() as $id => $definition) { + $class = $definition->getClass(); + + if (str_starts_with($class, '\\')) { + $class = substr($class, 1); + } + + try { + $class = $this->container->getParameterBag()->resolveValue($class); + } catch (ParameterNotFoundException) { + } + + $nodes[$id] = ['class' => str_replace('\\', '\\\\', $class), 'attributes' => array_merge($this->options['node.definition'], ['style' => $definition->isShared() ? 'filled' : 'dotted'])]; + $container->setDefinition($id, new Definition('stdClass')); + } + + foreach ($container->getServiceIds() as $id) { + if (\array_key_exists($id, $container->getAliases())) { + continue; + } + + if (!$container->hasDefinition($id)) { + $nodes[$id] = ['class' => str_replace('\\', '\\\\', $container->get($id)::class), 'attributes' => $this->options['node.instance']]; + } + } + + return $nodes; + } + + private function cloneContainer(): ContainerBuilder + { + $parameterBag = new ParameterBag($this->container->getParameterBag()->all()); + + $container = new ContainerBuilder($parameterBag); + $container->setDefinitions($this->container->getDefinitions()); + $container->setAliases($this->container->getAliases()); + $container->setResources($this->container->getResources()); + foreach ($this->container->getExtensions() as $extension) { + $container->registerExtension($extension); + } + + return $container; + } + + private function startDot(): string + { + return sprintf("digraph sc {\n %s\n node [%s];\n edge [%s];\n\n", + $this->addOptions($this->options['graph']), + $this->addOptions($this->options['node']), + $this->addOptions($this->options['edge']) + ); + } + + private function endDot(): string + { + return "}\n"; + } + + private function addAttributes(array $attributes): string + { + $code = []; + foreach ($attributes as $k => $v) { + $code[] = sprintf('%s="%s"', $k, $v); + } + + return $code ? ', '.implode(', ', $code) : ''; + } + + private function addOptions(array $options): string + { + $code = []; + foreach ($options as $k => $v) { + $code[] = sprintf('%s="%s"', $k, $v); + } + + return implode(' ', $code); + } + + private function dotize(string $id): string + { + return preg_replace('/\W/i', '_', $id); + } + + private function getAliases(string $id): array + { + $aliases = []; + foreach ($this->container->getAliases() as $alias => $origin) { + if ($id == $origin) { + $aliases[] = $alias; + } + } + + return $aliases; + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/PhpDumper.php b/vendor/symfony/dependency-injection/Dumper/PhpDumper.php new file mode 100644 index 0000000..55dd275 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/PhpDumper.php @@ -0,0 +1,2391 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\LazyClosure; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\EnvParameterException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; +use Symfony\Component\DependencyInjection\Loader\FileLoader; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\DependencyInjection\Variable; +use Symfony\Component\ErrorHandler\DebugClassLoader; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * PhpDumper dumps a service container as a PHP class. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpDumper extends Dumper +{ + /** + * Characters that might appear in the generated variable name as first character. + */ + public const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; + + /** + * Characters that might appear in the generated variable name as any but the first character. + */ + public const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; + + /** @var \SplObjectStorage|null */ + private ?\SplObjectStorage $definitionVariables = null; + private ?array $referenceVariables = null; + private int $variableCount; + private ?\SplObjectStorage $inlinedDefinitions = null; + private ?array $serviceCalls = null; + private array $reservedVariables = ['instance', 'class', 'this', 'container']; + private ExpressionLanguage $expressionLanguage; + private ?string $targetDirRegex = null; + private int $targetDirMaxMatches; + private string $docStar; + private array $serviceIdToMethodNameMap; + private array $usedMethodNames; + private string $namespace; + private bool $asFiles; + private string $hotPathTag; + private array $preloadTags; + private bool $inlineFactories; + private bool $inlineRequires; + private array $inlinedRequires = []; + private array $circularReferences = []; + private array $singleUsePrivateIds = []; + private array $preload = []; + private bool $addGetService = false; + private array $locatedIds = []; + private string $serviceLocatorTag; + private array $exportedVariables = []; + private array $dynamicParameters = []; + private string $baseClass; + private string $class; + private DumperInterface $proxyDumper; + private bool $hasProxyDumper = true; + + public function __construct(ContainerBuilder $container) + { + if (!$container->isCompiled()) { + throw new LogicException('Cannot dump an uncompiled container.'); + } + + parent::__construct($container); + } + + /** + * Sets the dumper to be used when dumping proxies in the generated container. + */ + public function setProxyDumper(DumperInterface $proxyDumper): void + { + $this->proxyDumper = $proxyDumper; + $this->hasProxyDumper = !$proxyDumper instanceof NullDumper; + } + + /** + * Dumps the service container as a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * * namespace: The class namespace + * * as_files: To split the container in several files + * + * @return string|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set + * + * @throws EnvParameterException When an env var exists but has not been dumped + */ + public function dump(array $options = []): string|array + { + $this->locatedIds = []; + $this->targetDirRegex = null; + $this->inlinedRequires = []; + $this->exportedVariables = []; + $this->dynamicParameters = []; + $options = array_merge([ + 'class' => 'ProjectServiceContainer', + 'base_class' => 'Container', + 'namespace' => '', + 'as_files' => false, + 'debug' => true, + 'hot_path_tag' => 'container.hot_path', + 'preload_tags' => ['container.preload', 'container.no_preload'], + 'inline_factories' => null, + 'inline_class_loader' => null, + 'preload_classes' => [], + 'service_locator_tag' => 'container.service_locator', + 'build_time' => time(), + ], $options); + + $this->addGetService = false; + $this->namespace = $options['namespace']; + $this->asFiles = $options['as_files']; + $this->hotPathTag = $options['hot_path_tag']; + $this->preloadTags = $options['preload_tags']; + + $this->inlineFactories = false; + if (isset($options['inline_factories'])) { + $this->inlineFactories = $this->asFiles && $options['inline_factories']; + } + + $this->inlineRequires = $options['debug']; + if (isset($options['inline_class_loader'])) { + $this->inlineRequires = $options['inline_class_loader']; + } + + $this->serviceLocatorTag = $options['service_locator_tag']; + $this->class = $options['class']; + + if (!str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { + $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass); + $this->baseClass = $baseClass; + } elseif ('Container' === $baseClass) { + $this->baseClass = Container::class; + } else { + $this->baseClass = $baseClass; + } + + $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); + + if (!$this->hasProxyDumper) { + (new AnalyzeServiceReferencesPass(true, false))->process($this->container); + (new CheckCircularReferencesPass())->process($this->container); + } + + $this->analyzeReferences(); + $this->docStar = $options['debug'] ? '*' : ''; + + if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) { + // Build a regexp where the first root dirs are mandatory, + // but every other sub-dir is optional up to the full path in $dir + // Mandate at least 1 root dir and not more than 5 optional dirs. + + $dir = explode(\DIRECTORY_SEPARATOR, realpath($dir)); + $i = \count($dir); + + if (2 + (int) ('\\' === \DIRECTORY_SEPARATOR) <= $i) { + $regex = ''; + $lastOptionalDir = $i > 8 ? $i - 5 : (2 + (int) ('\\' === \DIRECTORY_SEPARATOR)); + $this->targetDirMaxMatches = $i - $lastOptionalDir; + + while (--$i >= $lastOptionalDir) { + $regex = sprintf('(%s%s)?', preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex); + } + + do { + $regex = preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#').$regex; + } while (0 < --$i); + + $this->targetDirRegex = '#(^|file://|[:;, \|\r\n])'.preg_quote($dir[0], '#').$regex.'#'; + } + } + + $proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null; + + if ($options['preload_classes']) { + $this->preload = array_combine($options['preload_classes'], $options['preload_classes']); + } + + $code = $this->addDefaultParametersMethod(); + $code = + $this->startClass($options['class'], $baseClass, $this->inlineFactories && $proxyClasses). + $this->addServices($services). + $this->addDeprecatedAliases(). + $code + ; + + $proxyClasses ??= $this->generateProxyClasses(); + + if ($this->addGetService) { + $code = preg_replace( + "/\r?\n\r?\n public function __construct.+?\\{\r?\n/s", + "\n protected \Closure \$getService;$0", + $code, + 1 + ); + } + + if ($this->asFiles) { + $fileTemplate = <<docStar} + * @internal This class has been auto-generated by the Symfony Dependency Injection Component. + */ +class %s extends {$options['class']} +{%s} + +EOF; + $files = []; + $preloadedFiles = []; + $ids = $this->container->getRemovedIds(); + foreach ($this->container->getDefinitions() as $id => $definition) { + if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) { + $ids[$id] = true; + } + } + if ($ids = array_keys($ids)) { + sort($ids); + $c = "doExport($id)." => true,\n"; + } + $files['removed-ids.php'] = $c."];\n"; + } + + if (!$this->inlineFactories) { + foreach ($this->generateServiceFiles($services) as $file => [$c, $preload]) { + $files[$file] = sprintf($fileTemplate, substr($file, 0, -4), $c); + + if ($preload) { + $preloadedFiles[$file] = $file; + } + } + foreach ($proxyClasses as $file => $c) { + $files[$file] = "endClass(); + + if ($this->inlineFactories && $proxyClasses) { + $files['proxy-classes.php'] = " $c) { + $code["Container{$hash}/{$file}"] = substr_replace($c, "namespace ? "\nnamespace {$this->namespace};\n" : ''; + $time = $options['build_time']; + $id = hash('crc32', $hash.$time); + $this->asFiles = false; + + if ($this->preload && null !== $autoloadFile = $this->getAutoloadFile()) { + $autoloadFile = trim($this->export($autoloadFile), '()\\'); + + $preloadedFiles = array_reverse($preloadedFiles); + if ('' !== $preloadedFiles = implode("';\nrequire __DIR__.'/", $preloadedFiles)) { + $preloadedFiles = "require __DIR__.'/$preloadedFiles';\n"; + } + + $code[$options['class'].'.preload.php'] = <<= 7.4 when preloading is desired + +use Symfony\Component\DependencyInjection\Dumper\Preloader; + +if (in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + return; +} + +require $autoloadFile; +(require __DIR__.'/{$options['class']}.php')->set(\\Container{$hash}\\{$options['class']}::class, null); +$preloadedFiles +\$classes = []; + +EOF; + + foreach ($this->preload as $class) { + if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) { + continue; + } + if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) { + $code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class); + } + } + + $code[$options['class'].'.preload.php'] .= <<<'EOF' + +$preloaded = Preloader::preload($classes); + +EOF; + } + + $code[$options['class'].'.php'] = << '$hash', + 'container.build_id' => '$id', + 'container.build_time' => $time, + 'container.runtime_mode' => \\in_array(\\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? 'web=0' : 'web=1', +], __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}'); + +EOF; + } else { + $code .= $this->endClass(); + foreach ($proxyClasses as $c) { + $code .= $c; + } + } + + $this->targetDirRegex = null; + $this->inlinedRequires = []; + $this->circularReferences = []; + $this->locatedIds = []; + $this->exportedVariables = []; + $this->dynamicParameters = []; + $this->preload = []; + + $unusedEnvs = []; + foreach ($this->container->getEnvCounters() as $env => $use) { + if (!$use) { + $unusedEnvs[] = $env; + } + } + if ($unusedEnvs) { + throw new EnvParameterException($unusedEnvs, null, 'Environment variables "%s" are never used. Please, check your container\'s configuration.'); + } + + return $code; + } + + /** + * Retrieves the currently set proxy dumper or instantiates one. + */ + private function getProxyDumper(): DumperInterface + { + return $this->proxyDumper ??= new LazyServiceDumper($this->class); + } + + private function analyzeReferences(): void + { + (new AnalyzeServiceReferencesPass(false, $this->hasProxyDumper))->process($this->container); + $checkedNodes = []; + $this->circularReferences = []; + $this->singleUsePrivateIds = []; + foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { + if (!$node->getValue() instanceof Definition) { + continue; + } + + if ($this->isSingleUsePrivateNode($node)) { + $this->singleUsePrivateIds[$id] = $id; + } + + $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes); + } + + $this->container->getCompiler()->getServiceReferenceGraph()->clear(); + $this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences); + } + + private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = true): void + { + $path[$sourceId] = $byConstructor; + $checkedNodes[$sourceId] = true; + foreach ($edges as $edge) { + $node = $edge->getDestNode(); + $id = $node->getId(); + if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) { + continue; + } + + if (isset($path[$id])) { + $loop = null; + $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); + $pathInLoop = [$id, []]; + foreach ($path as $k => $pathByConstructor) { + if (null !== $loop) { + $loop[] = $k; + $pathInLoop[1][$k] = $pathByConstructor; + $loops[$k][] = &$pathInLoop; + $loopByConstructor = $loopByConstructor && $pathByConstructor; + } elseif ($k === $id) { + $loop = []; + } + } + $this->addCircularReferences($id, $loop, $loopByConstructor); + } elseif (!isset($checkedNodes[$id])) { + $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor() && !$edge->isLazy()); + } elseif (isset($loops[$id])) { + // we already had detected loops for this edge + // let's check if we have a common ancestor in one of the detected loops + foreach ($loops[$id] as [$first, $loopPath]) { + if (!isset($path[$first])) { + continue; + } + // We have a common ancestor, let's fill the current path + $fillPath = null; + foreach ($loopPath as $k => $pathByConstructor) { + if (null !== $fillPath) { + $fillPath[$k] = $pathByConstructor; + } elseif ($k === $id) { + $fillPath = $path; + $fillPath[$k] = $pathByConstructor; + } + } + + // we can now build the loop + $loop = null; + $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); + foreach ($fillPath as $k => $pathByConstructor) { + if (null !== $loop) { + $loop[] = $k; + $loopByConstructor = $loopByConstructor && $pathByConstructor; + } elseif ($k === $first) { + $loop = []; + } + } + $this->addCircularReferences($first, $loop, $loopByConstructor); + break; + } + } + } + unset($path[$sourceId]); + } + + private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor): void + { + $currentId = $sourceId; + $currentPath = array_reverse($currentPath); + $currentPath[] = $currentId; + foreach ($currentPath as $parentId) { + if (empty($this->circularReferences[$parentId][$currentId])) { + $this->circularReferences[$parentId][$currentId] = $byConstructor; + } + + $currentId = $parentId; + } + } + + private function collectLineage(string $class, array &$lineage): void + { + if (isset($lineage[$class])) { + return; + } + if (!$r = $this->container->getReflectionClass($class, false)) { + return; + } + if (is_a($class, $this->baseClass, true)) { + return; + } + $file = $r->getFileName(); + if (str_ends_with($file, ') : eval()\'d code')) { + $file = substr($file, 0, strrpos($file, '(', -17)); + } + if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) { + return; + } + + $lineage[$class] = substr($exportedFile, 1, -1); + + if ($parent = $r->getParentClass()) { + $this->collectLineage($parent->name, $lineage); + } + + foreach ($r->getInterfaces() as $parent) { + $this->collectLineage($parent->name, $lineage); + } + + foreach ($r->getTraits() as $parent) { + $this->collectLineage($parent->name, $lineage); + } + + unset($lineage[$class]); + $lineage[$class] = substr($exportedFile, 1, -1); + } + + private function generateProxyClasses(): array + { + $proxyClasses = []; + $alreadyGenerated = []; + $definitions = $this->container->getDefinitions(); + $strip = '' === $this->docStar; + $proxyDumper = $this->getProxyDumper(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (!$definition = $this->isProxyCandidate($definition, $asGhostObject, $id)) { + continue; + } + if (isset($alreadyGenerated[$asGhostObject][$class = $definition->getClass()])) { + continue; + } + $alreadyGenerated[$asGhostObject][$class] = true; + + foreach (array_column($definition->getTag('proxy'), 'interface') ?: [$class] as $r) { + if (!$r = $this->container->getReflectionClass($r)) { + continue; + } + do { + $file = $r->getFileName(); + if (str_ends_with($file, ') : eval()\'d code')) { + $file = substr($file, 0, strrpos($file, '(', -17)); + } + if (is_file($file)) { + $this->container->addResource(new FileResource($file)); + } + $r = $r->getParentClass() ?: null; + } while ($r?->isUserDefined()); + } + + if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition, $id)) { + continue; + } + + if ($this->inlineRequires) { + $lineage = []; + $this->collectLineage($class, $lineage); + + $code = ''; + foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { + if ($this->inlineFactories) { + $this->inlinedRequires[$file] = true; + } + $code .= sprintf("include_once %s;\n", $file); + } + + $proxyCode = $code.$proxyCode; + } + + if ($strip) { + $proxyCode = "inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode; + $i = strpos($proxyClass, 'class'); + $proxyClass = substr($proxyClass, 6 + $i, strpos($proxyClass, ' ', 7 + $i) - $i - 6); + + if ($this->asFiles || $this->namespace) { + $proxyCode .= "\nif (!\\class_exists('$proxyClass', false)) {\n \\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n}\n"; + } + + $proxyClasses[$proxyClass.'.php'] = $proxyCode; + } + + return $proxyClasses; + } + + private function addServiceInclude(string $cId, Definition $definition, bool $isProxyCandidate): string + { + $code = ''; + + if ($this->inlineRequires && (!$this->isHotPath($definition) || $isProxyCandidate)) { + $lineage = []; + foreach ($this->inlinedDefinitions as $def) { + if (!$def->isDeprecated()) { + foreach ($this->getClasses($def, $cId) as $class) { + $this->collectLineage($class, $lineage); + } + } + } + + foreach ($this->serviceCalls as $id => [$callCount, $behavior]) { + if ('service_container' !== $id && $id !== $cId + && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior + && $this->container->has($id) + && $this->isTrivialInstance($def = $this->container->findDefinition($id)) + ) { + foreach ($this->getClasses($def, $cId) as $class) { + $this->collectLineage($class, $lineage); + } + } + } + + foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { + $code .= sprintf(" include_once %s;\n", $file); + } + } + + foreach ($this->inlinedDefinitions as $def) { + if ($file = $def->getFile()) { + $file = $this->dumpValue($file); + $file = '(' === $file[0] ? substr($file, 1, -1) : $file; + $code .= sprintf(" include_once %s;\n", $file); + } + } + + if ('' !== $code) { + $code .= "\n"; + } + + return $code; + } + + /** + * @throws InvalidArgumentException + * @throws RuntimeException + */ + private function addServiceInstance(string $id, Definition $definition, bool $isSimpleInstance): string + { + $class = $this->dumpValue($definition->getClass()); + + if (str_starts_with($class, "'") && !str_contains($class, '$') && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); + } + + $asGhostObject = false; + $isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id); + $instantiation = ''; + + $lastWitherIndex = null; + foreach ($definition->getMethodCalls() as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + + if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) { + $instantiation = sprintf('$container->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); + } elseif (!$isSimpleInstance) { + $instantiation = '$instance'; + } + + $return = ''; + if ($isSimpleInstance) { + $return = 'return '; + } else { + $instantiation .= ' = '; + } + + return $this->addNewInstance($definition, ' '.$return.$instantiation, $id, $asGhostObject); + } + + private function isTrivialInstance(Definition $definition): bool + { + if ($definition->hasErrors()) { + return true; + } + if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) { + return false; + } + if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < \count($definition->getArguments())) { + return false; + } + + foreach ($definition->getArguments() as $arg) { + if (!$arg || $arg instanceof Parameter) { + continue; + } + if (\is_array($arg) && 3 >= \count($arg)) { + foreach ($arg as $k => $v) { + if ($this->dumpValue($k) !== $this->dumpValue($k, false)) { + return false; + } + if (!$v || $v instanceof Parameter) { + continue; + } + if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) { + continue; + } + if (!\is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) { + return false; + } + } + } elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) { + continue; + } elseif (!\is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) { + return false; + } + } + + return true; + } + + private function addServiceMethodCalls(Definition $definition, string $variableName, ?string $sharedNonLazyId): string + { + $lastWitherIndex = null; + foreach ($definition->getMethodCalls() as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + + $calls = ''; + foreach ($definition->getMethodCalls() as $k => $call) { + $arguments = []; + foreach ($call[1] as $i => $value) { + $arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value); + } + + $witherAssignation = ''; + + if ($call[2] ?? false) { + if (null !== $sharedNonLazyId && $lastWitherIndex === $k && 'instance' === $variableName) { + $witherAssignation = sprintf('$container->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); + } + $witherAssignation .= sprintf('$%s = ', $variableName); + } + + $calls .= $this->wrapServiceConditionals($call[1], sprintf(" %s\$%s->%s(%s);\n", $witherAssignation, $variableName, $call[0], implode(', ', $arguments))); + } + + return $calls; + } + + private function addServiceProperties(Definition $definition, string $variableName = 'instance'): string + { + $code = ''; + foreach ($definition->getProperties() as $name => $value) { + $code .= sprintf(" \$%s->%s = %s;\n", $variableName, $name, $this->dumpValue($value)); + } + + return $code; + } + + private function addServiceConfigurator(Definition $definition, string $variableName = 'instance'): string + { + if (!$callable = $definition->getConfigurator()) { + return ''; + } + + if (\is_array($callable)) { + if ($callable[0] instanceof Reference + || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) + ) { + return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + } + + $class = $this->dumpValue($callable[0]); + // If the class is a string we can optimize away + if (str_starts_with($class, "'") && !str_contains($class, '$')) { + return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName); + } + + if (str_starts_with($class, 'new ')) { + return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + } + + return sprintf(" [%s, '%s'](\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + } + + return sprintf(" %s(\$%s);\n", $callable, $variableName); + } + + private function addService(string $id, Definition $definition): array + { + $this->definitionVariables = new \SplObjectStorage(); + $this->referenceVariables = []; + $this->variableCount = 0; + $this->referenceVariables[$id] = new Variable('instance'); + + $return = []; + + if ($class = $definition->getClass()) { + $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); + $return[] = sprintf(str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); + } elseif ($definition->getFactory()) { + $factory = $definition->getFactory(); + if (\is_string($factory) && !str_starts_with($factory, '@=')) { + $return[] = sprintf('@return object An instance returned by %s()', $factory); + } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { + $class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0]; + $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); + $return[] = sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]); + } + } + + if ($definition->isDeprecated()) { + if ($return && str_starts_with($return[\count($return) - 1], '@return')) { + $return[] = ''; + } + + $deprecation = $definition->getDeprecation($id); + $return[] = sprintf('@deprecated %s', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); + } + + $return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return)); + $return = $this->container->resolveEnvPlaceholders($return); + + $shared = $definition->isShared() ? ' shared' : ''; + $public = $definition->isPublic() ? 'public' : 'private'; + $autowired = $definition->isAutowired() ? ' autowired' : ''; + $asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition); + $methodName = $this->generateMethodName($id); + + if ($asFile || $definition->isLazy()) { + $lazyInitialization = ', $lazyLoad = true'; + } else { + $lazyInitialization = ''; + } + + $code = <<docStar} + * Gets the $public '$id'$shared$autowired service. + * + * $return +EOF; + $code = str_replace('*/', ' ', $code).<<hasErrors() && $e = $definition->getErrors()) { + $code .= sprintf(" throw new RuntimeException(%s);\n", $this->export(reset($e))); + } else { + $this->serviceCalls = []; + $this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls); + + if ($definition->isDeprecated()) { + $deprecation = $definition->getDeprecation($id); + $code .= sprintf(" trigger_deprecation(%s, %s, %s);\n\n", $this->export($deprecation['package']), $this->export($deprecation['version']), $this->export($deprecation['message'])); + } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { + foreach ($this->inlinedDefinitions as $def) { + foreach ($this->getClasses($def, $id) as $class) { + $this->preload[$class] = $class; + } + } + } + + if (!$definition->isShared()) { + $factory = sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + } + + $asGhostObject = false; + if ($isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id)) { + $definition = $isProxyCandidate; + + if (!$definition->isShared()) { + $code .= sprintf(' %s ??= ', $factory); + + if ($definition->isPublic()) { + $code .= sprintf("fn () => self::%s(\$container);\n\n", $asFile ? 'do' : $methodName); + } else { + $code .= sprintf("self::%s(...);\n\n", $asFile ? 'do' : $methodName); + } + } + $lazyLoad = $asGhostObject ? '$proxy' : 'false'; + + $factoryCode = $asFile ? sprintf('self::do($container, %s)', $lazyLoad) : sprintf('self::%s($container, %s)', $methodName, $lazyLoad); + $code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); + } + + $c = $this->addServiceInclude($id, $definition, null !== $isProxyCandidate); + + if ('' !== $c && $isProxyCandidate && !$definition->isShared()) { + $c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); + $code .= " static \$include = true;\n\n"; + $code .= " if (\$include) {\n"; + $code .= $c; + $code .= " \$include = false;\n"; + $code .= " }\n\n"; + } else { + $code .= $c; + } + + $c = $this->addInlineService($id, $definition); + + if (!$isProxyCandidate && !$definition->isShared()) { + $c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); + $lazyloadInitialization = $definition->isLazy() ? ', $lazyLoad = true' : ''; + + $c = sprintf(" %s = function (\$container%s) {\n%s };\n\n return %1\$s(\$container);\n", $factory, $lazyloadInitialization, $c); + } + + $code .= $c; + } + + $code .= " }\n"; + + $this->definitionVariables = $this->inlinedDefinitions = null; + $this->referenceVariables = $this->serviceCalls = null; + + return [$file, $code]; + } + + private function addInlineVariables(string $id, Definition $definition, array $arguments, bool $forConstructor): string + { + $code = ''; + + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $code .= $this->addInlineVariables($id, $definition, $argument, $forConstructor); + } elseif ($argument instanceof Reference) { + $code .= $this->addInlineReference($id, $definition, $argument, $forConstructor); + } elseif ($argument instanceof Definition) { + $code .= $this->addInlineService($id, $definition, $argument, $forConstructor); + } + } + + return $code; + } + + private function addInlineReference(string $id, Definition $definition, string $targetId, bool $forConstructor): string + { + while ($this->container->hasAlias($targetId)) { + $targetId = (string) $this->container->getAlias($targetId); + } + + [$callCount, $behavior] = $this->serviceCalls[$targetId]; + + if ($id === $targetId) { + return $this->addInlineService($id, $definition, $definition); + } + + if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) { + return ''; + } + + if ($this->container->hasDefinition($targetId) && ($def = $this->container->getDefinition($targetId)) && !$def->isShared()) { + return ''; + } + + $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]) && !($this->hasProxyDumper && $definition->isLazy()); + + if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) { + $code = $this->addInlineService($id, $definition, $definition); + } else { + $code = ''; + } + + if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) { + return $code; + } + + $name = $this->getNextVariableName(); + $this->referenceVariables[$targetId] = new Variable($name); + + $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null; + $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference)); + + if (!$hasSelfRef || !$forConstructor) { + return $code; + } + + $code .= sprintf(<<<'EOTXT' + + if (isset($container->%s[%s])) { + return $container->%1$s[%2$s]; + } + +EOTXT + , + $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', + $this->doExport($id) + ); + + return $code; + } + + private function addInlineService(string $id, Definition $definition, ?Definition $inlineDef = null, bool $forConstructor = true): string + { + $code = ''; + + if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { + foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) { + if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) { + $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); + } + } + } + + if (isset($this->definitionVariables[$inlineDef ??= $definition])) { + return $code; + } + + $arguments = [$inlineDef->getArguments(), $inlineDef->getFactory()]; + + $code .= $this->addInlineVariables($id, $definition, $arguments, $forConstructor); + + if ($arguments = array_filter([$inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()])) { + $isSimpleInstance = false; + } elseif ($definition !== $inlineDef && 2 > $this->inlinedDefinitions[$inlineDef]) { + return $code; + } + + $asGhostObject = false; + $isProxyCandidate = $this->isProxyCandidate($inlineDef, $asGhostObject, $id); + + if (isset($this->definitionVariables[$inlineDef])) { + $isSimpleInstance = false; + } else { + $name = $definition === $inlineDef ? 'instance' : $this->getNextVariableName(); + $this->definitionVariables[$inlineDef] = new Variable($name); + $code .= '' !== $code ? "\n" : ''; + + if ('instance' === $name) { + $code .= $this->addServiceInstance($id, $definition, $isSimpleInstance); + } else { + $code .= $this->addNewInstance($inlineDef, ' $'.$name.' = ', $id); + } + + if ('' !== $inline = $this->addInlineVariables($id, $definition, $arguments, false)) { + $code .= "\n".$inline."\n"; + } elseif ($arguments && 'instance' === $name) { + $code .= "\n"; + } + + $code .= $this->addServiceProperties($inlineDef, $name); + $code .= $this->addServiceMethodCalls($inlineDef, $name, !$isProxyCandidate && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null); + $code .= $this->addServiceConfigurator($inlineDef, $name); + } + + if (!$isRootInstance || $isSimpleInstance) { + return $code; + } + + return $code."\n return \$instance;\n"; + } + + private function addServices(?array &$services = null): string + { + $publicServices = $privateServices = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (!$definition->isSynthetic()) { + $services[$id] = $this->addService($id, $definition); + } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { + $services[$id] = null; + + foreach ($this->getClasses($definition, $id) as $class) { + $this->preload[$class] = $class; + } + } + } + + foreach ($definitions as $id => $definition) { + if (!([$file, $code] = $services[$id]) || null !== $file) { + continue; + } + if ($definition->isPublic()) { + $publicServices .= $code; + } elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) { + $privateServices .= $code; + } + } + + return $publicServices.$privateServices; + } + + private function generateServiceFiles(array $services): iterable + { + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (([$file, $code] = $services[$id]) && null !== $file && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) { + yield $file => [$code, $definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1]) && !$definition->isDeprecated() && !$definition->hasErrors()]; + } + } + } + + private function addNewInstance(Definition $definition, string $return = '', ?string $id = null, bool $asGhostObject = false): string + { + $tail = $return ? str_repeat(')', substr_count($return, '(') - substr_count($return, ')')).";\n" : ''; + + if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) { + $arguments = []; + foreach ($definition->getArgument(0) as $k => $argument) { + $arguments[$k] = $argument->getValues()[0]; + } + + return $return.$this->dumpValue(new ServiceLocatorArgument($arguments)).$tail; + } + + $arguments = []; + foreach ($definition->getArguments() as $i => $value) { + $arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value); + } + + if (null !== $definition->getFactory()) { + $callable = $definition->getFactory(); + + if ('current' === $callable && [0] === array_keys($definition->getArguments()) && \is_array($value) && [0] === array_keys($value)) { + return $return.$this->dumpValue($value[0]).$tail; + } + + if (['Closure', 'fromCallable'] === $callable) { + $callable = $definition->getArgument(0); + if ($callable instanceof ServiceClosureArgument) { + return $return.$this->dumpValue($callable).$tail; + } + + $arguments = ['...']; + + if ($callable instanceof Reference || $callable instanceof Definition) { + $callable = [$callable, '__invoke']; + } + } + + if (\is_string($callable) && str_starts_with($callable, '@=')) { + return $return.sprintf('(($args = %s) ? (%s) : null)', + $this->dumpValue(new ServiceLocatorArgument($definition->getArguments())), + $this->getExpressionLanguage()->compile(substr($callable, 2), ['container' => 'container', 'args' => 'args']) + ).$tail; + } + + if (!\is_array($callable)) { + return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail; + } + + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a')); + } + + if (['...'] === $arguments && ($definition->isLazy() || 'Closure' !== ($definition->getClass() ?? 'Closure')) && ( + $callable[0] instanceof Reference + || ($callable[0] instanceof Definition && !$this->definitionVariables->contains($callable[0])) + )) { + $initializer = 'fn () => '.$this->dumpValue($callable[0]); + + return $return.LazyClosure::getCode($initializer, $callable, $definition, $this->container, $id).$tail; + } + + if ($callable[0] instanceof Reference + || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) + ) { + return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + } + + $class = $this->dumpValue($callable[0]); + // If the class is a string we can optimize away + if (str_starts_with($class, "'") && !str_contains($class, '$')) { + if ("''" === $class) { + throw new RuntimeException(sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline')); + } + + return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + } + + if (str_starts_with($class, 'new ')) { + return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + } + + return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; + } + + if (null === $class = $definition->getClass()) { + throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); + } + + if (!$asGhostObject) { + return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail; + } + + if (!method_exists($this->container->getParameterBag()->resolveValue($class), '__construct')) { + return $return.'$lazyLoad'.$tail; + } + + return $return.sprintf('($lazyLoad->__construct(%s) && false ?: $lazyLoad)', implode(', ', $arguments)).$tail; + } + + private function startClass(string $class, string $baseClass, bool $hasProxyClasses): string + { + $namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; + + $code = <<docStar} + * @internal This class has been auto-generated by the Symfony Dependency Injection Component. + */ +class $class extends $baseClass +{ + private const DEPRECATED_PARAMETERS = []; + + protected \$parameters = []; + + public function __construct() + { + +EOF; + $code = str_replace(" private const DEPRECATED_PARAMETERS = [];\n\n", $this->addDeprecatedParameters(), $code); + if ($this->asFiles) { + $code = str_replace('__construct()', '__construct(private array $buildParameters = [], protected string $containerDir = __DIR__)', $code); + + if (null !== $this->targetDirRegex) { + $code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code); + $code .= ' $this->targetDir = \\dirname($containerDir);'."\n"; + } + } + + if (Container::class !== $this->baseClass) { + $r = $this->container->getReflectionClass($this->baseClass, false); + if (null !== $r + && (null !== $constructor = $r->getConstructor()) + && 0 === $constructor->getNumberOfRequiredParameters() + && Container::class !== $constructor->getDeclaringClass()->name + ) { + $code .= " parent::__construct();\n"; + $code .= " \$this->parameterBag = null;\n\n"; + } + } + + if ($this->container->getParameterBag()->all()) { + $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n"; + } + $code .= " \$this->services = \$this->privates = [];\n"; + + $code .= $this->addSyntheticIds(); + $code .= $this->addMethodMap(); + $code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : ''; + $code .= $this->addAliases(); + $code .= $this->addInlineRequires($hasProxyClasses); + $code .= <<addRemovedIds(); + + if ($this->asFiles && !$this->inlineFactories) { + $code .= <<<'EOF' + + protected function load($file, $lazyLoad = true): mixed + { + if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { + return $class::do($this, $lazyLoad); + } + + if ('.' === $file[-4]) { + $class = substr($class, 0, -4); + } else { + $file .= '.php'; + } + + $service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file; + + return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service; + } + +EOF; + } + + foreach ($this->container->getDefinitions() as $definition) { + if (!$definition->isLazy() || !$this->hasProxyDumper) { + continue; + } + + if ($this->asFiles && !$this->inlineFactories) { + $proxyLoader = "class_exists(\$class, false) || require __DIR__.'/'.\$class.'.php';\n\n "; + } else { + $proxyLoader = ''; + } + + $code .= <<container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if ($definition->isSynthetic() && 'service_container' !== $id) { + $code .= ' '.$this->doExport($id)." => true,\n"; + } + } + + return $code ? " \$this->syntheticIds = [\n{$code} ];\n" : ''; + } + + private function addRemovedIds(): string + { + $ids = $this->container->getRemovedIds(); + foreach ($this->container->getDefinitions() as $id => $definition) { + if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) { + $ids[$id] = true; + } + } + if (!$ids) { + return ''; + } + if ($this->asFiles) { + $code = "require \$this->containerDir.\\DIRECTORY_SEPARATOR.'removed-ids.php'"; + } else { + $code = ''; + $ids = array_keys($ids); + sort($ids); + foreach ($ids as $id) { + if (preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id)) { + continue; + } + $code .= ' '.$this->doExport($id)." => true,\n"; + } + + $code = "[\n{$code} ]"; + } + + return <<container->getParameterBag()) instanceof ParameterBag) { + return ''; + } + + if (!$deprecated = $bag->allDeprecated()) { + return ''; + } + $code = ''; + ksort($deprecated); + foreach ($deprecated as $param => $deprecation) { + $code .= ' '.$this->doExport($param).' => ['.implode(', ', array_map($this->doExport(...), $deprecation))."],\n"; + } + + return " private const DEPRECATED_PARAMETERS = [\n{$code} ];\n\n"; + } + + private function addMethodMap(): string + { + $code = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) { + $code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n"; + } + } + + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $id) { + if (!$id->isDeprecated()) { + continue; + } + $code .= ' '.$this->doExport($alias).' => '.$this->doExport($this->generateMethodName($alias)).",\n"; + } + + return $code ? " \$this->methodMap = [\n{$code} ];\n" : ''; + } + + private function addFileMap(): string + { + $code = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (!$definition->isSynthetic() && $definition->isPublic() && !$this->isHotPath($definition)) { + $code .= sprintf(" %s => '%s',\n", $this->doExport($id), $this->generateMethodName($id)); + } + } + + return $code ? " \$this->fileMap = [\n{$code} ];\n" : ''; + } + + private function addAliases(): string + { + if (!$aliases = $this->container->getAliases()) { + return "\n \$this->aliases = [];\n"; + } + + $code = " \$this->aliases = [\n"; + ksort($aliases); + foreach ($aliases as $alias => $id) { + if ($id->isDeprecated()) { + continue; + } + + $id = (string) $id; + while (isset($aliases[$id])) { + $id = (string) $aliases[$id]; + } + $code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n"; + } + + return $code." ];\n"; + } + + private function addDeprecatedAliases(): string + { + $code = ''; + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $definition) { + if (!$definition->isDeprecated()) { + continue; + } + $public = $definition->isPublic() ? 'public' : 'private'; + $id = (string) $definition; + $methodNameAlias = $this->generateMethodName($alias); + $idExported = $this->export($id); + $deprecation = $definition->getDeprecation($alias); + $packageExported = $this->export($deprecation['package']); + $versionExported = $this->export($deprecation['version']); + $messageExported = $this->export($deprecation['message']); + $code .= <<docStar} + * Gets the $public '$alias' alias. + * + * @return object The "$id" service. + */ + protected static function {$methodNameAlias}(\$container) + { + trigger_deprecation($packageExported, $versionExported, $messageExported); + + return \$container->get($idExported); + } + +EOF; + } + + return $code; + } + + private function addInlineRequires(bool $hasProxyClasses): string + { + $lineage = []; + $hotPathServices = $this->hotPathTag && $this->inlineRequires ? $this->container->findTaggedServiceIds($this->hotPathTag) : []; + + foreach ($hotPathServices as $id => $tags) { + $definition = $this->container->getDefinition($id); + + if ($definition->isLazy() && $this->hasProxyDumper) { + continue; + } + + $inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]); + + foreach ($inlinedDefinitions as $def) { + foreach ($this->getClasses($def, $id) as $class) { + $this->collectLineage($class, $lineage); + } + } + } + + $code = ''; + + foreach ($lineage as $file) { + if (!isset($this->inlinedRequires[$file])) { + $this->inlinedRequires[$file] = true; + $code .= sprintf("\n include_once %s;", $file); + } + } + + if ($hasProxyClasses) { + $code .= "\n include_once __DIR__.'/proxy-classes.php';"; + } + + return $code ? sprintf("\n \$this->privates['service_container'] = static function (\$container) {%s\n };\n", $code) : ''; + } + + private function addDefaultParametersMethod(): string + { + if (!$this->container->getParameterBag()->all()) { + return ''; + } + + $php = []; + $dynamicPhp = []; + + foreach ($this->container->getParameterBag()->all() as $key => $value) { + if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) { + throw new InvalidArgumentException(sprintf('Parameter name cannot use env parameters: "%s".', $resolvedKey)); + } + $hasEnum = false; + $export = $this->exportParameters([$value], '', 12, $hasEnum); + $export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2); + + if ($hasEnum || preg_match("/\\\$container->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w*+'\)|targetDir\.'')/", $export[1])) { + $dynamicPhp[$key] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); + $this->dynamicParameters[$key] = true; + } else { + $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); + } + } + $parameters = sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', 8)); + + $code = <<<'EOF' + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (isset(self::DEPRECATED_PARAMETERS[$name])) { + trigger_deprecation(...self::DEPRECATED_PARAMETERS[$name]); + } + + if (isset($this->buildParameters[$name])) { + return $this->buildParameters[$name]; + } + + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { + throw new ParameterNotFoundException($name); + } + if (isset($this->loadedDynamicParameters[$name])) { + return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + + return $this->parameters[$name]; + } + + public function hasParameter(string $name): bool + { + if (isset($this->buildParameters[$name])) { + return true; + } + + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); + } + + public function setParameter(string $name, $value): void + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + public function getParameterBag(): ParameterBagInterface + { + if (!isset($this->parameterBag)) { + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + foreach ($this->buildParameters as $name => $value) { + $parameters[$name] = $value; + } + $this->parameterBag = new FrozenParameterBag($parameters, self::DEPRECATED_PARAMETERS); + } + + return $this->parameterBag; + } + +EOF; + + if (!$this->asFiles) { + $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code); + } + + if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag || !$bag->allDeprecated()) { + $code = preg_replace("/\n.*DEPRECATED_PARAMETERS.*\n.*\n.*\n/m", '', $code, 1); + $code = str_replace(', self::DEPRECATED_PARAMETERS', '', $code); + } + + if ($dynamicPhp) { + $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); + $getDynamicParameter = <<<'EOF' + $container = $this; + $value = match ($name) { +%s + default => throw new ParameterNotFoundException($name), + }; + $this->loadedDynamicParameters[$name] = true; + + return $this->dynamicParameters[$name] = $value; +EOF; + $getDynamicParameter = sprintf($getDynamicParameter, implode("\n", $dynamicPhp)); + } else { + $loadedDynamicParameters = '[]'; + $getDynamicParameter = str_repeat(' ', 8).'throw new ParameterNotFoundException($name);'; + } + + $code .= << $value) { + if (\is_array($value)) { + $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4, $hasEnum); + } elseif ($value instanceof ArgumentInterface) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_debug_type($value), $path.'/'.$key)); + } elseif ($value instanceof Variable) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key)); + } elseif ($value instanceof Definition) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path.'/'.$key)); + } elseif ($value instanceof Reference) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key)); + } elseif ($value instanceof Expression) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain expressions. Expression "%s" found in "%s".', $value, $path.'/'.$key)); + } elseif ($value instanceof \UnitEnum) { + $hasEnum = true; + $value = sprintf('\%s::%s', $value::class, $value->name); + } else { + $value = $this->export($value); + } + + $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), $this->export($key), $value); + } + + return sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', $indent - 4)); + } + + private function endClass(): string + { + return <<<'EOF' +} + +EOF; + } + + private function wrapServiceConditionals(mixed $value, string $code): string + { + if (!$condition = $this->getServiceConditionals($value)) { + return $code; + } + + // re-indent the wrapped code + $code = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $code))); + + return sprintf(" if (%s) {\n%s }\n", $condition, $code); + } + + private function getServiceConditionals(mixed $value): string + { + $conditions = []; + foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { + if (!$this->container->hasDefinition($service)) { + return 'false'; + } + $conditions[] = sprintf('isset($container->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); + } + foreach (ContainerBuilder::getServiceConditionals($value) as $service) { + if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { + continue; + } + + $conditions[] = sprintf('$container->has(%s)', $this->doExport($service)); + } + + if (!$conditions) { + return ''; + } + + return implode(' && ', $conditions); + } + + private function getDefinitionsFromArguments(array $arguments, ?\SplObjectStorage $definitions = null, array &$calls = [], ?bool $byConstructor = null): \SplObjectStorage + { + $definitions ??= new \SplObjectStorage(); + + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor); + } elseif ($argument instanceof Reference) { + $id = (string) $argument; + + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + + if (!isset($calls[$id])) { + $calls[$id] = [0, $argument->getInvalidBehavior(), $byConstructor]; + } else { + $calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior()); + } + + ++$calls[$id][0]; + } elseif (!$argument instanceof Definition) { + // no-op + } elseif (isset($definitions[$argument])) { + $definitions[$argument] = 1 + $definitions[$argument]; + } else { + $definitions[$argument] = 1; + $arguments = [$argument->getArguments(), $argument->getFactory()]; + $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null === $byConstructor || $byConstructor); + $arguments = [$argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()]; + $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null !== $byConstructor && $byConstructor); + } + } + + return $definitions; + } + + /** + * @throws RuntimeException + */ + private function dumpValue(mixed $value, bool $interpolate = true): string + { + if (\is_array($value)) { + if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) { + return $this->dumpValue("%$param%"); + } + $isList = array_is_list($value); + $code = []; + foreach ($value as $k => $v) { + $code[] = $isList ? $this->dumpValue($v, $interpolate) : sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); + } + + return sprintf('[%s]', implode(', ', $code)); + } elseif ($value instanceof ArgumentInterface) { + $scope = [$this->definitionVariables, $this->referenceVariables]; + $this->definitionVariables = $this->referenceVariables = null; + + try { + if ($value instanceof ServiceClosureArgument) { + $value = $value->getValues()[0]; + $code = $this->dumpValue($value, $interpolate); + + $returnedType = ''; + if ($value instanceof TypedReference) { + $returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', str_replace(['|', '&'], ['|\\', '&\\'], $value->getType())); + } + + $attribute = ''; + if ($value instanceof Reference) { + $attribute = 'name: '.$this->dumpValue((string) $value, $interpolate); + + if ($this->container->hasDefinition($value) && ($class = $this->container->findDefinition($value)->getClass()) && $class !== (string) $value) { + $attribute .= ', class: '.$this->dumpValue($class, $interpolate); + } + + $attribute = sprintf('#[\Closure(%s)] ', $attribute); + } + + return sprintf('%sfn ()%s => %s', $attribute, $returnedType, $code); + } + + if ($value instanceof IteratorArgument) { + if (!$values = $value->getValues()) { + return 'new RewindableGenerator(fn () => new \EmptyIterator(), 0)'; + } + + $code = []; + $code[] = 'new RewindableGenerator(function () use ($container) {'; + + $operands = [0]; + foreach ($values as $k => $v) { + ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; + $v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate))); + foreach (explode("\n", $v) as $v) { + if ($v) { + $code[] = ' '.$v; + } + } + } + + $code[] = sprintf(' }, %s)', \count($operands) > 1 ? 'fn () => '.implode(' + ', $operands) : $operands[0]); + + return implode("\n", $code); + } + + if ($value instanceof ServiceLocatorArgument) { + $serviceMap = ''; + $serviceTypes = ''; + foreach ($value->getValues() as $k => $v) { + if (!$v instanceof Reference) { + $serviceMap .= sprintf("\n %s => [%s],", $this->export($k), $this->dumpValue($v)); + $serviceTypes .= sprintf("\n %s => '?',", $this->export($k)); + continue; + } + $id = (string) $v; + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + $definition = $this->container->getDefinition($id); + $load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e); + $serviceMap .= sprintf("\n %s => [%s, %s, %s, %s],", + $this->export($k), + $this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false), + $this->doExport($id), + $this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id) : null), + $this->export($load) + ); + $serviceTypes .= sprintf("\n %s => %s,", $this->export($k), $this->export($v instanceof TypedReference ? $v->getType() : '?')); + $this->locatedIds[$id] = true; + } + $this->addGetService = true; + + return sprintf('new \%s($container->getService ??= $container->getService(...), [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); + } + } finally { + [$this->definitionVariables, $this->referenceVariables] = $scope; + } + } elseif ($value instanceof Definition) { + if ($value->hasErrors() && $e = $value->getErrors()) { + return sprintf('throw new RuntimeException(%s)', $this->export(reset($e))); + } + if ($this->definitionVariables?->contains($value)) { + return $this->dumpValue($this->definitionVariables[$value], $interpolate); + } + if ($value->getMethodCalls()) { + throw new RuntimeException('Cannot dump definitions which have method calls.'); + } + if ($value->getProperties()) { + throw new RuntimeException('Cannot dump definitions which have properties.'); + } + if (null !== $value->getConfigurator()) { + throw new RuntimeException('Cannot dump definitions which have a configurator.'); + } + + return $this->addNewInstance($value); + } elseif ($value instanceof Variable) { + return '$'.$value; + } elseif ($value instanceof Reference) { + $id = (string) $value; + + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + + if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) { + return $this->dumpValue($this->referenceVariables[$id], $interpolate); + } + + return $this->getServiceCall($id, $value); + } elseif ($value instanceof Expression) { + return $this->getExpressionLanguage()->compile((string) $value, ['container' => 'container']); + } elseif ($value instanceof Parameter) { + return $this->dumpParameter($value); + } elseif (true === $interpolate && \is_string($value)) { + if (preg_match('/^%([^%]+)%$/', $value, $match)) { + // we do this to deal with non string values (Boolean, integer, ...) + // the preg_replace_callback converts them to strings + return $this->dumpParameter($match[1]); + } else { + $replaceParameters = fn ($match) => "'.".$this->dumpParameter($match[2]).".'"; + + $code = str_replace('%%', '%', preg_replace_callback('/(?export($value))); + + return $code; + } + } elseif ($value instanceof \UnitEnum) { + return sprintf('\%s::%s', $value::class, $value->name); + } elseif ($value instanceof AbstractArgument) { + throw new RuntimeException($value->getTextWithContext()); + } elseif (\is_object($value) || \is_resource($value)) { + throw new RuntimeException(sprintf('Unable to dump a service container if a parameter is an object or a resource, got "%s".', get_debug_type($value))); + } + + return $this->export($value); + } + + /** + * Dumps a string to a literal (aka PHP Code) class value. + * + * @throws RuntimeException + */ + private function dumpLiteralClass(string $class): string + { + if (str_contains($class, '$')) { + return sprintf('${($_ = %s) && false ?: "_"}', $class); + } + if (!str_starts_with($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s).', $class ?: 'n/a')); + } + + $class = substr(str_replace('\\\\', '\\', $class), 1, -1); + + return str_starts_with($class, '\\') ? $class : '\\'.$class; + } + + private function dumpParameter(string $name): string + { + if (!$this->container->hasParameter($name) || ($this->dynamicParameters[$name] ?? false)) { + return sprintf('$container->getParameter(%s)', $this->doExport($name)); + } + + $value = $this->container->getParameter($name); + $dumpedValue = $this->dumpValue($value, false); + + if (!$value || !\is_array($value)) { + return $dumpedValue; + } + + return sprintf('$container->parameters[%s]', $this->doExport($name)); + } + + private function getServiceCall(string $id, ?Reference $reference = null): string + { + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + + if ('service_container' === $id) { + return '$container'; + } + + if ($this->container->hasDefinition($id) && $definition = $this->container->getDefinition($id)) { + if ($definition->isSynthetic()) { + $code = sprintf('$container->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : ''); + } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { + $code = 'null'; + if (!$definition->isShared()) { + return $code; + } + } elseif ($this->isTrivialInstance($definition)) { + if ($definition->hasErrors() && $e = $definition->getErrors()) { + return sprintf('throw new RuntimeException(%s)', $this->export(reset($e))); + } + $code = $this->addNewInstance($definition, '', $id); + if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { + return sprintf('($container->%s[%s] ??= %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); + } + $code = "($code)"; + } else { + $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$container->load('%s')" : 'self::%s($container)'; + $code = sprintf($code, $this->generateMethodName($id)); + + if (!$definition->isShared()) { + $factory = sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + $code = sprintf('(isset(%s) ? %1$s($container) : %s)', $factory, $code); + } + } + if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { + $code = sprintf('($container->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); + } + + return $code; + } + if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { + return 'null'; + } + if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) { + $code = sprintf('$container->get(%s, ContainerInterface::NULL_ON_INVALID_REFERENCE)', $this->doExport($id)); + } else { + $code = sprintf('$container->get(%s)', $this->doExport($id)); + } + + return sprintf('($container->services[%s] ?? %s)', $this->doExport($id), $code); + } + + /** + * Initializes the method names map to avoid conflicts with the Container methods. + */ + private function initializeMethodNamesMap(string $class): void + { + $this->serviceIdToMethodNameMap = []; + $this->usedMethodNames = []; + + if ($reflectionClass = $this->container->getReflectionClass($class)) { + foreach ($reflectionClass->getMethods() as $method) { + $this->usedMethodNames[strtolower($method->getName())] = true; + } + } + } + + /** + * @throws InvalidArgumentException + */ + private function generateMethodName(string $id): string + { + if (isset($this->serviceIdToMethodNameMap[$id])) { + return $this->serviceIdToMethodNameMap[$id]; + } + + $i = strrpos($id, '\\'); + $name = Container::camelize(false !== $i && isset($id[1 + $i]) ? substr($id, 1 + $i) : $id); + $name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name); + $methodName = 'get'.$name.'Service'; + $suffix = 1; + + while (isset($this->usedMethodNames[strtolower($methodName)])) { + ++$suffix; + $methodName = 'get'.$name.$suffix.'Service'; + } + + $this->serviceIdToMethodNameMap[$id] = $methodName; + $this->usedMethodNames[strtolower($methodName)] = true; + + return $methodName; + } + + private function getNextVariableName(): string + { + $firstChars = self::FIRST_CHARS; + $firstCharsLength = \strlen($firstChars); + $nonFirstChars = self::NON_FIRST_CHARS; + $nonFirstCharsLength = \strlen($nonFirstChars); + + while (true) { + $name = ''; + $i = $this->variableCount; + + if ('' === $name) { + $name .= $firstChars[$i % $firstCharsLength]; + $i = (int) ($i / $firstCharsLength); + } + + while ($i > 0) { + --$i; + $name .= $nonFirstChars[$i % $nonFirstCharsLength]; + $i = (int) ($i / $nonFirstCharsLength); + } + + ++$this->variableCount; + + // check that the name is not reserved + if (\in_array($name, $this->reservedVariables, true)) { + continue; + } + + return $name; + } + } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (!isset($this->expressionLanguage)) { + if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { + throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + $providers = $this->container->getExpressionLanguageProviders(); + $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { + $id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null; + + if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) { + return $this->getServiceCall($id); + } + + return sprintf('$container->get(%s)', $arg); + }); + + if ($this->container->isTrackingResources()) { + foreach ($providers as $provider) { + $this->container->addObjectResource($provider); + } + } + } + + return $this->expressionLanguage; + } + + private function isHotPath(Definition $definition): bool + { + return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated(); + } + + private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool + { + if ($node->getValue()->isPublic()) { + return false; + } + $ids = []; + foreach ($node->getInEdges() as $edge) { + if (!$value = $edge->getSourceNode()->getValue()) { + continue; + } + if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) { + return false; + } + $ids[$edge->getSourceNode()->getId()] = true; + } + + return 1 === \count($ids); + } + + private function export(mixed $value): mixed + { + if (null !== $this->targetDirRegex && \is_string($value) && preg_match($this->targetDirRegex, $value, $matches, \PREG_OFFSET_CAPTURE)) { + $suffix = $matches[0][1] + \strlen($matches[0][0]); + $matches[0][1] += \strlen($matches[1][0]); + $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : ''; + + if ('\\' === \DIRECTORY_SEPARATOR && isset($value[$suffix])) { + $cookie = '\\'.random_int(100000, \PHP_INT_MAX); + $suffix = '.'.$this->doExport(str_replace('\\', $cookie, substr($value, $suffix)), true); + $suffix = str_replace('\\'.$cookie, "'.\\DIRECTORY_SEPARATOR.'", $suffix); + } else { + $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; + } + + $dirname = $this->asFiles ? '$container->containerDir' : '__DIR__'; + $offset = 2 + $this->targetDirMaxMatches - \count($matches); + + if (0 < $offset) { + $dirname = sprintf('\dirname(__DIR__, %d)', $offset + (int) $this->asFiles); + } elseif ($this->asFiles) { + $dirname = "\$container->targetDir.''"; // empty string concatenation on purpose + } + + if ($prefix || $suffix) { + return sprintf('(%s%s%s)', $prefix, $dirname, $suffix); + } + + return $dirname; + } + + return $this->doExport($value, true); + } + + private function doExport(mixed $value, bool $resolveEnv = false): mixed + { + $shouldCacheValue = $resolveEnv && \is_string($value); + if ($shouldCacheValue && isset($this->exportedVariables[$value])) { + return $this->exportedVariables[$value]; + } + if (\is_string($value) && str_contains($value, "\n")) { + $cleanParts = explode("\n", $value); + $cleanParts = array_map(fn ($part) => var_export($part, true), $cleanParts); + $export = implode('."\n".', $cleanParts); + } else { + $export = var_export($value, true); + } + + if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$container->getEnv('string:%s').'")) { + $export = $resolvedExport; + if (str_ends_with($export, ".''")) { + $export = substr($export, 0, -3); + if ("'" === $export[1]) { + $export = substr_replace($export, '', 23, 7); + } + } + if ("'" === $export[1]) { + $export = substr($export, 3); + } + } + + if ($shouldCacheValue) { + $this->exportedVariables[$value] = $export; + } + + return $export; + } + + private function getAutoloadFile(): ?string + { + $file = null; + + foreach (spl_autoload_functions() as $autoloader) { + if (!\is_array($autoloader)) { + continue; + } + + if ($autoloader[0] instanceof DebugClassLoader) { + $autoloader = $autoloader[0]->getClassLoader(); + } + + if (!\is_array($autoloader) || !$autoloader[0] instanceof ClassLoader || !$autoloader[0]->findFile(__CLASS__)) { + continue; + } + + foreach (get_declared_classes() as $class) { + if (str_starts_with($class, 'ComposerAutoloaderInit') && $class::getLoader() === $autoloader[0]) { + $file = \dirname((new \ReflectionClass($class))->getFileName(), 2).'/autoload.php'; + + if (null !== $this->targetDirRegex && preg_match($this->targetDirRegex.'A', $file)) { + return $file; + } + } + } + } + + return $file; + } + + private function getClasses(Definition $definition, string $id): array + { + $classes = []; + $resolve = $this->container->getParameterBag()->resolveValue(...); + + while ($definition instanceof Definition) { + foreach ($definition->getTag($this->preloadTags[0]) as $tag) { + if (!isset($tag['class'])) { + throw new InvalidArgumentException(sprintf('Missing attribute "class" on tag "%s" for service "%s".', $this->preloadTags[0], $id)); + } + + $classes[] = trim($tag['class'], '\\'); + } + + if ($class = $definition->getClass()) { + $classes[] = trim($resolve($class), '\\'); + } + $factory = $definition->getFactory(); + + if (!\is_array($factory)) { + $factory = [$factory]; + } + + if (\is_string($factory[0])) { + $factory[0] = $resolve($factory[0]); + + if (false !== $i = strrpos($factory[0], '::')) { + $factory[0] = substr($factory[0], 0, $i); + } + $classes[] = trim($factory[0], '\\'); + } + + $definition = $factory[0]; + } + + return $classes; + } + + private function isProxyCandidate(Definition $definition, ?bool &$asGhostObject, string $id): ?Definition + { + $asGhostObject = false; + + if (['Closure', 'fromCallable'] === $definition->getFactory()) { + return null; + } + + if (!$definition->isLazy() || !$this->hasProxyDumper) { + return null; + } + + return $this->getProxyDumper()->isProxyCandidate($definition, $asGhostObject, $id) ? $definition : null; + } + + /** + * Removes comments from a PHP source string. + * + * We don't use the PHP php_strip_whitespace() function + * as we want the content to be readable and well-formatted. + */ + private static function stripComments(string $source): string + { + if (!\function_exists('token_get_all')) { + return $source; + } + + $rawChunk = ''; + $output = ''; + $tokens = token_get_all($source); + $ignoreSpace = false; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1]) || 'b"' === $token) { + $rawChunk .= $token; + } elseif (\T_START_HEREDOC === $token[0]) { + $output .= $rawChunk.$token[1]; + do { + $token = $tokens[++$i]; + $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; + } while (\T_END_HEREDOC !== $token[0]); + $rawChunk = ''; + } elseif (\T_WHITESPACE === $token[0]) { + if ($ignoreSpace) { + $ignoreSpace = false; + + continue; + } + + // replace multiple new lines with a single newline + $rawChunk .= preg_replace(['/\n{2,}/S'], "\n", $token[1]); + } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { + if (!\in_array($rawChunk[\strlen($rawChunk) - 1], [' ', "\n", "\r", "\t"], true)) { + $rawChunk .= ' '; + } + $ignoreSpace = true; + } else { + $rawChunk .= $token[1]; + + // The PHP-open tag already has a new-line + if (\T_OPEN_TAG === $token[0]) { + $ignoreSpace = true; + } else { + $ignoreSpace = false; + } + } + } + + $output .= $rawChunk; + + unset($tokens, $rawChunk); + gc_mem_caches(); + + return $output; + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/Preloader.php b/vendor/symfony/dependency-injection/Dumper/Preloader.php new file mode 100644 index 0000000..8caa1de --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/Preloader.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +/** + * @author Nicolas Grekas + */ +final class Preloader +{ + public static function append(string $file, array $list): void + { + if (!file_exists($file)) { + throw new \LogicException(sprintf('File "%s" does not exist.', $file)); + } + + $cacheDir = \dirname($file); + $classes = []; + + foreach ($list as $item) { + if (str_starts_with($item, $cacheDir)) { + file_put_contents($file, sprintf("require_once __DIR__.%s;\n", var_export(strtr(substr($item, \strlen($cacheDir)), \DIRECTORY_SEPARATOR, '/'), true)), \FILE_APPEND); + continue; + } + + $classes[] = sprintf("\$classes[] = %s;\n", var_export($item, true)); + } + + file_put_contents($file, sprintf("\n\$classes = [];\n%s\$preloaded = Preloader::preload(\$classes, \$preloaded);\n", implode('', $classes)), \FILE_APPEND); + } + + public static function preload(array $classes, array $preloaded = []): array + { + set_error_handler(function ($t, $m, $f, $l) { + if (error_reporting() & $t) { + if (__FILE__ !== $f) { + throw new \ErrorException($m, 0, $t, $f, $l); + } + + throw new \ReflectionException($m); + } + }); + + $prev = []; + + try { + while ($prev !== $classes) { + $prev = $classes; + foreach ($classes as $c) { + if (!isset($preloaded[$c])) { + self::doPreload($c, $preloaded); + } + } + $classes = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); + } + } finally { + restore_error_handler(); + } + + return $preloaded; + } + + private static function doPreload(string $class, array &$preloaded): void + { + if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], true)) { + return; + } + + $preloaded[$class] = true; + + try { + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + return; + } + + $r = new \ReflectionClass($class); + + if ($r->isInternal()) { + return; + } + + $r->getConstants(); + $r->getDefaultProperties(); + + foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) { + self::preloadType($p->getType(), $preloaded); + } + + foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) { + foreach ($m->getParameters() as $p) { + if ($p->isDefaultValueAvailable() && $p->isDefaultValueConstant()) { + $c = $p->getDefaultValueConstantName(); + + if ($i = strpos($c, '::')) { + self::doPreload(substr($c, 0, $i), $preloaded); + } + } + + self::preloadType($p->getType(), $preloaded); + } + + self::preloadType($m->getReturnType(), $preloaded); + } + } catch (\Throwable) { + // ignore missing classes + } + } + + private static function preloadType(?\ReflectionType $t, array &$preloaded): void + { + if (!$t) { + return; + } + + foreach (($t instanceof \ReflectionUnionType || $t instanceof \ReflectionIntersectionType) ? $t->getTypes() : [$t] as $t) { + if (!$t->isBuiltin()) { + self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/XmlDumper.php b/vendor/symfony/dependency-injection/Dumper/XmlDumper.php new file mode 100644 index 0000000..6ae8d5c --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/XmlDumper.php @@ -0,0 +1,432 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * XmlDumper dumps a service container as an XML string. + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class XmlDumper extends Dumper +{ + private \DOMDocument $document; + + /** + * Dumps the service container as an XML string. + */ + public function dump(array $options = []): string + { + $this->document = new \DOMDocument('1.0', 'utf-8'); + $this->document->formatOutput = true; + + $container = $this->document->createElementNS('http://symfony.com/schema/dic/services', 'container'); + $container->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $container->setAttribute('xsi:schemaLocation', 'http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd'); + + $this->addParameters($container); + $this->addServices($container); + + $this->document->appendChild($container); + $xml = $this->document->saveXML(); + unset($this->document); + + return $this->container->resolveEnvPlaceholders($xml); + } + + private function addParameters(\DOMElement $parent): void + { + $data = $this->container->getParameterBag()->all(); + if (!$data) { + return; + } + + if ($this->container->isCompiled()) { + $data = $this->escape($data); + } + + $parameters = $this->document->createElement('parameters'); + $parent->appendChild($parameters); + $this->convertParameters($data, 'parameter', $parameters); + } + + private function addMethodCalls(array $methodcalls, \DOMElement $parent): void + { + foreach ($methodcalls as $methodcall) { + $call = $this->document->createElement('call'); + $call->setAttribute('method', $methodcall[0]); + if (\count($methodcall[1])) { + $this->convertParameters($methodcall[1], 'argument', $call); + } + if ($methodcall[2] ?? false) { + $call->setAttribute('returns-clone', 'true'); + } + $parent->appendChild($call); + } + } + + private function addService(Definition $definition, ?string $id, \DOMElement $parent): void + { + $service = $this->document->createElement('service'); + if (null !== $id) { + $service->setAttribute('id', $id); + } + if ($class = $definition->getClass()) { + if (str_starts_with($class, '\\')) { + $class = substr($class, 1); + } + + $service->setAttribute('class', $class); + } + if (!$definition->isShared()) { + $service->setAttribute('shared', 'false'); + } + if ($definition->isPublic()) { + $service->setAttribute('public', 'true'); + } + if ($definition->isSynthetic()) { + $service->setAttribute('synthetic', 'true'); + } + if ($definition->isLazy()) { + $service->setAttribute('lazy', 'true'); + } + if (null !== $decoratedService = $definition->getDecoratedService()) { + [$decorated, $renamedId, $priority] = $decoratedService; + $service->setAttribute('decorates', $decorated); + + $decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE], true)) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore'; + $service->setAttribute('decoration-on-invalid', $invalidBehavior); + } + if (null !== $renamedId) { + $service->setAttribute('decoration-inner-name', $renamedId); + } + if (0 !== $priority) { + $service->setAttribute('decoration-priority', $priority); + } + } + + $tags = $definition->getTags(); + $tags['container.error'] = array_map(fn ($e) => ['message' => $e], $definition->getErrors()); + foreach ($tags as $name => $tags) { + foreach ($tags as $attributes) { + $tag = $this->document->createElement('tag'); + + // Check if we have recursive attributes + if (array_filter($attributes, \is_array(...))) { + $tag->setAttribute('name', $name); + $this->addTagRecursiveAttributes($tag, $attributes); + } else { + if (!\array_key_exists('name', $attributes)) { + $tag->setAttribute('name', $name); + } else { + $tag->appendChild($this->document->createTextNode($name)); + } + foreach ($attributes as $key => $value) { + $tag->setAttribute($key, $value ?? ''); + } + } + $service->appendChild($tag); + } + } + + if ($definition->getFile()) { + $file = $this->document->createElement('file'); + $file->appendChild($this->document->createTextNode($definition->getFile())); + $service->appendChild($file); + } + + if ($parameters = $definition->getArguments()) { + $this->convertParameters($parameters, 'argument', $service); + } + + if ($parameters = $definition->getProperties()) { + $this->convertParameters($parameters, 'property', $service, 'name'); + } + + $this->addMethodCalls($definition->getMethodCalls(), $service); + + if ($callable = $definition->getFactory()) { + if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) { + $service->setAttribute('constructor', $callable[1]); + } else { + $factory = $this->document->createElement('factory'); + + if (\is_array($callable) && $callable[0] instanceof Definition) { + $this->addService($callable[0], null, $factory); + $factory->setAttribute('method', $callable[1]); + } elseif (\is_array($callable)) { + if (null !== $callable[0]) { + $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); + } + $factory->setAttribute('method', $callable[1]); + } else { + $factory->setAttribute('function', $callable); + } + $service->appendChild($factory); + } + } + + if ($definition->isDeprecated()) { + $deprecation = $definition->getDeprecation('%service_id%'); + $deprecated = $this->document->createElement('deprecated'); + $deprecated->appendChild($this->document->createTextNode($definition->getDeprecation('%service_id%')['message'])); + $deprecated->setAttribute('package', $deprecation['package']); + $deprecated->setAttribute('version', $deprecation['version']); + + $service->appendChild($deprecated); + } + + if ($definition->isAutowired()) { + $service->setAttribute('autowire', 'true'); + } + + if ($definition->isAutoconfigured()) { + $service->setAttribute('autoconfigure', 'true'); + } + + if ($definition->isAbstract()) { + $service->setAttribute('abstract', 'true'); + } + + if ($callable = $definition->getConfigurator()) { + $configurator = $this->document->createElement('configurator'); + + if (\is_array($callable) && $callable[0] instanceof Definition) { + $this->addService($callable[0], null, $configurator); + $configurator->setAttribute('method', $callable[1]); + } elseif (\is_array($callable)) { + $configurator->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); + $configurator->setAttribute('method', $callable[1]); + } else { + $configurator->setAttribute('function', $callable); + } + $service->appendChild($configurator); + } + + $parent->appendChild($service); + } + + private function addServiceAlias(string $alias, Alias $id, \DOMElement $parent): void + { + $service = $this->document->createElement('service'); + $service->setAttribute('id', $alias); + $service->setAttribute('alias', $id); + if ($id->isPublic()) { + $service->setAttribute('public', 'true'); + } + + if ($id->isDeprecated()) { + $deprecation = $id->getDeprecation('%alias_id%'); + $deprecated = $this->document->createElement('deprecated'); + $deprecated->appendChild($this->document->createTextNode($deprecation['message'])); + $deprecated->setAttribute('package', $deprecation['package']); + $deprecated->setAttribute('version', $deprecation['version']); + + $service->appendChild($deprecated); + } + + $parent->appendChild($service); + } + + private function addServices(\DOMElement $parent): void + { + $definitions = $this->container->getDefinitions(); + if (!$definitions) { + return; + } + + $services = $this->document->createElement('services'); + foreach ($definitions as $id => $definition) { + $this->addService($definition, $id, $services); + } + + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $id) { + while (isset($aliases[(string) $id])) { + $id = $aliases[(string) $id]; + } + $this->addServiceAlias($alias, $id, $services); + } + $parent->appendChild($services); + } + + private function addTagRecursiveAttributes(\DOMElement $parent, array $attributes): void + { + foreach ($attributes as $name => $value) { + $attribute = $this->document->createElement('attribute'); + $attribute->setAttribute('name', $name); + + if (\is_array($value)) { + $this->addTagRecursiveAttributes($attribute, $value); + } else { + $attribute->appendChild($this->document->createTextNode($value)); + } + + $parent->appendChild($attribute); + } + } + + private function convertParameters(array $parameters, string $type, \DOMElement $parent, string $keyAttribute = 'key'): void + { + $withKeys = !array_is_list($parameters); + foreach ($parameters as $key => $value) { + $element = $this->document->createElement($type); + if ($withKeys) { + $element->setAttribute($keyAttribute, $key); + } + + if (\is_array($tag = $value)) { + $element->setAttribute('type', 'collection'); + $this->convertParameters($value, $type, $element, 'key'); + } elseif ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) { + $element->setAttribute('type', $value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator'); + $element->setAttribute('tag', $tag->getTag()); + + if (null !== $tag->getIndexAttribute()) { + $element->setAttribute('index-by', $tag->getIndexAttribute()); + + if (null !== $tag->getDefaultIndexMethod()) { + $element->setAttribute('default-index-method', $tag->getDefaultIndexMethod()); + } + if (null !== $tag->getDefaultPriorityMethod()) { + $element->setAttribute('default-priority-method', $tag->getDefaultPriorityMethod()); + } + } + if ($excludes = $tag->getExclude()) { + if (1 === \count($excludes)) { + $element->setAttribute('exclude', $excludes[0]); + } else { + foreach ($excludes as $exclude) { + $element->appendChild($this->document->createElement('exclude', $exclude)); + } + } + } + if (!$tag->excludeSelf()) { + $element->setAttribute('exclude-self', 'false'); + } + } elseif ($value instanceof IteratorArgument) { + $element->setAttribute('type', 'iterator'); + $this->convertParameters($value->getValues(), $type, $element, 'key'); + } elseif ($value instanceof ServiceLocatorArgument) { + $element->setAttribute('type', 'service_locator'); + $this->convertParameters($value->getValues(), $type, $element, 'key'); + } elseif ($value instanceof ServiceClosureArgument && !$value->getValues()[0] instanceof Reference) { + $element->setAttribute('type', 'service_closure'); + $this->convertParameters($value->getValues(), $type, $element, 'key'); + } elseif ($value instanceof Reference || $value instanceof ServiceClosureArgument) { + $element->setAttribute('type', 'service'); + if ($value instanceof ServiceClosureArgument) { + $element->setAttribute('type', 'service_closure'); + $value = $value->getValues()[0]; + } + $element->setAttribute('id', (string) $value); + $behavior = $value->getInvalidBehavior(); + if (ContainerInterface::NULL_ON_INVALID_REFERENCE == $behavior) { + $element->setAttribute('on-invalid', 'null'); + } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE == $behavior) { + $element->setAttribute('on-invalid', 'ignore'); + } elseif (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE == $behavior) { + $element->setAttribute('on-invalid', 'ignore_uninitialized'); + } + } elseif ($value instanceof Definition) { + $element->setAttribute('type', 'service'); + $this->addService($value, null, $element); + } elseif ($value instanceof Expression) { + $element->setAttribute('type', 'expression'); + $text = $this->document->createTextNode(self::phpToXml((string) $value)); + $element->appendChild($text); + } elseif (\is_string($value) && !preg_match('/^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*+$/u', $value)) { + $element->setAttribute('type', 'binary'); + $text = $this->document->createTextNode(self::phpToXml(base64_encode($value))); + $element->appendChild($text); + } elseif ($value instanceof \UnitEnum) { + $element->setAttribute('type', 'constant'); + $element->appendChild($this->document->createTextNode(self::phpToXml($value))); + } elseif ($value instanceof AbstractArgument) { + $element->setAttribute('type', 'abstract'); + $text = $this->document->createTextNode(self::phpToXml($value->getText())); + $element->appendChild($text); + } else { + if (\in_array($value, ['null', 'true', 'false'], true)) { + $element->setAttribute('type', 'string'); + } + + if (\is_string($value) && (is_numeric($value) || preg_match('/^0b[01]*$/', $value) || preg_match('/^0x[0-9a-f]++$/i', $value))) { + $element->setAttribute('type', 'string'); + } + + $text = $this->document->createTextNode(self::phpToXml($value)); + $element->appendChild($text); + } + $parent->appendChild($element); + } + } + + /** + * Escapes arguments. + */ + private function escape(array $arguments): array + { + $args = []; + foreach ($arguments as $k => $v) { + if (\is_array($v)) { + $args[$k] = $this->escape($v); + } elseif (\is_string($v)) { + $args[$k] = str_replace('%', '%%', $v); + } else { + $args[$k] = $v; + } + } + + return $args; + } + + /** + * Converts php types to xml types. + * + * @throws RuntimeException When trying to dump object or resource + */ + public static function phpToXml(mixed $value): string + { + switch (true) { + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case $value instanceof Parameter: + return '%'.$value.'%'; + case $value instanceof \UnitEnum: + return sprintf('%s::%s', $value::class, $value->name); + case \is_object($value) || \is_resource($value): + throw new RuntimeException(sprintf('Unable to dump a service container if a parameter is an object or a resource, got "%s".', get_debug_type($value))); + default: + return (string) $value; + } + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/YamlDumper.php b/vendor/symfony/dependency-injection/Dumper/YamlDumper.php new file mode 100644 index 0000000..fac33bc --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/YamlDumper.php @@ -0,0 +1,380 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Yaml\Dumper as YmlDumper; +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Tag\TaggedValue; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlDumper dumps a service container as a YAML string. + * + * @author Fabien Potencier + */ +class YamlDumper extends Dumper +{ + private YmlDumper $dumper; + + /** + * Dumps the service container as an YAML string. + */ + public function dump(array $options = []): string + { + if (!class_exists(YmlDumper::class)) { + throw new LogicException('Unable to dump the container as the Symfony Yaml Component is not installed. Try running "composer require symfony/yaml".'); + } + + $this->dumper ??= new YmlDumper(); + + return $this->container->resolveEnvPlaceholders($this->addParameters()."\n".$this->addServices()); + } + + private function addService(string $id, Definition $definition): string + { + $code = " $id:\n"; + if ($class = $definition->getClass()) { + if (str_starts_with($class, '\\')) { + $class = substr($class, 1); + } + + $code .= sprintf(" class: %s\n", $this->dumper->dump($class)); + } + + if (!$definition->isPrivate()) { + $code .= sprintf(" public: %s\n", $definition->isPublic() ? 'true' : 'false'); + } + + $tagsCode = ''; + $tags = $definition->getTags(); + $tags['container.error'] = array_map(fn ($e) => ['message' => $e], $definition->getErrors()); + foreach ($tags as $name => $tags) { + foreach ($tags as $attributes) { + $att = []; + foreach ($attributes as $key => $value) { + $att[] = sprintf('%s: %s', $this->dumper->dump($key), $this->dumper->dump($value)); + } + $att = $att ? ': { '.implode(', ', $att).' }' : ''; + + $tagsCode .= sprintf(" - %s%s\n", $this->dumper->dump($name), $att); + } + } + if ($tagsCode) { + $code .= " tags:\n".$tagsCode; + } + + if ($definition->getFile()) { + $code .= sprintf(" file: %s\n", $this->dumper->dump($definition->getFile())); + } + + if ($definition->isSynthetic()) { + $code .= " synthetic: true\n"; + } + + if ($definition->isDeprecated()) { + $code .= " deprecated:\n"; + foreach ($definition->getDeprecation('%service_id%') as $key => $value) { + if ('' !== $value) { + $code .= sprintf(" %s: %s\n", $key, $this->dumper->dump($value)); + } + } + } + + if ($definition->isAutowired()) { + $code .= " autowire: true\n"; + } + + if ($definition->isAutoconfigured()) { + $code .= " autoconfigure: true\n"; + } + + if ($definition->isAbstract()) { + $code .= " abstract: true\n"; + } + + if ($definition->isLazy()) { + $code .= " lazy: true\n"; + } + + if ($definition->getArguments()) { + $code .= sprintf(" arguments: %s\n", $this->dumper->dump($this->dumpValue($definition->getArguments()), 0)); + } + + if ($definition->getProperties()) { + $code .= sprintf(" properties: %s\n", $this->dumper->dump($this->dumpValue($definition->getProperties()), 0)); + } + + if ($definition->getMethodCalls()) { + $code .= sprintf(" calls:\n%s\n", $this->dumper->dump($this->dumpValue($definition->getMethodCalls()), 1, 12)); + } + + if (!$definition->isShared()) { + $code .= " shared: false\n"; + } + + if (null !== $decoratedService = $definition->getDecoratedService()) { + [$decorated, $renamedId, $priority] = $decoratedService; + $code .= sprintf(" decorates: %s\n", $decorated); + if (null !== $renamedId) { + $code .= sprintf(" decoration_inner_name: %s\n", $renamedId); + } + if (0 !== $priority) { + $code .= sprintf(" decoration_priority: %s\n", $priority); + } + + $decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE])) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore'; + $code .= sprintf(" decoration_on_invalid: %s\n", $invalidBehavior); + } + } + + if ($callable = $definition->getFactory()) { + if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) { + $code .= sprintf(" constructor: %s\n", $callable[1]); + } else { + $code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + } + } + + if ($callable = $definition->getConfigurator()) { + $code .= sprintf(" configurator: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + } + + return $code; + } + + private function addServiceAlias(string $alias, Alias $id): string + { + $deprecated = ''; + + if ($id->isDeprecated()) { + $deprecated = " deprecated:\n"; + + foreach ($id->getDeprecation('%alias_id%') as $key => $value) { + if ('' !== $value) { + $deprecated .= sprintf(" %s: %s\n", $key, $value); + } + } + } + + if (!$id->isDeprecated() && $id->isPrivate()) { + return sprintf(" %s: '@%s'\n", $alias, $id); + } + + if ($id->isPublic()) { + $deprecated = " public: true\n".$deprecated; + } + + return sprintf(" %s:\n alias: %s\n%s", $alias, $id, $deprecated); + } + + private function addServices(): string + { + if (!$this->container->getDefinitions()) { + return ''; + } + + $code = "services:\n"; + foreach ($this->container->getDefinitions() as $id => $definition) { + $code .= $this->addService($id, $definition); + } + + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $id) { + while (isset($aliases[(string) $id])) { + $id = $aliases[(string) $id]; + } + $code .= $this->addServiceAlias($alias, $id); + } + + return $code; + } + + private function addParameters(): string + { + if (!$this->container->getParameterBag()->all()) { + return ''; + } + + $parameters = $this->prepareParameters($this->container->getParameterBag()->all(), $this->container->isCompiled()); + + return $this->dumper->dump(['parameters' => $parameters], 2); + } + + /** + * Dumps callable to YAML format. + */ + private function dumpCallable(mixed $callable): mixed + { + if (\is_array($callable)) { + if ($callable[0] instanceof Reference) { + $callable = [$this->getServiceCall((string) $callable[0], $callable[0]), $callable[1]]; + } else { + $callable = [$callable[0], $callable[1]]; + } + } + + return $callable; + } + + /** + * Dumps the value to YAML format. + * + * @throws RuntimeException When trying to dump object or resource + */ + private function dumpValue(mixed $value): mixed + { + if ($value instanceof ServiceClosureArgument) { + $value = $value->getValues()[0]; + + return new TaggedValue('service_closure', $this->dumpValue($value)); + } + if ($value instanceof ArgumentInterface) { + $tag = $value; + + if ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) { + if (null === $tag->getIndexAttribute()) { + $content = $tag->getTag(); + } else { + $content = [ + 'tag' => $tag->getTag(), + 'index_by' => $tag->getIndexAttribute(), + ]; + + if (null !== $tag->getDefaultIndexMethod()) { + $content['default_index_method'] = $tag->getDefaultIndexMethod(); + } + if (null !== $tag->getDefaultPriorityMethod()) { + $content['default_priority_method'] = $tag->getDefaultPriorityMethod(); + } + } + if ($excludes = $tag->getExclude()) { + if (!\is_array($content)) { + $content = ['tag' => $content]; + } + $content['exclude'] = 1 === \count($excludes) ? $excludes[0] : $excludes; + } + if (!$tag->excludeSelf()) { + $content['exclude_self'] = false; + } + + return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator', $content); + } + + if ($value instanceof IteratorArgument) { + $tag = 'iterator'; + } elseif ($value instanceof ServiceLocatorArgument) { + $tag = 'service_locator'; + } else { + throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_debug_type($value))); + } + + return new TaggedValue($tag, $this->dumpValue($value->getValues())); + } + + if (\is_array($value)) { + $code = []; + foreach ($value as $k => $v) { + $code[$k] = $this->dumpValue($v); + } + + return $code; + } elseif ($value instanceof Reference) { + return $this->getServiceCall((string) $value, $value); + } elseif ($value instanceof Parameter) { + return $this->getParameterCall((string) $value); + } elseif ($value instanceof Expression) { + return $this->getExpressionCall((string) $value); + } elseif ($value instanceof Definition) { + return new TaggedValue('service', (new Parser())->parse("_:\n".$this->addService('_', $value), Yaml::PARSE_CUSTOM_TAGS)['_']['_']); + } elseif ($value instanceof \UnitEnum) { + return new TaggedValue('php/enum', sprintf('%s::%s', $value::class, $value->name)); + } elseif ($value instanceof AbstractArgument) { + return new TaggedValue('abstract', $value->getText()); + } elseif (\is_object($value) || \is_resource($value)) { + throw new RuntimeException(sprintf('Unable to dump a service container if a parameter is an object or a resource, got "%s".', get_debug_type($value))); + } + + return $value; + } + + private function getServiceCall(string $id, ?Reference $reference = null): string + { + if (null !== $reference) { + switch ($reference->getInvalidBehavior()) { + case ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: break; + case ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: break; + case ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: return sprintf('@!%s', $id); + default: return sprintf('@?%s', $id); + } + } + + return sprintf('@%s', $id); + } + + private function getParameterCall(string $id): string + { + return sprintf('%%%s%%', $id); + } + + private function getExpressionCall(string $expression): string + { + return sprintf('@=%s', $expression); + } + + private function prepareParameters(array $parameters, bool $escape = true): array + { + $filtered = []; + foreach ($parameters as $key => $value) { + if (\is_array($value)) { + $value = $this->prepareParameters($value, $escape); + } elseif ($value instanceof Reference || \is_string($value) && str_starts_with($value, '@')) { + $value = '@'.$value; + } + + $filtered[$key] = $value; + } + + return $escape ? $this->escape($filtered) : $filtered; + } + + private function escape(array $arguments): array + { + $args = []; + foreach ($arguments as $k => $v) { + if (\is_array($v)) { + $args[$k] = $this->escape($v); + } elseif (\is_string($v)) { + $args[$k] = str_replace('%', '%%', $v); + } else { + $args[$k] = $v; + } + } + + return $args; + } +} diff --git a/vendor/symfony/dependency-injection/EnvVarLoaderInterface.php b/vendor/symfony/dependency-injection/EnvVarLoaderInterface.php new file mode 100644 index 0000000..803156b --- /dev/null +++ b/vendor/symfony/dependency-injection/EnvVarLoaderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * EnvVarLoaderInterface objects return key/value pairs that are added to the list of available env vars. + * + * @author Nicolas Grekas + */ +interface EnvVarLoaderInterface +{ + /** + * @return array Key/value pairs that can be accessed using the regular "%env()%" syntax + */ + public function loadEnvVars(): array; +} diff --git a/vendor/symfony/dependency-injection/EnvVarProcessor.php b/vendor/symfony/dependency-injection/EnvVarProcessor.php new file mode 100644 index 0000000..20b1d25 --- /dev/null +++ b/vendor/symfony/dependency-injection/EnvVarProcessor.php @@ -0,0 +1,378 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; +use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Nicolas Grekas + */ +class EnvVarProcessor implements EnvVarProcessorInterface, ResetInterface +{ + private ContainerInterface $container; + /** @var \Traversable */ + private \Traversable $loaders; + /** @var \Traversable */ + private \Traversable $originalLoaders; + private array $loadedVars = []; + + /** + * @param \Traversable|null $loaders + */ + public function __construct(ContainerInterface $container, ?\Traversable $loaders = null) + { + $this->container = $container; + $this->originalLoaders = $this->loaders = $loaders ?? new \ArrayIterator(); + } + + public static function getProvidedTypes(): array + { + return [ + 'base64' => 'string', + 'bool' => 'bool', + 'not' => 'bool', + 'const' => 'bool|int|float|string|array', + 'csv' => 'array', + 'file' => 'string', + 'float' => 'float', + 'int' => 'int', + 'json' => 'array', + 'key' => 'bool|int|float|string|array', + 'url' => 'array', + 'query_string' => 'array', + 'resolve' => 'string', + 'default' => 'bool|int|float|string|array', + 'string' => 'string', + 'trim' => 'string', + 'require' => 'bool|int|float|string|array', + 'enum' => \BackedEnum::class, + 'shuffle' => 'array', + 'defined' => 'bool', + 'urlencode' => 'string', + ]; + } + + public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed + { + $i = strpos($name, ':'); + + if ('key' === $prefix) { + if (false === $i) { + throw new RuntimeException(sprintf('Invalid env "key:%s": a key specifier should be provided.', $name)); + } + + $next = substr($name, $i + 1); + $key = substr($name, 0, $i); + $array = $getEnv($next); + + if (!\is_array($array)) { + throw new RuntimeException(sprintf('Resolved value of "%s" did not result in an array value.', $next)); + } + + if (!isset($array[$key]) && !\array_key_exists($key, $array)) { + throw new EnvNotFoundException(sprintf('Key "%s" not found in %s (resolved from "%s").', $key, json_encode($array), $next)); + } + + return $array[$key]; + } + + if ('enum' === $prefix) { + if (false === $i) { + throw new RuntimeException(sprintf('Invalid env "enum:%s": a "%s" class-string should be provided.', $name, \BackedEnum::class)); + } + + $next = substr($name, $i + 1); + $backedEnumClassName = substr($name, 0, $i); + $backedEnumValue = $getEnv($next); + + if (!\is_string($backedEnumValue) && !\is_int($backedEnumValue)) { + throw new RuntimeException(sprintf('Resolved value of "%s" did not result in a string or int value.', $next)); + } + + if (!is_subclass_of($backedEnumClassName, \BackedEnum::class)) { + throw new RuntimeException(sprintf('"%s" is not a "%s".', $backedEnumClassName, \BackedEnum::class)); + } + + return $backedEnumClassName::tryFrom($backedEnumValue) ?? throw new RuntimeException(sprintf('Enum value "%s" is not backed by "%s".', $backedEnumValue, $backedEnumClassName)); + } + + if ('defined' === $prefix) { + try { + return '' !== ($getEnv($name) ?? ''); + } catch (EnvNotFoundException) { + return false; + } + } + + if ('default' === $prefix) { + if (false === $i) { + throw new RuntimeException(sprintf('Invalid env "default:%s": a fallback parameter should be provided.', $name)); + } + + $next = substr($name, $i + 1); + $default = substr($name, 0, $i); + + if ('' !== $default && !$this->container->hasParameter($default)) { + throw new RuntimeException(sprintf('Invalid env fallback in "default:%s": parameter "%s" not found.', $name, $default)); + } + + try { + $env = $getEnv($next); + + if ('' !== $env && null !== $env) { + return $env; + } + } catch (EnvNotFoundException) { + // no-op + } + + return '' === $default ? null : $this->container->getParameter($default); + } + + if ('file' === $prefix || 'require' === $prefix) { + if (!\is_scalar($file = $getEnv($name))) { + throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name)); + } + if (!is_file($file)) { + throw new EnvNotFoundException(sprintf('File "%s" not found (resolved from "%s").', $file, $name)); + } + + if ('file' === $prefix) { + return file_get_contents($file); + } else { + return require $file; + } + } + + $returnNull = false; + if ('' === $prefix) { + if ('' === $name) { + return null; + } + $returnNull = true; + $prefix = 'string'; + } + + if (false !== $i || 'string' !== $prefix) { + $env = $getEnv($name); + } elseif ('' === ($env = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null))) + || (false !== $env && false === $env ??= getenv($name) ?? false) // null is a possible value because of thread safety issues + ) { + foreach ($this->loadedVars as $i => $vars) { + if (false === $env = $vars[$name] ?? $env) { + continue; + } + if ($env instanceof \Stringable) { + $this->loadedVars[$i][$name] = $env = (string) $env; + } + if ('' !== ($env ?? '')) { + break; + } + } + + if (false === $env || '' === $env) { + $loaders = $this->loaders; + $this->loaders = new \ArrayIterator(); + + try { + $i = 0; + $ended = true; + $count = $loaders instanceof \Countable ? $loaders->count() : 0; + foreach ($loaders as $loader) { + if (\count($this->loadedVars) > $i++) { + continue; + } + $this->loadedVars[] = $vars = $loader->loadEnvVars(); + if (false === $env = $vars[$name] ?? $env) { + continue; + } + if ($env instanceof \Stringable) { + $this->loadedVars[array_key_last($this->loadedVars)][$name] = $env = (string) $env; + } + if ('' !== ($env ?? '')) { + $ended = false; + break; + } + } + if ($ended || $count === $i) { + $loaders = $this->loaders; + } + } catch (ParameterCircularReferenceException) { + // skip loaders that need an env var that is not defined + } finally { + $this->loaders = $loaders; + } + } + + if (false === $env) { + if (!$this->container->hasParameter("env($name)")) { + throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name)); + } + + $env = $this->container->getParameter("env($name)"); + } + } + + if (null === $env) { + if ($returnNull) { + return null; + } + + if (!isset($this->getProvidedTypes()[$prefix])) { + throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix)); + } + + if (!\in_array($prefix, ['string', 'bool', 'not', 'int', 'float'], true)) { + return null; + } + } + + if ('shuffle' === $prefix) { + \is_array($env) ? shuffle($env) : throw new RuntimeException(sprintf('Env var "%s" cannot be shuffled, expected array, got "%s".', $name, get_debug_type($env))); + + return $env; + } + + if (null !== $env && !\is_scalar($env)) { + throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix)); + } + + if ('string' === $prefix) { + return (string) $env; + } + + if (\in_array($prefix, ['bool', 'not'], true)) { + $env = (bool) (filter_var($env, \FILTER_VALIDATE_BOOL) ?: filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)); + + return 'not' === $prefix xor $env; + } + + if ('int' === $prefix) { + if (null !== $env && false === $env = filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)) { + throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to int.', $name)); + } + + return (int) $env; + } + + if ('float' === $prefix) { + if (null !== $env && false === $env = filter_var($env, \FILTER_VALIDATE_FLOAT)) { + throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to float.', $name)); + } + + return (float) $env; + } + + if ('const' === $prefix) { + if (!\defined($env)) { + throw new RuntimeException(sprintf('Env var "%s" maps to undefined constant "%s".', $name, $env)); + } + + return \constant($env); + } + + if ('base64' === $prefix) { + return base64_decode(strtr($env, '-_', '+/')); + } + + if ('json' === $prefix) { + $env = json_decode($env, true); + + if (\JSON_ERROR_NONE !== json_last_error()) { + throw new RuntimeException(sprintf('Invalid JSON in env var "%s": ', $name).json_last_error_msg()); + } + + if (null !== $env && !\is_array($env)) { + throw new RuntimeException(sprintf('Invalid JSON env var "%s": array or null expected, "%s" given.', $name, get_debug_type($env))); + } + + return $env; + } + + if ('url' === $prefix) { + $params = parse_url($env); + + if (false === $params) { + throw new RuntimeException(sprintf('Invalid URL in env var "%s".', $name)); + } + if (!isset($params['scheme'], $params['host'])) { + throw new RuntimeException(sprintf('Invalid URL env var "%s": schema and host expected, "%s" given.', $name, $env)); + } + $params += [ + 'port' => null, + 'user' => null, + 'pass' => null, + 'path' => null, + 'query' => null, + 'fragment' => null, + ]; + + $params['user'] = null !== $params['user'] ? rawurldecode($params['user']) : null; + $params['pass'] = null !== $params['pass'] ? rawurldecode($params['pass']) : null; + + // remove the '/' separator + $params['path'] = '/' === ($params['path'] ?? '/') ? '' : substr($params['path'], 1); + + return $params; + } + + if ('query_string' === $prefix) { + $queryString = parse_url($env, \PHP_URL_QUERY) ?: $env; + parse_str($queryString, $result); + + return $result; + } + + if ('resolve' === $prefix) { + return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($name, $getEnv) { + if (!isset($match[1])) { + return '%'; + } + + if (str_starts_with($match[1], 'env(') && str_ends_with($match[1], ')') && 'env()' !== $match[1]) { + $value = $getEnv(substr($match[1], 4, -1)); + } else { + $value = $this->container->getParameter($match[1]); + } + + if (!\is_scalar($value)) { + throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, get_debug_type($value))); + } + + return $value; + }, $env); + } + + if ('csv' === $prefix) { + return '' === $env ? [] : str_getcsv($env, ',', '"', ''); + } + + if ('trim' === $prefix) { + return trim($env); + } + + if ('urlencode' === $prefix) { + return rawurlencode($env); + } + + throw new RuntimeException(sprintf('Unsupported env var prefix "%s" for env name "%s".', $prefix, $name)); + } + + public function reset(): void + { + $this->loadedVars = []; + $this->loaders = $this->originalLoaders; + } +} diff --git a/vendor/symfony/dependency-injection/EnvVarProcessorInterface.php b/vendor/symfony/dependency-injection/EnvVarProcessorInterface.php new file mode 100644 index 0000000..3cda639 --- /dev/null +++ b/vendor/symfony/dependency-injection/EnvVarProcessorInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * The EnvVarProcessorInterface is implemented by objects that manage environment-like variables. + * + * @author Nicolas Grekas + */ +interface EnvVarProcessorInterface +{ + /** + * Returns the value of the given variable as managed by the current instance. + * + * @param string $prefix The namespace of the variable; when the empty string is passed, null values should be kept as is + * @param string $name The name of the variable within the namespace + * @param \Closure(string): mixed $getEnv A closure that allows fetching more env vars + * + * @throws RuntimeException on error + */ + public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed; + + /** + * @return array The PHP-types managed by getEnv(), keyed by prefixes + */ + public static function getProvidedTypes(): array; +} diff --git a/vendor/symfony/dependency-injection/Exception/AutoconfigureFailedException.php b/vendor/symfony/dependency-injection/Exception/AutoconfigureFailedException.php new file mode 100644 index 0000000..f7ce978 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/AutoconfigureFailedException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +class AutoconfigureFailedException extends AutowiringFailedException +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/AutowiringFailedException.php b/vendor/symfony/dependency-injection/Exception/AutowiringFailedException.php new file mode 100644 index 0000000..53e05ce --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/AutowiringFailedException.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Thrown when a definition cannot be autowired. + */ +class AutowiringFailedException extends RuntimeException +{ + private string $serviceId; + private ?\Closure $messageCallback = null; + + public function __construct(string $serviceId, string|\Closure $message = '', int $code = 0, ?\Throwable $previous = null) + { + $this->serviceId = $serviceId; + + if ($message instanceof \Closure && \function_exists('xdebug_is_enabled') && xdebug_is_enabled()) { + $message = $message(); + } + + if (!$message instanceof \Closure) { + parent::__construct($message, $code, $previous); + + return; + } + + $this->messageCallback = $message; + parent::__construct('', $code, $previous); + + $this->message = new class($this->message, $this->messageCallback) { + private string|self $message; + private ?\Closure $messageCallback; + + public function __construct(&$message, &$messageCallback) + { + $this->message = &$message; + $this->messageCallback = &$messageCallback; + } + + public function __toString(): string + { + $messageCallback = $this->messageCallback; + $this->messageCallback = null; + + try { + return $this->message = $messageCallback(); + } catch (\Throwable $e) { + return $this->message = $e->getMessage(); + } + } + }; + } + + public function getMessageCallback(): ?\Closure + { + return $this->messageCallback; + } + + public function getServiceId(): string + { + return $this->serviceId; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/BadMethodCallException.php b/vendor/symfony/dependency-injection/Exception/BadMethodCallException.php new file mode 100644 index 0000000..959238e --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/BadMethodCallException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base BadMethodCallException for Dependency Injection component. + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/EnvNotFoundException.php b/vendor/symfony/dependency-injection/Exception/EnvNotFoundException.php new file mode 100644 index 0000000..04ac848 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/EnvNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when an environment variable is not found. + * + * @author Nicolas Grekas + */ +class EnvNotFoundException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/EnvParameterException.php b/vendor/symfony/dependency-injection/Exception/EnvParameterException.php new file mode 100644 index 0000000..6cd53c9 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/EnvParameterException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception wraps exceptions whose messages contain a reference to an env parameter. + * + * @author Nicolas Grekas + */ +class EnvParameterException extends InvalidArgumentException +{ + public function __construct(array $envs, ?\Throwable $previous = null, string $message = 'Incompatible use of dynamic environment variables "%s" found in parameters.') + { + parent::__construct(sprintf($message, implode('", "', $envs)), 0, $previous); + } +} diff --git a/vendor/symfony/dependency-injection/Exception/ExceptionInterface.php b/vendor/symfony/dependency-injection/Exception/ExceptionInterface.php new file mode 100644 index 0000000..6202df7 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ExceptionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +use Psr\Container\ContainerExceptionInterface; + +/** + * Base ExceptionInterface for Dependency Injection component. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +interface ExceptionInterface extends ContainerExceptionInterface, \Throwable +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/InvalidArgumentException.php b/vendor/symfony/dependency-injection/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..119bb7d --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base InvalidArgumentException for Dependency Injection component. + * + * @author Bulat Shakirzyanov + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/InvalidParameterTypeException.php b/vendor/symfony/dependency-injection/Exception/InvalidParameterTypeException.php new file mode 100644 index 0000000..2a11626 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/InvalidParameterTypeException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Thrown when trying to inject a parameter into a constructor/method with an incompatible type. + * + * @author Nicolas Grekas + * @author Julien Maulny + */ +class InvalidParameterTypeException extends InvalidArgumentException +{ + public function __construct(string $serviceId, string $type, \ReflectionParameter $parameter) + { + $acceptedType = $parameter->getType(); + $acceptedType = $acceptedType instanceof \ReflectionNamedType ? $acceptedType->getName() : (string) $acceptedType; + $this->code = $type; + + $function = $parameter->getDeclaringFunction(); + $functionName = $function instanceof \ReflectionMethod + ? sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName()) + : $function->getName(); + + parent::__construct(sprintf('Invalid definition for service "%s": argument %d of "%s()" accepts "%s", "%s" passed.', $serviceId, 1 + $parameter->getPosition(), $functionName, $acceptedType, $type)); + } +} diff --git a/vendor/symfony/dependency-injection/Exception/LogicException.php b/vendor/symfony/dependency-injection/Exception/LogicException.php new file mode 100644 index 0000000..17a070c --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base LogicException for Dependency Injection component. + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/OutOfBoundsException.php b/vendor/symfony/dependency-injection/Exception/OutOfBoundsException.php new file mode 100644 index 0000000..a61f143 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/OutOfBoundsException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base OutOfBoundsException for Dependency Injection component. + */ +class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php b/vendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php new file mode 100644 index 0000000..408801f --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when a circular reference in a parameter is detected. + * + * @author Fabien Potencier + */ +class ParameterCircularReferenceException extends RuntimeException +{ + private array $parameters; + + public function __construct(array $parameters, ?\Throwable $previous = null) + { + parent::__construct(sprintf('Circular reference detected for parameter "%s" ("%s" > "%s").', $parameters[0], implode('" > "', $parameters), $parameters[0]), 0, $previous); + + $this->parameters = $parameters; + } + + public function getParameters(): array + { + return $this->parameters; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/ParameterNotFoundException.php b/vendor/symfony/dependency-injection/Exception/ParameterNotFoundException.php new file mode 100644 index 0000000..13de87b --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ParameterNotFoundException.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +use Psr\Container\NotFoundExceptionInterface; + +/** + * This exception is thrown when a non-existent parameter is used. + * + * @author Fabien Potencier + */ +class ParameterNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface +{ + /** + * @param string $key The requested parameter key + * @param string|null $sourceId The service id that references the non-existent parameter + * @param string|null $sourceKey The parameter key that references the non-existent parameter + * @param \Throwable|null $previous The previous exception + * @param string[] $alternatives Some parameter name alternatives + * @param string|null $nonNestedAlternative The alternative parameter name when the user expected dot notation for nested parameters + */ + public function __construct( + private string $key, + private ?string $sourceId = null, + private ?string $sourceKey = null, + ?\Throwable $previous = null, + private array $alternatives = [], + private ?string $nonNestedAlternative = null, + private ?string $sourceExtensionName = null, + ) { + parent::__construct('', 0, $previous); + + $this->updateRepr(); + } + + public function updateRepr(): void + { + if (null !== $this->sourceId) { + $this->message = sprintf('The service "%s" has a dependency on a non-existent parameter "%s".', $this->sourceId, $this->key); + } elseif (null !== $this->sourceKey) { + $this->message = sprintf('The parameter "%s" has a dependency on a non-existent parameter "%s".', $this->sourceKey, $this->key); + } elseif (null !== $this->sourceExtensionName) { + $this->message = sprintf('You have requested a non-existent parameter "%s" while loading extension "%s".', $this->key, $this->sourceExtensionName); + } elseif ('.' === ($this->key[0] ?? '')) { + $this->message = sprintf('Parameter "%s" not found. It was probably deleted during the compilation of the container.', $this->key); + } else { + $this->message = sprintf('You have requested a non-existent parameter "%s".', $this->key); + } + + if ($this->alternatives) { + if (1 == \count($this->alternatives)) { + $this->message .= ' Did you mean this: "'; + } else { + $this->message .= ' Did you mean one of these: "'; + } + $this->message .= implode('", "', $this->alternatives).'"?'; + } elseif (null !== $this->nonNestedAlternative) { + $this->message .= ' You cannot access nested array items, do you want to inject "'.$this->nonNestedAlternative.'" instead?'; + } + } + + public function getKey(): string + { + return $this->key; + } + + public function getSourceId(): ?string + { + return $this->sourceId; + } + + public function getSourceKey(): ?string + { + return $this->sourceKey; + } + + public function setSourceId(?string $sourceId): void + { + $this->sourceId = $sourceId; + + $this->updateRepr(); + } + + public function setSourceKey(?string $sourceKey): void + { + $this->sourceKey = $sourceKey; + + $this->updateRepr(); + } + + public function setSourceExtensionName(?string $sourceExtensionName): void + { + $this->sourceExtensionName = $sourceExtensionName; + + $this->updateRepr(); + } +} diff --git a/vendor/symfony/dependency-injection/Exception/RuntimeException.php b/vendor/symfony/dependency-injection/Exception/RuntimeException.php new file mode 100644 index 0000000..5c24541 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base RuntimeException for Dependency Injection component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php b/vendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php new file mode 100644 index 0000000..f7a85bd --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when a circular reference is detected. + * + * @author Johannes M. Schmitt + */ +class ServiceCircularReferenceException extends RuntimeException +{ + private string $serviceId; + private array $path; + + public function __construct(string $serviceId, array $path, ?\Throwable $previous = null) + { + parent::__construct(sprintf('Circular reference detected for service "%s", path: "%s".', $serviceId, implode(' -> ', $path)), 0, $previous); + + $this->serviceId = $serviceId; + $this->path = $path; + } + + public function getServiceId(): string + { + return $this->serviceId; + } + + public function getPath(): array + { + return $this->path; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/ServiceNotFoundException.php b/vendor/symfony/dependency-injection/Exception/ServiceNotFoundException.php new file mode 100644 index 0000000..a7f82ff --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ServiceNotFoundException.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +use Psr\Container\NotFoundExceptionInterface; + +/** + * This exception is thrown when a non-existent service is requested. + * + * @author Johannes M. Schmitt + */ +class ServiceNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface +{ + private string $id; + private ?string $sourceId; + private array $alternatives; + + public function __construct(string $id, ?string $sourceId = null, ?\Throwable $previous = null, array $alternatives = [], ?string $msg = null) + { + if (null !== $msg) { + // no-op + } elseif (null === $sourceId) { + $msg = sprintf('You have requested a non-existent service "%s".', $id); + } else { + $msg = sprintf('The service "%s" has a dependency on a non-existent service "%s".', $sourceId, $id); + } + + if ($alternatives) { + if (1 == \count($alternatives)) { + $msg .= ' Did you mean this: "'; + } else { + $msg .= ' Did you mean one of these: "'; + } + $msg .= implode('", "', $alternatives).'"?'; + } + + parent::__construct($msg, 0, $previous); + + $this->id = $id; + $this->sourceId = $sourceId; + $this->alternatives = $alternatives; + } + + public function getId(): string + { + return $this->id; + } + + public function getSourceId(): ?string + { + return $this->sourceId; + } + + public function getAlternatives(): array + { + return $this->alternatives; + } +} diff --git a/vendor/symfony/dependency-injection/ExpressionLanguage.php b/vendor/symfony/dependency-injection/ExpressionLanguage.php new file mode 100644 index 0000000..84d45db --- /dev/null +++ b/vendor/symfony/dependency-injection/ExpressionLanguage.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; + +if (!class_exists(BaseExpressionLanguage::class)) { + return; +} + +/** + * Adds some function to the default ExpressionLanguage. + * + * @author Fabien Potencier + * + * @see ExpressionLanguageProvider + */ +class ExpressionLanguage extends BaseExpressionLanguage +{ + public function __construct(?CacheItemPoolInterface $cache = null, array $providers = [], ?callable $serviceCompiler = null, ?\Closure $getEnv = null) + { + // prepend the default provider to let users override it easily + array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler, $getEnv)); + + parent::__construct($cache, $providers); + } +} diff --git a/vendor/symfony/dependency-injection/ExpressionLanguageProvider.php b/vendor/symfony/dependency-injection/ExpressionLanguageProvider.php new file mode 100644 index 0000000..60479ea --- /dev/null +++ b/vendor/symfony/dependency-injection/ExpressionLanguageProvider.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\ExpressionLanguage\ExpressionFunction; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * Define some ExpressionLanguage functions. + * + * To get a service, use service('request'). + * To get a parameter, use parameter('kernel.debug'). + * To get an env variable, use env('SOME_VARIABLE'). + * + * @author Fabien Potencier + */ +class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface +{ + private ?\Closure $serviceCompiler; + + private ?\Closure $getEnv; + + public function __construct(?callable $serviceCompiler = null, ?\Closure $getEnv = null) + { + $this->serviceCompiler = null === $serviceCompiler ? null : $serviceCompiler(...); + $this->getEnv = $getEnv; + } + + public function getFunctions(): array + { + return [ + new ExpressionFunction('service', $this->serviceCompiler ?? fn ($arg) => sprintf('$container->get(%s)', $arg), fn (array $variables, $value) => $variables['container']->get($value)), + + new ExpressionFunction('parameter', fn ($arg) => sprintf('$container->getParameter(%s)', $arg), fn (array $variables, $value) => $variables['container']->getParameter($value)), + + new ExpressionFunction('env', fn ($arg) => sprintf('$container->getEnv(%s)', $arg), function (array $variables, $value) { + if (!$this->getEnv) { + throw new LogicException('You need to pass a getEnv closure to the expression language provider to use the "env" function.'); + } + + return ($this->getEnv)($value); + }), + + new ExpressionFunction('arg', fn ($arg) => sprintf('$args?->get(%s)', $arg), fn (array $variables, $value) => $variables['args']?->get($value)), + ]; + } +} diff --git a/vendor/symfony/dependency-injection/Extension/AbstractExtension.php b/vendor/symfony/dependency-injection/Extension/AbstractExtension.php new file mode 100644 index 0000000..795ed81 --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/AbstractExtension.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\Configuration; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * An Extension that provides configuration hooks. + * + * @author Yonel Ceruto + */ +abstract class AbstractExtension extends Extension implements ConfigurableExtensionInterface, PrependExtensionInterface +{ + use ExtensionTrait; + + public function configure(DefinitionConfigurator $definition): void + { + } + + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + { + return new Configuration($this, $container, $this->getAlias()); + } + + final public function prepend(ContainerBuilder $container): void + { + $callback = function (ContainerConfigurator $configurator) use ($container) { + $this->prependExtension($configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this, true); + } + + final public function load(array $configs, ContainerBuilder $container): void + { + $config = $this->processConfiguration($this->getConfiguration([], $container), $configs); + + $callback = function (ContainerConfigurator $configurator) use ($config, $container) { + $this->loadExtension($config, $configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this); + } +} diff --git a/vendor/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php b/vendor/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php new file mode 100644 index 0000000..b8927e4 --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/ConfigurableExtensionInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\ConfigurableInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * @author Yonel Ceruto + */ +interface ConfigurableExtensionInterface extends ConfigurableInterface +{ + /** + * Allows an extension to prepend the extension configurations. + */ + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void; + + /** + * Loads a specific configuration. + */ + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void; +} diff --git a/vendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php b/vendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php new file mode 100644 index 0000000..a42967f --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * ConfigurationExtensionInterface is the interface implemented by container extension classes. + * + * @author Kevin Bond + */ +interface ConfigurationExtensionInterface +{ + /** + * Returns extension configuration. + * + * @return ConfigurationInterface|null + */ + public function getConfiguration(array $config, ContainerBuilder $container); +} diff --git a/vendor/symfony/dependency-injection/Extension/Extension.php b/vendor/symfony/dependency-injection/Extension/Extension.php new file mode 100644 index 0000000..d0bd05e --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/Extension.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * Provides useful features shared by many extensions. + * + * @author Fabien Potencier + */ +abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface +{ + private array $processedConfigs = []; + + /** + * @return string|false + */ + public function getXsdValidationBasePath() + { + return false; + } + + /** + * @return string + */ + public function getNamespace() + { + return 'http://example.org/schema/dic/'.$this->getAlias(); + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * This convention is to remove the "Extension" postfix from the class + * name and then lowercase and underscore the result. So: + * + * AcmeHelloExtension + * + * becomes + * + * acme_hello + * + * This can be overridden in a sub-class to specify the alias manually. + * + * @throws BadMethodCallException When the extension name does not follow conventions + */ + public function getAlias(): string + { + $className = static::class; + if (!str_ends_with($className, 'Extension')) { + throw new BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.'); + } + $classBaseName = substr(strrchr($className, '\\'), 1, -9); + + return Container::underscore($classBaseName); + } + + /** + * @return ConfigurationInterface|null + */ + public function getConfiguration(array $config, ContainerBuilder $container) + { + $class = static::class; + + if (str_contains($class, "\0")) { + return null; // ignore anonymous classes + } + + $class = substr_replace($class, '\Configuration', strrpos($class, '\\')); + $class = $container->getReflectionClass($class); + + if (!$class) { + return null; + } + + if (!$class->implementsInterface(ConfigurationInterface::class)) { + throw new LogicException(sprintf('The extension configuration class "%s" must implement "%s".', $class->getName(), ConfigurationInterface::class)); + } + + if (!($constructor = $class->getConstructor()) || !$constructor->getNumberOfRequiredParameters()) { + return $class->newInstance(); + } + + return null; + } + + final protected function processConfiguration(ConfigurationInterface $configuration, array $configs): array + { + $processor = new Processor(); + + return $this->processedConfigs[] = $processor->processConfiguration($configuration, $configs); + } + + /** + * @internal + */ + final public function getProcessedConfigs(): array + { + try { + return $this->processedConfigs; + } finally { + $this->processedConfigs = []; + } + } + + /** + * @throws InvalidArgumentException When the config is not enableable + */ + protected function isConfigEnabled(ContainerBuilder $container, array $config): bool + { + if (!\array_key_exists('enabled', $config)) { + throw new InvalidArgumentException("The config array has no 'enabled' key."); + } + + return (bool) $container->getParameterBag()->resolveValue($config['enabled']); + } +} diff --git a/vendor/symfony/dependency-injection/Extension/ExtensionInterface.php b/vendor/symfony/dependency-injection/Extension/ExtensionInterface.php new file mode 100644 index 0000000..bd57eef --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/ExtensionInterface.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * ExtensionInterface is the interface implemented by container extension classes. + * + * @author Fabien Potencier + */ +interface ExtensionInterface +{ + /** + * Loads a specific configuration. + * + * @param array> $configs + * + * @return void + * + * @throws \InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $configs, ContainerBuilder $container); + + /** + * Returns the namespace to be used for this extension (XML namespace). + * + * @return string + */ + public function getNamespace(); + + /** + * Returns the base path for the XSD files. + * + * @return string|false + */ + public function getXsdValidationBasePath(); + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string + */ + public function getAlias(); +} diff --git a/vendor/symfony/dependency-injection/Extension/ExtensionTrait.php b/vendor/symfony/dependency-injection/Extension/ExtensionTrait.php new file mode 100644 index 0000000..0bb0088 --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/ExtensionTrait.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Loader\DelegatingLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; +use Symfony\Component\DependencyInjection\Loader\GlobFileLoader; +use Symfony\Component\DependencyInjection\Loader\IniFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** + * @author Yonel Ceruto + */ +trait ExtensionTrait +{ + private function executeConfiguratorCallback(ContainerBuilder $container, \Closure $callback, ConfigurableExtensionInterface $subject, bool $prepend = false): void + { + $env = $container->getParameter('kernel.environment'); + $loader = $this->createContainerLoader($container, $env, $prepend); + $file = (new \ReflectionObject($subject))->getFileName(); + $bundleLoader = $loader->getResolver()->resolve($file); + if (!$bundleLoader instanceof PhpFileLoader) { + throw new \LogicException('Unable to create the ContainerConfigurator.'); + } + $bundleLoader->setCurrentDir(\dirname($file)); + $instanceof = &\Closure::bind(fn &() => $this->instanceof, $bundleLoader, $bundleLoader)(); + + try { + $callback(new ContainerConfigurator($container, $bundleLoader, $instanceof, $file, $file, $env)); + } finally { + $instanceof = []; + $bundleLoader->registerAliasesForSinglyImplementedInterfaces(); + } + } + + private function createContainerLoader(ContainerBuilder $container, string $env, bool $prepend): DelegatingLoader + { + $buildDir = $container->getParameter('kernel.build_dir'); + $locator = new FileLocator(); + $resolver = new LoaderResolver([ + new XmlFileLoader($container, $locator, $env, $prepend), + new YamlFileLoader($container, $locator, $env, $prepend), + new IniFileLoader($container, $locator, $env), + new PhpFileLoader($container, $locator, $env, new ConfigBuilderGenerator($buildDir), $prepend), + new GlobFileLoader($container, $locator, $env), + new DirectoryLoader($container, $locator, $env), + new ClosureLoader($container, $env), + ]); + + return new DelegatingLoader($resolver); + } +} diff --git a/vendor/symfony/dependency-injection/Extension/PrependExtensionInterface.php b/vendor/symfony/dependency-injection/Extension/PrependExtensionInterface.php new file mode 100644 index 0000000..0df94e1 --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/PrependExtensionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +interface PrependExtensionInterface +{ + /** + * Allow an extension to prepend the extension configurations. + * + * @return void + */ + public function prepend(ContainerBuilder $container); +} diff --git a/vendor/symfony/dependency-injection/LICENSE b/vendor/symfony/dependency-injection/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/dependency-injection/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php new file mode 100644 index 0000000..c516ed6 --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Lazy proxy instantiator, capable of instantiating a proxy given a container, the + * service definitions and a callback that produces the real service instance. + * + * @author Marco Pivetta + */ +interface InstantiatorInterface +{ + /** + * Instantiates a proxy object. + * + * @param string $id Identifier of the requested service + * @param callable(object=) $realInstantiator A callback that is capable of producing the real service instance + */ + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object; +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php new file mode 100644 index 0000000..40b128d --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/LazyServiceInstantiator.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; + +/** + * @author Nicolas Grekas + */ +final class LazyServiceInstantiator implements InstantiatorInterface +{ + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object + { + $dumper = new LazyServiceDumper(); + + if (!$dumper->isProxyCandidate($definition, $asGhostObject, $id)) { + throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id)); + } + + if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject), false)) { + eval($dumper->getProxyCode($definition, $id)); + } + + return $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator); + } +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php new file mode 100644 index 0000000..a0c445e --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Noop proxy instantiator - produces the real service instead of a proxy instance. + * + * @author Marco Pivetta + */ +class RealServiceInstantiator implements InstantiatorInterface +{ + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object + { + return $realInstantiator(); + } +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php new file mode 100644 index 0000000..05f2fbf --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * Lazy proxy dumper capable of generating the instantiation logic PHP code for proxied services. + * + * @author Marco Pivetta + */ +interface DumperInterface +{ + /** + * Inspects whether the given definitions should produce proxy instantiation logic in the dumped container. + * + * @param bool|null &$asGhostObject Set to true after the call if the proxy is a ghost object + */ + public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool; + + /** + * Generates the code to be used to instantiate a proxy in the dumped factory code. + */ + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string; + + /** + * Generates the code for the lazy proxy. + */ + public function getProxyCode(Definition $definition, ?string $id = null): string; +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php new file mode 100644 index 0000000..251819a --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\VarExporter\Exception\LogicException; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * @author Nicolas Grekas + */ +final class LazyServiceDumper implements DumperInterface +{ + public function __construct( + private string $salt = '', + ) { + } + + public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool + { + $asGhostObject = false; + + if ($definition->hasTag('proxy')) { + if (!$definition->isLazy()) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": setting the "proxy" tag on a service requires it to be "lazy".', $id ?? $definition->getClass())); + } + + return true; + } + + if (!$definition->isLazy()) { + return false; + } + + if (!($class = $definition->getClass()) || !(class_exists($class) || interface_exists($class, false))) { + return false; + } + + if ($definition->getFactory()) { + return true; + } + + foreach ($definition->getMethodCalls() as $call) { + if ($call[2] ?? false) { + return true; + } + } + + try { + $asGhostObject = (bool) ProxyHelper::generateLazyGhost(new \ReflectionClass($class)); + } catch (LogicException) { + } + + return true; + } + + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string + { + $instantiation = 'return'; + + if ($definition->isShared()) { + $instantiation .= sprintf(' $container->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); + } + + $asGhostObject = str_contains($factoryCode, '$proxy'); + $proxyClass = $this->getProxyClass($definition, $asGhostObject); + + if (!$asGhostObject) { + return <<createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyProxy(static fn () => $factoryCode)); + } + + + EOF; + } + + $factoryCode = sprintf('static fn ($proxy) => %s', $factoryCode); + + return <<createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyGhost($factoryCode)); + } + + + EOF; + } + + public function getProxyCode(Definition $definition, ?string $id = null): string + { + if (!$this->isProxyCandidate($definition, $asGhostObject, $id)) { + throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id ?? $definition->getClass())); + } + $proxyClass = $this->getProxyClass($definition, $asGhostObject, $class); + + if ($asGhostObject) { + try { + return ($class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyGhost($class); + } catch (LogicException $e) { + throw new InvalidArgumentException(sprintf('Cannot generate lazy ghost for service "%s".', $id ?? $definition->getClass()), 0, $e); + } + } + $interfaces = []; + + if ($definition->hasTag('proxy')) { + foreach ($definition->getTag('proxy') as $tag) { + if (!isset($tag['interface'])) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": the "interface" attribute is missing on a "proxy" tag.', $id ?? $definition->getClass())); + } + if (!interface_exists($tag['interface']) && !class_exists($tag['interface'], false)) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": several "proxy" tags found but "%s" is not an interface.', $id ?? $definition->getClass(), $tag['interface'])); + } + if ('object' !== $definition->getClass() && !is_a($class->name, $tag['interface'], true)) { + throw new InvalidArgumentException(sprintf('Invalid "proxy" tag for service "%s": class "%s" doesn\'t implement "%s".', $id ?? $definition->getClass(), $definition->getClass(), $tag['interface'])); + } + $interfaces[] = new \ReflectionClass($tag['interface']); + } + + $class = 1 === \count($interfaces) && !$interfaces[0]->isInterface() ? array_pop($interfaces) : null; + } elseif ($class->isInterface()) { + $interfaces = [$class]; + $class = null; + } + + try { + return ($class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyProxy($class, $interfaces); + } catch (LogicException $e) { + throw new InvalidArgumentException(sprintf('Cannot generate lazy proxy for service "%s".', $id ?? $definition->getClass()), 0, $e); + } + } + + public function getProxyClass(Definition $definition, bool $asGhostObject, ?\ReflectionClass &$class = null): string + { + $class = 'object' !== $definition->getClass() ? $definition->getClass() : 'stdClass'; + $class = new \ReflectionClass($class); + + return preg_replace('/^.*\\\\/', '', $definition->getClass()) + .($asGhostObject ? 'Ghost' : 'Proxy') + .ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7)); + } +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php new file mode 100644 index 0000000..c987b19 --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * Null dumper, negates any proxy code generation for any given service definition. + * + * @author Marco Pivetta + * + * @final + */ +class NullDumper implements DumperInterface +{ + public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool + { + return $asGhostObject = false; + } + + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string + { + return ''; + } + + public function getProxyCode(Definition $definition, ?string $id = null): string + { + return ''; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/ClosureLoader.php b/vendor/symfony/dependency-injection/Loader/ClosureLoader.php new file mode 100644 index 0000000..1e3061d --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/ClosureLoader.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * ClosureLoader loads service definitions from a PHP closure. + * + * The Closure has access to the container as its first argument. + * + * @author Fabien Potencier + */ +class ClosureLoader extends Loader +{ + private ContainerBuilder $container; + + public function __construct(ContainerBuilder $container, ?string $env = null) + { + $this->container = $container; + parent::__construct($env); + } + + public function load(mixed $resource, ?string $type = null): mixed + { + return $resource($this->container, $this->env); + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return $resource instanceof \Closure; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php new file mode 100644 index 0000000..36c15e1 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Config\Loader\ParamConfigurator; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +abstract class AbstractConfigurator +{ + public const FACTORY = 'unknown'; + + /** + * @var \Closure(mixed, bool):mixed|null + */ + public static ?\Closure $valuePreProcessor = null; + + /** @internal */ + protected Definition|Alias|null $definition = null; + + public function __call(string $method, array $args): mixed + { + if (method_exists($this, 'set'.$method)) { + return $this->{'set'.$method}(...$args); + } + + throw new \BadMethodCallException(sprintf('Call to undefined method "%s::%s()".', static::class, $method)); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + /** + * Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value. + * + * @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars, arrays and enum are + * + * @return mixed the value, optionally cast to a Definition/Reference + */ + public static function processValue(mixed $value, bool $allowServices = false): mixed + { + if (\is_array($value)) { + foreach ($value as $k => $v) { + $value[$k] = static::processValue($v, $allowServices); + } + + return self::$valuePreProcessor ? (self::$valuePreProcessor)($value, $allowServices) : $value; + } + + if (self::$valuePreProcessor) { + $value = (self::$valuePreProcessor)($value, $allowServices); + } + + if ($value instanceof ReferenceConfigurator) { + $reference = new Reference($value->id, $value->invalidBehavior); + + return $value instanceof ClosureReferenceConfigurator ? new ServiceClosureArgument($reference) : $reference; + } + + if ($value instanceof InlineServiceConfigurator) { + $def = $value->definition; + $value->definition = null; + + return $def; + } + + if ($value instanceof ParamConfigurator) { + return (string) $value; + } + + if ($value instanceof self) { + throw new InvalidArgumentException(sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY)); + } + + switch (true) { + case null === $value: + case \is_scalar($value): + case $value instanceof \UnitEnum: + return $value; + + case $value instanceof ArgumentInterface: + case $value instanceof Definition: + case $value instanceof Expression: + case $value instanceof Parameter: + case $value instanceof AbstractArgument: + case $value instanceof Reference: + if ($allowServices) { + return $value; + } + } + + throw new InvalidArgumentException(sprintf('Cannot use values of type "%s" in service configuration files.', get_debug_type($value))); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php new file mode 100644 index 0000000..295a351 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +abstract class AbstractServiceConfigurator extends AbstractConfigurator +{ + protected ServicesConfigurator $parent; + protected ?string $id; + private array $defaultTags = []; + + public function __construct(ServicesConfigurator $parent, Definition $definition, ?string $id = null, array $defaultTags = []) + { + $this->parent = $parent; + $this->definition = $definition; + $this->id = $id; + $this->defaultTags = $defaultTags; + } + + public function __destruct() + { + // default tags should be added last + foreach ($this->defaultTags as $name => $attributes) { + foreach ($attributes as $attribute) { + $this->definition->addTag($name, $attribute); + } + } + $this->defaultTags = []; + } + + /** + * Registers a service. + */ + final public function set(?string $id, ?string $class = null): ServiceConfigurator + { + $this->__destruct(); + + return $this->parent->set($id, $class); + } + + /** + * Creates an alias. + */ + final public function alias(string $id, string $referencedId): AliasConfigurator + { + $this->__destruct(); + + return $this->parent->alias($id, $referencedId); + } + + /** + * Registers a PSR-4 namespace using a glob pattern. + */ + final public function load(string $namespace, string $resource): PrototypeConfigurator + { + $this->__destruct(); + + return $this->parent->load($namespace, $resource); + } + + /** + * Gets an already defined service definition. + * + * @throws ServiceNotFoundException if the service definition does not exist + */ + final public function get(string $id): ServiceConfigurator + { + $this->__destruct(); + + return $this->parent->get($id); + } + + /** + * Removes an already defined service definition or alias. + */ + final public function remove(string $id): ServicesConfigurator + { + $this->__destruct(); + + return $this->parent->remove($id); + } + + /** + * Registers a stack of decorator services. + * + * @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services + */ + final public function stack(string $id, array $services): AliasConfigurator + { + $this->__destruct(); + + return $this->parent->stack($id, $services); + } + + /** + * Registers a service. + */ + final public function __invoke(string $id, ?string $class = null): ServiceConfigurator + { + $this->__destruct(); + + return $this->parent->set($id, $class); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php new file mode 100644 index 0000000..650a956 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Alias; + +/** + * @author Nicolas Grekas + */ +class AliasConfigurator extends AbstractServiceConfigurator +{ + use Traits\DeprecateTrait; + use Traits\PublicTrait; + + public const FACTORY = 'alias'; + + public function __construct(ServicesConfigurator $parent, Alias $alias) + { + $this->parent = $parent; + $this->definition = $alias; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php new file mode 100644 index 0000000..ba83d91 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +class ClosureReferenceConfigurator extends ReferenceConfigurator +{ +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php new file mode 100644 index 0000000..b943186 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Config\Loader\ParamConfigurator; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\UndefinedExtensionHandler; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * @author Nicolas Grekas + */ +class ContainerConfigurator extends AbstractConfigurator +{ + public const FACTORY = 'container'; + + private ContainerBuilder $container; + private PhpFileLoader $loader; + private array $instanceof; + private string $path; + private string $file; + private int $anonymousCount = 0; + private ?string $env; + + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, ?string $env = null) + { + $this->container = $container; + $this->loader = $loader; + $this->instanceof = &$instanceof; + $this->path = $path; + $this->file = $file; + $this->env = $env; + } + + final public function extension(string $namespace, array $config, bool $prepend = false): void + { + if ($prepend) { + $this->container->prependExtensionConfig($namespace, static::processValue($config)); + + return; + } + + if (!$this->container->hasExtension($namespace)) { + $extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); + throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($namespace, $this->file, $namespace, $extensions)); + } + + $this->container->loadFromExtension($namespace, static::processValue($config)); + } + + final public function import(string $resource, ?string $type = null, bool|string $ignoreErrors = false): void + { + $this->loader->setCurrentDir(\dirname($this->path)); + $this->loader->import($resource, $type, $ignoreErrors, $this->file); + } + + final public function parameters(): ParametersConfigurator + { + return new ParametersConfigurator($this->container); + } + + final public function services(): ServicesConfigurator + { + return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount); + } + + /** + * Get the current environment to be able to write conditional configuration. + */ + final public function env(): ?string + { + return $this->env; + } + + final public function withPath(string $path): static + { + $clone = clone $this; + $clone->path = $clone->file = $path; + $clone->loader->setCurrentDir(\dirname($path)); + + return $clone; + } +} + +/** + * Creates a parameter. + */ +function param(string $name): ParamConfigurator +{ + return new ParamConfigurator($name); +} + +/** + * Creates a reference to a service. + */ +function service(string $serviceId): ReferenceConfigurator +{ + return new ReferenceConfigurator($serviceId); +} + +/** + * Creates an inline service. + */ +function inline_service(?string $class = null): InlineServiceConfigurator +{ + return new InlineServiceConfigurator(new Definition($class)); +} + +/** + * Creates a service locator. + * + * @param array $values + */ +function service_locator(array $values): ServiceLocatorArgument +{ + $values = AbstractConfigurator::processValue($values, true); + + return new ServiceLocatorArgument($values); +} + +/** + * Creates a lazy iterator. + * + * @param ReferenceConfigurator[] $values + */ +function iterator(array $values): IteratorArgument +{ + return new IteratorArgument(AbstractConfigurator::processValue($values, true)); +} + +/** + * Creates a lazy iterator by tag name. + */ +function tagged_iterator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): TaggedIteratorArgument +{ + return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf); +} + +/** + * Creates a service locator by tag name. + */ +function tagged_locator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): ServiceLocatorArgument +{ + return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf)); +} + +/** + * Creates an expression. + */ +function expr(string $expression): Expression +{ + return new Expression($expression); +} + +/** + * Creates an abstract argument. + */ +function abstract_arg(string $description): AbstractArgument +{ + return new AbstractArgument($description); +} + +/** + * Creates an environment variable reference. + */ +function env(string $name): EnvConfigurator +{ + return new EnvConfigurator($name); +} + +/** + * Creates a closure service reference. + */ +function service_closure(string $serviceId): ClosureReferenceConfigurator +{ + return new ClosureReferenceConfigurator($serviceId); +} + +/** + * Creates a closure. + */ +function closure(string|array|ReferenceConfigurator|Expression $callable): InlineServiceConfigurator +{ + return (new InlineServiceConfigurator(new Definition('Closure'))) + ->factory(['Closure', 'fromCallable']) + ->args([$callable]); +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php new file mode 100644 index 0000000..1f26c97 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + */ +class DefaultsConfigurator extends AbstractServiceConfigurator +{ + use Traits\AutoconfigureTrait; + use Traits\AutowireTrait; + use Traits\BindTrait; + use Traits\PublicTrait; + + public const FACTORY = 'defaults'; + + private ?string $path; + + public function __construct(ServicesConfigurator $parent, Definition $definition, ?string $path = null) + { + parent::__construct($parent, $definition, null, []); + + $this->path = $path; + } + + /** + * Adds a tag for this definition. + * + * @return $this + * + * @throws InvalidArgumentException when an invalid tag name or attribute is provided + */ + final public function tag(string $name, array $attributes = []): static + { + if ('' === $name) { + throw new InvalidArgumentException('The tag name in "_defaults" must be a non-empty string.'); + } + + $this->validateAttributes($name, $attributes); + + $this->definition->addTag($name, $attributes); + + return $this; + } + + /** + * Defines an instanceof-conditional to be applied to following service definitions. + */ + final public function instanceof(string $fqcn): InstanceofConfigurator + { + return $this->parent->instanceof($fqcn); + } + + private function validateAttributes(string $tag, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($tag, $value, [...$path, $name]); + } elseif (!\is_scalar($value ?? '')) { + $name = implode('.', [...$path, $name]); + throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type or an array of scalar-type.', $tag, $name)); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php new file mode 100644 index 0000000..fe67803 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php @@ -0,0 +1,236 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Config\Loader\ParamConfigurator; + +class EnvConfigurator extends ParamConfigurator +{ + /** + * @var string[] + */ + private array $stack; + + public function __construct(string $name) + { + $this->stack = explode(':', $name); + } + + public function __toString(): string + { + return '%env('.implode(':', $this->stack).')%'; + } + + /** + * @return $this + */ + public function __call(string $name, array $arguments): static + { + $processor = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $name)); + + $this->custom($processor, ...$arguments); + + return $this; + } + + /** + * @return $this + */ + public function custom(string $processor, ...$args): static + { + array_unshift($this->stack, $processor, ...$args); + + return $this; + } + + /** + * @return $this + */ + public function base64(): static + { + array_unshift($this->stack, 'base64'); + + return $this; + } + + /** + * @return $this + */ + public function bool(): static + { + array_unshift($this->stack, 'bool'); + + return $this; + } + + /** + * @return $this + */ + public function not(): static + { + array_unshift($this->stack, 'not'); + + return $this; + } + + /** + * @return $this + */ + public function const(): static + { + array_unshift($this->stack, 'const'); + + return $this; + } + + /** + * @return $this + */ + public function csv(): static + { + array_unshift($this->stack, 'csv'); + + return $this; + } + + /** + * @return $this + */ + public function file(): static + { + array_unshift($this->stack, 'file'); + + return $this; + } + + /** + * @return $this + */ + public function float(): static + { + array_unshift($this->stack, 'float'); + + return $this; + } + + /** + * @return $this + */ + public function int(): static + { + array_unshift($this->stack, 'int'); + + return $this; + } + + /** + * @return $this + */ + public function json(): static + { + array_unshift($this->stack, 'json'); + + return $this; + } + + /** + * @return $this + */ + public function key(string $key): static + { + array_unshift($this->stack, 'key', $key); + + return $this; + } + + /** + * @return $this + */ + public function url(): static + { + array_unshift($this->stack, 'url'); + + return $this; + } + + /** + * @return $this + */ + public function queryString(): static + { + array_unshift($this->stack, 'query_string'); + + return $this; + } + + /** + * @return $this + */ + public function resolve(): static + { + array_unshift($this->stack, 'resolve'); + + return $this; + } + + /** + * @return $this + */ + public function default(string $fallbackParam): static + { + array_unshift($this->stack, 'default', $fallbackParam); + + return $this; + } + + /** + * @return $this + */ + public function string(): static + { + array_unshift($this->stack, 'string'); + + return $this; + } + + /** + * @return $this + */ + public function trim(): static + { + array_unshift($this->stack, 'trim'); + + return $this; + } + + /** + * @return $this + */ + public function require(): static + { + array_unshift($this->stack, 'require'); + + return $this; + } + + /** + * @param class-string<\BackedEnum> $backedEnumClassName + * + * @return $this + */ + public function enum(string $backedEnumClassName): static + { + array_unshift($this->stack, 'enum', $backedEnumClassName); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php new file mode 100644 index 0000000..7fe0d3d --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/FromCallableConfigurator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * @author Nicolas Grekas + */ +class FromCallableConfigurator extends AbstractServiceConfigurator +{ + use Traits\AbstractTrait; + use Traits\AutoconfigureTrait; + use Traits\AutowireTrait; + use Traits\BindTrait; + use Traits\DecorateTrait; + use Traits\DeprecateTrait; + use Traits\LazyTrait; + use Traits\PublicTrait; + use Traits\ShareTrait; + use Traits\TagTrait; + + public const FACTORY = 'services'; + + private ServiceConfigurator $serviceConfigurator; + + public function __construct(ServiceConfigurator $serviceConfigurator, Definition $definition) + { + $this->serviceConfigurator = $serviceConfigurator; + + parent::__construct($serviceConfigurator->parent, $definition, $serviceConfigurator->id); + } + + public function __destruct() + { + $this->serviceConfigurator->__destruct(); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php new file mode 100644 index 0000000..0b1990e --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * @author Nicolas Grekas + */ +class InlineServiceConfigurator extends AbstractConfigurator +{ + use Traits\ArgumentTrait; + use Traits\AutowireTrait; + use Traits\BindTrait; + use Traits\CallTrait; + use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; + use Traits\FactoryTrait; + use Traits\FileTrait; + use Traits\LazyTrait; + use Traits\ParentTrait; + use Traits\PropertyTrait; + use Traits\TagTrait; + + public const FACTORY = 'service'; + + private string $id = '[inline]'; + private bool $allowParent = true; + private ?string $path = null; + + public function __construct(Definition $definition) + { + $this->definition = $definition; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php new file mode 100644 index 0000000..9de0baa --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * @author Nicolas Grekas + */ +class InstanceofConfigurator extends AbstractServiceConfigurator +{ + use Traits\AutowireTrait; + use Traits\BindTrait; + use Traits\CallTrait; + use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; + use Traits\LazyTrait; + use Traits\PropertyTrait; + use Traits\PublicTrait; + use Traits\ShareTrait; + use Traits\TagTrait; + + public const FACTORY = 'instanceof'; + + private ?string $path; + + public function __construct(ServicesConfigurator $parent, Definition $definition, string $id, ?string $path = null) + { + parent::__construct($parent, $definition, $id, []); + + $this->path = $path; + } + + /** + * Defines an instanceof-conditional to be applied to following service definitions. + */ + final public function instanceof(string $fqcn): self + { + return $this->parent->instanceof($fqcn); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php new file mode 100644 index 0000000..df5a94b --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * @author Nicolas Grekas + */ +class ParametersConfigurator extends AbstractConfigurator +{ + public const FACTORY = 'parameters'; + + private ContainerBuilder $container; + + public function __construct(ContainerBuilder $container) + { + $this->container = $container; + } + + /** + * @return $this + */ + final public function set(string $name, mixed $value): static + { + if ($value instanceof Expression) { + throw new InvalidArgumentException(sprintf('Using an expression in parameter "%s" is not allowed.', $name)); + } + + $this->container->setParameter($name, static::processValue($value, true)); + + return $this; + } + + /** + * @return $this + */ + final public function __invoke(string $name, mixed $value): static + { + return $this->set($name, $value); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php new file mode 100644 index 0000000..5d84472 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; + +/** + * @author Nicolas Grekas + */ +class PrototypeConfigurator extends AbstractServiceConfigurator +{ + use Traits\AbstractTrait; + use Traits\ArgumentTrait; + use Traits\AutoconfigureTrait; + use Traits\AutowireTrait; + use Traits\BindTrait; + use Traits\CallTrait; + use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; + use Traits\DeprecateTrait; + use Traits\FactoryTrait; + use Traits\LazyTrait; + use Traits\ParentTrait; + use Traits\PropertyTrait; + use Traits\PublicTrait; + use Traits\ShareTrait; + use Traits\TagTrait; + + public const FACTORY = 'load'; + + private PhpFileLoader $loader; + private string $resource; + private ?array $excludes = null; + private bool $allowParent; + private ?string $path; + + public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent, ?string $path = null) + { + $definition = new Definition(); + if (!$defaults->isPublic() || !$defaults->isPrivate()) { + $definition->setPublic($defaults->isPublic()); + } + $definition->setAutowired($defaults->isAutowired()); + $definition->setAutoconfigured($defaults->isAutoconfigured()); + // deep clone, to avoid multiple process of the same instance in the passes + $definition->setBindings(unserialize(serialize($defaults->getBindings()))); + $definition->setChanges([]); + + $this->loader = $loader; + $this->resource = $resource; + $this->allowParent = $allowParent; + $this->path = $path; + + parent::__construct($parent, $definition, $namespace, $defaults->getTags()); + } + + public function __destruct() + { + parent::__destruct(); + + if (isset($this->loader)) { + $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes, $this->path); + } + unset($this->loader); + } + + /** + * Excludes files from registration using glob patterns. + * + * @param string[]|string $excludes + * + * @return $this + */ + final public function exclude(array|string $excludes): static + { + $this->excludes = (array) $excludes; + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php new file mode 100644 index 0000000..4a83f9c --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * @author Nicolas Grekas + */ +class ReferenceConfigurator extends AbstractConfigurator +{ + /** @internal */ + protected string $id; + + /** @internal */ + protected int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + + public function __construct(string $id) + { + $this->id = $id; + } + + /** + * @return $this + */ + final public function ignoreOnInvalid(): static + { + $this->invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + + return $this; + } + + /** + * @return $this + */ + final public function nullOnInvalid(): static + { + $this->invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + + return $this; + } + + /** + * @return $this + */ + final public function ignoreOnUninitialized(): static + { + $this->invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; + + return $this; + } + + public function __toString(): string + { + return $this->id; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php new file mode 100644 index 0000000..57f498a --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; + +/** + * @author Nicolas Grekas + */ +class ServiceConfigurator extends AbstractServiceConfigurator +{ + use Traits\AbstractTrait; + use Traits\ArgumentTrait; + use Traits\AutoconfigureTrait; + use Traits\AutowireTrait; + use Traits\BindTrait; + use Traits\CallTrait; + use Traits\ClassTrait; + use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; + use Traits\DecorateTrait; + use Traits\DeprecateTrait; + use Traits\FactoryTrait; + use Traits\FileTrait; + use Traits\FromCallableTrait; + use Traits\LazyTrait; + use Traits\ParentTrait; + use Traits\PropertyTrait; + use Traits\PublicTrait; + use Traits\ShareTrait; + use Traits\SyntheticTrait; + use Traits\TagTrait; + + public const FACTORY = 'services'; + + private ContainerBuilder $container; + private array $instanceof; + private bool $allowParent; + private ?string $path; + private bool $destructed = false; + + public function __construct(ContainerBuilder $container, array $instanceof, bool $allowParent, ServicesConfigurator $parent, Definition $definition, ?string $id, array $defaultTags, ?string $path = null) + { + $this->container = $container; + $this->instanceof = $instanceof; + $this->allowParent = $allowParent; + $this->path = $path; + + parent::__construct($parent, $definition, $id, $defaultTags); + } + + public function __destruct() + { + if ($this->destructed) { + return; + } + $this->destructed = true; + + parent::__destruct(); + + $this->container->removeBindings($this->id); + $this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof)); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php b/vendor/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php new file mode 100644 index 0000000..0c2e5a4 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; + +/** + * @author Nicolas Grekas + */ +class ServicesConfigurator extends AbstractConfigurator +{ + public const FACTORY = 'services'; + + private Definition $defaults; + private ContainerBuilder $container; + private PhpFileLoader $loader; + private array $instanceof; + private ?string $path; + private string $anonymousHash; + private int $anonymousCount; + + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, ?string $path = null, int &$anonymousCount = 0) + { + $this->defaults = new Definition(); + $this->container = $container; + $this->loader = $loader; + $this->instanceof = &$instanceof; + $this->path = $path; + $this->anonymousHash = ContainerBuilder::hash($path ?: mt_rand()); + $this->anonymousCount = &$anonymousCount; + $instanceof = []; + } + + /** + * Defines a set of defaults for following service definitions. + */ + final public function defaults(): DefaultsConfigurator + { + return new DefaultsConfigurator($this, $this->defaults = new Definition(), $this->path); + } + + /** + * Defines an instanceof-conditional to be applied to following service definitions. + */ + final public function instanceof(string $fqcn): InstanceofConfigurator + { + $this->instanceof[$fqcn] = $definition = new ChildDefinition(''); + + return new InstanceofConfigurator($this, $definition, $fqcn, $this->path); + } + + /** + * Registers a service. + * + * @param string|null $id The service id, or null to create an anonymous service + * @param string|null $class The class of the service, or null when $id is also the class name + */ + final public function set(?string $id, ?string $class = null): ServiceConfigurator + { + $defaults = $this->defaults; + $definition = new Definition(); + + if (null === $id) { + if (!$class) { + throw new \LogicException('Anonymous services must have a class name.'); + } + + $id = sprintf('.%d_%s', ++$this->anonymousCount, preg_replace('/^.*\\\\/', '', $class).'~'.$this->anonymousHash); + } elseif (!$defaults->isPublic() || !$defaults->isPrivate()) { + $definition->setPublic($defaults->isPublic() && !$defaults->isPrivate()); + } + + $definition->setAutowired($defaults->isAutowired()); + $definition->setAutoconfigured($defaults->isAutoconfigured()); + // deep clone, to avoid multiple process of the same instance in the passes + $definition->setBindings(unserialize(serialize($defaults->getBindings()))); + $definition->setChanges([]); + + $configurator = new ServiceConfigurator($this->container, $this->instanceof, true, $this, $definition, $id, $defaults->getTags(), $this->path); + + return null !== $class ? $configurator->class($class) : $configurator; + } + + /** + * Removes an already defined service definition or alias. + * + * @return $this + */ + final public function remove(string $id): static + { + $this->container->removeDefinition($id); + $this->container->removeAlias($id); + + return $this; + } + + /** + * Creates an alias. + */ + final public function alias(string $id, string $referencedId): AliasConfigurator + { + $ref = static::processValue($referencedId, true); + $alias = new Alias((string) $ref); + if (!$this->defaults->isPublic() || !$this->defaults->isPrivate()) { + $alias->setPublic($this->defaults->isPublic()); + } + $this->container->setAlias($id, $alias); + + return new AliasConfigurator($this, $alias); + } + + /** + * Registers a PSR-4 namespace using a glob pattern. + */ + final public function load(string $namespace, string $resource): PrototypeConfigurator + { + return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, true, $this->path); + } + + /** + * Gets an already defined service definition. + * + * @throws ServiceNotFoundException if the service definition does not exist + */ + final public function get(string $id): ServiceConfigurator + { + $definition = $this->container->getDefinition($id); + + return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), true, $this, $definition, $id, []); + } + + /** + * Registers a stack of decorator services. + * + * @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services + */ + final public function stack(string $id, array $services): AliasConfigurator + { + foreach ($services as $i => $service) { + if ($service instanceof InlineServiceConfigurator) { + $definition = $service->definition->setInstanceofConditionals($this->instanceof); + + $changes = $definition->getChanges(); + $definition->setAutowired((isset($changes['autowired']) ? $definition : $this->defaults)->isAutowired()); + $definition->setAutoconfigured((isset($changes['autoconfigured']) ? $definition : $this->defaults)->isAutoconfigured()); + $definition->setBindings(array_merge($this->defaults->getBindings(), $definition->getBindings())); + $definition->setChanges($changes); + + $services[$i] = $definition; + } elseif (!$service instanceof ReferenceConfigurator) { + throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service), $i, $id)); + } + } + + $alias = $this->alias($id, ''); + $alias->definition = $this->set($id) + ->parent('') + ->args($services) + ->tag('container.stack') + ->definition; + + return $alias; + } + + /** + * Registers a service. + */ + final public function __invoke(string $id, ?string $class = null): ServiceConfigurator + { + return $this->set($id, $class); + } + + public function __destruct() + { + $this->loader->registerAliasesForSinglyImplementedInterfaces(); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php new file mode 100644 index 0000000..b42b070 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait AbstractTrait +{ + /** + * Whether this definition is abstract, that means it merely serves as a + * template for other definitions. + * + * @return $this + */ + final public function abstract(bool $abstract = true): static + { + $this->definition->setAbstract($abstract); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php new file mode 100644 index 0000000..67051f3 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait ArgumentTrait +{ + /** + * Sets the arguments to pass to the service constructor/factory method. + * + * @return $this + */ + final public function args(array $arguments): static + { + $this->definition->setArguments(static::processValue($arguments, true)); + + return $this; + } + + /** + * Sets one argument to pass to the service constructor/factory method. + * + * @return $this + */ + final public function arg(string|int $key, mixed $value): static + { + $this->definition->setArgument($key, static::processValue($value, true)); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php new file mode 100644 index 0000000..f5762c5 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +trait AutoconfigureTrait +{ + /** + * Sets whether or not instanceof conditionals should be prepended with a global set. + * + * @return $this + * + * @throws InvalidArgumentException when a parent is already set + */ + final public function autoconfigure(bool $autoconfigured = true): static + { + $this->definition->setAutoconfigured($autoconfigured); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php new file mode 100644 index 0000000..9bce28f --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait AutowireTrait +{ + /** + * Enables/disables autowiring. + * + * @return $this + */ + final public function autowire(bool $autowired = true): static + { + $this->definition->setAutowired($autowired); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php new file mode 100644 index 0000000..6bf6b6f --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Loader\Configurator\DefaultsConfigurator; +use Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator; + +trait BindTrait +{ + /** + * Sets bindings. + * + * Bindings map $named or FQCN arguments to values that should be + * injected in the matching parameters (of the constructor, of methods + * called and of controller actions). + * + * @param string $nameOrFqcn A parameter name with its "$" prefix, or an FQCN + * @param mixed $valueOrRef The value or reference to bind + * + * @return $this + */ + final public function bind(string $nameOrFqcn, mixed $valueOrRef): static + { + $valueOrRef = static::processValue($valueOrRef, true); + $bindings = $this->definition->getBindings(); + $type = $this instanceof DefaultsConfigurator ? BoundArgument::DEFAULTS_BINDING : ($this instanceof InstanceofConfigurator ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING); + $bindings[$nameOrFqcn] = new BoundArgument($valueOrRef, true, $type, $this->path ?? null); + $this->definition->setBindings($bindings); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php new file mode 100644 index 0000000..dbfb158 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +trait CallTrait +{ + /** + * Adds a method to call after service initialization. + * + * @param string $method The method name to call + * @param array $arguments An array of arguments to pass to the method call + * @param bool $returnsClone Whether the call returns the service instance or not + * + * @return $this + * + * @throws InvalidArgumentException on empty $method param + */ + final public function call(string $method, array $arguments = [], bool $returnsClone = false): static + { + $this->definition->addMethodCall($method, static::processValue($arguments, true), $returnsClone); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php new file mode 100644 index 0000000..429cebc --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait ClassTrait +{ + /** + * Sets the service class. + * + * @return $this + */ + final public function class(?string $class): static + { + $this->definition->setClass($class); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php new file mode 100644 index 0000000..a4b447c --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; + +trait ConfiguratorTrait +{ + /** + * Sets a configurator to call after the service is fully initialized. + * + * @return $this + */ + final public function configurator(string|array|ReferenceConfigurator $configurator): static + { + $this->definition->setConfigurator(static::processValue($configurator, true)); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php new file mode 100644 index 0000000..7f16ed5 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ConstructorTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait ConstructorTrait +{ + /** + * Sets a static constructor. + * + * @return $this + */ + final public function constructor(string $constructor): static + { + $this->definition->setFactory([null, $constructor]); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php new file mode 100644 index 0000000..afb56ae --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +trait DecorateTrait +{ + /** + * Sets the service that this service is decorating. + * + * @param string|null $id The decorated service id, use null to remove decoration + * + * @return $this + * + * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals + */ + final public function decorate(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static + { + $this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php new file mode 100644 index 0000000..04ff9a0 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +trait DeprecateTrait +{ + /** + * Whether this definition is deprecated, that means it should not be called anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + final public function deprecate(string $package, string $version, string $message): static + { + $this->definition->setDeprecated($package, $version, $message); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php new file mode 100644 index 0000000..1c19f1d --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; +use Symfony\Component\ExpressionLanguage\Expression; + +trait FactoryTrait +{ + /** + * Sets a factory. + * + * @return $this + */ + final public function factory(string|array|ReferenceConfigurator|Expression $factory): static + { + if (\is_string($factory) && 1 === substr_count($factory, ':')) { + $factoryParts = explode(':', $factory); + + throw new InvalidArgumentException(sprintf('Invalid factory "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $factory, $factoryParts[0], $factoryParts[1])); + } + + if ($factory instanceof Expression) { + $factory = '@='.$factory; + } + + $this->definition->setFactory(static::processValue($factory, true)); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php new file mode 100644 index 0000000..7b72181 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait FileTrait +{ + /** + * Sets a file to require before creating the service. + * + * @return $this + */ + final public function file(string $file): static + { + $this->definition->setFile($file); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php new file mode 100644 index 0000000..e3508ab --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/FromCallableTrait.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Loader\Configurator\FromCallableConfigurator; +use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; +use Symfony\Component\ExpressionLanguage\Expression; + +trait FromCallableTrait +{ + final public function fromCallable(string|array|ReferenceConfigurator|Expression $callable): FromCallableConfigurator + { + if ($this->definition instanceof ChildDefinition) { + throw new InvalidArgumentException('The configuration key "parent" is unsupported when using "fromCallable()".'); + } + + foreach ([ + 'synthetic' => 'isSynthetic', + 'factory' => 'getFactory', + 'file' => 'getFile', + 'arguments' => 'getArguments', + 'properties' => 'getProperties', + 'configurator' => 'getConfigurator', + 'calls' => 'getMethodCalls', + ] as $key => $method) { + if ($this->definition->$method()) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported when using "fromCallable()".', $key)); + } + } + + $this->definition->setFactory(['Closure', 'fromCallable']); + + if (\is_string($callable) && 1 === substr_count($callable, ':')) { + $parts = explode(':', $callable); + + throw new InvalidArgumentException(sprintf('Invalid callable "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $callable, $parts[0], $parts[1])); + } + + if ($callable instanceof Expression) { + $callable = '@='.$callable; + } + + $this->definition->setArguments([static::processValue($callable, true)]); + + if ('Closure' !== ($this->definition->getClass() ?? 'Closure')) { + $this->definition->setLazy(true); + } else { + $this->definition->setClass('Closure'); + } + + return new FromCallableConfigurator($this, $this->definition); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php new file mode 100644 index 0000000..ac4326b --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait LazyTrait +{ + /** + * Sets the lazy flag of this service. + * + * @param bool|string $lazy A FQCN to derivate the lazy proxy from or `true` to make it extend from the definition's class + * + * @return $this + */ + final public function lazy(bool|string $lazy = true): static + { + $this->definition->setLazy((bool) $lazy); + if (\is_string($lazy)) { + $this->definition->addTag('proxy', ['interface' => $lazy]); + } + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php new file mode 100644 index 0000000..4096025 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +trait ParentTrait +{ + /** + * Sets the Definition to inherit from. + * + * @return $this + * + * @throws InvalidArgumentException when parent cannot be set + */ + final public function parent(string $parent): static + { + if (!$this->allowParent) { + throw new InvalidArgumentException(sprintf('A parent cannot be defined when either "_instanceof" or "_defaults" are also defined for service prototype "%s".', $this->id)); + } + + if ($this->definition instanceof ChildDefinition) { + $this->definition->setParent($parent); + } else { + // cast Definition to ChildDefinition + $definition = serialize($this->definition); + $definition = substr_replace($definition, '53', 2, 2); + $definition = substr_replace($definition, 'Child', 44, 0); + $definition = unserialize($definition); + + $this->definition = $definition->setParent($parent); + } + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php new file mode 100644 index 0000000..0dab40f --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait PropertyTrait +{ + /** + * Sets a specific property. + * + * @return $this + */ + final public function property(string $name, mixed $value): static + { + $this->definition->setProperty($name, static::processValue($value, true)); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php new file mode 100644 index 0000000..3d88d74 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait PublicTrait +{ + /** + * @return $this + */ + final public function public(): static + { + $this->definition->setPublic(true); + + return $this; + } + + /** + * @return $this + */ + final public function private(): static + { + $this->definition->setPublic(false); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php new file mode 100644 index 0000000..801fabc --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait ShareTrait +{ + /** + * Sets if the service must be shared or not. + * + * @return $this + */ + final public function share(bool $shared = true): static + { + $this->definition->setShared($shared); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php new file mode 100644 index 0000000..5e8c4b3 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +trait SyntheticTrait +{ + /** + * Sets whether this definition is synthetic, that is not constructed by the + * container, but dynamically injected. + * + * @return $this + */ + final public function synthetic(bool $synthetic = true): static + { + $this->definition->setSynthetic($synthetic); + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php new file mode 100644 index 0000000..a38d04a --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +trait TagTrait +{ + /** + * Adds a tag for this definition. + * + * @return $this + */ + final public function tag(string $name, array $attributes = []): static + { + if ('' === $name) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" must be a non-empty string.', $this->id)); + } + + $this->validateAttributes($name, $attributes); + + $this->definition->addTag($name, $attributes); + + return $this; + } + + private function validateAttributes(string $tag, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($tag, $value, [...$path, $name]); + } elseif (!\is_scalar($value ?? '')) { + $name = implode('.', [...$path, $name]); + throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type or an array of scalar-types for service "%s", tag "%s", attribute "%s".', $this->id, $tag, $name)); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Loader/DirectoryLoader.php b/vendor/symfony/dependency-injection/Loader/DirectoryLoader.php new file mode 100644 index 0000000..d435366 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/DirectoryLoader.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +/** + * DirectoryLoader is a recursive loader to go through directories. + * + * @author Sebastien Lavoie + */ +class DirectoryLoader extends FileLoader +{ + public function load(mixed $file, ?string $type = null): mixed + { + $file = rtrim($file, '/'); + $path = $this->locator->locate($file); + $this->container->fileExists($path, false); + + foreach (scandir($path) as $dir) { + if ('.' !== $dir[0]) { + if (is_dir($path.'/'.$dir)) { + $dir .= '/'; // append / to allow recursion + } + + $this->setCurrentDir($path); + + $this->import($dir, null, false, $path); + } + } + + return null; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + if ('directory' === $type) { + return true; + } + + return null === $type && \is_string($resource) && str_ends_with($resource, '/'); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/FileLoader.php b/vendor/symfony/dependency-injection/Loader/FileLoader.php new file mode 100644 index 0000000..b39a86e --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/FileLoader.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; +use Symfony\Component\Config\Exception\LoaderLoadException; +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader; +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Exclude; +use Symfony\Component\DependencyInjection\Attribute\When; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * FileLoader is the abstract class used by all built-in loaders that are file based. + * + * @author Fabien Potencier + */ +abstract class FileLoader extends BaseFileLoader +{ + public const ANONYMOUS_ID_REGEXP = '/^\.\d+_[^~]*+~[._a-zA-Z\d]{7}$/'; + + protected ContainerBuilder $container; + protected bool $isLoadingInstanceof = false; + protected array $instanceof = []; + protected array $interfaces = []; + protected array $singlyImplemented = []; + /** @var array */ + protected array $aliases = []; + protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = true; + protected bool $prepend = false; + protected array $extensionConfigs = []; + protected int $importing = 0; + + /** + * @param bool $prepend Whether to prepend extension config instead of appending them + */ + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null, bool $prepend = false) + { + $this->container = $container; + $this->prepend = $prepend; + + parent::__construct($locator, $env); + } + + /** + * @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found + */ + public function import(mixed $resource, ?string $type = null, bool|string $ignoreErrors = false, ?string $sourceResource = null, $exclude = null): mixed + { + $args = \func_get_args(); + + if ($ignoreNotFound = 'not_found' === $ignoreErrors) { + $args[2] = false; + } elseif (!\is_bool($ignoreErrors)) { + throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors))); + } + + ++$this->importing; + try { + return parent::import(...$args); + } catch (LoaderLoadException $e) { + if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) { + throw $e; + } + + foreach ($prev->getTrace() as $frame) { + if ('import' === ($frame['function'] ?? null) && is_a($frame['class'] ?? '', Loader::class, true)) { + break; + } + } + + if (__FILE__ !== $frame['file']) { + throw $e; + } + } finally { + --$this->importing; + $this->loadExtensionConfigs(); + } + + return null; + } + + /** + * Registers a set of classes as services using PSR-4 for discovery. + * + * @param Definition $prototype A definition to use as template + * @param string $namespace The namespace prefix of classes in the scanned directory + * @param string $resource The directory to look for classes, glob-patterns allowed + * @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude + * @param string|null $source The path to the file that defines the auto-discovery rule + */ + public function registerClasses(Definition $prototype, string $namespace, string $resource, string|array|null $exclude = null, ?string $source = null): void + { + if (!str_ends_with($namespace, '\\')) { + throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": "%s".', $namespace)); + } + if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++$/', $namespace)) { + throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace)); + } + // This can happen with YAML files + if (\is_array($exclude) && \in_array(null, $exclude, true)) { + throw new InvalidArgumentException('The exclude list must not contain a "null" value.'); + } + // This can happen with XML files + if (\is_array($exclude) && \in_array('', $exclude, true)) { + throw new InvalidArgumentException('The exclude list must not contain an empty value.'); + } + + $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); + $autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null; + $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes, $source); + + $getPrototype = static fn () => clone $prototype; + $serialized = serialize($prototype); + + // avoid deep cloning if no definitions are nested + if (strpos($serialized, 'O:48:"Symfony\Component\DependencyInjection\Definition"', 55) + || strpos($serialized, 'O:53:"Symfony\Component\DependencyInjection\ChildDefinition"', 55) + ) { + // prepare for deep cloning + foreach (['Arguments', 'Properties', 'MethodCalls', 'Configurator', 'Factory', 'Bindings'] as $key) { + $serialized = serialize($prototype->{'get'.$key}()); + + if (strpos($serialized, 'O:48:"Symfony\Component\DependencyInjection\Definition"') + || strpos($serialized, 'O:53:"Symfony\Component\DependencyInjection\ChildDefinition"') + ) { + $getPrototype = static fn () => $getPrototype()->{'set'.$key}(unserialize($serialized)); + } + } + } + unset($serialized); + + foreach ($classes as $class => $errorMessage) { + if (null === $errorMessage && $autoconfigureAttributes) { + $r = $this->container->getReflectionClass($class); + if ($r->getAttributes(Exclude::class)[0] ?? null) { + $this->addContainerExcludedTag($class, $source); + continue; + } + if ($this->env) { + $attribute = null; + foreach ($r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + if ($this->env === $attribute->newInstance()->env) { + $attribute = null; + break; + } + } + if (null !== $attribute) { + $this->addContainerExcludedTag($class, $source); + continue; + } + } + } + + if (interface_exists($class, false)) { + $this->interfaces[] = $class; + } else { + $this->setDefinition($class, $definition = $getPrototype()); + if (null !== $errorMessage) { + $definition->addError($errorMessage); + + continue; + } + $definition->setClass($class); + + $interfaces = []; + foreach (class_implements($class, false) as $interface) { + $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; + $interfaces[] = $interface; + } + + if (!$autoconfigureAttributes) { + continue; + } + $r = $this->container->getReflectionClass($class); + $defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null; + foreach ($r->getAttributes(AsAlias::class) as $attr) { + /** @var AsAlias $attribute */ + $attribute = $attr->newInstance(); + $alias = $attribute->id ?? $defaultAlias; + $public = $attribute->public; + if (null === $alias) { + throw new LogicException(sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class)); + } + if (isset($this->aliases[$alias])) { + throw new LogicException(sprintf('The "%s" alias has already been defined with the #[AsAlias] attribute in "%s".', $alias, $this->aliases[$alias])); + } + $this->aliases[$alias] = new Alias($class, $public); + } + } + } + + foreach ($this->aliases as $alias => $aliasDefinition) { + $this->container->setAlias($alias, $aliasDefinition); + } + + if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) { + $this->registerAliasesForSinglyImplementedInterfaces(); + } + } + + public function registerAliasesForSinglyImplementedInterfaces(): void + { + foreach ($this->interfaces as $interface) { + if (!empty($this->singlyImplemented[$interface]) && !isset($this->aliases[$interface]) && !$this->container->has($interface)) { + $this->container->setAlias($interface, $this->singlyImplemented[$interface]); + } + } + + $this->interfaces = $this->singlyImplemented = $this->aliases = []; + } + + final protected function loadExtensionConfig(string $namespace, array $config): void + { + if (!$this->prepend) { + $this->container->loadFromExtension($namespace, $config); + + return; + } + + if ($this->importing) { + if (!isset($this->extensionConfigs[$namespace])) { + $this->extensionConfigs[$namespace] = []; + } + array_unshift($this->extensionConfigs[$namespace], $config); + + return; + } + + $this->container->prependExtensionConfig($namespace, $config); + } + + final protected function loadExtensionConfigs(): void + { + if ($this->importing || !$this->extensionConfigs) { + return; + } + + foreach ($this->extensionConfigs as $namespace => $configs) { + foreach ($configs as $config) { + $this->container->prependExtensionConfig($namespace, $config); + } + } + + $this->extensionConfigs = []; + } + + /** + * Registers a definition in the container with its instanceof-conditionals. + */ + protected function setDefinition(string $id, Definition $definition): void + { + $this->container->removeBindings($id); + + foreach ($definition->getTag('container.error') as $error) { + if (isset($error['message'])) { + $definition->addError($error['message']); + } + } + + if ($this->isLoadingInstanceof) { + if (!$definition instanceof ChildDefinition) { + throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_debug_type($definition))); + } + $this->instanceof[$id] = $definition; + } else { + $this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof)); + } + } + + private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes, ?string $source): array + { + $parameterBag = $this->container->getParameterBag(); + + $excludePaths = []; + $excludePrefix = null; + $excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns)); + foreach ($excludePatterns as $excludePattern) { + foreach ($this->glob($excludePattern, true, $resource, true, true) as $path => $info) { + $excludePrefix ??= $resource->getPrefix(); + + // normalize Windows slashes and remove trailing slashes + $excludePaths[rtrim(str_replace('\\', '/', $path), '/')] = true; + } + } + + $pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern)); + $classes = []; + $prefixLen = null; + foreach ($this->glob($pattern, true, $resource, false, false, $excludePaths) as $path => $info) { + if (null === $prefixLen) { + $prefixLen = \strlen($resource->getPrefix()); + + if ($excludePrefix && !str_starts_with($excludePrefix, $resource->getPrefix())) { + throw new InvalidArgumentException(sprintf('Invalid "exclude" pattern when importing classes for "%s": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s).', $namespace, $excludePattern, $pattern)); + } + } + + if (isset($excludePaths[str_replace('\\', '/', $path)])) { + continue; + } + + if (!str_ends_with($path, '.php')) { + continue; + } + $class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, -4)), '\\'); + + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $class)) { + continue; + } + + try { + $r = $this->container->getReflectionClass($class); + } catch (\ReflectionException $e) { + $classes[$class] = $e->getMessage(); + continue; + } + // check to make sure the expected class exists + if (!$r) { + throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern)); + } + + if ($r->isInstantiable() || $r->isInterface()) { + $classes[$class] = null; + } + + if ($autoconfigureAttributes && !$r->isInstantiable()) { + $autoconfigureAttributes->processClass($this->container, $r); + } + } + + // track only for new & removed files + if ($resource instanceof GlobResource) { + $this->container->addResource($resource); + } else { + foreach ($resource as $path) { + $this->container->fileExists($path, false); + } + } + + if (null !== $prefixLen) { + foreach ($excludePaths as $path => $_) { + $class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, str_ends_with($path, '.php') ? -4 : null)), '\\'); + $this->addContainerExcludedTag($class, $source); + } + } + + return $classes; + } + + private function addContainerExcludedTag(string $class, ?string $source): void + { + if ($this->container->has($class)) { + return; + } + + static $attributes = []; + + if (null !== $source && !isset($attributes[$source])) { + $attributes[$source] = ['source' => sprintf('in "%s/%s"', basename(\dirname($source)), basename($source))]; + } + + $this->container->register($class, $class) + ->setAbstract(true) + ->addTag('container.excluded', null !== $source ? $attributes[$source] : []); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/GlobFileLoader.php b/vendor/symfony/dependency-injection/Loader/GlobFileLoader.php new file mode 100644 index 0000000..4716f11 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/GlobFileLoader.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +/** + * GlobFileLoader loads files from a glob pattern. + * + * @author Nicolas Grekas + */ +class GlobFileLoader extends FileLoader +{ + public function load(mixed $resource, ?string $type = null): mixed + { + foreach ($this->glob($resource, false, $globResource) as $path => $info) { + $this->import($path); + } + + $this->container->addResource($globResource); + + return null; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return 'glob' === $type; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/IniFileLoader.php b/vendor/symfony/dependency-injection/Loader/IniFileLoader.php new file mode 100644 index 0000000..424fbdd --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/IniFileLoader.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * IniFileLoader loads parameters from INI files. + * + * @author Fabien Potencier + */ +class IniFileLoader extends FileLoader +{ + public function load(mixed $resource, ?string $type = null): mixed + { + $path = $this->locator->locate($resource); + + $this->container->fileExists($path); + + // first pass to catch parsing errors + $result = parse_ini_file($path, true); + if (false === $result || [] === $result) { + throw new InvalidArgumentException(sprintf('The "%s" file is not valid.', $resource)); + } + + // real raw parsing + $result = parse_ini_file($path, true, \INI_SCANNER_RAW); + + if (isset($result['parameters']) && \is_array($result['parameters'])) { + foreach ($result['parameters'] as $key => $value) { + if (\is_array($value)) { + $this->container->setParameter($key, array_map($this->phpize(...), $value)); + } else { + $this->container->setParameter($key, $this->phpize($value)); + } + } + } + + if ($this->env && \is_array($result['parameters@'.$this->env] ?? null)) { + foreach ($result['parameters@'.$this->env] as $key => $value) { + $this->container->setParameter($key, $this->phpize($value)); + } + } + + return null; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + if (!\is_string($resource)) { + return false; + } + + if (null === $type && 'ini' === pathinfo($resource, \PATHINFO_EXTENSION)) { + return true; + } + + return 'ini' === $type; + } + + /** + * Note that the following features are not supported: + * * strings with escaped quotes are not supported "foo\"bar"; + * * string concatenation ("foo" "bar"). + */ + private function phpize(string $value): mixed + { + // trim on the right as comments removal keep whitespaces + if ($value !== $v = rtrim($value)) { + $value = '""' === substr_replace($v, '', 1, -1) ? substr($v, 1, -1) : $v; + } + $lowercaseValue = strtolower($value); + + return match (true) { + \defined($value) => \constant($value), + 'yes' === $lowercaseValue, + 'on' === $lowercaseValue => true, + 'no' === $lowercaseValue, + 'off' === $lowercaseValue, + 'none' === $lowercaseValue => false, + isset($value[1]) && ( + ("'" === $value[0] && "'" === $value[\strlen($value) - 1]) + || ('"' === $value[0] && '"' === $value[\strlen($value) - 1]) + ) => substr($value, 1, -1), // quoted string + default => XmlUtils::phpize($value), + }; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/PhpFileLoader.php b/vendor/symfony/dependency-injection/Loader/PhpFileLoader.php new file mode 100644 index 0000000..d160080 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/PhpFileLoader.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; +use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface; +use Symfony\Component\Config\Builder\ConfigBuilderInterface; +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\DependencyInjection\Attribute\When; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * PhpFileLoader loads service definitions from a PHP file. + * + * The PHP file is required and the $container variable can be + * used within the file to change the container. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false; + private ?ConfigBuilderGeneratorInterface $generator; + + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null, ?ConfigBuilderGeneratorInterface $generator = null, bool $prepend = false) + { + parent::__construct($container, $locator, $env, $prepend); + $this->generator = $generator; + } + + public function load(mixed $resource, ?string $type = null): mixed + { + // the container and loader variables are exposed to the included file below + $container = $this->container; + $loader = $this; + + $path = $this->locator->locate($resource); + $this->setCurrentDir(\dirname($path)); + $this->container->fileExists($path); + + // the closure forbids access to the private scope in the included file + $load = \Closure::bind(function ($path, $env) use ($container, $loader, $resource, $type) { + return include $path; + }, $this, ProtectedPhpFileLoader::class); + + try { + $callback = $load($path, $this->env); + + if (\is_object($callback) && \is_callable($callback)) { + $this->executeCallback($callback, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $path); + } + } finally { + $this->instanceof = []; + $this->registerAliasesForSinglyImplementedInterfaces(); + } + + return null; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + if (!\is_string($resource)) { + return false; + } + + if (null === $type && 'php' === pathinfo($resource, \PATHINFO_EXTENSION)) { + return true; + } + + return 'php' === $type; + } + + /** + * Resolve the parameters to the $callback and execute it. + */ + private function executeCallback(callable $callback, ContainerConfigurator $containerConfigurator, string $path): void + { + $callback = $callback(...); + $arguments = []; + $configBuilders = []; + $r = new \ReflectionFunction($callback); + + $attribute = null; + foreach ($r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + if ($this->env === $attribute->newInstance()->env) { + $attribute = null; + break; + } + } + if (null !== $attribute) { + return; + } + + foreach ($r->getParameters() as $parameter) { + $reflectionType = $parameter->getType(); + if (!$reflectionType instanceof \ReflectionNamedType) { + throw new \InvalidArgumentException(sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s" or "%s").', $parameter->getName(), $path, ContainerConfigurator::class, ContainerBuilder::class)); + } + $type = $reflectionType->getName(); + + switch ($type) { + case ContainerConfigurator::class: + $arguments[] = $containerConfigurator; + break; + case ContainerBuilder::class: + $arguments[] = $this->container; + break; + case FileLoader::class: + case self::class: + $arguments[] = $this; + break; + case 'string': + if (null !== $this->env && 'env' === $parameter->getName()) { + $arguments[] = $this->env; + break; + } + // no break + default: + try { + $configBuilder = $this->configBuilder($type); + } catch (InvalidArgumentException|\LogicException $e) { + throw new \InvalidArgumentException(sprintf('Could not resolve argument "%s" for "%s".', $type.' $'.$parameter->getName(), $path), 0, $e); + } + $configBuilders[] = $configBuilder; + $arguments[] = $configBuilder; + } + } + + // Force load ContainerConfigurator to make env(), param() etc available. + class_exists(ContainerConfigurator::class); + + ++$this->importing; + try { + $callback(...$arguments); + } finally { + --$this->importing; + } + + foreach ($configBuilders as $configBuilder) { + $this->loadExtensionConfig($configBuilder->getExtensionAlias(), ContainerConfigurator::processValue($configBuilder->toArray())); + } + + $this->loadExtensionConfigs(); + } + + /** + * @param string $namespace FQCN string for a class implementing ConfigBuilderInterface + */ + private function configBuilder(string $namespace): ConfigBuilderInterface + { + if (!class_exists(ConfigBuilderGenerator::class)) { + throw new \LogicException('You cannot use the config builder as the Config component is not installed. Try running "composer require symfony/config".'); + } + + if (null === $this->generator) { + throw new \LogicException('You cannot use the ConfigBuilders without providing a class implementing ConfigBuilderGeneratorInterface.'); + } + + // If class exists and implements ConfigBuilderInterface + if (class_exists($namespace) && is_subclass_of($namespace, ConfigBuilderInterface::class)) { + return new $namespace(); + } + + // If it does not start with Symfony\Config\ we don't know how to handle this + if (!str_starts_with($namespace, 'Symfony\\Config\\')) { + throw new InvalidArgumentException(sprintf('Could not find or generate class "%s".', $namespace)); + } + + // Try to get the extension alias + $alias = Container::underscore(substr($namespace, 15, -6)); + + if (str_contains($alias, '\\')) { + throw new InvalidArgumentException('You can only use "root" ConfigBuilders from "Symfony\\Config\\" namespace. Nested classes like "Symfony\\Config\\Framework\\CacheConfig" cannot be used.'); + } + + if (!$this->container->hasExtension($alias)) { + $extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); + throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($namespace, null, $alias, $extensions)); + } + + $extension = $this->container->getExtension($alias); + if (!$extension instanceof ConfigurationExtensionInterface) { + throw new \LogicException(sprintf('You cannot use the config builder for "%s" because the extension does not implement "%s".', $namespace, ConfigurationExtensionInterface::class)); + } + + $configuration = $extension->getConfiguration([], $this->container); + $loader = $this->generator->build($configuration); + + return $loader(); + } +} + +/** + * @internal + */ +final class ProtectedPhpFileLoader extends PhpFileLoader +{ +} diff --git a/vendor/symfony/dependency-injection/Loader/UndefinedExtensionHandler.php b/vendor/symfony/dependency-injection/Loader/UndefinedExtensionHandler.php new file mode 100644 index 0000000..953104e --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/UndefinedExtensionHandler.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +class UndefinedExtensionHandler +{ + private const BUNDLE_EXTENSIONS = [ + 'debug' => 'DebugBundle', + 'doctrine' => 'DoctrineBundle', + 'doctrine_migrations' => 'DoctrineMigrationsBundle', + 'framework' => 'FrameworkBundle', + 'maker' => 'MakerBundle', + 'monolog' => 'MonologBundle', + 'security' => 'SecurityBundle', + 'twig' => 'TwigBundle', + 'twig_component' => 'TwigComponentBundle', + 'ux_icons' => 'UXIconsBundle', + 'web_profiler' => 'WebProfilerBundle', + ]; + + public static function getErrorMessage(string $extensionName, ?string $loadingFilePath, string $namespaceOrAlias, array $foundExtensionNamespaces): string + { + $message = ''; + if (isset(self::BUNDLE_EXTENSIONS[$extensionName])) { + $message .= sprintf('Did you forget to install or enable the %s? ', self::BUNDLE_EXTENSIONS[$extensionName]); + } + + $message .= match (true) { + \is_string($loadingFilePath) => sprintf('There is no extension able to load the configuration for "%s" (in "%s"). ', $extensionName, $loadingFilePath), + default => sprintf('There is no extension able to load the configuration for "%s". ', $extensionName), + }; + + $message .= sprintf('Looked for namespace "%s", found "%s".', $namespaceOrAlias, $foundExtensionNamespaces ? implode('", "', $foundExtensionNamespaces) : 'none'); + + return $message; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php b/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php new file mode 100644 index 0000000..668a754 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php @@ -0,0 +1,886 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * XmlFileLoader loads XML files service definitions. + * + * @author Fabien Potencier + */ +class XmlFileLoader extends FileLoader +{ + public const NS = 'http://symfony.com/schema/dic/services'; + + protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false; + + public function load(mixed $resource, ?string $type = null): mixed + { + $path = $this->locator->locate($resource); + + $xml = $this->parseFileToDOM($path); + + $this->container->fileExists($path); + + $this->loadXml($xml, $path); + + if ($this->env) { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + foreach ($xpath->query(sprintf('//container:when[@env="%s"]', $this->env)) ?: [] as $root) { + $env = $this->env; + $this->env = null; + try { + $this->loadXml($xml, $path, $root); + } finally { + $this->env = $env; + } + } + } + + return null; + } + + private function loadXml(\DOMDocument $xml, string $path, ?\DOMNode $root = null): void + { + $defaults = $this->getServiceDefaults($xml, $path, $root); + + // anonymous services + $this->processAnonymousServices($xml, $path, $root); + + // imports + $this->parseImports($xml, $path, $root); + + // parameters + $this->parseParameters($xml, $path, $root); + + // extensions + $this->loadFromExtensions($xml, $root); + + // services + try { + $this->parseDefinitions($xml, $path, $defaults, $root); + } finally { + $this->instanceof = []; + $this->registerAliasesForSinglyImplementedInterfaces(); + } + } + + public function supports(mixed $resource, ?string $type = null): bool + { + if (!\is_string($resource)) { + return false; + } + + if (null === $type && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION)) { + return true; + } + + return 'xml' === $type; + } + + private function parseParameters(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void + { + if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) { + $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file)); + } + } + + private function parseImports(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void + { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + if (false === $imports = $xpath->query('.//container:imports/container:import', $root)) { + return; + } + + $defaultDirectory = \dirname($file); + foreach ($imports as $import) { + $this->setCurrentDir($defaultDirectory); + $this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, XmlUtils::phpize($import->getAttribute('ignore-errors')) ?: false, $file); + } + } + + private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, ?\DOMNode $root = null): void + { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + if (false === $services = $xpath->query('.//container:services/container:service|.//container:services/container:prototype|.//container:services/container:stack', $root)) { + return; + } + $this->setCurrentDir(\dirname($file)); + + $this->instanceof = []; + $this->isLoadingInstanceof = true; + $instanceof = $xpath->query('.//container:services/container:instanceof', $root); + foreach ($instanceof as $service) { + $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition())); + } + + $this->isLoadingInstanceof = false; + foreach ($services as $service) { + if ('stack' === $service->tagName) { + $service->setAttribute('parent', '-'); + $definition = $this->parseDefinition($service, $file, $defaults) + ->setTags(array_merge_recursive(['container.stack' => [[]]], $defaults->getTags())) + ; + $this->setDefinition($id = (string) $service->getAttribute('id'), $definition); + $stack = []; + + foreach ($this->getChildren($service, 'service') as $k => $frame) { + $k = $frame->getAttribute('id') ?: $k; + $frame->setAttribute('id', $id.'" at index "'.$k); + + if ($alias = $frame->getAttribute('alias')) { + $this->validateAlias($frame, $file); + $stack[$k] = new Reference($alias); + } else { + $stack[$k] = $this->parseDefinition($frame, $file, $defaults) + ->setInstanceofConditionals($this->instanceof); + } + } + + $definition->setArguments($stack); + } elseif (null !== $definition = $this->parseDefinition($service, $file, $defaults)) { + if ('prototype' === $service->tagName) { + $excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue'); + if ($service->hasAttribute('exclude')) { + if (\count($excludes) > 0) { + throw new InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $excludes = [$service->getAttribute('exclude')]; + } + $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes, $file); + } else { + $this->setDefinition((string) $service->getAttribute('id'), $definition); + } + } + } + } + + private function getServiceDefaults(\DOMDocument $xml, string $file, ?\DOMNode $root = null): Definition + { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + if (null === $defaultsNode = $xpath->query('.//container:services/container:defaults', $root)->item(0)) { + return new Definition(); + } + + $defaultsNode->setAttribute('id', ''); + + return $this->parseDefinition($defaultsNode, $file, new Definition()); + } + + /** + * Parses an individual Definition. + */ + private function parseDefinition(\DOMElement $service, string $file, Definition $defaults): ?Definition + { + if ($alias = $service->getAttribute('alias')) { + $this->validateAlias($service, $file); + + $this->container->setAlias($service->getAttribute('id'), $alias = new Alias($alias)); + if ($publicAttr = $service->getAttribute('public')) { + $alias->setPublic(XmlUtils::phpize($publicAttr)); + } elseif ($defaults->getChanges()['public'] ?? false) { + $alias->setPublic($defaults->isPublic()); + } + + if ($deprecated = $this->getChildren($service, 'deprecated')) { + $message = $deprecated[0]->nodeValue ?: ''; + $package = $deprecated[0]->getAttribute('package') ?: ''; + $version = $deprecated[0]->getAttribute('version') ?: ''; + + if (!$deprecated[0]->hasAttribute('package')) { + throw new InvalidArgumentException(sprintf('Missing attribute "package" at node "deprecated" in "%s".', $file)); + } + + if (!$deprecated[0]->hasAttribute('version')) { + throw new InvalidArgumentException(sprintf('Missing attribute "version" at node "deprecated" in "%s".', $file)); + } + + $alias->setDeprecated($package, $version, $message); + } + + return null; + } + + if ($this->isLoadingInstanceof) { + $definition = new ChildDefinition(''); + } elseif ($parent = $service->getAttribute('parent')) { + $definition = new ChildDefinition($parent); + } else { + $definition = new Definition(); + } + + if ($defaults->getChanges()['public'] ?? false) { + $definition->setPublic($defaults->isPublic()); + } + $definition->setAutowired($defaults->isAutowired()); + $definition->setAutoconfigured($defaults->isAutoconfigured()); + $definition->setChanges([]); + + foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) { + if ($value = $service->getAttribute($key)) { + $method = 'set'.$key; + $definition->$method(XmlUtils::phpize($value)); + } + } + + if ($value = $service->getAttribute('lazy')) { + $definition->setLazy((bool) $value = XmlUtils::phpize($value)); + if (\is_string($value)) { + $definition->addTag('proxy', ['interface' => $value]); + } + } + + if ($value = $service->getAttribute('autowire')) { + $definition->setAutowired(XmlUtils::phpize($value)); + } + + if ($value = $service->getAttribute('autoconfigure')) { + $definition->setAutoconfigured(XmlUtils::phpize($value)); + } + + if ($files = $this->getChildren($service, 'file')) { + $definition->setFile($files[0]->nodeValue); + } + + if ($deprecated = $this->getChildren($service, 'deprecated')) { + $message = $deprecated[0]->nodeValue ?: ''; + $package = $deprecated[0]->getAttribute('package') ?: ''; + $version = $deprecated[0]->getAttribute('version') ?: ''; + + if (!$deprecated[0]->hasAttribute('package')) { + throw new InvalidArgumentException(sprintf('Missing attribute "package" at node "deprecated" in "%s".', $file)); + } + + if (!$deprecated[0]->hasAttribute('version')) { + throw new InvalidArgumentException(sprintf('Missing attribute "version" at node "deprecated" in "%s".', $file)); + } + + $definition->setDeprecated($package, $version, $message); + } + + $definition->setArguments($this->getArgumentsAsPhp($service, 'argument', $file, $definition instanceof ChildDefinition)); + $definition->setProperties($this->getArgumentsAsPhp($service, 'property', $file)); + + if ($factories = $this->getChildren($service, 'factory')) { + $factory = $factories[0]; + if ($function = $factory->getAttribute('function')) { + $definition->setFactory($function); + } elseif ($expression = $factory->getAttribute('expression')) { + if (!class_exists(Expression::class)) { + throw new \LogicException('The "expression" attribute cannot be used on factories without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + $definition->setFactory('@='.$expression); + } else { + if ($childService = $factory->getAttribute('service')) { + $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); + } else { + $class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null; + } + + $definition->setFactory([$class, $factory->getAttribute('method') ?: '__invoke']); + } + } + + if ($constructor = $service->getAttribute('constructor')) { + if (null !== $definition->getFactory()) { + throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $service->getAttribute('id'))); + } + + $definition->setFactory([null, $constructor]); + } + + if ($configurators = $this->getChildren($service, 'configurator')) { + $configurator = $configurators[0]; + if ($function = $configurator->getAttribute('function')) { + $definition->setConfigurator($function); + } else { + if ($childService = $configurator->getAttribute('service')) { + $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); + } else { + $class = $configurator->getAttribute('class'); + } + + $definition->setConfigurator([$class, $configurator->getAttribute('method') ?: '__invoke']); + } + } + + foreach ($this->getChildren($service, 'call') as $call) { + $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), XmlUtils::phpize($call->getAttribute('returns-clone'))); + } + + $tags = $this->getChildren($service, 'tag'); + + foreach ($tags as $tag) { + $tagNameComesFromAttribute = $tag->childElementCount || '' === $tag->nodeValue; + if ('' === $tagName = $tagNameComesFromAttribute ? $tag->getAttribute('name') : $tag->nodeValue) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', (string) $service->getAttribute('id'), $file)); + } + + $parameters = $this->getTagAttributes($tag, sprintf('The attribute name of tag "%s" for service "%s" in %s must be a non-empty string.', $tagName, (string) $service->getAttribute('id'), $file)); + foreach ($tag->attributes as $name => $node) { + if ($tagNameComesFromAttribute && 'name' === $name) { + continue; + } + + if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) { + $parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue); + } + // keep not normalized key + $parameters[$name] = XmlUtils::phpize($node->nodeValue); + } + + $definition->addTag($tagName, $parameters); + } + + $definition->setTags(array_merge_recursive($definition->getTags(), $defaults->getTags())); + + $bindings = $this->getArgumentsAsPhp($service, 'bind', $file); + $bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING; + foreach ($bindings as $argument => $value) { + $bindings[$argument] = new BoundArgument($value, true, $bindingType, $file); + } + + // deep clone, to avoid multiple process of the same instance in the passes + $bindings = array_merge(unserialize(serialize($defaults->getBindings())), $bindings); + + if ($bindings) { + $definition->setBindings($bindings); + } + + if ($decorates = $service->getAttribute('decorates')) { + $decorationOnInvalid = $service->getAttribute('decoration-on-invalid') ?: 'exception'; + if ('exception' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('ignore' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif ('null' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } else { + throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration-on-invalid" on service "%s". Did you mean "exception", "ignore" or "null" in "%s"?', $decorationOnInvalid, $service->getAttribute('id'), $file)); + } + + $renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null; + $priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0; + + $definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior); + } + + if ($callable = $this->getChildren($service, 'from-callable')) { + if ($definition instanceof ChildDefinition) { + throw new InvalidArgumentException(sprintf('Attribute "parent" is unsupported when using "" on service "%s".', (string) $service->getAttribute('id'))); + } + + foreach ([ + 'Attribute "synthetic"' => 'isSynthetic', + 'Attribute "file"' => 'getFile', + 'Tag ""' => 'getFactory', + 'Tag ""' => 'getArguments', + 'Tag ""' => 'getProperties', + 'Tag ""' => 'getConfigurator', + 'Tag ""' => 'getMethodCalls', + ] as $key => $method) { + if ($definition->$method()) { + throw new InvalidArgumentException($key.sprintf(' is unsupported when using "" on service "%s".', (string) $service->getAttribute('id'))); + } + } + + $definition->setFactory(['Closure', 'fromCallable']); + + if ('Closure' !== ($definition->getClass() ?? 'Closure')) { + $definition->setLazy(true); + } else { + $definition->setClass('Closure'); + } + + $callable = $callable[0]; + if ($function = $callable->getAttribute('function')) { + $definition->setArguments([$function]); + } elseif ($expression = $callable->getAttribute('expression')) { + if (!class_exists(Expression::class)) { + throw new \LogicException('The "expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + $definition->setArguments(['@='.$expression]); + } else { + if ($childService = $callable->getAttribute('service')) { + $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); + } else { + $class = $callable->hasAttribute('class') ? $callable->getAttribute('class') : null; + } + + $definition->setArguments([[$class, $callable->getAttribute('method') ?: '__invoke']]); + } + } + + return $definition; + } + + /** + * Parses an XML file to a \DOMDocument. + * + * @throws InvalidArgumentException When loading of XML file returns error + */ + private function parseFileToDOM(string $file): \DOMDocument + { + try { + $dom = XmlUtils::loadFile($file, $this->validateSchema(...)); + } catch (\InvalidArgumentException $e) { + $invalidSecurityElements = []; + $errors = explode("\n", $e->getMessage()); + foreach ($errors as $i => $error) { + if (preg_match("#^\[ERROR 1871] Element '\{http://symfony\.com/schema/dic/security}([^']+)'#", $error, $matches)) { + $invalidSecurityElements[$i] = $matches[1]; + } + } + if ($invalidSecurityElements) { + $dom = XmlUtils::loadFile($file); + + foreach ($invalidSecurityElements as $errorIndex => $tagName) { + foreach ($dom->getElementsByTagNameNS('http://symfony.com/schema/dic/security', $tagName) as $element) { + if (!$parent = $element->parentNode) { + continue; + } + if ('http://symfony.com/schema/dic/security' !== $parent->namespaceURI) { + continue; + } + if ('provider' === $parent->localName || 'firewall' === $parent->localName) { + unset($errors[$errorIndex]); + } + } + } + } + if ($errors) { + throw new InvalidArgumentException(sprintf('Unable to parse file "%s": ', $file).implode("/n", $errors), $e->getCode(), $e); + } + } + + $this->validateExtensions($dom, $file); + + return $dom; + } + + /** + * Processes anonymous services. + */ + private function processAnonymousServices(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void + { + $definitions = []; + $count = 0; + $suffix = '~'.ContainerBuilder::hash($file); + + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + // anonymous services as arguments/properties + if (false !== $nodes = $xpath->query('.//container:argument[@type="service"][not(@id)]|.//container:property[@type="service"][not(@id)]|.//container:bind[not(@id)]|.//container:factory[not(@service)]|.//container:configurator[not(@service)]', $root)) { + foreach ($nodes as $node) { + if ($services = $this->getChildren($node, 'service')) { + // give it a unique name + $id = sprintf('.%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')).$suffix); + $node->setAttribute('id', $id); + $node->setAttribute('service', $id); + + $definitions[$id] = [$services[0], $file]; + $services[0]->setAttribute('id', $id); + + // anonymous services are always private + // we could not use the constant false here, because of XML parsing + $services[0]->setAttribute('public', 'false'); + } + } + } + + // anonymous services "in the wild" + if (false !== $nodes = $xpath->query('.//container:services/container:service[not(@id)]', $root)) { + foreach ($nodes as $node) { + throw new InvalidArgumentException(sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo())); + } + } + + // resolve definitions + uksort($definitions, 'strnatcmp'); + foreach (array_reverse($definitions) as $id => [$domElement, $file]) { + if (null !== $definition = $this->parseDefinition($domElement, $file, new Definition())) { + $this->setDefinition($id, $definition); + } + } + } + + private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file, bool $isChildDefinition = false): array + { + $arguments = []; + foreach ($this->getChildren($node, $name) as $arg) { + if ($arg->hasAttribute('name')) { + $arg->setAttribute('key', $arg->getAttribute('name')); + } + + // this is used by ChildDefinition to overwrite a specific + // argument of the parent definition + if ($arg->hasAttribute('index')) { + $key = ($isChildDefinition ? 'index_' : '').$arg->getAttribute('index'); + } elseif (!$arg->hasAttribute('key')) { + // Append an empty argument, then fetch its key to overwrite it later + $arguments[] = null; + $keys = array_keys($arguments); + $key = array_pop($keys); + } else { + $key = $arg->getAttribute('key'); + } + + $trim = $arg->hasAttribute('trim') && XmlUtils::phpize($arg->getAttribute('trim')); + $onInvalid = $arg->getAttribute('on-invalid'); + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('ignore' == $onInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif ('ignore_uninitialized' == $onInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; + } elseif ('null' == $onInvalid) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } + + switch ($type = $arg->getAttribute('type')) { + case 'service': + if ('' === $arg->getAttribute('id')) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file)); + } + + $arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior); + break; + case 'expression': + if (!class_exists(Expression::class)) { + throw new \LogicException('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + + $arguments[$key] = new Expression($arg->nodeValue); + break; + case 'collection': + $arguments[$key] = $this->getArgumentsAsPhp($arg, $name, $file); + break; + case 'iterator': + $arg = $this->getArgumentsAsPhp($arg, $name, $file); + $arguments[$key] = new IteratorArgument($arg); + break; + case 'closure': + case 'service_closure': + if ('' !== $arg->getAttribute('id')) { + $arg = new Reference($arg->getAttribute('id'), $invalidBehavior); + } else { + $arg = $this->getArgumentsAsPhp($arg, $name, $file); + } + $arguments[$key] = match ($type) { + 'service_closure' => new ServiceClosureArgument($arg), + 'closure' => (new Definition('Closure')) + ->setFactory(['Closure', 'fromCallable']) + ->addArgument($arg), + }; + break; + case 'service_locator': + $arg = $this->getArgumentsAsPhp($arg, $name, $file); + $arguments[$key] = new ServiceLocatorArgument($arg); + break; + case 'tagged': + case 'tagged_iterator': + case 'tagged_locator': + $forLocator = 'tagged_locator' === $type; + + if (!$arg->getAttribute('tag')) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file)); + } + + $excludes = array_column($this->getChildren($arg, 'exclude'), 'nodeValue'); + if ($arg->hasAttribute('exclude')) { + if (\count($excludes) > 0) { + throw new InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $excludes = [$arg->getAttribute('exclude')]; + } + + $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes, !$arg->hasAttribute('exclude-self') || XmlUtils::phpize($arg->getAttribute('exclude-self'))); + + if ($forLocator) { + $arguments[$key] = new ServiceLocatorArgument($arguments[$key]); + } + break; + case 'binary': + if (false === $value = base64_decode($arg->nodeValue)) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="binary" is not a valid base64 encoded string.', $name)); + } + $arguments[$key] = $value; + break; + case 'abstract': + $arguments[$key] = new AbstractArgument($arg->nodeValue); + break; + case 'string': + $arguments[$key] = $trim ? trim($arg->nodeValue) : $arg->nodeValue; + break; + case 'constant': + $arguments[$key] = \constant(trim($arg->nodeValue)); + break; + default: + $arguments[$key] = XmlUtils::phpize($trim ? trim($arg->nodeValue) : $arg->nodeValue); + } + } + + return $arguments; + } + + /** + * Get child elements by name. + * + * @return \DOMElement[] + */ + private function getChildren(\DOMNode $node, string $name): array + { + $children = []; + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement && $child->localName === $name && self::NS === $child->namespaceURI) { + $children[] = $child; + } + } + + return $children; + } + + private function getTagAttributes(\DOMNode $node, string $missingName): array + { + $parameters = []; + $children = $this->getChildren($node, 'attribute'); + + foreach ($children as $childNode) { + if ('' === $name = $childNode->getAttribute('name')) { + throw new InvalidArgumentException($missingName); + } + + if ($this->getChildren($childNode, 'attribute')) { + $parameters[$name] = $this->getTagAttributes($childNode, $missingName); + } else { + if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) { + $parameters[$normalizedName] = XmlUtils::phpize($childNode->nodeValue); + } + // keep not normalized key + $parameters[$name] = XmlUtils::phpize($childNode->nodeValue); + } + } + + return $parameters; + } + + /** + * Validates a documents XML schema. + * + * @throws RuntimeException When extension references a non-existent XSD file + */ + public function validateSchema(\DOMDocument $dom): bool + { + $schemaLocations = ['http://symfony.com/schema/dic/services' => str_replace('\\', '/', __DIR__.'/schema/dic/services/services-1.0.xsd')]; + + if ($element = $dom->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')) { + $items = preg_split('/\s+/', $element); + for ($i = 0, $nb = \count($items); $i < $nb; $i += 2) { + if (!$this->container->hasExtension($items[$i])) { + continue; + } + + if (($extension = $this->container->getExtension($items[$i])) && false !== $extension->getXsdValidationBasePath()) { + $ns = $extension->getNamespace(); + $path = str_replace([$ns, str_replace('http://', 'https://', $ns)], str_replace('\\', '/', $extension->getXsdValidationBasePath()).'/', $items[$i + 1]); + + if (!is_file($path)) { + throw new RuntimeException(sprintf('Extension "%s" references a non-existent XSD file "%s".', get_debug_type($extension), $path)); + } + + $schemaLocations[$items[$i]] = $path; + } + } + } + + $tmpfiles = []; + $imports = ''; + foreach ($schemaLocations as $namespace => $location) { + $parts = explode('/', $location); + $locationstart = 'file:///'; + if (0 === stripos($location, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + if ($tmpfile) { + copy($location, $tmpfile); + $tmpfiles[] = $tmpfile; + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } else { + array_shift($parts); + $locationstart = 'phar:///'; + } + } elseif ('\\' === \DIRECTORY_SEPARATOR && str_starts_with($location, '\\\\')) { + $locationstart = ''; + } + $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $location = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); + + $imports .= sprintf(' '."\n", $namespace, $location); + } + + $source = << + + + +$imports + +EOF + ; + + if ($this->shouldEnableEntityLoader()) { + $disableEntities = libxml_disable_entity_loader(false); + $valid = @$dom->schemaValidateSource($source); + libxml_disable_entity_loader($disableEntities); + } else { + $valid = @$dom->schemaValidateSource($source); + } + foreach ($tmpfiles as $tmpfile) { + @unlink($tmpfile); + } + + return $valid; + } + + private function shouldEnableEntityLoader(): bool + { + static $dom, $schema; + if (null === $dom) { + $dom = new \DOMDocument(); + $dom->loadXML(''); + + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $schema = ' + + +'; + file_put_contents($tmpfile, ' + + + +'); + } + + return !@$dom->schemaValidateSource($schema); + } + + private function validateAlias(\DOMElement $alias, string $file): void + { + foreach ($alias->attributes as $name => $node) { + if (!\in_array($name, ['alias', 'id', 'public'])) { + throw new InvalidArgumentException(sprintf('Invalid attribute "%s" defined for alias "%s" in "%s".', $name, $alias->getAttribute('id'), $file)); + } + } + + foreach ($alias->childNodes as $child) { + if (!$child instanceof \DOMElement || self::NS !== $child->namespaceURI) { + continue; + } + if ('deprecated' !== $child->localName) { + throw new InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file)); + } + } + } + + /** + * Validates an extension. + * + * @throws InvalidArgumentException When no extension is found corresponding to a tag + */ + private function validateExtensions(\DOMDocument $dom, string $file): void + { + foreach ($dom->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) { + continue; + } + + // can it be handled by an extension? + if (!$this->prepend && !$this->container->hasExtension($node->namespaceURI)) { + $extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getNamespace(), $this->container->getExtensions())); + throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($node->tagName, $file, $node->namespaceURI, $extensionNamespaces)); + } + } + } + + /** + * Loads from an extension. + */ + private function loadFromExtensions(\DOMDocument $xml): void + { + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) { + continue; + } + + $values = static::convertDomElementToArray($node); + if (!\is_array($values)) { + $values = []; + } + + $this->loadExtensionConfig($node->namespaceURI, $values); + } + + $this->loadExtensionConfigs(); + } + + /** + * Converts a \DOMElement object to a PHP array. + * + * The following rules applies during the conversion: + * + * * Each tag is converted to a key value or an array + * if there is more than one "value" + * + * * The content of a tag is set under a "value" key (bar) + * if the tag also has some nested tags + * + * * The attributes are converted to keys () + * + * * The nested-tags are converted to keys (bar) + * + * @param \DOMElement $element A \DOMElement instance + */ + public static function convertDomElementToArray(\DOMElement $element): mixed + { + return XmlUtils::convertDomElementToArray($element, false); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php b/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php new file mode 100644 index 0000000..fa98ca1 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php @@ -0,0 +1,989 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Tag\TaggedValue; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileLoader loads YAML files service definitions. + * + * @author Fabien Potencier + */ +class YamlFileLoader extends FileLoader +{ + private const SERVICE_KEYWORDS = [ + 'alias' => 'alias', + 'parent' => 'parent', + 'class' => 'class', + 'shared' => 'shared', + 'synthetic' => 'synthetic', + 'lazy' => 'lazy', + 'public' => 'public', + 'abstract' => 'abstract', + 'deprecated' => 'deprecated', + 'factory' => 'factory', + 'file' => 'file', + 'arguments' => 'arguments', + 'properties' => 'properties', + 'configurator' => 'configurator', + 'calls' => 'calls', + 'tags' => 'tags', + 'decorates' => 'decorates', + 'decoration_inner_name' => 'decoration_inner_name', + 'decoration_priority' => 'decoration_priority', + 'decoration_on_invalid' => 'decoration_on_invalid', + 'autowire' => 'autowire', + 'autoconfigure' => 'autoconfigure', + 'bind' => 'bind', + 'constructor' => 'constructor', + ]; + + private const PROTOTYPE_KEYWORDS = [ + 'resource' => 'resource', + 'namespace' => 'namespace', + 'exclude' => 'exclude', + 'parent' => 'parent', + 'shared' => 'shared', + 'lazy' => 'lazy', + 'public' => 'public', + 'abstract' => 'abstract', + 'deprecated' => 'deprecated', + 'factory' => 'factory', + 'arguments' => 'arguments', + 'properties' => 'properties', + 'configurator' => 'configurator', + 'calls' => 'calls', + 'tags' => 'tags', + 'autowire' => 'autowire', + 'autoconfigure' => 'autoconfigure', + 'bind' => 'bind', + 'constructor' => 'constructor', + ]; + + private const INSTANCEOF_KEYWORDS = [ + 'shared' => 'shared', + 'lazy' => 'lazy', + 'public' => 'public', + 'properties' => 'properties', + 'configurator' => 'configurator', + 'calls' => 'calls', + 'tags' => 'tags', + 'autowire' => 'autowire', + 'bind' => 'bind', + 'constructor' => 'constructor', + ]; + + private const DEFAULTS_KEYWORDS = [ + 'public' => 'public', + 'tags' => 'tags', + 'autowire' => 'autowire', + 'autoconfigure' => 'autoconfigure', + 'bind' => 'bind', + ]; + + protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false; + + private YamlParser $yamlParser; + private int $anonymousServicesCount; + private string $anonymousServicesSuffix; + + public function load(mixed $resource, ?string $type = null): mixed + { + $path = $this->locator->locate($resource); + + $content = $this->loadFile($path); + + $this->container->fileExists($path); + + // empty file + if (null === $content) { + return null; + } + + ++$this->importing; + try { + $this->loadContent($content, $path); + + // per-env configuration + if ($this->env && isset($content['when@'.$this->env])) { + if (!\is_array($content['when@'.$this->env])) { + throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path)); + } + + $env = $this->env; + $this->env = null; + try { + $this->loadContent($content['when@'.$env], $path); + } finally { + $this->env = $env; + } + } + } finally { + --$this->importing; + } + $this->loadExtensionConfigs(); + + return null; + } + + private function loadContent(array $content, string $path): void + { + // imports + $this->parseImports($content, $path); + + // parameters + if (isset($content['parameters'])) { + if (!\is_array($content['parameters'])) { + throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in "%s". Check your YAML syntax.', $path)); + } + + foreach ($content['parameters'] as $key => $value) { + $this->container->setParameter($key, $this->resolveServices($value, $path, true)); + } + } + + // extensions + $this->loadFromExtensions($content); + + // services + $this->anonymousServicesCount = 0; + $this->anonymousServicesSuffix = '~'.ContainerBuilder::hash($path); + $this->setCurrentDir(\dirname($path)); + try { + $this->parseDefinitions($content, $path); + } finally { + $this->instanceof = []; + $this->registerAliasesForSinglyImplementedInterfaces(); + } + } + + public function supports(mixed $resource, ?string $type = null): bool + { + if (!\is_string($resource)) { + return false; + } + + if (null === $type && \in_array(pathinfo($resource, \PATHINFO_EXTENSION), ['yaml', 'yml'], true)) { + return true; + } + + return \in_array($type, ['yaml', 'yml'], true); + } + + private function parseImports(array $content, string $file): void + { + if (!isset($content['imports'])) { + return; + } + + if (!\is_array($content['imports'])) { + throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in "%s". Check your YAML syntax.', $file)); + } + + $defaultDirectory = \dirname($file); + foreach ($content['imports'] as $import) { + if (!\is_array($import)) { + $import = ['resource' => $import]; + } + if (!isset($import['resource'])) { + throw new InvalidArgumentException(sprintf('An import should provide a resource in "%s". Check your YAML syntax.', $file)); + } + + $this->setCurrentDir($defaultDirectory); + $this->import($import['resource'], $import['type'] ?? null, $import['ignore_errors'] ?? false, $file); + } + } + + private function parseDefinitions(array $content, string $file, bool $trackBindings = true): void + { + if (!isset($content['services'])) { + return; + } + + if (!\is_array($content['services'])) { + throw new InvalidArgumentException(sprintf('The "services" key should contain an array in "%s". Check your YAML syntax.', $file)); + } + + if (\array_key_exists('_instanceof', $content['services'])) { + $instanceof = $content['services']['_instanceof']; + unset($content['services']['_instanceof']); + + if (!\is_array($instanceof)) { + throw new InvalidArgumentException(sprintf('Service "_instanceof" key must be an array, "%s" given in "%s".', get_debug_type($instanceof), $file)); + } + $this->instanceof = []; + $this->isLoadingInstanceof = true; + foreach ($instanceof as $id => $service) { + if (!$service || !\is_array($service)) { + throw new InvalidArgumentException(sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in "%s". Check your YAML syntax.', $id, $file)); + } + if (\is_string($service) && str_starts_with($service, '@')) { + throw new InvalidArgumentException(sprintf('Type definition "%s" cannot be an alias within "_instanceof" in "%s". Check your YAML syntax.', $id, $file)); + } + $this->parseDefinition($id, $service, $file, [], false, $trackBindings); + } + } + + $this->isLoadingInstanceof = false; + $defaults = $this->parseDefaults($content, $file); + foreach ($content['services'] as $id => $service) { + $this->parseDefinition($id, $service, $file, $defaults, false, $trackBindings); + } + } + + /** + * @throws InvalidArgumentException + */ + private function parseDefaults(array &$content, string $file): array + { + if (!\array_key_exists('_defaults', $content['services'])) { + return []; + } + $defaults = $content['services']['_defaults']; + unset($content['services']['_defaults']); + + if (!\is_array($defaults)) { + throw new InvalidArgumentException(sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', get_debug_type($defaults), $file)); + } + + foreach ($defaults as $key => $default) { + if (!isset(self::DEFAULTS_KEYWORDS[$key])) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, implode('", "', self::DEFAULTS_KEYWORDS))); + } + } + + if (isset($defaults['tags'])) { + if (!\is_array($tags = $defaults['tags'])) { + throw new InvalidArgumentException(sprintf('Parameter "tags" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file)); + } + + foreach ($tags as $tag) { + if (!\is_array($tag)) { + $tag = ['name' => $tag]; + } + + if (1 === \count($tag) && \is_array(current($tag))) { + $name = key($tag); + $tag = current($tag); + } else { + if (!isset($tag['name'])) { + throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in "%s".', $file)); + } + $name = $tag['name']; + unset($tag['name']); + } + + if (!\is_string($name) || '' === $name) { + throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in "%s".', $file)); + } + + $this->validateAttributes(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, '%s', $file), $tag); + } + } + + if (isset($defaults['bind'])) { + if (!\is_array($defaults['bind'])) { + throw new InvalidArgumentException(sprintf('Parameter "bind" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file)); + } + + foreach ($this->resolveServices($defaults['bind'], $file) as $argument => $value) { + $defaults['bind'][$argument] = new BoundArgument($value, true, BoundArgument::DEFAULTS_BINDING, $file); + } + } + + return $defaults; + } + + private function isUsingShortSyntax(array $service): bool + { + foreach ($service as $key => $value) { + if (\is_string($key) && ('' === $key || ('$' !== $key[0] && !str_contains($key, '\\')))) { + return false; + } + } + + return true; + } + + /** + * @throws InvalidArgumentException When tags are invalid + */ + private function parseDefinition(string $id, array|string|null $service, string $file, array $defaults, bool $return = false, bool $trackBindings = true): Definition|Alias|null + { + if (preg_match('/^_[a-zA-Z0-9_]*$/', $id)) { + throw new InvalidArgumentException(sprintf('Service names that start with an underscore are reserved. Rename the "%s" service or define it in XML instead.', $id)); + } + + if (\is_string($service) && str_starts_with($service, '@')) { + $alias = new Alias(substr($service, 1)); + + if (isset($defaults['public'])) { + $alias->setPublic($defaults['public']); + } + + return $return ? $alias : $this->container->setAlias($id, $alias); + } + + if (\is_array($service) && $this->isUsingShortSyntax($service)) { + $service = ['arguments' => $service]; + } + + if (!\is_array($service ??= [])) { + throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but "%s" found for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file)); + } + + if (isset($service['stack'])) { + if (!\is_array($service['stack'])) { + throw new InvalidArgumentException(sprintf('A stack must be an array of definitions, "%s" given for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file)); + } + + $stack = []; + + foreach ($service['stack'] as $k => $frame) { + if (\is_array($frame) && 1 === \count($frame) && !isset(self::SERVICE_KEYWORDS[key($frame)])) { + $frame = [ + 'class' => key($frame), + 'arguments' => current($frame), + ]; + } + + if (\is_array($frame) && isset($frame['stack'])) { + throw new InvalidArgumentException(sprintf('Service stack "%s" cannot contain another stack in "%s".', $id, $file)); + } + + $definition = $this->parseDefinition($id.'" at index "'.$k, $frame, $file, $defaults, true); + + if ($definition instanceof Definition) { + $definition->setInstanceofConditionals($this->instanceof); + } + + $stack[$k] = $definition; + } + + if ($diff = array_diff(array_keys($service), ['stack', 'public', 'deprecated'])) { + throw new InvalidArgumentException(sprintf('Invalid attribute "%s"; supported ones are "public" and "deprecated" for service "%s" in "%s". Check your YAML syntax.', implode('", "', $diff), $id, $file)); + } + + $service = [ + 'parent' => '', + 'arguments' => $stack, + 'tags' => ['container.stack'], + 'public' => $service['public'] ?? null, + 'deprecated' => $service['deprecated'] ?? null, + ]; + } + + $definition = isset($service[0]) && $service[0] instanceof Definition ? array_shift($service) : null; + $return = null === $definition ? $return : true; + + if (isset($service['from_callable'])) { + foreach (['alias', 'parent', 'synthetic', 'factory', 'file', 'arguments', 'properties', 'configurator', 'calls'] as $key) { + if (isset($service['factory'])) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for the service "%s" when using "from_callable" in "%s".', $key, $id, $file)); + } + } + + if ('Closure' !== $service['class'] ??= 'Closure') { + $service['lazy'] = true; + } + + $service['factory'] = ['Closure', 'fromCallable']; + $service['arguments'] = [$service['from_callable']]; + unset($service['from_callable']); + } + + $this->checkDefinition($id, $service, $file); + + if (isset($service['alias'])) { + $alias = new Alias($service['alias']); + + if (isset($service['public'])) { + $alias->setPublic($service['public']); + } elseif (isset($defaults['public'])) { + $alias->setPublic($defaults['public']); + } + + foreach ($service as $key => $value) { + if (!\in_array($key, ['alias', 'public', 'deprecated'])) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias", "public" and "deprecated".', $key, $id, $file)); + } + + if ('deprecated' === $key) { + $deprecation = \is_array($value) ? $value : ['message' => $value]; + + if (!isset($deprecation['package'])) { + throw new InvalidArgumentException(sprintf('Missing attribute "package" of the "deprecated" option in "%s".', $file)); + } + + if (!isset($deprecation['version'])) { + throw new InvalidArgumentException(sprintf('Missing attribute "version" of the "deprecated" option in "%s".', $file)); + } + + $alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); + } + } + + return $return ? $alias : $this->container->setAlias($id, $alias); + } + + $changes = []; + if (null !== $definition) { + $changes = $definition->getChanges(); + } elseif ($this->isLoadingInstanceof) { + $definition = new ChildDefinition(''); + } elseif (isset($service['parent'])) { + if ('' !== $service['parent'] && '@' === $service['parent'][0]) { + throw new InvalidArgumentException(sprintf('The value of the "parent" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['parent'], substr($service['parent'], 1))); + } + + $definition = new ChildDefinition($service['parent']); + } else { + $definition = new Definition(); + } + + if (isset($defaults['public'])) { + $definition->setPublic($defaults['public']); + } + if (isset($defaults['autowire'])) { + $definition->setAutowired($defaults['autowire']); + } + if (isset($defaults['autoconfigure'])) { + $definition->setAutoconfigured($defaults['autoconfigure']); + } + + $definition->setChanges($changes); + + if (isset($service['class'])) { + $definition->setClass($service['class']); + } + + if (isset($service['shared'])) { + $definition->setShared($service['shared']); + } + + if (isset($service['synthetic'])) { + $definition->setSynthetic($service['synthetic']); + } + + if (isset($service['lazy'])) { + $definition->setLazy((bool) $service['lazy']); + if (\is_string($service['lazy'])) { + $definition->addTag('proxy', ['interface' => $service['lazy']]); + } + } + + if (isset($service['public'])) { + $definition->setPublic($service['public']); + } + + if (isset($service['abstract'])) { + $definition->setAbstract($service['abstract']); + } + + if (isset($service['deprecated'])) { + $deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']]; + + if (!isset($deprecation['package'])) { + throw new InvalidArgumentException(sprintf('Missing attribute "package" of the "deprecated" option in "%s".', $file)); + } + + if (!isset($deprecation['version'])) { + throw new InvalidArgumentException(sprintf('Missing attribute "version" of the "deprecated" option in "%s".', $file)); + } + + $definition->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); + } + + if (isset($service['factory'])) { + $definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file)); + } + + if (isset($service['constructor'])) { + if (null !== $definition->getFactory()) { + throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $id)); + } + + $definition->setFactory([null, $service['constructor']]); + } + + if (isset($service['file'])) { + $definition->setFile($service['file']); + } + + if (isset($service['arguments'])) { + $definition->setArguments($this->resolveServices($service['arguments'], $file)); + } + + if (isset($service['properties'])) { + $definition->setProperties($this->resolveServices($service['properties'], $file)); + } + + if (isset($service['configurator'])) { + $definition->setConfigurator($this->parseCallable($service['configurator'], 'configurator', $id, $file)); + } + + if (isset($service['calls'])) { + if (!\is_array($service['calls'])) { + throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); + } + + foreach ($service['calls'] as $k => $call) { + if (!\is_array($call) && (!\is_string($k) || !$call instanceof TaggedValue)) { + throw new InvalidArgumentException(sprintf('Invalid method call for service "%s": expected map or array, "%s" given in "%s".', $id, $call instanceof TaggedValue ? '!'.$call->getTag() : get_debug_type($call), $file)); + } + + if (\is_string($k)) { + throw new InvalidArgumentException(sprintf('Invalid method call for service "%s", did you forget a leading dash before "%s: ..." in "%s"?', $id, $k, $file)); + } + + if (isset($call['method']) && \is_string($call['method'])) { + $method = $call['method']; + $args = $call['arguments'] ?? []; + $returnsClone = $call['returns_clone'] ?? false; + } else { + if (1 === \count($call) && \is_string(key($call))) { + $method = key($call); + $args = $call[$method]; + + if ($args instanceof TaggedValue) { + if ('returns_clone' !== $args->getTag()) { + throw new InvalidArgumentException(sprintf('Unsupported tag "!%s", did you mean "!returns_clone" for service "%s" in "%s"?', $args->getTag(), $id, $file)); + } + + $returnsClone = true; + $args = $args->getValue(); + } else { + $returnsClone = false; + } + } elseif (empty($call[0])) { + throw new InvalidArgumentException(sprintf('Invalid call for service "%s": the method must be defined as the first index of an array or as the only key of a map in "%s".', $id, $file)); + } else { + $method = $call[0]; + $args = $call[1] ?? []; + $returnsClone = $call[2] ?? false; + } + } + + if (!\is_array($args)) { + throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in "%s". Check your YAML syntax.', $method, $id, $file)); + } + + $args = $this->resolveServices($args, $file); + $definition->addMethodCall($method, $args, $returnsClone); + } + } + + $tags = $service['tags'] ?? []; + if (!\is_array($tags)) { + throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); + } + + if (isset($defaults['tags'])) { + $tags = array_merge($tags, $defaults['tags']); + } + + foreach ($tags as $tag) { + if (!\is_array($tag)) { + $tag = ['name' => $tag]; + } + + if (1 === \count($tag) && \is_array(current($tag))) { + $name = key($tag); + $tag = current($tag); + } else { + if (!isset($tag['name'])) { + throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file)); + } + $name = $tag['name']; + unset($tag['name']); + } + + if (!\is_string($name) || '' === $name) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file)); + } + + $this->validateAttributes(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, '%s', $file), $tag); + + $definition->addTag($name, $tag); + } + + if (null !== $decorates = $service['decorates'] ?? null) { + if ('' !== $decorates && '@' === $decorates[0]) { + throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($decorates, 1))); + } + + $decorationOnInvalid = \array_key_exists('decoration_on_invalid', $service) ? $service['decoration_on_invalid'] : 'exception'; + if ('exception' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('ignore' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif (null === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } elseif ('null' === $decorationOnInvalid) { + throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean null (without quotes) in "%s"?', $decorationOnInvalid, $id, $file)); + } else { + throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean "exception", "ignore" or null in "%s"?', $decorationOnInvalid, $id, $file)); + } + + $renameId = $service['decoration_inner_name'] ?? null; + $priority = $service['decoration_priority'] ?? 0; + + $definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior); + } + + if (isset($service['autowire'])) { + $definition->setAutowired($service['autowire']); + } + + if (isset($defaults['bind']) || isset($service['bind'])) { + // deep clone, to avoid multiple process of the same instance in the passes + $bindings = $definition->getBindings(); + $bindings += isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : []; + + if (isset($service['bind'])) { + if (!\is_array($service['bind'])) { + throw new InvalidArgumentException(sprintf('Parameter "bind" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); + } + + $bindings = array_merge($bindings, $this->resolveServices($service['bind'], $file)); + $bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING; + foreach ($bindings as $argument => $value) { + if (!$value instanceof BoundArgument) { + $bindings[$argument] = new BoundArgument($value, $trackBindings, $bindingType, $file); + } + } + } + + $definition->setBindings($bindings); + } + + if (isset($service['autoconfigure'])) { + $definition->setAutoconfigured($service['autoconfigure']); + } + + if (\array_key_exists('namespace', $service) && !\array_key_exists('resource', $service)) { + throw new InvalidArgumentException(sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in "%s". Check your YAML syntax.', $id, $file)); + } + + if ($return) { + if (\array_key_exists('resource', $service)) { + throw new InvalidArgumentException(sprintf('Invalid "resource" attribute found for service "%s" in "%s". Check your YAML syntax.', $id, $file)); + } + + return $definition; + } + + if (\array_key_exists('resource', $service)) { + if (!\is_string($service['resource'])) { + throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file)); + } + $exclude = $service['exclude'] ?? null; + $namespace = $service['namespace'] ?? $id; + $this->registerClasses($definition, $namespace, $service['resource'], $exclude, $file); + } else { + $this->setDefinition($id, $definition); + } + + return null; + } + + /** + * @throws InvalidArgumentException When errors occur + */ + private function parseCallable(mixed $callable, string $parameter, string $id, string $file): string|array|Reference + { + if (\is_string($callable)) { + if (str_starts_with($callable, '@=')) { + if ('factory' !== $parameter) { + throw new InvalidArgumentException(sprintf('Using expressions in "%s" for the "%s" service is not supported in "%s".', $parameter, $id, $file)); + } + if (!class_exists(Expression::class)) { + throw new \LogicException('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + + return $callable; + } + + if ('' !== $callable && '@' === $callable[0]) { + if (!str_contains($callable, ':')) { + return [$this->resolveServices($callable, $file), '__invoke']; + } + + throw new InvalidArgumentException(sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s" in "%s").', $parameter, $id, $callable, substr($callable, 1), $file)); + } + + return $callable; + } + + if (\is_array($callable)) { + if (isset($callable[0]) && isset($callable[1])) { + return [$this->resolveServices($callable[0], $file), $callable[1]]; + } + + if ('factory' === $parameter && isset($callable[1]) && null === $callable[0]) { + return $callable; + } + + throw new InvalidArgumentException(sprintf('Parameter "%s" must contain an array with two elements for service "%s" in "%s". Check your YAML syntax.', $parameter, $id, $file)); + } + + throw new InvalidArgumentException(sprintf('Parameter "%s" must be a string or an array for service "%s" in "%s". Check your YAML syntax.', $parameter, $id, $file)); + } + + /** + * Loads a YAML file. + * + * @throws InvalidArgumentException when the given file is not a local file or when it does not exist + */ + protected function loadFile(string $file): ?array + { + if (!class_exists(YamlParser::class)) { + throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed. Try running "composer require symfony/yaml".'); + } + + if (!stream_is_local($file)) { + throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file)); + } + + if (!is_file($file)) { + throw new InvalidArgumentException(sprintf('The file "%s" does not exist.', $file)); + } + + $this->yamlParser ??= new YamlParser(); + + try { + $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS); + } catch (ParseException $e) { + throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $file).$e->getMessage(), 0, $e); + } + + return $this->validate($configuration, $file); + } + + /** + * Validates a YAML file. + * + * @throws InvalidArgumentException When service file is not valid + */ + private function validate(mixed $content, string $file): ?array + { + if (null === $content) { + return $content; + } + + if (!\is_array($content)) { + throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file)); + } + + foreach ($content as $namespace => $data) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || str_starts_with($namespace, 'when@')) { + continue; + } + + if (!$this->prepend && !$this->container->hasExtension($namespace)) { + $extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); + throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($namespace, $file, $namespace, $extensionNamespaces)); + } + } + + return $content; + } + + private function resolveServices(mixed $value, string $file, bool $isParameter = false): mixed + { + if ($value instanceof TaggedValue) { + $argument = $value->getValue(); + + if ('closure' === $value->getTag()) { + $argument = $this->resolveServices($argument, $file, $isParameter); + + return (new Definition('Closure')) + ->setFactory(['Closure', 'fromCallable']) + ->addArgument($argument); + } + if ('iterator' === $value->getTag()) { + if (!\is_array($argument)) { + throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts sequences in "%s".', $file)); + } + $argument = $this->resolveServices($argument, $file, $isParameter); + + return new IteratorArgument($argument); + } + if ('service_closure' === $value->getTag()) { + $argument = $this->resolveServices($argument, $file, $isParameter); + + return new ServiceClosureArgument($argument); + } + if ('service_locator' === $value->getTag()) { + if (!\is_array($argument)) { + throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file)); + } + + $argument = $this->resolveServices($argument, $file, $isParameter); + + return new ServiceLocatorArgument($argument); + } + if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], true)) { + $forLocator = 'tagged_locator' === $value->getTag(); + + if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) { + if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude', 'exclude_self'])) { + throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "%s".', $value->getTag(), implode('", "', $diff), implode('", "', $supportedKeys))); + } + + $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null), $argument['exclude_self'] ?? true); + } elseif (\is_string($argument) && $argument) { + $argument = new TaggedIteratorArgument($argument, null, null, $forLocator); + } else { + throw new InvalidArgumentException(sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file)); + } + + if ($forLocator) { + $argument = new ServiceLocatorArgument($argument); + } + + return $argument; + } + if ('service' === $value->getTag()) { + if ($isParameter) { + throw new InvalidArgumentException(sprintf('Using an anonymous service in a parameter is not allowed in "%s".', $file)); + } + + $isLoadingInstanceof = $this->isLoadingInstanceof; + $this->isLoadingInstanceof = false; + $instanceof = $this->instanceof; + $this->instanceof = []; + + $id = sprintf('.%d_%s', ++$this->anonymousServicesCount, preg_replace('/^.*\\\\/', '', $argument['class'] ?? '').$this->anonymousServicesSuffix); + $this->parseDefinition($id, $argument, $file, []); + + if (!$this->container->hasDefinition($id)) { + throw new InvalidArgumentException(sprintf('Creating an alias using the tag "!service" is not allowed in "%s".', $file)); + } + + $this->container->getDefinition($id); + + $this->isLoadingInstanceof = $isLoadingInstanceof; + $this->instanceof = $instanceof; + + return new Reference($id); + } + if ('abstract' === $value->getTag()) { + return new AbstractArgument($value->getValue()); + } + + throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag())); + } + + if (\is_array($value)) { + foreach ($value as $k => $v) { + $value[$k] = $this->resolveServices($v, $file, $isParameter); + } + } elseif (\is_string($value) && str_starts_with($value, '@=')) { + if ($isParameter) { + throw new InvalidArgumentException(sprintf('Using expressions in parameters is not allowed in "%s".', $file)); + } + + if (!class_exists(Expression::class)) { + throw new \LogicException('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); + } + + return new Expression(substr($value, 2)); + } elseif (\is_string($value) && str_starts_with($value, '@')) { + if (str_starts_with($value, '@@')) { + $value = substr($value, 1); + $invalidBehavior = null; + } elseif (str_starts_with($value, '@!')) { + $value = substr($value, 2); + $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; + } elseif (str_starts_with($value, '@?')) { + $value = substr($value, 2); + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } else { + $value = substr($value, 1); + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } + + if (null !== $invalidBehavior) { + $value = new Reference($value, $invalidBehavior); + } + } + + return $value; + } + + private function loadFromExtensions(array $content): void + { + foreach ($content as $namespace => $values) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || str_starts_with($namespace, 'when@')) { + continue; + } + + if (!\is_array($values)) { + $values = []; + } + + $this->loadExtensionConfig($namespace, $values); + } + + $this->loadExtensionConfigs(); + } + + private function checkDefinition(string $id, array $definition, string $file): void + { + if ($this->isLoadingInstanceof) { + $keywords = self::INSTANCEOF_KEYWORDS; + } elseif (isset($definition['resource']) || isset($definition['namespace'])) { + $keywords = self::PROTOTYPE_KEYWORDS; + } else { + $keywords = self::SERVICE_KEYWORDS; + } + + foreach ($definition as $key => $value) { + if (!isset($keywords[$key])) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, implode('", "', $keywords))); + } + } + } + + private function validateAttributes(string $message, array $attributes, array $path = []): void + { + foreach ($attributes as $name => $value) { + if (\is_array($value)) { + $this->validateAttributes($message, $value, [...$path, $name]); + } elseif (!\is_scalar($value ?? '')) { + $name = implode('.', [...$path, $name]); + throw new InvalidArgumentException(sprintf($message, $name)); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd b/vendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd new file mode 100644 index 0000000..c071e34 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/dependency-injection/Parameter.php b/vendor/symfony/dependency-injection/Parameter.php new file mode 100644 index 0000000..90dcc92 --- /dev/null +++ b/vendor/symfony/dependency-injection/Parameter.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Parameter represents a parameter reference. + * + * @author Fabien Potencier + */ +class Parameter +{ + private string $id; + + public function __construct(string $id) + { + $this->id = $id; + } + + public function __toString(): string + { + return $this->id; + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/ContainerBag.php b/vendor/symfony/dependency-injection/ParameterBag/ContainerBag.php new file mode 100644 index 0000000..7aa5ff8 --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/ContainerBag.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Container; + +/** + * @author Nicolas Grekas + */ +class ContainerBag extends FrozenParameterBag implements ContainerBagInterface +{ + private Container $container; + + public function __construct(Container $container) + { + $this->container = $container; + } + + public function all(): array + { + return $this->container->getParameterBag()->all(); + } + + public function get(string $name): array|bool|string|int|float|\UnitEnum|null + { + return $this->container->getParameter($name); + } + + public function has(string $name): bool + { + return $this->container->hasParameter($name); + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php b/vendor/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php new file mode 100644 index 0000000..2b92207 --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; + +/** + * ContainerBagInterface is the interface implemented by objects that manage service container parameters. + * + * @author Nicolas Grekas + */ +interface ContainerBagInterface extends ContainerInterface +{ + /** + * Gets the service container parameters. + */ + public function all(): array; + + /** + * Replaces parameter placeholders (%name%) by their values. + * + * @template TValue of array|scalar + * + * @param TValue $value + * + * @psalm-return (TValue is scalar ? array|scalar : array) + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + */ + public function resolveValue(mixed $value): mixed; + + /** + * Escape parameter placeholders %. + */ + public function escapeValue(mixed $value): mixed; + + /** + * Unescape parameter placeholders %. + */ + public function unescapeValue(mixed $value): mixed; +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php b/vendor/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php new file mode 100644 index 0000000..f70a32a --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * @author Nicolas Grekas + */ +class EnvPlaceholderParameterBag extends ParameterBag +{ + private string $envPlaceholderUniquePrefix; + private array $envPlaceholders = []; + private array $unusedEnvPlaceholders = []; + private array $providedTypes = []; + + private static int $counter = 0; + + public function get(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (str_starts_with($name, 'env(') && str_ends_with($name, ')') && 'env()' !== $name) { + $env = substr($name, 4, -1); + + if (isset($this->envPlaceholders[$env])) { + foreach ($this->envPlaceholders[$env] as $placeholder) { + return $placeholder; // return first result + } + } + if (isset($this->unusedEnvPlaceholders[$env])) { + foreach ($this->unusedEnvPlaceholders[$env] as $placeholder) { + return $placeholder; // return first result + } + } + if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w*+$/', $env)) { + throw new InvalidArgumentException(sprintf('The given env var name "%s" contains invalid characters (allowed characters: letters, digits, hyphens, backslashes and colons).', $name)); + } + if ($this->has($name) && null !== ($defaultValue = parent::get($name)) && !\is_string($defaultValue)) { + throw new RuntimeException(sprintf('The default value of an env() parameter must be a string or null, but "%s" given to "%s".', get_debug_type($defaultValue), $name)); + } + + $uniqueName = hash('xxh128', $name.'_'.self::$counter++); + $placeholder = sprintf('%s_%s_%s', $this->getEnvPlaceholderUniquePrefix(), strtr($env, ':-.\\', '____'), $uniqueName); + $this->envPlaceholders[$env][$placeholder] = $placeholder; + + return $placeholder; + } + + return parent::get($name); + } + + /** + * Gets the common env placeholder prefix for env vars created by this bag. + */ + public function getEnvPlaceholderUniquePrefix(): string + { + if (!isset($this->envPlaceholderUniquePrefix)) { + $reproducibleEntropy = unserialize(serialize($this->parameters)); + array_walk_recursive($reproducibleEntropy, function (&$v) { $v = null; }); + $this->envPlaceholderUniquePrefix = 'env_'.substr(hash('xxh128', serialize($reproducibleEntropy)), -16); + } + + return $this->envPlaceholderUniquePrefix; + } + + /** + * Returns the map of env vars used in the resolved parameter values to their placeholders. + * + * @return string[][] A map of env var names to their placeholders + */ + public function getEnvPlaceholders(): array + { + return $this->envPlaceholders; + } + + public function getUnusedEnvPlaceholders(): array + { + return $this->unusedEnvPlaceholders; + } + + public function clearUnusedEnvPlaceholders(): void + { + $this->unusedEnvPlaceholders = []; + } + + /** + * Merges the env placeholders of another EnvPlaceholderParameterBag. + */ + public function mergeEnvPlaceholders(self $bag): void + { + if ($newPlaceholders = $bag->getEnvPlaceholders()) { + $this->envPlaceholders += $newPlaceholders; + + foreach ($newPlaceholders as $env => $placeholders) { + $this->envPlaceholders[$env] += $placeholders; + } + } + + if ($newUnusedPlaceholders = $bag->getUnusedEnvPlaceholders()) { + $this->unusedEnvPlaceholders += $newUnusedPlaceholders; + + foreach ($newUnusedPlaceholders as $env => $placeholders) { + $this->unusedEnvPlaceholders[$env] += $placeholders; + } + } + } + + /** + * Maps env prefixes to their corresponding PHP types. + */ + public function setProvidedTypes(array $providedTypes): void + { + $this->providedTypes = $providedTypes; + } + + /** + * Gets the PHP types corresponding to env() parameter prefixes. + * + * @return string[][] + */ + public function getProvidedTypes(): array + { + return $this->providedTypes; + } + + public function resolve(): void + { + if ($this->resolved) { + return; + } + parent::resolve(); + + foreach ($this->envPlaceholders as $env => $placeholders) { + if ($this->has($name = "env($env)") && null !== ($default = $this->parameters[$name]) && !\is_string($default)) { + throw new RuntimeException(sprintf('The default value of env parameter "%s" must be a string or null, "%s" given.', $env, get_debug_type($default))); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php b/vendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php new file mode 100644 index 0000000..38fca41 --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * Holds read-only parameters. + * + * @author Fabien Potencier + */ +class FrozenParameterBag extends ParameterBag +{ + /** + * For performance reasons, the constructor assumes that + * all keys are already lowercased. + * + * This is always the case when used internally. + */ + public function __construct( + array $parameters = [], + protected array $deprecatedParameters = [], + ) { + $this->parameters = $parameters; + $this->resolved = true; + } + + public function clear(): never + { + throw new LogicException('Impossible to call clear() on a frozen ParameterBag.'); + } + + public function add(array $parameters): never + { + throw new LogicException('Impossible to call add() on a frozen ParameterBag.'); + } + + public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value): never + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): never + { + throw new LogicException('Impossible to call deprecate() on a frozen ParameterBag.'); + } + + public function remove(string $name): never + { + throw new LogicException('Impossible to call remove() on a frozen ParameterBag.'); + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/ParameterBag.php b/vendor/symfony/dependency-injection/ParameterBag/ParameterBag.php new file mode 100644 index 0000000..d0a12a9 --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/ParameterBag.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * Holds parameters. + * + * @author Fabien Potencier + */ +class ParameterBag implements ParameterBagInterface +{ + protected array $parameters = []; + protected bool $resolved = false; + protected array $deprecatedParameters = []; + + public function __construct(array $parameters = []) + { + $this->add($parameters); + } + + public function clear(): void + { + $this->parameters = []; + } + + public function add(array $parameters): void + { + foreach ($parameters as $key => $value) { + $this->set($key, $value); + } + } + + public function all(): array + { + return $this->parameters; + } + + public function allDeprecated(): array + { + return $this->deprecatedParameters; + } + + public function get(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (!\array_key_exists($name, $this->parameters)) { + if (!$name) { + throw new ParameterNotFoundException($name); + } + + $alternatives = []; + foreach ($this->parameters as $key => $parameterValue) { + $lev = levenshtein($name, $key); + if ($lev <= \strlen($name) / 3 || str_contains($key, $name)) { + $alternatives[] = $key; + } + } + + $nonNestedAlternative = null; + if (!\count($alternatives) && str_contains($name, '.')) { + $namePartsLength = array_map('strlen', explode('.', $name)); + $key = substr($name, 0, -1 * (1 + array_pop($namePartsLength))); + while (\count($namePartsLength)) { + if ($this->has($key)) { + if (\is_array($this->get($key))) { + $nonNestedAlternative = $key; + } + break; + } + + $key = substr($key, 0, -1 * (1 + array_pop($namePartsLength))); + } + } + + throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative); + } + + if (isset($this->deprecatedParameters[$name])) { + trigger_deprecation(...$this->deprecatedParameters[$name]); + } + + return $this->parameters[$name]; + } + + public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value): void + { + if (is_numeric($name)) { + throw new InvalidArgumentException(sprintf('The parameter name "%s" cannot be numeric.', $name)); + } + + $this->parameters[$name] = $value; + } + + /** + * Deprecates a service container parameter. + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): void + { + if (!\array_key_exists($name, $this->parameters)) { + throw new ParameterNotFoundException($name); + } + + $this->deprecatedParameters[$name] = [$package, $version, $message, $name]; + } + + public function has(string $name): bool + { + return \array_key_exists($name, $this->parameters); + } + + public function remove(string $name): void + { + unset($this->parameters[$name], $this->deprecatedParameters[$name]); + } + + public function resolve(): void + { + if ($this->resolved) { + return; + } + + $parameters = []; + foreach ($this->parameters as $key => $value) { + try { + $value = $this->resolveValue($value); + $parameters[$key] = $this->unescapeValue($value); + } catch (ParameterNotFoundException $e) { + $e->setSourceKey($key); + + throw $e; + } + } + + $this->parameters = $parameters; + $this->resolved = true; + } + + /** + * Replaces parameter placeholders (%name%) by their values. + * + * @template TValue of array|scalar + * + * @param TValue $value + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * + * @psalm-return (TValue is scalar ? array|scalar : array) + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + * @throws ParameterCircularReferenceException if a circular reference if detected + * @throws RuntimeException when a given parameter has a type problem + */ + public function resolveValue(mixed $value, array $resolving = []): mixed + { + if (\is_array($value)) { + $args = []; + foreach ($value as $key => $v) { + $resolvedKey = \is_string($key) ? $this->resolveValue($key, $resolving) : $key; + if (!\is_scalar($resolvedKey) && !$resolvedKey instanceof \Stringable) { + throw new RuntimeException(sprintf('Array keys must be a scalar-value, but found key "%s" to resolve to type "%s".', $key, get_debug_type($resolvedKey))); + } + + $args[$resolvedKey] = $this->resolveValue($v, $resolving); + } + + return $args; + } + + if (!\is_string($value) || '' === $value || !str_contains($value, '%')) { + return $value; + } + + return $this->resolveString($value, $resolving); + } + + /** + * Resolves parameters inside a string. + * + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + * @throws ParameterCircularReferenceException if a circular reference if detected + * @throws RuntimeException when a given parameter has a type problem + */ + public function resolveString(string $value, array $resolving = []): mixed + { + // we do this to deal with non string values (Boolean, integer, ...) + // as the preg_replace_callback throw an exception when trying + // a non-string in a parameter value + if (preg_match('/^%([^%\s]+)%$/', $value, $match)) { + $key = $match[1]; + + if (isset($resolving[$key])) { + throw new ParameterCircularReferenceException(array_keys($resolving)); + } + + $resolving[$key] = true; + + return $this->resolved ? $this->get($key) : $this->resolveValue($this->get($key), $resolving); + } + + return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($resolving, $value) { + // skip %% + if (!isset($match[1])) { + return '%%'; + } + + $key = $match[1]; + if (isset($resolving[$key])) { + throw new ParameterCircularReferenceException(array_keys($resolving)); + } + + $resolved = $this->get($key); + + if (!\is_string($resolved) && !is_numeric($resolved)) { + throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "%s" of type "%s" inside string value "%s".', $key, get_debug_type($resolved), $value)); + } + + $resolved = (string) $resolved; + $resolving[$key] = true; + + return $this->isResolved() ? $resolved : $this->resolveString($resolved, $resolving); + }, $value); + } + + public function isResolved(): bool + { + return $this->resolved; + } + + public function escapeValue(mixed $value): mixed + { + if (\is_string($value)) { + return str_replace('%', '%%', $value); + } + + if (\is_array($value)) { + $result = []; + foreach ($value as $k => $v) { + $result[$k] = $this->escapeValue($v); + } + + return $result; + } + + return $value; + } + + public function unescapeValue(mixed $value): mixed + { + if (\is_string($value)) { + return str_replace('%%', '%', $value); + } + + if (\is_array($value)) { + $result = []; + foreach ($value as $k => $v) { + $result[$k] = $this->unescapeValue($v); + } + + return $result; + } + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php b/vendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php new file mode 100644 index 0000000..41f2a06 --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; + +/** + * ParameterBagInterface is the interface implemented by objects that manage service container parameters. + * + * @author Fabien Potencier + */ +interface ParameterBagInterface +{ + /** + * Clears all parameters. + * + * @throws LogicException if the ParameterBagInterface cannot be cleared + */ + public function clear(): void; + + /** + * Adds parameters to the service container parameters. + * + * @throws LogicException if the parameter cannot be added + */ + public function add(array $parameters): void; + + /** + * Gets the service container parameters. + */ + public function all(): array; + + /** + * Gets a service container parameter. + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function get(string $name): array|bool|string|int|float|\UnitEnum|null; + + /** + * Removes a parameter. + */ + public function remove(string $name): void; + + /** + * Sets a service container parameter. + * + * @throws LogicException if the parameter cannot be set + */ + public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value): void; + + /** + * Returns true if a parameter name is defined. + */ + public function has(string $name): bool; + + /** + * Replaces parameter placeholders (%name%) by their values for all parameters. + */ + public function resolve(): void; + + /** + * Replaces parameter placeholders (%name%) by their values. + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + */ + public function resolveValue(mixed $value): mixed; + + /** + * Escape parameter placeholders %. + */ + public function escapeValue(mixed $value): mixed; + + /** + * Unescape parameter placeholders %. + */ + public function unescapeValue(mixed $value): mixed; +} diff --git a/vendor/symfony/dependency-injection/README.md b/vendor/symfony/dependency-injection/README.md new file mode 100644 index 0000000..fa6719a --- /dev/null +++ b/vendor/symfony/dependency-injection/README.md @@ -0,0 +1,14 @@ +DependencyInjection Component +============================= + +The DependencyInjection component allows you to standardize and centralize the +way objects are constructed in your application. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/dependency_injection.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/dependency-injection/Reference.php b/vendor/symfony/dependency-injection/Reference.php new file mode 100644 index 0000000..2a89dda --- /dev/null +++ b/vendor/symfony/dependency-injection/Reference.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Reference represents a service reference. + * + * @author Fabien Potencier + */ +class Reference +{ + private string $id; + private int $invalidBehavior; + + public function __construct(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + { + $this->id = $id; + $this->invalidBehavior = $invalidBehavior; + } + + public function __toString(): string + { + return $this->id; + } + + /** + * Returns the behavior to be used when the service does not exist. + */ + public function getInvalidBehavior(): int + { + return $this->invalidBehavior; + } +} diff --git a/vendor/symfony/dependency-injection/ReverseContainer.php b/vendor/symfony/dependency-injection/ReverseContainer.php new file mode 100644 index 0000000..22d1b35 --- /dev/null +++ b/vendor/symfony/dependency-injection/ReverseContainer.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +/** + * Turns public and "container.reversible" services back to their ids. + * + * @author Nicolas Grekas + */ +final class ReverseContainer +{ + private Container $serviceContainer; + private ContainerInterface $reversibleLocator; + private string $tagName; + private \Closure $getServiceId; + + public function __construct(Container $serviceContainer, ContainerInterface $reversibleLocator, string $tagName = 'container.reversible') + { + $this->serviceContainer = $serviceContainer; + $this->reversibleLocator = $reversibleLocator; + $this->tagName = $tagName; + $this->getServiceId = \Closure::bind(fn (object $service): ?string => array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null, $serviceContainer, Container::class); + } + + /** + * Returns the id of the passed object when it exists as a service. + * + * To be reversible, services need to be either public or be tagged with "container.reversible". + */ + public function getId(object $service): ?string + { + if ($this->serviceContainer === $service) { + return 'service_container'; + } + + if (null === $id = ($this->getServiceId)($service)) { + return null; + } + + if ($this->serviceContainer->has($id) || $this->reversibleLocator->has($id)) { + return $id; + } + + return null; + } + + /** + * @throws ServiceNotFoundException When the service is not reversible + */ + public function getService(string $id): object + { + if ($this->reversibleLocator->has($id)) { + return $this->reversibleLocator->get($id); + } + + if (isset($this->serviceContainer->getRemovedIds()[$id])) { + throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName)); + } + + return $this->serviceContainer->get($id); + } +} diff --git a/vendor/symfony/dependency-injection/ServiceLocator.php b/vendor/symfony/dependency-injection/ServiceLocator.php new file mode 100644 index 0000000..fbf7669 --- /dev/null +++ b/vendor/symfony/dependency-injection/ServiceLocator.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Contracts\Service\ServiceCollectionInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * @author Robin Chalas + * @author Nicolas Grekas + * + * @template-covariant T of mixed + * + * @implements ServiceCollectionInterface + */ +class ServiceLocator implements ServiceCollectionInterface +{ + use ServiceLocatorTrait { + get as private doGet; + } + + private ?string $externalId = null; + private ?Container $container = null; + + public function get(string $id): mixed + { + if (!$this->externalId) { + return $this->doGet($id); + } + + try { + return $this->doGet($id); + } catch (RuntimeException $e) { + $what = sprintf('service "%s" required by "%s"', $id, $this->externalId); + $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage()); + + if ($e->getMessage() === $message) { + $message = sprintf('Cannot resolve %s: %s', $what, $message); + } + + $r = new \ReflectionProperty($e, 'message'); + $r->setValue($e, $message); + + throw $e; + } + } + + public function __invoke(string $id): mixed + { + return isset($this->factories[$id]) ? $this->get($id) : null; + } + + /** + * @internal + */ + public function withContext(string $externalId, Container $container): static + { + $locator = clone $this; + $locator->externalId = $externalId; + $locator->container = $container; + + return $locator; + } + + public function count(): int + { + return \count($this->getProvidedServices()); + } + + public function getIterator(): \Traversable + { + foreach ($this->getProvidedServices() as $id => $config) { + yield $id => $this->get($id); + } + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if ($this->loading) { + $msg = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives()); + + return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], $msg); + } + + $class = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 4); + $class = isset($class[3]['object']) ? $class[3]['object']::class : null; + $externalId = $this->externalId ?: $class; + + $msg = []; + $msg[] = sprintf('Service "%s" not found:', $id); + + if (!$this->container) { + $class = null; + } elseif ($this->container->has($id) || isset($this->container->getRemovedIds()[$id])) { + $msg[] = 'even though it exists in the app\'s container,'; + } else { + try { + $this->container->get($id); + $class = null; + } catch (ServiceNotFoundException $e) { + if ($e->getAlternatives()) { + $msg[] = sprintf('did you mean %s? Anyway,', $this->formatAlternatives($e->getAlternatives(), 'or')); + } else { + $class = null; + } + } + } + if ($externalId) { + $msg[] = sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives()); + } else { + $msg[] = sprintf('the current service locator %s', $this->formatAlternatives()); + } + + if (!$class) { + // no-op + } elseif (is_subclass_of($class, ServiceSubscriberInterface::class)) { + $msg[] = sprintf('Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', preg_replace('/([^\\\\]++\\\\)++/', '', $class)); + } else { + $msg[] = 'Try using dependency injection instead.'; + } + + return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], implode(' ', $msg)); + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new ServiceCircularReferenceException($id, $path); + } + + private function formatAlternatives(?array $alternatives = null, string $separator = 'and'): string + { + $format = '"%s"%s'; + if (null === $alternatives) { + if (!$alternatives = array_keys($this->factories)) { + return 'is empty...'; + } + $format = sprintf('only knows about the %s service%s.', $format, 1 < \count($alternatives) ? 's' : ''); + } + $last = array_pop($alternatives); + + return sprintf($format, $alternatives ? implode('", "', $alternatives) : $last, $alternatives ? sprintf(' %s "%s"', $separator, $last) : ''); + } +} diff --git a/vendor/symfony/dependency-injection/StaticEnvVarLoader.php b/vendor/symfony/dependency-injection/StaticEnvVarLoader.php new file mode 100644 index 0000000..be1ada5 --- /dev/null +++ b/vendor/symfony/dependency-injection/StaticEnvVarLoader.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +class StaticEnvVarLoader implements EnvVarLoaderInterface +{ + private array $envVars; + + public function __construct(private EnvVarLoaderInterface $envVarLoader) + { + } + + public function loadEnvVars(): array + { + return $this->envVars ??= $this->envVarLoader->loadEnvVars(); + } +} diff --git a/vendor/symfony/dependency-injection/TaggedContainerInterface.php b/vendor/symfony/dependency-injection/TaggedContainerInterface.php new file mode 100644 index 0000000..a108a99 --- /dev/null +++ b/vendor/symfony/dependency-injection/TaggedContainerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * TaggedContainerInterface is the interface implemented when a container knows how to deals with tags. + * + * @author Fabien Potencier + */ +interface TaggedContainerInterface extends ContainerInterface +{ + /** + * Returns service ids for a given tag. + * + * @param string $name The tag name + */ + public function findTaggedServiceIds(string $name): array; +} diff --git a/vendor/symfony/dependency-injection/TypedReference.php b/vendor/symfony/dependency-injection/TypedReference.php new file mode 100644 index 0000000..acdd3b8 --- /dev/null +++ b/vendor/symfony/dependency-injection/TypedReference.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Represents a PHP type-hinted service reference. + * + * @author Nicolas Grekas + */ +class TypedReference extends Reference +{ + private string $type; + private ?string $name; + private array $attributes; + + /** + * @param string $id The service identifier + * @param string $type The PHP type of the identified service + * @param int $invalidBehavior The behavior when the service does not exist + * @param string|null $name The name of the argument targeting the service + * @param array $attributes The attributes to be used + */ + public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, ?string $name = null, array $attributes = []) + { + $this->name = $type === $id ? $name : null; + parent::__construct($id, $invalidBehavior); + $this->type = $type; + $this->attributes = $attributes; + } + + public function getType(): string + { + return $this->type; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getAttributes(): array + { + return $this->attributes; + } +} diff --git a/vendor/symfony/dependency-injection/Variable.php b/vendor/symfony/dependency-injection/Variable.php new file mode 100644 index 0000000..bb275ce --- /dev/null +++ b/vendor/symfony/dependency-injection/Variable.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Represents a variable. + * + * $var = new Variable('a'); + * + * will be dumped as + * + * $a + * + * by the PHP dumper. + * + * @author Johannes M. Schmitt + */ +class Variable +{ + private string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function __toString(): string + { + return $this->name; + } +} diff --git a/vendor/symfony/dependency-injection/composer.json b/vendor/symfony/dependency-injection/composer.json new file mode 100644 index 0000000..b5fda9b --- /dev/null +++ b/vendor/symfony/dependency-injection/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/dependency-injection", + "type": "library", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4|^7.0" + }, + "require-dev": { + "symfony/yaml": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\DependencyInjection\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..0ed3a24 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +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. diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..9814864 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..ceb6c07 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..2d56512 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/doctrine-bridge/ArgumentResolver/EntityValueResolver.php b/vendor/symfony/doctrine-bridge/ArgumentResolver/EntityValueResolver.php new file mode 100644 index 0000000..97fcf35 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/ArgumentResolver/EntityValueResolver.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\ArgumentResolver; + +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\NoResultException; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; +use Symfony\Bridge\Doctrine\Attribute\MapEntity; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Yields the entity matching the criteria provided in the route. + * + * @author Fabien Potencier + * @author Jérémy Derussé + */ +final class EntityValueResolver implements ValueResolverInterface +{ + public function __construct( + private ManagerRegistry $registry, + private ?ExpressionLanguage $expressionLanguage = null, + private MapEntity $defaults = new MapEntity(), + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (\is_object($request->attributes->get($argument->getName()))) { + return []; + } + + $options = $argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF); + $options = ($options[0] ?? $this->defaults)->withDefaults($this->defaults, $argument->getType()); + + if (!$options->class || $options->disabled) { + return []; + } + if (!$manager = $this->getManager($options->objectManager, $options->class)) { + return []; + } + + $message = ''; + if (null !== $options->expr) { + if (null === $object = $this->findViaExpression($manager, $request, $options)) { + $message = sprintf(' The expression "%s" returned null.', $options->expr); + } + // find by identifier? + } elseif (false === $object = $this->find($manager, $request, $options, $argument)) { + // find by criteria + if (!$criteria = $this->getCriteria($request, $options, $manager, $argument)) { + return []; + } + try { + $object = $manager->getRepository($options->class)->findOneBy($criteria); + } catch (NoResultException|ConversionException) { + $object = null; + } + } + + if (null === $object && !$argument->isNullable()) { + throw new NotFoundHttpException($options->message ?? (sprintf('"%s" object not found by "%s".', $options->class, self::class).$message)); + } + + return [$object]; + } + + private function getManager(?string $name, string $class): ?ObjectManager + { + if (null === $name) { + return $this->registry->getManagerForClass($class); + } + + try { + $manager = $this->registry->getManager($name); + } catch (\InvalidArgumentException) { + return null; + } + + return $manager->getMetadataFactory()->isTransient($class) ? null : $manager; + } + + private function find(ObjectManager $manager, Request $request, MapEntity $options, ArgumentMetadata $argument): false|object|null + { + if ($options->mapping || $options->exclude) { + return false; + } + + $id = $this->getIdentifier($request, $options, $argument); + if (false === $id || null === $id) { + return $id; + } + if (\is_array($id) && \in_array(null, $id, true)) { + return null; + } + + if ($options->evictCache && $manager instanceof EntityManagerInterface) { + $cacheProvider = $manager->getCache(); + if ($cacheProvider && $cacheProvider->containsEntity($options->class, $id)) { + $cacheProvider->evictEntity($options->class, $id); + } + } + + try { + return $manager->getRepository($options->class)->find($id); + } catch (NoResultException|ConversionException) { + return null; + } + } + + private function getIdentifier(Request $request, MapEntity $options, ArgumentMetadata $argument): mixed + { + if (\is_array($options->id)) { + $id = []; + foreach ($options->id as $field) { + // Convert "%s_uuid" to "foobar_uuid" + if (str_contains($field, '%s')) { + $field = sprintf($field, $argument->getName()); + } + + $id[$field] = $request->attributes->get($field); + } + + return $id; + } + + if ($options->id) { + return $request->attributes->get($options->id) ?? ($options->stripNull ? false : null); + } + + $name = $argument->getName(); + + if ($request->attributes->has($name)) { + if (\is_array($id = $request->attributes->get($name))) { + return false; + } + + foreach ($request->attributes->get('_route_mapping') ?? [] as $parameter => $attribute) { + if ($name === $attribute) { + $options->mapping = [$name => $parameter]; + + return false; + } + } + + return $id ?? ($options->stripNull ? false : null); + } + + if ($request->attributes->has('id')) { + return $request->attributes->get('id') ?? ($options->stripNull ? false : null); + } + + return false; + } + + private function getCriteria(Request $request, MapEntity $options, ObjectManager $manager, ArgumentMetadata $argument): array + { + if (!($mapping = $options->mapping) && \is_array($criteria = $request->attributes->get($argument->getName()))) { + foreach ($options->exclude as $exclude) { + unset($criteria[$exclude]); + } + + if ($options->stripNull) { + $criteria = array_filter($criteria, static fn ($value) => null !== $value); + } + + return $criteria; + } elseif (null === $mapping) { + trigger_deprecation('symfony/doctrine-bridge', '7.1', 'Relying on auto-mapping for Doctrine entities is deprecated for argument $%s of "%s": declare the identifier using either the #[MapEntity] attribute or mapped route parameters.', $argument->getName(), method_exists($argument, 'getControllerName') ? $argument->getControllerName() : 'n/a'); + $mapping = $request->attributes->keys(); + } + + if ($mapping && array_is_list($mapping)) { + $mapping = array_combine($mapping, $mapping); + } + + foreach ($options->exclude as $exclude) { + unset($mapping[$exclude]); + } + + if (!$mapping) { + return []; + } + + $criteria = []; + $metadata = null === $options->mapping ? $manager->getClassMetadata($options->class) : false; + + foreach ($mapping as $attribute => $field) { + if ($metadata && !$metadata->hasField($field) && (!$metadata->hasAssociation($field) || !$metadata->isSingleValuedAssociation($field))) { + continue; + } + + $criteria[$field] = $request->attributes->get($attribute); + } + + if ($options->stripNull) { + $criteria = array_filter($criteria, static fn ($value) => null !== $value); + } + + return $criteria; + } + + private function findViaExpression(ObjectManager $manager, Request $request, MapEntity $options): object|iterable|null + { + if (!$this->expressionLanguage) { + throw new \LogicException(sprintf('You cannot use the "%s" if the ExpressionLanguage component is not available. Try running "composer require symfony/expression-language".', __CLASS__)); + } + + $repository = $manager->getRepository($options->class); + $variables = array_merge($request->attributes->all(), [ + 'repository' => $repository, + 'request' => $request, + ]); + + try { + return $this->expressionLanguage->evaluate($options->expr, $variables); + } catch (NoResultException|ConversionException) { + return null; + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Attribute/MapEntity.php b/vendor/symfony/doctrine-bridge/Attribute/MapEntity.php new file mode 100644 index 0000000..73d73d5 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Attribute/MapEntity.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Attribute; + +use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver; +use Symfony\Component\HttpKernel\Attribute\ValueResolver; + +/** + * Indicates that a controller argument should receive an Entity. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapEntity extends ValueResolver +{ + /** + * @param class-string|null $class The entity class + * @param string|null $objectManager Specify the object manager used to retrieve the entity + * @param string|null $expr An expression to fetch the entity using the {@see https://symfony.com/doc/current/components/expression_language.html ExpressionLanguage} syntax. + * Any request attribute are available as a variable, and your entity repository in the 'repository' variable. + * @param array|null $mapping Configures the properties and values to use with the findOneBy() method + * The key is the route placeholder name and the value is the Doctrine property name + * @param string[]|null $exclude Configures the properties that should be used in the findOneBy() method by excluding + * one or more properties so that not all are used + * @param bool|null $stripNull Whether to prevent null values from being used as parameters in the query (defaults to false) + * @param string[]|string|null $id If an id option is configured and matches a route parameter, then the resolver will find by the primary key + * @param bool|null $evictCache If true, forces Doctrine to always fetch the entity from the database instead of cache (defaults to false) + */ + public function __construct( + public ?string $class = null, + public ?string $objectManager = null, + public ?string $expr = null, + public ?array $mapping = null, + public ?array $exclude = null, + public ?bool $stripNull = null, + public array|string|null $id = null, + public ?bool $evictCache = null, + bool $disabled = false, + string $resolver = EntityValueResolver::class, + public ?string $message = null, + ) { + parent::__construct($resolver, $disabled); + $this->selfValidate(); + } + + public function withDefaults(self $defaults, ?string $class): static + { + $clone = clone $this; + $clone->class ??= class_exists($class ?? '') ? $class : null; + $clone->objectManager ??= $defaults->objectManager; + $clone->expr ??= $defaults->expr; + $clone->mapping ??= $defaults->mapping; + $clone->exclude ??= $defaults->exclude ?? []; + $clone->stripNull ??= $defaults->stripNull ?? false; + $clone->id ??= $defaults->id; + $clone->evictCache ??= $defaults->evictCache ?? false; + $clone->message ??= $defaults->message; + + $clone->selfValidate(); + + return $clone; + } + + private function selfValidate(): void + { + if (!$this->id) { + return; + } + if ($this->mapping) { + throw new \LogicException('The "id" and "mapping" options cannot be used together on #[MapEntity] attributes.'); + } + if ($this->exclude) { + throw new \LogicException('The "id" and "exclude" options cannot be used together on #[MapEntity] attributes.'); + } + $this->mapping = []; + } +} diff --git a/vendor/symfony/doctrine-bridge/CHANGELOG.md b/vendor/symfony/doctrine-bridge/CHANGELOG.md new file mode 100644 index 0000000..4c6e029 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/CHANGELOG.md @@ -0,0 +1,193 @@ +CHANGELOG +========= + +7.1 +--- + + * Allow `EntityValueResolver` to return a list of entities + * Add support for auto-closing idle connections + * Allow validating every class against `UniqueEntity` constraint + * Deprecate auto-mapping of entities in favor of mapped route parameters + +7.0 +--- + + * Remove `DoctrineDbalCacheAdapterSchemaSubscriber`, use `DoctrineDbalCacheAdapterSchemaListener` instead + * Remove `MessengerTransportDoctrineSchemaSubscriber`, use `MessengerTransportDoctrineSchemaListener` instead + * Remove `RememberMeTokenProviderDoctrineSchemaSubscriber`, use `RememberMeTokenProviderDoctrineSchemaListener` instead + * Remove `DbalLogger`, use a middleware instead + * Remove `DoctrineDataCollector::addLogger()`, use a `DebugDataHolder` instead + * Remove `ContainerAwareLoader`, use dependency injection in your fixtures instead + * `ContainerAwareEventManager::getListeners()` must be called with an event name + * DoctrineBridge now requires `doctrine/event-manager:^2` + * Add parameter `$isSameDatabase` to `DoctrineTokenProvider::configureSchema()` + +6.4 +--- + + * [BC BREAK] Add argument `$buildDir` to `ProxyCacheWarmer::warmUp()` + * [BC BREAK] Add return type-hints to `EntityFactory` + * Deprecate `DbalLogger`, use a middleware instead + * Deprecate not constructing `DoctrineDataCollector` with an instance of `DebugDataHolder` + * Deprecate `DoctrineDataCollector::addLogger()`, use a `DebugDataHolder` instead + * Deprecate `ContainerAwareLoader`, use dependency injection in your fixtures instead + * Always pass the `Request` object to `EntityValueResolver`'s expression + * [BC BREAK] Change argument `$lastUsed` of `DoctrineTokenProvider::updateToken()` to accept `DateTimeInterface` + +6.3 +--- + + * Deprecate passing Doctrine subscribers to `ContainerAwareEventManager` class, use listeners instead + * Add `AbstractSchemaListener`, `LockStoreSchemaListener` and `PdoSessionHandlerSchemaListener` + * Deprecate `DoctrineDbalCacheAdapterSchemaSubscriber` in favor of `DoctrineDbalCacheAdapterSchemaListener` + * Deprecate `MessengerTransportDoctrineSchemaSubscriber` in favor of `MessengerTransportDoctrineSchemaListener` + * Deprecate `RememberMeTokenProviderDoctrineSchemaSubscriber` in favor of `RememberMeTokenProviderDoctrineSchemaListener` + * Add optional parameter `$isSameDatabase` to `DoctrineTokenProvider::configureSchema()` + +6.2 +--- + + * Add `#[MapEntity]` with its corresponding `EntityValueResolver` + * Add `NAME` constant to `UlidType` and `UuidType` + +6.0 +--- + + * Remove `DoctrineTestHelper` and `TestRepositoryFactory` + +5.4 +--- + + * Add `DoctrineOpenTransactionLoggerMiddleware` to log when a transaction has been left open + * Deprecate `PdoCacheAdapterDoctrineSchemaSubscriber` and add `DoctrineDbalCacheAdapterSchemaSubscriber` instead + * `UniqueEntity` constraint retrieves a maximum of two entities if the default repository method is used. + * Add support for the newer bundle structure to `AbstractDoctrineExtension::loadMappingInformation()` + * Add argument `$bundleDir` to `AbstractDoctrineExtension::getMappingDriverBundleConfigDefaults()` + * Add argument `$bundleDir` to `AbstractDoctrineExtension::getMappingResourceConfigDirectory()` + +5.3 +--- + + * Deprecate `UserLoaderInterface::loadUserByUsername()` in favor of `UserLoaderInterface::loadUserByIdentifier() + * Deprecate `DoctrineTestHelper` and `TestRepositoryFactory` + * [BC BREAK] Remove `UuidV*Generator` classes + * Add `UuidGenerator` + * Add support for the new security-core `TokenVerifierInterface` in `DoctrineTokenProvider`, fixing parallel requests handling in remember-me + +5.2.0 +----- + + * added support for symfony/uid as `UlidType` and `UuidType` as Doctrine types + * added `UlidGenerator`, `UuidV1Generator`, `UuidV4Generator` and `UuidV6Generator` + +5.0.0 +----- + + * the `getMetadataDriverClass()` method is abstract and must be implemented by class extending `AbstractDoctrineExtension` + * passing an `IdReader` to the `DoctrineChoiceLoader` when the query cannot be optimized with single id field, throws an exception; pass `null` instead + * not explicitly passing an instance of `IdReader` to `DoctrineChoiceLoader` when it can optimize single id field, will not apply any optimization + * `DoctrineExtractor` now requires an `EntityManagerInterface` on instantiation + +4.4.0 +----- + + * [BC BREAK] using null as `$classValidatorRegexp` value in `DoctrineLoader::__construct` will not enable auto-mapping for all classes anymore, use `'{.*}'` instead. + * added `DoctrineClearEntityManagerWorkerSubscriber` + * deprecated `RegistryInterface`, use `Doctrine\Persistence\ManagerRegistry` + * added support for invokable event listeners + * added `getMetadataDriverClass` method to deprecate class parameters in service configuration files + +4.3.0 +----- + + * changed guessing of DECIMAL to set the `input` option of `NumberType` to string + * deprecated not passing an `IdReader` to the `DoctrineChoiceLoader` when query can be optimized with a single id field + * deprecated passing an `IdReader` to the `DoctrineChoiceLoader` when entities have a composite id + * added two Messenger middleware: `DoctrinePingConnectionMiddleware` and `DoctrineCloseConnectionMiddleware` + +4.2.0 +----- + + * deprecated injecting `ClassMetadataFactory` in `DoctrineExtractor`, + an instance of `EntityManagerInterface` should be injected instead + * added support for `simple_array` type + * the `DoctrineTransactionMiddlewareFactory` class has been removed + +4.1.0 +----- + + * added support for datetime immutable types in form type guesser + +4.0.0 +----- + + * the first constructor argument of the `DoctrineChoiceLoader` class must be + an `ObjectManager` implementation + * removed the `MergeDoctrineCollectionListener::onBind()` method + * trying to reset a non-lazy manager service using the `ManagerRegistry::resetService()` + method throws an exception + * removed the `DoctrineParserCache` class + +3.4.0 +----- + + * added support for doctrine/dbal v2.6 types + * added cause of UniqueEntity constraint violation + * deprecated `DbalSessionHandler` and `DbalSessionHandlerSchema` in favor of + `Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + +3.1.0 +----- + + * added "{{ value }}" message placeholder to UniqueEntityValidator + * deprecated `MergeDoctrineCollectionListener::onBind` in favor of + `MergeDoctrineCollectionListener::onSubmit` + * deprecated passing `ChoiceListFactoryInterface` as first argument of + `DoctrineChoiceLoader`'s constructor + +3.0.0 +----- + + * removed `EntityChoiceList` + * removed `$manager` (2nd) and `$class` (3th) arguments of `ORMQueryBuilderLoader` + * removed passing a query builder closure to `ORMQueryBuilderLoader` + * removed `loader` and `property` options of the `DoctrineType` + +2.8.0 +----- + + * deprecated using the entity provider with a Doctrine repository implementing UserProviderInterface + * added UserLoaderInterface for loading users through Doctrine. + +2.7.0 +----- + + * added DoctrineChoiceLoader + * deprecated EntityChoiceList + * deprecated passing a query builder closure to ORMQueryBuilderLoader + * deprecated $manager and $em arguments of ORMQueryBuilderLoader + * added optional arguments $propertyAccessor and $choiceListFactory to DoctrineOrmExtension constructor + * deprecated "loader" and "property" options of DoctrineType + +2.4.0 +----- + + * deprecated DoctrineOrmTestCase class + +2.2.0 +----- + + * added an optional PropertyAccessorInterface parameter to DoctrineType, + EntityType and EntityChoiceList + +2.1.0 +----- + + * added a default implementation of the ManagerRegistry + * added a session storage for Doctrine DBAL + * DoctrineOrmTypeGuesser now guesses "collection" for array Doctrine type + * DoctrineType now caches its choice lists in order to improve performance + * DoctrineType now uses ManagerRegistry::getManagerForClass() if the option "em" is not set + * UniqueEntity validation constraint now accepts a "repositoryMethod" option that will be used to check for uniqueness instead of the default "findBy" + * [BC BREAK] the DbalLogger::log() visibility has been changed from public to + protected diff --git a/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php b/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php new file mode 100644 index 0000000..ddf222e --- /dev/null +++ b/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\CacheWarmer; + +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; + +/** + * The proxy generator cache warmer generates all entity proxies. + * + * In the process of generating proxies the cache for all the metadata is primed also, + * since this information is necessary to build the proxies in the first place. + * + * @author Benjamin Eberlei + * + * @final since Symfony 7.1 + */ +class ProxyCacheWarmer implements CacheWarmerInterface +{ + public function __construct( + private readonly ManagerRegistry $registry, + ) { + } + + /** + * This cache warmer is not optional, without proxies fatal error occurs! + */ + public function isOptional(): bool + { + return false; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + $files = []; + foreach ($this->registry->getManagers() as $em) { + // we need the directory no matter the proxy cache generation strategy + if (!is_dir($proxyCacheDir = $em->getConfiguration()->getProxyDir())) { + if (false === @mkdir($proxyCacheDir, 0777, true) && !is_dir($proxyCacheDir)) { + throw new \RuntimeException(sprintf('Unable to create the Doctrine Proxy directory "%s".', $proxyCacheDir)); + } + } elseif (!is_writable($proxyCacheDir)) { + throw new \RuntimeException(sprintf('The Doctrine Proxy directory "%s" is not writeable for the current system user.', $proxyCacheDir)); + } + + // if proxies are autogenerated we don't need to generate them in the cache warmer + if ($em->getConfiguration()->getAutoGenerateProxyClasses()) { + continue; + } + + $classes = $em->getMetadataFactory()->getAllMetadata(); + + $em->getProxyFactory()->generateProxyClasses($classes); + + foreach (scandir($proxyCacheDir) as $file) { + if (!is_dir($file = $proxyCacheDir.'/'.$file)) { + $files[] = $file; + } + } + } + + return $files; + } +} diff --git a/vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php b/vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php new file mode 100644 index 0000000..3d331ac --- /dev/null +++ b/vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\EventManager; +use Doctrine\Common\EventSubscriber; +use Psr\Container\ContainerInterface; + +/** + * Allows lazy loading of listener and subscriber services. + * + * @author Johannes M. Schmitt + */ +class ContainerAwareEventManager extends EventManager +{ + /** + * Map of registered listeners. + * + * => + */ + private array $listeners = []; + private array $initialized = []; + private bool $initializedSubscribers = false; + private array $initializedHashMapping = []; + private array $methods = []; + private ContainerInterface $container; + + /** + * @param list $listeners List of [events, listener] tuples + */ + public function __construct(ContainerInterface $container, array $listeners = []) + { + $this->container = $container; + $this->listeners = $listeners; + } + + public function dispatchEvent(string $eventName, ?EventArgs $eventArgs = null): void + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + if (!isset($this->listeners[$eventName])) { + return; + } + + $eventArgs ??= EventArgs::getEmptyInstance(); + + if (!isset($this->initialized[$eventName])) { + $this->initializeListeners($eventName); + } + + foreach ($this->listeners[$eventName] as $hash => $listener) { + $listener->{$this->methods[$eventName][$hash]}($eventArgs); + } + } + + public function getListeners(string $event): array + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + if (!isset($this->initialized[$event])) { + $this->initializeListeners($event); + } + + return $this->listeners[$event]; + } + + public function getAllListeners(): array + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + + foreach ($this->listeners as $event => $listeners) { + if (!isset($this->initialized[$event])) { + $this->initializeListeners($event); + } + } + + return $this->listeners; + } + + public function hasListeners(string $event): bool + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + + return isset($this->listeners[$event]) && $this->listeners[$event]; + } + + public function addEventListener(string|array $events, object|string $listener): void + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + + $hash = $this->getHash($listener); + + foreach ((array) $events as $event) { + // Overrides listener if a previous one was associated already + // Prevents duplicate listeners on same event (same instance only) + $this->listeners[$event][$hash] = $listener; + + if (\is_string($listener)) { + unset($this->initialized[$event]); + unset($this->initializedHashMapping[$event][$hash]); + } else { + $this->methods[$event][$hash] = $this->getMethod($listener, $event); + } + } + } + + public function removeEventListener(string|array $events, object|string $listener): void + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + + $hash = $this->getHash($listener); + + foreach ((array) $events as $event) { + if (isset($this->initializedHashMapping[$event][$hash])) { + $hash = $this->initializedHashMapping[$event][$hash]; + unset($this->initializedHashMapping[$event][$hash]); + } + + // Check if we actually have this listener associated + if (isset($this->listeners[$event][$hash])) { + unset($this->listeners[$event][$hash]); + } + + if (isset($this->methods[$event][$hash])) { + unset($this->methods[$event][$hash]); + } + } + } + + public function addEventSubscriber(EventSubscriber $subscriber): void + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + + parent::addEventSubscriber($subscriber); + } + + public function removeEventSubscriber(EventSubscriber $subscriber): void + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); + } + + parent::removeEventSubscriber($subscriber); + } + + private function initializeListeners(string $eventName): void + { + $this->initialized[$eventName] = true; + + // We'll refill the whole array in order to keep the same order + $listeners = []; + foreach ($this->listeners[$eventName] as $hash => $listener) { + if (\is_string($listener)) { + $listener = $this->container->get($listener); + $newHash = $this->getHash($listener); + + $this->initializedHashMapping[$eventName][$hash] = $newHash; + + $listeners[$newHash] = $listener; + + $this->methods[$eventName][$newHash] = $this->getMethod($listener, $eventName); + } else { + $listeners[$hash] = $listener; + } + } + + $this->listeners[$eventName] = $listeners; + } + + private function initializeSubscribers(): void + { + $this->initializedSubscribers = true; + $listeners = $this->listeners; + $this->listeners = []; + foreach ($listeners as $listener) { + if (\is_array($listener)) { + $this->addEventListener(...$listener); + continue; + } + + throw new \InvalidArgumentException(sprintf('Using Doctrine subscriber "%s" is not allowed. Register it as a listener instead, using e.g. the #[AsDoctrineListener] or #[AsDocumentListener] attribute.', \is_object($listener) ? get_debug_type($listener) : $listener)); + } + } + + private function getHash(string|object $listener): string + { + if (\is_string($listener)) { + return '_service_'.$listener; + } + + return spl_object_hash($listener); + } + + private function getMethod(object $listener, string $event): string + { + if (!method_exists($listener, $event) && method_exists($listener, '__invoke')) { + return '__invoke'; + } + + return $event; + } +} diff --git a/vendor/symfony/doctrine-bridge/DataCollector/DoctrineDataCollector.php b/vendor/symfony/doctrine-bridge/DataCollector/DoctrineDataCollector.php new file mode 100644 index 0000000..ef0a369 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DataCollector/DoctrineDataCollector.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DataCollector; + +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Type; +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * DoctrineDataCollector. + * + * @author Fabien Potencier + */ +class DoctrineDataCollector extends DataCollector +{ + private array $connections; + private array $managers; + + public function __construct( + private ManagerRegistry $registry, + private DebugDataHolder $debugDataHolder, + ) { + $this->connections = $registry->getConnectionNames(); + $this->managers = $registry->getManagerNames(); + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $this->data = [ + 'queries' => $this->collectQueries(), + 'connections' => $this->connections, + 'managers' => $this->managers, + ]; + } + + private function collectQueries(): array + { + $queries = []; + + foreach ($this->debugDataHolder->getData() as $name => $data) { + $queries[$name] = $this->sanitizeQueries($name, $data); + } + + return $queries; + } + + public function reset(): void + { + $this->data = []; + $this->debugDataHolder->reset(); + } + + public function getManagers(): array + { + return $this->data['managers']; + } + + public function getConnections(): array + { + return $this->data['connections']; + } + + public function getQueryCount(): int + { + return array_sum(array_map('count', $this->data['queries'])); + } + + public function getQueries(): array + { + return $this->data['queries']; + } + + public function getTime(): float + { + $time = 0; + foreach ($this->data['queries'] as $queries) { + foreach ($queries as $query) { + $time += $query['executionMS']; + } + } + + return $time; + } + + public function getName(): string + { + return 'db'; + } + + protected function getCasters(): array + { + return parent::getCasters() + [ + ObjectParameter::class => static function (ObjectParameter $o, array $a, Stub $s): array { + $s->class = $o->getClass(); + $s->value = $o->getObject(); + + $r = new \ReflectionClass($o->getClass()); + if ($f = $r->getFileName()) { + $s->attr['file'] = $f; + $s->attr['line'] = $r->getStartLine(); + } else { + unset($s->attr['file']); + unset($s->attr['line']); + } + + if ($error = $o->getError()) { + return [Caster::PREFIX_VIRTUAL.'⚠' => $error->getMessage()]; + } + + if ($o->isStringable()) { + return [Caster::PREFIX_VIRTUAL.'__toString()' => (string) $o->getObject()]; + } + + return [Caster::PREFIX_VIRTUAL.'⚠' => sprintf('Object of class "%s" could not be converted to string.', $o->getClass())]; + }, + ]; + } + + private function sanitizeQueries(string $connectionName, array $queries): array + { + foreach ($queries as $i => $query) { + $queries[$i] = $this->sanitizeQuery($connectionName, $query); + } + + return $queries; + } + + private function sanitizeQuery(string $connectionName, array $query): array + { + $query['explainable'] = true; + $query['runnable'] = true; + $query['params'] ??= []; + if (!\is_array($query['params'])) { + $query['params'] = [$query['params']]; + } + if (!\is_array($query['types'])) { + $query['types'] = []; + } + foreach ($query['params'] as $j => $param) { + $e = null; + if (isset($query['types'][$j])) { + // Transform the param according to the type + $type = $query['types'][$j]; + if (\is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $query['types'][$j] = $type->getBindingType(); + try { + $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + } catch (\TypeError|ConversionException) { + } + } + } + + [$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e); + if (!$explainable) { + $query['explainable'] = false; + } + + if (!$runnable) { + $query['runnable'] = false; + } + } + + $query['params'] = $this->cloneVar($query['params']); + + return $query; + } + + /** + * Sanitizes a param. + * + * The return value is an array with the sanitized value and a boolean + * indicating if the original value was kept (allowing to use the sanitized + * value to explain the query). + */ + private function sanitizeParam(mixed $var, ?\Throwable $error): array + { + if (\is_object($var)) { + return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error]; + } + + if ($error) { + return ['⚠ '.$error->getMessage(), false, false]; + } + + if (\is_array($var)) { + $a = []; + $explainable = $runnable = true; + foreach ($var as $k => $v) { + [$value, $e, $r] = $this->sanitizeParam($v, null); + $explainable = $explainable && $e; + $runnable = $runnable && $r; + $a[$k] = $value; + } + + return [$a, $explainable, $runnable]; + } + + if (\is_resource($var)) { + return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false]; + } + + return [$var, true, true]; + } +} diff --git a/vendor/symfony/doctrine-bridge/DataCollector/ObjectParameter.php b/vendor/symfony/doctrine-bridge/DataCollector/ObjectParameter.php new file mode 100644 index 0000000..ce134ca --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DataCollector/ObjectParameter.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DataCollector; + +final class ObjectParameter +{ + private bool $stringable; + private string $class; + + public function __construct( + private readonly object $object, + private readonly ?\Throwable $error, + ) { + $this->stringable = $this->object instanceof \Stringable; + $this->class = $object::class; + } + + public function getObject(): object + { + return $this->object; + } + + public function getError(): ?\Throwable + { + return $this->error; + } + + public function isStringable(): bool + { + return $this->stringable; + } + + public function getClass(): string + { + return $this->class; + } +} diff --git a/vendor/symfony/doctrine-bridge/DependencyInjection/AbstractDoctrineExtension.php b/vendor/symfony/doctrine-bridge/DependencyInjection/AbstractDoctrineExtension.php new file mode 100644 index 0000000..94b99d8 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DependencyInjection/AbstractDoctrineExtension.php @@ -0,0 +1,425 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DependencyInjection; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** + * This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need. + * + * @author Benjamin Eberlei + */ +abstract class AbstractDoctrineExtension extends Extension +{ + /** + * Used inside metadata driver method to simplify aggregation of data. + */ + protected array $aliasMap = []; + + /** + * Used inside metadata driver method to simplify aggregation of data. + */ + protected array $drivers = []; + + /** + * @param array $objectManager A configured object manager + * + * @throws \InvalidArgumentException + */ + protected function loadMappingInformation(array $objectManager, ContainerBuilder $container): void + { + if ($objectManager['auto_mapping']) { + // automatically register bundle mappings + foreach (array_keys($container->getParameter('kernel.bundles')) as $bundle) { + if (!isset($objectManager['mappings'][$bundle])) { + $objectManager['mappings'][$bundle] = [ + 'mapping' => true, + 'is_bundle' => true, + ]; + } + } + } + + foreach ($objectManager['mappings'] as $mappingName => $mappingConfig) { + if (null !== $mappingConfig && false === $mappingConfig['mapping']) { + continue; + } + + $mappingConfig = array_replace([ + 'dir' => false, + 'type' => false, + 'prefix' => false, + ], (array) $mappingConfig); + + $mappingConfig['dir'] = $container->getParameterBag()->resolveValue($mappingConfig['dir']); + // a bundle configuration is detected by realizing that the specified dir is not absolute and existing + if (!isset($mappingConfig['is_bundle'])) { + $mappingConfig['is_bundle'] = !is_dir($mappingConfig['dir']); + } + + if ($mappingConfig['is_bundle']) { + $bundle = null; + $bundleMetadata = null; + foreach ($container->getParameter('kernel.bundles') as $name => $class) { + if ($mappingName === $name) { + $bundle = new \ReflectionClass($class); + $bundleMetadata = $container->getParameter('kernel.bundles_metadata')[$name]; + + break; + } + } + + if (null === $bundle) { + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName)); + } + + $mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container, $bundleMetadata['path']); + if (!$mappingConfig) { + continue; + } + } elseif (!$mappingConfig['type']) { + $mappingConfig['type'] = 'attribute'; + } + + $this->assertValidMappingConfiguration($mappingConfig, $objectManager['name']); + $this->setMappingDriverConfig($mappingConfig, $mappingName); + $this->setMappingDriverAlias($mappingConfig, $mappingName); + } + } + + /** + * Register the alias for this mapping driver. + * + * Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks. + */ + protected function setMappingDriverAlias(array $mappingConfig, string $mappingName): void + { + if (isset($mappingConfig['alias'])) { + $this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix']; + } else { + $this->aliasMap[$mappingName] = $mappingConfig['prefix']; + } + } + + /** + * Register the mapping driver configuration for later use with the object managers metadata driver chain. + * + * @throws \InvalidArgumentException + */ + protected function setMappingDriverConfig(array $mappingConfig, string $mappingName): void + { + $mappingDirectory = $mappingConfig['dir']; + if (!is_dir($mappingDirectory)) { + throw new \InvalidArgumentException(sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName)); + } + + $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory; + } + + /** + * If this is a bundle controlled mapping all the missing information can be autodetected by this method. + * + * Returns false when autodetection failed, an array of the completed information otherwise. + */ + protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container, ?string $bundleDir = null): array|false + { + $bundleClassDir = \dirname($bundle->getFileName()); + $bundleDir ??= $bundleClassDir; + + if (!$bundleConfig['type']) { + $bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container); + + if (!$bundleConfig['type'] && $bundleDir !== $bundleClassDir) { + $bundleConfig['type'] = $this->detectMetadataDriver($bundleClassDir, $container); + } + } + + if (!$bundleConfig['type']) { + // skip this bundle, no mapping information was found. + return false; + } + + if (!$bundleConfig['dir']) { + if (\in_array($bundleConfig['type'], ['staticphp', 'attribute'])) { + $bundleConfig['dir'] = $bundleClassDir.'/'.$this->getMappingObjectDefaultName(); + } else { + $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory($bundleDir); + } + } else { + $bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir']; + } + + if (!$bundleConfig['prefix']) { + $bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName(); + } + + return $bundleConfig; + } + + /** + * Register all the collected mapping information with the object manager by registering the appropriate mapping drivers. + */ + protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container): void + { + // configure metadata driver for each bundle based on the type of mapping files found + if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'))) { + $chainDriverDef = $container->getDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver')); + } else { + $chainDriverDef = new Definition($this->getMetadataDriverClass('driver_chain')); + } + + foreach ($this->drivers as $driverType => $driverPaths) { + $mappingService = $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver'); + if ($container->hasDefinition($mappingService)) { + $mappingDriverDef = $container->getDefinition($mappingService); + $args = $mappingDriverDef->getArguments(); + $args[0] = array_merge(array_values($driverPaths), $args[0]); + $mappingDriverDef->setArguments($args); + } else { + $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [ + array_values($driverPaths), + ]); + } + if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml') + || str_contains($mappingDriverDef->getClass(), 'Yaml') || str_contains($mappingDriverDef->getClass(), 'Xml') + ) { + $mappingDriverDef->setArguments([array_flip($driverPaths)]); + $mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']); + } + + $container->setDefinition($mappingService, $mappingDriverDef); + + foreach ($driverPaths as $prefix => $driverPath) { + $chainDriverDef->addMethodCall('addDriver', [new Reference($mappingService), $prefix]); + } + } + + $container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef); + } + + /** + * Assertion if the specified mapping information is valid. + * + * @throws \InvalidArgumentException + */ + protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName): void + { + if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) { + throw new \InvalidArgumentException(sprintf('Mapping definitions for Doctrine manager "%s" require at least the "type", "dir" and "prefix" options.', $objectManagerName)); + } + + if (!is_dir($mappingConfig['dir'])) { + throw new \InvalidArgumentException(sprintf('Specified non-existing directory "%s" as Doctrine mapping source.', $mappingConfig['dir'])); + } + + if (!\in_array($mappingConfig['type'], ['xml', 'yml', 'php', 'staticphp', 'attribute'])) { + throw new \InvalidArgumentException(sprintf('Can only configure "xml", "yml", "php", "staticphp" or "attribute" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. You can register them by adding a new driver to the "%s" service definition.', $this->getObjectManagerElementName($objectManagerName.'_metadata_driver'))); + } + } + + /** + * Detects what metadata driver to use for the supplied directory. + */ + protected function detectMetadataDriver(string $dir, ContainerBuilder $container): ?string + { + $configPath = $this->getMappingResourceConfigDirectory($dir); + $extension = $this->getMappingResourceExtension(); + + if (glob($dir.'/'.$configPath.'/*.'.$extension.'.xml', \GLOB_NOSORT)) { + $driver = 'xml'; + } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.yml', \GLOB_NOSORT)) { + $driver = 'yml'; + } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.php', \GLOB_NOSORT)) { + $driver = 'php'; + } else { + // add the closest existing directory as a resource + $resource = $dir.'/'.$configPath; + while (!is_dir($resource)) { + $resource = \dirname($resource); + } + $container->fileExists($resource, false); + + if ($container->fileExists($dir.'/'.$this->getMappingObjectDefaultName(), false)) { + return 'attribute'; + } + + return null; + } + $container->fileExists($dir.'/'.$configPath, false); + + return $driver; + } + + /** + * Loads a configured object manager metadata, query or result cache driver. + * + * @throws \InvalidArgumentException in case of unknown driver type + */ + protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName): void + { + $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container); + } + + /** + * Loads a cache driver. + * + * @throws \InvalidArgumentException + */ + protected function loadCacheDriver(string $cacheName, string $objectManagerName, array $cacheDriver, ContainerBuilder $container): string + { + $cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName); + + switch ($cacheDriver['type']) { + case 'service': + $container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false)); + + return $cacheDriverServiceId; + case 'memcached': + $memcachedClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcached.class').'%'; + $memcachedInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcached_instance.class').'%'; + $memcachedHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.memcached_host').'%'; + $memcachedPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcached_port').'%'; + $cacheDef = new Definition($memcachedClass); + $memcachedInstance = new Definition($memcachedInstanceClass); + $memcachedInstance->addMethodCall('addServer', [ + $memcachedHost, $memcachedPort, + ]); + $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)), $memcachedInstance); + $cacheDef->addMethodCall('setMemcached', [new Reference($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)))]); + break; + case 'redis': + $redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%'; + $redisInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.redis_instance.class').'%'; + $redisHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.redis_host').'%'; + $redisPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.redis_port').'%'; + $cacheDef = new Definition($redisClass); + $redisInstance = new Definition($redisInstanceClass); + $redisInstance->addMethodCall('connect', [ + $redisHost, $redisPort, + ]); + $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)), $redisInstance); + $cacheDef->addMethodCall('setRedis', [new Reference($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)))]); + break; + case 'apc': + case 'apcu': + case 'array': + case 'xcache': + case 'wincache': + case 'zenddata': + $cacheDef = new Definition('%'.$this->getObjectManagerElementName(sprintf('cache.%s.class', $cacheDriver['type'])).'%'); + break; + default: + throw new \InvalidArgumentException(sprintf('"%s" is an unrecognized Doctrine cache driver.', $cacheDriver['type'])); + } + + if (!isset($cacheDriver['namespace'])) { + // generate a unique namespace for the given application + if ($container->hasParameter('cache.prefix.seed')) { + $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); + } else { + $seed = '_'.$container->getParameter('kernel.project_dir'); + $seed .= '.'.$container->getParameter('kernel.container_class'); + } + + $namespace = 'sf_'.$this->getMappingResourceExtension().'_'.$objectManagerName.'_'.ContainerBuilder::hash($seed); + + $cacheDriver['namespace'] = $namespace; + } + + $cacheDef->addMethodCall('setNamespace', [$cacheDriver['namespace']]); + + $container->setDefinition($cacheDriverServiceId, $cacheDef); + + return $cacheDriverServiceId; + } + + /** + * Returns a modified version of $managerConfigs. + * + * The manager called $autoMappedManager will map all bundles that are not mapped by other managers. + */ + protected function fixManagersAutoMappings(array $managerConfigs, array $bundles): array + { + if ($autoMappedManager = $this->validateAutoMapping($managerConfigs)) { + foreach (array_keys($bundles) as $bundle) { + foreach ($managerConfigs as $manager) { + if (isset($manager['mappings'][$bundle])) { + continue 2; + } + } + $managerConfigs[$autoMappedManager]['mappings'][$bundle] = [ + 'mapping' => true, + 'is_bundle' => true, + ]; + } + $managerConfigs[$autoMappedManager]['auto_mapping'] = false; + } + + return $managerConfigs; + } + + /** + * Prefixes the relative dependency injection container path with the object manager prefix. + * + * @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager' + */ + abstract protected function getObjectManagerElementName(string $name): string; + + /** + * Noun that describes the mapped objects such as Entity or Document. + * + * Will be used for autodetection of persistent objects directory. + */ + abstract protected function getMappingObjectDefaultName(): string; + + /** + * Relative path from the bundle root to the directory where mapping files reside. + */ + abstract protected function getMappingResourceConfigDirectory(?string $bundleDir = null): string; + + /** + * Extension used by the mapping files. + */ + abstract protected function getMappingResourceExtension(): string; + + /** + * The class name used by the various mapping drivers. + */ + abstract protected function getMetadataDriverClass(string $driverType): string; + + /** + * Search for a manager that is declared as 'auto_mapping' = true. + * + * @throws \LogicException + */ + private function validateAutoMapping(array $managerConfigs): ?string + { + $autoMappedManager = null; + foreach ($managerConfigs as $name => $manager) { + if (!$manager['auto_mapping']) { + continue; + } + + if (null !== $autoMappedManager) { + throw new \LogicException(sprintf('You cannot enable "auto_mapping" on more than one manager at the same time (found in "%s" and "%s"").', $autoMappedManager, $name)); + } + + $autoMappedManager = $name; + } + + return $autoMappedManager; + } +} diff --git a/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/DoctrineValidationPass.php b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/DoctrineValidationPass.php new file mode 100644 index 0000000..38802e8 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/DoctrineValidationPass.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Registers additional validators. + * + * @author Benjamin Eberlei + */ +class DoctrineValidationPass implements CompilerPassInterface +{ + public function __construct( + private readonly string $managerType, + ) { + } + + public function process(ContainerBuilder $container): void + { + $this->updateValidatorMappingFiles($container, 'xml', 'xml'); + $this->updateValidatorMappingFiles($container, 'yaml', 'yml'); + } + + /** + * Gets the validation mapping files for the format and extends them with + * files matching a doctrine search pattern (Resources/config/validation.orm.xml). + */ + private function updateValidatorMappingFiles(ContainerBuilder $container, string $mapping, string $extension): void + { + if (!$container->hasParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files')) { + return; + } + + $files = $container->getParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files'); + $validationPath = '/config/validation.'.$this->managerType.'.'.$extension; + + foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { + if ($container->fileExists($file = $bundle['path'].'/Resources'.$validationPath) || $container->fileExists($file = $bundle['path'].$validationPath)) { + $files[] = $file; + } + } + + $container->setParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files', $files); + } +} diff --git a/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php new file mode 100644 index 0000000..cf9cb23 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; + +use Symfony\Bridge\Doctrine\ContainerAwareEventManager; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers event listeners to the available doctrine connections. + * + * @author Jeremy Mikola + * @author Alexander + * @author David Maicher + */ +class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface +{ + private array $connections; + + /** + * @var array + */ + private array $eventManagers = []; + + /** + * @param string $managerTemplate sprintf() template for generating the event + * manager's service ID for a connection name + * @param string $tagPrefix Tag prefix for listeners + */ + public function __construct( + private readonly string $connectionsParameter, + private readonly string $managerTemplate, + private readonly string $tagPrefix, + ) { + } + + public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter($this->connectionsParameter)) { + return; + } + + $this->connections = $container->getParameter($this->connectionsParameter); + $listenerRefs = $this->addTaggedServices($container); + + // replace service container argument of event managers with smaller service locator + // so services can even remain private + foreach ($listenerRefs as $connection => $refs) { + $this->getEventManagerDef($container, $connection) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs)); + } + } + + private function addTaggedServices(ContainerBuilder $container): array + { + $listenerRefs = []; + $managerDefs = []; + foreach ($this->findAndSortTags($container) as [$id, $tag]) { + $connections = isset($tag['connection']) + ? [$container->getParameterBag()->resolveValue($tag['connection'])] + : array_keys($this->connections); + if (!isset($tag['event'])) { + throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); + } + foreach ($connections as $con) { + if (!isset($this->connections[$con])) { + throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: "%s".', $con, $id, implode('", "', array_keys($this->connections)))); + } + + if (!isset($managerDefs[$con])) { + $managerDef = $parentDef = $this->getEventManagerDef($container, $con); + while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { + $parentDef = $container->findDefinition($parentDef->getParent()); + } + $managerClass = $container->getParameterBag()->resolveValue($parentDef->getClass()); + $managerDefs[$con] = [$managerDef, $managerClass]; + } else { + [$managerDef, $managerClass] = $managerDefs[$con]; + } + + if (ContainerAwareEventManager::class === $managerClass) { + $refs = $managerDef->getArguments()[1] ?? []; + $listenerRefs[$con][$id] = new Reference($id); + $refs[] = [[$tag['event']], $id]; + $managerDef->setArgument(1, $refs); + } else { + $managerDef->addMethodCall('addEventListener', [[$tag['event']], new Reference($id)]); + } + } + } + + return $listenerRefs; + } + + private function getEventManagerDef(ContainerBuilder $container, string $name): Definition + { + if (!isset($this->eventManagers[$name])) { + $this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name)); + } + + return $this->eventManagers[$name]; + } + + /** + * Finds and orders all service tags with the given name by their priority. + * + * The order of additions must be respected for services having the same priority, + * and knowing that the \SplPriorityQueue class does not respect the FIFO method, + * we should not use this class. + * + * @see https://bugs.php.net/53710 + * @see https://bugs.php.net/60926 + */ + private function findAndSortTags(ContainerBuilder $container): array + { + $sortedTags = []; + + foreach ($container->findTaggedServiceIds($this->tagPrefix.'.event_listener', true) as $serviceId => $tags) { + foreach ($tags as $attributes) { + $priority = $attributes['priority'] ?? 0; + $sortedTags[$priority][] = [$serviceId, $attributes]; + } + } + + krsort($sortedTags); + + return array_merge(...$sortedTags); + } +} diff --git a/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterMappingsPass.php b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterMappingsPass.php new file mode 100644 index 0000000..2aad6ef --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterMappingsPass.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Base class for the doctrine bundles to provide a compiler pass class that + * helps to register doctrine mappings. + * + * The compiler pass is meant to register the mappings with the metadata + * chain driver corresponding to one of the object managers. + * + * For concrete implementations, see the RegisterXyMappingsPass classes + * in the DoctrineBundle resp. + * DoctrineMongodbBundle, DoctrineCouchdbBundle and DoctrinePhpcrBundle. + * + * @author David Buchmann + */ +abstract class RegisterMappingsPass implements CompilerPassInterface +{ + /** + * DI object for the driver to use, either a service definition for a + * private service or a reference for a public service. + */ + protected Definition|Reference $driver; + + /** + * List of namespaces handled by the driver. + * + * @var string[] + */ + protected array $namespaces; + + /** + * List of potential container parameters that hold the object manager name + * to register the mappings with the correct metadata driver, for example + * ['acme.manager', 'doctrine.default_entity_manager']. + * + * @var string[] + */ + protected array $managerParameters; + + /** + * Naming pattern of the metadata chain driver service ids, for example + * 'doctrine.orm.%s_metadata_driver'. + */ + protected string $driverPattern; + + /** + * A name for a parameter in the container. If set, this compiler pass will + * only do anything if the parameter is present. (But regardless of the + * value of that parameter. + */ + protected string|false $enabledParameter; + + /** + * The $managerParameters is an ordered list of container parameters that could provide the + * name of the manager to register these namespaces and alias on. The first non-empty name + * is used, the others skipped. + * + * The $aliasMap parameter can be used to define bundle namespace shortcuts like the + * DoctrineBundle provides automatically for objects in the default Entity/Document folder. + * + * @param Definition|Reference $driver Driver DI definition or reference + * @param string[] $namespaces List of namespaces handled by $driver + * @param string[] $managerParameters list of container parameters that could + * hold the manager name + * @param string $driverPattern Pattern for the metadata driver service name + * @param string|false $enabledParameter Service container parameter that must be + * present to enable the mapping. Set to false + * to not do any check, optional. + * @param string $configurationPattern Pattern for the Configuration service name, + * for example 'doctrine.orm.%s_configuration'. + * @param string $registerAliasMethodName Method name to call on the configuration service. This + * depends on the Doctrine implementation. + * For example addEntityNamespace. + * @param string[] $aliasMap Map of alias to namespace + */ + public function __construct( + Definition|Reference $driver, + array $namespaces, + array $managerParameters, + string $driverPattern, + string|false $enabledParameter = false, + private readonly string $configurationPattern = '', + private readonly string $registerAliasMethodName = '', + private readonly array $aliasMap = [], + ) { + $this->driver = $driver; + $this->namespaces = $namespaces; + $this->managerParameters = $managerParameters; + $this->driverPattern = $driverPattern; + $this->enabledParameter = $enabledParameter; + + if ($aliasMap && (!$configurationPattern || !$registerAliasMethodName)) { + throw new \InvalidArgumentException('configurationPattern and registerAliasMethodName are required to register namespace alias.'); + } + } + + /** + * Register mappings and alias with the metadata drivers. + */ + public function process(ContainerBuilder $container): void + { + if (!$this->enabled($container)) { + return; + } + + $mappingDriverDef = $this->getDriver($container); + $chainDriverDefService = $this->getChainDriverServiceName($container); + // Definition for a Doctrine\Persistence\Mapping\Driver\MappingDriverChain + $chainDriverDef = $container->getDefinition($chainDriverDefService); + foreach ($this->namespaces as $namespace) { + $chainDriverDef->addMethodCall('addDriver', [$mappingDriverDef, $namespace]); + } + + if (!\count($this->aliasMap)) { + return; + } + + $configurationServiceName = $this->getConfigurationServiceName($container); + // Definition of the Doctrine\...\Configuration class specific to the Doctrine flavour. + $configurationServiceDefinition = $container->getDefinition($configurationServiceName); + foreach ($this->aliasMap as $alias => $namespace) { + $configurationServiceDefinition->addMethodCall($this->registerAliasMethodName, [$alias, $namespace]); + } + } + + /** + * Get the service name of the metadata chain driver that the mappings + * should be registered with. + * + * @throws InvalidArgumentException if non of the managerParameters has a + * non-empty value + */ + protected function getChainDriverServiceName(ContainerBuilder $container): string + { + return sprintf($this->driverPattern, $this->getManagerName($container)); + } + + /** + * Create the service definition for the metadata driver. + * + * @param ContainerBuilder $container Passed on in case an extending class + * needs access to the container + */ + protected function getDriver(ContainerBuilder $container): Definition|Reference + { + return $this->driver; + } + + /** + * Get the service name from the pattern and the configured manager name. + * + * @throws InvalidArgumentException if none of the managerParameters has a + * non-empty value + */ + private function getConfigurationServiceName(ContainerBuilder $container): string + { + return sprintf($this->configurationPattern, $this->getManagerName($container)); + } + + /** + * Determine the manager name. + * + * The default implementation loops over the managerParameters and returns + * the first non-empty parameter. + * + * @throws InvalidArgumentException if none of the managerParameters is found in the container + */ + private function getManagerName(ContainerBuilder $container): string + { + foreach ($this->managerParameters as $param) { + if ($container->hasParameter($param)) { + $name = $container->getParameter($param); + if ($name) { + return $name; + } + } + } + + throw new InvalidArgumentException(sprintf('Could not find the manager name parameter in the container. Tried the following parameter names: "%s".', implode('", "', $this->managerParameters))); + } + + /** + * Determine whether this mapping should be activated or not. This allows + * to take this decision with the container builder available. + * + * This default implementation checks if the class has the enabledParameter + * configured and if so if that parameter is present in the container. + */ + protected function enabled(ContainerBuilder $container): bool + { + return !$this->enabledParameter || $container->hasParameter($this->enabledParameter); + } +} diff --git a/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterUidTypePass.php b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterUidTypePass.php new file mode 100644 index 0000000..fd27f60 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterUidTypePass.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; + +use Symfony\Bridge\Doctrine\Types\UlidType; +use Symfony\Bridge\Doctrine\Types\UuidType; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Uid\AbstractUid; + +final class RegisterUidTypePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!class_exists(AbstractUid::class)) { + return; + } + + if (!$container->hasParameter('doctrine.dbal.connection_factory.types')) { + return; + } + + $typeDefinition = $container->getParameter('doctrine.dbal.connection_factory.types'); + + if (!isset($typeDefinition['uuid'])) { + $typeDefinition['uuid'] = ['class' => UuidType::class]; + } + + if (!isset($typeDefinition['ulid'])) { + $typeDefinition['ulid'] = ['class' => UlidType::class]; + } + + $container->setParameter('doctrine.dbal.connection_factory.types', $typeDefinition); + } +} diff --git a/vendor/symfony/doctrine-bridge/DependencyInjection/Security/UserProvider/EntityFactory.php b/vendor/symfony/doctrine-bridge/DependencyInjection/Security/UserProvider/EntityFactory.php new file mode 100644 index 0000000..d39b953 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/DependencyInjection/Security/UserProvider/EntityFactory.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider; + +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * EntityFactory creates services for Doctrine user provider. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * + * @final + */ +class EntityFactory implements UserProviderFactoryInterface +{ + public function __construct( + private readonly string $key, + private readonly string $providerId, + ) { + } + + public function create(ContainerBuilder $container, string $id, array $config): void + { + $container + ->setDefinition($id, new ChildDefinition($this->providerId)) + ->addArgument($config['class']) + ->addArgument($config['property']) + ->addArgument($config['manager_name']) + ; + } + + public function getKey(): string + { + return $this->key; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->children() + ->scalarNode('class') + ->isRequired() + ->info('The full entity class name of your user class.') + ->cannotBeEmpty() + ->end() + ->scalarNode('property')->defaultNull()->end() + ->scalarNode('manager_name')->defaultNull()->end() + ->end() + ; + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/ChoiceList/DoctrineChoiceLoader.php b/vendor/symfony/doctrine-bridge/Form/ChoiceList/DoctrineChoiceLoader.php new file mode 100644 index 0000000..1b7c94d --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/ChoiceList/DoctrineChoiceLoader.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\ChoiceList; + +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; + +/** + * Loads choices using a Doctrine object manager. + * + * @author Bernhard Schussek + */ +class DoctrineChoiceLoader extends AbstractChoiceLoader +{ + /** @var class-string */ + private readonly string $class; + + /** + * Creates a new choice loader. + * + * Optionally, an implementation of {@link EntityLoaderInterface} can be + * passed which optimizes the object loading for one of the Doctrine + * mapper implementations. + * + * @param string $class The class name of the loaded objects + */ + public function __construct( + private readonly ObjectManager $manager, + string $class, + private readonly ?IdReader $idReader = null, + private readonly ?EntityLoaderInterface $objectLoader = null, + ) { + if ($idReader && !$idReader->isSingleId()) { + throw new \InvalidArgumentException(sprintf('The "$idReader" argument of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); + } + + $this->class = $manager->getClassMetadata($class)->getName(); + } + + protected function loadChoices(): iterable + { + return $this->objectLoader + ? $this->objectLoader->getEntities() + : $this->manager->getRepository($this->class)->findAll(); + } + + protected function doLoadValuesForChoices(array $choices): array + { + // Optimize performance for single-field identifiers. We already + // know that the IDs are used as values + // Attention: This optimization does not check choices for existence + if ($this->idReader) { + throw new LogicException('Not defining the IdReader explicitly as a value callback when the query can be optimized is not supported.'); + } + + return parent::doLoadValuesForChoices($choices); + } + + protected function doLoadChoicesForValues(array $values, ?callable $value): array + { + if ($this->idReader && null === $value) { + throw new LogicException('Not defining the IdReader explicitly as a value callback when the query can be optimized is not supported.'); + } + + $idReader = null; + if (\is_array($value) && $value[0] instanceof IdReader) { + $idReader = $value[0]; + } elseif ($value instanceof \Closure && ($rThis = (new \ReflectionFunction($value))->getClosureThis()) instanceof IdReader) { + $idReader = $rThis; + } + + // Optimize performance in case we have an object loader and + // a single-field identifier + if ($idReader && $this->objectLoader) { + $objects = []; + $objectsById = []; + + // Maintain order and indices from the given $values + // An alternative approach to the following loop is to add the + // "INDEX BY" clause to the Doctrine query in the loader, + // but I'm not sure whether that's doable in a generic fashion. + foreach ($this->objectLoader->getEntitiesByIds($idReader->getIdField(), $values) as $object) { + $objectsById[$idReader->getIdValue($object)] = $object; + } + + foreach ($values as $i => $id) { + if (isset($objectsById[$id])) { + $objects[$i] = $objectsById[$id]; + } + } + + return $objects; + } + + return parent::doLoadChoicesForValues($values, $value); + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/ChoiceList/EntityLoaderInterface.php b/vendor/symfony/doctrine-bridge/Form/ChoiceList/EntityLoaderInterface.php new file mode 100644 index 0000000..51a497b --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/ChoiceList/EntityLoaderInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\ChoiceList; + +/** + * Custom loader for entities in the choice list. + * + * @author Benjamin Eberlei + */ +interface EntityLoaderInterface +{ + /** + * Returns an array of entities that are valid choices in the corresponding choice list. + */ + public function getEntities(): array; + + /** + * Returns an array of entities matching the given identifiers. + */ + public function getEntitiesByIds(string $identifier, array $values): array; +} diff --git a/vendor/symfony/doctrine-bridge/Form/ChoiceList/IdReader.php b/vendor/symfony/doctrine-bridge/Form/ChoiceList/IdReader.php new file mode 100644 index 0000000..1baed3b --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/ChoiceList/IdReader.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\ChoiceList; + +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\Form\Exception\RuntimeException; + +/** + * A utility for reading object IDs. + * + * @author Bernhard Schussek + * + * @internal + */ +class IdReader +{ + private readonly bool $singleId; + private readonly bool $intId; + private readonly string $idField; + private readonly ?self $associationIdReader; + + public function __construct( + private readonly ObjectManager $om, + private readonly ClassMetadata $classMetadata, + ) { + $ids = $classMetadata->getIdentifierFieldNames(); + $idType = $classMetadata->getTypeOfField(current($ids)); + + $singleId = 1 === \count($ids); + $this->idField = current($ids); + + // single field association are resolved, since the schema column could be an int + if ($singleId && $classMetadata->hasAssociation($this->idField)) { + $this->associationIdReader = new self($om, $om->getClassMetadata( + $classMetadata->getAssociationTargetClass($this->idField) + )); + + $singleId = $this->associationIdReader->isSingleId(); + $this->intId = $this->associationIdReader->isIntId(); + } else { + $this->intId = $singleId && \in_array($idType, ['integer', 'smallint', 'bigint']); + $this->associationIdReader = null; + } + + $this->singleId = $singleId; + } + + /** + * Returns whether the class has a single-column ID. + */ + public function isSingleId(): bool + { + return $this->singleId; + } + + /** + * Returns whether the class has a single-column integer ID. + */ + public function isIntId(): bool + { + return $this->intId; + } + + /** + * Returns the ID value for an object. + * + * This method assumes that the object has a single-column ID. + */ + public function getIdValue(?object $object = null): string + { + if (!$object) { + return ''; + } + + if (!$this->om->contains($object)) { + throw new RuntimeException(sprintf('Entity of type "%s" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?', get_debug_type($object))); + } + + $this->om->initializeObject($object); + + $idValue = current($this->classMetadata->getIdentifierValues($object)); + + if ($this->associationIdReader) { + $idValue = $this->associationIdReader->getIdValue($idValue); + } + + return (string) $idValue; + } + + /** + * Returns the name of the ID field. + * + * This method assumes that the object has a single-column ID. + */ + public function getIdField(): string + { + return $this->idField; + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/ChoiceList/ORMQueryBuilderLoader.php b/vendor/symfony/doctrine-bridge/Form/ChoiceList/ORMQueryBuilderLoader.php new file mode 100644 index 0000000..9d7b9d3 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\ChoiceList; + +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Type; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Loads entities using a {@link QueryBuilder} instance. + * + * @author Benjamin Eberlei + * @author Bernhard Schussek + */ +class ORMQueryBuilderLoader implements EntityLoaderInterface +{ + public function __construct( + private readonly QueryBuilder $queryBuilder, + ) { + } + + public function getEntities(): array + { + return $this->queryBuilder->getQuery()->execute(); + } + + public function getEntitiesByIds(string $identifier, array $values): array + { + if (null !== $this->queryBuilder->getMaxResults() || 0 < (int) $this->queryBuilder->getFirstResult()) { + // an offset or a limit would apply on results including the where clause with submitted id values + // that could make invalid choices valid + $choices = []; + $metadata = $this->queryBuilder->getEntityManager()->getClassMetadata(current($this->queryBuilder->getRootEntities())); + + foreach ($this->getEntities() as $entity) { + if (\in_array((string) current($metadata->getIdentifierValues($entity)), $values, true)) { + $choices[] = $entity; + } + } + + return $choices; + } + + $qb = clone $this->queryBuilder; + $alias = current($qb->getRootAliases()); + $parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier; + $parameter = str_replace('.', '_', $parameter); + $where = $qb->expr()->in($alias.'.'.$identifier, ':'.$parameter); + + // Guess type + $entity = current($qb->getRootEntities()); + $metadata = $qb->getEntityManager()->getClassMetadata($entity); + if (\in_array($type = $metadata->getTypeOfField($identifier), ['integer', 'bigint', 'smallint'])) { + $parameterType = ArrayParameterType::INTEGER; + + // Filter out non-integer values (e.g. ""). If we don't, some + // databases such as PostgreSQL fail. + $values = array_values(array_filter($values, fn ($v) => (string) $v === (string) (int) $v || ctype_digit($v))); + } elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) { + $parameterType = ArrayParameterType::STRING; + + // Like above, but we just filter out empty strings. + $values = array_values(array_filter($values, fn ($v) => '' !== (string) $v)); + + // Convert values into right type + if (Type::hasType($type)) { + $doctrineType = Type::getType($type); + $platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform(); + foreach ($values as &$value) { + try { + $value = $doctrineType->convertToDatabaseValue($value, $platform); + } catch (ConversionException $e) { + throw new TransformationFailedException(sprintf('Failed to transform "%s" into "%s".', $value, $type), 0, $e); + } + } + unset($value); + } + } else { + $parameterType = ArrayParameterType::STRING; + } + if (!$values) { + return []; + } + + return $qb->andWhere($where) + ->getQuery() + ->setParameter($parameter, $values, $parameterType) + ->getResult(); + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/DataTransformer/CollectionToArrayTransformer.php b/vendor/symfony/doctrine-bridge/Form/DataTransformer/CollectionToArrayTransformer.php new file mode 100644 index 0000000..61fc5f8 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/DataTransformer/CollectionToArrayTransformer.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\DataTransformer; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + * + * @implements DataTransformerInterface + */ +class CollectionToArrayTransformer implements DataTransformerInterface +{ + /** + * Transforms a collection into an array. + * + * @throws TransformationFailedException + */ + public function transform(mixed $collection): mixed + { + if (null === $collection) { + return []; + } + + // For cases when the collection getter returns $collection->toArray() + // in order to prevent modifications of the returned collection + if (\is_array($collection)) { + return $collection; + } + + if (!$collection instanceof Collection) { + throw new TransformationFailedException('Expected a Doctrine\Common\Collections\Collection object.'); + } + + return $collection->toArray(); + } + + /** + * Transforms an array into a collection. + */ + public function reverseTransform(mixed $array): Collection + { + if ('' === $array || null === $array) { + $array = []; + } else { + $array = (array) $array; + } + + return new ArrayCollection($array); + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/DoctrineOrmExtension.php b/vendor/symfony/doctrine-bridge/Form/DoctrineOrmExtension.php new file mode 100644 index 0000000..66ae854 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/DoctrineOrmExtension.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form; + +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Form\FormTypeGuesserInterface; + +class DoctrineOrmExtension extends AbstractExtension +{ + protected ManagerRegistry $registry; + + public function __construct(ManagerRegistry $registry) + { + $this->registry = $registry; + } + + protected function loadTypes(): array + { + return [ + new EntityType($this->registry), + ]; + } + + protected function loadTypeGuesser(): ?FormTypeGuesserInterface + { + return new DoctrineOrmTypeGuesser($this->registry); + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/DoctrineOrmTypeGuesser.php b/vendor/symfony/doctrine-bridge/Form/DoctrineOrmTypeGuesser.php new file mode 100644 index 0000000..47986a2 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/DoctrineOrmTypeGuesser.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form; + +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\FieldMapping; +use Doctrine\ORM\Mapping\JoinColumnMapping; +use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\MappingException; +use Doctrine\Persistence\Proxy; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\DateIntervalType; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\TimeType; +use Symfony\Component\Form\FormTypeGuesserInterface; +use Symfony\Component\Form\Guess\Guess; +use Symfony\Component\Form\Guess\TypeGuess; +use Symfony\Component\Form\Guess\ValueGuess; + +class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface +{ + protected ManagerRegistry $registry; + + private array $cache = []; + + public function __construct(ManagerRegistry $registry) + { + $this->registry = $registry; + } + + public function guessType(string $class, string $property): ?TypeGuess + { + if (!$ret = $this->getMetadata($class)) { + return new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE); + } + + [$metadata, $name] = $ret; + + if ($metadata->hasAssociation($property)) { + $multiple = $metadata->isCollectionValuedAssociation($property); + $mapping = $metadata->getAssociationMapping($property); + + return new TypeGuess(EntityType::class, ['em' => $name, 'class' => $mapping['targetEntity'], 'multiple' => $multiple], Guess::HIGH_CONFIDENCE); + } + + return match ($metadata->getTypeOfField($property)) { + 'array', // DBAL < 4 + Types::SIMPLE_ARRAY => new TypeGuess(CollectionType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::BOOLEAN => new TypeGuess(CheckboxType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATETIME_MUTABLE, + Types::DATETIMETZ_MUTABLE, + 'vardatetime' => new TypeGuess(DateTimeType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATETIME_IMMUTABLE, + Types::DATETIMETZ_IMMUTABLE => new TypeGuess(DateTimeType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE), + Types::DATEINTERVAL => new TypeGuess(DateIntervalType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATE_MUTABLE => new TypeGuess(DateType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATE_IMMUTABLE => new TypeGuess(DateType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE), + Types::TIME_MUTABLE => new TypeGuess(TimeType::class, [], Guess::HIGH_CONFIDENCE), + Types::TIME_IMMUTABLE => new TypeGuess(TimeType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE), + Types::DECIMAL => new TypeGuess(NumberType::class, ['input' => 'string'], Guess::MEDIUM_CONFIDENCE), + Types::FLOAT => new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::INTEGER, + Types::BIGINT, + Types::SMALLINT => new TypeGuess(IntegerType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::STRING => new TypeGuess(TextType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::TEXT => new TypeGuess(TextareaType::class, [], Guess::MEDIUM_CONFIDENCE), + default => new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE), + }; + } + + public function guessRequired(string $class, string $property): ?ValueGuess + { + $classMetadatas = $this->getMetadata($class); + + if (!$classMetadatas) { + return null; + } + + /** @var ClassMetadataInfo $classMetadata */ + $classMetadata = $classMetadatas[0]; + + // Check whether the field exists and is nullable or not + if (isset($classMetadata->fieldMappings[$property])) { + if (!$classMetadata->isNullable($property) && Types::BOOLEAN !== $classMetadata->getTypeOfField($property)) { + return new ValueGuess(true, Guess::HIGH_CONFIDENCE); + } + + return new ValueGuess(false, Guess::MEDIUM_CONFIDENCE); + } + + // Check whether the association exists, is a to-one association and its + // join column is nullable or not + if ($classMetadata->isAssociationWithSingleJoinColumn($property)) { + $mapping = $classMetadata->getAssociationMapping($property); + + if (null === self::getMappingValue($mapping['joinColumns'][0], 'nullable')) { + // The "nullable" option defaults to true, in that case the + // field should not be required. + return new ValueGuess(false, Guess::HIGH_CONFIDENCE); + } + + return new ValueGuess(!self::getMappingValue($mapping['joinColumns'][0], 'nullable'), Guess::HIGH_CONFIDENCE); + } + + return null; + } + + public function guessMaxLength(string $class, string $property): ?ValueGuess + { + $ret = $this->getMetadata($class); + if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { + $mapping = $ret[0]->getFieldMapping($property); + + $length = $mapping instanceof FieldMapping ? $mapping->length : ($mapping['length'] ?? null); + + if (null !== $length) { + return new ValueGuess($length, Guess::HIGH_CONFIDENCE); + } + + if (\in_array($ret[0]->getTypeOfField($property), [Types::DECIMAL, Types::FLOAT])) { + return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); + } + } + + return null; + } + + public function guessPattern(string $class, string $property): ?ValueGuess + { + $ret = $this->getMetadata($class); + if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { + if (\in_array($ret[0]->getTypeOfField($property), [Types::DECIMAL, Types::FLOAT])) { + return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); + } + } + + return null; + } + + /** + * @template T of object + * + * @param class-string $class + * + * @return array{0:ClassMetadata, 1:string}|null + */ + protected function getMetadata(string $class): ?array + { + // normalize class name + $class = self::getRealClass(ltrim($class, '\\')); + + if (\array_key_exists($class, $this->cache)) { + return $this->cache[$class]; + } + + $this->cache[$class] = null; + foreach ($this->registry->getManagers() as $name => $em) { + try { + return $this->cache[$class] = [$em->getClassMetadata($class), $name]; + } catch (MappingException) { + // not an entity or mapped super class + } catch (LegacyMappingException) { + // not an entity or mapped super class, using Doctrine ORM 2.2 + } + } + + return null; + } + + private static function getRealClass(string $class): string + { + if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { + return $class; + } + + return substr($class, $pos + Proxy::MARKER_LENGTH + 2); + } + + private static function getMappingValue(array|JoinColumnMapping $mapping, string $key): mixed + { + if ($mapping instanceof JoinColumnMapping) { + return $mapping->$key ?? null; + } + + return $mapping[$key] ?? null; + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/EventListener/MergeDoctrineCollectionListener.php b/vendor/symfony/doctrine-bridge/Form/EventListener/MergeDoctrineCollectionListener.php new file mode 100644 index 0000000..befd028 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/EventListener/MergeDoctrineCollectionListener.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\EventListener; + +use Doctrine\Common\Collections\Collection; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; + +/** + * Merge changes from the request to a Doctrine\Common\Collections\Collection instance. + * + * This works with ORM, MongoDB and CouchDB instances of the collection interface. + * + * @author Bernhard Schussek + * + * @see Collection + */ +class MergeDoctrineCollectionListener implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + // Higher priority than core MergeCollectionListener so that this one + // is called before + return [ + FormEvents::SUBMIT => [ + ['onSubmit', 5], + ], + ]; + } + + public function onSubmit(FormEvent $event): void + { + $collection = $event->getForm()->getData(); + $data = $event->getData(); + + // If all items were removed, call clear which has a higher + // performance on persistent collections + if ($collection instanceof Collection && 0 === \count($data)) { + $collection->clear(); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php b/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php new file mode 100644 index 0000000..5b62d13 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\Type; + +use Doctrine\Common\Collections\Collection; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; +use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; +use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; +use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader; +use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; +use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; +use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\Exception\RuntimeException; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Service\ResetInterface; + +abstract class DoctrineType extends AbstractType implements ResetInterface +{ + protected ManagerRegistry $registry; + + /** + * @var IdReader[] + */ + private array $idReaders = []; + + /** + * @var EntityLoaderInterface[] + */ + private array $entityLoaders = []; + + /** + * Creates the label for a choice. + * + * For backwards compatibility, objects are cast to strings by default. + * + * @internal This method is public to be usable as callback. It should not + * be used in user code. + */ + public static function createChoiceLabel(object $choice): string + { + return (string) $choice; + } + + /** + * Creates the field name for a choice. + * + * This method is used to generate field names if the underlying object has + * a single-column integer ID. In that case, the value of the field is + * the ID of the object. That ID is also used as field name. + * + * @param string $value The choice value. Corresponds to the object's ID here. + * + * @internal This method is public to be usable as callback. It should not + * be used in user code. + */ + public static function createChoiceName(object $choice, int|string $key, string $value): string + { + return str_replace('-', '_', $value); + } + + /** + * Gets important parts from QueryBuilder that will allow to cache its results. + * For instance in ORM two query builders with an equal SQL string and + * equal parameters are considered to be equal. + * + * @param object $queryBuilder A query builder, type declaration is not present here as there + * is no common base class for the different implementations + * + * @internal This method is public to be usable as callback. It should not + * be used in user code. + */ + public function getQueryBuilderPartsForCachingHash(object $queryBuilder): ?array + { + return null; + } + + public function __construct(ManagerRegistry $registry) + { + $this->registry = $registry; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ($options['multiple'] && interface_exists(Collection::class)) { + $builder + ->addEventSubscriber(new MergeDoctrineCollectionListener()) + ->addViewTransformer(new CollectionToArrayTransformer(), true) + ; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $choiceLoader = function (Options $options) { + // Unless the choices are given explicitly, load them on demand + if (null === $options['choices']) { + // If there is no QueryBuilder we can safely cache + $vary = [$options['em'], $options['class']]; + + // also if concrete Type can return important QueryBuilder parts to generate + // hash key we go for it as well, otherwise fallback on the instance + if ($options['query_builder']) { + $vary[] = $this->getQueryBuilderPartsForCachingHash($options['query_builder']) ?? $options['query_builder']; + } + + return ChoiceList::loader($this, new DoctrineChoiceLoader( + $options['em'], + $options['class'], + $options['id_reader'], + $this->getCachedEntityLoader( + $options['em'], + $options['query_builder'] ?? $options['em']->getRepository($options['class'])->createQueryBuilder('e'), + $options['class'], + $vary + ) + ), $vary); + } + + return null; + }; + + $choiceName = function (Options $options) { + // If the object has a single-column, numeric ID, use that ID as + // field name. We can only use numeric IDs as names, as we cannot + // guarantee that a non-numeric ID contains a valid form name + if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isIntId()) { + return ChoiceList::fieldName($this, [__CLASS__, 'createChoiceName']); + } + + // Otherwise, an incrementing integer is used as name automatically + return null; + }; + + // The choices are always indexed by ID (see "choices" normalizer + // and DoctrineChoiceLoader), unless the ID is composite. Then they + // are indexed by an incrementing integer. + // Use the ID/incrementing integer as choice value. + $choiceValue = function (Options $options) { + // If the entity has a single-column ID, use that ID as value + if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isSingleId()) { + return ChoiceList::value($this, $options['id_reader']->getIdValue(...), $options['id_reader']); + } + + // Otherwise, an incrementing integer is used as value automatically + return null; + }; + + $emNormalizer = function (Options $options, $em) { + if (null !== $em) { + if ($em instanceof ObjectManager) { + return $em; + } + + return $this->registry->getManager($em); + } + + $em = $this->registry->getManagerForClass($options['class']); + + if (null === $em) { + throw new RuntimeException(sprintf('Class "%s" seems not to be a managed Doctrine entity. Did you forget to map it?', $options['class'])); + } + + return $em; + }; + + // Invoke the query builder closure so that we can cache choice lists + // for equal query builders + $queryBuilderNormalizer = function (Options $options, $queryBuilder) { + if (\is_callable($queryBuilder)) { + $queryBuilder = $queryBuilder($options['em']->getRepository($options['class'])); + } + + return $queryBuilder; + }; + + // Set the "id_reader" option via the normalizer. This option is not + // supposed to be set by the user. + // The ID reader is a utility that is needed to read the object IDs + // when generating the field values. The callback generating the + // field values has no access to the object manager or the class + // of the field, so we store that information in the reader. + // The reader is cached so that two choice lists for the same class + // (and hence with the same reader) can successfully be cached. + $idReaderNormalizer = fn (Options $options) => $this->getCachedIdReader($options['em'], $options['class']); + + $resolver->setDefaults([ + 'em' => null, + 'query_builder' => null, + 'choices' => null, + 'choice_loader' => $choiceLoader, + 'choice_label' => ChoiceList::label($this, [__CLASS__, 'createChoiceLabel']), + 'choice_name' => $choiceName, + 'choice_value' => $choiceValue, + 'id_reader' => null, // internal + 'choice_translation_domain' => false, + ]); + + $resolver->setRequired(['class']); + + $resolver->setNormalizer('em', $emNormalizer); + $resolver->setNormalizer('query_builder', $queryBuilderNormalizer); + $resolver->setNormalizer('id_reader', $idReaderNormalizer); + + $resolver->setAllowedTypes('em', ['null', 'string', ObjectManager::class]); + } + + /** + * Return the default loader object. + */ + abstract public function getLoader(ObjectManager $manager, object $queryBuilder, string $class): EntityLoaderInterface; + + public function getParent(): string + { + return ChoiceType::class; + } + + public function reset(): void + { + $this->idReaders = []; + $this->entityLoaders = []; + } + + private function getCachedIdReader(ObjectManager $manager, string $class): ?IdReader + { + $hash = CachingFactoryDecorator::generateHash([$manager, $class]); + + if (isset($this->idReaders[$hash])) { + return $this->idReaders[$hash]; + } + + $idReader = new IdReader($manager, $manager->getClassMetadata($class)); + + // don't cache the instance for composite ids that cannot be optimized + return $this->idReaders[$hash] = $idReader->isSingleId() ? $idReader : null; + } + + private function getCachedEntityLoader(ObjectManager $manager, object $queryBuilder, string $class, array $vary): EntityLoaderInterface + { + $hash = CachingFactoryDecorator::generateHash($vary); + + return $this->entityLoaders[$hash] ??= $this->getLoader($manager, $queryBuilder, $class); + } +} diff --git a/vendor/symfony/doctrine-bridge/Form/Type/EntityType.php b/vendor/symfony/doctrine-bridge/Form/Type/EntityType.php new file mode 100644 index 0000000..9b8bda7 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Form/Type/EntityType.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\Type; + +use Doctrine\ORM\Query\Parameter; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ObjectManager; +use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class EntityType extends DoctrineType +{ + public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); + + // Invoke the query builder closure so that we can cache choice lists + // for equal query builders + $queryBuilderNormalizer = function (Options $options, $queryBuilder) { + if (\is_callable($queryBuilder)) { + $queryBuilder = $queryBuilder($options['em']->getRepository($options['class'])); + + if (null !== $queryBuilder && !$queryBuilder instanceof QueryBuilder) { + throw new UnexpectedTypeException($queryBuilder, QueryBuilder::class); + } + } + + return $queryBuilder; + }; + + $resolver->setNormalizer('query_builder', $queryBuilderNormalizer); + $resolver->setAllowedTypes('query_builder', ['null', 'callable', QueryBuilder::class]); + } + + /** + * Return the default loader object. + * + * @param QueryBuilder $queryBuilder + */ + public function getLoader(ObjectManager $manager, object $queryBuilder, string $class): ORMQueryBuilderLoader + { + if (!$queryBuilder instanceof QueryBuilder) { + throw new \TypeError(sprintf('Expected an instance of "%s", but got "%s".', QueryBuilder::class, get_debug_type($queryBuilder))); + } + + return new ORMQueryBuilderLoader($queryBuilder); + } + + public function getBlockPrefix(): string + { + return 'entity'; + } + + /** + * We consider two query builders with an equal SQL string and + * equal parameters to be equal. + * + * @param QueryBuilder $queryBuilder + * + * @internal This method is public to be usable as callback. It should not + * be used in user code. + */ + public function getQueryBuilderPartsForCachingHash(object $queryBuilder): ?array + { + if (!$queryBuilder instanceof QueryBuilder) { + throw new \TypeError(sprintf('Expected an instance of "%s", but got "%s".', QueryBuilder::class, get_debug_type($queryBuilder))); + } + + return [ + $queryBuilder->getQuery()->getSQL(), + array_map($this->parameterToArray(...), $queryBuilder->getParameters()->toArray()), + ]; + } + + /** + * Converts a query parameter to an array. + */ + private function parameterToArray(Parameter $parameter): array + { + return [$parameter->getName(), $parameter->getType(), $parameter->getValue()]; + } +} diff --git a/vendor/symfony/doctrine-bridge/IdGenerator/UlidGenerator.php b/vendor/symfony/doctrine-bridge/IdGenerator/UlidGenerator.php new file mode 100644 index 0000000..4c227ee --- /dev/null +++ b/vendor/symfony/doctrine-bridge/IdGenerator/UlidGenerator.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\IdGenerator; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Id\AbstractIdGenerator; +use Symfony\Component\Uid\Factory\UlidFactory; +use Symfony\Component\Uid\Ulid; + +final class UlidGenerator extends AbstractIdGenerator +{ + public function __construct( + private readonly ?UlidFactory $factory = null, + ) { + } + + /** + * doctrine/orm < 2.11 BC layer. + */ + public function generate(EntityManager $em, $entity): Ulid + { + return $this->generateId($em, $entity); + } + + public function generateId(EntityManagerInterface $em, $entity): Ulid + { + if ($this->factory) { + return $this->factory->create(); + } + + return new Ulid(); + } +} diff --git a/vendor/symfony/doctrine-bridge/IdGenerator/UuidGenerator.php b/vendor/symfony/doctrine-bridge/IdGenerator/UuidGenerator.php new file mode 100644 index 0000000..72bab54 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/IdGenerator/UuidGenerator.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\IdGenerator; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Id\AbstractIdGenerator; +use Symfony\Component\Uid\Factory\NameBasedUuidFactory; +use Symfony\Component\Uid\Factory\RandomBasedUuidFactory; +use Symfony\Component\Uid\Factory\TimeBasedUuidFactory; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\Uuid; + +final class UuidGenerator extends AbstractIdGenerator +{ + private readonly UuidFactory $protoFactory; + private UuidFactory|NameBasedUuidFactory|RandomBasedUuidFactory|TimeBasedUuidFactory $factory; + private ?string $entityGetter = null; + + public function __construct(?UuidFactory $factory = null) + { + $this->protoFactory = $this->factory = $factory ?? new UuidFactory(); + } + + /** + * doctrine/orm < 2.11 BC layer. + */ + public function generate(EntityManager $em, $entity): Uuid + { + return $this->generateId($em, $entity); + } + + public function generateId(EntityManagerInterface $em, $entity): Uuid + { + if (null !== $this->entityGetter) { + if (\is_callable([$entity, $this->entityGetter])) { + return $this->factory->create($entity->{$this->entityGetter}()); + } + + return $this->factory->create($entity->{$this->entityGetter}); + } + + return $this->factory->create(); + } + + public function nameBased(string $entityGetter, Uuid|string|null $namespace = null): static + { + $clone = clone $this; + $clone->factory = $clone->protoFactory->nameBased($namespace); + $clone->entityGetter = $entityGetter; + + return $clone; + } + + public function randomBased(): static + { + $clone = clone $this; + $clone->factory = $clone->protoFactory->randomBased(); + $clone->entityGetter = null; + + return $clone; + } + + public function timeBased(Uuid|string|null $node = null): static + { + $clone = clone $this; + $clone->factory = $clone->protoFactory->timeBased($node); + $clone->entityGetter = null; + + return $clone; + } +} diff --git a/vendor/symfony/doctrine-bridge/LICENSE b/vendor/symfony/doctrine-bridge/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/doctrine-bridge/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/doctrine-bridge/ManagerRegistry.php b/vendor/symfony/doctrine-bridge/ManagerRegistry.php new file mode 100644 index 0000000..302b1a2 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/ManagerRegistry.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine; + +use Doctrine\Persistence\AbstractManagerRegistry; +use ProxyManager\Proxy\GhostObjectInterface; +use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\VarExporter\LazyObjectInterface; + +/** + * References Doctrine connections and entity/document managers. + * + * @author Lukas Kahwe Smith + */ +abstract class ManagerRegistry extends AbstractManagerRegistry +{ + protected Container $container; + + protected function getService($name): object + { + return $this->container->get($name); + } + + protected function resetService($name): void + { + if (!$this->container->initialized($name)) { + return; + } + $manager = $this->container->get($name); + + if ($manager instanceof LazyObjectInterface) { + if (!$manager->resetLazyObject()) { + throw new \LogicException(sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name)); + } + + return; + } + if (!$manager instanceof LazyLoadingInterface) { + throw new \LogicException(sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name)); + } + if ($manager instanceof GhostObjectInterface) { + throw new \LogicException('Resetting a lazy-ghost-object manager service is not supported.'); + } + $manager->setProxyInitializer(\Closure::bind( + function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) { + if (isset($this->aliases[$name])) { + $name = $this->aliases[$name]; + } + if (isset($this->fileMap[$name])) { + $wrappedInstance = $this->load($this->fileMap[$name], false); + } elseif ((new \ReflectionMethod($this, $this->methodMap[$name]))->isStatic()) { + $wrappedInstance = $this->{$this->methodMap[$name]}($this, false); + } else { + $wrappedInstance = $this->{$this->methodMap[$name]}(false); + } + + $manager->setProxyInitializer(null); + + return true; + }, + $this->container, + Container::class + )); + } +} diff --git a/vendor/symfony/doctrine-bridge/Messenger/AbstractDoctrineMiddleware.php b/vendor/symfony/doctrine-bridge/Messenger/AbstractDoctrineMiddleware.php new file mode 100644 index 0000000..649a197 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Messenger/AbstractDoctrineMiddleware.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Messenger; + +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; +use Symfony\Component\Messenger\Middleware\MiddlewareInterface; +use Symfony\Component\Messenger\Middleware\StackInterface; + +/** + * @author Konstantin Myakshin + * + * @internal + */ +abstract class AbstractDoctrineMiddleware implements MiddlewareInterface +{ + protected ManagerRegistry $managerRegistry; + protected ?string $entityManagerName; + + public function __construct(ManagerRegistry $managerRegistry, ?string $entityManagerName = null) + { + $this->managerRegistry = $managerRegistry; + $this->entityManagerName = $entityManagerName; + } + + final public function handle(Envelope $envelope, StackInterface $stack): Envelope + { + try { + $entityManager = $this->managerRegistry->getManager($this->entityManagerName); + } catch (\InvalidArgumentException $e) { + throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e); + } + + return $this->handleForManager($entityManager, $envelope, $stack); + } + + abstract protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope; +} diff --git a/vendor/symfony/doctrine-bridge/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php b/vendor/symfony/doctrine-bridge/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php new file mode 100644 index 0000000..4ab09d4 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Messenger; + +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; +use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; + +/** + * Clears entity managers between messages being handled to avoid outdated data. + * + * @author Ryan Weaver + */ +class DoctrineClearEntityManagerWorkerSubscriber implements EventSubscriberInterface +{ + public function __construct( + private readonly ManagerRegistry $managerRegistry, + ) { + } + + public function onWorkerMessageHandled(): void + { + $this->clearEntityManagers(); + } + + public function onWorkerMessageFailed(): void + { + $this->clearEntityManagers(); + } + + public static function getSubscribedEvents(): array + { + return [ + WorkerMessageHandledEvent::class => 'onWorkerMessageHandled', + WorkerMessageFailedEvent::class => 'onWorkerMessageFailed', + ]; + } + + private function clearEntityManagers(): void + { + foreach ($this->managerRegistry->getManagers() as $manager) { + $manager->clear(); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Messenger/DoctrineCloseConnectionMiddleware.php b/vendor/symfony/doctrine-bridge/Messenger/DoctrineCloseConnectionMiddleware.php new file mode 100644 index 0000000..b0a96e0 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Messenger/DoctrineCloseConnectionMiddleware.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Messenger; + +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Middleware\StackInterface; +use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp; + +/** + * Closes connection and therefore saves number of connections. + * + * @author Fuong + */ +class DoctrineCloseConnectionMiddleware extends AbstractDoctrineMiddleware +{ + protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope + { + try { + $connection = $entityManager->getConnection(); + + return $stack->next()->handle($envelope, $stack); + } finally { + if (null !== $envelope->last(ConsumedByWorkerStamp::class)) { + $connection->close(); + } + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Messenger/DoctrineOpenTransactionLoggerMiddleware.php b/vendor/symfony/doctrine-bridge/Messenger/DoctrineOpenTransactionLoggerMiddleware.php new file mode 100644 index 0000000..7638015 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Messenger/DoctrineOpenTransactionLoggerMiddleware.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Messenger; + +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Middleware\StackInterface; + +/** + * Middleware to log when transaction has been left open. + * + * @author Grégoire Pineau + */ +class DoctrineOpenTransactionLoggerMiddleware extends AbstractDoctrineMiddleware +{ + private bool $isHandling = false; + + public function __construct( + ManagerRegistry $managerRegistry, + ?string $entityManagerName = null, + private readonly ?LoggerInterface $logger = null, + ) { + parent::__construct($managerRegistry, $entityManagerName); + } + + protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope + { + if ($this->isHandling) { + return $stack->next()->handle($envelope, $stack); + } + + $this->isHandling = true; + $initialTransactionLevel = $entityManager->getConnection()->getTransactionNestingLevel(); + + try { + return $stack->next()->handle($envelope, $stack); + } finally { + if ($entityManager->getConnection()->getTransactionNestingLevel() > $initialTransactionLevel) { + $this->logger?->error('A handler opened a transaction but did not close it.', [ + 'message' => $envelope->getMessage(), + ]); + } + $this->isHandling = false; + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Messenger/DoctrinePingConnectionMiddleware.php b/vendor/symfony/doctrine-bridge/Messenger/DoctrinePingConnectionMiddleware.php new file mode 100644 index 0000000..08e1269 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Messenger/DoctrinePingConnectionMiddleware.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Messenger; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception as DBALException; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Middleware\StackInterface; +use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp; + +/** + * Checks whether the connection is still open or reconnects otherwise. + * + * @author Fuong + */ +class DoctrinePingConnectionMiddleware extends AbstractDoctrineMiddleware +{ + protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope + { + if (null !== $envelope->last(ConsumedByWorkerStamp::class)) { + $this->pingConnection($entityManager); + } + + return $stack->next()->handle($envelope, $stack); + } + + private function pingConnection(EntityManagerInterface $entityManager): void + { + $connection = $entityManager->getConnection(); + + try { + $this->executeDummySql($connection); + } catch (DBALException) { + $connection->close(); + // Attempt to reestablish the lazy connection by sending another query. + $this->executeDummySql($connection); + } + + if (!$entityManager->isOpen()) { + $this->managerRegistry->resetManager($this->entityManagerName); + } + } + + /** + * @throws DBALException + */ + private function executeDummySql(Connection $connection): void + { + $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); + } +} diff --git a/vendor/symfony/doctrine-bridge/Messenger/DoctrineTransactionMiddleware.php b/vendor/symfony/doctrine-bridge/Messenger/DoctrineTransactionMiddleware.php new file mode 100644 index 0000000..e483155 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Messenger/DoctrineTransactionMiddleware.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Messenger; + +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\HandlerFailedException; +use Symfony\Component\Messenger\Middleware\StackInterface; +use Symfony\Component\Messenger\Stamp\HandledStamp; + +/** + * Wraps all handlers in a single doctrine transaction. + * + * @author Tobias Nyholm + */ +class DoctrineTransactionMiddleware extends AbstractDoctrineMiddleware +{ + protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope + { + $entityManager->getConnection()->beginTransaction(); + try { + $envelope = $stack->next()->handle($envelope, $stack); + $entityManager->flush(); + $entityManager->getConnection()->commit(); + + return $envelope; + } catch (\Throwable $exception) { + $entityManager->getConnection()->rollBack(); + + if ($exception instanceof HandlerFailedException) { + // Remove all HandledStamp from the envelope so the retry will execute all handlers again. + // When a handler fails, the queries of allegedly successful previous handlers just got rolled back. + throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getWrappedExceptions()); + } + + throw $exception; + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/Connection.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/Connection.php new file mode 100644 index 0000000..e20510c --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/Connection.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; +use Doctrine\DBAL\Driver\Result; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * @author Alexander M. Turek + * + * @internal + */ +final class Connection extends AbstractConnectionMiddleware +{ + public function __construct( + ConnectionInterface $connection, + private readonly DebugDataHolder $debugDataHolder, + private readonly ?Stopwatch $stopwatch, + private readonly string $connectionName, + ) { + parent::__construct($connection); + } + + public function prepare(string $sql): Statement + { + return new Statement( + parent::prepare($sql), + $this->debugDataHolder, + $this->connectionName, + $sql, + $this->stopwatch, + ); + } + + public function query(string $sql): Result + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::query($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function exec(string $sql): int + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + $affectedRows = parent::exec($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + + return $affectedRows; + } + + public function beginTransaction(): void + { + $query = new Query('"START TRANSACTION"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + parent::beginTransaction(); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function commit(): void + { + $query = new Query('"COMMIT"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + parent::commit(); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function rollBack(): void + { + $query = new Query('"ROLLBACK"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + parent::rollBack(); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Connection.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Connection.php new file mode 100644 index 0000000..8d01c02 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Connection.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug\DBAL3; + +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; +use Doctrine\DBAL\Driver\Result; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Connection extends AbstractConnectionMiddleware +{ + private int $nestingLevel = 0; + + public function __construct( + ConnectionInterface $connection, + private readonly DebugDataHolder $debugDataHolder, + private readonly ?Stopwatch $stopwatch, + private readonly string $connectionName, + ) { + parent::__construct($connection); + } + + public function prepare(string $sql): Statement + { + return new Statement( + parent::prepare($sql), + $this->debugDataHolder, + $this->connectionName, + $sql, + $this->stopwatch, + ); + } + + public function query(string $sql): Result + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::query($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function exec(string $sql): int + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::exec($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function beginTransaction(): bool + { + $query = null; + if (1 === ++$this->nestingLevel) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::beginTransaction(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function commit(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::commit(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function rollBack(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::rollBack(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Statement.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Statement.php new file mode 100644 index 0000000..cd059f8 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Statement.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug\DBAL3; + +use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Statement extends AbstractStatementMiddleware +{ + private readonly Query $query; + + public function __construct( + StatementInterface $statement, + private readonly DebugDataHolder $debugDataHolder, + private readonly string $connectionName, + string $sql, + private readonly ?Stopwatch $stopwatch = null, + ) { + $this->query = new Query($sql); + + parent::__construct($statement); + } + + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + $this->query->setParam($param, $variable, $type); + + return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); + } + + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + $this->query->setValue($param, $value, $type); + + return parent::bindValue($param, $value, $type); + } + + public function execute($params = null): ResultInterface + { + if (null !== $params) { + $this->query->setValues($params); + } + + // clone to prevent variables by reference to change + $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::execute($params); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/DebugDataHolder.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/DebugDataHolder.php new file mode 100644 index 0000000..4e1052a --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/DebugDataHolder.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +/** + * @author Laurent VOULLEMIER + */ +class DebugDataHolder +{ + private array $data = []; + + public function addQuery(string $connectionName, Query $query): void + { + $this->data[$connectionName][] = [ + 'sql' => $query->getSql(), + 'params' => $query->getParams(), + 'types' => $query->getTypes(), + 'executionMS' => $query->getDuration(...), // stop() may not be called at this point + ]; + } + + public function getData(): array + { + foreach ($this->data as $connectionName => $dataForConn) { + foreach ($dataForConn as $idx => $data) { + if (\is_callable($data['executionMS'])) { + $this->data[$connectionName][$idx]['executionMS'] = $data['executionMS'](); + } + } + } + + return $this->data; + } + + public function reset(): void + { + $this->data = []; + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/Driver.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/Driver.php new file mode 100644 index 0000000..ea1ecfb --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/Driver.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Driver extends AbstractDriverMiddleware +{ + public function __construct( + DriverInterface $driver, + private readonly DebugDataHolder $debugDataHolder, + private readonly ?Stopwatch $stopwatch, + private readonly string $connectionName, + ) { + parent::__construct($driver); + } + + public function connect(array $params): ConnectionInterface + { + $connection = parent::connect($params); + + if ('void' !== (string) (new \ReflectionMethod(DriverInterface\Connection::class, 'commit'))->getReturnType()) { + return new DBAL3\Connection( + $connection, + $this->debugDataHolder, + $this->stopwatch, + $this->connectionName + ); + } + + return new Connection( + $connection, + $this->debugDataHolder, + $this->stopwatch, + $this->connectionName + ); + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/Middleware.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/Middleware.php new file mode 100644 index 0000000..5f8a246 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/Middleware.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Middleware to collect debug data. + * + * @author Laurent VOULLEMIER + */ +final class Middleware implements MiddlewareInterface +{ + public function __construct( + private readonly DebugDataHolder $debugDataHolder, + private readonly ?Stopwatch $stopwatch, + private readonly string $connectionName = 'default', + ) { + } + + public function wrap(DriverInterface $driver): DriverInterface + { + return new Driver($driver, $this->debugDataHolder, $this->stopwatch, $this->connectionName); + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/Query.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/Query.php new file mode 100644 index 0000000..7bbc8db --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/Query.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\ParameterType; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +class Query +{ + private array $params = []; + + /** @var array */ + private array $types = []; + + private ?float $start = null; + private ?float $duration = null; + + public function __construct( + private readonly string $sql, + ) { + } + + public function start(): void + { + $this->start = microtime(true); + } + + public function stop(): void + { + if (null !== $this->start) { + $this->duration = microtime(true) - $this->start; + } + } + + public function setParam(string|int $param, mixed &$variable, ParameterType|int $type): void + { + // Numeric indexes start at 0 in profiler + $idx = \is_int($param) ? $param - 1 : $param; + + $this->params[$idx] = &$variable; + $this->types[$idx] = $type; + } + + public function setValue(string|int $param, mixed $value, ParameterType|int $type): void + { + // Numeric indexes start at 0 in profiler + $idx = \is_int($param) ? $param - 1 : $param; + + $this->params[$idx] = $value; + $this->types[$idx] = $type; + } + + /** + * @param array $values + */ + public function setValues(array $values): void + { + foreach ($values as $param => $value) { + $this->setValue($param, $value, ParameterType::STRING); + } + } + + public function getSql(): string + { + return $this->sql; + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + + /** + * @return array + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * Query duration in seconds. + */ + public function getDuration(): ?float + { + return $this->duration; + } + + public function __clone() + { + $copy = []; + foreach ($this->params as $param => $valueOrVariable) { + $copy[$param] = $valueOrVariable; + } + $this->params = $copy; + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/Debug/Statement.php b/vendor/symfony/doctrine-bridge/Middleware/Debug/Statement.php new file mode 100644 index 0000000..85e6c35 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/Debug/Statement.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * @author Alexander M. Turek + * + * @internal + */ +final class Statement extends AbstractStatementMiddleware +{ + private Query $query; + + public function __construct( + StatementInterface $statement, + private readonly DebugDataHolder $debugDataHolder, + private readonly string $connectionName, + string $sql, + private readonly ?Stopwatch $stopwatch = null, + ) { + parent::__construct($statement); + + $this->query = new Query($sql); + } + + public function bindValue(int|string $param, mixed $value, ParameterType $type): void + { + $this->query->setValue($param, $value, $type); + + parent::bindValue($param, $value, $type); + } + + public function execute(): ResultInterface + { + // clone to prevent variables by reference to change + $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::execute(); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Driver.php b/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Driver.php new file mode 100644 index 0000000..566002c --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Driver.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\IdleConnection; + +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; + +final class Driver extends AbstractDriverMiddleware +{ + public function __construct( + DriverInterface $driver, + private \ArrayObject $connectionExpiries, + private readonly int $ttl, + private readonly string $connectionName, + ) { + parent::__construct($driver); + } + + public function connect(array $params): ConnectionInterface + { + $timestamp = time(); + $connection = parent::connect($params); + $this->connectionExpiries[$this->connectionName] = $timestamp + $this->ttl; + + return $connection; + } +} diff --git a/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Listener.php b/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Listener.php new file mode 100644 index 0000000..11f7053 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Listener.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\IdleConnection; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +final class Listener implements EventSubscriberInterface +{ + /** + * @param \ArrayObject $connectionExpiries + */ + public function __construct( + private readonly \ArrayObject $connectionExpiries, + private ContainerInterface $container, + ) { + } + + public function onKernelRequest(RequestEvent $event): void + { + $timestamp = time(); + + foreach ($this->connectionExpiries as $name => $expiry) { + if ($timestamp >= $expiry) { + // unset before so that we won't retry in case of any failure + $this->connectionExpiries->offsetUnset($name); + + try { + $connection = $this->container->get("doctrine.dbal.{$name}_connection"); + $connection->close(); + } catch (\Exception) { + // ignore exceptions to remain fail-safe + } + } + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => ['onKernelRequest', 192], // before session listeners since they could use the DB + ]; + } +} diff --git a/vendor/symfony/doctrine-bridge/PropertyInfo/DoctrineExtractor.php b/vendor/symfony/doctrine-bridge/PropertyInfo/DoctrineExtractor.php new file mode 100644 index 0000000..cf32c6c --- /dev/null +++ b/vendor/symfony/doctrine-bridge/PropertyInfo/DoctrineExtractor.php @@ -0,0 +1,434 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\PropertyInfo; + +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\BigIntType; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\AssociationMapping; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\EmbeddedClassMapping; +use Doctrine\ORM\Mapping\FieldMapping; +use Doctrine\ORM\Mapping\JoinColumnMapping; +use Doctrine\ORM\Mapping\MappingException as OrmMappingException; +use Doctrine\Persistence\Mapping\MappingException; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * Extracts data using Doctrine ORM and ODM metadata. + * + * @author Kévin Dunglas + */ +class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface +{ + public function __construct( + private readonly EntityManagerInterface $entityManager, + ) { + } + + public function getProperties(string $class, array $context = []): ?array + { + if (null === $metadata = $this->getMetadata($class)) { + return null; + } + + $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); + + if ($metadata instanceof ClassMetadata && $metadata->embeddedClasses) { + $properties = array_filter($properties, fn ($property) => !str_contains($property, '.')); + + $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); + } + + return $properties; + } + + public function getType(string $class, string $property, array $context = []): ?Type + { + if (null === $metadata = $this->getMetadata($class)) { + return null; + } + + if ($metadata->hasAssociation($property)) { + $class = $metadata->getAssociationTargetClass($property); + + if ($metadata->isSingleValuedAssociation($property)) { + if ($metadata instanceof ClassMetadata) { + $associationMapping = $metadata->getAssociationMapping($property); + $nullable = $this->isAssociationNullable($associationMapping); + } else { + $nullable = false; + } + + return $nullable ? Type::nullable(Type::object($class)) : Type::object($class); + } + + $collectionKeyType = TypeIdentifier::INT; + + if ($metadata instanceof ClassMetadata) { + $associationMapping = $metadata->getAssociationMapping($property); + + if (self::getMappingValue($associationMapping, 'indexBy')) { + $subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity')); + + // Check if indexBy value is a property + $fieldName = self::getMappingValue($associationMapping, 'indexBy'); + if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { + $fieldName = $subMetadata->getFieldForColumn(self::getMappingValue($associationMapping, 'indexBy')); + // Not a property, maybe a column name? + if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { + // Maybe the column name is the association join column? + $associationMapping = $subMetadata->getAssociationMapping($fieldName); + + $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName); + $subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity')); + + // Not a property, maybe a column name? + if (null === ($typeOfField = $subMetadata->getTypeOfField($indexProperty))) { + $fieldName = $subMetadata->getFieldForColumn($indexProperty); + $typeOfField = $subMetadata->getTypeOfField($fieldName); + } + } + } + + if (!$collectionKeyType = $this->getTypeIdentifier($typeOfField)) { + return null; + } + } + } + + return Type::collection(Type::object(Collection::class), Type::object($class), Type::builtin($collectionKeyType)); + } + + if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) { + return Type::object(self::getMappingValue($metadata->embeddedClasses[$property], 'class')); + } + + if (!$metadata->hasField($property)) { + return null; + } + + $typeOfField = $metadata->getTypeOfField($property); + + if (!$typeIdentifier = $this->getTypeIdentifier($typeOfField)) { + return null; + } + + $nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property); + + // DBAL 4 has a special fallback strategy for BINGINT (int -> string) + if (Types::BIGINT === $typeOfField && !method_exists(BigIntType::class, 'getName')) { + return Type::collection(Type::int(), Type::string()); + } + + $enumType = null; + + if (null !== $enumClass = self::getMappingValue($metadata->getFieldMapping($property), 'enumType') ?? null) { + $enumType = $nullable ? Type::nullable(Type::enum($enumClass)) : Type::enum($enumClass); + } + + $builtinType = $nullable ? Type::nullable(Type::builtin($typeIdentifier)) : Type::builtin($typeIdentifier); + + return match ($typeIdentifier) { + TypeIdentifier::OBJECT => match ($typeOfField) { + Types::DATE_MUTABLE, Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, 'vardatetime', Types::TIME_MUTABLE => $nullable ? Type::nullable(Type::object(\DateTime::class)) : Type::object(\DateTime::class), + Types::DATE_IMMUTABLE, Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_IMMUTABLE, Types::TIME_IMMUTABLE => $nullable ? Type::nullable(Type::object(\DateTimeImmutable::class)) : Type::object(\DateTimeImmutable::class), + Types::DATEINTERVAL => $nullable ? Type::nullable(Type::object(\DateInterval::class)) : Type::object(\DateInterval::class), + default => $builtinType, + }, + TypeIdentifier::ARRAY => match ($typeOfField) { + 'array', 'json_array' => $enumType ? null : ($nullable ? Type::nullable(Type::array()) : Type::array()), + Types::SIMPLE_ARRAY => $nullable ? Type::nullable(Type::list($enumType ?? Type::string())) : Type::list($enumType ?? Type::string()), + default => $builtinType, + }, + TypeIdentifier::INT, TypeIdentifier::STRING => $enumType ? $enumType : $builtinType, + default => $builtinType, + }; + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + if (null === $metadata = $this->getMetadata($class)) { + return null; + } + + if ($metadata->hasAssociation($property)) { + $class = $metadata->getAssociationTargetClass($property); + + if ($metadata->isSingleValuedAssociation($property)) { + if ($metadata instanceof ClassMetadata) { + $associationMapping = $metadata->getAssociationMapping($property); + + $nullable = $this->isAssociationNullable($associationMapping); + } else { + $nullable = false; + } + + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $class)]; + } + + $collectionKeyType = LegacyType::BUILTIN_TYPE_INT; + + if ($metadata instanceof ClassMetadata) { + $associationMapping = $metadata->getAssociationMapping($property); + + if (self::getMappingValue($associationMapping, 'indexBy')) { + $subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity')); + + // Check if indexBy value is a property + $fieldName = self::getMappingValue($associationMapping, 'indexBy'); + if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { + $fieldName = $subMetadata->getFieldForColumn(self::getMappingValue($associationMapping, 'indexBy')); + // Not a property, maybe a column name? + if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { + // Maybe the column name is the association join column? + $associationMapping = $subMetadata->getAssociationMapping($fieldName); + + $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName); + $subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity')); + + // Not a property, maybe a column name? + if (null === ($typeOfField = $subMetadata->getTypeOfField($indexProperty))) { + $fieldName = $subMetadata->getFieldForColumn($indexProperty); + $typeOfField = $subMetadata->getTypeOfField($fieldName); + } + } + } + + if (!$collectionKeyType = $this->getTypeIdentifierLegacy($typeOfField)) { + return null; + } + } + } + + return [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, + false, + Collection::class, + true, + new LegacyType($collectionKeyType), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, $class) + )]; + } + + if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) { + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, self::getMappingValue($metadata->embeddedClasses[$property], 'class'))]; + } + + if ($metadata->hasField($property)) { + $typeOfField = $metadata->getTypeOfField($property); + + if (!$builtinType = $this->getTypeIdentifierLegacy($typeOfField)) { + return null; + } + + $nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property); + + // DBAL 4 has a special fallback strategy for BINGINT (int -> string) + if (Types::BIGINT === $typeOfField && !method_exists(BigIntType::class, 'getName')) { + return [ + new LegacyType(LegacyType::BUILTIN_TYPE_INT, $nullable), + new LegacyType(LegacyType::BUILTIN_TYPE_STRING, $nullable), + ]; + } + + $enumType = null; + if (null !== $enumClass = self::getMappingValue($metadata->getFieldMapping($property), 'enumType') ?? null) { + $enumType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $enumClass); + } + + switch ($builtinType) { + case LegacyType::BUILTIN_TYPE_OBJECT: + switch ($typeOfField) { + case Types::DATE_MUTABLE: + case Types::DATETIME_MUTABLE: + case Types::DATETIMETZ_MUTABLE: + case 'vardatetime': + case Types::TIME_MUTABLE: + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')]; + + case Types::DATE_IMMUTABLE: + case Types::DATETIME_IMMUTABLE: + case Types::DATETIMETZ_IMMUTABLE: + case Types::TIME_IMMUTABLE: + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable')]; + + case Types::DATEINTERVAL: + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval')]; + } + + break; + case LegacyType::BUILTIN_TYPE_ARRAY: + switch ($typeOfField) { + case 'array': // DBAL < 4 + case 'json_array': // DBAL < 3 + // return null if $enumType is set, because we can't determine if collectionKeyType is string or int + if ($enumType) { + return null; + } + + return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true)]; + + case Types::SIMPLE_ARRAY: + return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $enumType ?? new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]; + } + break; + case LegacyType::BUILTIN_TYPE_INT: + case LegacyType::BUILTIN_TYPE_STRING: + if ($enumType) { + return [$enumType]; + } + break; + } + + return [new LegacyType($builtinType, $nullable)]; + } + + return null; + } + + public function isReadable(string $class, string $property, array $context = []): ?bool + { + return null; + } + + public function isWritable(string $class, string $property, array $context = []): ?bool + { + if ( + null === ($metadata = $this->getMetadata($class)) + || ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType + || !\in_array($property, $metadata->getIdentifierFieldNames(), true) + ) { + return null; + } + + return false; + } + + private function getMetadata(string $class): ?ClassMetadata + { + try { + return $this->entityManager->getClassMetadata($class); + } catch (MappingException|OrmMappingException) { + return null; + } + } + + /** + * Determines whether an association is nullable. + * + * @param array|AssociationMapping $associationMapping + * + * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 + */ + private function isAssociationNullable(array|AssociationMapping $associationMapping): bool + { + if (self::getMappingValue($associationMapping, 'id')) { + return false; + } + + if (!self::getMappingValue($associationMapping, 'joinColumns')) { + return true; + } + + $joinColumns = self::getMappingValue($associationMapping, 'joinColumns'); + foreach ($joinColumns as $joinColumn) { + if (false === self::getMappingValue($joinColumn, 'nullable')) { + return false; + } + } + + return true; + } + + /** + * Gets the corresponding built-in PHP type. + */ + private function getTypeIdentifier(string $doctrineType): ?TypeIdentifier + { + return match ($doctrineType) { + Types::SMALLINT, + Types::INTEGER => TypeIdentifier::INT, + Types::FLOAT => TypeIdentifier::FLOAT, + Types::BIGINT, + Types::STRING, + Types::TEXT, + Types::GUID, + Types::DECIMAL => TypeIdentifier::STRING, + Types::BOOLEAN => TypeIdentifier::BOOL, + Types::BLOB, + Types::BINARY => TypeIdentifier::RESOURCE, + 'object', // DBAL < 4 + Types::DATE_MUTABLE, + Types::DATETIME_MUTABLE, + Types::DATETIMETZ_MUTABLE, + 'vardatetime', + Types::TIME_MUTABLE, + Types::DATE_IMMUTABLE, + Types::DATETIME_IMMUTABLE, + Types::DATETIMETZ_IMMUTABLE, + Types::TIME_IMMUTABLE, + Types::DATEINTERVAL => TypeIdentifier::OBJECT, + 'array', // DBAL < 4 + 'json_array', // DBAL < 3 + Types::SIMPLE_ARRAY => TypeIdentifier::ARRAY, + default => null, + }; + } + + private function getTypeIdentifierLegacy(string $doctrineType): ?string + { + return match ($doctrineType) { + Types::SMALLINT, + Types::INTEGER => LegacyType::BUILTIN_TYPE_INT, + Types::FLOAT => LegacyType::BUILTIN_TYPE_FLOAT, + Types::BIGINT, + Types::STRING, + Types::TEXT, + Types::GUID, + Types::DECIMAL => LegacyType::BUILTIN_TYPE_STRING, + Types::BOOLEAN => LegacyType::BUILTIN_TYPE_BOOL, + Types::BLOB, + Types::BINARY => LegacyType::BUILTIN_TYPE_RESOURCE, + 'object', // DBAL < 4 + Types::DATE_MUTABLE, + Types::DATETIME_MUTABLE, + Types::DATETIMETZ_MUTABLE, + 'vardatetime', + Types::TIME_MUTABLE, + Types::DATE_IMMUTABLE, + Types::DATETIME_IMMUTABLE, + Types::DATETIMETZ_IMMUTABLE, + Types::TIME_IMMUTABLE, + Types::DATEINTERVAL => LegacyType::BUILTIN_TYPE_OBJECT, + 'array', // DBAL < 4 + 'json_array', // DBAL < 3 + Types::SIMPLE_ARRAY => LegacyType::BUILTIN_TYPE_ARRAY, + default => null, + }; + } + + private static function getMappingValue(array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping $mapping, string $key): mixed + { + if ($mapping instanceof AssociationMapping || $mapping instanceof EmbeddedClassMapping || $mapping instanceof FieldMapping || $mapping instanceof JoinColumnMapping) { + return $mapping->$key ?? null; + } + + return $mapping[$key] ?? null; + } +} diff --git a/vendor/symfony/doctrine-bridge/README.md b/vendor/symfony/doctrine-bridge/README.md new file mode 100644 index 0000000..fb7b1cd --- /dev/null +++ b/vendor/symfony/doctrine-bridge/README.md @@ -0,0 +1,13 @@ +Doctrine Bridge +=============== + +The Doctrine bridge provides integration for +[Doctrine](http://www.doctrine-project.org/) with various Symfony components. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/doctrine-bridge/SchemaListener/AbstractSchemaListener.php b/vendor/symfony/doctrine-bridge/SchemaListener/AbstractSchemaListener.php new file mode 100644 index 0000000..7d286d7 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/SchemaListener/AbstractSchemaListener.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; + +abstract class AbstractSchemaListener +{ + abstract public function postGenerateSchema(GenerateSchemaEventArgs $event): void; + + protected function getIsSameDatabaseChecker(Connection $connection): \Closure + { + return static function (\Closure $exec) use ($connection): bool { + $schemaManager = $connection->createSchemaManager(); + + $checkTable = 'schema_subscriber_check_'.bin2hex(random_bytes(7)); + $table = new Table($checkTable); + $table->addColumn('id', Types::INTEGER) + ->setAutoincrement(true) + ->setNotnull(true); + $table->setPrimaryKey(['id']); + + $schemaManager->createTable($table); + + try { + $exec(sprintf('DROP TABLE %s', $checkTable)); + } catch (\Exception) { + // ignore + } + + try { + $schemaManager->dropTable($checkTable); + + return false; + } catch (TableNotFoundException) { + return true; + } + }; + } +} diff --git a/vendor/symfony/doctrine-bridge/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php b/vendor/symfony/doctrine-bridge/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php new file mode 100644 index 0000000..ee2e427 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; + +/** + * Automatically adds the cache table needed for the DoctrineDbalAdapter of + * the Cache component. + */ +class DoctrineDbalCacheAdapterSchemaListener extends AbstractSchemaListener +{ + /** + * @param iterable $dbalAdapters + */ + public function __construct( + private readonly iterable $dbalAdapters, + ) { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->dbalAdapters as $dbalAdapter) { + $dbalAdapter->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/SchemaListener/LockStoreSchemaListener.php b/vendor/symfony/doctrine-bridge/SchemaListener/LockStoreSchemaListener.php new file mode 100644 index 0000000..c4c3b0b --- /dev/null +++ b/vendor/symfony/doctrine-bridge/SchemaListener/LockStoreSchemaListener.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\Lock\PersistingStoreInterface; +use Symfony\Component\Lock\Store\DoctrineDbalStore; + +final class LockStoreSchemaListener extends AbstractSchemaListener +{ + /** + * @param iterable $stores + */ + public function __construct( + private readonly iterable $stores, + ) { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->stores as $store) { + if (!$store instanceof DoctrineDbalStore) { + continue; + } + + $store->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); + } + } +} diff --git a/vendor/symfony/doctrine-bridge/SchemaListener/MessengerTransportDoctrineSchemaListener.php b/vendor/symfony/doctrine-bridge/SchemaListener/MessengerTransportDoctrineSchemaListener.php new file mode 100644 index 0000000..ce3f017 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/SchemaListener/MessengerTransportDoctrineSchemaListener.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport; +use Symfony\Component\Messenger\Transport\TransportInterface; + +/** + * Automatically adds any required database tables to the Doctrine Schema. + */ +class MessengerTransportDoctrineSchemaListener extends AbstractSchemaListener +{ + private const PROCESSING_TABLE_FLAG = self::class.':processing'; + + /** + * @param iterable $transports + */ + public function __construct( + private readonly iterable $transports, + ) { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->transports as $transport) { + if (!$transport instanceof DoctrineTransport) { + continue; + } + + $transport->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); + } + } + + public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void + { + $table = $event->getTable(); + + // if this method triggers a nested create table below, allow Doctrine to work like normal + if ($table->hasOption(self::PROCESSING_TABLE_FLAG)) { + return; + } + + foreach ($this->transports as $transport) { + if (!$transport instanceof DoctrineTransport) { + continue; + } + + if (!$extraSql = $transport->getExtraSetupSqlForTable($table)) { + continue; + } + + // avoid this same listener from creating a loop on this table + $table->addOption(self::PROCESSING_TABLE_FLAG, true); + $createTableSql = $event->getPlatform()->getCreateTableSQL($table); + + /* + * Add all the SQL needed to create the table and tell Doctrine + * to "preventDefault" so that only our SQL is used. This is + * the only way to inject some extra SQL. + */ + $event->addSql($createTableSql); + foreach ($extraSql as $sql) { + $event->addSql($sql); + } + $event->preventDefault(); + + return; + } + } +} diff --git a/vendor/symfony/doctrine-bridge/SchemaListener/PdoSessionHandlerSchemaListener.php b/vendor/symfony/doctrine-bridge/SchemaListener/PdoSessionHandlerSchemaListener.php new file mode 100644 index 0000000..5035743 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/SchemaListener/PdoSessionHandlerSchemaListener.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +final class PdoSessionHandlerSchemaListener extends AbstractSchemaListener +{ + private PdoSessionHandler $sessionHandler; + + public function __construct(\SessionHandlerInterface $sessionHandler) + { + if ($sessionHandler instanceof PdoSessionHandler) { + $this->sessionHandler = $sessionHandler; + } + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + if (!isset($this->sessionHandler)) { + return; + } + + $connection = $event->getEntityManager()->getConnection(); + + $this->sessionHandler->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); + } +} diff --git a/vendor/symfony/doctrine-bridge/SchemaListener/RememberMeTokenProviderDoctrineSchemaListener.php b/vendor/symfony/doctrine-bridge/SchemaListener/RememberMeTokenProviderDoctrineSchemaListener.php new file mode 100644 index 0000000..60027e9 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/SchemaListener/RememberMeTokenProviderDoctrineSchemaListener.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; +use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler; +use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; + +/** + * Automatically adds the rememberme table needed for the {@see DoctrineTokenProvider}. + */ +class RememberMeTokenProviderDoctrineSchemaListener extends AbstractSchemaListener +{ + /** + * @param iterable $rememberMeHandlers + */ + public function __construct( + private readonly iterable $rememberMeHandlers, + ) { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->rememberMeHandlers as $rememberMeHandler) { + if ( + $rememberMeHandler instanceof PersistentRememberMeHandler + && ($tokenProvider = $rememberMeHandler->getTokenProvider()) instanceof DoctrineTokenProvider + ) { + $tokenProvider->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); + } + } + } +} diff --git a/vendor/symfony/doctrine-bridge/Security/RememberMe/DoctrineTokenProvider.php b/vendor/symfony/doctrine-bridge/Security/RememberMe/DoctrineTokenProvider.php new file mode 100644 index 0000000..206fda1 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Security/RememberMe/DoctrineTokenProvider.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Security\RememberMe; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Types\Types; +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; +use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; +use Symfony\Component\Security\Core\Authentication\RememberMe\TokenVerifierInterface; +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; + +/** + * This class provides storage for the tokens that is set in "remember-me" + * cookies. This way no password secrets will be stored in the cookies on + * the client machine, and thus the security is improved. + * + * This depends only on doctrine in order to get a database connection + * and to do the conversion of the datetime column. + * + * In order to use this class, you need the following table in your database: + * + * CREATE TABLE `rememberme_token` ( + * `series` char(88) UNIQUE PRIMARY KEY NOT NULL, + * `value` char(88) NOT NULL, + * `lastUsed` datetime NOT NULL, + * `class` varchar(100) NOT NULL, + * `username` varchar(200) NOT NULL + * ); + */ +final class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInterface +{ + public function __construct( + private readonly Connection $conn, + ) { + } + + public function loadTokenBySeries(string $series): PersistentTokenInterface + { + // the alias for lastUsed works around case insensitivity in PostgreSQL + $sql = 'SELECT class, username, value, lastUsed AS last_used FROM rememberme_token WHERE series=:series'; + $paramValues = ['series' => $series]; + $paramTypes = ['series' => ParameterType::STRING]; + $stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes); + $row = $stmt->fetchAssociative() ?: throw new TokenNotFoundException('No token found.'); + + return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTimeImmutable($row['last_used'])); + } + + public function deleteTokenBySeries(string $series): void + { + $sql = 'DELETE FROM rememberme_token WHERE series=:series'; + $paramValues = ['series' => $series]; + $paramTypes = ['series' => ParameterType::STRING]; + $this->conn->executeStatement($sql, $paramValues, $paramTypes); + } + + public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void + { + $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed WHERE series=:series'; + $paramValues = [ + 'value' => $tokenValue, + 'lastUsed' => \DateTimeImmutable::createFromInterface($lastUsed), + 'series' => $series, + ]; + $paramTypes = [ + 'value' => ParameterType::STRING, + 'lastUsed' => Types::DATETIME_IMMUTABLE, + 'series' => ParameterType::STRING, + ]; + $updated = $this->conn->executeStatement($sql, $paramValues, $paramTypes); + if ($updated < 1) { + throw new TokenNotFoundException('No token found.'); + } + } + + public function createNewToken(PersistentTokenInterface $token): void + { + $sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)'; + $paramValues = [ + 'class' => $token->getClass(), + 'username' => $token->getUserIdentifier(), + 'series' => $token->getSeries(), + 'value' => $token->getTokenValue(), + 'lastUsed' => \DateTimeImmutable::createFromInterface($token->getLastUsed()), + ]; + $paramTypes = [ + 'class' => ParameterType::STRING, + 'username' => ParameterType::STRING, + 'series' => ParameterType::STRING, + 'value' => ParameterType::STRING, + 'lastUsed' => Types::DATETIME_IMMUTABLE, + ]; + $this->conn->executeStatement($sql, $paramValues, $paramTypes); + } + + public function verifyToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue): bool + { + // Check if the token value matches the current persisted token + if (hash_equals($token->getTokenValue(), $tokenValue)) { + return true; + } + + // Generate an alternative series id here by changing the suffix == to _ + // this is needed to be able to store an older token value in the database + // which has a PRIMARY(series), and it works as long as series ids are + // generated using base64_encode(random_bytes(64)) which always outputs + // a == suffix, but if it should not work for some reason we abort + // for safety + $tmpSeries = preg_replace('{=+$}', '_', $token->getSeries()); + if ($tmpSeries === $token->getSeries()) { + return false; + } + + // Check if the previous token is present. If the given $tokenValue + // matches the previous token (and it is outdated by at most 60seconds) + // we also accept it as a valid value. + try { + $tmpToken = $this->loadTokenBySeries($tmpSeries); + } catch (TokenNotFoundException) { + return false; + } + + if ($tmpToken->getLastUsed()->getTimestamp() + 60 < time()) { + return false; + } + + return hash_equals($tmpToken->getTokenValue(), $tokenValue); + } + + public function updateExistingToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void + { + if (!$token instanceof PersistentToken) { + return; + } + + // Persist a copy of the previous token for authentication + // in verifyToken should the old token still be sent by the browser + // in a request concurrent to the one that did this token update + $tmpSeries = preg_replace('{=+$}', '_', $token->getSeries()); + // if we cannot generate a unique series it is not worth trying further + if ($tmpSeries === $token->getSeries()) { + return; + } + + $this->conn->beginTransaction(); + try { + $this->deleteTokenBySeries($tmpSeries); + $lastUsed = \DateTime::createFromInterface($lastUsed); + $this->createNewToken(new PersistentToken($token->getClass(), $token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed)); + + $this->conn->commit(); + } catch (\Exception $e) { + $this->conn->rollBack(); + throw $e; + } + } + + /** + * Adds the Table to the Schema if "remember me" uses this Connection. + */ + public function configureSchema(Schema $schema, Connection $forConnection, \Closure $isSameDatabase): void + { + if ($schema->hasTable('rememberme_token')) { + return; + } + + if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) { + return; + } + + $this->addTableToSchema($schema); + } + + private function addTableToSchema(Schema $schema): void + { + $table = $schema->createTable('rememberme_token'); + $table->addColumn('series', Types::STRING, ['length' => 88]); + $table->addColumn('value', Types::STRING, ['length' => 88]); + $table->addColumn('lastUsed', Types::DATETIME_IMMUTABLE); + $table->addColumn('class', Types::STRING, ['length' => 100]); + $table->addColumn('username', Types::STRING, ['length' => 200]); + $table->setPrimaryKey(['series']); + } +} diff --git a/vendor/symfony/doctrine-bridge/Security/User/EntityUserProvider.php b/vendor/symfony/doctrine-bridge/Security/User/EntityUserProvider.php new file mode 100644 index 0000000..22ec621 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Security/User/EntityUserProvider.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Security\User; + +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; +use Doctrine\Persistence\Proxy; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * Wrapper around a Doctrine ObjectManager. + * + * Provides provisioning for Doctrine entity users. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + * + * @template TUser of UserInterface + * + * @template-implements UserProviderInterface + */ +class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInterface +{ + private string $class; + + public function __construct( + private readonly ManagerRegistry $registry, + private readonly string $classOrAlias, + private readonly ?string $property = null, + private readonly ?string $managerName = null, + ) { + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + $repository = $this->getRepository(); + if (null !== $this->property) { + $user = $repository->findOneBy([$this->property => $identifier]); + } else { + if (!$repository instanceof UserLoaderInterface) { + throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, get_debug_type($repository))); + } + + $user = $repository->loadUserByIdentifier($identifier); + } + + if (null === $user) { + $e = new UserNotFoundException(sprintf('User "%s" not found.', $identifier)); + $e->setUserIdentifier($identifier); + + throw $e; + } + + return $user; + } + + public function refreshUser(UserInterface $user): UserInterface + { + $class = $this->getClass(); + if (!$user instanceof $class) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); + } + + $repository = $this->getRepository(); + if ($repository instanceof UserProviderInterface) { + $refreshedUser = $repository->refreshUser($user); + } else { + // The user must be reloaded via the primary key as all other data + // might have changed without proper persistence in the database. + // That's the case when the user has been changed by a form with + // validation errors. + if (!$id = $this->getClassMetadata()->getIdentifierValues($user)) { + throw new \InvalidArgumentException('You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.'); + } + + $refreshedUser = $repository->find($id); + if (null === $refreshedUser) { + $e = new UserNotFoundException('User with id '.json_encode($id).' not found.'); + $e->setUserIdentifier(json_encode($id)); + + throw $e; + } + } + + if ($refreshedUser instanceof Proxy && !$refreshedUser->__isInitialized()) { + $refreshedUser->__load(); + } + + return $refreshedUser; + } + + public function supportsClass(string $class): bool + { + return $class === $this->getClass() || is_subclass_of($class, $this->getClass()); + } + + /** + * @final + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + $class = $this->getClass(); + if (!$user instanceof $class) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); + } + + $repository = $this->getRepository(); + if ($user instanceof PasswordAuthenticatedUserInterface && $repository instanceof PasswordUpgraderInterface) { + $repository->upgradePassword($user, $newHashedPassword); + } + } + + private function getObjectManager(): ObjectManager + { + return $this->registry->getManager($this->managerName); + } + + private function getRepository(): ObjectRepository + { + return $this->getObjectManager()->getRepository($this->classOrAlias); + } + + private function getClass(): string + { + if (!isset($this->class)) { + $class = $this->classOrAlias; + + if (str_contains($class, ':')) { + $class = $this->getClassMetadata()->getName(); + } + + $this->class = $class; + } + + return $this->class; + } + + private function getClassMetadata(): ClassMetadata + { + return $this->getObjectManager()->getClassMetadata($this->classOrAlias); + } +} diff --git a/vendor/symfony/doctrine-bridge/Security/User/UserLoaderInterface.php b/vendor/symfony/doctrine-bridge/Security/User/UserLoaderInterface.php new file mode 100644 index 0000000..e22e0bf --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Security/User/UserLoaderInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Security\User; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Represents a class that loads UserInterface objects from Doctrine source for the authentication system. + * + * This interface is meant to facilitate the loading of a User from Doctrine source using a custom method. + * If you want to implement your own logic of retrieving the user from Doctrine your repository should implement this + * interface. + * + * @see UserInterface + * + * @author Michal Trojanowski + */ +interface UserLoaderInterface +{ + /** + * Loads the user for the given user identifier (e.g. username or email). + * + * This method must return null if the user is not found. + */ + public function loadUserByIdentifier(string $identifier): ?UserInterface; +} diff --git a/vendor/symfony/doctrine-bridge/Types/AbstractUidType.php b/vendor/symfony/doctrine-bridge/Types/AbstractUidType.php new file mode 100644 index 0000000..570c1b0 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Types/AbstractUidType.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Exception\InvalidType; +use Doctrine\DBAL\Types\Exception\ValueNotConvertible; +use Doctrine\DBAL\Types\Type; +use Symfony\Component\Uid\AbstractUid; + +abstract class AbstractUidType extends Type +{ + /** + * @return class-string + */ + abstract protected function getUidClass(): string; + + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string + { + if ($this->hasNativeGuidType($platform)) { + return $platform->getGuidTypeDeclarationSQL($column); + } + + return $platform->getBinaryTypeDeclarationSQL([ + 'length' => 16, + 'fixed' => true, + ]); + } + + /** + * @throws ConversionException + */ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?AbstractUid + { + if ($value instanceof AbstractUid || null === $value) { + return $value; + } + + if (!\is_string($value)) { + $this->throwInvalidType($value); + } + + try { + return $this->getUidClass()::fromString($value); + } catch (\InvalidArgumentException $e) { + $this->throwValueNotConvertible($value, $e); + } + } + + /** + * @throws ConversionException + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + $toString = $this->hasNativeGuidType($platform) ? 'toRfc4122' : 'toBinary'; + + if ($value instanceof AbstractUid) { + return $value->$toString(); + } + + if (null === $value || '' === $value) { + return null; + } + + if (!\is_string($value)) { + $this->throwInvalidType($value); + } + + try { + return $this->getUidClass()::fromString($value)->$toString(); + } catch (\InvalidArgumentException $e) { + $this->throwValueNotConvertible($value, $e); + } + } + + public function requiresSQLCommentHint(AbstractPlatform $platform): bool + { + return true; + } + + private function hasNativeGuidType(AbstractPlatform $platform): bool + { + return $platform->getGuidTypeDeclarationSQL([]) !== $platform->getStringTypeDeclarationSQL(['fixed' => true, 'length' => 36]); + } + + private function throwInvalidType(mixed $value): never + { + if (!class_exists(InvalidType::class)) { + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); + } + + throw InvalidType::new($value, $this->getName(), ['null', 'string', AbstractUid::class]); + } + + private function throwValueNotConvertible(mixed $value, \Throwable $previous): never + { + if (!class_exists(ValueNotConvertible::class)) { + throw ConversionException::conversionFailed($value, $this->getName(), $previous); + } + + throw ValueNotConvertible::new($value, $this->getName(), null, $previous); + } +} diff --git a/vendor/symfony/doctrine-bridge/Types/UlidType.php b/vendor/symfony/doctrine-bridge/Types/UlidType.php new file mode 100644 index 0000000..c7c5d6c --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Types/UlidType.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Types; + +use Symfony\Component\Uid\Ulid; + +final class UlidType extends AbstractUidType +{ + public const NAME = 'ulid'; + + public function getName(): string + { + return self::NAME; + } + + protected function getUidClass(): string + { + return Ulid::class; + } +} diff --git a/vendor/symfony/doctrine-bridge/Types/UuidType.php b/vendor/symfony/doctrine-bridge/Types/UuidType.php new file mode 100644 index 0000000..a7a0c2b --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Types/UuidType.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Types; + +use Symfony\Component\Uid\Uuid; + +final class UuidType extends AbstractUidType +{ + public const NAME = 'uuid'; + + public function getName(): string + { + return self::NAME; + } + + protected function getUidClass(): string + { + return Uuid::class; + } +} diff --git a/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntity.php b/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntity.php new file mode 100644 index 0000000..34a16df --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntity.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Constraint for the Unique Entity validator. + * + * @author Benjamin Eberlei + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class UniqueEntity extends Constraint +{ + public const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f'; + + protected const ERROR_NAMES = [ + self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR', + ]; + + public string $message = 'This value is already used.'; + public string $service = 'doctrine.orm.validator.unique'; + public ?string $em = null; + public ?string $entityClass = null; + public string $repositoryMethod = 'findBy'; + public array|string $fields = []; + public ?string $errorPath = null; + public bool|array|string $ignoreNull = true; + public array $identifierFieldNames = []; + + /** + * @param array|string $fields The combination of fields that must contain unique values or a set of options + * @param bool|string[]|string $ignoreNull The combination of fields that ignore null values + * @param string|null $em The entity manager used to query for uniqueness instead of the manager of this class + * @param string|null $entityClass The entity class to enforce uniqueness on instead of the current class + * @param string|null $repositoryMethod The repository method to check uniqueness instead of findBy. The method will receive as its argument + * a fieldName => value associative array according to the fields option configuration + * @param string|null $errorPath Bind the constraint violation to this field instead of the first one in the fields option configuration + */ + public function __construct( + array|string $fields, + ?string $message = null, + ?string $service = null, + ?string $em = null, + ?string $entityClass = null, + ?string $repositoryMethod = null, + ?string $errorPath = null, + bool|string|array|null $ignoreNull = null, + ?array $identifierFieldNames = null, + ?array $groups = null, + $payload = null, + array $options = [], + ) { + if (\is_array($fields) && \is_string(key($fields)) && [] === array_diff(array_keys($fields), array_merge(array_keys(get_class_vars(static::class)), ['value']))) { + $options = array_merge($fields, $options); + } else { + $options['fields'] = $fields; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->service = $service ?? $this->service; + $this->em = $em ?? $this->em; + $this->entityClass = $entityClass ?? $this->entityClass; + $this->repositoryMethod = $repositoryMethod ?? $this->repositoryMethod; + $this->errorPath = $errorPath ?? $this->errorPath; + $this->ignoreNull = $ignoreNull ?? $this->ignoreNull; + $this->identifierFieldNames = $identifierFieldNames ?? $this->identifierFieldNames; + } + + public function getRequiredOptions(): array + { + return ['fields']; + } + + /** + * The validator must be defined as a service with this name. + */ + public function validatedBy(): string + { + return $this->service; + } + + public function getTargets(): string|array + { + return self::CLASS_CONSTRAINT; + } + + public function getDefaultOption(): ?string + { + return 'fields'; + } +} diff --git a/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php b/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php new file mode 100644 index 0000000..ec5aac9 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Validator\Constraints; + +use Doctrine\ORM\Mapping\MappingException as ORMMappingException; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException; +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Unique Entity Validator checks if one or a set of fields contain unique values. + * + * @author Benjamin Eberlei + */ +class UniqueEntityValidator extends ConstraintValidator +{ + public function __construct( + private readonly ManagerRegistry $registry, + ) { + } + + /** + * @throws UnexpectedTypeException + * @throws ConstraintDefinitionException + */ + public function validate(mixed $value, Constraint $constraint): void + { + if (!$constraint instanceof UniqueEntity) { + throw new UnexpectedTypeException($constraint, UniqueEntity::class); + } + + if (!\is_array($constraint->fields) && !\is_string($constraint->fields)) { + throw new UnexpectedTypeException($constraint->fields, 'array'); + } + + if (null !== $constraint->errorPath && !\is_string($constraint->errorPath)) { + throw new UnexpectedTypeException($constraint->errorPath, 'string or null'); + } + + $fields = (array) $constraint->fields; + + if (0 === \count($fields)) { + throw new ConstraintDefinitionException('At least one field has to be specified.'); + } + + if (null === $value) { + return; + } + + if (!\is_object($value)) { + throw new UnexpectedValueException($value, 'object'); + } + + $entityClass = $constraint->entityClass ?? $value::class; + + if ($constraint->em) { + $em = $this->registry->getManager($constraint->em); + + if (!$em) { + throw new ConstraintDefinitionException(sprintf('Object manager "%s" does not exist.', $constraint->em)); + } + } else { + $em = $this->registry->getManagerForClass($entityClass); + + if (!$em) { + throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', $entityClass)); + } + } + + try { + $em->getRepository($value::class); + $isValueEntity = true; + } catch (ORMMappingException|PersistenceMappingException) { + $isValueEntity = false; + } + + $class = $em->getClassMetadata($entityClass); + + $criteria = []; + $hasIgnorableNullValue = false; + + $fieldValues = $this->getFieldValues($value, $class, $fields, $isValueEntity); + + foreach ($fieldValues as $fieldName => $fieldValue) { + if (null === $fieldValue && $this->ignoreNullForField($constraint, $fieldName)) { + $hasIgnorableNullValue = true; + + continue; + } + + $criteria[$fieldName] = $fieldValue; + + if (null !== $criteria[$fieldName] && $class->hasAssociation($fieldName)) { + /* Ensure the Proxy is initialized before using reflection to + * read its identifiers. This is necessary because the wrapped + * getter methods in the Proxy are being bypassed. + */ + $em->initializeObject($criteria[$fieldName]); + } + } + + // validation doesn't fail if one of the fields is null and if null values should be ignored + if ($hasIgnorableNullValue) { + return; + } + + // skip validation if there are no criteria (this can happen when the + // "ignoreNull" option is enabled and fields to be checked are null + if (!$criteria) { + return; + } + + if (null !== $constraint->entityClass) { + /* Retrieve repository from given entity name. + * We ensure the retrieved repository can handle the entity + * by checking the entity is the same, or subclass of the supported entity. + */ + $repository = $em->getRepository($constraint->entityClass); + $supportedClass = $repository->getClassName(); + + if ($isValueEntity && !$value instanceof $supportedClass) { + $class = $em->getClassMetadata($value::class); + throw new ConstraintDefinitionException(sprintf('The "%s" entity repository does not support the "%s" entity. The entity should be an instance of or extend "%s".', $constraint->entityClass, $class->getName(), $supportedClass)); + } + } else { + $repository = $em->getRepository($value::class); + } + + $arguments = [$criteria]; + + /* If the default repository method is used, it is always enough to retrieve at most two entities because: + * - No entity returned, the current entity is definitely unique. + * - More than one entity returned, the current entity cannot be unique. + * - One entity returned the uniqueness depends on the current entity. + */ + if ('findBy' === $constraint->repositoryMethod) { + $arguments = [$criteria, null, 2]; + } + + $result = $repository->{$constraint->repositoryMethod}(...$arguments); + + if ($result instanceof \IteratorAggregate) { + $result = $result->getIterator(); + } + + /* If the result is a MongoCursor, it must be advanced to the first + * element. Rewinding should have no ill effect if $result is another + * iterator implementation. + */ + if ($result instanceof \Iterator) { + $result->rewind(); + if ($result instanceof \Countable && 1 < \count($result)) { + $result = [$result->current(), $result->current()]; + } else { + $result = $result->valid() && null !== $result->current() ? [$result->current()] : []; + } + } elseif (\is_array($result)) { + reset($result); + } else { + $result = null === $result ? [] : [$result]; + } + + /* If no entity matched the query criteria or a single entity matched, + * which is the same as the entity being validated, the criteria is + * unique. + */ + if (!$result || (1 === \count($result) && current($result) === $value)) { + return; + } + + /* If a single entity matched the query criteria, which is the same as + * the entity being updated by validated object, the criteria is unique. + */ + if (!$isValueEntity && !empty($constraint->identifierFieldNames) && 1 === \count($result)) { + $fieldValues = $this->getFieldValues($value, $class, $constraint->identifierFieldNames); + if (array_values($class->getIdentifierFieldNames()) != array_values($constraint->identifierFieldNames)) { + throw new ConstraintDefinitionException(sprintf('The "%s" entity identifier field names should be "%s", not "%s".', $entityClass, implode(', ', $class->getIdentifierFieldNames()), implode(', ', $constraint->identifierFieldNames))); + } + + $entityMatched = true; + + foreach ($constraint->identifierFieldNames as $identifierFieldName) { + $propertyValue = $this->getPropertyValue($entityClass, $identifierFieldName, current($result)); + if ($fieldValues[$identifierFieldName] !== $propertyValue) { + $entityMatched = false; + break; + } + } + + if ($entityMatched) { + return; + } + } + + $errorPath = $constraint->errorPath ?? current($fields); + $invalidValue = $criteria[$errorPath] ?? $criteria[current($fields)]; + + $this->context->buildViolation($constraint->message) + ->atPath($errorPath) + ->setParameter('{{ value }}', $this->formatWithIdentifiers($em, $class, $invalidValue)) + ->setInvalidValue($invalidValue) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->setCause($result) + ->addViolation(); + } + + private function ignoreNullForField(UniqueEntity $constraint, string $fieldName): bool + { + if (\is_bool($constraint->ignoreNull)) { + return $constraint->ignoreNull; + } + + return \in_array($fieldName, (array) $constraint->ignoreNull, true); + } + + private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, mixed $value): string + { + if (!\is_object($value) || $value instanceof \DateTimeInterface) { + return $this->formatValue($value, self::PRETTY_DATE); + } + + if ($value instanceof \Stringable) { + return (string) $value; + } + + if ($class->getName() !== $idClass = $value::class) { + // non-unique value might be a composite PK that consists of other entity objects + if ($em->getMetadataFactory()->hasMetadataFor($idClass)) { + $identifiers = $em->getClassMetadata($idClass)->getIdentifierValues($value); + } else { + // this case might happen if the non-unique column has a custom doctrine type and its value is an object + // in which case we cannot get any identifiers for it + $identifiers = []; + } + } else { + $identifiers = $class->getIdentifierValues($value); + } + + if (!$identifiers) { + return sprintf('object("%s")', $idClass); + } + + array_walk($identifiers, function (&$id, $field) { + if (!\is_object($id) || $id instanceof \DateTimeInterface) { + $idAsString = $this->formatValue($id, self::PRETTY_DATE); + } else { + $idAsString = sprintf('object("%s")', $id::class); + } + + $id = sprintf('%s => %s', $field, $idAsString); + }); + + return sprintf('object("%s") identified by (%s)', $idClass, implode(', ', $identifiers)); + } + + private function getFieldValues(mixed $object, ClassMetadata $class, array $fields, bool $isValueEntity = false): array + { + if (!$isValueEntity) { + $reflectionObject = new \ReflectionObject($object); + } + + $fieldValues = []; + $objectClass = $object::class; + + foreach ($fields as $objectFieldName => $entityFieldName) { + if (!$class->hasField($entityFieldName) && !$class->hasAssociation($entityFieldName)) { + throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $entityFieldName)); + } + + $fieldName = \is_int($objectFieldName) ? $entityFieldName : $objectFieldName; + if (!$isValueEntity && !$reflectionObject->hasProperty($fieldName)) { + throw new ConstraintDefinitionException(sprintf('The field "%s" is not a property of class "%s".', $fieldName, $objectClass)); + } + + $fieldValues[$entityFieldName] = $isValueEntity && $object instanceof ($class->getName()) + ? $class->reflFields[$fieldName]->getValue($object) + : $this->getPropertyValue($objectClass, $fieldName, $object); + } + + return $fieldValues; + } + + private function getPropertyValue(string $class, string $name, mixed $object): mixed + { + $property = new \ReflectionProperty($class, $name); + + return $property->getValue($object); + } +} diff --git a/vendor/symfony/doctrine-bridge/Validator/DoctrineInitializer.php b/vendor/symfony/doctrine-bridge/Validator/DoctrineInitializer.php new file mode 100644 index 0000000..ca5c466 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Validator/DoctrineInitializer.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Validator; + +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Validator\ObjectInitializerInterface; + +/** + * Automatically loads proxy object before validation. + * + * @author Fabien Potencier + */ +class DoctrineInitializer implements ObjectInitializerInterface +{ + protected ManagerRegistry $registry; + + public function __construct(ManagerRegistry $registry) + { + $this->registry = $registry; + } + + public function initialize(object $object): void + { + $this->registry->getManagerForClass($object::class)?->initializeObject($object); + } +} diff --git a/vendor/symfony/doctrine-bridge/Validator/DoctrineLoader.php b/vendor/symfony/doctrine-bridge/Validator/DoctrineLoader.php new file mode 100644 index 0000000..93413c4 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/Validator/DoctrineLoader.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Validator; + +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata as OrmClassMetadata; +use Doctrine\ORM\Mapping\FieldMapping; +use Doctrine\ORM\Mapping\MappingException as OrmMappingException; +use Doctrine\Persistence\Mapping\MappingException; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\Mapping\AutoMappingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait; +use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; + +/** + * Guesses and loads the appropriate constraints using Doctrine's metadata. + * + * @author Kévin Dunglas + */ +final class DoctrineLoader implements LoaderInterface +{ + use AutoMappingTrait; + + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly ?string $classValidatorRegexp = null, + ) { + } + + public function loadClassMetadata(ClassMetadata $metadata): bool + { + $className = $metadata->getClassName(); + try { + $doctrineMetadata = $this->entityManager->getClassMetadata($className); + } catch (MappingException|OrmMappingException) { + return false; + } + + if (!$doctrineMetadata instanceof OrmClassMetadata) { + return false; + } + + $loaded = false; + $enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp); + + /* Available keys: + - type + - scale + - length + - unique + - nullable + - precision + */ + $existingUniqueFields = $this->getExistingUniqueFields($metadata); + + // Type and nullable aren't handled here, use the PropertyInfo Loader instead. + foreach ($doctrineMetadata->fieldMappings as $mapping) { + $enabledForProperty = $enabledForClass; + $lengthConstraint = null; + foreach ($metadata->getPropertyMetadata(self::getFieldMappingValue($mapping, 'fieldName')) as $propertyMetadata) { + // Enabling or disabling auto-mapping explicitly always takes precedence + if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) { + continue 2; + } + if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) { + $enabledForProperty = true; + } + + foreach ($propertyMetadata->getConstraints() as $constraint) { + if ($constraint instanceof Length) { + $lengthConstraint = $constraint; + } + } + } + + if (!$enabledForProperty) { + continue; + } + + if (true === (self::getFieldMappingValue($mapping, 'unique') ?? false) && !isset($existingUniqueFields[self::getFieldMappingValue($mapping, 'fieldName')])) { + $metadata->addConstraint(new UniqueEntity(['fields' => self::getFieldMappingValue($mapping, 'fieldName')])); + $loaded = true; + } + + if (null === (self::getFieldMappingValue($mapping, 'length') ?? null) || null !== (self::getFieldMappingValue($mapping, 'enumType') ?? null) || !\in_array(self::getFieldMappingValue($mapping, 'type'), ['string', 'text'], true)) { + continue; + } + + if (null === $lengthConstraint) { + if (self::getFieldMappingValue($mapping, 'originalClass') && !str_contains(self::getFieldMappingValue($mapping, 'declaredField'), '.')) { + $metadata->addPropertyConstraint(self::getFieldMappingValue($mapping, 'declaredField'), new Valid()); + $loaded = true; + } elseif (property_exists($className, self::getFieldMappingValue($mapping, 'fieldName')) && (!$doctrineMetadata->isMappedSuperclass || $metadata->getReflectionClass()->getProperty(self::getFieldMappingValue($mapping, 'fieldName'))->isPrivate())) { + $metadata->addPropertyConstraint(self::getFieldMappingValue($mapping, 'fieldName'), new Length(['max' => self::getFieldMappingValue($mapping, 'length')])); + $loaded = true; + } + } elseif (null === $lengthConstraint->max) { + // If a Length constraint exists and no max length has been explicitly defined, set it + $lengthConstraint->max = self::getFieldMappingValue($mapping, 'length'); + } + } + + return $loaded; + } + + private function getExistingUniqueFields(ClassMetadata $metadata): array + { + $fields = []; + foreach ($metadata->getConstraints() as $constraint) { + if (!$constraint instanceof UniqueEntity) { + continue; + } + + if (\is_string($constraint->fields)) { + $fields[$constraint->fields] = true; + } elseif (\is_array($constraint->fields) && 1 === \count($constraint->fields)) { + $fields[$constraint->fields[0]] = true; + } + } + + return $fields; + } + + private static function getFieldMappingValue(array|FieldMapping $mapping, string $key): mixed + { + if ($mapping instanceof FieldMapping) { + return $mapping->$key ?? null; + } + + return $mapping[$key] ?? null; + } +} diff --git a/vendor/symfony/doctrine-bridge/composer.json b/vendor/symfony/doctrine-bridge/composer.json new file mode 100644 index 0000000..00cc394 --- /dev/null +++ b/vendor/symfony/doctrine-bridge/composer.json @@ -0,0 +1,75 @@ +{ + "name": "symfony/doctrine-bridge", + "type": "symfony-bridge", + "description": "Provides integration for Doctrine with various Symfony components", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4.6|^7.0.6", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "doctrine/collections": "^1.0|^2.0", + "doctrine/data-fixtures": "^1.1", + "doctrine/dbal": "^3.6|^4", + "doctrine/orm": "^2.15|^3", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<2.15", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/form": "<6.4.6|>=7,<7.0.6", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/lock": "<6.4", + "symfony/messenger": "<6.4", + "symfony/property-info": "<6.4", + "symfony/security-bundle": "<6.4", + "symfony/security-core": "<6.4", + "symfony/validator": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/dotenv/CHANGELOG.md b/vendor/symfony/dotenv/CHANGELOG.md new file mode 100644 index 0000000..a587fba --- /dev/null +++ b/vendor/symfony/dotenv/CHANGELOG.md @@ -0,0 +1,50 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `SYMFONY_DOTENV_PATH` variable with the path to the `.env` file loaded by `Dotenv::loadEnv()` or `Dotenv::bootEnv()` + +6.2 +--- + + * Add a new `filter` argument to `debug:dotenv` command to filter variable names + +5.4 +--- + + * Add `dotenv:dump` command to compile the contents of the .env files into a PHP-optimized file called `.env.local.php` + * Add `debug:dotenv` command to list all dotenv files with variables and values + * Add `$overrideExistingVars` on `Dotenv::bootEnv()` and `Dotenv::loadEnv()` + +5.1.0 +----- + + * added `Dotenv::bootEnv()` to check for `.env.local.php` before calling `Dotenv::loadEnv()` + * added `Dotenv::setProdEnvs()` and `Dotenv::usePutenv()` + * made Dotenv's constructor accept `$envKey` and `$debugKey` arguments, to define + the name of the env vars that configure the env name and debug settings + * deprecated passing `$usePutenv` argument to Dotenv's constructor + +5.0.0 +----- + + * using `putenv()` is disabled by default + +4.3.0 +----- + + * deprecated use of `putenv()` by default. This feature will be opted-in with a constructor argument to `Dotenv` + +4.2.0 +----- + + * added `Dotenv::overload()` and `$overrideExistingVars` as optional parameter of `Dotenv::populate()` + * added `Dotenv::loadEnv()` to load a .env file and its corresponding .env.local, .env.$env and .env.$env.local files if they exist + +3.3.0 +----- + + * [BC BREAK] Since v3.3.7, the latest Dotenv files override the previous ones. Real env vars are not affected and are not overridden. + * added the component diff --git a/vendor/symfony/dotenv/Command/DebugCommand.php b/vendor/symfony/dotenv/Command/DebugCommand.php new file mode 100644 index 0000000..d0c2723 --- /dev/null +++ b/vendor/symfony/dotenv/Command/DebugCommand.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Dotenv\Dotenv; + +/** + * A console command to debug current dotenv files with variables and values. + * + * @author Christopher Hertel + */ +#[AsCommand(name: 'debug:dotenv', description: 'List all dotenv files with variables and values')] +final class DebugCommand extends Command +{ + /** + * @deprecated since Symfony 6.1 + */ + protected static $defaultName = 'debug:dotenv'; + + /** + * @deprecated since Symfony 6.1 + */ + protected static $defaultDescription = 'List all dotenv files with variables and values'; + + private string $kernelEnvironment; + private string $projectDirectory; + + public function __construct(string $kernelEnvironment, string $projectDirectory) + { + $this->kernelEnvironment = $kernelEnvironment; + $this->projectDirectory = $projectDirectory; + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('filter', InputArgument::OPTIONAL, 'The name of an environment variable or a filter.', null, $this->getAvailableVars(...)), + ]) + ->setHelp(<<<'EOT' +The %command.full_name% command displays all the environment variables configured by dotenv: + + php %command.full_name% + +To get specific variables, specify its full or partial name: + + php %command.full_name% FOO_BAR + +EOT + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $io->title('Dotenv Variables & Files'); + + if (!\array_key_exists('SYMFONY_DOTENV_VARS', $_SERVER)) { + $io->error('Dotenv component is not initialized.'); + + return 1; + } + + $filePath = $_SERVER['SYMFONY_DOTENV_PATH'] ?? $this->projectDirectory.\DIRECTORY_SEPARATOR.'.env'; + + $envFiles = $this->getEnvFiles($filePath); + $availableFiles = array_filter($envFiles, 'is_file'); + + if (\in_array(sprintf('%s.local.php', $filePath), $availableFiles, true)) { + $io->warning(sprintf('Due to existing dump file (%s.local.php) all other dotenv files are skipped.', $this->getRelativeName($filePath))); + } + + if (is_file($filePath) && is_file(sprintf('%s.dist', $filePath))) { + $io->warning(sprintf('The file %s.dist gets skipped due to the existence of %1$s.', $this->getRelativeName($filePath))); + } + + $io->section('Scanned Files (in descending priority)'); + $io->listing(array_map(fn (string $envFile) => \in_array($envFile, $availableFiles, true) + ? sprintf('✓ %s', $this->getRelativeName($envFile)) + : sprintf('⨯ %s', $this->getRelativeName($envFile)), $envFiles)); + + $nameFilter = $input->getArgument('filter'); + $variables = $this->getVariables($availableFiles, $nameFilter); + + $io->section('Variables'); + + if ($variables || null === $nameFilter) { + $io->table( + array_merge(['Variable', 'Value'], array_map($this->getRelativeName(...), $availableFiles)), + $variables + ); + + $io->comment('Note that values might be different between web and CLI.'); + } else { + $io->warning(sprintf('No variables match the given filter "%s".', $nameFilter)); + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('filter')) { + $suggestions->suggestValues($this->getAvailableVars()); + } + } + + private function getVariables(array $envFiles, ?string $nameFilter): array + { + $variables = []; + $fileValues = []; + $dotenvVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? '')); + + foreach ($envFiles as $envFile) { + $fileValues[$envFile] = $this->loadValues($envFile); + $variables += $fileValues[$envFile]; + } + + foreach ($variables as $var => $varDetails) { + if (null !== $nameFilter && 0 !== stripos($var, $nameFilter)) { + unset($variables[$var]); + continue; + } + + $realValue = $_SERVER[$var] ?? ''; + $varDetails = [$var, ''.OutputFormatter::escape($realValue).'']; + $varSeen = !isset($dotenvVars[$var]); + + foreach ($envFiles as $envFile) { + if (null === $value = $fileValues[$envFile][$var] ?? null) { + $varDetails[] = 'n/a'; + continue; + } + + $shortenedValue = OutputFormatter::escape($this->getHelper('formatter')->truncate($value, 30)); + $varDetails[] = $value === $realValue && !$varSeen ? ''.$shortenedValue.'' : $shortenedValue; + $varSeen = $varSeen || $value === $realValue; + } + + $variables[$var] = $varDetails; + } + + ksort($variables); + + return $variables; + } + + private function getAvailableVars(): array + { + $filePath = $_SERVER['SYMFONY_DOTENV_PATH'] ?? $this->projectDirectory.\DIRECTORY_SEPARATOR.'.env'; + $envFiles = $this->getEnvFiles($filePath); + + return array_keys($this->getVariables(array_filter($envFiles, 'is_file'), null)); + } + + private function getEnvFiles(string $filePath): array + { + $files = [ + sprintf('%s.local.php', $filePath), + sprintf('%s.%s.local', $filePath, $this->kernelEnvironment), + sprintf('%s.%s', $filePath, $this->kernelEnvironment), + ]; + + if ('test' !== $this->kernelEnvironment) { + $files[] = sprintf('%s.local', $filePath); + } + + if (!is_file($filePath) && is_file(sprintf('%s.dist', $filePath))) { + $files[] = sprintf('%s.dist', $filePath); + } else { + $files[] = $filePath; + } + + return $files; + } + + private function getRelativeName(string $filePath): string + { + if (str_starts_with($filePath, $this->projectDirectory)) { + return substr($filePath, \strlen($this->projectDirectory) + 1); + } + + return basename($filePath); + } + + private function loadValues(string $filePath): array + { + if (str_ends_with($filePath, '.php')) { + return include $filePath; + } + + return (new Dotenv())->parse(file_get_contents($filePath)); + } +} diff --git a/vendor/symfony/dotenv/Command/DotenvDumpCommand.php b/vendor/symfony/dotenv/Command/DotenvDumpCommand.php new file mode 100644 index 0000000..37383dc --- /dev/null +++ b/vendor/symfony/dotenv/Command/DotenvDumpCommand.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; +use Symfony\Component\Dotenv\Dotenv; + +/** + * A console command to compile .env files into a PHP-optimized file called .env.local.php. + * + * @internal + */ +#[Autoconfigure(bind: ['$projectDir' => '%kernel.project_dir%', '$defaultEnv' => '%kernel.environment%'])] +#[AsCommand(name: 'dotenv:dump', description: 'Compile .env files to .env.local.php')] +final class DotenvDumpCommand extends Command +{ + public function __construct( + private string $projectDir, + private ?string $defaultEnv = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('env', null === $this->defaultEnv ? InputArgument::REQUIRED : InputArgument::OPTIONAL, 'The application environment to dump .env files for - e.g. "prod".'), + ]) + ->addOption('empty', null, InputOption::VALUE_NONE, 'Ignore the content of .env files') + ->setHelp(<<<'EOT' +The %command.name% command compiles .env files into a PHP-optimized file called .env.local.php. + + %command.full_name% +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $config = []; + if (is_file($projectDir = $this->projectDir)) { + $config = ['dotenv_path' => basename($projectDir)]; + $projectDir = \dirname($projectDir); + } + + $composerFile = $projectDir.'/composer.json'; + $config += (is_file($composerFile) ? json_decode(file_get_contents($composerFile), true) : [])['extra']['runtime'] ?? []; + $dotenvPath = $projectDir.'/'.($config['dotenv_path'] ?? '.env'); + $env = $input->getArgument('env') ?? $this->defaultEnv; + $envKey = $config['env_var_name'] ?? 'APP_ENV'; + + if ($input->getOption('empty')) { + $vars = [$envKey => $env]; + } else { + $vars = $this->loadEnv($dotenvPath, $env, $config); + $env = $vars[$envKey]; + } + + $vars = var_export($vars, true); + $vars = <<writeln(sprintf('Successfully dumped .env files in .env.local.php for the %s environment.', $env)); + + return 0; + } + + private function loadEnv(string $dotenvPath, string $env, array $config): array + { + $envKey = $config['env_var_name'] ?? 'APP_ENV'; + $testEnvs = $config['test_envs'] ?? ['test']; + + $dotenv = new Dotenv($envKey); + + $globalsBackup = [$_SERVER, $_ENV]; + unset($_SERVER[$envKey]); + $_ENV = [$envKey => $env]; + $_SERVER['SYMFONY_DOTENV_VARS'] = implode(',', array_keys($_SERVER)); + + try { + $dotenv->loadEnv($dotenvPath, null, 'dev', $testEnvs); + unset($_ENV['SYMFONY_DOTENV_VARS']); + unset($_ENV['SYMFONY_DOTENV_PATH']); + + return $_ENV; + } finally { + [$_SERVER, $_ENV] = $globalsBackup; + } + } +} diff --git a/vendor/symfony/dotenv/Dotenv.php b/vendor/symfony/dotenv/Dotenv.php new file mode 100644 index 0000000..e29051e --- /dev/null +++ b/vendor/symfony/dotenv/Dotenv.php @@ -0,0 +1,569 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv; + +use Symfony\Component\Dotenv\Exception\FormatException; +use Symfony\Component\Dotenv\Exception\FormatExceptionContext; +use Symfony\Component\Dotenv\Exception\PathException; +use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException; +use Symfony\Component\Process\Process; + +/** + * Manages .env files. + * + * @author Fabien Potencier + * @author Kévin Dunglas + */ +final class Dotenv +{ + public const VARNAME_REGEX = '(?i:_?[A-Z][A-Z0-9_]*+)'; + public const STATE_VARNAME = 0; + public const STATE_VALUE = 1; + + private string $path; + private int $cursor; + private int $lineno; + private string $data; + private int $end; + private array $values = []; + private array $prodEnvs = ['prod']; + private bool $usePutenv = false; + + public function __construct( + private string $envKey = 'APP_ENV', + private string $debugKey = 'APP_DEBUG', + ) { + } + + /** + * @return $this + */ + public function setProdEnvs(array $prodEnvs): static + { + $this->prodEnvs = $prodEnvs; + + return $this; + } + + /** + * @param bool $usePutenv If `putenv()` should be used to define environment variables or not. + * Beware that `putenv()` is not thread safe, that's why it's not enabled by default + * + * @return $this + */ + public function usePutenv(bool $usePutenv = true): static + { + $this->usePutenv = $usePutenv; + + return $this; + } + + /** + * Loads one or several .env files. + * + * @param string $path A file to load + * @param string ...$extraPaths A list of additional files to load + * + * @throws FormatException when a file has a syntax error + * @throws PathException when a file does not exist or is not readable + */ + public function load(string $path, string ...$extraPaths): void + { + $this->doLoad(false, \func_get_args()); + } + + /** + * Loads a .env file and the corresponding .env.local, .env.$env and .env.$env.local files if they exist. + * + * .env.local is always ignored in test env because tests should produce the same results for everyone. + * .env.dist is loaded when it exists and .env is not found. + * + * @param string $path A file to load + * @param string|null $envKey The name of the env vars that defines the app env + * @param string $defaultEnv The app env to use when none is defined + * @param array $testEnvs A list of app envs for which .env.local should be ignored + * @param bool $overrideExistingVars Whether existing environment variables set by the system should be overridden + * + * @throws FormatException when a file has a syntax error + * @throws PathException when a file does not exist or is not readable + */ + public function loadEnv(string $path, ?string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void + { + $this->populatePath($path); + + $k = $envKey ?? $this->envKey; + + if (is_file($path) || !is_file($p = "$path.dist")) { + $this->doLoad($overrideExistingVars, [$path]); + } else { + $this->doLoad($overrideExistingVars, [$p]); + } + + if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) { + $this->populate([$k => $env = $defaultEnv], $overrideExistingVars); + } + + if (!\in_array($env, $testEnvs, true) && is_file($p = "$path.local")) { + $this->doLoad($overrideExistingVars, [$p]); + $env = $_SERVER[$k] ?? $_ENV[$k] ?? $env; + } + + if ('local' === $env) { + return; + } + + if (is_file($p = "$path.$env")) { + $this->doLoad($overrideExistingVars, [$p]); + } + + if (is_file($p = "$path.$env.local")) { + $this->doLoad($overrideExistingVars, [$p]); + } + } + + /** + * Loads env vars from .env.local.php if the file exists or from the other .env files otherwise. + * + * This method also configures the APP_DEBUG env var according to the current APP_ENV. + * + * See method loadEnv() for rules related to .env files. + */ + public function bootEnv(string $path, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void + { + $p = $path.'.local.php'; + $env = is_file($p) ? include $p : null; + $k = $this->envKey; + + if (\is_array($env) && ($overrideExistingVars || !isset($env[$k]) || ($_SERVER[$k] ?? $_ENV[$k] ?? $env[$k]) === $env[$k])) { + $this->populatePath($path); + $this->populate($env, $overrideExistingVars); + } else { + $this->loadEnv($path, $k, $defaultEnv, $testEnvs, $overrideExistingVars); + } + + $_SERVER += $_ENV; + + $k = $this->debugKey; + $debug = $_SERVER[$k] ?? !\in_array($_SERVER[$this->envKey], $this->prodEnvs, true); + $_SERVER[$k] = $_ENV[$k] = (int) $debug || (!\is_bool($debug) && filter_var($debug, \FILTER_VALIDATE_BOOL)) ? '1' : '0'; + } + + /** + * Loads one or several .env files and enables override existing vars. + * + * @param string $path A file to load + * @param string ...$extraPaths A list of additional files to load + * + * @throws FormatException when a file has a syntax error + * @throws PathException when a file does not exist or is not readable + */ + public function overload(string $path, string ...$extraPaths): void + { + $this->doLoad(true, \func_get_args()); + } + + /** + * Sets values as environment variables (via putenv, $_ENV, and $_SERVER). + * + * @param array $values An array of env variables + * @param bool $overrideExistingVars Whether existing environment variables set by the system should be overridden + */ + public function populate(array $values, bool $overrideExistingVars = false): void + { + $updateLoadedVars = false; + $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '')); + + foreach ($values as $name => $value) { + $notHttpName = !str_starts_with($name, 'HTTP_'); + if (isset($_SERVER[$name]) && $notHttpName && !isset($_ENV[$name])) { + $_ENV[$name] = $_SERVER[$name]; + } + + // don't check existence with getenv() because of thread safety issues + if (!isset($loadedVars[$name]) && !$overrideExistingVars && isset($_ENV[$name])) { + continue; + } + + if ($this->usePutenv) { + putenv("$name=$value"); + } + + $_ENV[$name] = $value; + if ($notHttpName) { + $_SERVER[$name] = $value; + } + + if (!isset($loadedVars[$name])) { + $loadedVars[$name] = $updateLoadedVars = true; + } + } + + if ($updateLoadedVars) { + unset($loadedVars['']); + $loadedVars = implode(',', array_keys($loadedVars)); + $_ENV['SYMFONY_DOTENV_VARS'] = $_SERVER['SYMFONY_DOTENV_VARS'] = $loadedVars; + + if ($this->usePutenv) { + putenv('SYMFONY_DOTENV_VARS='.$loadedVars); + } + } + } + + /** + * Parses the contents of an .env file. + * + * @param string $data The data to be parsed + * @param string $path The original file name where data where stored (used for more meaningful error messages) + * + * @throws FormatException when a file has a syntax error + */ + public function parse(string $data, string $path = '.env'): array + { + $this->path = $path; + $this->data = str_replace(["\r\n", "\r"], "\n", $data); + $this->lineno = 1; + $this->cursor = 0; + $this->end = \strlen($this->data); + $state = self::STATE_VARNAME; + $this->values = []; + $name = ''; + + $this->skipEmptyLines(); + + while ($this->cursor < $this->end) { + switch ($state) { + case self::STATE_VARNAME: + $name = $this->lexVarname(); + $state = self::STATE_VALUE; + break; + + case self::STATE_VALUE: + $this->values[$name] = $this->lexValue(); + $state = self::STATE_VARNAME; + break; + } + } + + if (self::STATE_VALUE === $state) { + $this->values[$name] = ''; + } + + try { + return $this->values; + } finally { + $this->values = []; + unset($this->path, $this->cursor, $this->lineno, $this->data, $this->end); + } + } + + private function lexVarname(): string + { + // var name + optional export + if (!preg_match('/(export[ \t]++)?('.self::VARNAME_REGEX.')/A', $this->data, $matches, 0, $this->cursor)) { + throw $this->createFormatException('Invalid character in variable name'); + } + $this->moveCursor($matches[0]); + + if ($this->cursor === $this->end || "\n" === $this->data[$this->cursor] || '#' === $this->data[$this->cursor]) { + if ($matches[1]) { + throw $this->createFormatException('Unable to unset an environment variable'); + } + + throw $this->createFormatException('Missing = in the environment variable declaration'); + } + + if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) { + throw $this->createFormatException('Whitespace characters are not supported after the variable name'); + } + + if ('=' !== $this->data[$this->cursor]) { + throw $this->createFormatException('Missing = in the environment variable declaration'); + } + ++$this->cursor; + + return $matches[2]; + } + + private function lexValue(): string + { + if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) { + $this->moveCursor($matches[0]); + $this->skipEmptyLines(); + + return ''; + } + + if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) { + throw $this->createFormatException('Whitespace are not supported before the value'); + } + + $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '')); + unset($loadedVars['']); + $v = ''; + + do { + if ("'" === $this->data[$this->cursor]) { + $len = 0; + + do { + if ($this->cursor + ++$len === $this->end) { + $this->cursor += $len; + + throw $this->createFormatException('Missing quote to end the value'); + } + } while ("'" !== $this->data[$this->cursor + $len]); + + $v .= substr($this->data, 1 + $this->cursor, $len - 1); + $this->cursor += 1 + $len; + } elseif ('"' === $this->data[$this->cursor]) { + $value = ''; + + if (++$this->cursor === $this->end) { + throw $this->createFormatException('Missing quote to end the value'); + } + + while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) { + $value .= $this->data[$this->cursor]; + ++$this->cursor; + + if ($this->cursor === $this->end) { + throw $this->createFormatException('Missing quote to end the value'); + } + } + ++$this->cursor; + $value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value); + $resolvedValue = $value; + $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); + $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); + $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); + $v .= $resolvedValue; + } else { + $value = ''; + $prevChr = $this->data[$this->cursor - 1]; + while ($this->cursor < $this->end && !\in_array($this->data[$this->cursor], ["\n", '"', "'"], true) && !((' ' === $prevChr || "\t" === $prevChr) && '#' === $this->data[$this->cursor])) { + if ('\\' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && ('"' === $this->data[$this->cursor + 1] || "'" === $this->data[$this->cursor + 1])) { + ++$this->cursor; + } + + $value .= $prevChr = $this->data[$this->cursor]; + + if ('$' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && '(' === $this->data[$this->cursor + 1]) { + ++$this->cursor; + $value .= '('.$this->lexNestedExpression().')'; + } + + ++$this->cursor; + } + $value = rtrim($value); + $resolvedValue = $value; + $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); + $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); + $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); + + if ($resolvedValue === $value && preg_match('/\s+/', $value)) { + throw $this->createFormatException('A value containing spaces must be surrounded by quotes'); + } + + $v .= $resolvedValue; + + if ($this->cursor < $this->end && '#' === $this->data[$this->cursor]) { + break; + } + } + } while ($this->cursor < $this->end && "\n" !== $this->data[$this->cursor]); + + $this->skipEmptyLines(); + + return $v; + } + + private function lexNestedExpression(): string + { + ++$this->cursor; + $value = ''; + + while ("\n" !== $this->data[$this->cursor] && ')' !== $this->data[$this->cursor]) { + $value .= $this->data[$this->cursor]; + + if ('(' === $this->data[$this->cursor]) { + $value .= $this->lexNestedExpression().')'; + } + + ++$this->cursor; + + if ($this->cursor === $this->end) { + throw $this->createFormatException('Missing closing parenthesis.'); + } + } + + if ("\n" === $this->data[$this->cursor]) { + throw $this->createFormatException('Missing closing parenthesis.'); + } + + return $value; + } + + private function skipEmptyLines(): void + { + if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + } + } + + private function resolveCommands(string $value, array $loadedVars): string + { + if (!str_contains($value, '$')) { + return $value; + } + + $regex = '/ + (\\\\)? # escaped with a backslash? + \$ + (? + \( # require opening parenthesis + ([^()]|\g)+ # allow any number of non-parens, or balanced parens (by nesting the expression recursively) + \) # require closing paren + ) + /x'; + + return preg_replace_callback($regex, function ($matches) use ($loadedVars) { + if ('\\' === $matches[1]) { + return substr($matches[0], 1); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + throw new \LogicException('Resolving commands is not supported on Windows.'); + } + + if (!class_exists(Process::class)) { + throw new \LogicException('Resolving commands requires the Symfony Process component. Try running "composer require symfony/process".'); + } + + $process = Process::fromShellCommandline('echo '.$matches[0]); + + $env = []; + foreach ($this->values as $name => $value) { + if (isset($loadedVars[$name]) || (!isset($_ENV[$name]) && !(isset($_SERVER[$name]) && !str_starts_with($name, 'HTTP_')))) { + $env[$name] = $value; + } + } + $process->setEnv($env); + + try { + $process->mustRun(); + } catch (ProcessException) { + throw $this->createFormatException(sprintf('Issue expanding a command (%s)', $process->getErrorOutput())); + } + + return rtrim($process->getOutput(), "\n\r"); + }, $value); + } + + private function resolveVariables(string $value, array $loadedVars): string + { + if (!str_contains($value, '$')) { + return $value; + } + + $regex = '/ + (?\\\\*) # escaped with a backslash? + \$ + (?!\() # no opening parenthesis + (?P\{)? # optional brace + (?P'.self::VARNAME_REGEX.')? # var name + (?P:[-=][^\}]++)? # optional default value + (?P\})? # optional closing brace + /x'; + + $value = preg_replace_callback($regex, function ($matches) use ($loadedVars) { + // odd number of backslashes means the $ character is escaped + if (1 === \strlen($matches['backslashes']) % 2) { + return substr($matches[0], 1); + } + + // unescaped $ not followed by variable name + if (!isset($matches['name'])) { + return $matches[0]; + } + + if ('{' === $matches['opening_brace'] && !isset($matches['closing_brace'])) { + throw $this->createFormatException('Unclosed braces on variable expansion'); + } + + $name = $matches['name']; + if (isset($loadedVars[$name]) && isset($this->values[$name])) { + $value = $this->values[$name]; + } elseif (isset($_ENV[$name])) { + $value = $_ENV[$name]; + } elseif (isset($_SERVER[$name]) && !str_starts_with($name, 'HTTP_')) { + $value = $_SERVER[$name]; + } elseif (isset($this->values[$name])) { + $value = $this->values[$name]; + } else { + $value = (string) getenv($name); + } + + if ('' === $value && isset($matches['default_value']) && '' !== $matches['default_value']) { + $unsupportedChars = strpbrk($matches['default_value'], '\'"{$'); + if (false !== $unsupportedChars) { + throw $this->createFormatException(sprintf('Unsupported character "%s" found in the default value of variable "$%s".', $unsupportedChars[0], $name)); + } + + $value = substr($matches['default_value'], 2); + + if ('=' === $matches['default_value'][1]) { + $this->values[$name] = $value; + } + } + + if (!$matches['opening_brace'] && isset($matches['closing_brace'])) { + $value .= '}'; + } + + return $matches['backslashes'].$value; + }, $value); + + return $value; + } + + private function moveCursor(string $text): void + { + $this->cursor += \strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + private function createFormatException(string $message): FormatException + { + return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor)); + } + + private function doLoad(bool $overrideExistingVars, array $paths): void + { + foreach ($paths as $path) { + if (!is_readable($path) || is_dir($path)) { + throw new PathException($path); + } + + $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars); + } + } + + private function populatePath(string $path): void + { + $_ENV['SYMFONY_DOTENV_PATH'] = $_SERVER['SYMFONY_DOTENV_PATH'] = $path; + + if ($this->usePutenv) { + putenv('SYMFONY_DOTENV_PATH='.$path); + } + } +} diff --git a/vendor/symfony/dotenv/Exception/ExceptionInterface.php b/vendor/symfony/dotenv/Exception/ExceptionInterface.php new file mode 100644 index 0000000..140a93f --- /dev/null +++ b/vendor/symfony/dotenv/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * Interface for exceptions. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/dotenv/Exception/FormatException.php b/vendor/symfony/dotenv/Exception/FormatException.php new file mode 100644 index 0000000..81c00bb --- /dev/null +++ b/vendor/symfony/dotenv/Exception/FormatException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * Thrown when a file has a syntax error. + * + * @author Fabien Potencier + */ +final class FormatException extends \LogicException implements ExceptionInterface +{ + public function __construct( + string $message, + private FormatExceptionContext $context, + int $code = 0, + ?\Throwable $previous = null, + ) { + parent::__construct(sprintf("%s in \"%s\" at line %d.\n%s", $message, $context->getPath(), $context->getLineno(), $context->getDetails()), $code, $previous); + } + + public function getContext(): FormatExceptionContext + { + return $this->context; + } +} diff --git a/vendor/symfony/dotenv/Exception/FormatExceptionContext.php b/vendor/symfony/dotenv/Exception/FormatExceptionContext.php new file mode 100644 index 0000000..86ce7e5 --- /dev/null +++ b/vendor/symfony/dotenv/Exception/FormatExceptionContext.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * @author Fabien Potencier + */ +final class FormatExceptionContext +{ + public function __construct( + private string $data, + private string $path, + private int $lineno, + private int $cursor, + ) { + } + + public function getPath(): string + { + return $this->path; + } + + public function getLineno(): int + { + return $this->lineno; + } + + public function getDetails(): string + { + $before = str_replace("\n", '\n', substr($this->data, max(0, $this->cursor - 20), min(20, $this->cursor))); + $after = str_replace("\n", '\n', substr($this->data, $this->cursor, 20)); + + return '...'.$before.$after."...\n".str_repeat(' ', \strlen($before) + 2).'^ line '.$this->lineno.' offset '.$this->cursor; + } +} diff --git a/vendor/symfony/dotenv/Exception/PathException.php b/vendor/symfony/dotenv/Exception/PathException.php new file mode 100644 index 0000000..e432b2e --- /dev/null +++ b/vendor/symfony/dotenv/Exception/PathException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * Thrown when a file does not exist or is not readable. + * + * @author Fabien Potencier + */ +final class PathException extends \RuntimeException implements ExceptionInterface +{ + public function __construct(string $path, int $code = 0, ?\Throwable $previous = null) + { + parent::__construct(sprintf('Unable to read the "%s" environment file.', $path), $code, $previous); + } +} diff --git a/vendor/symfony/dotenv/LICENSE b/vendor/symfony/dotenv/LICENSE new file mode 100644 index 0000000..0223acd --- /dev/null +++ b/vendor/symfony/dotenv/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-present Fabien Potencier + +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. diff --git a/vendor/symfony/dotenv/README.md b/vendor/symfony/dotenv/README.md new file mode 100644 index 0000000..2a1cc02 --- /dev/null +++ b/vendor/symfony/dotenv/README.md @@ -0,0 +1,36 @@ +Dotenv Component +================ + +Symfony Dotenv parses `.env` files to make environment variables stored in them +accessible via `$_SERVER` or `$_ENV`. + +Getting Started +--------------- + +```bash +composer require symfony/dotenv +``` + +```php +use Symfony\Component\Dotenv\Dotenv; + +$dotenv = new Dotenv(); +$dotenv->load(__DIR__.'/.env'); + +// you can also load several files +$dotenv->load(__DIR__.'/.env', __DIR__.'/.env.dev'); + +// overwrites existing env variables +$dotenv->overload(__DIR__.'/.env'); + +// loads .env, .env.local, and .env.$APP_ENV.local or .env.$APP_ENV +$dotenv->loadEnv(__DIR__.'/.env'); +``` + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/dotenv/composer.json b/vendor/symfony/dotenv/composer.json new file mode 100644 index 0000000..34c4718 --- /dev/null +++ b/vendor/symfony/dotenv/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/dotenv", + "type": "library", + "description": "Registers environment variables from a .env file", + "keywords": ["environment", "env", "dotenv"], + "homepage": "https://symfony.com", + "license" : "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Dotenv\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/error-handler/BufferingLogger.php b/vendor/symfony/error-handler/BufferingLogger.php new file mode 100644 index 0000000..6790974 --- /dev/null +++ b/vendor/symfony/error-handler/BufferingLogger.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Psr\Log\AbstractLogger; + +/** + * A buffering logger that stacks logs for later. + * + * @author Nicolas Grekas + */ +class BufferingLogger extends AbstractLogger +{ + private array $logs = []; + + public function log($level, $message, array $context = []): void + { + $this->logs[] = [$level, $message, $context]; + } + + public function cleanLogs(): array + { + $logs = $this->logs; + $this->logs = []; + + return $logs; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + foreach ($this->logs as [$level, $message, $context]) { + if (str_contains($message, '{')) { + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) { + $message = str_replace("{{$key}}", $val, $message); + } elseif ($val instanceof \DateTimeInterface) { + $message = str_replace("{{$key}}", $val->format(\DateTimeInterface::RFC3339), $message); + } elseif (\is_object($val)) { + $message = str_replace("{{$key}}", '[object '.get_debug_type($val).']', $message); + } else { + $message = str_replace("{{$key}}", '['.\gettype($val).']', $message); + } + } + } + + error_log(sprintf('%s [%s] %s', date(\DateTimeInterface::RFC3339), $level, $message)); + } + } +} diff --git a/vendor/symfony/error-handler/CHANGELOG.md b/vendor/symfony/error-handler/CHANGELOG.md new file mode 100644 index 0000000..c3037ad --- /dev/null +++ b/vendor/symfony/error-handler/CHANGELOG.md @@ -0,0 +1,45 @@ +CHANGELOG +========= + +7.1 +--- + + * Increase log level to "error" at least for all PHP errors + +6.4 +--- + + * `FlattenExceptionNormalizer` no longer implements `ContextAwareNormalizerInterface` + +6.3 +--- + + * Display exception properties in the HTML error page + +6.1 +--- + + * Report overridden `@final` constants and properties + * Read environment variable `SYMFONY_IDE` to configure file link format + +5.4 +--- + + * Make `DebugClassLoader` trigger deprecation notices on missing return types + * Add `SYMFONY_PATCH_TYPE_DECLARATIONS='force=2'` mode to `DebugClassLoader` to turn annotations into native return types + +5.2.0 +----- + + * added the ability to set `HtmlErrorRenderer::$template` to a custom template to render when not in debug mode. + +5.1.0 +----- + + * The `HtmlErrorRenderer` and `SerializerErrorRenderer` add `X-Debug-Exception` and `X-Debug-Exception-File` headers in debug mode. + +4.4.0 +----- + + * added the component + * added `ErrorHandler::call()` method utility to turn any PHP error into `\ErrorException` diff --git a/vendor/symfony/error-handler/Debug.php b/vendor/symfony/error-handler/Debug.php new file mode 100644 index 0000000..d54a38c --- /dev/null +++ b/vendor/symfony/error-handler/Debug.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +/** + * Registers all the debug tools. + * + * @author Fabien Potencier + */ +class Debug +{ + public static function enable(): ErrorHandler + { + error_reporting(-1); + + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + ini_set('display_errors', 0); + } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOL) || \ini_get('error_log')) { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + + @ini_set('zend.assertions', 1); + ini_set('assert.active', 1); + ini_set('assert.exception', 1); + + DebugClassLoader::enable(); + + return ErrorHandler::register(new ErrorHandler(new BufferingLogger(), true)); + } +} diff --git a/vendor/symfony/error-handler/DebugClassLoader.php b/vendor/symfony/error-handler/DebugClassLoader.php new file mode 100644 index 0000000..5c8672e --- /dev/null +++ b/vendor/symfony/error-handler/DebugClassLoader.php @@ -0,0 +1,1275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Composer\InstalledVersions; +use Doctrine\Common\Persistence\Proxy as LegacyProxy; +use Doctrine\Persistence\Proxy; +use Mockery\MockInterface; +use Phake\IMock; +use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation; +use PHPUnit\Framework\MockObject\MockObject; +use Prophecy\Prophecy\ProphecySubjectInterface; +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\ErrorHandler\Internal\TentativeTypes; +use Symfony\Component\VarExporter\LazyObjectInterface; + +/** + * Autoloader checking if the class is really defined in the file found. + * + * The ClassLoader will wrap all registered autoloaders + * and will throw an exception if a file is found but does + * not declare the class. + * + * It can also patch classes to turn docblocks into actual return types. + * This behavior is controlled by the SYMFONY_PATCH_TYPE_DECLARATIONS env var, + * which is a url-encoded array with the follow parameters: + * - "force": any value enables deprecation notices - can be any of: + * - "phpdoc" to patch only docblock annotations + * - "2" to add all possible return types + * - "1" to add return types but only to tests/final/internal/private methods + * - "php": the target version of PHP - e.g. "7.1" doesn't generate "object" types + * - "deprecations": "1" to trigger a deprecation notice when a child class misses a + * return type while the parent declares an "@return" annotation + * + * Note that patching doesn't care about any coding style so you'd better to run + * php-cs-fixer after, with rules "phpdoc_trim_consecutive_blank_line_separation" + * and "no_superfluous_phpdoc_tags" enabled typically. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * @author Nicolas Grekas + * @author Guilhem Niot + */ +class DebugClassLoader +{ + private const SPECIAL_RETURN_TYPES = [ + 'void' => 'void', + 'null' => 'null', + 'resource' => 'resource', + 'boolean' => 'bool', + 'true' => 'true', + 'false' => 'false', + 'integer' => 'int', + 'array' => 'array', + 'bool' => 'bool', + 'callable' => 'callable', + 'float' => 'float', + 'int' => 'int', + 'iterable' => 'iterable', + 'object' => 'object', + 'string' => 'string', + 'self' => 'self', + 'parent' => 'parent', + 'mixed' => 'mixed', + 'static' => 'static', + '$this' => 'static', + 'list' => 'array', + 'class-string' => 'string', + 'never' => 'never', + ]; + + private const BUILTIN_RETURN_TYPES = [ + 'void' => true, + 'array' => true, + 'false' => true, + 'bool' => true, + 'callable' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'object' => true, + 'string' => true, + 'self' => true, + 'parent' => true, + 'mixed' => true, + 'static' => true, + 'null' => true, + 'true' => true, + 'never' => true, + ]; + + private const MAGIC_METHODS = [ + '__isset' => 'bool', + '__sleep' => 'array', + '__toString' => 'string', + '__debugInfo' => 'array', + '__serialize' => 'array', + '__set' => 'void', + '__unset' => 'void', + '__unserialize' => 'void', + '__wakeup' => 'void', + ]; + + /** + * @var callable + */ + private $classLoader; + private bool $isFinder; + private array $loaded = []; + private array $patchTypes = []; + + private static int $caseCheck; + private static array $checkedClasses = []; + private static array $final = []; + private static array $finalMethods = []; + private static array $finalProperties = []; + private static array $finalConstants = []; + private static array $deprecated = []; + private static array $internal = []; + private static array $internalMethods = []; + private static array $annotatedParameters = []; + private static array $darwinCache = ['/' => ['/', []]]; + private static array $method = []; + private static array $returnTypes = []; + private static array $methodTraits = []; + private static array $fileOffsets = []; + + public function __construct(callable $classLoader) + { + $this->classLoader = $classLoader; + $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile'); + parse_str($_ENV['SYMFONY_PATCH_TYPE_DECLARATIONS'] ?? $_SERVER['SYMFONY_PATCH_TYPE_DECLARATIONS'] ?? getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes); + $this->patchTypes += [ + 'force' => null, + 'php' => \PHP_MAJOR_VERSION.'.'.\PHP_MINOR_VERSION, + 'deprecations' => true, + ]; + + if ('phpdoc' === $this->patchTypes['force']) { + $this->patchTypes['force'] = 'docblock'; + } + + if (!isset(self::$caseCheck)) { + $file = is_file(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR); + $i = strrpos($file, \DIRECTORY_SEPARATOR); + $dir = substr($file, 0, 1 + $i); + $file = substr($file, 1 + $i); + $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); + $test = realpath($dir.$test); + + if (false === $test || false === $i) { + // filesystem is case-sensitive + self::$caseCheck = 0; + } elseif (str_ends_with($test, $file)) { + // filesystem is case-insensitive and realpath() normalizes the case of characters + self::$caseCheck = 1; + } elseif ('Darwin' === \PHP_OS_FAMILY) { + // on MacOSX, HFS+ is case-insensitive but realpath() doesn't normalize the case of characters + self::$caseCheck = 2; + } else { + // filesystem case checks failed, fallback to disabling them + self::$caseCheck = 0; + } + } + } + + public function getClassLoader(): callable + { + return $this->classLoader; + } + + /** + * Wraps all autoloaders. + */ + public static function enable(): void + { + // Ensures we don't hit https://bugs.php.net/42098 + class_exists(ErrorHandler::class); + class_exists(\Psr\Log\LogLevel::class); + + if (!\is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (!\is_array($function) || !$function[0] instanceof self) { + $function = [new static($function), 'loadClass']; + } + + spl_autoload_register($function); + } + } + + /** + * Disables the wrapping. + */ + public static function disable(): void + { + if (!\is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (\is_array($function) && $function[0] instanceof self) { + $function = $function[0]->getClassLoader(); + } + + spl_autoload_register($function); + } + } + + public static function checkClasses(): bool + { + if (!\is_array($functions = spl_autoload_functions())) { + return false; + } + + $loader = null; + + foreach ($functions as $function) { + if (\is_array($function) && $function[0] instanceof self) { + $loader = $function[0]; + break; + } + } + + if (null === $loader) { + return false; + } + + static $offsets = [ + 'get_declared_interfaces' => 0, + 'get_declared_traits' => 0, + 'get_declared_classes' => 0, + ]; + + foreach ($offsets as $getSymbols => $i) { + $symbols = $getSymbols(); + + for (; $i < \count($symbols); ++$i) { + if (!is_subclass_of($symbols[$i], MockObject::class) + && !is_subclass_of($symbols[$i], ProphecySubjectInterface::class) + && !is_subclass_of($symbols[$i], Proxy::class) + && !is_subclass_of($symbols[$i], ProxyInterface::class) + && !is_subclass_of($symbols[$i], LazyObjectInterface::class) + && !is_subclass_of($symbols[$i], LegacyProxy::class) + && !is_subclass_of($symbols[$i], MockInterface::class) + && !is_subclass_of($symbols[$i], IMock::class) + ) { + $loader->checkClass($symbols[$i]); + } + } + + $offsets[$getSymbols] = $i; + } + + return true; + } + + public function findFile(string $class): ?string + { + return $this->isFinder ? ($this->classLoader[0]->findFile($class) ?: null) : null; + } + + /** + * Loads the given class or interface. + * + * @throws \RuntimeException + */ + public function loadClass(string $class): void + { + $e = error_reporting(error_reporting() | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR); + + try { + if ($this->isFinder && !isset($this->loaded[$class])) { + $this->loaded[$class] = true; + if (!$file = $this->classLoader[0]->findFile($class) ?: '') { + // no-op + } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) { + include $file; + + return; + } elseif (false === include $file) { + return; + } + } else { + ($this->classLoader)($class); + $file = ''; + } + } finally { + error_reporting($e); + } + + $this->checkClass($class, $file); + } + + private function checkClass(string $class, ?string $file = null): void + { + $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + + if (null !== $file && $class && '\\' === $class[0]) { + $class = substr($class, 1); + } + + if ($exists) { + if (isset(self::$checkedClasses[$class])) { + return; + } + self::$checkedClasses[$class] = true; + + $refl = new \ReflectionClass($class); + if (null === $file && $refl->isInternal()) { + return; + } + $name = $refl->getName(); + + if ($name !== $class && 0 === strcasecmp($name, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); + } + + $deprecations = $this->checkAnnotations($refl, $name); + + foreach ($deprecations as $message) { + @trigger_error($message, \E_USER_DEPRECATED); + } + } + + if (!$file) { + return; + } + + if (!$exists) { + if (str_contains($class, '/')) { + throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); + } + + throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); + } + + if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2])); + } + } + + public function checkAnnotations(\ReflectionClass $refl, string $class): array + { + if ( + 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7' === $class + || 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6' === $class + ) { + return []; + } + $deprecations = []; + + $className = str_contains($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; + + // Don't trigger deprecations for classes in the same vendor + if ($class !== $className) { + $vendor = preg_match('/^namespace ([^;\\\\\s]++)[;\\\\]/m', @file_get_contents($refl->getFileName()), $vendor) ? $vendor[1].'\\' : ''; + $vendorLen = \strlen($vendor); + } elseif (2 > $vendorLen = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) { + $vendorLen = 0; + $vendor = ''; + } else { + $vendor = str_replace('_', '\\', substr($class, 0, $vendorLen)); + } + + $parent = get_parent_class($class) ?: null; + self::$returnTypes[$class] = []; + $classIsTemplate = false; + + // Detect annotations on the class + if ($doc = $this->parsePhpDoc($refl)) { + $classIsTemplate = isset($doc['template']) || isset($doc['template-covariant']); + + foreach (['final', 'deprecated', 'internal'] as $annotation) { + if (null !== $description = $doc[$annotation][0] ?? null) { + self::${$annotation}[$class] = '' !== $description ? ' '.$description.(preg_match('/[.!]$/', $description) ? '' : '.') : '.'; + } + } + + if ($refl->isInterface() && isset($doc['method'])) { + foreach ($doc['method'] as $name => [$static, $returnType, $signature, $description]) { + self::$method[$class][] = [$class, $static, $returnType, $name.$signature, $description]; + + if ('' !== $returnType) { + $this->setReturnType($returnType, $refl->name, $name, $refl->getFileName(), $parent); + } + } + } + } + + $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent); + if ($parent) { + $parentAndOwnInterfaces[$parent] = $parent; + + if (!isset(self::$checkedClasses[$parent])) { + $this->checkClass($parent); + } + + if (isset(self::$final[$parent])) { + $deprecations[] = sprintf('The "%s" class is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $className); + } + } + + // Detect if the parent is annotated + foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) { + if (!isset(self::$checkedClasses[$use])) { + $this->checkClass($use); + } + if (isset(self::$deprecated[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) && !isset(self::$deprecated[$class])) { + $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait'); + $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses'); + + $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s', $className, $type, $verb, $use, self::$deprecated[$use]); + } + if (isset(self::$internal[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen)) { + $deprecations[] = sprintf('The "%s" %s is considered internal%s It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $className); + } + if (isset(self::$method[$use])) { + if ($refl->isAbstract()) { + if (isset(self::$method[$class])) { + self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]); + } else { + self::$method[$class] = self::$method[$use]; + } + } elseif (!$refl->isInterface()) { + if (!strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) + && str_starts_with($className, 'Symfony\\') + && (!class_exists(InstalledVersions::class) + || 'symfony/symfony' !== InstalledVersions::getRootPackage()['name']) + ) { + // skip "same vendor" @method deprecations for Symfony\* classes unless symfony/symfony is being tested + continue; + } + $hasCall = $refl->hasMethod('__call'); + $hasStaticCall = $refl->hasMethod('__callStatic'); + foreach (self::$method[$use] as [$interface, $static, $returnType, $name, $description]) { + if ($static ? $hasStaticCall : $hasCall) { + continue; + } + $realName = substr($name, 0, strpos($name, '(')); + if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) { + $deprecations[] = sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '').$interface, $name, $returnType ? ': '.$returnType : '', null === $description ? '.' : ': '.$description); + } + } + } + } + } + + if (trait_exists($class)) { + $file = $refl->getFileName(); + + foreach ($refl->getMethods() as $method) { + if ($method->getFileName() === $file) { + self::$methodTraits[$file][$method->getStartLine()] = $class; + } + } + + return $deprecations; + } + + // Inherit @final, @internal, @param and @return annotations for methods + self::$finalMethods[$class] = []; + self::$internalMethods[$class] = []; + self::$annotatedParameters[$class] = []; + self::$finalProperties[$class] = []; + self::$finalConstants[$class] = []; + foreach ($parentAndOwnInterfaces as $use) { + foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes', 'finalProperties', 'finalConstants'] as $property) { + if (isset(self::${$property}[$use])) { + self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use]; + } + } + + if (null !== (TentativeTypes::RETURN_TYPES[$use] ?? null)) { + foreach (TentativeTypes::RETURN_TYPES[$use] as $method => $returnType) { + $returnType = explode('|', $returnType); + foreach ($returnType as $i => $t) { + if ('?' !== $t && !isset(self::BUILTIN_RETURN_TYPES[$t])) { + $returnType[$i] = '\\'.$t; + } + } + $returnType = implode('|', $returnType); + + self::$returnTypes[$class] += [$method => [$returnType, str_starts_with($returnType, '?') ? substr($returnType, 1).'|null' : $returnType, $use, '']]; + } + } + } + + foreach ($refl->getMethods() as $method) { + if ($method->class !== $class) { + continue; + } + + if (null === $ns = self::$methodTraits[$method->getFileName()][$method->getStartLine()] ?? null) { + $ns = $vendor; + $len = $vendorLen; + } elseif (2 > $len = 1 + (strpos($ns, '\\') ?: strpos($ns, '_'))) { + $len = 0; + $ns = ''; + } else { + $ns = str_replace('_', '\\', substr($ns, 0, $len)); + } + + if ($parent && isset(self::$finalMethods[$parent][$method->name])) { + [$declaringClass, $message] = self::$finalMethods[$parent][$method->name]; + $deprecations[] = sprintf('The "%s::%s()" method is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className); + } + + if (isset(self::$internalMethods[$class][$method->name])) { + [$declaringClass, $message] = self::$internalMethods[$class][$method->name]; + if (strncmp($ns, $declaringClass, $len)) { + $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className); + } + } + + // To read method annotations + $doc = $this->parsePhpDoc($method); + + if (($classIsTemplate || isset($doc['template']) || isset($doc['template-covariant'])) && $method->hasReturnType()) { + unset($doc['return']); + } + + if (isset(self::$annotatedParameters[$class][$method->name])) { + $definedParameters = []; + foreach ($method->getParameters() as $parameter) { + $definedParameters[$parameter->name] = true; + } + + foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) { + if (!isset($definedParameters[$parameterName]) && !isset($doc['param'][$parameterName])) { + $deprecations[] = sprintf($deprecation, $className); + } + } + } + + $forcePatchTypes = $this->patchTypes['force']; + + if ($canAddReturnType = null !== $forcePatchTypes && !str_contains($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) { + $this->patchTypes['force'] = $forcePatchTypes ?: 'docblock'; + + $canAddReturnType = 2 === (int) $forcePatchTypes + || false !== stripos($method->getFileName(), \DIRECTORY_SEPARATOR.'Tests'.\DIRECTORY_SEPARATOR) + || $refl->isFinal() + || $method->isFinal() + || $method->isPrivate() + || ('.' === (self::$internal[$class] ?? null) && !$refl->isAbstract()) + || '.' === (self::$final[$class] ?? null) + || '' === ($doc['final'][0] ?? null) + || '' === ($doc['internal'][0] ?? null) + ; + } + + if (null !== ($returnType = self::$returnTypes[$class][$method->name] ?? null) && 'docblock' === $this->patchTypes['force'] && !$method->hasReturnType() && isset(TentativeTypes::RETURN_TYPES[$returnType[2]][$method->name])) { + $this->patchReturnTypeWillChange($method); + } + + if (null !== ($returnType ??= self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !isset($doc['return'])) { + [$normalizedType, $returnType, $declaringClass, $declaringFile] = \is_string($returnType) ? [$returnType, $returnType, '', ''] : $returnType; + + if ($canAddReturnType && 'docblock' !== $this->patchTypes['force']) { + $this->patchMethod($method, $returnType, $declaringFile, $normalizedType); + } + if (!isset($doc['deprecated']) && strncmp($ns, $declaringClass, $len)) { + if ('docblock' === $this->patchTypes['force']) { + $this->patchMethod($method, $returnType, $declaringFile, $normalizedType); + } elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) { + $deprecations[] = sprintf('Method "%s::%s()" might add "%s" as a native return type declaration in the future. Do the same in %s "%s" now to avoid errors or add an explicit @return annotation to suppress this message.', $declaringClass, $method->name, $normalizedType, interface_exists($declaringClass) ? 'implementation' : 'child class', $className); + } + } + } + + if (!$doc) { + $this->patchTypes['force'] = $forcePatchTypes; + + continue; + } + + if (isset($doc['return'])) { + $this->setReturnType($doc['return'] ?? self::MAGIC_METHODS[$method->name], $method->class, $method->name, $method->getFileName(), $parent, $method->getReturnType()); + + if (isset(self::$returnTypes[$class][$method->name][0]) && $canAddReturnType) { + $this->fixReturnStatements($method, self::$returnTypes[$class][$method->name][0]); + } + + if ($method->isPrivate()) { + unset(self::$returnTypes[$class][$method->name]); + } + } + + $this->patchTypes['force'] = $forcePatchTypes; + + if ($method->isPrivate()) { + continue; + } + + $finalOrInternal = false; + + foreach (['final', 'internal'] as $annotation) { + if (null !== $description = $doc[$annotation][0] ?? null) { + self::${$annotation.'Methods'}[$class][$method->name] = [$class, '' !== $description ? ' '.$description.(preg_match('/[[:punct:]]$/', $description) ? '' : '.') : '.']; + $finalOrInternal = true; + } + } + + if ($finalOrInternal || $method->isConstructor() || !isset($doc['param']) || StatelessInvocation::class === $class) { + continue; + } + if (!isset(self::$annotatedParameters[$class][$method->name])) { + $definedParameters = []; + foreach ($method->getParameters() as $parameter) { + $definedParameters[$parameter->name] = true; + } + } + foreach ($doc['param'] as $parameterName => $parameterType) { + if (!isset($definedParameters[$parameterName])) { + self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its %s "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, interface_exists($className) ? 'interface' : 'parent class', $className); + } + } + } + + $finals = isset(self::$final[$class]) || $refl->isFinal() ? [] : [ + 'finalConstants' => $refl->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC | \ReflectionClassConstant::IS_PROTECTED), + 'finalProperties' => $refl->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED), + ]; + foreach ($finals as $type => $reflectors) { + foreach ($reflectors as $r) { + if ($r->class !== $class) { + continue; + } + + $doc = $this->parsePhpDoc($r); + + foreach ($parentAndOwnInterfaces as $use) { + if (isset(self::${$type}[$use][$r->name]) && !isset($doc['deprecated']) && ('finalConstants' === $type || substr($use, 0, strrpos($use, '\\')) !== substr($use, 0, strrpos($class, '\\')))) { + $msg = 'finalConstants' === $type ? '%s" constant' : '$%s" property'; + $deprecations[] = sprintf('The "%s::'.$msg.' is considered final. You should not override it in "%s".', self::${$type}[$use][$r->name], $r->name, $class); + } + } + + if (isset($doc['final']) || ('finalProperties' === $type && str_starts_with($class, 'Symfony\\') && !$r->hasType())) { + self::${$type}[$class][$r->name] = $class; + } + } + } + + return $deprecations; + } + + public function checkCase(\ReflectionClass $refl, string $file, string $class): ?array + { + $real = explode('\\', $class.strrchr($file, '.')); + $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file)); + + $i = \count($tail) - 1; + $j = \count($real) - 1; + + while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { + --$i; + --$j; + } + + array_splice($tail, 0, $i + 1); + + if (!$tail) { + return null; + } + + $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail); + $tailLen = \strlen($tail); + $real = $refl->getFileName(); + + if (2 === self::$caseCheck) { + $real = $this->darwinRealpath($real); + } + + if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) + && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) + ) { + return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)]; + } + + return null; + } + + /** + * `realpath` on MacOSX doesn't normalize the case of characters. + */ + private function darwinRealpath(string $real): string + { + $i = 1 + strrpos($real, '/'); + $file = substr($real, $i); + $real = substr($real, 0, $i); + + if (isset(self::$darwinCache[$real])) { + $kDir = $real; + } else { + $kDir = strtolower($real); + + if (isset(self::$darwinCache[$kDir])) { + $real = self::$darwinCache[$kDir][0]; + } else { + $dir = getcwd(); + + if (!@chdir($real)) { + return $real.$file; + } + + $real = getcwd().'/'; + chdir($dir); + + $dir = $real; + $k = $kDir; + $i = \strlen($dir) - 1; + while (!isset(self::$darwinCache[$k])) { + self::$darwinCache[$k] = [$dir, []]; + self::$darwinCache[$dir] = &self::$darwinCache[$k]; + + while ('/' !== $dir[--$i]) { + } + $k = substr($k, 0, ++$i); + $dir = substr($dir, 0, $i--); + } + } + } + + $dirFiles = self::$darwinCache[$kDir][1]; + + if (!isset($dirFiles[$file]) && str_ends_with($file, ') : eval()\'d code')) { + // Get the file name from "file_name.php(123) : eval()'d code" + $file = substr($file, 0, strrpos($file, '(', -17)); + } + + if (isset($dirFiles[$file])) { + return $real.$dirFiles[$file]; + } + + $kFile = strtolower($file); + + if (!isset($dirFiles[$kFile])) { + foreach (scandir($real, 2) as $f) { + if ('.' !== $f[0]) { + $dirFiles[$f] = $f; + if ($f === $file) { + $kFile = $file; + } elseif ($f !== $k = strtolower($f)) { + $dirFiles[$k] = $f; + } + } + } + self::$darwinCache[$kDir][1] = $dirFiles; + } + + return $real.$dirFiles[$kFile]; + } + + /** + * `class_implements` includes interfaces from the parents so we have to manually exclude them. + * + * @return string[] + */ + private function getOwnInterfaces(string $class, ?string $parent): array + { + $ownInterfaces = class_implements($class, false); + + if ($parent) { + foreach (class_implements($parent, false) as $interface) { + unset($ownInterfaces[$interface]); + } + } + + foreach ($ownInterfaces as $interface) { + foreach (class_implements($interface) as $interface) { + unset($ownInterfaces[$interface]); + } + } + + return $ownInterfaces; + } + + private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, ?\ReflectionType $returnType = null): void + { + if ('__construct' === $method) { + return; + } + + if ('null' === $types) { + self::$returnTypes[$class][$method] = ['null', 'null', $class, $filename]; + + return; + } + + if ($nullable = str_starts_with($types, 'null|')) { + $types = substr($types, 5); + } elseif ($nullable = str_ends_with($types, '|null')) { + $types = substr($types, 0, -5); + } + $arrayType = ['array' => 'array']; + $typesMap = []; + $glue = str_contains($types, '&') ? '&' : '|'; + foreach (explode($glue, $types) as $t) { + $t = self::SPECIAL_RETURN_TYPES[strtolower($t)] ?? $t; + $typesMap[$this->normalizeType($t, $class, $parent, $returnType)][$t] = $t; + } + + if (isset($typesMap['array'])) { + if (isset($typesMap['Traversable']) || isset($typesMap['\Traversable'])) { + $typesMap['iterable'] = $arrayType !== $typesMap['array'] ? $typesMap['array'] : ['iterable']; + unset($typesMap['array'], $typesMap['Traversable'], $typesMap['\Traversable']); + } elseif ($arrayType !== $typesMap['array'] && isset(self::$returnTypes[$class][$method]) && !$returnType) { + return; + } + } + + if (isset($typesMap['array']) && isset($typesMap['iterable'])) { + if ($arrayType !== $typesMap['array']) { + $typesMap['iterable'] = $typesMap['array']; + } + unset($typesMap['array']); + } + + $iterable = $object = true; + foreach ($typesMap as $n => $t) { + if ('null' !== $n) { + $iterable = $iterable && (\in_array($n, ['array', 'iterable']) || str_contains($n, 'Iterator')); + $object = $object && (\in_array($n, ['callable', 'object', '$this', 'static']) || !isset(self::SPECIAL_RETURN_TYPES[$n])); + } + } + + $phpTypes = []; + $docTypes = []; + + foreach ($typesMap as $n => $t) { + if ('null' === $n) { + $nullable = true; + continue; + } + + $docTypes[] = $t; + + if ('mixed' === $n || 'void' === $n) { + $nullable = false; + $phpTypes = ['' => $n]; + continue; + } + + if ('resource' === $n) { + // there is no native type for "resource" + return; + } + + if (!preg_match('/^(?:\\\\?[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)+$/', $n)) { + // exclude any invalid PHP class name (e.g. `Cookie::SAMESITE_*`) + continue; + } + + if (!isset($phpTypes[''])) { + $phpTypes[] = $n; + } + } + $docTypes = array_merge([], ...$docTypes); + + if (!$phpTypes) { + return; + } + + if (1 < \count($phpTypes)) { + if ($iterable && '8.0' > $this->patchTypes['php']) { + $phpTypes = $docTypes = ['iterable']; + } elseif ($object && 'object' === $this->patchTypes['force']) { + $phpTypes = $docTypes = ['object']; + } elseif ('8.0' > $this->patchTypes['php']) { + // ignore multi-types return declarations + return; + } + } + + $phpType = sprintf($nullable ? (1 < \count($phpTypes) ? '%s|null' : '?%s') : '%s', implode($glue, $phpTypes)); + $docType = sprintf($nullable ? '%s|null' : '%s', implode($glue, $docTypes)); + + self::$returnTypes[$class][$method] = [$phpType, $docType, $class, $filename]; + } + + private function normalizeType(string $type, string $class, ?string $parent, ?\ReflectionType $returnType): string + { + if (isset(self::SPECIAL_RETURN_TYPES[$lcType = strtolower($type)])) { + if ('parent' === $lcType = self::SPECIAL_RETURN_TYPES[$lcType]) { + $lcType = null !== $parent ? '\\'.$parent : 'parent'; + } elseif ('self' === $lcType) { + $lcType = '\\'.$class; + } + + return $lcType; + } + + // We could resolve "use" statements to return the FQDN + // but this would be too expensive for a runtime checker + + if (!str_ends_with($type, '[]')) { + return $type; + } + + if ($returnType instanceof \ReflectionNamedType) { + $type = $returnType->getName(); + + if ('mixed' !== $type) { + return isset(self::SPECIAL_RETURN_TYPES[$type]) ? $type : '\\'.$type; + } + } + + return 'array'; + } + + /** + * Utility method to add #[ReturnTypeWillChange] where php triggers deprecations. + */ + private function patchReturnTypeWillChange(\ReflectionMethod $method): void + { + if (\count($method->getAttributes(\ReturnTypeWillChange::class))) { + return; + } + + if (!is_file($file = $method->getFileName())) { + return; + } + + $fileOffset = self::$fileOffsets[$file] ?? 0; + + $code = file($file); + + $startLine = $method->getStartLine() + $fileOffset - 2; + + if (false !== stripos($code[$startLine], 'ReturnTypeWillChange')) { + return; + } + + $code[$startLine] .= " #[\\ReturnTypeWillChange]\n"; + self::$fileOffsets[$file] = 1 + $fileOffset; + file_put_contents($file, $code); + } + + /** + * Utility method to add @return annotations to the Symfony code-base where it triggers self-deprecations. + */ + private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType): void + { + static $patchedMethods = []; + static $useStatements = []; + + if (!is_file($file = $method->getFileName()) || isset($patchedMethods[$file][$startLine = $method->getStartLine()])) { + return; + } + + $patchedMethods[$file][$startLine] = true; + $fileOffset = self::$fileOffsets[$file] ?? 0; + $startLine += $fileOffset - 2; + if ($nullable = str_ends_with($returnType, '|null')) { + $returnType = substr($returnType, 0, -5); + } + $glue = str_contains($returnType, '&') ? '&' : '|'; + $returnType = explode($glue, $returnType); + $code = file($file); + + foreach ($returnType as $i => $type) { + if (preg_match('/((?:\[\])+)$/', $type, $m)) { + $type = substr($type, 0, -\strlen($m[1])); + $format = '%s'.$m[1]; + } else { + $format = null; + } + + if (isset(self::SPECIAL_RETURN_TYPES[$type]) || ('\\' === $type[0] && !$p = strrpos($type, '\\', 1))) { + continue; + } + + [$namespace, $useOffset, $useMap] = $useStatements[$file] ??= self::getUseStatements($file); + + if ('\\' !== $type[0]) { + [$declaringNamespace, , $declaringUseMap] = $useStatements[$declaringFile] ??= self::getUseStatements($declaringFile); + + $p = strpos($type, '\\', 1); + $alias = $p ? substr($type, 0, $p) : $type; + + if (isset($declaringUseMap[$alias])) { + $type = '\\'.$declaringUseMap[$alias].($p ? substr($type, $p) : ''); + } else { + $type = '\\'.$declaringNamespace.$type; + } + + $p = strrpos($type, '\\', 1); + } + + $alias = substr($type, 1 + $p); + $type = substr($type, 1); + + if (!isset($useMap[$alias]) && (class_exists($c = $namespace.$alias) || interface_exists($c) || trait_exists($c))) { + $useMap[$alias] = $c; + } + + if (!isset($useMap[$alias])) { + $useStatements[$file][2][$alias] = $type; + $code[$useOffset] = "use $type;\n".$code[$useOffset]; + ++$fileOffset; + } elseif ($useMap[$alias] !== $type) { + $alias .= 'FIXME'; + $useStatements[$file][2][$alias] = $type; + $code[$useOffset] = "use $type as $alias;\n".$code[$useOffset]; + ++$fileOffset; + } + + $returnType[$i] = null !== $format ? sprintf($format, $alias) : $alias; + } + + if ('docblock' === $this->patchTypes['force'] || ('object' === $normalizedType && '7.1' === $this->patchTypes['php'])) { + $returnType = implode($glue, $returnType).($nullable ? '|null' : ''); + + if (str_contains($code[$startLine], '#[')) { + --$startLine; + } + + if ($method->getDocComment()) { + $code[$startLine] = " * @return $returnType\n".$code[$startLine]; + } else { + $code[$startLine] .= <<fixReturnStatements($method, $normalizedType); + } + + private static function getUseStatements(string $file): array + { + $namespace = ''; + $useMap = []; + $useOffset = 0; + + if (!is_file($file)) { + return [$namespace, $useOffset, $useMap]; + } + + $file = file($file); + + for ($i = 0; $i < \count($file); ++$i) { + if (preg_match('/^(class|interface|trait|abstract) /', $file[$i])) { + break; + } + + if (str_starts_with($file[$i], 'namespace ')) { + $namespace = substr($file[$i], \strlen('namespace '), -2).'\\'; + $useOffset = $i + 2; + } + + if (str_starts_with($file[$i], 'use ')) { + $useOffset = $i; + + for (; str_starts_with($file[$i], 'use '); ++$i) { + $u = explode(' as ', substr($file[$i], 4, -2), 2); + + if (1 === \count($u)) { + $p = strrpos($u[0], '\\'); + $useMap[substr($u[0], false !== $p ? 1 + $p : 0)] = $u[0]; + } else { + $useMap[$u[1]] = $u[0]; + } + } + + break; + } + } + + return [$namespace, $useOffset, $useMap]; + } + + private function fixReturnStatements(\ReflectionMethod $method, string $returnType): void + { + if ('docblock' !== $this->patchTypes['force']) { + if ('7.1' === $this->patchTypes['php'] && 'object' === ltrim($returnType, '?')) { + return; + } + + if ('7.4' > $this->patchTypes['php'] && $method->hasReturnType()) { + return; + } + + if ('8.0' > $this->patchTypes['php'] && (str_contains($returnType, '|') || \in_array($returnType, ['mixed', 'static'], true))) { + return; + } + + if ('8.1' > $this->patchTypes['php'] && str_contains($returnType, '&')) { + return; + } + } + + if (!is_file($file = $method->getFileName())) { + return; + } + + $fixedCode = $code = file($file); + $i = (self::$fileOffsets[$file] ?? 0) + $method->getStartLine(); + + if ('?' !== $returnType && 'docblock' !== $this->patchTypes['force']) { + $fixedCode[$i - 1] = preg_replace('/\)(?::[^;\n]++)?(;?\n)/', "): $returnType\\1", $code[$i - 1]); + } + + $end = $method->isGenerator() ? $i : $method->getEndLine(); + $inClosure = false; + $braces = 0; + for (; $i < $end; ++$i) { + if (!$inClosure) { + $inClosure = false !== strpos($code[$i], 'function ('); + } + + if ($inClosure) { + $braces += substr_count($code[$i], '{') - substr_count($code[$i], '}'); + $inClosure = $braces > 0; + + continue; + } + + if ('void' === $returnType) { + $fixedCode[$i] = str_replace(' return null;', ' return;', $code[$i]); + } elseif ('mixed' === $returnType || '?' === $returnType[0]) { + $fixedCode[$i] = str_replace(' return;', ' return null;', $code[$i]); + } else { + $fixedCode[$i] = str_replace(' return;', " return $returnType!?;", $code[$i]); + } + } + + if ($fixedCode !== $code) { + file_put_contents($file, $fixedCode); + } + } + + /** + * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector + */ + private function parsePhpDoc(\Reflector $reflector): array + { + if (!$doc = $reflector->getDocComment()) { + return []; + } + + $tagName = ''; + $tagContent = ''; + + $tags = []; + + foreach (explode("\n", substr($doc, 3, -2)) as $line) { + $line = ltrim($line); + $line = ltrim($line, '*'); + + if ('' === $line = trim($line)) { + if ('' !== $tagName) { + $tags[$tagName][] = $tagContent; + } + $tagName = $tagContent = ''; + continue; + } + + if ('@' === $line[0]) { + if ('' !== $tagName) { + $tags[$tagName][] = $tagContent; + $tagContent = ''; + } + + if (preg_match('{^@([-a-zA-Z0-9_:]++)(\s|$)}', $line, $m)) { + $tagName = $m[1]; + $tagContent = str_replace("\t", ' ', ltrim(substr($line, 2 + \strlen($tagName)))); + } else { + $tagName = ''; + } + } elseif ('' !== $tagName) { + $tagContent .= ' '.str_replace("\t", ' ', $line); + } + } + + if ('' !== $tagName) { + $tags[$tagName][] = $tagContent; + } + + foreach ($tags['method'] ?? [] as $i => $method) { + unset($tags['method'][$i]); + + $parts = preg_split('{(\s++|\((?:[^()]*+|(?R))*\)(?: *: *[^ ]++)?|<(?:[^<>]*+|(?R))*>|\{(?:[^{}]*+|(?R))*\})}', $method, -1, \PREG_SPLIT_DELIM_CAPTURE); + $returnType = ''; + $static = 'static' === $parts[0]; + + for ($i = $static ? 2 : 0; null !== $p = $parts[$i] ?? null; $i += 2) { + if (\in_array($p, ['', '|', '&', 'callable'], true) || \in_array(substr($returnType, -1), ['|', '&'], true)) { + $returnType .= trim($parts[$i - 1] ?? '').$p; + continue; + } + + $signature = '(' === ($parts[$i + 1][0] ?? '(') ? $parts[$i + 1] ?? '()' : null; + + if (null === $signature && '' === $returnType) { + $returnType = $p; + continue; + } + + if ($static && 2 === $i) { + $static = false; + $returnType = 'static'; + } + + if (\in_array($description = trim(implode('', \array_slice($parts, 2 + $i))), ['', '.'], true)) { + $description = null; + } elseif (!preg_match('/[.!]$/', $description)) { + $description .= '.'; + } + + $tags['method'][$p] = [$static, $returnType, $signature ?? '()', $description]; + break; + } + } + + foreach ($tags['param'] ?? [] as $i => $param) { + unset($tags['param'][$i]); + + if (\strlen($param) !== strcspn($param, '<{(')) { + $param = preg_replace('{\(([^()]*+|(?R))*\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\{([^{}]*+|(?R))*\}}', '', $param); + } + + if (false === $i = strpos($param, '$')) { + continue; + } + + $type = 0 === $i ? '' : rtrim(substr($param, 0, $i), ' &'); + $param = substr($param, 1 + $i, (strpos($param, ' ', $i) ?: (1 + $i + \strlen($param))) - $i - 1); + + $tags['param'][$param] = $type; + } + + foreach (['var', 'return'] as $k) { + if (null === $v = $tags[$k][0] ?? null) { + continue; + } + if (\strlen($v) !== strcspn($v, '<{(')) { + $v = preg_replace('{\(([^()]*+|(?R))*\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\{([^{}]*+|(?R))*\}}', '', $v); + } + + $tags[$k] = substr($v, 0, strpos($v, ' ') ?: \strlen($v)) ?: null; + } + + return $tags; + } +} diff --git a/vendor/symfony/error-handler/Error/ClassNotFoundError.php b/vendor/symfony/error-handler/Error/ClassNotFoundError.php new file mode 100644 index 0000000..8a17745 --- /dev/null +++ b/vendor/symfony/error-handler/Error/ClassNotFoundError.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Error; + +class ClassNotFoundError extends \Error +{ + public function __construct(string $message, \Throwable $previous) + { + parent::__construct($message, $previous->getCode(), $previous->getPrevious()); + + foreach ([ + 'file' => $previous->getFile(), + 'line' => $previous->getLine(), + 'trace' => $previous->getTrace(), + ] as $property => $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setValue($this, $value); + } + } +} diff --git a/vendor/symfony/error-handler/Error/FatalError.php b/vendor/symfony/error-handler/Error/FatalError.php new file mode 100644 index 0000000..b80e3fa --- /dev/null +++ b/vendor/symfony/error-handler/Error/FatalError.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Error; + +class FatalError extends \Error +{ + /** + * @param array $error An array as returned by error_get_last() + */ + public function __construct( + string $message, + int $code, + private array $error, + ?int $traceOffset = null, + bool $traceArgs = true, + ?array $trace = null, + ) { + parent::__construct($message, $code); + + if (null !== $trace) { + if (!$traceArgs) { + foreach ($trace as &$frame) { + unset($frame['args'], $frame['this'], $frame); + } + } + } elseif (null !== $traceOffset) { + if (\function_exists('xdebug_get_function_stack') && \in_array(\ini_get('xdebug.mode'), ['develop', false], true) && $trace = @xdebug_get_function_stack()) { + if (0 < $traceOffset) { + array_splice($trace, -$traceOffset); + } + + foreach ($trace as &$frame) { + if (!isset($frame['type'])) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (isset($frame['class'])) { + $frame['type'] = '::'; + } + } elseif ('dynamic' === $frame['type']) { + $frame['type'] = '->'; + } elseif ('static' === $frame['type']) { + $frame['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (!$traceArgs) { + unset($frame['params'], $frame['args']); + } elseif (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + unset($frame['params']); + } + } + + unset($frame); + $trace = array_reverse($trace); + } else { + $trace = []; + } + } + + foreach ([ + 'file' => $error['file'], + 'line' => $error['line'], + 'trace' => $trace, + ] as $property => $value) { + if (null !== $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setValue($this, $value); + } + } + } + + public function getError(): array + { + return $this->error; + } +} diff --git a/vendor/symfony/error-handler/Error/OutOfMemoryError.php b/vendor/symfony/error-handler/Error/OutOfMemoryError.php new file mode 100644 index 0000000..d685c3d --- /dev/null +++ b/vendor/symfony/error-handler/Error/OutOfMemoryError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Error; + +class OutOfMemoryError extends FatalError +{ +} diff --git a/vendor/symfony/error-handler/Error/UndefinedFunctionError.php b/vendor/symfony/error-handler/Error/UndefinedFunctionError.php new file mode 100644 index 0000000..5063b73 --- /dev/null +++ b/vendor/symfony/error-handler/Error/UndefinedFunctionError.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Error; + +class UndefinedFunctionError extends \Error +{ + public function __construct(string $message, \Throwable $previous) + { + parent::__construct($message, $previous->getCode(), $previous->getPrevious()); + + foreach ([ + 'file' => $previous->getFile(), + 'line' => $previous->getLine(), + 'trace' => $previous->getTrace(), + ] as $property => $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setValue($this, $value); + } + } +} diff --git a/vendor/symfony/error-handler/Error/UndefinedMethodError.php b/vendor/symfony/error-handler/Error/UndefinedMethodError.php new file mode 100644 index 0000000..35f1545 --- /dev/null +++ b/vendor/symfony/error-handler/Error/UndefinedMethodError.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Error; + +class UndefinedMethodError extends \Error +{ + public function __construct(string $message, \Throwable $previous) + { + parent::__construct($message, $previous->getCode(), $previous->getPrevious()); + + foreach ([ + 'file' => $previous->getFile(), + 'line' => $previous->getLine(), + 'trace' => $previous->getTrace(), + ] as $property => $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setValue($this, $value); + } + } +} diff --git a/vendor/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php b/vendor/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php new file mode 100644 index 0000000..b4623cf --- /dev/null +++ b/vendor/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorEnhancer; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\ErrorHandler\DebugClassLoader; +use Symfony\Component\ErrorHandler\Error\ClassNotFoundError; +use Symfony\Component\ErrorHandler\Error\FatalError; + +/** + * @author Fabien Potencier + */ +class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface +{ + public function enhance(\Throwable $error): ?\Throwable + { + // Some specific versions of PHP produce a fatal error when extending a not found class. + $message = !$error instanceof FatalError ? $error->getMessage() : $error->getError()['message']; + if (!preg_match('/^(Class|Interface|Trait) [\'"]([^\'"]+)[\'"] not found$/', $message, $matches)) { + return null; + } + $typeName = strtolower($matches[1]); + $fullyQualifiedClassName = $matches[2]; + + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { + $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); + $tail = ' for another namespace?'; + } else { + $className = $fullyQualifiedClassName; + $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); + $tail = '?'; + } + + if ($candidates = $this->getClassCandidates($className)) { + $tail = array_pop($candidates).'"?'; + if ($candidates) { + $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; + } else { + $tail = ' for "'.$tail; + } + } + $message .= "\nDid you forget a \"use\" statement".$tail; + + return new ClassNotFoundError($message, $error); + } + + /** + * Tries to guess the full namespace for a given class name. + * + * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer + * autoloader (that should cover all common cases). + * + * @param string $class A class name (without its namespace) + * + * Returns an array of possible fully qualified class names + */ + private function getClassCandidates(string $class): array + { + if (!\is_array($functions = spl_autoload_functions())) { + return []; + } + + // find Symfony and Composer autoloaders + $classes = []; + + foreach ($functions as $function) { + if (!\is_array($function)) { + continue; + } + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + + if (!\is_array($function)) { + continue; + } + } + + if ($function[0] instanceof ClassLoader) { + foreach ($function[0]->getPrefixes() as $prefix => $paths) { + foreach ($paths as $path) { + $classes[] = $this->findClassInPath($path, $class, $prefix); + } + } + + foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { + foreach ($paths as $path) { + $classes[] = $this->findClassInPath($path, $class, $prefix); + } + } + } + } + + return array_unique(array_merge([], ...$classes)); + } + + private function findClassInPath(string $path, string $class, string $prefix): array + { + $path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path); + if (!$path || !is_dir($path)) { + return []; + } + + $classes = []; + $filename = $class.'.php'; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { + $classes[] = $class; + } + } + + return $classes; + } + + private function convertFileToClass(string $path, string $file, string $prefix): ?string + { + $candidates = [ + // namespaced class + $namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file), + // namespaced class (with target dir) + $prefix.$namespacedClass, + // namespaced class (with target dir and separator) + $prefix.'\\'.$namespacedClass, + // PEAR class + str_replace('\\', '_', $namespacedClass), + // PEAR class (with target dir) + str_replace('\\', '_', $prefix.$namespacedClass), + // PEAR class (with target dir and separator) + str_replace('\\', '_', $prefix.'\\'.$namespacedClass), + ]; + + if ($prefix) { + $candidates = array_filter($candidates, fn ($candidate) => str_starts_with($candidate, $prefix)); + } + + // We cannot use the autoloader here as most of them use require; but if the class + // is not found, the new autoloader call will require the file again leading to a + // "cannot redeclare class" error. + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + // Symfony may ship some polyfills, like "Normalizer". But if the Intl + // extension is already installed, the next require_once will fail with + // a compile error because the class is already defined. And this one + // does not throw a Throwable. So it's better to skip it here. + if (str_contains($file, 'Resources/stubs')) { + return null; + } + + try { + require_once $file; + } catch (\Throwable) { + return null; + } + + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + return null; + } + + private function classExists(string $class): bool + { + return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + } +} diff --git a/vendor/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php b/vendor/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php new file mode 100644 index 0000000..7c3f4ef --- /dev/null +++ b/vendor/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorEnhancer; + +interface ErrorEnhancerInterface +{ + /** + * Returns an \Throwable instance if the class is able to improve the error, null otherwise. + */ + public function enhance(\Throwable $error): ?\Throwable; +} diff --git a/vendor/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php b/vendor/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php new file mode 100644 index 0000000..0458c26 --- /dev/null +++ b/vendor/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorEnhancer; + +use Symfony\Component\ErrorHandler\Error\FatalError; +use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError; + +/** + * @author Fabien Potencier + */ +class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface +{ + public function enhance(\Throwable $error): ?\Throwable + { + if ($error instanceof FatalError) { + return null; + } + + $message = $error->getMessage(); + $messageLen = \strlen($message); + $notFoundSuffix = '()'; + $notFoundSuffixLen = \strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return null; + } + + if (0 !== substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) { + return null; + } + + $prefix = 'Call to undefined function '; + $prefixLen = \strlen($prefix); + if (!str_starts_with($message, $prefix)) { + return null; + } + + $fullyQualifiedFunctionName = substr($message, $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { + $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); + } else { + $functionName = $fullyQualifiedFunctionName; + $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); + } + + $candidates = []; + foreach (get_defined_functions() as $type => $definedFunctionNames) { + foreach ($definedFunctionNames as $definedFunctionName) { + if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { + $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); + } else { + $definedFunctionNameBasename = $definedFunctionName; + } + + if ($definedFunctionNameBasename === $functionName) { + $candidates[] = '\\'.$definedFunctionName; + } + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedFunctionError($message, $error); + } +} diff --git a/vendor/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php b/vendor/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php new file mode 100644 index 0000000..80eaec9 --- /dev/null +++ b/vendor/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorEnhancer; + +use Symfony\Component\ErrorHandler\Error\FatalError; +use Symfony\Component\ErrorHandler\Error\UndefinedMethodError; + +/** + * @author Grégoire Pineau + */ +class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface +{ + public function enhance(\Throwable $error): ?\Throwable + { + if ($error instanceof FatalError) { + return null; + } + + $message = $error->getMessage(); + preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $message, $matches); + if (!$matches) { + return null; + } + + $className = $matches[1]; + $methodName = $matches[2]; + + $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); + + if ('' === $methodName || !class_exists($className) || null === $methods = get_class_methods($className)) { + // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) + return new UndefinedMethodError($message, $error); + } + + $candidates = []; + foreach ($methods as $definedMethodName) { + $lev = levenshtein($methodName, $definedMethodName); + if ($lev <= \strlen($methodName) / 3 || str_contains($definedMethodName, $methodName)) { + $candidates[] = $definedMethodName; + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedMethodError($message, $error); + } +} diff --git a/vendor/symfony/error-handler/ErrorHandler.php b/vendor/symfony/error-handler/ErrorHandler.php new file mode 100644 index 0000000..c0af370 --- /dev/null +++ b/vendor/symfony/error-handler/ErrorHandler.php @@ -0,0 +1,747 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\ErrorHandler\Error\FatalError; +use Symfony\Component\ErrorHandler\Error\OutOfMemoryError; +use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer; +use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface; +use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer; +use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer; +use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; + +/** + * A generic ErrorHandler for the PHP engine. + * + * Provides five bit fields that control how errors are handled: + * - thrownErrors: errors thrown as \ErrorException + * - loggedErrors: logged errors, when not @-silenced + * - scopedErrors: errors thrown or logged with their local context + * - tracedErrors: errors logged with their stack trace + * - screamedErrors: never @-silenced errors + * + * Each error level can be logged by a dedicated PSR-3 logger object. + * Screaming only applies to logging. + * Throwing takes precedence over logging. + * Uncaught exceptions are logged as E_ERROR. + * E_DEPRECATED and E_USER_DEPRECATED levels never throw. + * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. + * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. + * As errors have a performance cost, repeated errors are all logged, so that the developer + * can see them and weight them as more important to fix than others of the same level. + * + * @author Nicolas Grekas + * @author Grégoire Pineau + * + * @final + */ +class ErrorHandler +{ + private array $levels = [ + \E_DEPRECATED => 'Deprecated', + \E_USER_DEPRECATED => 'User Deprecated', + \E_NOTICE => 'Notice', + \E_USER_NOTICE => 'User Notice', + \E_WARNING => 'Warning', + \E_USER_WARNING => 'User Warning', + \E_COMPILE_WARNING => 'Compile Warning', + \E_CORE_WARNING => 'Core Warning', + \E_USER_ERROR => 'User Error', + \E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + \E_COMPILE_ERROR => 'Compile Error', + \E_PARSE => 'Parse Error', + \E_ERROR => 'Error', + \E_CORE_ERROR => 'Core Error', + ]; + + private array $loggers = [ + \E_DEPRECATED => [null, LogLevel::INFO], + \E_USER_DEPRECATED => [null, LogLevel::INFO], + \E_NOTICE => [null, LogLevel::ERROR], + \E_USER_NOTICE => [null, LogLevel::ERROR], + \E_WARNING => [null, LogLevel::ERROR], + \E_USER_WARNING => [null, LogLevel::ERROR], + \E_COMPILE_WARNING => [null, LogLevel::ERROR], + \E_CORE_WARNING => [null, LogLevel::ERROR], + \E_USER_ERROR => [null, LogLevel::CRITICAL], + \E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL], + \E_COMPILE_ERROR => [null, LogLevel::CRITICAL], + \E_PARSE => [null, LogLevel::CRITICAL], + \E_ERROR => [null, LogLevel::CRITICAL], + \E_CORE_ERROR => [null, LogLevel::CRITICAL], + ]; + + private int $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private int $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private int $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE + private int $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE + private int $loggedErrors = 0; + private \Closure $configureException; + private bool $debug; + + private bool $isRecursive = false; + private bool $isRoot = false; + /** @var callable|null */ + private $exceptionHandler; + private ?BufferingLogger $bootstrappingLogger = null; + + private static ?string $reservedMemory = null; + private static array $silencedErrorCache = []; + private static int $silencedErrorCount = 0; + private static int $exitCode = 0; + + /** + * Registers the error handler. + */ + public static function register(?self $handler = null, bool $replace = true): self + { + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', 32768); + register_shutdown_function(self::handleFatalError(...)); + } + + if ($handlerIsNew = null === $handler) { + $handler = new static(); + } + + if (null === $prev = set_error_handler([$handler, 'handleError'])) { + restore_error_handler(); + // Specifying the error types earlier would expose us to https://bugs.php.net/63206 + set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors); + $handler->isRoot = true; + } + + if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) { + $handler = $prev[0]; + $replace = false; + } + if (!$replace && $prev) { + restore_error_handler(); + $handlerIsRegistered = \is_array($prev) && $handler === $prev[0]; + } else { + $handlerIsRegistered = true; + } + if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) { + restore_exception_handler(); + if (!$handlerIsRegistered) { + $handler = $prev[0]; + } elseif ($handler !== $prev[0] && $replace) { + set_exception_handler([$handler, 'handleException']); + $p = $prev[0]->setExceptionHandler(null); + $handler->setExceptionHandler($p); + $prev[0]->setExceptionHandler($p); + } + } else { + $handler->setExceptionHandler($prev ?? [$handler, 'renderException']); + } + + $handler->throwAt(\E_ALL & $handler->thrownErrors, true); + + return $handler; + } + + /** + * Calls a function and turns any PHP error into \ErrorException. + * + * @throws \ErrorException When $function(...$arguments) triggers a PHP error + */ + public static function call(callable $function, mixed ...$arguments): mixed + { + set_error_handler(static function (int $type, string $message, string $file, int $line) { + if (__FILE__ === $file) { + $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); + $file = $trace[2]['file'] ?? $file; + $line = $trace[2]['line'] ?? $line; + } + + throw new \ErrorException($message, 0, $type, $file, $line); + }); + + try { + return $function(...$arguments); + } finally { + restore_error_handler(); + } + } + + public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = false) + { + if (\PHP_VERSION_ID < 80400) { + $this->levels[\E_STRICT] = 'Runtime Notice'; + $this->loggers[\E_STRICT] = [null, LogLevel::ERROR]; + } + + if ($bootstrappingLogger) { + $this->bootstrappingLogger = $bootstrappingLogger; + $this->setDefaultLogger($bootstrappingLogger); + } + $traceReflector = new \ReflectionProperty(\Exception::class, 'trace'); + $this->configureException = \Closure::bind(static function ($e, $trace, $file = null, $line = null) use ($traceReflector) { + $traceReflector->setValue($e, $trace); + $e->file = $file ?? $e->file; + $e->line = $line ?? $e->line; + }, null, new class() extends \Exception { + }); + $this->debug = $debug; + } + + /** + * Sets a logger to non assigned errors levels. + * + * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels + * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param bool $replace Whether to replace or not any existing logger + */ + public function setDefaultLogger(LoggerInterface $logger, array|int|null $levels = \E_ALL, bool $replace = false): void + { + $loggers = []; + + if (\is_array($levels)) { + foreach ($levels as $type => $logLevel) { + if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { + $loggers[$type] = [$logger, $logLevel]; + } + } + } else { + $levels ??= \E_ALL; + foreach ($this->loggers as $type => $log) { + if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { + $log[0] = $logger; + $loggers[$type] = $log; + } + } + } + + $this->setLoggers($loggers); + } + + /** + * Sets a logger for each error level. + * + * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map + * + * @throws \InvalidArgumentException + */ + public function setLoggers(array $loggers): array + { + $prevLogged = $this->loggedErrors; + $prev = $this->loggers; + $flush = []; + + foreach ($loggers as $type => $log) { + if (!isset($prev[$type])) { + throw new \InvalidArgumentException('Unknown error type: '.$type); + } + if (!\is_array($log)) { + $log = [$log]; + } elseif (!\array_key_exists(0, $log)) { + throw new \InvalidArgumentException('No logger provided.'); + } + if (null === $log[0]) { + $this->loggedErrors &= ~$type; + } elseif ($log[0] instanceof LoggerInterface) { + $this->loggedErrors |= $type; + } else { + throw new \InvalidArgumentException('Invalid logger provided.'); + } + $this->loggers[$type] = $log + $prev[$type]; + + if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { + $flush[$type] = $type; + } + } + $this->reRegister($prevLogged | $this->thrownErrors); + + if ($flush) { + foreach ($this->bootstrappingLogger->cleanLogs() as $log) { + $type = ThrowableUtils::getSeverity($log[2]['exception']); + if (!isset($flush[$type])) { + $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); + } elseif ($this->loggers[$type][0]) { + $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); + } + } + } + + return $prev; + } + + public function setExceptionHandler(?callable $handler): ?callable + { + $prev = $this->exceptionHandler; + $this->exceptionHandler = $handler; + + return $prev; + } + + /** + * Sets the PHP error levels that throw an exception when a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for thrown errors + * @param bool $replace Replace or amend the previous value + */ + public function throwAt(int $levels, bool $replace = false): int + { + $prev = $this->thrownErrors; + $this->thrownErrors = ($levels | \E_RECOVERABLE_ERROR | \E_USER_ERROR) & ~\E_USER_DEPRECATED & ~\E_DEPRECATED; + if (!$replace) { + $this->thrownErrors |= $prev; + } + $this->reRegister($prev | $this->loggedErrors); + + return $prev; + } + + /** + * Sets the PHP error levels for which local variables are preserved. + * + * @param int $levels A bit field of E_* constants for scoped errors + * @param bool $replace Replace or amend the previous value + */ + public function scopeAt(int $levels, bool $replace = false): int + { + $prev = $this->scopedErrors; + $this->scopedErrors = $levels; + if (!$replace) { + $this->scopedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the PHP error levels for which the stack trace is preserved. + * + * @param int $levels A bit field of E_* constants for traced errors + * @param bool $replace Replace or amend the previous value + */ + public function traceAt(int $levels, bool $replace = false): int + { + $prev = $this->tracedErrors; + $this->tracedErrors = $levels; + if (!$replace) { + $this->tracedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the error levels where the @-operator is ignored. + * + * @param int $levels A bit field of E_* constants for screamed errors + * @param bool $replace Replace or amend the previous value + */ + public function screamAt(int $levels, bool $replace = false): int + { + $prev = $this->screamedErrors; + $this->screamedErrors = $levels; + if (!$replace) { + $this->screamedErrors |= $prev; + } + + return $prev; + } + + /** + * Re-registers as a PHP error handler if levels changed. + */ + private function reRegister(int $prev): void + { + if ($prev !== ($this->thrownErrors | $this->loggedErrors)) { + $handler = set_error_handler(static fn () => null); + $handler = \is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler === $this) { + restore_error_handler(); + if ($this->isRoot) { + set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors); + } else { + set_error_handler([$this, 'handleError']); + } + } + } + } + + /** + * Handles errors by filtering then logging them according to the configured bit fields. + * + * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself + * + * @throws \ErrorException When $this->thrownErrors requests so + * + * @internal + */ + public function handleError(int $type, string $message, string $file, int $line): bool + { + if (\E_WARNING === $type && '"' === $message[0] && str_contains($message, '" targeting switch is equivalent to "break')) { + $type = \E_DEPRECATED; + } + + // Level is the current error reporting level to manage silent error. + $level = error_reporting(); + $silenced = 0 === ($level & $type); + // Strong errors are not authorized to be silenced. + $level |= \E_RECOVERABLE_ERROR | \E_USER_ERROR | \E_DEPRECATED | \E_USER_DEPRECATED; + $log = $this->loggedErrors & $type; + $throw = $this->thrownErrors & $type & $level; + $type &= $level | $this->screamedErrors; + + // Never throw on warnings triggered by assert() + if (\E_WARNING === $type && 'a' === $message[0] && 0 === strncmp($message, 'assert(): ', 10)) { + $throw = 0; + } + + if (!$type || (!$log && !$throw)) { + return false; + } + + $logMessage = $this->levels[$type].': '.$message; + + if (!$throw && !($type & $level)) { + if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) { + $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : []; + $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace); + } elseif (isset(self::$silencedErrorCache[$id][$message])) { + $lightTrace = null; + $errorAsException = self::$silencedErrorCache[$id][$message]; + ++$errorAsException->count; + } else { + $lightTrace = []; + $errorAsException = null; + } + + if (100 < ++self::$silencedErrorCount) { + self::$silencedErrorCache = $lightTrace = []; + self::$silencedErrorCount = 1; + } + if ($errorAsException) { + self::$silencedErrorCache[$id][$message] = $errorAsException; + } + if (null === $lightTrace) { + return true; + } + } else { + if (\PHP_VERSION_ID < 80303 && str_contains($message, '@anonymous')) { + $backtrace = debug_backtrace(false, 5); + + for ($i = 1; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['function'], $backtrace[$i]['args'][0]) + && ('trigger_error' === $backtrace[$i]['function'] || 'user_error' === $backtrace[$i]['function']) + ) { + if ($backtrace[$i]['args'][0] !== $message) { + $message = $backtrace[$i]['args'][0]; + } + + break; + } + } + } + + if (str_contains($message, "@anonymous\0")) { + $message = $this->parseAnonymousClass($message); + $logMessage = $this->levels[$type].': '.$message; + } + + $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); + + if ($throw || $this->tracedErrors & $type) { + $backtrace = $errorAsException->getTrace(); + $backtrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); + ($this->configureException)($errorAsException, $backtrace, $file, $line); + } else { + ($this->configureException)($errorAsException, []); + } + } + + if ($throw) { + throw $errorAsException; + } + + if ($this->isRecursive) { + $log = 0; + } else { + try { + $this->isRecursive = true; + $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; + $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []); + } finally { + $this->isRecursive = false; + } + } + + return !$silenced && $type && $log; + } + + /** + * Handles an exception by logging then forwarding it to another handler. + * + * @internal + */ + public function handleException(\Throwable $exception): void + { + $handlerException = null; + + if (!$exception instanceof FatalError) { + self::$exitCode = 255; + + $type = ThrowableUtils::getSeverity($exception); + } else { + $type = $exception->getError()['type']; + } + + if ($this->loggedErrors & $type) { + if (str_contains($message = $exception->getMessage(), "@anonymous\0")) { + $message = $this->parseAnonymousClass($message); + } + + if ($exception instanceof FatalError) { + $message = 'Fatal '.$message; + } elseif ($exception instanceof \Error) { + $message = 'Uncaught Error: '.$message; + } elseif ($exception instanceof \ErrorException) { + $message = 'Uncaught '.$message; + } else { + $message = 'Uncaught Exception: '.$message; + } + + try { + $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]); + } catch (\Throwable $handlerException) { + } + } + + $exception = $this->enhanceError($exception); + + $exceptionHandler = $this->exceptionHandler; + $this->exceptionHandler = [$this, 'renderException']; + + if (null === $exceptionHandler || $exceptionHandler === $this->exceptionHandler) { + $this->exceptionHandler = null; + } + + try { + if (null !== $exceptionHandler) { + $exceptionHandler($exception); + + return; + } + $handlerException ??= $exception; + } catch (\Throwable $handlerException) { + } + if ($exception === $handlerException && null === $this->exceptionHandler) { + self::$reservedMemory = null; // Disable the fatal error handler + throw $exception; // Give back $exception to the native handler + } + + $loggedErrors = $this->loggedErrors; + if ($exception === $handlerException) { + $this->loggedErrors &= ~$type; + } + + try { + $this->handleException($handlerException); + } finally { + $this->loggedErrors = $loggedErrors; + } + } + + /** + * Shutdown registered function for handling PHP fatal errors. + * + * @param array|null $error An array as returned by error_get_last() + * + * @internal + */ + public static function handleFatalError(?array $error = null): void + { + if (null === self::$reservedMemory) { + return; + } + + $handler = self::$reservedMemory = null; + $handlers = []; + $previousHandler = null; + $sameHandlerLimit = 10; + + while (!\is_array($handler) || !$handler[0] instanceof self) { + $handler = set_exception_handler('is_int'); + restore_exception_handler(); + + if (!$handler) { + break; + } + restore_exception_handler(); + + if ($handler !== $previousHandler) { + array_unshift($handlers, $handler); + $previousHandler = $handler; + } elseif (0 === --$sameHandlerLimit) { + $handler = null; + break; + } + } + foreach ($handlers as $h) { + set_exception_handler($h); + } + if (!$handler) { + if (null === $error && $exitCode = self::$exitCode) { + register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); + } + + return; + } + if ($handler !== $h) { + $handler[0]->setExceptionHandler($h); + } + $handler = $handler[0]; + $handlers = []; + + if ($exit = null === $error) { + $error = error_get_last(); + } + + if ($error && $error['type'] &= \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR) { + // Let's not throw anymore but keep logging + $handler->throwAt(0, true); + $trace = $error['backtrace'] ?? null; + + if (str_starts_with($error['message'], 'Allowed memory') || str_starts_with($error['message'], 'Out of memory')) { + $fatalError = new OutOfMemoryError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, false, $trace); + } else { + $fatalError = new FatalError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, true, $trace); + } + } else { + $fatalError = null; + } + + try { + if (null !== $fatalError) { + self::$exitCode = 255; + $handler->handleException($fatalError); + } + } catch (FatalError) { + // Ignore this re-throw + } + + if ($exit && $exitCode = self::$exitCode) { + register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); + } + } + + /** + * Renders the given exception. + * + * As this method is mainly called during boot where nothing is yet available, + * the output is always either HTML or CLI depending where PHP runs. + */ + private function renderException(\Throwable $exception): void + { + $renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug); + + $exception = $renderer->render($exception); + + if (!headers_sent()) { + http_response_code($exception->getStatusCode()); + + foreach ($exception->getHeaders() as $name => $value) { + header($name.': '.$value, false); + } + } + + echo $exception->getAsString(); + } + + public function enhanceError(\Throwable $exception): \Throwable + { + if ($exception instanceof OutOfMemoryError) { + return $exception; + } + + foreach ($this->getErrorEnhancers() as $errorEnhancer) { + if ($e = $errorEnhancer->enhance($exception)) { + return $e; + } + } + + return $exception; + } + + /** + * Override this method if you want to define more error enhancers. + * + * @return ErrorEnhancerInterface[] + */ + protected function getErrorEnhancers(): iterable + { + return [ + new UndefinedFunctionErrorEnhancer(), + new UndefinedMethodErrorEnhancer(), + new ClassNotFoundErrorEnhancer(), + ]; + } + + /** + * Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader. + */ + private function cleanTrace(array $backtrace, int $type, string &$file, int &$line, bool $throw): array + { + $lightTrace = $backtrace; + + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $lightTrace = \array_slice($lightTrace, 1 + $i); + break; + } + } + if (\E_USER_DEPRECATED === $type) { + for ($i = 0; isset($lightTrace[$i]); ++$i) { + if (!isset($lightTrace[$i]['file'], $lightTrace[$i]['line'], $lightTrace[$i]['function'])) { + continue; + } + if (!isset($lightTrace[$i]['class']) && 'trigger_deprecation' === $lightTrace[$i]['function']) { + $file = $lightTrace[$i]['file']; + $line = $lightTrace[$i]['line']; + $lightTrace = \array_slice($lightTrace, 1 + $i); + break; + } + } + } + if (class_exists(DebugClassLoader::class, false)) { + for ($i = \count($lightTrace) - 2; 0 < $i; --$i) { + if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) { + array_splice($lightTrace, --$i, 2); + } + } + } + if (!($throw || $this->scopedErrors & $type)) { + for ($i = 0; isset($lightTrace[$i]); ++$i) { + unset($lightTrace[$i]['args'], $lightTrace[$i]['object']); + } + } + + return $lightTrace; + } + + /** + * Parse the error message by removing the anonymous class notation + * and using the parent class instead if possible. + */ + private function parseAnonymousClass(string $message): string + { + return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); + } +} diff --git a/vendor/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php b/vendor/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php new file mode 100644 index 0000000..04b3edb --- /dev/null +++ b/vendor/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +// Help opcache.preload discover always-needed symbols +class_exists(CliDumper::class); + +/** + * @author Nicolas Grekas + */ +class CliErrorRenderer implements ErrorRendererInterface +{ + public function render(\Throwable $exception): FlattenException + { + $cloner = new VarCloner(); + $dumper = new class() extends CliDumper { + protected function supportsColors(): bool + { + $outputStream = $this->outputStream; + $this->outputStream = fopen('php://stdout', 'w'); + + try { + return parent::supportsColors(); + } finally { + $this->outputStream = $outputStream; + } + } + }; + + return FlattenException::createFromThrowable($exception) + ->setAsString($dumper->dump($cloner->cloneVar($exception), true)); + } +} diff --git a/vendor/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php b/vendor/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php new file mode 100644 index 0000000..2e28962 --- /dev/null +++ b/vendor/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; + +/** + * Formats an exception to be used as response content. + * + * @author Yonel Ceruto + */ +interface ErrorRendererInterface +{ + public const IDE_LINK_FORMATS = [ + 'textmate' => 'txmt://open?url=file://%f&line=%l', + 'macvim' => 'mvim://open?url=file://%f&line=%l', + 'emacs' => 'emacs://open?url=file://%f&line=%l', + 'sublime' => 'subl://open?url=file://%f&line=%l', + 'phpstorm' => 'phpstorm://open?file=%f&line=%l', + 'atom' => 'atom://core/open/file?filename=%f&line=%l', + 'vscode' => 'vscode://file/%f:%l', + ]; + + /** + * Renders a Throwable as a FlattenException. + */ + public function render(\Throwable $exception): FlattenException; +} diff --git a/vendor/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php b/vendor/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php new file mode 100644 index 0000000..30b8663 --- /dev/null +++ b/vendor/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * Formats debug file links. + * + * @author Jérémy Romey + * + * @final + */ +class FileLinkFormatter +{ + private array|false $fileLinkFormat; + + /** + * @param string|\Closure $urlFormat The URL format, or a closure that returns it on-demand + */ + public function __construct( + string|array|null $fileLinkFormat = null, + private ?RequestStack $requestStack = null, + private ?string $baseDir = null, + private string|\Closure|null $urlFormat = null, + ) { + $fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? ''; + + if (!\is_array($f = $fileLinkFormat)) { + $f = (ErrorRendererInterface::IDE_LINK_FORMATS[$f] ?? $f) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; + $i = strpos($f, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); + $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); + } + + $this->fileLinkFormat = $fileLinkFormat; + } + + public function format(string $file, int $line): string|false + { + if ($fmt = $this->getFileLinkFormat()) { + for ($i = 1; isset($fmt[$i]); ++$i) { + if (str_starts_with($file, $k = $fmt[$i++])) { + $file = substr_replace($file, $fmt[$i], 0, \strlen($k)); + break; + } + } + + return strtr($fmt[0], ['%f' => $file, '%l' => $line]); + } + + return false; + } + + /** + * @internal + */ + public function __sleep(): array + { + $this->fileLinkFormat = $this->getFileLinkFormat(); + + return ['fileLinkFormat']; + } + + /** + * @internal + */ + public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString): ?string + { + try { + return $router->generate($routeName).$queryString; + } catch (\Throwable) { + return null; + } + } + + private function getFileLinkFormat(): array|false + { + if ($this->fileLinkFormat) { + return $this->fileLinkFormat; + } + + if ($this->requestStack && $this->baseDir && $this->urlFormat) { + $request = $this->requestStack->getMainRequest(); + + if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) { + return [ + $request->getSchemeAndHttpHost().$this->urlFormat, + $this->baseDir.\DIRECTORY_SEPARATOR, '', + ]; + } + } + + return false; + } +} diff --git a/vendor/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php b/vendor/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php new file mode 100644 index 0000000..70ea82f --- /dev/null +++ b/vendor/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php @@ -0,0 +1,355 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Psr\Log\LoggerInterface; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * @author Yonel Ceruto + */ +class HtmlErrorRenderer implements ErrorRendererInterface +{ + private const GHOST_ADDONS = [ + '02-14' => self::GHOST_HEART, + '02-29' => self::GHOST_PLUS, + '10-18' => self::GHOST_GIFT, + ]; + + private const GHOST_GIFT = 'M124.00534057617188,5.3606138080358505 C124.40059661865234,4.644828304648399 125.1237564086914,3.712414965033531 123.88127899169922,3.487462028861046 C123.53517150878906,3.3097832053899765 123.18894958496094,2.9953975528478622 122.8432846069336,3.345616325736046 C122.07421112060547,3.649444565176964 121.40750122070312,4.074306473135948 122.2164306640625,4.869479164481163 C122.57514953613281,5.3830065578222275 122.90142822265625,6.503447040915489 123.3077621459961,6.626829609274864 C123.55027770996094,6.210384353995323 123.7774658203125,5.785196766257286 124.00534057617188,5.3606138080358505 zM122.30630493164062,7.336987480521202 C121.60028076171875,6.076864704489708 121.03211975097656,4.72498320043087 120.16796875,3.562500938773155 C119.11695098876953,2.44033907353878 117.04605865478516,2.940566048026085 116.57544708251953,4.387995228171349 C115.95028686523438,5.819030746817589 117.2991714477539,7.527640804648399 118.826171875,7.348545059561729 C119.98493194580078,7.367936596274376 121.15027618408203,7.420116886496544 122.30630493164062,7.336987480521202 zM128.1732177734375,7.379541382193565 C129.67486572265625,7.17823551595211 130.53842163085938,5.287807449698448 129.68344116210938,4.032590612769127 C128.92578125,2.693056806921959 126.74605560302734,2.6463639587163925 125.98509216308594,4.007616028189659 C125.32617950439453,5.108129009604454 124.75428009033203,6.258124336600304 124.14962768554688,7.388818249106407 C125.48638916015625,7.465229496359825 126.8357162475586,7.447416767477989 128.1732177734375,7.379541382193565 zM130.6601104736328,8.991325363516808 C131.17202758789062,8.540884003043175 133.1543731689453,8.009847149252892 131.65304565429688,7.582054600119591 C131.2811279296875,7.476506695151329 130.84751892089844,6.99234913289547 130.5132598876953,7.124847874045372 C129.78744506835938,8.02728746831417 128.67140197753906,8.55669592320919 127.50616455078125,8.501235947012901 C127.27806091308594,8.576229080557823 126.11459350585938,8.38720129430294 126.428955078125,8.601900085806847 C127.25099182128906,9.070617660880089 128.0523223876953,9.579657539725304 128.902587890625,9.995706543326378 C129.49813842773438,9.678531631827354 130.0761260986328,9.329126343131065 130.6601104736328,8.991325363516808 zM118.96446990966797,9.246344551444054 C119.4022445678711,8.991325363516808 119.84001922607422,8.736305221915245 120.27779388427734,8.481284126639366 C118.93965911865234,8.414779648184776 117.40827941894531,8.607666000723839 116.39698791503906,7.531384453177452 C116.11186981201172,7.212117180228233 115.83845520019531,6.846597656607628 115.44329071044922,7.248530372977257 C114.96995544433594,7.574637398123741 113.5140609741211,7.908811077475548 114.63501739501953,8.306883797049522 C115.61112976074219,8.883499130606651 116.58037567138672,9.474181160330772 117.58061218261719,10.008124336600304 C118.05723571777344,9.784612640738487 118.50651550292969,9.5052699893713 118.96446990966797,9.246344551444054 zM125.38018035888672,12.091858848929405 C125.9474868774414,11.636047348380089 127.32159423828125,11.201767906546593 127.36749267578125,10.712632164359093 C126.08487701416016,9.974547371268272 124.83960723876953,9.152772888541222 123.49772644042969,8.528907760977745 C123.03594207763672,8.353693947196007 122.66152954101562,8.623294815421104 122.28982543945312,8.857431396842003 C121.19065856933594,9.51122473180294 120.06505584716797,10.12446115911007 119.00167083740234,10.835315689444542 C120.39238739013672,11.69529627263546 121.79983520507812,12.529837593436241 123.22095489501953,13.338589653372765 C123.94580841064453,12.932025894522667 124.66128540039062,12.508862480521202 125.38018035888672,12.091858848929405 zM131.07164001464844,13.514615997672081 C131.66018676757812,13.143282875418663 132.2487335205078,12.771927818655968 132.8372802734375,12.400571808218956 C132.8324737548828,11.156818374991417 132.8523406982422,9.912529930472374 132.81829833984375,8.669195160269737 C131.63046264648438,9.332009300589561 130.45948791503906,10.027913078665733 129.30828857421875,10.752535805106163 C129.182373046875,12.035354599356651 129.24623107910156,13.33940313756466 129.27359008789062,14.628684982657433 C129.88104248046875,14.27079389989376 130.4737548828125,13.888019546866417 131.07164001464844,13.514640793204308 zM117.26847839355469,12.731024727225304 C117.32825469970703,11.67083452641964 117.45709991455078,10.46224020421505 116.17853546142578,10.148179039359093 C115.37110900878906,9.77159021794796 114.25194549560547,8.806716904044151 113.62991333007812,8.81639002263546 C113.61052703857422,10.0110072940588 113.62078857421875,11.20585821568966 113.61869049072266,12.400571808218956 C114.81139373779297,13.144886955618858 115.98292541503906,13.925040230154991 117.20137023925781,14.626662239432335 C117.31951141357422,14.010867103934288 117.24227905273438,13.35805033147335 117.26847839355469,12.731024727225304 zM125.80937957763672,16.836034759879112 C126.51483917236328,16.390663132071495 127.22030639648438,15.945291504263878 127.92576599121094,15.49991987645626 C127.92250061035156,14.215868934988976 127.97560119628906,12.929980263113976 127.91757202148438,11.647302612662315 C127.14225769042969,11.869626984000206 126.25550079345703,12.556857094168663 125.43866729736328,12.983742699027061 C124.82704162597656,13.342005714774132 124.21542358398438,13.700271591544151 123.60379028320312,14.05853746831417 C123.61585235595703,15.429577812552452 123.57081604003906,16.803131088614464 123.64839172363281,18.172149643301964 C124.37957000732422,17.744937881827354 125.09130859375,17.284801468253136 125.80937957763672,16.836034759879112 zM122.8521499633789,16.115344032645226 C122.8521499633789,15.429741844534874 122.8521499633789,14.744139656424522 122.8521499633789,14.05853746831417 C121.43595123291016,13.230924591422081 120.02428436279297,12.395455345511436 118.60256958007812,11.577354416251183 C118.52394104003906,12.888403877615929 118.56887817382812,14.204405769705772 118.55702209472656,15.517732605338097 C119.97289276123047,16.4041957706213 121.37410736083984,17.314891800284386 122.80789947509766,18.172149643301964 C122.86368560791016,17.488990768790245 122.84332275390625,16.800363525748253 122.8521499633789,16.115344032645226 zM131.10684204101562,18.871450409293175 C131.68399047851562,18.48711584508419 132.2611541748047,18.10278509557247 132.8383026123047,17.718475326895714 C132.81423950195312,16.499977096915245 132.89776611328125,15.264989838004112 132.77627563476562,14.05993078649044 C131.5760040283203,14.744719490408897 130.41763305664062,15.524359688162804 129.23875427246094,16.255397781729698 C129.26707458496094,17.516149505972862 129.18060302734375,18.791316971182823 129.3108367919922,20.041303619742393 C129.91973876953125,19.667551025748253 130.51010131835938,19.264152511954308 131.10684204101562,18.871450409293175 zM117.2557373046875,18.188333496451378 C117.25104522705078,17.549470886588097 117.24633026123047,16.91058538854122 117.24163055419922,16.271720871329308 C116.04924774169922,15.525708183646202 114.87187957763672,14.75476549565792 113.66158294677734,14.038097366690636 C113.5858383178711,15.262084946036339 113.62901306152344,16.49083898961544 113.61761474609375,17.717010483145714 C114.82051086425781,18.513254150748253 116.00987243652344,19.330610260367393 117.22888946533203,20.101993545889854 C117.27559661865234,19.466014847159386 117.25241088867188,18.825733169913292 117.2557373046875,18.188333496451378 zM125.8398666381836,22.38675306737423 C126.54049682617188,21.921453461050987 127.24110412597656,21.456151947379112 127.94172668457031,20.99083136022091 C127.94009399414062,19.693386062979698 127.96646118164062,18.395381912589073 127.93160247802734,17.098379120230675 C126.50540924072266,17.97775076329708 125.08877563476562,18.873308166861534 123.68258666992188,19.78428266942501 C123.52366638183594,21.03710363805294 123.626708984375,22.32878302037716 123.62647247314453,23.595300659537315 C124.06291198730469,23.86113165318966 125.1788101196289,22.68297766149044 125.8398666381836,22.38675306737423 zM122.8521499633789,21.83134649693966 C122.76741790771484,20.936696991324425 123.21651458740234,19.67745779454708 122.0794677734375,19.330633148550987 C120.93280029296875,18.604360565543175 119.7907485961914,17.870157226920128 118.62899780273438,17.16818617284298 C118.45966339111328,18.396427139639854 118.63676452636719,19.675991043448448 118.50668334960938,20.919256195425987 C119.89984130859375,21.92635916173458 121.32942199707031,22.88914106786251 122.78502655029297,23.803510650992393 C122.90177917480469,23.1627406924963 122.82917022705078,22.48402212560177 122.8521499633789,21.83134649693966 zM117.9798355102539,21.59483526647091 C116.28416442871094,20.46288488805294 114.58848571777344,19.330957397818565 112.892822265625,18.199007019400597 C112.89473724365234,14.705654129385948 112.84647369384766,11.211485847830772 112.90847778320312,7.718807205557823 C113.7575912475586,7.194885239005089 114.66117858886719,6.765397056937218 115.5350341796875,6.284702762961388 C114.97061157226562,4.668964847922325 115.78496551513672,2.7054970115423203 117.42159271240234,2.1007001250982285 C118.79354095458984,1.537783369421959 120.44731903076172,2.0457767099142075 121.32200622558594,3.23083733022213 C121.95732116699219,2.9050118774175644 122.59264373779297,2.5791852325201035 123.22796630859375,2.253336176276207 C123.86669921875,2.5821153968572617 124.50543975830078,2.9108948558568954 125.1441650390625,3.23967407643795 C126.05941009521484,2.154020771384239 127.62747192382812,1.5344576686620712 128.986328125,2.1429056972265244 C130.61741638183594,2.716217741370201 131.50650024414062,4.675290569663048 130.9215545654297,6.2884936183691025 C131.8018341064453,6.78548763692379 132.7589111328125,7.1738648265600204 133.5660400390625,7.780336365103722 C133.60182189941406,11.252970680594444 133.56637573242188,14.726140961050987 133.5631103515625,18.199007019400597 C130.18914794921875,20.431867584586143 126.86984252929688,22.74994657933712 123.44108581542969,24.897907242178917 C122.44406127929688,24.897628769278526 121.5834732055664,23.815067276358604 120.65831756591797,23.37616156041622 C119.76387023925781,22.784828171133995 118.87168884277344,22.19007681310177 117.9798355102539,21.59483526647091 z'; + private const GHOST_HEART = 'M125.91386369681868,8.305165958366445 C128.95033202169043,-0.40540639102854037 140.8469835342744,8.305165958366445 125.91386369681868,19.504526138305664 C110.98208663272044,8.305165958366445 122.87795231771452,-0.40540639102854037 125.91386369681868,8.305165958366445 z'; + private const GHOST_PLUS = 'M111.36824226379395,8.969108581542969 L118.69175148010254,8.969108581542969 L118.69175148010254,1.6455793380737305 L126.20429420471191,1.6455793380737305 L126.20429420471191,8.969108581542969 L133.52781105041504,8.969108581542969 L133.52781105041504,16.481630325317383 L126.20429420471191,16.481630325317383 L126.20429420471191,23.805158615112305 L118.69175148010254,23.805158615112305 L118.69175148010254,16.481630325317383 L111.36824226379395,16.481630325317383 z'; + + private bool|\Closure $debug; + private string $charset; + private FileLinkFormatter $fileLinkFormat; + private string|\Closure $outputBuffer; + + private static string $template = 'views/error.html.php'; + + /** + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + * @param string|callable $outputBuffer The output buffer as a string or a callable that should return it + */ + public function __construct( + bool|callable $debug = false, + ?string $charset = null, + string|FileLinkFormatter|null $fileLinkFormat = null, + private ?string $projectDir = null, + string|callable $outputBuffer = '', + private ?LoggerInterface $logger = null, + ) { + $this->debug = \is_bool($debug) ? $debug : $debug(...); + $this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8'); + $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : new FileLinkFormatter($fileLinkFormat); + $this->outputBuffer = \is_string($outputBuffer) ? $outputBuffer : $outputBuffer(...); + } + + public function render(\Throwable $exception): FlattenException + { + $headers = ['Content-Type' => 'text/html; charset='.$this->charset]; + if (\is_bool($this->debug) ? $this->debug : ($this->debug)($exception)) { + $headers['X-Debug-Exception'] = rawurlencode(substr($exception->getMessage(), 0, 2000)); + $headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine(); + } + + $exception = FlattenException::createWithDataRepresentation($exception, null, $headers); + + return $exception->setAsString($this->renderException($exception)); + } + + /** + * Gets the HTML content associated with the given exception. + */ + public function getBody(FlattenException $exception): string + { + return $this->renderException($exception, 'views/exception.html.php'); + } + + /** + * Gets the stylesheet associated with the given exception. + */ + public function getStylesheet(): string + { + if (!$this->debug) { + return $this->include('assets/css/error.css'); + } + + return $this->include('assets/css/exception.css'); + } + + public static function isDebug(RequestStack $requestStack, bool $debug): \Closure + { + return static function () use ($requestStack, $debug): bool { + if (!$request = $requestStack->getCurrentRequest()) { + return $debug; + } + + return $debug && $request->attributes->getBoolean('showException', true); + }; + } + + public static function getAndCleanOutputBuffer(RequestStack $requestStack): \Closure + { + return static function () use ($requestStack): string { + if (!$request = $requestStack->getCurrentRequest()) { + return ''; + } + + $startObLevel = $request->headers->get('X-Php-Ob-Level', -1); + + if (ob_get_level() <= $startObLevel) { + return ''; + } + + Response::closeOutputBuffers($startObLevel + 1, true); + + return ob_get_clean(); + }; + } + + private function renderException(FlattenException $exception, string $debugTemplate = 'views/exception_full.html.php'): string + { + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); + $statusText = $this->escape($exception->getStatusText()); + $statusCode = $this->escape($exception->getStatusCode()); + + if (!$debug) { + return $this->include(self::$template, [ + 'statusText' => $statusText, + 'statusCode' => $statusCode, + ]); + } + + $exceptionMessage = $this->escape($exception->getMessage()); + + return $this->include($debugTemplate, [ + 'exception' => $exception, + 'exceptionMessage' => $exceptionMessage, + 'statusText' => $statusText, + 'statusCode' => $statusCode, + 'logger' => null !== $this->logger && class_exists(DebugLoggerConfigurator::class) ? DebugLoggerConfigurator::getDebugLogger($this->logger) : null, + 'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)(), + ]); + } + + private function dumpValue(Data $value): string + { + $dumper = new HtmlDumper(); + $dumper->setTheme('light'); + + return $dumper->dump($value, true); + } + + private function formatArgs(array $args): string + { + $result = []; + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->abbrClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } elseif (preg_match('/[^\x07-\x0D\x1B\x20-\xFF]/', $item[1])) { + $formattedValue = 'binary string'; + } else { + $formattedValue = str_replace("\n", '', $this->escape(var_export($item[1], true))); + } + + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escape($key), $formattedValue); + } + + return implode(', ', $result); + } + + private function formatArgsAsText(array $args): string + { + return strip_tags($this->formatArgs($args)); + } + + private function escape(string $string): string + { + return htmlspecialchars($string, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); + } + + private function abbrClass(string $class): string + { + $parts = explode('\\', $class); + $short = array_pop($parts); + + return sprintf('%s', $class, $short); + } + + private function getFileRelative(string $file): ?string + { + $file = str_replace('\\', '/', $file); + + if (null !== $this->projectDir && str_starts_with($file, $this->projectDir)) { + return ltrim(substr($file, \strlen($this->projectDir)), '/'); + } + + return null; + } + + /** + * Formats a file path. + * + * @param string $file An absolute file path + * @param int $line The line number + * @param string $text Use this text for the link rather than the file path + */ + private function formatFile(string $file, int $line, ?string $text = null): string + { + $file = trim($file); + + if (null === $text) { + $text = $file; + if (null !== $rel = $this->getFileRelative($text)) { + $rel = explode('/', $rel, 2); + $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); + } + } + + if (0 < $line) { + $text .= ' at line '.$line; + } + + $link = $this->fileLinkFormat->format($file, $line); + + return sprintf('%s', $this->escape($link), $text); + } + + /** + * Returns an excerpt of a code file around the given line number. + * + * @param string $file A file path + * @param int $line The selected line number + * @param int $srcContext The number of displayed lines around or -1 for the whole file + */ + private function fileExcerpt(string $file, int $line, int $srcContext = 3): string + { + if (is_file($file) && is_readable($file)) { + // highlight_file could throw warnings + // see https://bugs.php.net/25725 + $code = @highlight_file($file, true); + if (\PHP_VERSION_ID >= 80300) { + // remove main pre/code tags + $code = preg_replace('#^\s*(.*)\s*
#s', '\\1', $code); + // split multiline span tags + $code = preg_replace_callback('#]++)>((?:[^<\\n]*+\\n)++[^<]*+)#', function ($m) { + return "".str_replace("\n", "\n", $m[2]).''; + }, $code); + $content = explode("\n", $code); + } else { + // remove main code/span tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline spans + $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', fn ($m) => "".str_replace('
', "

", $m[2]).'', $code); + $content = explode('
', $code); + } + + $lines = []; + if (0 > $srcContext) { + $srcContext = \count($content); + } + + for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) { + $lines[] = ''.$this->fixCodeMarkup($content[$i - 1]).''; + } + + return '
    '.implode("\n", $lines).'
'; + } + + return ''; + } + + private function fixCodeMarkup(string $line): string + { + // ending tag from previous line + $opening = strpos($line, ''); + if (false !== $closing && (false === $opening || $closing < $opening)) { + $line = substr_replace($line, '', $closing, 7); + } + + // missing tag at the end of line + $opening = strrpos($line, ''); + if (false !== $opening && (false === $closing || $closing < $opening)) { + $line .= ''; + } + + return trim($line); + } + + private function formatFileFromText(string $text): string + { + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text) ?? $text; + } + + private function formatLogMessage(string $message, array $context): string + { + if ($context && str_contains($message, '{')) { + $replacements = []; + foreach ($context as $key => $val) { + if (\is_scalar($val)) { + $replacements['{'.$key.'}'] = $val; + } + } + + if ($replacements) { + $message = strtr($message, $replacements); + } + } + + return $this->escape($message); + } + + private function addElementToGhost(): string + { + if (!isset(self::GHOST_ADDONS[date('m-d')])) { + return ''; + } + + return ''; + } + + private function include(string $name, array $context = []): string + { + extract($context, \EXTR_SKIP); + ob_start(); + + include is_file(\dirname(__DIR__).'/Resources/'.$name) ? \dirname(__DIR__).'/Resources/'.$name : $name; + + return trim(ob_get_clean()); + } + + /** + * Allows overriding the default non-debug template. + * + * @param string $template path to the custom template file to render + */ + public static function setTemplate(string $template): void + { + self::$template = $template; + } +} diff --git a/vendor/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php b/vendor/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php new file mode 100644 index 0000000..3cc6b8e --- /dev/null +++ b/vendor/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Formats an exception using Serializer for rendering. + * + * @author Nicolas Grekas + */ +class SerializerErrorRenderer implements ErrorRendererInterface +{ + private string|\Closure $format; + private ErrorRendererInterface $fallbackErrorRenderer; + private bool|\Closure $debug; + + /** + * @param string|callable(FlattenException) $format The format as a string or a callable that should return it + * formats not supported by Request::getMimeTypes() should be given as mime types + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + */ + public function __construct( + private SerializerInterface $serializer, + string|callable $format, + ?ErrorRendererInterface $fallbackErrorRenderer = null, + bool|callable $debug = false, + ) { + $this->format = \is_string($format) ? $format : $format(...); + $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); + $this->debug = \is_bool($debug) ? $debug : $debug(...); + } + + public function render(\Throwable $exception): FlattenException + { + $headers = ['Vary' => 'Accept']; + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); + if ($debug) { + $headers['X-Debug-Exception'] = rawurlencode(substr($exception->getMessage(), 0, 2000)); + $headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine(); + } + + $flattenException = FlattenException::createFromThrowable($exception, null, $headers); + + try { + $format = \is_string($this->format) ? $this->format : ($this->format)($flattenException); + $headers['Content-Type'] = Request::getMimeTypes($format)[0] ?? $format; + + $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [ + 'exception' => $exception, + 'debug' => $debug, + ])); + } catch (NotEncodableValueException) { + $flattenException = $this->fallbackErrorRenderer->render($exception); + } + + return $flattenException->setHeaders($flattenException->getHeaders() + $headers); + } + + public static function getPreferredFormat(RequestStack $requestStack): \Closure + { + return static function () use ($requestStack) { + if (!$request = $requestStack->getCurrentRequest()) { + throw new NotEncodableValueException(); + } + + return $request->getPreferredFormat(); + }; + } +} diff --git a/vendor/symfony/error-handler/Exception/FlattenException.php b/vendor/symfony/error-handler/Exception/FlattenException.php new file mode 100644 index 0000000..3947315 --- /dev/null +++ b/vendor/symfony/error-handler/Exception/FlattenException.php @@ -0,0 +1,440 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Exception; + +use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * FlattenException wraps a PHP Error or Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException +{ + private string $message; + private string|int $code; + private ?self $previous = null; + private array $trace; + private string $traceAsString; + private string $class; + private int $statusCode; + private string $statusText; + private array $headers; + private string $file; + private int $line; + private ?string $asString = null; + private Data $dataRepresentation; + + public static function create(\Exception $exception, ?int $statusCode = null, array $headers = []): static + { + return static::createFromThrowable($exception, $statusCode, $headers); + } + + public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): static + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } elseif ($exception instanceof RequestExceptionInterface) { + $statusCode = 400; + } + + $statusCode ??= 500; + + if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { + $statusText = Response::$statusTexts[$statusCode]; + } else { + $statusText = 'Whoops, looks like something went wrong.'; + } + + $e->setStatusText($statusText); + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromThrowable($exception); + $e->setClass(get_debug_type($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + + $previous = $exception->getPrevious(); + + if ($previous instanceof \Throwable) { + $e->setPrevious(static::createFromThrowable($previous)); + } + + return $e; + } + + public static function createWithDataRepresentation(\Throwable $throwable, ?int $statusCode = null, array $headers = [], ?VarCloner $cloner = null): static + { + $e = static::createFromThrowable($throwable, $statusCode, $headers); + + static $defaultCloner; + + if (!$cloner ??= $defaultCloner) { + $cloner = $defaultCloner = new VarCloner(); + $cloner->addCasters([ + \Throwable::class => function (\Throwable $e, array $a, Stub $s, bool $isNested): array { + if (!$isNested) { + unset($a[Caster::PREFIX_PROTECTED.'message']); + unset($a[Caster::PREFIX_PROTECTED.'code']); + unset($a[Caster::PREFIX_PROTECTED.'file']); + unset($a[Caster::PREFIX_PROTECTED.'line']); + unset($a["\0Error\0trace"], $a["\0Exception\0trace"]); + unset($a["\0Error\0previous"], $a["\0Exception\0previous"]); + } + + return $a; + }, + ]); + } + + return $e->setDataRepresentation($cloner->cloneVar($throwable)); + } + + public function toArray(): array + { + $exceptions = []; + foreach (array_merge([$this], $this->getAllPrevious()) as $exception) { + $exceptions[] = [ + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + 'data' => $exception->getDataRepresentation(), + ]; + } + + return $exceptions; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * @return $this + */ + public function setStatusCode(int $code): static + { + $this->statusCode = $code; + + return $this; + } + + public function getHeaders(): array + { + return $this->headers; + } + + /** + * @return $this + */ + public function setHeaders(array $headers): static + { + $this->headers = $headers; + + return $this; + } + + public function getClass(): string + { + return $this->class; + } + + /** + * @return $this + */ + public function setClass(string $class): static + { + $this->class = str_contains($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; + + return $this; + } + + public function getFile(): string + { + return $this->file; + } + + /** + * @return $this + */ + public function setFile(string $file): static + { + $this->file = $file; + + return $this; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return $this + */ + public function setLine(int $line): static + { + $this->line = $line; + + return $this; + } + + public function getStatusText(): string + { + return $this->statusText; + } + + /** + * @return $this + */ + public function setStatusText(string $statusText): static + { + $this->statusText = $statusText; + + return $this; + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * @return $this + */ + public function setMessage(string $message): static + { + if (str_contains($message, "@anonymous\0")) { + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); + } + + $this->message = $message; + + return $this; + } + + /** + * @return int|string int most of the time (might be a string with PDOException) + */ + public function getCode(): int|string + { + return $this->code; + } + + /** + * @return $this + */ + public function setCode(int|string $code): static + { + $this->code = $code; + + return $this; + } + + public function getPrevious(): ?self + { + return $this->previous; + } + + /** + * @return $this + */ + public function setPrevious(?self $previous): static + { + $this->previous = $previous; + + return $this; + } + + /** + * @return self[] + */ + public function getAllPrevious(): array + { + $exceptions = []; + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace(): array + { + return $this->trace; + } + + /** + * @return $this + */ + public function setTraceFromThrowable(\Throwable $throwable): static + { + $this->traceAsString = $throwable->getTraceAsString(); + + return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine()); + } + + /** + * @return $this + */ + public function setTrace(array $trace, ?string $file, ?int $line): static + { + $this->trace = []; + $this->trace[] = [ + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => [], + ]; + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = [ + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => $entry['class'] ?? '', + 'type' => $entry['type'] ?? '', + 'function' => $entry['function'] ?? null, + 'file' => $entry['file'] ?? null, + 'line' => $entry['line'] ?? null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [], + ]; + } + + return $this; + } + + public function getDataRepresentation(): ?Data + { + return $this->dataRepresentation ?? null; + } + + /** + * @return $this + */ + public function setDataRepresentation(Data $data): static + { + $this->dataRepresentation = $data; + + return $this; + } + + private function flattenArgs(array $args, int $level = 0, int &$count = 0): array + { + $result = []; + foreach ($args as $key => $value) { + if (++$count > 1e4) { + return ['array', '*SKIPPED over 10000 entries*']; + } + if ($value instanceof \__PHP_Incomplete_Class) { + $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)]; + } elseif (\is_object($value)) { + $result[$key] = ['object', get_debug_type($value)]; + } elseif (\is_array($value)) { + if ($level > 10) { + $result[$key] = ['array', '*DEEP NESTED ARRAY*']; + } else { + $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)]; + } + } elseif (null === $value) { + $result[$key] = ['null', null]; + } elseif (\is_bool($value)) { + $result[$key] = ['boolean', $value]; + } elseif (\is_int($value)) { + $result[$key] = ['integer', $value]; + } elseif (\is_float($value)) { + $result[$key] = ['float', $value]; + } elseif (\is_resource($value)) { + $result[$key] = ['resource', get_resource_type($value)]; + } else { + $result[$key] = ['string', (string) $value]; + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value): string + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } + + public function getTraceAsString(): string + { + return $this->traceAsString; + } + + /** + * @return $this + */ + public function setAsString(?string $asString): static + { + $this->asString = $asString; + + return $this; + } + + public function getAsString(): string + { + if (null !== $this->asString) { + return $this->asString; + } + + $message = ''; + $next = false; + + foreach (array_reverse(array_merge([$this], $this->getAllPrevious())) as $exception) { + if ($next) { + $message .= 'Next '; + } else { + $next = true; + } + $message .= $exception->getClass(); + + if ('' != $exception->getMessage()) { + $message .= ': '.$exception->getMessage(); + } + + $message .= ' in '.$exception->getFile().':'.$exception->getLine(). + "\nStack trace:\n".$exception->getTraceAsString()."\n\n"; + } + + return rtrim($message); + } +} diff --git a/vendor/symfony/error-handler/Exception/SilencedErrorContext.php b/vendor/symfony/error-handler/Exception/SilencedErrorContext.php new file mode 100644 index 0000000..b67a2bc --- /dev/null +++ b/vendor/symfony/error-handler/Exception/SilencedErrorContext.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Exception; + +/** + * Data Object that represents a Silenced Error. + * + * @author Grégoire Pineau + */ +class SilencedErrorContext implements \JsonSerializable +{ + public int $count = 1; + + public function __construct( + private int $severity, + private string $file, + private int $line, + private array $trace = [], + int $count = 1, + ) { + $this->count = $count; + } + + public function getSeverity(): int + { + return $this->severity; + } + + public function getFile(): string + { + return $this->file; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTrace(): array + { + return $this->trace; + } + + public function jsonSerialize(): array + { + return [ + 'severity' => $this->severity, + 'file' => $this->file, + 'line' => $this->line, + 'trace' => $this->trace, + 'count' => $this->count, + ]; + } +} diff --git a/vendor/symfony/error-handler/Internal/TentativeTypes.php b/vendor/symfony/error-handler/Internal/TentativeTypes.php new file mode 100644 index 0000000..1e8afe3 --- /dev/null +++ b/vendor/symfony/error-handler/Internal/TentativeTypes.php @@ -0,0 +1,1643 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Internal; + +/** + * This class has been generated by extract-tentative-return-types.php. + * + * @internal + */ +class TentativeTypes +{ + public const RETURN_TYPES = [ + 'CURLFile' => [ + 'getFilename' => 'string', + 'getMimeType' => 'string', + 'getPostFilename' => 'string', + 'setMimeType' => 'void', + 'setPostFilename' => 'void', + ], + 'DateTimeInterface' => [ + 'format' => 'string', + 'getTimezone' => 'DateTimeZone|false', + 'getOffset' => 'int', + 'getTimestamp' => 'int', + 'diff' => 'DateInterval', + '__wakeup' => 'void', + ], + 'DateTime' => [ + '__wakeup' => 'void', + '__set_state' => 'DateTime', + 'createFromImmutable' => 'static', + 'createFromFormat' => 'DateTime|false', + 'getLastErrors' => 'array|false', + 'format' => 'string', + 'modify' => 'DateTime|false', + 'add' => 'DateTime', + 'sub' => 'DateTime', + 'getTimezone' => 'DateTimeZone|false', + 'setTimezone' => 'DateTime', + 'getOffset' => 'int', + 'setTime' => 'DateTime', + 'setDate' => 'DateTime', + 'setISODate' => 'DateTime', + 'setTimestamp' => 'DateTime', + 'getTimestamp' => 'int', + 'diff' => 'DateInterval', + ], + 'DateTimeImmutable' => [ + '__wakeup' => 'void', + '__set_state' => 'DateTimeImmutable', + 'createFromFormat' => 'DateTimeImmutable|false', + 'getLastErrors' => 'array|false', + 'format' => 'string', + 'getTimezone' => 'DateTimeZone|false', + 'getOffset' => 'int', + 'getTimestamp' => 'int', + 'diff' => 'DateInterval', + 'modify' => 'DateTimeImmutable|false', + 'add' => 'DateTimeImmutable', + 'sub' => 'DateTimeImmutable', + 'setTimezone' => 'DateTimeImmutable', + 'setTime' => 'DateTimeImmutable', + 'setDate' => 'DateTimeImmutable', + 'setISODate' => 'DateTimeImmutable', + 'setTimestamp' => 'DateTimeImmutable', + 'createFromMutable' => 'static', + ], + 'DateTimeZone' => [ + 'getName' => 'string', + 'getOffset' => 'int', + 'getTransitions' => 'array|false', + 'getLocation' => 'array|false', + 'listAbbreviations' => 'array', + 'listIdentifiers' => 'array', + '__wakeup' => 'void', + '__set_state' => 'DateTimeZone', + ], + 'DateInterval' => [ + 'createFromDateString' => 'DateInterval|false', + 'format' => 'string', + '__wakeup' => 'void', + '__set_state' => 'DateInterval', + ], + 'DatePeriod' => [ + 'getStartDate' => 'DateTimeInterface', + 'getEndDate' => '?DateTimeInterface', + 'getDateInterval' => 'DateInterval', + 'getRecurrences' => '?int', + '__wakeup' => 'void', + '__set_state' => 'DatePeriod', + ], + 'DOMNode' => [ + 'C14N' => 'string|false', + 'C14NFile' => 'int|false', + 'getLineNo' => 'int', + 'getNodePath' => '?string', + 'hasAttributes' => 'bool', + 'hasChildNodes' => 'bool', + 'isDefaultNamespace' => 'bool', + 'isSameNode' => 'bool', + 'isSupported' => 'bool', + 'lookupNamespaceURI' => '?string', + 'lookupPrefix' => '?string', + 'normalize' => 'void', + ], + 'DOMImplementation' => [ + 'getFeature' => 'never', + 'hasFeature' => 'bool', + ], + 'DOMDocumentFragment' => [ + 'appendXML' => 'bool', + ], + 'DOMNodeList' => [ + 'count' => 'int', + ], + 'DOMCharacterData' => [ + 'appendData' => 'bool', + 'insertData' => 'bool', + 'deleteData' => 'bool', + 'replaceData' => 'bool', + ], + 'DOMAttr' => [ + 'isId' => 'bool', + ], + 'DOMElement' => [ + 'getAttribute' => 'string', + 'getAttributeNS' => 'string', + 'getElementsByTagName' => 'DOMNodeList', + 'getElementsByTagNameNS' => 'DOMNodeList', + 'hasAttribute' => 'bool', + 'hasAttributeNS' => 'bool', + 'removeAttribute' => 'bool', + 'removeAttributeNS' => 'void', + 'setAttributeNS' => 'void', + 'setIdAttribute' => 'void', + 'setIdAttributeNS' => 'void', + 'setIdAttributeNode' => 'void', + ], + 'DOMDocument' => [ + 'createComment' => 'DOMComment', + 'createDocumentFragment' => 'DOMDocumentFragment', + 'createTextNode' => 'DOMText', + 'getElementById' => '?DOMElement', + 'getElementsByTagName' => 'DOMNodeList', + 'getElementsByTagNameNS' => 'DOMNodeList', + 'normalizeDocument' => 'void', + 'registerNodeClass' => 'bool', + 'save' => 'int|false', + 'saveHTML' => 'string|false', + 'saveHTMLFile' => 'int|false', + 'saveXML' => 'string|false', + 'schemaValidate' => 'bool', + 'schemaValidateSource' => 'bool', + 'relaxNGValidate' => 'bool', + 'relaxNGValidateSource' => 'bool', + 'validate' => 'bool', + 'xinclude' => 'int|false', + ], + 'DOMText' => [ + 'isWhitespaceInElementContent' => 'bool', + 'isElementContentWhitespace' => 'bool', + ], + 'DOMNamedNodeMap' => [ + 'getNamedItem' => '?DOMNode', + 'getNamedItemNS' => '?DOMNode', + 'item' => '?DOMNode', + 'count' => 'int', + ], + 'DOMXPath' => [ + 'evaluate' => 'mixed', + 'query' => 'mixed', + 'registerNamespace' => 'bool', + 'registerPhpFunctions' => 'void', + ], + 'finfo' => [ + 'file' => 'string|false', + 'buffer' => 'string|false', + ], + 'IntlPartsIterator' => [ + 'getBreakIterator' => 'IntlBreakIterator', + 'getRuleStatus' => 'int', + ], + 'IntlBreakIterator' => [ + 'createCharacterInstance' => '?IntlBreakIterator', + 'createCodePointInstance' => 'IntlCodePointBreakIterator', + 'createLineInstance' => '?IntlBreakIterator', + 'createSentenceInstance' => '?IntlBreakIterator', + 'createTitleInstance' => '?IntlBreakIterator', + 'createWordInstance' => '?IntlBreakIterator', + 'current' => 'int', + 'first' => 'int', + 'following' => 'int', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + 'getLocale' => 'string|false', + 'getPartsIterator' => 'IntlPartsIterator', + 'getText' => '?string', + 'isBoundary' => 'bool', + 'last' => 'int', + 'next' => 'int', + 'preceding' => 'int', + 'previous' => 'int', + 'setText' => '?bool', + ], + 'IntlRuleBasedBreakIterator' => [ + 'getBinaryRules' => 'string|false', + 'getRules' => 'string|false', + 'getRuleStatus' => 'int', + 'getRuleStatusVec' => 'array|false', + ], + 'IntlCodePointBreakIterator' => [ + 'getLastCodePoint' => 'int', + ], + 'IntlCalendar' => [ + 'createInstance' => '?IntlCalendar', + 'equals' => 'bool', + 'fieldDifference' => 'int|false', + 'add' => 'bool', + 'after' => 'bool', + 'before' => 'bool', + 'fromDateTime' => '?IntlCalendar', + 'get' => 'int|false', + 'getActualMaximum' => 'int|false', + 'getActualMinimum' => 'int|false', + 'getAvailableLocales' => 'array', + 'getDayOfWeekType' => 'int|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + 'getFirstDayOfWeek' => 'int|false', + 'getGreatestMinimum' => 'int|false', + 'getKeywordValuesForLocale' => 'IntlIterator|false', + 'getLeastMaximum' => 'int|false', + 'getLocale' => 'string|false', + 'getMaximum' => 'int|false', + 'getMinimalDaysInFirstWeek' => 'int|false', + 'getMinimum' => 'int|false', + 'getNow' => 'float', + 'getRepeatedWallTimeOption' => 'int', + 'getSkippedWallTimeOption' => 'int', + 'getTime' => 'float|false', + 'getTimeZone' => 'IntlTimeZone|false', + 'getType' => 'string', + 'getWeekendTransition' => 'int|false', + 'inDaylightTime' => 'bool', + 'isEquivalentTo' => 'bool', + 'isLenient' => 'bool', + 'isWeekend' => 'bool', + 'roll' => 'bool', + 'isSet' => 'bool', + 'setTime' => 'bool', + 'setTimeZone' => 'bool', + 'toDateTime' => 'DateTime|false', + ], + 'IntlGregorianCalendar' => [ + 'setGregorianChange' => 'bool', + 'getGregorianChange' => 'float', + 'isLeapYear' => 'bool', + ], + 'Collator' => [ + 'create' => '?Collator', + 'compare' => 'int|false', + 'sort' => 'bool', + 'sortWithSortKeys' => 'bool', + 'asort' => 'bool', + 'getAttribute' => 'int|false', + 'setAttribute' => 'bool', + 'getStrength' => 'int', + 'getLocale' => 'string|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + 'getSortKey' => 'string|false', + ], + 'IntlIterator' => [ + 'current' => 'mixed', + 'key' => 'mixed', + 'next' => 'void', + 'rewind' => 'void', + 'valid' => 'bool', + ], + 'UConverter' => [ + 'convert' => 'string|false', + 'fromUCallback' => 'string|int|array|null', + 'getAliases' => 'array|false|null', + 'getAvailable' => 'array', + 'getDestinationEncoding' => 'string|false|null', + 'getDestinationType' => 'int|false|null', + 'getErrorCode' => 'int', + 'getErrorMessage' => '?string', + 'getSourceEncoding' => 'string|false|null', + 'getSourceType' => 'int|false|null', + 'getStandards' => '?array', + 'getSubstChars' => 'string|false|null', + 'reasonText' => 'string', + 'setDestinationEncoding' => 'bool', + 'setSourceEncoding' => 'bool', + 'setSubstChars' => 'bool', + 'toUCallback' => 'string|int|array|null', + 'transcode' => 'string|false', + ], + 'IntlDateFormatter' => [ + 'create' => '?IntlDateFormatter', + 'getDateType' => 'int|false', + 'getTimeType' => 'int|false', + 'getCalendar' => 'int|false', + 'setCalendar' => 'bool', + 'getTimeZoneId' => 'string|false', + 'getCalendarObject' => 'IntlCalendar|false|null', + 'getTimeZone' => 'IntlTimeZone|false', + 'setTimeZone' => '?bool', + 'setPattern' => 'bool', + 'getPattern' => 'string|false', + 'getLocale' => 'string|false', + 'setLenient' => 'void', + 'isLenient' => 'bool', + 'format' => 'string|false', + 'formatObject' => 'string|false', + 'parse' => 'int|float|false', + 'localtime' => 'array|false', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'NumberFormatter' => [ + 'create' => '?NumberFormatter', + 'format' => 'string|false', + 'parse' => 'int|float|false', + 'formatCurrency' => 'string|false', + 'parseCurrency' => 'float|false', + 'setAttribute' => 'bool', + 'getAttribute' => 'int|float|false', + 'setTextAttribute' => 'bool', + 'getTextAttribute' => 'string|false', + 'setSymbol' => 'bool', + 'getSymbol' => 'string|false', + 'setPattern' => 'bool', + 'getPattern' => 'string|false', + 'getLocale' => 'string|false', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'Locale' => [ + 'getDefault' => 'string', + 'getPrimaryLanguage' => '?string', + 'getScript' => '?string', + 'getRegion' => '?string', + 'getKeywords' => 'array|false|null', + 'getDisplayScript' => 'string|false', + 'getDisplayRegion' => 'string|false', + 'getDisplayName' => 'string|false', + 'getDisplayLanguage' => 'string|false', + 'getDisplayVariant' => 'string|false', + 'composeLocale' => 'string|false', + 'parseLocale' => '?array', + 'getAllVariants' => '?array', + 'filterMatches' => '?bool', + 'lookup' => '?string', + 'canonicalize' => '?string', + 'acceptFromHttp' => 'string|false', + ], + 'MessageFormatter' => [ + 'create' => '?MessageFormatter', + 'format' => 'string|false', + 'formatMessage' => 'string|false', + 'parse' => 'array|false', + 'parseMessage' => 'array|false', + 'setPattern' => 'bool', + 'getPattern' => 'string|false', + 'getLocale' => 'string', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'Normalizer' => [ + 'normalize' => 'string|false', + 'isNormalized' => 'bool', + 'getRawDecomposition' => '?string', + ], + 'ResourceBundle' => [ + 'create' => '?ResourceBundle', + 'get' => 'mixed', + 'count' => 'int', + 'getLocales' => 'array|false', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'Spoofchecker' => [ + 'isSuspicious' => 'bool', + 'areConfusable' => 'bool', + 'setAllowedLocales' => 'void', + 'setChecks' => 'void', + 'setRestrictionLevel' => 'void', + ], + 'IntlTimeZone' => [ + 'countEquivalentIDs' => 'int|false', + 'createDefault' => 'IntlTimeZone', + 'createEnumeration' => 'IntlIterator|false', + 'createTimeZone' => '?IntlTimeZone', + 'createTimeZoneIDEnumeration' => 'IntlIterator|false', + 'fromDateTimeZone' => '?IntlTimeZone', + 'getCanonicalID' => 'string|false', + 'getDisplayName' => 'string|false', + 'getDSTSavings' => 'int', + 'getEquivalentID' => 'string|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + 'getGMT' => 'IntlTimeZone', + 'getID' => 'string|false', + 'getOffset' => 'bool', + 'getRawOffset' => 'int', + 'getRegion' => 'string|false', + 'getTZDataVersion' => 'string|false', + 'getUnknown' => 'IntlTimeZone', + 'getWindowsID' => 'string|false', + 'getIDForWindowsID' => 'string|false', + 'hasSameRules' => 'bool', + 'toDateTimeZone' => 'DateTimeZone|false', + 'useDaylightTime' => 'bool', + ], + 'Transliterator' => [ + 'create' => '?Transliterator', + 'createFromRules' => '?Transliterator', + 'createInverse' => '?Transliterator', + 'listIDs' => 'array|false', + 'transliterate' => 'string|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + ], + 'IntlChar' => [ + 'hasBinaryProperty' => '?bool', + 'charAge' => '?array', + 'charDigitValue' => '?int', + 'charDirection' => '?int', + 'charFromName' => '?int', + 'charMirror' => 'int|string|null', + 'charName' => '?string', + 'charType' => '?int', + 'chr' => '?string', + 'digit' => 'int|false|null', + 'enumCharNames' => '?bool', + 'enumCharTypes' => 'void', + 'foldCase' => 'int|string|null', + 'forDigit' => 'int', + 'getBidiPairedBracket' => 'int|string|null', + 'getBlockCode' => '?int', + 'getCombiningClass' => '?int', + 'getFC_NFKC_Closure' => 'string|false|null', + 'getIntPropertyMaxValue' => 'int', + 'getIntPropertyMinValue' => 'int', + 'getIntPropertyValue' => '?int', + 'getNumericValue' => '?float', + 'getPropertyEnum' => 'int', + 'getPropertyName' => 'string|false', + 'getPropertyValueEnum' => 'int', + 'getPropertyValueName' => 'string|false', + 'getUnicodeVersion' => 'array', + 'isalnum' => '?bool', + 'isalpha' => '?bool', + 'isbase' => '?bool', + 'isblank' => '?bool', + 'iscntrl' => '?bool', + 'isdefined' => '?bool', + 'isdigit' => '?bool', + 'isgraph' => '?bool', + 'isIDIgnorable' => '?bool', + 'isIDPart' => '?bool', + 'isIDStart' => '?bool', + 'isISOControl' => '?bool', + 'isJavaIDPart' => '?bool', + 'isJavaIDStart' => '?bool', + 'isJavaSpaceChar' => '?bool', + 'islower' => '?bool', + 'isMirrored' => '?bool', + 'isprint' => '?bool', + 'ispunct' => '?bool', + 'isspace' => '?bool', + 'istitle' => '?bool', + 'isUAlphabetic' => '?bool', + 'isULowercase' => '?bool', + 'isupper' => '?bool', + 'isUUppercase' => '?bool', + 'isUWhiteSpace' => '?bool', + 'isWhitespace' => '?bool', + 'isxdigit' => '?bool', + 'ord' => '?int', + 'tolower' => 'int|string|null', + 'totitle' => 'int|string|null', + 'toupper' => 'int|string|null', + ], + 'JsonSerializable' => [ + 'jsonSerialize' => 'mixed', + ], + 'mysqli' => [ + 'autocommit' => 'bool', + 'begin_transaction' => 'bool', + 'change_user' => 'bool', + 'character_set_name' => 'string', + 'commit' => 'bool', + 'connect' => 'bool', + 'dump_debug_info' => 'bool', + 'get_charset' => '?object', + 'get_client_info' => 'string', + 'get_connection_stats' => 'array', + 'get_server_info' => 'string', + 'get_warnings' => 'mysqli_warning|false', + 'kill' => 'bool', + 'multi_query' => 'bool', + 'more_results' => 'bool', + 'next_result' => 'bool', + 'ping' => 'bool', + 'poll' => 'int|false', + 'prepare' => 'mysqli_stmt|false', + 'query' => 'mysqli_result|bool', + 'real_connect' => 'bool', + 'real_escape_string' => 'string', + 'reap_async_query' => 'mysqli_result|bool', + 'escape_string' => 'string', + 'real_query' => 'bool', + 'release_savepoint' => 'bool', + 'rollback' => 'bool', + 'savepoint' => 'bool', + 'select_db' => 'bool', + 'set_charset' => 'bool', + 'options' => 'bool', + 'set_opt' => 'bool', + 'stat' => 'string|false', + 'stmt_init' => 'mysqli_stmt|false', + 'store_result' => 'mysqli_result|false', + 'thread_safe' => 'bool', + 'use_result' => 'mysqli_result|false', + 'refresh' => 'bool', + ], + 'mysqli_result' => [ + 'close' => 'void', + 'free' => 'void', + 'data_seek' => 'bool', + 'fetch_field' => 'object|false', + 'fetch_fields' => 'array', + 'fetch_field_direct' => 'object|false', + 'fetch_all' => 'array', + 'fetch_array' => 'array|null|false', + 'fetch_assoc' => 'array|null|false', + 'fetch_object' => 'object|null|false', + 'fetch_row' => 'array|null|false', + 'field_seek' => 'bool', + 'free_result' => 'void', + ], + 'mysqli_stmt' => [ + 'attr_get' => 'int', + 'attr_set' => 'bool', + 'bind_param' => 'bool', + 'bind_result' => 'bool', + 'data_seek' => 'void', + 'execute' => 'bool', + 'fetch' => '?bool', + 'get_warnings' => 'mysqli_warning|false', + 'result_metadata' => 'mysqli_result|false', + 'more_results' => 'bool', + 'next_result' => 'bool', + 'num_rows' => 'int|string', + 'send_long_data' => 'bool', + 'free_result' => 'void', + 'reset' => 'bool', + 'prepare' => 'bool', + 'store_result' => 'bool', + 'get_result' => 'mysqli_result|false', + ], + 'OCILob' => [ + 'save' => 'bool', + 'import' => 'bool', + 'saveFile' => 'bool', + 'load' => 'string|false', + 'read' => 'string|false', + 'eof' => 'bool', + 'tell' => 'int|false', + 'rewind' => 'bool', + 'seek' => 'bool', + 'size' => 'int|false', + 'write' => 'int|false', + 'append' => 'bool', + 'truncate' => 'bool', + 'erase' => 'int|false', + 'flush' => 'bool', + 'setBuffering' => 'bool', + 'getBuffering' => 'bool', + 'writeToFile' => 'bool', + 'export' => 'bool', + 'writeTemporary' => 'bool', + 'close' => 'bool', + 'free' => 'bool', + ], + 'OCICollection' => [ + 'free' => 'bool', + 'append' => 'bool', + 'getElem' => 'string|float|null|false', + 'assign' => 'bool', + 'assignElem' => 'bool', + 'size' => 'int|false', + 'max' => 'int|false', + 'trim' => 'bool', + ], + 'PDO' => [ + 'beginTransaction' => 'bool', + 'commit' => 'bool', + 'errorCode' => '?string', + 'errorInfo' => 'array', + 'exec' => 'int|false', + 'getAttribute' => 'mixed', + 'getAvailableDrivers' => 'array', + 'inTransaction' => 'bool', + 'lastInsertId' => 'string|false', + 'prepare' => 'PDOStatement|false', + 'query' => 'PDOStatement|false', + 'quote' => 'string|false', + 'rollBack' => 'bool', + 'setAttribute' => 'bool', + ], + 'PDOStatement' => [ + 'bindColumn' => 'bool', + 'bindParam' => 'bool', + 'bindValue' => 'bool', + 'closeCursor' => 'bool', + 'columnCount' => 'int', + 'debugDumpParams' => '?bool', + 'errorCode' => '?string', + 'errorInfo' => 'array', + 'execute' => 'bool', + 'fetch' => 'mixed', + 'fetchAll' => 'array', + 'fetchColumn' => 'mixed', + 'fetchObject' => 'object|false', + 'getAttribute' => 'mixed', + 'getColumnMeta' => 'array|false', + 'nextRowset' => 'bool', + 'rowCount' => 'int', + 'setAttribute' => 'bool', + ], + 'PDO_PGSql_Ext' => [ + 'pgsqlCopyFromArray' => 'bool', + 'pgsqlCopyFromFile' => 'bool', + 'pgsqlCopyToArray' => 'array|false', + 'pgsqlCopyToFile' => 'bool', + 'pgsqlLOBCreate' => 'string|false', + 'pgsqlLOBUnlink' => 'bool', + 'pgsqlGetNotify' => 'array|false', + 'pgsqlGetPid' => 'int', + ], + 'PDO_SQLite_Ext' => [ + 'sqliteCreateFunction' => 'bool', + 'sqliteCreateAggregate' => 'bool', + 'sqliteCreateCollation' => 'bool', + ], + 'Phar' => [ + 'addEmptyDir' => 'void', + 'addFile' => 'void', + 'addFromString' => 'void', + 'buildFromDirectory' => 'array', + 'buildFromIterator' => 'array', + 'compressFiles' => 'void', + 'compress' => '?Phar', + 'decompress' => '?Phar', + 'convertToExecutable' => '?Phar', + 'convertToData' => '?PharData', + 'count' => 'int', + 'extractTo' => 'bool', + 'getAlias' => '?string', + 'getPath' => 'string', + 'getMetadata' => 'mixed', + 'getModified' => 'bool', + 'getSignature' => 'array|false', + 'getStub' => 'string', + 'getVersion' => 'string', + 'hasMetadata' => 'bool', + 'isBuffering' => 'bool', + 'isCompressed' => 'int|false', + 'isFileFormat' => 'bool', + 'isWritable' => 'bool', + 'offsetExists' => 'bool', + 'offsetGet' => 'SplFileInfo', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'setAlias' => 'bool', + 'setDefaultStub' => 'bool', + 'setMetadata' => 'void', + 'setSignatureAlgorithm' => 'void', + 'startBuffering' => 'void', + 'stopBuffering' => 'void', + ], + 'PharData' => [ + 'addEmptyDir' => 'void', + 'addFile' => 'void', + 'addFromString' => 'void', + 'buildFromDirectory' => 'array', + 'buildFromIterator' => 'array', + 'compressFiles' => 'void', + 'compress' => '?PharData', + 'decompress' => '?PharData', + 'convertToExecutable' => '?Phar', + 'convertToData' => '?PharData', + 'count' => 'int', + 'extractTo' => 'bool', + 'getAlias' => '?string', + 'getPath' => 'string', + 'getMetadata' => 'mixed', + 'getModified' => 'bool', + 'getSignature' => 'array|false', + 'getStub' => 'string', + 'getVersion' => 'string', + 'hasMetadata' => 'bool', + 'isBuffering' => 'bool', + 'isCompressed' => 'int|false', + 'isFileFormat' => 'bool', + 'isWritable' => 'bool', + 'offsetExists' => 'bool', + 'offsetGet' => 'SplFileInfo', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'setAlias' => 'bool', + 'setDefaultStub' => 'bool', + 'setMetadata' => 'void', + 'setSignatureAlgorithm' => 'void', + 'startBuffering' => 'void', + 'stopBuffering' => 'void', + ], + 'PharFileInfo' => [ + 'chmod' => 'void', + 'getCompressedSize' => 'int', + 'getCRC32' => 'int', + 'getContent' => 'string', + 'getMetadata' => 'mixed', + 'getPharFlags' => 'int', + 'hasMetadata' => 'bool', + 'isCompressed' => 'bool', + 'isCRCChecked' => 'bool', + 'setMetadata' => 'void', + ], + 'Reflection' => [ + 'getModifierNames' => 'array', + ], + 'ReflectionFunctionAbstract' => [ + 'inNamespace' => 'bool', + 'isClosure' => 'bool', + 'isDeprecated' => 'bool', + 'isInternal' => 'bool', + 'isUserDefined' => 'bool', + 'isGenerator' => 'bool', + 'isVariadic' => 'bool', + 'isStatic' => 'bool', + 'getClosureThis' => '?object', + 'getClosureCalledClass' => '?ReflectionClass', + 'getClosureScopeClass' => '?ReflectionClass', + 'getDocComment' => 'string|false', + 'getEndLine' => 'int|false', + 'getExtension' => '?ReflectionExtension', + 'getExtensionName' => 'string|false', + 'getFileName' => 'string|false', + 'getName' => 'string', + 'getNamespaceName' => 'string', + 'getNumberOfParameters' => 'int', + 'getNumberOfRequiredParameters' => 'int', + 'getParameters' => 'array', + 'getShortName' => 'string', + 'getStartLine' => 'int|false', + 'getStaticVariables' => 'array', + 'returnsReference' => 'bool', + 'hasReturnType' => 'bool', + 'getReturnType' => '?ReflectionType', + ], + 'ReflectionFunction' => [ + 'isDisabled' => 'bool', + 'invoke' => 'mixed', + 'invokeArgs' => 'mixed', + 'getClosure' => 'Closure', + 'getExecutingLine' => 'int', + 'getExecutingFile' => 'string', + 'getTrace' => 'array', + 'getFunction' => 'ReflectionFunctionAbstract', + 'getThis' => '?object', + 'getExecutingGenerator' => 'Generator', + ], + 'ReflectionMethod' => [ + 'isPublic' => 'bool', + 'isPrivate' => 'bool', + 'isProtected' => 'bool', + 'isAbstract' => 'bool', + 'isFinal' => 'bool', + 'isConstructor' => 'bool', + 'isDestructor' => 'bool', + 'getClosure' => 'Closure', + 'getModifiers' => 'int', + 'invoke' => 'mixed', + 'invokeArgs' => 'mixed', + 'getDeclaringClass' => 'ReflectionClass', + 'getPrototype' => 'ReflectionMethod', + 'setAccessible' => 'void', + ], + 'ReflectionClass' => [ + 'getName' => 'string', + 'isInternal' => 'bool', + 'isUserDefined' => 'bool', + 'isAnonymous' => 'bool', + 'isInstantiable' => 'bool', + 'isCloneable' => 'bool', + 'getFileName' => 'string|false', + 'getStartLine' => 'int|false', + 'getEndLine' => 'int|false', + 'getDocComment' => 'string|false', + 'getConstructor' => '?ReflectionMethod', + 'hasMethod' => 'bool', + 'getMethod' => 'ReflectionMethod', + 'getMethods' => 'array', + 'hasProperty' => 'bool', + 'getProperty' => 'ReflectionProperty', + 'getProperties' => 'array', + 'hasConstant' => 'bool', + 'getConstants' => 'array', + 'getReflectionConstants' => 'array', + 'getConstant' => 'mixed', + 'getReflectionConstant' => 'ReflectionClassConstant|false', + 'getInterfaces' => 'array', + 'getInterfaceNames' => 'array', + 'isInterface' => 'bool', + 'getTraits' => 'array', + 'getTraitNames' => 'array', + 'getTraitAliases' => 'array', + 'isTrait' => 'bool', + 'isAbstract' => 'bool', + 'isFinal' => 'bool', + 'getModifiers' => 'int', + 'isInstance' => 'bool', + 'newInstance' => 'object', + 'newInstanceWithoutConstructor' => 'object', + 'newInstanceArgs' => '?object', + 'getParentClass' => 'ReflectionClass|false', + 'isSubclassOf' => 'bool', + 'getStaticProperties' => '?array', + 'getStaticPropertyValue' => 'mixed', + 'setStaticPropertyValue' => 'void', + 'getDefaultProperties' => 'array', + 'isIterable' => 'bool', + 'isIterateable' => 'bool', + 'implementsInterface' => 'bool', + 'getExtension' => '?ReflectionExtension', + 'getExtensionName' => 'string|false', + 'inNamespace' => 'bool', + 'getNamespaceName' => 'string', + 'getShortName' => 'string', + ], + 'ReflectionProperty' => [ + 'getName' => 'string', + 'getValue' => 'mixed', + 'setValue' => 'void', + 'isInitialized' => 'bool', + 'isPublic' => 'bool', + 'isPrivate' => 'bool', + 'isProtected' => 'bool', + 'isStatic' => 'bool', + 'isDefault' => 'bool', + 'getModifiers' => 'int', + 'getDeclaringClass' => 'ReflectionClass', + 'getDocComment' => 'string|false', + 'setAccessible' => 'void', + 'getType' => '?ReflectionType', + 'hasType' => 'bool', + 'getDefaultValue' => 'mixed', + ], + 'ReflectionClassConstant' => [ + 'getName' => 'string', + 'getValue' => 'mixed', + 'isPublic' => 'bool', + 'isPrivate' => 'bool', + 'isProtected' => 'bool', + 'getModifiers' => 'int', + 'getDeclaringClass' => 'ReflectionClass', + 'getDocComment' => 'string|false', + ], + 'ReflectionParameter' => [ + 'getName' => 'string', + 'isPassedByReference' => 'bool', + 'canBePassedByValue' => 'bool', + 'getDeclaringFunction' => 'ReflectionFunctionAbstract', + 'getDeclaringClass' => '?ReflectionClass', + 'getClass' => '?ReflectionClass', + 'hasType' => 'bool', + 'getType' => '?ReflectionType', + 'isArray' => 'bool', + 'isCallable' => 'bool', + 'allowsNull' => 'bool', + 'getPosition' => 'int', + 'isOptional' => 'bool', + 'isDefaultValueAvailable' => 'bool', + 'getDefaultValue' => 'mixed', + 'isDefaultValueConstant' => 'bool', + 'getDefaultValueConstantName' => '?string', + 'isVariadic' => 'bool', + ], + 'ReflectionType' => [ + 'allowsNull' => 'bool', + ], + 'ReflectionNamedType' => [ + 'getName' => 'string', + 'isBuiltin' => 'bool', + ], + 'ReflectionExtension' => [ + 'getName' => 'string', + 'getVersion' => '?string', + 'getFunctions' => 'array', + 'getConstants' => 'array', + 'getINIEntries' => 'array', + 'getClasses' => 'array', + 'getClassNames' => 'array', + 'getDependencies' => 'array', + 'info' => 'void', + 'isPersistent' => 'bool', + 'isTemporary' => 'bool', + ], + 'ReflectionZendExtension' => [ + 'getName' => 'string', + 'getVersion' => 'string', + 'getAuthor' => 'string', + 'getURL' => 'string', + 'getCopyright' => 'string', + ], + 'SessionHandlerInterface' => [ + 'open' => 'bool', + 'close' => 'bool', + 'read' => 'string|false', + 'write' => 'bool', + 'destroy' => 'bool', + 'gc' => 'int|false', + ], + 'SessionIdInterface' => [ + 'create_sid' => 'string', + ], + 'SessionUpdateTimestampHandlerInterface' => [ + 'validateId' => 'bool', + 'updateTimestamp' => 'bool', + ], + 'SessionHandler' => [ + 'open' => 'bool', + 'close' => 'bool', + 'read' => 'string|false', + 'write' => 'bool', + 'destroy' => 'bool', + 'gc' => 'int|false', + 'create_sid' => 'string', + ], + 'SimpleXMLElement' => [ + 'xpath' => 'array|null|false', + 'registerXPathNamespace' => 'bool', + 'asXML' => 'string|bool', + 'saveXML' => 'string|bool', + 'getNamespaces' => 'array', + 'getDocNamespaces' => 'array|false', + 'children' => '?SimpleXMLElement', + 'attributes' => '?SimpleXMLElement', + 'addChild' => '?SimpleXMLElement', + 'addAttribute' => 'void', + 'getName' => 'string', + 'count' => 'int', + 'rewind' => 'void', + 'valid' => 'bool', + 'current' => 'SimpleXMLElement', + 'key' => 'string', + 'next' => 'void', + 'hasChildren' => 'bool', + 'getChildren' => '?SimpleXMLElement', + ], + 'SNMP' => [ + 'close' => 'bool', + 'setSecurity' => 'bool', + 'get' => 'mixed', + 'getnext' => 'mixed', + 'walk' => 'array|false', + 'set' => 'bool', + 'getErrno' => 'int', + 'getError' => 'string', + ], + 'SoapServer' => [ + 'fault' => 'void', + 'addSoapHeader' => 'void', + 'setPersistence' => 'void', + 'setClass' => 'void', + 'setObject' => 'void', + 'getFunctions' => 'array', + 'addFunction' => 'void', + 'handle' => 'void', + ], + 'SoapClient' => [ + '__call' => 'mixed', + '__soapCall' => 'mixed', + '__getFunctions' => '?array', + '__getTypes' => '?array', + '__getLastRequest' => '?string', + '__getLastResponse' => '?string', + '__getLastRequestHeaders' => '?string', + '__getLastResponseHeaders' => '?string', + '__doRequest' => '?string', + '__setCookie' => 'void', + '__getCookies' => 'array', + '__setSoapHeaders' => 'bool', + '__setLocation' => '?string', + ], + 'ArrayObject' => [ + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'append' => 'void', + 'getArrayCopy' => 'array', + 'count' => 'int', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'asort' => 'bool', + 'ksort' => 'bool', + 'uasort' => 'bool', + 'uksort' => 'bool', + 'natsort' => 'bool', + 'natcasesort' => 'bool', + 'unserialize' => 'void', + 'serialize' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + 'getIterator' => 'Iterator', + 'exchangeArray' => 'array', + 'setIteratorClass' => 'void', + 'getIteratorClass' => 'string', + '__debugInfo' => 'array', + ], + 'ArrayIterator' => [ + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'append' => 'void', + 'getArrayCopy' => 'array', + 'count' => 'int', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'asort' => 'bool', + 'ksort' => 'bool', + 'uasort' => 'bool', + 'uksort' => 'bool', + 'natsort' => 'bool', + 'natcasesort' => 'bool', + 'unserialize' => 'void', + 'serialize' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'string|int|null', + 'next' => 'void', + 'valid' => 'bool', + 'seek' => 'void', + '__debugInfo' => 'array', + ], + 'RecursiveArrayIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveArrayIterator', + ], + 'SplFileInfo' => [ + 'getPath' => 'string', + 'getFilename' => 'string', + 'getExtension' => 'string', + 'getBasename' => 'string', + 'getPathname' => 'string', + 'getPerms' => 'int|false', + 'getInode' => 'int|false', + 'getSize' => 'int|false', + 'getOwner' => 'int|false', + 'getGroup' => 'int|false', + 'getATime' => 'int|false', + 'getMTime' => 'int|false', + 'getCTime' => 'int|false', + 'getType' => 'string|false', + 'isWritable' => 'bool', + 'isReadable' => 'bool', + 'isExecutable' => 'bool', + 'isFile' => 'bool', + 'isDir' => 'bool', + 'isLink' => 'bool', + 'getLinkTarget' => 'string|false', + 'getRealPath' => 'string|false', + 'getFileInfo' => 'SplFileInfo', + 'getPathInfo' => '?SplFileInfo', + 'openFile' => 'SplFileObject', + 'setFileClass' => 'void', + 'setInfoClass' => 'void', + '__debugInfo' => 'array', + '_bad_state_ex' => 'void', + ], + 'DirectoryIterator' => [ + 'getFilename' => 'string', + 'getExtension' => 'string', + 'getBasename' => 'string', + 'isDot' => 'bool', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + 'seek' => 'void', + ], + 'FilesystemIterator' => [ + 'rewind' => 'void', + 'key' => 'string', + 'current' => 'string|SplFileInfo|FilesystemIterator', + 'getFlags' => 'int', + 'setFlags' => 'void', + ], + 'RecursiveDirectoryIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => 'RecursiveDirectoryIterator', + 'getSubPath' => 'string', + 'getSubPathname' => 'string', + ], + 'GlobIterator' => [ + 'count' => 'int', + ], + 'SplFileObject' => [ + 'rewind' => 'void', + 'eof' => 'bool', + 'valid' => 'bool', + 'fgets' => 'string', + 'fread' => 'string|false', + 'fgetcsv' => 'array|false', + 'fputcsv' => 'int|false', + 'setCsvControl' => 'void', + 'getCsvControl' => 'array', + 'flock' => 'bool', + 'fflush' => 'bool', + 'ftell' => 'int|false', + 'fseek' => 'int', + 'fgetc' => 'string|false', + 'fpassthru' => 'int', + 'fscanf' => 'array|int|null', + 'fwrite' => 'int|false', + 'fstat' => 'array', + 'ftruncate' => 'bool', + 'current' => 'string|array|false', + 'key' => 'int', + 'next' => 'void', + 'setFlags' => 'void', + 'getFlags' => 'int', + 'setMaxLineLen' => 'void', + 'getMaxLineLen' => 'int', + 'hasChildren' => 'false', + 'getChildren' => 'null', + 'seek' => 'void', + 'getCurrentLine' => 'string', + ], + 'SplDoublyLinkedList' => [ + 'add' => 'void', + 'pop' => 'mixed', + 'shift' => 'mixed', + 'push' => 'void', + 'unshift' => 'void', + 'top' => 'mixed', + 'bottom' => 'mixed', + '__debugInfo' => 'array', + 'count' => 'int', + 'isEmpty' => 'bool', + 'setIteratorMode' => 'int', + 'getIteratorMode' => 'int', + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'int', + 'prev' => 'void', + 'next' => 'void', + 'valid' => 'bool', + 'unserialize' => 'void', + 'serialize' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + ], + 'SplQueue' => [ + 'enqueue' => 'void', + 'dequeue' => 'mixed', + ], + 'SplFixedArray' => [ + '__wakeup' => 'void', + 'count' => 'int', + 'toArray' => 'array', + 'fromArray' => 'SplFixedArray', + 'getSize' => 'int', + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + ], + 'SplPriorityQueue' => [ + 'compare' => 'int', + 'setExtractFlags' => 'int', + 'top' => 'mixed', + 'extract' => 'mixed', + 'count' => 'int', + 'isEmpty' => 'bool', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'int', + 'next' => 'void', + 'valid' => 'bool', + 'isCorrupted' => 'bool', + 'getExtractFlags' => 'int', + '__debugInfo' => 'array', + ], + 'SplHeap' => [ + 'extract' => 'mixed', + 'insert' => 'bool', + 'top' => 'mixed', + 'count' => 'int', + 'isEmpty' => 'bool', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'int', + 'next' => 'void', + 'valid' => 'bool', + 'recoverFromCorruption' => 'bool', + 'compare' => 'int', + 'isCorrupted' => 'bool', + '__debugInfo' => 'array', + ], + 'SplMinHeap' => [ + 'compare' => 'int', + ], + 'SplMaxHeap' => [ + 'compare' => 'int', + ], + 'EmptyIterator' => [ + 'current' => 'never', + 'next' => 'void', + 'key' => 'never', + 'valid' => 'false', + 'rewind' => 'void', + ], + 'CallbackFilterIterator' => [ + 'accept' => 'bool', + ], + 'RecursiveCallbackFilterIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => 'RecursiveCallbackFilterIterator', + ], + 'RecursiveIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveIterator', + ], + 'RecursiveIteratorIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + 'getDepth' => 'int', + 'getSubIterator' => '?RecursiveIterator', + 'getInnerIterator' => 'RecursiveIterator', + 'beginIteration' => 'void', + 'endIteration' => 'void', + 'callHasChildren' => 'bool', + 'callGetChildren' => '?RecursiveIterator', + 'beginChildren' => 'void', + 'endChildren' => 'void', + 'nextElement' => 'void', + 'setMaxDepth' => 'void', + 'getMaxDepth' => 'int|false', + ], + 'OuterIterator' => [ + 'getInnerIterator' => '?Iterator', + ], + 'IteratorIterator' => [ + 'getInnerIterator' => '?Iterator', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + ], + 'FilterIterator' => [ + 'accept' => 'bool', + 'rewind' => 'void', + 'next' => 'void', + ], + 'RecursiveFilterIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveFilterIterator', + ], + 'ParentIterator' => [ + 'accept' => 'bool', + ], + 'SeekableIterator' => [ + 'seek' => 'void', + ], + 'LimitIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'next' => 'void', + 'seek' => 'int', + 'getPosition' => 'int', + ], + 'CachingIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'next' => 'void', + 'hasNext' => 'bool', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'offsetExists' => 'bool', + 'getCache' => 'array', + 'count' => 'int', + ], + 'RecursiveCachingIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveCachingIterator', + ], + 'NoRewindIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + ], + 'AppendIterator' => [ + 'append' => 'void', + 'rewind' => 'void', + 'valid' => 'bool', + 'current' => 'mixed', + 'next' => 'void', + 'getIteratorIndex' => '?int', + 'getArrayIterator' => 'ArrayIterator', + ], + 'InfiniteIterator' => [ + 'next' => 'void', + ], + 'RegexIterator' => [ + 'accept' => 'bool', + 'getMode' => 'int', + 'setMode' => 'void', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'getRegex' => 'string', + 'getPregFlags' => 'int', + 'setPregFlags' => 'void', + ], + 'RecursiveRegexIterator' => [ + 'accept' => 'bool', + 'hasChildren' => 'bool', + 'getChildren' => 'RecursiveRegexIterator', + ], + 'RecursiveTreeIterator' => [ + 'key' => 'mixed', + 'current' => 'mixed', + 'getPrefix' => 'string', + 'setPostfix' => 'void', + 'setPrefixPart' => 'void', + 'getEntry' => 'string', + 'getPostfix' => 'string', + ], + 'SplObserver' => [ + 'update' => 'void', + ], + 'SplSubject' => [ + 'attach' => 'void', + 'detach' => 'void', + 'notify' => 'void', + ], + 'SplObjectStorage' => [ + 'attach' => 'void', + 'detach' => 'void', + 'contains' => 'bool', + 'addAll' => 'int', + 'removeAll' => 'int', + 'removeAllExcept' => 'int', + 'getInfo' => 'mixed', + 'setInfo' => 'void', + 'count' => 'int', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'int', + 'current' => 'object', + 'next' => 'void', + 'unserialize' => 'void', + 'serialize' => 'string', + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'getHash' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + '__debugInfo' => 'array', + ], + 'MultipleIterator' => [ + 'getFlags' => 'int', + 'setFlags' => 'void', + 'attachIterator' => 'void', + 'detachIterator' => 'void', + 'containsIterator' => 'bool', + 'countIterators' => 'int', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'array', + 'current' => 'array', + 'next' => 'void', + '__debugInfo' => 'array', + ], + 'SQLite3' => [ + 'open' => 'void', + 'version' => 'array', + 'lastInsertRowID' => 'int', + 'lastErrorCode' => 'int', + 'lastExtendedErrorCode' => 'int', + 'lastErrorMsg' => 'string', + 'changes' => 'int', + 'busyTimeout' => 'bool', + 'loadExtension' => 'bool', + 'backup' => 'bool', + 'escapeString' => 'string', + 'prepare' => 'SQLite3Stmt|false', + 'exec' => 'bool', + 'query' => 'SQLite3Result|false', + 'querySingle' => 'mixed', + 'createFunction' => 'bool', + 'createAggregate' => 'bool', + 'createCollation' => 'bool', + 'enableExceptions' => 'bool', + 'enableExtendedResultCodes' => 'bool', + 'setAuthorizer' => 'bool', + ], + 'SQLite3Stmt' => [ + 'bindParam' => 'bool', + 'bindValue' => 'bool', + 'clear' => 'bool', + 'close' => 'bool', + 'execute' => 'SQLite3Result|false', + 'getSQL' => 'string|false', + 'paramCount' => 'int', + 'readOnly' => 'bool', + 'reset' => 'bool', + ], + 'SQLite3Result' => [ + 'numColumns' => 'int', + 'columnName' => 'string|false', + 'columnType' => 'int|false', + 'fetchArray' => 'array|false', + 'reset' => 'bool', + ], + 'Directory' => [ + 'close' => 'void', + 'rewind' => 'void', + 'read' => 'string|false', + ], + 'php_user_filter' => [ + 'filter' => 'int', + 'onCreate' => 'bool', + 'onClose' => 'void', + ], + 'tidy' => [ + 'getOpt' => 'string|int|bool', + 'cleanRepair' => 'bool', + 'parseFile' => 'bool', + 'parseString' => 'bool', + 'repairString' => 'string|false', + 'repairFile' => 'string|false', + 'diagnose' => 'bool', + 'getRelease' => 'string', + 'getConfig' => 'array', + 'getStatus' => 'int', + 'getHtmlVer' => 'int', + 'getOptDoc' => 'string|false', + 'isXhtml' => 'bool', + 'isXml' => 'bool', + 'root' => '?tidyNode', + 'head' => '?tidyNode', + 'html' => '?tidyNode', + 'body' => '?tidyNode', + ], + 'XMLReader' => [ + 'getAttribute' => '?string', + 'getAttributeNo' => '?string', + 'getAttributeNs' => '?string', + 'getParserProperty' => 'bool', + 'isValid' => 'bool', + 'lookupNamespace' => '?string', + 'moveToAttribute' => 'bool', + 'moveToAttributeNo' => 'bool', + 'moveToAttributeNs' => 'bool', + 'moveToElement' => 'bool', + 'moveToFirstAttribute' => 'bool', + 'moveToNextAttribute' => 'bool', + 'read' => 'bool', + 'next' => 'bool', + 'readInnerXml' => 'string', + 'readOuterXml' => 'string', + 'readString' => 'string', + 'setSchema' => 'bool', + 'setParserProperty' => 'bool', + 'setRelaxNGSchema' => 'bool', + 'setRelaxNGSchemaSource' => 'bool', + 'expand' => 'DOMNode|false', + ], + 'XMLWriter' => [ + 'openUri' => 'bool', + 'openMemory' => 'bool', + 'setIndent' => 'bool', + 'setIndentString' => 'bool', + 'startComment' => 'bool', + 'endComment' => 'bool', + 'startAttribute' => 'bool', + 'endAttribute' => 'bool', + 'writeAttribute' => 'bool', + 'startAttributeNs' => 'bool', + 'writeAttributeNs' => 'bool', + 'startElement' => 'bool', + 'endElement' => 'bool', + 'fullEndElement' => 'bool', + 'startElementNs' => 'bool', + 'writeElement' => 'bool', + 'writeElementNs' => 'bool', + 'startPi' => 'bool', + 'endPi' => 'bool', + 'writePi' => 'bool', + 'startCdata' => 'bool', + 'endCdata' => 'bool', + 'writeCdata' => 'bool', + 'text' => 'bool', + 'writeRaw' => 'bool', + 'startDocument' => 'bool', + 'endDocument' => 'bool', + 'writeComment' => 'bool', + 'startDtd' => 'bool', + 'endDtd' => 'bool', + 'writeDtd' => 'bool', + 'startDtdElement' => 'bool', + 'endDtdElement' => 'bool', + 'writeDtdElement' => 'bool', + 'startDtdAttlist' => 'bool', + 'endDtdAttlist' => 'bool', + 'writeDtdAttlist' => 'bool', + 'startDtdEntity' => 'bool', + 'endDtdEntity' => 'bool', + 'writeDtdEntity' => 'bool', + 'outputMemory' => 'string', + 'flush' => 'string|int', + ], + 'XSLTProcessor' => [ + 'importStylesheet' => 'bool', + 'transformToDoc' => 'DOMDocument|false', + 'transformToUri' => 'int', + 'transformToXml' => 'string|null|false', + 'setParameter' => 'bool', + 'getParameter' => 'string|false', + 'removeParameter' => 'bool', + 'hasExsltSupport' => 'bool', + 'registerPHPFunctions' => 'void', + 'setSecurityPrefs' => 'int', + 'getSecurityPrefs' => 'int', + ], + 'ZipArchive' => [ + 'open' => 'bool|int', + 'setPassword' => 'bool', + 'close' => 'bool', + 'count' => 'int', + 'getStatusString' => 'string', + 'addEmptyDir' => 'bool', + 'addFromString' => 'bool', + 'addFile' => 'bool', + 'replaceFile' => 'bool', + 'addGlob' => 'array|false', + 'addPattern' => 'array|false', + 'renameIndex' => 'bool', + 'renameName' => 'bool', + 'setArchiveComment' => 'bool', + 'getArchiveComment' => 'string|false', + 'setCommentIndex' => 'bool', + 'setCommentName' => 'bool', + 'setMtimeIndex' => 'bool', + 'setMtimeName' => 'bool', + 'getCommentIndex' => 'string|false', + 'getCommentName' => 'string|false', + 'deleteIndex' => 'bool', + 'deleteName' => 'bool', + 'statName' => 'array|false', + 'statIndex' => 'array|false', + 'locateName' => 'int|false', + 'getNameIndex' => 'string|false', + 'unchangeArchive' => 'bool', + 'unchangeAll' => 'bool', + 'unchangeIndex' => 'bool', + 'unchangeName' => 'bool', + 'extractTo' => 'bool', + 'getFromName' => 'string|false', + 'getFromIndex' => 'string|false', + 'setExternalAttributesName' => 'bool', + 'setExternalAttributesIndex' => 'bool', + 'getExternalAttributesName' => 'bool', + 'getExternalAttributesIndex' => 'bool', + 'setCompressionName' => 'bool', + 'setCompressionIndex' => 'bool', + 'setEncryptionName' => 'bool', + 'setEncryptionIndex' => 'bool', + 'registerProgressCallback' => 'bool', + 'registerCancelCallback' => 'bool', + ], + 'Exception' => [ + '__wakeup' => 'void', + ], + 'Error' => [ + '__wakeup' => 'void', + ], + 'IteratorAggregate' => [ + 'getIterator' => 'Traversable', + ], + 'Iterator' => [ + 'current' => 'mixed', + 'next' => 'void', + 'key' => 'mixed', + 'valid' => 'bool', + 'rewind' => 'void', + ], + 'ArrayAccess' => [ + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + ], + 'Countable' => [ + 'count' => 'int', + ], + ]; +} diff --git a/vendor/symfony/error-handler/LICENSE b/vendor/symfony/error-handler/LICENSE new file mode 100644 index 0000000..f37c76b --- /dev/null +++ b/vendor/symfony/error-handler/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-present Fabien Potencier + +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. diff --git a/vendor/symfony/error-handler/README.md b/vendor/symfony/error-handler/README.md new file mode 100644 index 0000000..68904dd --- /dev/null +++ b/vendor/symfony/error-handler/README.md @@ -0,0 +1,44 @@ +ErrorHandler Component +====================== + +The ErrorHandler component provides tools to manage errors and ease debugging PHP code. + +Getting Started +--------------- + +```bash +composer require symfony/error-handler +``` + +```php +use Symfony\Component\ErrorHandler\Debug; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\ErrorHandler\DebugClassLoader; + +Debug::enable(); + +// or enable only one feature +//ErrorHandler::register(); +//DebugClassLoader::enable(); + +// If you want a custom generic template when debug is not enabled +// HtmlErrorRenderer::setTemplate('/path/to/custom/error.html.php'); + +$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) { + // if any code executed inside this anonymous function fails, a PHP exception + // will be thrown, even if the code uses the '@' PHP silence operator + $data = json_decode(file_get_contents($filename), true); + $data['read_at'] = date($datetimeFormat); + file_put_contents($filename, json_encode($data)); + + return $data; +}); +``` + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/error-handler/Resources/assets/css/error.css b/vendor/symfony/error-handler/Resources/assets/css/error.css new file mode 100644 index 0000000..332d818 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/css/error.css @@ -0,0 +1,4 @@ +body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; } +.container { margin: 30px; max-width: 600px; } +h1 { color: #dc3545; font-size: 24px; } +h2 { font-size: 18px; } diff --git a/vendor/symfony/error-handler/Resources/assets/css/exception.css b/vendor/symfony/error-handler/Resources/assets/css/exception.css new file mode 100644 index 0000000..b40c58d --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/css/exception.css @@ -0,0 +1,359 @@ +/* This file is based on WebProfilerBundle/Resources/views/Profiler/profiler.css.twig. + If you make any change in this file, verify the same change is needed in the other file. */ +:root { + --font-sans-serif: Helvetica, Arial, sans-serif; + --page-background: #f9f9f9; + --color-text: #222; + /* when updating any of these colors, do the same in toolbar.css.twig */ + --color-success: #4f805d; + --color-warning: #a46a1f; + --color-error: #b0413e; + --color-muted: #999; + --tab-background: #f0f0f0; + --tab-border-color: #e5e5e5; + --tab-active-border-color: #d4d4d4; + --tab-color: #444; + --tab-active-background: #fff; + --tab-active-color: var(--color-text); + --tab-disabled-background: #f5f5f5; + --tab-disabled-color: #999; + --selected-badge-background: #e5e5e5; + --selected-badge-color: #525252; + --selected-badge-shadow: inset 0 0 0 1px #d4d4d4; + --selected-badge-warning-background: #fde496; + --selected-badge-warning-color: #785b02; + --selected-badge-warning-shadow: inset 0 0 0 1px #e6af05; + --selected-badge-danger-background: #FCE9ED; + --selected-badge-danger-color: #83122A; + --selected-badge-danger-shadow: inset 0 0 0 1px #F5B8C5; + --metric-value-background: #fff; + --metric-value-color: inherit; + --metric-unit-color: #999; + --metric-label-background: #e0e0e0; + --metric-label-color: inherit; + --table-border: #e0e0e0; + --table-background: #fff; + --table-header: #e0e0e0; + --trace-selected-background: #F7E5A1; + --tree-active-background: #F7E5A1; + --exception-title-color: var(--base-2); + --shadow: 0px 0px 1px rgba(128, 128, 128, .2); + --border: 1px solid #e0e0e0; + --background-error: var(--color-error); + --highlight-comment: #969896; + --highlight-default: #222222; + --highlight-keyword: #a71d5d; + --highlight-string: #183691; + --base-0: #fff; + --base-1: #f5f5f5; + --base-2: #e0e0e0; + --base-3: #ccc; + --base-4: #666; + --base-5: #444; + --base-6: #222; +} + +.theme-dark { + --page-background: #36393e; + --color-text: #e0e0e0; + --color-muted: #777; + --color-error: #d43934; + --tab-background: #404040; + --tab-border-color: #737373; + --tab-active-border-color: #171717; + --tab-color: var(--color-text); + --tab-active-background: #d4d4d4; + --tab-active-color: #262626; + --tab-disabled-background: var(--page-background); + --tab-disabled-color: #a3a3a3; + --selected-badge-background: #555; + --selected-badge-color: #ddd; + --selected-badge-shadow: none; + --selected-badge-warning-background: #fcd55f; + --selected-badge-warning-color: #785b02; + --selected-badge-warning-shadow: inset 0 0 0 1px #af8503; + --selected-badge-danger-background: #B41939; + --selected-badge-danger-color: #FCE9ED; + --selected-badge-danger-shadow: none; + --metric-value-background: #555; + --metric-value-color: inherit; + --metric-unit-color: #999; + --metric-label-background: #777; + --metric-label-color: #e0e0e0; + --trace-selected-background: #71663acc; + --table-border: #444; + --table-background: #333; + --table-header: #555; + --info-background: rgba(79, 148, 195, 0.5); + --tree-active-background: var(--metric-label-background); + --exception-title-color: var(--base-2); + --shadow: 0px 0px 1px rgba(32, 32, 32, .2); + --border: 1px solid #666; + --background-error: #b0413e; + --highlight-comment: #dedede; + --highlight-default: var(--base-6); + --highlight-keyword: #ff413c; + --highlight-string: #70a6fd; + --base-0: #2e3136; + --base-1: #444; + --base-2: #666; + --base-3: #666; + --base-4: #666; + --base-5: #e0e0e0; + --base-6: #f5f5f5; + --card-label-background: var(--tab-active-background); + --card-label-color: var(--tab-active-color); +} + +html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}summary{cursor: pointer} + +html { + /* always display the vertical scrollbar to avoid jumps when toggling contents */ + overflow-y: scroll; +} +body { background-color: var(--page-background); color: var(--base-6); font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; } + +a { cursor: pointer; text-decoration: none; } +a:hover { text-decoration: underline; } +abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } + +code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } + +table, tr, th, td { background: var(--base-0); border-collapse: collapse; vertical-align: top; } +table { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } +table th, table td { border: solid var(--base-2); border-width: 1px 0; padding: 8px 10px; } +table th { background-color: var(--base-2); font-weight: bold; text-align: left; } + +.m-t-5 { margin-top: 5px; } +.hidden-xs-down { display: none; } +.block { display: block; } +.full-width { width: 100%; } +.hidden { display: none; } +.prewrap { white-space: pre-wrap; } +.nowrap { white-space: nowrap; } +.newline { display: block; } +.break-long-words { word-wrap: break-word; overflow-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; hyphenate-character: ''; min-width: 0; } +.text-small { font-size: 12px !important; } +.text-muted { color: #999; } +.text-bold { font-weight: bold; } +.empty { border: 4px dashed var(--base-2); color: #999; margin: 1em 0; padding: .5em 2em; } + +.status-success { background: rgba(94, 151, 110, 0.3); } +.status-warning { background: rgba(240, 181, 24, 0.3); } +.status-error { background: rgba(176, 65, 62, 0.2); } +.status-success td, .status-warning td, .status-error td { background: transparent; } +tr.status-error td, tr.status-warning td { border-bottom: 1px solid var(--base-2); border-top: 1px solid var(--base-2); } +.status-warning .colored { color: #A46A1F; } +.status-error .colored { color: var(--color-error); } + +.sf-toggle { cursor: pointer; position: relative; } +.sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; } +.sf-toggle-content.sf-toggle-hidden { display: none; } +.sf-toggle-content.sf-toggle-visible { display: block; } +thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; } +.sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; } +.sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; } + +.tab-navigation { + background-color: var(--tab-background); + border-radius: 6px; + box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 5px var(--page-background); + display: inline-flex; + flex-wrap: wrap; + margin: 0 0 15px; + padding: 0; + user-select: none; + -webkit-user-select: none; +} +.sf-tabs-sm .tab-navigation { + box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 4px var(--page-background); + margin: 0 0 10px; +} +.tab-navigation .tab-control { + background: transparent; + border: 0; + box-shadow: none; + transition: box-shadow .05s ease-in, background-color .05s ease-in; + cursor: pointer; + font-size: 14px; + font-weight: 500; + line-height: 1.4; + margin: 0; + padding: 4px 14px; + position: relative; + text-align: center; + z-index: 1; +} +.sf-tabs-sm .tab-navigation .tab-control { + font-size: 13px; + padding: 2.5px 10px; +} +.tab-navigation .tab-control:before { + background: var(--tab-border-color); + bottom: 15%; + content: ""; + left: 0; + position: absolute; + top: 15%; + width: 1px; +} +.tab-navigation .tab-control:first-child:before, +.tab-navigation .tab-control.active + .tab-control:before, +.tab-navigation .tab-control.active:before { + width: 0; +} +.tab-navigation .tab-control .badge { + background: var(--selected-badge-background); + box-shadow: var(--selected-badge-shadow); + color: var(--selected-badge-color); + display: inline-block; + font-size: 12px; + font-weight: bold; + line-height: 1; + margin-left: 8px; + min-width: 10px; + padding: 2px 6px; + text-align: center; + white-space: nowrap; +} +.tab-navigation .tab-control.disabled { + color: var(--tab-disabled-color); +} +.tab-navigation .tab-control.active { + background-color: var(--tab-active-background); + border-radius: 6px; + box-shadow: inset 0 0 0 1.5px var(--tab-active-border-color); + color: var(--tab-active-color); + position: relative; + z-index: 1; +} +.theme-dark .tab-navigation li.active { + box-shadow: inset 0 0 0 1px var(--tab-border-color); +} +.tab-content > *:first-child { + margin-top: 0; +} +.tab-navigation .tab-control .badge.status-warning { + background: var(--selected-badge-warning-background); + box-shadow: var(--selected-badge-warning-shadow); + color: var(--selected-badge-warning-color); +} +.tab-navigation .tab-control .badge.status-error { + background: var(--selected-badge-danger-background); + box-shadow: var(--selected-badge-danger-shadow); + color: var(--selected-badge-danger-color); +} + +.sf-tabs .tab:not(:first-child) { display: none; } + +[data-filters] { position: relative; } +[data-filtered] { cursor: pointer; } +[data-filtered]:after { content: '\00a0\25BE'; } +[data-filtered]:hover .filter-list li { display: inline-flex; } +[class*="filter-hidden-"] { display: none; } +.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; } +.filter-list :after { content: ''; } +.filter-list li { + background: var(--tab-disabled-background); + border-bottom: var(--border); + color: var(--tab-disabled-color); + display: none; + list-style: none; + margin: 0; + padding: 5px 10px; + text-align: left; + font-weight: normal; +} +.filter-list li.active { + background: var(--tab-background); + color: var(--tab-color); +} +.filter-list li.last-active { + background: var(--tab-active-background); + color: var(--tab-active-color); +} + +.filter-list-level li { cursor: s-resize; } +.filter-list-level li.active { cursor: n-resize; } +.filter-list-level li.last-active { cursor: default; } +.filter-list-level li.last-active:before { content: '\2714\00a0'; } +.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } +.filter-list-choice li.active:before { color: unset; } + +.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } +.container::after { content: ""; display: table; clear: both; } + +header { background-color: #222; color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; } +header .container { display: flex; justify-content: space-between; } +.logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; } +.logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; } + +.help-link { margin-left: 15px; } +.help-link a { color: inherit; } +.help-link .icon svg { height: 15px; width: 15px; opacity: .7; vertical-align: -2px; } +.help-link a:hover { color: #EEE; text-decoration: none; } +.help-link a:hover svg { opacity: .9; } + +.exception-summary { background: var(--background-error); border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; } +.exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; } +.exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; } +.exception-metadata h2, .exception-metadata h2 > a { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; } +.exception-http small { font-size: 13px; opacity: .7; } +.exception-hierarchy { flex: 1; } +.exception-hierarchy .icon { margin: 0 3px; opacity: .7; } +.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; } + +.exception-without-message .exception-message-wrapper { display: none; } +.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 15px 8px; } +.exception-message { flex-grow: 1; } +.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } +.exception-message.long { font-size: 18px; } +.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } +.exception-message a:hover { border-bottom-color: #ffffff; } + +.exception-properties-wrapper { margin: .8em 0; } +.exception-properties { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); } +.exception-properties pre { margin: 0; padding: 0.2em 0; } + +.exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } + +.trace + .trace { margin-top: 30px; } +.trace-head { background-color: var(--base-2); padding: 10px; position: relative; } +.trace-head .trace-class { color: var(--base-6); font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } +.trace-head .trace-namespace { color: #999; display: block; font-size: 13px; } +.trace-head .icon { position: absolute; right: 0; top: 0; } +.trace-head .icon svg { fill: var(--base-5); height: 24px; width: 24px; } + +.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 0 0 1em; table-layout: fixed; } + +.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } + +.trace-line { position: relative; padding-top: 8px; padding-bottom: 8px; } +.trace-line + .trace-line { border-top: var(--border); } +.trace-line:hover { background: var(--base-1); } +.trace-line a { color: var(--base-6); } +.trace-line .icon { opacity: .4; position: absolute; left: 10px; } +.trace-line .icon svg { fill: var(--base-5); height: 16px; width: 16px; } +.trace-line .icon.icon-copy { left: auto; top: auto; padding-left: 5px; display: none } +.trace-line:hover .icon.icon-copy:not(.hidden) { display: inline-block } +.trace-line-header { padding-left: 36px; padding-right: 10px; } + +.trace-file-path, .trace-file-path a { color: var(--base-6); font-size: 13px; } +.trace-class { color: var(--color-error); } +.trace-type { padding: 0 2px; } +.trace-method { color: var(--color-error); font-weight: bold; } +.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } + +.trace-code { background: var(--base-0); font-size: 12px; margin: 10px 10px 2px 10px; padding: 10px; overflow-x: auto; white-space: nowrap; } +.trace-code ol { margin: 0; float: left; } +.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; } +.trace-code li + li { margin-top: 5px; } +.trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; } +.trace-code li code { color: var(--base-6); white-space: pre; } + +.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; } + +@media (min-width: 575px) { + .hidden-xs-down { display: initial; } + .help-link { margin-left: 30px; } +} diff --git a/vendor/symfony/error-handler/Resources/assets/css/exception_full.css b/vendor/symfony/error-handler/Resources/assets/css/exception_full.css new file mode 100644 index 0000000..fa77cb3 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/css/exception_full.css @@ -0,0 +1,128 @@ +.sf-reset .traces { + padding-bottom: 14px; +} +.sf-reset .traces li { + font-size: 12px; + color: #868686; + padding: 5px 4px; + list-style-type: decimal; + margin-left: 20px; +} +.sf-reset #logs .traces li.error { + font-style: normal; + color: #AA3333; + background: #f9ecec; +} +.sf-reset #logs .traces li.warning { + font-style: normal; + background: #ffcc00; +} +/* fix for Opera not liking empty
  • */ +.sf-reset .traces li:after { + content: "\00A0"; +} +.sf-reset .trace { + border: 1px solid #D3D3D3; + padding: 10px; + overflow: auto; + margin: 10px 0 20px; +} +.sf-reset .block-exception { + -moz-border-radius: 16px; + -webkit-border-radius: 16px; + border-radius: 16px; + margin-bottom: 20px; + background-color: #f6f6f6; + border: 1px solid #dfdfdf; + padding: 30px 28px; + word-wrap: break-word; + overflow: hidden; +} +.sf-reset .block-exception div { + color: #313131; + font-size: 10px; +} +.sf-reset .block-exception-detected .illustration-exception, +.sf-reset .block-exception-detected .text-exception { + float: left; +} +.sf-reset .block-exception-detected .illustration-exception { + width: 152px; +} +.sf-reset .block-exception-detected .text-exception { + width: 670px; + padding: 30px 44px 24px 46px; + position: relative; +} +.sf-reset .text-exception .open-quote, +.sf-reset .text-exception .close-quote { + font-family: Arial, Helvetica, sans-serif; + position: absolute; + color: #C9C9C9; + font-size: 8em; +} +.sf-reset .open-quote { + top: 0; + left: 0; +} +.sf-reset .close-quote { + bottom: -0.5em; + right: 50px; +} +.sf-reset .block-exception p { + font-family: Arial, Helvetica, sans-serif; +} +.sf-reset .block-exception p a, +.sf-reset .block-exception p a:hover { + color: #565656; +} +.sf-reset .logs h2 { + float: left; + width: 654px; +} +.sf-reset .error-count, .sf-reset .support { + float: right; + width: 170px; + text-align: right; +} +.sf-reset .error-count span { + display: inline-block; + background-color: #aacd4e; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + padding: 4px; + color: white; + margin-right: 2px; + font-size: 11px; + font-weight: bold; +} + +.sf-reset .support a { + display: inline-block; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + padding: 4px; + color: #000000; + margin-right: 2px; + font-size: 11px; + font-weight: bold; +} + +.sf-reset .toggle { + vertical-align: middle; +} +.sf-reset .linked ul, +.sf-reset .linked li { + display: inline; +} +.sf-reset #output-content { + color: #000; + font-size: 12px; +} +.sf-reset #traces-text pre { + white-space: pre; + font-size: 12px; + font-family: monospace; +} diff --git a/vendor/symfony/error-handler/Resources/assets/images/chevron-right.svg b/vendor/symfony/error-handler/Resources/assets/images/chevron-right.svg new file mode 100644 index 0000000..6837aff --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/chevron-right.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/images/favicon.png.base64 b/vendor/symfony/error-handler/Resources/assets/images/favicon.png.base64 new file mode 100644 index 0000000..fb076ed --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/favicon.png.base64 @@ -0,0 +1 @@ +data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgCAYAAAABtRhCAAADVUlEQVRIx82XX0jTURTHLYPyqZdefQx66CEo80+aYpoIkqzUikz6Z5klQoWUWYRIJYEUGpQ+lIr9U5dOTLdCtkmWZis3rbnC5fw/neYW002307mX/cZvP3/7o1PwwOdh95x7vnf39zvnd29AgBer2xO6DclAXiMqZAqxIiNIN/IYSUS2BPhjmGATchUxI+ADWiRhpWK7HKuHFVBFdmU5YvnI4grFGCaReF/EBH4KsZlGgj2JBTuCYBWRIYF8YoEOJ6wBt/gEs7mBbyOjQXruPLSdOgPCiEiPSUUHDoL8Ug5IUo9B/d5wrt+G7OAKNrODPuVdB6vRCIzN6SdBlpW9RIgk/1FeAXabzRlrUPVCS/JhbmwudztnGeeH9AyXBIwtmM3wLinZJZHifjHw2V+NBoRh+9ixQrbgbnaSIcl7cGea6hoXQbNe7za241oeO5Z0p42M4BV2EqP2D50wo+6HzvwC6C4sApNOR8cmOrtcnhtj2kYRyC9eBvXzKrBZrXSs72kFd1t3MoKVbMekQkEnSNKOO8fac3LpmK6l1TlGtsxmsdKFsecPYgwxst0cwROMYDXboSotg0WLBRqjY51jLYcENElXwW2XJKPydvoI2GN9T8rBtrAArYIUruBJXkFheCQYlCpQP6uk5dAQFQNaUROMSGVQFxLmkoQsxDJrhLbTZ+nvVsERME9MgPJRKV/58AsyomTSzE813WLFvWK++qI0xSfQl8k8Pg46sYRuv5t6dS+4RqxDwaa4BGjYH+NTQvKScIp9+YL/hoZh3jDtLRHtt2C3g6bmhX+CpsFBWg7ilDSPgj0lD2ncr5ev/BP8VvyAJhqVyZeUhPOrEhEFxgEtjft846Z/guQTNT89Q5P9flMLoth4F7808wKtWWKzAwNQHxrh/1vaid2F+XpYTSbQf1XA2McOmOpROnvpvMEA4tSjq1cW0sws2gCYxswY6TKkvzYnJq1NHZLnRU4BX+4U0uburvusu8Kv8iHY7qefkM4IFngJHEOUXmLEPgiGsI8YnlZILit3vSSLRTQe/MPIZva5pshNIEmyFQlCvruJKXPkCEfmePzkphXHdzZNQdoRI9KPlBAxlj/I8U97ERPS5bjGbWDFbEdqHVe5caTBeZZx2H/IMvzeN15yoQAAAABJRU5ErkJggg== diff --git a/vendor/symfony/error-handler/Resources/assets/images/icon-book.svg b/vendor/symfony/error-handler/Resources/assets/images/icon-book.svg new file mode 100644 index 0000000..498a74f --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/icon-book.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/images/icon-copy.svg b/vendor/symfony/error-handler/Resources/assets/images/icon-copy.svg new file mode 100644 index 0000000..844a4f9 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/icon-copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/symfony/error-handler/Resources/assets/images/icon-minus-square-o.svg b/vendor/symfony/error-handler/Resources/assets/images/icon-minus-square-o.svg new file mode 100644 index 0000000..be534ad --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/icon-minus-square-o.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/images/icon-minus-square.svg b/vendor/symfony/error-handler/Resources/assets/images/icon-minus-square.svg new file mode 100644 index 0000000..471c274 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/icon-minus-square.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/images/icon-plus-square-o.svg b/vendor/symfony/error-handler/Resources/assets/images/icon-plus-square-o.svg new file mode 100644 index 0000000..b2593a9 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/icon-plus-square-o.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/images/icon-plus-square.svg b/vendor/symfony/error-handler/Resources/assets/images/icon-plus-square.svg new file mode 100644 index 0000000..2f5c3b3 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/icon-plus-square.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/images/icon-support.svg b/vendor/symfony/error-handler/Resources/assets/images/icon-support.svg new file mode 100644 index 0000000..03fd8e7 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/icon-support.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/images/symfony-ghost.svg.php b/vendor/symfony/error-handler/Resources/assets/images/symfony-ghost.svg.php new file mode 100644 index 0000000..4b2f9c1 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/symfony-ghost.svg.php @@ -0,0 +1 @@ +addElementToGhost(); ?> diff --git a/vendor/symfony/error-handler/Resources/assets/images/symfony-logo.svg b/vendor/symfony/error-handler/Resources/assets/images/symfony-logo.svg new file mode 100644 index 0000000..f10824a --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/images/symfony-logo.svg @@ -0,0 +1 @@ + diff --git a/vendor/symfony/error-handler/Resources/assets/js/exception.js b/vendor/symfony/error-handler/Resources/assets/js/exception.js new file mode 100644 index 0000000..22ce675 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/assets/js/exception.js @@ -0,0 +1,304 @@ +/* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. + If you make any change in this file, verify the same change is needed in the other file. */ +/* .tab'); + var tabNavigation = document.createElement('div'); + tabNavigation.className = 'tab-navigation'; + tabNavigation.setAttribute('role', 'tablist'); + + var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ + for (var j = 0; j < tabs.length; j++) { + var tabId = 'tab-' + i + '-' + j; + var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; + + var tabNavigationItem = document.createElement('button'); + addClass(tabNavigationItem, 'tab-control'); + tabNavigationItem.setAttribute('data-tab-id', tabId); + tabNavigationItem.setAttribute('role', 'tab'); + tabNavigationItem.setAttribute('aria-controls', tabId); + if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } + if (hasClass(tabs[j], 'disabled')) { + addClass(tabNavigationItem, 'disabled'); + } + tabNavigationItem.innerHTML = tabTitle; + tabNavigation.appendChild(tabNavigationItem); + + var tabContent = tabs[j].querySelector('.tab-content'); + tabContent.parentElement.setAttribute('id', tabId); + } + + tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); + addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); + } + + /* display the active tab and add the 'click' event listeners */ + for (i = 0; i < tabGroups.length; i++) { + tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation .tab-control'); + + for (j = 0; j < tabNavigation.length; j++) { + tabId = tabNavigation[j].getAttribute('data-tab-id'); + var tabPanel = document.getElementById(tabId); + tabPanel.setAttribute('role', 'tabpanel'); + tabPanel.setAttribute('aria-labelledby', tabId); + tabPanel.querySelector('.tab-title').className = 'hidden'; + + if (hasClass(tabNavigation[j], 'active')) { + tabPanel.className = 'block'; + tabNavigation[j].setAttribute('aria-selected', 'true'); + tabNavigation[j].removeAttribute('tabindex'); + } else { + tabPanel.className = 'hidden'; + tabNavigation[j].removeAttribute('aria-selected'); + tabNavigation[j].setAttribute('tabindex', '-1'); + } + + tabNavigation[j].addEventListener('click', function(e) { + var activeTab = e.target || e.srcElement; + + /* needed because when the tab contains HTML contents, user can click */ + /* on any of those elements instead of their parent '
  • + + + + + + + + + + + = 400) { + $status = 'error'; + } elseif ($log['priority'] >= 300) { + $status = 'warning'; + } else { + $severity = 0; + if (($exception = $log['context']['exception'] ?? null) instanceof \ErrorException || $exception instanceof \Symfony\Component\ErrorHandler\Exception\SilencedErrorContext) { + $severity = $exception->getSeverity(); + } + $status = \E_DEPRECATED === $severity || \E_USER_DEPRECATED === $severity ? 'warning' : 'normal'; + } ?> + data-filter-channel="escape($log['channel']); ?>"> + + + + + + + + +
    LevelChannelMessage
    + escape($log['priorityName']); ?> + + + escape($log['channel']); ?> + + formatLogMessage($log['message'], $log['context']); ?> + +
    escape(json_encode($log['context'], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); ?>
    + +
    diff --git a/vendor/symfony/error-handler/Resources/views/trace.html.php b/vendor/symfony/error-handler/Resources/views/trace.html.php new file mode 100644 index 0000000..aaf6be1 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/views/trace.html.php @@ -0,0 +1,43 @@ +
    + + include('assets/images/icon-minus-square.svg'); ?> + include('assets/images/icon-plus-square.svg'); ?> + + + + abbrClass($trace['class']); ?>(formatArgs($trace['args']); ?>) + + + + fileLinkFormat->format($trace['file'], $lineNumber); + $filePath = strtr(strip_tags($this->formatFile($trace['file'], $lineNumber)), [' at line '.$lineNumber => '']); + $filePathParts = explode(\DIRECTORY_SEPARATOR, $filePath); + ?> + + in + + + + + + + + (line ) + + + +
    + +
    + fileExcerpt($trace['file'], $trace['line'], 5), [ + '#DD0000' => 'var(--highlight-string)', + '#007700' => 'var(--highlight-keyword)', + '#0000BB' => 'var(--highlight-default)', + '#FF8000' => 'var(--highlight-comment)', + ]); ?> +
    + diff --git a/vendor/symfony/error-handler/Resources/views/traces.html.php b/vendor/symfony/error-handler/Resources/views/traces.html.php new file mode 100644 index 0000000..fd834c1 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/views/traces.html.php @@ -0,0 +1,59 @@ +
    +
    +
    +
    + include('assets/images/icon-minus-square-o.svg'); ?> + include('assets/images/icon-plus-square-o.svg'); ?> + + +
    + +

    + + + + +

    + + 1) { ?> +

    escape($exception['message']); ?>

    + +
    + +
    + Show exception properties +
    + dumpValue($exception['data']) ?> +
    +
    + +
    + +
    + $trace) { + $isVendorTrace = $trace['file'] && (str_contains($trace['file'], '/vendor/') || str_contains($trace['file'], '/var/cache/')); + $displayCodeSnippet = $isFirstUserCode && !$isVendorTrace; + if ($displayCodeSnippet) { + $isFirstUserCode = false; + } ?> +
    + include('views/trace.html.php', [ + 'prefix' => $index, + 'i' => $i, + 'trace' => $trace, + 'style' => $isVendorTrace ? 'compact' : ($displayCodeSnippet ? 'expanded' : ''), + ]); ?> +
    + +
    +
    +
    diff --git a/vendor/symfony/error-handler/Resources/views/traces_text.html.php b/vendor/symfony/error-handler/Resources/views/traces_text.html.php new file mode 100644 index 0000000..6b47840 --- /dev/null +++ b/vendor/symfony/error-handler/Resources/views/traces_text.html.php @@ -0,0 +1,43 @@ + + + + + + + + + + + + +
    +
    + 1) { ?> + [/] + + + include('assets/images/icon-minus-square-o.svg'); ?> + include('assets/images/icon-plus-square-o.svg'); ?> +
    +
    + +
    +escape($exception['class']).":\n";
    +                    if ($exception['message']) {
    +                        echo $this->escape($exception['message'])."\n";
    +                    }
    +
    +                    foreach ($exception['trace'] as $trace) {
    +                        echo "\n  ";
    +                        if ($trace['function']) {
    +                            echo $this->escape('at '.$trace['class'].$trace['type'].$trace['function']).'('.(isset($trace['args']) ? $this->formatArgsAsText($trace['args']) : '').')';
    +                        }
    +                        if ($trace['file'] && $trace['line']) {
    +                            echo($trace['function'] ? "\n     (" : 'at ').strtr(strip_tags($this->formatFile($trace['file'], $trace['line'])), [' at line '.$trace['line'] => '']).':'.$trace['line'].($trace['function'] ? ')' : '');
    +                        }
    +                    }
    +?>
    +                
    + +
    diff --git a/vendor/symfony/error-handler/ThrowableUtils.php b/vendor/symfony/error-handler/ThrowableUtils.php new file mode 100644 index 0000000..f8a6a9d --- /dev/null +++ b/vendor/symfony/error-handler/ThrowableUtils.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; + +/** + * @internal + */ +class ThrowableUtils +{ + public static function getSeverity(SilencedErrorContext|\Throwable $throwable): int + { + if ($throwable instanceof \ErrorException || $throwable instanceof SilencedErrorContext) { + return $throwable->getSeverity(); + } + + if ($throwable instanceof \ParseError) { + return \E_PARSE; + } + + if ($throwable instanceof \TypeError) { + return \E_RECOVERABLE_ERROR; + } + + return \E_ERROR; + } +} diff --git a/vendor/symfony/error-handler/composer.json b/vendor/symfony/error-handler/composer.json new file mode 100644 index 0000000..987a68f --- /dev/null +++ b/vendor/symfony/error-handler/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/error-handler", + "type": "library", + "description": "Provides tools to manage errors and ease debugging PHP code", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "require-dev": { + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "minimum-stability": "dev" +} diff --git a/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md b/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/event-dispatcher-contracts/Event.php b/vendor/symfony/event-dispatcher-contracts/Event.php new file mode 100644 index 0000000..2e7f998 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/Event.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Nicolas Grekas + */ +class Event implements StoppableEventInterface +{ + private bool $propagationStopped = false; + + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php new file mode 100644 index 0000000..2d7840d --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; + +/** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ +interface EventDispatcherInterface extends PsrEventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @template T of object + * + * @param T $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return T The passed $event MUST be returned + */ + public function dispatch(object $event, ?string $eventName = null): object; +} diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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. diff --git a/vendor/symfony/event-dispatcher-contracts/README.md b/vendor/symfony/event-dispatcher-contracts/README.md new file mode 100644 index 0000000..332b961 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/README.md @@ -0,0 +1,9 @@ +Symfony EventDispatcher Contracts +================================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/event-dispatcher-contracts/composer.json b/vendor/symfony/event-dispatcher-contracts/composer.json new file mode 100644 index 0000000..35956eb --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/event-dispatcher-contracts", + "type": "library", + "description": "Generic abstractions related to dispatching event", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php b/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php new file mode 100644 index 0000000..590ada9 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Attribute; + +/** + * Service tag to autoconfigure event listeners. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsEventListener +{ + /** + * @param string|null $event The event name to listen to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same event + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ + public function __construct( + public ?string $event = null, + public ?string $method = null, + public int $priority = 0, + public ?string $dispatcher = null, + ) { + } +} diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 0000000..76b2eab --- /dev/null +++ b/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG +========= + +6.0 +--- + + * Remove `LegacyEventDispatcherProxy` + +5.4 +--- + + * Allow `#[AsEventListener]` attribute on methods + +5.3 +--- + + * Add `#[AsEventListener]` attribute for declaring listeners on PHP 8 + +5.1.0 +----- + + * The `LegacyEventDispatcherProxy` class has been deprecated. + * Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`. + +5.0.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`. + * The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`. + * The `TraceableEventDispatcherInterface` has been removed. + * The `WrappedListener` class is now final. + +4.4.0 +----- + + * `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`. + * Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events. + +4.3.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated + * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead + +4.1.0 +----- + + * added support for invokable event listeners tagged with `kernel.event_listener` by default + * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added. + * The `TraceableEventDispatcherInterface` has been deprecated. + +4.0.0 +----- + + * removed the `ContainerAwareEventDispatcher` class + * added the `reset()` method to the `TraceableEventDispatcherInterface` + +3.4.0 +----- + + * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. + +3.3.0 +----- + + * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. + +3.0.0 +----- + + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. + * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` + and `Event::getName()` have been removed. + The event dispatcher and the event name are passed to the listener call. + +2.5.0 +----- + + * added Debug\TraceableEventDispatcher (originally in HttpKernel) + * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface + * added RegisterListenersPass (originally in HttpKernel) + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000..72ce526 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,351 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface +{ + /** + * @var \SplObjectStorage|null + */ + private ?\SplObjectStorage $callStack = null; + private array $wrappedListeners = []; + private array $orphanedEvents = []; + private string $currentRequestHash = ''; + + public function __construct( + private EventDispatcherInterface $dispatcher, + protected Stopwatch $stopwatch, + protected ?LoggerInterface $logger = null, + private ?RequestStack $requestStack = null, + ) { + } + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + public function addSubscriber(EventSubscriberInterface $subscriber): void + { + $this->dispatcher->addSubscriber($subscriber); + } + + public function removeListener(string $eventName, callable|array $listener): void + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + $this->dispatcher->removeListener($eventName, $listener); + } + + public function removeSubscriber(EventSubscriberInterface $subscriber): void + { + $this->dispatcher->removeSubscriber($subscriber); + } + + public function getListeners(?string $eventName = null): array + { + return $this->dispatcher->getListeners($eventName); + } + + public function getListenerPriority(string $eventName, callable|array $listener): ?int + { + // we might have wrapped listeners for the event (if called while dispatching) + // in that case get the priority by wrapper + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { + return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); + } + } + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + public function hasListeners(?string $eventName = null): bool + { + return $this->dispatcher->hasListeners($eventName); + } + + public function dispatch(object $event, ?string $eventName = null): object + { + $eventName ??= $event::class; + + $this->callStack ??= new \SplObjectStorage(); + + $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; + + if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + try { + $this->beforeDispatch($eventName, $event); + try { + $e = $this->stopwatch->start($eventName, 'section'); + try { + $this->dispatcher->dispatch($event, $eventName); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + } finally { + $this->afterDispatch($eventName, $event); + } + } finally { + $this->currentRequestHash = $currentRequestHash; + $this->postProcess($eventName); + } + + return $event; + } + + public function getCalledListeners(?Request $request = null): array + { + if (null === $this->callStack) { + return []; + } + + $hash = $request ? spl_object_hash($request) : null; + $called = []; + foreach ($this->callStack as $listener) { + [$eventName, $requestHash] = $this->callStack->getInfo(); + if (null === $hash || $hash === $requestHash) { + $called[] = $listener->getInfo($eventName); + } + } + + return $called; + } + + public function getNotCalledListeners(?Request $request = null): array + { + try { + $allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority(); + } catch (\Exception $e) { + $this->logger?->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); + + // unable to retrieve the uncalled listeners + return []; + } + + $hash = $request ? spl_object_hash($request) : null; + $calledListeners = []; + + if (null !== $this->callStack) { + foreach ($this->callStack as $calledListener) { + [, $requestHash] = $this->callStack->getInfo(); + + if (null === $hash || $hash === $requestHash) { + $calledListeners[] = $calledListener->getWrappedListener(); + } + } + } + + $notCalled = []; + + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as [$listener, $priority]) { + if (!\in_array($listener, $calledListeners, true)) { + if (!$listener instanceof WrappedListener) { + $listener = new WrappedListener($listener, null, $this->stopwatch, $this, $priority); + } + $notCalled[] = $listener->getInfo($eventName); + } + } + } + + uasort($notCalled, $this->sortNotCalledListeners(...)); + + return $notCalled; + } + + public function getOrphanedEvents(?Request $request = null): array + { + if ($request) { + return $this->orphanedEvents[spl_object_hash($request)] ?? []; + } + + if (!$this->orphanedEvents) { + return []; + } + + return array_merge(...array_values($this->orphanedEvents)); + } + + public function reset(): void + { + $this->callStack = null; + $this->orphanedEvents = []; + $this->currentRequestHash = ''; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + */ + public function __call(string $method, array $arguments): mixed + { + return $this->dispatcher->{$method}(...$arguments); + } + + /** + * Called before dispatching the event. + */ + protected function beforeDispatch(string $eventName, object $event): void + { + } + + /** + * Called after dispatching the event. + */ + protected function afterDispatch(string $eventName, object $event): void + { + } + + private function preProcess(string $eventName): void + { + if (!$this->dispatcher->hasListeners($eventName)) { + $this->orphanedEvents[$this->currentRequestHash][] = $eventName; + + return; + } + + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $priority = $this->getListenerPriority($eventName, $listener); + $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $priority); + $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); + } + } + + private function postProcess(string $eventName): void + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $priority = $this->getListenerPriority($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); + + if (null !== $this->logger) { + $context = ['event' => $eventName, 'listener' => $listener->getPretty()]; + } + + if ($listener->wasCalled()) { + $this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context); + } else { + $this->callStack->detach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); + } + + if ($listener->stoppedPropagation()) { + $this->logger?->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); + + $skipped = true; + } + } + } + + private function sortNotCalledListeners(array $a, array $b): int + { + if (0 !== $cmp = strcmp($a['event'], $b['event'])) { + return $cmp; + } + + if (\is_int($a['priority']) && !\is_int($b['priority'])) { + return 1; + } + + if (!\is_int($a['priority']) && \is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } + + private function getListenersWithPriority(): array + { + $result = []; + + $allListeners = new \ReflectionProperty(EventDispatcher::class, 'listeners'); + + foreach ($allListeners->getValue($this->dispatcher) as $eventName => $listenersByPriority) { + foreach ($listenersByPriority as $priority => $listeners) { + foreach ($listeners as $listener) { + $result[$eventName][] = [$listener, $priority]; + } + } + } + + return $result; + } + + private function getListenersWithoutPriority(): array + { + $result = []; + + foreach ($this->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + $result[$eventName][] = [$listener, null]; + } + } + + return $result; + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 0000000..b83115b --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Caster\ClassStub; + +/** + * @author Fabien Potencier + */ +final class WrappedListener +{ + private string|array|object $listener; + private ?\Closure $optimizedListener; + private string $name; + private bool $called = false; + private bool $stoppedPropagation = false; + private string $pretty; + private string $callableRef; + private ClassStub|string $stub; + private static bool $hasClassStub; + + public function __construct( + callable|array $listener, + ?string $name, + private Stopwatch $stopwatch, + private ?EventDispatcherInterface $dispatcher = null, + private ?int $priority = null, + ) { + $this->listener = $listener; + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null); + + if (\is_array($listener)) { + [$this->name, $this->callableRef] = $this->parseListener($listener); + $this->pretty = $this->name.'::'.$listener[1]; + $this->callableRef .= '::'.$listener[1]; + } elseif ($listener instanceof \Closure) { + $r = new \ReflectionFunction($listener); + if ($r->isAnonymous()) { + $this->pretty = $this->name = 'closure'; + } elseif ($class = $r->getClosureCalledClass()) { + $this->name = $class->name; + $this->pretty = $this->name.'::'.$r->name; + } else { + $this->pretty = $this->name = $r->name; + } + } elseif (\is_string($listener)) { + $this->pretty = $this->name = $listener; + } else { + $this->name = get_debug_type($listener); + $this->pretty = $this->name.'::__invoke'; + $this->callableRef = $listener::class.'::__invoke'; + } + + if (null !== $name) { + $this->name = $name; + } + + self::$hasClassStub ??= class_exists(ClassStub::class); + } + + public function getWrappedListener(): callable|array + { + return $this->listener; + } + + public function wasCalled(): bool + { + return $this->called; + } + + public function stoppedPropagation(): bool + { + return $this->stoppedPropagation; + } + + public function getPretty(): string + { + return $this->pretty; + } + + public function getInfo(string $eventName): array + { + $this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->callableRef ?? $this->listener) : $this->pretty.'()'; + + return [ + 'event' => $eventName, + 'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener), + 'pretty' => $this->pretty, + 'stub' => $this->stub, + ]; + } + + public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void + { + $dispatcher = $this->dispatcher ?: $dispatcher; + + $this->called = true; + $this->priority ??= $dispatcher->getListenerPriority($eventName, $this->listener); + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + try { + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + + if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } + + private function parseListener(array $listener): array + { + if ($listener[0] instanceof \Closure) { + foreach ((new \ReflectionFunction($listener[0]))->getAttributes(\Closure::class) as $attribute) { + if ($name = $attribute->getArguments()['name'] ?? false) { + return [$name, $attribute->getArguments()['class'] ?? $name]; + } + } + } + + if (\is_object($listener[0])) { + return [get_debug_type($listener[0]), $listener[0]::class]; + } + + return [$listener[0], $listener[0]]; + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 0000000..5308992 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This pass allows bundles to extend the list of event aliases. + * + * @author Alexander M. Turek + */ +class AddEventAliasesPass implements CompilerPassInterface +{ + public function __construct( + private array $eventAliases, + ) { + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter('event_dispatcher.event_aliases') ? $container->getParameter('event_dispatcher.event_aliases') : []; + + $container->setParameter( + 'event_dispatcher.event_aliases', + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000..29a76bb --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + private array $hotPathEvents = []; + private array $noPreloadEvents = []; + + /** + * @return $this + */ + public function setHotPathEvents(array $hotPathEvents): static + { + $this->hotPathEvents = array_flip($hotPathEvents); + + return $this; + } + + /** + * @return $this + */ + public function setNoPreloadEvents(array $noPreloadEvents): static + { + $this->noPreloadEvents = array_flip($noPreloadEvents); + + return $this; + } + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) { + return; + } + + $aliases = []; + + if ($container->hasParameter('event_dispatcher.event_aliases')) { + $aliases = $container->getParameter('event_dispatcher.event_aliases'); + } + + $globalDispatcherDefinition = $container->findDefinition('event_dispatcher'); + + foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) { + $noPreload = 0; + + foreach ($events as $event) { + $priority = $event['priority'] ?? 0; + + if (!isset($event['event'])) { + if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) { + continue; + } + + $event['method'] ??= '__invoke'; + $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); + } + + $event['event'] = $aliases[$event['event']] ?? $event['event']; + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback([ + '/(?<=\b|_)[a-z]/i', + '/[^a-z0-9]/i', + ], fn ($matches) => strtoupper($matches[0]), $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + + if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method'])) { + if (!$r->hasMethod('__invoke')) { + throw new InvalidArgumentException(sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id)); + } + + $event['method'] = '__invoke'; + } + } + + $dispatcherDefinition = $globalDispatcherDefinition; + if (isset($event['dispatcher'])) { + $dispatcherDefinition = $container->findDefinition($event['dispatcher']); + } + + $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); + + if (isset($this->hotPathEvents[$event['event']])) { + $container->getDefinition($id)->addTag('container.hot_path'); + } elseif (isset($this->noPreloadEvents[$event['event']])) { + ++$noPreload; + } + } + + if ($noPreload && \count($events) === $noPreload) { + $container->getDefinition($id)->addTag('container.no_preload'); + } + } + + $extractingDispatcher = new ExtractingEventDispatcher(); + + foreach ($container->findTaggedServiceIds('kernel.event_subscriber', true) as $id => $tags) { + $def = $container->getDefinition($id); + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $def->getClass(); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(EventSubscriberInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); + } + $class = $r->name; + + $dispatcherDefinitions = []; + foreach ($tags as $attributes) { + if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) { + continue; + } + + $dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($attributes['dispatcher']); + } + + if (!$dispatcherDefinitions) { + $dispatcherDefinitions = [$globalDispatcherDefinition]; + } + + $noPreload = 0; + ExtractingEventDispatcher::$aliases = $aliases; + ExtractingEventDispatcher::$subscriber = $class; + $extractingDispatcher->addSubscriber($extractingDispatcher); + foreach ($extractingDispatcher->listeners as $args) { + $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; + foreach ($dispatcherDefinitions as $dispatcherDefinition) { + $dispatcherDefinition->addMethodCall('addListener', $args); + } + + if (isset($this->hotPathEvents[$args[0]])) { + $container->getDefinition($id)->addTag('container.hot_path'); + } elseif (isset($this->noPreloadEvents[$args[0]])) { + ++$noPreload; + } + } + if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { + $container->getDefinition($id)->addTag('container.no_preload'); + } + $extractingDispatcher->listeners = []; + ExtractingEventDispatcher::$aliases = []; + } + } + + private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string + { + if ( + null === ($class = $container->getDefinition($id)->getClass()) + || !($r = $container->getReflectionClass($class, false)) + || !$r->hasMethod($method) + || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() + || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType + || $type->isBuiltin() + || Event::class === ($name = $type->getName()) + ) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); + } + + return $name; + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public array $listeners = []; + + public static array $aliases = []; + public static string $subscriber; + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->listeners[] = [$eventName, $listener[1], $priority]; + } + + public static function getSubscribedEvents(): array + { + $events = []; + + foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { + $events[self::$aliases[$eventName] ?? $eventName] = $params; + } + + return $events; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 0000000..43bc16b --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\Debug\WrappedListener; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + * @author Nicolas Grekas + */ +class EventDispatcher implements EventDispatcherInterface +{ + private array $listeners = []; + private array $sorted = []; + private array $optimized; + + public function __construct() + { + if (__CLASS__ === static::class) { + $this->optimized = []; + } + } + + public function dispatch(object $event, ?string $eventName = null): object + { + $eventName ??= $event::class; + + if (isset($this->optimized)) { + $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); + } else { + $listeners = $this->getListeners($eventName); + } + + if ($listeners) { + $this->callListeners($listeners, $eventName, $event); + } + + return $event; + } + + public function getListeners(?string $eventName = null): array + { + if (null !== $eventName) { + if (empty($this->listeners[$eventName])) { + return []; + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + public function getListenerPriority(string $eventName, callable|array $listener): ?int + { + if (empty($this->listeners[$eventName])) { + return null; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] ??= '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + return $priority; + } + } + } + + return null; + } + + public function hasListeners(?string $eventName = null): bool + { + if (null !== $eventName) { + return !empty($this->listeners[$eventName]); + } + + foreach ($this->listeners as $eventListeners) { + if ($eventListeners) { + return true; + } + } + + return false; + } + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName], $this->optimized[$eventName]); + } + + public function removeListener(string $eventName, callable|array $listener): void + { + if (empty($this->listeners[$eventName])) { + return; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as $k => &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] ??= '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); + } + } + + if (!$listeners) { + unset($this->listeners[$eventName][$priority]); + } + } + } + + public function addSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->addListener($eventName, [$subscriber, $params]); + } elseif (\is_string($params[0])) { + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); + } + } + } + } + + public function removeSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_array($params) && \is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, [$subscriber, $listener[0]]); + } + } else { + $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners + * @param string $eventName The name of the event to dispatch + * @param object $event The event object to pass to the event handlers/listeners + */ + protected function callListeners(iterable $listeners, string $eventName, object $event): void + { + $stoppable = $event instanceof StoppableEventInterface; + + foreach ($listeners as $listener) { + if ($stoppable && $event->isPropagationStopped()) { + break; + } + $listener($event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + */ + private function sortListeners(string $eventName): void + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + $this->sorted[$eventName][] = $listener; + } + } + } + + /** + * Optimizes the internal list of listeners for the given event by priority. + */ + private function optimizeListeners(string $eventName): array + { + krsort($this->listeners[$eventName]); + $this->optimized[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + $closure = &$this->optimized[$eventName][]; + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $closure = static function (...$args) use (&$listener, &$closure) { + if ($listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + ($closure = $listener(...))(...$args); + }; + } else { + $closure = $listener instanceof WrappedListener ? $listener : $listener(...); + } + } + } + + return $this->optimized[$eventName]; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 0000000..99f8b1a --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + */ +interface EventDispatcherInterface extends ContractsEventDispatcherInterface +{ + /** + * Adds an event listener that listens on the specified events. + * + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener(string $eventName, callable $listener, int $priority = 0): void; + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + */ + public function addSubscriber(EventSubscriberInterface $subscriber): void; + + /** + * Removes an event listener from the specified events. + */ + public function removeListener(string $eventName, callable $listener): void; + + public function removeSubscriber(EventSubscriberInterface $subscriber): void; + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @return array + */ + public function getListeners(?string $eventName = null): array; + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + */ + public function getListenerPriority(string $eventName, callable $listener): ?int; + + /** + * Checks whether an event has any registered listeners. + */ + public function hasListeners(?string $eventName = null): bool; +} diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000..2085e42 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows itself what events it is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * ['eventName' => 'methodName'] + * * ['eventName' => ['methodName', $priority]] + * * ['eventName' => [['methodName1', $priority], ['methodName2']]] + * + * The code must not depend on runtime state as it will only be called at compile time. + * All logic depending on runtime state must be put into the individual methods handling the events. + * + * @return array> + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 0000000..2ac654f --- /dev/null +++ b/vendor/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + * + * @implements \ArrayAccess + * @implements \IteratorAggregate + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + /** + * Encapsulate an event with $subject and $arguments. + * + * @param mixed $subject The subject of the event, usually an object or a callable + * @param array $arguments Arguments to store in the event + */ + public function __construct( + protected mixed $subject = null, + protected array $arguments = [], + ) { + } + + /** + * Getter for subject property. + */ + public function getSubject(): mixed + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @throws \InvalidArgumentException if key is not found + */ + public function getArgument(string $key): mixed + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @return $this + */ + public function setArgument(string $key, mixed $value): static + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * Set args property. + * + * @return $this + */ + public function setArguments(array $args = []): static + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + */ + public function hasArgument(string $key): bool + { + return \array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key + * + * @throws \InvalidArgumentException if key does not exist in $this->args + */ + public function offsetGet(mixed $key): mixed + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + */ + public function offsetSet(mixed $key, mixed $value): void + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + */ + public function offsetUnset(mixed $key): void + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + */ + public function offsetExists(mixed $key): bool + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 0000000..a6d078e --- /dev/null +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + public function __construct( + private EventDispatcherInterface $dispatcher, + ) { + } + + public function dispatch(object $event, ?string $eventName = null): object + { + return $this->dispatcher->dispatch($event, $eventName); + } + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function addSubscriber(EventSubscriberInterface $subscriber): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function removeListener(string $eventName, callable|array $listener): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function removeSubscriber(EventSubscriberInterface $subscriber): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function getListeners(?string $eventName = null): array + { + return $this->dispatcher->getListeners($eventName); + } + + public function getListenerPriority(string $eventName, callable|array $listener): ?int + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + public function hasListeners(?string $eventName = null): bool + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 0000000..dcdb68d --- /dev/null +++ b/vendor/symfony/event-dispatcher/README.md @@ -0,0 +1,15 @@ +EventDispatcher Component +========================= + +The EventDispatcher component provides tools that allow your application +components to communicate with each other by dispatching events and listening to +them. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 0000000..598bbdc --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/filesystem/CHANGELOG.md b/vendor/symfony/filesystem/CHANGELOG.md new file mode 100644 index 0000000..80818d1 --- /dev/null +++ b/vendor/symfony/filesystem/CHANGELOG.md @@ -0,0 +1,92 @@ +CHANGELOG +========= + +7.1 +--- + + * Add the `Filesystem::readFile()` method + +7.0 +--- + + * Add argument `$lock` to `Filesystem::appendToFile()` + +5.4 +--- + + * Add `Path` class + * Add `$lock` argument to `Filesystem::appendToFile()` + +5.0.0 +----- + + * `Filesystem::dumpFile()` and `appendToFile()` don't accept arrays anymore + +4.4.0 +----- + + * support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0 + * `tempnam()` now accepts a third argument `$suffix`. + +4.3.0 +----- + + * support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0 + * support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0 + +4.0.0 +----- + + * removed `LockHandler` + * Support for passing relative paths to `Filesystem::makePathRelative()` has been removed. + +3.4.0 +----- + + * support for passing relative paths to `Filesystem::makePathRelative()` is deprecated and will be removed in 4.0 + +3.3.0 +----- + + * added `appendToFile()` to append contents to existing files + +3.2.0 +----- + + * added `readlink()` as a platform independent method to read links + +3.0.0 +----- + + * removed `$mode` argument from `Filesystem::dumpFile()` + +2.8.0 +----- + + * added tempnam() a stream aware version of PHP's native tempnam() + +2.6.0 +----- + + * added LockHandler + +2.3.12 +------ + + * deprecated dumpFile() file mode argument. + +2.3.0 +----- + + * added the dumpFile() method to atomically write files + +2.2.0 +----- + + * added a delete option for the mirror() method + +2.1.0 +----- + + * 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value + * created the component diff --git a/vendor/symfony/filesystem/Exception/ExceptionInterface.php b/vendor/symfony/filesystem/Exception/ExceptionInterface.php new file mode 100644 index 0000000..fc438d9 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Romain Neutron + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/filesystem/Exception/FileNotFoundException.php b/vendor/symfony/filesystem/Exception/FileNotFoundException.php new file mode 100644 index 0000000..06b732b --- /dev/null +++ b/vendor/symfony/filesystem/Exception/FileNotFoundException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a file couldn't be found. + * + * @author Fabien Potencier + * @author Christian Gärtner + */ +class FileNotFoundException extends IOException +{ + public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null) + { + if (null === $message) { + if (null === $path) { + $message = 'File could not be found.'; + } else { + $message = sprintf('File "%s" could not be found.', $path); + } + } + + parent::__construct($message, $code, $previous, $path); + } +} diff --git a/vendor/symfony/filesystem/Exception/IOException.php b/vendor/symfony/filesystem/Exception/IOException.php new file mode 100644 index 0000000..46ab8b4 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a filesystem operation failure happens. + * + * @author Romain Neutron + * @author Christian Gärtner + * @author Fabien Potencier + */ +class IOException extends \RuntimeException implements IOExceptionInterface +{ + public function __construct( + string $message, + int $code = 0, + ?\Throwable $previous = null, + private ?string $path = null, + ) { + parent::__construct($message, $code, $previous); + } + + public function getPath(): ?string + { + return $this->path; + } +} diff --git a/vendor/symfony/filesystem/Exception/IOExceptionInterface.php b/vendor/symfony/filesystem/Exception/IOExceptionInterface.php new file mode 100644 index 0000000..90c71db --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOExceptionInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * IOException interface for file and input/output stream related exceptions thrown by the component. + * + * @author Christian Gärtner + */ +interface IOExceptionInterface extends ExceptionInterface +{ + /** + * Returns the associated path for the exception. + */ + public function getPath(): ?string; +} diff --git a/vendor/symfony/filesystem/Exception/InvalidArgumentException.php b/vendor/symfony/filesystem/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..abadc20 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * @author Christian Flothmann + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/filesystem/Exception/RuntimeException.php b/vendor/symfony/filesystem/Exception/RuntimeException.php new file mode 100644 index 0000000..a7512dc --- /dev/null +++ b/vendor/symfony/filesystem/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * @author Théo Fidry + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/filesystem/Filesystem.php b/vendor/symfony/filesystem/Filesystem.php new file mode 100644 index 0000000..03e449d --- /dev/null +++ b/vendor/symfony/filesystem/Filesystem.php @@ -0,0 +1,761 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\FileNotFoundException; +use Symfony\Component\Filesystem\Exception\InvalidArgumentException; +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * Provides basic utility to manipulate the file system. + * + * @author Fabien Potencier + */ +class Filesystem +{ + private static ?string $lastError = null; + + /** + * Copies a file. + * + * If the target file is older than the origin file, it's always overwritten. + * If the target file is newer, it is overwritten only when the + * $overwriteNewerFiles option is set to true. + * + * @throws FileNotFoundException When originFile doesn't exist + * @throws IOException When copy fails + */ + public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false): void + { + $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://'); + if ($originIsLocal && !is_file($originFile)) { + throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile); + } + + $this->mkdir(\dirname($targetFile)); + + $doCopy = true; + if (!$overwriteNewerFiles && null === parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) { + $doCopy = filemtime($originFile) > filemtime($targetFile); + } + + if ($doCopy) { + // https://bugs.php.net/64634 + if (!$source = self::box('fopen', $originFile, 'r')) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); + } + + // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default + if (!$target = self::box('fopen', $targetFile, 'w', false, stream_context_create(['ftp' => ['overwrite' => true]]))) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); + } + + $bytesCopied = stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + unset($source, $target); + + if (!is_file($targetFile)) { + throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile); + } + + if ($originIsLocal) { + // Like `cp`, preserve executable permission bits + self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); + + // Like `cp`, preserve the file modification time + self::box('touch', $targetFile, filemtime($originFile)); + + if ($bytesCopied !== $bytesOrigin = filesize($originFile)) { + throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); + } + } + } + } + + /** + * Creates a directory recursively. + * + * @throws IOException On any directory creation failure + */ + public function mkdir(string|iterable $dirs, int $mode = 0777): void + { + foreach ($this->toIterable($dirs) as $dir) { + if (is_dir($dir)) { + continue; + } + + if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) { + throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir); + } + } + } + + /** + * Checks the existence of files or directories. + */ + public function exists(string|iterable $files): bool + { + $maxPathLength = \PHP_MAXPATHLEN - 2; + + foreach ($this->toIterable($files) as $file) { + if (\strlen($file) > $maxPathLength) { + throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file); + } + + if (!file_exists($file)) { + return false; + } + } + + return true; + } + + /** + * Sets access and modification time of file. + * + * @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used + * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used + * + * @throws IOException When touch fails + */ + public function touch(string|iterable $files, ?int $time = null, ?int $atime = null): void + { + foreach ($this->toIterable($files) as $file) { + if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) { + throw new IOException(sprintf('Failed to touch "%s": ', $file).self::$lastError, 0, null, $file); + } + } + } + + /** + * Removes files or directories. + * + * @throws IOException When removal fails + */ + public function remove(string|iterable $files): void + { + if ($files instanceof \Traversable) { + $files = iterator_to_array($files, false); + } elseif (!\is_array($files)) { + $files = [$files]; + } + + self::doRemove($files, false); + } + + private static function doRemove(array $files, bool $isRecursive): void + { + $files = array_reverse($files); + foreach ($files as $file) { + if (is_link($file)) { + // See https://bugs.php.net/52176 + if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError); + } + } elseif (is_dir($file)) { + if (!$isRecursive) { + $tmpName = \dirname(realpath($file)).'/.!'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-!')); + + if (file_exists($tmpName)) { + try { + self::doRemove([$tmpName], true); + } catch (IOException) { + } + } + + if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) { + $origFile = $file; + $file = $tmpName; + } else { + $origFile = null; + } + } + + $filesystemIterator = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS); + self::doRemove(iterator_to_array($filesystemIterator, true), true); + + if (!self::box('rmdir', $file) && file_exists($file) && !$isRecursive) { + $lastError = self::$lastError; + + if (null !== $origFile && self::box('rename', $file, $origFile)) { + $file = $origFile; + } + + throw new IOException(sprintf('Failed to remove directory "%s": ', $file).$lastError); + } + } elseif (!self::box('unlink', $file) && ((self::$lastError && str_contains(self::$lastError, 'Permission denied')) || file_exists($file))) { + throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError); + } + } + } + + /** + * Change mode for an array of files or directories. + * + * @param int $mode The new mode (octal) + * @param int $umask The mode mask (octal) + * @param bool $recursive Whether change the mod recursively or not + * + * @throws IOException When the change fails + */ + public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { + if (!self::box('chmod', $file, $mode & ~$umask)) { + throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file); + } + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); + } + } + } + + /** + * Change the owner of an array of files or directories. + * + * @param string|int $user A user name or number + * @param bool $recursive Whether change the owner recursively or not + * + * @throws IOException When the change fails + */ + public function chown(string|iterable $files, string|int $user, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chown(new \FilesystemIterator($file), $user, true); + } + if (is_link($file) && \function_exists('lchown')) { + if (!self::box('lchown', $file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); + } + } else { + if (!self::box('chown', $file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); + } + } + } + } + + /** + * Change the group of an array of files or directories. + * + * @param string|int $group A group name or number + * @param bool $recursive Whether change the group recursively or not + * + * @throws IOException When the change fails + */ + public function chgrp(string|iterable $files, string|int $group, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chgrp(new \FilesystemIterator($file), $group, true); + } + if (is_link($file) && \function_exists('lchgrp')) { + if (!self::box('lchgrp', $file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); + } + } else { + if (!self::box('chgrp', $file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); + } + } + } + } + + /** + * Renames a file or a directory. + * + * @throws IOException When target file or directory already exists + * @throws IOException When origin cannot be renamed + */ + public function rename(string $origin, string $target, bool $overwrite = false): void + { + // we check that target does not exist + if (!$overwrite && $this->isReadable($target)) { + throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); + } + + if (!self::box('rename', $origin, $target)) { + if (is_dir($origin)) { + // See https://bugs.php.net/54097 & https://php.net/rename#113943 + $this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]); + $this->remove($origin); + + return; + } + throw new IOException(sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target); + } + } + + /** + * Tells whether a file exists and is readable. + * + * @throws IOException When windows path is longer than 258 characters + */ + private function isReadable(string $filename): bool + { + $maxPathLength = \PHP_MAXPATHLEN - 2; + + if (\strlen($filename) > $maxPathLength) { + throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename); + } + + return is_readable($filename); + } + + /** + * Creates a symbolic link or copy a directory. + * + * @throws IOException When symlink fails + */ + public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false): void + { + self::assertFunctionExists('symlink'); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $originDir = strtr($originDir, '/', '\\'); + $targetDir = strtr($targetDir, '/', '\\'); + + if ($copyOnWindows) { + $this->mirror($originDir, $targetDir); + + return; + } + } + + $this->mkdir(\dirname($targetDir)); + + if (is_link($targetDir)) { + if (readlink($targetDir) === $originDir) { + return; + } + $this->remove($targetDir); + } + + if (!self::box('symlink', $originDir, $targetDir)) { + $this->linkException($originDir, $targetDir, 'symbolic'); + } + } + + /** + * Creates a hard link, or several hard links to a file. + * + * @param string|string[] $targetFiles The target file(s) + * + * @throws FileNotFoundException When original file is missing or not a file + * @throws IOException When link fails, including if link already exists + */ + public function hardlink(string $originFile, string|iterable $targetFiles): void + { + self::assertFunctionExists('link'); + + if (!$this->exists($originFile)) { + throw new FileNotFoundException(null, 0, null, $originFile); + } + + if (!is_file($originFile)) { + throw new FileNotFoundException(sprintf('Origin file "%s" is not a file.', $originFile)); + } + + foreach ($this->toIterable($targetFiles) as $targetFile) { + if (is_file($targetFile)) { + if (fileinode($originFile) === fileinode($targetFile)) { + continue; + } + $this->remove($targetFile); + } + + if (!self::box('link', $originFile, $targetFile)) { + $this->linkException($originFile, $targetFile, 'hard'); + } + } + } + + /** + * @param string $linkType Name of the link type, typically 'symbolic' or 'hard' + */ + private function linkException(string $origin, string $target, string $linkType): never + { + if (self::$lastError) { + if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) { + throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target); + } + } + throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s": ', $linkType, $origin, $target).self::$lastError, 0, null, $target); + } + + /** + * Resolves links in paths. + * + * With $canonicalize = false (default) + * - if $path does not exist or is not a link, returns null + * - if $path is a link, returns the next direct target of the link without considering the existence of the target + * + * With $canonicalize = true + * - if $path does not exist, returns null + * - if $path exists, returns its absolute fully resolved final version + */ + public function readlink(string $path, bool $canonicalize = false): ?string + { + if (!$canonicalize && !is_link($path)) { + return null; + } + + if ($canonicalize) { + if (!$this->exists($path)) { + return null; + } + + return realpath($path); + } + + return readlink($path); + } + + /** + * Given an existing path, convert it to a path relative to a given starting path. + */ + public function makePathRelative(string $endPath, string $startPath): string + { + if (!$this->isAbsolutePath($startPath)) { + throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath)); + } + + if (!$this->isAbsolutePath($endPath)) { + throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath)); + } + + // Normalize separators on Windows + if ('\\' === \DIRECTORY_SEPARATOR) { + $endPath = str_replace('\\', '/', $endPath); + $startPath = str_replace('\\', '/', $startPath); + } + + $splitDriveLetter = fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) + ? [substr($path, 2), strtoupper($path[0])] + : [$path, null]; + + $splitPath = function ($path) { + $result = []; + + foreach (explode('/', trim($path, '/')) as $segment) { + if ('..' === $segment) { + array_pop($result); + } elseif ('.' !== $segment && '' !== $segment) { + $result[] = $segment; + } + } + + return $result; + }; + + [$endPath, $endDriveLetter] = $splitDriveLetter($endPath); + [$startPath, $startDriveLetter] = $splitDriveLetter($startPath); + + $startPathArr = $splitPath($startPath); + $endPathArr = $splitPath($endPath); + + if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) { + // End path is on another drive, so no relative path exists + return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : ''); + } + + // Find for which directory the common path stops + $index = 0; + while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { + ++$index; + } + + // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) + if (1 === \count($startPathArr) && '' === $startPathArr[0]) { + $depth = 0; + } else { + $depth = \count($startPathArr) - $index; + } + + // Repeated "../" for each level need to reach the common path + $traverser = str_repeat('../', $depth); + + $endPathRemainder = implode('/', \array_slice($endPathArr, $index)); + + // Construct $endPath from traversing to the common path, then to the remaining $endPath + $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : ''); + + return '' === $relativePath ? './' : $relativePath; + } + + /** + * Mirrors a directory to another. + * + * Copies files and directories from the origin directory into the target directory. By default: + * + * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option) + * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option) + * + * @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created + * @param array $options An array of boolean options + * Valid options are: + * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) + * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) + * + * @throws IOException When file type is unknown + */ + public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = []): void + { + $targetDir = rtrim($targetDir, '/\\'); + $originDir = rtrim($originDir, '/\\'); + $originDirLen = \strlen($originDir); + + if (!$this->exists($originDir)) { + throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir); + } + + // Iterate in destination folder to remove obsolete entries + if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { + $deleteIterator = $iterator; + if (null === $deleteIterator) { + $flags = \FilesystemIterator::SKIP_DOTS; + $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); + } + $targetDirLen = \strlen($targetDir); + foreach ($deleteIterator as $file) { + $origin = $originDir.substr($file->getPathname(), $targetDirLen); + if (!$this->exists($origin)) { + $this->remove($file); + } + } + } + + $copyOnWindows = $options['copy_on_windows'] ?? false; + + if (null === $iterator) { + $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); + } + + $this->mkdir($targetDir); + $filesCreatedWhileMirroring = []; + + foreach ($iterator as $file) { + if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) { + continue; + } + + $target = $targetDir.substr($file->getPathname(), $originDirLen); + $filesCreatedWhileMirroring[$target] = true; + + if (!$copyOnWindows && is_link($file)) { + $this->symlink($file->getLinkTarget(), $target); + } elseif (is_dir($file)) { + $this->mkdir($target); + } elseif (is_file($file)) { + $this->copy($file, $target, $options['override'] ?? false); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); + } + } + } + + /** + * Returns whether the file path is an absolute path. + */ + public function isAbsolutePath(string $file): bool + { + return '' !== $file && (strspn($file, '/\\', 0, 1) + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === $file[1] + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, \PHP_URL_SCHEME) + ); + } + + /** + * Creates a temporary file with support for custom stream wrappers. + * + * @param string $prefix The prefix of the generated temporary filename + * Note: Windows uses only the first three characters of prefix + * @param string $suffix The suffix of the generated temporary filename + * + * @return string The new temporary filename (with path), or throw an exception on failure + */ + public function tempnam(string $dir, string $prefix, string $suffix = ''): string + { + [$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir); + + // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem + if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) { + // If tempnam failed or no scheme return the filename otherwise prepend the scheme + if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) { + if (null !== $scheme && 'gs' !== $scheme) { + return $scheme.'://'.$tmpFile; + } + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created: '.self::$lastError); + } + + // Loop until we create a valid temp file or have reached 10 attempts + for ($i = 0; $i < 10; ++$i) { + // Create a unique filename + $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix; + + // Use fopen instead of file_exists as some streams do not support stat + // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability + if (!$handle = self::box('fopen', $tmpFile, 'x+')) { + continue; + } + + // Close the file if it was successfully opened + self::box('fclose', $handle); + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created: '.self::$lastError); + } + + /** + * Atomically dumps content into a file. + * + * @param string|resource $content The data to write into the file + * + * @throws IOException if the file cannot be written to + */ + public function dumpFile(string $filename, $content): void + { + if (\is_array($content)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); + } + + $dir = \dirname($filename); + + if (is_link($filename) && $linkTarget = $this->readlink($filename)) { + $this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content); + + return; + } + + if (!is_dir($dir)) { + $this->mkdir($dir); + } + + // Will create a temp file with 0600 access rights + // when the filesystem supports chmod. + $tmpFile = $this->tempnam($dir, basename($filename)); + + try { + if (false === self::box('file_put_contents', $tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); + } + + self::box('chmod', $tmpFile, self::box('fileperms', $filename) ?: 0666 & ~umask()); + + $this->rename($tmpFile, $filename, true); + } finally { + if (file_exists($tmpFile)) { + self::box('unlink', $tmpFile); + } + } + } + + /** + * Appends content to an existing file. + * + * @param string|resource $content The content to append + * @param bool $lock Whether the file should be locked when writing to it + * + * @throws IOException If the file is not writable + */ + public function appendToFile(string $filename, $content, bool $lock = false): void + { + if (\is_array($content)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); + } + + $dir = \dirname($filename); + + if (!is_dir($dir)) { + $this->mkdir($dir); + } + + if (false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) { + throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); + } + } + + /** + * Returns the content of a file as a string. + * + * @throws IOException If the file cannot be read + */ + public function readFile(string $filename): string + { + if (is_dir($filename)) { + throw new IOException(sprintf('Failed to read file "%s": File is a directory.', $filename)); + } + + $content = self::box('file_get_contents', $filename); + if (false === $content) { + throw new IOException(sprintf('Failed to read file "%s": ', $filename).self::$lastError, 0, null, $filename); + } + + return $content; + } + + private function toIterable(string|iterable $files): iterable + { + return is_iterable($files) ? $files : [$files]; + } + + /** + * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]). + */ + private function getSchemeAndHierarchy(string $filename): array + { + $components = explode('://', $filename, 2); + + return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]]; + } + + private static function assertFunctionExists(string $func): void + { + if (!\function_exists($func)) { + throw new IOException(sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func)); + } + } + + private static function box(string $func, mixed ...$args): mixed + { + self::assertFunctionExists($func); + + self::$lastError = null; + set_error_handler(self::handleError(...)); + try { + return $func(...$args); + } finally { + restore_error_handler(); + } + } + + /** + * @internal + */ + public static function handleError(int $type, string $msg): void + { + self::$lastError = $msg; + } +} diff --git a/vendor/symfony/filesystem/LICENSE b/vendor/symfony/filesystem/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/filesystem/Path.php b/vendor/symfony/filesystem/Path.php new file mode 100644 index 0000000..db9ce4b --- /dev/null +++ b/vendor/symfony/filesystem/Path.php @@ -0,0 +1,816 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\InvalidArgumentException; +use Symfony\Component\Filesystem\Exception\RuntimeException; + +/** + * Contains utility methods for handling path strings. + * + * The methods in this class are able to deal with both UNIX and Windows paths + * with both forward and backward slashes. All methods return normalized parts + * containing only forward slashes and no excess "." and ".." segments. + * + * @author Bernhard Schussek + * @author Thomas Schulz + * @author Théo Fidry + */ +final class Path +{ + /** + * The number of buffer entries that triggers a cleanup operation. + */ + private const CLEANUP_THRESHOLD = 1250; + + /** + * The buffer size after the cleanup operation. + */ + private const CLEANUP_SIZE = 1000; + + /** + * Buffers input/output of {@link canonicalize()}. + * + * @var array + */ + private static array $buffer = []; + + private static int $bufferSize = 0; + + /** + * Canonicalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Furthermore, all "." and ".." segments are removed as far as possible. + * ".." segments at the beginning of relative paths are not removed. + * + * ```php + * echo Path::canonicalize("\symfony\puli\..\css\style.css"); + * // => /symfony/css/style.css + * + * echo Path::canonicalize("../css/./style.css"); + * // => ../css/style.css + * ``` + * + * This method is able to deal with both UNIX and Windows paths. + */ + public static function canonicalize(string $path): string + { + if ('' === $path) { + return ''; + } + + // This method is called by many other methods in this class. Buffer + // the canonicalized paths to make up for the severe performance + // decrease. + if (isset(self::$buffer[$path])) { + return self::$buffer[$path]; + } + + // Replace "~" with user's home directory. + if ('~' === $path[0]) { + $path = self::getHomeDirectory().substr($path, 1); + } + + $path = self::normalize($path); + + [$root, $pathWithoutRoot] = self::split($path); + + $canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot); + + // Add the root directory again + self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts); + ++self::$bufferSize; + + // Clean up regularly to prevent memory leaks + if (self::$bufferSize > self::CLEANUP_THRESHOLD) { + self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true); + self::$bufferSize = self::CLEANUP_SIZE; + } + + return $canonicalPath; + } + + /** + * Normalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Contrary to {@link canonicalize()}, this method does not remove invalid + * or dot path segments. Consequently, it is much more efficient and should + * be used whenever the given path is known to be a valid, absolute system + * path. + * + * This method is able to deal with both UNIX and Windows paths. + */ + public static function normalize(string $path): string + { + return str_replace('\\', '/', $path); + } + + /** + * Returns the directory part of the path. + * + * This method is similar to PHP's dirname(), but handles various cases + * where dirname() returns a weird result: + * + * - dirname() does not accept backslashes on UNIX + * - dirname("C:/symfony") returns "C:", not "C:/" + * - dirname("C:/") returns ".", not "C:/" + * - dirname("C:") returns ".", not "C:/" + * - dirname("symfony") returns ".", not "" + * - dirname() does not canonicalize the result + * + * This method fixes these shortcomings and behaves like dirname() + * otherwise. + * + * The result is a canonical path. + * + * @return string The canonical directory part. Returns the root directory + * if the root directory is passed. Returns an empty string + * if a relative path is passed that contains no slashes. + * Returns an empty string if an empty string is passed. + */ + public static function getDirectory(string $path): string + { + if ('' === $path) { + return ''; + } + + $path = self::canonicalize($path); + + // Maintain scheme + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $scheme = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + if (false === $dirSeparatorPosition = strrpos($path, '/')) { + return ''; + } + + // Directory equals root directory "/" + if (0 === $dirSeparatorPosition) { + return $scheme.'/'; + } + + // Directory equals Windows root "C:/" + if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) { + return $scheme.substr($path, 0, 3); + } + + return $scheme.substr($path, 0, $dirSeparatorPosition); + } + + /** + * Returns canonical path of the user's home directory. + * + * Supported operating systems: + * + * - UNIX + * - Windows8 and upper + * + * If your operating system or environment isn't supported, an exception is thrown. + * + * The result is a canonical path. + * + * @throws RuntimeException If your operating system or environment isn't supported + */ + public static function getHomeDirectory(): string + { + // For UNIX support + if (getenv('HOME')) { + return self::canonicalize(getenv('HOME')); + } + + // For >= Windows8 support + if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) { + return self::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH')); + } + + throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported."); + } + + /** + * Returns the root directory of a path. + * + * The result is a canonical path. + * + * @return string The canonical root directory. Returns an empty string if + * the given path is relative or empty. + */ + public static function getRoot(string $path): string + { + if ('' === $path) { + return ''; + } + + // Maintain scheme + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $scheme = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + $firstCharacter = $path[0]; + + // UNIX root "/" or "\" (Windows style) + if ('/' === $firstCharacter || '\\' === $firstCharacter) { + return $scheme.'/'; + } + + $length = \strlen($path); + + // Windows root + if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) { + // Special case: "C:" + if (2 === $length) { + return $scheme.$path.'/'; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return $scheme.$firstCharacter.$path[1].'/'; + } + } + + return ''; + } + + /** + * Returns the file name without the extension from a file path. + * + * @param string|null $extension if specified, only that extension is cut + * off (may contain leading dot) + */ + public static function getFilenameWithoutExtension(string $path, ?string $extension = null): string + { + if ('' === $path) { + return ''; + } + + if (null !== $extension) { + // remove extension and trailing dot + return rtrim(basename($path, $extension), '.'); + } + + return pathinfo($path, \PATHINFO_FILENAME); + } + + /** + * Returns the extension from a file path (without leading dot). + * + * @param bool $forceLowerCase forces the extension to be lower-case + */ + public static function getExtension(string $path, bool $forceLowerCase = false): string + { + if ('' === $path) { + return ''; + } + + $extension = pathinfo($path, \PATHINFO_EXTENSION); + + if ($forceLowerCase) { + $extension = self::toLower($extension); + } + + return $extension; + } + + /** + * Returns whether the path has an (or the specified) extension. + * + * @param string $path the path string + * @param string|string[]|null $extensions if null or not provided, checks if + * an extension exists, otherwise + * checks for the specified extension + * or array of extensions (with or + * without leading dot) + * @param bool $ignoreCase whether to ignore case-sensitivity + */ + public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = false): bool + { + if ('' === $path) { + return false; + } + + $actualExtension = self::getExtension($path, $ignoreCase); + + // Only check if path has any extension + if ([] === $extensions || null === $extensions) { + return '' !== $actualExtension; + } + + if (\is_string($extensions)) { + $extensions = [$extensions]; + } + + foreach ($extensions as $key => $extension) { + if ($ignoreCase) { + $extension = self::toLower($extension); + } + + // remove leading '.' in extensions array + $extensions[$key] = ltrim($extension, '.'); + } + + return \in_array($actualExtension, $extensions, true); + } + + /** + * Changes the extension of a path string. + * + * @param string $path The path string with filename.ext to change. + * @param string $extension new extension (with or without leading dot) + * + * @return string the path string with new file extension + */ + public static function changeExtension(string $path, string $extension): string + { + if ('' === $path) { + return ''; + } + + $actualExtension = self::getExtension($path); + $extension = ltrim($extension, '.'); + + // No extension for paths + if (str_ends_with($path, '/')) { + return $path; + } + + // No actual extension in path + if (!$actualExtension) { + return $path.(str_ends_with($path, '.') ? '' : '.').$extension; + } + + return substr($path, 0, -\strlen($actualExtension)).$extension; + } + + public static function isAbsolute(string $path): bool + { + if ('' === $path) { + return false; + } + + // Strip scheme + if (false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) { + $path = substr($path, $schemeSeparatorPosition + 3); + } + + $firstCharacter = $path[0]; + + // UNIX root "/" or "\" (Windows style) + if ('/' === $firstCharacter || '\\' === $firstCharacter) { + return true; + } + + // Windows root + if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) { + // Special case: "C:" + if (2 === \strlen($path)) { + return true; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return true; + } + } + + return false; + } + + public static function isRelative(string $path): bool + { + return !self::isAbsolute($path); + } + + /** + * Turns a relative path into an absolute path in canonical form. + * + * Usually, the relative path is appended to the given base path. Dot + * segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * echo Path::makeAbsolute("../style.css", "/symfony/puli/css"); + * // => /symfony/puli/style.css + * ``` + * + * If an absolute path is passed, that path is returned unless its root + * directory is different than the one of the base path. In that case, an + * exception is thrown. + * + * ```php + * Path::makeAbsolute("/style.css", "/symfony/puli/css"); + * // => /style.css + * + * Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css"); + * // => C:/style.css + * + * Path::makeAbsolute("C:/style.css", "/symfony/puli/css"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @param string $basePath an absolute base path + * + * @throws InvalidArgumentException if the base path is not absolute or if + * the given path is an absolute path with + * a different root than the base path + */ + public static function makeAbsolute(string $path, string $basePath): string + { + if ('' === $basePath) { + throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath)); + } + + if (!self::isAbsolute($basePath)) { + throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath)); + } + + if (self::isAbsolute($path)) { + return self::canonicalize($path); + } + + if (false !== $schemeSeparatorPosition = strpos($basePath, '://')) { + $scheme = substr($basePath, 0, $schemeSeparatorPosition + 3); + $basePath = substr($basePath, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path); + } + + /** + * Turns a path into a relative path. + * + * The relative path is created relative to the given base path: + * + * ```php + * echo Path::makeRelative("/symfony/style.css", "/symfony/puli"); + * // => ../style.css + * ``` + * + * If a relative path is passed and the base path is absolute, the relative + * path is returned unchanged: + * + * ```php + * Path::makeRelative("style.css", "/symfony/puli/css"); + * // => style.css + * ``` + * + * If both paths are relative, the relative path is created with the + * assumption that both paths are relative to the same directory: + * + * ```php + * Path::makeRelative("style.css", "symfony/puli/css"); + * // => ../../../style.css + * ``` + * + * If both paths are absolute, their root directory must be the same, + * otherwise an exception is thrown: + * + * ```php + * Path::makeRelative("C:/symfony/style.css", "/symfony/puli"); + * // InvalidArgumentException + * ``` + * + * If the passed path is absolute, but the base path is not, an exception + * is thrown as well: + * + * ```php + * Path::makeRelative("/symfony/style.css", "symfony/puli"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @throws InvalidArgumentException if the base path is not absolute or if + * the given path has a different root + * than the base path + */ + public static function makeRelative(string $path, string $basePath): string + { + $path = self::canonicalize($path); + $basePath = self::canonicalize($basePath); + + [$root, $relativePath] = self::split($path); + [$baseRoot, $relativeBasePath] = self::split($basePath); + + // If the base path is given as absolute path and the path is already + // relative, consider it to be relative to the given absolute path + // already + if ('' === $root && '' !== $baseRoot) { + // If base path is already in its root + if ('' === $relativeBasePath) { + $relativePath = ltrim($relativePath, './\\'); + } + + return $relativePath; + } + + // If the passed path is absolute, but the base path is not, we + // cannot generate a relative path + if ('' !== $root && '' === $baseRoot) { + throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath)); + } + + // Fail if the roots of the two paths are different + if ($baseRoot && $root !== $baseRoot) { + throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot)); + } + + if ('' === $relativeBasePath) { + return $relativePath; + } + + // Build a "../../" prefix with as many "../" parts as necessary + $parts = explode('/', $relativePath); + $baseParts = explode('/', $relativeBasePath); + $dotDotPrefix = ''; + + // Once we found a non-matching part in the prefix, we need to add + // "../" parts for all remaining parts + $match = true; + + foreach ($baseParts as $index => $basePart) { + if ($match && isset($parts[$index]) && $basePart === $parts[$index]) { + unset($parts[$index]); + + continue; + } + + $match = false; + $dotDotPrefix .= '../'; + } + + return rtrim($dotDotPrefix.implode('/', $parts), '/'); + } + + /** + * Returns whether the given path is on the local filesystem. + */ + public static function isLocal(string $path): bool + { + return '' !== $path && !str_contains($path, '://'); + } + + /** + * Returns the longest common base path in canonical form of a set of paths or + * `null` if the paths are on different Windows partitions. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * '/symfony/css/style.css', + * '/symfony/css/..' + * ); + * // => /symfony + * ``` + * + * The root is returned if no common base path can be found: + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * '/symfony/css/style.css', + * '/puli/css/..' + * ); + * // => / + * ``` + * + * If the paths are located on different Windows partitions, `null` is + * returned. + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * 'C:/symfony/css/style.css', + * 'D:/symfony/css/..' + * ); + * // => null + * ``` + */ + public static function getLongestCommonBasePath(string ...$paths): ?string + { + [$bpRoot, $basePath] = self::split(self::canonicalize(reset($paths))); + + for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) { + [$root, $path] = self::split(self::canonicalize(current($paths))); + + // If we deal with different roots (e.g. C:/ vs. D:/), it's time + // to quit + if ($root !== $bpRoot) { + return null; + } + + // Make the base path shorter until it fits into path + while (true) { + if ('.' === $basePath) { + // No more base paths + $basePath = ''; + + // next path + continue 2; + } + + // Prevent false positives for common prefixes + // see isBasePath() + if (str_starts_with($path.'/', $basePath.'/')) { + // next path + continue 2; + } + + $basePath = \dirname($basePath); + } + } + + return $bpRoot.$basePath; + } + + /** + * Joins two or more path strings into a canonical path. + */ + public static function join(string ...$paths): string + { + $finalPath = null; + $wasScheme = false; + + foreach ($paths as $path) { + if ('' === $path) { + continue; + } + + if (null === $finalPath) { + // For first part we keep slashes, like '/top', 'C:\' or 'phar://' + $finalPath = $path; + $wasScheme = str_contains($path, '://'); + continue; + } + + // Only add slash if previous part didn't end with '/' or '\' + if (!\in_array(substr($finalPath, -1), ['/', '\\'], true)) { + $finalPath .= '/'; + } + + // If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim + $finalPath .= $wasScheme ? $path : ltrim($path, '/'); + $wasScheme = false; + } + + if (null === $finalPath) { + return ''; + } + + return self::canonicalize($finalPath); + } + + /** + * Returns whether a path is a base path of another path. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * Path::isBasePath('/symfony', '/symfony/css'); + * // => true + * + * Path::isBasePath('/symfony', '/symfony'); + * // => true + * + * Path::isBasePath('/symfony', '/symfony/..'); + * // => false + * + * Path::isBasePath('/symfony', '/puli'); + * // => false + * ``` + */ + public static function isBasePath(string $basePath, string $ofPath): bool + { + $basePath = self::canonicalize($basePath); + $ofPath = self::canonicalize($ofPath); + + // Append slashes to prevent false positives when two paths have + // a common prefix, for example /base/foo and /base/foobar. + // Don't append a slash for the root "/", because then that root + // won't be discovered as common prefix ("//" is not a prefix of + // "/foobar/"). + return str_starts_with($ofPath.'/', rtrim($basePath, '/').'/'); + } + + /** + * @return string[] + */ + private static function findCanonicalParts(string $root, string $pathWithoutRoot): array + { + $parts = explode('/', $pathWithoutRoot); + + $canonicalParts = []; + + // Collapse "." and "..", if possible + foreach ($parts as $part) { + if ('.' === $part || '' === $part) { + continue; + } + + // Collapse ".." with the previous part, if one exists + // Don't collapse ".." if the previous part is also ".." + if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) { + array_pop($canonicalParts); + + continue; + } + + // Only add ".." prefixes for relative paths + if ('..' !== $part || '' === $root) { + $canonicalParts[] = $part; + } + } + + return $canonicalParts; + } + + /** + * Splits a canonical path into its root directory and the remainder. + * + * If the path has no root directory, an empty root directory will be + * returned. + * + * If the root directory is a Windows style partition, the resulting root + * will always contain a trailing slash. + * + * list ($root, $path) = Path::split("C:/symfony") + * // => ["C:/", "symfony"] + * + * list ($root, $path) = Path::split("C:") + * // => ["C:/", ""] + * + * @return array{string, string} an array with the root directory and the remaining relative path + */ + private static function split(string $path): array + { + if ('' === $path) { + return ['', '']; + } + + // Remember scheme as part of the root, if any + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $root = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); + } else { + $root = ''; + } + + $length = \strlen($path); + + // Remove and remember root directory + if (str_starts_with($path, '/')) { + $root .= '/'; + $path = $length > 1 ? substr($path, 1) : ''; + } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { + if (2 === $length) { + // Windows special case: "C:" + $root .= $path.'/'; + $path = ''; + } elseif ('/' === $path[2]) { + // Windows normal case: "C:/".. + $root .= substr($path, 0, 3); + $path = $length > 3 ? substr($path, 3) : ''; + } + } + + return [$root, $path]; + } + + private static function toLower(string $string): string + { + if (false !== $encoding = mb_detect_encoding($string, null, true)) { + return mb_strtolower($string, $encoding); + } + + return strtolower($string); + } + + private function __construct() + { + } +} diff --git a/vendor/symfony/filesystem/README.md b/vendor/symfony/filesystem/README.md new file mode 100644 index 0000000..f2f6d45 --- /dev/null +++ b/vendor/symfony/filesystem/README.md @@ -0,0 +1,13 @@ +Filesystem Component +==================== + +The Filesystem component provides basic utilities for the filesystem. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/filesystem.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/filesystem/composer.json b/vendor/symfony/filesystem/composer.json new file mode 100644 index 0000000..c781e55 --- /dev/null +++ b/vendor/symfony/filesystem/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/filesystem", + "type": "library", + "description": "Provides basic utilities for the filesystem", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/finder/CHANGELOG.md b/vendor/symfony/finder/CHANGELOG.md new file mode 100644 index 0000000..e838302 --- /dev/null +++ b/vendor/symfony/finder/CHANGELOG.md @@ -0,0 +1,103 @@ +CHANGELOG +========= + +6.4 +--- + + * Add early directory pruning to `Finder::filter()` + +6.2 +--- + + * Add `Finder::sortByExtension()` and `Finder::sortBySize()` + * Add `Finder::sortByCaseInsensitiveName()` to sort by name with case insensitive sorting methods + +6.0 +--- + + * Remove `Comparator::setTarget()` and `Comparator::setOperator()` + +5.4.0 +----- + + * Deprecate `Comparator::setTarget()` and `Comparator::setOperator()` + * Add a constructor to `Comparator` that allows setting target and operator + * Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified + * Add recursive .gitignore files support + +5.0.0 +----- + + * added `$useNaturalSort` argument to `Finder::sortByName()` + +4.3.0 +----- + + * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore + +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + * the `Finder::sortByName()` method will have a new `$useNaturalSort` + argument in version 5.0, not defining it is deprecated + * added `Finder::reverseSorting()` to reverse the sorting + +4.0.0 +----- + + * removed `ExceptionInterface` + * removed `Symfony\Component\Finder\Iterator\FilterIterator` + +3.4.0 +----- + + * deprecated `Symfony\Component\Finder\Iterator\FilterIterator` + * added Finder::hasResults() method to check if any results were found + +3.3.0 +----- + + * added double-star matching to Glob::toRegex() + +3.0.0 +----- + + * removed deprecated classes + +2.8.0 +----- + + * deprecated adapters and related classes + +2.5.0 +----- + * added support for GLOB_BRACE in the paths passed to Finder::in() + +2.3.0 +----- + + * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) + * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception + +2.2.0 +----- + + * added Finder::path() and Finder::notPath() methods + * added finder adapters to improve performance on specific platforms + * added support for wildcard characters (glob patterns) in the paths passed + to Finder::in() + +2.1.0 +----- + + * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and + Finder::sortByModifiedTime() + * added Countable to Finder + * added support for an array of directories as an argument to + Finder::exclude() + * added searching based on the file content via Finder::contains() and + Finder::notContains() + * added support for the != operator in the Comparator + * [BC BREAK] filter expressions (used for file name and content) are no more + considered as regexps but glob patterns when they are enclosed in '*' or '?' diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php new file mode 100644 index 0000000..c3d40e7 --- /dev/null +++ b/vendor/symfony/finder/Comparator/Comparator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * @author Fabien Potencier + */ +class Comparator +{ + private string $operator; + + public function __construct( + private string $target, + string $operator = '==', + ) { + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } + + /** + * Gets the target value. + */ + public function getTarget(): string + { + return $this->target; + } + + /** + * Gets the comparison operator. + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Tests against the target. + */ + public function test(mixed $test): bool + { + return match ($this->operator) { + '>' => $test > $this->target, + '>=' => $test >= $this->target, + '<' => $test < $this->target, + '<=' => $test <= $this->target, + '!=' => $test != $this->target, + default => $test == $this->target, + }; + } +} diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 0000000..e0c523d --- /dev/null +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier + */ +class DateComparator extends Comparator +{ + /** + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTimeImmutable($matches[2]); + $target = $date->format('U'); + } catch (\Exception) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = $matches[1] ?? '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + parent::__construct($target, $operator); + } +} diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 0000000..dd30820 --- /dev/null +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * @param string|null $test A comparison string or null + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(?string $test) + { + if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024 * 1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024 * 1024 * 1024; + break; + } + } + + parent::__construct($target, $matches[1] ?: '=='); + } +} diff --git a/vendor/symfony/finder/Exception/AccessDeniedException.php b/vendor/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000..ee195ea --- /dev/null +++ b/vendor/symfony/finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/finder/Exception/DirectoryNotFoundException.php b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000..c6cc0f2 --- /dev/null +++ b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Andreas Erhard + */ +class DirectoryNotFoundException extends \InvalidArgumentException +{ +} diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php new file mode 100644 index 0000000..0894dcd --- /dev/null +++ b/vendor/symfony/finder/Finder.php @@ -0,0 +1,856 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\LazyIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class Finder implements \IteratorAggregate, \Countable +{ + public const IGNORE_VCS_FILES = 1; + public const IGNORE_DOT_FILES = 2; + public const IGNORE_VCS_IGNORED_FILES = 4; + + private int $mode = 0; + private array $names = []; + private array $notNames = []; + private array $exclude = []; + private array $filters = []; + private array $pruneFilters = []; + private array $depths = []; + private array $sizes = []; + private bool $followLinks = false; + private bool $reverseSorting = false; + private \Closure|int|false $sort = false; + private int $ignore = 0; + private array $dirs = []; + private array $dates = []; + private array $iterators = []; + private array $contains = []; + private array $notContains = []; + private array $paths = []; + private array $notPaths = []; + private bool $ignoreUnreadableDirs = false; + + private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + */ + public static function create(): static + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories(): static + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files(): static + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * $finder->depth(['>= 1', '< 3']) + * + * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth(string|int|array $levels): static + { + foreach ((array) $levels as $level) { + $this->depths[] = new NumberComparator($level); + } + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); + * + * @param string|string[] $dates A date range string or an array of date ranges + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date(string|array $dates): static + { + foreach ((array) $dates as $date) { + $this->dates[] = new DateComparator($date); + } + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('/\.php$/') + * $finder->name('*.php') // same as above, without dot files + * $finder->name('test.php') + * $finder->name(['test.py', 'test.php']) + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name(string|array $patterns): static + { + $this->names = array_merge($this->names, (array) $patterns); + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName(string|array $patterns): static + { + $this->notNames = array_merge($this->notNames, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * $finder->contains(['dolor', '/ipsum/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains(string|array $patterns): static + { + $this->contains = array_merge($this->contains, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * $finder->notContains(['lorem', '/dolor/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains(string|array $patterns): static + { + $this->notContains = array_merge($this->notContains, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * $finder->path(['some dir', 'another/dir']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path(string|array $patterns): static + { + $this->paths = array_merge($this->paths, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * $finder->notPath(['some/file.txt', 'another/file.log']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath(string|array $patterns): static + { + $this->notPaths = array_merge($this->notPaths, (array) $patterns); + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * $finder->size(['> 10K', '< 20K']) + * + * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size(string|int|array $sizes): static + { + foreach ((array) $sizes as $size) { + $this->sizes[] = new NumberComparator($size); + } + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude(string|array $dirs): static + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles(bool $ignoreDotFiles): static + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS(bool $ignoreVCS): static + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Forces Finder to obey .gitignore and ignore files based on rules listed there. + * + * This option is disabled by default. + * + * @return $this + */ + public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static + { + if ($ignoreVCSIgnored) { + $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern(string|array $pattern): void + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure): static + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by extension. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByExtension(): static + { + $this->sort = SortableIterator::SORT_BY_EXTENSION; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL : SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by name case insensitive. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE; + + return $this; + } + + /** + * Sorts files and directories by size. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortBySize(): static + { + $this->sort = SortableIterator::SORT_BY_SIZE; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType(): static + { + $this->sort = SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime(): static + { + $this->sort = SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting(): static + { + $this->reverseSorting = true; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime(): static + { + $this->sort = SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime(): static + { + $this->sort = SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @param \Closure(SplFileInfo): bool $closure + * @param bool $prune Whether to skip traversing directories further + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure, bool $prune = false): static + { + $this->filters[] = $closure; + + if ($prune) { + $this->pruneFilters[] = $closure; + } + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks(): static + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @return $this + */ + public function ignoreUnreadableDirs(bool $ignore = true): static + { + $this->ignoreUnreadableDirs = $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|string[] $dirs A directory path or an array of directories + * + * @return $this + * + * @throws DirectoryNotFoundException if one of the directories does not exist + */ + public function in(string|array $dirs): static + { + $resolvedDirs = []; + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = [$this->normalizeDir($dir)]; + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { + sort($glob); + $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); + } else { + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, ...$resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator(): \Iterator + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + $iterator = $this->searchInDirectory($this->dirs[0]); + + if ($this->sort || $this->reverseSorting) { + $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + if ($this->sort || $this->reverseSorting) { + $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append(iterable $iterator): static + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif (is_iterable($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); + $it[$file->getPathname()] = $file; + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if any results were found. + */ + public function hasResults(): bool + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + */ + public function count(): int + { + return iterator_count($this->getIterator()); + } + + private function searchInDirectory(string $dir): \Iterator + { + $exclude = $this->exclude; + $notPaths = $this->notPaths; + + if ($this->pruneFilters) { + $exclude = array_merge($exclude, $this->pruneFilters); + } + + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $exclude = array_merge($exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $notPaths[] = '#(^|/)\..+(/|$)#'; + } + + $minDepth = 0; + $maxDepth = \PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($exclude) { + $iterator = new ExcludeDirectoryFilterIterator($iterator, $exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { + $iterator = new DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); + } + + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir); + } + + return $iterator; + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper + */ + private function normalizeDir(string $dir): string + { + if ('/' === $dir) { + return $dir; + } + + $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + + if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { + $dir .= '/'; + } + + return $dir; + } +} diff --git a/vendor/symfony/finder/Gitignore.php b/vendor/symfony/finder/Gitignore.php new file mode 100644 index 0000000..bf05c5b --- /dev/null +++ b/vendor/symfony/finder/Gitignore.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Gitignore matches against text. + * + * @author Michael Voříšek + * @author Ahmed Abdou + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * Format specification: https://git-scm.com/docs/gitignore#_pattern_format + */ + public static function toRegex(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, false); + } + + public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, true); + } + + private static function buildRegex(string $gitignoreFileContent, bool $inverted): string + { + $gitignoreFileContent = preg_replace('~(? '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex); + $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + */ + public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#'): string + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = \strlen($glob); + for ($i = 0; $i < $sizeGlob; ++$i) { + $car = $glob[$i]; + if ($firstByte && $strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = '/' === $car; + + if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { + $car = '[^/]++/'; + if (!isset($glob[$i + 3])) { + $car .= '?'; + } + + if ($strictLeadingDot) { + $car = '(?=[^\.])'.$car; + } + + $car = '/(?:'.$car.')*'; + $i += 2 + isset($glob[$i + 3]); + + if ('/' === $delimiter) { + $car = str_replace('/', '\\/', $car); + } + } + + if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } +} diff --git a/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/vendor/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000..82ee81d --- /dev/null +++ b/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class CustomFilterIterator extends \FilterIterator +{ + private array $filters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!\is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === $filter($fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000..718d42b --- /dev/null +++ b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class DateRangeFilterIterator extends \FilterIterator +{ + private array $comparators = []; + + /** + * @param \Iterator $iterator + * @param DateComparator[] $comparators + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000..1cddb5f --- /dev/null +++ b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator + */ +class DepthRangeFilterIterator extends \FilterIterator +{ + private int $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator<\RecursiveIterator> $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000..ebbc76e --- /dev/null +++ b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + * + * @implements \RecursiveIterator + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + /** @var \Iterator */ + private \Iterator $iterator; + private bool $isRecursive; + /** @var array */ + private array $excludedDirs = []; + private ?string $excludedPattern = null; + /** @var list */ + private array $pruneFilters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param list $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = []; + foreach ($directories as $directory) { + if (!\is_string($directory)) { + if (!\is_callable($directory)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + + $this->pruneFilters[] = $directory; + + continue; + } + + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || str_contains($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + if ($this->pruneFilters && $this->hasChildren()) { + foreach ($this->pruneFilters as $pruneFilter) { + if (!$pruneFilter($this->current())) { + return false; + } + } + } + + return true; + } + + public function hasChildren(): bool + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren(): self + { + $children = new self($this->iterator->getChildren(), []); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000..2130378 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class FileTypeFilterIterator extends \FilterIterator +{ + public const ONLY_FILES = 1; + public const ONLY_DIRECTORIES = 2; + + private int $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, int $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000..bdc71ff --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + return $this->isAccepted($content); + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000..05d9535 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + * + * @extends MultiplePcreFilterIterator + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/vendor/symfony/finder/Iterator/LazyIterator.php b/vendor/symfony/finder/Iterator/LazyIterator.php new file mode 100644 index 0000000..5b5806b --- /dev/null +++ b/vendor/symfony/finder/Iterator/LazyIterator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * @author Jérémy Derussé + * + * @internal + */ +class LazyIterator implements \IteratorAggregate +{ + private \Closure $iteratorFactory; + + public function __construct(callable $iteratorFactory) + { + $this->iteratorFactory = $iteratorFactory(...); + } + + public function getIterator(): \Traversable + { + yield from ($this->iteratorFactory)(); + } +} diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000..3450c49 --- /dev/null +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator + */ +abstract class MultiplePcreFilterIterator extends \FilterIterator +{ + protected array $matchRegexps = []; + protected array $noMatchRegexps = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is accepted by the regex filters. + * + * If there is no regexps defined in the class, this method will accept the string. + * Such case can be handled by child classes before calling the method if they want to + * apply a different behavior. + */ + protected function isAccepted(string $string): bool + { + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $string)) { + return false; + } + } + + // should at least match one rule + if ($this->matchRegexps) { + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $string)) { + return true; + } + } + + return false; + } + + // If there is no match rules, the file is accepted + return true; + } + + /** + * Checks whether the string is a regex. + */ + protected function isRegex(string $str): bool + { + $availableModifiers = 'imsxuADUn'; + + if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } + + /** + * Converts string into regexp. + */ + abstract protected function toRegex(string $str): string; +} diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000..c6d5813 --- /dev/null +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000..f5fd2d4 --- /dev/null +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + * + * @extends \RecursiveDirectoryIterator + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + private bool $ignoreUnreadableDirs; + private bool $ignoreFirstRewind = true; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private string $rootPath; + private string $subPath; + private string $directorySeparator = '/'; + + /** + * @throws \RuntimeException + */ + public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + */ + public function current(): SplFileInfo + { + // the logic here avoids redoing the same work in all iterations + + if (!isset($this->subPath)) { + $this->subPath = $this->getSubPath(); + } + $subPathname = $this->subPath; + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + $basePath = $this->rootPath; + + if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) { + $basePath .= $this->directorySeparator; + } + + return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); + } + + public function hasChildren(bool $allowLinks = false): bool + { + $hasChildren = parent::hasChildren($allowLinks); + + if (!$hasChildren || !$this->ignoreUnreadableDirs) { + return $hasChildren; + } + + try { + parent::getChildren(); + + return true; + } catch (\UnexpectedValueException) { + // If directory is unreadable and finder is set to ignore it, skip children + return false; + } + } + + /** + * @throws AccessDeniedException + */ + public function getChildren(): \RecursiveDirectoryIterator + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + + public function next(): void + { + $this->ignoreFirstRewind = false; + + parent::next(); + } + + public function rewind(): void + { + // some streams like FTP are not rewindable, ignore the first rewind after creation, + // as newly created DirectoryIterator does not need to be rewound + if ($this->ignoreFirstRewind) { + $this->ignoreFirstRewind = false; + + return; + } + + parent::rewind(); + } +} diff --git a/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000..925830a --- /dev/null +++ b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + private array $comparators = []; + + /** + * @param \Iterator $iterator + * @param NumberComparator[] $comparators + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000..177cd0b --- /dev/null +++ b/vendor/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class SortableIterator implements \IteratorAggregate +{ + public const SORT_BY_NONE = 0; + public const SORT_BY_NAME = 1; + public const SORT_BY_TYPE = 2; + public const SORT_BY_ACCESSED_TIME = 3; + public const SORT_BY_CHANGED_TIME = 4; + public const SORT_BY_MODIFIED_TIME = 5; + public const SORT_BY_NAME_NATURAL = 6; + public const SORT_BY_NAME_CASE_INSENSITIVE = 7; + public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8; + public const SORT_BY_EXTENSION = 9; + public const SORT_BY_SIZE = 10; + + /** @var \Traversable */ + private \Traversable $iterator; + private \Closure|int $sort; + + /** + * @param \Traversable $iterator + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, int|callable $sort, bool $reverseOrder = false) + { + $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + if ($a->isDir() && $b->isFile()) { + return -$order; + } elseif ($a->isFile() && $b->isDir()) { + return $order; + } + + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime()); + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime()); + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime()); + } elseif (self::SORT_BY_EXTENSION === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension()); + } elseif (self::SORT_BY_SIZE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize()); + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; + } elseif (\is_callable($sort)) { + $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...); + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + public function getIterator(): \Traversable + { + if (1 === $this->sort) { + return $this->iterator; + } + + $array = iterator_to_array($this->iterator, true); + + if (-1 === $this->sort) { + $array = array_reverse($array); + } else { + uasort($array, $this->sort); + } + + return new \ArrayIterator($array); + } +} diff --git a/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php new file mode 100644 index 0000000..b278706 --- /dev/null +++ b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Gitignore; + +/** + * @extends \FilterIterator + */ +final class VcsIgnoredFilterIterator extends \FilterIterator +{ + private string $baseDir; + + /** + * @var array + */ + private array $gitignoreFilesCache = []; + + /** + * @var array + */ + private array $ignoredPathsCache = []; + + /** + * @param \Iterator $iterator + */ + public function __construct(\Iterator $iterator, string $baseDir) + { + $this->baseDir = $this->normalizePath($baseDir); + + foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) { + if (@is_dir("{$directory}/.git")) { + $this->baseDir = $directory; + break; + } + } + + parent::__construct($iterator); + } + + public function accept(): bool + { + $file = $this->current(); + + $fileRealPath = $this->normalizePath($file->getRealPath()); + + return !$this->isIgnored($fileRealPath); + } + + private function isIgnored(string $fileRealPath): bool + { + if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) { + $fileRealPath .= '/'; + } + + if (isset($this->ignoredPathsCache[$fileRealPath])) { + return $this->ignoredPathsCache[$fileRealPath]; + } + + $ignored = false; + + foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { + if ($this->isIgnored($parentDirectory)) { + // rules in ignored directories are ignored, no need to check further. + break; + } + + $fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1); + + if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) { + continue; + } + + [$exclusionRegex, $inclusionRegex] = $regexps; + + if (preg_match($exclusionRegex, $fileRelativePath)) { + $ignored = true; + + continue; + } + + if (preg_match($inclusionRegex, $fileRelativePath)) { + $ignored = false; + } + } + + return $this->ignoredPathsCache[$fileRealPath] = $ignored; + } + + /** + * @return list + */ + private function parentDirectoriesUpwards(string $from): array + { + $parentDirectories = []; + + $parentDirectory = $from; + + while (true) { + $newParentDirectory = \dirname($parentDirectory); + + // dirname('/') = '/' + if ($newParentDirectory === $parentDirectory) { + break; + } + + $parentDirectories[] = $parentDirectory = $newParentDirectory; + } + + return $parentDirectories; + } + + private function parentDirectoriesUpTo(string $from, string $upTo): array + { + return array_filter( + $this->parentDirectoriesUpwards($from), + static fn (string $directory): bool => str_starts_with($directory, $upTo) + ); + } + + /** + * @return list + */ + private function parentDirectoriesDownwards(string $fileRealPath): array + { + return array_reverse( + $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) + ); + } + + /** + * @return array{0: string, 1: string}|null + */ + private function readGitignoreFile(string $path): ?array + { + if (\array_key_exists($path, $this->gitignoreFilesCache)) { + return $this->gitignoreFilesCache[$path]; + } + + if (!file_exists($path)) { + return $this->gitignoreFilesCache[$path] = null; + } + + if (!is_file($path) || !is_readable($path)) { + throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable."); + } + + $gitignoreFileContent = file_get_contents($path); + + return $this->gitignoreFilesCache[$path] = [ + Gitignore::toRegex($gitignoreFileContent), + Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent), + ]; + } + + private function normalizePath(string $path): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return str_replace('\\', '/', $path); + } + + return $path; + } +} diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/finder/README.md b/vendor/symfony/finder/README.md new file mode 100644 index 0000000..22bdeb9 --- /dev/null +++ b/vendor/symfony/finder/README.md @@ -0,0 +1,14 @@ +Finder Component +================ + +The Finder component finds files and directories via an intuitive fluent +interface. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/finder.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php new file mode 100644 index 0000000..2afc378 --- /dev/null +++ b/vendor/symfony/finder/SplFileInfo.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + /** + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct( + string $file, + private string $relativePath, + private string $relativePathname, + ) { + parent::__construct($file); + } + + /** + * Returns the relative path. + * + * This path does not contain the file name. + */ + public function getRelativePath(): string + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + */ + public function getRelativePathname(): string + { + return $this->relativePathname; + } + + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, \PATHINFO_FILENAME); + } + + /** + * Returns the contents of the file. + * + * @throws \RuntimeException + */ + public function getContents(): string + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $content = file_get_contents($this->getPathname()); + } finally { + restore_error_handler(); + } + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json new file mode 100644 index 0000000..2b70600 --- /dev/null +++ b/vendor/symfony/finder/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Finds files and directories via an intuitive fluent interface", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/flex/LICENSE b/vendor/symfony/flex/LICENSE new file mode 100644 index 0000000..3c464ca --- /dev/null +++ b/vendor/symfony/flex/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2019 Fabien Potencier + +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. diff --git a/vendor/symfony/flex/README.md b/vendor/symfony/flex/README.md new file mode 100644 index 0000000..1102474 --- /dev/null +++ b/vendor/symfony/flex/README.md @@ -0,0 +1,10 @@ +

    + +

    + +[Symfony Flex][1] helps developers create [Symfony][2] applications, from the most +simple micro-style projects to the more complex ones with dozens of +dependencies. + +[1]: https://symfony.com/doc/current/setup/flex.html +[2]: https://symfony.com diff --git a/vendor/symfony/flex/composer.json b/vendor/symfony/flex/composer.json new file mode 100644 index 0000000..36573d7 --- /dev/null +++ b/vendor/symfony/flex/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/flex", + "type": "composer-plugin", + "description": "Composer plugin for Symfony", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=8.0", + "composer-plugin-api": "^2.1" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "extra": { + "class": "Symfony\\Flex\\Flex" + } +} diff --git a/vendor/symfony/flex/src/Command/DumpEnvCommand.php b/vendor/symfony/flex/src/Command/DumpEnvCommand.php new file mode 100644 index 0000000..6f7ed6f --- /dev/null +++ b/vendor/symfony/flex/src/Command/DumpEnvCommand.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Command; + +use Composer\Command\BaseCommand; +use Composer\Config; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Dotenv\Dotenv; +use Symfony\Flex\Options; + +class DumpEnvCommand extends BaseCommand +{ + private $config; + private $options; + + public function __construct(Config $config, Options $options) + { + $this->config = $config; + $this->options = $options; + + parent::__construct(); + } + + protected function configure() + { + $this->setName('symfony:dump-env') + ->setAliases(['dump-env']) + ->setDescription('Compiles .env files to .env.local.php.') + ->setDefinition([ + new InputArgument('env', InputArgument::OPTIONAL, 'The application environment to dump .env files for - e.g. "prod".'), + ]) + ->addOption('empty', null, InputOption::VALUE_NONE, 'Ignore the content of .env files') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $runtime = $this->options->get('runtime') ?? []; + $envKey = $runtime['env_var_name'] ?? 'APP_ENV'; + + if ($env = $input->getArgument('env') ?? $runtime['env'] ?? null) { + $_SERVER[$envKey] = $env; + } + + $path = $this->options->get('root-dir').'/'.($runtime['dotenv_path'] ?? '.env'); + + if (!$env || !$input->getOption('empty')) { + $vars = $this->loadEnv($path, $env, $runtime); + $env = $vars[$envKey]; + } + + if ($input->getOption('empty')) { + $vars = [$envKey => $env]; + } + + $vars = var_export($vars, true); + $vars = <<getIO()->writeError('Successfully dumped .env files in .env.local.php'); + + return 0; + } + + private function loadEnv(string $path, ?string $env, array $runtime): array + { + if (!file_exists($autoloadFile = $this->config->get('vendor-dir').'/autoload.php')) { + throw new \RuntimeException(sprintf('Please run "composer install" before running this command: "%s" not found.', $autoloadFile)); + } + + require $autoloadFile; + + if (!class_exists(Dotenv::class)) { + throw new \RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); + } + + $envKey = $runtime['env_var_name'] ?? 'APP_ENV'; + $globalsBackup = [$_SERVER, $_ENV]; + unset($_SERVER[$envKey]); + $_ENV = [$envKey => $env]; + $_SERVER['SYMFONY_DOTENV_VARS'] = implode(',', array_keys($_SERVER)); + putenv('SYMFONY_DOTENV_VARS='.$_SERVER['SYMFONY_DOTENV_VARS']); + + try { + if (method_exists(Dotenv::class, 'usePutenv')) { + $dotenv = new Dotenv(); + } else { + $dotenv = new Dotenv(false); + } + + if (!$env && file_exists($p = "$path.local")) { + $env = $_ENV[$envKey] = $dotenv->parse(file_get_contents($p), $p)[$envKey] ?? null; + } + + if (!$env) { + throw new \RuntimeException(sprintf('Please provide the name of the environment either by passing it as command line argument or by defining the "%s" variable in the ".env.local" file.', $envKey)); + } + + $testEnvs = $runtime['test_envs'] ?? ['test']; + + if (method_exists($dotenv, 'loadEnv')) { + $dotenv->loadEnv($path, $envKey, 'dev', $testEnvs); + } else { + // fallback code in case your Dotenv component is not 4.2 or higher (when loadEnv() was added) + $dotenv->load(file_exists($path) || !file_exists($p = "$path.dist") ? $path : $p); + + if (!\in_array($env, $testEnvs, true) && file_exists($p = "$path.local")) { + $dotenv->load($p); + } + + if (file_exists($p = "$path.$env")) { + $dotenv->load($p); + } + + if (file_exists($p = "$path.$env.local")) { + $dotenv->load($p); + } + } + + unset($_ENV['SYMFONY_DOTENV_VARS']); + $env = $_ENV; + } finally { + list($_SERVER, $_ENV) = $globalsBackup; + } + + return $env; + } +} diff --git a/vendor/symfony/flex/src/Command/InstallRecipesCommand.php b/vendor/symfony/flex/src/Command/InstallRecipesCommand.php new file mode 100644 index 0000000..d1ae1a4 --- /dev/null +++ b/vendor/symfony/flex/src/Command/InstallRecipesCommand.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Command; + +use Composer\Command\BaseCommand; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Flex\Event\UpdateEvent; +use Symfony\Flex\Flex; + +class InstallRecipesCommand extends BaseCommand +{ + /** @var Flex */ + private $flex; + private $rootDir; + private $dotenvPath; + + public function __construct(/* cannot be type-hinted */ $flex, string $rootDir, string $dotenvPath = '.env') + { + $this->flex = $flex; + $this->rootDir = $rootDir; + $this->dotenvPath = $dotenvPath; + + parent::__construct(); + } + + protected function configure() + { + $this->setName('symfony:recipes:install') + ->setAliases(['recipes:install', 'symfony:sync-recipes', 'sync-recipes', 'fix-recipes']) + ->setDescription('Installs or reinstalls recipes for already installed packages.') + ->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Recipes that should be installed.') + ->addOption('force', null, InputOption::VALUE_NONE, 'Overwrite existing files when a new version of a recipe is available') + ->addOption('reset', null, InputOption::VALUE_NONE, 'Reset all recipes back to their initial state (should be combined with --force)') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $win = '\\' === \DIRECTORY_SEPARATOR; + $force = (bool) $input->getOption('force'); + + if ($force && !@is_executable(strtok(exec($win ? 'where git' : 'command -v git'), \PHP_EOL))) { + throw new RuntimeException('Cannot run "sync-recipes --force": git not found.'); + } + + $symfonyLock = $this->flex->getLock(); + $composer = $this->getComposer(); + $locker = $composer->getLocker(); + $lockData = $locker->getLockData(); + + $packages = []; + $totalPackages = []; + foreach ($lockData['packages'] as $pkg) { + $totalPackages[] = $pkg['name']; + if ($force || !$symfonyLock->has($pkg['name'])) { + $packages[] = $pkg['name']; + } + } + foreach ($lockData['packages-dev'] as $pkg) { + $totalPackages[] = $pkg['name']; + if ($force || !$symfonyLock->has($pkg['name'])) { + $packages[] = $pkg['name']; + } + } + + $io = $this->getIO(); + + if (!$io->isVerbose()) { + $io->writeError([ + 'Run command with -v to see more details', + '', + ]); + } + + if ($targetPackages = $input->getArgument('packages')) { + if ($invalidPackages = array_diff($targetPackages, $totalPackages)) { + $io->writeError(sprintf('Cannot update: some packages are not installed: %s', implode(', ', $invalidPackages))); + + return 1; + } + + if ($packagesRequiringForce = array_diff($targetPackages, $packages)) { + $io->writeError(sprintf('Recipe(s) already installed for: %s', implode(', ', $packagesRequiringForce))); + $io->writeError('Re-run the command with --force to re-install the recipes.'); + $io->writeError(''); + } + + $packages = array_diff($targetPackages, $packagesRequiringForce); + } + + if (!$packages) { + $io->writeError('No recipes to install.'); + + return 0; + } + + $composer = $this->getComposer(); + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + + $operations = []; + foreach ($packages as $package) { + if (null === $pkg = $installedRepo->findPackage($package, '*')) { + $io->writeError(sprintf('Package %s is not installed', $package)); + + return 1; + } + + $operations[] = new InstallOperation($pkg); + } + + $dotenvFile = $this->dotenvPath; + $dotenvPath = $this->rootDir.'/'.$dotenvFile; + + if ($createEnvLocal = $force && file_exists($dotenvPath) && file_exists($dotenvPath.'.dist') && !file_exists($dotenvPath.'.local')) { + rename($dotenvPath, $dotenvPath.'.local'); + $pipes = []; + proc_close(proc_open(sprintf('git mv %s %s > %s 2>&1 || %s %1$s %2$s', ProcessExecutor::escape($dotenvFile.'.dist'), ProcessExecutor::escape($dotenvFile), $win ? 'NUL' : '/dev/null', $win ? 'rename' : 'mv'), $pipes, $pipes, $this->rootDir)); + if (file_exists($this->rootDir.'/phpunit.xml.dist')) { + touch($dotenvPath.'.test'); + } + } + + $this->flex->update(new UpdateEvent($force, (bool) $input->getOption('reset')), $operations); + + if ($force) { + $output = [ + '', + ' ', + ' Files have been reset to the latest version of the recipe. ', + ' ', + '', + ' * Use git diff to inspect the changes.', + '', + ' Not all of the changes will be relevant to your app: you now', + ' need to selectively add or revert them using e.g. a combination', + ' of git add -p and git checkout -p', + '', + ]; + + if ($createEnvLocal) { + $output[] = ' Dotenv files have been renamed: .env -> .env.local and .env.dist -> .env'; + $output[] = ' See https://symfony.com/doc/current/configuration/dot-env-changes.html'; + $output[] = ''; + } + + $output[] = ' * Use git checkout . to revert the changes.'; + $output[] = ''; + + if ($createEnvLocal) { + $root = '.' !== $this->rootDir ? $this->rootDir.'/' : ''; + $output[] = ' To revert the changes made to .env files, run'; + $output[] = sprintf(' git mv %s %s && %s %s %1$s', ProcessExecutor::escape($root.$dotenvFile), ProcessExecutor::escape($root.$dotenvFile.'.dist'), $win ? 'rename' : 'mv', ProcessExecutor::escape($root.$dotenvFile.'.local')); + $output[] = ''; + } + + $output[] = ' New (untracked) files can be inspected using git clean --dry-run'; + $output[] = ' Add the new files you want to keep using git add'; + $output[] = ' then delete the rest using git clean --force'; + $output[] = ''; + + $io->write($output); + } + + return 0; + } +} diff --git a/vendor/symfony/flex/src/Command/RecipesCommand.php b/vendor/symfony/flex/src/Command/RecipesCommand.php new file mode 100644 index 0000000..3cdac81 --- /dev/null +++ b/vendor/symfony/flex/src/Command/RecipesCommand.php @@ -0,0 +1,344 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Command; + +use Composer\Command\BaseCommand; +use Composer\Downloader\TransportException; +use Composer\Package\Package; +use Composer\Util\HttpDownloader; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Flex\GithubApi; +use Symfony\Flex\InformationOperation; +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; + +/** + * @author Maxime Hélias + */ +class RecipesCommand extends BaseCommand +{ + /** @var \Symfony\Flex\Flex */ + private $flex; + + private Lock $symfonyLock; + private GithubApi $githubApi; + + public function __construct(/* cannot be type-hinted */ $flex, Lock $symfonyLock, HttpDownloader $downloader) + { + $this->flex = $flex; + $this->symfonyLock = $symfonyLock; + $this->githubApi = new GithubApi($downloader); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('symfony:recipes') + ->setAliases(['recipes']) + ->setDescription('Shows information about all available recipes.') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect, if not provided all packages are.'), + ]) + ->addOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only recipes that are outdated') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository(); + + // Inspect one or all packages + $package = $input->getArgument('package'); + if (null !== $package) { + $packages = [strtolower($package)]; + } else { + $locker = $this->getComposer()->getLocker(); + $lockData = $locker->getLockData(); + + // Merge all packages installed + $packages = array_column(array_merge($lockData['packages'], $lockData['packages-dev']), 'name'); + $packages = array_unique(array_merge($packages, array_keys($this->symfonyLock->all()))); + } + + $operations = []; + foreach ($packages as $name) { + $pkg = $installedRepo->findPackage($name, '*'); + + if (!$pkg && $this->symfonyLock->has($name)) { + $pkgVersion = $this->symfonyLock->get($name)['version']; + $pkg = new Package($name, $pkgVersion, $pkgVersion); + } elseif (!$pkg) { + $this->getIO()->writeError(sprintf('Package %s is not installed', $name)); + + continue; + } + + $operations[] = new InformationOperation($pkg); + } + + $recipes = $this->flex->fetchRecipes($operations, false); + ksort($recipes); + + $nbRecipe = \count($recipes); + if ($nbRecipe <= 0) { + $this->getIO()->writeError('No recipe found'); + + return 1; + } + + // Display the information about a specific recipe + if (1 === $nbRecipe) { + $this->displayPackageInformation(current($recipes)); + + return 0; + } + + $outdated = $input->getOption('outdated'); + + $write = []; + $hasOutdatedRecipes = false; + foreach ($recipes as $name => $recipe) { + $lockRef = $this->symfonyLock->get($name)['recipe']['ref'] ?? null; + + $additional = null; + if (null === $lockRef && null !== $recipe->getRef()) { + $additional = '(recipe not installed)'; + } elseif ($recipe->getRef() !== $lockRef && !$recipe->isAuto()) { + $additional = '(update available)'; + } + + if ($outdated && null === $additional) { + continue; + } + + $hasOutdatedRecipes = true; + $write[] = sprintf(' * %s %s', $name, $additional); + } + + // Nothing to display + if (!$hasOutdatedRecipes) { + return 0; + } + + $this->getIO()->write(array_merge([ + '', + ' ', + sprintf(' %s recipes. ', $outdated ? ' Outdated' : 'Available'), + ' ', + '', + ], $write, [ + '', + 'Run:', + ' * composer recipes vendor/package to see details about a recipe.', + ' * composer recipes:update vendor/package to update that recipe.', + '', + ])); + + if ($outdated) { + return 1; + } + + return 0; + } + + private function displayPackageInformation(Recipe $recipe) + { + $io = $this->getIO(); + $recipeLock = $this->symfonyLock->get($recipe->getName()); + + $lockRef = $recipeLock['recipe']['ref'] ?? null; + $lockRepo = $recipeLock['recipe']['repo'] ?? null; + $lockFiles = $recipeLock['files'] ?? null; + $lockBranch = $recipeLock['recipe']['branch'] ?? null; + $lockVersion = $recipeLock['recipe']['version'] ?? $recipeLock['version'] ?? null; + + if ('master' === $lockBranch && \in_array($lockRepo, ['github.com/symfony/recipes', 'github.com/symfony/recipes-contrib'])) { + $lockBranch = 'main'; + } + + $status = 'up to date'; + if ($recipe->isAuto()) { + $status = 'auto-generated recipe'; + } elseif (null === $lockRef && null !== $recipe->getRef()) { + $status = 'recipe not installed'; + } elseif ($recipe->getRef() !== $lockRef) { + $status = 'update available'; + } + + $gitSha = null; + $commitDate = null; + if (null !== $lockRef && null !== $lockRepo) { + try { + $recipeCommitData = $this->githubApi->findRecipeCommitDataFromTreeRef( + $recipe->getName(), + $lockRepo, + $lockBranch ?? '', + $lockVersion, + $lockRef + ); + $gitSha = $recipeCommitData ? $recipeCommitData['commit'] : null; + $commitDate = $recipeCommitData ? $recipeCommitData['date'] : null; + } catch (TransportException $exception) { + $io->writeError('Error downloading exact git sha for installed recipe.'); + } + } + + $io->write('name : '.$recipe->getName()); + $io->write('version : '.($lockVersion ?? 'n/a')); + $io->write('status : '.$status); + if (!$recipe->isAuto() && null !== $lockVersion) { + $recipeUrl = sprintf( + 'https://%s/tree/%s/%s/%s', + $lockRepo, + // if something fails, default to the branch as the closest "sha" + $gitSha ?? $lockBranch, + $recipe->getName(), + $lockVersion + ); + + $io->write('installed recipe : '.$recipeUrl); + } + + if ($lockRef !== $recipe->getRef()) { + $io->write('latest recipe : '.$recipe->getURL()); + } + + if ($lockRef !== $recipe->getRef() && null !== $lockVersion) { + $historyUrl = sprintf( + 'https://%s/commits/%s/%s', + $lockRepo, + $lockBranch, + $recipe->getName() + ); + + // show commits since one second after the currently-installed recipe + if (null !== $commitDate) { + $historyUrl .= '?since='; + $historyUrl .= (new \DateTime($commitDate)) + ->setTimezone(new \DateTimeZone('UTC')) + ->modify('+1 seconds') + ->format('Y-m-d\TH:i:s\Z'); + } + + $io->write('recipe history : '.$historyUrl); + } + + if (null !== $lockFiles) { + $io->write('files : '); + $io->write(''); + + $tree = $this->generateFilesTree($lockFiles); + + $this->displayFilesTree($tree); + } + + if ($lockRef !== $recipe->getRef()) { + $io->write([ + '', + 'Update this recipe by running:', + sprintf('composer recipes:update %s', $recipe->getName()), + ]); + } + } + + private function generateFilesTree(array $files): array + { + $tree = []; + foreach ($files as $file) { + $path = explode('/', $file); + + $tree = array_merge_recursive($tree, $this->addNode($path)); + } + + return $tree; + } + + private function addNode(array $node): array + { + $current = array_shift($node); + + $subTree = []; + if (null !== $current) { + $subTree[$current] = $this->addNode($node); + } + + return $subTree; + } + + /** + * Note : We do not display file modification information with Configurator like ComposerScripts, Container, DockerComposer, Dockerfile, Env, Gitignore and Makefile. + */ + private function displayFilesTree(array $tree) + { + end($tree); + $endKey = key($tree); + foreach ($tree as $dir => $files) { + $treeBar = '├'; + $total = \count($files); + if (0 === $total || $endKey === $dir) { + $treeBar = '└'; + } + + $info = sprintf( + '%s──%s', + $treeBar, + $dir + ); + $this->writeTreeLine($info); + + $treeBar = str_replace('└', ' ', $treeBar); + + $this->displayTree($files, $treeBar); + } + } + + private function displayTree(array $tree, $previousTreeBar = '├', $level = 1) + { + $previousTreeBar = str_replace('├', '│', $previousTreeBar); + $treeBar = $previousTreeBar.' ├'; + + $i = 0; + $total = \count($tree); + + foreach ($tree as $dir => $files) { + ++$i; + if ($i === $total) { + $treeBar = $previousTreeBar.' └'; + } + + $info = sprintf( + '%s──%s', + $treeBar, + $dir + ); + $this->writeTreeLine($info); + + $treeBar = str_replace('└', ' ', $treeBar); + + $this->displayTree($files, $treeBar, $level + 1); + } + } + + private function writeTreeLine($line) + { + $io = $this->getIO(); + if (!$io->isDecorated()) { + $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); + } + + $io->write($line); + } +} diff --git a/vendor/symfony/flex/src/Command/UpdateRecipesCommand.php b/vendor/symfony/flex/src/Command/UpdateRecipesCommand.php new file mode 100644 index 0000000..15296ef --- /dev/null +++ b/vendor/symfony/flex/src/Command/UpdateRecipesCommand.php @@ -0,0 +1,415 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Command; + +use Composer\Command\BaseCommand; +use Composer\IO\IOInterface; +use Composer\Package\Package; +use Composer\Package\PackageInterface; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Flex\Configurator; +use Symfony\Flex\Downloader; +use Symfony\Flex\Flex; +use Symfony\Flex\GithubApi; +use Symfony\Flex\InformationOperation; +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipePatcher; +use Symfony\Flex\Update\RecipeUpdate; + +class UpdateRecipesCommand extends BaseCommand +{ + /** @var Flex */ + private $flex; + private $downloader; + private $configurator; + private $rootDir; + private $githubApi; + private $processExecutor; + + public function __construct(/* cannot be type-hinted */ $flex, Downloader $downloader, $httpDownloader, Configurator $configurator, string $rootDir) + { + $this->flex = $flex; + $this->downloader = $downloader; + $this->configurator = $configurator; + $this->rootDir = $rootDir; + $this->githubApi = new GithubApi($httpDownloader); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('symfony:recipes:update') + ->setAliases(['recipes:update']) + ->setDescription('Updates an already-installed recipe to the latest version.') + ->addArgument('package', InputArgument::OPTIONAL, 'Recipe that should be updated.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $win = '\\' === \DIRECTORY_SEPARATOR; + $runtimeExceptionClass = class_exists(RuntimeException::class) ? RuntimeException::class : \RuntimeException::class; + if (!@is_executable(strtok(exec($win ? 'where git' : 'command -v git'), \PHP_EOL))) { + throw new $runtimeExceptionClass('Cannot run "recipes:update": git not found.'); + } + + $io = $this->getIO(); + if (!$this->isIndexClean($io)) { + $io->write([ + ' Cannot run recipes:update: Your git index contains uncommitted changes.', + ' Please commit or stash them and try again!', + ]); + + return 1; + } + + $packageName = $input->getArgument('package'); + $symfonyLock = $this->flex->getLock(); + if (!$packageName) { + $packageName = $this->askForPackage($io, $symfonyLock); + + if (null === $packageName) { + $io->writeError('All packages appear to be up-to-date!'); + + return 0; + } + } + + if (!$symfonyLock->has($packageName)) { + $io->writeError([ + 'Package not found inside symfony.lock. It looks like it\'s not installed?', + sprintf('Try running composer recipes:install %s --force -v to re-install the recipe.', $packageName), + ]); + + return 1; + } + + $packageLockData = $symfonyLock->get($packageName); + if (!isset($packageLockData['recipe'])) { + $io->writeError([ + 'It doesn\'t look like this package had a recipe when it was originally installed.', + 'To install the latest version of the recipe, if there is one, run:', + sprintf(' composer recipes:install %s --force -v', $packageName), + ]); + + return 1; + } + + $recipeRef = $packageLockData['recipe']['ref'] ?? null; + $recipeVersion = $packageLockData['recipe']['version'] ?? null; + if (!$recipeRef || !$recipeVersion) { + $io->writeError([ + 'The version of the installed recipe was not saved into symfony.lock.', + 'This is possible if it was installed by an old version of Symfony Flex.', + 'Update the recipe by re-installing the latest version with:', + sprintf(' composer recipes:install %s --force -v', $packageName), + ]); + + return 1; + } + + $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository(); + $package = $installedRepo->findPackage($packageName, '*') ?? new Package($packageName, $packageLockData['version'], $packageLockData['version']); + $originalRecipe = $this->getRecipe($package, $recipeRef, $recipeVersion); + + if (null === $originalRecipe) { + $io->writeError([ + 'The original recipe version you have installed could not be found, it may be too old.', + 'Update the recipe by re-installing the latest version with:', + sprintf(' composer recipes:install %s --force -v', $packageName), + ]); + + return 1; + } + + $newRecipe = $this->getRecipe($package); + + if ($newRecipe->getRef() === $originalRecipe->getRef()) { + $io->write(sprintf('This recipe for %s is already at the latest version.', $packageName)); + + return 0; + } + + $io->write([ + sprintf(' Updating recipe for %s...', $packageName), + '', + ]); + + $recipeUpdate = new RecipeUpdate($originalRecipe, $newRecipe, $symfonyLock, $this->rootDir); + $this->configurator->populateUpdate($recipeUpdate); + $originalComposerJsonHash = $this->flex->getComposerJsonHash(); + $patcher = new RecipePatcher($this->rootDir, $io); + + try { + $patch = $patcher->generatePatch($recipeUpdate->getOriginalFiles(), $recipeUpdate->getNewFiles()); + $hasConflicts = !$patcher->applyPatch($patch); + } catch (\Throwable $throwable) { + $io->writeError([ + 'There was an error applying the recipe update patch', + $throwable->getMessage(), + '', + 'Update the recipe by re-installing the latest version with:', + sprintf(' composer recipes:install %s --force -v', $packageName), + ]); + + return 1; + } + + $symfonyLock->add($packageName, $newRecipe->getLock()); + $this->flex->finish($this->rootDir, $originalComposerJsonHash); + + // stage symfony.lock, as all patched files with already be staged + $cmdOutput = ''; + $this->getProcessExecutor()->execute('git add symfony.lock', $cmdOutput, $this->rootDir); + + $io->write([ + ' ', + ' Yes! Recipe updated! ', + ' ', + '', + ]); + + if ($hasConflicts) { + $io->write([ + ' The recipe was updated but with one or more conflicts.', + ' Run git status to see them.', + ' After resolving, commit your changes like normal.', + ]); + } else { + if (!$patch->getPatch()) { + // no changes were required + $io->write([ + ' No files were changed as a result of the update.', + ]); + } else { + $io->write([ + ' Run git status or git diff --cached to see the changes.', + ' When you\'re ready, commit these changes like normal.', + ]); + } + } + + if (0 !== \count($recipeUpdate->getCopyFromPackagePaths())) { + $io->write([ + '', + ' NOTE:', + ' This recipe copies the following paths from the bundle into your app:', + ]); + foreach ($recipeUpdate->getCopyFromPackagePaths() as $source => $target) { + $io->write(sprintf(' * %s => %s', $source, $target)); + } + $io->write([ + '', + ' The recipe updater has no way of knowing if these files have changed since you originally installed the recipe.', + ' And so, no updates were made to these paths.', + ]); + } + + if (0 !== \count($patch->getRemovedPatches())) { + if (1 === \count($patch->getRemovedPatches())) { + $notes = [ + sprintf(' The file %s was not updated because it doesn\'t exist in your app.', array_keys($patch->getRemovedPatches())[0]), + ]; + } else { + $notes = [' The following files were not updated because they don\'t exist in your app:']; + foreach ($patch->getRemovedPatches() as $filename => $contents) { + $notes[] = sprintf(' * %s', $filename); + } + } + $io->write([ + '', + ' NOTE:', + ]); + $io->write($notes); + $io->write(''); + if ($io->askConfirmation(' Would you like to save the "diff" to a file so you can review it? (Y/n) ')) { + $patchFilename = str_replace('/', '.', $packageName).'.updates-for-deleted-files.patch'; + file_put_contents($this->rootDir.'/'.$patchFilename, implode("\n", $patch->getRemovedPatches())); + $io->write([ + '', + sprintf(' Saved diff to %s', $patchFilename), + ]); + } + } + + if ($patch->getPatch()) { + $io->write(''); + $io->write(' Calculating CHANGELOG...', false); + $changelog = $this->generateChangelog($originalRecipe); + $io->write("\r", false); // clear current line + if ($changelog) { + $io->write($changelog); + } else { + $io->write('No CHANGELOG could be calculated.'); + } + } + + return 0; + } + + private function getRecipe(PackageInterface $package, ?string $recipeRef = null, ?string $recipeVersion = null): ?Recipe + { + $operation = new InformationOperation($package); + if (null !== $recipeRef) { + $operation->setSpecificRecipeVersion($recipeRef, $recipeVersion); + } + $recipes = $this->downloader->getRecipes([$operation]); + + if (0 === \count($recipes['manifests'] ?? [])) { + return null; + } + + return new Recipe( + $package, + $package->getName(), + $operation->getOperationType(), + $recipes['manifests'][$package->getName()], + $recipes['locks'][$package->getName()] ?? [] + ); + } + + private function generateChangelog(Recipe $originalRecipe): ?array + { + $recipeData = $originalRecipe->getLock()['recipe'] ?? null; + if (null === $recipeData) { + return null; + } + + if (!isset($recipeData['ref']) || !isset($recipeData['repo']) || !isset($recipeData['branch']) || !isset($recipeData['version'])) { + return null; + } + + $currentRecipeVersionData = $this->githubApi->findRecipeCommitDataFromTreeRef( + $originalRecipe->getName(), + $recipeData['repo'], + $recipeData['branch'], + $recipeData['version'], + $recipeData['ref'] + ); + + if (!$currentRecipeVersionData) { + return null; + } + + $recipeVersions = $this->githubApi->getVersionsOfRecipe( + $recipeData['repo'], + $recipeData['branch'], + $originalRecipe->getName() + ); + if (!$recipeVersions) { + return null; + } + + $newerRecipeVersions = array_filter($recipeVersions, function ($version) use ($recipeData) { + return version_compare($version, $recipeData['version'], '>'); + }); + + $newCommits = $currentRecipeVersionData['new_commits']; + foreach ($newerRecipeVersions as $newerRecipeVersion) { + $newCommits = array_merge( + $newCommits, + $this->githubApi->getCommitDataForPath($recipeData['repo'], $originalRecipe->getName().'/'.$newerRecipeVersion, $recipeData['branch']) + ); + } + + $newCommits = array_unique($newCommits); + asort($newCommits); + + $pullRequests = []; + foreach ($newCommits as $commit => $date) { + $pr = $this->githubApi->getPullRequestForCommit($commit, $recipeData['repo']); + if ($pr) { + $pullRequests[$pr['number']] = $pr; + } + } + + $lines = []; + // borrowed from symfony/console's OutputFormatterStyle + $handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); + foreach ($pullRequests as $number => $data) { + $url = $data['url']; + if ($handlesHrefGracefully) { + $url = "\033]8;;$url\033\\$number\033]8;;\033\\"; + } + $lines[] = sprintf(' * %s (PR %s)', $data['title'], $url); + } + + return $lines; + } + + private function askForPackage(IOInterface $io, Lock $symfonyLock): ?string + { + $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository(); + + $operations = []; + foreach ($symfonyLock->all() as $name => $lock) { + if (isset($lock['recipe']['ref'])) { + $package = $installedRepo->findPackage($name, '*') ?? new Package($name, $lock['version'], $lock['version']); + $operations[] = new InformationOperation($package); + } + } + + $recipes = $this->flex->fetchRecipes($operations, false); + ksort($recipes); + + $outdatedRecipes = []; + foreach ($recipes as $name => $recipe) { + $lockRef = $symfonyLock->get($name)['recipe']['ref'] ?? null; + + if (null !== $lockRef && $recipe->getRef() !== $lockRef && !$recipe->isAuto()) { + $outdatedRecipes[] = $name; + } + } + + if (0 === \count($outdatedRecipes)) { + return null; + } + + $question = 'Which outdated recipe would you like to update? (default: 0)'; + + $choice = $io->select( + $question, + $outdatedRecipes, + 0 + ); + + return $outdatedRecipes[$choice]; + } + + private function isIndexClean(IOInterface $io): bool + { + $output = ''; + + $this->getProcessExecutor()->execute('git status --porcelain --untracked-files=no', $output, $this->rootDir); + if ('' !== trim($output)) { + return false; + } + + return true; + } + + private function getProcessExecutor(): ProcessExecutor + { + if (null === $this->processExecutor) { + $this->processExecutor = new ProcessExecutor($this->getIO()); + } + + return $this->processExecutor; + } +} diff --git a/vendor/symfony/flex/src/Configurator.php b/vendor/symfony/flex/src/Configurator.php new file mode 100644 index 0000000..5697682 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Symfony\Flex\Configurator\AbstractConfigurator; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class Configurator +{ + private $composer; + private $io; + private $options; + private $configurators; + private $postInstallConfigurators; + private $cache; + + public function __construct(Composer $composer, IOInterface $io, Options $options) + { + $this->composer = $composer; + $this->io = $io; + $this->options = $options; + // ordered list of configurators + $this->configurators = [ + 'bundles' => Configurator\BundlesConfigurator::class, + 'copy-from-recipe' => Configurator\CopyFromRecipeConfigurator::class, + 'copy-from-package' => Configurator\CopyFromPackageConfigurator::class, + 'env' => Configurator\EnvConfigurator::class, + 'container' => Configurator\ContainerConfigurator::class, + 'makefile' => Configurator\MakefileConfigurator::class, + 'composer-scripts' => Configurator\ComposerScriptsConfigurator::class, + 'gitignore' => Configurator\GitignoreConfigurator::class, + 'dockerfile' => Configurator\DockerfileConfigurator::class, + 'docker-compose' => Configurator\DockerComposeConfigurator::class, + ]; + $this->postInstallConfigurators = [ + 'add-lines' => Configurator\AddLinesConfigurator::class, + ]; + } + + public function install(Recipe $recipe, Lock $lock, array $options = []) + { + $manifest = $recipe->getManifest(); + foreach (array_keys($this->configurators) as $key) { + if (isset($manifest[$key])) { + $this->get($key)->configure($recipe, $manifest[$key], $lock, $options); + } + } + } + + /** + * Run after all recipes have been installed to run post-install configurators. + */ + public function postInstall(Recipe $recipe, Lock $lock, array $options = []) + { + $manifest = $recipe->getManifest(); + foreach (array_keys($this->postInstallConfigurators) as $key) { + if (isset($manifest[$key])) { + $this->get($key)->configure($recipe, $manifest[$key], $lock, $options); + } + } + } + + public function populateUpdate(RecipeUpdate $recipeUpdate): void + { + $originalManifest = $recipeUpdate->getOriginalRecipe()->getManifest(); + $newManifest = $recipeUpdate->getNewRecipe()->getManifest(); + $allConfigurators = array_merge($this->configurators, $this->postInstallConfigurators); + foreach (array_keys($allConfigurators) as $key) { + if (!isset($originalManifest[$key]) && !isset($newManifest[$key])) { + continue; + } + + $this->get($key)->update($recipeUpdate, $originalManifest[$key] ?? [], $newManifest[$key] ?? []); + } + } + + public function unconfigure(Recipe $recipe, Lock $lock) + { + $manifest = $recipe->getManifest(); + + $allConfigurators = array_merge($this->configurators, $this->postInstallConfigurators); + + foreach (array_keys($allConfigurators) as $key) { + if (isset($manifest[$key])) { + $this->get($key)->unconfigure($recipe, $manifest[$key], $lock); + } + } + } + + private function get($key): AbstractConfigurator + { + if (!isset($this->configurators[$key]) && !isset($this->postInstallConfigurators[$key])) { + throw new \InvalidArgumentException(sprintf('Unknown configurator "%s".', $key)); + } + + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + + $class = isset($this->configurators[$key]) ? $this->configurators[$key] : $this->postInstallConfigurators[$key]; + + return $this->cache[$key] = new $class($this->composer, $this->io, $this->options); + } +} diff --git a/vendor/symfony/flex/src/Configurator/AbstractConfigurator.php b/vendor/symfony/flex/src/Configurator/AbstractConfigurator.php new file mode 100644 index 0000000..aea4dda --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/AbstractConfigurator.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Symfony\Flex\Lock; +use Symfony\Flex\Options; +use Symfony\Flex\Path; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +abstract class AbstractConfigurator +{ + protected $composer; + protected $io; + protected $options; + protected $path; + + public function __construct(Composer $composer, IOInterface $io, Options $options) + { + $this->composer = $composer; + $this->io = $io; + $this->options = $options; + $this->path = new Path($options->get('root-dir')); + } + + abstract public function configure(Recipe $recipe, $config, Lock $lock, array $options = []); + + abstract public function unconfigure(Recipe $recipe, $config, Lock $lock); + + abstract public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void; + + protected function write($messages, $verbosity = IOInterface::VERBOSE) + { + if (!\is_array($messages)) { + $messages = [$messages]; + } + foreach ($messages as $i => $message) { + $messages[$i] = ' '.$message; + } + $this->io->writeError($messages, true, $verbosity); + } + + protected function isFileMarked(Recipe $recipe, string $file): bool + { + return is_file($file) && false !== strpos(file_get_contents($file), sprintf('###> %s ###', $recipe->getName())); + } + + protected function markData(Recipe $recipe, string $data): string + { + return "\n".sprintf('###> %s ###%s%s%s###< %s ###%s', $recipe->getName(), "\n", rtrim($data, "\r\n"), "\n", $recipe->getName(), "\n"); + } + + protected function isFileXmlMarked(Recipe $recipe, string $file): bool + { + return is_file($file) && false !== strpos(file_get_contents($file), sprintf('###+ %s ###', $recipe->getName())); + } + + protected function markXmlData(Recipe $recipe, string $data): string + { + return "\n".sprintf(' %s%s%s %s', $recipe->getName(), "\n", rtrim($data, "\r\n"), "\n", $recipe->getName(), "\n"); + } + + /** + * @return bool True if section was found and replaced + */ + protected function updateData(string $file, string $data): bool + { + if (!file_exists($file)) { + return false; + } + + $contents = file_get_contents($file); + + $newContents = $this->updateDataString($contents, $data); + if (null === $newContents) { + return false; + } + + file_put_contents($file, $newContents); + + return true; + } + + /** + * @return string|null returns the updated content if the section was found, null if not found + */ + protected function updateDataString(string $contents, string $data): ?string + { + $pieces = explode("\n", trim($data)); + $startMark = trim(reset($pieces)); + $endMark = trim(end($pieces)); + + if (false === strpos($contents, $startMark) || false === strpos($contents, $endMark)) { + return null; + } + + $pattern = '/'.preg_quote($startMark, '/').'.*?'.preg_quote($endMark, '/').'/s'; + + return preg_replace($pattern, trim($data), $contents); + } + + protected function extractSection(Recipe $recipe, string $contents): ?string + { + $section = $this->markData($recipe, '----'); + + $pieces = explode("\n", trim($section)); + $startMark = trim(reset($pieces)); + $endMark = trim(end($pieces)); + + $pattern = '/'.preg_quote($startMark, '/').'.*?'.preg_quote($endMark, '/').'/s'; + + $matches = []; + preg_match($pattern, $contents, $matches); + + return $matches[0] ?? null; + } +} diff --git a/vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php b/vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php new file mode 100644 index 0000000..146b28b --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php @@ -0,0 +1,270 @@ + + * @author Ryan Weaver + */ +class AddLinesConfigurator extends AbstractConfigurator +{ + private const POSITION_TOP = 'top'; + private const POSITION_BOTTOM = 'bottom'; + private const POSITION_AFTER_TARGET = 'after_target'; + + private const VALID_POSITIONS = [ + self::POSITION_TOP, + self::POSITION_BOTTOM, + self::POSITION_AFTER_TARGET, + ]; + + /** + * Holds file contents for files that have been loaded. + * This allows us to "change" the contents of a file multiple + * times before we actually write it out. + * + * @var string[] + */ + private $fileContents = []; + + public function configure(Recipe $recipe, $config, Lock $lock, array $options = []): void + { + $this->fileContents = []; + $this->executeConfigure($recipe, $config); + + foreach ($this->fileContents as $file => $contents) { + $this->write(sprintf('[add-lines] Patching file "%s"', $this->relativize($file))); + file_put_contents($file, $contents); + } + } + + public function unconfigure(Recipe $recipe, $config, Lock $lock): void + { + $this->fileContents = []; + $this->executeUnconfigure($recipe, $config); + + foreach ($this->fileContents as $file => $change) { + $this->write(sprintf('[add-lines] Reverting file "%s"', $this->relativize($file))); + file_put_contents($file, $change); + } + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + // manually check for "requires", as unconfigure ignores it + $originalConfig = array_filter($originalConfig, function ($item) { + return !isset($item['requires']) || $this->isPackageInstalled($item['requires']); + }); + + // reset the file content cache + $this->fileContents = []; + $this->executeUnconfigure($recipeUpdate->getOriginalRecipe(), $originalConfig); + $this->executeConfigure($recipeUpdate->getNewRecipe(), $newConfig); + $newFiles = []; + $originalFiles = []; + foreach ($this->fileContents as $file => $contents) { + // set the original file to the current contents + $originalFiles[$this->relativize($file)] = file_get_contents($file); + // and the new file where the old recipe was unconfigured, and the new configured + $newFiles[$this->relativize($file)] = $contents; + } + $recipeUpdate->addOriginalFiles($originalFiles); + $recipeUpdate->addNewFiles($newFiles); + } + + public function executeConfigure(Recipe $recipe, $config): void + { + foreach ($config as $patch) { + if (!isset($patch['file'])) { + $this->write(sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName())); + + continue; + } + + if (isset($patch['requires']) && !$this->isPackageInstalled($patch['requires'])) { + continue; + } + + if (!isset($patch['content'])) { + $this->write(sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName())); + + continue; + } + $content = $patch['content']; + + $file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]); + $warnIfMissing = isset($patch['warn_if_missing']) && $patch['warn_if_missing']; + if (!is_file($file)) { + $this->write([ + sprintf('Could not add lines to file %s as it does not exist. Missing lines:', $patch['file']), + '"""', + $content, + '"""', + '', + ], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE); + + continue; + } + + if (!isset($patch['position'])) { + $this->write(sprintf('The "position" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName())); + + continue; + } + $position = $patch['position']; + if (!\in_array($position, self::VALID_POSITIONS, true)) { + $this->write(sprintf('The "position" key must be one of "%s" for the "add-lines" configurator for recipe "%s". Skipping', implode('", "', self::VALID_POSITIONS), $recipe->getName())); + + continue; + } + + if (self::POSITION_AFTER_TARGET === $position && !isset($patch['target'])) { + $this->write(sprintf('The "target" key is required when "position" is "%s" for the "add-lines" configurator for recipe "%s". Skipping', self::POSITION_AFTER_TARGET, $recipe->getName())); + + continue; + } + $target = isset($patch['target']) ? $patch['target'] : null; + + $newContents = $this->getPatchedContents($file, $content, $position, $target, $warnIfMissing); + $this->fileContents[$file] = $newContents; + } + } + + public function executeUnconfigure(Recipe $recipe, $config): void + { + foreach ($config as $patch) { + if (!isset($patch['file'])) { + $this->write(sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName())); + + continue; + } + + // Ignore "requires": the target packages may have just become uninstalled. + // Checking for a "content" match is enough. + + $file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]); + if (!is_file($file)) { + continue; + } + + if (!isset($patch['content'])) { + $this->write(sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName())); + + continue; + } + $value = $patch['content']; + + $newContents = $this->getUnPatchedContents($file, $value); + $this->fileContents[$file] = $newContents; + } + } + + private function getPatchedContents(string $file, string $value, string $position, ?string $target, bool $warnIfMissing): string + { + $fileContents = $this->readFile($file); + + if (false !== strpos($fileContents, $value)) { + return $fileContents; // already includes value, skip + } + + switch ($position) { + case self::POSITION_BOTTOM: + $fileContents .= "\n".$value; + + break; + case self::POSITION_TOP: + $fileContents = $value."\n".$fileContents; + + break; + case self::POSITION_AFTER_TARGET: + $lines = explode("\n", $fileContents); + $targetFound = false; + foreach ($lines as $key => $line) { + if (false !== strpos($line, $target)) { + array_splice($lines, $key + 1, 0, $value); + $targetFound = true; + + break; + } + } + $fileContents = implode("\n", $lines); + + if (!$targetFound) { + $this->write([ + sprintf('Could not add lines after "%s" as no such string was found in "%s". Missing lines:', $target, $file), + '"""', + $value, + '"""', + '', + ], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE); + } + + break; + } + + return $fileContents; + } + + private function getUnPatchedContents(string $file, $value): string + { + $fileContents = $this->readFile($file); + + if (false === strpos($fileContents, $value)) { + return $fileContents; // value already gone! + } + + if (false !== strpos($fileContents, "\n".$value)) { + $value = "\n".$value; + } elseif (false !== strpos($fileContents, $value."\n")) { + $value = $value."\n"; + } + + $position = strpos($fileContents, $value); + + return substr_replace($fileContents, '', $position, \strlen($value)); + } + + private function isPackageInstalled($packages): bool + { + if (\is_string($packages)) { + $packages = [$packages]; + } + + $installedRepo = $this->composer->getRepositoryManager()->getLocalRepository(); + + foreach ($packages as $packageName) { + if (null === $installedRepo->findPackage($packageName, '*')) { + return false; + } + } + + return true; + } + + private function relativize(string $path): string + { + $rootDir = $this->options->get('root-dir'); + if (0 === strpos($path, $rootDir)) { + $path = substr($path, \strlen($rootDir) + 1); + } + + return ltrim($path, '/\\'); + } + + private function readFile(string $file): string + { + if (isset($this->fileContents[$file])) { + return $this->fileContents[$file]; + } + + $fileContents = file_get_contents($file); + $this->fileContents[$file] = $fileContents; + + return $fileContents; + } +} diff --git a/vendor/symfony/flex/src/Configurator/BundlesConfigurator.php b/vendor/symfony/flex/src/Configurator/BundlesConfigurator.php new file mode 100644 index 0000000..2aab5e7 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/BundlesConfigurator.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class BundlesConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $bundles, Lock $lock, array $options = []) + { + $this->write('Enabling the package as a Symfony bundle'); + $registered = $this->configureBundles($bundles); + $this->dump($this->getConfFile(), $registered); + } + + public function unconfigure(Recipe $recipe, $bundles, Lock $lock) + { + $this->write('Disabling the Symfony bundle'); + $file = $this->getConfFile(); + if (!file_exists($file)) { + return; + } + + $registered = $this->load($file); + foreach (array_keys($this->prepareBundles($bundles)) as $class) { + unset($registered[$class]); + } + $this->dump($file, $registered); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + $originalBundles = $this->configureBundles($originalConfig, true); + $recipeUpdate->setOriginalFile( + $this->getLocalConfFile(), + $this->buildContents($originalBundles) + ); + + $newBundles = $this->configureBundles($newConfig, true); + $recipeUpdate->setNewFile( + $this->getLocalConfFile(), + $this->buildContents($newBundles) + ); + } + + private function configureBundles(array $bundles, bool $resetEnvironments = false): array + { + $file = $this->getConfFile(); + $registered = $this->load($file); + $classes = $this->prepareBundles($bundles); + if (isset($classes[$fwb = 'Symfony\Bundle\FrameworkBundle\FrameworkBundle'])) { + foreach ($classes[$fwb] as $env) { + $registered[$fwb][$env] = true; + } + unset($classes[$fwb]); + } + foreach ($classes as $class => $envs) { + // do not override existing configured envs for a bundle + if (!isset($registered[$class]) || $resetEnvironments) { + if ($resetEnvironments) { + // used during calculating an "upgrade" + // here, we want to "undo" the bundle's configuration entirely + // then re-add it fresh, in case some environments have been + // removed in an updated version of the recipe + $registered[$class] = []; + } + + foreach ($envs as $env) { + $registered[$class][$env] = true; + } + } + } + + return $registered; + } + + private function prepareBundles(array $bundles): array + { + foreach ($bundles as $class => $envs) { + $bundles[ltrim($class, '\\')] = $envs; + } + + return $bundles; + } + + private function load(string $file): array + { + $bundles = file_exists($file) ? (require $file) : []; + if (!\is_array($bundles)) { + $bundles = []; + } + + return $bundles; + } + + private function dump(string $file, array $bundles) + { + $contents = $this->buildContents($bundles); + + if (!is_dir(\dirname($file))) { + mkdir(\dirname($file), 0777, true); + } + + file_put_contents($file, $contents); + + if (\function_exists('opcache_invalidate')) { + opcache_invalidate($file); + } + } + + private function buildContents(array $bundles): string + { + $contents = " $envs) { + $contents .= " $class::class => ["; + foreach ($envs as $env => $value) { + $booleanValue = var_export($value, true); + $contents .= "'$env' => $booleanValue, "; + } + $contents = substr($contents, 0, -2)."],\n"; + } + $contents .= "];\n"; + + return $contents; + } + + private function getConfFile(): string + { + return $this->options->get('root-dir').'/'.$this->getLocalConfFile(); + } + + private function getLocalConfFile(): string + { + return $this->options->expandTargetDir('%CONFIG_DIR%/bundles.php'); + } +} diff --git a/vendor/symfony/flex/src/Configurator/ComposerScriptsConfigurator.php b/vendor/symfony/flex/src/Configurator/ComposerScriptsConfigurator.php new file mode 100644 index 0000000..abdcefc --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/ComposerScriptsConfigurator.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Composer\Factory; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class ComposerScriptsConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $scripts, Lock $lock, array $options = []) + { + $json = new JsonFile(Factory::getComposerFile()); + + file_put_contents($json->getPath(), $this->configureScripts($scripts, $json)); + } + + public function unconfigure(Recipe $recipe, $scripts, Lock $lock) + { + $json = new JsonFile(Factory::getComposerFile()); + + $jsonContents = $json->read(); + $autoScripts = $jsonContents['scripts']['auto-scripts'] ?? []; + foreach (array_keys($scripts) as $cmd) { + unset($autoScripts[$cmd]); + } + + $manipulator = new JsonManipulator(file_get_contents($json->getPath())); + $manipulator->addSubNode('scripts', 'auto-scripts', $autoScripts); + + file_put_contents($json->getPath(), $manipulator->getContents()); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + $json = new JsonFile(Factory::getComposerFile()); + $jsonPath = ltrim(str_replace($recipeUpdate->getRootDir(), '', $json->getPath()), '/\\'); + + $recipeUpdate->setOriginalFile( + $jsonPath, + $this->configureScripts($originalConfig, $json) + ); + $recipeUpdate->setNewFile( + $jsonPath, + $this->configureScripts($newConfig, $json) + ); + } + + private function configureScripts(array $scripts, JsonFile $json): string + { + $jsonContents = $json->read(); + $autoScripts = $jsonContents['scripts']['auto-scripts'] ?? []; + $autoScripts = array_merge($autoScripts, $scripts); + + $manipulator = new JsonManipulator(file_get_contents($json->getPath())); + $manipulator->addSubNode('scripts', 'auto-scripts', $autoScripts); + + return $manipulator->getContents(); + } +} diff --git a/vendor/symfony/flex/src/Configurator/ContainerConfigurator.php b/vendor/symfony/flex/src/Configurator/ContainerConfigurator.php new file mode 100644 index 0000000..e501a09 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/ContainerConfigurator.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class ContainerConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $parameters, Lock $lock, array $options = []) + { + $this->write('Setting parameters'); + $contents = $this->configureParameters($parameters); + + if (null !== $contents) { + file_put_contents($this->options->get('root-dir').'/'.$this->getServicesPath(), $contents); + } + } + + public function unconfigure(Recipe $recipe, $parameters, Lock $lock) + { + $this->write('Unsetting parameters'); + $target = $this->options->get('root-dir').'/'.$this->getServicesPath(); + $lines = $this->removeParametersFromLines(file($target), $parameters); + file_put_contents($target, implode('', $lines)); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + $recipeUpdate->setOriginalFile( + $this->getServicesPath(), + $this->configureParameters($originalConfig, true) + ); + + // for the new file, we need to update any values *and* remove any removed values + $removedParameters = []; + foreach ($originalConfig as $name => $value) { + if (!isset($newConfig[$name])) { + $removedParameters[$name] = $value; + } + } + + $updatedFile = $this->configureParameters($newConfig, true); + $lines = $this->removeParametersFromLines(explode("\n", $updatedFile), $removedParameters); + + $recipeUpdate->setNewFile( + $this->getServicesPath(), + implode("\n", $lines) + ); + } + + private function configureParameters(array $parameters, bool $update = false): string + { + $target = $this->options->get('root-dir').'/'.$this->getServicesPath(); + $endAt = 0; + $isParameters = false; + $lines = []; + foreach (file($target) as $i => $line) { + $lines[] = $line; + if (!$isParameters && !preg_match('/^parameters:/', $line)) { + continue; + } + if (!$isParameters) { + $isParameters = true; + continue; + } + if (!preg_match('/^\s+.*/', $line) && '' !== trim($line)) { + $endAt = $i - 1; + $isParameters = false; + continue; + } + foreach ($parameters as $key => $value) { + $matches = []; + if (preg_match(sprintf('/^\s+%s\:/', preg_quote($key, '/')), $line, $matches)) { + if ($update) { + $lines[$i] = substr($line, 0, \strlen($matches[0])).' '.str_replace("'", "''", $value)."\n"; + } + + unset($parameters[$key]); + } + } + } + + if ($parameters) { + $parametersLines = []; + if (!$endAt) { + $parametersLines[] = "parameters:\n"; + } + foreach ($parameters as $key => $value) { + if (\is_array($value)) { + $parametersLines[] = sprintf(" %s:\n%s", $key, $this->dumpYaml(2, $value)); + continue; + } + $parametersLines[] = sprintf(" %s: '%s'%s", $key, str_replace("'", "''", $value), "\n"); + } + if (!$endAt) { + $parametersLines[] = "\n"; + } + array_splice($lines, $endAt, 0, $parametersLines); + } + + return implode('', $lines); + } + + private function removeParametersFromLines(array $sourceLines, array $parameters): array + { + $lines = []; + foreach ($sourceLines as $line) { + if ($this->removeParameters(1, $parameters, $line)) { + continue; + } + $lines[] = $line; + } + + return $lines; + } + + private function removeParameters($level, $params, $line) + { + foreach ($params as $key => $value) { + if (\is_array($value) && $this->removeParameters($level + 1, $value, $line)) { + return true; + } + if (preg_match(sprintf('/^(\s{%d}|\t{%d})+%s\:/', 4 * $level, $level, preg_quote($key, '/')), $line)) { + return true; + } + } + + return false; + } + + private function dumpYaml($level, $array): string + { + $line = ''; + foreach ($array as $key => $value) { + $line .= str_repeat(' ', $level); + if (!\is_array($value)) { + $line .= sprintf("%s: '%s'\n", $key, str_replace("'", "''", $value)); + continue; + } + $line .= sprintf("%s:\n", $key).$this->dumpYaml($level + 1, $value); + } + + return $line; + } + + private function getServicesPath(): string + { + return $this->options->expandTargetDir('%CONFIG_DIR%/services.yaml'); + } +} diff --git a/vendor/symfony/flex/src/Configurator/CopyFromPackageConfigurator.php b/vendor/symfony/flex/src/Configurator/CopyFromPackageConfigurator.php new file mode 100644 index 0000000..9d28fa2 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/CopyFromPackageConfigurator.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class CopyFromPackageConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $config, Lock $lock, array $options = []) + { + $this->write('Copying files from package'); + $packageDir = $this->composer->getInstallationManager()->getInstallPath($recipe->getPackage()); + $options = array_merge($this->options->toArray(), $options); + + $files = $this->getFilesToCopy($config, $packageDir); + foreach ($files as $source => $target) { + $this->copyFile($source, $target, $options); + } + } + + public function unconfigure(Recipe $recipe, $config, Lock $lock) + { + $this->write('Removing files from package'); + $packageDir = $this->composer->getInstallationManager()->getInstallPath($recipe->getPackage()); + $this->removeFiles($config, $packageDir, $this->options->get('root-dir')); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + $packageDir = $this->composer->getInstallationManager()->getInstallPath($recipeUpdate->getNewRecipe()->getPackage()); + foreach ($originalConfig as $source => $target) { + if (isset($newConfig[$source])) { + // path is in both, we cannot update + $recipeUpdate->addCopyFromPackagePath( + $packageDir.'/'.$source, + $this->options->expandTargetDir($target) + ); + + unset($newConfig[$source]); + } + + // if any paths were removed from the recipe, we'll keep them + } + + // any remaining files are new, and we can copy them + foreach ($this->getFilesToCopy($newConfig, $packageDir) as $source => $target) { + if (!file_exists($source)) { + throw new \LogicException(sprintf('File "%s" does not exist!', $source)); + } + + $recipeUpdate->setNewFile($target, file_get_contents($source)); + } + } + + private function getFilesToCopy(array $manifest, string $from): array + { + $files = []; + foreach ($manifest as $source => $target) { + $target = $this->options->expandTargetDir($target); + if ('/' === substr($source, -1)) { + $files = array_merge($files, $this->getFilesForDir($this->path->concatenate([$from, $source]), $this->path->concatenate([$target]))); + + continue; + } + + $files[$this->path->concatenate([$from, $source])] = $target; + } + + return $files; + } + + private function removeFiles(array $manifest, string $from, string $to) + { + foreach ($manifest as $source => $target) { + $target = $this->options->expandTargetDir($target); + if ('/' === substr($source, -1)) { + $this->removeFilesFromDir($this->path->concatenate([$from, $source]), $this->path->concatenate([$to, $target])); + } else { + $targetPath = $this->path->concatenate([$to, $target]); + if (file_exists($targetPath)) { + @unlink($targetPath); + $this->write(sprintf(' Removed "%s"', $this->path->relativize($targetPath))); + } + } + } + } + + private function getFilesForDir(string $source, string $target): array + { + $iterator = $this->createSourceIterator($source, \RecursiveIteratorIterator::SELF_FIRST); + $files = []; + foreach ($iterator as $item) { + $targetPath = $this->path->concatenate([$target, $iterator->getSubPathName()]); + + $files[(string) $item] = $targetPath; + } + + return $files; + } + + /** + * @param string $source The absolute path to the source file + * @param string $target The relative (to root dir) path to the target + */ + public function copyFile(string $source, string $target, array $options) + { + $target = $this->options->get('root-dir').'/'.$target; + if (is_dir($source)) { + // directory will be created when a file is copied to it + return; + } + + $overwrite = $options['force'] ?? false; + if (!$this->options->shouldWriteFile($target, $overwrite)) { + return; + } + + if (!file_exists($source)) { + throw new \LogicException(sprintf('File "%s" does not exist!', $source)); + } + + if (!file_exists(\dirname($target))) { + mkdir(\dirname($target), 0777, true); + $this->write(sprintf(' Created "%s"', $this->path->relativize(\dirname($target)))); + } + + file_put_contents($target, $this->options->expandTargetDir(file_get_contents($source))); + @chmod($target, fileperms($target) | (fileperms($source) & 0111)); + $this->write(sprintf(' Created "%s"', $this->path->relativize($target))); + } + + private function removeFilesFromDir(string $source, string $target) + { + if (!is_dir($source)) { + return; + } + $iterator = $this->createSourceIterator($source, \RecursiveIteratorIterator::CHILD_FIRST); + foreach ($iterator as $item) { + $targetPath = $this->path->concatenate([$target, $iterator->getSubPathName()]); + if ($item->isDir()) { + // that removes the dir only if it is empty + @rmdir($targetPath); + $this->write(sprintf(' Removed directory "%s"', $this->path->relativize($targetPath))); + } else { + @unlink($targetPath); + $this->write(sprintf(' Removed "%s"', $this->path->relativize($targetPath))); + } + } + } + + private function createSourceIterator(string $source, int $mode): \RecursiveIteratorIterator + { + return new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), $mode); + } +} diff --git a/vendor/symfony/flex/src/Configurator/CopyFromRecipeConfigurator.php b/vendor/symfony/flex/src/Configurator/CopyFromRecipeConfigurator.php new file mode 100644 index 0000000..b423311 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/CopyFromRecipeConfigurator.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class CopyFromRecipeConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $config, Lock $lock, array $options = []) + { + $this->write('Copying files from recipe'); + $options = array_merge($this->options->toArray(), $options); + + $lock->add($recipe->getName(), ['files' => $this->copyFiles($config, $recipe->getFiles(), $options)]); + } + + public function unconfigure(Recipe $recipe, $config, Lock $lock) + { + $this->write('Removing files from recipe'); + $this->removeFiles($config, $this->getRemovableFilesFromRecipeAndLock($recipe, $lock), $this->options->get('root-dir')); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + foreach ($recipeUpdate->getOriginalRecipe()->getFiles() as $filename => $data) { + $recipeUpdate->setOriginalFile($filename, $data['contents']); + } + + $files = []; + foreach ($recipeUpdate->getNewRecipe()->getFiles() as $filename => $data) { + $recipeUpdate->setNewFile($filename, $data['contents']); + + $files[] = $this->getLocalFilePath($recipeUpdate->getRootDir(), $filename); + } + $recipeUpdate->getLock()->add($recipeUpdate->getPackageName(), ['files' => $files]); + } + + private function getRemovableFilesFromRecipeAndLock(Recipe $recipe, Lock $lock): array + { + $lockedFiles = array_unique( + array_reduce( + array_column($lock->all(), 'files'), + function (array $carry, array $package) { + return array_merge($carry, $package); + }, + [] + ) + ); + + $removableFiles = $recipe->getFiles(); + + $lockedFiles = array_map('realpath', $lockedFiles); + + // Compare file paths by their real path to abstract OS differences + foreach (array_keys($removableFiles) as $file) { + if (\in_array(realpath($file), $lockedFiles)) { + unset($removableFiles[$file]); + } + } + + return $removableFiles; + } + + private function copyFiles(array $manifest, array $files, array $options): array + { + $copiedFiles = []; + $to = $options['root-dir'] ?? '.'; + + foreach ($manifest as $source => $target) { + $target = $this->options->expandTargetDir($target); + if ('/' === substr($source, -1)) { + $copiedFiles = array_merge( + $copiedFiles, + $this->copyDir($source, $this->path->concatenate([$to, $target]), $files, $options) + ); + } else { + $copiedFiles[] = $this->copyFile($this->path->concatenate([$to, $target]), $files[$source]['contents'], $files[$source]['executable'], $options); + } + } + + return $copiedFiles; + } + + private function copyDir(string $source, string $target, array $files, array $options): array + { + $copiedFiles = []; + foreach ($files as $file => $data) { + if (0 === strpos($file, $source)) { + $file = $this->path->concatenate([$target, substr($file, \strlen($source))]); + $copiedFiles[] = $this->copyFile($file, $data['contents'], $data['executable'], $options); + } + } + + return $copiedFiles; + } + + private function copyFile(string $to, string $contents, bool $executable, array $options): string + { + $overwrite = $options['force'] ?? false; + $basePath = $options['root-dir'] ?? '.'; + $copiedFile = $this->getLocalFilePath($basePath, $to); + + if (!$this->options->shouldWriteFile($to, $overwrite)) { + return $copiedFile; + } + + if (!is_dir(\dirname($to))) { + mkdir(\dirname($to), 0777, true); + } + + file_put_contents($to, $this->options->expandTargetDir($contents)); + if ($executable) { + @chmod($to, fileperms($to) | 0111); + } + + $this->write(sprintf(' Created "%s"', $this->path->relativize($to))); + + return $copiedFile; + } + + private function removeFiles(array $manifest, array $files, string $to) + { + foreach ($manifest as $source => $target) { + $target = $this->options->expandTargetDir($target); + + if ('.git' === $target) { + // never remove the main Git directory, even if it was created by a recipe + continue; + } + + if ('/' === substr($source, -1)) { + foreach (array_keys($files) as $file) { + if (0 === strpos($file, $source)) { + $this->removeFile($this->path->concatenate([$to, $target, substr($file, \strlen($source))])); + } + } + } else { + $this->removeFile($this->path->concatenate([$to, $target])); + } + } + } + + private function removeFile(string $to) + { + if (!file_exists($to)) { + return; + } + + @unlink($to); + $this->write(sprintf(' Removed "%s"', $this->path->relativize($to))); + + if (0 === \count(glob(\dirname($to).'/*', \GLOB_NOSORT))) { + @rmdir(\dirname($to)); + } + } + + private function getLocalFilePath(string $basePath, $destination): string + { + return str_replace($basePath.\DIRECTORY_SEPARATOR, '', $destination); + } +} diff --git a/vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php b/vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php new file mode 100644 index 0000000..42bdda9 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php @@ -0,0 +1,404 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Composer\Composer; +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Flex\Lock; +use Symfony\Flex\Options; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * Adds services and volumes to compose.yaml file. + * + * @author Kévin Dunglas + */ +class DockerComposeConfigurator extends AbstractConfigurator +{ + private $filesystem; + + public static $configureDockerRecipes = null; + + public function __construct(Composer $composer, IOInterface $io, Options $options) + { + parent::__construct($composer, $io, $options); + + $this->filesystem = new Filesystem(); + } + + public function configure(Recipe $recipe, $config, Lock $lock, array $options = []) + { + if (!self::shouldConfigureDockerRecipe($this->composer, $this->io, $recipe)) { + return; + } + + $this->configureDockerCompose($recipe, $config, $options['force'] ?? false); + + $this->write('Docker Compose definitions have been modified. Please run "docker compose up --build" again to apply the changes.'); + } + + public function unconfigure(Recipe $recipe, $config, Lock $lock) + { + $rootDir = $this->options->get('root-dir'); + foreach ($this->normalizeConfig($config) as $file => $extra) { + if (null === $dockerComposeFile = $this->findDockerComposeFile($rootDir, $file)) { + continue; + } + + $name = $recipe->getName(); + // Remove recipe and add break line + $contents = preg_replace(sprintf('{%s+###> %s ###.*?###< %s ###%s+}s', "\n", $name, $name, "\n"), \PHP_EOL.\PHP_EOL, file_get_contents($dockerComposeFile), -1, $count); + if (!$count) { + return; + } + + foreach ($extra as $key => $value) { + if (0 === preg_match(sprintf('{^%s:[ \t\r\n]*([ \t]+\w|#)}m', $key), $contents, $matches)) { + $contents = preg_replace(sprintf('{\n?^%s:[ \t\r\n]*}sm', $key), '', $contents, -1, $count); + } + } + + $this->write(sprintf('Removing Docker Compose entries from "%s"', $dockerComposeFile)); + file_put_contents($dockerComposeFile, ltrim($contents, "\n")); + } + + $this->write('Docker Compose definitions have been modified. Please run "docker compose up" again to apply the changes.'); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + if (!self::shouldConfigureDockerRecipe($this->composer, $this->io, $recipeUpdate->getNewRecipe())) { + return; + } + + $recipeUpdate->addOriginalFiles( + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig) + ); + + $recipeUpdate->addNewFiles( + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig) + ); + } + + public static function shouldConfigureDockerRecipe(Composer $composer, IOInterface $io, Recipe $recipe): bool + { + if (null !== self::$configureDockerRecipes) { + return self::$configureDockerRecipes; + } + + if (null !== $dockerPreference = $composer->getPackage()->getExtra()['symfony']['docker'] ?? null) { + self::$configureDockerRecipes = $dockerPreference; + + return self::$configureDockerRecipes; + } + + if ('install' !== $recipe->getJob()) { + // default to not configuring + return false; + } + + if (!isset($_SERVER['SYMFONY_DOCKER'])) { + $answer = self::askDockerSupport($io, $recipe); + } elseif (filter_var($_SERVER['SYMFONY_DOCKER'], \FILTER_VALIDATE_BOOLEAN)) { + $answer = 'p'; + } else { + $answer = 'x'; + } + + if ('n' === $answer) { + self::$configureDockerRecipes = false; + + return self::$configureDockerRecipes; + } + if ('y' === $answer) { + self::$configureDockerRecipes = true; + + return self::$configureDockerRecipes; + } + + // yes or no permanently + self::$configureDockerRecipes = 'p' === $answer; + $json = new JsonFile(Factory::getComposerFile()); + $manipulator = new JsonManipulator(file_get_contents($json->getPath())); + $manipulator->addSubNode('extra', 'symfony.docker', self::$configureDockerRecipes); + file_put_contents($json->getPath(), $manipulator->getContents()); + + return self::$configureDockerRecipes; + } + + /** + * Normalizes the config and return the name of the main Docker Compose file if applicable. + */ + private function normalizeConfig(array $config): array + { + foreach ($config as $key => $val) { + // Support for the short recipe syntax that modifies compose.yaml only + if (isset($val[0])) { + return ['compose.yaml' => $config]; + } + + if (!str_starts_with($key, 'docker-')) { + continue; + } + + // If the recipe still use the legacy "docker-compose.yml" names, remove the "docker-" prefix and change the extension + $newKey = pathinfo(substr($key, 7), \PATHINFO_FILENAME).'.yaml'; + $config[$newKey] = $val; + unset($config[$key]); + } + + return $config; + } + + /** + * Finds the Docker Compose file according to these rules: https://docs.docker.com/compose/reference/envvars/#compose_file. + */ + private function findDockerComposeFile(string $rootDir, string $file): ?string + { + if (isset($_SERVER['COMPOSE_FILE'])) { + $filenameToFind = pathinfo($file, \PATHINFO_FILENAME); + $separator = $_SERVER['COMPOSE_PATH_SEPARATOR'] ?? ('\\' === \DIRECTORY_SEPARATOR ? ';' : ':'); + + $files = explode($separator, $_SERVER['COMPOSE_FILE']); + foreach ($files as $f) { + $filename = pathinfo($f, \PATHINFO_FILENAME); + if ($filename !== $filenameToFind && "docker-$filenameToFind" !== $filename) { + continue; + } + + if (!$this->filesystem->isAbsolutePath($f)) { + $f = realpath(sprintf('%s/%s', $rootDir, $f)); + } + + if ($this->filesystem->exists($f)) { + return $f; + } + } + } + + // COMPOSE_FILE not set, or doesn't contain the file we're looking for + $dir = $rootDir; + do { + if ( + $this->filesystem->exists($dockerComposeFile = sprintf('%s/%s', $dir, $file)) || + // Test with the ".yml" extension if the file doesn't end up with ".yaml" + $this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml') || + // Test with the legacy "docker-" suffix if "compose.ya?ml" doesn't exist + $this->filesystem->exists($dockerComposeFile = sprintf('%s/docker-%s', $dir, $file)) || + $this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml') + ) { + return $dockerComposeFile; + } + + $previousDir = $dir; + $dir = \dirname($dir); + } while ($dir !== $previousDir); + + return null; + } + + private function parse($level, $indent, $services): string + { + $line = ''; + foreach ($services as $key => $value) { + $line .= str_repeat(' ', $indent * $level); + if (!\is_array($value)) { + if (\is_string($key)) { + $line .= sprintf('%s:', $key); + } + $line .= sprintf("%s\n", $value); + continue; + } + $line .= sprintf("%s:\n", $key).$this->parse($level + 1, $indent, $value); + } + + return $line; + } + + private function configureDockerCompose(Recipe $recipe, array $config, bool $update): void + { + $rootDir = $this->options->get('root-dir'); + foreach ($this->normalizeConfig($config) as $file => $extra) { + $dockerComposeFile = $this->findDockerComposeFile($rootDir, $file); + if (null === $dockerComposeFile) { + $dockerComposeFile = $rootDir.'/'.$file; + file_put_contents($dockerComposeFile, ''); + $this->write(sprintf(' Created "%s"', $file)); + } + + if (!$update && $this->isFileMarked($recipe, $dockerComposeFile)) { + continue; + } + + $this->write(sprintf('Adding Docker Compose definitions to "%s"', $dockerComposeFile)); + + $offset = 2; + $node = null; + $endAt = []; + $startAt = []; + $lines = []; + $nodesLines = []; + foreach (file($dockerComposeFile) as $i => $line) { + $lines[] = $line; + $ltrimedLine = ltrim($line, ' '); + if (null !== $node) { + $nodesLines[$node][$i] = $line; + } + + // Skip blank lines and comments + if (('' !== $ltrimedLine && 0 === strpos($ltrimedLine, '#')) || '' === trim($line)) { + continue; + } + + // Extract Docker Compose keys (usually "services" and "volumes") + if (!preg_match('/^[\'"]?([a-zA-Z0-9]+)[\'"]?:\s*$/', $line, $matches)) { + // Detect indentation to use + $offestLine = \strlen($line) - \strlen($ltrimedLine); + if ($offset > $offestLine && 0 !== $offestLine) { + $offset = $offestLine; + } + continue; + } + + // Keep end in memory (check break line on previous line) + $endAt[$node] = !$i || '' !== trim($lines[$i - 1]) ? $i : $i - 1; + $node = $matches[1]; + if (!isset($nodesLines[$node])) { + $nodesLines[$node] = []; + } + if (!isset($startAt[$node])) { + // the section contents starts at the next line + $startAt[$node] = $i + 1; + } + } + $endAt[$node] = \count($lines) + 1; + + foreach ($extra as $key => $value) { + if (isset($endAt[$key])) { + $data = $this->markData($recipe, $this->parse(1, $offset, $value)); + $updatedContents = $this->updateDataString(implode('', $nodesLines[$key]), $data); + if (null === $updatedContents) { + // not an update: just add to section + array_splice($lines, $endAt[$key], 0, $data); + + continue; + } + + $originalEndAt = $endAt[$key]; + $length = $endAt[$key] - $startAt[$key]; + array_splice($lines, $startAt[$key], $length, ltrim($updatedContents, "\n")); + + // reset any start/end positions after this to the new positions + foreach ($startAt as $sectionKey => $at) { + if ($at > $originalEndAt) { + $startAt[$sectionKey] = $at - $length - 1; + } + } + foreach ($endAt as $sectionKey => $at) { + if ($at > $originalEndAt) { + $endAt[$sectionKey] = $at - $length; + } + } + + continue; + } + + $lines[] = sprintf("\n%s:", $key); + $lines[] = $this->markData($recipe, $this->parse(1, $offset, $value)); + } + + file_put_contents($dockerComposeFile, implode('', $lines)); + } + } + + private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $config): array + { + if (0 === \count($config)) { + return []; + } + + $files = array_filter(array_map(function ($file) use ($rootDir) { + return $this->findDockerComposeFile($rootDir, $file); + }, array_keys($config))); + + $originalContents = []; + foreach ($files as $file) { + $originalContents[$file] = file_exists($file) ? file_get_contents($file) : null; + } + + $this->configureDockerCompose( + $recipe, + $config, + true + ); + + $updatedContents = []; + foreach ($files as $file) { + $localPath = $file; + if (0 === strpos($file, $rootDir)) { + $localPath = substr($file, \strlen($rootDir) + 1); + } + $localPath = ltrim($localPath, '/\\'); + $updatedContents[$localPath] = file_exists($file) ? file_get_contents($file) : null; + } + + foreach ($originalContents as $file => $contents) { + if (null === $contents) { + if (file_exists($file)) { + unlink($file); + } + } else { + file_put_contents($file, $contents); + } + } + + return $updatedContents; + } + + private static function askDockerSupport(IOInterface $io, Recipe $recipe): string + { + $warning = $io->isInteractive() ? 'WARNING' : 'IGNORING'; + $io->writeError(sprintf(' - %s %s', $warning, $recipe->getFormattedOrigin())); + $question = ' The recipe for this package contains some Docker configuration. + + This may create/update compose.yaml or update Dockerfile (if it exists). + + Do you want to include Docker configuration from recipes? + [y] Yes + [n] No + [p] Yes permanently, never ask again for this project + [x] No permanently, never ask again for this project + (defaults to y): '; + + return $io->askAndValidate( + $question, + function ($value) { + if (null === $value) { + return 'y'; + } + $value = strtolower($value[0]); + if (!\in_array($value, ['y', 'n', 'p', 'x'], true)) { + throw new \InvalidArgumentException('Invalid choice.'); + } + + return $value; + }, + null, + 'y' + ); + } +} diff --git a/vendor/symfony/flex/src/Configurator/DockerfileConfigurator.php b/vendor/symfony/flex/src/Configurator/DockerfileConfigurator.php new file mode 100644 index 0000000..423cf9c --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/DockerfileConfigurator.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * Adds commands to a Dockerfile. + * + * @author Kévin Dunglas + */ +class DockerfileConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $config, Lock $lock, array $options = []) + { + if (!DockerComposeConfigurator::shouldConfigureDockerRecipe($this->composer, $this->io, $recipe)) { + return; + } + + $this->configureDockerfile($recipe, $config, $options['force'] ?? false); + } + + public function unconfigure(Recipe $recipe, $config, Lock $lock) + { + if (!file_exists($dockerfile = $this->options->get('root-dir').'/Dockerfile')) { + return; + } + + $name = $recipe->getName(); + $contents = preg_replace(sprintf('{%s+###> %s ###.*?###< %s ###%s+}s', "\n", $name, $name, "\n"), "\n", file_get_contents($dockerfile), -1, $count); + if (!$count) { + return; + } + + $this->write('Removing Dockerfile entries'); + file_put_contents($dockerfile, ltrim($contents, "\n")); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + if (!DockerComposeConfigurator::shouldConfigureDockerRecipe($this->composer, $this->io, $recipeUpdate->getNewRecipe())) { + return; + } + + $recipeUpdate->setOriginalFile( + 'Dockerfile', + $this->getContentsAfterApplyingRecipe($recipeUpdate->getOriginalRecipe(), $originalConfig) + ); + + $recipeUpdate->setNewFile( + 'Dockerfile', + $this->getContentsAfterApplyingRecipe($recipeUpdate->getNewRecipe(), $newConfig) + ); + } + + private function configureDockerfile(Recipe $recipe, array $config, bool $update, bool $writeOutput = true): void + { + $dockerfile = $this->options->get('root-dir').'/Dockerfile'; + if (!file_exists($dockerfile) || (!$update && $this->isFileMarked($recipe, $dockerfile))) { + return; + } + + if ($writeOutput) { + $this->write('Adding Dockerfile entries'); + } + + $data = ltrim($this->markData($recipe, implode("\n", $config)), "\n"); + if ($this->updateData($dockerfile, $data)) { + // done! Existing spot updated + return; + } + + $lines = []; + foreach (file($dockerfile) as $line) { + $lines[] = $line; + if (!preg_match('/^###> recipes ###$/', $line)) { + continue; + } + + $lines[] = $data; + } + + file_put_contents($dockerfile, implode('', $lines)); + } + + private function getContentsAfterApplyingRecipe(Recipe $recipe, array $config): ?string + { + if (0 === \count($config)) { + return null; + } + + $dockerfile = $this->options->get('root-dir').'/Dockerfile'; + $originalContents = file_exists($dockerfile) ? file_get_contents($dockerfile) : null; + + $this->configureDockerfile( + $recipe, + $config, + true, + false + ); + + $updatedContents = file_exists($dockerfile) ? file_get_contents($dockerfile) : null; + + if (null === $originalContents) { + if (file_exists($dockerfile)) { + unlink($dockerfile); + } + } else { + file_put_contents($dockerfile, $originalContents); + } + + return $updatedContents; + } +} diff --git a/vendor/symfony/flex/src/Configurator/EnvConfigurator.php b/vendor/symfony/flex/src/Configurator/EnvConfigurator.php new file mode 100644 index 0000000..97c62d6 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/EnvConfigurator.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class EnvConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $vars, Lock $lock, array $options = []) + { + $this->write('Adding environment variable defaults'); + + $this->configureEnvDist($recipe, $vars, $options['force'] ?? false); + if (!file_exists($this->options->get('root-dir').'/'.($this->options->get('runtime')['dotenv_path'] ?? '.env').'.test')) { + $this->configurePhpUnit($recipe, $vars, $options['force'] ?? false); + } + } + + public function unconfigure(Recipe $recipe, $vars, Lock $lock) + { + $this->unconfigureEnvFiles($recipe, $vars); + $this->unconfigurePhpUnit($recipe, $vars); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + $recipeUpdate->addOriginalFiles( + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig) + ); + + $recipeUpdate->addNewFiles( + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig) + ); + } + + private function configureEnvDist(Recipe $recipe, $vars, bool $update) + { + $dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env'; + + foreach ([$dotenvPath.'.dist', $dotenvPath] as $file) { + $env = $this->options->get('root-dir').'/'.$file; + if (!is_file($env)) { + continue; + } + + if (!$update && $this->isFileMarked($recipe, $env)) { + continue; + } + + $data = ''; + foreach ($vars as $key => $value) { + $existingValue = $update ? $this->findExistingValue($key, $env, $recipe) : null; + $value = $this->evaluateValue($value, $existingValue); + if ('#' === $key[0] && is_numeric(substr($key, 1))) { + if ('' === $value) { + $data .= "#\n"; + } else { + $data .= '# '.$value."\n"; + } + + continue; + } + + $value = $this->options->expandTargetDir($value); + if (false !== strpbrk($value, " \t\n&!\"")) { + $value = '"'.str_replace(['\\', '"', "\t", "\n"], ['\\\\', '\\"', '\t', '\n'], $value).'"'; + } + $data .= "$key=$value\n"; + } + $data = $this->markData($recipe, $data); + + if (!$this->updateData($env, $data)) { + file_put_contents($env, $data, \FILE_APPEND); + } + } + } + + private function configurePhpUnit(Recipe $recipe, $vars, bool $update) + { + foreach (['phpunit.xml.dist', 'phpunit.xml'] as $file) { + $phpunit = $this->options->get('root-dir').'/'.$file; + if (!is_file($phpunit)) { + continue; + } + + if (!$update && $this->isFileXmlMarked($recipe, $phpunit)) { + continue; + } + + $data = ''; + foreach ($vars as $key => $value) { + $value = $this->evaluateValue($value); + if ('#' === $key[0]) { + if (is_numeric(substr($key, 1))) { + $doc = new \DOMDocument(); + $data .= ' '.$doc->saveXML($doc->createComment(' '.$value.' '))."\n"; + } else { + $value = $this->options->expandTargetDir($value); + $doc = new \DOMDocument(); + $fragment = $doc->createElement('env'); + $fragment->setAttribute('name', substr($key, 1)); + $fragment->setAttribute('value', $value); + $data .= ' '.str_replace(['<', '/>'], [''], $doc->saveXML($fragment))."\n"; + } + } else { + $value = $this->options->expandTargetDir($value); + $doc = new \DOMDocument(); + $fragment = $doc->createElement('env'); + $fragment->setAttribute('name', $key); + $fragment->setAttribute('value', $value); + $data .= ' '.$doc->saveXML($fragment)."\n"; + } + } + $data = $this->markXmlData($recipe, $data); + + if (!$this->updateData($phpunit, $data)) { + file_put_contents($phpunit, preg_replace('{^(\s+)}m', $data.'$1', file_get_contents($phpunit))); + } + } + } + + private function unconfigureEnvFiles(Recipe $recipe, $vars) + { + $dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env'; + + foreach ([$dotenvPath, $dotenvPath.'.dist'] as $file) { + $env = $this->options->get('root-dir').'/'.$file; + if (!file_exists($env)) { + continue; + } + + $contents = preg_replace(sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($env), -1, $count); + if (!$count) { + continue; + } + + $this->write(sprintf('Removing environment variables from %s', $file)); + file_put_contents($env, $contents); + } + } + + private function unconfigurePhpUnit(Recipe $recipe, $vars) + { + foreach (['phpunit.xml.dist', 'phpunit.xml'] as $file) { + $phpunit = $this->options->get('root-dir').'/'.$file; + if (!is_file($phpunit)) { + continue; + } + + $contents = preg_replace(sprintf('{%s*\s+.*%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($phpunit), -1, $count); + if (!$count) { + continue; + } + + $this->write(sprintf('Removing environment variables from %s', $file)); + file_put_contents($phpunit, $contents); + } + } + + /** + * Evaluates expressions like %generate(secret)%. + * + * If $originalValue is passed, and the value contains an expression. + * the $originalValue is used. + */ + private function evaluateValue($value, ?string $originalValue = null) + { + if ('%generate(secret)%' === $value) { + if (null !== $originalValue) { + return $originalValue; + } + + return $this->generateRandomBytes(); + } + if (preg_match('~^%generate\(secret,\s*([0-9]+)\)%$~', $value, $matches)) { + if (null !== $originalValue) { + return $originalValue; + } + + return $this->generateRandomBytes($matches[1]); + } + + return $value; + } + + private function generateRandomBytes($length = 16) + { + return bin2hex(random_bytes($length)); + } + + private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $vars): array + { + $dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env'; + $files = [$dotenvPath, $dotenvPath.'.dist', 'phpunit.xml.dist', 'phpunit.xml']; + + if (0 === \count($vars)) { + return array_fill_keys($files, null); + } + + $originalContents = []; + foreach ($files as $file) { + $originalContents[$file] = file_exists($rootDir.'/'.$file) ? file_get_contents($rootDir.'/'.$file) : null; + } + + $this->configureEnvDist( + $recipe, + $vars, + true + ); + + if (!file_exists($rootDir.'/'.$dotenvPath.'.test')) { + $this->configurePhpUnit( + $recipe, + $vars, + true + ); + } + + $updatedContents = []; + foreach ($files as $file) { + $updatedContents[$file] = file_exists($rootDir.'/'.$file) ? file_get_contents($rootDir.'/'.$file) : null; + } + + foreach ($originalContents as $file => $contents) { + if (null === $contents) { + if (file_exists($rootDir.'/'.$file)) { + unlink($rootDir.'/'.$file); + } + } else { + file_put_contents($rootDir.'/'.$file, $contents); + } + } + + return $updatedContents; + } + + /** + * Attempts to find the existing value of an environment variable. + */ + private function findExistingValue(string $var, string $filename, Recipe $recipe): ?string + { + if (!file_exists($filename)) { + return null; + } + + $contents = file_get_contents($filename); + $section = $this->extractSection($recipe, $contents); + if (!$section) { + return null; + } + + $lines = explode("\n", $section); + foreach ($lines as $line) { + if (0 !== strpos($line, sprintf('%s=', $var))) { + continue; + } + + return trim(substr($line, \strlen($var) + 1)); + } + + return null; + } +} diff --git a/vendor/symfony/flex/src/Configurator/GitignoreConfigurator.php b/vendor/symfony/flex/src/Configurator/GitignoreConfigurator.php new file mode 100644 index 0000000..9d33d6c --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/GitignoreConfigurator.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class GitignoreConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $vars, Lock $lock, array $options = []) + { + $this->write('Adding entries to .gitignore'); + + $this->configureGitignore($recipe, $vars, $options['force'] ?? false); + } + + public function unconfigure(Recipe $recipe, $vars, Lock $lock) + { + $file = $this->options->get('root-dir').'/.gitignore'; + if (!file_exists($file)) { + return; + } + + $contents = preg_replace(sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($file), -1, $count); + if (!$count) { + return; + } + + $this->write('Removing entries in .gitignore'); + file_put_contents($file, ltrim($contents, "\r\n")); + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + $recipeUpdate->setOriginalFile( + '.gitignore', + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig) + ); + + $recipeUpdate->setNewFile( + '.gitignore', + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig) + ); + } + + private function configureGitignore(Recipe $recipe, array $vars, bool $update) + { + $gitignore = $this->options->get('root-dir').'/.gitignore'; + if (!$update && $this->isFileMarked($recipe, $gitignore)) { + return; + } + + $data = ''; + foreach ($vars as $value) { + $value = $this->options->expandTargetDir($value); + $data .= "$value\n"; + } + $data = "\n".ltrim($this->markData($recipe, $data), "\r\n"); + + if (!$this->updateData($gitignore, $data)) { + file_put_contents($gitignore, $data, \FILE_APPEND); + } + } + + private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, $vars): ?string + { + if (0 === \count($vars)) { + return null; + } + + $file = $rootDir.'/.gitignore'; + $originalContents = file_exists($file) ? file_get_contents($file) : null; + + $this->configureGitignore( + $recipe, + $vars, + true + ); + + $updatedContents = file_exists($file) ? file_get_contents($file) : null; + + if (null === $originalContents) { + if (file_exists($file)) { + unlink($file); + } + } else { + file_put_contents($file, $originalContents); + } + + return $updatedContents; + } +} diff --git a/vendor/symfony/flex/src/Configurator/MakefileConfigurator.php b/vendor/symfony/flex/src/Configurator/MakefileConfigurator.php new file mode 100644 index 0000000..5b5abe4 --- /dev/null +++ b/vendor/symfony/flex/src/Configurator/MakefileConfigurator.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Configurator; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +/** + * @author Fabien Potencier + */ +class MakefileConfigurator extends AbstractConfigurator +{ + public function configure(Recipe $recipe, $definitions, Lock $lock, array $options = []) + { + $this->write('Adding Makefile entries'); + + $this->configureMakefile($recipe, $definitions, $options['force'] ?? false); + } + + public function unconfigure(Recipe $recipe, $vars, Lock $lock) + { + if (!file_exists($makefile = $this->options->get('root-dir').'/Makefile')) { + return; + } + + $contents = preg_replace(sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($makefile), -1, $count); + if (!$count) { + return; + } + + $this->write(sprintf('Removing Makefile entries from %s', $makefile)); + if (!trim($contents)) { + @unlink($makefile); + } else { + file_put_contents($makefile, ltrim($contents, "\r\n")); + } + } + + public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void + { + $recipeUpdate->setOriginalFile( + 'Makefile', + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig) + ); + + $recipeUpdate->setNewFile( + 'Makefile', + $this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig) + ); + } + + private function configureMakefile(Recipe $recipe, array $definitions, bool $update) + { + $makefile = $this->options->get('root-dir').'/Makefile'; + if (!$update && $this->isFileMarked($recipe, $makefile)) { + return; + } + + $data = $this->options->expandTargetDir(implode("\n", $definitions)); + $data = $this->markData($recipe, $data); + $data = "\n".ltrim($data, "\r\n"); + + if (!file_exists($makefile)) { + $envKey = $this->options->get('runtime')['env_var_name'] ?? 'APP_ENV'; + $dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env'; + file_put_contents( + $this->options->get('root-dir').'/Makefile', + <<updateData($makefile, $data)) { + file_put_contents($makefile, $data, \FILE_APPEND); + } + } + + private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $definitions): ?string + { + if (0 === \count($definitions)) { + return null; + } + + $file = $rootDir.'/Makefile'; + $originalContents = file_exists($file) ? file_get_contents($file) : null; + + $this->configureMakefile( + $recipe, + $definitions, + true + ); + + $updatedContents = file_exists($file) ? file_get_contents($file) : null; + + if (null === $originalContents) { + if (file_exists($file)) { + unlink($file); + } + } else { + file_put_contents($file, $originalContents); + } + + return $updatedContents; + } +} diff --git a/vendor/symfony/flex/src/Downloader.php b/vendor/symfony/flex/src/Downloader.php new file mode 100644 index 0000000..02c6221 --- /dev/null +++ b/vendor/symfony/flex/src/Downloader.php @@ -0,0 +1,469 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Cache; +use Composer\Composer; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Util\Http\Response as ComposerResponse; +use Composer\Util\HttpDownloader; +use Composer\Util\Loop; + +/** + * @author Fabien Potencier + * @author Nicolas Grekas + */ +class Downloader +{ + private const DEFAULT_ENDPOINTS = [ + 'https://raw.githubusercontent.com/symfony/recipes/flex/main/index.json', + 'https://raw.githubusercontent.com/symfony/recipes-contrib/flex/main/index.json', + ]; + private const MAX_LENGTH = 1000; + + private static $versions; + private static $aliases; + + private $io; + private $sess; + private $cache; + + private HttpDownloader $rfs; + private $degradedMode = false; + private $endpoints; + private $index; + private $conflicts; + private $legacyEndpoint; + private $caFile; + private $enabled = true; + private $composer; + + public function __construct(Composer $composer, IOInterface $io, HttpDownloader $rfs) + { + if (getenv('SYMFONY_CAFILE')) { + $this->caFile = getenv('SYMFONY_CAFILE'); + } + + if (null === $endpoint = $composer->getPackage()->getExtra()['symfony']['endpoint'] ?? null) { + $this->endpoints = self::DEFAULT_ENDPOINTS; + } elseif (\is_array($endpoint) || false !== strpos($endpoint, '.json') || 'flex://defaults' === $endpoint) { + $this->endpoints = array_values((array) $endpoint); + if (\is_string($endpoint) && false !== strpos($endpoint, '.json')) { + $this->endpoints[] = 'flex://defaults'; + } + } else { + $this->legacyEndpoint = rtrim($endpoint, '/'); + } + + if (false === $endpoint = getenv('SYMFONY_ENDPOINT')) { + // no-op + } elseif (false !== strpos($endpoint, '.json') || 'flex://defaults' === $endpoint) { + $this->endpoints ?? $this->endpoints = self::DEFAULT_ENDPOINTS; + array_unshift($this->endpoints, $endpoint); + $this->legacyEndpoint = null; + } else { + $this->endpoints = null; + $this->legacyEndpoint = rtrim($endpoint, '/'); + } + + if (null !== $this->endpoints) { + if (false !== $i = array_search('flex://defaults', $this->endpoints, true)) { + array_splice($this->endpoints, $i, 1, self::DEFAULT_ENDPOINTS); + } + + $this->endpoints = array_fill_keys($this->endpoints, []); + } + + $this->io = $io; + $config = $composer->getConfig(); + $this->rfs = $rfs; + $this->cache = new Cache($io, $config->get('cache-repo-dir').'/flex'); + $this->sess = bin2hex(random_bytes(16)); + $this->composer = $composer; + } + + public function getSessionId(): string + { + return $this->sess; + } + + public function isEnabled() + { + return $this->enabled; + } + + public function disable() + { + $this->enabled = false; + } + + public function getVersions() + { + $this->initialize(); + + return self::$versions ?? self::$versions = current($this->get([$this->legacyEndpoint.'/versions.json'])); + } + + public function getAliases() + { + $this->initialize(); + + return self::$aliases ?? self::$aliases = current($this->get([$this->legacyEndpoint.'/aliases.json'])); + } + + /** + * Downloads recipes. + * + * @param OperationInterface[] $operations + */ + public function getRecipes(array $operations): array + { + $this->initialize(); + + if ($this->conflicts) { + $lockedRepository = $this->composer->getLocker()->getLockedRepository(); + foreach ($this->conflicts as $conflicts) { + foreach ($conflicts as $package => $versions) { + foreach ($versions as $version => $conflicts) { + foreach ($conflicts as $conflictingPackage => $constraint) { + if ($lockedRepository->findPackage($conflictingPackage, $constraint)) { + unset($this->index[$package][$version]); + } + } + } + } + } + $this->conflicts = []; + } + + $data = []; + $urls = []; + $chunk = ''; + $recipeRef = null; + foreach ($operations as $operation) { + $o = 'i'; + if ($operation instanceof UpdateOperation) { + $package = $operation->getTargetPackage(); + $o = 'u'; + } else { + $package = $operation->getPackage(); + if ($operation instanceof UninstallOperation) { + $o = 'r'; + } + + if ($operation instanceof InformationOperation) { + $recipeRef = $operation->getRecipeRef(); + } + } + + $version = $package->getPrettyVersion(); + if ($operation instanceof InformationOperation && $operation->getVersion()) { + $version = $operation->getVersion(); + } + if (0 === strpos($version, 'dev-') && isset($package->getExtra()['branch-alias'])) { + $branchAliases = $package->getExtra()['branch-alias']; + if ( + (isset($branchAliases[$version]) && $alias = $branchAliases[$version]) || + (isset($branchAliases['dev-main']) && $alias = $branchAliases['dev-main']) || + (isset($branchAliases['dev-trunk']) && $alias = $branchAliases['dev-trunk']) || + (isset($branchAliases['dev-develop']) && $alias = $branchAliases['dev-develop']) || + (isset($branchAliases['dev-default']) && $alias = $branchAliases['dev-default']) || + (isset($branchAliases['dev-latest']) && $alias = $branchAliases['dev-latest']) || + (isset($branchAliases['dev-next']) && $alias = $branchAliases['dev-next']) || + (isset($branchAliases['dev-current']) && $alias = $branchAliases['dev-current']) || + (isset($branchAliases['dev-support']) && $alias = $branchAliases['dev-support']) || + (isset($branchAliases['dev-tip']) && $alias = $branchAliases['dev-tip']) || + (isset($branchAliases['dev-master']) && $alias = $branchAliases['dev-master']) + ) { + $version = $alias; + } + } + + if ($recipeVersions = $this->index[$package->getName()] ?? null) { + $version = explode('.', preg_replace('/^dev-|^v|\.x-dev$|-dev$/', '', $version)); + $version = $version[0].'.'.($version[1] ?? '9999999'); + + foreach (array_reverse($recipeVersions) as $v => $endpoint) { + if (version_compare($version, $v, '<')) { + continue; + } + + $data['locks'][$package->getName()]['version'] = $version; + $data['locks'][$package->getName()]['recipe']['version'] = $v; + $links = $this->endpoints[$endpoint]['_links']; + + if (null !== $recipeRef && isset($links['archived_recipes_template'])) { + if (isset($links['archived_recipes_template_relative'])) { + $links['archived_recipes_template'] = preg_replace('{[^/\?]*+(?=\?|$)}', $links['archived_recipes_template_relative'], $endpoint, 1); + } + + $urls[] = strtr($links['archived_recipes_template'], [ + '{package_dotted}' => str_replace('/', '.', $package->getName()), + '{ref}' => $recipeRef, + ]); + + break; + } + + if (isset($links['recipe_template_relative'])) { + $links['recipe_template'] = preg_replace('{[^/\?]*+(?=\?|$)}', $links['recipe_template_relative'], $endpoint, 1); + } + + $urls[] = strtr($links['recipe_template'], [ + '{package_dotted}' => str_replace('/', '.', $package->getName()), + '{package}' => $package->getName(), + '{version}' => $v, + ]); + + break; + } + + continue; + } + + if (\is_array($recipeVersions)) { + $data['conflicts'][$package->getName()] = true; + } + + if (null !== $this->endpoints) { + continue; + } + + // FIXME: Multi name with getNames() + $name = str_replace('/', ',', $package->getName()); + $path = sprintf('%s,%s%s', $name, $o, $version); + if ($date = $package->getReleaseDate()) { + $path .= ','.$date->format('U'); + } + if (\strlen($chunk) + \strlen($path) > self::MAX_LENGTH) { + $urls[] = $this->legacyEndpoint.'/p/'.$chunk; + $chunk = $path; + } elseif ($chunk) { + $chunk .= ';'.$path; + } else { + $chunk = $path; + } + } + if ($chunk) { + $urls[] = $this->legacyEndpoint.'/p/'.$chunk; + } + + if (null === $this->endpoints) { + foreach ($this->get($urls, true) as $body) { + foreach ($body['manifests'] ?? [] as $name => $manifest) { + $data['manifests'][$name] = $manifest; + } + foreach ($body['locks'] ?? [] as $name => $lock) { + $data['locks'][$name] = $lock; + } + } + } else { + foreach ($this->get($urls, true) as $body) { + foreach ($body['manifests'] ?? [] as $name => $manifest) { + if (null === $version = $data['locks'][$name]['recipe']['version'] ?? null) { + continue; + } + $endpoint = $this->endpoints[$this->index[$name][$version]]; + + $data['locks'][$name]['recipe'] = [ + 'repo' => $endpoint['_links']['repository'], + 'branch' => $endpoint['branch'], + 'version' => $version, + 'ref' => $manifest['ref'], + ]; + + foreach ($manifest['files'] ?? [] as $i => $file) { + $manifest['files'][$i]['contents'] = \is_array($file['contents']) ? implode("\n", $file['contents']) : base64_decode($file['contents']); + } + + $data['manifests'][$name] = $manifest + [ + 'repository' => $endpoint['_links']['repository'], + 'package' => $name, + 'version' => $version, + 'origin' => strtr($endpoint['_links']['origin_template'], [ + '{package}' => $name, + '{version}' => $version, + ]), + 'is_contrib' => $endpoint['is_contrib'] ?? false, + ]; + } + } + } + + return $data; + } + + /** + * Used to "hide" a recipe version so that the next most-recent will be returned. + * + * This is used when resolving "conflicts". + */ + public function removeRecipeFromIndex(string $packageName, string $version) + { + unset($this->index[$packageName][$version]); + } + + /** + * Fetches and decodes JSON HTTP response bodies. + */ + private function get(array $urls, bool $isRecipe = false, int $try = 3): array + { + $responses = []; + $retries = []; + $options = []; + + foreach ($urls as $url) { + $cacheKey = self::generateCacheKey($url); + $headers = []; + + if (preg_match('{^https?://api\.github\.com/}', $url)) { + $headers[] = 'Accept: application/vnd.github.v3.raw'; + } elseif (preg_match('{^https?://raw\.githubusercontent\.com/}', $url) && $this->io->hasAuthentication('github.com')) { + $auth = $this->io->getAuthentication('github.com'); + if ('x-oauth-basic' === $auth['password']) { + $headers[] = 'Authorization: token '.$auth['username']; + } + } elseif ($this->legacyEndpoint) { + $headers[] = 'Package-Session: '.$this->sess; + } + + if ($contents = $this->cache->read($cacheKey)) { + $cachedResponse = Response::fromJson(json_decode($contents, true)); + if ($lastModified = $cachedResponse->getHeader('last-modified')) { + $headers[] = 'If-Modified-Since: '.$lastModified; + } + if ($eTag = $cachedResponse->getHeader('etag')) { + $headers[] = 'If-None-Match: '.$eTag; + } + $responses[$url] = $cachedResponse->getBody(); + } + + $options[$url] = $this->getOptions($headers); + } + + $loop = new Loop($this->rfs); + $jobs = []; + foreach ($urls as $url) { + $jobs[] = $this->rfs->add($url, $options[$url])->then(function (ComposerResponse $response) use ($url, &$responses) { + if (200 === $response->getStatusCode()) { + $cacheKey = self::generateCacheKey($url); + $responses[$url] = $this->parseJson($response->getBody(), $url, $cacheKey, $response->getHeaders())->getBody(); + } + }, function (\Exception $e) use ($url, &$retries) { + $retries[] = [$url, $e]; + }); + } + $loop->wait($jobs); + + if (!$retries) { + return $responses; + } + + if (0 < --$try) { + usleep(100000); + + return $this->get(array_column($retries, 0), $isRecipe, $try) + $responses; + } + + foreach ($retries as [$url, $e]) { + if (isset($responses[$url])) { + $this->switchToDegradedMode($e, $url); + } elseif ($isRecipe) { + $this->io->writeError('Failed to download recipe: '.$e->getMessage().''); + } else { + throw $e; + } + } + + return $responses; + } + + private function parseJson(string $json, string $url, string $cacheKey, array $lastHeaders): Response + { + $data = JsonFile::parseJson($json, $url); + if (!empty($data['warning'])) { + $this->io->writeError('Warning from '.$url.': '.$data['warning'].''); + } + if (!empty($data['info'])) { + $this->io->writeError('Info from '.$url.': '.$data['info'].''); + } + + $response = new Response($data, $lastHeaders); + if ($cacheKey && ($response->getHeader('last-modified') || $response->getHeader('etag'))) { + $this->cache->write($cacheKey, json_encode($response)); + } + + return $response; + } + + private function switchToDegradedMode(\Exception $e, string $url) + { + if (!$this->degradedMode) { + $this->io->writeError(''.$e->getMessage().''); + $this->io->writeError(''.$url.' could not be fully loaded, package information was loaded from the local cache and may be out of date'); + } + $this->degradedMode = true; + } + + private function getOptions(array $headers): array + { + $options = ['http' => ['header' => $headers]]; + + if (null !== $this->caFile) { + $options['ssl']['cafile'] = $this->caFile; + } + + return $options; + } + + private function initialize() + { + if (null !== $this->index || null === $this->endpoints) { + $this->index ?? $this->index = []; + + return; + } + + $indexes = self::$versions = self::$aliases = []; + + foreach ($this->get(array_keys($this->endpoints)) as $endpoint => $index) { + $indexes[$endpoint] = $index; + } + + foreach ($this->endpoints as $endpoint => $config) { + $config = $indexes[$endpoint] ?? []; + foreach ($config['recipes'] ?? [] as $package => $versions) { + $this->index[$package] = $this->index[$package] ?? array_fill_keys($versions, $endpoint); + } + $this->conflicts[] = $config['recipe-conflicts'] ?? []; + self::$versions += $config['versions'] ?? []; + self::$aliases += $config['aliases'] ?? []; + unset($config['recipes'], $config['recipe-conflicts'], $config['versions'], $config['aliases']); + $this->endpoints[$endpoint] = $config; + } + } + + private static function generateCacheKey(string $url): string + { + $url = preg_replace('{^https://api.github.com/repos/([^/]++/[^/]++)/contents/}', '$1/', $url); + $url = preg_replace('{^https://raw.githubusercontent.com/([^/]++/[^/]++)/}', '$1/', $url); + + $key = preg_replace('{[^a-z0-9.]}i', '-', $url); + + // eCryptfs can have problems with filenames longer than around 143 chars + return \strlen($key) > 140 ? md5($url) : $key; + } +} diff --git a/vendor/symfony/flex/src/Event/UpdateEvent.php b/vendor/symfony/flex/src/Event/UpdateEvent.php new file mode 100644 index 0000000..06dbe0c --- /dev/null +++ b/vendor/symfony/flex/src/Event/UpdateEvent.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Event; + +use Composer\Script\Event; +use Composer\Script\ScriptEvents; + +class UpdateEvent extends Event +{ + private $force; + private $reset; + + public function __construct(bool $force, bool $reset) + { + $this->name = ScriptEvents::POST_UPDATE_CMD; + $this->force = $force; + $this->reset = $reset; + } + + public function force(): bool + { + return $this->force; + } + + public function reset(): bool + { + return $this->reset; + } +} diff --git a/vendor/symfony/flex/src/Flex.php b/vendor/symfony/flex/src/Flex.php new file mode 100644 index 0000000..97b6491 --- /dev/null +++ b/vendor/symfony/flex/src/Flex.php @@ -0,0 +1,869 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Command\GlobalCommand; +use Composer\Composer; +use Composer\Console\Application; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Transaction; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Factory; +use Composer\Installer; +use Composer\Installer\InstallerEvent; +use Composer\Installer\InstallerEvents; +use Composer\Installer\PackageEvent; +use Composer\Installer\PackageEvents; +use Composer\Installer\SuggestedPackagesReporter; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Package\BasePackage; +use Composer\Package\Locker; +use Composer\Package\Package; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PluginInterface; +use Composer\Plugin\PrePoolCreateEvent; +use Composer\Script\Event; +use Composer\Script\ScriptEvents; +use Composer\Semver\VersionParser; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Flex\Event\UpdateEvent; +use Symfony\Flex\Unpack\Operation; +use Symfony\Thanks\Thanks; + +/** + * @author Fabien Potencier + * @author Nicolas Grekas + */ +class Flex implements PluginInterface, EventSubscriberInterface +{ + public static $storedOperations = []; + + /** + * @var Composer + */ + private $composer; + + /** + * @var IOInterface + */ + private $io; + + private $config; + private $options; + private $configurator; + private $downloader; + + /** + * @var Installer + */ + private $installer; + private $postInstallOutput = ['']; + private $operations = []; + private $lock; + private $displayThanksReminder = 0; + private $dryRun = false; + private $reinstall; + private static $activated = true; + private static $aliasResolveCommands = [ + 'require' => true, + 'update' => false, + 'remove' => false, + 'unpack' => true, + ]; + private $filter; + + /** + * @return void + */ + public function activate(Composer $composer, IOInterface $io) + { + if (!\extension_loaded('openssl')) { + self::$activated = false; + $io->writeError('Symfony Flex has been disabled. You must enable the openssl extension in your "php.ini" file.'); + + return; + } + + // to avoid issues when Flex is upgraded, we load all PHP classes now + // that way, we are sure to use all classes from the same version + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__, \FilesystemIterator::SKIP_DOTS)) as $file) { + if ('.php' === substr($file, -4)) { + class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__), -4))); + } + } + + $this->composer = $composer; + $this->io = $io; + $this->config = $composer->getConfig(); + $this->options = $this->initOptions(); + + // if Flex is being upgraded, the original operations from the original Flex + // instance are stored in the static property, so we can reuse them now. + if (property_exists(self::class, 'storedOperations') && self::$storedOperations) { + $this->operations = self::$storedOperations; + self::$storedOperations = []; + } + + $symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? '')); + + $rfs = Factory::createHttpDownloader($this->io, $this->config); + + $this->downloader = $downloader = new Downloader($composer, $io, $rfs); + + if ($symfonyRequire) { + $this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader); + } + + $composerFile = Factory::getComposerFile(); + $composerLock = 'json' === pathinfo($composerFile, \PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile.'.lock'; + $symfonyLock = str_replace('composer', 'symfony', basename($composerLock)); + + $this->configurator = new Configurator($composer, $io, $this->options); + $this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: \dirname($composerLock).'/'.(basename($composerLock) !== $symfonyLock ? $symfonyLock : 'symfony.lock')); + + $disable = true; + foreach (array_merge($composer->getPackage()->getRequires() ?? [], $composer->getPackage()->getDevRequires() ?? []) as $link) { + // recipes apply only when symfony/flex is found in "require" or "require-dev" in the root package + if ('symfony/flex' === $link->getTarget()) { + $disable = false; + break; + } + } + if ($disable) { + $downloader->disable(); + } + + $backtrace = $this->configureInstaller(); + + foreach ($backtrace as $trace) { + if (!isset($trace['object']) || !isset($trace['args'][0])) { + continue; + } + + if (!$trace['object'] instanceof Application || !$trace['args'][0] instanceof ArgvInput) { + continue; + } + + // In Composer 1.0.*, $input knows about option and argument definitions + // Since Composer >=1.1, $input contains only raw values + $input = $trace['args'][0]; + $app = $trace['object']; + + $resolver = new PackageResolver($this->downloader); + + try { + $command = $input->getFirstArgument(); + $command = $command ? $app->find($command)->getName() : null; + } catch (\InvalidArgumentException $e) { + } + + if ('create-project' === $command) { + if ($input->hasOption('remove-vcs')) { + $input->setOption('remove-vcs', true); + } + } elseif ('update' === $command) { + $this->displayThanksReminder = 1; + } elseif ('outdated' === $command) { + $symfonyRequire = null; + } + + if (isset(self::$aliasResolveCommands[$command])) { + if ($input->hasArgument('packages')) { + $input->setArgument('packages', $resolver->resolve($input->getArgument('packages'), self::$aliasResolveCommands[$command])); + } + } + + if ($input->hasParameterOption('--prefer-lowest', true)) { + // When prefer-lowest is set and no stable version has been released, + // we consider "dev" more stable than "alpha", "beta" or "RC". This + // allows testing lowest versions with potential fixes applied. + BasePackage::$stabilities['dev'] = 1 + BasePackage::STABILITY_STABLE; + } + + $app->add(new Command\RecipesCommand($this, $this->lock, $rfs)); + $app->add(new Command\InstallRecipesCommand($this, $this->options->get('root-dir'), $this->options->get('runtime')['dotenv_path'] ?? '.env')); + $app->add(new Command\UpdateRecipesCommand($this, $this->downloader, $rfs, $this->configurator, $this->options->get('root-dir'))); + $app->add(new Command\DumpEnvCommand($this->config, $this->options)); + + break; + } + } + + /** + * @return void + */ + public function deactivate(Composer $composer, IOInterface $io) + { + // store operations in case Flex is being upgraded + self::$storedOperations = $this->operations; + self::$activated = false; + } + + public function configureInstaller() + { + $backtrace = debug_backtrace(); + foreach ($backtrace as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Installer) { + $this->installer = $trace['object']->setSuggestedPackagesReporter(new SuggestedPackagesReporter(new NullIO())); + + $updateAllowList = \Closure::bind(function () { + return $this->updateAllowList; + }, $this->installer, $this->installer)(); + + if (['php' => 0] === $updateAllowList) { + $this->dryRun = true; // prevent recipes from being uninstalled when removing a pack + } + } + + if (isset($trace['object']) && $trace['object'] instanceof GlobalCommand) { + $this->downloader->disable(); + } + } + + return $backtrace; + } + + public function configureProject(Event $event) + { + if (!$this->downloader->isEnabled()) { + $this->io->writeError('Project configuration is disabled: "symfony/flex" not found in the root composer.json'); + + return; + } + + // Remove LICENSE (which do not apply to the user project) + @unlink('LICENSE'); + + // Update composer.json (project is proprietary by default) + $file = Factory::getComposerFile(); + $contents = file_get_contents($file); + $manipulator = new JsonManipulator($contents); + $json = JsonFile::parseJson($contents); + + // new projects are most of the time proprietary + $manipulator->addMainKey('license', 'proprietary'); + + // extra.branch-alias doesn't apply to the project + $manipulator->removeSubNode('extra', 'branch-alias'); + + // 'name' and 'description' are only required for public packages + // don't use $manipulator->removeProperty() for BC with Composer 1.0 + $contents = preg_replace(['{^\s*+"name":.*,$\n}m', '{^\s*+"description":.*,$\n}m'], '', $manipulator->getContents(), 1); + file_put_contents($file, $contents); + + $this->updateComposerLock(); + } + + public function recordFlexInstall(PackageEvent $event) + { + if (null === $this->reinstall && 'symfony/flex' === $event->getOperation()->getPackage()->getName()) { + $this->reinstall = true; + } + } + + public function record(PackageEvent $event) + { + if ($this->shouldRecordOperation($event->getOperation(), $event->isDevMode(), $event->getComposer())) { + $this->operations[] = $event->getOperation(); + } + } + + public function recordOperations(InstallerEvent $event) + { + if (!$event->isExecutingOperations()) { + return; + } + + $versionParser = new VersionParser(); + $packages = []; + foreach ($this->lock->all() as $name => $info) { + $packages[] = new Package($name, $versionParser->normalize($info['version']), $info['version']); + } + + $transation = \Closure::bind(function () use ($packages, $event) { + return new Transaction($packages, $event->getTransaction()->resultPackageMap); + }, null, Transaction::class)(); + + foreach ($transation->getOperations() as $operation) { + if (!$operation instanceof UninstallOperation && $this->shouldRecordOperation($operation, $event->isDevMode(), $event->getComposer())) { + $this->operations[] = $operation; + } + } + } + + public function update(Event $event, $operations = []) + { + if ($operations) { + $this->operations = $operations; + } + + $this->install($event); + + $file = Factory::getComposerFile(); + $contents = file_get_contents($file); + $json = JsonFile::parseJson($contents); + + if (!$this->reinstall && !isset($json['flex-require']) && !isset($json['flex-require-dev'])) { + $this->unpack($event); + + return; + } + + // merge "flex-require" with "require" + $manipulator = new JsonManipulator($contents); + $sortPackages = $this->composer->getConfig()->get('sort-packages'); + $symfonyVersion = $json['extra']['symfony']['require'] ?? null; + $versions = $symfonyVersion ? $this->downloader->getVersions() : null; + foreach (['require', 'require-dev'] as $type) { + if (!isset($json['flex-'.$type])) { + continue; + } + foreach ($json['flex-'.$type] as $package => $constraint) { + if ($symfonyVersion && '*' === $constraint && isset($versions['splits'][$package])) { + // replace unbounded constraints for symfony/* packages by extra.symfony.require + $constraint = $symfonyVersion; + } + $manipulator->addLink($type, $package, $constraint, $sortPackages); + } + + $manipulator->removeMainKey('flex-'.$type); + } + + file_put_contents($file, $manipulator->getContents()); + + $this->reinstall($event, true); + } + + public function install(Event $event) + { + $rootDir = $this->options->get('root-dir'); + $runtime = $this->options->get('runtime'); + $dotenvPath = $rootDir.'/'.($runtime['dotenv_path'] ?? '.env'); + + if (!file_exists($dotenvPath) && !file_exists($dotenvPath.'.local') && file_exists($dotenvPath.'.dist') && false === strpos(file_get_contents($dotenvPath.'.dist'), '.env.local')) { + copy($dotenvPath.'.dist', $dotenvPath); + } + + // Execute missing recipes + $recipes = ScriptEvents::POST_UPDATE_CMD === $event->getName() ? $this->fetchRecipes($this->operations, $event instanceof UpdateEvent && $event->reset()) : []; + $this->operations = []; // Reset the operation after getting recipes + + if (2 === $this->displayThanksReminder) { + $love = '\\' === \DIRECTORY_SEPARATOR ? 'love' : '💖 '; + $star = '\\' === \DIRECTORY_SEPARATOR ? 'star' : '★ '; + + $this->io->writeError(''); + $this->io->writeError('What about running composer global require symfony/thanks && composer thanks now?'); + $this->io->writeError(sprintf('This will spread some %s by sending a %s to the GitHub repositories of your fellow package maintainers.', $love, $star)); + } + + $this->io->writeError(''); + + if (!$recipes) { + if (ScriptEvents::POST_UPDATE_CMD === $event->getName()) { + $this->finish($rootDir); + } + + if ($this->downloader->isEnabled()) { + $this->io->writeError('Run composer recipes at any time to see the status of your Symfony recipes.'); + $this->io->writeError(''); + } + + return; + } + + $this->io->writeError(sprintf('Symfony operations: %d recipe%s (%s)', \count($recipes), \count($recipes) > 1 ? 's' : '', $this->downloader->getSessionId())); + $installContribs = $this->composer->getPackage()->getExtra()['symfony']['allow-contrib'] ?? false; + $manifest = null; + $originalComposerJsonHash = $this->getComposerJsonHash(); + $postInstallRecipes = []; + foreach ($recipes as $recipe) { + if ('install' === $recipe->getJob() && !$installContribs && $recipe->isContrib()) { + $warning = $this->io->isInteractive() ? 'WARNING' : 'IGNORING'; + $this->io->writeError(sprintf(' - %s %s', $warning, $this->formatOrigin($recipe))); + $question = sprintf(' The recipe for this package comes from the "contrib" repository, which is open to community contributions. + Review the recipe at %s + + Do you want to execute this recipe? + [y] Yes + [n] No + [a] Yes for all packages, only for the current installation session + [p] Yes permanently, never ask again for this project + (defaults to n): ', $recipe->getURL()); + $answer = $this->io->askAndValidate( + $question, + function ($value) { + if (null === $value) { + return 'n'; + } + $value = strtolower($value[0]); + if (!\in_array($value, ['y', 'n', 'a', 'p'])) { + throw new \InvalidArgumentException('Invalid choice.'); + } + + return $value; + }, + null, + 'n' + ); + if ('n' === $answer) { + continue; + } + if ('a' === $answer) { + $installContribs = true; + } + if ('p' === $answer) { + $installContribs = true; + $json = new JsonFile(Factory::getComposerFile()); + $manipulator = new JsonManipulator(file_get_contents($json->getPath())); + $manipulator->addSubNode('extra', 'symfony.allow-contrib', true); + file_put_contents($json->getPath(), $manipulator->getContents()); + } + } + + switch ($recipe->getJob()) { + case 'install': + $postInstallRecipes[] = $recipe; + $this->io->writeError(sprintf(' - Configuring %s', $this->formatOrigin($recipe))); + $this->configurator->install($recipe, $this->lock, [ + 'force' => $event instanceof UpdateEvent && $event->force(), + ]); + $manifest = $recipe->getManifest(); + if (isset($manifest['post-install-output'])) { + $this->postInstallOutput[] = sprintf(' %s instructions:', $recipe->getName()); + $this->postInstallOutput[] = ''; + foreach ($manifest['post-install-output'] as $line) { + $this->postInstallOutput[] = $this->options->expandTargetDir($line); + } + $this->postInstallOutput[] = ''; + } + break; + case 'update': + break; + case 'uninstall': + $this->io->writeError(sprintf(' - Unconfiguring %s', $this->formatOrigin($recipe))); + $this->configurator->unconfigure($recipe, $this->lock); + break; + } + } + + if (method_exists($this->configurator, 'postInstall')) { + foreach ($postInstallRecipes as $recipe) { + $this->configurator->postInstall($recipe, $this->lock, [ + 'force' => $event instanceof UpdateEvent && $event->force(), + ]); + } + } + + if (null !== $manifest) { + array_unshift( + $this->postInstallOutput, + ' ', + ' What\'s next? ', + ' ', + '', + 'Some files have been created and/or updated to configure your new packages.', + 'Please review, edit and commit them: these files are yours.' + ); + } + + $this->finish($rootDir, $originalComposerJsonHash); + } + + public function finish(string $rootDir, ?string $originalComposerJsonHash = null): void + { + $this->synchronizePackageJson($rootDir); + $this->lock->write(); + + if ($originalComposerJsonHash && $this->getComposerJsonHash() !== $originalComposerJsonHash) { + $this->updateComposerLock(); + } + } + + private function synchronizePackageJson(string $rootDir) + { + $rootDir = realpath($rootDir); + $vendorDir = trim((new Filesystem())->makePathRelative($this->config->get('vendor-dir'), $rootDir), '/'); + + $executor = new ScriptExecutor($this->composer, $this->io, $this->options); + $synchronizer = new PackageJsonSynchronizer($rootDir, $vendorDir, $executor, $this->io); + + if ($synchronizer->shouldSynchronize()) { + $lockData = $this->composer->getLocker()->getLockData(); + + if ($synchronizer->synchronize(array_merge($lockData['packages'] ?? [], $lockData['packages-dev'] ?? []))) { + $this->io->writeError('Synchronizing package.json with PHP packages'); + $this->io->writeError('Don\'t forget to run npm install --force or yarn install --force to refresh your JavaScript dependencies!'); + $this->io->writeError(''); + } + } + } + + /** + * @return void + */ + public function uninstall(Composer $composer, IOInterface $io) + { + $this->lock->delete(); + } + + public function enableThanksReminder() + { + if (1 === $this->displayThanksReminder) { + $this->displayThanksReminder = !class_exists(Thanks::class, false) ? 2 : 0; + } + } + + public function executeAutoScripts(Event $event) + { + $event->stopPropagation(); + + // force reloading scripts as we might have added and removed during this run + $json = new JsonFile(Factory::getComposerFile()); + $jsonContents = $json->read(); + + $executor = new ScriptExecutor($this->composer, $this->io, $this->options); + foreach ($jsonContents['scripts']['auto-scripts'] as $cmd => $type) { + $executor->execute($type, $cmd); + } + + $this->io->write($this->postInstallOutput); + $this->postInstallOutput = []; + } + + /** + * @return Recipe[] + */ + public function fetchRecipes(array $operations, bool $reset): array + { + if (!$this->downloader->isEnabled()) { + $this->io->writeError('Symfony recipes are disabled: "symfony/flex" not found in the root composer.json'); + + return []; + } + $devPackages = null; + $data = $this->downloader->getRecipes($operations); + $manifests = $data['manifests'] ?? []; + $locks = $data['locks'] ?? []; + // symfony/flex recipes should always be applied first + $flexRecipe = []; + // symfony/framework-bundle recipe should always be applied first after the metapackages + $recipes = [ + 'symfony/framework-bundle' => null, + ]; + $packRecipes = []; + $metaRecipes = []; + + foreach ($operations as $operation) { + if ($operation instanceof UpdateOperation) { + $package = $operation->getTargetPackage(); + } else { + $package = $operation->getPackage(); + } + + // FIXME: Multi name with getNames() + $name = $package->getName(); + $job = method_exists($operation, 'getOperationType') ? $operation->getOperationType() : $operation->getJobType(); + + if (!isset($manifests[$name]) && isset($data['conflicts'][$name])) { + $this->io->writeError(sprintf(' - Skipping recipe for %s: all versions of the recipe conflict with your package versions.', $name)); + continue; + } + + while ($this->doesRecipeConflict($manifests[$name] ?? [], $operation)) { + $this->downloader->removeRecipeFromIndex($name, $manifests[$name]['version']); + $newData = $this->downloader->getRecipes([$operation]); + $newManifests = $newData['manifests'] ?? []; + + if (!isset($newManifests[$name])) { + // no older recipe found + $this->io->writeError(sprintf(' - Skipping recipe for %s: all versions of the recipe conflict with your package versions.', $name)); + + continue 2; + } + + // push the "old" recipe into the $manifests + $manifests[$name] = $newManifests[$name]; + $locks[$name] = $newData['locks'][$name]; + } + + if ($operation instanceof InstallOperation && isset($locks[$name])) { + $ref = $this->lock->get($name)['recipe']['ref'] ?? null; + if (!$reset && $ref && ($locks[$name]['recipe']['ref'] ?? null) === $ref) { + continue; + } + $this->lock->set($name, $locks[$name]); + } elseif ($operation instanceof UninstallOperation) { + if (!$this->lock->has($name)) { + continue; + } + $this->lock->remove($name); + } + + if (isset($manifests[$name])) { + $recipe = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []); + + if ('symfony-pack' === $package->getType()) { + $packRecipes[$name] = $recipe; + } elseif ('metapackage' === $package->getType()) { + $metaRecipes[$name] = $recipe; + } elseif ('symfony/flex' === $name) { + $flexRecipe = [$name => $recipe]; + } else { + $recipes[$name] = $recipe; + } + } else { + $bundles = []; + + if (null === $devPackages) { + $devPackages = array_column($this->composer->getLocker()->getLockData()['packages-dev'], 'name'); + } + $envs = \in_array($name, $devPackages) ? ['dev', 'test'] : ['all']; + $bundle = new SymfonyBundle($this->composer, $package, $job); + foreach ($bundle->getClassNames() as $bundleClass) { + $bundles[$bundleClass] = $envs; + } + + if ($bundles) { + $manifest = [ + 'origin' => sprintf('%s:%s@auto-generated recipe', $name, $package->getPrettyVersion()), + 'manifest' => ['bundles' => $bundles], + ]; + $recipes[$name] = new Recipe($package, $name, $job, $manifest); + + if ($operation instanceof InstallOperation) { + $this->lock->set($name, ['version' => $package->getPrettyVersion()]); + } + } + } + } + + return array_merge($flexRecipe, $packRecipes, $metaRecipes, array_filter($recipes)); + } + + public function truncatePackages(PrePoolCreateEvent $event) + { + if (!$this->filter) { + return; + } + + $rootPackage = $this->composer->getPackage(); + $lockedPackages = $event->getRequest()->getFixedOrLockedPackages(); + + $event->setPackages($this->filter->removeLegacyPackages($event->getPackages(), $rootPackage, $lockedPackages)); + } + + public function getComposerJsonHash(): string + { + return md5_file(Factory::getComposerFile()); + } + + public function getLock(): Lock + { + if (null === $this->lock) { + throw new \Exception('Cannot access lock before calling activate().'); + } + + return $this->lock; + } + + private function initOptions(): Options + { + $extra = $this->composer->getPackage()->getExtra(); + + $options = array_merge([ + 'bin-dir' => 'bin', + 'conf-dir' => 'conf', + 'config-dir' => 'config', + 'src-dir' => 'src', + 'var-dir' => 'var', + 'public-dir' => 'public', + 'root-dir' => $extra['symfony']['root-dir'] ?? '.', + 'runtime' => $extra['runtime'] ?? [], + ], $extra); + + return new Options($options, $this->io); + } + + private function formatOrigin(Recipe $recipe): string + { + if (method_exists($recipe, 'getFormattedOrigin')) { + return $recipe->getFormattedOrigin(); + } + + // BC with upgrading from flex < 1.18 + $origin = $recipe->getOrigin(); + + // symfony/translation:3.3@github.com/symfony/recipes:branch + if (!preg_match('/^([^:]++):([^@]++)@(.+)$/', $origin, $matches)) { + return $origin; + } + + return sprintf('%s (>=%s): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? ''.$matches[3].'' : $matches[3]); + } + + private function shouldRecordOperation(OperationInterface $operation, bool $isDevMode, ?Composer $composer = null): bool + { + if ($this->dryRun || $this->reinstall) { + return false; + } + + if ($operation instanceof UpdateOperation) { + $package = $operation->getTargetPackage(); + } else { + $package = $operation->getPackage(); + } + + // when Composer runs with --no-dev, ignore uninstall operations on packages from require-dev + if (!$isDevMode && $operation instanceof UninstallOperation) { + foreach (($composer ?? $this->composer)->getLocker()->getLockData()['packages-dev'] as $p) { + if ($package->getName() === $p['name']) { + return false; + } + } + } + + // FIXME: Multi name with getNames() + $name = $package->getName(); + if ($operation instanceof InstallOperation) { + if (!$this->lock->has($name)) { + return true; + } + } elseif ($operation instanceof UninstallOperation) { + return true; + } + + return false; + } + + private function updateComposerLock() + { + $lock = substr(Factory::getComposerFile(), 0, -4).'lock'; + $composerJson = file_get_contents(Factory::getComposerFile()); + $lockFile = new JsonFile($lock, null, $this->io); + $locker = new Locker($this->io, $lockFile, $this->composer->getInstallationManager(), $composerJson); + $lockData = $locker->getLockData(); + $lockData['content-hash'] = Locker::getContentHash($composerJson); + $lockFile->write($lockData); + } + + private function unpack(Event $event) + { + $jsonPath = Factory::getComposerFile(); + $json = JsonFile::parseJson(file_get_contents($jsonPath)); + $sortPackages = $this->composer->getConfig()->get('sort-packages'); + $unpackOp = new Operation(true, $sortPackages); + + foreach (['require', 'require-dev'] as $type) { + foreach ($json[$type] ?? [] as $package => $constraint) { + $unpackOp->addPackage($package, $constraint, 'require-dev' === $type); + } + } + + $unpacker = new Unpacker($this->composer, new PackageResolver($this->downloader), $this->dryRun); + $result = $unpacker->unpack($unpackOp); + + if (!$result->getUnpacked()) { + return; + } + + $this->io->writeError('Unpacking Symfony packs'); + foreach ($result->getUnpacked() as $pkg) { + $this->io->writeError(sprintf(' - Unpacked %s', $pkg->getName())); + } + + $unpacker->updateLock($result, $this->io); + + $this->reinstall($event, false); + } + + private function reinstall(Event $event, bool $update) + { + $this->reinstall = false; + $event->stopPropagation(); + + $ed = $this->composer->getEventDispatcher(); + $disableScripts = !method_exists($ed, 'setRunScripts') || !((array) $ed)["\0*\0runScripts"]; + $composer = Factory::create($this->io, null, false, $disableScripts); + + $installer = clone $this->installer; + $installer->__construct( + $this->io, + $composer->getConfig(), + $composer->getPackage(), + $composer->getDownloadManager(), + $composer->getRepositoryManager(), + $composer->getLocker(), + $composer->getInstallationManager(), + $composer->getEventDispatcher(), + $composer->getAutoloadGenerator() + ); + if (method_exists($installer, 'setPlatformRequirementFilter')) { + $installer->setPlatformRequirementFilter(((array) $this->installer)["\0*\0platformRequirementFilter"]); + } + + if (!$update) { + $installer->setUpdateAllowList(['php']); + } + + $installer->run(); + + $this->io->write($this->postInstallOutput); + $this->postInstallOutput = []; + } + + public static function getSubscribedEvents(): array + { + if (!self::$activated) { + return []; + } + + $events = [ + PackageEvents::POST_PACKAGE_UPDATE => 'enableThanksReminder', + PackageEvents::POST_PACKAGE_INSTALL => 'recordFlexInstall', + PackageEvents::POST_PACKAGE_UNINSTALL => 'record', + InstallerEvents::PRE_OPERATIONS_EXEC => 'recordOperations', + PluginEvents::PRE_POOL_CREATE => 'truncatePackages', + ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject', + ScriptEvents::POST_INSTALL_CMD => 'install', + ScriptEvents::PRE_UPDATE_CMD => 'configureInstaller', + ScriptEvents::POST_UPDATE_CMD => 'update', + 'auto-scripts' => 'executeAutoScripts', + ]; + + return $events; + } + + private function doesRecipeConflict(array $recipeData, OperationInterface $operation): bool + { + if (empty($recipeData['manifest']['conflict']) || $operation instanceof UninstallOperation) { + return false; + } + + $lockedRepository = $this->composer->getLocker()->getLockedRepository(); + + foreach ($recipeData['manifest']['conflict'] as $conflictingPackage => $constraint) { + if ($lockedRepository->findPackage($conflictingPackage, $constraint)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/flex/src/GithubApi.php b/vendor/symfony/flex/src/GithubApi.php new file mode 100644 index 0000000..391304c --- /dev/null +++ b/vendor/symfony/flex/src/GithubApi.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Util\HttpDownloader; +use Composer\Util\RemoteFilesystem; + +class GithubApi +{ + /** @var HttpDownloader|RemoteFilesystem */ + private $downloader; + + public function __construct($downloader) + { + $this->downloader = $downloader; + } + + /** + * Attempts to find data about when the recipe was installed. + * + * Returns an array containing: + * commit: The git sha of the last commit of the recipe + * date: The date of the commit + * new_commits: An array of commit sha's in this recipe's directory+version since the commit + * The key is the sha & the value is the date + */ + public function findRecipeCommitDataFromTreeRef(string $package, string $repo, string $branch, string $version, string $lockRef): ?array + { + $repositoryName = $this->getRepositoryName($repo); + if (!$repositoryName) { + return null; + } + + $recipePath = sprintf('%s/%s', $package, $version); + $commitsData = $this->requestGitHubApi(sprintf( + 'https://api.github.com/repos/%s/commits?path=%s&sha=%s', + $repositoryName, + $recipePath, + $branch + )); + + $commitShas = []; + foreach ($commitsData as $commitData) { + $commitShas[$commitData['sha']] = $commitData['commit']['committer']['date']; + // go back the commits one-by-one + $treeUrl = $commitData['commit']['tree']['url'].'?recursive=true'; + + // fetch the full tree, then look for the tree for the package path + $treeData = $this->requestGitHubApi($treeUrl); + foreach ($treeData['tree'] as $treeItem) { + if ($treeItem['path'] !== $recipePath) { + continue; + } + + if ($treeItem['sha'] === $lockRef) { + // remove *this* commit from the new commits list + array_pop($commitShas); + + return [ + // shorten for brevity + 'commit' => substr($commitData['sha'], 0, 7), + 'date' => $commitData['commit']['committer']['date'], + 'new_commits' => $commitShas, + ]; + } + } + } + + return null; + } + + public function getVersionsOfRecipe(string $repo, string $branch, string $recipePath): ?array + { + $repositoryName = $this->getRepositoryName($repo); + if (!$repositoryName) { + return null; + } + + $url = sprintf( + 'https://api.github.com/repos/%s/contents/%s?ref=%s', + $repositoryName, + $recipePath, + $branch + ); + $contents = $this->requestGitHubApi($url); + $versions = []; + foreach ($contents as $fileData) { + if ('dir' !== $fileData['type']) { + continue; + } + + $versions[] = $fileData['name']; + } + + return $versions; + } + + public function getCommitDataForPath(string $repo, string $path, string $branch): array + { + $repositoryName = $this->getRepositoryName($repo); + if (!$repositoryName) { + return []; + } + + $commitsData = $this->requestGitHubApi(sprintf( + 'https://api.github.com/repos/%s/commits?path=%s&sha=%s', + $repositoryName, + $path, + $branch + )); + + $data = []; + foreach ($commitsData as $commitData) { + $data[$commitData['sha']] = $commitData['commit']['committer']['date']; + } + + return $data; + } + + public function getPullRequestForCommit(string $commit, string $repo): ?array + { + $data = $this->requestGitHubApi('https://api.github.com/search/issues?q='.$commit.'+is:pull-request'); + + if (0 === \count($data['items'])) { + return null; + } + + $repositoryName = $this->getRepositoryName($repo); + if (!$repositoryName) { + return null; + } + + $bestItem = null; + foreach ($data['items'] as $item) { + // make sure the PR referenced isn't from a different repository + if (false === strpos($item['html_url'], sprintf('%s/pull', $repositoryName))) { + continue; + } + + if (null === $bestItem) { + $bestItem = $item; + + continue; + } + + // find the first PR to reference - avoids rare cases where an invalid + // PR that references *many* commits is first + // e.g. https://api.github.com/search/issues?q=a1a70353f64f405cfbacfc4ce860af623442d6e5 + if ($item['number'] < $bestItem['number']) { + $bestItem = $item; + } + } + + if (!$bestItem) { + return null; + } + + return [ + 'number' => $bestItem['number'], + 'url' => $bestItem['html_url'], + 'title' => $bestItem['title'], + ]; + } + + private function requestGitHubApi(string $path) + { + $contents = $this->downloader->get($path)->getBody(); + + return json_decode($contents, true); + } + + /** + * Converts the "repo" stored in symfony.lock to a repository name. + * + * For example: "github.com/symfony/recipes" => "symfony/recipes" + */ + private function getRepositoryName(string $repo): ?string + { + // only supports public repository placement + if (0 !== strpos($repo, 'github.com')) { + return null; + } + + $parts = explode('/', $repo); + if (3 !== \count($parts)) { + return null; + } + + return implode('/', [$parts[1], $parts[2]]); + } +} diff --git a/vendor/symfony/flex/src/InformationOperation.php b/vendor/symfony/flex/src/InformationOperation.php new file mode 100644 index 0000000..8cad6a8 --- /dev/null +++ b/vendor/symfony/flex/src/InformationOperation.php @@ -0,0 +1,95 @@ + + */ +class InformationOperation implements OperationInterface +{ + private $package; + private $recipeRef = null; + private $version = null; + + public function __construct(PackageInterface $package) + { + $this->package = $package; + } + + /** + * Call to get information about a specific version of a recipe. + * + * Both $recipeRef and $version would normally come from the symfony.lock file. + */ + public function setSpecificRecipeVersion(string $recipeRef, string $version) + { + $this->recipeRef = $recipeRef; + $this->version = $version; + } + + /** + * Returns package instance. + * + * @return PackageInterface + */ + public function getPackage() + { + return $this->package; + } + + public function getRecipeRef(): ?string + { + return $this->recipeRef; + } + + public function getVersion(): ?string + { + return $this->version; + } + + public function getJobType() + { + return 'information'; + } + + /** + * {@inheritdoc} + * + * @return string + */ + public function getOperationType() + { + return 'information'; + } + + /** + * {@inheritdoc} + * + * @return string + */ + public function show($lock) + { + $pretty = method_exists($this->package, 'getFullPrettyVersion') ? $this->package->getFullPrettyVersion() : $this->formatVersion($this->package); + + return 'Information '.$this->package->getPrettyName().' ('.$pretty.')'; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->show(false); + } + + /** + * Compatibility for Composer 1.x, not needed in Composer 2. + */ + public function getReason() + { + return null; + } +} diff --git a/vendor/symfony/flex/src/Lock.php b/vendor/symfony/flex/src/Lock.php new file mode 100644 index 0000000..a39a15a --- /dev/null +++ b/vendor/symfony/flex/src/Lock.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Json\JsonFile; + +/** + * @author Fabien Potencier + */ +class Lock +{ + private $json; + private $lock = []; + private $changed = false; + + public function __construct($lockFile) + { + $this->json = new JsonFile($lockFile); + if ($this->json->exists()) { + $this->lock = $this->json->read(); + } + } + + public function has($name): bool + { + return \array_key_exists($name, $this->lock); + } + + public function add($name, $data) + { + $current = $this->lock[$name] ?? []; + $this->lock[$name] = array_merge($current, $data); + $this->changed = true; + } + + public function get($name) + { + return $this->lock[$name] ?? null; + } + + public function set($name, $data) + { + if (!\array_key_exists($name, $this->lock) || $data !== $this->lock[$name]) { + $this->lock[$name] = $data; + $this->changed = true; + } + } + + public function remove($name) + { + if (\array_key_exists($name, $this->lock)) { + unset($this->lock[$name]); + $this->changed = true; + } + } + + public function write() + { + if (!$this->changed) { + return; + } + + if ($this->lock) { + ksort($this->lock); + $this->json->write($this->lock); + } elseif ($this->json->exists()) { + @unlink($this->json->getPath()); + } + } + + public function delete() + { + @unlink($this->json->getPath()); + } + + public function all(): array + { + return $this->lock; + } +} diff --git a/vendor/symfony/flex/src/Options.php b/vendor/symfony/flex/src/Options.php new file mode 100644 index 0000000..3e41f22 --- /dev/null +++ b/vendor/symfony/flex/src/Options.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\IO\IOInterface; +use Composer\Util\ProcessExecutor; + +/** + * @author Fabien Potencier + */ +class Options +{ + private $options; + private $writtenFiles = []; + private $io; + + public function __construct(array $options = [], ?IOInterface $io = null) + { + $this->options = $options; + $this->io = $io; + } + + public function get(string $name) + { + return $this->options[$name] ?? null; + } + + public function expandTargetDir(string $target): string + { + return preg_replace_callback('{%(.+?)%}', function ($matches) { + $option = str_replace('_', '-', strtolower($matches[1])); + if (!isset($this->options[$option])) { + return $matches[0]; + } + + return rtrim($this->options[$option], '/'); + }, $target); + } + + public function shouldWriteFile(string $file, bool $overwrite): bool + { + if (isset($this->writtenFiles[$file])) { + return false; + } + $this->writtenFiles[$file] = true; + + if (!file_exists($file)) { + return true; + } + + if (!$overwrite) { + return false; + } + + if (!filesize($file)) { + return true; + } + + exec('git status --short --ignored --untracked-files=all -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status); + + if (0 !== $status) { + return $this->io && $this->io->askConfirmation(sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false); + } + + if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) { + return true; + } + + $name = basename($file); + $name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name; + + return $this->io && $this->io->askConfirmation(sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false); + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/vendor/symfony/flex/src/PackageFilter.php b/vendor/symfony/flex/src/PackageFilter.php new file mode 100644 index 0000000..091bcd7 --- /dev/null +++ b/vendor/symfony/flex/src/PackageFilter.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Intervals; +use Composer\Semver\VersionParser; + +/** + * @author Nicolas Grekas + */ +class PackageFilter +{ + private $versions; + private $versionParser; + private $symfonyRequire; + private $symfonyConstraints; + private $downloader; + private $io; + + public function __construct(IOInterface $io, string $symfonyRequire, Downloader $downloader) + { + $this->versionParser = new VersionParser(); + $this->symfonyRequire = $symfonyRequire; + $this->symfonyConstraints = $this->versionParser->parseConstraints($symfonyRequire); + $this->downloader = $downloader; + $this->io = $io; + } + + /** + * @param PackageInterface[] $data + * @param PackageInterface[] $lockedPackages + * + * @return PackageInterface[] + */ + public function removeLegacyPackages(array $data, RootPackageInterface $rootPackage, array $lockedPackages): array + { + if (!$this->symfonyConstraints || !$data) { + return $data; + } + + $lockedVersions = []; + foreach ($lockedPackages as $package) { + $lockedVersions[$package->getName()] = [$package->getVersion()]; + if ($package instanceof AliasPackage) { + $lockedVersions[$package->getName()][] = $package->getAliasOf()->getVersion(); + } + } + + $rootConstraints = []; + foreach ($rootPackage->getRequires() + $rootPackage->getDevRequires() as $name => $link) { + $rootConstraints[$name] = $link->getConstraint(); + } + + $knownVersions = $this->getVersions(); + $filteredPackages = []; + $symfonyPackages = []; + $oneSymfony = false; + foreach ($data as $package) { + $name = $package->getName(); + $versions = [$package->getVersion()]; + if ($package instanceof AliasPackage) { + $versions[] = $package->getAliasOf()->getVersion(); + } + + if ('symfony/symfony' !== $name && ( + !isset($knownVersions['splits'][$name]) + || array_intersect($versions, $lockedVersions[$name] ?? []) + || (isset($rootConstraints[$name]) && !Intervals::haveIntersections($this->symfonyConstraints, $rootConstraints[$name])) + || ('symfony/psr-http-message-bridge' === $name && 6.4 > $versions[0]) + )) { + $filteredPackages[] = $package; + continue; + } + + if (null !== $alias = $package->getExtra()['branch-alias'][$package->getVersion()] ?? null) { + $versions[] = $this->versionParser->normalize($alias); + } + + foreach ($versions as $version) { + if ($this->symfonyConstraints->matches(new Constraint('==', $version))) { + $filteredPackages[] = $package; + $oneSymfony = $oneSymfony || 'symfony/symfony' === $name; + continue 2; + } + } + + if ('symfony/symfony' === $name) { + $symfonyPackages[] = $package; + } elseif (null !== $this->io) { + $this->io->writeError(sprintf('Restricting packages listed in "symfony/symfony" to "%s"', $this->symfonyRequire)); + $this->io = null; + } + } + + if ($symfonyPackages && !$oneSymfony) { + $filteredPackages = array_merge($filteredPackages, $symfonyPackages); + } + + return $filteredPackages; + } + + private function getVersions(): array + { + if (null !== $this->versions) { + return $this->versions; + } + + $versions = $this->downloader->getVersions(); + $this->downloader = null; + $okVersions = []; + + if (!isset($versions['splits'])) { + throw new \LogicException('The Flex index is missing a "splits" entry. Did you forget to add "flex://defaults" in the "extra.symfony.endpoint" array of your composer.json?'); + } + foreach ($versions['splits'] as $name => $vers) { + foreach ($vers as $i => $v) { + if (!isset($okVersions[$v])) { + $okVersions[$v] = false; + $w = '.x' === substr($v, -2) ? $versions['next'] : $v; + + for ($j = 0; $j < 60; ++$j) { + if ($this->symfonyConstraints->matches(new Constraint('==', $w.'.'.$j.'.0'))) { + $okVersions[$v] = true; + break; + } + } + } + + if (!$okVersions[$v]) { + unset($vers[$i]); + } + } + + if (!$vers || $vers === $versions['splits'][$name]) { + unset($versions['splits'][$name]); + } + } + + return $this->versions = $versions; + } +} diff --git a/vendor/symfony/flex/src/PackageJsonSynchronizer.php b/vendor/symfony/flex/src/PackageJsonSynchronizer.php new file mode 100644 index 0000000..698894c --- /dev/null +++ b/vendor/symfony/flex/src/PackageJsonSynchronizer.php @@ -0,0 +1,403 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Semver\Semver; +use Composer\Semver\VersionParser; +use Seld\JsonLint\ParsingException; + +/** + * Synchronize package.json files detected in installed PHP packages with + * the current application. + */ +class PackageJsonSynchronizer +{ + private $rootDir; + private $vendorDir; + private $scriptExecutor; + private $io; + private $versionParser; + + public function __construct(string $rootDir, string $vendorDir, ScriptExecutor $scriptExecutor, IOInterface $io) + { + $this->rootDir = $rootDir; + $this->vendorDir = $vendorDir; + $this->scriptExecutor = $scriptExecutor; + $this->io = $io; + $this->versionParser = new VersionParser(); + } + + public function shouldSynchronize(): bool + { + return $this->rootDir && (file_exists($this->rootDir.'/package.json') || file_exists($this->rootDir.'/importmap.php')); + } + + public function synchronize(array $phpPackages): bool + { + if (file_exists($this->rootDir.'/importmap.php')) { + $this->synchronizeForAssetMapper($phpPackages); + + return false; + } + + try { + JsonFile::parseJson(file_get_contents($this->rootDir.'/package.json')); + } catch (ParsingException $e) { + // if package.json is invalid (possible during a recipe upgrade), we can't update the file + return false; + } + + $didChangePackageJson = $this->removeObsoletePackageJsonLinks(); + + $dependencies = []; + + $phpPackages = $this->normalizePhpPackages($phpPackages); + foreach ($phpPackages as $phpPackage) { + foreach ($this->resolvePackageJsonDependencies($phpPackage) as $dependency => $constraint) { + $dependencies[$dependency][$phpPackage['name']] = $constraint; + } + } + + $didChangePackageJson = $this->registerDependenciesInPackageJson($dependencies) || $didChangePackageJson; + + // Register controllers and entrypoints in controllers.json + $this->updateControllersJsonFile($phpPackages); + + return $didChangePackageJson; + } + + private function synchronizeForAssetMapper(array $phpPackages): void + { + $importMapEntries = []; + $phpPackages = $this->normalizePhpPackages($phpPackages); + foreach ($phpPackages as $phpPackage) { + foreach ($this->resolveImportMapPackages($phpPackage) as $name => $dependencyConfig) { + $importMapEntries[$name] = $dependencyConfig; + } + } + + $this->updateImportMap($importMapEntries); + $this->updateControllersJsonFile($phpPackages); + } + + private function removeObsoletePackageJsonLinks(): bool + { + $didChangePackageJson = false; + + $manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json')); + $content = json_decode($manipulator->getContents(), true); + + $jsDependencies = $content['dependencies'] ?? []; + $jsDevDependencies = $content['devDependencies'] ?? []; + + foreach (['dependencies' => $jsDependencies, 'devDependencies' => $jsDevDependencies] as $key => $packages) { + foreach ($packages as $name => $version) { + if ('@' !== $name[0] || 0 !== strpos($version, 'file:'.$this->vendorDir.'/') || false === strpos($version, '/assets')) { + continue; + } + if (file_exists($this->rootDir.'/'.substr($version, 5).'/package.json')) { + continue; + } + + $manipulator->removeSubNode($key, $name); + $didChangePackageJson = true; + } + } + + file_put_contents($this->rootDir.'/package.json', $manipulator->getContents()); + + return $didChangePackageJson; + } + + private function resolvePackageJsonDependencies($phpPackage): array + { + $dependencies = []; + + if (!$packageJson = $this->resolvePackageJson($phpPackage)) { + return $dependencies; + } + + if ($packageJson->read()['symfony']['needsPackageAsADependency'] ?? true) { + $dependencies['@'.$phpPackage['name']] = 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13); + } + + foreach ($packageJson->read()['peerDependencies'] ?? [] as $peerDependency => $constraint) { + $dependencies[$peerDependency] = $constraint; + } + + return $dependencies; + } + + private function resolveImportMapPackages($phpPackage): array + { + if (!$packageJson = $this->resolvePackageJson($phpPackage)) { + return []; + } + + $dependencies = []; + + foreach ($packageJson->read()['symfony']['importmap'] ?? [] as $importMapName => $constraintConfig) { + if (\is_array($constraintConfig)) { + $constraint = $constraintConfig['version'] ?? []; + $package = $constraintConfig['package'] ?? $importMapName; + } else { + $constraint = $constraintConfig; + $package = $importMapName; + } + + if (0 === strpos($constraint, 'path:')) { + $path = substr($constraint, 5); + $path = str_replace('%PACKAGE%', \dirname($packageJson->getPath()), $path); + + $dependencies[$importMapName] = [ + 'path' => $path, + ]; + + continue; + } + + $dependencies[$importMapName] = [ + 'version' => $constraint, + 'package' => $package, + ]; + } + + return $dependencies; + } + + private function registerDependenciesInPackageJson(array $flexDependencies): bool + { + $didChangePackageJson = false; + + $manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json')); + $content = json_decode($manipulator->getContents(), true); + + foreach ($flexDependencies as $dependency => $constraints) { + if (1 !== \count($constraints) && 1 !== \count(array_count_values($constraints))) { + // If the flex packages have a colliding peer dependency, leave the resolution to the user + continue; + } + + $constraint = array_shift($constraints); + + $parentNode = isset($content['dependencies'][$dependency]) ? 'dependencies' : 'devDependencies'; + if (!isset($content[$parentNode][$dependency])) { + $content['devDependencies'][$dependency] = $constraint; + $didChangePackageJson = true; + } elseif ($constraint !== $content[$parentNode][$dependency]) { + if ($this->shouldUpdateConstraint($content[$parentNode][$dependency], $constraint)) { + $content[$parentNode][$dependency] = $constraint; + $didChangePackageJson = true; + } + } + } + + if ($didChangePackageJson) { + if (isset($content['dependencies'])) { + $manipulator->addMainKey('dependencies', $content['dependencies']); + } + + if (isset($content['devDependencies'])) { + $devDependencies = $content['devDependencies']; + uksort($devDependencies, 'strnatcmp'); + $manipulator->addMainKey('devDependencies', $devDependencies); + } + + $newContents = $manipulator->getContents(); + if ($newContents === file_get_contents($this->rootDir.'/package.json')) { + return false; + } + + file_put_contents($this->rootDir.'/package.json', $manipulator->getContents()); + } + + return $didChangePackageJson; + } + + private function shouldUpdateConstraint(string $existingConstraint, string $constraint) + { + try { + $existingConstraint = $this->versionParser->parseConstraints($existingConstraint); + $constraint = $this->versionParser->parseConstraints($constraint); + + return !$existingConstraint->matches($constraint); + } catch (\UnexpectedValueException $e) { + return true; + } + } + + /** + * @param array $importMapEntries + */ + private function updateImportMap(array $importMapEntries): void + { + if (!$importMapEntries) { + return; + } + + $importMapData = include $this->rootDir.'/importmap.php'; + + foreach ($importMapEntries as $name => $importMapEntry) { + if (isset($importMapData[$name])) { + if (!isset($importMapData[$name]['version'])) { + // AssetMapper 6.3 + continue; + } + + $version = $importMapData[$name]['version']; + $versionConstraint = $importMapEntry['version'] ?? null; + + // if the version constraint is satisfied, skip - else, update the package + if (Semver::satisfies($version, $versionConstraint)) { + continue; + } + + $this->io->writeError(sprintf('Updating package %s from %s to %s.', $name, $version, $versionConstraint)); + } + + if (isset($importMapEntry['path'])) { + $arguments = [$name, '--path='.$importMapEntry['path']]; + $this->scriptExecutor->execute( + 'symfony-cmd', + 'importmap:require', + $arguments + ); + + continue; + } + + if (isset($importMapEntry['version'])) { + $packageName = $importMapEntry['package'].'@'.$importMapEntry['version']; + if ($importMapEntry['package'] !== $name) { + $packageName .= '='.$name; + } + $arguments = [$packageName]; + $this->scriptExecutor->execute( + 'symfony-cmd', + 'importmap:require', + $arguments + ); + + continue; + } + + throw new \InvalidArgumentException(sprintf('Invalid importmap entry: "%s".', var_export($importMapEntry, true))); + } + } + + private function updateControllersJsonFile(array $phpPackages) + { + if (!file_exists($controllersJsonPath = $this->rootDir.'/assets/controllers.json')) { + return; + } + + try { + $previousControllersJson = (new JsonFile($controllersJsonPath))->read(); + } catch (ParsingException $e) { + // if controllers.json is invalid (possible during a recipe upgrade), we can't update the file + return; + } + $newControllersJson = [ + 'controllers' => [], + 'entrypoints' => $previousControllersJson['entrypoints'], + ]; + + foreach ($phpPackages as $phpPackage) { + if (!$packageJson = $this->resolvePackageJson($phpPackage)) { + continue; + } + $name = '@'.$phpPackage['name']; + + foreach ($packageJson->read()['symfony']['controllers'] ?? [] as $controllerName => $defaultConfig) { + // If the package has just been added (no config), add the default config provided by the package + if (!isset($previousControllersJson['controllers'][$name][$controllerName])) { + $config = []; + $config['enabled'] = $defaultConfig['enabled']; + $config['fetch'] = $defaultConfig['fetch'] ?? 'eager'; + + if (isset($defaultConfig['autoimport'])) { + $config['autoimport'] = $defaultConfig['autoimport']; + } + + $newControllersJson['controllers'][$name][$controllerName] = $config; + + continue; + } + + // Otherwise, the package exists: merge new config with user config + $previousConfig = $previousControllersJson['controllers'][$name][$controllerName]; + + $config = []; + $config['enabled'] = $previousConfig['enabled']; + $config['fetch'] = $previousConfig['fetch'] ?? 'eager'; + + if (isset($defaultConfig['autoimport'])) { + $config['autoimport'] = []; + + // Use for each autoimport either the previous config if one existed or the default config otherwise + foreach ($defaultConfig['autoimport'] as $autoimport => $enabled) { + $config['autoimport'][$autoimport] = $previousConfig['autoimport'][$autoimport] ?? $enabled; + } + } + + $newControllersJson['controllers'][$name][$controllerName] = $config; + } + + foreach ($packageJson->read()['symfony']['entrypoints'] ?? [] as $entrypoint => $filename) { + if (!isset($newControllersJson['entrypoints'][$entrypoint])) { + $newControllersJson['entrypoints'][$entrypoint] = $filename; + } + } + } + + file_put_contents($controllersJsonPath, json_encode($newControllersJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n"); + } + + private function resolvePackageJson(array $phpPackage): ?JsonFile + { + $packageDir = $this->rootDir.'/'.$this->vendorDir.'/'.$phpPackage['name']; + + if (!\in_array('symfony-ux', $phpPackage['keywords'] ?? [], true)) { + return null; + } + + foreach (['/assets', '/Resources/assets', '/src/Resources/assets'] as $subdir) { + $packageJsonPath = $packageDir.$subdir.'/package.json'; + + if (!file_exists($packageJsonPath)) { + continue; + } + + return new JsonFile($packageJsonPath); + } + + return null; + } + + private function normalizePhpPackages(array $phpPackages): array + { + foreach ($phpPackages as $k => $phpPackage) { + if (\is_string($phpPackage)) { + // support for smooth upgrades from older flex versions + $phpPackages[$k] = $phpPackage = [ + 'name' => $phpPackage, + 'keywords' => ['symfony-ux'], + ]; + } + } + + return $phpPackages; + } +} diff --git a/vendor/symfony/flex/src/PackageResolver.php b/vendor/symfony/flex/src/PackageResolver.php new file mode 100644 index 0000000..c7a7e5e --- /dev/null +++ b/vendor/symfony/flex/src/PackageResolver.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Factory; +use Composer\Package\Version\VersionParser; +use Composer\Repository\PlatformRepository; + +/** + * @author Fabien Potencier + */ +class PackageResolver +{ + private static $SYMFONY_VERSIONS = ['lts', 'previous', 'stable', 'next', 'dev']; + private $downloader; + + public function __construct(Downloader $downloader) + { + $this->downloader = $downloader; + } + + public function resolve(array $arguments = [], bool $isRequire = false): array + { + // first pass split on : and = to resolve package names + $packages = []; + foreach ($arguments as $i => $argument) { + if ((false !== $pos = strpos($argument, ':')) || (false !== $pos = strpos($argument, '='))) { + $package = $this->resolvePackageName(substr($argument, 0, $pos), $i, $isRequire); + $version = substr($argument, $pos + 1); + $packages[] = $package.':'.$version; + } else { + $packages[] = $this->resolvePackageName($argument, $i, $isRequire); + } + } + + // second pass to resolve versions + $versionParser = new VersionParser(); + $requires = []; + foreach ($versionParser->parseNameVersionPairs($packages) as $package) { + $requires[] = $package['name'].$this->parseVersion($package['name'], $package['version'] ?? '', $isRequire); + } + + return array_unique($requires); + } + + public function parseVersion(string $package, string $version, bool $isRequire): string + { + if (0 !== strpos($package, 'symfony/')) { + return $version ? ':'.$version : ''; + } + + $versions = $this->downloader->getVersions(); + + if (!isset($versions['splits'][$package])) { + return $version ? ':'.$version : ''; + } + + if (!$version || '*' === $version) { + try { + $config = @json_decode(file_get_contents(Factory::getComposerFile()), true); + } finally { + if (!$isRequire || !(isset($config['extra']['symfony']['require']) || isset($config['require']['symfony/framework-bundle']))) { + return ''; + } + } + $version = $config['extra']['symfony']['require'] ?? $config['require']['symfony/framework-bundle']; + } elseif ('dev' === $version) { + $version = '^'.$versions['dev-name'].'@dev'; + } elseif ('next' === $version) { + $version = '^'.$versions[$version].'@dev'; + } elseif (\in_array($version, self::$SYMFONY_VERSIONS, true)) { + $version = '^'.$versions[$version]; + } + + return ':'.$version; + } + + private function resolvePackageName(string $argument, int $position, bool $isRequire): string + { + $skippedPackages = ['mirrors', 'nothing', '']; + + if (!$isRequire) { + $skippedPackages[] = 'lock'; + } + + if (false !== strpos($argument, '/') || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $argument) || preg_match('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $argument) || \in_array($argument, $skippedPackages)) { + return $argument; + } + + $aliases = $this->downloader->getAliases(); + + if (isset($aliases[$argument])) { + $argument = $aliases[$argument]; + } else { + // is it a version or an alias that does not exist? + try { + $versionParser = new VersionParser(); + $versionParser->parseConstraints($argument); + } catch (\UnexpectedValueException $e) { + // is it a special Symfony version? + if (!\in_array($argument, self::$SYMFONY_VERSIONS, true)) { + $this->throwAlternatives($argument, $position); + } + } + } + + return $argument; + } + + /** + * @throws \UnexpectedValueException + */ + private function throwAlternatives(string $argument, int $position) + { + $alternatives = []; + foreach ($this->downloader->getAliases() as $alias => $package) { + $lev = levenshtein($argument, $alias); + if ($lev <= \strlen($argument) / 3 || ('' !== $argument && false !== strpos($alias, $argument))) { + $alternatives[$package][] = $alias; + } + } + + // First position can only be a package name, not a version + if ($alternatives || 0 === $position) { + $message = sprintf('"%s" is not a valid alias.', $argument); + if ($alternatives) { + if (1 === \count($alternatives)) { + $message .= " Did you mean this:\n"; + } else { + $message .= " Did you mean one of these:\n"; + } + foreach ($alternatives as $package => $aliases) { + $message .= sprintf(" \"%s\", supported aliases: \"%s\"\n", $package, implode('", "', $aliases)); + } + } + } else { + $message = sprintf('Could not parse version constraint "%s".', $argument); + } + + throw new \UnexpectedValueException($message); + } +} diff --git a/vendor/symfony/flex/src/Path.php b/vendor/symfony/flex/src/Path.php new file mode 100644 index 0000000..8c7218b --- /dev/null +++ b/vendor/symfony/flex/src/Path.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +/** + * @internal + */ +class Path +{ + private $workingDirectory; + + public function __construct($workingDirectory) + { + $this->workingDirectory = $workingDirectory; + } + + public function relativize(string $absolutePath): string + { + $relativePath = str_replace($this->workingDirectory, '.', $absolutePath); + + return is_dir($absolutePath) ? rtrim($relativePath, '/').'/' : $relativePath; + } + + public function concatenate(array $parts): string + { + $first = array_shift($parts); + + return array_reduce($parts, function (string $initial, string $next): string { + return rtrim($initial, '/').'/'.ltrim($next, '/'); + }, $first); + } +} diff --git a/vendor/symfony/flex/src/Recipe.php b/vendor/symfony/flex/src/Recipe.php new file mode 100644 index 0000000..3c86972 --- /dev/null +++ b/vendor/symfony/flex/src/Recipe.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Package\PackageInterface; + +/** + * @author Fabien Potencier + */ +class Recipe +{ + private $package; + private $name; + private $job; + private $data; + private $lock; + + public function __construct(PackageInterface $package, string $name, string $job, array $data, array $lock = []) + { + $this->package = $package; + $this->name = $name; + $this->job = $job; + $this->data = $data; + $this->lock = $lock; + } + + public function getPackage(): PackageInterface + { + return $this->package; + } + + public function getName(): string + { + return $this->name; + } + + public function getJob(): string + { + return $this->job; + } + + public function getManifest(): array + { + if (!isset($this->data['manifest'])) { + throw new \LogicException(sprintf('Manifest is not available for recipe "%s".', $this->name)); + } + + return $this->data['manifest']; + } + + public function getFiles(): array + { + return $this->data['files'] ?? []; + } + + public function getOrigin(): string + { + return $this->data['origin'] ?? ''; + } + + public function getFormattedOrigin(): string + { + if (!$this->getOrigin()) { + return ''; + } + + // symfony/translation:3.3@github.com/symfony/recipes:branch + if (!preg_match('/^([^:]++):([^@]++)@(.+)$/', $this->getOrigin(), $matches)) { + return $this->getOrigin(); + } + + return sprintf('%s (>=%s): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? ''.$matches[3].'' : $matches[3]); + } + + public function getURL(): string + { + if (!$this->data['origin']) { + return ''; + } + + // symfony/translation:3.3@github.com/symfony/recipes:branch + if (!preg_match('/^([^:]++):([^@]++)@([^:]++):(.+)$/', $this->data['origin'], $matches)) { + // that excludes auto-generated recipes, which is what we want + return ''; + } + + return sprintf('https://%s/tree/%s/%s/%s', $matches[3], $matches[4], $matches[1], $matches[2]); + } + + public function isContrib(): bool + { + return $this->data['is_contrib'] ?? false; + } + + public function getRef() + { + return $this->lock['recipe']['ref'] ?? null; + } + + public function isAuto(): bool + { + return !isset($this->lock['recipe']); + } + + public function getVersion(): string + { + return $this->lock['recipe']['version'] ?? $this->lock['version']; + } + + public function getLock(): array + { + return $this->lock; + } +} diff --git a/vendor/symfony/flex/src/Response.php b/vendor/symfony/flex/src/Response.php new file mode 100644 index 0000000..f334c03 --- /dev/null +++ b/vendor/symfony/flex/src/Response.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +/** + * @author Fabien Potencier + */ +class Response implements \JsonSerializable +{ + private $body; + private $origHeaders; + private $headers; + private $code; + + /** + * @param mixed $body The response as JSON + */ + public function __construct($body, array $headers = [], int $code = 200) + { + $this->body = $body; + $this->origHeaders = $headers; + $this->headers = $this->parseHeaders($headers); + $this->code = $code; + } + + public function getStatusCode(): int + { + return $this->code; + } + + public function getHeader(string $name): string + { + return $this->headers[strtolower($name)][0] ?? ''; + } + + public function getHeaders(string $name): array + { + return $this->headers[strtolower($name)] ?? []; + } + + public function getBody() + { + return $this->body; + } + + public function getOrigHeaders(): array + { + return $this->origHeaders; + } + + public static function fromJson(array $json): self + { + $response = new self($json['body']); + $response->headers = $json['headers']; + + return $response; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ['body' => $this->body, 'headers' => $this->headers]; + } + + private function parseHeaders(array $headers): array + { + $values = []; + foreach (array_reverse($headers) as $header) { + if (preg_match('{^([^:]++):\s*(.+?)\s*$}i', $header, $match)) { + $values[strtolower($match[1])][] = $match[2]; + } elseif (preg_match('{^HTTP/}i', $header)) { + break; + } + } + + return $values; + } +} diff --git a/vendor/symfony/flex/src/ScriptExecutor.php b/vendor/symfony/flex/src/ScriptExecutor.php new file mode 100644 index 0000000..3162248 --- /dev/null +++ b/vendor/symfony/flex/src/ScriptExecutor.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Composer; +use Composer\EventDispatcher\ScriptExecutionException; +use Composer\IO\IOInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * @author Fabien Potencier + */ +class ScriptExecutor +{ + private $composer; + private $io; + private $options; + private $executor; + + public function __construct(Composer $composer, IOInterface $io, Options $options, ?ProcessExecutor $executor = null) + { + $this->composer = $composer; + $this->io = $io; + $this->options = $options; + $this->executor = $executor ?: new ProcessExecutor(); + } + + /** + * @throws ScriptExecutionException if the executed command returns a non-0 exit code + */ + public function execute(string $type, string $cmd, array $arguments = []) + { + $parsedCmd = $this->options->expandTargetDir($cmd); + if (null === $expandedCmd = $this->expandCmd($type, $parsedCmd, $arguments)) { + return; + } + + $cmdOutput = new StreamOutput(fopen('php://temp', 'rw'), OutputInterface::VERBOSITY_VERBOSE, $this->io->isDecorated()); + $outputHandler = function ($type, $buffer) use ($cmdOutput) { + $cmdOutput->write($buffer, false, OutputInterface::OUTPUT_RAW); + }; + + $this->io->writeError(sprintf('Executing script %s', $parsedCmd), $this->io->isVerbose()); + $exitCode = $this->executor->execute($expandedCmd, $outputHandler); + + $code = 0 === $exitCode ? ' [OK]' : ' [KO]'; + + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('Executed script %s %s', $cmd, $code)); + } else { + $this->io->writeError($code); + } + + if (0 !== $exitCode) { + $this->io->writeError(' [KO]'); + $this->io->writeError(sprintf('Script %s returned with error code %s', $cmd, $exitCode)); + fseek($cmdOutput->getStream(), 0); + foreach (explode("\n", stream_get_contents($cmdOutput->getStream())) as $line) { + $this->io->writeError('!! '.$line); + } + + throw new ScriptExecutionException($cmd, $exitCode); + } + } + + private function expandCmd(string $type, string $cmd, array $arguments) + { + switch ($type) { + case 'symfony-cmd': + return $this->expandSymfonyCmd($cmd, $arguments); + case 'php-script': + return $this->expandPhpScript($cmd, $arguments); + case 'script': + return $cmd; + default: + throw new \InvalidArgumentException(sprintf('Invalid symfony/flex auto-script in composer.json: "%s" is not a valid type of command.', $type)); + } + } + + private function expandSymfonyCmd(string $cmd, array $arguments) + { + $repo = $this->composer->getRepositoryManager()->getLocalRepository(); + if (!$repo->findPackage('symfony/console', new MatchAllConstraint())) { + $this->io->writeError(sprintf('Skipping "%s" (needs symfony/console to run).', $cmd)); + + return null; + } + + $console = ProcessExecutor::escape($this->options->get('root-dir').'/'.$this->options->get('bin-dir').'/console'); + if ($this->io->isDecorated()) { + $console .= ' --ansi'; + } + + return $this->expandPhpScript($console.' '.$cmd, $arguments); + } + + private function expandPhpScript(string $cmd, array $scriptArguments): string + { + $phpFinder = new PhpExecutableFinder(); + if (!$php = $phpFinder->find(false)) { + throw new \RuntimeException('The PHP executable could not be found, add it to your PATH and try again.'); + } + + $arguments = $phpFinder->findArguments(); + + if ($env = (string) getenv('COMPOSER_ORIGINAL_INIS')) { + $paths = explode(\PATH_SEPARATOR, $env); + $ini = array_shift($paths); + } else { + $ini = php_ini_loaded_file(); + } + + if ($ini) { + $arguments[] = '--php-ini='.$ini; + } + + if ($memoryLimit = (string) getenv('COMPOSER_MEMORY_LIMIT')) { + $arguments[] = "-d memory_limit={$memoryLimit}"; + } + + $phpArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $arguments)); + $scriptArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $scriptArguments)); + + return ProcessExecutor::escape($php).($phpArgs ? ' '.$phpArgs : '').' '.$cmd.($scriptArgs ? ' '.$scriptArgs : ''); + } +} diff --git a/vendor/symfony/flex/src/SymfonyBundle.php b/vendor/symfony/flex/src/SymfonyBundle.php new file mode 100644 index 0000000..7d1d8a1 --- /dev/null +++ b/vendor/symfony/flex/src/SymfonyBundle.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Composer; +use Composer\Package\PackageInterface; + +/** + * @author Fabien Potencier + */ +class SymfonyBundle +{ + private $package; + private $operation; + private $vendorDir; + + public function __construct(Composer $composer, PackageInterface $package, string $operation) + { + $this->package = $package; + $this->operation = $operation; + $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/'); + } + + public function getClassNames(): array + { + $uninstall = 'uninstall' === $this->operation; + $classes = []; + $autoload = $this->package->getAutoload(); + $isSyliusPlugin = 'sylius-plugin' === $this->package->getType(); + foreach (['psr-4' => true, 'psr-0' => false] as $psr => $isPsr4) { + if (!isset($autoload[$psr])) { + continue; + } + + foreach ($autoload[$psr] as $namespace => $paths) { + if (!\is_array($paths)) { + $paths = [$paths]; + } + foreach ($paths as $path) { + foreach ($this->extractClassNames($namespace, $isSyliusPlugin) as $class) { + // we only check class existence on install as we do have the code available + // in contrast to uninstall operation + if (!$uninstall && !$this->isBundleClass($class, $path, $isPsr4)) { + continue; + } + + $classes[] = $class; + } + } + } + } + + return $classes; + } + + private function extractClassNames(string $namespace, bool $isSyliusPlugin): array + { + $namespace = trim($namespace, '\\'); + $class = $namespace.'\\'; + $parts = explode('\\', $namespace); + $suffix = $parts[\count($parts) - 1]; + $endOfWord = substr($suffix, -6); + + if ($isSyliusPlugin) { + if ('Bundle' !== $endOfWord && 'Plugin' !== $endOfWord) { + $suffix .= 'Bundle'; + } + } elseif ('Bundle' !== $endOfWord) { + $suffix .= 'Bundle'; + } + + $classes = [$class.$suffix]; + $acc = ''; + foreach (\array_slice($parts, 0, -1) as $part) { + if ('Bundle' === $part || ($isSyliusPlugin && 'Plugin' === $part)) { + continue; + } + $classes[] = $class.$part.$suffix; + $acc .= $part; + $classes[] = $class.$acc.$suffix; + } + + return array_unique($classes); + } + + private function isBundleClass(string $class, string $path, bool $isPsr4): bool + { + $classPath = ($this->vendorDir ? $this->vendorDir.'/' : '').$this->package->getPrettyName().'/'.$path.'/'; + $parts = explode('\\', $class); + $class = $parts[\count($parts) - 1]; + if (!$isPsr4) { + $classPath .= str_replace('\\', '', implode('/', \array_slice($parts, 0, -1))).'/'; + } + $classPath .= str_replace('\\', '/', $class).'.php'; + + if (!file_exists($classPath)) { + return false; + } + + // heuristic that should work in almost all cases + $classContents = file_get_contents($classPath); + + return (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\Bundle')) + || (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\AbstractBundle')); + } +} diff --git a/vendor/symfony/flex/src/Unpack/Operation.php b/vendor/symfony/flex/src/Unpack/Operation.php new file mode 100644 index 0000000..1a6efd3 --- /dev/null +++ b/vendor/symfony/flex/src/Unpack/Operation.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Unpack; + +class Operation +{ + private $packages = []; + private $unpack; + private $sort; + + public function __construct(bool $unpack, bool $sort) + { + $this->unpack = $unpack; + $this->sort = $sort; + } + + public function addPackage(string $name, string $version, bool $dev) + { + $this->packages[] = [ + 'name' => $name, + 'version' => $version, + 'dev' => $dev, + ]; + } + + public function getPackages(): array + { + return $this->packages; + } + + public function shouldUnpack(): bool + { + return $this->unpack; + } + + public function shouldSort(): bool + { + return $this->sort; + } +} diff --git a/vendor/symfony/flex/src/Unpack/Result.php b/vendor/symfony/flex/src/Unpack/Result.php new file mode 100644 index 0000000..e352936 --- /dev/null +++ b/vendor/symfony/flex/src/Unpack/Result.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Unpack; + +use Composer\Package\PackageInterface; + +class Result +{ + private $unpacked = []; + private $required = []; + + public function addUnpacked(PackageInterface $package): bool + { + $name = $package->getName(); + + if (!isset($this->unpacked[$name])) { + $this->unpacked[$name] = $package; + + return true; + } + + return false; + } + + /** + * @return PackageInterface[] + */ + public function getUnpacked(): array + { + return $this->unpacked; + } + + public function addRequired(string $package) + { + $this->required[] = $package; + } + + /** + * @return string[] + */ + public function getRequired(): array + { + // we need at least one package for the command to work properly + return $this->required ?: ['symfony/flex']; + } +} diff --git a/vendor/symfony/flex/src/Unpacker.php b/vendor/symfony/flex/src/Unpacker.php new file mode 100644 index 0000000..926a77e --- /dev/null +++ b/vendor/symfony/flex/src/Unpacker.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex; + +use Composer\Composer; +use Composer\Config\JsonConfigSource; +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Package\Locker; +use Composer\Package\Version\VersionSelector; +use Composer\Repository\CompositeRepository; +use Composer\Repository\RepositorySet; +use Composer\Semver\VersionParser; +use Symfony\Flex\Unpack\Operation; +use Symfony\Flex\Unpack\Result; + +class Unpacker +{ + private $composer; + private $resolver; + private $dryRun; + private $versionParser; + + public function __construct(Composer $composer, PackageResolver $resolver, bool $dryRun) + { + $this->composer = $composer; + $this->resolver = $resolver; + $this->dryRun = $dryRun; + $this->versionParser = new VersionParser(); + } + + public function unpack(Operation $op, ?Result $result = null, &$links = [], bool $devRequire = false): Result + { + if (null === $result) { + $result = new Result(); + } + + $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); + foreach ($op->getPackages() as $package) { + $pkg = $localRepo->findPackage($package['name'], '*'); + $pkg = $pkg ?? $this->composer->getRepositoryManager()->findPackage($package['name'], $package['version'] ?: '*'); + + // not unpackable or no --unpack flag or empty packs (markers) + if ( + null === $pkg || + 'symfony-pack' !== $pkg->getType() || + !$op->shouldUnpack() || + 0 === \count($pkg->getRequires()) + \count($pkg->getDevRequires()) + ) { + $result->addRequired($package['name'].($package['version'] ? ':'.$package['version'] : '')); + + continue; + } + + if (!$result->addUnpacked($pkg)) { + continue; + } + + $requires = []; + foreach ($pkg->getRequires() as $link) { + $requires[$link->getTarget()] = $link; + } + $devRequires = $pkg->getDevRequires(); + + foreach ($devRequires as $i => $link) { + if (!isset($requires[$link->getTarget()])) { + throw new \RuntimeException(sprintf('Symfony pack "%s" must duplicate all entries from "require-dev" into "require" but entry "%s" was not found.', $package['name'], $link->getTarget())); + } + $devRequires[$i] = $requires[$link->getTarget()]; + unset($requires[$link->getTarget()]); + } + + $versionSelector = null; + foreach ([$requires, $devRequires] as $dev => $requires) { + $dev = $dev ?: $devRequire ?: $package['dev']; + + foreach ($requires as $link) { + if ('php' === $linkName = $link->getTarget()) { + continue; + } + + $constraint = $link->getPrettyConstraint(); + $constraint = substr($this->resolver->parseVersion($linkName, $constraint, true), 1) ?: $constraint; + + if ($subPkg = $localRepo->findPackage($linkName, '*')) { + if ('symfony-pack' === $subPkg->getType()) { + $subOp = new Operation(true, $op->shouldSort()); + $subOp->addPackage($subPkg->getName(), $constraint, $dev); + $result = $this->unpack($subOp, $result, $links, $dev); + continue; + } + + if ('*' === $constraint) { + if (null === $versionSelector) { + $pool = new RepositorySet($this->composer->getPackage()->getMinimumStability(), $this->composer->getPackage()->getStabilityFlags()); + $pool->addRepository(new CompositeRepository($this->composer->getRepositoryManager()->getRepositories())); + $versionSelector = new VersionSelector($pool); + } + + $constraint = $versionSelector->findRecommendedRequireVersion($subPkg); + } + } + + $linkType = $dev ? 'require-dev' : 'require'; + $constraint = $this->versionParser->parseConstraints($constraint); + + if (isset($links[$linkName])) { + $links[$linkName]['constraints'][] = $constraint; + if ('require' === $linkType) { + $links[$linkName]['type'] = 'require'; + } + } else { + $links[$linkName] = [ + 'type' => $linkType, + 'name' => $linkName, + 'constraints' => [$constraint], + ]; + } + } + } + } + + if ($this->dryRun || 1 < \func_num_args()) { + return $result; + } + + $jsonPath = Factory::getComposerFile(); + $jsonContent = file_get_contents($jsonPath); + $jsonStored = json_decode($jsonContent, true); + $jsonManipulator = new JsonManipulator($jsonContent); + + foreach ($links as $link) { + // nothing to do, package is already present in the "require" section + if (isset($jsonStored['require'][$link['name']])) { + continue; + } + + if (isset($jsonStored['require-dev'][$link['name']])) { + // nothing to do, package is already present in the "require-dev" section + if ('require-dev' === $link['type']) { + continue; + } + + // removes package from "require-dev", because it will be moved to "require" + // save stored constraint + $link['constraints'][] = $this->versionParser->parseConstraints($jsonStored['require-dev'][$link['name']]); + $jsonManipulator->removeSubNode('require-dev', $link['name']); + } + + $constraint = end($link['constraints']); + + if (!$jsonManipulator->addLink($link['type'], $link['name'], $constraint->getPrettyString(), $op->shouldSort())) { + throw new \RuntimeException(sprintf('Unable to unpack package "%s".', $link['name'])); + } + } + + file_put_contents($jsonPath, $jsonManipulator->getContents()); + + return $result; + } + + public function updateLock(Result $result, IOInterface $io): void + { + $json = new JsonFile(Factory::getComposerFile()); + $manipulator = new JsonConfigSource($json); + $locker = $this->composer->getLocker(); + $lockData = $locker->getLockData(); + + foreach ($result->getUnpacked() as $package) { + $manipulator->removeLink('require-dev', $package->getName()); + foreach ($lockData['packages-dev'] as $i => $pkg) { + if ($package->getName() === $pkg['name']) { + unset($lockData['packages-dev'][$i]); + } + } + $manipulator->removeLink('require', $package->getName()); + foreach ($lockData['packages'] as $i => $pkg) { + if ($package->getName() === $pkg['name']) { + unset($lockData['packages'][$i]); + } + } + } + $jsonContent = file_get_contents($json->getPath()); + $lockData['packages'] = array_values($lockData['packages']); + $lockData['packages-dev'] = array_values($lockData['packages-dev']); + $lockData['content-hash'] = Locker::getContentHash($jsonContent); + $lockFile = new JsonFile(substr($json->getPath(), 0, -4).'lock', null, $io); + + if (!$this->dryRun) { + $lockFile->write($lockData); + } + + // force removal of files under vendor/ + $locker = new Locker($io, $lockFile, $this->composer->getInstallationManager(), $jsonContent); + $this->composer->setLocker($locker); + } +} diff --git a/vendor/symfony/flex/src/Update/DiffHelper.php b/vendor/symfony/flex/src/Update/DiffHelper.php new file mode 100644 index 0000000..7072889 --- /dev/null +++ b/vendor/symfony/flex/src/Update/DiffHelper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Update; + +class DiffHelper +{ + public static function removeFilesFromPatch(string $patch, array $files, array &$removedPatches): string + { + foreach ($files as $filename) { + $start = strpos($patch, sprintf('diff --git a/%s b/%s', $filename, $filename)); + if (false === $start) { + throw new \LogicException(sprintf('Could not find file "%s" in the patch.', $filename)); + } + + $end = strpos($patch, 'diff --git a/', $start + 1); + $contentBefore = substr($patch, 0, $start); + if (false === $end) { + // last patch in the file + $removedPatches[$filename] = rtrim(substr($patch, $start), "\n"); + $patch = rtrim($contentBefore, "\n"); + + continue; + } + + $removedPatches[$filename] = rtrim(substr($patch, $start, $end - $start), "\n"); + $patch = $contentBefore.substr($patch, $end); + } + + // valid patches end with a blank line + if ($patch && "\n" !== substr($patch, \strlen($patch) - 1, 1)) { + $patch = $patch."\n"; + } + + return $patch; + } +} diff --git a/vendor/symfony/flex/src/Update/RecipePatch.php b/vendor/symfony/flex/src/Update/RecipePatch.php new file mode 100644 index 0000000..bb6abdb --- /dev/null +++ b/vendor/symfony/flex/src/Update/RecipePatch.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Update; + +class RecipePatch +{ + private $patch; + private $blobs; + private $deletedFiles; + private $removedPatches; + + public function __construct(string $patch, array $blobs, array $deletedFiles, array $removedPatches = []) + { + $this->patch = $patch; + $this->blobs = $blobs; + $this->deletedFiles = $deletedFiles; + $this->removedPatches = $removedPatches; + } + + public function getPatch(): string + { + return $this->patch; + } + + public function getBlobs(): array + { + return $this->blobs; + } + + public function getDeletedFiles(): array + { + return $this->deletedFiles; + } + + /** + * Patches for modified files that were removed because the file + * has been deleted in the user's project. + */ + public function getRemovedPatches(): array + { + return $this->removedPatches; + } +} diff --git a/vendor/symfony/flex/src/Update/RecipePatcher.php b/vendor/symfony/flex/src/Update/RecipePatcher.php new file mode 100644 index 0000000..1de2b34 --- /dev/null +++ b/vendor/symfony/flex/src/Update/RecipePatcher.php @@ -0,0 +1,259 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Update; + +use Composer\IO\IOInterface; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; + +class RecipePatcher +{ + private $rootDir; + private $filesystem; + private $io; + private $processExecutor; + + public function __construct(string $rootDir, IOInterface $io) + { + $this->rootDir = $rootDir; + $this->filesystem = new Filesystem(); + $this->io = $io; + $this->processExecutor = new ProcessExecutor($io); + } + + /** + * Applies the patch. If it fails unexpectedly, an exception will be thrown. + * + * @return bool returns true if fully successful, false if conflicts were encountered + */ + public function applyPatch(RecipePatch $patch): bool + { + $withConflicts = $this->_applyPatchFile($patch); + + foreach ($patch->getDeletedFiles() as $deletedFile) { + if (file_exists($this->rootDir.'/'.$deletedFile)) { + $this->execute(sprintf('git rm %s', ProcessExecutor::escape($deletedFile)), $this->rootDir); + } + } + + return $withConflicts; + } + + public function generatePatch(array $originalFiles, array $newFiles): RecipePatch + { + $ignoredFiles = $this->getIgnoredFiles(array_keys($originalFiles) + array_keys($newFiles)); + + // null implies "file does not exist" + $originalFiles = array_filter($originalFiles, function ($file, $fileName) use ($ignoredFiles) { + return null !== $file && !\in_array($fileName, $ignoredFiles); + }, \ARRAY_FILTER_USE_BOTH); + + $newFiles = array_filter($newFiles, function ($file, $fileName) use ($ignoredFiles) { + return null !== $file && !\in_array($fileName, $ignoredFiles); + }, \ARRAY_FILTER_USE_BOTH); + + $deletedFiles = []; + // find removed files & record that they are deleted + // unset them from originalFiles to avoid unnecessary blobs being added + foreach ($originalFiles as $file => $contents) { + if (!isset($newFiles[$file])) { + $deletedFiles[] = $file; + unset($originalFiles[$file]); + } + } + + // If a file is being modified, but does not exist in the current project, + // it cannot be patched. We generate the diff for these, but then remove + // it from the patch (and optionally report this diff to the user). + $modifiedFiles = array_intersect_key(array_keys($originalFiles), array_keys($newFiles)); + $deletedModifiedFiles = []; + foreach ($modifiedFiles as $modifiedFile) { + if (!file_exists($this->rootDir.'/'.$modifiedFile) && $originalFiles[$modifiedFile] !== $newFiles[$modifiedFile]) { + $deletedModifiedFiles[] = $modifiedFile; + } + } + + // Use git binary to get project path from repository root + $prefix = trim($this->execute('git rev-parse --show-prefix', $this->rootDir)); + $tmpPath = sys_get_temp_dir().'/_flex_recipe_update'.uniqid(mt_rand(), true); + $this->filesystem->mkdir($tmpPath); + + try { + $this->execute('git init', $tmpPath); + $this->execute('git config commit.gpgsign false', $tmpPath); + $this->execute('git config user.name "Flex Updater"', $tmpPath); + $this->execute('git config user.email ""', $tmpPath); + + $blobs = []; + if (\count($originalFiles) > 0) { + $this->writeFiles($originalFiles, $tmpPath); + $this->execute('git add -A', $tmpPath); + $this->execute('git commit -m "original files"', $tmpPath); + + $blobs = $this->generateBlobs($originalFiles, $tmpPath); + } + + $this->writeFiles($newFiles, $tmpPath); + $this->execute('git add -A', $tmpPath); + + $patchString = $this->execute(sprintf('git diff --cached --src-prefix "a/%s" --dst-prefix "b/%s"', $prefix, $prefix), $tmpPath); + $removedPatches = []; + $patchString = DiffHelper::removeFilesFromPatch($patchString, $deletedModifiedFiles, $removedPatches); + + return new RecipePatch( + $patchString, + $blobs, + $deletedFiles, + $removedPatches + ); + } finally { + try { + $this->filesystem->remove($tmpPath); + } catch (IOException $e) { + // this can sometimes fail due to git file permissions + // if that happens, just leave it: we're in the temp directory anyways + } + } + } + + private function writeFiles(array $files, string $directory): void + { + foreach ($files as $filename => $contents) { + $path = $directory.'/'.$filename; + if (null === $contents) { + if (file_exists($path)) { + unlink($path); + } + + continue; + } + + if (!file_exists(\dirname($path))) { + $this->filesystem->mkdir(\dirname($path)); + } + file_put_contents($path, $contents); + } + } + + private function execute(string $command, string $cwd): string + { + $output = ''; + $statusCode = $this->processExecutor->execute($command, $output, $cwd); + + if (0 !== $statusCode) { + throw new \LogicException(sprintf('Command "%s" failed: "%s". Output: "%s".', $command, $this->processExecutor->getErrorOutput(), $output)); + } + + return $output; + } + + /** + * Adds git blobs for each original file. + * + * For patching to work, each original file & contents needs to be + * available to git as a blob. This is because the patch contains + * the ref to the original blob, and git uses that to find the + * original file (which is needed for the 3-way merge). + */ + private function addMissingBlobs(array $blobs): array + { + $addedBlobs = []; + foreach ($blobs as $hash => $contents) { + $blobPath = $this->getBlobPath($this->rootDir, $hash); + if (file_exists($blobPath)) { + continue; + } + + $addedBlobs[] = $blobPath; + if (!file_exists(\dirname($blobPath))) { + $this->filesystem->mkdir(\dirname($blobPath)); + } + file_put_contents($blobPath, $contents); + } + + return $addedBlobs; + } + + private function generateBlobs(array $originalFiles, string $originalFilesRoot): array + { + $addedBlobs = []; + foreach ($originalFiles as $filename => $contents) { + // if the file didn't originally exist, no blob needed + if (!file_exists($originalFilesRoot.'/'.$filename)) { + continue; + } + + $hash = trim($this->execute('git hash-object '.ProcessExecutor::escape($filename), $originalFilesRoot)); + $addedBlobs[$hash] = file_get_contents($this->getBlobPath($originalFilesRoot, $hash)); + } + + return $addedBlobs; + } + + private function getBlobPath(string $gitRoot, string $hash): string + { + $gitDir = trim($this->execute('git rev-parse --absolute-git-dir', $gitRoot)); + + $hashStart = substr($hash, 0, 2); + $hashEnd = substr($hash, 2); + + return $gitDir.'/objects/'.$hashStart.'/'.$hashEnd; + } + + private function _applyPatchFile(RecipePatch $patch) + { + if (!$patch->getPatch()) { + // nothing to do! + return true; + } + + $addedBlobs = $this->addMissingBlobs($patch->getBlobs()); + + $patchPath = $this->rootDir.'/_flex_recipe_update.patch'; + file_put_contents($patchPath, $patch->getPatch()); + + try { + $this->execute('git update-index --refresh', $this->rootDir); + + $output = ''; + $statusCode = $this->processExecutor->execute('git apply "_flex_recipe_update.patch" -3', $output, $this->rootDir); + + if (0 === $statusCode) { + // successful with no conflicts + return true; + } + + if (false !== strpos($this->processExecutor->getErrorOutput(), 'with conflicts')) { + // successful with conflicts + return false; + } + + throw new \LogicException('Error applying the patch: '.$this->processExecutor->getErrorOutput()); + } finally { + unlink($patchPath); + // clean up any temporary blobs + foreach ($addedBlobs as $filename) { + unlink($filename); + } + } + } + + private function getIgnoredFiles(array $fileNames): array + { + $args = implode(' ', array_map([ProcessExecutor::class, 'escape'], $fileNames)); + $output = ''; + $this->processExecutor->execute(sprintf('git check-ignore %s', $args), $output, $this->rootDir); + + return $this->processExecutor->splitLines($output); + } +} diff --git a/vendor/symfony/flex/src/Update/RecipeUpdate.php b/vendor/symfony/flex/src/Update/RecipeUpdate.php new file mode 100644 index 0000000..9944938 --- /dev/null +++ b/vendor/symfony/flex/src/Update/RecipeUpdate.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Update; + +use Symfony\Flex\Lock; +use Symfony\Flex\Recipe; + +class RecipeUpdate +{ + private $originalRecipe; + private $newRecipe; + private $lock; + private $rootDir; + + /** @var string[] */ + private $originalRecipeFiles = []; + /** @var string[] */ + private $newRecipeFiles = []; + private $copyFromPackagePaths = []; + + public function __construct(Recipe $originalRecipe, Recipe $newRecipe, Lock $lock, string $rootDir) + { + $this->originalRecipe = $originalRecipe; + $this->newRecipe = $newRecipe; + $this->lock = $lock; + $this->rootDir = $rootDir; + } + + public function getOriginalRecipe(): Recipe + { + return $this->originalRecipe; + } + + public function getNewRecipe(): Recipe + { + return $this->newRecipe; + } + + public function getLock(): Lock + { + return $this->lock; + } + + public function getRootDir(): string + { + return $this->rootDir; + } + + public function getPackageName(): string + { + return $this->originalRecipe->getName(); + } + + public function setOriginalFile(string $filename, ?string $contents): void + { + $this->originalRecipeFiles[$filename] = $contents; + } + + public function setNewFile(string $filename, ?string $contents): void + { + $this->newRecipeFiles[$filename] = $contents; + } + + public function addOriginalFiles(array $files) + { + foreach ($files as $file => $contents) { + if (null === $contents) { + continue; + } + + $this->setOriginalFile($file, $contents); + } + } + + public function addNewFiles(array $files) + { + foreach ($files as $file => $contents) { + if (null === $contents) { + continue; + } + + $this->setNewFile($file, $contents); + } + } + + public function getOriginalFiles(): array + { + return $this->originalRecipeFiles; + } + + public function getNewFiles(): array + { + return $this->newRecipeFiles; + } + + public function getCopyFromPackagePaths(): array + { + return $this->copyFromPackagePaths; + } + + public function addCopyFromPackagePath(string $source, string $target) + { + $this->copyFromPackagePaths[$source] = $target; + } +} diff --git a/vendor/symfony/form/AbstractExtension.php b/vendor/symfony/form/AbstractExtension.php new file mode 100644 index 0000000..908f4f8 --- /dev/null +++ b/vendor/symfony/form/AbstractExtension.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +abstract class AbstractExtension implements FormExtensionInterface +{ + /** + * The types provided by this extension. + * + * @var FormTypeInterface[] + */ + private array $types; + + /** + * The type extensions provided by this extension. + * + * @var FormTypeExtensionInterface[][] + */ + private array $typeExtensions; + + /** + * The type guesser provided by this extension. + */ + private ?FormTypeGuesserInterface $typeGuesser = null; + + /** + * Whether the type guesser has been loaded. + */ + private bool $typeGuesserLoaded = false; + + public function getType(string $name): FormTypeInterface + { + if (!isset($this->types)) { + $this->initTypes(); + } + + if (!isset($this->types[$name])) { + throw new InvalidArgumentException(sprintf('The type "%s" cannot be loaded by this extension.', $name)); + } + + return $this->types[$name]; + } + + public function hasType(string $name): bool + { + if (!isset($this->types)) { + $this->initTypes(); + } + + return isset($this->types[$name]); + } + + public function getTypeExtensions(string $name): array + { + if (!isset($this->typeExtensions)) { + $this->initTypeExtensions(); + } + + return $this->typeExtensions[$name] + ?? []; + } + + public function hasTypeExtensions(string $name): bool + { + if (!isset($this->typeExtensions)) { + $this->initTypeExtensions(); + } + + return isset($this->typeExtensions[$name]) && \count($this->typeExtensions[$name]) > 0; + } + + public function getTypeGuesser(): ?FormTypeGuesserInterface + { + if (!$this->typeGuesserLoaded) { + $this->initTypeGuesser(); + } + + return $this->typeGuesser; + } + + /** + * Registers the types. + * + * @return FormTypeInterface[] + */ + protected function loadTypes(): array + { + return []; + } + + /** + * Registers the type extensions. + * + * @return FormTypeExtensionInterface[] + */ + protected function loadTypeExtensions(): array + { + return []; + } + + /** + * Registers the type guesser. + */ + protected function loadTypeGuesser(): ?FormTypeGuesserInterface + { + return null; + } + + /** + * Initializes the types. + * + * @throws UnexpectedTypeException if any registered type is not an instance of FormTypeInterface + */ + private function initTypes(): void + { + $this->types = []; + + foreach ($this->loadTypes() as $type) { + if (!$type instanceof FormTypeInterface) { + throw new UnexpectedTypeException($type, FormTypeInterface::class); + } + + $this->types[$type::class] = $type; + } + } + + /** + * Initializes the type extensions. + * + * @throws UnexpectedTypeException if any registered type extension is not + * an instance of FormTypeExtensionInterface + */ + private function initTypeExtensions(): void + { + $this->typeExtensions = []; + + foreach ($this->loadTypeExtensions() as $extension) { + if (!$extension instanceof FormTypeExtensionInterface) { + throw new UnexpectedTypeException($extension, FormTypeExtensionInterface::class); + } + + foreach ($extension::getExtendedTypes() as $extendedType) { + $this->typeExtensions[$extendedType][] = $extension; + } + } + } + + /** + * Initializes the type guesser. + * + * @throws UnexpectedTypeException if the type guesser is not an instance of FormTypeGuesserInterface + */ + private function initTypeGuesser(): void + { + $this->typeGuesserLoaded = true; + + $this->typeGuesser = $this->loadTypeGuesser(); + if (null !== $this->typeGuesser && !$this->typeGuesser instanceof FormTypeGuesserInterface) { + throw new UnexpectedTypeException($this->typeGuesser, FormTypeGuesserInterface::class); + } + } +} diff --git a/vendor/symfony/form/AbstractRendererEngine.php b/vendor/symfony/form/AbstractRendererEngine.php new file mode 100644 index 0000000..1968f5a --- /dev/null +++ b/vendor/symfony/form/AbstractRendererEngine.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Contracts\Service\ResetInterface; + +/** + * Default implementation of {@link FormRendererEngineInterface}. + * + * @author Bernhard Schussek + */ +abstract class AbstractRendererEngine implements FormRendererEngineInterface, ResetInterface +{ + /** + * The variable in {@link FormView} used as cache key. + */ + public const CACHE_KEY_VAR = 'cache_key'; + + /** + * @var array[] + */ + protected array $themes = []; + + /** + * @var bool[] + */ + protected array $useDefaultThemes = []; + + /** + * @var array[] + */ + protected array $resources = []; + + /** + * @var array> + */ + private array $resourceHierarchyLevels = []; + + /** + * Creates a new renderer engine. + * + * @param array $defaultThemes The default themes. The type of these + * themes is open to the implementation. + */ + public function __construct( + protected array $defaultThemes = [], + ) { + } + + public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void + { + $cacheKey = $view->vars[self::CACHE_KEY_VAR]; + + // Do not cast, as casting turns objects into arrays of properties + $this->themes[$cacheKey] = \is_array($themes) ? $themes : [$themes]; + $this->useDefaultThemes[$cacheKey] = $useDefaultThemes; + + // Unset instead of resetting to an empty array, in order to allow + // implementations (like TwigRendererEngine) to check whether $cacheKey + // is set at all. + unset($this->resources[$cacheKey], $this->resourceHierarchyLevels[$cacheKey]); + } + + public function getResourceForBlockName(FormView $view, string $blockName): mixed + { + $cacheKey = $view->vars[self::CACHE_KEY_VAR]; + + if (!isset($this->resources[$cacheKey][$blockName])) { + $this->loadResourceForBlockName($cacheKey, $view, $blockName); + } + + return $this->resources[$cacheKey][$blockName]; + } + + public function getResourceForBlockNameHierarchy(FormView $view, array $blockNameHierarchy, int $hierarchyLevel): mixed + { + $cacheKey = $view->vars[self::CACHE_KEY_VAR]; + $blockName = $blockNameHierarchy[$hierarchyLevel]; + + if (!isset($this->resources[$cacheKey][$blockName])) { + $this->loadResourceForBlockNameHierarchy($cacheKey, $view, $blockNameHierarchy, $hierarchyLevel); + } + + return $this->resources[$cacheKey][$blockName]; + } + + public function getResourceHierarchyLevel(FormView $view, array $blockNameHierarchy, int $hierarchyLevel): int|false + { + $cacheKey = $view->vars[self::CACHE_KEY_VAR]; + $blockName = $blockNameHierarchy[$hierarchyLevel]; + + if (!isset($this->resources[$cacheKey][$blockName])) { + $this->loadResourceForBlockNameHierarchy($cacheKey, $view, $blockNameHierarchy, $hierarchyLevel); + } + + // If $block was previously rendered loaded with loadTemplateForBlock(), the template + // is cached but the hierarchy level is not. In this case, we know that the block + // exists at this very hierarchy level, so we can just set it. + if (!isset($this->resourceHierarchyLevels[$cacheKey][$blockName])) { + $this->resourceHierarchyLevels[$cacheKey][$blockName] = $hierarchyLevel; + } + + return $this->resourceHierarchyLevels[$cacheKey][$blockName]; + } + + /** + * Loads the cache with the resource for a given block name. + * + * @see getResourceForBlock() + */ + abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool; + + /** + * Loads the cache with the resource for a specific level of a block hierarchy. + * + * @see getResourceForBlockHierarchy() + */ + private function loadResourceForBlockNameHierarchy(string $cacheKey, FormView $view, array $blockNameHierarchy, int $hierarchyLevel): bool + { + $blockName = $blockNameHierarchy[$hierarchyLevel]; + + // Try to find a template for that block + if ($this->loadResourceForBlockName($cacheKey, $view, $blockName)) { + // If loadTemplateForBlock() returns true, it was able to populate the + // cache. The only missing thing is to set the hierarchy level at which + // the template was found. + $this->resourceHierarchyLevels[$cacheKey][$blockName] = $hierarchyLevel; + + return true; + } + + if ($hierarchyLevel > 0) { + $parentLevel = $hierarchyLevel - 1; + $parentBlockName = $blockNameHierarchy[$parentLevel]; + + // The next two if statements contain slightly duplicated code. This is by intention + // and tries to avoid execution of unnecessary checks in order to increase performance. + + if (isset($this->resources[$cacheKey][$parentBlockName])) { + // It may happen that the parent block is already loaded, but its level is not. + // In this case, the parent block must have been loaded by loadResourceForBlock(), + // which does not check the hierarchy of the block. Subsequently the block must have + // been found directly on the parent level. + if (!isset($this->resourceHierarchyLevels[$cacheKey][$parentBlockName])) { + $this->resourceHierarchyLevels[$cacheKey][$parentBlockName] = $parentLevel; + } + + // Cache the shortcuts for further accesses + $this->resources[$cacheKey][$blockName] = $this->resources[$cacheKey][$parentBlockName]; + $this->resourceHierarchyLevels[$cacheKey][$blockName] = $this->resourceHierarchyLevels[$cacheKey][$parentBlockName]; + + return true; + } + + if ($this->loadResourceForBlockNameHierarchy($cacheKey, $view, $blockNameHierarchy, $parentLevel)) { + // Cache the shortcuts for further accesses + $this->resources[$cacheKey][$blockName] = $this->resources[$cacheKey][$parentBlockName]; + $this->resourceHierarchyLevels[$cacheKey][$blockName] = $this->resourceHierarchyLevels[$cacheKey][$parentBlockName]; + + return true; + } + } + + // Cache the result for further accesses + $this->resources[$cacheKey][$blockName] = false; + $this->resourceHierarchyLevels[$cacheKey][$blockName] = false; + + return false; + } + + public function reset(): void + { + $this->themes = []; + $this->useDefaultThemes = []; + $this->resources = []; + $this->resourceHierarchyLevels = []; + } +} diff --git a/vendor/symfony/form/AbstractType.php b/vendor/symfony/form/AbstractType.php new file mode 100644 index 0000000..8fffa37 --- /dev/null +++ b/vendor/symfony/form/AbstractType.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Util\StringUtil; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Bernhard Schussek + */ +abstract class AbstractType implements FormTypeInterface +{ + /** + * @return string|null + */ + public function getParent() + { + return FormType::class; + } + + /** + * @return void + */ + public function configureOptions(OptionsResolver $resolver) + { + } + + /** + * @return void + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + } + + /** + * @return void + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + } + + /** + * @return void + */ + public function finishView(FormView $view, FormInterface $form, array $options) + { + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return StringUtil::fqcnToBlockPrefix(static::class) ?: ''; + } +} diff --git a/vendor/symfony/form/AbstractTypeExtension.php b/vendor/symfony/form/AbstractTypeExtension.php new file mode 100644 index 0000000..9f6da0a --- /dev/null +++ b/vendor/symfony/form/AbstractTypeExtension.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Bernhard Schussek + */ +abstract class AbstractTypeExtension implements FormTypeExtensionInterface +{ + /** + * @return void + */ + public function configureOptions(OptionsResolver $resolver): void + { + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + } +} diff --git a/vendor/symfony/form/Button.php b/vendor/symfony/form/Button.php new file mode 100644 index 0000000..c35f93d --- /dev/null +++ b/vendor/symfony/form/Button.php @@ -0,0 +1,369 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\AlreadySubmittedException; +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * A form button. + * + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class Button implements \IteratorAggregate, FormInterface +{ + private ?FormInterface $parent = null; + private bool $submitted = false; + + /** + * Creates a new button from a form configuration. + */ + public function __construct( + private FormConfigInterface $config, + ) { + } + + /** + * Unsupported method. + */ + public function offsetExists(mixed $offset): bool + { + return false; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws BadMethodCallException + */ + public function offsetGet(mixed $offset): FormInterface + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws BadMethodCallException + */ + public function offsetSet(mixed $offset, mixed $value): void + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws BadMethodCallException + */ + public function offsetUnset(mixed $offset): void + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + public function setParent(?FormInterface $parent): static + { + if ($this->submitted) { + throw new AlreadySubmittedException('You cannot set the parent of a submitted button.'); + } + + $this->parent = $parent; + + return $this; + } + + public function getParent(): ?FormInterface + { + return $this->parent; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws BadMethodCallException + */ + public function add(string|FormInterface $child, ?string $type = null, array $options = []): static + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws BadMethodCallException + */ + public function get(string $name): FormInterface + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + */ + public function has(string $name): bool + { + return false; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws BadMethodCallException + */ + public function remove(string $name): static + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + public function all(): array + { + return []; + } + + public function getErrors(bool $deep = false, bool $flatten = true): FormErrorIterator + { + return new FormErrorIterator($this, []); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @return $this + */ + public function setData(mixed $modelData): static + { + // no-op, called during initialization of the form tree + return $this; + } + + /** + * Unsupported method. + */ + public function getData(): mixed + { + return null; + } + + /** + * Unsupported method. + */ + public function getNormData(): mixed + { + return null; + } + + /** + * Unsupported method. + */ + public function getViewData(): mixed + { + return null; + } + + /** + * Unsupported method. + */ + public function getExtraData(): array + { + return []; + } + + /** + * Returns the button's configuration. + */ + public function getConfig(): FormConfigInterface + { + return $this->config; + } + + /** + * Returns whether the button is submitted. + */ + public function isSubmitted(): bool + { + return $this->submitted; + } + + /** + * Returns the name by which the button is identified in forms. + */ + public function getName(): string + { + return $this->config->getName(); + } + + /** + * Unsupported method. + */ + public function getPropertyPath(): ?PropertyPathInterface + { + return null; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function addError(FormError $error): static + { + throw new BadMethodCallException('Buttons cannot have errors.'); + } + + /** + * Unsupported method. + */ + public function isValid(): bool + { + return true; + } + + /** + * Unsupported method. + */ + public function isRequired(): bool + { + return false; + } + + public function isDisabled(): bool + { + if ($this->parent?->isDisabled()) { + return true; + } + + return $this->config->getDisabled(); + } + + /** + * Unsupported method. + */ + public function isEmpty(): bool + { + return true; + } + + /** + * Unsupported method. + */ + public function isSynchronized(): bool + { + return true; + } + + /** + * Unsupported method. + */ + public function getTransformationFailure(): ?TransformationFailedException + { + return null; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function initialize(): static + { + throw new BadMethodCallException('Buttons cannot be initialized. Call initialize() on the root form instead.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function handleRequest(mixed $request = null): static + { + throw new BadMethodCallException('Buttons cannot handle requests. Call handleRequest() on the root form instead.'); + } + + /** + * Submits data to the button. + * + * @return $this + * + * @throws AlreadySubmittedException if the button has already been submitted + */ + public function submit(array|string|null $submittedData, bool $clearMissing = true): static + { + if ($this->submitted) { + throw new AlreadySubmittedException('A form can only be submitted once.'); + } + + $this->submitted = true; + + return $this; + } + + public function getRoot(): FormInterface + { + return $this->parent ? $this->parent->getRoot() : $this; + } + + public function isRoot(): bool + { + return null === $this->parent; + } + + public function createView(?FormView $parent = null): FormView + { + if (null === $parent && $this->parent) { + $parent = $this->parent->createView(); + } + + $type = $this->config->getType(); + $options = $this->config->getOptions(); + + $view = $type->createView($this, $parent); + + $type->buildView($view, $this, $options); + $type->finishView($view, $this, $options); + + return $view; + } + + /** + * Unsupported method. + */ + public function count(): int + { + return 0; + } + + /** + * Unsupported method. + */ + public function getIterator(): \EmptyIterator + { + return new \EmptyIterator(); + } +} diff --git a/vendor/symfony/form/ButtonBuilder.php b/vendor/symfony/form/ButtonBuilder.php new file mode 100644 index 0000000..4f70791 --- /dev/null +++ b/vendor/symfony/form/ButtonBuilder.php @@ -0,0 +1,669 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * A builder for {@link Button} instances. + * + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface +{ + protected bool $locked = false; + + private bool $disabled = false; + private ResolvedFormTypeInterface $type; + private string $name; + private array $attributes = []; + + /** + * @throws InvalidArgumentException if the name is empty + */ + public function __construct( + ?string $name, + private array $options = [], + ) { + if ('' === $name || null === $name) { + throw new InvalidArgumentException('Buttons cannot have empty names.'); + } + + $this->name = $name; + + FormConfigBuilder::validateName($name); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function add(string|FormBuilderInterface $child, ?string $type = null, array $options = []): never + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function create(string $name, ?string $type = null, array $options = []): never + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function get(string $name): never + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function remove(string $name): never + { + throw new BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + */ + public function has(string $name): bool + { + return false; + } + + /** + * Returns the children. + */ + public function all(): array + { + return []; + } + + /** + * Creates the button. + */ + public function getForm(): Button + { + return new Button($this->getFormConfig()); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function addEventListener(string $eventName, callable $listener, int $priority = 0): never + { + throw new BadMethodCallException('Buttons do not support event listeners.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function addEventSubscriber(EventSubscriberInterface $subscriber): never + { + throw new BadMethodCallException('Buttons do not support event subscribers.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function resetViewTransformers(): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function resetModelTransformers(): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * @return $this + */ + public function setAttribute(string $name, mixed $value): static + { + $this->attributes[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function setAttributes(array $attributes): static + { + $this->attributes = $attributes; + + return $this; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setDataMapper(?DataMapperInterface $dataMapper): never + { + throw new BadMethodCallException('Buttons do not support data mappers.'); + } + + /** + * Set whether the button is disabled. + * + * @return $this + */ + public function setDisabled(bool $disabled): static + { + $this->disabled = $disabled; + + return $this; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setEmptyData(mixed $emptyData): never + { + throw new BadMethodCallException('Buttons do not support empty data.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setErrorBubbling(bool $errorBubbling): never + { + throw new BadMethodCallException('Buttons do not support error bubbling.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setRequired(bool $required): never + { + throw new BadMethodCallException('Buttons cannot be required.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): never + { + throw new BadMethodCallException('Buttons do not support property paths.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setMapped(bool $mapped): never + { + throw new BadMethodCallException('Buttons do not support data mapping.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setByReference(bool $byReference): never + { + throw new BadMethodCallException('Buttons do not support data mapping.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setCompound(bool $compound): never + { + throw new BadMethodCallException('Buttons cannot be compound.'); + } + + /** + * Sets the type of the button. + * + * @return $this + */ + public function setType(ResolvedFormTypeInterface $type): static + { + $this->type = $type; + + return $this; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setData(mixed $data): never + { + throw new BadMethodCallException('Buttons do not support data.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setDataLocked(bool $locked): never + { + throw new BadMethodCallException('Buttons do not support data locking.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setFormFactory(FormFactoryInterface $formFactory): never + { + throw new BadMethodCallException('Buttons do not support form factories.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setAction(string $action): never + { + throw new BadMethodCallException('Buttons do not support actions.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setMethod(string $method): never + { + throw new BadMethodCallException('Buttons do not support methods.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setRequestHandler(RequestHandlerInterface $requestHandler): never + { + throw new BadMethodCallException('Buttons do not support request handlers.'); + } + + /** + * Unsupported method. + * + * @return $this + * + * @throws BadMethodCallException + */ + public function setAutoInitialize(bool $initialize): static + { + if (true === $initialize) { + throw new BadMethodCallException('Buttons do not support automatic initialization.'); + } + + return $this; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setInheritData(bool $inheritData): never + { + throw new BadMethodCallException('Buttons do not support data inheritance.'); + } + + /** + * Builds and returns the button configuration. + */ + public function getFormConfig(): FormConfigInterface + { + // This method should be idempotent, so clone the builder + $config = clone $this; + $config->locked = true; + + return $config; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function setIsEmptyCallback(?callable $isEmptyCallback): never + { + throw new BadMethodCallException('Buttons do not support "is empty" callback.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function getEventDispatcher(): never + { + throw new BadMethodCallException('Buttons do not support event dispatching.'); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Unsupported method. + */ + public function getPropertyPath(): ?PropertyPathInterface + { + return null; + } + + /** + * Unsupported method. + */ + public function getMapped(): bool + { + return false; + } + + /** + * Unsupported method. + */ + public function getByReference(): bool + { + return false; + } + + /** + * Unsupported method. + */ + public function getCompound(): bool + { + return false; + } + + /** + * Returns the form type used to construct the button. + */ + public function getType(): ResolvedFormTypeInterface + { + return $this->type; + } + + /** + * Unsupported method. + */ + public function getViewTransformers(): array + { + return []; + } + + /** + * Unsupported method. + */ + public function getModelTransformers(): array + { + return []; + } + + /** + * Unsupported method. + */ + public function getDataMapper(): ?DataMapperInterface + { + return null; + } + + /** + * Unsupported method. + */ + public function getRequired(): bool + { + return false; + } + + /** + * Returns whether the button is disabled. + */ + public function getDisabled(): bool + { + return $this->disabled; + } + + /** + * Unsupported method. + */ + public function getErrorBubbling(): bool + { + return false; + } + + /** + * Unsupported method. + */ + public function getEmptyData(): mixed + { + return null; + } + + /** + * Returns additional attributes of the button. + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * Returns whether the attribute with the given name exists. + */ + public function hasAttribute(string $name): bool + { + return \array_key_exists($name, $this->attributes); + } + + /** + * Returns the value of the given attribute. + */ + public function getAttribute(string $name, mixed $default = null): mixed + { + return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * Unsupported method. + */ + public function getData(): mixed + { + return null; + } + + /** + * Unsupported method. + */ + public function getDataClass(): ?string + { + return null; + } + + /** + * Unsupported method. + */ + public function getDataLocked(): bool + { + return false; + } + + /** + * Unsupported method. + */ + public function getFormFactory(): never + { + throw new BadMethodCallException('Buttons do not support adding children.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function getAction(): never + { + throw new BadMethodCallException('Buttons do not support actions.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function getMethod(): never + { + throw new BadMethodCallException('Buttons do not support methods.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function getRequestHandler(): never + { + throw new BadMethodCallException('Buttons do not support request handlers.'); + } + + /** + * Unsupported method. + */ + public function getAutoInitialize(): bool + { + return false; + } + + /** + * Unsupported method. + */ + public function getInheritData(): bool + { + return false; + } + + /** + * Returns all options passed during the construction of the button. + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * Returns whether a specific option exists. + */ + public function hasOption(string $name): bool + { + return \array_key_exists($name, $this->options); + } + + /** + * Returns the value of a specific option. + */ + public function getOption(string $name, mixed $default = null): mixed + { + return \array_key_exists($name, $this->options) ? $this->options[$name] : $default; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function getIsEmptyCallback(): never + { + throw new BadMethodCallException('Buttons do not support "is empty" callback.'); + } + + /** + * Unsupported method. + */ + public function count(): int + { + return 0; + } + + /** + * Unsupported method. + */ + public function getIterator(): \EmptyIterator + { + return new \EmptyIterator(); + } +} diff --git a/vendor/symfony/form/ButtonTypeInterface.php b/vendor/symfony/form/ButtonTypeInterface.php new file mode 100644 index 0000000..dd5117c --- /dev/null +++ b/vendor/symfony/form/ButtonTypeInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A type that should be converted into a {@link Button} instance. + * + * @author Bernhard Schussek + */ +interface ButtonTypeInterface extends FormTypeInterface +{ +} diff --git a/vendor/symfony/form/CHANGELOG.md b/vendor/symfony/form/CHANGELOG.md new file mode 100644 index 0000000..0420af3 --- /dev/null +++ b/vendor/symfony/form/CHANGELOG.md @@ -0,0 +1,632 @@ +CHANGELOG +========= + +7.1 +--- + + * Add option `separator` to `ChoiceType` to use a custom separator after preferred choices (use the new `separator_html` option to display the separator text as HTML) + * Deprecate not configuring the `default_protocol` option of the `UrlType`, it will default to `null` in 8.0 (the current default is `'http'`) + * Add a `keep_as_list` option to `CollectionType` + * Add an `input` option to `MoneyType`, to be able to cast the transformed value to `integer` + +7.0 +--- + + * Throw when using `DateTime` or `DateTimeImmutable` model data with a different timezone than configured with the + `model_timezone` option in `DateType`, `DateTimeType`, and `TimeType` + * Make the "widget" option of date/time form types default to "single_text" + * Require explicit argument when calling `Button/Form::setParent()`, `ButtonBuilder/FormConfigBuilder::setDataMapper()`, `TransformationFailedException::setInvalidMessage()` + +6.4 +--- + + * Deprecate using `DateTime` or `DateTimeImmutable` model data with a different timezone than configured with the + `model_timezone` option in `DateType`, `DateTimeType`, and `TimeType` + * Deprecate `PostSetDataEvent::setData()`, use `PreSetDataEvent::setData()` instead + * Deprecate `PostSubmitEvent::setData()`, use `PreSubmitDataEvent::setData()` or `SubmitDataEvent::setData()` instead + * Add `duplicate_preferred_choices` option in `ChoiceType` + * Add `$duplicatePreferredChoices` parameter to `ChoiceListFactoryInterface::createView()` + +6.3 +--- + + * Don't render seconds for HTML5 date pickers unless "with_seconds" is explicitly set + * Add a `placeholder_attr` option to `ChoiceType` + * Deprecate not configuring the "widget" option of date/time form types, it will default to "single_text" in v7 + +6.2 +--- + + * Allow passing `TranslatableInterface` objects to the `ChoiceView` label + * Allow passing `TranslatableInterface` objects to the `help` option + * Deprecate calling `Button/Form::setParent()`, `ButtonBuilder/FormConfigBuilder::setDataMapper()`, `TransformationFailedException::setInvalidMessage()` without arguments + * Change the signature of `FormConfigBuilderInterface::setDataMapper()` to `setDataMapper(?DataMapperInterface)` + * Change the signature of `FormInterface::setParent()` to `setParent(?self)` + * Add `PasswordHasherExtension` with support for `hash_property_path` option in `PasswordType` + +6.1 +--- + + * Add a `prototype_options` option to `CollectionType` + +6.0 +--- + + * Remove `PropertyPathMaper` + * Remove `Symfony\Component\Form\Extension\Validator\Util\ServerParams` + * Remove `FormPass` configuration + * Remove the `NumberToLocalizedStringTransformer::ROUND_*` constants, use `\NumberFormatter::ROUND_*` instead + * The `rounding_mode` option of the `PercentType` defaults to `\NumberFormatter::ROUND_HALFUP` + * The rounding mode argument of the constructor of `PercentToLocalizedStringTransformer` defaults to `\NumberFormatter::ROUND_HALFUP` + * Add `FormConfigInterface::getIsEmptyCallback()` and `FormConfigBuilderInterface::setIsEmptyCallback()` + * Change `$forms` parameter type of the `DataMapper::mapDataToForms()` method from `iterable` to `\Traversable` + * Change `$forms` parameter type of the `DataMapper::mapFormsToData()` method from `iterable` to `\Traversable` + * Change `$checkboxes` parameter type of the `CheckboxListMapper::mapDataToForms()` method from `iterable` to `\Traversable` + * Change `$checkboxes` parameter type of the `CheckboxListMapper::mapFormsToData()` method from `iterable` to `\Traversable` + * Change `$radios` parameter type of the `RadioListMapper::mapDataToForms()` method from `iterable` to `\Traversable` + * Change `$radios` parameter type of the `RadioListMapper::mapFormsToData()` method from `iterable` to `\Traversable` + +5.4 +--- + + * Deprecate calling `FormErrorIterator::children()` if the current element is not iterable. + * Allow to pass `TranslatableMessage` objects to the `help` option + * Add the `EnumType` + +5.3 +--- + + * Changed `$forms` parameter type of the `DataMapperInterface::mapDataToForms()` method from `iterable` to `\Traversable`. + * Changed `$forms` parameter type of the `DataMapperInterface::mapFormsToData()` method from `iterable` to `\Traversable`. + * Deprecated passing an array as the second argument of the `DataMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `DataMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Deprecated passing an array as the second argument of the `CheckboxListMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead. + * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Added a `choice_translation_parameters` option to `ChoiceType` + * Add `UuidType` and `UlidType` + * Dependency on `symfony/intl` was removed. Install `symfony/intl` if you are using `LocaleType`, `CountryType`, `CurrencyType`, `LanguageType` or `TimezoneType`. + * Add `priority` option to `BaseType` and sorting view fields + +5.2.0 +----- + + * Added support for using the `{{ label }}` placeholder in constraint messages, which is replaced in the `ViolationMapper` by the corresponding field form label. + * Added `DataMapper`, `ChainAccessor`, `PropertyPathAccessor` and `CallbackAccessor` with new callable `getter` and `setter` options for each form type + * Deprecated `PropertyPathMapper` in favor of `DataMapper` and `PropertyPathAccessor` + * Added an `html5` option to `MoneyType` and `PercentType`, to use `` + +5.1.0 +----- + + * Deprecated not configuring the `rounding_mode` option of the `PercentType`. It will default to `\NumberFormatter::ROUND_HALFUP` in Symfony 6. + * Deprecated not passing a rounding mode to the constructor of `PercentToLocalizedStringTransformer`. It will default to `\NumberFormatter::ROUND_HALFUP` in Symfony 6. + * Added `collection_entry` block prefix to `CollectionType` entries + * Added a `choice_filter` option to `ChoiceType` + * Added argument `callable|null $filter` to `ChoiceListFactoryInterface::createListFromChoices()` and `createListFromLoader()` - not defining them is deprecated. + * Added a `ChoiceList` facade to leverage explicit choice list caching based on options + * Added an `AbstractChoiceLoader` to simplify implementations and handle global optimizations + * The `view_timezone` option defaults to the `model_timezone` if no `reference_date` is configured. + * Implementing the `FormConfigInterface` without implementing the `getIsEmptyCallback()` method + is deprecated. The method will be added to the interface in 6.0. + * Implementing the `FormConfigBuilderInterface` without implementing the `setIsEmptyCallback()` method + is deprecated. The method will be added to the interface in 6.0. + * Added a `rounding_mode` option for the PercentType and correctly round the value when submitted + * Deprecated `Symfony\Component\Form\Extension\Validator\Util\ServerParams` in favor of its parent class `Symfony\Component\Form\Util\ServerParams` + * Added the `html5` option to the `ColorType` to validate the input + * Deprecated `NumberToLocalizedStringTransformer::ROUND_*` constants, use `\NumberFormatter::ROUND_*` instead + +5.0.0 +----- + + * Removed support for using different values for the "model_timezone" and "view_timezone" options of the `TimeType` + without configuring a reference date. + * Removed the `scale` option of the `IntegerType`. + * Using the `date_format`, `date_widget`, and `time_widget` options of the `DateTimeType` when the `widget` option is + set to `single_text` is not supported anymore. + * The `format` option of `DateType` and `DateTimeType` cannot be used when the `html5` option is enabled. + * Using names for buttons that do not start with a letter, a digit, or an underscore throw an exception + * Using names for buttons that do not contain only letters, digits, underscores, hyphens, and colons throw an exception. + * removed the `ChoiceLoaderInterface` implementation in `CountryType`, `LanguageType`, `LocaleType` and `CurrencyType` + * removed `getExtendedType()` method of the `FormTypeExtensionInterface` + * added static `getExtendedTypes()` method to the `FormTypeExtensionInterface` + * calling to `FormRenderer::searchAndRenderBlock()` method for fields which were already rendered throw a `BadMethodCallException` + * removed the `regions` option of the `TimezoneType` + * removed the `$scale` argument of the `IntegerToLocalizedStringTransformer` + * removed `TemplatingExtension` and `TemplatingRendererEngine` classes, use Twig instead + * passing a null message when instantiating a `Symfony\Component\Form\FormError` is not allowed + * removed support for using `int` or `float` as data for the `NumberType` when the `input` option is set to `string` + +4.4.0 +----- + + * add new `WeekType` + * using different values for the "model_timezone" and "view_timezone" options of the `TimeType` without configuring a + reference date is deprecated + * preferred choices are repeated in the list of all choices + * deprecated using `int` or `float` as data for the `NumberType` when the `input` option is set to `string` + * The type guesser guesses the HTML accept attribute when a mime type is configured in the File or Image constraint. + * Overriding the methods `FormIntegrationTestCase::setUp()`, `TypeTestCase::setUp()` and `TypeTestCase::tearDown()` without the `void` return-type is deprecated. + * marked all dispatched event classes as `@final` + * Added the `validate` option to `SubmitType` to toggle the browser built-in form validation. + * Added the `alpha3` option to `LanguageType` and `CountryType` to use alpha3 instead of alpha2 codes + +4.3.0 +----- + + * added a `symbol` option to the `PercentType` that allows to disable or customize the output of the percent character + * Using the `format` option of `DateType` and `DateTimeType` when the `html5` option is enabled is deprecated. + * Using names for buttons that do not start with a letter, a digit, or an underscore is deprecated and will lead to an + exception in 5.0. + * Using names for buttons that do not contain only letters, digits, underscores, hyphens, and colons is deprecated and + will lead to an exception in 5.0. + * added `html5` option to `NumberType` that allows to render `type="number"` input fields + * deprecated using the `date_format`, `date_widget`, and `time_widget` options of the `DateTimeType` when the `widget` + option is set to `single_text` + * added `block_prefix` option to `BaseType`. + * added `help_html` option to display the `help` text as HTML. + * `FormError` doesn't implement `Serializable` anymore + * `FormDataCollector` has been marked as `final` + * added `label_translation_parameters`, `attr_translation_parameters`, `help_translation_parameters` options + to `FormType` to pass translation parameters to form labels, attributes (`placeholder` and `title`) and help text respectively. + The passed parameters will replace placeholders in translation messages. + + ```php + class OrderType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('comment', TextType::class, [ + 'label' => 'Comment to the order to %company%', + 'label_translation_parameters' => [ + '%company%' => 'Acme', + ], + 'help' => 'The address of the %company% is %address%', + 'help_translation_parameters' => [ + '%company%' => 'Acme Ltd.', + '%address%' => '4 Form street, Symfonyville', + ], + ]) + } + } + ``` + * added the `input_format` option to `DateType`, `DateTimeType`, and `TimeType` to specify the input format when setting + the `input` option to `string` + * dispatch `PreSubmitEvent` on `form.pre_submit` + * dispatch `SubmitEvent` on `form.submit` + * dispatch `PostSubmitEvent` on `form.post_submit` + * dispatch `PreSetDataEvent` on `form.pre_set_data` + * dispatch `PostSetDataEvent` on `form.post_set_data` + * added an `input` option to `NumberType` + * removed default option grouping in `TimezoneType`, use `group_by` instead + +4.2.0 +----- + + * The `getExtendedType()` method of the `FormTypeExtensionInterface` is deprecated and will be removed in 5.0. Type + extensions must implement the static `getExtendedTypes()` method instead and return an iterable of extended types. + + Before: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + + // ... + } + ``` + + After: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } + + // ... + } + ``` + * deprecated the `$scale` argument of the `IntegerToLocalizedStringTransformer` + * added `Symfony\Component\Form\ClearableErrorsInterface` + * deprecated calling `FormRenderer::searchAndRenderBlock` for fields which were already rendered + * added a cause when a CSRF error has occurred + * deprecated the `scale` option of the `IntegerType` + * removed restriction on allowed HTTP methods + * deprecated the `regions` option of the `TimezoneType` + +4.1.0 +----- + + * added `input=datetime_immutable` to `DateType`, `TimeType`, `DateTimeType` + * added `rounding_mode` option to `MoneyType` + * added `choice_translation_locale` option to `CountryType`, `LanguageType`, `LocaleType` and `CurrencyType` + * deprecated the `ChoiceLoaderInterface` implementation in `CountryType`, `LanguageType`, `LocaleType` and `CurrencyType` + * added `input=datetime_immutable` to DateType, TimeType, DateTimeType + * added `rounding_mode` option to MoneyType + +4.0.0 +----- + + * using the `choices` option in `CountryType`, `CurrencyType`, `LanguageType`, + `LocaleType`, and `TimezoneType` when the `choice_loader` option is not `null` + is not supported anymore and the configured choices will be ignored + * callable strings that are passed to the options of the `ChoiceType` are + treated as property paths + * the `choices_as_values` option of the `ChoiceType` has been removed + * removed the support for caching loaded choice lists in `LazyChoiceList`, + cache the choice list in the used `ChoiceLoaderInterface` implementation + instead + * removed the support for objects implementing both `\Traversable` and `\ArrayAccess` in `ResizeFormListener::preSubmit()` + * removed the ability to use `FormDataCollector` without the `symfony/var-dumper` component + * removed passing a `ValueExporter` instance to the `FormDataExtractor::__construct()` method + * removed passing guesser services ids as the fourth argument of `DependencyInjectionExtension::__construct()` + * removed the ability to validate an unsubmitted form. + * removed `ChoiceLoaderInterface` implementation in `TimezoneType` + * added the `false_values` option to the `CheckboxType` which allows to configure custom values which will be treated as `false` during submission + +3.4.0 +----- + + * added `DebugCommand` + * deprecated `ChoiceLoaderInterface` implementation in `TimezoneType` + * added options "input" and "regions" to `TimezoneType` + * added an option to ``Symfony\Component\Form\FormRendererEngineInterface::setTheme()`` and + ``Symfony\Component\Form\FormRendererInterface::setTheme()`` to disable usage of default themes when rendering a form + +3.3.0 +----- + + * deprecated using "choices" option in ``CountryType``, ``CurrencyType``, ``LanguageType``, ``LocaleType``, and + ``TimezoneType`` when "choice_loader" is not ``null`` + * added `Symfony\Component\Form\FormErrorIterator::findByCodes()` + * added `getTypedExtensions`, `getTypes`, and `getTypeGuessers` to `Symfony\Component\Form\Test\FormIntegrationTestCase` + * added `FormPass` + +3.2.0 +----- + + * added `CallbackChoiceLoader` + * implemented `ChoiceLoaderInterface` in children of `ChoiceType` + +3.1.0 +----- + + * deprecated the "choices_as_values" option of ChoiceType + * deprecated support for data objects that implements both `Traversable` and + `ArrayAccess` in `ResizeFormListener::preSubmit` method + * Using callable strings as choice options in `ChoiceType` has been deprecated + and will be used as `PropertyPath` instead of callable in Symfony 4.0. + * implemented `DataTransformerInterface` in `TextType` + * deprecated caching loaded choice list in `LazyChoiceList::$loadedList` + +3.0.0 +----- + + * removed `FormTypeInterface::setDefaultOptions()` method + * removed `AbstractType::setDefaultOptions()` method + * removed `FormTypeExtensionInterface::setDefaultOptions()` method + * removed `AbstractTypeExtension::setDefaultOptions()` method + * added `FormTypeInterface::configureOptions()` method + * added `FormTypeExtensionInterface::configureOptions()` method + +2.8.0 +----- + + * added option "choice_translation_domain" to DateType, TimeType and DateTimeType. + * deprecated option "read_only" in favor of "attr['readonly']" + * added the html5 "range" FormType + * deprecated the "cascade_validation" option in favor of setting "constraints" + with the Valid constraint + * moved data trimming logic of TrimListener into StringUtil + * [BC BREAK] When registering a type extension through the DI extension, the tag alias has to match the actual extended type. + +2.7.38 +------ + + * [BC BREAK] the `isFileUpload()` method was added to the `RequestHandlerInterface` + +2.7.0 +----- + + * added option "choice_translation_domain" to ChoiceType. + * deprecated option "precision" in favor of "scale" + * deprecated the overwriting of AbstractType::setDefaultOptions() in favor of overwriting AbstractType::configureOptions(). + * deprecated the overwriting of AbstractTypeExtension::setDefaultOptions() in favor of overwriting AbstractTypeExtension::configureOptions(). + * added new ChoiceList interface and implementations in the Symfony\Component\Form\ChoiceList namespace + * added new ChoiceView in the Symfony\Component\Form\ChoiceList\View namespace + * choice groups are now represented by ChoiceGroupView objects in the view + * deprecated the old ChoiceList interface and implementations + * deprecated the old ChoiceView class + * added CheckboxListMapper and RadioListMapper + * deprecated ChoiceToBooleanArrayTransformer and ChoicesToBooleanArrayTransformer + * deprecated FixCheckboxInputListener and FixRadioInputListener + * deprecated the "choice_list" option of ChoiceType + * added new options to ChoiceType: + * "choices_as_values" + * "choice_loader" + * "choice_label" + * "choice_name" + * "choice_value" + * "choice_attr" + * "group_by" + +2.6.2 +----- + + * Added back the `model_timezone` and `view_timezone` options for `TimeType`, `DateType` + and `BirthdayType` + +2.6.0 +----- + + * added "html5" option to Date, Time and DateTimeFormType to be able to + enable/disable HTML5 input date when widget option is "single_text" + * added "label_format" option with possible placeholders "%name%" and "%id%" + * [BC BREAK] drop support for model_timezone and view_timezone options in TimeType, DateType and BirthdayType, + update to 2.6.2 to get back support for these options + +2.5.0 +------ + + * deprecated options "max_length" and "pattern" in favor of putting these values in "attr" option + * added an option for multiple files upload + * form errors now reference their cause (constraint violation, exception, ...) + * form errors now remember which form they were originally added to + * [BC BREAK] added two optional parameters to FormInterface::getErrors() and + changed the method to return a Symfony\Component\Form\FormErrorIterator + instance instead of an array + * errors mapped to unsubmitted forms are discarded now + * ObjectChoiceList now compares choices by their value, if a value path is + given + * you can now pass interface names in the "data_class" option + * [BC BREAK] added `FormInterface::getTransformationFailure()` + +2.4.0 +----- + + * moved CSRF implementation to the new Security CSRF sub-component + * deprecated CsrfProviderInterface and its implementations + * deprecated options "csrf_provider" and "intention" in favor of the new options "csrf_token_manager" and "csrf_token_id" + +2.3.0 +----- + + * deprecated FormPerformanceTestCase and FormIntegrationTestCase in the Symfony\Component\Form\Tests namespace and moved them to the Symfony\Component\Form\Test namespace + * deprecated TypeTestCase in the Symfony\Component\Form\Tests\Extension\Core\Type namespace and moved it to the Symfony\Component\Form\Test namespace + * changed FormRenderer::humanize() to humanize also camel cased field name + * added RequestHandlerInterface and FormInterface::handleRequest() + * deprecated passing a Request instance to FormInterface::bind() + * added options "method" and "action" to FormType + * deprecated option "virtual" in favor "inherit_data" + * deprecated VirtualFormAwareIterator in favor of InheritDataAwareIterator + * [BC BREAK] removed the "array" type hint from DataMapperInterface + * improved forms inheriting their parent data to actually return that data from getData(), getNormData() and getViewData() + * added component-level exceptions for various SPL exceptions + changed all uses of the deprecated Exception class to use more specialized exceptions instead + removed NotInitializedException, NotValidException, TypeDefinitionException, TypeLoaderException, CreationException + * added events PRE_SUBMIT, SUBMIT and POST_SUBMIT + * deprecated events PRE_BIND, BIND and POST_BIND + * [BC BREAK] renamed bind() and isBound() in FormInterface to submit() and isSubmitted() + * added methods submit() and isSubmitted() to Form + * deprecated bind() and isBound() in Form + * deprecated AlreadyBoundException in favor of AlreadySubmittedException + * added support for PATCH requests + * [BC BREAK] added initialize() to FormInterface + * [BC BREAK] added getAutoInitialize() to FormConfigInterface + * [BC BREAK] added setAutoInitialize() to FormConfigBuilderInterface + * [BC BREAK] initialization for Form instances added to a form tree must be manually disabled + * PRE_SET_DATA is now guaranteed to be called after children were added by the form builder, + unless FormInterface::setData() is called manually + * fixed CSRF error message to be translated + * custom CSRF error messages can now be set through the "csrf_message" option + * fixed: expanded single-choice fields now show a radio button for the empty value + +2.2.0 +----- + + * TrimListener now removes unicode whitespaces + * deprecated getParent(), setParent() and hasParent() in FormBuilderInterface + * FormInterface::add() now accepts a FormInterface instance OR a field's name, type and options + * removed special characters between the choice or text fields of DateType unless + the option "format" is set to a custom value + * deprecated FormException and introduced ExceptionInterface instead + * [BC BREAK] FormException is now an interface + * protected FormBuilder methods from being called when it is turned into a FormConfigInterface with getFormConfig() + * [BC BREAK] inserted argument `$message` in the constructor of `FormError` + * the PropertyPath class and related classes were moved to a dedicated + PropertyAccess component. During the move, InvalidPropertyException was + renamed to NoSuchPropertyException. FormUtil was split: FormUtil::singularify() + can now be found in Symfony\Component\PropertyAccess\StringUtil. The methods + getValue() and setValue() from PropertyPath were extracted into a new class + PropertyAccessor. + * added an optional PropertyAccessorInterface parameter to FormType, + ObjectChoiceList and PropertyPathMapper + * [BC BREAK] PropertyPathMapper and FormType now have a constructor + * [BC BREAK] setting the option "validation_groups" to ``false`` now disables validation + instead of assuming group "Default" + +2.1.0 +----- + + * [BC BREAK] ``read_only`` field attribute now renders as ``readonly="readonly"``, use ``disabled`` instead + * [BC BREAK] child forms now aren't validated anymore by default + * made validation of form children configurable (new option: cascade_validation) + * added support for validation groups as callbacks + * made the translation catalogue configurable via the "translation_domain" option + * added Form::getErrorsAsString() to help debugging forms + * allowed setting different options for RepeatedType fields (like the label) + * added support for empty form name at root level, this enables rendering forms + without form name prefix in field names + * [BC BREAK] form and field names must start with a letter, digit or underscore + and only contain letters, digits, underscores, hyphens and colons + * [BC BREAK] changed default name of the prototype in the "collection" type + from "$$name$$" to "\__name\__". No dollars are appended/prepended to custom + names anymore. + * [BC BREAK] improved ChoiceListInterface + * [BC BREAK] added SimpleChoiceList and LazyChoiceList as replacement of + ArrayChoiceList + * added ChoiceList and ObjectChoiceList to use objects as choices + * [BC BREAK] removed EntitiesToArrayTransformer and EntityToIdTransformer. + The former has been replaced by CollectionToArrayTransformer in combination + with EntityChoiceList, the latter is not required in the core anymore. + * [BC BREAK] renamed + * ArrayToBooleanChoicesTransformer to ChoicesToBooleanArrayTransformer + * ScalarToBooleanChoicesTransformer to ChoiceToBooleanArrayTransformer + * ArrayToChoicesTransformer to ChoicesToValuesTransformer + * ScalarToChoiceTransformer to ChoiceToValueTransformer + to be consistent with the naming in ChoiceListInterface. + They were merged into ChoiceList and have no public equivalent anymore. + * choice fields now throw a FormException if neither the "choices" nor the + "choice_list" option is set + * the radio type is now a child of the checkbox type + * the collection, choice (with multiple selection) and entity (with multiple + selection) types now make use of addXxx() and removeXxx() methods in your + model if you set "by_reference" to false. For a custom, non-recognized + singular form, set the "property_path" option like this: "plural|singular" + * forms now don't create an empty object anymore if they are completely + empty and not required. The empty value for such forms is null. + * added constant Guess::VERY_HIGH_CONFIDENCE + * [BC BREAK] The methods `add`, `remove`, `setParent`, `bind` and `setData` + in class Form now throw an exception if the form is already bound + * fields of constrained classes without a NotBlank or NotNull constraint are + set to not required now, as stated in the docs + * fixed TimeType and DateTimeType to not display seconds when "widget" is + "single_text" unless "with_seconds" is set to true + * checkboxes of in an expanded multiple-choice field don't include the choice + in their name anymore. Their names terminate with "[]" now. + * deprecated FormValidatorInterface and substituted its implementations + by event subscribers + * simplified CSRF protection and removed the csrf type + * deprecated FieldType and merged it into FormType + * added new option "compound" that lets you switch between field and form behavior + * [BC BREAK] renamed theme blocks + * "field_*" to "form_*" + * "field_widget" to "form_widget_simple" + * "widget_choice_options" to "choice_widget_options" + * "generic_label" to "form_label" + * added theme blocks "form_widget_compound", "choice_widget_expanded" and + "choice_widget_collapsed" to make theming more modular + * ValidatorTypeGuesser now guesses "collection" for array type constraint + * added method `guessPattern` to FormTypeGuesserInterface to guess which pattern to use in the HTML5 attribute "pattern" + * deprecated method `guessMinLength` in favor of `guessPattern` + * labels don't display field attributes anymore. Label attributes can be + passed in the "label_attr" option/variable + * added option "mapped" which should be used instead of setting "property_path" to false + * [BC BREAK] "data_class" now *must* be set if a form maps to an object and should be left empty otherwise + * improved error mapping on forms + * dot (".") rules are now allowed to map errors assigned to a form to + one of its children + * errors are not mapped to unsynchronized forms anymore + * [BC BREAK] changed Form constructor to accept a single `FormConfigInterface` object + * [BC BREAK] changed argument order in the FormBuilder constructor + * added Form method `getViewData` + * deprecated Form methods + * `getTypes` + * `getErrorBubbling` + * `getNormTransformers` + * `getClientTransformers` + * `getAttribute` + * `hasAttribute` + * `getClientData` + * added FormBuilder methods + * `getTypes` + * `addViewTransformer` + * `getViewTransformers` + * `resetViewTransformers` + * `addModelTransformer` + * `getModelTransformers` + * `resetModelTransformers` + * deprecated FormBuilder methods + * `prependClientTransformer` + * `appendClientTransformer` + * `getClientTransformers` + * `resetClientTransformers` + * `prependNormTransformer` + * `appendNormTransformer` + * `getNormTransformers` + * `resetNormTransformers` + * deprecated the option "validation_constraint" in favor of the new + option "constraints" + * removed superfluous methods from DataMapperInterface + * `mapFormToData` + * `mapDataToForm` + * added `setDefaultOptions` to FormTypeInterface and FormTypeExtensionInterface + which accepts an OptionsResolverInterface instance + * deprecated the methods `getDefaultOptions` and `getAllowedOptionValues` + in FormTypeInterface and FormTypeExtensionInterface + * options passed during construction can now be accessed from FormConfigInterface + * added FormBuilderInterface and FormConfigEditorInterface + * [BC BREAK] the method `buildForm` in FormTypeInterface and FormTypeExtensionInterface + now receives a FormBuilderInterface instead of a FormBuilder instance + * [BC BREAK] the method `buildViewBottomUp` was renamed to `finishView` in + FormTypeInterface and FormTypeExtensionInterface + * [BC BREAK] the options array is now passed as last argument of the + methods + * `buildView` + * `finishView` + in FormTypeInterface and FormTypeExtensionInterface + * [BC BREAK] no options are passed to `getParent` of FormTypeInterface anymore + * deprecated DataEvent and FilterDataEvent in favor of the new FormEvent which is + now passed to all events thrown by the component + * FormEvents::BIND now replaces FormEvents::BIND_NORM_DATA + * FormEvents::PRE_SET_DATA now replaces FormEvents::SET_DATA + * FormEvents::PRE_BIND now replaces FormEvents::BIND_CLIENT_DATA + * deprecated FormEvents::SET_DATA, FormEvents::BIND_CLIENT_DATA and + FormEvents::BIND_NORM_DATA + * [BC BREAK] reversed the order of the first two arguments to `createNamed` + and `createNamedBuilder` in `FormFactoryInterface` + * deprecated `getChildren` in Form and FormBuilder in favor of `all` + * deprecated `hasChildren` in Form and FormBuilder in favor of `count` + * FormBuilder now implements \IteratorAggregate + * [BC BREAK] compound forms now always need a data mapper + * FormBuilder now maintains the order when explicitly adding form builders as children + * ChoiceType now doesn't add the empty value anymore if the choices already contain an empty element + * DateType, TimeType and DateTimeType now show empty values again if not required + * [BC BREAK] fixed rendering of errors for DateType, BirthdayType and similar ones + * [BC BREAK] fixed: form constraints are only validated if they belong to the validated group + * deprecated `bindRequest` in `Form` and replaced it by a listener to FormEvents::PRE_BIND + * fixed: the "data" option supersedes default values from the model + * changed DateType to refer to the "format" option for calculating the year and day choices instead + of padding them automatically + * [BC BREAK] DateType defaults to the format "yyyy-MM-dd" now if the widget is + "single_text", in order to support the HTML 5 date field out of the box + * added the option "format" to DateTimeType + * [BC BREAK] DateTimeType now outputs RFC 3339 dates by default, as generated and + consumed by HTML5 browsers, if the widget is "single_text" + * deprecated the options "data_timezone" and "user_timezone" in DateType, DateTimeType and TimeType + and renamed them to "model_timezone" and "view_timezone" + * fixed: TransformationFailedExceptions thrown in the model transformer are now caught by the form + * added FormRegistryInterface, ResolvedFormTypeInterface and ResolvedFormTypeFactoryInterface + * deprecated FormFactory methods + * `addType` + * `hasType` + * `getType` + * [BC BREAK] FormFactory now expects a FormRegistryInterface and a ResolvedFormTypeFactoryInterface as constructor argument + * [BC BREAK] The method `createBuilder` in FormTypeInterface is not supported anymore for performance reasons + * [BC BREAK] Removed `setTypes` from FormBuilder + * deprecated AbstractType methods + * `getExtensions` + * `setExtensions` + * ChoiceType now caches its created choice lists to improve performance + * [BC BREAK] Rows of a collection field cannot be themed individually anymore. All rows in the collection + field now have the same block names, which contains "entry" where it previously contained the row index. + * [BC BREAK] When registering a type through the DI extension, the tag alias has to match the actual type name. + * added FormRendererInterface, FormRendererEngineInterface and implementations of these interfaces + * [BC BREAK] removed the following methods from FormUtil: + * `toArrayKey` + * `toArrayKeys` + * `isChoiceGroup` + * `isChoiceSelected` + * [BC BREAK] renamed method `renderBlock` in FormHelper to `block` and changed its signature + * made FormView properties public and deprecated their accessor methods + * made the normalized data of a form accessible in the template through the variable "form.vars.data" + * made the original data of a choice accessible in the template through the property "choice.data" + * added convenience class Forms and FormFactoryBuilderInterface diff --git a/vendor/symfony/form/CallbackTransformer.php b/vendor/symfony/form/CallbackTransformer.php new file mode 100644 index 0000000..2a79b5b --- /dev/null +++ b/vendor/symfony/form/CallbackTransformer.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +class CallbackTransformer implements DataTransformerInterface +{ + private \Closure $transform; + private \Closure $reverseTransform; + + public function __construct(callable $transform, callable $reverseTransform) + { + $this->transform = $transform(...); + $this->reverseTransform = $reverseTransform(...); + } + + public function transform(mixed $data): mixed + { + return ($this->transform)($data); + } + + public function reverseTransform(mixed $data): mixed + { + return ($this->reverseTransform)($data); + } +} diff --git a/vendor/symfony/form/ChoiceList/ArrayChoiceList.php b/vendor/symfony/form/ChoiceList/ArrayChoiceList.php new file mode 100644 index 0000000..9ac4fd6 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/ArrayChoiceList.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +/** + * A list of choices with arbitrary data types. + * + * The user of this class is responsible for assigning string values to the + * choices and for their uniqueness. + * Both the choices and their values are passed to the constructor. + * Each choice must have a corresponding value (with the same key) in + * the values array. + * + * @author Bernhard Schussek + */ +class ArrayChoiceList implements ChoiceListInterface +{ + protected array $choices; + + /** + * The values indexed by the original keys. + */ + protected array $structuredValues; + + /** + * The original keys of the choices array. + */ + protected array $originalKeys; + protected ?\Closure $valueCallback = null; + + /** + * Creates a list with the given choices and values. + * + * The given choice array must have the same array keys as the value array. + * + * @param iterable $choices The selectable choices + * @param callable|null $value The callable for creating the value + * for a choice. If `null` is passed, + * incrementing integers are used as + * values + */ + public function __construct(iterable $choices, ?callable $value = null) + { + if ($choices instanceof \Traversable) { + $choices = iterator_to_array($choices); + } + + if (null === $value && $this->castableToString($choices)) { + $value = static fn ($choice) => false === $choice ? '0' : (string) $choice; + } + + if (null !== $value) { + // If a deterministic value generator was passed, use it later + $this->valueCallback = $value(...); + } else { + // Otherwise generate incrementing integers as values + $value = static function () { + static $i = 0; + + return $i++; + }; + } + + // If the choices are given as recursive array (i.e. with explicit + // choice groups), flatten the array. The grouping information is needed + // in the view only. + $this->flatten($choices, $value, $choicesByValues, $keysByValues, $structuredValues); + + $this->choices = $choicesByValues; + $this->originalKeys = $keysByValues; + $this->structuredValues = $structuredValues; + } + + public function getChoices(): array + { + return $this->choices; + } + + public function getValues(): array + { + return array_map('strval', array_keys($this->choices)); + } + + public function getStructuredValues(): array + { + return $this->structuredValues; + } + + public function getOriginalKeys(): array + { + return $this->originalKeys; + } + + public function getChoicesForValues(array $values): array + { + $choices = []; + + foreach ($values as $i => $givenValue) { + if (\array_key_exists($givenValue, $this->choices)) { + $choices[$i] = $this->choices[$givenValue]; + } + } + + return $choices; + } + + public function getValuesForChoices(array $choices): array + { + $values = []; + + // Use the value callback to compare choices by their values, if present + if ($this->valueCallback) { + $givenValues = []; + + foreach ($choices as $i => $givenChoice) { + $givenValues[$i] = (string) ($this->valueCallback)($givenChoice); + } + + return array_intersect($givenValues, array_keys($this->choices)); + } + + // Otherwise compare choices by identity + foreach ($choices as $i => $givenChoice) { + foreach ($this->choices as $value => $choice) { + if ($choice === $givenChoice) { + $values[$i] = (string) $value; + break; + } + } + } + + return $values; + } + + /** + * Flattens an array into the given output variables. + * + * @param array $choices The array to flatten + * @param callable $value The callable for generating choice values + * @param array|null $choicesByValues The flattened choices indexed by the + * corresponding values + * @param array|null $keysByValues The original keys indexed by the + * corresponding values + * @param array|null $structuredValues The values indexed by the original keys + * + * @internal + */ + protected function flatten(array $choices, callable $value, ?array &$choicesByValues, ?array &$keysByValues, ?array &$structuredValues): void + { + if (null === $choicesByValues) { + $choicesByValues = []; + $keysByValues = []; + $structuredValues = []; + } + + foreach ($choices as $key => $choice) { + if (\is_array($choice)) { + $this->flatten($choice, $value, $choicesByValues, $keysByValues, $structuredValues[$key]); + + continue; + } + + $choiceValue = (string) $value($choice); + $choicesByValues[$choiceValue] = $choice; + $keysByValues[$choiceValue] = $key; + $structuredValues[$key] = $choiceValue; + } + } + + /** + * Checks whether the given choices can be cast to strings without + * generating duplicates. + * This method is responsible for preventing conflict between scalar values + * and the empty value. + */ + private function castableToString(array $choices, array &$cache = []): bool + { + foreach ($choices as $choice) { + if (\is_array($choice)) { + if (!$this->castableToString($choice, $cache)) { + return false; + } + + continue; + } elseif (!\is_scalar($choice)) { + return false; + } + + // prevent having false casted to the empty string by isset() + $choice = false === $choice ? '0' : (string) $choice; + + if (isset($cache[$choice])) { + return false; + } + + $cache[$choice] = true; + } + + return true; + } +} diff --git a/vendor/symfony/form/ChoiceList/ChoiceList.php b/vendor/symfony/form/ChoiceList/ChoiceList.php new file mode 100644 index 0000000..31166c1 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/ChoiceList.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceAttr; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFieldName; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFilter; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceTranslationParameters; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue; +use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy; +use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice; +use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A set of convenient static methods to create cacheable choice list options. + * + * @author Jules Pietri + */ +final class ChoiceList +{ + /** + * Creates a cacheable loader from any callable providing iterable choices. + * + * @param callable $choices A callable that must return iterable choices or grouped choices + * @param mixed $vary Dynamic data used to compute a unique hash when caching the loader + */ + public static function lazy(FormTypeInterface|FormTypeExtensionInterface $formType, callable $choices, mixed $vary = null): ChoiceLoader + { + return self::loader($formType, new CallbackChoiceLoader($choices), $vary); + } + + /** + * Decorates a loader to make it cacheable. + * + * @param ChoiceLoaderInterface $loader A loader responsible for creating loading choices or grouped choices + * @param mixed $vary Dynamic data used to compute a unique hash when caching the loader + */ + public static function loader(FormTypeInterface|FormTypeExtensionInterface $formType, ChoiceLoaderInterface $loader, mixed $vary = null): ChoiceLoader + { + return new ChoiceLoader($formType, $loader, $vary); + } + + /** + * Decorates a "choice_value" callback to make it cacheable. + * + * @param callable|array $value Any pseudo callable to create a unique string value from a choice + * @param mixed $vary Dynamic data used to compute a unique hash when caching the callback + */ + public static function value(FormTypeInterface|FormTypeExtensionInterface $formType, callable|array $value, mixed $vary = null): ChoiceValue + { + return new ChoiceValue($formType, $value, $vary); + } + + /** + * @param callable|array $filter Any pseudo callable to filter a choice list + * @param mixed $vary Dynamic data used to compute a unique hash when caching the callback + */ + public static function filter(FormTypeInterface|FormTypeExtensionInterface $formType, callable|array $filter, mixed $vary = null): ChoiceFilter + { + return new ChoiceFilter($formType, $filter, $vary); + } + + /** + * Decorates a "choice_label" option to make it cacheable. + * + * @param callable|false $label Any pseudo callable to create a label from a choice or false to discard it + * @param mixed $vary Dynamic data used to compute a unique hash when caching the option + */ + public static function label(FormTypeInterface|FormTypeExtensionInterface $formType, callable|false $label, mixed $vary = null): ChoiceLabel + { + return new ChoiceLabel($formType, $label, $vary); + } + + /** + * Decorates a "choice_name" callback to make it cacheable. + * + * @param callable|array $fieldName Any pseudo callable to create a field name from a choice + * @param mixed $vary Dynamic data used to compute a unique hash when caching the callback + */ + public static function fieldName(FormTypeInterface|FormTypeExtensionInterface $formType, callable|array $fieldName, mixed $vary = null): ChoiceFieldName + { + return new ChoiceFieldName($formType, $fieldName, $vary); + } + + /** + * Decorates a "choice_attr" option to make it cacheable. + * + * @param callable|array $attr Any pseudo callable or array to create html attributes from a choice + * @param mixed $vary Dynamic data used to compute a unique hash when caching the option + */ + public static function attr(FormTypeInterface|FormTypeExtensionInterface $formType, callable|array $attr, mixed $vary = null): ChoiceAttr + { + return new ChoiceAttr($formType, $attr, $vary); + } + + /** + * Decorates a "choice_translation_parameters" option to make it cacheable. + * + * @param callable|array $translationParameters Any pseudo callable or array to create translation parameters from a choice + * @param mixed $vary Dynamic data used to compute a unique hash when caching the option + */ + public static function translationParameters(FormTypeInterface|FormTypeExtensionInterface $formType, callable|array $translationParameters, mixed $vary = null): ChoiceTranslationParameters + { + return new ChoiceTranslationParameters($formType, $translationParameters, $vary); + } + + /** + * Decorates a "group_by" callback to make it cacheable. + * + * @param callable|array $groupBy Any pseudo callable to return a group name from a choice + * @param mixed $vary Dynamic data used to compute a unique hash when caching the callback + */ + public static function groupBy(FormTypeInterface|FormTypeExtensionInterface $formType, callable|array $groupBy, mixed $vary = null): GroupBy + { + return new GroupBy($formType, $groupBy, $vary); + } + + /** + * Decorates a "preferred_choices" option to make it cacheable. + * + * @param callable|array $preferred Any pseudo callable or array to return a group name from a choice + * @param mixed $vary Dynamic data used to compute a unique hash when caching the option + */ + public static function preferred(FormTypeInterface|FormTypeExtensionInterface $formType, callable|array $preferred, mixed $vary = null): PreferredChoice + { + return new PreferredChoice($formType, $preferred, $vary); + } + + /** + * Should not be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/form/ChoiceList/ChoiceListInterface.php b/vendor/symfony/form/ChoiceList/ChoiceListInterface.php new file mode 100644 index 0000000..e711a97 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/ChoiceListInterface.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +/** + * A list of choices that can be selected in a choice field. + * + * A choice list assigns unique string values to each of a list of choices. + * These string values are displayed in the "value" attributes in HTML and + * submitted back to the server. + * + * The acceptable data types for the choices depend on the implementation. + * Values must always be strings and (within the list) free of duplicates. + * + * @author Bernhard Schussek + */ +interface ChoiceListInterface +{ + /** + * Returns all selectable choices. + * + * @return array The selectable choices indexed by the corresponding values + */ + public function getChoices(): array; + + /** + * Returns the values for the choices. + * + * The values are strings that do not contain duplicates: + * + * $form->add('field', 'choice', [ + * 'choices' => [ + * 'Decided' => ['Yes' => true, 'No' => false], + * 'Undecided' => ['Maybe' => null], + * ], + * ]); + * + * In this example, the result of this method is: + * + * [ + * 'Yes' => '0', + * 'No' => '1', + * 'Maybe' => '2', + * ] + * + * Null and false MUST NOT conflict when being casted to string. + * For this some default incremented values SHOULD be computed. + * + * @return string[] + */ + public function getValues(): array; + + /** + * Returns the values in the structure originally passed to the list. + * + * Contrary to {@link getValues()}, the result is indexed by the original + * keys of the choices. If the original array contained nested arrays, these + * nested arrays are represented here as well: + * + * $form->add('field', 'choice', [ + * 'choices' => [ + * 'Decided' => ['Yes' => true, 'No' => false], + * 'Undecided' => ['Maybe' => null], + * ], + * ]); + * + * In this example, the result of this method is: + * + * [ + * 'Decided' => ['Yes' => '0', 'No' => '1'], + * 'Undecided' => ['Maybe' => '2'], + * ] + * + * Nested arrays do not make sense in a view format unless + * they are used as a convenient way of grouping. + * If the implementation does not intend to support grouped choices, + * this method SHOULD be equivalent to {@link getValues()}. + * The $groupBy callback parameter SHOULD be used instead. + * + * @return string[] + */ + public function getStructuredValues(): array; + + /** + * Returns the original keys of the choices. + * + * The original keys are the keys of the choice array that was passed in the + * "choice" option of the choice type. Note that this array may contain + * duplicates if the "choice" option contained choice groups: + * + * $form->add('field', 'choice', [ + * 'choices' => [ + * 'Decided' => [true, false], + * 'Undecided' => [null], + * ], + * ]); + * + * In this example, the original key 0 appears twice, once for `true` and + * once for `null`. + * + * @return int[]|string[] The original choice keys indexed by the + * corresponding choice values + */ + public function getOriginalKeys(): array; + + /** + * Returns the choices corresponding to the given values. + * + * The choices are returned with the same keys and in the same order as the + * corresponding values in the given array. + * + * @param string[] $values An array of choice values. Non-existing values in + * this array are ignored + */ + public function getChoicesForValues(array $values): array; + + /** + * Returns the values corresponding to the given choices. + * + * The values are returned with the same keys and in the same order as the + * corresponding choices in the given array. + * + * @param array $choices An array of choices. Non-existing choices in this + * array are ignored + * + * @return string[] + */ + public function getValuesForChoices(array $choices): array; +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/AbstractStaticOption.php b/vendor/symfony/form/ChoiceList/Factory/Cache/AbstractStaticOption.php new file mode 100644 index 0000000..2686017 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/AbstractStaticOption.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A template decorator for static {@see ChoiceType} options. + * + * Used as fly weight for {@see CachingFactoryDecorator}. + * + * @internal + * + * @author Jules Pietri + */ +abstract class AbstractStaticOption +{ + private static array $options = []; + + private bool|string|array|\Closure|ChoiceLoaderInterface $option; + + /** + * @param mixed $option Any pseudo callable, array, string or bool to define a choice list option + * @param mixed $vary Dynamic data used to compute a unique hash when caching the option + */ + final public function __construct(FormTypeInterface|FormTypeExtensionInterface $formType, mixed $option, mixed $vary = null) + { + $hash = CachingFactoryDecorator::generateHash([static::class, $formType, $vary]); + + $this->option = self::$options[$hash] ??= $option instanceof \Closure || \is_string($option) || \is_bool($option) || $option instanceof ChoiceLoaderInterface || !\is_callable($option) ? $option : $option(...); + } + + final public function getOption(): mixed + { + return $this->option; + } + + final public static function reset(): void + { + self::$options = []; + } +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceAttr.php b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceAttr.php new file mode 100644 index 0000000..8de6956 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceAttr.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_attr" option. + * + * @internal + * + * @author Jules Pietri + */ +final class ChoiceAttr extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceFieldName.php b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceFieldName.php new file mode 100644 index 0000000..0c71e20 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceFieldName.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_name" callback. + * + * @internal + * + * @author Jules Pietri + */ +final class ChoiceFieldName extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceFilter.php b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceFilter.php new file mode 100644 index 0000000..13b8cd8 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceFilter.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_filter" option. + * + * @internal + * + * @author Jules Pietri + */ +final class ChoiceFilter extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceLabel.php b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceLabel.php new file mode 100644 index 0000000..664a090 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceLabel.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_label" option. + * + * @internal + * + * @author Jules Pietri + */ +final class ChoiceLabel extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceLoader.php b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceLoader.php new file mode 100644 index 0000000..1d64f10 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceLoader.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_loader" option. + * + * @internal + * + * @author Jules Pietri + */ +final class ChoiceLoader extends AbstractStaticOption implements ChoiceLoaderInterface +{ + public function loadChoiceList(?callable $value = null): ChoiceListInterface + { + return $this->getOption()->loadChoiceList($value); + } + + public function loadChoicesForValues(array $values, ?callable $value = null): array + { + return $this->getOption()->loadChoicesForValues($values, $value); + } + + public function loadValuesForChoices(array $choices, ?callable $value = null): array + { + return $this->getOption()->loadValuesForChoices($choices, $value); + } +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php new file mode 100644 index 0000000..e9ab5c7 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_translation_parameters" option. + * + * @internal + * + * @author Vincent Langlet + */ +final class ChoiceTranslationParameters extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceValue.php b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceValue.php new file mode 100644 index 0000000..d96f1e9 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/ChoiceValue.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_value" callback. + * + * @internal + * + * @author Jules Pietri + */ +final class ChoiceValue extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/GroupBy.php b/vendor/symfony/form/ChoiceList/Factory/Cache/GroupBy.php new file mode 100644 index 0000000..2ad492c --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/GroupBy.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "group_by" callback. + * + * @internal + * + * @author Jules Pietri + */ +final class GroupBy extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/Cache/PreferredChoice.php b/vendor/symfony/form/ChoiceList/Factory/Cache/PreferredChoice.php new file mode 100644 index 0000000..4aefd69 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/Cache/PreferredChoice.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "preferred_choices" option. + * + * @internal + * + * @author Jules Pietri + */ +final class PreferredChoice extends AbstractStaticOption +{ +} diff --git a/vendor/symfony/form/ChoiceList/Factory/CachingFactoryDecorator.php b/vendor/symfony/form/ChoiceList/Factory/CachingFactoryDecorator.php new file mode 100644 index 0000000..687fcec --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory; + +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\View\ChoiceListView; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Caches the choice lists created by the decorated factory. + * + * To cache a list based on its options, arguments must be decorated + * by a {@see Cache\AbstractStaticOption} implementation. + * + * @author Bernhard Schussek + * @author Jules Pietri + */ +class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterface +{ + private ChoiceListFactoryInterface $decoratedFactory; + + /** + * @var ChoiceListInterface[] + */ + private array $lists = []; + + /** + * @var ChoiceListView[] + */ + private array $views = []; + + /** + * Generates a SHA-256 hash for the given value. + * + * Optionally, a namespace string can be passed. Calling this method will + * the same values, but different namespaces, will return different hashes. + * + * @return string The SHA-256 hash + * + * @internal + */ + public static function generateHash(mixed $value, string $namespace = ''): string + { + if (\is_object($value)) { + $value = spl_object_hash($value); + } elseif (\is_array($value)) { + array_walk_recursive($value, static function (&$v) { + if (\is_object($v)) { + $v = spl_object_hash($v); + } + }); + } + + return hash('sha256', $namespace.':'.serialize($value)); + } + + public function __construct(ChoiceListFactoryInterface $decoratedFactory) + { + $this->decoratedFactory = $decoratedFactory; + } + + /** + * Returns the decorated factory. + */ + public function getDecoratedFactory(): ChoiceListFactoryInterface + { + return $this->decoratedFactory; + } + + public function createListFromChoices(iterable $choices, mixed $value = null, mixed $filter = null): ChoiceListInterface + { + if ($choices instanceof \Traversable) { + $choices = iterator_to_array($choices); + } + + $cache = true; + // Only cache per value and filter when needed. The value is not validated on purpose. + // The decorated factory may decide which values to accept and which not. + if ($value instanceof Cache\ChoiceValue) { + $value = $value->getOption(); + } elseif ($value) { + $cache = false; + } + if ($filter instanceof Cache\ChoiceFilter) { + $filter = $filter->getOption(); + } elseif ($filter) { + $cache = false; + } + + if (!$cache) { + return $this->decoratedFactory->createListFromChoices($choices, $value, $filter); + } + + $hash = self::generateHash([$choices, $value, $filter], 'fromChoices'); + + if (!isset($this->lists[$hash])) { + $this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value, $filter); + } + + return $this->lists[$hash]; + } + + public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value = null, mixed $filter = null): ChoiceListInterface + { + $cache = true; + + if ($loader instanceof Cache\ChoiceLoader) { + $loader = $loader->getOption(); + } else { + $cache = false; + } + + if ($value instanceof Cache\ChoiceValue) { + $value = $value->getOption(); + } elseif ($value) { + $cache = false; + } + + if ($filter instanceof Cache\ChoiceFilter) { + $filter = $filter->getOption(); + } elseif ($filter) { + $cache = false; + } + + if (!$cache) { + return $this->decoratedFactory->createListFromLoader($loader, $value, $filter); + } + + $hash = self::generateHash([$loader, $value, $filter], 'fromLoader'); + + if (!isset($this->lists[$hash])) { + $this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value, $filter); + } + + return $this->lists[$hash]; + } + + public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView + { + $cache = true; + + if ($preferredChoices instanceof Cache\PreferredChoice) { + $preferredChoices = $preferredChoices->getOption(); + } elseif ($preferredChoices) { + $cache = false; + } + + if ($label instanceof Cache\ChoiceLabel) { + $label = $label->getOption(); + } elseif (null !== $label) { + $cache = false; + } + + if ($index instanceof Cache\ChoiceFieldName) { + $index = $index->getOption(); + } elseif ($index) { + $cache = false; + } + + if ($groupBy instanceof Cache\GroupBy) { + $groupBy = $groupBy->getOption(); + } elseif ($groupBy) { + $cache = false; + } + + if ($attr instanceof Cache\ChoiceAttr) { + $attr = $attr->getOption(); + } elseif ($attr) { + $cache = false; + } + + if ($labelTranslationParameters instanceof Cache\ChoiceTranslationParameters) { + $labelTranslationParameters = $labelTranslationParameters->getOption(); + } elseif ([] !== $labelTranslationParameters) { + $cache = false; + } + + if (!$cache) { + return $this->decoratedFactory->createView( + $list, + $preferredChoices, + $label, + $index, + $groupBy, + $attr, + $labelTranslationParameters, + $duplicatePreferredChoices, + ); + } + + $hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters, $duplicatePreferredChoices]); + + if (!isset($this->views[$hash])) { + $this->views[$hash] = $this->decoratedFactory->createView( + $list, + $preferredChoices, + $label, + $index, + $groupBy, + $attr, + $labelTranslationParameters, + $duplicatePreferredChoices, + ); + } + + return $this->views[$hash]; + } + + public function reset(): void + { + $this->lists = []; + $this->views = []; + Cache\AbstractStaticOption::reset(); + } +} diff --git a/vendor/symfony/form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/vendor/symfony/form/ChoiceList/Factory/ChoiceListFactoryInterface.php new file mode 100644 index 0000000..a9ca6db --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory; + +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\View\ChoiceListView; + +/** + * Creates {@link ChoiceListInterface} instances. + * + * @author Bernhard Schussek + */ +interface ChoiceListFactoryInterface +{ + /** + * Creates a choice list for the given choices. + * + * The choices should be passed in the values of the choices array. + * + * Optionally, a callable can be passed for generating the choice values. + * The callable receives the choice as only argument. + * Null may be passed when the choice list contains the empty value. + * + * @param callable|null $filter The callable filtering the choices + */ + public function createListFromChoices(iterable $choices, ?callable $value = null, ?callable $filter = null): ChoiceListInterface; + + /** + * Creates a choice list that is loaded with the given loader. + * + * Optionally, a callable can be passed for generating the choice values. + * The callable receives the choice as only argument. + * Null may be passed when the choice list contains the empty value. + * + * @param callable|null $filter The callable filtering the choices + */ + public function createListFromLoader(ChoiceLoaderInterface $loader, ?callable $value = null, ?callable $filter = null): ChoiceListInterface; + + /** + * Creates a view for the given choice list. + * + * Callables may be passed for all optional arguments. The callables receive + * the choice as first and the array key as the second argument. + * + * * The callable for the label and the name should return the generated + * label/choice name. + * * The callable for the preferred choices should return true or false, + * depending on whether the choice should be preferred or not. + * * The callable for the grouping should return the group name or null if + * a choice should not be grouped. + * * The callable for the attributes should return an array of HTML + * attributes that will be inserted in the tag of the choice. + * + * If no callable is passed, the labels will be generated from the choice + * keys. The view indices will be generated using an incrementing integer + * by default. + * + * The preferred choices can also be passed as array. Each choice that is + * contained in that array will be marked as preferred. + * + * The attributes can be passed as multi-dimensional array. The keys should + * match the keys of the choices. The values should be arrays of HTML + * attributes that should be added to the respective choice. + * + * @param array|callable|null $preferredChoices The preferred choices + * @param callable|false|null $label The callable generating the choice labels; + * pass false to discard the label + * @param array|callable|null $attr The callable generating the HTML attributes + * @param array|callable $labelTranslationParameters The parameters used to translate the choice labels + * @param bool $duplicatePreferredChoices Whether the preferred choices should be duplicated + * on top of the list and in their original position + * or only in the top of the list + */ + public function createView(ChoiceListInterface $list, array|callable|null $preferredChoices = null, callable|false|null $label = null, ?callable $index = null, ?callable $groupBy = null, array|callable|null $attr = null, array|callable $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView; +} diff --git a/vendor/symfony/form/ChoiceList/Factory/DefaultChoiceListFactory.php b/vendor/symfony/form/ChoiceList/Factory/DefaultChoiceListFactory.php new file mode 100644 index 0000000..586ac82 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory; + +use Symfony\Component\Form\ChoiceList\ArrayChoiceList; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\LazyChoiceList; +use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator; +use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; +use Symfony\Component\Form\ChoiceList\View\ChoiceListView; +use Symfony\Component\Form\ChoiceList\View\ChoiceView; +use Symfony\Contracts\Translation\TranslatableInterface; + +/** + * Default implementation of {@link ChoiceListFactoryInterface}. + * + * @author Bernhard Schussek + * @author Jules Pietri + */ +class DefaultChoiceListFactory implements ChoiceListFactoryInterface +{ + public function createListFromChoices(iterable $choices, ?callable $value = null, ?callable $filter = null): ChoiceListInterface + { + if ($filter) { + // filter the choice list lazily + return $this->createListFromLoader(new FilterChoiceLoaderDecorator( + new CallbackChoiceLoader(static fn () => $choices), + $filter + ), $value); + } + + return new ArrayChoiceList($choices, $value); + } + + public function createListFromLoader(ChoiceLoaderInterface $loader, ?callable $value = null, ?callable $filter = null): ChoiceListInterface + { + if ($filter) { + $loader = new FilterChoiceLoaderDecorator($loader, $filter); + } + + return new LazyChoiceList($loader, $value); + } + + public function createView(ChoiceListInterface $list, array|callable|null $preferredChoices = null, callable|false|null $label = null, ?callable $index = null, ?callable $groupBy = null, array|callable|null $attr = null, array|callable $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView + { + $preferredViews = []; + $preferredViewsOrder = []; + $otherViews = []; + $choices = $list->getChoices(); + $keys = $list->getOriginalKeys(); + + if (!\is_callable($preferredChoices)) { + if (!$preferredChoices) { + $preferredChoices = null; + } else { + // make sure we have keys that reflect order + $preferredChoices = array_values($preferredChoices); + $preferredChoices = static fn ($choice) => array_search($choice, $preferredChoices, true); + } + } + + // The names are generated from an incrementing integer by default + $index ??= 0; + + // If $groupBy is a callable returning a string + // choices are added to the group with the name returned by the callable. + // If $groupBy is a callable returning an array + // choices are added to the groups with names returned by the callable + // If the callable returns null, the choice is not added to any group + if (\is_callable($groupBy)) { + foreach ($choices as $value => $choice) { + self::addChoiceViewsGroupedByCallable( + $groupBy, + $choice, + $value, + $label, + $keys, + $index, + $attr, + $labelTranslationParameters, + $preferredChoices, + $preferredViews, + $preferredViewsOrder, + $otherViews, + $duplicatePreferredChoices, + ); + } + + // Remove empty group views that may have been created by + // addChoiceViewsGroupedByCallable() + foreach ($preferredViews as $key => $view) { + if ($view instanceof ChoiceGroupView && 0 === \count($view->choices)) { + unset($preferredViews[$key]); + } + } + + foreach ($otherViews as $key => $view) { + if ($view instanceof ChoiceGroupView && 0 === \count($view->choices)) { + unset($otherViews[$key]); + } + } + + foreach ($preferredViewsOrder as $key => $groupViewsOrder) { + if ($groupViewsOrder) { + $preferredViewsOrder[$key] = min($groupViewsOrder); + } else { + unset($preferredViewsOrder[$key]); + } + } + } else { + // Otherwise use the original structure of the choices + self::addChoiceViewsFromStructuredValues( + $list->getStructuredValues(), + $label, + $choices, + $keys, + $index, + $attr, + $labelTranslationParameters, + $preferredChoices, + $preferredViews, + $preferredViewsOrder, + $otherViews, + $duplicatePreferredChoices, + ); + } + + uksort($preferredViews, static fn ($a, $b) => isset($preferredViewsOrder[$a], $preferredViewsOrder[$b]) ? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b] : 0); + + return new ChoiceListView($otherViews, $preferredViews); + } + + private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews, bool $duplicatePreferredChoices): void + { + // $value may be an integer or a string, since it's stored in the array + // keys. We want to guarantee it's a string though. + $key = $keys[$value]; + $nextIndex = \is_int($index) ? $index++ : $index($choice, $key, $value); + + // BC normalize label to accept a false value + if (null === $label) { + // If the labels are null, use the original choice key by default + $label = (string) $key; + } elseif (false !== $label) { + // If "choice_label" is set to false and "expanded" is true, the value false + // should be passed on to the "label" option of the checkboxes/radio buttons + $dynamicLabel = $label($choice, $key, $value); + + if (false === $dynamicLabel) { + $label = false; + } elseif ($dynamicLabel instanceof TranslatableInterface) { + $label = $dynamicLabel; + } else { + $label = (string) $dynamicLabel; + } + } + + $view = new ChoiceView( + $choice, + $value, + $label, + // The attributes may be a callable or a mapping from choice indices + // to nested arrays + \is_callable($attr) ? $attr($choice, $key, $value) : ($attr[$key] ?? []), + // The label translation parameters may be a callable or a mapping from choice indices + // to nested arrays + \is_callable($labelTranslationParameters) ? $labelTranslationParameters($choice, $key, $value) : ($labelTranslationParameters[$key] ?? []) + ); + + // $isPreferred may be null if no choices are preferred + if (null !== $isPreferred && false !== $preferredKey = $isPreferred($choice, $key, $value)) { + $preferredViews[$nextIndex] = $view; + $preferredViewsOrder[$nextIndex] = $preferredKey; + + if ($duplicatePreferredChoices) { + $otherViews[$nextIndex] = $view; + } + } else { + $otherViews[$nextIndex] = $view; + } + } + + private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews, bool $duplicatePreferredChoices): void + { + foreach ($values as $key => $value) { + if (null === $value) { + continue; + } + + // Add the contents of groups to new ChoiceGroupView instances + if (\is_array($value)) { + $preferredViewsForGroup = []; + $otherViewsForGroup = []; + + self::addChoiceViewsFromStructuredValues( + $value, + $label, + $choices, + $keys, + $index, + $attr, + $labelTranslationParameters, + $isPreferred, + $preferredViewsForGroup, + $preferredViewsOrder, + $otherViewsForGroup, + $duplicatePreferredChoices, + ); + + if (\count($preferredViewsForGroup) > 0) { + $preferredViews[$key] = new ChoiceGroupView($key, $preferredViewsForGroup); + } + + if (\count($otherViewsForGroup) > 0) { + $otherViews[$key] = new ChoiceGroupView($key, $otherViewsForGroup); + } + + continue; + } + + // Add ungrouped items directly + self::addChoiceView( + $choices[$value], + $value, + $label, + $keys, + $index, + $attr, + $labelTranslationParameters, + $isPreferred, + $preferredViews, + $preferredViewsOrder, + $otherViews, + $duplicatePreferredChoices, + ); + } + } + + private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews, bool $duplicatePreferredChoices): void + { + $groupLabels = $groupBy($choice, $keys[$value], $value); + + if (null === $groupLabels) { + // If the callable returns null, don't group the choice + self::addChoiceView( + $choice, + $value, + $label, + $keys, + $index, + $attr, + $labelTranslationParameters, + $isPreferred, + $preferredViews, + $preferredViewsOrder, + $otherViews, + $duplicatePreferredChoices, + ); + + return; + } + + $groupLabels = \is_array($groupLabels) ? array_map('strval', $groupLabels) : [(string) $groupLabels]; + + foreach ($groupLabels as $groupLabel) { + // Initialize the group views if necessary. Unnecessarily built group + // views will be cleaned up at the end of createView() + if (!isset($preferredViews[$groupLabel])) { + $preferredViews[$groupLabel] = new ChoiceGroupView($groupLabel); + $otherViews[$groupLabel] = new ChoiceGroupView($groupLabel); + } + if (!isset($preferredViewsOrder[$groupLabel])) { + $preferredViewsOrder[$groupLabel] = []; + } + + self::addChoiceView( + $choice, + $value, + $label, + $keys, + $index, + $attr, + $labelTranslationParameters, + $isPreferred, + $preferredViews[$groupLabel]->choices, + $preferredViewsOrder[$groupLabel], + $otherViews[$groupLabel]->choices, + $duplicatePreferredChoices, + ); + } + } +} diff --git a/vendor/symfony/form/ChoiceList/Factory/PropertyAccessDecorator.php b/vendor/symfony/form/ChoiceList/Factory/PropertyAccessDecorator.php new file mode 100644 index 0000000..c83ef17 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Factory; + +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\View\ChoiceListView; +use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * Adds property path support to a choice list factory. + * + * Pass the decorated factory to the constructor: + * + * $decorator = new PropertyAccessDecorator($factory); + * + * You can now pass property paths for generating choice values, labels, view + * indices, HTML attributes and for determining the preferred choices and the + * choice groups: + * + * // extract values from the $value property + * $list = $createListFromChoices($objects, 'value'); + * + * @author Bernhard Schussek + */ +class PropertyAccessDecorator implements ChoiceListFactoryInterface +{ + private ChoiceListFactoryInterface $decoratedFactory; + private PropertyAccessorInterface $propertyAccessor; + + public function __construct(ChoiceListFactoryInterface $decoratedFactory, ?PropertyAccessorInterface $propertyAccessor = null) + { + $this->decoratedFactory = $decoratedFactory; + $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + } + + /** + * Returns the decorated factory. + */ + public function getDecoratedFactory(): ChoiceListFactoryInterface + { + return $this->decoratedFactory; + } + + public function createListFromChoices(iterable $choices, mixed $value = null, mixed $filter = null): ChoiceListInterface + { + if (\is_string($value)) { + $value = new PropertyPath($value); + } + + if ($value instanceof PropertyPathInterface) { + $accessor = $this->propertyAccessor; + // The callable may be invoked with a non-object/array value + // when such values are passed to + // ChoiceListInterface::getValuesForChoices(). Handle this case + // so that the call to getValue() doesn't break. + $value = static fn ($choice) => \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; + } + + if (\is_string($filter)) { + $filter = new PropertyPath($filter); + } + + if ($filter instanceof PropertyPath) { + $accessor = $this->propertyAccessor; + $filter = static fn ($choice) => (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); + } + + return $this->decoratedFactory->createListFromChoices($choices, $value, $filter); + } + + public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value = null, mixed $filter = null): ChoiceListInterface + { + if (\is_string($value)) { + $value = new PropertyPath($value); + } + + if ($value instanceof PropertyPathInterface) { + $accessor = $this->propertyAccessor; + // The callable may be invoked with a non-object/array value + // when such values are passed to + // ChoiceListInterface::getValuesForChoices(). Handle this case + // so that the call to getValue() doesn't break. + $value = static fn ($choice) => \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; + } + + if (\is_string($filter)) { + $filter = new PropertyPath($filter); + } + + if ($filter instanceof PropertyPath) { + $accessor = $this->propertyAccessor; + $filter = static fn ($choice) => (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); + } + + return $this->decoratedFactory->createListFromLoader($loader, $value, $filter); + } + + public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView + { + $accessor = $this->propertyAccessor; + + if (\is_string($label)) { + $label = new PropertyPath($label); + } + + if ($label instanceof PropertyPathInterface) { + $label = static fn ($choice) => $accessor->getValue($choice, $label); + } + + if (\is_string($preferredChoices)) { + $preferredChoices = new PropertyPath($preferredChoices); + } + + if ($preferredChoices instanceof PropertyPathInterface) { + $preferredChoices = static function ($choice) use ($accessor, $preferredChoices) { + try { + return $accessor->getValue($choice, $preferredChoices); + } catch (UnexpectedTypeException) { + // Assume not preferred if not readable + return false; + } + }; + } + + if (\is_string($index)) { + $index = new PropertyPath($index); + } + + if ($index instanceof PropertyPathInterface) { + $index = static fn ($choice) => $accessor->getValue($choice, $index); + } + + if (\is_string($groupBy)) { + $groupBy = new PropertyPath($groupBy); + } + + if ($groupBy instanceof PropertyPathInterface) { + $groupBy = static function ($choice) use ($accessor, $groupBy) { + try { + return $accessor->getValue($choice, $groupBy); + } catch (UnexpectedTypeException) { + // Don't group if path is not readable + return null; + } + }; + } + + if (\is_string($attr)) { + $attr = new PropertyPath($attr); + } + + if ($attr instanceof PropertyPathInterface) { + $attr = static fn ($choice) => $accessor->getValue($choice, $attr); + } + + if (\is_string($labelTranslationParameters)) { + $labelTranslationParameters = new PropertyPath($labelTranslationParameters); + } + + if ($labelTranslationParameters instanceof PropertyPath) { + $labelTranslationParameters = static fn ($choice) => $accessor->getValue($choice, $labelTranslationParameters); + } + + return $this->decoratedFactory->createView( + $list, + $preferredChoices, + $label, + $index, + $groupBy, + $attr, + $labelTranslationParameters, + $duplicatePreferredChoices, + ); + } +} diff --git a/vendor/symfony/form/ChoiceList/LazyChoiceList.php b/vendor/symfony/form/ChoiceList/LazyChoiceList.php new file mode 100644 index 0000000..8e0eaaa --- /dev/null +++ b/vendor/symfony/form/ChoiceList/LazyChoiceList.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; + +/** + * A choice list that loads its choices lazily. + * + * The choices are fetched using a {@link ChoiceLoaderInterface} instance. + * If only {@link getChoicesForValues()} or {@link getValuesForChoices()} is + * called, the choice list is only loaded partially for improved performance. + * + * Once {@link getChoices()} or {@link getValues()} is called, the list is + * loaded fully. + * + * @author Bernhard Schussek + */ +class LazyChoiceList implements ChoiceListInterface +{ + /** + * The callable creating string values for each choice. + * + * If null, choices are cast to strings. + */ + private ?\Closure $value; + + /** + * Creates a lazily-loaded list using the given loader. + * + * Optionally, a callable can be passed for generating the choice values. + * The callable receives the choice as first and the array key as the second + * argument. + * + * @param callable|null $value The callable creating string values for each choice. + * If null, choices are cast to strings. + */ + public function __construct( + private ChoiceLoaderInterface $loader, + ?callable $value = null, + ) { + $this->value = null === $value ? null : $value(...); + } + + public function getChoices(): array + { + return $this->loader->loadChoiceList($this->value)->getChoices(); + } + + public function getValues(): array + { + return $this->loader->loadChoiceList($this->value)->getValues(); + } + + public function getStructuredValues(): array + { + return $this->loader->loadChoiceList($this->value)->getStructuredValues(); + } + + public function getOriginalKeys(): array + { + return $this->loader->loadChoiceList($this->value)->getOriginalKeys(); + } + + public function getChoicesForValues(array $values): array + { + return $this->loader->loadChoicesForValues($values, $this->value); + } + + public function getValuesForChoices(array $choices): array + { + return $this->loader->loadValuesForChoices($choices, $this->value); + } +} diff --git a/vendor/symfony/form/ChoiceList/Loader/AbstractChoiceLoader.php b/vendor/symfony/form/ChoiceList/Loader/AbstractChoiceLoader.php new file mode 100644 index 0000000..749e2fb --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Loader/AbstractChoiceLoader.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Loader; + +use Symfony\Component\Form\ChoiceList\ArrayChoiceList; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; + +/** + * @author Jules Pietri + */ +abstract class AbstractChoiceLoader implements ChoiceLoaderInterface +{ + private ?iterable $choices; + + /** + * @final + */ + public function loadChoiceList(?callable $value = null): ChoiceListInterface + { + return new ArrayChoiceList($this->choices ??= $this->loadChoices(), $value); + } + + public function loadChoicesForValues(array $values, ?callable $value = null): array + { + if (!$values) { + return []; + } + + return $this->doLoadChoicesForValues($values, $value); + } + + public function loadValuesForChoices(array $choices, ?callable $value = null): array + { + if (!$choices) { + return []; + } + + if ($value) { + // if a value callback exists, use it + return array_map(fn ($item) => (string) $value($item), $choices); + } + + return $this->doLoadValuesForChoices($choices); + } + + abstract protected function loadChoices(): iterable; + + protected function doLoadChoicesForValues(array $values, ?callable $value): array + { + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + protected function doLoadValuesForChoices(array $choices): array + { + return $this->loadChoiceList()->getValuesForChoices($choices); + } +} diff --git a/vendor/symfony/form/ChoiceList/Loader/CallbackChoiceLoader.php b/vendor/symfony/form/ChoiceList/Loader/CallbackChoiceLoader.php new file mode 100644 index 0000000..088f91d --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Loader/CallbackChoiceLoader.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Loader; + +/** + * Loads an {@link ArrayChoiceList} instance from a callable returning iterable choices. + * + * @author Jules Pietri + */ +class CallbackChoiceLoader extends AbstractChoiceLoader +{ + private \Closure $callback; + + /** + * @param callable $callback The callable returning iterable choices + */ + public function __construct(callable $callback) + { + $this->callback = $callback(...); + } + + protected function loadChoices(): iterable + { + return ($this->callback)(); + } +} diff --git a/vendor/symfony/form/ChoiceList/Loader/ChoiceLoaderInterface.php b/vendor/symfony/form/ChoiceList/Loader/ChoiceLoaderInterface.php new file mode 100644 index 0000000..d5f803c --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Loader/ChoiceLoaderInterface.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Loader; + +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; + +/** + * Loads a choice list. + * + * The methods {@link loadChoicesForValues()} and {@link loadValuesForChoices()} + * can be used to load the list only partially in cases where a fully-loaded + * list is not necessary. + * + * @author Bernhard Schussek + */ +interface ChoiceLoaderInterface +{ + /** + * Loads a list of choices. + * + * Optionally, a callable can be passed for generating the choice values. + * The callable receives the choice as only argument. + * Null may be passed when the choice list contains the empty value. + * + * @param callable|null $value The callable which generates the values + * from choices + */ + public function loadChoiceList(?callable $value = null): ChoiceListInterface; + + /** + * Loads the choices corresponding to the given values. + * + * The choices are returned with the same keys and in the same order as the + * corresponding values in the given array. + * + * Optionally, a callable can be passed for generating the choice values. + * The callable receives the choice as only argument. + * Null may be passed when the choice list contains the empty value. + * + * @param string[] $values An array of choice values. Non-existing + * values in this array are ignored + * @param callable|null $value The callable generating the choice values + */ + public function loadChoicesForValues(array $values, ?callable $value = null): array; + + /** + * Loads the values corresponding to the given choices. + * + * The values are returned with the same keys and in the same order as the + * corresponding choices in the given array. + * + * Optionally, a callable can be passed for generating the choice values. + * The callable receives the choice as only argument. + * Null may be passed when the choice list contains the empty value. + * + * @param array $choices An array of choices. Non-existing choices in + * this array are ignored + * @param callable|null $value The callable generating the choice values + * + * @return string[] + */ + public function loadValuesForChoices(array $choices, ?callable $value = null): array; +} diff --git a/vendor/symfony/form/ChoiceList/Loader/FilterChoiceLoaderDecorator.php b/vendor/symfony/form/ChoiceList/Loader/FilterChoiceLoaderDecorator.php new file mode 100644 index 0000000..393c73e --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Loader/FilterChoiceLoaderDecorator.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Loader; + +/** + * A decorator to filter choices only when they are loaded or partially loaded. + * + * @author Jules Pietri + */ +class FilterChoiceLoaderDecorator extends AbstractChoiceLoader +{ + private ChoiceLoaderInterface $decoratedLoader; + private \Closure $filter; + + public function __construct(ChoiceLoaderInterface $loader, callable $filter) + { + $this->decoratedLoader = $loader; + $this->filter = $filter(...); + } + + protected function loadChoices(): iterable + { + $list = $this->decoratedLoader->loadChoiceList(); + + if (array_values($list->getValues()) === array_values($structuredValues = $list->getStructuredValues())) { + return array_filter(array_combine($list->getOriginalKeys(), $list->getChoices()), $this->filter); + } + + foreach ($structuredValues as $group => $values) { + if (\is_array($values)) { + if ($values && $filtered = array_filter($list->getChoicesForValues($values), $this->filter)) { + $choices[$group] = $filtered; + } + continue; + // filter empty groups + } + + if ($filtered = array_filter($list->getChoicesForValues([$values]), $this->filter)) { + $choices[$group] = $filtered[0]; + } + } + + return $choices ?? []; + } + + public function loadChoicesForValues(array $values, ?callable $value = null): array + { + return array_filter($this->decoratedLoader->loadChoicesForValues($values, $value), $this->filter); + } + + public function loadValuesForChoices(array $choices, ?callable $value = null): array + { + return $this->decoratedLoader->loadValuesForChoices(array_filter($choices, $this->filter), $value); + } +} diff --git a/vendor/symfony/form/ChoiceList/Loader/IntlCallbackChoiceLoader.php b/vendor/symfony/form/ChoiceList/Loader/IntlCallbackChoiceLoader.php new file mode 100644 index 0000000..0931d3e --- /dev/null +++ b/vendor/symfony/form/ChoiceList/Loader/IntlCallbackChoiceLoader.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Loader; + +/** + * Callback choice loader optimized for Intl choice types. + * + * @author Jules Pietri + * @author Yonel Ceruto + */ +class IntlCallbackChoiceLoader extends CallbackChoiceLoader +{ + public function loadChoicesForValues(array $values, ?callable $value = null): array + { + return parent::loadChoicesForValues(array_filter($values), $value); + } + + public function loadValuesForChoices(array $choices, ?callable $value = null): array + { + $choices = array_filter($choices); + + // If no callable is set, choices are the same as values + if (null === $value) { + return $choices; + } + + return parent::loadValuesForChoices($choices, $value); + } +} diff --git a/vendor/symfony/form/ChoiceList/View/ChoiceGroupView.php b/vendor/symfony/form/ChoiceList/View/ChoiceGroupView.php new file mode 100644 index 0000000..923fab2 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/View/ChoiceGroupView.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\View; + +/** + * Represents a group of choices in templates. + * + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class ChoiceGroupView implements \IteratorAggregate +{ + /** + * Creates a new choice group view. + * + * @param array $choices the choice views in the group + */ + public function __construct( + public string $label, + public array $choices = [], + ) { + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->choices); + } +} diff --git a/vendor/symfony/form/ChoiceList/View/ChoiceListView.php b/vendor/symfony/form/ChoiceList/View/ChoiceListView.php new file mode 100644 index 0000000..f64d10e --- /dev/null +++ b/vendor/symfony/form/ChoiceList/View/ChoiceListView.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\View; + +/** + * Represents a choice list in templates. + * + * A choice list contains choices and optionally preferred choices which are + * displayed in the very beginning of the list. Both choices and preferred + * choices may be grouped in {@link ChoiceGroupView} instances. + * + * @author Bernhard Schussek + */ +class ChoiceListView +{ + /** + * Creates a new choice list view. + * + * @param array $choices The choice views + * @param array $preferredChoices the preferred choice views + */ + public function __construct( + public array $choices = [], + public array $preferredChoices = [], + ) { + } + + /** + * Returns whether a placeholder is in the choices. + * + * A placeholder must be the first child element, not be in a group and have an empty value. + */ + public function hasPlaceholder(): bool + { + if ($this->preferredChoices) { + $firstChoice = reset($this->preferredChoices); + + return $firstChoice instanceof ChoiceView && '' === $firstChoice->value; + } + + $firstChoice = reset($this->choices); + + return $firstChoice instanceof ChoiceView && '' === $firstChoice->value; + } +} diff --git a/vendor/symfony/form/ChoiceList/View/ChoiceView.php b/vendor/symfony/form/ChoiceList/View/ChoiceView.php new file mode 100644 index 0000000..d59a7c9 --- /dev/null +++ b/vendor/symfony/form/ChoiceList/View/ChoiceView.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\View; + +use Symfony\Contracts\Translation\TranslatableInterface; + +/** + * Represents a choice in templates. + * + * @author Bernhard Schussek + */ +class ChoiceView +{ + /** + * Creates a new choice view. + * + * @param mixed $data The original choice + * @param string $value The view representation of the choice + * @param string|TranslatableInterface|false $label The label displayed to humans; pass false to discard the label + * @param array $attr Additional attributes for the HTML tag + * @param array $labelTranslationParameters Additional parameters used to translate the label + */ + public function __construct( + public mixed $data, + public string $value, + public string|TranslatableInterface|false $label, + public array $attr = [], + public array $labelTranslationParameters = [], + ) { + } +} diff --git a/vendor/symfony/form/ClearableErrorsInterface.php b/vendor/symfony/form/ClearableErrorsInterface.php new file mode 100644 index 0000000..a05ece0 --- /dev/null +++ b/vendor/symfony/form/ClearableErrorsInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A form element whose errors can be cleared. + * + * @author Colin O'Dell + */ +interface ClearableErrorsInterface +{ + /** + * Removes all the errors of this form. + * + * @param bool $deep Whether to remove errors from child forms as well + * + * @return $this + */ + public function clearErrors(bool $deep = false): static; +} diff --git a/vendor/symfony/form/ClickableInterface.php b/vendor/symfony/form/ClickableInterface.php new file mode 100644 index 0000000..9be7de0 --- /dev/null +++ b/vendor/symfony/form/ClickableInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A clickable form element. + * + * @author Bernhard Schussek + */ +interface ClickableInterface +{ + /** + * Returns whether this element was clicked. + */ + public function isClicked(): bool; +} diff --git a/vendor/symfony/form/Command/DebugCommand.php b/vendor/symfony/form/Command/DebugCommand.php new file mode 100644 index 0000000..4d99012 --- /dev/null +++ b/vendor/symfony/form/Command/DebugCommand.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\Form\Console\Helper\DescriptorHelper; +use Symfony\Component\Form\Extension\Core\CoreExtension; +use Symfony\Component\Form\FormRegistryInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A console command for retrieving information about form types. + * + * @author Yonel Ceruto + */ +#[AsCommand(name: 'debug:form', description: 'Display form type information')] +class DebugCommand extends Command +{ + public function __construct( + private FormRegistryInterface $formRegistry, + private array $namespaces = ['Symfony\Component\Form\Extension\Core\Type'], + private array $types = [], + private array $extensions = [], + private array $guessers = [], + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('class', InputArgument::OPTIONAL, 'The form type class'), + new InputArgument('option', InputArgument::OPTIONAL, 'The form type option'), + new InputOption('show-deprecated', null, InputOption::VALUE_NONE, 'Display deprecated options in form types'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command displays information about form types. + + php %command.full_name% + +The command lists all built-in types, services types, type extensions and +guessers currently available. + + php %command.full_name% Symfony\Component\Form\Extension\Core\Type\ChoiceType + php %command.full_name% ChoiceType + +The command lists all defined options that contains the given form type, +as well as their parents and type extensions. + + php %command.full_name% ChoiceType choice_value + +Use the --show-deprecated option to display form types with +deprecated options or the deprecated options of the given form type: + + php %command.full_name% --show-deprecated + php %command.full_name% ChoiceType --show-deprecated + +The command displays the definition of the given option name. + + php %command.full_name% --format=json + +The command lists everything in a machine readable json format. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + if (null === $class = $input->getArgument('class')) { + $object = null; + $options['core_types'] = $this->getCoreTypes(); + $options['service_types'] = array_values(array_diff($this->types, $options['core_types'])); + if ($input->getOption('show-deprecated')) { + $options['core_types'] = $this->filterTypesByDeprecated($options['core_types']); + $options['service_types'] = $this->filterTypesByDeprecated($options['service_types']); + } + $options['extensions'] = $this->extensions; + $options['guessers'] = $this->guessers; + foreach ($options as $k => $list) { + sort($options[$k]); + } + } else { + if (!class_exists($class) || !is_subclass_of($class, FormTypeInterface::class)) { + $class = $this->getFqcnTypeClass($input, $io, $class); + } + $resolvedType = $this->formRegistry->getType($class); + + if ($option = $input->getArgument('option')) { + $object = $resolvedType->getOptionsResolver(); + + if (!$object->isDefined($option)) { + $message = sprintf('Option "%s" is not defined in "%s".', $option, $resolvedType->getInnerType()::class); + + if ($alternatives = $this->findAlternatives($option, $object->getDefinedOptions())) { + if (1 === \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new InvalidArgumentException($message); + } + + $options['type'] = $resolvedType->getInnerType(); + $options['option'] = $option; + } else { + $object = $resolvedType; + } + } + + $helper = new DescriptorHelper($this->fileLinkFormatter); + $options['format'] = $input->getOption('format'); + $options['show_deprecated'] = $input->getOption('show-deprecated'); + $helper->describe($io, $object, $options); + + return 0; + } + + private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, string $shortClassName): string + { + $classes = $this->getFqcnTypeClasses($shortClassName); + + if (0 === $count = \count($classes)) { + $message = sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces)); + + $allTypes = array_merge($this->getCoreTypes(), $this->types); + if ($alternatives = $this->findAlternatives($shortClassName, $allTypes)) { + if (1 === \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new InvalidArgumentException($message); + } + if (1 === $count) { + return $classes[0]; + } + if (!$input->isInteractive()) { + throw new InvalidArgumentException(sprintf("The type \"%s\" is ambiguous.\n\nDid you mean one of these?\n %s.", $shortClassName, implode("\n ", $classes))); + } + + return $io->choice(sprintf("The type \"%s\" is ambiguous.\n\nSelect one of the following form types to display its information:", $shortClassName), $classes, $classes[0]); + } + + private function getFqcnTypeClasses(string $shortClassName): array + { + $classes = []; + sort($this->namespaces); + foreach ($this->namespaces as $namespace) { + if (class_exists($fqcn = $namespace.'\\'.$shortClassName)) { + $classes[] = $fqcn; + } elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName))) { + $classes[] = $fqcn; + } elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName).'Type')) { + $classes[] = $fqcn; + } elseif (str_ends_with($shortClassName, 'type') && class_exists($fqcn = $namespace.'\\'.ucfirst(substr($shortClassName, 0, -4).'Type'))) { + $classes[] = $fqcn; + } + } + + return $classes; + } + + private function getCoreTypes(): array + { + $coreExtension = new CoreExtension(); + $loadTypesRefMethod = (new \ReflectionObject($coreExtension))->getMethod('loadTypes'); + $coreTypes = $loadTypesRefMethod->invoke($coreExtension); + $coreTypes = array_map(static fn (FormTypeInterface $type) => $type::class, $coreTypes); + sort($coreTypes); + + return $coreTypes; + } + + private function filterTypesByDeprecated(array $types): array + { + $typesWithDeprecatedOptions = []; + foreach ($types as $class) { + $optionsResolver = $this->formRegistry->getType($class)->getOptionsResolver(); + foreach ($optionsResolver->getDefinedOptions() as $option) { + if ($optionsResolver->isDeprecated($option)) { + $typesWithDeprecatedOptions[] = $class; + break; + } + } + } + + return $typesWithDeprecatedOptions; + } + + private function findAlternatives(string $name, array $collection): array + { + $alternatives = []; + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $threshold = 1e3; + $alternatives = array_filter($alternatives, static fn ($lev) => $lev < 2 * $threshold); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('class')) { + $suggestions->suggestValues(array_merge($this->getCoreTypes(), $this->types)); + + return; + } + + if ($input->mustSuggestArgumentValuesFor('option') && null !== $class = $input->getArgument('class')) { + $this->completeOptions($class, $suggestions); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function completeOptions(string $class, CompletionSuggestions $suggestions): void + { + if (!class_exists($class) || !is_subclass_of($class, FormTypeInterface::class)) { + $classes = $this->getFqcnTypeClasses($class); + + if (1 === \count($classes)) { + $class = $classes[0]; + } + } + + if (!$this->formRegistry->hasType($class)) { + return; + } + + $resolvedType = $this->formRegistry->getType($class); + $suggestions->suggestValues($resolvedType->getOptionsResolver()->getDefinedOptions()); + } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } +} diff --git a/vendor/symfony/form/Console/Descriptor/Descriptor.php b/vendor/symfony/form/Console/Descriptor/Descriptor.php new file mode 100644 index 0000000..f4835fb --- /dev/null +++ b/vendor/symfony/form/Console/Descriptor/Descriptor.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Console\Descriptor; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\OutputStyle; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Form\ResolvedFormTypeInterface; +use Symfony\Component\Form\Util\OptionsResolverWrapper; +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; +use Symfony\Component\OptionsResolver\Exception\NoConfigurationException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Yonel Ceruto + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + protected OutputStyle $output; + protected array $ownOptions = []; + protected array $overriddenOptions = []; + protected array $parentOptions = []; + protected array $extensionOptions = []; + protected array $requiredOptions = []; + protected array $parents = []; + protected array $extensions = []; + + public function describe(OutputInterface $output, ?object $object, array $options = []): void + { + $this->output = $output instanceof OutputStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); + + match (true) { + null === $object => $this->describeDefaults($options), + $object instanceof ResolvedFormTypeInterface => $this->describeResolvedFormType($object, $options), + $object instanceof OptionsResolver => $this->describeOption($object, $options), + default => throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))), + }; + } + + abstract protected function describeDefaults(array $options): void; + + abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []): void; + + abstract protected function describeOption(OptionsResolver $optionsResolver, array $options): void; + + protected function collectOptions(ResolvedFormTypeInterface $type): void + { + $this->parents = []; + $this->extensions = []; + + if (null !== $type->getParent()) { + $optionsResolver = clone $this->getParentOptionsResolver($type->getParent()); + } else { + $optionsResolver = new OptionsResolver(); + } + + $type->getInnerType()->configureOptions($ownOptionsResolver = new OptionsResolverWrapper()); + $this->ownOptions = array_diff($ownOptionsResolver->getDefinedOptions(), $optionsResolver->getDefinedOptions()); + $overriddenOptions = array_intersect(array_merge($ownOptionsResolver->getDefinedOptions(), $ownOptionsResolver->getUndefinedOptions()), $optionsResolver->getDefinedOptions()); + + $this->parentOptions = []; + foreach ($this->parents as $class => $parentOptions) { + $this->overriddenOptions[$class] = array_intersect($overriddenOptions, $parentOptions); + $this->parentOptions[$class] = array_diff($parentOptions, $overriddenOptions); + } + + $type->getInnerType()->configureOptions($optionsResolver); + $this->collectTypeExtensionsOptions($type, $optionsResolver); + $this->extensionOptions = []; + foreach ($this->extensions as $class => $extensionOptions) { + $this->overriddenOptions[$class] = array_intersect($overriddenOptions, $extensionOptions); + $this->extensionOptions[$class] = array_diff($extensionOptions, $overriddenOptions); + } + + $this->overriddenOptions = array_filter($this->overriddenOptions); + $this->parentOptions = array_filter($this->parentOptions); + $this->extensionOptions = array_filter($this->extensionOptions); + $this->requiredOptions = $optionsResolver->getRequiredOptions(); + + $this->parents = array_keys($this->parents); + $this->extensions = array_keys($this->extensions); + } + + protected function getOptionDefinition(OptionsResolver $optionsResolver, string $option): array + { + $definition = []; + + if ($info = $optionsResolver->getInfo($option)) { + $definition = [ + 'info' => $info, + ]; + } + + $definition += [ + 'required' => $optionsResolver->isRequired($option), + 'deprecated' => $optionsResolver->isDeprecated($option), + ]; + + $introspector = new OptionsResolverIntrospector($optionsResolver); + + $map = [ + 'default' => 'getDefault', + 'lazy' => 'getLazyClosures', + 'allowedTypes' => 'getAllowedTypes', + 'allowedValues' => 'getAllowedValues', + 'normalizers' => 'getNormalizers', + 'deprecation' => 'getDeprecation', + ]; + + foreach ($map as $key => $method) { + try { + $definition[$key] = $introspector->{$method}($option); + } catch (NoConfigurationException) { + // noop + } + } + + if (isset($definition['deprecation']['message']) && \is_string($definition['deprecation']['message'])) { + $definition['deprecationMessage'] = strtr($definition['deprecation']['message'], ['%name%' => $option]); + $definition['deprecationPackage'] = $definition['deprecation']['package']; + $definition['deprecationVersion'] = $definition['deprecation']['version']; + } + + return $definition; + } + + protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type): void + { + $deprecatedOptions = []; + $resolver = $type->getOptionsResolver(); + foreach ($resolver->getDefinedOptions() as $option) { + if ($resolver->isDeprecated($option)) { + $deprecatedOptions[] = $option; + } + } + + $filterByDeprecated = static function (array $options) use ($deprecatedOptions) { + foreach ($options as $class => $opts) { + if ($deprecated = array_intersect($deprecatedOptions, $opts)) { + $options[$class] = $deprecated; + } else { + unset($options[$class]); + } + } + + return $options; + }; + + $this->ownOptions = array_intersect($deprecatedOptions, $this->ownOptions); + $this->overriddenOptions = $filterByDeprecated($this->overriddenOptions); + $this->parentOptions = $filterByDeprecated($this->parentOptions); + $this->extensionOptions = $filterByDeprecated($this->extensionOptions); + } + + private function getParentOptionsResolver(ResolvedFormTypeInterface $type): OptionsResolver + { + $this->parents[$class = $type->getInnerType()::class] = []; + + if (null !== $type->getParent()) { + $optionsResolver = clone $this->getParentOptionsResolver($type->getParent()); + } else { + $optionsResolver = new OptionsResolver(); + } + + $inheritedOptions = $optionsResolver->getDefinedOptions(); + $type->getInnerType()->configureOptions($optionsResolver); + $this->parents[$class] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions); + + $this->collectTypeExtensionsOptions($type, $optionsResolver); + + return $optionsResolver; + } + + private function collectTypeExtensionsOptions(ResolvedFormTypeInterface $type, OptionsResolver $optionsResolver): void + { + foreach ($type->getTypeExtensions() as $extension) { + $inheritedOptions = $optionsResolver->getDefinedOptions(); + $extension->configureOptions($optionsResolver); + $this->extensions[$extension::class] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions); + } + } +} diff --git a/vendor/symfony/form/Console/Descriptor/JsonDescriptor.php b/vendor/symfony/form/Console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000..1f5c7bf --- /dev/null +++ b/vendor/symfony/form/Console/Descriptor/JsonDescriptor.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Console\Descriptor; + +use Symfony\Component\Form\ResolvedFormTypeInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Yonel Ceruto + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + protected function describeDefaults(array $options): void + { + $data['builtin_form_types'] = $options['core_types']; + $data['service_form_types'] = $options['service_types']; + if (!$options['show_deprecated']) { + $data['type_extensions'] = $options['extensions']; + $data['type_guessers'] = $options['guessers']; + } + + $this->writeData($data, $options); + } + + protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []): void + { + $this->collectOptions($resolvedFormType); + + if ($options['show_deprecated']) { + $this->filterOptionsByDeprecated($resolvedFormType); + } + + $formOptions = [ + 'own' => $this->ownOptions, + 'overridden' => $this->overriddenOptions, + 'parent' => $this->parentOptions, + 'extension' => $this->extensionOptions, + 'required' => $this->requiredOptions, + ]; + $this->sortOptions($formOptions); + + $data = [ + 'class' => $resolvedFormType->getInnerType()::class, + 'block_prefix' => $resolvedFormType->getInnerType()->getBlockPrefix(), + 'options' => $formOptions, + 'parent_types' => $this->parents, + 'type_extensions' => $this->extensions, + ]; + + $this->writeData($data, $options); + } + + protected function describeOption(OptionsResolver $optionsResolver, array $options): void + { + $definition = $this->getOptionDefinition($optionsResolver, $options['option']); + + $map = []; + if ($definition['deprecated']) { + $map['deprecated'] = 'deprecated'; + if (\is_string($definition['deprecationMessage'])) { + $map['deprecation_message'] = 'deprecationMessage'; + } + } + $map += [ + 'info' => 'info', + 'required' => 'required', + 'default' => 'default', + 'allowed_types' => 'allowedTypes', + 'allowed_values' => 'allowedValues', + ]; + foreach ($map as $label => $name) { + if (\array_key_exists($name, $definition)) { + $data[$label] = $definition[$name]; + + if ('default' === $name) { + $data['is_lazy'] = isset($definition['lazy']); + } + } + } + $data['has_normalizer'] = isset($definition['normalizers']); + + $this->writeData($data, $options); + } + + private function writeData(array $data, array $options): void + { + $flags = $options['json_encoding'] ?? 0; + + $this->output->write(json_encode($data, $flags | \JSON_PRETTY_PRINT)."\n"); + } + + private function sortOptions(array &$options): void + { + foreach ($options as &$opts) { + $sorted = false; + foreach ($opts as &$opt) { + if (\is_array($opt)) { + sort($opt); + $sorted = true; + } + } + if (!$sorted) { + sort($opts); + } + } + } +} diff --git a/vendor/symfony/form/Console/Descriptor/TextDescriptor.php b/vendor/symfony/form/Console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000..7b723a0 --- /dev/null +++ b/vendor/symfony/form/Console/Descriptor/TextDescriptor.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Console\Descriptor; + +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\Form\ResolvedFormTypeInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Yonel Ceruto + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + public function __construct( + private readonly ?FileLinkFormatter $fileLinkFormatter = null, + ) { + } + + protected function describeDefaults(array $options): void + { + if ($options['core_types']) { + $this->output->section('Built-in form types (Symfony\Component\Form\Extension\Core\Type)'); + $shortClassNames = array_map(fn ($fqcn) => $this->formatClassLink($fqcn, \array_slice(explode('\\', $fqcn), -1)[0]), $options['core_types']); + for ($i = 0, $loopsMax = \count($shortClassNames); $i * 5 < $loopsMax; ++$i) { + $this->output->writeln(' '.implode(', ', \array_slice($shortClassNames, $i * 5, 5))); + } + } + + if ($options['service_types']) { + $this->output->section('Service form types'); + $this->output->listing(array_map($this->formatClassLink(...), $options['service_types'])); + } + + if (!$options['show_deprecated']) { + if ($options['extensions']) { + $this->output->section('Type extensions'); + $this->output->listing(array_map($this->formatClassLink(...), $options['extensions'])); + } + + if ($options['guessers']) { + $this->output->section('Type guessers'); + $this->output->listing(array_map($this->formatClassLink(...), $options['guessers'])); + } + } + } + + protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []): void + { + $this->collectOptions($resolvedFormType); + + if ($options['show_deprecated']) { + $this->filterOptionsByDeprecated($resolvedFormType); + } + + $formOptions = $this->normalizeAndSortOptionsColumns(array_filter([ + 'own' => $this->ownOptions, + 'overridden' => $this->overriddenOptions, + 'parent' => $this->parentOptions, + 'extension' => $this->extensionOptions, + ])); + + // setting headers and column order + $tableHeaders = array_intersect_key([ + 'own' => 'Options', + 'overridden' => 'Overridden options', + 'parent' => 'Parent options', + 'extension' => 'Extension options', + ], $formOptions); + + $this->output->title(sprintf('%s (Block prefix: "%s")', $resolvedFormType->getInnerType()::class, $resolvedFormType->getInnerType()->getBlockPrefix())); + + if ($formOptions) { + $this->output->table($tableHeaders, $this->buildTableRows($tableHeaders, $formOptions)); + } + + if ($this->parents) { + $this->output->section('Parent types'); + $this->output->listing(array_map($this->formatClassLink(...), $this->parents)); + } + + if ($this->extensions) { + $this->output->section('Type extensions'); + $this->output->listing(array_map($this->formatClassLink(...), $this->extensions)); + } + } + + protected function describeOption(OptionsResolver $optionsResolver, array $options): void + { + $definition = $this->getOptionDefinition($optionsResolver, $options['option']); + + $dump = new Dumper($this->output); + $map = []; + if ($definition['deprecated']) { + $map = [ + 'Deprecated' => 'deprecated', + 'Deprecation package' => 'deprecationPackage', + 'Deprecation version' => 'deprecationVersion', + 'Deprecation message' => 'deprecationMessage', + ]; + } + $map += [ + 'Info' => 'info', + 'Required' => 'required', + 'Default' => 'default', + 'Allowed types' => 'allowedTypes', + 'Allowed values' => 'allowedValues', + 'Normalizers' => 'normalizers', + ]; + $rows = []; + foreach ($map as $label => $name) { + $value = \array_key_exists($name, $definition) ? $dump($definition[$name]) : '-'; + if ('default' === $name && isset($definition['lazy'])) { + $value = "Value: $value\n\nClosure(s): ".$dump($definition['lazy']); + } + + $rows[] = ["$label", $value]; + $rows[] = new TableSeparator(); + } + array_pop($rows); + + $this->output->title(sprintf('%s (%s)', $options['type']::class, $options['option'])); + $this->output->table([], $rows); + } + + private function buildTableRows(array $headers, array $options): array + { + $tableRows = []; + $count = \count(max($options)); + for ($i = 0; $i < $count; ++$i) { + $cells = []; + foreach (array_keys($headers) as $group) { + $option = $options[$group][$i] ?? null; + if (\is_string($option) && \in_array($option, $this->requiredOptions, true)) { + $option .= ' (required)'; + } + $cells[] = $option; + } + $tableRows[] = $cells; + } + + return $tableRows; + } + + private function normalizeAndSortOptionsColumns(array $options): array + { + foreach ($options as $group => $opts) { + $sorted = false; + foreach ($opts as $class => $opt) { + if (\is_string($class)) { + unset($options[$group][$class]); + } + + if (!\is_array($opt) || 0 === \count($opt)) { + continue; + } + + if (!$sorted) { + $options[$group] = []; + } else { + $options[$group][] = null; + } + $options[$group][] = sprintf('%s', (new \ReflectionClass($class))->getShortName()); + $options[$group][] = new TableSeparator(); + + sort($opt); + $sorted = true; + $options[$group] = array_merge($options[$group], $opt); + } + + if (!$sorted) { + sort($options[$group]); + } + } + + return $options; + } + + private function formatClassLink(string $class, ?string $text = null): string + { + $text ??= $class; + + if ('' === $fileLink = $this->getFileLink($class)) { + return $text; + } + + return sprintf('%s', $fileLink, $text); + } + + private function getFileLink(string $class): string + { + if (null === $this->fileLinkFormatter) { + return ''; + } + + try { + $r = new \ReflectionClass($class); + } catch (\ReflectionException) { + return ''; + } + + return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); + } +} diff --git a/vendor/symfony/form/Console/Helper/DescriptorHelper.php b/vendor/symfony/form/Console/Helper/DescriptorHelper.php new file mode 100644 index 0000000..776b9ea --- /dev/null +++ b/vendor/symfony/form/Console/Helper/DescriptorHelper.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Console\Helper; + +use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\Form\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Form\Console\Descriptor\TextDescriptor; + +/** + * @author Yonel Ceruto + * + * @internal + */ +class DescriptorHelper extends BaseDescriptorHelper +{ + public function __construct(?FileLinkFormatter $fileLinkFormatter = null) + { + $this + ->register('txt', new TextDescriptor($fileLinkFormatter)) + ->register('json', new JsonDescriptor()) + ; + } +} diff --git a/vendor/symfony/form/DataAccessorInterface.php b/vendor/symfony/form/DataAccessorInterface.php new file mode 100644 index 0000000..a0aea7e --- /dev/null +++ b/vendor/symfony/form/DataAccessorInterface.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Writes and reads values to/from an object or array bound to a form. + * + * @author Yonel Ceruto + */ +interface DataAccessorInterface +{ + /** + * Returns the value at the end of the property of the object graph. + * + * @throws Exception\AccessException If unable to read from the given form data + */ + public function getValue(object|array $viewData, FormInterface $form): mixed; + + /** + * Sets the value at the end of the property of the object graph. + * + * @throws Exception\AccessException If unable to write the given value + */ + public function setValue(object|array &$viewData, mixed $value, FormInterface $form): void; + + /** + * Returns whether a value can be read from an object graph. + * + * Whenever this method returns true, {@link getValue()} is guaranteed not + * to throw an exception when called with the same arguments. + */ + public function isReadable(object|array $viewData, FormInterface $form): bool; + + /** + * Returns whether a value can be written at a given object graph. + * + * Whenever this method returns true, {@link setValue()} is guaranteed not + * to throw an exception when called with the same arguments. + */ + public function isWritable(object|array $viewData, FormInterface $form): bool; +} diff --git a/vendor/symfony/form/DataMapperInterface.php b/vendor/symfony/form/DataMapperInterface.php new file mode 100644 index 0000000..b8ca86b --- /dev/null +++ b/vendor/symfony/form/DataMapperInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @author Bernhard Schussek + */ +interface DataMapperInterface +{ + /** + * Maps the view data of a compound form to its children. + * + * The method is responsible for calling {@link FormInterface::setData()} + * on the children of compound forms, defining their underlying model data. + * + * @param mixed $viewData View data of the compound form being initialized + * @param \Traversable $forms A list of {@link FormInterface} instances + * + * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported + */ + public function mapDataToForms(mixed $viewData, \Traversable $forms): void; + + /** + * Maps the model data of a list of children forms into the view data of their parent. + * + * This is the internal cascade call of FormInterface::submit for compound forms, since they + * cannot be bound to any input nor the request as scalar, but their children may: + * + * $compoundForm->submit($arrayOfChildrenViewData) + * // inside: + * $childForm->submit($childViewData); + * // for each entry, do the same and/or reverse transform + * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) + * // then reverse transform + * + * When a simple form is submitted the following is happening: + * + * $simpleForm->submit($submittedViewData) + * // inside: + * $this->viewData = $submittedViewData + * // then reverse transform + * + * The model data can be an array or an object, so this second argument is always passed + * by reference. + * + * @param \Traversable $forms A list of {@link FormInterface} instances + * @param mixed &$viewData The compound form's view data that get mapped + * its children model data + * + * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported + */ + public function mapFormsToData(\Traversable $forms, mixed &$viewData): void; +} diff --git a/vendor/symfony/form/DataTransformerInterface.php b/vendor/symfony/form/DataTransformerInterface.php new file mode 100644 index 0000000..aeab6f2 --- /dev/null +++ b/vendor/symfony/form/DataTransformerInterface.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms a value between different representations. + * + * @author Bernhard Schussek + * + * @template TValue + * @template TTransformedValue + */ +interface DataTransformerInterface +{ + /** + * Transforms a value from the original representation to a transformed representation. + * + * This method is called when the form field is initialized with its default data, on + * two occasions for two types of transformers: + * + * 1. Model transformers which normalize the model data. + * This is mainly useful when the same form type (the same configuration) + * has to handle different kind of underlying data, e.g The DateType can + * deal with strings or \DateTime objects as input. + * + * 2. View transformers which adapt the normalized data to the view format. + * a/ When the form is simple, the value returned by convention is used + * directly in the view and thus can only be a string or an array. In + * this case the data class should be null. + * + * b/ When the form is compound the returned value should be an array or + * an object to be mapped to the children. Each property of the compound + * data will be used as model data by each child and will be transformed + * too. In this case data class should be the class of the object, or null + * when it is an array. + * + * All transformers are called in a configured order from model data to view value. + * At the end of this chain the view data will be validated against the data class + * setting. + * + * This method must be able to deal with empty values. Usually this will + * be NULL, but depending on your implementation other empty values are + * possible as well (such as empty strings). The reasoning behind this is + * that data transformers must be chainable. If the transform() method + * of the first data transformer outputs NULL, the second must be able to + * process that value. + * + * @param TValue|null $value The value in the original representation + * + * @return TTransformedValue|null + * + * @throws TransformationFailedException when the transformation fails + */ + public function transform(mixed $value): mixed; + + /** + * Transforms a value from the transformed representation to its original + * representation. + * + * This method is called when {@link Form::submit()} is called to transform the requests tainted data + * into an acceptable format. + * + * The same transformers are called in the reverse order so the responsibility is to + * return one of the types that would be expected as input of transform(). + * + * This method must be able to deal with empty values. Usually this will + * be an empty string, but depending on your implementation other empty + * values are possible as well (such as NULL). The reasoning behind + * this is that value transformers must be chainable. If the + * reverseTransform() method of the first value transformer outputs an + * empty string, the second value transformer must be able to process that + * value. + * + * By convention, reverseTransform() should return NULL if an empty string + * is passed. + * + * @param TTransformedValue|null $value The value in the transformed representation + * + * @return TValue|null + * + * @throws TransformationFailedException when the transformation fails + */ + public function reverseTransform(mixed $value): mixed; +} diff --git a/vendor/symfony/form/DependencyInjection/FormPass.php b/vendor/symfony/form/DependencyInjection/FormPass.php new file mode 100644 index 0000000..4087311 --- /dev/null +++ b/vendor/symfony/form/DependencyInjection/FormPass.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds all services with the tags "form.type", "form.type_extension" and + * "form.type_guesser" as arguments of the "form.extension" service. + * + * @author Bernhard Schussek + */ +class FormPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('form.extension')) { + return; + } + + $definition = $container->getDefinition('form.extension'); + $definition->replaceArgument(0, $this->processFormTypes($container)); + $definition->replaceArgument(1, $this->processFormTypeExtensions($container)); + $definition->replaceArgument(2, $this->processFormTypeGuessers($container)); + } + + private function processFormTypes(ContainerBuilder $container): Reference + { + // Get service locator argument + $servicesMap = []; + $namespaces = ['Symfony\Component\Form\Extension\Core\Type' => true]; + + // Builds an array with fully-qualified type class names as keys and service IDs as values + foreach ($container->findTaggedServiceIds('form.type', true) as $serviceId => $tag) { + // Add form type service to the service locator + $serviceDefinition = $container->getDefinition($serviceId); + $servicesMap[$formType = $serviceDefinition->getClass()] = new Reference($serviceId); + $namespaces[substr($formType, 0, strrpos($formType, '\\'))] = true; + } + + if ($container->hasDefinition('console.command.form_debug')) { + $commandDefinition = $container->getDefinition('console.command.form_debug'); + $commandDefinition->setArgument(1, array_keys($namespaces)); + $commandDefinition->setArgument(2, array_keys($servicesMap)); + } + + return ServiceLocatorTagPass::register($container, $servicesMap); + } + + private function processFormTypeExtensions(ContainerBuilder $container): array + { + $typeExtensions = []; + $typeExtensionsClasses = []; + foreach ($this->findAndSortTaggedServices('form.type_extension', $container) as $reference) { + $serviceId = (string) $reference; + $serviceDefinition = $container->getDefinition($serviceId); + + $tag = $serviceDefinition->getTag('form.type_extension'); + $typeExtensionClass = $container->getParameterBag()->resolveValue($serviceDefinition->getClass()); + + if (isset($tag[0]['extended_type'])) { + $typeExtensions[$tag[0]['extended_type']][] = new Reference($serviceId); + $typeExtensionsClasses[] = $typeExtensionClass; + } else { + $extendsTypes = false; + + $typeExtensionsClasses[] = $typeExtensionClass; + foreach ($typeExtensionClass::getExtendedTypes() as $extendedType) { + $typeExtensions[$extendedType][] = new Reference($serviceId); + $extendsTypes = true; + } + + if (!$extendsTypes) { + throw new InvalidArgumentException(sprintf('The getExtendedTypes() method for service "%s" does not return any extended types.', $serviceId)); + } + } + } + + foreach ($typeExtensions as $extendedType => $extensions) { + $typeExtensions[$extendedType] = new IteratorArgument($extensions); + } + + if ($container->hasDefinition('console.command.form_debug')) { + $commandDefinition = $container->getDefinition('console.command.form_debug'); + $commandDefinition->setArgument(3, $typeExtensionsClasses); + } + + return $typeExtensions; + } + + private function processFormTypeGuessers(ContainerBuilder $container): ArgumentInterface + { + $guessers = []; + $guessersClasses = []; + foreach ($container->findTaggedServiceIds('form.type_guesser', true) as $serviceId => $tags) { + $guessers[] = new Reference($serviceId); + + $serviceDefinition = $container->getDefinition($serviceId); + $guessersClasses[] = $serviceDefinition->getClass(); + } + + if ($container->hasDefinition('console.command.form_debug')) { + $commandDefinition = $container->getDefinition('console.command.form_debug'); + $commandDefinition->setArgument(4, $guessersClasses); + } + + return new IteratorArgument($guessers); + } +} diff --git a/vendor/symfony/form/Event/PostSetDataEvent.php b/vendor/symfony/form/Event/PostSetDataEvent.php new file mode 100644 index 0000000..5b6430a --- /dev/null +++ b/vendor/symfony/form/Event/PostSetDataEvent.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Event; + +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\FormEvent; + +/** + * This event is dispatched at the end of the Form::setData() method. + * + * It can be used to modify a form depending on the populated data (adding or + * removing fields dynamically). + */ +final class PostSetDataEvent extends FormEvent +{ + public function setData(mixed $data): never + { + throw new BadMethodCallException('Form data cannot be changed during "form.post_set_data", you should use "form.pre_set_data" instead.'); + } +} diff --git a/vendor/symfony/form/Event/PostSubmitEvent.php b/vendor/symfony/form/Event/PostSubmitEvent.php new file mode 100644 index 0000000..88cd5c4 --- /dev/null +++ b/vendor/symfony/form/Event/PostSubmitEvent.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Event; + +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\FormEvent; + +/** + * This event is dispatched after the Form::submit() + * once the model and view data have been denormalized. + * + * It can be used to fetch data after denormalization. + */ +final class PostSubmitEvent extends FormEvent +{ + public function setData(mixed $data): never + { + throw new BadMethodCallException('Form data cannot be changed during "form.post_submit", you should use "form.pre_submit" or "form.submit" instead.'); + } +} diff --git a/vendor/symfony/form/Event/PreSetDataEvent.php b/vendor/symfony/form/Event/PreSetDataEvent.php new file mode 100644 index 0000000..2644fda --- /dev/null +++ b/vendor/symfony/form/Event/PreSetDataEvent.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Event; + +use Symfony\Component\Form\FormEvent; + +/** + * This event is dispatched at the beginning of the Form::setData() method. + * + * It can be used to modify the data given during pre-population. + */ +final class PreSetDataEvent extends FormEvent +{ +} diff --git a/vendor/symfony/form/Event/PreSubmitEvent.php b/vendor/symfony/form/Event/PreSubmitEvent.php new file mode 100644 index 0000000..a72ac5d --- /dev/null +++ b/vendor/symfony/form/Event/PreSubmitEvent.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Event; + +use Symfony\Component\Form\FormEvent; + +/** + * This event is dispatched at the beginning of the Form::submit() method. + * + * It can be used to: + * - Change data from the request, before submitting the data to the form. + * - Add or remove form fields, before submitting the data to the form. + */ +final class PreSubmitEvent extends FormEvent +{ +} diff --git a/vendor/symfony/form/Event/SubmitEvent.php b/vendor/symfony/form/Event/SubmitEvent.php new file mode 100644 index 0000000..71d3b06 --- /dev/null +++ b/vendor/symfony/form/Event/SubmitEvent.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Event; + +use Symfony\Component\Form\FormEvent; + +/** + * This event is dispatched just before the Form::submit() method + * transforms back the normalized data to the model and view data. + * + * It can be used to change data from the normalized representation of the data. + */ +final class SubmitEvent extends FormEvent +{ +} diff --git a/vendor/symfony/form/Exception/AccessException.php b/vendor/symfony/form/Exception/AccessException.php new file mode 100644 index 0000000..ac712cc --- /dev/null +++ b/vendor/symfony/form/Exception/AccessException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +class AccessException extends RuntimeException +{ +} diff --git a/vendor/symfony/form/Exception/AlreadySubmittedException.php b/vendor/symfony/form/Exception/AlreadySubmittedException.php new file mode 100644 index 0000000..5e8c305 --- /dev/null +++ b/vendor/symfony/form/Exception/AlreadySubmittedException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Thrown when an operation is called that is not acceptable after submitting + * a form. + * + * @author Bernhard Schussek + */ +class AlreadySubmittedException extends LogicException +{ +} diff --git a/vendor/symfony/form/Exception/BadMethodCallException.php b/vendor/symfony/form/Exception/BadMethodCallException.php new file mode 100644 index 0000000..27649dd --- /dev/null +++ b/vendor/symfony/form/Exception/BadMethodCallException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Base BadMethodCallException for the Form component. + * + * @author Bernhard Schussek + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/form/Exception/ErrorMappingException.php b/vendor/symfony/form/Exception/ErrorMappingException.php new file mode 100644 index 0000000..a696849 --- /dev/null +++ b/vendor/symfony/form/Exception/ErrorMappingException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +class ErrorMappingException extends RuntimeException +{ +} diff --git a/vendor/symfony/form/Exception/ExceptionInterface.php b/vendor/symfony/form/Exception/ExceptionInterface.php new file mode 100644 index 0000000..69145f0 --- /dev/null +++ b/vendor/symfony/form/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Base ExceptionInterface for the Form component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/form/Exception/InvalidArgumentException.php b/vendor/symfony/form/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..a270e0c --- /dev/null +++ b/vendor/symfony/form/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Base InvalidArgumentException for the Form component. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/form/Exception/InvalidConfigurationException.php b/vendor/symfony/form/Exception/InvalidConfigurationException.php new file mode 100644 index 0000000..daa0c42 --- /dev/null +++ b/vendor/symfony/form/Exception/InvalidConfigurationException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +class InvalidConfigurationException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/form/Exception/LogicException.php b/vendor/symfony/form/Exception/LogicException.php new file mode 100644 index 0000000..8487802 --- /dev/null +++ b/vendor/symfony/form/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Base LogicException for Form component. + * + * @author Alexander Kotynia + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/form/Exception/OutOfBoundsException.php b/vendor/symfony/form/Exception/OutOfBoundsException.php new file mode 100644 index 0000000..44d3116 --- /dev/null +++ b/vendor/symfony/form/Exception/OutOfBoundsException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Base OutOfBoundsException for Form component. + * + * @author Alexander Kotynia + */ +class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/form/Exception/RuntimeException.php b/vendor/symfony/form/Exception/RuntimeException.php new file mode 100644 index 0000000..0af48a4 --- /dev/null +++ b/vendor/symfony/form/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Base RuntimeException for the Form component. + * + * @author Bernhard Schussek + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/form/Exception/StringCastException.php b/vendor/symfony/form/Exception/StringCastException.php new file mode 100644 index 0000000..f9b51d6 --- /dev/null +++ b/vendor/symfony/form/Exception/StringCastException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +class StringCastException extends RuntimeException +{ +} diff --git a/vendor/symfony/form/Exception/TransformationFailedException.php b/vendor/symfony/form/Exception/TransformationFailedException.php new file mode 100644 index 0000000..3973d70 --- /dev/null +++ b/vendor/symfony/form/Exception/TransformationFailedException.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Indicates a value transformation error. + * + * @author Bernhard Schussek + */ +class TransformationFailedException extends RuntimeException +{ + private ?string $invalidMessage; + private array $invalidMessageParameters; + + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, ?string $invalidMessage = null, array $invalidMessageParameters = []) + { + parent::__construct($message, $code, $previous); + + $this->setInvalidMessage($invalidMessage, $invalidMessageParameters); + } + + /** + * Sets the message that will be shown to the user. + * + * @param string|null $invalidMessage The message or message key + * @param array $invalidMessageParameters Data to be passed into the translator + */ + public function setInvalidMessage(?string $invalidMessage, array $invalidMessageParameters = []): void + { + $this->invalidMessage = $invalidMessage; + $this->invalidMessageParameters = $invalidMessageParameters; + } + + public function getInvalidMessage(): ?string + { + return $this->invalidMessage; + } + + public function getInvalidMessageParameters(): array + { + return $this->invalidMessageParameters; + } +} diff --git a/vendor/symfony/form/Exception/UnexpectedTypeException.php b/vendor/symfony/form/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..7a4dc29 --- /dev/null +++ b/vendor/symfony/form/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +class UnexpectedTypeException extends InvalidArgumentException +{ + public function __construct(mixed $value, string $expectedType) + { + parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, get_debug_type($value))); + } +} diff --git a/vendor/symfony/form/Extension/Core/CoreExtension.php b/vendor/symfony/form/Extension/Core/CoreExtension.php new file mode 100644 index 0000000..1640ed0 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/CoreExtension.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core; + +use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface; +use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; +use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; +use Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Represents the main form extension, which loads the core functionality. + * + * @author Bernhard Schussek + */ +class CoreExtension extends AbstractExtension +{ + private PropertyAccessorInterface $propertyAccessor; + private ChoiceListFactoryInterface $choiceListFactory; + + public function __construct( + ?PropertyAccessorInterface $propertyAccessor = null, + ?ChoiceListFactoryInterface $choiceListFactory = null, + private ?TranslatorInterface $translator = null, + ) { + $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + $this->choiceListFactory = $choiceListFactory ?? new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor)); + } + + protected function loadTypes(): array + { + return [ + new Type\FormType($this->propertyAccessor), + new Type\BirthdayType(), + new Type\CheckboxType(), + new Type\ChoiceType($this->choiceListFactory, $this->translator), + new Type\CollectionType(), + new Type\CountryType(), + new Type\DateIntervalType(), + new Type\DateType(), + new Type\DateTimeType(), + new Type\EmailType(), + new Type\HiddenType(), + new Type\IntegerType(), + new Type\LanguageType(), + new Type\LocaleType(), + new Type\MoneyType(), + new Type\NumberType(), + new Type\PasswordType(), + new Type\PercentType(), + new Type\RadioType(), + new Type\RangeType(), + new Type\RepeatedType(), + new Type\SearchType(), + new Type\TextareaType(), + new Type\TextType(), + new Type\TimeType(), + new Type\TimezoneType(), + new Type\UrlType(), + new Type\FileType($this->translator), + new Type\ButtonType(), + new Type\SubmitType(), + new Type\ResetType(), + new Type\CurrencyType(), + new Type\TelType(), + new Type\ColorType($this->translator), + new Type\WeekType(), + ]; + } + + protected function loadTypeExtensions(): array + { + return [ + new TransformationFailureExtension($this->translator), + ]; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataAccessor/CallbackAccessor.php b/vendor/symfony/form/Extension/Core/DataAccessor/CallbackAccessor.php new file mode 100644 index 0000000..a7d5bb1 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataAccessor/CallbackAccessor.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataAccessor; + +use Symfony\Component\Form\DataAccessorInterface; +use Symfony\Component\Form\Exception\AccessException; +use Symfony\Component\Form\FormInterface; + +/** + * Writes and reads values to/from an object or array using callback functions. + * + * @author Yonel Ceruto + */ +class CallbackAccessor implements DataAccessorInterface +{ + public function getValue(object|array $data, FormInterface $form): mixed + { + if (null === $getter = $form->getConfig()->getOption('getter')) { + throw new AccessException('Unable to read from the given form data as no getter is defined.'); + } + + return ($getter)($data, $form); + } + + public function setValue(object|array &$data, mixed $value, FormInterface $form): void + { + if (null === $setter = $form->getConfig()->getOption('setter')) { + throw new AccessException('Unable to write the given value as no setter is defined.'); + } + + ($setter)($data, $form->getData(), $form); + } + + public function isReadable(object|array $data, FormInterface $form): bool + { + return null !== $form->getConfig()->getOption('getter'); + } + + public function isWritable(object|array $data, FormInterface $form): bool + { + return null !== $form->getConfig()->getOption('setter'); + } +} diff --git a/vendor/symfony/form/Extension/Core/DataAccessor/ChainAccessor.php b/vendor/symfony/form/Extension/Core/DataAccessor/ChainAccessor.php new file mode 100644 index 0000000..e7b9da0 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataAccessor/ChainAccessor.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataAccessor; + +use Symfony\Component\Form\DataAccessorInterface; +use Symfony\Component\Form\Exception\AccessException; +use Symfony\Component\Form\FormInterface; + +/** + * @author Yonel Ceruto + */ +class ChainAccessor implements DataAccessorInterface +{ + /** + * @param DataAccessorInterface[]|iterable $accessors + */ + public function __construct( + private iterable $accessors, + ) { + } + + public function getValue(object|array $data, FormInterface $form): mixed + { + foreach ($this->accessors as $accessor) { + if ($accessor->isReadable($data, $form)) { + return $accessor->getValue($data, $form); + } + } + + throw new AccessException('Unable to read from the given form data as no accessor in the chain is able to read the data.'); + } + + public function setValue(object|array &$data, mixed $value, FormInterface $form): void + { + foreach ($this->accessors as $accessor) { + if ($accessor->isWritable($data, $form)) { + $accessor->setValue($data, $value, $form); + + return; + } + } + + throw new AccessException('Unable to write the given value as no accessor in the chain is able to set the data.'); + } + + public function isReadable(object|array $data, FormInterface $form): bool + { + foreach ($this->accessors as $accessor) { + if ($accessor->isReadable($data, $form)) { + return true; + } + } + + return false; + } + + public function isWritable(object|array $data, FormInterface $form): bool + { + foreach ($this->accessors as $accessor) { + if ($accessor->isWritable($data, $form)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataAccessor/PropertyPathAccessor.php b/vendor/symfony/form/Extension/Core/DataAccessor/PropertyPathAccessor.php new file mode 100644 index 0000000..33d01fd --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataAccessor/PropertyPathAccessor.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataAccessor; + +use Symfony\Component\Form\DataAccessorInterface; +use Symfony\Component\Form\DataMapperInterface; +use Symfony\Component\Form\Exception\AccessException; +use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\PropertyAccess\Exception\AccessException as PropertyAccessException; +use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * Writes and reads values to/from an object or array using property path. + * + * @author Yonel Ceruto + * @author Bernhard Schussek + */ +class PropertyPathAccessor implements DataAccessorInterface +{ + private PropertyAccessorInterface $propertyAccessor; + + public function __construct(?PropertyAccessorInterface $propertyAccessor = null) + { + $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); + } + + public function getValue(object|array $data, FormInterface $form): mixed + { + if (null === $propertyPath = $form->getPropertyPath()) { + throw new AccessException('Unable to read from the given form data as no property path is defined.'); + } + + return $this->getPropertyValue($data, $propertyPath); + } + + public function setValue(object|array &$data, mixed $value, FormInterface $form): void + { + if (null === $propertyPath = $form->getPropertyPath()) { + throw new AccessException('Unable to write the given value as no property path is defined.'); + } + + $getValue = function () use ($data, $form, $propertyPath) { + $dataMapper = $this->getDataMapper($form); + + if ($dataMapper instanceof DataMapper && null !== $dataAccessor = $dataMapper->getDataAccessor()) { + return $dataAccessor->getValue($data, $form); + } + + return $this->getPropertyValue($data, $propertyPath); + }; + + // If the field is of type DateTimeInterface and the data is the same skip the update to + // keep the original object hash + if ($value instanceof \DateTimeInterface && $value == $getValue()) { + return; + } + + // If the data is identical to the value in $data, we are + // dealing with a reference + if (!\is_object($data) || !$form->getConfig()->getByReference() || $value !== $getValue()) { + try { + $this->propertyAccessor->setValue($data, $propertyPath, $value); + } catch (NoSuchPropertyException $e) { + throw new NoSuchPropertyException($e->getMessage().' Make the property public, add a setter, or set the "mapped" field option in the form type to be false.', 0, $e); + } + } + } + + public function isReadable(object|array $data, FormInterface $form): bool + { + return null !== $form->getPropertyPath(); + } + + public function isWritable(object|array $data, FormInterface $form): bool + { + return null !== $form->getPropertyPath(); + } + + private function getPropertyValue(object|array $data, PropertyPathInterface $propertyPath): mixed + { + try { + return $this->propertyAccessor->getValue($data, $propertyPath); + } catch (PropertyAccessException $e) { + if (\is_array($data) && $e instanceof NoSuchIndexException) { + return null; + } + + if (!$e instanceof UninitializedPropertyException + // For versions without UninitializedPropertyException check the exception message + && (class_exists(UninitializedPropertyException::class) || !str_contains($e->getMessage(), 'You should initialize it')) + ) { + throw $e; + } + + return null; + } + } + + private function getDataMapper(FormInterface $form): ?DataMapperInterface + { + do { + $dataMapper = $form->getConfig()->getDataMapper(); + } while (null === $dataMapper && null !== $form = $form->getParent()); + + return $dataMapper; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataMapper/CheckboxListMapper.php b/vendor/symfony/form/Extension/Core/DataMapper/CheckboxListMapper.php new file mode 100644 index 0000000..eff6d16 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataMapper/CheckboxListMapper.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataMapper; + +use Symfony\Component\Form\DataMapperInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Maps choices to/from checkbox forms. + * + * A {@link ChoiceListInterface} implementation is used to find the + * corresponding string values for the choices. Each checkbox form whose "value" + * option corresponds to any of the selected values is marked as selected. + * + * @author Bernhard Schussek + */ +class CheckboxListMapper implements DataMapperInterface +{ + public function mapDataToForms(mixed $choices, \Traversable $checkboxes): void + { + if (!\is_array($choices ??= [])) { + throw new UnexpectedTypeException($choices, 'array'); + } + + foreach ($checkboxes as $checkbox) { + $value = $checkbox->getConfig()->getOption('value'); + $checkbox->setData(\in_array($value, $choices, true)); + } + } + + public function mapFormsToData(\Traversable $checkboxes, mixed &$choices): void + { + if (!\is_array($choices)) { + throw new UnexpectedTypeException($choices, 'array'); + } + + $values = []; + + foreach ($checkboxes as $checkbox) { + if ($checkbox->getData()) { + // construct an array of choice values + $values[] = $checkbox->getConfig()->getOption('value'); + } + } + + $choices = $values; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataMapper/DataMapper.php b/vendor/symfony/form/Extension/Core/DataMapper/DataMapper.php new file mode 100644 index 0000000..a7bf980 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataMapper/DataMapper.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataMapper; + +use Symfony\Component\Form\DataAccessorInterface; +use Symfony\Component\Form\DataMapperInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Extension\Core\DataAccessor\CallbackAccessor; +use Symfony\Component\Form\Extension\Core\DataAccessor\ChainAccessor; +use Symfony\Component\Form\Extension\Core\DataAccessor\PropertyPathAccessor; + +/** + * Maps arrays/objects to/from forms using data accessors. + * + * @author Bernhard Schussek + */ +class DataMapper implements DataMapperInterface +{ + private DataAccessorInterface $dataAccessor; + + public function __construct(?DataAccessorInterface $dataAccessor = null) + { + $this->dataAccessor = $dataAccessor ?? new ChainAccessor([ + new CallbackAccessor(), + new PropertyPathAccessor(), + ]); + } + + public function mapDataToForms(mixed $data, \Traversable $forms): void + { + $empty = null === $data || [] === $data; + + if (!$empty && !\is_array($data) && !\is_object($data)) { + throw new UnexpectedTypeException($data, 'object, array or empty'); + } + + foreach ($forms as $form) { + $config = $form->getConfig(); + + if (!$empty && $config->getMapped() && $this->dataAccessor->isReadable($data, $form)) { + $form->setData($this->dataAccessor->getValue($data, $form)); + } else { + $form->setData($config->getData()); + } + } + } + + public function mapFormsToData(\Traversable $forms, mixed &$data): void + { + if (null === $data) { + return; + } + + if (!\is_array($data) && !\is_object($data)) { + throw new UnexpectedTypeException($data, 'object, array or empty'); + } + + foreach ($forms as $form) { + $config = $form->getConfig(); + + // Write-back is disabled if the form is not synchronized (transformation failed), + // if the form was not submitted and if the form is disabled (modification not allowed) + if ($config->getMapped() && $form->isSubmitted() && $form->isSynchronized() && !$form->isDisabled() && $this->dataAccessor->isWritable($data, $form)) { + $this->dataAccessor->setValue($data, $form->getData(), $form); + } + } + } + + /** + * @internal + */ + public function getDataAccessor(): DataAccessorInterface + { + return $this->dataAccessor; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataMapper/RadioListMapper.php b/vendor/symfony/form/Extension/Core/DataMapper/RadioListMapper.php new file mode 100644 index 0000000..5313473 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataMapper/RadioListMapper.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataMapper; + +use Symfony\Component\Form\DataMapperInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Maps choices to/from radio forms. + * + * A {@link ChoiceListInterface} implementation is used to find the + * corresponding string values for the choices. The radio form whose "value" + * option corresponds to the selected value is marked as selected. + * + * @author Bernhard Schussek + */ +class RadioListMapper implements DataMapperInterface +{ + public function mapDataToForms(mixed $choice, \Traversable $radios): void + { + if (!\is_string($choice)) { + throw new UnexpectedTypeException($choice, 'string'); + } + + foreach ($radios as $radio) { + $value = $radio->getConfig()->getOption('value'); + $radio->setData($choice === $value); + } + } + + public function mapFormsToData(\Traversable $radios, mixed &$choice): void + { + if (null !== $choice && !\is_string($choice)) { + throw new UnexpectedTypeException($choice, 'null or string'); + } + + $choice = null; + + foreach ($radios as $radio) { + if ($radio->getData()) { + if ('placeholder' === $radio->getName()) { + return; + } + + $choice = $radio->getConfig()->getOption('value'); + + return; + } + } + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php new file mode 100644 index 0000000..828bd81 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + * + * @implements DataTransformerInterface + */ +class ArrayToPartsTransformer implements DataTransformerInterface +{ + public function __construct( + private array $partMapping, + ) { + } + + public function transform(mixed $array): mixed + { + if (!\is_array($array ??= [])) { + throw new TransformationFailedException('Expected an array.'); + } + + $result = []; + + foreach ($this->partMapping as $partKey => $originalKeys) { + if (!$array) { + $result[$partKey] = null; + } else { + $result[$partKey] = array_intersect_key($array, array_flip($originalKeys)); + } + } + + return $result; + } + + public function reverseTransform(mixed $array): mixed + { + if (!\is_array($array)) { + throw new TransformationFailedException('Expected an array.'); + } + + $result = []; + $emptyKeys = []; + + foreach ($this->partMapping as $partKey => $originalKeys) { + if (!empty($array[$partKey])) { + foreach ($originalKeys as $originalKey) { + if (isset($array[$partKey][$originalKey])) { + $result[$originalKey] = $array[$partKey][$originalKey]; + } + } + } else { + $emptyKeys[] = $partKey; + } + } + + if (\count($emptyKeys) > 0) { + if (\count($emptyKeys) === \count($this->partMapping)) { + // All parts empty + return null; + } + + throw new TransformationFailedException(sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys))); + } + + return $result; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php new file mode 100644 index 0000000..8d311b3 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\InvalidArgumentException; + +/** + * @template TTransformedValue + * + * @implements DataTransformerInterface<\DateTimeInterface, TTransformedValue> + */ +abstract class BaseDateTimeTransformer implements DataTransformerInterface +{ + protected static array $formats = [ + \IntlDateFormatter::NONE, + \IntlDateFormatter::FULL, + \IntlDateFormatter::LONG, + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::SHORT, + ]; + + protected string $inputTimezone; + protected string $outputTimezone; + + /** + * @param string|null $inputTimezone The name of the input timezone + * @param string|null $outputTimezone The name of the output timezone + * + * @throws InvalidArgumentException if a timezone is not valid + */ + public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null) + { + $this->inputTimezone = $inputTimezone ?: date_default_timezone_get(); + $this->outputTimezone = $outputTimezone ?: date_default_timezone_get(); + + // Check if input and output timezones are valid + try { + new \DateTimeZone($this->inputTimezone); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Input timezone is invalid: "%s".', $this->inputTimezone), $e->getCode(), $e); + } + + try { + new \DateTimeZone($this->outputTimezone); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Output timezone is invalid: "%s".', $this->outputTimezone), $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/BooleanToStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/BooleanToStringTransformer.php new file mode 100644 index 0000000..e91bdb4 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/BooleanToStringTransformer.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a Boolean and a string. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * + * @implements DataTransformerInterface + */ +class BooleanToStringTransformer implements DataTransformerInterface +{ + private string $trueValue; + + private array $falseValues; + + /** + * @param string $trueValue The value emitted upon transform if the input is true + */ + public function __construct(string $trueValue, array $falseValues = [null]) + { + $this->trueValue = $trueValue; + $this->falseValues = $falseValues; + if (\in_array($this->trueValue, $this->falseValues, true)) { + throw new InvalidArgumentException('The specified "true" value is contained in the false-values.'); + } + } + + /** + * Transforms a Boolean into a string. + * + * @param bool $value Boolean value + * + * @throws TransformationFailedException if the given value is not a Boolean + */ + public function transform(mixed $value): ?string + { + if (null === $value) { + return null; + } + + if (!\is_bool($value)) { + throw new TransformationFailedException('Expected a Boolean.'); + } + + return $value ? $this->trueValue : null; + } + + /** + * Transforms a string into a Boolean. + * + * @param string $value String value + * + * @throws TransformationFailedException if the given value is not a string + */ + public function reverseTransform(mixed $value): bool + { + if (\in_array($value, $this->falseValues, true)) { + return false; + } + + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + return true; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php new file mode 100644 index 0000000..52ee25e --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + * + * @implements DataTransformerInterface + */ +class ChoiceToValueTransformer implements DataTransformerInterface +{ + public function __construct( + private ChoiceListInterface $choiceList, + ) { + } + + public function transform(mixed $choice): mixed + { + return (string) current($this->choiceList->getValuesForChoices([$choice])); + } + + public function reverseTransform(mixed $value): mixed + { + if (null !== $value && !\is_string($value)) { + throw new TransformationFailedException('Expected a string or null.'); + } + + $choices = $this->choiceList->getChoicesForValues([(string) $value]); + + if (1 !== \count($choices)) { + if (null === $value || '' === $value) { + return null; + } + + throw new TransformationFailedException(sprintf('The choice "%s" does not exist or is not unique.', $value)); + } + + return current($choices); + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php new file mode 100644 index 0000000..aa22338 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + * + * @implements DataTransformerInterface + */ +class ChoicesToValuesTransformer implements DataTransformerInterface +{ + public function __construct( + private ChoiceListInterface $choiceList, + ) { + } + + /** + * @throws TransformationFailedException if the given value is not an array + */ + public function transform(mixed $array): array + { + if (null === $array) { + return []; + } + + if (!\is_array($array)) { + throw new TransformationFailedException('Expected an array.'); + } + + return $this->choiceList->getValuesForChoices($array); + } + + /** + * @throws TransformationFailedException if the given value is not an array + * or if no matching choice could be + * found for some given value + */ + public function reverseTransform(mixed $array): array + { + if (null === $array) { + return []; + } + + if (!\is_array($array)) { + throw new TransformationFailedException('Expected an array.'); + } + + $choices = $this->choiceList->getChoicesForValues($array); + + if (\count($choices) !== \count($array)) { + throw new TransformationFailedException('Could not find all matching choices for the given values.'); + } + + return $choices; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DataTransformerChain.php b/vendor/symfony/form/Extension/Core/DataTransformer/DataTransformerChain.php new file mode 100644 index 0000000..e34be74 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DataTransformerChain.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Passes a value through multiple value transformers. + * + * @author Bernhard Schussek + */ +class DataTransformerChain implements DataTransformerInterface +{ + /** + * Uses the given value transformers to transform values. + * + * @param DataTransformerInterface[] $transformers + */ + public function __construct( + protected array $transformers, + ) { + } + + /** + * Passes the value through the transform() method of all nested transformers. + * + * The transformers receive the value in the same order as they were passed + * to the constructor. Each transformer receives the result of the previous + * transformer as input. The output of the last transformer is returned + * by this method. + * + * @param mixed $value The original value + * + * @throws TransformationFailedException + */ + public function transform(mixed $value): mixed + { + foreach ($this->transformers as $transformer) { + $value = $transformer->transform($value); + } + + return $value; + } + + /** + * Passes the value through the reverseTransform() method of all nested + * transformers. + * + * The transformers receive the value in the reverse order as they were passed + * to the constructor. Each transformer receives the result of the previous + * transformer as input. The output of the last transformer is returned + * by this method. + * + * @param mixed $value The transformed value + * + * @throws TransformationFailedException + */ + public function reverseTransform(mixed $value): mixed + { + for ($i = \count($this->transformers) - 1; $i >= 0; --$i) { + $value = $this->transformers[$i]->reverseTransform($value); + } + + return $value; + } + + /** + * @return DataTransformerInterface[] + */ + public function getTransformers(): array + { + return $this->transformers; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php new file mode 100644 index 0000000..08c0585 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Transforms between a normalized date interval and an interval string/array. + * + * @author Steffen Roßkamp + * + * @implements DataTransformerInterface<\DateInterval, array> + */ +class DateIntervalToArrayTransformer implements DataTransformerInterface +{ + public const YEARS = 'years'; + public const MONTHS = 'months'; + public const DAYS = 'days'; + public const HOURS = 'hours'; + public const MINUTES = 'minutes'; + public const SECONDS = 'seconds'; + public const INVERT = 'invert'; + + private const AVAILABLE_FIELDS = [ + self::YEARS => 'y', + self::MONTHS => 'm', + self::DAYS => 'd', + self::HOURS => 'h', + self::MINUTES => 'i', + self::SECONDS => 's', + self::INVERT => 'r', + ]; + private array $fields; + + /** + * @param string[]|null $fields The date fields + * @param bool $pad Whether to use padding + */ + public function __construct( + ?array $fields = null, + private bool $pad = false, + ) { + $this->fields = $fields ?? ['years', 'months', 'days', 'hours', 'minutes', 'seconds', 'invert']; + } + + /** + * Transforms a normalized date interval into an interval array. + * + * @param \DateInterval $dateInterval Normalized date interval + * + * @throws UnexpectedTypeException if the given value is not a \DateInterval instance + */ + public function transform(mixed $dateInterval): array + { + if (null === $dateInterval) { + return array_intersect_key( + [ + 'years' => '', + 'months' => '', + 'weeks' => '', + 'days' => '', + 'hours' => '', + 'minutes' => '', + 'seconds' => '', + 'invert' => false, + ], + array_flip($this->fields) + ); + } + if (!$dateInterval instanceof \DateInterval) { + throw new UnexpectedTypeException($dateInterval, \DateInterval::class); + } + $result = []; + foreach (self::AVAILABLE_FIELDS as $field => $char) { + $result[$field] = $dateInterval->format('%'.($this->pad ? strtoupper($char) : $char)); + } + if (\in_array('weeks', $this->fields, true)) { + $result['weeks'] = '0'; + if (isset($result['days']) && (int) $result['days'] >= 7) { + $result['weeks'] = (string) floor($result['days'] / 7); + $result['days'] = (string) ($result['days'] % 7); + } + } + $result['invert'] = '-' === $result['invert']; + $result = array_intersect_key($result, array_flip($this->fields)); + + return $result; + } + + /** + * Transforms an interval array into a normalized date interval. + * + * @param array $value Interval array + * + * @throws UnexpectedTypeException if the given value is not an array + * @throws TransformationFailedException if the value could not be transformed + */ + public function reverseTransform(mixed $value): ?\DateInterval + { + if (null === $value) { + return null; + } + if (!\is_array($value)) { + throw new UnexpectedTypeException($value, 'array'); + } + if ('' === implode('', $value)) { + return null; + } + $emptyFields = []; + foreach ($this->fields as $field) { + if (!isset($value[$field])) { + $emptyFields[] = $field; + } + } + if (\count($emptyFields) > 0) { + throw new TransformationFailedException(sprintf('The fields "%s" should not be empty.', implode('", "', $emptyFields))); + } + if (isset($value['invert']) && !\is_bool($value['invert'])) { + throw new TransformationFailedException('The value of "invert" must be boolean.'); + } + foreach (self::AVAILABLE_FIELDS as $field => $char) { + if ('invert' !== $field && isset($value[$field]) && !ctype_digit((string) $value[$field])) { + throw new TransformationFailedException(sprintf('This amount of "%s" is invalid.', $field)); + } + } + try { + if (!empty($value['weeks'])) { + $interval = sprintf( + 'P%sY%sM%sWT%sH%sM%sS', + empty($value['years']) ? '0' : $value['years'], + empty($value['months']) ? '0' : $value['months'], + empty($value['weeks']) ? '0' : $value['weeks'], + empty($value['hours']) ? '0' : $value['hours'], + empty($value['minutes']) ? '0' : $value['minutes'], + empty($value['seconds']) ? '0' : $value['seconds'] + ); + } else { + $interval = sprintf( + 'P%sY%sM%sDT%sH%sM%sS', + empty($value['years']) ? '0' : $value['years'], + empty($value['months']) ? '0' : $value['months'], + empty($value['days']) ? '0' : $value['days'], + empty($value['hours']) ? '0' : $value['hours'], + empty($value['minutes']) ? '0' : $value['minutes'], + empty($value['seconds']) ? '0' : $value['seconds'] + ); + } + $dateInterval = new \DateInterval($interval); + if (isset($value['invert'])) { + $dateInterval->invert = $value['invert'] ? 1 : 0; + } + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateInterval; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php new file mode 100644 index 0000000..e1a3d97 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Transforms between a date string and a DateInterval object. + * + * @author Steffen Roßkamp + * + * @implements DataTransformerInterface<\DateInterval, string> + */ +class DateIntervalToStringTransformer implements DataTransformerInterface +{ + /** + * Transforms a \DateInterval instance to a string. + * + * @see \DateInterval::format() for supported formats + * + * @param string $format The date format + */ + public function __construct( + private string $format = 'P%yY%mM%dDT%hH%iM%sS', + ) { + } + + /** + * Transforms a DateInterval object into a date string with the configured format. + * + * @param \DateInterval|null $value A DateInterval object + * + * @throws UnexpectedTypeException if the given value is not a \DateInterval instance + */ + public function transform(mixed $value): string + { + if (null === $value) { + return ''; + } + if (!$value instanceof \DateInterval) { + throw new UnexpectedTypeException($value, \DateInterval::class); + } + + return $value->format($this->format); + } + + /** + * Transforms a date string in the configured format into a DateInterval object. + * + * @param string $value An ISO 8601 or date string like date interval presentation + * + * @throws UnexpectedTypeException if the given value is not a string + * @throws TransformationFailedException if the date interval could not be parsed + */ + public function reverseTransform(mixed $value): ?\DateInterval + { + if (null === $value) { + return null; + } + if (!\is_string($value)) { + throw new UnexpectedTypeException($value, 'string'); + } + if ('' === $value) { + return null; + } + if (!$this->isISO8601($value)) { + throw new TransformationFailedException('Non ISO 8601 date strings are not supported yet.'); + } + $valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $this->format).'$/'; + if (!preg_match($valuePattern, $value)) { + throw new TransformationFailedException(sprintf('Value "%s" contains intervals not accepted by format "%s".', $value, $this->format)); + } + try { + $dateInterval = new \DateInterval($value); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateInterval; + } + + private function isISO8601(string $string): bool + { + return preg_match('/^P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string); + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeImmutableToDateTimeTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeImmutableToDateTimeTransformer.php new file mode 100644 index 0000000..3f285b4 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeImmutableToDateTimeTransformer.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a DateTimeImmutable object and a DateTime object. + * + * @author Valentin Udaltsov + * + * @implements DataTransformerInterface<\DateTimeImmutable, \DateTime> + */ +final class DateTimeImmutableToDateTimeTransformer implements DataTransformerInterface +{ + /** + * Transforms a DateTimeImmutable into a DateTime object. + * + * @param \DateTimeImmutable|null $value A DateTimeImmutable object + * + * @throws TransformationFailedException If the given value is not a \DateTimeImmutable + */ + public function transform(mixed $value): ?\DateTime + { + if (null === $value) { + return null; + } + + if (!$value instanceof \DateTimeImmutable) { + throw new TransformationFailedException('Expected a \DateTimeImmutable.'); + } + + return \DateTime::createFromImmutable($value); + } + + /** + * Transforms a DateTime object into a DateTimeImmutable object. + * + * @param \DateTime|null $value A DateTime object + * + * @throws TransformationFailedException If the given value is not a \DateTime + */ + public function reverseTransform(mixed $value): ?\DateTimeImmutable + { + if (null === $value) { + return null; + } + + if (!$value instanceof \DateTime) { + throw new TransformationFailedException('Expected a \DateTime.'); + } + + return \DateTimeImmutable::createFromMutable($value); + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php new file mode 100644 index 0000000..8c3d2d2 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a normalized time and a localized time string/array. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * + * @extends BaseDateTimeTransformer + */ +class DateTimeToArrayTransformer extends BaseDateTimeTransformer +{ + private array $fields; + private \DateTimeInterface $referenceDate; + + /** + * @param string|null $inputTimezone The input timezone + * @param string|null $outputTimezone The output timezone + * @param string[]|null $fields The date fields + * @param bool $pad Whether to use padding + */ + public function __construct( + ?string $inputTimezone = null, + ?string $outputTimezone = null, + ?array $fields = null, + private bool $pad = false, + ?\DateTimeInterface $referenceDate = null, + ) { + parent::__construct($inputTimezone, $outputTimezone); + + $this->fields = $fields ?? ['year', 'month', 'day', 'hour', 'minute', 'second']; + $this->referenceDate = $referenceDate ?? new \DateTimeImmutable('1970-01-01 00:00:00'); + } + + /** + * Transforms a normalized date into a localized date. + * + * @param \DateTimeInterface $dateTime A DateTimeInterface object + * + * @throws TransformationFailedException If the given value is not a \DateTimeInterface + */ + public function transform(mixed $dateTime): array + { + if (null === $dateTime) { + return array_intersect_key([ + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ], array_flip($this->fields)); + } + + if (!$dateTime instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = \DateTimeImmutable::createFromInterface($dateTime); + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + $result = array_intersect_key([ + 'year' => $dateTime->format('Y'), + 'month' => $dateTime->format('m'), + 'day' => $dateTime->format('d'), + 'hour' => $dateTime->format('H'), + 'minute' => $dateTime->format('i'), + 'second' => $dateTime->format('s'), + ], array_flip($this->fields)); + + if (!$this->pad) { + foreach ($result as &$entry) { + // remove leading zeros + $entry = (string) (int) $entry; + } + // unset reference to keep scope clear + unset($entry); + } + + return $result; + } + + /** + * Transforms a localized date into a normalized date. + * + * @param array $value Localized date + * + * @throws TransformationFailedException If the given value is not an array, + * if the value could not be transformed + */ + public function reverseTransform(mixed $value): ?\DateTime + { + if (null === $value) { + return null; + } + + if (!\is_array($value)) { + throw new TransformationFailedException('Expected an array.'); + } + + if ('' === implode('', $value)) { + return null; + } + + $emptyFields = []; + + foreach ($this->fields as $field) { + if (!isset($value[$field])) { + $emptyFields[] = $field; + } + } + + if (\count($emptyFields) > 0) { + throw new TransformationFailedException(sprintf('The fields "%s" should not be empty.', implode('", "', $emptyFields))); + } + + if (isset($value['month']) && !ctype_digit((string) $value['month'])) { + throw new TransformationFailedException('This month is invalid.'); + } + + if (isset($value['day']) && !ctype_digit((string) $value['day'])) { + throw new TransformationFailedException('This day is invalid.'); + } + + if (isset($value['year']) && !ctype_digit((string) $value['year'])) { + throw new TransformationFailedException('This year is invalid.'); + } + + if (!empty($value['month']) && !empty($value['day']) && !empty($value['year']) && false === checkdate($value['month'], $value['day'], $value['year'])) { + throw new TransformationFailedException('This is an invalid date.'); + } + + if (isset($value['hour']) && !ctype_digit((string) $value['hour'])) { + throw new TransformationFailedException('This hour is invalid.'); + } + + if (isset($value['minute']) && !ctype_digit((string) $value['minute'])) { + throw new TransformationFailedException('This minute is invalid.'); + } + + if (isset($value['second']) && !ctype_digit((string) $value['second'])) { + throw new TransformationFailedException('This second is invalid.'); + } + + try { + $dateTime = new \DateTime(sprintf( + '%s-%s-%s %s:%s:%s', + empty($value['year']) ? $this->referenceDate->format('Y') : $value['year'], + empty($value['month']) ? $this->referenceDate->format('m') : $value['month'], + empty($value['day']) ? $this->referenceDate->format('d') : $value['day'], + $value['hour'] ?? $this->referenceDate->format('H'), + $value['minute'] ?? $this->referenceDate->format('i'), + $value['second'] ?? $this->referenceDate->format('s') + ), + new \DateTimeZone($this->outputTimezone) + ); + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateTime; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php new file mode 100644 index 0000000..855b22a --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Franz Wilding + * @author Bernhard Schussek + * @author Fred Cox + * + * @extends BaseDateTimeTransformer + */ +class DateTimeToHtml5LocalDateTimeTransformer extends BaseDateTimeTransformer +{ + public const HTML5_FORMAT = 'Y-m-d\\TH:i:s'; + public const HTML5_FORMAT_NO_SECONDS = 'Y-m-d\\TH:i'; + + public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null, private bool $withSeconds = false) + { + parent::__construct($inputTimezone, $outputTimezone); + } + + /** + * Transforms a \DateTime into a local date and time string. + * + * According to the HTML standard, the input string of a datetime-local + * input is an RFC3339 date followed by 'T', followed by an RFC3339 time. + * https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string + * + * @param \DateTimeInterface $dateTime + * + * @throws TransformationFailedException If the given value is not an + * instance of \DateTime or \DateTimeInterface + */ + public function transform(mixed $dateTime): string + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = \DateTimeImmutable::createFromInterface($dateTime); + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + return $dateTime->format($this->withSeconds ? self::HTML5_FORMAT : self::HTML5_FORMAT_NO_SECONDS); + } + + /** + * Transforms a local date and time string into a \DateTime. + * + * When transforming back to DateTime the regex is slightly laxer, taking into + * account rules for parsing a local date and time string + * https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-local-date-and-time-string + * + * @param string $dateTimeLocal Formatted string + * + * @throws TransformationFailedException If the given value is not a string, + * if the value could not be transformed + */ + public function reverseTransform(mixed $dateTimeLocal): ?\DateTime + { + if (!\is_string($dateTimeLocal)) { + throw new TransformationFailedException('Expected a string.'); + } + + if ('' === $dateTimeLocal) { + return null; + } + + // to maintain backwards compatibility we do not strictly validate the submitted date + // see https://github.com/symfony/symfony/issues/28699 + if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})[T ]\d{2}:\d{2}(?::\d{2})?/', $dateTimeLocal, $matches)) { + throw new TransformationFailedException(sprintf('The date "%s" is not a valid date.', $dateTimeLocal)); + } + + try { + $dateTime = new \DateTime($dateTimeLocal, new \DateTimeZone($this->outputTimezone)); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + if ($this->inputTimezone !== $dateTime->getTimezone()->getName()) { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + + if (!checkdate($matches[2], $matches[3], $matches[1])) { + throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3])); + } + + return $dateTime; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php new file mode 100644 index 0000000..b7ea092 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Transforms between a normalized time and a localized time string. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * + * @extends BaseDateTimeTransformer + */ +class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer +{ + private int $dateFormat; + private int $timeFormat; + + /** + * @see BaseDateTimeTransformer::formats for available format options + * + * @param string|null $inputTimezone The name of the input timezone + * @param string|null $outputTimezone The name of the output timezone + * @param int|null $dateFormat The date format + * @param int|null $timeFormat The time format + * @param int $calendar One of the \IntlDateFormatter calendar constants + * @param string|null $pattern A pattern to pass to \IntlDateFormatter + * + * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string + */ + public function __construct( + ?string $inputTimezone = null, + ?string $outputTimezone = null, + ?int $dateFormat = null, + ?int $timeFormat = null, + private int $calendar = \IntlDateFormatter::GREGORIAN, + private ?string $pattern = null, + ) { + parent::__construct($inputTimezone, $outputTimezone); + + $dateFormat ??= \IntlDateFormatter::MEDIUM; + $timeFormat ??= \IntlDateFormatter::SHORT; + + if (!\in_array($dateFormat, self::$formats, true)) { + throw new UnexpectedTypeException($dateFormat, implode('", "', self::$formats)); + } + + if (!\in_array($timeFormat, self::$formats, true)) { + throw new UnexpectedTypeException($timeFormat, implode('", "', self::$formats)); + } + + $this->dateFormat = $dateFormat; + $this->timeFormat = $timeFormat; + } + + /** + * Transforms a normalized date into a localized date string/array. + * + * @param \DateTimeInterface $dateTime A DateTimeInterface object + * + * @throws TransformationFailedException if the given value is not a \DateTimeInterface + * or if the date could not be transformed + */ + public function transform(mixed $dateTime): string + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + $value = $this->getIntlDateFormatter()->format($dateTime->getTimestamp()); + + if (0 != intl_get_error_code()) { + throw new TransformationFailedException(intl_get_error_message()); + } + + return $value; + } + + /** + * Transforms a localized date string/array into a normalized date. + * + * @param string $value Localized date string + * + * @throws TransformationFailedException if the given value is not a string, + * if the date could not be parsed + */ + public function reverseTransform(mixed $value): ?\DateTime + { + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + if ('' === $value) { + return null; + } + + // date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due + // to DST changes + $dateOnly = $this->isPatternDateOnly(); + $dateFormatter = $this->getIntlDateFormatter($dateOnly); + + try { + $timestamp = @$dateFormatter->parse($value); + } catch (\IntlException $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + if (0 != intl_get_error_code()) { + throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code()); + } elseif ($timestamp > 253402214400) { + // This timestamp represents UTC midnight of 9999-12-31 to prevent 5+ digit years + throw new TransformationFailedException('Years beyond 9999 are not supported.'); + } elseif (false === $timestamp) { + // the value couldn't be parsed but the Intl extension didn't report an error code, this + // could be the case when the Intl polyfill is used which always returns 0 as the error code + throw new TransformationFailedException(sprintf('"%s" could not be parsed as a date.', $value)); + } + + try { + if ($dateOnly) { + // we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight + $dateTime = new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->outputTimezone)); + } else { + // read timestamp into DateTime object - the formatter delivers a timestamp + $dateTime = new \DateTime(sprintf('@%s', $timestamp)); + } + // set timezone separately, as it would be ignored if set via the constructor, + // see https://php.net/datetime.construct + $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + if ($this->outputTimezone !== $this->inputTimezone) { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + + return $dateTime; + } + + /** + * Returns a preconfigured IntlDateFormatter instance. + * + * @param bool $ignoreTimezone Use UTC regardless of the configured timezone + * + * @throws TransformationFailedException in case the date formatter cannot be constructed + */ + protected function getIntlDateFormatter(bool $ignoreTimezone = false): \IntlDateFormatter + { + $dateFormat = $this->dateFormat; + $timeFormat = $this->timeFormat; + $timezone = new \DateTimeZone($ignoreTimezone ? 'UTC' : $this->outputTimezone); + + $calendar = $this->calendar; + $pattern = $this->pattern; + + $intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern ?? ''); + + // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/66323 + if (!$intlDateFormatter) { + throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code()); + } + + $intlDateFormatter->setLenient(false); + + return $intlDateFormatter; + } + + /** + * Checks if the pattern contains only a date. + */ + protected function isPatternDateOnly(): bool + { + if (null === $this->pattern) { + return false; + } + + // strip escaped text + $pattern = preg_replace("#'(.*?)'#", '', $this->pattern); + + // check for the absence of time-related placeholders + return 0 === preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern); + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php new file mode 100644 index 0000000..41e63e5 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + * + * @extends BaseDateTimeTransformer + */ +class DateTimeToRfc3339Transformer extends BaseDateTimeTransformer +{ + /** + * Transforms a normalized date into a localized date. + * + * @param \DateTimeInterface $dateTime A DateTimeInterface object + * + * @throws TransformationFailedException If the given value is not a \DateTimeInterface + */ + public function transform(mixed $dateTime): string + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = \DateTimeImmutable::createFromInterface($dateTime); + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + return preg_replace('/\+00:00$/', 'Z', $dateTime->format('c')); + } + + /** + * Transforms a formatted string following RFC 3339 into a normalized date. + * + * @param string $rfc3339 Formatted string + * + * @throws TransformationFailedException If the given value is not a string, + * if the value could not be transformed + */ + public function reverseTransform(mixed $rfc3339): ?\DateTime + { + if (!\is_string($rfc3339)) { + throw new TransformationFailedException('Expected a string.'); + } + + if ('' === $rfc3339) { + return null; + } + + if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|(?:(?:\+|-)\d{2}:\d{2}))$/', $rfc3339, $matches)) { + throw new TransformationFailedException(sprintf('The date "%s" is not a valid date.', $rfc3339)); + } + + try { + $dateTime = new \DateTime($rfc3339); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + if ($this->inputTimezone !== $dateTime->getTimezone()->getName()) { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + + if (!checkdate($matches[2], $matches[3], $matches[1])) { + throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3])); + } + + return $dateTime; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php new file mode 100644 index 0000000..1353614 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a date string and a DateTime object. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * + * @extends BaseDateTimeTransformer + */ +class DateTimeToStringTransformer extends BaseDateTimeTransformer +{ + /** + * Format used for generating strings. + */ + private string $generateFormat; + + /** + * Format used for parsing strings. + * + * Different than the {@link $generateFormat} because formats for parsing + * support additional characters in PHP that are not supported for + * generating strings. + */ + private string $parseFormat; + + /** + * Transforms a \DateTime instance to a string. + * + * @see \DateTime::format() for supported formats + * + * @param string|null $inputTimezone The name of the input timezone + * @param string|null $outputTimezone The name of the output timezone + * @param string $format The date format + * @param string|null $parseFormat The parse format when different from $format + */ + public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null, string $format = 'Y-m-d H:i:s', ?string $parseFormat = null) + { + parent::__construct($inputTimezone, $outputTimezone); + + $this->generateFormat = $format; + $this->parseFormat = $parseFormat ?? $format; + + // See https://php.net/datetime.createfromformat + // The character "|" in the format makes sure that the parts of a date + // that are *not* specified in the format are reset to the corresponding + // values from 1970-01-01 00:00:00 instead of the current time. + // Without "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 12:32:47", + // where the time corresponds to the current server time. + // With "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 00:00:00", + // which is at least deterministic and thus used here. + if (!str_contains($this->parseFormat, '|')) { + $this->parseFormat .= '|'; + } + } + + /** + * Transforms a DateTime object into a date string with the configured format + * and timezone. + * + * @param \DateTimeInterface $dateTime A DateTimeInterface object + * + * @throws TransformationFailedException If the given value is not a \DateTimeInterface + */ + public function transform(mixed $dateTime): string + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + $dateTime = \DateTimeImmutable::createFromInterface($dateTime); + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + + return $dateTime->format($this->generateFormat); + } + + /** + * Transforms a date string in the configured timezone into a DateTime object. + * + * @param string $value A value as produced by PHP's date() function + * + * @throws TransformationFailedException If the given value is not a string, + * or could not be transformed + */ + public function reverseTransform(mixed $value): ?\DateTime + { + if (!$value) { + return null; + } + + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + if (str_contains($value, "\0")) { + throw new TransformationFailedException('Null bytes not allowed'); + } + + $outputTz = new \DateTimeZone($this->outputTimezone); + $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz); + + $lastErrors = \DateTime::getLastErrors() ?: ['error_count' => 0, 'warning_count' => 0]; + + if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { + throw new TransformationFailedException(implode(', ', array_merge(array_values($lastErrors['warnings']), array_values($lastErrors['errors'])))); + } + + try { + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateTime; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php new file mode 100644 index 0000000..33c1b1d --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a timestamp and a DateTime object. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * + * @extends BaseDateTimeTransformer + */ +class DateTimeToTimestampTransformer extends BaseDateTimeTransformer +{ + /** + * Transforms a DateTime object into a timestamp in the configured timezone. + * + * @param \DateTimeInterface $dateTime A DateTimeInterface object + * + * @throws TransformationFailedException If the given value is not a \DateTimeInterface + */ + public function transform(mixed $dateTime): ?int + { + if (null === $dateTime) { + return null; + } + + if (!$dateTime instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + return $dateTime->getTimestamp(); + } + + /** + * Transforms a timestamp in the configured timezone into a DateTime object. + * + * @param string $value A timestamp + * + * @throws TransformationFailedException If the given value is not a timestamp + * or if the given timestamp is invalid + */ + public function reverseTransform(mixed $value): ?\DateTime + { + if (null === $value) { + return null; + } + + if (!is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + + try { + $dateTime = new \DateTime(); + $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + $dateTime->setTimestamp($value); + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateTime; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php new file mode 100644 index 0000000..50767f7 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a timezone identifier string and a DateTimeZone object. + * + * @author Roland Franssen + * + * @implements DataTransformerInterface<\DateTimeZone|array<\DateTimeZone>, string|array> + */ +class DateTimeZoneToStringTransformer implements DataTransformerInterface +{ + public function __construct( + private bool $multiple = false, + ) { + } + + public function transform(mixed $dateTimeZone): mixed + { + if (null === $dateTimeZone) { + return null; + } + + if ($this->multiple) { + if (!\is_array($dateTimeZone)) { + throw new TransformationFailedException('Expected an array of \DateTimeZone objects.'); + } + + return array_map([new self(), 'transform'], $dateTimeZone); + } + + if (!$dateTimeZone instanceof \DateTimeZone) { + throw new TransformationFailedException('Expected a \DateTimeZone object.'); + } + + return $dateTimeZone->getName(); + } + + public function reverseTransform(mixed $value): mixed + { + if (null === $value) { + return null; + } + + if ($this->multiple) { + if (!\is_array($value)) { + throw new TransformationFailedException('Expected an array of timezone identifier strings.'); + } + + return array_map([new self(), 'reverseTransform'], $value); + } + + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a timezone identifier string.'); + } + + try { + return new \DateTimeZone($value); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php new file mode 100644 index 0000000..eb5a2d6 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between an integer and a localized number with grouping + * (each thousand) and comma separators. + * + * @author Bernhard Schussek + */ +class IntegerToLocalizedStringTransformer extends NumberToLocalizedStringTransformer +{ + /** + * Constructs a transformer. + * + * @param bool $grouping Whether thousands should be grouped + * @param int|null $roundingMode One of the ROUND_ constants in this class + * @param string|null $locale locale used for transforming + */ + public function __construct(?bool $grouping = false, ?int $roundingMode = \NumberFormatter::ROUND_DOWN, ?string $locale = null) + { + parent::__construct(0, $grouping, $roundingMode, $locale); + } + + public function reverseTransform(mixed $value): int|float|null + { + $decimalSeparator = $this->getNumberFormatter()->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + + if (\is_string($value) && str_contains($value, $decimalSeparator)) { + throw new TransformationFailedException(sprintf('The value "%s" is not a valid integer.', $value)); + } + + $result = parent::reverseTransform($value); + + return null !== $result ? (int) $result : null; + } + + /** + * @internal + */ + protected function castParsedValue(int|float $value): int|float + { + return $value; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php new file mode 100644 index 0000000..446a95f --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a timezone identifier string and a IntlTimeZone object. + * + * @author Roland Franssen + * + * @implements DataTransformerInterface<\IntlTimeZone|array<\IntlTimeZone>, string|array> + */ +class IntlTimeZoneToStringTransformer implements DataTransformerInterface +{ + public function __construct( + private bool $multiple = false, + ) { + } + + public function transform(mixed $intlTimeZone): mixed + { + if (null === $intlTimeZone) { + return null; + } + + if ($this->multiple) { + if (!\is_array($intlTimeZone)) { + throw new TransformationFailedException('Expected an array of \IntlTimeZone objects.'); + } + + return array_map([new self(), 'transform'], $intlTimeZone); + } + + if (!$intlTimeZone instanceof \IntlTimeZone) { + throw new TransformationFailedException('Expected a \IntlTimeZone object.'); + } + + return $intlTimeZone->getID(); + } + + public function reverseTransform(mixed $value): mixed + { + if (null === $value) { + return null; + } + + if ($this->multiple) { + if (!\is_array($value)) { + throw new TransformationFailedException('Expected an array of timezone identifier strings.'); + } + + return array_map([new self(), 'reverseTransform'], $value); + } + + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a timezone identifier string.'); + } + + $intlTimeZone = \IntlTimeZone::createTimeZone($value); + + if ('Etc/Unknown' === $intlTimeZone->getID()) { + throw new TransformationFailedException(sprintf('Unknown timezone identifier "%s".', $value)); + } + + return $intlTimeZone; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php new file mode 100644 index 0000000..caf6750 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a normalized format and a localized money string. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + */ +class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransformer +{ + private int $divisor; + + public function __construct( + ?int $scale = 2, + ?bool $grouping = true, + ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, + ?int $divisor = 1, + ?string $locale = null, + private readonly string $input = 'float', + ) { + parent::__construct($scale ?? 2, $grouping ?? true, $roundingMode, $locale); + + $this->divisor = $divisor ?? 1; + } + + /** + * Transforms a normalized format into a localized money string. + * + * @param int|float|null $value Normalized number + * + * @throws TransformationFailedException if the given value is not numeric or + * if the value cannot be transformed + */ + public function transform(mixed $value): string + { + if (null !== $value && 1 !== $this->divisor) { + if (!is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + $value /= $this->divisor; + } + + return parent::transform($value); + } + + /** + * Transforms a localized money string into a normalized format. + * + * @param string $value Localized money string + * + * @throws TransformationFailedException if the given value is not a string + * or if the value cannot be transformed + */ + public function reverseTransform(mixed $value): int|float|null + { + $value = parent::reverseTransform($value); + if (null !== $value) { + $value = (string) ($value * $this->divisor); + + if ('float' === $this->input) { + return (float) $value; + } + + if ($value > \PHP_INT_MAX || $value < \PHP_INT_MIN) { + throw new TransformationFailedException(sprintf('Cannot cast "%s" to an integer. Try setting the input to "float" instead.', $value)); + } + + $value = (int) $value; + } + + return $value; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php new file mode 100644 index 0000000..4d609a5 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a number type and a localized number with grouping + * (each thousand) and comma separators. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * + * @implements DataTransformerInterface + */ +class NumberToLocalizedStringTransformer implements DataTransformerInterface +{ + protected bool $grouping; + protected int $roundingMode; + + public function __construct( + private ?int $scale = null, + ?bool $grouping = false, + ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, + private ?string $locale = null, + ) { + $this->grouping = $grouping ?? false; + $this->roundingMode = $roundingMode ?? \NumberFormatter::ROUND_HALFUP; + } + + /** + * Transforms a number type into localized number. + * + * @param int|float|null $value Number value + * + * @throws TransformationFailedException if the given value is not numeric + * or if the value cannot be transformed + */ + public function transform(mixed $value): string + { + if (null === $value) { + return ''; + } + + if (!is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + + $formatter = $this->getNumberFormatter(); + $value = $formatter->format($value); + + if (intl_is_failure($formatter->getErrorCode())) { + throw new TransformationFailedException($formatter->getErrorMessage()); + } + + // Convert non-breaking and narrow non-breaking spaces to normal ones + $value = str_replace(["\xc2\xa0", "\xe2\x80\xaf"], ' ', $value); + + return $value; + } + + /** + * Transforms a localized number into an integer or float. + * + * @param string $value The localized value + * + * @throws TransformationFailedException if the given value is not a string + * or if the value cannot be transformed + */ + public function reverseTransform(mixed $value): int|float|null + { + if (null !== $value && !\is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + if (null === $value || '' === $value) { + return null; + } + + if (\in_array($value, ['NaN', 'NAN', 'nan'], true)) { + throw new TransformationFailedException('"NaN" is not a valid number.'); + } + + $position = 0; + $formatter = $this->getNumberFormatter(); + $groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); + $decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + + if ('.' !== $decSep && (!$this->grouping || '.' !== $groupSep)) { + $value = str_replace('.', $decSep, $value); + } + + if (',' !== $decSep && (!$this->grouping || ',' !== $groupSep)) { + $value = str_replace(',', $decSep, $value); + } + + //If the value is in exponential notation with a negative exponent, we end up with a float value too + if (str_contains($value, $decSep) || false !== stripos($value, 'e-')) { + $type = \NumberFormatter::TYPE_DOUBLE; + } else { + $type = \PHP_INT_SIZE === 8 + ? \NumberFormatter::TYPE_INT64 + : \NumberFormatter::TYPE_INT32; + } + + $result = $formatter->parse($value, $type, $position); + + if (intl_is_failure($formatter->getErrorCode())) { + throw new TransformationFailedException($formatter->getErrorMessage()); + } + + if ($result >= \PHP_INT_MAX || $result <= -\PHP_INT_MAX) { + throw new TransformationFailedException('I don\'t have a clear idea what infinity looks like.'); + } + + $result = $this->castParsedValue($result); + + if (false !== $encoding = mb_detect_encoding($value, null, true)) { + $length = mb_strlen($value, $encoding); + $remainder = mb_substr($value, $position, $length, $encoding); + } else { + $length = \strlen($value); + $remainder = substr($value, $position, $length); + } + + // After parsing, position holds the index of the character where the + // parsing stopped + if ($position < $length) { + // Check if there are unrecognized characters at the end of the + // number (excluding whitespace characters) + $remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0"); + + if ('' !== $remainder) { + throw new TransformationFailedException(sprintf('The number contains unrecognized characters: "%s".', $remainder)); + } + } + + // NumberFormatter::parse() does not round + return $this->round($result); + } + + /** + * Returns a preconfigured \NumberFormatter instance. + */ + protected function getNumberFormatter(): \NumberFormatter + { + $formatter = new \NumberFormatter($this->locale ?? \Locale::getDefault(), \NumberFormatter::DECIMAL); + + if (null !== $this->scale) { + $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->scale); + $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode); + } + + $formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->grouping); + + return $formatter; + } + + /** + * @internal + */ + protected function castParsedValue(int|float $value): int|float + { + if (\is_int($value) && $value === (int) $float = (float) $value) { + return $float; + } + + return $value; + } + + /** + * Rounds a number according to the configured scale and rounding mode. + */ + private function round(int|float $number): int|float + { + if (null !== $this->scale) { + // shift number to maintain the correct scale during rounding + $roundingCoef = 10 ** $this->scale; + // string representation to avoid rounding errors, similar to bcmul() + $number = (string) ($number * $roundingCoef); + + $number = match ($this->roundingMode) { + \NumberFormatter::ROUND_CEILING => ceil($number), + \NumberFormatter::ROUND_FLOOR => floor($number), + \NumberFormatter::ROUND_UP => $number > 0 ? ceil($number) : floor($number), + \NumberFormatter::ROUND_DOWN => $number > 0 ? floor($number) : ceil($number), + \NumberFormatter::ROUND_HALFEVEN => round($number, 0, \PHP_ROUND_HALF_EVEN), + \NumberFormatter::ROUND_HALFUP => round($number, 0, \PHP_ROUND_HALF_UP), + \NumberFormatter::ROUND_HALFDOWN => round($number, 0, \PHP_ROUND_HALF_DOWN), + }; + + $number = 1 === $roundingCoef ? (int) $number : $number / $roundingCoef; + } + + return $number; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php new file mode 100644 index 0000000..d4d8aad --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Transforms between a normalized format (integer or float) and a percentage value. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * + * @implements DataTransformerInterface + */ +class PercentToLocalizedStringTransformer implements DataTransformerInterface +{ + public const FRACTIONAL = 'fractional'; + public const INTEGER = 'integer'; + + protected static array $types = [ + self::FRACTIONAL, + self::INTEGER, + ]; + + private string $type; + private int $scale; + + /** + * @see self::$types for a list of supported types + * + * @param int $roundingMode A value from \NumberFormatter, such as \NumberFormatter::ROUND_HALFUP + * @param bool $html5Format Use an HTML5 specific format, see https://www.w3.org/TR/html51/sec-forms.html#date-time-and-number-formats + * + * @throws UnexpectedTypeException if the given value of type is unknown + */ + public function __construct( + ?int $scale = null, + ?string $type = null, + private int $roundingMode = \NumberFormatter::ROUND_HALFUP, + private bool $html5Format = false, + ) { + $type ??= self::FRACTIONAL; + + if (!\in_array($type, self::$types, true)) { + throw new UnexpectedTypeException($type, implode('", "', self::$types)); + } + + $this->type = $type; + $this->scale = $scale ?? 0; + } + + /** + * Transforms between a normalized format (integer or float) into a percentage value. + * + * @param int|float $value Normalized value + * + * @throws TransformationFailedException if the given value is not numeric or + * if the value could not be transformed + */ + public function transform(mixed $value): string + { + if (null === $value) { + return ''; + } + + if (!is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + + if (self::FRACTIONAL == $this->type) { + $value *= 100; + } + + $formatter = $this->getNumberFormatter(); + $value = $formatter->format($value); + + if (intl_is_failure($formatter->getErrorCode())) { + throw new TransformationFailedException($formatter->getErrorMessage()); + } + + // replace the UTF-8 non break spaces + return $value; + } + + /** + * Transforms between a percentage value into a normalized format (integer or float). + * + * @param string $value Percentage value + * + * @throws TransformationFailedException if the given value is not a string or + * if the value could not be transformed + */ + public function reverseTransform(mixed $value): int|float|null + { + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + if ('' === $value) { + return null; + } + + $position = 0; + $formatter = $this->getNumberFormatter(); + $groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); + $decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + $grouping = $formatter->getAttribute(\NumberFormatter::GROUPING_USED); + + if ('.' !== $decSep && (!$grouping || '.' !== $groupSep)) { + $value = str_replace('.', $decSep, $value); + } + + if (',' !== $decSep && (!$grouping || ',' !== $groupSep)) { + $value = str_replace(',', $decSep, $value); + } + + if (str_contains($value, $decSep)) { + $type = \NumberFormatter::TYPE_DOUBLE; + } else { + $type = \PHP_INT_SIZE === 8 ? \NumberFormatter::TYPE_INT64 : \NumberFormatter::TYPE_INT32; + } + + // replace normal spaces so that the formatter can read them + $result = $formatter->parse(str_replace(' ', "\xc2\xa0", $value), $type, $position); + + if (intl_is_failure($formatter->getErrorCode())) { + throw new TransformationFailedException($formatter->getErrorMessage()); + } + + if (self::FRACTIONAL == $this->type) { + $result /= 100; + } + + if (\function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value, null, true)) { + $length = mb_strlen($value, $encoding); + $remainder = mb_substr($value, $position, $length, $encoding); + } else { + $length = \strlen($value); + $remainder = substr($value, $position, $length); + } + + // After parsing, position holds the index of the character where the + // parsing stopped + if ($position < $length) { + // Check if there are unrecognized characters at the end of the + // number (excluding whitespace characters) + $remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0"); + + if ('' !== $remainder) { + throw new TransformationFailedException(sprintf('The number contains unrecognized characters: "%s".', $remainder)); + } + } + + return $this->round($result); + } + + /** + * Returns a preconfigured \NumberFormatter instance. + */ + protected function getNumberFormatter(): \NumberFormatter + { + // Values used in HTML5 number inputs should be formatted as in "1234.5", ie. 'en' format without grouping, + // according to https://www.w3.org/TR/html51/sec-forms.html#date-time-and-number-formats + $formatter = new \NumberFormatter($this->html5Format ? 'en' : \Locale::getDefault(), \NumberFormatter::DECIMAL); + + if ($this->html5Format) { + $formatter->setAttribute(\NumberFormatter::GROUPING_USED, 0); + } + + $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->scale); + + $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode); + + return $formatter; + } + + /** + * Rounds a number according to the configured scale and rounding mode. + */ + private function round(int|float $number): int|float + { + // shift number to maintain the correct scale during rounding + $roundingCoef = 10 ** $this->scale; + + if (self::FRACTIONAL === $this->type) { + $roundingCoef *= 100; + } + + // string representation to avoid rounding errors, similar to bcmul() + $number = (string) ($number * $roundingCoef); + + $number = match ($this->roundingMode) { + \NumberFormatter::ROUND_CEILING => ceil($number), + \NumberFormatter::ROUND_FLOOR => floor($number), + \NumberFormatter::ROUND_UP => $number > 0 ? ceil($number) : floor($number), + \NumberFormatter::ROUND_DOWN => $number > 0 ? floor($number) : ceil($number), + \NumberFormatter::ROUND_HALFEVEN => round($number, 0, \PHP_ROUND_HALF_EVEN), + \NumberFormatter::ROUND_HALFUP => round($number, 0, \PHP_ROUND_HALF_UP), + \NumberFormatter::ROUND_HALFDOWN => round($number, 0, \PHP_ROUND_HALF_DOWN), + }; + + $number = 1 === $roundingCoef ? (int) $number : $number / $roundingCoef; + + return $number; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/StringToFloatTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/StringToFloatTransformer.php new file mode 100644 index 0000000..f47fcb6 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/StringToFloatTransformer.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @implements DataTransformerInterface + */ +class StringToFloatTransformer implements DataTransformerInterface +{ + public function __construct( + private ?int $scale = null, + ) { + } + + public function transform(mixed $value): ?float + { + if (null === $value) { + return null; + } + + if (!\is_string($value) || !is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric string.'); + } + + return (float) $value; + } + + public function reverseTransform(mixed $value): ?string + { + if (null === $value) { + return null; + } + + if (!\is_int($value) && !\is_float($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + + if ($this->scale > 0) { + return number_format((float) $value, $this->scale, '.', ''); + } + + return (string) $value; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/UlidToStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/UlidToStringTransformer.php new file mode 100644 index 0000000..7ace73a --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/UlidToStringTransformer.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Uid\Ulid; + +/** + * Transforms between a ULID string and a Ulid object. + * + * @author Pavel Dyakonov + * + * @implements DataTransformerInterface + */ +class UlidToStringTransformer implements DataTransformerInterface +{ + /** + * Transforms a Ulid object into a string. + * + * @param Ulid $value A Ulid object + * + * @throws TransformationFailedException If the given value is not a Ulid object + */ + public function transform(mixed $value): ?string + { + if (null === $value) { + return null; + } + + if (!$value instanceof Ulid) { + throw new TransformationFailedException('Expected a Ulid.'); + } + + return (string) $value; + } + + /** + * Transforms a ULID string into a Ulid object. + * + * @param string $value A ULID string + * + * @throws TransformationFailedException If the given value is not a string, + * or could not be transformed + */ + public function reverseTransform(mixed $value): ?Ulid + { + if (null === $value || '' === $value) { + return null; + } + + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + try { + $ulid = new Ulid($value); + } catch (\InvalidArgumentException $e) { + throw new TransformationFailedException(sprintf('The value "%s" is not a valid ULID.', $value), $e->getCode(), $e); + } + + return $ulid; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/UuidToStringTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/UuidToStringTransformer.php new file mode 100644 index 0000000..cc794a0 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/UuidToStringTransformer.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Uid\Uuid; + +/** + * Transforms between a UUID string and a Uuid object. + * + * @author Pavel Dyakonov + * + * @implements DataTransformerInterface + */ +class UuidToStringTransformer implements DataTransformerInterface +{ + /** + * Transforms a Uuid object into a string. + * + * @param Uuid $value A Uuid object + * + * @throws TransformationFailedException If the given value is not a Uuid object + */ + public function transform(mixed $value): ?string + { + if (null === $value) { + return null; + } + + if (!$value instanceof Uuid) { + throw new TransformationFailedException('Expected a Uuid.'); + } + + return (string) $value; + } + + /** + * Transforms a UUID string into a Uuid object. + * + * @param string $value A UUID string + * + * @throws TransformationFailedException If the given value is not a string, + * or could not be transformed + */ + public function reverseTransform(mixed $value): ?Uuid + { + if (null === $value || '' === $value) { + return null; + } + + if (!\is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + if (!Uuid::isValid($value)) { + throw new TransformationFailedException(sprintf('The value "%s" is not a valid UUID.', $value)); + } + + try { + return Uuid::fromString($value); + } catch (\InvalidArgumentException $e) { + throw new TransformationFailedException(sprintf('The value "%s" is not a valid UUID.', $value), $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php new file mode 100644 index 0000000..e50f30c --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + * + * @implements DataTransformerInterface + */ +class ValueToDuplicatesTransformer implements DataTransformerInterface +{ + public function __construct( + private array $keys, + ) { + } + + /** + * Duplicates the given value through the array. + */ + public function transform(mixed $value): array + { + $result = []; + + foreach ($this->keys as $key) { + $result[$key] = $value; + } + + return $result; + } + + /** + * Extracts the duplicated value from an array. + * + * @throws TransformationFailedException if the given value is not an array or + * if the given array cannot be transformed + */ + public function reverseTransform(mixed $array): mixed + { + if (!\is_array($array)) { + throw new TransformationFailedException('Expected an array.'); + } + + $result = current($array); + $emptyKeys = []; + + foreach ($this->keys as $key) { + if (isset($array[$key]) && false !== $array[$key] && [] !== $array[$key]) { + if ($array[$key] !== $result) { + throw new TransformationFailedException('All values in the array should be the same.'); + } + } else { + $emptyKeys[] = $key; + } + } + + if (\count($emptyKeys) > 0) { + if (\count($emptyKeys) == \count($this->keys)) { + // All keys empty + return null; + } + + throw new TransformationFailedException(sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys))); + } + + return $result; + } +} diff --git a/vendor/symfony/form/Extension/Core/DataTransformer/WeekToArrayTransformer.php b/vendor/symfony/form/Extension/Core/DataTransformer/WeekToArrayTransformer.php new file mode 100644 index 0000000..c10bc73 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/DataTransformer/WeekToArrayTransformer.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between an ISO 8601 week date string and an array. + * + * @author Damien Fayet + * + * @implements DataTransformerInterface + */ +class WeekToArrayTransformer implements DataTransformerInterface +{ + /** + * Transforms a string containing an ISO 8601 week date into an array. + * + * @param string|null $value A week date string + * + * @return array{year: int|null, week: int|null} + * + * @throws TransformationFailedException If the given value is not a string, + * or if the given value does not follow the right format + */ + public function transform(mixed $value): array + { + if (null === $value) { + return ['year' => null, 'week' => null]; + } + + if (!\is_string($value)) { + throw new TransformationFailedException(sprintf('Value is expected to be a string but was "%s".', get_debug_type($value))); + } + + if (0 === preg_match('/^(?P\d{4})-W(?P\d{2})$/', $value, $matches)) { + throw new TransformationFailedException('Given data does not follow the date format "Y-\WW".'); + } + + return [ + 'year' => (int) $matches['year'], + 'week' => (int) $matches['week'], + ]; + } + + /** + * Transforms an array into a week date string. + * + * @param array{year: int|null, week: int|null} $value + * + * @return string|null A week date string following the format Y-\WW + * + * @throws TransformationFailedException If the given value cannot be merged in a valid week date string, + * or if the obtained week date does not exists + */ + public function reverseTransform(mixed $value): ?string + { + if (null === $value || [] === $value) { + return null; + } + + if (!\is_array($value)) { + throw new TransformationFailedException(sprintf('Value is expected to be an array, but was "%s".', get_debug_type($value))); + } + + if (!\array_key_exists('year', $value)) { + throw new TransformationFailedException('Key "year" is missing.'); + } + + if (!\array_key_exists('week', $value)) { + throw new TransformationFailedException('Key "week" is missing.'); + } + + if ($additionalKeys = array_diff(array_keys($value), ['year', 'week'])) { + throw new TransformationFailedException(sprintf('Expected only keys "year" and "week" to be present, but also got ["%s"].', implode('", "', $additionalKeys))); + } + + if (null === $value['year'] && null === $value['week']) { + return null; + } + + if (!\is_int($value['year'])) { + throw new TransformationFailedException(sprintf('Year is expected to be an integer, but was "%s".', get_debug_type($value['year']))); + } + + if (!\is_int($value['week'])) { + throw new TransformationFailedException(sprintf('Week is expected to be an integer, but was "%s".', get_debug_type($value['week']))); + } + + // The 28th December is always in the last week of the year + if (date('W', strtotime('28th December '.$value['year'])) < $value['week']) { + throw new TransformationFailedException(sprintf('Week "%d" does not exist for year "%d".', $value['week'], $value['year'])); + } + + return sprintf('%d-W%02d', $value['year'], $value['week']); + } +} diff --git a/vendor/symfony/form/Extension/Core/EventListener/FixUrlProtocolListener.php b/vendor/symfony/form/Extension/Core/EventListener/FixUrlProtocolListener.php new file mode 100644 index 0000000..97cd3ad --- /dev/null +++ b/vendor/symfony/form/Extension/Core/EventListener/FixUrlProtocolListener.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; + +/** + * Adds a protocol to a URL if it doesn't already have one. + * + * @author Bernhard Schussek + */ +class FixUrlProtocolListener implements EventSubscriberInterface +{ + /** + * @param string|null $defaultProtocol The URL scheme to add when there is none or null to not modify the data + */ + public function __construct( + private ?string $defaultProtocol = 'http', + ) { + } + + public function onSubmit(FormEvent $event): void + { + $data = $event->getData(); + + if ($this->defaultProtocol && $data && \is_string($data) && !preg_match('~^(?:[/.]|[\w+.-]+://|[^:/?@#]++@)~', $data)) { + $event->setData($this->defaultProtocol.'://'.$data); + } + } + + public static function getSubscribedEvents(): array + { + return [FormEvents::SUBMIT => 'onSubmit']; + } +} diff --git a/vendor/symfony/form/Extension/Core/EventListener/MergeCollectionListener.php b/vendor/symfony/form/Extension/Core/EventListener/MergeCollectionListener.php new file mode 100644 index 0000000..61428d5 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/EventListener/MergeCollectionListener.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; + +/** + * @author Bernhard Schussek + */ +class MergeCollectionListener implements EventSubscriberInterface +{ + /** + * @param bool $allowAdd Whether values might be added to the collection + * @param bool $allowDelete Whether values might be removed from the collection + */ + public function __construct( + private bool $allowAdd = false, + private bool $allowDelete = false, + ) { + } + + public static function getSubscribedEvents(): array + { + return [ + FormEvents::SUBMIT => 'onSubmit', + ]; + } + + public function onSubmit(FormEvent $event): void + { + $dataToMergeInto = $event->getForm()->getNormData(); + $data = $event->getData() ?? []; + + if (!\is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { + throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); + } + + if (null !== $dataToMergeInto && !\is_array($dataToMergeInto) && !($dataToMergeInto instanceof \Traversable && $dataToMergeInto instanceof \ArrayAccess)) { + throw new UnexpectedTypeException($dataToMergeInto, 'array or (\Traversable and \ArrayAccess)'); + } + + // If we are not allowed to change anything, return immediately + if ($data === $dataToMergeInto || (!$this->allowAdd && !$this->allowDelete)) { + $event->setData($dataToMergeInto); + + return; + } + + if (null === $dataToMergeInto) { + // No original data was set. Set it if allowed + if ($this->allowAdd) { + $dataToMergeInto = $data; + } + } else { + // Calculate delta + $itemsToAdd = \is_object($data) ? clone $data : $data; + $itemsToDelete = []; + + foreach ($dataToMergeInto as $beforeKey => $beforeItem) { + foreach ($data as $afterKey => $afterItem) { + if ($afterItem === $beforeItem) { + // Item found, next original item + unset($itemsToAdd[$afterKey]); + continue 2; + } + } + + // Item not found, remember for deletion + $itemsToDelete[] = $beforeKey; + } + + // Remove deleted items before adding to free keys that are to be + // replaced + if ($this->allowDelete) { + foreach ($itemsToDelete as $key) { + unset($dataToMergeInto[$key]); + } + } + + // Add remaining items + if ($this->allowAdd) { + foreach ($itemsToAdd as $key => $item) { + if (!isset($dataToMergeInto[$key])) { + $dataToMergeInto[$key] = $item; + } else { + $dataToMergeInto[] = $item; + } + } + } + } + + $event->setData($dataToMergeInto); + } +} diff --git a/vendor/symfony/form/Extension/Core/EventListener/ResizeFormListener.php b/vendor/symfony/form/Extension/Core/EventListener/ResizeFormListener.php new file mode 100644 index 0000000..d67efab --- /dev/null +++ b/vendor/symfony/form/Extension/Core/EventListener/ResizeFormListener.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormInterface; + +/** + * Resize a collection form element based on the data sent from the client. + * + * @author Bernhard Schussek + */ +class ResizeFormListener implements EventSubscriberInterface +{ + protected array $prototypeOptions; + + private \Closure|bool $deleteEmpty; + + public function __construct( + private string $type, + private array $options = [], + private bool $allowAdd = false, + private bool $allowDelete = false, + bool|callable $deleteEmpty = false, + ?array $prototypeOptions = null, + private bool $keepAsList = false, + ) { + $this->deleteEmpty = \is_bool($deleteEmpty) ? $deleteEmpty : $deleteEmpty(...); + $this->prototypeOptions = $prototypeOptions ?? $options; + } + + public static function getSubscribedEvents(): array + { + return [ + FormEvents::PRE_SET_DATA => 'preSetData', + FormEvents::PRE_SUBMIT => 'preSubmit', + // (MergeCollectionListener, MergeDoctrineCollectionListener) + FormEvents::SUBMIT => ['onSubmit', 50], + ]; + } + + public function preSetData(FormEvent $event): void + { + $form = $event->getForm(); + $data = $event->getData() ?? []; + + if (!\is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { + throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); + } + + // First remove all rows + foreach ($form as $name => $child) { + $form->remove($name); + } + + // Then add all rows again in the correct order + foreach ($data as $name => $value) { + $form->add($name, $this->type, array_replace([ + 'property_path' => '['.$name.']', + ], $this->options)); + } + } + + public function preSubmit(FormEvent $event): void + { + $form = $event->getForm(); + $data = $event->getData(); + + if (!\is_array($data)) { + $data = []; + } + + // Remove all empty rows + if ($this->allowDelete) { + foreach ($form as $name => $child) { + if (!isset($data[$name])) { + $form->remove($name); + } + } + } + + // Add all additional rows + if ($this->allowAdd) { + foreach ($data as $name => $value) { + if (!$form->has($name)) { + $form->add($name, $this->type, array_replace([ + 'property_path' => '['.$name.']', + ], $this->prototypeOptions)); + } + } + } + } + + public function onSubmit(FormEvent $event): void + { + $form = $event->getForm(); + $data = $event->getData() ?? []; + + // At this point, $data is an array or an array-like object that already contains the + // new entries, which were added by the data mapper. The data mapper ignores existing + // entries, so we need to manually unset removed entries in the collection. + + if (!\is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { + throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); + } + + if ($this->deleteEmpty) { + $previousData = $form->getData(); + /** @var FormInterface $child */ + foreach ($form as $name => $child) { + if (!$child->isValid() || !$child->isSynchronized()) { + continue; + } + + $isNew = !isset($previousData[$name]); + $isEmpty = \is_callable($this->deleteEmpty) ? ($this->deleteEmpty)($child->getData()) : $child->isEmpty(); + + // $isNew can only be true if allowAdd is true, so we don't + // need to check allowAdd again + if ($isEmpty && ($isNew || $this->allowDelete)) { + unset($data[$name]); + $form->remove($name); + } + } + } + + // The data mapper only adds, but does not remove items, so do this + // here + if ($this->allowDelete) { + $toDelete = []; + + foreach ($data as $name => $child) { + if (!$form->has($name)) { + $toDelete[] = $name; + } + } + + foreach ($toDelete as $name) { + unset($data[$name]); + } + } + + if ($this->keepAsList) { + $formReindex = []; + foreach ($form as $name => $child) { + $formReindex[] = $child; + $form->remove($name); + } + foreach ($formReindex as $index => $child) { + $form->add($index, $this->type, array_replace([ + 'property_path' => '['.$index.']', + ], $this->options)); + } + $data = array_values($data); + } + + $event->setData($data); + } +} diff --git a/vendor/symfony/form/Extension/Core/EventListener/TransformationFailureListener.php b/vendor/symfony/form/Extension/Core/EventListener/TransformationFailureListener.php new file mode 100644 index 0000000..48f0b9a --- /dev/null +++ b/vendor/symfony/form/Extension/Core/EventListener/TransformationFailureListener.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Christian Flothmann + */ +class TransformationFailureListener implements EventSubscriberInterface +{ + public function __construct( + private ?TranslatorInterface $translator = null, + ) { + } + + public static function getSubscribedEvents(): array + { + return [ + FormEvents::POST_SUBMIT => ['convertTransformationFailureToFormError', -1024], + ]; + } + + public function convertTransformationFailureToFormError(FormEvent $event): void + { + $form = $event->getForm(); + + if (null === $form->getTransformationFailure() || !$form->isValid()) { + return; + } + + foreach ($form as $child) { + if (!$child->isSynchronized()) { + return; + } + } + + $clientDataAsString = \is_scalar($form->getViewData()) ? (string) $form->getViewData() : get_debug_type($form->getViewData()); + $messageTemplate = $form->getConfig()->getOption('invalid_message', 'The value {{ value }} is not valid.'); + $messageParameters = array_replace(['{{ value }}' => $clientDataAsString], $form->getConfig()->getOption('invalid_message_parameters', [])); + + if (null !== $this->translator) { + $message = $this->translator->trans($messageTemplate, $messageParameters); + } else { + $message = strtr($messageTemplate, $messageParameters); + } + + $form->addError(new FormError($message, $messageTemplate, $messageParameters, null, $form->getTransformationFailure())); + } +} diff --git a/vendor/symfony/form/Extension/Core/EventListener/TrimListener.php b/vendor/symfony/form/Extension/Core/EventListener/TrimListener.php new file mode 100644 index 0000000..11151d3 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/EventListener/TrimListener.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\Util\StringUtil; + +/** + * Trims string data. + * + * @author Bernhard Schussek + */ +class TrimListener implements EventSubscriberInterface +{ + public function preSubmit(FormEvent $event): void + { + $data = $event->getData(); + + if (!\is_string($data)) { + return; + } + + $event->setData(StringUtil::trim($data)); + } + + public static function getSubscribedEvents(): array + { + return [FormEvents::PRE_SUBMIT => 'preSubmit']; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/BaseType.php b/vendor/symfony/form/Extension/Core/Type/BaseType.php new file mode 100644 index 0000000..68190c7 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/BaseType.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractRendererEngine; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Encapsulates common logic of {@link FormType} and {@link ButtonType}. + * + * This type does not appear in the form's type inheritance chain and as such + * cannot be extended (via {@link \Symfony\Component\Form\FormExtensionInterface}) nor themed. + * + * @author Bernhard Schussek + */ +abstract class BaseType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->setDisabled($options['disabled']); + $builder->setAutoInitialize($options['auto_initialize']); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $name = $form->getName(); + $blockName = $options['block_name'] ?: $form->getName(); + $translationDomain = $options['translation_domain']; + $labelTranslationParameters = $options['label_translation_parameters']; + $attrTranslationParameters = $options['attr_translation_parameters']; + $labelFormat = $options['label_format']; + + if ($view->parent) { + if ('' !== ($parentFullName = $view->parent->vars['full_name'])) { + $id = sprintf('%s_%s', $view->parent->vars['id'], $name); + $fullName = sprintf('%s[%s]', $parentFullName, $name); + $uniqueBlockPrefix = sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName); + } else { + $id = $name; + $fullName = $name; + $uniqueBlockPrefix = '_'.$blockName; + } + + $translationDomain ??= $view->parent->vars['translation_domain']; + + $labelTranslationParameters = array_merge($view->parent->vars['label_translation_parameters'], $labelTranslationParameters); + $attrTranslationParameters = array_merge($view->parent->vars['attr_translation_parameters'], $attrTranslationParameters); + + if (!$labelFormat) { + $labelFormat = $view->parent->vars['label_format']; + } + + $rootFormAttrOption = $form->getRoot()->getConfig()->getOption('form_attr'); + if ($options['form_attr'] || $rootFormAttrOption) { + $options['attr']['form'] = \is_string($rootFormAttrOption) ? $rootFormAttrOption : $form->getRoot()->getName(); + if (empty($options['attr']['form'])) { + throw new LogicException('"form_attr" option must be a string identifier on root form when it has no id.'); + } + } + } else { + $id = \is_string($options['form_attr']) ? $options['form_attr'] : $name; + $fullName = $name; + $uniqueBlockPrefix = '_'.$blockName; + + // Strip leading underscores and digits. These are allowed in + // form names, but not in HTML4 ID attributes. + // https://www.w3.org/TR/html401/struct/global#adef-id + $id = ltrim($id, '_0123456789'); + } + + $blockPrefixes = []; + for ($type = $form->getConfig()->getType(); null !== $type; $type = $type->getParent()) { + array_unshift($blockPrefixes, $type->getBlockPrefix()); + } + if (null !== $options['block_prefix']) { + $blockPrefixes[] = $options['block_prefix']; + } + $blockPrefixes[] = $uniqueBlockPrefix; + + $view->vars = array_replace($view->vars, [ + 'form' => $view, + 'id' => $id, + 'name' => $name, + 'full_name' => $fullName, + 'disabled' => $form->isDisabled(), + 'label' => $options['label'], + 'label_format' => $labelFormat, + 'label_html' => $options['label_html'], + 'multipart' => false, + 'attr' => $options['attr'], + 'block_prefixes' => $blockPrefixes, + 'unique_block_prefix' => $uniqueBlockPrefix, + 'row_attr' => $options['row_attr'], + 'translation_domain' => $translationDomain, + 'label_translation_parameters' => $labelTranslationParameters, + 'attr_translation_parameters' => $attrTranslationParameters, + 'priority' => $options['priority'], + // Using the block name here speeds up performance in collection + // forms, where each entry has the same full block name. + // Including the type is important too, because if rows of a + // collection form have different types (dynamically), they should + // be rendered differently. + // https://github.com/symfony/symfony/issues/5038 + AbstractRendererEngine::CACHE_KEY_VAR => $uniqueBlockPrefix.'_'.$form->getConfig()->getType()->getBlockPrefix(), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'block_name' => null, + 'block_prefix' => null, + 'disabled' => false, + 'label' => null, + 'label_format' => null, + 'row_attr' => [], + 'label_html' => false, + 'label_translation_parameters' => [], + 'attr_translation_parameters' => [], + 'attr' => [], + 'translation_domain' => null, + 'auto_initialize' => true, + 'priority' => 0, + 'form_attr' => false, + ]); + + $resolver->setAllowedTypes('block_prefix', ['null', 'string']); + $resolver->setAllowedTypes('attr', 'array'); + $resolver->setAllowedTypes('row_attr', 'array'); + $resolver->setAllowedTypes('label_html', 'bool'); + $resolver->setAllowedTypes('priority', 'int'); + $resolver->setAllowedTypes('form_attr', ['bool', 'string']); + + $resolver->setInfo('priority', 'The form rendering priority (higher priorities will be rendered first)'); + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/BirthdayType.php b/vendor/symfony/form/Extension/Core/Type/BirthdayType.php new file mode 100644 index 0000000..651b880 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/BirthdayType.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class BirthdayType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'years' => range((int) date('Y') - 120, date('Y')), + 'invalid_message' => 'Please enter a valid birthdate.', + ]); + + $resolver->setAllowedTypes('years', 'array'); + } + + public function getParent(): ?string + { + return DateType::class; + } + + public function getBlockPrefix(): string + { + return 'birthday'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/ButtonType.php b/vendor/symfony/form/Extension/Core/Type/ButtonType.php new file mode 100644 index 0000000..0ac0585 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/ButtonType.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\ButtonTypeInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * A form button. + * + * @author Bernhard Schussek + */ +class ButtonType extends BaseType implements ButtonTypeInterface +{ + public function getParent(): ?string + { + return null; + } + + public function getBlockPrefix(): string + { + return 'button'; + } + + public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); + + $resolver->setDefault('auto_initialize', false); + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/CheckboxType.php b/vendor/symfony/form/Extension/Core/Type/CheckboxType.php new file mode 100644 index 0000000..2f21279 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/CheckboxType.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\DataTransformer\BooleanToStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class CheckboxType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Unlike in other types, where the data is NULL by default, it + // needs to be a Boolean here. setData(null) is not acceptable + // for checkboxes and radio buttons (unless a custom model + // transformer handles this case). + // We cannot solve this case via overriding the "data" option, because + // doing so also calls setDataLocked(true). + $builder->setData($options['data'] ?? false); + $builder->addViewTransformer(new BooleanToStringTransformer($options['value'], $options['false_values'])); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars = array_replace($view->vars, [ + 'value' => $options['value'], + 'checked' => null !== $form->getViewData(), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $emptyData = static fn (FormInterface $form, $viewData) => $viewData; + + $resolver->setDefaults([ + 'value' => '1', + 'empty_data' => $emptyData, + 'compound' => false, + 'false_values' => [null], + 'invalid_message' => 'The checkbox has an invalid value.', + 'is_empty_callback' => static fn ($modelData): bool => false === $modelData, + ]); + + $resolver->setAllowedTypes('false_values', 'array'); + } + + public function getBlockPrefix(): string + { + return 'checkbox'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/ChoiceType.php b/vendor/symfony/form/Extension/Core/Type/ChoiceType.php new file mode 100644 index 0000000..2e9cb70 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/ChoiceType.php @@ -0,0 +1,469 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceAttr; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFieldName; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFilter; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceTranslationParameters; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue; +use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy; +use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice; +use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface; +use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; +use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; +use Symfony\Component\Form\ChoiceList\View\ChoiceListView; +use Symfony\Component\Form\ChoiceList\View\ChoiceView; +use Symfony\Component\Form\Event\PreSubmitEvent; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Extension\Core\DataMapper\CheckboxListMapper; +use Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper; +use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; +use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Contracts\Translation\TranslatorInterface; + +class ChoiceType extends AbstractType +{ + private ChoiceListFactoryInterface $choiceListFactory; + private ?TranslatorInterface $translator; + + public function __construct(?ChoiceListFactoryInterface $choiceListFactory = null, ?TranslatorInterface $translator = null) + { + $this->choiceListFactory = $choiceListFactory ?? new CachingFactoryDecorator( + new PropertyAccessDecorator( + new DefaultChoiceListFactory() + ) + ); + $this->translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $unknownValues = []; + $choiceList = $this->createChoiceList($options); + $builder->setAttribute('choice_list', $choiceList); + + if ($options['expanded']) { + $builder->setDataMapper($options['multiple'] ? new CheckboxListMapper() : new RadioListMapper()); + + // Initialize all choices before doing the index check below. + // This helps in cases where index checks are optimized for non + // initialized choice lists. For example, when using an SQL driver, + // the index check would read in one SQL query and the initialization + // requires another SQL query. When the initialization is done first, + // one SQL query is sufficient. + + $choiceListView = $this->createChoiceListView($choiceList, $options); + $builder->setAttribute('choice_list_view', $choiceListView); + + // Check if the choices already contain the empty value + // Only add the placeholder option if this is not the case + if (null !== $options['placeholder'] && 0 === \count($choiceList->getChoicesForValues(['']))) { + $placeholderView = new ChoiceView(null, '', $options['placeholder'], $options['placeholder_attr']); + + // "placeholder" is a reserved name + $this->addSubForm($builder, 'placeholder', $placeholderView, $options); + } + + $this->addSubForms($builder, $choiceListView->preferredChoices, $options); + $this->addSubForms($builder, $choiceListView->choices, $options); + } + + if ($options['expanded'] || $options['multiple']) { + // Make sure that scalar, submitted values are converted to arrays + // which can be submitted to the checkboxes/radio buttons + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($choiceList, $options, &$unknownValues) { + /** @var PreSubmitEvent $event */ + $form = $event->getForm(); + $data = $event->getData(); + + // Since the type always use mapper an empty array will not be + // considered as empty in Form::submit(), we need to evaluate + // empty data here so its value is submitted to sub forms + if (null === $data) { + $emptyData = $form->getConfig()->getEmptyData(); + $data = $emptyData instanceof \Closure ? $emptyData($form, $data) : $emptyData; + } + + // Convert the submitted data to a string, if scalar, before + // casting it to an array + if (!\is_array($data)) { + if ($options['multiple']) { + throw new TransformationFailedException('Expected an array.'); + } + + $data = (array) (string) $data; + } + + // A map from submitted values to integers + $valueMap = array_flip($data); + + // Make a copy of the value map to determine whether any unknown + // values were submitted + $unknownValues = $valueMap; + + // Reconstruct the data as mapping from child names to values + $knownValues = []; + + if ($options['expanded']) { + /** @var FormInterface $child */ + foreach ($form as $child) { + $value = $child->getConfig()->getOption('value'); + + // Add the value to $data with the child's name as key + if (isset($valueMap[$value])) { + $knownValues[$child->getName()] = $value; + unset($unknownValues[$value]); + continue; + } else { + $knownValues[$child->getName()] = null; + } + } + } else { + foreach ($choiceList->getChoicesForValues($data) as $key => $choice) { + $knownValues[] = $data[$key]; + unset($unknownValues[$data[$key]]); + } + } + + // The empty value is always known, independent of whether a + // field exists for it or not + unset($unknownValues['']); + + // Throw exception if unknown values were submitted (multiple choices will be handled in a different event listener below) + if (\count($unknownValues) > 0 && !$options['multiple']) { + throw new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues)))); + } + + $event->setData($knownValues); + }); + } + + if ($options['multiple']) { + $messageTemplate = $options['invalid_message'] ?? 'The value {{ value }} is not valid.'; + $translator = $this->translator; + + $builder->addEventListener(FormEvents::POST_SUBMIT, static function (FormEvent $event) use (&$unknownValues, $messageTemplate, $translator) { + // Throw exception if unknown values were submitted + if (\count($unknownValues) > 0) { + $form = $event->getForm(); + + $clientDataAsString = \is_scalar($form->getViewData()) ? (string) $form->getViewData() : (\is_array($form->getViewData()) ? implode('", "', array_keys($unknownValues)) : \gettype($form->getViewData())); + + if ($translator) { + $message = $translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators'); + } else { + $message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]); + } + + $form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', $clientDataAsString)))); + } + }); + + // tag without "multiple" option or list of radio inputs + $builder->addViewTransformer(new ChoiceToValueTransformer($choiceList)); + } + + if ($options['multiple'] && $options['by_reference']) { + // Make sure the collection created during the client->norm + // transformation is merged back into the original collection + $builder->addEventSubscriber(new MergeCollectionListener(true, true)); + } + + // To avoid issues when the submitted choices are arrays (i.e. array to string conversions), + // we have to ensure that all elements of the submitted choice data are NULL, strings or ints. + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) { + $data = $event->getData(); + + if (!\is_array($data)) { + return; + } + + foreach ($data as $v) { + if (null !== $v && !\is_string($v) && !\is_int($v)) { + throw new TransformationFailedException('All choices submitted must be NULL, strings or ints.'); + } + } + }, 256); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $choiceTranslationDomain = $options['choice_translation_domain']; + if ($view->parent && null === $choiceTranslationDomain) { + $choiceTranslationDomain = $view->vars['translation_domain']; + } + + /** @var ChoiceListInterface $choiceList */ + $choiceList = $form->getConfig()->getAttribute('choice_list'); + + /** @var ChoiceListView $choiceListView */ + $choiceListView = $form->getConfig()->hasAttribute('choice_list_view') + ? $form->getConfig()->getAttribute('choice_list_view') + : $this->createChoiceListView($choiceList, $options); + + $view->vars = array_replace($view->vars, [ + 'multiple' => $options['multiple'], + 'expanded' => $options['expanded'], + 'preferred_choices' => $choiceListView->preferredChoices, + 'choices' => $choiceListView->choices, + 'separator' => $options['separator'], + 'separator_html' => $options['separator_html'], + 'placeholder' => null, + 'placeholder_attr' => [], + 'choice_translation_domain' => $choiceTranslationDomain, + 'choice_translation_parameters' => $options['choice_translation_parameters'], + ]); + + // The decision, whether a choice is selected, is potentially done + // thousand of times during the rendering of a template. Provide a + // closure here that is optimized for the value of the form, to + // avoid making the type check inside the closure. + if ($options['multiple']) { + $view->vars['is_selected'] = static fn ($choice, array $values) => \in_array($choice, $values, true); + } else { + $view->vars['is_selected'] = static fn ($choice, $value) => $choice === $value; + } + + // Check if the choices already contain the empty value + $view->vars['placeholder_in_choices'] = $choiceListView->hasPlaceholder(); + + // Only add the empty value option if this is not the case + if (null !== $options['placeholder'] && !$view->vars['placeholder_in_choices']) { + $view->vars['placeholder'] = $options['placeholder']; + $view->vars['placeholder_attr'] = $options['placeholder_attr']; + } + + if ($options['multiple'] && !$options['expanded']) { + // Add "[]" to the name in case a select tag with multiple options is + // displayed. Otherwise only one of the selected options is sent in the + // POST request. + $view->vars['full_name'] .= '[]'; + } + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + if ($options['expanded']) { + // Radio buttons should have the same name as the parent + $childName = $view->vars['full_name']; + + // Checkboxes should append "[]" to allow multiple selection + if ($options['multiple']) { + $childName .= '[]'; + } + + foreach ($view as $childView) { + $childView->vars['full_name'] = $childName; + } + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $emptyData = static function (Options $options) { + if ($options['expanded'] && !$options['multiple']) { + return null; + } + + if ($options['multiple']) { + return []; + } + + return ''; + }; + + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; + + $placeholderNormalizer = static function (Options $options, $placeholder) { + if ($options['multiple']) { + // never use an empty value for this case + return null; + } elseif ($options['required'] && ($options['expanded'] || isset($options['attr']['size']) && $options['attr']['size'] > 1)) { + // placeholder for required radio buttons or a select with size > 1 does not make sense + return null; + } elseif (false === $placeholder) { + // an empty value should be added but the user decided otherwise + return null; + } elseif ($options['expanded'] && '' === $placeholder) { + // never use an empty label for radio buttons + return 'None'; + } + + // empty value has been set explicitly + return $placeholder; + }; + + $compound = static fn (Options $options) => $options['expanded']; + + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { + if (true === $choiceTranslationDomain) { + return $options['translation_domain']; + } + + return $choiceTranslationDomain; + }; + + $resolver->setDefaults([ + 'multiple' => false, + 'expanded' => false, + 'choices' => [], + 'choice_filter' => null, + 'choice_loader' => null, + 'choice_label' => null, + 'choice_name' => null, + 'choice_value' => null, + 'choice_attr' => null, + 'choice_translation_parameters' => [], + 'preferred_choices' => [], + 'separator' => '-------------------', + 'separator_html' => false, + 'duplicate_preferred_choices' => true, + 'group_by' => null, + 'empty_data' => $emptyData, + 'placeholder' => $placeholderDefault, + 'placeholder_attr' => [], + 'error_bubbling' => false, + 'compound' => $compound, + // The view data is always a string or an array of strings, + // even if the "data" option is manually set to an object. + // See https://github.com/symfony/symfony/pull/5582 + 'data_class' => null, + 'choice_translation_domain' => true, + 'trim' => false, + 'invalid_message' => 'The selected choice is invalid.', + ]); + + $resolver->setNormalizer('placeholder', $placeholderNormalizer); + $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); + + $resolver->setAllowedTypes('choices', ['null', 'array', \Traversable::class]); + $resolver->setAllowedTypes('choice_translation_domain', ['null', 'bool', 'string']); + $resolver->setAllowedTypes('choice_loader', ['null', ChoiceLoaderInterface::class, ChoiceLoader::class]); + $resolver->setAllowedTypes('choice_filter', ['null', 'callable', 'string', PropertyPath::class, ChoiceFilter::class]); + $resolver->setAllowedTypes('choice_label', ['null', 'bool', 'callable', 'string', PropertyPath::class, ChoiceLabel::class]); + $resolver->setAllowedTypes('choice_name', ['null', 'callable', 'string', PropertyPath::class, ChoiceFieldName::class]); + $resolver->setAllowedTypes('choice_value', ['null', 'callable', 'string', PropertyPath::class, ChoiceValue::class]); + $resolver->setAllowedTypes('choice_attr', ['null', 'array', 'callable', 'string', PropertyPath::class, ChoiceAttr::class]); + $resolver->setAllowedTypes('choice_translation_parameters', ['null', 'array', 'callable', ChoiceTranslationParameters::class]); + $resolver->setAllowedTypes('placeholder_attr', ['array']); + $resolver->setAllowedTypes('preferred_choices', ['array', \Traversable::class, 'callable', 'string', PropertyPath::class, PreferredChoice::class]); + $resolver->setAllowedTypes('separator', ['string']); + $resolver->setAllowedTypes('separator_html', ['bool']); + $resolver->setAllowedTypes('duplicate_preferred_choices', 'bool'); + $resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', PropertyPath::class, GroupBy::class]); + } + + public function getBlockPrefix(): string + { + return 'choice'; + } + + /** + * Adds the sub fields for an expanded choice field. + */ + private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options): void + { + foreach ($choiceViews as $name => $choiceView) { + // Flatten groups + if (\is_array($choiceView)) { + $this->addSubForms($builder, $choiceView, $options); + continue; + } + + if ($choiceView instanceof ChoiceGroupView) { + $this->addSubForms($builder, $choiceView->choices, $options); + continue; + } + + $this->addSubForm($builder, $name, $choiceView, $options); + } + } + + private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceView $choiceView, array $options): void + { + $choiceOpts = [ + 'value' => $choiceView->value, + 'label' => $choiceView->label, + 'label_html' => $options['label_html'], + 'attr' => $choiceView->attr, + 'label_translation_parameters' => $choiceView->labelTranslationParameters, + 'translation_domain' => $options['choice_translation_domain'], + 'block_name' => 'entry', + ]; + + if ($options['multiple']) { + $choiceType = CheckboxType::class; + // The user can check 0 or more checkboxes. If required + // is true, they are required to check all of them. + $choiceOpts['required'] = false; + } else { + $choiceType = RadioType::class; + } + + $builder->add($name, $choiceType, $choiceOpts); + } + + private function createChoiceList(array $options): ChoiceListInterface + { + if (null !== $options['choice_loader']) { + return $this->choiceListFactory->createListFromLoader( + $options['choice_loader'], + $options['choice_value'], + $options['choice_filter'] + ); + } + + // Harden against NULL values (like in EntityType and ModelType) + $choices = $options['choices'] ?? []; + + return $this->choiceListFactory->createListFromChoices( + $choices, + $options['choice_value'], + $options['choice_filter'] + ); + } + + private function createChoiceListView(ChoiceListInterface $choiceList, array $options): ChoiceListView + { + return $this->choiceListFactory->createView( + $choiceList, + $options['preferred_choices'], + $options['choice_label'], + $options['choice_name'], + $options['group_by'], + $options['choice_attr'], + $options['choice_translation_parameters'], + $options['duplicate_preferred_choices'], + ); + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/CollectionType.php b/vendor/symfony/form/Extension/Core/Type/CollectionType.php new file mode 100644 index 0000000..3cef931 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/CollectionType.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class CollectionType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $resizePrototypeOptions = null; + if ($options['allow_add'] && $options['prototype']) { + $resizePrototypeOptions = array_replace($options['entry_options'], $options['prototype_options']); + $prototypeOptions = array_replace([ + 'required' => $options['required'], + 'label' => $options['prototype_name'].'label__', + ], $resizePrototypeOptions); + + if (null !== $options['prototype_data']) { + $prototypeOptions['data'] = $options['prototype_data']; + } + + $prototype = $builder->create($options['prototype_name'], $options['entry_type'], $prototypeOptions); + $builder->setAttribute('prototype', $prototype->getForm()); + } + + $resizeListener = new ResizeFormListener( + $options['entry_type'], + $options['entry_options'], + $options['allow_add'], + $options['allow_delete'], + $options['delete_empty'], + $resizePrototypeOptions, + $options['keep_as_list'] + ); + + $builder->addEventSubscriber($resizeListener); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars = array_replace($view->vars, [ + 'allow_add' => $options['allow_add'], + 'allow_delete' => $options['allow_delete'], + ]); + + if ($form->getConfig()->hasAttribute('prototype')) { + $prototype = $form->getConfig()->getAttribute('prototype'); + $view->vars['prototype'] = $prototype->setParent($form)->createView($view); + } + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $prefixOffset = -2; + // check if the entry type also defines a block prefix + /** @var FormInterface $entry */ + foreach ($form as $entry) { + if ($entry->getConfig()->getOption('block_prefix')) { + --$prefixOffset; + } + + break; + } + + foreach ($view as $entryView) { + array_splice($entryView->vars['block_prefixes'], $prefixOffset, 0, 'collection_entry'); + } + + /** @var FormInterface $prototype */ + if ($prototype = $form->getConfig()->getAttribute('prototype')) { + if ($view->vars['prototype']->vars['multipart']) { + $view->vars['multipart'] = true; + } + + if ($prefixOffset > -3 && $prototype->getConfig()->getOption('block_prefix')) { + --$prefixOffset; + } + + array_splice($view->vars['prototype']->vars['block_prefixes'], $prefixOffset, 0, 'collection_entry'); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $entryOptionsNormalizer = static function (Options $options, $value) { + $value['block_name'] = 'entry'; + + return $value; + }; + + $resolver->setDefaults([ + 'allow_add' => false, + 'allow_delete' => false, + 'prototype' => true, + 'prototype_data' => null, + 'prototype_name' => '__name__', + 'entry_type' => TextType::class, + 'entry_options' => [], + 'prototype_options' => [], + 'delete_empty' => false, + 'invalid_message' => 'The collection is invalid.', + 'keep_as_list' => false, + ]); + + $resolver->setNormalizer('entry_options', $entryOptionsNormalizer); + + $resolver->setAllowedTypes('delete_empty', ['bool', 'callable']); + $resolver->setAllowedTypes('prototype_options', 'array'); + $resolver->setAllowedTypes('keep_as_list', ['bool']); + } + + public function getBlockPrefix(): string + { + return 'collection'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/ColorType.php b/vendor/symfony/form/Extension/Core/Type/ColorType.php new file mode 100644 index 0000000..d67d6dc --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/ColorType.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; + +class ColorType extends AbstractType +{ + /** + * @see https://www.w3.org/TR/html52/sec-forms.html#color-state-typecolor + */ + private const HTML5_PATTERN = '/^#[0-9a-f]{6}$/i'; + + public function __construct( + private ?TranslatorInterface $translator = null, + ) { + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['html5']) { + return; + } + + $translator = $this->translator; + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($translator): void { + $value = $event->getData(); + if (null === $value || '' === $value) { + return; + } + + if (\is_string($value) && preg_match(self::HTML5_PATTERN, $value)) { + return; + } + + $messageTemplate = 'This value is not a valid HTML5 color.'; + $messageParameters = [ + '{{ value }}' => \is_scalar($value) ? (string) $value : \gettype($value), + ]; + $message = $translator?->trans($messageTemplate, $messageParameters, 'validators') ?? $messageTemplate; + + $event->getForm()->addError(new FormError($message, $messageTemplate, $messageParameters)); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'html5' => false, + 'invalid_message' => 'Please select a valid color.', + ]); + + $resolver->setAllowedTypes('html5', 'bool'); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'color'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/CountryType.php b/vendor/symfony/form/Extension/Core/Type/CountryType.php new file mode 100644 index 0000000..adef745 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/CountryType.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Intl\Countries; +use Symfony\Component\Intl\Intl; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class CountryType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } + + $choiceTranslationLocale = $options['choice_translation_locale']; + $alpha3 = $options['alpha3']; + + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip($alpha3 ? Countries::getAlpha3Names($choiceTranslationLocale) : Countries::getNames($choiceTranslationLocale))), [$choiceTranslationLocale, $alpha3]); + }, + 'choice_translation_domain' => false, + 'choice_translation_locale' => null, + 'alpha3' => false, + 'invalid_message' => 'Please select a valid country.', + ]); + + $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); + $resolver->setAllowedTypes('alpha3', 'bool'); + } + + public function getParent(): ?string + { + return ChoiceType::class; + } + + public function getBlockPrefix(): string + { + return 'country'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/CurrencyType.php b/vendor/symfony/form/Extension/Core/Type/CurrencyType.php new file mode 100644 index 0000000..3581a77 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/CurrencyType.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Intl\Currencies; +use Symfony\Component\Intl\Intl; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class CurrencyType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } + + $choiceTranslationLocale = $options['choice_translation_locale']; + + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip(Currencies::getNames($choiceTranslationLocale))), $choiceTranslationLocale); + }, + 'choice_translation_domain' => false, + 'choice_translation_locale' => null, + 'invalid_message' => 'Please select a valid currency.', + ]); + + $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); + } + + public function getParent(): ?string + { + return ChoiceType::class; + } + + public function getBlockPrefix(): string + { + return 'currency'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/DateIntervalType.php b/vendor/symfony/form/Extension/Core/Type/DateIntervalType.php new file mode 100644 index 0000000..e7ebb3d --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/DateIntervalType.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\InvalidConfigurationException; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateIntervalToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateIntervalToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\IntegerToLocalizedStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ReversedTransformer; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Steffen Roßkamp + */ +class DateIntervalType extends AbstractType +{ + private const TIME_PARTS = [ + 'years', + 'months', + 'weeks', + 'days', + 'hours', + 'minutes', + 'seconds', + ]; + private const WIDGETS = [ + 'text' => TextType::class, + 'integer' => IntegerType::class, + 'choice' => ChoiceType::class, + ]; + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['with_years'] && !$options['with_months'] && !$options['with_weeks'] && !$options['with_days'] && !$options['with_hours'] && !$options['with_minutes'] && !$options['with_seconds']) { + throw new InvalidConfigurationException('You must enable at least one interval field.'); + } + if ($options['with_invert'] && 'single_text' === $options['widget']) { + throw new InvalidConfigurationException('The single_text widget does not support invertible intervals.'); + } + if ($options['with_weeks'] && $options['with_days']) { + throw new InvalidConfigurationException('You cannot enable weeks and days fields together.'); + } + $format = 'P'; + $parts = []; + if ($options['with_years']) { + $format .= '%yY'; + $parts[] = 'years'; + } + if ($options['with_months']) { + $format .= '%mM'; + $parts[] = 'months'; + } + if ($options['with_weeks']) { + $format .= '%wW'; + $parts[] = 'weeks'; + } + if ($options['with_days']) { + $format .= '%dD'; + $parts[] = 'days'; + } + if ($options['with_hours'] || $options['with_minutes'] || $options['with_seconds']) { + $format .= 'T'; + } + if ($options['with_hours']) { + $format .= '%hH'; + $parts[] = 'hours'; + } + if ($options['with_minutes']) { + $format .= '%iM'; + $parts[] = 'minutes'; + } + if ($options['with_seconds']) { + $format .= '%sS'; + $parts[] = 'seconds'; + } + if ($options['with_invert']) { + $parts[] = 'invert'; + } + if ('single_text' === $options['widget']) { + $builder->addViewTransformer(new DateIntervalToStringTransformer($format)); + } else { + foreach (self::TIME_PARTS as $part) { + if ($options['with_'.$part]) { + $childOptions = [ + 'error_bubbling' => true, + 'label' => $options['labels'][$part], + // Append generic carry-along options + 'required' => $options['required'], + 'translation_domain' => $options['translation_domain'], + // when compound the array entries are ignored, we need to cascade the configuration here + 'empty_data' => $options['empty_data'][$part] ?? null, + ]; + if ('choice' === $options['widget']) { + $childOptions['choice_translation_domain'] = false; + $childOptions['choices'] = $options[$part]; + $childOptions['placeholder'] = $options['placeholder'][$part]; + } + $childForm = $builder->create($part, self::WIDGETS[$options['widget']], $childOptions); + if ('integer' === $options['widget']) { + $childForm->addModelTransformer( + new ReversedTransformer( + new IntegerToLocalizedStringTransformer() + ) + ); + } + $builder->add($childForm); + } + } + if ($options['with_invert']) { + $builder->add('invert', CheckboxType::class, [ + 'label' => $options['labels']['invert'], + 'error_bubbling' => true, + 'required' => false, + 'translation_domain' => $options['translation_domain'], + ]); + } + $builder->addViewTransformer(new DateIntervalToArrayTransformer($parts, 'text' === $options['widget'])); + } + if ('string' === $options['input']) { + $builder->addModelTransformer( + new ReversedTransformer( + new DateIntervalToStringTransformer($format) + ) + ); + } elseif ('array' === $options['input']) { + $builder->addModelTransformer( + new ReversedTransformer( + new DateIntervalToArrayTransformer($parts) + ) + ); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $vars = [ + 'widget' => $options['widget'], + 'with_invert' => $options['with_invert'], + ]; + foreach (self::TIME_PARTS as $part) { + $vars['with_'.$part] = $options['with_'.$part]; + } + $view->vars = array_replace($view->vars, $vars); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; + $emptyData = static fn (Options $options) => 'single_text' === $options['widget'] ? '' : []; + + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; + + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { + if (\is_array($placeholder)) { + $default = $placeholderDefault($options); + + return array_merge(array_fill_keys(self::TIME_PARTS, $default), $placeholder); + } + + return array_fill_keys(self::TIME_PARTS, $placeholder); + }; + + $labelsNormalizer = static fn (Options $options, array $labels) => array_replace([ + 'years' => null, + 'months' => null, + 'days' => null, + 'weeks' => null, + 'hours' => null, + 'minutes' => null, + 'seconds' => null, + 'invert' => 'Negative interval', + ], array_filter($labels, static fn ($label) => null !== $label)); + + $resolver->setDefaults([ + 'with_years' => true, + 'with_months' => true, + 'with_days' => true, + 'with_weeks' => false, + 'with_hours' => false, + 'with_minutes' => false, + 'with_seconds' => false, + 'with_invert' => false, + 'years' => range(0, 100), + 'months' => range(0, 12), + 'weeks' => range(0, 52), + 'days' => range(0, 31), + 'hours' => range(0, 24), + 'minutes' => range(0, 60), + 'seconds' => range(0, 60), + 'widget' => 'choice', + 'input' => 'dateinterval', + 'placeholder' => $placeholderDefault, + 'by_reference' => true, + 'error_bubbling' => false, + // If initialized with a \DateInterval object, FormType initializes + // this option to "\DateInterval". Since the internal, normalized + // representation is not \DateInterval, but an array, we need to unset + // this option. + 'data_class' => null, + 'compound' => $compound, + 'empty_data' => $emptyData, + 'labels' => [], + 'invalid_message' => 'Please choose a valid date interval.', + ]); + $resolver->setNormalizer('placeholder', $placeholderNormalizer); + $resolver->setNormalizer('labels', $labelsNormalizer); + + $resolver->setAllowedValues( + 'input', + [ + 'dateinterval', + 'string', + 'array', + ] + ); + $resolver->setAllowedValues( + 'widget', + [ + 'single_text', + 'text', + 'integer', + 'choice', + ] + ); + // Don't clone \DateInterval classes, as i.e. format() + // does not work after that + $resolver->setAllowedValues('by_reference', true); + + $resolver->setAllowedTypes('years', 'array'); + $resolver->setAllowedTypes('months', 'array'); + $resolver->setAllowedTypes('weeks', 'array'); + $resolver->setAllowedTypes('days', 'array'); + $resolver->setAllowedTypes('hours', 'array'); + $resolver->setAllowedTypes('minutes', 'array'); + $resolver->setAllowedTypes('seconds', 'array'); + $resolver->setAllowedTypes('with_years', 'bool'); + $resolver->setAllowedTypes('with_months', 'bool'); + $resolver->setAllowedTypes('with_weeks', 'bool'); + $resolver->setAllowedTypes('with_days', 'bool'); + $resolver->setAllowedTypes('with_hours', 'bool'); + $resolver->setAllowedTypes('with_minutes', 'bool'); + $resolver->setAllowedTypes('with_seconds', 'bool'); + $resolver->setAllowedTypes('with_invert', 'bool'); + $resolver->setAllowedTypes('labels', 'array'); + } + + public function getBlockPrefix(): string + { + return 'dateinterval'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/DateTimeType.php b/vendor/symfony/form/Extension/Core/Type/DateTimeType.php new file mode 100644 index 0000000..1c67eeb --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/DateTimeType.php @@ -0,0 +1,346 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToDateTimeTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5LocalDateTimeTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ReversedTransformer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class DateTimeType extends AbstractType +{ + public const DEFAULT_DATE_FORMAT = \IntlDateFormatter::MEDIUM; + public const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM; + + /** + * The HTML5 datetime-local format as defined in + * http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local. + */ + public const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + + private const ACCEPTED_FORMATS = [ + \IntlDateFormatter::FULL, + \IntlDateFormatter::LONG, + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::SHORT, + ]; + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $parts = ['year', 'month', 'day', 'hour']; + $dateParts = ['year', 'month', 'day']; + $timeParts = ['hour']; + + if ($options['with_minutes']) { + $parts[] = 'minute'; + $timeParts[] = 'minute'; + } + + if ($options['with_seconds']) { + $parts[] = 'second'; + $timeParts[] = 'second'; + } + + $dateFormat = \is_int($options['date_format']) ? $options['date_format'] : self::DEFAULT_DATE_FORMAT; + $timeFormat = self::DEFAULT_TIME_FORMAT; + $calendar = \IntlDateFormatter::GREGORIAN; + $pattern = \is_string($options['format']) ? $options['format'] : null; + + if (!\in_array($dateFormat, self::ACCEPTED_FORMATS, true)) { + throw new InvalidOptionsException('The "date_format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.'); + } + + if ('single_text' === $options['widget']) { + if (self::HTML5_FORMAT === $pattern) { + $builder->addViewTransformer(new DateTimeToHtml5LocalDateTimeTransformer( + $options['model_timezone'], + $options['view_timezone'], + $options['with_seconds'] + )); + } else { + $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( + $options['model_timezone'], + $options['view_timezone'], + $dateFormat, + $timeFormat, + $calendar, + $pattern + )); + } + } else { + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: []; + // Only pass a subset of the options to children + $dateOptions = array_intersect_key($options, array_flip([ + 'years', + 'months', + 'days', + 'placeholder', + 'choice_translation_domain', + 'required', + 'translation_domain', + 'html5', + 'invalid_message', + 'invalid_message_parameters', + ])); + + if ($emptyData instanceof \Closure) { + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); + + return $emptyData[$option] ?? ''; + }; + + $dateOptions['empty_data'] = $lazyEmptyData('date'); + } elseif (isset($emptyData['date'])) { + $dateOptions['empty_data'] = $emptyData['date']; + } + + $timeOptions = array_intersect_key($options, array_flip([ + 'hours', + 'minutes', + 'seconds', + 'with_minutes', + 'with_seconds', + 'placeholder', + 'choice_translation_domain', + 'required', + 'translation_domain', + 'html5', + 'invalid_message', + 'invalid_message_parameters', + ])); + + if ($emptyData instanceof \Closure) { + $timeOptions['empty_data'] = $lazyEmptyData('time'); + } elseif (isset($emptyData['time'])) { + $timeOptions['empty_data'] = $emptyData['time']; + } + + if (false === $options['label']) { + $dateOptions['label'] = false; + $timeOptions['label'] = false; + } + + $dateOptions['widget'] = $options['date_widget'] ?? $options['widget'] ?? 'choice'; + $timeOptions['widget'] = $options['time_widget'] ?? $options['widget'] ?? 'choice'; + + if (null !== $options['date_label']) { + $dateOptions['label'] = $options['date_label']; + } + + if (null !== $options['time_label']) { + $timeOptions['label'] = $options['time_label']; + } + + if (null !== $options['date_format']) { + $dateOptions['format'] = $options['date_format']; + } + + $dateOptions['input'] = $timeOptions['input'] = 'array'; + $dateOptions['error_bubbling'] = $timeOptions['error_bubbling'] = true; + + $builder + ->addViewTransformer(new DataTransformerChain([ + new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts), + new ArrayToPartsTransformer([ + 'date' => $dateParts, + 'time' => $timeParts, + ]), + ])) + ->add('date', DateType::class, $dateOptions) + ->add('time', TimeType::class, $timeOptions) + ; + } + + if ('datetime_immutable' === $options['input']) { + $builder->addModelTransformer(new DateTimeImmutableToDateTimeTransformer()); + } elseif ('string' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], $options['input_format']) + )); + } elseif ('timestamp' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) + )); + } elseif ('array' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) + )); + } + + if (\in_array($options['input'], ['datetime', 'datetime_immutable'], true) && null !== $options['model_timezone']) { + $builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) use ($options): void { + $date = $event->getData(); + + if (!$date instanceof \DateTimeInterface) { + return; + } + + if ($date->getTimezone()->getName() !== $options['model_timezone']) { + throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone'])); + } + }); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['widget'] = $options['widget']; + + // Change the input to an HTML5 datetime input if + // * the widget is set to "single_text" + // * the format matches the one expected by HTML5 + // * the html5 is set to true + if ($options['html5'] && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { + $view->vars['type'] = 'datetime-local'; + + // we need to force the browser to display the seconds by + // adding the HTML attribute step if not already defined. + // Otherwise the browser will not display and so not send the seconds + // therefore the value will always be considered as invalid. + if (!isset($view->vars['attr']['step'])) { + if ($options['with_seconds']) { + $view->vars['attr']['step'] = 1; + } elseif (!$options['with_minutes']) { + $view->vars['attr']['step'] = 3600; + } + } + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; + + $resolver->setDefaults([ + 'input' => 'datetime', + 'model_timezone' => null, + 'view_timezone' => null, + 'format' => self::HTML5_FORMAT, + 'date_format' => null, + 'widget' => null, + 'date_widget' => null, + 'time_widget' => null, + 'with_minutes' => true, + 'with_seconds' => false, + 'html5' => true, + // Don't modify \DateTime classes by reference, we treat + // them like immutable value objects + 'by_reference' => false, + 'error_bubbling' => false, + // If initialized with a \DateTime object, FormType initializes + // this option to "\DateTime". Since the internal, normalized + // representation is not \DateTime, but an array, we need to unset + // this option. + 'data_class' => null, + 'compound' => $compound, + 'date_label' => null, + 'time_label' => null, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', + 'input_format' => 'Y-m-d H:i:s', + 'invalid_message' => 'Please enter a valid date and time.', + ]); + + // Don't add some defaults in order to preserve the defaults + // set in DateType and TimeType + $resolver->setDefined([ + 'placeholder', + 'choice_translation_domain', + 'years', + 'months', + 'days', + 'hours', + 'minutes', + 'seconds', + ]); + + $resolver->setAllowedValues('input', [ + 'datetime', + 'datetime_immutable', + 'string', + 'timestamp', + 'array', + ]); + $resolver->setAllowedValues('date_widget', [ + null, // inherit default from DateType + 'single_text', + 'text', + 'choice', + ]); + $resolver->setAllowedValues('time_widget', [ + null, // inherit default from TimeType + 'single_text', + 'text', + 'choice', + ]); + // This option will overwrite "date_widget" and "time_widget" options + $resolver->setAllowedValues('widget', [ + null, // default, don't overwrite options + 'single_text', + 'text', + 'choice', + ]); + + $resolver->setAllowedTypes('input_format', 'string'); + + $resolver->setNormalizer('date_format', static function (Options $options, $dateFormat) { + if (null !== $dateFormat && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { + throw new LogicException(sprintf('Cannot use the "date_format" option of the "%s" with an HTML5 date.', self::class)); + } + + return $dateFormat; + }); + $resolver->setNormalizer('widget', static function (Options $options, $widget) { + if ('single_text' === $widget) { + if (null !== $options['date_widget']) { + throw new LogicException(sprintf('Cannot use the "date_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class)); + } + if (null !== $options['time_widget']) { + throw new LogicException(sprintf('Cannot use the "time_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class)); + } + } elseif (null === $widget && null === $options['date_widget'] && null === $options['time_widget']) { + return 'single_text'; + } + + return $widget; + }); + $resolver->setNormalizer('html5', static function (Options $options, $html5) { + if ($html5 && self::HTML5_FORMAT !== $options['format']) { + throw new LogicException(sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class)); + } + + return $html5; + }); + } + + public function getBlockPrefix(): string + { + return 'datetime'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/DateType.php b/vendor/symfony/form/Extension/Core/Type/DateType.php new file mode 100644 index 0000000..cfbbaf4 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/DateType.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToDateTimeTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ReversedTransformer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class DateType extends AbstractType +{ + public const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM; + public const HTML5_FORMAT = 'yyyy-MM-dd'; + + private const ACCEPTED_FORMATS = [ + \IntlDateFormatter::FULL, + \IntlDateFormatter::LONG, + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::SHORT, + ]; + + private const WIDGETS = [ + 'text' => TextType::class, + 'choice' => ChoiceType::class, + ]; + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $dateFormat = \is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT; + $timeFormat = \IntlDateFormatter::NONE; + $calendar = \IntlDateFormatter::GREGORIAN; + $pattern = \is_string($options['format']) ? $options['format'] : ''; + + if (!\in_array($dateFormat, self::ACCEPTED_FORMATS, true)) { + throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.'); + } + + if ('single_text' === $options['widget']) { + if ('' !== $pattern && !str_contains($pattern, 'y') && !str_contains($pattern, 'M') && !str_contains($pattern, 'd')) { + throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" or "d". Its current value is "%s".', $pattern)); + } + + $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( + $options['model_timezone'], + $options['view_timezone'], + $dateFormat, + $timeFormat, + $calendar, + $pattern + )); + } else { + if ('' !== $pattern && (!str_contains($pattern, 'y') || !str_contains($pattern, 'M') || !str_contains($pattern, 'd'))) { + throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".', $pattern)); + } + + $yearOptions = $monthOptions = $dayOptions = [ + 'error_bubbling' => true, + 'empty_data' => '', + ]; + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: []; + + if ($emptyData instanceof \Closure) { + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); + + return $emptyData[$option] ?? ''; + }; + + $yearOptions['empty_data'] = $lazyEmptyData('year'); + $monthOptions['empty_data'] = $lazyEmptyData('month'); + $dayOptions['empty_data'] = $lazyEmptyData('day'); + } else { + if (isset($emptyData['year'])) { + $yearOptions['empty_data'] = $emptyData['year']; + } + if (isset($emptyData['month'])) { + $monthOptions['empty_data'] = $emptyData['month']; + } + if (isset($emptyData['day'])) { + $dayOptions['empty_data'] = $emptyData['day']; + } + } + + if (isset($options['invalid_message'])) { + $dayOptions['invalid_message'] = $options['invalid_message']; + $monthOptions['invalid_message'] = $options['invalid_message']; + $yearOptions['invalid_message'] = $options['invalid_message']; + } + + if (isset($options['invalid_message_parameters'])) { + $dayOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + $monthOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + $yearOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + } + + $formatter = new \IntlDateFormatter( + \Locale::getDefault(), + $dateFormat, + $timeFormat, + // see https://bugs.php.net/66323 + class_exists(\IntlTimeZone::class, false) ? \IntlTimeZone::createDefault() : null, + $calendar, + $pattern + ); + + // new \IntlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/66323 + if (!$formatter) { + throw new InvalidOptionsException(intl_get_error_message(), intl_get_error_code()); + } + + $formatter->setLenient(false); + + if ('choice' === $options['widget']) { + // Only pass a subset of the options to children + $yearOptions['choices'] = $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])); + $yearOptions['placeholder'] = $options['placeholder']['year']; + $yearOptions['choice_translation_domain'] = $options['choice_translation_domain']['year']; + $monthOptions['choices'] = $this->formatTimestamps($formatter, '/[M|L]+/', $this->listMonths($options['months'])); + $monthOptions['placeholder'] = $options['placeholder']['month']; + $monthOptions['choice_translation_domain'] = $options['choice_translation_domain']['month']; + $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])); + $dayOptions['placeholder'] = $options['placeholder']['day']; + $dayOptions['choice_translation_domain'] = $options['choice_translation_domain']['day']; + } + + // Append generic carry-along options + foreach (['required', 'translation_domain'] as $passOpt) { + $yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt]; + } + + $builder + ->add('year', self::WIDGETS[$options['widget']], $yearOptions) + ->add('month', self::WIDGETS[$options['widget']], $monthOptions) + ->add('day', self::WIDGETS[$options['widget']], $dayOptions) + ->addViewTransformer(new DateTimeToArrayTransformer( + $options['model_timezone'], $options['view_timezone'], ['year', 'month', 'day'] + )) + ->setAttribute('formatter', $formatter) + ; + } + + if ('datetime_immutable' === $options['input']) { + $builder->addModelTransformer(new DateTimeImmutableToDateTimeTransformer()); + } elseif ('string' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], $options['input_format']) + )); + } elseif ('timestamp' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) + )); + } elseif ('array' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], ['year', 'month', 'day']) + )); + } + + if (\in_array($options['input'], ['datetime', 'datetime_immutable'], true) && null !== $options['model_timezone']) { + $builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) use ($options): void { + $date = $event->getData(); + + if (!$date instanceof \DateTimeInterface) { + return; + } + + if ($date->getTimezone()->getName() !== $options['model_timezone']) { + throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone'])); + } + }); + } + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['widget'] = $options['widget']; + + // Change the input to an HTML5 date input if + // * the widget is set to "single_text" + // * the format matches the one expected by HTML5 + // * the html5 is set to true + if ($options['html5'] && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { + $view->vars['type'] = 'date'; + } + + if ($form->getConfig()->hasAttribute('formatter')) { + $pattern = $form->getConfig()->getAttribute('formatter')->getPattern(); + + // remove special characters unless the format was explicitly specified + if (!\is_string($options['format'])) { + // remove quoted strings first + $pattern = preg_replace('/\'[^\']+\'/', '', $pattern); + + // remove remaining special chars + $pattern = preg_replace('/[^yMd]+/', '', $pattern); + } + + // set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy) + // lookup various formats at http://userguide.icu-project.org/formatparse/datetime + if (preg_match('/^([yMd]+)[^yMd]*([yMd]+)[^yMd]*([yMd]+)$/', $pattern)) { + $pattern = preg_replace(['/y+/', '/M+/', '/d+/'], ['{{ year }}', '{{ month }}', '{{ day }}'], $pattern); + } else { + // default fallback + $pattern = '{{ year }}{{ month }}{{ day }}'; + } + + $view->vars['date_pattern'] = $pattern; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; + + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; + + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { + if (\is_array($placeholder)) { + $default = $placeholderDefault($options); + + return array_merge( + ['year' => $default, 'month' => $default, 'day' => $default], + $placeholder + ); + } + + return [ + 'year' => $placeholder, + 'month' => $placeholder, + 'day' => $placeholder, + ]; + }; + + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { + if (\is_array($choiceTranslationDomain)) { + $default = false; + + return array_replace( + ['year' => $default, 'month' => $default, 'day' => $default], + $choiceTranslationDomain + ); + } + + return [ + 'year' => $choiceTranslationDomain, + 'month' => $choiceTranslationDomain, + 'day' => $choiceTranslationDomain, + ]; + }; + + $format = static fn (Options $options) => 'single_text' === $options['widget'] ? self::HTML5_FORMAT : self::DEFAULT_FORMAT; + + $resolver->setDefaults([ + 'years' => range((int) date('Y') - 5, (int) date('Y') + 5), + 'months' => range(1, 12), + 'days' => range(1, 31), + 'widget' => 'single_text', + 'input' => 'datetime', + 'format' => $format, + 'model_timezone' => null, + 'view_timezone' => null, + 'placeholder' => $placeholderDefault, + 'html5' => true, + // Don't modify \DateTime classes by reference, we treat + // them like immutable value objects + 'by_reference' => false, + 'error_bubbling' => false, + // If initialized with a \DateTime object, FormType initializes + // this option to "\DateTime". Since the internal, normalized + // representation is not \DateTime, but an array, we need to unset + // this option. + 'data_class' => null, + 'compound' => $compound, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', + 'choice_translation_domain' => false, + 'input_format' => 'Y-m-d', + 'invalid_message' => 'Please enter a valid date.', + ]); + + $resolver->setNormalizer('placeholder', $placeholderNormalizer); + $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); + + $resolver->setAllowedValues('input', [ + 'datetime', + 'datetime_immutable', + 'string', + 'timestamp', + 'array', + ]); + $resolver->setAllowedValues('widget', [ + 'single_text', + 'text', + 'choice', + ]); + + $resolver->setAllowedTypes('format', ['int', 'string']); + $resolver->setAllowedTypes('years', 'array'); + $resolver->setAllowedTypes('months', 'array'); + $resolver->setAllowedTypes('days', 'array'); + $resolver->setAllowedTypes('input_format', 'string'); + + $resolver->setNormalizer('html5', static function (Options $options, $html5) { + if ($html5 && 'single_text' === $options['widget'] && self::HTML5_FORMAT !== $options['format']) { + throw new LogicException(sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class)); + } + + return $html5; + }); + } + + public function getBlockPrefix(): string + { + return 'date'; + } + + private function formatTimestamps(\IntlDateFormatter $formatter, string $regex, array $timestamps): array + { + $pattern = $formatter->getPattern(); + $timezone = $formatter->getTimeZoneId(); + $formattedTimestamps = []; + + $formatter->setTimeZone('UTC'); + + if (preg_match($regex, $pattern, $matches)) { + $formatter->setPattern($matches[0]); + + foreach ($timestamps as $timestamp => $choice) { + $formattedTimestamps[$formatter->format($timestamp)] = $choice; + } + + // I'd like to clone the formatter above, but then we get a + // segmentation fault, so let's restore the old state instead + $formatter->setPattern($pattern); + } + + $formatter->setTimeZone($timezone); + + return $formattedTimestamps; + } + + private function listYears(array $years): array + { + $result = []; + + foreach ($years as $year) { + $result[\PHP_INT_SIZE === 4 ? \DateTimeImmutable::createFromFormat('Y e', $year.' UTC')->format('U') : gmmktime(0, 0, 0, 6, 15, $year)] = $year; + } + + return $result; + } + + private function listMonths(array $months): array + { + $result = []; + + foreach ($months as $month) { + $result[gmmktime(0, 0, 0, $month, 15)] = $month; + } + + return $result; + } + + private function listDays(array $days): array + { + $result = []; + + foreach ($days as $day) { + $result[gmmktime(0, 0, 0, 5, $day)] = $day; + } + + return $result; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/EmailType.php b/vendor/symfony/form/Extension/Core/Type/EmailType.php new file mode 100644 index 0000000..99a5689 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/EmailType.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class EmailType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'invalid_message' => 'Please enter a valid email address.', + ]); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'email'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/EnumType.php b/vendor/symfony/form/Extension/Core/Type/EnumType.php new file mode 100644 index 0000000..bfede9c --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/EnumType.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatableInterface; + +/** + * A choice type for native PHP enums. + * + * @author Alexander M. Turek + */ +final class EnumType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setRequired(['class']) + ->setAllowedTypes('class', 'string') + ->setAllowedValues('class', enum_exists(...)) + ->setDefault('choices', static fn (Options $options): array => $options['class']::cases()) + ->setDefault('choice_label', static fn (\UnitEnum $choice) => $choice instanceof TranslatableInterface ? $choice : $choice->name) + ->setDefault('choice_value', static function (Options $options): ?\Closure { + if (!is_a($options['class'], \BackedEnum::class, true)) { + return null; + } + + return static function (?\BackedEnum $choice): ?string { + if (null === $choice) { + return null; + } + + return (string) $choice->value; + }; + }) + ; + } + + public function getParent(): string + { + return ChoiceType::class; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/FileType.php b/vendor/symfony/form/Extension/Core/Type/FileType.php new file mode 100644 index 0000000..b4b4d86 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/FileType.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Event\PreSubmitEvent; +use Symfony\Component\Form\FileUploadError; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; + +class FileType extends AbstractType +{ + public const KIB_BYTES = 1024; + public const MIB_BYTES = 1048576; + + private const SUFFIXES = [ + 1 => 'bytes', + self::KIB_BYTES => 'KiB', + self::MIB_BYTES => 'MiB', + ]; + + public function __construct( + private ?TranslatorInterface $translator = null, + ) { + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Ensure that submitted data is always an uploaded file or an array of some + $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) { + /** @var PreSubmitEvent $event */ + $form = $event->getForm(); + $requestHandler = $form->getConfig()->getRequestHandler(); + + if ($options['multiple']) { + $data = []; + $files = $event->getData(); + + if (!\is_array($files)) { + $files = []; + } + + foreach ($files as $file) { + if ($requestHandler->isFileUpload($file)) { + $data[] = $file; + + if (method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($file)) { + $form->addError($this->getFileUploadError($errorCode)); + } + } + } + + // Since the array is never considered empty in the view data format + // on submission, we need to evaluate the configured empty data here + if ([] === $data) { + $emptyData = $form->getConfig()->getEmptyData(); + $data = $emptyData instanceof \Closure ? $emptyData($form, $data) : $emptyData; + } + + $event->setData($data); + } elseif ($requestHandler->isFileUpload($event->getData()) && method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($event->getData())) { + $form->addError($this->getFileUploadError($errorCode)); + } elseif (!$requestHandler->isFileUpload($event->getData())) { + $event->setData(null); + } + }); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['multiple']) { + $view->vars['full_name'] .= '[]'; + $view->vars['attr']['multiple'] = 'multiple'; + } + + $view->vars = array_replace($view->vars, [ + 'type' => 'file', + 'value' => '', + ]); + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['multipart'] = true; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $dataClass = null; + if (class_exists(File::class)) { + $dataClass = static fn (Options $options) => $options['multiple'] ? null : File::class; + } + + $emptyData = static fn (Options $options) => $options['multiple'] ? [] : null; + + $resolver->setDefaults([ + 'compound' => false, + 'data_class' => $dataClass, + 'empty_data' => $emptyData, + 'multiple' => false, + 'allow_file_upload' => true, + 'invalid_message' => 'Please select a valid file.', + ]); + } + + public function getBlockPrefix(): string + { + return 'file'; + } + + private function getFileUploadError(int $errorCode): FileUploadError + { + $messageParameters = []; + + if (\UPLOAD_ERR_INI_SIZE === $errorCode) { + [$limitAsString, $suffix] = $this->factorizeSizes(0, self::getMaxFilesize()); + $messageTemplate = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.'; + $messageParameters = [ + '{{ limit }}' => $limitAsString, + '{{ suffix }}' => $suffix, + ]; + } elseif (\UPLOAD_ERR_FORM_SIZE === $errorCode) { + $messageTemplate = 'The file is too large.'; + } else { + $messageTemplate = 'The file could not be uploaded.'; + } + + if (null !== $this->translator) { + $message = $this->translator->trans($messageTemplate, $messageParameters, 'validators'); + } else { + $message = strtr($messageTemplate, $messageParameters); + } + + return new FileUploadError($message, $messageTemplate, $messageParameters); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * This method should be kept in sync with Symfony\Component\HttpFoundation\File\UploadedFile::getMaxFilesize(). + */ + private static function getMaxFilesize(): int|float + { + $iniMax = strtolower(\ini_get('upload_max_filesize')); + + if ('' === $iniMax) { + return \PHP_INT_MAX; + } + + $max = ltrim($iniMax, '+'); + if (str_starts_with($max, '0x')) { + $max = \intval($max, 16); + } elseif (str_starts_with($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Converts the limit to the smallest possible number + * (i.e. try "MB", then "kB", then "bytes"). + * + * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::factorizeSizes(). + */ + private function factorizeSizes(int $size, int|float $limit): array + { + $coef = self::MIB_BYTES; + $coefFactor = self::KIB_BYTES; + + $limitAsString = (string) ($limit / $coef); + + // Restrict the limit to 2 decimals (without rounding! we + // need the precise value) + while (self::moreDecimalsThan($limitAsString, 2)) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + } + + // Convert size to the same measure, but round to 2 decimals + $sizeAsString = (string) round($size / $coef, 2); + + // If the size and limit produce the same string output + // (due to rounding), reduce the coefficient + while ($sizeAsString === $limitAsString) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + $sizeAsString = (string) round($size / $coef, 2); + } + + return [$limitAsString, self::SUFFIXES[$coef]]; + } + + /** + * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::moreDecimalsThan(). + */ + private static function moreDecimalsThan(string $double, int $numberOfDecimals): bool + { + return \strlen($double) > \strlen(round($double, $numberOfDecimals)); + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/FormType.php b/vendor/symfony/form/Extension/Core/Type/FormType.php new file mode 100644 index 0000000..9497bad --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/FormType.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataAccessor\CallbackAccessor; +use Symfony\Component\Form\Extension\Core\DataAccessor\ChainAccessor; +use Symfony\Component\Form\Extension\Core\DataAccessor\PropertyPathAccessor; +use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper; +use Symfony\Component\Form\Extension\Core\EventListener\TrimListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Contracts\Translation\TranslatableInterface; + +class FormType extends BaseType +{ + private DataMapper $dataMapper; + + public function __construct(?PropertyAccessorInterface $propertyAccessor = null) + { + $this->dataMapper = new DataMapper(new ChainAccessor([ + new CallbackAccessor(), + new PropertyPathAccessor($propertyAccessor ?? PropertyAccess::createPropertyAccessor()), + ])); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + parent::buildForm($builder, $options); + + $isDataOptionSet = \array_key_exists('data', $options); + + $builder + ->setRequired($options['required']) + ->setErrorBubbling($options['error_bubbling']) + ->setEmptyData($options['empty_data']) + ->setPropertyPath($options['property_path']) + ->setMapped($options['mapped']) + ->setByReference($options['by_reference']) + ->setInheritData($options['inherit_data']) + ->setCompound($options['compound']) + ->setData($isDataOptionSet ? $options['data'] : null) + ->setDataLocked($isDataOptionSet) + ->setDataMapper($options['compound'] ? $this->dataMapper : null) + ->setMethod($options['method']) + ->setAction($options['action']); + + if ($options['trim']) { + $builder->addEventSubscriber(new TrimListener()); + } + + $builder->setIsEmptyCallback($options['is_empty_callback']); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + parent::buildView($view, $form, $options); + + $name = $form->getName(); + $helpTranslationParameters = $options['help_translation_parameters']; + + if ($view->parent) { + if ('' === $name) { + throw new LogicException('Form node with empty name can be used only as root form node.'); + } + + // Complex fields are read-only if they themselves or their parents are. + if (!isset($view->vars['attr']['readonly']) && isset($view->parent->vars['attr']['readonly']) && false !== $view->parent->vars['attr']['readonly']) { + $view->vars['attr']['readonly'] = true; + } + + $helpTranslationParameters = array_merge($view->parent->vars['help_translation_parameters'], $helpTranslationParameters); + } + + $formConfig = $form->getConfig(); + $view->vars = array_replace($view->vars, [ + 'errors' => $form->getErrors(), + 'valid' => $form->isSubmitted() ? $form->isValid() : true, + 'value' => $form->getViewData(), + 'data' => $form->getNormData(), + 'required' => $form->isRequired(), + 'label_attr' => $options['label_attr'], + 'help' => $options['help'], + 'help_attr' => $options['help_attr'], + 'help_html' => $options['help_html'], + 'help_translation_parameters' => $helpTranslationParameters, + 'compound' => $formConfig->getCompound(), + 'method' => $formConfig->getMethod(), + 'action' => $formConfig->getAction(), + 'submitted' => $form->isSubmitted(), + ]); + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $multipart = false; + + foreach ($view->children as $child) { + if ($child->vars['multipart']) { + $multipart = true; + break; + } + } + + $view->vars['multipart'] = $multipart; + } + + public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); + + // Derive "data_class" option from passed "data" object + $dataClass = static fn (Options $options) => isset($options['data']) && \is_object($options['data']) ? $options['data']::class : null; + + // Derive "empty_data" closure from "data_class" option + $emptyData = static function (Options $options) { + $class = $options['data_class']; + + if (null !== $class) { + return static fn (FormInterface $form) => $form->isEmpty() && !$form->isRequired() ? null : new $class(); + } + + return static fn (FormInterface $form) => $form->getConfig()->getCompound() ? [] : ''; + }; + + // Wrap "post_max_size_message" in a closure to translate it lazily + $uploadMaxSizeMessage = static fn (Options $options) => static fn () => $options['post_max_size_message']; + + // For any form that is not represented by a single HTML control, + // errors should bubble up by default + $errorBubbling = static fn (Options $options) => $options['compound'] && !$options['inherit_data']; + + // If data is given, the form is locked to that data + // (independent of its value) + $resolver->setDefined([ + 'data', + ]); + + $resolver->setDefaults([ + 'data_class' => $dataClass, + 'empty_data' => $emptyData, + 'trim' => true, + 'required' => true, + 'property_path' => null, + 'mapped' => true, + 'by_reference' => true, + 'error_bubbling' => $errorBubbling, + 'label_attr' => [], + 'inherit_data' => false, + 'compound' => true, + 'method' => 'POST', + // According to RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt) + // section 4.2., empty URIs are considered same-document references + 'action' => '', + 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', + 'upload_max_size_message' => $uploadMaxSizeMessage, // internal + 'allow_file_upload' => false, + 'help' => null, + 'help_attr' => [], + 'help_html' => false, + 'help_translation_parameters' => [], + 'invalid_message' => 'This value is not valid.', + 'invalid_message_parameters' => [], + 'is_empty_callback' => null, + 'getter' => null, + 'setter' => null, + ]); + + $resolver->setAllowedTypes('label_attr', 'array'); + $resolver->setAllowedTypes('action', 'string'); + $resolver->setAllowedTypes('upload_max_size_message', ['callable']); + $resolver->setAllowedTypes('help', ['string', 'null', TranslatableInterface::class]); + $resolver->setAllowedTypes('help_attr', 'array'); + $resolver->setAllowedTypes('help_html', 'bool'); + $resolver->setAllowedTypes('is_empty_callback', ['null', 'callable']); + $resolver->setAllowedTypes('getter', ['null', 'callable']); + $resolver->setAllowedTypes('setter', ['null', 'callable']); + + $resolver->setInfo('getter', 'A callable that accepts two arguments (the view data and the current form field) and must return a value.'); + $resolver->setInfo('setter', 'A callable that accepts three arguments (a reference to the view data, the submitted value and the current form field).'); + } + + public function getParent(): ?string + { + return null; + } + + public function getBlockPrefix(): string + { + return 'form'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/HiddenType.php b/vendor/symfony/form/Extension/Core/Type/HiddenType.php new file mode 100644 index 0000000..73449a3 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/HiddenType.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class HiddenType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + // hidden fields cannot have a required attribute + 'required' => false, + // Pass errors to the parent + 'error_bubbling' => true, + 'compound' => false, + 'invalid_message' => 'The hidden field is invalid.', + ]); + } + + public function getBlockPrefix(): string + { + return 'hidden'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/IntegerType.php b/vendor/symfony/form/Extension/Core/Type/IntegerType.php new file mode 100644 index 0000000..365c8a8 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/IntegerType.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\DataTransformer\IntegerToLocalizedStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class IntegerType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addViewTransformer(new IntegerToLocalizedStringTransformer($options['grouping'], $options['rounding_mode'], !$options['grouping'] ? 'en' : null)); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['grouping']) { + $view->vars['type'] = 'text'; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'grouping' => false, + // Integer cast rounds towards 0, so do the same when displaying fractions + 'rounding_mode' => \NumberFormatter::ROUND_DOWN, + 'compound' => false, + 'invalid_message' => 'Please enter an integer.', + ]); + + $resolver->setAllowedValues('rounding_mode', [ + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ]); + } + + public function getBlockPrefix(): string + { + return 'integer'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/LanguageType.php b/vendor/symfony/form/Extension/Core/Type/LanguageType.php new file mode 100644 index 0000000..e81571f --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/LanguageType.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Languages; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class LanguageType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } + $choiceTranslationLocale = $options['choice_translation_locale']; + $useAlpha3Codes = $options['alpha3']; + $choiceSelfTranslation = $options['choice_self_translation']; + + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static function () use ($choiceTranslationLocale, $useAlpha3Codes, $choiceSelfTranslation) { + if (true === $choiceSelfTranslation) { + foreach (Languages::getLanguageCodes() as $alpha2Code) { + try { + $languageCode = $useAlpha3Codes ? Languages::getAlpha3Code($alpha2Code) : $alpha2Code; + $languagesList[$languageCode] = Languages::getName($alpha2Code, $alpha2Code); + } catch (MissingResourceException) { + // ignore errors like "Couldn't read the indices for the locale 'meta'" + } + } + } else { + $languagesList = $useAlpha3Codes ? Languages::getAlpha3Names($choiceTranslationLocale) : Languages::getNames($choiceTranslationLocale); + } + + return array_flip($languagesList); + }), [$choiceTranslationLocale, $useAlpha3Codes, $choiceSelfTranslation]); + }, + 'choice_translation_domain' => false, + 'choice_translation_locale' => null, + 'alpha3' => false, + 'choice_self_translation' => false, + 'invalid_message' => 'Please select a valid language.', + ]); + + $resolver->setAllowedTypes('choice_self_translation', ['bool']); + $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); + $resolver->setAllowedTypes('alpha3', 'bool'); + + $resolver->setNormalizer('choice_self_translation', static function (Options $options, $value) { + if (true === $value && $options['choice_translation_locale']) { + throw new LogicException('Cannot use the "choice_self_translation" and "choice_translation_locale" options at the same time. Remove one of them.'); + } + + return $value; + }); + } + + public function getParent(): ?string + { + return ChoiceType::class; + } + + public function getBlockPrefix(): string + { + return 'language'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/LocaleType.php b/vendor/symfony/form/Extension/Core/Type/LocaleType.php new file mode 100644 index 0000000..d0124e6 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/LocaleType.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Locales; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class LocaleType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => function (Options $options) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); + } + + $choiceTranslationLocale = $options['choice_translation_locale']; + + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip(Locales::getNames($choiceTranslationLocale))), $choiceTranslationLocale); + }, + 'choice_translation_domain' => false, + 'choice_translation_locale' => null, + 'invalid_message' => 'Please select a valid locale.', + ]); + + $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); + } + + public function getParent(): ?string + { + return ChoiceType::class; + } + + public function getBlockPrefix(): string + { + return 'locale'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/MoneyType.php b/vendor/symfony/form/Extension/Core/Type/MoneyType.php new file mode 100644 index 0000000..2657b03 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/MoneyType.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\MoneyToLocalizedStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class MoneyType extends AbstractType +{ + protected static array $patterns = []; + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Values used in HTML5 number inputs should be formatted as in "1234.5", ie. 'en' format without grouping, + // according to https://www.w3.org/TR/html51/sec-forms.html#date-time-and-number-formats + $builder + ->addViewTransformer(new MoneyToLocalizedStringTransformer( + $options['scale'], + $options['grouping'], + $options['rounding_mode'], + $options['divisor'], + $options['html5'] ? 'en' : null, + $options['input'], + )) + ; + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['money_pattern'] = self::getPattern($options['currency']); + + if ($options['html5']) { + $view->vars['type'] = 'number'; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'scale' => 2, + 'grouping' => false, + 'rounding_mode' => \NumberFormatter::ROUND_HALFUP, + 'divisor' => 1, + 'currency' => 'EUR', + 'compound' => false, + 'html5' => false, + 'invalid_message' => 'Please enter a valid money amount.', + 'input' => 'float', + ]); + + $resolver->setAllowedValues('rounding_mode', [ + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ]); + + $resolver->setAllowedTypes('scale', 'int'); + + $resolver->setAllowedTypes('html5', 'bool'); + + $resolver->setAllowedValues('input', ['float', 'integer']); + + $resolver->setNormalizer('grouping', static function (Options $options, $value) { + if ($value && $options['html5']) { + throw new LogicException('Cannot use the "grouping" option when the "html5" option is enabled.'); + } + + return $value; + }); + } + + public function getBlockPrefix(): string + { + return 'money'; + } + + /** + * Returns the pattern for this locale in UTF-8. + * + * The pattern contains the placeholder "{{ widget }}" where the HTML tag should + * be inserted + */ + protected static function getPattern(?string $currency): string + { + if (!$currency) { + return '{{ widget }}'; + } + + $locale = \Locale::getDefault(); + + if (!isset(self::$patterns[$locale])) { + self::$patterns[$locale] = []; + } + + if (!isset(self::$patterns[$locale][$currency])) { + $format = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); + $pattern = $format->formatCurrency('123', $currency); + + // the spacings between currency symbol and number are ignored, because + // a single space leads to better readability in combination with input + // fields + + // the regex also considers non-break spaces (0xC2 or 0xA0 in UTF-8) + + preg_match('/^([^\s\xc2\xa0]*)[\s\xc2\xa0]*123(?:[,.]0+)?[\s\xc2\xa0]*([^\s\xc2\xa0]*)$/u', $pattern, $matches); + + if (!empty($matches[1])) { + self::$patterns[$locale][$currency] = $matches[1].' {{ widget }}'; + } elseif (!empty($matches[2])) { + self::$patterns[$locale][$currency] = '{{ widget }} '.$matches[2]; + } else { + self::$patterns[$locale][$currency] = '{{ widget }}'; + } + } + + return self::$patterns[$locale][$currency]; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/NumberType.php b/vendor/symfony/form/Extension/Core/Type/NumberType.php new file mode 100644 index 0000000..ce516a3 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/NumberType.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\StringToFloatTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class NumberType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addViewTransformer(new NumberToLocalizedStringTransformer( + $options['scale'], + $options['grouping'], + $options['rounding_mode'], + $options['html5'] ? 'en' : null + )); + + if ('string' === $options['input']) { + $builder->addModelTransformer(new StringToFloatTransformer($options['scale'])); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['html5']) { + $view->vars['type'] = 'number'; + + if (!isset($view->vars['attr']['step'])) { + $view->vars['attr']['step'] = 'any'; + } + } else { + $view->vars['attr']['inputmode'] = 0 === $options['scale'] ? 'numeric' : 'decimal'; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + // default scale is locale specific (usually around 3) + 'scale' => null, + 'grouping' => false, + 'rounding_mode' => \NumberFormatter::ROUND_HALFUP, + 'compound' => false, + 'input' => 'number', + 'html5' => false, + 'invalid_message' => 'Please enter a number.', + ]); + + $resolver->setAllowedValues('rounding_mode', [ + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ]); + $resolver->setAllowedValues('input', ['number', 'string']); + $resolver->setAllowedTypes('scale', ['null', 'int']); + $resolver->setAllowedTypes('html5', 'bool'); + + $resolver->setNormalizer('grouping', static function (Options $options, $value) { + if (true === $value && $options['html5']) { + throw new LogicException('Cannot use the "grouping" option when the "html5" option is enabled.'); + } + + return $value; + }); + } + + public function getBlockPrefix(): string + { + return 'number'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/PasswordType.php b/vendor/symfony/form/Extension/Core/Type/PasswordType.php new file mode 100644 index 0000000..72cc8ec --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/PasswordType.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PasswordType extends AbstractType +{ + public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['always_empty'] || !$form->isSubmitted()) { + $view->vars['value'] = ''; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'always_empty' => true, + 'trim' => false, + 'invalid_message' => 'The password is invalid.', + ]); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'password'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/PercentType.php b/vendor/symfony/form/Extension/Core/Type/PercentType.php new file mode 100644 index 0000000..fae102c --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/PercentType.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\DataTransformer\PercentToLocalizedStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PercentType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addViewTransformer(new PercentToLocalizedStringTransformer( + $options['scale'], + $options['type'], + $options['rounding_mode'], + $options['html5'] + )); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['symbol'] = $options['symbol']; + + if ($options['html5']) { + $view->vars['type'] = 'number'; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'scale' => 0, + 'rounding_mode' => \NumberFormatter::ROUND_HALFUP, + 'symbol' => '%', + 'type' => 'fractional', + 'compound' => false, + 'html5' => false, + 'invalid_message' => 'Please enter a percentage value.', + ]); + + $resolver->setAllowedValues('type', [ + 'fractional', + 'integer', + ]); + $resolver->setAllowedValues('rounding_mode', [ + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ]); + $resolver->setAllowedTypes('scale', 'int'); + $resolver->setAllowedTypes('symbol', ['bool', 'string']); + $resolver->setAllowedTypes('html5', 'bool'); + } + + public function getBlockPrefix(): string + { + return 'percent'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/RadioType.php b/vendor/symfony/form/Extension/Core/Type/RadioType.php new file mode 100644 index 0000000..ac72a20 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/RadioType.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class RadioType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'invalid_message' => 'Please select a valid option.', + ]); + } + + public function getParent(): ?string + { + return CheckboxType::class; + } + + public function getBlockPrefix(): string + { + return 'radio'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/RangeType.php b/vendor/symfony/form/Extension/Core/Type/RangeType.php new file mode 100644 index 0000000..edb04b4 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/RangeType.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class RangeType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'invalid_message' => 'Please choose a valid range.', + ]); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'range'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/RepeatedType.php b/vendor/symfony/form/Extension/Core/Type/RepeatedType.php new file mode 100644 index 0000000..96d2c07 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/RepeatedType.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\DataTransformer\ValueToDuplicatesTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class RepeatedType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Overwrite required option for child fields + $options['first_options']['required'] = $options['required']; + $options['second_options']['required'] = $options['required']; + + if (!isset($options['options']['error_bubbling'])) { + $options['options']['error_bubbling'] = $options['error_bubbling']; + } + + // children fields must always be mapped + $defaultOptions = ['mapped' => true]; + + $builder + ->addViewTransformer(new ValueToDuplicatesTransformer([ + $options['first_name'], + $options['second_name'], + ])) + ->add($options['first_name'], $options['type'], array_merge($options['options'], $options['first_options'], $defaultOptions)) + ->add($options['second_name'], $options['type'], array_merge($options['options'], $options['second_options'], $defaultOptions)) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'type' => TextType::class, + 'options' => [], + 'first_options' => [], + 'second_options' => [], + 'first_name' => 'first', + 'second_name' => 'second', + 'error_bubbling' => false, + 'invalid_message' => 'The values do not match.', + ]); + + $resolver->setAllowedTypes('options', 'array'); + $resolver->setAllowedTypes('first_options', 'array'); + $resolver->setAllowedTypes('second_options', 'array'); + } + + public function getBlockPrefix(): string + { + return 'repeated'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/ResetType.php b/vendor/symfony/form/Extension/Core/Type/ResetType.php new file mode 100644 index 0000000..9a53a3d --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/ResetType.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ButtonTypeInterface; + +/** + * A reset button. + * + * @author Bernhard Schussek + */ +class ResetType extends AbstractType implements ButtonTypeInterface +{ + public function getParent(): ?string + { + return ButtonType::class; + } + + public function getBlockPrefix(): string + { + return 'reset'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/SearchType.php b/vendor/symfony/form/Extension/Core/Type/SearchType.php new file mode 100644 index 0000000..f69cf79 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/SearchType.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class SearchType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'invalid_message' => 'Please enter a valid search term.', + ]); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'search'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/SubmitType.php b/vendor/symfony/form/Extension/Core/Type/SubmitType.php new file mode 100644 index 0000000..5681060 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/SubmitType.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\SubmitButtonTypeInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * A submit button. + * + * @author Bernhard Schussek + */ +class SubmitType extends AbstractType implements SubmitButtonTypeInterface +{ + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['clicked'] = $form->isClicked(); + + if (!$options['validate']) { + $view->vars['attr']['formnovalidate'] = true; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefault('validate', true); + $resolver->setAllowedTypes('validate', 'bool'); + } + + public function getParent(): ?string + { + return ButtonType::class; + } + + public function getBlockPrefix(): string + { + return 'submit'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/TelType.php b/vendor/symfony/form/Extension/Core/Type/TelType.php new file mode 100644 index 0000000..29a3b5d --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/TelType.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class TelType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'invalid_message' => 'Please provide a valid phone number.', + ]); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'tel'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/TextType.php b/vendor/symfony/form/Extension/Core/Type/TextType.php new file mode 100644 index 0000000..bff23ea --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/TextType.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class TextType extends AbstractType implements DataTransformerInterface +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + // When empty_data is explicitly set to an empty string, + // a string should always be returned when NULL is submitted + // This gives more control and thus helps preventing some issues + // with PHP 7 which allows type hinting strings in functions + // See https://github.com/symfony/symfony/issues/5906#issuecomment-203189375 + if ('' === $options['empty_data']) { + $builder->addViewTransformer($this); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'compound' => false, + ]); + } + + public function getBlockPrefix(): string + { + return 'text'; + } + + public function transform(mixed $data): mixed + { + // Model data should not be transformed + return $data; + } + + public function reverseTransform(mixed $data): mixed + { + return $data ?? ''; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/TextareaType.php b/vendor/symfony/form/Extension/Core/Type/TextareaType.php new file mode 100644 index 0000000..1615964 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/TextareaType.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; + +class TextareaType extends AbstractType +{ + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['pattern'] = null; + unset($view->vars['attr']['pattern']); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'textarea'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/TimeType.php b/vendor/symfony/form/Extension/Core/Type/TimeType.php new file mode 100644 index 0000000..4e01489 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/TimeType.php @@ -0,0 +1,384 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Event\PreSubmitEvent; +use Symfony\Component\Form\Exception\InvalidConfigurationException; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToDateTimeTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ReversedTransformer; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class TimeType extends AbstractType +{ + private const WIDGETS = [ + 'text' => TextType::class, + 'choice' => ChoiceType::class, + ]; + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $parts = ['hour']; + $format = 'H'; + + if ($options['with_seconds'] && !$options['with_minutes']) { + throw new InvalidConfigurationException('You cannot disable minutes if you have enabled seconds.'); + } + + if (null !== $options['reference_date'] && $options['reference_date']->getTimezone()->getName() !== $options['model_timezone']) { + throw new InvalidConfigurationException(sprintf('The configured "model_timezone" (%s) must match the timezone of the "reference_date" (%s).', $options['model_timezone'], $options['reference_date']->getTimezone()->getName())); + } + + if ($options['with_minutes']) { + $format .= ':i'; + $parts[] = 'minute'; + } + + if ($options['with_seconds']) { + $format .= ':s'; + $parts[] = 'second'; + } + + if ('single_text' === $options['widget']) { + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $e) use ($options) { + /** @var PreSubmitEvent $event */ + $data = $e->getData(); + if ($data && preg_match('/^(?P\d{2}):(?P\d{2})(?::(?P\d{2})(?:\.\d+)?)?$/', $data, $matches)) { + if ($options['with_seconds']) { + // handle seconds ignored by user's browser when with_seconds enabled + // https://codereview.chromium.org/450533009/ + $e->setData(sprintf('%s:%s:%s', $matches['hours'], $matches['minutes'], $matches['seconds'] ?? '00')); + } else { + $e->setData(sprintf('%s:%s', $matches['hours'], $matches['minutes'])); + } + } + }); + + $parseFormat = null; + + if (null !== $options['reference_date']) { + $parseFormat = 'Y-m-d '.$format; + + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($options) { + $data = $event->getData(); + + if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $data)) { + $event->setData($options['reference_date']->format('Y-m-d ').$data); + } + }); + } + + $builder->addViewTransformer(new DateTimeToStringTransformer($options['model_timezone'], $options['view_timezone'], $format, $parseFormat)); + } else { + $hourOptions = $minuteOptions = $secondOptions = [ + 'error_bubbling' => true, + 'empty_data' => '', + ]; + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: []; + + if ($emptyData instanceof \Closure) { + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); + + return $emptyData[$option] ?? ''; + }; + + $hourOptions['empty_data'] = $lazyEmptyData('hour'); + } elseif (isset($emptyData['hour'])) { + $hourOptions['empty_data'] = $emptyData['hour']; + } + + if (isset($options['invalid_message'])) { + $hourOptions['invalid_message'] = $options['invalid_message']; + $minuteOptions['invalid_message'] = $options['invalid_message']; + $secondOptions['invalid_message'] = $options['invalid_message']; + } + + if (isset($options['invalid_message_parameters'])) { + $hourOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + $minuteOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + $secondOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + } + + if ('choice' === $options['widget']) { + $hours = $minutes = []; + + foreach ($options['hours'] as $hour) { + $hours[str_pad($hour, 2, '0', \STR_PAD_LEFT)] = $hour; + } + + // Only pass a subset of the options to children + $hourOptions['choices'] = $hours; + $hourOptions['placeholder'] = $options['placeholder']['hour']; + $hourOptions['choice_translation_domain'] = $options['choice_translation_domain']['hour']; + + if ($options['with_minutes']) { + foreach ($options['minutes'] as $minute) { + $minutes[str_pad($minute, 2, '0', \STR_PAD_LEFT)] = $minute; + } + + $minuteOptions['choices'] = $minutes; + $minuteOptions['placeholder'] = $options['placeholder']['minute']; + $minuteOptions['choice_translation_domain'] = $options['choice_translation_domain']['minute']; + } + + if ($options['with_seconds']) { + $seconds = []; + + foreach ($options['seconds'] as $second) { + $seconds[str_pad($second, 2, '0', \STR_PAD_LEFT)] = $second; + } + + $secondOptions['choices'] = $seconds; + $secondOptions['placeholder'] = $options['placeholder']['second']; + $secondOptions['choice_translation_domain'] = $options['choice_translation_domain']['second']; + } + + // Append generic carry-along options + foreach (['required', 'translation_domain'] as $passOpt) { + $hourOptions[$passOpt] = $options[$passOpt]; + + if ($options['with_minutes']) { + $minuteOptions[$passOpt] = $options[$passOpt]; + } + + if ($options['with_seconds']) { + $secondOptions[$passOpt] = $options[$passOpt]; + } + } + } + + $builder->add('hour', self::WIDGETS[$options['widget']], $hourOptions); + + if ($options['with_minutes']) { + if ($emptyData instanceof \Closure) { + $minuteOptions['empty_data'] = $lazyEmptyData('minute'); + } elseif (isset($emptyData['minute'])) { + $minuteOptions['empty_data'] = $emptyData['minute']; + } + $builder->add('minute', self::WIDGETS[$options['widget']], $minuteOptions); + } + + if ($options['with_seconds']) { + if ($emptyData instanceof \Closure) { + $secondOptions['empty_data'] = $lazyEmptyData('second'); + } elseif (isset($emptyData['second'])) { + $secondOptions['empty_data'] = $emptyData['second']; + } + $builder->add('second', self::WIDGETS[$options['widget']], $secondOptions); + } + + $builder->addViewTransformer(new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, 'text' === $options['widget'], $options['reference_date'])); + } + + if ('datetime_immutable' === $options['input']) { + $builder->addModelTransformer(new DateTimeImmutableToDateTimeTransformer()); + } elseif ('string' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], $options['input_format']) + )); + } elseif ('timestamp' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) + )); + } elseif ('array' === $options['input']) { + $builder->addModelTransformer(new ReversedTransformer( + new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts, 'text' === $options['widget'], $options['reference_date']) + )); + } + + if (\in_array($options['input'], ['datetime', 'datetime_immutable'], true) && null !== $options['model_timezone']) { + $builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) use ($options): void { + $date = $event->getData(); + + if (!$date instanceof \DateTimeInterface) { + return; + } + + if ($date->getTimezone()->getName() !== $options['model_timezone']) { + throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone'])); + } + }); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars = array_replace($view->vars, [ + 'widget' => $options['widget'], + 'with_minutes' => $options['with_minutes'], + 'with_seconds' => $options['with_seconds'], + ]); + + // Change the input to an HTML5 time input if + // * the widget is set to "single_text" + // * the html5 is set to true + if ($options['html5'] && 'single_text' === $options['widget']) { + $view->vars['type'] = 'time'; + + // we need to force the browser to display the seconds by + // adding the HTML attribute step if not already defined. + // Otherwise the browser will not display and so not send the seconds + // therefore the value will always be considered as invalid. + if (!isset($view->vars['attr']['step'])) { + if ($options['with_seconds']) { + $view->vars['attr']['step'] = 1; + } elseif (!$options['with_minutes']) { + $view->vars['attr']['step'] = 3600; + } + } + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; + + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; + + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { + if (\is_array($placeholder)) { + $default = $placeholderDefault($options); + + return array_merge( + ['hour' => $default, 'minute' => $default, 'second' => $default], + $placeholder + ); + } + + return [ + 'hour' => $placeholder, + 'minute' => $placeholder, + 'second' => $placeholder, + ]; + }; + + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { + if (\is_array($choiceTranslationDomain)) { + $default = false; + + return array_replace( + ['hour' => $default, 'minute' => $default, 'second' => $default], + $choiceTranslationDomain + ); + } + + return [ + 'hour' => $choiceTranslationDomain, + 'minute' => $choiceTranslationDomain, + 'second' => $choiceTranslationDomain, + ]; + }; + + $modelTimezone = static function (Options $options, $value): ?string { + if (null !== $value) { + return $value; + } + + if (null !== $options['reference_date']) { + return $options['reference_date']->getTimezone()->getName(); + } + + return null; + }; + + $viewTimezone = static function (Options $options, $value): ?string { + if (null !== $value) { + return $value; + } + + if (null !== $options['model_timezone'] && null === $options['reference_date']) { + return $options['model_timezone']; + } + + return null; + }; + + $resolver->setDefaults([ + 'hours' => range(0, 23), + 'minutes' => range(0, 59), + 'seconds' => range(0, 59), + 'widget' => 'single_text', + 'input' => 'datetime', + 'input_format' => 'H:i:s', + 'with_minutes' => true, + 'with_seconds' => false, + 'model_timezone' => $modelTimezone, + 'view_timezone' => $viewTimezone, + 'reference_date' => null, + 'placeholder' => $placeholderDefault, + 'html5' => true, + // Don't modify \DateTime classes by reference, we treat + // them like immutable value objects + 'by_reference' => false, + 'error_bubbling' => false, + // If initialized with a \DateTime object, FormType initializes + // this option to "\DateTime". Since the internal, normalized + // representation is not \DateTime, but an array, we need to unset + // this option. + 'data_class' => null, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', + 'compound' => $compound, + 'choice_translation_domain' => false, + 'invalid_message' => 'Please enter a valid time.', + ]); + + $resolver->setNormalizer('view_timezone', static function (Options $options, $viewTimezone): ?string { + if (null !== $options['model_timezone'] && $viewTimezone !== $options['model_timezone'] && null === $options['reference_date']) { + throw new LogicException('Using different values for the "model_timezone" and "view_timezone" options without configuring a reference date is not supported.'); + } + + return $viewTimezone; + }); + + $resolver->setNormalizer('placeholder', $placeholderNormalizer); + $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); + + $resolver->setAllowedValues('input', [ + 'datetime', + 'datetime_immutable', + 'string', + 'timestamp', + 'array', + ]); + $resolver->setAllowedValues('widget', [ + 'single_text', + 'text', + 'choice', + ]); + + $resolver->setAllowedTypes('hours', 'array'); + $resolver->setAllowedTypes('minutes', 'array'); + $resolver->setAllowedTypes('seconds', 'array'); + $resolver->setAllowedTypes('input_format', 'string'); + $resolver->setAllowedTypes('model_timezone', ['null', 'string']); + $resolver->setAllowedTypes('view_timezone', ['null', 'string']); + $resolver->setAllowedTypes('reference_date', ['null', \DateTimeInterface::class]); + } + + public function getBlockPrefix(): string + { + return 'time'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/TimezoneType.php b/vendor/symfony/form/Extension/Core/Type/TimezoneType.php new file mode 100644 index 0000000..01ce68c --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/TimezoneType.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Timezones; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class TimezoneType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ('datetimezone' === $options['input']) { + $builder->addModelTransformer(new DateTimeZoneToStringTransformer($options['multiple'])); + } elseif ('intltimezone' === $options['input']) { + $builder->addModelTransformer(new IntlTimeZoneToStringTransformer($options['multiple'])); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'intl' => false, + 'choice_loader' => function (Options $options) { + $input = $options['input']; + + if ($options['intl']) { + if (!class_exists(Intl::class)) { + throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s" with option "intl=true". Try running "composer require symfony/intl".', static::class)); + } + + $choiceTranslationLocale = $options['choice_translation_locale']; + + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => self::getIntlTimezones($input, $choiceTranslationLocale)), [$input, $choiceTranslationLocale]); + } + + return ChoiceList::lazy($this, static fn () => self::getPhpTimezones($input), $input); + }, + 'choice_translation_domain' => false, + 'choice_translation_locale' => null, + 'input' => 'string', + 'invalid_message' => 'Please select a valid timezone.', + 'regions' => \DateTimeZone::ALL, + ]); + + $resolver->setAllowedTypes('intl', ['bool']); + + $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); + $resolver->setNormalizer('choice_translation_locale', static function (Options $options, $value) { + if (null !== $value && !$options['intl']) { + throw new LogicException('The "choice_translation_locale" option can only be used if the "intl" option is set to true.'); + } + + return $value; + }); + + $resolver->setAllowedValues('input', ['string', 'datetimezone', 'intltimezone']); + $resolver->setNormalizer('input', static function (Options $options, $value) { + if ('intltimezone' === $value && !class_exists(\IntlTimeZone::class)) { + throw new LogicException('Cannot use "intltimezone" input because the PHP intl extension is not available.'); + } + + return $value; + }); + } + + public function getParent(): ?string + { + return ChoiceType::class; + } + + public function getBlockPrefix(): string + { + return 'timezone'; + } + + private static function getPhpTimezones(string $input): array + { + $timezones = []; + + foreach (\DateTimeZone::listIdentifiers(\DateTimeZone::ALL) as $timezone) { + if ('intltimezone' === $input && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($timezone)->getID()) { + continue; + } + + $timezones[str_replace(['/', '_'], [' / ', ' '], $timezone)] = $timezone; + } + + return $timezones; + } + + private static function getIntlTimezones(string $input, ?string $locale = null): array + { + $timezones = array_flip(Timezones::getNames($locale)); + + if ('intltimezone' === $input) { + foreach ($timezones as $name => $timezone) { + if ('Etc/Unknown' === \IntlTimeZone::createTimeZone($timezone)->getID()) { + unset($timezones[$name]); + } + } + } + + return $timezones; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/TransformationFailureExtension.php b/vendor/symfony/form/Extension/Core/Type/TransformationFailureExtension.php new file mode 100644 index 0000000..e90cd71 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/TransformationFailureExtension.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\EventListener\TransformationFailureListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Christian Flothmann + */ +class TransformationFailureExtension extends AbstractTypeExtension +{ + public function __construct( + private ?TranslatorInterface $translator = null, + ) { + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!isset($options['constraints'])) { + $builder->addEventSubscriber(new TransformationFailureListener($this->translator)); + } + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/UlidType.php b/vendor/symfony/form/Extension/Core/Type/UlidType.php new file mode 100644 index 0000000..a17b9d9 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/UlidType.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\DataTransformer\UlidToStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Pavel Dyakonov + */ +class UlidType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->addViewTransformer(new UlidToStringTransformer()) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'compound' => false, + 'invalid_message' => 'Please enter a valid ULID.', + ]); + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/UrlType.php b/vendor/symfony/form/Extension/Core/Type/UrlType.php new file mode 100644 index 0000000..fd60257 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/UrlType.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\EventListener\FixUrlProtocolListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class UrlType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (null !== $options['default_protocol']) { + $builder->addEventSubscriber(new FixUrlProtocolListener($options['default_protocol'])); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['default_protocol']) { + $view->vars['attr']['inputmode'] = 'url'; + $view->vars['type'] = 'text'; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'default_protocol' => static function (Options $options) { + trigger_deprecation('symfony/form', '7.1', 'Not configuring the "default_protocol" option when using the UrlType is deprecated. It will default to "null" in 8.0.'); + + return 'http'; + }, + 'invalid_message' => 'Please enter a valid URL.', + ]); + + $resolver->setAllowedTypes('default_protocol', ['null', 'string']); + } + + public function getParent(): ?string + { + return TextType::class; + } + + public function getBlockPrefix(): string + { + return 'url'; + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/UuidType.php b/vendor/symfony/form/Extension/Core/Type/UuidType.php new file mode 100644 index 0000000..1035939 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/UuidType.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\DataTransformer\UuidToStringTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Pavel Dyakonov + */ +class UuidType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->addViewTransformer(new UuidToStringTransformer()) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'compound' => false, + 'invalid_message' => 'Please enter a valid UUID.', + ]); + } +} diff --git a/vendor/symfony/form/Extension/Core/Type/WeekType.php b/vendor/symfony/form/Extension/Core/Type/WeekType.php new file mode 100644 index 0000000..c3ffae0 --- /dev/null +++ b/vendor/symfony/form/Extension/Core/Type/WeekType.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\WeekToArrayTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ReversedTransformer; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class WeekType extends AbstractType +{ + private const WIDGETS = [ + 'text' => IntegerType::class, + 'choice' => ChoiceType::class, + ]; + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ('string' === $options['input']) { + $builder->addModelTransformer(new WeekToArrayTransformer()); + } + + if ('single_text' === $options['widget']) { + $builder->addViewTransformer(new ReversedTransformer(new WeekToArrayTransformer())); + } else { + $yearOptions = $weekOptions = [ + 'error_bubbling' => true, + 'empty_data' => '', + ]; + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: []; + + $yearOptions['empty_data'] = $emptyData['year'] ?? ''; + $weekOptions['empty_data'] = $emptyData['week'] ?? ''; + + if (isset($options['invalid_message'])) { + $yearOptions['invalid_message'] = $options['invalid_message']; + $weekOptions['invalid_message'] = $options['invalid_message']; + } + + if (isset($options['invalid_message_parameters'])) { + $yearOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + $weekOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + } + + if ('choice' === $options['widget']) { + // Only pass a subset of the options to children + $yearOptions['choices'] = array_combine($options['years'], $options['years']); + $yearOptions['placeholder'] = $options['placeholder']['year']; + $yearOptions['choice_translation_domain'] = $options['choice_translation_domain']['year']; + + $weekOptions['choices'] = array_combine($options['weeks'], $options['weeks']); + $weekOptions['placeholder'] = $options['placeholder']['week']; + $weekOptions['choice_translation_domain'] = $options['choice_translation_domain']['week']; + + // Append generic carry-along options + foreach (['required', 'translation_domain'] as $passOpt) { + $yearOptions[$passOpt] = $options[$passOpt]; + $weekOptions[$passOpt] = $options[$passOpt]; + } + } + + $builder->add('year', self::WIDGETS[$options['widget']], $yearOptions); + $builder->add('week', self::WIDGETS[$options['widget']], $weekOptions); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['widget'] = $options['widget']; + + if ($options['html5']) { + $view->vars['type'] = 'week'; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; + + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; + + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { + if (\is_array($placeholder)) { + $default = $placeholderDefault($options); + + return array_merge( + ['year' => $default, 'week' => $default], + $placeholder + ); + } + + return [ + 'year' => $placeholder, + 'week' => $placeholder, + ]; + }; + + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { + if (\is_array($choiceTranslationDomain)) { + $default = false; + + return array_replace( + ['year' => $default, 'week' => $default], + $choiceTranslationDomain + ); + } + + return [ + 'year' => $choiceTranslationDomain, + 'week' => $choiceTranslationDomain, + ]; + }; + + $resolver->setDefaults([ + 'years' => range(date('Y') - 10, date('Y') + 10), + 'weeks' => array_combine(range(1, 53), range(1, 53)), + 'widget' => 'single_text', + 'input' => 'array', + 'placeholder' => $placeholderDefault, + 'html5' => static fn (Options $options) => 'single_text' === $options['widget'], + 'error_bubbling' => false, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', + 'compound' => $compound, + 'choice_translation_domain' => false, + 'invalid_message' => 'Please enter a valid week.', + ]); + + $resolver->setNormalizer('placeholder', $placeholderNormalizer); + $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); + $resolver->setNormalizer('html5', static function (Options $options, $html5) { + if ($html5 && 'single_text' !== $options['widget']) { + throw new LogicException(sprintf('The "widget" option of "%s" must be set to "single_text" when the "html5" option is enabled.', self::class)); + } + + return $html5; + }); + + $resolver->setAllowedValues('input', [ + 'string', + 'array', + ]); + + $resolver->setAllowedValues('widget', [ + 'single_text', + 'text', + 'choice', + ]); + + $resolver->setAllowedTypes('years', 'int[]'); + $resolver->setAllowedTypes('weeks', 'int[]'); + } + + public function getBlockPrefix(): string + { + return 'week'; + } +} diff --git a/vendor/symfony/form/Extension/Csrf/CsrfExtension.php b/vendor/symfony/form/Extension/Csrf/CsrfExtension.php new file mode 100644 index 0000000..33c4616 --- /dev/null +++ b/vendor/symfony/form/Extension/Csrf/CsrfExtension.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Csrf; + +use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * This extension protects forms by using a CSRF token. + * + * @author Bernhard Schussek + */ +class CsrfExtension extends AbstractExtension +{ + public function __construct( + private CsrfTokenManagerInterface $tokenManager, + private ?TranslatorInterface $translator = null, + private ?string $translationDomain = null, + ) { + } + + protected function loadTypeExtensions(): array + { + return [ + new Type\FormTypeCsrfExtension($this->tokenManager, true, '_token', $this->translator, $this->translationDomain), + ]; + } +} diff --git a/vendor/symfony/form/Extension/Csrf/EventListener/CsrfValidationListener.php b/vendor/symfony/form/Extension/Csrf/EventListener/CsrfValidationListener.php new file mode 100644 index 0000000..4492240 --- /dev/null +++ b/vendor/symfony/form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Csrf\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\Util\ServerParams; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Bernhard Schussek + */ +class CsrfValidationListener implements EventSubscriberInterface +{ + private ServerParams $serverParams; + + public static function getSubscribedEvents(): array + { + return [ + FormEvents::PRE_SUBMIT => 'preSubmit', + ]; + } + + public function __construct( + private string $fieldName, + private CsrfTokenManagerInterface $tokenManager, + private string $tokenId, + private string $errorMessage, + private ?TranslatorInterface $translator = null, + private ?string $translationDomain = null, + ?ServerParams $serverParams = null, + ) { + $this->serverParams = $serverParams ?? new ServerParams(); + } + + public function preSubmit(FormEvent $event): void + { + $form = $event->getForm(); + $postRequestSizeExceeded = 'POST' === $form->getConfig()->getMethod() && $this->serverParams->hasPostMaxSizeBeenExceeded(); + + if ($form->isRoot() && $form->getConfig()->getOption('compound') && !$postRequestSizeExceeded) { + $data = $event->getData(); + + $csrfValue = \is_string($data[$this->fieldName] ?? null) ? $data[$this->fieldName] : null; + $csrfToken = new CsrfToken($this->tokenId, $csrfValue); + + if (null === $csrfValue || !$this->tokenManager->isTokenValid($csrfToken)) { + $errorMessage = $this->errorMessage; + + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($errorMessage, [], $this->translationDomain); + } + + $form->addError(new FormError($errorMessage, $errorMessage, [], null, $csrfToken)); + } + + if (\is_array($data)) { + unset($data[$this->fieldName]); + $event->setData($data); + } + } + } +} diff --git a/vendor/symfony/form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/vendor/symfony/form/Extension/Csrf/Type/FormTypeCsrfExtension.php new file mode 100644 index 0000000..0ad4dae --- /dev/null +++ b/vendor/symfony/form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Csrf\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Util\ServerParams; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Bernhard Schussek + */ +class FormTypeCsrfExtension extends AbstractTypeExtension +{ + public function __construct( + private CsrfTokenManagerInterface $defaultTokenManager, + private bool $defaultEnabled = true, + private string $defaultFieldName = '_token', + private ?TranslatorInterface $translator = null, + private ?string $translationDomain = null, + private ?ServerParams $serverParams = null, + ) { + } + + /** + * Adds a CSRF field to the form when the CSRF protection is enabled. + */ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['csrf_protection']) { + return; + } + + $builder + ->addEventSubscriber(new CsrfValidationListener( + $options['csrf_field_name'], + $options['csrf_token_manager'], + $options['csrf_token_id'] ?: ($builder->getName() ?: $builder->getType()->getInnerType()::class), + $options['csrf_message'], + $this->translator, + $this->translationDomain, + $this->serverParams + )) + ; + } + + /** + * Adds a CSRF field to the root form view. + */ + public function finishView(FormView $view, FormInterface $form, array $options): void + { + if ($options['csrf_protection'] && !$view->parent && $options['compound']) { + $factory = $form->getConfig()->getFormFactory(); + $tokenId = $options['csrf_token_id'] ?: ($form->getName() ?: $form->getConfig()->getType()->getInnerType()::class); + $data = (string) $options['csrf_token_manager']->getToken($tokenId); + + $csrfForm = $factory->createNamed($options['csrf_field_name'], HiddenType::class, $data, [ + 'block_prefix' => 'csrf_token', + 'mapped' => false, + ]); + + $view->children[$options['csrf_field_name']] = $csrfForm->createView($view); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'csrf_protection' => $this->defaultEnabled, + 'csrf_field_name' => $this->defaultFieldName, + 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', + 'csrf_token_manager' => $this->defaultTokenManager, + 'csrf_token_id' => null, + ]); + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } +} diff --git a/vendor/symfony/form/Extension/DataCollector/DataCollectorExtension.php b/vendor/symfony/form/Extension/DataCollector/DataCollectorExtension.php new file mode 100644 index 0000000..9fb8422 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/DataCollectorExtension.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector; + +use Symfony\Component\Form\AbstractExtension; + +/** + * Extension for collecting data of the forms on a page. + * + * @author Robert Schönthal + * @author Bernhard Schussek + */ +class DataCollectorExtension extends AbstractExtension +{ + public function __construct( + private FormDataCollectorInterface $dataCollector, + ) { + } + + protected function loadTypeExtensions(): array + { + return [ + new Type\DataCollectorTypeExtension($this->dataCollector), + ]; + } +} diff --git a/vendor/symfony/form/Extension/DataCollector/EventListener/DataCollectorListener.php b/vendor/symfony/form/Extension/DataCollector/EventListener/DataCollectorListener.php new file mode 100644 index 0000000..02cffbe --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/EventListener/DataCollectorListener.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; + +/** + * Listener that invokes a data collector for the {@link FormEvents::POST_SET_DATA} + * and {@link FormEvents::POST_SUBMIT} events. + * + * @author Bernhard Schussek + */ +class DataCollectorListener implements EventSubscriberInterface +{ + public function __construct( + private FormDataCollectorInterface $dataCollector, + ) { + } + + public static function getSubscribedEvents(): array + { + return [ + // High priority in order to be called as soon as possible + FormEvents::POST_SET_DATA => ['postSetData', 255], + // Low priority in order to be called as late as possible + FormEvents::POST_SUBMIT => ['postSubmit', -255], + ]; + } + + /** + * Listener for the {@link FormEvents::POST_SET_DATA} event. + */ + public function postSetData(FormEvent $event): void + { + if ($event->getForm()->isRoot()) { + // Collect basic information about each form + $this->dataCollector->collectConfiguration($event->getForm()); + + // Collect the default data + $this->dataCollector->collectDefaultData($event->getForm()); + } + } + + /** + * Listener for the {@link FormEvents::POST_SUBMIT} event. + */ + public function postSubmit(FormEvent $event): void + { + if ($event->getForm()->isRoot()) { + // Collect the submitted data of each form + $this->dataCollector->collectSubmittedData($event->getForm()); + + // Assemble a form tree + // This is done again after the view is built, but we need it here as the view is not always created. + $this->dataCollector->buildPreliminaryFormTree($event->getForm()); + } + } +} diff --git a/vendor/symfony/form/Extension/DataCollector/FormDataCollector.php b/vendor/symfony/form/Extension/DataCollector/FormDataCollector.php new file mode 100644 index 0000000..348be44 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/FormDataCollector.php @@ -0,0 +1,299 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Caster\StubCaster; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Data collector for {@link FormInterface} instances. + * + * @author Robert Schönthal + * @author Bernhard Schussek + * + * @final + */ +class FormDataCollector extends DataCollector implements FormDataCollectorInterface +{ + /** + * Stores the collected data per {@link FormInterface} instance. + * + * Uses the hashes of the forms as keys. This is preferable over using + * {@link \SplObjectStorage}, because in this way no references are kept + * to the {@link FormInterface} instances. + */ + private array $dataByForm; + + /** + * Stores the collected data per {@link FormView} instance. + * + * Uses the hashes of the views as keys. This is preferable over using + * {@link \SplObjectStorage}, because in this way no references are kept + * to the {@link FormView} instances. + */ + private array $dataByView; + + /** + * Connects {@link FormView} with {@link FormInterface} instances. + * + * Uses the hashes of the views as keys and the hashes of the forms as + * values. This is preferable over storing the objects directly, because + * this way they can safely be discarded by the GC. + */ + private array $formsByView; + + public function __construct( + private FormDataExtractorInterface $dataExtractor, + ) { + if (!class_exists(ClassStub::class)) { + throw new \LogicException(sprintf('The VarDumper component is needed for using the "%s" class. Install symfony/var-dumper version 3.4 or above.', __CLASS__)); + } + + $this->reset(); + } + + /** + * Does nothing. The data is collected during the form event listeners. + */ + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + } + + public function reset(): void + { + $this->data = [ + 'forms' => [], + 'forms_by_hash' => [], + 'nb_errors' => 0, + ]; + } + + public function associateFormWithView(FormInterface $form, FormView $view): void + { + $this->formsByView[spl_object_hash($view)] = spl_object_hash($form); + } + + public function collectConfiguration(FormInterface $form): void + { + $hash = spl_object_hash($form); + + if (!isset($this->dataByForm[$hash])) { + $this->dataByForm[$hash] = []; + } + + $this->dataByForm[$hash] = array_replace( + $this->dataByForm[$hash], + $this->dataExtractor->extractConfiguration($form) + ); + + foreach ($form as $child) { + $this->collectConfiguration($child); + } + } + + public function collectDefaultData(FormInterface $form): void + { + $hash = spl_object_hash($form); + + if (!isset($this->dataByForm[$hash])) { + // field was created by form event + $this->collectConfiguration($form); + } + + $this->dataByForm[$hash] = array_replace( + $this->dataByForm[$hash], + $this->dataExtractor->extractDefaultData($form) + ); + + foreach ($form as $child) { + $this->collectDefaultData($child); + } + } + + public function collectSubmittedData(FormInterface $form): void + { + $hash = spl_object_hash($form); + + if (!isset($this->dataByForm[$hash])) { + // field was created by form event + $this->collectConfiguration($form); + $this->collectDefaultData($form); + } + + $this->dataByForm[$hash] = array_replace( + $this->dataByForm[$hash], + $this->dataExtractor->extractSubmittedData($form) + ); + + // Count errors + if (isset($this->dataByForm[$hash]['errors'])) { + $this->data['nb_errors'] += \count($this->dataByForm[$hash]['errors']); + } + + foreach ($form as $child) { + $this->collectSubmittedData($child); + + // Expand current form if there are children with errors + if (empty($this->dataByForm[$hash]['has_children_error'])) { + $childData = $this->dataByForm[spl_object_hash($child)]; + $this->dataByForm[$hash]['has_children_error'] = !empty($childData['has_children_error']) || !empty($childData['errors']); + } + } + } + + public function collectViewVariables(FormView $view): void + { + $hash = spl_object_hash($view); + + if (!isset($this->dataByView[$hash])) { + $this->dataByView[$hash] = []; + } + + $this->dataByView[$hash] = array_replace( + $this->dataByView[$hash], + $this->dataExtractor->extractViewVariables($view) + ); + + foreach ($view->children as $child) { + $this->collectViewVariables($child); + } + } + + public function buildPreliminaryFormTree(FormInterface $form): void + { + $this->data['forms'][$form->getName()] = &$this->recursiveBuildPreliminaryFormTree($form, $this->data['forms_by_hash']); + } + + public function buildFinalFormTree(FormInterface $form, FormView $view): void + { + $this->data['forms'][$form->getName()] = &$this->recursiveBuildFinalFormTree($form, $view, $this->data['forms_by_hash']); + } + + public function getName(): string + { + return 'form'; + } + + public function getData(): array|Data + { + return $this->data; + } + + /** + * @internal + */ + public function __sleep(): array + { + foreach ($this->data['forms_by_hash'] as &$form) { + if (isset($form['type_class']) && !$form['type_class'] instanceof ClassStub) { + $form['type_class'] = new ClassStub($form['type_class']); + } + } + + $this->data = $this->cloneVar($this->data); + + return parent::__sleep(); + } + + protected function getCasters(): array + { + return parent::getCasters() + [ + \Exception::class => static function (\Exception $e, array $a, Stub $s) { + foreach (["\0Exception\0previous", "\0Exception\0trace"] as $k) { + if (isset($a[$k])) { + unset($a[$k]); + ++$s->cut; + } + } + + return $a; + }, + FormInterface::class => static fn (FormInterface $f, array $a) => [ + Caster::PREFIX_VIRTUAL.'name' => $f->getName(), + Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub($f->getConfig()->getType()->getInnerType()::class), + ], + FormView::class => StubCaster::cutInternals(...), + ConstraintViolationInterface::class => static fn (ConstraintViolationInterface $v, array $a) => [ + Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(), + Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(), + Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(), + ], + ]; + } + + private function &recursiveBuildPreliminaryFormTree(FormInterface $form, array &$outputByHash): array + { + $hash = spl_object_hash($form); + + $output = &$outputByHash[$hash]; + $output = $this->dataByForm[$hash] + ?? []; + + $output['children'] = []; + + foreach ($form as $name => $child) { + $output['children'][$name] = &$this->recursiveBuildPreliminaryFormTree($child, $outputByHash); + } + + return $output; + } + + private function &recursiveBuildFinalFormTree(?FormInterface $form, FormView $view, array &$outputByHash): array + { + $viewHash = spl_object_hash($view); + $formHash = null; + + if (null !== $form) { + $formHash = spl_object_hash($form); + } elseif (isset($this->formsByView[$viewHash])) { + // The FormInterface instance of the CSRF token is never contained in + // the FormInterface tree of the form, so we need to get the + // corresponding FormInterface instance for its view in a different way + $formHash = $this->formsByView[$viewHash]; + } + if (null !== $formHash) { + $output = &$outputByHash[$formHash]; + } + + $output = $this->dataByView[$viewHash] + ?? []; + + if (null !== $formHash) { + $output = array_replace( + $output, + $this->dataByForm[$formHash] + ?? [] + ); + } + + $output['children'] = []; + + foreach ($view->children as $name => $childView) { + // The CSRF token, for example, is never added to the form tree. + // It is only present in the view. + $childForm = $form?->has($name) ? $form->get($name) : null; + + $output['children'][$name] = &$this->recursiveBuildFinalFormTree($childForm, $childView, $outputByHash); + } + + return $output; + } +} diff --git a/vendor/symfony/form/Extension/DataCollector/FormDataCollectorInterface.php b/vendor/symfony/form/Extension/DataCollector/FormDataCollectorInterface.php new file mode 100644 index 0000000..7c79039 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/FormDataCollectorInterface.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * Collects and structures information about forms. + * + * @author Bernhard Schussek + */ +interface FormDataCollectorInterface extends DataCollectorInterface +{ + /** + * Stores configuration data of the given form and its children. + */ + public function collectConfiguration(FormInterface $form): void; + + /** + * Stores the default data of the given form and its children. + */ + public function collectDefaultData(FormInterface $form): void; + + /** + * Stores the submitted data of the given form and its children. + */ + public function collectSubmittedData(FormInterface $form): void; + + /** + * Stores the view variables of the given form view and its children. + */ + public function collectViewVariables(FormView $view): void; + + /** + * Specifies that the given objects represent the same conceptual form. + */ + public function associateFormWithView(FormInterface $form, FormView $view): void; + + /** + * Assembles the data collected about the given form and its children as + * a tree-like data structure. + * + * The result can be queried using {@link getData()}. + */ + public function buildPreliminaryFormTree(FormInterface $form): void; + + /** + * Assembles the data collected about the given form and its children as + * a tree-like data structure. + * + * The result can be queried using {@link getData()}. + * + * Contrary to {@link buildPreliminaryFormTree()}, a {@link FormView} + * object has to be passed. The tree structure of this view object will be + * used for structuring the resulting data. That means, if a child is + * present in the view, but not in the form, it will be present in the final + * data array anyway. + * + * When {@link FormView} instances are present in the view tree, for which + * no corresponding {@link FormInterface} objects can be found in the form + * tree, only the view data will be included in the result. If a + * corresponding {@link FormInterface} exists otherwise, call + * {@link associateFormWithView()} before calling this method. + */ + public function buildFinalFormTree(FormInterface $form, FormView $view): void; + + /** + * Returns all collected data. + */ + public function getData(): array|Data; +} diff --git a/vendor/symfony/form/Extension/DataCollector/FormDataExtractor.php b/vendor/symfony/form/Extension/DataCollector/FormDataExtractor.php new file mode 100644 index 0000000..158cf32 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/FormDataExtractor.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Validator\ConstraintViolationInterface; + +/** + * Default implementation of {@link FormDataExtractorInterface}. + * + * @author Bernhard Schussek + */ +class FormDataExtractor implements FormDataExtractorInterface +{ + public function extractConfiguration(FormInterface $form): array + { + $data = [ + 'id' => $this->buildId($form), + 'name' => $form->getName(), + 'type_class' => $form->getConfig()->getType()->getInnerType()::class, + 'synchronized' => $form->isSynchronized(), + 'passed_options' => [], + 'resolved_options' => [], + ]; + + foreach ($form->getConfig()->getAttribute('data_collector/passed_options', []) as $option => $value) { + $data['passed_options'][$option] = $value; + } + + foreach ($form->getConfig()->getOptions() as $option => $value) { + $data['resolved_options'][$option] = $value; + } + + ksort($data['passed_options']); + ksort($data['resolved_options']); + + return $data; + } + + public function extractDefaultData(FormInterface $form): array + { + $data = [ + 'default_data' => [ + 'norm' => $form->getNormData(), + ], + 'submitted_data' => [], + ]; + + if ($form->getData() !== $form->getNormData()) { + $data['default_data']['model'] = $form->getData(); + } + + if ($form->getViewData() !== $form->getNormData()) { + $data['default_data']['view'] = $form->getViewData(); + } + + return $data; + } + + public function extractSubmittedData(FormInterface $form): array + { + $data = [ + 'submitted_data' => [ + 'norm' => $form->getNormData(), + ], + 'errors' => [], + ]; + + if ($form->getViewData() !== $form->getNormData()) { + $data['submitted_data']['view'] = $form->getViewData(); + } + + if ($form->getData() !== $form->getNormData()) { + $data['submitted_data']['model'] = $form->getData(); + } + + foreach ($form->getErrors() as $error) { + $errorData = [ + 'message' => $error->getMessage(), + 'origin' => \is_object($error->getOrigin()) + ? spl_object_hash($error->getOrigin()) + : null, + 'trace' => [], + ]; + + $cause = $error->getCause(); + + while (null !== $cause) { + if ($cause instanceof ConstraintViolationInterface) { + $errorData['trace'][] = $cause; + $cause = method_exists($cause, 'getCause') ? $cause->getCause() : null; + + continue; + } + + if ($cause instanceof \Exception) { + $errorData['trace'][] = $cause; + $cause = $cause->getPrevious(); + + continue; + } + + $errorData['trace'][] = $cause; + + break; + } + + $data['errors'][] = $errorData; + } + + $data['synchronized'] = $form->isSynchronized(); + + return $data; + } + + public function extractViewVariables(FormView $view): array + { + $data = [ + 'id' => $view->vars['id'] ?? null, + 'name' => $view->vars['name'] ?? null, + 'view_vars' => [], + ]; + + foreach ($view->vars as $varName => $value) { + $data['view_vars'][$varName] = $value; + } + + ksort($data['view_vars']); + + return $data; + } + + /** + * Recursively builds an HTML ID for a form. + */ + private function buildId(FormInterface $form): string + { + $id = $form->getName(); + + if (null !== $form->getParent()) { + $id = $this->buildId($form->getParent()).'_'.$id; + } + + return $id; + } +} diff --git a/vendor/symfony/form/Extension/DataCollector/FormDataExtractorInterface.php b/vendor/symfony/form/Extension/DataCollector/FormDataExtractorInterface.php new file mode 100644 index 0000000..d6e46d4 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/FormDataExtractorInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; + +/** + * Extracts arrays of information out of forms. + * + * @author Bernhard Schussek + */ +interface FormDataExtractorInterface +{ + /** + * Extracts the configuration data of a form. + */ + public function extractConfiguration(FormInterface $form): array; + + /** + * Extracts the default data of a form. + */ + public function extractDefaultData(FormInterface $form): array; + + /** + * Extracts the submitted data of a form. + */ + public function extractSubmittedData(FormInterface $form): array; + + /** + * Extracts the view variables of a form. + */ + public function extractViewVariables(FormView $view): array; +} diff --git a/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php b/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php new file mode 100644 index 0000000..90e28a6 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector\Proxy; + +use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormTypeInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ResolvedFormTypeInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Proxy that invokes a data collector when creating a form and its view. + * + * @author Bernhard Schussek + */ +class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface +{ + public function __construct( + private ResolvedFormTypeInterface $proxiedType, + private FormDataCollectorInterface $dataCollector, + ) { + } + + public function getBlockPrefix(): string + { + return $this->proxiedType->getBlockPrefix(); + } + + public function getParent(): ?ResolvedFormTypeInterface + { + return $this->proxiedType->getParent(); + } + + public function getInnerType(): FormTypeInterface + { + return $this->proxiedType->getInnerType(); + } + + public function getTypeExtensions(): array + { + return $this->proxiedType->getTypeExtensions(); + } + + public function createBuilder(FormFactoryInterface $factory, string $name, array $options = []): FormBuilderInterface + { + $builder = $this->proxiedType->createBuilder($factory, $name, $options); + + $builder->setAttribute('data_collector/passed_options', $options); + $builder->setType($this); + + return $builder; + } + + public function createView(FormInterface $form, ?FormView $parent = null): FormView + { + return $this->proxiedType->createView($form, $parent); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $this->proxiedType->buildForm($builder, $options); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $this->proxiedType->buildView($view, $form, $options); + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $this->proxiedType->finishView($view, $form, $options); + + // Remember which view belongs to which form instance, so that we can + // get the collected data for a view when its form instance is not + // available (e.g. CSRF token) + $this->dataCollector->associateFormWithView($form, $view); + + // Since the CSRF token is only present in the FormView tree, we also + // need to check the FormView tree instead of calling isRoot() on the + // FormInterface tree + if (null === $view->parent) { + $this->dataCollector->collectViewVariables($view); + + // Re-assemble data, in case FormView instances were added, for + // which no FormInterface instances were present (e.g. CSRF token). + // Since finishView() is called after finishing the views of all + // children, we can safely assume that information has been + // collected about the complete form tree. + $this->dataCollector->buildFinalFormTree($form, $view); + } + } + + public function getOptionsResolver(): OptionsResolver + { + return $this->proxiedType->getOptionsResolver(); + } +} diff --git a/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php b/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php new file mode 100644 index 0000000..a052a17 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector\Proxy; + +use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; +use Symfony\Component\Form\FormTypeInterface; +use Symfony\Component\Form\ResolvedFormTypeFactoryInterface; +use Symfony\Component\Form\ResolvedFormTypeInterface; + +/** + * Proxy that wraps resolved types into {@link ResolvedTypeDataCollectorProxy} + * instances. + * + * @author Bernhard Schussek + */ +class ResolvedTypeFactoryDataCollectorProxy implements ResolvedFormTypeFactoryInterface +{ + public function __construct( + private ResolvedFormTypeFactoryInterface $proxiedFactory, + private FormDataCollectorInterface $dataCollector, + ) { + } + + public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface + { + return new ResolvedTypeDataCollectorProxy( + $this->proxiedFactory->createResolvedType($type, $typeExtensions, $parent), + $this->dataCollector + ); + } +} diff --git a/vendor/symfony/form/Extension/DataCollector/Type/DataCollectorTypeExtension.php b/vendor/symfony/form/Extension/DataCollector/Type/DataCollectorTypeExtension.php new file mode 100644 index 0000000..0f40968 --- /dev/null +++ b/vendor/symfony/form/Extension/DataCollector/Type/DataCollectorTypeExtension.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener; +use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; +use Symfony\Component\Form\FormBuilderInterface; + +/** + * Type extension for collecting data of a form with this type. + * + * @author Robert Schönthal + * @author Bernhard Schussek + */ +class DataCollectorTypeExtension extends AbstractTypeExtension +{ + private DataCollectorListener $listener; + + public function __construct(FormDataCollectorInterface $dataCollector) + { + $this->listener = new DataCollectorListener($dataCollector); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addEventSubscriber($this->listener); + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } +} diff --git a/vendor/symfony/form/Extension/DependencyInjection/DependencyInjectionExtension.php b/vendor/symfony/form/Extension/DependencyInjection/DependencyInjectionExtension.php new file mode 100644 index 0000000..420f26b --- /dev/null +++ b/vendor/symfony/form/Extension/DependencyInjection/DependencyInjectionExtension.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\FormExtensionInterface; +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeGuesserChain; +use Symfony\Component\Form\FormTypeGuesserInterface; +use Symfony\Component\Form\FormTypeInterface; + +class DependencyInjectionExtension implements FormExtensionInterface +{ + private ?FormTypeGuesserChain $guesser = null; + private bool $guesserLoaded = false; + + /** + * @param array> $typeExtensionServices + */ + public function __construct( + private ContainerInterface $typeContainer, + private array $typeExtensionServices, + private iterable $guesserServices, + ) { + } + + public function getType(string $name): FormTypeInterface + { + if (!$this->typeContainer->has($name)) { + throw new InvalidArgumentException(sprintf('The field type "%s" is not registered in the service container.', $name)); + } + + return $this->typeContainer->get($name); + } + + public function hasType(string $name): bool + { + return $this->typeContainer->has($name); + } + + public function getTypeExtensions(string $name): array + { + $extensions = []; + + if (isset($this->typeExtensionServices[$name])) { + foreach ($this->typeExtensionServices[$name] as $extension) { + $extensions[] = $extension; + + $extendedTypes = []; + foreach ($extension::getExtendedTypes() as $extendedType) { + $extendedTypes[] = $extendedType; + } + + // validate the result of getExtendedTypes() to ensure it is consistent with the service definition + if (!\in_array($name, $extendedTypes, true)) { + throw new InvalidArgumentException(sprintf('The extended type "%s" specified for the type extension class "%s" does not match any of the actual extended types (["%s"]).', $name, $extension::class, implode('", "', $extendedTypes))); + } + } + } + + return $extensions; + } + + public function hasTypeExtensions(string $name): bool + { + return isset($this->typeExtensionServices[$name]); + } + + public function getTypeGuesser(): ?FormTypeGuesserInterface + { + if (!$this->guesserLoaded) { + $this->guesserLoaded = true; + $guessers = []; + + foreach ($this->guesserServices as $serviceId => $service) { + $guessers[] = $service; + } + + if ($guessers) { + $this->guesser = new FormTypeGuesserChain($guessers); + } + } + + return $this->guesser; + } +} diff --git a/vendor/symfony/form/Extension/HtmlSanitizer/HtmlSanitizerExtension.php b/vendor/symfony/form/Extension/HtmlSanitizer/HtmlSanitizerExtension.php new file mode 100644 index 0000000..6c4bf49 --- /dev/null +++ b/vendor/symfony/form/Extension/HtmlSanitizer/HtmlSanitizerExtension.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\HtmlSanitizer; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Form\AbstractExtension; + +/** + * Integrates the HtmlSanitizer component with the Form library. + * + * @author Nicolas Grekas + */ +class HtmlSanitizerExtension extends AbstractExtension +{ + public function __construct( + private ContainerInterface $sanitizers, + private string $defaultSanitizer = 'default', + ) { + } + + protected function loadTypeExtensions(): array + { + return [ + new Type\TextTypeHtmlSanitizerExtension($this->sanitizers, $this->defaultSanitizer), + ]; + } +} diff --git a/vendor/symfony/form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php b/vendor/symfony/form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php new file mode 100644 index 0000000..8c339a0 --- /dev/null +++ b/vendor/symfony/form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\HtmlSanitizer\Type; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Titouan Galopin + */ +class TextTypeHtmlSanitizerExtension extends AbstractTypeExtension +{ + public function __construct( + private ContainerInterface $sanitizers, + private string $defaultSanitizer = 'default', + ) { + } + + public static function getExtendedTypes(): iterable + { + return [TextType::class]; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults(['sanitize_html' => false, 'sanitizer' => null]) + ->setAllowedTypes('sanitize_html', 'bool') + ->setAllowedTypes('sanitizer', ['string', 'null']) + ; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['sanitize_html']) { + return; + } + + $sanitizers = $this->sanitizers; + $sanitizer = $options['sanitizer'] ?? $this->defaultSanitizer; + + $builder->addEventListener( + FormEvents::PRE_SUBMIT, + static function (FormEvent $event) use ($sanitizers, $sanitizer) { + if (\is_scalar($data = $event->getData()) && '' !== trim($data)) { + $event->setData($sanitizers->get($sanitizer)->sanitize($data)); + } + }, + 10000 /* as soon as possible */ + ); + } +} diff --git a/vendor/symfony/form/Extension/HttpFoundation/HttpFoundationExtension.php b/vendor/symfony/form/Extension/HttpFoundation/HttpFoundationExtension.php new file mode 100644 index 0000000..85bc4f4 --- /dev/null +++ b/vendor/symfony/form/Extension/HttpFoundation/HttpFoundationExtension.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\HttpFoundation; + +use Symfony\Component\Form\AbstractExtension; + +/** + * Integrates the HttpFoundation component with the Form library. + * + * @author Bernhard Schussek + */ +class HttpFoundationExtension extends AbstractExtension +{ + protected function loadTypeExtensions(): array + { + return [ + new Type\FormTypeHttpFoundationExtension(), + ]; + } +} diff --git a/vendor/symfony/form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/vendor/symfony/form/Extension/HttpFoundation/HttpFoundationRequestHandler.php new file mode 100644 index 0000000..d7875e7 --- /dev/null +++ b/vendor/symfony/form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\HttpFoundation; + +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\RequestHandlerInterface; +use Symfony\Component\Form\Util\FormUtil; +use Symfony\Component\Form\Util\ServerParams; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; + +/** + * A request processor using the {@link Request} class of the HttpFoundation + * component. + * + * @author Bernhard Schussek + */ +class HttpFoundationRequestHandler implements RequestHandlerInterface +{ + private ServerParams $serverParams; + + public function __construct(?ServerParams $serverParams = null) + { + $this->serverParams = $serverParams ?? new ServerParams(); + } + + public function handleRequest(FormInterface $form, mixed $request = null): void + { + if (!$request instanceof Request) { + throw new UnexpectedTypeException($request, Request::class); + } + + $name = $form->getName(); + $method = $form->getConfig()->getMethod(); + + if ($method !== $request->getMethod()) { + return; + } + + // For request methods that must not have a request body we fetch data + // from the query string. Otherwise we look for data in the request body. + if ('GET' === $method || 'HEAD' === $method || 'TRACE' === $method) { + if ('' === $name) { + $data = $request->query->all(); + } else { + // Don't submit GET requests if the form's name does not exist + // in the request + if (!$request->query->has($name)) { + return; + } + + $data = $request->query->all()[$name]; + } + } else { + // Mark the form with an error if the uploaded size was too large + // This is done here and not in FormValidator because $_POST is + // empty when that error occurs. Hence the form is never submitted. + if ($this->serverParams->hasPostMaxSizeBeenExceeded()) { + // Submit the form, but don't clear the default values + $form->submit(null, false); + + $form->addError(new FormError( + $form->getConfig()->getOption('upload_max_size_message')(), + null, + ['{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()] + )); + + return; + } + + if ('' === $name) { + $params = $request->request->all(); + $files = $request->files->all(); + } elseif ($request->request->has($name) || $request->files->has($name)) { + $default = $form->getConfig()->getCompound() ? [] : null; + $params = $request->request->all()[$name] ?? $default; + $files = $request->files->get($name, $default); + } else { + // Don't submit the form if it is not present in the request + return; + } + + if (\is_array($params) && \is_array($files)) { + $data = FormUtil::mergeParamsAndFiles($params, $files); + } else { + $data = $params ?: $files; + } + } + + // Don't auto-submit the form unless at least one field is present. + if ('' === $name && \count(array_intersect_key($data, $form->all())) <= 0) { + return; + } + + $form->submit($data, 'PATCH' !== $method); + } + + public function isFileUpload(mixed $data): bool + { + return $data instanceof File; + } + + public function getUploadFileError(mixed $data): ?int + { + if (!$data instanceof UploadedFile || $data->isValid()) { + return null; + } + + return $data->getError(); + } +} diff --git a/vendor/symfony/form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php b/vendor/symfony/form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php new file mode 100644 index 0000000..5139308 --- /dev/null +++ b/vendor/symfony/form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\HttpFoundation\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\RequestHandlerInterface; + +/** + * @author Bernhard Schussek + */ +class FormTypeHttpFoundationExtension extends AbstractTypeExtension +{ + private RequestHandlerInterface $requestHandler; + + public function __construct(?RequestHandlerInterface $requestHandler = null) + { + $this->requestHandler = $requestHandler ?? new HttpFoundationRequestHandler(); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->setRequestHandler($this->requestHandler); + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } +} diff --git a/vendor/symfony/form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php b/vendor/symfony/form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php new file mode 100644 index 0000000..3ddac5f --- /dev/null +++ b/vendor/symfony/form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\PasswordHasher\EventListener; + +use Symfony\Component\Form\Exception\InvalidConfigurationException; +use Symfony\Component\Form\Extension\Core\Type\RepeatedType; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; + +/** + * @author Sébastien Alfaiate + * @author Gábor Egyed + */ +class PasswordHasherListener +{ + private array $passwords = []; + + public function __construct( + private UserPasswordHasherInterface $passwordHasher, + private ?PropertyAccessorInterface $propertyAccessor = null, + ) { + $this->propertyAccessor ??= PropertyAccess::createPropertyAccessor(); + } + + public function registerPassword(FormEvent $event): void + { + if (null === $event->getData() || '' === $event->getData()) { + return; + } + + $this->assertNotMapped($event->getForm()); + + $this->passwords[] = [ + 'form' => $event->getForm(), + 'property_path' => $event->getForm()->getConfig()->getOption('hash_property_path'), + 'password' => $event->getData(), + ]; + } + + public function hashPasswords(FormEvent $event): void + { + $form = $event->getForm(); + + if (!$form->isRoot()) { + return; + } + + if ($form->isValid()) { + foreach ($this->passwords as $password) { + $user = $this->getUser($password['form']); + + $this->propertyAccessor->setValue( + $user, + $password['property_path'], + $this->passwordHasher->hashPassword($user, $password['password']) + ); + } + } + + $this->passwords = []; + } + + private function getTargetForm(FormInterface $form): FormInterface + { + if (!$parentForm = $form->getParent()) { + return $form; + } + + $parentType = $parentForm->getConfig()->getType(); + + do { + if ($parentType->getInnerType() instanceof RepeatedType) { + return $parentForm; + } + } while ($parentType = $parentType->getParent()); + + return $form; + } + + private function getUser(FormInterface $form): PasswordAuthenticatedUserInterface + { + $parent = $this->getTargetForm($form)->getParent(); + + if (!($user = $parent?->getData()) || !$user instanceof PasswordAuthenticatedUserInterface) { + throw new InvalidConfigurationException(sprintf('The "hash_property_path" option only supports "%s" objects, "%s" given.', PasswordAuthenticatedUserInterface::class, get_debug_type($user))); + } + + return $user; + } + + private function assertNotMapped(FormInterface $form): void + { + if ($this->getTargetForm($form)->getConfig()->getMapped()) { + throw new InvalidConfigurationException('The "hash_property_path" option cannot be used on mapped field.'); + } + } +} diff --git a/vendor/symfony/form/Extension/PasswordHasher/PasswordHasherExtension.php b/vendor/symfony/form/Extension/PasswordHasher/PasswordHasherExtension.php new file mode 100644 index 0000000..b9675c2 --- /dev/null +++ b/vendor/symfony/form/Extension/PasswordHasher/PasswordHasherExtension.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\PasswordHasher; + +use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; + +/** + * Integrates the PasswordHasher component with the Form library. + * + * @author Sébastien Alfaiate + */ +class PasswordHasherExtension extends AbstractExtension +{ + public function __construct( + private PasswordHasherListener $passwordHasherListener, + ) { + } + + protected function loadTypeExtensions(): array + { + return [ + new Type\FormTypePasswordHasherExtension($this->passwordHasherListener), + new Type\PasswordTypePasswordHasherExtension($this->passwordHasherListener), + ]; + } +} diff --git a/vendor/symfony/form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php b/vendor/symfony/form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php new file mode 100644 index 0000000..8836f95 --- /dev/null +++ b/vendor/symfony/form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\PasswordHasher\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvents; + +/** + * @author Sébastien Alfaiate + */ +class FormTypePasswordHasherExtension extends AbstractTypeExtension +{ + public function __construct( + private PasswordHasherListener $passwordHasherListener, + ) { + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'hashPasswords']); + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } +} diff --git a/vendor/symfony/form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php b/vendor/symfony/form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php new file mode 100644 index 0000000..1d73e07 --- /dev/null +++ b/vendor/symfony/form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\PasswordHasher\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\PropertyAccess\PropertyPath; + +/** + * @author Sébastien Alfaiate + */ +class PasswordTypePasswordHasherExtension extends AbstractTypeExtension +{ + public function __construct( + private PasswordHasherListener $passwordHasherListener, + ) { + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ($options['hash_property_path']) { + $builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'registerPassword']); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'hash_property_path' => null, + ]); + + $resolver->setAllowedTypes('hash_property_path', ['null', 'string', PropertyPath::class]); + + $resolver->setInfo('hash_property_path', 'A valid PropertyAccess syntax where the hashed password will be set.'); + } + + public static function getExtendedTypes(): iterable + { + return [PasswordType::class]; + } +} diff --git a/vendor/symfony/form/Extension/Validator/Constraints/Form.php b/vendor/symfony/form/Extension/Validator/Constraints/Form.php new file mode 100644 index 0000000..8be25c0 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/Constraints/Form.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @author Bernhard Schussek + */ +class Form extends Constraint +{ + public const NOT_SYNCHRONIZED_ERROR = '1dafa156-89e1-4736-b832-419c2e501fca'; + public const NO_SUCH_FIELD_ERROR = '6e5212ed-a197-4339-99aa-5654798a4854'; + + protected const ERROR_NAMES = [ + self::NOT_SYNCHRONIZED_ERROR => 'NOT_SYNCHRONIZED_ERROR', + self::NO_SUCH_FIELD_ERROR => 'NO_SUCH_FIELD_ERROR', + ]; + + public function getTargets(): string|array + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/vendor/symfony/form/Extension/Validator/Constraints/FormValidator.php b/vendor/symfony/form/Extension/Validator/Constraints/FormValidator.php new file mode 100644 index 0000000..4163587 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/Constraints/FormValidator.php @@ -0,0 +1,276 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Constraints; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Composite; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class FormValidator extends ConstraintValidator +{ + /** + * @var \SplObjectStorage> + */ + private \SplObjectStorage $resolvedGroups; + + public function validate(mixed $form, Constraint $formConstraint): void + { + if (!$formConstraint instanceof Form) { + throw new UnexpectedTypeException($formConstraint, Form::class); + } + + if (!$form instanceof FormInterface) { + return; + } + + /* @var FormInterface $form */ + $config = $form->getConfig(); + + $validator = $this->context->getValidator()->inContext($this->context); + + if ($form->isSubmitted() && $form->isSynchronized()) { + // Validate the form data only if transformation succeeded + $groups = $this->getValidationGroups($form); + + if (!$groups) { + return; + } + + $data = $form->getData(); + // Validate the data against its own constraints + $validateDataGraph = $form->isRoot() + && (\is_object($data) || \is_array($data)) + && (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups)) + ; + + // Validate the data against the constraints defined in the form + /** @var Constraint[] $constraints */ + $constraints = $config->getOption('constraints', []); + + $hasChildren = $form->count() > 0; + + if ($hasChildren && $form->isRoot()) { + $this->resolvedGroups = new \SplObjectStorage(); + } + + if ($groups instanceof GroupSequence) { + // Validate the data, the form AND nested fields in sequence + $violationsCount = $this->context->getViolations()->count(); + + foreach ($groups->groups as $group) { + if ($validateDataGraph) { + $validator->atPath('data')->validate($data, null, $group); + } + + if ($groupedConstraints = self::getConstraintsInGroups($constraints, $group)) { + $validator->atPath('data')->validate($data, $groupedConstraints, $group); + } + + foreach ($form->all() as $field) { + if ($field->isSubmitted()) { + // remember to validate this field in one group only + // otherwise resolving the groups would reuse the same + // sequence recursively, thus some fields could fail + // in different steps without breaking early enough + $this->resolvedGroups[$field] = (array) $group; + $fieldFormConstraint = new Form(); + $fieldFormConstraint->groups = $group; + $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); + $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint, $group); + } + } + + if ($violationsCount < $this->context->getViolations()->count()) { + break; + } + } + } else { + if ($validateDataGraph) { + $validator->atPath('data')->validate($data, null, $groups); + } + + $groupedConstraints = []; + + foreach ($constraints as $constraint) { + // For the "Valid" constraint, validate the data in all groups + if ($constraint instanceof Valid) { + if (\is_object($data) || \is_array($data)) { + $validator->atPath('data')->validate($data, $constraint, $groups); + } + + continue; + } + + // Otherwise validate a constraint only once for the first + // matching group + foreach ($groups as $group) { + if (\in_array($group, $constraint->groups, true)) { + $groupedConstraints[$group][] = $constraint; + + // Prevent duplicate validation + if (!$constraint instanceof Composite) { + continue 2; + } + } + } + } + + foreach ($groupedConstraints as $group => $constraint) { + $validator->atPath('data')->validate($data, $constraint, $group); + } + + foreach ($form->all() as $field) { + if ($field->isSubmitted()) { + $this->resolvedGroups[$field] = $groups; + $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); + $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $formConstraint); + } + } + } + + if ($hasChildren && $form->isRoot()) { + // destroy storage to avoid memory leaks + $this->resolvedGroups = new \SplObjectStorage(); + } + } elseif (!$form->isSynchronized()) { + $childrenSynchronized = true; + + /** @var FormInterface $child */ + foreach ($form as $child) { + if (!$child->isSynchronized()) { + $childrenSynchronized = false; + $this->context->setNode($this->context->getValue(), $child, $this->context->getMetadata(), $this->context->getPropertyPath()); + $validator->atPath(sprintf('children[%s]', $child->getName()))->validate($child, $formConstraint); + } + } + + // Mark the form with an error if it is not synchronized BUT all + // of its children are synchronized. If any child is not + // synchronized, an error is displayed there already and showing + // a second error in its parent form is pointless, or worse, may + // lead to duplicate errors if error bubbling is enabled on the + // child. + // See also https://github.com/symfony/symfony/issues/4359 + if ($childrenSynchronized) { + $clientDataAsString = \is_scalar($form->getViewData()) + ? (string) $form->getViewData() + : get_debug_type($form->getViewData()); + + $failure = $form->getTransformationFailure(); + + $this->context->setConstraint($formConstraint); + $this->context->buildViolation($failure->getInvalidMessage() ?? $config->getOption('invalid_message')) + ->setParameters(array_replace( + ['{{ value }}' => $clientDataAsString], + $config->getOption('invalid_message_parameters'), + $failure->getInvalidMessageParameters() + )) + ->setInvalidValue($form->getViewData()) + ->setCode(Form::NOT_SYNCHRONIZED_ERROR) + ->setCause($failure) + ->addViolation(); + } + } + + // Mark the form with an error if it contains extra fields + if (!$config->getOption('allow_extra_fields') && \count($form->getExtraData()) > 0) { + $this->context->setConstraint($formConstraint); + $this->context->buildViolation($config->getOption('extra_fields_message', '')) + ->setParameter('{{ extra_fields }}', '"'.implode('", "', array_keys($form->getExtraData())).'"') + ->setPlural(\count($form->getExtraData())) + ->setInvalidValue($form->getExtraData()) + ->setCode(Form::NO_SUCH_FIELD_ERROR) + ->addViolation(); + } + } + + /** + * Returns the validation groups of the given form. + * + * @return string|GroupSequence|array + */ + private function getValidationGroups(FormInterface $form): string|GroupSequence|array + { + // Determine the clicked button of the complete form tree + $clickedButton = null; + + if (method_exists($form, 'getClickedButton')) { + $clickedButton = $form->getClickedButton(); + } + + if (null !== $clickedButton) { + $groups = $clickedButton->getConfig()->getOption('validation_groups'); + + if (null !== $groups) { + return self::resolveValidationGroups($groups, $form); + } + } + + do { + $groups = $form->getConfig()->getOption('validation_groups'); + + if (null !== $groups) { + return self::resolveValidationGroups($groups, $form); + } + + if (isset($this->resolvedGroups[$form])) { + return $this->resolvedGroups[$form]; + } + + $form = $form->getParent(); + } while (null !== $form); + + return [Constraint::DEFAULT_GROUP]; + } + + /** + * Post-processes the validation groups option for a given form. + * + * @param string|GroupSequence|array|callable $groups The validation groups + * + * @return GroupSequence|array + */ + private static function resolveValidationGroups(string|GroupSequence|array|callable $groups, FormInterface $form): GroupSequence|array + { + if (!\is_string($groups) && \is_callable($groups)) { + $groups = $groups($form); + } + + if ($groups instanceof GroupSequence) { + return $groups; + } + + return (array) $groups; + } + + private static function getConstraintsInGroups(array $constraints, string|array $group): array + { + $groups = (array) $group; + + return array_filter($constraints, static function (Constraint $constraint) use ($groups) { + foreach ($groups as $group) { + if (\in_array($group, $constraint->groups, true)) { + return true; + } + } + + return false; + }); + } +} diff --git a/vendor/symfony/form/Extension/Validator/EventListener/ValidationListener.php b/vendor/symfony/form/Extension/Validator/EventListener/ValidationListener.php new file mode 100644 index 0000000..b3c16db --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/EventListener/ValidationListener.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Validator\Constraints\Form; +use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * @author Bernhard Schussek + */ +class ValidationListener implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [FormEvents::POST_SUBMIT => 'validateForm']; + } + + public function __construct( + private ValidatorInterface $validator, + private ViolationMapperInterface $violationMapper, + ) { + } + + public function validateForm(FormEvent $event): void + { + $form = $event->getForm(); + + if ($form->isRoot()) { + // Form groups are validated internally (FormValidator). Here we don't set groups as they are retrieved into the validator. + foreach ($this->validator->validate($form) as $violation) { + // Allow the "invalid" constraint to be put onto + // non-synchronized forms + $allowNonSynchronized = $violation->getConstraint() instanceof Form && Form::NOT_SYNCHRONIZED_ERROR === $violation->getCode(); + + $this->violationMapper->mapViolation($violation, $form, $allowNonSynchronized); + } + } + } +} diff --git a/vendor/symfony/form/Extension/Validator/Type/BaseValidatorExtension.php b/vendor/symfony/form/Extension/Validator/Type/BaseValidatorExtension.php new file mode 100644 index 0000000..6b70df7 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/Type/BaseValidatorExtension.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Encapsulates common logic of {@link FormTypeValidatorExtension} and + * {@link SubmitTypeValidatorExtension}. + * + * @author Bernhard Schussek + */ +abstract class BaseValidatorExtension extends AbstractTypeExtension +{ + public function configureOptions(OptionsResolver $resolver): void + { + // Make sure that validation groups end up as null, closure or array + $validationGroupsNormalizer = static function (Options $options, $groups) { + if (false === $groups) { + return []; + } + + if (!$groups) { + return null; + } + + if (\is_callable($groups)) { + return $groups; + } + + if ($groups instanceof GroupSequence) { + return $groups; + } + + return (array) $groups; + }; + + $resolver->setDefaults([ + 'validation_groups' => null, + ]); + + $resolver->setNormalizer('validation_groups', $validationGroupsNormalizer); + } +} diff --git a/vendor/symfony/form/Extension/Validator/Type/FormTypeValidatorExtension.php b/vendor/symfony/form/Extension/Validator/Type/FormTypeValidatorExtension.php new file mode 100644 index 0000000..14029e3 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener; +use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormRendererInterface; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Bernhard Schussek + */ +class FormTypeValidatorExtension extends BaseValidatorExtension +{ + private ViolationMapper $violationMapper; + + public function __construct( + private ValidatorInterface $validator, + private bool $legacyErrorMessages = true, + ?FormRendererInterface $formRenderer = null, + ?TranslatorInterface $translator = null, + ) { + $this->violationMapper = new ViolationMapper($formRenderer, $translator); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addEventSubscriber(new ValidationListener($this->validator, $this->violationMapper)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); + + // Constraint should always be converted to an array + $constraintsNormalizer = static fn (Options $options, $constraints) => \is_object($constraints) ? [$constraints] : (array) $constraints; + + $resolver->setDefaults([ + 'error_mapping' => [], + 'constraints' => [], + 'invalid_message' => 'This value is not valid.', + 'invalid_message_parameters' => [], + 'allow_extra_fields' => false, + 'extra_fields_message' => 'This form should not contain extra fields.', + ]); + $resolver->setAllowedTypes('constraints', [Constraint::class, Constraint::class.'[]']); + $resolver->setNormalizer('constraints', $constraintsNormalizer); + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } +} diff --git a/vendor/symfony/form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php b/vendor/symfony/form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php new file mode 100644 index 0000000..949dca4 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\RepeatedType; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Bernhard Schussek + */ +class RepeatedTypeValidatorExtension extends AbstractTypeExtension +{ + public function configureOptions(OptionsResolver $resolver): void + { + // Map errors to the first field + $errorMapping = static fn (Options $options) => ['.' => $options['first_name']]; + + $resolver->setDefaults([ + 'error_mapping' => $errorMapping, + ]); + } + + public static function getExtendedTypes(): iterable + { + return [RepeatedType::class]; + } +} diff --git a/vendor/symfony/form/Extension/Validator/Type/SubmitTypeValidatorExtension.php b/vendor/symfony/form/Extension/Validator/Type/SubmitTypeValidatorExtension.php new file mode 100644 index 0000000..8efae7d --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/Type/SubmitTypeValidatorExtension.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\Extension\Core\Type\SubmitType; + +/** + * @author Bernhard Schussek + */ +class SubmitTypeValidatorExtension extends BaseValidatorExtension +{ + public static function getExtendedTypes(): iterable + { + return [SubmitType::class]; + } +} diff --git a/vendor/symfony/form/Extension/Validator/Type/UploadValidatorExtension.php b/vendor/symfony/form/Extension/Validator/Type/UploadValidatorExtension.php new file mode 100644 index 0000000..7c1e965 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/Type/UploadValidatorExtension.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + * @author David Badura + */ +class UploadValidatorExtension extends AbstractTypeExtension +{ + public function __construct( + private TranslatorInterface $translator, + private ?string $translationDomain = null, + ) { + } + + public function configureOptions(OptionsResolver $resolver): void + { + $translator = $this->translator; + $translationDomain = $this->translationDomain; + $resolver->setNormalizer('upload_max_size_message', static fn (Options $options, $message) => static fn () => $translator->trans($message(), [], $translationDomain)); + } + + public static function getExtendedTypes(): iterable + { + return [FormType::class]; + } +} diff --git a/vendor/symfony/form/Extension/Validator/ValidatorExtension.php b/vendor/symfony/form/Extension/Validator/ValidatorExtension.php new file mode 100644 index 0000000..522a769 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ValidatorExtension.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator; + +use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Form\Extension\Validator\Constraints\Form; +use Symfony\Component\Form\FormRendererInterface; +use Symfony\Component\Form\FormTypeGuesserInterface; +use Symfony\Component\Validator\Constraints\Traverse; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Extension supporting the Symfony Validator component in forms. + * + * @author Bernhard Schussek + */ +class ValidatorExtension extends AbstractExtension +{ + public function __construct( + private ValidatorInterface $validator, + private bool $legacyErrorMessages = true, + private ?FormRendererInterface $formRenderer = null, + private ?TranslatorInterface $translator = null, + ) { + $metadata = $validator->getMetadataFor(\Symfony\Component\Form\Form::class); + + // Register the form constraints in the validator programmatically. + // This functionality is required when using the Form component without + // the DIC, where the XML file is loaded automatically. Thus the following + // code must be kept synchronized with validation.xml + + /* @var $metadata ClassMetadata */ + $metadata->addConstraint(new Form()); + $metadata->addConstraint(new Traverse(false)); + } + + public function loadTypeGuesser(): ?FormTypeGuesserInterface + { + return new ValidatorTypeGuesser($this->validator); + } + + protected function loadTypeExtensions(): array + { + return [ + new Type\FormTypeValidatorExtension($this->validator, $this->legacyErrorMessages, $this->formRenderer, $this->translator), + new Type\RepeatedTypeValidatorExtension(), + new Type\SubmitTypeValidatorExtension(), + ]; + } +} diff --git a/vendor/symfony/form/Extension/Validator/ValidatorTypeGuesser.php b/vendor/symfony/form/Extension/Validator/ValidatorTypeGuesser.php new file mode 100644 index 0000000..72ae8dd --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ValidatorTypeGuesser.php @@ -0,0 +1,291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator; + +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\LanguageType; +use Symfony\Component\Form\Extension\Core\Type\LocaleType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\TimeType; +use Symfony\Component\Form\Extension\Core\Type\UrlType; +use Symfony\Component\Form\FormTypeGuesserInterface; +use Symfony\Component\Form\Guess\Guess; +use Symfony\Component\Form\Guess\TypeGuess; +use Symfony\Component\Form\Guess\ValueGuess; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Count; +use Symfony\Component\Validator\Constraints\Country; +use Symfony\Component\Validator\Constraints\Currency; +use Symfony\Component\Validator\Constraints\Date; +use Symfony\Component\Validator\Constraints\DateTime; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\File; +use Symfony\Component\Validator\Constraints\Image; +use Symfony\Component\Validator\Constraints\Ip; +use Symfony\Component\Validator\Constraints\IsFalse; +use Symfony\Component\Validator\Constraints\IsTrue; +use Symfony\Component\Validator\Constraints\Language; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\Locale; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Constraints\Range; +use Symfony\Component\Validator\Constraints\Regex; +use Symfony\Component\Validator\Constraints\Time; +use Symfony\Component\Validator\Constraints\Type; +use Symfony\Component\Validator\Constraints\Url; +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; + +class ValidatorTypeGuesser implements FormTypeGuesserInterface +{ + public function __construct( + private MetadataFactoryInterface $metadataFactory, + ) { + } + + public function guessType(string $class, string $property): ?TypeGuess + { + return $this->guess($class, $property, $this->guessTypeForConstraint(...)); + } + + public function guessRequired(string $class, string $property): ?ValueGuess + { + // If we don't find any constraint telling otherwise, we can assume + // that a field is not required (with LOW_CONFIDENCE) + return $this->guess($class, $property, $this->guessRequiredForConstraint(...), false); + } + + public function guessMaxLength(string $class, string $property): ?ValueGuess + { + return $this->guess($class, $property, $this->guessMaxLengthForConstraint(...)); + } + + public function guessPattern(string $class, string $property): ?ValueGuess + { + return $this->guess($class, $property, $this->guessPatternForConstraint(...)); + } + + /** + * Guesses a field class name for a given constraint. + */ + public function guessTypeForConstraint(Constraint $constraint): ?TypeGuess + { + switch ($constraint::class) { + case Type::class: + switch ($constraint->type) { + case 'array': + return new TypeGuess(CollectionType::class, [], Guess::MEDIUM_CONFIDENCE); + case 'boolean': + case 'bool': + return new TypeGuess(CheckboxType::class, [], Guess::MEDIUM_CONFIDENCE); + + case 'double': + case 'float': + case 'numeric': + case 'real': + return new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE); + + case 'integer': + case 'int': + case 'long': + return new TypeGuess(IntegerType::class, [], Guess::MEDIUM_CONFIDENCE); + + case \DateTime::class: + case '\DateTime': + return new TypeGuess(DateType::class, [], Guess::MEDIUM_CONFIDENCE); + + case \DateTimeImmutable::class: + case '\DateTimeImmutable': + case \DateTimeInterface::class: + case '\DateTimeInterface': + return new TypeGuess(DateType::class, ['input' => 'datetime_immutable'], Guess::MEDIUM_CONFIDENCE); + + case 'string': + return new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE); + } + break; + + case Country::class: + return new TypeGuess(CountryType::class, [], Guess::HIGH_CONFIDENCE); + + case Currency::class: + return new TypeGuess(CurrencyType::class, [], Guess::HIGH_CONFIDENCE); + + case Date::class: + return new TypeGuess(DateType::class, ['input' => 'string'], Guess::HIGH_CONFIDENCE); + + case DateTime::class: + return new TypeGuess(DateTimeType::class, ['input' => 'string'], Guess::HIGH_CONFIDENCE); + + case Email::class: + return new TypeGuess(EmailType::class, [], Guess::HIGH_CONFIDENCE); + + case File::class: + case Image::class: + $options = []; + if ($constraint->mimeTypes) { + $options = ['attr' => ['accept' => implode(',', (array) $constraint->mimeTypes)]]; + } + + return new TypeGuess(FileType::class, $options, Guess::HIGH_CONFIDENCE); + + case Language::class: + return new TypeGuess(LanguageType::class, [], Guess::HIGH_CONFIDENCE); + + case Locale::class: + return new TypeGuess(LocaleType::class, [], Guess::HIGH_CONFIDENCE); + + case Time::class: + return new TypeGuess(TimeType::class, ['input' => 'string'], Guess::HIGH_CONFIDENCE); + + case Url::class: + return new TypeGuess(UrlType::class, [], Guess::HIGH_CONFIDENCE); + + case Ip::class: + return new TypeGuess(TextType::class, [], Guess::MEDIUM_CONFIDENCE); + + case Length::class: + case Regex::class: + return new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE); + + case Range::class: + return new TypeGuess(NumberType::class, [], Guess::LOW_CONFIDENCE); + + case Count::class: + return new TypeGuess(CollectionType::class, [], Guess::LOW_CONFIDENCE); + + case IsTrue::class: + case IsFalse::class: + return new TypeGuess(CheckboxType::class, [], Guess::MEDIUM_CONFIDENCE); + } + + return null; + } + + /** + * Guesses whether a field is required based on the given constraint. + */ + public function guessRequiredForConstraint(Constraint $constraint): ?ValueGuess + { + return match ($constraint::class) { + NotNull::class, + NotBlank::class, + IsTrue::class => new ValueGuess(true, Guess::HIGH_CONFIDENCE), + default => null, + }; + } + + /** + * Guesses a field's maximum length based on the given constraint. + */ + public function guessMaxLengthForConstraint(Constraint $constraint): ?ValueGuess + { + switch ($constraint::class) { + case Length::class: + if (is_numeric($constraint->max)) { + return new ValueGuess($constraint->max, Guess::HIGH_CONFIDENCE); + } + break; + + case Type::class: + if (\in_array($constraint->type, ['double', 'float', 'numeric', 'real'])) { + return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); + } + break; + + case Range::class: + if (is_numeric($constraint->max)) { + return new ValueGuess(\strlen((string) $constraint->max), Guess::LOW_CONFIDENCE); + } + break; + } + + return null; + } + + /** + * Guesses a field's pattern based on the given constraint. + */ + public function guessPatternForConstraint(Constraint $constraint): ?ValueGuess + { + switch ($constraint::class) { + case Length::class: + if (is_numeric($constraint->min)) { + return new ValueGuess(sprintf('.{%s,}', (string) $constraint->min), Guess::LOW_CONFIDENCE); + } + break; + + case Regex::class: + $htmlPattern = $constraint->getHtmlPattern(); + + if (null !== $htmlPattern) { + return new ValueGuess($htmlPattern, Guess::HIGH_CONFIDENCE); + } + break; + + case Range::class: + if (is_numeric($constraint->min)) { + return new ValueGuess(sprintf('.{%s,}', \strlen((string) $constraint->min)), Guess::LOW_CONFIDENCE); + } + break; + + case Type::class: + if (\in_array($constraint->type, ['double', 'float', 'numeric', 'real'])) { + return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); + } + break; + } + + return null; + } + + /** + * Iterates over the constraints of a property, executes a constraints on + * them and returns the best guess. + * + * @param \Closure $closure The closure that returns a guess + * for a given constraint + * @param mixed $defaultValue The default value assumed if no other value + * can be guessed + */ + protected function guess(string $class, string $property, \Closure $closure, mixed $defaultValue = null): ?Guess + { + $guesses = []; + $classMetadata = $this->metadataFactory->getMetadataFor($class); + + if ($classMetadata instanceof ClassMetadataInterface && $classMetadata->hasPropertyMetadata($property)) { + foreach ($classMetadata->getPropertyMetadata($property) as $memberMetadata) { + foreach ($memberMetadata->getConstraints() as $constraint) { + if ($guess = $closure($constraint)) { + $guesses[] = $guess; + } + } + } + } + + if (null !== $defaultValue) { + $guesses[] = new ValueGuess($defaultValue, Guess::LOW_CONFIDENCE); + } + + return Guess::getBestGuess($guesses); + } +} diff --git a/vendor/symfony/form/Extension/Validator/ViolationMapper/MappingRule.php b/vendor/symfony/form/Extension/Validator/ViolationMapper/MappingRule.php new file mode 100644 index 0000000..f9a61cc --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ViolationMapper/MappingRule.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\Exception\ErrorMappingException; +use Symfony\Component\Form\FormInterface; + +/** + * @author Bernhard Schussek + */ +class MappingRule +{ + public function __construct( + private FormInterface $origin, + private string $propertyPath, + private string $targetPath, + ) { + } + + public function getOrigin(): FormInterface + { + return $this->origin; + } + + /** + * Matches a property path against the rule path. + * + * If the rule matches, the form mapped by the rule is returned. + * Otherwise this method returns false. + */ + public function match(string $propertyPath): ?FormInterface + { + return $propertyPath === $this->propertyPath ? $this->getTarget() : null; + } + + /** + * Matches a property path against a prefix of the rule path. + */ + public function isPrefix(string $propertyPath): bool + { + $length = \strlen($propertyPath); + $prefix = substr($this->propertyPath, 0, $length); + $next = $this->propertyPath[$length] ?? null; + + return $prefix === $propertyPath && ('[' === $next || '.' === $next); + } + + /** + * @throws ErrorMappingException + */ + public function getTarget(): FormInterface + { + $childNames = explode('.', $this->targetPath); + $target = $this->origin; + + foreach ($childNames as $childName) { + if (!$target->has($childName)) { + throw new ErrorMappingException(sprintf('The child "%s" of "%s" mapped by the rule "%s" in "%s" does not exist.', $childName, $target->getName(), $this->targetPath, $this->origin->getName())); + } + $target = $target->get($childName); + } + + return $target; + } +} diff --git a/vendor/symfony/form/Extension/Validator/ViolationMapper/RelativePath.php b/vendor/symfony/form/Extension/Validator/ViolationMapper/RelativePath.php new file mode 100644 index 0000000..c139933 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ViolationMapper/RelativePath.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\PropertyAccess\PropertyPath; + +/** + * @author Bernhard Schussek + */ +class RelativePath extends PropertyPath +{ + public function __construct( + private FormInterface $root, + string $propertyPath, + ) { + parent::__construct($propertyPath); + } + + public function getRoot(): FormInterface + { + return $this->root; + } +} diff --git a/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationMapper.php b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationMapper.php new file mode 100644 index 0000000..faca255 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationMapper.php @@ -0,0 +1,338 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\FileUploadError; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormRendererInterface; +use Symfony\Component\Form\Util\InheritDataAwareIterator; +use Symfony\Component\PropertyAccess\PropertyPathBuilder; +use Symfony\Component\PropertyAccess\PropertyPathIterator; +use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface; +use Symfony\Component\Validator\Constraints\File; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Bernhard Schussek + */ +class ViolationMapper implements ViolationMapperInterface +{ + private bool $allowNonSynchronized = false; + + public function __construct( + private ?FormRendererInterface $formRenderer = null, + private ?TranslatorInterface $translator = null, + ) { + } + + public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false): void + { + $this->allowNonSynchronized = $allowNonSynchronized; + + // The scope is the currently found most specific form that + // an error should be mapped to. After setting the scope, the + // mapper will try to continue to find more specific matches in + // the children of scope. If it cannot, the error will be + // mapped to this scope. + $scope = null; + + $violationPath = null; + $relativePath = null; + $match = false; + + // Don't create a ViolationPath instance for empty property paths + if ('' !== $violation->getPropertyPath()) { + $violationPath = new ViolationPath($violation->getPropertyPath()); + $relativePath = $this->reconstructPath($violationPath, $form); + } + + // This case happens if the violation path is empty and thus + // the violation should be mapped to the root form + if (null === $violationPath) { + $scope = $form; + } + + // In general, mapping happens from the root form to the leaf forms + // First, the rules of the root form are applied to determine + // the subsequent descendant. The rules of this descendant are then + // applied to find the next and so on, until we have found the + // most specific form that matches the violation. + + // If any of the forms found in this process is not synchronized, + // mapping is aborted. Non-synchronized forms could not reverse + // transform the value entered by the user, thus any further violations + // caused by the (invalid) reverse transformed value should be + // ignored. + + if (null !== $relativePath) { + // Set the scope to the root of the relative path + // This root will usually be $form. If the path contains + // an unmapped form though, the last unmapped form found + // will be the root of the path. + $scope = $relativePath->getRoot(); + $it = new PropertyPathIterator($relativePath); + + while ($this->acceptsErrors($scope) && null !== ($child = $this->matchChild($scope, $it))) { + $scope = $child; + $it->next(); + $match = true; + } + } + + // This case happens if an error happened in the data under a + // form inheriting its parent data that does not match any of the + // children of that form. + if (null !== $violationPath && !$match) { + // If we could not map the error to anything more specific + // than the root element, map it to the innermost directly + // mapped form of the violation path + // e.g. "children[foo].children[bar].data.baz" + // Here the innermost directly mapped child is "bar" + + $scope = $form; + $it = new ViolationPathIterator($violationPath); + + // Note: acceptsErrors() will always return true for forms inheriting + // their parent data, because these forms can never be non-synchronized + // (they don't do any data transformation on their own) + while ($this->acceptsErrors($scope) && $it->valid() && $it->mapsForm()) { + if (!$scope->has($it->current())) { + // Break if we find a reference to a non-existing child + break; + } + + $scope = $scope->get($it->current()); + $it->next(); + } + } + + // Follow dot rules until we have the final target + $mapping = $scope->getConfig()->getOption('error_mapping'); + + while ($this->acceptsErrors($scope) && isset($mapping['.'])) { + $dotRule = new MappingRule($scope, '.', $mapping['.']); + $scope = $dotRule->getTarget(); + $mapping = $scope->getConfig()->getOption('error_mapping'); + } + + // Only add the error if the form is synchronized + if ($this->acceptsErrors($scope)) { + if ($violation->getConstraint() instanceof File && (string) \UPLOAD_ERR_INI_SIZE === $violation->getCode()) { + $errorsTarget = $scope; + + while (null !== $errorsTarget->getParent() && $errorsTarget->getConfig()->getErrorBubbling()) { + $errorsTarget = $errorsTarget->getParent(); + } + + $errors = $errorsTarget->getErrors(); + $errorsTarget->clearErrors(); + + foreach ($errors as $error) { + if (!$error instanceof FileUploadError) { + $errorsTarget->addError($error); + } + } + } + + $message = $violation->getMessage(); + $messageTemplate = $violation->getMessageTemplate(); + + if (str_contains($message, '{{ label }}') || str_contains($messageTemplate, '{{ label }}')) { + $form = $scope; + + do { + $labelFormat = $form->getConfig()->getOption('label_format'); + } while (null === $labelFormat && null !== $form = $form->getParent()); + + if (null !== $labelFormat) { + $label = str_replace( + [ + '%name%', + '%id%', + ], + [ + $scope->getName(), + (string) $scope->getPropertyPath(), + ], + $labelFormat + ); + } else { + $label = $scope->getConfig()->getOption('label'); + } + + if (false !== $label) { + if (null === $label && null !== $this->formRenderer) { + $label = $this->formRenderer->humanize($scope->getName()); + } else { + $label ??= $scope->getName(); + } + + if (null !== $this->translator) { + $form = $scope; + $translationParameters[] = $form->getConfig()->getOption('label_translation_parameters', []); + + do { + $translationDomain = $form->getConfig()->getOption('translation_domain'); + array_unshift( + $translationParameters, + $form->getConfig()->getOption('label_translation_parameters', []) + ); + } while (null === $translationDomain && null !== $form = $form->getParent()); + + $translationParameters = array_merge([], ...$translationParameters); + + $label = $this->translator->trans( + $label, + $translationParameters, + $translationDomain + ); + } + + $message = str_replace('{{ label }}', $label, $message); + $messageTemplate = str_replace('{{ label }}', $label, $messageTemplate); + } + } + + $scope->addError(new FormError( + $message, + $messageTemplate, + $violation->getParameters(), + $violation->getPlural(), + $violation + )); + } + } + + /** + * Tries to match the beginning of the property path at the + * current position against the children of the scope. + * + * If a matching child is found, it is returned. Otherwise + * null is returned. + */ + private function matchChild(FormInterface $form, PropertyPathIteratorInterface $it): ?FormInterface + { + $target = null; + $chunk = ''; + $foundAtIndex = null; + + // Construct mapping rules for the given form + $rules = []; + + foreach ($form->getConfig()->getOption('error_mapping') as $propertyPath => $targetPath) { + // Dot rules are considered at the very end + if ('.' !== $propertyPath) { + $rules[] = new MappingRule($form, $propertyPath, $targetPath); + } + } + + $children = iterator_to_array(new \RecursiveIteratorIterator(new InheritDataAwareIterator($form)), false); + + while ($it->valid()) { + if ($it->isIndex()) { + $chunk .= '['.$it->current().']'; + } else { + $chunk .= ('' === $chunk ? '' : '.').$it->current(); + } + + // Test mapping rules as long as we have any + foreach ($rules as $key => $rule) { + /* @var MappingRule $rule */ + + // Mapping rule matches completely, terminate. + if (null !== ($form = $rule->match($chunk))) { + return $form; + } + + // Keep only rules that have $chunk as prefix + if (!$rule->isPrefix($chunk)) { + unset($rules[$key]); + } + } + + /** @var FormInterface $child */ + foreach ($children as $i => $child) { + $childPath = (string) $child->getPropertyPath(); + if ($childPath === $chunk) { + $target = $child; + $foundAtIndex = $it->key(); + } elseif (str_starts_with($childPath, $chunk)) { + continue; + } + + unset($children[$i]); + } + + $it->next(); + } + + if (null !== $foundAtIndex) { + $it->seek($foundAtIndex); + } + + return $target; + } + + /** + * Reconstructs a property path from a violation path and a form tree. + */ + private function reconstructPath(ViolationPath $violationPath, FormInterface $origin): ?RelativePath + { + $propertyPathBuilder = new PropertyPathBuilder($violationPath); + $it = $violationPath->getIterator(); + $scope = $origin; + + // Remember the current index in the builder + $i = 0; + + // Expand elements that map to a form (like "children[address]") + for ($it->rewind(); $it->valid() && $it->mapsForm(); $it->next()) { + if (!$scope->has($it->current())) { + // Scope relates to a form that does not exist + // Bail out + break; + } + + // Process child form + $scope = $scope->get($it->current()); + + if ($scope->getConfig()->getInheritData()) { + // Form inherits its parent data + // Cut the piece out of the property path and proceed + $propertyPathBuilder->remove($i); + } else { + /* @var \Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath */ + $propertyPath = $scope->getPropertyPath(); + + if (null === $propertyPath) { + // Property path of a mapped form is null + // Should not happen, bail out + break; + } + + $propertyPathBuilder->replace($i, 1, $propertyPath); + $i += $propertyPath->getLength(); + } + } + + $finalPath = $propertyPathBuilder->getPropertyPath(); + + return null !== $finalPath ? new RelativePath($origin, $finalPath) : null; + } + + private function acceptsErrors(FormInterface $form): bool + { + return $this->allowNonSynchronized || $form->isSynchronized(); + } +} diff --git a/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php new file mode 100644 index 0000000..8d1f242 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Validator\ConstraintViolation; + +/** + * @author Bernhard Schussek + */ +interface ViolationMapperInterface +{ + /** + * Maps a constraint violation to a form in the form tree under + * the given form. + * + * @param bool $allowNonSynchronized Whether to allow mapping to non-synchronized forms + */ + public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false): void; +} diff --git a/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationPath.php b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationPath.php new file mode 100644 index 0000000..a9a0f15 --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationPath.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\Exception\OutOfBoundsException; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class ViolationPath implements \IteratorAggregate, PropertyPathInterface +{ + /** @var list */ + private array $elements = []; + private array $isIndex = []; + private array $mapsForm = []; + private string $pathAsString = ''; + private int $length = 0; + + /** + * Creates a new violation path from a string. + * + * @param string $violationPath The property path of a {@link \Symfony\Component\Validator\ConstraintViolation} object + */ + public function __construct(string $violationPath) + { + $path = new PropertyPath($violationPath); + $elements = $path->getElements(); + $data = false; + + for ($i = 0, $l = \count($elements); $i < $l; ++$i) { + if (!$data) { + // The element "data" has not yet been passed + if ('children' === $elements[$i] && $path->isProperty($i)) { + // Skip element "children" + ++$i; + + // Next element must exist and must be an index + // Otherwise consider this the end of the path + if ($i >= $l || !$path->isIndex($i)) { + break; + } + + // All the following index items (regardless if .children is + // explicitly used) are children and grand-children + for (; $i < $l && $path->isIndex($i); ++$i) { + $this->elements[] = $elements[$i]; + $this->isIndex[] = true; + $this->mapsForm[] = true; + } + + // Rewind the pointer as the last element above didn't match + // (even if the pointer was moved forward) + --$i; + } elseif ('data' === $elements[$i] && $path->isProperty($i)) { + // Skip element "data" + ++$i; + + // End of path + if ($i >= $l) { + break; + } + + $this->elements[] = $elements[$i]; + $this->isIndex[] = $path->isIndex($i); + $this->mapsForm[] = false; + $data = true; + } else { + // Neither "children" nor "data" property found + // Consider this the end of the path + break; + } + } else { + // Already after the "data" element + // Pick everything as is + $this->elements[] = $elements[$i]; + $this->isIndex[] = $path->isIndex($i); + $this->mapsForm[] = false; + } + } + + $this->length = \count($this->elements); + + $this->buildString(); + } + + public function __toString(): string + { + return $this->pathAsString; + } + + public function getLength(): int + { + return $this->length; + } + + public function getParent(): ?PropertyPathInterface + { + if ($this->length <= 1) { + return null; + } + + $parent = clone $this; + + --$parent->length; + array_pop($parent->elements); + array_pop($parent->isIndex); + array_pop($parent->mapsForm); + + $parent->buildString(); + + return $parent; + } + + public function getElements(): array + { + return $this->elements; + } + + public function getElement(int $index): string + { + if (!isset($this->elements[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index)); + } + + return $this->elements[$index]; + } + + public function isProperty(int $index): bool + { + if (!isset($this->isIndex[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index)); + } + + return !$this->isIndex[$index]; + } + + public function isIndex(int $index): bool + { + if (!isset($this->isIndex[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index)); + } + + return $this->isIndex[$index]; + } + + public function isNullSafe(int $index): bool + { + return false; + } + + /** + * Returns whether an element maps directly to a form. + * + * Consider the following violation path: + * + * children[address].children[office].data.street + * + * In this example, "address" and "office" map to forms, while + * "street does not. + * + * @throws OutOfBoundsException if the offset is invalid + */ + public function mapsForm(int $index): bool + { + if (!isset($this->mapsForm[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index)); + } + + return $this->mapsForm[$index]; + } + + /** + * Returns a new iterator for this path. + */ + public function getIterator(): ViolationPathIterator + { + return new ViolationPathIterator($this); + } + + /** + * Builds the string representation from the elements. + */ + private function buildString(): void + { + $this->pathAsString = ''; + $data = false; + + foreach ($this->elements as $index => $element) { + if ($this->mapsForm[$index]) { + $this->pathAsString .= ".children[$element]"; + } elseif (!$data) { + $this->pathAsString .= '.data'.($this->isIndex[$index] ? "[$element]" : ".$element"); + $data = true; + } else { + $this->pathAsString .= $this->isIndex[$index] ? "[$element]" : ".$element"; + } + } + + if ('' !== $this->pathAsString) { + // remove leading dot + $this->pathAsString = substr($this->pathAsString, 1); + } + } +} diff --git a/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationPathIterator.php b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationPathIterator.php new file mode 100644 index 0000000..06c719d --- /dev/null +++ b/vendor/symfony/form/Extension/Validator/ViolationMapper/ViolationPathIterator.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\PropertyAccess\PropertyPathIterator; + +/** + * @author Bernhard Schussek + */ +class ViolationPathIterator extends PropertyPathIterator +{ + public function __construct(ViolationPath $violationPath) + { + parent::__construct($violationPath); + } + + public function mapsForm(): bool + { + return $this->path->mapsForm($this->key()); + } +} diff --git a/vendor/symfony/form/FileUploadError.php b/vendor/symfony/form/FileUploadError.php new file mode 100644 index 0000000..20142b2 --- /dev/null +++ b/vendor/symfony/form/FileUploadError.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @internal + */ +class FileUploadError extends FormError +{ +} diff --git a/vendor/symfony/form/Form.php b/vendor/symfony/form/Form.php new file mode 100644 index 0000000..44b9726 --- /dev/null +++ b/vendor/symfony/form/Form.php @@ -0,0 +1,1010 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Event\PostSetDataEvent; +use Symfony\Component\Form\Event\PostSubmitEvent; +use Symfony\Component\Form\Event\PreSetDataEvent; +use Symfony\Component\Form\Event\PreSubmitEvent; +use Symfony\Component\Form\Event\SubmitEvent; +use Symfony\Component\Form\Exception\AlreadySubmittedException; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Exception\OutOfBoundsException; +use Symfony\Component\Form\Exception\RuntimeException; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Util\FormUtil; +use Symfony\Component\Form\Util\InheritDataAwareIterator; +use Symfony\Component\Form\Util\OrderedHashMap; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * Form represents a form. + * + * To implement your own form fields, you need to have a thorough understanding + * of the data flow within a form. A form stores its data in three different + * representations: + * + * (1) the "model" format required by the form's object + * (2) the "normalized" format for internal processing + * (3) the "view" format used for display simple fields + * or map children model data for compound fields + * + * A date field, for example, may store a date as "Y-m-d" string (1) in the + * object. To facilitate processing in the field, this value is normalized + * to a DateTime object (2). In the HTML representation of your form, a + * localized string (3) may be presented to and modified by the user, or it could be an array of values + * to be mapped to choices fields. + * + * In most cases, format (1) and format (2) will be the same. For example, + * a checkbox field uses a Boolean value for both internal processing and + * storage in the object. In these cases you need to set a view transformer + * to convert between formats (2) and (3). You can do this by calling + * addViewTransformer(). + * + * In some cases though it makes sense to make format (1) configurable. To + * demonstrate this, let's extend our above date field to store the value + * either as "Y-m-d" string or as timestamp. Internally we still want to + * use a DateTime object for processing. To convert the data from string/integer + * to DateTime you can set a model transformer by calling + * addModelTransformer(). The normalized data is then converted to the displayed + * data as described before. + * + * The conversions (1) -> (2) -> (3) use the transform methods of the transformers. + * The conversions (3) -> (2) -> (1) use the reverseTransform methods of the transformers. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterface +{ + private ?FormInterface $parent = null; + + /** + * A map of FormInterface instances. + * + * @var OrderedHashMap + */ + private OrderedHashMap $children; + + /** + * @var FormError[] + */ + private array $errors = []; + + private bool $submitted = false; + + /** + * The button that was used to submit the form. + */ + private FormInterface|ClickableInterface|null $clickedButton = null; + + private mixed $modelData = null; + private mixed $normData = null; + private mixed $viewData = null; + + /** + * The submitted values that don't belong to any children. + */ + private array $extraData = []; + + /** + * The transformation failure generated during submission, if any. + */ + private ?TransformationFailedException $transformationFailure = null; + + /** + * Whether the form's data has been initialized. + * + * When the data is initialized with its default value, that default value + * is passed through the transformer chain in order to synchronize the + * model, normalized and view format for the first time. This is done + * lazily in order to save performance when {@link setData()} is called + * manually, making the initialization with the configured default value + * superfluous. + */ + private bool $defaultDataSet = false; + + /** + * Whether setData() is currently being called. + */ + private bool $lockSetData = false; + + private string $name = ''; + + /** + * Whether the form inherits its underlying data from its parent. + */ + private bool $inheritData; + + private ?PropertyPathInterface $propertyPath = null; + + /** + * @throws LogicException if a data mapper is not provided for a compound form + */ + public function __construct( + private FormConfigInterface $config, + ) { + // Compound forms always need a data mapper, otherwise calls to + // `setData` and `add` will not lead to the correct population of + // the child forms. + if ($config->getCompound() && !$config->getDataMapper()) { + throw new LogicException('Compound forms need a data mapper.'); + } + + // If the form inherits the data from its parent, it is not necessary + // to call setData() with the default data. + if ($this->inheritData = $config->getInheritData()) { + $this->defaultDataSet = true; + } + + $this->children = new OrderedHashMap(); + $this->name = $config->getName(); + } + + public function __clone() + { + $this->children = clone $this->children; + + foreach ($this->children as $key => $child) { + $this->children[$key] = clone $child; + } + } + + public function getConfig(): FormConfigInterface + { + return $this->config; + } + + public function getName(): string + { + return $this->name; + } + + public function getPropertyPath(): ?PropertyPathInterface + { + if ($this->propertyPath || $this->propertyPath = $this->config->getPropertyPath()) { + return $this->propertyPath; + } + + if ('' === $this->name) { + return null; + } + + $parent = $this->parent; + + while ($parent?->getConfig()->getInheritData()) { + $parent = $parent->getParent(); + } + + if ($parent && null === $parent->getConfig()->getDataClass()) { + $this->propertyPath = new PropertyPath('['.$this->name.']'); + } else { + $this->propertyPath = new PropertyPath($this->name); + } + + return $this->propertyPath; + } + + public function isRequired(): bool + { + if (null === $this->parent || $this->parent->isRequired()) { + return $this->config->getRequired(); + } + + return false; + } + + public function isDisabled(): bool + { + if (null === $this->parent || !$this->parent->isDisabled()) { + return $this->config->getDisabled(); + } + + return true; + } + + public function setParent(?FormInterface $parent): static + { + if ($this->submitted) { + throw new AlreadySubmittedException('You cannot set the parent of a submitted form.'); + } + + if (null !== $parent && '' === $this->name) { + throw new LogicException('A form with an empty name cannot have a parent form.'); + } + + $this->parent = $parent; + + return $this; + } + + public function getParent(): ?FormInterface + { + return $this->parent; + } + + public function getRoot(): FormInterface + { + return $this->parent ? $this->parent->getRoot() : $this; + } + + public function isRoot(): bool + { + return null === $this->parent; + } + + public function setData(mixed $modelData): static + { + // If the form is submitted while disabled, it is set to submitted, but the data is not + // changed. In such cases (i.e. when the form is not initialized yet) don't + // abort this method. + if ($this->submitted && $this->defaultDataSet) { + throw new AlreadySubmittedException('You cannot change the data of a submitted form.'); + } + + // If the form inherits its parent's data, disallow data setting to + // prevent merge conflicts + if ($this->inheritData) { + throw new RuntimeException('You cannot change the data of a form inheriting its parent data.'); + } + + // Don't allow modifications of the configured data if the data is locked + if ($this->config->getDataLocked() && $modelData !== $this->config->getData()) { + return $this; + } + + if (\is_object($modelData) && !$this->config->getByReference()) { + $modelData = clone $modelData; + } + + if ($this->lockSetData) { + throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call setData(). You should call setData() on the FormEvent object instead.'); + } + + $this->lockSetData = true; + $dispatcher = $this->config->getEventDispatcher(); + + // Hook to change content of the model data before transformation and mapping children + if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) { + $event = new PreSetDataEvent($this, $modelData); + $dispatcher->dispatch($event, FormEvents::PRE_SET_DATA); + $modelData = $event->getData(); + } + + // Treat data as strings unless a transformer exists + if (\is_scalar($modelData) && !$this->config->getViewTransformers() && !$this->config->getModelTransformers()) { + $modelData = (string) $modelData; + } + + // Synchronize representations - must not change the content! + // Transformation exceptions are not caught on initialization + $normData = $this->modelToNorm($modelData); + $viewData = $this->normToView($normData); + + // Validate if view data matches data class (unless empty) + if (!FormUtil::isEmpty($viewData)) { + $dataClass = $this->config->getDataClass(); + + if (null !== $dataClass && !$viewData instanceof $dataClass) { + $actualType = get_debug_type($viewData); + + throw new LogicException('The form\'s view data is expected to be a "'.$dataClass.'", but it is a "'.$actualType.'". You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms "'.$actualType.'" to an instance of "'.$dataClass.'".'); + } + } + + $this->modelData = $modelData; + $this->normData = $normData; + $this->viewData = $viewData; + $this->defaultDataSet = true; + $this->lockSetData = false; + + // Compound forms don't need to invoke this method if they don't have children + if (\count($this->children) > 0) { + // Update child forms from the data (unless their config data is locked) + $this->config->getDataMapper()->mapDataToForms($viewData, new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children))); + } + + if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { + $event = new PostSetDataEvent($this, $modelData); + $dispatcher->dispatch($event, FormEvents::POST_SET_DATA); + } + + return $this; + } + + public function getData(): mixed + { + if ($this->inheritData) { + if (!$this->parent) { + throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); + } + + return $this->parent->getData(); + } + + if (!$this->defaultDataSet) { + if ($this->lockSetData) { + throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getData() if the form data has not already been set. You should call getData() on the FormEvent object instead.'); + } + + $this->setData($this->config->getData()); + } + + return $this->modelData; + } + + public function getNormData(): mixed + { + if ($this->inheritData) { + if (!$this->parent) { + throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); + } + + return $this->parent->getNormData(); + } + + if (!$this->defaultDataSet) { + if ($this->lockSetData) { + throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getNormData() if the form data has not already been set.'); + } + + $this->setData($this->config->getData()); + } + + return $this->normData; + } + + public function getViewData(): mixed + { + if ($this->inheritData) { + if (!$this->parent) { + throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); + } + + return $this->parent->getViewData(); + } + + if (!$this->defaultDataSet) { + if ($this->lockSetData) { + throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getViewData() if the form data has not already been set.'); + } + + $this->setData($this->config->getData()); + } + + return $this->viewData; + } + + public function getExtraData(): array + { + return $this->extraData; + } + + public function initialize(): static + { + if (null !== $this->parent) { + throw new RuntimeException('Only root forms should be initialized.'); + } + + // Guarantee that the *_SET_DATA events have been triggered once the + // form is initialized. This makes sure that dynamically added or + // removed fields are already visible after initialization. + if (!$this->defaultDataSet) { + $this->setData($this->config->getData()); + } + + return $this; + } + + public function handleRequest(mixed $request = null): static + { + $this->config->getRequestHandler()->handleRequest($this, $request); + + return $this; + } + + public function submit(mixed $submittedData, bool $clearMissing = true): static + { + if ($this->submitted) { + throw new AlreadySubmittedException('A form can only be submitted once.'); + } + + // Initialize errors in the very beginning so we're sure + // they are collectable during submission only + $this->errors = []; + + // Obviously, a disabled form should not change its data upon submission. + if ($this->isDisabled()) { + $this->submitted = true; + + return $this; + } + + // The data must be initialized if it was not initialized yet. + // This is necessary to guarantee that the *_SET_DATA listeners + // are always invoked before submit() takes place. + if (!$this->defaultDataSet) { + $this->setData($this->config->getData()); + } + + // Treat false as NULL to support binding false to checkboxes. + // Don't convert NULL to a string here in order to determine later + // whether an empty value has been submitted or whether no value has + // been submitted at all. This is important for processing checkboxes + // and radio buttons with empty values. + if (false === $submittedData) { + $submittedData = null; + } elseif (\is_scalar($submittedData)) { + $submittedData = (string) $submittedData; + } elseif ($this->config->getRequestHandler()->isFileUpload($submittedData)) { + if (!$this->config->getOption('allow_file_upload')) { + $submittedData = null; + $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, file upload given.'); + } + } elseif (\is_array($submittedData) && !$this->config->getCompound() && !$this->config->getOption('multiple', false)) { + $submittedData = null; + $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, array given.'); + } + + $dispatcher = $this->config->getEventDispatcher(); + + $modelData = null; + $normData = null; + $viewData = null; + + try { + if (null !== $this->transformationFailure) { + throw $this->transformationFailure; + } + + // Hook to change content of the data submitted by the browser + if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { + $event = new PreSubmitEvent($this, $submittedData); + $dispatcher->dispatch($event, FormEvents::PRE_SUBMIT); + $submittedData = $event->getData(); + } + + // Check whether the form is compound. + // This check is preferable over checking the number of children, + // since forms without children may also be compound. + // (think of empty collection forms) + if ($this->config->getCompound()) { + if (!\is_array($submittedData ??= [])) { + throw new TransformationFailedException('Compound forms expect an array or NULL on submission.'); + } + + foreach ($this->children as $name => $child) { + $isSubmitted = \array_key_exists($name, $submittedData); + + if ($isSubmitted || $clearMissing) { + $child->submit($isSubmitted ? $submittedData[$name] : null, $clearMissing); + unset($submittedData[$name]); + + if (null !== $this->clickedButton) { + continue; + } + + if ($child instanceof ClickableInterface && $child->isClicked()) { + $this->clickedButton = $child; + + continue; + } + + if (method_exists($child, 'getClickedButton') && null !== $child->getClickedButton()) { + $this->clickedButton = $child->getClickedButton(); + } + } + } + + $this->extraData = $submittedData; + } + + // Forms that inherit their parents' data also are not processed, + // because then it would be too difficult to merge the changes in + // the child and the parent form. Instead, the parent form also takes + // changes in the grandchildren (i.e. children of the form that inherits + // its parent's data) into account. + // (see InheritDataAwareIterator below) + if (!$this->inheritData) { + // If the form is compound, the view data is merged with the data + // of the children using the data mapper. + // If the form is not compound, the view data is assigned to the submitted data. + $viewData = $this->config->getCompound() ? $this->viewData : $submittedData; + + if (FormUtil::isEmpty($viewData)) { + $emptyData = $this->config->getEmptyData(); + + if ($emptyData instanceof \Closure) { + $emptyData = $emptyData($this, $viewData); + } + + $viewData = $emptyData; + } + + // Merge form data from children into existing view data + // It is not necessary to invoke this method if the form has no children, + // even if it is compound. + if (\count($this->children) > 0) { + // Use InheritDataAwareIterator to process children of + // descendants that inherit this form's data. + // These descendants will not be submitted normally (see the check + // for $this->config->getInheritData() above) + $this->config->getDataMapper()->mapFormsToData( + new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children)), + $viewData + ); + } + + // Normalize data to unified representation + $normData = $this->viewToNorm($viewData); + + // Hook to change content of the data in the normalized + // representation + if ($dispatcher->hasListeners(FormEvents::SUBMIT)) { + $event = new SubmitEvent($this, $normData); + $dispatcher->dispatch($event, FormEvents::SUBMIT); + $normData = $event->getData(); + } + + // Synchronize representations - must not change the content! + $modelData = $this->normToModel($normData); + $viewData = $this->normToView($normData); + } + } catch (TransformationFailedException $e) { + $this->transformationFailure = $e; + + // If $viewData was not yet set, set it to $submittedData so that + // the erroneous data is accessible on the form. + // Forms that inherit data never set any data, because the getters + // forward to the parent form's getters anyway. + if (null === $viewData && !$this->inheritData) { + $viewData = $submittedData; + } + } + + $this->submitted = true; + $this->modelData = $modelData; + $this->normData = $normData; + $this->viewData = $viewData; + + if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) { + $event = new PostSubmitEvent($this, $viewData); + $dispatcher->dispatch($event, FormEvents::POST_SUBMIT); + } + + return $this; + } + + public function addError(FormError $error): static + { + if (null === $error->getOrigin()) { + $error->setOrigin($this); + } + + if ($this->parent && $this->config->getErrorBubbling()) { + $this->parent->addError($error); + } else { + $this->errors[] = $error; + } + + return $this; + } + + public function isSubmitted(): bool + { + return $this->submitted; + } + + public function isSynchronized(): bool + { + return null === $this->transformationFailure; + } + + public function getTransformationFailure(): ?TransformationFailedException + { + return $this->transformationFailure; + } + + public function isEmpty(): bool + { + foreach ($this->children as $child) { + if (!$child->isEmpty()) { + return false; + } + } + + if (null !== $isEmptyCallback = $this->config->getIsEmptyCallback()) { + return $isEmptyCallback($this->modelData); + } + + return FormUtil::isEmpty($this->modelData) + // arrays, countables + || (is_countable($this->modelData) && 0 === \count($this->modelData)) + // traversables that are not countable + || ($this->modelData instanceof \Traversable && 0 === iterator_count($this->modelData)); + } + + public function isValid(): bool + { + if (!$this->submitted) { + throw new LogicException('Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() and ensure that it\'s true before calling Form::isValid().'); + } + + if ($this->isDisabled()) { + return true; + } + + return 0 === \count($this->getErrors(true)); + } + + /** + * Returns the button that was used to submit the form. + */ + public function getClickedButton(): FormInterface|ClickableInterface|null + { + if ($this->clickedButton) { + return $this->clickedButton; + } + + return $this->parent && method_exists($this->parent, 'getClickedButton') ? $this->parent->getClickedButton() : null; + } + + public function getErrors(bool $deep = false, bool $flatten = true): FormErrorIterator + { + $errors = $this->errors; + + // Copy the errors of nested forms to the $errors array + if ($deep) { + foreach ($this as $child) { + /** @var FormInterface $child */ + if ($child->isSubmitted() && $child->isValid()) { + continue; + } + + $iterator = $child->getErrors(true, $flatten); + + if (0 === \count($iterator)) { + continue; + } + + if ($flatten) { + foreach ($iterator as $error) { + $errors[] = $error; + } + } else { + $errors[] = $iterator; + } + } + } + + return new FormErrorIterator($this, $errors); + } + + public function clearErrors(bool $deep = false): static + { + $this->errors = []; + + if ($deep) { + // Clear errors from children + foreach ($this as $child) { + if ($child instanceof ClearableErrorsInterface) { + $child->clearErrors(true); + } + } + } + + return $this; + } + + public function all(): array + { + return iterator_to_array($this->children); + } + + public function add(FormInterface|string $child, ?string $type = null, array $options = []): static + { + if ($this->submitted) { + throw new AlreadySubmittedException('You cannot add children to a submitted form.'); + } + + if (!$this->config->getCompound()) { + throw new LogicException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?'); + } + + if (!$child instanceof FormInterface) { + // Never initialize child forms automatically + $options['auto_initialize'] = false; + + if (null === $type && null === $this->config->getDataClass()) { + $type = TextType::class; + } + + if (null === $type) { + $child = $this->config->getFormFactory()->createForProperty($this->config->getDataClass(), $child, null, $options); + } else { + $child = $this->config->getFormFactory()->createNamed($child, $type, null, $options); + } + } elseif ($child->getConfig()->getAutoInitialize()) { + throw new RuntimeException(sprintf('Automatic initialization is only supported on root forms. You should set the "auto_initialize" option to false on the field "%s".', $child->getName())); + } + + $this->children[$child->getName()] = $child; + + $child->setParent($this); + + // If setData() is currently being called, there is no need to call + // mapDataToForms() here, as mapDataToForms() is called at the end + // of setData() anyway. Not doing this check leads to an endless + // recursion when initializing the form lazily and an event listener + // (such as ResizeFormListener) adds fields depending on the data: + // + // * setData() is called, the form is not initialized yet + // * add() is called by the listener (setData() is not complete, so + // the form is still not initialized) + // * getViewData() is called + // * setData() is called since the form is not initialized yet + // * ... endless recursion ... + // + // Also skip data mapping if setData() has not been called yet. + // setData() will be called upon form initialization and data mapping + // will take place by then. + if (!$this->lockSetData && $this->defaultDataSet && !$this->inheritData) { + $viewData = $this->getViewData(); + $this->config->getDataMapper()->mapDataToForms( + $viewData, + new \RecursiveIteratorIterator(new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child]))) + ); + } + + return $this; + } + + public function remove(string $name): static + { + if ($this->submitted) { + throw new AlreadySubmittedException('You cannot remove children from a submitted form.'); + } + + if (isset($this->children[$name])) { + if (!$this->children[$name]->isSubmitted()) { + $this->children[$name]->setParent(null); + } + + unset($this->children[$name]); + } + + return $this; + } + + public function has(string $name): bool + { + return isset($this->children[$name]); + } + + public function get(string $name): FormInterface + { + if (isset($this->children[$name])) { + return $this->children[$name]; + } + + throw new OutOfBoundsException(sprintf('Child "%s" does not exist.', $name)); + } + + /** + * Returns whether a child with the given name exists (implements the \ArrayAccess interface). + * + * @param string $name The name of the child + */ + public function offsetExists(mixed $name): bool + { + return $this->has($name); + } + + /** + * Returns the child with the given name (implements the \ArrayAccess interface). + * + * @param string $name The name of the child + * + * @throws OutOfBoundsException if the named child does not exist + */ + public function offsetGet(mixed $name): FormInterface + { + return $this->get($name); + } + + /** + * Adds a child to the form (implements the \ArrayAccess interface). + * + * @param string $name Ignored. The name of the child is used + * @param FormInterface $child The child to be added + * + * @throws AlreadySubmittedException if the form has already been submitted + * @throws LogicException when trying to add a child to a non-compound form + * + * @see self::add() + */ + public function offsetSet(mixed $name, mixed $child): void + { + $this->add($child); + } + + /** + * Removes the child with the given name from the form (implements the \ArrayAccess interface). + * + * @param string $name The name of the child to remove + * + * @throws AlreadySubmittedException if the form has already been submitted + */ + public function offsetUnset(mixed $name): void + { + $this->remove($name); + } + + /** + * Returns the iterator for this group. + * + * @return \Traversable + */ + public function getIterator(): \Traversable + { + return $this->children; + } + + /** + * Returns the number of form children (implements the \Countable interface). + */ + public function count(): int + { + return \count($this->children); + } + + public function createView(?FormView $parent = null): FormView + { + if (null === $parent && $this->parent) { + $parent = $this->parent->createView(); + } + + $type = $this->config->getType(); + $options = $this->config->getOptions(); + + // The methods createView(), buildView() and finishView() are called + // explicitly here in order to be able to override either of them + // in a custom resolved form type. + $view = $type->createView($this, $parent); + + $type->buildView($view, $this, $options); + + foreach ($this->children as $name => $child) { + $view->children[$name] = $child->createView($view); + } + + $this->sort($view->children); + + $type->finishView($view, $this, $options); + + return $view; + } + + /** + * Sorts view fields based on their priority value. + */ + private function sort(array &$children): void + { + $c = []; + $i = 0; + $needsSorting = false; + foreach ($children as $name => $child) { + $c[$name] = ['p' => $child->vars['priority'] ?? 0, 'i' => $i++]; + + if (0 !== $c[$name]['p']) { + $needsSorting = true; + } + } + + if (!$needsSorting) { + return; + } + + uksort($children, static fn ($a, $b): int => [$c[$b]['p'], $c[$a]['i']] <=> [$c[$a]['p'], $c[$b]['i']]); + } + + /** + * Normalizes the underlying data if a model transformer is set. + * + * @throws TransformationFailedException If the underlying data cannot be transformed to "normalized" format + */ + private function modelToNorm(mixed $value): mixed + { + try { + foreach ($this->config->getModelTransformers() as $transformer) { + $value = $transformer->transform($value); + } + } catch (TransformationFailedException $exception) { + throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + } + + return $value; + } + + /** + * Reverse transforms a value if a model transformer is set. + * + * @throws TransformationFailedException If the value cannot be transformed to "model" format + */ + private function normToModel(mixed $value): mixed + { + try { + $transformers = $this->config->getModelTransformers(); + + for ($i = \count($transformers) - 1; $i >= 0; --$i) { + $value = $transformers[$i]->reverseTransform($value); + } + } catch (TransformationFailedException $exception) { + throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + } + + return $value; + } + + /** + * Transforms the value if a view transformer is set. + * + * @throws TransformationFailedException If the normalized value cannot be transformed to "view" format + */ + private function normToView(mixed $value): mixed + { + // Scalar values should be converted to strings to + // facilitate differentiation between empty ("") and zero (0). + // Only do this for simple forms, as the resulting value in + // compound forms is passed to the data mapper and thus should + // not be converted to a string before. + if (!($transformers = $this->config->getViewTransformers()) && !$this->config->getCompound()) { + return null === $value || \is_scalar($value) ? (string) $value : $value; + } + + try { + foreach ($transformers as $transformer) { + $value = $transformer->transform($value); + } + } catch (TransformationFailedException $exception) { + throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + } + + return $value; + } + + /** + * Reverse transforms a value if a view transformer is set. + * + * @throws TransformationFailedException If the submitted value cannot be transformed to "normalized" format + */ + private function viewToNorm(mixed $value): mixed + { + if (!$transformers = $this->config->getViewTransformers()) { + return '' === $value ? null : $value; + } + + try { + for ($i = \count($transformers) - 1; $i >= 0; --$i) { + $value = $transformers[$i]->reverseTransform($value); + } + } catch (TransformationFailedException $exception) { + throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + } + + return $value; + } +} diff --git a/vendor/symfony/form/FormBuilder.php b/vendor/symfony/form/FormBuilder.php new file mode 100644 index 0000000..58bc9c8 --- /dev/null +++ b/vendor/symfony/form/FormBuilder.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\Extension\Core\Type\TextType; + +/** + * A builder for creating {@link Form} instances. + * + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormBuilderInterface +{ + /** + * The children of the form builder. + * + * @var FormBuilderInterface[] + */ + private array $children = []; + + /** + * The data of children who haven't been converted to form builders yet. + */ + private array $unresolvedChildren = []; + + public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, FormFactoryInterface $factory, array $options = []) + { + parent::__construct($name, $dataClass, $dispatcher, $options); + + $this->setFormFactory($factory); + } + + public function add(FormBuilderInterface|string $child, ?string $type = null, array $options = []): static + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + if ($child instanceof FormBuilderInterface) { + $this->children[$child->getName()] = $child; + + // In case an unresolved child with the same name exists + unset($this->unresolvedChildren[$child->getName()]); + + return $this; + } + + // Add to "children" to maintain order + $this->children[$child] = null; + $this->unresolvedChildren[$child] = [$type, $options]; + + return $this; + } + + public function create(string $name, ?string $type = null, array $options = []): FormBuilderInterface + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + if (null === $type && null === $this->getDataClass()) { + $type = TextType::class; + } + + if (null !== $type) { + return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options); + } + + return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options); + } + + public function get(string $name): FormBuilderInterface + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + if (isset($this->unresolvedChildren[$name])) { + return $this->resolveChild($name); + } + + if (isset($this->children[$name])) { + return $this->children[$name]; + } + + throw new InvalidArgumentException(sprintf('The child with the name "%s" does not exist.', $name)); + } + + public function remove(string $name): static + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + unset($this->unresolvedChildren[$name], $this->children[$name]); + + return $this; + } + + public function has(string $name): bool + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]); + } + + public function all(): array + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->resolveChildren(); + + return $this->children; + } + + public function count(): int + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + return \count($this->children); + } + + public function getFormConfig(): FormConfigInterface + { + /** @var $config self */ + $config = parent::getFormConfig(); + + $config->children = []; + $config->unresolvedChildren = []; + + return $config; + } + + public function getForm(): FormInterface + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->resolveChildren(); + + $form = new Form($this->getFormConfig()); + + foreach ($this->children as $child) { + // Automatic initialization is only supported on root forms + $form->add($child->setAutoInitialize(false)->getForm()); + } + + if ($this->getAutoInitialize()) { + // Automatically initialize the form if it is configured so + $form->initialize(); + } + + return $form; + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + if ($this->locked) { + throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + return new \ArrayIterator($this->all()); + } + + /** + * Converts an unresolved child into a {@link FormBuilderInterface} instance. + */ + private function resolveChild(string $name): FormBuilderInterface + { + [$type, $options] = $this->unresolvedChildren[$name]; + + unset($this->unresolvedChildren[$name]); + + return $this->children[$name] = $this->create($name, $type, $options); + } + + /** + * Converts all unresolved children into {@link FormBuilder} instances. + */ + private function resolveChildren(): void + { + foreach ($this->unresolvedChildren as $name => $info) { + $this->children[$name] = $this->create($name, $info[0], $info[1]); + } + + $this->unresolvedChildren = []; + } +} diff --git a/vendor/symfony/form/FormBuilderInterface.php b/vendor/symfony/form/FormBuilderInterface.php new file mode 100644 index 0000000..08d2930 --- /dev/null +++ b/vendor/symfony/form/FormBuilderInterface.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @author Bernhard Schussek + * + * @extends \Traversable + */ +interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuilderInterface +{ + /** + * Adds a new field to this group. A field must have a unique name within + * the group. Otherwise the existing field is overwritten. + * + * If you add a nested group, this group should also be represented in the + * object hierarchy. + * + * @param array $options + */ + public function add(string|self $child, ?string $type = null, array $options = []): static; + + /** + * Creates a form builder. + * + * @param string $name The name of the form or the name of the property + * @param string|null $type The type of the form or null if name is a property + * @param array $options + */ + public function create(string $name, ?string $type = null, array $options = []): self; + + /** + * Returns a child by name. + * + * @throws Exception\InvalidArgumentException if the given child does not exist + */ + public function get(string $name): self; + + /** + * Removes the field with the given name. + */ + public function remove(string $name): static; + + /** + * Returns whether a field with the given name exists. + */ + public function has(string $name): bool; + + /** + * Returns the children. + * + * @return array + */ + public function all(): array; + + /** + * Creates the form. + */ + public function getForm(): FormInterface; +} diff --git a/vendor/symfony/form/FormConfigBuilder.php b/vendor/symfony/form/FormConfigBuilder.php new file mode 100644 index 0000000..e0fb01d --- /dev/null +++ b/vendor/symfony/form/FormConfigBuilder.php @@ -0,0 +1,653 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * A basic form configuration. + * + * @author Bernhard Schussek + */ +class FormConfigBuilder implements FormConfigBuilderInterface +{ + protected bool $locked = false; + + /** + * Caches a globally unique {@link NativeRequestHandler} instance. + */ + private static NativeRequestHandler $nativeRequestHandler; + + private string $name; + private ?PropertyPathInterface $propertyPath = null; + private bool $mapped = true; + private bool $byReference = true; + private bool $inheritData = false; + private bool $compound = false; + private ResolvedFormTypeInterface $type; + private array $viewTransformers = []; + private array $modelTransformers = []; + private ?DataMapperInterface $dataMapper = null; + private bool $required = true; + private bool $disabled = false; + private bool $errorBubbling = false; + private mixed $emptyData = null; + private array $attributes = []; + private mixed $data = null; + private ?string $dataClass; + private bool $dataLocked = false; + private FormFactoryInterface $formFactory; + private string $action = ''; + private string $method = 'POST'; + private RequestHandlerInterface $requestHandler; + private bool $autoInitialize = false; + private ?\Closure $isEmptyCallback = null; + + /** + * Creates an empty form configuration. + * + * @param string|null $name The form name + * @param string|null $dataClass The class of the form's data + * + * @throws InvalidArgumentException if the data class is not a valid class or if + * the name contains invalid characters + */ + public function __construct( + ?string $name, + ?string $dataClass, + private EventDispatcherInterface $dispatcher, + private array $options = [], + ) { + self::validateName($name); + + if (null !== $dataClass && !class_exists($dataClass) && !interface_exists($dataClass, false)) { + throw new InvalidArgumentException(sprintf('Class "%s" not found. Is the "data_class" form option set correctly?', $dataClass)); + } + + $this->name = (string) $name; + $this->dataClass = $dataClass; + } + + public function addEventListener(string $eventName, callable $listener, int $priority = 0): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->dispatcher->addListener($eventName, $listener, $priority); + + return $this; + } + + public function addEventSubscriber(EventSubscriberInterface $subscriber): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->dispatcher->addSubscriber($subscriber); + + return $this; + } + + public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + if ($forcePrepend) { + array_unshift($this->viewTransformers, $viewTransformer); + } else { + $this->viewTransformers[] = $viewTransformer; + } + + return $this; + } + + public function resetViewTransformers(): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->viewTransformers = []; + + return $this; + } + + public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + if ($forceAppend) { + $this->modelTransformers[] = $modelTransformer; + } else { + array_unshift($this->modelTransformers, $modelTransformer); + } + + return $this; + } + + public function resetModelTransformers(): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->modelTransformers = []; + + return $this; + } + + public function getEventDispatcher(): EventDispatcherInterface + { + if ($this->locked && !$this->dispatcher instanceof ImmutableEventDispatcher) { + $this->dispatcher = new ImmutableEventDispatcher($this->dispatcher); + } + + return $this->dispatcher; + } + + public function getName(): string + { + return $this->name; + } + + public function getPropertyPath(): ?PropertyPathInterface + { + return $this->propertyPath; + } + + public function getMapped(): bool + { + return $this->mapped; + } + + public function getByReference(): bool + { + return $this->byReference; + } + + public function getInheritData(): bool + { + return $this->inheritData; + } + + public function getCompound(): bool + { + return $this->compound; + } + + public function getType(): ResolvedFormTypeInterface + { + return $this->type; + } + + public function getViewTransformers(): array + { + return $this->viewTransformers; + } + + public function getModelTransformers(): array + { + return $this->modelTransformers; + } + + public function getDataMapper(): ?DataMapperInterface + { + return $this->dataMapper; + } + + public function getRequired(): bool + { + return $this->required; + } + + public function getDisabled(): bool + { + return $this->disabled; + } + + public function getErrorBubbling(): bool + { + return $this->errorBubbling; + } + + public function getEmptyData(): mixed + { + return $this->emptyData; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + public function hasAttribute(string $name): bool + { + return \array_key_exists($name, $this->attributes); + } + + public function getAttribute(string $name, mixed $default = null): mixed + { + return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + public function getData(): mixed + { + return $this->data; + } + + public function getDataClass(): ?string + { + return $this->dataClass; + } + + public function getDataLocked(): bool + { + return $this->dataLocked; + } + + public function getFormFactory(): FormFactoryInterface + { + if (!isset($this->formFactory)) { + throw new BadMethodCallException('The form factory must be set before retrieving it.'); + } + + return $this->formFactory; + } + + public function getAction(): string + { + return $this->action; + } + + public function getMethod(): string + { + return $this->method; + } + + public function getRequestHandler(): RequestHandlerInterface + { + return $this->requestHandler ??= self::$nativeRequestHandler ??= new NativeRequestHandler(); + } + + public function getAutoInitialize(): bool + { + return $this->autoInitialize; + } + + public function getOptions(): array + { + return $this->options; + } + + public function hasOption(string $name): bool + { + return \array_key_exists($name, $this->options); + } + + public function getOption(string $name, mixed $default = null): mixed + { + return \array_key_exists($name, $this->options) ? $this->options[$name] : $default; + } + + public function getIsEmptyCallback(): ?callable + { + return $this->isEmptyCallback; + } + + /** + * @return $this + */ + public function setAttribute(string $name, mixed $value): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->attributes[$name] = $value; + + return $this; + } + + /** + * @return $this + */ + public function setAttributes(array $attributes): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->attributes = $attributes; + + return $this; + } + + /** + * @return $this + */ + public function setDataMapper(?DataMapperInterface $dataMapper): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->dataMapper = $dataMapper; + + return $this; + } + + /** + * @return $this + */ + public function setDisabled(bool $disabled): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->disabled = $disabled; + + return $this; + } + + /** + * @return $this + */ + public function setEmptyData(mixed $emptyData): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->emptyData = $emptyData; + + return $this; + } + + /** + * @return $this + */ + public function setErrorBubbling(bool $errorBubbling): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->errorBubbling = $errorBubbling; + + return $this; + } + + /** + * @return $this + */ + public function setRequired(bool $required): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->required = $required; + + return $this; + } + + /** + * @return $this + */ + public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + if (null !== $propertyPath && !$propertyPath instanceof PropertyPathInterface) { + $propertyPath = new PropertyPath($propertyPath); + } + + $this->propertyPath = $propertyPath; + + return $this; + } + + /** + * @return $this + */ + public function setMapped(bool $mapped): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->mapped = $mapped; + + return $this; + } + + /** + * @return $this + */ + public function setByReference(bool $byReference): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->byReference = $byReference; + + return $this; + } + + /** + * @return $this + */ + public function setInheritData(bool $inheritData): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->inheritData = $inheritData; + + return $this; + } + + /** + * @return $this + */ + public function setCompound(bool $compound): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->compound = $compound; + + return $this; + } + + /** + * @return $this + */ + public function setType(ResolvedFormTypeInterface $type): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->type = $type; + + return $this; + } + + /** + * @return $this + */ + public function setData(mixed $data): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->data = $data; + + return $this; + } + + /** + * @return $this + */ + public function setDataLocked(bool $locked): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->dataLocked = $locked; + + return $this; + } + + /** + * @return $this + */ + public function setFormFactory(FormFactoryInterface $formFactory): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->formFactory = $formFactory; + + return $this; + } + + /** + * @return $this + */ + public function setAction(string $action): static + { + if ($this->locked) { + throw new BadMethodCallException('The config builder cannot be modified anymore.'); + } + + $this->action = $action; + + return $this; + } + + /** + * @return $this + */ + public function setMethod(string $method): static + { + if ($this->locked) { + throw new BadMethodCallException('The config builder cannot be modified anymore.'); + } + + $this->method = strtoupper($method); + + return $this; + } + + /** + * @return $this + */ + public function setRequestHandler(RequestHandlerInterface $requestHandler): static + { + if ($this->locked) { + throw new BadMethodCallException('The config builder cannot be modified anymore.'); + } + + $this->requestHandler = $requestHandler; + + return $this; + } + + /** + * @return $this + */ + public function setAutoInitialize(bool $initialize): static + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + $this->autoInitialize = $initialize; + + return $this; + } + + public function getFormConfig(): FormConfigInterface + { + if ($this->locked) { + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); + } + + // This method should be idempotent, so clone the builder + $config = clone $this; + $config->locked = true; + + return $config; + } + + /** + * @return $this + */ + public function setIsEmptyCallback(?callable $isEmptyCallback): static + { + $this->isEmptyCallback = null === $isEmptyCallback ? null : $isEmptyCallback(...); + + return $this; + } + + /** + * Validates whether the given variable is a valid form name. + * + * @throws InvalidArgumentException if the name contains invalid characters + * + * @internal + */ + final public static function validateName(?string $name): void + { + if (!self::isValidName($name)) { + throw new InvalidArgumentException(sprintf('The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', $name)); + } + } + + /** + * Returns whether the given variable contains a valid form name. + * + * A name is accepted if it + * + * * is empty + * * starts with a letter, digit or underscore + * * contains only letters, digits, numbers, underscores ("_"), + * hyphens ("-") and colons (":") + */ + final public static function isValidName(?string $name): bool + { + return '' === $name || null === $name || preg_match('/^[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D', $name); + } +} diff --git a/vendor/symfony/form/FormConfigBuilderInterface.php b/vendor/symfony/form/FormConfigBuilderInterface.php new file mode 100644 index 0000000..6b22b9f --- /dev/null +++ b/vendor/symfony/form/FormConfigBuilderInterface.php @@ -0,0 +1,260 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * @author Bernhard Schussek + */ +interface FormConfigBuilderInterface extends FormConfigInterface +{ + /** + * Adds an event listener to an event on this form. + * + * @param int $priority The priority of the listener. Listeners + * with a higher priority are called before + * listeners with a lower priority. + * + * @return $this + */ + public function addEventListener(string $eventName, callable $listener, int $priority = 0): static; + + /** + * Adds an event subscriber for events on this form. + * + * @return $this + */ + public function addEventSubscriber(EventSubscriberInterface $subscriber): static; + + /** + * Appends / prepends a transformer to the view transformer chain. + * + * The transform method of the transformer is used to convert data from the + * normalized to the view format. + * The reverseTransform method of the transformer is used to convert from the + * view to the normalized format. + * + * @param bool $forcePrepend If set to true, prepend instead of appending + * + * @return $this + */ + public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false): static; + + /** + * Clears the view transformers. + * + * @return $this + */ + public function resetViewTransformers(): static; + + /** + * Prepends / appends a transformer to the normalization transformer chain. + * + * The transform method of the transformer is used to convert data from the + * model to the normalized format. + * The reverseTransform method of the transformer is used to convert from the + * normalized to the model format. + * + * @param bool $forceAppend If set to true, append instead of prepending + * + * @return $this + */ + public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false): static; + + /** + * Clears the normalization transformers. + * + * @return $this + */ + public function resetModelTransformers(): static; + + /** + * Sets the value for an attribute. + * + * @param mixed $value The value of the attribute + * + * @return $this + */ + public function setAttribute(string $name, mixed $value): static; + + /** + * Sets the attributes. + * + * @return $this + */ + public function setAttributes(array $attributes): static; + + /** + * Sets the data mapper used by the form. + * + * @return $this + */ + public function setDataMapper(?DataMapperInterface $dataMapper): static; + + /** + * Sets whether the form is disabled. + * + * @return $this + */ + public function setDisabled(bool $disabled): static; + + /** + * Sets the data used for the client data when no value is submitted. + * + * @param mixed $emptyData The empty data + * + * @return $this + */ + public function setEmptyData(mixed $emptyData): static; + + /** + * Sets whether errors bubble up to the parent. + * + * @return $this + */ + public function setErrorBubbling(bool $errorBubbling): static; + + /** + * Sets whether this field is required to be filled out when submitted. + * + * @return $this + */ + public function setRequired(bool $required): static; + + /** + * Sets the property path that the form should be mapped to. + * + * @param string|PropertyPathInterface|null $propertyPath The property path or null if the path should be set + * automatically based on the form's name + * + * @return $this + */ + public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): static; + + /** + * Sets whether the form should be mapped to an element of its + * parent's data. + * + * @return $this + */ + public function setMapped(bool $mapped): static; + + /** + * Sets whether the form's data should be modified by reference. + * + * @return $this + */ + public function setByReference(bool $byReference): static; + + /** + * Sets whether the form should read and write the data of its parent. + * + * @return $this + */ + public function setInheritData(bool $inheritData): static; + + /** + * Sets whether the form should be compound. + * + * @return $this + * + * @see FormConfigInterface::getCompound() + */ + public function setCompound(bool $compound): static; + + /** + * Sets the resolved type. + * + * @return $this + */ + public function setType(ResolvedFormTypeInterface $type): static; + + /** + * Sets the initial data of the form. + * + * @param mixed $data The data of the form in model format + * + * @return $this + */ + public function setData(mixed $data): static; + + /** + * Locks the form's data to the data passed in the configuration. + * + * A form with locked data is restricted to the data passed in + * this configuration. The data can only be modified then by + * submitting the form or using PRE_SET_DATA event. + * + * It means data passed to a factory method or mapped from the + * parent will be ignored. + * + * @return $this + */ + public function setDataLocked(bool $locked): static; + + /** + * Sets the form factory used for creating new forms. + * + * @return $this + */ + public function setFormFactory(FormFactoryInterface $formFactory): static; + + /** + * Sets the target URL of the form. + * + * @return $this + */ + public function setAction(string $action): static; + + /** + * Sets the HTTP method used by the form. + * + * @return $this + */ + public function setMethod(string $method): static; + + /** + * Sets the request handler used by the form. + * + * @return $this + */ + public function setRequestHandler(RequestHandlerInterface $requestHandler): static; + + /** + * Sets whether the form should be initialized automatically. + * + * Should be set to true only for root forms. + * + * @param bool $initialize True to initialize the form automatically, + * false to suppress automatic initialization. + * In the second case, you need to call + * {@link FormInterface::initialize()} manually. + * + * @return $this + */ + public function setAutoInitialize(bool $initialize): static; + + /** + * Builds and returns the form configuration. + */ + public function getFormConfig(): FormConfigInterface; + + /** + * Sets the callback that will be called to determine if the model + * data of the form is empty or not. + * + * @return $this + */ + public function setIsEmptyCallback(?callable $isEmptyCallback): static; +} diff --git a/vendor/symfony/form/FormConfigInterface.php b/vendor/symfony/form/FormConfigInterface.php new file mode 100644 index 0000000..93d1998 --- /dev/null +++ b/vendor/symfony/form/FormConfigInterface.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * The configuration of a {@link Form} object. + * + * @author Bernhard Schussek + */ +interface FormConfigInterface +{ + /** + * Returns the event dispatcher used to dispatch form events. + */ + public function getEventDispatcher(): EventDispatcherInterface; + + /** + * Returns the name of the form used as HTTP parameter. + */ + public function getName(): string; + + /** + * Returns the property path that the form should be mapped to. + */ + public function getPropertyPath(): ?PropertyPathInterface; + + /** + * Returns whether the form should be mapped to an element of its + * parent's data. + */ + public function getMapped(): bool; + + /** + * Returns whether the form's data should be modified by reference. + */ + public function getByReference(): bool; + + /** + * Returns whether the form should read and write the data of its parent. + */ + public function getInheritData(): bool; + + /** + * Returns whether the form is compound. + * + * This property is independent of whether the form actually has + * children. A form can be compound and have no children at all, like + * for example an empty collection form. + * The contrary is not possible, a form which is not compound + * cannot have any children. + */ + public function getCompound(): bool; + + /** + * Returns the resolved form type used to construct the form. + */ + public function getType(): ResolvedFormTypeInterface; + + /** + * Returns the view transformers of the form. + * + * @return DataTransformerInterface[] + */ + public function getViewTransformers(): array; + + /** + * Returns the model transformers of the form. + * + * @return DataTransformerInterface[] + */ + public function getModelTransformers(): array; + + /** + * Returns the data mapper of the compound form or null for a simple form. + */ + public function getDataMapper(): ?DataMapperInterface; + + /** + * Returns whether the form is required. + */ + public function getRequired(): bool; + + /** + * Returns whether the form is disabled. + */ + public function getDisabled(): bool; + + /** + * Returns whether errors attached to the form will bubble to its parent. + */ + public function getErrorBubbling(): bool; + + /** + * Used when the view data is empty on submission. + * + * When the form is compound it will also be used to map the + * children data. + * + * The empty data must match the view format as it will passed to the first view transformer's + * "reverseTransform" method. + */ + public function getEmptyData(): mixed; + + /** + * Returns additional attributes of the form. + */ + public function getAttributes(): array; + + /** + * Returns whether the attribute with the given name exists. + */ + public function hasAttribute(string $name): bool; + + /** + * Returns the value of the given attribute. + */ + public function getAttribute(string $name, mixed $default = null): mixed; + + /** + * Returns the initial data of the form. + */ + public function getData(): mixed; + + /** + * Returns the class of the view data or null if the data is scalar or an array. + */ + public function getDataClass(): ?string; + + /** + * Returns whether the form's data is locked. + * + * A form with locked data is restricted to the data passed in + * this configuration. The data can only be modified then by + * submitting the form. + */ + public function getDataLocked(): bool; + + /** + * Returns the form factory used for creating new forms. + */ + public function getFormFactory(): FormFactoryInterface; + + /** + * Returns the target URL of the form. + */ + public function getAction(): string; + + /** + * Returns the HTTP method used by the form. + */ + public function getMethod(): string; + + /** + * Returns the request handler used by the form. + */ + public function getRequestHandler(): RequestHandlerInterface; + + /** + * Returns whether the form should be initialized upon creation. + */ + public function getAutoInitialize(): bool; + + /** + * Returns all options passed during the construction of the form. + * + * @return array The passed options + */ + public function getOptions(): array; + + /** + * Returns whether a specific option exists. + */ + public function hasOption(string $name): bool; + + /** + * Returns the value of a specific option. + */ + public function getOption(string $name, mixed $default = null): mixed; + + /** + * Returns a callable that takes the model data as argument and that returns if it is empty or not. + */ + public function getIsEmptyCallback(): ?callable; +} diff --git a/vendor/symfony/form/FormError.php b/vendor/symfony/form/FormError.php new file mode 100644 index 0000000..335d9e2 --- /dev/null +++ b/vendor/symfony/form/FormError.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\BadMethodCallException; + +/** + * Wraps errors in forms. + * + * @author Bernhard Schussek + */ +class FormError +{ + protected string $messageTemplate; + + /** + * The form that spawned this error. + */ + private ?FormInterface $origin = null; + + /** + * Any array key in $messageParameters will be used as a placeholder in + * $messageTemplate. + * + * @param string $message The translated error message + * @param string|null $messageTemplate The template for the error message + * @param array $messageParameters The parameters that should be + * substituted in the message template + * @param int|null $messagePluralization The value for error message pluralization + * @param mixed $cause The cause of the error + * + * @see \Symfony\Component\Translation\Translator + */ + public function __construct( + private string $message, + ?string $messageTemplate = null, + protected array $messageParameters = [], + protected ?int $messagePluralization = null, + private mixed $cause = null, + ) { + $this->messageTemplate = $messageTemplate ?: $message; + } + + /** + * Returns the error message. + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * Returns the error message template. + */ + public function getMessageTemplate(): string + { + return $this->messageTemplate; + } + + /** + * Returns the parameters to be inserted in the message template. + */ + public function getMessageParameters(): array + { + return $this->messageParameters; + } + + /** + * Returns the value for error message pluralization. + */ + public function getMessagePluralization(): ?int + { + return $this->messagePluralization; + } + + /** + * Returns the cause of this error. + */ + public function getCause(): mixed + { + return $this->cause; + } + + /** + * Sets the form that caused this error. + * + * This method must only be called once. + * + * @throws BadMethodCallException If the method is called more than once + */ + public function setOrigin(FormInterface $origin): void + { + if (null !== $this->origin) { + throw new BadMethodCallException('setOrigin() must only be called once.'); + } + + $this->origin = $origin; + } + + /** + * Returns the form that caused this error. + */ + public function getOrigin(): ?FormInterface + { + return $this->origin; + } +} diff --git a/vendor/symfony/form/FormErrorIterator.php b/vendor/symfony/form/FormErrorIterator.php new file mode 100644 index 0000000..a614e72 --- /dev/null +++ b/vendor/symfony/form/FormErrorIterator.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Exception\OutOfBoundsException; +use Symfony\Component\Validator\ConstraintViolation; + +/** + * Iterates over the errors of a form. + * + * This class supports recursive iteration. In order to iterate recursively, + * pass a structure of {@link FormError} and {@link FormErrorIterator} objects + * to the $errors constructor argument. + * + * You can also wrap the iterator into a {@link \RecursiveIteratorIterator} to + * flatten the recursive structure into a flat list of errors. + * + * @author Bernhard Schussek + * + * @template T of FormError|FormErrorIterator + * + * @implements \ArrayAccess + * @implements \RecursiveIterator + * @implements \SeekableIterator + */ +class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \ArrayAccess, \Countable, \Stringable +{ + /** + * The prefix used for indenting nested error messages. + */ + public const INDENTATION = ' '; + + /** + * @var list + */ + private array $errors; + + /** + * @param list $errors + * + * @throws InvalidArgumentException If the errors are invalid + */ + public function __construct( + private FormInterface $form, + array $errors, + ) { + foreach ($errors as $error) { + if (!($error instanceof FormError || $error instanceof self)) { + throw new InvalidArgumentException(sprintf('The errors must be instances of "Symfony\Component\Form\FormError" or "%s". Got: "%s".', __CLASS__, get_debug_type($error))); + } + } + + $this->errors = $errors; + } + + /** + * Returns all iterated error messages as string. + */ + public function __toString(): string + { + $string = ''; + + foreach ($this->errors as $error) { + if ($error instanceof FormError) { + $string .= 'ERROR: '.$error->getMessage()."\n"; + } else { + /* @var self $error */ + $string .= $error->getForm()->getName().":\n"; + $string .= self::indent((string) $error); + } + } + + return $string; + } + + /** + * Returns the iterated form. + */ + public function getForm(): FormInterface + { + return $this->form; + } + + /** + * Returns the current element of the iterator. + * + * @return T An error or an iterator containing nested errors + */ + public function current(): FormError|self + { + return current($this->errors); + } + + /** + * Advances the iterator to the next position. + */ + public function next(): void + { + next($this->errors); + } + + /** + * Returns the current position of the iterator. + */ + public function key(): int + { + return key($this->errors); + } + + /** + * Returns whether the iterator's position is valid. + */ + public function valid(): bool + { + return null !== key($this->errors); + } + + /** + * Sets the iterator's position to the beginning. + * + * This method detects if errors have been added to the form since the + * construction of the iterator. + */ + public function rewind(): void + { + reset($this->errors); + } + + /** + * Returns whether a position exists in the iterator. + * + * @param int $position The position + */ + public function offsetExists(mixed $position): bool + { + return isset($this->errors[$position]); + } + + /** + * Returns the element at a position in the iterator. + * + * @param int $position The position + * + * @return T + * + * @throws OutOfBoundsException If the given position does not exist + */ + public function offsetGet(mixed $position): FormError|self + { + if (!isset($this->errors[$position])) { + throw new OutOfBoundsException('The offset '.$position.' does not exist.'); + } + + return $this->errors[$position]; + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function offsetSet(mixed $position, mixed $value): void + { + throw new BadMethodCallException('The iterator doesn\'t support modification of elements.'); + } + + /** + * Unsupported method. + * + * @throws BadMethodCallException + */ + public function offsetUnset(mixed $position): void + { + throw new BadMethodCallException('The iterator doesn\'t support modification of elements.'); + } + + /** + * Returns whether the current element of the iterator can be recursed + * into. + */ + public function hasChildren(): bool + { + return current($this->errors) instanceof self; + } + + public function getChildren(): self + { + if (!$this->hasChildren()) { + throw new LogicException(sprintf('The current element is not iterable. Use "%s" to get the current element.', self::class.'::current()')); + } + + /** @var self $children */ + $children = current($this->errors); + + return $children; + } + + /** + * Returns the number of elements in the iterator. + * + * Note that this is not the total number of errors, if the constructor + * parameter $deep was set to true! In that case, you should wrap the + * iterator into a {@link \RecursiveIteratorIterator} with the standard mode + * {@link \RecursiveIteratorIterator::LEAVES_ONLY} and count the result. + * + * $iterator = new \RecursiveIteratorIterator($form->getErrors(true)); + * $count = count(iterator_to_array($iterator)); + * + * Alternatively, set the constructor argument $flatten to true as well. + * + * $count = count($form->getErrors(true, true)); + */ + public function count(): int + { + return \count($this->errors); + } + + /** + * Sets the position of the iterator. + * + * @throws OutOfBoundsException If the position is invalid + */ + public function seek(int $position): void + { + if (!isset($this->errors[$position])) { + throw new OutOfBoundsException('The offset '.$position.' does not exist.'); + } + + reset($this->errors); + + while ($position !== key($this->errors)) { + next($this->errors); + } + } + + /** + * Creates iterator for errors with specific codes. + * + * @param string|string[] $codes The codes to find + */ + public function findByCodes(string|array $codes): static + { + $codes = (array) $codes; + $errors = []; + foreach ($this as $error) { + $cause = $error->getCause(); + if ($cause instanceof ConstraintViolation && \in_array($cause->getCode(), $codes, true)) { + $errors[] = $error; + } + } + + return new static($this->form, $errors); + } + + /** + * Utility function for indenting multi-line strings. + */ + private static function indent(string $string): string + { + return rtrim(self::INDENTATION.str_replace("\n", "\n".self::INDENTATION, $string), ' '); + } +} diff --git a/vendor/symfony/form/FormEvent.php b/vendor/symfony/form/FormEvent.php new file mode 100644 index 0000000..e6a3878 --- /dev/null +++ b/vendor/symfony/form/FormEvent.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Bernhard Schussek + */ +class FormEvent extends Event +{ + public function __construct( + private FormInterface $form, + protected mixed $data, + ) { + } + + /** + * Returns the form at the source of the event. + */ + public function getForm(): FormInterface + { + return $this->form; + } + + /** + * Returns the data associated with this event. + */ + public function getData(): mixed + { + return $this->data; + } + + /** + * Allows updating with some filtered data. + */ + public function setData(mixed $data): void + { + $this->data = $data; + } +} diff --git a/vendor/symfony/form/FormEvents.php b/vendor/symfony/form/FormEvents.php new file mode 100644 index 0000000..cf4d97f --- /dev/null +++ b/vendor/symfony/form/FormEvents.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Event\PostSetDataEvent; +use Symfony\Component\Form\Event\PostSubmitEvent; +use Symfony\Component\Form\Event\PreSetDataEvent; +use Symfony\Component\Form\Event\PreSubmitEvent; +use Symfony\Component\Form\Event\SubmitEvent; + +/** + * To learn more about how form events work check the documentation + * entry at {@link https://symfony.com/doc/any/components/form/form_events.html}. + * + * To learn how to dynamically modify forms using events check the cookbook + * entry at {@link https://symfony.com/doc/any/cookbook/form/dynamic_form_modification.html}. + * + * @author Bernhard Schussek + */ +final class FormEvents +{ + /** + * The PRE_SUBMIT event is dispatched at the beginning of the Form::submit() method. + * + * It can be used to: + * - Change data from the request, before submitting the data to the form. + * - Add or remove form fields, before submitting the data to the form. + * + * @Event("Symfony\Component\Form\Event\PreSubmitEvent") + */ + public const PRE_SUBMIT = 'form.pre_submit'; + + /** + * The SUBMIT event is dispatched after the Form::submit() method + * has changed the view data by the request data, or submitted and mapped + * the children if the form is compound, and after reverse transformation + * to normalized representation. + * + * It's also dispatched just before the Form::submit() method transforms back + * the normalized data to the model and view data. + * + * So at this stage children of compound forms are submitted and synchronized, unless + * their transformation failed, but a parent would still be at the PRE_SUBMIT level. + * + * Since the current form is not synchronized yet, it is still possible to add and + * remove fields. + * + * @Event("Symfony\Component\Form\Event\SubmitEvent") + */ + public const SUBMIT = 'form.submit'; + + /** + * The FormEvents::POST_SUBMIT event is dispatched at the very end of the Form::submit(). + * + * It this stage the model and view data may have been denormalized. Otherwise the form + * is desynchronized because transformation failed during submission. + * + * It can be used to fetch data after denormalization. + * + * The event attaches the current view data. To know whether this is the renormalized data + * or the invalid request data, call Form::isSynchronized() first. + * + * @Event("Symfony\Component\Form\Event\PostSubmitEvent") + */ + public const POST_SUBMIT = 'form.post_submit'; + + /** + * The FormEvents::PRE_SET_DATA event is dispatched at the beginning of the Form::setData() method. + * + * It can be used to: + * - Modify the data given during pre-population; + * - Keep synchronized the form depending on the data (adding or removing fields dynamically). + * + * @Event("Symfony\Component\Form\Event\PreSetDataEvent") + */ + public const PRE_SET_DATA = 'form.pre_set_data'; + + /** + * The FormEvents::POST_SET_DATA event is dispatched at the end of the Form::setData() method. + * + * This event can be used to modify the form depending on the final state of the underlying data + * accessible in every representation: model, normalized and view. + * + * @Event("Symfony\Component\Form\Event\PostSetDataEvent") + */ + public const POST_SET_DATA = 'form.post_set_data'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + PreSubmitEvent::class => self::PRE_SUBMIT, + SubmitEvent::class => self::SUBMIT, + PostSubmitEvent::class => self::POST_SUBMIT, + PreSetDataEvent::class => self::PRE_SET_DATA, + PostSetDataEvent::class => self::POST_SET_DATA, + ]; + + private function __construct() + { + } +} diff --git a/vendor/symfony/form/FormExtensionInterface.php b/vendor/symfony/form/FormExtensionInterface.php new file mode 100644 index 0000000..e540e18 --- /dev/null +++ b/vendor/symfony/form/FormExtensionInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Interface for extensions which provide types, type extensions and a guesser. + */ +interface FormExtensionInterface +{ + /** + * Returns a type by name. + * + * @param string $name The name of the type + * + * @throws Exception\InvalidArgumentException if the given type is not supported by this extension + */ + public function getType(string $name): FormTypeInterface; + + /** + * Returns whether the given type is supported. + * + * @param string $name The name of the type + */ + public function hasType(string $name): bool; + + /** + * Returns the extensions for the given type. + * + * @param string $name The name of the type + * + * @return FormTypeExtensionInterface[] + */ + public function getTypeExtensions(string $name): array; + + /** + * Returns whether this extension provides type extensions for the given type. + * + * @param string $name The name of the type + */ + public function hasTypeExtensions(string $name): bool; + + /** + * Returns the type guesser provided by this extension. + */ + public function getTypeGuesser(): ?FormTypeGuesserInterface; +} diff --git a/vendor/symfony/form/FormFactory.php b/vendor/symfony/form/FormFactory.php new file mode 100644 index 0000000..dcf7b36 --- /dev/null +++ b/vendor/symfony/form/FormFactory.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\TextType; + +class FormFactory implements FormFactoryInterface +{ + public function __construct( + private FormRegistryInterface $registry, + ) { + } + + public function create(string $type = FormType::class, mixed $data = null, array $options = []): FormInterface + { + return $this->createBuilder($type, $data, $options)->getForm(); + } + + public function createNamed(string $name, string $type = FormType::class, mixed $data = null, array $options = []): FormInterface + { + return $this->createNamedBuilder($name, $type, $data, $options)->getForm(); + } + + public function createForProperty(string $class, string $property, mixed $data = null, array $options = []): FormInterface + { + return $this->createBuilderForProperty($class, $property, $data, $options)->getForm(); + } + + public function createBuilder(string $type = FormType::class, mixed $data = null, array $options = []): FormBuilderInterface + { + return $this->createNamedBuilder($this->registry->getType($type)->getBlockPrefix(), $type, $data, $options); + } + + public function createNamedBuilder(string $name, string $type = FormType::class, mixed $data = null, array $options = []): FormBuilderInterface + { + if (null !== $data && !\array_key_exists('data', $options)) { + $options['data'] = $data; + } + + $type = $this->registry->getType($type); + + $builder = $type->createBuilder($this, $name, $options); + + // Explicitly call buildForm() in order to be able to override either + // createBuilder() or buildForm() in the resolved form type + $type->buildForm($builder, $builder->getOptions()); + + return $builder; + } + + public function createBuilderForProperty(string $class, string $property, mixed $data = null, array $options = []): FormBuilderInterface + { + if (null === $guesser = $this->registry->getTypeGuesser()) { + return $this->createNamedBuilder($property, TextType::class, $data, $options); + } + + $typeGuess = $guesser->guessType($class, $property); + $maxLengthGuess = $guesser->guessMaxLength($class, $property); + $requiredGuess = $guesser->guessRequired($class, $property); + $patternGuess = $guesser->guessPattern($class, $property); + + $type = $typeGuess ? $typeGuess->getType() : TextType::class; + + $maxLength = $maxLengthGuess?->getValue(); + $pattern = $patternGuess?->getValue(); + + if (null !== $pattern) { + $options = array_replace_recursive(['attr' => ['pattern' => $pattern]], $options); + } + + if (null !== $maxLength) { + $options = array_replace_recursive(['attr' => ['maxlength' => $maxLength]], $options); + } + + if ($requiredGuess) { + $options = array_merge(['required' => $requiredGuess->getValue()], $options); + } + + // user options may override guessed options + if ($typeGuess) { + $attrs = []; + $typeGuessOptions = $typeGuess->getOptions(); + if (isset($typeGuessOptions['attr']) && isset($options['attr'])) { + $attrs = ['attr' => array_merge($typeGuessOptions['attr'], $options['attr'])]; + } + + $options = array_merge($typeGuessOptions, $options, $attrs); + } + + return $this->createNamedBuilder($property, $type, $data, $options); + } +} diff --git a/vendor/symfony/form/FormFactoryBuilder.php b/vendor/symfony/form/FormFactoryBuilder.php new file mode 100644 index 0000000..90e3bf2 --- /dev/null +++ b/vendor/symfony/form/FormFactoryBuilder.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Extension\Core\CoreExtension; + +/** + * The default implementation of FormFactoryBuilderInterface. + * + * @author Bernhard Schussek + */ +class FormFactoryBuilder implements FormFactoryBuilderInterface +{ + private ResolvedFormTypeFactoryInterface $resolvedTypeFactory; + + /** + * @var FormExtensionInterface[] + */ + private array $extensions = []; + + /** + * @var FormTypeInterface[] + */ + private array $types = []; + + /** + * @var FormTypeExtensionInterface[][] + */ + private array $typeExtensions = []; + + /** + * @var FormTypeGuesserInterface[] + */ + private array $typeGuessers = []; + + public function __construct( + private bool $forceCoreExtension = false, + ) { + } + + public function setResolvedTypeFactory(ResolvedFormTypeFactoryInterface $resolvedTypeFactory): static + { + $this->resolvedTypeFactory = $resolvedTypeFactory; + + return $this; + } + + public function addExtension(FormExtensionInterface $extension): static + { + $this->extensions[] = $extension; + + return $this; + } + + public function addExtensions(array $extensions): static + { + $this->extensions = array_merge($this->extensions, $extensions); + + return $this; + } + + public function addType(FormTypeInterface $type): static + { + $this->types[] = $type; + + return $this; + } + + public function addTypes(array $types): static + { + foreach ($types as $type) { + $this->types[] = $type; + } + + return $this; + } + + public function addTypeExtension(FormTypeExtensionInterface $typeExtension): static + { + foreach ($typeExtension::getExtendedTypes() as $extendedType) { + $this->typeExtensions[$extendedType][] = $typeExtension; + } + + return $this; + } + + public function addTypeExtensions(array $typeExtensions): static + { + foreach ($typeExtensions as $typeExtension) { + $this->addTypeExtension($typeExtension); + } + + return $this; + } + + public function addTypeGuesser(FormTypeGuesserInterface $typeGuesser): static + { + $this->typeGuessers[] = $typeGuesser; + + return $this; + } + + public function addTypeGuessers(array $typeGuessers): static + { + $this->typeGuessers = array_merge($this->typeGuessers, $typeGuessers); + + return $this; + } + + public function getFormFactory(): FormFactoryInterface + { + $extensions = $this->extensions; + + if ($this->forceCoreExtension) { + $hasCoreExtension = false; + + foreach ($extensions as $extension) { + if ($extension instanceof CoreExtension) { + $hasCoreExtension = true; + break; + } + } + + if (!$hasCoreExtension) { + array_unshift($extensions, new CoreExtension()); + } + } + + if (\count($this->types) > 0 || \count($this->typeExtensions) > 0 || \count($this->typeGuessers) > 0) { + if (\count($this->typeGuessers) > 1) { + $typeGuesser = new FormTypeGuesserChain($this->typeGuessers); + } else { + $typeGuesser = $this->typeGuessers[0] ?? null; + } + + $extensions[] = new PreloadedExtension($this->types, $this->typeExtensions, $typeGuesser); + } + + $registry = new FormRegistry($extensions, $this->resolvedTypeFactory ?? new ResolvedFormTypeFactory()); + + return new FormFactory($registry); + } +} diff --git a/vendor/symfony/form/FormFactoryBuilderInterface.php b/vendor/symfony/form/FormFactoryBuilderInterface.php new file mode 100644 index 0000000..70bdf50 --- /dev/null +++ b/vendor/symfony/form/FormFactoryBuilderInterface.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A builder for FormFactoryInterface objects. + * + * @author Bernhard Schussek + */ +interface FormFactoryBuilderInterface +{ + /** + * Sets the factory for creating ResolvedFormTypeInterface instances. + * + * @return $this + */ + public function setResolvedTypeFactory(ResolvedFormTypeFactoryInterface $resolvedTypeFactory): static; + + /** + * Adds an extension to be loaded by the factory. + * + * @return $this + */ + public function addExtension(FormExtensionInterface $extension): static; + + /** + * Adds a list of extensions to be loaded by the factory. + * + * @param FormExtensionInterface[] $extensions The extensions + * + * @return $this + */ + public function addExtensions(array $extensions): static; + + /** + * Adds a form type to the factory. + * + * @return $this + */ + public function addType(FormTypeInterface $type): static; + + /** + * Adds a list of form types to the factory. + * + * @param FormTypeInterface[] $types The form types + * + * @return $this + */ + public function addTypes(array $types): static; + + /** + * Adds a form type extension to the factory. + * + * @return $this + */ + public function addTypeExtension(FormTypeExtensionInterface $typeExtension): static; + + /** + * Adds a list of form type extensions to the factory. + * + * @param FormTypeExtensionInterface[] $typeExtensions The form type extensions + * + * @return $this + */ + public function addTypeExtensions(array $typeExtensions): static; + + /** + * Adds a type guesser to the factory. + * + * @return $this + */ + public function addTypeGuesser(FormTypeGuesserInterface $typeGuesser): static; + + /** + * Adds a list of type guessers to the factory. + * + * @param FormTypeGuesserInterface[] $typeGuessers The type guessers + * + * @return $this + */ + public function addTypeGuessers(array $typeGuessers): static; + + /** + * Builds and returns the factory. + */ + public function getFormFactory(): FormFactoryInterface; +} diff --git a/vendor/symfony/form/FormFactoryInterface.php b/vendor/symfony/form/FormFactoryInterface.php new file mode 100644 index 0000000..0f311c0 --- /dev/null +++ b/vendor/symfony/form/FormFactoryInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * Allows creating a form based on a name, a class or a property. + * + * @author Bernhard Schussek + */ +interface FormFactoryInterface +{ + /** + * Returns a form. + * + * @see createBuilder() + * + * @param mixed $data The initial data + * + * @throws InvalidOptionsException if any given option is not applicable to the given type + */ + public function create(string $type = FormType::class, mixed $data = null, array $options = []): FormInterface; + + /** + * Returns a form. + * + * @see createNamedBuilder() + * + * @param mixed $data The initial data + * + * @throws InvalidOptionsException if any given option is not applicable to the given type + */ + public function createNamed(string $name, string $type = FormType::class, mixed $data = null, array $options = []): FormInterface; + + /** + * Returns a form for a property of a class. + * + * @see createBuilderForProperty() + * + * @param string $class The fully qualified class name + * @param string $property The name of the property to guess for + * @param mixed $data The initial data + * + * @throws InvalidOptionsException if any given option is not applicable to the form type + */ + public function createForProperty(string $class, string $property, mixed $data = null, array $options = []): FormInterface; + + /** + * Returns a form builder. + * + * @param mixed $data The initial data + * + * @throws InvalidOptionsException if any given option is not applicable to the given type + */ + public function createBuilder(string $type = FormType::class, mixed $data = null, array $options = []): FormBuilderInterface; + + /** + * Returns a form builder. + * + * @param mixed $data The initial data + * + * @throws InvalidOptionsException if any given option is not applicable to the given type + */ + public function createNamedBuilder(string $name, string $type = FormType::class, mixed $data = null, array $options = []): FormBuilderInterface; + + /** + * Returns a form builder for a property of a class. + * + * If any of the 'required' and type options can be guessed, + * and are not provided in the options argument, the guessed value is used. + * + * @param string $class The fully qualified class name + * @param string $property The name of the property to guess for + * @param mixed $data The initial data + * + * @throws InvalidOptionsException if any given option is not applicable to the form type + */ + public function createBuilderForProperty(string $class, string $property, mixed $data = null, array $options = []): FormBuilderInterface; +} diff --git a/vendor/symfony/form/FormInterface.php b/vendor/symfony/form/FormInterface.php new file mode 100644 index 0000000..23392c4 --- /dev/null +++ b/vendor/symfony/form/FormInterface.php @@ -0,0 +1,289 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * A form group bundling multiple forms in a hierarchical structure. + * + * @author Bernhard Schussek + * + * @extends \ArrayAccess + * @extends \Traversable + */ +interface FormInterface extends \ArrayAccess, \Traversable, \Countable +{ + /** + * Sets the parent form. + * + * @param FormInterface|null $parent The parent form or null if it's the root + * + * @return $this + * + * @throws Exception\AlreadySubmittedException if the form has already been submitted + * @throws Exception\LogicException when trying to set a parent for a form with + * an empty name + */ + public function setParent(?self $parent): static; + + /** + * Returns the parent form. + */ + public function getParent(): ?self; + + /** + * Adds or replaces a child to the form. + * + * @param FormInterface|string $child The FormInterface instance or the name of the child + * @param string|null $type The child's type, if a name was passed + * @param array $options The child's options, if a name was passed + * + * @return $this + * + * @throws Exception\AlreadySubmittedException if the form has already been submitted + * @throws Exception\LogicException when trying to add a child to a non-compound form + * @throws Exception\UnexpectedTypeException if $child or $type has an unexpected type + */ + public function add(self|string $child, ?string $type = null, array $options = []): static; + + /** + * Returns the child with the given name. + * + * @throws Exception\OutOfBoundsException if the named child does not exist + */ + public function get(string $name): self; + + /** + * Returns whether a child with the given name exists. + */ + public function has(string $name): bool; + + /** + * Removes a child from the form. + * + * @return $this + * + * @throws Exception\AlreadySubmittedException if the form has already been submitted + */ + public function remove(string $name): static; + + /** + * Returns all children in this group. + * + * @return self[] + */ + public function all(): array; + + /** + * Returns the errors of this form. + * + * @param bool $deep Whether to include errors of child forms as well + * @param bool $flatten Whether to flatten the list of errors in case + * $deep is set to true + */ + public function getErrors(bool $deep = false, bool $flatten = true): FormErrorIterator; + + /** + * Updates the form with default model data. + * + * @param mixed $modelData The data formatted as expected for the underlying object + * + * @return $this + * + * @throws Exception\AlreadySubmittedException If the form has already been submitted + * @throws Exception\LogicException if the view data does not match the expected type + * according to {@link FormConfigInterface::getDataClass} + * @throws Exception\RuntimeException If listeners try to call setData in a cycle or if + * the form inherits data from its parent + * @throws Exception\TransformationFailedException if the synchronization failed + */ + public function setData(mixed $modelData): static; + + /** + * Returns the model data in the format needed for the underlying object. + * + * @return mixed When the field is not submitted, the default data is returned. + * When the field is submitted, the default data has been bound + * to the submitted view data. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent + */ + public function getData(): mixed; + + /** + * Returns the normalized data of the field, used as internal bridge + * between model data and view data. + * + * @return mixed When the field is not submitted, the default data is returned. + * When the field is submitted, the normalized submitted data + * is returned if the field is synchronized with the view data, + * null otherwise. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent + */ + public function getNormData(): mixed; + + /** + * Returns the view data of the field. + * + * It may be defined by {@link FormConfigInterface::getDataClass}. + * + * There are two cases: + * + * - When the form is compound the view data is mapped to the children. + * Each child will use its mapped data as model data. + * It can be an array, an object or null. + * + * - When the form is simple its view data is used to be bound + * to the submitted data. + * It can be a string or an array. + * + * In both cases the view data is the actual altered data on submission. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent + */ + public function getViewData(): mixed; + + /** + * Returns the extra submitted data. + * + * @return array The submitted data which do not belong to a child + */ + public function getExtraData(): array; + + /** + * Returns the form's configuration. + */ + public function getConfig(): FormConfigInterface; + + /** + * Returns whether the form is submitted. + */ + public function isSubmitted(): bool; + + /** + * Returns the name by which the form is identified in forms. + * + * Only root forms are allowed to have an empty name. + */ + public function getName(): string; + + /** + * Returns the property path that the form is mapped to. + */ + public function getPropertyPath(): ?PropertyPathInterface; + + /** + * Adds an error to this form. + * + * @return $this + */ + public function addError(FormError $error): static; + + /** + * Returns whether the form and all children are valid. + * + * @throws Exception\LogicException if the form is not submitted + */ + public function isValid(): bool; + + /** + * Returns whether the form is required to be filled out. + * + * If the form has a parent and the parent is not required, this method + * will always return false. Otherwise the value set with setRequired() + * is returned. + */ + public function isRequired(): bool; + + /** + * Returns whether this form is disabled. + * + * The content of a disabled form is displayed, but not allowed to be + * modified. The validation of modified disabled forms should fail. + * + * Forms whose parents are disabled are considered disabled regardless of + * their own state. + */ + public function isDisabled(): bool; + + /** + * Returns whether the form is empty. + */ + public function isEmpty(): bool; + + /** + * Returns whether the data in the different formats is synchronized. + * + * If the data is not synchronized, you can get the transformation failure + * by calling {@link getTransformationFailure()}. + * + * If the form is not submitted, this method always returns true. + */ + public function isSynchronized(): bool; + + /** + * Returns the data transformation failure, if any, during submission. + */ + public function getTransformationFailure(): ?Exception\TransformationFailedException; + + /** + * Initializes the form tree. + * + * Should be called on the root form after constructing the tree. + * + * @return $this + * + * @throws Exception\RuntimeException If the form is not the root + */ + public function initialize(): static; + + /** + * Inspects the given request and calls {@link submit()} if the form was + * submitted. + * + * Internally, the request is forwarded to the configured + * {@link RequestHandlerInterface} instance, which determines whether to + * submit the form or not. + * + * @return $this + */ + public function handleRequest(mixed $request = null): static; + + /** + * Submits data to the form. + * + * @param string|array|null $submittedData The submitted data + * @param bool $clearMissing Whether to set fields to NULL + * when they are missing in the + * submitted data. This argument + * is only used in compound form + * + * @return $this + * + * @throws Exception\AlreadySubmittedException if the form has already been submitted + */ + public function submit(string|array|null $submittedData, bool $clearMissing = true): static; + + /** + * Returns the root of the form tree. + */ + public function getRoot(): self; + + /** + * Returns whether the field is the root of the form tree. + */ + public function isRoot(): bool; + + public function createView(?FormView $parent = null): FormView; +} diff --git a/vendor/symfony/form/FormRegistry.php b/vendor/symfony/form/FormRegistry.php new file mode 100644 index 0000000..95a0077 --- /dev/null +++ b/vendor/symfony/form/FormRegistry.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\ExceptionInterface; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * The central registry of the Form component. + * + * @author Bernhard Schussek + */ +class FormRegistry implements FormRegistryInterface +{ + /** + * @var FormExtensionInterface[] + */ + private array $extensions = []; + + /** + * @var ResolvedFormTypeInterface[] + */ + private array $types = []; + + private FormTypeGuesserInterface|false|null $guesser = false; + private array $checkedTypes = []; + + /** + * @param FormExtensionInterface[] $extensions + * + * @throws UnexpectedTypeException if any extension does not implement FormExtensionInterface + */ + public function __construct( + array $extensions, + private ResolvedFormTypeFactoryInterface $resolvedTypeFactory, + ) { + foreach ($extensions as $extension) { + if (!$extension instanceof FormExtensionInterface) { + throw new UnexpectedTypeException($extension, FormExtensionInterface::class); + } + } + + $this->extensions = $extensions; + } + + public function getType(string $name): ResolvedFormTypeInterface + { + if (!isset($this->types[$name])) { + $type = null; + + foreach ($this->extensions as $extension) { + if ($extension->hasType($name)) { + $type = $extension->getType($name); + break; + } + } + + if (!$type) { + // Support fully-qualified class names + if (!class_exists($name)) { + throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not exist.', $name)); + } + if (!is_subclass_of($name, FormTypeInterface::class)) { + throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not implement "Symfony\Component\Form\FormTypeInterface".', $name)); + } + + $type = new $name(); + } + + $this->types[$name] = $this->resolveType($type); + } + + return $this->types[$name]; + } + + /** + * Wraps a type into a ResolvedFormTypeInterface implementation and connects it with its parent type. + */ + private function resolveType(FormTypeInterface $type): ResolvedFormTypeInterface + { + $parentType = $type->getParent(); + $fqcn = $type::class; + + if (isset($this->checkedTypes[$fqcn])) { + $types = implode(' > ', array_merge(array_keys($this->checkedTypes), [$fqcn])); + throw new LogicException(sprintf('Circular reference detected for form type "%s" (%s).', $fqcn, $types)); + } + + $this->checkedTypes[$fqcn] = true; + + $typeExtensions = []; + try { + foreach ($this->extensions as $extension) { + $typeExtensions[] = $extension->getTypeExtensions($fqcn); + } + + return $this->resolvedTypeFactory->createResolvedType( + $type, + array_merge([], ...$typeExtensions), + $parentType ? $this->getType($parentType) : null + ); + } finally { + unset($this->checkedTypes[$fqcn]); + } + } + + public function hasType(string $name): bool + { + if (isset($this->types[$name])) { + return true; + } + + try { + $this->getType($name); + } catch (ExceptionInterface) { + return false; + } + + return true; + } + + public function getTypeGuesser(): ?FormTypeGuesserInterface + { + if (false === $this->guesser) { + $guessers = []; + + foreach ($this->extensions as $extension) { + $guesser = $extension->getTypeGuesser(); + + if ($guesser) { + $guessers[] = $guesser; + } + } + + $this->guesser = $guessers ? new FormTypeGuesserChain($guessers) : null; + } + + return $this->guesser; + } + + public function getExtensions(): array + { + return $this->extensions; + } +} diff --git a/vendor/symfony/form/FormRegistryInterface.php b/vendor/symfony/form/FormRegistryInterface.php new file mode 100644 index 0000000..5c76b5c --- /dev/null +++ b/vendor/symfony/form/FormRegistryInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * The central registry of the Form component. + * + * @author Bernhard Schussek + */ +interface FormRegistryInterface +{ + /** + * Returns a form type by name. + * + * This method registers the type extensions from the form extensions. + * + * @throws Exception\InvalidArgumentException if the type cannot be retrieved from any extension + */ + public function getType(string $name): ResolvedFormTypeInterface; + + /** + * Returns whether the given form type is supported. + */ + public function hasType(string $name): bool; + + /** + * Returns the guesser responsible for guessing types. + */ + public function getTypeGuesser(): ?FormTypeGuesserInterface; + + /** + * Returns the extensions loaded by the framework. + * + * @return FormExtensionInterface[] + */ + public function getExtensions(): array; +} diff --git a/vendor/symfony/form/FormRenderer.php b/vendor/symfony/form/FormRenderer.php new file mode 100644 index 0000000..a9ffd4f --- /dev/null +++ b/vendor/symfony/form/FormRenderer.php @@ -0,0 +1,283 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Twig\Environment; + +/** + * Renders a form into HTML using a rendering engine. + * + * @author Bernhard Schussek + */ +class FormRenderer implements FormRendererInterface +{ + public const CACHE_KEY_VAR = 'unique_block_prefix'; + + private array $blockNameHierarchyMap = []; + private array $hierarchyLevelMap = []; + private array $variableStack = []; + + public function __construct( + private FormRendererEngineInterface $engine, + private ?CsrfTokenManagerInterface $csrfTokenManager = null, + ) { + } + + public function getEngine(): FormRendererEngineInterface + { + return $this->engine; + } + + public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void + { + $this->engine->setTheme($view, $themes, $useDefaultThemes); + } + + public function renderCsrfToken(string $tokenId): string + { + if (null === $this->csrfTokenManager) { + throw new BadMethodCallException('CSRF tokens can only be generated if a CsrfTokenManagerInterface is injected in FormRenderer::__construct(). Try running "composer require symfony/security-csrf".'); + } + + return $this->csrfTokenManager->getToken($tokenId)->getValue(); + } + + public function renderBlock(FormView $view, string $blockName, array $variables = []): string + { + $resource = $this->engine->getResourceForBlockName($view, $blockName); + + if (!$resource) { + throw new LogicException(sprintf('No block "%s" found while rendering the form.', $blockName)); + } + + $viewCacheKey = $view->vars[self::CACHE_KEY_VAR]; + + // The variables are cached globally for a view (instead of for the + // current suffix) + if (!isset($this->variableStack[$viewCacheKey])) { + $this->variableStack[$viewCacheKey] = []; + + // The default variable scope contains all view variables, merged with + // the variables passed explicitly to the helper + $scopeVariables = $view->vars; + + $varInit = true; + } else { + // Reuse the current scope and merge it with the explicitly passed variables + $scopeVariables = end($this->variableStack[$viewCacheKey]); + + $varInit = false; + } + + // Merge the passed with the existing attributes + if (isset($variables['attr']) && isset($scopeVariables['attr'])) { + $variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']); + } + + // Merge the passed with the exist *label* attributes + if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) { + $variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']); + } + + // Do not use array_replace_recursive(), otherwise array variables + // cannot be overwritten + $variables = array_replace($scopeVariables, $variables); + + $this->variableStack[$viewCacheKey][] = $variables; + + // Do the rendering + $html = $this->engine->renderBlock($view, $resource, $blockName, $variables); + + // Clear the stack + array_pop($this->variableStack[$viewCacheKey]); + + if ($varInit) { + unset($this->variableStack[$viewCacheKey]); + } + + return $html; + } + + public function searchAndRenderBlock(FormView $view, string $blockNameSuffix, array $variables = []): string + { + $renderOnlyOnce = 'row' === $blockNameSuffix || 'widget' === $blockNameSuffix; + + if ($renderOnlyOnce && $view->isRendered()) { + // This is not allowed, because it would result in rendering same IDs multiple times, which is not valid. + throw new BadMethodCallException(sprintf('Field "%s" has already been rendered, save the result of previous render call to a variable and output that instead.', $view->vars['name'])); + } + + // The cache key for storing the variables and types + $viewCacheKey = $view->vars[self::CACHE_KEY_VAR]; + $viewAndSuffixCacheKey = $viewCacheKey.$blockNameSuffix; + + // In templates, we have to deal with two kinds of block hierarchies: + // + // +---------+ +---------+ + // | Theme B | -------> | Theme A | + // +---------+ +---------+ + // + // form_widget -------> form_widget + // ^ + // | + // choice_widget -----> choice_widget + // + // The first kind of hierarchy is the theme hierarchy. This allows to + // override the block "choice_widget" from Theme A in the extending + // Theme B. This kind of inheritance needs to be supported by the + // template engine and, for example, offers "parent()" or similar + // functions to fall back from the custom to the parent implementation. + // + // The second kind of hierarchy is the form type hierarchy. This allows + // to implement a custom "choice_widget" block (no matter in which theme), + // or to fallback to the block of the parent type, which would be + // "form_widget" in this example (again, no matter in which theme). + // If the designer wants to explicitly fallback to "form_widget" in their + // custom "choice_widget", for example because they only want to wrap + // a
    around the original implementation, they can call the + // widget() function again to render the block for the parent type. + // + // The second kind is implemented in the following blocks. + if (!isset($this->blockNameHierarchyMap[$viewAndSuffixCacheKey])) { + // INITIAL CALL + // Calculate the hierarchy of template blocks and start on + // the bottom level of the hierarchy (= "__
    " block) + $blockNameHierarchy = []; + foreach ($view->vars['block_prefixes'] as $blockNamePrefix) { + $blockNameHierarchy[] = $blockNamePrefix.'_'.$blockNameSuffix; + } + $hierarchyLevel = \count($blockNameHierarchy) - 1; + + $hierarchyInit = true; + } else { + // RECURSIVE CALL + // If a block recursively calls searchAndRenderBlock() again, resume rendering + // using the parent type in the hierarchy. + $blockNameHierarchy = $this->blockNameHierarchyMap[$viewAndSuffixCacheKey]; + $hierarchyLevel = $this->hierarchyLevelMap[$viewAndSuffixCacheKey] - 1; + + $hierarchyInit = false; + } + + // The variables are cached globally for a view (instead of for the + // current suffix) + if (!isset($this->variableStack[$viewCacheKey])) { + $this->variableStack[$viewCacheKey] = []; + + // The default variable scope contains all view variables, merged with + // the variables passed explicitly to the helper + $scopeVariables = $view->vars; + + $varInit = true; + } else { + // Reuse the current scope and merge it with the explicitly passed variables + $scopeVariables = end($this->variableStack[$viewCacheKey]); + + $varInit = false; + } + + // Load the resource where this block can be found + $resource = $this->engine->getResourceForBlockNameHierarchy($view, $blockNameHierarchy, $hierarchyLevel); + + // Update the current hierarchy level to the one at which the resource was + // found. For example, if looking for "choice_widget", but only a resource + // is found for its parent "form_widget", then the level is updated here + // to the parent level. + $hierarchyLevel = $this->engine->getResourceHierarchyLevel($view, $blockNameHierarchy, $hierarchyLevel); + + // The actually existing block name in $resource + $blockName = $blockNameHierarchy[$hierarchyLevel]; + + // Escape if no resource exists for this block + if (!$resource) { + if (\count($blockNameHierarchy) !== \count(array_unique($blockNameHierarchy))) { + throw new LogicException(sprintf('Unable to render the form because the block names array contains duplicates: "%s".', implode('", "', array_reverse($blockNameHierarchy)))); + } + + throw new LogicException(sprintf('Unable to render the form as none of the following blocks exist: "%s".', implode('", "', array_reverse($blockNameHierarchy)))); + } + + // Merge the passed with the existing attributes + if (isset($variables['attr']) && isset($scopeVariables['attr'])) { + $variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']); + } + + // Merge the passed with the exist *label* attributes + if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) { + $variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']); + } + + // Do not use array_replace_recursive(), otherwise array variables + // cannot be overwritten + $variables = array_replace($scopeVariables, $variables); + + // In order to make recursive calls possible, we need to store the block hierarchy, + // the current level of the hierarchy and the variables so that this method can + // resume rendering one level higher of the hierarchy when it is called recursively. + // + // We need to store these values in maps (associative arrays) because within a + // call to widget() another call to widget() can be made, but for a different view + // object. These nested calls should not override each other. + $this->blockNameHierarchyMap[$viewAndSuffixCacheKey] = $blockNameHierarchy; + $this->hierarchyLevelMap[$viewAndSuffixCacheKey] = $hierarchyLevel; + + // We also need to store the variables for the view so that we can render other + // blocks for the same view using the same variables as in the outer block. + $this->variableStack[$viewCacheKey][] = $variables; + + // Do the rendering + $html = $this->engine->renderBlock($view, $resource, $blockName, $variables); + + // Clear the stack + array_pop($this->variableStack[$viewCacheKey]); + + // Clear the caches if they were filled for the first time within + // this function call + if ($hierarchyInit) { + unset($this->blockNameHierarchyMap[$viewAndSuffixCacheKey], $this->hierarchyLevelMap[$viewAndSuffixCacheKey]); + } + + if ($varInit) { + unset($this->variableStack[$viewCacheKey]); + } + + if ($renderOnlyOnce) { + $view->setRendered(); + } + + return $html; + } + + public function humanize(string $text): string + { + return ucfirst(strtolower(trim(preg_replace(['/([A-Z])/', '/[_\s]+/'], ['_$1', ' '], $text)))); + } + + /** + * @internal + */ + public function encodeCurrency(Environment $environment, string $text, string $widget = ''): string + { + if ('UTF-8' === $charset = $environment->getCharset()) { + $text = htmlspecialchars($text, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + } else { + $text = htmlentities($text, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + $text = iconv('UTF-8', $charset, $text); + $widget = iconv('UTF-8', $charset, $widget); + } + + return str_replace('{{ widget }}', $widget, $text); + } +} diff --git a/vendor/symfony/form/FormRendererEngineInterface.php b/vendor/symfony/form/FormRendererEngineInterface.php new file mode 100644 index 0000000..e3c4ba8 --- /dev/null +++ b/vendor/symfony/form/FormRendererEngineInterface.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Adapter for rendering form templates with a specific templating engine. + * + * @author Bernhard Schussek + */ +interface FormRendererEngineInterface +{ + /** + * Sets the theme(s) to be used for rendering a view and its children. + * + * @param FormView $view The view to assign the theme(s) to + * @param mixed $themes The theme(s). The type of these themes + * is open to the implementation. + */ + public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void; + + /** + * Returns the resource for a block name. + * + * The resource is first searched in the themes attached to $view, then + * in the themes of its parent view and so on, until a resource was found. + * + * The type of the resource is decided by the implementation. The resource + * is later passed to {@link renderBlock()} by the rendering algorithm. + * + * @param FormView $view The view for determining the used themes. + * First the themes attached directly to the + * view with {@link setTheme()} are considered, + * then the ones of its parent etc. + * + * @return mixed the renderer resource or false, if none was found + */ + public function getResourceForBlockName(FormView $view, string $blockName): mixed; + + /** + * Returns the resource for a block hierarchy. + * + * A block hierarchy is an array which starts with the root of the hierarchy + * and continues with the child of that root, the child of that child etc. + * The following is an example for a block hierarchy: + * + * form_widget + * text_widget + * url_widget + * + * In this example, "url_widget" is the most specific block, while the other + * blocks are its ancestors in the hierarchy. + * + * The second parameter $hierarchyLevel determines the level of the hierarchy + * that should be rendered. For example, if $hierarchyLevel is 2 for the + * above hierarchy, the engine will first look for the block "url_widget", + * then, if that does not exist, for the block "text_widget" etc. + * + * The type of the resource is decided by the implementation. The resource + * is later passed to {@link renderBlock()} by the rendering algorithm. + * + * @param FormView $view The view for determining the used themes. + * First the themes attached directly to + * the view with {@link setTheme()} are + * considered, then the ones of its parent etc. + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block + * at the beginning + * @param int $hierarchyLevel The level in the hierarchy at which to start + * looking. Level 0 indicates the root block, i.e. + * the first element of $blockNameHierarchy. + * + * @return mixed The renderer resource or false, if none was found + */ + public function getResourceForBlockNameHierarchy(FormView $view, array $blockNameHierarchy, int $hierarchyLevel): mixed; + + /** + * Returns the hierarchy level at which a resource can be found. + * + * A block hierarchy is an array which starts with the root of the hierarchy + * and continues with the child of that root, the child of that child etc. + * The following is an example for a block hierarchy: + * + * form_widget + * text_widget + * url_widget + * + * The second parameter $hierarchyLevel determines the level of the hierarchy + * that should be rendered. + * + * If we call this method with the hierarchy level 2, the engine will first + * look for a resource for block "url_widget". If such a resource exists, + * the method returns 2. Otherwise it tries to find a resource for block + * "text_widget" (at level 1) and, again, returns 1 if a resource was found. + * The method continues to look for resources until the root level was + * reached and nothing was found. In this case false is returned. + * + * The type of the resource is decided by the implementation. The resource + * is later passed to {@link renderBlock()} by the rendering algorithm. + * + * @param FormView $view The view for determining the used themes. + * First the themes attached directly to + * the view with {@link setTheme()} are + * considered, then the ones of its parent etc. + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block + * at the beginning + * @param int $hierarchyLevel The level in the hierarchy at which to start + * looking. Level 0 indicates the root block, i.e. + * the first element of $blockNameHierarchy. + */ + public function getResourceHierarchyLevel(FormView $view, array $blockNameHierarchy, int $hierarchyLevel): int|false; + + /** + * Renders a block in the given renderer resource. + * + * The resource can be obtained by calling {@link getResourceForBlock()} + * or {@link getResourceForBlockHierarchy()}. The type of the resource is + * decided by the implementation. + * + * @param FormView $view The view to render + * @param mixed $resource The renderer resource + * @param array $variables The variables to pass to the template + */ + public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string; +} diff --git a/vendor/symfony/form/FormRendererInterface.php b/vendor/symfony/form/FormRendererInterface.php new file mode 100644 index 0000000..d57a904 --- /dev/null +++ b/vendor/symfony/form/FormRendererInterface.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Renders a form into HTML. + * + * @author Bernhard Schussek + */ +interface FormRendererInterface +{ + /** + * Returns the engine used by this renderer. + */ + public function getEngine(): FormRendererEngineInterface; + + /** + * Sets the theme(s) to be used for rendering a view and its children. + * + * @param FormView $view The view to assign the theme(s) to + * @param mixed $themes The theme(s). The type of these themes + * is open to the implementation. + * @param bool $useDefaultThemes If true, will use default themes specified + * in the renderer + */ + public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void; + + /** + * Renders a named block of the form theme. + * + * @param FormView $view The view for which to render the block + * @param array $variables The variables to pass to the template + */ + public function renderBlock(FormView $view, string $blockName, array $variables = []): string; + + /** + * Searches and renders a block for a given name suffix. + * + * The block is searched by combining the block names stored in the + * form view with the given suffix. If a block name is found, that + * block is rendered. + * + * If this method is called recursively, the block search is continued + * where a block was found before. + * + * @param FormView $view The view for which to render the block + * @param array $variables The variables to pass to the template + */ + public function searchAndRenderBlock(FormView $view, string $blockNameSuffix, array $variables = []): string; + + /** + * Renders a CSRF token. + * + * Use this helper for CSRF protection without the overhead of creating a + * form. + * + * + * + * Check the token in your action using the same token ID. + * + * // $csrfProvider being an instance of Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface + * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { + * throw new \RuntimeException('CSRF attack detected.'); + * } + */ + public function renderCsrfToken(string $tokenId): string; + + /** + * Makes a technical name human readable. + * + * Sequences of underscores are replaced by single spaces. The first letter + * of the resulting string is capitalized, while all other letters are + * turned to lowercase. + */ + public function humanize(string $text): string; +} diff --git a/vendor/symfony/form/FormTypeExtensionInterface.php b/vendor/symfony/form/FormTypeExtensionInterface.php new file mode 100644 index 0000000..3e2d2d0 --- /dev/null +++ b/vendor/symfony/form/FormTypeExtensionInterface.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Bernhard Schussek + */ +interface FormTypeExtensionInterface +{ + /** + * Gets the extended types. + * + * @return string[] + */ + public static function getExtendedTypes(): iterable; + + /** + * @return void + */ + public function configureOptions(OptionsResolver $resolver): void; + + /** + * Builds the form. + * + * This method is called after the extended type has built the form to + * further modify it. + * + * @param array $options + * + * @see FormTypeInterface::buildForm() + */ + public function buildForm(FormBuilderInterface $builder, array $options): void; + + /** + * Builds the view. + * + * This method is called after the extended type has built the view to + * further modify it. + * + * @param array $options + * + * @see FormTypeInterface::buildView() + */ + public function buildView(FormView $view, FormInterface $form, array $options): void; + + /** + * Finishes the view. + * + * This method is called after the extended type has finished the view to + * further modify it. + * + * @param array $options + * + * @see FormTypeInterface::finishView() + */ + public function finishView(FormView $view, FormInterface $form, array $options): void; +} diff --git a/vendor/symfony/form/FormTypeGuesserChain.php b/vendor/symfony/form/FormTypeGuesserChain.php new file mode 100644 index 0000000..ed94ece --- /dev/null +++ b/vendor/symfony/form/FormTypeGuesserChain.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Guess\Guess; +use Symfony\Component\Form\Guess\TypeGuess; +use Symfony\Component\Form\Guess\ValueGuess; + +class FormTypeGuesserChain implements FormTypeGuesserInterface +{ + private array $guessers = []; + + /** + * @param FormTypeGuesserInterface[] $guessers + * + * @throws UnexpectedTypeException if any guesser does not implement FormTypeGuesserInterface + */ + public function __construct(iterable $guessers) + { + $tmpGuessers = []; + foreach ($guessers as $guesser) { + if (!$guesser instanceof FormTypeGuesserInterface) { + throw new UnexpectedTypeException($guesser, FormTypeGuesserInterface::class); + } + + if ($guesser instanceof self) { + $tmpGuessers[] = $guesser->guessers; + } else { + $tmpGuessers[] = [$guesser]; + } + } + + $this->guessers = array_merge([], ...$tmpGuessers); + } + + public function guessType(string $class, string $property): ?TypeGuess + { + return $this->guess(static fn ($guesser) => $guesser->guessType($class, $property)); + } + + public function guessRequired(string $class, string $property): ?ValueGuess + { + return $this->guess(static fn ($guesser) => $guesser->guessRequired($class, $property)); + } + + public function guessMaxLength(string $class, string $property): ?ValueGuess + { + return $this->guess(static fn ($guesser) => $guesser->guessMaxLength($class, $property)); + } + + public function guessPattern(string $class, string $property): ?ValueGuess + { + return $this->guess(static fn ($guesser) => $guesser->guessPattern($class, $property)); + } + + /** + * Executes a closure for each guesser and returns the best guess from the + * return values. + * + * @param \Closure $closure The closure to execute. Accepts a guesser + * as argument and should return a Guess instance + */ + private function guess(\Closure $closure): ?Guess + { + $guesses = []; + + foreach ($this->guessers as $guesser) { + if ($guess = $closure($guesser)) { + $guesses[] = $guess; + } + } + + return Guess::getBestGuess($guesses); + } +} diff --git a/vendor/symfony/form/FormTypeGuesserInterface.php b/vendor/symfony/form/FormTypeGuesserInterface.php new file mode 100644 index 0000000..ab43793 --- /dev/null +++ b/vendor/symfony/form/FormTypeGuesserInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @author Bernhard Schussek + */ +interface FormTypeGuesserInterface +{ + /** + * Returns a field guess for a property name of a class. + */ + public function guessType(string $class, string $property): ?Guess\TypeGuess; + + /** + * Returns a guess whether a property of a class is required. + */ + public function guessRequired(string $class, string $property): ?Guess\ValueGuess; + + /** + * Returns a guess about the field's maximum length. + */ + public function guessMaxLength(string $class, string $property): ?Guess\ValueGuess; + + /** + * Returns a guess about the field's pattern. + */ + public function guessPattern(string $class, string $property): ?Guess\ValueGuess; +} diff --git a/vendor/symfony/form/FormTypeInterface.php b/vendor/symfony/form/FormTypeInterface.php new file mode 100644 index 0000000..2bc9f77 --- /dev/null +++ b/vendor/symfony/form/FormTypeInterface.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Bernhard Schussek + */ +interface FormTypeInterface +{ + /** + * Returns the name of the parent type. + * + * The parent type and its extensions will configure the form with the + * following methods before the current implementation. + * + * @return string|null + */ + public function getParent(); + + /** + * Configures the options for this type. + * + * @return void + */ + public function configureOptions(OptionsResolver $resolver); + + /** + * Builds the form. + * + * This method is called for each type in the hierarchy starting from the + * top most type. Type extensions can further modify the form. + * + * @param array $options + * + * @return void + * + * @see FormTypeExtensionInterface::buildForm() + */ + public function buildForm(FormBuilderInterface $builder, array $options); + + /** + * Builds the form view. + * + * This method is called for each type in the hierarchy starting from the + * top most type. Type extensions can further modify the view. + * + * A view of a form is built before the views of the child forms are built. + * This means that you cannot access child views in this method. If you need + * to do so, move your logic to {@link finishView()} instead. + * + * @param array $options + * + * @return void + * + * @see FormTypeExtensionInterface::buildView() + */ + public function buildView(FormView $view, FormInterface $form, array $options); + + /** + * Finishes the form view. + * + * This method gets called for each type in the hierarchy starting from the + * top most type. Type extensions can further modify the view. + * + * When this method is called, views of the form's children have already + * been built and finished and can be accessed. You should only implement + * such logic in this method that actually accesses child views. For everything + * else you are recommended to implement {@link buildView()} instead. + * + * @param array $options + * + * @return void + * + * @see FormTypeExtensionInterface::finishView() + */ + public function finishView(FormView $view, FormInterface $form, array $options); + + /** + * Returns the prefix of the template block name for this type. + * + * The block prefix defaults to the underscored short class name with + * the "Type" suffix removed (e.g. "UserProfileType" => "user_profile"). + * + * @return string + */ + public function getBlockPrefix(); +} diff --git a/vendor/symfony/form/FormView.php b/vendor/symfony/form/FormView.php new file mode 100644 index 0000000..93804bb --- /dev/null +++ b/vendor/symfony/form/FormView.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\BadMethodCallException; + +/** + * @author Bernhard Schussek + * + * @implements \ArrayAccess + * @implements \IteratorAggregate + */ +class FormView implements \ArrayAccess, \IteratorAggregate, \Countable +{ + /** + * The variables assigned to this view. + */ + public array $vars = [ + 'value' => null, + 'attr' => [], + ]; + + /** + * The child views. + * + * @var array + */ + public array $children = []; + + /** + * Is the form attached to this renderer rendered? + * + * Rendering happens when either the widget or the row method was called. + * Row implicitly includes widget, however certain rendering mechanisms + * have to skip widget rendering when a row is rendered. + */ + private bool $rendered = false; + + private bool $methodRendered = false; + + /** + * @param FormView|null $parent The parent view + */ + public function __construct( + public ?self $parent = null, + ) { + } + + /** + * Returns whether the view was already rendered. + */ + public function isRendered(): bool + { + if (true === $this->rendered || 0 === \count($this->children)) { + return $this->rendered; + } + + foreach ($this->children as $child) { + if (!$child->isRendered()) { + return false; + } + } + + return $this->rendered = true; + } + + /** + * Marks the view as rendered. + * + * @return $this + */ + public function setRendered(): static + { + $this->rendered = true; + + return $this; + } + + public function isMethodRendered(): bool + { + return $this->methodRendered; + } + + public function setMethodRendered(): void + { + $this->methodRendered = true; + } + + /** + * Returns a child by name (implements \ArrayAccess). + * + * @param int|string $name The child name + */ + public function offsetGet(mixed $name): self + { + return $this->children[$name]; + } + + /** + * Returns whether the given child exists (implements \ArrayAccess). + * + * @param int|string $name The child name + */ + public function offsetExists(mixed $name): bool + { + return isset($this->children[$name]); + } + + /** + * Implements \ArrayAccess. + * + * @throws BadMethodCallException always as setting a child by name is not allowed + */ + public function offsetSet(mixed $name, mixed $value): void + { + throw new BadMethodCallException('Not supported.'); + } + + /** + * Removes a child (implements \ArrayAccess). + * + * @param int|string $name The child name + */ + public function offsetUnset(mixed $name): void + { + unset($this->children[$name]); + } + + /** + * Returns an iterator to iterate over children (implements \IteratorAggregate). + * + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->children); + } + + public function count(): int + { + return \count($this->children); + } +} diff --git a/vendor/symfony/form/Forms.php b/vendor/symfony/form/Forms.php new file mode 100644 index 0000000..020e75e --- /dev/null +++ b/vendor/symfony/form/Forms.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Entry point of the Form component. + * + * Use this class to conveniently create new form factories: + * + * use Symfony\Component\Form\Forms; + * + * $formFactory = Forms::createFormFactory(); + * + * $form = $formFactory->createBuilder() + * ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + * ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + * ->add('age', 'Symfony\Component\Form\Extension\Core\Type\IntegerType') + * ->add('color', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', [ + * 'choices' => ['Red' => 'r', 'Blue' => 'b'], + * ]) + * ->getForm(); + * + * You can also add custom extensions to the form factory: + * + * $formFactory = Forms::createFormFactoryBuilder() + * ->addExtension(new AcmeExtension()) + * ->getFormFactory(); + * + * If you create custom form types or type extensions, it is + * generally recommended to create your own extensions that lazily + * load these types and type extensions. In projects where performance + * does not matter that much, you can also pass them directly to the + * form factory: + * + * $formFactory = Forms::createFormFactoryBuilder() + * ->addType(new PersonType()) + * ->addType(new PhoneNumberType()) + * ->addTypeExtension(new FormTypeHelpTextExtension()) + * ->getFormFactory(); + * + * Support for the Validator component is provided by ValidatorExtension. + * This extension needs a validator object to function properly: + * + * use Symfony\Component\Validator\Validation; + * use Symfony\Component\Form\Extension\Validator\ValidatorExtension; + * + * $validator = Validation::createValidator(); + * $formFactory = Forms::createFormFactoryBuilder() + * ->addExtension(new ValidatorExtension($validator)) + * ->getFormFactory(); + * + * @author Bernhard Schussek + */ +final class Forms +{ + /** + * Creates a form factory with the default configuration. + */ + public static function createFormFactory(): FormFactoryInterface + { + return self::createFormFactoryBuilder()->getFormFactory(); + } + + /** + * Creates a form factory builder with the default configuration. + */ + public static function createFormFactoryBuilder(): FormFactoryBuilderInterface + { + return new FormFactoryBuilder(true); + } + + /** + * This class cannot be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/form/Guess/Guess.php b/vendor/symfony/form/Guess/Guess.php new file mode 100644 index 0000000..fc19ed9 --- /dev/null +++ b/vendor/symfony/form/Guess/Guess.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Guess; + +use Symfony\Component\Form\Exception\InvalidArgumentException; + +/** + * Base class for guesses made by TypeGuesserInterface implementation. + * + * Each instance contains a confidence value about the correctness of the guess. + * Thus an instance with confidence HIGH_CONFIDENCE is more likely to be + * correct than an instance with confidence LOW_CONFIDENCE. + * + * @author Bernhard Schussek + */ +abstract class Guess +{ + /** + * Marks an instance with a value that is extremely likely to be correct. + */ + public const VERY_HIGH_CONFIDENCE = 3; + + /** + * Marks an instance with a value that is very likely to be correct. + */ + public const HIGH_CONFIDENCE = 2; + + /** + * Marks an instance with a value that is likely to be correct. + */ + public const MEDIUM_CONFIDENCE = 1; + + /** + * Marks an instance with a value that may be correct. + */ + public const LOW_CONFIDENCE = 0; + + /** + * The confidence about the correctness of the value. + * + * One of VERY_HIGH_CONFIDENCE, HIGH_CONFIDENCE, MEDIUM_CONFIDENCE + * and LOW_CONFIDENCE. + */ + private int $confidence; + + /** + * Returns the guess most likely to be correct from a list of guesses. + * + * If there are multiple guesses with the same, highest confidence, the + * returned guess is any of them. + * + * @param static[] $guesses An array of guesses + */ + public static function getBestGuess(array $guesses): ?static + { + $result = null; + $maxConfidence = -1; + + foreach ($guesses as $guess) { + if ($maxConfidence < $confidence = $guess->getConfidence()) { + $maxConfidence = $confidence; + $result = $guess; + } + } + + return $result; + } + + /** + * @throws InvalidArgumentException if the given value of confidence is unknown + */ + public function __construct(int $confidence) + { + if (self::VERY_HIGH_CONFIDENCE !== $confidence && self::HIGH_CONFIDENCE !== $confidence + && self::MEDIUM_CONFIDENCE !== $confidence && self::LOW_CONFIDENCE !== $confidence) { + throw new InvalidArgumentException('The confidence should be one of the constants defined in Guess.'); + } + + $this->confidence = $confidence; + } + + /** + * Returns the confidence that the guessed value is correct. + * + * @return int One of the constants VERY_HIGH_CONFIDENCE, HIGH_CONFIDENCE, + * MEDIUM_CONFIDENCE and LOW_CONFIDENCE + */ + public function getConfidence(): int + { + return $this->confidence; + } +} diff --git a/vendor/symfony/form/Guess/TypeGuess.php b/vendor/symfony/form/Guess/TypeGuess.php new file mode 100644 index 0000000..b62873d --- /dev/null +++ b/vendor/symfony/form/Guess/TypeGuess.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Guess; + +/** + * Contains a guessed class name and a list of options for creating an instance + * of that class. + * + * @author Bernhard Schussek + */ +class TypeGuess extends Guess +{ + /** + * @param string $type The guessed field type + * @param array $options The options for creating instances of the + * guessed class + * @param int $confidence The confidence that the guessed class name + * is correct + */ + public function __construct( + private string $type, + private array $options, + int $confidence, + ) { + parent::__construct($confidence); + } + + /** + * Returns the guessed field type. + */ + public function getType(): string + { + return $this->type; + } + + /** + * Returns the guessed options for creating instances of the guessed type. + */ + public function getOptions(): array + { + return $this->options; + } +} diff --git a/vendor/symfony/form/Guess/ValueGuess.php b/vendor/symfony/form/Guess/ValueGuess.php new file mode 100644 index 0000000..2283287 --- /dev/null +++ b/vendor/symfony/form/Guess/ValueGuess.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Guess; + +/** + * Contains a guessed value. + * + * @author Bernhard Schussek + */ +class ValueGuess extends Guess +{ + /** + * @param int $confidence The confidence that the guessed class name is correct + */ + public function __construct( + private string|int|bool|null $value, + int $confidence, + ) { + parent::__construct($confidence); + } + + /** + * Returns the guessed value. + */ + public function getValue(): string|int|bool|null + { + return $this->value; + } +} diff --git a/vendor/symfony/form/LICENSE b/vendor/symfony/form/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/form/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/form/NativeRequestHandler.php b/vendor/symfony/form/NativeRequestHandler.php new file mode 100644 index 0000000..bee54fa --- /dev/null +++ b/vendor/symfony/form/NativeRequestHandler.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Util\FormUtil; +use Symfony\Component\Form\Util\ServerParams; + +/** + * A request handler using PHP super globals $_GET, $_POST and $_SERVER. + * + * @author Bernhard Schussek + */ +class NativeRequestHandler implements RequestHandlerInterface +{ + private ServerParams $serverParams; + + /** + * The allowed keys of the $_FILES array. + */ + private const FILE_KEYS = [ + 'error', + 'full_path', + 'name', + 'size', + 'tmp_name', + 'type', + ]; + + public function __construct(?ServerParams $params = null) + { + $this->serverParams = $params ?? new ServerParams(); + } + + /** + * @throws UnexpectedTypeException If the $request is not null + */ + public function handleRequest(FormInterface $form, mixed $request = null): void + { + if (null !== $request) { + throw new UnexpectedTypeException($request, 'null'); + } + + $name = $form->getName(); + $method = $form->getConfig()->getMethod(); + + if ($method !== self::getRequestMethod()) { + return; + } + + // For request methods that must not have a request body we fetch data + // from the query string. Otherwise we look for data in the request body. + if ('GET' === $method || 'HEAD' === $method || 'TRACE' === $method) { + if ('' === $name) { + $data = $_GET; + } else { + // Don't submit GET requests if the form's name does not exist + // in the request + if (!isset($_GET[$name])) { + return; + } + + $data = $_GET[$name]; + } + } else { + // Mark the form with an error if the uploaded size was too large + // This is done here and not in FormValidator because $_POST is + // empty when that error occurs. Hence the form is never submitted. + if ($this->serverParams->hasPostMaxSizeBeenExceeded()) { + // Submit the form, but don't clear the default values + $form->submit(null, false); + + $form->addError(new FormError( + $form->getConfig()->getOption('upload_max_size_message')(), + null, + ['{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()] + )); + + return; + } + + $fixedFiles = []; + foreach ($_FILES as $fileKey => $file) { + $fixedFiles[$fileKey] = self::stripEmptyFiles(self::fixPhpFilesArray($file)); + } + + if ('' === $name) { + $params = $_POST; + $files = $fixedFiles; + } elseif (\array_key_exists($name, $_POST) || \array_key_exists($name, $fixedFiles)) { + $default = $form->getConfig()->getCompound() ? [] : null; + $params = \array_key_exists($name, $_POST) ? $_POST[$name] : $default; + $files = \array_key_exists($name, $fixedFiles) ? $fixedFiles[$name] : $default; + } else { + // Don't submit the form if it is not present in the request + return; + } + + if (\is_array($params) && \is_array($files)) { + $data = FormUtil::mergeParamsAndFiles($params, $files); + } else { + $data = $params ?: $files; + } + } + + // Don't auto-submit the form unless at least one field is present. + if ('' === $name && \count(array_intersect_key($data, $form->all())) <= 0) { + return; + } + + if (\is_array($data) && \array_key_exists('_method', $data) && $method === $data['_method'] && !$form->has('_method')) { + unset($data['_method']); + } + + $form->submit($data, 'PATCH' !== $method); + } + + public function isFileUpload(mixed $data): bool + { + // POST data will always be strings or arrays of strings. Thus, we can be sure + // that the submitted data is a file upload if the "error" value is an integer + // (this value must have been injected by PHP itself). + return \is_array($data) && isset($data['error']) && \is_int($data['error']); + } + + public function getUploadFileError(mixed $data): ?int + { + if (!\is_array($data)) { + return null; + } + + if (!isset($data['error'])) { + return null; + } + + if (!\is_int($data['error'])) { + return null; + } + + if (\UPLOAD_ERR_OK === $data['error']) { + return null; + } + + return $data['error']; + } + + private static function getRequestMethod(): string + { + $method = isset($_SERVER['REQUEST_METHOD']) + ? strtoupper($_SERVER['REQUEST_METHOD']) + : 'GET'; + + if ('POST' === $method && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } + + return $method; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * This method is identical to {@link \Symfony\Component\HttpFoundation\FileBag::fixPhpFilesArray} + * and should be kept as such in order to port fixes quickly and easily. + */ + private static function fixPhpFilesArray(mixed $data): mixed + { + if (!\is_array($data)) { + return $data; + } + + $keys = array_keys($data + ['full_path' => null]); + sort($keys); + + if (self::FILE_KEYS !== $keys || !isset($data['name']) || !\is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::FILE_KEYS as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = self::fixPhpFilesArray([ + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + ] + (isset($data['full_path'][$key]) ? [ + 'full_path' => $data['full_path'][$key], + ] : [])); + } + + return $files; + } + + private static function stripEmptyFiles(mixed $data): mixed + { + if (!\is_array($data)) { + return $data; + } + + $keys = array_keys($data + ['full_path' => null]); + sort($keys); + + if (self::FILE_KEYS === $keys) { + if (\UPLOAD_ERR_NO_FILE === $data['error']) { + return null; + } + + return $data; + } + + foreach ($data as $key => $value) { + $data[$key] = self::stripEmptyFiles($value); + } + + return $data; + } +} diff --git a/vendor/symfony/form/PreloadedExtension.php b/vendor/symfony/form/PreloadedExtension.php new file mode 100644 index 0000000..58d8f13 --- /dev/null +++ b/vendor/symfony/form/PreloadedExtension.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\InvalidArgumentException; + +/** + * A form extension with preloaded types, type extensions and type guessers. + * + * @author Bernhard Schussek + */ +class PreloadedExtension implements FormExtensionInterface +{ + private array $types = []; + + /** + * Creates a new preloaded extension. + * + * @param FormTypeInterface[] $types The types that the extension should support + * @param FormTypeExtensionInterface[][] $typeExtensions The type extensions that the extension should support + */ + public function __construct( + array $types, + private array $typeExtensions, + private ?FormTypeGuesserInterface $typeGuesser = null, + ) { + foreach ($types as $type) { + $this->types[$type::class] = $type; + } + } + + public function getType(string $name): FormTypeInterface + { + if (!isset($this->types[$name])) { + throw new InvalidArgumentException(sprintf('The type "%s" cannot be loaded by this extension.', $name)); + } + + return $this->types[$name]; + } + + public function hasType(string $name): bool + { + return isset($this->types[$name]); + } + + public function getTypeExtensions(string $name): array + { + return $this->typeExtensions[$name] + ?? []; + } + + public function hasTypeExtensions(string $name): bool + { + return !empty($this->typeExtensions[$name]); + } + + public function getTypeGuesser(): ?FormTypeGuesserInterface + { + return $this->typeGuesser; + } +} diff --git a/vendor/symfony/form/README.md b/vendor/symfony/form/README.md new file mode 100644 index 0000000..0cda654 --- /dev/null +++ b/vendor/symfony/form/README.md @@ -0,0 +1,13 @@ +Form Component +============== + +The Form component allows you to easily create, process and reuse HTML forms. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/form.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/form/RequestHandlerInterface.php b/vendor/symfony/form/RequestHandlerInterface.php new file mode 100644 index 0000000..2a4bccf --- /dev/null +++ b/vendor/symfony/form/RequestHandlerInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Submits forms if they were submitted. + * + * @author Bernhard Schussek + */ +interface RequestHandlerInterface +{ + /** + * Submits a form if it was submitted. + */ + public function handleRequest(FormInterface $form, mixed $request = null): void; + + /** + * Returns true if the given data is a file upload. + */ + public function isFileUpload(mixed $data): bool; +} diff --git a/vendor/symfony/form/ResolvedFormType.php b/vendor/symfony/form/ResolvedFormType.php new file mode 100644 index 0000000..964619c --- /dev/null +++ b/vendor/symfony/form/ResolvedFormType.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * A wrapper for a form type and its extensions. + * + * @author Bernhard Schussek + */ +class ResolvedFormType implements ResolvedFormTypeInterface +{ + /** + * @var FormTypeExtensionInterface[] + */ + private array $typeExtensions; + + private OptionsResolver $optionsResolver; + + /** + * @param FormTypeExtensionInterface[] $typeExtensions + */ + public function __construct( + private FormTypeInterface $innerType, + array $typeExtensions = [], + private ?ResolvedFormTypeInterface $parent = null, + ) { + foreach ($typeExtensions as $extension) { + if (!$extension instanceof FormTypeExtensionInterface) { + throw new UnexpectedTypeException($extension, FormTypeExtensionInterface::class); + } + } + + $this->typeExtensions = $typeExtensions; + } + + public function getBlockPrefix(): string + { + return $this->innerType->getBlockPrefix(); + } + + public function getParent(): ?ResolvedFormTypeInterface + { + return $this->parent; + } + + public function getInnerType(): FormTypeInterface + { + return $this->innerType; + } + + public function getTypeExtensions(): array + { + return $this->typeExtensions; + } + + public function createBuilder(FormFactoryInterface $factory, string $name, array $options = []): FormBuilderInterface + { + try { + $options = $this->getOptionsResolver()->resolve($options); + } catch (ExceptionInterface $e) { + throw new $e(sprintf('An error has occurred resolving the options of the form "%s": ', get_debug_type($this->getInnerType())).$e->getMessage(), $e->getCode(), $e); + } + + // Should be decoupled from the specific option at some point + $dataClass = $options['data_class'] ?? null; + + $builder = $this->newBuilder($name, $dataClass, $factory, $options); + $builder->setType($this); + + return $builder; + } + + public function createView(FormInterface $form, ?FormView $parent = null): FormView + { + return $this->newView($parent); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $this->parent?->buildForm($builder, $options); + + $this->innerType->buildForm($builder, $options); + + foreach ($this->typeExtensions as $extension) { + $extension->buildForm($builder, $options); + } + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $this->parent?->buildView($view, $form, $options); + + $this->innerType->buildView($view, $form, $options); + + foreach ($this->typeExtensions as $extension) { + $extension->buildView($view, $form, $options); + } + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $this->parent?->finishView($view, $form, $options); + + $this->innerType->finishView($view, $form, $options); + + foreach ($this->typeExtensions as $extension) { + /* @var FormTypeExtensionInterface $extension */ + $extension->finishView($view, $form, $options); + } + } + + public function getOptionsResolver(): OptionsResolver + { + if (!isset($this->optionsResolver)) { + if (null !== $this->parent) { + $this->optionsResolver = clone $this->parent->getOptionsResolver(); + } else { + $this->optionsResolver = new OptionsResolver(); + } + + $this->innerType->configureOptions($this->optionsResolver); + + foreach ($this->typeExtensions as $extension) { + $extension->configureOptions($this->optionsResolver); + } + } + + return $this->optionsResolver; + } + + /** + * Creates a new builder instance. + * + * Override this method if you want to customize the builder class. + */ + protected function newBuilder(string $name, ?string $dataClass, FormFactoryInterface $factory, array $options): FormBuilderInterface + { + if ($this->innerType instanceof ButtonTypeInterface) { + return new ButtonBuilder($name, $options); + } + + if ($this->innerType instanceof SubmitButtonTypeInterface) { + return new SubmitButtonBuilder($name, $options); + } + + return new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options); + } + + /** + * Creates a new view instance. + * + * Override this method if you want to customize the view class. + */ + protected function newView(?FormView $parent = null): FormView + { + return new FormView($parent); + } +} diff --git a/vendor/symfony/form/ResolvedFormTypeFactory.php b/vendor/symfony/form/ResolvedFormTypeFactory.php new file mode 100644 index 0000000..437f9c5 --- /dev/null +++ b/vendor/symfony/form/ResolvedFormTypeFactory.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @author Bernhard Schussek + */ +class ResolvedFormTypeFactory implements ResolvedFormTypeFactoryInterface +{ + public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface + { + return new ResolvedFormType($type, $typeExtensions, $parent); + } +} diff --git a/vendor/symfony/form/ResolvedFormTypeFactoryInterface.php b/vendor/symfony/form/ResolvedFormTypeFactoryInterface.php new file mode 100644 index 0000000..9fd39e7 --- /dev/null +++ b/vendor/symfony/form/ResolvedFormTypeFactoryInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Creates ResolvedFormTypeInterface instances. + * + * This interface allows you to use your custom ResolvedFormTypeInterface + * implementation, within which you can customize the concrete FormBuilderInterface + * implementations or FormView subclasses that are used by the framework. + * + * @author Bernhard Schussek + */ +interface ResolvedFormTypeFactoryInterface +{ + /** + * Resolves a form type. + * + * @param FormTypeExtensionInterface[] $typeExtensions + * + * @throws Exception\UnexpectedTypeException if the types parent {@link FormTypeInterface::getParent()} is not a string + * @throws Exception\InvalidArgumentException if the types parent cannot be retrieved from any extension + */ + public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface; +} diff --git a/vendor/symfony/form/ResolvedFormTypeInterface.php b/vendor/symfony/form/ResolvedFormTypeInterface.php new file mode 100644 index 0000000..690e0d7 --- /dev/null +++ b/vendor/symfony/form/ResolvedFormTypeInterface.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * A wrapper for a form type and its extensions. + * + * @author Bernhard Schussek + */ +interface ResolvedFormTypeInterface +{ + /** + * Returns the prefix of the template block name for this type. + */ + public function getBlockPrefix(): string; + + /** + * Returns the parent type. + */ + public function getParent(): ?self; + + /** + * Returns the wrapped form type. + */ + public function getInnerType(): FormTypeInterface; + + /** + * Returns the extensions of the wrapped form type. + * + * @return FormTypeExtensionInterface[] + */ + public function getTypeExtensions(): array; + + /** + * Creates a new form builder for this type. + * + * @param string $name The name for the builder + */ + public function createBuilder(FormFactoryInterface $factory, string $name, array $options = []): FormBuilderInterface; + + /** + * Creates a new form view for a form of this type. + */ + public function createView(FormInterface $form, ?FormView $parent = null): FormView; + + /** + * Configures a form builder for the type hierarchy. + */ + public function buildForm(FormBuilderInterface $builder, array $options): void; + + /** + * Configures a form view for the type hierarchy. + * + * It is called before the children of the view are built. + */ + public function buildView(FormView $view, FormInterface $form, array $options): void; + + /** + * Finishes a form view for the type hierarchy. + * + * It is called after the children of the view have been built. + */ + public function finishView(FormView $view, FormInterface $form, array $options): void; + + /** + * Returns the configured options resolver used for this type. + */ + public function getOptionsResolver(): OptionsResolver; +} diff --git a/vendor/symfony/form/Resources/config/validation.xml b/vendor/symfony/form/Resources/config/validation.xml new file mode 100644 index 0000000..918f101 --- /dev/null +++ b/vendor/symfony/form/Resources/config/validation.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.af.xlf b/vendor/symfony/form/Resources/translations/validators.af.xlf new file mode 100644 index 0000000..c726e93 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.af.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Hierdie vorm moet nie ekstra velde bevat nie. + + + The uploaded file was too large. Please try to upload a smaller file. + Die opgelaaide lêer was te groot. Probeer asseblief 'n kleiner lêer. + + + The CSRF token is invalid. Please try to resubmit the form. + Die CSRF-teken is ongeldig. Probeer asseblief om die vorm weer in te dien. + + + This value is not a valid HTML5 color. + Hierdie waarde is nie 'n geldige HTML5 kleur nie. + + + Please enter a valid birthdate. + Voer asseblief 'n geldige geboortedatum in. + + + The selected choice is invalid. + Die gekiesde opsie is nie geldig nie. + + + The collection is invalid. + Die versameling is nie geldig nie. + + + Please select a valid color. + Kies asseblief 'n geldige kleur. + + + Please select a valid country. + Kies asseblief 'n geldige land. + + + Please select a valid currency. + Kies asseblief 'n geldige geldeenheid. + + + Please choose a valid date interval. + Kies asseblief 'n geldige datum interval. + + + Please enter a valid date and time. + Voer asseblilef 'n geldige datum en tyd in. + + + Please enter a valid date. + Voer asseblief 'n geldige datum in. + + + Please select a valid file. + Kies asseblief 'n geldige lêer. + + + The hidden field is invalid. + Die versteekte veld is nie geldig nie. + + + Please enter an integer. + Voer asseblief 'n geldige heeltal in. + + + Please select a valid language. + Kies assblief 'n geldige taal. + + + Please select a valid locale. + Voer assebliefn 'n geldige locale in. + + + Please enter a valid money amount. + Voer asseblief 'n geldige bedrag in. + + + Please enter a number. + Voer asseblief 'n nommer in. + + + The password is invalid. + Die wagwoord is ongeldig. + + + Please enter a percentage value. + Voer asseblief 'n geldige persentasie waarde in. + + + The values do not match. + Die waardes is nie dieselfde nie. + + + Please enter a valid time. + Voer asseblief 'n geldige tyd in time. + + + Please select a valid timezone. + Kies asseblief 'n geldige tydsone. + + + Please enter a valid URL. + Voer asseblief 'n geldige URL in. + + + Please enter a valid search term. + Voer asseblief 'n geldige soek term in. + + + Please provide a valid phone number. + Verskaf asseblief 'n geldige telefoonnommer. + + + The checkbox has an invalid value. + Die blokkie het 'n ongeldige waarde. + + + Please enter a valid email address. + Voer asseblief 'n geldige e-pos adres in. + + + Please select a valid option. + Kies asseblief 'n geldige opsie. + + + Please select a valid range. + Kies asseblief 'n geldige reeks. + + + Please enter a valid week. + Voer assblief 'n geldige week in. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.ar.xlf b/vendor/symfony/form/Resources/translations/validators.ar.xlf new file mode 100644 index 0000000..d18b469 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.ar.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + هذا النموذج يجب الا يحتوى على اى حقول اضاÙية. + + + The uploaded file was too large. Please try to upload a smaller file. + مساحة المل٠المرسل كبيرة. من ÙØ¶Ù„Ùƒ حاول ارسال مل٠اصغر. + + + The CSRF token is invalid. Please try to resubmit the form. + قيمة رمز الموقع غير صحيحة. من ÙØ¶Ù„Ùƒ اعد ارسال النموذج. + + + This value is not a valid HTML5 color. + هذه القيمة ليست لون HTML5 صالحًا. + + + Please enter a valid birthdate. + الرجاء ادخال تاريخ ميلاد صالح. + + + The selected choice is invalid. + الاختيار المحدد غير صالح. + + + The collection is invalid. + المجموعة غير صالحة. + + + Please select a valid color. + الرجاء اختيار لون صالح. + + + Please select a valid country. + الرجاء اختيار بلد صالح. + + + Please select a valid currency. + الرجاء اختيار عملة صالحة. + + + Please choose a valid date interval. + الرجاء اختيار ÙØ§ØµÙ„ زمني صالح. + + + Please enter a valid date and time. + الرجاء إدخال تاريخ ووقت صالحين. + + + Please enter a valid date. + الرجاء إدخال تاريخ صالح. + + + Please select a valid file. + الرجاء اختيار مل٠صالح. + + + The hidden field is invalid. + الحقل المخÙÙŠ غير صالح. + + + Please enter an integer. + الرجاء إدخال عدد صحيح. + + + Please select a valid language. + الرجاء اختيار لغة صالحة. + + + Please select a valid locale. + الرجاء اختيار لغة صالحة. + + + Please enter a valid money amount. + الرجاء إدخال مبلغ مالي صالح. + + + Please enter a number. + الرجاء إدخال رقم. + + + The password is invalid. + كلمة المرور غير صحيحة. + + + Please enter a percentage value. + الرجاء إدخال قيمة النسبة المئوية. + + + The values do not match. + القيم لا تتطابق. + + + Please enter a valid time. + الرجاء إدخال وقت صالح. + + + Please select a valid timezone. + الرجاء تحديد منطقة زمنية صالحة. + + + Please enter a valid URL. + أدخل عنوان الرابط صحيح من ÙØ¶Ù„Ùƒ. + + + Please enter a valid search term. + الرجاء إدخال مصطلح البحث ساري Ø§Ù„Ù…ÙØ¹ÙˆÙ„. + + + Please provide a valid phone number. + يرجى تقديم رقم هات٠صالح. + + + The checkbox has an invalid value. + خانة الاختيار لها قيمة غير صالحة. + + + Please enter a valid email address. + رجاء قم بإدخال بريد الكتروني صحيح + + + Please select a valid option. + الرجاء تحديد خيار صالح. + + + Please select a valid range. + يرجى تحديد نطاق صالح. + + + Please enter a valid week. + الرجاء إدخال أسبوع صالح. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.az.xlf b/vendor/symfony/form/Resources/translations/validators.az.xlf new file mode 100644 index 0000000..87791b6 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.az.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Bu formada É™lavÉ™ sahÉ™ olmamalıdır. + + + The uploaded file was too large. Please try to upload a smaller file. + YüklÉ™nÉ™n fayl çox böyükdür. LütfÉ™n daha kiçik fayl yüklÉ™yin. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF niÅŸanı yanlışdır. Lütfen formanı yenidÉ™n göndÉ™rin. + + + This value is not a valid HTML5 color. + Bu dÉ™yÉ™r doÄŸru bir HTML5 rÉ™ngi deyil. + + + Please enter a valid birthdate. + ZÉ™hmÉ™t olmasa doÄŸru bir doÄŸum günü daxil edin. + + + The selected choice is invalid. + SeçilmiÅŸ seçim doÄŸru deyil. + + + The collection is invalid. + Kolleksiya doÄŸru deyil. + + + Please select a valid color. + ZÉ™hmÉ™t olmasa doÄŸru bir rÉ™ng seçin. + + + Please select a valid country. + ZÉ™hmÉ™t olmasa doÄŸru bir ölkÉ™ seçin. + + + Please select a valid currency. + ZÉ™hmÉ™t olmasa doÄŸru bir valyuta seçin. + + + Please choose a valid date interval. + ZÉ™hmÉ™t olmasa doÄŸru bir tarix aralığı seçin. + + + Please enter a valid date and time. + ZÉ™hmÉ™t olmasa doÄŸru bir tarix ve saat daxil edin. + + + Please enter a valid date. + ZÉ™hmÉ™t olmasa doÄŸru bir tarix daxil edin. + + + Please select a valid file. + ZÉ™hmÉ™t olmasa doÄŸru bir fayl seçin. + + + The hidden field is invalid. + Gizli sahÉ™ doÄŸru deyil. + + + Please enter an integer. + ZÉ™hmÉ™t olmasa bir tam É™dÉ™d daxil edin. + + + Please select a valid language. + ZÉ™hmÉ™t olmasa doÄŸru bir dil seçin. + + + Please select a valid locale. + ZÉ™hmÉ™t olmasa doÄŸru bir yer seçin. + + + Please enter a valid money amount. + ZÉ™hmÉ™t olmasa doÄŸru bir pul miqdarı daxil edin. + + + Please enter a number. + ZÉ™hmÉ™t olmasa doÄŸru bir rÉ™qÉ™m daxil edin. + + + The password is invalid. + Parol doÄŸru deyil. + + + Please enter a percentage value. + ZÉ™hmÉ™t olmasa doÄŸru bir faiz dÉ™yÉ™ri daxil edin. + + + The values do not match. + DÉ™yÉ™rlÉ™r örtüşmür. + + + Please enter a valid time. + ZÉ™hmÉ™t olmasa doÄŸru bir saat daxil edin. + + + Please select a valid timezone. + ZÉ™hmÉ™t olmasa doÄŸru bir saat qurÅŸağı seçin. + + + Please enter a valid URL. + ZÉ™hmÉ™t olmasa doÄŸru bir URL daxil edin. + + + Please enter a valid search term. + ZÉ™hmÉ™t olmasa doÄŸru bir axtarış termini daxil edin. + + + Please provide a valid phone number. + ZÉ™hmÉ™t olmasa doÄŸru bir telefon nömrÉ™si seçin. + + + The checkbox has an invalid value. + Seçim qutusunda doÄŸru olmayan dÉ™yÉ™r var. + + + Please enter a valid email address. + ZÉ™hmÉ™t olmasa doÄŸru bir e-poçt seçin. + + + Please select a valid option. + ZÉ™hmÉ™t olmasa doÄŸru bir variant seçin. + + + Please select a valid range. + ZÉ™hmÉ™t olmasa doÄŸru bir aralıq seçin. + + + Please enter a valid week. + ZÉ™hmÉ™t olmasa doÄŸru bir hÉ™ftÉ™ seçin. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.be.xlf b/vendor/symfony/form/Resources/translations/validators.be.xlf new file mode 100644 index 0000000..b24976e --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.be.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + ГÑта форма не павінна мець дадатковых палей. + + + The uploaded file was too large. Please try to upload a smaller file. + Запампаваны файл быў занадта вÑлікім. Калі лаÑка, паÑпрабуйце запампаваць файл меншага памеру. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-токен не Ñапраўдны. Калі лаÑка, паÑпрабуйце ÑÑˆÑ‡Ñ Ñ€Ð°Ð· адправіць форму. + + + This value is not a valid HTML5 color. + ЗначÑнне не з'ÑўлÑецца карÑктным HTML5 колерам. + + + Please enter a valid birthdate. + Калі лаÑка, увÑдзіце карÑктную дату нараджÑннÑ. + + + The selected choice is invalid. + Выбраны варыÑнт некарÑктны. + + + The collection is invalid. + ÐšÐ°Ð»ÐµÐºÑ†Ñ‹Ñ Ð½ÐµÐºÐ°Ñ€Ñктна. + + + Please select a valid color. + Калі лаÑка, выберыце карÑктны колер. + + + Please select a valid country. + Калі лаÑка, выберыце карÑктную краіну. + + + Please select a valid currency. + Калі лаÑка, выберыце карÑктную валюту. + + + Please choose a valid date interval. + Калі лаÑка, выберыце карÑктны інтÑрвал дат. + + + Please enter a valid date and time. + Калі лаÑка, увÑдзіце карÑÐºÑ‚Ð½Ñ‹Ñ Ð´Ð°Ñ‚Ñƒ Ñ– чаÑ. + + + Please enter a valid date. + Калі лаÑка, увÑдзіце карÑктную дату. + + + Please select a valid file. + Калі лаÑка, выберыце карÑктны файл. + + + The hidden field is invalid. + ЗначÑнне Ñхаванага Ð¿Ð¾Ð»Ñ Ð½ÐµÐºÐ°Ñ€Ñктна. + + + Please enter an integer. + Калі лаÑка, увÑдзіце цÑлы лік. + + + Please select a valid language. + Калі лаÑка, выберыце карÑктную мову. + + + Please select a valid locale. + Калі лаÑка, выберыце карÑктную лакаль. + + + Please enter a valid money amount. + Калі лаÑка, увÑдзіце карÑктную колькаÑць грошай. + + + Please enter a number. + Калі лаÑка, увÑдзіце нумар. + + + The password is invalid. + ÐÑправільны пароль. + + + Please enter a percentage value. + Калі лаÑка, увÑдзіце працÑнтнае значÑнне. + + + The values do not match. + ЗначÑнні не Ñупадаюць. + + + Please enter a valid time. + Калі лаÑка, увÑдзіце карÑктны чаÑ. + + + Please select a valid timezone. + Калі лаÑка, выберыце карÑктны гадзінны поÑÑ. + + + Please enter a valid URL. + Калі лаÑка, увÑдзіце карÑктны URL. + + + Please enter a valid search term. + Калі лаÑка, увÑдзіце карÑктны пошукавы запыт. + + + Please provide a valid phone number. + Калі лаÑка, увÑдзіце карÑктны нумар Ñ‚Ñлефона. + + + The checkbox has an invalid value. + Флажок мае некарÑктнае значÑнне. + + + Please enter a valid email address. + Калі лаÑка, увÑдзіце карÑктны Ð°Ð´Ñ€Ð°Ñ Ñлектроннай пошты. + + + Please select a valid option. + Калі лаÑка, выберыце карÑктны варыÑнт. + + + Please select a valid range. + Калі лаÑка, выберыце карÑктны дыÑпазон. + + + Please enter a valid week. + Калі лаÑка, увÑдзіце карÑктны тыдзень. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.bg.xlf b/vendor/symfony/form/Resources/translations/validators.bg.xlf new file mode 100644 index 0000000..19b80f5 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.bg.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Тази форма не трÑбва да Ñъдържа допълнителни полета. + + + The uploaded file was too large. Please try to upload a smaller file. + КачениÑÑ‚ файл е твърде голÑм. МолÑ, опитайте да качите по-малък файл. + + + The CSRF token is invalid. Please try to resubmit the form. + Ðевалиден CSRF токен. МолÑ, опитайте да изпратите формата отново. + + + This value is not a valid HTML5 color. + СтойноÑтта не е валиден HTML5 цвÑÑ‚. + + + Please enter a valid birthdate. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидна дата на раждане. + + + The selected choice is invalid. + Избраните ÑтойноÑти не Ñа валидни. + + + The collection is invalid. + КолекциÑта не е валидна. + + + Please select a valid color. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валиден цвÑÑ‚. + + + Please select a valid country. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валидна държава. + + + Please select a valid currency. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валидна валута. + + + Please choose a valid date interval. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валиден интервал от дати. + + + Please enter a valid date and time. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидни дата и чаÑ. + + + Please enter a valid date. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидна дата. + + + Please select a valid file. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валиден файл. + + + The hidden field is invalid. + Скритото поле е невалидно. + + + Please enter an integer. + ÐœÐ¾Ð»Ñ Ð¿Ð¾Ð¿ÑŠÐ»Ð½ÐµÑ‚Ðµ цÑло чиÑло. + + + Please select a valid language. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валиден език. + + + Please select a valid locale. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валиден език. + + + Please enter a valid money amount. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидна парична Ñума. + + + Please enter a number. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ чиÑло. + + + The password is invalid. + Паролата е невалидна. + + + Please enter a percentage value. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ процентна ÑтойноÑÑ‚. + + + The values do not match. + СтойноÑтите не Ñъвпадат. + + + Please enter a valid time. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидно време. + + + Please select a valid timezone. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валидна чаÑова зона. + + + Please enter a valid URL. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валиден URL. + + + Please enter a valid search term. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидно търÑене. + + + Please provide a valid phone number. + ÐœÐ¾Ð»Ñ Ð¾Ñигурете валиден телефонен номер. + + + The checkbox has an invalid value. + Отметката има невалидна ÑтойноÑÑ‚. + + + Please enter a valid email address. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидна ел. поща. + + + Please select a valid option. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валидна опциÑ. + + + Please select a valid range. + ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ валиден обхват. + + + Please enter a valid week. + ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидна Ñедмица. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.bs.xlf b/vendor/symfony/form/Resources/translations/validators.bs.xlf new file mode 100644 index 0000000..d360635 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.bs.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ovaj obrazac ne bi trebalo da sadrži dodatna polja. + + + The uploaded file was too large. Please try to upload a smaller file. + Prenijeta (uploaded) datoteka je prevelika. Molim pokuÅ¡ajte prenijeti manju datoteku. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF vrijednost nije ispravna. Molim pokuÅ¡ajte ponovo da poÅ¡aljete obrazac. + + + This value is not a valid HTML5 color. + Ova vrijednost nije važeća HTML5 boja. + + + Please enter a valid birthdate. + Molim upiÅ¡ite ispravan datum roÄ‘enja. + + + The selected choice is invalid. + Odabrani izbor nije ispravan. + + + The collection is invalid. + Ova kolekcija nije ispravna. + + + Please select a valid color. + Molim izaberite ispravnu boju. + + + Please select a valid country. + Molim izaberite ispravnu državu. + + + Please select a valid currency. + Molim izaberite ispravnu valutu. + + + Please choose a valid date interval. + Molim izaberite ispravan datumski interval. + + + Please enter a valid date and time. + Molim upiÅ¡ite ispravan datum i vrijeme. + + + Please enter a valid date. + Molim upiÅ¡ite ispravan datum. + + + Please select a valid file. + Molim izaberite ispravnu datoteku. + + + The hidden field is invalid. + Skriveno polje nije ispravno. + + + Please enter an integer. + Molim upiÅ¡ite cijeli broj (integer). + + + Please select a valid language. + Molim izaberite ispravan jezik. + + + Please select a valid locale. + Molim izaberite ispravnu lokalizaciju. + + + Please enter a valid money amount. + Molim upiÅ¡ite ispravnu koliÄinu novca. + + + Please enter a number. + Molim upiÅ¡ite broj. + + + The password is invalid. + Ova lozinka nije ispravna. + + + Please enter a percentage value. + Molim upiÅ¡ite procentualnu vrijednost. + + + The values do not match. + Date vrijednosti se ne poklapaju. + + + Please enter a valid time. + Molim upiÅ¡ite ispravno vrijeme. + + + Please select a valid timezone. + Molim izaberite ispravnu vremensku zonu. + + + Please enter a valid URL. + Molim upiÅ¡ite ispravan URL. + + + Please enter a valid search term. + Molim upiÅ¡ite ispravan termin za pretragu. + + + Please provide a valid phone number. + Molim navedite ispravan broj telefona. + + + The checkbox has an invalid value. + Polje za potvrdu sadrži neispravnu vrijednost. + + + Please enter a valid email address. + Molim upiÅ¡ite ispravnu email adresu. + + + Please select a valid option. + Molim izaberite ispravnu opciju. + + + Please select a valid range. + Molim izaberite ispravan opseg. + + + Please enter a valid week. + Molim upiÅ¡ite ispravnu sedmicu. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.ca.xlf b/vendor/symfony/form/Resources/translations/validators.ca.xlf new file mode 100644 index 0000000..76df582 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.ca.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Aquest formulari no hauria de contenir camps addicionals. + + + The uploaded file was too large. Please try to upload a smaller file. + L'arxiu pujat és massa gran. Per favor, pugi un arxiu més petit. + + + The CSRF token is invalid. Please try to resubmit the form. + El token CSRF no és vàlid. Per favor, provi d'enviar novament el formulari. + + + This value is not a valid HTML5 color. + Aquest valor no és un color HTML5 valid. + + + Please enter a valid birthdate. + Per favor introdueix una data d'aniversari valida. + + + The selected choice is invalid. + L'opció escollida és invalida. + + + The collection is invalid. + La col·lecció és invalida. + + + Please select a valid color. + Per favor selecciona un color vàlid. + + + Please select a valid country. + Per favor selecciona una ciutat vàlida. + + + Please select a valid currency. + Per favor selecciona una moneda vàlida. + + + Please choose a valid date interval. + Per favor escull un interval de dates vàlides. + + + Please enter a valid date and time. + Per favor introdueix una data i temps vàlid. + + + Please enter a valid date. + Per favor introdueix una data vàlida. + + + Please select a valid file. + Per favor selecciona un arxiu vàlid. + + + The hidden field is invalid. + El camp ocult és invàlid. + + + Please enter an integer. + Per favor introdueix un enter. + + + Please select a valid language. + Per favor selecciona un idioma vàlid. + + + Please select a valid locale. + Per favor seleccioneu una configuració regional vàlida + + + Please enter a valid money amount. + Per favor introdueix una quantitat de diners vàlids. + + + Please enter a number. + Per favor introdueix un número. + + + The password is invalid. + La contrasenya es invàlida. + + + Please enter a percentage value. + Per favor introdueix un valor percentual. + + + The values do not match. + Els valors no coincideixen. + + + Please enter a valid time. + Per favor introdueix un temps vàlid. + + + Please select a valid timezone. + Per favor selecciona una zona horària vàlida. + + + Please enter a valid URL. + Per favor introdueix una URL vàlida. + + + Please enter a valid search term. + Per favor introdueix un concepte de cerca vàlid. + + + Please provide a valid phone number. + Per favor introdueix un número de telèfon vàlid. + + + The checkbox has an invalid value. + La casella de selecció te un valor invàlid. + + + Please enter a valid email address. + Per favor introdueix un correu electrònic vàlid. + + + Please select a valid option. + Per favor selecciona una opció vàlida. + + + Please select a valid range. + Per favor selecciona un rang vàlid. + + + Please enter a valid week. + Per favor introdueix una setmana vàlida. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.cs.xlf b/vendor/symfony/form/Resources/translations/validators.cs.xlf new file mode 100644 index 0000000..829fea1 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.cs.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Tato skupina polí nesmí obsahovat další pole. + + + The uploaded file was too large. Please try to upload a smaller file. + Nahraný soubor je příliÅ¡ velký. Nahrajte prosím menší soubor. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF token je neplatný. Zkuste prosím znovu odeslat formulář. + + + This value is not a valid HTML5 color. + Tato hodnota není platná HTML5 barva. + + + Please enter a valid birthdate. + Prosím zadejte platný datum narození. + + + The selected choice is invalid. + Vybraná možnost není platná. + + + The collection is invalid. + Kolekce není platná. + + + Please select a valid color. + Prosím vyberte platnou barvu. + + + Please select a valid country. + Prosím vyberte platnou zemi. + + + Please select a valid currency. + Prosím vyberte platnou mÄ›nu. + + + Please choose a valid date interval. + Prosím vyberte platné rozpÄ›tí dat. + + + Please enter a valid date and time. + Prosím zadejte platný datum a Äas. + + + Please enter a valid date. + Prosím zadejte platný datum. + + + Please select a valid file. + Prosím vyberte platný soubor. + + + The hidden field is invalid. + Skryté pole není platné. + + + Please enter an integer. + Prosím zadejte Äíslo. + + + Please select a valid language. + Prosím zadejte platný jazyk. + + + Please select a valid locale. + Prosím zadejte platný jazyk. + + + Please enter a valid money amount. + Prosím zadejte platnou Äástku. + + + Please enter a number. + Prosím zadejte Äíslo. + + + The password is invalid. + Heslo není platné. + + + Please enter a percentage value. + Prosím zadejte procentuální hodnotu. + + + The values do not match. + Hodnoty se neshodují. + + + Please enter a valid time. + Prosím zadejte platný Äas. + + + Please select a valid timezone. + Prosím vyberte platné Äasové pásmo. + + + Please enter a valid URL. + Prosím zadejte platnou URL. + + + Please enter a valid search term. + Prosím zadejte platný výraz k vyhledání. + + + Please provide a valid phone number. + Prosím zadejte platné telefonní Äíslo. + + + The checkbox has an invalid value. + ZaÅ¡krtávací políÄko má neplatnou hodnotu. + + + Please enter a valid email address. + Prosím zadejte platnou emailovou adresu. + + + Please select a valid option. + Prosím vyberte platnou možnost. + + + Please select a valid range. + Prosím vyberte platný rozsah. + + + Please enter a valid week. + Prosím zadejte platný týden. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.cy.xlf b/vendor/symfony/form/Resources/translations/validators.cy.xlf new file mode 100644 index 0000000..48f18af --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.cy.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ni ddylai'r ffurflen gynnwys meysydd ychwanegol. + + + The uploaded file was too large. Please try to upload a smaller file. + Roedd y ffeil a uwchlwythwyd yn rhy fawr. Ceisiwch uwchlwytho ffeil llai. + + + The CSRF token is invalid. Please try to resubmit the form. + Mae'r tocyn CSRF yn annilys. Ceisiwch ailgyflwyno'r ffurflen. + + + This value is not a valid HTML5 color. + Nid yw'r gwerth hwn yn lliw HTML5 dilys. + + + Please enter a valid birthdate. + Nodwch ddyddiad geni dilys. + + + The selected choice is invalid. + Mae'r dewis a ddewiswyd yn annilys. + + + The collection is invalid. + Mae'r casgliad yn annilys. + + + Please select a valid color. + Dewiswch liw dilys. + + + Please select a valid country. + Dewiswch wlad ddilys. + + + Please select a valid currency. + Dewiswch arian cyfred dilys. + + + Please choose a valid date interval. + Dewiswch ystod dyddiadau dilys. + + + Please enter a valid date and time. + Nodwch ddyddiad ac amser dilys. + + + Please enter a valid date. + Nodwch ddyddiad dilys. + + + Please select a valid file. + Dewiswch ffeil ddilys. + + + The hidden field is invalid. + Mae'r maes cudd yn annilys. + + + Please enter an integer. + Nodwch rif cyfan. + + + Please select a valid language. + Dewiswch iaith ddilys. + + + Please select a valid locale. + Dewiswch leoliad dilys. + + + Please enter a valid money amount. + Nodwch swm arian dilys. + + + Please enter a number. + Nodwch rif. + + + The password is invalid. + Mae'r cyfrinair yn annilys. + + + Please enter a percentage value. + Nodwch werth canran. + + + The values do not match. + Nid yw'r gwerthoedd yn cyfateb. + + + Please enter a valid time. + Nodwch amser dilys. + + + Please select a valid timezone. + Dewiswch barth amser dilys. + + + Please enter a valid URL. + Nodwch URL dilys. + + + Please enter a valid search term. + Nodwch derm chwilio dilys. + + + Please provide a valid phone number. + Darparwch rif ffôn dilys. + + + The checkbox has an invalid value. + Mae gan y blwch ticio werth annilys. + + + Please enter a valid email address. + Nodwch gyfeiriad e-bost dilys. + + + Please select a valid option. + Dewiswch opsiwn dilys. + + + Please select a valid range. + Dewiswch ystod ddilys. + + + Please enter a valid week. + Nodwch wythnos ddilys. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.da.xlf b/vendor/symfony/form/Resources/translations/validators.da.xlf new file mode 100644 index 0000000..36f49b2 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.da.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Feltgruppen mÃ¥ ikke indeholde ekstra felter. + + + The uploaded file was too large. Please try to upload a smaller file. + Den uploadede fil var for stor. Upload venligst en mindre fil. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-token er ugyldig. Prøv venligst at genindsende. + + + This value is not a valid HTML5 color. + Værdien er ikke en gyldig HTML5 farve. + + + Please enter a valid birthdate. + Indtast venligst en gyldig fødselsdato. + + + The selected choice is invalid. + Den valgte mulighed er ugyldig . + + + The collection is invalid. + Samlingen er ugyldig. + + + Please select a valid color. + Vælg venligst en gyldig farve. + + + Please select a valid country. + Vælg venligst et gyldigt land. + + + Please select a valid currency. + Vælg venligst en gyldig valuta. + + + Please choose a valid date interval. + Vælg venligst et gyldigt datointerval. + + + Please enter a valid date and time. + Vælg venligst en gyldig dato og tid. + + + Please enter a valid date. + Vælg venligst en gyldig dato. + + + Please select a valid file. + Vælg venligst en gyldig fil. + + + The hidden field is invalid. + Det skjulte felt er ugyldigt. + + + Please enter an integer. + Indsæt veligst et heltal. + + + Please select a valid language. + Vælg venligst et gyldigt sprog. + + + Please select a valid locale. + Vælg venligst en gyldigt sprogkode. + + + Please enter a valid money amount. + Vælg venligst et gyldigt beløb. + + + Please enter a number. + Indtast venligst et nummer. + + + The password is invalid. + Passwordet er ugyldigt. + + + Please enter a percentage value. + Indtast venligst en procentværdi. + + + The values do not match. + Værdierne er ikke ens. + + + Please enter a valid time. + Indtast venligst en gyldig tid. + + + Please select a valid timezone. + Vælg venligst en gyldig tidszone. + + + Please enter a valid URL. + Indtast venligst en gyldig URL. + + + Please enter a valid search term. + Indtast venligst et gyldigt søgeord. + + + Please provide a valid phone number. + Giv venligst et gyldigt telefonnummer. + + + The checkbox has an invalid value. + Checkboxen har en ugyldigt værdi. + + + Please enter a valid email address. + Indtast venligst en gyldig e-mailadresse. + + + Please select a valid option. + Vælg venligst en gyldig mulighed. + + + Please select a valid range. + Vælg venligst et gyldigt interval . + + + Please enter a valid week. + Indtast venligst en gyldig uge. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.de.xlf b/vendor/symfony/form/Resources/translations/validators.de.xlf new file mode 100644 index 0000000..759fa2a --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.de.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Dieses Formular sollte keine zusätzlichen Felder enthalten. + + + The uploaded file was too large. Please try to upload a smaller file. + Die hochgeladene Datei ist zu groß. Versuchen Sie bitte eine kleinere Datei hochzuladen. + + + The CSRF token is invalid. Please try to resubmit the form. + Der CSRF-Token ist ungültig. Versuchen Sie bitte, das Formular erneut zu senden. + + + This value is not a valid HTML5 color. + Dieser Wert ist keine gültige HTML5 Farbe. + + + Please enter a valid birthdate. + Bitte geben Sie ein gültiges Geburtsdatum ein. + + + The selected choice is invalid. + Die Auswahl ist ungültig. + + + The collection is invalid. + Diese Gruppe von Feldern ist ungültig. + + + Please select a valid color. + Bitte geben Sie eine gültige Farbe ein. + + + Please select a valid country. + Bitte wählen Sie ein gültiges Land aus. + + + Please select a valid currency. + Bitte wählen Sie eine gültige Währung aus. + + + Please choose a valid date interval. + Bitte wählen Sie ein gültiges Datumsintervall. + + + Please enter a valid date and time. + Bitte geben Sie ein gültiges Datum samt Uhrzeit ein. + + + Please enter a valid date. + Bitte geben Sie ein gültiges Datum ein. + + + Please select a valid file. + Bitte wählen Sie eine gültige Datei. + + + The hidden field is invalid. + Das versteckte Feld ist ungültig. + + + Please enter an integer. + Bitte geben Sie eine ganze Zahl ein. + + + Please select a valid language. + Bitte wählen Sie eine gültige Sprache. + + + Please select a valid locale. + Bitte wählen Sie eine gültige Locale-Einstellung aus. + + + Please enter a valid money amount. + Bitte geben Sie einen gültigen Geldbetrag ein. + + + Please enter a number. + Bitte geben Sie eine gültige Zahl ein. + + + The password is invalid. + Das Kennwort ist ungültig. + + + Please enter a percentage value. + Bitte geben Sie einen gültigen Prozentwert ein. + + + The values do not match. + Die Werte stimmen nicht überein. + + + Please enter a valid time. + Bitte geben Sie eine gültige Uhrzeit ein. + + + Please select a valid timezone. + Bitte wählen Sie eine gültige Zeitzone. + + + Please enter a valid URL. + Bitte geben Sie eine gültige URL ein. + + + Please enter a valid search term. + Bitte geben Sie einen gültigen Suchbegriff ein. + + + Please provide a valid phone number. + Bitte geben Sie eine gültige Telefonnummer ein. + + + The checkbox has an invalid value. + Das Kontrollkästchen hat einen ungültigen Wert. + + + Please enter a valid email address. + Bitte geben Sie eine gültige E-Mail-Adresse ein. + + + Please select a valid option. + Bitte wählen Sie eine gültige Option. + + + Please select a valid range. + Bitte wählen Sie einen gültigen Bereich. + + + Please enter a valid week. + Bitte geben Sie eine gültige Woche ein. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.el.xlf b/vendor/symfony/form/Resources/translations/validators.el.xlf new file mode 100644 index 0000000..b544dcb --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.el.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Αυτή η φόÏμα δεν Ï€Ïέπει να πεÏιέχει επιπλέον πεδία. + + + The uploaded file was too large. Please try to upload a smaller file. + Το αÏχείο είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿. ΠαÏακαλοÏμε Ï€Ïοσπαθήστε να ανεβάσετε ένα μικÏότεÏο αÏχείο. + + + The CSRF token is invalid. Please try to resubmit the form. + Το CSRF token δεν είναι έγκυÏο. ΠαÏακαλοÏμε δοκιμάστε να υποβάλετε τη φόÏμα ξανά. + + + This value is not a valid HTML5 color. + Αυτή η τιμή δέν έναι έγκυÏο χÏώμα HTML5. + + + Please enter a valid birthdate. + ΠαÏακαλόυμε ειχάγεται μία έγκυÏη ημεÏομηνία γέννησης. + + + The selected choice is invalid. + Η επιλεγμένη επιλογή δέν είναι έγκυÏη. + + + The collection is invalid. + Η συλλογή δέν είναι έγκυÏη. + + + Please select a valid color. + ΠαÏακαλοÏμε επιλέξτε ένα έγκυÏο χÏώμα. + + + Please select a valid country. + ΠαÏακαλοÏμε επιλέξτε μία έγκυÏη χώÏα. + + + Please select a valid currency. + ΠαÏακαλοÏμε επιλέξτε ένα έγυÏο νόμισμα. + + + Please choose a valid date interval. + ΠαÏακαλοÏμε επιλέξτε ένα έγκυÏο διάστημα ημεÏομηνίας. + + + Please enter a valid date and time. + ΠαÏακαλοÏμε εισαγάγετε μια έγκυÏη ημεÏομηνία και ÏŽÏα. + + + Please enter a valid date. + ΠαÏακαλοÏμε εισάγετε μία έγκυÏη ημεÏομηνία. + + + Please select a valid file. + ΠαÏακαλοÏμε επιλέξτε ένα έγκυÏο αÏχείο. + + + The hidden field is invalid. + Το κÏυφό πεδίο δέν είναι έγκυÏο. + + + Please enter an integer. + ΠαÏακαλοÏμε εισάγετε έναν ακέÏαιο αÏιθμό. + + + Please select a valid language. + ΠαÏακαλοÏμε επιλέξτε μία έγκυÏη γλώσσα. + + + Please select a valid locale. + ΠαÏακαλοÏμε επιλέξτε μία έγκυÏη τοπικοποίηση. + + + Please enter a valid money amount. + ΠαÏακαλοÏμε εισάγετε ένα έγκυÏο χÏηματικό ποσό. + + + Please enter a number. + ΠαÏακαλοÏμε εισάγετε έναν αÏιθμό. + + + The password is invalid. + Ο κωδικός δέν είναι έγκυÏος. + + + Please enter a percentage value. + ΠαÏακαλοÏμε εισάγετε μία ποσοστιαία τιμή. + + + The values do not match. + Οι τιμές δέν ταιÏιάζουν. + + + Please enter a valid time. + ΠαÏακαλοÏμε εισάγετε μία έγκυÏη ÏŽÏα. + + + Please select a valid timezone. + ΠαÏακαλοÏμε επιλέξτε μία έγυÏη ζώνη ÏŽÏας. + + + Please enter a valid URL. + ΠαÏακαλοÏμε εισάγετε μια έγκυÏη διεÏθυνση URL. + + + Please enter a valid search term. + ΠαÏακαλοÏμε εισάγετε έναν έγκυÏο ÏŒÏο αναζήτησης. + + + Please provide a valid phone number. + ΠαÏακαλοÏμε καταχωÏίστε έναν έγκυÏο αÏιθμό τηλεφώνου. + + + The checkbox has an invalid value. + Το πλαίσιο ελέγχου έχει μή έγκυÏη τιμή. + + + Please enter a valid email address. + ΠαÏακαλοÏμε εισάγετε μία έγκυÏη ηλεκτÏονική διεÏθυνση. + + + Please select a valid option. + ΠαÏακαλοÏμε επιλέξτε μία έγκυÏη επιλογή. + + + Please select a valid range. + ΠαÏακαλοÏμε επιλέξτε ένα έγυÏο εÏÏος. + + + Please enter a valid week. + ΠαÏακαλοÏμε εισάγετε μία έγκυÏη εβδομάδα. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.en.xlf b/vendor/symfony/form/Resources/translations/validators.en.xlf new file mode 100644 index 0000000..57d3da9 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.en.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + This form should not contain extra fields. + + + The uploaded file was too large. Please try to upload a smaller file. + The uploaded file was too large. Please try to upload a smaller file. + + + The CSRF token is invalid. Please try to resubmit the form. + The CSRF token is invalid. Please try to resubmit the form. + + + This value is not a valid HTML5 color. + This value is not a valid HTML5 color. + + + Please enter a valid birthdate. + Please enter a valid birthdate. + + + The selected choice is invalid. + The selected choice is invalid. + + + The collection is invalid. + The collection is invalid. + + + Please select a valid color. + Please select a valid color. + + + Please select a valid country. + Please select a valid country. + + + Please select a valid currency. + Please select a valid currency. + + + Please choose a valid date interval. + Please choose a valid date interval. + + + Please enter a valid date and time. + Please enter a valid date and time. + + + Please enter a valid date. + Please enter a valid date. + + + Please select a valid file. + Please select a valid file. + + + The hidden field is invalid. + The hidden field is invalid. + + + Please enter an integer. + Please enter an integer. + + + Please select a valid language. + Please select a valid language. + + + Please select a valid locale. + Please select a valid locale. + + + Please enter a valid money amount. + Please enter a valid money amount. + + + Please enter a number. + Please enter a number. + + + The password is invalid. + The password is invalid. + + + Please enter a percentage value. + Please enter a percentage value. + + + The values do not match. + The values do not match. + + + Please enter a valid time. + Please enter a valid time. + + + Please select a valid timezone. + Please select a valid timezone. + + + Please enter a valid URL. + Please enter a valid URL. + + + Please enter a valid search term. + Please enter a valid search term. + + + Please provide a valid phone number. + Please provide a valid phone number. + + + The checkbox has an invalid value. + The checkbox has an invalid value. + + + Please enter a valid email address. + Please enter a valid email address. + + + Please select a valid option. + Please select a valid option. + + + Please select a valid range. + Please select a valid range. + + + Please enter a valid week. + Please enter a valid week. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.es.xlf b/vendor/symfony/form/Resources/translations/validators.es.xlf new file mode 100644 index 0000000..301e2b3 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.es.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Este formulario no debería contener campos adicionales. + + + The uploaded file was too large. Please try to upload a smaller file. + El archivo subido es demasiado grande. Por favor, suba un archivo más pequeño. + + + The CSRF token is invalid. Please try to resubmit the form. + El token CSRF no es válido. Por favor, pruebe a enviar nuevamente el formulario. + + + This value is not a valid HTML5 color. + Este valor no es un color HTML5 válido. + + + Please enter a valid birthdate. + Por favor, ingrese una fecha de cumpleaños válida. + + + The selected choice is invalid. + La opción seleccionada no es válida. + + + The collection is invalid. + La colección no es válida. + + + Please select a valid color. + Por favor, seleccione un color válido. + + + Please select a valid country. + Por favor, seleccione un país válido. + + + Please select a valid currency. + Por favor, seleccione una moneda válida. + + + Please choose a valid date interval. + Por favor, elija un intervalo de fechas válido. + + + Please enter a valid date and time. + Por favor, ingrese una fecha y hora válidas. + + + Please enter a valid date. + Por favor, ingrese una fecha valida. + + + Please select a valid file. + Por favor, seleccione un archivo válido. + + + The hidden field is invalid. + El campo oculto no es válido. + + + Please enter an integer. + Por favor, ingrese un número entero. + + + Please select a valid language. + Por favor, seleccione un idioma válido. + + + Please select a valid locale. + Por favor, seleccione una configuración regional válida. + + + Please enter a valid money amount. + Por favor, ingrese una cantidad de dinero válida. + + + Please enter a number. + Por favor, ingrese un número. + + + The password is invalid. + La contraseña no es válida. + + + Please enter a percentage value. + Por favor, ingrese un valor porcentual. + + + The values do not match. + Los valores no coinciden. + + + Please enter a valid time. + Por favor, ingrese una hora válida. + + + Please select a valid timezone. + Por favor, seleccione una zona horaria válida. + + + Please enter a valid URL. + Por favor, ingrese una URL válida. + + + Please enter a valid search term. + Por favor, ingrese un término de búsqueda válido. + + + Please provide a valid phone number. + Por favor, proporcione un número de teléfono válido. + + + The checkbox has an invalid value. + La casilla de verificación tiene un valor inválido. + + + Please enter a valid email address. + Por favor, ingrese una dirección de correo electrónico válida. + + + Please select a valid option. + Por favor, seleccione una opción válida. + + + Please select a valid range. + Por favor, seleccione un rango válido. + + + Please enter a valid week. + Por favor, ingrese una semana válida. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.et.xlf b/vendor/symfony/form/Resources/translations/validators.et.xlf new file mode 100644 index 0000000..0767220 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.et.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Väljade grupp ei tohiks sisalda lisaväljasid. + + + The uploaded file was too large. Please try to upload a smaller file. + Üleslaaditud fail oli liiga suur. Palun proovi uuesti väiksema failiga. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-märgis on vigane. Palun proovi vormi uuesti esitada. + + + This value is not a valid HTML5 color. + See väärtus ei ole korrektne HTML5 värv. + + + Please enter a valid birthdate. + Palun sisesta korrektne sünnikuupäev. + + + The selected choice is invalid. + Tehtud valik on vigane. + + + The collection is invalid. + Kogum on vigane. + + + Please select a valid color. + Palun vali korrektne värv. + + + Please select a valid country. + Palun vali korrektne riik. + + + Please select a valid currency. + Palun vali korrektne valuuta. + + + Please choose a valid date interval. + Palun vali korrektne kuupäevade vahemik. + + + Please enter a valid date and time. + Palun sisesta korrektne kuupäev ja kellaaeg. + + + Please enter a valid date. + Palun sisesta korrektne kuupäev. + + + Please select a valid file. + Palun vali korrektne fail. + + + The hidden field is invalid. + Peidetud väli on vigane. + + + Please enter an integer. + Palun sisesta täisarv. + + + Please select a valid language. + Palun vali korrektne keel. + + + Please select a valid locale. + Palun vali korrektne keelekood. + + + Please enter a valid money amount. + Palun sisesta korrektne rahaline väärtus. + + + Please enter a number. + Palun sisesta number. + + + The password is invalid. + Vigane parool. + + + Please enter a percentage value. + Palun sisesta protsendiline väärtus. + + + The values do not match. + Väärtused ei klapi. + + + Please enter a valid time. + Palun sisesta korrektne aeg. + + + Please select a valid timezone. + Palun vali korrektne ajavöönd. + + + Please enter a valid URL. + Palun sisesta korrektne URL. + + + Please enter a valid search term. + Palun sisesta korrektne otsingutermin. + + + Please provide a valid phone number. + Palun sisesta korrektne telefoninumber. + + + The checkbox has an invalid value. + Märkeruudu väärtus on vigane. + + + Please enter a valid email address. + Palun sisesta korrektne e-posti aadress. + + + Please select a valid option. + Palun tee korrektne valik. + + + Please select a valid range. + Palun vali korrektne vahemik. + + + Please enter a valid week. + Palun sisesta korrektne nädal. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.eu.xlf b/vendor/symfony/form/Resources/translations/validators.eu.xlf new file mode 100644 index 0000000..a73c63a --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.eu.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Formulario honek ez luke aparteko eremurik eduki behar. + + + The uploaded file was too large. Please try to upload a smaller file. + Igotako fitxategia handiegia da. Mesedez saiatu fitxategi txikiago bat igotzen. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF tokena baliogabea da. Mesedez, saiatu berriro formularioa bidaltzen. + + + This value is not a valid HTML5 color. + Balio hori ez da HTML5 kolore onargarria. + + + Please enter a valid birthdate. + Mesedez, sartu baliozko urtebetetze-eguna. + + + The selected choice is invalid. + Hautatutako aukera ez da egokia. + + + The collection is invalid. + Bilduma ez da baliozkoa. + + + Please select a valid color. + Mesedez, hautatu baliozko kolore bat. + + + Please select a valid country. + Mesedez, hautatu baliozko herrialde bat. + + + Please select a valid currency. + Mesedez, hautatu baliozko moneta bat. + + + Please choose a valid date interval. + Mesedez, hautatu baliozko data-tarte bat. + + + Please enter a valid date and time. + Mesedez, sartu baliozko data eta ordua. + + + Please enter a valid date. + Mesedez, sartu baliozko data bat. + + + Please select a valid file. + Mesedez, hautatu baliozko fitxategi bat. + + + The hidden field is invalid. + Eremu ezkutua ez da baliozkoa. + + + Please enter an integer. + Mesedez, sartu zenbaki oso bat. + + + Please select a valid language. + Mesedez, hautatu baliozko hizkuntza bat. + + + Please select a valid locale. + Mesedez, hautatu baliozko eskualde-konfigurazio bat. + + + Please enter a valid money amount. + Mesedez, sartu baliozko diru-kopuru bat. + + + Please enter a number. + Mesedez, sartu zenbaki bat. + + + The password is invalid. + Pasahitza ez da zuzena. + + + Please enter a percentage value. + Mesedez, sartu portzentajezko balio bat. + + + The values do not match. + Balioak ez datoz bat. + + + Please enter a valid time. + Mesedez, sartu baliozko ordu bat. + + + Please select a valid timezone. + Mesedez, hautatu baliozko ordu-eremua. + + + Please enter a valid URL. + Mesedez, sartu baliozko URL bat. + + + Please enter a valid search term. + Mesedez, sartu bilaketa-termino onargarri bat. + + + Please provide a valid phone number. + Mesedez, eman baliozko telefono-zenbaki bat. + + + The checkbox has an invalid value. + Egiaztatze-laukiak balio baliogabea du. + + + Please enter a valid email address. + Mesedez, sartu baliozko helbide elektroniko bat. + + + Please select a valid option. + Mesedez, hautatu baliozko aukera bat. + + + Please select a valid range. + Mesedez, hautatu baliozko tarte bat. + + + Please enter a valid week. + Mesedez, sartu baliozko aste bat. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.fa.xlf b/vendor/symfony/form/Resources/translations/validators.fa.xlf new file mode 100644 index 0000000..2ebb1cc --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.fa.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + این ÙØ±Ù… نباید شامل Ùیلدهای اضاÙÛŒ باشد. + + + The uploaded file was too large. Please try to upload a smaller file. + ÙØ§ÛŒÙ„ بارگذاری‌شده بسیار بزرگ است. Ù„Ø·ÙØ§Ù‹ ÙØ§ÛŒÙ„ کوچک‌تری را بارگذاری نمایید. + + + The CSRF token is invalid. Please try to resubmit the form. + توکن CSRF نامعتبر است. Ù„Ø·ÙØ§Ù‹ ÙØ±Ù… را مجدداً ارسال نمایید. + + + This value is not a valid HTML5 color. + این مقدار یک رنگ معتبر HTML5 نیست. + + + Please enter a valid birthdate. + Ù„Ø·ÙØ§Ù‹ یک تاریخ تولد معتبر وارد نمایید. + + + The selected choice is invalid. + گزینه‌ انتخاب‌ شده نامعتبر است. + + + The collection is invalid. + این مجموعه نامعتبر است. + + + Please select a valid color. + Ù„Ø·ÙØ§Ù‹ یک رنگ معتبر انتخاب کنید. + + + Please select a valid country. + Ù„Ø·ÙØ§Ù‹ یک کشور معتبر انتخاب کنید. + + + Please select a valid currency. + Ù„Ø·ÙØ§Ù‹ یک واحد پول معتبر انتخاب کنید. + + + Please choose a valid date interval. + Ù„Ø·ÙØ§Ù‹ یک بازه‌ زمانی معتبر انتخاب کنید. + + + Please enter a valid date and time. + Ù„Ø·ÙØ§Ù‹ یک تاریخ Ùˆ زمان معتبر وارد کنید. + + + Please enter a valid date. + Ù„Ø·ÙØ§Ù‹ یک تاریخ معتبر وارد کنید. + + + Please select a valid file. + Ù„Ø·ÙØ§Ù‹ یک ÙØ§ÛŒÙ„ معتبر انتخاب کنید. + + + The hidden field is invalid. + Ùیلد مخÙÛŒ نامعتبر است. + + + Please enter an integer. + Ù„Ø·ÙØ§Ù‹ یک عدد صحیح وارد کنید. + + + Please select a valid language. + Ù„Ø·ÙØ§Ù‹ یک زبان معتبر انتخاب کنید. + + + Please select a valid locale. + Ù„Ø·ÙØ§Ù‹ یک منطقه‌جغراÙیایی (locale) معتبر انتخاب کنید. + + + Please enter a valid money amount. + Ù„Ø·ÙØ§Ù‹ یک مقدار پول معتبر وارد کنید. + + + Please enter a number. + Ù„Ø·ÙØ§Ù‹ یک عدد وارد کنید. + + + The password is invalid. + رمزعبور نامعتبر است. + + + Please enter a percentage value. + Ù„Ø·ÙØ§Ù‹ یک درصد معتبر وارد کنید. + + + The values do not match. + مقادیر تطابق ندارند. + + + Please enter a valid time. + Ù„Ø·ÙØ§Ù‹ یک زمان معتبر وارد کنید. + + + Please select a valid timezone. + Ù„Ø·ÙØ§Ù‹ یک منطقه‌زمانی معتبر وارد کنید. + + + Please enter a valid URL. + Ù„Ø·ÙØ§Ù‹ یک URL معتبر وارد کنید. + + + Please enter a valid search term. + Ù„Ø·ÙØ§Ù‹ یک عبارت جستجوی معتبر وارد کنید. + + + Please provide a valid phone number. + Ù„Ø·ÙØ§Ù‹ یک شماره تلÙÙ† معتبر وارد کنید. + + + The checkbox has an invalid value. + کادر انتخاب (checkbox) دارای مقداری نامعتبر است. + + + Please enter a valid email address. + Ù„Ø·ÙØ§Ù‹ یک آدرس رایانامه (ایمیل) معتبر وارد کنید. + + + Please select a valid option. + Ù„Ø·ÙØ§Ù‹ یک گزینه‌ معتبر انتخاب کنید. + + + Please select a valid range. + Ù„Ø·ÙØ§Ù‹ یک محدوده‌ معتبر انتخاب کنید. + + + Please enter a valid week. + Ù„Ø·ÙØ§Ù‹ یک Ù‡ÙØªÙ‡â€Œ معتبر وارد کنید. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.fi.xlf b/vendor/symfony/form/Resources/translations/validators.fi.xlf new file mode 100644 index 0000000..4383654 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.fi.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Tämä lomake ei voi sisältää ylimääräisiä kenttiä. + + + The uploaded file was too large. Please try to upload a smaller file. + Ladattu tiedosto on liian iso. Ole hyvä ja lataa pienempi tiedosto. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-tarkiste on virheellinen. Ole hyvä ja yritä lähettää lomake uudestaan. + + + This value is not a valid HTML5 color. + Tämä arvo ei ole kelvollinen HTML5-väri. + + + Please enter a valid birthdate. + Syötä kelvollinen syntymäaika. + + + The selected choice is invalid. + Valittu vaihtoehto ei kelpaa. + + + The collection is invalid. + Ryhmä ei kelpaa. + + + Please select a valid color. + Valitse kelvollinen väri. + + + Please select a valid country. + Valitse kelvollinen maa. + + + Please select a valid currency. + Valitse kelvollinen valuutta. + + + Please choose a valid date interval. + Valitse kelvollinen aikaväli. + + + Please enter a valid date and time. + Syötä kelvolliset päivä ja aika. + + + Please enter a valid date. + Syötä kelvollinen päivä. + + + Please select a valid file. + Valitse kelvollinen tiedosto. + + + The hidden field is invalid. + Piilotettu kenttä ei ole kelvollinen. + + + Please enter an integer. + Syötä kokonaisluku. + + + Please select a valid language. + Valitse kelvollinen kieli. + + + Please select a valid locale. + Valitse kelvollinen kielikoodi. + + + Please enter a valid money amount. + Syötä kelvollinen rahasumma. + + + Please enter a number. + Syötä numero. + + + The password is invalid. + Salasana ei kelpaa. + + + Please enter a percentage value. + Syötä prosenttiluku. + + + The values do not match. + Arvot eivät vastaa toisiaan. + + + Please enter a valid time. + Syötä kelvollinen kellonaika. + + + Please select a valid timezone. + Valitse kelvollinen aikavyöhyke. + + + Please enter a valid URL. + Syötä kelvollinen URL. + + + Please enter a valid search term. + Syötä kelvollinen hakusana. + + + Please provide a valid phone number. + Anna kelvollinen puhelinnumero. + + + The checkbox has an invalid value. + Valintaruudun arvo ei kelpaa. + + + Please enter a valid email address. + Syötä kelvollinen sähköpostiosoite. + + + Please select a valid option. + Valitse kelvollinen vaihtoehto. + + + Please select a valid range. + Valitse kelvollinen väli. + + + Please enter a valid week. + Syötä kelvollinen viikko. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.fr.xlf b/vendor/symfony/form/Resources/translations/validators.fr.xlf new file mode 100644 index 0000000..cbfb4f8 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.fr.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ce formulaire ne doit pas contenir de champs supplémentaires. + + + The uploaded file was too large. Please try to upload a smaller file. + Le fichier téléchargé est trop volumineux. Merci d'essayer d'envoyer un fichier plus petit. + + + The CSRF token is invalid. Please try to resubmit the form. + Le jeton CSRF est invalide. Veuillez renvoyer le formulaire. + + + This value is not a valid HTML5 color. + Cette valeur n'est pas une couleur HTML5 valide. + + + Please enter a valid birthdate. + Veuillez entrer une date de naissance valide. + + + The selected choice is invalid. + Le choix sélectionné est invalide. + + + The collection is invalid. + La collection est invalide. + + + Please select a valid color. + Veuillez sélectionner une couleur valide. + + + Please select a valid country. + Veuillez sélectionner un pays valide. + + + Please select a valid currency. + Veuillez sélectionner une devise valide. + + + Please choose a valid date interval. + Veuillez choisir un intervalle de dates valide. + + + Please enter a valid date and time. + Veuillez saisir une date et une heure valides. + + + Please enter a valid date. + Veuillez entrer une date valide. + + + Please select a valid file. + Veuillez sélectionner un fichier valide. + + + The hidden field is invalid. + Le champ masqué n'est pas valide. + + + Please enter an integer. + Veuillez saisir un entier. + + + Please select a valid language. + Veuillez sélectionner une langue valide. + + + Please select a valid locale. + Veuillez sélectionner une langue valide. + + + Please enter a valid money amount. + Veuillez saisir un montant valide. + + + Please enter a number. + Veuillez saisir un nombre. + + + The password is invalid. + Le mot de passe est invalide. + + + Please enter a percentage value. + Veuillez saisir un pourcentage valide. + + + The values do not match. + Les valeurs ne correspondent pas. + + + Please enter a valid time. + Veuillez saisir une heure valide. + + + Please select a valid timezone. + Veuillez sélectionner un fuseau horaire valide. + + + Please enter a valid URL. + Veuillez saisir une URL valide. + + + Please enter a valid search term. + Veuillez saisir un terme de recherche valide. + + + Please provide a valid phone number. + Veuillez fournir un numéro de téléphone valide. + + + The checkbox has an invalid value. + La case à cocher a une valeur non valide. + + + Please enter a valid email address. + Veuillez saisir une adresse email valide. + + + Please select a valid option. + Veuillez sélectionner une option valide. + + + Please select a valid range. + Veuillez sélectionner une plage valide. + + + Please enter a valid week. + Veuillez entrer une semaine valide. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.gl.xlf b/vendor/symfony/form/Resources/translations/validators.gl.xlf new file mode 100644 index 0000000..e3427f8 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.gl.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Este formulario non debería conter campos adicionais. + + + The uploaded file was too large. Please try to upload a smaller file. + O arquivo subido é demasiado grande. Por favor, suba un arquivo máis pequeno. + + + The CSRF token is invalid. Please try to resubmit the form. + O token CSRF non é válido. Por favor, probe a enviar novamente o formulario. + + + This value is not a valid HTML5 color. + Este valor non é unha cor HTML5 válida. + + + Please enter a valid birthdate. + Insire unha data de aniversario válida. + + + The selected choice is invalid. + A opción seleccionada non é válida. + + + The collection is invalid. + A colección non é válida. + + + Please select a valid color. + Por favor, seleccione unha cor válida. + + + Please select a valid country. + Por favor, seleccione un país válido. + + + Please select a valid currency. + Por favor, seleccione unha moeda válida. + + + Please choose a valid date interval. + Por favor, escolla un intervalo de datas válido. + + + Please enter a valid date and time. + Por favor, introduza unha data e hora válidas. + + + Please enter a valid date. + Por favor, introduce unha data válida. + + + Please select a valid file. + Por favor, seleccione un ficheiro válido. + + + The hidden field is invalid. + O campo oculto non é válido. + + + Please enter an integer. + Por favor, introduza un número enteiro. + + + Please select a valid language. + Por favor, selecciona un idioma válido. + + + Please select a valid locale. + Por favor, seleccione unha configuración rexional válida. + + + Please enter a valid money amount. + Por favor, introduza unha cantidade de diñeiro válida. + + + Please enter a number. + Por favor, introduza un número. + + + The password is invalid. + O contrasinal non é válido. + + + Please enter a percentage value. + Por favor, introduza un valor porcentual. + + + The values do not match. + Os valores non coinciden. + + + Please enter a valid time. + Por favor, introduza unha hora válida. + + + Please select a valid timezone. + Por favor, selecciona unha zona horaria válida. + + + Please enter a valid URL. + Por favor, introduce un URL válido. + + + Please enter a valid search term. + Por favor, introduce un termo de busca válido. + + + Please provide a valid phone number. + Por favor, fornecer un número de teléfono válido. + + + The checkbox has an invalid value. + A caixa de verificación ten un valor non válido. + + + Please enter a valid email address. + Por favor, introduce un enderezo de correo electrónico válido. + + + Please select a valid option. + Por favor, seleccione unha opción válida. + + + Please select a valid range. + Por favor, seleccione un intervalo válido. + + + Please enter a valid week. + Por favor, introduce unha semana válida. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.he.xlf b/vendor/symfony/form/Resources/translations/validators.he.xlf new file mode 100644 index 0000000..41428ac --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.he.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + הטופס ×œ× ×¦×¨×™×š להכיל שדות נוספי×. + + + The uploaded file was too large. Please try to upload a smaller file. + הקובץ שהועלה גדול מדי. נסה להעלות קובץ קטן יותר. + + + The CSRF token is invalid. Please try to resubmit the form. + ×סימון CSRF ×ינו חוקי. ×× × × ×¡×” לשלוח שוב ×ת הטופס. + + + This value is not a valid HTML5 color. + ערך ×–×” ×ינו צבע HTML5 חוקי. + + + Please enter a valid birthdate. + × × ×œ×”×–×™×Ÿ ×ת ת×ריך לידה תקני. + + + The selected choice is invalid. + הבחירה שנבחרה ××™× ×” חוקית. + + + The collection is invalid. + ×”×וסף ×ינו חוקי. + + + Please select a valid color. + ×× × ×‘×—×¨ צבע חוקי. + + + Please select a valid country. + ×× × ×‘×—×¨ מדינה חוקית. + + + Please select a valid currency. + ×× × ×‘×—×¨ מטבע חוקי. + + + Please choose a valid date interval. + ×× × ×‘×—×¨ מרווח ת××¨×™×›×™× ×—×•×§×™. + + + Please enter a valid date and time. + ×× × ×”×–×Ÿ ת×ריך ושעה תקני×. + + + Please enter a valid date. + × × ×œ×”×–×™×Ÿ ת×ריך חוקי. + + + Please select a valid file. + ×× × ×‘×—×¨ קובץ חוקי. + + + The hidden field is invalid. + השדה הנסתר ×ינו חוקי. + + + Please enter an integer. + ×× × ×”×–×Ÿ מספר של×. + + + Please select a valid language. + ×× × ×‘×—×¨ שפה חוקי. + + + Please select a valid locale. + ×× × ×‘×—×¨ שפה מקומית. + + + Please enter a valid money amount. + ×× × ×”×–×Ÿ ×¡×›×•× ×›×¡×£ חוקי. + + + Please enter a number. + ×× × ×”×–×Ÿ מספר. + + + The password is invalid. + הסיסמה ××™× ×” חוקית. + + + Please enter a percentage value. + ×× × ×”×–×Ÿ ערך ב×חוזי×. + + + The values do not match. + ×”×¢×¨×›×™× ××™× × ×ª×•×מי×. + + + Please enter a valid time. + ×× × ×”×–×Ÿ שעה חוקי. + + + Please select a valid timezone. + ×× × ×‘×—×¨ ×זור זמן חוקי. + + + Please enter a valid URL. + × × ×œ×”×–×™×Ÿ ×ת כתובת ×תר חוקית. + + + Please enter a valid search term. + ×× × ×”×–×Ÿ מונח חיפוש חוקי. + + + Please provide a valid phone number. + ×× × ×¡×¤×§ מספר טלפון חוקי. + + + The checkbox has an invalid value. + לתיבת הסימון יש ערך ×œ× ×—×•×§×™. + + + Please enter a valid email address. + ×× × ×”×–×Ÿ כתובת דו×"ל תקנית. + + + Please select a valid option. + ×× × ×‘×—×¨ ×פשרות חוקית. + + + Please select a valid range. + ×× × ×‘×—×¨ טווח חוקי. + + + Please enter a valid week. + ×× × ×”×–×Ÿ שבוע תקף. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.hr.xlf b/vendor/symfony/form/Resources/translations/validators.hr.xlf new file mode 100644 index 0000000..e3aa7b2 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.hr.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ovaj obrazac ne smije sadržavati dodatna polja. + + + The uploaded file was too large. Please try to upload a smaller file. + Prenesena datoteka je prevelika. Molim pokuÅ¡ajte prenijeti manju datoteku. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF vrijednost nije ispravna. PokuÅ¡ajte ponovo poslati obrazac. + + + This value is not a valid HTML5 color. + Ova vrijednost nije važeća HTML5 boja. + + + Please enter a valid birthdate. + Molim upiÅ¡ite ispravan datum roÄ‘enja. + + + The selected choice is invalid. + Odabrani izbor nije ispravan. + + + The collection is invalid. + Kolekcija nije ispravna. + + + Please select a valid color. + Molim odaberite ispravnu boju. + + + Please select a valid country. + Molim odaberite ispravnu državu. + + + Please select a valid currency. + Molim odaberite ispravnu valutu. + + + Please choose a valid date interval. + Molim odaberite ispravni vremenski interval. + + + Please enter a valid date and time. + Molim unesite ispravni datum i vrijeme. + + + Please enter a valid date. + Molim odaberite ispravan datum. + + + Please select a valid file. + Molim odaberite ispravnu datoteku. + + + The hidden field is invalid. + Skriveno polje nije ispravno. + + + Please enter an integer. + Molim unesite cijeli broj. + + + Please select a valid language. + Molim odaberite ispravan jezik. + + + Please select a valid locale. + Molim odaberite ispravnu lokalizaciju. + + + Please enter a valid money amount. + Molim unesite ispravan iznos novca. + + + Please enter a number. + Molim unesite broj. + + + The password is invalid. + Ova lozinka nije ispravna. + + + Please enter a percentage value. + Molim unesite vrijednost postotka. + + + The values do not match. + Ove vrijednosti se ne poklapaju. + + + Please enter a valid time. + Molim unesite ispravno vrijeme. + + + Please select a valid timezone. + Molim odaberite ispravnu vremensku zonu. + + + Please enter a valid URL. + Molim unesite ispravan URL. + + + Please enter a valid search term. + Molim unesite ispravan pojam za pretraživanje. + + + Please provide a valid phone number. + Molim navedite ispravan telefonski broj. + + + The checkbox has an invalid value. + Polje za potvrdu sadrži neispravnu vrijednost. + + + Please enter a valid email address. + Molim unesite valjanu adresu elektronske poÅ¡te. + + + Please select a valid option. + Molim odaberite ispravnu opciju. + + + Please select a valid range. + Molim odaberite ispravan raspon. + + + Please enter a valid week. + Molim unesite ispravni tjedan. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.hu.xlf b/vendor/symfony/form/Resources/translations/validators.hu.xlf new file mode 100644 index 0000000..0ea74fe --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.hu.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ez a mezÅ‘csoport nem tartalmazhat extra mezÅ‘ket. + + + The uploaded file was too large. Please try to upload a smaller file. + A feltöltött fájl túl nagy. Kérem, próbáljon egy kisebb fájlt feltölteni. + + + The CSRF token is invalid. Please try to resubmit the form. + Érvénytelen CSRF token. Kérem, próbálja újra elküldeni az űrlapot. + + + This value is not a valid HTML5 color. + Ez az érték nem egy érvényes HTML5 szín. + + + Please enter a valid birthdate. + Kérjük, adjon meg egy valós születési dátumot. + + + The selected choice is invalid. + A kiválasztott opció érvénytelen. + + + The collection is invalid. + A gyűjtemény érvénytelen. + + + Please select a valid color. + Kérjük, válasszon egy érvényes színt. + + + Please select a valid country. + Kérjük, válasszon egy érvényes országot. + + + Please select a valid currency. + Kérjük, válasszon egy érvényes pénznemet. + + + Please choose a valid date interval. + Kérjük, válasszon egy érvényes dátumintervallumot. + + + Please enter a valid date and time. + Kérjük, adjon meg egy érvényes dátumot és idÅ‘pontot. + + + Please enter a valid date. + Kérjük, adjon meg egy érvényes dátumot. + + + Please select a valid file. + Kérjük, válasszon egy érvényes fájlt. + + + The hidden field is invalid. + A rejtett mezÅ‘ érvénytelen. + + + Please enter an integer. + Kérjük, adjon meg egy egész számot. + + + Please select a valid language. + Kérjük, válasszon egy érvényes nyelvet. + + + Please select a valid locale. + Kérjük, válasszon egy érvényes területi beállítást. + + + Please enter a valid money amount. + Kérjük, adjon meg egy érvényes pénzösszeget. + + + Please enter a number. + Kérjük, adjon meg egy számot. + + + The password is invalid. + A jelszó érvénytelen. + + + Please enter a percentage value. + Kérjük, adjon meg egy százalékos értéket. + + + The values do not match. + Az értékek nem egyeznek. + + + Please enter a valid time. + Kérjük, adjon meg egy érvényes idÅ‘pontot. + + + Please select a valid timezone. + Kérjük, válasszon érvényes idÅ‘zónát. + + + Please enter a valid URL. + Kérjük, adjon meg egy érvényes URL-t. + + + Please enter a valid search term. + Kérjük, adjon meg egy érvényes keresési kifejezést. + + + Please provide a valid phone number. + Kérjük, adjon egy érvényes telefonszámot + + + The checkbox has an invalid value. + A jelölÅ‘négyzet értéke érvénytelen. + + + Please enter a valid email address. + Kérjük valós e-mail címet adjon meg. + + + Please select a valid option. + Kérjük, válasszon egy érvényes beállítást. + + + Please select a valid range. + Kérjük, válasszon egy érvényes tartományt. + + + Please enter a valid week. + Kérjük, adjon meg egy érvényes hetet. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.hy.xlf b/vendor/symfony/form/Resources/translations/validators.hy.xlf new file mode 100644 index 0000000..ccca247 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.hy.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ô±ÕµÕ½ Õ±Ö‡Õ¨ Õ¹ÕºÕ¥Õ¿Ö„ Õ§ ÕºÕ¡Ö€Õ¸Ö‚Õ¶Õ¡Õ¯Õ« Õ¬Ö€Õ¡ÖÕ¸Ö‚ÖÕ«Õ¹ Õ¿Õ¸Õ²Õ¥Ö€Ö‰ + + + The uploaded file was too large. Please try to upload a smaller file. + ÕŽÕ¥Ö€Õ¢Õ¥Õ¼Õ¶Õ¾Õ¡Õ® Ö†Õ¡ÕµÕ¬Õ¨ Õ¹Õ¡ÖƒÕ¡Õ¦Õ¡Õ¶Ö Õ´Õ¥Õ® Õ§. Ô½Õ¶Õ¤Ö€Õ¾Õ¸Ö‚Õ´ Õ§ Õ¾Õ¥Ö€Õ¢Õ¥Õ¼Õ¶Õ¥Õ¬ Õ¡Õ¾Õ¥Õ¬Õ« ÖƒÕ¸Ö„Ö€ Õ¹Õ¡ÖƒÕ½Õ« Ö†Õ¡ÕµÕ¬Ö‰ + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF Õ¡Ö€ÕªÕ¥Ö„Õ¨ Õ¡Õ¶Õ©Õ¸Ö‚ÕµÕ¬Õ¡Õ¿Ö€Õ¥Õ¬Õ« Õ§. Õ“Õ¸Ö€Õ±Õ¥Ö„ Õ¶Õ¸Ö€Õ«Ö Õ¸Ö‚Õ²Õ¡Ö€Õ¯Õ¥Õ¬ Õ±Ö‡Õ¨Ö‰ + + + This value is not a valid HTML5 color. + Ô±ÕµÕ½ Õ¡Ö€ÕªÕ¥Ö„Õ¨ Õ¾Õ¡Õ¾Õ¥Ö€ HTML5 Õ£Õ¸Ö‚ÕµÕ¶ Õ¹Õ§Ö‰ + + + Please enter a valid birthdate. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ®Õ¶Õ¶Õ¤ÕµÕ¡Õ¶ Õ¡Õ´Õ½Õ¡Õ©Õ«Õ¾Ö‰ + + + The selected choice is invalid. + Ô¸Õ¶Õ¿Ö€Õ¾Õ¡Õ® Õ¨Õ¶Õ¿Ö€Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶Õ¶ Õ¡Õ¶Õ¾Õ¡Õ¾Õ¥Ö€ Õ§Ö‰ + + + The collection is invalid. + Õ€Õ¡Õ´Õ¡Õ­Õ¸Ö‚Õ´Õ¢Õ¶ Õ¡Õ¶Õ¾Õ¡Õ¾Õ¥Ö€ Õ§Ö‰ + + + Please select a valid color. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ£Õ¸Ö‚ÕµÕ¶Ö‰ + + + Please select a valid country. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¥Ö€Õ¯Õ«Ö€Ö‰ + + + Please select a valid currency. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¡Ö€ÕªÕ¸Ö‚ÕµÕ©Ö‰ + + + Please choose a valid date interval. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ³Õ«Õ·Õ¿ Õ¡Õ´Õ½Õ¡Õ©Õ¾Õ¥Ö€Õ« Õ´Õ«Õ»Õ¡Õ¯Õ¡ÕµÖ„Ö‰ + + + Please enter a valid date and time. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¡Õ´Õ½Õ¡Õ©Õ«Õ¾ Ö‡ ÕªÕ¡Õ´Ö‰ + + + Please enter a valid date. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¡Õ´Õ½Õ¡Õ©Õ«Õ¾Ö‰ + + + Please select a valid file. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Ö†Õ¡ÕµÕ¬Ö‰ + + + The hidden field is invalid. + Ô¹Õ¡Ö„Õ¶Õ¾Õ¡Õ® Õ¤Õ¡Õ·Õ¿Õ¨ Õ¡Õ¶Õ¾Õ¡Õ¾Õ¥Ö€ Õ§Ö‰ + + + Please enter an integer. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¡Õ´Õ¢Õ¸Õ²Õ» Õ©Õ«Õ¾Ö‰ + + + Please select a valid language. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¬Õ¥Õ¦Õ¸Ö‚Ö‰ + + + Please select a valid locale. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¿Õ¥Õ²Õ¡ÕµÕ¶Õ¡ÖÕ¸Ö‚Õ´Ö‰ + + + Please enter a valid money amount. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ£Õ¸Ö‚Õ´Õ¡Ö€Ö‰ + + + Please enter a number. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ°Õ¡Õ´Õ¡Ö€Ö‰ + + + The password is invalid. + Ô³Õ¡Õ²Õ¿Õ¶Õ¡Õ¢Õ¡Õ¼Õ¶ Õ¡Õ¶Õ¾Õ¡Õ¾Õ¥Ö€ Õ§Ö‰ + + + Please enter a percentage value. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¿Õ¸Õ¯Õ¸Õ½Õ¡ÕµÕ«Õ¶ Õ¡Ö€ÕªÕ¥Ö„Ö‰ + + + The values do not match. + Ô±Ö€ÕªÕ¥Ö„Õ¶Õ¥Ö€Õ¨ Õ¹Õ¥Õ¶ Õ°Õ¡Õ´Õ¨Õ¶Õ¯Õ¶Õ¸Ö‚Õ´Ö‰ + + + Please enter a valid time. + Õ„Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Ö„ Õ¾Õ¡Õ¾Õ¥Ö€ ÕªÕ¡Õ´Õ¡Õ¶Õ¡Õ¯Ö‰ + + + Please select a valid timezone. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ ÕªÕ¡Õ´Õ¡ÕµÕ«Õ¶ Õ£Õ¸Õ¿Õ«Ö‰ + + + Please enter a valid URL. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ URLÖ‰ + + + Please enter a valid search term. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¸Ö€Õ¸Õ¶Õ´Õ¡Õ¶ Õ¿Õ¥Ö€Õ´Õ«Õ¶Ö‰ + + + Please provide a valid phone number. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¿Ö€Õ¡Õ´Õ¡Õ¤Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ°Õ¥Õ¼Õ¡Õ­Õ¸Õ½Õ¡Õ°Õ¡Õ´Õ¡Ö€Ö‰ + + + The checkbox has an invalid value. + Õ†Õ·Õ´Õ¡Õ¶ Õ¾Õ¡Õ¶Õ¤Õ¡Õ¯Õ¨ Õ¡Õ¶Õ¾Õ¡Õ¾Õ¥Ö€ Õ¡Ö€ÕªÕ¥Ö„ Õ¸Ö‚Õ¶Õ«Ö‰ + + + Please enter a valid email address. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ´Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ§Õ¬-Õ°Õ¡Õ½ÖÕ¥Ö‰ + + + Please select a valid option. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ³Õ«Õ·Õ¿ Õ¿Õ¡Ö€Õ¢Õ¥Ö€Õ¡Õ¯Ö‰ + + + Please select a valid range. + Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬ Õ¾Õ¡Õ¾Õ¥Ö€ Õ¿Õ«Ö€Õ¸Ö‚ÕµÕ©Ö‰ + + + Please enter a valid week. + Õ„Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Ö„ Õ¾Õ¡Õ¾Õ¥Ö€ Õ·Õ¡Õ¢Õ¡Õ©Ö‰ + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.id.xlf b/vendor/symfony/form/Resources/translations/validators.id.xlf new file mode 100644 index 0000000..e4b43f7 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.id.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Gabungan kolom tidak boleh mengandung kolom tambahan. + + + The uploaded file was too large. Please try to upload a smaller file. + Berkas yang di unggah terlalu besar. Silahkan coba unggah berkas yang lebih kecil. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-Token tidak sah. Silahkan coba kirim ulang formulir. + + + This value is not a valid HTML5 color. + Nilai ini bukan merupakan HTML5 color yang sah. + + + Please enter a valid birthdate. + Silahkan masukkan tanggal lahir yang sah. + + + The selected choice is invalid. + Pilihan yang dipilih tidak sah. + + + The collection is invalid. + Koleksi tidak sah. + + + Please select a valid color. + Silahkan pilih warna yang sah. + + + Please select a valid country. + Silahkan pilih negara yang sah. + + + Please select a valid currency. + Silahkan pilih mata uang yang sah. + + + Please choose a valid date interval. + Silahkan pilih interval tanggal yang sah. + + + Please enter a valid date and time. + Silahkan masukkan tanggal dan waktu yang sah. + + + Please enter a valid date. + Silahkan masukkan tanggal yang sah. + + + Please select a valid file. + Silahkan pilih berkas yang sah. + + + The hidden field is invalid. + Ruas yang tersembunyi tidak sah. + + + Please enter an integer. + Silahkan masukkan angka. + + + Please select a valid language. + Silahlan pilih bahasa yang sah. + + + Please select a valid locale. + Silahkan pilih local yang sah. + + + Please enter a valid money amount. + Silahkan masukkan nilai uang yang sah. + + + Please enter a number. + Silahkan masukkan sebuah angka + + + The password is invalid. + Kata sandi tidak sah. + + + Please enter a percentage value. + Silahkan masukkan sebuah nilai persentase. + + + The values do not match. + Nilainya tidak cocok. + + + Please enter a valid time. + Silahkan masukkan waktu yang sah. + + + Please select a valid timezone. + Silahkan pilih zona waktu yang sah. + + + Please enter a valid URL. + Silahkan masukkan URL yang sah. + + + Please enter a valid search term. + Silahkan masukkan kata pencarian yang sah. + + + Please provide a valid phone number. + Silahkan sediakan nomor telepon yang sah. + + + The checkbox has an invalid value. + Nilai dari checkbox tidak sah. + + + Please enter a valid email address. + Silahkan masukkan alamat surel yang sah. + + + Please select a valid option. + Silahkan pilih opsi yang sah. + + + Please select a valid range. + Silahkan pilih rentang yang sah. + + + Please enter a valid week. + Silahkan masukkan minggu yang sah. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.it.xlf b/vendor/symfony/form/Resources/translations/validators.it.xlf new file mode 100644 index 0000000..bdea713 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.it.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Questo form non dovrebbe contenere nessun campo extra. + + + The uploaded file was too large. Please try to upload a smaller file. + Il file caricato è troppo grande. Per favore, carica un file più piccolo. + + + The CSRF token is invalid. Please try to resubmit the form. + Il token CSRF non è valido. Prova a reinviare il form. + + + This value is not a valid HTML5 color. + Il valore non è un colore HTML5 valido. + + + Please enter a valid birthdate. + Per favore, inserisci una data di compleanno valida. + + + The selected choice is invalid. + La scelta selezionata non è valida. + + + The collection is invalid. + La collezione non è valida. + + + Please select a valid color. + Per favore, seleziona un colore valido. + + + Please select a valid country. + Per favore, seleziona un paese valido. + + + Please select a valid currency. + Per favore, seleziona una valuta valida. + + + Please choose a valid date interval. + Per favore, scegli un intervallo di date valido. + + + Please enter a valid date and time. + Per favore, inserisci una data e ora valida. + + + Please enter a valid date. + Per favore, inserisci una data valida. + + + Please select a valid file. + Per favore, seleziona un file valido. + + + The hidden field is invalid. + Il campo nascosto non è valido. + + + Please enter an integer. + Per favore, inserisci un numero intero. + + + Please select a valid language. + Per favore, seleziona una lingua valida. + + + Please select a valid locale. + Per favore, seleziona una lingua valida. + + + Please enter a valid money amount. + Per favore, inserisci un importo valido. + + + Please enter a number. + Per favore, inserisci un numero. + + + The password is invalid. + La password non è valida. + + + Please enter a percentage value. + Per favore, inserisci un valore percentuale. + + + The values do not match. + I valori non corrispondono. + + + Please enter a valid time. + Per favore, inserisci un orario valido. + + + Please select a valid timezone. + Per favore, seleziona un fuso orario valido. + + + Please enter a valid URL. + Per favore, inserisci un URL valido. + + + Please enter a valid search term. + Per favore, inserisci un termine di ricerca valido. + + + Please provide a valid phone number. + Per favore, indica un numero di telefono valido. + + + The checkbox has an invalid value. + La casella di selezione non ha un valore valido. + + + Please enter a valid email address. + Per favore, indica un indirizzo email valido. + + + Please select a valid option. + Per favore, seleziona un'opzione valida. + + + Please select a valid range. + Per favore, seleziona un intervallo valido. + + + Please enter a valid week. + Per favore, inserisci una settimana valida. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.ja.xlf b/vendor/symfony/form/Resources/translations/validators.ja.xlf new file mode 100644 index 0000000..5728d9b --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.ja.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + フィールドグループã«è¿½åŠ ã®ãƒ•ィールドをå«ã‚“ã§ã¯ãªã‚Šã¾ã›ã‚“。 + + + The uploaded file was too large. Please try to upload a smaller file. + アップロードã•れãŸãƒ•ァイルãŒå¤§ãã™ãŽã¾ã™ã€‚å°ã•ãªãƒ•ァイルã§å†åº¦ã‚¢ãƒƒãƒ—ロードã—ã¦ãã ã•ã„。 + + + The CSRF token is invalid. Please try to resubmit the form. + CSRFトークンãŒç„¡åйã§ã™ã€å†é€ä¿¡ã—ã¦ãã ã•ã„。 + + + This value is not a valid HTML5 color. + 有効ãªHTML5ã®è‰²ã§ã¯ã‚りã¾ã›ã‚“。 + + + Please enter a valid birthdate. + 有効ãªç”Ÿå¹´æœˆæ—¥ã‚’入力ã—ã¦ãã ã•ã„。 + + + The selected choice is invalid. + é¸æŠžã—ãŸå€¤ã¯ç„¡åйã§ã™ã€‚ + + + The collection is invalid. + コレクションã¯ç„¡åйã§ã™ã€‚ + + + Please select a valid color. + 有効ãªè‰²ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please select a valid country. + 有効ãªå›½ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please select a valid currency. + 有効ãªé€šè²¨ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please choose a valid date interval. + æœ‰åŠ¹ãªæ—¥ä»˜é–“éš”ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please enter a valid date and time. + æœ‰åŠ¹ãªæ—¥æ™‚を入力ã—ã¦ãã ã•ã„。 + + + Please enter a valid date. + æœ‰åŠ¹ãªæ—¥ä»˜ã‚’入力ã—ã¦ãã ã•ã„。 + + + Please select a valid file. + 有効ãªãƒ•ã‚¡ã‚¤ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + The hidden field is invalid. + éš ã—フィールドãŒç„¡åйã§ã™ã€‚ + + + Please enter an integer. + æ•´æ•°ã§å…¥åŠ›ã—ã¦ãã ã•ã„。 + + + Please select a valid language. + 有効ãªè¨€èªžã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please select a valid locale. + 有効ãªãƒ­ã‚±ãƒ¼ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please enter a valid money amount. + 有効ãªé‡‘é¡ã‚’入力ã—ã¦ãã ã•ã„。 + + + Please enter a number. + 数値ã§å…¥åŠ›ã—ã¦ãã ã•ã„。 + + + The password is invalid. + パスワードãŒç„¡åйã§ã™ã€‚ + + + Please enter a percentage value. + パーセント値ã§å…¥åŠ›ã—ã¦ãã ã•ã„。 + + + The values do not match. + 値ãŒä¸€è‡´ã—ã¾ã›ã‚“。 + + + Please enter a valid time. + æœ‰åŠ¹ãªæ™‚間を入力ã—ã¦ãã ã•ã„。 + + + Please select a valid timezone. + 有効ãªã‚¿ã‚¤ãƒ ã‚¾ãƒ¼ãƒ³ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please enter a valid URL. + 有効ãªURLを入力ã—ã¦ãã ã•ã„。 + + + Please enter a valid search term. + æœ‰åŠ¹ãªæ¤œç´¢èªžã‚’入力ã—ã¦ãã ã•ã„。 + + + Please provide a valid phone number. + 有効ãªé›»è©±ç•ªå·ã‚’入力ã—ã¦ãã ã•ã„。 + + + The checkbox has an invalid value. + ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ã®å€¤ãŒç„¡åйã§ã™ã€‚ + + + Please enter a valid email address. + 有効ãªãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’入力ã—ã¦ãã ã•ã„。 + + + Please select a valid option. + 有効ãªå€¤ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please select a valid range. + 有効ãªç¯„å›²ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + + + Please enter a valid week. + 有効ãªé€±ã‚’入力ã—ã¦ãã ã•ã„。 + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.lb.xlf b/vendor/symfony/form/Resources/translations/validators.lb.xlf new file mode 100644 index 0000000..1f4ee82 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.lb.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Dës Feldergrupp sollt keng zousätzlech Felder enthalen. + + + The uploaded file was too large. Please try to upload a smaller file. + De geschécktene Fichier ass ze grouss. Versicht wann ech gelift ee méi klenge Fichier eropzelueden. + + + The CSRF token is invalid. Please try to resubmit the form. + Den CSRF-Token ass ongëlteg. Versicht wann ech gelift de Formulaire nach eng Kéier ze schécken. + + + This value is not a valid HTML5 color. + Dëse Wäert ass keng gëlteg HTML5-Faarf. + + + Please enter a valid birthdate. + W.e.g. e gëltege Gebuertsdatum aginn. + + + The selected choice is invalid. + Den ausgewielte Choix ass ongëlteg. + + + The collection is invalid. + D'Kollektioun ass ongëlteg. + + + Please select a valid color. + W.e.g. eng gëlteg Faarf auswielen. + + + Please select a valid country. + W.e.g. e gëltegt Land auswielen. + + + Please select a valid currency. + W.e.g. eng gëlteg Wärung auswielen. + + + Please choose a valid date interval. + W.e.g. e gëltegen Datumsinterval aginn. + + + Please enter a valid date and time. + W.e.g. eng gëlteg Datum an Zäit aginn. + + + Please enter a valid date. + W.e.g. eng gëltegen Datum aginn. + + + Please select a valid file. + W.e.g. e gëltege Fichier auswielen. + + + The hidden field is invalid. + Dat verstoppte Feld ass ongëlteg. + + + Please enter an integer. + W.e.g. eng ganz Zuel aginn. + + + Please select a valid language. + W.e.g. e gëltegt Sprooch auswielen. + + + Please select a valid locale. + W.e.g. e gëltegt Regionalschema auswielen. + + + Please enter a valid money amount. + W.e.g. eng gëlteg Geldzomm aginn. + + + Please enter a number. + W.e.g. eng Zuel aginn. + + + The password is invalid. + D'Passwuert ass ongëlteg. + + + Please enter a percentage value. + W.e.g. e Prozentwäert aginn. + + + The values do not match. + D'Wäerter stëmmen net iwwereneen. + + + Please enter a valid time. + W.e.g. eng gëlteg Zäit aginn. + + + Please select a valid timezone. + W.e.g. eng gëlteg Zäitzon auswielen. + + + Please enter a valid URL. + W.e.g. eng gëlteg URL aginn. + + + Please enter a valid search term. + W.e.g. e gëltege Sichbegrëff aginn. + + + Please provide a valid phone number. + W.e.g. eng gëlteg Telefonsnummer uginn. + + + The checkbox has an invalid value. + D'Ukräizfeld huet en ongëltege Wäert. + + + Please enter a valid email address. + W.e.g. eng gëlteg E-Mail-Adress aginn. + + + Please select a valid option. + W.e.g. eng gëlteg Optioun auswielen. + + + Please select a valid range. + W.e.g. eng gëlteg Spannbreet auswielen. + + + Please enter a valid week. + W.e.g. eng gëlteg Woch aginn. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.lt.xlf b/vendor/symfony/form/Resources/translations/validators.lt.xlf new file mode 100644 index 0000000..aba1120 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.lt.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Forma negali turÄ—ti papildomų laukų. + + + The uploaded file was too large. Please try to upload a smaller file. + Ä®kelta byla yra per didelÄ—. bandykite įkelti mažesnÄ™. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF kodas nepriimtinas. Bandykite siųsti formos užklausÄ… dar kartÄ…. + + + This value is not a valid HTML5 color. + Å i reikÅ¡mÄ— nÄ—ra HTML5 spalva. + + + Please enter a valid birthdate. + PraÅ¡ome įvesti tinkamÄ… gimimo datÄ…. + + + The selected choice is invalid. + Pasirinktas pasirinkimas yra neteisingas. + + + The collection is invalid. + Neteisingas sÄ…raÅ¡as. + + + Please select a valid color. + PraÅ¡ome pasirinkti tinkamÄ… spalvÄ…. + + + Please select a valid country. + PraÅ¡ome pasirinkti tinkamÄ… Å¡alį. + + + Please select a valid currency. + PraÅ¡ome pasirinkti tinkamÄ… valiutÄ…. + + + Please choose a valid date interval. + PraÅ¡ome pasirinkti tinkamÄ… datos intervalÄ…. + + + Please enter a valid date and time. + PraÅ¡ome įvesti tinkamÄ… datÄ… ir laikÄ…. + + + Please enter a valid date. + PraÅ¡ome įvesti tinkamÄ… datÄ…. + + + Please select a valid file. + PraÅ¡ome pasirinkti tinkamÄ… bylÄ…. + + + The hidden field is invalid. + Klaidingas paslÄ—ptasis laukas. + + + Please enter an integer. + PraÅ¡ome įvesti sveikÄ… skaiÄių. + + + Please select a valid language. + PraÅ¡ome pasirinkti tinkamÄ… kalbÄ…. + + + Please select a valid locale. + PraÅ¡ome pasirinkti tinkamÄ… lokalÄ™. + + + Please enter a valid money amount. + PraÅ¡ome įvesti tinkamÄ… pinigų sumÄ…. + + + Please enter a number. + PraÅ¡ome įvesti numerį. + + + The password is invalid. + Klaidingas slaptažodis. + + + Please enter a percentage value. + PraÅ¡ome įvesti procentinÄ™ reikÅ¡mÄ™. + + + The values do not match. + ReikÅ¡mÄ—s nesutampa. + + + Please enter a valid time. + PraÅ¡ome įvesti tinkamÄ… laikÄ…. + + + Please select a valid timezone. + PraÅ¡ome pasirinkti tinkamÄ… laiko zonÄ…. + + + Please enter a valid URL. + PraÅ¡ome įvesti tinkamÄ… URL. + + + Please enter a valid search term. + PraÅ¡ome įvesti tinkamÄ… paieÅ¡kos terminÄ…. + + + Please provide a valid phone number. + PraÅ¡ome pateikti tinkamÄ… telefono numerį. + + + The checkbox has an invalid value. + Klaidinga žymimajo langelio reikÅ¡mÄ—. + + + Please enter a valid email address. + PraÅ¡ome įvesti tinkamÄ… el. paÅ¡to adresÄ…. + + + Please select a valid option. + PraÅ¡ome pasirinkti tinkamÄ… parinktį. + + + Please select a valid range. + PraÅ¡ome pasirinkti tinkamÄ… diapozonÄ…. + + + Please enter a valid week. + PraÅ¡ome įvesti tinkamÄ… savaitÄ™. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.lv.xlf b/vendor/symfony/form/Resources/translations/validators.lv.xlf new file mode 100644 index 0000000..fb358dc --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.lv.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Å ajÄ veidlapÄ nevajadzÄ“tu bÅ«t papildus ievades laukiem. + + + The uploaded file was too large. Please try to upload a smaller file. + AugÅ¡upielÄdÄ“tÄ faila izmÄ“rs bija par lielu. LÅ«dzu mēģiniet augÅ¡upielÄdÄ“t mazÄka izmÄ“ra failu. + + + The CSRF token is invalid. Please try to resubmit the form. + Dotais CSRF talons nav derÄ«gs. LÅ«dzu mēģiniet vÄ“lreiz iesniegt veidlapu. + + + This value is not a valid HTML5 color. + Å Ä« vertÄ«ba nav derÄ«ga HTML5 krÄsa. + + + Please enter a valid birthdate. + LÅ«dzu, ievadiet derÄ«gu dzimÅ¡anas datumu. + + + The selected choice is invalid. + IezÄ«mÄ“tÄ izvÄ“le nav derÄ«ga. + + + The collection is invalid. + Kolekcija nav derÄ«ga. + + + Please select a valid color. + LÅ«dzu, izvÄ“lieties derÄ«gu krÄsu. + + + Please select a valid country. + LÅ«dzu, izvÄ“lieties derÄ«gu valsti. + + + Please select a valid currency. + LÅ«dzu, izvÄ“lieties derÄ«gu valÅ«tu. + + + Please choose a valid date interval. + LÅ«dzu, izvÄ“lieties derÄ«gu datumu intervÄlu. + + + Please enter a valid date and time. + LÅ«dzu, ievadiet derÄ«gu datumu un laiku. + + + Please enter a valid date. + LÅ«dzu, ievadiet derÄ«gu datumu. + + + Please select a valid file. + LÅ«dzu, izvÄ“lieties derÄ«gu failu. + + + The hidden field is invalid. + SlÄ“ptÄ lauka vÄ“rtÄ«ba ir nederÄ«ga. + + + Please enter an integer. + LÅ«dzu, ievadiet veselu skaitli. + + + Please select a valid language. + LÅ«dzu, izvÄ“lieties derÄ«gu valodu. + + + Please select a valid locale. + LÅ«dzu, izvÄ“lieties derÄ«gu lokalizÄciju. + + + Please enter a valid money amount. + LÅ«dzu, ievadiet derÄ«gu naudas lielumu. + + + Please enter a number. + LÅ«dzu, ievadiet skaitli. + + + The password is invalid. + Parole ir nederÄ«ga. + + + Please enter a percentage value. + LÅ«dzu, ievadiet procentuÄlo lielumu. + + + The values do not match. + VÄ“rtÄ«bas nesakrÄ«t. + + + Please enter a valid time. + LÅ«dzu, ievadiet derÄ«gu laiku. + + + Please select a valid timezone. + LÅ«dzu, izvÄ“lieties derÄ«gu laika zonu. + + + Please enter a valid URL. + LÅ«dzu, ievadiet derÄ«gu URL. + + + Please enter a valid search term. + LÅ«dzu, ievadiet derÄ«gu meklēšanas nosacÄ«jumu. + + + Please provide a valid phone number. + LÅ«dzu, ievadiet derÄ«gu tÄlruņa numuru. + + + The checkbox has an invalid value. + IzvÄ“les rÅ«tiņai ir nederÄ«ga vÄ“rtÄ«ba. + + + Please enter a valid email address. + LÅ«dzu, ievadiet derÄ«gu e-pasta adresi. + + + Please select a valid option. + LÅ«dzu, izvÄ“lieties derÄ«gu opciju. + + + Please select a valid range. + LÅ«dzu, izvÄ“lieties derÄ«gu diapazonu. + + + Please enter a valid week. + LÅ«dzu, ievadiet derÄ«gu nedēļu. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.mk.xlf b/vendor/symfony/form/Resources/translations/validators.mk.xlf new file mode 100644 index 0000000..5f2af85 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.mk.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Оваа форма не треба да Ñодржи дополнителни полиња. + + + The uploaded file was too large. Please try to upload a smaller file. + Датотеката што Ñе обидовте да ја подигнете е преголема. Ве молиме обидете Ñе Ñо помала датотека. + + + The CSRF token is invalid. Please try to resubmit the form. + Вашиот CSRF токен е невалиден. Ве молиме иÑпратете ја формата одново. + + + This value is not a valid HTML5 color. + Оваа вредноÑÑ‚ не е валидна HTML5 боја. + + + Please enter a valid birthdate. + Ве молиме внеÑете валидна дата на раѓање. + + + The selected choice is invalid. + Избраната опција е невалидна. + + + The collection is invalid. + Колекцијата е невалидна. + + + Please select a valid color. + Ве молиме одберете валидна боја. + + + Please select a valid country. + Ве молиме одберете валидна земја. + + + Please select a valid currency. + Ве молиме одберете валидна валута. + + + Please choose a valid date interval. + Ве молиме одберете валиден интервал помеѓу два датума. + + + Please enter a valid date and time. + Ве молиме внеÑете валиден датум и време. + + + Please enter a valid date. + Ве молиме внеÑете валиден датум. + + + Please select a valid file. + Ве молиме одберете валидна датотека. + + + The hidden field is invalid. + Скриеното поле е невалидно. + + + Please enter an integer. + Ве молиме внеÑете цел број. + + + Please select a valid language. + Ве молиме одберете валиден јазик. + + + Please select a valid locale. + Ве молиме одберете валидна локализација. + + + Please enter a valid money amount. + Ве молиме внеÑете валидна Ñума на пари. + + + Please enter a number. + Ве молиме внеÑете број. + + + The password is invalid. + Лозинката е погрешна. + + + Please enter a percentage value. + Ве молиме внеÑете процентуална вредноÑÑ‚. + + + The values do not match. + ВредноÑтите не Ñе Ñовпаѓаат. + + + Please enter a valid time. + Ве молиме внеÑете валидно време. + + + Please select a valid timezone. + Ве молиме одберете валидна временÑка зона. + + + Please enter a valid URL. + Ве молиме внеÑете валиден униформен локатор на реÑурÑи (URL). + + + Please enter a valid search term. + Ве молиме внеÑете валиден термин за пребарување. + + + Please provide a valid phone number. + Ве молиме внеÑете валиден телефонÑки број. + + + The checkbox has an invalid value. + Полето за штиклирање има неважечка вредноÑÑ‚. + + + Please enter a valid email address. + Ве молиме внеÑете валидна адреÑа за е-пошта. + + + Please select a valid option. + Ве молиме одберете валидна опција. + + + Please select a valid range. + Ве молиме одберете важечки опÑег. + + + Please enter a valid week. + Ве молиме внеÑете валидна недела. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.mn.xlf b/vendor/symfony/form/Resources/translations/validators.mn.xlf new file mode 100644 index 0000000..2e6d09b --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.mn.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Форм нÑмÑлт талбар багтаах боломжгүй. + + + The uploaded file was too large. Please try to upload a smaller file. + Upload хийÑÑн файл Ñ…ÑÑ‚Ñрхий том байна. Бага Ñ…ÑмжÑÑÑ‚Ñй файл оруулна уу. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF token буруу байна. Формоо дахин илгÑÑÐ½Ñ Ò¯Ò¯. + + + This value is not a valid HTML5 color. + Ð­Ð½Ñ ÑƒÑ‚Ð³Ð° зөв HTML5 өнгө биш байна. + + + Please enter a valid birthdate. + Зөв төрÑөн он Ñар оруулна уу. + + + The selected choice is invalid. + СонгоÑон утга буруу байна. + + + The collection is invalid. + Цуглуулга буруу байна. + + + Please select a valid color. + ҮнÑн зөв өнгө Ñонгоно уу. + + + Please select a valid country. + ҮнÑн зөв ÑƒÐ»Ñ Ñонгоно уу. + + + Please select a valid currency. + ҮнÑн зөв мөнгөн Ñ‚ÑмдÑгт Ñонгоно уу. + + + Please choose a valid date interval. + ҮнÑн зөв цагын зай Ñонгоно уу. + + + Please enter a valid date and time. + ҮнÑн зөв он цаг оруулна уу. + + + Please enter a valid date. + ҮнÑн зөв он цаг өдөр оруулна уу. + + + Please select a valid file. + ҮнÑн зөв файл Ñонгоно уу. + + + The hidden field is invalid. + Ðууц талбарын утга буруу байна. + + + Please enter an integer. + БүхÑл тоо оруулна уу. + + + Please select a valid language. + ҮнÑн зөв Ñ…Ñл Ñонгоно уу. + + + Please select a valid locale. + ҮнÑн зөв Ð±Ò¯Ñ Ñонгоно уу. + + + Please enter a valid money amount. + ҮнÑн зөв мөнгөний Ñ…ÑмжÑÑ Ñонгоно уу. + + + Please enter a number. + Тоо оруулна уу. + + + The password is invalid. + Ðууц үг буруу байна. + + + Please enter a percentage value. + Хувь утга оруулна уу. + + + The values do not match. + Утга хоорондоо таарахгүй байна. + + + Please enter a valid time. + ҮнÑн зөв цаг оруулна уу. + + + Please select a valid timezone. + ҮнÑн зөв цагын Ð±Ò¯Ñ Ð¾Ñ€ÑƒÑƒÐ»Ð½Ð° уу. + + + Please enter a valid URL. + ҮнÑн зөв URL оруулна уу. + + + Please enter a valid search term. + ҮнÑн зөв хайх утга оруулна уу. + + + Please provide a valid phone number. + ҮнÑн зөв утаÑны дугаар оруулна уу. + + + The checkbox has an invalid value. + Сонгох хайрцаг буруу утгатай байна. + + + Please enter a valid email address. + ҮнÑн зөв и-мÑйл хаÑг оруулна уу. + + + Please select a valid option. + ҮнÑн зөв Ñонголт Ñонгоно уу. + + + Please select a valid range. + ҮнÑн зөв Ñ…Ñзгаарын утга Ñонгоно уу. + + + Please enter a valid week. + ҮнÑн зөв долоо хоног Ñонгоно уу. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.my.xlf b/vendor/symfony/form/Resources/translations/validators.my.xlf new file mode 100644 index 0000000..9ecb9d3 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.my.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + ဤ ဖောင်သည် field အပိုများ မပါá€á€„်သင့်ပါዠ+ + + The uploaded file was too large. Please try to upload a smaller file. + Upload á€á€„်သောဖိုင်သည်အလွန်ကြီးလွန်းသည်ዠကျေးဇူးပြုá သေးငယ်သည့်ဖိုင်ကိုá€á€„်ရန်ကြိုးစားပါዠ+ + + The CSRF token is invalid. Please try to resubmit the form. + သင့်လျှော်သော် CSRF á€á€­á€¯á€€á€„် မဟုá€á€ºá€•ါዠကျေးဇူးပြုáဖောင်ကိုပြန်á€á€„်ပါዠ+ + + This value is not a valid HTML5 color. + ဤá€á€”်ဖိုးသည် သင့်လျှော်သော် HTML5 အရောင်မဟုá€á€ºá€•ါዠ+ + + Please enter a valid birthdate. + ကျေးဇူးပြုá မှန်ကန်သောမွေးနေ့ကိုထည့်ပါዠ+ + + The selected choice is invalid. + သင့် ရွေးá€á€»á€šá€ºá€™á€¾á€¯á€žá€Šá€ºá€™á€™á€¾á€”်ကန်ပါዠ+ + + The collection is invalid. + ဤ collection သည်သင့်လျှော်သော် collection မဟုá€á€ºá€•ါዠ+ + + Please select a valid color. + ကျေးဇူးပြုá မှန်ကန်သောအရောင်ကိုရွေးပါዠ+ + + Please select a valid country. + ကျေးဇူးပြုá မှန်ကန်သောနိုင်ငံကိုရွေးပါዠ+ + + Please select a valid currency. + ကျေးဇူးပြုá မှန်ကန်သောငွေကြေးကိုရွေးပါዠ+ + + Please choose a valid date interval. + ကျေးဇူးပြုá မှန်ကန်သောနေ ရက်စွဲကိုရွေးပါዠ+ + + Please enter a valid date and time. + ကျေးဇူးပြုá မှန်ကန်သောနေ ရက်စွဲနှင့်အá€á€»á€­á€”် ကိုထည့်ပါዠ+ + + Please enter a valid date. + ကျေးဇူးပြုá မှန်ကန်သောနေ ရက်စွဲကိုထည့်ပါዠ+ + + Please select a valid file. + ကျေးဇူးပြုá မှန်ကန်သောနေ ဖိုင်ကိုရွေးá€á€»á€šá€ºá€•ါዠ+ + + The hidden field is invalid. + မသင့် လျှော်သော် hidden field ဖြစ်နေသည်ዠ+ + + Please enter an integer. + ကျေးဇူးပြုá Integer á€á€”်ဖိုးသာထည့်ပါዠ+ + + Please select a valid language. + ကျေးဇူးပြုá မှန်ကန်သော ဘာသာစကားကိုရွေးá€á€»á€šá€ºá€•ါዠ+ + + Please select a valid locale. + ကျေးဇူးပြုá မှန်ကန်သော locale ကိုရွေးá€á€»á€šá€ºá€•ါዠ+ + + Please enter a valid money amount. + ကျေးဇူးပြုá မှန်ကန်သော ပိုက်ဆံပမာဠကိုထည့်ပါዠ+ + + Please enter a number. + ကျေးဇူးပြုá မှန်ကန်သော နံပါá€á€º ကိုရွေးá€á€»á€šá€ºá€•ါዠ+ + + The password is invalid. + မှန်ကန်သောစကားá€á€¾á€€á€ºá€™á€Ÿá€¯á€á€ºá€•ါዠ+ + + Please enter a percentage value. + ကျေးဇူးပြုá ရာá€á€­á€¯á€„်နှုန်းá€á€”်ဖိုးထည့်ပါዠ+ + + The values do not match. + á€á€”်ဖိုးများကိုက်ညီမှုမရှိပါዠ+ + + Please enter a valid time. + ကျေးဇူးပြုá မှန်ကန်သောအá€á€»á€­á€”်ကိုထည့်ပါዠ+ + + Please select a valid timezone. + ကျေးဇူးပြုá မှန်ကန်သောအá€á€»á€­á€”်ဇုန်ကိုရွေးပါዠ+ + + Please enter a valid URL. + ကျေးဇူးပြုá သင့်လျှော်သော် URL ကိုရွေးပါዠ+ + + Please enter a valid search term. + ကျေးဇူးပြုá သင့် လျှော်သော်ရှာဖွေမှု term များထည့်ပါዠ+ + + Please provide a valid phone number. + ကျေးဇူးပြုá သင့် လျှော်သော်ရှာဖွေမှု ဖုန်းနံပါá€á€ºá€‘ည့်ပါዠ+ + + The checkbox has an invalid value. + Checkbox á€á€”်ဖိုးသည် မှန်ကန်မှုမရှိပါዠ+ + + Please enter a valid email address. + ကျေးဇူးပြုá မှန်ကန်သော် email လိပ်စာထည့်ပါዠ+ + + Please select a valid option. + ကျေးဇူးပြုá မှန်ကန်သော် ရွေးá€á€»á€šá€ºá€™á€¾á€¯ ကိုရွေးပါዠ+ + + Please select a valid range. + ကျေးဇူးပြုá မှန်ကန်သော အပိုင်းအá€á€¼á€¬á€¸ ကိုရွေးပါዠ+ + + Please enter a valid week. + ကျေးဇူးပြုá မှန်ကန်သောရက်သá€á€¹á€á€•á€á€ºá€€á€­á€¯á€‘ည့်ပါዠ+ + + + diff --git a/vendor/symfony/form/Resources/translations/validators.nb.xlf b/vendor/symfony/form/Resources/translations/validators.nb.xlf new file mode 100644 index 0000000..193306b --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.nb.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Feltgruppen mÃ¥ ikke inneholde ekstra felter. + + + The uploaded file was too large. Please try to upload a smaller file. + Den opplastede filen var for stor. Vennligst last opp en mindre fil. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-tokenen er ugyldig. Vennligst prøv Ã¥ sende inn skjemaet pÃ¥ nytt. + + + This value is not a valid HTML5 color. + Denne verdien er ikke en gyldig HTML5-farge. + + + Please enter a valid birthdate. + Vennligst oppgi gyldig fødselsdato. + + + The selected choice is invalid. + Det valgte valget er ugyldig. + + + The collection is invalid. + Samlingen er ugyldig. + + + Please select a valid color. + Velg en gyldig farge. + + + Please select a valid country. + Vennligst velg et gyldig land. + + + Please select a valid currency. + Vennligst velg en gyldig valuta. + + + Please choose a valid date interval. + Vennligst velg et gyldig datointervall. + + + Please enter a valid date and time. + Vennligst angi en gyldig dato og tid. + + + Please enter a valid date. + Vennligst oppgi en gyldig dato. + + + Please select a valid file. + Vennligst velg en gyldig fil. + + + The hidden field is invalid. + Det skjulte feltet er ugyldig. + + + Please enter an integer. + Vennligst skriv inn et heltall. + + + Please select a valid language. + Vennligst velg et gyldig sprÃ¥k. + + + Please select a valid locale. + Vennligst velg et gyldig sted. + + + Please enter a valid money amount. + Vennligst angi et gyldig pengebeløp. + + + Please enter a number. + Vennligst skriv inn et nummer. + + + The password is invalid. + Passordet er ugyldig. + + + Please enter a percentage value. + Vennligst angi en prosentverdi. + + + The values do not match. + Verdiene stemmer ikke overens. + + + Please enter a valid time. + Vennligst angi et gyldig tidspunkt. + + + Please select a valid timezone. + Vennligst velg en gyldig tidssone. + + + Please enter a valid URL. + Vennligst skriv inn en gyldig URL. + + + Please enter a valid search term. + Vennligst angi et gyldig søketerm. + + + Please provide a valid phone number. + Vennligst oppgi et gyldig telefonnummer. + + + The checkbox has an invalid value. + Avkrysningsboksen har en ugyldig verdi. + + + Please enter a valid email address. + Vennligst skriv inn en gyldig e-post adresse. + + + Please select a valid option. + Vennligst velg et gyldig alternativ. + + + Please select a valid range. + Vennligst velg et gyldig omrÃ¥de. + + + Please enter a valid week. + Vennligst skriv inn en gyldig uke. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.nl.xlf b/vendor/symfony/form/Resources/translations/validators.nl.xlf new file mode 100644 index 0000000..6330ecf --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.nl.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Dit formulier mag geen extra velden bevatten. + + + The uploaded file was too large. Please try to upload a smaller file. + Het geüploade bestand is te groot. Probeer een kleiner bestand te uploaden. + + + The CSRF token is invalid. Please try to resubmit the form. + De CSRF-token is ongeldig. Probeer het formulier opnieuw te versturen. + + + This value is not a valid HTML5 color. + Dit is geen geldige HTML5 kleur. + + + Please enter a valid birthdate. + Vul een geldige geboortedatum in. + + + The selected choice is invalid. + Deze keuze is ongeldig. + + + The collection is invalid. + Deze collectie is ongeldig. + + + Please select a valid color. + Kies een geldige kleur. + + + Please select a valid country. + Kies een geldige landnaam. + + + Please select a valid currency. + Kies een geldige valuta. + + + Please choose a valid date interval. + Kies een geldig tijdinterval. + + + Please enter a valid date and time. + Vul een geldige datum en tijd in. + + + Please enter a valid date. + Vul een geldige datum in. + + + Please select a valid file. + Kies een geldig bestand. + + + The hidden field is invalid. + Het verborgen veld is incorrect. + + + Please enter an integer. + Vul een geldig getal in. + + + Please select a valid language. + Kies een geldige taal. + + + Please select a valid locale. + Kies een geldige locale. + + + Please enter a valid money amount. + Vul een geldig bedrag in. + + + Please enter a number. + Vul een geldig getal in. + + + The password is invalid. + Het wachtwoord is incorrect. + + + Please enter a percentage value. + Vul een geldig percentage in. + + + The values do not match. + De waardes komen niet overeen. + + + Please enter a valid time. + Vul een geldige tijd in. + + + Please select a valid timezone. + Vul een geldige tijdzone in. + + + Please enter a valid URL. + Vul een geldige URL in. + + + Please enter a valid search term. + Vul een geldige zoekterm in. + + + Please provide a valid phone number. + Vul een geldig telefoonnummer in. + + + The checkbox has an invalid value. + De checkbox heeft een incorrecte waarde. + + + Please enter a valid email address. + Vul een geldig e-mailadres in. + + + Please select a valid option. + Kies een geldige optie. + + + Please select a valid range. + Kies een geldig bereik. + + + Please enter a valid week. + Vul een geldige week in. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.nn.xlf b/vendor/symfony/form/Resources/translations/validators.nn.xlf new file mode 100644 index 0000000..0722b45 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.nn.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Feltgruppa kan ikkje innehalde ekstra felt. + + + The uploaded file was too large. Please try to upload a smaller file. + Fila du lasta opp var for stor. Last opp ei mindre fil. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-teiknet er ugyldig. Ver venleg og prøv Ã¥ sende inn skjemaet pÃ¥ nytt. + + + This value is not a valid HTML5 color. + Verdien er ikkje ein gyldig HTML5-farge. + + + Please enter a valid birthdate. + Gje opp ein gyldig fødselsdato. + + + The selected choice is invalid. + Valget du gjorde er ikkje gyldig. + + + The collection is invalid. + Samlinga er ikkje gyldig. + + + Please select a valid color. + Gje opp ein gyldig farge. + + + Please select a valid country. + Gje opp eit gyldig land. + + + Please select a valid currency. + Gje opp ein gyldig valuta. + + + Please choose a valid date interval. + Gje opp eit gyldig datointervall. + + + Please enter a valid date and time. + Gje opp ein gyldig dato og tid. + + + Please enter a valid date. + Gje opp ein gyldig dato. + + + Please select a valid file. + Velg ei gyldig fil. + + + The hidden field is invalid. + Det skjulte feltet er ikkje gyldig. + + + Please enter an integer. + Gje opp eit heiltal. + + + Please select a valid language. + Gje opp eit gyldig sprÃ¥k. + + + Please select a valid locale. + Gje opp eit gyldig locale. + + + Please enter a valid money amount. + Gje opp ein gyldig sum pengar. + + + Please enter a number. + Gje opp eit nummer. + + + The password is invalid. + Passordet er ikkje gyldig. + + + Please enter a percentage value. + Gje opp ein prosentverdi. + + + The values do not match. + Verdiane er ikkje eins. + + + Please enter a valid time. + Gje opp ei gyldig tid. + + + Please select a valid timezone. + Gje opp ei gyldig tidssone. + + + Please enter a valid URL. + Gje opp ein gyldig URL. + + + Please enter a valid search term. + Gje opp gyldige søkjeord. + + + Please provide a valid phone number. + Gje opp eit gyldig telefonnummer. + + + The checkbox has an invalid value. + Sjekkboksen har ein ugyldig verdi. + + + Please enter a valid email address. + Gje opp ei gyldig e-postadresse. + + + Please select a valid option. + Velg eit gyldig vilkÃ¥r. + + + Please select a valid range. + Velg eit gyldig spenn. + + + Please enter a valid week. + Gje opp ei gyldig veke. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.no.xlf b/vendor/symfony/form/Resources/translations/validators.no.xlf new file mode 100644 index 0000000..193306b --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.no.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Feltgruppen mÃ¥ ikke inneholde ekstra felter. + + + The uploaded file was too large. Please try to upload a smaller file. + Den opplastede filen var for stor. Vennligst last opp en mindre fil. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-tokenen er ugyldig. Vennligst prøv Ã¥ sende inn skjemaet pÃ¥ nytt. + + + This value is not a valid HTML5 color. + Denne verdien er ikke en gyldig HTML5-farge. + + + Please enter a valid birthdate. + Vennligst oppgi gyldig fødselsdato. + + + The selected choice is invalid. + Det valgte valget er ugyldig. + + + The collection is invalid. + Samlingen er ugyldig. + + + Please select a valid color. + Velg en gyldig farge. + + + Please select a valid country. + Vennligst velg et gyldig land. + + + Please select a valid currency. + Vennligst velg en gyldig valuta. + + + Please choose a valid date interval. + Vennligst velg et gyldig datointervall. + + + Please enter a valid date and time. + Vennligst angi en gyldig dato og tid. + + + Please enter a valid date. + Vennligst oppgi en gyldig dato. + + + Please select a valid file. + Vennligst velg en gyldig fil. + + + The hidden field is invalid. + Det skjulte feltet er ugyldig. + + + Please enter an integer. + Vennligst skriv inn et heltall. + + + Please select a valid language. + Vennligst velg et gyldig sprÃ¥k. + + + Please select a valid locale. + Vennligst velg et gyldig sted. + + + Please enter a valid money amount. + Vennligst angi et gyldig pengebeløp. + + + Please enter a number. + Vennligst skriv inn et nummer. + + + The password is invalid. + Passordet er ugyldig. + + + Please enter a percentage value. + Vennligst angi en prosentverdi. + + + The values do not match. + Verdiene stemmer ikke overens. + + + Please enter a valid time. + Vennligst angi et gyldig tidspunkt. + + + Please select a valid timezone. + Vennligst velg en gyldig tidssone. + + + Please enter a valid URL. + Vennligst skriv inn en gyldig URL. + + + Please enter a valid search term. + Vennligst angi et gyldig søketerm. + + + Please provide a valid phone number. + Vennligst oppgi et gyldig telefonnummer. + + + The checkbox has an invalid value. + Avkrysningsboksen har en ugyldig verdi. + + + Please enter a valid email address. + Vennligst skriv inn en gyldig e-post adresse. + + + Please select a valid option. + Vennligst velg et gyldig alternativ. + + + Please select a valid range. + Vennligst velg et gyldig omrÃ¥de. + + + Please enter a valid week. + Vennligst skriv inn en gyldig uke. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.pl.xlf b/vendor/symfony/form/Resources/translations/validators.pl.xlf new file mode 100644 index 0000000..767f05d --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.pl.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ten formularz nie powinien zawierać dodatkowych pól. + + + The uploaded file was too large. Please try to upload a smaller file. + Wgrany plik byÅ‚ za duży. ProszÄ™ spróbować wgrać mniejszy plik. + + + The CSRF token is invalid. Please try to resubmit the form. + Token CSRF jest nieprawidÅ‚owy. ProszÄ™ spróbować wysÅ‚ać formularz ponownie. + + + This value is not a valid HTML5 color. + Ta wartość nie jest prawidÅ‚owym kolorem HTML5. + + + Please enter a valid birthdate. + ProszÄ™ wprowadzić prawidÅ‚owÄ… datÄ™ urodzenia. + + + The selected choice is invalid. + Wybrana wartość jest nieprawidÅ‚owa. + + + The collection is invalid. + Zbiór jest nieprawidÅ‚owy. + + + Please select a valid color. + ProszÄ™ wybrać prawidÅ‚owy kolor. + + + Please select a valid country. + ProszÄ™ wybrać prawidÅ‚owy kraj. + + + Please select a valid currency. + ProszÄ™ wybrać prawidÅ‚owÄ… walutÄ™. + + + Please choose a valid date interval. + ProszÄ™ wybrać prawidÅ‚owy przedziaÅ‚ czasowy. + + + Please enter a valid date and time. + ProszÄ™ wprowadzić prawidÅ‚owÄ… datÄ™ i czas. + + + Please enter a valid date. + ProszÄ™ wprowadzić prawidÅ‚owÄ… datÄ™. + + + Please select a valid file. + ProszÄ™ wybrać prawidÅ‚owy plik. + + + The hidden field is invalid. + Ukryte pole jest nieprawidÅ‚owe. + + + Please enter an integer. + ProszÄ™ wprowadzić liczbÄ™ caÅ‚kowitÄ…. + + + Please select a valid language. + ProszÄ™ wybrać prawidÅ‚owy jÄ™zyk. + + + Please select a valid locale. + ProszÄ™ wybrać prawidÅ‚owÄ… lokalizacjÄ™. + + + Please enter a valid money amount. + ProszÄ™ wybrać prawidÅ‚owÄ… ilość pieniÄ™dzy. + + + Please enter a number. + ProszÄ™ wprowadzić liczbÄ™. + + + The password is invalid. + HasÅ‚o jest nieprawidÅ‚owe. + + + Please enter a percentage value. + ProszÄ™ wprowadzić wartość procentowÄ…. + + + The values do not match. + WartoÅ›ci siÄ™ nie zgadzajÄ…. + + + Please enter a valid time. + ProszÄ™ wprowadzić prawidÅ‚owy czas. + + + Please select a valid timezone. + ProszÄ™ wybrać prawidÅ‚owÄ… strefÄ™ czasowÄ…. + + + Please enter a valid URL. + ProszÄ™ wprowadzić prawidÅ‚owy adres URL. + + + Please enter a valid search term. + ProszÄ™ wprowadzić prawidÅ‚owy termin wyszukiwania. + + + Please provide a valid phone number. + ProszÄ™ wprowadzić prawidÅ‚owy numer telefonu. + + + The checkbox has an invalid value. + Pole wyboru posiada nieprawidÅ‚owÄ… wartość. + + + Please enter a valid email address. + ProszÄ™ wprowadzić prawidÅ‚owy adres email. + + + Please select a valid option. + ProszÄ™ wybrać prawidÅ‚owÄ… opcjÄ™. + + + Please select a valid range. + ProszÄ™ wybrać prawidÅ‚owy zakres. + + + Please enter a valid week. + ProszÄ™ wybrać prawidÅ‚owy tydzieÅ„. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.pt.xlf b/vendor/symfony/form/Resources/translations/validators.pt.xlf new file mode 100644 index 0000000..755108f --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.pt.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Este formulário não deveria possuir mais campos. + + + The uploaded file was too large. Please try to upload a smaller file. + O ficheiro enviado é muito grande. Por favor, tente enviar um ficheiro menor. + + + The CSRF token is invalid. Please try to resubmit the form. + O token CSRF está inválido. Por favor, tente enviar o formulário novamente. + + + This value is not a valid HTML5 color. + Este valor não é uma cor HTML5 válida. + + + Please enter a valid birthdate. + Por favor, informe uma data de nascimento válida. + + + The selected choice is invalid. + A escolha seleccionada é inválida. + + + The collection is invalid. + A coleção é inválida. + + + Please select a valid color. + Por favor, selecione uma cor válida. + + + Please select a valid country. + Por favor, selecione um país válido. + + + Please select a valid currency. + Por favor, selecione uma moeda válida. + + + Please choose a valid date interval. + Por favor, escolha um intervalo de datas válido. + + + Please enter a valid date and time. + Por favor, informe uma data e horário válidos. + + + Please enter a valid date. + Por favor, informe uma data válida. + + + Please select a valid file. + Por favor, selecione um ficheiro válido. + + + The hidden field is invalid. + O campo oculto é inválido. + + + Please enter an integer. + Por favor, informe um inteiro. + + + Please select a valid language. + Por favor selecione um idioma válido. + + + Please select a valid locale. + Por favor, selecione um locale válido. + + + Please enter a valid money amount. + Por favor, informe um valor monetário válido. + + + Please enter a number. + Por favor, informe um número. + + + The password is invalid. + A palavra-passe é inválida. + + + Please enter a percentage value. + Por favor, informe um valor percentual. + + + The values do not match. + Os valores não correspondem. + + + Please enter a valid time. + Por favor, informe uma hora válida. + + + Please select a valid timezone. + Por favor, selecione um fuso horário válido. + + + Please enter a valid URL. + Por favor, informe uma URL válida. + + + Please enter a valid search term. + Por favor, informe um termo de busca válido. + + + Please provide a valid phone number. + Por favor, infome um número de telefone válido. + + + The checkbox has an invalid value. + O checkbox possui um valor inválido. + + + Please enter a valid email address. + Por favor, informe um endereço de email válido. + + + Please select a valid option. + Por favor, selecione uma opção válida. + + + Please select a valid range. + Por favor, selecione um intervalo válido. + + + Please enter a valid week. + Por favor, selecione uma semana válida. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.pt_BR.xlf b/vendor/symfony/form/Resources/translations/validators.pt_BR.xlf new file mode 100644 index 0000000..c386ab3 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.pt_BR.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Este formulário não deve conter campos adicionais. + + + The uploaded file was too large. Please try to upload a smaller file. + O arquivo enviado é muito grande. Por favor, tente enviar um arquivo menor. + + + The CSRF token is invalid. Please try to resubmit the form. + O token CSRF é inválido. Por favor, tente reenviar o formulário. + + + This value is not a valid HTML5 color. + Este valor não é uma cor HTML5 válida. + + + Please enter a valid birthdate. + Por favor, informe uma data de nascimento válida. + + + The selected choice is invalid. + A escolha selecionada é inválida. + + + The collection is invalid. + A coleção é inválida. + + + Please select a valid color. + Por favor, selecione uma cor válida. + + + Please select a valid country. + Por favor, selecione um país válido. + + + Please select a valid currency. + Por favor, selecione uma moeda válida. + + + Please choose a valid date interval. + Por favor, escolha um intervalo de datas válido. + + + Please enter a valid date and time. + Por favor, informe uma data e horário válidos. + + + Please enter a valid date. + Por favor, informe uma data válida. + + + Please select a valid file. + Por favor, selecione um arquivo válido. + + + The hidden field is invalid. + O campo oculto é inválido. + + + Please enter an integer. + Por favor, informe um número inteiro. + + + Please select a valid language. + Por favor, selecione um idioma válido. + + + Please select a valid locale. + Por favor, selecione uma configuração de local válida. + + + Please enter a valid money amount. + Por favor, informe um valor monetário válido. + + + Please enter a number. + Por favor, informe um número. + + + The password is invalid. + A senha é inválida. + + + Please enter a percentage value. + Por favor, informe um valor percentual. + + + The values do not match. + Os valores não conferem. + + + Please enter a valid time. + Por favor, informe um horário válido. + + + Please select a valid timezone. + Por favor, selecione um fuso horário válido. + + + Please enter a valid URL. + Por favor, informe uma URL válida. + + + Please enter a valid search term. + Por favor, informe um termo de busca válido. + + + Please provide a valid phone number. + Por favor, informe um telefone válido. + + + The checkbox has an invalid value. + A caixa de seleção possui um valor inválido. + + + Please enter a valid email address. + Por favor, informe um endereço de e-mail válido. + + + Please select a valid option. + Por favor, selecione uma opção válida. + + + Please select a valid range. + Por favor, selecione um intervalo válido. + + + Please enter a valid week. + Por favor, informe uma semana válida. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.ro.xlf b/vendor/symfony/form/Resources/translations/validators.ro.xlf new file mode 100644 index 0000000..63b4c55 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.ro.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Acest formular nu ar trebui să conÈ›ină câmpuri suplimentare. + + + The uploaded file was too large. Please try to upload a smaller file. + FiÈ™ierul încărcat a fost prea mare. Vă rugăm sa încărcaÈ›i un fiÈ™ier mai mic. + + + The CSRF token is invalid. Please try to resubmit the form. + Token-ul CSRF este invalid. Vă rugăm să retrimiteÈ›i formularul. + + + This value is not a valid HTML5 color. + Această valoare nu este un cod de culoare HTML5 valid. + + + Please enter a valid birthdate. + Vă rugăm să introduceÈ›i o dată de naÈ™tere validă. + + + The selected choice is invalid. + Valoarea selectată este invalidă. + + + The collection is invalid. + ColecÈ›ia nu este validă. + + + Please select a valid color. + Vă rugăm să selectaÈ›i o culoare validă. + + + Please select a valid country. + Vă rugăm să selectaÈ›i o È›ară validă. + + + Please select a valid currency. + Vă rugăm să selectaÈ›i o monedă validă. + + + Please choose a valid date interval. + Vă rugăm să selectaÈ›i un interval de zile valid. + + + Please enter a valid date and time. + Vă rugăm să introduceÈ›i o dată È™i o oră validă. + + + Please enter a valid date. + Vă rugăm să introduceÈ›i o dată validă. + + + Please select a valid file. + Vă rugăm să selectaÈ›i un fiÈ™ier valid. + + + The hidden field is invalid. + Câmpul ascuns este invalid. + + + Please enter an integer. + Vă rugăm să introduceÈ›i un număr întreg. + + + Please select a valid language. + Vă rugăm să selectaÈ›i o limbă validă. + + + Please select a valid locale. + Vă rugăm să selectaÈ›i o setare locală validă. + + + Please enter a valid money amount. + Vă rugăm să introduceÈ›i o valoare monetară corectă. + + + Please enter a number. + Vă rugăm să introduceÈ›i un număr. + + + The password is invalid. + Parola nu este validă. + + + Please enter a percentage value. + Vă rugăm să introduceÈ›i o valoare procentuală. + + + The values do not match. + Valorile nu coincid. + + + Please enter a valid time. + Vă rugăm să introduceÈ›i o oră validă. + + + Please select a valid timezone. + Vă rugăm să selectaÈ›i un fus orar valid. + + + Please enter a valid URL. + Vă rugăm să introduceÈ›i un URL valid. + + + Please enter a valid search term. + Vă rugăm să introduceÈ›i un termen de căutare valid. + + + Please provide a valid phone number. + Vă rugăm să introduceÈ›i un număr de telefon valid. + + + The checkbox has an invalid value. + Bifa nu are o valoare validă. + + + Please enter a valid email address. + Vă rugăm să introduceÈ›i o adresă de email validă. + + + Please select a valid option. + Vă rugăm să selectaÈ›i o opÈ›iune validă. + + + Please select a valid range. + Vă rugăm să selectaÈ›i un interval valid. + + + Please enter a valid week. + Vă rugăm să introduceÈ›i o săptămână validă. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.ru.xlf b/vendor/symfony/form/Resources/translations/validators.ru.xlf new file mode 100644 index 0000000..26535d2 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.ru.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Эта форма не должна Ñодержать дополнительных полей. + + + The uploaded file was too large. Please try to upload a smaller file. + Загруженный файл Ñлишком большой. ПожалуйÑта, попробуйте загрузить файл меньшего размера. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF значение недопуÑтимо. ПожалуйÑта, попробуйте повторить отправку формы. + + + This value is not a valid HTML5 color. + Значение не ÑвлÑетÑÑ Ð´Ð¾Ð¿ÑƒÑтимым HTML5 цветом. + + + Please enter a valid birthdate. + ПожалуйÑта, введите дейÑтвительную дату рождениÑ. + + + The selected choice is invalid. + Выбранный вариант недопуÑтим. + + + The collection is invalid. + ÐšÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтима. + + + Please select a valid color. + ПожалуйÑта, выберите допуÑтимый цвет. + + + Please select a valid country. + ПожалуйÑта, выберите дейÑтвительную Ñтрану. + + + Please select a valid currency. + ПожалуйÑта, выберите дейÑтвительную валюту. + + + Please choose a valid date interval. + ПожалуйÑта, выберите дейÑтвительный период. + + + Please enter a valid date and time. + ПожалуйÑта, введите дейÑтвительные дату и времÑ. + + + Please enter a valid date. + ПожалуйÑта, введите дейÑтвительную дату. + + + Please select a valid file. + ПожалуйÑта, выберите допуÑтимый файл. + + + The hidden field is invalid. + Значение Ñкрытого Ð¿Ð¾Ð»Ñ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтимо. + + + Please enter an integer. + ПожалуйÑта, введите целое чиÑло. + + + Please select a valid language. + ПожалуйÑта, выберите допуÑтимый Ñзык. + + + Please select a valid locale. + ПожалуйÑта, выберите допуÑтимую локаль. + + + Please enter a valid money amount. + ПожалуйÑта, введите допуÑтимое количеÑтво денег. + + + Please enter a number. + ПожалуйÑта, введите номер. + + + The password is invalid. + Пароль недейÑтвителен. + + + Please enter a percentage value. + ПожалуйÑта, введите процентное значение. + + + The values do not match. + Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ Ñовпадают. + + + Please enter a valid time. + ПожалуйÑта, введите дейÑтвительное времÑ. + + + Please select a valid timezone. + ПожалуйÑта, выберите дейÑтвительный чаÑовой поÑÑ. + + + Please enter a valid URL. + ПожалуйÑта, введите дейÑтвительный URL. + + + Please enter a valid search term. + ПожалуйÑта, введите дейÑтвительный поиÑковый запроÑ. + + + Please provide a valid phone number. + ПожалуйÑта, введите дейÑтвительный номер телефона. + + + The checkbox has an invalid value. + Флажок имеет недопуÑтимое значение. + + + Please enter a valid email address. + ПожалуйÑта, введите допуÑтимый email адреÑ. + + + Please select a valid option. + ПожалуйÑта, выберите допуÑтимый вариант. + + + Please select a valid range. + ПожалуйÑта, выберите допуÑтимый диапазон. + + + Please enter a valid week. + ПожалуйÑта, введите дейÑтвительную неделю. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.sk.xlf b/vendor/symfony/form/Resources/translations/validators.sk.xlf new file mode 100644 index 0000000..72ecd13 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.sk.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Polia by nemali obsahovaÅ¥ ÄalÅ¡ie prvky. + + + The uploaded file was too large. Please try to upload a smaller file. + Odoslaný súbor je príliÅ¡ veľký. Prosím odoÅ¡lite súbor s menÅ¡ou veľkosÅ¥ou. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF token je neplatný. Prosím skúste znovu odoslaÅ¥ formulár. + + + This value is not a valid HTML5 color. + Táto hodnota nie je platná HTML5 farba. + + + Please enter a valid birthdate. + Prosím zadajte platný dátum narodenia. + + + The selected choice is invalid. + Vybraná možnosÅ¥ je neplatná. + + + The collection is invalid. + Kolekcia je neplatná. + + + Please select a valid color. + Prosím vyberte platnú farbu. + + + Please select a valid country. + Prosím vyberte platnú krajinu. + + + Please select a valid currency. + Prosím vyberte platnú menu. + + + Please choose a valid date interval. + Prosím vyberte platný rozsah dát. + + + Please enter a valid date and time. + Prosím zadajte platný dátum a Äas. + + + Please enter a valid date. + Prosím zadajte platný dátum. + + + Please select a valid file. + Prosím vyberte platný súbor. + + + The hidden field is invalid. + Skryté pole je neplatné. + + + Please enter an integer. + Prosím zadajte celé Äíslo. + + + Please select a valid language. + Prosím vyberte platný jazyk. + + + Please select a valid locale. + Prosím vyberte platné miestne nastavenia. + + + Please enter a valid money amount. + Prosím zadajte platnú Äiastku. + + + Please enter a number. + Prosím zadajte Äíslo. + + + The password is invalid. + Heslo je neprávne. + + + Please enter a percentage value. + Prosím zadajte percentuálnu hodnotu. + + + The values do not match. + Hodnoty nie sú zhodné. + + + Please enter a valid time. + Prosím zadajte platný Äas. + + + Please select a valid timezone. + Prosím vyberte platné Äasové pásmo. + + + Please enter a valid URL. + Prosím zadajte platnú URL. + + + Please enter a valid search term. + Prosím zadajte platný vyhľadávací výraz. + + + Please provide a valid phone number. + Prosím zadajte platné telefónne Äíslo. + + + The checkbox has an invalid value. + ZaÅ¡krtávacie políÄko má neplatnú hodnotu. + + + Please enter a valid email address. + Prosím zadajte platnú emailovú adresu. + + + Please select a valid option. + Prosím vyberte platnú možnosÅ¥. + + + Please select a valid range. + Prosím vyberte platný rozsah. + + + Please enter a valid week. + Prosím zadajte platný týždeň. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.sl.xlf b/vendor/symfony/form/Resources/translations/validators.sl.xlf new file mode 100644 index 0000000..c19949d --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.sl.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ta obrazec ne sme vsebovati dodatnih polj. + + + The uploaded file was too large. Please try to upload a smaller file. + Naložena datoteka je prevelika. Prosimo, poizkusite naložiti manjÅ¡o. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF vrednost je napaÄna. Prosimo, ponovno poÅ¡ljite obrazec. + + + This value is not a valid HTML5 color. + Ta vrednost ni veljavna barva HTML5. + + + Please enter a valid birthdate. + Prosimo, vnesite veljaven rojstni datum. + + + The selected choice is invalid. + Izbira ni veljavna. + + + The collection is invalid. + Zbirka ni veljavna. + + + Please select a valid color. + Prosimo, izberite veljavno barvo. + + + Please select a valid country. + Prosimo, izberite veljavno državo. + + + Please select a valid currency. + Prosimo, izberite veljavno valuto. + + + Please choose a valid date interval. + Prosimo, izberite veljaven datumski interval. + + + Please enter a valid date and time. + Prosimo, vnesite veljaven datum in Äas. + + + Please enter a valid date. + Prosimo, izberite veljaven datum. + + + Please select a valid file. + Prosimo, izberite veljavno datoteko. + + + The hidden field is invalid. + Skrito polje ni veljavno. + + + Please enter an integer. + Prosimo, vnesite celo Å¡tevilo. + + + Please select a valid language. + Prosimo, izberite veljaven jezik. + + + Please select a valid locale. + Prosimo, izberite veljavne podroÄne nastavitve. + + + Please enter a valid money amount. + Prosimo, vnesite veljaven denarni znesek. + + + Please enter a number. + Prosimo, vnesite Å¡tevilko. + + + The password is invalid. + Geslo ni veljavno. + + + Please enter a percentage value. + Prosimo, vnesite odstotno vrednost. + + + The values do not match. + Vrednosti se ne ujemajo. + + + Please enter a valid time. + Prosimo, vnesite veljaven Äas. + + + Please select a valid timezone. + Prosimo, izberite veljaven Äasovni pas. + + + Please enter a valid URL. + Prosimo, vnesite veljaven URL. + + + Please enter a valid search term. + Prosimo, vnesite veljaven iskalni izraz. + + + Please provide a valid phone number. + Prosimo, podajte veljavno telefonsko Å¡tevilko. + + + The checkbox has an invalid value. + Potrditveno polje vsebuje neveljavno vrednost. + + + Please enter a valid email address. + Prosimo, vnesite veljaven e-poÅ¡tni naslov. + + + Please select a valid option. + Prosimo, izberite veljavno možnost. + + + Please select a valid range. + Prosimo, izberite veljaven obseg. + + + Please enter a valid week. + Prosimo, vnesite veljaven teden. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.sq.xlf b/vendor/symfony/form/Resources/translations/validators.sq.xlf new file mode 100644 index 0000000..0feb137 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.sq.xlf @@ -0,0 +1,148 @@ + + + +
    + + Për fjalët e huaja, të cilat nuk kanë përkthim të drejtpërdrejtë, ju lutemi të ndiqni rregullat e mëposhtme: + a) në rast se emri është akronim i përdorur gjerësisht si i përveçëm, atëherë, emri lakohet pa thonjëza dhe mbaresa shkruhet me vizë ndarëse. Gjinia gjykohet sipas rastit. Shembull: JSON-i (mashkullore) + b) në rast se emri është akronim i papërdorur gjerësisht si i përveçëm, atëherë, emri lakohet pa thonjëza dhe mbaresa shkruhet me vizë ndarëse. Gjinia është femërore. Shembull: URL-ja (femërore) + c) në rast se emri duhet lakuar për shkak të rasës në fjali, atëherë, emri lakohet pa thonjëza dhe mbaresa shkruhet me vizë ndarëse. Shembull: host-i, prej host-it + d) në rast se emri nuk duhet lakuar për shkak të trajtës në fjali, atëherë, emri rrethohet me thonjëzat “â€. Shembull: “locale†+ +
    + + + This form should not contain extra fields. + Ky formular nuk duhet të përmbajë fusha shtesë. + + + The uploaded file was too large. Please try to upload a smaller file. + Skeda e ngarkuar ishte shumë e madhe. Ju lutemi provoni të ngarkoni një skedë më të vogël. + + + The CSRF token is invalid. Please try to resubmit the form. + Vlera CSRF është e pavlefshme. Ju lutemi provoni të ridërgoni formularin. + + + This value is not a valid HTML5 color. + Kjo vlerë nuk është një ngjyrë e vlefshme HTML5. + + + Please enter a valid birthdate. + Ju lutemi shkruani një datëlindje të vlefshme. + + + The selected choice is invalid. + Alternativa e zgjedhur është e pavlefshme. + + + The collection is invalid. + Koleksioni është i pavlefshëm. + + + Please select a valid color. + Ju lutemi zgjidhni një ngjyrë të vlefshme. + + + Please select a valid country. + Ju lutemi zgjidhni një shtet të vlefshëm. + + + Please select a valid currency. + Ju lutemi zgjidhni një valutë të vlefshme. + + + Please choose a valid date interval. + Ju lutemi zgjidhni një interval të vlefshëm. + + + Please enter a valid date and time. + Ju lutemi shkruani një datë dhe orë të vlefshme. + + + Please enter a valid date. + Ju lutemi shkruani një datë të vlefshme. + + + Please select a valid file. + Ju lutemi zgjidhni një skedë të vlefshme. + + + The hidden field is invalid. + Fusha e fshehur është e pavlefshme. + + + Please enter an integer. + Ju lutemi shkruani një numër të plotë. + + + Please select a valid language. + Ju lutemi zgjidhni një gjuhë të vlefshme. + + + Please select a valid locale. + Ju lutemi zgjidhni një “locale†të vlefshme. + + + Please enter a valid money amount. + Ju lutemi shkruani një shumë të vlefshme parash. + + + Please enter a number. + Ju lutemi shkruani një numër. + + + The password is invalid. + Fjalëkalimi është i pavlefshëm. + + + Please enter a percentage value. + Ju lutemi shkruani një vlerë përqindjeje. + + + The values do not match. + Vlerat nuk përputhen. + + + Please enter a valid time. + Ju lutemi shkruani një orë të vlefshme. + + + Please select a valid timezone. + Ju lutemi zgjidhni një zonë kohore të vlefshme. + + + Please enter a valid URL. + Ju lutemi shkruani një URL të vlefshme. + + + Please enter a valid search term. + Ju lutemi shkruani një term të vlefshëm kërkimi. + + + Please provide a valid phone number. + Ju lutemi jepni një numër telefoni të vlefshëm. + + + The checkbox has an invalid value. + Kutia e zgjedhjes ka një vlerë të pavlefshme. + + + Please enter a valid email address. + Ju lutemi shkruani një adresë të vlefshme email-i. + + + Please select a valid option. + Ju lutemi zgjidhni një alternativë të vlefshme. + + + Please select a valid range. + Ju lutemi zgjidhni një seri të vlefshme. + + + Please enter a valid week. + Ju lutemi shkruani një javë të vlefshme. + + +
    +
    diff --git a/vendor/symfony/form/Resources/translations/validators.sr_Cyrl.xlf b/vendor/symfony/form/Resources/translations/validators.sr_Cyrl.xlf new file mode 100644 index 0000000..4b3e5b9 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.sr_Cyrl.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Овај формулар не треба да Ñадржи додатна поља. + + + The uploaded file was too large. Please try to upload a smaller file. + Отпремљена датотека је била превелика. Молим покушајте отпремање мање датотеке. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF вредноÑÑ‚ није иÑправна. Покушајте поново. + + + This value is not a valid HTML5 color. + Ова вредноÑÑ‚ није иÑправна HTML5 боја. + + + Please enter a valid birthdate. + Молим упишите иÑправан датум рођења. + + + The selected choice is invalid. + Одабрани избор није иÑправан. + + + The collection is invalid. + Ова колекција није иÑправна. + + + Please select a valid color. + Молим изаберите иÑправну боју. + + + Please select a valid country. + Молим изаберите иÑправну државу. + + + Please select a valid currency. + Молим изаберите иÑправну валуту. + + + Please choose a valid date interval. + Молим изаберите иÑправан датумÑки интервал. + + + Please enter a valid date and time. + Молим упишите иÑправан датум и време. + + + Please enter a valid date. + Молим упишите иÑправан датум. + + + Please select a valid file. + Молим изаберите иÑправну датотеку. + + + The hidden field is invalid. + Скривено поље није иÑправно. + + + Please enter an integer. + Молим упишите цео број (integer). + + + Please select a valid language. + Молим изаберите иÑправан језик. + + + Please select a valid locale. + Молим изаберите иÑправну локализацију. + + + Please enter a valid money amount. + Молим упишите иÑправну количину новца. + + + Please enter a number. + Молим упишите број. + + + The password is invalid. + Ова лозинка није иÑправна. + + + Please enter a percentage value. + Молим упишите процентуалну вредноÑÑ‚. + + + The values do not match. + Дате вредноÑти Ñе не поклапају. + + + Please enter a valid time. + Молим упишите иÑправно време. + + + Please select a valid timezone. + Молим изаберите иÑправну временÑку зону. + + + Please enter a valid URL. + Молим упишите иÑправан URL. + + + Please enter a valid search term. + Молим упишите иÑправан термин за претрагу. + + + Please provide a valid phone number. + Молим наведите иÑправан број телефона. + + + The checkbox has an invalid value. + Поље за потврду Ñадржи неиÑправну вредноÑÑ‚. + + + Please enter a valid email address. + Молим упишите иÑправну email адреÑу. + + + Please select a valid option. + Молим изаберите иÑправну опцију. + + + Please select a valid range. + Молим изаберите иÑправан опÑег. + + + Please enter a valid week. + Молим упишите иÑправну Ñедмицу. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.sr_Latn.xlf b/vendor/symfony/form/Resources/translations/validators.sr_Latn.xlf new file mode 100644 index 0000000..6f64f56 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.sr_Latn.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ovaj formular ne treba da sadrži dodatna polja. + + + The uploaded file was too large. Please try to upload a smaller file. + Otpremljena datoteka je bila prevelika. Molim pokuÅ¡ajte otpremanje manje datoteke. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF vrednost nije ispravna. PokuÅ¡ajte ponovo. + + + This value is not a valid HTML5 color. + Ova vrednost nije ispravna HTML5 boja. + + + Please enter a valid birthdate. + Molim upiÅ¡ite ispravan datum roÄ‘enja. + + + The selected choice is invalid. + Odabrani izbor nije ispravan. + + + The collection is invalid. + Ova kolekcija nije ispravna. + + + Please select a valid color. + Molim izaberite ispravnu boju. + + + Please select a valid country. + Molim izaberite ispravnu državu. + + + Please select a valid currency. + Molim izaberite ispravnu valutu. + + + Please choose a valid date interval. + Molim izaberite ispravan datumski interval. + + + Please enter a valid date and time. + Molim upiÅ¡ite ispravan datum i vreme. + + + Please enter a valid date. + Molim upiÅ¡ite ispravan datum. + + + Please select a valid file. + Molim izaberite ispravnu datoteku. + + + The hidden field is invalid. + Skriveno polje nije ispravno. + + + Please enter an integer. + Molim upiÅ¡ite ceo broj (integer). + + + Please select a valid language. + Molim izaberite ispravan jezik. + + + Please select a valid locale. + Molim izaberite ispravnu lokalizaciju. + + + Please enter a valid money amount. + Molim upiÅ¡ite ispravnu koliÄinu novca. + + + Please enter a number. + Molim upiÅ¡ite broj. + + + The password is invalid. + Ova lozinka nije ispravna. + + + Please enter a percentage value. + Molim upiÅ¡ite procentualnu vrednost. + + + The values do not match. + Date vrednosti se ne poklapaju. + + + Please enter a valid time. + Molim upiÅ¡ite ispravno vreme. + + + Please select a valid timezone. + Molim izaberite ispravnu vremensku zonu. + + + Please enter a valid URL. + Molim upiÅ¡ite ispravan URL. + + + Please enter a valid search term. + Molim upiÅ¡ite ispravan termin za pretragu. + + + Please provide a valid phone number. + Molim navedite ispravan broj telefona. + + + The checkbox has an invalid value. + Polje za potvrdu sadrži neispravnu vrednost. + + + Please enter a valid email address. + Molim upiÅ¡ite ispravnu email adresu. + + + Please select a valid option. + Molim izaberite ispravnu opciju. + + + Please select a valid range. + Molim izaberite ispravan opseg. + + + Please enter a valid week. + Molim upiÅ¡ite ispravnu sedmicu. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.sv.xlf b/vendor/symfony/form/Resources/translations/validators.sv.xlf new file mode 100644 index 0000000..052a569 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.sv.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Formuläret kan inte innehÃ¥lla extra fält. + + + The uploaded file was too large. Please try to upload a smaller file. + Den uppladdade filen var för stor. Försök ladda upp en mindre fil. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF-elementet är inte giltigt. Försök att skicka formuläret igen. + + + This value is not a valid HTML5 color. + Värdet är inte en giltig HTML5-färg. + + + Please enter a valid birthdate. + Ange ett giltigt födelsedatum. + + + The selected choice is invalid. + Det valda alternativet är ogiltigt. + + + The collection is invalid. + Den här samlingen är ogiltig. + + + Please select a valid color. + Välj en giltig färg. + + + Please select a valid country. + Välj ett land. + + + Please select a valid currency. + Välj en valuta. + + + Please choose a valid date interval. + Välj ett giltigt datumintervall. + + + Please enter a valid date and time. + Ange ett giltigt datum och tid. + + + Please enter a valid date. + Ange ett giltigt datum. + + + Please select a valid file. + Välj en fil. + + + The hidden field is invalid. + Det dolda fältet är ogiltigt. + + + Please enter an integer. + Ange ett heltal. + + + Please select a valid language. + Välj sprÃ¥k. + + + Please select a valid locale. + Välj plats. + + + Please enter a valid money amount. + Ange en giltig summa pengar. + + + Please enter a number. + Ange en siffra. + + + The password is invalid. + Lösenordet är ogiltigt. + + + Please enter a percentage value. + Ange ett procentuellt värde. + + + The values do not match. + De angivna värdena stämmer inte överens. + + + Please enter a valid time. + Ange en giltig tid. + + + Please select a valid timezone. + Välj en tidszon. + + + Please enter a valid URL. + Ange en giltig URL. + + + Please enter a valid search term. + Ange ett giltigt sökbegrepp. + + + Please provide a valid phone number. + Ange ett giltigt telefonnummer. + + + The checkbox has an invalid value. + Kryssrutan har ett ogiltigt värde. + + + Please enter a valid email address. + Ange en giltig e-postadress. + + + Please select a valid option. + Välj ett alternativ. + + + Please select a valid range. + Välj ett intervall. + + + Please enter a valid week. + Ange en giltig vecka. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.th.xlf b/vendor/symfony/form/Resources/translations/validators.th.xlf new file mode 100644 index 0000000..82d417d --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.th.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + ฟอร์มนี้ไม่ควรมี extra fields + + + The uploaded file was too large. Please try to upload a smaller file. + ไฟล์ที่อัพโหลดมีขนาดใหà¸à¹ˆà¹€à¸à¸´à¸™à¹„ป à¸à¸£à¸¸à¸“าลองอัพโหลดใหม่อีà¸à¸„รั้งด้วยไฟล์ที่มีขนาดเล็à¸à¸¥à¸‡ + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF token ไม่ถูà¸à¸•้อง à¸à¸£à¸¸à¸“าลองส่งà¹à¸šà¸šà¸Ÿà¸­à¸£à¹Œà¸¡à¹ƒà¸«à¸¡à¹ˆ + + + This value is not a valid HTML5 color. + ค่านี้ไม่ใช่ค่าที่ถูà¸à¸•้องของค่าสี HTML5 + + + Please enter a valid birthdate. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸§à¸±à¸™à¹€à¸”ือนปีเà¸à¸´à¸”ที่ถูà¸à¸•้อง + + + The selected choice is invalid. + ตัวเลือà¸à¸—ี่เลิอà¸à¹„ม่ถูà¸à¸•้อง + + + The collection is invalid. + คอเล็à¸à¸Šà¸±à¹ˆà¸™à¹„ม่ถูà¸à¸•้อง + + + Please select a valid color. + à¸à¸£à¸¸à¸“าเลือà¸à¸„่าสีที่ถูà¸à¸•้อง + + + Please select a valid country. + à¸à¸£à¸¸à¸“าเลือà¸à¸›à¸£à¸°à¹€à¸—ศที่ถูà¸à¸•้อง + + + Please select a valid currency. + à¸à¸£à¸¸à¸¸à¸“าเลิอà¸à¸„่าสà¸à¸¸à¸¥à¹€à¸‡à¸´à¸™à¸—ี่ถูà¸à¸•้อง + + + Please choose a valid date interval. + à¸à¸£à¸¸à¸“ณาà¸à¸£à¸­à¸à¸Šà¹ˆà¸§à¸‡à¸§à¸±à¸™à¸—ี่ที่ถูà¸à¸•้อง + + + Please enter a valid date and time. + à¸à¸£à¸¸à¸“ณาà¸à¸£à¸­à¸à¸„่าเวลาà¹à¸¥à¸°à¸§à¸±à¸™à¸—ี่ที่ถูà¸à¸•้อง + + + Please enter a valid date. + à¸à¸£à¸¸à¸“ณาà¸à¸£à¸­à¸à¸„่าวันที่ที่ถูà¸à¸•้อง + + + Please select a valid file. + à¸à¸£à¸¸à¸“าเลือà¸à¹„ฟล์ที่ถูà¸à¸•้อง + + + The hidden field is invalid. + ค่า Hidden field ไม่ถูà¸à¸•้อง + + + Please enter an integer. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸•ัวเลขจำนวนเต็ม + + + Please select a valid language. + à¸à¸£à¸¸à¸“าเลือà¸à¸ à¸²à¸©à¸²à¸—ี่ถูà¸à¸•้อง + + + Please select a valid locale. + à¸à¸£à¸¸à¸“าเลือà¸à¸—้องถิ่นที่ถูà¸à¸•้อง + + + Please enter a valid money amount. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸ˆà¸³à¸™à¸§à¸™à¹€à¸‡à¸´à¸™à¸—ี่ถูà¸à¸•้อง + + + Please enter a number. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸•ัวเลข + + + The password is invalid. + รหัสผ่านไม่ถูà¸à¸•้อง + + + Please enter a percentage value. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸„่าเปอร์เซ็นต์ + + + The values do not match. + ค่าทั้งสองไม่ตรงà¸à¸±à¸™ + + + Please enter a valid time. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸„่าเวลาที่ถูà¸à¸•้อง + + + Please select a valid timezone. + à¸à¸£à¸¸à¸“าเลือà¸à¸„่าเขตเวลาที่ถูà¸à¸•้อง + + + Please enter a valid URL. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸ URL ที่ถูà¸à¸•้อง + + + Please enter a valid search term. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸„ำค้นหาที่ถูà¸à¸•้อง + + + Please provide a valid phone number. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¹€à¸šà¸­à¸£à¹Œà¹‚ทรศัพท์ที่ถูà¸à¸•้อง + + + The checkbox has an invalid value. + Checkbox มีค่าที่ไม่ถูà¸à¸•้อง + + + Please enter a valid email address. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸—ี่อยู่อีเมล์ที่ถูà¸à¸•้อง + + + Please select a valid option. + à¸à¸£à¸¸à¸“าเลือà¸à¸•ัวเลือà¸à¸—ี่ถูà¸à¸•้อง + + + Please select a valid range. + à¸à¸£à¸¸à¸“าเลือà¸à¸„่าช่วงที่ถูà¸à¸•้อง + + + Please enter a valid week. + à¸à¸£à¸¸à¸“าà¸à¸£à¸­à¸à¸„่าสัปดาห์ที่ถูà¸à¸•้อง + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.tl.xlf b/vendor/symfony/form/Resources/translations/validators.tl.xlf new file mode 100644 index 0000000..6aeef41 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.tl.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ang pormang itong ay hindi dapat magkarron ng dagdag na mga patlang. + + + The uploaded file was too large. Please try to upload a smaller file. + Ang ini-upload na file ay masyadong malaki. Pakiulit muling mag-upload ng mas maliit na file. + + + The CSRF token is invalid. Please try to resubmit the form. + Hindi balido ang CSRF token. Maagpasa muli ng isang pang porma. + + + This value is not a valid HTML5 color. + Ang halagang ito ay hindi wastong HTML5 color. + + + Please enter a valid birthdate. + Pakilagay ang tamang petsa ng kapanganakan. + + + The selected choice is invalid. + Ang pinagpiliang sagot ay hindi tama. + + + The collection is invalid. + Hindi balido ang koleksyon. + + + Please select a valid color. + Pakipiliin ang nararapat na kulay. + + + Please select a valid country. + Pakipiliin ang nararapat na bansa. + + + Please select a valid currency. + Pakipiliin ang tamang pananalapi. + + + Please choose a valid date interval. + Piliin ang wastong agwat ng petsa. + + + Please enter a valid date and time. + Piliin ang wastong petsa at oras. + + + Please enter a valid date. + Ilagay ang wastong petsa. + + + Please select a valid file. + Piliin ang balidong file. + + + The hidden field is invalid. + Hindi balido ang field na nakatago. + + + Please enter an integer. + Pakilagay ang integer. + + + Please select a valid language. + Piliin ang nararapat na lengguwahe. + + + Please select a valid locale. + Pakipili ang nararapat na locale. + + + Please enter a valid money amount. + Pakilagay ang tamang halaga ng pera. + + + Please enter a number. + Ilagay ang numero. + + + The password is invalid. + Hindi balido ang password. + + + Please enter a percentage value. + Pakilagay ang tamang porsyento ng halaga. + + + The values do not match. + Hindi tugma ang mga halaga. + + + Please enter a valid time. + Pakilagay ang tamang oras. + + + Please select a valid timezone. + Pakilagay ang tamang sona ng oras. + + + Please enter a valid URL. + Pakilagay ang balidong URL. + + + Please enter a valid search term. + Pakilagay ang balidong katagang sinasaliksik. + + + Please provide a valid phone number. + Pakilagay ang balidong numero ng telepono. + + + The checkbox has an invalid value. + Ang checkbox ay mayroon hindi balidong halaga. + + + Please enter a valid email address. + Pakilagay ang balidong email address. + + + Please select a valid option. + Pakipiliin ang balidong pagpipilian. + + + Please select a valid range. + Pakipilian ang balidong layo. + + + Please enter a valid week. + Pakilagay ang balidong linggo. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.tr.xlf b/vendor/symfony/form/Resources/translations/validators.tr.xlf new file mode 100644 index 0000000..71a4696 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.tr.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Form ekstra alanlar içeremez. + + + The uploaded file was too large. Please try to upload a smaller file. + Yüklenen dosya boyutu çok yüksek. Lütfen daha küçük bir dosya yüklemeyi deneyin. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF fiÅŸi geçersiz. Formu tekrar göndermeyi deneyin. + + + This value is not a valid HTML5 color. + Bu deÄŸer, geçerli bir HTML5 rengi deÄŸil. + + + Please enter a valid birthdate. + Lütfen geçerli bir doÄŸum tarihi girin. + + + The selected choice is invalid. + Seçilen seçim geçersiz. + + + The collection is invalid. + Koleksiyon geçersiz. + + + Please select a valid color. + Lütfen geçerli bir renk seçin. + + + Please select a valid country. + Lütfen geçerli bir ülke seçin. + + + Please select a valid currency. + Lütfen geçerli bir para birimi seçin. + + + Please choose a valid date interval. + Lütfen geçerli bir tarih aralığı seçin. + + + Please enter a valid date and time. + Lütfen geçerli bir tarih ve saat girin. + + + Please enter a valid date. + Lütfen geçerli bir tarih giriniz. + + + Please select a valid file. + Lütfen geçerli bir dosya seçin. + + + The hidden field is invalid. + Gizli alan geçersiz. + + + Please enter an integer. + Lütfen bir tam sayı girin. + + + Please select a valid language. + Lütfen geçerli bir dil seçin. + + + Please select a valid locale. + Lütfen geçerli bir yerel ayar seçin. + + + Please enter a valid money amount. + Lütfen geçerli bir para tutarı girin. + + + Please enter a number. + Lütfen bir numara giriniz. + + + The password is invalid. + Åžifre geçersiz. + + + Please enter a percentage value. + Lütfen bir yüzde deÄŸeri girin. + + + The values do not match. + DeÄŸerler eÅŸleÅŸmiyor. + + + Please enter a valid time. + Lütfen geçerli bir zaman girin. + + + Please select a valid timezone. + Lütfen geçerli bir saat dilimi seçin. + + + Please enter a valid URL. + Lütfen geçerli bir giriniz URL. + + + Please enter a valid search term. + Lütfen geçerli bir arama terimi girin. + + + Please provide a valid phone number. + lütfen geçerli bir telefon numarası saÄŸlayın. + + + The checkbox has an invalid value. + Onay kutusunda geçersiz bir deÄŸer var. + + + Please enter a valid email address. + Lütfen geçerli bir e-posta adresi girin. + + + Please select a valid option. + Lütfen geçerli bir seçenek seçin. + + + Please select a valid range. + Lütfen geçerli bir aralık seçin. + + + Please enter a valid week. + Lütfen geçerli bir hafta girin. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.uk.xlf b/vendor/symfony/form/Resources/translations/validators.uk.xlf new file mode 100644 index 0000000..c6bbca1 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.uk.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ð¦Ñ Ñ„Ð¾Ñ€Ð¼Ð° не повинна міÑтити додаткових полів. + + + The uploaded file was too large. Please try to upload a smaller file. + Завантажений файл занадто великий. Будь лаÑка, Ñпробуйте завантажити файл меншого розміру. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтиме. Будь лаÑка, Ñпробуйте відправити форму знову. + + + This value is not a valid HTML5 color. + Це Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ Ñ” допуÑтимим кольором HTML5. + + + Please enter a valid birthdate. + Будь лаÑка, введіть дійÑну дату народженнÑ. + + + The selected choice is invalid. + Обраний варіант недійÑний. + + + The collection is invalid. + ÐšÐ¾Ð»ÐµÐºÑ†Ñ–Ñ Ð½ÐµÐ´Ñ–Ð¹Ñна. + + + Please select a valid color. + Будь лаÑка, оберіть дійÑний колір. + + + Please select a valid country. + Будь лаÑка, оберіть дійÑну країну. + + + Please select a valid currency. + Будь лаÑка, оберіть дійÑну валюту. + + + Please choose a valid date interval. + Будь лаÑка, оберіть дійÑний інтервал дати. + + + Please enter a valid date and time. + Будь лаÑка, введіть дійÑну дату та чаÑ. + + + Please enter a valid date. + Будь лаÑка, введіть дійÑну дату. + + + Please select a valid file. + Будь лаÑка, оберіть дійÑний файл. + + + The hidden field is invalid. + Приховане поле недійÑне. + + + Please enter an integer. + Будь лаÑка, введіть ціле чиÑло. + + + Please select a valid language. + Будь лаÑка, оберіть дійÑну мову. + + + Please select a valid locale. + Будь лаÑка, оберіть дійÑну локаль. + + + Please enter a valid money amount. + Будь лаÑка, введіть дійÑну Ñуму грошей. + + + Please enter a number. + Будь лаÑка, введіть чиÑло. + + + The password is invalid. + Пароль недійÑний. + + + Please enter a percentage value. + Будь лаÑка, введіть процентне значеннÑ. + + + The values do not match. + Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ збігаютьÑÑ. + + + Please enter a valid time. + Будь лаÑка, введіть дійÑний чаÑ. + + + Please select a valid timezone. + Будь лаÑка, оберіть дійÑний чаÑовий поÑÑ. + + + Please enter a valid URL. + Будь лаÑка, введіть дійÑну URL-адреÑу. + + + Please enter a valid search term. + Будь лаÑка, введіть дійÑний пошуковий термін. + + + Please provide a valid phone number. + Будь лаÑка, введіть дійÑний номер телефону. + + + The checkbox has an invalid value. + Прапорець має недійÑне значеннÑ. + + + Please enter a valid email address. + Будь лаÑка, введіть дійÑну адреÑу електронної пошти. + + + Please select a valid option. + Будь лаÑка, оберіть дійÑний варіант. + + + Please select a valid range. + Будь лаÑка, оберіть дійÑний діапазон. + + + Please enter a valid week. + Будь лаÑка, введіть дійÑний тиждень. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.ur.xlf b/vendor/symfony/form/Resources/translations/validators.ur.xlf new file mode 100644 index 0000000..42b891b --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.ur.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + اس ÙØ§Ø±Ù… میں اضاÙÛŒ Ùیلڈز Ù†Ûیں Ûونی Ú†Ø§ÛØ¦ÛŒÚº + + + The uploaded file was too large. Please try to upload a smaller file. + اپ لوڈ کردھ ÙØ§Ø¦Ù„ Ø¨ÛØª بڑی تھی۔ Ø¨Ø±Ø§Û Ú©Ø±Ù… ایک چھوٹی ÙØ§Ø¦Ù„ اپ لوڈ کرنے Ú©ÛŒ کوشش کریں + + + The CSRF token is invalid. Please try to resubmit the form. + ٹوکن غلط ÛÛ’Û” براۓ کرم ÙØ§Ø±Ù… Ú©Ùˆ Ø¯ÙˆØ¨Ø§Ø±Û Ø¬Ù…Ø¹ کرانے Ú©ÛŒ کوشش کریں CSRF + + + This value is not a valid HTML5 color. + ر Ù†Ú¯ نھیں Ú¾Û’HTML یھ ولیو در ست + + + Please enter a valid birthdate. + براۓ کرم درست تاریخ پیدائش درج کریں + + + The selected choice is invalid. + منتخب Ú©Ø±Ø¯Û Ø§Ù†ØªØ®Ø§Ø¨ غلط ÛÛ’ + + + The collection is invalid. + یھ Ù…Ø¬Ù…ÙˆØ¹Û ØºÙ„Ø· ÛÛ’ + + + Please select a valid color. + براۓ کرم ایک درست رنگ منتخب کریں + + + Please select a valid country. + براۓ کرم ایک درست ملک منتخب کریں + + + Please select a valid currency. + براۓ کرم ایک درست کرنسی منتخب کریں + + + Please choose a valid date interval. + براۓ کرم ایک درست تاریخی وقÙÚ¾Û Ù…Ù†ØªØ®Ø¨ کریں + + + Please enter a valid date and time. + براۓ کرم ایک درست تاریخ اور وقت درج کریں + + + Please enter a valid date. + براۓ کرم ایک درست تاریخ درج کریں + + + Please select a valid file. + براۓ کرم ایک درست ÙØ§Ø¦Ù„ منتخب کریں + + + The hidden field is invalid. + Ù¾ÙˆØ´ÛŒØ¯Ú¾Û Ùیلڈ غلط ÛÛ’ + + + Please enter an integer. + براۓ کرم ایک عدد درج کریں + + + Please select a valid language. + براۓ کرم ایک درست زبان منتخب کریں + + + Please select a valid locale. + براۓ کرم ایک درست مقام منتخب کریں + + + Please enter a valid money amount. + براۓ کرم ایک درست رقم درج کریں + + + Please enter a number. + براۓ کرم ایک نمبر درج کریں + + + The password is invalid. + پاس ورڈ غلط ÛÛ’ + + + Please enter a percentage value. + Ø¨Ø±Ø§Û Ú©Ø±Ù… Ùیصد Ú©ÛŒ ويلو درج کریں + + + The values do not match. + ويليوذ Ù¹Ú¾ÙŠÚ© Ù†Ûیں Ûیں + + + Please enter a valid time. + براۓ کرم ایک درست وقت درج کریں + + + Please select a valid timezone. + براۓ کرم ایک درست ٹائم زون منتخب کریں + + + Please enter a valid URL. + براۓ کرم ایک درست ادريس درج کریں + + + Please enter a valid search term. + براۓ کرم ایک درست ويلو تلاش کيلۓ درج کریں + + + Please provide a valid phone number. + براۓ کرم ایک درست Ùون نمبر ÙØ±Ø§ÛÙ… کریں + + + The checkbox has an invalid value. + چیک باکس میں ایک غلط ويلو ÛÛ’ + + + Please enter a valid email address. + براۓ Ù…ÛØ±Ø¨Ø§Ù†ÛŒ قابل قبول ای میل ایڈریس لکھیں + + + Please select a valid option. + براۓ کرم ایک درست آپشن منتخب کریں + + + Please select a valid range. + براۓ کرم ایک درست رینج منتخب کریں + + + Please enter a valid week. + براۓ کرم ایک درست ÛÙØªÛ درج کریں + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.uz.xlf b/vendor/symfony/form/Resources/translations/validators.uz.xlf new file mode 100644 index 0000000..86be237 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.uz.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Ushbu fo'rmada qo'shimcha maydonlar bo'lmasligi kerak. + + + The uploaded file was too large. Please try to upload a smaller file. + Yuklab olingan fayl juda katta. Iltimos, kichikroq faylni yuklashga harakat qiling. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF qiymati yaroqsiz. Fo'rmani qayta yuborishga harakat qiling. + + + This value is not a valid HTML5 color. + Qiymat noto'g'ri, HTML5 rangi emas. + + + Please enter a valid birthdate. + Iltimos, tug'ilgan kuningizni to'g'ri kiriting. + + + The selected choice is invalid. + Tanlangan parametr noto'g'ri. + + + The collection is invalid. + Kolleksiya noto'g'ri + + + Please select a valid color. + Iltimos, to'g'ri rang tanlang. + + + Please select a valid country. + Iltimos, to'g'ri mamlakatni tanlang. + + + Please select a valid currency. + Iltimos, to'g'ri valyutani tanlang. + + + Please choose a valid date interval. + Iltimos, to'g'ri sana oralig'ini tanlang. + + + Please enter a valid date and time. + Iltimos, to'g'ri sana va vaqtni kiriting. + + + Please enter a valid date. + Iltimos, to'g'ri sanani kiriting. + + + Please select a valid file. + Iltimos, to'g'ri faylni tanlang. + + + The hidden field is invalid. + Yashirin maydon qiymati yaroqsiz. + + + Please enter an integer. + Iltimos, butun son kiriting. + + + Please select a valid language. + Iltimos, to'g'ri tilni tanlang. + + + Please select a valid locale. + Iltimos, to'g'ri localni tanlang. + + + Please enter a valid money amount. + Iltimos, tegishli miqdordagi pulni kiriting. + + + Please enter a number. + Iltimos, raqam kiriting. + + + The password is invalid. + Parol noto'g'ri. + + + Please enter a percentage value. + Iltimos, foyizli qiymat kiriting. + + + The values do not match. + Qiymatlar mos kelmaydi. + + + Please enter a valid time. + Iltimos, to'g'ri vaqtni tanlang. + + + Please select a valid timezone. + Iltimos, to'g'ri vaqt zonasini tanlang. + + + Please enter a valid URL. + Iltimos, to'g'ri URL kiriting. + + + Please enter a valid search term. + Iltimos, to'g'ri qidiruv so'zini kiriting. + + + Please provide a valid phone number. + Iltimos, to'g'ri telefon raqamini kiriting. + + + The checkbox has an invalid value. + Belgilash katagida yaroqsiz qiymat mavjud. + + + Please enter a valid email address. + Iltimos, to'g'ri email kiriting. + + + Please select a valid option. + Iltimos, yaroqli variantni tanlang. + + + Please select a valid range. + Iltimos, yaroqli oraliqni tanlang. + + + Please enter a valid week. + Iltimos, haqiqiy haftani kiriting. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.vi.xlf b/vendor/symfony/form/Resources/translations/validators.vi.xlf new file mode 100644 index 0000000..92171c0 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.vi.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + Mẫu này không nên chứa trưá»ng mở rá»™ng. + + + The uploaded file was too large. Please try to upload a smaller file. + Tập tin tải lên quá lá»›n. Vui lòng thá»­ lại vá»›i tập tin nhá» hÆ¡n. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF token không hợp lệ. Vui lòng thá»­ lại. + + + This value is not a valid HTML5 color. + Giá trị này không phải là màu HTML5 hợp lệ. + + + Please enter a valid birthdate. + Vui loÌ€ng nhập ngaÌ€y sinh hợp lệ. + + + The selected choice is invalid. + Lựa choÌ£n không hợp lệ. + + + The collection is invalid. + Danh saÌch không hợp lệ. + + + Please select a valid color. + Vui loÌ€ng choÌ£n một maÌ€u hợp lệ. + + + Please select a valid country. + Vui loÌ€ng choÌ£n đâÌt nươÌc hợp lệ. + + + Please select a valid currency. + Vui loÌ€ng choÌ£n tiền tệ hợp lệ. + + + Please choose a valid date interval. + Vui loÌ€ng choÌ£n một khoảng thời gian hợp lệ. + + + Please enter a valid date and time. + Vui loÌ€ng nhập ngaÌ€y vaÌ€ thời gian hợp lệ. + + + Please enter a valid date. + Vui loÌ€ng nhập ngaÌ€y hợp lệ. + + + Please select a valid file. + Vui loÌ€ng choÌ£n tệp hợp lệ. + + + The hidden field is invalid. + PhaÌ£m vi ẩn không hợp lệ. + + + Please enter an integer. + Vui loÌ€ng nhập một sÃ´Ì nguyên. + + + Please select a valid language. + Vui loÌ€ng choÌ£n ngôn ngữ hợp lệ. + + + Please select a valid locale. + Vui loÌ€ng choÌ£n miền hợp lệ. + + + Please enter a valid money amount. + Vui loÌ€ng nhập một khoảng tiền hợp lệ. + + + Please enter a number. + Vui lòng nhập má»™t con số. + + + The password is invalid. + Mật khẩu không hợp lệ. + + + Please enter a percentage value. + Vui loÌ€ng nhập một giaÌ triÌ£ phần trăm. + + + The values do not match. + CaÌc giaÌ triÌ£ không phuÌ€ hợp. + + + Please enter a valid time. + Vui loÌ€ng nhập thời gian hợp lệ. + + + Please select a valid timezone. + Vui loÌ€ng choÌ£n muÌi giờ hợp lệ. + + + Please enter a valid URL. + Vui loÌ€ng nhập một URL hợp lệ. + + + Please enter a valid search term. + Vui loÌ€ng nhập chuỗi tiÌ€m kiêÌm hợp lệ. + + + Please provide a valid phone number. + Vui loÌ€ng cung câÌp sÃ´Ì Ä‘iện thoaÌ£i hợp lệ. + + + The checkbox has an invalid value. + Hộp kiểm coÌ một giaÌ triÌ£ không hợp lệ. + + + Please enter a valid email address. + Vui loÌ€ng nhập Ä‘iÌ£a chỉ email hợp lệ. + + + Please select a valid option. + Vui loÌ€ng choÌ£n một phương aÌn hợp lệ. + + + Please select a valid range. + Vui loÌ€ng nhập một phaÌ£m vi hợp lệ. + + + Please enter a valid week. + Vui loÌ€ng nhập một tuần hợp lệ. + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.zh_CN.xlf b/vendor/symfony/form/Resources/translations/validators.zh_CN.xlf new file mode 100644 index 0000000..a1469b7 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.zh_CN.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + 该表å•中ä¸å¯æœ‰é¢å¤–字段. + + + The uploaded file was too large. Please try to upload a smaller file. + 上传文件太大, è¯·é‡æ–°å°è¯•上传一个较å°çš„æ–‡ä»¶. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF 验è¯ç¬¦æ— æ•ˆï¼Œ è¯·é‡æ–°æäº¤. + + + This value is not a valid HTML5 color. + è¯¥æ•°å€¼ä¸æ˜¯ä¸ªæœ‰æ•ˆçš„ HTML5 颜色。 + + + Please enter a valid birthdate. + 请输入有效的生日日期。 + + + The selected choice is invalid. + 所选的选项无效。 + + + The collection is invalid. + é›†åˆæ— æ•ˆã€‚ + + + Please select a valid color. + 请选择有效的颜色。 + + + Please select a valid country. + 请选择有效的国家。 + + + Please select a valid currency. + 请选择有效的货å¸ã€‚ + + + Please choose a valid date interval. + 请选择有效的日期间隔。 + + + Please enter a valid date and time. + 请输入有效的日期与时间。 + + + Please enter a valid date. + 请输入有效的日期。 + + + Please select a valid file. + 请选择有效的文件。 + + + The hidden field is invalid. + éšè—字段无效。 + + + Please enter an integer. + 请输入整数。 + + + Please select a valid language. + 请选择有效的语言。 + + + Please select a valid locale. + 请选择有效的语言环境。 + + + Please enter a valid money amount. + 请输入正确的金é¢ã€‚ + + + Please enter a number. + 请输入数字。 + + + The password is invalid. + å¯†ç æ— æ•ˆã€‚ + + + Please enter a percentage value. + 请输入百分比值。 + + + The values do not match. + 数值ä¸åŒ¹é…。 + + + Please enter a valid time. + 请输入有效的时间。 + + + Please select a valid timezone. + 请选择有效的时区。 + + + Please enter a valid URL. + 请输入有效的网å€ã€‚ + + + Please enter a valid search term. + 请输入有效的æœç´¢è¯ã€‚ + + + Please provide a valid phone number. + 请æä¾›æœ‰æ•ˆçš„æ‰‹æœºå·ç ã€‚ + + + The checkbox has an invalid value. + 无效的选框值。 + + + Please enter a valid email address. + 请输入有效的电å­é‚®ä»¶åœ°å€ã€‚ + + + Please select a valid option. + 请选择有效的选项。 + + + Please select a valid range. + 请选择有效的范围。 + + + Please enter a valid week. + 请输入有效的星期。 + + + + diff --git a/vendor/symfony/form/Resources/translations/validators.zh_TW.xlf b/vendor/symfony/form/Resources/translations/validators.zh_TW.xlf new file mode 100644 index 0000000..8317597 --- /dev/null +++ b/vendor/symfony/form/Resources/translations/validators.zh_TW.xlf @@ -0,0 +1,139 @@ + + + + + + This form should not contain extra fields. + 該表單中ä¸å¯æœ‰é¡å¤–字段。 + + + The uploaded file was too large. Please try to upload a smaller file. + 上傳文件太大, è«‹é‡æ–°å˜—試上傳一個較å°çš„æ–‡ä»¶ã€‚ + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF 驗證符無效, è«‹é‡æ–°æäº¤ã€‚ + + + This value is not a valid HTML5 color. + è©²æ•¸å€¼ä¸æ˜¯å€‹æœ‰æ•ˆçš„ HTML5 é¡è‰²ã€‚ + + + Please enter a valid birthdate. + 請輸入有效的生日日期。 + + + The selected choice is invalid. + 所é¸çš„é¸é …無效。 + + + The collection is invalid. + 集åˆç„¡æ•ˆã€‚ + + + Please select a valid color. + è«‹é¸æ“‡æœ‰æ•ˆçš„é¡è‰²ã€‚ + + + Please select a valid country. + è«‹é¸æ“‡æœ‰æ•ˆçš„國家。 + + + Please select a valid currency. + è«‹é¸æ“‡æœ‰æ•ˆçš„貨幣。 + + + Please choose a valid date interval. + è«‹é¸æ“‡æœ‰æ•ˆçš„æ—¥æœŸé–“隔。 + + + Please enter a valid date and time. + 請輸入有效的日期與時間。 + + + Please enter a valid date. + 請輸入有效的日期。 + + + Please select a valid file. + è«‹é¸æ“‡æœ‰æ•ˆçš„æ–‡ä»¶ã€‚ + + + The hidden field is invalid. + éš±è—字段無效。 + + + Please enter an integer. + 請輸入整數。 + + + Please select a valid language. + è«‹é¸æ“‡æœ‰æ•ˆçš„語言。 + + + Please select a valid locale. + è«‹é¸æ“‡æœ‰æ•ˆçš„語言環境。 + + + Please enter a valid money amount. + 請輸入正確的金é¡ã€‚ + + + Please enter a number. + 請輸入數字。 + + + The password is invalid. + 密碼無效。 + + + Please enter a percentage value. + 請輸入百分比值。 + + + The values do not match. + 數值ä¸åŒ¹é…。 + + + Please enter a valid time. + 請輸入有效的時間。 + + + Please select a valid timezone. + è«‹é¸æ“‡æœ‰æ•ˆçš„æ™‚å€ã€‚ + + + Please enter a valid URL. + 請輸入有效的網å€ã€‚ + + + Please enter a valid search term. + 請輸入有效的æœç´¢è©žã€‚ + + + Please provide a valid phone number. + è«‹æä¾›æœ‰æ•ˆçš„æ‰‹æ©Ÿè™Ÿç¢¼ã€‚ + + + The checkbox has an invalid value. + ç„¡æ•ˆçš„é¸æ¡†å€¼ã€‚ + + + Please enter a valid email address. + 請輸入有效的電å­éƒµä»¶åœ°å€ã€‚ + + + Please select a valid option. + è«‹é¸æ“‡æœ‰æ•ˆçš„é¸é …。 + + + Please select a valid range. + è«‹é¸æ“‡æœ‰æ•ˆçš„範åœã€‚ + + + Please enter a valid week. + 請輸入有效的星期。 + + + + diff --git a/vendor/symfony/form/ReversedTransformer.php b/vendor/symfony/form/ReversedTransformer.php new file mode 100644 index 0000000..4aa9245 --- /dev/null +++ b/vendor/symfony/form/ReversedTransformer.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Reverses a transformer. + * + * When the transform() method is called, the reversed transformer's + * reverseTransform() method is called and vice versa. + * + * @author Bernhard Schussek + */ +class ReversedTransformer implements DataTransformerInterface +{ + public function __construct( + protected DataTransformerInterface $reversedTransformer, + ) { + } + + public function transform(mixed $value): mixed + { + return $this->reversedTransformer->reverseTransform($value); + } + + public function reverseTransform(mixed $value): mixed + { + return $this->reversedTransformer->transform($value); + } +} diff --git a/vendor/symfony/form/SubmitButton.php b/vendor/symfony/form/SubmitButton.php new file mode 100644 index 0000000..37ce141 --- /dev/null +++ b/vendor/symfony/form/SubmitButton.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A button that submits the form. + * + * @author Bernhard Schussek + */ +class SubmitButton extends Button implements ClickableInterface +{ + private bool $clicked = false; + + public function isClicked(): bool + { + return $this->clicked; + } + + /** + * Submits data to the button. + * + * @return $this + * + * @throws Exception\AlreadySubmittedException if the form has already been submitted + */ + public function submit(array|string|null $submittedData, bool $clearMissing = true): static + { + if ($this->getConfig()->getDisabled()) { + $this->clicked = false; + + return $this; + } + + parent::submit($submittedData, $clearMissing); + + $this->clicked = null !== $submittedData; + + return $this; + } +} diff --git a/vendor/symfony/form/SubmitButtonBuilder.php b/vendor/symfony/form/SubmitButtonBuilder.php new file mode 100644 index 0000000..b98398f --- /dev/null +++ b/vendor/symfony/form/SubmitButtonBuilder.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A builder for {@link SubmitButton} instances. + * + * @author Bernhard Schussek + */ +class SubmitButtonBuilder extends ButtonBuilder +{ + /** + * Creates the button. + */ + public function getForm(): SubmitButton + { + return new SubmitButton($this->getFormConfig()); + } +} diff --git a/vendor/symfony/form/SubmitButtonTypeInterface.php b/vendor/symfony/form/SubmitButtonTypeInterface.php new file mode 100644 index 0000000..f7ac13f --- /dev/null +++ b/vendor/symfony/form/SubmitButtonTypeInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A type that should be converted into a {@link SubmitButton} instance. + * + * @author Bernhard Schussek + */ +interface SubmitButtonTypeInterface extends FormTypeInterface +{ +} diff --git a/vendor/symfony/form/Test/FormBuilderInterface.php b/vendor/symfony/form/Test/FormBuilderInterface.php new file mode 100644 index 0000000..185a8a1 --- /dev/null +++ b/vendor/symfony/form/Test/FormBuilderInterface.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Test; + +use Symfony\Component\Form\FormBuilderInterface as BaseFormBuilderInterface; + +interface FormBuilderInterface extends \Iterator, BaseFormBuilderInterface +{ +} diff --git a/vendor/symfony/form/Test/FormIntegrationTestCase.php b/vendor/symfony/form/Test/FormIntegrationTestCase.php new file mode 100644 index 0000000..5bf37fd --- /dev/null +++ b/vendor/symfony/form/Test/FormIntegrationTestCase.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\Forms; + +/** + * @author Bernhard Schussek + */ +abstract class FormIntegrationTestCase extends TestCase +{ + protected FormFactoryInterface $factory; + + protected function setUp(): void + { + $this->factory = Forms::createFormFactoryBuilder() + ->addExtensions($this->getExtensions()) + ->addTypeExtensions($this->getTypeExtensions()) + ->addTypes($this->getTypes()) + ->addTypeGuessers($this->getTypeGuessers()) + ->getFormFactory(); + } + + protected function getExtensions() + { + return []; + } + + protected function getTypeExtensions() + { + return []; + } + + protected function getTypes() + { + return []; + } + + protected function getTypeGuessers() + { + return []; + } +} diff --git a/vendor/symfony/form/Test/FormInterface.php b/vendor/symfony/form/Test/FormInterface.php new file mode 100644 index 0000000..4af4603 --- /dev/null +++ b/vendor/symfony/form/Test/FormInterface.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Test; + +use Symfony\Component\Form\FormInterface as BaseFormInterface; + +interface FormInterface extends \Iterator, BaseFormInterface +{ +} diff --git a/vendor/symfony/form/Test/FormPerformanceTestCase.php b/vendor/symfony/form/Test/FormPerformanceTestCase.php new file mode 100644 index 0000000..54ccc67 --- /dev/null +++ b/vendor/symfony/form/Test/FormPerformanceTestCase.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Test; + +use Symfony\Component\Form\Tests\VersionAwareTest; + +/** + * Base class for performance tests. + * + * Copied from Doctrine 2's OrmPerformanceTestCase. + * + * @author robo + * @author Bernhard Schussek + */ +abstract class FormPerformanceTestCase extends FormIntegrationTestCase +{ + use VersionAwareTest; + + protected int $maxRunningTime = 0; + + protected function runTest(): mixed + { + $s = microtime(true); + $result = parent::runTest(); + $time = microtime(true) - $s; + + if (0 != $this->maxRunningTime && $time > $this->maxRunningTime) { + $this->fail(sprintf('expected running time: <= %s but was: %s', $this->maxRunningTime, $time)); + } + + return $result; + } + + /** + * @throws \InvalidArgumentException + */ + public function setMaxRunningTime(int $maxRunningTime): void + { + if ($maxRunningTime < 0) { + throw new \InvalidArgumentException(); + } + + $this->maxRunningTime = $maxRunningTime; + } + + public function getMaxRunningTime(): int + { + return $this->maxRunningTime; + } +} diff --git a/vendor/symfony/form/Test/Traits/ValidatorExtensionTrait.php b/vendor/symfony/form/Test/Traits/ValidatorExtensionTrait.php new file mode 100644 index 0000000..b89095d --- /dev/null +++ b/vendor/symfony/form/Test/Traits/ValidatorExtensionTrait.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Test\Traits; + +use Symfony\Component\Form\Extension\Validator\ValidatorExtension; +use Symfony\Component\Form\Test\TypeTestCase; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +trait ValidatorExtensionTrait +{ + protected ValidatorInterface $validator; + + protected function getValidatorExtension(): ValidatorExtension + { + if (!interface_exists(ValidatorInterface::class)) { + throw new \Exception('In order to use the "ValidatorExtensionTrait", the symfony/validator component must be installed.'); + } + + if (!$this instanceof TypeTestCase) { + throw new \Exception(sprintf('The trait "ValidatorExtensionTrait" can only be added to a class that extends "%s".', TypeTestCase::class)); + } + + $this->validator = $this->createMock(ValidatorInterface::class); + $metadata = $this->getMockBuilder(ClassMetadata::class)->setConstructorArgs([''])->onlyMethods(['addPropertyConstraint'])->getMock(); + $this->validator->expects($this->any())->method('getMetadataFor')->willReturn($metadata); + $this->validator->expects($this->any())->method('validate')->willReturn(new ConstraintViolationList()); + + return new ValidatorExtension($this->validator, false); + } +} diff --git a/vendor/symfony/form/Test/TypeTestCase.php b/vendor/symfony/form/Test/TypeTestCase.php new file mode 100644 index 0000000..960b442 --- /dev/null +++ b/vendor/symfony/form/Test/TypeTestCase.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Test; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\FormBuilder; +use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait; + +abstract class TypeTestCase extends FormIntegrationTestCase +{ + protected FormBuilder $builder; + protected EventDispatcherInterface $dispatcher; + + protected function setUp(): void + { + parent::setUp(); + + $this->dispatcher = $this->createMock(EventDispatcherInterface::class); + $this->builder = new FormBuilder('', null, $this->dispatcher, $this->factory); + } + + protected function getExtensions() + { + $extensions = []; + + if (\in_array(ValidatorExtensionTrait::class, class_uses($this), true)) { + $extensions[] = $this->getValidatorExtension(); + } + + return $extensions; + } + + public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual) + { + self::assertEquals($expected->format('c'), $actual->format('c')); + } + + public static function assertDateIntervalEquals(\DateInterval $expected, \DateInterval $actual) + { + self::assertEquals($expected->format('%RP%yY%mM%dDT%hH%iM%sS'), $actual->format('%RP%yY%mM%dDT%hH%iM%sS')); + } +} diff --git a/vendor/symfony/form/Util/FormUtil.php b/vendor/symfony/form/Util/FormUtil.php new file mode 100644 index 0000000..1a5cd3b --- /dev/null +++ b/vendor/symfony/form/Util/FormUtil.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +/** + * @author Bernhard Schussek + */ +class FormUtil +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Returns whether the given data is empty. + * + * This logic is reused multiple times throughout the processing of + * a form and needs to be consistent. PHP keyword `empty` cannot + * be used as it also considers 0 and "0" to be empty. + */ + public static function isEmpty(mixed $data): bool + { + // Should not do a check for [] === $data!!! + // This method is used in occurrences where arrays are + // not considered to be empty, ever. + return null === $data || '' === $data; + } + + /** + * Recursively replaces or appends elements of the first array with elements + * of second array. If the key is an integer, the values will be appended to + * the new array; otherwise, the value from the second array will replace + * the one from the first array. + */ + public static function mergeParamsAndFiles(array $params, array $files): array + { + $isFilesList = array_is_list($files); + + foreach ($params as $key => $value) { + if (\is_array($value) && \is_array($files[$key] ?? null)) { + $params[$key] = self::mergeParamsAndFiles($value, $files[$key]); + unset($files[$key]); + } + } + + if (!$isFilesList) { + return array_replace($params, $files); + } + + foreach ($files as $value) { + $params[] = $value; + } + + return $params; + } +} diff --git a/vendor/symfony/form/Util/InheritDataAwareIterator.php b/vendor/symfony/form/Util/InheritDataAwareIterator.php new file mode 100644 index 0000000..26a2135 --- /dev/null +++ b/vendor/symfony/form/Util/InheritDataAwareIterator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +/** + * Iterator that traverses an array of forms. + * + * Contrary to \ArrayIterator, this iterator recognizes changes in the original + * array during iteration. + * + * You can wrap the iterator into a {@link \RecursiveIteratorIterator} in order to + * enter any child form that inherits its parent's data and iterate the children + * of that form as well. + * + * @author Bernhard Schussek + */ +class InheritDataAwareIterator extends \IteratorIterator implements \RecursiveIterator +{ + public function getChildren(): static + { + return new static($this->current()); + } + + public function hasChildren(): bool + { + return (bool) $this->current()->getConfig()->getInheritData(); + } +} diff --git a/vendor/symfony/form/Util/OptionsResolverWrapper.php b/vendor/symfony/form/Util/OptionsResolverWrapper.php new file mode 100644 index 0000000..51cba4e --- /dev/null +++ b/vendor/symfony/form/Util/OptionsResolverWrapper.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +use Symfony\Component\OptionsResolver\Exception\AccessException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Yonel Ceruto + * + * @internal + */ +class OptionsResolverWrapper extends OptionsResolver +{ + private array $undefined = []; + + /** + * @return $this + */ + public function setNormalizer(string $option, \Closure $normalizer): static + { + try { + parent::setNormalizer($option, $normalizer); + } catch (UndefinedOptionsException) { + $this->undefined[$option] = true; + } + + return $this; + } + + /** + * @return $this + */ + public function setAllowedValues(string $option, mixed $allowedValues): static + { + try { + parent::setAllowedValues($option, $allowedValues); + } catch (UndefinedOptionsException) { + $this->undefined[$option] = true; + } + + return $this; + } + + /** + * @return $this + */ + public function addAllowedValues(string $option, mixed $allowedValues): static + { + try { + parent::addAllowedValues($option, $allowedValues); + } catch (UndefinedOptionsException) { + $this->undefined[$option] = true; + } + + return $this; + } + + /** + * @param string|array $allowedTypes + * + * @return $this + */ + public function setAllowedTypes(string $option, $allowedTypes): static + { + try { + parent::setAllowedTypes($option, $allowedTypes); + } catch (UndefinedOptionsException) { + $this->undefined[$option] = true; + } + + return $this; + } + + /** + * @param string|array $allowedTypes + * + * @return $this + */ + public function addAllowedTypes(string $option, $allowedTypes): static + { + try { + parent::addAllowedTypes($option, $allowedTypes); + } catch (UndefinedOptionsException) { + $this->undefined[$option] = true; + } + + return $this; + } + + public function resolve(array $options = []): array + { + throw new AccessException('Resolve options is not supported.'); + } + + public function getUndefinedOptions(): array + { + return array_keys($this->undefined); + } +} diff --git a/vendor/symfony/form/Util/OrderedHashMap.php b/vendor/symfony/form/Util/OrderedHashMap.php new file mode 100644 index 0000000..145bd2e --- /dev/null +++ b/vendor/symfony/form/Util/OrderedHashMap.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +/** + * A hash map which keeps track of deletions and additions. + * + * Like in associative arrays, elements can be mapped to integer or string keys. + * Unlike associative arrays, the map keeps track of the order in which keys + * were added and removed. This order is reflected during iteration. + * + * The map supports concurrent modification during iteration. That means that + * you can insert and remove elements from within a foreach loop and the + * iterator will reflect those changes accordingly. + * + * While elements that are added during the loop are recognized by the iterator, + * changed elements are not. Otherwise the loop could be infinite if each loop + * changes the current element: + * + * $map = new OrderedHashMap(); + * $map[1] = 1; + * $map[2] = 2; + * $map[3] = 3; + * + * foreach ($map as $index => $value) { + * echo "$index: $value\n" + * if (1 === $index) { + * $map[1] = 4; + * $map[] = 5; + * } + * } + * + * print_r(iterator_to_array($map)); + * + * // => 1: 1 + * // 2: 2 + * // 3: 3 + * // 4: 5 + * // Array + * // ( + * // [1] => 4 + * // [2] => 2 + * // [3] => 3 + * // [4] => 5 + * // ) + * + * The map also supports multiple parallel iterators. That means that you can + * nest foreach loops without affecting each other's iteration: + * + * foreach ($map as $index => $value) { + * foreach ($map as $index2 => $value2) { + * // ... + * } + * } + * + * @author Bernhard Schussek + * + * @template TValue + * + * @implements \ArrayAccess + * @implements \IteratorAggregate + */ +class OrderedHashMap implements \ArrayAccess, \IteratorAggregate, \Countable +{ + /** + * The keys of the map in the order in which they were inserted or changed. + * + * @var list + */ + private array $orderedKeys = []; + + /** + * References to the cursors of all open iterators. + * + * @var array + */ + private array $managedCursors = []; + + /** + * Creates a new map. + * + * @param TValue[] $elements The initial elements of the map, indexed by their keys + */ + public function __construct( + private array $elements = [], + ) { + // the explicit string type-cast is necessary as digit-only keys would be returned as integers otherwise + $this->orderedKeys = array_map(strval(...), array_keys($elements)); + } + + public function offsetExists(mixed $key): bool + { + return isset($this->elements[$key]); + } + + public function offsetGet(mixed $key): mixed + { + if (!isset($this->elements[$key])) { + throw new \OutOfBoundsException(sprintf('The offset "%s" does not exist.', $key)); + } + + return $this->elements[$key]; + } + + public function offsetSet(mixed $key, mixed $value): void + { + if (null === $key || !isset($this->elements[$key])) { + if (null === $key) { + $key = [] === $this->orderedKeys + // If the array is empty, use 0 as key + ? 0 + // Imitate PHP behavior of generating a key that equals + // the highest existing integer key + 1 + : 1 + (int) max($this->orderedKeys); + } + + $this->orderedKeys[] = (string) $key; + } + + $this->elements[$key] = $value; + } + + public function offsetUnset(mixed $key): void + { + if (false !== ($position = array_search((string) $key, $this->orderedKeys))) { + array_splice($this->orderedKeys, $position, 1); + unset($this->elements[$key]); + + foreach ($this->managedCursors as $i => $cursor) { + if ($cursor >= $position) { + --$this->managedCursors[$i]; + } + } + } + } + + public function getIterator(): \Traversable + { + return new OrderedHashMapIterator($this->elements, $this->orderedKeys, $this->managedCursors); + } + + public function count(): int + { + return \count($this->elements); + } +} diff --git a/vendor/symfony/form/Util/OrderedHashMapIterator.php b/vendor/symfony/form/Util/OrderedHashMapIterator.php new file mode 100644 index 0000000..927a28c --- /dev/null +++ b/vendor/symfony/form/Util/OrderedHashMapIterator.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +/** + * Iterator for {@link OrderedHashMap} objects. + * + * @author Bernhard Schussek + * + * @internal + * + * @template-covariant TValue + * + * @implements \Iterator + */ +class OrderedHashMapIterator implements \Iterator +{ + private int $cursor = 0; + private int $cursorId; + private ?string $key = null; + /** @var TValue|null */ + private mixed $current = null; + + /** + * @param TValue[] $elements The elements of the map, indexed by their + * keys + * @param list $orderedKeys The keys of the map in the order in which + * they should be iterated + * @param array $managedCursors An array from which to reference the + * iterator's cursor as long as it is alive. + * This array is managed by the corresponding + * {@link OrderedHashMap} instance to support + * recognizing the deletion of elements. + */ + public function __construct( + private array &$elements, + private array &$orderedKeys, + private array &$managedCursors, + ) { + $this->cursorId = \count($managedCursors); + + $this->managedCursors[$this->cursorId] = &$this->cursor; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + /** + * Removes the iterator's cursors from the managed cursors of the + * corresponding {@link OrderedHashMap} instance. + */ + public function __destruct() + { + // Use array_splice() instead of unset() to prevent holes in the + // array indices, which would break the initialization of $cursorId + array_splice($this->managedCursors, $this->cursorId, 1); + } + + public function current(): mixed + { + return $this->current; + } + + public function next(): void + { + ++$this->cursor; + + if (isset($this->orderedKeys[$this->cursor])) { + $this->key = $this->orderedKeys[$this->cursor]; + $this->current = $this->elements[$this->key]; + } else { + $this->key = null; + $this->current = null; + } + } + + public function key(): mixed + { + return $this->key; + } + + public function valid(): bool + { + return null !== $this->key; + } + + public function rewind(): void + { + $this->cursor = 0; + + if (isset($this->orderedKeys[0])) { + $this->key = $this->orderedKeys[0]; + $this->current = $this->elements[$this->key]; + } else { + $this->key = null; + $this->current = null; + } + } +} diff --git a/vendor/symfony/form/Util/ServerParams.php b/vendor/symfony/form/Util/ServerParams.php new file mode 100644 index 0000000..2c23efc --- /dev/null +++ b/vendor/symfony/form/Util/ServerParams.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * @author Bernhard Schussek + */ +class ServerParams +{ + public function __construct( + private ?RequestStack $requestStack = null, + ) { + } + + /** + * Returns true if the POST max size has been exceeded in the request. + */ + public function hasPostMaxSizeBeenExceeded(): bool + { + $contentLength = $this->getContentLength(); + $maxContentLength = $this->getPostMaxSize(); + + return $maxContentLength && $contentLength > $maxContentLength; + } + + /** + * Returns maximum post size in bytes. + */ + public function getPostMaxSize(): int|float|null + { + $iniMax = strtolower($this->getNormalizedIniPostMaxSize()); + + if ('' === $iniMax) { + return null; + } + + $max = ltrim($iniMax, '+'); + if (str_starts_with($max, '0x')) { + $max = \intval($max, 16); + } elseif (str_starts_with($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns the normalized "post_max_size" ini setting. + */ + public function getNormalizedIniPostMaxSize(): string + { + return strtoupper(trim(\ini_get('post_max_size'))); + } + + /** + * Returns the content length of the request. + */ + public function getContentLength(): mixed + { + if (null !== $this->requestStack && null !== $request = $this->requestStack->getCurrentRequest()) { + return $request->server->get('CONTENT_LENGTH'); + } + + return isset($_SERVER['CONTENT_LENGTH']) + ? (int) $_SERVER['CONTENT_LENGTH'] + : null; + } +} diff --git a/vendor/symfony/form/Util/StringUtil.php b/vendor/symfony/form/Util/StringUtil.php new file mode 100644 index 0000000..45a50c1 --- /dev/null +++ b/vendor/symfony/form/Util/StringUtil.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +/** + * @author Issei Murasawa + * @author Bernhard Schussek + */ +class StringUtil +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Returns the trimmed data. + */ + public static function trim(string $string): string + { + if (null !== $result = @preg_replace('/^[\pZ\p{Cc}\p{Cf}]+|[\pZ\p{Cc}\p{Cf}]+$/u', '', $string)) { + return $result; + } + + return trim($string); + } + + /** + * Converts a fully-qualified class name to a block prefix. + * + * @param string $fqcn The fully-qualified class name + */ + public static function fqcnToBlockPrefix(string $fqcn): ?string + { + // Non-greedy ("+?") to match "type" suffix, if present + if (preg_match('~([^\\\\]+?)(type)?$~i', $fqcn, $matches)) { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $matches[1])); + } + + return null; + } +} diff --git a/vendor/symfony/form/composer.json b/vendor/symfony/form/composer.json new file mode 100644 index 0000000..40c021d --- /dev/null +++ b/vendor/symfony/form/composer.json @@ -0,0 +1,64 @@ +{ + "name": "symfony/form", + "type": "library", + "description": "Allows to easily create, process and reuse HTML forms", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/options-resolver": "^6.4|^7.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "doctrine/collections": "^1.0|^2.0", + "symfony/validator": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/error-handler": "<6.4", + "symfony/framework-bundle": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Form\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/framework-bundle/CHANGELOG.md b/vendor/symfony/framework-bundle/CHANGELOG.md new file mode 100644 index 0000000..4b04751 --- /dev/null +++ b/vendor/symfony/framework-bundle/CHANGELOG.md @@ -0,0 +1,666 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `CheckAliasValidityPass` to `lint:container` command + * Add `private_ranges` as a shortcut for private IP address ranges to the `trusted_proxies` option + * Mark classes `ConfigBuilderCacheWarmer`, `Router`, `SerializerCacheWarmer`, `TranslationsCacheWarmer`, `Translator` and `ValidatorCacheWarmer` as `final` + * Move the Router `cache_dir` to `kernel.build_dir` + * Deprecate the `router.cache_dir` config option + * Add `rate_limiter` tags to rate limiter services + * Add `secrets:reveal` command + * Add `rate_limiter` option to `http_client.default_options` and `http_client.scoped_clients` + * Attach the workflow's configuration to the `workflow` tag + * Add the `allowed_recipients` option for mailer to allow some users to receive + emails even if `recipients` is defined. + * Reset env vars when resetting the container + +7.0 +--- + + * Remove command `translation:update`, use `translation:extract` instead + * Make the `http_method_override` config option default to `false` + * Remove `AbstractController::renderForm()`, use `render()` instead + * Remove the `Symfony\Component\Serializer\Normalizer\ObjectNormalizer` and + `Symfony\Component\Serializer\Normalizer\PropertyNormalizer` autowiring aliases, type-hint against + `Symfony\Component\Serializer\Normalizer\NormalizerInterface` or implement `NormalizerAwareInterface` instead + * Remove the `Http\Client\HttpClient` service, use `Psr\Http\Client\ClientInterface` instead + * Remove the integration of Doctrine annotations, use native attributes instead + * Remove `EnableLoggerDebugModePass`, use argument `$debug` of HttpKernel's `Logger` instead + * Remove `AddDebugLogProcessorPass::configureLogger()`, use HttpKernel's `DebugLoggerConfigurator` instead + * Make the `framework.handle_all_throwables` config option default to `true` + * Make the `framework.php_errors.log` config option default to `true` + * Make the `framework.session.cookie_secure` config option default to `auto` + * Make the `framework.session.cookie_samesite` config option default to `lax` + * Make the `framework.session.handler_id` default to null if `save_path` is not set and to `session.handler.native_file` otherwise + * Make the `framework.uid.default_uuid_version` config option default to `7` + * Make the `framework.uid.time_based_uuid_version` config option default to `7` + * Make the `framework.validation.email_validation_mode` config option default to `html5` + * Remove the `framework.validation.enable_annotations` config option, use `framework.validation.enable_attributes` instead + * Remove the `framework.serializer.enable_annotations` config option, use `framework.serializer.enable_attributes` instead + * Remove the `routing.loader.annotation` service, use the `routing.loader.attribute` service instead + * Remove the `routing.loader.annotation.directory` service, use the `routing.loader.attribute.directory` service instead + * Remove the `routing.loader.annotation.file` service, use the `routing.loader.attribute.file` service instead + * Remove `AnnotatedRouteControllerLoader`, use `AttributeRouteControllerLoader` instead + * Remove `AddExpressionLanguageProvidersPass`, use `Symfony\Component\Routing\DependencyInjection\AddExpressionLanguageProvidersPass` instead + * Remove `DataCollectorTranslatorPass`, use `Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass` instead + * Remove `LoggingTranslatorPass`, use `Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass` instead + * Remove `WorkflowGuardListenerPass`, use `Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass` instead + +6.4 +--- + + * Add `HttpClientAssertionsTrait` + * Add `AbstractController::renderBlock()` and `renderBlockView()` + * Add native return type to `Translator` and to `Application::reset()` + * Deprecate the integration of Doctrine annotations, either uninstall the `doctrine/annotations` package or disable the integration by setting `framework.annotations` to `false` + * Enable `json_decode_detailed_errors` context for Serializer by default if `kernel.debug` is true and the `seld/jsonlint` package is installed + * Add `DomCrawlerAssertionsTrait::assertAnySelectorTextContains(string $selector, string $text)` + * Add `DomCrawlerAssertionsTrait::assertAnySelectorTextSame(string $selector, string $text)` + * Add `DomCrawlerAssertionsTrait::assertAnySelectorTextNotContains(string $selector, string $text)` + * Deprecate `EnableLoggerDebugModePass`, use argument `$debug` of HttpKernel's `Logger` instead + * Deprecate `AddDebugLogProcessorPass::configureLogger()`, use HttpKernel's `DebugLoggerConfigurator` instead + * Deprecate not setting the `framework.handle_all_throwables` config option; it will default to `true` in 7.0 + * Deprecate not setting the `framework.php_errors.log` config option; it will default to `true` in 7.0 + * Deprecate not setting the `framework.session.cookie_secure` config option; it will default to `auto` in 7.0 + * Deprecate not setting the `framework.session.cookie_samesite` config option; it will default to `lax` in 7.0 + * Deprecate not setting either `framework.session.handler_id` or `save_path` config options; `handler_id` will + default to null in 7.0 if `save_path` is not set and to `session.handler.native_file` otherwise + * Deprecate not setting the `framework.uid.default_uuid_version` config option; it will default to `7` in 7.0 + * Deprecate not setting the `framework.uid.time_based_uuid_version` config option; it will default to `7` in 7.0 + * Deprecate not setting the `framework.validation.email_validation_mode` config option; it will default to `html5` in 7.0 + * Deprecate `framework.validation.enable_annotations`, use `framework.validation.enable_attributes` instead + * Deprecate `framework.serializer.enable_annotations`, use `framework.serializer.enable_attributes` instead + * Add `array $tokenAttributes = []` optional parameter to `KernelBrowser::loginUser()` + * Add support for relative URLs in BrowserKit's redirect assertion + * Change BrowserKitAssertionsTrait::getClient() to be protected + * Deprecate the `framework.asset_mapper.provider` config option + * Add `--exclude` option to the `cache:pool:clear` command + * Add parameters deprecations to the output of `debug:container` command + * Change `framework.asset_mapper.importmap_polyfill` from a URL to the name of an item in the importmap + * Provide `$buildDir` when running `CacheWarmer` to build read-only resources + * Add the global `--profile` option to the console to enable profiling commands + * Deprecate the `routing.loader.annotation` service, use the `routing.loader.attribute` service instead + * Deprecate the `routing.loader.annotation.directory` service, use the `routing.loader.attribute.directory` service instead + * Deprecate the `routing.loader.annotation.file` service, use the `routing.loader.attribute.file` service instead + * Deprecate `AnnotatedRouteControllerLoader`, use `AttributeRouteControllerLoader` instead + * Deprecate `AddExpressionLanguageProvidersPass`, use `Symfony\Component\Routing\DependencyInjection\AddExpressionLanguageProvidersPass` instead + * Deprecate `DataCollectorTranslatorPass`, use `Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass` instead + * Deprecate `LoggingTranslatorPass`, use `Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass` instead + * Deprecate `WorkflowGuardListenerPass`, use `Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass` instead + +6.3 +--- + + * Add `extra` option for `http_client.default_options` and `http_client.scoped_client` + * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` + * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy + * Add `--format` option to the `debug:config` command + * Add support to pass namespace wildcard in `framework.messenger.routing` + * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` + * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead + * Allow setting private services with the test container + * Register alias for argument for workflow services with workflow name only + * Configure the `ErrorHandler` on `FrameworkBundle::boot()` + * Allow setting `debug.container.dump` to `false` to disable dumping the container to XML + * Add `framework.http_cache.skip_response_headers` option + * Display warmers duration on debug verbosity for `cache:clear` command + * Add `AbstractController::sendEarlyHints()` to send HTTP Early Hints + * Add autowiring aliases for `Http\Client\HttpAsyncClient` + * Deprecate the `Http\Client\HttpClient` service, use `Psr\Http\Client\ClientInterface` instead + * Add `stop_worker_on_signals` configuration option to `messenger` to define signals which would stop a worker + * Add support for `--all` option to clear all cache pools with `cache:pool:clear` command + * Add `--show-aliases` option to `debug:router` command + +6.2 +--- + + * Add `resolve-env` option to `debug:config` command to display actual values of environment variables in dumped configuration + * Add `NotificationAssertionsTrait` + * Add option `framework.handle_all_throwables` to allow `Symfony\Component\HttpKernel\HttpKernel` to handle all kinds of `Throwable` + * Make `AbstractController::render()` able to deal with forms and deprecate `renderForm()` + * Deprecate the `Symfony\Component\Serializer\Normalizer\ObjectNormalizer` and + `Symfony\Component\Serializer\Normalizer\PropertyNormalizer` autowiring aliases, type-hint against + `Symfony\Component\Serializer\Normalizer\NormalizerInterface` or implement `NormalizerAwareInterface` instead + * Add service usages list to the `debug:container` command output + * Add service and alias deprecation message to `debug:container []` output + * Tag all workflows services with `workflow`, those with type=workflow are + tagged with `workflow.workflow`, and those with type=state_machine with + `workflow.state_machine` + * Add `rate_limiter` configuration option to `messenger.transport` to allow rate limited transports using the RateLimiter component + * Remove `@internal` tag from secret vaults to allow them to be used directly outside the framework bundle and custom vaults to be added + * Deprecate `framework.form.legacy_error_messages` config node + * Add a `framework.router.cache_dir` configuration option to configure the default `Router` `cache_dir` option + * Add option `framework.messenger.buses.*.default_middleware.allow_no_senders` to enable throwing when a message doesn't have a sender + * Deprecate `AbstractController::renderForm()`, use `render()` instead + * Deprecate `FrameworkExtension::registerRateLimiter()` + +6.1 +--- + + * Add support for configuring semaphores + * Environment variable `SYMFONY_IDE` is read by default when `framework.ide` config is not set + * Load PHP configuration files by default in the `MicroKernelTrait` + * Add `cache:pool:invalidate-tags` command + * Add `xliff` support in addition to `xlf` for `XliffFileDumper` + * Deprecate the `reset_on_message` config option. It can be set to `true` only and does nothing now + * Add `trust_x_sendfile_type_header` option + * Add support for first-class callable route controller in `MicroKernelTrait` + * Add tag `routing.condition_service` to autoconfigure routing condition services + * Automatically register kernel methods marked with the `Symfony\Component\Routing\Annotation\Route` attribute or annotation as controllers in `MicroKernelTrait` + * Deprecate not setting the `http_method_override` config option. The default value will change to `false` in 7.0. + * Add `framework.profiler.collect_serializer_data` config option, set it to `true` to enable the serializer data collector and profiler panel + +6.0 +--- + + * Remove the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead + * Remove `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead + * Remove the `session` service and the `SessionInterface` alias, use the `\Symfony\Component\HttpFoundation\Request::getSession()` or the new `\Symfony\Component\HttpFoundation\RequestStack::getSession()` methods instead + * Remove the `session.attribute_bag` service and `session.flash_bag` service + * Remove the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead + * The `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`, + `cache_clearer`, `filesystem` and `validator` services are now private + * Remove the `output-format` and `xliff-version` options from `TranslationUpdateCommand` + * Remove `has()`, `get()`, `getDoctrine()`n and `dispatchMessage()` from `AbstractController`, use method/constructor injection instead + * Make the "framework.router.utf8" configuration option default to `true` + * Remove the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead + * Make the `profiler` service private + * Remove all other values than "none", "php_array" and "file" for `framework.annotation.cache` + * Register workflow services as private + * Remove support for passing a `RouteCollectionBuilder` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead + * Remove the `cache.adapter.doctrine` service + * Remove the `framework.translator.enabled_locales` config option, use `framework.enabled_locales` instead + * Make the `framework.messenger.reset_on_message` configuration option default to `true` + +5.4 +--- + + * Add `set_locale_from_accept_language` config option to automatically set the request locale based on the `Accept-Language` + HTTP request header and the `framework.enabled_locales` config option + * Add `set_content_language_from_locale` config option to automatically set the `Content-Language` HTTP response header based on the Request locale + * Deprecate the `framework.translator.enabled_locales`, use `framework.enabled_locales` instead + * Add autowiring alias for `HttpCache\StoreInterface` + * Add the ability to enable the profiler using a request query parameter, body parameter or attribute + * Deprecate the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead + * Deprecate the public `profiler` service to private + * Deprecate `get()`, `has()`, `getDoctrine()`, and `dispatchMessage()` in `AbstractController`, use method/constructor injection instead + * Deprecate the `cache.adapter.doctrine` service + * Add support for resetting container services after each messenger message + * Add `configureContainer()`, `configureRoutes()`, `getConfigDir()` and `getBundlesPath()` to `MicroKernelTrait` + * Add support for configuring log level, and status code by exception class + * Bind the `default_context` parameter onto serializer's encoders and normalizers + * Add support for `statusCode` default parameter when loading a template directly from route using the `Symfony\Bundle\FrameworkBundle\Controller\TemplateController` controller + * Deprecate `translation:update` command, use `translation:extract` instead + * Add `PhpStanExtractor` support for the PropertyInfo component + * Add `cache.adapter.doctrine_dbal` service to replace `cache.adapter.pdo` when a Doctrine DBAL connection is used. + +5.3 +--- + + * Deprecate the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead + * Deprecate the `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead + * Deprecate the `session` service and the `SessionInterface` alias, use the `Request::getSession()` or the new `RequestStack::getSession()` methods instead + * Add `AbstractController::renderForm()` to render a form and set the appropriate HTTP status code + * Add support for configuring PHP error level to log levels + * Add the `dispatcher` option to `debug:event-dispatcher` + * Add the `event_dispatcher.dispatcher` tag + * Add `assertResponseFormatSame()` in `BrowserKitAssertionsTrait` + * Add support for configuring UUID factory services + * Add tag `assets.package` to register asset packages + * Add support to use a PSR-6 compatible cache for Doctrine annotations + * Deprecate all other values than "none", "php_array" and "file" for `framework.annotation.cache` + * Add `KernelTestCase::getContainer()` as the best way to get a container in tests + * Rename the container parameter `profiler_listener.only_master_requests` to `profiler_listener.only_main_requests` + * Add service `fragment.uri_generator` to generate the URI of a fragment + * Deprecate registering workflow services as public + * Deprecate option `--xliff-version` of the `translation:update` command, use e.g. `--format=xlf20` instead + * Deprecate option `--output-format` of the `translation:update` command, use e.g. `--format=xlf20` instead + +5.2.0 +----- + + * Added `framework.http_cache` configuration tree + * Added `framework.trusted_proxies` and `framework.trusted_headers` configuration options + * Deprecated the public `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`, + `cache_clearer`, `filesystem` and `validator` services to private. + * Added `TemplateAwareDataCollectorInterface` and `AbstractDataCollector` to simplify custom data collector creation and leverage autoconfiguration + * Add `cache.adapter.redis_tag_aware` tag to use `RedisCacheAwareAdapter` + * added `framework.http_client.retry_failing` configuration tree + * added `assertCheckboxChecked()` and `assertCheckboxNotChecked()` in `WebTestCase` + * added `assertFormValue()` and `assertNoFormValue()` in `WebTestCase` + * Added "--as-tree=3" option to `translation:update` command to dump messages as a tree-like structure. The given value defines the level where to switch to inline YAML + * Deprecated the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead. + +5.1.0 +----- + * Removed `--no-backup` option from `translation:update` command (broken since `5.0.0`) + * Added link to source for controllers registered as named services + * Added link to source on controller on `router:match`/`debug:router` (when `framework.ide` is configured) + * Added the `framework.router.default_uri` configuration option to configure the default `RequestContext` + * Made `MicroKernelTrait::configureContainer()` compatible with `ContainerConfigurator` + * Added a new `mailer.message_bus` option to configure or disable the message bus to use to send mails. + * Added flex-compatible default implementation for `MicroKernelTrait::registerBundles()` + * Deprecated passing a `RouteCollectionBuilder` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead + * The `TemplateController` now accepts context argument + * Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0 + * Added tag `routing.expression_language_function` to define functions available in route conditions + * Added `debug:container --deprecations` option to see compile-time deprecations. + * Made `BrowserKitAssertionsTrait` report the original error message in case of a failure + * Added ability for `config:dump-reference` and `debug:config` to dump and debug kernel container extension configuration. + * Deprecated `session.attribute_bag` service and `session.flash_bag` service. + +5.0.0 +----- + + * Removed support to load translation resources from the legacy directories `src/Resources/translations/` and `src/Resources//translations/` + * Removed `ControllerNameParser`. + * Removed `ResolveControllerNameSubscriber` + * Removed support for `bundle:controller:action` to reference controllers. Use `serviceOrFqcn::method` instead + * Removed support for PHP templating, use Twig instead + * Removed `Controller`, use `AbstractController` instead + * Removed `Client`, use `KernelBrowser` instead + * Removed `ContainerAwareCommand`, use dependency injection instead + * Removed the `validation.strict_email` option, use `validation.email_validation_mode` instead + * Removed the `cache.app.simple` service and its corresponding PSR-16 autowiring alias + * Removed cache-related compiler passes and `RequestDataCollector` + * Removed the `translator.selector` and `session.save_listener` services + * Removed `SecurityUserValueResolver`, use `UserValueResolver` instead + * Removed `routing.loader.service`. + * Service route loaders must be tagged with `routing.route_loader`. + * Added `slugger` service and `SluggerInterface` alias + * Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services. + * Removed the `router.cache_class_prefix` parameter. + +4.4.0 +----- + + * Added `lint:container` command to check that services wiring matches type declarations + * Added `MailerAssertionsTrait` + * Deprecated support for `templating` engine in `TemplateController`, use Twig instead + * Deprecated the `$parser` argument of `ControllerResolver::__construct()` and `DelegatingLoader::__construct()` + * Deprecated the `controller_name_converter` and `resolve_controller_name_subscriber` services + * The `ControllerResolver` and `DelegatingLoader` classes have been marked as `final` + * Added support for configuring chained cache pools + * Deprecated calling `WebTestCase::createClient()` while a kernel has been booted, ensure the kernel is shut down before calling the method + * Deprecated `routing.loader.service`, use `routing.loader.container` instead. + * Not tagging service route loaders with `routing.route_loader` has been deprecated. + * Overriding the methods `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` without the `void` return-type is deprecated. + * Added new `error_controller` configuration to handle system exceptions + * Added sort option for `translation:update` command. + * [BC Break] The `framework.messenger.routing.senders` config key is not deeply merged anymore. + * Added `secrets:*` commands to deal with secrets seamlessly. + * Made `framework.session.handler_id` accept a DSN + * Marked the `RouterDataCollector` class as `@final`. + * [BC Break] The `framework.messenger.buses..middleware` config key is not deeply merged anymore. + * Moved `MailerAssertionsTrait` in `KernelTestCase` + +4.3.0 +----- + + * Deprecated the `framework.templating` option, configure the Twig bundle instead. + * Added `WebTestAssertionsTrait` (included by default in `WebTestCase`) + * Renamed `Client` to `KernelBrowser` + * Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will + be mandatory in 5.0. + * Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead + * Added the ability to specify a custom `serializer` option for each + transport under`framework.messenger.transports`. + * Added the `RegisterLocaleAwareServicesPass` and configured the `LocaleAwareListener` + * [BC Break] When using Messenger, the default transport changed from + using Symfony's serializer service to use `PhpSerializer`, which uses + PHP's native `serialize()` and `unserialize()` functions. To use the + original serialization method, set the `framework.messenger.default_serializer` + config option to `messenger.transport.symfony_serializer`. Or set the + `serializer` option under one specific `transport`. + * [BC Break] The `framework.messenger.serializer` config key changed to + `framework.messenger.default_serializer`, which holds the string service + id and `framework.messenger.symfony_serializer`, which configures the + options if you're using Symfony's serializer. + * [BC Break] Removed the `framework.messenger.routing.send_and_handle` configuration. + Instead of setting it to true, configure a `SyncTransport` and route messages to it. + * Added information about deprecated aliases in `debug:autowiring` + * Added php ini session options `sid_length` and `sid_bits_per_character` + to the `session` section of the configuration + * Added support for Translator paths, Twig paths in translation commands. + * Added support for PHP files with translations in translation commands. + * Added support for boolean container parameters within routes. + * Added the `messenger:setup-transports` command to setup messenger transports + * Added a `InMemoryTransport` to Messenger. Use it with a DSN starting with `in-memory://`. + * Added `framework.property_access.throw_exception_on_invalid_property_path` config option. + * Added `cache:pool:list` command to list all available cache pools. + +4.2.0 +----- + + * Added a `AbstractController::addLink()` method to add Link headers to the current response + * Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id) + * Allowed configuring PDO-based cache pools via a new `cache.adapter.pdo` abstract service + * Deprecated auto-injection of the container in AbstractController instances, register them as service subscribers instead + * Deprecated processing of services tagged `security.expression_language_provider` in favor of a new `AddExpressionLanguageProvidersPass` in SecurityBundle. + * Deprecated the `Symfony\Bundle\FrameworkBundle\Controller\Controller` class in favor of `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. + * Enabled autoconfiguration for `Psr\Log\LoggerAwareInterface` + * Added new "auto" mode for `framework.session.cookie_secure` to turn it on when HTTPS is used + * Removed the `framework.messenger.encoder` and `framework.messenger.decoder` options. Use the `framework.messenger.serializer.id` option to replace the Messenger serializer. + * Deprecated the `ContainerAwareCommand` class in favor of `Symfony\Component\Console\Command\Command` + * Made `debug:container` and `debug:autowiring` ignore backslashes in service ids + * Deprecated the `Templating\Helper\TranslatorHelper::transChoice()` method, use the `trans()` one instead with a `%count%` parameter + * Deprecated `CacheCollectorPass`. Use `Symfony\Component\Cache\DependencyInjection\CacheCollectorPass` instead. + * Deprecated `CachePoolClearerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass` instead. + * Deprecated `CachePoolPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPass` instead. + * Deprecated `CachePoolPrunerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass` instead. + * Deprecated support for legacy translations directories `src/Resources/translations/` and `src/Resources//translations/`, use `translations/` instead. + * Deprecated support for the legacy directory structure in `translation:update` and `debug:translation` commands. + +4.1.0 +----- + + * Allowed to pass an optional `LoggerInterface $logger` instance to the `Router` + * Added a new `parameter_bag` service with related autowiring aliases to access parameters as-a-service + * Allowed the `Router` to work with any PSR-11 container + * Added option in workflow dump command to label graph with a custom label + * Using a `RouterInterface` that does not implement the `WarmableInterface` is deprecated. + * Warming up a router in `RouterCacheWarmer` that does not implement the `WarmableInterface` is deprecated and will not + be supported anymore in 5.0. + * The `RequestDataCollector` class has been deprecated. Use the `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector` class instead. + * The `RedirectController` class allows for 307/308 HTTP status codes + * Deprecated `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead where `serviceOrFqcn` + is either the service ID or the FQCN of the controller. + * Deprecated `Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser` + * The `container.service_locator` tag of `ServiceLocator`s is now autoconfigured. + * Add the ability to search a route in `debug:router`. + * Add the ability to use SameSite cookies for sessions. + +4.0.0 +----- + + * The default `type` option of the `framework.workflows.*` configuration entries is `state_machine` + * removed `AddConsoleCommandPass`, `AddConstraintValidatorsPass`, + `AddValidatorInitializersPass`, `CompilerDebugDumpPass`, `ConfigCachePass`, + `ControllerArgumentValueResolverPass`, `FormPass`, `PropertyInfoPass`, + `RoutingResolverPass`, `SerializerPass`, `ValidateWorkflowsPass` + * made `Translator::__construct()` `$defaultLocale` argument required + * removed `SessionListener`, `TestSessionListener` + * Removed `cache:clear` warmup part along with the `--no-optional-warmers` option + * Removed core form types services registration when unnecessary + * Removed `framework.serializer.cache` option and `serializer.mapping.cache.apc`, `serializer.mapping.cache.doctrine.apc` services + * Removed `ConstraintValidatorFactory` + * Removed class parameters related to routing + * Removed absolute template paths support in the template name parser + * Removed support of the `KERNEL_DIR` environment variable with `KernelTestCase::getKernelClass()`. + * Removed the `KernelTestCase::getPhpUnitXmlDir()` and `KernelTestCase::getPhpUnitCliConfigArgument()` methods. + * Removed the "framework.validation.cache" configuration option. Configure the "cache.validator" service under "framework.cache.pools" instead. + * Removed `PhpStringTokenParser`, use `Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead. + * Removed `PhpExtractor`, use `Symfony\Component\Translation\Extractor\PhpExtractor` instead. + * Removed the `use_strict_mode` session option, it's is now enabled by default + +3.4.0 +----- + + * Added `translator.default_path` option and parameter + * Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated + * Made the `cache:clear` command to *not* clear "app" PSR-6 cache pools anymore, + but to still clear "system" ones; use the `cache:pool:clear` command to clear "app" pools instead + * Always register a minimalist logger that writes in `stderr` + * Deprecated `profiler.matcher` option + * Added support for `EventSubscriberInterface` on `MicroKernelTrait` + * Removed `doctrine/cache` from the list of required dependencies in `composer.json` + * Deprecated `validator.mapping.cache.doctrine.apc` service + * The `symfony/stopwatch` dependency has been removed, require it via `composer + require symfony/stopwatch` in your `dev` environment. + * Deprecated using the `KERNEL_DIR` environment variable with `KernelTestCase::getKernelClass()`. + * Deprecated the `KernelTestCase::getPhpUnitXmlDir()` and `KernelTestCase::getPhpUnitCliConfigArgument()` methods. + * Deprecated `AddCacheClearerPass`, use tagged iterator arguments instead. + * Deprecated `AddCacheWarmerPass`, use tagged iterator arguments instead. + * Deprecated `TranslationDumperPass`, use + `Symfony\Component\Translation\DependencyInjection\TranslationDumperPass` instead + * Deprecated `TranslationExtractorPass`, use + `Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass` instead + * Deprecated `TranslatorPass`, use + `Symfony\Component\Translation\DependencyInjection\TranslatorPass` instead + * Added `command` attribute to the `console.command` tag which takes the command + name as value, using it makes the command lazy + * Added `cache:pool:prune` command to allow manual stale cache item pruning of supported PSR-6 and PSR-16 cache pool + implementations + * Deprecated `Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader`, use + `Symfony\Component\Translation\Reader\TranslationReader` instead + * Deprecated `translation.loader` service, use `translation.reader` instead + * `AssetsInstallCommand::__construct()` now takes an instance of + `Symfony\Component\Filesystem\Filesystem` as first argument + * `CacheClearCommand::__construct()` now takes an instance of + `Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface` as + first argument + * `CachePoolClearCommand::__construct()` now takes an instance of + `Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer` as + first argument + * `EventDispatcherDebugCommand::__construct()` now takes an instance of + `Symfony\Component\EventDispatcher\EventDispatcherInterface` as + first argument + * `RouterDebugCommand::__construct()` now takes an instance of + `Symfony\Component\Routing\RouterInterface` as + first argument + * `RouterMatchCommand::__construct()` now takes an instance of + `Symfony\Component\Routing\RouterInterface` as + first argument + * `TranslationDebugCommand::__construct()` now takes an instance of + `Symfony\Component\Translation\TranslatorInterface` as + first argument + * `TranslationUpdateCommand::__construct()` now takes an instance of + `Symfony\Component\Translation\TranslatorInterface` as + first argument + * `AssetsInstallCommand`, `CacheClearCommand`, `CachePoolClearCommand`, + `EventDispatcherDebugCommand`, `RouterDebugCommand`, `RouterMatchCommand`, + `TranslationDebugCommand`, `TranslationUpdateCommand`, `XliffLintCommand` + and `YamlLintCommand` classes have been marked as final + * Added `asset.request_context.base_path` and `asset.request_context.secure` parameters + to provide a default request context in case the stack is empty (similar to `router.request_context.*` parameters) + * Display environment variables managed by `Dotenv` in `AboutCommand` + +3.3.0 +----- + + * Not defining the `type` option of the `framework.workflows.*` configuration entries is deprecated. + The default value will be `state_machine` in Symfony 4.0. + * Deprecated the `CompilerDebugDumpPass` class + * Deprecated the "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter + * Added a new version strategy option called "json_manifest_path" + that allows you to use the `JsonManifestVersionStrategy`. + * Added `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. It provides + the same helpers as the `Controller` class, but does not allow accessing the dependency + injection container, in order to encourage explicit dependency declarations. + * Added support for the `controller.service_arguments` tag, for injecting services into controllers' actions + * Changed default configuration for + assets/forms/validation/translation/serialization/csrf from `canBeEnabled()` to + `canBeDisabled()` when Flex is used + * The server:* commands and their associated router files were moved to WebServerBundle + * Translation related services are not loaded anymore when the `framework.translator` option + is disabled. + * Added `GlobalVariables::getToken()` + * Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass`. Use `Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass` instead. + * Added configurable paths for validation files + * Deprecated `SerializerPass`, use `Symfony\Component\Serializer\DependencyInjection\SerializerPass` instead + * Deprecated `FormPass`, use `Symfony\Component\Form\DependencyInjection\FormPass` instead + * Deprecated `SessionListener` + * Deprecated `TestSessionListener` + * Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass`. + Use tagged iterator arguments instead. + * Deprecated `PropertyInfoPass`, use `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass` instead + * Deprecated `ControllerArgumentValueResolverPass`. Use + `Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass` instead + * Deprecated `RoutingResolverPass`, use `Symfony\Component\Routing\DependencyInjection\RoutingResolverPass` instead + * [BC BREAK] The `server:run`, `server:start`, `server:stop` and + `server:status` console commands have been moved to a dedicated bundle. + Require `symfony/web-server-bundle` in your composer.json and register + `Symfony\Bundle\WebServerBundle\WebServerBundle` in your AppKernel to use them. + * Added `$defaultLocale` as 3rd argument of `Translator::__construct()` + making `Translator` works with any PSR-11 container + * Added `framework.serializer.mapping` config option allowing to define custom + serialization mapping files and directories + * Deprecated `AddValidatorInitializersPass`, use + `Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass` instead + * Deprecated `AddConstraintValidatorsPass`, use + `Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass` instead + * Deprecated `ValidateWorkflowsPass`, use + `Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass` instead + * Deprecated `ConstraintValidatorFactory`, use + `Symfony\Component\Validator\ContainerConstraintValidatorFactory` instead. + * Deprecated `PhpStringTokenParser`, use + `Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead. + * Deprecated `PhpExtractor`, use + `Symfony\Component\Translation\Extractor\PhpExtractor` instead. + +3.2.0 +----- + + * Removed `doctrine/annotations` from the list of required dependencies in `composer.json` + * Removed `symfony/security-core` and `symfony/security-csrf` from the list of required dependencies in `composer.json` + * Removed `symfony/templating` from the list of required dependencies in `composer.json` + * Removed `symfony/translation` from the list of required dependencies in `composer.json` + * Removed `symfony/asset` from the list of required dependencies in `composer.json` + * The `Resources/public/images/*` files have been removed. + * The `Resources/public/css/*.css` files have been removed (they are now inlined in TwigBundle). + * Added possibility to prioritize form type extensions with `'priority'` attribute on tags `form.type_extension` + +3.1.0 +----- + + * Added `Controller::json` to simplify creating JSON responses when using the Serializer component + * Deprecated absolute template paths support in the template name parser + * Deprecated using core form types without dependencies as services + * Added `Symfony\Component\HttpHernel\DataCollector\RequestDataCollector::onKernelResponse()` + * Added `Symfony\Bundle\FrameworkBundle\DataCollector\RequestDataCollector` + * The `framework.serializer.cache` option and the service `serializer.mapping.cache.apc` have been + deprecated. APCu should now be automatically used when available. + +3.0.0 +----- + + * removed `validator.api` parameter + * removed `alias` option of the `form.type` tag + +2.8.0 +----- + + * Deprecated the `alias` option of the `form.type_extension` tag in favor of the + `extended_type`/`extended-type` option + * Deprecated the `alias` option of the `form.type` tag + * Deprecated the Shell + +2.7.0 +----- + + * Added possibility to extract translation messages from a file or files besides extracting from a directory + * Added `TranslationsCacheWarmer` to create catalogues at warmup + +2.6.0 +----- + + * Added helper commands (`server:start`, `server:stop` and `server:status`) to control the built-in web + server in the background + * Added `Controller::isCsrfTokenValid` helper + * Added configuration for the PropertyAccess component + * Added `Controller::redirectToRoute` helper + * Added `Controller::addFlash` helper + * Added `Controller::isGranted` helper + * Added `Controller::denyAccessUnlessGranted` helper + * Deprecated `app.security` in twig as `app.user` and `is_granted()` are already available + +2.5.0 +----- + + * Added `translation:debug` command + * Added `--no-backup` option to `translation:update` command + * Added `config:debug` command + * Added `yaml:lint` command + * Deprecated the `RouterApacheDumperCommand` which will be removed in Symfony 3.0. + +2.4.0 +----- + + * allowed multiple IP addresses in profiler matcher settings + * added stopwatch helper to time templates with the WebProfilerBundle + * added service definition for "security.secure_random" service + * added service definitions for the new Security CSRF sub-component + +2.3.0 +----- + + * [BC BREAK] added a way to disable the profiler (when disabling the profiler, it is now completely removed) + To get the same "disabled" behavior as before, set `enabled` to `true` and `collect` to `false` + * [BC BREAK] the `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass` was moved + to `Component\HttpKernel\DependencyInjection\RegisterListenersPass` + * added ControllerNameParser::build() which converts a controller short notation (a:b:c) to a class::method notation + * added possibility to run PHP built-in server in production environment + * added possibility to load the serializer component in the service container + * added route debug information when using the `router:match` command + * added `TimedPhpEngine` + * added `--clean` option to the `translation:update` command + * added `http_method_override` option + * added support for default templates per render tag + * added FormHelper::form(), FormHelper::start() and FormHelper::end() + * deprecated FormHelper::enctype() in favor of FormHelper::start() + * RedirectController actions now receive the Request instance via the method signature. + +2.2.0 +----- + + * added a new `uri_signer` service to help sign URIs + * deprecated `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` and `Symfony\Bundle\FrameworkBundle\HttpKernel::forward()` + * deprecated the `Symfony\Bundle\FrameworkBundle\HttpKernel` class in favor of `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` + * added support for adding new HTTP content rendering strategies (like ESI and Hinclude) + in the DIC via the `kernel.fragment_renderer` tag + * [BC BREAK] restricted the `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method to only accept URIs or ControllerReference instances + * `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method signature changed and the first argument + must now be a URI or a ControllerReference instance (the `generateInternalUri()` method was removed) + * The internal routes (`Resources/config/routing/internal.xml`) have been removed and replaced with a listener (`Symfony\Component\HttpKernel\EventListener\FragmentListener`) + * The `render` method of the `actions` templating helper signature and arguments changed + * replaced Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver by Symfony\Component\HttpKernel\Controller\TraceableControllerResolver + * replaced Symfony\Component\HttpKernel\Debug\ContainerAwareTraceableEventDispatcher by Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher + * added Client::enableProfiler() + * a new parameter has been added to the DIC: `router.request_context.base_url` + You can customize it for your functional tests or for generating URLs with + the right base URL when your are in the CLI context. + * added support for default templates per render tag + +2.1.0 +----- + + * moved the translation files to the Form and Validator components + * changed the default extension for XLIFF files from .xliff to .xlf + * moved Symfony\Bundle\FrameworkBundle\ContainerAwareEventDispatcher to Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher + * moved Symfony\Bundle\FrameworkBundle\Debug\TraceableEventDispatcher to Symfony\Component\EventDispatcher\ContainerAwareTraceableEventDispatcher + * added a router:match command + * added a config:dump-reference command + * added a server:run command + * added kernel.event_subscriber tag + * added a way to create relative symlinks when running assets:install command (--relative option) + * added Controller::getUser() + * [BC BREAK] assets_base_urls and base_urls merging strategy has changed + * changed the default profiler storage to use the filesystem instead of SQLite + * added support for placeholders in route defaults and requirements (replaced + by the value set in the service container) + * added Filesystem component as a dependency + * added support for hinclude (use ``standalone: 'js'`` in render tag) + * session options: lifetime, path, domain, secure, httponly were deprecated. + Prefixed versions should now be used instead: cookie_lifetime, cookie_path, + cookie_domain, cookie_secure, cookie_httponly + * [BC BREAK] following session options: 'lifetime', 'path', 'domain', 'secure', + 'httponly' are now prefixed with cookie_ when dumped to the container + * Added `handler_id` configuration under `session` key to represent `session.handler` + service, defaults to `session.handler.native_file`. + * Added `gc_maxlifetime`, `gc_probability`, and `gc_divisor` to session + configuration. This means session garbage collection has a + `gc_probability`/`gc_divisor` chance of being run. The `gc_maxlifetime` defines + how long a session can idle for. It is different from cookie lifetime which + declares how long a cookie can be stored on the remote client. + * Removed 'auto_start' configuration parameter from session config. The session will + start on demand. + * [BC BREAK] TemplateNameParser::parseFromFilename() has been moved to a dedicated + parser: TemplateFilenameParser::parse(). + * [BC BREAK] Kernel parameters are replaced by their value wherever they appear + in Route patterns, requirements and defaults. Use '%%' as the escaped value for '%'. + * [BC BREAK] Switched behavior of flash messages to expire flash messages on retrieval + using Symfony\Component\HttpFoundation\Session\Flash\FlashBag as opposed to on + next pageload regardless of whether they are displayed or not. diff --git a/vendor/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/vendor/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php new file mode 100644 index 0000000..d809888 --- /dev/null +++ b/vendor/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; + +abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface +{ + /** + * @param string $phpArrayFile The PHP file where metadata are cached + */ + public function __construct( + private string $phpArrayFile, + ) { + } + + public function isOptional(): bool + { + return true; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + $arrayAdapter = new ArrayAdapter(); + + spl_autoload_register([ClassExistenceResource::class, 'throwOnRequiredClass']); + try { + if (!$this->doWarmUp($cacheDir, $arrayAdapter, $buildDir)) { + return []; + } + } finally { + spl_autoload_unregister([ClassExistenceResource::class, 'throwOnRequiredClass']); + } + + // the ArrayAdapter stores the values serialized + // to avoid mutation of the data after it was written to the cache + // so here we un-serialize the values first + $values = array_map(fn ($val) => null !== $val ? unserialize($val) : null, $arrayAdapter->getValues()); + + return $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()), $values); + } + + /** + * @return string[] A list of classes to preload on PHP 7.4+ + */ + protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array + { + return (array) $phpArrayAdapter->warmUp($values); + } + + /** + * @internal + */ + final protected function ignoreAutoloadException(string $class, \Exception $exception): void + { + try { + ClassExistenceResource::throwOnRequiredClass($class, $exception); + } catch (\ReflectionException) { + } + } + + /** + * @return bool false if there is nothing to warm-up + */ + abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool; +} diff --git a/vendor/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/vendor/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php new file mode 100644 index 0000000..ae27502 --- /dev/null +++ b/vendor/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; + +/** + * Clears the cache pools when warming up the cache. + * + * Do not use in production! + * + * @author Teoh Han Hui + * + * @internal + */ +final class CachePoolClearerCacheWarmer implements CacheWarmerInterface +{ + private Psr6CacheClearer $poolClearer; + private array $pools; + + /** + * @param string[] $pools + */ + public function __construct(Psr6CacheClearer $poolClearer, array $pools = []) + { + $this->poolClearer = $poolClearer; + $this->pools = $pools; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + foreach ($this->pools as $pool) { + if ($this->poolClearer->hasPool($pool)) { + $this->poolClearer->clearPool($pool); + } + } + + return []; + } + + public function isOptional(): bool + { + // optional cache warmers are not run when handling the request + return false; + } +} diff --git a/vendor/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php b/vendor/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php new file mode 100644 index 0000000..8b692c9 --- /dev/null +++ b/vendor/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; +use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * Generate all config builders. + * + * @author Tobias Nyholm + * + * @final since Symfony 7.1 + */ +class ConfigBuilderCacheWarmer implements CacheWarmerInterface +{ + private KernelInterface $kernel; + private ?LoggerInterface $logger; + + public function __construct(KernelInterface $kernel, ?LoggerInterface $logger = null) + { + $this->kernel = $kernel; + $this->logger = $logger; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + if (!$buildDir) { + return []; + } + + $generator = new ConfigBuilderGenerator($buildDir); + + if ($this->kernel instanceof Kernel) { + /** @var ContainerBuilder $container */ + $container = \Closure::bind(function (Kernel $kernel) { + $containerBuilder = $kernel->getContainerBuilder(); + $kernel->prepareContainer($containerBuilder); + + return $containerBuilder; + }, null, $this->kernel)($this->kernel); + + $extensions = $container->getExtensions(); + } else { + $extensions = []; + foreach ($this->kernel->getBundles() as $bundle) { + $extension = $bundle->getContainerExtension(); + if (null !== $extension) { + $extensions[] = $extension; + } + } + } + + foreach ($extensions as $extension) { + try { + $this->dumpExtension($extension, $generator); + } catch (\Exception $e) { + $this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}: '.$e->getMessage(), ['exception' => $e, 'extensionClass' => $extension::class]); + } + } + + // No need to preload anything + return []; + } + + private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGeneratorInterface $generator): void + { + $configuration = null; + if ($extension instanceof ConfigurationInterface) { + $configuration = $extension; + } elseif ($extension instanceof ConfigurationExtensionInterface) { + $container = $this->kernel->getContainer(); + $configuration = $extension->getConfiguration([], new ContainerBuilder($container instanceof Container ? new ContainerBag($container) : new ParameterBag())); + } + + if (!$configuration) { + return; + } + + $generator->build($configuration); + } + + public function isOptional(): bool + { + return false; + } +} diff --git a/vendor/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php b/vendor/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php new file mode 100644 index 0000000..eed5480 --- /dev/null +++ b/vendor/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * Generates the router matcher and generator classes. + * + * @author Fabien Potencier + * + * @final + */ +class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface +{ + private ContainerInterface $container; + + public function __construct(ContainerInterface $container) + { + // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. + $this->container = $container; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + if (!$buildDir) { + return []; + } + + $router = $this->container->get('router'); + + if ($router instanceof WarmableInterface) { + return (array) $router->warmUp($cacheDir, $buildDir); + } + + throw new \LogicException(sprintf('The router "%s" cannot be warmed up because it does not implement "%s".', get_debug_type($router), WarmableInterface::class)); + } + + public function isOptional(): bool + { + return true; + } + + public static function getSubscribedServices(): array + { + return [ + 'router' => RouterInterface::class, + ]; + } +} diff --git a/vendor/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php b/vendor/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php new file mode 100644 index 0000000..46da4da --- /dev/null +++ b/vendor/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\LoaderChain; +use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; +use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; +use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; + +/** + * Warms up XML and YAML serializer metadata. + * + * @author Titouan Galopin + * + * @final since Symfony 7.1 + */ +class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer +{ + /** + * @param LoaderInterface[] $loaders The serializer metadata loaders + * @param string $phpArrayFile The PHP file where metadata are cached + */ + public function __construct( + private array $loaders, + string $phpArrayFile, + ) { + parent::__construct($phpArrayFile); + } + + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool + { + if (!$this->loaders) { + return true; + } + + $metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayAdapter); + + foreach ($this->extractSupportedLoaders($this->loaders) as $loader) { + foreach ($loader->getMappedClasses() as $mappedClass) { + try { + $metadataFactory->getMetadataFor($mappedClass); + } catch (\Exception $e) { + $this->ignoreAutoloadException($mappedClass, $e); + } + } + } + + return true; + } + + /** + * @param LoaderInterface[] $loaders + * + * @return XmlFileLoader[]|YamlFileLoader[] + */ + private function extractSupportedLoaders(array $loaders): array + { + $supportedLoaders = []; + + foreach ($loaders as $loader) { + if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) { + $supportedLoaders[] = $loader; + } elseif ($loader instanceof LoaderChain) { + $supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders())); + } + } + + return $supportedLoaders; + } +} diff --git a/vendor/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php b/vendor/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php new file mode 100644 index 0000000..19b2725 --- /dev/null +++ b/vendor/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Generates the catalogues for translations. + * + * @author Xavier Leune + * + * @final since Symfony 7.1 + */ +class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface +{ + private ContainerInterface $container; + private TranslatorInterface $translator; + + public function __construct(ContainerInterface $container) + { + // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. + $this->container = $container; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + $this->translator ??= $this->container->get('translator'); + + if ($this->translator instanceof WarmableInterface) { + return (array) $this->translator->warmUp($cacheDir, $buildDir); + } + + return []; + } + + public function isOptional(): bool + { + return true; + } + + public static function getSubscribedServices(): array + { + return [ + 'translator' => TranslatorInterface::class, + ]; + } +} diff --git a/vendor/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php b/vendor/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php new file mode 100644 index 0000000..6ecaa4b --- /dev/null +++ b/vendor/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; +use Symfony\Component\Validator\Mapping\Loader\LoaderChain; +use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; +use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; +use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; +use Symfony\Component\Validator\ValidatorBuilder; + +/** + * Warms up XML and YAML validator metadata. + * + * @author Titouan Galopin + * + * @final since Symfony 7.1 + */ +class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer +{ + /** + * @param string $phpArrayFile The PHP file where metadata are cached + */ + public function __construct( + private ValidatorBuilder $validatorBuilder, + string $phpArrayFile, + ) { + parent::__construct($phpArrayFile); + } + + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool + { + $loaders = $this->validatorBuilder->getLoaders(); + $metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), $arrayAdapter); + + foreach ($this->extractSupportedLoaders($loaders) as $loader) { + foreach ($loader->getMappedClasses() as $mappedClass) { + try { + if ($metadataFactory->hasMetadataFor($mappedClass)) { + $metadataFactory->getMetadataFor($mappedClass); + } + } catch (\Exception $e) { + $this->ignoreAutoloadException($mappedClass, $e); + } + } + } + + return true; + } + + /** + * @return string[] A list of classes to preload on PHP 7.4+ + */ + protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array + { + // make sure we don't cache null values + $values = array_filter($values, fn ($val) => null !== $val); + + return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); + } + + /** + * @param LoaderInterface[] $loaders + * + * @return XmlFileLoader[]|YamlFileLoader[] + */ + private function extractSupportedLoaders(array $loaders): array + { + $supportedLoaders = []; + + foreach ($loaders as $loader) { + if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) { + $supportedLoaders[] = $loader; + } elseif ($loader instanceof LoaderChain) { + $supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders())); + } + } + + return $supportedLoaders; + } +} diff --git a/vendor/symfony/framework-bundle/Command/AboutCommand.php b/vendor/symfony/framework-bundle/Command/AboutCommand.php new file mode 100644 index 0000000..2c6cb44 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/AboutCommand.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * A console command to display information about the current installation. + * + * @author Roland Franssen + * + * @final + */ +#[AsCommand(name: 'about', description: 'Display information about the current project')] +class AboutCommand extends Command +{ + protected function configure(): void + { + $this + ->setHelp(<<<'EOT' +The %command.name% command displays information about the current Symfony project. + +The PHP section displays important configuration that could affect your application. The values might +be different between web and CLI. +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + + if (method_exists($kernel, 'getBuildDir')) { + $buildDir = $kernel->getBuildDir(); + } else { + $buildDir = $kernel->getCacheDir(); + } + + $rows = [ + ['Symfony'], + new TableSeparator(), + ['Version', Kernel::VERSION], + ['Long-Term Support', 4 === Kernel::MINOR_VERSION ? 'Yes' : 'No'], + ['End of maintenance', Kernel::END_OF_MAINTENANCE.(self::isExpired(Kernel::END_OF_MAINTENANCE) ? ' Expired' : ' ('.self::daysBeforeExpiration(Kernel::END_OF_MAINTENANCE).')')], + ['End of life', Kernel::END_OF_LIFE.(self::isExpired(Kernel::END_OF_LIFE) ? ' Expired' : ' ('.self::daysBeforeExpiration(Kernel::END_OF_LIFE).')')], + new TableSeparator(), + ['Kernel'], + new TableSeparator(), + ['Type', $kernel::class], + ['Environment', $kernel->getEnvironment()], + ['Debug', $kernel->isDebug() ? 'true' : 'false'], + ['Charset', $kernel->getCharset()], + ['Cache directory', self::formatPath($kernel->getCacheDir(), $kernel->getProjectDir()).' ('.self::formatFileSize($kernel->getCacheDir()).')'], + ['Build directory', self::formatPath($buildDir, $kernel->getProjectDir()).' ('.self::formatFileSize($buildDir).')'], + ['Log directory', self::formatPath($kernel->getLogDir(), $kernel->getProjectDir()).' ('.self::formatFileSize($kernel->getLogDir()).')'], + new TableSeparator(), + ['PHP'], + new TableSeparator(), + ['Version', \PHP_VERSION], + ['Architecture', (\PHP_INT_SIZE * 8).' bits'], + ['Intl locale', class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'], + ['Timezone', date_default_timezone_get().' ('.(new \DateTimeImmutable())->format(\DateTimeInterface::W3C).')'], + ['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'], + ['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'], + ['Xdebug', \extension_loaded('xdebug') ? 'true' : 'false'], + ]; + + $io->table([], $rows); + + return 0; + } + + private static function formatPath(string $path, string $baseDir): string + { + return preg_replace('~^'.preg_quote($baseDir, '~').'~', '.', $path); + } + + private static function formatFileSize(string $path): string + { + if (is_file($path)) { + $size = filesize($path) ?: 0; + } else { + if (!is_dir($path)) { + return 'n/a'; + } + + $size = 0; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)) as $file) { + if ($file->isReadable()) { + $size += $file->getSize(); + } + } + } + + return Helper::formatMemory($size); + } + + private static function isExpired(string $date): bool + { + $date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date); + + return false !== $date && new \DateTimeImmutable() > $date->modify('last day of this month 23:59:59'); + } + + private static function daysBeforeExpiration(string $date): string + { + $date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date); + + return (new \DateTimeImmutable())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days'); + } +} diff --git a/vendor/symfony/framework-bundle/Command/AbstractConfigCommand.php b/vendor/symfony/framework-bundle/Command/AbstractConfigCommand.php new file mode 100644 index 0000000..479bbfe --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/AbstractConfigCommand.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\StyleInterface; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * A console command for dumping available configuration reference. + * + * @author Kevin Bond + * @author Wouter J + * @author Grégoire Pineau + */ +abstract class AbstractConfigCommand extends ContainerDebugCommand +{ + protected function listBundles(OutputInterface|StyleInterface $output): void + { + $title = 'Available registered bundles with their extension alias if available'; + $headers = ['Bundle name', 'Extension alias']; + $rows = []; + + $bundles = $this->getApplication()->getKernel()->getBundles(); + usort($bundles, fn ($bundleA, $bundleB) => strcmp($bundleA->getName(), $bundleB->getName())); + + foreach ($bundles as $bundle) { + $extension = $bundle->getContainerExtension(); + $rows[] = [$bundle->getName(), $extension ? $extension->getAlias() : '']; + } + + if ($output instanceof StyleInterface) { + $output->title($title); + $output->table($headers, $rows); + } else { + $output->writeln($title); + $table = new Table($output); + $table->setHeaders($headers)->setRows($rows)->render(); + } + } + + protected function listNonBundleExtensions(OutputInterface|StyleInterface $output): void + { + $title = 'Available registered non-bundle extension aliases'; + $headers = ['Extension alias']; + $rows = []; + + $kernel = $this->getApplication()->getKernel(); + + $bundleExtensions = []; + foreach ($kernel->getBundles() as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $bundleExtensions[$extension::class] = true; + } + } + + $extensions = $this->getContainerBuilder($kernel)->getExtensions(); + + foreach ($extensions as $alias => $extension) { + if (isset($bundleExtensions[$extension::class])) { + continue; + } + $rows[] = [$alias]; + } + + if (!$rows) { + return; + } + + if ($output instanceof StyleInterface) { + $output->title($title); + $output->table($headers, $rows); + } else { + $output->writeln($title); + $table = new Table($output); + $table->setHeaders($headers)->setRows($rows)->render(); + } + } + + protected function findExtension(string $name): ExtensionInterface + { + $bundles = $this->initializeBundles(); + $minScore = \INF; + + $kernel = $this->getApplication()->getKernel(); + if ($kernel instanceof ExtensionInterface && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)) { + if ($name === $kernel->getAlias()) { + return $kernel; + } + + if ($kernel->getAlias()) { + $distance = levenshtein($name, $kernel->getAlias()); + + if ($distance < $minScore) { + $guess = $kernel->getAlias(); + $minScore = $distance; + } + } + } + + foreach ($bundles as $bundle) { + if ($name === $bundle->getName()) { + if (!$bundle->getContainerExtension()) { + throw new \LogicException(sprintf('Bundle "%s" does not have a container extension.', $name)); + } + + return $bundle->getContainerExtension(); + } + + $distance = levenshtein($name, $bundle->getName()); + + if ($distance < $minScore) { + $guess = $bundle->getName(); + $minScore = $distance; + } + } + + $container = $this->getContainerBuilder($kernel); + + if ($container->hasExtension($name)) { + return $container->getExtension($name); + } + + foreach ($container->getExtensions() as $extension) { + $distance = levenshtein($name, $extension->getAlias()); + + if ($distance < $minScore) { + $guess = $extension->getAlias(); + $minScore = $distance; + } + } + + if (!str_ends_with($name, 'Bundle')) { + $message = sprintf('No extensions with configuration available for "%s".', $name); + } else { + $message = sprintf('No extension with alias "%s" is enabled.', $name); + } + + if (isset($guess) && $minScore < 3) { + $message .= sprintf("\n\nDid you mean \"%s\"?", $guess); + } + + throw new LogicException($message); + } + + public function validateConfiguration(ExtensionInterface $extension, mixed $configuration): void + { + if (!$configuration) { + throw new \LogicException(sprintf('The extension with alias "%s" does not have its getConfiguration() method setup.', $extension->getAlias())); + } + + if (!$configuration instanceof ConfigurationInterface) { + throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', get_debug_type($configuration))); + } + } + + private function initializeBundles(): array + { + // Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method + // as this method is not called when the container is loaded from the cache. + $kernel = $this->getApplication()->getKernel(); + $container = $this->getContainerBuilder($kernel); + $bundles = $kernel->getBundles(); + foreach ($bundles as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $container->registerExtension($extension); + } + } + + foreach ($bundles as $bundle) { + $bundle->build($container); + } + + return $bundles; + } +} diff --git a/vendor/symfony/framework-bundle/Command/AssetsInstallCommand.php b/vendor/symfony/framework-bundle/Command/AssetsInstallCommand.php new file mode 100644 index 0000000..32b38de --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/AssetsInstallCommand.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * Command that places bundle web assets into a given directory. + * + * @author Fabien Potencier + * @author Gábor Egyed + * + * @final + */ +#[AsCommand(name: 'assets:install', description: 'Install bundle\'s web assets under a public directory')] +class AssetsInstallCommand extends Command +{ + public const METHOD_COPY = 'copy'; + public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; + public const METHOD_RELATIVE_SYMLINK = 'relative symlink'; + + public function __construct( + private Filesystem $filesystem, + private string $projectDir, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', null), + ]) + ->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them') + ->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks') + ->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist') + ->setHelp(<<<'EOT' +The %command.name% command installs bundle assets into a given +directory (e.g. the public directory). + + php %command.full_name% public + +A "bundles" directory will be created inside the target directory and the +"Resources/public" directory of each bundle will be copied into it. + +To create a symlink to each bundle instead of copying its assets, use the +--symlink option (will fall back to hard copies when symbolic links aren't possible: + + php %command.full_name% public --symlink + +To make symlink relative, add the --relative option: + + php %command.full_name% public --symlink --relative + +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + $targetArg = rtrim($input->getArgument('target') ?? '', '/'); + if (!$targetArg) { + $targetArg = $this->getPublicDirectory($kernel->getContainer()); + } + + if (!is_dir($targetArg)) { + $targetArg = $kernel->getProjectDir().'/'.$targetArg; + + if (!is_dir($targetArg)) { + throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $targetArg)); + } + } + + $bundlesDir = $targetArg.'/bundles/'; + + $io = new SymfonyStyle($input, $output); + $io->newLine(); + + if ($input->getOption('relative')) { + $expectedMethod = self::METHOD_RELATIVE_SYMLINK; + $io->text('Trying to install assets as relative symbolic links.'); + } elseif ($input->getOption('symlink')) { + $expectedMethod = self::METHOD_ABSOLUTE_SYMLINK; + $io->text('Trying to install assets as absolute symbolic links.'); + } else { + $expectedMethod = self::METHOD_COPY; + $io->text('Installing assets as hard copies.'); + } + + $io->newLine(); + + $rows = []; + $copyUsed = false; + $exitCode = 0; + $validAssetDirs = []; + /** @var BundleInterface $bundle */ + foreach ($kernel->getBundles() as $bundle) { + if (!is_dir($originDir = $bundle->getPath().'/Resources/public') && !is_dir($originDir = $bundle->getPath().'/public')) { + continue; + } + + $assetDir = preg_replace('/bundle$/', '', strtolower($bundle->getName())); + $targetDir = $bundlesDir.$assetDir; + $validAssetDirs[] = $assetDir; + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $message = sprintf("%s\n-> %s", $bundle->getName(), $targetDir); + } else { + $message = $bundle->getName(); + } + + try { + $this->filesystem->remove($targetDir); + + if (self::METHOD_RELATIVE_SYMLINK === $expectedMethod) { + $method = $this->relativeSymlinkWithFallback($originDir, $targetDir); + } elseif (self::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); + } else { + $method = $this->hardCopy($originDir, $targetDir); + } + + if (self::METHOD_COPY === $method) { + $copyUsed = true; + } + + if ($method === $expectedMethod) { + $rows[] = [sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */), $message, $method]; + } else { + $rows[] = [sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method]; + } + } catch (\Exception $e) { + $exitCode = 1; + $rows[] = [sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage()]; + } + } + // remove the assets of the bundles that no longer exist + if (!$input->getOption('no-cleanup') && is_dir($bundlesDir)) { + $dirsToRemove = Finder::create()->depth(0)->directories()->exclude($validAssetDirs)->in($bundlesDir); + $this->filesystem->remove($dirsToRemove); + } + + if ($rows) { + $io->table(['', 'Bundle', 'Method / Error'], $rows); + } + + if (0 !== $exitCode) { + $io->error('Some errors occurred while installing assets.'); + } else { + if ($copyUsed) { + $io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.'); + } + $io->success($rows ? 'All assets were successfully installed.' : 'No assets were provided by any bundle.'); + } + + return $exitCode; + } + + /** + * Try to create relative symlink. + * + * Falling back to absolute symlink and finally hard copy. + */ + private function relativeSymlinkWithFallback(string $originDir, string $targetDir): string + { + try { + $this->symlink($originDir, $targetDir, true); + $method = self::METHOD_RELATIVE_SYMLINK; + } catch (IOException) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); + } + + return $method; + } + + /** + * Try to create absolute symlink. + * + * Falling back to hard copy. + */ + private function absoluteSymlinkWithFallback(string $originDir, string $targetDir): string + { + try { + $this->symlink($originDir, $targetDir); + $method = self::METHOD_ABSOLUTE_SYMLINK; + } catch (IOException) { + // fall back to copy + $method = $this->hardCopy($originDir, $targetDir); + } + + return $method; + } + + /** + * Creates symbolic link. + * + * @throws IOException if link cannot be created + */ + private function symlink(string $originDir, string $targetDir, bool $relative = false): void + { + if ($relative) { + $this->filesystem->mkdir(\dirname($targetDir)); + $originDir = $this->filesystem->makePathRelative($originDir, realpath(\dirname($targetDir))); + } + $this->filesystem->symlink($originDir, $targetDir); + if (!file_exists($targetDir)) { + throw new IOException(sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0, null, $targetDir); + } + } + + /** + * Copies origin to target. + */ + private function hardCopy(string $originDir, string $targetDir): string + { + $this->filesystem->mkdir($targetDir, 0777); + // We use a custom iterator to ignore VCS files + $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + + return self::METHOD_COPY; + } + + private function getPublicDirectory(ContainerInterface $container): string + { + $defaultPublicDir = 'public'; + + if (null === $this->projectDir && !$container->hasParameter('kernel.project_dir')) { + return $defaultPublicDir; + } + + $composerFilePath = ($this->projectDir ?? $container->getParameter('kernel.project_dir')).'/composer.json'; + + if (!file_exists($composerFilePath)) { + return $defaultPublicDir; + } + + $composerConfig = json_decode($this->filesystem->readFile($composerFilePath), true, flags: \JSON_THROW_ON_ERROR); + + return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir; + } +} diff --git a/vendor/symfony/framework-bundle/Command/BuildDebugContainerTrait.php b/vendor/symfony/framework-bundle/Command/BuildDebugContainerTrait.php new file mode 100644 index 0000000..2f625e9 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/BuildDebugContainerTrait.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * @internal + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait BuildDebugContainerTrait +{ + protected ContainerBuilder $container; + + /** + * Loads the ContainerBuilder from the cache. + * + * @throws \LogicException + */ + protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilder + { + if (isset($this->container)) { + return $this->container; + } + + if (!$kernel->isDebug() || !$kernel->getContainer()->getParameter('debug.container.dump') || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { + $buildContainer = \Closure::bind(function () { + $this->initializeBundles(); + + return $this->buildContainer(); + }, $kernel, $kernel::class); + $container = $buildContainer(); + $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); + $container->compile(); + } else { + $buildContainer = \Closure::bind(function () { + $containerBuilder = $this->getContainerBuilder(); + $this->prepareContainer($containerBuilder); + + return $containerBuilder; + }, $kernel, $kernel::class); + $container = $buildContainer(); + (new XmlFileLoader($container, new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); + $locatorPass = new ServiceLocatorTagPass(); + $locatorPass->process($container); + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); + } + + return $this->container = $container; + } +} diff --git a/vendor/symfony/framework-bundle/Command/CacheClearCommand.php b/vendor/symfony/framework-bundle/Command/CacheClearCommand.php new file mode 100644 index 0000000..5581366 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/CacheClearCommand.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Dumper\Preloader; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; +use Symfony\Component\HttpKernel\RebootableInterface; + +/** + * Clear and Warmup the cache. + * + * @author Francis Besset + * @author Fabien Potencier + * + * @final + */ +#[AsCommand(name: 'cache:clear', description: 'Clear the cache')] +class CacheClearCommand extends Command +{ + private Filesystem $filesystem; + + public function __construct( + private CacheClearerInterface $cacheClearer, + ?Filesystem $filesystem = null, + ) { + parent::__construct(); + + $this->filesystem = $filesystem ?? new Filesystem(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'), + new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command clears and warms up the application cache for a given environment +and debug mode: + + php %command.full_name% --env=dev + php %command.full_name% --env=prod --no-debug +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $fs = $this->filesystem; + $io = new SymfonyStyle($input, $output); + + $kernel = $this->getApplication()->getKernel(); + $realCacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'); + $realBuildDir = $kernel->getContainer()->hasParameter('kernel.build_dir') ? $kernel->getContainer()->getParameter('kernel.build_dir') : $realCacheDir; + // the old cache dir name must not be longer than the real one to avoid exceeding + // the maximum length of a directory or file path within it (esp. Windows MAX_PATH) + $oldCacheDir = substr($realCacheDir, 0, -1).(str_ends_with($realCacheDir, '~') ? '+' : '~'); + $fs->remove($oldCacheDir); + + if (!is_writable($realCacheDir)) { + throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realCacheDir)); + } + + $useBuildDir = $realBuildDir !== $realCacheDir; + $oldBuildDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '~') ? '+' : '~'); + if ($useBuildDir) { + $fs->remove($oldBuildDir); + + if (!is_writable($realBuildDir)) { + throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realBuildDir)); + } + + if ($this->isNfs($realCacheDir)) { + $fs->remove($realCacheDir); + } else { + $fs->rename($realCacheDir, $oldCacheDir); + } + $fs->mkdir($realCacheDir); + } + + $io->comment(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + if ($useBuildDir) { + $this->cacheClearer->clear($realBuildDir); + } + $this->cacheClearer->clear($realCacheDir); + + // The current event dispatcher is stale, let's not use it anymore + $this->getApplication()->setDispatcher(new EventDispatcher()); + + $containerFile = (new \ReflectionObject($kernel->getContainer()))->getFileName(); + $containerDir = basename(\dirname($containerFile)); + + // the warmup cache dir name must have the same length as the real one + // to avoid the many problems in serialized resources files + $warmupDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '_') ? '-' : '_'); + + if ($output->isVerbose() && $fs->exists($warmupDir)) { + $io->comment('Clearing outdated warmup directory...'); + } + $fs->remove($warmupDir); + + if ($_SERVER['REQUEST_TIME'] <= filemtime($containerFile) && filemtime($containerFile) <= time()) { + if ($output->isVerbose()) { + $io->comment('Cache is fresh.'); + } + if (!$input->getOption('no-warmup') && !$input->getOption('no-optional-warmers')) { + if ($output->isVerbose()) { + $io->comment('Warming up optional cache...'); + } + $this->warmupOptionals($realCacheDir, $realBuildDir, $io); + } + } else { + $fs->mkdir($warmupDir); + + if (!$input->getOption('no-warmup')) { + if ($output->isVerbose()) { + $io->comment('Warming up cache...'); + } + $this->warmup($warmupDir, $realBuildDir); + + if (!$input->getOption('no-optional-warmers')) { + if ($output->isVerbose()) { + $io->comment('Warming up optional cache...'); + } + $this->warmupOptionals($useBuildDir ? $realCacheDir : $warmupDir, $warmupDir, $io); + } + } + + if (!$fs->exists($warmupDir.'/'.$containerDir)) { + $fs->rename($realBuildDir.'/'.$containerDir, $warmupDir.'/'.$containerDir); + touch($warmupDir.'/'.$containerDir.'.legacy'); + } + + if ($this->isNfs($realBuildDir)) { + $io->note('For better performance, you should move the cache and log directories to a non-shared folder of the VM.'); + $fs->remove($realBuildDir); + } else { + $fs->rename($realBuildDir, $oldBuildDir); + } + + $fs->rename($warmupDir, $realBuildDir); + + if ($output->isVerbose()) { + $io->comment('Removing old build and cache directory...'); + } + + if ($useBuildDir) { + try { + $fs->remove($oldBuildDir); + } catch (IOException $e) { + if ($output->isVerbose()) { + $io->warning($e->getMessage()); + } + } + } + + try { + $fs->remove($oldCacheDir); + } catch (IOException $e) { + if ($output->isVerbose()) { + $io->warning($e->getMessage()); + } + } + } + + if ($output->isVerbose()) { + $io->comment('Finished'); + } + + $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + + return 0; + } + + private function isNfs(string $dir): bool + { + static $mounts = null; + + if (null === $mounts) { + $mounts = []; + if ('/' === \DIRECTORY_SEPARATOR && @is_readable('/proc/mounts') && $files = @file('/proc/mounts')) { + foreach ($files as $mount) { + $mount = \array_slice(explode(' ', $mount), 1, -3); + if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) { + continue; + } + $mounts[] = implode(' ', $mount).'/'; + } + } + } + foreach ($mounts as $mount) { + if (str_starts_with($dir, $mount)) { + return true; + } + } + + return false; + } + + private function warmup(string $warmupDir, string $realBuildDir): void + { + // create a temporary kernel + $kernel = $this->getApplication()->getKernel(); + if (!$kernel instanceof RebootableInterface) { + throw new \LogicException('Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is not supported.'); + } + $kernel->reboot($warmupDir); + + // fix references to cached files with the real cache directory name + $search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)]; + $replace = str_replace('\\', '/', $realBuildDir); + foreach (Finder::create()->files()->in($warmupDir) as $file) { + $content = str_replace($search, $replace, $this->filesystem->readFile($file), $count); + if ($count) { + file_put_contents($file, $content); + } + } + } + + private function warmupOptionals(string $cacheDir, string $warmupDir, SymfonyStyle $io): void + { + $kernel = $this->getApplication()->getKernel(); + $warmer = $kernel->getContainer()->get('cache_warmer'); + // non optional warmers already ran during container compilation + $warmer->enableOnlyOptionalWarmers(); + $preload = (array) $warmer->warmUp($cacheDir, $warmupDir, $io); + + if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + Preloader::append($preloadFile, $preload); + } + } +} diff --git a/vendor/symfony/framework-bundle/Command/CachePoolClearCommand.php b/vendor/symfony/framework-bundle/Command/CachePoolClearCommand.php new file mode 100644 index 0000000..d5320e7 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/CachePoolClearCommand.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; + +/** + * Clear cache pools. + * + * @author Nicolas Grekas + */ +#[AsCommand(name: 'cache:pool:clear', description: 'Clear cache pools')] +final class CachePoolClearCommand extends Command +{ + /** + * @param string[]|null $poolNames + */ + public function __construct( + private Psr6CacheClearer $poolClearer, + private ?array $poolNames = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'A list of cache pools or cache pool clearers'), + ]) + ->addOption('all', null, InputOption::VALUE_NONE, 'Clear all cache pools') + ->addOption('exclude', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'A list of cache pools or cache pool clearers to exclude') + ->setHelp(<<<'EOF' +The %command.name% command clears the given cache pools or cache pool clearers. + + %command.full_name% [...] +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $kernel = $this->getApplication()->getKernel(); + $pools = []; + $clearers = []; + + $poolNames = $input->getArgument('pools'); + $excludedPoolNames = $input->getOption('exclude'); + if ($clearAll = $input->getOption('all')) { + if (!$this->poolNames) { + throw new InvalidArgumentException('Could not clear all cache pools, try specifying a specific pool or cache clearer.'); + } + + if (!$excludedPoolNames) { + $io->comment('Clearing all cache pools...'); + } + + $poolNames = $this->poolNames; + } elseif (!$poolNames) { + throw new InvalidArgumentException('Either specify at least one pool name, or provide the --all option to clear all pools.'); + } + + $poolNames = array_diff($poolNames, $excludedPoolNames); + + foreach ($poolNames as $id) { + if ($this->poolClearer->hasPool($id)) { + $pools[$id] = $id; + } elseif (!$clearAll || $kernel->getContainer()->has($id)) { + $pool = $kernel->getContainer()->get($id); + + if ($pool instanceof CacheItemPoolInterface) { + $pools[$id] = $pool; + } elseif ($pool instanceof Psr6CacheClearer) { + $clearers[$id] = $pool; + } else { + throw new InvalidArgumentException(sprintf('"%s" is not a cache pool nor a cache clearer.', $id)); + } + } + } + + foreach ($clearers as $id => $clearer) { + $io->comment(sprintf('Calling cache clearer: %s', $id)); + $clearer->clear($kernel->getContainer()->getParameter('kernel.cache_dir')); + } + + $failure = false; + foreach ($pools as $id => $pool) { + $io->comment(sprintf('Clearing cache pool: %s', $id)); + + if ($pool instanceof CacheItemPoolInterface) { + if (!$pool->clear()) { + $io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool)); + $failure = true; + } + } else { + if (false === $this->poolClearer->clearPool($id)) { + $io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool)); + $failure = true; + } + } + } + + if ($failure) { + return 1; + } + + $io->success('Cache was successfully cleared.'); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pools')) { + $suggestions->suggestValues($this->poolNames); + } + } +} diff --git a/vendor/symfony/framework-bundle/Command/CachePoolDeleteCommand.php b/vendor/symfony/framework-bundle/Command/CachePoolDeleteCommand.php new file mode 100644 index 0000000..e634e00 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/CachePoolDeleteCommand.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; + +/** + * Delete an item from a cache pool. + * + * @author Pierre du Plessis + */ +#[AsCommand(name: 'cache:pool:delete', description: 'Delete an item from a cache pool')] +final class CachePoolDeleteCommand extends Command +{ + /** + * @param string[]|null $poolNames + */ + public function __construct( + private Psr6CacheClearer $poolClearer, + private ?array $poolNames = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('pool', InputArgument::REQUIRED, 'The cache pool from which to delete an item'), + new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool'), + ]) + ->setHelp(<<<'EOF' +The %command.name% deletes an item from a given cache pool. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $pool = $input->getArgument('pool'); + $key = $input->getArgument('key'); + $cachePool = $this->poolClearer->getPool($pool); + + if (!$cachePool->hasItem($key)) { + $io->note(sprintf('Cache item "%s" does not exist in cache pool "%s".', $key, $pool)); + + return 0; + } + + if (!$cachePool->deleteItem($key)) { + throw new \Exception(sprintf('Cache item "%s" could not be deleted.', $key)); + } + + $io->success(sprintf('Cache item "%s" was successfully deleted.', $key)); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pool')) { + $suggestions->suggestValues($this->poolNames); + } + } +} diff --git a/vendor/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php b/vendor/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php new file mode 100644 index 0000000..f879a6d --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/CachePoolInvalidateTagsCommand.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * @author Kevin Bond + */ +#[AsCommand(name: 'cache:pool:invalidate-tags', description: 'Invalidate cache tags for all or a specific pool')] +final class CachePoolInvalidateTagsCommand extends Command +{ + private array $poolNames; + + public function __construct( + private ServiceProviderInterface $pools, + ) { + parent::__construct(); + + $this->poolNames = array_keys($pools->getProvidedServices()); + } + + protected function configure(): void + { + $this + ->addArgument('tags', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The tags to invalidate') + ->addOption('pool', 'p', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The pools to invalidate on') + ->setHelp(<<<'EOF' + The %command.name% command invalidates tags from taggable pools. By default, all pools + have the passed tags invalidated. Pass --pool=my_pool to invalidate tags on a specific pool. + + php %command.full_name% tag1 tag2 + php %command.full_name% tag1 tag2 --pool=cache2 --pool=cache1 + EOF) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $pools = $input->getOption('pool') ?: $this->poolNames; + $tags = $input->getArgument('tags'); + $tagList = implode(', ', $tags); + $errors = false; + + foreach ($pools as $name) { + $io->comment(sprintf('Invalidating tag(s): %s from pool %s.', $tagList, $name)); + + try { + $pool = $this->pools->get($name); + } catch (ServiceNotFoundException) { + $io->error(sprintf('Pool "%s" not found.', $name)); + $errors = true; + + continue; + } + + if (!$pool instanceof TagAwareCacheInterface) { + $io->error(sprintf('Pool "%s" is not taggable.', $name)); + $errors = true; + + continue; + } + + if (!$pool->invalidateTags($tags)) { + $io->error(sprintf('Cache tag(s) "%s" could not be invalidated for pool "%s".', $tagList, $name)); + $errors = true; + } + } + + if ($errors) { + $io->error('Done but with errors.'); + + return 1; + } + + $io->success('Successfully invalidated cache tags.'); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('pool')) { + $suggestions->suggestValues($this->poolNames); + } + } +} diff --git a/vendor/symfony/framework-bundle/Command/CachePoolListCommand.php b/vendor/symfony/framework-bundle/Command/CachePoolListCommand.php new file mode 100644 index 0000000..6b8e71e --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/CachePoolListCommand.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * List available cache pools. + * + * @author Tobias Nyholm + */ +#[AsCommand(name: 'cache:pool:list', description: 'List available cache pools')] +final class CachePoolListCommand extends Command +{ + /** + * @param string[] $poolNames + */ + public function __construct( + private array $poolNames, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setHelp(<<<'EOF' +The %command.name% command lists all available cache pools. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $io->table(['Pool name'], array_map(fn ($pool) => [$pool], $this->poolNames)); + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/CachePoolPruneCommand.php b/vendor/symfony/framework-bundle/Command/CachePoolPruneCommand.php new file mode 100644 index 0000000..fba1033 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/CachePoolPruneCommand.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Cache pool pruner command. + * + * @author Rob Frawley 2nd + */ +#[AsCommand(name: 'cache:pool:prune', description: 'Prune cache pools')] +final class CachePoolPruneCommand extends Command +{ + /** + * @param iterable $pools + */ + public function __construct( + private iterable $pools, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setHelp(<<<'EOF' +The %command.name% command deletes all expired items from all pruneable pools. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + foreach ($this->pools as $name => $pool) { + $io->comment(sprintf('Pruning cache pool: %s', $name)); + $pool->prune(); + } + + $io->success('Successfully pruned cache pool(s).'); + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/CacheWarmupCommand.php b/vendor/symfony/framework-bundle/Command/CacheWarmupCommand.php new file mode 100644 index 0000000..a4b32a5 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/CacheWarmupCommand.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Dumper\Preloader; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; + +/** + * Warmup the cache. + * + * @author Fabien Potencier + * + * @final + */ +#[AsCommand(name: 'cache:warmup', description: 'Warm up an empty cache')] +class CacheWarmupCommand extends Command +{ + public function __construct( + private CacheWarmerAggregate $cacheWarmer, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command warms up the cache. + +Before running this command, the cache must be empty. + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $kernel = $this->getApplication()->getKernel(); + $io->comment(sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + + if (!$input->getOption('no-optional-warmers')) { + $this->cacheWarmer->enableOptionalWarmers(); + } + $cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'); + + if ($kernel instanceof WarmableInterface) { + $kernel->warmUp($cacheDir); + } + + $preload = $this->cacheWarmer->warmUp($cacheDir); + + $buildDir = $kernel->getContainer()->getParameter('kernel.build_dir'); + if ($preload && $cacheDir === $buildDir && file_exists($preloadFile = $buildDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + Preloader::append($preloadFile, $preload); + } + + $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/ConfigDebugCommand.php b/vendor/symfony/framework-bundle/Command/ConfigDebugCommand.php new file mode 100644 index 0000000..cc116fc --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/ConfigDebugCommand.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\Yaml\Yaml; + +/** + * A console command for dumping available configuration reference. + * + * @author Grégoire Pineau + * + * @final + */ +#[AsCommand(name: 'debug:config', description: 'Dump the current configuration for an extension')] +class ConfigDebugCommand extends AbstractConfigCommand +{ + protected function configure(): void + { + $commentedHelpFormats = array_map(fn ($format) => sprintf('%s', $format), $this->getAvailableFormatOptions()); + $helpFormats = implode('", "', $commentedHelpFormats); + + $this + ->setDefinition([ + new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), + new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), + new InputOption('resolve-env', null, InputOption::VALUE_NONE, 'Display resolved environment variable values instead of placeholders'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'txt' : 'json'), + ]) + ->setHelp(<<%command.name% command dumps the current configuration for an +extension/bundle. + +Either the extension alias or bundle name can be used: + + php %command.full_name% framework + php %command.full_name% FrameworkBundle + +The --format option specifies the format of the configuration, +these are "{$helpFormats}". + + php %command.full_name% framework --format=json + +For dumping a specific option, add its path as second argument: + + php %command.full_name% framework serializer.enabled + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + if (null === $name = $input->getArgument('name')) { + $this->listBundles($errorIo); + $this->listNonBundleExtensions($errorIo); + + $errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. debug:config FrameworkBundle)'); + $errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. debug:config FrameworkBundle serializer to dump the framework.serializer configuration)'); + + return 0; + } + + $extension = $this->findExtension($name); + $extensionAlias = $extension->getAlias(); + $container = $this->compileContainer(); + + $config = $this->getConfig($extension, $container, $input->getOption('resolve-env')); + + $format = $input->getOption('format'); + + if (\in_array($format, ['txt', 'yml'], true) && !class_exists(Yaml::class)) { + $errorIo->error('Setting the "format" option to "txt" or "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=json" instead.'); + + return 1; + } + + if (null === $path = $input->getArgument('path')) { + if ('txt' === $input->getOption('format')) { + $io->title( + sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) + ); + } + + $io->writeln($this->convertToFormat([$extensionAlias => $config], $format)); + + return 0; + } + + try { + $config = $this->getConfigForPath($config, $path, $extensionAlias); + } catch (LogicException $e) { + $errorIo->error($e->getMessage()); + + return 1; + } + + $io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path)); + + $io->writeln($this->convertToFormat($config, $format)); + + return 0; + } + + private function convertToFormat(mixed $config, string $format): string + { + return match ($format) { + 'txt', 'yaml' => Yaml::dump($config, 10), + 'json' => json_encode($config, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + } + + private function compileContainer(): ContainerBuilder + { + $kernel = clone $this->getApplication()->getKernel(); + $kernel->boot(); + + $method = new \ReflectionMethod($kernel, 'buildContainer'); + $container = $method->invoke($kernel); + $container->getCompiler()->compile($container); + + return $container; + } + + /** + * Iterate over configuration until the last step of the given path. + * + * @throws LogicException If the configuration does not exist + */ + private function getConfigForPath(array $config, string $path, string $alias): mixed + { + $steps = explode('.', $path); + + foreach ($steps as $step) { + if (!\array_key_exists($step, $config)) { + throw new LogicException(sprintf('Unable to find configuration for "%s.%s".', $alias, $path)); + } + + $config = $config[$step]; + } + + return $config; + } + + private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array + { + $extensionAlias = $extension->getAlias(); + + $extensionConfig = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof ValidateEnvPlaceholdersPass) { + $extensionConfig = $pass->getExtensionConfig(); + break; + } + } + + if (isset($extensionConfig[$extensionAlias])) { + return $extensionConfig[$extensionAlias]; + } + + // Fall back to default config if the extension has one + + if (!$extension instanceof ConfigurationExtensionInterface && !$extension instanceof ConfigurationInterface) { + throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); + } + + $configs = $container->getExtensionConfig($extensionAlias); + $configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($configs, $container); + $this->validateConfiguration($extension, $configuration); + + return (new Processor())->processConfiguration($configuration, $configs); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues($this->getAvailableExtensions()); + $suggestions->suggestValues($this->getAvailableBundles()); + + return; + } + + if ($input->mustSuggestArgumentValuesFor('path') && null !== $name = $input->getArgument('name')) { + try { + $config = $this->getConfig($this->findExtension($name), $this->compileContainer()); + $paths = array_keys(self::buildPathsCompletion($config)); + $suggestions->suggestValues($paths); + } catch (LogicException) { + } + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableExtensions(): array + { + $kernel = $this->getApplication()->getKernel(); + + $extensions = []; + foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { + $extensions[] = $alias; + } + + return $extensions; + } + + private function getAvailableBundles(): array + { + $availableBundles = []; + foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { + $availableBundles[] = $bundle->getName(); + } + + return $availableBundles; + } + + private function getConfig(ExtensionInterface $extension, ContainerBuilder $container, bool $resolveEnvs = false): mixed + { + return $container->resolveEnvPlaceholders( + $container->getParameterBag()->resolveValue( + $this->getConfigForExtension($extension, $container) + ), $resolveEnvs ?: null + ); + } + + private static function buildPathsCompletion(array $paths, string $prefix = ''): array + { + $completionPaths = []; + foreach ($paths as $key => $values) { + if (\is_array($values)) { + $completionPaths += self::buildPathsCompletion($values, $prefix.$key.'.'); + } else { + $completionPaths[$prefix.$key] = null; + } + } + + return $completionPaths; + } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'yaml', 'json']; + } +} diff --git a/vendor/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php b/vendor/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php new file mode 100644 index 0000000..3231e5a --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper; +use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Yaml; + +/** + * A console command for dumping available configuration reference. + * + * @author Kevin Bond + * @author Wouter J + * @author Grégoire Pineau + * + * @final + */ +#[AsCommand(name: 'config:dump-reference', description: 'Dump the default configuration for an extension')] +class ConfigDumpReferenceCommand extends AbstractConfigCommand +{ + protected function configure(): void + { + $commentedHelpFormats = array_map(fn ($format) => sprintf('%s', $format), $this->getAvailableFormatOptions()); + $helpFormats = implode('", "', $commentedHelpFormats); + + $this + ->setDefinition([ + new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'), + new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'yaml'), + ]) + ->setHelp(<<%command.name% command dumps the default configuration for an +extension/bundle. + +Either the extension alias or bundle name can be used: + + php %command.full_name% framework + php %command.full_name% FrameworkBundle + +The --format option specifies the format of the configuration, +these are "{$helpFormats}". + + php %command.full_name% FrameworkBundle --format=xml + +For dumping a specific option, add its path as second argument (only available for the yaml format): + + php %command.full_name% framework http_client.default_options + +EOF + ) + ; + } + + /** + * @throws \LogicException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + if (null === $name = $input->getArgument('name')) { + $this->listBundles($errorIo); + $this->listNonBundleExtensions($errorIo); + + $errorIo->comment([ + 'Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. config:dump-reference FrameworkBundle)', + 'For dumping a specific option, add its path as the second argument of this command. (e.g. config:dump-reference FrameworkBundle http_client.default_options to dump the framework.http_client.default_options configuration)', + ]); + + return 0; + } + + $extension = $this->findExtension($name); + + if ($extension instanceof ConfigurationInterface) { + $configuration = $extension; + } else { + $configuration = $extension->getConfiguration([], $this->getContainerBuilder($this->getApplication()->getKernel())); + } + + $this->validateConfiguration($extension, $configuration); + + $format = $input->getOption('format'); + + if ('yaml' === $format && !class_exists(Yaml::class)) { + $errorIo->error('Setting the "format" option to "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=xml" instead.'); + + return 1; + } + + $path = $input->getArgument('path'); + + if (null !== $path && 'yaml' !== $format) { + $errorIo->error('The "path" option is only available for the "yaml" format.'); + + return 1; + } + + if ($name === $extension->getAlias()) { + $message = sprintf('Default configuration for extension with alias: "%s"', $name); + } else { + $message = sprintf('Default configuration for "%s"', $name); + } + + if (null !== $path) { + $message .= sprintf(' at path "%s"', $path); + } + + switch ($format) { + case 'yaml': + $io->writeln(sprintf('# %s', $message)); + $dumper = new YamlReferenceDumper(); + break; + case 'xml': + $io->writeln(sprintf('', $message)); + $dumper = new XmlReferenceDumper(); + break; + default: + $io->writeln($message); + throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))); + } + + $io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path)); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues($this->getAvailableExtensions()); + $suggestions->suggestValues($this->getAvailableBundles()); + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableExtensions(): array + { + $kernel = $this->getApplication()->getKernel(); + + $extensions = []; + foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { + $extensions[] = $alias; + } + + return $extensions; + } + + private function getAvailableBundles(): array + { + $bundles = []; + + foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { + $bundles[] = $bundle->getName(); + } + + return $bundles; + } + + private function getAvailableFormatOptions(): array + { + return ['yaml', 'xml']; + } +} diff --git a/vendor/symfony/framework-bundle/Command/ContainerDebugCommand.php b/vendor/symfony/framework-bundle/Command/ContainerDebugCommand.php new file mode 100644 index 0000000..df6aef5 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/ContainerDebugCommand.php @@ -0,0 +1,365 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; + +/** + * A console command for retrieving information about services. + * + * @author Ryan Weaver + * + * @internal + */ +#[AsCommand(name: 'debug:container', description: 'Display current services for an application')] +class ContainerDebugCommand extends Command +{ + use BuildDebugContainerTrait; + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'), + new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Show arguments in services'), + new InputOption('show-hidden', null, InputOption::VALUE_NONE, 'Show hidden (internal) services'), + new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Show all services with a specific tag'), + new InputOption('tags', null, InputOption::VALUE_NONE, 'Display tagged services for an application'), + new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Display a specific parameter for an application'), + new InputOption('parameters', null, InputOption::VALUE_NONE, 'Display parameters for an application'), + new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'), + new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'), + new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), + new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command displays all configured public services: + + php %command.full_name% + +To see deprecations generated during container compilation and cache warmup, use the --deprecations option: + + php %command.full_name% --deprecations + +To get specific information about a service, specify its name: + + php %command.full_name% validator + +To get specific information about a service including all its arguments, use the --show-arguments flag: + + php %command.full_name% validator --show-arguments + +To see available types that can be used for autowiring, use the --types flag: + + php %command.full_name% --types + +To see environment variables used by the container, use the --env-vars flag: + + php %command.full_name% --env-vars + +Display a specific environment variable by specifying its name with the --env-var option: + + php %command.full_name% --env-var=APP_ENV + +Use the --tags option to display tagged public services grouped by tag: + + php %command.full_name% --tags + +Find all services with a specific tag by specifying the tag name with the --tag option: + + php %command.full_name% --tag=form.type + +Use the --parameters option to display all parameters: + + php %command.full_name% --parameters + +Display a specific parameter by specifying its name with the --parameter option: + + php %command.full_name% --parameter=kernel.debug + +By default, internal services are hidden. You can display them +using the --show-hidden flag: + + php %command.full_name% --show-hidden + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + $this->validateInput($input); + $kernel = $this->getApplication()->getKernel(); + $object = $this->getContainerBuilder($kernel); + + if ($input->getOption('env-vars')) { + $options = ['env-vars' => true]; + } elseif ($envVar = $input->getOption('env-var')) { + $options = ['env-vars' => true, 'name' => $envVar]; + } elseif ($input->getOption('types')) { + $options = []; + $options['filter'] = $this->filterToServiceTypes(...); + } elseif ($input->getOption('parameters')) { + $parameters = []; + $parameterBag = $object->getParameterBag(); + foreach ($parameterBag->all() as $k => $v) { + $parameters[$k] = $object->resolveEnvPlaceholders($v); + } + $object = new ParameterBag($parameters); + if ($parameterBag instanceof ParameterBag) { + foreach ($parameterBag->allDeprecated() as $k => $deprecation) { + $object->deprecate($k, ...$deprecation); + } + } + $options = []; + } elseif ($parameter = $input->getOption('parameter')) { + $options = ['parameter' => $parameter]; + } elseif ($input->getOption('tags')) { + $options = ['group_by' => 'tags']; + } elseif ($tag = $input->getOption('tag')) { + $tag = $this->findProperTagName($input, $errorIo, $object, $tag); + $options = ['tag' => $tag]; + } elseif ($name = $input->getArgument('name')) { + $name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden')); + $options = ['id' => $name]; + } elseif ($input->getOption('deprecations')) { + $options = ['deprecations' => true]; + } else { + $options = []; + } + + $helper = new DescriptorHelper(); + $options['format'] = $input->getOption('format'); + $options['show_arguments'] = $input->getOption('show-arguments'); + $options['show_hidden'] = $input->getOption('show-hidden'); + $options['raw_text'] = $input->getOption('raw'); + $options['output'] = $io; + $options['is_debug'] = $kernel->isDebug(); + + try { + $helper->describe($io, $object, $options); + + if ('txt' === $options['format'] && isset($options['id'])) { + if ($object->hasDefinition($options['id'])) { + $definition = $object->getDefinition($options['id']); + if ($definition->isDeprecated()) { + $errorIo->warning($definition->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" service is deprecated.', $options['id'])); + } + } + if ($object->hasAlias($options['id'])) { + $alias = $object->getAlias($options['id']); + if ($alias->isDeprecated()) { + $errorIo->warning($alias->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" alias is deprecated.', $options['id'])); + } + } + } + + if (isset($options['id']) && isset($kernel->getContainer()->getRemovedIds()[$options['id']])) { + $errorIo->note(sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id'])); + } + } catch (ServiceNotFoundException $e) { + if ('' !== $e->getId() && '@' === $e->getId()[0]) { + throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]); + } + + throw $e; + } + + if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && !$input->getOption('env-vars') && !$input->getOption('env-var') && $input->isInteractive()) { + if ($input->getOption('tags')) { + $errorIo->comment('To search for a specific tag, re-run this command with a search term. (e.g. debug:container --tag=form.type)'); + } elseif ($input->getOption('parameters')) { + $errorIo->comment('To search for a specific parameter, re-run this command with a search term. (e.g. debug:container --parameter=kernel.debug)'); + } elseif (!$input->getOption('deprecations')) { + $errorIo->comment('To search for a specific service, re-run this command with a search term. (e.g. debug:container log)'); + } + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + + return; + } + + $kernel = $this->getApplication()->getKernel(); + $object = $this->getContainerBuilder($kernel); + + if ($input->mustSuggestArgumentValuesFor('name') + && !$input->getOption('tag') && !$input->getOption('tags') + && !$input->getOption('parameter') && !$input->getOption('parameters') + && !$input->getOption('env-var') && !$input->getOption('env-vars') + && !$input->getOption('types') && !$input->getOption('deprecations') + ) { + $suggestions->suggestValues($this->findServiceIdsContaining( + $object, + $input->getCompletionValue(), + (bool) $input->getOption('show-hidden') + )); + + return; + } + + if ($input->mustSuggestOptionValuesFor('tag')) { + $suggestions->suggestValues($object->findTags()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('parameter')) { + $suggestions->suggestValues(array_keys($object->getParameterBag()->all())); + } + } + + /** + * Validates input arguments and options. + * + * @throws \InvalidArgumentException + */ + protected function validateInput(InputInterface $input): void + { + $options = ['tags', 'tag', 'parameters', 'parameter']; + + $optionsCount = 0; + foreach ($options as $option) { + if ($input->getOption($option)) { + ++$optionsCount; + } + } + + $name = $input->getArgument('name'); + if ((null !== $name) && ($optionsCount > 0)) { + throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined with the service name argument.'); + } elseif ((null === $name) && $optionsCount > 1) { + throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined together.'); + } + } + + private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $container, string $name, bool $showHidden): string + { + $name = ltrim($name, '\\'); + + if ($container->has($name) || !$input->isInteractive()) { + return $name; + } + + $matchingServices = $this->findServiceIdsContaining($container, $name, $showHidden); + if (!$matchingServices) { + throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name)); + } + + if (1 === \count($matchingServices)) { + return $matchingServices[0]; + } + + return $io->choice('Select one of the following services to display its information', $matchingServices); + } + + private function findProperTagName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $container, string $tagName): string + { + if (\in_array($tagName, $container->findTags(), true) || !$input->isInteractive()) { + return $tagName; + } + + $matchingTags = $this->findTagsContaining($container, $tagName); + if (!$matchingTags) { + throw new InvalidArgumentException(sprintf('No tags found that match "%s".', $tagName)); + } + + if (1 === \count($matchingTags)) { + return $matchingTags[0]; + } + + return $io->choice('Select one of the following tags to display its information', $matchingTags); + } + + private function findServiceIdsContaining(ContainerBuilder $container, string $name, bool $showHidden): array + { + $serviceIds = $container->getServiceIds(); + $foundServiceIds = $foundServiceIdsIgnoringBackslashes = []; + foreach ($serviceIds as $serviceId) { + if (!$showHidden && str_starts_with($serviceId, '.')) { + continue; + } + if (!$showHidden && $container->hasDefinition($serviceId) && $container->getDefinition($serviceId)->hasTag('container.excluded')) { + continue; + } + if (false !== stripos(str_replace('\\', '', $serviceId), $name)) { + $foundServiceIdsIgnoringBackslashes[] = $serviceId; + } + if ('' === $name || false !== stripos($serviceId, $name)) { + $foundServiceIds[] = $serviceId; + } + } + + return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes; + } + + private function findTagsContaining(ContainerBuilder $container, string $tagName): array + { + $tags = $container->findTags(); + $foundTags = []; + foreach ($tags as $tag) { + if (str_contains($tag, $tagName)) { + $foundTags[] = $tag; + } + } + + return $foundTags; + } + + /** + * @internal + */ + public function filterToServiceTypes(string $serviceId): bool + { + // filter out things that could not be valid class names + if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^(?&V)(?:\\\\(?&V))*+(?: \$(?&V))?$/', $serviceId)) { + return false; + } + + // if the id has a \, assume it is a class + if (str_contains($serviceId, '\\')) { + return true; + } + + return class_exists($serviceId) || interface_exists($serviceId, false); + } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } +} diff --git a/vendor/symfony/framework-bundle/Command/ContainerLintCommand.php b/vendor/symfony/framework-bundle/Command/ContainerLintCommand.php new file mode 100644 index 0000000..cd6e065 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/ContainerLintCommand.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Compiler\CheckAliasValidityPass; +use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\ResolveFactoryClassPass; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\HttpKernel\Kernel; + +#[AsCommand(name: 'lint:container', description: 'Ensure that arguments injected into services match type declarations')] +final class ContainerLintCommand extends Command +{ + private ContainerBuilder $container; + + protected function configure(): void + { + $this + ->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + try { + $container = $this->getContainerBuilder(); + } catch (RuntimeException $e) { + $errorIo->error($e->getMessage()); + + return 2; + } + + $container->setParameter('container.build_time', time()); + + try { + $container->compile(); + } catch (InvalidArgumentException $e) { + $errorIo->error($e->getMessage()); + + return 1; + } + + $io->success('The container was linted successfully: all services are injected with values that are compatible with their type declarations.'); + + return 0; + } + + private function getContainerBuilder(): ContainerBuilder + { + if (isset($this->container)) { + return $this->container; + } + + $kernel = $this->getApplication()->getKernel(); + $kernelContainer = $kernel->getContainer(); + + if (!$kernel->isDebug() || !$kernelContainer->getParameter('debug.container.dump') || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) { + if (!$kernel instanceof Kernel) { + throw new RuntimeException(sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class)); + } + + $buildContainer = \Closure::bind(function (): ContainerBuilder { + $this->initializeBundles(); + + return $this->buildContainer(); + }, $kernel, $kernel::class); + $container = $buildContainer(); + } else { + if (!$kernelContainer instanceof Container) { + throw new RuntimeException(sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class)); + } + + (new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump')); + + $refl = new \ReflectionProperty($parameterBag, 'resolved'); + $refl->setValue($parameterBag, true); + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([new ResolveFactoryClassPass()]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); + } + + $container->setParameter('container.build_hash', 'lint_container'); + $container->setParameter('container.build_id', 'lint_container'); + + $container->addCompilerPass(new CheckAliasValidityPass(), PassConfig::TYPE_BEFORE_REMOVING, -100); + $container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100); + + return $this->container = $container; + } +} diff --git a/vendor/symfony/framework-bundle/Command/DebugAutowiringCommand.php b/vendor/symfony/framework-bundle/Command/DebugAutowiringCommand.php new file mode 100644 index 0000000..77011b1 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/DebugAutowiringCommand.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Attribute\Target; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; + +/** + * A console command for autowiring information. + * + * @author Ryan Weaver + * + * @internal + */ +#[AsCommand(name: 'debug:autowiring', description: 'List classes/interfaces you can use for autowiring')] +class DebugAutowiringCommand extends ContainerDebugCommand +{ + public function __construct( + ?string $name = null, + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { + parent::__construct($name); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'), + new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command displays the classes and interfaces that +you can use as type-hints for autowiring: + + php %command.full_name% + +You can also pass a search term to filter the list: + + php %command.full_name% log + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + $container = $this->getContainerBuilder($this->getApplication()->getKernel()); + $serviceIds = $container->getServiceIds(); + $serviceIds = array_filter($serviceIds, $this->filterToServiceTypes(...)); + + if ($search = $input->getArgument('search')) { + $searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search); + + $serviceIds = array_filter($serviceIds, fn ($serviceId) => false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.')); + + if (!$serviceIds) { + $errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search)); + + return 1; + } + } + + $reverseAliases = []; + + foreach ($container->getAliases() as $id => $alias) { + if ('.' === ($id[0] ?? null)) { + $reverseAliases[(string) $alias][] = $id; + } + } + + uasort($serviceIds, 'strnatcmp'); + + $io->title('Autowirable Types'); + $io->text('The following classes & interfaces can be used as type-hints when autowiring:'); + if ($search) { + $io->text(sprintf('(only showing classes/interfaces matching %s)', $search)); + } + $hasAlias = []; + $all = $input->getOption('all'); + $previousId = '-'; + $serviceIdsNb = 0; + foreach ($serviceIds as $serviceId) { + if ($container->hasDefinition($serviceId) && $container->getDefinition($serviceId)->hasTag('container.excluded')) { + continue; + } + $text = []; + $resolvedServiceId = $serviceId; + if (!str_starts_with($serviceId, $previousId.' $')) { + $text[] = ''; + $previousId = preg_replace('/ \$.*/', '', $serviceId); + if ('' !== $description = Descriptor::getClassDescription($previousId, $resolvedServiceId)) { + if (isset($hasAlias[$previousId])) { + continue; + } + $text[] = $description; + } + } + + $serviceLine = sprintf('%s', $serviceId); + if ('' !== $fileLink = $this->getFileLink($previousId)) { + $serviceLine = substr($serviceId, \strlen($previousId)); + $serviceLine = sprintf('%s', $fileLink, $previousId).('' !== $serviceLine ? sprintf('%s', $serviceLine) : ''); + } + + if ($container->hasAlias($serviceId)) { + $hasAlias[$serviceId] = true; + $serviceAlias = $container->getAlias($serviceId); + $alias = (string) $serviceAlias; + + $target = null; + foreach ($reverseAliases[(string) $serviceAlias] ?? [] as $id) { + if (!str_starts_with($id, '.'.$previousId.' $')) { + continue; + } + $target = substr($id, \strlen($previousId) + 3); + + if ($previousId.' $'.(new Target($target))->getParsedName() === $serviceId) { + $serviceLine .= ' - target:'.$target.''; + break; + } + } + + if ($container->hasDefinition($serviceAlias) && $decorated = $container->getDefinition($serviceAlias)->getTag('container.decorator')) { + $alias = $decorated[0]['id']; + } + + if ($alias !== $target) { + $serviceLine .= ' - alias:'.$alias.''; + } + + if ($serviceAlias->isDeprecated()) { + $serviceLine .= ' - deprecated'; + } + } elseif (!$all) { + ++$serviceIdsNb; + continue; + } elseif ($container->getDefinition($serviceId)->isDeprecated()) { + $serviceLine .= ' - deprecated'; + } + $text[] = $serviceLine; + $io->text($text); + } + + $io->newLine(); + + if (0 < $serviceIdsNb) { + $io->text(sprintf('%s more concrete service%s would be displayed when adding the "--all" option.', $serviceIdsNb, $serviceIdsNb > 1 ? 's' : '')); + } + if ($all) { + $io->text('Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.'); + } + + $io->newLine(); + + return 0; + } + + private function getFileLink(string $class): string + { + if (null === $this->fileLinkFormatter + || (null === $r = $this->getContainerBuilder($this->getApplication()->getKernel())->getReflectionClass($class, false))) { + return ''; + } + + return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('search')) { + $container = $this->getContainerBuilder($this->getApplication()->getKernel()); + + $suggestions->suggestValues(array_filter($container->getServiceIds(), $this->filterToServiceTypes(...))); + } + } +} diff --git a/vendor/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php b/vendor/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php new file mode 100644 index 0000000..52816e7 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * A console command for retrieving information about event dispatcher. + * + * @author Matthieu Auger + * + * @final + */ +#[AsCommand(name: 'debug:event-dispatcher', description: 'Display configured listeners for an application')] +class EventDispatcherDebugCommand extends Command +{ + private const DEFAULT_DISPATCHER = 'event_dispatcher'; + + public function __construct( + private ContainerInterface $dispatchers, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'), + new InputOption('dispatcher', null, InputOption::VALUE_REQUIRED, 'To view events of a specific event dispatcher', self::DEFAULT_DISPATCHER), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command displays all configured listeners: + + php %command.full_name% + +To get specific listeners for an event, specify its name: + + php %command.full_name% kernel.request +EOF + ) + ; + } + + /** + * @throws \LogicException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $options = []; + $dispatcherServiceName = $input->getOption('dispatcher'); + if (!$this->dispatchers->has($dispatcherServiceName)) { + $io->getErrorStyle()->error(sprintf('Event dispatcher "%s" is not available.', $dispatcherServiceName)); + + return 1; + } + + $dispatcher = $this->dispatchers->get($dispatcherServiceName); + + if ($event = $input->getArgument('event')) { + if ($dispatcher->hasListeners($event)) { + $options = ['event' => $event]; + } else { + // if there is no direct match, try find partial matches + $events = $this->searchForEvent($dispatcher, $event); + if (0 === \count($events)) { + $io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); + + return 0; + } elseif (1 === \count($events)) { + $options = ['event' => $events[array_key_first($events)]]; + } else { + $options = ['events' => $events]; + } + } + } + + $helper = new DescriptorHelper(); + + if (self::DEFAULT_DISPATCHER !== $dispatcherServiceName) { + $options['dispatcher_service_name'] = $dispatcherServiceName; + } + + $options['format'] = $input->getOption('format'); + $options['raw_text'] = $input->getOption('raw'); + $options['output'] = $io; + $helper->describe($io, $dispatcher, $options); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('event')) { + $dispatcherServiceName = $input->getOption('dispatcher'); + if ($this->dispatchers->has($dispatcherServiceName)) { + $dispatcher = $this->dispatchers->get($dispatcherServiceName); + $suggestions->suggestValues(array_keys($dispatcher->getListeners())); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('dispatcher')) { + if ($this->dispatchers instanceof ServiceProviderInterface) { + $suggestions->suggestValues(array_keys($this->dispatchers->getProvidedServices())); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function searchForEvent(EventDispatcherInterface $dispatcher, string $needle): array + { + $output = []; + $lcNeedle = strtolower($needle); + $allEvents = array_keys($dispatcher->getListeners()); + foreach ($allEvents as $event) { + if (str_contains(strtolower($event), $lcNeedle)) { + $output[] = $event; + } + } + + return $output; + } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } +} diff --git a/vendor/symfony/framework-bundle/Command/RouterDebugCommand.php b/vendor/symfony/framework-bundle/Command/RouterDebugCommand.php new file mode 100644 index 0000000..54df494 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/RouterDebugCommand.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouterInterface; + +/** + * A console command for retrieving information about routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @final + */ +#[AsCommand(name: 'debug:router', description: 'Display current routes for an application')] +class RouterDebugCommand extends Command +{ + use BuildDebugContainerTrait; + + public function __construct( + private RouterInterface $router, + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('name', InputArgument::OPTIONAL, 'A route name'), + new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'), + new InputOption('show-aliases', null, InputOption::VALUE_NONE, 'Show aliases in overview'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), + ]) + ->setHelp(<<<'EOF' +The %command.name% displays the configured routes: + + php %command.full_name% + +EOF + ) + ; + } + + /** + * @throws InvalidArgumentException When route does not exist + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $name = $input->getArgument('name'); + $helper = new DescriptorHelper($this->fileLinkFormatter); + $routes = $this->router->getRouteCollection(); + $container = null; + if ($this->fileLinkFormatter) { + $container = fn () => $this->getContainerBuilder($this->getApplication()->getKernel()); + } + + if ($name) { + $route = $routes->get($name); + $matchingRoutes = $this->findRouteNameContaining($name, $routes); + + if (!$input->isInteractive() && !$route && \count($matchingRoutes) > 1) { + $helper->describe($io, $this->findRouteContaining($name, $routes), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'show_controllers' => $input->getOption('show-controllers'), + 'show_aliases' => $input->getOption('show-aliases'), + 'output' => $io, + ]); + + return 0; + } + + if (!$route && $matchingRoutes) { + $default = 1 === \count($matchingRoutes) ? $matchingRoutes[0] : null; + $name = $io->choice('Select one of the matching routes', $matchingRoutes, $default); + $route = $routes->get($name); + } + + if (!$route) { + throw new InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); + } + + $helper->describe($io, $route, [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'name' => $name, + 'output' => $io, + 'container' => $container, + ]); + } else { + $helper->describe($io, $routes, [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'show_controllers' => $input->getOption('show-controllers'), + 'show_aliases' => $input->getOption('show-aliases'), + 'output' => $io, + 'container' => $container, + ]); + } + + return 0; + } + + private function findRouteNameContaining(string $name, RouteCollection $routes): array + { + $foundRoutesNames = []; + foreach ($routes as $routeName => $route) { + if (false !== stripos($routeName, $name)) { + $foundRoutesNames[] = $routeName; + } + } + + return $foundRoutesNames; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->router->getRouteCollection()->all())); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function findRouteContaining(string $name, RouteCollection $routes): RouteCollection + { + $foundRoutes = new RouteCollection(); + foreach ($routes as $routeName => $route) { + if (false !== stripos($routeName, $name)) { + $foundRoutes->add($routeName, $route); + } + } + + return $foundRoutes; + } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } +} diff --git a/vendor/symfony/framework-bundle/Command/RouterMatchCommand.php b/vendor/symfony/framework-bundle/Command/RouterMatchCommand.php new file mode 100644 index 0000000..475b403 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/RouterMatchCommand.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; +use Symfony\Component\Routing\RouterInterface; + +/** + * A console command to test route matching. + * + * @author Fabien Potencier + * + * @final + */ +#[AsCommand(name: 'router:match', description: 'Help debug routes by simulating a path info match')] +class RouterMatchCommand extends Command +{ + /** + * @param iterable $expressionLanguageProviders + */ + public function __construct( + private RouterInterface $router, + private iterable $expressionLanguageProviders = [], + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'), + new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Set the HTTP method'), + new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Set the URI scheme (usually http or https)'), + new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'), + ]) + ->setHelp(<<<'EOF' +The %command.name% shows which routes match a given request and which don't and for what reason: + + php %command.full_name% /foo + +or + + php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $context = $this->router->getContext(); + if (null !== $method = $input->getOption('method')) { + $context->setMethod($method); + } + if (null !== $scheme = $input->getOption('scheme')) { + $context->setScheme($scheme); + } + if (null !== $host = $input->getOption('host')) { + $context->setHost($host); + } + + $matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context); + foreach ($this->expressionLanguageProviders as $provider) { + $matcher->addExpressionLanguageProvider($provider); + } + + $traces = $matcher->getTraces($input->getArgument('path_info')); + + $io->newLine(); + + $matches = false; + foreach ($traces as $trace) { + if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) { + $io->text(sprintf('Route "%s" almost matches but %s', $trace['name'], lcfirst($trace['log']))); + } elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) { + $io->success(sprintf('Route "%s" matches', $trace['name'])); + + $routerDebugCommand = $this->getApplication()->find('debug:router'); + $routerDebugCommand->run(new ArrayInput(['name' => $trace['name']]), $output); + + $matches = true; + } elseif ($input->getOption('verbose')) { + $io->text(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log'])); + } + } + + if (!$matches) { + $io->error(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info'))); + + return 1; + } + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php b/vendor/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php new file mode 100644 index 0000000..cd8b26c --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Nicolas Grekas + * + * @internal + */ +#[AsCommand(name: 'secrets:decrypt-to-local', description: 'Decrypt all secrets and stores them in the local vault')] +final class SecretsDecryptToLocalCommand extends Command +{ + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault') + ->setHelp(<<<'EOF' +The %command.name% command decrypts all secrets and copies them in the local vault. + + %command.full_name% + +When the --force option is provided, secrets that already exist in the local vault are overridden. + + %command.full_name% --force +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + if (null === $this->localVault) { + $io->error('The local vault is disabled.'); + + return 1; + } + + $secrets = $this->vault->list(true); + + $io->comment(sprintf('%d secret%s found in the vault.', \count($secrets), 1 !== \count($secrets) ? 's' : '')); + + $skipped = 0; + if (!$input->getOption('force')) { + foreach ($this->localVault->list() as $k => $v) { + if (isset($secrets[$k])) { + ++$skipped; + unset($secrets[$k]); + } + } + } + + if ($skipped > 0) { + $io->warning([ + sprintf('%d secret%s already overridden in the local vault and will be skipped.', $skipped, 1 !== $skipped ? 's are' : ' is'), + 'Use the --force flag to override these.', + ]); + } + + foreach ($secrets as $k => $v) { + if (null === $v) { + $io->error($this->vault->getLastMessage() ?? sprintf('Secret "%s" has been skipped as there was an error reading it.', $k)); + continue; + } + + $this->localVault->seal($k, $v); + $io->note($this->localVault->getLastMessage()); + } + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php b/vendor/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php new file mode 100644 index 0000000..9740098 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Nicolas Grekas + * + * @internal + */ +#[AsCommand(name: 'secrets:encrypt-from-local', description: 'Encrypt all local secrets to the vault')] +final class SecretsEncryptFromLocalCommand extends Command +{ + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setHelp(<<<'EOF' +The %command.name% command encrypts all locally overridden secrets to the vault. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + if (null === $this->localVault) { + $io->error('The local vault is disabled.'); + + return 1; + } + + foreach ($this->vault->list(true) as $name => $value) { + $localValue = $this->localVault->reveal($name); + + if (null !== $localValue && $value !== $localValue) { + $this->vault->seal($name, $localValue); + } elseif (null !== $message = $this->localVault->getLastMessage()) { + $io->error($message); + + return 1; + } + } + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php b/vendor/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php new file mode 100644 index 0000000..66a752e --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +#[AsCommand(name: 'secrets:generate-keys', description: 'Generate new encryption keys')] +final class SecretsGenerateKeysCommand extends Command +{ + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') + ->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.') + ->setHelp(<<<'EOF' +The %command.name% command generates a new encryption key. + + %command.full_name% + +If encryption keys already exist, the command must be called with +the --rotate option in order to override those keys and re-encrypt +existing secrets. + + %command.full_name% --rotate +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + $vault = $input->getOption('local') ? $this->localVault : $this->vault; + + if (null === $vault) { + $io->success('The local vault is disabled.'); + + return 1; + } + + if (!$input->getOption('rotate')) { + if ($vault->generateKeys()) { + $io->success($vault->getLastMessage()); + + if ($this->vault === $vault) { + $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENTâš ï¸'); + } + + return 0; + } + + $io->warning($vault->getLastMessage()); + + return 1; + } + + $secrets = []; + foreach ($vault->list(true) as $name => $value) { + if (null === $value) { + $io->error($vault->getLastMessage()); + + return 1; + } + + $secrets[$name] = $value; + } + + if (!$vault->generateKeys(true)) { + $io->warning($vault->getLastMessage()); + + return 1; + } + + $io->success($vault->getLastMessage()); + + if ($secrets) { + foreach ($secrets as $name => $value) { + $vault->seal($name, $value); + } + + $io->comment('Existing secrets have been rotated to the new keys.'); + } + + if ($this->vault === $vault) { + $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENTâš ï¸'); + } + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/SecretsListCommand.php b/vendor/symfony/framework-bundle/Command/SecretsListCommand.php new file mode 100644 index 0000000..cdfc51c --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/SecretsListCommand.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +#[AsCommand(name: 'secrets:list', description: 'List all secrets')] +final class SecretsListCommand extends Command +{ + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names') + ->setHelp(<<<'EOF' +The %command.name% command list all stored secrets. + + %command.full_name% + +When the option --reveal is provided, the decrypted secrets are also displayed. + + %command.full_name% --reveal +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $io->comment('Use "%env()%" to reference a secret in a config file.'); + + if (!$reveal = $input->getOption('reveal')) { + $io->comment(sprintf('To reveal the secrets run php %s %s --reveal', $_SERVER['PHP_SELF'], $this->getName())); + } + + $secrets = $this->vault->list($reveal); + $localSecrets = $this->localVault?->list($reveal); + + $rows = []; + + $dump = new Dumper($output); + $dump = fn ($v) => null === $v ? '******' : $dump($v); + + foreach ($secrets as $name => $value) { + $rows[$name] = [$name, $dump($value)]; + } + + if (null !== $message = $this->vault->getLastMessage()) { + $io->comment($message); + } + + foreach ($localSecrets ?? [] as $name => $value) { + if (isset($rows[$name])) { + $rows[$name][] = $dump($value); + } + } + + if (null !== $this->localVault && null !== $message = $this->localVault->getLastMessage()) { + $io->comment($message); + } + + (new SymfonyStyle($input, $output)) + ->table(['Secret', 'Value'] + (null !== $localSecrets ? [2 => 'Local Value'] : []), $rows); + + $io->comment("Local values override secret values.\nUse secrets:set --local to define them."); + + return 0; + } +} diff --git a/vendor/symfony/framework-bundle/Command/SecretsRemoveCommand.php b/vendor/symfony/framework-bundle/Command/SecretsRemoveCommand.php new file mode 100644 index 0000000..11660b0 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/SecretsRemoveCommand.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +#[AsCommand(name: 'secrets:remove', description: 'Remove a secret from the vault')] +final class SecretsRemoveCommand extends Command +{ + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') + ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') + ->setHelp(<<<'EOF' +The %command.name% command removes a secret from the vault. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + $vault = $input->getOption('local') ? $this->localVault : $this->vault; + + if (null === $vault) { + $io->success('The local vault is disabled.'); + + return 1; + } + + if ($vault->remove($name = $input->getArgument('name'))) { + $io->success($vault->getLastMessage() ?? 'Secret was removed from the vault.'); + } else { + $io->comment($vault->getLastMessage() ?? 'Secret was not found in the vault.'); + } + + if ($this->vault === $vault && null !== $this->localVault->reveal($name)) { + $io->comment('Note that this secret is overridden in the local vault.'); + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if (!$input->mustSuggestArgumentValuesFor('name')) { + return; + } + + $vaultKeys = array_keys($this->vault->list(false)); + if ($input->getOption('local')) { + if (null === $this->localVault) { + return; + } + $vaultKeys = array_intersect($vaultKeys, array_keys($this->localVault->list(false))); + } + + $suggestions->suggestValues($vaultKeys); + } +} diff --git a/vendor/symfony/framework-bundle/Command/SecretsRevealCommand.php b/vendor/symfony/framework-bundle/Command/SecretsRevealCommand.php new file mode 100644 index 0000000..bcbdea1 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/SecretsRevealCommand.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @internal + */ +#[AsCommand(name: 'secrets:reveal', description: 'Reveal the value of a secret')] +final class SecretsRevealCommand extends Command +{ + public function __construct( + private readonly AbstractVault $vault, + private readonly ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret to reveal', null, fn () => array_keys($this->vault->list())) + ->setHelp(<<<'EOF' +The %command.name% command reveals a stored secret. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $secrets = $this->vault->list(true); + $localSecrets = $this->localVault?->list(true); + + $name = (string) $input->getArgument('name'); + + if (null !== $localSecrets && \array_key_exists($name, $localSecrets)) { + $io->writeln($localSecrets[$name]); + } else { + if (!\array_key_exists($name, $secrets)) { + $io->error(sprintf('The secret "%s" does not exist.', $name)); + + return self::INVALID; + } + + $io->writeln($secrets[$name]); + } + + return self::SUCCESS; + } +} diff --git a/vendor/symfony/framework-bundle/Command/SecretsSetCommand.php b/vendor/symfony/framework-bundle/Command/SecretsSetCommand.php new file mode 100644 index 0000000..49a20af --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/SecretsSetCommand.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +#[AsCommand(name: 'secrets:set', description: 'Set a secret in the vault')] +final class SecretsSetCommand extends Command +{ + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') + ->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN') + ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') + ->addOption('random', 'r', InputOption::VALUE_OPTIONAL, 'Generate a random value.', false) + ->setHelp(<<<'EOF' +The %command.name% command stores a secret in the vault. + + %command.full_name% + +To reference secrets in services.yaml or any other config +files, use "%env()%". + +By default, the secret value should be entered interactively. +Alternatively, provide a file where to read the secret from: + + php %command.full_name% filename + +Use "-" as a file name to read from STDIN: + + cat filename | php %command.full_name% - + +Use --local to override secrets for local needs. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $io = new SymfonyStyle($input, $errOutput); + $name = $input->getArgument('name'); + $vault = $input->getOption('local') ? $this->localVault : $this->vault; + + if (null === $vault) { + $io->error('The local vault is disabled.'); + + return 1; + } + + if ($this->localVault === $vault && !\array_key_exists($name, $this->vault->list())) { + $io->error(sprintf('Secret "%s" does not exist in the vault, you cannot override it locally.', $name)); + + return 1; + } + + if (0 < $random = $input->getOption('random') ?? 16) { + $value = strtr(substr(base64_encode(random_bytes($random)), 0, $random), '+/', '-_'); + } elseif (!$file = $input->getArgument('file')) { + $value = $io->askHidden('Please type the secret value'); + + if (null === $value) { + $io->warning('No value provided: using empty string'); + $value = ''; + } + } elseif ('-' === $file) { + $value = file_get_contents('php://stdin'); + } elseif (is_file($file) && is_readable($file)) { + $value = file_get_contents($file); + } elseif (!is_file($file)) { + throw new \InvalidArgumentException(sprintf('File not found: "%s".', $file)); + } elseif (!is_readable($file)) { + throw new \InvalidArgumentException(sprintf('File is not readable: "%s".', $file)); + } + + if ($vault->generateKeys()) { + $io->success($vault->getLastMessage()); + + if ($this->vault === $vault) { + $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENTâš ï¸'); + } + } + + $vault->seal($name, $value); + + $io->success($vault->getLastMessage() ?? 'Secret was successfully stored in the vault.'); + + if (0 < $random) { + $errOutput->write(' // The generated random value is: '); + $output->write($value); + $errOutput->writeln(''); + $io->newLine(); + } + + if ($this->vault === $vault && null !== $this->localVault->reveal($name)) { + $io->comment('Note that this secret is overridden in the local vault.'); + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->vault->list(false))); + } + } +} diff --git a/vendor/symfony/framework-bundle/Command/TranslationDebugCommand.php b/vendor/symfony/framework-bundle/Command/TranslationDebugCommand.php new file mode 100644 index 0000000..2438d3f --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/TranslationDebugCommand.php @@ -0,0 +1,400 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Translation\Catalogue\MergeOperation; +use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\LoggingTranslator; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\Translator; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Helps finding unused or missing translation messages in a given locale + * and comparing them with the fallback ones. + * + * @author Florian Voutzinos + * + * @final + */ +#[AsCommand(name: 'debug:translation', description: 'Display translation messages information')] +class TranslationDebugCommand extends Command +{ + public const EXIT_CODE_GENERAL_ERROR = 64; + public const EXIT_CODE_MISSING = 65; + public const EXIT_CODE_UNUSED = 66; + public const EXIT_CODE_FALLBACK = 68; + public const MESSAGE_MISSING = 0; + public const MESSAGE_UNUSED = 1; + public const MESSAGE_EQUALS_FALLBACK = 2; + + public function __construct( + private TranslatorInterface $translator, + private TranslationReaderInterface $reader, + private ExtractorInterface $extractor, + private ?string $defaultTransPath = null, + private ?string $defaultViewsPath = null, + private array $transPaths = [], + private array $codePaths = [], + private array $enabledLocales = [], + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), + new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'), + new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'), + new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Display only missing messages'), + new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'), + new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command helps finding unused or missing translation +messages and comparing them with the fallback ones by inspecting the +templates and translation files of a given bundle or the default translations directory. + +You can display information about bundle translations in a specific locale: + + php %command.full_name% en AcmeDemoBundle + +You can also specify a translation domain for the search: + + php %command.full_name% --domain=messages en AcmeDemoBundle + +You can only display missing messages: + + php %command.full_name% --only-missing en AcmeDemoBundle + +You can only display unused messages: + + php %command.full_name% --only-unused en AcmeDemoBundle + +You can display information about application translations in a specific locale: + + php %command.full_name% en + +You can display information about translations in all registered bundles in a specific locale: + + php %command.full_name% --all en + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $locale = $input->getArgument('locale'); + $domain = $input->getOption('domain'); + + $exitCode = self::SUCCESS; + + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + + // Define Root Paths + $transPaths = $this->getRootTransPaths(); + $codePaths = $this->getRootCodePaths($kernel); + + // Override with provided Bundle info + if (null !== $input->getArgument('bundle')) { + try { + $bundle = $kernel->getBundle($input->getArgument('bundle')); + $bundleDir = $bundle->getPath(); + $transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations']; + $codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates']; + if ($this->defaultTransPath) { + $transPaths[] = $this->defaultTransPath; + } + if ($this->defaultViewsPath) { + $codePaths[] = $this->defaultViewsPath; + } + } catch (\InvalidArgumentException) { + // such a bundle does not exist, so treat the argument as path + $path = $input->getArgument('bundle'); + + $transPaths = [$path.'/translations']; + $codePaths = [$path.'/templates']; + + if (!is_dir($transPaths[0])) { + throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); + } + } + } elseif ($input->getOption('all')) { + foreach ($kernel->getBundles() as $bundle) { + $bundleDir = $bundle->getPath(); + $transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations'; + $codePaths[] = is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundle->getPath().'/templates'; + } + } + + // Extract used messages + $extractedCatalogue = $this->extractMessages($locale, $codePaths); + + // Load defined messages + $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths); + + // Merge defined and extracted messages to get all message ids + $mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue); + $allMessages = $mergeOperation->getResult()->all($domain); + if (null !== $domain) { + $allMessages = [$domain => $allMessages]; + } + + // No defined or extracted messages + if (!$allMessages || null !== $domain && empty($allMessages[$domain])) { + $outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale); + + if (null !== $domain) { + $outputMessage .= sprintf(' and domain "%s"', $domain); + } + + $io->getErrorStyle()->warning($outputMessage); + + return self::EXIT_CODE_GENERAL_ERROR; + } + + // Load the fallback catalogues + $fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths); + + // Display header line + $headers = ['State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale)]; + foreach ($fallbackCatalogues as $fallbackCatalogue) { + $headers[] = sprintf('Fallback Message Preview (%s)', $fallbackCatalogue->getLocale()); + } + $rows = []; + // Iterate all message ids and determine their state + foreach ($allMessages as $domain => $messages) { + foreach (array_keys($messages) as $messageId) { + $value = $currentCatalogue->get($messageId, $domain); + $states = []; + + if ($extractedCatalogue->defines($messageId, $domain)) { + if (!$currentCatalogue->defines($messageId, $domain)) { + $states[] = self::MESSAGE_MISSING; + + if (!$input->getOption('only-unused')) { + $exitCode |= self::EXIT_CODE_MISSING; + } + } + } elseif ($currentCatalogue->defines($messageId, $domain)) { + $states[] = self::MESSAGE_UNUSED; + + if (!$input->getOption('only-missing')) { + $exitCode |= self::EXIT_CODE_UNUSED; + } + } + + if (!\in_array(self::MESSAGE_UNUSED, $states, true) && $input->getOption('only-unused') + || !\in_array(self::MESSAGE_MISSING, $states, true) && $input->getOption('only-missing') + ) { + continue; + } + + foreach ($fallbackCatalogues as $fallbackCatalogue) { + if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) { + $states[] = self::MESSAGE_EQUALS_FALLBACK; + + $exitCode |= self::EXIT_CODE_FALLBACK; + + break; + } + } + + $row = [$this->formatStates($states), $domain, $this->formatId($messageId), $this->sanitizeString($value)]; + foreach ($fallbackCatalogues as $fallbackCatalogue) { + $row[] = $this->sanitizeString($fallbackCatalogue->get($messageId, $domain)); + } + + $rows[] = $row; + } + } + + $io->table($headers, $rows); + + return $exitCode; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('locale')) { + $suggestions->suggestValues($this->enabledLocales); + + return; + } + + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + + if ($input->mustSuggestArgumentValuesFor('bundle')) { + $availableBundles = []; + foreach ($kernel->getBundles() as $bundle) { + $availableBundles[] = $bundle->getName(); + + if ($extension = $bundle->getContainerExtension()) { + $availableBundles[] = $extension->getAlias(); + } + } + + $suggestions->suggestValues($availableBundles); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domain')) { + $locale = $input->getArgument('locale'); + + $mergeOperation = new MergeOperation( + $this->extractMessages($locale, $this->getRootCodePaths($kernel)), + $this->loadCurrentMessages($locale, $this->getRootTransPaths()) + ); + + $suggestions->suggestValues($mergeOperation->getDomains()); + } + } + + private function formatState(int $state): string + { + if (self::MESSAGE_MISSING === $state) { + return ' missing '; + } + + if (self::MESSAGE_UNUSED === $state) { + return ' unused '; + } + + if (self::MESSAGE_EQUALS_FALLBACK === $state) { + return ' fallback '; + } + + return $state; + } + + private function formatStates(array $states): string + { + $result = []; + foreach ($states as $state) { + $result[] = $this->formatState($state); + } + + return implode(' ', $result); + } + + private function formatId(string $id): string + { + return sprintf('%s', $id); + } + + private function sanitizeString(string $string, int $length = 40): string + { + $string = trim(preg_replace('/\s+/', ' ', $string)); + + if (false !== $encoding = mb_detect_encoding($string, null, true)) { + if (mb_strlen($string, $encoding) > $length) { + return mb_substr($string, 0, $length - 3, $encoding).'...'; + } + } elseif (\strlen($string) > $length) { + return substr($string, 0, $length - 3).'...'; + } + + return $string; + } + + private function extractMessages(string $locale, array $transPaths): MessageCatalogue + { + $extractedCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + if (is_dir($path) || is_file($path)) { + $this->extractor->extract($path, $extractedCatalogue); + } + } + + return $extractedCatalogue; + } + + private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue + { + $currentCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + if (is_dir($path)) { + $this->reader->read($path, $currentCatalogue); + } + } + + return $currentCatalogue; + } + + /** + * @return MessageCatalogue[] + */ + private function loadFallbackCatalogues(string $locale, array $transPaths): array + { + $fallbackCatalogues = []; + if ($this->translator instanceof Translator || $this->translator instanceof DataCollectorTranslator || $this->translator instanceof LoggingTranslator) { + foreach ($this->translator->getFallbackLocales() as $fallbackLocale) { + if ($fallbackLocale === $locale) { + continue; + } + + $fallbackCatalogue = new MessageCatalogue($fallbackLocale); + foreach ($transPaths as $path) { + if (is_dir($path)) { + $this->reader->read($path, $fallbackCatalogue); + } + } + $fallbackCatalogues[] = $fallbackCatalogue; + } + } + + return $fallbackCatalogues; + } + + private function getRootTransPaths(): array + { + $transPaths = $this->transPaths; + if ($this->defaultTransPath) { + $transPaths[] = $this->defaultTransPath; + } + + return $transPaths; + } + + private function getRootCodePaths(KernelInterface $kernel): array + { + $codePaths = $this->codePaths; + $codePaths[] = $kernel->getProjectDir().'/src'; + if ($this->defaultViewsPath) { + $codePaths[] = $this->defaultViewsPath; + } + + return $codePaths; + } +} diff --git a/vendor/symfony/framework-bundle/Command/TranslationUpdateCommand.php b/vendor/symfony/framework-bundle/Command/TranslationUpdateCommand.php new file mode 100644 index 0000000..1a883f8 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/TranslationUpdateCommand.php @@ -0,0 +1,435 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Translation\Catalogue\MergeOperation; +use Symfony\Component\Translation\Catalogue\TargetOperation; +use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\Writer\TranslationWriterInterface; + +/** + * A command that parses templates to extract translation messages and adds them + * into the translation files. + * + * @author Michel Salib + * + * @final + */ +#[AsCommand(name: 'translation:extract', description: 'Extract missing translations keys from code to translation files')] +class TranslationUpdateCommand extends Command +{ + private const ASC = 'asc'; + private const DESC = 'desc'; + private const SORT_ORDERS = [self::ASC, self::DESC]; + private const FORMATS = [ + 'xlf12' => ['xlf', '1.2'], + 'xlf20' => ['xlf', '2.0'], + ]; + + public function __construct( + private TranslationWriterInterface $writer, + private TranslationReaderInterface $reader, + private ExtractorInterface $extractor, + private string $defaultLocale, + private ?string $defaultTransPath = null, + private ?string $defaultViewsPath = null, + private array $transPaths = [], + private array $codePaths = [], + private array $enabledLocales = [], + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), + new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'), + new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'), + new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf12'), + new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'), + new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'), + new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'), + new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'), + new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically (only works with --dump-messages)', 'asc'), + new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command extracts translation strings from templates +of a given bundle or the default translations directory. It can display them or merge +the new ones into the translation files. + +When new translation strings are found it can automatically add a prefix to the translation +message. + +Example running against a Bundle (AcmeBundle) + + php %command.full_name% --dump-messages en AcmeBundle + php %command.full_name% --force --prefix="new_" fr AcmeBundle + +Example running against default messages directory + + php %command.full_name% --dump-messages en + php %command.full_name% --force --prefix="new_" fr + +You can sort the output with the --sort flag: + + php %command.full_name% --dump-messages --sort=asc en AcmeBundle + php %command.full_name% --dump-messages --sort=desc fr + +You can dump a tree-like structure using the yaml format with --as-tree flag: + + php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io; + + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + // check presence of force or dump-message + if (true !== $input->getOption('force') && true !== $input->getOption('dump-messages')) { + $errorIo->error('You must choose one of --force or --dump-messages'); + + return 1; + } + + $format = $input->getOption('format'); + $xliffVersion = '1.2'; + + if (\array_key_exists($format, self::FORMATS)) { + [$format, $xliffVersion] = self::FORMATS[$format]; + } + + // check format + $supportedFormats = $this->writer->getFormats(); + if (!\in_array($format, $supportedFormats, true)) { + $errorIo->error(['Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).', xlf12 and xlf20.']); + + return 1; + } + + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + + // Define Root Paths + $transPaths = $this->getRootTransPaths(); + $codePaths = $this->getRootCodePaths($kernel); + + $currentName = 'default directory'; + + // Override with provided Bundle info + if (null !== $input->getArgument('bundle')) { + try { + $foundBundle = $kernel->getBundle($input->getArgument('bundle')); + $bundleDir = $foundBundle->getPath(); + $transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations']; + $codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates']; + if ($this->defaultTransPath) { + $transPaths[] = $this->defaultTransPath; + } + if ($this->defaultViewsPath) { + $codePaths[] = $this->defaultViewsPath; + } + $currentName = $foundBundle->getName(); + } catch (\InvalidArgumentException) { + // such a bundle does not exist, so treat the argument as path + $path = $input->getArgument('bundle'); + + $transPaths = [$path.'/translations']; + $codePaths = [$path.'/templates']; + + if (!is_dir($transPaths[0])) { + throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); + } + } + } + + $io->title('Translation Messages Extractor and Dumper'); + $io->comment(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); + + $io->comment('Parsing templates...'); + $extractedCatalogue = $this->extractMessages($input->getArgument('locale'), $codePaths, $input->getOption('prefix')); + + $io->comment('Loading translation files...'); + $currentCatalogue = $this->loadCurrentMessages($input->getArgument('locale'), $transPaths); + + if (null !== $domain = $input->getOption('domain')) { + $currentCatalogue = $this->filterCatalogue($currentCatalogue, $domain); + $extractedCatalogue = $this->filterCatalogue($extractedCatalogue, $domain); + } + + // process catalogues + $operation = $input->getOption('clean') + ? new TargetOperation($currentCatalogue, $extractedCatalogue) + : new MergeOperation($currentCatalogue, $extractedCatalogue); + + // Exit if no messages found. + if (!\count($operation->getDomains())) { + $errorIo->warning('No translation messages were found.'); + + return 0; + } + + $resultMessage = 'Translation files were successfully updated'; + + $operation->moveMessagesToIntlDomainsIfPossible('new'); + + // show compiled list of messages + if (true === $input->getOption('dump-messages')) { + $extractedMessagesCount = 0; + $io->newLine(); + foreach ($operation->getDomains() as $domain) { + $newKeys = array_keys($operation->getNewMessages($domain)); + $allKeys = array_keys($operation->getMessages($domain)); + + $list = array_merge( + array_diff($allKeys, $newKeys), + array_map(fn ($id) => sprintf('%s', $id), $newKeys), + array_map(fn ($id) => sprintf('%s', $id), array_keys($operation->getObsoleteMessages($domain))) + ); + + $domainMessagesCount = \count($list); + + if ($sort = $input->getOption('sort')) { + $sort = strtolower($sort); + if (!\in_array($sort, self::SORT_ORDERS, true)) { + $errorIo->error(['Wrong sort order', 'Supported formats are: '.implode(', ', self::SORT_ORDERS).'.']); + + return 1; + } + + if (self::DESC === $sort) { + rsort($list); + } else { + sort($list); + } + } + + $io->section(sprintf('Messages extracted for domain "%s" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : '')); + $io->listing($list); + + $extractedMessagesCount += $domainMessagesCount; + } + + if ('xlf' === $format) { + $io->comment(sprintf('Xliff output version is %s', $xliffVersion)); + } + + $resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was'); + } + + // save the files + if (true === $input->getOption('force')) { + $io->comment('Writing files...'); + + $bundleTransPath = false; + foreach ($transPaths as $path) { + if (is_dir($path)) { + $bundleTransPath = $path; + } + } + + if (!$bundleTransPath) { + $bundleTransPath = end($transPaths); + } + + $this->writer->write($operation->getResult(), $format, ['path' => $bundleTransPath, 'default_locale' => $this->defaultLocale, 'xliff_version' => $xliffVersion, 'as_tree' => $input->getOption('as-tree'), 'inline' => $input->getOption('as-tree') ?? 0]); + + if (true === $input->getOption('dump-messages')) { + $resultMessage .= ' and translation files were updated'; + } + } + + $io->success($resultMessage.'.'); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('locale')) { + $suggestions->suggestValues($this->enabledLocales); + + return; + } + + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + if ($input->mustSuggestArgumentValuesFor('bundle')) { + $bundles = []; + + foreach ($kernel->getBundles() as $bundle) { + $bundles[] = $bundle->getName(); + if ($bundle->getContainerExtension()) { + $bundles[] = $bundle->getContainerExtension()->getAlias(); + } + } + + $suggestions->suggestValues($bundles); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(array_merge( + $this->writer->getFormats(), + array_keys(self::FORMATS) + )); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domain') && $locale = $input->getArgument('locale')) { + $extractedCatalogue = $this->extractMessages($locale, $this->getRootCodePaths($kernel), $input->getOption('prefix')); + + $currentCatalogue = $this->loadCurrentMessages($locale, $this->getRootTransPaths()); + + // process catalogues + $operation = $input->getOption('clean') + ? new TargetOperation($currentCatalogue, $extractedCatalogue) + : new MergeOperation($currentCatalogue, $extractedCatalogue); + + $suggestions->suggestValues($operation->getDomains()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('sort')) { + $suggestions->suggestValues(self::SORT_ORDERS); + } + } + + private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue + { + $filteredCatalogue = new MessageCatalogue($catalogue->getLocale()); + + // extract intl-icu messages only + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + if ($intlMessages = $catalogue->all($intlDomain)) { + $filteredCatalogue->add($intlMessages, $intlDomain); + } + + // extract all messages and subtract intl-icu messages + if ($messages = array_diff($catalogue->all($domain), $intlMessages)) { + $filteredCatalogue->add($messages, $domain); + } + foreach ($catalogue->getResources() as $resource) { + $filteredCatalogue->addResource($resource); + } + + if ($metadata = $catalogue->getMetadata('', $intlDomain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $intlDomain); + } + } + + if ($metadata = $catalogue->getMetadata('', $domain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $domain); + } + } + + return $filteredCatalogue; + } + + private function extractMessages(string $locale, array $transPaths, string $prefix): MessageCatalogue + { + $extractedCatalogue = new MessageCatalogue($locale); + $this->extractor->setPrefix($prefix); + $transPaths = $this->filterDuplicateTransPaths($transPaths); + foreach ($transPaths as $path) { + if (is_dir($path) || is_file($path)) { + $this->extractor->extract($path, $extractedCatalogue); + } + } + + return $extractedCatalogue; + } + + private function filterDuplicateTransPaths(array $transPaths): array + { + $transPaths = array_filter(array_map('realpath', $transPaths)); + + sort($transPaths); + + $filteredPaths = []; + + foreach ($transPaths as $path) { + foreach ($filteredPaths as $filteredPath) { + if (str_starts_with($path, $filteredPath.\DIRECTORY_SEPARATOR)) { + continue 2; + } + } + + $filteredPaths[] = $path; + } + + return $filteredPaths; + } + + private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue + { + $currentCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + if (is_dir($path)) { + $this->reader->read($path, $currentCatalogue); + } + } + + return $currentCatalogue; + } + + private function getRootTransPaths(): array + { + $transPaths = $this->transPaths; + if ($this->defaultTransPath) { + $transPaths[] = $this->defaultTransPath; + } + + return $transPaths; + } + + private function getRootCodePaths(KernelInterface $kernel): array + { + $codePaths = $this->codePaths; + $codePaths[] = $kernel->getProjectDir().'/src'; + if ($this->defaultViewsPath) { + $codePaths[] = $this->defaultViewsPath; + } + + return $codePaths; + } +} diff --git a/vendor/symfony/framework-bundle/Command/WorkflowDumpCommand.php b/vendor/symfony/framework-bundle/Command/WorkflowDumpCommand.php new file mode 100644 index 0000000..d732305 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/WorkflowDumpCommand.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Workflow\Dumper\GraphvizDumper; +use Symfony\Component\Workflow\Dumper\MermaidDumper; +use Symfony\Component\Workflow\Dumper\PlantUmlDumper; +use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\StateMachine; + +/** + * @author Grégoire Pineau + * + * @final + */ +#[AsCommand(name: 'workflow:dump', description: 'Dump a workflow')] +class WorkflowDumpCommand extends Command +{ + private const DUMP_FORMAT_OPTIONS = [ + 'puml', + 'mermaid', + 'dot', + ]; + + public function __construct( + private ServiceLocator $workflows, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'), + new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'), + new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'), + new InputOption('with-metadata', null, InputOption::VALUE_NONE, 'Include the workflow\'s metadata in the dumped graph', null), + new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command dumps the graphical representation of a +workflow in different formats + +DOT: %command.full_name% | dot -Tpng > workflow.png +PUML: %command.full_name% --dump-format=puml | java -jar plantuml.jar -p > workflow.png +MERMAID: %command.full_name% --dump-format=mermaid | mmdc -o workflow.svg +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $workflowName = $input->getArgument('name'); + + if (!$this->workflows->has($workflowName)) { + throw new InvalidArgumentException(sprintf('The workflow named "%s" cannot be found.', $workflowName)); + } + $workflow = $this->workflows->get($workflowName); + $type = $workflow instanceof StateMachine ? 'state_machine' : 'workflow'; + $definition = $workflow->getDefinition(); + + switch ($input->getOption('dump-format')) { + case 'puml': + $transitionType = 'workflow' === $type ? PlantUmlDumper::WORKFLOW_TRANSITION : PlantUmlDumper::STATEMACHINE_TRANSITION; + $dumper = new PlantUmlDumper($transitionType); + break; + + case 'mermaid': + $transitionType = 'workflow' === $type ? MermaidDumper::TRANSITION_TYPE_WORKFLOW : MermaidDumper::TRANSITION_TYPE_STATEMACHINE; + $dumper = new MermaidDumper($transitionType); + break; + + case 'dot': + default: + $dumper = ('workflow' === $type) ? new GraphvizDumper() : new StateMachineGraphvizDumper(); + } + + $marking = new Marking(); + + foreach ($input->getArgument('marking') as $place) { + $marking->mark($place); + } + + $options = [ + 'name' => $workflowName, + 'with-metadata' => $input->getOption('with-metadata'), + 'nofooter' => true, + 'label' => $input->getOption('label'), + ]; + $output->writeln($dumper->dump($definition, $marking, $options)); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->workflows->getProvidedServices())); + } + + if ($input->mustSuggestOptionValuesFor('dump-format')) { + $suggestions->suggestValues(self::DUMP_FORMAT_OPTIONS); + } + } +} diff --git a/vendor/symfony/framework-bundle/Command/XliffLintCommand.php b/vendor/symfony/framework-bundle/Command/XliffLintCommand.php new file mode 100644 index 0000000..5b094f1 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/XliffLintCommand.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand; + +/** + * Validates XLIFF files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + * @author Javier Eguiluz + * + * @final + */ +#[AsCommand(name: 'lint:xliff', description: 'Lint an XLIFF file and outputs encountered errors')] +class XliffLintCommand extends BaseLintCommand +{ + public function __construct() + { + $directoryIteratorProvider = function ($directory, $default) { + if (!is_dir($directory)) { + $directory = $this->getApplication()->getKernel()->locateResource($directory); + } + + return $default($directory); + }; + + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); + + parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); + } + + protected function configure(): void + { + parent::configure(); + + $this->setHelp($this->getHelp().<<<'EOF' + +Or find all files in a bundle: + + php %command.full_name% @AcmeDemoBundle + +EOF + ); + } +} diff --git a/vendor/symfony/framework-bundle/Command/YamlLintCommand.php b/vendor/symfony/framework-bundle/Command/YamlLintCommand.php new file mode 100644 index 0000000..1413908 --- /dev/null +++ b/vendor/symfony/framework-bundle/Command/YamlLintCommand.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + * + * @final + */ +#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')] +class YamlLintCommand extends BaseLintCommand +{ + public function __construct() + { + $directoryIteratorProvider = function ($directory, $default) { + if (!is_dir($directory)) { + $directory = $this->getApplication()->getKernel()->locateResource($directory); + } + + return $default($directory); + }; + + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); + + parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); + } + + protected function configure(): void + { + parent::configure(); + + $this->setHelp($this->getHelp().<<<'EOF' + +Or find all files in a bundle: + + php %command.full_name% @AcmeDemoBundle + +EOF + ); + } +} diff --git a/vendor/symfony/framework-bundle/Console/Application.php b/vendor/symfony/framework-bundle/Console/Application.php new file mode 100644 index 0000000..1c41849 --- /dev/null +++ b/vendor/symfony/framework-bundle/Console/Application.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console; + +use Symfony\Component\Console\Application as BaseApplication; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Command\TraceableCommand; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * @author Fabien Potencier + */ +class Application extends BaseApplication +{ + private bool $commandsRegistered = false; + private array $registrationErrors = []; + + public function __construct( + private KernelInterface $kernel, + ) { + parent::__construct('Symfony', Kernel::VERSION); + + $inputDefinition = $this->getDefinition(); + $inputDefinition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $kernel->getEnvironment())); + $inputDefinition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switch off debug mode.')); + $inputDefinition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Enables profiling (requires debug).')); + } + + /** + * Gets the Kernel associated with this Console. + */ + public function getKernel(): KernelInterface + { + return $this->kernel; + } + + public function reset(): void + { + if ($this->kernel->getContainer()->has('services_resetter')) { + $this->kernel->getContainer()->get('services_resetter')->reset(); + } + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output): int + { + $this->registerCommands(); + + if ($this->registrationErrors) { + $this->renderRegistrationErrors($input, $output); + } + + $this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher')); + + return parent::doRun($input, $output); + } + + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int + { + $requestStack = null; + $renderRegistrationErrors = true; + + if (!$command instanceof ListCommand) { + if ($this->registrationErrors) { + $this->renderRegistrationErrors($input, $output); + $this->registrationErrors = []; + $renderRegistrationErrors = false; + } + } + + if ($input->hasParameterOption('--profile')) { + $container = $this->kernel->getContainer(); + + if (!$this->kernel->isDebug()) { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('Debug mode should be enabled when the "--profile" option is used.'); + } elseif (!$container->has('debug.stopwatch')) { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('The "--profile" option needs the Stopwatch component. Try running "composer require symfony/stopwatch".'); + } elseif (!$container->has('.virtual_request_stack')) { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('The "--profile" option needs the profiler integration. Try enabling the "framework.profiler" option.'); + } else { + $command = new TraceableCommand($command, $container->get('debug.stopwatch')); + + $requestStack = $container->get('.virtual_request_stack'); + $requestStack->push(new CliRequest($command)); + } + } + + try { + $returnCode = parent::doRunCommand($command, $input, $output); + } finally { + $requestStack?->pop(); + } + + if ($renderRegistrationErrors && $this->registrationErrors) { + $this->renderRegistrationErrors($input, $output); + $this->registrationErrors = []; + } + + return $returnCode; + } + + public function find(string $name): Command + { + $this->registerCommands(); + + return parent::find($name); + } + + public function get(string $name): Command + { + $this->registerCommands(); + + return parent::get($name); + } + + public function all(?string $namespace = null): array + { + $this->registerCommands(); + + return parent::all($namespace); + } + + public function getLongVersion(): string + { + return parent::getLongVersion().sprintf(' (env: %s, debug: %s)', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); + } + + public function add(Command $command): ?Command + { + $this->registerCommands(); + + return parent::add($command); + } + + protected function registerCommands(): void + { + if ($this->commandsRegistered) { + return; + } + + $this->commandsRegistered = true; + + $this->kernel->boot(); + + $container = $this->kernel->getContainer(); + + foreach ($this->kernel->getBundles() as $bundle) { + if ($bundle instanceof Bundle) { + try { + $bundle->registerCommands($this); + } catch (\Throwable $e) { + $this->registrationErrors[] = $e; + } + } + } + + if ($container->has('console.command_loader')) { + $this->setCommandLoader($container->get('console.command_loader')); + } + + if ($container->hasParameter('console.command.ids')) { + $lazyCommandIds = $container->hasParameter('console.lazy_command.ids') ? $container->getParameter('console.lazy_command.ids') : []; + foreach ($container->getParameter('console.command.ids') as $id) { + if (!isset($lazyCommandIds[$id])) { + try { + $this->add($container->get($id)); + } catch (\Throwable $e) { + $this->registrationErrors[] = $e; + } + } + } + } + } + + private function renderRegistrationErrors(InputInterface $input, OutputInterface $output): void + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('Some commands could not be registered:'); + + foreach ($this->registrationErrors as $error) { + $this->doRenderThrowable($error, $output); + } + } +} diff --git a/vendor/symfony/framework-bundle/Console/Descriptor/Descriptor.php b/vendor/symfony/framework-bundle/Console/Descriptor/Descriptor.php new file mode 100644 index 0000000..8541f71 --- /dev/null +++ b/vendor/symfony/framework-bundle/Console/Descriptor/Descriptor.php @@ -0,0 +1,363 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + protected OutputInterface $output; + + public function describe(OutputInterface $output, mixed $object, array $options = []): void + { + $this->output = $output; + + if ($object instanceof ContainerBuilder) { + (new AnalyzeServiceReferencesPass(false, false))->process($object); + } + + $deprecatedParameters = []; + if ($object instanceof ContainerBuilder && isset($options['parameter']) && ($parameterBag = $object->getParameterBag()) instanceof ParameterBag) { + $deprecatedParameters = $parameterBag->allDeprecated(); + } + + match (true) { + $object instanceof RouteCollection => $this->describeRouteCollection($object, $options), + $object instanceof Route => $this->describeRoute($object, $options), + $object instanceof ParameterBag => $this->describeContainerParameters($object, $options), + $object instanceof ContainerBuilder && !empty($options['env-vars']) => $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options), + $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by'] => $this->describeContainerTags($object, $options), + $object instanceof ContainerBuilder && isset($options['id']) => $this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options, $object), + $object instanceof ContainerBuilder && isset($options['parameter']) => $this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $deprecatedParameters[$options['parameter']] ?? null, $options), + $object instanceof ContainerBuilder && isset($options['deprecations']) => $this->describeContainerDeprecations($object, $options), + $object instanceof ContainerBuilder => $this->describeContainerServices($object, $options), + $object instanceof Definition => $this->describeContainerDefinition($object, $options), + $object instanceof Alias => $this->describeContainerAlias($object, $options), + $object instanceof EventDispatcherInterface => $this->describeEventDispatcherListeners($object, $options), + \is_callable($object) => $this->describeCallable($object, $options), + default => throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))), + }; + + if ($object instanceof ContainerBuilder) { + $object->getCompiler()->getServiceReferenceGraph()->clear(); + } + } + + protected function getOutput(): OutputInterface + { + return $this->output; + } + + protected function write(string $content, bool $decorated = false): void + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + abstract protected function describeRouteCollection(RouteCollection $routes, array $options = []): void; + + abstract protected function describeRoute(Route $route, array $options = []): void; + + abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void; + + abstract protected function describeContainerTags(ContainerBuilder $container, array $options = []): void; + + /** + * Describes a container service by its name. + * + * Common options are: + * * name: name of described service + * + * @param Definition|Alias|object $service + */ + abstract protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void; + + /** + * Describes container services. + * + * Common options are: + * * tag: filters described services by given tag + */ + abstract protected function describeContainerServices(ContainerBuilder $container, array $options = []): void; + + abstract protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void; + + abstract protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void; + + abstract protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void; + + abstract protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void; + + abstract protected function describeContainerEnvVars(array $envs, array $options = []): void; + + /** + * Describes event dispatcher listeners. + * + * Common options are: + * * name: name of listened event + */ + abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void; + + abstract protected function describeCallable(mixed $callable, array $options = []): void; + + protected function formatValue(mixed $value): string + { + if ($value instanceof \UnitEnum) { + return ltrim(var_export($value, true), '\\'); + } + + if (\is_object($value)) { + return sprintf('object(%s)', $value::class); + } + + if (\is_string($value)) { + return $value; + } + + return preg_replace("/\n\s*/s", '', var_export($value, true)); + } + + protected function formatParameter(mixed $value): string + { + if ($value instanceof \UnitEnum) { + return ltrim(var_export($value, true), '\\'); + } + + // Recursively search for enum values, so we can replace it + // before json_encode (which will not display anything for \UnitEnum otherwise) + if (\is_array($value)) { + array_walk_recursive($value, static function (&$value) { + if ($value instanceof \UnitEnum) { + $value = ltrim(var_export($value, true), '\\'); + } + }); + } + + if (\is_bool($value) || \is_array($value) || (null === $value)) { + $jsonString = json_encode($value); + + if (preg_match('/^(.{60})./us', $jsonString, $matches)) { + return $matches[1].'...'; + } + + return $jsonString; + } + + return (string) $value; + } + + protected function resolveServiceDefinition(ContainerBuilder $container, string $serviceId): mixed + { + if ($container->hasDefinition($serviceId)) { + return $container->getDefinition($serviceId); + } + + // Some service IDs don't have a Definition, they're aliases + if ($container->hasAlias($serviceId)) { + return $container->getAlias($serviceId); + } + + if ('service_container' === $serviceId) { + return (new Definition(ContainerInterface::class))->setPublic(true)->setSynthetic(true); + } + + // the service has been injected in some special way, just return the service + return $container->get($serviceId); + } + + protected function findDefinitionsByTag(ContainerBuilder $container, bool $showHidden): array + { + $definitions = []; + $tags = $container->findTags(); + asort($tags); + + foreach ($tags as $tag) { + foreach ($container->findTaggedServiceIds($tag) as $serviceId => $attributes) { + $definition = $this->resolveServiceDefinition($container, $serviceId); + + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + continue; + } + + if (!isset($definitions[$tag])) { + $definitions[$tag] = []; + } + + $definitions[$tag][$serviceId] = $definition; + } + } + + return $definitions; + } + + protected function sortParameters(ParameterBag $parameters): array + { + $parameters = $parameters->all(); + ksort($parameters); + + return $parameters; + } + + protected function sortServiceIds(array $serviceIds): array + { + asort($serviceIds); + + return $serviceIds; + } + + protected function sortTaggedServicesByPriority(array $services): array + { + $maxPriority = []; + foreach ($services as $service => $tags) { + $maxPriority[$service] = \PHP_INT_MIN; + foreach ($tags as $tag) { + $currentPriority = $tag['priority'] ?? 0; + if ($maxPriority[$service] < $currentPriority) { + $maxPriority[$service] = $currentPriority; + } + } + } + uasort($maxPriority, fn ($a, $b) => $b <=> $a); + + return array_keys($maxPriority); + } + + protected function sortTagsByPriority(array $tags): array + { + $sortedTags = []; + foreach ($tags as $tagName => $tag) { + $sortedTags[$tagName] = $this->sortByPriority($tag); + } + + return $sortedTags; + } + + protected function sortByPriority(array $tag): array + { + usort($tag, fn ($a, $b) => ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0)); + + return $tag; + } + + /** + * @return array + */ + protected function getReverseAliases(RouteCollection $routes): array + { + $reverseAliases = []; + foreach ($routes->getAliases() as $name => $alias) { + $reverseAliases[$alias->getId()][] = $name; + } + + return $reverseAliases; + } + + public static function getClassDescription(string $class, ?string &$resolvedClass = null): string + { + $resolvedClass = $class; + try { + $resource = new ClassExistenceResource($class, false); + + // isFresh() will explode ONLY if a parent class/trait does not exist + $resource->isFresh(0); + + $r = new \ReflectionClass($class); + $resolvedClass = $r->name; + + if ($docComment = $r->getDocComment()) { + $docComment = preg_split('#\n\s*\*\s*[\n@]#', substr($docComment, 3, -2), 2)[0]; + + return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment)); + } + } catch (\ReflectionException) { + } + + return ''; + } + + private function getContainerEnvVars(ContainerBuilder $container): array + { + if (!$container->hasParameter('debug.container.dump')) { + return []; + } + + if (!$container->getParameter('debug.container.dump') || !is_file($container->getParameter('debug.container.dump'))) { + return []; + } + + $file = file_get_contents($container->getParameter('debug.container.dump')); + preg_match_all('{%env\(((?:\w++:)*+\w++)\)%}', $file, $envVars); + $envVars = array_unique($envVars[1]); + + $bag = $container->getParameterBag(); + $getDefaultParameter = fn (string $name) => parent::get($name); + $getDefaultParameter = $getDefaultParameter->bindTo($bag, $bag::class); + + $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); + + $envs = []; + + foreach ($envVars as $env) { + $processor = 'string'; + if (false !== $i = strrpos($name = $env, ':')) { + $name = substr($env, $i + 1); + $processor = substr($env, 0, $i); + } + $defaultValue = ($hasDefault = $container->hasParameter("env($name)")) ? $getDefaultParameter("env($name)") : null; + if (false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name))) { + $runtimeValue = null; + } + $processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null; + $envs["$name$processor"] = [ + 'name' => $name, + 'processor' => $processor, + 'default_available' => $hasDefault, + 'default_value' => $defaultValue, + 'runtime_available' => $hasRuntime, + 'runtime_value' => $runtimeValue, + 'processed_value' => $processedValue, + ]; + } + ksort($envs); + + return array_values($envs); + } + + protected function getServiceEdges(ContainerBuilder $container, string $serviceId): array + { + try { + return array_values(array_unique(array_map( + fn (ServiceReferenceGraphEdge $edge) => $edge->getSourceNode()->getId(), + $container->getCompiler()->getServiceReferenceGraph()->getNode($serviceId)->getInEdges() + ))); + } catch (InvalidArgumentException $exception) { + return []; + } + } +} diff --git a/vendor/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php b/vendor/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000..88cf416 --- /dev/null +++ b/vendor/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php @@ -0,0 +1,457 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void + { + $data = []; + foreach ($routes->all() as $name => $route) { + $data[$name] = $this->getRouteData($route); + if (($showAliases ??= $options['show_aliases'] ?? false) && $aliases = ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []) { + $data[$name]['aliases'] = $aliases; + } + } + + $this->writeData($data, $options); + } + + protected function describeRoute(Route $route, array $options = []): void + { + $this->writeData($this->getRouteData($route), $options); + } + + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void + { + $this->writeData($this->sortParameters($parameters), $options); + } + + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void + { + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + $data = []; + + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { + $data[$tag] = []; + foreach ($definitions as $definition) { + $data[$tag][] = $this->getContainerDefinitionData($definition, true, false, $container, $options['id'] ?? null); + } + } + + $this->writeData($data, $options); + } + + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + if ($service instanceof Alias) { + $this->describeContainerAlias($service, $options, $container); + } elseif ($service instanceof Definition) { + $this->writeData($this->getContainerDefinitionData($service, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container, $options['id']), $options); + } else { + $this->writeData($service::class, $options); + } + } + + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void + { + $serviceIds = isset($options['tag']) && $options['tag'] + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($container->getServiceIds()); + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + $omitTags = isset($options['omit_tags']) && $options['omit_tags']; + $showArguments = isset($options['show_arguments']) && $options['show_arguments']; + $data = ['definitions' => [], 'aliases' => [], 'services' => []]; + + if (isset($options['filter'])) { + $serviceIds = array_filter($serviceIds, $options['filter']); + } + + foreach ($serviceIds as $serviceId) { + $service = $this->resolveServiceDefinition($container, $serviceId); + + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + continue; + } + + if ($service instanceof Alias) { + $data['aliases'][$serviceId] = $this->getContainerAliasData($service); + } elseif ($service instanceof Definition) { + if ($service->hasTag('container.excluded')) { + continue; + } + $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments, $container, $serviceId); + } else { + $data['services'][$serviceId] = $service::class; + } + } + + $this->writeData($data, $options); + } + + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void + { + $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container, $options['id'] ?? null), $options); + } + + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void + { + if (!$container) { + $this->writeData($this->getContainerAliasData($alias), $options); + + return; + } + + $this->writeData( + [$this->getContainerAliasData($alias), $this->getContainerDefinitionData($container->getDefinition((string) $alias), isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container, (string) $alias)], + array_merge($options, ['id' => (string) $alias]) + ); + } + + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void + { + $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options); + } + + protected function describeCallable(mixed $callable, array $options = []): void + { + $this->writeData($this->getCallableData($callable), $options); + } + + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void + { + $key = $options['parameter'] ?? ''; + $data = [$key => $parameter]; + + if ($deprecation) { + $data['_deprecation'] = sprintf('Since %s %s: %s', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))); + } + + $this->writeData($data, $options); + } + + protected function describeContainerEnvVars(array $envs, array $options = []): void + { + throw new LogicException('Using the JSON format to debug environment variables is not supported.'); + } + + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + + $formattedLogs = []; + $remainingCount = 0; + foreach ($logs as $log) { + $formattedLogs[] = [ + 'message' => $log['message'], + 'file' => $log['file'], + 'line' => $log['line'], + 'count' => $log['count'], + ]; + $remainingCount += $log['count']; + } + + $this->writeData(['remainingCount' => $remainingCount, 'deprecations' => $formattedLogs], $options); + } + + private function writeData(array $data, array $options): void + { + $flags = $options['json_encoding'] ?? 0; + + // Recursively search for enum values, so we can replace it + // before json_encode (which will not display anything for \UnitEnum otherwise) + array_walk_recursive($data, static function (&$value) { + if ($value instanceof \UnitEnum) { + $value = ltrim(var_export($value, true), '\\'); + } + }); + + $this->write(json_encode($data, $flags | \JSON_PRETTY_PRINT)."\n"); + } + + protected function getRouteData(Route $route): array + { + $data = [ + 'path' => $route->getPath(), + 'pathRegex' => $route->compile()->getRegex(), + 'host' => '' !== $route->getHost() ? $route->getHost() : 'ANY', + 'hostRegex' => '' !== $route->getHost() ? $route->compile()->getHostRegex() : '', + 'scheme' => $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY', + 'method' => $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY', + 'class' => $route::class, + 'defaults' => $route->getDefaults(), + 'requirements' => $route->getRequirements() ?: 'NO CUSTOM', + 'options' => $route->getOptions(), + ]; + + if ('' !== $route->getCondition()) { + $data['condition'] = $route->getCondition(); + } + + return $data; + } + + protected function sortParameters(ParameterBag $parameters): array + { + $sortedParameters = parent::sortParameters($parameters); + + if ($deprecated = $parameters->allDeprecated()) { + $deprecations = []; + + foreach ($deprecated as $parameter => $deprecation) { + $deprecations[$parameter] = sprintf('Since %s %s: %s', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))); + } + + $sortedParameters['_deprecations'] = $deprecations; + } + + return $sortedParameters; + } + + private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false, ?ContainerBuilder $container = null, ?string $id = null): array + { + $data = [ + 'class' => (string) $definition->getClass(), + 'public' => $definition->isPublic() && !$definition->isPrivate(), + 'synthetic' => $definition->isSynthetic(), + 'lazy' => $definition->isLazy(), + 'shared' => $definition->isShared(), + 'abstract' => $definition->isAbstract(), + 'autowire' => $definition->isAutowired(), + 'autoconfigure' => $definition->isAutoconfigured(), + ]; + + if ($definition->isDeprecated()) { + $data['deprecated'] = true; + $data['deprecation_message'] = $definition->getDeprecation($id)['message']; + } else { + $data['deprecated'] = false; + } + + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $data['description'] = $classDescription; + } + + if ($showArguments) { + $data['arguments'] = $this->describeValue($definition->getArguments(), $omitTags, $showArguments, $container, $id); + } + + $data['file'] = $definition->getFile(); + + if ($factory = $definition->getFactory()) { + if (\is_array($factory)) { + if ($factory[0] instanceof Reference) { + $data['factory_service'] = (string) $factory[0]; + } elseif ($factory[0] instanceof Definition) { + $data['factory_service'] = sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'class not configured'); + } else { + $data['factory_class'] = $factory[0]; + } + $data['factory_method'] = $factory[1]; + } else { + $data['factory_function'] = $factory; + } + } + + $calls = $definition->getMethodCalls(); + if (\count($calls) > 0) { + $data['calls'] = []; + foreach ($calls as $callData) { + $data['calls'][] = $callData[0]; + } + } + + if (!$omitTags) { + $data['tags'] = []; + foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) { + foreach ($tagData as $parameters) { + $data['tags'][] = ['name' => $tagName, 'parameters' => $parameters]; + } + } + } + + $data['usages'] = null !== $container && null !== $id ? $this->getServiceEdges($container, $id) : []; + + return $data; + } + + private function getContainerAliasData(Alias $alias): array + { + return [ + 'service' => (string) $alias, + 'public' => $alias->isPublic() && !$alias->isPrivate(), + ]; + } + + private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, array $options): array + { + $data = []; + $event = $options['event'] ?? null; + + if (null !== $event) { + foreach ($eventDispatcher->getListeners($event) as $listener) { + $l = $this->getCallableData($listener); + $l['priority'] = $eventDispatcher->getListenerPriority($event, $listener); + $data[] = $l; + } + } else { + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); + ksort($registeredListeners); + + foreach ($registeredListeners as $eventListened => $eventListeners) { + foreach ($eventListeners as $eventListener) { + $l = $this->getCallableData($eventListener); + $l['priority'] = $eventDispatcher->getListenerPriority($eventListened, $eventListener); + $data[$eventListened][] = $l; + } + } + } + + return $data; + } + + private function getCallableData(mixed $callable): array + { + $data = []; + + if (\is_array($callable)) { + $data['type'] = 'function'; + + if (\is_object($callable[0])) { + $data['name'] = $callable[1]; + $data['class'] = $callable[0]::class; + } else { + if (!str_starts_with($callable[1], 'parent::')) { + $data['name'] = $callable[1]; + $data['class'] = $callable[0]; + $data['static'] = true; + } else { + $data['name'] = substr($callable[1], 8); + $data['class'] = $callable[0]; + $data['static'] = true; + $data['parent'] = true; + } + } + + return $data; + } + + if (\is_string($callable)) { + $data['type'] = 'function'; + + if (!str_contains($callable, '::')) { + $data['name'] = $callable; + } else { + $callableParts = explode('::', $callable); + + $data['name'] = $callableParts[1]; + $data['class'] = $callableParts[0]; + $data['static'] = true; + } + + return $data; + } + + if ($callable instanceof \Closure) { + $data['type'] = 'closure'; + + $r = new \ReflectionFunction($callable); + if ($r->isAnonymous()) { + return $data; + } + $data['name'] = $r->name; + + if ($class = $r->getClosureCalledClass()) { + $data['class'] = $class->name; + if (!$r->getClosureThis()) { + $data['static'] = true; + } + } + + return $data; + } + + if (method_exists($callable, '__invoke')) { + $data['type'] = 'object'; + $data['name'] = $callable::class; + + return $data; + } + + throw new \InvalidArgumentException('Callable is not describable.'); + } + + private function describeValue($value, bool $omitTags, bool $showArguments, ?ContainerBuilder $container = null, ?string $id = null): mixed + { + if (\is_array($value)) { + $data = []; + foreach ($value as $k => $v) { + $data[$k] = $this->describeValue($v, $omitTags, $showArguments, $container, $id); + } + + return $data; + } + + if ($value instanceof ServiceClosureArgument) { + $value = $value->getValues()[0]; + } + + if ($value instanceof Reference) { + return [ + 'type' => 'service', + 'id' => (string) $value, + ]; + } + + if ($value instanceof AbstractArgument) { + return ['type' => 'abstract', 'text' => $value->getText()]; + } + + if ($value instanceof ArgumentInterface) { + return $this->describeValue($value->getValues(), $omitTags, $showArguments, $container, $id); + } + + if ($value instanceof Definition) { + return $this->getContainerDefinitionData($value, $omitTags, $showArguments, $container, $id); + } + + return $value; + } +} diff --git a/vendor/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000..7965990 --- /dev/null +++ b/vendor/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,451 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void + { + $first = true; + foreach ($routes->all() as $name => $route) { + if ($first) { + $first = false; + } else { + $this->write("\n\n"); + } + $this->describeRoute($route, ['name' => $name]); + if (($showAliases ??= $options['show_aliases'] ?? false) && $aliases = ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []) { + $this->write(sprintf("- Aliases: \n%s", implode("\n", array_map(static fn (string $alias): string => sprintf(' - %s', $alias), $aliases)))); + } + } + $this->write("\n"); + } + + protected function describeRoute(Route $route, array $options = []): void + { + $output = '- Path: '.$route->getPath() + ."\n".'- Path Regex: '.$route->compile()->getRegex() + ."\n".'- Host: '.('' !== $route->getHost() ? $route->getHost() : 'ANY') + ."\n".'- Host Regex: '.('' !== $route->getHost() ? $route->compile()->getHostRegex() : '') + ."\n".'- Scheme: '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY') + ."\n".'- Method: '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY') + ."\n".'- Class: '.$route::class + ."\n".'- Defaults: '.$this->formatRouterConfig($route->getDefaults()) + ."\n".'- Requirements: '.($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM') + ."\n".'- Options: '.$this->formatRouterConfig($route->getOptions()); + + if ('' !== $route->getCondition()) { + $output .= "\n".'- Condition: '.$route->getCondition(); + } + + $this->write(isset($options['name']) + ? $options['name']."\n".str_repeat('-', \strlen($options['name']))."\n\n".$output + : $output); + $this->write("\n"); + } + + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void + { + $deprecatedParameters = $parameters->allDeprecated(); + + $this->write("Container parameters\n====================\n"); + foreach ($this->sortParameters($parameters) as $key => $value) { + $this->write(sprintf( + "\n- `%s`: `%s`%s", + $key, + $this->formatParameter($value), + isset($deprecatedParameters[$key]) ? sprintf(' *Since %s %s: %s*', $deprecatedParameters[$key][0], $deprecatedParameters[$key][1], sprintf(...\array_slice($deprecatedParameters[$key], 2))) : '' + )); + } + } + + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void + { + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + $this->write("Container tags\n=============="); + + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { + $this->write("\n\n".$tag."\n".str_repeat('-', \strlen($tag))); + foreach ($definitions as $serviceId => $definition) { + $this->write("\n\n"); + $this->describeContainerDefinition($definition, ['omit_tags' => true, 'id' => $serviceId], $container); + } + } + } + + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + $childOptions = array_merge($options, ['id' => $options['id'], 'as_array' => true]); + + if ($service instanceof Alias) { + $this->describeContainerAlias($service, $childOptions, $container); + } elseif ($service instanceof Definition) { + $this->describeContainerDefinition($service, $childOptions, $container); + } else { + $this->write(sprintf('**`%s`:** `%s`', $options['id'], $service::class)); + } + } + + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + if (0 === \count($logs)) { + $this->write("## There are no deprecations in the logs!\n"); + + return; + } + + $formattedLogs = []; + $remainingCount = 0; + foreach ($logs as $log) { + $formattedLogs[] = sprintf("- %sx: \"%s\" in %s:%s\n", $log['count'], $log['message'], $log['file'], $log['line']); + $remainingCount += $log['count']; + } + + $this->write(sprintf("## Remaining deprecations (%s)\n\n", $remainingCount)); + foreach ($formattedLogs as $formattedLog) { + $this->write($formattedLog); + } + } + + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void + { + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + + $title = $showHidden ? 'Hidden services' : 'Services'; + if (isset($options['tag'])) { + $title .= ' with tag `'.$options['tag'].'`'; + } + $this->write($title."\n".str_repeat('=', \strlen($title))); + + $serviceIds = isset($options['tag']) && $options['tag'] + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($container->getServiceIds()); + $showArguments = isset($options['show_arguments']) && $options['show_arguments']; + $services = ['definitions' => [], 'aliases' => [], 'services' => []]; + + if (isset($options['filter'])) { + $serviceIds = array_filter($serviceIds, $options['filter']); + } + + foreach ($serviceIds as $serviceId) { + $service = $this->resolveServiceDefinition($container, $serviceId); + + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + continue; + } + + if ($service instanceof Alias) { + $services['aliases'][$serviceId] = $service; + } elseif ($service instanceof Definition) { + if ($service->hasTag('container.excluded')) { + continue; + } + $services['definitions'][$serviceId] = $service; + } else { + $services['services'][$serviceId] = $service; + } + } + + if (!empty($services['definitions'])) { + $this->write("\n\nDefinitions\n-----------\n"); + foreach ($services['definitions'] as $id => $service) { + $this->write("\n"); + $this->describeContainerDefinition($service, ['id' => $id, 'show_arguments' => $showArguments], $container); + } + } + + if (!empty($services['aliases'])) { + $this->write("\n\nAliases\n-------\n"); + foreach ($services['aliases'] as $id => $service) { + $this->write("\n"); + $this->describeContainerAlias($service, ['id' => $id]); + } + } + + if (!empty($services['services'])) { + $this->write("\n\nServices\n--------\n"); + foreach ($services['services'] as $id => $service) { + $this->write("\n"); + $this->write(sprintf('- `%s`: `%s`', $id, $service::class)); + } + } + } + + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void + { + $output = ''; + + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $output .= '- Description: `'.$classDescription.'`'."\n"; + } + + $output .= '- Class: `'.$definition->getClass().'`' + ."\n".'- Public: '.($definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no') + ."\n".'- Synthetic: '.($definition->isSynthetic() ? 'yes' : 'no') + ."\n".'- Lazy: '.($definition->isLazy() ? 'yes' : 'no') + ."\n".'- Shared: '.($definition->isShared() ? 'yes' : 'no') + ."\n".'- Abstract: '.($definition->isAbstract() ? 'yes' : 'no') + ."\n".'- Autowired: '.($definition->isAutowired() ? 'yes' : 'no') + ."\n".'- Autoconfigured: '.($definition->isAutoconfigured() ? 'yes' : 'no') + ; + + if ($definition->isDeprecated()) { + $output .= "\n".'- Deprecated: yes'; + $output .= "\n".'- Deprecation message: '.$definition->getDeprecation($options['id'])['message']; + } else { + $output .= "\n".'- Deprecated: no'; + } + + if (isset($options['show_arguments']) && $options['show_arguments']) { + $output .= "\n".'- Arguments: '.($definition->getArguments() ? 'yes' : 'no'); + } + + if ($definition->getFile()) { + $output .= "\n".'- File: `'.$definition->getFile().'`'; + } + + if ($factory = $definition->getFactory()) { + if (\is_array($factory)) { + if ($factory[0] instanceof Reference) { + $output .= "\n".'- Factory Service: `'.$factory[0].'`'; + } elseif ($factory[0] instanceof Definition) { + $output .= "\n".sprintf('- Factory Service: inline factory service (%s)', $factory[0]->getClass() ? sprintf('`%s`', $factory[0]->getClass()) : 'not configured'); + } else { + $output .= "\n".'- Factory Class: `'.$factory[0].'`'; + } + $output .= "\n".'- Factory Method: `'.$factory[1].'`'; + } else { + $output .= "\n".'- Factory Function: `'.$factory.'`'; + } + } + + $calls = $definition->getMethodCalls(); + foreach ($calls as $callData) { + $output .= "\n".'- Call: `'.$callData[0].'`'; + } + + if (!(isset($options['omit_tags']) && $options['omit_tags'])) { + foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) { + foreach ($tagData as $parameters) { + $output .= "\n".'- Tag: `'.$tagName.'`'; + foreach ($parameters as $name => $value) { + $output .= "\n".' - '.ucfirst($name).': '.(\is_array($value) ? $this->formatParameter($value) : $value); + } + } + } + } + + $inEdges = null !== $container && isset($options['id']) ? $this->getServiceEdges($container, $options['id']) : []; + $output .= "\n".'- Usages: '.($inEdges ? implode(', ', $inEdges) : 'none'); + + $this->write(isset($options['id']) ? sprintf("### %s\n\n%s\n", $options['id'], $output) : $output); + } + + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void + { + $output = '- Service: `'.$alias.'`' + ."\n".'- Public: '.($alias->isPublic() && !$alias->isPrivate() ? 'yes' : 'no'); + + if (!isset($options['id'])) { + $this->write($output); + + return; + } + + $this->write(sprintf("### %s\n\n%s\n", $options['id'], $output)); + + if (!$container) { + return; + } + + $this->write("\n"); + $this->describeContainerDefinition($container->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $container); + } + + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void + { + if (isset($options['parameter'])) { + $this->write(sprintf("%s\n%s\n\n%s%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter), $deprecation ? sprintf("\n\n*Since %s %s: %s*", $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))) : '')); + } else { + $this->write($parameter); + } + } + + protected function describeContainerEnvVars(array $envs, array $options = []): void + { + throw new LogicException('Using the markdown format to debug environment variables is not supported.'); + } + + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void + { + $event = $options['event'] ?? null; + $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; + + $title = 'Registered listeners'; + + if (null !== $dispatcherServiceName) { + $title .= sprintf(' of event dispatcher "%s"', $dispatcherServiceName); + } + + if (null !== $event) { + $title .= sprintf(' for event `%s` ordered by descending priority', $event); + $registeredListeners = $eventDispatcher->getListeners($event); + } else { + // Try to see if "events" exists + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); + } + + $this->write(sprintf('# %s', $title)."\n"); + + if (null !== $event) { + foreach ($registeredListeners as $order => $listener) { + $this->write("\n".sprintf('## Listener %d', $order + 1)."\n"); + $this->describeCallable($listener); + $this->write(sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($event, $listener))."\n"); + } + } else { + ksort($registeredListeners); + + foreach ($registeredListeners as $eventListened => $eventListeners) { + $this->write("\n".sprintf('## %s', $eventListened)."\n"); + + foreach ($eventListeners as $order => $eventListener) { + $this->write("\n".sprintf('### Listener %d', $order + 1)."\n"); + $this->describeCallable($eventListener); + $this->write(sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($eventListened, $eventListener))."\n"); + } + } + } + } + + protected function describeCallable(mixed $callable, array $options = []): void + { + $string = ''; + + if (\is_array($callable)) { + $string .= "\n- Type: `function`"; + + if (\is_object($callable[0])) { + $string .= "\n".sprintf('- Name: `%s`', $callable[1]); + $string .= "\n".sprintf('- Class: `%s`', $callable[0]::class); + } else { + if (!str_starts_with($callable[1], 'parent::')) { + $string .= "\n".sprintf('- Name: `%s`', $callable[1]); + $string .= "\n".sprintf('- Class: `%s`', $callable[0]); + $string .= "\n- Static: yes"; + } else { + $string .= "\n".sprintf('- Name: `%s`', substr($callable[1], 8)); + $string .= "\n".sprintf('- Class: `%s`', $callable[0]); + $string .= "\n- Static: yes"; + $string .= "\n- Parent: yes"; + } + } + + $this->write($string."\n"); + + return; + } + + if (\is_string($callable)) { + $string .= "\n- Type: `function`"; + + if (!str_contains($callable, '::')) { + $string .= "\n".sprintf('- Name: `%s`', $callable); + } else { + $callableParts = explode('::', $callable); + + $string .= "\n".sprintf('- Name: `%s`', $callableParts[1]); + $string .= "\n".sprintf('- Class: `%s`', $callableParts[0]); + $string .= "\n- Static: yes"; + } + + $this->write($string."\n"); + + return; + } + + if ($callable instanceof \Closure) { + $string .= "\n- Type: `closure`"; + + $r = new \ReflectionFunction($callable); + if ($r->isAnonymous()) { + $this->write($string."\n"); + + return; + } + $string .= "\n".sprintf('- Name: `%s`', $r->name); + + if ($class = $r->getClosureCalledClass()) { + $string .= "\n".sprintf('- Class: `%s`', $class->name); + if (!$r->getClosureThis()) { + $string .= "\n- Static: yes"; + } + } + + $this->write($string."\n"); + + return; + } + + if (method_exists($callable, '__invoke')) { + $string .= "\n- Type: `object`"; + $string .= "\n".sprintf('- Name: `%s`', $callable::class); + + $this->write($string."\n"); + + return; + } + + throw new \InvalidArgumentException('Callable is not describable.'); + } + + private function formatRouterConfig(array $array): string + { + if (!$array) { + return 'NONE'; + } + + $string = ''; + ksort($array); + foreach ($array as $name => $value) { + $string .= "\n".' - `'.$name.'`: '.$this->formatValue($value); + } + + return $string; + } +} diff --git a/vendor/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php b/vendor/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000..d728128 --- /dev/null +++ b/vendor/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php @@ -0,0 +1,674 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + public function __construct( + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { + } + + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void + { + $showControllers = isset($options['show_controllers']) && $options['show_controllers']; + + $tableHeaders = ['Name', 'Method', 'Scheme', 'Host', 'Path']; + if ($showControllers) { + $tableHeaders[] = 'Controller'; + } + + if ($showAliases = $options['show_aliases'] ?? false) { + $tableHeaders[] = 'Aliases'; + } + + $tableRows = []; + foreach ($routes->all() as $name => $route) { + $controller = $route->getDefault('_controller'); + + $row = [ + $name, + $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY', + $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY', + '' !== $route->getHost() ? $route->getHost() : 'ANY', + $this->formatControllerLink($controller, $route->getPath(), $options['container'] ?? null), + ]; + + if ($showControllers) { + $row[] = $controller ? $this->formatControllerLink($controller, $this->formatCallable($controller), $options['container'] ?? null) : ''; + } + + if ($showAliases) { + $row[] = implode('|', ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []); + } + + $tableRows[] = $row; + } + + if (isset($options['output'])) { + $options['output']->table($tableHeaders, $tableRows); + } else { + $table = new Table($this->getOutput()); + $table->setHeaders($tableHeaders)->setRows($tableRows); + $table->render(); + } + } + + protected function describeRoute(Route $route, array $options = []): void + { + $defaults = $route->getDefaults(); + if (isset($defaults['_controller'])) { + $defaults['_controller'] = $this->formatControllerLink($defaults['_controller'], $this->formatCallable($defaults['_controller']), $options['container'] ?? null); + } + + $tableHeaders = ['Property', 'Value']; + $tableRows = [ + ['Route Name', $options['name'] ?? ''], + ['Path', $route->getPath()], + ['Path Regex', $route->compile()->getRegex()], + ['Host', '' !== $route->getHost() ? $route->getHost() : 'ANY'], + ['Host Regex', '' !== $route->getHost() ? $route->compile()->getHostRegex() : ''], + ['Scheme', $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'], + ['Method', $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'], + ['Requirements', $route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM'], + ['Class', $route::class], + ['Defaults', $this->formatRouterConfig($defaults)], + ['Options', $this->formatRouterConfig($route->getOptions())], + ]; + + if ('' !== $route->getCondition()) { + $tableRows[] = ['Condition', $route->getCondition()]; + } + + $table = new Table($this->getOutput()); + $table->setHeaders($tableHeaders)->setRows($tableRows); + $table->render(); + } + + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void + { + $tableHeaders = ['Parameter', 'Value']; + + $deprecatedParameters = $parameters->allDeprecated(); + + $tableRows = []; + foreach ($this->sortParameters($parameters) as $parameter => $value) { + $tableRows[] = [$parameter, $this->formatParameter($value)]; + + if (isset($deprecatedParameters[$parameter])) { + $tableRows[] = [new TableCell( + sprintf('(Since %s %s: %s)', $deprecatedParameters[$parameter][0], $deprecatedParameters[$parameter][1], sprintf(...\array_slice($deprecatedParameters[$parameter], 2))), + ['colspan' => 2] + )]; + } + } + + $options['output']->title('Symfony Container Parameters'); + $options['output']->table($tableHeaders, $tableRows); + } + + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void + { + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + + if ($showHidden) { + $options['output']->title('Symfony Container Hidden Tags'); + } else { + $options['output']->title('Symfony Container Tags'); + } + + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { + $options['output']->section(sprintf('"%s" tag', $tag)); + $options['output']->listing(array_keys($definitions)); + } + } + + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + if ($service instanceof Alias) { + $this->describeContainerAlias($service, $options, $container); + } elseif ($service instanceof Definition) { + $this->describeContainerDefinition($service, $options, $container); + } else { + $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); + $options['output']->table( + ['Service ID', 'Class'], + [ + [$options['id'] ?? '-', $service::class], + ] + ); + } + } + + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void + { + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + $showTag = $options['tag'] ?? null; + + if ($showHidden) { + $title = 'Symfony Container Hidden Services'; + } else { + $title = 'Symfony Container Services'; + } + + if ($showTag) { + $title .= sprintf(' Tagged with "%s" Tag', $options['tag']); + } + + $options['output']->title($title); + + $serviceIds = isset($options['tag']) && $options['tag'] + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($container->getServiceIds()); + $maxTags = []; + + if (isset($options['filter'])) { + $serviceIds = array_filter($serviceIds, $options['filter']); + } + + foreach ($serviceIds as $key => $serviceId) { + $definition = $this->resolveServiceDefinition($container, $serviceId); + + // filter out hidden services unless shown explicitly + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + unset($serviceIds[$key]); + continue; + } + + if ($definition instanceof Definition) { + if ($definition->hasTag('container.excluded')) { + unset($serviceIds[$key]); + continue; + } + if ($showTag) { + $tags = $definition->getTag($showTag); + foreach ($tags as $tag) { + foreach ($tag as $key => $value) { + if (!isset($maxTags[$key])) { + $maxTags[$key] = \strlen($key); + } + if (\is_array($value)) { + $value = $this->formatParameter($value); + } + + if (\strlen($value) > $maxTags[$key]) { + $maxTags[$key] = \strlen($value); + } + } + } + } + } + } + + $tagsCount = \count($maxTags); + $tagsNames = array_keys($maxTags); + + $tableHeaders = array_merge(['Service ID'], $tagsNames, ['Class name']); + $tableRows = []; + $rawOutput = isset($options['raw_text']) && $options['raw_text']; + foreach ($serviceIds as $serviceId) { + $definition = $this->resolveServiceDefinition($container, $serviceId); + + $styledServiceId = $rawOutput ? $serviceId : sprintf('%s', OutputFormatter::escape($serviceId)); + if ($definition instanceof Definition) { + if ($showTag) { + foreach ($this->sortByPriority($definition->getTag($showTag)) as $key => $tag) { + $tagValues = []; + foreach ($tagsNames as $tagName) { + if (\is_array($tagValue = $tag[$tagName] ?? '')) { + $tagValue = $this->formatParameter($tagValue); + } + + $tagValues[] = $tagValue; + } + if (0 === $key) { + $tableRows[] = array_merge([$serviceId], $tagValues, [$definition->getClass()]); + } else { + $tableRows[] = array_merge([' (same service as previous, another tag)'], $tagValues, ['']); + } + } + } else { + $tableRows[] = [$styledServiceId, $definition->getClass()]; + } + } elseif ($definition instanceof Alias) { + $alias = $definition; + $tableRows[] = array_merge([$styledServiceId, sprintf('alias for "%s"', $alias)], $tagsCount ? array_fill(0, $tagsCount, '') : []); + } else { + $tableRows[] = array_merge([$styledServiceId, $definition::class], $tagsCount ? array_fill(0, $tagsCount, '') : []); + } + } + + $options['output']->table($tableHeaders, $tableRows); + } + + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void + { + if (isset($options['id'])) { + $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); + } + + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $options['output']->text($classDescription."\n"); + } + + $tableHeaders = ['Option', 'Value']; + + $tableRows[] = ['Service ID', $options['id'] ?? '-']; + $tableRows[] = ['Class', $definition->getClass() ?: '-']; + + $omitTags = isset($options['omit_tags']) && $options['omit_tags']; + if (!$omitTags && ($tags = $definition->getTags())) { + $tagInformation = []; + foreach ($tags as $tagName => $tagData) { + foreach ($tagData as $tagParameters) { + $parameters = array_map(fn ($key, $value) => sprintf('%s: %s', $key, \is_array($value) ? $this->formatParameter($value) : $value), array_keys($tagParameters), array_values($tagParameters)); + $parameters = implode(', ', $parameters); + + if ('' === $parameters) { + $tagInformation[] = sprintf('%s', $tagName); + } else { + $tagInformation[] = sprintf('%s (%s)', $tagName, $parameters); + } + } + } + $tagInformation = implode("\n", $tagInformation); + } else { + $tagInformation = '-'; + } + $tableRows[] = ['Tags', $tagInformation]; + + $calls = $definition->getMethodCalls(); + if (\count($calls) > 0) { + $callInformation = []; + foreach ($calls as $call) { + $callInformation[] = $call[0]; + } + $tableRows[] = ['Calls', implode(', ', $callInformation)]; + } + + $tableRows[] = ['Public', $definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no']; + $tableRows[] = ['Synthetic', $definition->isSynthetic() ? 'yes' : 'no']; + $tableRows[] = ['Lazy', $definition->isLazy() ? 'yes' : 'no']; + $tableRows[] = ['Shared', $definition->isShared() ? 'yes' : 'no']; + $tableRows[] = ['Abstract', $definition->isAbstract() ? 'yes' : 'no']; + $tableRows[] = ['Autowired', $definition->isAutowired() ? 'yes' : 'no']; + $tableRows[] = ['Autoconfigured', $definition->isAutoconfigured() ? 'yes' : 'no']; + + if ($definition->getFile()) { + $tableRows[] = ['Required File', $definition->getFile() ?: '-']; + } + + if ($factory = $definition->getFactory()) { + if (\is_array($factory)) { + if ($factory[0] instanceof Reference) { + $tableRows[] = ['Factory Service', $factory[0]]; + } elseif ($factory[0] instanceof Definition) { + $tableRows[] = ['Factory Service', sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'class not configured')]; + } else { + $tableRows[] = ['Factory Class', $factory[0]]; + } + $tableRows[] = ['Factory Method', $factory[1]]; + } else { + $tableRows[] = ['Factory Function', $factory]; + } + } + + $showArguments = isset($options['show_arguments']) && $options['show_arguments']; + $argumentsInformation = []; + if ($showArguments && ($arguments = $definition->getArguments())) { + foreach ($arguments as $argument) { + if ($argument instanceof ServiceClosureArgument) { + $argument = $argument->getValues()[0]; + } + if ($argument instanceof Reference) { + $argumentsInformation[] = sprintf('Service(%s)', (string) $argument); + } elseif ($argument instanceof IteratorArgument) { + if ($argument instanceof TaggedIteratorArgument) { + $argumentsInformation[] = sprintf('Tagged Iterator for "%s"%s', $argument->getTag(), $options['is_debug'] ? '' : sprintf(' (%d element(s))', \count($argument->getValues()))); + } else { + $argumentsInformation[] = sprintf('Iterator (%d element(s))', \count($argument->getValues())); + } + + foreach ($argument->getValues() as $ref) { + $argumentsInformation[] = sprintf('- Service(%s)', $ref); + } + } elseif ($argument instanceof ServiceLocatorArgument) { + $argumentsInformation[] = sprintf('Service locator (%d element(s))', \count($argument->getValues())); + } elseif ($argument instanceof Definition) { + $argumentsInformation[] = 'Inlined Service'; + } elseif ($argument instanceof \UnitEnum) { + $argumentsInformation[] = ltrim(var_export($argument, true), '\\'); + } elseif ($argument instanceof AbstractArgument) { + $argumentsInformation[] = sprintf('Abstract argument (%s)', $argument->getText()); + } else { + $argumentsInformation[] = \is_array($argument) ? sprintf('Array (%d element(s))', \count($argument)) : $argument; + } + } + + $tableRows[] = ['Arguments', implode("\n", $argumentsInformation)]; + } + + $inEdges = null !== $container && isset($options['id']) ? $this->getServiceEdges($container, $options['id']) : []; + $tableRows[] = ['Usages', $inEdges ? implode(\PHP_EOL, $inEdges) : 'none']; + + $options['output']->table($tableHeaders, $tableRows); + } + + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + $options['output']->warning('The deprecation file does not exist, please try warming the cache first.'); + + return; + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + if (0 === \count($logs)) { + $options['output']->success('There are no deprecations in the logs!'); + + return; + } + + $formattedLogs = []; + $remainingCount = 0; + foreach ($logs as $log) { + $formattedLogs[] = sprintf("%sx: %s\n in %s:%s", $log['count'], $log['message'], $log['file'], $log['line']); + $remainingCount += $log['count']; + } + $options['output']->title(sprintf('Remaining deprecations (%s)', $remainingCount)); + $options['output']->listing($formattedLogs); + } + + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void + { + if ($alias->isPublic() && !$alias->isPrivate()) { + $options['output']->comment(sprintf('This service is a public alias for the service %s', (string) $alias)); + } else { + $options['output']->comment(sprintf('This service is a private alias for the service %s', (string) $alias)); + } + + if (!$container) { + return; + } + + $this->describeContainerDefinition($container->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $container); + } + + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void + { + $parameterName = $options['parameter']; + $rows = [ + [$parameterName, $this->formatParameter($parameter)], + ]; + + if ($deprecation) { + $rows[] = [new TableCell( + sprintf('(Since %s %s: %s)', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2))), + ['colspan' => 2] + )]; + } + + $options['output']->table(['Parameter', 'Value'], $rows); + } + + protected function describeContainerEnvVars(array $envs, array $options = []): void + { + $dump = new Dumper($this->output); + $options['output']->title('Symfony Container Environment Variables'); + + if (null !== $name = $options['name'] ?? null) { + $options['output']->comment('Displaying detailed environment variable usage matching '.$name); + + $matches = false; + foreach ($envs as $env) { + if ($name === $env['name'] || false !== stripos($env['name'], $name)) { + $matches = true; + $options['output']->section('%env('.$env['processor'].':'.$env['name'].')%'); + $options['output']->table([], [ + ['Default value', $env['default_available'] ? $dump($env['default_value']) : 'n/a'], + ['Real value', $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a'], + ['Processed value', $env['default_available'] || $env['runtime_available'] ? $dump($env['processed_value']) : 'n/a'], + ]); + } + } + + if (!$matches) { + $options['output']->block('None of the environment variables match this name.'); + } else { + $options['output']->comment('Note real values might be different between web and CLI.'); + } + + return; + } + + if (!$envs) { + $options['output']->block('No environment variables are being used.'); + + return; + } + + $rows = []; + $missing = []; + foreach ($envs as $env) { + if (isset($rows[$env['name']])) { + continue; + } + + $rows[$env['name']] = [ + $env['name'], + $env['default_available'] ? $dump($env['default_value']) : 'n/a', + $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a', + ]; + if (!$env['default_available'] && !$env['runtime_available']) { + $missing[$env['name']] = true; + } + } + + $options['output']->table(['Name', 'Default value', 'Real value'], $rows); + $options['output']->comment('Note real values might be different between web and CLI.'); + + if ($missing) { + $options['output']->warning('The following variables are missing:'); + $options['output']->listing(array_keys($missing)); + } + } + + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void + { + $event = $options['event'] ?? null; + $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; + + $title = 'Registered Listeners'; + + if (null !== $dispatcherServiceName) { + $title .= sprintf(' of Event Dispatcher "%s"', $dispatcherServiceName); + } + + if (null !== $event) { + $title .= sprintf(' for "%s" Event', $event); + $registeredListeners = $eventDispatcher->getListeners($event); + } else { + $title .= ' Grouped by Event'; + // Try to see if "events" exists + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); + } + + $options['output']->title($title); + if (null !== $event) { + $this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']); + } else { + ksort($registeredListeners); + foreach ($registeredListeners as $eventListened => $eventListeners) { + $options['output']->section(sprintf('"%s" event', $eventListened)); + $this->renderEventListenerTable($eventDispatcher, $eventListened, $eventListeners, $options['output']); + } + } + } + + protected function describeCallable(mixed $callable, array $options = []): void + { + $this->writeText($this->formatCallable($callable), $options); + } + + private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io): void + { + $tableHeaders = ['Order', 'Callable', 'Priority']; + $tableRows = []; + + foreach ($eventListeners as $order => $listener) { + $tableRows[] = [sprintf('#%d', $order + 1), $this->formatCallable($listener), $eventDispatcher->getListenerPriority($event, $listener)]; + } + + $io->table($tableHeaders, $tableRows); + } + + private function formatRouterConfig(array $config): string + { + if (!$config) { + return 'NONE'; + } + + ksort($config); + + $configAsString = ''; + foreach ($config as $key => $value) { + $configAsString .= sprintf("\n%s: %s", $key, $this->formatValue($value)); + } + + return trim($configAsString); + } + + private function formatControllerLink(mixed $controller, string $anchorText, ?callable $getContainer = null): string + { + if (null === $this->fileLinkFormatter) { + return $anchorText; + } + + try { + if (null === $controller) { + return $anchorText; + } elseif (\is_array($controller)) { + $r = new \ReflectionMethod($controller[0], $controller[1]); + } elseif ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + } elseif (method_exists($controller, '__invoke')) { + $r = new \ReflectionMethod($controller, '__invoke'); + } elseif (!\is_string($controller)) { + return $anchorText; + } elseif (str_contains($controller, '::')) { + $r = new \ReflectionMethod(...explode('::', $controller, 2)); + } else { + $r = new \ReflectionFunction($controller); + } + } catch (\ReflectionException) { + if (\is_array($controller)) { + $controller = implode('::', $controller); + } + + $id = $controller; + $method = '__invoke'; + + if ($pos = strpos($controller, '::')) { + $id = substr($controller, 0, $pos); + $method = substr($controller, $pos + 2); + } + + if (!$getContainer || !($container = $getContainer()) || !$container->has($id)) { + return $anchorText; + } + + try { + $r = new \ReflectionMethod($container->findDefinition($id)->getClass(), $method); + } catch (\ReflectionException) { + return $anchorText; + } + } + + $fileLink = $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); + if ($fileLink) { + return sprintf('%s', $fileLink, $anchorText); + } + + return $anchorText; + } + + private function formatCallable(mixed $callable): string + { + if (\is_array($callable)) { + if (\is_object($callable[0])) { + return sprintf('%s::%s()', $callable[0]::class, $callable[1]); + } + + return sprintf('%s::%s()', $callable[0], $callable[1]); + } + + if (\is_string($callable)) { + return sprintf('%s()', $callable); + } + + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + if ($r->isAnonymous()) { + return 'Closure()'; + } + if ($class = $r->getClosureCalledClass()) { + return sprintf('%s::%s()', $class->name, $r->name); + } + + return $r->name.'()'; + } + + if (method_exists($callable, '__invoke')) { + return sprintf('%s::__invoke()', $callable::class); + } + + throw new \InvalidArgumentException('Callable is not describable.'); + } + + private function writeText(string $content, array $options = []): void + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } +} diff --git a/vendor/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php b/vendor/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000..c52b196 --- /dev/null +++ b/vendor/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php @@ -0,0 +1,608 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void + { + $this->writeDocument($this->getRouteCollectionDocument($routes, $options)); + } + + protected function describeRoute(Route $route, array $options = []): void + { + $this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null)); + } + + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void + { + $this->writeDocument($this->getContainerParametersDocument($parameters)); + } + + protected function describeContainerTags(ContainerBuilder $container, array $options = []): void + { + $this->writeDocument($this->getContainerTagsDocument($container, isset($options['show_hidden']) && $options['show_hidden'])); + } + + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $container, isset($options['show_arguments']) && $options['show_arguments'])); + } + + protected function describeContainerServices(ContainerBuilder $container, array $options = []): void + { + $this->writeDocument($this->getContainerServicesDocument($container, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null, $options['id'] ?? null)); + } + + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void + { + $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container)); + } + + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), true)); + + if (!$container) { + $this->writeDocument($dom); + + return; + } + + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($container->getDefinition((string) $alias), (string) $alias, false, false, $container)->childNodes->item(0), true)); + + $this->writeDocument($dom); + } + + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void + { + $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options)); + } + + protected function describeCallable(mixed $callable, array $options = []): void + { + $this->writeDocument($this->getCallableDocument($callable)); + } + + protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void + { + $this->writeDocument($this->getContainerParameterDocument($parameter, $deprecation, $options)); + } + + protected function describeContainerEnvVars(array $envs, array $options = []): void + { + throw new LogicException('Using the XML format to debug environment variables is not supported.'); + } + + protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $container->getParameter('kernel.build_dir'), $container->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($deprecationsXML = $dom->createElement('deprecations')); + + $remainingCount = 0; + foreach ($logs as $log) { + $deprecationsXML->appendChild($deprecationXML = $dom->createElement('deprecation')); + $deprecationXML->setAttribute('count', $log['count']); + $deprecationXML->appendChild($dom->createElement('message', $log['message'])); + $deprecationXML->appendChild($dom->createElement('file', $log['file'])); + $deprecationXML->appendChild($dom->createElement('line', $log['line'])); + $remainingCount += $log['count']; + } + + $deprecationsXML->setAttribute('remainingCount', $remainingCount); + + $this->writeDocument($dom); + } + + private function writeDocument(\DOMDocument $dom): void + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + private function getRouteCollectionDocument(RouteCollection $routes, array $options): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($routesXML = $dom->createElement('routes')); + + foreach ($routes->all() as $name => $route) { + $routeXML = $this->getRouteDocument($route, $name); + if (($showAliases ??= $options['show_aliases'] ?? false) && $aliases = ($reverseAliases ??= $this->getReverseAliases($routes))[$name] ?? []) { + $routeXML->firstChild->appendChild($aliasesXML = $routeXML->createElement('aliases')); + foreach ($aliases as $alias) { + $aliasesXML->appendChild($aliasXML = $routeXML->createElement('alias')); + $aliasXML->appendChild(new \DOMText($alias)); + } + } + + $routesXML->appendChild($routesXML->ownerDocument->importNode($routeXML->childNodes->item(0), true)); + } + + return $dom; + } + + private function getRouteDocument(Route $route, ?string $name = null): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($routeXML = $dom->createElement('route')); + + if ($name) { + $routeXML->setAttribute('name', $name); + } + + $routeXML->setAttribute('class', $route::class); + + $routeXML->appendChild($pathXML = $dom->createElement('path')); + $pathXML->setAttribute('regex', $route->compile()->getRegex()); + $pathXML->appendChild(new \DOMText($route->getPath())); + + if ('' !== $route->getHost()) { + $routeXML->appendChild($hostXML = $dom->createElement('host')); + $hostXML->setAttribute('regex', $route->compile()->getHostRegex()); + $hostXML->appendChild(new \DOMText($route->getHost())); + } + + foreach ($route->getSchemes() as $scheme) { + $routeXML->appendChild($schemeXML = $dom->createElement('scheme')); + $schemeXML->appendChild(new \DOMText($scheme)); + } + + foreach ($route->getMethods() as $method) { + $routeXML->appendChild($methodXML = $dom->createElement('method')); + $methodXML->appendChild(new \DOMText($method)); + } + + if ($route->getDefaults()) { + $routeXML->appendChild($defaultsXML = $dom->createElement('defaults')); + foreach ($route->getDefaults() as $attribute => $value) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->setAttribute('key', $attribute); + $defaultXML->appendChild(new \DOMText($this->formatValue($value))); + } + } + + $originRequirements = $requirements = $route->getRequirements(); + unset($requirements['_scheme'], $requirements['_method']); + if ($requirements) { + $routeXML->appendChild($requirementsXML = $dom->createElement('requirements')); + foreach ($originRequirements as $attribute => $pattern) { + $requirementsXML->appendChild($requirementXML = $dom->createElement('requirement')); + $requirementXML->setAttribute('key', $attribute); + $requirementXML->appendChild(new \DOMText($pattern)); + } + } + + if ($route->getOptions()) { + $routeXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($route->getOptions() as $name => $value) { + $optionsXML->appendChild($optionXML = $dom->createElement('option')); + $optionXML->setAttribute('key', $name); + $optionXML->appendChild(new \DOMText($this->formatValue($value))); + } + } + + if ('' !== $route->getCondition()) { + $routeXML->appendChild($conditionXML = $dom->createElement('condition')); + $conditionXML->appendChild(new \DOMText($route->getCondition())); + } + + return $dom; + } + + private function getContainerParametersDocument(ParameterBag $parameters): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($parametersXML = $dom->createElement('parameters')); + + $deprecatedParameters = $parameters->allDeprecated(); + + foreach ($this->sortParameters($parameters) as $key => $value) { + $parametersXML->appendChild($parameterXML = $dom->createElement('parameter')); + $parameterXML->setAttribute('key', $key); + $parameterXML->appendChild(new \DOMText($this->formatParameter($value))); + + if (isset($deprecatedParameters[$key])) { + $parameterXML->setAttribute('deprecated', sprintf('Since %s %s: %s', $deprecatedParameters[$key][0], $deprecatedParameters[$key][1], sprintf(...\array_slice($deprecatedParameters[$key], 2)))); + } + } + + return $dom; + } + + private function getContainerTagsDocument(ContainerBuilder $container, bool $showHidden = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($containerXML = $dom->createElement('container')); + + foreach ($this->findDefinitionsByTag($container, $showHidden) as $tag => $definitions) { + $containerXML->appendChild($tagXML = $dom->createElement('tag')); + $tagXML->setAttribute('name', $tag); + + foreach ($definitions as $serviceId => $definition) { + $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true, false, $container); + $tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), true)); + } + } + + return $dom; + } + + private function getContainerServiceDocument(object $service, string $id, ?ContainerBuilder $container = null, bool $showArguments = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + if ($service instanceof Alias) { + $dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), true)); + if ($container) { + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($container->getDefinition((string) $service), (string) $service, false, $showArguments, $container)->childNodes->item(0), true)); + } + } elseif ($service instanceof Definition) { + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, false, $showArguments, $container)->childNodes->item(0), true)); + } else { + $dom->appendChild($serviceXML = $dom->createElement('service')); + $serviceXML->setAttribute('id', $id); + $serviceXML->setAttribute('class', $service::class); + } + + return $dom; + } + + private function getContainerServicesDocument(ContainerBuilder $container, ?string $tag = null, bool $showHidden = false, bool $showArguments = false, ?callable $filter = null, ?string $id = null): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($containerXML = $dom->createElement('container')); + + $serviceIds = $tag + ? $this->sortTaggedServicesByPriority($container->findTaggedServiceIds($tag)) + : $this->sortServiceIds($container->getServiceIds()); + if ($filter) { + $serviceIds = array_filter($serviceIds, $filter); + } + + foreach ($serviceIds as $serviceId) { + $service = $this->resolveServiceDefinition($container, $serviceId); + + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + continue; + } + + if ($service instanceof Definition && $service->hasTag('container.excluded')) { + continue; + } + + $serviceXML = $this->getContainerServiceDocument($service, $serviceId, null, $showArguments); + $containerXML->appendChild($containerXML->ownerDocument->importNode($serviceXML->childNodes->item(0), true)); + } + + return $dom; + } + + private function getContainerDefinitionDocument(Definition $definition, ?string $id = null, bool $omitTags = false, bool $showArguments = false, ?ContainerBuilder $container = null): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($serviceXML = $dom->createElement('definition')); + + if ($id) { + $serviceXML->setAttribute('id', $id); + } + + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $serviceXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createCDATASection($classDescription)); + } + + $serviceXML->setAttribute('class', $definition->getClass() ?? ''); + + if ($factory = $definition->getFactory()) { + $serviceXML->appendChild($factoryXML = $dom->createElement('factory')); + + if (\is_array($factory)) { + if ($factory[0] instanceof Reference) { + $factoryXML->setAttribute('service', (string) $factory[0]); + } elseif ($factory[0] instanceof Definition) { + $factoryXML->setAttribute('service', sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'not configured')); + } else { + $factoryXML->setAttribute('class', $factory[0]); + } + $factoryXML->setAttribute('method', $factory[1]); + } else { + $factoryXML->setAttribute('function', $factory); + } + } + + $serviceXML->setAttribute('public', $definition->isPublic() && !$definition->isPrivate() ? 'true' : 'false'); + $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false'); + $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false'); + $serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false'); + $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false'); + $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false'); + $serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false'); + if ($definition->isDeprecated()) { + $serviceXML->setAttribute('deprecated', 'true'); + $serviceXML->setAttribute('deprecation_message', $definition->getDeprecation($id)['message']); + } else { + $serviceXML->setAttribute('deprecated', 'false'); + } + $serviceXML->setAttribute('file', $definition->getFile() ?? ''); + + $calls = $definition->getMethodCalls(); + if (\count($calls) > 0) { + $serviceXML->appendChild($callsXML = $dom->createElement('calls')); + foreach ($calls as $callData) { + $callsXML->appendChild($callXML = $dom->createElement('call')); + $callXML->setAttribute('method', $callData[0]); + if ($callData[2] ?? false) { + $callXML->setAttribute('returns-clone', 'true'); + } + } + } + + if ($showArguments) { + foreach ($this->getArgumentNodes($definition->getArguments(), $dom, $container) as $node) { + $serviceXML->appendChild($node); + } + } + + if (!$omitTags) { + if ($tags = $this->sortTagsByPriority($definition->getTags())) { + $serviceXML->appendChild($tagsXML = $dom->createElement('tags')); + foreach ($tags as $tagName => $tagData) { + foreach ($tagData as $parameters) { + $tagsXML->appendChild($tagXML = $dom->createElement('tag')); + $tagXML->setAttribute('name', $tagName); + foreach ($parameters as $name => $value) { + $tagXML->appendChild($parameterXML = $dom->createElement('parameter')); + $parameterXML->setAttribute('name', $name); + $parameterXML->appendChild(new \DOMText($this->formatParameter($value))); + } + } + } + } + } + + if (null !== $container && null !== $id) { + $edges = $this->getServiceEdges($container, $id); + if ($edges) { + $serviceXML->appendChild($usagesXML = $dom->createElement('usages')); + foreach ($edges as $edge) { + $usagesXML->appendChild($usageXML = $dom->createElement('usage')); + $usageXML->appendChild(new \DOMText($edge)); + } + } + } + + return $dom; + } + + /** + * @return \DOMNode[] + */ + private function getArgumentNodes(array $arguments, \DOMDocument $dom, ?ContainerBuilder $container = null): array + { + $nodes = []; + + foreach ($arguments as $argumentKey => $argument) { + $argumentXML = $dom->createElement('argument'); + + if (\is_string($argumentKey)) { + $argumentXML->setAttribute('key', $argumentKey); + } + + if ($argument instanceof ServiceClosureArgument) { + $argument = $argument->getValues()[0]; + } + + if ($argument instanceof Reference) { + $argumentXML->setAttribute('type', 'service'); + $argumentXML->setAttribute('id', (string) $argument); + } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) { + $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator'); + + foreach ($this->getArgumentNodes($argument->getValues(), $dom, $container) as $childArgumentXML) { + $argumentXML->appendChild($childArgumentXML); + } + } elseif ($argument instanceof Definition) { + $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true, $container)->childNodes->item(0), true)); + } elseif ($argument instanceof AbstractArgument) { + $argumentXML->setAttribute('type', 'abstract'); + $argumentXML->appendChild(new \DOMText($argument->getText())); + } elseif (\is_array($argument)) { + $argumentXML->setAttribute('type', 'collection'); + + foreach ($this->getArgumentNodes($argument, $dom, $container) as $childArgumentXML) { + $argumentXML->appendChild($childArgumentXML); + } + } elseif ($argument instanceof \UnitEnum) { + $argumentXML->setAttribute('type', 'constant'); + $argumentXML->appendChild(new \DOMText(ltrim(var_export($argument, true), '\\'))); + } else { + $argumentXML->appendChild(new \DOMText($argument)); + } + + $nodes[] = $argumentXML; + } + + return $nodes; + } + + private function getContainerAliasDocument(Alias $alias, ?string $id = null): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($aliasXML = $dom->createElement('alias')); + + if ($id) { + $aliasXML->setAttribute('id', $id); + } + + $aliasXML->setAttribute('service', (string) $alias); + $aliasXML->setAttribute('public', $alias->isPublic() && !$alias->isPrivate() ? 'true' : 'false'); + + return $dom; + } + + private function getContainerParameterDocument(mixed $parameter, ?array $deprecation, array $options = []): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($parameterXML = $dom->createElement('parameter')); + + if (isset($options['parameter'])) { + $parameterXML->setAttribute('key', $options['parameter']); + + if ($deprecation) { + $parameterXML->setAttribute('deprecated', sprintf('Since %s %s: %s', $deprecation[0], $deprecation[1], sprintf(...\array_slice($deprecation, 2)))); + } + } + + $parameterXML->appendChild(new \DOMText($this->formatParameter($parameter))); + + return $dom; + } + + private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, array $options): \DOMDocument + { + $event = $options['event'] ?? null; + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher')); + + if (null !== $event) { + $registeredListeners = $eventDispatcher->getListeners($event); + $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners); + } else { + // Try to see if "events" exists + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); + ksort($registeredListeners); + + foreach ($registeredListeners as $eventListened => $eventListeners) { + $eventDispatcherXML->appendChild($eventXML = $dom->createElement('event')); + $eventXML->setAttribute('name', $eventListened); + + $this->appendEventListenerDocument($eventDispatcher, $eventListened, $eventXML, $eventListeners); + } + } + + return $dom; + } + + private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners): void + { + foreach ($eventListeners as $listener) { + $callableXML = $this->getCallableDocument($listener); + $callableXML->childNodes->item(0)->setAttribute('priority', $eventDispatcher->getListenerPriority($event, $listener)); + + $element->appendChild($element->ownerDocument->importNode($callableXML->childNodes->item(0), true)); + } + } + + private function getCallableDocument(mixed $callable): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($callableXML = $dom->createElement('callable')); + + if (\is_array($callable)) { + $callableXML->setAttribute('type', 'function'); + + if (\is_object($callable[0])) { + $callableXML->setAttribute('name', $callable[1]); + $callableXML->setAttribute('class', $callable[0]::class); + } else { + if (!str_starts_with($callable[1], 'parent::')) { + $callableXML->setAttribute('name', $callable[1]); + $callableXML->setAttribute('class', $callable[0]); + $callableXML->setAttribute('static', 'true'); + } else { + $callableXML->setAttribute('name', substr($callable[1], 8)); + $callableXML->setAttribute('class', $callable[0]); + $callableXML->setAttribute('static', 'true'); + $callableXML->setAttribute('parent', 'true'); + } + } + + return $dom; + } + + if (\is_string($callable)) { + $callableXML->setAttribute('type', 'function'); + + if (!str_contains($callable, '::')) { + $callableXML->setAttribute('name', $callable); + } else { + $callableParts = explode('::', $callable); + + $callableXML->setAttribute('name', $callableParts[1]); + $callableXML->setAttribute('class', $callableParts[0]); + $callableXML->setAttribute('static', 'true'); + } + + return $dom; + } + + if ($callable instanceof \Closure) { + $callableXML->setAttribute('type', 'closure'); + + $r = new \ReflectionFunction($callable); + if ($r->isAnonymous()) { + return $dom; + } + $callableXML->setAttribute('name', $r->name); + + if ($class = $r->getClosureCalledClass()) { + $callableXML->setAttribute('class', $class->name); + if (!$r->getClosureThis()) { + $callableXML->setAttribute('static', 'true'); + } + } + + return $dom; + } + + if (method_exists($callable, '__invoke')) { + $callableXML->setAttribute('type', 'object'); + $callableXML->setAttribute('name', $callable::class); + + return $dom; + } + + throw new \InvalidArgumentException('Callable is not describable.'); + } +} diff --git a/vendor/symfony/framework-bundle/Console/Helper/DescriptorHelper.php b/vendor/symfony/framework-bundle/Console/Helper/DescriptorHelper.php new file mode 100644 index 0000000..b12a8d8 --- /dev/null +++ b/vendor/symfony/framework-bundle/Console/Helper/DescriptorHelper.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Helper; + +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\JsonDescriptor; +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\MarkdownDescriptor; +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor; +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; + +/** + * @author Jean-François Simon + * + * @internal + */ +class DescriptorHelper extends BaseDescriptorHelper +{ + public function __construct(?FileLinkFormatter $fileLinkFormatter = null) + { + $this + ->register('txt', new TextDescriptor($fileLinkFormatter)) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } +} diff --git a/vendor/symfony/framework-bundle/Controller/AbstractController.php b/vendor/symfony/framework-bundle/Controller/AbstractController.php new file mode 100644 index 0000000..f0c1d98 --- /dev/null +++ b/vendor/symfony/framework-bundle/Controller/AbstractController.php @@ -0,0 +1,452 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Link\EvolvableLinkInterface; +use Psr\Link\LinkInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Twig\Environment; + +/** + * Provides shortcuts for HTTP-related features in controllers. + * + * @author Fabien Potencier + */ +abstract class AbstractController implements ServiceSubscriberInterface +{ + protected ContainerInterface $container; + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $previous = $this->container ?? null; + $this->container = $container; + + return $previous; + } + + /** + * Gets a container parameter by its name. + */ + protected function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (!$this->container->has('parameter_bag')) { + throw new ServiceNotFoundException('parameter_bag.', null, null, [], sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); + } + + return $this->container->get('parameter_bag')->get($name); + } + + public static function getSubscribedServices(): array + { + return [ + 'router' => '?'.RouterInterface::class, + 'request_stack' => '?'.RequestStack::class, + 'http_kernel' => '?'.HttpKernelInterface::class, + 'serializer' => '?'.SerializerInterface::class, + 'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class, + 'twig' => '?'.Environment::class, + 'form.factory' => '?'.FormFactoryInterface::class, + 'security.token_storage' => '?'.TokenStorageInterface::class, + 'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class, + 'parameter_bag' => '?'.ContainerBagInterface::class, + 'web_link.http_header_serializer' => '?'.HttpHeaderSerializer::class, + ]; + } + + /** + * Generates a URL from the given parameters. + * + * @see UrlGeneratorInterface + */ + protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string + { + return $this->container->get('router')->generate($route, $parameters, $referenceType); + } + + /** + * Forwards the request to another controller. + * + * @param string $controller The controller name (a string like "App\Controller\PostController::index" or "App\Controller\PostController" if it is invokable) + */ + protected function forward(string $controller, array $path = [], array $query = []): Response + { + $request = $this->container->get('request_stack')->getCurrentRequest(); + $path['_controller'] = $controller; + $subRequest = $request->duplicate($query, null, $path); + + return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + } + + /** + * Returns a RedirectResponse to the given URL. + * + * @param int $status The HTTP status code (302 "Found" by default) + */ + protected function redirect(string $url, int $status = 302): RedirectResponse + { + return new RedirectResponse($url, $status); + } + + /** + * Returns a RedirectResponse to the given route with the given parameters. + * + * @param int $status The HTTP status code (302 "Found" by default) + */ + protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse + { + return $this->redirect($this->generateUrl($route, $parameters), $status); + } + + /** + * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. + * + * @param int $status The HTTP status code (200 "OK" by default) + */ + protected function json(mixed $data, int $status = 200, array $headers = [], array $context = []): JsonResponse + { + if ($this->container->has('serializer')) { + $json = $this->container->get('serializer')->serialize($data, 'json', array_merge([ + 'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS, + ], $context)); + + return new JsonResponse($json, $status, $headers, true); + } + + return new JsonResponse($data, $status, $headers); + } + + /** + * Returns a BinaryFileResponse object with original or customized file name and disposition header. + */ + protected function file(\SplFileInfo|string $file, ?string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse + { + $response = new BinaryFileResponse($file); + $response->setContentDisposition($disposition, $fileName ?? $response->getFile()->getFilename()); + + return $response; + } + + /** + * Adds a flash message to the current session for type. + * + * @throws \LogicException + */ + protected function addFlash(string $type, mixed $message): void + { + try { + $session = $this->container->get('request_stack')->getSession(); + } catch (SessionNotFoundException $e) { + throw new \LogicException('You cannot use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".', 0, $e); + } + + if (!$session instanceof FlashBagAwareSessionInterface) { + throw new \LogicException(sprintf('You cannot use the addFlash method because class "%s" doesn\'t implement "%s".', get_debug_type($session), FlashBagAwareSessionInterface::class)); + } + + $session->getFlashBag()->add($type, $message); + } + + /** + * Checks if the attribute is granted against the current authentication token and optionally supplied subject. + * + * @throws \LogicException + */ + protected function isGranted(mixed $attribute, mixed $subject = null): bool + { + if (!$this->container->has('security.authorization_checker')) { + throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); + } + + return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject); + } + + /** + * Throws an exception unless the attribute is granted against the current authentication token and optionally + * supplied subject. + * + * @throws AccessDeniedException + */ + protected function denyAccessUnlessGranted(mixed $attribute, mixed $subject = null, string $message = 'Access Denied.'): void + { + if (!$this->isGranted($attribute, $subject)) { + $exception = $this->createAccessDeniedException($message); + $exception->setAttributes([$attribute]); + $exception->setSubject($subject); + + throw $exception; + } + } + + /** + * Returns a rendered view. + * + * Forms found in parameters are auto-cast to form views. + */ + protected function renderView(string $view, array $parameters = []): string + { + return $this->doRenderView($view, null, $parameters, __FUNCTION__); + } + + /** + * Returns a rendered block from a view. + * + * Forms found in parameters are auto-cast to form views. + */ + protected function renderBlockView(string $view, string $block, array $parameters = []): string + { + return $this->doRenderView($view, $block, $parameters, __FUNCTION__); + } + + /** + * Renders a view. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + * Forms found in parameters are auto-cast to form views. + */ + protected function render(string $view, array $parameters = [], ?Response $response = null): Response + { + return $this->doRender($view, null, $parameters, $response, __FUNCTION__); + } + + /** + * Renders a block in a view. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + * Forms found in parameters are auto-cast to form views. + */ + protected function renderBlock(string $view, string $block, array $parameters = [], ?Response $response = null): Response + { + return $this->doRender($view, $block, $parameters, $response, __FUNCTION__); + } + + /** + * Streams a view. + */ + protected function stream(string $view, array $parameters = [], ?StreamedResponse $response = null): StreamedResponse + { + if (!$this->container->has('twig')) { + throw new \LogicException('You cannot use the "stream" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); + } + + $twig = $this->container->get('twig'); + + $callback = function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }; + + if (null === $response) { + return new StreamedResponse($callback); + } + + $response->setCallback($callback); + + return $response; + } + + /** + * Returns a NotFoundHttpException. + * + * This will result in a 404 response code. Usage example: + * + * throw $this->createNotFoundException('Page not found!'); + */ + protected function createNotFoundException(string $message = 'Not Found', ?\Throwable $previous = null): NotFoundHttpException + { + return new NotFoundHttpException($message, $previous); + } + + /** + * Returns an AccessDeniedException. + * + * This will result in a 403 response code. Usage example: + * + * throw $this->createAccessDeniedException('Unable to access this page!'); + * + * @throws \LogicException If the Security component is not available + */ + protected function createAccessDeniedException(string $message = 'Access Denied.', ?\Throwable $previous = null): AccessDeniedException + { + if (!class_exists(AccessDeniedException::class)) { + throw new \LogicException('You cannot use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".'); + } + + return new AccessDeniedException($message, $previous); + } + + /** + * Creates and returns a Form instance from the type of the form. + */ + protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface + { + return $this->container->get('form.factory')->create($type, $data, $options); + } + + /** + * Creates and returns a form builder instance. + */ + protected function createFormBuilder(mixed $data = null, array $options = []): FormBuilderInterface + { + return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options); + } + + /** + * Get a user from the Security Token Storage. + * + * @throws \LogicException If SecurityBundle is not available + * + * @see TokenInterface::getUser() + */ + protected function getUser(): ?UserInterface + { + if (!$this->container->has('security.token_storage')) { + throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); + } + + if (null === $token = $this->container->get('security.token_storage')->getToken()) { + return null; + } + + return $token->getUser(); + } + + /** + * Checks the validity of a CSRF token. + * + * @param string $id The id used when generating the token + * @param string|null $token The actual token sent with the request that should be validated + */ + protected function isCsrfTokenValid(string $id, #[\SensitiveParameter] ?string $token): bool + { + if (!$this->container->has('security.csrf.token_manager')) { + throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); + } + + return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token)); + } + + /** + * Adds a Link HTTP header to the current response. + * + * @see https://tools.ietf.org/html/rfc5988 + */ + protected function addLink(Request $request, LinkInterface $link): void + { + if (!class_exists(AddLinkHeaderListener::class)) { + throw new \LogicException('You cannot use the "addLink" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); + } + + if (null === $linkProvider = $request->attributes->get('_links')) { + $request->attributes->set('_links', new GenericLinkProvider([$link])); + + return; + } + + $request->attributes->set('_links', $linkProvider->withLink($link)); + } + + /** + * @param LinkInterface[] $links + */ + protected function sendEarlyHints(iterable $links = [], ?Response $response = null): Response + { + if (!$this->container->has('web_link.http_header_serializer')) { + throw new \LogicException('You cannot use the "sendEarlyHints" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); + } + + $response ??= new Response(); + + $populatedLinks = []; + foreach ($links as $link) { + if ($link instanceof EvolvableLinkInterface && !$link->getRels()) { + $link = $link->withRel('preload'); + } + + $populatedLinks[] = $link; + } + + $response->headers->set('Link', $this->container->get('web_link.http_header_serializer')->serialize($populatedLinks), false); + $response->sendHeaders(103); + + return $response; + } + + private function doRenderView(string $view, ?string $block, array $parameters, string $method): string + { + if (!$this->container->has('twig')) { + throw new \LogicException(sprintf('You cannot use the "%s" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".', $method)); + } + + foreach ($parameters as $k => $v) { + if ($v instanceof FormInterface) { + $parameters[$k] = $v->createView(); + } + } + + if (null !== $block) { + return $this->container->get('twig')->load($view)->renderBlock($block, $parameters); + } + + return $this->container->get('twig')->render($view, $parameters); + } + + private function doRender(string $view, ?string $block, array $parameters, ?Response $response, string $method): Response + { + $content = $this->doRenderView($view, $block, $parameters, $method); + $response ??= new Response(); + + if (200 === $response->getStatusCode()) { + foreach ($parameters as $v) { + if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) { + $response->setStatusCode(422); + break; + } + } + } + + $response->setContent($content); + + return $response; + } +} diff --git a/vendor/symfony/framework-bundle/Controller/ControllerResolver.php b/vendor/symfony/framework-bundle/Controller/ControllerResolver.php new file mode 100644 index 0000000..af8b494 --- /dev/null +++ b/vendor/symfony/framework-bundle/Controller/ControllerResolver.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Controller; + +use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; + +/** + * @author Fabien Potencier + * + * @final + */ +class ControllerResolver extends ContainerControllerResolver +{ + protected function instantiateController(string $class): object + { + $controller = parent::instantiateController($class); + + if ($controller instanceof AbstractController) { + if (null === $previousContainer = $controller->setContainer($this->container)) { + throw new \LogicException(sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class)); + } else { + $controller->setContainer($previousContainer); + } + } + + return $controller; + } +} diff --git a/vendor/symfony/framework-bundle/Controller/RedirectController.php b/vendor/symfony/framework-bundle/Controller/RedirectController.php new file mode 100644 index 0000000..1001fad --- /dev/null +++ b/vendor/symfony/framework-bundle/Controller/RedirectController.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Controller; + +use Symfony\Component\HttpFoundation\HeaderUtils; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * Redirects a request to another URL. + * + * @author Fabien Potencier + * + * @final + */ +class RedirectController +{ + public function __construct( + private ?UrlGeneratorInterface $router = null, + private ?int $httpPort = null, + private ?int $httpsPort = null, + ) { + } + + /** + * Redirects to another route with the given name. + * + * The response status code is 302 if the permanent parameter is false (default), + * and 301 if the redirection is permanent. + * + * In case the route name is empty, the status code will be 404 when permanent is false + * and 410 otherwise. + * + * @param string $route The route name to redirect to + * @param bool $permanent Whether the redirection is permanent + * @param bool|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore + * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method + * + * @throws HttpException In case the route name is empty + */ + public function redirectAction(Request $request, string $route, bool $permanent = false, bool|array $ignoreAttributes = false, bool $keepRequestMethod = false, bool $keepQueryParams = false): Response + { + if ('' == $route) { + throw new HttpException($permanent ? 410 : 404); + } + + $attributes = []; + if (false === $ignoreAttributes || \is_array($ignoreAttributes)) { + $attributes = $request->attributes->get('_route_params'); + + if ($keepQueryParams) { + if ($query = $request->server->get('QUERY_STRING')) { + $query = HeaderUtils::parseQuery($query); + } else { + $query = $request->query->all(); + } + + $attributes = array_merge($query, $attributes); + } + + unset($attributes['route'], $attributes['permanent'], $attributes['ignoreAttributes'], $attributes['keepRequestMethod'], $attributes['keepQueryParams']); + if ($ignoreAttributes) { + $attributes = array_diff_key($attributes, array_flip($ignoreAttributes)); + } + } + + if ($keepRequestMethod) { + $statusCode = $permanent ? 308 : 307; + } else { + $statusCode = $permanent ? 301 : 302; + } + + return new RedirectResponse($this->router->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $statusCode); + } + + /** + * Redirects to a URL. + * + * The response status code is 302 if the permanent parameter is false (default), + * and 301 if the redirection is permanent. + * + * In case the path is empty, the status code will be 404 when permanent is false + * and 410 otherwise. + * + * @param string $path The absolute path or URL to redirect to + * @param bool $permanent Whether the redirect is permanent or not + * @param string|null $scheme The URL scheme (null to keep the current one) + * @param int|null $httpPort The HTTP port (null to keep the current one for the same scheme or the default configured port) + * @param int|null $httpsPort The HTTPS port (null to keep the current one for the same scheme or the default configured port) + * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method + * + * @throws HttpException In case the path is empty + */ + public function urlRedirectAction(Request $request, string $path, bool $permanent = false, ?string $scheme = null, ?int $httpPort = null, ?int $httpsPort = null, bool $keepRequestMethod = false): Response + { + if ('' == $path) { + throw new HttpException($permanent ? 410 : 404); + } + + if ($keepRequestMethod) { + $statusCode = $permanent ? 308 : 307; + } else { + $statusCode = $permanent ? 301 : 302; + } + + // redirect if the path is a full URL + if (parse_url($path, \PHP_URL_SCHEME)) { + return new RedirectResponse($path, $statusCode); + } + + $scheme ??= $request->getScheme(); + + if ($qs = $request->server->get('QUERY_STRING') ?: $request->getQueryString()) { + if (!str_contains($path, '?')) { + $qs = '?'.$qs; + } else { + $qs = '&'.$qs; + } + } + + $port = ''; + if ('http' === $scheme) { + if (null === $httpPort) { + if ('http' === $request->getScheme()) { + $httpPort = $request->getPort(); + } else { + $httpPort = $this->httpPort; + } + } + + if (null !== $httpPort && 80 != $httpPort) { + $port = ":$httpPort"; + } + } elseif ('https' === $scheme) { + if (null === $httpsPort) { + if ('https' === $request->getScheme()) { + $httpsPort = $request->getPort(); + } else { + $httpsPort = $this->httpsPort; + } + } + + if (null !== $httpsPort && 443 != $httpsPort) { + $port = ":$httpsPort"; + } + } + + $url = $scheme.'://'.$request->getHost().$port.$request->getBaseUrl().$path.$qs; + + return new RedirectResponse($url, $statusCode); + } + + public function __invoke(Request $request): Response + { + $p = $request->attributes->get('_route_params', []); + + if (\array_key_exists('route', $p)) { + if (\array_key_exists('path', $p)) { + throw new \RuntimeException(sprintf('Ambiguous redirection settings, use the "path" or "route" parameter, not both: "%s" and "%s" found respectively in "%s" routing configuration.', $p['path'], $p['route'], $request->attributes->get('_route'))); + } + + return $this->redirectAction($request, $p['route'], $p['permanent'] ?? false, $p['ignoreAttributes'] ?? false, $p['keepRequestMethod'] ?? false, $p['keepQueryParams'] ?? false); + } + + if (\array_key_exists('path', $p)) { + return $this->urlRedirectAction($request, $p['path'], $p['permanent'] ?? false, $p['scheme'] ?? null, $p['httpPort'] ?? null, $p['httpsPort'] ?? null, $p['keepRequestMethod'] ?? false); + } + + throw new \RuntimeException(sprintf('The parameter "path" or "route" is required to configure the redirect action in "%s" routing configuration.', $request->attributes->get('_route'))); + } +} diff --git a/vendor/symfony/framework-bundle/Controller/TemplateController.php b/vendor/symfony/framework-bundle/Controller/TemplateController.php new file mode 100644 index 0000000..bcbcc38 --- /dev/null +++ b/vendor/symfony/framework-bundle/Controller/TemplateController.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Controller; + +use Symfony\Component\HttpFoundation\Response; +use Twig\Environment; + +/** + * TemplateController. + * + * @author Fabien Potencier + * + * @final + */ +class TemplateController +{ + public function __construct( + private ?Environment $twig = null, + ) { + } + + /** + * Renders a template. + * + * @param string $template The template name + * @param int|null $maxAge Max age for client caching + * @param int|null $sharedAge Max age for shared (proxy) caching + * @param bool|null $private Whether or not caching should apply for client caches only + * @param array $context The context (arguments) of the template + * @param int $statusCode The HTTP status code to return with the response (200 "OK" by default) + */ + public function templateAction(string $template, ?int $maxAge = null, ?int $sharedAge = null, ?bool $private = null, array $context = [], int $statusCode = 200): Response + { + if (null === $this->twig) { + throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); + } + + $response = new Response($this->twig->render($template, $context), $statusCode); + + if ($maxAge) { + $response->setMaxAge($maxAge); + } + + if (null !== $sharedAge) { + $response->setSharedMaxAge($sharedAge); + } + + if ($private) { + $response->setPrivate(); + } elseif (false === $private || (null === $private && (null !== $maxAge || null !== $sharedAge))) { + $response->setPublic(); + } + + return $response; + } + + /** + * @param int $statusCode The HTTP status code (200 "OK" by default) + */ + public function __invoke(string $template, ?int $maxAge = null, ?int $sharedAge = null, ?bool $private = null, array $context = [], int $statusCode = 200): Response + { + return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode); + } +} diff --git a/vendor/symfony/framework-bundle/DataCollector/AbstractDataCollector.php b/vendor/symfony/framework-bundle/DataCollector/AbstractDataCollector.php new file mode 100644 index 0000000..f2fa506 --- /dev/null +++ b/vendor/symfony/framework-bundle/DataCollector/AbstractDataCollector.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\DataCollector; + +/** + * @author Laurent VOULLEMIER + */ +abstract class AbstractDataCollector extends DataCollector implements TemplateAwareDataCollectorInterface +{ + public function getName(): string + { + return static::class; + } + + public static function getTemplate(): ?string + { + return null; + } +} diff --git a/vendor/symfony/framework-bundle/DataCollector/RouterDataCollector.php b/vendor/symfony/framework-bundle/DataCollector/RouterDataCollector.php new file mode 100644 index 0000000..e383d48 --- /dev/null +++ b/vendor/symfony/framework-bundle/DataCollector/RouterDataCollector.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DataCollector; + +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\DataCollector\RouterDataCollector as BaseRouterDataCollector; + +/** + * @author Fabien Potencier + * + * @final + */ +class RouterDataCollector extends BaseRouterDataCollector +{ + public function guessRoute(Request $request, mixed $controller): string + { + if (\is_array($controller)) { + $controller = $controller[0]; + } + + if ($controller instanceof RedirectController && $request->attributes->has('_route')) { + return $request->attributes->get('_route'); + } + + return parent::guessRoute($request, $controller); + } +} diff --git a/vendor/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php b/vendor/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php new file mode 100644 index 0000000..5ef17ab --- /dev/null +++ b/vendor/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; + +/** + * @author Laurent VOULLEMIER + */ +interface TemplateAwareDataCollectorInterface extends DataCollectorInterface +{ + public static function getTemplate(): ?string; +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php new file mode 100644 index 0000000..1efdcb8 --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class AddDebugLogProcessorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('profiler')) { + return; + } + if (!$container->hasDefinition('monolog.logger_prototype')) { + return; + } + if (!$container->hasDefinition('debug.log_processor')) { + return; + } + + $container->getDefinition('monolog.logger_prototype') + ->setConfigurator([new Reference('debug.debug_logger_configurator'), 'pushDebugLogger']); + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php new file mode 100644 index 0000000..c4b99c5 --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +class AssetsContextPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('assets.context')) { + return; + } + + if (!$container->hasDefinition('router.request_context')) { + $container->setParameter('asset.request_context.base_path', $container->getParameter('asset.request_context.base_path') ?? ''); + $container->setParameter('asset.request_context.secure', $container->getParameter('asset.request_context.secure') ?? false); + + return; + } + + $context = $container->getDefinition('assets.context'); + + if (null === $container->getParameter('asset.request_context.base_path')) { + $context->replaceArgument(1, (new Definition('string'))->setFactory([new Reference('router.request_context'), 'getBaseUrl'])); + } + + if (null === $container->getParameter('asset.request_context.secure')) { + $context->replaceArgument(2, (new Definition('bool'))->setFactory([new Reference('router.request_context'), 'isSecure'])); + } + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php new file mode 100644 index 0000000..e4023e6 --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\XmlDumper; + +/** + * Dumps the ContainerBuilder to a cache file so that it can be used by + * debugging tools such as the debug:container console command. + * + * @author Ryan Weaver + * @author Fabien Potencier + */ +class ContainerBuilderDebugDumpPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->getParameter('debug.container.dump')) { + return; + } + + $cache = new ConfigCache($container->getParameter('debug.container.dump'), true); + if (!$cache->isFresh()) { + $cache->write((new XmlDumper($container))->dump(), $container->getResources()); + } + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php new file mode 100644 index 0000000..09f2dba --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @internal + */ +class ErrorLoggerCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('debug.error_handler_configurator')) { + return; + } + + $definition = $container->getDefinition('debug.error_handler_configurator'); + if ($container->hasDefinition('monolog.logger.php')) { + $definition->replaceArgument(0, new Reference('monolog.logger.php')); + } + if ($container->hasDefinition('monolog.logger.deprecation')) { + $definition->replaceArgument(5, new Reference('monolog.logger.deprecation')); + } + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php new file mode 100644 index 0000000..05fe0a4 --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Bundle\FrameworkBundle\DataCollector\TemplateAwareDataCollectorInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged data_collector services to profiler service. + * + * @author Fabien Potencier + */ +class ProfilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (false === $container->hasDefinition('profiler')) { + return; + } + + $definition = $container->getDefinition('profiler'); + + $collectors = new \SplPriorityQueue(); + $order = \PHP_INT_MAX; + foreach ($container->findTaggedServiceIds('data_collector', true) as $id => $attributes) { + $priority = $attributes[0]['priority'] ?? 0; + $template = null; + + $collectorClass = $container->findDefinition($id)->getClass(); + if (isset($attributes[0]['template']) || is_subclass_of($collectorClass, TemplateAwareDataCollectorInterface::class)) { + $idForTemplate = $attributes[0]['id'] ?? $collectorClass; + if (!$idForTemplate) { + throw new InvalidArgumentException(sprintf('Data collector service "%s" must have an id attribute in order to specify a template.', $id)); + } + $template = [$idForTemplate, $attributes[0]['template'] ?? $collectorClass::getTemplate()]; + } + + $collectors->insert([$id, $template], [$priority, --$order]); + } + + $templates = []; + foreach ($collectors as $collector) { + $definition->addMethodCall('add', [new Reference($collector[0])]); + $templates[$collector[0]] = $collector[1]; + } + + $container->setParameter('data_collector.templates', $templates); + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php new file mode 100644 index 0000000..7f0ec5f --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Ahmed TAILOULOUTE + */ +class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('session.marshalling_handler')) { + return; + } + + $isMarshallerDecorated = false; + + foreach ($container->getDefinitions() as $definition) { + $decorated = $definition->getDecoratedService(); + if (null !== $decorated && 'session.marshaller' === $decorated[0]) { + $isMarshallerDecorated = true; + + break; + } + } + + if (!$isMarshallerDecorated) { + $container->removeDefinition('session.marshalling_handler'); + } + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php new file mode 100644 index 0000000..68a6ee1 --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class TestServiceContainerRealRefPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('test.private_services_locator')) { + return; + } + + $privateContainer = $container->getDefinition('test.private_services_locator'); + $definitions = $container->getDefinitions(); + $privateServices = $privateContainer->getArgument(0); + $renamedIds = []; + + foreach ($privateServices as $id => $argument) { + if (isset($definitions[$target = (string) $argument->getValues()[0]])) { + $argument->setValues([new Reference($target)]); + if ($id !== $target) { + $renamedIds[$id] = $target; + } + if ($inner = $definitions[$target]->getTag('container.decorator')[0]['inner'] ?? null) { + $renamedIds[$id] = $inner; + } + } else { + unset($privateServices[$id]); + } + } + + foreach ($container->getAliases() as $id => $target) { + while ($container->hasAlias($target = (string) $target)) { + $target = $container->getAlias($target); + } + + if ($definitions[$target]->hasTag('container.private')) { + $privateServices[$id] = new ServiceClosureArgument(new Reference($target)); + } + + $renamedIds[$id] = $target; + } + + $privateContainer->replaceArgument(0, $privateServices); + + if ($container->hasDefinition('test.service_container') && $renamedIds) { + $container->getDefinition('test.service_container')->setArgument(2, $renamedIds); + } + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php new file mode 100644 index 0000000..3b3dfcc --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class TestServiceContainerWeakRefPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('test.private_services_locator')) { + return; + } + + $privateServices = []; + $definitions = $container->getDefinitions(); + + foreach ($definitions as $id => $definition) { + if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->hasErrors() && !$definition->isAbstract()) { + $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + } + + $aliases = $container->getAliases(); + + foreach ($aliases as $id => $alias) { + if ($id && '.' !== $id[0] && (!$alias->isPublic() || $alias->isPrivate())) { + while (isset($aliases[$target = (string) $alias])) { + $alias = $aliases[$target]; + } + if (isset($definitions[$target]) && !$definitions[$target]->hasErrors() && !$definitions[$target]->isAbstract()) { + $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + } + } + + if ($privateServices) { + $id = (string) ServiceLocatorTagPass::register($container, $privateServices); + $container->setDefinition('test.private_services_locator', $container->getDefinition($id))->setPublic(true); + $container->removeDefinition($id); + } + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php new file mode 100644 index 0000000..a2a141a --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Find all service tags which are defined, but not used and yield a warning log message. + * + * @author Florian Pfitzer + */ +class UnusedTagsPass implements CompilerPassInterface +{ + private const KNOWN_TAGS = [ + 'asset_mapper.compiler', + 'assets.package', + 'auto_alias', + 'cache.pool', + 'cache.pool.clearer', + 'cache.taggable', + 'chatter.transport_factory', + 'config_cache.resource_checker', + 'console.command', + 'container.env_var_loader', + 'container.env_var_processor', + 'container.excluded', + 'container.hot_path', + 'container.no_preload', + 'container.preload', + 'container.private', + 'container.reversible', + 'container.service_locator', + 'container.service_locator_context', + 'container.service_subscriber', + 'container.stack', + 'controller.argument_value_resolver', + 'controller.service_arguments', + 'controller.targeted_value_resolver', + 'data_collector', + 'event_dispatcher.dispatcher', + 'form.type', + 'form.type_extension', + 'form.type_guesser', + 'html_sanitizer', + 'http_client.client', + 'kernel.cache_clearer', + 'kernel.cache_warmer', + 'kernel.event_listener', + 'kernel.event_subscriber', + 'kernel.fragment_renderer', + 'kernel.locale_aware', + 'kernel.reset', + 'ldap', + 'mailer.transport_factory', + 'messenger.bus', + 'messenger.message_handler', + 'messenger.receiver', + 'messenger.transport_factory', + 'mime.mime_type_guesser', + 'monolog.logger', + 'notifier.channel', + 'property_info.access_extractor', + 'property_info.initializable_extractor', + 'property_info.list_extractor', + 'property_info.type_extractor', + 'proxy', + 'remote_event.consumer', + 'routing.condition_service', + 'routing.expression_language_function', + 'routing.expression_language_provider', + 'routing.loader', + 'routing.route_loader', + 'scheduler.schedule_provider', + 'scheduler.task', + 'security.access_token_handler.oidc.signature_algorithm', + 'security.authenticator.login_linker', + 'security.expression_language_provider', + 'security.remember_me_handler', + 'security.voter', + 'serializer.encoder', + 'serializer.normalizer', + 'texter.transport_factory', + 'translation.dumper', + 'translation.extractor', + 'translation.extractor.visitor', + 'translation.loader', + 'translation.provider_factory', + 'twig.extension', + 'twig.loader', + 'twig.runtime', + 'validator.auto_mapper', + 'validator.constraint_validator', + 'validator.group_provider', + 'validator.initializer', + 'workflow', + ]; + + public function process(ContainerBuilder $container): void + { + $tags = array_unique(array_merge($container->findTags(), self::KNOWN_TAGS)); + + foreach ($container->findUnusedTags() as $tag) { + // skip known tags + if (\in_array($tag, self::KNOWN_TAGS, true)) { + continue; + } + + // check for typos + $candidates = []; + foreach ($tags as $definedTag) { + if ($definedTag === $tag) { + continue; + } + + if (str_contains($definedTag, $tag) || levenshtein($tag, $definedTag) <= \strlen($tag) / 3) { + $candidates[] = $definedTag; + } + } + + $services = array_keys($container->findTaggedServiceIds($tag)); + $message = sprintf('Tag "%s" was defined on service(s) "%s", but was never used.', $tag, implode('", "', $services)); + if ($candidates) { + $message .= sprintf(' Did you mean "%s"?', implode('", "', $candidates)); + } + + $container->log($this, $message); + } + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/Configuration.php b/vendor/symfony/framework-bundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000..2e9caca --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/Configuration.php @@ -0,0 +1,2510 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; + +use Doctrine\DBAL\Connection; +use Psr\Log\LogLevel; +use Seld\JsonLint\JsonParser; +use Symfony\Bundle\FullStack; +use Symfony\Component\Asset\Package; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\Form\Form; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\Lock\Lock; +use Symfony\Component\Lock\Store\SemaphoreStore; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Scheduler\Schedule; +use Symfony\Component\Semaphore\Semaphore; +use Symfony\Component\Serializer\Encoder\JsonDecode; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Translation\Translator; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Validator\Validation; +use Symfony\Component\Webhook\Controller\WebhookController; +use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\Workflow\WorkflowEvents; + +/** + * FrameworkExtension configuration structure. + */ +class Configuration implements ConfigurationInterface +{ + /** + * @param bool $debug Whether debugging is enabled or not + */ + public function __construct( + private bool $debug, + ) { + } + + /** + * Generates the configuration tree builder. + */ + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('framework'); + $rootNode = $treeBuilder->getRootNode(); + + $rootNode + ->beforeNormalization() + ->ifTrue(fn ($v) => !isset($v['assets']) && isset($v['templating']) && class_exists(Package::class)) + ->then(function ($v) { + $v['assets'] = []; + + return $v; + }) + ->end() + ->fixXmlConfig('enabled_locale') + ->children() + ->scalarNode('secret')->end() + ->booleanNode('http_method_override') + ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead") + ->defaultFalse() + ->end() + ->scalarNode('trust_x_sendfile_type_header') + ->info('Set true to enable support for xsendfile in binary file responses.') + ->defaultFalse() + ->end() + ->scalarNode('ide')->defaultValue($this->debug ? '%env(default::SYMFONY_IDE)%' : null)->end() + ->booleanNode('test')->end() + ->scalarNode('default_locale')->defaultValue('en')->end() + ->booleanNode('set_locale_from_accept_language') + ->info('Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed).') + ->defaultFalse() + ->end() + ->booleanNode('set_content_language_from_locale') + ->info('Whether to set the Content-Language HTTP header on the Response using the Request locale.') + ->defaultFalse() + ->end() + ->arrayNode('enabled_locales') + ->info('Defines the possible locales for the application. This list is used for generating translations files, but also to restrict which locales are allowed when it is set from Accept-Language header (using "set_locale_from_accept_language").') + ->prototype('scalar')->end() + ->end() + ->arrayNode('trusted_hosts') + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() + ->prototype('scalar')->end() + ->end() + ->scalarNode('trusted_proxies') + ->beforeNormalization() + ->ifTrue(fn ($v) => 'private_ranges' === $v) + ->then(fn ($v) => implode(',', IpUtils::PRIVATE_SUBNETS)) + ->end() + ->end() + ->arrayNode('trusted_headers') + ->fixXmlConfig('trusted_header') + ->performNoDeepMerging() + ->defaultValue(['x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto']) + ->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end() + ->enumPrototype() + ->values([ + 'forwarded', + 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix', + ]) + ->end() + ->end() + ->scalarNode('error_controller') + ->defaultValue('error_controller') + ->end() + ->booleanNode('handle_all_throwables')->info('HttpKernel will handle all kinds of \Throwable')->defaultTrue()->end() + ->end() + ; + + $willBeAvailable = static function (string $package, string $class, ?string $parentPackage = null) { + $parentPackages = (array) $parentPackage; + $parentPackages[] = 'symfony/framework-bundle'; + + return ContainerBuilder::willBeAvailable($package, $class, $parentPackages); + }; + + $enableIfStandalone = fn (string $package, string $class) => !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; + + $this->addCsrfSection($rootNode); + $this->addFormSection($rootNode, $enableIfStandalone); + $this->addHttpCacheSection($rootNode); + $this->addEsiSection($rootNode); + $this->addSsiSection($rootNode); + $this->addFragmentsSection($rootNode); + $this->addProfilerSection($rootNode); + $this->addWorkflowSection($rootNode); + $this->addRouterSection($rootNode); + $this->addSessionSection($rootNode); + $this->addRequestSection($rootNode); + $this->addAssetsSection($rootNode, $enableIfStandalone); + $this->addAssetMapperSection($rootNode, $enableIfStandalone); + $this->addTranslatorSection($rootNode, $enableIfStandalone); + $this->addValidationSection($rootNode, $enableIfStandalone); + $this->addAnnotationsSection($rootNode); + $this->addSerializerSection($rootNode, $enableIfStandalone); + $this->addPropertyAccessSection($rootNode, $willBeAvailable); + $this->addTypeInfoSection($rootNode, $enableIfStandalone); + $this->addPropertyInfoSection($rootNode, $enableIfStandalone); + $this->addCacheSection($rootNode, $willBeAvailable); + $this->addPhpErrorsSection($rootNode); + $this->addExceptionsSection($rootNode); + $this->addWebLinkSection($rootNode, $enableIfStandalone); + $this->addLockSection($rootNode, $enableIfStandalone); + $this->addSemaphoreSection($rootNode, $enableIfStandalone); + $this->addMessengerSection($rootNode, $enableIfStandalone); + $this->addSchedulerSection($rootNode, $enableIfStandalone); + $this->addRobotsIndexSection($rootNode); + $this->addHttpClientSection($rootNode, $enableIfStandalone); + $this->addMailerSection($rootNode, $enableIfStandalone); + $this->addSecretsSection($rootNode); + $this->addNotifierSection($rootNode, $enableIfStandalone); + $this->addRateLimiterSection($rootNode, $enableIfStandalone); + $this->addUidSection($rootNode, $enableIfStandalone); + $this->addHtmlSanitizerSection($rootNode, $enableIfStandalone); + $this->addWebhookSection($rootNode, $enableIfStandalone); + $this->addRemoteEventSection($rootNode, $enableIfStandalone); + + return $treeBuilder; + } + + private function addSecretsSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('secrets') + ->canBeDisabled() + ->children() + ->scalarNode('vault_directory')->defaultValue('%kernel.project_dir%/config/secrets/%kernel.runtime_environment%')->cannotBeEmpty()->end() + ->scalarNode('local_dotenv_file')->defaultValue('%kernel.project_dir%/.env.%kernel.environment%.local')->end() + ->scalarNode('decryption_env_var')->defaultValue('base64:default::SYMFONY_DECRYPTION_SECRET')->end() + ->end() + ->end() + ->end() + ; + } + + private function addCsrfSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('csrf_protection') + ->treatFalseLike(['enabled' => false]) + ->treatTrueLike(['enabled' => true]) + ->treatNullLike(['enabled' => true]) + ->addDefaultsIfNotSet() + ->children() + // defaults to framework.session.enabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class) + ->booleanNode('enabled')->defaultNull()->end() + ->end() + ->end() + ->end() + ; + } + + private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('form') + ->info('form configuration') + ->{$enableIfStandalone('symfony/form', Form::class)}() + ->children() + ->arrayNode('csrf_protection') + ->treatFalseLike(['enabled' => false]) + ->treatTrueLike(['enabled' => true]) + ->treatNullLike(['enabled' => true]) + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('enabled')->defaultNull()->end() // defaults to framework.csrf_protection.enabled + ->scalarNode('field_name')->defaultValue('_token')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('http_cache') + ->info('HTTP cache configuration') + ->canBeEnabled() + ->fixXmlConfig('private_header') + ->children() + ->booleanNode('debug')->defaultValue('%kernel.debug%')->end() + ->enumNode('trace_level') + ->values(['none', 'short', 'full']) + ->end() + ->scalarNode('trace_header')->end() + ->integerNode('default_ttl')->end() + ->arrayNode('private_headers') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() + ->arrayNode('skip_response_headers') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() + ->booleanNode('allow_reload')->end() + ->booleanNode('allow_revalidate')->end() + ->integerNode('stale_while_revalidate')->end() + ->integerNode('stale_if_error')->end() + ->booleanNode('terminate_on_cache_hit')->end() + ->end() + ->end() + ->end() + ; + } + + private function addEsiSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('esi') + ->info('esi configuration') + ->canBeEnabled() + ->end() + ->end() + ; + } + + private function addSsiSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('ssi') + ->info('ssi configuration') + ->canBeEnabled() + ->end() + ->end(); + } + + private function addFragmentsSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('fragments') + ->info('fragments configuration') + ->canBeEnabled() + ->children() + ->scalarNode('hinclude_default_template')->defaultNull()->end() + ->scalarNode('path')->defaultValue('/_fragment')->end() + ->end() + ->end() + ->end() + ; + } + + private function addProfilerSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('profiler') + ->info('profiler configuration') + ->canBeEnabled() + ->children() + ->booleanNode('collect')->defaultTrue()->end() + ->scalarNode('collect_parameter')->defaultNull()->info('The name of the parameter to use to enable or disable collection on a per request basis')->end() + ->booleanNode('only_exceptions')->defaultFalse()->end() + ->booleanNode('only_main_requests')->defaultFalse()->end() + ->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end() + ->booleanNode('collect_serializer_data')->info('Enables the serializer data collector and profiler panel')->defaultFalse()->end() + ->end() + ->end() + ->end() + ; + } + + private function addWorkflowSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('workflow') + ->children() + ->arrayNode('workflows') + ->canBeEnabled() + ->beforeNormalization() + ->always(function ($v) { + if (\is_array($v) && true === $v['enabled']) { + $workflows = $v; + unset($workflows['enabled']); + + if (1 === \count($workflows) && isset($workflows[0]['enabled']) && 1 === \count($workflows[0])) { + $workflows = []; + } + + if (1 === \count($workflows) && isset($workflows['workflows']) && !array_is_list($workflows['workflows']) && array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions'])) { + $workflows = $workflows['workflows']; + } + + foreach ($workflows as $key => $workflow) { + if (isset($workflow['enabled']) && false === $workflow['enabled']) { + throw new LogicException(sprintf('Cannot disable a single workflow. Remove the configuration for the workflow "%s" instead.', $key)); + } + + unset($workflows[$key]['enabled']); + } + + $v = [ + 'enabled' => true, + 'workflows' => $workflows, + ]; + } + + return $v; + }) + ->end() + ->children() + ->arrayNode('workflows') + ->useAttributeAsKey('name') + ->prototype('array') + ->fixXmlConfig('support') + ->fixXmlConfig('place') + ->fixXmlConfig('transition') + ->fixXmlConfig('event_to_dispatch', 'events_to_dispatch') + ->children() + ->arrayNode('audit_trail') + ->canBeEnabled() + ->end() + ->enumNode('type') + ->values(['workflow', 'state_machine']) + ->defaultValue('state_machine') + ->end() + ->arrayNode('marking_store') + ->children() + ->enumNode('type') + ->values(['method']) + ->end() + ->scalarNode('property') + ->cannotBeEmpty() + ->end() + ->scalarNode('service') + ->cannotBeEmpty() + ->end() + ->end() + ->end() + ->arrayNode('supports') + ->beforeNormalization() + ->ifString() + ->then(fn ($v) => [$v]) + ->end() + ->prototype('scalar') + ->cannotBeEmpty() + ->validate() + ->ifTrue(fn ($v) => !class_exists($v) && !interface_exists($v, false)) + ->thenInvalid('The supported class or interface "%s" does not exist.') + ->end() + ->end() + ->end() + ->scalarNode('support_strategy') + ->cannotBeEmpty() + ->end() + ->arrayNode('initial_marking') + ->beforeNormalization()->castToArray()->end() + ->defaultValue([]) + ->prototype('scalar')->end() + ->end() + ->variableNode('events_to_dispatch') + ->defaultValue(null) + ->validate() + ->ifTrue(function ($v) { + if (null === $v) { + return false; + } + if (!\is_array($v)) { + return true; + } + + foreach ($v as $value) { + if (!\is_string($value)) { + return true; + } + if (class_exists(WorkflowEvents::class) && !\in_array($value, WorkflowEvents::ALIASES, true)) { + return true; + } + } + + return false; + }) + ->thenInvalid('The value must be "null" or an array of workflow events (like ["workflow.enter"]).') + ->end() + ->info('Select which Transition events should be dispatched for this Workflow') + ->example(['workflow.enter', 'workflow.transition']) + ->end() + ->arrayNode('places') + ->beforeNormalization() + ->always() + ->then(function ($places) { + if (!\is_array($places)) { + throw new InvalidConfigurationException('The "places" option must be an array in workflow configuration.'); + } + + // It's an indexed array of shape ['place1', 'place2'] + if (isset($places[0]) && \is_string($places[0])) { + return array_map(function (string $place) { + return ['name' => $place]; + }, $places); + } + + // It's an indexed array, we let the validation occur + if (isset($places[0]) && \is_array($places[0])) { + return $places; + } + + foreach ($places as $name => $place) { + if (\is_array($place) && \array_key_exists('name', $place)) { + continue; + } + $place['name'] = $name; + $places[$name] = $place; + } + + return array_values($places); + }) + ->end() + ->prototype('array') + ->children() + ->scalarNode('name') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->arrayNode('metadata') + ->normalizeKeys(false) + ->defaultValue([]) + ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) + ->prototype('variable') + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('transitions') + ->beforeNormalization() + ->always() + ->then(function ($transitions) { + if (!\is_array($transitions)) { + throw new InvalidConfigurationException('The "transitions" option must be an array in workflow configuration.'); + } + + // It's an indexed array, we let the validation occur + if (isset($transitions[0]) && \is_array($transitions[0])) { + return $transitions; + } + + foreach ($transitions as $name => $transition) { + if (\is_array($transition) && \array_key_exists('name', $transition)) { + continue; + } + $transition['name'] = $name; + $transitions[$name] = $transition; + } + + return $transitions; + }) + ->end() + ->isRequired() + ->requiresAtLeastOneElement() + ->prototype('array') + ->children() + ->scalarNode('name') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('guard') + ->cannotBeEmpty() + ->info('An expression to block the transition') + ->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') + ->end() + ->arrayNode('from') + ->beforeNormalization() + ->ifString() + ->then(fn ($v) => [$v]) + ->end() + ->requiresAtLeastOneElement() + ->prototype('scalar') + ->cannotBeEmpty() + ->end() + ->end() + ->arrayNode('to') + ->beforeNormalization() + ->ifString() + ->then(fn ($v) => [$v]) + ->end() + ->requiresAtLeastOneElement() + ->prototype('scalar') + ->cannotBeEmpty() + ->end() + ->end() + ->arrayNode('metadata') + ->normalizeKeys(false) + ->defaultValue([]) + ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) + ->prototype('variable') + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('metadata') + ->normalizeKeys(false) + ->defaultValue([]) + ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) + ->prototype('variable') + ->end() + ->end() + ->end() + ->validate() + ->ifTrue(function ($v) { + return $v['supports'] && isset($v['support_strategy']); + }) + ->thenInvalid('"supports" and "support_strategy" cannot be used together.') + ->end() + ->validate() + ->ifTrue(function ($v) { + return !$v['supports'] && !isset($v['support_strategy']); + }) + ->thenInvalid('"supports" or "support_strategy" should be configured.') + ->end() + ->beforeNormalization() + ->always() + ->then(function ($values) { + // Special case to deal with XML when the user wants an empty array + if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) { + $values['events_to_dispatch'] = []; + unset($values['event_to_dispatch']); + } + + return $values; + }) + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRouterSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('router') + ->info('router configuration') + ->canBeEnabled() + ->children() + ->scalarNode('resource')->isRequired()->end() + ->scalarNode('type')->end() + ->scalarNode('cache_dir') + ->defaultValue('%kernel.build_dir%') + ->setDeprecated('symfony/framework-bundle', '7.1', 'Setting the "%path%.%node%" configuration option is deprecated. It will be removed in version 8.0.') + ->end() + ->scalarNode('default_uri') + ->info('The default URI used to generate URLs in a non-HTTP context') + ->defaultNull() + ->end() + ->scalarNode('http_port')->defaultValue(80)->end() + ->scalarNode('https_port')->defaultValue(443)->end() + ->scalarNode('strict_requirements') + ->info( + "set to true to throw an exception when a parameter does not match the requirements\n". + "set to false to disable exceptions when a parameter does not match the requirements (and return null instead)\n". + "set to null to disable parameter checks against requirements\n". + "'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production" + ) + ->defaultTrue() + ->end() + ->booleanNode('utf8')->defaultTrue()->end() + ->end() + ->end() + ->end() + ; + } + + private function addSessionSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('session') + ->info('session configuration') + ->canBeEnabled() + ->children() + ->scalarNode('storage_factory_id')->defaultValue('session.storage.factory.native')->end() + ->scalarNode('handler_id') + ->info('Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null.') + ->end() + ->scalarNode('name') + ->validate() + ->ifTrue(function ($v) { + parse_str($v, $parsed); + + return implode('&', array_keys($parsed)) !== (string) $v; + }) + ->thenInvalid('Session name %s contains illegal character(s)') + ->end() + ->end() + ->scalarNode('cookie_lifetime')->end() + ->scalarNode('cookie_path')->end() + ->scalarNode('cookie_domain')->end() + ->enumNode('cookie_secure')->values([true, false, 'auto'])->defaultValue('auto')->end() + ->booleanNode('cookie_httponly')->defaultTrue()->end() + ->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultValue('lax')->end() + ->booleanNode('use_cookies')->end() + ->scalarNode('gc_divisor')->end() + ->scalarNode('gc_probability')->defaultValue(1)->end() + ->scalarNode('gc_maxlifetime')->end() + ->scalarNode('save_path') + ->info('Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null') + ->end() + ->integerNode('metadata_update_threshold') + ->defaultValue(0) + ->info('seconds to wait between 2 session metadata updates') + ->end() + ->integerNode('sid_length') + ->min(22) + ->max(256) + ->end() + ->integerNode('sid_bits_per_character') + ->min(4) + ->max(6) + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRequestSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('request') + ->info('request configuration') + ->canBeEnabled() + ->fixXmlConfig('format') + ->children() + ->arrayNode('formats') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && isset($v['mime_type'])) + ->then(fn ($v) => $v['mime_type']) + ->end() + ->beforeNormalization()->castToArray()->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('assets') + ->info('assets configuration') + ->{$enableIfStandalone('symfony/asset', Package::class)}() + ->fixXmlConfig('base_url') + ->children() + ->booleanNode('strict_mode') + ->info('Throw an exception if an entry is missing from the manifest.json') + ->defaultFalse() + ->end() + ->scalarNode('version_strategy')->defaultNull()->end() + ->scalarNode('version')->defaultNull()->end() + ->scalarNode('version_format')->defaultValue('%%s?%%s')->end() + ->scalarNode('json_manifest_path')->defaultNull()->end() + ->scalarNode('base_path')->defaultValue('')->end() + ->arrayNode('base_urls') + ->requiresAtLeastOneElement() + ->beforeNormalization()->castToArray()->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->validate() + ->ifTrue(function ($v) { + return isset($v['version_strategy']) && isset($v['version']); + }) + ->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets".') + ->end() + ->validate() + ->ifTrue(function ($v) { + return isset($v['version_strategy']) && isset($v['json_manifest_path']); + }) + ->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets".') + ->end() + ->validate() + ->ifTrue(function ($v) { + return isset($v['version']) && isset($v['json_manifest_path']); + }) + ->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets".') + ->end() + ->fixXmlConfig('package') + ->children() + ->arrayNode('packages') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('array') + ->fixXmlConfig('base_url') + ->children() + ->booleanNode('strict_mode') + ->info('Throw an exception if an entry is missing from the manifest.json') + ->defaultFalse() + ->end() + ->scalarNode('version_strategy')->defaultNull()->end() + ->scalarNode('version') + ->beforeNormalization() + ->ifTrue(fn ($v) => '' === $v) + ->then(fn () => null) + ->end() + ->end() + ->scalarNode('version_format')->defaultNull()->end() + ->scalarNode('json_manifest_path')->defaultNull()->end() + ->scalarNode('base_path')->defaultValue('')->end() + ->arrayNode('base_urls') + ->requiresAtLeastOneElement() + ->beforeNormalization()->castToArray()->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->validate() + ->ifTrue(function ($v) { + return isset($v['version_strategy']) && isset($v['version']); + }) + ->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets" packages.') + ->end() + ->validate() + ->ifTrue(function ($v) { + return isset($v['version_strategy']) && isset($v['json_manifest_path']); + }) + ->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets" packages.') + ->end() + ->validate() + ->ifTrue(function ($v) { + return isset($v['version']) && isset($v['json_manifest_path']); + }) + ->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets" packages.') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addAssetMapperSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('asset_mapper') + ->info('Asset Mapper configuration') + ->{$enableIfStandalone('symfony/asset-mapper', AssetMapper::class)}() + ->fixXmlConfig('path') + ->fixXmlConfig('excluded_pattern') + ->fixXmlConfig('extension') + ->fixXmlConfig('importmap_script_attribute') + ->children() + // add array node called "paths" that will be an array of strings + ->arrayNode('paths') + ->info('Directories that hold assets that should be in the mapper. Can be a simple array of an array of ["path/to/assets": "namespace"]') + ->example(['assets/']) + ->normalizeKeys(false) + ->useAttributeAsKey('namespace') + ->beforeNormalization() + ->always() + ->then(function ($v) { + $result = []; + foreach ($v as $key => $item) { + // "dir" => "namespace" + if (\is_string($key)) { + $result[$key] = $item; + + continue; + } + + if (\is_array($item)) { + // $item = ["namespace" => "the/namespace", "value" => "the/dir"] + $result[$item['value']] = $item['namespace'] ?? ''; + } else { + // $item = "the/dir" + $result[$item] = ''; + } + } + + return $result; + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('excluded_patterns') + ->info('Array of glob patterns of asset file paths that should not be in the asset mapper') + ->prototype('scalar')->end() + ->example(['*/assets/build/*', '*/*_.scss']) + ->end() + // boolean called defaulting to true + ->booleanNode('exclude_dotfiles') + ->info('If true, any files starting with "." will be excluded from the asset mapper') + ->defaultTrue() + ->end() + ->booleanNode('server') + ->info('If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default)') + ->defaultValue($this->debug) + ->end() + ->scalarNode('public_prefix') + ->info('The public path where the assets will be written to (and served from when "server" is true)') + ->defaultValue('/assets/') + ->end() + ->enumNode('missing_import_mode') + ->values(['strict', 'warn', 'ignore']) + ->info('Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import \'./non-existent.js\'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is.') + ->defaultValue('warn') + ->end() + ->arrayNode('extensions') + ->info('Key-value pair of file extensions set to their mime type.') + ->normalizeKeys(false) + ->useAttributeAsKey('extension') + ->example(['.zip' => 'application/zip']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('importmap_path') + ->info('The path of the importmap.php file.') + ->defaultValue('%kernel.project_dir%/importmap.php') + ->end() + ->scalarNode('importmap_polyfill') + ->info('The importmap name that will be used to load the polyfill. Set to false to disable.') + ->validate() + ->ifTrue() + ->thenInvalid('Invalid "importmap_polyfill" value. Must be either an importmap name or false.') + ->end() + ->defaultValue('es-module-shims') + ->end() + ->arrayNode('importmap_script_attributes') + ->info('Key-value pair of attributes to add to script tags output for the importmap.') + ->normalizeKeys(false) + ->useAttributeAsKey('key') + ->example(['data-turbo-track' => 'reload']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('vendor_dir') + ->info('The directory to store JavaScript vendors.') + ->defaultValue('%kernel.project_dir%/assets/vendor') + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('translator') + ->info('translator configuration') + ->{$enableIfStandalone('symfony/translation', Translator::class)}() + ->fixXmlConfig('fallback') + ->fixXmlConfig('path') + ->fixXmlConfig('provider') + ->children() + ->arrayNode('fallbacks') + ->info('Defaults to the value of "default_locale".') + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() + ->prototype('scalar')->end() + ->defaultValue([]) + ->end() + ->booleanNode('logging')->defaultValue(false)->end() + ->scalarNode('formatter')->defaultValue('translator.formatter.default')->end() + ->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%/translations')->end() + ->scalarNode('default_path') + ->info('The default path used to load translations') + ->defaultValue('%kernel.project_dir%/translations') + ->end() + ->arrayNode('paths') + ->prototype('scalar')->end() + ->end() + ->arrayNode('pseudo_localization') + ->canBeEnabled() + ->fixXmlConfig('localizable_html_attribute') + ->children() + ->booleanNode('accents')->defaultTrue()->end() + ->floatNode('expansion_factor') + ->min(1.0) + ->defaultValue(1.0) + ->end() + ->booleanNode('brackets')->defaultTrue()->end() + ->booleanNode('parse_html')->defaultFalse()->end() + ->arrayNode('localizable_html_attributes') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->arrayNode('providers') + ->info('Translation providers you can read/write your translations from') + ->useAttributeAsKey('name') + ->prototype('array') + ->fixXmlConfig('domain') + ->fixXmlConfig('locale') + ->children() + ->scalarNode('dsn')->end() + ->arrayNode('domains') + ->prototype('scalar')->end() + ->defaultValue([]) + ->end() + ->arrayNode('locales') + ->prototype('scalar')->end() + ->defaultValue([]) + ->info('If not set, all locales listed under framework.enabled_locales are used.') + ->end() + ->end() + ->end() + ->defaultValue([]) + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('validation') + ->info('validation configuration') + ->{$enableIfStandalone('symfony/validator', Validation::class)}() + ->children() + ->scalarNode('cache')->end() + ->booleanNode('enable_attributes')->{class_exists(FullStack::class) ? 'defaultFalse' : 'defaultTrue'}()->end() + ->arrayNode('static_method') + ->defaultValue(['loadValidatorMetadata']) + ->prototype('scalar')->end() + ->treatFalseLike([]) + ->validate()->castToArray()->end() + ->end() + ->scalarNode('translation_domain')->defaultValue('validators')->end() + ->enumNode('email_validation_mode')->values(['html5', 'loose', 'strict'])->defaultValue('html5')->end() + ->arrayNode('mapping') + ->addDefaultsIfNotSet() + ->fixXmlConfig('path') + ->children() + ->arrayNode('paths') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->arrayNode('not_compromised_password') + ->canBeDisabled() + ->children() + ->booleanNode('enabled') + ->defaultTrue() + ->info('When disabled, compromised passwords will be accepted as valid.') + ->end() + ->scalarNode('endpoint') + ->defaultNull() + ->info('API endpoint for the NotCompromisedPassword Validator.') + ->end() + ->end() + ->end() + ->arrayNode('auto_mapping') + ->info('A collection of namespaces for which auto-mapping will be enabled by default, or null to opt-in with the EnableAutoMapping constraint.') + ->example([ + 'App\\Entity\\' => [], + 'App\\WithSpecificLoaders\\' => ['validator.property_info_loader'], + ]) + ->useAttributeAsKey('namespace') + ->normalizeKeys(false) + ->beforeNormalization() + ->ifArray() + ->then(function (array $values): array { + foreach ($values as $k => $v) { + if (isset($v['service'])) { + continue; + } + + if (isset($v['namespace'])) { + $values[$k]['services'] = []; + continue; + } + + if (!\is_array($v)) { + $values[$v]['services'] = []; + unset($values[$k]); + continue; + } + + $tmp = $v; + unset($values[$k]); + $values[$k]['services'] = $tmp; + } + + return $values; + }) + ->end() + ->arrayPrototype() + ->fixXmlConfig('service') + ->children() + ->arrayNode('services') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addAnnotationsSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('annotations') + ->canBeEnabled() + ->validate() + ->ifTrue(static fn (array $v) => $v['enabled']) + ->thenInvalid('Enabling the doctrine/annotations integration is not supported anymore.') + ->end() + ->end() + ; + } + + private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('serializer') + ->info('serializer configuration') + ->{$enableIfStandalone('symfony/serializer', Serializer::class)}() + ->children() + ->booleanNode('enable_attributes')->{class_exists(FullStack::class) ? 'defaultFalse' : 'defaultTrue'}()->end() + ->scalarNode('name_converter')->end() + ->scalarNode('circular_reference_handler')->end() + ->scalarNode('max_depth_handler')->end() + ->arrayNode('mapping') + ->addDefaultsIfNotSet() + ->fixXmlConfig('path') + ->children() + ->arrayNode('paths') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->arrayNode('default_context') + ->normalizeKeys(false) + ->validate() + ->ifTrue(fn () => $this->debug && class_exists(JsonParser::class)) + ->then(fn (array $v) => $v + [JsonDecode::DETAILED_ERROR_MESSAGES => true]) + ->end() + ->defaultValue([]) + ->prototype('variable')->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void + { + $rootNode + ->children() + ->arrayNode('property_access') + ->addDefaultsIfNotSet() + ->info('Property access configuration') + ->{$willBeAvailable('symfony/property-access', PropertyAccessor::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->children() + ->booleanNode('magic_call')->defaultFalse()->end() + ->booleanNode('magic_get')->defaultTrue()->end() + ->booleanNode('magic_set')->defaultTrue()->end() + ->booleanNode('throw_exception_on_invalid_index')->defaultFalse()->end() + ->booleanNode('throw_exception_on_invalid_property_path')->defaultTrue()->end() + ->end() + ->end() + ->end() + ; + } + + private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('property_info') + ->info('Property info configuration') + ->{$enableIfStandalone('symfony/property-info', PropertyInfoExtractorInterface::class)}() + ->end() + ->end() + ; + } + + private function addTypeInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('type_info') + ->info('Type info configuration') + ->{$enableIfStandalone('symfony/type-info', Type::class)}() + ->end() + ->end() + ; + } + + private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void + { + $rootNode + ->children() + ->arrayNode('cache') + ->info('Cache configuration') + ->addDefaultsIfNotSet() + ->fixXmlConfig('pool') + ->children() + ->scalarNode('prefix_seed') + ->info('Used to namespace cache keys when using several apps with the same shared backend') + ->defaultValue('_%kernel.project_dir%.%kernel.container_class%') + ->example('my-application-name/%kernel.environment%') + ->end() + ->scalarNode('app') + ->info('App related cache pools configuration') + ->defaultValue('cache.adapter.filesystem') + ->end() + ->scalarNode('system') + ->info('System related cache pools configuration') + ->defaultValue('cache.adapter.system') + ->end() + ->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools/app')->end() + ->scalarNode('default_psr6_provider')->end() + ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() + ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() + ->scalarNode('default_doctrine_dbal_provider')->defaultValue('database_connection')->end() + ->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null)->end() + ->arrayNode('pools') + ->useAttributeAsKey('name') + ->prototype('array') + ->fixXmlConfig('adapter') + ->beforeNormalization() + ->ifTrue(fn ($v) => isset($v['provider']) && \is_array($v['adapters'] ?? $v['adapter'] ?? null) && 1 < \count($v['adapters'] ?? $v['adapter'])) + ->thenInvalid('Pool cannot have a "provider" while more than one adapter is defined') + ->end() + ->children() + ->arrayNode('adapters') + ->performNoDeepMerging() + ->info('One or more adapters to chain for creating the pool, defaults to "cache.app".') + ->beforeNormalization()->castToArray()->end() + ->beforeNormalization() + ->always()->then(function ($values) { + if ([0] === array_keys($values) && \is_array($values[0])) { + return $values[0]; + } + $adapters = []; + + foreach ($values as $k => $v) { + if (\is_int($k) && \is_string($v)) { + $adapters[] = $v; + } elseif (!\is_array($v)) { + $adapters[$k] = $v; + } elseif (isset($v['provider'])) { + $adapters[$v['provider']] = $v['name'] ?? $v; + } else { + $adapters[] = $v['name'] ?? $v; + } + } + + return $adapters; + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->scalarNode('tags')->defaultNull()->end() + ->booleanNode('public')->defaultFalse()->end() + ->scalarNode('default_lifetime') + ->info('Default lifetime of the pool') + ->example('"300" for 5 minutes expressed in seconds, "PT5M" for five minutes expressed as ISO 8601 time interval, or "5 minutes" as a date expression') + ->end() + ->scalarNode('provider') + ->info('Overwrite the setting from the default provider for this adapter.') + ->end() + ->scalarNode('early_expiration_message_bus') + ->example('"messenger.default_bus" to send early expiration events to the default Messenger bus.') + ->end() + ->scalarNode('clearer')->end() + ->end() + ->end() + ->validate() + ->ifTrue(fn ($v) => isset($v['cache.app']) || isset($v['cache.system'])) + ->thenInvalid('"cache.app" and "cache.system" are reserved names') + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addPhpErrorsSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('php_errors') + ->info('PHP errors handling configuration') + ->addDefaultsIfNotSet() + ->children() + ->variableNode('log') + ->info('Use the application logger instead of the PHP logger for logging PHP errors.') + ->example('"true" to use the default configuration: log all errors. "false" to disable. An integer bit field of E_* constants, or an array mapping E_* constants to log levels.') + ->treatNullLike($this->debug) + ->defaultTrue() + ->beforeNormalization() + ->ifArray() + ->then(function (array $v): array { + if (!($v[0]['type'] ?? false)) { + return $v; + } + + // Fix XML normalization + + $ret = []; + foreach ($v as ['type' => $type, 'logLevel' => $logLevel]) { + $ret[$type] = $logLevel; + } + + return $ret; + }) + ->end() + ->validate() + ->ifTrue(fn ($v) => !(\is_int($v) || \is_bool($v) || \is_array($v))) + ->thenInvalid('The "php_errors.log" parameter should be either an integer, a boolean, or an array') + ->end() + ->end() + ->booleanNode('throw') + ->info('Throw PHP errors as \ErrorException instances.') + ->defaultValue($this->debug) + ->treatNullLike($this->debug) + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addExceptionsSection(ArrayNodeDefinition $rootNode): void + { + $logLevels = (new \ReflectionClass(LogLevel::class))->getConstants(); + + $rootNode + ->fixXmlConfig('exception') + ->children() + ->arrayNode('exceptions') + ->info('Exception handling configuration') + ->useAttributeAsKey('class') + ->prototype('array') + ->children() + ->scalarNode('log_level') + ->info('The level of log message. Null to let Symfony decide.') + ->validate() + ->ifTrue(fn ($v) => null !== $v && !\in_array($v, $logLevels, true)) + ->thenInvalid(sprintf('The log level is not valid. Pick one among "%s".', implode('", "', $logLevels))) + ->end() + ->defaultNull() + ->end() + ->scalarNode('status_code') + ->info('The status code of the response. Null or 0 to let Symfony decide.') + ->beforeNormalization() + ->ifTrue(fn ($v) => 0 === $v) + ->then(fn ($v) => null) + ->end() + ->validate() + ->ifTrue(fn ($v) => null !== $v && ($v < 100 || $v > 599)) + ->thenInvalid('The status code is not valid. Pick a value between 100 and 599.') + ->end() + ->defaultNull() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('lock') + ->info('Lock configuration') + ->{$enableIfStandalone('symfony/lock', Lock::class)}() + ->beforeNormalization() + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) + ->then(function ($v) { + $e = $v['enabled']; + unset($v['enabled']); + + return ['enabled' => $e, 'resources' => $v]; + }) + ->end() + ->addDefaultsIfNotSet() + ->validate() + ->ifTrue(fn ($config) => $config['enabled'] && !$config['resources']) + ->thenInvalid('At least one resource must be defined.') + ->end() + ->fixXmlConfig('resource') + ->children() + ->arrayNode('resources') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->defaultValue(['default' => [class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock']]) + ->beforeNormalization() + ->ifString()->then(fn ($v) => ['default' => $v]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) + ->then(function ($v) { + $resources = []; + foreach ($v as $resource) { + $resources[] = \is_array($resource) && isset($resource['name']) + ? [$resource['name'] => $resource['value']] + : ['default' => $resource] + ; + } + + return array_merge_recursive([], ...$resources); + }) + ->end() + ->prototype('array') + ->performNoDeepMerging() + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('semaphore') + ->info('Semaphore configuration') + ->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}() + ->beforeNormalization() + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) + ->then(function ($v) { + $e = $v['enabled']; + unset($v['enabled']); + + return ['enabled' => $e, 'resources' => $v]; + }) + ->end() + ->addDefaultsIfNotSet() + ->fixXmlConfig('resource') + ->children() + ->arrayNode('resources') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->requiresAtLeastOneElement() + ->beforeNormalization() + ->ifString()->then(fn ($v) => ['default' => $v]) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) + ->then(function ($v) { + $resources = []; + foreach ($v as $resource) { + $resources[] = \is_array($resource) && isset($resource['name']) + ? [$resource['name'] => $resource['value']] + : ['default' => $resource] + ; + } + + return array_merge_recursive([], ...$resources); + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('web_link') + ->info('web links configuration') + ->{$enableIfStandalone('symfony/weblink', HttpHeaderSerializer::class)}() + ->end() + ->end() + ; + } + + private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('messenger') + ->info('Messenger configuration') + ->{$enableIfStandalone('symfony/messenger', MessageBusInterface::class)}() + ->fixXmlConfig('transport') + ->fixXmlConfig('bus', 'buses') + ->validate() + ->ifTrue(fn ($v) => isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']) + ->thenInvalid('You must specify the "default_bus" if you define more than one bus.') + ->end() + ->validate() + ->ifTrue(fn ($v) => isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']])) + ->then(fn ($v) => throw new InvalidConfigurationException(sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], implode('", "', array_keys($v['buses']))))) + ->end() + ->children() + ->arrayNode('routing') + ->normalizeKeys(false) + ->useAttributeAsKey('message_class') + ->beforeNormalization() + ->always() + ->then(function ($config) { + if (!\is_array($config)) { + return []; + } + // If XML config with only one routing attribute + if (2 === \count($config) && isset($config['message-class']) && isset($config['sender'])) { + $config = [0 => $config]; + } + + $newConfig = []; + foreach ($config as $k => $v) { + if (!\is_int($k)) { + $newConfig[$k] = [ + 'senders' => $v['senders'] ?? (\is_array($v) ? array_values($v) : [$v]), + ]; + } else { + $newConfig[$v['message-class']]['senders'] = array_map( + function ($a) { + return \is_string($a) ? $a : $a['service']; + }, + array_values($v['sender']) + ); + } + } + + return $newConfig; + }) + ->end() + ->prototype('array') + ->performNoDeepMerging() + ->children() + ->arrayNode('senders') + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('serializer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('default_serializer') + ->defaultValue('messenger.transport.native_php_serializer') + ->info('Service id to use as the default serializer for the transports.') + ->end() + ->arrayNode('symfony_serializer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('format')->defaultValue('json')->info('Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default).')->end() + ->arrayNode('context') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->defaultValue([]) + ->info('Context array for the messenger.transport.symfony_serializer service (which is not the serializer used by default).') + ->prototype('variable')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('transports') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->beforeNormalization() + ->ifString() + ->then(function (string $dsn) { + return ['dsn' => $dsn]; + }) + ->end() + ->fixXmlConfig('option') + ->children() + ->scalarNode('dsn')->end() + ->scalarNode('serializer')->defaultNull()->info('Service id of a custom serializer to use.')->end() + ->arrayNode('options') + ->normalizeKeys(false) + ->defaultValue([]) + ->prototype('variable') + ->end() + ->end() + ->scalarNode('failure_transport') + ->defaultNull() + ->info('Transport name to send failed messages to (after all retries have failed).') + ->end() + ->arrayNode('retry_strategy') + ->addDefaultsIfNotSet() + ->beforeNormalization() + ->always(function ($v) { + if (isset($v['service']) && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) { + throw new \InvalidArgumentException('The "service" cannot be used along with the other "retry_strategy" options.'); + } + + return $v; + }) + ->end() + ->children() + ->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end() + ->integerNode('max_retries')->defaultValue(3)->min(0)->end() + ->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end() + ->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries))')->end() + ->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end() + ->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness to apply to the delay (between 0 and 1)')->end() + ->end() + ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use when processing messages') + ->end() + ->end() + ->end() + ->end() + ->scalarNode('failure_transport') + ->defaultNull() + ->info('Transport name to send failed messages to (after all retries have failed).') + ->end() + ->arrayNode('stop_worker_on_signals') + ->defaultValue([]) + ->info('A list of signals that should stop the worker; defaults to SIGTERM and SIGINT.') + ->integerPrototype()->end() + ->end() + ->scalarNode('default_bus')->defaultNull()->end() + ->arrayNode('buses') + ->defaultValue(['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]]) + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('default_middleware') + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_string($v) || \is_bool($v)) + ->then(fn ($v) => [ + 'enabled' => 'allow_no_handlers' === $v ? true : $v, + 'allow_no_handlers' => 'allow_no_handlers' === $v, + 'allow_no_senders' => true, + ]) + ->end() + ->canBeDisabled() + ->children() + ->booleanNode('allow_no_handlers')->defaultFalse()->end() + ->booleanNode('allow_no_senders')->defaultTrue()->end() + ->end() + ->end() + ->arrayNode('middleware') + ->performNoDeepMerging() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_string($v) || (\is_array($v) && !\is_int(key($v)))) + ->then(fn ($v) => [$v]) + ->end() + ->defaultValue([]) + ->arrayPrototype() + ->beforeNormalization() + ->always() + ->then(function ($middleware): array { + if (!\is_array($middleware)) { + return ['id' => $middleware]; + } + if (isset($middleware['id'])) { + return $middleware; + } + if (1 < \count($middleware)) { + throw new \InvalidArgumentException('Invalid middleware at path "framework.messenger": a map with a single factory id as key and its arguments as value was expected, '.json_encode($middleware).' given.'); + } + + return [ + 'id' => key($middleware), + 'arguments' => current($middleware), + ]; + }) + ->end() + ->fixXmlConfig('argument') + ->children() + ->scalarNode('id')->isRequired()->cannotBeEmpty()->end() + ->arrayNode('arguments') + ->normalizeKeys(false) + ->defaultValue([]) + ->prototype('variable') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addSchedulerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('scheduler') + ->info('Scheduler configuration') + ->{$enableIfStandalone('symfony/scheduler', Schedule::class)}() + ->end() + ->end() + ; + } + + private function addRobotsIndexSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->booleanNode('disallow_search_engine_index') + ->info('Enabled by default when debug is enabled.') + ->defaultValue($this->debug) + ->treatNullLike($this->debug) + ->end() + ->end() + ; + } + + private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('http_client') + ->info('HTTP Client configuration') + ->{$enableIfStandalone('symfony/http-client', HttpClient::class)}() + ->fixXmlConfig('scoped_client') + ->beforeNormalization() + ->always(function ($config) { + if (empty($config['scoped_clients'])) { + return $config; + } + + $hasDefaultRateLimiter = isset($config['default_options']['rate_limiter']); + $hasDefaultRetryFailed = \is_array($config['default_options']['retry_failed'] ?? null); + + if (!$hasDefaultRateLimiter && !$hasDefaultRetryFailed) { + return $config; + } + + foreach ($config['scoped_clients'] as &$scopedConfig) { + if ($hasDefaultRateLimiter) { + if (!isset($scopedConfig['rate_limiter']) || true === $scopedConfig['rate_limiter']) { + $scopedConfig['rate_limiter'] = $config['default_options']['rate_limiter']; + } elseif (false === $scopedConfig['rate_limiter']) { + $scopedConfig['rate_limiter'] = null; + } + } + + if ($hasDefaultRetryFailed) { + if (!isset($scopedConfig['retry_failed']) || true === $scopedConfig['retry_failed']) { + $scopedConfig['retry_failed'] = $config['default_options']['retry_failed']; + } elseif (\is_array($scopedConfig['retry_failed'])) { + $scopedConfig['retry_failed'] += $config['default_options']['retry_failed']; + } + } + } + + return $config; + }) + ->end() + ->children() + ->integerNode('max_host_connections') + ->info('The maximum number of connections to a single host.') + ->end() + ->arrayNode('default_options') + ->fixXmlConfig('header') + ->children() + ->arrayNode('headers') + ->info('Associative array: header => value(s).') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->arrayNode('vars') + ->info('Associative array: the default vars used to expand the templated URI.') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->integerNode('max_redirects') + ->info('The maximum number of redirects to follow.') + ->end() + ->scalarNode('http_version') + ->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.') + ->end() + ->arrayNode('resolve') + ->info('Associative array: domain => IP.') + ->useAttributeAsKey('host') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['host'], $config['value']) || \count($config) > 2) { + return $config; + } + + return [$config['host'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->scalarNode('proxy') + ->info('The URL of the proxy to pass requests through or null for automatic detection.') + ->end() + ->scalarNode('no_proxy') + ->info('A comma separated list of hosts that do not require a proxy to be reached.') + ->end() + ->floatNode('timeout') + ->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.') + ->end() + ->floatNode('max_duration') + ->info('The maximum execution time for the request+response as a whole.') + ->end() + ->scalarNode('bindto') + ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') + ->end() + ->booleanNode('verify_peer') + ->info('Indicates if the peer should be verified in a TLS context.') + ->end() + ->booleanNode('verify_host') + ->info('Indicates if the host should exist as a certificate common name.') + ->end() + ->scalarNode('cafile') + ->info('A certificate authority file.') + ->end() + ->scalarNode('capath') + ->info('A directory that contains multiple certificate authority files.') + ->end() + ->scalarNode('local_cert') + ->info('A PEM formatted certificate file.') + ->end() + ->scalarNode('local_pk') + ->info('A private key file.') + ->end() + ->scalarNode('passphrase') + ->info('The passphrase used to encrypt the "local_pk" file.') + ->end() + ->scalarNode('ciphers') + ->info('A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') + ->end() + ->arrayNode('peer_fingerprint') + ->info('Associative array: hashing algorithm => hash(es).') + ->normalizeKeys(false) + ->children() + ->variableNode('sha1')->end() + ->variableNode('pin-sha256')->end() + ->variableNode('md5')->end() + ->end() + ->end() + ->scalarNode('crypto_method') + ->info('The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants.') + ->end() + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use for throttling requests') + ->end() + ->append($this->createHttpClientRetrySection()) + ->end() + ->end() + ->scalarNode('mock_response_factory') + ->info('The id of the service that should generate mock responses. It should be either an invokable or an iterable.') + ->end() + ->arrayNode('scoped_clients') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->arrayPrototype() + ->fixXmlConfig('header') + ->beforeNormalization() + ->always() + ->then(function ($config) { + if (!class_exists(HttpClient::class)) { + throw new LogicException('HttpClient support cannot be enabled as the component is not installed. Try running "composer require symfony/http-client".'); + } + + return \is_array($config) ? $config : ['base_uri' => $config]; + }) + ->end() + ->validate() + ->ifTrue(fn ($v) => !isset($v['scope']) && !isset($v['base_uri'])) + ->thenInvalid('Either "scope" or "base_uri" should be defined.') + ->end() + ->validate() + ->ifTrue(fn ($v) => !empty($v['query']) && !isset($v['base_uri'])) + ->thenInvalid('"query" applies to "base_uri" but no base URI is defined.') + ->end() + ->children() + ->scalarNode('scope') + ->info('The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead.') + ->cannotBeEmpty() + ->end() + ->scalarNode('base_uri') + ->info('The URI to resolve relative URLs, following rules in RFC 3985, section 2.') + ->cannotBeEmpty() + ->end() + ->scalarNode('auth_basic') + ->info('An HTTP Basic authentication "username:password".') + ->end() + ->scalarNode('auth_bearer') + ->info('A token enabling HTTP Bearer authorization.') + ->end() + ->scalarNode('auth_ntlm') + ->info('A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension).') + ->end() + ->arrayNode('query') + ->info('Associative array of query string values merged with the base URI.') + ->useAttributeAsKey('key') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['key'], $config['value']) || \count($config) > 2) { + return $config; + } + + return [$config['key'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->arrayNode('headers') + ->info('Associative array: header => value(s).') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->integerNode('max_redirects') + ->info('The maximum number of redirects to follow.') + ->end() + ->scalarNode('http_version') + ->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.') + ->end() + ->arrayNode('resolve') + ->info('Associative array: domain => IP.') + ->useAttributeAsKey('host') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['host'], $config['value']) || \count($config) > 2) { + return $config; + } + + return [$config['host'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->scalarNode('proxy') + ->info('The URL of the proxy to pass requests through or null for automatic detection.') + ->end() + ->scalarNode('no_proxy') + ->info('A comma separated list of hosts that do not require a proxy to be reached.') + ->end() + ->floatNode('timeout') + ->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.') + ->end() + ->floatNode('max_duration') + ->info('The maximum execution time for the request+response as a whole.') + ->end() + ->scalarNode('bindto') + ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') + ->end() + ->booleanNode('verify_peer') + ->info('Indicates if the peer should be verified in a TLS context.') + ->end() + ->booleanNode('verify_host') + ->info('Indicates if the host should exist as a certificate common name.') + ->end() + ->scalarNode('cafile') + ->info('A certificate authority file.') + ->end() + ->scalarNode('capath') + ->info('A directory that contains multiple certificate authority files.') + ->end() + ->scalarNode('local_cert') + ->info('A PEM formatted certificate file.') + ->end() + ->scalarNode('local_pk') + ->info('A private key file.') + ->end() + ->scalarNode('passphrase') + ->info('The passphrase used to encrypt the "local_pk" file.') + ->end() + ->scalarNode('ciphers') + ->info('A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') + ->end() + ->arrayNode('peer_fingerprint') + ->info('Associative array: hashing algorithm => hash(es).') + ->normalizeKeys(false) + ->children() + ->variableNode('sha1')->end() + ->variableNode('pin-sha256')->end() + ->variableNode('md5')->end() + ->end() + ->end() + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use for throttling requests') + ->end() + ->append($this->createHttpClientRetrySection()) + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function createHttpClientRetrySection(): ArrayNodeDefinition + { + $root = new NodeBuilder(); + + return $root + ->arrayNode('retry_failed') + ->fixXmlConfig('http_code') + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->beforeNormalization() + ->always(function ($v) { + if (isset($v['retry_strategy']) && (isset($v['http_codes']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']) || isset($v['jitter']))) { + throw new \InvalidArgumentException('The "retry_strategy" option cannot be used along with the "http_codes", "delay", "multiplier", "max_delay" or "jitter" options.'); + } + + return $v; + }) + ->end() + ->children() + ->scalarNode('retry_strategy')->defaultNull()->info('service id to override the retry strategy')->end() + ->arrayNode('http_codes') + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() + ->then(static function ($v) { + $list = []; + foreach ($v as $key => $val) { + if (is_numeric($val)) { + $list[] = ['code' => $val]; + } elseif (\is_array($val)) { + if (isset($val['code']) || isset($val['methods'])) { + $list[] = $val; + } else { + $list[] = ['code' => $key, 'methods' => $val]; + } + } elseif (true === $val || null === $val) { + $list[] = ['code' => $key]; + } + } + + return $list; + }) + ->end() + ->useAttributeAsKey('code') + ->arrayPrototype() + ->fixXmlConfig('method') + ->children() + ->integerNode('code')->end() + ->arrayNode('methods') + ->beforeNormalization() + ->ifArray() + ->then(fn ($v) => array_map('strtoupper', $v)) + ->end() + ->prototype('scalar')->end() + ->info('A list of HTTP methods that triggers a retry for this status code. When empty, all methods are retried') + ->end() + ->end() + ->end() + ->info('A list of HTTP status code that triggers a retry') + ->end() + ->integerNode('max_retries')->defaultValue(3)->min(0)->end() + ->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end() + ->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries)')->end() + ->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end() + ->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness in percent (between 0 and 1) to apply to the delay')->end() + ->end() + ; + } + + private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('mailer') + ->info('Mailer configuration') + ->{$enableIfStandalone('symfony/mailer', Mailer::class)}() + ->validate() + ->ifTrue(fn ($v) => isset($v['dsn']) && \count($v['transports'])) + ->thenInvalid('"dsn" and "transports" cannot be used together.') + ->end() + ->fixXmlConfig('transport') + ->fixXmlConfig('header') + ->children() + ->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end() + ->scalarNode('dsn')->defaultNull()->end() + ->arrayNode('transports') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->arrayNode('envelope') + ->info('Mailer Envelope configuration') + ->fixXmlConfig('recipient') + ->fixXmlConfig('allowed_recipient') + ->children() + ->scalarNode('sender')->end() + ->arrayNode('recipients') + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() + ->then(fn ($v) => array_filter(array_values($v))) + ->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('allowed_recipients') + ->info('A list of regular expressions that allow recipients when "recipients" option is defined.') + ->example(['.*@example\.com']) + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() + ->then(fn ($v) => array_filter(array_values($v))) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->arrayNode('headers') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('array') + ->normalizeKeys(false) + ->beforeNormalization() + ->ifTrue(fn ($v) => !\is_array($v) || array_keys($v) !== ['value']) + ->then(fn ($v) => ['value' => $v]) + ->end() + ->children() + ->variableNode('value')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('notifier') + ->info('Notifier configuration') + ->{$enableIfStandalone('symfony/notifier', Notifier::class)}() + ->children() + ->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end() + ->end() + ->fixXmlConfig('chatter_transport') + ->children() + ->arrayNode('chatter_transports') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->end() + ->fixXmlConfig('texter_transport') + ->children() + ->arrayNode('texter_transports') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->end() + ->children() + ->booleanNode('notification_on_failed_messages')->defaultFalse()->end() + ->end() + ->children() + ->arrayNode('channel_policy') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('admin_recipient') + ->children() + ->arrayNode('admin_recipients') + ->prototype('array') + ->children() + ->scalarNode('email')->cannotBeEmpty()->end() + ->scalarNode('phone')->defaultValue('')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addWebhookSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('webhook') + ->info('Webhook configuration') + ->{$enableIfStandalone('symfony/webhook', WebhookController::class)}() + ->children() + ->scalarNode('message_bus')->defaultValue('messenger.default_bus')->info('The message bus to use.')->end() + ->arrayNode('routing') + ->normalizeKeys(false) + ->useAttributeAsKey('type') + ->prototype('array') + ->children() + ->scalarNode('service') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('secret') + ->defaultValue('') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRemoteEventSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('remote-event') + ->info('RemoteEvent configuration') + ->{$enableIfStandalone('symfony/remote-event', RemoteEvent::class)}() + ->end() + ->end() + ; + } + + private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('rate_limiter') + ->info('Rate limiter configuration') + ->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}() + ->fixXmlConfig('limiter') + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['limiters']) && !isset($v['limiter'])) + ->then(function (array $v) { + $newV = [ + 'enabled' => $v['enabled'] ?? true, + ]; + unset($v['enabled']); + + $newV['limiters'] = $v; + + return $newV; + }) + ->end() + ->children() + ->arrayNode('limiters') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('lock_factory') + ->info('The service ID of the lock factory used by this limiter (or null to disable locking)') + ->defaultValue('lock.factory') + ->end() + ->scalarNode('cache_pool') + ->info('The cache pool to use for storing the current limiter state') + ->defaultValue('cache.rate_limiter') + ->end() + ->scalarNode('storage_service') + ->info('The service ID of a custom storage implementation, this precedes any configured "cache_pool"') + ->defaultNull() + ->end() + ->enumNode('policy') + ->info('The algorithm to be used by this limiter') + ->isRequired() + ->values(['fixed_window', 'token_bucket', 'sliding_window', 'no_limit']) + ->end() + ->integerNode('limit') + ->info('The maximum allowed hits in a fixed interval or burst') + ->end() + ->scalarNode('interval') + ->info('Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).') + ->end() + ->arrayNode('rate') + ->info('Configures the fill rate if "policy" is set to "token_bucket"') + ->children() + ->scalarNode('interval') + ->info('Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).') + ->end() + ->integerNode('amount')->info('Amount of tokens to add each interval')->defaultValue(1)->end() + ->end() + ->end() + ->end() + ->validate() + ->ifTrue(fn ($v) => 'no_limit' !== $v['policy'] && !isset($v['limit'])) + ->thenInvalid('A limit must be provided when using a policy different than "no_limit".') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('uid') + ->info('Uid configuration') + ->{$enableIfStandalone('symfony/uid', UuidFactory::class)}() + ->addDefaultsIfNotSet() + ->children() + ->enumNode('default_uuid_version') + ->values([7, 6, 4, 1]) + ->defaultValue(7) + ->end() + ->enumNode('name_based_uuid_version') + ->defaultValue(5) + ->values([5, 3]) + ->end() + ->scalarNode('name_based_uuid_namespace') + ->cannotBeEmpty() + ->end() + ->enumNode('time_based_uuid_version') + ->values([7, 6, 1]) + ->defaultValue(7) + ->end() + ->scalarNode('time_based_uuid_node') + ->cannotBeEmpty() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('html_sanitizer') + ->info('HtmlSanitizer configuration') + ->{$enableIfStandalone('symfony/html-sanitizer', HtmlSanitizerInterface::class)}() + ->fixXmlConfig('sanitizer') + ->children() + ->arrayNode('sanitizers') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->fixXmlConfig('allow_element') + ->fixXmlConfig('block_element') + ->fixXmlConfig('drop_element') + ->fixXmlConfig('allow_attribute') + ->fixXmlConfig('drop_attribute') + ->fixXmlConfig('force_attribute') + ->fixXmlConfig('allowed_link_scheme') + ->fixXmlConfig('allowed_link_host') + ->fixXmlConfig('allowed_media_scheme') + ->fixXmlConfig('allowed_media_host') + ->fixXmlConfig('with_attribute_sanitizer') + ->fixXmlConfig('without_attribute_sanitizer') + ->children() + ->booleanNode('allow_safe_elements') + ->info('Allows "safe" elements and attributes.') + ->defaultFalse() + ->end() + ->booleanNode('allow_static_elements') + ->info('Allows all static elements and attributes from the W3C Sanitizer API standard.') + ->defaultFalse() + ->end() + ->arrayNode('allow_elements') + ->info('Configures the elements that the sanitizer should retain from the input. The element name is the key, the value is either a list of allowed attributes for this element or "*" to allow the default set of attributes (https://wicg.github.io/sanitizer-api/#default-configuration).') + ->example(['i' => '*', 'a' => ['title'], 'span' => 'class']) + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['attribute'] ?? $n) + ->end() + ->validate() + ->ifTrue(fn ($n): bool => !\is_string($n) && !\is_array($n)) + ->thenInvalid('The value must be either a string or an array of strings.') + ->end() + ->end() + ->end() + ->arrayNode('block_elements') + ->info('Configures elements as blocked. Blocked elements are elements the sanitizer should remove from the input, but retain their children.') + ->beforeNormalization() + ->ifString() + ->then(fn (string $n): array => (array) $n) + ->end() + ->scalarPrototype()->end() + ->end() + ->arrayNode('drop_elements') + ->info('Configures elements as dropped. Dropped elements are elements the sanitizer should remove from the input, including their children.') + ->beforeNormalization() + ->ifString() + ->then(fn (string $n): array => (array) $n) + ->end() + ->scalarPrototype()->end() + ->end() + ->arrayNode('allow_attributes') + ->info('Configures attributes as allowed. Allowed attributes are attributes the sanitizer should retain from the input.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['element'] ?? $n) + ->end() + ->end() + ->end() + ->arrayNode('drop_attributes') + ->info('Configures attributes as dropped. Dropped attributes are attributes the sanitizer should remove from the input.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['element'] ?? $n) + ->end() + ->end() + ->end() + ->arrayNode('force_attributes') + ->info('Forcefully set the values of certain attributes on certain elements.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->scalarPrototype()->end() + ->end() + ->end() + ->booleanNode('force_https_urls') + ->info('Transforms URLs using the HTTP scheme to use the HTTPS scheme instead.') + ->defaultFalse() + ->end() + ->arrayNode('allowed_link_schemes') + ->info('Allows only a given list of schemes to be used in links href attributes.') + ->scalarPrototype()->end() + ->end() + ->variableNode('allowed_link_hosts') + ->info('Allows only a given list of hosts to be used in links href attributes.') + ->defaultValue(null) + ->validate() + ->ifTrue(fn ($v) => !\is_array($v) && null !== $v) + ->thenInvalid('The "allowed_link_hosts" parameter must be an array or null') + ->end() + ->end() + ->booleanNode('allow_relative_links') + ->info('Allows relative URLs to be used in links href attributes.') + ->defaultFalse() + ->end() + ->arrayNode('allowed_media_schemes') + ->info('Allows only a given list of schemes to be used in media source attributes (img, audio, video, ...).') + ->scalarPrototype()->end() + ->end() + ->variableNode('allowed_media_hosts') + ->info('Allows only a given list of hosts to be used in media source attributes (img, audio, video, ...).') + ->defaultValue(null) + ->validate() + ->ifTrue(fn ($v) => !\is_array($v) && null !== $v) + ->thenInvalid('The "allowed_media_hosts" parameter must be an array or null') + ->end() + ->end() + ->booleanNode('allow_relative_medias') + ->info('Allows relative URLs to be used in media source attributes (img, audio, video, ...).') + ->defaultFalse() + ->end() + ->arrayNode('with_attribute_sanitizers') + ->info('Registers custom attribute sanitizers.') + ->scalarPrototype()->end() + ->end() + ->arrayNode('without_attribute_sanitizers') + ->info('Unregisters custom attribute sanitizers.') + ->scalarPrototype()->end() + ->end() + ->integerNode('max_input_length') + ->info('The maximum length allowed for the sanitized input.') + ->defaultValue(0) + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php b/vendor/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php new file mode 100644 index 0000000..1114246 --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php @@ -0,0 +1,3145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; + +use Composer\InstalledVersions; +use Http\Client\HttpAsyncClient; +use Http\Client\HttpClient; +use phpDocumentor\Reflection\DocBlockFactoryInterface; +use phpDocumentor\Reflection\Types\ContextFactory; +use PhpParser\Parser; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use Psr\Cache\CacheItemPoolInterface; +use Psr\Clock\ClockInterface as PsrClockInterface; +use Psr\Container\ContainerInterface as PsrContainerInterface; +use Psr\Http\Client\ClientInterface; +use Psr\Log\LoggerAwareInterface; +use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Symfony\Bridge\Twig\Extension\CsrfExtension; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; +use Symfony\Bundle\FullStack; +use Symfony\Bundle\MercureBundle\MercureBundle; +use Symfony\Component\Asset\PackageInterface; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface; +use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; +use Symfony\Component\Cache\DependencyInjection\CachePoolPass; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\ResourceCheckerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\DataCollector\CommandDataCollector; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Messenger\RunCommandMessageHandler; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; +use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Dotenv\Command\DebugCommand; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\Glob; +use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension; +use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeGuesserInterface; +use Symfony\Component\Form\FormTypeInterface; +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; +use Symfony\Component\HttpClient\Messenger\PingWebhookMessageHandler; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\RetryableHttpClient; +use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpClient\ThrottlingHttpClient; +use Symfony\Component\HttpClient\UriTemplateHttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver; +use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\LockInterface; +use Symfony\Component\Lock\PersistingStoreInterface; +use Symfony\Component\Lock\Store\StoreFactory; +use Symfony\Component\Mailer\Bridge as MailerBridge; +use Symfony\Component\Mailer\Command\MailerTestCommand; +use Symfony\Component\Mailer\EventListener\MessengerTransportListener; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mercure\HubRegistry; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; +use Symfony\Component\Messenger\Bridge as MessengerBridge; +use Symfony\Component\Messenger\Handler\BatchHandlerInterface; +use Symfony\Component\Messenger\MessageBus; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; +use Symfony\Component\Messenger\Transport\TransportFactoryInterface as MessengerTransportFactoryInterface; +use Symfony\Component\Messenger\Transport\TransportInterface; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\MimeTypeGuesserInterface; +use Symfony\Component\Mime\MimeTypes; +use Symfony\Component\Notifier\Bridge as NotifierBridge; +use Symfony\Component\Notifier\ChatterInterface; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Recipient\Recipient; +use Symfony\Component\Notifier\TexterInterface; +use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface; +use Symfony\Component\Process\Messenger\RunProcessMessageHandler; +use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Component\RateLimiter\RateLimiterFactory; +use Symfony\Component\RateLimiter\Storage\CacheStorage; +use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Scheduler\Attribute\AsCronTask; +use Symfony\Component\Scheduler\Attribute\AsPeriodicTask; +use Symfony\Component\Scheduler\Attribute\AsSchedule; +use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; +use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Semaphore\PersistingStoreInterface as SemaphoreStoreInterface; +use Symfony\Component\Semaphore\Semaphore; +use Symfony\Component\Semaphore\SemaphoreFactory; +use Symfony\Component\Semaphore\Store\StoreFactory as SemaphoreStoreFactory; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; +use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; +use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\String\LazyString; +use Symfony\Component\String\Slugger\SluggerInterface; +use Symfony\Component\Translation\Bridge as TranslationBridge; +use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand; +use Symfony\Component\Translation\Extractor\PhpAstExtractor; +use Symfony\Component\Translation\LocaleSwitcher; +use Symfony\Component\Translation\PseudoLocalizationTranslator; +use Symfony\Component\Translation\Translator; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider; +use Symfony\Component\Validator\ConstraintValidatorInterface; +use Symfony\Component\Validator\GroupProviderInterface; +use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; +use Symfony\Component\Validator\ObjectInitializerInterface; +use Symfony\Component\Validator\Validation; +use Symfony\Component\Webhook\Controller\WebhookController; +use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\Workflow; +use Symfony\Component\Workflow\WorkflowInterface; +use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; +use Symfony\Component\Yaml\Yaml; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\CallbackInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\Service\ResetInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +/** + * Process the configuration and prepare the dependency injection container with + * parameters and services. + */ +class FrameworkExtension extends Extension +{ + private array $configsEnabled = []; + + /** + * Responds to the app.config configuration parameter. + * + * @throws LogicException + */ + public function load(array $configs, ContainerBuilder $container): void + { + $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); + + if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('symfony/symfony') && 'symfony/symfony' !== (InstalledVersions::getRootPackage()['name'] ?? '')) { + throw new \LogicException('Requiring the "symfony/symfony" package is unsupported; replace it with standalone components instead.'); + } + + $loader->load('web.php'); + $loader->load('services.php'); + $loader->load('fragment_renderer.php'); + $loader->load('error_renderer.php'); + + if (!ContainerBuilder::willBeAvailable('symfony/clock', ClockInterface::class, ['symfony/framework-bundle'])) { + $container->removeDefinition('clock'); + $container->removeAlias(ClockInterface::class); + $container->removeAlias(PsrClockInterface::class); + } + + $container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class); + + $loader->load('process.php'); + + if (!class_exists(RunProcessMessageHandler::class)) { + $container->removeDefinition('process.messenger.process_message_handler'); + } + + if ($this->hasConsole()) { + $loader->load('console.php'); + + if (!class_exists(BaseXliffLintCommand::class)) { + $container->removeDefinition('console.command.xliff_lint'); + } + if (!class_exists(BaseYamlLintCommand::class)) { + $container->removeDefinition('console.command.yaml_lint'); + } + + if (!class_exists(DebugCommand::class)) { + $container->removeDefinition('console.command.dotenv_debug'); + } + + if (!class_exists(RunCommandMessageHandler::class)) { + $container->removeDefinition('console.messenger.application'); + $container->removeDefinition('console.messenger.execute_command_handler'); + } + } + + // Load Cache configuration first as it is used by other components + $loader->load('cache.php'); + + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + // warmup config enabled + $this->readConfigEnabled('translator', $container, $config['translator']); + $this->readConfigEnabled('property_access', $container, $config['property_access']); + $this->readConfigEnabled('profiler', $container, $config['profiler']); + $this->readConfigEnabled('workflows', $container, $config['workflows']); + + // A translator must always be registered (as support is included by + // default in the Form and Validator component). If disabled, an identity + // translator will be used and everything will still work as expected. + if ($this->readConfigEnabled('translator', $container, $config['translator']) || $this->readConfigEnabled('form', $container, $config['form']) || $this->readConfigEnabled('validation', $container, $config['validation'])) { + if (!class_exists(Translator::class) && $this->readConfigEnabled('translator', $container, $config['translator'])) { + throw new LogicException('Translation support cannot be enabled as the Translation component is not installed. Try running "composer require symfony/translation".'); + } + + if (class_exists(Translator::class)) { + $loader->load('identity_translator.php'); + } + } + + $container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']); + $container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']); + $container->getDefinition('http_kernel')->replaceArgument(4, $config['handle_all_throwables'] ?? false); + + // If the slugger is used but the String component is not available, we should throw an error + if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'])) { + $container->register('slugger', SluggerInterface::class) + ->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".'); + } else { + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'])) { + $container->register('slugger', SluggerInterface::class) + ->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".'); + } + + if (!\extension_loaded('intl') && !\defined('PHPUNIT_COMPOSER_INSTALL')) { + trigger_deprecation('', '', 'Please install the "intl" PHP extension for best performance.'); + } + } + + if (isset($config['secret'])) { + $container->setParameter('kernel.secret', $config['secret']); + } + + $container->setParameter('kernel.http_method_override', $config['http_method_override']); + $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']); + $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); + $container->setParameter('kernel.default_locale', $config['default_locale']); + $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); + $container->setParameter('kernel.error_controller', $config['error_controller']); + + if (($config['trusted_proxies'] ?? false) && ($config['trusted_headers'] ?? false)) { + $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); + $container->setParameter('kernel.trusted_headers', $this->resolveTrustedHeaders($config['trusted_headers'])); + } + + if (!$container->hasParameter('debug.file_link_format')) { + $container->setParameter('debug.file_link_format', $config['ide']); + } + + if (!empty($config['test'])) { + $loader->load('test.php'); + + if (!class_exists(AbstractBrowser::class)) { + $container->removeDefinition('test.client'); + } + } + + if ($this->readConfigEnabled('request', $container, $config['request'])) { + $this->registerRequestConfiguration($config['request'], $container, $loader); + } + + if ($this->readConfigEnabled('assets', $container, $config['assets'])) { + if (!class_exists(\Symfony\Component\Asset\Package::class)) { + throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".'); + } + + $this->registerAssetsConfiguration($config['assets'], $container, $loader); + } + + if ($this->readConfigEnabled('asset_mapper', $container, $config['asset_mapper'])) { + if (!class_exists(AssetMapper::class)) { + throw new LogicException('AssetMapper support cannot be enabled as the AssetMapper component is not installed. Try running "composer require symfony/asset-mapper".'); + } + + $this->registerAssetMapperConfiguration($config['asset_mapper'], $container, $loader, $this->readConfigEnabled('assets', $container, $config['assets']), $this->readConfigEnabled('http_client', $container, $config['http_client'])); + } else { + $container->removeDefinition('cache.asset_mapper'); + } + + if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { + $this->readConfigEnabled('rate_limiter', $container, $config['rate_limiter']); // makes sure that isInitializedConfigEnabled() will work + $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); + } + + if ($this->readConfigEnabled('mailer', $container, $config['mailer'])) { + $this->registerMailerConfiguration($config['mailer'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook'])); + + if (!$this->hasConsole() || !class_exists(MailerTestCommand::class)) { + $container->removeDefinition('console.command.mailer_test'); + } + } + + $propertyInfoEnabled = $this->readConfigEnabled('property_info', $container, $config['property_info']); + $this->registerHttpCacheConfiguration($config['http_cache'], $container, $config['http_method_override']); + $this->registerEsiConfiguration($config['esi'], $container, $loader); + $this->registerSsiConfiguration($config['ssi'], $container, $loader); + $this->registerFragmentsConfiguration($config['fragments'], $container, $loader); + $this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale'], $config['enabled_locales']); + $this->registerWorkflowConfiguration($config['workflows'], $container, $loader); + $this->registerDebugConfiguration($config['php_errors'], $container, $loader); + $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']); + $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader); + $this->registerSecretsConfiguration($config['secrets'], $container, $loader); + + $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); + + if ($this->readConfigEnabled('serializer', $container, $config['serializer'])) { + if (!class_exists(Serializer::class)) { + throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'); + } + + $this->registerSerializerConfiguration($config['serializer'], $container, $loader); + } else { + $container->getDefinition('argument_resolver.request_payload') + ->setArguments([]) + ->addError('You can neither use "#[MapRequestPayload]" nor "#[MapQueryString]" since the Serializer component is not ' + .(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer.enabled" to true.' : 'installed. Try running "composer require symfony/serializer-pack".') + ) + ->addTag('container.error') + ->clearTag('kernel.event_subscriber'); + + $container->removeDefinition('console.command.serializer_debug'); + } + + if ($this->readConfigEnabled('type_info', $container, $config['type_info'])) { + $this->registerTypeInfoConfiguration($container, $loader); + } + + if ($propertyInfoEnabled) { + $this->registerPropertyInfoConfiguration($container, $loader); + } + + if ($this->readConfigEnabled('lock', $container, $config['lock'])) { + $this->registerLockConfiguration($config['lock'], $container, $loader); + } + + if ($this->readConfigEnabled('semaphore', $container, $config['semaphore'])) { + $this->registerSemaphoreConfiguration($config['semaphore'], $container, $loader); + } + + if ($this->readConfigEnabled('rate_limiter', $container, $config['rate_limiter'])) { + if (!interface_exists(LimiterInterface::class)) { + throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); + } + + $this->registerRateLimiterConfiguration($config['rate_limiter'], $container, $loader); + } + + if ($this->readConfigEnabled('web_link', $container, $config['web_link'])) { + if (!class_exists(HttpHeaderSerializer::class)) { + throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".'); + } + + $loader->load('web_link.php'); + } + + if ($this->readConfigEnabled('uid', $container, $config['uid'])) { + if (!class_exists(UuidFactory::class)) { + throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".'); + } + + $this->registerUidConfiguration($config['uid'], $container, $loader); + } else { + $container->removeDefinition('argument_resolver.uid'); + } + + // register cache before session so both can share the connection services + $this->registerCacheConfiguration($config['cache'], $container); + + if ($this->readConfigEnabled('session', $container, $config['session'])) { + if (!\extension_loaded('session')) { + throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.'); + } + + $this->registerSessionConfiguration($config['session'], $container, $loader); + if (!empty($config['test'])) { + // test listener will replace the existing session listener + // as we are aliasing to avoid duplicated registered events + $container->setAlias('session_listener', 'test.session.listener'); + } + } elseif (!empty($config['test'])) { + $container->removeDefinition('test.session.listener'); + } + + // csrf depends on session being registered + if (null === $config['csrf_protection']['enabled']) { + $this->writeConfigEnabled('csrf_protection', $this->readConfigEnabled('session', $container, $config['session']) && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle']), $config['csrf_protection']); + } + $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); + + // form depends on csrf being registered + if ($this->readConfigEnabled('form', $container, $config['form'])) { + if (!class_exists(Form::class)) { + throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".'); + } + + $this->registerFormConfiguration($config, $container, $loader); + + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'])) { + $this->writeConfigEnabled('validation', true, $config['validation']); + } else { + $container->setParameter('validator.translation_domain', 'validators'); + + $container->removeDefinition('form.type_extension.form.validator'); + $container->removeDefinition('form.type_guesser.validator'); + } + if (!$this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer']) || !class_exists(TextTypeHtmlSanitizerExtension::class)) { + $container->removeDefinition('form.type_extension.form.html_sanitizer'); + } + } else { + $container->removeDefinition('console.command.form_debug'); + } + + // validation depends on form, annotations being registered + $this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled); + + $messengerEnabled = $this->readConfigEnabled('messenger', $container, $config['messenger']); + + if ($this->readConfigEnabled('scheduler', $container, $config['scheduler'])) { + if (!$messengerEnabled) { + throw new LogicException('Scheduler support cannot be enabled as the Messenger component is not '.(interface_exists(MessageBusInterface::class) ? 'enabled.' : 'installed. Try running "composer require symfony/messenger".')); + } + $this->registerSchedulerConfiguration($config['scheduler'], $container, $loader); + } else { + $container->removeDefinition('cache.scheduler'); + $container->removeDefinition('console.command.scheduler_debug'); + } + + // messenger depends on validation being registered + if ($messengerEnabled) { + $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $this->readConfigEnabled('validation', $container, $config['validation'])); + } else { + $container->removeDefinition('console.command.messenger_consume_messages'); + $container->removeDefinition('console.command.messenger_stats'); + $container->removeDefinition('console.command.messenger_debug'); + $container->removeDefinition('console.command.messenger_stop_workers'); + $container->removeDefinition('console.command.messenger_setup_transports'); + $container->removeDefinition('console.command.messenger_failed_messages_retry'); + $container->removeDefinition('console.command.messenger_failed_messages_show'); + $container->removeDefinition('console.command.messenger_failed_messages_remove'); + $container->removeDefinition('cache.messenger.restart_workers_signal'); + + if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(MessengerBridge\Amqp\Transport\AmqpTransportFactory::class)) { + if (class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) { + $container->getDefinition('messenger.transport.amqp.factory') + ->setClass(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class) + ->addTag('messenger.transport_factory'); + } else { + $container->removeDefinition('messenger.transport.amqp.factory'); + } + } + + if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(MessengerBridge\Redis\Transport\RedisTransportFactory::class)) { + if (class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) { + $container->getDefinition('messenger.transport.redis.factory') + ->setClass(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class) + ->addTag('messenger.transport_factory'); + } else { + $container->removeDefinition('messenger.transport.redis.factory'); + } + } + } + + // notifier depends on messenger, mailer being registered + if ($this->readConfigEnabled('notifier', $container, $config['notifier'])) { + $this->registerNotifierConfiguration($config['notifier'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook'])); + } + + // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered + $this->registerProfilerConfiguration($config['profiler'], $container, $loader); + + if ($this->readConfigEnabled('webhook', $container, $config['webhook'])) { + $this->registerWebhookConfiguration($config['webhook'], $container, $loader); + + // If Webhook is installed but the HttpClient or Serializer components are not available, we should throw an error + if (!$this->readConfigEnabled('http_client', $container, $config['http_client'])) { + $container->getDefinition('webhook.transport') + ->setArguments([]) + ->addError('You cannot use the "webhook transport" service since the HttpClient component is not ' + .(class_exists(ScopingHttpClient::class) ? 'enabled. Try setting "framework.http_client.enabled" to true.' : 'installed. Try running "composer require symfony/http-client".') + ) + ->addTag('container.error'); + } + if (!$this->readConfigEnabled('serializer', $container, $config['serializer'])) { + $container->getDefinition('webhook.body_configurator.json') + ->setArguments([]) + ->addError('You cannot use the "webhook transport" service since the Serializer component is not ' + .(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer.enabled" to true.' : 'installed. Try running "composer require symfony/serializer-pack".') + ) + ->addTag('container.error'); + } + } + + if ($this->readConfigEnabled('remote-event', $container, $config['remote-event'])) { + $this->registerRemoteEventConfiguration($config['remote-event'], $container, $loader); + } + + if ($this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer'])) { + if (!class_exists(HtmlSanitizerConfig::class)) { + throw new LogicException('HtmlSanitizer support cannot be enabled as the HtmlSanitizer component is not installed. Try running "composer require symfony/html-sanitizer".'); + } + + $this->registerHtmlSanitizerConfiguration($config['html_sanitizer'], $container, $loader); + } + + if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'])) { + $loader->load('mime_type.php'); + } + + $container->registerForAutoconfiguration(PackageInterface::class) + ->addTag('assets.package'); + $container->registerForAutoconfiguration(AssetCompilerInterface::class) + ->addTag('asset_mapper.compiler'); + $container->registerForAutoconfiguration(Command::class) + ->addTag('console.command'); + $container->registerForAutoconfiguration(ResourceCheckerInterface::class) + ->addTag('config_cache.resource_checker'); + $container->registerForAutoconfiguration(EnvVarLoaderInterface::class) + ->addTag('container.env_var_loader'); + $container->registerForAutoconfiguration(EnvVarProcessorInterface::class) + ->addTag('container.env_var_processor'); + $container->registerForAutoconfiguration(CallbackInterface::class) + ->addTag('container.reversible'); + $container->registerForAutoconfiguration(ServiceLocator::class) + ->addTag('container.service_locator'); + $container->registerForAutoconfiguration(ServiceSubscriberInterface::class) + ->addTag('container.service_subscriber'); + $container->registerForAutoconfiguration(ValueResolverInterface::class) + ->addTag('controller.argument_value_resolver'); + $container->registerForAutoconfiguration(AbstractController::class) + ->addTag('controller.service_arguments'); + $container->registerForAutoconfiguration(DataCollectorInterface::class) + ->addTag('data_collector'); + $container->registerForAutoconfiguration(FormTypeInterface::class) + ->addTag('form.type'); + $container->registerForAutoconfiguration(FormTypeGuesserInterface::class) + ->addTag('form.type_guesser'); + $container->registerForAutoconfiguration(FormTypeExtensionInterface::class) + ->addTag('form.type_extension'); + $container->registerForAutoconfiguration(CacheClearerInterface::class) + ->addTag('kernel.cache_clearer'); + $container->registerForAutoconfiguration(CacheWarmerInterface::class) + ->addTag('kernel.cache_warmer'); + $container->registerForAutoconfiguration(EventDispatcherInterface::class) + ->addTag('event_dispatcher.dispatcher'); + $container->registerForAutoconfiguration(EventSubscriberInterface::class) + ->addTag('kernel.event_subscriber'); + $container->registerForAutoconfiguration(LocaleAwareInterface::class) + ->addTag('kernel.locale_aware'); + $container->registerForAutoconfiguration(ResetInterface::class) + ->addTag('kernel.reset', ['method' => 'reset']); + + if (!interface_exists(MarshallerInterface::class)) { + $container->registerForAutoconfiguration(ResettableInterface::class) + ->addTag('kernel.reset', ['method' => 'reset']); + } + + $container->registerForAutoconfiguration(PropertyListExtractorInterface::class) + ->addTag('property_info.list_extractor'); + $container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class) + ->addTag('property_info.type_extractor'); + $container->registerForAutoconfiguration(PropertyDescriptionExtractorInterface::class) + ->addTag('property_info.description_extractor'); + $container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class) + ->addTag('property_info.access_extractor'); + $container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class) + ->addTag('property_info.initializable_extractor'); + $container->registerForAutoconfiguration(EncoderInterface::class) + ->addTag('serializer.encoder'); + $container->registerForAutoconfiguration(DecoderInterface::class) + ->addTag('serializer.encoder'); + $container->registerForAutoconfiguration(NormalizerInterface::class) + ->addTag('serializer.normalizer'); + $container->registerForAutoconfiguration(DenormalizerInterface::class) + ->addTag('serializer.normalizer'); + $container->registerForAutoconfiguration(ConstraintValidatorInterface::class) + ->addTag('validator.constraint_validator'); + $container->registerForAutoconfiguration(GroupProviderInterface::class) + ->addTag('validator.group_provider'); + $container->registerForAutoconfiguration(ObjectInitializerInterface::class) + ->addTag('validator.initializer'); + $container->registerForAutoconfiguration(BatchHandlerInterface::class) + ->addTag('messenger.message_handler'); + $container->registerForAutoconfiguration(MessengerTransportFactoryInterface::class) + ->addTag('messenger.transport_factory'); + $container->registerForAutoconfiguration(MimeTypeGuesserInterface::class) + ->addTag('mime.mime_type_guesser'); + $container->registerForAutoconfiguration(LoggerAwareInterface::class) + ->addMethodCall('setLogger', [new Reference('logger')]); + + $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) { + $tagAttributes = get_object_vars($attribute); + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); + } + $tagAttributes['method'] = $reflector->getName(); + } + $definition->addTag('kernel.event_listener', $tagAttributes); + }); + $container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void { + $definition->addTag('controller.service_arguments'); + }); + $container->registerAttributeForAutoconfiguration(AsRemoteEventConsumer::class, static function (ChildDefinition $definition, AsRemoteEventConsumer $attribute): void { + $definition->addTag('remote_event.consumer', ['consumer' => $attribute->name]); + }); + $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { + $tagAttributes = get_object_vars($attribute); + $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; + unset($tagAttributes['fromTransport']); + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('AsMessageHandler attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); + } + $tagAttributes['method'] = $reflector->getName(); + } + $definition->addTag('messenger.message_handler', $tagAttributes); + }); + $container->registerAttributeForAutoconfiguration(AsTargetedValueResolver::class, static function (ChildDefinition $definition, AsTargetedValueResolver $attribute): void { + $definition->addTag('controller.targeted_value_resolver', $attribute->name ? ['name' => $attribute->name] : []); + }); + $container->registerAttributeForAutoconfiguration(AsSchedule::class, static function (ChildDefinition $definition, AsSchedule $attribute): void { + $definition->addTag('scheduler.schedule_provider', ['name' => $attribute->name]); + }); + foreach ([AsPeriodicTask::class, AsCronTask::class] as $taskAttributeClass) { + $container->registerAttributeForAutoconfiguration( + $taskAttributeClass, + static function (ChildDefinition $definition, AsPeriodicTask|AsCronTask $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { + $tagAttributes = get_object_vars($attribute) + [ + 'trigger' => match (true) { + $attribute instanceof AsPeriodicTask => 'every', + $attribute instanceof AsCronTask => 'cron', + }, + ]; + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('"%s" attribute cannot declare a method on "%s::%s()".', $attribute::class, $reflector->class, $reflector->name)); + } + $tagAttributes['method'] = $reflector->getName(); + } + $definition->addTag('scheduler.task', $tagAttributes); + } + ); + } + + if (!$container->getParameter('kernel.debug')) { + // remove tagged iterator argument for resource checkers + $container->getDefinition('config_cache_factory')->setArguments([]); + } + + if (!$config['disallow_search_engine_index'] ?? false) { + $container->removeDefinition('disallow_search_engine_index_response_listener'); + } + + $container->registerForAutoconfiguration(RouteLoaderInterface::class) + ->addTag('routing.route_loader'); + + $container->setParameter('container.behavior_describing_tags', [ + 'container.do_not_inline', + 'container.service_locator', + 'container.service_subscriber', + 'kernel.event_subscriber', + 'kernel.event_listener', + 'kernel.locale_aware', + 'kernel.reset', + ]); + } + + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + { + return new Configuration($container->getParameter('kernel.debug')); + } + + protected function hasConsole(): bool + { + return class_exists(Application::class); + } + + private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('form.php'); + + if (null === $config['form']['csrf_protection']['enabled']) { + $this->writeConfigEnabled('form.csrf_protection', $config['csrf_protection']['enabled'], $config['form']['csrf_protection']); + } + + if ($this->readConfigEnabled('form.csrf_protection', $container, $config['form']['csrf_protection'])) { + if (!$container->hasDefinition('security.csrf.token_generator')) { + throw new \LogicException('To use form CSRF protection, "framework.csrf_protection" must be enabled.'); + } + + $loader->load('form_csrf.php'); + + $container->setParameter('form.type_extension.csrf.enabled', true); + $container->setParameter('form.type_extension.csrf.field_name', $config['form']['csrf_protection']['field_name']); + } else { + $container->setParameter('form.type_extension.csrf.enabled', false); + } + + if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'])) { + $container->removeDefinition('form.type_extension.upload.validator'); + } + } + + private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride): void + { + $options = $config; + unset($options['enabled']); + + if (!$options['private_headers']) { + unset($options['private_headers']); + } + + if (!$options['skip_response_headers']) { + unset($options['skip_response_headers']); + } + + $container->getDefinition('http_cache') + ->setPublic($config['enabled']) + ->replaceArgument(3, $options); + + if ($httpMethodOverride) { + $container->getDefinition('http_cache') + ->addArgument((new Definition('void')) + ->setFactory([Request::class, 'enableHttpMethodParameterOverride']) + ); + } + } + + private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$this->readConfigEnabled('esi', $container, $config)) { + $container->removeDefinition('fragment.renderer.esi'); + + return; + } + + $loader->load('esi.php'); + } + + private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$this->readConfigEnabled('ssi', $container, $config)) { + $container->removeDefinition('fragment.renderer.ssi'); + + return; + } + + $loader->load('ssi.php'); + } + + private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$this->readConfigEnabled('fragments', $container, $config)) { + $container->removeDefinition('fragment.renderer.hinclude'); + + return; + } + + $container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']); + + $loader->load('fragment_listener.php'); + $container->setParameter('fragment.path', $config['path']); + } + + private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$this->readConfigEnabled('profiler', $container, $config)) { + // this is needed for the WebProfiler to work even if the profiler is disabled + $container->setParameter('data_collector.templates', []); + + return; + } + + $loader->load('profiling.php'); + $loader->load('collectors.php'); + $loader->load('cache_debug.php'); + + if ($this->isInitializedConfigEnabled('form')) { + $loader->load('form_debug.php'); + } + + if ($this->isInitializedConfigEnabled('validation')) { + $loader->load('validator_debug.php'); + } + + if ($this->isInitializedConfigEnabled('translator')) { + $loader->load('translation_debug.php'); + + $container->getDefinition('translator.data_collector')->setDecoratedService('translator'); + } + + if ($this->isInitializedConfigEnabled('messenger')) { + $loader->load('messenger_debug.php'); + } + + if ($this->isInitializedConfigEnabled('mailer')) { + $loader->load('mailer_debug.php'); + } + + if ($this->isInitializedConfigEnabled('workflows')) { + $loader->load('workflow_debug.php'); + } + + if ($this->isInitializedConfigEnabled('http_client')) { + $loader->load('http_client_debug.php'); + } + + if ($this->isInitializedConfigEnabled('notifier')) { + $loader->load('notifier_debug.php'); + } + + if ($this->isInitializedConfigEnabled('serializer') && $config['collect_serializer_data']) { + $loader->load('serializer_debug.php'); + } + + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); + $container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests']); + + // Choose storage class based on the DSN + [$class] = explode(':', $config['dsn'], 2); + if ('file' !== $class) { + throw new \LogicException(sprintf('Driver "%s" is not supported for the profiler.', $class)); + } + + $container->setParameter('profiler.storage.dsn', $config['dsn']); + + $container->getDefinition('profiler') + ->addArgument($config['collect']) + ->addTag('kernel.reset', ['method' => 'reset']); + + $container->getDefinition('profiler_listener') + ->addArgument($config['collect_parameter']); + + if (!$container->getParameter('kernel.debug') || !class_exists(CliRequest::class) || !$container->has('debug.stopwatch')) { + $container->removeDefinition('console_profiler_listener'); + } + + if (!class_exists(CommandDataCollector::class)) { + $container->removeDefinition('.data_collector.command'); + } + } + + private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$config['enabled']) { + $container->removeDefinition('console.command.workflow_dump'); + + return; + } + + if (!class_exists(Workflow\Workflow::class)) { + throw new LogicException('Workflow support cannot be enabled as the Workflow component is not installed. Try running "composer require symfony/workflow".'); + } + + $loader->load('workflow.php'); + + $registryDefinition = $container->getDefinition('workflow.registry'); + + foreach ($config['workflows'] as $name => $workflow) { + $type = $workflow['type']; + $workflowId = sprintf('%s.%s', $type, $name); + + // Process Metadata (workflow + places (transition is done in the "create transition" block)) + $metadataStoreDefinition = new Definition(Workflow\Metadata\InMemoryMetadataStore::class, [[], [], null]); + if ($workflow['metadata']) { + $metadataStoreDefinition->replaceArgument(0, $workflow['metadata']); + } + $placesMetadata = []; + foreach ($workflow['places'] as $place) { + if ($place['metadata']) { + $placesMetadata[$place['name']] = $place['metadata']; + } + } + if ($placesMetadata) { + $metadataStoreDefinition->replaceArgument(1, $placesMetadata); + } + + // Create transitions + $transitions = []; + $guardsConfiguration = []; + $transitionsMetadataDefinition = new Definition(\SplObjectStorage::class); + // Global transition counter per workflow + $transitionCounter = 0; + foreach ($workflow['transitions'] as $transition) { + if ('workflow' === $type) { + $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $transition['from'], $transition['to']]); + $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); + $container->setDefinition($transitionId, $transitionDefinition); + $transitions[] = new Reference($transitionId); + if (isset($transition['guard'])) { + $configuration = new Definition(Workflow\EventListener\GuardExpression::class); + $configuration->addArgument(new Reference($transitionId)); + $configuration->addArgument($transition['guard']); + $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); + $guardsConfiguration[$eventName][] = $configuration; + } + if ($transition['metadata']) { + $transitionsMetadataDefinition->addMethodCall('attach', [ + new Reference($transitionId), + $transition['metadata'], + ]); + } + } elseif ('state_machine' === $type) { + foreach ($transition['from'] as $from) { + foreach ($transition['to'] as $to) { + $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $from, $to]); + $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); + $container->setDefinition($transitionId, $transitionDefinition); + $transitions[] = new Reference($transitionId); + if (isset($transition['guard'])) { + $configuration = new Definition(Workflow\EventListener\GuardExpression::class); + $configuration->addArgument(new Reference($transitionId)); + $configuration->addArgument($transition['guard']); + $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); + $guardsConfiguration[$eventName][] = $configuration; + } + if ($transition['metadata']) { + $transitionsMetadataDefinition->addMethodCall('attach', [ + new Reference($transitionId), + $transition['metadata'], + ]); + } + } + } + } + } + $metadataStoreDefinition->replaceArgument(2, $transitionsMetadataDefinition); + $container->setDefinition(sprintf('%s.metadata_store', $workflowId), $metadataStoreDefinition); + + // Create places + $places = array_column($workflow['places'], 'name'); + $initialMarking = $workflow['initial_marking'] ?? []; + + // Create a Definition + $definitionDefinition = new Definition(Workflow\Definition::class); + $definitionDefinition->addArgument($places); + $definitionDefinition->addArgument($transitions); + $definitionDefinition->addArgument($initialMarking); + $definitionDefinition->addArgument(new Reference(sprintf('%s.metadata_store', $workflowId))); + + // Create MarkingStore + $markingStoreDefinition = null; + if (isset($workflow['marking_store']['type']) || isset($workflow['marking_store']['property'])) { + $markingStoreDefinition = new ChildDefinition('workflow.marking_store.method'); + $markingStoreDefinition->setArguments([ + 'state_machine' === $type, // single state + $workflow['marking_store']['property'] ?? 'marking', + ]); + } elseif (isset($workflow['marking_store']['service'])) { + $markingStoreDefinition = new Reference($workflow['marking_store']['service']); + } + + // Create Workflow + $workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type)); + $workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId))); + $workflowDefinition->replaceArgument(1, $markingStoreDefinition); + $workflowDefinition->replaceArgument(3, $name); + $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); + + $workflowDefinition->addTag('workflow', ['name' => $name, 'metadata' => $workflow['metadata']]); + if ('workflow' === $type) { + $workflowDefinition->addTag('workflow.workflow', ['name' => $name]); + } elseif ('state_machine' === $type) { + $workflowDefinition->addTag('workflow.state_machine', ['name' => $name]); + } + + // Store to container + $container->setDefinition($workflowId, $workflowDefinition); + $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition); + $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type); + $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name); + + // Validate Workflow + if ('state_machine' === $workflow['type']) { + $validator = new Workflow\Validator\StateMachineValidator(); + } else { + $validator = new Workflow\Validator\WorkflowValidator(); + } + + $trs = array_map(fn (Reference $ref): Workflow\Transition => $container->get((string) $ref), $transitions); + $realDefinition = new Workflow\Definition($places, $trs, $initialMarking); + $validator->validate($realDefinition, $name); + + // Add workflow to Registry + if ($workflow['supports']) { + foreach ($workflow['supports'] as $supportedClassName) { + $strategyDefinition = new Definition(Workflow\SupportStrategy\InstanceOfSupportStrategy::class, [$supportedClassName]); + $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), $strategyDefinition]); + } + } elseif (isset($workflow['support_strategy'])) { + $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), new Reference($workflow['support_strategy'])]); + } + + // Enable the AuditTrail + if ($workflow['audit_trail']['enabled']) { + $listener = new Definition(Workflow\EventListener\AuditTrailListener::class); + $listener->addTag('monolog.logger', ['channel' => 'workflow']); + $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.leave', $name), 'method' => 'onLeave']); + $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.transition', $name), 'method' => 'onTransition']); + $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.enter', $name), 'method' => 'onEnter']); + $listener->addArgument(new Reference('logger')); + $container->setDefinition(sprintf('.%s.listener.audit_trail', $workflowId), $listener); + } + + // Add Guard Listener + if ($guardsConfiguration) { + if (!class_exists(ExpressionLanguage::class)) { + throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + + if (!class_exists(AuthenticationEvents::class)) { + throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".'); + } + + $guard = new Definition(Workflow\EventListener\GuardListener::class); + + $guard->setArguments([ + $guardsConfiguration, + new Reference('workflow.security.expression_language'), + new Reference('security.token_storage'), + new Reference('security.authorization_checker'), + new Reference('security.authentication.trust_resolver'), + new Reference('security.role_hierarchy'), + new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]); + foreach ($guardsConfiguration as $eventName => $config) { + $guard->addTag('kernel.event_listener', ['event' => $eventName, 'method' => 'onTransition']); + } + + $container->setDefinition(sprintf('.%s.listener.guard', $workflowId), $guard); + $container->setParameter('workflow.has_guard_listeners', true); + } + } + } + + private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('debug_prod.php'); + + $debug = $container->getParameter('kernel.debug'); + + if (class_exists(Stopwatch::class)) { + $container->register('debug.stopwatch', Stopwatch::class) + ->addArgument(true) + ->setPublic($debug) + ->addTag('kernel.reset', ['method' => 'reset']); + $container->setAlias(Stopwatch::class, new Alias('debug.stopwatch', false)); + } + + if ($debug && !$container->hasParameter('debug.container.dump')) { + $container->setParameter('debug.container.dump', '%kernel.build_dir%/%kernel.container_class%.xml'); + } + + if ($debug && class_exists(Stopwatch::class)) { + $loader->load('debug.php'); + } + + $definition = $container->findDefinition('debug.error_handler_configurator'); + + if (false === $config['log']) { + $definition->replaceArgument(0, null); + } elseif (true !== $config['log']) { + $definition->replaceArgument(1, $config['log']); + } + + if (!$config['throw']) { + $container->setParameter('debug.error_handler.throw_at', 0); + } + + if ($debug && class_exists(DebugProcessor::class)) { + $definition = new Definition(DebugProcessor::class); + $definition->addArgument(new Reference('.virtual_request_stack')); + $definition->addTag('kernel.reset', ['method' => 'reset']); + $container->setDefinition('debug.log_processor', $definition); + + $container->register('debug.debug_logger_configurator', DebugLoggerConfigurator::class) + ->setArguments([new Reference('debug.log_processor'), '%kernel.runtime_mode.web%']); + } + } + + private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []): void + { + if (!$this->readConfigEnabled('router', $container, $config)) { + $container->removeDefinition('console.command.router_debug'); + $container->removeDefinition('console.command.router_match'); + $container->removeDefinition('messenger.middleware.router_context'); + + return; + } + if (!class_exists(RouterContextMiddleware::class)) { + $container->removeDefinition('messenger.middleware.router_context'); + } + + $loader->load('routing.php'); + + if ($config['utf8']) { + $container->getDefinition('routing.loader')->replaceArgument(1, ['utf8' => true]); + } + + if ($enabledLocales) { + $enabledLocales = implode('|', array_map('preg_quote', $enabledLocales)); + $container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]); + } + + if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'])) { + $container->removeDefinition('router.expression_language_provider'); + } + + $container->setParameter('router.resource', $config['resource']); + $container->setParameter('router.cache_dir', $config['cache_dir']); + $router = $container->findDefinition('router.default'); + $argument = $router->getArgument(2); + $argument['strict_requirements'] = $config['strict_requirements']; + if (isset($config['type'])) { + $argument['resource_type'] = $config['type']; + } + $router->replaceArgument(2, $argument); + + $container->setParameter('request_listener.http_port', $config['http_port']); + $container->setParameter('request_listener.https_port', $config['https_port']); + + if (null !== $config['default_uri']) { + $container->getDefinition('router.request_context') + ->replaceArgument(0, $config['default_uri']); + } + } + + private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('session.php'); + + // session storage + $container->setAlias('session.storage.factory', $config['storage_factory_id']); + + $options = ['cache_limiter' => '0']; + foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'sid_length', 'sid_bits_per_character'] as $key) { + if (isset($config[$key])) { + $options[$key] = $config[$key]; + } + } + + if ('auto' === ($options['cookie_secure'] ?? null)) { + $container->getDefinition('session.storage.factory.native')->replaceArgument(3, true); + $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(2, true); + } + + $container->setParameter('session.storage.options', $options); + + // session handler (the internal callback registered with PHP session management) + if (null === ($config['handler_id'] ?? $config['save_path'] ?? null)) { + $config['save_path'] = null; + $container->setAlias('session.handler', 'session.handler.native'); + } else { + $config['handler_id'] ??= 'session.handler.native_file'; + + if (!\array_key_exists('save_path', $config)) { + $config['save_path'] = '%kernel.cache_dir%/sessions'; + } + $container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs); + + if ($usedEnvs || preg_match('#^[a-z]++://#', $config['handler_id'])) { + $id = '.cache_connection.'.ContainerBuilder::hash($config['handler_id']); + + $container->getDefinition('session.abstract_handler') + ->replaceArgument(0, $container->hasDefinition($id) ? new Reference($id) : $config['handler_id']); + + $container->setAlias('session.handler', 'session.abstract_handler'); + } else { + $container->setAlias('session.handler', $config['handler_id']); + } + } + + $container->setParameter('session.save_path', $config['save_path']); + + $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']); + } + + private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if ($config['formats']) { + $loader->load('request.php'); + + $listener = $container->getDefinition('request.add_request_formats_listener'); + $listener->replaceArgument(0, $config['formats']); + } + } + + private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('assets.php'); + + if ($config['version_strategy']) { + $defaultVersion = new Reference($config['version_strategy']); + } else { + $defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default', $config['strict_mode']); + } + + $defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion); + $container->setDefinition('assets._default_package', $defaultPackage); + + foreach ($config['packages'] as $name => $package) { + if (null !== $package['version_strategy']) { + $version = new Reference($package['version_strategy']); + } elseif (!\array_key_exists('version', $package) && null === $package['json_manifest_path']) { + // if neither version nor json_manifest_path are specified, use the default + $version = $defaultVersion; + } else { + // let format fallback to main version_format + $format = $package['version_format'] ?: $config['version_format']; + $version = $package['version'] ?? null; + $version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name, $package['strict_mode']); + } + + $packageDefinition = $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version) + ->addTag('assets.package', ['package' => $name]); + $container->setDefinition('assets._package_'.$name, $packageDefinition); + $container->registerAliasForArgument('assets._package_'.$name, PackageInterface::class, $name.'.package'); + } + } + + private function registerAssetMapperConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $assetEnabled, bool $httpClientEnabled): void + { + $loader->load('asset_mapper.php'); + + if (!$assetEnabled) { + $container->removeDefinition('asset_mapper.asset_package'); + } + + if (!$httpClientEnabled) { + $container->register('asset_mapper.http_client', HttpClientInterface::class) + ->addTag('container.error') + ->addError('You cannot use the AssetMapper integration since the HttpClient component is not enabled. Try enabling the "framework.http_client" config option.'); + } + + $paths = $config['paths']; + foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { + if ($container->fileExists($dir = $bundle['path'].'/Resources/public') || $container->fileExists($dir = $bundle['path'].'/public')) { + $paths[$dir] = sprintf('bundles/%s', preg_replace('/bundle$/', '', strtolower($name))); + } + } + $excludedPathPatterns = []; + foreach ($config['excluded_patterns'] as $path) { + $excludedPathPatterns[] = Glob::toRegex($path, true, false); + } + + $container->getDefinition('asset_mapper.repository') + ->setArgument(0, $paths) + ->setArgument(2, $excludedPathPatterns) + ->setArgument(3, $config['exclude_dotfiles']); + + $container->getDefinition('asset_mapper.public_assets_path_resolver') + ->setArgument(0, $config['public_prefix']); + + $publicDirectory = $this->getPublicDirectory($container); + $publicAssetsDirectory = rtrim($publicDirectory.'/'.ltrim($config['public_prefix'], '/'), '/'); + $container->getDefinition('asset_mapper.local_public_assets_filesystem') + ->setArgument(0, $publicDirectory) + ; + + $container->getDefinition('asset_mapper.compiled_asset_mapper_config_reader') + ->setArgument(0, $publicAssetsDirectory); + + if (!$config['server']) { + $container->removeDefinition('asset_mapper.dev_server_subscriber'); + } else { + $container->getDefinition('asset_mapper.dev_server_subscriber') + ->setArgument(1, $config['public_prefix']) + ->setArgument(2, $config['extensions']); + } + + $container->getDefinition('asset_mapper.compiler.css_asset_url_compiler') + ->setArgument(0, $config['missing_import_mode']); + + $container->getDefinition('asset_mapper.compiler.javascript_import_path_compiler') + ->setArgument(1, $config['missing_import_mode']); + + $container + ->getDefinition('asset_mapper.importmap.remote_package_storage') + ->replaceArgument(0, $config['vendor_dir']) + ; + $container + ->getDefinition('asset_mapper.mapped_asset_factory') + ->replaceArgument(2, $config['vendor_dir']) + ; + + $container + ->getDefinition('asset_mapper.importmap.config_reader') + ->replaceArgument(0, $config['importmap_path']) + ; + + $container + ->getDefinition('asset_mapper.importmap.renderer') + ->replaceArgument(3, $config['importmap_polyfill']) + ->replaceArgument(4, $config['importmap_script_attributes']) + ; + } + + /** + * Returns a definition for an asset package. + */ + private function createPackageDefinition(?string $basePath, array $baseUrls, Reference $version): Definition + { + if ($basePath && $baseUrls) { + throw new \LogicException('An asset package cannot have base URLs and base paths.'); + } + + $package = new ChildDefinition($baseUrls ? 'assets.url_package' : 'assets.path_package'); + $package + ->replaceArgument(0, $baseUrls ?: $basePath) + ->replaceArgument(1, $version) + ; + + return $package; + } + + private function createVersion(ContainerBuilder $container, ?string $version, ?string $format, ?string $jsonManifestPath, string $name, bool $strictMode): Reference + { + // Configuration prevents $version and $jsonManifestPath from being set + if (null !== $version) { + $def = new ChildDefinition('assets.static_version_strategy'); + $def + ->replaceArgument(0, $version) + ->replaceArgument(1, $format) + ; + $container->setDefinition('assets._version_'.$name, $def); + + return new Reference('assets._version_'.$name); + } + + if (null !== $jsonManifestPath) { + $def = new ChildDefinition('assets.json_manifest_version_strategy'); + $def->replaceArgument(0, $jsonManifestPath); + $def->replaceArgument(2, $strictMode); + $container->setDefinition('assets._version_'.$name, $def); + + return new Reference('assets._version_'.$name); + } + + return new Reference('assets.empty_version_strategy'); + } + + private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales): void + { + if (!$this->readConfigEnabled('translator', $container, $config)) { + $container->removeDefinition('console.command.translation_debug'); + $container->removeDefinition('console.command.translation_extract'); + $container->removeDefinition('console.command.translation_pull'); + $container->removeDefinition('console.command.translation_push'); + + return; + } + + $loader->load('translation.php'); + + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleSwitcher::class, ['symfony/framework-bundle'])) { + $container->removeDefinition('translation.locale_switcher'); + } + + // don't use ContainerBuilder::willBeAvailable() as these are not needed in production + if (interface_exists(Parser::class) && class_exists(PhpAstExtractor::class)) { + $container->removeDefinition('translation.extractor.php'); + } else { + $container->removeDefinition('translation.extractor.php_ast'); + } + + $loader->load('translation_providers.php'); + + // Use the "real" translator instead of the identity default + $container->setAlias('translator', 'translator.default')->setPublic(true); + $container->setAlias('translator.formatter', new Alias($config['formatter'], false)); + $translator = $container->findDefinition('translator.default'); + $translator->addMethodCall('setFallbackLocales', [$config['fallbacks'] ?: [$defaultLocale]]); + + $defaultOptions = $translator->getArgument(4); + $defaultOptions['cache_dir'] = $config['cache_dir']; + $translator->setArgument(4, $defaultOptions); + $translator->setArgument(5, $enabledLocales); + + $container->setParameter('translator.logging', $config['logging']); + $container->setParameter('translator.default_path', $config['default_path']); + + // Discover translation directories + $dirs = []; + $transPaths = []; + $nonExistingDirs = []; + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'])) { + $r = new \ReflectionClass(Validation::class); + + $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; + } + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'])) { + $r = new \ReflectionClass(Form::class); + + $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; + } + if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'])) { + $r = new \ReflectionClass(AuthenticationException::class); + + $dirs[] = $transPaths[] = \dirname($r->getFileName(), 2).'/Resources/translations'; + } + $defaultDir = $container->getParameterBag()->resolveValue($config['default_path']); + foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { + if ($container->fileExists($dir = $bundle['path'].'/Resources/translations') || $container->fileExists($dir = $bundle['path'].'/translations')) { + $dirs[] = $transPaths[] = $dir; + } else { + $nonExistingDirs[] = $dir; + } + } + + foreach ($config['paths'] as $dir) { + if ($container->fileExists($dir)) { + $dirs[] = $transPaths[] = $dir; + } else { + throw new \UnexpectedValueException(sprintf('"%s" defined in translator.paths does not exist or is not a directory.', $dir)); + } + } + + if ($container->hasDefinition('console.command.translation_debug')) { + $container->getDefinition('console.command.translation_debug')->replaceArgument(5, $transPaths); + } + + if ($container->hasDefinition('console.command.translation_extract')) { + $container->getDefinition('console.command.translation_extract')->replaceArgument(6, $transPaths); + } + + if (null === $defaultDir) { + // allow null + } elseif ($container->fileExists($defaultDir)) { + $dirs[] = $defaultDir; + } else { + $nonExistingDirs[] = $defaultDir; + } + + // Register translation resources + if ($dirs) { + $files = []; + + foreach ($dirs as $dir) { + $finder = Finder::create() + ->followLinks() + ->files() + ->filter(fn (\SplFileInfo $file) => 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename())) + ->in($dir) + ->sortByName() + ; + foreach ($finder as $file) { + $fileNameParts = explode('.', basename($file)); + $locale = $fileNameParts[\count($fileNameParts) - 2]; + if (!isset($files[$locale])) { + $files[$locale] = []; + } + + $files[$locale][] = (string) $file; + } + } + + $projectDir = $container->getParameter('kernel.project_dir'); + + $options = array_merge( + $translator->getArgument(4), + [ + 'resource_files' => $files, + 'scanned_directories' => $scannedDirectories = array_merge($dirs, $nonExistingDirs), + 'cache_vary' => [ + 'scanned_directories' => array_map(fn ($dir) => str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir, $scannedDirectories), + ], + ] + ); + + $translator->replaceArgument(4, $options); + } + + if ($config['pseudo_localization']['enabled']) { + $options = $config['pseudo_localization']; + unset($options['enabled']); + + $container + ->register('translator.pseudo', PseudoLocalizationTranslator::class) + ->setDecoratedService('translator', null, -1) // Lower priority than "translator.data_collector" + ->setArguments([ + new Reference('translator.pseudo.inner'), + $options, + ]); + } + + $classToServices = [ + TranslationBridge\Crowdin\CrowdinProviderFactory::class => 'translation.provider_factory.crowdin', + TranslationBridge\Loco\LocoProviderFactory::class => 'translation.provider_factory.loco', + TranslationBridge\Lokalise\LokaliseProviderFactory::class => 'translation.provider_factory.lokalise', + TranslationBridge\Phrase\PhraseProviderFactory::class => 'translation.provider_factory.phrase', + ]; + + $parentPackages = ['symfony/framework-bundle', 'symfony/translation', 'symfony/http-client']; + + foreach ($classToServices as $class => $service) { + $package = substr($service, \strlen('translation.provider_factory.')); + + if (!$container->hasDefinition('http_client') || !ContainerBuilder::willBeAvailable(sprintf('symfony/%s-translation-provider', $package), $class, $parentPackages)) { + $container->removeDefinition($service); + } + } + + if (!$config['providers']) { + return; + } + + $locales = $enabledLocales; + + foreach ($config['providers'] as $provider) { + if ($provider['locales']) { + $locales += $provider['locales']; + } + } + + $locales = array_unique($locales); + + $container->getDefinition('console.command.translation_pull') + ->replaceArgument(4, array_merge($transPaths, [$config['default_path']])) + ->replaceArgument(5, $locales) + ; + + $container->getDefinition('console.command.translation_push') + ->replaceArgument(2, array_merge($transPaths, [$config['default_path']])) + ->replaceArgument(3, $locales) + ; + + $container->getDefinition('translation.provider_collection_factory') + ->replaceArgument(1, $locales) + ; + + $container->getDefinition('translation.provider_collection')->setArgument(0, $config['providers']); + } + + private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled): void + { + if (!$this->readConfigEnabled('validation', $container, $config)) { + $container->removeDefinition('console.command.validator_debug'); + + return; + } + + if (!class_exists(Validation::class)) { + throw new LogicException('Validation support cannot be enabled as the Validator component is not installed. Try running "composer require symfony/validator".'); + } + + if (!isset($config['email_validation_mode'])) { + $config['email_validation_mode'] = 'loose'; + } + + $loader->load('validator.php'); + + $validatorBuilder = $container->getDefinition('validator.builder'); + + $container->setParameter('validator.translation_domain', $config['translation_domain']); + + $files = ['xml' => [], 'yml' => []]; + $this->registerValidatorMapping($container, $config, $files); + + if (!empty($files['xml'])) { + $validatorBuilder->addMethodCall('addXmlMappings', [$files['xml']]); + } + + if (!empty($files['yml'])) { + $validatorBuilder->addMethodCall('addYamlMappings', [$files['yml']]); + } + + $definition = $container->findDefinition('validator.email'); + $definition->replaceArgument(0, $config['email_validation_mode']); + + if (\array_key_exists('enable_attributes', $config) && $config['enable_attributes']) { + $validatorBuilder->addMethodCall('enableAttributeMapping'); + } + + if (\array_key_exists('static_method', $config) && $config['static_method']) { + foreach ($config['static_method'] as $methodName) { + $validatorBuilder->addMethodCall('addMethodMapping', [$methodName]); + } + } + + if (!$container->getParameter('kernel.debug')) { + $validatorBuilder->addMethodCall('setMappingCache', [new Reference('validator.mapping.cache.adapter')]); + } + + $container->setParameter('validator.auto_mapping', $config['auto_mapping']); + if (!$propertyInfoEnabled || !class_exists(PropertyInfoLoader::class)) { + $container->removeDefinition('validator.property_info_loader'); + } + + $container + ->getDefinition('validator.not_compromised_password') + ->setArgument(2, $config['not_compromised_password']['enabled']) + ->setArgument(3, $config['not_compromised_password']['endpoint']) + ; + + if (!class_exists(ExpressionLanguage::class)) { + $container->removeDefinition('validator.expression_language'); + $container->removeDefinition('validator.expression_language_provider'); + } elseif (!class_exists(ExpressionLanguageProvider::class)) { + $container->removeDefinition('validator.expression_language_provider'); + } + } + + private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files): void + { + $fileRecorder = function ($extension, $path) use (&$files) { + $files['yaml' === $extension ? 'yml' : $extension][] = $path; + }; + + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'])) { + $reflClass = new \ReflectionClass(Form::class); + $fileRecorder('xml', \dirname($reflClass->getFileName()).'/Resources/config/validation.xml'); + } + + foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { + $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config'; + + if ( + $container->fileExists($file = $configDir.'/validation.yaml', false) + || $container->fileExists($file = $configDir.'/validation.yml', false) + ) { + $fileRecorder('yml', $file); + } + + if ($container->fileExists($file = $configDir.'/validation.xml', false)) { + $fileRecorder('xml', $file); + } + + if ($container->fileExists($dir = $configDir.'/validation', '/^$/')) { + $this->registerMappingFilesFromDir($dir, $fileRecorder); + } + } + + $projectDir = $container->getParameter('kernel.project_dir'); + if ($container->fileExists($dir = $projectDir.'/config/validator', '/^$/')) { + $this->registerMappingFilesFromDir($dir, $fileRecorder); + } + + $this->registerMappingFilesFromConfig($container, $config, $fileRecorder); + } + + private function registerMappingFilesFromDir(string $dir, callable $fileRecorder): void + { + foreach (Finder::create()->followLinks()->files()->in($dir)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) { + $fileRecorder($file->getExtension(), $file->getRealPath()); + } + } + + private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder): void + { + foreach ($config['mapping']['paths'] as $path) { + if (is_dir($path)) { + $this->registerMappingFilesFromDir($path, $fileRecorder); + $container->addResource(new DirectoryResource($path, '/^$/')); + } elseif ($container->fileExists($path, false)) { + if (!preg_match('/\.(xml|ya?ml)$/', $path, $matches)) { + throw new \RuntimeException(sprintf('Unsupported mapping type in "%s", supported types are XML & Yaml.', $path)); + } + $fileRecorder($matches[1], $path); + } else { + throw new \RuntimeException(sprintf('Could not open file or directory "%s".', $path)); + } + } + } + + private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$this->readConfigEnabled('property_access', $container, $config)) { + return; + } + + $loader->load('property_access.php'); + + $magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS; + $magicMethods |= $config['magic_call'] ? PropertyAccessor::MAGIC_CALL : 0; + $magicMethods |= $config['magic_get'] ? PropertyAccessor::MAGIC_GET : 0; + $magicMethods |= $config['magic_set'] ? PropertyAccessor::MAGIC_SET : 0; + + $throw = PropertyAccessor::DO_NOT_THROW; + $throw |= $config['throw_exception_on_invalid_index'] ? PropertyAccessor::THROW_ON_INVALID_INDEX : 0; + $throw |= $config['throw_exception_on_invalid_property_path'] ? PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH : 0; + + $container + ->getDefinition('property_accessor') + ->replaceArgument(0, $magicMethods) + ->replaceArgument(1, $throw) + ->replaceArgument(3, new Reference(PropertyReadInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE)) + ->replaceArgument(4, new Reference(PropertyWriteInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE)) + ; + } + + private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$this->readConfigEnabled('secrets', $container, $config)) { + $container->removeDefinition('console.command.secrets_set'); + $container->removeDefinition('console.command.secrets_list'); + $container->removeDefinition('console.command.secrets_reveal'); + $container->removeDefinition('console.command.secrets_remove'); + $container->removeDefinition('console.command.secrets_generate_key'); + $container->removeDefinition('console.command.secrets_decrypt_to_local'); + $container->removeDefinition('console.command.secrets_encrypt_from_local'); + + return; + } + + $loader->load('secrets.php'); + + $container->getDefinition('secrets.vault')->replaceArgument(0, $config['vault_directory']); + + if ($config['local_dotenv_file']) { + $container->getDefinition('secrets.local_vault')->replaceArgument(0, $config['local_dotenv_file']); + } else { + $container->removeDefinition('secrets.local_vault'); + } + + if ($config['decryption_env_var']) { + if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w++$/', $config['decryption_env_var'])) { + throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var'])); + } + + if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'])) { + $container->getDefinition('secrets.decryption_key')->replaceArgument(1, $config['decryption_env_var']); + } else { + $container->getDefinition('secrets.vault')->replaceArgument(1, "%env({$config['decryption_env_var']})%"); + $container->removeDefinition('secrets.decryption_key'); + } + } else { + $container->getDefinition('secrets.vault')->replaceArgument(1, null); + $container->removeDefinition('secrets.decryption_key'); + } + } + + private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!$this->readConfigEnabled('csrf_protection', $container, $config)) { + return; + } + + if (!class_exists(\Symfony\Component\Security\Csrf\CsrfToken::class)) { + throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".'); + } + + if (!$this->isInitializedConfigEnabled('session')) { + throw new \LogicException('CSRF protection needs sessions to be enabled.'); + } + + // Enable services for CSRF protection (even without forms) + $loader->load('security_csrf.php'); + + if (!class_exists(CsrfExtension::class)) { + $container->removeDefinition('twig.extension.security_csrf'); + } + } + + private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('serializer.php'); + + $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); + + if (!$this->isInitializedConfigEnabled('property_access')) { + $container->removeAlias('serializer.property_accessor'); + $container->removeDefinition('serializer.normalizer.object'); + } + + if (!class_exists(Yaml::class)) { + $container->removeDefinition('serializer.encoder.yaml'); + } + + if (!$this->isInitializedConfigEnabled('property_access')) { + $container->removeDefinition('serializer.denormalizer.unwrapping'); + } + + if (!class_exists(Headers::class)) { + $container->removeDefinition('serializer.normalizer.mime_message'); + } + + if ($container->getParameter('kernel.debug')) { + $container->removeDefinition('serializer.mapping.cache_class_metadata_factory'); + } + + if (!$this->readConfigEnabled('translator', $container, $config)) { + $container->removeDefinition('serializer.normalizer.translatable'); + } + + $serializerLoaders = []; + if (isset($config['enable_attributes']) && $config['enable_attributes']) { + $attributeLoader = new Definition(AttributeLoader::class); + + $serializerLoaders[] = $attributeLoader; + } + + $fileRecorder = function ($extension, $path) use (&$serializerLoaders) { + $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? YamlFileLoader::class : XmlFileLoader::class, [$path]); + $serializerLoaders[] = $definition; + }; + + foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { + $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config'; + + if ($container->fileExists($file = $configDir.'/serialization.xml', false)) { + $fileRecorder('xml', $file); + } + + if ( + $container->fileExists($file = $configDir.'/serialization.yaml', false) + || $container->fileExists($file = $configDir.'/serialization.yml', false) + ) { + $fileRecorder('yml', $file); + } + + if ($container->fileExists($dir = $configDir.'/serialization', '/^$/')) { + $this->registerMappingFilesFromDir($dir, $fileRecorder); + } + } + + $projectDir = $container->getParameter('kernel.project_dir'); + if ($container->fileExists($dir = $projectDir.'/config/serializer', '/^$/')) { + $this->registerMappingFilesFromDir($dir, $fileRecorder); + } + + $this->registerMappingFilesFromConfig($container, $config, $fileRecorder); + + $chainLoader->replaceArgument(0, $serializerLoaders); + $container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders); + + if (isset($config['name_converter']) && $config['name_converter']) { + $container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter'])); + } + + $defaultContext = $config['default_context'] ?? []; + + if ($defaultContext) { + $container->setParameter('serializer.default_context', $defaultContext); + } + + if (!$container->hasDefinition('serializer.normalizer.object')) { + return; + } + + $arguments = $container->getDefinition('serializer.normalizer.object')->getArguments(); + $context = $arguments[6] ?? $defaultContext; + + if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) { + $context += ['circular_reference_handler' => new Reference($config['circular_reference_handler'])]; + $container->getDefinition('serializer.normalizer.object')->setArgument(5, null); + } + + if ($config['max_depth_handler'] ?? false) { + $context += ['max_depth_handler' => new Reference($config['max_depth_handler'])]; + } + + $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context); + + $container->getDefinition('serializer.normalizer.property')->setArgument(5, $defaultContext); + } + + private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!interface_exists(PropertyInfoExtractorInterface::class)) { + throw new LogicException('PropertyInfo support cannot be enabled as the PropertyInfo component is not installed. Try running "composer require symfony/property-info".'); + } + + $loader->load('property_info.php'); + + if ( + ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/property-info']) + && ContainerBuilder::willBeAvailable('phpdocumentor/type-resolver', ContextFactory::class, ['symfony/framework-bundle', 'symfony/property-info']) + ) { + $definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class); + $definition->addTag('property_info.type_extractor', ['priority' => -1000]); + } + + if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) { + $definition = $container->register('property_info.php_doc_extractor', PhpDocExtractor::class); + $definition->addTag('property_info.description_extractor', ['priority' => -1000]); + $definition->addTag('property_info.type_extractor', ['priority' => -1001]); + } + + if ($container->getParameter('kernel.debug')) { + $container->removeDefinition('property_info.cache'); + } + } + + private function registerTypeInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(Type::class)) { + throw new LogicException('TypeInfo support cannot be enabled as the TypeInfo component is not installed. Try running "composer require symfony/type-info".'); + } + + $loader->load('type_info.php'); + + if (ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/type-info'])) { + $container->register('type_info.resolver.string', StringTypeResolver::class); + + /** @var ServiceLocatorArgument $resolversLocator */ + $resolversLocator = $container->getDefinition('type_info.resolver')->getArgument(0); + $resolversLocator->setValues($resolversLocator->getValues() + [ + 'string' => new Reference('type_info.resolver.string'), + ]); + } + } + + private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('lock.php'); + + foreach ($config['resources'] as $resourceName => $resourceStores) { + if (0 === \count($resourceStores)) { + continue; + } + + // Generate stores + $storeDefinitions = []; + foreach ($resourceStores as $resourceStore) { + $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); + $storeDefinition = new Definition(PersistingStoreInterface::class); + $storeDefinition + ->setFactory([StoreFactory::class, 'createStore']) + ->setArguments([$resourceStore]) + ->addTag('lock.store'); + + $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); + + $storeDefinition = new Reference($storeDefinitionId); + + $storeDefinitions[] = $storeDefinition; + } + + // Wrap array of stores with CombinedStore + if (\count($storeDefinitions) > 1) { + $combinedDefinition = new ChildDefinition('lock.store.combined.abstract'); + $combinedDefinition->replaceArgument(0, $storeDefinitions); + $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($resourceStores), $combinedDefinition); + } + + // Generate factories for each resource + $factoryDefinition = new ChildDefinition('lock.factory.abstract'); + $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId)); + $container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition); + + // provide alias for default resource + if ('default' === $resourceName) { + $container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false)); + $container->setAlias(LockFactory::class, new Alias('lock.factory', false)); + } else { + $container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory'); + } + } + } + + private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('semaphore.php'); + + foreach ($config['resources'] as $resourceName => $resourceStore) { + $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); + $storeDefinition = new Definition(SemaphoreStoreInterface::class); + $storeDefinition->setFactory([SemaphoreStoreFactory::class, 'createStore']); + $storeDefinition->setArguments([$resourceStore]); + + $container->setDefinition($storeDefinitionId = '.semaphore.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); + + // Generate factories for each resource + $factoryDefinition = new ChildDefinition('semaphore.factory.abstract'); + $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId)); + $container->setDefinition('semaphore.'.$resourceName.'.factory', $factoryDefinition); + + // Generate services for semaphore instances + $semaphoreDefinition = new Definition(Semaphore::class); + $semaphoreDefinition->setFactory([new Reference('semaphore.'.$resourceName.'.factory'), 'createSemaphore']); + $semaphoreDefinition->setArguments([$resourceName]); + + // provide alias for default resource + if ('default' === $resourceName) { + $container->setAlias('semaphore.factory', new Alias('semaphore.'.$resourceName.'.factory', false)); + $container->setAlias(SemaphoreFactory::class, new Alias('semaphore.factory', false)); + } else { + $container->registerAliasForArgument('semaphore.'.$resourceName.'.factory', SemaphoreFactory::class, $resourceName.'.semaphore.factory'); + } + } + } + + private function registerSchedulerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(SchedulerTransportFactory::class)) { + throw new LogicException('Scheduler support cannot be enabled as the Scheduler component is not installed. Try running "composer require symfony/scheduler".'); + } + + $loader->load('scheduler.php'); + + if (!$this->hasConsole()) { + $container->removeDefinition('console.command.scheduler_debug'); + } + } + + private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $validationEnabled): void + { + if (!interface_exists(MessageBusInterface::class)) { + throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".'); + } + + if (!$this->hasConsole()) { + $container->removeDefinition('console.command.messenger_stats'); + } + + $loader->load('messenger.php'); + + if (!interface_exists(DenormalizerInterface::class)) { + $container->removeDefinition('serializer.normalizer.flatten_exception'); + } + + if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', MessengerBridge\Amqp\Transport\AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory'); + } + + if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', MessengerBridge\Redis\Transport\RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory'); + } + + if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', MessengerBridge\AmazonSqs\Transport\AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory'); + } + + if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', MessengerBridge\Beanstalkd\Transport\BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory'); + } + + if ($config['stop_worker_on_signals'] && $this->hasConsole()) { + $container->getDefinition('console.command.messenger_consume_messages') + ->replaceArgument(8, $config['stop_worker_on_signals']); + $container->getDefinition('console.command.messenger_failed_messages_retry') + ->replaceArgument(6, $config['stop_worker_on_signals']); + } + + if (null === $config['default_bus'] && 1 === \count($config['buses'])) { + $config['default_bus'] = key($config['buses']); + } + + $defaultMiddleware = [ + 'before' => [ + ['id' => 'add_bus_name_stamp_middleware'], + ['id' => 'reject_redelivered_message_middleware'], + ['id' => 'dispatch_after_current_bus'], + ['id' => 'failed_message_processing_middleware'], + ], + 'after' => [ + ['id' => 'send_message'], + ['id' => 'handle_message'], + ], + ]; + foreach ($config['buses'] as $busId => $bus) { + $middleware = $bus['middleware']; + + if ($bus['default_middleware']['enabled']) { + $defaultMiddleware['after'][0]['arguments'] = [$bus['default_middleware']['allow_no_senders']]; + $defaultMiddleware['after'][1]['arguments'] = [$bus['default_middleware']['allow_no_handlers']]; + + // argument to add_bus_name_stamp_middleware + $defaultMiddleware['before'][0]['arguments'] = [$busId]; + + $middleware = array_merge($defaultMiddleware['before'], $middleware, $defaultMiddleware['after']); + } + + foreach ($middleware as $middlewareItem) { + if (!$validationEnabled && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) { + throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".'); + } + } + + if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) { + array_unshift($middleware, ['id' => 'traceable', 'arguments' => [$busId]]); + } + + $container->setParameter($busId.'.middleware', $middleware); + $container->register($busId, MessageBus::class)->addArgument([])->addTag('messenger.bus'); + + if ($busId === $config['default_bus']) { + $container->setAlias('messenger.default_bus', $busId)->setPublic(true); + $container->setAlias(MessageBusInterface::class, $busId); + } else { + $container->registerAliasForArgument($busId, MessageBusInterface::class); + } + } + + if (empty($config['transports'])) { + $container->removeDefinition('messenger.transport.symfony_serializer'); + $container->removeDefinition('messenger.transport.amqp.factory'); + $container->removeDefinition('messenger.transport.redis.factory'); + $container->removeDefinition('messenger.transport.sqs.factory'); + $container->removeDefinition('messenger.transport.beanstalkd.factory'); + $container->removeAlias(SerializerInterface::class); + } else { + $container->getDefinition('messenger.transport.symfony_serializer') + ->replaceArgument(1, $config['serializer']['symfony_serializer']['format']) + ->replaceArgument(2, $config['serializer']['symfony_serializer']['context']); + $container->setAlias('messenger.default_serializer', $config['serializer']['default_serializer']); + } + + $failureTransports = []; + if ($config['failure_transport']) { + if (!isset($config['transports'][$config['failure_transport']])) { + throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $config['failure_transport'])); + } + + $container->setAlias('messenger.failure_transports.default', 'messenger.transport.'.$config['failure_transport']); + $failureTransports[] = $config['failure_transport']; + } + + $failureTransportsByName = []; + foreach ($config['transports'] as $name => $transport) { + if ($transport['failure_transport']) { + $failureTransports[] = $transport['failure_transport']; + $failureTransportsByName[$name] = $transport['failure_transport']; + } elseif ($config['failure_transport']) { + $failureTransportsByName[$name] = $config['failure_transport']; + } + } + + $senderAliases = []; + $transportRetryReferences = []; + $transportRateLimiterReferences = []; + foreach ($config['transports'] as $name => $transport) { + $serializerId = $transport['serializer'] ?? 'messenger.default_serializer'; + $transportDefinition = (new Definition(TransportInterface::class)) + ->setFactory([new Reference('messenger.transport_factory'), 'createTransport']) + ->setArguments([$transport['dsn'], $transport['options'] + ['transport_name' => $name], new Reference($serializerId)]) + ->addTag('messenger.receiver', [ + 'alias' => $name, + 'is_failure_transport' => \in_array($name, $failureTransports, true), + ]) + ; + $container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition); + $senderAliases[$name] = $transportId; + + if (null !== $transport['retry_strategy']['service']) { + $transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']); + } else { + $retryServiceId = sprintf('messenger.retry.multiplier_retry_strategy.%s', $name); + $retryDefinition = new ChildDefinition('messenger.retry.abstract_multiplier_retry_strategy'); + $retryDefinition + ->replaceArgument(0, $transport['retry_strategy']['max_retries']) + ->replaceArgument(1, $transport['retry_strategy']['delay']) + ->replaceArgument(2, $transport['retry_strategy']['multiplier']) + ->replaceArgument(3, $transport['retry_strategy']['max_delay']) + ->replaceArgument(4, $transport['retry_strategy']['jitter']); + $container->setDefinition($retryServiceId, $retryDefinition); + + $transportRetryReferences[$name] = new Reference($retryServiceId); + } + + if ($transport['rate_limiter']) { + if (!interface_exists(LimiterInterface::class)) { + throw new LogicException('Rate limiter cannot be used within Messenger as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); + } + + $transportRateLimiterReferences[$name] = new Reference('limiter.'.$transport['rate_limiter']); + } + } + + $senderReferences = []; + // alias => service_id + foreach ($senderAliases as $alias => $serviceId) { + $senderReferences[$alias] = new Reference($serviceId); + } + // service_id => service_id + foreach ($senderAliases as $serviceId) { + $senderReferences[$serviceId] = new Reference($serviceId); + } + + foreach ($config['transports'] as $name => $transport) { + if ($transport['failure_transport']) { + if (!isset($senderReferences[$transport['failure_transport']])) { + throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $transport['failure_transport'])); + } + } + } + + $failureTransportReferencesByTransportName = array_map(fn ($failureTransportName) => $senderReferences[$failureTransportName], $failureTransportsByName); + + $messageToSendersMapping = []; + foreach ($config['routing'] as $message => $messageConfiguration) { + if ('*' !== $message && !class_exists($message) && !interface_exists($message, false) && !preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++\*$/', $message)) { + if (str_contains($message, '*')) { + throw new LogicException(sprintf('Invalid Messenger routing configuration: invalid namespace "%s" wildcard.', $message)); + } + + throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message)); + } + + // make sure senderAliases contains all senders + foreach ($messageConfiguration['senders'] as $sender) { + if (!isset($senderReferences[$sender])) { + throw new LogicException(sprintf('Invalid Messenger routing configuration: the "%s" class is being routed to a sender called "%s". This is not a valid transport or service id.', $message, $sender)); + } + } + + $messageToSendersMapping[$message] = $messageConfiguration['senders']; + } + + $sendersServiceLocator = ServiceLocatorTagPass::register($container, $senderReferences); + + $container->getDefinition('messenger.senders_locator') + ->replaceArgument(0, $messageToSendersMapping) + ->replaceArgument(1, $sendersServiceLocator) + ; + + $container->getDefinition('messenger.retry.send_failed_message_for_retry_listener') + ->replaceArgument(0, $sendersServiceLocator) + ; + + $container->getDefinition('messenger.retry_strategy_locator') + ->replaceArgument(0, $transportRetryReferences); + + if (!$transportRateLimiterReferences) { + $container->removeDefinition('messenger.rate_limiter_locator'); + } else { + $container->getDefinition('messenger.rate_limiter_locator') + ->replaceArgument(0, $transportRateLimiterReferences); + } + + if (\count($failureTransports) > 0) { + if ($this->hasConsole()) { + $container->getDefinition('console.command.messenger_failed_messages_retry') + ->replaceArgument(0, $config['failure_transport']); + $container->getDefinition('console.command.messenger_failed_messages_show') + ->replaceArgument(0, $config['failure_transport']); + $container->getDefinition('console.command.messenger_failed_messages_remove') + ->replaceArgument(0, $config['failure_transport']); + } + + $failureTransportsByTransportNameServiceLocator = ServiceLocatorTagPass::register($container, $failureTransportReferencesByTransportName); + $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener') + ->replaceArgument(0, $failureTransportsByTransportNameServiceLocator); + } else { + $container->removeDefinition('messenger.failure.send_failed_message_to_failure_transport_listener'); + $container->removeDefinition('console.command.messenger_failed_messages_retry'); + $container->removeDefinition('console.command.messenger_failed_messages_show'); + $container->removeDefinition('console.command.messenger_failed_messages_remove'); + } + + if (!$container->hasDefinition('console.command.messenger_consume_messages')) { + $container->removeDefinition('messenger.listener.reset_services'); + } + } + + private function registerCacheConfiguration(array $config, ContainerBuilder $container): void + { + $version = new Parameter('container.build_id'); + $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); + $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version); + $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']); + + if (isset($config['prefix_seed'])) { + $container->setParameter('cache.prefix.seed', $config['prefix_seed']); + } + if ($container->hasParameter('cache.prefix.seed')) { + // Inline any env vars referenced in the parameter + $container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true)); + } + foreach (['psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) { + if (isset($config[$name = 'default_'.$name.'_provider'])) { + $container->setAlias('cache.'.$name, new Alias(CachePoolPass::getServiceProvider($container, $config[$name]), false)); + } + } + foreach (['app', 'system'] as $name) { + $config['pools']['cache.'.$name] = [ + 'adapters' => [$config[$name]], + 'public' => true, + 'tags' => false, + ]; + } + foreach ($config['pools'] as $name => $pool) { + $pool['adapters'] = $pool['adapters'] ?: ['cache.app']; + + $isRedisTagAware = ['cache.adapter.redis_tag_aware'] === $pool['adapters']; + foreach ($pool['adapters'] as $provider => $adapter) { + if (($config['pools'][$adapter]['adapters'] ?? null) === ['cache.adapter.redis_tag_aware']) { + $isRedisTagAware = true; + } elseif ($config['pools'][$adapter]['tags'] ?? false) { + $pool['adapters'][$provider] = $adapter = '.'.$adapter.'.inner'; + } + } + + if (1 === \count($pool['adapters'])) { + if (!isset($pool['provider']) && !\is_int($provider)) { + $pool['provider'] = $provider; + } + $definition = new ChildDefinition($adapter); + } else { + $definition = new Definition(ChainAdapter::class, [$pool['adapters'], 0]); + $pool['reset'] = 'reset'; + } + + if ($isRedisTagAware && 'cache.app' === $name) { + $container->setAlias('cache.app.taggable', $name); + $definition->addTag('cache.taggable', ['pool' => $name]); + } elseif ($isRedisTagAware) { + $tagAwareId = $name; + $container->setAlias('.'.$name.'.inner', $name); + $definition->addTag('cache.taggable', ['pool' => $name]); + } elseif ($pool['tags']) { + if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) { + $pool['tags'] = '.'.$pool['tags'].'.inner'; + } + $container->register($name, TagAwareAdapter::class) + ->addArgument(new Reference('.'.$name.'.inner')) + ->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null) + ->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) + ->setPublic($pool['public']) + ->addTag('cache.taggable', ['pool' => $name]) + ->addTag('monolog.logger', ['channel' => 'cache']); + + $pool['name'] = $tagAwareId = $name; + $pool['public'] = false; + $name = '.'.$name.'.inner'; + } elseif (!\in_array($name, ['cache.app', 'cache.system'], true)) { + $tagAwareId = '.'.$name.'.taggable'; + $container->register($tagAwareId, TagAwareAdapter::class) + ->addArgument(new Reference($name)) + ->addTag('cache.taggable', ['pool' => $name]) + ; + } + + if (!\in_array($name, ['cache.app', 'cache.system'], true)) { + $container->registerAliasForArgument($tagAwareId, TagAwareCacheInterface::class, $pool['name'] ?? $name); + $container->registerAliasForArgument($name, CacheInterface::class, $pool['name'] ?? $name); + $container->registerAliasForArgument($name, CacheItemPoolInterface::class, $pool['name'] ?? $name); + } + + $definition->setPublic($pool['public']); + unset($pool['adapters'], $pool['public'], $pool['tags']); + + $definition->addTag('cache.pool', $pool); + $container->setDefinition($name, $definition); + } + + if (class_exists(PropertyAccessor::class)) { + $propertyAccessDefinition = $container->register('cache.property_access', AdapterInterface::class); + + if (!$container->getParameter('kernel.debug')) { + $propertyAccessDefinition->setFactory([PropertyAccessor::class, 'createCache']); + $propertyAccessDefinition->setArguments(['', 0, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]); + $propertyAccessDefinition->addTag('cache.pool', ['clearer' => 'cache.system_clearer']); + $propertyAccessDefinition->addTag('monolog.logger', ['channel' => 'cache']); + } else { + $propertyAccessDefinition->setClass(ArrayAdapter::class); + $propertyAccessDefinition->setArguments([0, false]); + } + } + } + + private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('http_client.php'); + + $options = $config['default_options'] ?? []; + $rateLimiter = $options['rate_limiter'] ?? null; + unset($options['rate_limiter']); + $retryOptions = $options['retry_failed'] ?? ['enabled' => false]; + unset($options['retry_failed']); + $defaultUriTemplateVars = $options['vars'] ?? []; + unset($options['vars']); + $container->getDefinition('http_client.transport')->setArguments([$options, $config['max_host_connections'] ?? 6]); + + if (!class_exists(PingWebhookMessageHandler::class)) { + $container->removeDefinition('http_client.messenger.ping_webhook_handler'); + } + + if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'])) { + $container->removeDefinition('psr18.http_client'); + $container->removeAlias(ClientInterface::class); + } + + if (!$hasHttplug = ContainerBuilder::willBeAvailable('php-http/httplug', HttpAsyncClient::class, ['symfony/framework-bundle', 'symfony/http-client'])) { + $container->removeDefinition('httplug.http_client'); + $container->removeAlias(HttpAsyncClient::class); + $container->removeAlias(HttpClient::class); + } + + if (null !== $rateLimiter) { + $this->registerThrottlingHttpClient($rateLimiter, 'http_client', $container); + } + + if ($this->readConfigEnabled('http_client.retry_failed', $container, $retryOptions)) { + $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); + } + + if (ContainerBuilder::willBeAvailable('guzzlehttp/uri-template', \GuzzleHttp\UriTemplate\UriTemplate::class, [])) { + $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.guzzle'); + } elseif (ContainerBuilder::willBeAvailable('rize/uri-template', \Rize\UriTemplate::class, [])) { + $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.rize'); + } + + $container + ->getDefinition('http_client.uri_template') + ->setArgument(2, $defaultUriTemplateVars); + + foreach ($config['scoped_clients'] as $name => $scopeConfig) { + if ($container->has($name)) { + throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name)); + } + + $scope = $scopeConfig['scope'] ?? null; + unset($scopeConfig['scope']); + $rateLimiter = $scopeConfig['rate_limiter'] ?? null; + unset($scopeConfig['rate_limiter']); + $retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => false]; + unset($scopeConfig['retry_failed']); + + if (null === $scope) { + $baseUri = $scopeConfig['base_uri']; + unset($scopeConfig['base_uri']); + + $container->register($name, ScopingHttpClient::class) + ->setFactory([ScopingHttpClient::class, 'forBaseUri']) + ->setArguments([new Reference('http_client.transport'), $baseUri, $scopeConfig]) + ->addTag('http_client.client') + ; + } else { + $container->register($name, ScopingHttpClient::class) + ->setArguments([new Reference('http_client.transport'), [$scope => $scopeConfig], $scope]) + ->addTag('http_client.client') + ; + } + + if (null !== $rateLimiter) { + $this->registerThrottlingHttpClient($rateLimiter, $name, $container); + } + + if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'.retry_failed', $container, $retryOptions)) { + $this->registerRetryableHttpClient($retryOptions, $name, $container); + } + + $container + ->register($name.'.uri_template', UriTemplateHttpClient::class) + ->setDecoratedService($name, null, 7) // Between TraceableHttpClient (5) and RetryableHttpClient (10) + ->setArguments([ + new Reference($name.'.uri_template.inner'), + new Reference('http_client.uri_template_expander', ContainerInterface::NULL_ON_INVALID_REFERENCE), + $defaultUriTemplateVars, + ]); + + $container->registerAliasForArgument($name, HttpClientInterface::class); + + if ($hasPsr18) { + $container->setDefinition('psr18.'.$name, new ChildDefinition('psr18.http_client')) + ->replaceArgument(0, new Reference($name)); + + $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name); + } + + if ($hasHttplug) { + $container->setDefinition('httplug.'.$name, new ChildDefinition('httplug.http_client')) + ->replaceArgument(0, new Reference($name)); + + $container->registerAliasForArgument('httplug.'.$name, HttpAsyncClient::class, $name); + } + } + + if ($responseFactoryId = $config['mock_response_factory'] ?? null) { + $container->register('http_client.mock_client', MockHttpClient::class) + ->setDecoratedService('http_client.transport', null, -10) // lower priority than TraceableHttpClient (5) + ->setArguments([new Reference($responseFactoryId)]); + } + } + + private function registerThrottlingHttpClient(string $rateLimiter, string $name, ContainerBuilder $container): void + { + if (!class_exists(ThrottlingHttpClient::class)) { + throw new LogicException('Rate limiter support cannot be enabled as version 7.1+ of the HttpClient component is required.'); + } + + if (!$this->isInitializedConfigEnabled('rate_limiter')) { + throw new LogicException('Rate limiter cannot be used within HttpClient as the RateLimiter component is not enabled.'); + } + + $container->register($name.'.throttling.limiter', LimiterInterface::class) + ->setFactory([new Reference('limiter.'.$rateLimiter), 'create']); + + $container + ->register($name.'.throttling', ThrottlingHttpClient::class) + ->setDecoratedService($name, null, 15) // higher priority than RetryableHttpClient (10) + ->setArguments([new Reference($name.'.throttling.inner'), new Reference($name.'.throttling.limiter')]); + } + + private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container): void + { + if (null !== $options['retry_strategy']) { + $retryStrategy = new Reference($options['retry_strategy']); + } else { + $retryStrategy = new ChildDefinition('http_client.abstract_retry_strategy'); + $codes = []; + foreach ($options['http_codes'] as $code => $codeOptions) { + if ($codeOptions['methods']) { + $codes[$code] = $codeOptions['methods']; + } else { + $codes[] = $code; + } + } + + $retryStrategy + ->replaceArgument(0, $codes ?: GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES) + ->replaceArgument(1, $options['delay']) + ->replaceArgument(2, $options['multiplier']) + ->replaceArgument(3, $options['max_delay']) + ->replaceArgument(4, $options['jitter']); + $container->setDefinition($name.'.retry_strategy', $retryStrategy); + + $retryStrategy = new Reference($name.'.retry_strategy'); + } + + $container + ->register($name.'.retryable', RetryableHttpClient::class) + ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient (5) + ->setArguments([new Reference($name.'.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')]) + ->addTag('monolog.logger', ['channel' => 'http_client']); + } + + private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void + { + if (!class_exists(Mailer::class)) { + throw new LogicException('Mailer support cannot be enabled as the component is not installed. Try running "composer require symfony/mailer".'); + } + + $loader->load('mailer.php'); + $loader->load('mailer_transports.php'); + if (!\count($config['transports']) && null === $config['dsn']) { + $config['dsn'] = 'smtp://null'; + } + $transports = $config['dsn'] ? ['main' => $config['dsn']] : $config['transports']; + $container->getDefinition('mailer.transports')->setArgument(0, $transports); + $container->getDefinition('mailer.default_transport')->setArgument(0, current($transports)); + + $mailer = $container->getDefinition('mailer.mailer'); + if (false === $messageBus = $config['message_bus']) { + $mailer->replaceArgument(1, null); + } else { + $mailer->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + + $classToServices = [ + MailerBridge\Azure\Transport\AzureTransportFactory::class => 'mailer.transport_factory.azure', + MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo', + MailerBridge\Google\Transport\GmailTransportFactory::class => 'mailer.transport_factory.gmail', + MailerBridge\Infobip\Transport\InfobipTransportFactory::class => 'mailer.transport_factory.infobip', + MailerBridge\MailerSend\Transport\MailerSendTransportFactory::class => 'mailer.transport_factory.mailersend', + MailerBridge\Mailgun\Transport\MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', + MailerBridge\Mailjet\Transport\MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', + MailerBridge\MailPace\Transport\MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace', + MailerBridge\Mailchimp\Transport\MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', + MailerBridge\Postmark\Transport\PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', + MailerBridge\Resend\Transport\ResendTransportFactory::class => 'mailer.transport_factory.resend', + MailerBridge\Scaleway\Transport\ScalewayTransportFactory::class => 'mailer.transport_factory.scaleway', + MailerBridge\Sendgrid\Transport\SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', + MailerBridge\Amazon\Transport\SesTransportFactory::class => 'mailer.transport_factory.amazon', + ]; + + foreach ($classToServices as $class => $service) { + $package = substr($service, \strlen('mailer.transport_factory.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) { + $container->removeDefinition($service); + } + } + + if ($webhookEnabled) { + $webhookRequestParsers = [ + MailerBridge\Brevo\Webhook\BrevoRequestParser::class => 'mailer.webhook.request_parser.brevo', + MailerBridge\MailerSend\Webhook\MailerSendRequestParser::class => 'mailer.webhook.request_parser.mailersend', + MailerBridge\Mailgun\Webhook\MailgunRequestParser::class => 'mailer.webhook.request_parser.mailgun', + MailerBridge\Mailjet\Webhook\MailjetRequestParser::class => 'mailer.webhook.request_parser.mailjet', + MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark', + MailerBridge\Resend\Webhook\ResendRequestParser::class => 'mailer.webhook.request_parser.resend', + MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class => 'mailer.webhook.request_parser.sendgrid', + ]; + + foreach ($webhookRequestParsers as $class => $service) { + $package = substr($service, \strlen('mailer.webhook.request_parser.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) { + $container->removeDefinition($service); + } + } + } + + $envelopeListener = $container->getDefinition('mailer.envelope_listener'); + $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); + $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); + $envelopeListener->setArgument(2, $config['envelope']['allowed_recipients'] ?? []); + + if ($config['headers']) { + $headers = new Definition(Headers::class); + foreach ($config['headers'] as $name => $data) { + $value = $data['value']; + if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) { + $value = (array) $value; + } + $headers->addMethodCall('addHeader', [$name, $value]); + } + $messageListener = $container->getDefinition('mailer.message_listener'); + $messageListener->setArgument(0, $headers); + } else { + $container->removeDefinition('mailer.message_listener'); + } + + if (!class_exists(MessengerTransportListener::class)) { + $container->removeDefinition('mailer.messenger_transport_listener'); + } + + if ($webhookEnabled) { + $loader->load('mailer_webhook.php'); + } + } + + private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void + { + if (!class_exists(Notifier::class)) { + throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".'); + } + + $loader->load('notifier.php'); + $loader->load('notifier_transports.php'); + + if ($config['chatter_transports']) { + $container->getDefinition('chatter.transports')->setArgument(0, $config['chatter_transports']); + } else { + $container->removeDefinition('chatter'); + $container->removeAlias(ChatterInterface::class); + } + if ($config['texter_transports']) { + $container->getDefinition('texter.transports')->setArgument(0, $config['texter_transports']); + } else { + $container->removeDefinition('texter'); + $container->removeAlias(TexterInterface::class); + } + + if ($this->isInitializedConfigEnabled('mailer')) { + $sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0); + $container->getDefinition('notifier.channel.email')->setArgument(2, $sender); + } else { + $container->removeDefinition('notifier.channel.email'); + } + + foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms'] as $serviceId) { + if (!$container->hasDefinition($serviceId)) { + continue; + } + + if (false === $messageBus = $config['message_bus']) { + $container->getDefinition($serviceId)->replaceArgument(1, null); + } else { + $container->getDefinition($serviceId)->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + } + + if ($this->isInitializedConfigEnabled('messenger')) { + if ($config['notification_on_failed_messages']) { + $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber'); + } + + // as we have a bus, the channels don't need the transports + $container->getDefinition('notifier.channel.chat')->setArgument(0, null); + if ($container->hasDefinition('notifier.channel.email')) { + $container->getDefinition('notifier.channel.email')->setArgument(0, null); + } + $container->getDefinition('notifier.channel.sms')->setArgument(0, null); + $container->getDefinition('notifier.channel.push')->setArgument(0, null); + } + + $container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']); + + $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class) + ->addTag('chatter.transport_factory'); + + $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class) + ->addTag('texter.transport_factory'); + + $classToServices = [ + NotifierBridge\AllMySms\AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', + NotifierBridge\AmazonSns\AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', + NotifierBridge\Bandwidth\BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth', + NotifierBridge\Bluesky\BlueskyTransportFactory::class => 'notifier.transport_factory.bluesky', + NotifierBridge\Brevo\BrevoTransportFactory::class => 'notifier.transport_factory.brevo', + NotifierBridge\Chatwork\ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', + NotifierBridge\Clickatell\ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', + NotifierBridge\ClickSend\ClickSendTransportFactory::class => 'notifier.transport_factory.click-send', + NotifierBridge\ContactEveryone\ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone', + NotifierBridge\Discord\DiscordTransportFactory::class => 'notifier.transport_factory.discord', + NotifierBridge\Engagespot\EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot', + NotifierBridge\Esendex\EsendexTransportFactory::class => 'notifier.transport_factory.esendex', + NotifierBridge\Expo\ExpoTransportFactory::class => 'notifier.transport_factory.expo', + NotifierBridge\FakeChat\FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', + NotifierBridge\FakeSms\FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', + NotifierBridge\Firebase\FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', + NotifierBridge\FortySixElks\FortySixElksTransportFactory::class => 'notifier.transport_factory.forty-six-elks', + NotifierBridge\FreeMobile\FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', + NotifierBridge\GatewayApi\GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', + NotifierBridge\Gitter\GitterTransportFactory::class => 'notifier.transport_factory.gitter', + NotifierBridge\GoIp\GoIpTransportFactory::class => 'notifier.transport_factory.go-ip', + NotifierBridge\GoogleChat\GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', + NotifierBridge\Infobip\InfobipTransportFactory::class => 'notifier.transport_factory.infobip', + NotifierBridge\Iqsms\IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', + NotifierBridge\Isendpro\IsendproTransportFactory::class => 'notifier.transport_factory.isendpro', + NotifierBridge\KazInfoTeh\KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', + NotifierBridge\LightSms\LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', + NotifierBridge\LineNotify\LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify', + NotifierBridge\LinkedIn\LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', + NotifierBridge\Lox24\Lox24TransportFactory::class => 'notifier.transport_factory.lox24', + NotifierBridge\Mailjet\MailjetTransportFactory::class => 'notifier.transport_factory.mailjet', + NotifierBridge\Mastodon\MastodonTransportFactory::class => 'notifier.transport_factory.mastodon', + NotifierBridge\Mattermost\MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', + NotifierBridge\Mercure\MercureTransportFactory::class => 'notifier.transport_factory.mercure', + NotifierBridge\MessageBird\MessageBirdTransportFactory::class => 'notifier.transport_factory.message-bird', + NotifierBridge\MessageMedia\MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media', + NotifierBridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams', + NotifierBridge\Mobyt\MobytTransportFactory::class => 'notifier.transport_factory.mobyt', + NotifierBridge\Novu\NovuTransportFactory::class => 'notifier.transport_factory.novu', + NotifierBridge\Ntfy\NtfyTransportFactory::class => 'notifier.transport_factory.ntfy', + NotifierBridge\Octopush\OctopushTransportFactory::class => 'notifier.transport_factory.octopush', + NotifierBridge\OneSignal\OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', + NotifierBridge\OrangeSms\OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', + NotifierBridge\OvhCloud\OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', + NotifierBridge\PagerDuty\PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty', + NotifierBridge\Plivo\PlivoTransportFactory::class => 'notifier.transport_factory.plivo', + NotifierBridge\Pushover\PushoverTransportFactory::class => 'notifier.transport_factory.pushover', + NotifierBridge\Pushy\PushyTransportFactory::class => 'notifier.transport_factory.pushy', + NotifierBridge\Redlink\RedlinkTransportFactory::class => 'notifier.transport_factory.redlink', + NotifierBridge\RingCentral\RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', + NotifierBridge\RocketChat\RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', + NotifierBridge\Sendberry\SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', + NotifierBridge\SimpleTextin\SimpleTextinTransportFactory::class => 'notifier.transport_factory.simple-textin', + NotifierBridge\Sevenio\SevenIoTransportFactory::class => 'notifier.transport_factory.sevenio', + NotifierBridge\Sinch\SinchTransportFactory::class => 'notifier.transport_factory.sinch', + NotifierBridge\Slack\SlackTransportFactory::class => 'notifier.transport_factory.slack', + NotifierBridge\Sms77\Sms77TransportFactory::class => 'notifier.transport_factory.sms77', + NotifierBridge\Smsapi\SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', + NotifierBridge\SmsBiuras\SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', + NotifierBridge\Smsbox\SmsboxTransportFactory::class => 'notifier.transport_factory.smsbox', + NotifierBridge\Smsc\SmscTransportFactory::class => 'notifier.transport_factory.smsc', + NotifierBridge\SmsFactor\SmsFactorTransportFactory::class => 'notifier.transport_factory.sms-factor', + NotifierBridge\Smsmode\SmsmodeTransportFactory::class => 'notifier.transport_factory.smsmode', + NotifierBridge\SmsSluzba\SmsSluzbaTransportFactory::class => 'notifier.transport_factory.sms-sluzba', + NotifierBridge\Smsense\SmsenseTransportFactory::class => 'notifier.transport_factory.smsense', + NotifierBridge\SpotHit\SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', + NotifierBridge\Telegram\TelegramTransportFactory::class => 'notifier.transport_factory.telegram', + NotifierBridge\Telnyx\TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', + NotifierBridge\Termii\TermiiTransportFactory::class => 'notifier.transport_factory.termii', + NotifierBridge\TurboSms\TurboSmsTransportFactory::class => 'notifier.transport_factory.turbo-sms', + NotifierBridge\Twilio\TwilioTransportFactory::class => 'notifier.transport_factory.twilio', + NotifierBridge\Twitter\TwitterTransportFactory::class => 'notifier.transport_factory.twitter', + NotifierBridge\Unifonic\UnifonicTransportFactory::class => 'notifier.transport_factory.unifonic', + NotifierBridge\Vonage\VonageTransportFactory::class => 'notifier.transport_factory.vonage', + NotifierBridge\Yunpian\YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', + NotifierBridge\Zendesk\ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', + NotifierBridge\Zulip\ZulipTransportFactory::class => 'notifier.transport_factory.zulip', + ]; + + $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; + + foreach ($classToServices as $class => $service) { + $package = substr($service, \strlen('notifier.transport_factory.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages)) { + $container->removeDefinition($service); + } + } + + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages) && \in_array(MercureBundle::class, $container->getParameter('kernel.bundles'), true)) { + $container->getDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]) + ->replaceArgument(0, new Reference(HubRegistry::class)) + ->replaceArgument(1, new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE)) + ->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); + } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages)) { + $container->removeDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]); + } + + if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', NotifierBridge\FakeChat\FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { + $container->getDefinition($classToServices[NotifierBridge\FakeChat\FakeChatTransportFactory::class]) + ->replaceArgument(0, new Reference('mailer')) + ->replaceArgument(1, new Reference('logger')) + ->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE)) + ->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); + } + + if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', NotifierBridge\FakeSms\FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { + $container->getDefinition($classToServices[NotifierBridge\FakeSms\FakeSmsTransportFactory::class]) + ->replaceArgument(0, new Reference('mailer')) + ->replaceArgument(1, new Reference('logger')) + ->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE)) + ->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); + } + + if (ContainerBuilder::willBeAvailable('symfony/bluesky-notifier', NotifierBridge\Bluesky\BlueskyTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier'])) { + $container->getDefinition($classToServices[NotifierBridge\Bluesky\BlueskyTransportFactory::class]) + ->addArgument(new Reference('logger')); + } + + if (isset($config['admin_recipients'])) { + $notifier = $container->getDefinition('notifier'); + foreach ($config['admin_recipients'] as $i => $recipient) { + $id = 'notifier.admin_recipient.'.$i; + $container->setDefinition($id, new Definition(Recipient::class, [$recipient['email'], $recipient['phone']])); + $notifier->addMethodCall('addAdminRecipient', [new Reference($id)]); + } + } + + if ($webhookEnabled) { + $loader->load('notifier_webhook.php'); + + $webhookRequestParsers = [ + NotifierBridge\Twilio\Webhook\TwilioRequestParser::class => 'notifier.webhook.request_parser.twilio', + NotifierBridge\Vonage\Webhook\VonageRequestParser::class => 'notifier.webhook.request_parser.vonage', + ]; + + foreach ($webhookRequestParsers as $class => $service) { + $package = substr($service, \strlen('notifier.webhook.request_parser.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, ['symfony/framework-bundle', 'symfony/notifier'])) { + $container->removeDefinition($service); + } + } + } + } + + private function registerWebhookConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(WebhookController::class)) { + throw new LogicException('Webhook support cannot be enabled as the component is not installed. Try running "composer require symfony/webhook".'); + } + + $loader->load('webhook.php'); + + $parsers = []; + foreach ($config['routing'] as $type => $cfg) { + $parsers[$type] = [ + 'parser' => new Reference($cfg['service']), + 'secret' => $cfg['secret'], + ]; + } + + $controller = $container->getDefinition('webhook.controller'); + $controller->replaceArgument(0, $parsers); + $controller->replaceArgument(1, new Reference($config['message_bus'])); + } + + private function registerRemoteEventConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(RemoteEvent::class)) { + throw new LogicException('RemoteEvent support cannot be enabled as the component is not installed. Try running "composer require symfony/remote-event".'); + } + + $loader->load('remote_event.php'); + } + + private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('rate_limiter.php'); + + foreach ($config['limiters'] as $name => $limiterConfig) { + // default configuration (when used by other DI extensions) + $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; + + $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')) + ->addTag('rate_limiter', ['name' => $name]); + + if (null !== $limiterConfig['lock_factory']) { + if (!interface_exists(LockInterface::class)) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name)); + } + + if (!$this->isInitializedConfigEnabled('lock')) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name)); + } + + $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); + } + unset($limiterConfig['lock_factory']); + + if (null === $storageId = $limiterConfig['storage_service'] ?? null) { + $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); + } + + $limiter->replaceArgument(1, new Reference($storageId)); + unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']); + + $limiterConfig['id'] = $name; + $limiter->replaceArgument(0, $limiterConfig); + + $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); + } + } + + private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('uid.php'); + + $container->getDefinition('uuid.factory') + ->setArguments([ + $config['default_uuid_version'], + $config['time_based_uuid_version'], + $config['name_based_uuid_version'], + UuidV4::class, + $config['time_based_uuid_node'] ?? null, + $config['name_based_uuid_namespace'] ?? null, + ]) + ; + + if (isset($config['name_based_uuid_namespace'])) { + $container->getDefinition('name_based_uuid.factory') + ->setArguments([$config['name_based_uuid_namespace']]); + } + } + + private function registerHtmlSanitizerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + $loader->load('html_sanitizer.php'); + + foreach ($config['sanitizers'] as $sanitizerName => $sanitizerConfig) { + $configId = 'html_sanitizer.config.'.$sanitizerName; + $def = $container->register($configId, HtmlSanitizerConfig::class); + + // Base + if ($sanitizerConfig['allow_safe_elements']) { + $def->addMethodCall('allowSafeElements', [], true); + } + + if ($sanitizerConfig['allow_static_elements']) { + $def->addMethodCall('allowStaticElements', [], true); + } + + // Configures elements + foreach ($sanitizerConfig['allow_elements'] as $element => $attributes) { + $def->addMethodCall('allowElement', [$element, $attributes], true); + } + + foreach ($sanitizerConfig['block_elements'] as $element) { + $def->addMethodCall('blockElement', [$element], true); + } + + foreach ($sanitizerConfig['drop_elements'] as $element) { + $def->addMethodCall('dropElement', [$element], true); + } + + // Configures attributes + foreach ($sanitizerConfig['allow_attributes'] as $attribute => $elements) { + $def->addMethodCall('allowAttribute', [$attribute, $elements], true); + } + + foreach ($sanitizerConfig['drop_attributes'] as $attribute => $elements) { + $def->addMethodCall('dropAttribute', [$attribute, $elements], true); + } + + // Force attributes + foreach ($sanitizerConfig['force_attributes'] as $element => $attributes) { + foreach ($attributes as $attrName => $attrValue) { + $def->addMethodCall('forceAttribute', [$element, $attrName, $attrValue], true); + } + } + + // Settings + $def->addMethodCall('forceHttpsUrls', [$sanitizerConfig['force_https_urls']], true); + if ($sanitizerConfig['allowed_link_schemes']) { + $def->addMethodCall('allowLinkSchemes', [$sanitizerConfig['allowed_link_schemes']], true); + } + $def->addMethodCall('allowLinkHosts', [$sanitizerConfig['allowed_link_hosts']], true); + $def->addMethodCall('allowRelativeLinks', [$sanitizerConfig['allow_relative_links']], true); + if ($sanitizerConfig['allowed_media_schemes']) { + $def->addMethodCall('allowMediaSchemes', [$sanitizerConfig['allowed_media_schemes']], true); + } + $def->addMethodCall('allowMediaHosts', [$sanitizerConfig['allowed_media_hosts']], true); + $def->addMethodCall('allowRelativeMedias', [$sanitizerConfig['allow_relative_medias']], true); + + // Custom attribute sanitizers + foreach ($sanitizerConfig['with_attribute_sanitizers'] as $serviceName) { + $def->addMethodCall('withAttributeSanitizer', [new Reference($serviceName)], true); + } + + foreach ($sanitizerConfig['without_attribute_sanitizers'] as $serviceName) { + $def->addMethodCall('withoutAttributeSanitizer', [new Reference($serviceName)], true); + } + + if ($sanitizerConfig['max_input_length']) { + $def->addMethodCall('withMaxInputLength', [$sanitizerConfig['max_input_length']], true); + } + + // Create the sanitizer and link its config + $sanitizerId = 'html_sanitizer.sanitizer.'.$sanitizerName; + $container->register($sanitizerId, HtmlSanitizer::class) + ->addTag('html_sanitizer', ['sanitizer' => $sanitizerName]) + ->addArgument(new Reference($configId)); + + if ('default' !== $sanitizerName) { + $container->registerAliasForArgument($sanitizerId, HtmlSanitizerInterface::class, $sanitizerName); + } + } + } + + private function resolveTrustedHeaders(array $headers): int + { + $trustedHeaders = 0; + + foreach ($headers as $h) { + $trustedHeaders |= match ($h) { + 'forwarded' => Request::HEADER_FORWARDED, + 'x-forwarded-for' => Request::HEADER_X_FORWARDED_FOR, + 'x-forwarded-host' => Request::HEADER_X_FORWARDED_HOST, + 'x-forwarded-proto' => Request::HEADER_X_FORWARDED_PROTO, + 'x-forwarded-port' => Request::HEADER_X_FORWARDED_PORT, + 'x-forwarded-prefix' => Request::HEADER_X_FORWARDED_PREFIX, + default => 0, + }; + } + + return $trustedHeaders; + } + + public function getXsdValidationBasePath(): string|false + { + return \dirname(__DIR__).'/Resources/config/schema'; + } + + public function getNamespace(): string + { + return 'http://symfony.com/schema/dic/symfony'; + } + + protected function isConfigEnabled(ContainerBuilder $container, array $config): bool + { + throw new \LogicException('To prevent using outdated configuration, you must use the "readConfigEnabled" method instead.'); + } + + private function isInitializedConfigEnabled(string $path): bool + { + if (isset($this->configsEnabled[$path])) { + return $this->configsEnabled[$path]; + } + + throw new LogicException(sprintf('Can not read config enabled at "%s" because it has not been initialized.', $path)); + } + + private function readConfigEnabled(string $path, ContainerBuilder $container, array $config): bool + { + return $this->configsEnabled[$path] ??= parent::isConfigEnabled($container, $config); + } + + private function writeConfigEnabled(string $path, bool $value, array &$config): void + { + if (isset($this->configsEnabled[$path])) { + throw new LogicException('Can not change config enabled because it has already been read.'); + } + + $this->configsEnabled[$path] = $value; + $config['enabled'] = $value; + } + + private function getPublicDirectory(ContainerBuilder $container): string + { + $projectDir = $container->getParameter('kernel.project_dir'); + $defaultPublicDir = $projectDir.'/public'; + + $composerFilePath = $projectDir.'/composer.json'; + + if (!file_exists($composerFilePath)) { + return $defaultPublicDir; + } + + $container->addResource(new FileResource($composerFilePath)); + $composerConfig = json_decode((new Filesystem())->readFile($composerFilePath), true, flags: \JSON_THROW_ON_ERROR); + + return isset($composerConfig['extra']['public-dir']) ? $projectDir.'/'.$composerConfig['extra']['public-dir'] : $defaultPublicDir; + } +} diff --git a/vendor/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php b/vendor/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php new file mode 100644 index 0000000..158054f --- /dev/null +++ b/vendor/symfony/framework-bundle/DependencyInjection/VirtualRequestStackPass.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class VirtualRequestStackPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if ($container->has('.virtual_request_stack')) { + return; + } + + if ($container->hasDefinition('debug.event_dispatcher')) { + $container->getDefinition('debug.event_dispatcher')->replaceArgument(3, new Reference('request_stack', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); + } + + if ($container->hasDefinition('debug.log_processor')) { + $container->getDefinition('debug.log_processor')->replaceArgument(0, new Reference('request_stack')); + } + } +} diff --git a/vendor/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php b/vendor/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php new file mode 100644 index 0000000..7bf23f0 --- /dev/null +++ b/vendor/symfony/framework-bundle/EventListener/ConsoleProfilerListener.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Profiler\Profile; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @internal + * + * @author Jules Pietri + */ +final class ConsoleProfilerListener implements EventSubscriberInterface +{ + private ?\Throwable $error = null; + /** @var \SplObjectStorage */ + private \SplObjectStorage $profiles; + /** @var \SplObjectStorage */ + private \SplObjectStorage $parents; + + public function __construct( + private readonly Profiler $profiler, + private readonly RequestStack $requestStack, + private readonly Stopwatch $stopwatch, + private readonly bool $cliMode, + private readonly ?UrlGeneratorInterface $urlGenerator = null, + ) { + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::COMMAND => ['initialize', 4096], + ConsoleEvents::ERROR => ['catch', -2048], + ConsoleEvents::TERMINATE => ['profile', -4096], + ]; + } + + public function initialize(ConsoleCommandEvent $event): void + { + if (!$this->cliMode) { + return; + } + + $input = $event->getInput(); + if (!$input->hasOption('profile') || !$input->getOption('profile')) { + $this->profiler->disable(); + + return; + } + + $request = $this->requestStack->getCurrentRequest(); + + if (!$request instanceof CliRequest || $request->command !== $event->getCommand()) { + return; + } + + $request->attributes->set('_stopwatch_token', substr(hash('xxh128', uniqid(mt_rand(), true)), 0, 6)); + $this->stopwatch->openSection(); + } + + public function catch(ConsoleErrorEvent $event): void + { + if (!$this->cliMode) { + return; + } + + $this->error = $event->getError(); + } + + public function profile(ConsoleTerminateEvent $event): void + { + if (!$this->cliMode || !$this->profiler->isEnabled()) { + return; + } + + $request = $this->requestStack->getCurrentRequest(); + + if (!$request instanceof CliRequest || $request->command !== $event->getCommand()) { + return; + } + + if (null !== $sectionId = $request->attributes->get('_stopwatch_token')) { + // we must close the section before saving the profile to allow late collect + try { + $this->stopwatch->stopSection($sectionId); + } catch (\LogicException) { + // noop + } + } + + $request->command->exitCode = $event->getExitCode(); + $request->command->interruptedBySignal = $event->getInterruptingSignal(); + + $profile = $this->profiler->collect($request, $request->getResponse(), $this->error); + $this->error = null; + $this->profiles[$request] = $profile; + + if ($this->parents[$request] = $this->requestStack->getParentRequest()) { + // do not save on sub commands + return; + } + + // attach children to parents + foreach ($this->profiles as $request) { + if (null !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + $output = $event->getOutput(); + $output = $output instanceof ConsoleOutputInterface && $output->isVerbose() ? $output->getErrorOutput() : null; + + // save profiles + foreach ($this->profiles as $r) { + $p = $this->profiles[$r]; + $this->profiler->saveProfile($p); + + if ($this->urlGenerator && $output) { + $token = $p->getToken(); + $output->writeln(sprintf( + 'See profile %s', + $this->urlGenerator->generate('_profiler', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL), + $token + )); + } + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } +} diff --git a/vendor/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php b/vendor/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php new file mode 100644 index 0000000..d7bdc8e --- /dev/null +++ b/vendor/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Suggests a package, that should be installed (via composer), + * if the package is missing, and the input command namespace can be mapped to a Symfony bundle. + * + * @author PrzemysÅ‚aw Bogusz + * + * @internal + */ +final class SuggestMissingPackageSubscriber implements EventSubscriberInterface +{ + private const PACKAGES = [ + 'doctrine' => [ + 'fixtures' => ['DoctrineFixturesBundle', 'doctrine/doctrine-fixtures-bundle --dev'], + 'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'], + '_default' => ['Doctrine ORM', 'symfony/orm-pack'], + ], + 'make' => [ + '_default' => ['MakerBundle', 'symfony/maker-bundle --dev'], + ], + 'server' => [ + '_default' => ['Debug Bundle', 'symfony/debug-bundle --dev'], + ], + ]; + + public function onConsoleError(ConsoleErrorEvent $event): void + { + if (!$event->getError() instanceof CommandNotFoundException) { + return; + } + + [$namespace, $command] = explode(':', $event->getInput()->getFirstArgument()) + [1 => '']; + + if (!isset(self::PACKAGES[$namespace])) { + return; + } + + if (isset(self::PACKAGES[$namespace][$command])) { + $suggestion = self::PACKAGES[$namespace][$command]; + $exact = true; + } else { + $suggestion = self::PACKAGES[$namespace]['_default']; + $exact = false; + } + + $error = $event->getError(); + + if ($error->getAlternatives() && !$exact) { + return; + } + + $message = sprintf("%s\n\nYou may be looking for a command provided by the \"%s\" which is currently not installed. Try running \"composer require %s\".", $error->getMessage(), $suggestion[0], $suggestion[1]); + $event->setError(new CommandNotFoundException($message)); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', 0], + ]; + } +} diff --git a/vendor/symfony/framework-bundle/FrameworkBundle.php b/vendor/symfony/framework-bundle/FrameworkBundle.php new file mode 100644 index 0000000..26784be --- /dev/null +++ b/vendor/symfony/framework-bundle/FrameworkBundle.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle; + +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AssetsContextPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ErrorLoggerCompilerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RemoveUnusedSessionMarshallingHandlerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\VirtualRequestStackPass; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\Cache\Adapter\PhpFilesAdapter; +use Symfony\Component\Cache\DependencyInjection\CacheCollectorPass; +use Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass; +use Symfony\Component\Cache\DependencyInjection\CachePoolPass; +use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass; +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Dotenv\Dotenv; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; +use Symfony\Component\Form\DependencyInjection\FormPass; +use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; +use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; +use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterLocaleAwareServicesPass; +use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; +use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Messenger\DependencyInjection\MessengerPass; +use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass; +use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; +use Symfony\Component\Routing\DependencyInjection\AddExpressionLanguageProvidersPass; +use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; +use Symfony\Component\Scheduler\DependencyInjection\AddScheduleMessengerPass; +use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass; +use Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass; +use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass; +use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass; +use Symfony\Component\Translation\DependencyInjection\TranslatorPass; +use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass; +use Symfony\Component\Validator\DependencyInjection\AddAutoMappingConfigurationPass; +use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; +use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\Workflow\DependencyInjection\WorkflowDebugPass; +use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass; + +// Help opcache.preload discover always-needed symbols +class_exists(ApcuAdapter::class); +class_exists(ArrayAdapter::class); +class_exists(ChainAdapter::class); +class_exists(PhpArrayAdapter::class); +class_exists(PhpFilesAdapter::class); +class_exists(Dotenv::class); +class_exists(ErrorHandler::class); +class_exists(Hydrator::class); +class_exists(Registry::class); + +/** + * Bundle. + * + * @author Fabien Potencier + */ +class FrameworkBundle extends Bundle +{ + public function boot(): void + { + $_ENV['DOCTRINE_DEPRECATIONS'] = $_SERVER['DOCTRINE_DEPRECATIONS'] ??= 'trigger'; + + $handler = ErrorHandler::register(null, false); + + $this->container->get('debug.error_handler_configurator')->configure($handler); + + if ($this->container->getParameter('kernel.http_method_override')) { + Request::enableHttpMethodParameterOverride(); + } + + if ($this->container->hasParameter('kernel.trust_x_sendfile_type_header') && $this->container->getParameter('kernel.trust_x_sendfile_type_header')) { + BinaryFileResponse::trustXSendfileTypeHeader(); + } + } + + public function build(ContainerBuilder $container): void + { + parent::build($container); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->setHotPathEvents([ + KernelEvents::REQUEST, + KernelEvents::CONTROLLER, + KernelEvents::CONTROLLER_ARGUMENTS, + KernelEvents::RESPONSE, + KernelEvents::FINISH_REQUEST, + ]); + if (class_exists(ConsoleEvents::class)) { + $registerListenersPass->setNoPreloadEvents([ + ConsoleEvents::COMMAND, + ConsoleEvents::TERMINATE, + ConsoleEvents::ERROR, + ]); + } + + $container->addCompilerPass(new AssetsContextPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); + $container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $container->addCompilerPass(new RegisterControllerArgumentLocatorsPass()); + $container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new RoutingResolverPass()); + $this->addCompilerPassIfExists($container, DataCollectorTranslatorPass::class); + $container->addCompilerPass(new ProfilerPass()); + // must be registered before removing private services as some might be listeners/subscribers + // but as late as possible to get resolved parameters + $container->addCompilerPass($registerListenersPass, PassConfig::TYPE_BEFORE_REMOVING); + $this->addCompilerPassIfExists($container, AddConstraintValidatorsPass::class); + $this->addCompilerPassIfExists($container, AddValidatorInitializersPass::class); + $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class, PassConfig::TYPE_BEFORE_REMOVING); + // must be registered as late as possible to get access to all Twig paths registered in + // twig.template_iterator definition + $this->addCompilerPassIfExists($container, TranslatorPass::class, PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $this->addCompilerPassIfExists($container, TranslatorPathsPass::class, PassConfig::TYPE_AFTER_REMOVING); + $this->addCompilerPassIfExists($container, LoggingTranslatorPass::class); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + $this->addCompilerPassIfExists($container, TranslationExtractorPass::class); + $this->addCompilerPassIfExists($container, TranslationDumperPass::class); + $container->addCompilerPass(new FragmentRendererPass()); + $this->addCompilerPassIfExists($container, SerializerPass::class); + $this->addCompilerPassIfExists($container, PropertyInfoPass::class); + $container->addCompilerPass(new ControllerArgumentValueResolverPass()); + $container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32); + $container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); + $this->addCompilerPassIfExists($container, FormPass::class); + $this->addCompilerPassIfExists($container, WorkflowGuardListenerPass::class); + $container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $container->addCompilerPass(new RegisterLocaleAwareServicesPass()); + $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); + $container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING); + $this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class); + $this->addCompilerPassIfExists($container, AddScheduleMessengerPass::class); + $this->addCompilerPassIfExists($container, MessengerPass::class); + $this->addCompilerPassIfExists($container, HttpClientPass::class); + $this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class); + $container->addCompilerPass(new RegisterReverseContainerPass(true)); + $container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass()); + // must be registered after MonologBundle's LoggerChannelPass + $container->addCompilerPass(new ErrorLoggerCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $container->addCompilerPass(new VirtualRequestStackPass()); + + if ($container->getParameter('kernel.debug')) { + $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2); + $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_BEFORE_REMOVING, -255); + $container->addCompilerPass(new CacheCollectorPass(), PassConfig::TYPE_BEFORE_REMOVING); + $this->addCompilerPassIfExists($container, WorkflowDebugPass::class); + } + } + + private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): void + { + $container->addResource(new ClassExistenceResource($class)); + + if (class_exists($class)) { + $container->addCompilerPass(new $class(), $type, $priority); + } + } +} diff --git a/vendor/symfony/framework-bundle/HttpCache/HttpCache.php b/vendor/symfony/framework-bundle/HttpCache/HttpCache.php new file mode 100644 index 0000000..f163708 --- /dev/null +++ b/vendor/symfony/framework-bundle/HttpCache/HttpCache.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * Manages HTTP cache objects in a Container. + * + * @author Fabien Potencier + */ +class HttpCache extends BaseHttpCache +{ + protected ?string $cacheDir = null; + + private ?StoreInterface $store = null; + private array $options; + + /** + * @param $cache The cache directory (default used if null) or the storage instance + */ + public function __construct( + protected KernelInterface $kernel, + string|StoreInterface|null $cache = null, + private ?SurrogateInterface $surrogate = null, + ?array $options = null, + ) { + $this->options = $options ?? []; + + if ($cache instanceof StoreInterface) { + $this->store = $cache; + } else { + $this->cacheDir = $cache; + } + + if (null === $options && $kernel->isDebug()) { + $this->options = ['debug' => true]; + } + + if ($this->options['debug'] ?? false) { + $this->options += ['stale_if_error' => 0]; + } + + parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($this->options, $this->getOptions())); + } + + protected function forward(Request $request, bool $catch = false, ?Response $entry = null): Response + { + $this->getKernel()->boot(); + $this->getKernel()->getContainer()->set('cache', $this); + + return parent::forward($request, $catch, $entry); + } + + /** + * Returns an array of options to customize the Cache configuration. + */ + protected function getOptions(): array + { + return []; + } + + protected function createSurrogate(): SurrogateInterface + { + return $this->surrogate ?? new Esi(); + } + + protected function createStore(): StoreInterface + { + return $this->store ?? new Store($this->cacheDir ?: $this->kernel->getCacheDir().'/http_cache'); + } +} diff --git a/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php b/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php new file mode 100644 index 0000000..b3f49c0 --- /dev/null +++ b/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Kernel; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\AbstractConfigurator; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader as ContainerPhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; +use Symfony\Component\Routing\Loader\PhpFileLoader as RoutingPhpFileLoader; +use Symfony\Component\Routing\RouteCollection; + +/** + * A Kernel that provides configuration hooks. + * + * @author Ryan Weaver + * @author Fabien Potencier + */ +trait MicroKernelTrait +{ + /** + * Configures the container. + * + * You can register extensions: + * + * $container->extension('framework', [ + * 'secret' => '%secret%' + * ]); + * + * Or services: + * + * $container->services()->set('halloween', 'FooBundle\HalloweenProvider'); + * + * Or parameters: + * + * $container->parameters()->set('halloween', 'lot of fun'); + */ + private function configureContainer(ContainerConfigurator $container, LoaderInterface $loader, ContainerBuilder $builder): void + { + $configDir = $this->getConfigDir(); + + $container->import($configDir.'/{packages}/*.{php,yaml}'); + $container->import($configDir.'/{packages}/'.$this->environment.'/*.{php,yaml}'); + + if (is_file($configDir.'/services.yaml')) { + $container->import($configDir.'/services.yaml'); + $container->import($configDir.'/{services}_'.$this->environment.'.yaml'); + } else { + $container->import($configDir.'/{services}.php'); + $container->import($configDir.'/{services}_'.$this->environment.'.php'); + } + } + + /** + * Adds or imports routes into your application. + * + * $routes->import($this->getConfigDir().'/*.{yaml,php}'); + * $routes + * ->add('admin_dashboard', '/admin') + * ->controller('App\Controller\AdminController::dashboard') + * ; + */ + private function configureRoutes(RoutingConfigurator $routes): void + { + $configDir = $this->getConfigDir(); + + $routes->import($configDir.'/{routes}/'.$this->environment.'/*.{php,yaml}'); + $routes->import($configDir.'/{routes}/*.{php,yaml}'); + + if (is_file($configDir.'/routes.yaml')) { + $routes->import($configDir.'/routes.yaml'); + } else { + $routes->import($configDir.'/{routes}.php'); + } + + if (false !== ($fileName = (new \ReflectionObject($this))->getFileName())) { + $routes->import($fileName, 'attribute'); + } + } + + /** + * Gets the path to the configuration directory. + */ + private function getConfigDir(): string + { + return $this->getProjectDir().'/config'; + } + + /** + * Gets the path to the bundles configuration file. + */ + private function getBundlesPath(): string + { + return $this->getConfigDir().'/bundles.php'; + } + + public function getCacheDir(): string + { + if (isset($_SERVER['APP_CACHE_DIR'])) { + return $_SERVER['APP_CACHE_DIR'].'/'.$this->environment; + } + + return parent::getCacheDir(); + } + + public function getBuildDir(): string + { + if (isset($_SERVER['APP_BUILD_DIR'])) { + return $_SERVER['APP_BUILD_DIR'].'/'.$this->environment; + } + + return parent::getBuildDir(); + } + + public function getLogDir(): string + { + return $_SERVER['APP_LOG_DIR'] ?? parent::getLogDir(); + } + + public function registerBundles(): iterable + { + $contents = require $this->getBundlesPath(); + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + $loader->load(function (ContainerBuilder $container) use ($loader) { + $container->loadFromExtension('framework', [ + 'router' => [ + 'resource' => 'kernel::loadRoutes', + 'type' => 'service', + ], + ]); + + $kernelClass = str_contains(static::class, "@anonymous\0") ? parent::class : static::class; + + if (!$container->hasDefinition('kernel')) { + $container->register('kernel', $kernelClass) + ->addTag('controller.service_arguments') + ->setAutoconfigured(true) + ->setSynthetic(true) + ->setPublic(true) + ; + } + + $kernelDefinition = $container->getDefinition('kernel'); + $kernelDefinition->addTag('routing.route_loader'); + + $container->addObjectResource($this); + $container->fileExists($this->getBundlesPath()); + + $configureContainer = new \ReflectionMethod($this, 'configureContainer'); + $configuratorClass = $configureContainer->getNumberOfParameters() > 0 && ($type = $configureContainer->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null; + + if ($configuratorClass && !is_a(ContainerConfigurator::class, $configuratorClass, true)) { + $configureContainer->getClosure($this)($container, $loader); + + return; + } + + $file = (new \ReflectionObject($this))->getFileName(); + /* @var ContainerPhpFileLoader $kernelLoader */ + $kernelLoader = $loader->getResolver()->resolve($file); + $kernelLoader->setCurrentDir(\dirname($file)); + $instanceof = &\Closure::bind(fn &() => $this->instanceof, $kernelLoader, $kernelLoader)(); + + $valuePreProcessor = AbstractConfigurator::$valuePreProcessor; + AbstractConfigurator::$valuePreProcessor = fn ($value) => $this === $value ? new Reference('kernel') : $value; + + try { + $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); + } finally { + $instanceof = []; + $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); + AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; + } + + $container->setAlias($kernelClass, 'kernel')->setPublic(true); + }); + } + + /** + * @internal + */ + public function loadRoutes(LoaderInterface $loader): RouteCollection + { + $file = (new \ReflectionObject($this))->getFileName(); + /* @var RoutingPhpFileLoader $kernelLoader */ + $kernelLoader = $loader->getResolver()->resolve($file, 'php'); + $kernelLoader->setCurrentDir(\dirname($file)); + $collection = new RouteCollection(); + + $configureRoutes = new \ReflectionMethod($this, 'configureRoutes'); + $configureRoutes->getClosure($this)(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment())); + + foreach ($collection as $route) { + $controller = $route->getDefault('_controller'); + + if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) { + $route->setDefault('_controller', ['kernel', $controller[1]]); + } elseif ($controller instanceof \Closure && $this === ($r = new \ReflectionFunction($controller))->getClosureThis() && !$r->isAnonymous()) { + $route->setDefault('_controller', ['kernel', $r->name]); + } + } + + return $collection; + } +} diff --git a/vendor/symfony/framework-bundle/KernelBrowser.php b/vendor/symfony/framework-bundle/KernelBrowser.php new file mode 100644 index 0000000..e06b9a0 --- /dev/null +++ b/vendor/symfony/framework-bundle/KernelBrowser.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle; + +use Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken; +use Symfony\Component\BrowserKit\Cookie; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelBrowser; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\Profiler\Profile as HttpProfile; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Simulates a browser and makes requests to a Kernel object. + * + * @author Fabien Potencier + */ +class KernelBrowser extends HttpKernelBrowser +{ + private bool $hasPerformedRequest = false; + private bool $profiler = false; + private bool $reboot = true; + + public function __construct(KernelInterface $kernel, array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) + { + parent::__construct($kernel, $server, $history, $cookieJar); + } + + public function getContainer(): ContainerInterface + { + $container = $this->kernel->getContainer(); + + return $container->has('test.service_container') ? $container->get('test.service_container') : $container; + } + + public function getKernel(): KernelInterface + { + return $this->kernel; + } + + /** + * Gets the profile associated with the current Response. + */ + public function getProfile(): HttpProfile|false|null + { + if (null === $this->response || !$this->getContainer()->has('profiler')) { + return false; + } + + return $this->getContainer()->get('profiler')->loadProfileFromResponse($this->response); + } + + /** + * Enables the profiler for the very next request. + * + * If the profiler is not enabled, the call to this method does nothing. + */ + public function enableProfiler(): void + { + if ($this->getContainer()->has('profiler')) { + $this->profiler = true; + } + } + + /** + * Disables kernel reboot between requests. + * + * By default, the Client reboots the Kernel for each request. This method + * allows to keep the same kernel across requests. + */ + public function disableReboot(): void + { + $this->reboot = false; + } + + /** + * Enables kernel reboot between requests. + */ + public function enableReboot(): void + { + $this->reboot = true; + } + + /** + * @param UserInterface $user + * @param array $tokenAttributes + * + * @return $this + */ + public function loginUser(object $user, string $firewallContext = 'main', array $tokenAttributes = []): static + { + if (!interface_exists(UserInterface::class)) { + throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed. Try running "composer require symfony/security-core".', __METHOD__)); + } + + if (!$user instanceof UserInterface) { + throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, get_debug_type($user))); + } + + $token = new TestBrowserToken($user->getRoles(), $user, $firewallContext); + $token->setAttributes($tokenAttributes); + + $container = $this->getContainer(); + $container->get('security.untracked_token_storage')->setToken($token); + + if (!$container->has('session.factory')) { + return $this; + } + + $session = $container->get('session.factory')->createSession(); + $session->set('_security_'.$firewallContext, serialize($token)); + $session->save(); + + $domains = array_unique(array_map(fn (Cookie $cookie) => $cookie->getName() === $session->getName() ? $cookie->getDomain() : '', $this->getCookieJar()->all())) ?: ['']; + foreach ($domains as $domain) { + $cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain); + $this->getCookieJar()->set($cookie); + } + + return $this; + } + + /** + * @param Request $request + */ + protected function doRequest(object $request): Response + { + // avoid shutting down the Kernel if no request has been performed yet + // WebTestCase::createClient() boots the Kernel but do not handle a request + if ($this->hasPerformedRequest && $this->reboot) { + $this->kernel->boot(); + $this->kernel->shutdown(); + } else { + $this->hasPerformedRequest = true; + } + + if ($this->profiler) { + $this->profiler = false; + + $this->kernel->boot(); + $this->getContainer()->get('profiler')->enable(); + } + + return parent::doRequest($request); + } + + /** + * @param Request $request + */ + protected function doRequestInProcess(object $request): Response + { + $response = parent::doRequestInProcess($request); + + $this->profiler = false; + + return $response; + } + + /** + * Returns the script to execute when the request must be insulated. + * + * It assumes that the autoloader is named 'autoload.php' and that it is + * stored in the same directory as the kernel (this is the case for the + * Symfony Standard Edition). If this is not your case, create your own + * client and override this method. + * + * @param Request $request + */ + protected function getScript(object $request): string + { + $kernel = var_export(serialize($this->kernel), true); + $request = var_export(serialize($request), true); + $errorReporting = error_reporting(); + + $requires = ''; + foreach (get_declared_classes() as $class) { + if (str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $file = \dirname($r->getFileName(), 2).'/autoload.php'; + if (is_file($file)) { + $requires .= 'require_once '.var_export($file, true).";\n"; + } + } + } + + if (!$requires) { + throw new \RuntimeException('Composer autoloader not found.'); + } + + $requires .= 'require_once '.var_export((new \ReflectionObject($this->kernel))->getFileName(), true).";\n"; + + $profilerCode = ''; + if ($this->profiler) { + $profilerCode = <<<'EOF' +$container = $kernel->getContainer(); +$container = $container->has('test.service_container') ? $container->get('test.service_container') : $container; +$container->get('profiler')->enable(); +EOF; + } + + $code = <<boot(); +$profilerCode + +\$request = unserialize($request); +EOF; + + return $code.$this->getHandleScript(); + } +} diff --git a/vendor/symfony/framework-bundle/LICENSE b/vendor/symfony/framework-bundle/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/framework-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/framework-bundle/README.md b/vendor/symfony/framework-bundle/README.md new file mode 100644 index 0000000..220d72a --- /dev/null +++ b/vendor/symfony/framework-bundle/README.md @@ -0,0 +1,20 @@ +FrameworkBundle +=============== + +FrameworkBundle provides a tight integration between Symfony components and the +Symfony full-stack framework. + +Sponsor +------- + +Help Symfony by [sponsoring][1] its development! + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/sponsor diff --git a/vendor/symfony/framework-bundle/Resources/bin/check-unused-known-tags.php b/vendor/symfony/framework-bundle/Resources/bin/check-unused-known-tags.php new file mode 100644 index 0000000..55658f5 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/bin/check-unused-known-tags.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== \PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} + +require dirname(__DIR__, 6).'/vendor/autoload.php'; + +use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\UnusedTagsPassUtils; + +$target = dirname(__DIR__, 2).'/DependencyInjection/Compiler/UnusedTagsPass.php'; +$contents = file_get_contents($target); +$contents = preg_replace('{private const KNOWN_TAGS = \[(.+?)\];}sm', "private const KNOWN_TAGS = [\n '".implode("',\n '", UnusedTagsPassUtils::getDefinedTags())."',\n ];", $contents); +file_put_contents($target, $contents); diff --git a/vendor/symfony/framework-bundle/Resources/config/asset_mapper.php b/vendor/symfony/framework-bundle/Resources/config/asset_mapper.php new file mode 100644 index 0000000..404e7af --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/asset_mapper.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperCompiler; +use Symfony\Component\AssetMapper\AssetMapperDevServerSubscriber; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\AssetMapperRepository; +use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand; +use Symfony\Component\AssetMapper\Command\DebugAssetMapperCommand; +use Symfony\Component\AssetMapper\Command\ImportMapAuditCommand; +use Symfony\Component\AssetMapper\Command\ImportMapInstallCommand; +use Symfony\Component\AssetMapper\Command\ImportMapOutdatedCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand; +use Symfony\Component\AssetMapper\Command\ImportMapUpdateCommand; +use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader; +use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler; +use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler; +use Symfony\Component\AssetMapper\Compiler\SourceMappingUrlsCompiler; +use Symfony\Component\AssetMapper\Factory\CachedMappedAssetFactory; +use Symfony\Component\AssetMapper\Factory\MappedAssetFactory; +use Symfony\Component\AssetMapper\ImportMap\ImportMapAuditor; +use Symfony\Component\AssetMapper\ImportMap\ImportMapConfigReader; +use Symfony\Component\AssetMapper\ImportMap\ImportMapGenerator; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; +use Symfony\Component\AssetMapper\ImportMap\ImportMapUpdateChecker; +use Symfony\Component\AssetMapper\ImportMap\ImportMapVersionChecker; +use Symfony\Component\AssetMapper\ImportMap\RemotePackageDownloader; +use Symfony\Component\AssetMapper\ImportMap\RemotePackageStorage; +use Symfony\Component\AssetMapper\ImportMap\Resolver\JsDelivrEsmResolver; +use Symfony\Component\AssetMapper\MapperAwareAssetPackage; +use Symfony\Component\AssetMapper\Path\LocalPublicAssetsFilesystem; +use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolver; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('asset_mapper', AssetMapper::class) + ->args([ + service('asset_mapper.repository'), + service('asset_mapper.mapped_asset_factory'), + service('asset_mapper.compiled_asset_mapper_config_reader'), + ]) + ->alias(AssetMapperInterface::class, 'asset_mapper') + + ->alias('asset_mapper.http_client', 'http_client') + + ->set('asset_mapper.mapped_asset_factory', MappedAssetFactory::class) + ->args([ + service('asset_mapper.public_assets_path_resolver'), + service('asset_mapper_compiler'), + abstract_arg('vendor directory'), + ]) + + ->set('asset_mapper.cached_mapped_asset_factory', CachedMappedAssetFactory::class) + ->args([ + service('.inner'), + param('kernel.cache_dir').'/asset_mapper', + param('kernel.debug'), + ]) + ->decorate('asset_mapper.mapped_asset_factory') + + ->set('asset_mapper.repository', AssetMapperRepository::class) + ->args([ + abstract_arg('array of asset mapper paths'), + param('kernel.project_dir'), + abstract_arg('array of excluded path patterns'), + abstract_arg('exclude dot files'), + param('kernel.debug'), + ]) + + ->set('asset_mapper.public_assets_path_resolver', PublicAssetsPathResolver::class) + ->args([ + abstract_arg('asset public prefix'), + ]) + + ->set('asset_mapper.local_public_assets_filesystem', LocalPublicAssetsFilesystem::class) + ->args([ + abstract_arg('public directory'), + ]) + + ->set('asset_mapper.compiled_asset_mapper_config_reader', CompiledAssetMapperConfigReader::class) + ->args([ + abstract_arg('public assets directory'), + ]) + + ->set('asset_mapper.asset_package', MapperAwareAssetPackage::class) + ->decorate('assets._default_package') + ->args([ + service('.inner'), + service('asset_mapper'), + ]) + + ->set('asset_mapper.dev_server_subscriber', AssetMapperDevServerSubscriber::class) + ->args([ + service('asset_mapper'), + abstract_arg('asset public prefix'), + abstract_arg('extensions map'), + service('cache.asset_mapper'), + service('profiler')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + + ->set('asset_mapper.command.compile', AssetMapperCompileCommand::class) + ->args([ + service('asset_mapper.compiled_asset_mapper_config_reader'), + service('asset_mapper'), + service('asset_mapper.importmap.generator'), + service('asset_mapper.local_public_assets_filesystem'), + param('kernel.project_dir'), + param('kernel.debug'), + service('event_dispatcher')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('asset_mapper.command.debug', DebugAssetMapperCommand::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.repository'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('asset_mapper_compiler', AssetMapperCompiler::class) + ->args([ + tagged_iterator('asset_mapper.compiler'), + service_closure('asset_mapper'), + ]) + + ->set('asset_mapper.compiler.css_asset_url_compiler', CssAssetUrlCompiler::class) + ->args([ + abstract_arg('missing import mode'), + service('logger'), + ]) + ->tag('asset_mapper.compiler') + ->tag('monolog.logger', ['channel' => 'asset_mapper']) + + ->set('asset_mapper.compiler.source_mapping_urls_compiler', SourceMappingUrlsCompiler::class) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.compiler.javascript_import_path_compiler', JavaScriptImportPathCompiler::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + abstract_arg('missing import mode'), + service('logger'), + ]) + ->tag('asset_mapper.compiler') + ->tag('monolog.logger', ['channel' => 'asset_mapper']) + + ->set('asset_mapper.importmap.config_reader', ImportMapConfigReader::class) + ->args([ + abstract_arg('importmap.php path'), + service('asset_mapper.importmap.remote_package_storage'), + ]) + + ->set('asset_mapper.importmap.manager', ImportMapManager::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.importmap.remote_package_downloader'), + service('asset_mapper.importmap.resolver'), + ]) + ->alias(ImportMapManager::class, 'asset_mapper.importmap.manager') + + ->set('asset_mapper.importmap.generator', ImportMapGenerator::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.compiled_asset_mapper_config_reader'), + service('asset_mapper.importmap.config_reader'), + ]) + + ->set('asset_mapper.importmap.remote_package_storage', RemotePackageStorage::class) + ->args([ + abstract_arg('vendor directory'), + ]) + + ->set('asset_mapper.importmap.remote_package_downloader', RemotePackageDownloader::class) + ->args([ + service('asset_mapper.importmap.remote_package_storage'), + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.importmap.resolver'), + ]) + + ->set('asset_mapper.importmap.version_checker', ImportMapVersionChecker::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.importmap.remote_package_downloader'), + ]) + + ->set('asset_mapper.importmap.resolver', JsDelivrEsmResolver::class) + ->args([service('asset_mapper.http_client')]) + + ->set('asset_mapper.importmap.renderer', ImportMapRenderer::class) + ->args([ + service('asset_mapper.importmap.generator'), + service('assets.packages')->nullOnInvalid(), + param('kernel.charset'), + abstract_arg('polyfill URL'), + abstract_arg('script HTML attributes'), + service('request_stack'), + ]) + + ->set('asset_mapper.importmap.auditor', ImportMapAuditor::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.http_client'), + ]) + ->set('asset_mapper.importmap.update_checker', ImportMapUpdateChecker::class) + ->args([ + service('asset_mapper.importmap.config_reader'), + service('asset_mapper.http_client'), + ]) + + ->set('asset_mapper.importmap.command.require', ImportMapRequireCommand::class) + ->args([ + service('asset_mapper.importmap.manager'), + service('asset_mapper.importmap.version_checker'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.remove', ImportMapRemoveCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.update', ImportMapUpdateCommand::class) + ->args([ + service('asset_mapper.importmap.manager'), + service('asset_mapper.importmap.version_checker'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.install', ImportMapInstallCommand::class) + ->args([ + service('asset_mapper.importmap.remote_package_downloader'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.audit', ImportMapAuditCommand::class) + ->args([service('asset_mapper.importmap.auditor')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.outdated', ImportMapOutdatedCommand::class) + ->args([service('asset_mapper.importmap.update_checker')]) + ->tag('console.command') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/assets.php b/vendor/symfony/framework-bundle/Resources/config/assets.php new file mode 100644 index 0000000..2457121 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/assets.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Asset\Context\RequestStackContext; +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\Packages; +use Symfony\Component\Asset\PathPackage; +use Symfony\Component\Asset\UrlPackage; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('asset.request_context.base_path', null) + ->set('asset.request_context.secure', null) + ; + + $container->services() + ->set('assets.packages', Packages::class) + ->args([ + service('assets._default_package'), + tagged_iterator('assets.package', 'package'), + ]) + + ->alias(Packages::class, 'assets.packages') + + ->set('assets.empty_package', Package::class) + ->args([ + service('assets.empty_version_strategy'), + ]) + + ->alias('assets._default_package', 'assets.empty_package') + + ->set('assets.context', RequestStackContext::class) + ->args([ + service('request_stack'), + param('asset.request_context.base_path'), + param('asset.request_context.secure'), + ]) + + ->set('assets.path_package', PathPackage::class) + ->abstract() + ->args([ + abstract_arg('base path'), + abstract_arg('version strategy'), + service('assets.context'), + ]) + + ->set('assets.url_package', UrlPackage::class) + ->abstract() + ->args([ + abstract_arg('base URLs'), + abstract_arg('version strategy'), + service('assets.context'), + ]) + + ->set('assets.static_version_strategy', StaticVersionStrategy::class) + ->abstract() + ->args([ + abstract_arg('version'), + abstract_arg('format'), + ]) + + ->set('assets.empty_version_strategy', EmptyVersionStrategy::class) + + ->set('assets.json_manifest_version_strategy', JsonManifestVersionStrategy::class) + ->abstract() + ->args([ + abstract_arg('manifest path'), + service('http_client')->nullOnInvalid(), + false, + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/cache.php b/vendor/symfony/framework-bundle/Resources/config/cache.php new file mode 100644 index 0000000..eceb9bd --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/cache.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Adapter\MemcachedAdapter; +use Symfony\Component\Cache\Adapter\PdoAdapter; +use Symfony\Component\Cache\Adapter\ProxyAdapter; +use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Messenger\EarlyExpirationHandler; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('cache.app') + ->parent('cache.adapter.filesystem') + ->public() + ->tag('cache.pool', ['clearer' => 'cache.app_clearer']) + + ->set('cache.app.taggable', TagAwareAdapter::class) + ->args([service('cache.app')]) + ->tag('cache.taggable', ['pool' => 'cache.app']) + + ->set('cache.system') + ->parent('cache.adapter.system') + ->public() + ->tag('cache.pool') + + ->set('cache.validator') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.serializer') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.property_info') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.asset_mapper') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.messenger.restart_workers_signal') + ->parent('cache.app') + ->private() + ->tag('cache.pool') + + ->set('cache.scheduler') + ->parent('cache.app') + ->private() + ->tag('cache.pool') + + ->set('cache.adapter.system', AdapterInterface::class) + ->abstract() + ->factory([AbstractAdapter::class, 'createSystemCache']) + ->args([ + '', // namespace + 0, // default lifetime + abstract_arg('version'), + sprintf('%s/pools/system', param('kernel.cache_dir')), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('cache.pool', ['clearer' => 'cache.system_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.apcu', ApcuAdapter::class) + ->abstract() + ->args([ + '', // namespace + 0, // default lifetime + abstract_arg('version'), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.filesystem', FilesystemAdapter::class) + ->abstract() + ->args([ + '', // namespace + 0, // default lifetime + sprintf('%s/pools/app', param('kernel.cache_dir')), + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.psr6', ProxyAdapter::class) + ->abstract() + ->args([ + abstract_arg('PSR-6 provider service'), + '', // namespace + 0, // default lifetime + ]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_psr6_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + + ->set('cache.adapter.redis', RedisAdapter::class) + ->abstract() + ->args([ + abstract_arg('Redis connection service'), + '', // namespace + 0, // default lifetime + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_redis_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.redis_tag_aware', RedisTagAwareAdapter::class) + ->abstract() + ->args([ + abstract_arg('Redis connection service'), + '', // namespace + 0, // default lifetime + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_redis_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.memcached', MemcachedAdapter::class) + ->abstract() + ->args([ + abstract_arg('Memcached connection service'), + '', // namespace + 0, // default lifetime + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_memcached_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.doctrine_dbal', DoctrineDbalAdapter::class) + ->abstract() + ->args([ + abstract_arg('DBAL connection service'), + '', // namespace + 0, // default lifetime + [], // table options + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_doctrine_dbal_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.pdo', PdoAdapter::class) + ->abstract() + ->args([ + abstract_arg('PDO connection service'), + '', // namespace + 0, // default lifetime + [], // table options + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_pdo_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.array', ArrayAdapter::class) + ->abstract() + ->args([ + 0, // default lifetime + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.default_marshaller', DefaultMarshaller::class) + ->args([ + null, // use igbinary_serialize() when available + '%kernel.debug%', + ]) + + ->set('cache.early_expiration_handler', EarlyExpirationHandler::class) + ->args([ + service('reverse_container'), + ]) + ->tag('messenger.message_handler') + + ->set('cache.default_clearer', Psr6CacheClearer::class) + ->args([ + [], + ]) + + ->set('cache.system_clearer') + ->parent('cache.default_clearer') + ->public() + + ->set('cache.global_clearer') + ->parent('cache.default_clearer') + ->public() + + ->alias('cache.app_clearer', 'cache.default_clearer') + ->public() + + ->alias(CacheItemPoolInterface::class, 'cache.app') + + ->alias(CacheInterface::class, 'cache.app') + + ->alias(TagAwareCacheInterface::class, 'cache.app.taggable') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/cache_debug.php b/vendor/symfony/framework-bundle/Resources/config/cache_debug.php new file mode 100644 index 0000000..8f29d9f --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/cache_debug.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\CachePoolClearerCacheWarmer; +use Symfony\Component\Cache\DataCollector\CacheDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + // DataCollector (public to prevent inlining, made private in CacheCollectorPass) + ->set('data_collector.cache', CacheDataCollector::class) + ->public() + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/cache.html.twig', + 'id' => 'cache', + 'priority' => 275, + ]) + + // CacheWarmer used in dev to clear cache pool + ->set('cache_pool_clearer.cache_warmer', CachePoolClearerCacheWarmer::class) + ->args([ + service('cache.system_clearer'), + [ + 'cache.validator', + 'cache.serializer', + ], + ]) + ->tag('kernel.cache_warmer', ['priority' => 64]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/collectors.php b/vendor/symfony/framework-bundle/Resources/config/collectors.php new file mode 100644 index 0000000..aa6d4e3 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/collectors.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector; +use Symfony\Component\Console\DataCollector\CommandDataCollector; +use Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector; +use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; +use Symfony\Component\HttpKernel\DataCollector\EventDataCollector; +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpKernel\KernelEvents; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.config', ConfigDataCollector::class) + ->call('setKernel', [service('kernel')->ignoreOnInvalid()]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/config.html.twig', 'id' => 'config', 'priority' => -255]) + + ->set('data_collector.request', RequestDataCollector::class) + ->args([ + service('.virtual_request_stack')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('data_collector', ['template' => '@WebProfiler/Collector/request.html.twig', 'id' => 'request', 'priority' => 335]) + + ->set('data_collector.request.session_collector', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([[service('data_collector.request'), 'collectSessionUsage']]) + + ->set('data_collector.ajax', AjaxDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/ajax.html.twig', 'id' => 'ajax', 'priority' => 315]) + + ->set('data_collector.exception', ExceptionDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/exception.html.twig', 'id' => 'exception', 'priority' => 305]) + + ->set('data_collector.events', EventDataCollector::class) + ->args([ + tagged_iterator('event_dispatcher.dispatcher', 'name'), + service('.virtual_request_stack')->ignoreOnInvalid(), + ]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/events.html.twig', 'id' => 'events', 'priority' => 290]) + + ->set('data_collector.logger', LoggerDataCollector::class) + ->args([ + service('logger')->ignoreOnInvalid(), + sprintf('%s/%s', param('kernel.build_dir'), param('kernel.container_class')), + service('.virtual_request_stack')->ignoreOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'profiler']) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/logger.html.twig', 'id' => 'logger', 'priority' => 300]) + + ->set('data_collector.time', TimeDataCollector::class) + ->args([ + service('kernel')->ignoreOnInvalid(), + service('debug.stopwatch')->ignoreOnInvalid(), + ]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/time.html.twig', 'id' => 'time', 'priority' => 330]) + + ->set('data_collector.memory', MemoryDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/memory.html.twig', 'id' => 'memory', 'priority' => 325]) + + ->set('data_collector.router', RouterDataCollector::class) + ->tag('kernel.event_listener', ['event' => KernelEvents::CONTROLLER, 'method' => 'onKernelController']) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/router.html.twig', 'id' => 'router', 'priority' => 285]) + + ->set('.data_collector.command', CommandDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/command.html.twig', 'id' => 'command', 'priority' => 335]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/console.php b/vendor/symfony/framework-bundle/Resources/config/console.php new file mode 100644 index 0000000..b4f7dfc --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/console.php @@ -0,0 +1,393 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Command\AboutCommand; +use Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand; +use Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolInvalidateTagsCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand; +use Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand; +use Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand; +use Symfony\Bundle\FrameworkBundle\Command\ContainerDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand; +use Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand; +use Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand; +use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; +use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand; +use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber; +use Symfony\Component\Console\EventListener\ErrorListener; +use Symfony\Component\Console\Messenger\RunCommandMessageHandler; +use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand; +use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; +use Symfony\Component\Messenger\Command\DebugCommand as MessengerDebugCommand; +use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand; +use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand; +use Symfony\Component\Messenger\Command\FailedMessagesShowCommand; +use Symfony\Component\Messenger\Command\SetupTransportsCommand; +use Symfony\Component\Messenger\Command\StatsCommand; +use Symfony\Component\Messenger\Command\StopWorkersCommand; +use Symfony\Component\Scheduler\Command\DebugCommand as SchedulerDebugCommand; +use Symfony\Component\Serializer\Command\DebugCommand as SerializerDebugCommand; +use Symfony\Component\Translation\Command\TranslationPullCommand; +use Symfony\Component\Translation\Command\TranslationPushCommand; +use Symfony\Component\Translation\Command\XliffLintCommand; +use Symfony\Component\Validator\Command\DebugCommand as ValidatorDebugCommand; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('console.error_listener', ErrorListener::class) + ->args([ + service('logger')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'console']) + + ->set('console.suggest_missing_package_subscriber', SuggestMissingPackageSubscriber::class) + ->tag('kernel.event_subscriber') + + ->set('console.command.about', AboutCommand::class) + ->tag('console.command') + + ->set('console.command.assets_install', AssetsInstallCommand::class) + ->args([ + service('filesystem'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('console.command.cache_clear', CacheClearCommand::class) + ->args([ + service('cache_clearer'), + service('filesystem'), + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_clear', CachePoolClearCommand::class) + ->args([ + service('cache.global_clearer'), + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_prune', CachePoolPruneCommand::class) + ->args([ + [], + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_invalidate_tags', CachePoolInvalidateTagsCommand::class) + ->args([ + tagged_locator('cache.taggable', 'pool'), + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class) + ->args([ + service('cache.global_clearer'), + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_list', CachePoolListCommand::class) + ->args([ + null, + ]) + ->tag('console.command') + + ->set('console.command.cache_warmup', CacheWarmupCommand::class) + ->args([ + service('cache_warmer'), + ]) + ->tag('console.command') + + ->set('console.command.config_debug', ConfigDebugCommand::class) + ->tag('console.command') + + ->set('console.command.config_dump_reference', ConfigDumpReferenceCommand::class) + ->tag('console.command') + + ->set('console.command.container_debug', ContainerDebugCommand::class) + ->tag('console.command') + + ->set('console.command.container_lint', ContainerLintCommand::class) + ->tag('console.command') + + ->set('console.command.debug_autowiring', DebugAutowiringCommand::class) + ->args([ + null, + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.dotenv_debug', DotenvDebugCommand::class) + ->args([ + param('kernel.environment'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('console.command.event_dispatcher_debug', EventDispatcherDebugCommand::class) + ->args([ + tagged_locator('event_dispatcher.dispatcher', 'name'), + ]) + ->tag('console.command') + + ->set('console.command.messenger_consume_messages', ConsumeMessagesCommand::class) + ->args([ + abstract_arg('Routable message bus'), + service('messenger.receiver_locator'), + service('event_dispatcher'), + service('logger')->nullOnInvalid(), + [], // Receiver names + service('messenger.listener.reset_services')->nullOnInvalid(), + [], // Bus names + service('messenger.rate_limiter_locator')->nullOnInvalid(), + null, + ]) + ->tag('console.command') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('console.command.messenger_setup_transports', SetupTransportsCommand::class) + ->args([ + service('messenger.receiver_locator'), + [], // Receiver names + ]) + ->tag('console.command') + + ->set('console.command.messenger_debug', MessengerDebugCommand::class) + ->args([ + [], // Message to handlers mapping + ]) + ->tag('console.command') + + ->set('console.command.messenger_stop_workers', StopWorkersCommand::class) + ->args([ + service('cache.messenger.restart_workers_signal'), + ]) + ->tag('console.command') + + ->set('console.command.messenger_failed_messages_retry', FailedMessagesRetryCommand::class) + ->args([ + abstract_arg('Default failure receiver name'), + abstract_arg('Receivers'), + service('messenger.routable_message_bus'), + service('event_dispatcher'), + service('logger')->nullOnInvalid(), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), + null, + ]) + ->tag('console.command') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('console.command.messenger_failed_messages_show', FailedMessagesShowCommand::class) + ->args([ + abstract_arg('Default failure receiver name'), + abstract_arg('Receivers'), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.messenger_failed_messages_remove', FailedMessagesRemoveCommand::class) + ->args([ + abstract_arg('Default failure receiver name'), + abstract_arg('Receivers'), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.messenger_stats', StatsCommand::class) + ->args([ + service('messenger.receiver_locator'), + abstract_arg('Receivers names'), + ]) + ->tag('console.command') + + ->set('console.command.scheduler_debug', SchedulerDebugCommand::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), + ]) + ->tag('console.command') + + ->set('console.command.router_debug', RouterDebugCommand::class) + ->args([ + service('router'), + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.router_match', RouterMatchCommand::class) + ->args([ + service('router'), + tagged_iterator('routing.expression_language_provider'), + ]) + ->tag('console.command') + + ->set('console.command.serializer_debug', SerializerDebugCommand::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + ]) + ->tag('console.command') + + ->set('console.command.translation_debug', TranslationDebugCommand::class) + ->args([ + service('translator'), + service('translation.reader'), + service('translation.extractor'), + param('translator.default_path'), + null, // twig.default_path + [], // Translator paths + [], // Twig paths + param('kernel.enabled_locales'), + ]) + ->tag('console.command') + + ->set('console.command.translation_extract', TranslationUpdateCommand::class) + ->args([ + service('translation.writer'), + service('translation.reader'), + service('translation.extractor'), + param('kernel.default_locale'), + param('translator.default_path'), + null, // twig.default_path + [], // Translator paths + [], // Twig paths + param('kernel.enabled_locales'), + ]) + ->tag('console.command') + + ->set('console.command.validator_debug', ValidatorDebugCommand::class) + ->args([ + service('validator'), + ]) + ->tag('console.command') + + ->set('console.command.translation_pull', TranslationPullCommand::class) + ->args([ + service('translation.provider_collection'), + service('translation.writer'), + service('translation.reader'), + param('kernel.default_locale'), + [], // Translator paths + [], // Enabled locales + ]) + ->tag('console.command', ['command' => 'translation:pull']) + + ->set('console.command.translation_push', TranslationPushCommand::class) + ->args([ + service('translation.provider_collection'), + service('translation.reader'), + [], // Translator paths + [], // Enabled locales + ]) + ->tag('console.command', ['command' => 'translation:push']) + + ->set('console.command.workflow_dump', WorkflowDumpCommand::class) + ->args([ + tagged_locator('workflow', 'name'), + ]) + ->tag('console.command') + + ->set('console.command.xliff_lint', XliffLintCommand::class) + ->tag('console.command') + + ->set('console.command.yaml_lint', YamlLintCommand::class) + ->tag('console.command') + + ->set('console.command.form_debug', \Symfony\Component\Form\Command\DebugCommand::class) + ->args([ + service('form.registry'), + [], // All form types namespaces are stored here by FormPass + [], // All services form types are stored here by FormPass + [], // All type extensions are stored here by FormPass + [], // All type guessers are stored here by FormPass + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_set', SecretsSetCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_remove', SecretsRemoveCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_generate_key', SecretsGenerateKeysCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_list', SecretsListCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_reveal', SecretsRevealCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_encrypt_from_local', SecretsEncryptFromLocalCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.messenger.application', Application::class) + ->share(false) + ->call('setAutoExit', [false]) + ->args([ + service('kernel'), + ]) + + ->set('console.messenger.execute_command_handler', RunCommandMessageHandler::class) + ->args([ + service('console.messenger.application'), + ]) + ->tag('messenger.message_handler') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/debug.php b/vendor/symfony/framework-bundle/Resources/config/debug.php new file mode 100644 index 0000000..5c42665 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/debug.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\NotTaggedControllerValueResolver; +use Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver; +use Symfony\Component\HttpKernel\Controller\TraceableControllerResolver; +use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.event_dispatcher', TraceableEventDispatcher::class) + ->decorate('event_dispatcher') + ->args([ + service('debug.event_dispatcher.inner'), + service('debug.stopwatch'), + service('logger')->nullOnInvalid(), + service('.virtual_request_stack')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'event']) + ->tag('kernel.reset', ['method' => 'reset']) + + ->set('debug.controller_resolver', TraceableControllerResolver::class) + ->decorate('controller_resolver') + ->args([ + service('debug.controller_resolver.inner'), + service('debug.stopwatch'), + ]) + + ->set('debug.argument_resolver', TraceableArgumentResolver::class) + ->decorate('argument_resolver') + ->args([ + service('debug.argument_resolver.inner'), + service('debug.stopwatch'), + ]) + + ->set('argument_resolver.not_tagged_controller', NotTaggedControllerValueResolver::class) + ->args([abstract_arg('Controller argument, set in FrameworkExtension')]) + ->tag('controller.argument_value_resolver', ['priority' => -200]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/debug_prod.php b/vendor/symfony/framework-bundle/Resources/config/debug_prod.php new file mode 100644 index 0000000..691786f --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/debug_prod.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\HttpKernel\Debug\ErrorHandlerConfigurator; +use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; + +return static function (ContainerConfigurator $container) { + $container->parameters()->set('debug.error_handler.throw_at', -1); + + $container->services() + ->set('debug.error_handler_configurator', ErrorHandlerConfigurator::class) + ->public() + ->args([ + service('logger')->nullOnInvalid(), + null, // Log levels map for enabled error levels + param('debug.error_handler.throw_at'), + param('kernel.debug'), + param('kernel.debug'), + null, // Deprecation logger if different from the one above + ]) + ->tag('monolog.logger', ['channel' => 'php']) + + ->set('debug.debug_handlers_listener', DebugHandlersListener::class) + ->args([null, param('kernel.runtime_mode.web')]) + ->tag('kernel.event_subscriber') + + ->set('debug.file_link_formatter', FileLinkFormatter::class) + ->args([param('debug.file_link_format')]) + + ->alias(FileLinkFormatter::class, 'debug.file_link_formatter') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/error_renderer.php b/vendor/symfony/framework-bundle/Resources/config/error_renderer.php new file mode 100644 index 0000000..67f28ce --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/error_renderer.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('error_handler.error_renderer.html', HtmlErrorRenderer::class) + ->args([ + inline_service() + ->factory([HtmlErrorRenderer::class, 'isDebug']) + ->args([ + service('request_stack'), + param('kernel.debug'), + ]), + param('kernel.charset'), + service('debug.file_link_formatter')->nullOnInvalid(), + param('kernel.project_dir'), + inline_service() + ->factory([HtmlErrorRenderer::class, 'getAndCleanOutputBuffer']) + ->args([service('request_stack')]), + service('logger')->nullOnInvalid(), + ]) + + ->alias('error_renderer.html', 'error_handler.error_renderer.html') + ->alias('error_renderer', 'error_renderer.html') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/esi.php b/vendor/symfony/framework-bundle/Resources/config/esi.php new file mode 100644 index 0000000..7ac9f90 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/esi.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; +use Symfony\Component\HttpKernel\HttpCache\Esi; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('esi', Esi::class) + + ->set('esi_listener', SurrogateListener::class) + ->args([service('esi')->ignoreOnInvalid()]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/form.php b/vendor/symfony/framework-bundle/Resources/config/form.php new file mode 100644 index 0000000..3c936a2 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/form.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; +use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\ColorType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension; +use Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension; +use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension; +use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; +use Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension; +use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension; +use Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension; +use Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension; +use Symfony\Component\Form\Extension\Validator\Type\UploadValidatorExtension; +use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser; +use Symfony\Component\Form\FormFactory; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\FormRegistry; +use Symfony\Component\Form\FormRegistryInterface; +use Symfony\Component\Form\ResolvedFormTypeFactory; +use Symfony\Component\Form\ResolvedFormTypeFactoryInterface; +use Symfony\Component\Form\Util\ServerParams; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('form.resolved_type_factory', ResolvedFormTypeFactory::class) + + ->alias(ResolvedFormTypeFactoryInterface::class, 'form.resolved_type_factory') + + ->set('form.registry', FormRegistry::class) + ->args([ + [ + /* + * We don't need to be able to add more extensions. + * more types can be registered with the form.type tag + * more type extensions can be registered with the form.type_extension tag + * more type_guessers can be registered with the form.type_guesser tag + */ + service('form.extension'), + ], + service('form.resolved_type_factory'), + ]) + + ->alias(FormRegistryInterface::class, 'form.registry') + + ->set('form.factory', FormFactory::class) + ->args([service('form.registry')]) + + ->alias(FormFactoryInterface::class, 'form.factory') + + ->set('form.extension', DependencyInjectionExtension::class) + ->args([ + abstract_arg('All services with tag "form.type" are stored in a service locator by FormPass'), + abstract_arg('All services with tag "form.type_extension" are stored here by FormPass'), + abstract_arg('All services with tag "form.type_guesser" are stored here by FormPass'), + ]) + + ->set('form.type_guesser.validator', ValidatorTypeGuesser::class) + ->args([service('validator.mapping.class_metadata_factory')]) + ->tag('form.type_guesser') + + ->alias('form.property_accessor', 'property_accessor') + + ->set('form.choice_list_factory.default', DefaultChoiceListFactory::class) + + ->set('form.choice_list_factory.property_access', PropertyAccessDecorator::class) + ->args([ + service('form.choice_list_factory.default'), + service('form.property_accessor'), + ]) + + ->set('form.choice_list_factory.cached', CachingFactoryDecorator::class) + ->args([service('form.choice_list_factory.property_access')]) + ->tag('kernel.reset', ['method' => 'reset']) + + ->alias('form.choice_list_factory', 'form.choice_list_factory.cached') + + ->set('form.type.form', FormType::class) + ->args([service('form.property_accessor')]) + ->tag('form.type') + + ->set('form.type.choice', ChoiceType::class) + ->args([ + service('form.choice_list_factory'), + service('translator')->ignoreOnInvalid(), + ]) + ->tag('form.type') + + ->set('form.type.file', FileType::class) + ->args([service('translator')->ignoreOnInvalid()]) + ->tag('form.type') + + ->set('form.type.color', ColorType::class) + ->args([service('translator')->ignoreOnInvalid()]) + ->tag('form.type') + + ->set('form.type_extension.form.transformation_failure_handling', TransformationFailureExtension::class) + ->args([service('translator')->ignoreOnInvalid()]) + ->tag('form.type_extension', ['extended-type' => FormType::class]) + + ->set('form.type_extension.form.html_sanitizer', TextTypeHtmlSanitizerExtension::class) + ->args([tagged_locator('html_sanitizer', 'sanitizer')]) + ->tag('form.type_extension', ['extended-type' => TextType::class]) + + ->set('form.type_extension.form.http_foundation', FormTypeHttpFoundationExtension::class) + ->args([service('form.type_extension.form.request_handler')]) + ->tag('form.type_extension') + + ->set('form.type_extension.form.request_handler', HttpFoundationRequestHandler::class) + ->args([service('form.server_params')]) + + ->set('form.server_params', ServerParams::class) + ->args([service('request_stack')]) + + ->set('form.type_extension.form.validator', FormTypeValidatorExtension::class) + ->args([ + service('validator'), + false, + service('twig.form.renderer')->ignoreOnInvalid(), + service('translator')->ignoreOnInvalid(), + ]) + ->tag('form.type_extension', ['extended-type' => FormType::class]) + + ->set('form.type_extension.repeated.validator', RepeatedTypeValidatorExtension::class) + ->tag('form.type_extension') + + ->set('form.type_extension.submit.validator', SubmitTypeValidatorExtension::class) + ->tag('form.type_extension', ['extended-type' => SubmitType::class]) + + ->set('form.type_extension.upload.validator', UploadValidatorExtension::class) + ->args([ + service('translator'), + param('validator.translation_domain'), + ]) + ->tag('form.type_extension') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/form_csrf.php b/vendor/symfony/framework-bundle/Resources/config/form_csrf.php new file mode 100644 index 0000000..c8e5e97 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/form_csrf.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('form.type_extension.csrf', FormTypeCsrfExtension::class) + ->args([ + service('security.csrf.token_manager'), + param('form.type_extension.csrf.enabled'), + param('form.type_extension.csrf.field_name'), + service('translator')->nullOnInvalid(), + param('validator.translation_domain'), + service('form.server_params'), + ]) + ->tag('form.type_extension') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/form_debug.php b/vendor/symfony/framework-bundle/Resources/config/form_debug.php new file mode 100644 index 0000000..f5e2c3e --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/form_debug.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Form\Extension\DataCollector\FormDataCollector; +use Symfony\Component\Form\Extension\DataCollector\FormDataExtractor; +use Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy; +use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension; +use Symfony\Component\Form\ResolvedFormTypeFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('form.resolved_type_factory', ResolvedTypeFactoryDataCollectorProxy::class) + ->args([ + inline_service(ResolvedFormTypeFactory::class), + service('data_collector.form'), + ]) + + ->set('form.type_extension.form.data_collector', DataCollectorTypeExtension::class) + ->args([service('data_collector.form')]) + ->tag('form.type_extension') + + ->set('data_collector.form.extractor', FormDataExtractor::class) + + ->set('data_collector.form', FormDataCollector::class) + ->args([service('data_collector.form.extractor')]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/form.html.twig', 'id' => 'form', 'priority' => 310]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/fragment_listener.php b/vendor/symfony/framework-bundle/Resources/config/fragment_listener.php new file mode 100644 index 0000000..465c304 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/fragment_listener.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('fragment.listener', FragmentListener::class) + ->args([service('uri_signer'), param('fragment.path')]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/fragment_renderer.php b/vendor/symfony/framework-bundle/Resources/config/fragment_renderer.php new file mode 100644 index 0000000..76f49c6 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/fragment_renderer.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('fragment.renderer.hinclude.global_template', null) + ->set('fragment.path', '/_fragment') + ; + + $container->services() + ->set('fragment.handler', LazyLoadingFragmentHandler::class) + ->args([ + abstract_arg('fragment renderer locator'), + service('request_stack'), + param('kernel.debug'), + ]) + + ->set('fragment.uri_generator', FragmentUriGenerator::class) + ->args([param('fragment.path'), service('uri_signer'), service('request_stack')]) + ->alias(FragmentUriGeneratorInterface::class, 'fragment.uri_generator') + + ->set('fragment.renderer.inline', InlineFragmentRenderer::class) + ->args([service('http_kernel'), service('event_dispatcher')]) + ->call('setFragmentPath', [param('fragment.path')]) + ->tag('kernel.fragment_renderer', ['alias' => 'inline']) + + ->set('fragment.renderer.hinclude', HIncludeFragmentRenderer::class) + ->args([ + service('twig')->nullOnInvalid(), + service('uri_signer'), + param('fragment.renderer.hinclude.global_template'), + ]) + ->call('setFragmentPath', [param('fragment.path')]) + + ->set('fragment.renderer.esi', EsiFragmentRenderer::class) + ->args([ + service('esi')->nullOnInvalid(), + service('fragment.renderer.inline'), + service('uri_signer'), + ]) + ->call('setFragmentPath', [param('fragment.path')]) + ->tag('kernel.fragment_renderer', ['alias' => 'esi']) + + ->set('fragment.renderer.ssi', SsiFragmentRenderer::class) + ->args([ + service('ssi')->nullOnInvalid(), + service('fragment.renderer.inline'), + service('uri_signer'), + ]) + ->call('setFragmentPath', [param('fragment.path')]) + ->tag('kernel.fragment_renderer', ['alias' => 'ssi']) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/html_sanitizer.php b/vendor/symfony/framework-bundle/Resources/config/html_sanitizer.php new file mode 100644 index 0000000..175dc2e --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/html_sanitizer.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('html_sanitizer.config.default', HtmlSanitizerConfig::class) + ->call('allowSafeElements', [], true) + + ->set('html_sanitizer.sanitizer.default', HtmlSanitizer::class) + ->args([service('html_sanitizer.config.default')]) + ->tag('html_sanitizer', ['sanitizer' => 'default']) + + ->alias('html_sanitizer', 'html_sanitizer.sanitizer.default') + ->alias(HtmlSanitizerInterface::class, 'html_sanitizer') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/http_client.php b/vendor/symfony/framework-bundle/Resources/config/http_client.php new file mode 100644 index 0000000..12a5c01 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/http_client.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Http\Client\HttpAsyncClient; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpClient\HttplugClient; +use Symfony\Component\HttpClient\Messenger\PingWebhookMessageHandler; +use Symfony\Component\HttpClient\Psr18Client; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\UriTemplateHttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('http_client.transport', HttpClientInterface::class) + ->factory([HttpClient::class, 'create']) + ->args([ + [], // default options + abstract_arg('max host connections'), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'http_client']) + ->tag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore']) + + ->set('http_client', HttpClientInterface::class) + ->factory('current') + ->args([[service('http_client.transport')]]) + ->tag('http_client.client') + + ->alias(HttpClientInterface::class, 'http_client') + + ->set('psr18.http_client', Psr18Client::class) + ->args([ + service('http_client'), + service(ResponseFactoryInterface::class)->ignoreOnInvalid(), + service(StreamFactoryInterface::class)->ignoreOnInvalid(), + ]) + + ->alias(ClientInterface::class, 'psr18.http_client') + + ->set('httplug.http_client', HttplugClient::class) + ->args([ + service('http_client'), + service(ResponseFactoryInterface::class)->ignoreOnInvalid(), + service(StreamFactoryInterface::class)->ignoreOnInvalid(), + ]) + + ->alias(HttpAsyncClient::class, 'httplug.http_client') + + ->set('http_client.abstract_retry_strategy', GenericRetryStrategy::class) + ->abstract() + ->args([ + abstract_arg('http codes'), + abstract_arg('delay ms'), + abstract_arg('multiplier'), + abstract_arg('max delay ms'), + abstract_arg('jitter'), + ]) + + ->set('http_client.uri_template', UriTemplateHttpClient::class) + ->decorate('http_client', null, 7) // Between TraceableHttpClient (5) and RetryableHttpClient (10) + ->args([ + service('.inner'), + service('http_client.uri_template_expander')->nullOnInvalid(), + abstract_arg('default vars'), + ]) + + ->set('http_client.uri_template_expander.guzzle', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [\GuzzleHttp\UriTemplate\UriTemplate::class, 'expand'], + ]) + + ->set('http_client.uri_template_expander.rize', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [inline_service(\Rize\UriTemplate::class), 'expand'], + ]) + + ->set('http_client.messenger.ping_webhook_handler', PingWebhookMessageHandler::class) + ->args([ + service('http_client'), + ]) + ->tag('messenger.message_handler') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/http_client_debug.php b/vendor/symfony/framework-bundle/Resources/config/http_client_debug.php new file mode 100644 index 0000000..44031eb --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/http_client_debug.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.http_client', HttpClientDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/http_client.html.twig', + 'id' => 'http_client', + 'priority' => 250, + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/identity_translator.php b/vendor/symfony/framework-bundle/Resources/config/identity_translator.php new file mode 100644 index 0000000..5b88899 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/identity_translator.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Contracts\Translation\TranslatorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translator', IdentityTranslator::class) + ->alias(TranslatorInterface::class, 'translator') + + ->set('identity_translator', IdentityTranslator::class) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/lock.php b/vendor/symfony/framework-bundle/Resources/config/lock.php new file mode 100644 index 0000000..4e14636 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/lock.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\Store\CombinedStore; +use Symfony\Component\Lock\Strategy\ConsensusStrategy; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('lock.store.combined.abstract', CombinedStore::class)->abstract() + ->args([abstract_arg('List of stores'), service('lock.strategy.majority')]) + + ->set('lock.strategy.majority', ConsensusStrategy::class) + + ->set('lock.factory.abstract', LockFactory::class)->abstract() + ->args([abstract_arg('Store')]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'lock']) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/mailer.php b/vendor/symfony/framework-bundle/Resources/config/mailer.php new file mode 100644 index 0000000..9eb545c --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/mailer.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\Command\MailerTestCommand; +use Symfony\Component\Mailer\EventListener\EnvelopeListener; +use Symfony\Component\Mailer\EventListener\MessageListener; +use Symfony\Component\Mailer\EventListener\MessageLoggerListener; +use Symfony\Component\Mailer\EventListener\MessengerTransportListener; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Messenger\MessageHandler; +use Symfony\Component\Mailer\Transport; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mailer\Transport\Transports; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.mailer', Mailer::class) + ->args([ + service('mailer.transports'), + abstract_arg('message bus'), + service('event_dispatcher')->ignoreOnInvalid(), + ]) + ->alias('mailer', 'mailer.mailer') + ->alias(MailerInterface::class, 'mailer.mailer') + + ->set('mailer.transports', Transports::class) + ->factory([service('mailer.transport_factory'), 'fromStrings']) + ->args([ + abstract_arg('transports'), + ]) + + ->set('mailer.transport_factory', Transport::class) + ->args([ + tagged_iterator('mailer.transport_factory'), + ]) + + ->set('mailer.default_transport', TransportInterface::class) + ->factory([service('mailer.transport_factory'), 'fromString']) + ->args([ + abstract_arg('env(MAILER_DSN)'), + ]) + ->alias(TransportInterface::class, 'mailer.default_transport') + + ->set('mailer.messenger.message_handler', MessageHandler::class) + ->args([ + service('mailer.transports'), + ]) + ->tag('messenger.message_handler') + + ->set('mailer.envelope_listener', EnvelopeListener::class) + ->args([ + abstract_arg('sender'), + abstract_arg('recipients'), + ]) + ->tag('kernel.event_subscriber') + + ->set('mailer.message_listener', MessageListener::class) + ->args([ + abstract_arg('headers'), + ]) + ->tag('kernel.event_subscriber') + + ->set('mailer.message_logger_listener', MessageLoggerListener::class) + ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => 'reset']) + + ->set('mailer.messenger_transport_listener', MessengerTransportListener::class) + ->tag('kernel.event_subscriber') + + ->set('console.command.mailer_test', MailerTestCommand::class) + ->args([ + service('mailer.transports'), + ]) + ->tag('console.command') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/mailer_debug.php b/vendor/symfony/framework-bundle/Resources/config/mailer_debug.php new file mode 100644 index 0000000..cdb2057 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/mailer_debug.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\DataCollector\MessageDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.data_collector', MessageDataCollector::class) + ->args([ + service('mailer.message_logger_listener'), + ]) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/mailer.html.twig', + 'id' => 'mailer', + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/mailer_transports.php b/vendor/symfony/framework-bundle/Resources/config/mailer_transports.php new file mode 100644 index 0000000..5434b4c --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/mailer_transports.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Azure\Transport\AzureTransportFactory; +use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; +use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\MailerSend\Transport\MailerSendTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; +use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; +use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; +use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\NativeTransportFactory; +use Symfony\Component\Mailer\Transport\NullTransportFactory; +use Symfony\Component\Mailer\Transport\SendmailTransportFactory; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.transport_factory.abstract', AbstractTransportFactory::class) + ->abstract() + ->args([ + service('event_dispatcher'), + service('http_client')->ignoreOnInvalid(), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'mailer']); + + $factories = [ + 'amazon' => SesTransportFactory::class, + 'azure' => AzureTransportFactory::class, + 'brevo' => BrevoTransportFactory::class, + 'gmail' => GmailTransportFactory::class, + 'infobip' => InfobipTransportFactory::class, + 'mailchimp' => MandrillTransportFactory::class, + 'mailersend' => MailerSendTransportFactory::class, + 'mailgun' => MailgunTransportFactory::class, + 'mailjet' => MailjetTransportFactory::class, + 'mailpace' => MailPaceTransportFactory::class, + 'native' => NativeTransportFactory::class, + 'null' => NullTransportFactory::class, + 'postmark' => PostmarkTransportFactory::class, + 'resend' => ResendTransportFactory::class, + 'scaleway' => ScalewayTransportFactory::class, + 'sendgrid' => SendgridTransportFactory::class, + 'sendmail' => SendmailTransportFactory::class, + 'smtp' => EsmtpTransportFactory::class, + ]; + + foreach ($factories as $name => $class) { + $container->services() + ->set('mailer.transport_factory.'.$name, $class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory'); + } +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/mailer_webhook.php b/vendor/symfony/framework-bundle/Resources/config/mailer_webhook.php new file mode 100644 index 0000000..f9d2b96 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/mailer_webhook.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\Bridge\Brevo\RemoteEvent\BrevoPayloadConverter; +use Symfony\Component\Mailer\Bridge\Brevo\Webhook\BrevoRequestParser; +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\MailerSend\Webhook\MailerSendRequestParser; +use Symfony\Component\Mailer\Bridge\Mailgun\RemoteEvent\MailgunPayloadConverter; +use Symfony\Component\Mailer\Bridge\Mailgun\Webhook\MailgunRequestParser; +use Symfony\Component\Mailer\Bridge\Mailjet\RemoteEvent\MailjetPayloadConverter; +use Symfony\Component\Mailer\Bridge\Mailjet\Webhook\MailjetRequestParser; +use Symfony\Component\Mailer\Bridge\Postmark\RemoteEvent\PostmarkPayloadConverter; +use Symfony\Component\Mailer\Bridge\Postmark\Webhook\PostmarkRequestParser; +use Symfony\Component\Mailer\Bridge\Resend\RemoteEvent\ResendPayloadConverter; +use Symfony\Component\Mailer\Bridge\Resend\Webhook\ResendRequestParser; +use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter; +use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.payload_converter.brevo', BrevoPayloadConverter::class) + ->set('mailer.webhook.request_parser.brevo', BrevoRequestParser::class) + ->args([service('mailer.payload_converter.brevo')]) + ->alias(BrevoRequestParser::class, 'mailer.webhook.request_parser.brevo') + + ->set('mailer.payload_converter.mailersend', MailerSendPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailersend', MailerSendRequestParser::class) + ->args([service('mailer.payload_converter.mailersend')]) + ->alias(MailerSendRequestParser::class, 'mailer.webhook.request_parser.mailersend') + + ->set('mailer.payload_converter.mailgun', MailgunPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailgun', MailgunRequestParser::class) + ->args([service('mailer.payload_converter.mailgun')]) + ->alias(MailgunRequestParser::class, 'mailer.webhook.request_parser.mailgun') + + ->set('mailer.payload_converter.mailjet', MailjetPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailjet', MailjetRequestParser::class) + ->args([service('mailer.payload_converter.mailjet')]) + ->alias(MailjetRequestParser::class, 'mailer.webhook.request_parser.mailjet') + + ->set('mailer.payload_converter.postmark', PostmarkPayloadConverter::class) + ->set('mailer.webhook.request_parser.postmark', PostmarkRequestParser::class) + ->args([service('mailer.payload_converter.postmark')]) + ->alias(PostmarkRequestParser::class, 'mailer.webhook.request_parser.postmark') + + ->set('mailer.payload_converter.resend', ResendPayloadConverter::class) + ->set('mailer.webhook.request_parser.resend', ResendRequestParser::class) + ->args([service('mailer.payload_converter.resend')]) + ->alias(ResendRequestParser::class, 'mailer.webhook.request_parser.resend') + + ->set('mailer.payload_converter.sendgrid', SendgridPayloadConverter::class) + ->set('mailer.webhook.request_parser.sendgrid', SendgridRequestParser::class) + ->args([service('mailer.payload_converter.sendgrid')]) + ->alias(SendgridRequestParser::class, 'mailer.webhook.request_parser.sendgrid') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/messenger.php b/vendor/symfony/framework-bundle/Resources/config/messenger.php new file mode 100644 index 0000000..df24760 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/messenger.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; +use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; +use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; +use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; +use Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener; +use Symfony\Component\Messenger\EventListener\DispatchPcntlSignalListener; +use Symfony\Component\Messenger\EventListener\ResetServicesListener; +use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener; +use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener; +use Symfony\Component\Messenger\EventListener\StopWorkerOnCustomStopExceptionListener; +use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener; +use Symfony\Component\Messenger\Handler\RedispatchMessageHandler; +use Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware; +use Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware; +use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware; +use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; +use Symfony\Component\Messenger\Middleware\RejectRedeliveredMessageMiddleware; +use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; +use Symfony\Component\Messenger\Middleware\SendMessageMiddleware; +use Symfony\Component\Messenger\Middleware\TraceableMiddleware; +use Symfony\Component\Messenger\Middleware\ValidationMiddleware; +use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy; +use Symfony\Component\Messenger\RoutableMessageBus; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory; +use Symfony\Component\Messenger\Transport\Sender\SendersLocator; +use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; +use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; +use Symfony\Component\Messenger\Transport\Serialization\Serializer; +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; +use Symfony\Component\Messenger\Transport\Sync\SyncTransportFactory; +use Symfony\Component\Messenger\Transport\TransportFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->alias(SerializerInterface::class, 'messenger.default_serializer') + + // Asynchronous + ->set('messenger.senders_locator', SendersLocator::class) + ->args([ + abstract_arg('per message senders map'), + abstract_arg('senders service locator'), + ]) + ->set('messenger.middleware.send_message', SendMessageMiddleware::class) + ->abstract() + ->args([ + service('messenger.senders_locator'), + service('event_dispatcher'), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'messenger']) + + // Message encoding/decoding + ->set('messenger.transport.symfony_serializer', Serializer::class) + ->args([ + service('serializer'), + abstract_arg('format'), + abstract_arg('context'), + ]) + + ->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -880]) + + ->set('messenger.transport.native_php_serializer', PhpSerializer::class) + ->alias('messenger.default_serializer', 'messenger.transport.native_php_serializer') + + // Middleware + ->set('messenger.middleware.handle_message', HandleMessageMiddleware::class) + ->abstract() + ->args([ + abstract_arg('bus handler resolver'), + ]) + ->tag('monolog.logger', ['channel' => 'messenger']) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + + ->set('messenger.middleware.add_bus_name_stamp_middleware', AddBusNameStampMiddleware::class) + ->abstract() + + ->set('messenger.middleware.dispatch_after_current_bus', DispatchAfterCurrentBusMiddleware::class) + + ->set('messenger.middleware.validation', ValidationMiddleware::class) + ->args([ + service('validator'), + ]) + + ->set('messenger.middleware.reject_redelivered_message_middleware', RejectRedeliveredMessageMiddleware::class) + + ->set('messenger.middleware.failed_message_processing_middleware', FailedMessageProcessingMiddleware::class) + + ->set('messenger.middleware.traceable', TraceableMiddleware::class) + ->abstract() + ->args([ + service('debug.stopwatch'), + ]) + + ->set('messenger.middleware.router_context', RouterContextMiddleware::class) + ->args([ + service('router'), + ]) + + // Discovery + ->set('messenger.receiver_locator', ServiceLocator::class) + ->args([ + [], + ]) + ->tag('container.service_locator') + + // Transports + ->set('messenger.transport_factory', TransportFactory::class) + ->args([ + tagged_iterator('messenger.transport_factory'), + ]) + + ->set('messenger.transport.amqp.factory', AmqpTransportFactory::class) + + ->set('messenger.transport.redis.factory', RedisTransportFactory::class) + + ->set('messenger.transport.sync.factory', SyncTransportFactory::class) + ->args([ + service('messenger.routable_message_bus'), + ]) + ->tag('messenger.transport_factory') + + ->set('messenger.transport.in_memory.factory', InMemoryTransportFactory::class) + ->args([ + service('clock')->nullOnInvalid(), + ]) + ->tag('messenger.transport_factory') + ->tag('kernel.reset', ['method' => 'reset']) + + ->set('messenger.transport.sqs.factory', AmazonSqsTransportFactory::class) + ->args([ + service('logger')->ignoreOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('messenger.transport.beanstalkd.factory', BeanstalkdTransportFactory::class) + + // retry + ->set('messenger.retry_strategy_locator', ServiceLocator::class) + ->args([ + [], + ]) + ->tag('container.service_locator') + + ->set('messenger.retry.abstract_multiplier_retry_strategy', MultiplierRetryStrategy::class) + ->abstract() + ->args([ + abstract_arg('max retries'), + abstract_arg('delay ms'), + abstract_arg('multiplier'), + abstract_arg('max delay ms'), + abstract_arg('jitter'), + ]) + + // rate limiter + ->set('messenger.rate_limiter_locator', ServiceLocator::class) + ->args([[]]) + ->tag('container.service_locator') + + // worker event listener + ->set('messenger.retry.send_failed_message_for_retry_listener', SendFailedMessageForRetryListener::class) + ->args([ + abstract_arg('senders service locator'), + service('messenger.retry_strategy_locator'), + service('logger')->ignoreOnInvalid(), + service('event_dispatcher'), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('messenger.failure.add_error_details_stamp_listener', AddErrorDetailsStampListener::class) + ->tag('kernel.event_subscriber') + + ->set('messenger.failure.send_failed_message_to_failure_transport_listener', SendFailedMessageToFailureTransportListener::class) + ->args([ + abstract_arg('failure transports'), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('messenger.listener.dispatch_pcntl_signal_listener', DispatchPcntlSignalListener::class) + ->tag('kernel.event_subscriber') + + ->set('messenger.listener.stop_worker_on_restart_signal_listener', StopWorkerOnRestartSignalListener::class) + ->args([ + service('cache.messenger.restart_workers_signal'), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('messenger.listener.stop_worker_on_stop_exception_listener', StopWorkerOnCustomStopExceptionListener::class) + ->tag('kernel.event_subscriber') + + ->set('messenger.listener.reset_services', ResetServicesListener::class) + ->args([ + service('services_resetter'), + ]) + + ->set('messenger.routable_message_bus', RoutableMessageBus::class) + ->args([ + abstract_arg('message bus locator'), + service('messenger.default_bus'), + ]) + + ->set('messenger.redispatch_message_handler', RedispatchMessageHandler::class) + ->args([ + service('messenger.default_bus'), + ]) + ->tag('messenger.message_handler') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/messenger_debug.php b/vendor/symfony/framework-bundle/Resources/config/messenger_debug.php new file mode 100644 index 0000000..58f9be1 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/messenger_debug.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Messenger\DataCollector\MessengerDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.messenger', MessengerDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/messenger.html.twig', + 'id' => 'messenger', + 'priority' => 100, + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/mime_type.php b/vendor/symfony/framework-bundle/Resources/config/mime_type.php new file mode 100644 index 0000000..a7e9bbd --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/mime_type.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mime\MimeTypeGuesserInterface; +use Symfony\Component\Mime\MimeTypes; +use Symfony\Component\Mime\MimeTypesInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mime_types', MimeTypes::class) + ->call('setDefault', [service('mime_types')]) + + ->alias(MimeTypesInterface::class, 'mime_types') + ->alias(MimeTypeGuesserInterface::class, 'mime_types') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/notifier.php b/vendor/symfony/framework-bundle/Resources/config/notifier.php new file mode 100644 index 0000000..3bd19b8 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/notifier.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Monolog\Handler\NotifierHandler; +use Symfony\Component\Notifier\Channel\BrowserChannel; +use Symfony\Component\Notifier\Channel\ChannelPolicy; +use Symfony\Component\Notifier\Channel\ChatChannel; +use Symfony\Component\Notifier\Channel\EmailChannel; +use Symfony\Component\Notifier\Channel\PushChannel; +use Symfony\Component\Notifier\Channel\SmsChannel; +use Symfony\Component\Notifier\Chatter; +use Symfony\Component\Notifier\ChatterInterface; +use Symfony\Component\Notifier\EventListener\NotificationLoggerListener; +use Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener; +use Symfony\Component\Notifier\FlashMessage\DefaultFlashMessageImportanceMapper; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\PushMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Messenger\MessageHandler; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\NotifierInterface; +use Symfony\Component\Notifier\Texter; +use Symfony\Component\Notifier\TexterInterface; +use Symfony\Component\Notifier\Transport; +use Symfony\Component\Notifier\Transport\Transports; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('notifier', Notifier::class) + ->args([tagged_locator('notifier.channel', 'channel'), service('notifier.channel_policy')->ignoreOnInvalid()]) + + ->alias(NotifierInterface::class, 'notifier') + + ->set('notifier.channel_policy', ChannelPolicy::class) + ->args([[]]) + + ->set('notifier.flash_message_importance_mapper', DefaultFlashMessageImportanceMapper::class) + ->args([[]]) + + ->set('notifier.channel.browser', BrowserChannel::class) + ->args([service('request_stack'), service('notifier.flash_message_importance_mapper')]) + ->tag('notifier.channel', ['channel' => 'browser']) + + ->set('notifier.channel.chat', ChatChannel::class) + ->args([ + service('chatter.transports'), + abstract_arg('message bus'), + ]) + ->tag('notifier.channel', ['channel' => 'chat']) + + ->set('notifier.channel.sms', SmsChannel::class) + ->args([ + service('texter.transports'), + abstract_arg('message bus'), + ]) + ->tag('notifier.channel', ['channel' => 'sms']) + + ->set('notifier.channel.email', EmailChannel::class) + ->args([ + service('mailer.transports'), + abstract_arg('message bus'), + ]) + ->tag('notifier.channel', ['channel' => 'email']) + + ->set('notifier.channel.push', PushChannel::class) + ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->tag('notifier.channel', ['channel' => 'push']) + + ->set('notifier.monolog_handler', NotifierHandler::class) + ->args([service('notifier')]) + + ->set('notifier.failed_message_listener', SendFailedMessageToNotifierListener::class) + ->args([service('notifier')]) + + ->set('chatter', Chatter::class) + ->args([ + service('chatter.transports'), + abstract_arg('message bus'), + service('event_dispatcher')->ignoreOnInvalid(), + ]) + + ->alias(ChatterInterface::class, 'chatter') + + ->set('chatter.transports', Transports::class) + ->factory([service('chatter.transport_factory'), 'fromStrings']) + ->args([[]]) + + ->set('chatter.transport_factory', Transport::class) + ->args([tagged_iterator('chatter.transport_factory')]) + + ->set('chatter.messenger.chat_handler', MessageHandler::class) + ->args([service('chatter.transports')]) + ->tag('messenger.message_handler', ['handles' => ChatMessage::class]) + + ->set('texter', Texter::class) + ->args([ + service('texter.transports'), + abstract_arg('message bus'), + service('event_dispatcher')->ignoreOnInvalid(), + ]) + + ->alias(TexterInterface::class, 'texter') + + ->set('texter.transports', Transports::class) + ->factory([service('texter.transport_factory'), 'fromStrings']) + ->args([[]]) + + ->set('texter.transport_factory', Transport::class) + ->args([tagged_iterator('texter.transport_factory')]) + + ->set('texter.messenger.sms_handler', MessageHandler::class) + ->args([service('texter.transports')]) + ->tag('messenger.message_handler', ['handles' => SmsMessage::class]) + + ->set('texter.messenger.push_handler', MessageHandler::class) + ->args([service('texter.transports')]) + ->tag('messenger.message_handler', ['handles' => PushMessage::class]) + + ->set('notifier.notification_logger_listener', NotificationLoggerListener::class) + ->tag('kernel.event_subscriber') + + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/notifier_debug.php b/vendor/symfony/framework-bundle/Resources/config/notifier_debug.php new file mode 100644 index 0000000..47eab26 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/notifier_debug.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Notifier\DataCollector\NotificationDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('notifier.data_collector', NotificationDataCollector::class) + ->args([service('notifier.notification_logger_listener')]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/notifier.html.twig', 'id' => 'notifier']) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/notifier_transports.php b/vendor/symfony/framework-bundle/Resources/config/notifier_transports.php new file mode 100644 index 0000000..5ddc9ae --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/notifier_transports.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Notifier\Bridge; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\NullTransportFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('notifier.transport_factory.abstract', AbstractTransportFactory::class) + ->abstract() + ->args([ + service('event_dispatcher'), + service('http_client')->ignoreOnInvalid(), + ]); + + $chatterFactories = [ + 'bluesky' => Bridge\Bluesky\BlueskyTransportFactory::class, + 'chatwork' => Bridge\Chatwork\ChatworkTransportFactory::class, + 'discord' => Bridge\Discord\DiscordTransportFactory::class, + 'fake-chat' => Bridge\FakeChat\FakeChatTransportFactory::class, + 'firebase' => Bridge\Firebase\FirebaseTransportFactory::class, + 'gitter' => Bridge\Gitter\GitterTransportFactory::class, + 'google-chat' => Bridge\GoogleChat\GoogleChatTransportFactory::class, + 'line-notify' => Bridge\LineNotify\LineNotifyTransportFactory::class, + 'linked-in' => Bridge\LinkedIn\LinkedInTransportFactory::class, + 'mastodon' => Bridge\Mastodon\MastodonTransportFactory::class, + 'mattermost' => Bridge\Mattermost\MattermostTransportFactory::class, + 'mercure' => Bridge\Mercure\MercureTransportFactory::class, + 'microsoft-teams' => Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class, + 'pager-duty' => Bridge\PagerDuty\PagerDutyTransportFactory::class, + 'rocket-chat' => Bridge\RocketChat\RocketChatTransportFactory::class, + 'slack' => Bridge\Slack\SlackTransportFactory::class, + 'telegram' => Bridge\Telegram\TelegramTransportFactory::class, + 'twitter' => Bridge\Twitter\TwitterTransportFactory::class, + 'zendesk' => Bridge\Zendesk\ZendeskTransportFactory::class, + 'zulip' => Bridge\Zulip\ZulipTransportFactory::class, + ]; + + foreach ($chatterFactories as $name => $class) { + $container->services() + ->set('notifier.transport_factory.'.$name, $class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory'); + } + + $texterFactories = [ + 'all-my-sms' => Bridge\AllMySms\AllMySmsTransportFactory::class, + 'bandwidth' => Bridge\Bandwidth\BandwidthTransportFactory::class, + 'brevo' => Bridge\Brevo\BrevoTransportFactory::class, + 'click-send' => Bridge\ClickSend\ClickSendTransportFactory::class, + 'clickatell' => Bridge\Clickatell\ClickatellTransportFactory::class, + 'contact-everyone' => Bridge\ContactEveryone\ContactEveryoneTransportFactory::class, + 'engagespot' => Bridge\Engagespot\EngagespotTransportFactory::class, + 'esendex' => Bridge\Esendex\EsendexTransportFactory::class, + 'expo' => Bridge\Expo\ExpoTransportFactory::class, + 'fake-sms' => Bridge\FakeSms\FakeSmsTransportFactory::class, + 'forty-six-elks' => Bridge\FortySixElks\FortySixElksTransportFactory::class, + 'free-mobile' => Bridge\FreeMobile\FreeMobileTransportFactory::class, + 'gateway-api' => Bridge\GatewayApi\GatewayApiTransportFactory::class, + 'go-ip' => Bridge\GoIp\GoIpTransportFactory::class, + 'infobip' => Bridge\Infobip\InfobipTransportFactory::class, + 'iqsms' => Bridge\Iqsms\IqsmsTransportFactory::class, + 'isendpro' => Bridge\Isendpro\IsendproTransportFactory::class, + 'kaz-info-teh' => Bridge\KazInfoTeh\KazInfoTehTransportFactory::class, + 'light-sms' => Bridge\LightSms\LightSmsTransportFactory::class, + 'lox24' => Bridge\Lox24\Lox24TransportFactory::class, + 'mailjet' => Bridge\Mailjet\MailjetTransportFactory::class, + 'message-bird' => Bridge\MessageBird\MessageBirdTransportFactory::class, + 'message-media' => Bridge\MessageMedia\MessageMediaTransportFactory::class, + 'mobyt' => Bridge\Mobyt\MobytTransportFactory::class, + 'novu' => Bridge\Novu\NovuTransportFactory::class, + 'ntfy' => Bridge\Ntfy\NtfyTransportFactory::class, + 'octopush' => Bridge\Octopush\OctopushTransportFactory::class, + 'one-signal' => Bridge\OneSignal\OneSignalTransportFactory::class, + 'orange-sms' => Bridge\OrangeSms\OrangeSmsTransportFactory::class, + 'ovh-cloud' => Bridge\OvhCloud\OvhCloudTransportFactory::class, + 'plivo' => Bridge\Plivo\PlivoTransportFactory::class, + 'pushover' => Bridge\Pushover\PushoverTransportFactory::class, + 'pushy' => Bridge\Pushy\PushyTransportFactory::class, + 'redlink' => Bridge\Redlink\RedlinkTransportFactory::class, + 'ring-central' => Bridge\RingCentral\RingCentralTransportFactory::class, + 'sendberry' => Bridge\Sendberry\SendberryTransportFactory::class, + 'sevenio' => Bridge\Sevenio\SevenIoTransportFactory::class, + 'simple-textin' => Bridge\SimpleTextin\SimpleTextinTransportFactory::class, + 'sinch' => Bridge\Sinch\SinchTransportFactory::class, + 'sms-biuras' => Bridge\SmsBiuras\SmsBiurasTransportFactory::class, + 'sms-factor' => Bridge\SmsFactor\SmsFactorTransportFactory::class, + 'sms-sluzba' => Bridge\SmsSluzba\SmsSluzbaTransportFactory::class, + 'sms77' => Bridge\Sms77\Sms77TransportFactory::class, + 'smsapi' => Bridge\Smsapi\SmsapiTransportFactory::class, + 'smsbox' => Bridge\Smsbox\SmsboxTransportFactory::class, + 'smsc' => Bridge\Smsc\SmscTransportFactory::class, + 'smsense' => Bridge\Smsense\SmsenseTransportFactory::class, + 'smsmode' => Bridge\Smsmode\SmsmodeTransportFactory::class, + 'spot-hit' => Bridge\SpotHit\SpotHitTransportFactory::class, + 'telnyx' => Bridge\Telnyx\TelnyxTransportFactory::class, + 'termii' => Bridge\Termii\TermiiTransportFactory::class, + 'turbo-sms' => Bridge\TurboSms\TurboSmsTransportFactory::class, + 'twilio' => Bridge\Twilio\TwilioTransportFactory::class, + 'unifonic' => Bridge\Unifonic\UnifonicTransportFactory::class, + 'vonage' => Bridge\Vonage\VonageTransportFactory::class, + 'yunpian' => Bridge\Yunpian\YunpianTransportFactory::class, + ]; + + foreach ($texterFactories as $name => $class) { + $container->services() + ->set('notifier.transport_factory.'.$name, $class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory'); + } + + $container->services() + ->set('notifier.transport_factory.amazon-sns', Bridge\AmazonSns\AmazonSnsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.null', NullTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + ->tag('texter.transport_factory') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/notifier_webhook.php b/vendor/symfony/framework-bundle/Resources/config/notifier_webhook.php new file mode 100644 index 0000000..fc541fd --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/notifier_webhook.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Notifier\Bridge\Twilio\Webhook\TwilioRequestParser; +use Symfony\Component\Notifier\Bridge\Vonage\Webhook\VonageRequestParser; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('notifier.webhook.request_parser.twilio', TwilioRequestParser::class) + ->alias(TwilioRequestParser::class, 'notifier.webhook.request_parser.twilio') + + ->set('notifier.webhook.request_parser.vonage', VonageRequestParser::class) + ->alias(VonageRequestParser::class, 'notifier.webhook.request_parser.vonage') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/process.php b/vendor/symfony/framework-bundle/Resources/config/process.php new file mode 100644 index 0000000..909b848 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/process.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Process\Messenger\RunProcessMessageHandler; + +return static function (ContainerConfigurator $container) { + $container + ->services() + ->set('process.messenger.process_message_handler', RunProcessMessageHandler::class) + ->tag('messenger.message_handler') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/profiling.php b/vendor/symfony/framework-bundle/Resources/config/profiling.php new file mode 100644 index 0000000..eaef795 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/profiling.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\EventListener\ConsoleProfilerListener; +use Symfony\Component\HttpKernel\Debug\VirtualRequestStack; +use Symfony\Component\HttpKernel\EventListener\ProfilerListener; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('profiler', Profiler::class) + ->public() + ->args([service('profiler.storage'), service('logger')->nullOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'profiler']) + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.4']) + + ->set('profiler.storage', FileProfilerStorage::class) + ->args([param('profiler.storage.dsn')]) + + ->set('profiler_listener', ProfilerListener::class) + ->args([ + service('profiler'), + service('request_stack'), + null, + param('profiler_listener.only_exceptions'), + param('profiler_listener.only_main_requests'), + ]) + ->tag('kernel.event_subscriber') + + ->set('console_profiler_listener', ConsoleProfilerListener::class) + ->args([ + service('profiler'), + service('.virtual_request_stack'), + service('debug.stopwatch'), + param('kernel.runtime_mode.cli'), + service('router')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + + ->set('.virtual_request_stack', VirtualRequestStack::class) + ->args([service('request_stack')]) + ->public() + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/property_access.php b/vendor/symfony/framework-bundle/Resources/config/property_access.php new file mode 100644 index 0000000..85ab9f1 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/property_access.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('property_accessor', PropertyAccessor::class) + ->args([ + abstract_arg('magic methods allowed, set by the extension'), + abstract_arg('throw exceptions, set by the extension'), + service('cache.property_access')->ignoreOnInvalid(), + abstract_arg('propertyReadInfoExtractor, set by the extension'), + abstract_arg('propertyWriteInfoExtractor, set by the extension'), + ]) + + ->alias(PropertyAccessorInterface::class, 'property_accessor') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/property_info.php b/vendor/symfony/framework-bundle/Resources/config/property_info.php new file mode 100644 index 0000000..9058783 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/property_info.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('property_info', PropertyInfoExtractor::class) + ->args([[], [], [], [], []]) + + ->alias(PropertyAccessExtractorInterface::class, 'property_info') + ->alias(PropertyDescriptionExtractorInterface::class, 'property_info') + ->alias(PropertyInfoExtractorInterface::class, 'property_info') + ->alias(PropertyTypeExtractorInterface::class, 'property_info') + ->alias(PropertyListExtractorInterface::class, 'property_info') + ->alias(PropertyInitializableExtractorInterface::class, 'property_info') + + ->set('property_info.cache', PropertyInfoCacheExtractor::class) + ->decorate('property_info') + ->args([service('property_info.cache.inner'), service('cache.property_info')]) + + // Extractor + ->set('property_info.reflection_extractor', ReflectionExtractor::class) + ->tag('property_info.list_extractor', ['priority' => -1000]) + ->tag('property_info.type_extractor', ['priority' => -1002]) + ->tag('property_info.access_extractor', ['priority' => -1000]) + ->tag('property_info.initializable_extractor', ['priority' => -1000]) + + ->alias(PropertyReadInfoExtractorInterface::class, 'property_info.reflection_extractor') + ->alias(PropertyWriteInfoExtractorInterface::class, 'property_info.reflection_extractor') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/rate_limiter.php b/vendor/symfony/framework-bundle/Resources/config/rate_limiter.php new file mode 100644 index 0000000..727a1f6 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/rate_limiter.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\RateLimiter\RateLimiterFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('cache.rate_limiter') + ->parent('cache.app') + ->tag('cache.pool') + + ->set('limiter', RateLimiterFactory::class) + ->abstract() + ->args([ + abstract_arg('config'), + abstract_arg('storage'), + null, + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/remote_event.php b/vendor/symfony/framework-bundle/Resources/config/remote_event.php new file mode 100644 index 0000000..4120931 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/remote_event.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\RemoteEvent\Messenger\ConsumeRemoteEventHandler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('remote_event.messenger.handler', ConsumeRemoteEventHandler::class) + ->args([ + tagged_locator('remote_event.consumer', 'consumer'), + ]) + ->tag('messenger.message_handler') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/request.php b/vendor/symfony/framework-bundle/Resources/config/request.php new file mode 100644 index 0000000..ef8fc9a --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/request.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('request.add_request_formats_listener', AddRequestFormatsListener::class) + ->args([abstract_arg('formats')]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/routing.php b/vendor/symfony/framework-bundle/Resources/config/routing.php new file mode 100644 index 0000000..8cdbbf3 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/routing.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer; +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Bundle\FrameworkBundle\Routing\AttributeRouteControllerLoader; +use Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader; +use Symfony\Bundle\FrameworkBundle\Routing\RedirectableCompiledUrlMatcher; +use Symfony\Bundle\FrameworkBundle\Routing\Router; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\Routing\Generator\CompiledUrlGenerator; +use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Loader\AttributeDirectoryLoader; +use Symfony\Component\Routing\Loader\AttributeFileLoader; +use Symfony\Component\Routing\Loader\ContainerLoader; +use Symfony\Component\Routing\Loader\DirectoryLoader; +use Symfony\Component\Routing\Loader\GlobFileLoader; +use Symfony\Component\Routing\Loader\PhpFileLoader; +use Symfony\Component\Routing\Loader\Psr4DirectoryLoader; +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; +use Symfony\Component\Routing\Matcher\ExpressionLanguageProvider; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\Routing\RouterInterface; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('router.request_context.host', 'localhost') + ->set('router.request_context.scheme', 'http') + ->set('router.request_context.base_url', '') + ; + + $container->services() + ->set('routing.resolver', LoaderResolver::class) + + ->set('routing.loader.xml', XmlFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.yml', YamlFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.php', PhpFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.glob', GlobFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.directory', DirectoryLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.container', ContainerLoader::class) + ->args([ + tagged_locator('routing.route_loader'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.attribute', AttributeRouteControllerLoader::class) + ->args([ + '%kernel.environment%', + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->set('routing.loader.attribute.directory', AttributeDirectoryLoader::class) + ->args([ + service('file_locator'), + service('routing.loader.attribute'), + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->set('routing.loader.attribute.file', AttributeFileLoader::class) + ->args([ + service('file_locator'), + service('routing.loader.attribute'), + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->set('routing.loader.psr4', Psr4DirectoryLoader::class) + ->args([ + service('file_locator'), + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->set('routing.loader', DelegatingLoader::class) + ->public() + ->args([ + service('routing.resolver'), + [], // Default options + [], // Default requirements + ]) + + ->set('router.default', Router::class) + ->args([ + service(ContainerInterface::class), + param('router.resource'), + [ + 'cache_dir' => param('router.cache_dir'), + 'debug' => param('kernel.debug'), + 'generator_class' => CompiledUrlGenerator::class, + 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, + 'matcher_class' => RedirectableCompiledUrlMatcher::class, + 'matcher_dumper_class' => CompiledUrlMatcherDumper::class, + ], + service('router.request_context')->ignoreOnInvalid(), + service('parameter_bag')->ignoreOnInvalid(), + service('logger')->ignoreOnInvalid(), + param('kernel.default_locale'), + ]) + ->call('setConfigCacheFactory', [ + service('config_cache_factory'), + ]) + ->tag('monolog.logger', ['channel' => 'router']) + ->tag('container.service_subscriber', ['id' => 'routing.loader']) + ->alias('router', 'router.default') + ->public() + ->alias(RouterInterface::class, 'router') + ->alias(UrlGeneratorInterface::class, 'router') + ->alias(UrlMatcherInterface::class, 'router') + ->alias(RequestContextAwareInterface::class, 'router') + + ->set('router.request_context', RequestContext::class) + ->factory([RequestContext::class, 'fromUri']) + ->args([ + param('router.request_context.base_url'), + param('router.request_context.host'), + param('router.request_context.scheme'), + param('request_listener.http_port'), + param('request_listener.https_port'), + ]) + ->call('setParameter', [ + '_functions', + service('router.expression_language_provider')->ignoreOnInvalid(), + ]) + ->alias(RequestContext::class, 'router.request_context') + + ->set('router.expression_language_provider', ExpressionLanguageProvider::class) + ->args([ + tagged_locator('routing.expression_language_function', 'function'), + ]) + ->tag('routing.expression_language_provider') + + ->set('router.cache_warmer', RouterCacheWarmer::class) + ->args([service(ContainerInterface::class)]) + ->tag('container.service_subscriber', ['id' => 'router']) + ->tag('kernel.cache_warmer') + + ->set('router_listener', RouterListener::class) + ->args([ + service('router'), + service('request_stack'), + service('router.request_context')->ignoreOnInvalid(), + service('logger')->ignoreOnInvalid(), + param('kernel.project_dir'), + param('kernel.debug'), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'request']) + + ->set(RedirectController::class) + ->public() + ->args([ + service('router'), + inline_service('int') + ->factory([service('router.request_context'), 'getHttpPort']), + inline_service('int') + ->factory([service('router.request_context'), 'getHttpsPort']), + ]) + + ->set(TemplateController::class) + ->args([ + service('twig')->ignoreOnInvalid(), + ]) + ->public() + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/routing/errors.xml b/vendor/symfony/framework-bundle/Resources/config/routing/errors.xml new file mode 100644 index 0000000..13a9cc4 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/routing/errors.xml @@ -0,0 +1,12 @@ + + + + + + error_controller::preview + html + \d+ + + diff --git a/vendor/symfony/framework-bundle/Resources/config/routing/webhook.xml b/vendor/symfony/framework-bundle/Resources/config/routing/webhook.xml new file mode 100644 index 0000000..dfa95cf --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/routing/webhook.xml @@ -0,0 +1,11 @@ + + + + + + webhook.controller::handle + .+ + + diff --git a/vendor/symfony/framework-bundle/Resources/config/scheduler.php b/vendor/symfony/framework-bundle/Resources/config/scheduler.php new file mode 100644 index 0000000..7b2856d --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/scheduler.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Scheduler\EventListener\DispatchSchedulerEventListener; +use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; +use Symfony\Component\Scheduler\Messenger\ServiceCallMessageHandler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('scheduler.messenger.service_call_message_handler', ServiceCallMessageHandler::class) + ->args([ + tagged_locator('scheduler.task'), + ]) + ->tag('messenger.message_handler') + ->set('scheduler.messenger_transport_factory', SchedulerTransportFactory::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), + service('clock'), + ]) + ->tag('messenger.transport_factory') + ->set('scheduler.event_listener', DispatchSchedulerEventListener::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), + service('event_dispatcher'), + ]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd b/vendor/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd new file mode 100644 index 0000000..d8d2316 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd @@ -0,0 +1,971 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/framework-bundle/Resources/config/secrets.php b/vendor/symfony/framework-bundle/Resources/config/secrets.php new file mode 100644 index 0000000..8192f2f --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/secrets.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; +use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; +use Symfony\Component\DependencyInjection\StaticEnvVarLoader; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('secrets.vault', SodiumVault::class) + ->args([ + abstract_arg('Secret dir, set in FrameworkExtension'), + service('secrets.decryption_key')->ignoreOnInvalid(), + ]) + + ->set('secrets.env_var_loader', StaticEnvVarLoader::class) + ->args([service('secrets.vault')]) + ->tag('container.env_var_loader') + + ->set('secrets.decryption_key') + ->parent('container.env') + ->args([abstract_arg('Decryption env var, set in FrameworkExtension')]) + + ->set('secrets.local_vault', DotenvVault::class) + ->args([abstract_arg('.env file path, set in FrameworkExtension')]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/security_csrf.php b/vendor/symfony/framework-bundle/Resources/config/security_csrf.php new file mode 100644 index 0000000..bad2284 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/security_csrf.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\CsrfExtension; +use Symfony\Bridge\Twig\Extension\CsrfRuntime; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.csrf.token_generator', UriSafeTokenGenerator::class) + + ->alias(TokenGeneratorInterface::class, 'security.csrf.token_generator') + + ->set('security.csrf.token_storage', SessionTokenStorage::class) + ->args([service('request_stack')]) + + ->alias(TokenStorageInterface::class, 'security.csrf.token_storage') + + ->set('security.csrf.token_manager', CsrfTokenManager::class) + ->args([ + service('security.csrf.token_generator'), + service('security.csrf.token_storage'), + service('request_stack')->ignoreOnInvalid(), + ]) + + ->alias(CsrfTokenManagerInterface::class, 'security.csrf.token_manager') + + ->set('twig.runtime.security_csrf', CsrfRuntime::class) + ->args([service('security.csrf.token_manager')]) + ->tag('twig.runtime') + + ->set('twig.extension.security_csrf', CsrfExtension::class) + ->tag('twig.extension') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/semaphore.php b/vendor/symfony/framework-bundle/Resources/config/semaphore.php new file mode 100644 index 0000000..ce35c25 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/semaphore.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Semaphore\SemaphoreFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('semaphore.factory.abstract', SemaphoreFactory::class)->abstract() + ->args([abstract_arg('Store')]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'semaphore']) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/serializer.php b/vendor/symfony/framework-bundle/Resources/config/serializer.php new file mode 100644 index 0000000..c757769 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/serializer.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; +use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor; +use Symfony\Component\Serializer\Encoder\CsvEncoder; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Encoder\YamlEncoder; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; +use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\Mapping\Loader\LoaderChain; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; +use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; +use Symfony\Component\Serializer\Normalizer\DataUriNormalizer; +use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; +use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; +use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Normalizer\TranslatableNormalizer; +use Symfony\Component\Serializer\Normalizer\UidNormalizer; +use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerInterface; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('serializer.mapping.cache.file', '%kernel.cache_dir%/serialization.php') + ; + + $container->services() + ->set('serializer', Serializer::class) + ->args([[], []]) + + ->alias(SerializerInterface::class, 'serializer') + ->alias(NormalizerInterface::class, 'serializer') + ->alias(DenormalizerInterface::class, 'serializer') + ->alias(EncoderInterface::class, 'serializer') + ->alias(DecoderInterface::class, 'serializer') + + ->alias('serializer.property_accessor', 'property_accessor') + + // Discriminator Map + ->set('serializer.mapping.class_discriminator_resolver', ClassDiscriminatorFromClassMetadata::class) + ->args([service('serializer.mapping.class_metadata_factory')]) + + ->alias(ClassDiscriminatorResolverInterface::class, 'serializer.mapping.class_discriminator_resolver') + + // Normalizer + ->set('serializer.normalizer.constraint_violation_list', ConstraintViolationListNormalizer::class) + ->args([1 => service('serializer.name_converter.metadata_aware')]) + ->autowire(true) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.mime_message', MimeMessageNormalizer::class) + ->args([service('serializer.normalizer.property')]) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.datetimezone', DateTimeZoneNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.dateinterval', DateIntervalNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.data_uri', DataUriNormalizer::class) + ->args([service('mime_types')->nullOnInvalid()]) + ->tag('serializer.normalizer', ['priority' => -920]) + + ->set('serializer.normalizer.datetime', DateTimeNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -910]) + + ->set('serializer.normalizer.json_serializable', JsonSerializableNormalizer::class) + ->args([null, null]) + ->tag('serializer.normalizer', ['priority' => -950]) + + ->set('serializer.normalizer.problem', ProblemNormalizer::class) + ->args([param('kernel.debug'), '$translator' => service('translator')->nullOnInvalid()]) + ->tag('serializer.normalizer', ['priority' => -890]) + + ->set('serializer.denormalizer.unwrapping', UnwrappingDenormalizer::class) + ->args([service('serializer.property_accessor')]) + ->tag('serializer.normalizer', ['priority' => 1000]) + + ->set('serializer.normalizer.uid', UidNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -890]) + + ->set('serializer.normalizer.translatable', TranslatableNormalizer::class) + ->args(['$translator' => service('translator')]) + ->tag('serializer.normalizer', ['priority' => -920]) + + ->set('serializer.normalizer.form_error', FormErrorNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.object', ObjectNormalizer::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + service('serializer.name_converter.metadata_aware'), + service('serializer.property_accessor'), + service('property_info')->ignoreOnInvalid(), + service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), + null, + null, + service('property_info')->ignoreOnInvalid(), + ]) + ->tag('serializer.normalizer', ['priority' => -1000]) + + ->set('serializer.normalizer.property', PropertyNormalizer::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + service('serializer.name_converter.metadata_aware'), + service('property_info')->ignoreOnInvalid(), + service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), + null, + ]) + + ->set('serializer.denormalizer.array', ArrayDenormalizer::class) + ->tag('serializer.normalizer', ['priority' => -990]) + + // Loader + ->set('serializer.mapping.chain_loader', LoaderChain::class) + ->args([[]]) + + // Class Metadata Factory + ->set('serializer.mapping.class_metadata_factory', ClassMetadataFactory::class) + ->args([service('serializer.mapping.chain_loader')]) + + ->alias(ClassMetadataFactoryInterface::class, 'serializer.mapping.class_metadata_factory') + + // Cache + ->set('serializer.mapping.cache_warmer', SerializerCacheWarmer::class) + ->args([abstract_arg('The serializer metadata loaders'), param('serializer.mapping.cache.file')]) + ->tag('kernel.cache_warmer') + + ->set('serializer.mapping.cache.symfony', CacheItemPoolInterface::class) + ->factory([PhpArrayAdapter::class, 'create']) + ->args([param('serializer.mapping.cache.file'), service('cache.serializer')]) + + ->set('serializer.mapping.cache_class_metadata_factory', CacheClassMetadataFactory::class) + ->decorate('serializer.mapping.class_metadata_factory') + ->args([ + service('serializer.mapping.cache_class_metadata_factory.inner'), + service('serializer.mapping.cache.symfony'), + ]) + + // Encoders + ->set('serializer.encoder.xml', XmlEncoder::class) + ->tag('serializer.encoder') + + ->set('serializer.encoder.json', JsonEncoder::class) + ->args([null, null]) + ->tag('serializer.encoder') + + ->set('serializer.encoder.yaml', YamlEncoder::class) + ->args([null, null]) + ->tag('serializer.encoder') + + ->set('serializer.encoder.csv', CsvEncoder::class) + ->tag('serializer.encoder') + + // Name converter + ->set('serializer.name_converter.camel_case_to_snake_case', CamelCaseToSnakeCaseNameConverter::class) + + ->set('serializer.name_converter.metadata_aware', MetadataAwareNameConverter::class) + ->args([service('serializer.mapping.class_metadata_factory')]) + + // PropertyInfo extractor + ->set('property_info.serializer_extractor', SerializerExtractor::class) + ->args([service('serializer.mapping.class_metadata_factory')]) + ->tag('property_info.list_extractor', ['priority' => -999]) + + // ErrorRenderer integration + ->alias('error_renderer', 'error_renderer.serializer') + ->alias('error_renderer.serializer', 'error_handler.error_renderer.serializer') + + ->set('error_handler.error_renderer.serializer', SerializerErrorRenderer::class) + ->args([ + service('serializer'), + inline_service() + ->factory([SerializerErrorRenderer::class, 'getPreferredFormat']) + ->args([service('request_stack')]), + service('error_renderer.html'), + inline_service() + ->factory([HtmlErrorRenderer::class, 'isDebug']) + ->args([service('request_stack'), param('kernel.debug')]), + ]) + + ->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/serializer_debug.php b/vendor/symfony/framework-bundle/Resources/config/serializer_debug.php new file mode 100644 index 0000000..45b764f --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/serializer_debug.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Debug\TraceableSerializer; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.serializer', TraceableSerializer::class) + ->decorate('serializer') + ->args([ + service('debug.serializer.inner'), + service('serializer.data_collector'), + ]) + + ->set('serializer.data_collector', SerializerDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/serializer.html.twig', + 'id' => 'serializer', + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/services.php b/vendor/symfony/framework-bundle/Resources/config/services.php new file mode 100644 index 0000000..c85ccf5 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/services.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Clock\ClockInterface as PsrClockInterface; +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; +use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; +use Symfony\Component\Clock\Clock; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; +use Symfony\Component\Config\ResourceCheckerConfigCacheFactory; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker; +use Symfony\Component\DependencyInjection\EnvVarProcessor; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface as EventDispatcherInterfaceComponentAlias; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\UriSigner; +use Symfony\Component\HttpFoundation\UrlHelper; +use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; +use Symfony\Component\HttpKernel\EventListener\LocaleAwareListener; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner; +use Symfony\Component\Runtime\Runner\Symfony\ResponseRunner; +use Symfony\Component\Runtime\SymfonyRuntime; +use Symfony\Component\String\LazyString; +use Symfony\Component\String\Slugger\AsciiSlugger; +use Symfony\Component\String\Slugger\SluggerInterface; +use Symfony\Component\Workflow\WorkflowEvents; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +return static function (ContainerConfigurator $container) { + // this parameter is used at compile time in RegisterListenersPass + $container->parameters()->set('event_dispatcher.event_aliases', array_merge( + class_exists(ConsoleEvents::class) ? ConsoleEvents::ALIASES : [], + class_exists(FormEvents::class) ? FormEvents::ALIASES : [], + KernelEvents::ALIASES, + class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] + )); + + $container->services() + + ->set('parameter_bag', ContainerBag::class) + ->args([ + service('service_container'), + ]) + ->alias(ContainerBagInterface::class, 'parameter_bag') + ->alias(ParameterBagInterface::class, 'parameter_bag') + + ->set('event_dispatcher', EventDispatcher::class) + ->public() + ->tag('container.hot_path') + ->tag('event_dispatcher.dispatcher', ['name' => 'event_dispatcher']) + ->alias(EventDispatcherInterfaceComponentAlias::class, 'event_dispatcher') + ->alias(EventDispatcherInterface::class, 'event_dispatcher') + ->alias(PsrEventDispatcherInterface::class, 'event_dispatcher') + + ->set('http_kernel', HttpKernel::class) + ->public() + ->args([ + service('event_dispatcher'), + service('controller_resolver'), + service('request_stack'), + service('argument_resolver'), + false, + ]) + ->tag('container.hot_path') + ->tag('container.preload', ['class' => HttpKernelRunner::class]) + ->tag('container.preload', ['class' => ResponseRunner::class]) + ->tag('container.preload', ['class' => SymfonyRuntime::class]) + ->alias(HttpKernelInterface::class, 'http_kernel') + + ->set('request_stack', RequestStack::class) + ->public() + ->alias(RequestStack::class, 'request_stack') + + ->set('http_cache', HttpCache::class) + ->args([ + service('kernel'), + service('http_cache.store'), + service('esi')->nullOnInvalid(), + abstract_arg('options'), + ]) + ->tag('container.hot_path') + + ->set('http_cache.store', Store::class) + ->args([ + param('kernel.cache_dir').'/http_cache', + ]) + ->alias(StoreInterface::class, 'http_cache.store') + + ->set('url_helper', UrlHelper::class) + ->args([ + service('request_stack'), + service('router')->ignoreOnInvalid(), + ]) + ->alias(UrlHelper::class, 'url_helper') + + ->set('cache_warmer', CacheWarmerAggregate::class) + ->public() + ->args([ + tagged_iterator('kernel.cache_warmer'), + param('kernel.debug'), + sprintf('%s/%sDeprecations.log', param('kernel.build_dir'), param('kernel.container_class')), + ]) + ->tag('container.no_preload') + + ->set('cache_clearer', ChainCacheClearer::class) + ->args([ + tagged_iterator('kernel.cache_clearer'), + ]) + + ->set('kernel') + ->synthetic() + ->public() + ->alias(KernelInterface::class, 'kernel') + + ->set('filesystem', Filesystem::class) + ->alias(Filesystem::class, 'filesystem') + + ->set('file_locator', FileLocator::class) + ->args([ + service('kernel'), + ]) + ->alias(FileLocator::class, 'file_locator') + + ->set('uri_signer', UriSigner::class) + ->args([ + param('kernel.secret'), + ]) + ->alias(UriSigner::class, 'uri_signer') + + ->set('config_cache_factory', ResourceCheckerConfigCacheFactory::class) + ->args([ + tagged_iterator('config_cache.resource_checker'), + ]) + + ->set('dependency_injection.config.container_parameters_resource_checker', ContainerParametersResourceChecker::class) + ->args([ + service('service_container'), + ]) + ->tag('config_cache.resource_checker', ['priority' => -980]) + + ->set('config.resource.self_checking_resource_checker', SelfCheckingResourceChecker::class) + ->tag('config_cache.resource_checker', ['priority' => -990]) + + ->set('services_resetter', ServicesResetter::class) + ->public() + + ->set('reverse_container', ReverseContainer::class) + ->args([ + service('service_container'), + service_locator([]), + ]) + ->alias(ReverseContainer::class, 'reverse_container') + + ->set('locale_aware_listener', LocaleAwareListener::class) + ->args([ + [], // locale aware services + service('request_stack'), + ]) + ->tag('kernel.event_subscriber') + + ->set('container.env_var_processor', EnvVarProcessor::class) + ->args([ + service('service_container'), + tagged_iterator('container.env_var_loader'), + ]) + ->tag('container.env_var_processor') + + ->set('slugger', AsciiSlugger::class) + ->args([ + param('kernel.default_locale'), + ]) + ->tag('kernel.locale_aware') + ->alias(SluggerInterface::class, 'slugger') + + ->set('container.getenv', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [service('service_container'), 'getEnv'], + ]) + ->tag('routing.expression_language_function', ['function' => 'env']) + + ->set('container.get_routing_condition_service', \Closure::class) + ->public() + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [tagged_locator('routing.condition_service', 'alias'), 'get'], + ]) + ->tag('routing.expression_language_function', ['function' => 'service']) + + // inherit from this service to lazily access env vars + ->set('container.env', LazyString::class) + ->abstract() + ->factory([LazyString::class, 'fromCallable']) + ->args([ + service('container.getenv'), + ]) + ->set('config_builder.warmer', ConfigBuilderCacheWarmer::class) + ->args([service(KernelInterface::class), service('logger')->nullOnInvalid()]) + ->tag('kernel.cache_warmer') + + ->set('clock', Clock::class) + ->alias(ClockInterface::class, 'clock') + ->alias(PsrClockInterface::class, 'clock') + + // register as abstract and excluded, aka not-autowirable types + ->set(LoaderInterface::class)->abstract()->tag('container.excluded') + ->set(Request::class)->abstract()->tag('container.excluded') + ->set(Response::class)->abstract()->tag('container.excluded') + ->set(SessionInterface::class)->abstract()->tag('container.excluded') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/session.php b/vendor/symfony/framework-bundle/Resources/config/session.php new file mode 100644 index 0000000..907b1b5 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/session.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpFoundation\Session\SessionFactory; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MarshallingSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerFactory; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorageFactory; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorageFactory; +use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorageFactory; +use Symfony\Component\HttpKernel\EventListener\SessionListener; + +return static function (ContainerConfigurator $container) { + $container->parameters()->set('session.metadata.storage_key', '_sf2_meta'); + + $container->services() + ->set('session.factory', SessionFactory::class) + ->args([ + service('request_stack'), + service('session.storage.factory'), + [service('session_listener'), 'onSessionUsage'], + ]) + + ->set('session.storage.factory.native', NativeSessionStorageFactory::class) + ->args([ + param('session.storage.options'), + service('session.handler'), + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + false, + ]) + ->set('session.storage.factory.php_bridge', PhpBridgeSessionStorageFactory::class) + ->args([ + service('session.handler'), + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + false, + ]) + ->set('session.storage.factory.mock_file', MockFileSessionStorageFactory::class) + ->args([ + param('kernel.cache_dir').'/sessions', + 'MOCKSESSID', + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + ]) + + ->alias(\SessionHandlerInterface::class, 'session.handler') + + ->set('session.handler.native', StrictSessionHandler::class) + ->args([ + inline_service(\SessionHandler::class), + ]) + + ->set('session.handler.native_file', StrictSessionHandler::class) + ->args([ + inline_service(NativeFileSessionHandler::class) + ->args([param('session.save_path')]), + ]) + + ->set('session.abstract_handler', AbstractSessionHandler::class) + ->factory([SessionHandlerFactory::class, 'createHandler']) + ->args([abstract_arg('A string or a connection object'), []]) + + ->set('session_listener', SessionListener::class) + ->args([ + service_locator([ + 'session_factory' => service('session.factory')->ignoreOnInvalid(), + 'logger' => service('logger')->ignoreOnInvalid(), + 'session_collector' => service('data_collector.request.session_collector')->ignoreOnInvalid(), + ]), + param('kernel.debug'), + param('session.storage.options'), + ]) + ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => 'reset']) + + ->set('session.marshaller', IdentityMarshaller::class) + + ->set('session.marshalling_handler', MarshallingSessionHandler::class) + ->decorate('session.handler') + ->args([ + service('session.marshalling_handler.inner'), + service('session.marshaller'), + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/ssi.php b/vendor/symfony/framework-bundle/Resources/config/ssi.php new file mode 100644 index 0000000..d41aa74 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/ssi.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; +use Symfony\Component\HttpKernel\HttpCache\Ssi; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('ssi', Ssi::class) + + ->set('ssi_listener', SurrogateListener::class) + ->args([service('ssi')->ignoreOnInvalid()]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/test.php b/vendor/symfony/framework-bundle/Resources/config/test.php new file mode 100644 index 0000000..cef5dfc --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/test.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\TestContainer; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpKernel\EventListener\SessionListener; + +return static function (ContainerConfigurator $container) { + $container->parameters()->set('test.client.parameters', []); + + $container->services() + ->set('test.client', KernelBrowser::class) + ->args([ + service('kernel'), + param('test.client.parameters'), + service('test.client.history'), + service('test.client.cookiejar'), + ]) + ->share(false) + ->public() + + ->set('test.client.history', History::class)->share(false) + ->set('test.client.cookiejar', CookieJar::class)->share(false) + + ->set('test.session.listener', SessionListener::class) + ->args([ + service_locator([ + 'session_factory' => service('session.factory')->ignoreOnInvalid(), + ]), + param('kernel.debug'), + param('session.storage.options'), + ]) + ->tag('kernel.event_subscriber') + + ->set('test.service_container', TestContainer::class) + ->args([ + service('kernel'), + 'test.private_services_locator', + ]) + ->public() + + ->set('test.private_services_locator', ServiceLocator::class) + ->args([abstract_arg('callable collection')]) + ->public() + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/translation.php b/vendor/symfony/framework-bundle/Resources/config/translation.php new file mode 100644 index 0000000..a450e68 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/translation.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer; +use Symfony\Bundle\FrameworkBundle\Translation\Translator; +use Symfony\Component\Translation\Dumper\CsvFileDumper; +use Symfony\Component\Translation\Dumper\IcuResFileDumper; +use Symfony\Component\Translation\Dumper\IniFileDumper; +use Symfony\Component\Translation\Dumper\JsonFileDumper; +use Symfony\Component\Translation\Dumper\MoFileDumper; +use Symfony\Component\Translation\Dumper\PhpFileDumper; +use Symfony\Component\Translation\Dumper\PoFileDumper; +use Symfony\Component\Translation\Dumper\QtFileDumper; +use Symfony\Component\Translation\Dumper\XliffFileDumper; +use Symfony\Component\Translation\Dumper\YamlFileDumper; +use Symfony\Component\Translation\Extractor\ChainExtractor; +use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\Extractor\PhpAstExtractor; +use Symfony\Component\Translation\Extractor\Visitor\ConstraintVisitor; +use Symfony\Component\Translation\Extractor\Visitor\TranslatableMessageVisitor; +use Symfony\Component\Translation\Extractor\Visitor\TransMethodVisitor; +use Symfony\Component\Translation\Formatter\MessageFormatter; +use Symfony\Component\Translation\Loader\CsvFileLoader; +use Symfony\Component\Translation\Loader\IcuDatFileLoader; +use Symfony\Component\Translation\Loader\IcuResFileLoader; +use Symfony\Component\Translation\Loader\IniFileLoader; +use Symfony\Component\Translation\Loader\JsonFileLoader; +use Symfony\Component\Translation\Loader\MoFileLoader; +use Symfony\Component\Translation\Loader\PhpFileLoader; +use Symfony\Component\Translation\Loader\PoFileLoader; +use Symfony\Component\Translation\Loader\QtFileLoader; +use Symfony\Component\Translation\Loader\XliffFileLoader; +use Symfony\Component\Translation\Loader\YamlFileLoader; +use Symfony\Component\Translation\LocaleSwitcher; +use Symfony\Component\Translation\LoggingTranslator; +use Symfony\Component\Translation\Reader\TranslationReader; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\Writer\TranslationWriter; +use Symfony\Component\Translation\Writer\TranslationWriterInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translator.default', Translator::class) + ->args([ + abstract_arg('translation loaders locator'), + service('translator.formatter'), + param('kernel.default_locale'), + abstract_arg('translation loaders ids'), + [ + 'cache_dir' => param('kernel.cache_dir').'/translations', + 'debug' => param('kernel.debug'), + ], + abstract_arg('enabled locales'), + ]) + ->call('setConfigCacheFactory', [service('config_cache_factory')]) + ->tag('kernel.locale_aware') + + ->alias(TranslatorInterface::class, 'translator') + + ->set('translator.logging', LoggingTranslator::class) + ->args([ + service('translator.logging.inner'), + service('logger'), + ]) + ->tag('monolog.logger', ['channel' => 'translation']) + + ->set('translator.formatter.default', MessageFormatter::class) + ->args([service('identity_translator')]) + + ->set('translation.loader.php', PhpFileLoader::class) + ->tag('translation.loader', ['alias' => 'php']) + + ->set('translation.loader.yml', YamlFileLoader::class) + ->tag('translation.loader', ['alias' => 'yaml', 'legacy-alias' => 'yml']) + + ->set('translation.loader.xliff', XliffFileLoader::class) + ->tag('translation.loader', ['alias' => 'xlf', 'legacy-alias' => 'xliff']) + + ->set('translation.loader.po', PoFileLoader::class) + ->tag('translation.loader', ['alias' => 'po']) + + ->set('translation.loader.mo', MoFileLoader::class) + ->tag('translation.loader', ['alias' => 'mo']) + + ->set('translation.loader.qt', QtFileLoader::class) + ->tag('translation.loader', ['alias' => 'ts']) + + ->set('translation.loader.csv', CsvFileLoader::class) + ->tag('translation.loader', ['alias' => 'csv']) + + ->set('translation.loader.res', IcuResFileLoader::class) + ->tag('translation.loader', ['alias' => 'res']) + + ->set('translation.loader.dat', IcuDatFileLoader::class) + ->tag('translation.loader', ['alias' => 'dat']) + + ->set('translation.loader.ini', IniFileLoader::class) + ->tag('translation.loader', ['alias' => 'ini']) + + ->set('translation.loader.json', JsonFileLoader::class) + ->tag('translation.loader', ['alias' => 'json']) + + ->set('translation.dumper.php', PhpFileDumper::class) + ->tag('translation.dumper', ['alias' => 'php']) + + ->set('translation.dumper.xliff', XliffFileDumper::class) + ->tag('translation.dumper', ['alias' => 'xlf']) + + ->set('translation.dumper.xliff.xliff', XliffFileDumper::class) + ->args(['xliff']) + ->tag('translation.dumper', ['alias' => 'xliff']) + + ->set('translation.dumper.po', PoFileDumper::class) + ->tag('translation.dumper', ['alias' => 'po']) + + ->set('translation.dumper.mo', MoFileDumper::class) + ->tag('translation.dumper', ['alias' => 'mo']) + + ->set('translation.dumper.yml', YamlFileDumper::class) + ->tag('translation.dumper', ['alias' => 'yml']) + + ->set('translation.dumper.yaml', YamlFileDumper::class) + ->args(['yaml']) + ->tag('translation.dumper', ['alias' => 'yaml']) + + ->set('translation.dumper.qt', QtFileDumper::class) + ->tag('translation.dumper', ['alias' => 'ts']) + + ->set('translation.dumper.csv', CsvFileDumper::class) + ->tag('translation.dumper', ['alias' => 'csv']) + + ->set('translation.dumper.ini', IniFileDumper::class) + ->tag('translation.dumper', ['alias' => 'ini']) + + ->set('translation.dumper.json', JsonFileDumper::class) + ->tag('translation.dumper', ['alias' => 'json']) + + ->set('translation.dumper.res', IcuResFileDumper::class) + ->tag('translation.dumper', ['alias' => 'res']) + + ->set('translation.extractor.php_ast', PhpAstExtractor::class) + ->args([tagged_iterator('translation.extractor.visitor')]) + ->tag('translation.extractor', ['alias' => 'php']) + + ->set('translation.extractor.visitor.trans_method', TransMethodVisitor::class) + ->tag('translation.extractor.visitor') + + ->set('translation.extractor.visitor.translatable_message', TranslatableMessageVisitor::class) + ->tag('translation.extractor.visitor') + + ->set('translation.extractor.visitor.constraint', ConstraintVisitor::class) + ->tag('translation.extractor.visitor') + + ->set('translation.reader', TranslationReader::class) + ->alias(TranslationReaderInterface::class, 'translation.reader') + + ->set('translation.extractor', ChainExtractor::class) + ->alias(ExtractorInterface::class, 'translation.extractor') + + ->set('translation.writer', TranslationWriter::class) + ->alias(TranslationWriterInterface::class, 'translation.writer') + + ->set('translation.warmer', TranslationsCacheWarmer::class) + ->args([service(ContainerInterface::class)]) + ->tag('container.service_subscriber', ['id' => 'translator']) + ->tag('kernel.cache_warmer') + + ->set('translation.locale_switcher', LocaleSwitcher::class) + ->args([ + param('kernel.default_locale'), + tagged_iterator('kernel.locale_aware', exclude: 'translation.locale_switcher'), + service('router.request_context')->ignoreOnInvalid(), + ]) + ->tag('kernel.reset', ['method' => 'reset']) + ->tag('kernel.locale_aware') + ->alias(LocaleAwareInterface::class, 'translation.locale_switcher') + ->alias(LocaleSwitcher::class, 'translation.locale_switcher') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/translation_debug.php b/vendor/symfony/framework-bundle/Resources/config/translation_debug.php new file mode 100644 index 0000000..7a83301 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/translation_debug.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Translation\DataCollector\TranslationDataCollector; +use Symfony\Component\Translation\DataCollectorTranslator; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translator.data_collector', DataCollectorTranslator::class) + ->args([service('translator.data_collector.inner')]) + + ->set('data_collector.translation', TranslationDataCollector::class) + ->args([service('translator.data_collector')]) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/translation.html.twig', + 'id' => 'translation', + 'priority' => 275, + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/translation_providers.php b/vendor/symfony/framework-bundle/Resources/config/translation_providers.php new file mode 100644 index 0000000..ccb6820 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/translation_providers.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; +use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; +use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; +use Symfony\Component\Translation\Bridge\Phrase\PhraseProviderFactory; +use Symfony\Component\Translation\Provider\NullProviderFactory; +use Symfony\Component\Translation\Provider\TranslationProviderCollection; +use Symfony\Component\Translation\Provider\TranslationProviderCollectionFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translation.provider_collection', TranslationProviderCollection::class) + ->factory([service('translation.provider_collection_factory'), 'fromConfig']) + ->args([ + [], // Providers + ]) + + ->set('translation.provider_collection_factory', TranslationProviderCollectionFactory::class) + ->args([ + tagged_iterator('translation.provider_factory'), + [], // Enabled locales + ]) + + ->set('translation.provider_factory.null', NullProviderFactory::class) + ->tag('translation.provider_factory') + + ->set('translation.provider_factory.crowdin', CrowdinProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + param('kernel.default_locale'), + service('translation.loader.xliff'), + service('translation.dumper.xliff'), + ]) + ->tag('translation.provider_factory') + + ->set('translation.provider_factory.loco', LocoProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + param('kernel.default_locale'), + service('translation.loader.xliff'), + service('translator'), + ]) + ->tag('translation.provider_factory') + + ->set('translation.provider_factory.lokalise', LokaliseProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + param('kernel.default_locale'), + service('translation.loader.xliff'), + ]) + ->tag('translation.provider_factory') + + ->set('translation.provider_factory.phrase', PhraseProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + service('translation.loader.xliff'), + service('translation.dumper.xliff'), + service('cache.app'), + param('kernel.default_locale'), + ]) + ->tag('translation.provider_factory') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/type_info.php b/vendor/symfony/framework-bundle/Resources/config/type_info.php new file mode 100644 index 0000000..71e3646 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/type_info.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionParameterTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionPropertyTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionReturnTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + // type context + ->set('type_info.type_context_factory', TypeContextFactory::class) + ->args([service('type_info.resolver.string')->nullOnInvalid()]) + + // type resolvers + ->set('type_info.resolver', TypeResolver::class) + ->args([service_locator([ + \ReflectionType::class => service('type_info.resolver.reflection_type'), + \ReflectionParameter::class => service('type_info.resolver.reflection_parameter'), + \ReflectionProperty::class => service('type_info.resolver.reflection_property'), + \ReflectionFunctionAbstract::class => service('type_info.resolver.reflection_return'), + ])]) + ->alias(TypeResolverInterface::class, 'type_info.resolver') + + ->set('type_info.resolver.reflection_type', ReflectionTypeResolver::class) + ->args([service('type_info.type_context_factory')]) + + ->set('type_info.resolver.reflection_parameter', ReflectionParameterTypeResolver::class) + ->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')]) + + ->set('type_info.resolver.reflection_property', ReflectionPropertyTypeResolver::class) + ->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')]) + + ->set('type_info.resolver.reflection_return', ReflectionReturnTypeResolver::class) + ->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/uid.php b/vendor/symfony/framework-bundle/Resources/config/uid.php new file mode 100644 index 0000000..840fb97 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/uid.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Uid\Factory\NameBasedUuidFactory; +use Symfony\Component\Uid\Factory\RandomBasedUuidFactory; +use Symfony\Component\Uid\Factory\TimeBasedUuidFactory; +use Symfony\Component\Uid\Factory\UlidFactory; +use Symfony\Component\Uid\Factory\UuidFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('ulid.factory', UlidFactory::class) + ->alias(UlidFactory::class, 'ulid.factory') + + ->set('uuid.factory', UuidFactory::class) + ->alias(UuidFactory::class, 'uuid.factory') + + ->set('name_based_uuid.factory', NameBasedUuidFactory::class) + ->factory([service('uuid.factory'), 'nameBased']) + ->args([abstract_arg('Please set the "framework.uid.name_based_uuid_namespace" configuration option to use the "name_based_uuid.factory" service')]) + ->alias(NameBasedUuidFactory::class, 'name_based_uuid.factory') + + ->set('random_based_uuid.factory', RandomBasedUuidFactory::class) + ->factory([service('uuid.factory'), 'randomBased']) + ->alias(RandomBasedUuidFactory::class, 'random_based_uuid.factory') + + ->set('time_based_uuid.factory', TimeBasedUuidFactory::class) + ->factory([service('uuid.factory'), 'timeBased']) + ->alias(TimeBasedUuidFactory::class, 'time_based_uuid.factory') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/validator.php b/vendor/symfony/framework-bundle/Resources/config/validator.php new file mode 100644 index 0000000..adde2de --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/validator.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Validator\Constraints\EmailValidator; +use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider; +use Symfony\Component\Validator\Constraints\ExpressionValidator; +use Symfony\Component\Validator\Constraints\NoSuspiciousCharactersValidator; +use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; +use Symfony\Component\Validator\Constraints\WhenValidator; +use Symfony\Component\Validator\ContainerConstraintValidatorFactory; +use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; +use Symfony\Component\Validator\Validation; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Validator\ValidatorBuilder; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('validator.mapping.cache.file', param('kernel.cache_dir').'/validation.php'); + + $validatorsDir = \dirname((new \ReflectionClass(EmailValidator::class))->getFileName()); + + $container->services() + ->set('validator', ValidatorInterface::class) + ->factory([service('validator.builder'), 'getValidator']) + ->alias(ValidatorInterface::class, 'validator') + + ->set('validator.builder', ValidatorBuilder::class) + ->factory([Validation::class, 'createValidatorBuilder']) + ->call('setConstraintValidatorFactory', [ + service('validator.validator_factory'), + ]) + ->call('setGroupProviderLocator', [ + tagged_locator('validator.group_provider'), + ]) + ->call('setTranslator', [ + service('translator')->ignoreOnInvalid(), + ]) + ->call('setTranslationDomain', [ + param('validator.translation_domain'), + ]) + ->alias('validator.mapping.class_metadata_factory', 'validator') + + ->set('validator.mapping.cache_warmer', ValidatorCacheWarmer::class) + ->args([ + service('validator.builder'), + param('validator.mapping.cache.file'), + ]) + ->tag('kernel.cache_warmer') + + ->set('validator.mapping.cache.adapter', PhpArrayAdapter::class) + ->factory([PhpArrayAdapter::class, 'create']) + ->args([ + param('validator.mapping.cache.file'), + service('cache.validator'), + ]) + + ->set('validator.validator_factory', ContainerConstraintValidatorFactory::class) + ->args([ + abstract_arg('Constraint validators locator'), + ]) + + ->load('Symfony\Component\Validator\Constraints\\', $validatorsDir.'/*Validator.php') + ->exclude($validatorsDir.'/ExpressionLanguageSyntaxValidator.php') + ->abstract() + ->tag('container.excluded') + ->tag('validator.constraint_validator') + + ->set('validator.expression', ExpressionValidator::class) + ->args([service('validator.expression_language')->nullOnInvalid()]) + ->tag('validator.constraint_validator', [ + 'alias' => 'validator.expression', + ]) + + ->set('validator.expression_language', ExpressionLanguage::class) + ->args([service('cache.validator_expression_language')->nullOnInvalid()]) + ->call('registerProvider', [ + service('validator.expression_language_provider')->ignoreOnInvalid(), + ]) + + ->set('cache.validator_expression_language') + ->parent('cache.system') + ->tag('cache.pool') + + ->set('validator.expression_language_provider', ExpressionLanguageProvider::class) + + ->set('validator.email', EmailValidator::class) + ->args([ + abstract_arg('Default mode'), + ]) + ->tag('validator.constraint_validator') + + ->set('validator.not_compromised_password', NotCompromisedPasswordValidator::class) + ->args([ + service('http_client')->nullOnInvalid(), + param('kernel.charset'), + false, + ]) + ->tag('validator.constraint_validator') + + ->set('validator.when', WhenValidator::class) + ->args([service('validator.expression_language')->nullOnInvalid()]) + ->tag('validator.constraint_validator') + + ->set('validator.no_suspicious_characters', NoSuspiciousCharactersValidator::class) + ->args([param('kernel.enabled_locales')]) + ->tag('validator.constraint_validator', [ + 'alias' => NoSuspiciousCharactersValidator::class, + ]) + + ->set('validator.property_info_loader', PropertyInfoLoader::class) + ->args([ + service('property_info'), + service('property_info'), + service('property_info'), + ]) + ->tag('validator.auto_mapper') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/validator_debug.php b/vendor/symfony/framework-bundle/Resources/config/validator_debug.php new file mode 100644 index 0000000..e9fe441 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/validator_debug.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Validator\DataCollector\ValidatorDataCollector; +use Symfony\Component\Validator\Validator\TraceableValidator; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.validator', TraceableValidator::class) + ->decorate('validator', null, 255) + ->args([ + service('debug.validator.inner'), + ]) + ->tag('kernel.reset', [ + 'method' => 'reset', + ]) + + ->set('data_collector.validator', ValidatorDataCollector::class) + ->args([ + service('debug.validator'), + ]) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/validator.html.twig', + 'id' => 'validator', + 'priority' => 320, + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/web.php b/vendor/symfony/framework-bundle/Resources/config/web.php new file mode 100644 index 0000000..6710dab --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/web.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\UidValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; +use Symfony\Component\HttpKernel\Controller\ErrorController; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\EventListener\CacheAttributeListener; +use Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener; +use Symfony\Component\HttpKernel\EventListener\ErrorListener; +use Symfony\Component\HttpKernel\EventListener\LocaleListener; +use Symfony\Component\HttpKernel\EventListener\ResponseListener; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('controller_resolver', ControllerResolver::class) + ->args([ + service('service_container'), + service('logger')->ignoreOnInvalid(), + ]) + ->call('allowControllers', [[AbstractController::class, TemplateController::class]]) + ->tag('monolog.logger', ['channel' => 'request']) + + ->set('argument_metadata_factory', ArgumentMetadataFactory::class) + + ->set('argument_resolver', ArgumentResolver::class) + ->args([ + service('argument_metadata_factory'), + abstract_arg('argument value resolvers'), + abstract_arg('targeted value resolvers'), + ]) + + ->set('argument_resolver.backed_enum_resolver', BackedEnumValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => BackedEnumValueResolver::class]) + + ->set('argument_resolver.uid', UidValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => UidValueResolver::class]) + + ->set('argument_resolver.datetime', DateTimeValueResolver::class) + ->args([ + service('clock')->nullOnInvalid(), + ]) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => DateTimeValueResolver::class]) + + ->set('argument_resolver.request_payload', RequestPayloadValueResolver::class) + ->args([ + service('serializer'), + service('validator')->nullOnInvalid(), + service('translator')->nullOnInvalid(), + ]) + ->tag('controller.targeted_value_resolver', ['name' => RequestPayloadValueResolver::class]) + ->tag('kernel.event_subscriber') + ->lazy() + + ->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => RequestAttributeValueResolver::class]) + + ->set('argument_resolver.request', RequestValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => RequestValueResolver::class]) + + ->set('argument_resolver.session', SessionValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => SessionValueResolver::class]) + + ->set('argument_resolver.service', ServiceValueResolver::class) + ->args([ + abstract_arg('service locator, set in RegisterControllerArgumentLocatorsPass'), + ]) + ->tag('controller.argument_value_resolver', ['priority' => -50, 'name' => ServiceValueResolver::class]) + + ->set('argument_resolver.default', DefaultValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => -100, 'name' => DefaultValueResolver::class]) + + ->set('argument_resolver.variadic', VariadicValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => -150, 'name' => VariadicValueResolver::class]) + + ->set('argument_resolver.query_parameter_value_resolver', QueryParameterValueResolver::class) + ->tag('controller.targeted_value_resolver', ['name' => QueryParameterValueResolver::class]) + + ->set('response_listener', ResponseListener::class) + ->args([ + param('kernel.charset'), + abstract_arg('The "set_content_language_from_locale" config value'), + ]) + ->tag('kernel.event_subscriber') + + ->set('locale_listener', LocaleListener::class) + ->args([ + service('request_stack'), + param('kernel.default_locale'), + service('router')->ignoreOnInvalid(), + abstract_arg('The "set_locale_from_accept_language" config value'), + param('kernel.enabled_locales'), + ]) + ->tag('kernel.event_subscriber') + + ->set('validate_request_listener', ValidateRequestListener::class) + ->tag('kernel.event_subscriber') + + ->set('disallow_search_engine_index_response_listener', DisallowRobotsIndexingListener::class) + ->tag('kernel.event_subscriber') + + ->set('error_controller', ErrorController::class) + ->public() + ->args([ + service('http_kernel'), + param('kernel.error_controller'), + service('error_renderer'), + ]) + + ->set('exception_listener', ErrorListener::class) + ->args([ + param('kernel.error_controller'), + service('logger')->nullOnInvalid(), + param('kernel.debug'), + abstract_arg('an exceptions to log & status code mapping'), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'request']) + + ->set('controller.cache_attribute_listener', CacheAttributeListener::class) + ->tag('kernel.event_subscriber') + + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/web_link.php b/vendor/symfony/framework-bundle/Resources/config/web_link.php new file mode 100644 index 0000000..64345cc --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/web_link.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\HttpHeaderSerializer; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('web_link.http_header_serializer', HttpHeaderSerializer::class) + ->alias(HttpHeaderSerializer::class, 'web_link.http_header_serializer') + + ->set('web_link.add_link_header_listener', AddLinkHeaderListener::class) + ->args([ + service('web_link.http_header_serializer'), + ]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/webhook.php b/vendor/symfony/framework-bundle/Resources/config/webhook.php new file mode 100644 index 0000000..a7e9d58 --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/webhook.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Webhook\Client\RequestParser; +use Symfony\Component\Webhook\Controller\WebhookController; +use Symfony\Component\Webhook\Messenger\SendWebhookHandler; +use Symfony\Component\Webhook\Server\HeadersConfigurator; +use Symfony\Component\Webhook\Server\HeaderSignatureConfigurator; +use Symfony\Component\Webhook\Server\JsonBodyConfigurator; +use Symfony\Component\Webhook\Server\Transport; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('webhook.transport', Transport::class) + ->args([ + service('http_client'), + service('webhook.headers_configurator'), + service('webhook.body_configurator.json'), + service('webhook.signer'), + ]) + + ->set('webhook.headers_configurator', HeadersConfigurator::class) + + ->set('webhook.body_configurator.json', JsonBodyConfigurator::class) + ->args([ + service('serializer'), + ]) + + ->set('webhook.signer', HeaderSignatureConfigurator::class) + + ->set('webhook.messenger.send_handler', SendWebhookHandler::class) + ->args([ + service('webhook.transport'), + ]) + ->tag('messenger.message_handler') + + ->set('webhook.request_parser', RequestParser::class) + ->alias(RequestParser::class, 'webhook.request_parser') + + ->set('webhook.controller', WebhookController::class) + ->public() + ->args([ + abstract_arg('user defined parsers'), + abstract_arg('message bus'), + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/workflow.php b/vendor/symfony/framework-bundle/Resources/config/workflow.php new file mode 100644 index 0000000..b6c784b --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/workflow.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Workflow\EventListener\ExpressionLanguage; +use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; +use Symfony\Component\Workflow\Registry; +use Symfony\Component\Workflow\StateMachine; +use Symfony\Component\Workflow\Workflow; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('workflow.abstract', Workflow::class) + ->args([ + abstract_arg('workflow definition'), + abstract_arg('marking store'), + service('event_dispatcher')->ignoreOnInvalid(), + abstract_arg('workflow name'), + abstract_arg('events to dispatch'), + ]) + ->abstract() + ->set('state_machine.abstract', StateMachine::class) + ->args([ + abstract_arg('workflow definition'), + abstract_arg('marking store'), + service('event_dispatcher')->ignoreOnInvalid(), + abstract_arg('workflow name'), + abstract_arg('events to dispatch'), + ]) + ->abstract() + ->set('workflow.marking_store.method', MethodMarkingStore::class) + ->abstract() + ->set('workflow.registry', Registry::class) + ->alias(Registry::class, 'workflow.registry') + ->set('workflow.security.expression_language', ExpressionLanguage::class) + ; +}; diff --git a/vendor/symfony/framework-bundle/Resources/config/workflow_debug.php b/vendor/symfony/framework-bundle/Resources/config/workflow_debug.php new file mode 100644 index 0000000..4909b7d --- /dev/null +++ b/vendor/symfony/framework-bundle/Resources/config/workflow_debug.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Workflow\DataCollector\WorkflowDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.workflow', WorkflowDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/workflow.html.twig', + 'id' => 'workflow', + ]) + ->args([ + tagged_iterator('workflow', 'name'), + service('event_dispatcher'), + service('debug.file_link_formatter'), + ]) + ; +}; diff --git a/vendor/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php b/vendor/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php new file mode 100644 index 0000000..5e481d7 --- /dev/null +++ b/vendor/symfony/framework-bundle/Routing/Attribute/AsRoutingConditionService.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Attribute; + +use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; + +/** + * Service tag to autoconfigure routing condition services. + * + * You can tag a service: + * + * #[AsRoutingConditionService('foo')] + * class SomeFooService + * { + * public function bar(): bool + * { + * // ... + * } + * } + * + * Then you can use the tagged service in the routing condition: + * + * class PageController + * { + * #[Route('/page', condition: "service('foo').bar()")] + * public function page(): Response + * { + * // ... + * } + * } + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsRoutingConditionService extends AutoconfigureTag +{ + /** + * @param string|null $alias The alias of the service to use it in routing condition expressions + * @param int $priority Defines a priority that allows the routing condition service to override a service with the same alias + */ + public function __construct( + ?string $alias = null, + int $priority = 0, + ) { + parent::__construct('routing.condition_service', ['alias' => $alias, 'priority' => $priority]); + } +} diff --git a/vendor/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php b/vendor/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php new file mode 100644 index 0000000..7e43e1a --- /dev/null +++ b/vendor/symfony/framework-bundle/Routing/AttributeRouteControllerLoader.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +use Symfony\Component\Routing\Loader\AttributeClassLoader; +use Symfony\Component\Routing\Route; + +/** + * AttributeRouteControllerLoader is an implementation of AttributeClassLoader + * that sets the '_controller' default based on the class and method names. + * + * @author Fabien Potencier + * @author Alexandre Daubois + */ +class AttributeRouteControllerLoader extends AttributeClassLoader +{ + /** + * Configures the _controller default parameter of a given Route instance. + */ + protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void + { + if ('__invoke' === $method->getName()) { + $route->setDefault('_controller', $class->getName()); + } else { + $route->setDefault('_controller', $class->getName().'::'.$method->getName()); + } + } + + /** + * Makes the default route name more sane by removing common keywords. + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method): string + { + $name = preg_replace('/(bundle|controller)_/', '_', parent::getDefaultRouteName($class, $method)); + + if (str_ends_with($method->name, 'Action') || str_ends_with($method->name, '_action')) { + $name = preg_replace('/action(_\d+)?$/', '\\1', $name); + } + + return str_replace('__', '_', $name); + } +} diff --git a/vendor/symfony/framework-bundle/Routing/DelegatingLoader.php b/vendor/symfony/framework-bundle/Routing/DelegatingLoader.php new file mode 100644 index 0000000..42d4617 --- /dev/null +++ b/vendor/symfony/framework-bundle/Routing/DelegatingLoader.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +use Symfony\Component\Config\Exception\LoaderLoadException; +use Symfony\Component\Config\Loader\DelegatingLoader as BaseDelegatingLoader; +use Symfony\Component\Config\Loader\LoaderResolverInterface; +use Symfony\Component\Routing\RouteCollection; + +/** + * DelegatingLoader delegates route loading to other loaders using a loader resolver. + * + * This implementation resolves the _controller attribute from the short notation + * to the fully-qualified form (from a:b:c to class::method). + * + * @author Fabien Potencier + * + * @final + */ +class DelegatingLoader extends BaseDelegatingLoader +{ + private bool $loading = false; + + public function __construct( + LoaderResolverInterface $resolver, + private array $defaultOptions = [], + private array $defaultRequirements = [], + ) { + parent::__construct($resolver); + } + + public function load(mixed $resource, ?string $type = null): RouteCollection + { + if ($this->loading) { + // This can happen if a fatal error occurs in parent::load(). + // Here is the scenario: + // - while routes are being loaded by parent::load() below, a fatal error + // occurs (e.g. parse error in a controller while loading annotations); + // - PHP abruptly empties the stack trace, bypassing all catch/finally blocks; + // it then calls the registered shutdown functions; + // - the ErrorHandler catches the fatal error and re-injects it for rendering + // thanks to HttpKernel->terminateWithException() (that calls handleException()); + // - at this stage, if we try to load the routes again, we must prevent + // the fatal error from occurring a second time, + // otherwise the PHP process would be killed immediately; + // - while rendering the exception page, the router can be required + // (by e.g. the web profiler that needs to generate a URL); + // - this handles the case and prevents the second fatal error + // by triggering an exception beforehand. + + throw new LoaderLoadException($resource, null, 0, null, $type); + } + $this->loading = true; + + try { + $collection = parent::load($resource, $type); + } finally { + $this->loading = false; + } + + foreach ($collection->all() as $route) { + if ($this->defaultOptions) { + $route->setOptions($route->getOptions() + $this->defaultOptions); + } + if ($this->defaultRequirements) { + $route->setRequirements($route->getRequirements() + $this->defaultRequirements); + } + if (!\is_string($controller = $route->getDefault('_controller'))) { + continue; + } + + if (str_contains($controller, '::')) { + continue; + } + + $route->setDefault('_controller', $controller); + } + + return $collection; + } +} diff --git a/vendor/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php b/vendor/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php new file mode 100644 index 0000000..609502b --- /dev/null +++ b/vendor/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +use Symfony\Component\Routing\Matcher\CompiledUrlMatcher; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; + +/** + * @author Fabien Potencier + * + * @internal + */ +class RedirectableCompiledUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect(string $path, string $route, ?string $scheme = null): array + { + return [ + '_controller' => 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction', + 'path' => $path, + 'permanent' => true, + 'scheme' => $scheme, + 'httpPort' => $this->context->getHttpPort(), + 'httpsPort' => $this->context->getHttpsPort(), + '_route' => $route, + ]; + } +} diff --git a/vendor/symfony/framework-bundle/Routing/RouteLoaderInterface.php b/vendor/symfony/framework-bundle/Routing/RouteLoaderInterface.php new file mode 100644 index 0000000..d1cb55a --- /dev/null +++ b/vendor/symfony/framework-bundle/Routing/RouteLoaderInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +/** + * Marker interface for service route loaders. + */ +interface RouteLoaderInterface +{ +} diff --git a/vendor/symfony/framework-bundle/Routing/Router.php b/vendor/symfony/framework-bundle/Routing/Router.php new file mode 100644 index 0000000..69428a1 --- /dev/null +++ b/vendor/symfony/framework-bundle/Routing/Router.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\FileExistenceResource; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; +use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Router as BaseRouter; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * This Router creates the Loader only when the cache is empty. + * + * @author Fabien Potencier + * + * @final since Symfony 7.1 + */ +class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberInterface +{ + private ContainerInterface $container; + private array $collectedParameters = []; + private \Closure $paramFetcher; + + /** + * @param mixed $resource The main resource to load + */ + public function __construct(ContainerInterface $container, mixed $resource, array $options = [], ?RequestContext $context = null, ?ContainerInterface $parameters = null, ?LoggerInterface $logger = null, ?string $defaultLocale = null) + { + $this->container = $container; + $this->resource = $resource; + $this->context = $context ?? new RequestContext(); + $this->logger = $logger; + $this->setOptions($options); + + if ($parameters) { + $this->paramFetcher = $parameters->get(...); + } elseif ($container instanceof SymfonyContainerInterface) { + $this->paramFetcher = $container->getParameter(...); + } else { + throw new \LogicException(sprintf('You should either pass a "%s" instance or provide the $parameters argument of the "%s" method.', SymfonyContainerInterface::class, __METHOD__)); + } + + $this->defaultLocale = $defaultLocale; + } + + public function getRouteCollection(): RouteCollection + { + if (!isset($this->collection)) { + $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']); + $this->resolveParameters($this->collection); + $this->collection->addResource(new ContainerParametersResource($this->collectedParameters)); + + try { + $containerFile = ($this->paramFetcher)('kernel.build_dir').'/'.($this->paramFetcher)('kernel.container_class').'.php'; + if (file_exists($containerFile)) { + $this->collection->addResource(new FileResource($containerFile)); + } else { + $this->collection->addResource(new FileExistenceResource($containerFile)); + } + } catch (ParameterNotFoundException) { + } + } + + return $this->collection; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + if (null === $currentDir = $this->getOption('cache_dir')) { + return []; // skip warmUp when router doesn't use cache + } + + // force cache generation + $this->setOption('cache_dir', $buildDir ?? $cacheDir); + $this->getMatcher(); + $this->getGenerator(); + + $this->setOption('cache_dir', $currentDir); + + return [ + $this->getOption('generator_class'), + $this->getOption('matcher_class'), + ]; + } + + /** + * Replaces placeholders with service container parameter values in: + * - the route defaults, + * - the route requirements, + * - the route path, + * - the route host, + * - the route schemes, + * - the route methods. + */ + private function resolveParameters(RouteCollection $collection): void + { + foreach ($collection as $route) { + foreach ($route->getDefaults() as $name => $value) { + $route->setDefault($name, $this->resolve($value)); + } + + foreach ($route->getRequirements() as $name => $value) { + $route->setRequirement($name, $this->resolve($value)); + } + + $route->setPath($this->resolve($route->getPath())); + $route->setHost($this->resolve($route->getHost())); + + $schemes = []; + foreach ($route->getSchemes() as $scheme) { + $schemes[] = explode('|', $this->resolve($scheme)); + } + $route->setSchemes(array_merge([], ...$schemes)); + + $methods = []; + foreach ($route->getMethods() as $method) { + $methods[] = explode('|', $this->resolve($method)); + } + $route->setMethods(array_merge([], ...$methods)); + $route->setCondition($this->resolve($route->getCondition())); + } + } + + /** + * Recursively replaces %placeholders% with the service container parameters. + * + * @throws ParameterNotFoundException When a placeholder does not exist as a container parameter + * @throws RuntimeException When a container value is not a string or a numeric value + */ + private function resolve(mixed $value): mixed + { + if (\is_array($value)) { + foreach ($value as $key => $val) { + $value[$key] = $this->resolve($val); + } + + return $value; + } + + if (!\is_string($value)) { + return $value; + } + + $escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($value) { + // skip %% + if (!isset($match[1])) { + return '%%'; + } + + if (preg_match('/^env\((?:\w++:)*+\w++\)$/', $match[1])) { + throw new RuntimeException(sprintf('Using "%%%s%%" is not allowed in routing configuration.', $match[1])); + } + + $resolved = ($this->paramFetcher)($match[1]); + + if (\is_scalar($resolved)) { + $this->collectedParameters[$match[1]] = $resolved; + + if (\is_string($resolved)) { + $resolved = $this->resolve($resolved); + } + + if (\is_scalar($resolved)) { + return false === $resolved ? '0' : (string) $resolved; + } + } + + throw new RuntimeException(sprintf('The container parameter "%s", used in the route configuration value "%s", must be a string or numeric, but it is of type "%s".', $match[1], $value, get_debug_type($resolved))); + }, $value); + + return str_replace('%%', '%', $escapedValue); + } + + public static function getSubscribedServices(): array + { + return [ + 'routing.loader' => LoaderInterface::class, + ]; + } +} diff --git a/vendor/symfony/framework-bundle/Secrets/AbstractVault.php b/vendor/symfony/framework-bundle/Secrets/AbstractVault.php new file mode 100644 index 0000000..374964a --- /dev/null +++ b/vendor/symfony/framework-bundle/Secrets/AbstractVault.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Secrets; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractVault +{ + protected ?string $lastMessage = null; + + public function getLastMessage(): ?string + { + return $this->lastMessage; + } + + abstract public function generateKeys(bool $override = false): bool; + + abstract public function seal(string $name, string $value): void; + + abstract public function reveal(string $name): ?string; + + abstract public function remove(string $name): bool; + + abstract public function list(bool $reveal = false): array; + + protected function validateName(string $name): void + { + if (!preg_match('/^\w++$/D', $name)) { + throw new \LogicException(sprintf('Invalid secret name "%s": only "word" characters are allowed.', $name)); + } + } + + protected function getPrettyPath(string $path): string + { + return str_replace(getcwd().\DIRECTORY_SEPARATOR, '', $path); + } +} diff --git a/vendor/symfony/framework-bundle/Secrets/DotenvVault.php b/vendor/symfony/framework-bundle/Secrets/DotenvVault.php new file mode 100644 index 0000000..7bdd74c --- /dev/null +++ b/vendor/symfony/framework-bundle/Secrets/DotenvVault.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Secrets; + +/** + * @author Nicolas Grekas + */ +class DotenvVault extends AbstractVault +{ + public function __construct( + private string $dotenvFile, + ) { + $this->dotenvFile = strtr($dotenvFile, '/', \DIRECTORY_SEPARATOR); + } + + public function generateKeys(bool $override = false): bool + { + $this->lastMessage = 'The dotenv vault doesn\'t encrypt secrets thus doesn\'t need keys.'; + + return false; + } + + public function seal(string $name, string $value): void + { + $this->lastMessage = null; + $this->validateName($name); + $v = str_replace("'", "'\\''", $value); + + $content = is_file($this->dotenvFile) ? file_get_contents($this->dotenvFile) : ''; + $content = preg_replace("/^$name=((\\\\'|'[^']++')++|.*)/m", "$name='$v'", $content, -1, $count); + + if (!$count) { + $content .= "$name='$v'\n"; + } + + file_put_contents($this->dotenvFile, $content); + + $this->lastMessage = sprintf('Secret "%s" %s in "%s".', $name, $count ? 'added' : 'updated', $this->getPrettyPath($this->dotenvFile)); + } + + public function reveal(string $name): ?string + { + $this->lastMessage = null; + $this->validateName($name); + $v = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null)); + + if ('' === ($v ?? '')) { + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); + + return null; + } + + return $v; + } + + public function remove(string $name): bool + { + $this->lastMessage = null; + $this->validateName($name); + + $content = is_file($this->dotenvFile) ? file_get_contents($this->dotenvFile) : ''; + $content = preg_replace("/^$name=((\\\\'|'[^']++')++|.*)\n?/m", '', $content, -1, $count); + + if ($count) { + file_put_contents($this->dotenvFile, $content); + $this->lastMessage = sprintf('Secret "%s" removed from file "%s".', $name, $this->getPrettyPath($this->dotenvFile)); + + return true; + } + + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); + + return false; + } + + public function list(bool $reveal = false): array + { + $this->lastMessage = null; + $secrets = []; + + foreach ($_ENV as $k => $v) { + if ('' !== ($v ?? '') && preg_match('/^\w+$/D', $k)) { + $secrets[$k] = $reveal ? $v : null; + } + } + + foreach ($_SERVER as $k => $v) { + if ('' !== ($v ?? '') && preg_match('/^\w+$/D', $k)) { + $secrets[$k] = $reveal ? $v : null; + } + } + + return $secrets; + } +} diff --git a/vendor/symfony/framework-bundle/Secrets/SodiumVault.php b/vendor/symfony/framework-bundle/Secrets/SodiumVault.php new file mode 100644 index 0000000..dcf7986 --- /dev/null +++ b/vendor/symfony/framework-bundle/Secrets/SodiumVault.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Secrets; + +use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; +use Symfony\Component\String\LazyString; +use Symfony\Component\VarExporter\VarExporter; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + */ +class SodiumVault extends AbstractVault implements EnvVarLoaderInterface +{ + private ?string $encryptionKey = null; + private string|\Stringable|null $decryptionKey = null; + private string $pathPrefix; + private ?string $secretsDir; + + /** + * @param $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault + * or null to store generated keys in the provided $secretsDir + */ + public function __construct(string $secretsDir, #[\SensitiveParameter] string|\Stringable|null $decryptionKey = null) + { + $this->pathPrefix = rtrim(strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.basename($secretsDir).'.'; + $this->decryptionKey = $decryptionKey; + $this->secretsDir = $secretsDir; + } + + public function generateKeys(bool $override = false): bool + { + $this->lastMessage = null; + + if (null === $this->encryptionKey && '' !== $this->decryptionKey = (string) $this->decryptionKey) { + $this->lastMessage = 'Cannot generate keys when a decryption key has been provided while instantiating the vault.'; + + return false; + } + + try { + $this->loadKeys(); + } catch (\RuntimeException) { + // ignore failures to load keys + } + + if ('' !== $this->decryptionKey && !is_file($this->pathPrefix.'encrypt.public.php')) { + $this->export('encrypt.public', $this->encryptionKey); + } + + if (!$override && null !== $this->encryptionKey) { + $this->lastMessage = sprintf('Sodium keys already exist at "%s*.{public,private}" and won\'t be overridden.', $this->getPrettyPath($this->pathPrefix)); + + return false; + } + + $this->decryptionKey = sodium_crypto_box_keypair(); + $this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey); + + $this->export('encrypt.public', $this->encryptionKey); + $this->export('decrypt.private', $this->decryptionKey); + + $this->lastMessage = sprintf('Sodium keys have been generated at "%s*.public/private.php".', $this->getPrettyPath($this->pathPrefix)); + + return true; + } + + public function seal(string $name, string $value): void + { + $this->lastMessage = null; + $this->validateName($name); + $this->loadKeys(); + $filename = $this->getFilename($name); + $this->export($filename, sodium_crypto_box_seal($value, $this->encryptionKey ?? sodium_crypto_box_publickey($this->decryptionKey))); + + $list = $this->list(); + $list[$name] = null; + uksort($list, 'strnatcmp'); + file_put_contents($this->pathPrefix.'list.php', sprintf("lastMessage = sprintf('Secret "%s" encrypted in "%s"; you can commit it.', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + } + + public function reveal(string $name): ?string + { + $this->lastMessage = null; + $this->validateName($name); + + $filename = $this->getFilename($name); + if (!is_file($file = $this->pathPrefix.$filename.'.php')) { + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return null; + } + + if (!\function_exists('sodium_crypto_box_seal')) { + $this->lastMessage = sprintf('Secret "%s" cannot be revealed as the "sodium" PHP extension missing. Try running "composer require paragonie/sodium_compat" if you cannot enable the extension."', $name); + + return null; + } + + $this->loadKeys(); + + if ('' === $this->decryptionKey) { + $this->lastMessage = sprintf('Secret "%s" cannot be revealed as no decryption key was found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return null; + } + + if (false === $value = sodium_crypto_box_seal_open(include $file, $this->decryptionKey)) { + $this->lastMessage = sprintf('Secret "%s" cannot be revealed as the wrong decryption key was provided for "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return null; + } + + return $value; + } + + public function remove(string $name): bool + { + $this->lastMessage = null; + $this->validateName($name); + + $filename = $this->getFilename($name); + if (!is_file($file = $this->pathPrefix.$filename.'.php')) { + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return false; + } + + $list = $this->list(); + unset($list[$name]); + file_put_contents($this->pathPrefix.'list.php', sprintf("lastMessage = sprintf('Secret "%s" removed from "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return @unlink($file) || !file_exists($file); + } + + public function list(bool $reveal = false): array + { + $this->lastMessage = null; + + if (!is_file($file = $this->pathPrefix.'list.php')) { + return []; + } + + $secrets = include $file; + + if (!$reveal) { + return $secrets; + } + + foreach ($secrets as $name => $value) { + $secrets[$name] = $this->reveal($name); + } + + return $secrets; + } + + public function loadEnvVars(): array + { + $envs = []; + $reveal = $this->reveal(...); + + foreach ($this->list() as $name => $value) { + $envs[$name] = LazyString::fromCallable($reveal, $name); + } + + return $envs; + } + + private function loadKeys(): void + { + if (!\function_exists('sodium_crypto_box_seal')) { + throw new \LogicException('The "sodium" PHP extension is required to deal with secrets. Alternatively, try running "composer require paragonie/sodium_compat" if you cannot enable the extension.".'); + } + + if (null !== $this->encryptionKey || '' !== $this->decryptionKey = (string) $this->decryptionKey) { + return; + } + + if (is_file($this->pathPrefix.'decrypt.private.php')) { + $this->decryptionKey = (string) include $this->pathPrefix.'decrypt.private.php'; + } + + if (is_file($this->pathPrefix.'encrypt.public.php')) { + $this->encryptionKey = (string) include $this->pathPrefix.'encrypt.public.php'; + } elseif ('' !== $this->decryptionKey) { + $this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey); + } else { + throw new \RuntimeException(sprintf('Encryption key not found in "%s".', \dirname($this->pathPrefix))); + } + } + + private function export(string $filename, string $data): void + { + $b64 = 'decrypt.private' === $filename ? '// SYMFONY_DECRYPTION_SECRET='.base64_encode($data)."\n" : ''; + $name = basename($this->pathPrefix.$filename); + $data = str_replace('%', '\x', rawurlencode($data)); + $data = sprintf("createSecretsDir(); + + if (false === file_put_contents($this->pathPrefix.$filename.'.php', $data, \LOCK_EX)) { + $e = error_get_last(); + throw new \ErrorException($e['message'] ?? 'Failed to write secrets data.', 0, $e['type'] ?? \E_USER_WARNING); + } + } + + private function createSecretsDir(): void + { + if ($this->secretsDir && !is_dir($this->secretsDir) && !@mkdir($this->secretsDir, 0777, true) && !is_dir($this->secretsDir)) { + throw new \RuntimeException(sprintf('Unable to create the secrets directory (%s).', $this->secretsDir)); + } + + $this->secretsDir = null; + } + + private function getFilename(string $name): string + { + // The MD5 hash allows making secrets case-sensitive. The filename is not enough on Windows. + return $name.'.'.substr(md5($name), 0, 6); + } +} diff --git a/vendor/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php b/vendor/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php new file mode 100644 index 0000000..7eea648 --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use PHPUnit\Framework\ExpectationFailedException; +use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint; + +/** + * Ideas borrowed from Laravel Dusk's assertions. + * + * @see https://laravel.com/docs/5.7/dusk#available-assertions + */ +trait BrowserKitAssertionsTrait +{ + public static function assertResponseIsSuccessful(string $message = '', bool $verbose = true): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful($verbose), $message); + } + + public static function assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode, $verbose), $message); + } + + public static function assertResponseFormatSame(?string $expectedFormat, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat), $message); + } + + public static function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true): void + { + $constraint = new ResponseConstraint\ResponseIsRedirected($verbose); + if ($expectedLocation) { + if (class_exists(ResponseConstraint\ResponseHeaderLocationSame::class)) { + $locationConstraint = new ResponseConstraint\ResponseHeaderLocationSame(self::getRequest(), $expectedLocation); + } else { + $locationConstraint = new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation); + } + + $constraint = LogicalAnd::fromConstraints($constraint, $locationConstraint); + } + if ($expectedCode) { + $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode)); + } + + self::assertThatForResponse($constraint, $message); + } + + public static function assertResponseHasHeader(string $headerName, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseHasHeader($headerName), $message); + } + + public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void + { + self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message); + } + + public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message); + } + + public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message); + } + + public static function assertResponseHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message); + } + + public static function assertResponseNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message); + } + + public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = ''): void + { + self::assertThatForResponse(LogicalAnd::fromConstraints( + new ResponseConstraint\ResponseHasCookie($name, $path, $domain), + new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain) + ), $message); + } + + public static function assertResponseIsUnprocessable(string $message = '', bool $verbose = true): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable($verbose), $message); + } + + public static function assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + self::assertThatForClient(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message); + } + + public static function assertBrowserNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + self::assertThatForClient(new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message); + } + + public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', ?string $domain = null, string $message = ''): void + { + self::assertThatForClient(LogicalAnd::fromConstraints( + new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), + new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain) + ), $message); + } + + public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message); + } + + public static function assertRouteSame(string $expectedRoute, array $parameters = [], string $message = ''): void + { + $constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute); + $constraints = []; + foreach ($parameters as $key => $value) { + $constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value); + } + if ($constraints) { + $constraint = LogicalAnd::fromConstraints($constraint, ...$constraints); + } + + self::assertThat(self::getRequest(), $constraint, $message); + } + + public static function assertThatForResponse(Constraint $constraint, string $message = ''): void + { + try { + self::assertThat(self::getResponse(), $constraint, $message); + } catch (ExpectationFailedException $exception) { + if (($serverExceptionMessage = self::getResponse()->headers->get('X-Debug-Exception')) + && ($serverExceptionFile = self::getResponse()->headers->get('X-Debug-Exception-File'))) { + $serverExceptionFile = explode(':', $serverExceptionFile); + $exception->__construct($exception->getMessage(), $exception->getComparisonFailure(), new \ErrorException(rawurldecode($serverExceptionMessage), 0, 1, rawurldecode($serverExceptionFile[0]), $serverExceptionFile[1]), $exception->getPrevious()); + } + + throw $exception; + } + } + + public static function assertThatForClient(Constraint $constraint, string $message = ''): void + { + self::assertThat(self::getClient(), $constraint, $message); + } + + protected static function getClient(?AbstractBrowser $newClient = null): ?AbstractBrowser + { + static $client; + + if (0 < \func_num_args()) { + return $client = $newClient; + } + + if (!$client instanceof AbstractBrowser) { + static::fail(sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', __CLASS__)); + } + + return $client; + } + + private static function getResponse(): Response + { + if (!$response = self::getClient()->getResponse()) { + static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?'); + } + + return $response; + } + + private static function getRequest(): Request + { + if (!$request = self::getClient()->getRequest()) { + static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?'); + } + + return $request; + } +} diff --git a/vendor/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php b/vendor/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php new file mode 100644 index 0000000..024c78f --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint; +use Symfony\Component\DomCrawler\Test\Constraint\CrawlerSelectorExists; + +/** + * Ideas borrowed from Laravel Dusk's assertions. + * + * @see https://laravel.com/docs/5.7/dusk#available-assertions + */ +trait DomCrawlerAssertionsTrait +{ + public static function assertSelectorExists(string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new CrawlerSelectorExists($selector), $message); + } + + public static function assertSelectorNotExists(string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new LogicalNot(new CrawlerSelectorExists($selector)), $message); + } + + public static function assertSelectorCount(int $expectedCount, string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorCount($expectedCount, $selector), $message); + } + + public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text) + ), $message); + } + + public static function assertAnySelectorTextContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerAnySelectorTextContains($selector, $text) + ), $message); + } + + public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text) + ), $message); + } + + public static function assertAnySelectorTextSame(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerAnySelectorTextSame($selector, $text) + ), $message); + } + + public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)) + ), $message); + } + + public static function assertAnySelectorTextNotContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new LogicalNot(new DomCrawlerConstraint\CrawlerAnySelectorTextContains($selector, $text)) + ), $message); + } + + public static function assertPageTitleSame(string $expectedTitle, string $message = ''): void + { + self::assertSelectorTextSame('title', $expectedTitle, $message); + } + + public static function assertPageTitleContains(string $expectedTitle, string $message = ''): void + { + self::assertSelectorTextContains('title', $expectedTitle, $message); + } + + public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) + ), $message); + } + + public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)) + ), $message); + } + + public static function assertCheckboxChecked(string $fieldName, string $message = ''): void + { + self::assertThat(self::getCrawler(), new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked"), $message); + } + + public static function assertCheckboxNotChecked(string $fieldName, string $message = ''): void + { + self::assertThat(self::getCrawler(), new LogicalNot(new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked")), $message); + } + + public static function assertFormValue(string $formSelector, string $fieldName, string $value, string $message = ''): void + { + $node = self::getCrawler()->filter($formSelector); + self::assertNotEmpty($node, sprintf('Form "%s" not found.', $formSelector)); + $values = $node->form()->getValues(); + self::assertArrayHasKey($fieldName, $values, $message ?: sprintf('Field "%s" not found in form "%s".', $fieldName, $formSelector)); + self::assertSame($value, $values[$fieldName]); + } + + public static function assertNoFormValue(string $formSelector, string $fieldName, string $message = ''): void + { + $node = self::getCrawler()->filter($formSelector); + self::assertNotEmpty($node, sprintf('Form "%s" not found.', $formSelector)); + $values = $node->form()->getValues(); + self::assertArrayNotHasKey($fieldName, $values, $message ?: sprintf('Field "%s" has a value in form "%s".', $fieldName, $formSelector)); + } + + private static function getCrawler(): Crawler + { + if (!$crawler = self::getClient()->getCrawler()) { + static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?'); + } + + return $crawler; + } +} diff --git a/vendor/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php b/vendor/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php new file mode 100644 index 0000000..20c6460 --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; + +/* + * @author Mathieu Santostefano + */ + +trait HttpClientAssertionsTrait +{ + public static function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array|null $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void + { + /** @var KernelBrowser $client */ + $client = static::getClient(); + + if (!($profile = $client->getProfile())) { + static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.'); + } + + /** @var HttpClientDataCollector $httpClientDataCollector */ + $httpClientDataCollector = $profile->getCollector('http_client'); + $expectedRequestHasBeenFound = false; + + if (!\array_key_exists($httpClientId, $httpClientDataCollector->getClients())) { + static::fail(sprintf('HttpClient "%s" is not registered.', $httpClientId)); + } + + foreach ($httpClientDataCollector->getClients()[$httpClientId]['traces'] as $trace) { + if (($expectedUrl !== $trace['info']['url'] && $expectedUrl !== $trace['url']) + || $expectedMethod !== $trace['method'] + ) { + continue; + } + + if (null !== $expectedBody) { + $actualBody = null; + + if (null !== $trace['options']['body'] && null === $trace['options']['json']) { + $actualBody = \is_string($trace['options']['body']) ? $trace['options']['body'] : $trace['options']['body']->getValue(true); + } + + if (null === $trace['options']['body'] && null !== $trace['options']['json']) { + $actualBody = $trace['options']['json']->getValue(true); + } + + if (!$actualBody) { + continue; + } + + if ($expectedBody === $actualBody) { + $expectedRequestHasBeenFound = true; + + if (!$expectedHeaders) { + break; + } + } + } + + if ($expectedHeaders) { + $actualHeaders = $trace['options']['headers'] ?? []; + + foreach ($actualHeaders as $headerKey => $actualHeader) { + if (\array_key_exists($headerKey, $expectedHeaders) + && $expectedHeaders[$headerKey] === $actualHeader->getValue(true) + ) { + $expectedRequestHasBeenFound = true; + break 2; + } + } + } + + $expectedRequestHasBeenFound = true; + break; + } + + self::assertTrue($expectedRequestHasBeenFound, 'The expected request has not been called: "'.$expectedMethod.'" - "'.$expectedUrl.'"'); + } + + public function assertNotHttpClientRequest(string $unexpectedUrl, string $expectedMethod = 'GET', string $httpClientId = 'http_client'): void + { + /** @var KernelBrowser $client */ + $client = static::getClient(); + + if (!$profile = $client->getProfile()) { + static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.'); + } + + /** @var HttpClientDataCollector $httpClientDataCollector */ + $httpClientDataCollector = $profile->getCollector('http_client'); + $unexpectedUrlHasBeenFound = false; + + if (!\array_key_exists($httpClientId, $httpClientDataCollector->getClients())) { + static::fail(sprintf('HttpClient "%s" is not registered.', $httpClientId)); + } + + foreach ($httpClientDataCollector->getClients()[$httpClientId]['traces'] as $trace) { + if (($unexpectedUrl === $trace['info']['url'] || $unexpectedUrl === $trace['url']) + && $expectedMethod === $trace['method'] + ) { + $unexpectedUrlHasBeenFound = true; + break; + } + } + + self::assertFalse($unexpectedUrlHasBeenFound, sprintf('Unexpected URL called: "%s" - "%s"', $expectedMethod, $unexpectedUrl)); + } + + public static function assertHttpClientRequestCount(int $count, string $httpClientId = 'http_client'): void + { + /** @var KernelBrowser $client */ + $client = static::getClient(); + + if (!($profile = $client->getProfile())) { + static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.'); + } + + /** @var HttpClientDataCollector $httpClientDataCollector */ + $httpClientDataCollector = $profile->getCollector('http_client'); + + self::assertCount($count, $httpClientDataCollector->getClients()[$httpClientId]['traces']); + } +} diff --git a/vendor/symfony/framework-bundle/Test/KernelTestCase.php b/vendor/symfony/framework-bundle/Test/KernelTestCase.php new file mode 100644 index 0000000..632b782 --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/KernelTestCase.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * KernelTestCase is the base class for tests needing a Kernel. + * + * @author Fabien Potencier + */ +abstract class KernelTestCase extends TestCase +{ + use MailerAssertionsTrait; + use NotificationAssertionsTrait; + + protected static ?string $class = null; + protected static ?KernelInterface $kernel = null; + protected static bool $booted = false; + + protected function tearDown(): void + { + static::ensureKernelShutdown(); + static::$class = null; + static::$kernel = null; + static::$booted = false; + } + + /** + * @throws \RuntimeException + * @throws \LogicException + */ + protected static function getKernelClass(): string + { + if (!isset($_SERVER['KERNEL_CLASS']) && !isset($_ENV['KERNEL_CLASS'])) { + throw new \LogicException(sprintf('You must set the KERNEL_CLASS environment variable to the fully-qualified class name of your Kernel in phpunit.xml / phpunit.xml.dist or override the "%1$s::createKernel()" or "%1$s::getKernelClass()" method.', static::class)); + } + + if (!class_exists($class = $_ENV['KERNEL_CLASS'] ?? $_SERVER['KERNEL_CLASS'])) { + throw new \RuntimeException(sprintf('Class "%s" doesn\'t exist or cannot be autoloaded. Check that the KERNEL_CLASS value in phpunit.xml matches the fully-qualified class name of your Kernel or override the "%s::createKernel()" method.', $class, static::class)); + } + + return $class; + } + + /** + * Boots the Kernel for this test. + */ + protected static function bootKernel(array $options = []): KernelInterface + { + static::ensureKernelShutdown(); + + $kernel = static::createKernel($options); + $kernel->boot(); + static::$kernel = $kernel; + static::$booted = true; + + return static::$kernel; + } + + /** + * Provides a dedicated test container with access to both public and private + * services. The container will not include private services that have been + * inlined or removed. Private services will be removed when they are not + * used by other services. + * + * Using this method is the best way to get a container from your test code. + */ + protected static function getContainer(): Container + { + if (!static::$booted) { + static::bootKernel(); + } + + try { + return self::$kernel->getContainer()->get('test.service_container'); + } catch (ServiceNotFoundException $e) { + throw new \LogicException('Could not find service "test.service_container". Try updating the "framework.test" config to "true".', 0, $e); + } + } + + /** + * Creates a Kernel. + * + * Available options: + * + * * environment + * * debug + */ + protected static function createKernel(array $options = []): KernelInterface + { + static::$class ??= static::getKernelClass(); + + $env = $options['environment'] ?? $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'test'; + $debug = $options['debug'] ?? $_ENV['APP_DEBUG'] ?? $_SERVER['APP_DEBUG'] ?? true; + + return new static::$class($env, $debug); + } + + /** + * Shuts the kernel down if it was used in the test - called by the tearDown method by default. + */ + protected static function ensureKernelShutdown() + { + if (null !== static::$kernel) { + static::$kernel->boot(); + $container = static::$kernel->getContainer(); + static::$kernel->shutdown(); + static::$booted = false; + + if ($container instanceof ResetInterface) { + $container->reset(); + } + } + } +} diff --git a/vendor/symfony/framework-bundle/Test/MailerAssertionsTrait.php b/vendor/symfony/framework-bundle/Test/MailerAssertionsTrait.php new file mode 100644 index 0000000..2308c3e --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/MailerAssertionsTrait.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Event\MessageEvents; +use Symfony\Component\Mailer\Test\Constraint as MailerConstraint; +use Symfony\Component\Mime\RawMessage; +use Symfony\Component\Mime\Test\Constraint as MimeConstraint; + +trait MailerAssertionsTrait +{ + public static function assertEmailCount(int $count, ?string $transport = null, string $message = ''): void + { + self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport), $message); + } + + public static function assertQueuedEmailCount(int $count, ?string $transport = null, string $message = ''): void + { + self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport, true), $message); + } + + public static function assertEmailIsQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new MailerConstraint\EmailIsQueued(), $message); + } + + public static function assertEmailIsNotQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new LogicalNot(new MailerConstraint\EmailIsQueued()), $message); + } + + public static function assertEmailAttachmentCount(RawMessage $email, int $count, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailAttachmentCount($count), $message); + } + + public static function assertEmailTextBodyContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailTextBodyContains($text), $message); + } + + public static function assertEmailTextBodyNotContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailTextBodyContains($text)), $message); + } + + public static function assertEmailHtmlBodyContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailHtmlBodyContains($text), $message); + } + + public static function assertEmailHtmlBodyNotContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHtmlBodyContains($text)), $message); + } + + public static function assertEmailHasHeader(RawMessage $email, string $headerName, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailHasHeader($headerName), $message); + } + + public static function assertEmailNotHasHeader(RawMessage $email, string $headerName, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHasHeader($headerName)), $message); + } + + public static function assertEmailHeaderSame(RawMessage $email, string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailHeaderSame($headerName, $expectedValue), $message); + } + + public static function assertEmailHeaderNotSame(RawMessage $email, string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHeaderSame($headerName, $expectedValue)), $message); + } + + public static function assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailAddressContains($headerName, $expectedValue), $message); + } + + public static function assertEmailSubjectContains(RawMessage $email, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailSubjectContains($expectedValue), $message); + } + + public static function assertEmailSubjectNotContains(RawMessage $email, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailSubjectContains($expectedValue)), $message); + } + + /** + * @return MessageEvent[] + */ + public static function getMailerEvents(?string $transport = null): array + { + return self::getMessageMailerEvents()->getEvents($transport); + } + + public static function getMailerEvent(int $index = 0, ?string $transport = null): ?MessageEvent + { + return self::getMailerEvents($transport)[$index] ?? null; + } + + /** + * @return RawMessage[] + */ + public static function getMailerMessages(?string $transport = null): array + { + return self::getMessageMailerEvents()->getMessages($transport); + } + + public static function getMailerMessage(int $index = 0, ?string $transport = null): ?RawMessage + { + return self::getMailerMessages($transport)[$index] ?? null; + } + + private static function getMessageMailerEvents(): MessageEvents + { + $container = static::getContainer(); + if ($container->has('mailer.message_logger_listener')) { + return $container->get('mailer.message_logger_listener')->getEvents(); + } + + static::fail('A client must have Mailer enabled to make email assertions. Did you forget to require symfony/mailer?'); + } +} diff --git a/vendor/symfony/framework-bundle/Test/NotificationAssertionsTrait.php b/vendor/symfony/framework-bundle/Test/NotificationAssertionsTrait.php new file mode 100644 index 0000000..b684735 --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/NotificationAssertionsTrait.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\Notifier\Event\MessageEvent; +use Symfony\Component\Notifier\Event\NotificationEvents; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Test\Constraint as NotifierConstraint; + +/* + * @author Smaïne Milianni + */ +trait NotificationAssertionsTrait +{ + public static function assertNotificationCount(int $count, ?string $transportName = null, string $message = ''): void + { + self::assertThat(self::getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName), $message); + } + + public static function assertQueuedNotificationCount(int $count, ?string $transportName = null, string $message = ''): void + { + self::assertThat(self::getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName, true), $message); + } + + public static function assertNotificationIsQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new NotifierConstraint\NotificationIsQueued(), $message); + } + + public static function assertNotificationIsNotQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new LogicalNot(new NotifierConstraint\NotificationIsQueued()), $message); + } + + public static function assertNotificationSubjectContains(MessageInterface $notification, string $text, string $message = ''): void + { + self::assertThat($notification, new NotifierConstraint\NotificationSubjectContains($text), $message); + } + + public static function assertNotificationSubjectNotContains(MessageInterface $notification, string $text, string $message = ''): void + { + self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationSubjectContains($text)), $message); + } + + public static function assertNotificationTransportIsEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void + { + self::assertThat($notification, new NotifierConstraint\NotificationTransportIsEqual($transportName), $message); + } + + public static function assertNotificationTransportIsNotEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void + { + self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationTransportIsEqual($transportName)), $message); + } + + /** + * @return MessageEvent[] + */ + public static function getNotifierEvents(?string $transportName = null): array + { + return self::getNotificationEvents()->getEvents($transportName); + } + + public static function getNotifierEvent(int $index = 0, ?string $transportName = null): ?MessageEvent + { + return self::getNotifierEvents($transportName)[$index] ?? null; + } + + /** + * @return MessageInterface[] + */ + public static function getNotifierMessages(?string $transportName = null): array + { + return self::getNotificationEvents()->getMessages($transportName); + } + + public static function getNotifierMessage(int $index = 0, ?string $transportName = null): ?MessageInterface + { + return self::getNotifierMessages($transportName)[$index] ?? null; + } + + public static function getNotificationEvents(): NotificationEvents + { + $container = static::getContainer(); + if ($container->has('notifier.notification_logger_listener')) { + return $container->get('notifier.notification_logger_listener')->getEvents(); + } + + static::fail('A client must have Notifier enabled to make notifications assertions. Did you forget to require symfony/notifier?'); + } +} diff --git a/vendor/symfony/framework-bundle/Test/TestBrowserToken.php b/vendor/symfony/framework-bundle/Test/TestBrowserToken.php new file mode 100644 index 0000000..e186b2c --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/TestBrowserToken.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * A very limited token that is used to login in tests using the KernelBrowser. + * + * @author Wouter de Jong + */ +class TestBrowserToken extends AbstractToken +{ + public function __construct( + array $roles = [], + ?UserInterface $user = null, + private string $firewallName = 'main', + ) { + parent::__construct($roles); + + if (null !== $user) { + $this->setUser($user); + } + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function getCredentials(): mixed + { + return null; + } + + public function __serialize(): array + { + return [$this->firewallName, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [$this->firewallName, $parentData] = $data; + + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/framework-bundle/Test/TestContainer.php b/vendor/symfony/framework-bundle/Test/TestContainer.php new file mode 100644 index 0000000..e1e7a85 --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/TestContainer.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * A special container used in tests. This gives access to both public and + * private services. The container will not include private services that have + * been inlined or removed. Private services will be removed when they are not + * used by other services. + * + * @author Nicolas Grekas + * + * @internal + */ +class TestContainer extends Container +{ + public function __construct( + private KernelInterface $kernel, + private string $privateServicesLocatorId, + private array $renamedIds = [], + ) { + } + + public function compile(): void + { + $this->getPublicContainer()->compile(); + } + + public function isCompiled(): bool + { + return $this->getPublicContainer()->isCompiled(); + } + + public function getParameterBag(): ParameterBagInterface + { + return $this->getPublicContainer()->getParameterBag(); + } + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + return $this->getPublicContainer()->getParameter($name); + } + + public function hasParameter(string $name): bool + { + return $this->getPublicContainer()->hasParameter($name); + } + + public function setParameter(string $name, mixed $value): void + { + $this->getPublicContainer()->setParameter($name, $value); + } + + public function set(string $id, mixed $service): void + { + $container = $this->getPublicContainer(); + $renamedId = $this->renamedIds[$id] ?? $id; + + try { + $container->set($renamedId, $service); + } catch (InvalidArgumentException $e) { + if (!str_starts_with($e->getMessage(), "The \"$renamedId\" service is private")) { + throw $e; + } + if (isset($container->privates[$renamedId])) { + throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id)); + } + $container->privates[$renamedId] = $service; + } + } + + public function has(string $id): bool + { + return $this->getPublicContainer()->has($id) || $this->getPrivateContainer()->has($id); + } + + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object + { + return $this->getPrivateContainer()->has($id) ? $this->getPrivateContainer()->get($id) : $this->getPublicContainer()->get($id, $invalidBehavior); + } + + public function initialized(string $id): bool + { + return $this->getPublicContainer()->initialized($id); + } + + public function reset(): void + { + // ignore the call + } + + public function getServiceIds(): array + { + return $this->getPublicContainer()->getServiceIds(); + } + + public function getRemovedIds(): array + { + return $this->getPublicContainer()->getRemovedIds(); + } + + private function getPublicContainer(): Container + { + return $this->kernel->getContainer() ?? throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); + } + + private function getPrivateContainer(): ContainerInterface + { + return $this->getPublicContainer()->get($this->privateServicesLocatorId); + } +} diff --git a/vendor/symfony/framework-bundle/Test/WebTestAssertionsTrait.php b/vendor/symfony/framework-bundle/Test/WebTestAssertionsTrait.php new file mode 100644 index 0000000..aebd457 --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/WebTestAssertionsTrait.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +trait WebTestAssertionsTrait +{ + use BrowserKitAssertionsTrait; + use DomCrawlerAssertionsTrait; + use HttpClientAssertionsTrait; +} diff --git a/vendor/symfony/framework-bundle/Test/WebTestCase.php b/vendor/symfony/framework-bundle/Test/WebTestCase.php new file mode 100644 index 0000000..de31d4b --- /dev/null +++ b/vendor/symfony/framework-bundle/Test/WebTestCase.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +/** + * WebTestCase is the base class for functional tests. + * + * @author Fabien Potencier + */ +abstract class WebTestCase extends KernelTestCase +{ + use WebTestAssertionsTrait; + + protected function tearDown(): void + { + parent::tearDown(); + self::getClient(null); + } + + /** + * Creates a KernelBrowser. + * + * @param array $options An array of options to pass to the createKernel method + * @param array $server An array of server parameters + */ + protected static function createClient(array $options = [], array $server = []): KernelBrowser + { + if (static::$booted) { + throw new \LogicException(sprintf('Booting the kernel before calling "%s()" is not supported, the kernel should only be booted once.', __METHOD__)); + } + + $kernel = static::bootKernel($options); + + try { + $client = $kernel->getContainer()->get('test.client'); + } catch (ServiceNotFoundException) { + if (class_exists(KernelBrowser::class)) { + throw new \LogicException('You cannot create the client used in functional tests if the "framework.test" config is not set to true.'); + } + throw new \LogicException('You cannot create the client used in functional tests if the BrowserKit component is not available. Try running "composer require symfony/browser-kit".'); + } + + $client->setServerParameters($server); + + return self::getClient($client); + } +} diff --git a/vendor/symfony/framework-bundle/Translation/Translator.php b/vendor/symfony/framework-bundle/Translation/Translator.php new file mode 100644 index 0000000..870d69a --- /dev/null +++ b/vendor/symfony/framework-bundle/Translation/Translator.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Translation; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileExistenceResource; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use Symfony\Component\Translation\Translator as BaseTranslator; + +/** + * @author Fabien Potencier + * + * @final since Symfony 7.1 + */ +class Translator extends BaseTranslator implements WarmableInterface +{ + protected array $options = [ + 'cache_dir' => null, + 'debug' => false, + 'resource_files' => [], + 'scanned_directories' => [], + 'cache_vary' => [], + ]; + + /** + * @var list + */ + private array $resourceLocales; + + /** + * Holds parameters from addResource() calls so we can defer the actual + * parent::addResource() calls until initialize() is executed. + * + * @var array[] + */ + private array $resources = []; + + /** + * @var string[][] + */ + private array $resourceFiles; + + /** + * @var string[] + */ + private array $scannedDirectories; + + /** + * Constructor. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * resource_files: List of translation resources available grouped by locale. + * * cache_vary: An array of data that is serialized to generate the cached catalogue name. + * + * @param string[] $enabledLocales + * + * @throws InvalidArgumentException + */ + public function __construct( + protected ContainerInterface $container, + MessageFormatterInterface $formatter, + string $defaultLocale, + protected array $loaderIds = [], + array $options = [], + private array $enabledLocales = [], + ) { + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The Translator does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + $this->options = array_merge($this->options, $options); + $this->resourceLocales = array_keys($this->options['resource_files']); + $this->resourceFiles = $this->options['resource_files']; + $this->scannedDirectories = $this->options['scanned_directories']; + + parent::__construct($defaultLocale, $formatter, $this->options['cache_dir'], $this->options['debug'], $this->options['cache_vary']); + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + // skip warmUp when translator doesn't use cache + if (null === $this->options['cache_dir']) { + return []; + } + + $localesToWarmUp = $this->enabledLocales ?: array_merge($this->getFallbackLocales(), [$this->getLocale()], $this->resourceLocales); + + foreach (array_unique($localesToWarmUp) as $locale) { + // reset catalogue in case it's already loaded during the dump of the other locales. + if (isset($this->catalogues[$locale])) { + unset($this->catalogues[$locale]); + } + + $this->loadCatalogue($locale); + } + + return []; + } + + public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null): void + { + if ($this->resourceFiles) { + $this->addResourceFiles(); + } + $this->resources[] = [$format, $resource, $locale, $domain]; + } + + protected function initializeCatalogue(string $locale): void + { + $this->initialize(); + parent::initializeCatalogue($locale); + } + + /** + * @internal + */ + protected function doLoadCatalogue(string $locale): void + { + parent::doLoadCatalogue($locale); + + foreach ($this->scannedDirectories as $directory) { + $resourceClass = file_exists($directory) ? DirectoryResource::class : FileExistenceResource::class; + $this->catalogues[$locale]->addResource(new $resourceClass($directory)); + } + } + + protected function initialize(): void + { + if ($this->resourceFiles) { + $this->addResourceFiles(); + } + foreach ($this->resources as $params) { + [$format, $resource, $locale, $domain] = $params; + parent::addResource($format, $resource, $locale, $domain); + } + $this->resources = []; + + foreach ($this->loaderIds as $id => $aliases) { + foreach ($aliases as $alias) { + $this->addLoader($alias, $this->container->get($id)); + } + } + } + + private function addResourceFiles(): void + { + $filesByLocale = $this->resourceFiles; + $this->resourceFiles = []; + + foreach ($filesByLocale as $files) { + foreach ($files as $file) { + // filename is domain.locale.format + $fileNameParts = explode('.', basename($file)); + $format = array_pop($fileNameParts); + $locale = array_pop($fileNameParts); + $domain = implode('.', $fileNameParts); + $this->addResource($format, $file, $locale, $domain); + } + } + } +} diff --git a/vendor/symfony/framework-bundle/composer.json b/vendor/symfony/framework-bundle/composer.json new file mode 100644 index 0000000..af934f3 --- /dev/null +++ b/vendor/symfony/framework-bundle/composer.json @@ -0,0 +1,114 @@ +{ + "name": "symfony/framework-bundle", + "type": "symfony-bundle", + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^7.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/filesystem": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", + "seld/jsonlint": "^1.10", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/form": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1", + "symfony/validator": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "twig/twig": "^3.0.4" + }, + "conflict": { + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<6.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/dom-crawler": "<6.4", + "symfony/http-client": "<6.4", + "symfony/form": "<6.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/property-info": "<6.4", + "symfony/property-access": "<6.4", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", + "symfony/serializer": "<6.4", + "symfony/security-csrf": "<6.4", + "symfony/security-core": "<6.4", + "symfony/stopwatch": "<6.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/workflow": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/http-client-contracts/CHANGELOG.md b/vendor/symfony/http-client-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/http-client-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/http-client-contracts/ChunkInterface.php b/vendor/symfony/http-client-contracts/ChunkInterface.php new file mode 100644 index 0000000..0800cb3 --- /dev/null +++ b/vendor/symfony/http-client-contracts/ChunkInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * The interface of chunks returned by ResponseStreamInterface::current(). + * + * When the chunk is first, last or timeout, the content MUST be empty. + * When an unchecked timeout or a network error occurs, a TransportExceptionInterface + * MUST be thrown by the destructor unless one was already thrown by another method. + * + * @author Nicolas Grekas + */ +interface ChunkInterface +{ + /** + * Tells when the idle timeout has been reached. + * + * @throws TransportExceptionInterface on a network error + */ + public function isTimeout(): bool; + + /** + * Tells when headers just arrived. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function isFirst(): bool; + + /** + * Tells when the body just completed. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function isLast(): bool; + + /** + * Returns a [status code, headers] tuple when a 1xx status code was just received. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function getInformationalStatus(): ?array; + + /** + * Returns the content of the response chunk. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function getContent(): string; + + /** + * Returns the offset of the chunk in the response body. + */ + public function getOffset(): int; + + /** + * In case of error, returns the message that describes it. + */ + public function getError(): ?string; +} diff --git a/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php new file mode 100644 index 0000000..22d2b45 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 4xx response is returned. + * + * @author Nicolas Grekas + */ +interface ClientExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php new file mode 100644 index 0000000..971a7a2 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a content-type cannot be decoded to the expected representation. + * + * @author Nicolas Grekas + */ +interface DecodingExceptionInterface extends ExceptionInterface +{ +} diff --git a/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php new file mode 100644 index 0000000..e553b47 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * The base interface for all exceptions in the contract. + * + * @author Nicolas Grekas + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php new file mode 100644 index 0000000..17865ed --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Base interface for HTTP-related exceptions. + * + * @author Anton Chernikov + */ +interface HttpExceptionInterface extends ExceptionInterface +{ + public function getResponse(): ResponseInterface; +} diff --git a/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php new file mode 100644 index 0000000..edd9b8a --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 3xx response is returned and the "max_redirects" option has been reached. + * + * @author Nicolas Grekas + */ +interface RedirectionExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php new file mode 100644 index 0000000..9bfe135 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 5xx response is returned. + * + * @author Nicolas Grekas + */ +interface ServerExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php new file mode 100644 index 0000000..08acf9f --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When an idle timeout occurs. + * + * @author Nicolas Grekas + */ +interface TimeoutExceptionInterface extends TransportExceptionInterface +{ +} diff --git a/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php b/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php new file mode 100644 index 0000000..0c8d131 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When any error happens at the transport level. + * + * @author Nicolas Grekas + */ +interface TransportExceptionInterface extends ExceptionInterface +{ +} diff --git a/vendor/symfony/http-client-contracts/HttpClientInterface.php b/vendor/symfony/http-client-contracts/HttpClientInterface.php new file mode 100644 index 0000000..4bb1dd3 --- /dev/null +++ b/vendor/symfony/http-client-contracts/HttpClientInterface.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\Test\HttpClientTestCase; + +/** + * Provides flexible methods for requesting HTTP resources synchronously or asynchronously. + * + * @see HttpClientTestCase for a reference test suite + * + * @author Nicolas Grekas + */ +interface HttpClientInterface +{ + public const OPTIONS_DEFAULTS = [ + 'auth_basic' => null, // array|string - an array containing the username as first value, and optionally the + // password as the second one; or string like username:password - enabling HTTP Basic + // authentication (RFC 7617) + 'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750) + 'query' => [], // string[] - associative array of query string values to merge with the request's URL + 'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of values + 'body' => '', // array|string|resource|\Traversable|\Closure - the callback SHOULD yield a string + // smaller than the amount requested as argument; the empty string signals EOF; if + // an array is passed, it is meant as a form payload of field names and values + 'json' => null, // mixed - if set, implementations MUST set the "body" option to the JSON-encoded + // value and set the "content-type" header to a JSON-compatible value if it is not + // explicitly defined in the headers option - typically "application/json" + 'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) that + // MUST be available via $response->getInfo('user_data') - not used internally + 'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower than or equal to 0 + // means redirects should not be followed; "Authorization" and "Cookie" headers MUST + // NOT follow except for the initial host name + 'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0 + 'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2 + 'buffer' => true, // bool|resource|\Closure - whether the content of the response should be buffered or not, + // or a stream resource where the response body should be written, + // or a closure telling if/where the response should be buffered based on its headers + 'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort + // the request; it MUST be called on DNS resolution, on arrival of headers and on + // completion; it SHOULD be called on upload/download of data and at least 1/s + 'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution + 'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored + 'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached + 'timeout' => null, // float - the idle timeout (in seconds) - defaults to ini_get('default_socket_timeout') + 'max_duration' => 0, // float - the maximum execution time (in seconds) for the request+response as a whole; + // a value lower than or equal to 0 means it is unlimited + 'bindto' => '0', // string - the interface or the local socket to bind to + 'verify_peer' => true, // see https://php.net/context.ssl for the following options + 'verify_host' => true, + 'cafile' => null, + 'capath' => null, + 'local_cert' => null, + 'local_pk' => null, + 'passphrase' => null, + 'ciphers' => null, + 'peer_fingerprint' => null, + 'capture_peer_cert_chain' => false, + 'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, // STREAM_CRYPTO_METHOD_TLSv*_CLIENT - minimum TLS version + 'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options + ]; + + /** + * Requests an HTTP resource. + * + * Responses MUST be lazy, but their status code MUST be + * checked even if none of their public methods are called. + * + * Implementations are not required to support all options described above; they can also + * support more custom options; but in any case, they MUST throw a TransportExceptionInterface + * when an unsupported option is passed. + * + * @throws TransportExceptionInterface When an unsupported option is passed + */ + public function request(string $method, string $url, array $options = []): ResponseInterface; + + /** + * Yields responses chunk by chunk as they complete. + * + * @param ResponseInterface|iterable $responses One or more responses created by the current HTTP client + * @param float|null $timeout The idle timeout before yielding timeout chunks + */ + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface; + + /** + * Returns a new instance of the client with new default options. + */ + public function withOptions(array $options): static; +} diff --git a/vendor/symfony/http-client-contracts/LICENSE b/vendor/symfony/http-client-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/http-client-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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. diff --git a/vendor/symfony/http-client-contracts/README.md b/vendor/symfony/http-client-contracts/README.md new file mode 100644 index 0000000..24d72f5 --- /dev/null +++ b/vendor/symfony/http-client-contracts/README.md @@ -0,0 +1,9 @@ +Symfony HttpClient Contracts +============================ + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/http-client-contracts/ResponseInterface.php b/vendor/symfony/http-client-contracts/ResponseInterface.php new file mode 100644 index 0000000..387345c --- /dev/null +++ b/vendor/symfony/http-client-contracts/ResponseInterface.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * A (lazily retrieved) HTTP response. + * + * @author Nicolas Grekas + */ +interface ResponseInterface +{ + /** + * Gets the HTTP status code of the response. + * + * @throws TransportExceptionInterface when a network error occurs + */ + public function getStatusCode(): int; + + /** + * Gets the HTTP headers of the response. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @return string[][] The headers of the response keyed by header names in lowercase + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function getHeaders(bool $throw = true): array; + + /** + * Gets the response body as a string. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function getContent(bool $throw = true): string; + + /** + * Gets the response body decoded as array, typically from a JSON payload. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @throws DecodingExceptionInterface When the body cannot be decoded to an array + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toArray(bool $throw = true): array; + + /** + * Closes the response stream and all related buffers. + * + * No further chunk will be yielded after this method has been called. + */ + public function cancel(): void; + + /** + * Returns info coming from the transport layer. + * + * This method SHOULD NOT throw any ExceptionInterface and SHOULD be non-blocking. + * The returned info is "live": it can be empty and can change from one call to + * another, as the request/response progresses. + * + * The following info MUST be returned: + * - canceled (bool) - true if the response was canceled using ResponseInterface::cancel(), false otherwise + * - error (string|null) - the error message when the transfer was aborted, null otherwise + * - http_code (int) - the last response code or 0 when it is not known yet + * - http_method (string) - the HTTP verb of the last request + * - redirect_count (int) - the number of redirects followed while executing the request + * - redirect_url (string|null) - the resolved location of redirect responses, null otherwise + * - response_headers (array) - an array modelled after the special $http_response_header variable + * - start_time (float) - the time when the request was sent or 0.0 when it's pending + * - url (string) - the last effective URL of the request + * - user_data (mixed) - the value of the "user_data" request option, null if not set + * + * When the "capture_peer_cert_chain" option is true, the "peer_certificate_chain" + * attribute SHOULD list the peer certificates as an array of OpenSSL X.509 resources. + * + * Other info SHOULD be named after curl_getinfo()'s associative return value. + * + * @return mixed An array of all available info, or one of them when $type is + * provided, or null when an unsupported type is requested + */ + public function getInfo(?string $type = null): mixed; +} diff --git a/vendor/symfony/http-client-contracts/ResponseStreamInterface.php b/vendor/symfony/http-client-contracts/ResponseStreamInterface.php new file mode 100644 index 0000000..fa3e5db --- /dev/null +++ b/vendor/symfony/http-client-contracts/ResponseStreamInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +/** + * Yields response chunks, returned by HttpClientInterface::stream(). + * + * @author Nicolas Grekas + * + * @extends \Iterator + */ +interface ResponseStreamInterface extends \Iterator +{ + public function key(): ResponseInterface; + + public function current(): ChunkInterface; +} diff --git a/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php b/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php new file mode 100644 index 0000000..8e28bf5 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php @@ -0,0 +1,198 @@ + $v) { + switch ($k) { + default: + if (!str_starts_with($k, 'HTTP_')) { + continue 2; + } + // no break + case 'SERVER_NAME': + case 'SERVER_PROTOCOL': + case 'REQUEST_URI': + case 'REQUEST_METHOD': + case 'PHP_AUTH_USER': + case 'PHP_AUTH_PW': + $vars[$k] = $v; + } +} + +$json = json_encode($vars, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + +switch ($vars['REQUEST_URI']) { + default: + exit; + + case '/head': + header('Content-Length: '.strlen($json), true); + break; + + case '/': + case '/?a=a&b=b': + case 'http://127.0.0.1:8057/': + case 'http://localhost:8057/': + ob_start('ob_gzhandler'); + break; + + case '/103': + header('HTTP/1.1 103 Early Hints'); + header('Link: ; rel=preload; as=style', false); + header('Link: ; rel=preload; as=script', false); + flush(); + usleep(1000); + echo "HTTP/1.1 200 OK\r\n"; + echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n"; + echo "Content-Length: 13\r\n"; + echo "\r\n"; + echo 'Here the body'; + exit; + + case '/404': + header('Content-Type: application/json', true, 404); + break; + + case '/404-gzipped': + header('Content-Type: text/plain', true, 404); + ob_start('ob_gzhandler'); + @ob_flush(); + flush(); + usleep(300000); + echo 'some text'; + exit; + + case '/301': + if ('Basic Zm9vOmJhcg==' === $vars['HTTP_AUTHORIZATION']) { + header('Location: http://127.0.0.1:8057/302', true, 301); + } + break; + + case '/301/bad-tld': + header('Location: http://foo.example.', true, 301); + break; + + case '/301/invalid': + header('Location: //?foo=bar', true, 301); + break; + + case '/301/proxy': + case 'http://localhost:8057/301/proxy': + case 'http://127.0.0.1:8057/301/proxy': + header('Location: http://localhost:8057/', true, 301); + break; + + case '/302': + if (!isset($vars['HTTP_AUTHORIZATION'])) { + header('Location: http://localhost:8057/', true, 302); + } + break; + + case '/302/relative': + header('Location: ..', true, 302); + break; + + case '/304': + header('Content-Length: 10', true, 304); + echo '12345'; + + return; + + case '/307': + header('Location: http://localhost:8057/post', true, 307); + break; + + case '/length-broken': + header('Content-Length: 1000'); + break; + + case '/post': + $output = json_encode($_POST + ['REQUEST_METHOD' => $vars['REQUEST_METHOD']], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + header('Content-Type: application/json', true); + header('Content-Length: '.strlen($output)); + echo $output; + exit; + + case '/timeout-header': + usleep(300000); + break; + + case '/timeout-body': + echo '<1>'; + @ob_flush(); + flush(); + usleep(500000); + echo '<2>'; + exit; + + case '/timeout-long': + ignore_user_abort(false); + sleep(1); + while (true) { + echo '<1>'; + @ob_flush(); + flush(); + usleep(500); + } + exit; + + case '/chunked': + header('Transfer-Encoding: chunked'); + echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\nesome!\r\n0\r\n\r\n"; + exit; + + case '/chunked-broken': + header('Transfer-Encoding: chunked'); + echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\ne"; + exit; + + case '/gzip-broken': + header('Content-Encoding: gzip'); + echo str_repeat('-', 1000); + exit; + + case '/max-duration': + ignore_user_abort(false); + while (true) { + echo '<1>'; + @ob_flush(); + flush(); + usleep(500); + } + exit; + + case '/json': + header('Content-Type: application/json'); + echo json_encode([ + 'documents' => [ + ['id' => '/json/1'], + ['id' => '/json/2'], + ['id' => '/json/3'], + ], + ]); + exit; + + case '/json/1': + case '/json/2': + case '/json/3': + header('Content-Type: application/json'); + echo json_encode([ + 'title' => $vars['REQUEST_URI'], + ]); + + exit; +} + +header('Content-Type: application/json', true); + +echo $json; diff --git a/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php b/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php new file mode 100644 index 0000000..ec87d06 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php @@ -0,0 +1,1147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A reference test suite for HttpClientInterface implementations. + */ +abstract class HttpClientTestCase extends TestCase +{ + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + + public static function tearDownAfterClass(): void + { + TestHttpServer::stop(8067); + TestHttpServer::stop(8077); + } + + abstract protected function getHttpClient(string $testCase): HttpClientInterface; + + public function testGetRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => ['Foo' => 'baR'], + 'user_data' => $data = new \stdClass(), + ]); + + $this->assertSame([], $response->getInfo('response_headers')); + $this->assertSame($data, $response->getInfo()['user_data']); + $this->assertSame(200, $response->getStatusCode()); + + $info = $response->getInfo(); + $this->assertNull($info['error']); + $this->assertSame(0, $info['redirect_count']); + $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]); + $this->assertSame('Host: localhost:8057', $info['response_headers'][1]); + $this->assertSame('http://localhost:8057/', $info['url']); + + $headers = $response->getHeaders(); + + $this->assertSame('localhost:8057', $headers['host'][0]); + $this->assertSame(['application/json'], $headers['content-type']); + + $body = json_decode($response->getContent(), true); + $this->assertSame($body, $response->toArray()); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertSame('baR', $body['HTTP_FOO']); + + $response = $client->request('GET', 'http://localhost:8057/length-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testHeadRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('HEAD', 'http://localhost:8057/head', [ + 'headers' => ['Foo' => 'baR'], + 'user_data' => $data = new \stdClass(), + 'buffer' => false, + ]); + + $this->assertSame([], $response->getInfo('response_headers')); + $this->assertSame(200, $response->getStatusCode()); + + $info = $response->getInfo(); + $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]); + $this->assertSame('Host: localhost:8057', $info['response_headers'][1]); + + $headers = $response->getHeaders(); + + $this->assertSame('localhost:8057', $headers['host'][0]); + $this->assertSame(['application/json'], $headers['content-type']); + $this->assertTrue(0 < $headers['content-length'][0]); + + $this->assertSame('', $response->getContent()); + } + + public function testNonBufferedGetRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'buffer' => false, + 'headers' => ['Foo' => 'baR'], + ]); + + $body = $response->toArray(); + $this->assertSame('baR', $body['HTTP_FOO']); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testBufferSink() + { + $sink = fopen('php://temp', 'w+'); + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'buffer' => $sink, + 'headers' => ['Foo' => 'baR'], + ]); + + $body = $response->toArray(); + $this->assertSame('baR', $body['HTTP_FOO']); + + rewind($sink); + $sink = stream_get_contents($sink); + $this->assertSame($sink, $response->getContent()); + } + + public function testConditionalBuffering() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + $firstContent = $response->getContent(); + $secondContent = $response->getContent(); + + $this->assertSame($firstContent, $secondContent); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => fn () => false]); + $response->getContent(); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testReentrantBufferCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () use (&$response) { + $response->cancel(); + + return true; + }]); + + $this->assertSame(200, $response->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testThrowingBufferCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () { + throw new \Exception('Boo.'); + }]); + + $this->assertSame(200, $response->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + $this->expectExceptionMessage('Boo'); + $response->getContent(); + } + + public function testUnsupportedOption() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(\InvalidArgumentException::class); + $client->request('GET', 'http://localhost:8057', [ + 'capture_peer_cert' => 1.0, + ]); + } + + public function testHttpVersion() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'http_version' => 1.0, + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('HTTP/1.0 200 OK', $response->getInfo('response_headers')[0]); + + $body = $response->toArray(); + + $this->assertSame('HTTP/1.0', $body['SERVER_PROTOCOL']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('/', $body['REQUEST_URI']); + } + + public function testChunkedEncoding() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/chunked'); + + $this->assertSame(['chunked'], $response->getHeaders()['transfer-encoding']); + $this->assertSame('Symfony is awesome!', $response->getContent()); + + $response = $client->request('GET', 'http://localhost:8057/chunked-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testClientError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + $client->stream($response)->valid(); + + $this->assertSame(404, $response->getInfo('http_code')); + + try { + $response->getHeaders(); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface) { + } + + try { + $response->getContent(); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface) { + } + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']); + $this->assertNotEmpty($response->getContent(false)); + + $response = $client->request('GET', 'http://localhost:8057/404'); + + try { + foreach ($client->stream($response) as $chunk) { + $this->assertTrue($chunk->isFirst()); + } + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface) { + } + } + + public function testIgnoreErrors() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + $this->assertSame(404, $response->getStatusCode()); + } + + public function testDnsError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301/bad-tld'); + + try { + $response->getStatusCode(); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface) { + $this->addToAssertionCount(1); + } + + try { + $response->getStatusCode(); + $this->fail(TransportExceptionInterface::class.' still expected'); + } catch (TransportExceptionInterface) { + $this->addToAssertionCount(1); + } + + $response = $client->request('GET', 'http://localhost:8057/301/bad-tld'); + + try { + foreach ($client->stream($response) as $r => $chunk) { + } + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface) { + $this->addToAssertionCount(1); + } + + $this->assertSame($response, $r); + $this->assertNotNull($chunk->getError()); + + $this->expectException(TransportExceptionInterface::class); + foreach ($client->stream($response) as $chunk) { + } + } + + public function testInlineAuth() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://foo:bar%3Dbar@localhost:8057'); + + $body = $response->toArray(); + + $this->assertSame('foo', $body['PHP_AUTH_USER']); + $this->assertSame('bar=bar', $body['PHP_AUTH_PW']); + } + + public function testBadRequestBody() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(TransportExceptionInterface::class); + + $response = $client->request('POST', 'http://localhost:8057/', [ + 'body' => function () { yield []; }, + ]); + + $response->getStatusCode(); + } + + public function test304() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/304', [ + 'headers' => ['If-Match' => '"abc"'], + 'buffer' => false, + ]); + + $this->assertSame(304, $response->getStatusCode()); + $this->assertSame('', $response->getContent(false)); + } + + /** + * @testWith [[]] + * [["Content-Length: 7"]] + */ + public function testRedirects(array $headers = []) + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('POST', 'http://localhost:8057/301', [ + 'auth_basic' => 'foo:bar', + 'headers' => $headers, + 'body' => function () { + yield 'foo=bar'; + }, + ]); + + $body = $response->toArray(); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('Basic Zm9vOmJhcg==', $body['HTTP_AUTHORIZATION']); + $this->assertSame('http://localhost:8057/', $response->getInfo('url')); + + $this->assertSame(2, $response->getInfo('redirect_count')); + $this->assertNull($response->getInfo('redirect_url')); + + $expected = [ + 'HTTP/1.1 301 Moved Permanently', + 'Location: http://127.0.0.1:8057/302', + 'Content-Type: application/json', + 'HTTP/1.1 302 Found', + 'Location: http://localhost:8057/', + 'Content-Type: application/json', + 'HTTP/1.1 200 OK', + 'Content-Type: application/json', + ]; + + $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) { + return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true) && 'Content-Encoding: gzip' !== $h; + })); + + $this->assertSame($expected, $filteredHeaders); + } + + public function testInvalidRedirect() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301/invalid'); + + $this->assertSame(301, $response->getStatusCode()); + $this->assertSame(['//?foo=bar'], $response->getHeaders(false)['location']); + $this->assertSame(0, $response->getInfo('redirect_count')); + $this->assertNull($response->getInfo('redirect_url')); + + $this->expectException(RedirectionExceptionInterface::class); + $response->getHeaders(); + } + + public function testRelativeRedirects() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/302/relative'); + + $body = $response->toArray(); + + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertNull($response->getInfo('redirect_url')); + + $response = $client->request('GET', 'http://localhost:8057/302/relative', [ + 'max_redirects' => 0, + ]); + + $this->assertSame(302, $response->getStatusCode()); + $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url')); + } + + public function testRedirect307() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/307', [ + 'body' => function () { + yield 'foo=bar'; + }, + 'max_redirects' => 0, + ]); + + $this->assertSame(307, $response->getStatusCode()); + + $response = $client->request('POST', 'http://localhost:8057/307', [ + 'body' => 'foo=bar', + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testMaxRedirects() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301', [ + 'max_redirects' => 1, + 'auth_basic' => 'foo:bar', + ]); + + try { + $response->getHeaders(); + $this->fail(RedirectionExceptionInterface::class.' expected'); + } catch (RedirectionExceptionInterface) { + } + + $this->assertSame(302, $response->getStatusCode()); + $this->assertSame(1, $response->getInfo('redirect_count')); + $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url')); + + $expected = [ + 'HTTP/1.1 301 Moved Permanently', + 'Location: http://127.0.0.1:8057/302', + 'Content-Type: application/json', + 'HTTP/1.1 302 Found', + 'Location: http://localhost:8057/', + 'Content-Type: application/json', + ]; + + $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) { + return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true); + })); + + $this->assertSame($expected, $filteredHeaders); + } + + public function testStream() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057'); + $chunks = $client->stream($response); + $result = []; + + foreach ($chunks as $r => $chunk) { + if ($chunk->isTimeout()) { + $result[] = 't'; + } elseif ($chunk->isLast()) { + $result[] = 'l'; + } elseif ($chunk->isFirst()) { + $result[] = 'f'; + } + } + + $this->assertSame($response, $r); + $this->assertSame(['f', 'l'], $result); + + $chunk = null; + $i = 0; + + foreach ($client->stream($response) as $chunk) { + ++$i; + } + + $this->assertSame(1, $i); + $this->assertTrue($chunk->isLast()); + } + + public function testAddToStream() + { + $client = $this->getHttpClient(__FUNCTION__); + + $r1 = $client->request('GET', 'http://localhost:8057'); + + $completed = []; + + $pool = [$r1]; + + while ($pool) { + $chunks = $client->stream($pool); + $pool = []; + + foreach ($chunks as $r => $chunk) { + if (!$chunk->isLast()) { + continue; + } + + if ($r1 === $r) { + $r2 = $client->request('GET', 'http://localhost:8057'); + $pool[] = $r2; + } + + $completed[] = $r; + } + } + + $this->assertSame([$r1, $r2], $completed); + } + + public function testCompleteTypeError() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(\TypeError::class); + $client->stream(123); + } + + public function testOnProgress() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'headers' => ['Content-Length' => 14], + 'body' => 'foo=0123456789', + 'on_progress' => function (...$state) use (&$steps) { $steps[] = $state; }, + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + $this->assertSame([0, 0], \array_slice($steps[0], 0, 2)); + $lastStep = \array_slice($steps, -1)[0]; + $this->assertSame([57, 57], \array_slice($lastStep, 0, 2)); + $this->assertSame('http://localhost:8057/post', $steps[0][2]['url']); + } + + public function testPostJson() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'json' => ['foo' => 'bar'], + ]); + + $body = $response->toArray(); + + $this->assertStringContainsString('json', $body['content-type']); + unset($body['content-type']); + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testPostArray() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => ['foo' => 'bar'], + ]); + + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $response->toArray()); + } + + public function testPostResource() + { + $client = $this->getHttpClient(__FUNCTION__); + + $h = fopen('php://temp', 'w+'); + fwrite($h, 'foo=0123456789'); + rewind($h); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => $h, + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testPostCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => function () { + yield 'foo'; + yield ''; + yield '='; + yield '0123456789'; + }, + ]); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $response->toArray()); + } + + public function testCancel() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header'); + + $response->cancel(); + $this->expectException(TransportExceptionInterface::class); + $response->getHeaders(); + } + + public function testInfoOnCanceledResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057/timeout-header'); + + $this->assertFalse($response->getInfo('canceled')); + $response->cancel(); + $this->assertTrue($response->getInfo('canceled')); + } + + public function testCancelInStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + foreach ($client->stream($response) as $chunk) { + $response->cancel(); + } + + $this->expectException(TransportExceptionInterface::class); + + foreach ($client->stream($response) as $chunk) { + } + } + + public function testOnProgressCancel() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'on_progress' => function ($dlNow) { + if (0 < $dlNow) { + throw new \Exception('Aborting the request.'); + } + }, + ]); + + try { + foreach ($client->stream([$response]) as $chunk) { + } + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + $this->assertSame('Aborting the request.', $e->getPrevious()->getMessage()); + } + + $this->assertNotNull($response->getInfo('error')); + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testOnProgressError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'on_progress' => function ($dlNow) { + if (0 < $dlNow) { + throw new \Error('BUG.'); + } + }, + ]); + + try { + foreach ($client->stream([$response]) as $chunk) { + } + $this->fail('Error expected'); + } catch (\Error $e) { + $this->assertSame('BUG.', $e->getMessage()); + } + + $this->assertNotNull($response->getInfo('error')); + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testResolve() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://symfony.com:8057/', [ + 'resolve' => ['symfony.com' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(200, $client->request('GET', 'http://symfony.com:8057/')->getStatusCode()); + + $response = null; + $this->expectException(TransportExceptionInterface::class); + $client->request('GET', 'http://symfony.com:8057/', ['timeout' => 1]); + } + + public function testIdnResolve() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://0-------------------------------------------------------------0.com:8057/', [ + 'resolve' => ['0-------------------------------------------------------------0.com' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + + $response = $client->request('GET', 'http://Bücher.example:8057/', [ + 'resolve' => ['xn--bcher-kva.example' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testNotATimeout() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header', [ + 'timeout' => 0.9, + ]); + sleep(1); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testTimeoutOnAccess() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header', [ + 'timeout' => 0.1, + ]); + + $this->expectException(TransportExceptionInterface::class); + $response->getHeaders(); + } + + public function testTimeoutIsNotAFatalError() + { + usleep(300000); // wait for the previous test to release the server + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'timeout' => 0.25, + ]); + + try { + $response->getContent(); + $this->fail(TimeoutExceptionInterface::class.' expected'); + } catch (TimeoutExceptionInterface $e) { + } + + for ($i = 0; $i < 10; ++$i) { + try { + $this->assertSame('<1><2>', $response->getContent()); + break; + } catch (TimeoutExceptionInterface $e) { + } + } + + if (10 === $i) { + throw $e; + } + } + + public function testTimeoutOnStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + + $this->assertSame(200, $response->getStatusCode()); + $chunks = $client->stream([$response], 0.2); + + $result = []; + + foreach ($chunks as $r => $chunk) { + if ($chunk->isTimeout()) { + $result[] = 't'; + } else { + $result[] = $chunk->getContent(); + } + } + + $this->assertSame(['<1>', 't'], $result); + + $chunks = $client->stream([$response]); + + foreach ($chunks as $r => $chunk) { + $this->assertSame('<2>', $chunk->getContent()); + $this->assertSame('<1><2>', $r->getContent()); + + return; + } + + $this->fail('The response should have completed'); + } + + public function testUncheckedTimeoutThrows() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + $chunks = $client->stream([$response], 0.1); + + $this->expectException(TransportExceptionInterface::class); + + foreach ($chunks as $r => $chunk) { + } + } + + public function testTimeoutWithActiveConcurrentStream() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $streamingResponse = $client->request('GET', 'http://localhost:8067/max-duration'); + $blockingResponse = $client->request('GET', 'http://localhost:8077/timeout-body', [ + 'timeout' => 0.25, + ]); + + $this->assertSame(200, $streamingResponse->getStatusCode()); + $this->assertSame(200, $blockingResponse->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + + try { + $blockingResponse->getContent(); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testTimeoutOnInitialize() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $start = microtime(true); + $responses = []; + + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + + try { + foreach ($responses as $response) { + try { + $response->getContent(); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface) { + } + } + $responses = []; + + $duration = microtime(true) - $start; + + $this->assertLessThan(1.0, $duration); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testTimeoutOnDestruct() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $start = microtime(true); + $responses = []; + + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + + try { + while ($response = array_shift($responses)) { + try { + unset($response); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface) { + } + } + + $duration = microtime(true) - $start; + + $this->assertLessThan(1.0, $duration); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + $start = microtime(true); + $client->request('GET', 'http://localhost:8057/timeout-long'); + $client = null; + $duration = microtime(true) - $start; + + $this->assertGreaterThan(1, $duration); + $this->assertLessThan(4, $duration); + } + + public function testGetContentAfterDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + try { + $client->request('GET', 'http://localhost:8057/404'); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + $this->assertSame('GET', $e->getResponse()->toArray(false)['REQUEST_METHOD']); + } + } + + public function testGetEncodedContentAfterDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + try { + $client->request('GET', 'http://localhost:8057/404-gzipped'); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + $this->assertSame('some text', $e->getResponse()->getContent(false)); + } + } + + public function testProxy() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://localhost:8057', + ]); + + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://foo:b%3Dar@localhost:8057', + ]); + + $body = $response->toArray(); + $this->assertSame('Basic Zm9vOmI9YXI=', $body['HTTP_PROXY_AUTHORIZATION']); + + $_SERVER['http_proxy'] = 'http://localhost:8057'; + try { + $response = $client->request('GET', 'http://localhost:8057/'); + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + } finally { + unset($_SERVER['http_proxy']); + } + + $response = $client->request('GET', 'http://localhost:8057/301/proxy', [ + 'proxy' => 'http://localhost:8057', + ]); + + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + } + + public function testNoProxy() + { + putenv('no_proxy='.$_SERVER['no_proxy'] = 'example.com, localhost'); + + try { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://localhost:8057', + ]); + + $body = $response->toArray(); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + } finally { + putenv('no_proxy'); + unset($_SERVER['no_proxy']); + } + } + + /** + * @requires extension zlib + */ + public function testAutoEncodingRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + + $this->assertSame(200, $response->getStatusCode()); + + $headers = $response->getHeaders(); + + $this->assertSame(['Accept-Encoding'], $headers['vary']); + $this->assertStringContainsString('gzip', $headers['content-encoding'][0]); + + $body = $response->toArray(); + + $this->assertStringContainsString('gzip', $body['HTTP_ACCEPT_ENCODING']); + } + + public function testBaseUri() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', '../404', [ + 'base_uri' => 'http://localhost:8057/abc/', + ]); + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']); + } + + public function testQuery() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/?a=a', [ + 'query' => ['b' => 'b'], + ]); + + $body = $response->toArray(); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('/?a=a&b=b', $body['REQUEST_URI']); + } + + public function testInformationalResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $this->assertSame('Here the body', $response->getContent()); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testInformationalResponseStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $chunks = []; + foreach ($client->stream($response) as $chunk) { + $chunks[] = $chunk; + } + + $this->assertSame(103, $chunks[0]->getInformationalStatus()[0]); + $this->assertSame(['; rel=preload; as=style', '; rel=preload; as=script'], $chunks[0]->getInformationalStatus()[1]['link']); + $this->assertTrue($chunks[1]->isFirst()); + $this->assertSame('Here the body', $chunks[2]->getContent()); + $this->assertTrue($chunks[3]->isLast()); + $this->assertNull($chunks[3]->getInformationalStatus()); + + $this->assertSame(['date', 'content-length'], array_keys($response->getHeaders())); + $this->assertContains('Link: ; rel=preload; as=style', $response->getInfo('response_headers')); + } + + /** + * @requires extension zlib + */ + public function testUserlandEncodingRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => ['Accept-Encoding' => 'gzip'], + ]); + + $headers = $response->getHeaders(); + + $this->assertSame(['Accept-Encoding'], $headers['vary']); + $this->assertStringContainsString('gzip', $headers['content-encoding'][0]); + + $body = $response->getContent(); + $this->assertSame("\x1F", $body[0]); + + $body = json_decode(gzdecode($body), true); + $this->assertSame('gzip', $body['HTTP_ACCEPT_ENCODING']); + } + + /** + * @requires extension zlib + */ + public function testGzipBroken() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/gzip-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testMaxDuration() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/max-duration', [ + 'max_duration' => 0.1, + ]); + + $start = microtime(true); + + try { + $response->getContent(); + } catch (TransportExceptionInterface) { + $this->addToAssertionCount(1); + } + + $duration = microtime(true) - $start; + + $this->assertLessThan(10, $duration); + } + + public function testWithOptions() + { + $client = $this->getHttpClient(__FUNCTION__); + $client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']); + + $this->assertNotSame($client, $client2); + $this->assertSame($client::class, $client2::class); + + $response = $client2->request('GET', '/'); + $this->assertSame(200, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-client-contracts/Test/TestHttpServer.php b/vendor/symfony/http-client-contracts/Test/TestHttpServer.php new file mode 100644 index 0000000..2a27847 --- /dev/null +++ b/vendor/symfony/http-client-contracts/Test/TestHttpServer.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Test; + +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Process; + +class TestHttpServer +{ + private static array $process = []; + + /** + * @param string|null $workingDirectory + */ + public static function start(int $port = 8057/* , string $workingDirectory = null */): Process + { + $workingDirectory = \func_get_args()[1] ?? __DIR__.'/Fixtures/web'; + + if (isset(self::$process[$port])) { + self::$process[$port]->stop(); + } else { + register_shutdown_function(static function () use ($port) { + self::$process[$port]->stop(); + }); + } + + $finder = new PhpExecutableFinder(); + $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:'.$port])); + $process->setWorkingDirectory($workingDirectory); + $process->start(); + self::$process[$port] = $process; + + do { + usleep(50000); + } while (!@fopen('http://127.0.0.1:'.$port, 'r')); + + return $process; + } + + public static function stop(int $port = 8057) + { + if (isset(self::$process[$port])) { + self::$process[$port]->stop(); + } + } +} diff --git a/vendor/symfony/http-client-contracts/composer.json b/vendor/symfony/http-client-contracts/composer.json new file mode 100644 index 0000000..efb146e --- /dev/null +++ b/vendor/symfony/http-client-contracts/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/http-client-contracts", + "type": "library", + "description": "Generic abstractions related to HTTP clients", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/http-client/AmpHttpClient.php b/vendor/symfony/http-client/AmpHttpClient.php new file mode 100644 index 0000000..f93aaa8 --- /dev/null +++ b/vendor/symfony/http-client/AmpHttpClient.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Amp\CancelledException; +use Amp\Http\Client\DelegateHttpClient; +use Amp\Http\Client\InterceptedHttpClient; +use Amp\Http\Client\PooledHttpClient; +use Amp\Http\Client\Request; +use Amp\Http\Tunnel\Http1TunnelConnector; +use Amp\Promise; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\AmpClientState; +use Symfony\Component\HttpClient\Response\AmpResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(DelegateHttpClient::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\AmpHttpClient" as the "amphp/http-client" package is not installed. Try running "composer require amphp/http-client:^4.2.1".'); +} + +if (!interface_exists(Promise::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\AmpHttpClient" as the installed "amphp/http-client" is not compatible with this version of "symfony/http-client". Try downgrading "amphp/http-client" to "^4.2.1".'); +} + +/** + * A portable implementation of the HttpClientInterface contracts based on Amp's HTTP client. + * + * @author Nicolas Grekas + */ +final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + use LoggerAwareTrait; + + public const OPTIONS_DEFAULTS = HttpClientInterface::OPTIONS_DEFAULTS + [ + 'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, + ]; + + private array $defaultOptions = self::OPTIONS_DEFAULTS; + private static array $emptyDefaults = self::OPTIONS_DEFAULTS; + private AmpClientState $multi; + + /** + * @param array $defaultOptions Default requests' options + * @param callable|null $clientConfigurator A callable that builds a {@see DelegateHttpClient} from a {@see PooledHttpClient}; + * passing null builds an {@see InterceptedHttpClient} with 2 retries on failures + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], ?callable $clientConfigurator = null, int $maxHostConnections = 6, int $maxPendingPushes = 50) + { + $this->defaultOptions['buffer'] ??= self::shouldBuffer(...); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new AmpClientState($clientConfigurator, $maxHostConnections, $maxPendingPushes, $this->logger); + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + + $options['proxy'] = self::getProxy($options['proxy'], $url, $options['no_proxy']); + + if (null !== $options['proxy'] && !class_exists(Http1TunnelConnector::class)) { + throw new \LogicException('You cannot use the "proxy" option as the "amphp/http-tunnel" package is not installed. Try running "composer require amphp/http-tunnel".'); + } + + if ($options['bindto']) { + if (str_starts_with($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (str_starts_with($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } + } + + if (('' !== $options['body'] || 'POST' === $method || isset($options['normalized_headers']['content-length'])) && !isset($options['normalized_headers']['content-type'])) { + $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; + } + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient (Amp)'; + } + + if (0 < $options['max_duration']) { + $options['timeout'] = min($options['max_duration'], $options['timeout']); + } + + if ($options['resolve']) { + $this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache; + } + + if ($options['peer_fingerprint'] && !isset($options['peer_fingerprint']['pin-sha256'])) { + throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.'); + } + + $request = new Request(implode('', $url), $method); + + if ($options['http_version']) { + $request->setProtocolVersions(match ((float) $options['http_version']) { + 1.0 => ['1.0'], + 1.1 => ['1.1', '1.0'], + default => ['2', '1.1', '1.0'], + }); + } + + foreach ($options['headers'] as $v) { + $h = explode(': ', $v, 2); + $request->addHeader($h[0], $h[1]); + } + + $request->setTcpConnectTimeout(1000 * $options['timeout']); + $request->setTlsHandshakeTimeout(1000 * $options['timeout']); + $request->setTransferTimeout(1000 * $options['max_duration']); + if (method_exists($request, 'setInactivityTimeout')) { + $request->setInactivityTimeout(0); + } + + if ('' !== $request->getUri()->getUserInfo() && !$request->hasHeader('authorization')) { + $auth = explode(':', $request->getUri()->getUserInfo(), 2); + $auth = array_map('rawurldecode', $auth) + [1 => '']; + $request->setHeader('Authorization', 'Basic '.base64_encode(implode(':', $auth))); + } + + return new AmpResponse($this->multi, $request, $options, $this->logger); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof AmpResponse) { + $responses = [$responses]; + } + + return new ResponseStream(AmpResponse::stream($responses, $timeout)); + } + + public function reset(): void + { + $this->multi->dnsCache = []; + + foreach ($this->multi->pushedResponses as $authority => $pushedResponses) { + foreach ($pushedResponses as [$pushedUrl, $pushDeferred]) { + $pushDeferred->fail(new CancelledException()); + + $this->logger?->debug(sprintf('Unused pushed response: "%s"', $pushedUrl)); + } + } + + $this->multi->pushedResponses = []; + } +} diff --git a/vendor/symfony/http-client/AsyncDecoratorTrait.php b/vendor/symfony/http-client/AsyncDecoratorTrait.php new file mode 100644 index 0000000..785c34a --- /dev/null +++ b/vendor/symfony/http-client/AsyncDecoratorTrait.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; + +/** + * Eases with processing responses while streaming them. + * + * @author Nicolas Grekas + */ +trait AsyncDecoratorTrait +{ + use DecoratorTrait; + + /** + * @return AsyncResponse + */ + abstract public function request(string $method, string $url, array $options = []): ResponseInterface; + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof AsyncResponse) { + $responses = [$responses]; + } + + return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class)); + } +} diff --git a/vendor/symfony/http-client/CHANGELOG.md b/vendor/symfony/http-client/CHANGELOG.md new file mode 100644 index 0000000..0ed7e90 --- /dev/null +++ b/vendor/symfony/http-client/CHANGELOG.md @@ -0,0 +1,99 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `HttpOptions::setHeader()` to add or replace a single header + * Allow mocking `start_time` info in `MockResponse` + * Add `MockResponse::fromFile()` and `JsonMockResponse::fromFile()` methods to help using fixtures files + * Add `ThrottlingHttpClient` to enable limiting the number of requests within a certain period + * Deprecate the `setLogger()` methods of the `NoPrivateNetworkHttpClient`, `TraceableHttpClient` and `ScopingHttpClient` classes, configure the logger of the wrapped clients directly instead + +7.0 +--- + + * Remove implementing `Http\Message\RequestFactory` from `HttplugClient` + +6.4 +--- + + * Add `HarFileResponseFactory` testing utility, allow to replay responses from `.har` files + * Add `max_retries` option to `RetryableHttpClient` to adjust the retry logic on a per request level + * Add `PingWehookMessage` and `PingWebhookMessageHandler` + * Enable using EventSourceHttpClient::connect() for both GET and POST + +6.3 +--- + + * Add option `crypto_method` to set the minimum TLS version and make it default to v1.2 + * Add `UriTemplateHttpClient` to use URI templates as specified in the RFC 6570 + * Add `ServerSentEvent::getArrayData()` to get the Server-Sent Event's data decoded as an array when it's a JSON payload + * Allow array of urls as `base_uri` option value in `RetryableHttpClient` to retry on a new url each time + * Add `JsonMockResponse`, a `MockResponse` shortcut that automatically encodes the passed body to JSON and sets the content type to `application/json` by default + * Support file uploads by nesting resource streams in option "body" + +6.2 +--- + + * Make `HttplugClient` implement `Psr\Http\Message\RequestFactoryInterface`, `StreamFactoryInterface` and `UriFactoryInterface` + * Deprecate implementing `Http\Message\RequestFactory`, `StreamFactory` and `UriFactory` on `HttplugClient` + * Add `withOptions()` to `HttplugClient` and `Psr18Client` + +6.1 +--- + + * Allow yielding `Exception` from MockResponse's `$body` to mock transport errors + * Remove credentials from requests redirected to same host but different port + +5.4 +--- + + * Add `MockHttpClient::setResponseFactory()` method to be able to set response factory after client creating + +5.3 +--- + + * Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4 + * Add `DecoratorTrait` to ease writing simple decorators + +5.2.0 +----- + + * added `AsyncDecoratorTrait` to ease processing responses without breaking async + * added support for pausing responses with a new `pause_handler` callable exposed as an info item + * added `StreamableInterface` to ease turning responses into PHP streams + * added `MockResponse::getRequestMethod()` and `getRequestUrl()` to allow inspecting which request has been sent + * added `EventSourceHttpClient` a Server-Sent events stream implementing the [EventSource specification](https://www.w3.org/TR/eventsource/#eventsource) + * added option "extra.curl" to allow setting additional curl options in `CurlHttpClient` + * added `RetryableHttpClient` to automatically retry failed HTTP requests. + * added `extra.trace_content` option to `TraceableHttpClient` to prevent it from keeping the content in memory + +5.1.0 +----- + + * added `NoPrivateNetworkHttpClient` decorator + * added `AmpHttpClient`, a portable HTTP/2 implementation based on Amp + * added `LoggerAwareInterface` to `ScopingHttpClient` and `TraceableHttpClient` + * made `HttpClient::create()` return an `AmpHttpClient` when `amphp/http-client` is found but curl is not or too old + +4.4.0 +----- + + * added `canceled` to `ResponseInterface::getInfo()` + * added `HttpClient::createForBaseUri()` + * added `HttplugClient` with support for sync and async requests + * added `max_duration` option + * added support for NTLM authentication + * added `StreamWrapper` to cast any `ResponseInterface` instances to PHP streams. + * added `$response->toStream()` to cast responses to regular PHP streams + * made `Psr18Client` implement relevant PSR-17 factories and have streaming responses + * added `TraceableHttpClient`, `HttpClientDataCollector` and `HttpClientPass` to integrate with the web profiler + * allow enabling buffering conditionally with a Closure + * allow option "buffer" to be a stream resource + * allow arbitrary values for the "json" option + +4.3.0 +----- + + * added the component diff --git a/vendor/symfony/http-client/CachingHttpClient.php b/vendor/symfony/http-client/CachingHttpClient.php new file mode 100644 index 0000000..fd6a18c --- /dev/null +++ b/vendor/symfony/http-client/CachingHttpClient.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpClientKernel; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Adds caching on top of an HTTP client. + * + * The implementation buffers responses in memory and doesn't stream directly from the network. + * You can disable/enable this layer by setting option "no_cache" under "extra" to true/false. + * By default, caching is enabled unless the "buffer" option is set to false. + * + * @author Nicolas Grekas + */ +class CachingHttpClient implements HttpClientInterface, ResetInterface +{ + use HttpClientTrait; + + private HttpClientInterface $client; + private HttpCache $cache; + private array $defaultOptions = self::OPTIONS_DEFAULTS; + + public function __construct(HttpClientInterface $client, StoreInterface $store, array $defaultOptions = []) + { + if (!class_exists(HttpClientKernel::class)) { + throw new \LogicException(sprintf('Using "%s" requires that the HttpKernel component version 4.3 or higher is installed, try running "composer require symfony/http-kernel:^5.4".', __CLASS__)); + } + + $this->client = $client; + $kernel = new HttpClientKernel($client); + $this->cache = new HttpCache($kernel, $store, null, $defaultOptions); + + unset($defaultOptions['debug']); + unset($defaultOptions['default_ttl']); + unset($defaultOptions['private_headers']); + unset($defaultOptions['skip_response_headers']); + unset($defaultOptions['allow_reload']); + unset($defaultOptions['allow_revalidate']); + unset($defaultOptions['stale_while_revalidate']); + unset($defaultOptions['stale_if_error']); + unset($defaultOptions['trace_level']); + unset($defaultOptions['trace_header']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); + $url = implode('', $url); + + if (!empty($options['body']) || !empty($options['extra']['no_cache']) || !\in_array($method, ['GET', 'HEAD', 'OPTIONS'])) { + return $this->client->request($method, $url, $options); + } + + $request = Request::create($url, $method); + $request->attributes->set('http_client_options', $options); + + foreach ($options['normalized_headers'] as $name => $values) { + if ('cookie' !== $name) { + foreach ($values as $value) { + $request->headers->set($name, substr($value, 2 + \strlen($name)), false); + } + + continue; + } + + foreach ($values as $cookies) { + foreach (explode('; ', substr($cookies, \strlen('Cookie: '))) as $cookie) { + if ('' !== $cookie) { + $cookie = explode('=', $cookie, 2); + $request->cookies->set($cookie[0], $cookie[1] ?? ''); + } + } + } + } + + $response = $this->cache->handle($request); + $response = new MockResponse($response->getContent(), [ + 'http_code' => $response->getStatusCode(), + 'response_headers' => $response->headers->allPreserveCase(), + ]); + + return MockResponse::fromRequest($method, $url, $options, $response); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof ResponseInterface) { + $responses = [$responses]; + } + + $mockResponses = []; + $clientResponses = []; + + foreach ($responses as $response) { + if ($response instanceof MockResponse) { + $mockResponses[] = $response; + } else { + $clientResponses[] = $response; + } + } + + if (!$mockResponses) { + return $this->client->stream($clientResponses, $timeout); + } + + if (!$clientResponses) { + return new ResponseStream(MockResponse::stream($mockResponses, $timeout)); + } + + return new ResponseStream((function () use ($mockResponses, $clientResponses, $timeout) { + yield from MockResponse::stream($mockResponses, $timeout); + yield $this->client->stream($clientResponses, $timeout); + })()); + } + + public function reset(): void + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/vendor/symfony/http-client/Chunk/DataChunk.php b/vendor/symfony/http-client/Chunk/DataChunk.php new file mode 100644 index 0000000..70f1b13 --- /dev/null +++ b/vendor/symfony/http-client/Chunk/DataChunk.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class DataChunk implements ChunkInterface +{ + public function __construct( + private int $offset = 0, + private string $content = '', + ) { + } + + public function isTimeout(): bool + { + return false; + } + + public function isFirst(): bool + { + return false; + } + + public function isLast(): bool + { + return false; + } + + public function getInformationalStatus(): ?array + { + return null; + } + + public function getContent(): string + { + return $this->content; + } + + public function getOffset(): int + { + return $this->offset; + } + + public function getError(): ?string + { + return null; + } +} diff --git a/vendor/symfony/http-client/Chunk/ErrorChunk.php b/vendor/symfony/http-client/Chunk/ErrorChunk.php new file mode 100644 index 0000000..819056a --- /dev/null +++ b/vendor/symfony/http-client/Chunk/ErrorChunk.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Component\HttpClient\Exception\TimeoutException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class ErrorChunk implements ChunkInterface +{ + private bool $didThrow = false; + private string $errorMessage; + private ?\Throwable $error = null; + + public function __construct( + private int $offset, + \Throwable|string $error, + ) { + if (\is_string($error)) { + $this->errorMessage = $error; + } else { + $this->error = $error; + $this->errorMessage = $error->getMessage(); + } + } + + public function isTimeout(): bool + { + $this->didThrow = true; + + if (null !== $this->error) { + throw new TransportException($this->errorMessage, 0, $this->error); + } + + return true; + } + + public function isFirst(): bool + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + public function isLast(): bool + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + public function getInformationalStatus(): ?array + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + public function getContent(): string + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + public function getOffset(): int + { + return $this->offset; + } + + public function getError(): ?string + { + return $this->errorMessage; + } + + public function didThrow(?bool $didThrow = null): bool + { + if (null !== $didThrow && $this->didThrow !== $didThrow) { + return !$this->didThrow = $didThrow; + } + + return $this->didThrow; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (!$this->didThrow) { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + } +} diff --git a/vendor/symfony/http-client/Chunk/FirstChunk.php b/vendor/symfony/http-client/Chunk/FirstChunk.php new file mode 100644 index 0000000..f6ba8b8 --- /dev/null +++ b/vendor/symfony/http-client/Chunk/FirstChunk.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class FirstChunk extends DataChunk +{ + public function isFirst(): bool + { + return true; + } +} diff --git a/vendor/symfony/http-client/Chunk/InformationalChunk.php b/vendor/symfony/http-client/Chunk/InformationalChunk.php new file mode 100644 index 0000000..ca9eb47 --- /dev/null +++ b/vendor/symfony/http-client/Chunk/InformationalChunk.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class InformationalChunk extends DataChunk +{ + private array $status; + + public function __construct(int $statusCode, array $headers) + { + $this->status = [$statusCode, $headers]; + + parent::__construct(); + } + + public function getInformationalStatus(): ?array + { + return $this->status; + } +} diff --git a/vendor/symfony/http-client/Chunk/LastChunk.php b/vendor/symfony/http-client/Chunk/LastChunk.php new file mode 100644 index 0000000..a64d123 --- /dev/null +++ b/vendor/symfony/http-client/Chunk/LastChunk.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class LastChunk extends DataChunk +{ + public function isLast(): bool + { + return true; + } +} diff --git a/vendor/symfony/http-client/Chunk/ServerSentEvent.php b/vendor/symfony/http-client/Chunk/ServerSentEvent.php new file mode 100644 index 0000000..23b9cfd --- /dev/null +++ b/vendor/symfony/http-client/Chunk/ServerSentEvent.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Antoine Bluchet + * @author Nicolas Grekas + */ +final class ServerSentEvent extends DataChunk implements ChunkInterface +{ + private string $data = ''; + private string $id = ''; + private string $type = 'message'; + private float $retry = 0; + private ?array $jsonData = null; + + public function __construct(string $content) + { + parent::__construct(-1, $content); + + // remove BOM + if (str_starts_with($content, "\xEF\xBB\xBF")) { + $content = substr($content, 3); + } + + foreach (preg_split("/(?:\r\n|[\r\n])/", $content) as $line) { + if (0 === $i = strpos($line, ':')) { + continue; + } + + $i = false === $i ? \strlen($line) : $i; + $field = substr($line, 0, $i); + $i += 1 + (' ' === ($line[1 + $i] ?? '')); + + switch ($field) { + case 'id': + $this->id = substr($line, $i); + break; + case 'event': + $this->type = substr($line, $i); + break; + case 'data': + $this->data .= ('' === $this->data ? '' : "\n").substr($line, $i); + break; + case 'retry': + $retry = substr($line, $i); + + if ('' !== $retry && \strlen($retry) === strspn($retry, '0123456789')) { + $this->retry = $retry / 1000.0; + } + + break; + } + } + } + + public function getId(): string + { + return $this->id; + } + + public function getType(): string + { + return $this->type; + } + + public function getData(): string + { + return $this->data; + } + + public function getRetry(): float + { + return $this->retry; + } + + /** + * Gets the SSE data decoded as an array when it's a JSON payload. + */ + public function getArrayData(): array + { + if (null !== $this->jsonData) { + return $this->jsonData; + } + + if ('' === $this->data) { + throw new JsonException(sprintf('Server-Sent Event%s data is empty.', '' !== $this->id ? sprintf(' "%s"', $this->id) : '')); + } + + try { + $jsonData = json_decode($this->data, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new JsonException(sprintf('Decoding Server-Sent Event%s failed: ', '' !== $this->id ? sprintf(' "%s"', $this->id) : '').$e->getMessage(), $e->getCode()); + } + + if (!\is_array($jsonData)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned in Server-Sent Event%s.', get_debug_type($jsonData), '' !== $this->id ? sprintf(' "%s"', $this->id) : '')); + } + + return $this->jsonData = $jsonData; + } +} diff --git a/vendor/symfony/http-client/CurlHttpClient.php b/vendor/symfony/http-client/CurlHttpClient.php new file mode 100644 index 0000000..c30b78a --- /dev/null +++ b/vendor/symfony/http-client/CurlHttpClient.php @@ -0,0 +1,568 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\CurlClientState; +use Symfony\Component\HttpClient\Internal\PushedResponse; +use Symfony\Component\HttpClient\Response\CurlResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A performant implementation of the HttpClientInterface contracts based on the curl extension. + * + * This provides fully concurrent HTTP requests, with transparent + * HTTP/2 push when a curl version that supports it is installed. + * + * @author Nicolas Grekas + */ +final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + + public const OPTIONS_DEFAULTS = HttpClientInterface::OPTIONS_DEFAULTS + [ + 'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, + ]; + + private array $defaultOptions = self::OPTIONS_DEFAULTS + [ + 'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the + // password as the second one; or string like username:password - enabling NTLM auth + 'extra' => [ + 'curl' => [], // A list of extra curl options indexed by their corresponding CURLOPT_* + ], + ]; + private static array $emptyDefaults = self::OPTIONS_DEFAULTS + ['auth_ntlm' => null]; + + private ?LoggerInterface $logger = null; + + private int $maxHostConnections; + private int $maxPendingPushes; + + /** + * An internal object to share state between the client and its responses. + */ + private CurlClientState $multi; + + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 0) + { + if (!\extension_loaded('curl')) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.'); + } + + $this->maxHostConnections = $maxHostConnections; + $this->maxPendingPushes = $maxPendingPushes; + + $this->defaultOptions['buffer'] ??= self::shouldBuffer(...); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + } + + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + if (isset($this->multi)) { + $this->multi->logger = $logger; + } + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $multi = $this->ensureState(); + + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + $scheme = $url['scheme']; + $authority = $url['authority']; + $host = parse_url($authority, \PHP_URL_HOST); + $port = parse_url($authority, \PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443); + $proxy = self::getProxyUrl($options['proxy'], $url); + $url = implode('', $url); + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient (Curl)'; + } + + $curlopts = [ + \CURLOPT_URL => $url, + \CURLOPT_TCP_NODELAY => true, + \CURLOPT_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS, + \CURLOPT_REDIR_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS, + \CURLOPT_FOLLOWLOCATION => true, + \CURLOPT_MAXREDIRS => max(0, $options['max_redirects']), + \CURLOPT_COOKIEFILE => '', // Keep track of cookies during redirects + \CURLOPT_TIMEOUT => 0, + \CURLOPT_PROXY => $proxy, + \CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '', + \CURLOPT_SSL_VERIFYPEER => $options['verify_peer'], + \CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0, + \CURLOPT_CAINFO => $options['cafile'], + \CURLOPT_CAPATH => $options['capath'], + \CURLOPT_SSL_CIPHER_LIST => $options['ciphers'], + \CURLOPT_SSLCERT => $options['local_cert'], + \CURLOPT_SSLKEY => $options['local_pk'], + \CURLOPT_KEYPASSWD => $options['passphrase'], + \CURLOPT_CERTINFO => $options['capture_peer_cert_chain'], + \CURLOPT_SSLVERSION => match ($options['crypto_method']) { + \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT => \CURL_SSLVERSION_TLSv1_3, + \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT => \CURL_SSLVERSION_TLSv1_2, + \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT => \CURL_SSLVERSION_TLSv1_1, + \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT => \CURL_SSLVERSION_TLSv1_0, + }, + ]; + + if (1.0 === (float) $options['http_version']) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } elseif (1.1 === (float) $options['http_version']) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif (\defined('CURL_VERSION_HTTP2') && (\CURL_VERSION_HTTP2 & CurlClientState::$curlVersion['features']) && ('https:' === $scheme || 2.0 === (float) $options['http_version'])) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } + + if (isset($options['auth_ntlm'])) { + $curlopts[\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + + if (\is_array($options['auth_ntlm'])) { + $count = \count($options['auth_ntlm']); + if ($count <= 0 || $count > 2) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must contain 1 or 2 elements, %d given.', $count)); + } + + $options['auth_ntlm'] = implode(':', $options['auth_ntlm']); + } + + if (!\is_string($options['auth_ntlm'])) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must be a string or an array, "%s" given.', get_debug_type($options['auth_ntlm']))); + } + + $curlopts[\CURLOPT_USERPWD] = $options['auth_ntlm']; + } + + if (!\ZEND_THREAD_SAFE) { + $curlopts[\CURLOPT_DNS_USE_GLOBAL_CACHE] = false; + } + + if (\defined('CURLOPT_HEADEROPT') && \defined('CURLHEADER_SEPARATE')) { + $curlopts[\CURLOPT_HEADEROPT] = \CURLHEADER_SEPARATE; + } + + // curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map + if (isset($multi->dnsCache->hostnames[$host])) { + $options['resolve'] += [$host => $multi->dnsCache->hostnames[$host]]; + } + + if ($options['resolve'] || $multi->dnsCache->evictions) { + // First reset any old DNS cache entries then add the new ones + $resolve = $multi->dnsCache->evictions; + $multi->dnsCache->evictions = []; + + if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) { + // DNS cache removals require curl 7.42 or higher + $multi->reset(); + } + + foreach ($options['resolve'] as $resolveHost => $ip) { + $resolve[] = null === $ip ? "-$resolveHost:$port" : "$resolveHost:$port:$ip"; + $multi->dnsCache->hostnames[$resolveHost] = $ip; + $multi->dnsCache->removals["-$resolveHost:$port"] = "-$resolveHost:$port"; + } + + $curlopts[\CURLOPT_RESOLVE] = $resolve; + } + + if ('POST' === $method) { + // Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303 + $curlopts[\CURLOPT_POST] = true; + } elseif ('HEAD' === $method) { + $curlopts[\CURLOPT_NOBODY] = true; + } else { + $curlopts[\CURLOPT_CUSTOMREQUEST] = $method; + } + + if ('\\' !== \DIRECTORY_SEPARATOR && $options['timeout'] < 1) { + $curlopts[\CURLOPT_NOSIGNAL] = true; + } + + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + $options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided + } + $body = $options['body']; + + foreach ($options['headers'] as $i => $header) { + if (\is_string($body) && '' !== $body && 0 === stripos($header, 'Content-Length: ')) { + // Let curl handle Content-Length headers + unset($options['headers'][$i]); + continue; + } + if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) { + // curl requires a special syntax to send empty headers + $curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2); + } else { + $curlopts[\CURLOPT_HTTPHEADER][] = $header; + } + } + + // Prevent curl from sending its default Accept and Expect headers + foreach (['accept', 'expect'] as $header) { + if (!isset($options['normalized_headers'][$header][0])) { + $curlopts[\CURLOPT_HTTPHEADER][] = $header.':'; + } + } + + if (!\is_string($body)) { + if (\is_resource($body)) { + $curlopts[\CURLOPT_INFILE] = $body; + } else { + $curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { + static $eof = false; + static $buffer = ''; + + return self::readRequestBody($length, $body, $buffer, $eof); + }; + } + + if (isset($options['normalized_headers']['content-length'][0])) { + $curlopts[\CURLOPT_INFILESIZE] = (int) substr($options['normalized_headers']['content-length'][0], \strlen('Content-Length: ')); + } + if (!isset($options['normalized_headers']['transfer-encoding'])) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Transfer-Encoding:'.(isset($curlopts[\CURLOPT_INFILESIZE]) ? '' : ' chunked'); + } + + if ('POST' !== $method) { + $curlopts[\CURLOPT_UPLOAD] = true; + + if (!isset($options['normalized_headers']['content-type']) && 0 !== ($curlopts[\CURLOPT_INFILESIZE] ?? null)) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded'; + } + } + } elseif ('' !== $body || 'POST' === $method) { + $curlopts[\CURLOPT_POSTFIELDS] = $body; + } + + if ($options['peer_fingerprint']) { + if (!isset($options['peer_fingerprint']['pin-sha256'])) { + throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.'); + } + + $curlopts[\CURLOPT_PINNEDPUBLICKEY] = 'sha256//'.implode(';sha256//', $options['peer_fingerprint']['pin-sha256']); + } + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + $curlopts[\CURLOPT_UNIX_SOCKET_PATH] = $options['bindto']; + } elseif (!str_starts_with($options['bindto'], 'if!') && preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) { + $curlopts[\CURLOPT_INTERFACE] = $matches[1]; + $curlopts[\CURLOPT_LOCALPORT] = $matches[2]; + } else { + $curlopts[\CURLOPT_INTERFACE] = $options['bindto']; + } + } + + if (0 < $options['max_duration']) { + $curlopts[\CURLOPT_TIMEOUT_MS] = 1000 * $options['max_duration']; + } + + if (!empty($options['extra']['curl']) && \is_array($options['extra']['curl'])) { + $this->validateExtraCurlOptions($options['extra']['curl']); + $curlopts += $options['extra']['curl']; + } + + if ($pushedResponse = $multi->pushedResponses[$url] ?? null) { + unset($multi->pushedResponses[$url]); + + if (self::acceptPushForRequest($method, $options, $pushedResponse)) { + $this->logger?->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url)); + + // Reinitialize the pushed response with request's options + $ch = $pushedResponse->handle; + $pushedResponse = $pushedResponse->response; + $pushedResponse->__construct($multi, $url, $options, $this->logger); + } else { + $this->logger?->debug(sprintf('Rejecting pushed response: "%s"', $url)); + $pushedResponse = null; + } + } + + if (!$pushedResponse) { + $ch = curl_init(); + $this->logger?->info(sprintf('Request: "%s %s"', $method, $url)); + $curlopts += [\CURLOPT_SHARE => $multi->share]; + } + + foreach ($curlopts as $opt => $value) { + if (null !== $value && !curl_setopt($ch, $opt, $value) && \CURLOPT_CERTINFO !== $opt && (!\defined('CURLOPT_HEADEROPT') || \CURLOPT_HEADEROPT !== $opt)) { + $constantName = $this->findConstantName($opt); + throw new TransportException(sprintf('Curl option "%s" is not supported.', $constantName ?? $opt)); + } + } + + return $pushedResponse ?? new CurlResponse($multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host, $port), CurlClientState::$curlVersion['version_number'], $url); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof CurlResponse) { + $responses = [$responses]; + } + + $multi = $this->ensureState(); + + if ($multi->handle instanceof \CurlMultiHandle) { + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($multi->handle, $active)) { + } + } + + return new ResponseStream(CurlResponse::stream($responses, $timeout)); + } + + public function reset(): void + { + if (isset($this->multi)) { + $this->multi->reset(); + } + } + + /** + * Accepts pushed responses only if their headers related to authentication match the request. + */ + private static function acceptPushForRequest(string $method, array $options, PushedResponse $pushedResponse): bool + { + if ('' !== $options['body'] || $method !== $pushedResponse->requestHeaders[':method'][0]) { + return false; + } + + foreach (['proxy', 'no_proxy', 'bindto', 'local_cert', 'local_pk'] as $k) { + if ($options[$k] !== $pushedResponse->parentOptions[$k]) { + return false; + } + } + + foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { + $normalizedHeaders = $options['normalized_headers'][$k] ?? []; + foreach ($normalizedHeaders as $i => $v) { + $normalizedHeaders[$i] = substr($v, \strlen($k) + 2); + } + + if (($pushedResponse->requestHeaders[$k] ?? []) !== $normalizedHeaders) { + return false; + } + } + + return true; + } + + /** + * Wraps the request's body callback to allow it to return strings longer than curl requested. + */ + private static function readRequestBody(int $length, \Closure $body, string &$buffer, bool &$eof): string + { + if (!$eof && \strlen($buffer) < $length) { + if (!\is_string($data = $body($length))) { + throw new TransportException(sprintf('The return value of the "body" option callback must be a string, "%s" returned.', get_debug_type($data))); + } + + $buffer .= $data; + $eof = '' === $data; + } + + $data = substr($buffer, 0, $length); + $buffer = substr($buffer, $length); + + return $data; + } + + /** + * Resolves relative URLs on redirects and deals with authentication headers. + * + * Work around CVE-2018-1000007: Authorization and Cookie headers should not follow redirects - fixed in Curl 7.64 + */ + private static function createRedirectResolver(array $options, string $host, int $port): \Closure + { + $redirectHeaders = []; + if (0 < $options['max_redirects']) { + $redirectHeaders['host'] = $host; + $redirectHeaders['port'] = $port; + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Host:')); + + if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) { + $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:')); + } + } + + return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) { + try { + $location = self::parseUrl($location); + } catch (InvalidArgumentException) { + return null; + } + + if ($noContent && $redirectHeaders) { + $filterContentHeaders = static fn ($h) => 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); + $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); + } + + if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) { + $port = parse_url('http:'.$location['authority'], \PHP_URL_PORT) ?: ('http:' === $location['scheme'] ? 80 : 443); + $requestHeaders = $redirectHeaders['host'] === $host && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; + curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders); + } elseif ($noContent && $redirectHeaders) { + curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']); + } + + $url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)); + $url = self::resolveUrl($location, $url); + + curl_setopt($ch, \CURLOPT_PROXY, self::getProxyUrl($options['proxy'], $url)); + + return implode('', $url); + }; + } + + private function ensureState(): CurlClientState + { + if (!isset($this->multi)) { + $this->multi = new CurlClientState($this->maxHostConnections, $this->maxPendingPushes); + $this->multi->logger = $this->logger; + } + + return $this->multi; + } + + private function findConstantName(int $opt): ?string + { + $constants = array_filter(get_defined_constants(), static fn ($v, $k) => $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')), \ARRAY_FILTER_USE_BOTH); + + return key($constants); + } + + /** + * Prevents overriding options that are set internally throughout the request. + */ + private function validateExtraCurlOptions(array $options): void + { + $curloptsToConfig = [ + // options used in CurlHttpClient + \CURLOPT_HTTPAUTH => 'auth_ntlm', + \CURLOPT_USERPWD => 'auth_ntlm', + \CURLOPT_RESOLVE => 'resolve', + \CURLOPT_NOSIGNAL => 'timeout', + \CURLOPT_HTTPHEADER => 'headers', + \CURLOPT_INFILE => 'body', + \CURLOPT_READFUNCTION => 'body', + \CURLOPT_INFILESIZE => 'body', + \CURLOPT_POSTFIELDS => 'body', + \CURLOPT_UPLOAD => 'body', + \CURLOPT_INTERFACE => 'bindto', + \CURLOPT_TIMEOUT_MS => 'max_duration', + \CURLOPT_TIMEOUT => 'max_duration', + \CURLOPT_MAXREDIRS => 'max_redirects', + \CURLOPT_POSTREDIR => 'max_redirects', + \CURLOPT_PROXY => 'proxy', + \CURLOPT_NOPROXY => 'no_proxy', + \CURLOPT_SSL_VERIFYPEER => 'verify_peer', + \CURLOPT_SSL_VERIFYHOST => 'verify_host', + \CURLOPT_CAINFO => 'cafile', + \CURLOPT_CAPATH => 'capath', + \CURLOPT_SSL_CIPHER_LIST => 'ciphers', + \CURLOPT_SSLCERT => 'local_cert', + \CURLOPT_SSLKEY => 'local_pk', + \CURLOPT_KEYPASSWD => 'passphrase', + \CURLOPT_CERTINFO => 'capture_peer_cert_chain', + \CURLOPT_USERAGENT => 'normalized_headers', + \CURLOPT_REFERER => 'headers', + // options used in CurlResponse + \CURLOPT_NOPROGRESS => 'on_progress', + \CURLOPT_PROGRESSFUNCTION => 'on_progress', + ]; + + if (\defined('CURLOPT_UNIX_SOCKET_PATH')) { + $curloptsToConfig[\CURLOPT_UNIX_SOCKET_PATH] = 'bindto'; + } + + if (\defined('CURLOPT_PINNEDPUBLICKEY')) { + $curloptsToConfig[\CURLOPT_PINNEDPUBLICKEY] = 'peer_fingerprint'; + } + + $curloptsToCheck = [ + \CURLOPT_PRIVATE, + \CURLOPT_HEADERFUNCTION, + \CURLOPT_WRITEFUNCTION, + \CURLOPT_VERBOSE, + \CURLOPT_STDERR, + \CURLOPT_RETURNTRANSFER, + \CURLOPT_URL, + \CURLOPT_FOLLOWLOCATION, + \CURLOPT_HEADER, + \CURLOPT_CONNECTTIMEOUT, + \CURLOPT_CONNECTTIMEOUT_MS, + \CURLOPT_HTTP_VERSION, + \CURLOPT_PORT, + \CURLOPT_DNS_USE_GLOBAL_CACHE, + \CURLOPT_PROTOCOLS, + \CURLOPT_REDIR_PROTOCOLS, + \CURLOPT_COOKIEFILE, + \CURLINFO_REDIRECT_COUNT, + ]; + + if (\defined('CURLOPT_HTTP09_ALLOWED')) { + $curloptsToCheck[] = \CURLOPT_HTTP09_ALLOWED; + } + + if (\defined('CURLOPT_HEADEROPT')) { + $curloptsToCheck[] = \CURLOPT_HEADEROPT; + } + + $methodOpts = [ + \CURLOPT_POST, + \CURLOPT_PUT, + \CURLOPT_CUSTOMREQUEST, + \CURLOPT_HTTPGET, + \CURLOPT_NOBODY, + ]; + + foreach ($options as $opt => $optValue) { + if (isset($curloptsToConfig[$opt])) { + $constName = $this->findConstantName($opt) ?? $opt; + throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl", use option "%s" instead.', $constName, $curloptsToConfig[$opt])); + } + + if (\in_array($opt, $methodOpts, true)) { + throw new InvalidArgumentException('The HTTP method cannot be overridden using "extra.curl".'); + } + + if (\in_array($opt, $curloptsToCheck, true)) { + $constName = $this->findConstantName($opt) ?? $opt; + throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl".', $constName)); + } + } + } +} diff --git a/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php b/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php new file mode 100644 index 0000000..3881f3e --- /dev/null +++ b/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\DataCollector; + +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\HttpClientTrait; +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\Process\Process; +use Symfony\Component\VarDumper\Caster\ImgStub; + +/** + * @author Jérémy Romey + */ +final class HttpClientDataCollector extends DataCollector implements LateDataCollectorInterface +{ + use HttpClientTrait; + + /** + * @var TraceableHttpClient[] + */ + private array $clients = []; + + public function registerClient(string $name, TraceableHttpClient $client): void + { + $this->clients[$name] = $client; + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $this->lateCollect(); + } + + public function lateCollect(): void + { + $this->data['request_count'] = $this->data['request_count'] ?? 0; + $this->data['error_count'] = $this->data['error_count'] ?? 0; + $this->data += ['clients' => []]; + + foreach ($this->clients as $name => $client) { + [$errorCount, $traces] = $this->collectOnClient($client); + + $this->data['clients'] += [ + $name => [ + 'traces' => [], + 'error_count' => 0, + ], + ]; + + $this->data['clients'][$name]['traces'] = array_merge($this->data['clients'][$name]['traces'], $traces); + $this->data['request_count'] += \count($traces); + $this->data['error_count'] += $errorCount; + $this->data['clients'][$name]['error_count'] += $errorCount; + + $client->reset(); + } + } + + public function getClients(): array + { + return $this->data['clients'] ?? []; + } + + public function getRequestCount(): int + { + return $this->data['request_count'] ?? 0; + } + + public function getErrorCount(): int + { + return $this->data['error_count'] ?? 0; + } + + public function getName(): string + { + return 'http_client'; + } + + public function reset(): void + { + $this->data = [ + 'clients' => [], + 'request_count' => 0, + 'error_count' => 0, + ]; + } + + private function collectOnClient(TraceableHttpClient $client): array + { + $traces = $client->getTracedRequests(); + $errorCount = 0; + $baseInfo = [ + 'response_headers' => 1, + 'retry_count' => 1, + 'redirect_count' => 1, + 'redirect_url' => 1, + 'user_data' => 1, + 'error' => 1, + 'url' => 1, + ]; + + foreach ($traces as $i => $trace) { + if (400 <= ($trace['info']['http_code'] ?? 0)) { + ++$errorCount; + } + + $info = $trace['info']; + $traces[$i]['http_code'] = $info['http_code'] ?? 0; + + unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']); + + if (($info['http_method'] ?? null) === $trace['method']) { + unset($info['http_method']); + } + + if (($info['url'] ?? null) === $trace['url']) { + unset($info['url']); + } + + foreach ($info as $k => $v) { + if (!$v || (is_numeric($v) && 0 > $v)) { + unset($info[$k]); + } + } + + if (\is_string($content = $trace['content'])) { + $contentType = 'application/octet-stream'; + + foreach ($info['response_headers'] ?? [] as $h) { + if (0 === stripos($h, 'content-type: ')) { + $contentType = substr($h, \strlen('content-type: ')); + break; + } + } + + if (str_starts_with($contentType, 'image/') && class_exists(ImgStub::class)) { + $content = new ImgStub($content, $contentType, ''); + } else { + $content = [$content]; + } + + $content = ['response_content' => $content]; + } elseif (\is_array($content)) { + $content = ['response_json' => $content]; + } else { + $content = []; + } + + if (isset($info['retry_count'])) { + $content['retries'] = $info['previous_info']; + unset($info['previous_info']); + } + + $debugInfo = array_diff_key($info, $baseInfo); + $info = ['info' => $debugInfo] + array_diff_key($info, $debugInfo) + $content; + unset($traces[$i]['info']); // break PHP reference used by TraceableHttpClient + $traces[$i]['info'] = $this->cloneVar($info); + $traces[$i]['options'] = $this->cloneVar($trace['options']); + $traces[$i]['curlCommand'] = $this->getCurlCommand($trace); + } + + return [$errorCount, $traces]; + } + + private function getCurlCommand(array $trace): ?string + { + if (!isset($trace['info']['debug'])) { + return null; + } + + $url = $trace['info']['original_url'] ?? $trace['info']['url'] ?? $trace['url']; + $command = ['curl', '--compressed']; + + if (isset($trace['options']['resolve'])) { + $port = parse_url($url, \PHP_URL_PORT) ?: (str_starts_with('http:', $url) ? 80 : 443); + foreach ($trace['options']['resolve'] as $host => $ip) { + if (null !== $ip) { + $command[] = '--resolve '.escapeshellarg("$host:$port:$ip"); + } + } + } + + $dataArg = []; + + if ($json = $trace['options']['json'] ?? null) { + $dataArg[] = '--data-raw '.$this->escapePayload(self::jsonEncode($json)); + } elseif ($body = $trace['options']['body'] ?? null) { + if (\is_string($body)) { + $dataArg[] = '--data-raw '.$this->escapePayload($body); + } elseif (\is_array($body)) { + try { + $body = explode('&', self::normalizeBody($body)); + } catch (TransportException) { + return null; + } + foreach ($body as $value) { + $dataArg[] = '--data-raw '.$this->escapePayload(urldecode($value)); + } + } else { + return null; + } + } + + $dataArg = $dataArg ? implode(' ', $dataArg) : null; + + foreach (explode("\n", $trace['info']['debug']) as $line) { + $line = substr($line, 0, -1); + + if (str_starts_with('< ', $line)) { + // End of the request, beginning of the response. Stop parsing. + break; + } + + if (str_starts_with('Due to a bug in curl ', $line)) { + // When the curl client disables debug info due to a curl bug, we cannot build the command. + return null; + } + + if ('' === $line || preg_match('/^[*<]|(Host: )/', $line)) { + continue; + } + + if (preg_match('/^> ([A-Z]+)/', $line, $match)) { + $command[] = sprintf('--request %s', $match[1]); + $command[] = sprintf('--url %s', escapeshellarg($url)); + continue; + } + + $command[] = '--header '.escapeshellarg($line); + } + + if (null !== $dataArg) { + $command[] = $dataArg; + } + + return implode(" \\\n ", $command); + } + + private function escapePayload(string $payload): string + { + static $useProcess; + + if ($useProcess ??= class_exists(Process::class)) { + return (new Process([$payload]))->getCommandLine(); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return '"'.str_replace('"', '""', $payload).'"'; + } + + return "'".str_replace("'", "'\\''", $payload)."'"; + } +} diff --git a/vendor/symfony/http-client/DecoratorTrait.php b/vendor/symfony/http-client/DecoratorTrait.php new file mode 100644 index 0000000..6fcb349 --- /dev/null +++ b/vendor/symfony/http-client/DecoratorTrait.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Eases with writing decorators. + * + * @author Nicolas Grekas + */ +trait DecoratorTrait +{ + private HttpClientInterface $client; + + public function __construct(?HttpClientInterface $client = null) + { + $this->client = $client ?? HttpClient::create(); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + return $this->client->request($method, $url, $options); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + public function reset(): void + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php b/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php new file mode 100644 index 0000000..214a655 --- /dev/null +++ b/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpClient\TraceableHttpClient; + +final class HttpClientPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('data_collector.http_client')) { + return; + } + + foreach ($container->findTaggedServiceIds('http_client.client') as $id => $tags) { + $container->register('.debug.'.$id, TraceableHttpClient::class) + ->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) + ->addTag('kernel.reset', ['method' => 'reset']) + ->setDecoratedService($id, null, 5); + $container->getDefinition('data_collector.http_client') + ->addMethodCall('registerClient', [$id, new Reference('.debug.'.$id)]); + } + } +} diff --git a/vendor/symfony/http-client/EventSourceHttpClient.php b/vendor/symfony/http-client/EventSourceHttpClient.php new file mode 100644 index 0000000..b5f88dd --- /dev/null +++ b/vendor/symfony/http-client/EventSourceHttpClient.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\ServerSentEvent; +use Symfony\Component\HttpClient\Exception\EventSourceException; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Antoine Bluchet + * @author Nicolas Grekas + */ +final class EventSourceHttpClient implements HttpClientInterface, ResetInterface +{ + use AsyncDecoratorTrait, HttpClientTrait { + AsyncDecoratorTrait::withOptions insteadof HttpClientTrait; + } + + private float $reconnectionTime; + + public function __construct(?HttpClientInterface $client = null, float $reconnectionTime = 10.0) + { + $this->client = $client ?? HttpClient::create(); + $this->reconnectionTime = $reconnectionTime; + } + + public function connect(string $url, array $options = [], string $method = 'GET'): ResponseInterface + { + return $this->request($method, $url, self::mergeDefaultOptions($options, [ + 'buffer' => false, + 'headers' => [ + 'Accept' => 'text/event-stream', + 'Cache-Control' => 'no-cache', + ], + ], true)); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $state = new class() { + public ?string $buffer = null; + public ?string $lastEventId = null; + public float $reconnectionTime; + public ?float $lastError = null; + }; + $state->reconnectionTime = $this->reconnectionTime; + + if ($accept = self::normalizeHeaders($options['headers'] ?? [])['accept'] ?? []) { + $state->buffer = \in_array($accept, [['Accept: text/event-stream'], ['accept: text/event-stream']], true) ? '' : null; + + if (null !== $state->buffer) { + $options['extra']['trace_content'] = false; + } + } + + return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use ($state, $method, $url, $options) { + if (null !== $state->buffer) { + $context->setInfo('reconnection_time', $state->reconnectionTime); + $isTimeout = false; + } + $lastError = $state->lastError; + $state->lastError = null; + + try { + $isTimeout = $chunk->isTimeout(); + + if (null !== $chunk->getInformationalStatus() || $context->getInfo('canceled')) { + yield $chunk; + + return; + } + } catch (TransportExceptionInterface) { + $state->lastError = $lastError ?? hrtime(true) / 1E9; + + if (null === $state->buffer || ($isTimeout && hrtime(true) / 1E9 - $state->lastError < $state->reconnectionTime)) { + yield $chunk; + } else { + $options['headers']['Last-Event-ID'] = $state->lastEventId; + $state->buffer = ''; + $state->lastError = hrtime(true) / 1E9; + $context->getResponse()->cancel(); + $context->replaceRequest($method, $url, $options); + if ($isTimeout) { + yield $chunk; + } else { + $context->pause($state->reconnectionTime); + } + } + + return; + } + + if ($chunk->isFirst()) { + if (preg_match('/^text\/event-stream(;|$)/i', $context->getHeaders()['content-type'][0] ?? '')) { + $state->buffer = ''; + } elseif (null !== $lastError || (null !== $state->buffer && 200 === $context->getStatusCode())) { + throw new EventSourceException(sprintf('Response content-type is "%s" while "text/event-stream" was expected for "%s".', $context->getHeaders()['content-type'][0] ?? '', $context->getInfo('url'))); + } else { + $context->passthru(); + } + + if (null === $lastError) { + yield $chunk; + } + + return; + } + + if ($chunk->isLast()) { + if ('' !== $content = $state->buffer) { + $state->buffer = ''; + yield new DataChunk(-1, $content); + } + + yield $chunk; + + return; + } + + $content = $state->buffer.$chunk->getContent(); + $events = preg_split('/((?:\r\n){2,}|\r{2,}|\n{2,})/', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); + $state->buffer = array_pop($events); + + for ($i = 0; isset($events[$i]); $i += 2) { + $content = $events[$i].$events[1 + $i]; + if (!preg_match('/(?:^|\r\n|[\r\n])[^:\r\n]/', $content)) { + yield new DataChunk(-1, $content); + + continue; + } + + $event = new ServerSentEvent($content); + + if ('' !== $event->getId()) { + $context->setInfo('last_event_id', $state->lastEventId = $event->getId()); + } + + if ($event->getRetry()) { + $context->setInfo('reconnection_time', $state->reconnectionTime = $event->getRetry()); + } + + yield $event; + } + }); + } +} diff --git a/vendor/symfony/http-client/Exception/ClientException.php b/vendor/symfony/http-client/Exception/ClientException.php new file mode 100644 index 0000000..4264534 --- /dev/null +++ b/vendor/symfony/http-client/Exception/ClientException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; + +/** + * Represents a 4xx response. + * + * @author Nicolas Grekas + */ +final class ClientException extends \RuntimeException implements ClientExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/vendor/symfony/http-client/Exception/EventSourceException.php b/vendor/symfony/http-client/Exception/EventSourceException.php new file mode 100644 index 0000000..30ab795 --- /dev/null +++ b/vendor/symfony/http-client/Exception/EventSourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class EventSourceException extends \RuntimeException implements DecodingExceptionInterface +{ +} diff --git a/vendor/symfony/http-client/Exception/HttpExceptionTrait.php b/vendor/symfony/http-client/Exception/HttpExceptionTrait.php new file mode 100644 index 0000000..264ef24 --- /dev/null +++ b/vendor/symfony/http-client/Exception/HttpExceptionTrait.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait HttpExceptionTrait +{ + private ResponseInterface $response; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + $code = $response->getInfo('http_code'); + $url = $response->getInfo('url'); + $message = sprintf('HTTP %d returned for "%s".', $code, $url); + + $httpCodeFound = false; + $isJson = false; + foreach (array_reverse($response->getInfo('response_headers')) as $h) { + if (str_starts_with($h, 'HTTP/')) { + if ($httpCodeFound) { + break; + } + + $message = sprintf('%s returned for "%s".', $h, $url); + $httpCodeFound = true; + } + + if (0 === stripos($h, 'content-type:')) { + if (preg_match('/\bjson\b/i', $h)) { + $isJson = true; + } + + if ($httpCodeFound) { + break; + } + } + } + + // Try to guess a better error message using common API error formats + // The MIME type isn't explicitly checked because some formats inherit from others + // Ex: JSON:API follows RFC 7807 semantics, Hydra can be used in any JSON-LD-compatible format + if ($isJson && $body = json_decode($response->getContent(false), true)) { + if (isset($body['hydra:title']) || isset($body['hydra:description'])) { + // see http://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors + $separator = isset($body['hydra:title'], $body['hydra:description']) ? "\n\n" : ''; + $message = ($body['hydra:title'] ?? '').$separator.($body['hydra:description'] ?? ''); + } elseif ((isset($body['title']) || isset($body['detail'])) + && (\is_scalar($body['title'] ?? '') && \is_scalar($body['detail'] ?? ''))) { + // see RFC 7807 and https://jsonapi.org/format/#error-objects + $separator = isset($body['title'], $body['detail']) ? "\n\n" : ''; + $message = ($body['title'] ?? '').$separator.($body['detail'] ?? ''); + } + } + + parent::__construct($message, $code); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } +} diff --git a/vendor/symfony/http-client/Exception/InvalidArgumentException.php b/vendor/symfony/http-client/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..6c2fae7 --- /dev/null +++ b/vendor/symfony/http-client/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class InvalidArgumentException extends \InvalidArgumentException implements TransportExceptionInterface +{ +} diff --git a/vendor/symfony/http-client/Exception/JsonException.php b/vendor/symfony/http-client/Exception/JsonException.php new file mode 100644 index 0000000..54502e6 --- /dev/null +++ b/vendor/symfony/http-client/Exception/JsonException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; + +/** + * Thrown by responses' toArray() method when their content cannot be JSON-decoded. + * + * @author Nicolas Grekas + */ +final class JsonException extends \JsonException implements DecodingExceptionInterface +{ +} diff --git a/vendor/symfony/http-client/Exception/RedirectionException.php b/vendor/symfony/http-client/Exception/RedirectionException.php new file mode 100644 index 0000000..5b93670 --- /dev/null +++ b/vendor/symfony/http-client/Exception/RedirectionException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; + +/** + * Represents a 3xx response. + * + * @author Nicolas Grekas + */ +final class RedirectionException extends \RuntimeException implements RedirectionExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/vendor/symfony/http-client/Exception/ServerException.php b/vendor/symfony/http-client/Exception/ServerException.php new file mode 100644 index 0000000..c6f8273 --- /dev/null +++ b/vendor/symfony/http-client/Exception/ServerException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; + +/** + * Represents a 5xx response. + * + * @author Nicolas Grekas + */ +final class ServerException extends \RuntimeException implements ServerExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/vendor/symfony/http-client/Exception/TimeoutException.php b/vendor/symfony/http-client/Exception/TimeoutException.php new file mode 100644 index 0000000..a9155cc --- /dev/null +++ b/vendor/symfony/http-client/Exception/TimeoutException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class TimeoutException extends TransportException implements TimeoutExceptionInterface +{ +} diff --git a/vendor/symfony/http-client/Exception/TransportException.php b/vendor/symfony/http-client/Exception/TransportException.php new file mode 100644 index 0000000..a3a80c6 --- /dev/null +++ b/vendor/symfony/http-client/Exception/TransportException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +class TransportException extends \RuntimeException implements TransportExceptionInterface +{ +} diff --git a/vendor/symfony/http-client/HttpClient.php b/vendor/symfony/http-client/HttpClient.php new file mode 100644 index 0000000..0e7d9b4 --- /dev/null +++ b/vendor/symfony/http-client/HttpClient.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Amp\Http\Client\Connection\ConnectionLimitingPool; +use Amp\Promise; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A factory to instantiate the best possible HTTP client for the runtime. + * + * @author Nicolas Grekas + */ +final class HttpClient +{ + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public static function create(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface + { + if ($amp = class_exists(ConnectionLimitingPool::class) && interface_exists(Promise::class)) { + if (!\extension_loaded('curl')) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + // Skip curl when HTTP/2 push is unsupported or buggy, see https://bugs.php.net/77535 + if (!\defined('CURLMOPT_PUSHFUNCTION')) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + static $curlVersion = null; + $curlVersion ??= curl_version(); + + // HTTP/2 push crashes before curl 7.61 + if (0x073D00 > $curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & $curlVersion['features'])) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + } + + if (\extension_loaded('curl')) { + if ('\\' !== \DIRECTORY_SEPARATOR || isset($defaultOptions['cafile']) || isset($defaultOptions['capath']) || \ini_get('curl.cainfo') || \ini_get('openssl.cafile') || \ini_get('openssl.capath')) { + return new CurlHttpClient($defaultOptions, $maxHostConnections, $maxPendingPushes); + } + + @trigger_error('Configure the "curl.cainfo", "openssl.cafile" or "openssl.capath" php.ini setting to enable the CurlHttpClient', \E_USER_WARNING); + } + + if ($amp) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + @trigger_error((\extension_loaded('curl') ? 'Upgrade' : 'Install').' the curl extension or run "composer require amphp/http-client:^4.2.1" to perform async HTTP operations, including full HTTP/2 support', \E_USER_NOTICE); + + return new NativeHttpClient($defaultOptions, $maxHostConnections); + } + + /** + * Creates a client that adds options (e.g. authentication headers) only when the request URL matches the provided base URI. + */ + public static function createForBaseUri(string $baseUri, array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface + { + $client = self::create([], $maxHostConnections, $maxPendingPushes); + + return ScopingHttpClient::forBaseUri($client, $baseUri, $defaultOptions); + } +} diff --git a/vendor/symfony/http-client/HttpClientTrait.php b/vendor/symfony/http-client/HttpClientTrait.php new file mode 100644 index 0000000..8d270c0 --- /dev/null +++ b/vendor/symfony/http-client/HttpClientTrait.php @@ -0,0 +1,828 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Component\Mime\MimeTypes; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * Provides the common logic from writing HttpClientInterface implementations. + * + * All private methods are static to prevent implementers from creating memory leaks via circular references. + * + * @author Nicolas Grekas + */ +trait HttpClientTrait +{ + private static int $CHUNK_SIZE = 16372; + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions); + + return $clone; + } + + /** + * Validates and normalizes method, URL and options, and merges them with defaults. + * + * @throws InvalidArgumentException When a not-supported option is found + */ + private static function prepareRequest(?string $method, ?string $url, array $options, array $defaultOptions = [], bool $allowExtraOptions = false): array + { + if (null !== $method) { + if (\strlen($method) !== strspn($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')) { + throw new InvalidArgumentException(sprintf('Invalid HTTP method "%s", only uppercase letters are accepted.', $method)); + } + if (!$method) { + throw new InvalidArgumentException('The HTTP method cannot be empty.'); + } + } + + $options = self::mergeDefaultOptions($options, $defaultOptions, $allowExtraOptions); + + $buffer = $options['buffer'] ?? true; + + if ($buffer instanceof \Closure) { + $options['buffer'] = static function (array $headers) use ($buffer) { + if (!\is_bool($buffer = $buffer($headers))) { + if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) { + throw new \LogicException(sprintf('The closure passed as option "buffer" must return bool or stream resource, got "%s".', get_debug_type($buffer))); + } + + if (false === strpbrk($bufferInfo['mode'], 'acew+')) { + throw new \LogicException(sprintf('The stream returned by the closure passed as option "buffer" must be writeable, got mode "%s".', $bufferInfo['mode'])); + } + } + + return $buffer; + }; + } elseif (!\is_bool($buffer)) { + if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) { + throw new InvalidArgumentException(sprintf('Option "buffer" must be bool, stream resource or Closure, "%s" given.', get_debug_type($buffer))); + } + + if (false === strpbrk($bufferInfo['mode'], 'acew+')) { + throw new InvalidArgumentException(sprintf('The stream in option "buffer" must be writeable, mode "%s" given.', $bufferInfo['mode'])); + } + } + + if (isset($options['json'])) { + if (isset($options['body']) && '' !== $options['body']) { + throw new InvalidArgumentException('Define either the "json" or the "body" option, setting both is not supported.'); + } + $options['body'] = self::jsonEncode($options['json']); + unset($options['json']); + + if (!isset($options['normalized_headers']['content-type'])) { + $options['normalized_headers']['content-type'] = ['Content-Type: application/json']; + } + } + + if (!isset($options['normalized_headers']['accept'])) { + $options['normalized_headers']['accept'] = ['Accept: */*']; + } + + if (isset($options['body'])) { + $options['body'] = self::normalizeBody($options['body'], $options['normalized_headers']); + + if (\is_string($options['body']) + && (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16) + && ('' !== $h || '' !== $options['body']) + ) { + if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) { + unset($options['normalized_headers']['transfer-encoding']); + $options['body'] = self::dechunk($options['body']); + } + + $options['normalized_headers']['content-length'] = [substr_replace($h ?: 'Content-Length: ', \strlen($options['body']), 16)]; + } + } + + if (isset($options['peer_fingerprint'])) { + $options['peer_fingerprint'] = self::normalizePeerFingerprint($options['peer_fingerprint']); + } + + if (isset($options['crypto_method']) && !\in_array($options['crypto_method'], [ + \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT, + \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT, + \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, + \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT, + ], true)) { + throw new InvalidArgumentException('Option "crypto_method" must be one of "STREAM_CRYPTO_METHOD_TLSv1_*_CLIENT".'); + } + + // Validate on_progress + if (isset($options['on_progress']) && !\is_callable($onProgress = $options['on_progress'])) { + throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress))); + } + + if (\is_array($options['auth_basic'] ?? null)) { + $count = \count($options['auth_basic']); + if ($count <= 0 || $count > 2) { + throw new InvalidArgumentException(sprintf('Option "auth_basic" must contain 1 or 2 elements, "%s" given.', $count)); + } + + $options['auth_basic'] = implode(':', $options['auth_basic']); + } + + if (!\is_string($options['auth_basic'] ?? '')) { + throw new InvalidArgumentException(sprintf('Option "auth_basic" must be string or an array, "%s" given.', get_debug_type($options['auth_basic']))); + } + + if (isset($options['auth_bearer'])) { + if (!\is_string($options['auth_bearer'])) { + throw new InvalidArgumentException(sprintf('Option "auth_bearer" must be a string, "%s" given.', get_debug_type($options['auth_bearer']))); + } + if (preg_match('{[^\x21-\x7E]}', $options['auth_bearer'])) { + throw new InvalidArgumentException('Invalid character found in option "auth_bearer": '.json_encode($options['auth_bearer']).'.'); + } + } + + if (isset($options['auth_basic'], $options['auth_bearer'])) { + throw new InvalidArgumentException('Define either the "auth_basic" or the "auth_bearer" option, setting both is not supported.'); + } + + if (null !== $url) { + // Merge auth with headers + if (($options['auth_basic'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { + $options['normalized_headers']['authorization'] = ['Authorization: Basic '.base64_encode($options['auth_basic'])]; + } + // Merge bearer with headers + if (($options['auth_bearer'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { + $options['normalized_headers']['authorization'] = ['Authorization: Bearer '.$options['auth_bearer']]; + } + + unset($options['auth_basic'], $options['auth_bearer']); + + // Parse base URI + if (\is_string($options['base_uri'])) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + + // Validate and resolve URL + $url = self::parseUrl($url, $options['query']); + $url = self::resolveUrl($url, $options['base_uri'], $defaultOptions['query'] ?? []); + } + + // Finalize normalization of options + $options['http_version'] = (string) ($options['http_version'] ?? '') ?: null; + if (0 > $options['timeout'] = (float) ($options['timeout'] ?? \ini_get('default_socket_timeout'))) { + $options['timeout'] = 172800.0; // 2 days + } + + $options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0; + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + + return [$url, $options]; + } + + /** + * @throws InvalidArgumentException When an invalid option is found + */ + private static function mergeDefaultOptions(array $options, array $defaultOptions, bool $allowExtraOptions = false): array + { + $options['normalized_headers'] = self::normalizeHeaders($options['headers'] ?? []); + + if ($defaultOptions['headers'] ?? false) { + $options['normalized_headers'] += self::normalizeHeaders($defaultOptions['headers']); + } + + $options['headers'] = array_merge(...array_values($options['normalized_headers']) ?: [[]]); + + if ($resolve = $options['resolve'] ?? false) { + $options['resolve'] = []; + foreach ($resolve as $k => $v) { + $options['resolve'][substr(self::parseUrl('http://'.$k)['authority'], 2)] = (string) $v; + } + } + + // Option "query" is never inherited from defaults + $options['query'] ??= []; + + $options += $defaultOptions; + + if (isset(self::$emptyDefaults)) { + foreach (self::$emptyDefaults as $k => $v) { + if (!isset($options[$k])) { + $options[$k] = $v; + } + } + } + + if (isset($defaultOptions['extra'])) { + $options['extra'] += $defaultOptions['extra']; + } + + if ($resolve = $defaultOptions['resolve'] ?? false) { + foreach ($resolve as $k => $v) { + $options['resolve'] += [substr(self::parseUrl('http://'.$k)['authority'], 2) => (string) $v]; + } + } + + if ($allowExtraOptions || !$defaultOptions) { + return $options; + } + + // Look for unsupported options + foreach ($options as $name => $v) { + if (\array_key_exists($name, $defaultOptions) || 'normalized_headers' === $name) { + continue; + } + + if ('auth_ntlm' === $name) { + if (!\extension_loaded('curl')) { + $msg = 'try installing the "curl" extension to use "%s" instead.'; + } else { + $msg = 'try using "%s" instead.'; + } + + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" is not supported by "%s", '.$msg, __CLASS__, CurlHttpClient::class)); + } + + if ('vars' === $name) { + throw new InvalidArgumentException(sprintf('Option "vars" is not supported by "%s", try using "%s" instead.', __CLASS__, UriTemplateHttpClient::class)); + } + + $alternatives = []; + + foreach ($defaultOptions as $k => $v) { + if (levenshtein($name, $k) <= \strlen($name) / 3 || str_contains($k, $name)) { + $alternatives[] = $k; + } + } + + throw new InvalidArgumentException(sprintf('Unsupported option "%s" passed to "%s", did you mean "%s"?', $name, __CLASS__, implode('", "', $alternatives ?: array_keys($defaultOptions)))); + } + + return $options; + } + + /** + * @return string[][] + * + * @throws InvalidArgumentException When an invalid header is found + */ + private static function normalizeHeaders(array $headers): array + { + $normalizedHeaders = []; + + foreach ($headers as $name => $values) { + if ($values instanceof \Stringable) { + $values = (string) $values; + } + + if (\is_int($name)) { + if (!\is_string($values)) { + throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values))); + } + [$name, $values] = explode(':', $values, 2); + $values = [ltrim($values)]; + } elseif (!is_iterable($values)) { + if (\is_object($values)) { + throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values))); + } + + $values = (array) $values; + } + + $lcName = strtolower($name); + $normalizedHeaders[$lcName] = []; + + foreach ($values as $value) { + $normalizedHeaders[$lcName][] = $value = $name.': '.$value; + + if (\strlen($value) !== strcspn($value, "\r\n\0")) { + throw new InvalidArgumentException(sprintf('Invalid header: CR/LF/NUL found in "%s".', $value)); + } + } + } + + return $normalizedHeaders; + } + + /** + * @param array|string|resource|\Traversable|\Closure $body + * + * @return string|resource|\Closure + * + * @throws InvalidArgumentException When an invalid body is passed + */ + private static function normalizeBody($body, array &$normalizedHeaders = []) + { + if (\is_array($body)) { + static $cookie; + + $streams = []; + array_walk_recursive($body, $caster = static function (&$v) use (&$caster, &$streams, &$cookie) { + if (\is_resource($v) || $v instanceof StreamableInterface) { + $cookie = hash('xxh128', $cookie ??= random_bytes(8), true); + $k = substr(strtr(base64_encode($cookie), '+/', '-_'), 0, -2); + $streams[$k] = $v instanceof StreamableInterface ? $v->toStream(false) : $v; + $v = $k; + } elseif (\is_object($v)) { + if ($vars = get_object_vars($v)) { + array_walk_recursive($vars, $caster); + $v = $vars; + } elseif ($v instanceof \Stringable) { + $v = (string) $v; + } + } + }); + + $body = http_build_query($body, '', '&'); + + if ('' === $body || !$streams && !str_contains($normalizedHeaders['content-type'][0] ?? '', 'multipart/form-data')) { + if (!str_contains($normalizedHeaders['content-type'][0] ?? '', 'application/x-www-form-urlencoded')) { + $normalizedHeaders['content-type'] = ['Content-Type: application/x-www-form-urlencoded']; + } + + return $body; + } + + if (preg_match('{multipart/form-data; boundary=(?|"([^"\r\n]++)"|([-!#$%&\'*+.^_`|~_A-Za-z0-9]++))}', $normalizedHeaders['content-type'][0] ?? '', $boundary)) { + $boundary = $boundary[1]; + } else { + $boundary = substr(strtr(base64_encode($cookie ??= random_bytes(8)), '+/', '-_'), 0, -2); + $normalizedHeaders['content-type'] = ['Content-Type: multipart/form-data; boundary='.$boundary]; + } + + $body = explode('&', $body); + $contentLength = 0; + + foreach ($body as $i => $part) { + [$k, $v] = explode('=', $part, 2); + $part = ($i ? "\r\n" : '')."--{$boundary}\r\n"; + $k = str_replace(['"', "\r", "\n"], ['%22', '%0D', '%0A'], urldecode($k)); // see WHATWG HTML living standard + + if (!isset($streams[$v])) { + $part .= "Content-Disposition: form-data; name=\"{$k}\"\r\n\r\n".urldecode($v); + $contentLength += 0 <= $contentLength ? \strlen($part) : 0; + $body[$i] = [$k, $part, null]; + continue; + } + $v = $streams[$v]; + + if (!\is_array($m = @stream_get_meta_data($v))) { + throw new TransportException(sprintf('Invalid "%s" resource found in body part "%s".', get_resource_type($v), $k)); + } + if (feof($v)) { + throw new TransportException(sprintf('Uploaded stream ended for body part "%s".', $k)); + } + + $m += stream_context_get_options($v)['http'] ?? []; + $filename = basename($m['filename'] ?? $m['uri'] ?? 'unknown'); + $filename = str_replace(['"', "\r", "\n"], ['%22', '%0D', '%0A'], $filename); + $contentType = $m['content_type'] ?? null; + + if (($headers = $m['wrapper_data'] ?? []) instanceof StreamWrapper) { + $hasContentLength = false; + $headers = $headers->getResponse()->getInfo('response_headers'); + } elseif ($hasContentLength = 0 < $h = fstat($v)['size'] ?? 0) { + $contentLength += 0 <= $contentLength ? $h : 0; + } + + foreach (\is_array($headers) ? $headers : [] as $h) { + if (\is_string($h) && 0 === stripos($h, 'Content-Type: ')) { + $contentType ??= substr($h, 14); + } elseif (!$hasContentLength && \is_string($h) && 0 === stripos($h, 'Content-Length: ')) { + $hasContentLength = true; + $contentLength += 0 <= $contentLength ? substr($h, 16) : 0; + } elseif (\is_string($h) && 0 === stripos($h, 'Content-Encoding: ')) { + $contentLength = -1; + } + } + + if (!$hasContentLength) { + $contentLength = -1; + } + if (null === $contentType && 'plainfile' === ($m['wrapper_type'] ?? null) && isset($m['uri'])) { + $mimeTypes = class_exists(MimeTypes::class) ? MimeTypes::getDefault() : false; + $contentType = $mimeTypes ? $mimeTypes->guessMimeType($m['uri']) : null; + } + $contentType ??= 'application/octet-stream'; + + $part .= "Content-Disposition: form-data; name=\"{$k}\"; filename=\"{$filename}\"\r\n"; + $part .= "Content-Type: {$contentType}\r\n\r\n"; + + $contentLength += 0 <= $contentLength ? \strlen($part) : 0; + $body[$i] = [$k, $part, $v]; + } + + $body[++$i] = ['', "\r\n--{$boundary}--\r\n", null]; + + if (0 < $contentLength) { + $normalizedHeaders['content-length'] = ['Content-Length: '.($contentLength += \strlen($body[$i][1]))]; + } + + $body = static function ($size) use ($body) { + foreach ($body as $i => [$k, $part, $h]) { + unset($body[$i]); + + yield $part; + + while (null !== $h && !feof($h)) { + if (false === $part = fread($h, $size)) { + throw new TransportException(sprintf('Error while reading uploaded stream for body part "%s".', $k)); + } + + yield $part; + } + } + $h = null; + }; + } + + if (\is_string($body)) { + return $body; + } + + $generatorToCallable = static fn (\Generator $body): \Closure => static function () use ($body) { + while ($body->valid()) { + $chunk = $body->current(); + $body->next(); + + if ('' !== $chunk) { + return $chunk; + } + } + + return ''; + }; + + if ($body instanceof \Generator) { + return $generatorToCallable($body); + } + + if ($body instanceof \Traversable) { + return $generatorToCallable((static function ($body) { yield from $body; })($body)); + } + + if ($body instanceof \Closure) { + $r = new \ReflectionFunction($body); + $body = $r->getClosure(); + + if ($r->isGenerator()) { + $body = $body(self::$CHUNK_SIZE); + + return $generatorToCallable($body); + } + + return $body; + } + + if (!\is_array(@stream_get_meta_data($body))) { + throw new InvalidArgumentException(sprintf('Option "body" must be string, stream resource, iterable or callable, "%s" given.', get_debug_type($body))); + } + + return $body; + } + + private static function dechunk(string $body): string + { + $h = fopen('php://temp', 'w+'); + stream_filter_append($h, 'dechunk', \STREAM_FILTER_WRITE); + fwrite($h, $body); + $body = stream_get_contents($h, -1, 0); + rewind($h); + ftruncate($h, 0); + + if (fwrite($h, '-') && '' !== stream_get_contents($h, -1, 0)) { + throw new TransportException('Request body has broken chunked encoding.'); + } + + return $body; + } + + /** + * @throws InvalidArgumentException When an invalid fingerprint is passed + */ + private static function normalizePeerFingerprint(mixed $fingerprint): array + { + if (\is_string($fingerprint)) { + $fingerprint = match (\strlen($fingerprint = str_replace(':', '', $fingerprint))) { + 32 => ['md5' => $fingerprint], + 40 => ['sha1' => $fingerprint], + 44 => ['pin-sha256' => [$fingerprint]], + 64 => ['sha256' => $fingerprint], + default => throw new InvalidArgumentException(sprintf('Cannot auto-detect fingerprint algorithm for "%s".', $fingerprint)), + }; + } elseif (\is_array($fingerprint)) { + foreach ($fingerprint as $algo => $hash) { + $fingerprint[$algo] = 'pin-sha256' === $algo ? (array) $hash : str_replace(':', '', $hash); + } + } else { + throw new InvalidArgumentException(sprintf('Option "peer_fingerprint" must be string or array, "%s" given.', get_debug_type($fingerprint))); + } + + return $fingerprint; + } + + /** + * @throws InvalidArgumentException When the value cannot be json-encoded + */ + private static function jsonEncode(mixed $value, ?int $flags = null, int $maxDepth = 512): string + { + $flags ??= \JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION; + + try { + $value = json_encode($value, $flags | \JSON_THROW_ON_ERROR, $maxDepth); + } catch (\JsonException $e) { + throw new InvalidArgumentException('Invalid value for "json" option: '.$e->getMessage()); + } + + return $value; + } + + /** + * Resolves a URL against a base URI. + * + * @see https://tools.ietf.org/html/rfc3986#section-5.2.2 + * + * @throws InvalidArgumentException When an invalid URL is passed + */ + private static function resolveUrl(array $url, ?array $base, array $queryDefaults = []): array + { + $givenUrl = $url; + + if (null !== $base && '' === ($base['scheme'] ?? '').($base['authority'] ?? '')) { + throw new InvalidArgumentException(sprintf('Invalid "base_uri" option: host or scheme is missing in "%s".', implode('', $base))); + } + + if (null === $url['scheme'] && (null === $base || null === $base['scheme'])) { + throw new InvalidArgumentException(sprintf('Invalid URL: scheme is missing in "%s". Did you forget to add "http(s)://"?', implode('', $base ?? $url))); + } + + if (null === $base && '' === $url['scheme'].$url['authority']) { + throw new InvalidArgumentException(sprintf('Invalid URL: no "base_uri" option was provided and host or scheme is missing in "%s".', implode('', $url))); + } + + if (null !== $url['scheme']) { + $url['path'] = self::removeDotSegments($url['path'] ?? ''); + } else { + if (null !== $url['authority']) { + $url['path'] = self::removeDotSegments($url['path'] ?? ''); + } else { + if (null === $url['path']) { + $url['path'] = $base['path']; + $url['query'] ??= $base['query']; + } else { + if ('/' !== $url['path'][0]) { + if (null === $base['path']) { + $url['path'] = '/'.$url['path']; + } else { + $segments = explode('/', $base['path']); + array_splice($segments, -1, 1, [$url['path']]); + $url['path'] = implode('/', $segments); + } + } + + $url['path'] = self::removeDotSegments($url['path']); + } + + $url['authority'] = $base['authority']; + + if ($queryDefaults) { + $url['query'] = '?'.self::mergeQueryString(substr($url['query'] ?? '', 1), $queryDefaults, false); + } + } + + $url['scheme'] = $base['scheme']; + } + + if ('' === ($url['path'] ?? '')) { + $url['path'] = '/'; + } + + if ('?' === ($url['query'] ?? '')) { + $url['query'] = null; + } + + if (null !== $url['scheme'] && null === $url['authority']) { + throw new InvalidArgumentException(\sprintf('Invalid URL: host is missing in "%s".', implode('', $givenUrl))); + } + + return $url; + } + + /** + * Parses a URL and fixes its encoding if needed. + * + * @throws InvalidArgumentException When an invalid URL is passed + */ + private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array + { + if (false === $parts = parse_url($url)) { + throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url)); + } + + if ($query) { + $parts['query'] = self::mergeQueryString($parts['query'] ?? null, $query, true); + } + + $port = $parts['port'] ?? 0; + + if (null !== $scheme = $parts['scheme'] ?? null) { + if (!isset($allowedSchemes[$scheme = strtolower($scheme)])) { + throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s".', $url)); + } + + $port = $allowedSchemes[$scheme] === $port ? 0 : $port; + $scheme .= ':'; + } + + if (null !== $host = $parts['host'] ?? null) { + if (!\defined('INTL_IDNA_VARIANT_UTS46') && preg_match('/[\x80-\xFF]/', $host)) { + throw new InvalidArgumentException(sprintf('Unsupported IDN "%s", try enabling the "intl" PHP extension or running "composer require symfony/polyfill-intl-idn".', $host)); + } + + $host = \defined('INTL_IDNA_VARIANT_UTS46') ? idn_to_ascii($host, \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46) ?: strtolower($host) : strtolower($host); + $host .= $port ? ':'.$port : ''; + } + + foreach (['user', 'pass', 'path', 'query', 'fragment'] as $part) { + if (!isset($parts[$part])) { + continue; + } + + if (str_contains($parts[$part], '%')) { + // https://tools.ietf.org/html/rfc3986#section-2.3 + $parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', fn ($m) => rawurldecode($m[0]), $parts[$part]); + } + + // https://tools.ietf.org/html/rfc3986#section-3.3 + $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@{}%]++#", fn ($m) => rawurlencode($m[0]), $parts[$part]); + } + + return [ + 'scheme' => $scheme, + 'authority' => null !== $host ? '//'.(isset($parts['user']) ? $parts['user'].(isset($parts['pass']) ? ':'.$parts['pass'] : '').'@' : '').$host : null, + 'path' => isset($parts['path'][0]) ? $parts['path'] : null, + 'query' => isset($parts['query']) ? '?'.$parts['query'] : null, + 'fragment' => isset($parts['fragment']) ? '#'.$parts['fragment'] : null, + ]; + } + + /** + * Removes dot-segments from a path. + * + * @see https://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + private static function removeDotSegments(string $path): string + { + $result = ''; + + while (!\in_array($path, ['', '.', '..'], true)) { + if ('.' === $path[0] && (str_starts_with($path, $p = '../') || str_starts_with($path, $p = './'))) { + $path = substr($path, \strlen($p)); + } elseif ('/.' === $path || str_starts_with($path, '/./')) { + $path = substr_replace($path, '/', 0, 3); + } elseif ('/..' === $path || str_starts_with($path, '/../')) { + $i = strrpos($result, '/'); + $result = $i ? substr($result, 0, $i) : ''; + $path = substr_replace($path, '/', 0, 4); + } else { + $i = strpos($path, '/', 1) ?: \strlen($path); + $result .= substr($path, 0, $i); + $path = substr($path, $i); + } + } + + return $result; + } + + /** + * Merges and encodes a query array with a query string. + * + * @throws InvalidArgumentException When an invalid query-string value is passed + */ + private static function mergeQueryString(?string $queryString, array $queryArray, bool $replace): ?string + { + if (!$queryArray) { + return $queryString; + } + + $query = []; + + if (null !== $queryString) { + foreach (explode('&', $queryString) as $v) { + if ('' !== $v) { + $k = urldecode(explode('=', $v, 2)[0]); + $query[$k] = (isset($query[$k]) ? $query[$k].'&' : '').$v; + } + } + } + + if ($replace) { + foreach ($queryArray as $k => $v) { + if (null === $v) { + unset($query[$k]); + } + } + } + + $queryString = http_build_query($queryArray, '', '&', \PHP_QUERY_RFC3986); + $queryArray = []; + + if ($queryString) { + if (str_contains($queryString, '%')) { + // https://tools.ietf.org/html/rfc3986#section-2.3 + some chars not encoded by browsers + $queryString = strtr($queryString, [ + '%21' => '!', + '%24' => '$', + '%28' => '(', + '%29' => ')', + '%2A' => '*', + '%2F' => '/', + '%3A' => ':', + '%3B' => ';', + '%40' => '@', + '%5B' => '[', + '%5D' => ']', + ]); + } + + foreach (explode('&', $queryString) as $v) { + $queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v; + } + } + + return implode('&', $replace ? array_replace($query, $queryArray) : ($query + $queryArray)); + } + + /** + * Loads proxy configuration from the same environment variables as curl when no proxy is explicitly set. + */ + private static function getProxy(?string $proxy, array $url, ?string $noProxy): ?array + { + if (null === $proxy = self::getProxyUrl($proxy, $url)) { + return null; + } + + $proxy = (parse_url($proxy) ?: []) + ['scheme' => 'http']; + + if (!isset($proxy['host'])) { + throw new TransportException('Invalid HTTP proxy: host is missing.'); + } + + if ('http' === $proxy['scheme']) { + $proxyUrl = 'tcp://'.$proxy['host'].':'.($proxy['port'] ?? '80'); + } elseif ('https' === $proxy['scheme']) { + $proxyUrl = 'ssl://'.$proxy['host'].':'.($proxy['port'] ?? '443'); + } else { + throw new TransportException(sprintf('Unsupported proxy scheme "%s": "http" or "https" expected.', $proxy['scheme'])); + } + + $noProxy ??= $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? ''; + $noProxy = $noProxy ? preg_split('/[\s,]+/', $noProxy) : []; + + return [ + 'url' => $proxyUrl, + 'auth' => isset($proxy['user']) ? 'Basic '.base64_encode(rawurldecode($proxy['user']).':'.rawurldecode($proxy['pass'] ?? '')) : null, + 'no_proxy' => $noProxy, + ]; + } + + private static function getProxyUrl(?string $proxy, array $url): ?string + { + if (null !== $proxy) { + return $proxy; + } + + // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities + $proxy = $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null; + + if ('https:' === $url['scheme']) { + $proxy = $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? $proxy; + } + + return $proxy; + } + + private static function shouldBuffer(array $headers): bool + { + if (null === $contentType = $headers['content-type'][0] ?? null) { + return false; + } + + if (false !== $i = strpos($contentType, ';')) { + $contentType = substr($contentType, 0, $i); + } + + return $contentType && preg_match('#^(?:text/|application/(?:.+\+)?(?:json|xml)$)#i', $contentType); + } +} diff --git a/vendor/symfony/http-client/HttpOptions.php b/vendor/symfony/http-client/HttpOptions.php new file mode 100644 index 0000000..b256bb3 --- /dev/null +++ b/vendor/symfony/http-client/HttpOptions.php @@ -0,0 +1,347 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A helper providing autocompletion for available options. + * + * @see HttpClientInterface for a description of each options. + * + * @author Nicolas Grekas + */ +class HttpOptions +{ + private array $options = []; + + public function toArray(): array + { + return $this->options; + } + + /** + * @return $this + */ + public function setAuthBasic(string $user, #[\SensitiveParameter] string $password = ''): static + { + $this->options['auth_basic'] = $user; + + if ('' !== $password) { + $this->options['auth_basic'] .= ':'.$password; + } + + return $this; + } + + /** + * @return $this + */ + public function setAuthBearer(#[\SensitiveParameter] string $token): static + { + $this->options['auth_bearer'] = $token; + + return $this; + } + + /** + * @return $this + */ + public function setQuery(array $query): static + { + $this->options['query'] = $query; + + return $this; + } + + /** + * @return $this + */ + public function setHeader(string $key, string $value): static + { + $this->options['headers'][$key] = $value; + + return $this; + } + + /** + * @return $this + */ + public function setHeaders(iterable $headers): static + { + $this->options['headers'] = $headers; + + return $this; + } + + /** + * @param array|string|resource|\Traversable|\Closure $body + * + * @return $this + */ + public function setBody(mixed $body): static + { + $this->options['body'] = $body; + + return $this; + } + + /** + * @return $this + */ + public function setJson(mixed $json): static + { + $this->options['json'] = $json; + + return $this; + } + + /** + * @return $this + */ + public function setUserData(mixed $data): static + { + $this->options['user_data'] = $data; + + return $this; + } + + /** + * @return $this + */ + public function setMaxRedirects(int $max): static + { + $this->options['max_redirects'] = $max; + + return $this; + } + + /** + * @return $this + */ + public function setHttpVersion(string $version): static + { + $this->options['http_version'] = $version; + + return $this; + } + + /** + * @return $this + */ + public function setBaseUri(string $uri): static + { + $this->options['base_uri'] = $uri; + + return $this; + } + + /** + * @return $this + */ + public function setVars(array $vars): static + { + $this->options['vars'] = $vars; + + return $this; + } + + /** + * @return $this + */ + public function buffer(bool $buffer): static + { + $this->options['buffer'] = $buffer; + + return $this; + } + + /** + * @return $this + */ + public function setOnProgress(callable $callback): static + { + $this->options['on_progress'] = $callback; + + return $this; + } + + /** + * @return $this + */ + public function resolve(array $hostIps): static + { + $this->options['resolve'] = $hostIps; + + return $this; + } + + /** + * @return $this + */ + public function setProxy(string $proxy): static + { + $this->options['proxy'] = $proxy; + + return $this; + } + + /** + * @return $this + */ + public function setNoProxy(string $noProxy): static + { + $this->options['no_proxy'] = $noProxy; + + return $this; + } + + /** + * @return $this + */ + public function setTimeout(float $timeout): static + { + $this->options['timeout'] = $timeout; + + return $this; + } + + /** + * @return $this + */ + public function setMaxDuration(float $maxDuration): static + { + $this->options['max_duration'] = $maxDuration; + + return $this; + } + + /** + * @return $this + */ + public function bindTo(string $bindto): static + { + $this->options['bindto'] = $bindto; + + return $this; + } + + /** + * @return $this + */ + public function verifyPeer(bool $verify): static + { + $this->options['verify_peer'] = $verify; + + return $this; + } + + /** + * @return $this + */ + public function verifyHost(bool $verify): static + { + $this->options['verify_host'] = $verify; + + return $this; + } + + /** + * @return $this + */ + public function setCaFile(string $cafile): static + { + $this->options['cafile'] = $cafile; + + return $this; + } + + /** + * @return $this + */ + public function setCaPath(string $capath): static + { + $this->options['capath'] = $capath; + + return $this; + } + + /** + * @return $this + */ + public function setLocalCert(string $cert): static + { + $this->options['local_cert'] = $cert; + + return $this; + } + + /** + * @return $this + */ + public function setLocalPk(string $pk): static + { + $this->options['local_pk'] = $pk; + + return $this; + } + + /** + * @return $this + */ + public function setPassphrase(string $passphrase): static + { + $this->options['passphrase'] = $passphrase; + + return $this; + } + + /** + * @return $this + */ + public function setCiphers(string $ciphers): static + { + $this->options['ciphers'] = $ciphers; + + return $this; + } + + /** + * @return $this + */ + public function setPeerFingerprint(string|array $fingerprint): static + { + $this->options['peer_fingerprint'] = $fingerprint; + + return $this; + } + + /** + * @return $this + */ + public function capturePeerCertChain(bool $capture): static + { + $this->options['capture_peer_cert_chain'] = $capture; + + return $this; + } + + /** + * @return $this + */ + public function setExtra(string $name, mixed $value): static + { + $this->options['extra'][$name] = $value; + + return $this; + } +} diff --git a/vendor/symfony/http-client/HttplugClient.php b/vendor/symfony/http-client/HttplugClient.php new file mode 100644 index 0000000..67cf827 --- /dev/null +++ b/vendor/symfony/http-client/HttplugClient.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use GuzzleHttp\Promise\Promise as GuzzlePromise; +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\Utils; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Client\HttpAsyncClient; +use Http\Discovery\Psr17Factory; +use Http\Discovery\Psr17FactoryDiscovery; +use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Uri; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriFactoryInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpClient\Internal\HttplugWaitLoop; +use Symfony\Component\HttpClient\Response\HttplugPromise; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(HttpAsyncClient::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/discovery php-http/async-client-implementation:*".'); +} + +if (!interface_exists(RequestFactoryInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-factory" package is not installed. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); +} + +/** + * An adapter to turn a Symfony HttpClientInterface into an Httplug client. + * + * In comparison to Psr18Client, this client supports asynchronous requests. + * + * Run "composer require php-http/discovery php-http/async-client-implementation:*" + * to get the required dependencies. + * + * @author Nicolas Grekas + */ +final class HttplugClient implements ClientInterface, HttpAsyncClient, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface +{ + private HttpClientInterface $client; + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + + /** + * @var \SplObjectStorage|null + */ + private ?\SplObjectStorage $promisePool; + + private HttplugWaitLoop $waitLoop; + + public function __construct(?HttpClientInterface $client = null, ?ResponseFactoryInterface $responseFactory = null, ?StreamFactoryInterface $streamFactory = null) + { + $this->client = $client ?? HttpClient::create(); + $streamFactory ??= $responseFactory instanceof StreamFactoryInterface ? $responseFactory : null; + $this->promisePool = class_exists(Utils::class) ? new \SplObjectStorage() : null; + + if (null === $responseFactory || null === $streamFactory) { + if (class_exists(Psr17Factory::class)) { + $psr17Factory = new Psr17Factory(); + } elseif (class_exists(NyholmPsr17Factory::class)) { + $psr17Factory = new NyholmPsr17Factory(); + } else { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); + } + + $responseFactory ??= $psr17Factory; + $streamFactory ??= $psr17Factory; + } + + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + $this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory); + } + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->client = $clone->client->withOptions($options); + + return $clone; + } + + public function sendRequest(RequestInterface $request): Psr7ResponseInterface + { + try { + return HttplugWaitLoop::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $this->sendPsr7Request($request), true); + } catch (TransportExceptionInterface $e) { + throw new NetworkException($e->getMessage(), $request, $e); + } + } + + public function sendAsyncRequest(RequestInterface $request): HttplugPromise + { + if (!$promisePool = $this->promisePool) { + throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__)); + } + + try { + $response = $this->sendPsr7Request($request, true); + } catch (NetworkException $e) { + return new HttplugPromise(new RejectedPromise($e)); + } + + $waitLoop = $this->waitLoop; + + $promise = new GuzzlePromise(static function () use ($response, $waitLoop) { + $waitLoop->wait($response); + }, static function () use ($response, $promisePool) { + $response->cancel(); + unset($promisePool[$response]); + }); + + $promisePool[$response] = [$request, $promise]; + + return new HttplugPromise($promise); + } + + /** + * Resolves pending promises that complete before the timeouts are reached. + * + * When $maxDuration is null and $idleTimeout is reached, promises are rejected. + * + * @return int The number of remaining pending promises + */ + public function wait(?float $maxDuration = null, ?float $idleTimeout = null): int + { + return $this->waitLoop->wait(null, $maxDuration, $idleTimeout); + } + + /** + * @param UriInterface|string $uri + */ + public function createRequest(string $method, $uri = ''): RequestInterface + { + if ($this->responseFactory instanceof RequestFactoryInterface) { + $request = $this->responseFactory->createRequest($method, $uri); + } elseif (class_exists(Psr17FactoryDiscovery::class)) { + $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); + } elseif (class_exists(Request::class)) { + $request = new Request($method, $uri); + } else { + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); + } + + return $request; + } + + public function createStream(string $content = ''): StreamInterface + { + return $this->streamFactory->createStream($content); + } + + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + return $this->streamFactory->createStreamFromFile($filename, $mode); + } + + public function createStreamFromResource($resource): StreamInterface + { + return $this->streamFactory->createStreamFromResource($resource); + } + + public function createUri(string $uri = ''): UriInterface + { + if ($this->responseFactory instanceof UriFactoryInterface) { + return $this->responseFactory->createUri($uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findUriFactory()->createUri($uri); + } + + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->wait(); + } + + public function reset(): void + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } + + private function sendPsr7Request(RequestInterface $request, ?bool $buffer = null): ResponseInterface + { + try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + + $options = [ + 'headers' => $request->getHeaders(), + 'body' => $body->getContents(), + 'buffer' => $buffer, + ]; + + if ('1.0' === $request->getProtocolVersion()) { + $options['http_version'] = '1.0'; + } + + return $this->client->request($request->getMethod(), (string) $request->getUri(), $options); + } catch (\InvalidArgumentException $e) { + throw new RequestException($e->getMessage(), $request, $e); + } catch (TransportExceptionInterface $e) { + throw new NetworkException($e->getMessage(), $request, $e); + } + } +} diff --git a/vendor/symfony/http-client/Internal/AmpBody.php b/vendor/symfony/http-client/Internal/AmpBody.php new file mode 100644 index 0000000..abf8fbd --- /dev/null +++ b/vendor/symfony/http-client/Internal/AmpBody.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\ByteStream\InputStream; +use Amp\ByteStream\ResourceInputStream; +use Amp\Http\Client\RequestBody; +use Amp\Promise; +use Amp\Success; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class AmpBody implements RequestBody, InputStream +{ + private ResourceInputStream|\Closure|string $body; + private array $info; + private ?int $offset = 0; + private int $length = -1; + private ?int $uploaded = null; + + /** + * @param \Closure|resource|string $body + */ + public function __construct( + $body, + &$info, + private \Closure $onProgress, + ) { + $this->info = &$info; + + if (\is_resource($body)) { + $this->offset = ftell($body); + $this->length = fstat($body)['size']; + $this->body = new ResourceInputStream($body); + } elseif (\is_string($body)) { + $this->length = \strlen($body); + $this->body = $body; + } else { + $this->body = $body; + } + } + + public function createBodyStream(): InputStream + { + if (null !== $this->uploaded) { + $this->uploaded = null; + + if (\is_string($this->body)) { + $this->offset = 0; + } elseif ($this->body instanceof ResourceInputStream) { + fseek($this->body->getResource(), $this->offset); + } + } + + return $this; + } + + public function getHeaders(): Promise + { + return new Success([]); + } + + public function getBodyLength(): Promise + { + return new Success($this->length - $this->offset); + } + + public function read(): Promise + { + $this->info['size_upload'] += $this->uploaded; + $this->uploaded = 0; + ($this->onProgress)(); + + $chunk = $this->doRead(); + $chunk->onResolve(function ($e, $data) { + if (null !== $data) { + $this->uploaded = \strlen($data); + } else { + $this->info['upload_content_length'] = $this->info['size_upload']; + } + }); + + return $chunk; + } + + public static function rewind(RequestBody $body): RequestBody + { + if (!$body instanceof self) { + return $body; + } + + $body->uploaded = null; + + if ($body->body instanceof ResourceInputStream) { + fseek($body->body->getResource(), $body->offset); + + return new $body($body->body, $body->info, $body->onProgress); + } + + if (\is_string($body->body)) { + $body->offset = 0; + } + + return $body; + } + + private function doRead(): Promise + { + if ($this->body instanceof ResourceInputStream) { + return $this->body->read(); + } + + if (null === $this->offset || !$this->length) { + return new Success(); + } + + if (\is_string($this->body)) { + $this->offset = null; + + return new Success($this->body); + } + + if ('' === $data = ($this->body)(16372)) { + $this->offset = null; + + return new Success(); + } + + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + return new Success($data); + } +} diff --git a/vendor/symfony/http-client/Internal/AmpClientState.php b/vendor/symfony/http-client/Internal/AmpClientState.php new file mode 100644 index 0000000..6c47854 --- /dev/null +++ b/vendor/symfony/http-client/Internal/AmpClientState.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\CancellationToken; +use Amp\Deferred; +use Amp\Http\Client\Connection\ConnectionLimitingPool; +use Amp\Http\Client\Connection\DefaultConnectionFactory; +use Amp\Http\Client\InterceptedHttpClient; +use Amp\Http\Client\Interceptor\RetryRequests; +use Amp\Http\Client\PooledHttpClient; +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; +use Amp\Http\Tunnel\Http1TunnelConnector; +use Amp\Http\Tunnel\Https1TunnelConnector; +use Amp\Promise; +use Amp\Socket\Certificate; +use Amp\Socket\ClientTlsContext; +use Amp\Socket\ConnectContext; +use Amp\Socket\Connector; +use Amp\Socket\DnsConnector; +use Amp\Socket\SocketAddress; +use Amp\Success; +use Psr\Log\LoggerInterface; + +/** + * Internal representation of the Amp client's state. + * + * @author Nicolas Grekas + * + * @internal + */ +final class AmpClientState extends ClientState +{ + public array $dnsCache = []; + public int $responseCount = 0; + public array $pushedResponses = []; + + private array $clients = []; + private \Closure $clientConfigurator; + + public function __construct( + ?callable $clientConfigurator, + private int $maxHostConnections, + private int $maxPendingPushes, + private ?LoggerInterface &$logger, + ) { + $clientConfigurator ??= static fn (PooledHttpClient $client) => new InterceptedHttpClient($client, new RetryRequests(2)); + $this->clientConfigurator = $clientConfigurator(...); + } + + /** + * @return Promise + */ + public function request(array $options, Request $request, CancellationToken $cancellation, array &$info, \Closure $onProgress, &$handle): Promise + { + if ($options['proxy']) { + if ($request->hasHeader('proxy-authorization')) { + $options['proxy']['auth'] = $request->getHeader('proxy-authorization'); + } + + // Matching "no_proxy" should follow the behavior of curl + $host = $request->getUri()->getHost(); + foreach ($options['proxy']['no_proxy'] as $rule) { + $dotRule = '.'.ltrim($rule, '.'); + + if ('*' === $rule || $host === $rule || str_ends_with($host, $dotRule)) { + $options['proxy'] = null; + break; + } + } + } + + $request = clone $request; + + if ($request->hasHeader('proxy-authorization')) { + $request->removeHeader('proxy-authorization'); + } + + if ($options['capture_peer_cert_chain']) { + $info['peer_certificate_chain'] = []; + } + + $request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle)); + $request->setPushHandler(fn ($request, $response): Promise => $this->handlePush($request, $response, $options)); + + ($request->hasHeader('content-length') ? new Success((int) $request->getHeader('content-length')) : $request->getBody()->getBodyLength()) + ->onResolve(static function ($e, $bodySize) use (&$info) { + if (null !== $bodySize && 0 <= $bodySize) { + $info['upload_content_length'] = ((1 + $info['upload_content_length']) ?? 1) - 1 + $bodySize; + } + }); + + [$client, $connector] = $this->getClient($options); + $response = $client->request($request, $cancellation); + $response->onResolve(static function ($e) use ($connector, &$handle) { + if (null === $e) { + $handle = $connector->handle; + } + }); + + return $response; + } + + private function getClient(array $options): array + { + $options = [ + 'bindto' => $options['bindto'] ?: '0', + 'verify_peer' => $options['verify_peer'], + 'capath' => $options['capath'], + 'cafile' => $options['cafile'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + 'ciphers' => $options['ciphers'], + 'capture_peer_cert_chain' => $options['capture_peer_cert_chain'] || $options['peer_fingerprint'], + 'proxy' => $options['proxy'], + 'crypto_method' => $options['crypto_method'], + ]; + + $key = hash('xxh128', serialize($options)); + + if (isset($this->clients[$key])) { + return $this->clients[$key]; + } + + $context = new ClientTlsContext(''); + $options['verify_peer'] || $context = $context->withoutPeerVerification(); + $options['cafile'] && $context = $context->withCaFile($options['cafile']); + $options['capath'] && $context = $context->withCaPath($options['capath']); + $options['local_cert'] && $context = $context->withCertificate(new Certificate($options['local_cert'], $options['local_pk'])); + $options['ciphers'] && $context = $context->withCiphers($options['ciphers']); + $options['capture_peer_cert_chain'] && $context = $context->withPeerCapturing(); + $options['crypto_method'] && $context = $context->withMinimumVersion($options['crypto_method']); + + $connector = $handleConnector = new class() implements Connector { + public DnsConnector $connector; + public string $uri; + /** @var resource|null */ + public $handle; + + public function connect(string $uri, ?ConnectContext $context = null, ?CancellationToken $token = null): Promise + { + $result = $this->connector->connect($this->uri ?? $uri, $context, $token); + $result->onResolve(function ($e, $socket) { + $this->handle = $socket?->getResource(); + }); + + return $result; + } + }; + $connector->connector = new DnsConnector(new AmpResolver($this->dnsCache)); + + $context = (new ConnectContext()) + ->withTcpNoDelay() + ->withTlsContext($context); + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + $connector->uri = 'unix://'.$options['bindto']; + } else { + $context = $context->withBindTo($options['bindto']); + } + } + + if ($options['proxy']) { + $proxyUrl = parse_url($options['proxy']['url']); + $proxySocket = new SocketAddress($proxyUrl['host'], $proxyUrl['port']); + $proxyHeaders = $options['proxy']['auth'] ? ['Proxy-Authorization' => $options['proxy']['auth']] : []; + + if ('ssl' === $proxyUrl['scheme']) { + $connector = new Https1TunnelConnector($proxySocket, $context->getTlsContext(), $proxyHeaders, $connector); + } else { + $connector = new Http1TunnelConnector($proxySocket, $proxyHeaders, $connector); + } + } + + $maxHostConnections = 0 < $this->maxHostConnections ? $this->maxHostConnections : \PHP_INT_MAX; + $pool = new DefaultConnectionFactory($connector, $context); + $pool = ConnectionLimitingPool::byAuthority($maxHostConnections, $pool); + + return $this->clients[$key] = [($this->clientConfigurator)(new PooledHttpClient($pool)), $handleConnector]; + } + + private function handlePush(Request $request, Promise $response, array $options): Promise + { + $deferred = new Deferred(); + $authority = $request->getUri()->getAuthority(); + + if ($this->maxPendingPushes <= \count($this->pushedResponses[$authority] ?? [])) { + $fifoUrl = key($this->pushedResponses[$authority]); + unset($this->pushedResponses[$authority][$fifoUrl]); + $this->logger?->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl)); + } + + $url = (string) $request->getUri(); + $this->logger?->debug(sprintf('Queueing pushed response: "%s"', $url)); + $this->pushedResponses[$authority][] = [$url, $deferred, $request, $response, [ + 'proxy' => $options['proxy'], + 'bindto' => $options['bindto'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + ]]; + + return $deferred->promise(); + } +} diff --git a/vendor/symfony/http-client/Internal/AmpListener.php b/vendor/symfony/http-client/Internal/AmpListener.php new file mode 100644 index 0000000..9997425 --- /dev/null +++ b/vendor/symfony/http-client/Internal/AmpListener.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\Http\Client\Connection\Stream; +use Amp\Http\Client\EventListener; +use Amp\Http\Client\Request; +use Amp\Promise; +use Amp\Success; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class AmpListener implements EventListener +{ + private array $info; + + /** + * @param resource|null $handle + */ + public function __construct( + array &$info, + private array $pinSha256, + private \Closure $onProgress, + private &$handle, + ) { + $info += [ + 'connect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'starttransfer_time' => 0.0, + 'total_time' => 0.0, + 'namelookup_time' => 0.0, + 'primary_ip' => '', + 'primary_port' => 0, + ]; + + $this->info = &$info; + } + + public function startRequest(Request $request): Promise + { + $this->info['start_time'] ??= microtime(true); + ($this->onProgress)(); + + return new Success(); + } + + public function startDnsResolution(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startConnectionCreation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startTlsNegotiation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startSendingRequest(Request $request, Stream $stream): Promise + { + $host = $stream->getRemoteAddress()->getHost(); + + if (str_contains($host, ':')) { + $host = '['.$host.']'; + } + + $this->info['primary_ip'] = $host; + $this->info['primary_port'] = $stream->getRemoteAddress()->getPort(); + $this->info['pretransfer_time'] = microtime(true) - $this->info['start_time']; + $this->info['debug'] .= sprintf("* Connected to %s (%s) port %d\n", $request->getUri()->getHost(), $host, $this->info['primary_port']); + + if ((isset($this->info['peer_certificate_chain']) || $this->pinSha256) && null !== $tlsInfo = $stream->getTlsInfo()) { + foreach ($tlsInfo->getPeerCertificates() as $cert) { + $this->info['peer_certificate_chain'][] = openssl_x509_read($cert->toPem()); + } + + if ($this->pinSha256) { + $pin = openssl_pkey_get_public($this->info['peer_certificate_chain'][0]); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + if (!\in_array($pin, $this->pinSha256, true)) { + throw new TransportException(sprintf('SSL public key does not match pinned public key for "%s".', $this->info['url'])); + } + } + } + ($this->onProgress)(); + + $uri = $request->getUri(); + $requestUri = $uri->getPath() ?: '/'; + + if ('' !== $query = $uri->getQuery()) { + $requestUri .= '?'.$query; + } + + if ('CONNECT' === $method = $request->getMethod()) { + $requestUri = $uri->getHost().': '.($uri->getPort() ?? ('https' === $uri->getScheme() ? 443 : 80)); + } + + $this->info['debug'] .= sprintf("> %s %s HTTP/%s \r\n", $method, $requestUri, $request->getProtocolVersions()[0]); + + foreach ($request->getRawHeaders() as [$name, $value]) { + $this->info['debug'] .= $name.': '.$value."\r\n"; + } + $this->info['debug'] .= "\r\n"; + + return new Success(); + } + + public function completeSendingRequest(Request $request, Stream $stream): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startReceivingResponse(Request $request, Stream $stream): Promise + { + $this->info['starttransfer_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeReceivingResponse(Request $request, Stream $stream): Promise + { + $this->handle = null; + ($this->onProgress)(); + + return new Success(); + } + + public function completeDnsResolution(Request $request): Promise + { + $this->info['namelookup_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeConnectionCreation(Request $request): Promise + { + $this->info['connect_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeTlsNegotiation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function abort(Request $request, \Throwable $cause): Promise + { + return new Success(); + } +} diff --git a/vendor/symfony/http-client/Internal/AmpResolver.php b/vendor/symfony/http-client/Internal/AmpResolver.php new file mode 100644 index 0000000..aff8475 --- /dev/null +++ b/vendor/symfony/http-client/Internal/AmpResolver.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\Dns; +use Amp\Dns\Record; +use Amp\Promise; +use Amp\Success; + +/** + * Handles local overrides for the DNS resolver. + * + * @author Nicolas Grekas + * + * @internal + */ +class AmpResolver implements Dns\Resolver +{ + public function __construct( + private array &$dnsMap, + ) { + } + + public function resolve(string $name, ?int $typeRestriction = null): Promise + { + if (!isset($this->dnsMap[$name]) || !\in_array($typeRestriction, [Record::A, null], true)) { + return Dns\resolver()->resolve($name, $typeRestriction); + } + + return new Success([new Record($this->dnsMap[$name], Record::A, null)]); + } + + public function query(string $name, int $type): Promise + { + if (!isset($this->dnsMap[$name]) || Record::A !== $type) { + return Dns\resolver()->query($name, $type); + } + + return new Success([new Record($this->dnsMap[$name], Record::A, null)]); + } +} diff --git a/vendor/symfony/http-client/Internal/Canary.php b/vendor/symfony/http-client/Internal/Canary.php new file mode 100644 index 0000000..69da0fc --- /dev/null +++ b/vendor/symfony/http-client/Internal/Canary.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Canary +{ + public function __construct( + private \Closure $canceller, + ) { + } + + public function cancel(): void + { + if (isset($this->canceller)) { + $canceller = $this->canceller; + unset($this->canceller); + $canceller(); + } + } + + public function __destruct() + { + $this->cancel(); + } +} diff --git a/vendor/symfony/http-client/Internal/ClientState.php b/vendor/symfony/http-client/Internal/ClientState.php new file mode 100644 index 0000000..bc12cdc --- /dev/null +++ b/vendor/symfony/http-client/Internal/ClientState.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Internal representation of the client state. + * + * @author Alexander M. Turek + * + * @internal + */ +class ClientState +{ + public array $handlesActivity = []; + public array $openHandles = []; + public ?float $lastTimeout = null; +} diff --git a/vendor/symfony/http-client/Internal/CurlClientState.php b/vendor/symfony/http-client/Internal/CurlClientState.php new file mode 100644 index 0000000..ee0bafc --- /dev/null +++ b/vendor/symfony/http-client/Internal/CurlClientState.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Response\CurlResponse; + +/** + * Internal representation of the cURL client's state. + * + * @author Alexander M. Turek + * + * @internal + */ +final class CurlClientState extends ClientState +{ + public ?\CurlMultiHandle $handle; + public ?\CurlShareHandle $share; + public bool $performing = false; + + /** @var PushedResponse[] */ + public array $pushedResponses = []; + public DnsCache $dnsCache; + /** @var float[] */ + public array $pauseExpiries = []; + public int $execCounter = \PHP_INT_MIN; + public ?LoggerInterface $logger = null; + + public static array $curlVersion; + + public function __construct(int $maxHostConnections, int $maxPendingPushes) + { + self::$curlVersion ??= curl_version(); + + $this->handle = curl_multi_init(); + $this->dnsCache = new DnsCache(); + $this->reset(); + + // Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order + if (\defined('CURLPIPE_MULTIPLEX')) { + curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX); + } + if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) { + $maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections; + } + if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) { + curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections); + } + + // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535 + if (0 >= $maxPendingPushes) { + return; + } + + // HTTP/2 push crashes before curl 7.61 + if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > self::$curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & self::$curlVersion['features'])) { + return; + } + + // Clone to prevent a circular reference + $multi = clone $this; + $multi->handle = null; + $multi->share = null; + $multi->pushedResponses = &$this->pushedResponses; + $multi->logger = &$this->logger; + $multi->handlesActivity = &$this->handlesActivity; + $multi->openHandles = &$this->openHandles; + + curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static fn ($parent, $pushed, array $requestHeaders) => $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes)); + } + + public function reset(): void + { + foreach ($this->pushedResponses as $url => $response) { + $this->logger?->debug(sprintf('Unused pushed response: "%s"', $url)); + curl_multi_remove_handle($this->handle, $response->handle); + curl_close($response->handle); + } + + $this->pushedResponses = []; + $this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals; + $this->dnsCache->removals = $this->dnsCache->hostnames = []; + + $this->share = curl_share_init(); + + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS); + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION); + + if (\defined('CURL_LOCK_DATA_CONNECT')) { + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT); + } + } + + private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int + { + $headers = []; + $origin = curl_getinfo($parent, \CURLINFO_EFFECTIVE_URL); + + foreach ($requestHeaders as $h) { + if (false !== $i = strpos($h, ':', 1)) { + $headers[substr($h, 0, $i)][] = substr($h, 1 + $i); + } + } + + if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) { + $this->logger?->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin)); + + return \CURL_PUSH_DENY; + } + + $url = $headers[':scheme'][0].'://'.$headers[':authority'][0]; + + // curl before 7.65 doesn't validate the pushed ":authority" header, + // but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host, + // ignoring domains mentioned as alt-name in the certificate for now (same as curl). + if (!str_starts_with($origin, $url.'/')) { + $this->logger?->debug(sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url)); + + return \CURL_PUSH_DENY; + } + + if ($maxPendingPushes <= \count($this->pushedResponses)) { + $fifoUrl = key($this->pushedResponses); + unset($this->pushedResponses[$fifoUrl]); + $this->logger?->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl)); + } + + $url .= $headers[':path'][0]; + $this->logger?->debug(sprintf('Queueing pushed response: "%s"', $url)); + + $this->pushedResponses[$url] = new PushedResponse(new CurlResponse($this, $pushed), $headers, $this->openHandles[(int) $parent][1] ?? [], $pushed); + + return \CURL_PUSH_OK; + } +} diff --git a/vendor/symfony/http-client/Internal/DnsCache.php b/vendor/symfony/http-client/Internal/DnsCache.php new file mode 100644 index 0000000..cf33ccc --- /dev/null +++ b/vendor/symfony/http-client/Internal/DnsCache.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Cache for resolved DNS queries. + * + * @author Alexander M. Turek + * + * @internal + */ +final class DnsCache +{ + /** + * Resolved hostnames (hostname => IP address). + * + * @var string[] + */ + public array $hostnames = []; + + /** + * @var string[] + */ + public array $removals = []; + + /** + * @var string[] + */ + public array $evictions = []; +} diff --git a/vendor/symfony/http-client/Internal/HttplugWaitLoop.php b/vendor/symfony/http-client/Internal/HttplugWaitLoop.php new file mode 100644 index 0000000..aa172b8 --- /dev/null +++ b/vendor/symfony/http-client/Internal/HttplugWaitLoop.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Http\Client\Exception\NetworkException; +use Http\Promise\Promise; +use Psr\Http\Message\RequestInterface as Psr7RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class HttplugWaitLoop +{ + /** + * @param \SplObjectStorage|null $promisePool + */ + public function __construct( + private HttpClientInterface $client, + private ?\SplObjectStorage $promisePool, + private ResponseFactoryInterface $responseFactory, + private StreamFactoryInterface $streamFactory, + ) { + } + + public function wait(?ResponseInterface $pendingResponse, ?float $maxDuration = null, ?float $idleTimeout = null): int + { + if (!$this->promisePool) { + return 0; + } + + $guzzleQueue = \GuzzleHttp\Promise\Utils::queue(); + + if (0.0 === $remainingDuration = $maxDuration) { + $idleTimeout = 0.0; + } elseif (null !== $maxDuration) { + $startTime = hrtime(true) / 1E9; + $idleTimeout = max(0.0, min($maxDuration / 5, $idleTimeout ?? $maxDuration)); + } + + do { + foreach ($this->client->stream($this->promisePool, $idleTimeout) as $response => $chunk) { + try { + if (null !== $maxDuration && $chunk->isTimeout()) { + goto check_duration; + } + + if ($chunk->isFirst()) { + // Deactivate throwing on 3/4/5xx + $response->getStatusCode(); + } + + if (!$chunk->isLast()) { + goto check_duration; + } + + if ([, $promise] = $this->promisePool[$response] ?? null) { + unset($this->promisePool[$response]); + $promise->resolve(self::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $response, true)); + } + } catch (\Exception $e) { + if ([$request, $promise] = $this->promisePool[$response] ?? null) { + unset($this->promisePool[$response]); + + if ($e instanceof TransportExceptionInterface) { + $e = new NetworkException($e->getMessage(), $request, $e); + } + + $promise->reject($e); + } + } + + $guzzleQueue->run(); + + if ($pendingResponse === $response) { + return $this->promisePool->count(); + } + + check_duration: + if (null !== $maxDuration && $idleTimeout && $idleTimeout > $remainingDuration = max(0.0, $maxDuration - hrtime(true) / 1E9 + $startTime)) { + $idleTimeout = $remainingDuration / 5; + break; + } + } + + if (!$count = $this->promisePool->count()) { + return 0; + } + } while (null === $maxDuration || 0 < $remainingDuration); + + return $count; + } + + public static function createPsr7Response(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, HttpClientInterface $client, ResponseInterface $response, bool $buffer): Psr7ResponseInterface + { + $responseParameters = [$response->getStatusCode()]; + + foreach ($response->getInfo('response_headers') as $h) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (?:\d\d\d) (.+)#', $h, $m)) { + $responseParameters[1] = $m[1]; + } + } + + $psrResponse = $responseFactory->createResponse(...$responseParameters); + + foreach ($response->getHeaders(false) as $name => $values) { + foreach ($values as $value) { + try { + $psrResponse = $psrResponse->withAddedHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } + } + } + + if ($response instanceof StreamableInterface) { + $body = $streamFactory->createStreamFromResource($response->toStream(false)); + } elseif (!$buffer) { + $body = $streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $client)); + } else { + $body = $streamFactory->createStream($response->getContent(false)); + } + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $psrResponse->withBody($body); + } +} diff --git a/vendor/symfony/http-client/Internal/NativeClientState.php b/vendor/symfony/http-client/Internal/NativeClientState.php new file mode 100644 index 0000000..75f23a9 --- /dev/null +++ b/vendor/symfony/http-client/Internal/NativeClientState.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Internal representation of the native client's state. + * + * @author Alexander M. Turek + * + * @internal + */ +final class NativeClientState extends ClientState +{ + public int $id; + public int $maxHostConnections = \PHP_INT_MAX; + public int $responseCount = 0; + /** @var string[] */ + public array $dnsCache = []; + public bool $sleep = false; + /** @var int[] */ + public array $hosts = []; + + public function __construct() + { + $this->id = random_int(\PHP_INT_MIN, \PHP_INT_MAX); + } + + public function reset(): void + { + $this->responseCount = 0; + $this->dnsCache = []; + $this->hosts = []; + } +} diff --git a/vendor/symfony/http-client/Internal/PushedResponse.php b/vendor/symfony/http-client/Internal/PushedResponse.php new file mode 100644 index 0000000..e77069d --- /dev/null +++ b/vendor/symfony/http-client/Internal/PushedResponse.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Symfony\Component\HttpClient\Response\CurlResponse; + +/** + * A pushed response with its request headers. + * + * @author Alexander M. Turek + * + * @internal + */ +final class PushedResponse +{ + public function __construct( + public CurlResponse $response, + public array $requestHeaders, + public array $parentOptions, + public \CurlHandle $handle, + ) { + } +} diff --git a/vendor/symfony/http-client/LICENSE b/vendor/symfony/http-client/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/http-client/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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. diff --git a/vendor/symfony/http-client/Messenger/PingWebhookMessage.php b/vendor/symfony/http-client/Messenger/PingWebhookMessage.php new file mode 100644 index 0000000..17fa17f --- /dev/null +++ b/vendor/symfony/http-client/Messenger/PingWebhookMessage.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Messenger; + +/** + * @author Kevin Bond + */ +final class PingWebhookMessage implements \Stringable +{ + public function __construct( + public readonly string $method, + public readonly string $url, + public readonly array $options = [], + public readonly bool $throw = true, + ) { + } + + public function __toString(): string + { + return "[{$this->method}] {$this->url}"; + } +} diff --git a/vendor/symfony/http-client/Messenger/PingWebhookMessageHandler.php b/vendor/symfony/http-client/Messenger/PingWebhookMessageHandler.php new file mode 100644 index 0000000..a79eed2 --- /dev/null +++ b/vendor/symfony/http-client/Messenger/PingWebhookMessageHandler.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Messenger; + +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Kevin Bond + */ +class PingWebhookMessageHandler +{ + public function __construct( + private readonly HttpClientInterface $httpClient, + ) { + } + + public function __invoke(PingWebhookMessage $message): ResponseInterface + { + $response = $this->httpClient->request($message->method, $message->url, $message->options); + $response->getHeaders($message->throw); + + return $response; + } +} diff --git a/vendor/symfony/http-client/MockHttpClient.php b/vendor/symfony/http-client/MockHttpClient.php new file mode 100644 index 0000000..a0e08f9 --- /dev/null +++ b/vendor/symfony/http-client/MockHttpClient.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A test-friendly HttpClient that doesn't make actual HTTP requests. + * + * @author Nicolas Grekas + */ +class MockHttpClient implements HttpClientInterface, ResetInterface +{ + use HttpClientTrait; + + private ResponseInterface|\Closure|iterable|null $responseFactory; + private int $requestsCount = 0; + private array $defaultOptions = []; + + /** + * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory + */ + public function __construct(callable|iterable|ResponseInterface|null $responseFactory = null, ?string $baseUri = 'https://example.com') + { + $this->setResponseFactory($responseFactory); + $this->defaultOptions['base_uri'] = $baseUri; + } + + /** + * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory + */ + public function setResponseFactory($responseFactory): void + { + if ($responseFactory instanceof ResponseInterface) { + $responseFactory = [$responseFactory]; + } + + if (!$responseFactory instanceof \Iterator && null !== $responseFactory && !\is_callable($responseFactory)) { + $responseFactory = (static function () use ($responseFactory) { + yield from $responseFactory; + })(); + } + + $this->responseFactory = !\is_callable($responseFactory) ? $responseFactory : $responseFactory(...); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); + $url = implode('', $url); + + if (null === $this->responseFactory) { + $response = new MockResponse(); + } elseif (\is_callable($this->responseFactory)) { + $response = ($this->responseFactory)($method, $url, $options); + } elseif (!$this->responseFactory->valid()) { + throw new TransportException($this->requestsCount ? 'No more response left in the response factory iterator passed to MockHttpClient: the number of requests exceeds the number of responses.' : 'The response factory iterator passed to MockHttpClient is empty.'); + } else { + $responseFactory = $this->responseFactory->current(); + $response = \is_callable($responseFactory) ? $responseFactory($method, $url, $options) : $responseFactory; + $this->responseFactory->next(); + } + ++$this->requestsCount; + + if (!$response instanceof ResponseInterface) { + throw new TransportException(sprintf('The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "%s" given.', get_debug_type($response))); + } + + return MockResponse::fromRequest($method, $url, $options, $response); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof ResponseInterface) { + $responses = [$responses]; + } + + return new ResponseStream(MockResponse::stream($responses, $timeout)); + } + + public function getRequestsCount(): int + { + return $this->requestsCount; + } + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true); + + return $clone; + } + + public function reset(): void + { + $this->requestsCount = 0; + } +} diff --git a/vendor/symfony/http-client/NativeHttpClient.php b/vendor/symfony/http-client/NativeHttpClient.php new file mode 100644 index 0000000..db5cee6 --- /dev/null +++ b/vendor/symfony/http-client/NativeHttpClient.php @@ -0,0 +1,470 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\NativeClientState; +use Symfony\Component\HttpClient\Response\NativeResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A portable implementation of the HttpClientInterface contracts based on PHP stream wrappers. + * + * PHP stream wrappers are able to fetch response bodies concurrently, + * but each request is opened synchronously. + * + * @author Nicolas Grekas + */ +final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + use LoggerAwareTrait; + + public const OPTIONS_DEFAULTS = HttpClientInterface::OPTIONS_DEFAULTS + [ + 'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, + ]; + + private array $defaultOptions = self::OPTIONS_DEFAULTS; + private static array $emptyDefaults = self::OPTIONS_DEFAULTS; + + private NativeClientState $multi; + + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to open + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], int $maxHostConnections = 6) + { + $this->defaultOptions['buffer'] ??= self::shouldBuffer(...); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new NativeClientState(); + $this->multi->maxHostConnections = 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX; + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + throw new TransportException(__CLASS__.' cannot bind to local Unix sockets, use e.g. CurlHttpClient instead.'); + } + if (str_starts_with($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (str_starts_with($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } + } + + $hasContentLength = isset($options['normalized_headers']['content-length']); + $hasBody = '' !== $options['body'] || 'POST' === $method || $hasContentLength; + + $options['body'] = self::getBodyAsString($options['body']); + + if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) { + unset($options['normalized_headers']['transfer-encoding']); + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + $options['body'] = self::dechunk($options['body']); + } + if ('' === $options['body'] && $hasBody && !$hasContentLength) { + $options['headers'][] = 'Content-Length: 0'; + } + if ($hasBody && !isset($options['normalized_headers']['content-type'])) { + $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; + } + + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + // gzip is the most widely available algo, no need to deal with deflate + $options['headers'][] = 'Accept-Encoding: gzip'; + } + + if ($options['peer_fingerprint']) { + if (isset($options['peer_fingerprint']['pin-sha256']) && 1 === \count($options['peer_fingerprint'])) { + throw new TransportException(__CLASS__.' cannot verify "pin-sha256" fingerprints, please provide a "sha256" one.'); + } + + unset($options['peer_fingerprint']['pin-sha256']); + } + + $info = [ + 'response_headers' => [], + 'url' => $url, + 'error' => null, + 'canceled' => false, + 'http_method' => $method, + 'http_code' => 0, + 'redirect_count' => 0, + 'start_time' => 0.0, + 'connect_time' => 0.0, + 'redirect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'starttransfer_time' => 0.0, + 'total_time' => 0.0, + 'namelookup_time' => 0.0, + 'size_upload' => 0, + 'size_download' => 0, + 'size_body' => \strlen($options['body']), + 'primary_ip' => '', + 'primary_port' => 'http:' === $url['scheme'] ? 80 : 443, + 'debug' => \extension_loaded('curl') ? '' : "* Enable the curl extension for better performance\n", + ]; + + if ($onProgress = $options['on_progress']) { + $maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : \INF; + $onProgress = static function (...$progress) use ($onProgress, &$info, $maxDuration) { + if ($info['total_time'] >= $maxDuration) { + throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url']))); + } + + $progressInfo = $info; + $progressInfo['url'] = implode('', $info['url']); + unset($progressInfo['size_body']); + + // Memoize the last progress to ease calling the callback periodically when no network transfer happens + static $lastProgress = [0, 0]; + + if ($progress && -1 === $progress[0]) { + // Response completed + $lastProgress[0] = max($lastProgress); + } else { + $lastProgress = $progress ?: $lastProgress; + } + + $onProgress($lastProgress[0], $lastProgress[1], $progressInfo); + }; + } elseif (0 < $options['max_duration']) { + $maxDuration = $options['max_duration']; + $onProgress = static function () use (&$info, $maxDuration): void { + if ($info['total_time'] >= $maxDuration) { + throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url']))); + } + }; + } + + // Always register a notification callback to compute live stats about the response + $notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) { + $info['total_time'] = microtime(true) - $info['start_time']; + + if (\STREAM_NOTIFY_PROGRESS === $code) { + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; + $info['size_upload'] += $dlNow ? 0 : $info['size_body']; + $info['size_download'] = $dlNow; + } elseif (\STREAM_NOTIFY_CONNECT === $code) { + $info['connect_time'] = $info['total_time']; + $info['debug'] .= $info['request_header']; + unset($info['request_header']); + } else { + return; + } + + if ($onProgress) { + $onProgress($dlNow, $dlSize); + } + }; + + if ($options['resolve']) { + $this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache; + } + + $this->logger?->info(sprintf('Request: "%s %s"', $method, implode('', $url))); + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient (Native)'; + } + + if (0 < $options['max_duration']) { + $options['timeout'] = min($options['max_duration'], $options['timeout']); + } + + switch ($cryptoMethod = $options['crypto_method']) { + case \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT: + $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + // no break + case \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT: + $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + // no break + case \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT: + $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT; + } + + $context = [ + 'http' => [ + 'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'), + 'method' => $method, + 'content' => $options['body'], + 'ignore_errors' => true, + 'curl_verify_ssl_peer' => $options['verify_peer'], + 'curl_verify_ssl_host' => $options['verify_host'], + 'auto_decode' => false, // Disable dechunk filter, it's incompatible with stream_select() + 'timeout' => $options['timeout'], + 'follow_location' => false, // We follow redirects ourselves - the native logic is too limited + ], + 'ssl' => array_filter([ + 'verify_peer' => $options['verify_peer'], + 'verify_peer_name' => $options['verify_host'], + 'cafile' => $options['cafile'], + 'capath' => $options['capath'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + 'passphrase' => $options['passphrase'], + 'ciphers' => $options['ciphers'], + 'peer_fingerprint' => $options['peer_fingerprint'], + 'capture_peer_cert_chain' => $options['capture_peer_cert_chain'], + 'allow_self_signed' => (bool) $options['peer_fingerprint'], + 'SNI_enabled' => true, + 'disable_compression' => true, + 'crypto_method' => $cryptoMethod, + ], static fn ($v) => null !== $v), + 'socket' => [ + 'bindto' => $options['bindto'], + 'tcp_nodelay' => true, + ], + ]; + + $context = stream_context_create($context, ['notification' => $notification]); + + $resolver = static function ($multi) use ($context, $options, $url, &$info, $onProgress) { + [$host, $port] = self::parseHostPort($url, $info); + + if (!isset($options['normalized_headers']['host'])) { + $options['headers'][] = 'Host: '.$host.$port; + } + + $proxy = self::getProxy($options['proxy'], $url, $options['no_proxy']); + + if (!self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, 'https:' === $url['scheme'])) { + $ip = self::dnsResolve($host, $multi, $info, $onProgress); + $url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host)); + } + + return [self::createRedirectResolver($options, $host, $port, $proxy, $info, $onProgress), implode('', $url)]; + }; + + return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolver, $onProgress, $this->logger); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof NativeResponse) { + $responses = [$responses]; + } + + return new ResponseStream(NativeResponse::stream($responses, $timeout)); + } + + public function reset(): void + { + $this->multi->reset(); + } + + private static function getBodyAsString($body): string + { + if (\is_resource($body)) { + return stream_get_contents($body); + } + + if (!$body instanceof \Closure) { + return $body; + } + + $result = ''; + + while ('' !== $data = $body(self::$CHUNK_SIZE)) { + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + $result .= $data; + } + + return $result; + } + + /** + * Extracts the host and the port from the URL. + */ + private static function parseHostPort(array $url, array &$info): array + { + if ($port = parse_url($url['authority'], \PHP_URL_PORT) ?: '') { + $info['primary_port'] = $port; + $port = ':'.$port; + } else { + $info['primary_port'] = 'http:' === $url['scheme'] ? 80 : 443; + } + + return [parse_url($url['authority'], \PHP_URL_HOST), $port]; + } + + /** + * Resolves the IP of the host using the local DNS cache if possible. + */ + private static function dnsResolve(string $host, NativeClientState $multi, array &$info, ?\Closure $onProgress): string + { + if (null === $ip = $multi->dnsCache[$host] ?? null) { + $info['debug'] .= "* Hostname was NOT found in DNS cache\n"; + $now = microtime(true); + + if (!$ip = gethostbynamel($host)) { + throw new TransportException(sprintf('Could not resolve host "%s".', $host)); + } + + $info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now); + $multi->dnsCache[$host] = $ip = $ip[0]; + $info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n"; + } else { + $info['debug'] .= "* Hostname was found in DNS cache\n"; + } + + $info['primary_ip'] = $ip; + + if ($onProgress) { + // Notify DNS resolution + $onProgress(); + } + + return $ip; + } + + /** + * Handles redirects - the native logic is too buggy to be used. + */ + private static function createRedirectResolver(array $options, string $host, string $port, ?array $proxy, array &$info, ?\Closure $onProgress): \Closure + { + $redirectHeaders = []; + if (0 < $maxRedirects = $options['max_redirects']) { + $redirectHeaders = ['host' => $host, 'port' => $port]; + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Host:')); + + if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) { + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static fn ($h) => 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:')); + } + } + + return static function (NativeClientState $multi, ?string $location, $context) use (&$redirectHeaders, $proxy, &$info, $maxRedirects, $onProgress): ?string { + if (null === $location || $info['http_code'] < 300 || 400 <= $info['http_code']) { + $info['redirect_url'] = null; + + return null; + } + + try { + $url = self::parseUrl($location); + } catch (InvalidArgumentException) { + $info['redirect_url'] = null; + + return null; + } + + $url = self::resolveUrl($url, $info['url']); + $info['redirect_url'] = implode('', $url); + + if ($info['redirect_count'] >= $maxRedirects) { + return null; + } + + $info['url'] = $url; + ++$info['redirect_count']; + $info['redirect_time'] = microtime(true) - $info['start_time']; + + // Do like curl and browsers: turn POST to GET on 301, 302 and 303 + if (\in_array($info['http_code'], [301, 302, 303], true)) { + $options = stream_context_get_options($context)['http']; + + if ('POST' === $options['method'] || 303 === $info['http_code']) { + $info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET'; + $options['content'] = ''; + $filterContentHeaders = static fn ($h) => 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); + $options['header'] = array_filter($options['header'], $filterContentHeaders); + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); + $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); + + if (\PHP_VERSION_ID >= 80300) { + stream_context_set_options($context, ['http' => $options]); + } else { + stream_context_set_option($context, ['http' => $options]); + } + } + } + + [$host, $port] = self::parseHostPort($url, $info); + + if (false !== (parse_url($location, \PHP_URL_HOST) ?? false)) { + // Authorization and Cookie headers MUST NOT follow except for the initial host name + $requestHeaders = $redirectHeaders['host'] === $host && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; + $requestHeaders[] = 'Host: '.$host.$port; + $dnsResolve = !self::configureHeadersAndProxy($context, $host, $requestHeaders, $proxy, 'https:' === $url['scheme']); + } else { + $dnsResolve = isset(stream_context_get_options($context)['ssl']['peer_name']); + } + + if ($dnsResolve) { + $ip = self::dnsResolve($host, $multi, $info, $onProgress); + $url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host)); + } + + return implode('', $url); + }; + } + + private static function configureHeadersAndProxy($context, string $host, array $requestHeaders, ?array $proxy, bool $isSsl): bool + { + if (null === $proxy) { + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', $host); + + return false; + } + + // Matching "no_proxy" should follow the behavior of curl + + foreach ($proxy['no_proxy'] as $rule) { + $dotRule = '.'.ltrim($rule, '.'); + + if ('*' === $rule || $host === $rule || str_ends_with($host, $dotRule)) { + stream_context_set_option($context, 'http', 'proxy', null); + stream_context_set_option($context, 'http', 'request_fulluri', false); + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', $host); + + return false; + } + } + + if (null !== $proxy['auth']) { + $requestHeaders[] = 'Proxy-Authorization: '.$proxy['auth']; + } + + stream_context_set_option($context, 'http', 'proxy', $proxy['url']); + stream_context_set_option($context, 'http', 'request_fulluri', !$isSsl); + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', null); + + return true; + } +} diff --git a/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php b/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php new file mode 100644 index 0000000..3b930ad --- /dev/null +++ b/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Decorator that blocks requests to private networks by default. + * + * @author Hallison Boaventura + */ +final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + + private HttpClientInterface $client; + private string|array|null $subnets; + + /** + * @param string|array|null $subnets String or array of subnets using CIDR notation that will be used by IpUtils. + * If null is passed, the standard private subnets will be used. + */ + public function __construct(HttpClientInterface $client, string|array|null $subnets = null) + { + if (!class_exists(IpUtils::class)) { + throw new \LogicException(sprintf('You cannot use "%s" if the HttpFoundation component is not installed. Try running "composer require symfony/http-foundation".', __CLASS__)); + } + + $this->client = $client; + $this->subnets = $subnets; + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $onProgress = $options['on_progress'] ?? null; + if (null !== $onProgress && !\is_callable($onProgress)) { + throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress))); + } + + $subnets = $this->subnets; + + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets): void { + static $lastPrimaryIp = ''; + if ($info['primary_ip'] !== $lastPrimaryIp) { + if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? IpUtils::PRIVATE_SUBNETS)) { + throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url'])); + } + + $lastPrimaryIp = $info['primary_ip']; + } + + null !== $onProgress && $onProgress($dlNow, $dlSize, $info); + }; + + return $this->client->request($method, $url, $options); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + /** + * @deprecated since Symfony 7.1, configure the logger on the wrapper HTTP client directly instead + */ + public function setLogger(LoggerInterface $logger): void + { + trigger_deprecation('symfony/http-client', '7.1', 'Configure the logger on the wrapper HTTP client directly instead.'); + + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + public function reset(): void + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/vendor/symfony/http-client/Psr18Client.php b/vendor/symfony/http-client/Psr18Client.php new file mode 100644 index 0000000..d46a7b1 --- /dev/null +++ b/vendor/symfony/http-client/Psr18Client.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Http\Discovery\Psr17Factory; +use Http\Discovery\Psr17FactoryDiscovery; +use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Uri; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Http\Client\RequestExceptionInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriFactoryInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpClient\Internal\HttplugWaitLoop; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(ClientInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require php-http/discovery psr/http-client-implementation:*".'); +} + +if (!interface_exists(RequestFactoryInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); +} + +/** + * An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface. + * + * Run "composer require php-http/discovery psr/http-client-implementation:*" + * to get the required dependencies. + * + * @author Nicolas Grekas + */ +final class Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface +{ + private HttpClientInterface $client; + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + + public function __construct(?HttpClientInterface $client = null, ?ResponseFactoryInterface $responseFactory = null, ?StreamFactoryInterface $streamFactory = null) + { + $this->client = $client ?? HttpClient::create(); + $streamFactory ??= $responseFactory instanceof StreamFactoryInterface ? $responseFactory : null; + + if (null === $responseFactory || null === $streamFactory) { + if (class_exists(Psr17Factory::class)) { + $psr17Factory = new Psr17Factory(); + } elseif (class_exists(NyholmPsr17Factory::class)) { + $psr17Factory = new NyholmPsr17Factory(); + } else { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); + } + + $responseFactory ??= $psr17Factory; + $streamFactory ??= $psr17Factory; + } + + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->client = $clone->client->withOptions($options); + + return $clone; + } + + public function sendRequest(RequestInterface $request): ResponseInterface + { + try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + + $options = [ + 'headers' => $request->getHeaders(), + 'body' => $body->getContents(), + ]; + + if ('1.0' === $request->getProtocolVersion()) { + $options['http_version'] = '1.0'; + } + + $response = $this->client->request($request->getMethod(), (string) $request->getUri(), $options); + + return HttplugWaitLoop::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $response, false); + } catch (TransportExceptionInterface $e) { + if ($e instanceof \InvalidArgumentException) { + throw new Psr18RequestException($e, $request); + } + + throw new Psr18NetworkException($e, $request); + } + } + + public function createRequest(string $method, $uri): RequestInterface + { + if ($this->responseFactory instanceof RequestFactoryInterface) { + return $this->responseFactory->createRequest($method, $uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); + } + + if (class_exists(Request::class)) { + return new Request($method, $uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); + } + + public function createStream(string $content = ''): StreamInterface + { + $stream = $this->streamFactory->createStream($content); + + if ($stream->isSeekable()) { + $stream->seek(0); + } + + return $stream; + } + + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + return $this->streamFactory->createStreamFromFile($filename, $mode); + } + + public function createStreamFromResource($resource): StreamInterface + { + return $this->streamFactory->createStreamFromResource($resource); + } + + public function createUri(string $uri = ''): UriInterface + { + if ($this->responseFactory instanceof UriFactoryInterface) { + return $this->responseFactory->createUri($uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri); + } + + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); + } + + public function reset(): void + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} + +/** + * @internal + */ +class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface +{ + private RequestInterface $request; + + public function __construct(TransportExceptionInterface $e, RequestInterface $request) + { + parent::__construct($e->getMessage(), 0, $e); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} + +/** + * @internal + */ +class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface +{ + private RequestInterface $request; + + public function __construct(TransportExceptionInterface $e, RequestInterface $request) + { + parent::__construct($e->getMessage(), 0, $e); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/vendor/symfony/http-client/README.md b/vendor/symfony/http-client/README.md new file mode 100644 index 0000000..214489b --- /dev/null +++ b/vendor/symfony/http-client/README.md @@ -0,0 +1,13 @@ +HttpClient component +==================== + +The HttpClient component provides powerful methods to fetch HTTP resources synchronously or asynchronously. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_client.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/http-client/Response/AmpResponse.php b/vendor/symfony/http-client/Response/AmpResponse.php new file mode 100644 index 0000000..6f73c91 --- /dev/null +++ b/vendor/symfony/http-client/Response/AmpResponse.php @@ -0,0 +1,451 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Amp\ByteStream\StreamException; +use Amp\CancellationTokenSource; +use Amp\Coroutine; +use Amp\Deferred; +use Amp\Http\Client\HttpException; +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; +use Amp\Loop; +use Amp\Promise; +use Amp\Success; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\InformationalChunk; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\HttpClientTrait; +use Symfony\Component\HttpClient\Internal\AmpBody; +use Symfony\Component\HttpClient\Internal\AmpClientState; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class AmpResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait; + + private static string $nextId = 'a'; + + private ?array $options; + private \Closure $onProgress; + + private static ?string $delay = null; + + /** + * @internal + */ + public function __construct( + private AmpClientState $multi, + Request $request, + array $options, + ?LoggerInterface $logger, + ) { + $this->options = &$options; + $this->logger = $logger; + $this->timeout = $options['timeout']; + $this->shouldBuffer = $options['buffer']; + + if ($this->inflate = \extension_loaded('zlib') && !$request->hasHeader('accept-encoding')) { + $request->setHeader('Accept-Encoding', 'gzip'); + } + + $this->initializer = static fn (self $response) => null !== $response->options; + + $info = &$this->info; + $headers = &$this->headers; + $canceller = new CancellationTokenSource(); + $handle = &$this->handle; + + $info['url'] = (string) $request->getUri(); + $info['http_method'] = $request->getMethod(); + $info['start_time'] = null; + $info['redirect_url'] = null; + $info['original_url'] = $info['url']; + $info['redirect_time'] = 0.0; + $info['redirect_count'] = 0; + $info['size_upload'] = 0.0; + $info['size_download'] = 0.0; + $info['upload_content_length'] = -1.0; + $info['download_content_length'] = -1.0; + $info['user_data'] = $options['user_data']; + $info['max_duration'] = $options['max_duration']; + $info['debug'] = ''; + + $onProgress = $options['on_progress'] ?? static function () {}; + $onProgress = $this->onProgress = static function () use (&$info, $onProgress) { + $info['total_time'] = microtime(true) - $info['start_time']; + $onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info); + }; + + $pauseDeferred = new Deferred(); + $pause = new Success(); + + $throttleWatcher = null; + + $this->id = $id = self::$nextId++; + Loop::defer(static function () use ($request, $multi, $id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) { + return new Coroutine(self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause)); + }); + + $info['pause_handler'] = static function (float $duration) use (&$throttleWatcher, &$pauseDeferred, &$pause) { + if (null !== $throttleWatcher) { + Loop::cancel($throttleWatcher); + } + + $pause = $pauseDeferred->promise(); + + if ($duration <= 0) { + $deferred = $pauseDeferred; + $pauseDeferred = new Deferred(); + $deferred->resolve(); + } else { + $throttleWatcher = Loop::delay(ceil(1000 * $duration), static function () use (&$pauseDeferred) { + $deferred = $pauseDeferred; + $pauseDeferred = new Deferred(); + $deferred->resolve(); + }); + } + }; + + $multi->lastTimeout = null; + $multi->openHandles[$id] = $id; + ++$multi->responseCount; + + $this->canary = new Canary(static function () use ($canceller, $multi, $id) { + $canceller->cancel(); + unset($multi->openHandles[$id], $multi->handlesActivity[$id]); + }); + } + + public function getInfo(?string $type = null): mixed + { + return null !== $type ? $this->info[$type] ?? null : $this->info; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + try { + $this->doDestruct(); + } finally { + // Clear the DNS cache when all requests completed + if (0 >= --$this->multi->responseCount) { + $this->multi->responseCount = 0; + $this->multi->dnsCache = []; + } + } + } + + private static function schedule(self $response, array &$runningResponses): void + { + if (isset($runningResponses[0])) { + $runningResponses[0][1][$response->id] = $response; + } else { + $runningResponses[0] = [$response->multi, [$response->id => $response]]; + } + + if (!isset($response->multi->openHandles[$response->id])) { + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * @param AmpClientState $multi + */ + private static function perform(ClientState $multi, ?array &$responses = null): void + { + if ($responses) { + foreach ($responses as $response) { + try { + if ($response->info['start_time']) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + ($response->onProgress)(); + } + } catch (\Throwable $e) { + $multi->handlesActivity[$response->id][] = null; + $multi->handlesActivity[$response->id][] = $e; + } + } + } + } + + /** + * @param AmpClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + $timeout += hrtime(true) / 1E9; + self::$delay = Loop::defer(static function () use ($timeout) { + if (0 < $timeout -= hrtime(true) / 1E9) { + self::$delay = Loop::delay(ceil(1000 * $timeout), Loop::stop(...)); + } else { + Loop::stop(); + } + }); + + Loop::run(); + + return null === self::$delay ? 1 : 0; + } + + private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause): \Generator + { + $request->setInformationalResponseHandler(static function (Response $response) use ($multi, $id, &$info, &$headers) { + self::addResponseHeaders($response, $info, $headers); + $multi->handlesActivity[$id][] = new InformationalChunk($response->getStatus(), $response->getHeaders()); + self::stopLoop(); + }); + + try { + /* @var Response $response */ + if (null === $response = yield from self::getPushedResponse($request, $multi, $info, $headers, $options, $logger)) { + $logger?->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url'])); + + $response = yield from self::followRedirects($request, $multi, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause); + } + + $options = null; + + $multi->handlesActivity[$id][] = new FirstChunk(); + + if ('HEAD' === $response->getRequest()->getMethod() || \in_array($info['http_code'], [204, 304], true)) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + self::stopLoop(); + + return; + } + + if ($response->hasHeader('content-length')) { + $info['download_content_length'] = (float) $response->getHeader('content-length'); + } + + $body = $response->getBody(); + + while (true) { + self::stopLoop(); + + yield $pause; + + if (null === $data = yield $body->read()) { + break; + } + + $info['size_download'] += \strlen($data); + $multi->handlesActivity[$id][] = $data; + } + + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + } catch (\Throwable $e) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $e; + } finally { + $info['download_content_length'] = $info['size_download']; + } + + self::stopLoop(); + } + + private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause): \Generator + { + yield $pause; + + $originRequest->setBody(new AmpBody($options['body'], $info, $onProgress)); + $response = yield $multi->request($options, $originRequest, $canceller->getToken(), $info, $onProgress, $handle); + $previousUrl = null; + + while (true) { + self::addResponseHeaders($response, $info, $headers); + $status = $response->getStatus(); + + if (!\in_array($status, [301, 302, 303, 307, 308], true) || null === $location = $response->getHeader('location')) { + return $response; + } + + $urlResolver = new class() { + use HttpClientTrait { + parseUrl as public; + resolveUrl as public; + } + }; + + try { + $previousUrl ??= $urlResolver::parseUrl($info['url']); + $location = $urlResolver::parseUrl($location); + $location = $urlResolver::resolveUrl($location, $previousUrl); + $info['redirect_url'] = implode('', $location); + } catch (InvalidArgumentException) { + return $response; + } + + if (0 >= $options['max_redirects'] || $info['redirect_count'] >= $options['max_redirects']) { + return $response; + } + + $logger?->info(sprintf('Redirecting: "%s %s"', $status, $info['url'])); + + try { + // Discard body of redirects + while (null !== yield $response->getBody()->read()) { + } + } catch (HttpException|StreamException) { + // Ignore streaming errors on previous responses + } + + ++$info['redirect_count']; + $info['url'] = $info['redirect_url']; + $info['redirect_url'] = null; + $previousUrl = $location; + + $request = new Request($info['url'], $info['http_method']); + $request->setProtocolVersions($originRequest->getProtocolVersions()); + $request->setTcpConnectTimeout($originRequest->getTcpConnectTimeout()); + $request->setTlsHandshakeTimeout($originRequest->getTlsHandshakeTimeout()); + $request->setTransferTimeout($originRequest->getTransferTimeout()); + + if (\in_array($status, [301, 302, 303], true)) { + $originRequest->removeHeader('transfer-encoding'); + $originRequest->removeHeader('content-length'); + $originRequest->removeHeader('content-type'); + + // Do like curl and browsers: turn POST to GET on 301, 302 and 303 + if ('POST' === $response->getRequest()->getMethod() || 303 === $status) { + $info['http_method'] = 'HEAD' === $response->getRequest()->getMethod() ? 'HEAD' : 'GET'; + $request->setMethod($info['http_method']); + } + } else { + $request->setBody(AmpBody::rewind($response->getRequest()->getBody())); + } + + foreach ($originRequest->getRawHeaders() as [$name, $value]) { + $request->addHeader($name, $value); + } + + if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) { + $request->removeHeader('authorization'); + $request->removeHeader('cookie'); + $request->removeHeader('host'); + } + + yield $pause; + + $response = yield $multi->request($options, $request, $canceller->getToken(), $info, $onProgress, $handle); + $info['redirect_time'] = microtime(true) - $info['start_time']; + } + } + + private static function addResponseHeaders(Response $response, array &$info, array &$headers): void + { + $info['http_code'] = $response->getStatus(); + + if ($headers) { + $info['debug'] .= "< \r\n"; + $headers = []; + } + + $h = sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatus(), $response->getReason()); + $info['debug'] .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + + foreach ($response->getRawHeaders() as [$name, $value]) { + $headers[strtolower($name)][] = $value; + $h = $name.': '.$value; + $info['debug'] .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + } + + $info['debug'] .= "< \r\n"; + } + + /** + * Accepts pushed responses only if their headers related to authentication match the request. + */ + private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger): \Generator + { + if ('' !== $options['body']) { + return null; + } + + $authority = $request->getUri()->getAuthority(); + + foreach ($multi->pushedResponses[$authority] ?? [] as $i => [$pushedUrl, $pushDeferred, $pushedRequest, $pushedResponse, $parentOptions]) { + if ($info['url'] !== $pushedUrl || $info['http_method'] !== $pushedRequest->getMethod()) { + continue; + } + + foreach ($parentOptions as $k => $v) { + if ($options[$k] !== $v) { + continue 2; + } + } + + foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { + if ($pushedRequest->getHeaderArray($k) !== $request->getHeaderArray($k)) { + continue 2; + } + } + + $response = yield $pushedResponse; + + foreach ($response->getHeaderArray('vary') as $vary) { + foreach (preg_split('/\s*+,\s*+/', $vary) as $v) { + if ('*' === $v || ($pushedRequest->getHeaderArray($v) !== $request->getHeaderArray($v) && 'accept-encoding' !== strtolower($v))) { + $logger?->debug(sprintf('Skipping pushed response: "%s"', $info['url'])); + continue 3; + } + } + } + + $pushDeferred->resolve(); + $logger?->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url'])); + self::addResponseHeaders($response, $info, $headers); + unset($multi->pushedResponses[$authority][$i]); + + if (!$multi->pushedResponses[$authority]) { + unset($multi->pushedResponses[$authority]); + } + + return $response; + } + } + + private static function stopLoop(): void + { + if (null !== self::$delay) { + Loop::cancel(self::$delay); + self::$delay = null; + } + + Loop::defer(Loop::stop(...)); + } +} diff --git a/vendor/symfony/http-client/Response/AsyncContext.php b/vendor/symfony/http-client/Response/AsyncContext.php new file mode 100644 index 0000000..4f4d106 --- /dev/null +++ b/vendor/symfony/http-client/Response/AsyncContext.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * A DTO to work with AsyncResponse. + * + * @author Nicolas Grekas + */ +final class AsyncContext +{ + /** @var callable|null */ + private $passthru; + private HttpClientInterface $client; + private ResponseInterface $response; + private array $info = []; + /** @var resource|null */ + private $content; + private int $offset; + + /** + * @param resource|null $content + */ + public function __construct(?callable &$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset) + { + $this->passthru = &$passthru; + $this->client = $client; + $this->response = &$response; + $this->info = &$info; + $this->content = $content; + $this->offset = $offset; + } + + /** + * Returns the HTTP status without consuming the response. + */ + public function getStatusCode(): int + { + return $this->response->getInfo('http_code'); + } + + /** + * Returns the headers without consuming the response. + */ + public function getHeaders(): array + { + $headers = []; + + foreach ($this->response->getInfo('response_headers') as $h) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) { + $headers = []; + } elseif (2 === \count($m = explode(':', $h, 2))) { + $headers[strtolower($m[0])][] = ltrim($m[1]); + } + } + + return $headers; + } + + /** + * @return resource|null The PHP stream resource where the content is buffered, if it is + */ + public function getContent() + { + return $this->content; + } + + /** + * Creates a new chunk of content. + */ + public function createChunk(string $data): ChunkInterface + { + return new DataChunk($this->offset, $data); + } + + /** + * Pauses the request for the given number of seconds. + */ + public function pause(float $duration): void + { + if (\is_callable($pause = $this->response->getInfo('pause_handler'))) { + $pause($duration); + } elseif (0 < $duration) { + usleep((int) (1E6 * $duration)); + } + } + + /** + * Cancels the request and returns the last chunk to yield. + */ + public function cancel(): ChunkInterface + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->response->cancel(); + + return new LastChunk(); + } + + /** + * Returns the current info of the response. + */ + public function getInfo(?string $type = null): mixed + { + if (null !== $type) { + return $this->info[$type] ?? $this->response->getInfo($type); + } + + return $this->info + $this->response->getInfo(); + } + + /** + * Attaches an info to the response. + * + * @return $this + */ + public function setInfo(string $type, mixed $value): static + { + if ('canceled' === $type && $value !== $this->info['canceled']) { + throw new \LogicException('You cannot set the "canceled" info directly.'); + } + + if (null === $value) { + unset($this->info[$type]); + } else { + $this->info[$type] = $value; + } + + return $this; + } + + /** + * Returns the currently processed response. + */ + public function getResponse(): ResponseInterface + { + return $this->response; + } + + /** + * Replaces the currently processed response by doing a new request. + */ + public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface + { + $this->info['previous_info'][] = $info = $this->response->getInfo(); + if (null !== $onProgress = $options['on_progress'] ?? null) { + $thisInfo = &$this->info; + $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { + $onProgress($dlNow, $dlSize, $thisInfo + $info); + }; + } + if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) { + if (0 >= $options['max_duration'] = $info['max_duration'] - $info['total_time']) { + throw new TransportException(sprintf('Max duration was reached for "%s".', $info['url'])); + } + } + + return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options); + } + + /** + * Replaces the currently processed response by another one. + */ + public function replaceResponse(ResponseInterface $response): ResponseInterface + { + $this->info['previous_info'][] = $this->response->getInfo(); + + return $this->response = $response; + } + + /** + * Replaces or removes the chunk filter iterator. + * + * @param ?callable(ChunkInterface, self): ?\Iterator $passthru + */ + public function passthru(?callable $passthru = null): void + { + $this->passthru = $passthru ?? static function ($chunk, $context) { + $context->passthru = null; + + yield $chunk; + }; + } +} diff --git a/vendor/symfony/http-client/Response/AsyncResponse.php b/vendor/symfony/http-client/Response/AsyncResponse.php new file mode 100644 index 0000000..d139d3d --- /dev/null +++ b/vendor/symfony/http-client/Response/AsyncResponse.php @@ -0,0 +1,477 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Provides a single extension point to process a response's content stream. + * + * @author Nicolas Grekas + */ +class AsyncResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + + private const FIRST_CHUNK_YIELDED = 1; + private const LAST_CHUNK_YIELDED = 2; + + private ?HttpClientInterface $client; + private ResponseInterface $response; + private array $info = ['canceled' => false]; + /** @var callable|null */ + private $passthru; + private ?\Iterator $stream = null; + private ?int $yieldedState = null; + + /** + * @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru + */ + public function __construct(HttpClientInterface $client, string $method, string $url, array $options, ?callable $passthru = null) + { + $this->client = $client; + $this->shouldBuffer = $options['buffer'] ?? true; + + if (null !== $onProgress = $options['on_progress'] ?? null) { + $thisInfo = &$this->info; + $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { + $onProgress($dlNow, $dlSize, $thisInfo + $info); + }; + } + $this->response = $client->request($method, $url, ['buffer' => false] + $options); + $this->passthru = $passthru; + $this->initializer = static function (self $response, ?float $timeout = null) { + if (null === $response->shouldBuffer) { + return false; + } + + while (true) { + foreach (self::stream([$response], $timeout) as $chunk) { + if ($chunk->isTimeout() && $response->passthru) { + // Timeouts thrown during initialization are transport errors + foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) { + if ($chunk->isFirst()) { + return false; + } + } + + continue 2; + } + + if ($chunk->isFirst()) { + return false; + } + } + + return false; + } + }; + if (\array_key_exists('user_data', $options)) { + $this->info['user_data'] = $options['user_data']; + } + if (\array_key_exists('max_duration', $options)) { + $this->info['max_duration'] = $options['max_duration']; + } + } + + public function getStatusCode(): int + { + if ($this->initializer) { + self::initialize($this); + } + + return $this->response->getStatusCode(); + } + + public function getHeaders(bool $throw = true): array + { + if ($this->initializer) { + self::initialize($this); + } + + $headers = $this->response->getHeaders(false); + + if ($throw) { + $this->checkStatusCode(); + } + + return $headers; + } + + public function getInfo(?string $type = null): mixed + { + if (null !== $type) { + return $this->info[$type] ?? $this->response->getInfo($type); + } + + return $this->info + $this->response->getInfo(); + } + + /** + * @return resource + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->getHeaders(true); + } + + $handle = function () { + $stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response); + + return stream_get_meta_data($stream)['wrapper_data']->stream_cast(\STREAM_CAST_FOR_SELECT); + }; + + $stream = StreamWrapper::createResource($this); + stream_get_meta_data($stream)['wrapper_data'] + ->bindHandles($handle, $this->content); + + return $stream; + } + + public function cancel(): void + { + if ($this->info['canceled']) { + return; + } + + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->close(); + $client = $this->client; + $this->client = null; + + if (!$this->passthru) { + return; + } + + try { + foreach (self::passthru($client, $this, new LastChunk()) as $chunk) { + // no-op + } + + $this->passthru = null; + } catch (ExceptionInterface) { + // ignore any errors when canceling + } + } + + public function __destruct() + { + $httpException = null; + + if ($this->initializer && null === $this->getInfo('error')) { + try { + self::initialize($this, -0.0); + $this->getHeaders(true); + } catch (HttpExceptionInterface $httpException) { + // no-op + } + } + + if ($this->passthru && null === $this->getInfo('error')) { + $this->info['canceled'] = true; + + try { + foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) { + // no-op + } + } catch (ExceptionInterface) { + // ignore any errors when destructing + } + } + + if (null !== $httpException) { + throw $httpException; + } + } + + /** + * @internal + */ + public static function stream(iterable $responses, ?float $timeout = null, ?string $class = null): \Generator + { + while ($responses) { + $wrappedResponses = []; + $asyncMap = new \SplObjectStorage(); + $client = null; + + foreach ($responses as $r) { + if (!$r instanceof self) { + throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', $class ?? static::class, get_debug_type($r))); + } + + if (null !== $e = $r->info['error'] ?? null) { + yield $r => $chunk = new ErrorChunk($r->offset, new TransportException($e)); + $chunk->didThrow() ?: $chunk->getContent(); + continue; + } + + if (null === $client) { + $client = $r->client; + } elseif ($r->client !== $client) { + throw new TransportException('Cannot stream AsyncResponse objects with many clients.'); + } + + $asyncMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + + if ($r->stream) { + yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap); + + if (!isset($asyncMap[$response])) { + array_pop($wrappedResponses); + } + + if ($r->response !== $response && !isset($asyncMap[$r->response])) { + $asyncMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + } + } + } + + if (!$client || !$wrappedResponses) { + return; + } + + foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) { + $r = $asyncMap[$response]; + + if (null === $chunk->getError()) { + if ($chunk->isFirst()) { + // Ensure no exception is thrown on destruct for the wrapped response + $r->response->getStatusCode(); + } elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) { + $r->content = fopen('php://memory', 'w+'); + } + } + + if (!$r->passthru) { + if (null !== $chunk->getError() || $chunk->isLast()) { + unset($asyncMap[$response]); + } elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) { + $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); + $r->info['error'] = $chunk->getError(); + $r->response->cancel(); + } + + yield $r => $chunk; + continue; + } + + if (null !== $chunk->getError()) { + // no-op + } elseif ($chunk->isFirst()) { + $r->yieldedState = self::FIRST_CHUNK_YIELDED; + } elseif (self::FIRST_CHUNK_YIELDED !== $r->yieldedState && null === $chunk->getInformationalStatus()) { + throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class)); + } + + foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) { + yield $r => $chunk; + } + + if ($r->response !== $response && isset($asyncMap[$response])) { + break; + } + } + + if (null === $chunk->getError() && $chunk->isLast()) { + $r->yieldedState = self::LAST_CHUNK_YIELDED; + } + if (null === $chunk->getError() && self::LAST_CHUNK_YIELDED !== $r->yieldedState && $r->response === $response && null !== $r->client) { + throw new \LogicException('A chunk passthru must yield an "isLast()" chunk before ending a stream.'); + } + + $responses = []; + foreach ($asyncMap as $response) { + $r = $asyncMap[$response]; + + if (null !== $r->client) { + $responses[] = $asyncMap[$response]; + } + } + } + } + + /** + * @param \SplObjectStorage|null $asyncMap + */ + private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, ?\SplObjectStorage $asyncMap = null): \Generator + { + $r->stream = null; + $response = $r->response; + $context = new AsyncContext($r->passthru, $client, $r->response, $r->info, $r->content, $r->offset); + if (null === $stream = ($r->passthru)($chunk, $context)) { + if ($r->response === $response && (null !== $chunk->getError() || $chunk->isLast())) { + throw new \LogicException('A chunk passthru cannot swallow the last chunk.'); + } + + return; + } + + if (!$stream instanceof \Iterator) { + throw new \LogicException(sprintf('A chunk passthru must return an "Iterator", "%s" returned.', get_debug_type($stream))); + } + $r->stream = $stream; + + yield from self::passthruStream($response, $r, null, $asyncMap); + } + + /** + * @param \SplObjectStorage|null $asyncMap + */ + private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator + { + while (true) { + try { + if (null !== $chunk && $r->stream) { + $r->stream->next(); + } + + if (!$r->stream || !$r->stream->valid() || !$r->stream) { + $r->stream = null; + break; + } + } catch (\Throwable $e) { + unset($asyncMap[$response]); + $r->stream = null; + $r->info['error'] = $e->getMessage(); + $r->response->cancel(); + + yield $r => $chunk = new ErrorChunk($r->offset, $e); + $chunk->didThrow() ?: $chunk->getContent(); + break; + } + + $chunk = $r->stream->current(); + + if (!$chunk instanceof ChunkInterface) { + throw new \LogicException(sprintf('A chunk passthru must yield instances of "%s", "%s" yielded.', ChunkInterface::class, get_debug_type($chunk))); + } + + if (null !== $chunk->getError()) { + // no-op + } elseif ($chunk->isFirst()) { + $e = $r->openBuffer(); + + yield $r => $chunk; + + if ($r->initializer && null === $r->getInfo('error')) { + // Ensure the HTTP status code is always checked + $r->getHeaders(true); + } + + if (null === $e) { + continue; + } + + $r->response->cancel(); + $chunk = new ErrorChunk($r->offset, $e); + } elseif ('' !== $content = $chunk->getContent()) { + if (null !== $r->shouldBuffer) { + throw new \LogicException('A chunk passthru must yield an "isFirst()" chunk before any content chunk.'); + } + + if (null !== $r->content && \strlen($content) !== fwrite($r->content, $content)) { + $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); + $r->info['error'] = $chunk->getError(); + $r->response->cancel(); + } + } + + if (null !== $chunk->getError() || $chunk->isLast()) { + $stream = $r->stream; + $r->stream = null; + unset($asyncMap[$response]); + } + + if (null === $chunk->getError()) { + $r->offset += \strlen($content); + + yield $r => $chunk; + + if (!$chunk->isLast()) { + continue; + } + + $stream->next(); + + if ($stream->valid()) { + throw new \LogicException('A chunk passthru cannot yield after an "isLast()" chunk.'); + } + + $r->passthru = null; + } else { + if ($chunk instanceof ErrorChunk) { + $chunk->didThrow(false); + } else { + try { + $chunk = new ErrorChunk($chunk->getOffset(), !$chunk->isTimeout() ?: $chunk->getError()); + } catch (TransportExceptionInterface $e) { + $chunk = new ErrorChunk($chunk->getOffset(), $e); + } + } + + yield $r => $chunk; + $chunk->didThrow() ?: $chunk->getContent(); + } + + break; + } + } + + private function openBuffer(): ?\Throwable + { + if (null === $shouldBuffer = $this->shouldBuffer) { + throw new \LogicException('A chunk passthru cannot yield more than one "isFirst()" chunk.'); + } + + $e = $this->shouldBuffer = null; + + if ($shouldBuffer instanceof \Closure) { + try { + $shouldBuffer = $shouldBuffer($this->getHeaders(false)); + + if (null !== $e = $this->response->getInfo('error')) { + throw new TransportException($e); + } + } catch (\Throwable $e) { + $this->info['error'] = $e->getMessage(); + $this->response->cancel(); + } + } + + if (true === $shouldBuffer) { + $this->content = fopen('php://temp', 'w+'); + } elseif (\is_resource($shouldBuffer)) { + $this->content = $shouldBuffer; + } + + return $e; + } + + private function close(): void + { + $this->response->cancel(); + } +} diff --git a/vendor/symfony/http-client/Response/CommonResponseTrait.php b/vendor/symfony/http-client/Response/CommonResponseTrait.php new file mode 100644 index 0000000..5edad95 --- /dev/null +++ b/vendor/symfony/http-client/Response/CommonResponseTrait.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Component\HttpClient\Exception\RedirectionException; +use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * Implements common logic for response classes. + * + * @author Nicolas Grekas + * + * @internal + */ +trait CommonResponseTrait +{ + /** + * @var callable|null A callback that tells whether we're waiting for response headers + */ + private $initializer; + /** @var bool|\Closure|resource|null */ + private $shouldBuffer; + /** @var resource|null */ + private $content; + private int $offset = 0; + private ?array $jsonData = null; + + public function getContent(bool $throw = true): string + { + if ($this->initializer) { + self::initialize($this); + } + + if ($throw) { + $this->checkStatusCode(); + } + + if (null === $this->content) { + $content = null; + + foreach (self::stream([$this]) as $chunk) { + if (!$chunk->isLast()) { + $content .= $chunk->getContent(); + } + } + + if (null !== $content) { + return $content; + } + + if (null === $this->content) { + throw new TransportException('Cannot get the content of the response twice: buffering is disabled.'); + } + } else { + foreach (self::stream([$this]) as $chunk) { + // Chunks are buffered in $this->content already + } + } + + rewind($this->content); + + return stream_get_contents($this->content); + } + + public function toArray(bool $throw = true): array + { + if ('' === $content = $this->getContent($throw)) { + throw new JsonException('Response body is empty.'); + } + + if (null !== $this->jsonData) { + return $this->jsonData; + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode()); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url'))); + } + + if (null !== $this->content) { + // Option "buffer" is true + return $this->jsonData = $content; + } + + return $content; + } + + /** + * @return resource + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->getHeaders($throw); + } + + $stream = StreamWrapper::createResource($this); + stream_get_meta_data($stream)['wrapper_data'] + ->bindHandles($this->handle, $this->content); + + return $stream; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + /** + * Closes the response and all its network handles. + */ + abstract protected function close(): void; + + private static function initialize(self $response): void + { + if (null !== $response->getInfo('error')) { + throw new TransportException($response->getInfo('error')); + } + + try { + if (($response->initializer)($response, -0.0)) { + foreach (self::stream([$response], -0.0) as $chunk) { + if ($chunk->isFirst()) { + break; + } + } + } + } catch (\Throwable $e) { + // Persist timeouts thrown during initialization + $response->info['error'] = $e->getMessage(); + $response->close(); + throw $e; + } + + $response->initializer = null; + } + + private function checkStatusCode(): void + { + $code = $this->getInfo('http_code'); + + if (500 <= $code) { + throw new ServerException($this); + } + + if (400 <= $code) { + throw new ClientException($this); + } + + if (300 <= $code) { + throw new RedirectionException($this); + } + } +} diff --git a/vendor/symfony/http-client/Response/CurlResponse.php b/vendor/symfony/http-client/Response/CurlResponse.php new file mode 100644 index 0000000..8858342 --- /dev/null +++ b/vendor/symfony/http-client/Response/CurlResponse.php @@ -0,0 +1,461 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\InformationalChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Component\HttpClient\Internal\CurlClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class CurlResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait { + getContent as private doGetContent; + } + use TransportResponseTrait; + + /** + * @var resource + */ + private $debugBuffer; + + /** + * @internal + */ + public function __construct( + private CurlClientState $multi, + \CurlHandle|string $ch, + ?array $options = null, + ?LoggerInterface $logger = null, + string $method = 'GET', + ?callable $resolveRedirect = null, + ?int $curlVersion = null, + ?string $originalUrl = null, + ) { + if ($ch instanceof \CurlHandle) { + $this->handle = $ch; + $this->debugBuffer = fopen('php://temp', 'w+'); + if (0x074000 === $curlVersion) { + fwrite($this->debugBuffer, 'Due to a bug in curl 7.64.0, the debug log is disabled; use another version to work around the issue.'); + } else { + curl_setopt($ch, \CURLOPT_VERBOSE, true); + curl_setopt($ch, \CURLOPT_STDERR, $this->debugBuffer); + } + } else { + $this->info['url'] = $ch; + $ch = $this->handle; + } + + $this->id = $id = (int) $ch; + $this->logger = $logger; + $this->shouldBuffer = $options['buffer'] ?? true; + $this->timeout = $options['timeout'] ?? null; + $this->info['http_method'] = $method; + $this->info['user_data'] = $options['user_data'] ?? null; + $this->info['max_duration'] = $options['max_duration'] ?? null; + $this->info['start_time'] ??= microtime(true); + $this->info['original_url'] = $originalUrl ?? $this->info['url'] ?? curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL); + $info = &$this->info; + $headers = &$this->headers; + $debugBuffer = $this->debugBuffer; + + if (!$info['response_headers']) { + // Used to keep track of what we're waiting for + curl_setopt($ch, \CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter + } + + curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int { + return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger); + }); + + if (null === $options) { + // Pushed response: buffer until requested + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + $multi->handlesActivity[$id][] = $data; + curl_pause($ch, \CURLPAUSE_RECV); + + return \strlen($data); + }); + + return; + } + + $execCounter = $multi->execCounter; + $this->info['pause_handler'] = static function (float $duration) use ($ch, $multi, $execCounter) { + if (0 < $duration) { + if ($execCounter === $multi->execCounter) { + curl_multi_remove_handle($multi->handle, $ch); + } + + $lastExpiry = end($multi->pauseExpiries); + $multi->pauseExpiries[(int) $ch] = $duration += hrtime(true) / 1E9; + if (false !== $lastExpiry && $lastExpiry > $duration) { + asort($multi->pauseExpiries); + } + curl_pause($ch, \CURLPAUSE_ALL); + } else { + unset($multi->pauseExpiries[(int) $ch]); + curl_pause($ch, \CURLPAUSE_CONT); + curl_multi_add_handle($multi->handle, $ch); + } + }; + + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + curl_pause($ch, \CURLPAUSE_CONT); + + if ($onProgress = $options['on_progress']) { + $url = isset($info['url']) ? ['url' => $info['url']] : []; + curl_setopt($ch, \CURLOPT_NOPROGRESS, false); + curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) { + try { + rewind($debugBuffer); + $debug = ['debug' => stream_get_contents($debugBuffer)]; + $onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug); + } catch (\Throwable $e) { + $multi->handlesActivity[(int) $ch][] = null; + $multi->handlesActivity[(int) $ch][] = $e; + + return 1; // Abort the request + } + + return null; + }); + } + + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + if ('H' === (curl_getinfo($ch, \CURLINFO_PRIVATE)[0] ?? null)) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = new TransportException(sprintf('Unsupported protocol for "%s"', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); + + return 0; + } + + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + $multi->handlesActivity[$id][] = $data; + + return \strlen($data); + }); + + $multi->handlesActivity[$id][] = $data; + + return \strlen($data); + }); + + $this->initializer = static function (self $response) { + $waitFor = curl_getinfo($response->handle, \CURLINFO_PRIVATE); + + return 'H' === $waitFor[0]; + }; + + // Schedule the request in a non-blocking way + $multi->lastTimeout = null; + $multi->openHandles[$id] = [$ch, $options]; + curl_multi_add_handle($multi->handle, $ch); + + $this->canary = new Canary(static function () use ($ch, $multi, $id) { + unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]); + curl_setopt($ch, \CURLOPT_PRIVATE, '_0'); + + if ($multi->performing) { + return; + } + + curl_multi_remove_handle($multi->handle, $ch); + curl_setopt_array($ch, [ + \CURLOPT_NOPROGRESS => true, + \CURLOPT_PROGRESSFUNCTION => null, + \CURLOPT_HEADERFUNCTION => null, + \CURLOPT_WRITEFUNCTION => null, + \CURLOPT_READFUNCTION => null, + \CURLOPT_INFILE => null, + ]); + + if (!$multi->openHandles) { + // Schedule DNS cache eviction for the next request + $multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals; + $multi->dnsCache->removals = $multi->dnsCache->hostnames = []; + } + }); + } + + public function getInfo(?string $type = null): mixed + { + if (!$info = $this->finalInfo) { + $info = array_merge($this->info, curl_getinfo($this->handle)); + $info['url'] = $this->info['url'] ?? $info['url']; + $info['redirect_url'] = $this->info['redirect_url'] ?? null; + + // workaround curl not subtracting the time offset for pushed responses + if (isset($this->info['url']) && $info['start_time'] / 1000 < $info['total_time']) { + $info['total_time'] -= $info['starttransfer_time'] ?: $info['total_time']; + $info['starttransfer_time'] = 0.0; + } + + rewind($this->debugBuffer); + $info['debug'] = stream_get_contents($this->debugBuffer); + $waitFor = curl_getinfo($this->handle, \CURLINFO_PRIVATE); + + if ('H' !== $waitFor[0] && 'C' !== $waitFor[0]) { + curl_setopt($this->handle, \CURLOPT_VERBOSE, false); + rewind($this->debugBuffer); + ftruncate($this->debugBuffer, 0); + $this->finalInfo = $info; + } + } + + return null !== $type ? $info[$type] ?? null : $info; + } + + public function getContent(bool $throw = true): string + { + $performing = $this->multi->performing; + $this->multi->performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE); + + try { + return $this->doGetContent($throw); + } finally { + $this->multi->performing = $performing; + } + } + + public function __destruct() + { + try { + if (null === $this->timeout) { + return; // Unused pushed response + } + + $this->doDestruct(); + } finally { + if ($this->handle instanceof \CurlHandle) { + curl_setopt($this->handle, \CURLOPT_VERBOSE, false); + } + } + } + + private static function schedule(self $response, array &$runningResponses): void + { + if (isset($runningResponses[$i = (int) $response->multi->handle])) { + $runningResponses[$i][1][$response->id] = $response; + } else { + $runningResponses[$i] = [$response->multi, [$response->id => $response]]; + } + + if ('_0' === curl_getinfo($response->handle, \CURLINFO_PRIVATE)) { + // Response already completed + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * @param CurlClientState $multi + */ + private static function perform(ClientState $multi, ?array &$responses = null): void + { + if ($multi->performing) { + if ($responses) { + $response = current($responses); + $multi->handlesActivity[(int) $response->handle][] = null; + $multi->handlesActivity[(int) $response->handle][] = new TransportException(sprintf('Userland callback cannot use the client nor the response while processing "%s".', curl_getinfo($response->handle, \CURLINFO_EFFECTIVE_URL))); + } + + return; + } + + try { + $multi->performing = true; + ++$multi->execCounter; + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) { + } + + if (\CURLM_OK !== $err) { + throw new TransportException(curl_multi_strerror($err)); + } + + while ($info = curl_multi_info_read($multi->handle)) { + if (\CURLMSG_DONE !== $info['msg']) { + continue; + } + $result = $info['result']; + $id = (int) $ch = $info['handle']; + $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; + + if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /* CURLE_HTTP2 */ 16, /* CURLE_HTTP2_STREAM */ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) { + curl_multi_remove_handle($multi->handle, $ch); + $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter + curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); + curl_setopt($ch, \CURLOPT_FORBID_REUSE, true); + + if (0 === curl_multi_add_handle($multi->handle, $ch)) { + continue; + } + } + + if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) { + $multi->handlesActivity[$id][] = new FirstChunk(); + } + + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); + } + } finally { + $multi->performing = false; + } + } + + /** + * @param CurlClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + if ($multi->pauseExpiries) { + $now = hrtime(true) / 1E9; + + foreach ($multi->pauseExpiries as $id => $pauseExpiry) { + if ($now < $pauseExpiry) { + $timeout = min($timeout, $pauseExpiry - $now); + break; + } + + unset($multi->pauseExpiries[$id]); + curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT); + curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]); + } + } + + if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) { + return $selected; + } + + if ($multi->pauseExpiries && 0 < $timeout -= hrtime(true) / 1E9 - $now) { + usleep((int) (1E6 * $timeout)); + } + + return 0; + } + + /** + * Parses header lines as curl yields them to us. + */ + private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int + { + if (!str_ends_with($data, "\r\n")) { + return 0; + } + + $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; + + if ('H' !== $waitFor[0]) { + return \strlen($data); // Ignore HTTP trailers + } + + $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE); + + if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) { + return \strlen($data); // Ignore headers from responses to CONNECT requests + } + + if ("\r\n" !== $data) { + // Regular header line: add it to the list + self::addResponseHeaders([substr($data, 0, -2)], $info, $headers); + + if (!str_starts_with($data, 'HTTP/')) { + if (0 === stripos($data, 'Location:')) { + $location = trim(substr($data, 9, -2)); + } + + return \strlen($data); + } + + if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, \CURLINFO_CERTINFO)) { + $info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert')); + } + + if (300 <= $info['http_code'] && $info['http_code'] < 400) { + if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { + curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); + } elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) { + curl_setopt($ch, \CURLOPT_POSTFIELDS, ''); + } + } + + return \strlen($data); + } + + // End of headers: handle informational responses, redirects, etc. + + if (200 > $statusCode) { + $multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers); + $location = null; + + return \strlen($data); + } + + $info['redirect_url'] = null; + + if (300 <= $statusCode && $statusCode < 400 && null !== $location) { + if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) { + $info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET'; + curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']); + } + + if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) { + $options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT); + curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); + curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']); + } else { + $url = parse_url($location ?? ':'); + + if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) { + // Populate DNS cache for redirects if needed + $port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443); + curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]); + $multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port"; + } + } + } + + if (401 === $statusCode && isset($options['auth_ntlm']) && 0 === strncasecmp($headers['www-authenticate'][0] ?? '', 'NTLM ', 5)) { + // Continue with NTLM auth + } elseif ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { + // Headers and redirects completed, time to get the response's content + $multi->handlesActivity[$id][] = new FirstChunk(); + + if ('HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) { + $waitFor = '_0'; // no content expected + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + } else { + $waitFor[0] = 'C'; // C = content + } + + curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); + } elseif (null !== $info['redirect_url'] && $logger) { + $logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url'])); + } + + $location = null; + + return \strlen($data); + } +} diff --git a/vendor/symfony/http-client/Response/HttplugPromise.php b/vendor/symfony/http-client/Response/HttplugPromise.php new file mode 100644 index 0000000..2ec04a1 --- /dev/null +++ b/vendor/symfony/http-client/Response/HttplugPromise.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use GuzzleHttp\Promise\Create; +use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface; +use Http\Promise\Promise as HttplugPromiseInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; + +/** + * @author Tobias Nyholm + * + * @internal + */ +final class HttplugPromise implements HttplugPromiseInterface +{ + public function __construct( + private GuzzlePromiseInterface $promise, + ) { + } + + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self + { + return new self($this->promise->then( + $this->wrapThenCallback($onFulfilled), + $this->wrapThenCallback($onRejected) + )); + } + + public function cancel(): void + { + $this->promise->cancel(); + } + + public function getState(): string + { + return $this->promise->getState(); + } + + /** + * @return Psr7ResponseInterface|mixed + */ + public function wait($unwrap = true): mixed + { + $result = $this->promise->wait($unwrap); + + while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) { + $result = $result->wait($unwrap); + } + + return $result; + } + + private function wrapThenCallback(?callable $callback): ?callable + { + if (null === $callback) { + return null; + } + + return static fn ($value) => Create::promiseFor($callback($value)); + } +} diff --git a/vendor/symfony/http-client/Response/JsonMockResponse.php b/vendor/symfony/http-client/Response/JsonMockResponse.php new file mode 100644 index 0000000..2c61d96 --- /dev/null +++ b/vendor/symfony/http-client/Response/JsonMockResponse.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; + +class JsonMockResponse extends MockResponse +{ + /** + * @param mixed $body Any value that `json_encode()` can serialize + */ + public function __construct(mixed $body = [], array $info = []) + { + try { + $json = json_encode($body, \JSON_THROW_ON_ERROR | \JSON_PRESERVE_ZERO_FRACTION); + } catch (\JsonException $e) { + throw new InvalidArgumentException('JSON encoding failed: '.$e->getMessage(), $e->getCode(), $e); + } + + $info['response_headers']['content-type'] ??= 'application/json'; + + parent::__construct($json, $info); + } + + public static function fromFile(string $path, array $info = []): static + { + if (!is_file($path)) { + throw new InvalidArgumentException(sprintf('File not found: "%s".', $path)); + } + + $json = file_get_contents($path); + if (!json_validate($json)) { + throw new \InvalidArgumentException(sprintf('File "%s" does not contain valid JSON.', $path)); + } + + return new static(json_decode($json, true, flags: \JSON_THROW_ON_ERROR), $info); + } +} diff --git a/vendor/symfony/http-client/Response/MockResponse.php b/vendor/symfony/http-client/Response/MockResponse.php new file mode 100644 index 0000000..f57311e --- /dev/null +++ b/vendor/symfony/http-client/Response/MockResponse.php @@ -0,0 +1,338 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * A test-friendly response. + * + * @author Nicolas Grekas + */ +class MockResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait; + + private string|iterable|null $body; + private array $requestOptions = []; + private string $requestUrl; + private string $requestMethod; + + private static ClientState $mainMulti; + private static int $idSequence = 0; + + /** + * @param string|iterable $body The response body as a string or an iterable of strings, + * yielding an empty string simulates an idle timeout, + * throwing or yielding an exception yields an ErrorChunk + * + * @see ResponseInterface::getInfo() for possible info, e.g. "response_headers" + */ + public function __construct(string|iterable $body = '', array $info = []) + { + $this->body = $body; + $this->info = $info + ['http_code' => 200] + $this->info; + + if (!isset($info['response_headers'])) { + return; + } + + $responseHeaders = []; + + foreach ($info['response_headers'] as $k => $v) { + foreach ((array) $v as $v) { + $responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v; + } + } + + $this->info['response_headers'] = []; + self::addResponseHeaders($responseHeaders, $this->info, $this->headers); + } + + public static function fromFile(string $path, array $info = []): static + { + if (!is_file($path)) { + throw new \InvalidArgumentException(sprintf('File not found: "%s".', $path)); + } + + return new static(file_get_contents($path), $info); + } + + /** + * Returns the options used when doing the request. + */ + public function getRequestOptions(): array + { + return $this->requestOptions; + } + + /** + * Returns the URL used when doing the request. + */ + public function getRequestUrl(): string + { + return $this->requestUrl; + } + + /** + * Returns the method used when doing the request. + */ + public function getRequestMethod(): string + { + return $this->requestMethod; + } + + public function getInfo(?string $type = null): mixed + { + return null !== $type ? $this->info[$type] ?? null : $this->info; + } + + public function cancel(): void + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + try { + $this->body = null; + } catch (TransportException $e) { + // ignore errors when canceling + } + + $onProgress = $this->requestOptions['on_progress'] ?? static function () {}; + $dlSize = isset($this->headers['content-encoding']) || 'HEAD' === ($this->info['http_method'] ?? null) || \in_array($this->info['http_code'], [204, 304], true) ? 0 : (int) ($this->headers['content-length'][0] ?? 0); + $onProgress($this->offset, $dlSize, $this->info); + } + + public function __destruct() + { + $this->doDestruct(); + } + + protected function close(): void + { + $this->inflate = null; + $this->body = []; + } + + /** + * @internal + */ + public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self + { + $response = new self([]); + $response->requestOptions = $options; + $response->id = ++self::$idSequence; + $response->shouldBuffer = $options['buffer'] ?? true; + $response->initializer = static fn (self $response) => \is_array($response->body[0] ?? null); + + $response->info['redirect_count'] = 0; + $response->info['redirect_url'] = null; + $response->info['start_time'] = microtime(true); + $response->info['http_method'] = $method; + $response->info['http_code'] = 0; + $response->info['user_data'] = $options['user_data'] ?? null; + $response->info['max_duration'] = $options['max_duration'] ?? null; + $response->info['url'] = $url; + $response->info['original_url'] = $url; + + if ($mock instanceof self) { + $mock->requestOptions = $response->requestOptions; + $mock->requestMethod = $method; + $mock->requestUrl = $url; + } + + self::writeRequest($response, $options, $mock); + $response->body[] = [$options, $mock]; + + return $response; + } + + protected static function schedule(self $response, array &$runningResponses): void + { + if (!isset($response->id)) { + throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.'); + } + + $multi = self::$mainMulti ??= new ClientState(); + + if (!isset($runningResponses[0])) { + $runningResponses[0] = [$multi, []]; + } + + $runningResponses[0][1][$response->id] = $response; + } + + protected static function perform(ClientState $multi, array &$responses): void + { + foreach ($responses as $response) { + $id = $response->id; + + if (null === $response->body) { + // Canceled response + $response->body = []; + } elseif ([] === $response->body) { + // Error chunk + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } elseif (null === $chunk = array_shift($response->body)) { + // Last chunk + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = array_shift($response->body); + } elseif (\is_array($chunk)) { + // First chunk + try { + $offset = 0; + $chunk[1]->getStatusCode(); + $chunk[1]->getHeaders(false); + self::readResponse($response, $chunk[0], $chunk[1], $offset); + $multi->handlesActivity[$id][] = new FirstChunk(); + } catch (\Throwable $e) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $e; + } + } elseif ($chunk instanceof \Throwable) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $chunk; + } else { + // Data or timeout chunk + $multi->handlesActivity[$id][] = $chunk; + } + } + } + + protected static function select(ClientState $multi, float $timeout): int + { + return 42; + } + + /** + * Simulates sending the request. + */ + private static function writeRequest(self $response, array $options, ResponseInterface $mock): void + { + $onProgress = $options['on_progress'] ?? static function () {}; + $response->info += $mock->getInfo() ?: []; + if (null !== $mock->getInfo('start_time')) { + $response->info['start_time'] = $mock->getInfo('start_time'); + } + + // simulate "size_upload" if it is set + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] = 0.0; + } + + // simulate "total_time" if it is not set + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" DNS resolution + $onProgress(0, 0, $response->info); + + // consume the request body + if (\is_resource($body = $options['body'] ?? '')) { + $data = stream_get_contents($body); + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] += \strlen($data); + } + } elseif ($body instanceof \Closure) { + while ('' !== $data = $body(16372)) { + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + // "notify" upload progress + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] += \strlen($data); + } + + $onProgress(0, 0, $response->info); + } + } + } + + /** + * Simulates reading the response. + */ + private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset): void + { + $onProgress = $options['on_progress'] ?? static function () {}; + + // populate info related to headers + $info = $mock->getInfo() ?: []; + $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; + $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); + $dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); + + $response->info = [ + 'start_time' => $response->info['start_time'], + 'user_data' => $response->info['user_data'], + 'max_duration' => $response->info['max_duration'], + 'http_code' => $response->info['http_code'], + ] + $info + $response->info; + + if (null !== $response->info['error']) { + throw new TransportException($response->info['error']); + } + + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" headers arrival + $onProgress(0, $dlSize, $response->info); + + // cast response body to activity list + $body = $mock instanceof self ? $mock->body : $mock->getContent(false); + + if (!\is_string($body)) { + try { + foreach ($body as $chunk) { + if ($chunk instanceof \Throwable) { + throw $chunk; + } + + if ('' === $chunk = (string) $chunk) { + // simulate an idle timeout + $response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url'])); + } else { + $response->body[] = $chunk; + $offset += \strlen($chunk); + // "notify" download progress + $onProgress($offset, $dlSize, $response->info); + } + } + } catch (\Throwable $e) { + $response->body[] = $e; + } + } elseif ('' !== $body) { + $response->body[] = $body; + $offset = \strlen($body); + } + + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" completion + $onProgress($offset, $dlSize, $response->info); + + if ($dlSize && $offset !== $dlSize) { + throw new TransportException(sprintf('Transfer closed with %d bytes remaining to read.', $dlSize - $offset)); + } + } +} diff --git a/vendor/symfony/http-client/Response/NativeResponse.php b/vendor/symfony/http-client/Response/NativeResponse.php new file mode 100644 index 0000000..af7b25f --- /dev/null +++ b/vendor/symfony/http-client/Response/NativeResponse.php @@ -0,0 +1,370 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Component\HttpClient\Internal\NativeClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class NativeResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait; + + private \Closure $resolver; + private ?\Closure $onProgress; + private ?int $remaining = null; + + /** + * @var resource|null + */ + private $buffer; + + private float $pauseExpiry = 0.0; + + /** + * @internal + * @param $context resource + */ + public function __construct( + private NativeClientState $multi, + private $context, + private string $url, + array $options, + array &$info, + callable $resolver, + ?callable $onProgress, + ?LoggerInterface $logger, + ) { + $this->id = $id = (int) $context; + $this->logger = $logger; + $this->timeout = $options['timeout']; + $this->info = &$info; + $this->resolver = $resolver(...); + $this->onProgress = $onProgress ? $onProgress(...) : null; + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + $this->shouldBuffer = $options['buffer'] ?? true; + + // Temporary resource to dechunk the response stream + $this->buffer = fopen('php://temp', 'w+'); + + $info['original_url'] = implode('', $info['url']); + $info['user_data'] = $options['user_data']; + $info['max_duration'] = $options['max_duration']; + ++$multi->responseCount; + + $this->initializer = static fn (self $response) => null === $response->remaining; + + $pauseExpiry = &$this->pauseExpiry; + $info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) { + $pauseExpiry = 0 < $duration ? hrtime(true) / 1E9 + $duration : 0; + }; + + $this->canary = new Canary(static function () use ($multi, $id) { + if (null !== ($host = $multi->openHandles[$id][6] ?? null) && 0 >= --$multi->hosts[$host]) { + unset($multi->hosts[$host]); + } + unset($multi->openHandles[$id], $multi->handlesActivity[$id]); + }); + } + + public function getInfo(?string $type = null): mixed + { + if (!$info = $this->finalInfo) { + $info = $this->info; + $info['url'] = implode('', $info['url']); + unset($info['size_body'], $info['request_header']); + + if (null === $this->buffer) { + $this->finalInfo = $info; + } + } + + return null !== $type ? $info[$type] ?? null : $info; + } + + public function __destruct() + { + try { + $this->doDestruct(); + } finally { + // Clear the DNS cache when all requests completed + if (0 >= --$this->multi->responseCount) { + $this->multi->responseCount = 0; + $this->multi->dnsCache = []; + } + } + } + + private function open(): void + { + $url = $this->url; + + set_error_handler(function ($type, $msg) use (&$url) { + if (\E_NOTICE !== $type || 'fopen(): Content-type not specified assuming application/x-www-form-urlencoded' !== $msg) { + throw new TransportException($msg); + } + + $this->logger?->info(sprintf('%s for "%s".', $msg, $url ?? $this->url)); + }); + + try { + $this->info['start_time'] = microtime(true); + + [$resolver, $url] = ($this->resolver)($this->multi); + + while (true) { + $context = stream_context_get_options($this->context); + + if ($proxy = $context['http']['proxy'] ?? null) { + $this->info['debug'] .= "* Establish HTTP proxy tunnel to {$proxy}\n"; + $this->info['request_header'] = $url; + } else { + $this->info['debug'] .= "* Trying {$this->info['primary_ip']}...\n"; + $this->info['request_header'] = $this->info['url']['path'].$this->info['url']['query']; + } + + $this->info['request_header'] = sprintf("> %s %s HTTP/%s \r\n", $context['http']['method'], $this->info['request_header'], $context['http']['protocol_version']); + $this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n"; + + if (\array_key_exists('peer_name', $context['ssl']) && null === $context['ssl']['peer_name']) { + unset($context['ssl']['peer_name']); + $this->context = stream_context_create([], ['options' => $context] + stream_context_get_params($this->context)); + } + + // Send request and follow redirects when needed + $this->handle = $h = fopen($url, 'r', false, $this->context); + self::addResponseHeaders(stream_get_meta_data($h)['wrapper_data'], $this->info, $this->headers, $this->info['debug']); + $url = $resolver($this->multi, $this->headers['location'][0] ?? null, $this->context); + + if (null === $url) { + break; + } + + $this->logger?->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url)); + } + } catch (\Throwable $e) { + $this->close(); + $this->multi->handlesActivity[$this->id][] = null; + $this->multi->handlesActivity[$this->id][] = $e; + + return; + } finally { + $this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time']; + restore_error_handler(); + } + + if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) { + $this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain']; + } + + stream_set_blocking($h, false); + unset($this->context, $this->resolver); + + // Create dechunk buffers + if (isset($this->headers['content-length'])) { + $this->remaining = (int) $this->headers['content-length'][0]; + } elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) { + stream_filter_append($this->buffer, 'dechunk', \STREAM_FILTER_WRITE); + $this->remaining = -1; + } else { + $this->remaining = -2; + } + + $this->multi->handlesActivity[$this->id] = [new FirstChunk()]; + + if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) { + $this->multi->handlesActivity[$this->id][] = null; + $this->multi->handlesActivity[$this->id][] = null; + + return; + } + + $host = parse_url($this->info['redirect_url'] ?? $this->url, \PHP_URL_HOST); + $this->multi->lastTimeout = null; + $this->multi->openHandles[$this->id] = [&$this->pauseExpiry, $h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info, $host]; + $this->multi->hosts[$host] = 1 + ($this->multi->hosts[$host] ?? 0); + } + + private function close(): void + { + $this->canary->cancel(); + $this->handle = $this->buffer = $this->inflate = $this->onProgress = null; + } + + private static function schedule(self $response, array &$runningResponses): void + { + if (!isset($runningResponses[$i = $response->multi->id])) { + $runningResponses[$i] = [$response->multi, []]; + } + + $runningResponses[$i][1][$response->id] = $response; + + if (null === $response->buffer) { + // Response already completed + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * @param NativeClientState $multi + */ + private static function perform(ClientState $multi, ?array &$responses = null): void + { + foreach ($multi->openHandles as $i => [$pauseExpiry, $h, $buffer, $onProgress]) { + if ($pauseExpiry) { + if (hrtime(true) / 1E9 < $pauseExpiry) { + continue; + } + + $multi->openHandles[$i][0] = 0; + } + + $hasActivity = false; + $remaining = &$multi->openHandles[$i][4]; + $info = &$multi->openHandles[$i][5]; + $e = null; + + // Read incoming buffer and write it to the dechunk one + try { + if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) { + fwrite($buffer, $data); + $hasActivity = true; + $multi->sleep = false; + + if (-1 !== $remaining) { + $remaining -= \strlen($data); + } + } + } catch (\Throwable $e) { + $hasActivity = $onProgress = false; + } + + if (!$hasActivity) { + if ($onProgress) { + try { + // Notify the progress callback so that it can e.g. cancel + // the request if the stream is inactive for too long + $info['total_time'] = microtime(true) - $info['start_time']; + $onProgress(); + } catch (\Throwable $e) { + // no-op + } + } + } elseif ('' !== $data = stream_get_contents($buffer, -1, 0)) { + rewind($buffer); + ftruncate($buffer, 0); + + if (null === $e) { + $multi->handlesActivity[$i][] = $data; + } + } + + if (null !== $e || !$remaining || feof($h)) { + // Stream completed + $info['total_time'] = microtime(true) - $info['start_time']; + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; + + if ($onProgress) { + try { + $onProgress(-1); + } catch (\Throwable $e) { + // no-op + } + } + + if (null === $e) { + if (0 < $remaining) { + $e = new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $remaining)); + } elseif (-1 === $remaining && fwrite($buffer, '-') && '' !== stream_get_contents($buffer, -1, 0)) { + $e = new TransportException('Transfer closed with outstanding data remaining from chunked response.'); + } + } + + $multi->handlesActivity[$i][] = null; + $multi->handlesActivity[$i][] = $e; + if (null !== ($host = $multi->openHandles[$i][6] ?? null) && 0 >= --$multi->hosts[$host]) { + unset($multi->hosts[$host]); + } + unset($multi->openHandles[$i]); + $multi->sleep = false; + } + } + + if (null === $responses) { + return; + } + + $maxHosts = $multi->maxHostConnections; + + foreach ($responses as $i => $response) { + if (null !== $response->remaining || null === $response->buffer) { + continue; + } + + if ($response->pauseExpiry && hrtime(true) / 1E9 < $response->pauseExpiry) { + // Create empty open handles to tell we still have pending requests + $multi->openHandles[$i] = [\INF, null, null, null]; + } elseif ($maxHosts && $maxHosts > ($multi->hosts[parse_url($response->url, \PHP_URL_HOST)] ?? 0)) { + // Open the next pending request - this is a blocking operation so we do only one of them + $response->open(); + $multi->sleep = false; + self::perform($multi); + $maxHosts = 0; + } + } + } + + /** + * @param NativeClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + if (!$multi->sleep = !$multi->sleep) { + return -1; + } + + $_ = $handles = []; + $now = null; + + foreach ($multi->openHandles as [$pauseExpiry, $h]) { + if (null === $h) { + continue; + } + + if ($pauseExpiry && ($now ??= hrtime(true) / 1E9) < $pauseExpiry) { + $timeout = min($timeout, $pauseExpiry - $now); + continue; + } + + $handles[] = $h; + } + + if (!$handles) { + usleep((int) (1E6 * $timeout)); + + return 0; + } + + return stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout))); + } +} diff --git a/vendor/symfony/http-client/Response/ResponseStream.php b/vendor/symfony/http-client/Response/ResponseStream.php new file mode 100644 index 0000000..624b2f1 --- /dev/null +++ b/vendor/symfony/http-client/Response/ResponseStream.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; + +/** + * @author Nicolas Grekas + */ +final class ResponseStream implements ResponseStreamInterface +{ + public function __construct( + private \Generator $generator, + ) { + } + + public function key(): ResponseInterface + { + return $this->generator->key(); + } + + public function current(): ChunkInterface + { + return $this->generator->current(); + } + + public function next(): void + { + $this->generator->next(); + } + + public function rewind(): void + { + $this->generator->rewind(); + } + + public function valid(): bool + { + return $this->generator->valid(); + } +} diff --git a/vendor/symfony/http-client/Response/StreamWrapper.php b/vendor/symfony/http-client/Response/StreamWrapper.php new file mode 100644 index 0000000..50b9937 --- /dev/null +++ b/vendor/symfony/http-client/Response/StreamWrapper.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Allows turning ResponseInterface instances to PHP streams. + * + * @author Nicolas Grekas + */ +class StreamWrapper +{ + /** @var resource|null */ + public $context; + + private HttpClientInterface|ResponseInterface $client; + + private ResponseInterface $response; + + /** @var resource|string|null */ + private $content; + + /** @var resource|callable|null */ + private $handle; + + private bool $blocking = true; + private ?float $timeout = null; + private bool $eof = false; + private ?int $offset = 0; + + /** + * Creates a PHP stream resource from a ResponseInterface. + * + * @return resource + */ + public static function createResource(ResponseInterface $response, ?HttpClientInterface $client = null) + { + if ($response instanceof StreamableInterface) { + $stack = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + if ($response !== ($stack[1]['object'] ?? null)) { + return $response->toStream(false); + } + } + + if (null === $client && !method_exists($response, 'stream')) { + throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__)); + } + + static $registered = false; + + if (!$registered = $registered || stream_wrapper_register(strtr(__CLASS__, '\\', '-'), __CLASS__)) { + throw new \RuntimeException(error_get_last()['message'] ?? 'Registering the "symfony" stream wrapper failed.'); + } + + $context = [ + 'client' => $client ?? $response, + 'response' => $response, + ]; + + return fopen(strtr(__CLASS__, '\\', '-').'://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } + + /** + * @param resource|callable|null $handle The resource handle that should be monitored when + * stream_select() is used on the created stream + * @param resource|null $content The seekable resource where the response body is buffered + */ + public function bindHandles(&$handle, &$content): void + { + $this->handle = &$handle; + $this->content = &$content; + $this->offset = null; + } + + public function stream_open(string $path, string $mode, int $options): bool + { + if ('r' !== $mode) { + if ($options & \STREAM_REPORT_ERRORS) { + trigger_error(sprintf('Invalid mode "%s": only "r" is supported.', $mode), \E_USER_WARNING); + } + + return false; + } + + $context = stream_context_get_options($this->context)['symfony'] ?? null; + $this->client = $context['client'] ?? null; + $this->response = $context['response'] ?? null; + $this->context = null; + + if (null !== $this->client && null !== $this->response) { + return true; + } + + if ($options & \STREAM_REPORT_ERRORS) { + trigger_error('Missing options "client" or "response" in "symfony" stream context.', \E_USER_WARNING); + } + + return false; + } + + public function stream_read(int $count): string|false + { + if (\is_resource($this->content)) { + // Empty the internal activity list + foreach ($this->client->stream([$this->response], 0) as $chunk) { + try { + if (!$chunk->isTimeout() && $chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + if (0 !== fseek($this->content, $this->offset ?? 0)) { + return false; + } + + if ('' !== $data = fread($this->content, $count)) { + fseek($this->content, 0, \SEEK_END); + $this->offset += \strlen($data); + + return $data; + } + } + + if (\is_string($this->content)) { + if (\strlen($this->content) <= $count) { + $data = $this->content; + $this->content = null; + } else { + $data = substr($this->content, 0, $count); + $this->content = substr($this->content, $count); + } + $this->offset += \strlen($data); + + return $data; + } + + foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) { + try { + $this->eof = true; + $this->eof = !$chunk->isTimeout(); + + if (!$this->eof && !$this->blocking) { + return ''; + } + + $this->eof = $chunk->isLast(); + + if ($chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + + if ('' !== $data = $chunk->getContent()) { + if (\strlen($data) > $count) { + $this->content ??= substr($data, $count); + $data = substr($data, 0, $count); + } + $this->offset += \strlen($data); + + return $data; + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + return ''; + } + + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + if (\STREAM_OPTION_BLOCKING === $option) { + $this->blocking = (bool) $arg1; + } elseif (\STREAM_OPTION_READ_TIMEOUT === $option) { + $this->timeout = $arg1 + $arg2 / 1e6; + } else { + return false; + } + + return true; + } + + public function stream_tell(): int + { + return $this->offset ?? 0; + } + + public function stream_eof(): bool + { + return $this->eof && !\is_string($this->content); + } + + public function stream_seek(int $offset, int $whence = \SEEK_SET): bool + { + if (null === $this->content && null === $this->offset) { + $this->response->getStatusCode(); + $this->offset = 0; + } + + if (!\is_resource($this->content) || 0 !== fseek($this->content, 0, \SEEK_END)) { + return false; + } + + $size = ftell($this->content); + + if (\SEEK_CUR === $whence) { + $offset += $this->offset ?? 0; + } + + if (\SEEK_END === $whence || $size < $offset) { + foreach ($this->client->stream([$this->response]) as $chunk) { + try { + if ($chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + + // Chunks are buffered in $this->content already + $size += \strlen($chunk->getContent()); + + if (\SEEK_END !== $whence && $offset <= $size) { + break; + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + if (\SEEK_END === $whence) { + $offset += $size; + } + } + + if (0 <= $offset && $offset <= $size) { + $this->eof = false; + $this->offset = $offset; + + return true; + } + + return false; + } + + /** + * @return resource|false + */ + public function stream_cast(int $castAs) + { + if (\STREAM_CAST_FOR_SELECT === $castAs) { + $this->response->getHeaders(false); + + return (\is_callable($this->handle) ? ($this->handle)() : $this->handle) ?? false; + } + + return false; + } + + public function stream_stat(): array + { + try { + $headers = $this->response->getHeaders(false); + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + $headers = []; + } + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 33060, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => (int) ($headers['content-length'][0] ?? -1), + 'atime' => 0, + 'mtime' => strtotime($headers['last-modified'][0] ?? '') ?: 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + ]; + } + + private function __construct() + { + } +} diff --git a/vendor/symfony/http-client/Response/StreamableInterface.php b/vendor/symfony/http-client/Response/StreamableInterface.php new file mode 100644 index 0000000..eb1f933 --- /dev/null +++ b/vendor/symfony/http-client/Response/StreamableInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +interface StreamableInterface +{ + /** + * Casts the response to a PHP stream resource. + * + * @return resource + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toStream(bool $throw = true); +} diff --git a/vendor/symfony/http-client/Response/TraceableResponse.php b/vendor/symfony/http-client/Response/TraceableResponse.php new file mode 100644 index 0000000..e382f23 --- /dev/null +++ b/vendor/symfony/http-client/Response/TraceableResponse.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\RedirectionException; +use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\Stopwatch\StopwatchEvent; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class TraceableResponse implements ResponseInterface, StreamableInterface +{ + public function __construct( + private HttpClientInterface $client, + private ResponseInterface $response, + private mixed &$content, + private ?StopwatchEvent $event = null, + ) { + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + try { + if (method_exists($this->response, '__destruct')) { + $this->response->__destruct(); + } + } finally { + if ($this->event?->isStarted()) { + $this->event->stop(); + } + } + } + + public function getStatusCode(): int + { + try { + return $this->response->getStatusCode(); + } finally { + if ($this->event?->isStarted()) { + $this->event->lap(); + } + } + } + + public function getHeaders(bool $throw = true): array + { + try { + return $this->response->getHeaders($throw); + } finally { + if ($this->event?->isStarted()) { + $this->event->lap(); + } + } + } + + public function getContent(bool $throw = true): string + { + try { + if (false === $this->content) { + return $this->response->getContent($throw); + } + + return $this->content = $this->response->getContent(false); + } finally { + if ($this->event?->isStarted()) { + $this->event->stop(); + } + if ($throw) { + $this->checkStatusCode($this->response->getStatusCode()); + } + } + } + + public function toArray(bool $throw = true): array + { + try { + if (false === $this->content) { + return $this->response->toArray($throw); + } + + return $this->content = $this->response->toArray(false); + } finally { + if ($this->event?->isStarted()) { + $this->event->stop(); + } + if ($throw) { + $this->checkStatusCode($this->response->getStatusCode()); + } + } + } + + public function cancel(): void + { + $this->response->cancel(); + + if ($this->event?->isStarted()) { + $this->event->stop(); + } + } + + public function getInfo(?string $type = null): mixed + { + return $this->response->getInfo($type); + } + + /** + * Casts the response to a PHP stream resource. + * + * @return resource + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->response->getHeaders(true); + } + + if ($this->response instanceof StreamableInterface) { + return $this->response->toStream(false); + } + + return StreamWrapper::createResource($this->response, $this->client); + } + + /** + * @internal + */ + public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator + { + $wrappedResponses = []; + $traceableMap = new \SplObjectStorage(); + + foreach ($responses as $r) { + if (!$r instanceof self) { + throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($r))); + } + + $traceableMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + if ($r->event && !$r->event->isStarted()) { + $r->event->start(); + } + } + + foreach ($client->stream($wrappedResponses, $timeout) as $r => $chunk) { + if ($traceableMap[$r]->event && $traceableMap[$r]->event->isStarted()) { + try { + if ($chunk->isTimeout() || !$chunk->isLast()) { + $traceableMap[$r]->event->lap(); + } else { + $traceableMap[$r]->event->stop(); + } + } catch (TransportExceptionInterface $e) { + $traceableMap[$r]->event->stop(); + if ($chunk instanceof ErrorChunk) { + $chunk->didThrow(false); + } else { + $chunk = new ErrorChunk($chunk->getOffset(), $e); + } + } + } + yield $traceableMap[$r] => $chunk; + } + } + + private function checkStatusCode(int $code): void + { + if (500 <= $code) { + throw new ServerException($this); + } + + if (400 <= $code) { + throw new ClientException($this); + } + + if (300 <= $code) { + throw new RedirectionException($this); + } + } +} diff --git a/vendor/symfony/http-client/Response/TransportResponseTrait.php b/vendor/symfony/http-client/Response/TransportResponseTrait.php new file mode 100644 index 0000000..7b65fd7 --- /dev/null +++ b/vendor/symfony/http-client/Response/TransportResponseTrait.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; + +/** + * Implements common logic for transport-level response classes. + * + * @author Nicolas Grekas + * + * @internal + */ +trait TransportResponseTrait +{ + private Canary $canary; + private array $headers = []; + private array $info = [ + 'response_headers' => [], + 'http_code' => 0, + 'error' => null, + 'canceled' => false, + ]; + + /** @var object|resource|null */ + private $handle; + private int|string $id; + private ?float $timeout = 0; + private \InflateContext|bool|null $inflate = null; + private ?array $finalInfo = null; + private ?LoggerInterface $logger = null; + + public function getStatusCode(): int + { + if ($this->initializer) { + self::initialize($this); + } + + return $this->info['http_code']; + } + + public function getHeaders(bool $throw = true): array + { + if ($this->initializer) { + self::initialize($this); + } + + if ($throw) { + $this->checkStatusCode(); + } + + return $this->headers; + } + + public function cancel(): void + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->close(); + } + + /** + * Closes the response and all its network handles. + */ + protected function close(): void + { + $this->canary->cancel(); + $this->inflate = null; + } + + /** + * Adds pending responses to the activity list. + */ + abstract protected static function schedule(self $response, array &$runningResponses): void; + + /** + * Performs all pending non-blocking operations. + */ + abstract protected static function perform(ClientState $multi, array &$responses): void; + + /** + * Waits for network activity. + */ + abstract protected static function select(ClientState $multi, float $timeout): int; + + private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void + { + foreach ($responseHeaders as $h) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (\d\d\d)(?: |$)#', $h, $m)) { + if ($headers) { + $debug .= "< \r\n"; + $headers = []; + } + $info['http_code'] = (int) $m[1]; + } elseif (2 === \count($m = explode(':', $h, 2))) { + $headers[strtolower($m[0])][] = ltrim($m[1]); + } + + $debug .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + } + + $debug .= "< \r\n"; + } + + /** + * Ensures the request is always sent and that the response code was checked. + */ + private function doDestruct(): void + { + $this->shouldBuffer = true; + + if ($this->initializer && null === $this->info['error']) { + self::initialize($this); + $this->checkStatusCode(); + } + } + + /** + * Implements an event loop based on a buffer activity queue. + * + * @param iterable $responses + * + * @internal + */ + public static function stream(iterable $responses, ?float $timeout = null): \Generator + { + $runningResponses = []; + + foreach ($responses as $response) { + self::schedule($response, $runningResponses); + } + + $lastActivity = hrtime(true) / 1E9; + $elapsedTimeout = 0; + + if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) { + $timeout = null; + } elseif ($fromLastTimeout = 0 > $timeout) { + $timeout = -$timeout; + } + + while (true) { + $hasActivity = false; + $timeoutMax = 0; + $timeoutMin = $timeout ?? \INF; + + /** @var ClientState $multi */ + foreach ($runningResponses as $i => [$multi]) { + $responses = &$runningResponses[$i][1]; + self::perform($multi, $responses); + + foreach ($responses as $j => $response) { + $timeoutMax = $timeout ?? max($timeoutMax, $response->timeout); + $timeoutMin = min($timeoutMin, $response->timeout, 1); + $chunk = false; + + if ($fromLastTimeout && null !== $multi->lastTimeout) { + $elapsedTimeout = hrtime(true) / 1E9 - $multi->lastTimeout; + } + + if (isset($multi->handlesActivity[$j])) { + $multi->lastTimeout = null; + } elseif (!isset($multi->openHandles[$j])) { + unset($responses[$j]); + continue; + } elseif ($elapsedTimeout >= $timeoutMax) { + $multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))]; + $multi->lastTimeout ??= $lastActivity; + } else { + continue; + } + + while ($multi->handlesActivity[$j] ?? false) { + $hasActivity = true; + $elapsedTimeout = 0; + + if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) { + if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Error while processing content unencoding for "%s".', $response->getInfo('url')))]; + continue; + } + + if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))]; + continue; + } + + $chunkLen = \strlen($chunk); + $chunk = new DataChunk($response->offset, $chunk); + $response->offset += $chunkLen; + } elseif (null === $chunk) { + $e = $multi->handlesActivity[$j][0]; + unset($responses[$j], $multi->handlesActivity[$j]); + $response->close(); + + if (null !== $e) { + $response->info['error'] = $e->getMessage(); + + if ($e instanceof \Error) { + throw $e; + } + + $chunk = new ErrorChunk($response->offset, $e); + } else { + if (0 === $response->offset && null === $response->content) { + $response->content = fopen('php://memory', 'w+'); + } + + $chunk = new LastChunk($response->offset); + } + } elseif ($chunk instanceof ErrorChunk) { + unset($responses[$j]); + $elapsedTimeout = $timeoutMax; + } elseif ($chunk instanceof FirstChunk) { + if ($response->logger) { + $info = $response->getInfo(); + $response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url'])); + } + + $response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(\ZLIB_ENCODING_GZIP) : null; + + if ($response->shouldBuffer instanceof \Closure) { + try { + $response->shouldBuffer = ($response->shouldBuffer)($response->headers); + + if (null !== $response->info['error']) { + throw new TransportException($response->info['error']); + } + } catch (\Throwable $e) { + $response->close(); + $multi->handlesActivity[$j] = [null, $e]; + } + } + + if (true === $response->shouldBuffer) { + $response->content = fopen('php://temp', 'w+'); + } elseif (\is_resource($response->shouldBuffer)) { + $response->content = $response->shouldBuffer; + } + $response->shouldBuffer = null; + + yield $response => $chunk; + + if ($response->initializer && null === $response->info['error']) { + // Ensure the HTTP status code is always checked + $response->getHeaders(true); + } + + continue; + } + + yield $response => $chunk; + } + + unset($multi->handlesActivity[$j]); + + if ($chunk instanceof ErrorChunk && !$chunk->didThrow()) { + // Ensure transport exceptions are always thrown + $chunk->getContent(); + } + } + + if (!$responses) { + unset($runningResponses[$i]); + } + + // Prevent memory leaks + $multi->handlesActivity = $multi->handlesActivity ?: []; + $multi->openHandles = $multi->openHandles ?: []; + } + + if (!$runningResponses) { + break; + } + + if ($hasActivity) { + $lastActivity = hrtime(true) / 1E9; + continue; + } + + if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $elapsedTimeout))) { + usleep((int) min(500, 1E6 * $timeoutMin)); + } + + $elapsedTimeout = hrtime(true) / 1E9 - $lastActivity; + } + } +} diff --git a/vendor/symfony/http-client/Retry/GenericRetryStrategy.php b/vendor/symfony/http-client/Retry/GenericRetryStrategy.php new file mode 100644 index 0000000..95daf3b --- /dev/null +++ b/vendor/symfony/http-client/Retry/GenericRetryStrategy.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Retry; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * Decides to retry the request when HTTP status codes belong to the given list of codes. + * + * @author Jérémy Derussé + */ +class GenericRetryStrategy implements RetryStrategyInterface +{ + public const IDEMPOTENT_METHODS = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']; + public const DEFAULT_RETRY_STATUS_CODES = [ + 0 => self::IDEMPOTENT_METHODS, // for transport exceptions + 423, + 425, + 429, + 500 => self::IDEMPOTENT_METHODS, + 502, + 503, + 504 => self::IDEMPOTENT_METHODS, + 507 => self::IDEMPOTENT_METHODS, + 510 => self::IDEMPOTENT_METHODS, + ]; + + private int $delayMs; + private float $multiplier; + private int $maxDelayMs; + private float $jitter; + + /** + * @param array $statusCodes List of HTTP status codes that trigger a retry + * @param int $delayMs Amount of time to delay (or the initial value when multiplier is used) + * @param float $multiplier Multiplier to apply to the delay each time a retry occurs + * @param int $maxDelayMs Maximum delay to allow (0 means no maximum) + * @param float $jitter Probability of randomness int delay (0 = none, 1 = 100% random) + */ + public function __construct( + private array $statusCodes = self::DEFAULT_RETRY_STATUS_CODES, + int $delayMs = 1000, + float $multiplier = 2.0, + int $maxDelayMs = 0, + float $jitter = 0.1, + ) { + if ($delayMs < 0) { + throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMs)); + } + $this->delayMs = $delayMs; + + if ($multiplier < 1) { + throw new InvalidArgumentException(sprintf('Multiplier must be greater than or equal to one: "%s" given.', $multiplier)); + } + $this->multiplier = $multiplier; + + if ($maxDelayMs < 0) { + throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMs)); + } + $this->maxDelayMs = $maxDelayMs; + + if ($jitter < 0 || $jitter > 1) { + throw new InvalidArgumentException(sprintf('Jitter must be between 0 and 1: "%s" given.', $jitter)); + } + $this->jitter = $jitter; + } + + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool + { + $statusCode = $context->getStatusCode(); + if (\in_array($statusCode, $this->statusCodes, true)) { + return true; + } + if (isset($this->statusCodes[$statusCode]) && \is_array($this->statusCodes[$statusCode])) { + return \in_array($context->getInfo('http_method'), $this->statusCodes[$statusCode], true); + } + if (null === $exception) { + return false; + } + + if (\in_array(0, $this->statusCodes, true)) { + return true; + } + if (isset($this->statusCodes[0]) && \is_array($this->statusCodes[0])) { + return \in_array($context->getInfo('http_method'), $this->statusCodes[0], true); + } + + return false; + } + + public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int + { + $delay = $this->delayMs * $this->multiplier ** $context->getInfo('retry_count'); + + if ($this->jitter > 0) { + $randomness = (int) ($delay * $this->jitter); + $delay += random_int(-$randomness, +$randomness); + } + + if ($delay > $this->maxDelayMs && 0 !== $this->maxDelayMs) { + return $this->maxDelayMs; + } + + return (int) $delay; + } +} diff --git a/vendor/symfony/http-client/Retry/RetryStrategyInterface.php b/vendor/symfony/http-client/Retry/RetryStrategyInterface.php new file mode 100644 index 0000000..2576433 --- /dev/null +++ b/vendor/symfony/http-client/Retry/RetryStrategyInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Retry; + +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Jérémy Derussé + * @author Nicolas Grekas + */ +interface RetryStrategyInterface +{ + /** + * Returns whether the request should be retried. + * + * @param ?string $responseContent Null is passed when the body did not arrive yet + * + * @return bool|null Returns null to signal that the body is required to take a decision + */ + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool; + + /** + * Returns the time to wait in milliseconds. + */ + public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int; +} diff --git a/vendor/symfony/http-client/RetryableHttpClient.php b/vendor/symfony/http-client/RetryableHttpClient.php new file mode 100644 index 0000000..d3b7794 --- /dev/null +++ b/vendor/symfony/http-client/RetryableHttpClient.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\Retry\RetryStrategyInterface; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Automatically retries failing HTTP requests. + * + * @author Jérémy Derussé + */ +class RetryableHttpClient implements HttpClientInterface, ResetInterface +{ + use AsyncDecoratorTrait; + + private RetryStrategyInterface $strategy; + private int $maxRetries; + private ?LoggerInterface $logger; + private array $baseUris = []; + + /** + * @param int $maxRetries The maximum number of times to retry + */ + public function __construct(HttpClientInterface $client, ?RetryStrategyInterface $strategy = null, int $maxRetries = 3, ?LoggerInterface $logger = null) + { + $this->client = $client; + $this->strategy = $strategy ?? new GenericRetryStrategy(); + $this->maxRetries = $maxRetries; + $this->logger = $logger; + } + + public function withOptions(array $options): static + { + if (\array_key_exists('base_uri', $options)) { + if (\is_array($options['base_uri'])) { + $this->baseUris = $options['base_uri']; + unset($options['base_uri']); + } else { + $this->baseUris = []; + } + } + + $clone = clone $this; + $clone->maxRetries = (int) ($options['max_retries'] ?? $this->maxRetries); + unset($options['max_retries']); + + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $baseUris = \array_key_exists('base_uri', $options) ? $options['base_uri'] : $this->baseUris; + $baseUris = \is_array($baseUris) ? $baseUris : []; + $options = self::shiftBaseUri($options, $baseUris); + + $maxRetries = (int) ($options['max_retries'] ?? $this->maxRetries); + unset($options['max_retries']); + + if ($maxRetries <= 0) { + return new AsyncResponse($this->client, $method, $url, $options); + } + + return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, $maxRetries, &$baseUris) { + static $retryCount = 0; + static $content = ''; + static $firstChunk; + + $exception = null; + try { + if ($context->getInfo('canceled') || $chunk->isTimeout() || null !== $chunk->getInformationalStatus()) { + yield $chunk; + + return; + } + } catch (TransportExceptionInterface $exception) { + // catch TransportExceptionInterface to send it to the strategy + } + if (null !== $exception) { + // always retry request that fail to resolve DNS + if ('' !== $context->getInfo('primary_ip')) { + $shouldRetry = $this->strategy->shouldRetry($context, null, $exception); + if (null === $shouldRetry) { + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', $this->strategy::class)); + } + + if (false === $shouldRetry) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + } + } elseif ($chunk->isFirst()) { + if (false === $shouldRetry = $this->strategy->shouldRetry($context, null, null)) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + + // Body is needed to decide + if (null === $shouldRetry) { + $firstChunk = $chunk; + $content = ''; + + return; + } + } else { + if (!$chunk->isLast()) { + $content .= $chunk->getContent(); + + return; + } + + if (null === $shouldRetry = $this->strategy->shouldRetry($context, $content, null)) { + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', $this->strategy::class)); + } + + if (false === $shouldRetry) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + } + + $context->getResponse()->cancel(); + + $delay = $this->getDelayFromHeader($context->getHeaders()) ?? $this->strategy->getDelay($context, !$exception && $chunk->isLast() ? $content : null, $exception); + ++$retryCount; + $content = ''; + $firstChunk = null; + + $this->logger?->info('Try #{count} after {delay}ms'.($exception ? ': '.$exception->getMessage() : ', status code: '.$context->getStatusCode()), [ + 'count' => $retryCount, + 'delay' => $delay, + ]); + + $context->setInfo('retry_count', $retryCount); + $context->replaceRequest($method, $url, self::shiftBaseUri($options, $baseUris)); + $context->pause($delay / 1000); + + if ($retryCount >= $maxRetries) { + $context->passthru(); + } + }); + } + + private function getDelayFromHeader(array $headers): ?int + { + if (null !== $after = $headers['retry-after'][0] ?? null) { + if (is_numeric($after)) { + return (int) ($after * 1000); + } + + if (false !== $time = strtotime($after)) { + return max(0, $time - time()) * 1000; + } + } + + return null; + } + + private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, string &$content, ChunkInterface $lastChunk): \Generator + { + $context->passthru(); + + if (null !== $firstChunk) { + yield $firstChunk; + } + + if ('' !== $content) { + $chunk = $context->createChunk($content); + $content = ''; + + yield $chunk; + } + + yield $lastChunk; + } + + private static function shiftBaseUri(array $options, array &$baseUris): array + { + if ($baseUris) { + $baseUri = 1 < \count($baseUris) ? array_shift($baseUris) : current($baseUris); + $options['base_uri'] = \is_array($baseUri) ? $baseUri[array_rand($baseUri)] : $baseUri; + } + + return $options; + } +} diff --git a/vendor/symfony/http-client/ScopingHttpClient.php b/vendor/symfony/http-client/ScopingHttpClient.php new file mode 100644 index 0000000..0086b2d --- /dev/null +++ b/vendor/symfony/http-client/ScopingHttpClient.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Auto-configure the default options based on the requested URL. + * + * @author Anthony Martin + */ +class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface +{ + use HttpClientTrait; + + private HttpClientInterface $client; + private array $defaultOptionsByRegexp; + private ?string $defaultRegexp; + + public function __construct(HttpClientInterface $client, array $defaultOptionsByRegexp, ?string $defaultRegexp = null) + { + $this->client = $client; + $this->defaultOptionsByRegexp = $defaultOptionsByRegexp; + $this->defaultRegexp = $defaultRegexp; + + if (null !== $defaultRegexp && !isset($defaultOptionsByRegexp[$defaultRegexp])) { + throw new InvalidArgumentException(sprintf('No options are mapped to the provided "%s" default regexp.', $defaultRegexp)); + } + } + + public static function forBaseUri(HttpClientInterface $client, string $baseUri, array $defaultOptions = [], ?string $regexp = null): self + { + $regexp ??= preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($baseUri)))); + + $defaultOptions['base_uri'] = $baseUri; + + return new self($client, [$regexp => $defaultOptions], $regexp); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $e = null; + $url = self::parseUrl($url, $options['query'] ?? []); + + if (\is_string($options['base_uri'] ?? null)) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + + try { + $url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null)); + } catch (InvalidArgumentException $e) { + if (null === $this->defaultRegexp) { + throw $e; + } + + $defaultOptions = $this->defaultOptionsByRegexp[$this->defaultRegexp]; + $options = self::mergeDefaultOptions($options, $defaultOptions, true); + if (\is_string($options['base_uri'] ?? null)) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + $url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null, $defaultOptions['query'] ?? [])); + } + + foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) { + if (preg_match("{{$regexp}}A", $url)) { + if (null === $e || $regexp !== $this->defaultRegexp) { + $options = self::mergeDefaultOptions($options, $defaultOptions, true); + } + break; + } + } + + return $this->client->request($method, $url, $options); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + public function reset(): void + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } + + /** + * @deprecated since Symfony 7.1, configure the logger on the wrapper HTTP client directly instead + */ + public function setLogger(LoggerInterface $logger): void + { + trigger_deprecation('symfony/http-client', '7.1', 'Configure the logger on the wrapper HTTP client directly instead.'); + + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } +} diff --git a/vendor/symfony/http-client/Test/HarFileResponseFactory.php b/vendor/symfony/http-client/Test/HarFileResponseFactory.php new file mode 100644 index 0000000..7265709 --- /dev/null +++ b/vendor/symfony/http-client/Test/HarFileResponseFactory.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Test; + +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * See: https://w3c.github.io/web-performance/specs/HAR/Overview.html. + * + * @author Gary PEGEOT + */ +class HarFileResponseFactory +{ + public function __construct(private string $archiveFile) + { + } + + public function setArchiveFile(string $archiveFile): void + { + $this->archiveFile = $archiveFile; + } + + public function __invoke(string $method, string $url, array $options): ResponseInterface + { + if (!is_file($this->archiveFile)) { + throw new \InvalidArgumentException(sprintf('Invalid file path provided: "%s".', $this->archiveFile)); + } + + $json = json_decode(json: file_get_contents($this->archiveFile), associative: true, flags: \JSON_THROW_ON_ERROR); + + foreach ($json['log']['entries'] as $entry) { + /** + * @var array{status: int, headers: array, content: array} $response + * @var array{method: string, url: string, postData: array} $request + */ + ['response' => $response, 'request' => $request, 'startedDateTime' => $startedDateTime] = $entry; + + $body = $this->getContent($response['content']); + $entryMethod = $request['method']; + $entryUrl = $request['url']; + $requestBody = $options['body'] ?? null; + + if ($method !== $entryMethod || $url !== $entryUrl) { + continue; + } + + if (null !== $requestBody && $requestBody !== $this->getContent($request['postData'] ?? [])) { + continue; + } + + $info = [ + 'http_code' => $response['status'], + 'http_method' => $entryMethod, + 'response_headers' => [], + 'start_time' => strtotime($startedDateTime), + 'url' => $entryUrl, + ]; + + /** @var array{name: string, value: string} $header */ + foreach ($response['headers'] as $header) { + ['name' => $name, 'value' => $value] = $header; + + $info['response_headers'][$name][] = $value; + } + + return new MockResponse($body, $info); + } + + throw new TransportException(sprintf('File "%s" does not contain a response for HTTP request "%s" "%s".', $this->archiveFile, $method, $url)); + } + + /** + * @param array{text: string, encoding: string} $content + */ + private function getContent(array $content): string + { + $text = $content['text'] ?? ''; + $encoding = $content['encoding'] ?? null; + + return match ($encoding) { + 'base64' => base64_decode($text), + null => $text, + default => throw new \InvalidArgumentException(sprintf('Unsupported encoding "%s", currently only base64 is supported.', $encoding)), + }; + } +} diff --git a/vendor/symfony/http-client/ThrottlingHttpClient.php b/vendor/symfony/http-client/ThrottlingHttpClient.php new file mode 100644 index 0000000..66fc173 --- /dev/null +++ b/vendor/symfony/http-client/ThrottlingHttpClient.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Limits the number of requests within a certain period. + */ +class ThrottlingHttpClient implements HttpClientInterface, ResetInterface +{ + use DecoratorTrait { + reset as private traitReset; + } + + public function __construct( + HttpClientInterface $client, + private readonly LimiterInterface $rateLimiter, + ) { + $this->client = $client; + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $response = $this->client->request($method, $url, $options); + + if (0 < $waitDuration = $this->rateLimiter->reserve()->getWaitDuration()) { + $response->getInfo('pause_handler')($waitDuration); + } + + return $response; + } + + public function reset(): void + { + $this->traitReset(); + $this->rateLimiter->reset(); + } +} diff --git a/vendor/symfony/http-client/TraceableHttpClient.php b/vendor/symfony/http-client/TraceableHttpClient.php new file mode 100644 index 0000000..b6d30da --- /dev/null +++ b/vendor/symfony/http-client/TraceableHttpClient.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Component\HttpClient\Response\TraceableResponse; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jérémy Romey + */ +final class TraceableHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface +{ + private HttpClientInterface $client; + private ?Stopwatch $stopwatch; + private \ArrayObject $tracedRequests; + + public function __construct(HttpClientInterface $client, ?Stopwatch $stopwatch = null) + { + $this->client = $client; + $this->stopwatch = $stopwatch; + $this->tracedRequests = new \ArrayObject(); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $content = null; + $traceInfo = []; + $this->tracedRequests[] = [ + 'method' => $method, + 'url' => $url, + 'options' => $options, + 'info' => &$traceInfo, + 'content' => &$content, + ]; + $onProgress = $options['on_progress'] ?? null; + + if (false === ($options['extra']['trace_content'] ?? true)) { + unset($content); + $content = false; + } + + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use (&$traceInfo, $onProgress) { + $traceInfo = $info; + + if (null !== $onProgress) { + $onProgress($dlNow, $dlSize, $info); + } + }; + + return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content, $this->stopwatch?->start("$method $url", 'http_client')); + } + + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof TraceableResponse) { + $responses = [$responses]; + } + + return new ResponseStream(TraceableResponse::stream($this->client, $responses, $timeout)); + } + + public function getTracedRequests(): array + { + return $this->tracedRequests->getArrayCopy(); + } + + public function reset(): void + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + + $this->tracedRequests->exchangeArray([]); + } + + /** + * @deprecated since Symfony 7.1, configure the logger on the wrapper HTTP client directly instead + */ + public function setLogger(LoggerInterface $logger): void + { + trigger_deprecation('symfony/http-client', '7.1', 'Configure the logger on the wrapper HTTP client directly instead.'); + + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + public function withOptions(array $options): static + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } +} diff --git a/vendor/symfony/http-client/UriTemplateHttpClient.php b/vendor/symfony/http-client/UriTemplateHttpClient.php new file mode 100644 index 0000000..2767ed3 --- /dev/null +++ b/vendor/symfony/http-client/UriTemplateHttpClient.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +class UriTemplateHttpClient implements HttpClientInterface, ResetInterface +{ + use DecoratorTrait; + + /** + * @param (\Closure(string $url, array $vars): string)|null $expander + */ + public function __construct(?HttpClientInterface $client = null, private ?\Closure $expander = null, private array $defaultVars = []) + { + $this->client = $client ?? HttpClient::create(); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $vars = $this->defaultVars; + + if (\array_key_exists('vars', $options)) { + if (!\is_array($options['vars'])) { + throw new \InvalidArgumentException('The "vars" option must be an array.'); + } + + $vars = [...$vars, ...$options['vars']]; + unset($options['vars']); + } + + if ($vars) { + $url = ($this->expander ??= $this->createExpanderFromPopularVendors())($url, $vars); + } + + return $this->client->request($method, $url, $options); + } + + public function withOptions(array $options): static + { + if (!\is_array($options['vars'] ?? [])) { + throw new \InvalidArgumentException('The "vars" option must be an array.'); + } + + $clone = clone $this; + $clone->defaultVars = [...$clone->defaultVars, ...$options['vars'] ?? []]; + unset($options['vars']); + + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + /** + * @return \Closure(string $url, array $vars): string + */ + private function createExpanderFromPopularVendors(): \Closure + { + if (class_exists(\GuzzleHttp\UriTemplate\UriTemplate::class)) { + return \GuzzleHttp\UriTemplate\UriTemplate::expand(...); + } + + if (class_exists(\League\Uri\UriTemplate::class)) { + return static fn (string $url, array $vars): string => (new \League\Uri\UriTemplate($url))->expand($vars); + } + + if (class_exists(\Rize\UriTemplate::class)) { + return (new \Rize\UriTemplate())->expand(...); + } + + throw new \LogicException('Support for URI template requires a vendor to expand the URI. Run "composer require guzzlehttp/uri-template" or pass your own expander \Closure implementation.'); + } +} diff --git a/vendor/symfony/http-client/composer.json b/vendor/symfony/http-client/composer.json new file mode 100644 index 0000000..9a61622 --- /dev/null +++ b/vendor/symfony/http-client/composer.json @@ -0,0 +1,58 @@ +{ + "name": "symfony/http-client", + "type": "library", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "keywords": ["http"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3.4.1", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpClient\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/http-foundation/AcceptHeader.php b/vendor/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 0000000..853c000 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeaderItem::class); + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private array $items = []; + + private bool $sorted = true; + + /** + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + */ + public static function fromString(?string $headerValue): self + { + $parts = HeaderUtils::split($headerValue ?? '', ',;='); + + return new self(array_map(function ($subParts) { + static $index = 0; + $part = array_shift($subParts); + $attributes = HeaderUtils::combine($subParts); + + $item = new AcceptHeaderItem($part[0], $attributes); + $item->setIndex($index++); + + return $item; + }, $parts)); + } + + /** + * Returns header value's string representation. + */ + public function __toString(): string + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + */ + public function has(string $value): bool + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + */ + public function get(string $value): ?AcceptHeaderItem + { + return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null; + } + + /** + * Adds an item. + * + * @return $this + */ + public function add(AcceptHeaderItem $item): static + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all(): array + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + */ + public function filter(string $pattern): self + { + return new self(array_filter($this->items, fn (AcceptHeaderItem $item) => preg_match($pattern, $item->getValue()))); + } + + /** + * Returns first item. + */ + public function first(): ?AcceptHeaderItem + { + $this->sort(); + + return $this->items ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort(): void + { + if (!$this->sorted) { + uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/vendor/symfony/http-foundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 0000000..35ecd4e --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + private string $value; + private float $quality = 1.0; + private int $index = 0; + private array $attributes = []; + + public function __construct(string $value, array $attributes = []) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + */ + public static function fromString(?string $itemValue): self + { + $parts = HeaderUtils::split($itemValue ?? '', ';='); + + $part = array_shift($parts); + $attributes = HeaderUtils::combine($parts); + + return new self($part[0], $attributes); + } + + /** + * Returns header value's string representation. + */ + public function __toString(): string + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (\count($this->attributes) > 0) { + $string .= '; '.HeaderUtils::toString($this->attributes, ';'); + } + + return $string; + } + + /** + * Set the item value. + * + * @return $this + */ + public function setValue(string $value): static + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Set the item quality. + * + * @return $this + */ + public function setQuality(float $quality): static + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + */ + public function getQuality(): float + { + return $this->quality; + } + + /** + * Set the item index. + * + * @return $this + */ + public function setIndex(int $index): static + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + */ + public function getIndex(): int + { + return $this->index; + } + + /** + * Tests if an attribute exists. + */ + public function hasAttribute(string $name): bool + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + */ + public function getAttribute(string $name, mixed $default = null): mixed + { + return $this->attributes[$name] ?? $default; + } + + /** + * Returns all attributes. + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @return $this + */ + public function setAttribute(string $name, string $value): static + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = $value; + } + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 0000000..e49b1c9 --- /dev/null +++ b/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static bool $trustXSendfileTypeHeader = false; + + protected File $file; + protected ?\SplTempFileObject $tempFileObject = null; + protected int $offset = 0; + protected int $maxlen = -1; + protected bool $deleteFileAfterSend = false; + protected int $chunkSize = 16 * 1024; + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code (200 "OK" by default) + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct(\SplFileInfo|string $file, int $status = 200, array $headers = [], bool $public = true, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * Sets the file to stream. + * + * @return $this + * + * @throws FileException + */ + public function setFile(\SplFileInfo|string $file, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true): static + { + $isTemporaryFile = $file instanceof \SplTempFileObject; + $this->tempFileObject = $isTemporaryFile ? $file : null; + + if (!$file instanceof File) { + if ($file instanceof \SplFileInfo) { + $file = new File($file->getPathname(), !$isTemporaryFile); + } else { + $file = new File((string) $file); + } + } + + if (!$file->isReadable() && !$isTemporaryFile) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified && !$isTemporaryFile) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + */ + public function getFile(): File + { + return $this->file; + } + + /** + * Sets the response stream chunk size. + * + * @return $this + */ + public function setChunkSize(int $chunkSize): static + { + if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) { + throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.'); + } + + $this->chunkSize = $chunkSize; + + return $this; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + * + * @return $this + */ + public function setAutoLastModified(): static + { + $this->setLastModified(\DateTimeImmutable::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + * + * @return $this + */ + public function setAutoEtag(): static + { + $this->setEtag(base64_encode(hash_file('xxh128', $this->file->getPathname(), true))); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = ''): static + { + if ('' === $filename) { + $filename = $this->file->getFilename(); + } + + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + public function prepare(Request $request): static + { + if ($this->isInformational() || $this->isEmpty()) { + parent::prepare($request); + + $this->maxlen = 0; + + return $this; + } + + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + parent::prepare($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (false === $fileSize = $this->file->getSize()) { + return $this; + } + $this->headers->remove('Transfer-Encoding'); + $this->headers->set('Content-Length', $fileSize); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); + } + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } + if ('x-accel-redirect' === strtolower($type)) { + // Do X-Accel-Mapping substitutions. + // @link https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-redirect + $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); + foreach ($parts as $part) { + [$pathPrefix, $location] = $part; + if (str_starts_with($path, $pathPrefix)) { + $path = $location.substr($path, \strlen($pathPrefix)); + // Only set X-Accel-Redirect header if a valid URI can be produced + // as nginx does not serve arbitrary file paths. + $this->headers->set($type, $path); + $this->maxlen = 0; + break; + } + } + } else { + $this->headers->set($type, $path); + $this->maxlen = 0; + } + } elseif ($request->headers->has('Range') && $request->isMethod('GET')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { + $range = $request->headers->get('Range'); + + if (str_starts_with($range, 'bytes=')) { + [$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0]; + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + $end = min($end, $fileSize - 1); + if ($start < 0 || $start > $end) { + $this->setStatusCode(416); + $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize)); + } elseif ($end - $start < $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + } + } + } + + if ($request->isMethod('HEAD')) { + $this->maxlen = 0; + } + + return $this; + } + + private function hasValidIfRangeHeader(?string $header): bool + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + + public function sendContent(): static + { + try { + if (!$this->isSuccessful()) { + return $this; + } + + if (0 === $this->maxlen) { + return $this; + } + + $out = fopen('php://output', 'w'); + + if ($this->tempFileObject) { + $file = $this->tempFileObject; + $file->rewind(); + } else { + $file = new \SplFileObject($this->file->getPathname(), 'r'); + } + + ignore_user_abort(true); + + if (0 !== $this->offset) { + $file->fseek($this->offset); + } + + $length = $this->maxlen; + while ($length && !$file->eof()) { + $read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length; + + if (false === $data = $file->fread($read)) { + break; + } + while ('' !== $data) { + $read = fwrite($out, $data); + if (false === $read || connection_aborted()) { + break 2; + } + if (0 < $length) { + $length -= $read; + } + $data = substr($data, $read); + } + } + + fclose($out); + } finally { + if (null === $this->tempFileObject && $this->deleteFileAfterSend && is_file($this->file->getPathname())) { + unlink($this->file->getPathname()); + } + } + + return $this; + } + + /** + * @throws \LogicException when the content is not null + */ + public function setContent(?string $content): static + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + + return $this; + } + + public function getContent(): string|false + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader(): void + { + self::$trustXSendfileTypeHeader = true; + } + + /** + * If this is set to true, the file will be unlinked after the request is sent + * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. + * + * @return $this + */ + public function deleteFileAfterSend(bool $shouldDelete = true): static + { + $this->deleteFileAfterSend = $shouldDelete; + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/CHANGELOG.md b/vendor/symfony/http-foundation/CHANGELOG.md new file mode 100644 index 0000000..0034705 --- /dev/null +++ b/vendor/symfony/http-foundation/CHANGELOG.md @@ -0,0 +1,378 @@ +CHANGELOG +========= + +7.1 +--- + + * Add optional `$expirationParameter` argument to `UriSigner::__construct()` + * Add optional `$expiration` argument to `UriSigner::sign()` + * Rename `$parameter` argument of `UriSigner::__construct()` to `$hashParameter` + * Add `UploadedFile::getClientOriginalPath()` + * Add `QueryParameterRequestMatcher` + * Add `HeaderRequestMatcher` + * Add support for `\SplTempFileObject` in `BinaryFileResponse` + * Add `verbose` argument to response test constraints + +7.0 +--- + + * Calling `ParameterBag::filter()` throws an `UnexpectedValueException` on invalid value, unless flag `FILTER_NULL_ON_FAILURE` is set + * Calling `ParameterBag::getInt()` and `ParameterBag::getBool()` throws an `UnexpectedValueException` on invalid value + * Remove classes `RequestMatcher` and `ExpressionRequestMatcher` + * Remove `Request::getContentType()`, use `Request::getContentTypeFormat()` instead + * Throw an `InvalidArgumentException` when calling `Request::create()` with a malformed URI + * Require explicit argument when calling `JsonResponse::setCallback()`, `Response::setExpires/setLastModified/setEtag()`, `MockArraySessionStorage/NativeSessionStorage::setMetadataBag()`, `NativeSessionStorage::setSaveHandler()` + * Add argument `$statusCode` to `Response::sendHeaders()` and `StreamedResponse::sendHeaders()` + +6.4 +--- + + * Make `HeaderBag::getDate()`, `Response::getDate()`, `getExpires()` and `getLastModified()` return a `DateTimeImmutable` + * Support root-level `Generator` in `StreamedJsonResponse` + * Add `UriSigner` from the HttpKernel component + * Add `partitioned` flag to `Cookie` (CHIPS Cookie) + * Add argument `bool $flush = true` to `Response::send()` +* Make `MongoDbSessionHandler` instantiable with the mongodb extension directly + +6.3 +--- + + * Calling `ParameterBag::getDigit()`, `getAlnum()`, `getAlpha()` on an `array` throws a `UnexpectedValueException` instead of a `TypeError` + * Add `ParameterBag::getString()` to convert a parameter into string and throw an exception if the value is invalid + * Add `ParameterBag::getEnum()` + * Create migration for session table when pdo handler is used + * Add support for Relay PHP extension for Redis + * The `Response::sendHeaders()` method now takes an optional HTTP status code as parameter, allowing to send informational responses such as Early Hints responses (103 status code) + * Add `IpUtils::isPrivateIp()` + * Add `Request::getPayload(): InputBag` + * Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()`, + * Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set + +6.2 +--- + + * Add `StreamedJsonResponse` class for efficient JSON streaming + * The HTTP cache store uses the `xxh128` algorithm + * Deprecate calling `JsonResponse::setCallback()`, `Response::setExpires/setLastModified/setEtag()`, `MockArraySessionStorage/NativeSessionStorage::setMetadataBag()`, `NativeSessionStorage::setSaveHandler()` without arguments + * Add request matchers under the `Symfony\Component\HttpFoundation\RequestMatcher` namespace + * Deprecate `RequestMatcher` in favor of `ChainRequestMatcher` + * Deprecate `Symfony\Component\HttpFoundation\ExpressionRequestMatcher` in favor of `Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher` + +6.1 +--- + + * Add stale while revalidate and stale if error cache header + * Allow dynamic session "ttl" when using a remote storage + * Deprecate `Request::getContentType()`, use `Request::getContentTypeFormat()` instead + +6.0 +--- + + * Remove the `NamespacedAttributeBag` class + * Removed `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, `StreamedResponse::create()` and + `BinaryFileResponse::create()` methods (use `__construct()` instead) + * Not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()` throws an `\InvalidArgumentException`; wrap your filter in a closure instead + * Not passing a `Closure` together with `FILTER_CALLBACK` to `InputBag::filter()` throws an `\InvalidArgumentException`; wrap your filter in a closure instead + * Removed the `Request::HEADER_X_FORWARDED_ALL` constant, use either `Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO` or `Request::HEADER_X_FORWARDED_AWS_ELB` or `Request::HEADER_X_FORWARDED_TRAEFIK`constants instead + * Rename `RequestStack::getMasterRequest()` to `getMainRequest()` + * Not passing `FILTER_REQUIRE_ARRAY` or `FILTER_FORCE_ARRAY` flags to `InputBag::filter()` when filtering an array will throw `BadRequestException` + * Removed the `Request::HEADER_X_FORWARDED_ALL` constant + * Retrieving non-scalar values using `InputBag::get()` will throw `BadRequestException` (use `InputBad::all()` instead to retrieve an array) + * Passing non-scalar default value as the second argument `InputBag::get()` will throw `\InvalidArgumentException` + * Passing non-scalar, non-array value as the second argument `InputBag::set()` will throw `\InvalidArgumentException` + * Passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()` is not supported anymore. + +5.4 +--- + + * Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead. + * Add the `litespeed_finish_request` method to work with Litespeed + * Deprecate `upload_progress.*` and `url_rewriter.tags` session options + * Allow setting session options via DSN + +5.3 +--- + + * Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes + * Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException` + * Add the `RequestStack::getSession` method + * Deprecate the `NamespacedAttributeBag` class + * Add `ResponseFormatSame` PHPUnit constraint + * Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement + +5.2.0 +----- + + * added support for `X-Forwarded-Prefix` header + * added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names + * added `File::getContent()` + * added ability to use comma separated ip addresses for `RequestMatcher::matchIps()` + * added `Request::toArray()` to parse a JSON request body to an array + * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter` + * deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead. + * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead. + * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead + +5.1.0 +----- + + * added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`, + `Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`, + `Cookie::withRaw`, `Cookie::withSameSite` + * Deprecate `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use + `__construct()` instead) + * added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference + according to [RFC 8674](https://tools.ietf.org/html/rfc8674) + * made the Mime component an optional dependency + * added `MarshallingSessionHandler`, `IdentityMarshaller` + * made `Session` accept a callback to report when the session is being used + * Add support for all core cache control directives + * Added `Symfony\Component\HttpFoundation\InputBag` + * Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values + +5.0.0 +----- + + * made `Cookie` auto-secure and lax by default + * removed classes in the `MimeType` namespace, use the Symfony Mime component instead + * removed method `UploadedFile::getClientSize()` and the related constructor argument + * made `Request::getSession()` throw if the session has not been set before + * removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL` + * passing a null url when instantiating a `RedirectResponse` is not allowed + +4.4.0 +----- + + * passing arguments to `Request::isMethodSafe()` is deprecated. + * `ApacheRequest` is deprecated, use the `Request` class instead. + * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead + * [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column, + make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to + update your database. + * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, + make sure to run `CREATE INDEX expiry ON sessions (sess_lifetime)` to update your database + to speed up garbage collection of expired sessions. + * added `SessionHandlerFactory` to create session handlers with a DSN + * added `IpUtils::anonymize()` to help with GDPR compliance. + +4.3.0 +----- + + * added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`, + `ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame` + * deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`. + * deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`. + * deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`. + * deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`. + * added `UrlHelper` that allows to get an absolute URL and a relative path for a given path + +4.2.0 +----- + + * the default value of the "$secure" and "$samesite" arguments of Cookie's constructor + will respectively change from "false" to "null" and from "null" to "lax" in Symfony + 5.0, you should define their values explicitly or use "Cookie::create()" instead. + * added `matchPort()` in RequestMatcher + +4.1.3 +----- + + * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL` + HTTP headers has been dropped for security reasons. + +4.1.0 +----- + + * Query string normalization uses `parse_str()` instead of custom parsing logic. + * Passing the file size to the constructor of the `UploadedFile` class is deprecated. + * The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead. + * added `RedisSessionHandler` to use Redis as a session storage + * The `get()` method of the `AcceptHeader` class now takes into account the + `*` and `*/*` default values (if they are present in the Accept HTTP header) + when looking for items. + * deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead. + * added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`, + `IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to + handle failed `UploadedFile`. + * added `MigratingSessionHandler` for migrating between two session handlers without losing sessions + * added `HeaderUtils`. + +4.0.0 +----- + + * the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` + methods have been removed + * the `Request::HEADER_CLIENT_IP` constant has been removed, use + `Request::HEADER_X_FORWARDED_FOR` instead + * the `Request::HEADER_CLIENT_HOST` constant has been removed, use + `Request::HEADER_X_FORWARDED_HOST` instead + * the `Request::HEADER_CLIENT_PROTO` constant has been removed, use + `Request::HEADER_X_FORWARDED_PROTO` instead + * the `Request::HEADER_CLIENT_PORT` constant has been removed, use + `Request::HEADER_X_FORWARDED_PORT` instead + * checking for cacheable HTTP methods using the `Request::isMethodSafe()` + method (by not passing `false` as its argument) is not supported anymore and + throws a `\BadMethodCallException` + * the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed + * setting session save handlers that do not implement `\SessionHandlerInterface` in + `NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a + `\TypeError` + +3.4.0 +----- + + * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new + `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper + * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes + * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` + * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead + * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead + +3.3.0 +----- + + * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument, + see https://symfony.com/doc/current/deployment/proxies.html for more info, + * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods, + * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown, + disabling `Range` and `Content-Length` handling, switching to chunked encoding instead + * added the `Cookie::fromString()` method that allows to create a cookie from a + raw header string + +3.1.0 +----- + + * Added support for creating `JsonResponse` with a string of JSON data + +3.0.0 +----- + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + +2.8.0 +----- + + * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and + will be removed in 3.0. + +2.6.0 +----- + + * PdoSessionHandler changes + - implemented different session locking strategies to prevent loss of data by concurrent access to the same session + - [BC BREAK] save session data in a binary column without base64_encode + - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session + - implemented lazy connections that are only opened when a session is used by either passing a dsn string + explicitly or falling back to session.save_path ini setting + - added a createTable method that initializes a correctly defined table depending on the database vendor + +2.5.0 +----- + + * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation + of the options used while encoding data to JSON format. + +2.4.0 +----- + + * added RequestStack + * added Request::getEncodings() + * added accessors methods to session handlers + +2.3.0 +----- + + * added support for ranges of IPs in trusted proxies + * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) + * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases + to verify that Exceptions are properly thrown when the PDO queries fail. + +2.2.0 +----- + + * fixed the Request::create() precedence (URI information always take precedence now) + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() + * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects + * added a IpUtils class to check if an IP belongs to a CIDR + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to + enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) + * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 + * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 + +2.1.0 +----- + + * added Request::getSchemeAndHttpHost() and Request::getUserInfo() + * added a fluent interface to the Response class + * added Request::isProxyTrusted() + * added JsonResponse + * added a getTargetUrl method to RedirectResponse + * added support for streamed responses + * made Response::prepare() method the place to enforce HTTP specification + * [BC BREAK] moved management of the locale from the Session class to the Request class + * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() + * made FileBinaryMimeTypeGuesser command configurable + * added Request::getUser() and Request::getPassword() + * added support for the PATCH method in Request + * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 + * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) + * made mimetype to extension conversion configurable + * [BC BREAK] Moved all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. + Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. + * SessionHandlers must implement `\SessionHandlerInterface` or extend from the + `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. + * Added internal storage driver proxy mechanism for forward compatibility with + PHP 5.4 `\SessionHandler` class. + * Added session handlers for custom Memcache, Memcached and Null session save handlers. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and + `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class + is a mediator for the session storage internals including the session handlers + which do the real work of participating in the internal PHP session workflow. + * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit + and functional testing without starting real PHP sessions. Removed + `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit + tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` + for functional tests. These do not interact with global session ini + configuration values, session functions or `$_SESSION` superglobal. This means + they can be configured directly allowing multiple instances to work without + conflicting in the same PHP process. + * [BC BREAK] Removed the `close()` method from the `Session` class, as this is + now redundant. + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead + which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared + flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. + * Session data is now managed by `SessionBagInterface` to better encapsulate + session data. + * Refactored session attribute and flash messages system to their own + `SessionBagInterface` implementations. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This + implementation is ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire + behavior of messages auto expiring after one page page load. Messages must + be retrieved by `get()` or `all()`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate + attributes storage behavior from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for + namespace session attributes. + * Flash API can stores messages in an array so there may be multiple messages + per flash type. The old `Session` class API remains without BC break as it + will allow single messages as before. + * Added basic session meta-data to the session to record session create time, + last updated time, and the lifetime of the session cookie that was provided + to the client. + * Request::getClientIp() method doesn't take a parameter anymore but bases + itself on the trustProxy parameter. + * Added isMethod() to Request object. + * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of + a `Request` now all return a raw value (vs a urldecoded value before). Any call + to one of these methods must be checked and wrapped in a `rawurldecode()` if + needed. diff --git a/vendor/symfony/http-foundation/ChainRequestMatcher.php b/vendor/symfony/http-foundation/ChainRequestMatcher.php new file mode 100644 index 0000000..29486fc --- /dev/null +++ b/vendor/symfony/http-foundation/ChainRequestMatcher.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ChainRequestMatcher verifies that all checks match against a Request instance. + * + * @author Fabien Potencier + */ +class ChainRequestMatcher implements RequestMatcherInterface +{ + /** + * @param iterable $matchers + */ + public function __construct(private iterable $matchers) + { + } + + public function matches(Request $request): bool + { + foreach ($this->matchers as $matcher) { + if (!$matcher->matches($request)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php new file mode 100644 index 0000000..46be14b --- /dev/null +++ b/vendor/symfony/http-foundation/Cookie.php @@ -0,0 +1,409 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + public const SAMESITE_NONE = 'none'; + public const SAMESITE_LAX = 'lax'; + public const SAMESITE_STRICT = 'strict'; + + protected string $name; + protected ?string $value; + protected ?string $domain; + protected int $expire; + protected string $path; + protected ?bool $secure; + protected bool $httpOnly; + + private bool $raw; + private ?string $sameSite = null; + private bool $partitioned = false; + private bool $secureDefault = false; + + private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f"; + private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; + private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; + + /** + * Creates cookie from raw header string. + */ + public static function fromString(string $cookie, bool $decode = false): static + { + $data = [ + 'expires' => 0, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => false, + 'raw' => !$decode, + 'samesite' => null, + 'partitioned' => false, + ]; + + $parts = HeaderUtils::split($cookie, ';='); + $part = array_shift($parts); + + $name = $decode ? urldecode($part[0]) : $part[0]; + $value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null; + + $data = HeaderUtils::combine($parts) + $data; + $data['expires'] = self::expiresTimestamp($data['expires']); + + if (isset($data['max-age']) && ($data['max-age'] > 0 || $data['expires'] > time())) { + $data['expires'] = time() + (int) $data['max-age']; + } + + return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite'], $data['partitioned']); + } + + /** + * @see self::__construct + * + * @param self::SAMESITE_*|''|null $sameSite + */ + public static function create(string $name, ?string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false): self + { + return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite, $partitioned); + } + + /** + * @param string $name The name of the cookie + * @param string|null $value The value of the cookie + * @param int|string|\DateTimeInterface $expire The time the cookie expires + * @param string|null $path The path on the server in which the cookie will be available on + * @param string|null $domain The domain that the cookie is available to + * @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * @param bool $raw Whether the cookie value should be sent with no url encoding + * @param self::SAMESITE_*|''|null $sameSite Whether the cookie will be available for cross-site requests + * + * @throws \InvalidArgumentException + */ + public function __construct(string $name, ?string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false) + { + // from PHP source code + if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (!$name) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = self::expiresTimestamp($expire); + $this->path = $path ?: '/'; + $this->secure = $secure; + $this->httpOnly = $httpOnly; + $this->raw = $raw; + $this->sameSite = $this->withSameSite($sameSite)->sameSite; + $this->partitioned = $partitioned; + } + + /** + * Creates a cookie copy with a new value. + */ + public function withValue(?string $value): static + { + $cookie = clone $this; + $cookie->value = $value; + + return $cookie; + } + + /** + * Creates a cookie copy with a new domain that the cookie is available to. + */ + public function withDomain(?string $domain): static + { + $cookie = clone $this; + $cookie->domain = $domain; + + return $cookie; + } + + /** + * Creates a cookie copy with a new time the cookie expires. + */ + public function withExpires(int|string|\DateTimeInterface $expire = 0): static + { + $cookie = clone $this; + $cookie->expire = self::expiresTimestamp($expire); + + return $cookie; + } + + /** + * Converts expires formats to a unix timestamp. + */ + private static function expiresTimestamp(int|string|\DateTimeInterface $expire = 0): int + { + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + return 0 < $expire ? (int) $expire : 0; + } + + /** + * Creates a cookie copy with a new path on the server in which the cookie will be available on. + */ + public function withPath(string $path): static + { + $cookie = clone $this; + $cookie->path = '' === $path ? '/' : $path; + + return $cookie; + } + + /** + * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client. + */ + public function withSecure(bool $secure = true): static + { + $cookie = clone $this; + $cookie->secure = $secure; + + return $cookie; + } + + /** + * Creates a cookie copy that be accessible only through the HTTP protocol. + */ + public function withHttpOnly(bool $httpOnly = true): static + { + $cookie = clone $this; + $cookie->httpOnly = $httpOnly; + + return $cookie; + } + + /** + * Creates a cookie copy that uses no url encoding. + */ + public function withRaw(bool $raw = true): static + { + if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name)); + } + + $cookie = clone $this; + $cookie->raw = $raw; + + return $cookie; + } + + /** + * Creates a cookie copy with SameSite attribute. + * + * @param self::SAMESITE_*|''|null $sameSite + */ + public function withSameSite(?string $sameSite): static + { + if ('' === $sameSite) { + $sameSite = null; + } elseif (null !== $sameSite) { + $sameSite = strtolower($sameSite); + } + + if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) { + throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); + } + + $cookie = clone $this; + $cookie->sameSite = $sameSite; + + return $cookie; + } + + /** + * Creates a cookie copy that is tied to the top-level site in cross-site context. + */ + public function withPartitioned(bool $partitioned = true): static + { + $cookie = clone $this; + $cookie->partitioned = $partitioned; + + return $cookie; + } + + /** + * Returns the cookie as a string. + */ + public function __toString(): string + { + if ($this->isRaw()) { + $str = $this->getName(); + } else { + $str = str_replace(self::RESERVED_CHARS_FROM, self::RESERVED_CHARS_TO, $this->getName()); + } + + $str .= '='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d M Y H:i:s T', time() - 31536001).'; Max-Age=0'; + } else { + $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d M Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); + } + } + + if ($this->getPath()) { + $str .= '; path='.$this->getPath(); + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if ($this->isSecure()) { + $str .= '; secure'; + } + + if ($this->isHttpOnly()) { + $str .= '; httponly'; + } + + if (null !== $this->getSameSite()) { + $str .= '; samesite='.$this->getSameSite(); + } + + if ($this->isPartitioned()) { + $str .= '; partitioned'; + } + + return $str; + } + + /** + * Gets the name of the cookie. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Gets the value of the cookie. + */ + public function getValue(): ?string + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + */ + public function getDomain(): ?string + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + */ + public function getExpiresTime(): int + { + return $this->expire; + } + + /** + * Gets the max-age attribute. + */ + public function getMaxAge(): int + { + $maxAge = $this->expire - time(); + + return max(0, $maxAge); + } + + /** + * Gets the path on the server in which the cookie will be available on. + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + */ + public function isSecure(): bool + { + return $this->secure ?? $this->secureDefault; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + */ + public function isHttpOnly(): bool + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + */ + public function isCleared(): bool + { + return 0 !== $this->expire && $this->expire < time(); + } + + /** + * Checks if the cookie value should be sent with no url encoding. + */ + public function isRaw(): bool + { + return $this->raw; + } + + /** + * Checks whether the cookie should be tied to the top-level site in cross-site context. + */ + public function isPartitioned(): bool + { + return $this->partitioned; + } + + /** + * @return self::SAMESITE_*|null + */ + public function getSameSite(): ?string + { + return $this->sameSite; + } + + /** + * @param bool $default The default value of the "secure" flag when it is set to null + */ + public function setSecureDefault(bool $default): void + { + $this->secureDefault = $default; + } +} diff --git a/vendor/symfony/http-foundation/Exception/BadRequestException.php b/vendor/symfony/http-foundation/Exception/BadRequestException.php new file mode 100644 index 0000000..505e1cf --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/BadRequestException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user sends a malformed request. + */ +class BadRequestException extends UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 0000000..77aa0e1 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/ExceptionInterface.php b/vendor/symfony/http-foundation/Exception/ExceptionInterface.php new file mode 100644 index 0000000..e77c94f --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/http-foundation/Exception/JsonException.php b/vendor/symfony/http-foundation/Exception/JsonException.php new file mode 100644 index 0000000..6d1e0ae --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/JsonException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Thrown by Request::toArray() when the content cannot be JSON-decoded. + * + * @author Tobias Nyholm + */ +final class JsonException extends UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/LogicException.php b/vendor/symfony/http-foundation/Exception/LogicException.php new file mode 100644 index 0000000..2d3021f --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Base LogicException for Http Foundation component. + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php new file mode 100644 index 0000000..478d0dc --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Interface for Request exceptions. + * + * Exceptions implementing this interface should trigger an HTTP 400 response in the application code. + */ +interface RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php b/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php new file mode 100644 index 0000000..80a21bf --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a session does not exist. This happens in the following cases: + * - the session is not enabled + * - attempt to read a session outside a request context (ie. cli script). + * + * @author Jérémy Derussé + */ +class SessionNotFoundException extends \LogicException implements RequestExceptionInterface +{ + public function __construct(string $message = 'There is currently no session available.', int $code = 0, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php new file mode 100644 index 0000000..4818ef2 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user has performed an operation that should be considered + * suspicious from a security perspective. + */ +class SuspiciousOperationException extends UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/UnexpectedValueException.php b/vendor/symfony/http-foundation/Exception/UnexpectedValueException.php new file mode 100644 index 0000000..c3e6c9d --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/UnexpectedValueException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +class UnexpectedValueException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 0000000..136d2a9 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php b/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php new file mode 100644 index 0000000..c49f53a --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class CannotWriteFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php b/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php new file mode 100644 index 0000000..ed83499 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile. + * + * @author Florent Mata + */ +class ExtensionFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 0000000..fad5133 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred in the component File. + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 0000000..31bdf68 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when a file was not found. + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php b/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php new file mode 100644 index 0000000..8741be0 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class FormSizeFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php b/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php new file mode 100644 index 0000000..c8fde61 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class IniSizeFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/NoFileException.php b/vendor/symfony/http-foundation/File/Exception/NoFileException.php new file mode 100644 index 0000000..4b48cc7 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/NoFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php b/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php new file mode 100644 index 0000000..bdead2d --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoTmpDirFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/PartialFileException.php b/vendor/symfony/http-foundation/File/Exception/PartialFileException.php new file mode 100644 index 0000000..4641efb --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/PartialFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile. + * + * @author Florent Mata + */ +class PartialFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..905bd59 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct(mixed $value, string $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value))); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 0000000..7074e76 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload. + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/File.php b/vendor/symfony/http-foundation/File/File.php new file mode 100644 index 0000000..34ca5a5 --- /dev/null +++ b/vendor/symfony/http-foundation/File/File.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param bool $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + */ + public function __construct(string $path, bool $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @see MimeTypes + * @see getMimeType() + */ + public function guessExtension(): ?string + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null; + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesserInterface instance, + * which uses finfo_file() then the "file" system binary, + * depending on which of those are available. + * + * @see MimeTypes + */ + public function getMimeType(): ?string + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->guessMimeType($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @throws FileException if the target file could not be created + */ + public function move(string $directory, ?string $name = null): self + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $renamed = rename($this->getPathname(), $target); + } finally { + restore_error_handler(); + } + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + public function getContent(): string + { + $content = file_get_contents($this->getPathname()); + + if (false === $content) { + throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname())); + } + + return $content; + } + + protected function getTargetFile(string $directory, ?string $name = null): self + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory.', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory.', $directory)); + } + + $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * Returns locale independent base name of the given path. + */ + protected function getName(string $name): string + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/vendor/symfony/http-foundation/File/Stream.php b/vendor/symfony/http-foundation/File/Stream.php new file mode 100644 index 0000000..2c156b2 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Stream.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +/** + * A PHP stream of unknown size. + * + * @author Nicolas Grekas + */ +class Stream extends File +{ + public function getSize(): int|false + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/File/UploadedFile.php b/vendor/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 0000000..74e929f --- /dev/null +++ b/vendor/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException; +use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException; +use Symfony\Component\HttpFoundation\File\Exception\PartialFileException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +class UploadedFile extends File +{ + private bool $test; + private string $originalName; + private string $mimeType; + private int $error; + private string $originalPath; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name of the uploaded file + * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream + * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK + * @param bool $test Whether the test mode is active + * Local files are used in test mode hence the code should not enforce HTTP uploads + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + */ + public function __construct(string $path, string $originalName, ?string $mimeType = null, ?int $error = null, bool $test = false) + { + $this->originalName = $this->getName($originalName); + $this->originalPath = strtr($originalName, '\\', '/'); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->error = $error ?: \UPLOAD_ERR_OK; + $this->test = $test; + + parent::__construct($path, \UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * This should not be considered as a safe value to use for a file name on your servers. + */ + public function getClientOriginalName(): string + { + return $this->originalName; + } + + /** + * Returns the original file extension. + * + * It is extracted from the original file name that was uploaded. + * This should not be considered as a safe value to use for a file name on your servers. + */ + public function getClientOriginalExtension(): string + { + return pathinfo($this->originalName, \PATHINFO_EXTENSION); + } + + /** + * Returns the original file full path. + * + * It is extracted from the request from which the file has been uploaded. + * This should not be considered as a safe value to use for a file name/path on your servers. + * + * If this file was uploaded with the "webkitdirectory" upload directive, this will contain + * the path of the file relative to the uploaded root directory. Otherwise this will be identical + * to getClientOriginalName(). + */ + public function getClientOriginalPath(): string + { + return $this->originalPath; + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @see getMimeType() + */ + public function getClientMimeType(): string + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension(): ?string + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + */ + public function getError(): int + { + return $this->error; + } + + /** + * Returns whether the file has been uploaded with HTTP and no error occurred. + */ + public function isValid(): bool + { + $isOk = \UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @throws FileException if, for any reason, the file could not have been moved + */ + public function move(string $directory, ?string $name = null): File + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $moved = move_uploaded_file($this->getPathname(), $target); + } finally { + restore_error_handler(); + } + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + switch ($this->error) { + case \UPLOAD_ERR_INI_SIZE: + throw new IniSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_FORM_SIZE: + throw new FormSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_PARTIAL: + throw new PartialFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_FILE: + throw new NoFileException($this->getErrorMessage()); + case \UPLOAD_ERR_CANT_WRITE: + throw new CannotWriteFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_TMP_DIR: + throw new NoTmpDirFileException($this->getErrorMessage()); + case \UPLOAD_ERR_EXTENSION: + throw new ExtensionFileException($this->getErrorMessage()); + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX) + */ + public static function getMaxFilesize(): int|float + { + $sizePostMax = self::parseFilesize(\ini_get('post_max_size')); + $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize')); + + return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); + } + + private static function parseFilesize(string $size): int|float + { + if ('' === $size) { + return 0; + } + + $size = strtolower($size); + + $max = ltrim($size, '+'); + if (str_starts_with($max, '0x')) { + $max = \intval($max, 16); + } elseif (str_starts_with($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($size, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns an informative upload error message. + */ + public function getErrorMessage(): string + { + static $errors = [ + \UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', + \UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + \UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + \UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + \UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + \UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + \UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', + ]; + + $errorCode = $this->error; + $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; + $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/vendor/symfony/http-foundation/FileBag.php b/vendor/symfony/http-foundation/FileBag.php new file mode 100644 index 0000000..561e7cd --- /dev/null +++ b/vendor/symfony/http-foundation/FileBag.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private const FILE_KEYS = ['error', 'full_path', 'name', 'size', 'tmp_name', 'type']; + + /** + * @param array|UploadedFile[] $parameters An array of HTTP files + */ + public function __construct(array $parameters = []) + { + $this->replace($parameters); + } + + public function replace(array $files = []): void + { + $this->parameters = []; + $this->add($files); + } + + public function set(string $key, mixed $value): void + { + if (!\is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + public function add(array $files = []): void + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @return UploadedFile[]|UploadedFile|null + */ + protected function convertFileInformation(array|UploadedFile $file): array|UploadedFile|null + { + if ($file instanceof UploadedFile) { + return $file; + } + + $file = $this->fixPhpFilesArray($file); + $keys = array_keys($file + ['full_path' => null]); + sort($keys); + + if (self::FILE_KEYS === $keys) { + if (\UPLOAD_ERR_NO_FILE === $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['full_path'] ?? $file['name'], $file['type'], $file['error'], false); + } + } else { + $file = array_map(fn ($v) => $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v, $file); + if (array_is_list($file)) { + $file = array_filter($file); + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + */ + protected function fixPhpFilesArray(array $data): array + { + $keys = array_keys($data + ['full_path' => null]); + sort($keys); + + if (self::FILE_KEYS !== $keys || !isset($data['name']) || !\is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::FILE_KEYS as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray([ + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + ] + (isset($data['full_path'][$key]) ? [ + 'full_path' => $data['full_path'][$key], + ] : [])); + } + + return $files; + } +} diff --git a/vendor/symfony/http-foundation/HeaderBag.php b/vendor/symfony/http-foundation/HeaderBag.php new file mode 100644 index 0000000..4bab376 --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate> + */ +class HeaderBag implements \IteratorAggregate, \Countable, \Stringable +{ + protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; + + /** + * @var array> + */ + protected array $headers = []; + protected array $cacheControl = []; + + public function __construct(array $headers = []) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + */ + public function __toString(): string + { + if (!$headers = $this->all()) { + return ''; + } + + ksort($headers); + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + foreach ($headers as $name => $values) { + $name = ucwords($name, '-'); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @param string|null $key The name of the headers to return or null to get them all + * + * @return ($key is null ? array> : list) + */ + public function all(?string $key = null): array + { + if (null !== $key) { + return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; + } + + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return string[] + */ + public function keys(): array + { + return array_keys($this->all()); + } + + /** + * Replaces the current HTTP headers by a new set. + */ + public function replace(array $headers = []): void + { + $this->headers = []; + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + */ + public function add(array $headers): void + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the first header by name or the default one. + */ + public function get(string $key, ?string $default = null): ?string + { + $headers = $this->all($key); + + if (!$headers) { + return $default; + } + + if (null === $headers[0]) { + return null; + } + + return (string) $headers[0]; + } + + /** + * Sets a header by name. + * + * @param string|string[]|null $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set(string $key, string|array|null $values, bool $replace = true): void + { + $key = strtr($key, self::UPPER, self::LOWER); + + if (\is_array($values)) { + $values = array_values($values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + } else { + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = [$values]; + } else { + $this->headers[$key][] = $values; + } + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key])); + } + } + + /** + * Returns true if the HTTP header is defined. + */ + public function has(string $key): bool + { + return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all()); + } + + /** + * Returns true if the given HTTP header contains the given value. + */ + public function contains(string $key, string $value): bool + { + return \in_array($value, $this->all($key), true); + } + + /** + * Removes a header. + */ + public function remove(string $key): void + { + $key = strtr($key, self::UPPER, self::LOWER); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = []; + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate(string $key, ?\DateTimeInterface $default = null): ?\DateTimeImmutable + { + if (null === $value = $this->get($key)) { + return null !== $default ? \DateTimeImmutable::createFromInterface($default) : null; + } + + if (false === $date = \DateTimeImmutable::createFromFormat(\DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + */ + public function addCacheControlDirective(string $key, bool|string $value = true): void + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + */ + public function hasCacheControlDirective(string $key): bool + { + return \array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + */ + public function getCacheControlDirective(string $key): bool|string|null + { + return $this->cacheControl[$key] ?? null; + } + + /** + * Removes a Cache-Control directive. + */ + public function removeCacheControlDirective(string $key): void + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator> + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + */ + public function count(): int + { + return \count($this->headers); + } + + protected function getCacheControlHeader(): string + { + ksort($this->cacheControl); + + return HeaderUtils::toString($this->cacheControl, ','); + } + + /** + * Parses a Cache-Control HTTP header. + */ + protected function parseCacheControl(string $header): array + { + $parts = HeaderUtils::split($header, ',='); + + return HeaderUtils::combine($parts); + } +} diff --git a/vendor/symfony/http-foundation/HeaderUtils.php b/vendor/symfony/http-foundation/HeaderUtils.php new file mode 100644 index 0000000..110896e --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderUtils.php @@ -0,0 +1,298 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HTTP header utility functions. + * + * @author Christian Schmidt + */ +class HeaderUtils +{ + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Splits an HTTP header by one or more separators. + * + * Example: + * + * HeaderUtils::split('da, en-gb;q=0.8', ',;') + * // => ['da'], ['en-gb', 'q=0.8']] + * + * @param string $separators List of characters to split on, ordered by + * precedence, e.g. ',', ';=', or ',;=' + * + * @return array Nested array with as many levels as there are characters in + * $separators + */ + public static function split(string $header, string $separators): array + { + if ('' === $separators) { + throw new \InvalidArgumentException('At least one separator must be specified.'); + } + + $quotedSeparators = preg_quote($separators, '/'); + + preg_match_all(' + / + (?!\s) + (?: + # quoted-string + "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$) + | + # token + [^"'.$quotedSeparators.']+ + )+ + (?['.$quotedSeparators.']) + \s* + /x', trim($header), $matches, \PREG_SET_ORDER); + + return self::groupParts($matches, $separators); + } + + /** + * Combines an array of arrays into one associative array. + * + * Each of the nested arrays should have one or two elements. The first + * value will be used as the keys in the associative array, and the second + * will be used as the values, or true if the nested array only contains one + * element. Array keys are lowercased. + * + * Example: + * + * HeaderUtils::combine([['foo', 'abc'], ['bar']]) + * // => ['foo' => 'abc', 'bar' => true] + */ + public static function combine(array $parts): array + { + $assoc = []; + foreach ($parts as $part) { + $name = strtolower($part[0]); + $value = $part[1] ?? true; + $assoc[$name] = $value; + } + + return $assoc; + } + + /** + * Joins an associative array into a string for use in an HTTP header. + * + * The key and value of each entry are joined with '=', and all entries + * are joined with the specified separator and an additional space (for + * readability). Values are quoted if necessary. + * + * Example: + * + * HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',') + * // => 'foo=abc, bar, baz="a b c"' + */ + public static function toString(array $assoc, string $separator): string + { + $parts = []; + foreach ($assoc as $name => $value) { + if (true === $value) { + $parts[] = $name; + } else { + $parts[] = $name.'='.self::quote($value); + } + } + + return implode($separator.' ', $parts); + } + + /** + * Encodes a string as a quoted string, if necessary. + * + * If a string contains characters not allowed by the "token" construct in + * the HTTP specification, it is backslash-escaped and enclosed in quotes + * to match the "quoted-string" construct. + */ + public static function quote(string $s): string + { + if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) { + return $s; + } + + return '"'.addcslashes($s, '"\\"').'"'; + } + + /** + * Decodes a quoted string. + * + * If passed an unquoted string that matches the "token" construct (as + * defined in the HTTP specification), it is passed through verbatim. + */ + public static function unquote(string $s): string + { + return preg_replace('/\\\\(.)|"/', '$1', $s); + } + + /** + * Generates an HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' === $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (str_contains($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $params = ['filename' => $filenameFallback]; + if ($filename !== $filenameFallback) { + $params['filename*'] = "utf-8''".rawurlencode($filename); + } + + return $disposition.'; '.self::toString($params, ';'); + } + + /** + * Like parse_str(), but preserves dots in variable names. + */ + public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array + { + $q = []; + + foreach (explode($separator, $query) as $v) { + if (false !== $i = strpos($v, "\0")) { + $v = substr($v, 0, $i); + } + + if (false === $i = strpos($v, '=')) { + $k = urldecode($v); + $v = ''; + } else { + $k = urldecode(substr($v, 0, $i)); + $v = substr($v, $i); + } + + if (false !== $i = strpos($k, "\0")) { + $k = substr($k, 0, $i); + } + + $k = ltrim($k, ' '); + + if ($ignoreBrackets) { + $q[$k][] = urldecode(substr($v, 1)); + + continue; + } + + if (false === $i = strpos($k, '[')) { + $q[] = bin2hex($k).$v; + } else { + $q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v; + } + } + + if ($ignoreBrackets) { + return $q; + } + + parse_str(implode('&', $q), $q); + + $query = []; + + foreach ($q as $k => $v) { + if (false !== $i = strpos($k, '_')) { + $query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v; + } else { + $query[hex2bin($k)] = $v; + } + } + + return $query; + } + + private static function groupParts(array $matches, string $separators, bool $first = true): array + { + $separator = $separators[0]; + $separators = substr($separators, 1) ?: ''; + $i = 0; + + if ('' === $separators && !$first) { + $parts = ['']; + + foreach ($matches as $match) { + if (!$i && isset($match['separator'])) { + $i = 1; + $parts[1] = ''; + } else { + $parts[$i] .= self::unquote($match[0]); + } + } + + return $parts; + } + + $parts = []; + $partMatches = []; + + foreach ($matches as $match) { + if (($match['separator'] ?? null) === $separator) { + ++$i; + } else { + $partMatches[$i][] = $match; + } + } + + foreach ($partMatches as $matches) { + if ('' === $separators && '' !== $unquoted = self::unquote($matches[0][0])) { + $parts[] = $unquoted; + } elseif ($groupedParts = self::groupParts($matches, $separators, false)) { + $parts[] = $groupedParts; + } + } + + return $parts; + } +} diff --git a/vendor/symfony/http-foundation/InputBag.php b/vendor/symfony/http-foundation/InputBag.php new file mode 100644 index 0000000..dc5e127 --- /dev/null +++ b/vendor/symfony/http-foundation/InputBag.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use Symfony\Component\HttpFoundation\Exception\UnexpectedValueException; + +/** + * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE. + * + * @author Saif Eddin Gmati + */ +final class InputBag extends ParameterBag +{ + /** + * Returns a scalar input value by name. + * + * @param string|int|float|bool|null $default The default value if the input key does not exist + */ + public function get(string $key, mixed $default = null): string|int|float|bool|null + { + if (null !== $default && !\is_scalar($default) && !$default instanceof \Stringable) { + throw new \InvalidArgumentException(sprintf('Expected a scalar value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default))); + } + + $value = parent::get($key, $this); + + if (null !== $value && $this !== $value && !\is_scalar($value) && !$value instanceof \Stringable) { + throw new BadRequestException(sprintf('Input value "%s" contains a non-scalar value.', $key)); + } + + return $this === $value ? $default : $value; + } + + /** + * Replaces the current input values by a new set. + */ + public function replace(array $inputs = []): void + { + $this->parameters = []; + $this->add($inputs); + } + + /** + * Adds input values. + */ + public function add(array $inputs = []): void + { + foreach ($inputs as $input => $value) { + $this->set($input, $value); + } + } + + /** + * Sets an input by name. + * + * @param string|int|float|bool|array|null $value + */ + public function set(string $key, mixed $value): void + { + if (null !== $value && !\is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) { + throw new \InvalidArgumentException(sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value))); + } + + $this->parameters[$key] = $value; + } + + /** + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T + * + * @psalm-return ($default is null ? T|null : T) + */ + public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum + { + try { + return parent::getEnum($key, $class, $default); + } catch (UnexpectedValueException $e) { + throw new BadRequestException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the parameter value converted to string. + */ + public function getString(string $key, string $default = ''): string + { + // Shortcuts the parent method because the validation on scalar is already done in get(). + return (string) $this->get($key, $default); + } + + public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed + { + $value = $this->has($key) ? $this->all()[$key] : $default; + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) { + throw new BadRequestException(sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key)); + } + + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + $options['flags'] ??= 0; + $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE; + $options['flags'] |= \FILTER_NULL_ON_FAILURE; + + $value = filter_var($value, $filter, $options); + + if (null !== $value || $nullOnFailure) { + return $value; + } + + throw new BadRequestException(sprintf('Input value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key)); + } +} diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php new file mode 100644 index 0000000..ceab620 --- /dev/null +++ b/vendor/symfony/http-foundation/IpUtils.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + public const PRIVATE_SUBNETS = [ + '127.0.0.0/8', // RFC1700 (Loopback) + '10.0.0.0/8', // RFC1918 + '192.168.0.0/16', // RFC1918 + '172.16.0.0/12', // RFC1918 + '169.254.0.0/16', // RFC3927 + '0.0.0.0/8', // RFC5735 + '240.0.0.0/4', // RFC1112 + '::1/128', // Loopback + 'fc00::/7', // Unique Local Address + 'fe80::/10', // Link Local Address + '::ffff:0:0/96', // IPv4 translations + '::/128', // Unspecified address + ]; + + private static array $checkedIps = []; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. + * + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + */ + public static function checkIp(string $requestIp, string|array $ips): bool + { + if (!\is_array($ips)) { + $ips = [$ips]; + } + + $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @param string $ip IPv4 address or subnet in CIDR notation + * + * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet + */ + public static function checkIp4(string $requestIp, string $ip): bool + { + $cacheKey = $requestIp.'-'.$ip.'-v4'; + if (null !== $cacheValue = self::getCacheResult($cacheKey)) { + return $cacheValue; + } + + if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + return self::setCacheResult($cacheKey, false); + } + + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); + + if ('0' === $netmask) { + return self::setCacheResult($cacheKey, false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)); + } + + if ($netmask < 0 || $netmask > 32) { + return self::setCacheResult($cacheKey, false); + } + } else { + $address = $ip; + $netmask = 32; + } + + if (false === ip2long($address)) { + return self::setCacheResult($cacheKey, false); + } + + return self::setCacheResult($cacheKey, 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask)); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @author David Soria Parra + * + * @see https://github.com/dsp/v6tools + * + * @param string $ip IPv6 address or subnet in CIDR notation + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6(string $requestIp, string $ip): bool + { + $cacheKey = $requestIp.'-'.$ip.'-v6'; + if (null !== $cacheValue = self::getCacheResult($cacheKey)) { + return $cacheValue; + } + + if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + // Check to see if we were given a IP4 $requestIp or $ip by mistake + if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::setCacheResult($cacheKey, false); + } + + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); + + if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::setCacheResult($cacheKey, false); + } + + if ('0' === $netmask) { + return (bool) unpack('n*', @inet_pton($address)); + } + + if ($netmask < 1 || $netmask > 128) { + return self::setCacheResult($cacheKey, false); + } + } else { + if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::setCacheResult($cacheKey, false); + } + + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($requestIp)); + + if (!$bytesAddr || !$bytesTest) { + return self::setCacheResult($cacheKey, false); + } + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xFFFF >> $left) & 0xFFFF; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return self::setCacheResult($cacheKey, false); + } + } + + return self::setCacheResult($cacheKey, true); + } + + /** + * Anonymizes an IP/IPv6. + * + * Removes the last byte for v4 and the last 8 bytes for v6 IPs + */ + public static function anonymize(string $ip): string + { + $wrappedIPv6 = false; + if (str_starts_with($ip, '[') && str_ends_with($ip, ']')) { + $wrappedIPv6 = true; + $ip = substr($ip, 1, -1); + } + + $packedAddress = inet_pton($ip); + if (4 === \strlen($packedAddress)) { + $mask = '255.255.255.0'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) { + $mask = '::ffff:ffff:ff00'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) { + $mask = '::ffff:ff00'; + } else { + $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + } + $ip = inet_ntop($packedAddress & inet_pton($mask)); + + if ($wrappedIPv6) { + $ip = '['.$ip.']'; + } + + return $ip; + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of private IP subnets. + */ + public static function isPrivateIp(string $requestIp): bool + { + return self::checkIp($requestIp, self::PRIVATE_SUBNETS); + } + + private static function getCacheResult(string $cacheKey): ?bool + { + if (isset(self::$checkedIps[$cacheKey])) { + // Move the item last in cache (LRU) + $value = self::$checkedIps[$cacheKey]; + unset(self::$checkedIps[$cacheKey]); + self::$checkedIps[$cacheKey] = $value; + + return self::$checkedIps[$cacheKey]; + } + + return null; + } + + private static function setCacheResult(string $cacheKey, bool $result): bool + { + if (1000 < \count(self::$checkedIps)) { + // stop memory leak if there are many keys + self::$checkedIps = \array_slice(self::$checkedIps, 500, null, true); + } + + return self::$checkedIps[$cacheKey] = $result; + } +} diff --git a/vendor/symfony/http-foundation/JsonResponse.php b/vendor/symfony/http-foundation/JsonResponse.php new file mode 100644 index 0000000..4571d22 --- /dev/null +++ b/vendor/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected mixed $data; + protected ?string $callback = null; + + // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + public const DEFAULT_ENCODING_OPTIONS = 15; + + protected int $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; + + /** + * @param bool $json If the data is already a JSON string + */ + public function __construct(mixed $data = null, int $status = 200, array $headers = [], bool $json = false) + { + parent::__construct('', $status, $headers); + + if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) { + throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data))); + } + + $data ??= new \ArrayObject(); + + $json ? $this->setJson($data) : $this->setData($data); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::fromJsonString('{"key": "value"}') + * ->setSharedMaxAge(300); + * + * @param string $data The JSON response string + * @param int $status The response status code (200 "OK" by default) + * @param array $headers An array of response headers + */ + public static function fromJsonString(string $data, int $status = 200, array $headers = []): static + { + return new static($data, $status, $headers, true); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return $this + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback(?string $callback): static + { + if (null !== $callback) { + // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/ + // partially taken from https://github.com/willdurand/JsonpCallbackValidator + // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. + // (c) William Durand + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u'; + $reserved = [ + 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', + 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', + 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false', + ]; + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets a raw string containing a JSON document to be sent. + * + * @return $this + */ + public function setJson(string $json): static + { + $this->data = $json; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setData(mixed $data = []): static + { + try { + $data = json_encode($data, $this->encodingOptions); + } catch (\Exception $e) { + if ('Exception' === $e::class && str_starts_with($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + + if (\JSON_THROW_ON_ERROR & $this->encodingOptions) { + return $this->setJson($data); + } + + if (\JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $this->setJson($data); + } + + /** + * Returns options used while encoding data to JSON. + */ + public function getEncodingOptions(): int + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @return $this + */ + public function setEncodingOptions(int $encodingOptions): static + { + $this->encodingOptions = $encodingOptions; + + return $this->setData(json_decode($this->data)); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return $this + */ + protected function update(): static + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/vendor/symfony/http-foundation/LICENSE b/vendor/symfony/http-foundation/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/http-foundation/ParameterBag.php b/vendor/symfony/http-foundation/ParameterBag.php new file mode 100644 index 0000000..d60d3bd --- /dev/null +++ b/vendor/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use Symfony\Component\HttpFoundation\Exception\UnexpectedValueException; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + protected array $parameters; + + public function __construct(array $parameters = []) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @param string|null $key The name of the parameter to return or null to get them all + */ + public function all(?string $key = null): array + { + if (null === $key) { + return $this->parameters; + } + + if (!\is_array($value = $this->parameters[$key] ?? [])) { + throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value))); + } + + return $value; + } + + /** + * Returns the parameter keys. + */ + public function keys(): array + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + */ + public function replace(array $parameters = []): void + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + */ + public function add(array $parameters = []): void + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + public function get(string $key, mixed $default = null): mixed + { + return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + public function set(string $key, mixed $value): void + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + */ + public function has(string $key): bool + { + return \array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + */ + public function remove(string $key): void + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + */ + public function getAlpha(string $key, string $default = ''): string + { + return preg_replace('/[^[:alpha:]]/', '', $this->getString($key, $default)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + */ + public function getAlnum(string $key, string $default = ''): string + { + return preg_replace('/[^[:alnum:]]/', '', $this->getString($key, $default)); + } + + /** + * Returns the digits of the parameter value. + */ + public function getDigits(string $key, string $default = ''): string + { + return preg_replace('/[^[:digit:]]/', '', $this->getString($key, $default)); + } + + /** + * Returns the parameter as string. + */ + public function getString(string $key, string $default = ''): string + { + $value = $this->get($key, $default); + if (!\is_scalar($value) && !$value instanceof \Stringable) { + throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key)); + } + + return (string) $value; + } + + /** + * Returns the parameter value converted to integer. + */ + public function getInt(string $key, int $default = 0): int + { + return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR]); + } + + /** + * Returns the parameter value converted to boolean. + */ + public function getBoolean(string $key, bool $default = false): bool + { + return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR]); + } + + /** + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T + * + * @psalm-return ($default is null ? T|null : T) + */ + public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum + { + $value = $this->get($key); + + if (null === $value) { + return $default; + } + + try { + return $class::from($value); + } catch (\ValueError|\TypeError $e) { + throw new UnexpectedValueException(sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e); + } + } + + /** + * Filter key. + * + * @param int $filter FILTER_* constant + * @param int|array{flags?: int, options?: array} $options Flags from FILTER_* constants + * + * @see https://php.net/filter-var + */ + public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed + { + $value = $this->get($key, $default); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + // Add a convenience check for arrays. + if (\is_array($value) && !isset($options['flags'])) { + $options['flags'] = \FILTER_REQUIRE_ARRAY; + } + + if (\is_object($value) && !$value instanceof \Stringable) { + throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be filtered.', $key)); + } + + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + $options['flags'] ??= 0; + $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE; + $options['flags'] |= \FILTER_NULL_ON_FAILURE; + + $value = filter_var($value, $filter, $options); + + if (null !== $value || $nullOnFailure) { + return $value; + } + + throw new \UnexpectedValueException(sprintf('Parameter value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key)); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + */ + public function count(): int + { + return \count($this->parameters); + } +} diff --git a/vendor/symfony/http-foundation/README.md b/vendor/symfony/http-foundation/README.md new file mode 100644 index 0000000..5cf9007 --- /dev/null +++ b/vendor/symfony/http-foundation/README.md @@ -0,0 +1,14 @@ +HttpFoundation Component +======================== + +The HttpFoundation component defines an object-oriented layer for the HTTP +specification. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_foundation.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php b/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php new file mode 100644 index 0000000..550090f --- /dev/null +++ b/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Component\RateLimiter\Policy\NoLimiter; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * An implementation of PeekableRequestRateLimiterInterface that + * fits most use-cases. + * + * @author Wouter de Jong + */ +abstract class AbstractRequestRateLimiter implements PeekableRequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit + { + return $this->doConsume($request, 1); + } + + public function peek(Request $request): RateLimit + { + return $this->doConsume($request, 0); + } + + private function doConsume(Request $request, int $tokens): RateLimit + { + $limiters = $this->getLimiters($request); + if (0 === \count($limiters)) { + $limiters = [new NoLimiter()]; + } + + $minimalRateLimit = null; + foreach ($limiters as $limiter) { + $rateLimit = $limiter->consume($tokens); + + $minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit; + } + + return $minimalRateLimit; + } + + public function reset(Request $request): void + { + foreach ($this->getLimiters($request) as $limiter) { + $limiter->reset(); + } + } + + /** + * @return LimiterInterface[] a set of limiters using keys extracted from the request + */ + abstract protected function getLimiters(Request $request): array; + + private static function getMinimalRateLimit(RateLimit $first, RateLimit $second): RateLimit + { + if ($first->isAccepted() !== $second->isAccepted()) { + return $first->isAccepted() ? $second : $first; + } + + $firstRemainingTokens = $first->getRemainingTokens(); + $secondRemainingTokens = $second->getRemainingTokens(); + + if ($firstRemainingTokens === $secondRemainingTokens) { + return $first->getRetryAfter() < $second->getRetryAfter() ? $second : $first; + } + + return $firstRemainingTokens > $secondRemainingTokens ? $second : $first; + } +} diff --git a/vendor/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php b/vendor/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php new file mode 100644 index 0000000..63471af --- /dev/null +++ b/vendor/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * A request limiter which allows peeking ahead. + * + * This is valuable to reduce the cache backend load in scenarios + * like a login when we only want to consume a token on login failure, + * and where the majority of requests will be successful and thus not + * need to consume a token. + * + * This way we can peek ahead before allowing the request through, and + * only consume if the request failed (1 backend op). This is compared + * to always consuming and then resetting the limit if the request + * is successful (2 backend ops). + * + * @author Jordi Boggiano + */ +interface PeekableRequestRateLimiterInterface extends RequestRateLimiterInterface +{ + public function peek(Request $request): RateLimit; +} diff --git a/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php b/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php new file mode 100644 index 0000000..4c87a40 --- /dev/null +++ b/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * A special type of limiter that deals with requests. + * + * This allows to limit on different types of information + * from the requests. + * + * @author Wouter de Jong + */ +interface RequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit; + + public function reset(Request $request): void; +} diff --git a/vendor/symfony/http-foundation/RedirectResponse.php b/vendor/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 0000000..3c2e4b6 --- /dev/null +++ b/vendor/symfony/http-foundation/RedirectResponse.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + */ +class RedirectResponse extends Response +{ + protected string $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., + * but practically every browser redirects on paths only as well + * @param int $status The HTTP status code (302 "Found" by default) + * @param array $headers The headers (Location is always set to the given URL) + * + * @throws \InvalidArgumentException + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3 + */ + public function __construct(string $url, int $status = 302, array $headers = []) + { + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + + if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) { + $this->headers->remove('cache-control'); + } + } + + /** + * Returns the target URL. + */ + public function getTargetUrl(): string + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl(string $url): static + { + if ('' === $url) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + $this->headers->set('Content-Type', 'text/html; charset=utf-8'); + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php new file mode 100644 index 0000000..df33247 --- /dev/null +++ b/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,2092 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\JsonException; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeader::class); +class_exists(FileBag::class); +class_exists(HeaderBag::class); +class_exists(HeaderUtils::class); +class_exists(InputBag::class); +class_exists(ParameterBag::class); +class_exists(ServerBag::class); + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + public const HEADER_FORWARDED = 0b000001; // When using RFC 7239 + public const HEADER_X_FORWARDED_FOR = 0b000010; + public const HEADER_X_FORWARDED_HOST = 0b000100; + public const HEADER_X_FORWARDED_PROTO = 0b001000; + public const HEADER_X_FORWARDED_PORT = 0b010000; + public const HEADER_X_FORWARDED_PREFIX = 0b100000; + + public const HEADER_X_FORWARDED_AWS_ELB = 0b0011010; // AWS ELB doesn't send X-Forwarded-Host + public const HEADER_X_FORWARDED_TRAEFIK = 0b0111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy + + public const METHOD_HEAD = 'HEAD'; + public const METHOD_GET = 'GET'; + public const METHOD_POST = 'POST'; + public const METHOD_PUT = 'PUT'; + public const METHOD_PATCH = 'PATCH'; + public const METHOD_DELETE = 'DELETE'; + public const METHOD_PURGE = 'PURGE'; + public const METHOD_OPTIONS = 'OPTIONS'; + public const METHOD_TRACE = 'TRACE'; + public const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static array $trustedProxies = []; + + /** + * @var string[] + */ + protected static array $trustedHostPatterns = []; + + /** + * @var string[] + */ + protected static array $trustedHosts = []; + + protected static bool $httpMethodParameterOverride = false; + + /** + * Custom parameters. + */ + public ParameterBag $attributes; + + /** + * Request body parameters ($_POST). + * + * @see getPayload() for portability between content types + */ + public InputBag $request; + + /** + * Query string parameters ($_GET). + */ + public InputBag $query; + + /** + * Server and execution environment parameters ($_SERVER). + */ + public ServerBag $server; + + /** + * Uploaded files ($_FILES). + */ + public FileBag $files; + + /** + * Cookies ($_COOKIE). + */ + public InputBag $cookies; + + /** + * Headers (taken from the $_SERVER). + */ + public HeaderBag $headers; + + /** + * @var string|resource|false|null + */ + protected $content; + + /** + * @var string[]|null + */ + protected ?array $languages = null; + + /** + * @var string[]|null + */ + protected ?array $charsets = null; + + /** + * @var string[]|null + */ + protected ?array $encodings = null; + + /** + * @var string[]|null + */ + protected ?array $acceptableContentTypes = null; + + protected ?string $pathInfo = null; + protected ?string $requestUri = null; + protected ?string $baseUrl = null; + protected ?string $basePath = null; + protected ?string $method = null; + protected ?string $format = null; + protected SessionInterface|\Closure|null $session = null; + protected ?string $locale = null; + protected string $defaultLocale = 'en'; + + /** + * @var array|null + */ + protected static ?array $formats = null; + + protected static ?\Closure $requestFactory = null; + + private ?string $preferredFormat = null; + + private bool $isHostValid = true; + private bool $isForwardedValid = true; + private bool $isSafeContentPreferred; + + private array $trustedValuesCache = []; + + private static int $trustedHeaderSet = -1; + + private const FORWARDED_PARAMS = [ + self::HEADER_X_FORWARDED_FOR => 'for', + self::HEADER_X_FORWARDED_HOST => 'host', + self::HEADER_X_FORWARDED_PROTO => 'proto', + self::HEADER_X_FORWARDED_PORT => 'host', + ]; + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + */ + private const TRUSTED_HEADERS = [ + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX', + ]; + + /** @var bool */ + private $isIisRewrite = false; + + /** + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): void + { + $this->request = new InputBag($request); + $this->query = new InputBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new InputBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + */ + public static function createFromGlobals(): static + { + $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); + + if (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') + && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'], true) + ) { + parse_str($request->getContent(), $data); + $request->request = new InputBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string|resource|null $content The raw body data + */ + public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null): static + { + $server = array_replace([ + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + 'REQUEST_TIME_FLOAT' => microtime(true), + ], $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (false === $components) { + throw new \InvalidArgumentException(sprintf('Malformed URI "%s".', $uri)); + } + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = []; + break; + default: + $request = []; + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + */ + public static function setFactory(?callable $callable): void + { + self::$requestFactory = null === $callable ? null : $callable(...); + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array|null $query The GET parameters + * @param array|null $request The POST parameters + * @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array|null $cookies The COOKIE parameters + * @param array|null $files The FILES parameters + * @param array|null $server The SERVER parameters + */ + public function duplicate(?array $query = null, ?array $request = null, ?array $attributes = null, ?array $cookies = null, ?array $files = null, ?array $server = null): static + { + $dup = clone $this; + if (null !== $query) { + $dup->query = new InputBag($query); + } + if (null !== $request) { + $dup->request = new InputBag($request); + } + if (null !== $attributes) { + $dup->attributes = new ParameterBag($attributes); + } + if (null !== $cookies) { + $dup->cookies = new InputBag($cookies); + } + if (null !== $files) { + $dup->files = new FileBag($files); + } + if (null !== $server) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + public function __toString(): string + { + $content = $this->getContent(); + + $cookieHeader = ''; + $cookies = []; + + foreach ($this->cookies as $k => $v) { + $cookies[] = \is_array($v) ? http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "$k=$v"; + } + + if ($cookies) { + $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n"; + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers. + $cookieHeader."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals(): void + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; + + $requestOrder = \ini_get('request_order') ?: \ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = [[]]; + + foreach (str_split($requestOrder) as $order) { + $_REQUEST[] = $request[$order]; + } + + $_REQUEST = array_merge(...$_REQUEST); + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] + * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + */ + public static function setTrustedProxies(array $proxies, int $trustedHeaderSet): void + { + self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { + if ('REMOTE_ADDR' !== $proxy) { + $proxies[] = $proxy; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $proxies[] = $_SERVER['REMOTE_ADDR']; + } + + return $proxies; + }, []); + self::$trustedHeaderSet = $trustedHeaderSet; + } + + /** + * Gets the list of trusted proxies. + * + * @return string[] + */ + public static function getTrustedProxies(): array + { + return self::$trustedProxies; + } + + /** + * Gets the set of trusted headers from trusted proxies. + * + * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies + */ + public static function getTrustedHeaderSet(): int + { + return self::$trustedHeaderSet; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns): void + { + self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = []; + } + + /** + * Gets the list of trusted host patterns. + * + * @return string[] + */ + public static function getTrustedHosts(): array + { + return self::$trustedHostPatterns; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + */ + public static function normalizeQueryString(?string $qs): string + { + if ('' === ($qs ?? '')) { + return ''; + } + + $qs = HeaderUtils::parseQuery($qs); + ksort($qs); + + return http_build_query($qs, '', '&', \PHP_QUERY_RFC3986); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride(): void + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + */ + public static function getHttpMethodParameterOverride(): bool + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value from any bag. + * + * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the + * flexibility in controllers, it is better to explicitly get request parameters from the appropriate + * public property instead (attributes, query, request). + * + * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST + * + * @internal use explicit input sources instead + */ + public function get(string $key, mixed $default = null): mixed + { + if ($this !== $result = $this->attributes->get($key, $this)) { + return $result; + } + + if ($this->query->has($key)) { + return $this->query->all()[$key]; + } + + if ($this->request->has($key)) { + return $this->request->all()[$key]; + } + + return $default; + } + + /** + * Gets the Session. + * + * @throws SessionNotFoundException When session is not set properly + */ + public function getSession(): SessionInterface + { + $session = $this->session; + if (!$session instanceof SessionInterface && null !== $session) { + $this->setSession($session = $session()); + } + + if (null === $session) { + throw new SessionNotFoundException('Session has not been set.'); + } + + return $session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + */ + public function hasPreviousSession(): bool + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->getSession()->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory` + */ + public function hasSession(bool $skipIfUninitialized = false): bool + { + return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface); + } + + public function setSession(SessionInterface $session): void + { + $this->session = $session; + } + + /** + * @internal + * + * @param callable(): SessionInterface $factory + */ + public function setSessionFactory(callable $factory): void + { + $this->session = $factory(...); + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @see getClientIp() + */ + public function getClientIps(): array + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return [$ip]; + } + + return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip]; + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @see getClientIps() + * @see https://wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp(): ?string + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + */ + public function getScriptName(): string + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo(): string + { + return $this->pathInfo ??= $this->preparePathInfo(); + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath(): string + { + return $this->basePath ??= $this->prepareBasePath(); + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl(): string + { + $trustedPrefix = ''; + + // the proxy prefix must be prepended to any prefix being needed at the webserver level + if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) { + $trustedPrefix = rtrim($trustedPrefixValues[0], '/'); + } + + return $trustedPrefix.$this->getBaseUrlReal(); + } + + /** + * Returns the real base URL received by the webserver from which this request is executed. + * The URL does not include trusted reverse proxy prefix. + * + * @return string The raw URL (i.e. not urldecoded) + */ + private function getBaseUrlReal(): string + { + return $this->baseUrl ??= $this->prepareBaseUrl(); + } + + /** + * Gets the request's scheme. + */ + public function getScheme(): string + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * @return int|string|null Can be a string if fetched from the server bag + */ + public function getPort(): int|string|null + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ('[' === $host[0]) { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos && $port = substr($host, $pos + 1)) { + return (int) $port; + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + */ + public function getUser(): ?string + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + */ + public function getPassword(): ?string + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string|null A user name if any and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo(): ?string + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + */ + public function getHttpHost(): string + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' === $scheme && 80 == $port) || ('https' === $scheme && 443 == $port)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri(): string + { + return $this->requestUri ??= $this->prepareRequestUri(); + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + */ + public function getSchemeAndHttpHost(): string + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @see getQueryString() + */ + public function getUri(): string + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + */ + public function getUriForPath(string $path): string + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + */ + public function getRelativeUriForPath(string $path): string + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', substr($path, 1)); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see https://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + */ + public function getQueryString(): ?string + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + */ + public function isSecure(): bool + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) { + return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true); + } + + $https = $this->server->get('HTTPS'); + + return $https && 'off' !== strtolower($https); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * @throws SuspiciousOperationException when the host name is invalid or not trusted + */ + public function getHost(): string + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host)); + } + + if (\count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (\in_array($host, self::$trustedHosts, true)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host)); + } + + return $host; + } + + /** + * Sets the request method. + */ + public function setMethod(string $method): void + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @see getRealMethod() + */ + public function getMethod(): string + { + if (null !== $this->method) { + return $this->method; + } + + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' !== $this->method) { + return $this->method; + } + + $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE'); + + if (!$method && self::$httpMethodParameterOverride) { + $method = $this->request->get('_method', $this->query->get('_method', 'POST')); + } + + if (!\is_string($method)) { + return $this->method; + } + + $method = strtoupper($method); + + if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) { + return $this->method = $method; + } + + if (!preg_match('/^[A-Z]++$/D', $method)) { + throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method)); + } + + return $this->method = $method; + } + + /** + * Gets the "real" request method. + * + * @see getMethod() + */ + public function getRealMethod(): string + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + */ + public function getMimeType(string $format): ?string + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the mime types associated with the format. + * + * @return string[] + */ + public static function getMimeTypes(string $format): array + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return static::$formats[$format] ?? []; + } + + /** + * Gets the format associated with the mime type. + */ + public function getFormat(?string $mimeType): ?string + { + $canonicalMimeType = null; + if ($mimeType && false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = trim(substr($mimeType, 0, $pos)); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (\in_array($mimeType, (array) $mimeTypes, true)) { + return $format; + } + if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes, true)) { + return $format; + } + } + + return null; + } + + /** + * Associates a format with mime types. + * + * @param string|string[] $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat(?string $format, string|array $mimeTypes): void + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes]; + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request attribute + * * $default + * + * @see getPreferredFormat + */ + public function getRequestFormat(?string $default = 'html'): ?string + { + $this->format ??= $this->attributes->get('_format'); + + return $this->format ?? $default; + } + + /** + * Sets the request format. + */ + public function setRequestFormat(?string $format): void + { + $this->format = $format; + } + + /** + * Gets the usual name of the format associated with the request's media type (provided in the Content-Type header). + * + * @see Request::$formats + */ + public function getContentTypeFormat(): ?string + { + return $this->getFormat($this->headers->get('CONTENT_TYPE', '')); + } + + /** + * Sets the default locale. + */ + public function setDefaultLocale(string $locale): void + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + */ + public function getDefaultLocale(): string + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + */ + public function setLocale(string $locale): void + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + */ + public function getLocale(): string + { + return $this->locale ?? $this->defaultLocale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + */ + public function isMethod(string $method): bool + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether or not the method is safe. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + */ + public function isMethodSafe(): bool + { + return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); + } + + /** + * Checks whether or not the method is idempotent. + */ + public function isMethodIdempotent(): bool + { + return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + */ + public function isMethodCacheable(): bool + { + return \in_array($this->getMethod(), ['GET', 'HEAD']); + } + + /** + * Returns the protocol version. + * + * If the application is behind a proxy, the protocol version used in the + * requests between the client and the proxy and between the proxy and the + * server might be different. This returns the former (from the "Via" header) + * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns + * the latter (from the "SERVER_PROTOCOL" server parameter). + */ + public function getProtocolVersion(): ?string + { + if ($this->isFromTrustedProxy()) { + preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches); + + if ($matches) { + return 'HTTP/'.$matches[2]; + } + } + + return $this->server->get('SERVER_PROTOCOL'); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource + * + * @psalm-return ($asResource is true ? resource : string) + */ + public function getContent(bool $asResource = false) + { + $currentContentIsResource = \is_resource($this->content); + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (\is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'r'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the decoded form or json request body. + * + * @throws JsonException When the body cannot be decoded to an array + */ + public function getPayload(): InputBag + { + if ($this->request->count()) { + return clone $this->request; + } + + if ('' === $content = $this->getContent()) { + return new InputBag([]); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return new InputBag($content); + } + + /** + * Gets the request body decoded as array, typically from a JSON payload. + * + * @see getPayload() for portability between content types + * + * @throws JsonException When the body cannot be decoded to an array + */ + public function toArray(): array + { + if ('' === $content = $this->getContent()) { + throw new JsonException('Request body is empty.'); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return $content; + } + + /** + * Gets the Etags. + */ + public function getETags(): array + { + return preg_split('/\s*,\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY); + } + + public function isNoCache(): bool + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Gets the preferred format for the response by inspecting, in the following order: + * * the request format set using setRequestFormat; + * * the values of the Accept HTTP header. + * + * Note that if you use this method, you should send the "Vary: Accept" header + * in the response to prevent any issues with intermediary HTTP caches. + */ + public function getPreferredFormat(?string $default = 'html'): ?string + { + if (!isset($this->preferredFormat) && null !== $preferredFormat = $this->getRequestFormat(null)) { + $this->preferredFormat = $preferredFormat; + } + + if ($this->preferredFormat ?? null) { + return $this->preferredFormat; + } + + foreach ($this->getAcceptableContentTypes() as $mimeType) { + if ($this->preferredFormat = $this->getFormat($mimeType)) { + return $this->preferredFormat; + } + } + + return $default; + } + + /** + * Returns the preferred language. + * + * @param string[] $locales An array of ordered available locales + */ + public function getPreferredLanguage(?array $locales = null): ?string + { + $preferredLanguages = $this->getLanguages(); + + if (!$locales) { + return $preferredLanguages[0] ?? null; + } + + $locales = array_map($this->formatLocale(...), $locales ?? []); + if (!$preferredLanguages) { + return $locales[0]; + } + + if ($matches = array_intersect($preferredLanguages, $locales)) { + return current($matches); + } + + $combinations = array_merge(...array_map($this->getLanguageCombinations(...), $preferredLanguages)); + foreach ($combinations as $combination) { + foreach ($locales as $locale) { + if (str_starts_with($locale, $combination)) { + return $locale; + } + } + } + + return $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser ordered in the user browser preferences. + * + * @return string[] + */ + public function getLanguages(): array + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = []; + foreach ($languages as $acceptHeaderItem) { + $lang = $acceptHeaderItem->getValue(); + $this->languages[] = $this->formatLocale($lang); + } + $this->languages = array_unique($this->languages); + + return $this->languages; + } + + /** + * Strips the locale to only keep the canonicalized language value. + * + * Depending on the $locale value, this method can return values like : + * - language_Script_REGION: "fr_Latn_FR", "zh_Hans_TW" + * - language_Script: "fr_Latn", "zh_Hans" + * - language_REGION: "fr_FR", "zh_TW" + * - language: "fr", "zh" + * + * Invalid locale values are returned as is. + * + * @see https://wikipedia.org/wiki/IETF_language_tag + * @see https://datatracker.ietf.org/doc/html/rfc5646 + */ + private static function formatLocale(string $locale): string + { + [$language, $script, $region] = self::getLanguageComponents($locale); + + return implode('_', array_filter([$language, $script, $region])); + } + + /** + * Returns an array of all possible combinations of the language components. + * + * For instance, if the locale is "fr_Latn_FR", this method will return: + * - "fr_Latn_FR" + * - "fr_Latn" + * - "fr_FR" + * - "fr" + * + * @return string[] + */ + private static function getLanguageCombinations(string $locale): array + { + [$language, $script, $region] = self::getLanguageComponents($locale); + + return array_unique([ + implode('_', array_filter([$language, $script, $region])), + implode('_', array_filter([$language, $script])), + implode('_', array_filter([$language, $region])), + $language, + ]); + } + + /** + * Returns an array with the language components of the locale. + * + * For example: + * - If the locale is "fr_Latn_FR", this method will return "fr", "Latn", "FR" + * - If the locale is "fr_FR", this method will return "fr", null, "FR" + * - If the locale is "zh_Hans", this method will return "zh", "Hans", null + * + * @see https://wikipedia.org/wiki/IETF_language_tag + * @see https://datatracker.ietf.org/doc/html/rfc5646 + * + * @return array{string, string|null, string|null} + */ + private static function getLanguageComponents(string $locale): array + { + $locale = str_replace('_', '-', strtolower($locale)); + $pattern = '/^([a-zA-Z]{2,3}|i-[a-zA-Z]{5,})(?:-([a-zA-Z]{4}))?(?:-([a-zA-Z]{2}))?(?:-(.+))?$/'; + if (!preg_match($pattern, $locale, $matches)) { + return [$locale, null, null]; + } + if (str_starts_with($matches[1], 'i-')) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + $matches[1] = substr($matches[1], 2); + } + + return [ + $matches[1], + isset($matches[2]) ? ucfirst(strtolower($matches[2])) : null, + isset($matches[3]) ? strtoupper($matches[3]) : null, + ]; + } + + /** + * Gets a list of charsets acceptable by the client browser in preferable order. + * + * @return string[] + */ + public function getCharsets(): array + { + return $this->charsets ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all())); + } + + /** + * Gets a list of encodings acceptable by the client browser in preferable order. + * + * @return string[] + */ + public function getEncodings(): array + { + return $this->encodings ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all())); + } + + /** + * Gets a list of content types acceptable by the client browser in preferable order. + * + * @return string[] + */ + public function getAcceptableContentTypes(): array + { + return $this->acceptableContentTypes ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all())); + } + + /** + * Returns true if the request is an XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + */ + public function isXmlHttpRequest(): bool + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /** + * Checks whether the client browser prefers safe content or not according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function preferSafeContent(): bool + { + if (isset($this->isSafeContentPreferred)) { + return $this->isSafeContentPreferred; + } + + if (!$this->isSecure()) { + // see https://tools.ietf.org/html/rfc8674#section-3 + return $this->isSafeContentPreferred = false; + } + + return $this->isSafeContentPreferred = AcceptHeader::fromString($this->headers->get('Prefer'))->has('safe'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (https://framework.zend.com/license). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/) + */ + + protected function prepareRequestUri(): string + { + $requestUri = ''; + + if ($this->isIisRewrite() && '' != $this->server->get('UNENCODED_URL')) { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + + if ('' !== $requestUri && '/' === $requestUri[0]) { + // To only use path and query remove the fragment. + if (false !== $pos = strpos($requestUri, '#')) { + $requestUri = substr($requestUri, 0, $pos); + } + } else { + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, + // only use URL path. + $uriComponents = parse_url($requestUri); + + if (isset($uriComponents['path'])) { + $requestUri = $uriComponents['path']; + } + + if (isset($uriComponents['query'])) { + $requestUri .= '?'.$uriComponents['query']; + } + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + */ + protected function prepareBaseUrl(): string + { + $filename = basename($this->server->get('SCRIPT_FILENAME', '')); + + if (basename($this->server->get('SCRIPT_NAME', '')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF', '')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME', '')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = \count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl ?? ''); + if (!$basename || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) { + $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + */ + protected function prepareBasePath(): string + { + $baseUrl = $this->getBaseUrl(); + if (!$baseUrl) { + return ''; + } + + $filename = basename($this->server->get('SCRIPT_FILENAME')); + if (basename($baseUrl) === $filename) { + $basePath = \dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + */ + protected function preparePathInfo(): string + { + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if (false !== $pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if (null === ($baseUrl = $this->getBaseUrlReal())) { + return $requestUri; + } + + $pathInfo = substr($requestUri, \strlen($baseUrl)); + if (false === $pathInfo || '' === $pathInfo) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } + + return $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats(): void + { + static::$formats = [ + 'html' => ['text/html', 'application/xhtml+xml'], + 'txt' => ['text/plain'], + 'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'], + 'css' => ['text/css'], + 'json' => ['application/json', 'application/x-json'], + 'jsonld' => ['application/ld+json'], + 'xml' => ['text/xml', 'application/xml', 'application/x-xml'], + 'rdf' => ['application/rdf+xml'], + 'atom' => ['application/atom+xml'], + 'rss' => ['application/rss+xml'], + 'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'], + ]; + } + + private function setPhpDefaultLocale(string $locale): void + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists(\Locale::class, false)) { + \Locale::setDefault($locale); + } + } catch (\Exception) { + } + } + + /** + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, null otherwise. + */ + private function getUrlencodedPrefix(string $string, string $prefix): ?string + { + if ($this->isIisRewrite()) { + // ISS with UrlRewriteModule might report SCRIPT_NAME/PHP_SELF with wrong case + // see https://github.com/php/php-src/issues/11981 + if (0 !== stripos(rawurldecode($string), $prefix)) { + return null; + } + } elseif (!str_starts_with(rawurldecode($string), $prefix)) { + return null; + } + + $len = \strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return null; + } + + private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): static + { + if (self::$requestFactory) { + $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Indicates whether this request originated from a trusted proxy. + * + * This can be useful to determine whether or not to trust the + * contents of a proxy-specific header. + */ + public function isFromTrustedProxy(): bool + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies); + } + + /** + * This method is rather heavy because it splits and merges headers, and it's called by many other methods such as + * getPort(), isSecure(), getHost(), getClientIps(), getBaseUrl() etc. Thus, we try to cache the results for + * best performance. + */ + private function getTrustedValues(int $type, ?string $ip = null): array + { + $cacheKey = $type."\0".((self::$trustedHeaderSet & $type) ? $this->headers->get(self::TRUSTED_HEADERS[$type]) : ''); + $cacheKey .= "\0".$ip."\0".$this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); + + if (isset($this->trustedValuesCache[$cacheKey])) { + return $this->trustedValuesCache[$cacheKey]; + } + + $clientValues = []; + $forwardedValues = []; + + if ((self::$trustedHeaderSet & $type) && $this->headers->has(self::TRUSTED_HEADERS[$type])) { + foreach (explode(',', $this->headers->get(self::TRUSTED_HEADERS[$type])) as $v) { + $clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) { + $forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); + $parts = HeaderUtils::split($forwarded, ',;='); + $param = self::FORWARDED_PARAMS[$type]; + foreach ($parts as $subParts) { + if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) { + continue; + } + if (self::HEADER_X_FORWARDED_PORT === $type) { + if (str_ends_with($v, ']') || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $v = '0.0.0.0'.$v; + } + $forwardedValues[] = $v; + } + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $this->trustedValuesCache[$cacheKey] = $forwardedValues; + } + + if (!$forwardedValues) { + return $this->trustedValuesCache[$cacheKey] = $clientValues; + } + + if (!$this->isForwardedValid) { + return $this->trustedValuesCache[$cacheKey] = null !== $ip ? ['0.0.0.0', $ip] : []; + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, string $ip): array + { + if (!$clientIps) { + return []; + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + if (strpos($clientIp, '.')) { + // Strip :port from IPv4 addresses. This is allowed in Forwarded + // and may occur in X-Forwarded-For. + $i = strpos($clientIp, ':'); + if ($i) { + $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); + } + } elseif (str_starts_with($clientIp, '[')) { + // Strip brackets and :port from IPv6 addresses. + $i = strpos($clientIp, ']', 1); + $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); + } + + if (!filter_var($clientIp, \FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + $firstTrustedIp ??= $clientIp; + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp]; + } + + /** + * Is this IIS with UrlRewriteModule? + * + * This method consumes, caches and removed the IIS_WasUrlRewritten env var, + * so we don't inherit it to sub-requests. + */ + private function isIisRewrite(): bool + { + if (1 === $this->server->getInt('IIS_WasUrlRewritten')) { + $this->isIisRewrite = true; + $this->server->remove('IIS_WasUrlRewritten'); + } + + return $this->isIisRewrite; + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php new file mode 100644 index 0000000..09d6f49 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request attributes matches all regular expressions. + * + * @author Fabien Potencier + */ +class AttributesRequestMatcher implements RequestMatcherInterface +{ + /** + * @param array $regexps + */ + public function __construct(private array $regexps) + { + } + + public function matches(Request $request): bool + { + foreach ($this->regexps as $key => $regexp) { + $attribute = $request->attributes->get($key); + if (!\is_string($attribute)) { + return false; + } + if (!preg_match('{'.$regexp.'}', $attribute)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php new file mode 100644 index 0000000..935853f --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher implements RequestMatcherInterface +{ + public function __construct( + private ExpressionLanguage $language, + private Expression|string $expression, + ) { + } + + public function matches(Request $request): bool + { + return $this->language->evaluate($this->expression, [ + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + ]); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/HeaderRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/HeaderRequestMatcher.php new file mode 100644 index 0000000..8617a8a --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/HeaderRequestMatcher.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the presence of HTTP headers in a Request. + * + * @author Alexandre Daubois + */ +class HeaderRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $headers; + + /** + * @param string[]|string $headers A header or a list of headers + * Strings can contain a comma-delimited list of headers + */ + public function __construct(array|string $headers) + { + $this->headers = array_reduce((array) $headers, static fn (array $headers, string $header) => array_merge($headers, preg_split('/\s*,\s*/', $header)), []); + } + + public function matches(Request $request): bool + { + if (!$this->headers) { + return true; + } + + foreach ($this->headers as $header) { + if (!$request->headers->has($header)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php new file mode 100644 index 0000000..2836759 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request URL host name matches a regular expression. + * + * @author Fabien Potencier + */ +class HostRequestMatcher implements RequestMatcherInterface +{ + public function __construct(private string $regexp) + { + } + + public function matches(Request $request): bool + { + return preg_match('{'.$this->regexp.'}i', $request->getHost()); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php new file mode 100644 index 0000000..333612e --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the client IP of a Request. + * + * @author Fabien Potencier + */ +class IpsRequestMatcher implements RequestMatcherInterface +{ + private array $ips; + + /** + * @param string[]|string $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + * Strings can contain a comma-delimited list of IPs/ranges + */ + public function __construct(array|string $ips) + { + $this->ips = array_reduce((array) $ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); + } + + public function matches(Request $request): bool + { + if (!$this->ips) { + return true; + } + + return IpUtils::checkIp($request->getClientIp() ?? '', $this->ips); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php new file mode 100644 index 0000000..875f992 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request content is valid JSON. + * + * @author Fabien Potencier + */ +class IsJsonRequestMatcher implements RequestMatcherInterface +{ + public function matches(Request $request): bool + { + return json_validate($request->getContent()); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php new file mode 100644 index 0000000..b37f6e3 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the HTTP method of a Request. + * + * @author Fabien Potencier + */ +class MethodRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $methods = []; + + /** + * @param string[]|string $methods An HTTP method or an array of HTTP methods + * Strings can contain a comma-delimited list of methods + */ + public function __construct(array|string $methods) + { + $this->methods = array_reduce(array_map('strtoupper', (array) $methods), static fn (array $methods, string $method) => array_merge($methods, preg_split('/\s*,\s*/', $method)), []); + } + + public function matches(Request $request): bool + { + if (!$this->methods) { + return true; + } + + return \in_array($request->getMethod(), $this->methods, true); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php new file mode 100644 index 0000000..c7c7a02 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the Request URL path info matches a regular expression. + * + * @author Fabien Potencier + */ +class PathRequestMatcher implements RequestMatcherInterface +{ + public function __construct(private string $regexp) + { + } + + public function matches(Request $request): bool + { + return preg_match('{'.$this->regexp.'}', rawurldecode($request->getPathInfo())); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php new file mode 100644 index 0000000..5a01ce9 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the HTTP port of a Request. + * + * @author Fabien Potencier + */ +class PortRequestMatcher implements RequestMatcherInterface +{ + public function __construct(private int $port) + { + } + + public function matches(Request $request): bool + { + return $request->getPort() === $this->port; + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/QueryParameterRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/QueryParameterRequestMatcher.php new file mode 100644 index 0000000..86161e7 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/QueryParameterRequestMatcher.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the presence of HTTP query parameters of a Request. + * + * @author Alexandre Daubois + */ +class QueryParameterRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $parameters; + + /** + * @param string[]|string $parameters A parameter or a list of parameters + * Strings can contain a comma-delimited list of query parameters + */ + public function __construct(array|string $parameters) + { + $this->parameters = array_reduce(array_map(strtolower(...), (array) $parameters), static fn (array $parameters, string $parameter) => array_merge($parameters, preg_split('/\s*,\s*/', $parameter)), []); + } + + public function matches(Request $request): bool + { + if (!$this->parameters) { + return true; + } + + return 0 === \count(array_diff_assoc($this->parameters, $request->query->keys())); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php new file mode 100644 index 0000000..9c9cd58 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the HTTP scheme of a Request. + * + * @author Fabien Potencier + */ +class SchemeRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $schemes; + + /** + * @param string[]|string $schemes A scheme or a list of schemes + * Strings can contain a comma-delimited list of schemes + */ + public function __construct(array|string $schemes) + { + $this->schemes = array_reduce(array_map('strtolower', (array) $schemes), static fn (array $schemes, string $scheme) => array_merge($schemes, preg_split('/\s*,\s*/', $scheme)), []); + } + + public function matches(Request $request): bool + { + if (!$this->schemes) { + return true; + } + + return \in_array($request->getScheme(), $this->schemes, true); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 0000000..6dcc3e0 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + */ + public function matches(Request $request): bool; +} diff --git a/vendor/symfony/http-foundation/RequestStack.php b/vendor/symfony/http-foundation/RequestStack.php new file mode 100644 index 0000000..ac8263e --- /dev/null +++ b/vendor/symfony/http-foundation/RequestStack.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request stack that controls the lifecycle of requests. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private array $requests = []; + + /** + * Pushes a Request on the stack. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function push(Request $request): void + { + $this->requests[] = $request; + } + + /** + * Pops the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function pop(): ?Request + { + if (!$this->requests) { + return null; + } + + return array_pop($this->requests); + } + + public function getCurrentRequest(): ?Request + { + return end($this->requests) ?: null; + } + + /** + * Gets the main request. + * + * Be warned that making your code aware of the main request + * might make it un-compatible with other features of your framework + * like ESI support. + */ + public function getMainRequest(): ?Request + { + if (!$this->requests) { + return null; + } + + return $this->requests[0]; + } + + /** + * Returns the parent request of the current. + * + * Be warned that making your code aware of the parent request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * If current Request is the main request, it returns null. + */ + public function getParentRequest(): ?Request + { + $pos = \count($this->requests) - 2; + + return $this->requests[$pos] ?? null; + } + + /** + * Gets the current session. + * + * @throws SessionNotFoundException + */ + public function getSession(): SessionInterface + { + if ((null !== $request = end($this->requests) ?: null) && $request->hasSession()) { + return $request->getSession(); + } + + throw new SessionNotFoundException(); + } +} diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php new file mode 100644 index 0000000..22c09a0 --- /dev/null +++ b/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1315 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +// Help opcache.preload discover always-needed symbols +class_exists(ResponseHeaderBag::class); + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + public const HTTP_CONTINUE = 100; + public const HTTP_SWITCHING_PROTOCOLS = 101; + public const HTTP_PROCESSING = 102; // RFC2518 + public const HTTP_EARLY_HINTS = 103; // RFC8297 + public const HTTP_OK = 200; + public const HTTP_CREATED = 201; + public const HTTP_ACCEPTED = 202; + public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + public const HTTP_NO_CONTENT = 204; + public const HTTP_RESET_CONTENT = 205; + public const HTTP_PARTIAL_CONTENT = 206; + public const HTTP_MULTI_STATUS = 207; // RFC4918 + public const HTTP_ALREADY_REPORTED = 208; // RFC5842 + public const HTTP_IM_USED = 226; // RFC3229 + public const HTTP_MULTIPLE_CHOICES = 300; + public const HTTP_MOVED_PERMANENTLY = 301; + public const HTTP_FOUND = 302; + public const HTTP_SEE_OTHER = 303; + public const HTTP_NOT_MODIFIED = 304; + public const HTTP_USE_PROXY = 305; + public const HTTP_RESERVED = 306; + public const HTTP_TEMPORARY_REDIRECT = 307; + public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + public const HTTP_BAD_REQUEST = 400; + public const HTTP_UNAUTHORIZED = 401; + public const HTTP_PAYMENT_REQUIRED = 402; + public const HTTP_FORBIDDEN = 403; + public const HTTP_NOT_FOUND = 404; + public const HTTP_METHOD_NOT_ALLOWED = 405; + public const HTTP_NOT_ACCEPTABLE = 406; + public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + public const HTTP_REQUEST_TIMEOUT = 408; + public const HTTP_CONFLICT = 409; + public const HTTP_GONE = 410; + public const HTTP_LENGTH_REQUIRED = 411; + public const HTTP_PRECONDITION_FAILED = 412; + public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + public const HTTP_REQUEST_URI_TOO_LONG = 414; + public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + public const HTTP_EXPECTATION_FAILED = 417; + public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + public const HTTP_LOCKED = 423; // RFC4918 + public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 + public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725 + public const HTTP_INTERNAL_SERVER_ERROR = 500; + public const HTTP_NOT_IMPLEMENTED = 501; + public const HTTP_BAD_GATEWAY = 502; + public const HTTP_SERVICE_UNAVAILABLE = 503; + public const HTTP_GATEWAY_TIMEOUT = 504; + public const HTTP_VERSION_NOT_SUPPORTED = 505; + public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + public const HTTP_LOOP_DETECTED = 508; // RFC5842 + public const HTTP_NOT_EXTENDED = 510; // RFC2774 + public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + */ + private const HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = [ + 'must_revalidate' => false, + 'no_cache' => false, + 'no_store' => false, + 'no_transform' => false, + 'public' => false, + 'private' => false, + 'proxy_revalidate' => false, + 'max_age' => true, + 's_maxage' => true, + 'stale_if_error' => true, // RFC5861 + 'stale_while_revalidate' => true, // RFC5861 + 'immutable' => false, + 'last_modified' => true, + 'etag' => true, + ]; + + public ResponseHeaderBag $headers; + + protected string $content; + protected string $version; + protected int $statusCode; + protected string $statusText; + protected ?string $charset = null; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2021-10-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + */ + public static array $statusTexts = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Content Too Large', // RFC-ietf-httpbis-semantics + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ]; + + /** + * Tracks headers already sent in informational responses. + */ + private array $sentHeaders; + + /** + * @param int $status The HTTP status code (200 "OK" by default) + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct(?string $content = '', int $status = 200, array $headers = []) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @see prepare() + */ + public function __toString(): string + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @return $this + */ + public function prepare(Request $request): static + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + // prevent PHP from sending the Content-Type header based on default_mimetype + ini_set('default_mimetype', ''); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(null); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type') ?? '', 'text/') && false === stripos($headers->get('Content-Type') ?? '', 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && str_contains($headers->get('Cache-Control', ''), 'no-cache')) { + $headers->set('pragma', 'no-cache'); + $headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + if ($request->isSecure()) { + foreach ($headers->getCookies() as $cookie) { + $cookie->setSecureDefault(true); + } + } + + return $this; + } + + /** + * Sends HTTP headers. + * + * @param positive-int|null $statusCode The status code to use, override the statusCode property if set and not null + * + * @return $this + */ + public function sendHeaders(?int $statusCode = null): static + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + $informationalResponse = $statusCode >= 100 && $statusCode < 200; + if ($informationalResponse && !\function_exists('headers_send')) { + // skip informational responses if not supported by the SAPI + return $this; + } + + // headers + foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + // As recommended by RFC 8297, PHP automatically copies headers from previous 103 responses, we need to deal with that if headers changed + $previousValues = $this->sentHeaders[$name] ?? null; + if ($previousValues === $values) { + // Header already sent in a previous response, it will be automatically copied in this response by PHP + continue; + } + + $replace = 0 === strcasecmp($name, 'Content-Type'); + + if (null !== $previousValues && array_diff($previousValues, $values)) { + header_remove($name); + $previousValues = null; + } + + $newValues = null === $previousValues ? $values : array_diff($values, $previousValues); + + foreach ($newValues as $value) { + header($name.': '.$value, $replace, $this->statusCode); + } + + if ($informationalResponse) { + $this->sentHeaders[$name] = $values; + } + } + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + header('Set-Cookie: '.$cookie, false, $this->statusCode); + } + + if ($informationalResponse) { + headers_send($statusCode); + + return $this; + } + + $statusCode ??= $this->statusCode; + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode); + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent(): static + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @param bool $flush Whether output buffers should be flushed + * + * @return $this + */ + public function send(bool $flush = true): static + { + $this->sendHeaders(); + $this->sendContent(); + + if (!$flush) { + return $this; + } + + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (\function_exists('litespeed_finish_request')) { + litespeed_finish_request(); + } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + static::closeOutputBuffers(0, true); + flush(); + } + + return $this; + } + + /** + * Sets the response content. + * + * @return $this + */ + public function setContent(?string $content): static + { + $this->content = $content ?? ''; + + return $this; + } + + /** + * Gets the current response content. + */ + public function getContent(): string|false + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @return $this + * + * @final + */ + public function setProtocolVersion(string $version): static + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @final + */ + public function getProtocolVersion(): string + { + return $this->version; + } + + /** + * Sets the response status code. + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @final + */ + public function setStatusCode(int $code, ?string $text = null): static + { + $this->statusCode = $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = self::$statusTexts[$code] ?? 'unknown status'; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @final + */ + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @return $this + * + * @final + */ + public function setCharset(string $charset): static + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @final + */ + public function getCharset(): ?string + { + return $this->charset; + } + + /** + * Returns true if the response may safely be kept in a shared (surrogate) cache. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable because there is + * no way to tell when or how to remove them from the cache. + * + * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, + * for example "status codes that are defined as cacheable by default [...] + * can be reused by a cache with heuristic expiration unless otherwise indicated" + * (https://tools.ietf.org/html/rfc7231#section-6.1) + * + * @final + */ + public function isCacheable(): bool + { + if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @final + */ + public function isFresh(): bool + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @final + */ + public function isValidateable(): bool + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPrivate(): static + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPublic(): static + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "immutable". + * + * @return $this + * + * @final + */ + public function setImmutable(bool $immutable = true): static + { + if ($immutable) { + $this->headers->addCacheControlDirective('immutable'); + } else { + $this->headers->removeCacheControlDirective('immutable'); + } + + return $this; + } + + /** + * Returns true if the response is marked as "immutable". + * + * @final + */ + public function isImmutable(): bool + { + return $this->headers->hasCacheControlDirective('immutable'); + } + + /** + * Returns true if the response must be revalidated by shared caches once it has become stale. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @final + */ + public function mustRevalidate(): bool + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @throws \RuntimeException When the header is not parseable + * + * @final + */ + public function getDate(): ?\DateTimeImmutable + { + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @return $this + * + * @final + */ + public function setDate(\DateTimeInterface $date): static + { + $date = \DateTimeImmutable::createFromInterface($date); + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response in seconds. + * + * @final + */ + public function getAge(): int + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - (int) $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire(): static + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + $this->headers->remove('Expires'); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @final + */ + public function getExpires(): ?\DateTimeImmutable + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTimeImmutable::createFromFormat('U', time() - 172800); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setExpires(?\DateTimeInterface $date): static + { + if (null === $date) { + $this->headers->remove('Expires'); + + return $this; + } + + $date = \DateTimeImmutable::createFromInterface($date); + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @final + */ + public function getMaxAge(): ?int + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $expires = $this->getExpires()) { + $maxAge = (int) $expires->format('U') - (int) $this->getDate()->format('U'); + + return max($maxAge, 0); + } + + return null; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This method sets the Cache-Control max-age directive. + * + * @return $this + * + * @final + */ + public function setMaxAge(int $value): static + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be returned by shared caches when backend is down. + * + * This method sets the Cache-Control stale-if-error directive. + * + * @return $this + * + * @final + */ + public function setStaleIfError(int $value): static + { + $this->headers->addCacheControlDirective('stale-if-error', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer return stale content by shared caches. + * + * This method sets the Cache-Control stale-while-revalidate directive. + * + * @return $this + * + * @final + */ + public function setStaleWhileRevalidate(int $value): static + { + $this->headers->addCacheControlDirective('stale-while-revalidate', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This method sets the Cache-Control s-maxage directive. + * + * @return $this + * + * @final + */ + public function setSharedMaxAge(int $value): static + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the response's TTL is 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @final + */ + public function getTtl(): ?int + { + $maxAge = $this->getMaxAge(); + + return null !== $maxAge ? max($maxAge - $this->getAge(), 0) : null; + } + + /** + * Sets the response's time-to-live for shared caches in seconds. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @return $this + * + * @final + */ + public function setTtl(int $seconds): static + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches in seconds. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @return $this + * + * @final + */ + public function setClientTtl(int $seconds): static + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @final + */ + public function getLastModified(): ?\DateTimeImmutable + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setLastModified(?\DateTimeInterface $date): static + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + + return $this; + } + + $date = \DateTimeImmutable::createFromInterface($date); + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @final + */ + public function getEtag(): ?string + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + * + * @final + */ + public function setEtag(?string $etag, bool $weak = false): static + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (!str_starts_with($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag. + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @final + */ + public function setCache(array $options): static + { + if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + if (isset($options['stale_while_revalidate'])) { + $this->setStaleWhileRevalidate($options['stale_while_revalidate']); + } + + if (isset($options['stale_if_error'])) { + $this->setStaleIfError($options['stale_if_error']); + } + + foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) { + if (!$hasValue && isset($options[$directive])) { + if ($options[$directive]) { + $this->headers->addCacheControlDirective(str_replace('_', '-', $directive)); + } else { + $this->headers->removeCacheControlDirective(str_replace('_', '-', $directive)); + } + } + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @final + */ + public function setNotModified(): static + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @final + */ + public function hasVary(): bool + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @final + */ + public function getVary(): array + { + if (!$vary = $this->headers->all('Vary')) { + return []; + } + + $ret = []; + foreach ($vary as $item) { + $ret[] = preg_split('/[\s,]+/', $item); + } + + return array_merge([], ...$ret); + } + + /** + * Sets the Vary header. + * + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + * + * @final + */ + public function setVary(string|array $headers, bool $replace = true): static + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @final + */ + public function isNotModified(Request $request): bool + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if (($ifNoneMatchEtags = $request->getETags()) && (null !== $etag = $this->getEtag())) { + if (0 == strncmp($etag, 'W/', 2)) { + $etag = substr($etag, 2); + } + + // Use weak comparison as per https://tools.ietf.org/html/rfc7232#section-3.2. + foreach ($ifNoneMatchEtags as $ifNoneMatchEtag) { + if (0 == strncmp($ifNoneMatchEtag, 'W/', 2)) { + $ifNoneMatchEtag = substr($ifNoneMatchEtag, 2); + } + + if ($ifNoneMatchEtag === $etag || '*' === $ifNoneMatchEtag) { + $notModified = true; + break; + } + } + } + // Only do If-Modified-Since date comparison when If-None-Match is not present as per https://tools.ietf.org/html/rfc7232#section-3.3. + elseif ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @final + */ + public function isInvalid(): bool + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @final + */ + public function isInformational(): bool + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @final + */ + public function isSuccessful(): bool + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @final + */ + public function isRedirection(): bool + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @final + */ + public function isClientError(): bool + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @final + */ + public function isServerError(): bool + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @final + */ + public function isOk(): bool + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @final + */ + public function isForbidden(): bool + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @final + */ + public function isNotFound(): bool + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @final + */ + public function isRedirect(?string $location = null): bool + { + return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @final + */ + public function isEmpty(): bool + { + return \in_array($this->statusCode, [204, 304]); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @final + */ + public static function closeOutputBuffers(int $targetLevel, bool $flush): void + { + $status = ob_get_status(true); + $level = \count($status); + $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE); + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Marks a response as safe according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function setContentSafe(bool $safe = true): void + { + if ($safe) { + $this->headers->set('Preference-Applied', 'safe'); + } elseif ('safe' === $this->headers->get('Preference-Applied')) { + $this->headers->remove('Preference-Applied'); + } + + $this->setVary('Prefer', false); + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + * + * @final + */ + protected function ensureIEOverSSLCompatibility(Request $request): void + { + if (false !== stripos($this->headers->get('Content-Disposition') ?? '', 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT') ?? '', $match) && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/vendor/symfony/http-foundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 0000000..c8f0843 --- /dev/null +++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + public const COOKIES_FLAT = 'flat'; + public const COOKIES_ARRAY = 'array'; + + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + protected array $computedCacheControl = []; + protected array $cookies = []; + protected array $headerNames = []; + + public function __construct(array $headers = []) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * Returns the headers, with original capitalizations. + */ + public function allPreserveCase(): array + { + $headers = []; + foreach ($this->all() as $name => $value) { + $headers[$this->headerNames[$name] ?? $name] = $value; + } + + return $headers; + } + + public function allPreserveCaseWithoutCookies(): array + { + $headers = $this->allPreserveCase(); + if (isset($this->headerNames['set-cookie'])) { + unset($headers[$this->headerNames['set-cookie']]); + } + + return $headers; + } + + public function replace(array $headers = []): void + { + $this->headerNames = []; + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + public function all(?string $key = null): array + { + $headers = parent::all(); + + if (null !== $key) { + $key = strtr($key, self::UPPER, self::LOWER); + + return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies()); + } + + foreach ($this->getCookies() as $cookie) { + $headers['set-cookie'][] = (string) $cookie; + } + + return $headers; + } + + public function set(string $key, string|array|null $values, bool $replace = true): void + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + + if ('set-cookie' === $uniqueKey) { + if ($replace) { + $this->cookies = []; + } + foreach ((array) $values as $cookie) { + $this->setCookie(Cookie::fromString($cookie)); + } + $this->headerNames[$uniqueKey] = $key; + + return; + } + + $this->headerNames[$uniqueKey] = $key; + + parent::set($key, $values, $replace); + + // ensure the cache-control header has sensible defaults + if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true) && '' !== $computed = $this->computeCacheControlValue()) { + $this->headers['cache-control'] = [$computed]; + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + public function remove(string $key): void + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + unset($this->headerNames[$uniqueKey]); + + if ('set-cookie' === $uniqueKey) { + $this->cookies = []; + + return; + } + + parent::remove($key); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = []; + } + + if ('date' === $uniqueKey) { + $this->initDate(); + } + } + + public function hasCacheControlDirective(string $key): bool + { + return \array_key_exists($key, $this->computedCacheControl); + } + + public function getCacheControlDirective(string $key): bool|string|null + { + return $this->computedCacheControl[$key] ?? null; + } + + public function setCookie(Cookie $cookie): void + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + $this->headerNames['set-cookie'] = 'Set-Cookie'; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + */ + public function removeCookie(string $name, ?string $path = '/', ?string $domain = null): void + { + $path ??= '/'; + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + + if (!$this->cookies) { + unset($this->headerNames['set-cookie']); + } + } + + /** + * Returns an array with all cookies. + * + * @return Cookie[] + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies(string $format = self::COOKIES_FLAT): array + { + if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = []; + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + * + * @param bool $partitioned + */ + public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null /* , bool $partitioned = false */): void + { + $partitioned = 6 < \func_num_args() ? \func_get_arg(6) : false; + + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite, $partitioned)); + } + + /** + * @see HeaderUtils::makeDisposition() + */ + public function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback); + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + */ + protected function computeCacheControlValue(): string + { + if (!$this->cacheControl) { + if ($this->has('Last-Modified') || $this->has('Expires')) { + return 'private, must-revalidate'; // allows for heuristic expiration (RFC 7234 Section 4.2.2) in the case of "Last-Modified" + } + + // conservative by default + return 'no-cache, private'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } + + private function initDate(): void + { + $this->set('Date', gmdate('D, d M Y H:i:s').' GMT'); + } +} diff --git a/vendor/symfony/http-foundation/ServerBag.php b/vendor/symfony/http-foundation/ServerBag.php new file mode 100644 index 0000000..09fc386 --- /dev/null +++ b/vendor/symfony/http-foundation/ServerBag.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + */ + public function getHeaders(): array + { + $headers = []; + foreach ($this->parameters as $key => $value) { + if (str_starts_with($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true) && '' !== $value) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = $this->parameters['PHP_AUTH_PW'] ?? ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ index.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (2 == \count($exploded)) { + [$headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']] = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { + // In some circumstances PHP_AUTH_DIGEST needs to be set + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * https://php.net/reserved.variables.server + */ + $headers['AUTHORIZATION'] = $authorizationHeader; + } + } + } + + if (isset($headers['AUTHORIZATION'])) { + return $headers; + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.($headers['PHP_AUTH_PW'] ?? '')); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + + return $headers; + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 0000000..042f3bd --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage. + * + * @implements \IteratorAggregate + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + protected array $attributes = []; + + private string $name = 'attributes'; + private string $storageKey; + + /** + * @param string $storageKey The key used to store attributes in the session + */ + public function __construct(string $storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function initialize(array &$attributes): void + { + $this->attributes = &$attributes; + } + + public function getStorageKey(): string + { + return $this->storageKey; + } + + public function has(string $name): bool + { + return \array_key_exists($name, $this->attributes); + } + + public function get(string $name, mixed $default = null): mixed + { + return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + public function set(string $name, mixed $value): void + { + $this->attributes[$name] = $value; + } + + public function all(): array + { + return $this->attributes; + } + + public function replace(array $attributes): void + { + $this->attributes = []; + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + public function remove(string $name): mixed + { + $retval = null; + if (\array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + public function clear(): mixed + { + $return = $this->attributes; + $this->attributes = []; + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + */ + public function count(): int + { + return \count($this->attributes); + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 0000000..39ec9d7 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + */ + public function has(string $name): bool; + + /** + * Returns an attribute. + */ + public function get(string $name, mixed $default = null): mixed; + + /** + * Sets an attribute. + */ + public function set(string $name, mixed $value): void; + + /** + * Returns attributes. + * + * @return array + */ + public function all(): array; + + public function replace(array $attributes): void; + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name): mixed; +} diff --git a/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 0000000..2eba843 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private string $name = 'flashes'; + private array $flashes = ['display' => [], 'new' => []]; + private string $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function initialize(array &$flashes): void + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : []; + $this->flashes['new'] = []; + } + + public function add(string $type, mixed $message): void + { + $this->flashes['new'][$type][] = $message; + } + + public function peek(string $type, array $default = []): array + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + public function peekAll(): array + { + return \array_key_exists('display', $this->flashes) ? $this->flashes['display'] : []; + } + + public function get(string $type, array $default = []): array + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + public function all(): array + { + $return = $this->flashes['display']; + $this->flashes['display'] = []; + + return $return; + } + + public function setAll(array $messages): void + { + $this->flashes['new'] = $messages; + } + + public function set(string $type, string|array $messages): void + { + $this->flashes['new'][$type] = (array) $messages; + } + + public function has(string $type): bool + { + return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + public function keys(): array + { + return array_keys($this->flashes['display']); + } + + public function getStorageKey(): string + { + return $this->storageKey; + } + + public function clear(): mixed + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 0000000..044639b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + private string $name = 'flashes'; + private array $flashes = []; + private string $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function initialize(array &$flashes): void + { + $this->flashes = &$flashes; + } + + public function add(string $type, mixed $message): void + { + $this->flashes[$type][] = $message; + } + + public function peek(string $type, array $default = []): array + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + public function peekAll(): array + { + return $this->flashes; + } + + public function get(string $type, array $default = []): array + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + public function all(): array + { + $return = $this->peekAll(); + $this->flashes = []; + + return $return; + } + + public function set(string $type, string|array $messages): void + { + $this->flashes[$type] = (array) $messages; + } + + public function setAll(array $messages): void + { + $this->flashes = $messages; + } + + public function has(string $type): bool + { + return \array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + public function keys(): array + { + return array_keys($this->flashes); + } + + public function getStorageKey(): string + { + return $this->storageKey; + } + + public function clear(): mixed + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 0000000..79e98f5 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for the given type. + */ + public function add(string $type, mixed $message): void; + + /** + * Registers one or more messages for a given type. + */ + public function set(string $type, string|array $messages): void; + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type + * @param array $default Default value if $type does not exist + */ + public function peek(string $type, array $default = []): array; + + /** + * Gets all flash messages. + */ + public function peekAll(): array; + + /** + * Gets and clears flash from the stack. + * + * @param array $default Default value if $type does not exist + */ + public function get(string $type, array $default = []): array; + + /** + * Gets and clears flashes from the stack. + */ + public function all(): array; + + /** + * Sets all flash messages. + */ + public function setAll(array $messages): void; + + /** + * Has flash messages for a given type? + */ + public function has(string $type): bool; + + /** + * Returns a list of all defined types. + */ + public function keys(): array; +} diff --git a/vendor/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php b/vendor/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php new file mode 100644 index 0000000..90151d3 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; + +/** + * Interface for session with a flashbag. + */ +interface FlashBagAwareSessionInterface extends SessionInterface +{ + public function getFlashBag(): FlashBagInterface; +} diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php new file mode 100644 index 0000000..972021f --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,223 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(AttributeBag::class); +class_exists(FlashBag::class); +class_exists(SessionBagProxy::class); + +/** + * @author Fabien Potencier + * @author Drak + * + * @implements \IteratorAggregate + */ +class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Countable +{ + protected SessionStorageInterface $storage; + + private string $flashName; + private string $attributeName; + private array $data = []; + private int $usageIndex = 0; + private ?\Closure $usageReporter; + + public function __construct(?SessionStorageInterface $storage = null, ?AttributeBagInterface $attributes = null, ?FlashBagInterface $flashes = null, ?callable $usageReporter = null) + { + $this->storage = $storage ?? new NativeSessionStorage(); + $this->usageReporter = null === $usageReporter ? null : $usageReporter(...); + + $attributes ??= new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes ??= new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + public function start(): bool + { + return $this->storage->start(); + } + + public function has(string $name): bool + { + return $this->getAttributeBag()->has($name); + } + + public function get(string $name, mixed $default = null): mixed + { + return $this->getAttributeBag()->get($name, $default); + } + + public function set(string $name, mixed $value): void + { + $this->getAttributeBag()->set($name, $value); + } + + public function all(): array + { + return $this->getAttributeBag()->all(); + } + + public function replace(array $attributes): void + { + $this->getAttributeBag()->replace($attributes); + } + + public function remove(string $name): mixed + { + return $this->getAttributeBag()->remove($name); + } + + public function clear(): void + { + $this->getAttributeBag()->clear(); + } + + public function isStarted(): bool + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->getAttributeBag()->all()); + } + + /** + * Returns the number of attributes. + */ + public function count(): int + { + return \count($this->getAttributeBag()->all()); + } + + public function &getUsageIndex(): int + { + return $this->usageIndex; + } + + /** + * @internal + */ + public function isEmpty(): bool + { + if ($this->isStarted()) { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + } + foreach ($this->data as &$data) { + if ($data) { + return false; + } + } + + return true; + } + + public function invalidate(?int $lifetime = null): bool + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + public function migrate(bool $destroy = false, ?int $lifetime = null): bool + { + return $this->storage->regenerate($destroy, $lifetime); + } + + public function save(): void + { + $this->storage->save(); + } + + public function getId(): string + { + return $this->storage->getId(); + } + + public function setId(string $id): void + { + if ($this->storage->getId() !== $id) { + $this->storage->setId($id); + } + } + + public function getName(): string + { + return $this->storage->getName(); + } + + public function setName(string $name): void + { + $this->storage->setName($name); + } + + public function getMetadataBag(): MetadataBag + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return $this->storage->getMetadataBag(); + } + + public function registerBag(SessionBagInterface $bag): void + { + $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); + } + + public function getBag(string $name): SessionBagInterface + { + $bag = $this->storage->getBag($name); + + return method_exists($bag, 'getBag') ? $bag->getBag() : $bag; + } + + /** + * Gets the flashbag interface. + */ + public function getFlashBag(): FlashBagInterface + { + return $this->getBag($this->flashName); + } + + /** + * Gets the attributebag interface. + * + * Note that this method was added to help with IDE autocompletion. + */ + private function getAttributeBag(): AttributeBagInterface + { + return $this->getBag($this->attributeName); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 0000000..6a224cf --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name. + */ + public function getName(): string; + + /** + * Initializes the Bag. + */ + public function initialize(array &$array): void; + + /** + * Gets the storage key for this bag. + */ + public function getStorageKey(): string; + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained + */ + public function clear(): mixed; +} diff --git a/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/vendor/symfony/http-foundation/Session/SessionBagProxy.php new file mode 100644 index 0000000..e759d94 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionBagProxy.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class SessionBagProxy implements SessionBagInterface +{ + private SessionBagInterface $bag; + private array $data; + private ?int $usageIndex; + private ?\Closure $usageReporter; + + public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter) + { + $this->bag = $bag; + $this->data = &$data; + $this->usageIndex = &$usageIndex; + $this->usageReporter = null === $usageReporter ? null : $usageReporter(...); + } + + public function getBag(): SessionBagInterface + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return $this->bag; + } + + public function isEmpty(): bool + { + if (!isset($this->data[$this->bag->getStorageKey()])) { + return true; + } + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return empty($this->data[$this->bag->getStorageKey()]); + } + + public function getName(): string + { + return $this->bag->getName(); + } + + public function initialize(array &$array): void + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + $this->data[$this->bag->getStorageKey()] = &$array; + + $this->bag->initialize($array); + } + + public function getStorageKey(): string + { + return $this->bag->getStorageKey(); + } + + public function clear(): mixed + { + return $this->bag->clear(); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionFactory.php b/vendor/symfony/http-foundation/Session/SessionFactory.php new file mode 100644 index 0000000..c06ed4b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionFactory.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Session::class); + +/** + * @author Jérémy Derussé + */ +class SessionFactory implements SessionFactoryInterface +{ + private RequestStack $requestStack; + private SessionStorageFactoryInterface $storageFactory; + private ?\Closure $usageReporter; + + public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, ?callable $usageReporter = null) + { + $this->requestStack = $requestStack; + $this->storageFactory = $storageFactory; + $this->usageReporter = null === $usageReporter ? null : $usageReporter(...); + } + + public function createSession(): SessionInterface + { + return new Session($this->storageFactory->createStorage($this->requestStack->getMainRequest()), null, null, $this->usageReporter); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php b/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php new file mode 100644 index 0000000..b24fdc4 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Kevin Bond + */ +interface SessionFactoryInterface +{ + public function createSession(): SessionInterface; +} diff --git a/vendor/symfony/http-foundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 0000000..3e29ba4 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @throws \RuntimeException if session fails to start + */ + public function start(): bool; + + /** + * Returns the session ID. + */ + public function getId(): string; + + /** + * Sets the session ID. + */ + public function setId(string $id): void; + + /** + * Returns the session name. + */ + public function getName(): string; + + /** + * Sets the session name. + */ + public function setName(string $name): void; + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function invalidate(?int $lifetime = null): bool; + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function migrate(bool $destroy = false, ?int $lifetime = null): bool; + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(): void; + + /** + * Checks if an attribute is defined. + */ + public function has(string $name): bool; + + /** + * Returns an attribute. + */ + public function get(string $name, mixed $default = null): mixed; + + /** + * Sets an attribute. + */ + public function set(string $name, mixed $value): void; + + /** + * Returns attributes. + */ + public function all(): array; + + /** + * Sets attributes. + */ + public function replace(array $attributes): void; + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name): mixed; + + /** + * Clears all attributes. + */ + public function clear(): void; + + /** + * Checks if the session was started. + */ + public function isStarted(): bool; + + /** + * Registers a SessionBagInterface with the session. + */ + public function registerBag(SessionBagInterface $bag): void; + + /** + * Gets a bag instance by name. + */ + public function getBag(string $name): SessionBagInterface; + + /** + * Gets session meta. + */ + public function getMetadataBag(): MetadataBag; +} diff --git a/vendor/symfony/http-foundation/Session/SessionUtils.php b/vendor/symfony/http-foundation/Session/SessionUtils.php new file mode 100644 index 0000000..504c584 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionUtils.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session utility functions. + * + * @author Nicolas Grekas + * @author Rémon van de Kamp + * + * @internal + */ +final class SessionUtils +{ + /** + * Finds the session header amongst the headers that are to be sent, removes it, and returns + * it so the caller can process it further. + */ + public static function popSessionCookie(string $sessionName, #[\SensitiveParameter] string $sessionId): ?string + { + $sessionCookie = null; + $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName)); + $sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId)); + $otherCookies = []; + foreach (headers_list() as $h) { + if (0 !== stripos($h, 'Set-Cookie:')) { + continue; + } + if (11 === strpos($h, $sessionCookiePrefix, 11)) { + $sessionCookie = $h; + + if (11 !== strpos($h, $sessionCookieWithId, 11)) { + $otherCookies[] = $h; + } + } else { + $otherCookies[] = $h; + } + } + if (null === $sessionCookie) { + return null; + } + + header_remove('Set-Cookie'); + foreach ($otherCookies as $h) { + header($h, false); + } + + return $sessionCookie; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php new file mode 100644 index 0000000..288c242 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\SessionUtils; + +/** + * This abstract session handler provides a generic implementation + * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, + * enabling strict and lazy session handling. + * + * @author Nicolas Grekas + */ +abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private string $sessionName; + private string $prefetchId; + private string $prefetchData; + private ?string $newSessionId = null; + private string $igbinaryEmptyData; + + public function open(string $savePath, string $sessionName): bool + { + $this->sessionName = $sessionName; + if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) { + header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire'))); + } + + return true; + } + + abstract protected function doRead(#[\SensitiveParameter] string $sessionId): string; + + abstract protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool; + + abstract protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool; + + public function validateId(#[\SensitiveParameter] string $sessionId): bool + { + $this->prefetchData = $this->read($sessionId); + $this->prefetchId = $sessionId; + + return '' !== $this->prefetchData; + } + + public function read(#[\SensitiveParameter] string $sessionId): string + { + if (isset($this->prefetchId)) { + $prefetchId = $this->prefetchId; + $prefetchData = $this->prefetchData; + unset($this->prefetchId, $this->prefetchData); + + if ($prefetchId === $sessionId || '' === $prefetchData) { + $this->newSessionId = '' === $prefetchData ? $sessionId : null; + + return $prefetchData; + } + } + + $data = $this->doRead($sessionId); + $this->newSessionId = '' === $data ? $sessionId : null; + + return $data; + } + + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool + { + // see https://github.com/igbinary/igbinary/issues/146 + $this->igbinaryEmptyData ??= \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; + if ('' === $data || $this->igbinaryEmptyData === $data) { + return $this->destroy($sessionId); + } + $this->newSessionId = null; + + return $this->doWrite($sessionId, $data); + } + + public function destroy(#[\SensitiveParameter] string $sessionId): bool + { + if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL)) { + if (!isset($this->sessionName)) { + throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); + } + $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId); + + /* + * We send an invalidation Set-Cookie header (zero lifetime) + * when either the session was started or a cookie with + * the session name was sent by the client (in which case + * we know it's invalid as a valid session cookie would've + * started the session). + */ + if (null === $cookie || isset($_COOKIE[$this->sessionName])) { + $params = session_get_cookie_params(); + unset($params['lifetime']); + setcookie($this->sessionName, '', $params); + } + } + + return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php b/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php new file mode 100644 index 0000000..411a8d1 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class IdentityMarshaller implements MarshallerInterface +{ + public function marshall(array $values, ?array &$failed): array + { + foreach ($values as $key => $value) { + if (!\is_string($value)) { + throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__)); + } + } + + return $values; + } + + public function unmarshall(string $value): string + { + return $value; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php new file mode 100644 index 0000000..1567f54 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private AbstractSessionHandler $handler; + private MarshallerInterface $marshaller; + + public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller) + { + $this->handler = $handler; + $this->marshaller = $marshaller; + } + + public function open(string $savePath, string $name): bool + { + return $this->handler->open($savePath, $name); + } + + public function close(): bool + { + return $this->handler->close(); + } + + public function destroy(#[\SensitiveParameter] string $sessionId): bool + { + return $this->handler->destroy($sessionId); + } + + public function gc(int $maxlifetime): int|false + { + return $this->handler->gc($maxlifetime); + } + + public function read(#[\SensitiveParameter] string $sessionId): string + { + return $this->marshaller->unmarshall($this->handler->read($sessionId)); + } + + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $failed = []; + $marshalledData = $this->marshaller->marshall(['data' => $data], $failed); + + if (isset($failed['data'])) { + return false; + } + + return $this->handler->write($sessionId, $marshalledData['data']); + } + + public function validateId(#[\SensitiveParameter] string $sessionId): bool + { + return $this->handler->validateId($sessionId); + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return $this->handler->updateTimestamp($sessionId, $data); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 0000000..91a023d --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see https://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler extends AbstractSessionHandler +{ + private \Memcached $memcached; + + /** + * Time to live in seconds. + */ + private int|\Closure|null $ttl; + + /** + * Key prefix for shared environments. + */ + private string $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * ttl: The time to live in seconds. + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = []) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); + } + + $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null; + $this->prefix = $options['prefix'] ?? 'sf2s'; + } + + public function close(): bool + { + return $this->memcached->quit(); + } + + protected function doRead(#[\SensitiveParameter] string $sessionId): string + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl()); + + return true; + } + + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl()); + } + + private function getCompatibleTtl(): int + { + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + + // If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time. + // We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct. + if ($ttl > 60 * 60 * 24 * 30) { + $ttl += time(); + } + + return $ttl; + } + + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool + { + $result = $this->memcached->delete($this->prefix.$sessionId); + + return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode(); + } + + public function gc(int $maxlifetime): int|false + { + // not required here because memcached will auto expire the records anyhow. + return 0; + } + + /** + * Return a Memcached instance. + */ + protected function getMemcached(): \Memcached + { + return $this->memcached; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php new file mode 100644 index 0000000..8ed6a7b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Migrating session handler for migrating from one handler to another. It reads + * from the current handler and writes both the current and new ones. + * + * It ignores errors from the new handler. + * + * @author Ross Motley + * @author Oliver Radwell + */ +class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $currentHandler; + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $writeOnlyHandler; + + public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) + { + if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $currentHandler = new StrictSessionHandler($currentHandler); + } + if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler); + } + + $this->currentHandler = $currentHandler; + $this->writeOnlyHandler = $writeOnlyHandler; + } + + public function close(): bool + { + $result = $this->currentHandler->close(); + $this->writeOnlyHandler->close(); + + return $result; + } + + public function destroy(#[\SensitiveParameter] string $sessionId): bool + { + $result = $this->currentHandler->destroy($sessionId); + $this->writeOnlyHandler->destroy($sessionId); + + return $result; + } + + public function gc(int $maxlifetime): int|false + { + $result = $this->currentHandler->gc($maxlifetime); + $this->writeOnlyHandler->gc($maxlifetime); + + return $result; + } + + public function open(string $savePath, string $sessionName): bool + { + $result = $this->currentHandler->open($savePath, $sessionName); + $this->writeOnlyHandler->open($savePath, $sessionName); + + return $result; + } + + public function read(#[\SensitiveParameter] string $sessionId): string + { + // No reading from new handler until switch-over + return $this->currentHandler->read($sessionId); + } + + public function write(#[\SensitiveParameter] string $sessionId, string $sessionData): bool + { + $result = $this->currentHandler->write($sessionId, $sessionData); + $this->writeOnlyHandler->write($sessionId, $sessionData); + + return $result; + } + + public function validateId(#[\SensitiveParameter] string $sessionId): bool + { + // No reading from new handler until switch-over + return $this->currentHandler->validateId($sessionId); + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $sessionData): bool + { + $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); + $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); + + return $result; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 0000000..d558603 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use MongoDB\BSON\Binary; +use MongoDB\BSON\UTCDateTime; +use MongoDB\Client; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Query; + +/** + * Session handler using the MongoDB driver extension. + * + * @author Markus Bachmann + * @author Jérôme Tamarelle + * + * @see https://php.net/mongodb + */ +class MongoDbSessionHandler extends AbstractSessionHandler +{ + private Manager $manager; + private string $namespace; + private array $options; + private int|\Closure|null $ttl; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] + * * ttl: The time to live in seconds. + * + * It is strongly recommended to put an index on the `expiry_field` for + * garbage-collection. Alternatively it's possible to automatically expire + * the sessions in the database as described below: + * + * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions + * automatically. Such an index can for example look like this: + * + * db..createIndex( + * { "": 1 }, + * { "expireAfterSeconds": 0 } + * ) + * + * More details on: https://docs.mongodb.org/manual/tutorial/expire-data/ + * + * If you use such an index, you can drop `gc_probability` to 0 since + * no garbage-collection is required. + * + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct(Client|Manager $mongo, array $options) + { + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.'); + } + + if ($mongo instanceof Client) { + $mongo = $mongo->getManager(); + } + + $this->manager = $mongo; + $this->namespace = $options['database'].'.'.$options['collection']; + + $this->options = array_merge([ + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + ], $options); + $this->ttl = $this->options['ttl'] ?? null; + } + + public function close(): bool + { + return true; + } + + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool + { + $write = new BulkWrite(); + $write->delete( + [$this->options['id_field'] => $sessionId], + ['limit' => 1] + ); + + $this->manager->executeBulkWrite($this->namespace, $write); + + return true; + } + + public function gc(int $maxlifetime): int|false + { + $write = new BulkWrite(); + $write->delete( + [$this->options['expiry_field'] => ['$lt' => $this->getUTCDateTime()]], + ); + $result = $this->manager->executeBulkWrite($this->namespace, $write); + + return $result->getDeletedCount() ?? false; + } + + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + $expiry = $this->getUTCDateTime($ttl); + + $fields = [ + $this->options['time_field'] => $this->getUTCDateTime(), + $this->options['expiry_field'] => $expiry, + $this->options['data_field'] => new Binary($data, Binary::TYPE_GENERIC), + ]; + + $write = new BulkWrite(); + $write->update( + [$this->options['id_field'] => $sessionId], + ['$set' => $fields], + ['upsert' => true] + ); + + $this->manager->executeBulkWrite($this->namespace, $write); + + return true; + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + $expiry = $this->getUTCDateTime($ttl); + + $write = new BulkWrite(); + $write->update( + [$this->options['id_field'] => $sessionId], + ['$set' => [ + $this->options['time_field'] => $this->getUTCDateTime(), + $this->options['expiry_field'] => $expiry, + ]], + ['multi' => false], + ); + + $this->manager->executeBulkWrite($this->namespace, $write); + + return true; + } + + protected function doRead(#[\SensitiveParameter] string $sessionId): string + { + $cursor = $this->manager->executeQuery($this->namespace, new Query([ + $this->options['id_field'] => $sessionId, + $this->options['expiry_field'] => ['$gte' => $this->getUTCDateTime()], + ], [ + 'projection' => [ + '_id' => false, + $this->options['data_field'] => true, + ], + 'limit' => 1, + ])); + + foreach ($cursor as $document) { + return (string) $document->{$this->options['data_field']} ?? ''; + } + + // Not found + return ''; + } + + private function getUTCDateTime(int $additionalSeconds = 0): UTCDateTime + { + return new UTCDateTime((time() + $additionalSeconds) * 1000); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 0000000..f8c6151 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends \SessionHandler +{ + /** + * @param string|null $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see https://php.net/session.configuration#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + * @throws \RuntimeException When failing to create the save directory + */ + public function __construct(?string $savePath = null) + { + $baseDir = $savePath ??= \ini_get('session.save_path'); + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'.', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $baseDir)); + } + + if ($savePath !== \ini_get('session.save_path')) { + ini_set('session.save_path', $savePath); + } + if ('files' !== \ini_get('session.save_handler')) { + ini_set('session.save_handler', 'files'); + } + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 0000000..a77185e --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + */ +class NullSessionHandler extends AbstractSessionHandler +{ + public function close(): bool + { + return true; + } + + public function validateId(#[\SensitiveParameter] string $sessionId): bool + { + return true; + } + + protected function doRead(#[\SensitiveParameter] string $sessionId): string + { + return ''; + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return true; + } + + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return true; + } + + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool + { + return true; + } + + public function gc(int $maxlifetime): int|false + { + return 0; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 0000000..aa8ab56 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,899 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Types\Types; + +/** + * Session handler using a PDO connection to read and write data. + * + * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements + * different locking strategies to handle concurrent access to the same session. + * Locking is necessary to prevent loss of data due to race conditions and to keep + * the session data consistent between read() and write(). With locking, requests + * for the same session will wait until the other one finished writing. For this + * reason it's best practice to close a session as early as possible to improve + * concurrency. PHPs internal files session handler also implements locking. + * + * Attention: Since SQLite does not support row level locks but locks the whole database, + * it means only one session can be accessed at a time. Even different sessions would wait + * for another to finish. So saving session in SQLite should only be considered for + * development or prototypes. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason it must be saved in a binary column in the database like BLOB in MySQL. + * Saving it in a character column could corrupt the data. You can use createTable() + * to initialize a correctly defined table. + * + * @see https://php.net/sessionhandlerinterface + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + */ +class PdoSessionHandler extends AbstractSessionHandler +{ + /** + * No locking is done. This means sessions are prone to loss of data due to + * race conditions of concurrent requests to the same session. The last session + * write will win in this case. It might be useful when you implement your own + * logic to deal with this like an optimistic approach. + */ + public const LOCK_NONE = 0; + + /** + * Creates an application-level lock on a session. The disadvantage is that the + * lock is not enforced by the database and thus other, unaware parts of the + * application could still concurrently modify the session. The advantage is it + * does not require a transaction. + * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. + */ + public const LOCK_ADVISORY = 1; + + /** + * Issues a real row lock. Since it uses a transaction between opening and + * closing a session, you have to be careful when you use same database connection + * that you also use for your application logic. This mode is the default because + * it's the only reliable solution across DBMSs. + */ + public const LOCK_TRANSACTIONAL = 2; + + private \PDO $pdo; + + /** + * DSN string or null for session.save_path or false when lazy connection disabled. + */ + private string|false|null $dsn = false; + + private string $driver; + private string $table = 'sessions'; + private string $idCol = 'sess_id'; + private string $dataCol = 'sess_data'; + private string $lifetimeCol = 'sess_lifetime'; + private string $timeCol = 'sess_time'; + + /** + * Time to live in seconds. + */ + private int|\Closure|null $ttl; + + /** + * Username when lazy-connect. + */ + private ?string $username = null; + + /** + * Password when lazy-connect. + */ + private ?string $password = null; + + /** + * Connection options when lazy-connect. + */ + private array $connectionOptions = []; + + /** + * The strategy for locking, see constants. + */ + private int $lockMode = self::LOCK_TRANSACTIONAL; + + /** + * It's an array to support multiple reads before closing which is manual, non-standard usage. + * + * @var \PDOStatement[] An array of statements to release advisory locks + */ + private array $unlockStatements = []; + + /** + * True when the current session exists but expired according to session.gc_maxlifetime. + */ + private bool $sessionExpired = false; + + /** + * Whether a transaction is active. + */ + private bool $inTransaction = false; + + /** + * Whether gc() has been called. + */ + private bool $gcCalled = false; + + /** + * You can either pass an existing database connection as PDO instance or + * pass a DSN string that will be used to lazy-connect to the database + * when the session is actually used. Furthermore it's possible to pass null + * which will then use the session.save_path ini setting as PDO DSN parameter. + * + * List of available options: + * * db_table: The name of the table [default: sessions] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * * ttl: The time to live in seconds. + * + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null + * + * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + */ + public function __construct(#[\SensitiveParameter] \PDO|string|null $pdoOrDsn = null, #[\SensitiveParameter] array $options = []) + { + if ($pdoOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); + } + + $this->pdo = $pdoOrDsn; + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } elseif (\is_string($pdoOrDsn) && str_contains($pdoOrDsn, '://')) { + $this->dsn = $this->buildDsnFromUrl($pdoOrDsn); + } else { + $this->dsn = $pdoOrDsn; + } + + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->username = $options['db_username'] ?? $this->username; + $this->password = $options['db_password'] ?? $this->password; + $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; + $this->lockMode = $options['lock_mode'] ?? $this->lockMode; + $this->ttl = $options['ttl'] ?? null; + } + + /** + * Adds the Table to the Schema if it doesn't exist. + */ + public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null): void + { + if ($schema->hasTable($this->table) || ($isSameDatabase && !$isSameDatabase($this->getConnection()->exec(...)))) { + return; + } + + $table = $schema->createTable($this->table); + switch ($this->driver) { + case 'mysql': + $table->addColumn($this->idCol, Types::BINARY)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addOption('collate', 'utf8mb4_bin'); + $table->addOption('engine', 'InnoDB'); + break; + case 'sqlite': + $table->addColumn($this->idCol, Types::TEXT)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'pgsql': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BINARY)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'oci': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'sqlsrv': + $table->addColumn($this->idCol, Types::TEXT)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + $table->setPrimaryKey([$this->idCol]); + $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx'); + } + + /** + * Creates the table to store sessions which can be called once for setup. + * + * Session ID is saved in a column of maximum length 128 because that is enough even + * for a 512 bit configured session.hash_function like Whirlpool. Session data is + * saved in a BLOB. One could also use a shorter inlined varbinary column + * if one was sure the data fits into it. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable(): void + { + // connect if we are not yet + $this->getConnection(); + + $sql = match ($this->driver) { + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + 'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB", + 'sqlite' => "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + 'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + 'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + 'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)", + default => throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)), + }; + + try { + $this->pdo->exec($sql); + $this->pdo->exec("CREATE INDEX {$this->lifetimeCol}_idx ON $this->table ($this->lifetimeCol)"); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * Returns true when the current session exists but expired according to session.gc_maxlifetime. + * + * Can be used to distinguish between a new session and one that expired due to inactivity. + */ + public function isSessionExpired(): bool + { + return $this->sessionExpired; + } + + public function open(string $savePath, string $sessionName): bool + { + $this->sessionExpired = false; + + if (!isset($this->pdo)) { + $this->connect($this->dsn ?: $savePath); + } + + return parent::open($savePath, $sessionName); + } + + public function read(#[\SensitiveParameter] string $sessionId): string + { + try { + return parent::read($sessionId); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + public function gc(int $maxlifetime): int|false + { + // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. + // This way, pruning expired sessions does not block them from being started while the current session is used. + $this->gcCalled = true; + + return 0; + } + + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $maxlifetime = (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime')); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); + if (null !== $mergeStmt) { + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). + // We can just catch such an error and re-execute the update. This is similar to a serializable + // transaction with retry logic on serialization failures but without the overhead and without possible + // false positives due to longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (str_starts_with($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $expiry = time() + (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime')); + + try { + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindValue(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindValue(':expiry', $expiry, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + public function close(): bool + { + $this->commit(); + + while ($unlockStmt = array_shift($this->unlockStatements)) { + $unlockStmt->execute(); + } + + if ($this->gcCalled) { + $this->gcCalled = false; + + // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + } + + if (false !== $this->dsn) { + unset($this->pdo, $this->driver); // only close lazy-connection + } + + return true; + } + + /** + * Lazy-connects to the database. + */ + private function connect(#[\SensitiveParameter] string $dsn): void + { + $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Builds a PDO DSN from a URL-like connection string. + * + * @todo implement missing support for oci DSN (which look totally different from other PDO ones) + */ + private function buildDsnFromUrl(#[\SensitiveParameter] string $dsnOrUrl): string + { + // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid + $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); + + $params = parse_url($url); + + if (false === $params) { + return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already. + } + + $params = array_map('rawurldecode', $params); + + // Override the default username and password. Values passed through options will still win over these in the constructor. + if (isset($params['user'])) { + $this->username = $params['user']; + } + + if (isset($params['pass'])) { + $this->password = $params['pass']; + } + + if (!isset($params['scheme'])) { + throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler.'); + } + + $driverAliasMap = [ + 'mssql' => 'sqlsrv', + 'mysql2' => 'mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pgsql', + 'postgresql' => 'pgsql', + 'sqlite3' => 'sqlite', + ]; + + $driver = $driverAliasMap[$params['scheme']] ?? $params['scheme']; + + // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here. + if (str_starts_with($driver, 'pdo_') || str_starts_with($driver, 'pdo-')) { + $driver = substr($driver, 4); + } + + $dsn = null; + switch ($driver) { + case 'mysql': + $dsn = 'mysql:'; + if ('' !== ($params['query'] ?? '')) { + $queryParams = []; + parse_str($params['query'], $queryParams); + if ('' !== ($queryParams['charset'] ?? '')) { + $dsn .= 'charset='.$queryParams['charset'].';'; + } + + if ('' !== ($queryParams['unix_socket'] ?? '')) { + $dsn .= 'unix_socket='.$queryParams['unix_socket'].';'; + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + } + } + // If "unix_socket" is not in the query, we continue with the same process as pgsql + // no break + case 'pgsql': + $dsn ??= 'pgsql:'; + + if (isset($params['host']) && '' !== $params['host']) { + $dsn .= 'host='.$params['host'].';'; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= 'port='.$params['port'].';'; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + + case 'sqlite': + return 'sqlite:'.substr($params['path'], 1); + + case 'sqlsrv': + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= ','.$params['port']; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= ';Database='.$dbName; + } + + return $dsn; + + default: + throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme'])); + } + } + + /** + * Helper method to begin a transaction. + * + * Since SQLite does not support row level locks, we have to acquire a reserved lock + * on the database immediately. Because of https://bugs.php.net/42766 we have to create + * such a transaction manually which also means we cannot use PDO::commit or + * PDO::rollback or PDO::inTransaction for SQLite. + * + * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions + * due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . + * So we change it to READ COMMITTED. + */ + private function beginTransaction(): void + { + if (!$this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); + } else { + if ('mysql' === $this->driver) { + $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + } + $this->pdo->beginTransaction(); + } + $this->inTransaction = true; + } + } + + /** + * Helper method to commit a transaction. + */ + private function commit(): void + { + if ($this->inTransaction) { + try { + // commit read-write transaction which also releases the lock + if ('sqlite' === $this->driver) { + $this->pdo->exec('COMMIT'); + } else { + $this->pdo->commit(); + } + $this->inTransaction = false; + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + } + + /** + * Helper method to rollback a transaction. + */ + private function rollback(): void + { + // We only need to rollback if we are in a transaction. Otherwise the resulting + // error would hide the real problem why rollback was called. We might not be + // in a transaction when not using the transactional locking behavior or when + // two callbacks (e.g. destroy and write) are invoked that both fail. + if ($this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('ROLLBACK'); + } else { + $this->pdo->rollBack(); + } + $this->inTransaction = false; + } + } + + /** + * Reads the session data in respect to the different locking strategies. + * + * We need to make sure we do not return session data that is already considered garbage according + * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. + */ + protected function doRead(#[\SensitiveParameter] string $sessionId): string + { + if (self::LOCK_ADVISORY === $this->lockMode) { + $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); + } + + $selectSql = $this->getSelectSql(); + $selectStmt = $this->pdo->prepare($selectSql); + $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt = null; + + while (true) { + $selectStmt->execute(); + $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + $expiry = (int) $sessionRows[0][1]; + + if ($expiry < time()) { + $this->sessionExpired = true; + + return ''; + } + + return \is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + if (null !== $insertStmt) { + $this->rollback(); + throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); + } + + if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOL) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // In strict mode, session fixation is not possible: new sessions always start with a unique + // random id, so that concurrency is not possible and this code path can be skipped. + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block + // until other connections to the session are committed. + try { + $insertStmt = $this->getInsertStatement($sessionId, '', 0); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Catch duplicate key error because other connection created the session already. + // It would only not be the case when the other connection destroyed the session. + if (str_starts_with($e->getCode(), '23')) { + // Retrieve finished session data written by concurrent connection by restarting the loop. + // We have to start a new transaction as a failed query will mark the current transaction as + // aborted in PostgreSQL and disallow further queries within it. + $this->rollback(); + $this->beginTransaction(); + continue; + } + + throw $e; + } + } + + return ''; + } + } + + /** + * Executes an application-level lock on the database. + * + * @return \PDOStatement The statement that needs to be executed later to release the lock + * + * @throws \DomainException When an unsupported PDO driver is used + * + * @todo implement missing advisory locks + * - for oci using DBMS_LOCK.REQUEST + * - for sqlsrv using sp_getapplock with LockOwner = Session + */ + private function doAdvisoryLock(#[\SensitiveParameter] string $sessionId): \PDOStatement + { + switch ($this->driver) { + case 'mysql': + // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced. + $lockId = substr($sessionId, 0, 64); + // should we handle the return value? 0 on timeout, null on error + // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout + $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); + $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); + $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + + return $releaseStmt; + case 'pgsql': + // Obtaining an exclusive session level advisory lock requires an integer key. + // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters. + // So we cannot just use hexdec(). + if (4 === \PHP_INT_SIZE) { + $sessionInt1 = $this->convertStringToInt($sessionId); + $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); + $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); + $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + } else { + $sessionBigInt = $this->convertStringToInt($sessionId); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); + $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); + $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + } + + return $releaseStmt; + case 'sqlite': + throw new \DomainException('SQLite does not support advisory locks.'); + default: + throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + /** + * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer. + * + * Keep in mind, PHP integers are signed. + */ + private function convertStringToInt(string $string): int + { + if (4 === \PHP_INT_SIZE) { + return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + } + + $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]); + $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + + return $int2 + ($int1 << 32); + } + + /** + * Return a locking or nonlocking SQL query to read session information. + * + * @throws \DomainException When an unsupported PDO driver is used + */ + private function getSelectSql(): string + { + if (self::LOCK_TRANSACTIONAL === $this->lockMode) { + $this->beginTransaction(); + + switch ($this->driver) { + case 'mysql': + case 'oci': + case 'pgsql': + return "SELECT $this->dataCol, $this->lifetimeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + case 'sqlsrv': + return "SELECT $this->dataCol, $this->lifetimeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + case 'sqlite': + // we already locked when starting transaction + break; + default: + throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + return "SELECT $this->dataCol, $this->lifetimeCol FROM $this->table WHERE $this->idCol = :id"; + } + + /** + * Returns an insert statement supported by the database for writing session data. + */ + private function getInsertStatement(#[\SensitiveParameter] string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns an update statement supported by the database for writing session data. + */ + private function getUpdateStatement(#[\SensitiveParameter] string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. + */ + private function getMergeStatement(#[\SensitiveParameter] string $sessionId, string $data, int $maxlifetime): ?\PDOStatement + { + switch (true) { + case 'mysql' === $this->driver: + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/ + $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $this->driver: + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + // MERGE is not supported with LOBs: https://oracle.com/technetwork/articles/fuecks-lobs-095315.html + return null; + } + + $mergeStmt = $this->pdo->prepare($mergeSql); + + if ('sqlsrv' === $this->driver) { + $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(7, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); + } else { + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + } + + return $mergeStmt; + } + + /** + * Return a PDO instance. + */ + protected function getConnection(): \PDO + { + if (!isset($this->pdo)) { + $this->connect($this->dsn ?: \ini_get('session.save_path')); + } + + return $this->pdo; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php new file mode 100644 index 0000000..b696eee --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Predis\Response\ErrorInterface; +use Relay\Relay; + +/** + * Redis based session storage handler based on the Redis class + * provided by the PHP redis extension. + * + * @author Dalibor Karlović + */ +class RedisSessionHandler extends AbstractSessionHandler +{ + /** + * Key prefix for shared environments. + */ + private string $prefix; + + /** + * Time to live in seconds. + */ + private int|\Closure|null $ttl; + + /** + * List of available options: + * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server + * * ttl: The time to live in seconds. + * + * @throws \InvalidArgumentException When unsupported client or options are passed + */ + public function __construct( + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + array $options = [], + ) { + if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); + } + + $this->prefix = $options['prefix'] ?? 'sf_s'; + $this->ttl = $options['ttl'] ?? null; + } + + protected function doRead(#[\SensitiveParameter] string $sessionId): string + { + return $this->redis->get($this->prefix.$sessionId) ?: ''; + } + + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + $result = $this->redis->setEx($this->prefix.$sessionId, (int) $ttl, $data); + + return $result && !$result instanceof ErrorInterface; + } + + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool + { + static $unlink = true; + + if ($unlink) { + try { + $unlink = false !== $this->redis->unlink($this->prefix.$sessionId); + } catch (\Throwable) { + $unlink = false; + } + } + + if (!$unlink) { + $this->redis->del($this->prefix.$sessionId); + } + + return true; + } + + #[\ReturnTypeWillChange] + public function close(): bool + { + return true; + } + + public function gc(int $maxlifetime): int|false + { + return 0; + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); + + return $this->redis->expire($this->prefix.$sessionId, (int) $ttl); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php new file mode 100644 index 0000000..3f1d032 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; +use Relay\Relay; +use Symfony\Component\Cache\Adapter\AbstractAdapter; + +/** + * @author Nicolas Grekas + */ +class SessionHandlerFactory +{ + public static function createHandler(object|string $connection, array $options = []): AbstractSessionHandler + { + if ($query = \is_string($connection) ? parse_url($connection) : false) { + parse_str($query['query'] ?? '', $query); + + if (($options['ttl'] ?? null) instanceof \Closure) { + $query['ttl'] = $options['ttl']; + } + } + $options = ($query ?: []) + $options; + + switch (true) { + case $connection instanceof \Redis: + case $connection instanceof Relay: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + return new RedisSessionHandler($connection); + + case $connection instanceof \Memcached: + return new MemcachedSessionHandler($connection); + + case $connection instanceof \PDO: + return new PdoSessionHandler($connection); + + case !\is_string($connection): + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection))); + case str_starts_with($connection, 'file://'): + $savePath = substr($connection, 7); + + return new StrictSessionHandler(new NativeFileSessionHandler('' === $savePath ? null : $savePath)); + + case str_starts_with($connection, 'redis:'): + case str_starts_with($connection, 'rediss:'): + case str_starts_with($connection, 'memcached:'): + if (!class_exists(AbstractAdapter::class)) { + throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".'); + } + $handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return new $handlerClass($connection, array_intersect_key($options, ['prefix' => 1, 'ttl' => 1])); + + case str_starts_with($connection, 'pdo_oci://'): + if (!class_exists(DriverManager::class)) { + throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".'); + } + $connection[3] = '-'; + $params = (new DsnParser())->parse($connection); + $config = new Configuration(); + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + + $connection = DriverManager::getConnection($params, $config)->getNativeConnection(); + // no break; + + case str_starts_with($connection, 'mssql://'): + case str_starts_with($connection, 'mysql://'): + case str_starts_with($connection, 'mysql2://'): + case str_starts_with($connection, 'pgsql://'): + case str_starts_with($connection, 'postgres://'): + case str_starts_with($connection, 'postgresql://'): + case str_starts_with($connection, 'sqlsrv://'): + case str_starts_with($connection, 'sqlite://'): + case str_starts_with($connection, 'sqlite3://'): + return new PdoSessionHandler($connection, $options); + } + + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection)); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php new file mode 100644 index 0000000..1f86687 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`. + * + * @author Nicolas Grekas + */ +class StrictSessionHandler extends AbstractSessionHandler +{ + private \SessionHandlerInterface $handler; + private bool $doDestroy; + + public function __construct(\SessionHandlerInterface $handler) + { + if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { + throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class)); + } + + $this->handler = $handler; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @internal + */ + public function isWrapper(): bool + { + return $this->handler instanceof \SessionHandler; + } + + public function open(string $savePath, string $sessionName): bool + { + parent::open($savePath, $sessionName); + + return $this->handler->open($savePath, $sessionName); + } + + protected function doRead(#[\SensitiveParameter] string $sessionId): string + { + return $this->handler->read($sessionId); + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return $this->write($sessionId, $data); + } + + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return $this->handler->write($sessionId, $data); + } + + public function destroy(#[\SensitiveParameter] string $sessionId): bool + { + $this->doDestroy = true; + $destroyed = parent::destroy($sessionId); + + return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed; + } + + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool + { + $this->doDestroy = false; + + return $this->handler->destroy($sessionId); + } + + public function close(): bool + { + return $this->handler->close(); + } + + public function gc(int $maxlifetime): int|false + { + return $this->handler->gc($maxlifetime); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 0000000..3e80f7d --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + public const CREATED = 'c'; + public const UPDATED = 'u'; + public const LIFETIME = 'l'; + + protected array $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0]; + + private string $name = '__metadata'; + private string $storageKey; + private int $lastUsed; + private int $updateThreshold; + + /** + * @param string $storageKey The key used to store bag in the session + * @param int $updateThreshold The time to wait between two UPDATED updates + */ + public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + + public function initialize(array &$array): void + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + */ + public function getLifetime(): int + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew(?int $lifetime = null): void + { + $this->stampCreated($lifetime); + } + + public function getStorageKey(): string + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return int Unix timestamp + */ + public function getCreated(): int + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return int Unix timestamp + */ + public function getLastUsed(): int + { + return $this->lastUsed; + } + + public function clear(): mixed + { + // nothing to do + return null; + } + + public function getName(): string + { + return $this->name; + } + + /** + * Sets name. + */ + public function setName(string $name): void + { + $this->name = $name; + } + + private function stampCreated(?int $lifetime = null): void + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime'); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 0000000..617e51e --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + protected string $id = ''; + protected string $name; + protected bool $started = false; + protected bool $closed = false; + protected array $data = []; + protected MetadataBag $metadataBag; + + /** + * @var SessionBagInterface[] + */ + protected array $bags = []; + + public function __construct(string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + public function setSessionData(array $array): void + { + $this->data = $array; + } + + public function start(): bool + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): void + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function save(): void + { + if (!$this->started || $this->closed) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + public function clear(): void + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + public function registerBag(SessionBagInterface $bag): void + { + $this->bags[$bag->getName()] = $bag; + } + + public function getBag(string $name): SessionBagInterface + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + public function isStarted(): bool + { + return $this->started; + } + + public function setMetadataBag(?MetadataBag $bag): void + { + $this->metadataBag = $bag ?? new MetadataBag(); + } + + /** + * Gets the MetadataBag. + */ + public function getMetadataBag(): MetadataBag + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + */ + protected function generateId(): string + { + return bin2hex(random_bytes(16)); + } + + protected function loadSession(): void + { + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] ??= []; + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 0000000..48dd74d --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing where you may need to persist session data + * across separate PHP processes. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + private string $savePath; + + /** + * @param string|null $savePath Path of directory to save session files + */ + public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) + { + $savePath ??= sys_get_temp_dir(); + + if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $savePath)); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + public function start(): bool + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + public function save(): void + { + if (!$this->started) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); + } + + $data = $this->data; + + foreach ($this->bags as $bag) { + if (empty($data[$key = $bag->getStorageKey()])) { + unset($data[$key]); + } + } + if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) { + unset($data[$key]); + } + + try { + if ($data) { + $path = $this->getFilePath(); + $tmp = $path.bin2hex(random_bytes(6)); + file_put_contents($tmp, serialize($data)); + rename($tmp, $path); + } else { + $this->destroy(); + } + } finally { + $this->data = $data; + } + + // this is needed when the session object is re-used across multiple requests + // in functional tests. + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy(): void + { + set_error_handler(static function () {}); + try { + unlink($this->getFilePath()); + } finally { + restore_error_handler(); + } + } + + /** + * Calculate path to file. + */ + private function getFilePath(): string + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read(): void + { + set_error_handler(static function () {}); + try { + $data = file_get_contents($this->getFilePath()); + } finally { + restore_error_handler(); + } + + $this->data = $data ? unserialize($data) : []; + + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php new file mode 100644 index 0000000..6727cf1 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(MockFileSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class MockFileSessionStorageFactory implements SessionStorageFactoryInterface +{ + private ?string $savePath; + private string $name; + private ?MetadataBag $metaBag; + + /** + * @see MockFileSessionStorage constructor. + */ + public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) + { + $this->savePath = $savePath; + $this->name = $name; + $this->metaBag = $metaBag; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 0000000..d32292a --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,402 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +// Help opcache.preload discover always-needed symbols +class_exists(MetadataBag::class); +class_exists(StrictSessionHandler::class); +class_exists(SessionHandlerProxy::class); + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * @var SessionBagInterface[] + */ + protected array $bags = []; + protected bool $started = false; + protected bool $closed = false; + protected AbstractProxy|\SessionHandlerInterface $saveHandler; + protected MetadataBag $metadataBag; + + /** + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * + * @see https://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "" (use "0" to prevent headers from being sent entirely). + * cache_expire, "0" + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * cookie_samesite, null + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * lazy_write, "1" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_strict_mode, "1" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * sid_length, "32" + * sid_bits_per_character, "5" + * trans_sid_hosts, $_SERVER['HTTP_HOST'] + * trans_sid_tags, "a=href,area=href,frame=src,form=" + */ + public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $options += [ + 'cache_limiter' => '', + 'cache_expire' => 0, + 'use_cookies' => 1, + 'lazy_write' => 1, + 'use_strict_mode' => 1, + ]; + + session_register_shutdown(); + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + */ + public function getSaveHandler(): AbstractProxy|\SessionHandlerInterface + { + return $this->saveHandler; + } + + public function start(): bool + { + if ($this->started) { + return true; + } + + if (\PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL) && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + $sessionId = $_COOKIE[session_name()] ?? null; + /* + * Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`. + * + * ---------- Part 1 + * + * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6. + * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character. + * Allowed values are integers such as: + * - 4 for range `a-f0-9` + * - 5 for range `a-v0-9` + * - 6 for range `a-zA-Z0-9,-` + * + * ---------- Part 2 + * + * The part `{22,250}` is related to the PHP ini directive `session.sid_length`. + * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length. + * Allowed values are integers between 22 and 256, but we use 250 for the max. + * + * Where does the 250 come from? + * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255. + * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250. + * + * ---------- Conclusion + * + * The parts 1 and 2 prevent the warning below: + * `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.` + * + * The part 2 prevents the warning below: + * `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).` + */ + if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) { + // the session ID in the header is invalid, create a new one + session_id(session_create_id()); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session.'); + } + + $this->loadSession(); + + return true; + } + + public function getId(): string + { + return $this->saveHandler->getId(); + } + + public function setId(string $id): void + { + $this->saveHandler->setId($id); + } + + public function getName(): string + { + return $this->saveHandler->getName(); + } + + public function setName(string $name): void + { + $this->saveHandler->setName($name); + } + + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool + { + // Cannot regenerate the session ID for non-active sessions. + if (\PHP_SESSION_ACTIVE !== session_status()) { + return false; + } + + if (headers_sent()) { + return false; + } + + if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) { + $this->save(); + ini_set('session.cookie_lifetime', $lifetime); + $this->start(); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + return session_regenerate_id($destroy); + } + + public function save(): void + { + // Store a copy so we can restore the bags in case the session was not left empty + $session = $_SESSION; + + foreach ($this->bags as $bag) { + if (empty($_SESSION[$key = $bag->getStorageKey()])) { + unset($_SESSION[$key]); + } + } + if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { + unset($_SESSION[$key]); + } + + // Register error handler to add information about the current save handler + $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) { + if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) { + $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler; + $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', $handler::class); + } + + return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false; + }); + + try { + session_write_close(); + } finally { + restore_error_handler(); + + // Restore only if not empty + if ($_SESSION) { + $_SESSION = $session; + } + } + + $this->closed = true; + $this->started = false; + } + + public function clear(): void + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + public function registerBag(SessionBagInterface $bag): void + { + if ($this->started) { + throw new \LogicException('Cannot register a bag when the session is already started.'); + } + + $this->bags[$bag->getName()] = $bag; + } + + public function getBag(string $name): SessionBagInterface + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); + } + + if (!$this->started && $this->saveHandler->isActive()) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + public function setMetadataBag(?MetadataBag $metaBag): void + { + $this->metadataBag = $metaBag ?? new MetadataBag(); + } + + /** + * Gets the MetadataBag. + */ + public function getMetadataBag(): MetadataBag + { + return $this->metadataBag; + } + + public function isStarted(): bool + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives [key => value] + * + * @see https://php.net/session.configuration + */ + public function setOptions(array $options): void + { + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + $validOptions = array_flip([ + 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite', + 'gc_divisor', 'gc_maxlifetime', 'gc_probability', + 'lazy_write', 'name', 'referer_check', + 'serialize_handler', 'use_strict_mode', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', + 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', + ]); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + if ('cookie_secure' === $key && 'auto' === $value) { + continue; + } + ini_set('session.'.$key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', '/tmp'); + * + * or pass in a \SessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler. + * + * @see https://php.net/session-set-save-handler + * @see https://php.net/sessionhandlerinterface + * @see https://php.net/sessionhandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler(AbstractProxy|\SessionHandlerInterface|null $saveHandler): void + { + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler())); + } + $this->saveHandler = $saveHandler; + + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + if ($this->saveHandler instanceof SessionHandlerProxy) { + session_set_save_handler($this->saveHandler, false); + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + */ + protected function loadSession(?array &$session = null): void + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : []; + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php new file mode 100644 index 0000000..6463a4c --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +// Help opcache.preload discover always-needed symbols +class_exists(NativeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class NativeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private array $options; + private AbstractProxy|\SessionHandlerInterface|null $handler; + private ?MetadataBag $metaBag; + private bool $secure; + + /** + * @see NativeSessionStorage constructor. + */ + public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false) + { + $this->options = $options; + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag); + if ($this->secure && $request?->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 0000000..8a8c50c --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +/** + * Allows session to be started by PHP and managed by Symfony. + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + public function __construct(AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + public function start(): bool + { + if ($this->started) { + return true; + } + + $this->loadSession(); + + return true; + } + + public function clear(): void + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php new file mode 100644 index 0000000..aa4f800 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +// Help opcache.preload discover always-needed symbols +class_exists(PhpBridgeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private AbstractProxy|\SessionHandlerInterface|null $handler; + private ?MetadataBag $metaBag; + private bool $secure; + + public function __construct(AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false) + { + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag); + if ($this->secure && $request?->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 0000000..c3a0278 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * @author Drak + */ +abstract class AbstractProxy +{ + protected bool $wrapper = false; + + protected ?string $saveHandlerName = null; + + /** + * Gets the session.save_handler name. + */ + public function getSaveHandlerName(): ?string + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + */ + public function isSessionHandlerInterface(): bool + { + return $this instanceof \SessionHandlerInterface; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + */ + public function isWrapper(): bool + { + return $this->wrapper; + } + + /** + * Has a session started? + */ + public function isActive(): bool + { + return \PHP_SESSION_ACTIVE === session_status(); + } + + /** + * Gets the session ID. + */ + public function getId(): string + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @throws \LogicException + */ + public function setId(string $id): void + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session.'); + } + + session_id($id); + } + + /** + * Gets the session name. + */ + public function getName(): string + { + return session_name(); + } + + /** + * Sets the session name. + * + * @throws \LogicException + */ + public function setName(string $name): void + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session.'); + } + + session_name($name); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 0000000..b8df97f --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; + +/** + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + protected \SessionHandlerInterface $handler; + + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = $handler instanceof \SessionHandler; + $this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user'; + } + + public function getHandler(): \SessionHandlerInterface + { + return $this->handler; + } + + // \SessionHandlerInterface + + public function open(string $savePath, string $sessionName): bool + { + return $this->handler->open($savePath, $sessionName); + } + + public function close(): bool + { + return $this->handler->close(); + } + + public function read(#[\SensitiveParameter] string $sessionId): string|false + { + return $this->handler->read($sessionId); + } + + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return $this->handler->write($sessionId, $data); + } + + public function destroy(#[\SensitiveParameter] string $sessionId): bool + { + return $this->handler->destroy($sessionId); + } + + public function gc(int $maxlifetime): int|false + { + return $this->handler->gc($maxlifetime); + } + + public function validateId(#[\SensitiveParameter] string $sessionId): bool + { + return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); + } + + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool + { + return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php new file mode 100644 index 0000000..d03f0da --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + */ +interface SessionStorageFactoryInterface +{ + /** + * Creates a new instance of SessionStorageInterface. + */ + public function createStorage(?Request $request): SessionStorageInterface; +} diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000..c51850d --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @throws \RuntimeException if something goes wrong starting the session + */ + public function start(): bool; + + /** + * Checks if the session is started. + */ + public function isStarted(): bool; + + /** + * Returns the session ID. + */ + public function getId(): string; + + /** + * Sets the session ID. + */ + public function setId(string $id): void; + + /** + * Returns the session name. + */ + public function getName(): string; + + /** + * Sets the session name. + */ + public function setName(string $name): void; + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * Care: When regenerating the session ID no locking is involved in PHP's + * session design. See https://bugs.php.net/61470 for a discussion. + * So you must make sure the regenerated session is saved BEFORE sending the + * headers with the new ID. Symfony's HttpKernel offers a listener for this. + * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. + * Otherwise session data could get lost again for concurrent requests with the + * new ID. One result could be that you get logged out after just logging in. + * + * @param bool $destroy Destroy session when regenerating? + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @throws \RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool; + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case + * it should actually persist the session data if required. + * + * @throws \RuntimeException if the session is saved without being started, or if the session + * is already closed + */ + public function save(): void; + + /** + * Clear all session data in memory. + */ + public function clear(): void; + + /** + * Gets a SessionBagInterface by name. + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag(string $name): SessionBagInterface; + + /** + * Registers a SessionBagInterface for use. + */ + public function registerBag(SessionBagInterface $bag): void; + + public function getMetadataBag(): MetadataBag; +} diff --git a/vendor/symfony/http-foundation/StreamedJsonResponse.php b/vendor/symfony/http-foundation/StreamedJsonResponse.php new file mode 100644 index 0000000..5b20ce9 --- /dev/null +++ b/vendor/symfony/http-foundation/StreamedJsonResponse.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedJsonResponse represents a streamed HTTP response for JSON. + * + * A StreamedJsonResponse uses a structure and generics to create an + * efficient resource-saving JSON response. + * + * It is recommended to use flush() function after a specific number of items to directly stream the data. + * + * @see flush() + * + * @author Alexander Schranz + * + * Example usage: + * + * function loadArticles(): \Generator + * // some streamed loading + * yield ['title' => 'Article 1']; + * yield ['title' => 'Article 2']; + * yield ['title' => 'Article 3']; + * // recommended to use flush() after every specific number of items + * }), + * + * $response = new StreamedJsonResponse( + * // json structure with generators in which will be streamed + * [ + * '_embedded' => [ + * 'articles' => loadArticles(), // any generator which you want to stream as list of data + * ], + * ], + * ); + */ +class StreamedJsonResponse extends StreamedResponse +{ + private const PLACEHOLDER = '__symfony_json__'; + + /** + * @param mixed[] $data JSON Data containing PHP generators which will be streamed as list of data or a Generator + * @param int $status The HTTP status code (200 "OK" by default) + * @param array $headers An array of HTTP headers + * @param int $encodingOptions Flags for the json_encode() function + */ + public function __construct( + private readonly iterable $data, + int $status = 200, + array $headers = [], + private int $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS, + ) { + parent::__construct($this->stream(...), $status, $headers); + + if (!$this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + } + + private function stream(): void + { + $jsonEncodingOptions = \JSON_THROW_ON_ERROR | $this->encodingOptions; + $keyEncodingOptions = $jsonEncodingOptions & ~\JSON_NUMERIC_CHECK; + + $this->streamData($this->data, $jsonEncodingOptions, $keyEncodingOptions); + } + + private function streamData(mixed $data, int $jsonEncodingOptions, int $keyEncodingOptions): void + { + if (\is_array($data)) { + $this->streamArray($data, $jsonEncodingOptions, $keyEncodingOptions); + + return; + } + + if (is_iterable($data) && !$data instanceof \JsonSerializable) { + $this->streamIterable($data, $jsonEncodingOptions, $keyEncodingOptions); + + return; + } + + echo json_encode($data, $jsonEncodingOptions); + } + + private function streamArray(array $data, int $jsonEncodingOptions, int $keyEncodingOptions): void + { + $generators = []; + + array_walk_recursive($data, function (&$item, $key) use (&$generators) { + if (self::PLACEHOLDER === $key) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $key; + } + + // generators should be used but for better DX all kind of Traversable and objects are supported + if (\is_object($item)) { + $generators[] = $item; + $item = self::PLACEHOLDER; + } elseif (self::PLACEHOLDER === $item) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $item; + } + }); + + $jsonParts = explode('"'.self::PLACEHOLDER.'"', json_encode($data, $jsonEncodingOptions)); + + foreach ($generators as $index => $generator) { + // send first and between parts of the structure + echo $jsonParts[$index]; + + $this->streamData($generator, $jsonEncodingOptions, $keyEncodingOptions); + } + + // send last part of the structure + echo $jsonParts[array_key_last($jsonParts)]; + } + + private function streamIterable(iterable $iterable, int $jsonEncodingOptions, int $keyEncodingOptions): void + { + $isFirstItem = true; + $startTag = '['; + + foreach ($iterable as $key => $item) { + if ($isFirstItem) { + $isFirstItem = false; + // depending on the first elements key the generator is detected as a list or map + // we can not check for a whole list or map because that would hurt the performance + // of the streamed response which is the main goal of this response class + if (0 !== $key) { + $startTag = '{'; + } + + echo $startTag; + } else { + // if not first element of the generic, a separator is required between the elements + echo ','; + } + + if ('{' === $startTag) { + echo json_encode((string) $key, $keyEncodingOptions).':'; + } + + $this->streamData($item, $jsonEncodingOptions, $keyEncodingOptions); + } + + if ($isFirstItem) { // indicates that the generator was empty + echo '['; + } + + echo '[' === $startTag ? ']' : '}'; + } +} diff --git a/vendor/symfony/http-foundation/StreamedResponse.php b/vendor/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 0000000..3acaade --- /dev/null +++ b/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() function + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + */ +class StreamedResponse extends Response +{ + protected ?\Closure $callback = null; + protected bool $streamed = false; + + private bool $headersSent = false; + + /** + * @param int $status The HTTP status code (200 "OK" by default) + */ + public function __construct(?callable $callback = null, int $status = 200, array $headers = []) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + $this->headersSent = false; + } + + /** + * Sets the PHP callback associated with this Response. + * + * @return $this + */ + public function setCallback(callable $callback): static + { + $this->callback = $callback(...); + + return $this; + } + + public function getCallback(): ?\Closure + { + if (!isset($this->callback)) { + return null; + } + + return ($this->callback)(...); + } + + /** + * This method only sends the headers once. + * + * @param positive-int|null $statusCode The status code to use, override the statusCode property if set and not null + * + * @return $this + */ + public function sendHeaders(?int $statusCode = null): static + { + if ($this->headersSent) { + return $this; + } + + if ($statusCode < 100 || $statusCode >= 200) { + $this->headersSent = true; + } + + return parent::sendHeaders($statusCode); + } + + /** + * This method only sends the content once. + * + * @return $this + */ + public function sendContent(): static + { + if ($this->streamed) { + return $this; + } + + $this->streamed = true; + + if (!isset($this->callback)) { + throw new \LogicException('The Response callback must be set.'); + } + + ($this->callback)(); + + return $this; + } + + /** + * @return $this + * + * @throws \LogicException when the content is not null + */ + public function setContent(?string $content): static + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + + $this->streamed = true; + + return $this; + } + + public function getContent(): string|false + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php b/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php new file mode 100644 index 0000000..6e11426 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; + +final class RequestAttributeValueSame extends Constraint +{ + private string $name; + private string $value; + + public function __construct(string $name, string $value) + { + $this->name = $name; + $this->value = $value; + } + + public function toString(): string + { + return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value); + } + + /** + * @param Request $request + */ + protected function matches($request): bool + { + return $this->value === $request->attributes->get($this->name); + } + + /** + * @param Request $request + */ + protected function failureDescription($request): string + { + return 'the Request '.$this->toString(); + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php new file mode 100644 index 0000000..768007b --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseCookieValueSame extends Constraint +{ + private string $name; + private string $value; + private string $path; + private ?string $domain; + + public function __construct(string $name, string $value, string $path = '/', ?string $domain = null) + { + $this->name = $name; + $this->value = $value; + $this->path = $path; + $this->domain = $domain; + } + + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + $str .= sprintf(' with value "%s"', $this->value); + + return $str; + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + $cookie = $this->getCookie($response); + if (!$cookie) { + return false; + } + + return $this->value === (string) $cookie->getValue(); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + protected function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain); + + return reset($filteredCookies) ?: null; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php new file mode 100644 index 0000000..c75321f --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Asserts that the response is in the given format. + * + * @author Kévin Dunglas + */ +final class ResponseFormatSame extends Constraint +{ + private Request $request; + private ?string $format; + + public function __construct( + Request $request, + ?string $format, + private readonly bool $verbose = true, + ) { + $this->request = $request; + $this->format = $format; + } + + public function toString(): string + { + return 'format is '.($this->format ?? 'null'); + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + return $this->format === $this->request->getFormat($response->headers->get('Content-Type')); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + */ + protected function additionalFailureDescription($response): string + { + return $this->verbose ? (string) $response : explode("\r\n\r\n", (string) $response)[0]; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php new file mode 100644 index 0000000..8eccea9 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasCookie extends Constraint +{ + private string $name; + private string $path; + private ?string $domain; + + public function __construct(string $name, string $path = '/', ?string $domain = null) + { + $this->name = $name; + $this->path = $path; + $this->domain = $domain; + } + + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + + return $str; + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + return null !== $this->getCookie($response); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + private function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain); + + return reset($filteredCookies) ?: null; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php new file mode 100644 index 0000000..08522c8 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasHeader extends Constraint +{ + private string $headerName; + + public function __construct(string $headerName) + { + $this->headerName = $headerName; + } + + public function toString(): string + { + return sprintf('has header "%s"', $this->headerName); + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + return $response->headers->has($this->headerName); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php new file mode 100644 index 0000000..9286ec7 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHeaderLocationSame extends Constraint +{ + public function __construct(private Request $request, private string $expectedValue) + { + } + + public function toString(): string + { + return sprintf('has header "Location" matching "%s"', $this->expectedValue); + } + + protected function matches($other): bool + { + if (!$other instanceof Response) { + return false; + } + + $location = $other->headers->get('Location'); + + if (null === $location) { + return false; + } + + return $this->toFullUrl($this->expectedValue) === $this->toFullUrl($location); + } + + protected function failureDescription($other): string + { + return 'the Response '.$this->toString(); + } + + private function toFullUrl(string $url): string + { + if (null === parse_url($url, \PHP_URL_PATH)) { + $url .= '/'; + } + + if (str_starts_with($url, '//')) { + return sprintf('%s:%s', $this->request->getScheme(), $url); + } + + if (str_starts_with($url, '/')) { + return $this->request->getSchemeAndHttpHost().$url; + } + + return $url; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php new file mode 100644 index 0000000..8141df9 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHeaderSame extends Constraint +{ + private string $headerName; + private string $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + public function toString(): string + { + return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + return $this->expectedValue === $response->headers->get($this->headerName, null); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php new file mode 100644 index 0000000..b7ae15e --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsRedirected extends Constraint +{ + /** + * @param bool $verbose If true, the entire response is printed on failure. If false, the response body is omitted. + */ + public function __construct(private readonly bool $verbose = true) + { + } + + public function toString(): string + { + return 'is redirected'; + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + return $response->isRedirect(); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + */ + protected function additionalFailureDescription($response): string + { + return $this->verbose ? (string) $response : explode("\r\n\r\n", (string) $response)[0]; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php new file mode 100644 index 0000000..94a65ed --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsSuccessful extends Constraint +{ + /** + * @param bool $verbose If true, the entire response is printed on failure. If false, the response body is omitted. + */ + public function __construct(private readonly bool $verbose = true) + { + } + + public function toString(): string + { + return 'is successful'; + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + return $response->isSuccessful(); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + */ + protected function additionalFailureDescription($response): string + { + return $this->verbose ? (string) $response : explode("\r\n\r\n", (string) $response)[0]; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php new file mode 100644 index 0000000..799d558 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsUnprocessable extends Constraint +{ + /** + * @param bool $verbose If true, the entire response is printed on failure. If false, the response body is omitted. + */ + public function __construct(private readonly bool $verbose = true) + { + } + + public function toString(): string + { + return 'is unprocessable'; + } + + /** + * @param Response $other + */ + protected function matches($other): bool + { + return Response::HTTP_UNPROCESSABLE_ENTITY === $other->getStatusCode(); + } + + /** + * @param Response $other + */ + protected function failureDescription($other): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + */ + protected function additionalFailureDescription($response): string + { + return $this->verbose ? (string) $response : explode("\r\n\r\n", (string) $response)[0]; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php new file mode 100644 index 0000000..5ca6373 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseStatusCodeSame extends Constraint +{ + private int $statusCode; + + public function __construct(int $statusCode, private readonly bool $verbose = true) + { + $this->statusCode = $statusCode; + } + + public function toString(): string + { + return 'status code is '.$this->statusCode; + } + + /** + * @param Response $response + */ + protected function matches($response): bool + { + return $this->statusCode === $response->getStatusCode(); + } + + /** + * @param Response $response + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + */ + protected function additionalFailureDescription($response): string + { + return $this->verbose ? (string) $response : explode("\r\n\r\n", (string) $response)[0]; + } +} diff --git a/vendor/symfony/http-foundation/UriSigner.php b/vendor/symfony/http-foundation/UriSigner.php new file mode 100644 index 0000000..4415026 --- /dev/null +++ b/vendor/symfony/http-foundation/UriSigner.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\LogicException; + +/** + * @author Fabien Potencier + */ +class UriSigner +{ + private string $secret; + private string $hashParameter; + private string $expirationParameter; + + /** + * @param string $hashParameter Query string parameter to use + * @param string $expirationParameter Query string parameter to use for expiration + */ + public function __construct(#[\SensitiveParameter] string $secret, string $hashParameter = '_hash', string $expirationParameter = '_expiration') + { + if (!$secret) { + throw new \InvalidArgumentException('A non-empty secret is required.'); + } + + $this->secret = $secret; + $this->hashParameter = $hashParameter; + $this->expirationParameter = $expirationParameter; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding the query string parameter + * which value depends on the URI and the secret. + * + * @param \DateTimeInterface|\DateInterval|int|null $expiration The expiration for the given URI. + * If $expiration is a \DateTimeInterface, it's expected to be the exact date + time. + * If $expiration is a \DateInterval, the interval is added to "now" to get the date + time. + * If $expiration is an int, it's expected to be a timestamp in seconds of the exact date + time. + * If $expiration is null, no expiration. + * + * The expiration is added as a query string parameter. + */ + public function sign(string $uri/*, \DateTimeInterface|\DateInterval|int|null $expiration = null*/): string + { + $expiration = null; + + if (1 < \func_num_args()) { + $expiration = func_get_arg(1); + } + + if (null !== $expiration && !$expiration instanceof \DateTimeInterface && !$expiration instanceof \DateInterval && !\is_int($expiration)) { + throw new \TypeError(sprintf('The second argument of %s() must be an instance of %s or %s, an integer or null (%s given).', __METHOD__, \DateTimeInterface::class, \DateInterval::class, get_debug_type($expiration))); + } + + $url = parse_url($uri); + $params = []; + + if (isset($url['query'])) { + parse_str($url['query'], $params); + } + + if (isset($params[$this->hashParameter])) { + throw new LogicException(sprintf('URI query parameter conflict: parameter name "%s" is reserved.', $this->hashParameter)); + } + + if (isset($params[$this->expirationParameter])) { + throw new LogicException(sprintf('URI query parameter conflict: parameter name "%s" is reserved.', $this->expirationParameter)); + } + + if (null !== $expiration) { + $params[$this->expirationParameter] = $this->getExpirationTime($expiration); + } + + $uri = $this->buildUrl($url, $params); + $params[$this->hashParameter] = $this->computeHash($uri); + + return $this->buildUrl($url, $params); + } + + /** + * Checks that a URI contains the correct hash. + * Also checks if the URI has not expired (If you used expiration during signing). + */ + public function check(string $uri): bool + { + $url = parse_url($uri); + $params = []; + + if (isset($url['query'])) { + parse_str($url['query'], $params); + } + + if (empty($params[$this->hashParameter])) { + return false; + } + + $hash = $params[$this->hashParameter]; + unset($params[$this->hashParameter]); + + if (!hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash)) { + return false; + } + + if ($expiration = $params[$this->expirationParameter] ?? false) { + return time() < $expiration; + } + + return true; + } + + public function checkRequest(Request $request): bool + { + $qs = ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''; + + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + return $this->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$qs); + } + + private function computeHash(string $uri): string + { + return base64_encode(hash_hmac('sha256', $uri, $this->secret, true)); + } + + private function buildUrl(array $url, array $params = []): string + { + ksort($params, \SORT_STRING); + $url['query'] = http_build_query($params, '', '&'); + + $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; + $host = $url['host'] ?? ''; + $port = isset($url['port']) ? ':'.$url['port'] : ''; + $user = $url['user'] ?? ''; + $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = $url['path'] ?? ''; + $query = $url['query'] ? '?'.$url['query'] : ''; + $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; + + return $scheme.$user.$pass.$host.$port.$path.$query.$fragment; + } + + private function getExpirationTime(\DateTimeInterface|\DateInterval|int $expiration): string + { + if ($expiration instanceof \DateTimeInterface) { + return $expiration->format('U'); + } + + if ($expiration instanceof \DateInterval) { + return \DateTimeImmutable::createFromFormat('U', time())->add($expiration)->format('U'); + } + + return (string) $expiration; + } +} diff --git a/vendor/symfony/http-foundation/UrlHelper.php b/vendor/symfony/http-foundation/UrlHelper.php new file mode 100644 index 0000000..f971cf6 --- /dev/null +++ b/vendor/symfony/http-foundation/UrlHelper.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * A helper service for manipulating URLs within and outside the request scope. + * + * @author Valentin Udaltsov + */ +final class UrlHelper +{ + public function __construct( + private RequestStack $requestStack, + private RequestContextAwareInterface|RequestContext|null $requestContext = null, + ) { + } + + public function getAbsoluteUrl(string $path): string + { + if (str_contains($path, '://') || str_starts_with($path, '//')) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $this->getAbsoluteUrlFromContext($path); + } + + if ('#' === $path[0]) { + $path = $request->getRequestUri().$path; + } elseif ('?' === $path[0]) { + $path = $request->getPathInfo().$path; + } + + if (!$path || '/' !== $path[0]) { + $prefix = $request->getPathInfo(); + $last = \strlen($prefix) - 1; + if ($last !== $pos = strrpos($prefix, '/')) { + $prefix = substr($prefix, 0, $pos).'/'; + } + + return $request->getUriForPath($prefix.$path); + } + + return $request->getSchemeAndHttpHost().$path; + } + + public function getRelativePath(string $path): string + { + if (str_contains($path, '://') || str_starts_with($path, '//')) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $path; + } + + return $request->getRelativeUriForPath($path); + } + + private function getAbsoluteUrlFromContext(string $path): string + { + if (null === $context = $this->requestContext) { + return $path; + } + + if ($context instanceof RequestContextAwareInterface) { + $context = $context->getContext(); + } + + if ('' === $host = $context->getHost()) { + return $path; + } + + $scheme = $context->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 !== $context->getHttpPort()) { + $port = ':'.$context->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) { + $port = ':'.$context->getHttpsPort(); + } + + if ('#' === $path[0]) { + $queryString = $context->getQueryString(); + $path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path; + } elseif ('?' === $path[0]) { + $path = $context->getPathInfo().$path; + } + + if ('/' !== $path[0]) { + $path = rtrim($context->getBaseUrl(), '/').'/'.$path; + } + + return $scheme.'://'.$host.$port.$path; + } +} diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json new file mode 100644 index 0000000..6e88fc1 --- /dev/null +++ b/vendor/symfony/http-foundation/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Defines an object-oriented layer for the HTTP specification", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/http-kernel/Attribute/AsController.php b/vendor/symfony/http-kernel/Attribute/AsController.php new file mode 100644 index 0000000..0f2c91d --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/AsController.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * Autoconfigures controllers as services by applying + * the `controller.service_arguments` tag to them. + * + * This enables injecting services as method arguments in addition + * to other conventional dependency injection strategies. + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_FUNCTION)] +class AsController +{ + public function __construct() + { + } +} diff --git a/vendor/symfony/http-kernel/Attribute/AsTargetedValueResolver.php b/vendor/symfony/http-kernel/Attribute/AsTargetedValueResolver.php new file mode 100644 index 0000000..0635566 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/AsTargetedValueResolver.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * Service tag to autoconfigure targeted value resolvers. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsTargetedValueResolver +{ + /** + * @param string|null $name The name with which the resolver can be targeted + */ + public function __construct(public readonly ?string $name = null) + { + } +} diff --git a/vendor/symfony/http-kernel/Attribute/Cache.php b/vendor/symfony/http-kernel/Attribute/Cache.php new file mode 100644 index 0000000..19d13e9 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/Cache.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * Describes the default HTTP cache headers on controllers. + * Headers defined in the Cache attribute are ignored if they are already set + * by the controller. + * + * @see https://symfony.com/doc/current/http_cache.html#making-your-responses-http-cacheable + * + * @author Fabien Potencier + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +final class Cache +{ + public function __construct( + /** + * The expiration date as a valid date for the strtotime() function. + */ + public ?string $expires = null, + + /** + * The number of seconds that the response is considered fresh by a private + * cache like a web browser. + */ + public int|string|null $maxage = null, + + /** + * The number of seconds that the response is considered fresh by a public + * cache like a reverse proxy cache. + */ + public int|string|null $smaxage = null, + + /** + * If true, the contents will be stored in a public cache and served to all + * the next requests. + */ + public ?bool $public = null, + + /** + * If true, the response is not served stale by a cache in any circumstance + * without first revalidating with the origin. + */ + public bool $mustRevalidate = false, + + /** + * Set "Vary" header. + * + * Example: + * ['Accept-Encoding', 'User-Agent'] + * + * @see https://symfony.com/doc/current/http_cache/cache_vary.html + * + * @var string[] + */ + public array $vary = [], + + /** + * An expression to compute the Last-Modified HTTP header. + * + * The expression is evaluated by the ExpressionLanguage component, it + * receives all the request attributes and the resolved controller arguments. + * + * The result of the expression must be a DateTimeInterface. + */ + public ?string $lastModified = null, + + /** + * An expression to compute the ETag HTTP header. + * + * The expression is evaluated by the ExpressionLanguage component, it + * receives all the request attributes and the resolved controller arguments. + * + * The result must be a string that will be hashed. + */ + public ?string $etag = null, + + /** + * max-stale Cache-Control header + * It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...). + */ + public int|string|null $maxStale = null, + + /** + * stale-while-revalidate Cache-Control header + * It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...). + */ + public int|string|null $staleWhileRevalidate = null, + + /** + * stale-if-error Cache-Control header + * It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...). + */ + public int|string|null $staleIfError = null, + ) { + } +} diff --git a/vendor/symfony/http-kernel/Attribute/MapDateTime.php b/vendor/symfony/http-kernel/Attribute/MapDateTime.php new file mode 100644 index 0000000..db6b4d6 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/MapDateTime.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; + +/** + * Controller parameter tag to configure DateTime arguments. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapDateTime extends ValueResolver +{ + /** + * @param string|null $format The DateTime format to use, @see https://php.net/datetime.format + * @param bool $disabled Whether this value resolver is disabled; this allows to enable a value resolver globally while disabling it in specific cases + * @param class-string|string $resolver The name of the resolver to use + */ + public function __construct( + public readonly ?string $format = null, + bool $disabled = false, + string $resolver = DateTimeValueResolver::class, + ) { + parent::__construct($resolver, $disabled); + } +} diff --git a/vendor/symfony/http-kernel/Attribute/MapQueryParameter.php b/vendor/symfony/http-kernel/Attribute/MapQueryParameter.php new file mode 100644 index 0000000..5b147b8 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/MapQueryParameter.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; + +/** + * Can be used to pass a query parameter to a controller argument. + * + * @author Ruud Kamphuis + * @author Ionut Enache + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +final class MapQueryParameter extends ValueResolver +{ + /** + * @see https://php.net/filter.filters.validate for filter, flags and options + * + * @param string|null $name The name of the query parameter; if null, the name of the argument in the controller will be used + * @param (FILTER_VALIDATE_*)|(FILTER_SANITIZE_*)|null $filter The filter to pass to "filter_var()" + * @param int-mask-of<(FILTER_FLAG_*)|FILTER_NULL_ON_FAILURE> $flags The flags to pass to "filter_var()" + * @param array $options The options to pass to "filter_var()" + * @param class-string|string $resolver The name of the resolver to use + */ + public function __construct( + public ?string $name = null, + public ?int $filter = null, + public int $flags = 0, + public array $options = [], + string $resolver = QueryParameterValueResolver::class, + public int $validationFailedStatusCode = Response::HTTP_NOT_FOUND, + ) { + parent::__construct($resolver); + } +} diff --git a/vendor/symfony/http-kernel/Attribute/MapQueryString.php b/vendor/symfony/http-kernel/Attribute/MapQueryString.php new file mode 100644 index 0000000..dfff4dd --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/MapQueryString.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Controller parameter tag to map the query string of the request to typed object and validate it. + * + * @author Konstantin Myakshin + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapQueryString extends ValueResolver +{ + public ArgumentMetadata $metadata; + + /** + * @param array $serializationContext The serialization context to use when deserializing the query string + * @param string|GroupSequence|array|null $validationGroups The validation groups to use when validating the query string mapping + * @param class-string $resolver The class name of the resolver to use + * @param int $validationFailedStatusCode The HTTP code to return if the validation fails + */ + public function __construct( + public readonly array $serializationContext = [], + public readonly string|GroupSequence|array|null $validationGroups = null, + string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_NOT_FOUND, + ) { + parent::__construct($resolver); + } +} diff --git a/vendor/symfony/http-kernel/Attribute/MapRequestPayload.php b/vendor/symfony/http-kernel/Attribute/MapRequestPayload.php new file mode 100644 index 0000000..cf08638 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/MapRequestPayload.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Controller parameter tag to map the request content to typed object and validate it. + * + * @author Konstantin Myakshin + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapRequestPayload extends ValueResolver +{ + public ArgumentMetadata $metadata; + + /** + * @param array|string|null $acceptFormat The payload formats to accept (i.e. "json", "xml") + * @param array $serializationContext The serialization context to use when deserializing the payload + * @param string|GroupSequence|array|null $validationGroups The validation groups to use when validating the query string mapping + * @param class-string $resolver The class name of the resolver to use + * @param int $validationFailedStatusCode The HTTP code to return if the validation fails + * @param class-string|string|null $type The element type for array deserialization + */ + public function __construct( + public readonly array|string|null $acceptFormat = null, + public readonly array $serializationContext = [], + public readonly string|GroupSequence|array|null $validationGroups = null, + string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY, + public readonly ?string $type = null, + ) { + parent::__construct($resolver); + } +} diff --git a/vendor/symfony/http-kernel/Attribute/MapUploadedFile.php b/vendor/symfony/http-kernel/Attribute/MapUploadedFile.php new file mode 100644 index 0000000..f90b511 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/MapUploadedFile.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Validator\Constraint; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapUploadedFile extends ValueResolver +{ + public ArgumentMetadata $metadata; + + public function __construct( + /** @var Constraint|array|null */ + public Constraint|array|null $constraints = null, + public ?string $name = null, + string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY, + ) { + parent::__construct($resolver); + } +} diff --git a/vendor/symfony/http-kernel/Attribute/ValueResolver.php b/vendor/symfony/http-kernel/Attribute/ValueResolver.php new file mode 100644 index 0000000..e295965 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/ValueResolver.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; + +/** + * Defines which value resolver should be used for a given parameter. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)] +class ValueResolver +{ + /** + * @param class-string|string $resolver The class name of the resolver to use + * @param bool $disabled Whether this value resolver is disabled; this allows to enable a value resolver globally while disabling it in specific cases + */ + public function __construct( + public string $resolver, + public bool $disabled = false, + ) { + } +} diff --git a/vendor/symfony/http-kernel/Attribute/WithHttpStatus.php b/vendor/symfony/http-kernel/Attribute/WithHttpStatus.php new file mode 100644 index 0000000..18aa624 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/WithHttpStatus.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * Defines the HTTP status code applied to an exception. + * + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class WithHttpStatus +{ + /** + * @param int $statusCode The HTTP status code to use + * @param array $headers The HTTP headers to add to the response + */ + public function __construct( + public readonly int $statusCode, + public readonly array $headers = [], + ) { + } +} diff --git a/vendor/symfony/http-kernel/Attribute/WithLogLevel.php b/vendor/symfony/http-kernel/Attribute/WithLogLevel.php new file mode 100644 index 0000000..534c802 --- /dev/null +++ b/vendor/symfony/http-kernel/Attribute/WithLogLevel.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Psr\Log\LogLevel; + +/** + * Defines the log level applied to an exception. + * + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class WithLogLevel +{ + /** + * @param LogLevel::* $level The level to use to log the exception + */ + public function __construct(public readonly string $level) + { + if (!\defined('Psr\Log\LogLevel::'.strtoupper($this->level))) { + throw new \InvalidArgumentException(sprintf('Invalid log level "%s".', $this->level)); + } + } +} diff --git a/vendor/symfony/http-kernel/Bundle/AbstractBundle.php b/vendor/symfony/http-kernel/Bundle/AbstractBundle.php new file mode 100644 index 0000000..76f314a --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/AbstractBundle.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurableExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * A Bundle that provides configuration hooks. + * + * @author Yonel Ceruto + */ +abstract class AbstractBundle extends Bundle implements ConfigurableExtensionInterface +{ + protected string $extensionAlias = ''; + + public function configure(DefinitionConfigurator $definition): void + { + } + + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + } + + public function getContainerExtension(): ?ExtensionInterface + { + if ('' === $this->extensionAlias) { + $this->extensionAlias = Container::underscore(preg_replace('/Bundle$/', '', $this->getName())); + } + + return $this->extension ??= new BundleExtension($this, $this->extensionAlias); + } + + public function getPath(): string + { + if (!isset($this->path)) { + $reflected = new \ReflectionObject($this); + // assume the modern directory structure by default + $this->path = \dirname($reflected->getFileName(), 2); + } + + return $this->path; + } +} diff --git a/vendor/symfony/http-kernel/Bundle/Bundle.php b/vendor/symfony/http-kernel/Bundle/Bundle.php new file mode 100644 index 0000000..b1f7c7c --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/Bundle.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\Console\Application; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * An implementation of BundleInterface that adds a few conventions for DependencyInjection extensions. + * + * @author Fabien Potencier + */ +abstract class Bundle implements BundleInterface +{ + protected string $name; + protected ExtensionInterface|false|null $extension = null; + protected string $path; + protected ?ContainerInterface $container; + + private string $namespace; + + /** + * @return void + */ + public function boot() + { + } + + /** + * @return void + */ + public function shutdown() + { + } + + /** + * This method can be overridden to register compilation passes, + * other extensions, ... + * + * @return void + */ + public function build(ContainerBuilder $container) + { + } + + /** + * Returns the bundle's container extension. + * + * @throws \LogicException + */ + public function getContainerExtension(): ?ExtensionInterface + { + if (!isset($this->extension)) { + $extension = $this->createContainerExtension(); + + if (null !== $extension) { + if (!$extension instanceof ExtensionInterface) { + throw new \LogicException(sprintf('Extension "%s" must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_debug_type($extension))); + } + + // check naming convention + $basename = preg_replace('/Bundle$/', '', $this->getName()); + $expectedAlias = Container::underscore($basename); + + if ($expectedAlias != $extension->getAlias()) { + throw new \LogicException(sprintf('Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.', $expectedAlias, $extension->getAlias())); + } + + $this->extension = $extension; + } else { + $this->extension = false; + } + } + + return $this->extension ?: null; + } + + public function getNamespace(): string + { + if (!isset($this->namespace)) { + $this->parseClassName(); + } + + return $this->namespace; + } + + public function getPath(): string + { + if (!isset($this->path)) { + $reflected = new \ReflectionObject($this); + $this->path = \dirname($reflected->getFileName()); + } + + return $this->path; + } + + /** + * Returns the bundle name (the class short name). + */ + final public function getName(): string + { + if (!isset($this->name)) { + $this->parseClassName(); + } + + return $this->name; + } + + /** + * @return void + */ + public function registerCommands(Application $application) + { + } + + /** + * Returns the bundle's container extension class. + */ + protected function getContainerExtensionClass(): string + { + $basename = preg_replace('/Bundle$/', '', $this->getName()); + + return $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension'; + } + + /** + * Creates the bundle's container extension. + */ + protected function createContainerExtension(): ?ExtensionInterface + { + return class_exists($class = $this->getContainerExtensionClass()) ? new $class() : null; + } + + private function parseClassName(): void + { + $pos = strrpos(static::class, '\\'); + $this->namespace = false === $pos ? '' : substr(static::class, 0, $pos); + $this->name ??= false === $pos ? static::class : substr(static::class, $pos + 1); + } + + public function setContainer(?ContainerInterface $container): void + { + $this->container = $container; + } +} diff --git a/vendor/symfony/http-kernel/Bundle/BundleExtension.php b/vendor/symfony/http-kernel/Bundle/BundleExtension.php new file mode 100644 index 0000000..8392218 --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/BundleExtension.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\Config\Definition\Configuration; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurableExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Extension\ExtensionTrait; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +/** + * @author Yonel Ceruto + * + * @internal + */ +class BundleExtension extends Extension implements PrependExtensionInterface +{ + use ExtensionTrait; + + public function __construct( + private ConfigurableExtensionInterface $subject, + private string $alias, + ) { + } + + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + { + return new Configuration($this->subject, $container, $this->getAlias()); + } + + public function getAlias(): string + { + return $this->alias; + } + + public function prepend(ContainerBuilder $container): void + { + $callback = function (ContainerConfigurator $configurator) use ($container) { + $this->subject->prependExtension($configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this->subject, true); + } + + public function load(array $configs, ContainerBuilder $container): void + { + $config = $this->processConfiguration($this->getConfiguration([], $container), $configs); + + $callback = function (ContainerConfigurator $configurator) use ($config, $container) { + $this->subject->loadExtension($config, $configurator, $container); + }; + + $this->executeConfiguratorCallback($container, $callback, $this->subject); + } +} diff --git a/vendor/symfony/http-kernel/Bundle/BundleInterface.php b/vendor/symfony/http-kernel/Bundle/BundleInterface.php new file mode 100644 index 0000000..36502e8 --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/BundleInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * BundleInterface. + * + * @author Fabien Potencier + */ +interface BundleInterface +{ + /** + * Boots the Bundle. + * + * @return void + */ + public function boot(); + + /** + * Shutdowns the Bundle. + * + * @return void + */ + public function shutdown(); + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * @return void + */ + public function build(ContainerBuilder $container); + + /** + * Returns the container extension that should be implicitly loaded. + */ + public function getContainerExtension(): ?ExtensionInterface; + + /** + * Returns the bundle name (the class short name). + */ + public function getName(): string; + + /** + * Gets the Bundle namespace. + */ + public function getNamespace(): string; + + /** + * Gets the Bundle directory path. + * + * The path should always be returned as a Unix path (with /). + */ + public function getPath(): string; + + public function setContainer(?ContainerInterface $container): void; +} diff --git a/vendor/symfony/http-kernel/CHANGELOG.md b/vendor/symfony/http-kernel/CHANGELOG.md new file mode 100644 index 0000000..eb35d73 --- /dev/null +++ b/vendor/symfony/http-kernel/CHANGELOG.md @@ -0,0 +1,412 @@ +CHANGELOG +========= + +7.1 +--- + + * Add method `isKernelTerminating()` to `ExceptionEvent` that allows to check if an exception was thrown while the kernel is being terminated + * Add `HttpException::fromStatusCode()` + * Add `$validationFailedStatusCode` argument to `#[MapQueryParameter]` that allows setting a custom HTTP status code when validation fails + * Add `NearMissValueResolverException` to let value resolvers report when an argument could be under their watch but failed to be resolved + * Add `$type` argument to `#[MapRequestPayload]` that allows mapping a list of items + * The `Extension` class is marked as internal, extend the `Extension` class from the DependencyInjection component instead + * Deprecate `Extension::addAnnotatedClassesToCompile()` + * Deprecate `AddAnnotatedClassesToCachePass` + * Deprecate the `setAnnotatedClassCache()` and `getAnnotatedClassesToCompile()` methods of the `Kernel` class + * Add `#[MapUploadedFile]` attribute to fetch, validate, and inject uploaded files into controller arguments + +7.0 +--- + + * Add argument `$reflector` to `ArgumentResolverInterface::getArguments()` and `ArgumentMetadataFactoryInterface::createArgumentMetadata()` + * Remove `ArgumentValueResolverInterface`, use `ValueResolverInterface` instead + * Remove `StreamedResponseListener` + * Remove `AbstractSurrogate::$phpEscapeMap` + * Remove `HttpKernelInterface::MASTER_REQUEST` + * Remove `terminate_on_cache_hit` option from `HttpCache` + * Require explicit argument when calling `ConfigDataCollector::setKernel()`, `RouterListener::setCurrentRequest()` + * Remove `Kernel::stripComments()` + * Remove `FileLinkFormatter`, use `FileLinkFormatter` from the ErrorHandler component instead + * Remove `UriSigner`, use `UriSigner` from the HttpFoundation component instead + * Add argument `$buildDir` to `WarmableInterface` + * Add argument `$filter` to `Profiler::find()` and `FileProfilerStorage::find()` + +6.4 +--- + + * Support backed enums in #[MapQueryParameter] + * `BundleInterface` no longer extends `ContainerAwareInterface` + * Add optional `$className` parameter to `ControllerEvent::getAttributes()` + * Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass` + * Add argument `$validationFailedStatusCode` to `#[MapQueryString]` and `#[MapRequestPayload]` + * Add argument `$debug` to `Logger` + * Add class `DebugLoggerConfigurator` + * Add parameters `kernel.runtime_mode` and `kernel.runtime_mode.*`, all set from env var `APP_RUNTIME_MODE` + * Deprecate `Kernel::stripComments()` + * Support the `!` character at the beginning of a string as a negation operator in the url filter of the profiler + * Deprecate `UriSigner`, use `UriSigner` from the HttpFoundation component instead + * Deprecate `FileLinkFormatter`, use `FileLinkFormatter` from the ErrorHandler component instead + * Add argument `$buildDir` to `WarmableInterface` + * Add argument `$filter` to `Profiler::find()` and `FileProfilerStorage::find()` + * Add `ControllerResolver::allowControllers()` to define which callables are legit controllers when the `_check_controller_is_allowed` request attribute is set + +6.3 +--- + + * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead + * `FileProfilerStorage` removes profiles automatically after two days + * Add `#[WithHttpStatus]` for defining status codes for exceptions + * Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver` + * Add `#[WithLogLevel]` for defining log levels for exceptions + * Add `skip_response_headers` to the `HttpCache` options + * Introduce targeted value resolvers with `#[ValueResolver]` and `#[AsTargetedValueResolver]` + * Add `#[MapRequestPayload]` to map and validate request payload from `Request::getContent()` or `Request::$request->all()` to typed objects + * Add `#[MapQueryString]` to map and validate request query string from `Request::$query->all()` to typed objects + * Add `#[MapQueryParameter]` to map and validate individual query parameters to controller arguments + * Collect data from every event dispatcher + +6.2 +--- + + * Add constructor argument `bool $handleAllThrowable` to `HttpKernel` + * Add `ControllerEvent::getAttributes()` to handle attributes on controllers + * Add `#[Cache]` to describe the default HTTP cache headers on controllers + * Add `absolute_uri` option to surrogate fragment renderers + * Add `ValueResolverInterface` and deprecate `ArgumentValueResolverInterface` + * Add argument `$reflector` to `ArgumentResolverInterface` and `ArgumentMetadataFactoryInterface` + * Deprecate calling `ConfigDataCollector::setKernel()`, `RouterListener::setCurrentRequest()` without arguments + +6.1 +--- + + * Add `BackedEnumValueResolver` to resolve backed enum cases from request attributes in controller arguments + * Add `DateTimeValueResolver` to resolve request attributes into DateTime objects in controller arguments + * Deprecate StreamedResponseListener, it's not needed anymore + * Add `Profiler::isEnabled()` so collaborating collector services may elect to omit themselves + * Add the `UidValueResolver` argument value resolver + * Add `AbstractBundle` class for DI configuration/definition on a single file + * Update the path of a bundle placed in the `src/` directory to the parent directory when `AbstractBundle` is used + +6.0 +--- + + * Remove `ArgumentInterface` + * Remove `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead + * Remove support for returning a `ContainerBuilder` from `KernelInterface::registerContainerConfiguration()` + * Remove `KernelEvent::isMasterRequest()`, use `isMainRequest()` instead + * Remove support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead + +5.4 +--- + + * Add the ability to enable the profiler using a request query parameter, body parameter or attribute + * Deprecate `AbstractTestSessionListener` and `TestSessionListener`, use `AbstractSessionListener` and `SessionListener` instead + * Deprecate the `fileLinkFormat` parameter of `DebugHandlersListener` + * Add support for configuring log level, and status code by exception class + * Allow ignoring "kernel.reset" methods that don't exist with "on_invalid" attribute + +5.3 +--- + + * Deprecate `ArgumentInterface` + * Add `ArgumentMetadata::getAttributes()` + * Deprecate `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead + * Mark the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal + * Deprecate returning a `ContainerBuilder` from `KernelInterface::registerContainerConfiguration()` + * Deprecate `HttpKernelInterface::MASTER_REQUEST` and add `HttpKernelInterface::MAIN_REQUEST` as replacement + * Deprecate `KernelEvent::isMasterRequest()` and add `isMainRequest()` as replacement + * Add `#[AsController]` attribute for declaring standalone controllers on PHP 8 + * Add `FragmentUriGeneratorInterface` and `FragmentUriGenerator` to generate the URI of a fragment + +5.2.0 +----- + + * added session usage + * made the public `http_cache` service handle requests when available + * allowed enabling trusted hosts and proxies using new `kernel.trusted_hosts`, + `kernel.trusted_proxies` and `kernel.trusted_headers` parameters + * content of request parameter `_password` is now also hidden + in the request profiler raw content section + * Allowed adding attributes on controller arguments that will be passed to argument resolvers. + * kernels implementing the `ExtensionInterface` will now be auto-registered to the container + * added parameter `kernel.runtime_environment`, defined as `%env(default:kernel.environment:APP_RUNTIME_ENV)%` + * do not set a default `Accept` HTTP header when using `HttpKernelBrowser` + +5.1.0 +----- + + * allowed to use a specific logger channel for deprecations + * made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+; + not returning an array is deprecated + * made kernels implementing `WarmableInterface` be part of the cache warmup stage + * deprecated support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead + * allowed using public aliases to reference controllers + * added session usage reporting when the `_stateless` attribute of the request is set to `true` + * added `AbstractSessionListener::onSessionUsage()` to report when the session is used while a request is stateless + +5.0.0 +----- + + * removed support for getting the container from a non-booted kernel + * removed the first and second constructor argument of `ConfigDataCollector` + * removed `ConfigDataCollector::getApplicationName()` + * removed `ConfigDataCollector::getApplicationVersion()` + * removed support for `Symfony\Component\Templating\EngineInterface` in `HIncludeFragmentRenderer`, use a `Twig\Environment` only + * removed `TranslatorListener` in favor of `LocaleAwareListener` + * removed `getRootDir()` and `getName()` from `Kernel` and `KernelInterface` + * removed `FilterControllerArgumentsEvent`, use `ControllerArgumentsEvent` instead + * removed `FilterControllerEvent`, use `ControllerEvent` instead + * removed `FilterResponseEvent`, use `ResponseEvent` instead + * removed `GetResponseEvent`, use `RequestEvent` instead + * removed `GetResponseForControllerResultEvent`, use `ViewEvent` instead + * removed `GetResponseForExceptionEvent`, use `ExceptionEvent` instead + * removed `PostResponseEvent`, use `TerminateEvent` instead + * removed `SaveSessionListener` in favor of `AbstractSessionListener` + * removed `Client`, use `HttpKernelBrowser` instead + * added method `getProjectDir()` to `KernelInterface` + * removed methods `serialize` and `unserialize` from `DataCollector`, store the serialized state in the data property instead + * made `ProfilerStorageInterface` internal + * removed the second and third argument of `KernelInterface::locateResource` + * removed the second and third argument of `FileLocator::__construct` + * removed loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as + fallback directories. + * removed class `ExceptionListener`, use `ErrorListener` instead + +4.4.0 +----- + + * The `DebugHandlersListener` class has been marked as `final` + * Added new Bundle directory convention consistent with standard skeletons + * Deprecated the second and third argument of `KernelInterface::locateResource` + * Deprecated the second and third argument of `FileLocator::__construct` + * Deprecated loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as + fallback directories. Resources like service definitions are usually loaded relative to the + current directory or with a glob pattern. The fallback directories have never been advocated + so you likely do not use those in any app based on the SF Standard or Flex edition. + * Marked all dispatched event classes as `@final` + * Added `ErrorController` to enable the preview and error rendering mechanism + * Getting the container from a non-booted kernel is deprecated. + * Marked the `AjaxDataCollector`, `ConfigDataCollector`, `EventDataCollector`, + `ExceptionDataCollector`, `LoggerDataCollector`, `MemoryDataCollector`, + `RequestDataCollector` and `TimeDataCollector` classes as `@final`. + * Marked the `RouterDataCollector::collect()` method as `@final`. + * The `DataCollectorInterface::collect()` and `Profiler::collect()` methods third parameter signature + will be `\Throwable $exception = null` instead of `\Exception $exception = null` in Symfony 5.0. + * Deprecated methods `ExceptionEvent::get/setException()`, use `get/setThrowable()` instead + * Deprecated class `ExceptionListener`, use `ErrorListener` instead + +4.3.0 +----- + + * renamed `Client` to `HttpKernelBrowser` + * `KernelInterface` doesn't extend `Serializable` anymore + * deprecated the `Kernel::serialize()` and `unserialize()` methods + * increased the priority of `Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener` + * made `Symfony\Component\HttpKernel\EventListener\LocaleListener` set the default locale early + * deprecated `TranslatorListener` in favor of `LocaleAwareListener` + * added the registration of all `LocaleAwareInterface` implementations into the `LocaleAwareListener` + * made `FileLinkFormatter` final and not implement `Serializable` anymore + * the base `DataCollector` doesn't implement `Serializable` anymore, you should + store all the serialized state in the data property instead + * `DumpDataCollector` has been marked as `final` + * added an event listener to prevent search engines from indexing applications in debug mode. + * renamed `FilterControllerArgumentsEvent` to `ControllerArgumentsEvent` + * renamed `FilterControllerEvent` to `ControllerEvent` + * renamed `FilterResponseEvent` to `ResponseEvent` + * renamed `GetResponseEvent` to `RequestEvent` + * renamed `GetResponseForControllerResultEvent` to `ViewEvent` + * renamed `GetResponseForExceptionEvent` to `ExceptionEvent` + * renamed `PostResponseEvent` to `TerminateEvent` + * added `HttpClientKernel` for handling requests with an `HttpClientInterface` instance + * added `trace_header` and `trace_level` configuration options to `HttpCache` + +4.2.0 +----- + + * deprecated `KernelInterface::getRootDir()` and the `kernel.root_dir` parameter + * deprecated `KernelInterface::getName()` and the `kernel.name` parameter + * deprecated the first and second constructor argument of `ConfigDataCollector` + * deprecated `ConfigDataCollector::getApplicationName()` + * deprecated `ConfigDataCollector::getApplicationVersion()` + +4.1.0 +----- + + * added orphaned events support to `EventDataCollector` + * `ExceptionListener` now logs exceptions at priority `0` (previously logged at `-128`) + * Added support for using `service::method` to reference controllers, making it consistent with other cases. It is recommended over the `service:action` syntax with a single colon, which will be deprecated in the future. + * Added the ability to profile individual argument value resolvers via the + `Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver` + +4.0.0 +----- + + * removed the `DataCollector::varToString()` method, use `DataCollector::cloneVar()` + instead + * using the `DataCollector::cloneVar()` method requires the VarDumper component + * removed the `ValueExporter` class + * removed `ControllerResolverInterface::getArguments()` + * removed `TraceableControllerResolver::getArguments()` + * removed `ControllerResolver::getArguments()` and the ability to resolve arguments + * removed the `argument_resolver` service dependency from the `debug.controller_resolver` + * removed `LazyLoadingFragmentHandler::addRendererService()` + * removed `Psr6CacheClearer::addPool()` + * removed `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` + * removed `Kernel::loadClassCache()`, `Kernel::doLoadClassCache()`, `Kernel::setClassCache()`, + and `Kernel::getEnvParameters()` + * support for the `X-Status-Code` when handling exceptions in the `HttpKernel` + has been dropped, use the `HttpKernel::allowCustomResponseCode()` method + instead + * removed convention-based commands registration + * removed the `ChainCacheClearer::add()` method + * removed the `CacheaWarmerAggregate::add()` and `setWarmers()` methods + * made `CacheWarmerAggregate` and `ChainCacheClearer` classes final + +3.4.0 +----- + + * added a minimalist PSR-3 `Logger` class that writes in `stderr` + * made kernels implementing `CompilerPassInterface` able to process the container + * deprecated bundle inheritance + * added `RebootableInterface` and implemented it in `Kernel` + * deprecated commands auto registration + * deprecated `EnvParametersResource` + * added `Symfony\Component\HttpKernel\Client::catchExceptions()` + * deprecated the `ChainCacheClearer::add()` method + * deprecated the `CacheaWarmerAggregate::add()` and `setWarmers()` methods + * made `CacheWarmerAggregate` and `ChainCacheClearer` classes final + * added the possibility to reset the profiler to its initial state + * deprecated data collectors without a `reset()` method + * deprecated implementing `DebugLoggerInterface` without a `clear()` method + +3.3.0 +----- + + * added `kernel.project_dir` and `Kernel::getProjectDir()` + * deprecated `kernel.root_dir` and `Kernel::getRootDir()` + * deprecated `Kernel::getEnvParameters()` + * deprecated the special `SYMFONY__` environment variables + * added the possibility to change the query string parameter used by `UriSigner` + * deprecated `LazyLoadingFragmentHandler::addRendererService()` + * deprecated `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` + * deprecated `Psr6CacheClearer::addPool()` + +3.2.0 +----- + + * deprecated `DataCollector::varToString()`, use `cloneVar()` instead + * changed surrogate capability name in `AbstractSurrogate::addSurrogateCapability` to 'symfony' + * Added `ControllerArgumentValueResolverPass` + +3.1.0 +----- + * deprecated passing objects as URI attributes to the ESI and SSI renderers + * deprecated `ControllerResolver::getArguments()` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` as argument to `HttpKernel` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolver` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getMethod()` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getRedirect()` + * added the `kernel.controller_arguments` event, triggered after controller arguments have been resolved + +3.0.0 +----- + + * removed `Symfony\Component\HttpKernel\Kernel::init()` + * removed `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle()` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle()` + * removed `Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher::setProfiler()` + * removed `Symfony\Component\HttpKernel\EventListener\FragmentListener::getLocalIpAddresses()` + * removed `Symfony\Component\HttpKernel\EventListener\LocaleListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\RouterListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelRequest()` + * removed `Symfony\Component\HttpKernel\Fragment\FragmentHandler::setRequest()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::hasSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::addSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::needsEsiParsing()` + * removed `Symfony\Component\HttpKernel\HttpCache\HttpCache::getEsi()` + * removed `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` + * removed `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass` + * removed `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` + * removed `Symfony\Component\HttpKernel\EventListener\EsiListener` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategy` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategyInterface` + * removed `Symfony\Component\HttpKernel\Log\LoggerInterface` + * removed `Symfony\Component\HttpKernel\Log\NullLogger` + * removed `Symfony\Component\HttpKernel\Profiler::import()` + * removed `Symfony\Component\HttpKernel\Profiler::export()` + +2.8.0 +----- + + * deprecated `Profiler::import` and `Profiler::export` + +2.7.0 +----- + + * added the HTTP status code to profiles + +2.6.0 +----- + + * deprecated `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener`, use `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` instead + * deprecated unused method `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle` + +2.5.0 +----- + + * deprecated `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass`, use `Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass` instead + +2.4.0 +----- + + * added event listeners for the session + * added the KernelEvents::FINISH_REQUEST event + +2.3.0 +----- + + * [BC BREAK] renamed `Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener` to `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` and changed its constructor + * deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`, + `Symfony\Component\HttpKernel\Exception\FatalErrorException` and `Symfony\Component\HttpKernel\Exception\FlattenException` + * deprecated `Symfony\Component\HttpKernel\Kernel::init()` + * added the possibility to specify an id an extra attributes to hinclude tags + * added the collect of data if a controller is a Closure in the Request collector + * pass exceptions from the ExceptionListener to the logger using the logging context to allow for more + detailed messages + +2.2.0 +----- + + * [BC BREAK] the path info for sub-request is now always _fragment (or whatever you configured instead of the default) + * added Symfony\Component\HttpKernel\EventListener\FragmentListener + * added Symfony\Component\HttpKernel\UriSigner + * added Symfony\Component\HttpKernel\FragmentRenderer and rendering strategies (in Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface) + * added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel + * added ControllerReference to create reference of Controllers (used in the FragmentRenderer class) + * [BC BREAK] renamed TimeDataCollector::getTotalTime() to + TimeDataCollector::getDuration() + * updated the MemoryDataCollector to include the memory used in the + kernel.terminate event listeners + * moved the Stopwatch classes to a new component + * added TraceableControllerResolver + * added TraceableEventDispatcher (removed ContainerAwareTraceableEventDispatcher) + * added support for WinCache opcode cache in ConfigDataCollector + +2.1.0 +----- + + * [BC BREAK] the charset is now configured via the Kernel::getCharset() method + * [BC BREAK] the current locale for the user is not stored anymore in the session + * added the HTTP method to the profiler storage + * updated all listeners to implement EventSubscriberInterface + * added TimeDataCollector + * added ContainerAwareTraceableEventDispatcher + * moved TraceableEventDispatcherInterface to the EventDispatcher component + * added RouterListener, LocaleListener, and StreamedResponseListener + * added CacheClearerInterface (and ChainCacheClearer) + * added a kernel.terminate event (via TerminableInterface and PostResponseEvent) + * added a Stopwatch class + * added WarmableInterface + * improved extensibility between bundles + * added profiler storages for Memcache(d), File-based, MongoDB, Redis + * moved Filesystem class to its own component diff --git a/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php new file mode 100644 index 0000000..f40ad9b --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * CacheClearerInterface. + * + * @author Dustin Dobervich + */ +interface CacheClearerInterface +{ + /** + * Clears any caches necessary. + */ + public function clear(string $cacheDir): void; +} diff --git a/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php new file mode 100644 index 0000000..5a8205e --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * ChainCacheClearer. + * + * @author Dustin Dobervich + * + * @final + */ +class ChainCacheClearer implements CacheClearerInterface +{ + /** + * @param iterable $clearers + */ + public function __construct( + private iterable $clearers = [], + ) { + } + + public function clear(string $cacheDir): void + { + foreach ($this->clearers as $clearer) { + $clearer->clear($cacheDir); + } + } +} diff --git a/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php new file mode 100644 index 0000000..50bf4e0 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Nicolas Grekas + */ +class Psr6CacheClearer implements CacheClearerInterface +{ + private array $pools = []; + + /** + * @param array $pools + */ + public function __construct(array $pools = []) + { + $this->pools = $pools; + } + + public function hasPool(string $name): bool + { + return isset($this->pools[$name]); + } + + /** + * @throws \InvalidArgumentException If the cache pool with the given name does not exist + */ + public function getPool(string $name): CacheItemPoolInterface + { + if (!$this->hasPool($name)) { + throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name)); + } + + return $this->pools[$name]; + } + + /** + * @throws \InvalidArgumentException If the cache pool with the given name does not exist + */ + public function clearPool(string $name): bool + { + if (!isset($this->pools[$name])) { + throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name)); + } + + return $this->pools[$name]->clear(); + } + + public function clear(string $cacheDir): void + { + foreach ($this->pools as $pool) { + $pool->clear(); + } + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php new file mode 100644 index 0000000..2707e46 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Abstract cache warmer that knows how to write a file to the cache. + * + * @author Fabien Potencier + */ +abstract class CacheWarmer implements CacheWarmerInterface +{ + protected function writeCacheFile(string $file, $content): void + { + $tmpFile = @tempnam(\dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php new file mode 100644 index 0000000..0d1d76d --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Aggregates several cache warmers into a single one. + * + * @author Fabien Potencier + * + * @final + */ +class CacheWarmerAggregate implements CacheWarmerInterface +{ + private bool $optionalsEnabled = false; + private bool $onlyOptionalsEnabled = false; + + /** + * @param iterable $warmers + */ + public function __construct( + private iterable $warmers = [], + private bool $debug = false, + private ?string $deprecationLogsFilepath = null, + ) { + } + + public function enableOptionalWarmers(): void + { + $this->optionalsEnabled = true; + } + + public function enableOnlyOptionalWarmers(): void + { + $this->onlyOptionalsEnabled = $this->optionalsEnabled = true; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null, ?SymfonyStyle $io = null): array + { + if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { + $collectedLogs = []; + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + if (isset($collectedLogs[$message])) { + ++$collectedLogs[$message]['count']; + + return null; + } + + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); + // Clean the trace by removing first frames added by the error handler itself. + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $backtrace = \array_slice($backtrace, 1 + $i); + break; + } + } + + $collectedLogs[$message] = [ + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'trace' => $backtrace, + 'count' => 1, + ]; + + return null; + }); + } + + $preload = []; + try { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) { + continue; + } + + $start = microtime(true); + foreach ((array) $warmer->warmUp($cacheDir, $buildDir) as $item) { + if (is_dir($item) || (str_starts_with($item, \dirname($cacheDir)) && !is_file($item)) || ($buildDir && str_starts_with($item, \dirname($buildDir)) && !is_file($item))) { + throw new \LogicException(sprintf('"%s::warmUp()" should return a list of files or classes but "%s" is none of them.', $warmer::class, $item)); + } + $preload[] = $item; + } + + if ($io?->isDebug()) { + $io->info(sprintf('"%s" completed in %0.2fms.', $warmer::class, 1000 * (microtime(true) - $start))); + } + } + } finally { + if ($collectDeprecations) { + restore_error_handler(); + + if (is_file($this->deprecationLogsFilepath)) { + $previousLogs = unserialize(file_get_contents($this->deprecationLogsFilepath)); + if (\is_array($previousLogs)) { + $collectedLogs = array_merge($previousLogs, $collectedLogs); + } + } + + file_put_contents($this->deprecationLogsFilepath, serialize(array_values($collectedLogs))); + } + } + + return array_values(array_unique($preload)); + } + + public function isOptional(): bool + { + return false; + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php new file mode 100644 index 0000000..d1c5101 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes able to warm up the cache. + * + * @author Fabien Potencier + */ +interface CacheWarmerInterface extends WarmableInterface +{ + /** + * Checks whether this warmer is optional or not. + * + * Optional warmers can be ignored on certain conditions. + * + * A warmer should return true if the cache can be + * generated incrementally and on-demand. + */ + public function isOptional(): bool; +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php new file mode 100644 index 0000000..7ffe3c0 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes that support warming their cache. + * + * @author Fabien Potencier + */ +interface WarmableInterface +{ + /** + * Warms up the cache. + * + * @param string $cacheDir Where warm-up artifacts should be stored + * @param string|null $buildDir Where read-only artifacts should go; null when called after compile-time + * + * @return string[] A list of classes or files to preload + */ + public function warmUp(string $cacheDir, ?string $buildDir = null): array; +} diff --git a/vendor/symfony/http-kernel/Config/FileLocator.php b/vendor/symfony/http-kernel/Config/FileLocator.php new file mode 100644 index 0000000..01fc757 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/FileLocator.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\FileLocator as BaseFileLocator; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * FileLocator uses the KernelInterface to locate resources in bundles. + * + * @author Fabien Potencier + */ +class FileLocator extends BaseFileLocator +{ + public function __construct( + private KernelInterface $kernel, + ) { + parent::__construct(); + } + + public function locate(string $file, ?string $currentPath = null, bool $first = true): string|array + { + if (isset($file[0]) && '@' === $file[0]) { + $resource = $this->kernel->locateResource($file); + + return $first ? $resource : [$resource]; + } + + return parent::locate($file, $currentPath, $first); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php new file mode 100644 index 0000000..10b3382 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\ValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; +use Symfony\Component\HttpKernel\Exception\ResolverNotFoundException; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * Responsible for resolving the arguments passed to an action. + * + * @author Iltar van der Berg + */ +final class ArgumentResolver implements ArgumentResolverInterface +{ + private ArgumentMetadataFactoryInterface $argumentMetadataFactory; + private iterable $argumentValueResolvers; + private ?ContainerInterface $namedResolvers; + + /** + * @param iterable $argumentValueResolvers + */ + public function __construct(?ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [], ?ContainerInterface $namedResolvers = null) + { + $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory(); + $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); + $this->namedResolvers = $namedResolvers; + } + + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array + { + $arguments = []; + + foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, $reflector) as $metadata) { + $argumentValueResolvers = $this->argumentValueResolvers; + $disabledResolvers = []; + + if ($this->namedResolvers && $attributes = $metadata->getAttributesOfType(ValueResolver::class, $metadata::IS_INSTANCEOF)) { + $resolverName = null; + foreach ($attributes as $attribute) { + if ($attribute->disabled) { + $disabledResolvers[$attribute->resolver] = true; + } elseif ($resolverName) { + throw new \LogicException(sprintf('You can only pin one resolver per argument, but argument "$%s" of "%s()" has more.', $metadata->getName(), $metadata->getControllerName())); + } else { + $resolverName = $attribute->resolver; + } + } + + if ($resolverName) { + if (!$this->namedResolvers->has($resolverName)) { + throw new ResolverNotFoundException($resolverName, $this->namedResolvers instanceof ServiceProviderInterface ? array_keys($this->namedResolvers->getProvidedServices()) : []); + } + + $argumentValueResolvers = [ + $this->namedResolvers->get($resolverName), + new RequestAttributeValueResolver(), + new DefaultValueResolver(), + ]; + } + } + + $valueResolverExceptions = []; + foreach ($argumentValueResolvers as $name => $resolver) { + if (isset($disabledResolvers[\is_int($name) ? $resolver::class : $name])) { + continue; + } + + try { + $count = 0; + foreach ($resolver->resolve($request, $metadata) as $argument) { + ++$count; + $arguments[] = $argument; + } + } catch (NearMissValueResolverException $e) { + $valueResolverExceptions[] = $e; + } + + if (1 < $count && !$metadata->isVariadic()) { + throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at most one value for non-variadic arguments.', get_debug_type($resolver))); + } + + if ($count) { + // continue to the next controller argument + continue 2; + } + } + + $reasons = array_map(static fn (NearMissValueResolverException $e) => $e->getMessage(), $valueResolverExceptions); + if (!$reasons) { + $reasons[] = 'Either the argument is nullable and no null value has been provided, no default value has been provided or there is a non-optional argument after this one.'; + } + + $reasonCounter = 1; + if (\count($reasons) > 1) { + foreach ($reasons as $i => $reason) { + $reasons[$i] = $reasonCounter.') '.$reason; + ++$reasonCounter; + } + } + + throw new \RuntimeException(sprintf('Controller "%s" requires the "$%s" argument that could not be resolved. '.($reasonCounter > 1 ? 'Possible reasons: ' : '').'%s', $metadata->getControllerName(), $metadata->getName(), implode(' ', $reasons))); + } + + return $arguments; + } + + /** + * @return iterable + */ + public static function getDefaultArgumentValueResolvers(): iterable + { + return [ + new RequestAttributeValueResolver(), + new RequestValueResolver(), + new SessionValueResolver(), + new DefaultValueResolver(), + new VariadicValueResolver(), + ]; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php new file mode 100644 index 0000000..ad1191f --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/BackedEnumValueResolver.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Attempt to resolve backed enum cases from request attributes, for a route path parameter, + * leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type. + * + * @author Maxime Steinhausser + */ +final class BackedEnumValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + if (!is_subclass_of($argument->getType(), \BackedEnum::class)) { + return []; + } + + if ($argument->isVariadic()) { + // only target route path parameters, which cannot be variadic. + return []; + } + + // do not support if no value can be resolved at all + // letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used + // or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error. + if (!$request->attributes->has($argument->getName())) { + return []; + } + + $value = $request->attributes->get($argument->getName()); + + if (null === $value) { + return [null]; + } + + if ($value instanceof \BackedEnum) { + return [$value]; + } + + if (!\is_int($value) && !\is_string($value)) { + throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".', $argument->getType(), $argument->getName(), get_debug_type($value))); + } + + /** @var class-string<\BackedEnum> $enumType */ + $enumType = $argument->getType(); + + try { + return [$enumType::from($value)]; + } catch (\ValueError|\TypeError $e) { + throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: ', $argument->getType(), $argument->getName()).$e->getMessage(), $e); + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php new file mode 100644 index 0000000..981ebf6 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Clock\ClockInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\MapDateTime; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Convert DateTime instances from request attribute variable. + * + * @author Benjamin Eberlei + * @author Tim Goudriaan + */ +final class DateTimeValueResolver implements ValueResolverInterface +{ + public function __construct( + private readonly ?ClockInterface $clock = null, + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!is_a($argument->getType(), \DateTimeInterface::class, true) || !$request->attributes->has($argument->getName())) { + return []; + } + + $value = $request->attributes->get($argument->getName()); + $class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType(); + + if (!$value) { + if ($argument->isNullable()) { + return [null]; + } + if (!$this->clock) { + return [new $class()]; + } + $value = $this->clock->now(); + } + + if ($value instanceof \DateTimeInterface) { + return [$value instanceof $class ? $value : $class::createFromInterface($value)]; + } + + $format = null; + + if ($attributes = $argument->getAttributes(MapDateTime::class, ArgumentMetadata::IS_INSTANCEOF)) { + $attribute = $attributes[0]; + $format = $attribute->format; + } + + if (null !== $format) { + $date = $class::createFromFormat($format, $value, $this->clock?->now()->getTimeZone()); + + if (($class::getLastErrors() ?: ['warning_count' => 0])['warning_count']) { + $date = false; + } + } else { + if (false !== filter_var($value, \FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { + $value = '@'.$value; + } + try { + $date = new $class($value, $this->clock?->now()->getTimeZone()); + } catch (\Exception) { + $date = false; + } + } + + if (!$date) { + throw new NotFoundHttpException(sprintf('Invalid date given for parameter "%s".', $argument->getName())); + } + + return [$date]; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php new file mode 100644 index 0000000..bf114f3 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the default value defined in the action signature when no value has been given. + * + * @author Iltar van der Berg + */ +final class DefaultValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if ($argument->hasDefaultValue()) { + return [$argument->getDefaultValue()]; + } + + if (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()) { + return [null]; + } + + return []; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php new file mode 100644 index 0000000..547580e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Provides an intuitive error message when controller fails because it is not registered as a service. + * + * @author Simeon Kolev + */ +final class NotTaggedControllerValueResolver implements ValueResolverInterface +{ + public function __construct( + private ContainerInterface $container, + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + $controller = $request->attributes->get('_controller'); + + if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { + $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return []; + } + + if ('\\' === $controller[0]) { + $controller = ltrim($controller, '\\'); + } + + if (!$this->container->has($controller)) { + $controller = (false !== $i = strrpos($controller, ':')) + ? substr($controller, 0, $i).strtolower(substr($controller, $i)) + : $controller.'::__invoke'; + } + + if ($this->container->has($controller)) { + return []; + } + + $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); + $message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what); + + throw new RuntimeException($message); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php new file mode 100644 index 0000000..6a0542f --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/QueryParameterValueResolver.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * Resolve arguments of type: array, string, int, float, bool, \BackedEnum from query parameters. + * + * @author Ruud Kamphuis + * @author Nicolas Grekas + * @author Mateusz Anders + * @author Ionut Enache + */ +final class QueryParameterValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!$attribute = $argument->getAttributesOfType(MapQueryParameter::class)[0] ?? null) { + return []; + } + + $name = $attribute->name ?? $argument->getName(); + $validationFailedCode = $attribute->validationFailedStatusCode; + + if (!$request->query->has($name)) { + if ($argument->isNullable() || $argument->hasDefaultValue()) { + return []; + } + + throw HttpException::fromStatusCode($validationFailedCode, sprintf('Missing query parameter "%s".', $name)); + } + + $value = $request->query->all()[$name]; + $type = $argument->getType(); + + if (null === $attribute->filter && 'array' === $type) { + if (!$argument->isVariadic()) { + return [(array) $value]; + } + + $filtered = array_values(array_filter((array) $value, \is_array(...))); + + if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw HttpException::fromStatusCode($validationFailedCode, sprintf('Invalid query parameter "%s".', $name)); + } + + return $filtered; + } + + $options = [ + 'flags' => $attribute->flags | \FILTER_NULL_ON_FAILURE, + 'options' => $attribute->options, + ]; + + if ('array' === $type || $argument->isVariadic()) { + $value = (array) $value; + $options['flags'] |= \FILTER_REQUIRE_ARRAY; + } else { + $options['flags'] |= \FILTER_REQUIRE_SCALAR; + } + + $enumType = null; + $filter = match ($type) { + 'array' => \FILTER_DEFAULT, + 'string' => \FILTER_DEFAULT, + 'int' => \FILTER_VALIDATE_INT, + 'float' => \FILTER_VALIDATE_FLOAT, + 'bool' => \FILTER_VALIDATE_BOOL, + default => match ($enumType = is_subclass_of($type, \BackedEnum::class) ? (new \ReflectionEnum($type))->getBackingType()->getName() : null) { + 'int' => \FILTER_VALIDATE_INT, + 'string' => \FILTER_DEFAULT, + default => throw new \LogicException(sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')), + }, + }; + + $value = filter_var($value, $attribute->filter ?? $filter, $options); + + if (null !== $enumType && null !== $value) { + $enumFrom = static function ($value) use ($type) { + if (!\is_string($value) && !\is_int($value)) { + return null; + } + + try { + return $type::from($value); + } catch (\ValueError) { + return null; + } + }; + + $value = \is_array($value) ? array_map($enumFrom, $value) : $enumFrom($value); + } + + if (null === $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw HttpException::fromStatusCode($validationFailedCode, sprintf('Invalid query parameter "%s".', $name)); + } + + if (!\is_array($value)) { + return [$value]; + } + + $filtered = array_filter($value, static fn ($v) => null !== $v); + + if ($argument->isVariadic()) { + $filtered = array_values($filtered); + } + + if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw HttpException::fromStatusCode($validationFailedCode, sprintf('Invalid query parameter "%s".', $name)); + } + + return $argument->isVariadic() ? $filtered : [$filtered]; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php new file mode 100644 index 0000000..2a8d48e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a non-variadic argument's value from the request attributes. + * + * @author Iltar van der Berg + */ +final class RequestAttributeValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + return !$argument->isVariadic() && $request->attributes->has($argument->getName()) ? [$request->attributes->get($argument->getName())] : []; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php new file mode 100644 index 0000000..c35d5e7 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; +use Symfony\Component\HttpKernel\Attribute\MapUploadedFile; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; +use Symfony\Component\Serializer\Exception\UnsupportedFormatException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Exception\ValidationFailedException; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Konstantin Myakshin + * + * @final + */ +class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscriberInterface +{ + /** + * @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT + * @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS + */ + private const CONTEXT_DENORMALIZE = [ + 'disable_type_enforcement' => true, + 'collect_denormalization_errors' => true, + ]; + + /** + * @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS + */ + private const CONTEXT_DESERIALIZE = [ + 'collect_denormalization_errors' => true, + ]; + + public function __construct( + private readonly SerializerInterface&DenormalizerInterface $serializer, + private readonly ?ValidatorInterface $validator = null, + private readonly ?TranslatorInterface $translator = null, + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + $attribute = $argument->getAttributesOfType(MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF)[0] + ?? $argument->getAttributesOfType(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0] + ?? $argument->getAttributesOfType(MapUploadedFile::class, ArgumentMetadata::IS_INSTANCEOF)[0] + ?? null; + + if (!$attribute) { + return []; + } + + if (!$attribute instanceof MapUploadedFile && $argument->isVariadic()) { + throw new \LogicException(sprintf('Mapping variadic argument "$%s" is not supported.', $argument->getName())); + } + + if ($attribute instanceof MapRequestPayload) { + if ('array' === $argument->getType()) { + if (!$attribute->type) { + throw new NearMissValueResolverException(sprintf('Please set the $type argument of the #[%s] attribute to the type of the objects in the expected array.', MapRequestPayload::class)); + } + } elseif ($attribute->type) { + throw new NearMissValueResolverException(sprintf('Please set its type to "array" when using argument $type of #[%s].', MapRequestPayload::class)); + } + } + + $attribute->metadata = $argument; + + return [$attribute]; + } + + public function onKernelControllerArguments(ControllerArgumentsEvent $event): void + { + $arguments = $event->getArguments(); + + foreach ($arguments as $i => $argument) { + if ($argument instanceof MapQueryString) { + $payloadMapper = $this->mapQueryString(...); + $validationFailedCode = $argument->validationFailedStatusCode; + } elseif ($argument instanceof MapRequestPayload) { + $payloadMapper = $this->mapRequestPayload(...); + $validationFailedCode = $argument->validationFailedStatusCode; + } elseif ($argument instanceof MapUploadedFile) { + $payloadMapper = $this->mapUploadedFile(...); + $validationFailedCode = $argument->validationFailedStatusCode; + } else { + continue; + } + $request = $event->getRequest(); + + if (!$argument->metadata->getType()) { + throw new \LogicException(sprintf('Could not resolve the "$%s" controller argument: argument should be typed.', $argument->metadata->getName())); + } + + if ($this->validator) { + $violations = new ConstraintViolationList(); + try { + $payload = $payloadMapper($request, $argument->metadata, $argument); + } catch (PartialDenormalizationException $e) { + $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p); + foreach ($e->getErrors() as $error) { + $parameters = []; + $template = 'This value was of an unexpected type.'; + if ($expectedTypes = $error->getExpectedTypes()) { + $template = 'This value should be of type {{ type }}.'; + $parameters['{{ type }}'] = implode('|', $expectedTypes); + } + if ($error->canUseMessageForUser()) { + $parameters['hint'] = $error->getMessage(); + } + $message = $trans($template, $parameters, 'validators'); + $violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null)); + } + $payload = $e->getData(); + } + + if (null !== $payload && !\count($violations)) { + $constraints = $argument->constraints ?? null; + if (\is_array($payload) && !empty($constraints) && !$constraints instanceof Assert\All) { + $constraints = new Assert\All($constraints); + } + $violations->addAll($this->validator->validate($payload, $constraints, $argument->validationGroups ?? null)); + } + + if (\count($violations)) { + throw HttpException::fromStatusCode($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException($payload, $violations)); + } + } else { + try { + $payload = $payloadMapper($request, $argument->metadata, $argument); + } catch (PartialDenormalizationException $e) { + throw HttpException::fromStatusCode($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e); + } + } + + if (null === $payload) { + $payload = match (true) { + $argument->metadata->hasDefaultValue() => $argument->metadata->getDefaultValue(), + $argument->metadata->isNullable() => null, + default => throw HttpException::fromStatusCode($validationFailedCode), + }; + } + + $arguments[$i] = $payload; + } + + $event->setArguments($arguments); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments', + ]; + } + + private function mapQueryString(Request $request, ArgumentMetadata $argument, MapQueryString $attribute): ?object + { + if (!$data = $request->query->all()) { + return null; + } + + return $this->serializer->denormalize($data, $argument->getType(), null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ['filter_bool' => true]); + } + + private function mapRequestPayload(Request $request, ArgumentMetadata $argument, MapRequestPayload $attribute): object|array|null + { + if (null === $format = $request->getContentTypeFormat()) { + throw new UnsupportedMediaTypeHttpException('Unsupported format.'); + } + + if ($attribute->acceptFormat && !\in_array($format, (array) $attribute->acceptFormat, true)) { + throw new UnsupportedMediaTypeHttpException(sprintf('Unsupported format, expects "%s", but "%s" given.', implode('", "', (array) $attribute->acceptFormat), $format)); + } + + if ('array' === $argument->getType() && null !== $attribute->type) { + $type = $attribute->type.'[]'; + } else { + $type = $argument->getType(); + } + + if ($data = $request->request->all()) { + return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ('form' === $format ? ['filter_bool' => true] : [])); + } + + if ('' === $data = $request->getContent()) { + return null; + } + + if ('form' === $format) { + throw new BadRequestHttpException('Request payload contains invalid "form" data.'); + } + + try { + return $this->serializer->deserialize($data, $type, $format, self::CONTEXT_DESERIALIZE + $attribute->serializationContext); + } catch (UnsupportedFormatException $e) { + throw new UnsupportedMediaTypeHttpException(sprintf('Unsupported format: "%s".', $format), $e); + } catch (NotEncodableValueException $e) { + throw new BadRequestHttpException(sprintf('Request payload contains invalid "%s" data.', $format), $e); + } catch (UnexpectedPropertyException $e) { + throw new BadRequestHttpException(sprintf('Request payload contains invalid "%s" property.', $e->property), $e); + } + } + + private function mapUploadedFile(Request $request, ArgumentMetadata $argument, MapUploadedFile $attribute): UploadedFile|array|null + { + return $request->files->get($attribute->name ?? $argument->getName(), []); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php new file mode 100644 index 0000000..bc34089 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; + +/** + * Yields the same instance as the request object passed along. + * + * @author Iltar van der Berg + */ +final class RequestValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class)) { + return [$request]; + } + + if (str_ends_with($argument->getType() ?? '', '\\Request')) { + throw new NearMissValueResolverException(sprintf('Looks like you required a Request object with the wrong class name "%s". Did you mean to use "%s" instead?', $argument->getType(), Request::class)); + } + + return []; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php new file mode 100644 index 0000000..a7f61db --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; + +/** + * Yields a service keyed by _controller and argument name. + * + * @author Nicolas Grekas + */ +final class ServiceValueResolver implements ValueResolverInterface +{ + public function __construct( + private ContainerInterface $container, + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + $controller = $request->attributes->get('_controller'); + + if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { + $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return []; + } + + if ('\\' === $controller[0]) { + $controller = ltrim($controller, '\\'); + } + + if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) { + $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); + } + + if (!$this->container->has($controller) || !$this->container->get($controller)->has($argument->getName())) { + return []; + } + + try { + return [$this->container->get($controller)->get($argument->getName())]; + } catch (RuntimeException $e) { + $what = 'argument $'.$argument->getName(); + $message = str_replace(sprintf('service "%s"', $argument->getName()), $what, $e->getMessage()); + $what .= sprintf(' of "%s()"', $controller); + $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $message); + + if ($e->getMessage() === $message) { + $message = sprintf('Cannot resolve %s: %s', $what, $message); + } + + throw new NearMissValueResolverException($message, $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php new file mode 100644 index 0000000..30b7f1d --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the Session. + * + * @author Iltar van der Berg + */ +final class SessionValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!$request->hasSession()) { + return []; + } + + $type = $argument->getType(); + if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { + return []; + } + + return $request->getSession() instanceof $type ? [$request->getSession()] : []; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php new file mode 100644 index 0000000..41fd1d9 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Provides timing information via the stopwatch. + * + * @author Iltar van der Berg + */ +final class TraceableValueResolver implements ValueResolverInterface +{ + public function __construct( + private ValueResolverInterface $inner, + private Stopwatch $stopwatch, + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + $method = $this->inner::class.'::'.__FUNCTION__; + $this->stopwatch->start($method, 'controller.argument_value_resolver'); + + yield from $this->inner->resolve($request, $argument); + + $this->stopwatch->stop($method); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php new file mode 100644 index 0000000..8537360 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/UidValueResolver.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Uid\AbstractUid; + +final class UidValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if ($argument->isVariadic() + || !\is_string($value = $request->attributes->get($argument->getName())) + || null === ($uidClass = $argument->getType()) + || !is_subclass_of($uidClass, AbstractUid::class, true) + ) { + return []; + } + + try { + return [$uidClass::fromString($value)]; + } catch (\InvalidArgumentException $e) { + throw new NotFoundHttpException(sprintf('The uid for the "%s" parameter is invalid.', $argument->getName()), $e); + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php new file mode 100644 index 0000000..1297cca --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a variadic argument's values from the request attributes. + * + * @author Iltar van der Berg + */ +final class VariadicValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!$argument->isVariadic() || !$request->attributes->has($argument->getName())) { + return []; + } + + $values = $request->attributes->get($argument->getName()); + + if (!\is_array($values)) { + throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), get_debug_type($values))); + } + + return $values; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php new file mode 100644 index 0000000..2090a59 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * An ArgumentResolverInterface instance knows how to determine the + * arguments for a specific action. + * + * @author Fabien Potencier + */ +interface ArgumentResolverInterface +{ + /** + * Returns the arguments to pass to the controller. + * + * @throws \RuntimeException When no value could be provided for a required argument + */ + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array; +} diff --git a/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php new file mode 100644 index 0000000..6c86a86 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Container; + +/** + * A controller resolver searching for a controller in a psr-11 container when using the "service::method" notation. + * + * @author Fabien Potencier + * @author Maxime Steinhausser + */ +class ContainerControllerResolver extends ControllerResolver +{ + public function __construct( + protected ContainerInterface $container, + ?LoggerInterface $logger = null, + ) { + parent::__construct($logger); + } + + protected function instantiateController(string $class): object + { + $class = ltrim($class, '\\'); + + if ($this->container->has($class)) { + return $this->container->get($class); + } + + try { + return parent::instantiateController($class); + } catch (\Error $e) { + } + + $this->throwExceptionIfControllerWasRemoved($class, $e); + + if ($e instanceof \ArgumentCountError) { + throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', $class), 0, $e); + } + + throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class.', $class), 0, $e); + } + + private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous): void + { + if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) { + throw new \InvalidArgumentException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous); + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerReference.php b/vendor/symfony/http-kernel/Controller/ControllerReference.php new file mode 100644 index 0000000..0ecdc29 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerReference.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Acts as a marker and a data holder for a Controller. + * + * Some methods in Symfony accept both a URI (as a string) or a controller as + * an argument. In the latter case, instead of passing an array representing + * the controller, you can use an instance of this class. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class ControllerReference +{ + public array $attributes = []; + public array $query = []; + + /** + * @param string $controller The controller name + * @param array $attributes An array of parameters to add to the Request attributes + * @param array $query An array of parameters to add to the Request query string + */ + public function __construct( + public string $controller, + array $attributes = [], + array $query = [], + ) { + $this->attributes = $attributes; + $this->query = $query; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolver.php b/vendor/symfony/http-kernel/Controller/ControllerResolver.php new file mode 100644 index 0000000..a56eac4 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolver.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\AsController; + +/** + * This implementation uses the '_controller' request attribute to determine + * the controller to execute. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class ControllerResolver implements ControllerResolverInterface +{ + private array $allowedControllerTypes = []; + private array $allowedControllerAttributes = [AsController::class => AsController::class]; + + public function __construct( + private ?LoggerInterface $logger = null, + ) { + } + + /** + * @param array $types + * @param array $attributes + */ + public function allowControllers(array $types = [], array $attributes = []): void + { + foreach ($types as $type) { + $this->allowedControllerTypes[$type] = $type; + } + + foreach ($attributes as $attribute) { + $this->allowedControllerAttributes[$attribute] = $attribute; + } + } + + /** + * @throws BadRequestException when the request has attribute "_check_controller_is_allowed" set to true and the controller is not allowed + */ + public function getController(Request $request): callable|false + { + if (!$controller = $request->attributes->get('_controller')) { + $this->logger?->warning('Unable to look for the controller as the "_controller" parameter is missing.'); + + return false; + } + + if (\is_array($controller)) { + if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) { + try { + $controller[0] = $this->instantiateController($controller[0]); + } catch (\Error|\LogicException $e) { + if (\is_callable($controller)) { + return $this->checkController($request, $controller); + } + + throw $e; + } + } + + if (!\is_callable($controller)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); + } + + return $this->checkController($request, $controller); + } + + if (\is_object($controller)) { + if (!\is_callable($controller)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); + } + + return $this->checkController($request, $controller); + } + + if (\function_exists($controller)) { + return $this->checkController($request, $controller); + } + + try { + $callable = $this->createController($controller); + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$e->getMessage(), 0, $e); + } + + if (!\is_callable($callable)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($callable)); + } + + return $this->checkController($request, $callable); + } + + /** + * Returns a callable for the given controller. + * + * @throws \InvalidArgumentException When the controller cannot be created + */ + protected function createController(string $controller): callable + { + if (!str_contains($controller, '::')) { + $controller = $this->instantiateController($controller); + + if (!\is_callable($controller)) { + throw new \InvalidArgumentException($this->getControllerError($controller)); + } + + return $controller; + } + + [$class, $method] = explode('::', $controller, 2); + + try { + $controller = [$this->instantiateController($class), $method]; + } catch (\Error|\LogicException $e) { + try { + if ((new \ReflectionMethod($class, $method))->isStatic()) { + return $class.'::'.$method; + } + } catch (\ReflectionException) { + throw $e; + } + + throw $e; + } + + if (!\is_callable($controller)) { + throw new \InvalidArgumentException($this->getControllerError($controller)); + } + + return $controller; + } + + /** + * Returns an instantiated controller. + */ + protected function instantiateController(string $class): object + { + return new $class(); + } + + private function getControllerError(mixed $callable): string + { + if (\is_string($callable)) { + if (str_contains($callable, '::')) { + $callable = explode('::', $callable, 2); + } else { + return sprintf('Function "%s" does not exist.', $callable); + } + } + + if (\is_object($callable)) { + $availableMethods = $this->getClassMethodsWithoutMagicMethods($callable); + $alternativeMsg = $availableMethods ? sprintf(' or use one of the available methods: "%s"', implode('", "', $availableMethods)) : ''; + + return sprintf('Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.', get_debug_type($callable), $alternativeMsg); + } + + if (!\is_array($callable)) { + return sprintf('Invalid type for controller given, expected string, array or object, got "%s".', get_debug_type($callable)); + } + + if (!isset($callable[0]) || !isset($callable[1]) || 2 !== \count($callable)) { + return 'Invalid array callable, expected [controller, method].'; + } + + [$controller, $method] = $callable; + + if (\is_string($controller) && !class_exists($controller)) { + return sprintf('Class "%s" does not exist.', $controller); + } + + $className = \is_object($controller) ? get_debug_type($controller) : $controller; + + if (method_exists($controller, $method)) { + return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className); + } + + $collection = $this->getClassMethodsWithoutMagicMethods($controller); + + $alternatives = []; + + foreach ($collection as $item) { + $lev = levenshtein($method, $item); + + if ($lev <= \strlen($method) / 3 || str_contains($item, $method)) { + $alternatives[] = $item; + } + } + + asort($alternatives); + + $message = sprintf('Expected method "%s" on class "%s"', $method, $className); + + if (\count($alternatives) > 0) { + $message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives)); + } else { + $message .= sprintf('. Available methods: "%s".', implode('", "', $collection)); + } + + return $message; + } + + private function getClassMethodsWithoutMagicMethods($classOrObject): array + { + $methods = get_class_methods($classOrObject); + + return array_filter($methods, fn (string $method) => 0 !== strncmp($method, '__', 2)); + } + + private function checkController(Request $request, callable $controller): callable + { + if (!$request->attributes->get('_check_controller_is_allowed', false)) { + return $controller; + } + + $r = null; + + if (\is_array($controller)) { + [$class, $name] = $controller; + $name = (\is_string($class) ? $class : $class::class).'::'.$name; + } elseif (\is_object($controller) && !$controller instanceof \Closure) { + $class = $controller; + $name = $class::class.'::__invoke'; + } else { + $r = new \ReflectionFunction($controller); + $name = $r->name; + + if ($r->isAnonymous()) { + $name = $class = \Closure::class; + } elseif ($class = $r->getClosureCalledClass()) { + $class = $class->name; + $name = $class.'::'.$name; + } + } + + if ($class) { + foreach ($this->allowedControllerTypes as $type) { + if (is_a($class, $type, true)) { + return $controller; + } + } + } + + $r ??= new \ReflectionClass($class); + + foreach ($r->getAttributes() as $attribute) { + if (isset($this->allowedControllerAttributes[$attribute->getName()])) { + return $controller; + } + } + + if (str_contains($name, '@anonymous')) { + $name = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $name); + } + + throw new BadRequestException(sprintf('Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class)); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php new file mode 100644 index 0000000..6cfe6c3 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * A ControllerResolverInterface implementation knows how to determine the + * controller to execute based on a Request object. + * + * A Controller can be any valid PHP callable. + * + * @author Fabien Potencier + */ +interface ControllerResolverInterface +{ + /** + * Returns the Controller instance associated with a Request. + * + * As several resolvers can exist for a single application, a resolver must + * return false when it is not able to determine the controller. + * + * The resolver must only throw an exception when it should be able to load a + * controller but cannot because of some errors made by the developer. + * + * @return callable|false A PHP callable representing the Controller, + * or false if this resolver is not able to determine the controller + * + * @throws \LogicException If a controller was found based on the request but it is not callable + */ + public function getController(Request $request): callable|false; +} diff --git a/vendor/symfony/http-kernel/Controller/ErrorController.php b/vendor/symfony/http-kernel/Controller/ErrorController.php new file mode 100644 index 0000000..616920e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ErrorController.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Renders error or exception pages from a given FlattenException. + * + * @author Yonel Ceruto + * @author Matthias Pigulla + */ +class ErrorController +{ + public function __construct( + private HttpKernelInterface $kernel, + private string|object|array|null $controller, + private ErrorRendererInterface $errorRenderer, + ) { + } + + public function __invoke(\Throwable $exception): Response + { + $exception = $this->errorRenderer->render($exception); + + return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders()); + } + + public function preview(Request $request, int $code): Response + { + /* + * This Request mimics the parameters set by + * \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with + * the additional "showException" flag. + */ + $subRequest = $request->duplicate(null, null, [ + '_controller' => $this->controller, + 'exception' => new HttpException($code, 'This is a sample exception.'), + 'logger' => null, + 'showException' => false, + ]); + + return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + } +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php new file mode 100644 index 0000000..c6dac59 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Fabien Potencier + */ +class TraceableArgumentResolver implements ArgumentResolverInterface +{ + public function __construct( + private ArgumentResolverInterface $resolver, + private Stopwatch $stopwatch, + ) { + } + + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array + { + $e = $this->stopwatch->start('controller.get_arguments'); + + try { + return $this->resolver->getArguments($request, $controller, $reflector); + } finally { + $e->stop(); + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php new file mode 100644 index 0000000..f6b993c --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Fabien Potencier + */ +class TraceableControllerResolver implements ControllerResolverInterface +{ + public function __construct( + private ControllerResolverInterface $resolver, + private Stopwatch $stopwatch, + ) { + } + + public function getController(Request $request): callable|false + { + $e = $this->stopwatch->start('controller.get_callable'); + + try { + return $this->resolver->getController($request); + } finally { + $e->stop(); + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ValueResolverInterface.php b/vendor/symfony/http-kernel/Controller/ValueResolverInterface.php new file mode 100644 index 0000000..a861705 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ValueResolverInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Responsible for resolving the value of an argument based on its metadata. + * + * @author Nicolas Grekas + */ +interface ValueResolverInterface +{ + /** + * Returns the possible value(s). + */ + public function resolve(Request $request, ArgumentMetadata $argument): iterable; +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php new file mode 100644 index 0000000..bee9e9d --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Responsible for storing metadata of an argument. + * + * @author Iltar van der Berg + */ +class ArgumentMetadata +{ + public const IS_INSTANCEOF = 2; + + /** + * @param object[] $attributes + */ + public function __construct( + private string $name, + private ?string $type, + private bool $isVariadic, + private bool $hasDefaultValue, + private mixed $defaultValue, + private bool $isNullable = false, + private array $attributes = [], + private string $controllerName = 'n/a', + ) { + $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); + } + + /** + * Returns the name as given in PHP, $foo would yield "foo". + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns the type of the argument. + * + * The type is the PHP class in 5.5+ and additionally the basic type in PHP 7.0+. + */ + public function getType(): ?string + { + return $this->type; + } + + /** + * Returns whether the argument is defined as "...$variadic". + */ + public function isVariadic(): bool + { + return $this->isVariadic; + } + + /** + * Returns whether the argument has a default value. + * + * Implies whether an argument is optional. + */ + public function hasDefaultValue(): bool + { + return $this->hasDefaultValue; + } + + /** + * Returns whether the argument accepts null values. + */ + public function isNullable(): bool + { + return $this->isNullable; + } + + /** + * Returns the default value of the argument. + * + * @throws \LogicException if no default value is present; {@see self::hasDefaultValue()} + */ + public function getDefaultValue(): mixed + { + if (!$this->hasDefaultValue) { + throw new \LogicException(sprintf('Argument $%s does not have a default value. Use "%s::hasDefaultValue()" to avoid this exception.', $this->name, __CLASS__)); + } + + return $this->defaultValue; + } + + /** + * @param class-string $name + * @param self::IS_INSTANCEOF|0 $flags + * + * @return array + */ + public function getAttributes(?string $name = null, int $flags = 0): array + { + if (!$name) { + return $this->attributes; + } + + return $this->getAttributesOfType($name, $flags); + } + + /** + * @template T of object + * + * @param class-string $name + * @param self::IS_INSTANCEOF|0 $flags + * + * @return array + */ + public function getAttributesOfType(string $name, int $flags = 0): array + { + $attributes = []; + if ($flags & self::IS_INSTANCEOF) { + foreach ($this->attributes as $attribute) { + if ($attribute instanceof $name) { + $attributes[] = $attribute; + } + } + } else { + foreach ($this->attributes as $attribute) { + if ($attribute::class === $name) { + $attributes[] = $attribute; + } + } + } + + return $attributes; + } + + public function getControllerName(): string + { + return $this->controllerName; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php new file mode 100644 index 0000000..26b80f9 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Builds {@see ArgumentMetadata} objects based on the given Controller. + * + * @author Iltar van der Berg + */ +final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface +{ + public function createArgumentMetadata(string|object|array $controller, ?\ReflectionFunctionAbstract $reflector = null): array + { + $arguments = []; + $reflector ??= new \ReflectionFunction($controller(...)); + $controllerName = $this->getPrettyName($reflector); + + foreach ($reflector->getParameters() as $param) { + $attributes = []; + foreach ($param->getAttributes() as $reflectionAttribute) { + if (class_exists($reflectionAttribute->getName())) { + $attributes[] = $reflectionAttribute->newInstance(); + } + } + + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes, $controllerName); + } + + return $arguments; + } + + /** + * Returns an associated type to the given parameter if available. + */ + private function getType(\ReflectionParameter $parameter): ?string + { + if (!$type = $parameter->getType()) { + return null; + } + $name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; + + return match (strtolower($name)) { + 'self' => $parameter->getDeclaringClass()?->name, + 'parent' => get_parent_class($parameter->getDeclaringClass()?->name ?? '') ?: null, + default => $name, + }; + } + + private function getPrettyName(\ReflectionFunctionAbstract $r): string + { + $name = $r->name; + + if ($r instanceof \ReflectionMethod) { + return $r->class.'::'.$name; + } + + if ($r->isAnonymous() || !$class = $r->getClosureCalledClass()) { + return $name; + } + + return $class->name.'::'.$name; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php new file mode 100644 index 0000000..4f4bc07 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Builds method argument data. + * + * @author Iltar van der Berg + */ +interface ArgumentMetadataFactoryInterface +{ + /** + * @return ArgumentMetadata[] + */ + public function createArgumentMetadata(string|object|array $controller, ?\ReflectionFunctionAbstract $reflector = null): array; +} diff --git a/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php new file mode 100644 index 0000000..3c8d2f0 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @author Bart van den Burg + * + * @final + */ +class AjaxDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + // all collecting is done client side + } + + public function reset(): void + { + // all collecting is done client side + } + + public function getName(): string + { + return 'ajax'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php new file mode 100644 index 0000000..7c8f469 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Fabien Potencier + * + * @final + */ +class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private KernelInterface $kernel; + + /** + * Sets the Kernel associated with this Request. + */ + public function setKernel(KernelInterface $kernel): void + { + $this->kernel = $kernel; + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $eom = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); + $eol = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); + + $this->data = [ + 'token' => $response->headers->get('X-Debug-Token'), + 'symfony_version' => Kernel::VERSION, + 'symfony_minor_version' => sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION), + 'symfony_lts' => 4 === Kernel::MINOR_VERSION, + 'symfony_state' => $this->determineSymfonyState(), + 'symfony_eom' => $eom->format('F Y'), + 'symfony_eol' => $eol->format('F Y'), + 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', + 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', + 'php_version' => \PHP_VERSION, + 'php_architecture' => \PHP_INT_SIZE * 8, + 'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', + 'php_timezone' => date_default_timezone_get(), + 'xdebug_enabled' => \extension_loaded('xdebug'), + 'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL), + 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL), + 'bundles' => [], + 'sapi_name' => \PHP_SAPI, + ]; + + if (isset($this->kernel)) { + foreach ($this->kernel->getBundles() as $name => $bundle) { + $this->data['bundles'][$name] = new ClassStub($bundle::class); + } + } + + if (preg_match('~^(\d+(?:\.\d+)*)(.+)?$~', $this->data['php_version'], $matches) && isset($matches[2])) { + $this->data['php_version'] = $matches[1]; + $this->data['php_version_extra'] = $matches[2]; + } + } + + public function lateCollect(): void + { + $this->data = $this->cloneVar($this->data); + } + + /** + * Gets the token. + */ + public function getToken(): ?string + { + return $this->data['token']; + } + + /** + * Gets the Symfony version. + */ + public function getSymfonyVersion(): string + { + return $this->data['symfony_version']; + } + + /** + * Returns the state of the current Symfony release + * as one of: unknown, dev, stable, eom, eol. + */ + public function getSymfonyState(): string + { + return $this->data['symfony_state']; + } + + /** + * Returns the minor Symfony version used (without patch numbers of extra + * suffix like "RC", "beta", etc.). + */ + public function getSymfonyMinorVersion(): string + { + return $this->data['symfony_minor_version']; + } + + public function isSymfonyLts(): bool + { + return $this->data['symfony_lts']; + } + + /** + * Returns the human readable date when this Symfony version ends its + * maintenance period. + */ + public function getSymfonyEom(): string + { + return $this->data['symfony_eom']; + } + + /** + * Returns the human readable date when this Symfony version reaches its + * "end of life" and won't receive bugs or security fixes. + */ + public function getSymfonyEol(): string + { + return $this->data['symfony_eol']; + } + + /** + * Gets the PHP version. + */ + public function getPhpVersion(): string + { + return $this->data['php_version']; + } + + /** + * Gets the PHP version extra part. + */ + public function getPhpVersionExtra(): ?string + { + return $this->data['php_version_extra'] ?? null; + } + + public function getPhpArchitecture(): int + { + return $this->data['php_architecture']; + } + + public function getPhpIntlLocale(): string + { + return $this->data['php_intl_locale']; + } + + public function getPhpTimezone(): string + { + return $this->data['php_timezone']; + } + + /** + * Gets the environment. + */ + public function getEnv(): string + { + return $this->data['env']; + } + + /** + * Returns true if the debug is enabled. + * + * @return bool|string true if debug is enabled, false otherwise or a string if no kernel was set + */ + public function isDebug(): bool|string + { + return $this->data['debug']; + } + + /** + * Returns true if the Xdebug is enabled. + */ + public function hasXdebug(): bool + { + return $this->data['xdebug_enabled']; + } + + /** + * Returns true if the function xdebug_info is available. + */ + public function hasXdebugInfo(): bool + { + return \function_exists('xdebug_info'); + } + + /** + * Returns true if APCu is enabled. + */ + public function hasApcu(): bool + { + return $this->data['apcu_enabled']; + } + + /** + * Returns true if Zend OPcache is enabled. + */ + public function hasZendOpcache(): bool + { + return $this->data['zend_opcache_enabled']; + } + + public function getBundles(): array|Data + { + return $this->data['bundles']; + } + + /** + * Gets the PHP SAPI name. + */ + public function getSapiName(): string + { + return $this->data['sapi_name']; + } + + public function getName(): string + { + return 'config'; + } + + private function determineSymfonyState(): string + { + $now = new \DateTimeImmutable(); + $eom = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); + $eol = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE)->modify('last day of this month'); + + if ($now > $eol) { + $versionState = 'eol'; + } elseif ($now > $eom) { + $versionState = 'eom'; + } elseif ('' !== Kernel::EXTRA_VERSION) { + $versionState = 'dev'; + } else { + $versionState = 'stable'; + } + + return $versionState; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollector.php b/vendor/symfony/http-kernel/DataCollector/DataCollector.php new file mode 100644 index 0000000..607bb81 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollector.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\VarDumper\Caster\CutStub; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * DataCollector. + * + * Children of this class must store the collected data in the data property. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +abstract class DataCollector implements DataCollectorInterface +{ + protected array|Data $data = []; + + private ClonerInterface $cloner; + + /** + * Converts the variable into a serializable Data instance. + * + * This array can be displayed in the template using + * the VarDumper component. + */ + protected function cloneVar(mixed $var): Data + { + if ($var instanceof Data) { + return $var; + } + if (!isset($this->cloner)) { + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(-1); + $this->cloner->addCasters($this->getCasters()); + } + + return $this->cloner->cloneVar($var); + } + + /** + * @return callable[] The casters to add to the cloner + */ + protected function getCasters(): array + { + $casters = [ + '*' => function ($v, array $a, Stub $s, $isNested) { + if (!$v instanceof Stub) { + $b = $a; + foreach ($a as $k => $v) { + if (!\is_object($v) || $v instanceof \DateTimeInterface || $v instanceof Stub) { + continue; + } + + try { + $a[$k] = $s = new CutStub($v); + + if ($b[$k] === $s) { + // we've hit a non-typed reference + $a[$k] = $v; + } + } catch (\TypeError $e) { + // we've hit a typed reference + } + } + } + + return $a; + }, + ] + ReflectionCaster::UNSET_CLOSURE_FILE_INFO; + + return $casters; + } + + public function __sleep(): array + { + return ['data']; + } + + public function __wakeup(): void + { + } + + /** + * @internal to prevent implementing \Serializable + */ + final protected function serialize(): void + { + } + + /** + * @internal to prevent implementing \Serializable + */ + final protected function unserialize(string $data): void + { + } + + /** + * @return void + */ + public function reset() + { + $this->data = []; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php new file mode 100644 index 0000000..5e8593d --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Service\ResetInterface; + +/** + * DataCollectorInterface. + * + * @author Fabien Potencier + */ +interface DataCollectorInterface extends ResetInterface +{ + /** + * Collects data for the given Request and Response. + * + * @return void + */ + public function collect(Request $request, Response $response, ?\Throwable $exception = null); + + /** + * Returns the name of the collector. + * + * @return string + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php new file mode 100644 index 0000000..2ad1118 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Server\Connection; + +/** + * @author Nicolas Grekas + * + * @final + */ +class DumpDataCollector extends DataCollector implements DataDumperInterface +{ + private string|FileLinkFormatter|false $fileLinkFormat; + private int $dataCount = 0; + private bool $isCollected = true; + private int $clonesCount = 0; + private int $clonesIndex = 0; + private array $rootRefs; + private string $charset; + private mixed $sourceContextProvider; + private bool $webMode; + + public function __construct( + private ?Stopwatch $stopwatch = null, + string|FileLinkFormatter|null $fileLinkFormat = null, + ?string $charset = null, + private ?RequestStack $requestStack = null, + private DataDumperInterface|Connection|null $dumper = null, + ?bool $webMode = null, + ) { + $fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter && false === $fileLinkFormat->format('', 0) ? false : $fileLinkFormat; + $this->charset = $charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'; + $this->webMode = $webMode ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true); + + // All clones share these properties by reference: + $this->rootRefs = [ + &$this->data, + &$this->dataCount, + &$this->isCollected, + &$this->clonesCount, + ]; + + $this->sourceContextProvider = $dumper instanceof Connection && isset($dumper->getContextProviders()['source']) ? $dumper->getContextProviders()['source'] : new SourceContextProvider($this->charset); + } + + public function __clone() + { + $this->clonesIndex = ++$this->clonesCount; + } + + public function dump(Data $data): ?string + { + $this->stopwatch?->start('dump'); + + ['name' => $name, 'file' => $file, 'line' => $line, 'file_excerpt' => $fileExcerpt] = $this->sourceContextProvider->getContext(); + + if (!$this->dumper || $this->dumper instanceof Connection && !$this->dumper->write($data)) { + $this->isCollected = false; + } + + $context = $data->getContext(); + $label = $context['label'] ?? ''; + unset($context['label']); + $data = $data->withContext($context); + + if ($this->dumper && !$this->dumper instanceof Connection) { + $this->doDump($this->dumper, $data, $name, $file, $line, $label); + } + + if (!$this->dataCount) { + $this->data = []; + } + $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt', 'label'); + ++$this->dataCount; + + $this->stopwatch?->stop('dump'); + + return null; + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + if (!$this->dataCount) { + $this->data = []; + } + + // Sub-requests and programmatic calls stay in the collected profile. + if ($this->dumper || ($this->requestStack && $this->requestStack->getMainRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { + return; + } + + // In all other conditions that remove the web debug toolbar, dumps are written on the output. + if (!$this->requestStack + || !$response->headers->has('X-Debug-Token') + || $response->isRedirection() + || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type') ?? '', 'html')) + || 'html' !== $request->getRequestFormat() + || false === strripos($response->getContent(), '') + ) { + if ($response->headers->has('Content-Type') && str_contains($response->headers->get('Content-Type') ?? '', 'html')) { + $dumper = new HtmlDumper('php://output', $this->charset); + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + } else { + $dumper = new CliDumper('php://output', $this->charset); + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + } + + foreach ($this->data as $dump) { + $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line'], $dump['label'] ?? ''); + } + } + } + + public function reset(): void + { + $this->stopwatch?->reset(); + parent::reset(); + $this->dataCount = 0; + $this->isCollected = true; + $this->clonesCount = 0; + $this->clonesIndex = 0; + } + + /** + * @internal + */ + public function __sleep(): array + { + if (!$this->dataCount) { + $this->data = []; + } + + if ($this->clonesCount !== $this->clonesIndex) { + return []; + } + + $this->data[] = $this->fileLinkFormat; + $this->data[] = $this->charset; + $this->dataCount = 0; + $this->isCollected = true; + + return parent::__sleep(); + } + + /** + * @internal + */ + public function __wakeup(): void + { + parent::__wakeup(); + + $charset = array_pop($this->data); + $fileLinkFormat = array_pop($this->data); + $this->dataCount = \count($this->data); + foreach ($this->data as $dump) { + if (!\is_string($dump['name']) || !\is_string($dump['file']) || !\is_int($dump['line'])) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + } + + self::__construct($this->stopwatch ?? null, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null); + } + + public function getDumpsCount(): int + { + return $this->dataCount; + } + + public function getDumps(string $format, int $maxDepthLimit = -1, int $maxItemsPerDepth = -1): array + { + $data = fopen('php://memory', 'r+'); + + if ('html' === $format) { + $dumper = new HtmlDumper($data, $this->charset); + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + } else { + throw new \InvalidArgumentException(sprintf('Invalid dump format: "%s".', $format)); + } + $dumps = []; + + if (!$this->dataCount) { + return $this->data = []; + } + + foreach ($this->data as $dump) { + $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); + $dump['data'] = stream_get_contents($data, -1, 0); + ftruncate($data, 0); + rewind($data); + $dumps[] = $dump; + } + + return $dumps; + } + + public function getName(): string + { + return 'dump'; + } + + public function __destruct() + { + if (0 === $this->clonesCount-- && !$this->isCollected && $this->dataCount) { + $this->clonesCount = 0; + $this->isCollected = true; + + $h = headers_list(); + $i = \count($h); + array_unshift($h, 'Content-Type: '.\ini_get('default_mimetype')); + while (0 !== stripos($h[$i], 'Content-Type:')) { + --$i; + } + + if ($this->webMode) { + $dumper = new HtmlDumper('php://output', $this->charset); + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + } else { + $dumper = new CliDumper('php://output', $this->charset); + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + } + + foreach ($this->data as $i => $dump) { + $this->data[$i] = null; + $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line'], $dump['label'] ?? ''); + } + + $this->data = []; + $this->dataCount = 0; + } + } + + private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line, string $label): void + { + if ($dumper instanceof CliDumper) { + $contextDumper = function ($name, $file, $line, $fmt, $label) { + $this->line = '' !== $label ? $this->style('meta', $label).' in ' : ''; + + if ($this instanceof HtmlDumper) { + if ($file) { + $s = $this->style('meta', '%s'); + $f = strip_tags($this->style('', $file)); + $name = strip_tags($this->style('', $name)); + if ($fmt && $link = \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line)) { + $name = sprintf(''.$s.'', strip_tags($this->style('', $link)), $f, $name); + } else { + $name = sprintf(''.$s.'', $f, $name); + } + } else { + $name = $this->style('meta', $name); + } + $this->line .= $name.' on line '.$this->style('meta', $line).':'; + } else { + $this->line .= $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; + } + $this->dumpLine(0); + }; + $contextDumper = $contextDumper->bindTo($dumper, $dumper); + $contextDumper($name, $file, $line, $this->fileLinkFormat, $label); + } else { + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar(('' !== $label ? $label.' in ' : '').$name.' on line '.$line.':')); + } + $dumper->dump($data); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php new file mode 100644 index 0000000..3a94dbc --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Fabien Potencier + * + * @see TraceableEventDispatcher + * + * @final + */ +class EventDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** @var iterable */ + private iterable $dispatchers; + private ?Request $currentRequest = null; + + /** + * @param iterable|EventDispatcherInterface|null $dispatchers + */ + public function __construct( + iterable|EventDispatcherInterface|null $dispatchers = null, + private ?RequestStack $requestStack = null, + private string $defaultDispatcher = 'event_dispatcher', + ) { + if ($dispatchers instanceof EventDispatcherInterface) { + $dispatchers = [$this->defaultDispatcher => $dispatchers]; + } + $this->dispatchers = $dispatchers ?? []; + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; + $this->data = []; + } + + public function reset(): void + { + parent::reset(); + + foreach ($this->dispatchers as $dispatcher) { + if ($dispatcher instanceof ResetInterface) { + $dispatcher->reset(); + } + } + } + + public function lateCollect(): void + { + foreach ($this->dispatchers as $name => $dispatcher) { + if (!$dispatcher instanceof TraceableEventDispatcher) { + continue; + } + + $this->setCalledListeners($dispatcher->getCalledListeners($this->currentRequest), $name); + $this->setNotCalledListeners($dispatcher->getNotCalledListeners($this->currentRequest), $name); + $this->setOrphanedEvents($dispatcher->getOrphanedEvents($this->currentRequest), $name); + } + + $this->data = $this->cloneVar($this->data); + } + + public function getData(): array|Data + { + return $this->data; + } + + /** + * @see TraceableEventDispatcher + */ + public function setCalledListeners(array $listeners, ?string $dispatcher = null): void + { + $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] = $listeners; + } + + /** + * @see TraceableEventDispatcher + */ + public function getCalledListeners(?string $dispatcher = null): array|Data + { + return $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] ?? []; + } + + /** + * @see TraceableEventDispatcher + */ + public function setNotCalledListeners(array $listeners, ?string $dispatcher = null): void + { + $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] = $listeners; + } + + /** + * @see TraceableEventDispatcher + */ + public function getNotCalledListeners(?string $dispatcher = null): array|Data + { + return $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] ?? []; + } + + /** + * @param array $events An array of orphaned events + * + * @see TraceableEventDispatcher + */ + public function setOrphanedEvents(array $events, ?string $dispatcher = null): void + { + $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] = $events; + } + + /** + * @see TraceableEventDispatcher + */ + public function getOrphanedEvents(?string $dispatcher = null): array|Data + { + return $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] ?? []; + } + + public function getName(): string + { + return 'events'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php new file mode 100644 index 0000000..80156bc --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @author Fabien Potencier + * + * @final + */ +class ExceptionDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + if (null !== $exception) { + $this->data = [ + 'exception' => FlattenException::createWithDataRepresentation($exception), + ]; + } + } + + public function hasException(): bool + { + return isset($this->data['exception']); + } + + public function getException(): \Exception|FlattenException + { + return $this->data['exception']; + } + + public function getMessage(): string + { + return $this->data['exception']->getMessage(); + } + + public function getCode(): int + { + return $this->data['exception']->getCode(); + } + + public function getStatusCode(): int + { + return $this->data['exception']->getStatusCode(); + } + + public function getTrace(): array + { + return $this->data['exception']->getTrace(); + } + + public function getName(): string + { + return 'exception'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php new file mode 100644 index 0000000..efa1a4f --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +/** + * LateDataCollectorInterface. + * + * @author Fabien Potencier + */ +interface LateDataCollectorInterface +{ + /** + * Collects data as late as possible. + * + * @return void + */ + public function lateCollect(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php new file mode 100644 index 0000000..ecf638f --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -0,0 +1,334 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Fabien Potencier + * + * @final + */ +class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private ?DebugLoggerInterface $logger; + private ?Request $currentRequest = null; + private ?array $processedLogs = null; + + public function __construct( + ?object $logger = null, + private ?string $containerPathPrefix = null, + private ?RequestStack $requestStack = null, + ) { + $this->logger = DebugLoggerConfigurator::getDebugLogger($logger); + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; + } + + public function lateCollect(): void + { + if ($this->logger) { + $containerDeprecationLogs = $this->getContainerDeprecationLogs(); + $this->data = $this->computeErrorsCount($containerDeprecationLogs); + // get compiler logs later (only when they are needed) to improve performance + $this->data['compiler_logs'] = []; + $this->data['compiler_logs_filepath'] = $this->containerPathPrefix.'Compiler.log'; + $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs($this->currentRequest), $containerDeprecationLogs)); + $this->data = $this->cloneVar($this->data); + } + $this->currentRequest = null; + } + + public function getLogs(): Data|array + { + return $this->data['logs'] ?? []; + } + + public function getProcessedLogs(): array + { + if (null !== $this->processedLogs) { + return $this->processedLogs; + } + + $rawLogs = $this->getLogs(); + if ([] === $rawLogs) { + return $this->processedLogs = $rawLogs; + } + + $logs = []; + foreach ($this->getLogs()->getValue() as $rawLog) { + $rawLogData = $rawLog->getValue(); + + if ($rawLogData['priority']->getValue() > 300) { + $logType = 'error'; + } elseif (isset($rawLogData['scream']) && false === $rawLogData['scream']->getValue()) { + $logType = 'deprecation'; + } elseif (isset($rawLogData['scream']) && true === $rawLogData['scream']->getValue()) { + $logType = 'silenced'; + } else { + $logType = 'regular'; + } + + $logs[] = [ + 'type' => $logType, + 'errorCount' => $rawLog['errorCount'] ?? 1, + 'timestamp' => $rawLogData['timestamp_rfc3339']->getValue(), + 'priority' => $rawLogData['priority']->getValue(), + 'priorityName' => $rawLogData['priorityName']->getValue(), + 'channel' => $rawLogData['channel']->getValue(), + 'message' => $rawLogData['message'], + 'context' => $rawLogData['context'], + ]; + } + + // sort logs from oldest to newest + usort($logs, static fn ($logA, $logB) => $logA['timestamp'] <=> $logB['timestamp']); + + return $this->processedLogs = $logs; + } + + public function getFilters(): array + { + $filters = [ + 'channel' => [], + 'priority' => [ + 'Debug' => 100, + 'Info' => 200, + 'Notice' => 250, + 'Warning' => 300, + 'Error' => 400, + 'Critical' => 500, + 'Alert' => 550, + 'Emergency' => 600, + ], + ]; + + $allChannels = []; + foreach ($this->getProcessedLogs() as $log) { + if ('' === trim($log['channel'] ?? '')) { + continue; + } + + $allChannels[] = $log['channel']; + } + $channels = array_unique($allChannels); + sort($channels); + $filters['channel'] = $channels; + + return $filters; + } + + public function getPriorities(): Data|array + { + return $this->data['priorities'] ?? []; + } + + public function countErrors(): int + { + return $this->data['error_count'] ?? 0; + } + + public function countDeprecations(): int + { + return $this->data['deprecation_count'] ?? 0; + } + + public function countWarnings(): int + { + return $this->data['warning_count'] ?? 0; + } + + public function countScreams(): int + { + return $this->data['scream_count'] ?? 0; + } + + public function getCompilerLogs(): Data + { + return $this->cloneVar($this->getContainerCompilerLogs($this->data['compiler_logs_filepath'] ?? null)); + } + + public function getName(): string + { + return 'logger'; + } + + private function getContainerDeprecationLogs(): array + { + if (null === $this->containerPathPrefix || !is_file($file = $this->containerPathPrefix.'Deprecations.log')) { + return []; + } + + if ('' === $logContent = trim(file_get_contents($file))) { + return []; + } + + $bootTime = filemtime($file); + $logs = []; + foreach (unserialize($logContent) as $log) { + $log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])]; + $log['timestamp'] = $bootTime; + $log['timestamp_rfc3339'] = (new \DateTimeImmutable())->setTimestamp($bootTime)->format(\DateTimeInterface::RFC3339_EXTENDED); + $log['priority'] = 100; + $log['priorityName'] = 'DEBUG'; + $log['channel'] = null; + $log['scream'] = false; + unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); + $logs[] = $log; + } + + return $logs; + } + + private function getContainerCompilerLogs(?string $compilerLogsFilepath = null): array + { + if (!$compilerLogsFilepath || !is_file($compilerLogsFilepath)) { + return []; + } + + $logs = []; + foreach (file($compilerLogsFilepath, \FILE_IGNORE_NEW_LINES) as $log) { + $log = explode(': ', $log, 2); + if (!isset($log[1]) || !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $log[0])) { + $log = ['Unknown Compiler Pass', implode(': ', $log)]; + } + + $logs[$log[0]][] = ['message' => $log[1]]; + } + + return $logs; + } + + private function sanitizeLogs(array $logs): array + { + $sanitizedLogs = []; + $silencedLogs = []; + + foreach ($logs as $log) { + if (!$this->isSilencedOrDeprecationErrorLog($log)) { + $sanitizedLogs[] = $log; + + continue; + } + + $message = '_'.$log['message']; + $exception = $log['context']['exception']; + + if ($exception instanceof SilencedErrorContext) { + if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + continue; + } + $silencedLogs[$h] = true; + + if (!isset($sanitizedLogs[$message])) { + $sanitizedLogs[$message] = $log + [ + 'errorCount' => 0, + 'scream' => true, + ]; + } + $sanitizedLogs[$message]['errorCount'] += $exception->count; + + continue; + } + + $errorId = hash('xxh128', "{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); + + if (isset($sanitizedLogs[$errorId])) { + ++$sanitizedLogs[$errorId]['errorCount']; + } else { + $log += [ + 'errorCount' => 1, + 'scream' => false, + ]; + + $sanitizedLogs[$errorId] = $log; + } + } + + return array_values($sanitizedLogs); + } + + private function isSilencedOrDeprecationErrorLog(array $log): bool + { + if (!isset($log['context']['exception'])) { + return false; + } + + $exception = $log['context']['exception']; + + if ($exception instanceof SilencedErrorContext) { + return true; + } + + if ($exception instanceof \ErrorException && \in_array($exception->getSeverity(), [\E_DEPRECATED, \E_USER_DEPRECATED], true)) { + return true; + } + + return false; + } + + private function computeErrorsCount(array $containerDeprecationLogs): array + { + $silencedLogs = []; + $count = [ + 'error_count' => $this->logger->countErrors($this->currentRequest), + 'deprecation_count' => 0, + 'warning_count' => 0, + 'scream_count' => 0, + 'priorities' => [], + ]; + + foreach ($this->logger->getLogs($this->currentRequest) as $log) { + if (isset($count['priorities'][$log['priority']])) { + ++$count['priorities'][$log['priority']]['count']; + } else { + $count['priorities'][$log['priority']] = [ + 'count' => 1, + 'name' => $log['priorityName'], + ]; + } + if ('WARNING' === $log['priorityName']) { + ++$count['warning_count']; + } + + if ($this->isSilencedOrDeprecationErrorLog($log)) { + $exception = $log['context']['exception']; + if ($exception instanceof SilencedErrorContext) { + if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + continue; + } + $silencedLogs[$h] = true; + $count['scream_count'] += $exception->count; + } else { + ++$count['deprecation_count']; + } + } + } + + foreach ($containerDeprecationLogs as $deprecationLog) { + $count['deprecation_count'] += $deprecationLog['context']['exception']->count; + } + + ksort($count['priorities']); + + return $count; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php new file mode 100644 index 0000000..9715f94 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @author Fabien Potencier + * + * @final + */ +class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface +{ + public function __construct() + { + $this->reset(); + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $this->updateMemoryUsage(); + } + + public function reset(): void + { + $this->data = [ + 'memory' => 0, + 'memory_limit' => $this->convertToBytes(\ini_get('memory_limit')), + ]; + } + + public function lateCollect(): void + { + $this->updateMemoryUsage(); + } + + public function getMemory(): int + { + return $this->data['memory']; + } + + public function getMemoryLimit(): int|float + { + return $this->data['memory_limit']; + } + + public function updateMemoryUsage(): void + { + $this->data['memory'] = memory_get_peak_usage(true); + } + + public function getName(): string + { + return 'memory'; + } + + private function convertToBytes(string $memoryLimit): int|float + { + if ('-1' === $memoryLimit) { + return -1; + } + + $memoryLimit = strtolower($memoryLimit); + $max = strtolower(ltrim($memoryLimit, '+')); + if (str_starts_with($max, '0x')) { + $max = \intval($max, 16); + } elseif (str_starts_with($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($memoryLimit, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php new file mode 100644 index 0000000..88e879b --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -0,0 +1,498 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Fabien Potencier + * + * @final + */ +class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface +{ + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $controllers; + private array $sessionUsages = []; + + public function __construct( + private ?RequestStack $requestStack = null, + ) { + $this->controllers = new \SplObjectStorage(); + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + // attributes are serialized and as they can be anything, they need to be converted to strings. + $attributes = []; + $route = ''; + foreach ($request->attributes->all() as $key => $value) { + if ('_route' === $key) { + $route = \is_object($value) ? $value->getPath() : $value; + $attributes[$key] = $route; + } else { + $attributes[$key] = $value; + } + } + + $content = $request->getContent(); + + $sessionMetadata = []; + $sessionAttributes = []; + $flashes = []; + if (!$request->attributes->getBoolean('_stateless') && $request->hasSession()) { + $session = $request->getSession(); + if ($session->isStarted()) { + $sessionMetadata['Created'] = date(\DATE_RFC822, $session->getMetadataBag()->getCreated()); + $sessionMetadata['Last used'] = date(\DATE_RFC822, $session->getMetadataBag()->getLastUsed()); + $sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime(); + $sessionAttributes = $session->all(); + $flashes = $session->getFlashBag()->peekAll(); + } + } + + $statusCode = $response->getStatusCode(); + + $responseCookies = []; + foreach ($response->headers->getCookies() as $cookie) { + $responseCookies[$cookie->getName()] = $cookie; + } + + $dotenvVars = []; + foreach (explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '') as $name) { + if ('' !== $name && isset($_ENV[$name])) { + $dotenvVars[$name] = $_ENV[$name]; + } + } + + $this->data = [ + 'method' => $request->getMethod(), + 'format' => $request->getRequestFormat(), + 'content_type' => $response->headers->get('Content-Type', 'text/html'), + 'status_text' => Response::$statusTexts[$statusCode] ?? '', + 'status_code' => $statusCode, + 'request_query' => $request->query->all(), + 'request_request' => $request->request->all(), + 'request_files' => $request->files->all(), + 'request_headers' => $request->headers->all(), + 'request_server' => $request->server->all(), + 'request_cookies' => $request->cookies->all(), + 'request_attributes' => $attributes, + 'route' => $route, + 'response_headers' => $response->headers->all(), + 'response_cookies' => $responseCookies, + 'session_metadata' => $sessionMetadata, + 'session_attributes' => $sessionAttributes, + 'session_usages' => array_values($this->sessionUsages), + 'stateless_check' => $this->requestStack?->getMainRequest()?->attributes->get('_stateless') ?? false, + 'flashes' => $flashes, + 'path_info' => $request->getPathInfo(), + 'controller' => 'n/a', + 'locale' => $request->getLocale(), + 'dotenv_vars' => $dotenvVars, + ]; + + if (isset($this->data['request_headers']['php-auth-pw'])) { + $this->data['request_headers']['php-auth-pw'] = '******'; + } + + if (isset($this->data['request_server']['PHP_AUTH_PW'])) { + $this->data['request_server']['PHP_AUTH_PW'] = '******'; + } + + if (isset($this->data['request_request']['_password'])) { + $encodedPassword = rawurlencode($this->data['request_request']['_password']); + $content = str_replace('_password='.$encodedPassword, '_password=******', $content); + $this->data['request_request']['_password'] = '******'; + } + + $this->data['content'] = $content; + + foreach ($this->data as $key => $value) { + if (!\is_array($value)) { + continue; + } + if ('request_headers' === $key || 'response_headers' === $key) { + $this->data[$key] = array_map(fn ($v) => isset($v[0]) && !isset($v[1]) ? $v[0] : $v, $value); + } + } + + if (isset($this->controllers[$request])) { + $this->data['controller'] = $this->parseController($this->controllers[$request]); + unset($this->controllers[$request]); + } + + if ($request->attributes->has('_redirected') && $redirectCookie = $request->cookies->get('sf_redirect')) { + $this->data['redirect'] = json_decode($redirectCookie, true); + + $response->headers->clearCookie('sf_redirect'); + } + + if ($response->isRedirect()) { + $response->headers->setCookie(new Cookie( + 'sf_redirect', + json_encode([ + 'token' => $response->headers->get('x-debug-token'), + 'route' => $request->attributes->get('_route', 'n/a'), + 'method' => $request->getMethod(), + 'controller' => $this->parseController($request->attributes->get('_controller')), + 'status_code' => $statusCode, + 'status_text' => Response::$statusTexts[$statusCode], + ]), + 0, '/', null, $request->isSecure(), true, false, 'lax' + )); + } + + $this->data['identifier'] = $this->data['route'] ?: (\is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']); + + if ($response->headers->has('x-previous-debug-token')) { + $this->data['forward_token'] = $response->headers->get('x-previous-debug-token'); + } + } + + public function lateCollect(): void + { + $this->data = $this->cloneVar($this->data); + } + + public function reset(): void + { + parent::reset(); + $this->controllers = new \SplObjectStorage(); + $this->sessionUsages = []; + } + + public function getMethod(): string + { + return $this->data['method']; + } + + public function getPathInfo(): string + { + return $this->data['path_info']; + } + + public function getRequestRequest(): ParameterBag + { + return new ParameterBag($this->data['request_request']->getValue()); + } + + public function getRequestQuery(): ParameterBag + { + return new ParameterBag($this->data['request_query']->getValue()); + } + + public function getRequestFiles(): ParameterBag + { + return new ParameterBag($this->data['request_files']->getValue()); + } + + public function getRequestHeaders(): ParameterBag + { + return new ParameterBag($this->data['request_headers']->getValue()); + } + + public function getRequestServer(bool $raw = false): ParameterBag + { + return new ParameterBag($this->data['request_server']->getValue($raw)); + } + + public function getRequestCookies(bool $raw = false): ParameterBag + { + return new ParameterBag($this->data['request_cookies']->getValue($raw)); + } + + public function getRequestAttributes(): ParameterBag + { + return new ParameterBag($this->data['request_attributes']->getValue()); + } + + public function getResponseHeaders(): ParameterBag + { + return new ParameterBag($this->data['response_headers']->getValue()); + } + + public function getResponseCookies(): ParameterBag + { + return new ParameterBag($this->data['response_cookies']->getValue()); + } + + public function getSessionMetadata(): array + { + return $this->data['session_metadata']->getValue(); + } + + public function getSessionAttributes(): array + { + return $this->data['session_attributes']->getValue(); + } + + public function getStatelessCheck(): bool + { + return $this->data['stateless_check']; + } + + public function getSessionUsages(): Data|array + { + return $this->data['session_usages']; + } + + public function getFlashes(): array + { + return $this->data['flashes']->getValue(); + } + + /** + * @return string|resource + */ + public function getContent() + { + return $this->data['content']; + } + + public function isJsonRequest(): bool + { + return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']); + } + + public function getPrettyJson(): ?string + { + $decoded = json_decode($this->getContent()); + + return \JSON_ERROR_NONE === json_last_error() ? json_encode($decoded, \JSON_PRETTY_PRINT) : null; + } + + public function getContentType(): string + { + return $this->data['content_type']; + } + + public function getStatusText(): string + { + return $this->data['status_text']; + } + + public function getStatusCode(): int + { + return $this->data['status_code']; + } + + public function getFormat(): string + { + return $this->data['format']; + } + + public function getLocale(): string + { + return $this->data['locale']; + } + + public function getDotenvVars(): ParameterBag + { + return new ParameterBag($this->data['dotenv_vars']->getValue()); + } + + /** + * Gets the route name. + * + * The _route request attributes is automatically set by the Router Matcher. + */ + public function getRoute(): string + { + return $this->data['route']; + } + + public function getIdentifier(): string + { + return $this->data['identifier']; + } + + /** + * Gets the route parameters. + * + * The _route_params request attributes is automatically set by the RouterListener. + */ + public function getRouteParams(): array + { + return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : []; + } + + /** + * Gets the parsed controller. + * + * @return array|string|Data The controller as a string or array of data + * with keys 'class', 'method', 'file' and 'line' + */ + public function getController(): array|string|Data + { + return $this->data['controller']; + } + + /** + * Gets the previous request attributes. + * + * @return array|Data|false A legacy array of data from the previous redirection response + * or false otherwise + */ + public function getRedirect(): array|Data|false + { + return $this->data['redirect'] ?? false; + } + + public function getForwardToken(): ?string + { + return $this->data['forward_token'] ?? null; + } + + public function onKernelController(ControllerEvent $event): void + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + public function onKernelResponse(ResponseEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + if ($event->getRequest()->cookies->has('sf_redirect')) { + $event->getRequest()->attributes->set('_redirected', true); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER => 'onKernelController', + KernelEvents::RESPONSE => 'onKernelResponse', + ]; + } + + public function getName(): string + { + return 'request'; + } + + public function collectSessionUsage(): void + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + + $traceEndIndex = \count($trace) - 1; + for ($i = $traceEndIndex; $i > 0; --$i) { + if (null !== ($class = $trace[$i]['class'] ?? null) && (is_subclass_of($class, SessionInterface::class) || is_subclass_of($class, SessionBagInterface::class))) { + $traceEndIndex = $i; + break; + } + } + + if ((\count($trace) - 1) === $traceEndIndex) { + return; + } + + // Remove part of the backtrace that belongs to session only + array_splice($trace, 0, $traceEndIndex); + + // Merge identical backtraces generated by internal call reports + $name = sprintf('%s:%s', $trace[1]['class'] ?? $trace[0]['file'], $trace[0]['line']); + if (!\array_key_exists($name, $this->sessionUsages)) { + $this->sessionUsages[$name] = [ + 'name' => $name, + 'file' => $trace[0]['file'], + 'line' => $trace[0]['line'], + 'trace' => $trace, + ]; + } + } + + /** + * @return array|string An array of controller data or a simple string + */ + private function parseController(array|object|string|null $controller): array|string + { + if (\is_string($controller) && str_contains($controller, '::')) { + $controller = explode('::', $controller); + } + + if (\is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + + return [ + 'class' => \is_object($controller[0]) ? get_debug_type($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } catch (\ReflectionException) { + if (\is_callable($controller)) { + // using __call or __callStatic + return [ + 'class' => \is_object($controller[0]) ? get_debug_type($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => 'n/a', + 'line' => 'n/a', + ]; + } + } + } + + if ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + + $controller = [ + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + if ($r->isAnonymous()) { + return $controller; + } + $controller['method'] = $r->name; + + if ($class = $r->getClosureCalledClass()) { + $controller['class'] = $class->name; + } else { + return $r->name; + } + + return $controller; + } + + if (\is_object($controller)) { + $r = new \ReflectionClass($controller); + + return [ + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + + return \is_string($controller) ? $controller : 'n/a'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php new file mode 100644 index 0000000..cb7b233 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\ControllerEvent; + +/** + * @author Fabien Potencier + */ +class RouterDataCollector extends DataCollector +{ + /** + * @var \SplObjectStorage + */ + protected \SplObjectStorage $controllers; + + public function __construct() + { + $this->reset(); + } + + /** + * @final + */ + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + if ($response instanceof RedirectResponse) { + $this->data['redirect'] = true; + $this->data['url'] = $response->getTargetUrl(); + + if ($this->controllers->contains($request)) { + $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); + } + } + + unset($this->controllers[$request]); + } + + public function reset(): void + { + $this->controllers = new \SplObjectStorage(); + + $this->data = [ + 'redirect' => false, + 'url' => null, + 'route' => null, + ]; + } + + protected function guessRoute(Request $request, string|object|array $controller): string + { + return 'n/a'; + } + + /** + * Remembers the controller associated to each request. + */ + public function onKernelController(ControllerEvent $event): void + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + /** + * @return bool Whether this request will result in a redirect + */ + public function getRedirect(): bool + { + return $this->data['redirect']; + } + + public function getTargetUrl(): ?string + { + return $this->data['url']; + } + + public function getTargetRoute(): ?string + { + return $this->data['route']; + } + + public function getName(): string + { + return 'router'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php new file mode 100644 index 0000000..c9b830f --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Stopwatch\StopwatchEvent; + +/** + * @author Fabien Potencier + * + * @final + */ +class TimeDataCollector extends DataCollector implements LateDataCollectorInterface +{ + public function __construct( + private readonly ?KernelInterface $kernel = null, + private readonly ?Stopwatch $stopwatch = null, + ) { + $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + if (null !== $this->kernel) { + $startTime = $this->kernel->getStartTime(); + } else { + $startTime = $request->server->get('REQUEST_TIME_FLOAT'); + } + + $this->data = [ + 'token' => $request->attributes->get('_stopwatch_token'), + 'start_time' => $startTime * 1000, + 'events' => [], + 'stopwatch_installed' => class_exists(Stopwatch::class, false), + ]; + } + + public function reset(): void + { + $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; + + $this->stopwatch?->reset(); + } + + public function lateCollect(): void + { + if (null !== $this->stopwatch && isset($this->data['token'])) { + $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); + } + unset($this->data['token']); + } + + /** + * @param StopwatchEvent[] $events The request events + */ + public function setEvents(array $events): void + { + foreach ($events as $event) { + $event->ensureStopped(); + } + + $this->data['events'] = $events; + } + + /** + * @return StopwatchEvent[] + */ + public function getEvents(): array + { + return $this->data['events']; + } + + /** + * Gets the request elapsed time. + */ + public function getDuration(): float + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + $lastEvent = $this->data['events']['__section__']; + + return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime(); + } + + /** + * Gets the initialization time. + * + * This is the time spent until the beginning of the request handling. + */ + public function getInitTime(): float + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); + } + + public function getStartTime(): float + { + return $this->data['start_time']; + } + + public function isStopwatchInstalled(): bool + { + return $this->data['stopwatch_installed']; + } + + public function getName(): string + { + return 'time'; + } +} diff --git a/vendor/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php b/vendor/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php new file mode 100644 index 0000000..38650be --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/ErrorHandlerConfigurator.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Psr\Log\LoggerInterface; +use Symfony\Component\ErrorHandler\ErrorHandler; + +/** + * Configures the error handler. + * + * @final + * + * @internal + */ +class ErrorHandlerConfigurator +{ + private array|int|null $levels; + private ?int $throwAt; + + /** + * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param bool $scope Enables/disables scoping mode + */ + public function __construct( + private ?LoggerInterface $logger = null, + array|int|null $levels = \E_ALL, + ?int $throwAt = \E_ALL, + private bool $scream = true, + private bool $scope = true, + private ?LoggerInterface $deprecationLogger = null, + ) { + $this->levels = $levels ?? \E_ALL; + $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); + } + + /** + * Configures the error handler. + */ + public function configure(ErrorHandler $handler): void + { + if ($this->logger || $this->deprecationLogger) { + $this->setDefaultLoggers($handler); + if (\is_array($this->levels)) { + $levels = 0; + foreach ($this->levels as $type => $log) { + $levels |= $type; + } + } else { + $levels = $this->levels; + } + + if ($this->scream) { + $handler->screamAt($levels); + } + if ($this->scope) { + $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); + } else { + $handler->scopeAt(0, true); + } + $this->logger = $this->deprecationLogger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); + } + } + + private function setDefaultLoggers(ErrorHandler $handler): void + { + if (\is_array($this->levels)) { + $levelsDeprecatedOnly = []; + $levelsWithoutDeprecated = []; + foreach ($this->levels as $type => $log) { + if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) { + $levelsDeprecatedOnly[$type] = $log; + } else { + $levelsWithoutDeprecated[$type] = $log; + } + } + } else { + $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED); + $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED; + } + + $defaultLoggerLevels = $this->levels; + if ($this->deprecationLogger && $levelsDeprecatedOnly) { + $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly); + $defaultLoggerLevels = $levelsWithoutDeprecated; + } + + if ($this->logger && $defaultLoggerLevels) { + $handler->setDefaultLogger($this->logger, $defaultLoggerLevels); + } + } +} diff --git a/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000..2b2e2e2 --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher extends BaseTraceableEventDispatcher +{ + protected function beforeDispatch(string $eventName, object $event): void + { + switch ($eventName) { + case KernelEvents::REQUEST: + $event->getRequest()->attributes->set('_stopwatch_token', substr(hash('xxh128', uniqid(mt_rand(), true)), 0, 6)); + $this->stopwatch->openSection(); + break; + case KernelEvents::VIEW: + case KernelEvents::RESPONSE: + // stop only if a controller has been executed + if ($this->stopwatch->isStarted('controller')) { + $this->stopwatch->stop('controller'); + } + break; + case KernelEvents::TERMINATE: + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { + break; + } + // There is a very special case when using built-in AppCache class as kernel wrapper, in the case + // of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A]. + // In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID + // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception + // which must be caught. + try { + $this->stopwatch->openSection($sectionId); + } catch (\LogicException) { + } + break; + } + } + + protected function afterDispatch(string $eventName, object $event): void + { + switch ($eventName) { + case KernelEvents::CONTROLLER_ARGUMENTS: + $this->stopwatch->start('controller', 'section'); + break; + case KernelEvents::RESPONSE: + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { + break; + } + $this->stopwatch->stopSection($sectionId); + break; + case KernelEvents::TERMINATE: + // In the special case described in the `preDispatch` method above, the `$token` section + // does not exist, then closing it throws an exception which must be caught. + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { + break; + } + try { + $this->stopwatch->stopSection($sectionId); + } catch (\LogicException) { + } + break; + } + } +} diff --git a/vendor/symfony/http-kernel/Debug/VirtualRequestStack.php b/vendor/symfony/http-kernel/Debug/VirtualRequestStack.php new file mode 100644 index 0000000..ded9aae --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/VirtualRequestStack.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * A stack able to deal with virtual requests. + * + * @internal + * + * @author Jules Pietri + */ +final class VirtualRequestStack extends RequestStack +{ + public function __construct( + private readonly RequestStack $decorated, + ) { + } + + public function push(Request $request): void + { + if ($request->attributes->has('_virtual_type')) { + if ($this->decorated->getCurrentRequest()) { + throw new \LogicException('Cannot mix virtual and HTTP requests.'); + } + + parent::push($request); + + return; + } + + $this->decorated->push($request); + } + + public function pop(): ?Request + { + return $this->decorated->pop() ?? parent::pop(); + } + + public function getCurrentRequest(): ?Request + { + return $this->decorated->getCurrentRequest() ?? parent::getCurrentRequest(); + } + + public function getMainRequest(): ?Request + { + return $this->decorated->getMainRequest() ?? parent::getMainRequest(); + } + + public function getParentRequest(): ?Request + { + return $this->decorated->getParentRequest() ?? parent::getParentRequest(); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php new file mode 100644 index 0000000..c8ed6b8 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\ErrorHandler\DebugClassLoader; +use Symfony\Component\HttpKernel\Kernel; + +trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s" class is deprecated since Symfony 7.1 and will be removed in 8.0.', AddAnnotatedClassesToCachePass::class); + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier + * + * @deprecated since Symfony 7.1, to be removed in 8.0 + */ +class AddAnnotatedClassesToCachePass implements CompilerPassInterface +{ + public function __construct( + private Kernel $kernel, + ) { + } + + public function process(ContainerBuilder $container): void + { + $annotatedClasses = []; + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof Extension) { + $annotatedClasses[] = $extension->getAnnotatedClassesToCompile(); + } + } + + $annotatedClasses = array_merge($this->kernel->getAnnotatedClassesToCompile(), ...$annotatedClasses); + + $existingClasses = $this->getClassesInComposerClassMaps(); + + $annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses); + $this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses)); + } + + /** + * Expands the given class patterns using a list of existing classes. + * + * @param array $patterns The class patterns to expand + * @param array $classes The existing classes to match against the patterns + */ + private function expandClasses(array $patterns, array $classes): array + { + $expanded = []; + + // Explicit classes declared in the patterns are returned directly + foreach ($patterns as $key => $pattern) { + if (!str_ends_with($pattern, '\\') && !str_contains($pattern, '*')) { + unset($patterns[$key]); + $expanded[] = ltrim($pattern, '\\'); + } + } + + // Match patterns with the classes list + $regexps = $this->patternsToRegexps($patterns); + + foreach ($classes as $class) { + $class = ltrim($class, '\\'); + + if ($this->matchAnyRegexps($class, $regexps)) { + $expanded[] = $class; + } + } + + return array_unique($expanded); + } + + private function getClassesInComposerClassMaps(): array + { + $classes = []; + + foreach (spl_autoload_functions() as $function) { + if (!\is_array($function)) { + continue; + } + + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + } + + if (\is_array($function) && $function[0] instanceof ClassLoader) { + $classes += array_filter($function[0]->getClassMap()); + } + } + + return array_keys($classes); + } + + private function patternsToRegexps(array $patterns): array + { + $regexps = []; + + foreach ($patterns as $pattern) { + // Escape user input + $regex = preg_quote(ltrim($pattern, '\\')); + + // Wildcards * and ** + $regex = strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']); + + // If this class does not end by a slash, anchor the end + if (!str_ends_with($regex, '\\')) { + $regex .= '$'; + } + + $regexps[] = '{^\\\\'.$regex.'}'; + } + + return $regexps; + } + + private function matchAnyRegexps(string $class, array $regexps): bool + { + $isTest = str_contains($class, 'Test'); + + foreach ($regexps as $regex) { + if ($isTest && !str_contains($regex, 'Test')) { + continue; + } + + if (preg_match($regex, '\\'.$class)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php new file mode 100644 index 0000000..714fdb7 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This extension sub-class provides first-class integration with the + * Config/Definition Component. + * + * You can use this as base class if + * + * a) you use the Config/Definition component for configuration, + * b) your configuration class is named "Configuration", and + * c) the configuration class resides in the DependencyInjection sub-folder. + * + * @author Johannes M. Schmitt + */ +abstract class ConfigurableExtension extends Extension +{ + final public function load(array $configs, ContainerBuilder $container): void + { + $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); + } + + /** + * Configures the passed container according to the merged configuration. + */ + abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void; +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php new file mode 100644 index 0000000..d760e3b --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Gathers and configures the argument value resolvers. + * + * @author Iltar van der Berg + */ +class ControllerArgumentValueResolverPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('argument_resolver')) { + return; + } + + $definitions = $container->getDefinitions(); + $namedResolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.targeted_value_resolver', 'name', needsIndexes: true), $container); + $resolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.argument_value_resolver', 'name', needsIndexes: true), $container); + + foreach ($resolvers as $name => $resolver) { + if ($definitions[(string) $resolver]->hasTag('controller.targeted_value_resolver')) { + unset($resolvers[$name]); + } else { + $namedResolvers[$name] ??= clone $resolver; + } + } + + if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has('debug.stopwatch')) { + foreach ($resolvers as $name => $resolver) { + $resolvers[$name] = new Reference('.debug.value_resolver.'.$resolver); + $container->register('.debug.value_resolver.'.$resolver, TraceableValueResolver::class) + ->setArguments([$resolver, new Reference('debug.stopwatch')]); + } + foreach ($namedResolvers as $name => $resolver) { + $namedResolvers[$name] = new Reference('.debug.value_resolver.'.$resolver); + $container->register('.debug.value_resolver.'.$resolver, TraceableValueResolver::class) + ->setArguments([$resolver, new Reference('debug.stopwatch')]); + } + } + + $container + ->getDefinition('argument_resolver') + ->replaceArgument(1, new IteratorArgument(array_values($resolvers))) + ->setArgument(2, new ServiceLocatorArgument($namedResolvers)) + ; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/Extension.php b/vendor/symfony/http-kernel/DependencyInjection/Extension.php new file mode 100644 index 0000000..87b81a8 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/Extension.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; + +/** + * Allow adding classes to the class cache. + * + * @author Fabien Potencier + * + * @internal since Symfony 7.1, to be deprecated in 8.1; use Symfony\Component\DependencyInjection\Extension\Extension instead + */ +abstract class Extension extends BaseExtension +{ + private array $annotatedClasses = []; + + /** + * Gets the annotated classes to cache. + * + * @return string[] + * + * @deprecated since Symfony 7.1, to be removed in 8.0 + */ + public function getAnnotatedClassesToCompile(): array + { + trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s()" method is deprecated since Symfony 7.1 and will be removed in 8.0.', __METHOD__); + + return $this->annotatedClasses; + } + + /** + * Adds annotated classes to the class cache. + * + * @param string[] $annotatedClasses An array of class patterns + * + * @deprecated since Symfony 7.1, to be removed in 8.0 + */ + public function addAnnotatedClassesToCompile(array $annotatedClasses): void + { + trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s()" method is deprecated since Symfony 7.1 and will be removed in 8.0.', __METHOD__); + + $this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php new file mode 100644 index 0000000..1d49c24 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies. + * + * @author Fabien Potencier + */ +class FragmentRendererPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('fragment.handler')) { + return; + } + + $definition = $container->getDefinition('fragment.handler'); + $renderers = []; + foreach ($container->findTaggedServiceIds('kernel.fragment_renderer', true) as $id => $tags) { + $def = $container->getDefinition($id); + $class = $container->getParameterBag()->resolveValue($def->getClass()); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(FragmentRendererInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, FragmentRendererInterface::class)); + } + + foreach ($tags as $tag) { + $renderers[$tag['alias']] = new Reference($id); + } + } + + $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers)); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php new file mode 100644 index 0000000..d8ff64b --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; + +/** + * Lazily loads fragment renderers from the dependency injection container. + * + * @author Fabien Potencier + */ +class LazyLoadingFragmentHandler extends FragmentHandler +{ + /** + * @var array + */ + private array $initialized = []; + + public function __construct( + private ContainerInterface $container, + RequestStack $requestStack, + bool $debug = false, + ) { + parent::__construct($requestStack, [], $debug); + } + + public function render(string|ControllerReference $uri, string $renderer = 'inline', array $options = []): ?string + { + if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) { + $this->addRenderer($this->container->get($renderer)); + $this->initialized[$renderer] = true; + } + + return parent::render($uri, $renderer, $options); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/LoggerPass.php b/vendor/symfony/http-kernel/DependencyInjection/LoggerPass.php new file mode 100644 index 0000000..2cc7595 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/LoggerPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Log\Logger; + +/** + * Registers the default logger if necessary. + * + * @author Kévin Dunglas + */ +class LoggerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $container->setAlias(LoggerInterface::class, 'logger'); + + if ($container->has('logger')) { + return; + } + + if ($debug = $container->getParameter('kernel.debug')) { + $debug = $container->hasParameter('kernel.runtime_mode.web') + ? $container->getParameter('kernel.runtime_mode.web') + : !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true); + } + + $container->register('logger', Logger::class) + ->setArguments([null, null, null, new Reference(RequestStack::class), $debug]); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php new file mode 100644 index 0000000..8336d8c --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass as BaseMergeExtensionConfigurationPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Ensures certain extensions are always loaded. + * + * @author Kris Wallsmith + */ +class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass +{ + /** + * @param string[] $extensions + */ + public function __construct( + private array $extensions, + ) { + } + + public function process(ContainerBuilder $container): void + { + foreach ($this->extensions as $extension) { + if (!\count($container->getExtensionConfig($extension))) { + $container->loadFromExtension($extension, []); + } + } + + parent::process($container); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php new file mode 100644 index 0000000..8237d8d --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; +use Symfony\Component\DependencyInjection\Attribute\Target; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * Creates the service-locators required by ServiceValueResolver. + * + * @author Nicolas Grekas + */ +class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('argument_resolver.service') && !$container->hasDefinition('argument_resolver.not_tagged_controller')) { + return; + } + + $parameterBag = $container->getParameterBag(); + $controllers = []; + $controllerClasses = []; + + $publicAliases = []; + foreach ($container->getAliases() as $id => $alias) { + if ($alias->isPublic() && !$alias->isPrivate()) { + $publicAliases[(string) $alias][] = $id; + } + } + + $emptyAutowireAttributes = class_exists(Autowire::class) ? null : []; + + foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) { + $def = $container->getDefinition($id); + $def->setPublic(true); + $def->setLazy(false); + $class = $def->getClass(); + $autowire = $def->isAutowired(); + $bindings = $def->getBindings(); + + // resolve service class, taking parent definitions into account + while ($def instanceof ChildDefinition) { + $def = $container->findDefinition($def->getParent()); + $class = $class ?: $def->getClass(); + $bindings += $def->getBindings(); + } + $class = $parameterBag->resolveValue($class); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + $controllerClasses[] = $class; + + // get regular public methods + $methods = []; + $arguments = []; + foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) { + if ('setContainer' === $r->name) { + continue; + } + if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) { + $methods[strtolower($r->name)] = [$r, $r->getParameters()]; + } + } + + // validate and collect explicit per-actions and per-arguments service references + foreach ($tags as $attributes) { + if (!isset($attributes['action']) && !isset($attributes['argument']) && !isset($attributes['id'])) { + $autowire = true; + continue; + } + foreach (['action', 'argument', 'id'] as $k) { + if (!isset($attributes[$k][0])) { + throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "controller.service_arguments" %s for service "%s".', $k, json_encode($attributes, \JSON_UNESCAPED_UNICODE), $id)); + } + } + if (!isset($methods[$action = strtolower($attributes['action'])])) { + throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "controller.service_arguments" for service "%s": no public "%s()" method found on class "%s".', $id, $attributes['action'], $class)); + } + [$r, $parameters] = $methods[$action]; + $found = false; + + foreach ($parameters as $p) { + if ($attributes['argument'] === $p->name) { + if (!isset($arguments[$r->name][$p->name])) { + $arguments[$r->name][$p->name] = $attributes['id']; + } + $found = true; + break; + } + } + + if (!$found) { + throw new InvalidArgumentException(sprintf('Invalid "controller.service_arguments" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $id, $r->name, $attributes['argument'], $class)); + } + } + + foreach ($methods as [$r, $parameters]) { + /** @var \ReflectionMethod $r */ + + // create a per-method map of argument-names to service/type-references + $args = []; + $erroredIds = 0; + foreach ($parameters as $p) { + /** @var \ReflectionParameter $p */ + $type = preg_replace('/(^|[(|&])\\\\/', '\1', $target = ltrim(ProxyHelper::exportType($p) ?? '', '?')); + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + $autowireAttributes = $autowire ? $emptyAutowireAttributes : []; + $parsedName = $p->name; + $k = null; + + if (isset($arguments[$r->name][$p->name])) { + $target = $arguments[$r->name][$p->name]; + if ('?' !== $target[0]) { + $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('' === $target = (string) substr($target, 1)) { + throw new InvalidArgumentException(sprintf('A "controller.service_arguments" tag must have non-empty "id" attributes for service "%s".', $id)); + } elseif ($p->allowsNull() && !$p->isOptional()) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } + } elseif (isset($bindings[$bindingName = $type.' $'.$name = Target::parseName($p, $k, $parsedName)]) + || isset($bindings[$bindingName = $type.' $'.$parsedName]) + || isset($bindings[$bindingName = '$'.$name]) + || isset($bindings[$bindingName = $type]) + ) { + $binding = $bindings[$bindingName]; + + [$bindingValue, $bindingId, , $bindingType, $bindingFile] = $binding->getValues(); + $binding->setValues([$bindingValue, $bindingId, true, $bindingType, $bindingFile]); + + $args[$p->name] = $bindingValue; + + continue; + } elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) { + continue; + } elseif (is_subclass_of($type, \UnitEnum::class)) { + // do not attempt to register enum typed arguments if not already present in bindings + continue; + } elseif (!$p->allowsNull()) { + $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; + } + + if (Request::class === $type || SessionInterface::class === $type || Response::class === $type) { + continue; + } + + if ($autowireAttributes) { + $attribute = $autowireAttributes[0]->newInstance(); + $value = $parameterBag->resolveValue($attribute->value); + + if ($attribute instanceof AutowireCallable) { + $args[$p->name] = $attribute->buildDefinition($value, $type, $p); + } elseif ($value instanceof Reference) { + $args[$p->name] = $type ? new TypedReference($value, $type, $invalidBehavior, $p->name) : new Reference($value, $invalidBehavior); + } else { + $args[$p->name] = new Reference('.value.'.$container->hash($value)); + $container->register((string) $args[$p->name], 'mixed') + ->setFactory('current') + ->addArgument([$value]); + } + + continue; + } + + if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) { + $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); + + // see if the type-hint lives in the same namespace as the controller + if (0 === strncmp($type, $class, strrpos($class, '\\'))) { + $message .= ' Did you forget to add a use statement?'; + } + + $container->register($erroredId = '.errored.'.$container->hash($message), $type) + ->addError($message); + + $args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE); + ++$erroredIds; + } else { + $target = preg_replace('/(^|[(|&])\\\\/', '\1', $target); + $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior); + } + } + // register the maps as a per-method service-locators + if ($args) { + $controllers[$id.'::'.$r->name] = ServiceLocatorTagPass::register($container, $args, \count($args) !== $erroredIds ? $id.'::'.$r->name.'()' : null); + + foreach ($publicAliases[$id] ?? [] as $alias) { + $controllers[$alias.'::'.$r->name] = clone $controllers[$id.'::'.$r->name]; + } + } + } + } + + $controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers); + + if ($container->hasDefinition('argument_resolver.service')) { + $container->getDefinition('argument_resolver.service') + ->replaceArgument(0, $controllerLocatorRef); + } + + if ($container->hasDefinition('argument_resolver.not_tagged_controller')) { + $container->getDefinition('argument_resolver.not_tagged_controller') + ->replaceArgument(0, $controllerLocatorRef); + } + + $container->setAlias('argument_resolver.controller_locator', (string) $controllerLocatorRef); + + if ($container->hasDefinition('controller_resolver')) { + $container->getDefinition('controller_resolver') + ->addMethodCall('allowControllers', [array_unique($controllerClasses)]); + } + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php b/vendor/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php new file mode 100644 index 0000000..3c7d5ac --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Register all services that have the "kernel.locale_aware" tag into the listener. + * + * @author Pierre Bobiet + */ +class RegisterLocaleAwareServicesPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('locale_aware_listener')) { + return; + } + + $services = []; + + foreach ($container->findTaggedServiceIds('kernel.locale_aware') as $id => $tags) { + $services[] = new Reference($id); + } + + if (!$services) { + $container->removeDefinition('locale_aware_listener'); + + return; + } + + $container + ->getDefinition('locale_aware_listener') + ->setArgument(0, new IteratorArgument($services)) + ; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php new file mode 100644 index 0000000..b2e7832 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes empty service-locators registered for ServiceValueResolver. + * + * @author Nicolas Grekas + */ +class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $controllerLocator = $container->findDefinition('argument_resolver.controller_locator'); + $controllers = $controllerLocator->getArgument(0); + + foreach ($controllers as $controller => $argumentRef) { + $argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]); + + if ($argumentLocator->getFactory()) { + $argumentLocator = $container->getDefinition($argumentLocator->getFactory()[0]); + } + + if (!$argumentLocator->getArgument(0)) { + // remove empty argument locators + $reason = sprintf('Removing service-argument resolver for controller "%s": no corresponding services exist for the referenced types.', $controller); + } else { + // any methods listed for call-at-instantiation cannot be actions + $reason = false; + [$id, $action] = explode('::', $controller); + + if ($container->hasAlias($id)) { + continue; + } + + $controllerDef = $container->getDefinition($id); + foreach ($controllerDef->getMethodCalls() as [$method]) { + if (0 === strcasecmp($action, $method)) { + $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); + break; + } + } + if (!$reason) { + // see Symfony\Component\HttpKernel\Controller\ContainerControllerResolver + $controllers[$id.':'.$action] = $argumentRef; + + if ('__invoke' === $action) { + $controllers[$id] = $argumentRef; + } + continue; + } + } + + unset($controllers[$controller]); + $container->log($this, $reason); + } + + $controllerLocator->replaceArgument(0, $controllers); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ResettableServicePass.php b/vendor/symfony/http-kernel/DependencyInjection/ResettableServicePass.php new file mode 100644 index 0000000..7a09418 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ResettableServicePass.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Alexander M. Turek + */ +class ResettableServicePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->has('services_resetter')) { + return; + } + + $services = $methods = []; + + foreach ($container->findTaggedServiceIds('kernel.reset', true) as $id => $tags) { + $services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE); + + foreach ($tags as $attributes) { + if (!isset($attributes['method'])) { + throw new RuntimeException(sprintf('Tag "kernel.reset" requires the "method" attribute to be set on service "%s".', $id)); + } + + if (!isset($methods[$id])) { + $methods[$id] = []; + } + + if ('ignore' === ($attributes['on_invalid'] ?? null)) { + $attributes['method'] = '?'.$attributes['method']; + } + + $methods[$id][] = $attributes['method']; + } + } + + if (!$services) { + $container->removeAlias('services_resetter'); + $container->removeDefinition('services_resetter'); + + return; + } + + $container->findDefinition('services_resetter') + ->setArgument(0, new IteratorArgument($services)) + ->setArgument(1, $methods); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ServicesResetter.php b/vendor/symfony/http-kernel/DependencyInjection/ServicesResetter.php new file mode 100644 index 0000000..a98f280 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ServicesResetter.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Resets provided services. + * + * @author Alexander M. Turek + * @author Nicolas Grekas + * + * @internal + */ +class ServicesResetter implements ResetInterface +{ + /** + * @param \Traversable $resettableServices + * @param array $resetMethods + */ + public function __construct( + private \Traversable $resettableServices, + private array $resetMethods, + ) { + } + + public function reset(): void + { + foreach ($this->resettableServices as $id => $service) { + if ($service instanceof LazyObjectInterface && !$service->isLazyObjectInitialized(true)) { + continue; + } + + if ($service instanceof LazyLoadingInterface && !$service->isProxyInitialized()) { + continue; + } + + foreach ((array) $this->resetMethods[$id] as $resetMethod) { + if ('?' === $resetMethod[0] && !method_exists($service, $resetMethod = substr($resetMethod, 1))) { + continue; + } + + $service->$resetMethod(); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Event/ControllerArgumentsEvent.php b/vendor/symfony/http-kernel/Event/ControllerArgumentsEvent.php new file mode 100644 index 0000000..f0273fd --- /dev/null +++ b/vendor/symfony/http-kernel/Event/ControllerArgumentsEvent.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows filtering of controller arguments. + * + * You can call getController() to retrieve the controller and getArguments + * to retrieve the current arguments. With setArguments() you can replace + * arguments that are used to call the controller. + * + * Arguments set in the event must be compatible with the signature of the + * controller. + * + * @author Christophe Coevoet + */ +final class ControllerArgumentsEvent extends KernelEvent +{ + private ControllerEvent $controllerEvent; + private array $namedArguments; + + public function __construct( + HttpKernelInterface $kernel, + callable|ControllerEvent $controller, + private array $arguments, + Request $request, + ?int $requestType, + ) { + parent::__construct($kernel, $request, $requestType); + + if (!$controller instanceof ControllerEvent) { + $controller = new ControllerEvent($kernel, $controller, $request, $requestType); + } + + $this->controllerEvent = $controller; + } + + public function getController(): callable + { + return $this->controllerEvent->getController(); + } + + /** + * @param array>|null $attributes + */ + public function setController(callable $controller, ?array $attributes = null): void + { + $this->controllerEvent->setController($controller, $attributes); + unset($this->namedArguments); + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + unset($this->namedArguments); + } + + public function getNamedArguments(): array + { + if (isset($this->namedArguments)) { + return $this->namedArguments; + } + + $namedArguments = []; + $arguments = $this->arguments; + + foreach ($this->controllerEvent->getControllerReflector()->getParameters() as $i => $param) { + if ($param->isVariadic()) { + $namedArguments[$param->name] = \array_slice($arguments, $i); + break; + } + if (\array_key_exists($i, $arguments)) { + $namedArguments[$param->name] = $arguments[$i]; + } elseif ($param->isDefaultvalueAvailable()) { + $namedArguments[$param->name] = $param->getDefaultValue(); + } + } + + return $this->namedArguments = $namedArguments; + } + + /** + * @template T of class-string|null + * + * @param T $className + * + * @return array>|list + * + * @psalm-return (T is null ? array> : list) + */ + public function getAttributes(?string $className = null): array + { + return $this->controllerEvent->getAttributes($className); + } +} diff --git a/vendor/symfony/http-kernel/Event/ControllerEvent.php b/vendor/symfony/http-kernel/Event/ControllerEvent.php new file mode 100644 index 0000000..3d03e6d --- /dev/null +++ b/vendor/symfony/http-kernel/Event/ControllerEvent.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows filtering of a controller callable. + * + * You can call getController() to retrieve the current controller. With + * setController() you can set a new controller that is used in the processing + * of the request. + * + * Controllers should be callables. + * + * @author Bernhard Schussek + */ +final class ControllerEvent extends KernelEvent +{ + private string|array|object $controller; + private \ReflectionFunctionAbstract $controllerReflector; + private array $attributes; + + public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->setController($controller); + } + + public function getController(): callable + { + return $this->controller; + } + + public function getControllerReflector(): \ReflectionFunctionAbstract + { + return $this->controllerReflector; + } + + /** + * @param array>|null $attributes + */ + public function setController(callable $controller, ?array $attributes = null): void + { + if (null !== $attributes) { + $this->attributes = $attributes; + } + + if (isset($this->controller) && ($controller instanceof \Closure ? $controller == $this->controller : $controller === $this->controller)) { + $this->controller = $controller; + + return; + } + + if (null === $attributes) { + unset($this->attributes); + } + + if (\is_array($controller) && method_exists(...$controller)) { + $this->controllerReflector = new \ReflectionMethod(...$controller); + } elseif (\is_string($controller) && str_contains($controller, '::')) { + $this->controllerReflector = new \ReflectionMethod(...explode('::', $controller, 2)); + } else { + $this->controllerReflector = new \ReflectionFunction($controller(...)); + } + + $this->controller = $controller; + } + + /** + * @template T of class-string|null + * + * @param T $className + * + * @return array>|list + * + * @psalm-return (T is null ? array> : list) + */ + public function getAttributes(?string $className = null): array + { + if (isset($this->attributes)) { + return null === $className ? $this->attributes : $this->attributes[$className] ?? []; + } + + if (\is_array($this->controller) && method_exists(...$this->controller)) { + $class = new \ReflectionClass($this->controller[0]); + } elseif (\is_string($this->controller) && false !== $i = strpos($this->controller, '::')) { + $class = new \ReflectionClass(substr($this->controller, 0, $i)); + } else { + $class = $this->controllerReflector instanceof \ReflectionFunction && $this->controllerReflector->isAnonymous() ? null : $this->controllerReflector->getClosureCalledClass(); + } + $this->attributes = []; + + foreach (array_merge($class?->getAttributes() ?? [], $this->controllerReflector->getAttributes()) as $attribute) { + if (class_exists($attribute->getName())) { + $this->attributes[$attribute->getName()][] = $attribute->newInstance(); + } + } + + return null === $className ? $this->attributes : $this->attributes[$className] ?? []; + } +} diff --git a/vendor/symfony/http-kernel/Event/ExceptionEvent.php b/vendor/symfony/http-kernel/Event/ExceptionEvent.php new file mode 100644 index 0000000..d4f4c4d --- /dev/null +++ b/vendor/symfony/http-kernel/Event/ExceptionEvent.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to create a response for a thrown exception. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * You can also call setThrowable() to replace the thrown exception. This + * exception will be thrown if no response is set during processing of this + * event. + * + * @author Bernhard Schussek + */ +final class ExceptionEvent extends RequestEvent +{ + private \Throwable $throwable; + private bool $allowCustomResponseCode = false; + + public function __construct( + HttpKernelInterface $kernel, + Request $request, + int $requestType, + \Throwable $e, + private bool $isKernelTerminating = false, + ) { + parent::__construct($kernel, $request, $requestType); + + $this->setThrowable($e); + } + + public function getThrowable(): \Throwable + { + return $this->throwable; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + */ + public function setThrowable(\Throwable $exception): void + { + $this->throwable = $exception; + } + + /** + * Mark the event as allowing a custom response code. + */ + public function allowCustomResponseCode(): void + { + $this->allowCustomResponseCode = true; + } + + /** + * Returns true if the event allows a custom response code. + */ + public function isAllowingCustomResponseCode(): bool + { + return $this->allowCustomResponseCode; + } + + public function isKernelTerminating(): bool + { + return $this->isKernelTerminating; + } +} diff --git a/vendor/symfony/http-kernel/Event/FinishRequestEvent.php b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php new file mode 100644 index 0000000..306a7ad --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +/** + * Triggered whenever a request is fully processed. + * + * @author Benjamin Eberlei + */ +final class FinishRequestEvent extends KernelEvent +{ +} diff --git a/vendor/symfony/http-kernel/Event/KernelEvent.php b/vendor/symfony/http-kernel/Event/KernelEvent.php new file mode 100644 index 0000000..bc6643f --- /dev/null +++ b/vendor/symfony/http-kernel/Event/KernelEvent.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Base class for events dispatched in the HttpKernel component. + * + * @author Bernhard Schussek + */ +class KernelEvent extends Event +{ + /** + * @param int $requestType The request type the kernel is currently processing; one of + * HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST + */ + public function __construct( + private HttpKernelInterface $kernel, + private Request $request, + private ?int $requestType, + ) { + } + + /** + * Returns the kernel in which this event was thrown. + */ + public function getKernel(): HttpKernelInterface + { + return $this->kernel; + } + + /** + * Returns the request the kernel is currently processing. + */ + public function getRequest(): Request + { + return $this->request; + } + + /** + * Returns the request type the kernel is currently processing. + * + * @return int One of HttpKernelInterface::MAIN_REQUEST and + * HttpKernelInterface::SUB_REQUEST + */ + public function getRequestType(): int + { + return $this->requestType; + } + + /** + * Checks if this is the main request. + */ + public function isMainRequest(): bool + { + return HttpKernelInterface::MAIN_REQUEST === $this->requestType; + } +} diff --git a/vendor/symfony/http-kernel/Event/RequestEvent.php b/vendor/symfony/http-kernel/Event/RequestEvent.php new file mode 100644 index 0000000..8b5c084 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/RequestEvent.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to create a response for a request. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class RequestEvent extends KernelEvent +{ + private ?Response $response = null; + + /** + * Returns the response object. + */ + public function getResponse(): ?Response + { + return $this->response; + } + + /** + * Sets a response and stops event propagation. + */ + public function setResponse(Response $response): void + { + $this->response = $response; + + $this->stopPropagation(); + } + + /** + * Returns whether a response was set. + */ + public function hasResponse(): bool + { + return null !== $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Event/ResponseEvent.php b/vendor/symfony/http-kernel/Event/ResponseEvent.php new file mode 100644 index 0000000..65235d0 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/ResponseEvent.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to filter a Response object. + * + * You can call getResponse() to retrieve the current response. With + * setResponse() you can set a new response that will be returned to the + * browser. + * + * @author Bernhard Schussek + */ +final class ResponseEvent extends KernelEvent +{ + public function __construct( + HttpKernelInterface $kernel, + Request $request, + int $requestType, + private Response $response, + ) { + parent::__construct($kernel, $request, $requestType); + } + + public function getResponse(): Response + { + return $this->response; + } + + public function setResponse(Response $response): void + { + $this->response = $response; + } +} diff --git a/vendor/symfony/http-kernel/Event/TerminateEvent.php b/vendor/symfony/http-kernel/Event/TerminateEvent.php new file mode 100644 index 0000000..95a0bc8 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/TerminateEvent.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to execute logic after a response was sent. + * + * Since it's only triggered on main requests, the `getRequestType()` method + * will always return the value of `HttpKernelInterface::MAIN_REQUEST`. + * + * @author Jordi Boggiano + */ +final class TerminateEvent extends KernelEvent +{ + public function __construct( + HttpKernelInterface $kernel, + Request $request, + private Response $response, + ) { + parent::__construct($kernel, $request, HttpKernelInterface::MAIN_REQUEST); + } + + public function getResponse(): Response + { + return $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Event/ViewEvent.php b/vendor/symfony/http-kernel/Event/ViewEvent.php new file mode 100644 index 0000000..5f466b5 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/ViewEvent.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to create a response for the return value of a controller. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +final class ViewEvent extends RequestEvent +{ + public function __construct( + HttpKernelInterface $kernel, + Request $request, + int $requestType, + private mixed $controllerResult, + public readonly ?ControllerArgumentsEvent $controllerArgumentsEvent = null, + ) { + parent::__construct($kernel, $request, $requestType); + } + + public function getControllerResult(): mixed + { + return $this->controllerResult; + } + + public function setControllerResult(mixed $controllerResult): void + { + $this->controllerResult = $controllerResult; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php new file mode 100644 index 0000000..5c9517b --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php @@ -0,0 +1,314 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\SessionUtils; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Sets the session onto the request on the "kernel.request" event and saves + * it on the "kernel.response" event. + * + * In addition, if the session has been started it overrides the Cache-Control + * header in such a way that all caching is disabled in that case. + * If you have a scenario where caching responses with session information in + * them makes sense, you can disable this behaviour by setting the header + * AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER on the response. + * + * @author Johannes M. Schmitt + * @author Tobias Schultze + */ +abstract class AbstractSessionListener implements EventSubscriberInterface, ResetInterface +{ + public const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl'; + + /** + * @param array $sessionOptions + * + * @internal + */ + public function __construct( + private ?ContainerInterface $container = null, + private bool $debug = false, + private array $sessionOptions = [], + ) { + } + + /** + * @internal + */ + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $request = $event->getRequest(); + if (!$request->hasSession()) { + $request->setSessionFactory(function () use ($request) { + // Prevent calling `$this->getSession()` twice in case the Request (and the below factory) is cloned + static $sess; + + if (!$sess) { + $sess = $this->getSession(); + $request->setSession($sess); + + /* + * For supporting sessions in php runtime with runners like roadrunner or swoole, the session + * cookie needs to be read from the cookie bag and set on the session storage. + * + * Do not set it when a native php session is active. + */ + if ($sess && !$sess->isStarted() && \PHP_SESSION_ACTIVE !== session_status()) { + $sessionId = $sess->getId() ?: $request->cookies->get($sess->getName(), ''); + $sess->setId($sessionId); + } + } + + return $sess; + }); + } + } + + /** + * @internal + */ + public function onKernelResponse(ResponseEvent $event): void + { + if (!$event->isMainRequest() || (!$this->container->has('initialized_session') && !$event->getRequest()->hasSession())) { + return; + } + + $response = $event->getResponse(); + $autoCacheControl = !$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER); + // Always remove the internal header if present + $response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER); + if (!$event->getRequest()->hasSession(true)) { + return; + } + $session = $event->getRequest()->getSession(); + + if ($session->isStarted()) { + /* + * Saves the session, in case it is still open, before sending the response/headers. + * + * This ensures several things in case the developer did not save the session explicitly: + * + * * If a session save handler without locking is used, it ensures the data is available + * on the next request, e.g. after a redirect. PHPs auto-save at script end via + * session_register_shutdown is executed after fastcgi_finish_request. So in this case + * the data could be missing the next request because it might not be saved the moment + * the new request is processed. + * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like + * the one above. But by saving the session before long-running things in the terminate event, + * we ensure the session is not blocked longer than needed. + * * When regenerating the session ID no locking is involved in PHPs session design. See + * https://bugs.php.net/61470 for a discussion. So in this case, the session must + * be saved anyway before sending the headers with the new session ID. Otherwise session + * data could get lost again for concurrent requests with the new ID. One result could be + * that you get logged out after just logging in. + * + * This listener should be executed as one of the last listeners, so that previous listeners + * can still operate on the open session. This prevents the overhead of restarting it. + * Listeners after closing the session can still work with the session as usual because + * Symfonys session implementation starts the session on demand. So writing to it after + * it is saved will just restart it. + */ + $session->save(); + + /* + * For supporting sessions in php runtime with runners like roadrunner or swoole the session + * cookie need to be written on the response object and should not be written by PHP itself. + */ + $sessionName = $session->getName(); + $sessionId = $session->getId(); + $sessionOptions = $this->getSessionOptions($this->sessionOptions); + $sessionCookiePath = $sessionOptions['cookie_path'] ?? '/'; + $sessionCookieDomain = $sessionOptions['cookie_domain'] ?? null; + $sessionCookieSecure = $sessionOptions['cookie_secure'] ?? false; + $sessionCookieHttpOnly = $sessionOptions['cookie_httponly'] ?? true; + $sessionCookieSameSite = $sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX; + $sessionUseCookies = $sessionOptions['use_cookies'] ?? true; + + SessionUtils::popSessionCookie($sessionName, $sessionId); + + if ($sessionUseCookies) { + $request = $event->getRequest(); + $requestSessionCookieId = $request->cookies->get($sessionName); + + $isSessionEmpty = ($session instanceof Session ? $session->isEmpty() : !$session->all()) && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions + if ($requestSessionCookieId && $isSessionEmpty) { + // PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument + // which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy + // when the session gets invalidated (for example on logout) so we must handle this case here too + // otherwise we would send two Set-Cookie headers back with the response + SessionUtils::popSessionCookie($sessionName, 'deleted'); + $response->headers->clearCookie( + $sessionName, + $sessionCookiePath, + $sessionCookieDomain, + $sessionCookieSecure, + $sessionCookieHttpOnly, + $sessionCookieSameSite + ); + } elseif ($sessionId !== $requestSessionCookieId && !$isSessionEmpty) { + $expire = 0; + $lifetime = $sessionOptions['cookie_lifetime'] ?? null; + if ($lifetime) { + $expire = time() + $lifetime; + } + + $response->headers->setCookie( + Cookie::create( + $sessionName, + $sessionId, + $expire, + $sessionCookiePath, + $sessionCookieDomain, + $sessionCookieSecure, + $sessionCookieHttpOnly, + false, + $sessionCookieSameSite + ) + ); + } + } + } + + if ($session instanceof Session ? 0 === $session->getUsageIndex() : !$session->isStarted()) { + return; + } + + if ($autoCacheControl) { + $maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge(); + $response + ->setExpires(new \DateTimeImmutable('+'.$maxAge.' seconds')) + ->setPrivate() + ->setMaxAge($maxAge) + ->headers->addCacheControlDirective('must-revalidate'); + } + + if (!$event->getRequest()->attributes->get('_stateless', false)) { + return; + } + + if ($this->debug) { + throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.'); + } + + if ($this->container->has('logger')) { + $this->container->get('logger')->warning('Session was used while the request was declared stateless.'); + } + } + + /** + * @internal + */ + public function onSessionUsage(): void + { + if (!$this->debug) { + return; + } + + if ($this->container?->has('session_collector')) { + $this->container->get('session_collector')(); + } + + if (!$requestStack = $this->container?->has('request_stack') ? $this->container->get('request_stack') : null) { + return; + } + + $stateless = false; + $clonedRequestStack = clone $requestStack; + while (null !== ($request = $clonedRequestStack->pop()) && !$stateless) { + $stateless = $request->attributes->get('_stateless'); + } + + if (!$stateless) { + return; + } + + if (!$session = $requestStack->getCurrentRequest()->getSession()) { + return; + } + + if ($session->isStarted()) { + $session->save(); + } + + throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.'); + } + + /** + * @internal + */ + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => ['onKernelRequest', 128], + // low priority to come after regular response listeners + KernelEvents::RESPONSE => ['onKernelResponse', -1000], + ]; + } + + /** + * @internal + */ + public function reset(): void + { + if (\PHP_SESSION_ACTIVE === session_status()) { + session_abort(); + } + + session_unset(); + $_SESSION = []; + + if (!headers_sent()) { // session id can only be reset when no headers were so we check for headers_sent first + session_id(''); + } + } + + /** + * Gets the session object. + * + * @internal + */ + abstract protected function getSession(): ?SessionInterface; + + private function getSessionOptions(array $sessionOptions): array + { + $mergedSessionOptions = []; + + foreach (session_get_cookie_params() as $key => $value) { + $mergedSessionOptions['cookie_'.$key] = $value; + } + + foreach ($sessionOptions as $key => $value) { + // do the same logic as in the NativeSessionStorage + if ('cookie_secure' === $key && 'auto' === $value) { + continue; + } + $mergedSessionOptions[$key] = $value; + } + + return $mergedSessionOptions; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php new file mode 100644 index 0000000..f5dcdfe --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Adds configured formats to each request. + * + * @author Gildas Quemener + * + * @final + */ +class AddRequestFormatsListener implements EventSubscriberInterface +{ + public function __construct( + private array $formats, + ) { + } + + /** + * Adds request formats. + */ + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + foreach ($this->formats as $format => $mimeTypes) { + $request->setFormat($format, $mimeTypes); + } + } + + public static function getSubscribedEvents(): array + { + return [KernelEvents::REQUEST => ['onKernelRequest', 100]]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/CacheAttributeListener.php b/vendor/symfony/http-kernel/EventListener/CacheAttributeListener.php new file mode 100644 index 0000000..f428ea9 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/CacheAttributeListener.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\Cache; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Handles HTTP cache headers configured via the Cache attribute. + * + * @author Fabien Potencier + */ +class CacheAttributeListener implements EventSubscriberInterface +{ + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $lastModified; + + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $etags; + + public function __construct( + private ?ExpressionLanguage $expressionLanguage = null, + ) { + $this->lastModified = new \SplObjectStorage(); + $this->etags = new \SplObjectStorage(); + } + + /** + * Handles HTTP validation headers. + */ + public function onKernelControllerArguments(ControllerArgumentsEvent $event): void + { + $request = $event->getRequest(); + + if (!\is_array($attributes = $request->attributes->get('_cache') ?? $event->getAttributes()[Cache::class] ?? null)) { + return; + } + + $request->attributes->set('_cache', $attributes); + $response = null; + $lastModified = null; + $etag = null; + + /** @var Cache[] $attributes */ + foreach ($attributes as $cache) { + if (null !== $cache->lastModified) { + $lastModified = $this->getExpressionLanguage()->evaluate($cache->lastModified, array_merge($request->attributes->all(), $event->getNamedArguments())); + ($response ??= new Response())->setLastModified($lastModified); + } + + if (null !== $cache->etag) { + $etag = hash('sha256', $this->getExpressionLanguage()->evaluate($cache->etag, array_merge($request->attributes->all(), $event->getNamedArguments()))); + ($response ??= new Response())->setEtag($etag); + } + } + + if ($response?->isNotModified($request)) { + $event->setController(static fn () => $response); + $event->stopPropagation(); + + return; + } + + if (null !== $etag) { + $this->etags[$request] = $etag; + } + if (null !== $lastModified) { + $this->lastModified[$request] = $lastModified; + } + } + + /** + * Modifies the response to apply HTTP cache headers when needed. + */ + public function onKernelResponse(ResponseEvent $event): void + { + $request = $event->getRequest(); + + /** @var Cache[] $attributes */ + if (!\is_array($attributes = $request->attributes->get('_cache'))) { + return; + } + $response = $event->getResponse(); + + // http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1 + if (!\in_array($response->getStatusCode(), [200, 203, 300, 301, 302, 304, 404, 410])) { + unset($this->lastModified[$request]); + unset($this->etags[$request]); + + return; + } + + if (isset($this->lastModified[$request]) && !$response->headers->has('Last-Modified')) { + $response->setLastModified($this->lastModified[$request]); + } + + if (isset($this->etags[$request]) && !$response->headers->has('Etag')) { + $response->setEtag($this->etags[$request]); + } + + unset($this->lastModified[$request]); + unset($this->etags[$request]); + $hasVary = $response->headers->has('Vary'); + + foreach (array_reverse($attributes) as $cache) { + if (null !== $cache->smaxage && !$response->headers->hasCacheControlDirective('s-maxage')) { + $response->setSharedMaxAge($this->toSeconds($cache->smaxage)); + } + + if ($cache->mustRevalidate) { + $response->headers->addCacheControlDirective('must-revalidate'); + } + + if (null !== $cache->maxage && !$response->headers->hasCacheControlDirective('max-age')) { + $response->setMaxAge($this->toSeconds($cache->maxage)); + } + + if (null !== $cache->maxStale && !$response->headers->hasCacheControlDirective('max-stale')) { + $response->headers->addCacheControlDirective('max-stale', $this->toSeconds($cache->maxStale)); + } + + if (null !== $cache->staleWhileRevalidate && !$response->headers->hasCacheControlDirective('stale-while-revalidate')) { + $response->headers->addCacheControlDirective('stale-while-revalidate', $this->toSeconds($cache->staleWhileRevalidate)); + } + + if (null !== $cache->staleIfError && !$response->headers->hasCacheControlDirective('stale-if-error')) { + $response->headers->addCacheControlDirective('stale-if-error', $this->toSeconds($cache->staleIfError)); + } + + if (null !== $cache->expires && !$response->headers->has('Expires')) { + $response->setExpires(new \DateTimeImmutable('@'.strtotime($cache->expires, time()))); + } + + if (!$hasVary && $cache->vary) { + $response->setVary($cache->vary, false); + } + } + + foreach ($attributes as $cache) { + if (true === $cache->public) { + $response->setPublic(); + } + + if (false === $cache->public) { + $response->setPrivate(); + } + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 10], + KernelEvents::RESPONSE => ['onKernelResponse', -10], + ]; + } + + private function getExpressionLanguage(): ExpressionLanguage + { + return $this->expressionLanguage ??= class_exists(ExpressionLanguage::class) + ? new ExpressionLanguage() + : throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + + private function toSeconds(int|string $time): int + { + if (!is_numeric($time)) { + $now = time(); + $time = strtotime($time, $now) - $now; + } + + return $time; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php new file mode 100644 index 0000000..df1443b --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Sets an exception handler. + * + * @author Nicolas Grekas + * + * @final + * + * @internal + */ +class DebugHandlersListener implements EventSubscriberInterface +{ + private string|object|null $earlyHandler; + private ?\Closure $exceptionHandler; + private bool $webMode; + private bool $firstCall = true; + private bool $hasTerminatedWithException = false; + + /** + * @param bool $webMode + * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception + */ + public function __construct(?callable $exceptionHandler = null, bool|LoggerInterface|null $webMode = null) + { + if ($webMode instanceof LoggerInterface) { + // BC with Symfony 5 + $webMode = null; + } + $handler = set_exception_handler('is_int'); + $this->earlyHandler = \is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + + $this->exceptionHandler = null === $exceptionHandler ? null : $exceptionHandler(...); + $this->webMode = $webMode ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true); + } + + /** + * Configures the error handler. + */ + public function configure(?object $event = null): void + { + if ($event instanceof ConsoleEvent && $this->webMode) { + return; + } + if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) { + return; + } + $this->firstCall = $this->hasTerminatedWithException = false; + + if (!$this->exceptionHandler) { + if ($event instanceof KernelEvent) { + if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) { + $request = $event->getRequest(); + $hasRun = &$this->hasTerminatedWithException; + $this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) { + if ($hasRun) { + throw $e; + } + + $hasRun = true; + $kernel->terminateWithException($e, $request); + }; + } + } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) { + $output = $event->getOutput(); + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->exceptionHandler = static function (\Throwable $e) use ($app, $output) { + $app->renderThrowable($e, $output); + }; + } + } + if ($this->exceptionHandler) { + $handler = set_exception_handler(static fn () => null); + $handler = \is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + + if (!$handler instanceof ErrorHandler) { + $handler = $this->earlyHandler; + } + + if ($handler instanceof ErrorHandler) { + $handler->setExceptionHandler($this->exceptionHandler); + } + $this->exceptionHandler = null; + } + } + + public static function getSubscribedEvents(): array + { + $events = [KernelEvents::REQUEST => ['configure', 2048]]; + + if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) { + $events[ConsoleEvents::COMMAND] = ['configure', 2048]; + } + + return $events; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php b/vendor/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php new file mode 100644 index 0000000..9631096 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Ensures that the application is not indexed by search engines. + * + * @author Gary PEGEOT + */ +class DisallowRobotsIndexingListener implements EventSubscriberInterface +{ + private const HEADER_NAME = 'X-Robots-Tag'; + + public function onResponse(ResponseEvent $event): void + { + if (!$event->getResponse()->headers->has(static::HEADER_NAME)) { + $event->getResponse()->headers->set(static::HEADER_NAME, 'noindex'); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::RESPONSE => ['onResponse', -255], + ]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DumpListener.php b/vendor/symfony/http-kernel/EventListener/DumpListener.php new file mode 100644 index 0000000..836e54d --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DumpListener.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\Server\Connection; +use Symfony\Component\VarDumper\VarDumper; + +/** + * Configures dump() handler. + * + * @author Nicolas Grekas + */ +class DumpListener implements EventSubscriberInterface +{ + public function __construct( + private ClonerInterface $cloner, + private DataDumperInterface $dumper, + private ?Connection $connection = null, + ) { + } + + public function configure(): void + { + $cloner = $this->cloner; + $dumper = $this->dumper; + $connection = $this->connection; + + VarDumper::setHandler(static function ($var, ?string $label = null) use ($cloner, $dumper, $connection) { + $data = $cloner->cloneVar($var); + if (null !== $label) { + $data = $data->withContext(['label' => $label]); + } + + if (!$connection || !$connection->write($data)) { + $dumper->dump($data); + } + }); + } + + public static function getSubscribedEvents(): array + { + if (!class_exists(ConsoleEvents::class)) { + return []; + } + + // Register early to have a working dump() as early as possible + return [ConsoleEvents::COMMAND => ['configure', 1024]]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ErrorListener.php b/vendor/symfony/http-kernel/EventListener/ErrorListener.php new file mode 100644 index 0000000..fad05ae --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ErrorListener.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; +use Symfony\Component\HttpKernel\Attribute\WithLogLevel; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; + +/** + * @author Fabien Potencier + */ +class ErrorListener implements EventSubscriberInterface +{ + /** + * @param array|null}> $exceptionsMapping + */ + public function __construct( + protected string|object|array|null $controller, + protected ?LoggerInterface $logger = null, + protected bool $debug = false, + protected array $exceptionsMapping = [], + ) { + } + + public function logKernelException(ExceptionEvent $event): void + { + $throwable = $event->getThrowable(); + $logLevel = $this->resolveLogLevel($throwable); + + foreach ($this->exceptionsMapping as $class => $config) { + if (!$throwable instanceof $class || !$config['status_code']) { + continue; + } + if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() !== $config['status_code']) { + $headers = $throwable instanceof HttpExceptionInterface ? $throwable->getHeaders() : []; + $throwable = HttpException::fromStatusCode($config['status_code'], $throwable->getMessage(), $throwable, $headers); + $event->setThrowable($throwable); + } + break; + } + + // There's no specific status code defined in the configuration for this exception + if (!$throwable instanceof HttpExceptionInterface && $withHttpStatus = $this->getInheritedAttribute($throwable::class, WithHttpStatus::class)) { + $throwable = HttpException::fromStatusCode($withHttpStatus->statusCode, $throwable->getMessage(), $throwable, $withHttpStatus->headers); + $event->setThrowable($throwable); + } + + $e = FlattenException::createFromThrowable($throwable); + + $this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel); + } + + public function onKernelException(ExceptionEvent $event): void + { + if (null === $this->controller) { + return; + } + + if (!$this->debug && $event->isKernelTerminating()) { + return; + } + + $throwable = $event->getThrowable(); + + if ($exceptionHandler = set_exception_handler(var_dump(...))) { + restore_exception_handler(); + if (\is_array($exceptionHandler) && $exceptionHandler[0] instanceof ErrorHandler) { + $throwable = $exceptionHandler[0]->enhanceError($event->getThrowable()); + } + } + + $request = $this->duplicateRequest($throwable, $event->getRequest()); + + try { + $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + $f = FlattenException::createFromThrowable($e); + + $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), basename($e->getFile()), $e->getLine())); + + $prev = $e; + do { + if ($throwable === $wrapper = $prev) { + throw $e; + } + } while ($prev = $wrapper->getPrevious()); + + $prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous'); + $prev->setValue($wrapper, $throwable); + + throw $e; + } + + $event->setResponse($response); + + if ($this->debug) { + $event->getRequest()->attributes->set('_remove_csp_headers', true); + } + } + + public function removeCspHeader(ResponseEvent $event): void + { + if ($this->debug && $event->getRequest()->attributes->get('_remove_csp_headers', false)) { + $event->getResponse()->headers->remove('Content-Security-Policy'); + } + } + + public function onControllerArguments(ControllerArgumentsEvent $event): void + { + $e = $event->getRequest()->attributes->get('exception'); + + if (!$e instanceof \Throwable || false === $k = array_search($e, $event->getArguments(), true)) { + return; + } + + $r = new \ReflectionFunction($event->getController()(...)); + $r = $r->getParameters()[$k] ?? null; + + if ($r && (!($r = $r->getType()) instanceof \ReflectionNamedType || FlattenException::class === $r->getName())) { + $arguments = $event->getArguments(); + $arguments[$k] = FlattenException::createFromThrowable($e); + $event->setArguments($arguments); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER_ARGUMENTS => 'onControllerArguments', + KernelEvents::EXCEPTION => [ + ['logKernelException', 0], + ['onKernelException', -128], + ], + KernelEvents::RESPONSE => ['removeCspHeader', -128], + ]; + } + + /** + * Logs an exception. + */ + protected function logException(\Throwable $exception, string $message, ?string $logLevel = null): void + { + if (null === $this->logger) { + return; + } + + $logLevel ??= $this->resolveLogLevel($exception); + + $this->logger->log($logLevel, $message, ['exception' => $exception]); + } + + /** + * Resolves the level to be used when logging the exception. + */ + private function resolveLogLevel(\Throwable $throwable): string + { + foreach ($this->exceptionsMapping as $class => $config) { + if ($throwable instanceof $class && $config['log_level']) { + return $config['log_level']; + } + } + + if ($withLogLevel = $this->getInheritedAttribute($throwable::class, WithLogLevel::class)) { + return $withLogLevel->level; + } + + if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() >= 500) { + return LogLevel::CRITICAL; + } + + return LogLevel::ERROR; + } + + /** + * Clones the request for the exception. + */ + protected function duplicateRequest(\Throwable $exception, Request $request): Request + { + $attributes = [ + '_controller' => $this->controller, + 'exception' => $exception, + 'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger), + ]; + $request = $request->duplicate(null, null, $attributes); + $request->setMethod('GET'); + + return $request; + } + + /** + * @template T + * + * @param class-string $attribute + * + * @return T|null + */ + private function getInheritedAttribute(string $class, string $attribute): ?object + { + $class = new \ReflectionClass($class); + $interfaces = []; + $attributeReflector = null; + $parentInterfaces = []; + $ownInterfaces = []; + + do { + if ($attributes = $class->getAttributes($attribute, \ReflectionAttribute::IS_INSTANCEOF)) { + $attributeReflector = $attributes[0]; + $parentInterfaces = class_implements($class->name); + break; + } + + $interfaces[] = class_implements($class->name); + } while ($class = $class->getParentClass()); + + while ($interfaces) { + $ownInterfaces = array_diff_key(array_pop($interfaces), $parentInterfaces); + $parentInterfaces += $ownInterfaces; + + foreach ($ownInterfaces as $interface) { + $class = new \ReflectionClass($interface); + + if ($attributes = $class->getAttributes($attribute, \ReflectionAttribute::IS_INSTANCEOF)) { + $attributeReflector = $attributes[0]; + } + } + } + + return $attributeReflector?->newInstance(); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/FragmentListener.php b/vendor/symfony/http-kernel/EventListener/FragmentListener.php new file mode 100644 index 0000000..866ae04 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/FragmentListener.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\UriSigner; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Handles content fragments represented by special URIs. + * + * All URL paths starting with /_fragment are handled as + * content fragments by this listener. + * + * Throws an AccessDeniedHttpException exception if the request + * is not signed or if it is not an internal sub-request. + * + * @author Fabien Potencier + * + * @final + */ +class FragmentListener implements EventSubscriberInterface +{ + /** + * @param string $fragmentPath The path that triggers this listener + */ + public function __construct( + private UriSigner $signer, + private string $fragmentPath = '/_fragment', + ) { + } + + /** + * Fixes request attributes when the path is '/_fragment'. + * + * @throws AccessDeniedHttpException if the request does not come from a trusted IP + */ + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + + if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) { + return; + } + + if ($request->attributes->has('_controller')) { + // Is a sub-request: no need to parse _path but it should still be removed from query parameters as below. + $request->query->remove('_path'); + + return; + } + + if ($event->isMainRequest()) { + $this->validateRequest($request); + } + + parse_str($request->query->get('_path', ''), $attributes); + $attributes['_check_controller_is_allowed'] = true; + $request->attributes->add($attributes); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', []), $attributes)); + $request->query->remove('_path'); + } + + protected function validateRequest(Request $request): void + { + // is the Request safe? + if (!$request->isMethodSafe()) { + throw new AccessDeniedHttpException(); + } + + // is the Request signed? + if ($this->signer->checkRequest($request)) { + return; + } + + throw new AccessDeniedHttpException(); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [['onKernelRequest', 48]], + ]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/LocaleAwareListener.php b/vendor/symfony/http-kernel/EventListener/LocaleAwareListener.php new file mode 100644 index 0000000..1c78c4b --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/LocaleAwareListener.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +/** + * Pass the current locale to the provided services. + * + * @author Pierre Bobiet + */ +class LocaleAwareListener implements EventSubscriberInterface +{ + /** + * @param iterable $localeAwareServices + */ + public function __construct( + private iterable $localeAwareServices, + private RequestStack $requestStack, + ) { + } + + public function onKernelRequest(RequestEvent $event): void + { + $this->setLocale($event->getRequest()->getLocale(), $event->getRequest()->getDefaultLocale()); + } + + public function onKernelFinishRequest(FinishRequestEvent $event): void + { + if (null === $parentRequest = $this->requestStack->getParentRequest()) { + foreach ($this->localeAwareServices as $service) { + $service->setLocale($event->getRequest()->getDefaultLocale()); + } + + return; + } + + $this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale()); + } + + public static function getSubscribedEvents(): array + { + return [ + // must be registered after the Locale listener + KernelEvents::REQUEST => [['onKernelRequest', 15]], + KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', -15]], + ]; + } + + private function setLocale(string $locale, string $defaultLocale): void + { + foreach ($this->localeAwareServices as $service) { + try { + $service->setLocale($locale); + } catch (\InvalidArgumentException) { + $service->setLocale($defaultLocale); + } + } + } +} diff --git a/vendor/symfony/http-kernel/EventListener/LocaleListener.php b/vendor/symfony/http-kernel/EventListener/LocaleListener.php new file mode 100644 index 0000000..b905f77 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/LocaleListener.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + * + * @final + */ +class LocaleListener implements EventSubscriberInterface +{ + public function __construct( + private RequestStack $requestStack, + private string $defaultLocale = 'en', + private ?RequestContextAwareInterface $router = null, + private bool $useAcceptLanguageHeader = false, + private array $enabledLocales = [], + ) { + } + + public function setDefaultLocale(KernelEvent $event): void + { + $event->getRequest()->setDefaultLocale($this->defaultLocale); + } + + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + + $this->setLocale($request); + $this->setRouterContext($request); + } + + public function onKernelFinishRequest(FinishRequestEvent $event): void + { + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request): void + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } elseif ($this->useAcceptLanguageHeader) { + if ($request->getLanguages() && $preferredLanguage = $request->getPreferredLanguage($this->enabledLocales)) { + $request->setLocale($preferredLanguage); + } + $request->attributes->set('_vary_by_language', true); + } + } + + private function setRouterContext(Request $request): void + { + $this->router?->getContext()->setParameter('_locale', $request->getLocale()); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [ + ['setDefaultLocale', 100], + // must be registered after the Router to have access to the _locale + ['onKernelRequest', 16], + ], + KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]], + ]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ProfilerListener.php b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php new file mode 100644 index 0000000..ecaaceb --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Event\TerminateEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profile; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * ProfilerListener collects data for the current request by listening to the kernel events. + * + * @author Fabien Potencier + * + * @final + */ +class ProfilerListener implements EventSubscriberInterface +{ + private ?\Throwable $exception = null; + /** @var \SplObjectStorage */ + private \SplObjectStorage $profiles; + /** @var \SplObjectStorage */ + private \SplObjectStorage $parents; + + /** + * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise + * @param bool $onlyMainRequests True if the profiler only collects data when the request is the main request, false otherwise + */ + public function __construct( + private Profiler $profiler, + private RequestStack $requestStack, + private ?RequestMatcherInterface $matcher = null, + private bool $onlyException = false, + private bool $onlyMainRequests = false, + private ?string $collectParameter = null, + ) { + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + /** + * Handles the onKernelException event. + */ + public function onKernelException(ExceptionEvent $event): void + { + if ($this->onlyMainRequests && !$event->isMainRequest()) { + return; + } + + $this->exception = $event->getThrowable(); + } + + /** + * Handles the onKernelResponse event. + */ + public function onKernelResponse(ResponseEvent $event): void + { + if ($this->onlyMainRequests && !$event->isMainRequest()) { + return; + } + + if ($this->onlyException && null === $this->exception) { + return; + } + + $request = $event->getRequest(); + if (null !== $this->collectParameter && null !== $collectParameterValue = $request->get($this->collectParameter)) { + true === $collectParameterValue || filter_var($collectParameterValue, \FILTER_VALIDATE_BOOL) ? $this->profiler->enable() : $this->profiler->disable(); + } + + $exception = $this->exception; + $this->exception = null; + + if (null !== $this->matcher && !$this->matcher->matches($request)) { + return; + } + + $session = !$request->attributes->getBoolean('_stateless') && $request->hasPreviousSession() ? $request->getSession() : null; + + if ($session instanceof Session) { + $usageIndexValue = $usageIndexReference = &$session->getUsageIndex(); + $usageIndexReference = \PHP_INT_MIN; + } + + try { + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + } finally { + if ($session instanceof Session) { + $usageIndexReference = $usageIndexValue; + } + } + + $this->profiles[$request] = $profile; + + $this->parents[$request] = $this->requestStack->getParentRequest(); + } + + public function onKernelTerminate(TerminateEvent $event): void + { + // attach children to parents + foreach ($this->profiles as $request) { + if (null !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + // save profiles + foreach ($this->profiles as $request) { + $this->profiler->saveProfile($this->profiles[$request]); + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::RESPONSE => ['onKernelResponse', -100], + KernelEvents::EXCEPTION => ['onKernelException', 0], + KernelEvents::TERMINATE => ['onKernelTerminate', -1024], + ]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ResponseListener.php b/vendor/symfony/http-kernel/EventListener/ResponseListener.php new file mode 100644 index 0000000..690bfaf --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ResponseListener.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * ResponseListener fixes the Response headers based on the Request. + * + * @author Fabien Potencier + * + * @final + */ +class ResponseListener implements EventSubscriberInterface +{ + public function __construct( + private string $charset, + private bool $addContentLanguageHeader = false, + ) { + } + + /** + * Filters the Response. + */ + public function onKernelResponse(ResponseEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $response = $event->getResponse(); + + if (null === $response->getCharset()) { + $response->setCharset($this->charset); + } + + if ($this->addContentLanguageHeader && !$response->isInformational() && !$response->isEmpty() && !$response->headers->has('Content-Language')) { + $response->headers->set('Content-Language', $event->getRequest()->getLocale()); + } + + if ($event->getRequest()->attributes->get('_vary_by_language')) { + $response->setVary('Accept-Language', false); + } + + $response->prepare($event->getRequest()); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::RESPONSE => 'onKernelResponse', + ]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/RouterListener.php b/vendor/symfony/http-kernel/EventListener/RouterListener.php new file mode 100644 index 0000000..689d081 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/RouterListener.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * Initializes the context from the request and sets request attributes based on a matching route. + * + * @author Fabien Potencier + * @author Yonel Ceruto + * + * @final + */ +class RouterListener implements EventSubscriberInterface +{ + private RequestContext $context; + + /** + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) + * + * @throws \InvalidArgumentException + */ + public function __construct( + private UrlMatcherInterface|RequestMatcherInterface $matcher, + private RequestStack $requestStack, + ?RequestContext $context = null, + private ?LoggerInterface $logger = null, + private ?string $projectDir = null, + private bool $debug = true, + ) { + if (null === $context && !$matcher instanceof RequestContextAwareInterface) { + throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); + } + + $this->context = $context ?? $matcher->getContext(); + } + + private function setCurrentRequest(?Request $request): void + { + if (null !== $request) { + try { + $this->context->fromRequest($request); + } catch (\UnexpectedValueException $e) { + throw new BadRequestHttpException($e->getMessage(), $e, $e->getCode()); + } + } + } + + /** + * After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator + * operates on the correct context again. + */ + public function onKernelFinishRequest(): void + { + $this->setCurrentRequest($this->requestStack->getParentRequest()); + } + + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + + $this->setCurrentRequest($request); + + if ($request->attributes->has('_controller')) { + // routing is already done + return; + } + + // add attributes based on the request (routing) + try { + // matching a request is more powerful than matching a URL path + context, so try that first + if ($this->matcher instanceof RequestMatcherInterface) { + $parameters = $this->matcher->matchRequest($request); + } else { + $parameters = $this->matcher->match($request->getPathInfo()); + } + + $this->logger?->info('Matched route "{route}".', [ + 'route' => $parameters['_route'] ?? 'n/a', + 'route_parameters' => $parameters, + 'request_uri' => $request->getUri(), + 'method' => $request->getMethod(), + ]); + + $attributes = $parameters; + if ($mapping = $parameters['_route_mapping'] ?? false) { + unset($parameters['_route_mapping']); + $mappedAttributes = []; + $attributes = []; + + foreach ($parameters as $parameter => $value) { + $attribute = $mapping[$parameter] ?? $parameter; + + if (!isset($mappedAttributes[$attribute])) { + $attributes[$attribute] = $value; + $mappedAttributes[$attribute] = $parameter; + } elseif ('' !== $mappedAttributes[$attribute]) { + $attributes[$attribute] = [ + $mappedAttributes[$attribute] => $attributes[$attribute], + $parameter => $value, + ]; + $mappedAttributes[$attribute] = ''; + } else { + $attributes[$attribute][$parameter] = $value; + } + } + + $attributes['_route_mapping'] = $mapping; + } + + $request->attributes->add($attributes); + unset($parameters['_route'], $parameters['_controller']); + $request->attributes->set('_route_params', $parameters); + } catch (ResourceNotFoundException $e) { + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getUriForPath($request->getPathInfo())); + + if ($referer = $request->headers->get('referer')) { + $message .= sprintf(' (from "%s")', $referer); + } + + throw new NotFoundHttpException($message, $e); + } catch (MethodNotAllowedException $e) { + $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getUriForPath($request->getPathInfo()), implode(', ', $e->getAllowedMethods())); + + throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); + } + } + + public function onKernelException(ExceptionEvent $event): void + { + if (!$this->debug || !($e = $event->getThrowable()) instanceof NotFoundHttpException) { + return; + } + + if ($e->getPrevious() instanceof NoConfigurationException) { + $event->setResponse($this->createWelcomeResponse()); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [['onKernelRequest', 32]], + KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]], + KernelEvents::EXCEPTION => ['onKernelException', -64], + ]; + } + + private function createWelcomeResponse(): Response + { + $version = Kernel::VERSION; + $projectDir = realpath((string) $this->projectDir).\DIRECTORY_SEPARATOR; + $docVersion = substr(Kernel::VERSION, 0, 3); + + ob_start(); + include \dirname(__DIR__).'/Resources/welcome.html.php'; + + return new Response(ob_get_clean(), Response::HTTP_NOT_FOUND); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SessionListener.php b/vendor/symfony/http-kernel/EventListener/SessionListener.php new file mode 100644 index 0000000..5652685 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SessionListener.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + * + * @final + */ +class SessionListener extends AbstractSessionListener +{ + public function __construct( + private ?ContainerInterface $container = null, + bool $debug = false, + array $sessionOptions = [], + ) { + parent::__construct($container, $debug, $sessionOptions); + } + + protected function getSession(): ?SessionInterface + { + if ($this->container->has('session_factory')) { + return $this->container->get('session_factory')->createSession(); + } + + return null; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SurrogateListener.php b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php new file mode 100644 index 0000000..f6db59e --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates. + * + * @author Fabien Potencier + * + * @final + */ +class SurrogateListener implements EventSubscriberInterface +{ + public function __construct( + private ?SurrogateInterface $surrogate = null, + ) { + } + + /** + * Filters the Response. + */ + public function onKernelResponse(ResponseEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $kernel = $event->getKernel(); + $surrogate = $this->surrogate; + if ($kernel instanceof HttpCache) { + $surrogate = $kernel->getSurrogate(); + if (null !== $this->surrogate && $this->surrogate->getName() !== $surrogate->getName()) { + $surrogate = $this->surrogate; + } + } + + if (null === $surrogate) { + return; + } + + $surrogate->addSurrogateControl($event->getResponse()); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::RESPONSE => 'onKernelResponse', + ]; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php new file mode 100644 index 0000000..187633d --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Validates Requests. + * + * @author Magnus Nordlander + * + * @final + */ +class ValidateRequestListener implements EventSubscriberInterface +{ + /** + * Performs the validation. + */ + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + $request = $event->getRequest(); + + if ($request::getTrustedProxies()) { + $request->getClientIps(); + } + + $request->getHost(); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [ + ['onKernelRequest', 256], + ], + ]; + } +} diff --git a/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php new file mode 100644 index 0000000..0f9ea71 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Fabien Potencier + * @author Christophe Coevoet + */ +class AccessDeniedHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(403, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php new file mode 100644 index 0000000..57a7a25 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class BadRequestHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(400, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ConflictHttpException.php b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php new file mode 100644 index 0000000..997c4a6 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class ConflictHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(409, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php b/vendor/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php new file mode 100644 index 0000000..ecfc2f8 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Grégoire Pineau + */ +class ControllerDoesNotReturnResponseException extends \LogicException +{ + public function __construct(string $message, callable $controller, string $file, int $line) + { + parent::__construct($message); + + if (!$controllerDefinition = $this->parseControllerDefinition($controller)) { + return; + } + + $this->file = $controllerDefinition['file']; + $this->line = $controllerDefinition['line']; + $r = new \ReflectionProperty(\Exception::class, 'trace'); + $r->setValue($this, array_merge([ + [ + 'line' => $line, + 'file' => $file, + ], + ], $this->getTrace())); + } + + private function parseControllerDefinition(callable $controller): ?array + { + if (\is_string($controller) && str_contains($controller, '::')) { + $controller = explode('::', $controller); + } + + if (\is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + + return [ + 'file' => $r->getFileName(), + 'line' => $r->getEndLine(), + ]; + } catch (\ReflectionException) { + return null; + } + } + + if ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + + return [ + 'file' => $r->getFileName(), + 'line' => $r->getEndLine(), + ]; + } + + if (\is_object($controller)) { + $r = new \ReflectionClass($controller); + + try { + $line = $r->getMethod('__invoke')->getEndLine(); + } catch (\ReflectionException) { + $line = $r->getEndLine(); + } + + return [ + 'file' => $r->getFileName(), + 'line' => $line, + ]; + } + + return null; + } +} diff --git a/vendor/symfony/http-kernel/Exception/GoneHttpException.php b/vendor/symfony/http-kernel/Exception/GoneHttpException.php new file mode 100644 index 0000000..c40d597 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/GoneHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class GoneHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(410, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpException.php b/vendor/symfony/http-kernel/Exception/HttpException.php new file mode 100644 index 0000000..9a71bcb --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpException.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * HttpException. + * + * @author Kris Wallsmith + */ +class HttpException extends \RuntimeException implements HttpExceptionInterface +{ + public function __construct( + private int $statusCode, + string $message = '', + ?\Throwable $previous = null, + private array $headers = [], + int $code = 0, + ) { + parent::__construct($message, $code, $previous); + } + + public static function fromStatusCode(int $statusCode, string $message = '', ?\Throwable $previous = null, array $headers = [], int $code = 0): self + { + return match ($statusCode) { + 400 => new BadRequestHttpException($message, $previous, $code, $headers), + 403 => new AccessDeniedHttpException($message, $previous, $code, $headers), + 404 => new NotFoundHttpException($message, $previous, $code, $headers), + 406 => new NotAcceptableHttpException($message, $previous, $code, $headers), + 409 => new ConflictHttpException($message, $previous, $code, $headers), + 410 => new GoneHttpException($message, $previous, $code, $headers), + 411 => new LengthRequiredHttpException($message, $previous, $code, $headers), + 412 => new PreconditionFailedHttpException($message, $previous, $code, $headers), + 423 => new LockedHttpException($message, $previous, $code, $headers), + 415 => new UnsupportedMediaTypeHttpException($message, $previous, $code, $headers), + 422 => new UnprocessableEntityHttpException($message, $previous, $code, $headers), + 428 => new PreconditionRequiredHttpException($message, $previous, $code, $headers), + 429 => new TooManyRequestsHttpException(null, $message, $previous, $code, $headers), + 503 => new ServiceUnavailableHttpException(null, $message, $previous, $code, $headers), + default => new static($statusCode, $message, $previous, $headers, $code), + }; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function setHeaders(array $headers): void + { + $this->headers = $headers; + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php new file mode 100644 index 0000000..7eb0cbd --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Interface for HTTP error exceptions. + * + * @author Kris Wallsmith + */ +interface HttpExceptionInterface extends \Throwable +{ + /** + * Returns the status code. + */ + public function getStatusCode(): int; + + /** + * Returns response headers. + */ + public function getHeaders(): array; +} diff --git a/vendor/symfony/http-kernel/Exception/InvalidMetadataException.php b/vendor/symfony/http-kernel/Exception/InvalidMetadataException.php new file mode 100644 index 0000000..129267a --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/InvalidMetadataException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +class InvalidMetadataException extends \LogicException +{ +} diff --git a/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php new file mode 100644 index 0000000..ca8741e --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class LengthRequiredHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(411, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/LockedHttpException.php b/vendor/symfony/http-kernel/Exception/LockedHttpException.php new file mode 100644 index 0000000..3f05c22 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/LockedHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Peter Dietrich + */ +class LockedHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(423, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php new file mode 100644 index 0000000..33572e4 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Kris Wallsmith + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * @param string[] $allow An array of allowed methods + */ + public function __construct(array $allow, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + $headers['Allow'] = strtoupper(implode(', ', $allow)); + + parent::__construct(405, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NearMissValueResolverException.php b/vendor/symfony/http-kernel/Exception/NearMissValueResolverException.php new file mode 100644 index 0000000..73ccfe9 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NearMissValueResolverException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Lets value resolvers tell when an argument could be under their watch but failed to be resolved. + * + * Throwing this exception inside `ValueResolverInterface::resolve` does not interrupt the value resolvers chain. + */ +class NearMissValueResolverException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php new file mode 100644 index 0000000..13e9c23 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class NotAcceptableHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(406, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php new file mode 100644 index 0000000..e1b489e --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Fabien Potencier + */ +class NotFoundHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(404, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php new file mode 100644 index 0000000..8ec710e --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class PreconditionFailedHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(412, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php new file mode 100644 index 0000000..8488769 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class PreconditionRequiredHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(428, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ResolverNotFoundException.php b/vendor/symfony/http-kernel/Exception/ResolverNotFoundException.php new file mode 100644 index 0000000..6d9fb8a --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ResolverNotFoundException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +class ResolverNotFoundException extends \RuntimeException +{ + /** + * @param string[] $alternatives + */ + public function __construct(string $name, array $alternatives = []) + { + $msg = sprintf('You have requested a non-existent resolver "%s".', $name); + if ($alternatives) { + if (1 === \count($alternatives)) { + $msg .= ' Did you mean this: "'; + } else { + $msg .= ' Did you mean one of these: "'; + } + $msg .= implode('", "', $alternatives).'"?'; + } + + parent::__construct($msg); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php new file mode 100644 index 0000000..842271d --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class ServiceUnavailableHttpException extends HttpException +{ + /** + * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried + */ + public function __construct(int|string|null $retryAfter = null, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + if ($retryAfter) { + $headers['Retry-After'] = $retryAfter; + } + + parent::__construct(503, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php new file mode 100644 index 0000000..2f749aa --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class TooManyRequestsHttpException extends HttpException +{ + /** + * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried + */ + public function __construct(int|string|null $retryAfter = null, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + if ($retryAfter) { + $headers['Retry-After'] = $retryAfter; + } + + parent::__construct(429, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php new file mode 100644 index 0000000..de8f314 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class UnauthorizedHttpException extends HttpException +{ + /** + * @param string $challenge WWW-Authenticate challenge string + */ + public function __construct(string $challenge, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + $headers['WWW-Authenticate'] = $challenge; + + parent::__construct(401, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php b/vendor/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php new file mode 100644 index 0000000..0145b16 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Mathias Arlaud + */ +class UnexpectedSessionUsageException extends \LogicException +{ +} diff --git a/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php new file mode 100644 index 0000000..162aa30 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Steve Hutchins + */ +class UnprocessableEntityHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(422, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php new file mode 100644 index 0000000..736337b --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class UnsupportedMediaTypeHttpException extends HttpException +{ + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) + { + parent::__construct(415, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php new file mode 100644 index 0000000..ddd5bfc --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\UriSigner; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; + +/** + * Implements Surrogate rendering strategy. + * + * @author Fabien Potencier + */ +abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer +{ + /** + * The "fallback" strategy when surrogate is not available should always be an + * instance of InlineFragmentRenderer. + * + * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported + */ + public function __construct( + private ?SurrogateInterface $surrogate, + private FragmentRendererInterface $inlineStrategy, + private ?UriSigner $signer = null, + ) { + } + + /** + * Note that if the current Request has no surrogate capability, this method + * falls back to use the inline rendering strategy. + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + * * comment: a comment to add when returning the surrogate tag + * * absolute_uri: whether to generate an absolute URI or not. Default is false + * + * Note, that not all surrogate strategies support all options. For now + * 'alt' and 'comment' are only supported by ESI. + * + * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface + */ + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response + { + if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { + $request->attributes->set('_check_controller_is_allowed', true); + + if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { + throw new \InvalidArgumentException('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is not supported. Use a different rendering strategy or pass scalar values.'); + } + + return $this->inlineStrategy->render($uri, $request, $options); + } + + $absolute = $options['absolute_uri'] ?? false; + + if ($uri instanceof ControllerReference) { + $uri = $this->generateSignedFragmentUri($uri, $request, $absolute); + } + + $alt = $options['alt'] ?? null; + if ($alt instanceof ControllerReference) { + $alt = $this->generateSignedFragmentUri($alt, $request, $absolute); + } + + $tag = $this->surrogate->renderIncludeTag($uri, $alt, $options['ignore_errors'] ?? false, $options['comment'] ?? ''); + + return new Response($tag); + } + + private function generateSignedFragmentUri(ControllerReference $uri, Request $request, bool $absolute): string + { + return (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request, $absolute); + } + + private function containsNonScalars(array $values): bool + { + foreach ($values as $value) { + if (\is_scalar($value) || null === $value) { + continue; + } + + if (!\is_array($value) || $this->containsNonScalars($value)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php new file mode 100644 index 0000000..da4c34a --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the ESI rendering strategy. + * + * @author Fabien Potencier + */ +class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + public function getName(): string + { + return 'esi'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentHandler.php b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php new file mode 100644 index 0000000..3fde950 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * Renders a URI that represents a resource fragment. + * + * This class handles the rendering of resource fragments that are included into + * a main resource. The handling of the rendering is managed by specialized renderers. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class FragmentHandler +{ + /** @var array */ + private array $renderers = []; + + /** + * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct( + private RequestStack $requestStack, + array $renderers = [], + private bool $debug = false, + ) { + foreach ($renderers as $renderer) { + $this->addRenderer($renderer); + } + } + + /** + * Adds a renderer. + */ + public function addRenderer(FragmentRendererInterface $renderer): void + { + $this->renderers[$renderer->getName()] = $renderer; + } + + /** + * Renders a URI and returns the Response content. + * + * Available options: + * + * * ignore_errors: true to return an empty string in case of an error + * + * @throws \InvalidArgumentException when the renderer does not exist + * @throws \LogicException when no main request is being handled + */ + public function render(string|ControllerReference $uri, string $renderer = 'inline', array $options = []): ?string + { + if (!isset($options['ignore_errors'])) { + $options['ignore_errors'] = !$this->debug; + } + + if (!isset($this->renderers[$renderer])) { + throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer)); + } + + if (!$request = $this->requestStack->getCurrentRequest()) { + throw new \LogicException('Rendering a fragment can only be done when handling a Request.'); + } + + return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options)); + } + + /** + * Delivers the Response as a string. + * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \RuntimeException when the Response is not successful + */ + protected function deliver(Response $response): ?string + { + if (!$response->isSuccessful()) { + $responseStatusCode = $response->getStatusCode(); + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $this->requestStack->getCurrentRequest()->getUri(), $responseStatusCode), 0, new HttpException($responseStatusCode)); + } + + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + $response->sendContent(); + + return null; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php new file mode 100644 index 0000000..a61f2e6 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Interface implemented by all rendering strategies. + * + * @author Fabien Potencier + */ +interface FragmentRendererInterface +{ + /** + * Renders a URI and returns the Response content. + */ + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response; + + /** + * Gets the name of the strategy. + */ + public function getName(): string; +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentUriGenerator.php b/vendor/symfony/http-kernel/Fragment/FragmentUriGenerator.php new file mode 100644 index 0000000..2fb8873 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentUriGenerator.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\UriSigner; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Generates a fragment URI. + * + * @author Kévin Dunglas + * @author Fabien Potencier + */ +final class FragmentUriGenerator implements FragmentUriGeneratorInterface +{ + public function __construct( + private string $fragmentPath, + private ?UriSigner $signer = null, + private ?RequestStack $requestStack = null, + ) { + } + + public function generate(ControllerReference $controller, ?Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string + { + if (null === $request && (null === $this->requestStack || null === $request = $this->requestStack->getCurrentRequest())) { + throw new \LogicException('Generating a fragment URL can only be done when handling a Request.'); + } + + if ($sign && null === $this->signer) { + throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); + } + + if ($strict) { + $this->checkNonScalar($controller->attributes); + } + + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($controller->attributes['_format'])) { + $controller->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($controller->attributes['_locale'])) { + $controller->attributes['_locale'] = $request->getLocale(); + } + + $controller->attributes['_controller'] = $controller->controller; + $controller->query['_path'] = http_build_query($controller->attributes, '', '&'); + $path = $this->fragmentPath.'?'.http_build_query($controller->query, '', '&'); + + // we need to sign the absolute URI, but want to return the path only. + $fragmentUri = $sign || $absolute ? $request->getUriForPath($path) : $request->getBaseUrl().$path; + + if (!$sign) { + return $fragmentUri; + } + + $fragmentUri = $this->signer->sign($fragmentUri); + + return $absolute ? $fragmentUri : substr($fragmentUri, \strlen($request->getSchemeAndHttpHost())); + } + + private function checkNonScalar(array $values): void + { + foreach ($values as $key => $value) { + if (\is_array($value)) { + $this->checkNonScalar($value); + } elseif (!\is_scalar($value) && null !== $value) { + throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php b/vendor/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php new file mode 100644 index 0000000..6b1317c --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Interface implemented by rendering strategies able to generate a URL for a fragment. + * + * @author Kévin Dunglas + */ +interface FragmentUriGeneratorInterface +{ + /** + * Generates a fragment URI for a given controller. + * + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + * @param bool $sign Whether to sign the URL or not + */ + public function generate(ControllerReference $controller, ?Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string; +} diff --git a/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php new file mode 100644 index 0000000..0271ea5 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\UriSigner; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Twig\Environment; + +/** + * Implements the Hinclude rendering strategy. + * + * @author Fabien Potencier + */ +class HIncludeFragmentRenderer extends RoutableFragmentRenderer +{ + /** + * @param string|null $globalDefaultTemplate The global default content (it can be a template name or the content) + */ + public function __construct( + private ?Environment $twig = null, + private ?UriSigner $signer = null, + private ?string $globalDefaultTemplate = null, + private string $charset = 'utf-8', + ) { + } + + /** + * Checks if a templating engine has been set. + */ + public function hasTemplating(): bool + { + return null !== $this->twig; + } + + /** + * Additional available options: + * + * * default: The default content (it can be a template name or the content) + * * id: An optional hx:include tag id attribute + * * attributes: An optional array of hx:include tag attributes + */ + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response + { + if ($uri instanceof ControllerReference) { + $uri = (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); + } + + // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. + $uri = str_replace('&', '&', $uri); + + $template = $options['default'] ?? $this->globalDefaultTemplate; + if (null !== $this->twig && $template && $this->twig->getLoader()->exists($template)) { + $content = $this->twig->render($template); + } else { + $content = $template; + } + + $attributes = isset($options['attributes']) && \is_array($options['attributes']) ? $options['attributes'] : []; + if (isset($options['id']) && $options['id']) { + $attributes['id'] = $options['id']; + } + $renderedAttributes = ''; + if (\count($attributes) > 0) { + $flags = \ENT_QUOTES | \ENT_SUBSTITUTE; + foreach ($attributes as $attribute => $value) { + $renderedAttributes .= sprintf( + ' %s="%s"', + htmlspecialchars($attribute, $flags, $this->charset, false), + htmlspecialchars($value, $flags, $this->charset, false) + ); + } + } + + return new Response(sprintf('%s', $uri, $renderedAttributes, $content)); + } + + public function getName(): string + { + return 'hinclude'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php new file mode 100644 index 0000000..2dbe7be --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. + * + * @author Fabien Potencier + */ +class InlineFragmentRenderer extends RoutableFragmentRenderer +{ + public function __construct( + private HttpKernelInterface $kernel, + private ?EventDispatcherInterface $dispatcher = null, + ) { + } + + /** + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + */ + public function render(string|ControllerReference $uri, Request $request, array $options = []): Response + { + $reference = null; + if ($uri instanceof ControllerReference) { + $reference = $uri; + + // Remove attributes from the generated URI because if not, the Symfony + // routing system will use them to populate the Request attributes. We don't + // want that as we want to preserve objects (so we manually set Request attributes + // below instead) + $attributes = $reference->attributes; + $reference->attributes = []; + + // The request format and locale might have been overridden by the user + foreach (['_format', '_locale'] as $key) { + if (isset($attributes[$key])) { + $reference->attributes[$key] = $attributes[$key]; + } + } + + $uri = $this->generateFragmentUri($uri, $request, false, false); + + $reference->attributes = array_merge($attributes, $reference->attributes); + } + + $subRequest = $this->createSubRequest($uri, $request); + + // override Request attributes as they can be objects (which are not supported by the generated URI) + if (null !== $reference) { + $subRequest->attributes->add($reference->attributes); + } + + $level = ob_get_level(); + try { + return SubRequestHandler::handle($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + // we dispatch the exception event to trigger the logging + // the response that comes back is ignored + if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { + $event = new ExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + + $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); + } + + // let's clean up the output buffers that were created by the sub-request + Response::closeOutputBuffers($level, false); + + if (isset($options['alt'])) { + $alt = $options['alt']; + unset($options['alt']); + + return $this->render($alt, $request, $options); + } + + if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { + throw $e; + } + + return new Response(); + } + } + + protected function createSubRequest(string $uri, Request $request): Request + { + $cookies = $request->cookies->all(); + $server = $request->server->all(); + + unset($server['HTTP_IF_MODIFIED_SINCE']); + unset($server['HTTP_IF_NONE_MATCH']); + + $subRequest = Request::create($uri, 'get', [], $cookies, [], $server); + if ($request->headers->has('Surrogate-Capability')) { + $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); + } + + static $setSession; + + $setSession ??= \Closure::bind(static function ($subRequest, $request) { $subRequest->session = $request->session; }, null, Request::class); + $setSession($subRequest, $request); + + if ($request->get('_format')) { + $subRequest->attributes->set('_format', $request->get('_format')); + } + if ($request->getDefaultLocale() !== $request->getLocale()) { + $subRequest->setLocale($request->getLocale()); + } + if ($request->attributes->has('_stateless')) { + $subRequest->attributes->set('_stateless', $request->attributes->get('_stateless')); + } + if ($request->attributes->has('_check_controller_is_allowed')) { + $subRequest->attributes->set('_check_controller_is_allowed', $request->attributes->get('_check_controller_is_allowed')); + } + + return $subRequest; + } + + public function getName(): string + { + return 'inline'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php new file mode 100644 index 0000000..f9b400c --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +/** + * Adds the possibility to generate a fragment URI for a given Controller. + * + * @author Fabien Potencier + */ +abstract class RoutableFragmentRenderer implements FragmentRendererInterface +{ + /** + * @internal + */ + protected string $fragmentPath = '/_fragment'; + + /** + * Sets the fragment path that triggers the fragment listener. + * + * @see FragmentListener + */ + public function setFragmentPath(string $path): void + { + $this->fragmentPath = $path; + } + + /** + * Generates a fragment URI for a given controller. + * + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + */ + protected function generateFragmentUri(ControllerReference $reference, Request $request, bool $absolute = false, bool $strict = true): string + { + return (new FragmentUriGenerator($this->fragmentPath))->generate($reference, $request, $absolute, $strict, false); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php new file mode 100644 index 0000000..6319bcd --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the SSI rendering strategy. + * + * @author Sebastian Krebs + */ +class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + public function getName(): string + { + return 'ssi'; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php new file mode 100644 index 0000000..d0e8e2a --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Abstract class implementing Surrogate capabilities to Request and Response instances. + * + * @author Fabien Potencier + * @author Robin Chalas + */ +abstract class AbstractSurrogate implements SurrogateInterface +{ + /** + * @param array $contentTypes An array of content-type that should be parsed for Surrogate information + * (default: text/html, text/xml, application/xhtml+xml, and application/xml) + */ + public function __construct( + protected array $contentTypes = ['text/html', 'text/xml', 'application/xhtml+xml', 'application/xml'], + ) { + } + + /** + * Returns a new cache strategy instance. + */ + public function createCacheStrategy(): ResponseCacheStrategyInterface + { + return new ResponseCacheStrategy(); + } + + public function hasSurrogateCapability(Request $request): bool + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return str_contains($value, sprintf('%s/1.0', strtoupper($this->getName()))); + } + + public function addSurrogateCapability(Request $request): void + { + $current = $request->headers->get('Surrogate-Capability'); + $new = sprintf('symfony="%s/1.0"', strtoupper($this->getName())); + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + public function needsParsing(Response $response): bool + { + if (!$control = $response->headers->get('Surrogate-Control')) { + return false; + } + + $pattern = sprintf('#content="[^"]*%s/1.0[^"]*"#', strtoupper($this->getName())); + + return (bool) preg_match($pattern, $control); + } + + public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors): string + { + $subRequest = Request::create($uri, Request::METHOD_GET, [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all()); + + try { + $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); + + if (!$response->isSuccessful() && Response::HTTP_NOT_MODIFIED !== $response->getStatusCode()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $subRequest->getUri(), $response->getStatusCode())); + } + + return $response->getContent(); + } catch (\Exception $e) { + if ($alt) { + return $this->handle($cache, $alt, '', $ignoreErrors); + } + + if (!$ignoreErrors) { + throw $e; + } + } + + return ''; + } + + /** + * Remove the Surrogate from the Surrogate-Control header. + */ + protected function removeFromControl(Response $response): void + { + if (!$response->headers->has('Surrogate-Control')) { + return; + } + + $value = $response->headers->get('Surrogate-Control'); + $upperName = strtoupper($this->getName()); + + if (sprintf('content="%s/1.0"', $upperName) == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match(sprintf('#,\s*content="%s/1.0"#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#,\s*content="%s/1.0"#', $upperName), '', $value)); + } elseif (preg_match(sprintf('#content="%s/1.0",\s*#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value)); + } + } + + protected static function generateBodyEvalBoundary(): string + { + static $cookie; + $cookie = hash('xxh128', $cookie ?? $cookie = random_bytes(16), true); + $boundary = base64_encode($cookie); + + \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === \strlen($boundary)); + + return $boundary; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Esi.php b/vendor/symfony/http-kernel/HttpCache/Esi.php new file mode 100644 index 0000000..8a33823 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Esi.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Esi implements the ESI capabilities to Request and Response instances. + * + * For more information, read the following W3C notes: + * + * * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang) + * + * * Edge Architecture Specification (http://www.w3.org/TR/edge-arch) + * + * @author Fabien Potencier + */ +class Esi extends AbstractSurrogate +{ + public function getName(): string + { + return 'esi'; + } + + public function addSurrogateControl(Response $response): void + { + if (str_contains($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); + } + } + + public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string + { + $html = sprintf('', + $uri, + $ignoreErrors ? ' onerror="continue"' : '', + $alt ? sprintf(' alt="%s"', $alt) : '' + ); + + if ($comment) { + return sprintf("\n%s", $comment, $html); + } + + return $html; + } + + public function process(Request $request, Response $response): Response + { + $type = $response->headers->get('Content-Type'); + if (!$type) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!\in_array($parts[0], $this->contentTypes, true)) { + return $response; + } + + // we don't use a proper XML parser here as we can have ESI tags in a plain text response + $content = $response->getContent(); + $content = preg_replace('#.*?#s', '', $content); + $content = preg_replace('#]+>#s', '', $content); + + $boundary = self::generateBodyEvalBoundary(); + $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); + + $i = 1; + while (isset($chunks[$i])) { + $options = []; + preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, \PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['src'])) { + throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); + } + + $chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n"; + $i += 2; + } + $content = $boundary.implode('', $chunks).$boundary; + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'ESI'); + + // remove ESI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + + return $response; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/HttpCache/HttpCache.php new file mode 100644 index 0000000..549eaa2 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/HttpCache.php @@ -0,0 +1,740 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; + +/** + * Cache provides HTTP caching. + * + * @author Fabien Potencier + */ +class HttpCache implements HttpKernelInterface, TerminableInterface +{ + public const BODY_EVAL_BOUNDARY_LENGTH = 24; + + private Request $request; + private ?ResponseCacheStrategyInterface $surrogateCacheStrategy = null; + private array $options = []; + private array $traces = []; + + /** + * Constructor. + * + * The available options are: + * + * * debug If true, exceptions are thrown when things go wrong. Otherwise, the cache + * will try to carry on and deliver a meaningful response. + * + * * trace_level May be one of 'none', 'short' and 'full'. For 'short', a concise trace of the + * main request will be added as an HTTP header. 'full' will add traces for all + * requests (including ESI subrequests). (default: 'full' if in debug; 'none' otherwise) + * + * * trace_header Header name to use for traces. (default: X-Symfony-Cache) + * + * * default_ttl The number of seconds that a cache entry should be considered + * fresh when no explicit freshness information is provided in + * a response. Explicit Cache-Control or Expires headers + * override this value. (default: 0) + * + * * private_headers Set of request headers that trigger "private" cache-control behavior + * on responses that don't explicitly state whether the response is + * public or private via a Cache-Control directive. (default: Authorization and Cookie) + * + * * skip_response_headers Set of response headers that are never cached even if a response is cacheable (public). + * (default: Set-Cookie) + * + * * allow_reload Specifies whether the client can force a cache reload by including a + * Cache-Control "no-cache" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * allow_revalidate Specifies whether the client can force a cache revalidate by including + * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the + * Response TTL precision is a second) during which the cache can immediately return + * a stale response while it revalidates it in the background (default: 2). + * This setting is overridden by the stale-while-revalidate HTTP Cache-Control + * extension (see RFC 5861). + * + * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which + * the cache can serve a stale response when an error is encountered (default: 60). + * This setting is overridden by the stale-if-error HTTP Cache-Control extension + * (see RFC 5861). + */ + public function __construct( + private HttpKernelInterface $kernel, + private StoreInterface $store, + private ?SurrogateInterface $surrogate = null, + array $options = [], + ) { + + // needed in case there is a fatal error because the backend is too slow to respond + register_shutdown_function($this->store->cleanup(...)); + + $this->options = array_merge([ + 'debug' => false, + 'default_ttl' => 0, + 'private_headers' => ['Authorization', 'Cookie'], + 'skip_response_headers' => ['Set-Cookie'], + 'allow_reload' => false, + 'allow_revalidate' => false, + 'stale_while_revalidate' => 2, + 'stale_if_error' => 60, + 'trace_level' => 'none', + 'trace_header' => 'X-Symfony-Cache', + ], $options); + + if (!isset($options['trace_level'])) { + $this->options['trace_level'] = $this->options['debug'] ? 'full' : 'none'; + } + } + + /** + * Gets the current store. + */ + public function getStore(): StoreInterface + { + return $this->store; + } + + /** + * Returns an array of events that took place during processing of the last request. + */ + public function getTraces(): array + { + return $this->traces; + } + + private function addTraces(Response $response): void + { + $traceString = null; + + if ('full' === $this->options['trace_level']) { + $traceString = $this->getLog(); + } + + if ('short' === $this->options['trace_level'] && $masterId = array_key_first($this->traces)) { + $traceString = implode('/', $this->traces[$masterId]); + } + + if (null !== $traceString) { + $response->headers->add([$this->options['trace_header'] => $traceString]); + } + } + + /** + * Returns a log message for the events of the last request processing. + */ + public function getLog(): string + { + $log = []; + foreach ($this->traces as $request => $traces) { + $log[] = sprintf('%s: %s', $request, implode(', ', $traces)); + } + + return implode('; ', $log); + } + + /** + * Gets the Request instance associated with the main request. + */ + public function getRequest(): Request + { + return $this->request; + } + + /** + * Gets the Kernel instance. + */ + public function getKernel(): HttpKernelInterface + { + return $this->kernel; + } + + /** + * Gets the Surrogate instance. + * + * @throws \LogicException + */ + public function getSurrogate(): SurrogateInterface + { + return $this->surrogate; + } + + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response + { + // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism + if (HttpKernelInterface::MAIN_REQUEST === $type) { + $this->traces = []; + // Keep a clone of the original request for surrogates so they can access it. + // We must clone here to get a separate instance because the application will modify the request during + // the application flow (we know it always does because we do ourselves by setting REMOTE_ADDR to 127.0.0.1 + // and adding the X-Forwarded-For header, see HttpCache::forward()). + $this->request = clone $request; + if (null !== $this->surrogate) { + $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); + } + } + + $this->traces[$this->getTraceKey($request)] = []; + + if (!$request->isMethodSafe()) { + $response = $this->invalidate($request, $catch); + } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { + $response = $this->pass($request, $catch); + } elseif ($this->options['allow_reload'] && $request->isNoCache()) { + /* + If allow_reload is configured and the client requests "Cache-Control: no-cache", + reload the cache by fetching a fresh response and caching it (if possible). + */ + $this->record($request, 'reload'); + $response = $this->fetch($request, $catch); + } else { + $response = $this->lookup($request, $catch); + } + + $this->restoreResponseBody($request, $response); + + if (HttpKernelInterface::MAIN_REQUEST === $type) { + $this->addTraces($response); + } + + if (null !== $this->surrogate) { + if (HttpKernelInterface::MAIN_REQUEST === $type) { + $this->surrogateCacheStrategy->update($response); + } else { + $this->surrogateCacheStrategy->add($response); + } + } + + $response->prepare($request); + + if (HttpKernelInterface::MAIN_REQUEST === $type) { + $response->isNotModified($request); + } + + return $response; + } + + public function terminate(Request $request, Response $response): void + { + // Do not call any listeners in case of a cache hit. + // This ensures identical behavior as if you had a separate + // reverse caching proxy such as Varnish and the like. + if (\in_array('fresh', $this->traces[$this->getTraceKey($request)] ?? [], true)) { + return; + } + + if ($this->getKernel() instanceof TerminableInterface) { + $this->getKernel()->terminate($request, $response); + } + } + + /** + * Forwards the Request to the backend without storing the Response in the cache. + * + * @param bool $catch Whether to process exceptions + */ + protected function pass(Request $request, bool $catch = false): Response + { + $this->record($request, 'pass'); + + return $this->forward($request, $catch); + } + + /** + * Invalidates non-safe methods (like POST, PUT, and DELETE). + * + * @param bool $catch Whether to process exceptions + * + * @throws \Exception + * + * @see RFC2616 13.10 + */ + protected function invalidate(Request $request, bool $catch = false): Response + { + $response = $this->pass($request, $catch); + + // invalidate only when the response is successful + if ($response->isSuccessful() || $response->isRedirect()) { + try { + $this->store->invalidate($request); + + // As per the RFC, invalidate Location and Content-Location URLs if present + foreach (['Location', 'Content-Location'] as $header) { + if ($uri = $response->headers->get($header)) { + $subRequest = Request::create($uri, 'get', [], [], [], $request->server->all()); + + $this->store->invalidate($subRequest); + } + } + + $this->record($request, 'invalidate'); + } catch (\Exception $e) { + $this->record($request, 'invalidate-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + } + + return $response; + } + + /** + * Lookups a Response from the cache for the given Request. + * + * When a matching cache entry is found and is fresh, it uses it as the + * response without forwarding any request to the backend. When a matching + * cache entry is found but is stale, it attempts to "validate" the entry with + * the backend using conditional GET. When no matching cache entry is found, + * it triggers "miss" processing. + * + * @param bool $catch Whether to process exceptions + * + * @throws \Exception + */ + protected function lookup(Request $request, bool $catch = false): Response + { + try { + $entry = $this->store->lookup($request); + } catch (\Exception $e) { + $this->record($request, 'lookup-failed'); + + if ($this->options['debug']) { + throw $e; + } + + return $this->pass($request, $catch); + } + + if (null === $entry) { + $this->record($request, 'miss'); + + return $this->fetch($request, $catch); + } + + if (!$this->isFreshEnough($request, $entry)) { + $this->record($request, 'stale'); + + return $this->validate($request, $entry, $catch); + } + + if ($entry->headers->hasCacheControlDirective('no-cache')) { + return $this->validate($request, $entry, $catch); + } + + $this->record($request, 'fresh'); + + $entry->headers->set('Age', $entry->getAge()); + + return $entry; + } + + /** + * Validates that a cache entry is fresh. + * + * The original request is used as a template for a conditional + * GET request with the backend. + * + * @param bool $catch Whether to process exceptions + */ + protected function validate(Request $request, Response $entry, bool $catch = false): Response + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // add our cached last-modified validator + if ($entry->headers->has('Last-Modified')) { + $subRequest->headers->set('If-Modified-Since', $entry->headers->get('Last-Modified')); + } + + // Add our cached etag validator to the environment. + // We keep the etags from the client to handle the case when the client + // has a different private valid entry which is not cached here. + $cachedEtags = $entry->getEtag() ? [$entry->getEtag()] : []; + $requestEtags = $request->getETags(); + if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { + $subRequest->headers->set('If-None-Match', implode(', ', $etags)); + } + + $response = $this->forward($subRequest, $catch, $entry); + + if (304 == $response->getStatusCode()) { + $this->record($request, 'valid'); + + // return the response and not the cache entry if the response is valid but not cached + $etag = $response->getEtag(); + if ($etag && \in_array($etag, $requestEtags, true) && !\in_array($etag, $cachedEtags, true)) { + return $response; + } + + $entry = clone $entry; + $entry->headers->remove('Date'); + + foreach (['Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified'] as $name) { + if ($response->headers->has($name)) { + $entry->headers->set($name, $response->headers->get($name)); + } + } + + $response = $entry; + } else { + $this->record($request, 'invalid'); + } + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Unconditionally fetches a fresh response from the backend and + * stores it in the cache if is cacheable. + * + * @param bool $catch Whether to process exceptions + */ + protected function fetch(Request $request, bool $catch = false): Response + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // avoid that the backend sends no content + $subRequest->headers->remove('If-Modified-Since'); + $subRequest->headers->remove('If-None-Match'); + + $response = $this->forward($subRequest, $catch); + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and returns the Response. + * + * All backend requests (cache passes, fetches, cache validations) + * run through this method. + * + * @param bool $catch Whether to catch exceptions or not + * @param Response|null $entry A Response instance (the stale entry if present, null otherwise) + */ + protected function forward(Request $request, bool $catch = false, ?Response $entry = null): Response + { + $this->surrogate?->addSurrogateCapability($request); + + // always a "master" request (as the real master request can be in cache) + $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST, $catch); + + /* + * Support stale-if-error given on Responses or as a config option. + * RFC 7234 summarizes in Section 4.2.4 (but also mentions with the individual + * Cache-Control directives) that + * + * A cache MUST NOT generate a stale response if it is prohibited by an + * explicit in-protocol directive (e.g., by a "no-store" or "no-cache" + * cache directive, a "must-revalidate" cache-response-directive, or an + * applicable "s-maxage" or "proxy-revalidate" cache-response-directive; + * see Section 5.2.2). + * + * https://tools.ietf.org/html/rfc7234#section-4.2.4 + * + * We deviate from this in one detail, namely that we *do* serve entries in the + * stale-if-error case even if they have a `s-maxage` Cache-Control directive. + */ + if (null !== $entry + && \in_array($response->getStatusCode(), [500, 502, 503, 504]) + && !$entry->headers->hasCacheControlDirective('no-cache') + && !$entry->mustRevalidate() + ) { + if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { + $age = $this->options['stale_if_error']; + } + + /* + * stale-if-error gives the (extra) time that the Response may be used *after* it has become stale. + * So we compare the time the $entry has been sitting in the cache already with the + * time it was fresh plus the allowed grace period. + */ + if ($entry->getAge() <= $entry->getMaxAge() + $age) { + $this->record($request, 'stale-if-error'); + + return $entry; + } + } + + /* + RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate + clock MUST NOT send a "Date" header, although it MUST send one in most other cases + except for 1xx or 5xx responses where it MAY do so. + + Anyway, a client that received a message without a "Date" header MUST add it. + */ + if (!$response->headers->has('Date')) { + $response->setDate(\DateTimeImmutable::createFromFormat('U', time())); + } + + $this->processResponseBody($request, $response); + + if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { + $response->setPrivate(); + } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { + $response->setTtl($this->options['default_ttl']); + } + + return $response; + } + + /** + * Checks whether the cache entry is "fresh enough" to satisfy the Request. + */ + protected function isFreshEnough(Request $request, Response $entry): bool + { + if (!$entry->isFresh()) { + return $this->lock($request, $entry); + } + + if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) { + return $maxAge > 0 && $maxAge >= $entry->getAge(); + } + + return true; + } + + /** + * Locks a Request during the call to the backend. + * + * @return bool true if the cache entry can be returned even if it is staled, false otherwise + */ + protected function lock(Request $request, Response $entry): bool + { + // try to acquire a lock to call the backend + $lock = $this->store->lock($request); + + if (true === $lock) { + // we have the lock, call the backend + return false; + } + + // there is already another process calling the backend + + // May we serve a stale response? + if ($this->mayServeStaleWhileRevalidate($entry)) { + $this->record($request, 'stale-while-revalidate'); + + return true; + } + + // wait for the lock to be released + if ($this->waitForLock($request)) { + // replace the current entry with the fresh one + $new = $this->lookup($request); + $entry->headers = $new->headers; + $entry->setContent($new->getContent()); + $entry->setStatusCode($new->getStatusCode()); + $entry->setProtocolVersion($new->getProtocolVersion()); + foreach ($new->headers->getCookies() as $cookie) { + $entry->headers->setCookie($cookie); + } + } else { + // backend is slow as hell, send a 503 response (to avoid the dog pile effect) + $entry->setStatusCode(503); + $entry->setContent('503 Service Unavailable'); + $entry->headers->set('Retry-After', 10); + } + + return true; + } + + /** + * Writes the Response to the cache. + * + * @throws \Exception + */ + protected function store(Request $request, Response $response): void + { + try { + $restoreHeaders = []; + foreach ($this->options['skip_response_headers'] as $header) { + if (!$response->headers->has($header)) { + continue; + } + + $restoreHeaders[$header] = $response->headers->all($header); + $response->headers->remove($header); + } + + $this->store->write($request, $response); + $this->record($request, 'store'); + + $response->headers->set('Age', $response->getAge()); + } catch (\Exception $e) { + $this->record($request, 'store-failed'); + + if ($this->options['debug']) { + throw $e; + } + } finally { + foreach ($restoreHeaders as $header => $values) { + $response->headers->set($header, $values); + } + } + + // now that the response is cached, release the lock + $this->store->unlock($request); + } + + /** + * Restores the Response body. + */ + private function restoreResponseBody(Request $request, Response $response): void + { + if ($response->headers->has('X-Body-Eval')) { + \assert(self::BODY_EVAL_BOUNDARY_LENGTH === 24); + + ob_start(); + + $content = $response->getContent(); + $boundary = substr($content, 0, 24); + $j = strpos($content, $boundary, 24); + echo substr($content, 24, $j - 24); + $i = $j + 24; + + while (false !== $j = strpos($content, $boundary, $i)) { + [$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4); + $i = $j + 24; + + echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors); + echo $part; + } + + $response->setContent(ob_get_clean()); + $response->headers->remove('X-Body-Eval'); + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); + } + } elseif ($response->headers->has('X-Body-File')) { + // Response does not include possibly dynamic content (ESI, SSI), so we need + // not handle the content for HEAD requests + if (!$request->isMethod('HEAD')) { + $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + } + } else { + return; + } + + $response->headers->remove('X-Body-File'); + } + + protected function processResponseBody(Request $request, Response $response): void + { + if ($this->surrogate?->needsParsing($response)) { + $this->surrogate->process($request, $response); + } + } + + /** + * Checks if the Request includes authorization or other sensitive information + * that should cause the Response to be considered private by default. + */ + private function isPrivateRequest(Request $request): bool + { + foreach ($this->options['private_headers'] as $key) { + $key = strtolower(str_replace('HTTP_', '', $key)); + + if ('cookie' === $key) { + if (\count($request->cookies->all())) { + return true; + } + } elseif ($request->headers->has($key)) { + return true; + } + } + + return false; + } + + /** + * Records that an event took place. + */ + private function record(Request $request, string $event): void + { + $this->traces[$this->getTraceKey($request)][] = $event; + } + + /** + * Calculates the key we use in the "trace" array for a given request. + */ + private function getTraceKey(Request $request): string + { + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + + return $request->getMethod().' '.$path; + } + + /** + * Checks whether the given (cached) response may be served as "stale" when a revalidation + * is currently in progress. + */ + private function mayServeStaleWhileRevalidate(Response $entry): bool + { + $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); + $timeout ??= $this->options['stale_while_revalidate']; + + $age = $entry->getAge(); + $maxAge = $entry->getMaxAge() ?? 0; + $ttl = $maxAge - $age; + + return abs($ttl) < $timeout; + } + + /** + * Waits for the store to release a locked entry. + */ + private function waitForLock(Request $request): bool + { + $wait = 0; + while ($this->store->isLocked($request) && $wait < 100) { + usleep(50000); + ++$wait; + } + + return $wait < 100; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php new file mode 100644 index 0000000..c7d32f6 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategy knows how to compute the Response cache HTTP header + * based on the different response cache headers. + * + * This implementation changes the main response TTL to the smallest TTL received + * or force validation if one of the surrogates has validation cache strategy. + * + * @author Fabien Potencier + */ +class ResponseCacheStrategy implements ResponseCacheStrategyInterface +{ + /** + * Cache-Control headers that are sent to the final response if they appear in ANY of the responses. + */ + private const OVERRIDE_DIRECTIVES = ['private', 'no-cache', 'no-store', 'no-transform', 'must-revalidate', 'proxy-revalidate']; + + /** + * Cache-Control headers that are sent to the final response if they appear in ALL of the responses. + */ + private const INHERIT_DIRECTIVES = ['public', 'immutable']; + + private int $embeddedResponses = 0; + private bool $isNotCacheableResponseEmbedded = false; + private int $age = 0; + private \DateTimeInterface|false|null $lastModified = null; + private array $flagDirectives = [ + 'no-cache' => null, + 'no-store' => null, + 'no-transform' => null, + 'must-revalidate' => null, + 'proxy-revalidate' => null, + 'public' => null, + 'private' => null, + 'immutable' => null, + ]; + private array $ageDirectives = [ + 'max-age' => null, + 's-maxage' => null, + 'expires' => null, + ]; + + public function add(Response $response): void + { + ++$this->embeddedResponses; + + foreach (self::OVERRIDE_DIRECTIVES as $directive) { + if ($response->headers->hasCacheControlDirective($directive)) { + $this->flagDirectives[$directive] = true; + } + } + + foreach (self::INHERIT_DIRECTIVES as $directive) { + if (false !== $this->flagDirectives[$directive]) { + $this->flagDirectives[$directive] = $response->headers->hasCacheControlDirective($directive); + } + } + + $age = $response->getAge(); + $this->age = max($this->age, $age); + + if ($this->willMakeFinalResponseUncacheable($response)) { + $this->isNotCacheableResponseEmbedded = true; + + return; + } + + $isHeuristicallyCacheable = $response->headers->hasCacheControlDirective('public'); + $maxAge = $response->headers->hasCacheControlDirective('max-age') ? (int) $response->headers->getCacheControlDirective('max-age') : null; + $this->storeRelativeAgeDirective('max-age', $maxAge, $age, $isHeuristicallyCacheable); + $sharedMaxAge = $response->headers->hasCacheControlDirective('s-maxage') ? (int) $response->headers->getCacheControlDirective('s-maxage') : $maxAge; + $this->storeRelativeAgeDirective('s-maxage', $sharedMaxAge, $age, $isHeuristicallyCacheable); + + $expires = $response->getExpires(); + $expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null; + $this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0, $isHeuristicallyCacheable); + + if (false !== $this->lastModified) { + $lastModified = $response->getLastModified(); + $this->lastModified = $lastModified ? max($this->lastModified, $lastModified) : false; + } + } + + public function update(Response $response): void + { + // if we have no embedded Response, do nothing + if (0 === $this->embeddedResponses) { + return; + } + + // Remove Etag since it cannot be merged from embedded responses. + $response->setEtag(null); + + $this->add($response); + + $response->headers->set('Age', $this->age); + + if ($this->isNotCacheableResponseEmbedded) { + $response->setLastModified(null); + + if ($this->flagDirectives['no-store']) { + $response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate'); + } else { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + } + + return; + } + + $response->setLastModified($this->lastModified ?: null); + + $flags = array_filter($this->flagDirectives); + + if (isset($flags['must-revalidate'])) { + $flags['no-cache'] = true; + } + + $response->headers->set('Cache-Control', implode(', ', array_keys($flags))); + + $maxAge = null; + + if (is_numeric($this->ageDirectives['max-age'])) { + $maxAge = $this->ageDirectives['max-age'] + $this->age; + $response->headers->addCacheControlDirective('max-age', $maxAge); + } + + if (is_numeric($this->ageDirectives['s-maxage'])) { + $sMaxage = $this->ageDirectives['s-maxage'] + $this->age; + + if ($maxAge !== $sMaxage) { + $response->headers->addCacheControlDirective('s-maxage', $sMaxage); + } + } + + if (is_numeric($this->ageDirectives['expires'])) { + $date = clone $response->getDate(); + $date = $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds'); + $response->setExpires($date); + } + } + + /** + * RFC2616, Section 13.4. + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 + */ + private function willMakeFinalResponseUncacheable(Response $response): bool + { + // RFC2616: A response received with a status code of 200, 203, 300, 301 or 410 + // MAY be stored by a cache […] unless a cache-control directive prohibits caching. + if ($response->headers->hasCacheControlDirective('no-cache') + || $response->headers->hasCacheControlDirective('no-store') + ) { + return true; + } + + // Etag headers cannot be merged, they render the response uncacheable + // by default (except if the response also has max-age etc.). + if (null === $response->getEtag() && \in_array($response->getStatusCode(), [200, 203, 300, 301, 410])) { + return false; + } + + // RFC2616: A response received with any other status code (e.g. status codes 302 and 307) + // MUST NOT be returned in a reply to a subsequent request unless there are + // cache-control directives or another header(s) that explicitly allow it. + $cacheControl = ['max-age', 's-maxage', 'must-revalidate', 'proxy-revalidate', 'public', 'private']; + foreach ($cacheControl as $key) { + if ($response->headers->hasCacheControlDirective($key)) { + return false; + } + } + + if ($response->headers->has('Expires')) { + return false; + } + + return true; + } + + /** + * Store lowest max-age/s-maxage/expires for the final response. + * + * The response might have been stored in cache a while ago. To keep things comparable, + * we have to subtract the age so that the value is normalized for an age of 0. + * + * If the value is lower than the currently stored value, we update the value, to keep a rolling + * minimal value of each instruction. + * + * If the value is NULL and the isHeuristicallyCacheable parameter is false, the directive will + * not be set on the final response. In this case, not all responses had the directive set and no + * value can be found that satisfies the requirements of all responses. The directive will be dropped + * from the final response. + * + * If the isHeuristicallyCacheable parameter is true, however, the current response has been marked + * as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve + * as an upper bound. In this case, we can proceed and possibly keep the directive on the final response. + */ + private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable): void + { + if (null === $value) { + if ($isHeuristicallyCacheable) { + /* + * See https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.2 + * This particular response does not require maximum lifetime; heuristics might be applied. + * Other responses, however, might have more stringent requirements on maximum lifetime. + * So, return early here so that the final response can have the more limiting value set. + */ + return; + } + $this->ageDirectives[$directive] = false; + } + + if (false !== $this->ageDirectives[$directive]) { + $value -= $age; + $this->ageDirectives[$directive] = null !== $this->ageDirectives[$directive] ? min($this->ageDirectives[$directive], $value) : $value; + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php new file mode 100644 index 0000000..6143a13 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php @@ -0,0 +1,37 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategyInterface implementations know how to compute the + * Response cache HTTP header based on the different response cache headers. + * + * @author Fabien Potencier + */ +interface ResponseCacheStrategyInterface +{ + /** + * Adds a Response. + */ + public function add(Response $response): void; + + /** + * Updates the Response HTTP headers based on the embedded Responses. + */ + public function update(Response $response): void; +} diff --git a/vendor/symfony/http-kernel/HttpCache/Ssi.php b/vendor/symfony/http-kernel/HttpCache/Ssi.php new file mode 100644 index 0000000..3886864 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Ssi.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Ssi implements the SSI capabilities to Request and Response instances. + * + * @author Sebastian Krebs + */ +class Ssi extends AbstractSurrogate +{ + public function getName(): string + { + return 'ssi'; + } + + public function addSurrogateControl(Response $response): void + { + if (str_contains($response->getContent(), '', $uri); + } + + public function process(Request $request, Response $response): Response + { + $type = $response->headers->get('Content-Type'); + if (!$type) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!\in_array($parts[0], $this->contentTypes, true)) { + return $response; + } + + // we don't use a proper XML parser here as we can have SSI tags in a plain text response + $content = $response->getContent(); + $boundary = self::generateBodyEvalBoundary(); + $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); + + $i = 1; + while (isset($chunks[$i])) { + $options = []; + preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, \PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['virtual'])) { + throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); + } + + $chunks[$i] = $boundary.$options['virtual']."\n\n\n"; + $i += 2; + } + $content = $boundary.implode('', $chunks).$boundary; + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'SSI'); + + // remove SSI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + + return $response; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Store.php b/vendor/symfony/http-kernel/HttpCache/Store.php new file mode 100644 index 0000000..a4a709a --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Store.php @@ -0,0 +1,488 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Store implements all the logic for storing cache metadata (Request and Response headers). + * + * @author Fabien Potencier + */ +class Store implements StoreInterface +{ + /** @var \SplObjectStorage */ + private \SplObjectStorage $keyCache; + /** @var array */ + private array $locks = []; + + /** + * Constructor. + * + * The available options are: + * + * * private_headers Set of response headers that should not be stored + * when a response is cached. (default: Set-Cookie) + * + * @throws \RuntimeException + */ + public function __construct( + protected string $root, + private array $options = [], + ) { + if (!is_dir($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { + throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); + } + $this->keyCache = new \SplObjectStorage(); + $this->options['private_headers'] ??= ['Set-Cookie']; + } + + /** + * Cleanups storage. + */ + public function cleanup(): void + { + // unlock everything + foreach ($this->locks as $lock) { + flock($lock, \LOCK_UN); + fclose($lock); + } + + $this->locks = []; + } + + /** + * Tries to lock the cache for a given Request, without blocking. + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request): bool|string + { + $key = $this->getCacheKey($request); + + if (!isset($this->locks[$key])) { + $path = $this->getPath($key); + if (!is_dir(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + return $path; + } + $h = fopen($path, 'c'); + if (!flock($h, \LOCK_EX | \LOCK_NB)) { + fclose($h); + + return $path; + } + + $this->locks[$key] = $h; + } + + return true; + } + + /** + * Releases the lock for the given Request. + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request): bool + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + flock($this->locks[$key], \LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + + return true; + } + + return false; + } + + public function isLocked(Request $request): bool + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + return true; // shortcut if lock held by this process + } + + if (!is_file($path = $this->getPath($key))) { + return false; + } + + $h = fopen($path, 'r'); + flock($h, \LOCK_EX | \LOCK_NB, $wouldBlock); + flock($h, \LOCK_UN); // release the lock we just acquired + fclose($h); + + return (bool) $wouldBlock; + } + + /** + * Locates a cached Response for the Request provided. + */ + public function lookup(Request $request): ?Response + { + $key = $this->getCacheKey($request); + + if (!$entries = $this->getMetadata($key)) { + return null; + } + + // find a cached entry that matches the request. + $match = null; + foreach ($entries as $entry) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { + $match = $entry; + + break; + } + } + + if (null === $match) { + return null; + } + + $headers = $match[1]; + if (file_exists($path = $this->getPath($headers['x-content-digest'][0]))) { + return $this->restoreResponse($headers, $path); + } + + // TODO the metaStore referenced an entity that doesn't exist in + // the entityStore. We definitely want to return nil but we should + // also purge the entry from the meta-store when this is detected. + return null; + } + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @throws \RuntimeException + */ + public function write(Request $request, Response $response): string + { + $key = $this->getCacheKey($request); + $storedEnv = $this->persistRequest($request); + + if ($response->headers->has('X-Body-File')) { + // Assume the response came from disk, but at least perform some safeguard checks + if (!$response->headers->has('X-Content-Digest')) { + throw new \RuntimeException('A restored response must have the X-Content-Digest header.'); + } + + $digest = $response->headers->get('X-Content-Digest'); + if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) { + throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.'); + } + // Everything seems ok, omit writing content to disk + } else { + $digest = $this->generateContentDigest($response); + $response->headers->set('X-Content-Digest', $digest); + + if (!$this->save($digest, $response->getContent(), false)) { + throw new \RuntimeException('Unable to store the entity.'); + } + + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); + } + } + + // read existing cache entries, remove non-varying, and add this one to the list + $entries = []; + $vary = $response->headers->get('vary'); + foreach ($this->getMetadata($key) as $entry) { + if (!isset($entry[1]['vary'][0])) { + $entry[1]['vary'] = ['']; + } + + if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary ?? '', $entry[0], $storedEnv)) { + $entries[] = $entry; + } + } + + $headers = $this->persistResponse($response); + unset($headers['age']); + + foreach ($this->options['private_headers'] as $h) { + unset($headers[strtolower($h)]); + } + + array_unshift($entries, [$storedEnv, $headers]); + + if (!$this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + + return $key; + } + + /** + * Returns content digest for $response. + */ + protected function generateContentDigest(Response $response): string + { + return 'en'.hash('xxh128', $response->getContent()); + } + + /** + * Invalidates all cache entries that match the request. + * + * @throws \RuntimeException + */ + public function invalidate(Request $request): void + { + $modified = false; + $key = $this->getCacheKey($request); + + $entries = []; + foreach ($this->getMetadata($key) as $entry) { + $response = $this->restoreResponse($entry[1]); + if ($response->isFresh()) { + $response->expire(); + $modified = true; + $entries[] = [$entry[0], $this->persistResponse($response)]; + } else { + $entries[] = $entry; + } + } + + if ($modified && !$this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + } + + /** + * Determines whether two Request HTTP header sets are non-varying based on + * the vary response header value provided. + * + * @param string|null $vary A Response vary header + * @param array $env1 A Request HTTP header array + * @param array $env2 A Request HTTP header array + */ + private function requestsMatch(?string $vary, array $env1, array $env2): bool + { + if (!$vary) { + return true; + } + + foreach (preg_split('/[\s,]+/', $vary) as $header) { + $key = str_replace('_', '-', strtolower($header)); + $v1 = $env1[$key] ?? null; + $v2 = $env2[$key] ?? null; + if ($v1 !== $v2) { + return false; + } + } + + return true; + } + + /** + * Gets all data associated with the given key. + * + * Use this method only if you know what you are doing. + */ + private function getMetadata(string $key): array + { + if (!$entries = $this->load($key)) { + return []; + } + + return unserialize($entries) ?: []; + } + + /** + * Purges data for the given URL. + * + * This method purges both the HTTP and the HTTPS version of the cache entry. + * + * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise + */ + public function purge(string $url): bool + { + $http = preg_replace('#^https:#', 'http:', $url); + $https = preg_replace('#^http:#', 'https:', $url); + + $purgedHttp = $this->doPurge($http); + $purgedHttps = $this->doPurge($https); + + return $purgedHttp || $purgedHttps; + } + + /** + * Purges data for the given URL. + */ + private function doPurge(string $url): bool + { + $key = $this->getCacheKey(Request::create($url)); + if (isset($this->locks[$key])) { + flock($this->locks[$key], \LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + } + + if (is_file($path = $this->getPath($key))) { + unlink($path); + + return true; + } + + return false; + } + + /** + * Loads data for the given key. + */ + private function load(string $key): ?string + { + $path = $this->getPath($key); + + return is_file($path) && false !== ($contents = @file_get_contents($path)) ? $contents : null; + } + + /** + * Save data for the given key. + */ + private function save(string $key, string $data, bool $overwrite = true): bool + { + $path = $this->getPath($key); + + if (!$overwrite && file_exists($path)) { + return true; + } + + if (isset($this->locks[$key])) { + $fp = $this->locks[$key]; + @ftruncate($fp, 0); + @fseek($fp, 0); + $len = @fwrite($fp, $data); + if (\strlen($data) !== $len) { + @ftruncate($fp, 0); + + return false; + } + } else { + if (!is_dir(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + return false; + } + + $tmpFile = tempnam(\dirname($path), basename($path)); + if (false === $fp = @fopen($tmpFile, 'w')) { + @unlink($tmpFile); + + return false; + } + @fwrite($fp, $data); + @fclose($fp); + + if ($data != file_get_contents($tmpFile)) { + @unlink($tmpFile); + + return false; + } + + if (false === @rename($tmpFile, $path)) { + @unlink($tmpFile); + + return false; + } + } + + @chmod($path, 0666 & ~umask()); + + return true; + } + + public function getPath(string $key): string + { + return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); + } + + /** + * Generates a cache key for the given Request. + * + * This method should return a key that must only depend on a + * normalized version of the request URI. + * + * If the same URI can have more than one representation, based on some + * headers, use a Vary header to indicate them, and each representation will + * be stored independently under the same cache key. + */ + protected function generateCacheKey(Request $request): string + { + return 'md'.hash('sha256', $request->getUri()); + } + + /** + * Returns a cache key for the given Request. + */ + private function getCacheKey(Request $request): string + { + if (isset($this->keyCache[$request])) { + return $this->keyCache[$request]; + } + + return $this->keyCache[$request] = $this->generateCacheKey($request); + } + + /** + * Persists the Request HTTP headers. + */ + private function persistRequest(Request $request): array + { + return $request->headers->all(); + } + + /** + * Persists the Response HTTP headers. + */ + private function persistResponse(Response $response): array + { + $headers = $response->headers->all(); + $headers['X-Status'] = [$response->getStatusCode()]; + + return $headers; + } + + /** + * Restores a Response from the HTTP headers and body. + */ + private function restoreResponse(array $headers, ?string $path = null): ?Response + { + $status = $headers['X-Status'][0]; + unset($headers['X-Status']); + $content = null; + + if (null !== $path) { + $headers['X-Body-File'] = [$path]; + unset($headers['x-body-file']); + + if ($headers['X-Body-Eval'] ?? $headers['x-body-eval'] ?? false) { + $content = file_get_contents($path); + \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === 24); + if (48 > \strlen($content) || substr($content, -24) !== substr($content, 0, 24)) { + return null; + } + } + } + + return new Response($content, $status, $headers); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/StoreInterface.php b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php new file mode 100644 index 0000000..1fd3f05 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php @@ -0,0 +1,79 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by HTTP cache stores. + * + * @author Fabien Potencier + */ +interface StoreInterface +{ + /** + * Locates a cached Response for the Request provided. + */ + public function lookup(Request $request): ?Response; + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @return string The key under which the response is stored + */ + public function write(Request $request, Response $response): string; + + /** + * Invalidates all cache entries that match the request. + */ + public function invalidate(Request $request): void; + + /** + * Locks the cache for a given Request. + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request): bool|string; + + /** + * Releases the lock for the given Request. + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request): bool; + + /** + * Returns whether or not a lock exists. + * + * @return bool true if lock exists, false otherwise + */ + public function isLocked(Request $request): bool; + + /** + * Purges data for the given URL. + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + public function purge(string $url): bool; + + /** + * Cleanups storage. + */ + public function cleanup(): void; +} diff --git a/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php b/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php new file mode 100644 index 0000000..253071f --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class SubRequestHandler +{ + public static function handle(HttpKernelInterface $kernel, Request $request, int $type, bool $catch): Response + { + // save global state related to trusted headers and proxies + $trustedProxies = Request::getTrustedProxies(); + $trustedHeaderSet = Request::getTrustedHeaderSet(); + + // remove untrusted values + $remoteAddr = $request->server->get('REMOTE_ADDR'); + if (!$remoteAddr || !IpUtils::checkIp($remoteAddr, $trustedProxies)) { + $trustedHeaders = [ + 'FORWARDED' => $trustedHeaderSet & Request::HEADER_FORWARDED, + 'X_FORWARDED_FOR' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_FOR, + 'X_FORWARDED_HOST' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_HOST, + 'X_FORWARDED_PROTO' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PROTO, + 'X_FORWARDED_PORT' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PORT, + 'X_FORWARDED_PREFIX' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PREFIX, + ]; + foreach (array_filter($trustedHeaders) as $name => $key) { + $request->headers->remove($name); + $request->server->remove('HTTP_'.$name); + } + } + + // compute trusted values, taking any trusted proxies into account + $trustedIps = []; + $trustedValues = []; + foreach (array_reverse($request->getClientIps()) as $ip) { + $trustedIps[] = $ip; + $trustedValues[] = sprintf('for="%s"', $ip); + } + if ($ip !== $remoteAddr) { + $trustedIps[] = $remoteAddr; + $trustedValues[] = sprintf('for="%s"', $remoteAddr); + } + + // set trusted values, reusing as much as possible the global trusted settings + if (Request::HEADER_FORWARDED & $trustedHeaderSet) { + $trustedValues[0] .= sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme()); + $request->headers->set('Forwarded', $v = implode(', ', $trustedValues)); + $request->server->set('HTTP_FORWARDED', $v); + } + if (Request::HEADER_X_FORWARDED_FOR & $trustedHeaderSet) { + $request->headers->set('X-Forwarded-For', $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_X_FORWARDED_FOR', $v); + } elseif (!(Request::HEADER_FORWARDED & $trustedHeaderSet)) { + Request::setTrustedProxies($trustedProxies, $trustedHeaderSet | Request::HEADER_X_FORWARDED_FOR); + $request->headers->set('X-Forwarded-For', $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_X_FORWARDED_FOR', $v); + } + + // fix the client IP address by setting it to 127.0.0.1, + // which is the core responsibility of this method + $request->server->set('REMOTE_ADDR', '127.0.0.1'); + + // ensure 127.0.0.1 is set as trusted proxy + if (!IpUtils::checkIp('127.0.0.1', $trustedProxies)) { + Request::setTrustedProxies(array_merge($trustedProxies, ['127.0.0.1']), Request::getTrustedHeaderSet()); + } + + try { + return $kernel->handle($request, $type, $catch); + } finally { + // restore global state + Request::setTrustedProxies($trustedProxies, $trustedHeaderSet); + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php new file mode 100644 index 0000000..45b358b --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +interface SurrogateInterface +{ + /** + * Returns surrogate name. + */ + public function getName(): string; + + /** + * Returns a new cache strategy instance. + */ + public function createCacheStrategy(): ResponseCacheStrategyInterface; + + /** + * Checks that at least one surrogate has Surrogate capability. + */ + public function hasSurrogateCapability(Request $request): bool; + + /** + * Adds Surrogate-capability to the given Request. + */ + public function addSurrogateCapability(Request $request): void; + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. + * + * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. + */ + public function addSurrogateControl(Response $response): void; + + /** + * Checks that the Response needs to be parsed for Surrogate tags. + */ + public function needsParsing(Response $response): bool; + + /** + * Renders a Surrogate tag. + * + * @param string|null $alt An alternate URI + * @param string $comment A comment to add as an esi:include tag + */ + public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string; + + /** + * Replaces a Response Surrogate tags with the included resource content. + */ + public function process(Request $request, Response $response): Response; + + /** + * Handles a Surrogate from the cache. + * + * @param string $alt An alternative URI + * + * @throws \RuntimeException + * @throws \Exception + */ + public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors): string; +} diff --git a/vendor/symfony/http-kernel/HttpClientKernel.php b/vendor/symfony/http-kernel/HttpClientKernel.php new file mode 100644 index 0000000..7c719e8 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpClientKernel.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\FormDataPart; +use Symfony\Component\Mime\Part\TextPart; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ResponseHeaderBag::class); + +/** + * An implementation of a Symfony HTTP kernel using a "real" HTTP client. + * + * @author Fabien Potencier + */ +final class HttpClientKernel implements HttpKernelInterface +{ + private HttpClientInterface $client; + + public function __construct(?HttpClientInterface $client = null) + { + if (null === $client && !class_exists(HttpClient::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); + } + + $this->client = $client ?? HttpClient::create(); + } + + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response + { + $headers = $this->getHeaders($request); + $body = ''; + if (null !== $part = $this->getBody($request)) { + $headers = array_merge($headers, $part->getPreparedHeaders()->toArray()); + $body = $part->bodyToIterable(); + } + $response = $this->client->request($request->getMethod(), $request->getUri(), [ + 'headers' => $headers, + 'body' => $body, + ] + $request->attributes->get('http_client_options', [])); + + $response = new Response($response->getContent(!$catch), $response->getStatusCode(), $response->getHeaders(!$catch)); + + $response->headers->remove('X-Body-File'); + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Content-Digest'); + + $response->headers = new class($response->headers->all()) extends ResponseHeaderBag { + protected function computeCacheControlValue(): string + { + return $this->getCacheControlHeader(); // preserve the original value + } + }; + + return $response; + } + + private function getBody(Request $request): ?AbstractPart + { + if (\in_array($request->getMethod(), ['GET', 'HEAD'])) { + return null; + } + + if (!class_exists(AbstractPart::class)) { + throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + if ($content = $request->getContent()) { + return new TextPart($content, 'utf-8', 'plain', '8bit'); + } + + $fields = $request->request->all(); + foreach ($request->files->all() as $name => $file) { + $fields[$name] = DataPart::fromPath($file->getPathname(), $file->getClientOriginalName(), $file->getClientMimeType()); + } + + return new FormDataPart($fields); + } + + private function getHeaders(Request $request): array + { + $headers = []; + foreach ($request->headers as $key => $value) { + $headers[$key] = $value; + } + $cookies = []; + foreach ($request->cookies->all() as $name => $value) { + $cookies[] = $name.'='.$value; + } + if ($cookies) { + $headers['cookie'] = implode('; ', $cookies); + } + + return $headers; + } +} diff --git a/vendor/symfony/http-kernel/HttpKernel.php b/vendor/symfony/http-kernel/HttpKernel.php new file mode 100644 index 0000000..c3715f0 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernel.php @@ -0,0 +1,321 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Event\TerminateEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ControllerArgumentsEvent::class); +class_exists(ControllerEvent::class); +class_exists(ExceptionEvent::class); +class_exists(FinishRequestEvent::class); +class_exists(RequestEvent::class); +class_exists(ResponseEvent::class); +class_exists(TerminateEvent::class); +class_exists(ViewEvent::class); +class_exists(KernelEvents::class); + +/** + * HttpKernel notifies events to convert a Request object to a Response one. + * + * @author Fabien Potencier + */ +class HttpKernel implements HttpKernelInterface, TerminableInterface +{ + protected RequestStack $requestStack; + private ArgumentResolverInterface $argumentResolver; + private bool $terminating = false; + + public function __construct( + protected EventDispatcherInterface $dispatcher, + protected ControllerResolverInterface $resolver, + ?RequestStack $requestStack = null, + ?ArgumentResolverInterface $argumentResolver = null, + private bool $handleAllThrowables = false, + ) { + $this->requestStack = $requestStack ?? new RequestStack(); + $this->argumentResolver = $argumentResolver ?? new ArgumentResolver(); + } + + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response + { + $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); + + $this->requestStack->push($request); + $response = null; + try { + return $response = $this->handleRaw($request, $type); + } catch (\Throwable $e) { + if ($e instanceof \Error && !$this->handleAllThrowables) { + throw $e; + } + + if ($e instanceof RequestExceptionInterface) { + $e = new BadRequestHttpException($e->getMessage(), $e); + } + if (false === $catch) { + $this->finishRequest($request, $type); + + throw $e; + } + + return $response = $this->handleThrowable($e, $request, $type); + } finally { + $this->requestStack->pop(); + + if ($response instanceof StreamedResponse && $callback = $response->getCallback()) { + $requestStack = $this->requestStack; + + $response->setCallback(static function () use ($request, $callback, $requestStack) { + $requestStack->push($request); + try { + $callback(); + } finally { + $requestStack->pop(); + } + }); + } + } + } + + public function terminate(Request $request, Response $response): void + { + try { + $this->terminating = true; + $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE); + } finally { + $this->terminating = false; + } + } + + /** + * @internal + */ + public function terminateWithException(\Throwable $exception, ?Request $request = null): void + { + if (!$request ??= $this->requestStack->getMainRequest()) { + throw $exception; + } + + if ($pop = $request !== $this->requestStack->getMainRequest()) { + $this->requestStack->push($request); + } + + try { + $response = $this->handleThrowable($exception, $request, self::MAIN_REQUEST); + } finally { + if ($pop) { + $this->requestStack->pop(); + } + } + + $response->sendHeaders(); + $response->sendContent(); + + $this->terminate($request, $response); + } + + /** + * Handles a request to convert it to a response. + * + * Exceptions are not caught. + * + * @throws \LogicException If one of the listener does not behave as expected + * @throws NotFoundHttpException When controller cannot be found + */ + private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response + { + // request + $event = new RequestEvent($this, $request, $type); + $this->dispatcher->dispatch($event, KernelEvents::REQUEST); + + if ($event->hasResponse()) { + return $this->filterResponse($event->getResponse(), $request, $type); + } + + // load controller + if (false === $controller = $this->resolver->getController($request)) { + throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); + } + + $event = new ControllerEvent($this, $controller, $request, $type); + $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); + $controller = $event->getController(); + + // controller arguments + $arguments = $this->argumentResolver->getArguments($request, $controller, $event->getControllerReflector()); + + $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type); + $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); + $controller = $event->getController(); + $arguments = $event->getArguments(); + + // call controller + $response = $controller(...$arguments); + + // view + if (!$response instanceof Response) { + $event = new ViewEvent($this, $request, $type, $response, $event); + $this->dispatcher->dispatch($event, KernelEvents::VIEW); + + if ($event->hasResponse()) { + $response = $event->getResponse(); + } else { + $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this->varToString($response)); + + // the user may have forgotten to return something + if (null === $response) { + $msg .= ' Did you forget to add a return statement somewhere in your controller?'; + } + + throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17); + } + } + + return $this->filterResponse($response, $request, $type); + } + + /** + * Filters a response object. + * + * @throws \RuntimeException if the passed object is not a Response instance + */ + private function filterResponse(Response $response, Request $request, int $type): Response + { + $event = new ResponseEvent($this, $request, $type, $response); + + $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); + + $this->finishRequest($request, $type); + + return $event->getResponse(); + } + + /** + * Publishes the finish request event, then pop the request from the stack. + * + * Note that the order of the operations is important here, otherwise + * operations such as {@link RequestStack::getParentRequest()} can lead to + * weird results. + */ + private function finishRequest(Request $request, int $type): void + { + $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); + } + + /** + * Handles a throwable by trying to convert it to a Response. + */ + private function handleThrowable(\Throwable $e, Request $request, int $type): Response + { + $event = new ExceptionEvent($this, $request, $type, $e, isKernelTerminating: $this->terminating); + $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); + + // a listener might have replaced the exception + $e = $event->getThrowable(); + + if (!$event->hasResponse()) { + $this->finishRequest($request, $type); + + throw $e; + } + + $response = $event->getResponse(); + + // the developer asked for a specific status code + if (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { + // ensure that we actually have an error response + if ($e instanceof HttpExceptionInterface) { + // keep the HTTP status code and headers + $response->setStatusCode($e->getStatusCode()); + $response->headers->add($e->getHeaders()); + } else { + $response->setStatusCode(500); + } + } + + try { + return $this->filterResponse($response, $request, $type); + } catch (\Throwable $e) { + if ($e instanceof \Error && !$this->handleAllThrowables) { + throw $e; + } + + return $response; + } + } + + /** + * Returns a human-readable string for the specified variable. + */ + private function varToString(mixed $var): string + { + if (\is_object($var)) { + return sprintf('an object of type %s', $var::class); + } + + if (\is_array($var)) { + $a = []; + foreach ($var as $k => $v) { + $a[] = sprintf('%s => ...', $k); + } + + return sprintf('an array ([%s])', mb_substr(implode(', ', $a), 0, 255)); + } + + if (\is_resource($var)) { + return sprintf('a resource (%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'a boolean value (false)'; + } + + if (true === $var) { + return 'a boolean value (true)'; + } + + if (\is_string($var)) { + return sprintf('a string ("%s%s")', mb_substr($var, 0, 255), mb_strlen($var) > 255 ? '...' : ''); + } + + if (is_numeric($var)) { + return sprintf('a number (%s)', (string) $var); + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/HttpKernelBrowser.php b/vendor/symfony/http-kernel/HttpKernelBrowser.php new file mode 100644 index 0000000..7f89e35 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernelBrowser.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\Request as DomRequest; +use Symfony\Component\BrowserKit\Response as DomResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Simulates a browser and makes requests to an HttpKernel instance. + * + * @author Fabien Potencier + * + * @method Request getRequest() + * @method Response getResponse() + */ +class HttpKernelBrowser extends AbstractBrowser +{ + private bool $catchExceptions = true; + + /** + * @param array $server The server parameters (equivalent of $_SERVER) + */ + public function __construct( + protected HttpKernelInterface $kernel, + array $server = [], + ?History $history = null, + ?CookieJar $cookieJar = null, + ) { + // These class properties must be set before calling the parent constructor, as it may depend on it. + $this->followRedirects = false; + + parent::__construct($server, $history, $cookieJar); + } + + /** + * Sets whether to catch exceptions when the kernel is handling a request. + */ + public function catchExceptions(bool $catchExceptions): void + { + $this->catchExceptions = $catchExceptions; + } + + /** + * @param Request $request + */ + protected function doRequest(object $request): Response + { + $response = $this->kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, $this->catchExceptions); + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($request, $response); + } + + return $response; + } + + /** + * @param Request $request + */ + protected function getScript(object $request): string + { + $kernel = var_export(serialize($this->kernel), true); + $request = var_export(serialize($request), true); + + $errorReporting = error_reporting(); + + $requires = ''; + foreach (get_declared_classes() as $class) { + if (str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $file = \dirname($r->getFileName(), 2).'/autoload.php'; + if (file_exists($file)) { + $requires .= 'require_once '.var_export($file, true).";\n"; + } + } + } + + if (!$requires) { + throw new \RuntimeException('Composer autoloader not found.'); + } + + $code = <<getHandleScript(); + } + + protected function getHandleScript(): string + { + return <<<'EOF' +$response = $kernel->handle($request); + +if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { + $kernel->terminate($request, $response); +} + +echo serialize($response); +EOF; + } + + protected function filterRequest(DomRequest $request): Request + { + $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $server = $request->getServer(), $request->getContent()); + if (!isset($server['HTTP_ACCEPT'])) { + $httpRequest->headers->remove('Accept'); + } + + foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { + $httpRequest->files->set($key, $value); + } + + return $httpRequest; + } + + /** + * Filters an array of files. + * + * This method created test instances of UploadedFile so that the move() + * method can be called on those instances. + * + * If the size of a file is greater than the allowed size (from php.ini) then + * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. + * + * @see UploadedFile + */ + protected function filterFiles(array $files): array + { + $filtered = []; + foreach ($files as $key => $value) { + if (\is_array($value)) { + $filtered[$key] = $this->filterFiles($value); + } elseif ($value instanceof UploadedFile) { + if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { + $filtered[$key] = new UploadedFile( + '', + $value->getClientOriginalName(), + $value->getClientMimeType(), + \UPLOAD_ERR_INI_SIZE, + true + ); + } else { + $filtered[$key] = new UploadedFile( + $value->getPathname(), + $value->getClientOriginalName(), + $value->getClientMimeType(), + $value->getError(), + true + ); + } + } + } + + return $filtered; + } + + /** + * @param Response $response + */ + protected function filterResponse(object $response): DomResponse + { + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $response->headers->all()); + } +} diff --git a/vendor/symfony/http-kernel/HttpKernelInterface.php b/vendor/symfony/http-kernel/HttpKernelInterface.php new file mode 100644 index 0000000..e941567 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernelInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpKernelInterface handles a Request to convert it to a Response. + * + * @author Fabien Potencier + */ +interface HttpKernelInterface +{ + public const MAIN_REQUEST = 1; + public const SUB_REQUEST = 2; + + /** + * Handles a Request to convert it to a Response. + * + * When $catch is true, the implementation must catch all exceptions + * and do its best to convert them to a Response instance. + * + * @param int $type The type of the request + * (one of HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST) + * @param bool $catch Whether to catch exceptions or not + * + * @throws \Exception When an Exception occurs during processing + */ + public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true): Response; +} diff --git a/vendor/symfony/http-kernel/Kernel.php b/vendor/symfony/http-kernel/Kernel.php new file mode 100644 index 0000000..3999386 --- /dev/null +++ b/vendor/symfony/http-kernel/Kernel.php @@ -0,0 +1,768 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Loader\DelegatingLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\RemoveBuildParametersPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Dumper\Preloader; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; +use Symfony\Component\DependencyInjection\Loader\GlobFileLoader; +use Symfony\Component\DependencyInjection\Loader\IniFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\ErrorHandler\DebugClassLoader; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +// Help opcache.preload discover always-needed symbols +class_exists(ConfigCache::class); + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * Environment names must always start with a letter and + * they must only contain letters and numbers. + * + * @author Fabien Potencier + */ +abstract class Kernel implements KernelInterface, RebootableInterface, TerminableInterface +{ + /** + * @var array + */ + protected array $bundles = []; + + protected ?ContainerInterface $container = null; + protected bool $booted = false; + protected ?float $startTime = null; + + private string $projectDir; + private ?string $warmupDir = null; + private int $requestStackSize = 0; + private bool $resetServices = false; + + /** + * @var array + */ + private static array $freshCache = []; + + public const VERSION = '7.1.4'; + public const VERSION_ID = 70104; + public const MAJOR_VERSION = 7; + public const MINOR_VERSION = 1; + public const RELEASE_VERSION = 4; + public const EXTRA_VERSION = ''; + + public const END_OF_MAINTENANCE = '01/2025'; + public const END_OF_LIFE = '01/2025'; + + public function __construct( + protected string $environment, + protected bool $debug, + ) { + if (!$environment) { + throw new \InvalidArgumentException(sprintf('Invalid environment provided to "%s": the environment cannot be empty.', get_debug_type($this))); + } + } + + public function __clone() + { + $this->booted = false; + $this->container = null; + $this->requestStackSize = 0; + $this->resetServices = false; + } + + public function boot(): void + { + if (true === $this->booted) { + if (!$this->requestStackSize && $this->resetServices) { + if ($this->container->has('services_resetter')) { + $this->container->get('services_resetter')->reset(); + } + $this->resetServices = false; + if ($this->debug) { + $this->startTime = microtime(true); + } + } + + return; + } + + if (null === $this->container) { + $this->preBoot(); + } + + foreach ($this->getBundles() as $bundle) { + $bundle->setContainer($this->container); + $bundle->boot(); + } + + $this->booted = true; + } + + public function reboot(?string $warmupDir): void + { + $this->shutdown(); + $this->warmupDir = $warmupDir; + $this->boot(); + } + + public function terminate(Request $request, Response $response): void + { + if (false === $this->booted) { + return; + } + + if ($this->getHttpKernel() instanceof TerminableInterface) { + $this->getHttpKernel()->terminate($request, $response); + } + } + + public function shutdown(): void + { + if (false === $this->booted) { + return; + } + + $this->booted = false; + + foreach ($this->getBundles() as $bundle) { + $bundle->shutdown(); + $bundle->setContainer(null); + } + + $this->container = null; + $this->requestStackSize = 0; + $this->resetServices = false; + } + + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response + { + if (!$this->booted) { + $container = $this->container ?? $this->preBoot(); + + if ($container->has('http_cache')) { + return $container->get('http_cache')->handle($request, $type, $catch); + } + } + + $this->boot(); + ++$this->requestStackSize; + $this->resetServices = true; + + try { + return $this->getHttpKernel()->handle($request, $type, $catch); + } finally { + --$this->requestStackSize; + } + } + + /** + * Gets an HTTP kernel from the container. + */ + protected function getHttpKernel(): HttpKernelInterface + { + return $this->container->get('http_kernel'); + } + + public function getBundles(): array + { + return $this->bundles; + } + + public function getBundle(string $name): BundleInterface + { + if (!isset($this->bundles[$name])) { + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, get_debug_type($this))); + } + + return $this->bundles[$name]; + } + + public function locateResource(string $name): string + { + if ('@' !== $name[0]) { + throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); + } + + if (str_contains($name, '..')) { + throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name)); + } + + $bundleName = substr($name, 1); + $path = ''; + if (str_contains($bundleName, '/')) { + [$bundleName, $path] = explode('/', $bundleName, 2); + } + + $bundle = $this->getBundle($bundleName); + if (file_exists($file = $bundle->getPath().'/'.$path)) { + return $file; + } + + throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); + } + + public function getEnvironment(): string + { + return $this->environment; + } + + public function isDebug(): bool + { + return $this->debug; + } + + /** + * Gets the application root dir (path of the project's composer file). + */ + public function getProjectDir(): string + { + if (!isset($this->projectDir)) { + $r = new \ReflectionObject($this); + + if (!is_file($dir = $r->getFileName())) { + throw new \LogicException(sprintf('Cannot auto-detect project dir for kernel of class "%s".', $r->name)); + } + + $dir = $rootDir = \dirname($dir); + while (!is_file($dir.'/composer.json')) { + if ($dir === \dirname($dir)) { + return $this->projectDir = $rootDir; + } + $dir = \dirname($dir); + } + $this->projectDir = $dir; + } + + return $this->projectDir; + } + + public function getContainer(): ContainerInterface + { + if (!$this->container) { + throw new \LogicException('Cannot retrieve the container from a non-booted kernel.'); + } + + return $this->container; + } + + /** + * @internal + * + * @deprecated since Symfony 7.1, to be removed in 8.0 + */ + public function setAnnotatedClassCache(array $annotatedClasses): void + { + trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s()" method is deprecated since Symfony 7.1 and will be removed in 8.0.', __METHOD__); + + file_put_contents(($this->warmupDir ?: $this->getBuildDir()).'/annotations.map', sprintf('debug && null !== $this->startTime ? $this->startTime : -\INF; + } + + public function getCacheDir(): string + { + return $this->getProjectDir().'/var/cache/'.$this->environment; + } + + public function getBuildDir(): string + { + // Returns $this->getCacheDir() for backward compatibility + return $this->getCacheDir(); + } + + public function getLogDir(): string + { + return $this->getProjectDir().'/var/log'; + } + + public function getCharset(): string + { + return 'UTF-8'; + } + + /** + * Gets the patterns defining the classes to parse and cache for annotations. + * + * @return string[] + * + * @deprecated since Symfony 7.1, to be removed in 8.0 + */ + public function getAnnotatedClassesToCompile(): array + { + trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s()" method is deprecated since Symfony 7.1 and will be removed in 8.0.', __METHOD__); + + return []; + } + + /** + * Initializes bundles. + * + * @throws \LogicException if two bundles share a common name + */ + protected function initializeBundles(): void + { + // init bundles + $this->bundles = []; + foreach ($this->registerBundles() as $bundle) { + $name = $bundle->getName(); + if (isset($this->bundles[$name])) { + throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s".', $name)); + } + $this->bundles[$name] = $bundle; + } + } + + /** + * The extension point similar to the Bundle::build() method. + * + * Use this method to register compiler passes and manipulate the container during the building process. + */ + protected function build(ContainerBuilder $container): void + { + } + + /** + * Gets the container class. + * + * @throws \InvalidArgumentException If the generated classname is invalid + */ + protected function getContainerClass(): string + { + $class = static::class; + $class = str_contains($class, "@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class; + $class = str_replace('\\', '_', $class).ucfirst($this->environment).($this->debug ? 'Debug' : '').'Container'; + + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) { + throw new \InvalidArgumentException(sprintf('The environment "%s" contains invalid characters, it can only contain characters allowed in PHP class names.', $this->environment)); + } + + return $class; + } + + /** + * Gets the container's base class. + * + * All names except Container must be fully qualified. + */ + protected function getContainerBaseClass(): string + { + return 'Container'; + } + + /** + * Initializes the service container. + * + * The built version of the service container is used when fresh, otherwise the + * container is built. + */ + protected function initializeContainer(): void + { + $class = $this->getContainerClass(); + $buildDir = $this->warmupDir ?: $this->getBuildDir(); + $cache = new ConfigCache($buildDir.'/'.$class.'.php', $this->debug); + $cachePath = $cache->getPath(); + + // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors + $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); + + try { + if (is_file($cachePath) && \is_object($this->container = include $cachePath) + && (!$this->debug || (self::$freshCache[$cachePath] ?? $cache->isFresh())) + ) { + self::$freshCache[$cachePath] = true; + $this->container->set('kernel', $this); + error_reporting($errorLevel); + + return; + } + } catch (\Throwable $e) { + } + + $oldContainer = \is_object($this->container) ? new \ReflectionClass($this->container) : $this->container = null; + + try { + is_dir($buildDir) ?: mkdir($buildDir, 0777, true); + + if ($lock = fopen($cachePath.'.lock', 'w+')) { + if (!flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock) && !flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) { + fclose($lock); + $lock = null; + } elseif (!is_file($cachePath) || !\is_object($this->container = include $cachePath)) { + $this->container = null; + } elseif (!$oldContainer || $this->container::class !== $oldContainer->name) { + flock($lock, \LOCK_UN); + fclose($lock); + $this->container->set('kernel', $this); + + return; + } + } + } catch (\Throwable $e) { + } finally { + error_reporting($errorLevel); + } + + if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { + $collectedLogs = []; + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + if (isset($collectedLogs[$message])) { + ++$collectedLogs[$message]['count']; + + return null; + } + + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5); + // Clean the trace by removing first frames added by the error handler itself. + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $backtrace = \array_slice($backtrace, 1 + $i); + break; + } + } + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (!isset($backtrace[$i]['file'], $backtrace[$i]['line'], $backtrace[$i]['function'])) { + continue; + } + if (!isset($backtrace[$i]['class']) && 'trigger_deprecation' === $backtrace[$i]['function']) { + $file = $backtrace[$i]['file']; + $line = $backtrace[$i]['line']; + $backtrace = \array_slice($backtrace, 1 + $i); + break; + } + } + + // Remove frames added by DebugClassLoader. + for ($i = \count($backtrace) - 2; 0 < $i; --$i) { + if (DebugClassLoader::class === ($backtrace[$i]['class'] ?? null)) { + $backtrace = [$backtrace[$i + 1]]; + break; + } + } + + $collectedLogs[$message] = [ + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'trace' => [$backtrace[0]], + 'count' => 1, + ]; + + return null; + }); + } + + try { + $container = null; + $container = $this->buildContainer(); + $container->compile(); + } finally { + if ($collectDeprecations) { + restore_error_handler(); + + @file_put_contents($buildDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); + @file_put_contents($buildDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); + } + } + + $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); + + if ($lock) { + flock($lock, \LOCK_UN); + fclose($lock); + } + + $this->container = require $cachePath; + $this->container->set('kernel', $this); + + if ($oldContainer && $this->container::class !== $oldContainer->name) { + // Because concurrent requests might still be using them, + // old container files are not removed immediately, + // but on a next dump of the container. + static $legacyContainers = []; + $oldContainerDir = \dirname($oldContainer->getFileName()); + $legacyContainers[$oldContainerDir.'.legacy'] = true; + foreach (glob(\dirname($oldContainerDir).\DIRECTORY_SEPARATOR.'*.legacy', \GLOB_NOSORT) as $legacyContainer) { + if (!isset($legacyContainers[$legacyContainer]) && @unlink($legacyContainer)) { + (new Filesystem())->remove(substr($legacyContainer, 0, -7)); + } + } + + touch($oldContainerDir.'.legacy'); + } + + $buildDir = $this->container->getParameter('kernel.build_dir'); + $cacheDir = $this->container->getParameter('kernel.cache_dir'); + $preload = $this instanceof WarmableInterface ? (array) $this->warmUp($cacheDir, $buildDir) : []; + + if ($this->container->has('cache_warmer')) { + $cacheWarmer = $this->container->get('cache_warmer'); + + if ($cacheDir !== $buildDir) { + $cacheWarmer->enableOptionalWarmers(); + } + + $preload = array_merge($preload, (array) $cacheWarmer->warmUp($cacheDir, $buildDir)); + } + + if ($preload && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { + Preloader::append($preloadFile, $preload); + } + } + + /** + * Returns the kernel parameters. + * + * @return array + */ + protected function getKernelParameters(): array + { + $bundles = []; + $bundlesMetadata = []; + + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = $bundle::class; + $bundlesMetadata[$name] = [ + 'path' => $bundle->getPath(), + 'namespace' => $bundle->getNamespace(), + ]; + } + + return [ + 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), + 'kernel.environment' => $this->environment, + 'kernel.runtime_environment' => '%env(default:kernel.environment:APP_RUNTIME_ENV)%', + 'kernel.runtime_mode' => '%env(query_string:default:container.runtime_mode:APP_RUNTIME_MODE)%', + 'kernel.runtime_mode.web' => '%env(bool:default::key:web:default:kernel.runtime_mode:)%', + 'kernel.runtime_mode.cli' => '%env(not:default:kernel.runtime_mode.web:)%', + 'kernel.runtime_mode.worker' => '%env(bool:default::key:worker:default:kernel.runtime_mode:)%', + 'kernel.debug' => $this->debug, + 'kernel.build_dir' => realpath($buildDir = $this->warmupDir ?: $this->getBuildDir()) ?: $buildDir, + 'kernel.cache_dir' => realpath($cacheDir = ($this->getCacheDir() === $this->getBuildDir() ? ($this->warmupDir ?: $this->getCacheDir()) : $this->getCacheDir())) ?: $cacheDir, + 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.bundles_metadata' => $bundlesMetadata, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ]; + } + + /** + * Builds the service container. + * + * @throws \RuntimeException + */ + protected function buildContainer(): ContainerBuilder + { + foreach (['cache' => $this->getCacheDir(), 'build' => $this->warmupDir ?: $this->getBuildDir(), 'logs' => $this->getLogDir()] as $name => $dir) { + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the "%s" directory (%s).', $name, $dir)); + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf('Unable to write in the "%s" directory (%s).', $name, $dir)); + } + } + + $container = $this->getContainerBuilder(); + $container->addObjectResource($this); + $this->prepareContainer($container); + $this->registerContainerConfiguration($this->getContainerLoader($container)); + + return $container; + } + + /** + * Prepares the ContainerBuilder before it is compiled. + */ + protected function prepareContainer(ContainerBuilder $container): void + { + $extensions = []; + foreach ($this->bundles as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $container->registerExtension($extension); + } + + if ($this->debug) { + $container->addObjectResource($bundle); + } + } + + foreach ($this->bundles as $bundle) { + $bundle->build($container); + } + + $this->build($container); + + foreach ($container->getExtensions() as $extension) { + $extensions[] = $extension->getAlias(); + } + + // ensure these extensions are implicitly loaded + $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); + } + + /** + * Gets a new ContainerBuilder instance used to build the service container. + */ + protected function getContainerBuilder(): ContainerBuilder + { + $container = new ContainerBuilder(); + $container->getParameterBag()->add($this->getKernelParameters()); + + if ($this instanceof ExtensionInterface) { + $container->registerExtension($this); + } + if ($this instanceof CompilerPassInterface) { + $container->addCompilerPass($this, PassConfig::TYPE_BEFORE_OPTIMIZATION, -10000); + } + + return $container; + } + + /** + * Dumps the service container to PHP code in the cache. + * + * @param string $class The name of the class to generate + * @param string $baseClass The name of the container's base class + */ + protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, string $class, string $baseClass): void + { + // cache the container + $dumper = new PhpDumper($container); + + $buildParameters = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof RemoveBuildParametersPass) { + $buildParameters = array_merge($buildParameters, $pass->getRemovedParameters()); + } + } + + $content = $dumper->dump([ + 'class' => $class, + 'base_class' => $baseClass, + 'file' => $cache->getPath(), + 'as_files' => true, + 'debug' => $this->debug, + 'inline_factories' => $buildParameters['.container.dumper.inline_factories'] ?? false, + 'inline_class_loader' => $buildParameters['.container.dumper.inline_class_loader'] ?? $this->debug, + 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(), + 'preload_classes' => array_map('get_class', $this->bundles), + ]); + + $rootCode = array_pop($content); + $dir = \dirname($cache->getPath()).'/'; + $fs = new Filesystem(); + + foreach ($content as $file => $code) { + $fs->dumpFile($dir.$file, $code); + @chmod($dir.$file, 0666 & ~umask()); + } + $legacyFile = \dirname($dir.key($content)).'.legacy'; + if (is_file($legacyFile)) { + @unlink($legacyFile); + } + + $cache->write($rootCode, $container->getResources()); + } + + /** + * Returns a loader for the container. + */ + protected function getContainerLoader(ContainerInterface $container): DelegatingLoader + { + $env = $this->getEnvironment(); + $locator = new FileLocator($this); + $resolver = new LoaderResolver([ + new XmlFileLoader($container, $locator, $env), + new YamlFileLoader($container, $locator, $env), + new IniFileLoader($container, $locator, $env), + new PhpFileLoader($container, $locator, $env, class_exists(ConfigBuilderGenerator::class) ? new ConfigBuilderGenerator($this->getBuildDir()) : null), + new GlobFileLoader($container, $locator, $env), + new DirectoryLoader($container, $locator, $env), + new ClosureLoader($container, $env), + ]); + + return new DelegatingLoader($resolver); + } + + private function preBoot(): ContainerInterface + { + if ($this->debug) { + $this->startTime = microtime(true); + } + if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) { + if (\function_exists('putenv')) { + putenv('SHELL_VERBOSITY=3'); + } + $_ENV['SHELL_VERBOSITY'] = 3; + $_SERVER['SHELL_VERBOSITY'] = 3; + } + + $this->initializeBundles(); + $this->initializeContainer(); + + $container = $this->container; + + if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts = $container->getParameter('kernel.trusted_hosts')) { + Request::setTrustedHosts($trustedHosts); + } + + if ($container->hasParameter('kernel.trusted_proxies') && $container->hasParameter('kernel.trusted_headers') && $trustedProxies = $container->getParameter('kernel.trusted_proxies')) { + Request::setTrustedProxies(\is_array($trustedProxies) ? $trustedProxies : array_map('trim', explode(',', $trustedProxies)), $container->getParameter('kernel.trusted_headers')); + } + + return $container; + } + + public function __sleep(): array + { + return ['environment', 'debug']; + } + + public function __wakeup(): void + { + if (\is_object($this->environment) || \is_object($this->debug)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + $this->__construct($this->environment, $this->debug); + } +} diff --git a/vendor/symfony/http-kernel/KernelEvents.php b/vendor/symfony/http-kernel/KernelEvents.php new file mode 100644 index 0000000..3d47f60 --- /dev/null +++ b/vendor/symfony/http-kernel/KernelEvents.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Event\TerminateEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; + +/** + * Contains all events thrown in the HttpKernel component. + * + * @author Bernhard Schussek + */ +final class KernelEvents +{ + /** + * The REQUEST event occurs at the very beginning of request + * dispatching. + * + * This event allows you to create a response for a request before any + * other code in the framework is executed. + * + * @Event("Symfony\Component\HttpKernel\Event\RequestEvent") + */ + public const REQUEST = 'kernel.request'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears. + * + * This event allows you to create a response for a thrown exception or + * to modify the thrown exception. + * + * @Event("Symfony\Component\HttpKernel\Event\ExceptionEvent") + */ + public const EXCEPTION = 'kernel.exception'; + + /** + * The CONTROLLER event occurs once a controller was found for + * handling a request. + * + * This event allows you to change the controller that will handle the + * request. + * + * @Event("Symfony\Component\HttpKernel\Event\ControllerEvent") + */ + public const CONTROLLER = 'kernel.controller'; + + /** + * The CONTROLLER_ARGUMENTS event occurs once controller arguments have been resolved. + * + * This event allows you to change the arguments that will be passed to + * the controller. + * + * @Event("Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent") + */ + public const CONTROLLER_ARGUMENTS = 'kernel.controller_arguments'; + + /** + * The VIEW event occurs when the return value of a controller + * is not a Response instance. + * + * This event allows you to create a response for the return value of the + * controller. + * + * @Event("Symfony\Component\HttpKernel\Event\ViewEvent") + */ + public const VIEW = 'kernel.view'; + + /** + * The RESPONSE event occurs once a response was created for + * replying to a request. + * + * This event allows you to modify or replace the response that will be + * replied. + * + * @Event("Symfony\Component\HttpKernel\Event\ResponseEvent") + */ + public const RESPONSE = 'kernel.response'; + + /** + * The FINISH_REQUEST event occurs when a response was generated for a request. + * + * This event allows you to reset the global and environmental state of + * the application, when it was changed during the request. + * + * @Event("Symfony\Component\HttpKernel\Event\FinishRequestEvent") + */ + public const FINISH_REQUEST = 'kernel.finish_request'; + + /** + * The TERMINATE event occurs once a response was sent. + * + * This event allows you to run expensive post-response jobs. + * + * @Event("Symfony\Component\HttpKernel\Event\TerminateEvent") + */ + public const TERMINATE = 'kernel.terminate'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + ControllerArgumentsEvent::class => self::CONTROLLER_ARGUMENTS, + ControllerEvent::class => self::CONTROLLER, + ResponseEvent::class => self::RESPONSE, + FinishRequestEvent::class => self::FINISH_REQUEST, + RequestEvent::class => self::REQUEST, + ViewEvent::class => self::VIEW, + ExceptionEvent::class => self::EXCEPTION, + TerminateEvent::class => self::TERMINATE, + ]; +} diff --git a/vendor/symfony/http-kernel/KernelInterface.php b/vendor/symfony/http-kernel/KernelInterface.php new file mode 100644 index 0000000..14a053a --- /dev/null +++ b/vendor/symfony/http-kernel/KernelInterface.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of application kernel and bundles. + * + * @author Fabien Potencier + */ +interface KernelInterface extends HttpKernelInterface +{ + /** + * Returns an array of bundles to register. + * + * @return iterable + */ + public function registerBundles(): iterable; + + /** + * Loads the container configuration. + * + * @return void + */ + public function registerContainerConfiguration(LoaderInterface $loader); + + /** + * Boots the current kernel. + * + * @return void + */ + public function boot(); + + /** + * Shutdowns the kernel. + * + * This method is mainly useful when doing functional testing. + * + * @return void + */ + public function shutdown(); + + /** + * Gets the registered bundle instances. + * + * @return array + */ + public function getBundles(): array; + + /** + * Returns a bundle. + * + * @throws \InvalidArgumentException when the bundle is not enabled + */ + public function getBundle(string $name): BundleInterface; + + /** + * Returns the file path for a given bundle resource. + * + * A Resource can be a file or a directory. + * + * The resource name must follow the following pattern: + * + * "@BundleName/path/to/a/file.something" + * + * where BundleName is the name of the bundle + * and the remaining part is the relative path in the bundle. + * + * @throws \InvalidArgumentException if the file cannot be found or the name is not valid + * @throws \RuntimeException if the name contains invalid/unsafe characters + */ + public function locateResource(string $name): string; + + /** + * Gets the environment. + */ + public function getEnvironment(): string; + + /** + * Checks if debug mode is enabled. + */ + public function isDebug(): bool; + + /** + * Gets the project dir (path of the project's composer file). + */ + public function getProjectDir(): string; + + /** + * Gets the current container. + */ + public function getContainer(): ContainerInterface; + + /** + * Gets the request start time (not available if debug is disabled). + */ + public function getStartTime(): float; + + /** + * Gets the cache directory. + * + * Since Symfony 5.2, the cache directory should be used for caches that are written at runtime. + * For caches and artifacts that can be warmed at compile-time and deployed as read-only, + * use the new "build directory" returned by the {@see getBuildDir()} method. + */ + public function getCacheDir(): string; + + /** + * Returns the build directory. + * + * This directory should be used to store build artifacts, and can be read-only at runtime. + * Caches written at runtime should be stored in the "cache directory" ({@see KernelInterface::getCacheDir()}). + */ + public function getBuildDir(): string; + + /** + * Gets the log directory. + */ + public function getLogDir(): string; + + /** + * Gets the charset of the application. + */ + public function getCharset(): string; +} diff --git a/vendor/symfony/http-kernel/LICENSE b/vendor/symfony/http-kernel/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/http-kernel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/http-kernel/Log/DebugLoggerConfigurator.php b/vendor/symfony/http-kernel/Log/DebugLoggerConfigurator.php new file mode 100644 index 0000000..e036f39 --- /dev/null +++ b/vendor/symfony/http-kernel/Log/DebugLoggerConfigurator.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +use Monolog\Logger; + +/** + * @author Nicolas Grekas + */ +class DebugLoggerConfigurator +{ + private ?object $processor = null; + + public function __construct(callable $processor, ?bool $enable = null) + { + if ($enable ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + $this->processor = \is_object($processor) ? $processor : $processor(...); + } + } + + public function pushDebugLogger(Logger $logger): void + { + if ($this->processor) { + $logger->pushProcessor($this->processor); + } + } + + public static function getDebugLogger(mixed $logger): ?DebugLoggerInterface + { + if ($logger instanceof DebugLoggerInterface) { + return $logger; + } + + if (!$logger instanceof Logger) { + return null; + } + + foreach ($logger->getProcessors() as $processor) { + if ($processor instanceof DebugLoggerInterface) { + return $processor; + } + } + + return null; + } +} diff --git a/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php new file mode 100644 index 0000000..50bb08d --- /dev/null +++ b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +use Symfony\Component\HttpFoundation\Request; + +/** + * DebugLoggerInterface. + * + * @author Fabien Potencier + */ +interface DebugLoggerInterface +{ + /** + * Returns an array of logs. + * + * @return array, + * message: string, + * priority: int, + * priorityName: string, + * timestamp: int, + * timestamp_rfc3339: string, + * }> + */ + public function getLogs(?Request $request = null): array; + + /** + * Returns the number of errors. + */ + public function countErrors(?Request $request = null): int; + + /** + * Removes all log records. + */ + public function clear(): void; +} diff --git a/vendor/symfony/http-kernel/Log/Logger.php b/vendor/symfony/http-kernel/Log/Logger.php new file mode 100644 index 0000000..6b7a90d --- /dev/null +++ b/vendor/symfony/http-kernel/Log/Logger.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Minimalist PSR-3 logger designed to write in stderr or any other stream. + * + * @author Kévin Dunglas + */ +class Logger extends AbstractLogger implements DebugLoggerInterface +{ + private const LEVELS = [ + LogLevel::DEBUG => 0, + LogLevel::INFO => 1, + LogLevel::NOTICE => 2, + LogLevel::WARNING => 3, + LogLevel::ERROR => 4, + LogLevel::CRITICAL => 5, + LogLevel::ALERT => 6, + LogLevel::EMERGENCY => 7, + ]; + private const PRIORITIES = [ + LogLevel::DEBUG => 100, + LogLevel::INFO => 200, + LogLevel::NOTICE => 250, + LogLevel::WARNING => 300, + LogLevel::ERROR => 400, + LogLevel::CRITICAL => 500, + LogLevel::ALERT => 550, + LogLevel::EMERGENCY => 600, + ]; + + private int $minLevelIndex; + private \Closure $formatter; + private bool $debug = false; + private array $logs = []; + private array $errorCount = []; + + /** @var resource|null */ + private $handle; + + /** + * @param string|resource|null $output + */ + public function __construct(?string $minLevel = null, $output = null, ?callable $formatter = null, private readonly ?RequestStack $requestStack = null, bool $debug = false) + { + if (null === $minLevel) { + $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING; + + if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { + $minLevel = match ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) { + -1 => LogLevel::ERROR, + 1 => LogLevel::NOTICE, + 2 => LogLevel::INFO, + 3 => LogLevel::DEBUG, + default => $minLevel, + }; + } + } + + if (!isset(self::LEVELS[$minLevel])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel)); + } + + $this->minLevelIndex = self::LEVELS[$minLevel]; + $this->formatter = null !== $formatter ? $formatter(...) : $this->format(...); + if ($output && false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) { + throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output)); + } + $this->debug = $debug; + } + + public function enableDebug(): void + { + $this->debug = true; + } + + public function log($level, $message, array $context = []): void + { + if (!isset(self::LEVELS[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + if (self::LEVELS[$level] < $this->minLevelIndex) { + return; + } + + $formatter = $this->formatter; + if ($this->handle) { + @fwrite($this->handle, $formatter($level, $message, $context).\PHP_EOL); + } else { + error_log($formatter($level, $message, $context, false)); + } + + if ($this->debug && $this->requestStack) { + $this->record($level, $message, $context); + } + } + + public function getLogs(?Request $request = null): array + { + if ($request) { + return $this->logs[spl_object_id($request)] ?? []; + } + + return array_merge(...array_values($this->logs)); + } + + public function countErrors(?Request $request = null): int + { + if ($request) { + return $this->errorCount[spl_object_id($request)] ?? 0; + } + + return array_sum($this->errorCount); + } + + public function clear(): void + { + $this->logs = []; + $this->errorCount = []; + } + + private function format(string $level, string $message, array $context, bool $prefixDate = true): string + { + if (str_contains($message, '{')) { + $replacements = []; + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || $val instanceof \Stringable) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object '.$val::class.']'; + } else { + $replacements["{{$key}}"] = '['.\gettype($val).']'; + } + } + + $message = strtr($message, $replacements); + } + + $log = sprintf('[%s] %s', $level, $message); + if ($prefixDate) { + $log = date(\DateTimeInterface::RFC3339).' '.$log; + } + + return $log; + } + + private function record($level, $message, array $context): void + { + $request = $this->requestStack->getCurrentRequest(); + $key = $request ? spl_object_id($request) : ''; + + $this->logs[$key][] = [ + 'channel' => null, + 'context' => $context, + 'message' => $message, + 'priority' => self::PRIORITIES[$level], + 'priorityName' => $level, + 'timestamp' => time(), + 'timestamp_rfc3339' => date(\DATE_RFC3339_EXTENDED), + ]; + + $this->errorCount[$key] ??= 0; + switch ($level) { + case LogLevel::ERROR: + case LogLevel::CRITICAL: + case LogLevel::ALERT: + case LogLevel::EMERGENCY: + ++$this->errorCount[$key]; + } + } +} diff --git a/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php new file mode 100644 index 0000000..2d31bc2 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -0,0 +1,348 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * Storage for profiler using files. + * + * @author Alexandre Salomé + */ +class FileProfilerStorage implements ProfilerStorageInterface +{ + /** + * Folder where profiler data are stored. + */ + private string $folder; + + /** + * Constructs the file storage using a "dsn-like" path. + * + * Example : "file:/path/to/the/storage/folder" + * + * @throws \RuntimeException + */ + public function __construct(string $dsn) + { + if (!str_starts_with($dsn, 'file:')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); + } + $this->folder = substr($dsn, 5); + + if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder)); + } + } + + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null, ?\Closure $filter = null): array + { + $file = $this->getIndexFilename(); + + if (!file_exists($file)) { + return []; + } + + $file = fopen($file, 'r'); + fseek($file, 0, \SEEK_END); + + $result = []; + while (\count($result) < $limit && $line = $this->readLineFromFile($file)) { + $values = str_getcsv($line); + + if (7 > \count($values)) { + // skip invalid lines + continue; + } + + [$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode, $csvVirtualType] = $values + [7 => null]; + $csvTime = (int) $csvTime; + + $urlFilter = false; + if ($url) { + $urlFilter = str_starts_with($url, '!') ? str_contains($csvUrl, substr($url, 1)) : !str_contains($csvUrl, $url); + } + + if ($ip && !str_contains($csvIp, $ip) || $urlFilter || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) { + continue; + } + + if ($start && $csvTime < $start) { + continue; + } + + if ($end && $csvTime > $end) { + continue; + } + + $profile = [ + 'token' => $csvToken, + 'ip' => $csvIp, + 'method' => $csvMethod, + 'url' => $csvUrl, + 'time' => $csvTime, + 'parent' => $csvParent, + 'status_code' => $csvStatusCode, + 'virtual_type' => $csvVirtualType ?: 'request', + ]; + + if ($filter && !$filter($profile)) { + continue; + } + + $result[$csvToken] = $profile; + } + + fclose($file); + + return array_values($result); + } + + public function purge(): void + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } else { + rmdir($file); + } + } + } + + public function read(string $token): ?Profile + { + return $this->doRead($token); + } + + /** + * @throws \RuntimeException + */ + public function write(Profile $profile): bool + { + $file = $this->getFilename($profile->getToken()); + + $profileIndexed = is_file($file); + if (!$profileIndexed) { + // Create directory + $dir = \dirname($file); + if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir)); + } + } + + $profileToken = $profile->getToken(); + // when there are errors in sub-requests, the parent and/or children tokens + // may equal the profile token, resulting in infinite loops + $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; + $childrenToken = array_filter(array_map(fn (Profile $p) => $profileToken !== $p->getToken() ? $p->getToken() : null, $profile->getChildren())); + + // Store profile + $data = [ + 'token' => $profileToken, + 'parent' => $parentToken, + 'children' => $childrenToken, + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + 'status_code' => $profile->getStatusCode(), + 'virtual_type' => $profile->getVirtualType() ?? 'request', + ]; + + $data = serialize($data); + + if (\function_exists('gzencode')) { + $data = gzencode($data, 3); + } + + if (false === file_put_contents($file, $data, \LOCK_EX)) { + return false; + } + + if (!$profileIndexed) { + // Add to index + if (false === $file = fopen($this->getIndexFilename(), 'a')) { + return false; + } + + fputcsv($file, [ + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime() ?: time(), + $profile->getParentToken(), + $profile->getStatusCode(), + $profile->getVirtualType() ?? 'request', + ]); + fclose($file); + + if (1 === mt_rand(1, 10)) { + $this->removeExpiredProfiles(); + } + } + + return true; + } + + /** + * Gets filename to store data, associated to the token. + */ + protected function getFilename(string $token): string + { + // Uses 4 last characters, because first are mostly the same. + $folderA = substr($token, -2, 2); + $folderB = substr($token, -4, 2); + + return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token; + } + + /** + * Gets the index filename. + */ + protected function getIndexFilename(): string + { + return $this->folder.'/index.csv'; + } + + /** + * Reads a line in the file, backward. + * + * This function automatically skips the empty lines and do not include the line return in result value. + * + * @param resource $file The file resource, with the pointer placed at the end of the line to read + */ + protected function readLineFromFile($file): mixed + { + $line = ''; + $position = ftell($file); + + if (0 === $position) { + return null; + } + + while (true) { + $chunkSize = min($position, 1024); + $position -= $chunkSize; + fseek($file, $position); + + if (0 === $chunkSize) { + // bof reached + break; + } + + $buffer = fread($file, $chunkSize); + + if (false === ($upTo = strrpos($buffer, "\n"))) { + $line = $buffer.$line; + continue; + } + + $position += $upTo; + $line = substr($buffer, $upTo + 1).$line; + fseek($file, max(0, $position), \SEEK_SET); + + if ('' !== $line) { + break; + } + } + + return '' === $line ? null : $line; + } + + protected function createProfileFromData(string $token, array $data, ?Profile $parent = null): Profile + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setStatusCode($data['status_code']); + $profile->setVirtualType($data['virtual_type'] ?: 'request'); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (null !== $childProfile = $this->doRead($token, $profile)) { + $profile->addChild($childProfile); + } + } + + return $profile; + } + + private function doRead($token, ?Profile $profile = null): ?Profile + { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return null; + } + + $h = fopen($file, 'r'); + flock($h, \LOCK_SH); + $data = stream_get_contents($h); + flock($h, \LOCK_UN); + fclose($h); + + if (\function_exists('gzdecode')) { + $data = @gzdecode($data) ?: $data; + } + + if (!$data = unserialize($data)) { + return null; + } + + return $this->createProfileFromData($token, $data, $profile); + } + + private function removeExpiredProfiles(): void + { + $minimalProfileTimestamp = time() - 2 * 86400; + $file = $this->getIndexFilename(); + $handle = fopen($file, 'r'); + + if ($offset = is_file($file.'.offset') ? (int) file_get_contents($file.'.offset') : 0) { + fseek($handle, $offset); + } + + while ($line = fgets($handle)) { + $values = str_getcsv($line); + + if (7 > \count($values)) { + // skip invalid lines + $offset += \strlen($line); + continue; + } + + [$csvToken, , , , $csvTime] = $values; + + if ($csvTime >= $minimalProfileTimestamp) { + break; + } + + @unlink($this->getFilename($csvToken)); + $offset += \strlen($line); + } + fclose($handle); + + file_put_contents($file.'.offset', $offset); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profile.php b/vendor/symfony/http-kernel/Profiler/Profile.php new file mode 100644 index 0000000..a18e2e5 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profile.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; + +/** + * Profile. + * + * @author Fabien Potencier + */ +class Profile +{ + + /** + * @var DataCollectorInterface[] + */ + private array $collectors = []; + + private ?string $ip = null; + private ?string $method = null; + private ?string $url = null; + private ?int $time = null; + private ?int $statusCode = null; + private ?self $parent = null; + private ?string $virtualType = null; + + /** + * @var Profile[] + */ + private array $children = []; + + public function __construct( + private string $token, + ) { + } + + public function setToken(string $token): void + { + $this->token = $token; + } + + /** + * Gets the token. + */ + public function getToken(): string + { + return $this->token; + } + + /** + * Sets the parent token. + */ + public function setParent(self $parent): void + { + $this->parent = $parent; + } + + /** + * Returns the parent profile. + */ + public function getParent(): ?self + { + return $this->parent; + } + + /** + * Returns the parent token. + */ + public function getParentToken(): ?string + { + return $this->parent?->getToken(); + } + + /** + * Returns the IP. + */ + public function getIp(): ?string + { + return $this->ip; + } + + public function setIp(?string $ip): void + { + $this->ip = $ip; + } + + /** + * Returns the request method. + */ + public function getMethod(): ?string + { + return $this->method; + } + + public function setMethod(string $method): void + { + $this->method = $method; + } + + /** + * Returns the URL. + */ + public function getUrl(): ?string + { + return $this->url; + } + + public function setUrl(?string $url): void + { + $this->url = $url; + } + + public function getTime(): int + { + return $this->time ?? 0; + } + + public function setTime(int $time): void + { + $this->time = $time; + } + + public function setStatusCode(int $statusCode): void + { + $this->statusCode = $statusCode; + } + + public function getStatusCode(): ?int + { + return $this->statusCode; + } + + /** + * @internal + */ + public function setVirtualType(?string $virtualType): void + { + $this->virtualType = $virtualType; + } + + /** + * @internal + */ + public function getVirtualType(): ?string + { + return $this->virtualType; + } + + /** + * Finds children profilers. + * + * @return self[] + */ + public function getChildren(): array + { + return $this->children; + } + + /** + * Sets children profiler. + * + * @param Profile[] $children + */ + public function setChildren(array $children): void + { + $this->children = []; + foreach ($children as $child) { + $this->addChild($child); + } + } + + /** + * Adds the child token. + */ + public function addChild(self $child): void + { + $this->children[] = $child; + $child->setParent($this); + } + + public function getChildByToken(string $token): ?self + { + foreach ($this->children as $child) { + if ($token === $child->getToken()) { + return $child; + } + } + + return null; + } + + /** + * Gets a Collector by name. + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function getCollector(string $name): DataCollectorInterface + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + /** + * Gets the Collectors associated with this profile. + * + * @return DataCollectorInterface[] + */ + public function getCollectors(): array + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profile. + * + * @param DataCollectorInterface[] $collectors + */ + public function setCollectors(array $collectors): void + { + $this->collectors = []; + foreach ($collectors as $collector) { + $this->addCollector($collector); + } + } + + /** + * Adds a Collector. + */ + public function addCollector(DataCollectorInterface $collector): void + { + $this->collectors[$collector->getName()] = $collector; + } + + public function hasCollector(string $name): bool + { + return isset($this->collectors[$name]); + } + + public function __sleep(): array + { + return ['token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode', 'virtualType']; + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profiler.php b/vendor/symfony/http-kernel/Profiler/Profiler.php new file mode 100644 index 0000000..004173b --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profiler.php @@ -0,0 +1,244 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Profiler. + * + * @author Fabien Potencier + */ +class Profiler implements ResetInterface +{ + /** + * @var DataCollectorInterface[] + */ + private array $collectors = []; + + private bool $initiallyEnabled = true; + + public function __construct( + private ProfilerStorageInterface $storage, + private ?LoggerInterface $logger = null, + private bool $enabled = true, + ) { + $this->initiallyEnabled = $enabled; + } + + /** + * Disables the profiler. + */ + public function disable(): void + { + $this->enabled = false; + } + + /** + * Enables the profiler. + */ + public function enable(): void + { + $this->enabled = true; + } + + public function isEnabled(): bool + { + return $this->enabled; + } + + /** + * Loads the Profile for the given Response. + */ + public function loadProfileFromResponse(Response $response): ?Profile + { + if (!$token = $response->headers->get('X-Debug-Token')) { + return null; + } + + return $this->loadProfile($token); + } + + /** + * Loads the Profile for the given token. + */ + public function loadProfile(string $token): ?Profile + { + return $this->storage->read($token); + } + + /** + * Saves a Profile. + */ + public function saveProfile(Profile $profile): bool + { + // late collect + foreach ($profile->getCollectors() as $collector) { + if ($collector instanceof LateDataCollectorInterface) { + $collector->lateCollect(); + } + } + + if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { + $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => $this->storage::class]); + } + + return $ret; + } + + /** + * Purges all data from the storage. + */ + public function purge(): void + { + $this->storage->purge(); + } + + /** + * Finds profiler tokens for the given criteria. + * + * @param int|null $limit The maximum number of tokens to return + * @param string|null $start The start date to search from + * @param string|null $end The end date to search to + * @param \Closure|null $filter A filter to apply on the list of tokens + * + * @see https://php.net/datetime.formats for the supported date/time formats + */ + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?string $start, ?string $end, ?string $statusCode = null, ?\Closure $filter = null): array + { + return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode, $filter); + } + + /** + * Collects data for the given Response. + */ + public function collect(Request $request, Response $response, ?\Throwable $exception = null): ?Profile + { + if (false === $this->enabled) { + return null; + } + + $profile = new Profile(substr(hash('xxh128', uniqid(mt_rand(), true)), 0, 6)); + $profile->setTime(time()); + $profile->setUrl($request->getUri()); + $profile->setMethod($request->getMethod()); + $profile->setStatusCode($response->getStatusCode()); + try { + $profile->setIp($request->getClientIp()); + } catch (ConflictingHeadersException) { + $profile->setIp('Unknown'); + } + + if ($request->attributes->has('_virtual_type')) { + $profile->setVirtualType($request->attributes->get('_virtual_type')); + } + + if ($prevToken = $response->headers->get('X-Debug-Token')) { + $response->headers->set('X-Previous-Debug-Token', $prevToken); + } + + $response->headers->set('X-Debug-Token', $profile->getToken()); + + foreach ($this->collectors as $collector) { + $collector->collect($request, $response, $exception); + + // we need to clone for sub-requests + $profile->addCollector(clone $collector); + } + + return $profile; + } + + public function reset(): void + { + foreach ($this->collectors as $collector) { + $collector->reset(); + } + $this->enabled = $this->initiallyEnabled; + } + + /** + * Gets the Collectors associated with this profiler. + */ + public function all(): array + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profiler. + * + * @param DataCollectorInterface[] $collectors An array of collectors + */ + public function set(array $collectors = []): void + { + $this->collectors = []; + foreach ($collectors as $collector) { + $this->add($collector); + } + } + + /** + * Adds a Collector. + */ + public function add(DataCollectorInterface $collector): void + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + */ + public function has(string $name): bool + { + return isset($this->collectors[$name]); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function get(string $name): DataCollectorInterface + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + private function getTimestamp(?string $value): ?int + { + if (null === $value || '' === $value) { + return null; + } + + try { + $value = new \DateTimeImmutable(is_numeric($value) ? '@'.$value : $value); + } catch (\Exception) { + return null; + } + + return $value->getTimestamp(); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php new file mode 100644 index 0000000..eed353d --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * ProfilerStorageInterface. + * + * This interface exists for historical reasons. The only supported + * implementation is FileProfilerStorage. + * + * As the profiler must only be used on non-production servers, the file storage + * is more than enough and no other implementations will ever be supported. + * + * @internal + * + * @author Fabien Potencier + */ +interface ProfilerStorageInterface +{ + /** + * Finds profiler tokens for the given criteria. + * + * @param int|null $limit The maximum number of tokens to return + * @param int|null $start The start date to search from + * @param int|null $end The end date to search to + * @param string|null $statusCode The response status code + * @param \Closure|null $filter A filter to apply on the list of tokens + */ + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null, ?\Closure $filter = null): array; + + /** + * Reads data associated with the given token. + * + * The method returns false if the token does not exist in the storage. + */ + public function read(string $token): ?Profile; + + /** + * Saves a Profile. + */ + public function write(Profile $profile): bool; + + /** + * Purges all data from the database. + */ + public function purge(): void; +} diff --git a/vendor/symfony/http-kernel/README.md b/vendor/symfony/http-kernel/README.md new file mode 100644 index 0000000..18d15f5 --- /dev/null +++ b/vendor/symfony/http-kernel/README.md @@ -0,0 +1,15 @@ +HttpKernel Component +==================== + +The HttpKernel component provides a structured process for converting a Request +into a Response by making use of the EventDispatcher component. It's flexible +enough to create full-stack frameworks, micro-frameworks or advanced CMS systems like Drupal. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_kernel.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/http-kernel/RebootableInterface.php b/vendor/symfony/http-kernel/RebootableInterface.php new file mode 100644 index 0000000..3c4029f --- /dev/null +++ b/vendor/symfony/http-kernel/RebootableInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Allows the Kernel to be rebooted using a temporary cache directory. + * + * @author Nicolas Grekas + */ +interface RebootableInterface +{ + /** + * Reboots a kernel. + * + * The getBuildDir() method of a rebootable kernel should not be called + * while building the container. Use the %kernel.build_dir% parameter instead. + * + * @param string|null $warmupDir pass null to reboot in the regular build directory + */ + public function reboot(?string $warmupDir): void; +} diff --git a/vendor/symfony/http-kernel/Resources/welcome.html.php b/vendor/symfony/http-kernel/Resources/welcome.html.php new file mode 100644 index 0000000..4fd262e --- /dev/null +++ b/vendor/symfony/http-kernel/Resources/welcome.html.php @@ -0,0 +1,330 @@ + +SVG; + +// SVG icons from the Tabler Icons project +// MIT License - Copyright (c) 2020-2023 Paweł Kuna +// https://github.com/tabler/tabler-icons/blob/master/LICENSE + +$renderBoxIconSvg = << + + + + + + +SVG; + +$renderFolderIconSvg = << + + + +SVG; + +$renderInfoIconSvg = << + + + + + +SVG; + +$renderNextStepIconSvg = << + + + + + +SVG; + +$renderLearnIconSvg = << +SVG; + +$renderCommunityIconSvg = << +SVG; + +$renderUpdatesIconSvg = << +SVG; + +$renderWavesSvg = << +SVG; +?> + + + + + + Welcome to Symfony! + + + + +
    +
    + + +
    +
      +
    • + + You are using Symfony version +
    • + +
    • + + Your application is ready at: +
    • + +
    • + + You are seeing this page because the homepage URL is not configured and debug mode is enabled. +
    • +
    + +

    + Next Step + + + Create your first page + to replace this placeholder page. + +

    +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    + + diff --git a/vendor/symfony/http-kernel/TerminableInterface.php b/vendor/symfony/http-kernel/TerminableInterface.php new file mode 100644 index 0000000..5b3e186 --- /dev/null +++ b/vendor/symfony/http-kernel/TerminableInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Terminable extends the Kernel request/response cycle with dispatching a post + * response event after sending the response and before shutting down the kernel. + * + * @author Jordi Boggiano + * @author Pierre Minnieur + */ +interface TerminableInterface +{ + /** + * Terminates a request/response cycle. + * + * Should be called after sending the response and before shutting down the kernel. + */ + public function terminate(Request $request, Response $response): void; +} diff --git a/vendor/symfony/http-kernel/composer.json b/vendor/symfony/http-kernel/composer.json new file mode 100644 index 0000000..8e54c82 --- /dev/null +++ b/vendor/symfony/http-kernel/composer.json @@ -0,0 +1,81 @@ +{ + "name": "symfony/http-kernel", + "type": "library", + "description": "Provides a structured process for converting a Request into a Response", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "psr/cache": "^1.0|^2.0|^3.0", + "twig/twig": "^3.0.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.0.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/mailer/CHANGELOG.md b/vendor/symfony/mailer/CHANGELOG.md new file mode 100644 index 0000000..54e2a6c --- /dev/null +++ b/vendor/symfony/mailer/CHANGELOG.md @@ -0,0 +1,110 @@ +CHANGELOG +========= + +7.1 +--- + + * Dispatch Postmark's "406 - Inactive recipient" API error code as a `PostmarkDeliveryEvent` instead of throwing an exception + * Add DSN param `auto_tls` to disable automatic STARTTLS + * Add support for allowing some users even if `recipients` is defined in `EnvelopeListener` + +7.0 +--- + + * Remove the OhMySmtp bridge in favor of the MailPace bridge + +6.4 +--- + + * Add DSN parameter `peer_fingerprint` to verify TLS certificate fingerprint + * Change the default port for the `mailjet+smtp` transport from 465 to 587 + +6.3 +--- + + * Add `MessageEvent::reject()` to allow rejecting an email before sending it + * Change the default port for the `mailgun+smtp` transport from 465 to 587 + * Add `$authenticators` parameter in `EsmtpTransport` constructor and `EsmtpTransport::setAuthenticators()` + to allow overriding of default eSMTP authenticators + +6.2.7 +----- + + * [BC BREAK] The following data providers for `TransportFactoryTestCase` are now static: + `supportsProvider()`, `createProvider()`, `unsupportedSchemeProvider()`and `incompleteDsnProvider()` + +6.2 +--- + + * Add a `mailer:test` command + * Add `SentMessageEvent` and `FailedMessageEvent` events + +6.1 +--- + + * Make `start()` and `stop()` methods public on `SmtpTransport` + * Improve extensibility of `EsmtpTransport` + +6.0 +--- + + * The `HttpTransportException` class takes a string at first argument + +5.4 +--- + + * Enable the mailer to operate on any PSR-14-compatible event dispatcher + +5.3 +--- + + * added the `mailer` monolog channel and set it on all transport definitions + +5.2.0 +----- + + * added `NativeTransportFactory` to configure a transport based on php.ini settings + * added `local_domain`, `restart_threshold`, `restart_threshold_sleep` and `ping_threshold` options for `smtp` + * added `command` option for `sendmail` + +4.4.0 +----- + + * [BC BREAK] changed the `NullTransport` DSN from `smtp://null` to `null://null` + * [BC BREAK] renamed `SmtpEnvelope` to `Envelope`, renamed `DelayedSmtpEnvelope` to + `DelayedEnvelope` + * [BC BREAK] changed the syntax for failover and roundrobin DSNs + + Before: + + dummy://a || dummy://b (for failover) + dummy://a && dummy://b (for roundrobin) + + After: + + failover(dummy://a dummy://b) + roundrobin(dummy://a dummy://b) + + * added support for multiple transports on a `Mailer` instance + * [BC BREAK] removed the `auth_mode` DSN option (it is now always determined automatically) + * STARTTLS cannot be enabled anymore (it is used automatically if TLS is disabled and the server supports STARTTLS) + * [BC BREAK] Removed the `encryption` DSN option (use `smtps` instead) + * Added support for the `smtps` protocol (does the same as using `smtp` and port `465`) + * Added PHPUnit constraints + * Added `MessageDataCollector` + * Added `MessageEvents` and `MessageLoggerListener` to allow collecting sent emails + * [BC BREAK] `TransportInterface` has a new `__toString()` method + * [BC BREAK] Classes `AbstractApiTransport` and `AbstractHttpTransport` moved under `Transport` sub-namespace. + * [BC BREAK] Transports depend on `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` + instead of `Symfony\Component\EventDispatcher\EventDispatcherInterface`. + * Added possibility to register custom transport for dsn by implementing + `Symfony\Component\Mailer\Transport\TransportFactoryInterface` and tagging with `mailer.transport_factory` tag in DI. + * Added `Symfony\Component\Mailer\Test\TransportFactoryTestCase` to ease testing custom transport factories. + * Added `SentMessage::getDebug()` and `TransportExceptionInterface::getDebug` to help debugging + * Made `MessageEvent` final + * add DSN parameter `verify_peer` to disable TLS peer verification for SMTP transport + +4.3.0 +----- + + * Added the component. diff --git a/vendor/symfony/mailer/Command/MailerTestCommand.php b/vendor/symfony/mailer/Command/MailerTestCommand.php new file mode 100644 index 0000000..6cde762 --- /dev/null +++ b/vendor/symfony/mailer/Command/MailerTestCommand.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; + +/** + * A console command to test Mailer transports. + */ +#[AsCommand(name: 'mailer:test', description: 'Test Mailer transports by sending an email')] +final class MailerTestCommand extends Command +{ + public function __construct(private TransportInterface $transport) + { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('to', InputArgument::REQUIRED, 'The recipient of the message') + ->addOption('from', null, InputOption::VALUE_OPTIONAL, 'The sender of the message', 'from@example.org') + ->addOption('subject', null, InputOption::VALUE_OPTIONAL, 'The subject of the message', 'Testing transport') + ->addOption('body', null, InputOption::VALUE_OPTIONAL, 'The body of the message', 'Testing body') + ->addOption('transport', null, InputOption::VALUE_OPTIONAL, 'The transport to be used') + ->setHelp(<<<'EOF' +The %command.name% command tests a Mailer transport by sending a simple email message: + +php %command.full_name% to@example.com + +You can also specify a specific transport: + + php %command.full_name% to@example.com --transport=transport_name + +Note that this command bypasses the Messenger bus if configured. + +EOF + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $message = (new Email()) + ->to($input->getArgument('to')) + ->from($input->getOption('from')) + ->subject($input->getOption('subject')) + ->text($input->getOption('body')) + ; + if ($transport = $input->getOption('transport')) { + $message->getHeaders()->addTextHeader('X-Transport', $transport); + } + + $this->transport->send($message); + + return 0; + } +} diff --git a/vendor/symfony/mailer/DataCollector/MessageDataCollector.php b/vendor/symfony/mailer/DataCollector/MessageDataCollector.php new file mode 100644 index 0000000..5c9ac35 --- /dev/null +++ b/vendor/symfony/mailer/DataCollector/MessageDataCollector.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\Mailer\Event\MessageEvents; +use Symfony\Component\Mailer\EventListener\MessageLoggerListener; + +/** + * @author Fabien Potencier + */ +final class MessageDataCollector extends DataCollector +{ + private MessageEvents $events; + + public function __construct(MessageLoggerListener $logger) + { + $this->events = $logger->getEvents(); + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $this->data['events'] = $this->events; + } + + public function getEvents(): MessageEvents + { + return $this->data['events']; + } + + /** + * @internal + */ + public function base64Encode(string $data): string + { + return base64_encode($data); + } + + public function reset(): void + { + $this->data = []; + } + + public function getName(): string + { + return 'mailer'; + } +} diff --git a/vendor/symfony/mailer/DelayedEnvelope.php b/vendor/symfony/mailer/DelayedEnvelope.php new file mode 100644 index 0000000..7458db2 --- /dev/null +++ b/vendor/symfony/mailer/DelayedEnvelope.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer; + +use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Message; + +/** + * @author Fabien Potencier + * + * @internal + */ +final class DelayedEnvelope extends Envelope +{ + private bool $senderSet = false; + private bool $recipientsSet = false; + private Message $message; + + public function __construct(Message $message) + { + $this->message = $message; + } + + public function setSender(Address $sender): void + { + parent::setSender($sender); + + $this->senderSet = true; + } + + public function getSender(): Address + { + if (!$this->senderSet) { + parent::setSender(self::getSenderFromHeaders($this->message->getHeaders())); + } + + return parent::getSender(); + } + + public function setRecipients(array $recipients): void + { + parent::setRecipients($recipients); + + $this->recipientsSet = (bool) parent::getRecipients(); + } + + /** + * @return Address[] + */ + public function getRecipients(): array + { + if ($this->recipientsSet) { + return parent::getRecipients(); + } + + return self::getRecipientsFromHeaders($this->message->getHeaders()); + } + + private static function getRecipientsFromHeaders(Headers $headers): array + { + $recipients = []; + foreach (['to', 'cc', 'bcc'] as $name) { + foreach ($headers->all($name) as $header) { + foreach ($header->getAddresses() as $address) { + $recipients[] = $address; + } + } + } + + return $recipients; + } + + private static function getSenderFromHeaders(Headers $headers): Address + { + if ($sender = $headers->get('Sender')) { + return $sender->getAddress(); + } + if ($return = $headers->get('Return-Path')) { + return $return->getAddress(); + } + if ($from = $headers->get('From')) { + return $from->getAddresses()[0]; + } + + throw new LogicException('Unable to determine the sender of the message.'); + } +} diff --git a/vendor/symfony/mailer/Envelope.php b/vendor/symfony/mailer/Envelope.php new file mode 100644 index 0000000..0a4af2e --- /dev/null +++ b/vendor/symfony/mailer/Envelope.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer; + +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\RawMessage; + +/** + * @author Fabien Potencier + */ +class Envelope +{ + private Address $sender; + private array $recipients = []; + + /** + * @param Address[] $recipients + */ + public function __construct(Address $sender, array $recipients) + { + $this->setSender($sender); + $this->setRecipients($recipients); + } + + public static function create(RawMessage $message): self + { + if (RawMessage::class === $message::class) { + throw new LogicException('Cannot send a RawMessage instance without an explicit Envelope.'); + } + + return new DelayedEnvelope($message); + } + + public function setSender(Address $sender): void + { + // to ensure deliverability of bounce emails independent of UTF-8 capabilities of SMTP servers + if (!preg_match('/^[^@\x80-\xFF]++@/', $sender->getAddress())) { + throw new InvalidArgumentException(sprintf('Invalid sender "%s": non-ASCII characters not supported in local-part of email.', $sender->getAddress())); + } + $this->sender = $sender; + } + + /** + * @return Address Returns a "mailbox" as specified by RFC 2822 + * Must be converted to an "addr-spec" when used as a "MAIL FROM" value in SMTP (use getAddress()) + */ + public function getSender(): Address + { + return $this->sender; + } + + /** + * @param Address[] $recipients + */ + public function setRecipients(array $recipients): void + { + if (!$recipients) { + throw new InvalidArgumentException('An envelope must have at least one recipient.'); + } + + $this->recipients = []; + foreach ($recipients as $recipient) { + if (!$recipient instanceof Address) { + throw new InvalidArgumentException(sprintf('A recipient must be an instance of "%s" (got "%s").', Address::class, get_debug_type($recipient))); + } + $this->recipients[] = new Address($recipient->getAddress()); + } + } + + /** + * @return Address[] + */ + public function getRecipients(): array + { + return $this->recipients; + } +} diff --git a/vendor/symfony/mailer/Event/FailedMessageEvent.php b/vendor/symfony/mailer/Event/FailedMessageEvent.php new file mode 100644 index 0000000..4808c65 --- /dev/null +++ b/vendor/symfony/mailer/Event/FailedMessageEvent.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Event; + +use Symfony\Component\Mime\RawMessage; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Fabien Potencier + */ +final class FailedMessageEvent extends Event +{ + public function __construct( + private RawMessage $message, + private \Throwable $error, + ) { + } + + public function getMessage(): RawMessage + { + return $this->message; + } + + public function getError(): \Throwable + { + return $this->error; + } +} diff --git a/vendor/symfony/mailer/Event/MessageEvent.php b/vendor/symfony/mailer/Event/MessageEvent.php new file mode 100644 index 0000000..f00fdd5 --- /dev/null +++ b/vendor/symfony/mailer/Event/MessageEvent.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Event; + +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Messenger\Stamp\StampInterface; +use Symfony\Component\Mime\RawMessage; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Allows the transformation of a Message and the Envelope before the email is sent. + * + * @author Fabien Potencier + */ +final class MessageEvent extends Event +{ + private RawMessage $message; + private Envelope $envelope; + private string $transport; + private bool $queued; + private bool $rejected = false; + + /** @var StampInterface[] */ + private array $stamps = []; + + public function __construct(RawMessage $message, Envelope $envelope, string $transport, bool $queued = false) + { + $this->message = $message; + $this->envelope = $envelope; + $this->transport = $transport; + $this->queued = $queued; + } + + public function getMessage(): RawMessage + { + return $this->message; + } + + public function setMessage(RawMessage $message): void + { + $this->message = $message; + } + + public function getEnvelope(): Envelope + { + return $this->envelope; + } + + public function setEnvelope(Envelope $envelope): void + { + $this->envelope = $envelope; + } + + public function getTransport(): string + { + return $this->transport; + } + + public function isQueued(): bool + { + return $this->queued; + } + + public function isRejected(): bool + { + return $this->rejected; + } + + public function reject(): void + { + $this->rejected = true; + $this->stopPropagation(); + } + + public function addStamp(StampInterface $stamp): void + { + if (!$this->queued) { + throw new LogicException(sprintf('Cannot call "%s()" on a message that is not meant to be queued.', __METHOD__)); + } + + $this->stamps[] = $stamp; + } + + /** + * @return StampInterface[] + */ + public function getStamps(): array + { + if (!$this->queued) { + throw new LogicException(sprintf('Cannot call "%s()" on a message that is not meant to be queued.', __METHOD__)); + } + + return $this->stamps; + } +} diff --git a/vendor/symfony/mailer/Event/MessageEvents.php b/vendor/symfony/mailer/Event/MessageEvents.php new file mode 100644 index 0000000..2b438e3 --- /dev/null +++ b/vendor/symfony/mailer/Event/MessageEvents.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Event; + +use Symfony\Component\Mime\RawMessage; + +/** + * @author Fabien Potencier + */ +class MessageEvents +{ + /** + * @var MessageEvent[] + */ + private array $events = []; + + /** + * @var array + */ + private array $transports = []; + + public function add(MessageEvent $event): void + { + $this->events[] = $event; + $this->transports[$event->getTransport()] = true; + } + + public function getTransports(): array + { + return array_keys($this->transports); + } + + /** + * @return MessageEvent[] + */ + public function getEvents(?string $name = null): array + { + if (null === $name) { + return $this->events; + } + + $events = []; + foreach ($this->events as $event) { + if ($name === $event->getTransport()) { + $events[] = $event; + } + } + + return $events; + } + + /** + * @return RawMessage[] + */ + public function getMessages(?string $name = null): array + { + $events = $this->getEvents($name); + $messages = []; + foreach ($events as $event) { + $messages[] = $event->getMessage(); + } + + return $messages; + } +} diff --git a/vendor/symfony/mailer/Event/SentMessageEvent.php b/vendor/symfony/mailer/Event/SentMessageEvent.php new file mode 100644 index 0000000..a412a9f --- /dev/null +++ b/vendor/symfony/mailer/Event/SentMessageEvent.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Event; + +use Symfony\Component\Mailer\SentMessage; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Fabien Potencier + */ +final class SentMessageEvent extends Event +{ + public function __construct(private SentMessage $message) + { + } + + public function getMessage(): SentMessage + { + return $this->message; + } +} diff --git a/vendor/symfony/mailer/EventListener/EnvelopeListener.php b/vendor/symfony/mailer/EventListener/EnvelopeListener.php new file mode 100644 index 0000000..9a1e39c --- /dev/null +++ b/vendor/symfony/mailer/EventListener/EnvelopeListener.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Message; + +/** + * Manipulates the Envelope of a Message. + * + * @author Fabien Potencier + * @author Grégoire Pineau + */ +class EnvelopeListener implements EventSubscriberInterface +{ + private ?Address $sender = null; + + /** + * @var Address[]|null + */ + private ?array $recipients = null; + + /** + * @param array $recipients + * @param string[] $allowedRecipients An array of regex to match the allowed recipients + */ + public function __construct( + Address|string|null $sender = null, + ?array $recipients = null, + private array $allowedRecipients = [], + ) { + if (null !== $sender) { + $this->sender = Address::create($sender); + } + if (null !== $recipients) { + $this->recipients = Address::createArray($recipients); + } + } + + public function onMessage(MessageEvent $event): void + { + if ($this->sender) { + $event->getEnvelope()->setSender($this->sender); + + $message = $event->getMessage(); + if ($message instanceof Message) { + if (!$message->getHeaders()->has('Sender') && !$message->getHeaders()->has('From')) { + $message->getHeaders()->addMailboxHeader('Sender', $this->sender); + } + } + } + + if ($this->recipients) { + $recipients = $this->recipients; + if ($this->allowedRecipients) { + foreach ($event->getEnvelope()->getRecipients() as $recipient) { + foreach ($this->allowedRecipients as $allowedRecipient) { + if (!preg_match('{\A'.$allowedRecipient.'\z}', $recipient->getAddress())) { + continue; + } + // dedup + foreach ($recipients as $r) { + if ($r->getName() === $recipient->getName() && $r->getAddress() === $recipient->getAddress()) { + continue 2; + } + } + + $recipients[] = $recipient; + continue 2; + } + } + } + + $event->getEnvelope()->setRecipients($recipients); + } + } + + public static function getSubscribedEvents(): array + { + return [ + // should be the last one to allow header changes by other listeners first + MessageEvent::class => ['onMessage', -255], + ]; + } +} diff --git a/vendor/symfony/mailer/EventListener/MessageListener.php b/vendor/symfony/mailer/EventListener/MessageListener.php new file mode 100644 index 0000000..ec822d9 --- /dev/null +++ b/vendor/symfony/mailer/EventListener/MessageListener.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Exception\RuntimeException; +use Symfony\Component\Mime\BodyRendererInterface; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Header\MailboxListHeader; +use Symfony\Component\Mime\Message; + +/** + * Manipulates the headers and the body of a Message. + * + * @author Fabien Potencier + */ +class MessageListener implements EventSubscriberInterface +{ + public const HEADER_SET_IF_EMPTY = 1; + public const HEADER_ADD = 2; + public const HEADER_REPLACE = 3; + public const DEFAULT_RULES = [ + 'from' => self::HEADER_SET_IF_EMPTY, + 'return-path' => self::HEADER_SET_IF_EMPTY, + 'reply-to' => self::HEADER_ADD, + 'to' => self::HEADER_SET_IF_EMPTY, + 'cc' => self::HEADER_ADD, + 'bcc' => self::HEADER_ADD, + ]; + + private ?Headers $headers; + private array $headerRules = []; + private ?BodyRendererInterface $renderer; + + public function __construct(?Headers $headers = null, ?BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES) + { + $this->headers = $headers; + $this->renderer = $renderer; + foreach ($headerRules as $headerName => $rule) { + $this->addHeaderRule($headerName, $rule); + } + } + + public function addHeaderRule(string $headerName, int $rule): void + { + if ($rule < 1 || $rule > 3) { + throw new InvalidArgumentException(sprintf('The "%d" rule is not supported.', $rule)); + } + + $this->headerRules[strtolower($headerName)] = $rule; + } + + public function onMessage(MessageEvent $event): void + { + $message = $event->getMessage(); + if (!$message instanceof Message) { + return; + } + + $this->setHeaders($message); + $this->renderMessage($message); + } + + private function setHeaders(Message $message): void + { + if (!$this->headers) { + return; + } + + $headers = $message->getHeaders(); + foreach ($this->headers->all() as $name => $header) { + if (!$headers->has($name)) { + $headers->add($header); + + continue; + } + + switch ($this->headerRules[$name] ?? self::HEADER_SET_IF_EMPTY) { + case self::HEADER_SET_IF_EMPTY: + break; + + case self::HEADER_REPLACE: + $headers->remove($name); + $headers->add($header); + + break; + + case self::HEADER_ADD: + if (!Headers::isUniqueHeader($name)) { + $headers->add($header); + + break; + } + + $h = $headers->get($name); + if (!$h instanceof MailboxListHeader) { + throw new RuntimeException(sprintf('Unable to set header "%s".', $name)); + } + + Headers::checkHeaderClass($header); + foreach ($header->getAddresses() as $address) { + $h->addAddress($address); + } + } + } + } + + private function renderMessage(Message $message): void + { + if (!$this->renderer) { + return; + } + + $this->renderer->render($message); + } + + public static function getSubscribedEvents(): array + { + return [ + MessageEvent::class => 'onMessage', + ]; + } +} diff --git a/vendor/symfony/mailer/EventListener/MessageLoggerListener.php b/vendor/symfony/mailer/EventListener/MessageLoggerListener.php new file mode 100644 index 0000000..73338fd --- /dev/null +++ b/vendor/symfony/mailer/EventListener/MessageLoggerListener.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Event\MessageEvents; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Logs Messages. + * + * @author Fabien Potencier + */ +class MessageLoggerListener implements EventSubscriberInterface, ResetInterface +{ + private MessageEvents $events; + + public function __construct() + { + $this->events = new MessageEvents(); + } + + public function reset(): void + { + $this->events = new MessageEvents(); + } + + public function onMessage(MessageEvent $event): void + { + $this->events->add($event); + } + + public function getEvents(): MessageEvents + { + return $this->events; + } + + public static function getSubscribedEvents(): array + { + return [ + MessageEvent::class => ['onMessage', -255], + ]; + } +} diff --git a/vendor/symfony/mailer/EventListener/MessengerTransportListener.php b/vendor/symfony/mailer/EventListener/MessengerTransportListener.php new file mode 100644 index 0000000..8cf8cc8 --- /dev/null +++ b/vendor/symfony/mailer/EventListener/MessengerTransportListener.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Messenger\Stamp\TransportNamesStamp; +use Symfony\Component\Mime\Message; + +/** + * Allows messages to be sent to specific Messenger transports via the "X-Bus-Transport" MIME header. + * + * @author Fabien Potencier + */ +final class MessengerTransportListener implements EventSubscriberInterface +{ + public function onMessage(MessageEvent $event): void + { + if (!$event->isQueued()) { + return; + } + + $message = $event->getMessage(); + if (!$message instanceof Message || !$message->getHeaders()->has('X-Bus-Transport')) { + return; + } + + $names = $message->getHeaders()->get('X-Bus-Transport')->getBody(); + $names = array_map('trim', explode(',', $names)); + $event->addStamp(new TransportNamesStamp($names)); + $message->getHeaders()->remove('X-Bus-Transport'); + } + + public static function getSubscribedEvents(): array + { + return [ + MessageEvent::class => 'onMessage', + ]; + } +} diff --git a/vendor/symfony/mailer/Exception/ExceptionInterface.php b/vendor/symfony/mailer/Exception/ExceptionInterface.php new file mode 100644 index 0000000..2f0f3a6 --- /dev/null +++ b/vendor/symfony/mailer/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/mailer/Exception/HttpTransportException.php b/vendor/symfony/mailer/Exception/HttpTransportException.php new file mode 100644 index 0000000..ad10910 --- /dev/null +++ b/vendor/symfony/mailer/Exception/HttpTransportException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Fabien Potencier + */ +class HttpTransportException extends TransportException +{ + private ResponseInterface $response; + + public function __construct(string $message, ResponseInterface $response, int $code = 0, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->response = $response; + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } +} diff --git a/vendor/symfony/mailer/Exception/IncompleteDsnException.php b/vendor/symfony/mailer/Exception/IncompleteDsnException.php new file mode 100644 index 0000000..f2618b6 --- /dev/null +++ b/vendor/symfony/mailer/Exception/IncompleteDsnException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * @author Konstantin Myakshin + */ +class IncompleteDsnException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/mailer/Exception/InvalidArgumentException.php b/vendor/symfony/mailer/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..ba53334 --- /dev/null +++ b/vendor/symfony/mailer/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * @author Fabien Potencier + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/mailer/Exception/LogicException.php b/vendor/symfony/mailer/Exception/LogicException.php new file mode 100644 index 0000000..487c0a3 --- /dev/null +++ b/vendor/symfony/mailer/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * @author Fabien Potencier + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/mailer/Exception/RuntimeException.php b/vendor/symfony/mailer/Exception/RuntimeException.php new file mode 100644 index 0000000..44b79cc --- /dev/null +++ b/vendor/symfony/mailer/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * @author Fabien Potencier + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/mailer/Exception/TransportException.php b/vendor/symfony/mailer/Exception/TransportException.php new file mode 100644 index 0000000..ea538d6 --- /dev/null +++ b/vendor/symfony/mailer/Exception/TransportException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * @author Fabien Potencier + */ +class TransportException extends RuntimeException implements TransportExceptionInterface +{ + private string $debug = ''; + + public function getDebug(): string + { + return $this->debug; + } + + public function appendDebug(string $debug): void + { + $this->debug .= $debug; + } +} diff --git a/vendor/symfony/mailer/Exception/TransportExceptionInterface.php b/vendor/symfony/mailer/Exception/TransportExceptionInterface.php new file mode 100644 index 0000000..4318f5c --- /dev/null +++ b/vendor/symfony/mailer/Exception/TransportExceptionInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * @author Fabien Potencier + */ +interface TransportExceptionInterface extends ExceptionInterface +{ + public function getDebug(): string; + + public function appendDebug(string $debug): void; +} diff --git a/vendor/symfony/mailer/Exception/UnexpectedResponseException.php b/vendor/symfony/mailer/Exception/UnexpectedResponseException.php new file mode 100644 index 0000000..e779df1 --- /dev/null +++ b/vendor/symfony/mailer/Exception/UnexpectedResponseException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +class UnexpectedResponseException extends TransportException +{ +} diff --git a/vendor/symfony/mailer/Exception/UnsupportedSchemeException.php b/vendor/symfony/mailer/Exception/UnsupportedSchemeException.php new file mode 100644 index 0000000..5ac0d3d --- /dev/null +++ b/vendor/symfony/mailer/Exception/UnsupportedSchemeException.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +use Symfony\Component\Mailer\Bridge; +use Symfony\Component\Mailer\Transport\Dsn; + +/** + * @author Konstantin Myakshin + */ +class UnsupportedSchemeException extends LogicException +{ + private const SCHEME_TO_PACKAGE_MAP = [ + 'azure' => [ + 'class' => Bridge\Azure\Transport\AzureTransportFactory::class, + 'package' => 'symfony/azure-mailer', + ], + 'brevo' => [ + 'class' => Bridge\Brevo\Transport\BrevoTransportFactory::class, + 'package' => 'symfony/brevo-mailer', + ], + 'gmail' => [ + 'class' => Bridge\Google\Transport\GmailTransportFactory::class, + 'package' => 'symfony/google-mailer', + ], + 'infobip' => [ + 'class' => Bridge\Infobip\Transport\InfobipTransportFactory::class, + 'package' => 'symfony/infobip-mailer', + ], + 'mailersend' => [ + 'class' => Bridge\MailerSend\Transport\MailerSendTransportFactory::class, + 'package' => 'symfony/mailersend-mailer', + ], + 'mailgun' => [ + 'class' => Bridge\Mailgun\Transport\MailgunTransportFactory::class, + 'package' => 'symfony/mailgun-mailer', + ], + 'mailjet' => [ + 'class' => Bridge\Mailjet\Transport\MailjetTransportFactory::class, + 'package' => 'symfony/mailjet-mailer', + ], + 'mailpace' => [ + 'class' => Bridge\MailPace\Transport\MailPaceTransportFactory::class, + 'package' => 'symfony/mail-pace-mailer', + ], + 'mandrill' => [ + 'class' => Bridge\Mailchimp\Transport\MandrillTransportFactory::class, + 'package' => 'symfony/mailchimp-mailer', + ], + 'postmark' => [ + 'class' => Bridge\Postmark\Transport\PostmarkTransportFactory::class, + 'package' => 'symfony/postmark-mailer', + ], + 'resend' => [ + 'class' => Bridge\Resend\Transport\ResendTransportFactory::class, + 'package' => 'symfony/resend-mailer', + ], + 'scaleway' => [ + 'class' => Bridge\Scaleway\Transport\ScalewayTransportFactory::class, + 'package' => 'symfony/scaleway-mailer', + ], + 'sendgrid' => [ + 'class' => Bridge\Sendgrid\Transport\SendgridTransportFactory::class, + 'package' => 'symfony/sendgrid-mailer', + ], + 'ses' => [ + 'class' => Bridge\Amazon\Transport\SesTransportFactory::class, + 'package' => 'symfony/amazon-mailer', + ], + ]; + + public function __construct(Dsn $dsn, ?string $name = null, array $supported = []) + { + $provider = $dsn->getScheme(); + if (false !== $pos = strpos($provider, '+')) { + $provider = substr($provider, 0, $pos); + } + $package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null; + if ($package && !class_exists($package['class'])) { + parent::__construct(sprintf('Unable to send emails via "%s" as the bridge is not installed. Try running "composer require %s".', $provider, $package['package'])); + + return; + } + + $message = sprintf('The "%s" scheme is not supported', $dsn->getScheme()); + if ($name && $supported) { + $message .= sprintf('; supported schemes for mailer "%s" are: "%s"', $name, implode('", "', $supported)); + } + + parent::__construct($message.'.'); + } +} diff --git a/vendor/symfony/mailer/Header/MetadataHeader.php b/vendor/symfony/mailer/Header/MetadataHeader.php new file mode 100644 index 0000000..d6ee544 --- /dev/null +++ b/vendor/symfony/mailer/Header/MetadataHeader.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Header; + +use Symfony\Component\Mime\Header\UnstructuredHeader; + +/** + * @author Kevin Bond + */ +final class MetadataHeader extends UnstructuredHeader +{ + private string $key; + + public function __construct(string $key, string $value) + { + $this->key = $key; + + parent::__construct('X-Metadata-'.$key, $value); + } + + public function getKey(): string + { + return $this->key; + } +} diff --git a/vendor/symfony/mailer/Header/TagHeader.php b/vendor/symfony/mailer/Header/TagHeader.php new file mode 100644 index 0000000..7115cae --- /dev/null +++ b/vendor/symfony/mailer/Header/TagHeader.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Header; + +use Symfony\Component\Mime\Header\UnstructuredHeader; + +/** + * @author Kevin Bond + */ +final class TagHeader extends UnstructuredHeader +{ + public function __construct(string $value) + { + parent::__construct('X-Tag', $value); + } +} diff --git a/vendor/symfony/mailer/LICENSE b/vendor/symfony/mailer/LICENSE new file mode 100644 index 0000000..f37c76b --- /dev/null +++ b/vendor/symfony/mailer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-present Fabien Potencier + +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. diff --git a/vendor/symfony/mailer/Mailer.php b/vendor/symfony/mailer/Mailer.php new file mode 100644 index 0000000..6df9909 --- /dev/null +++ b/vendor/symfony/mailer/Mailer.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Messenger\SendEmailMessage; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Messenger\Exception\HandlerFailedException; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Mime\RawMessage; + +/** + * @author Fabien Potencier + */ +final class Mailer implements MailerInterface +{ + private TransportInterface $transport; + private ?MessageBusInterface $bus; + private ?EventDispatcherInterface $dispatcher; + + public function __construct(TransportInterface $transport, ?MessageBusInterface $bus = null, ?EventDispatcherInterface $dispatcher = null) + { + $this->transport = $transport; + $this->bus = $bus; + $this->dispatcher = $dispatcher; + } + + public function send(RawMessage $message, ?Envelope $envelope = null): void + { + if (null === $this->bus) { + $this->transport->send($message, $envelope); + + return; + } + + $stamps = []; + if (null !== $this->dispatcher) { + // The dispatched event here has `queued` set to `true`; the goal is NOT to render the message, but to let + // listeners do something before a message is sent to the queue. + // We are using a cloned message as we still want to dispatch the **original** message, not the one modified by listeners. + // That's because the listeners will run again when the email is sent via Messenger by the transport (see `AbstractTransport`). + // Listeners should act depending on the `$queued` argument of the `MessageEvent` instance. + $clonedMessage = clone $message; + $clonedEnvelope = null !== $envelope ? clone $envelope : Envelope::create($clonedMessage); + $event = new MessageEvent($clonedMessage, $clonedEnvelope, (string) $this->transport, true); + $this->dispatcher->dispatch($event); + $stamps = $event->getStamps(); + + if ($event->isRejected()) { + return; + } + } + + try { + $this->bus->dispatch(new SendEmailMessage($message, $envelope), $stamps); + } catch (HandlerFailedException $e) { + foreach ($e->getWrappedExceptions() as $nested) { + if ($nested instanceof TransportExceptionInterface) { + throw $nested; + } + } + throw $e; + } + } +} diff --git a/vendor/symfony/mailer/MailerInterface.php b/vendor/symfony/mailer/MailerInterface.php new file mode 100644 index 0000000..ebac4b5 --- /dev/null +++ b/vendor/symfony/mailer/MailerInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer; + +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mime\RawMessage; + +/** + * Interface for mailers able to send emails synchronously and/or asynchronously. + * + * Implementations must support synchronous and asynchronous sending. + * + * @author Fabien Potencier + */ +interface MailerInterface +{ + /** + * @throws TransportExceptionInterface + */ + public function send(RawMessage $message, ?Envelope $envelope = null): void; +} diff --git a/vendor/symfony/mailer/Messenger/MessageHandler.php b/vendor/symfony/mailer/Messenger/MessageHandler.php new file mode 100644 index 0000000..f8fb14f --- /dev/null +++ b/vendor/symfony/mailer/Messenger/MessageHandler.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Messenger; + +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Fabien Potencier + */ +class MessageHandler +{ + private TransportInterface $transport; + + public function __construct(TransportInterface $transport) + { + $this->transport = $transport; + } + + public function __invoke(SendEmailMessage $message): ?SentMessage + { + return $this->transport->send($message->getMessage(), $message->getEnvelope()); + } +} diff --git a/vendor/symfony/mailer/Messenger/SendEmailMessage.php b/vendor/symfony/mailer/Messenger/SendEmailMessage.php new file mode 100644 index 0000000..18bd506 --- /dev/null +++ b/vendor/symfony/mailer/Messenger/SendEmailMessage.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Messenger; + +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mime\RawMessage; + +/** + * @author Fabien Potencier + */ +class SendEmailMessage +{ + private RawMessage $message; + private ?Envelope $envelope; + + public function __construct(RawMessage $message, ?Envelope $envelope = null) + { + $this->message = $message; + $this->envelope = $envelope; + } + + public function getMessage(): RawMessage + { + return $this->message; + } + + public function getEnvelope(): ?Envelope + { + return $this->envelope; + } +} diff --git a/vendor/symfony/mailer/README.md b/vendor/symfony/mailer/README.md new file mode 100644 index 0000000..04d8f76 --- /dev/null +++ b/vendor/symfony/mailer/README.md @@ -0,0 +1,74 @@ +Mailer Component +================ + +The Mailer component helps sending emails. + +Getting Started +--------------- + +```bash +composer require symfony/mailer +``` + +```php +use Symfony\Component\Mailer\Transport; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mime\Email; + +$transport = Transport::fromDsn('smtp://localhost'); +$mailer = new Mailer($transport); + +$email = (new Email()) + ->from('hello@example.com') + ->to('you@example.com') + //->cc('cc@example.com') + //->bcc('bcc@example.com') + //->replyTo('fabien@example.com') + //->priority(Email::PRIORITY_HIGH) + ->subject('Time for Symfony Mailer!') + ->text('Sending emails is fun again!') + ->html('

    See Twig integration for better HTML integration!

    '); + +$mailer->send($email); +``` + +To enable the Twig integration of the Mailer, require `symfony/twig-bridge` and +set up the `BodyRenderer`: + +```php +use Symfony\Bridge\Twig\Mime\BodyRenderer; +use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Mailer\EventListener\MessageListener; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\Transport; +use Twig\Environment as TwigEnvironment; + +$twig = new TwigEnvironment(...); +$messageListener = new MessageListener(null, new BodyRenderer($twig)); + +$eventDispatcher = new EventDispatcher(); +$eventDispatcher->addSubscriber($messageListener); + +$transport = Transport::fromDsn('smtp://localhost', $eventDispatcher); +$mailer = new Mailer($transport, null, $eventDispatcher); + +$email = (new TemplatedEmail()) + // ... + ->htmlTemplate('emails/signup.html.twig') + ->context([ + 'expiration_date' => new \DateTimeImmutable('+7 days'), + 'username' => 'foo', + ]) +; +$mailer->send($email); +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/mailer.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/mailer/SentMessage.php b/vendor/symfony/mailer/SentMessage.php new file mode 100644 index 0000000..be84711 --- /dev/null +++ b/vendor/symfony/mailer/SentMessage.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer; + +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +/** + * @author Fabien Potencier + */ +class SentMessage +{ + private RawMessage $original; + private RawMessage $raw; + private Envelope $envelope; + private string $messageId; + private string $debug = ''; + + /** + * @internal + */ + public function __construct(RawMessage $message, Envelope $envelope) + { + $message->ensureValidity(); + + $this->original = $message; + $this->envelope = $envelope; + + if ($message instanceof Message) { + $message = clone $message; + $headers = $message->getHeaders(); + if (!$headers->has('Message-ID')) { + $headers->addIdHeader('Message-ID', $message->generateMessageId()); + } + $this->messageId = $headers->get('Message-ID')->getId(); + $this->raw = new RawMessage($message->toIterable()); + } else { + $this->raw = $message; + } + } + + public function getMessage(): RawMessage + { + return $this->raw; + } + + public function getOriginalMessage(): RawMessage + { + return $this->original; + } + + public function getEnvelope(): Envelope + { + return $this->envelope; + } + + public function setMessageId(string $id): void + { + $this->messageId = $id; + } + + public function getMessageId(): string + { + return $this->messageId; + } + + public function getDebug(): string + { + return $this->debug; + } + + public function appendDebug(string $debug): void + { + $this->debug .= $debug; + } + + public function toString(): string + { + return $this->raw->toString(); + } + + public function toIterable(): iterable + { + return $this->raw->toIterable(); + } +} diff --git a/vendor/symfony/mailer/Test/Constraint/EmailCount.php b/vendor/symfony/mailer/Test/Constraint/EmailCount.php new file mode 100644 index 0000000..2ba80f2 --- /dev/null +++ b/vendor/symfony/mailer/Test/Constraint/EmailCount.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mailer\Event\MessageEvents; + +final class EmailCount extends Constraint +{ + private int $expectedValue; + private ?string $transport; + private bool $queued; + + public function __construct(int $expectedValue, ?string $transport = null, bool $queued = false) + { + $this->expectedValue = $expectedValue; + $this->transport = $transport; + $this->queued = $queued; + } + + public function toString(): string + { + return sprintf('%shas %s "%d" emails', $this->transport ? $this->transport.' ' : '', $this->queued ? 'queued' : 'sent', $this->expectedValue); + } + + /** + * @param MessageEvents $events + */ + protected function matches($events): bool + { + return $this->expectedValue === $this->countEmails($events); + } + + /** + * @param MessageEvents $events + */ + protected function failureDescription($events): string + { + return sprintf('the Transport %s (%d %s)', $this->toString(), $this->countEmails($events), $this->queued ? 'queued' : 'sent'); + } + + private function countEmails(MessageEvents $events): int + { + $count = 0; + foreach ($events->getEvents($this->transport) as $event) { + if ( + ($this->queued && $event->isQueued()) + || (!$this->queued && !$event->isQueued()) + ) { + ++$count; + } + } + + return $count; + } +} diff --git a/vendor/symfony/mailer/Test/Constraint/EmailIsQueued.php b/vendor/symfony/mailer/Test/Constraint/EmailIsQueued.php new file mode 100644 index 0000000..46436aa --- /dev/null +++ b/vendor/symfony/mailer/Test/Constraint/EmailIsQueued.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mailer\Event\MessageEvent; + +final class EmailIsQueued extends Constraint +{ + public function toString(): string + { + return 'is queued'; + } + + /** + * @param MessageEvent $event + */ + protected function matches($event): bool + { + return $event->isQueued(); + } + + /** + * @param MessageEvent $event + */ + protected function failureDescription($event): string + { + return 'the Email '.$this->toString(); + } +} diff --git a/vendor/symfony/mailer/Test/TransportFactoryTestCase.php b/vendor/symfony/mailer/Test/TransportFactoryTestCase.php new file mode 100644 index 0000000..2ca7db9 --- /dev/null +++ b/vendor/symfony/mailer/Test/TransportFactoryTestCase.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Exception\IncompleteDsnException; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A test case to ease testing Transport Factory. + * + * @author Konstantin Myakshin + */ +abstract class TransportFactoryTestCase extends TestCase +{ + protected const USER = 'u$er'; + protected const PASSWORD = 'pa$s'; + + protected EventDispatcherInterface $dispatcher; + protected HttpClientInterface $client; + protected LoggerInterface $logger; + + abstract public function getFactory(): TransportFactoryInterface; + + abstract public static function supportsProvider(): iterable; + + abstract public static function createProvider(): iterable; + + public static function unsupportedSchemeProvider(): iterable + { + return []; + } + + public static function incompleteDsnProvider(): iterable + { + return []; + } + + /** + * @dataProvider supportsProvider + */ + public function testSupports(Dsn $dsn, bool $supports) + { + $factory = $this->getFactory(); + + $this->assertSame($supports, $factory->supports($dsn)); + } + + /** + * @dataProvider createProvider + */ + public function testCreate(Dsn $dsn, TransportInterface $transport) + { + $factory = $this->getFactory(); + + $this->assertEquals($transport, $factory->create($dsn)); + if (str_contains('smtp', $dsn->getScheme())) { + $this->assertStringMatchesFormat($dsn->getScheme().'://%S'.$dsn->getHost().'%S', (string) $transport); + } + } + + /** + * @dataProvider unsupportedSchemeProvider + */ + public function testUnsupportedSchemeException(Dsn $dsn, ?string $message = null) + { + $factory = $this->getFactory(); + + $this->expectException(UnsupportedSchemeException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } + + /** + * @dataProvider incompleteDsnProvider + */ + public function testIncompleteDsnException(Dsn $dsn) + { + $factory = $this->getFactory(); + + $this->expectException(IncompleteDsnException::class); + $factory->create($dsn); + } + + protected function getDispatcher(): EventDispatcherInterface + { + return $this->dispatcher ??= $this->createMock(EventDispatcherInterface::class); + } + + protected function getClient(): HttpClientInterface + { + return $this->client ??= $this->createMock(HttpClientInterface::class); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger ??= $this->createMock(LoggerInterface::class); + } +} diff --git a/vendor/symfony/mailer/Transport.php b/vendor/symfony/mailer/Transport.php new file mode 100644 index 0000000..7d8a0ed --- /dev/null +++ b/vendor/symfony/mailer/Transport.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Azure\Transport\AzureTransportFactory; +use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; +use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\MailerSend\Transport\MailerSendTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; +use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; +use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; +use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\FailoverTransport; +use Symfony\Component\Mailer\Transport\NativeTransportFactory; +use Symfony\Component\Mailer\Transport\NullTransportFactory; +use Symfony\Component\Mailer\Transport\RoundRobinTransport; +use Symfony\Component\Mailer\Transport\SendmailTransportFactory; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mailer\Transport\Transports; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Fabien Potencier + * @author Konstantin Myakshin + */ +final class Transport +{ + private const FACTORY_CLASSES = [ + AzureTransportFactory::class, + BrevoTransportFactory::class, + GmailTransportFactory::class, + InfobipTransportFactory::class, + MailerSendTransportFactory::class, + MailgunTransportFactory::class, + MailjetTransportFactory::class, + MailPaceTransportFactory::class, + MandrillTransportFactory::class, + PostmarkTransportFactory::class, + ResendTransportFactory::class, + ScalewayTransportFactory::class, + SendgridTransportFactory::class, + SesTransportFactory::class, + ]; + + private iterable $factories; + + public static function fromDsn(#[\SensitiveParameter] string $dsn, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface + { + $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger))); + + return $factory->fromString($dsn); + } + + public static function fromDsns(#[\SensitiveParameter] array $dsns, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface + { + $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger))); + + return $factory->fromStrings($dsns); + } + + /** + * @param TransportFactoryInterface[] $factories + */ + public function __construct(iterable $factories) + { + $this->factories = $factories; + } + + public function fromStrings(#[\SensitiveParameter] array $dsns): Transports + { + $transports = []; + foreach ($dsns as $name => $dsn) { + $transports[$name] = $this->fromString($dsn); + } + + return new Transports($transports); + } + + public function fromString(#[\SensitiveParameter] string $dsn): TransportInterface + { + [$transport, $offset] = $this->parseDsn($dsn); + if ($offset !== \strlen($dsn)) { + throw new InvalidArgumentException('The mailer DSN has some garbage at the end.'); + } + + return $transport; + } + + private function parseDsn(#[\SensitiveParameter] string $dsn, int $offset = 0): array + { + static $keywords = [ + 'failover' => FailoverTransport::class, + 'roundrobin' => RoundRobinTransport::class, + ]; + + while (true) { + foreach ($keywords as $name => $class) { + $name .= '('; + if ($name === substr($dsn, $offset, \strlen($name))) { + $offset += \strlen($name) - 1; + preg_match('{\(([^()]|(?R))*\)}A', $dsn, $matches, 0, $offset); + if (!isset($matches[0])) { + continue; + } + + ++$offset; + $args = []; + while (true) { + [$arg, $offset] = $this->parseDsn($dsn, $offset); + $args[] = $arg; + if (\strlen($dsn) === $offset) { + break; + } + ++$offset; + if (')' === $dsn[$offset - 1]) { + break; + } + } + + return [new $class($args), $offset]; + } + } + + if (preg_match('{(\w+)\(}A', $dsn, $matches, 0, $offset)) { + throw new InvalidArgumentException(sprintf('The "%s" keyword is not valid (valid ones are "%s"), ', $matches[1], implode('", "', array_keys($keywords)))); + } + + if ($pos = strcspn($dsn, ' )', $offset)) { + return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset, $pos))), $offset + $pos]; + } + + return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset))), \strlen($dsn)]; + } + } + + public function fromDsnObject(Dsn $dsn): TransportInterface + { + foreach ($this->factories as $factory) { + if ($factory->supports($dsn)) { + return $factory->create($dsn); + } + } + + throw new UnsupportedSchemeException($dsn); + } + + /** + * @return \Traversable + */ + public static function getDefaultFactories(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): \Traversable + { + foreach (self::FACTORY_CLASSES as $factoryClass) { + if (class_exists($factoryClass)) { + yield new $factoryClass($dispatcher, $client, $logger); + } + } + + yield new NullTransportFactory($dispatcher, $client, $logger); + + yield new SendmailTransportFactory($dispatcher, $client, $logger); + + yield new EsmtpTransportFactory($dispatcher, $client, $logger); + + yield new NativeTransportFactory($dispatcher, $client, $logger); + } +} diff --git a/vendor/symfony/mailer/Transport/AbstractApiTransport.php b/vendor/symfony/mailer/Transport/AbstractApiTransport.php new file mode 100644 index 0000000..040745e --- /dev/null +++ b/vendor/symfony/mailer/Transport/AbstractApiTransport.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\RuntimeException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\MessageConverter; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Fabien Potencier + */ +abstract class AbstractApiTransport extends AbstractHttpTransport +{ + abstract protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface; + + protected function doSendHttp(SentMessage $message): ResponseInterface + { + try { + $email = MessageConverter::toEmail($message->getOriginalMessage()); + } catch (\Exception $e) { + throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: ', __CLASS__).$e->getMessage(), 0, $e); + } + + return $this->doSendApi($message, $email, $message->getEnvelope()); + } + + /** + * @return Address[] + */ + protected function getRecipients(Email $email, Envelope $envelope): array + { + return array_filter($envelope->getRecipients(), fn (Address $address) => false === \in_array($address, array_merge($email->getCc(), $email->getBcc()), true)); + } +} diff --git a/vendor/symfony/mailer/Transport/AbstractHttpTransport.php b/vendor/symfony/mailer/Transport/AbstractHttpTransport.php new file mode 100644 index 0000000..1ae1b94 --- /dev/null +++ b/vendor/symfony/mailer/Transport/AbstractHttpTransport.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\Mailer\Exception\HttpTransportException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Victor Bocharsky + */ +abstract class AbstractHttpTransport extends AbstractTransport +{ + protected ?string $host = null; + protected ?int $port = null; + protected ?HttpClientInterface $client; + + public function __construct(?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) + { + $this->client = $client; + if (null === $client) { + if (!class_exists(HttpClient::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); + } + + $this->client = HttpClient::create(); + } + + parent::__construct($dispatcher, $logger); + } + + /** + * @return $this + */ + public function setHost(?string $host): static + { + $this->host = $host; + + return $this; + } + + /** + * @return $this + */ + public function setPort(?int $port): static + { + $this->port = $port; + + return $this; + } + + abstract protected function doSendHttp(SentMessage $message): ResponseInterface; + + protected function doSend(SentMessage $message): void + { + try { + $response = $this->doSendHttp($message); + $message->appendDebug($response->getInfo('debug') ?? ''); + } catch (HttpTransportException $e) { + $e->appendDebug($e->getResponse()->getInfo('debug') ?? ''); + + throw $e; + } + } +} diff --git a/vendor/symfony/mailer/Transport/AbstractTransport.php b/vendor/symfony/mailer/Transport/AbstractTransport.php new file mode 100644 index 0000000..9a1cd68 --- /dev/null +++ b/vendor/symfony/mailer/Transport/AbstractTransport.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Event\FailedMessageEvent; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Event\SentMessageEvent; +use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\BodyRendererInterface; +use Symfony\Component\Mime\RawMessage; + +/** + * @author Fabien Potencier + */ +abstract class AbstractTransport implements TransportInterface +{ + private ?EventDispatcherInterface $dispatcher; + private LoggerInterface $logger; + private float $rate = 0; + private float $lastSent = 0; + + public function __construct(?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->logger = $logger ?? new NullLogger(); + } + + /** + * Sets the maximum number of messages to send per second (0 to disable). + * + * @return $this + */ + public function setMaxPerSecond(float $rate): static + { + if (0 >= $rate) { + $rate = 0; + } + + $this->rate = $rate; + $this->lastSent = 0; + + return $this; + } + + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage + { + $message = clone $message; + $envelope = null !== $envelope ? clone $envelope : Envelope::create($message); + + try { + if (!$this->dispatcher) { + $sentMessage = new SentMessage($message, $envelope); + $this->doSend($sentMessage); + + return $sentMessage; + } + + $event = new MessageEvent($message, $envelope, (string) $this); + $this->dispatcher->dispatch($event); + if ($event->isRejected()) { + return null; + } + + $envelope = $event->getEnvelope(); + $message = $event->getMessage(); + + if ($message instanceof TemplatedEmail && !$message->isRendered()) { + throw new LogicException(sprintf('You must configure a "%s" when a "%s" instance has a text or HTML template set.', BodyRendererInterface::class, get_debug_type($message))); + } + + $sentMessage = new SentMessage($message, $envelope); + + try { + $this->doSend($sentMessage); + } catch (\Throwable $error) { + $this->dispatcher->dispatch(new FailedMessageEvent($message, $error)); + $this->checkThrottling(); + + throw $error; + } + + $this->dispatcher->dispatch(new SentMessageEvent($sentMessage)); + + return $sentMessage; + } finally { + $this->checkThrottling(); + } + } + + abstract protected function doSend(SentMessage $message): void; + + /** + * @param Address[] $addresses + * + * @return string[] + */ + protected function stringifyAddresses(array $addresses): array + { + return array_map(fn (Address $a) => $a->toString(), $addresses); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger; + } + + private function checkThrottling(): void + { + if (0 == $this->rate) { + return; + } + + $sleep = (1 / $this->rate) - (microtime(true) - $this->lastSent); + if (0 < $sleep) { + $this->logger->debug(sprintf('Email transport "%s" sleeps for %.2f seconds', __CLASS__, $sleep)); + usleep((int) ($sleep * 1000000)); + } + $this->lastSent = microtime(true); + } +} diff --git a/vendor/symfony/mailer/Transport/AbstractTransportFactory.php b/vendor/symfony/mailer/Transport/AbstractTransportFactory.php new file mode 100644 index 0000000..1e6b794 --- /dev/null +++ b/vendor/symfony/mailer/Transport/AbstractTransportFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Exception\IncompleteDsnException; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Konstantin Myakshin + */ +abstract class AbstractTransportFactory implements TransportFactoryInterface +{ + protected ?EventDispatcherInterface $dispatcher; + protected ?HttpClientInterface $client; + protected ?LoggerInterface $logger; + + public function __construct(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->client = $client; + $this->logger = $logger; + } + + public function supports(Dsn $dsn): bool + { + return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true); + } + + abstract protected function getSupportedSchemes(): array; + + protected function getUser(Dsn $dsn): string + { + return $dsn->getUser() ?? throw new IncompleteDsnException('User is not set.'); + } + + protected function getPassword(Dsn $dsn): string + { + return $dsn->getPassword() ?? throw new IncompleteDsnException('Password is not set.'); + } +} diff --git a/vendor/symfony/mailer/Transport/Dsn.php b/vendor/symfony/mailer/Transport/Dsn.php new file mode 100644 index 0000000..0cff6aa --- /dev/null +++ b/vendor/symfony/mailer/Transport/Dsn.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\InvalidArgumentException; + +/** + * @author Konstantin Myakshin + */ +final class Dsn +{ + private string $scheme; + private string $host; + private ?string $user; + private ?string $password; + private ?int $port; + private array $options; + + public function __construct(string $scheme, string $host, ?string $user = null, #[\SensitiveParameter] ?string $password = null, ?int $port = null, array $options = []) + { + $this->scheme = $scheme; + $this->host = $host; + $this->user = $user; + $this->password = $password; + $this->port = $port; + $this->options = $options; + } + + public static function fromString(#[\SensitiveParameter] string $dsn): self + { + if (false === $params = parse_url($dsn)) { + throw new InvalidArgumentException('The mailer DSN is invalid.'); + } + + if (!isset($params['scheme'])) { + throw new InvalidArgumentException('The mailer DSN must contain a scheme.'); + } + + if (!isset($params['host'])) { + throw new InvalidArgumentException('The mailer DSN must contain a host (use "default" by default).'); + } + + $user = '' !== ($params['user'] ?? '') ? rawurldecode($params['user']) : null; + $password = '' !== ($params['pass'] ?? '') ? rawurldecode($params['pass']) : null; + $port = $params['port'] ?? null; + parse_str($params['query'] ?? '', $query); + + return new self($params['scheme'], $params['host'], $user, $password, $port, $query); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getHost(): string + { + return $this->host; + } + + public function getUser(): ?string + { + return $this->user; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function getPort(?int $default = null): ?int + { + return $this->port ?? $default; + } + + public function getOption(string $key, mixed $default = null): mixed + { + return $this->options[$key] ?? $default; + } +} diff --git a/vendor/symfony/mailer/Transport/FailoverTransport.php b/vendor/symfony/mailer/Transport/FailoverTransport.php new file mode 100644 index 0000000..d6a7477 --- /dev/null +++ b/vendor/symfony/mailer/Transport/FailoverTransport.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +/** + * Uses several Transports using a failover algorithm. + * + * @author Fabien Potencier + */ +class FailoverTransport extends RoundRobinTransport +{ + private ?TransportInterface $currentTransport = null; + + protected function getNextTransport(): ?TransportInterface + { + if (null === $this->currentTransport || $this->isTransportDead($this->currentTransport)) { + $this->currentTransport = parent::getNextTransport(); + } + + return $this->currentTransport; + } + + protected function getInitialCursor(): int + { + return 0; + } + + protected function getNameSymbol(): string + { + return 'failover'; + } +} diff --git a/vendor/symfony/mailer/Transport/NativeTransportFactory.php b/vendor/symfony/mailer/Transport/NativeTransportFactory.php new file mode 100644 index 0000000..8afa53c --- /dev/null +++ b/vendor/symfony/mailer/Transport/NativeTransportFactory.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport; +use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; + +/** + * Factory that configures a transport (sendmail or SMTP) based on php.ini settings. + * + * @author Laurent VOULLEMIER + */ +final class NativeTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + if (!\in_array($dsn->getScheme(), $this->getSupportedSchemes(), true)) { + throw new UnsupportedSchemeException($dsn, 'native', $this->getSupportedSchemes()); + } + + if ($sendMailPath = ini_get('sendmail_path')) { + return new SendmailTransport($sendMailPath, $this->dispatcher, $this->logger); + } + + if ('\\' !== \DIRECTORY_SEPARATOR) { + throw new TransportException('sendmail_path is not configured in php.ini.'); + } + + // Only for windows hosts; at this point non-windows + // host have already thrown an exception or returned a transport + $host = ini_get('SMTP'); + $port = (int) ini_get('smtp_port'); + + if (!$host || !$port) { + throw new TransportException('smtp or smtp_port is not configured in php.ini.'); + } + + $socketStream = new SocketStream(); + $socketStream->setHost($host); + $socketStream->setPort($port); + if (465 !== $port) { + $socketStream->disableTls(); + } + + return new SmtpTransport($socketStream, $this->dispatcher, $this->logger); + } + + protected function getSupportedSchemes(): array + { + return ['native']; + } +} diff --git a/vendor/symfony/mailer/Transport/NullTransport.php b/vendor/symfony/mailer/Transport/NullTransport.php new file mode 100644 index 0000000..92fb82a --- /dev/null +++ b/vendor/symfony/mailer/Transport/NullTransport.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\SentMessage; + +/** + * Pretends messages have been sent, but just ignores them. + * + * @author Fabien Potencier + */ +final class NullTransport extends AbstractTransport +{ + protected function doSend(SentMessage $message): void + { + } + + public function __toString(): string + { + return 'null://'; + } +} diff --git a/vendor/symfony/mailer/Transport/NullTransportFactory.php b/vendor/symfony/mailer/Transport/NullTransportFactory.php new file mode 100644 index 0000000..4c45f39 --- /dev/null +++ b/vendor/symfony/mailer/Transport/NullTransportFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; + +/** + * @author Konstantin Myakshin + */ +final class NullTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + if ('null' === $dsn->getScheme()) { + return new NullTransport($this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes()); + } + + protected function getSupportedSchemes(): array + { + return ['null']; + } +} diff --git a/vendor/symfony/mailer/Transport/RoundRobinTransport.php b/vendor/symfony/mailer/Transport/RoundRobinTransport.php new file mode 100644 index 0000000..ac9709b --- /dev/null +++ b/vendor/symfony/mailer/Transport/RoundRobinTransport.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mime\RawMessage; + +/** + * Uses several Transports using a round robin algorithm. + * + * @author Fabien Potencier + */ +class RoundRobinTransport implements TransportInterface +{ + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $deadTransports; + private array $transports = []; + private int $retryPeriod; + private int $cursor = -1; + + /** + * @param TransportInterface[] $transports + */ + public function __construct(array $transports, int $retryPeriod = 60) + { + if (!$transports) { + throw new TransportException(sprintf('"%s" must have at least one transport configured.', static::class)); + } + + $this->transports = $transports; + $this->deadTransports = new \SplObjectStorage(); + $this->retryPeriod = $retryPeriod; + } + + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage + { + $exception = null; + + while ($transport = $this->getNextTransport()) { + try { + return $transport->send($message, $envelope); + } catch (TransportExceptionInterface $e) { + $exception ??= new TransportException('All transports failed.'); + $exception->appendDebug(sprintf("Transport \"%s\": %s\n", $transport, $e->getDebug())); + $this->deadTransports[$transport] = microtime(true); + } + } + + throw $exception ?? new TransportException('No transports found.'); + } + + public function __toString(): string + { + return $this->getNameSymbol().'('.implode(' ', array_map('strval', $this->transports)).')'; + } + + /** + * Rotates the transport list around and returns the first instance. + */ + protected function getNextTransport(): ?TransportInterface + { + if (-1 === $this->cursor) { + $this->cursor = $this->getInitialCursor(); + } + + $cursor = $this->cursor; + while (true) { + $transport = $this->transports[$cursor]; + + if (!$this->isTransportDead($transport)) { + break; + } + + if ((microtime(true) - $this->deadTransports[$transport]) > $this->retryPeriod) { + $this->deadTransports->detach($transport); + + break; + } + + if ($this->cursor === $cursor = $this->moveCursor($cursor)) { + return null; + } + } + + $this->cursor = $this->moveCursor($cursor); + + return $transport; + } + + protected function isTransportDead(TransportInterface $transport): bool + { + return $this->deadTransports->contains($transport); + } + + protected function getInitialCursor(): int + { + // the cursor initial value is randomized so that + // when are not in a daemon, we are still rotating the transports + return mt_rand(0, \count($this->transports) - 1); + } + + protected function getNameSymbol(): string + { + return 'roundrobin'; + } + + private function moveCursor(int $cursor): int + { + return ++$cursor >= \count($this->transports) ? 0 : $cursor; + } +} diff --git a/vendor/symfony/mailer/Transport/SendmailTransport.php b/vendor/symfony/mailer/Transport/SendmailTransport.php new file mode 100644 index 0000000..3add460 --- /dev/null +++ b/vendor/symfony/mailer/Transport/SendmailTransport.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport; +use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream; +use Symfony\Component\Mailer\Transport\Smtp\Stream\ProcessStream; +use Symfony\Component\Mime\RawMessage; + +/** + * SendmailTransport for sending mail through a Sendmail/Postfix (etc..) binary. + * + * Transport can be instantiated through SendmailTransportFactory or NativeTransportFactory: + * + * - SendmailTransportFactory to use most common sendmail path and recommended options + * - NativeTransportFactory when configuration is set via php.ini + * + * @author Fabien Potencier + * @author Chris Corbyn + */ +class SendmailTransport extends AbstractTransport +{ + private string $command = '/usr/sbin/sendmail -bs'; + private ProcessStream $stream; + private ?SmtpTransport $transport = null; + + /** + * Constructor. + * + * Supported modes are -bs and -t, with any additional flags desired. + * + * The recommended mode is "-bs" since it is interactive and failure notifications are hence possible. + * Note that the -t mode does not support error reporting and does not support Bcc properly (the Bcc headers are not removed). + * + * If using -t mode, you are strongly advised to include -oi or -i in the flags (like /usr/sbin/sendmail -oi -t) + * + * -f flag will be appended automatically if one is not present. + */ + public function __construct(?string $command = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) + { + parent::__construct($dispatcher, $logger); + + if (null !== $command) { + if (!str_contains($command, ' -bs') && !str_contains($command, ' -t')) { + throw new \InvalidArgumentException(sprintf('Unsupported sendmail command flags "%s"; must be one of "-bs" or "-t" but can include additional flags.', $command)); + } + + $this->command = $command; + } + + $this->stream = new ProcessStream(); + if (str_contains($this->command, ' -bs')) { + $this->stream->setCommand($this->command); + $this->stream->setInteractive(true); + $this->transport = new SmtpTransport($this->stream, $dispatcher, $logger); + } + } + + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage + { + if ($this->transport) { + return $this->transport->send($message, $envelope); + } + + return parent::send($message, $envelope); + } + + public function __toString(): string + { + if ($this->transport) { + return (string) $this->transport; + } + + return 'smtp://sendmail'; + } + + protected function doSend(SentMessage $message): void + { + $this->getLogger()->debug(sprintf('Email transport "%s" starting', __CLASS__)); + + $command = $this->command; + + if ($recipients = $message->getEnvelope()->getRecipients()) { + $command = str_replace(' -t', '', $command); + } + + if (!str_contains($command, ' -f')) { + $command .= ' -f'.escapeshellarg($message->getEnvelope()->getSender()->getEncodedAddress()); + } + + $chunks = AbstractStream::replace("\r\n", "\n", $message->toIterable()); + + if (!str_contains($command, ' -i') && !str_contains($command, ' -oi')) { + $chunks = AbstractStream::replace("\n.", "\n..", $chunks); + } + + foreach ($recipients as $recipient) { + $command .= ' '.escapeshellarg($recipient->getEncodedAddress()); + } + + $this->stream->setCommand($command); + $this->stream->initialize(); + foreach ($chunks as $chunk) { + $this->stream->write($chunk); + } + $this->stream->flush(); + $this->stream->terminate(); + + $this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__)); + } +} diff --git a/vendor/symfony/mailer/Transport/SendmailTransportFactory.php b/vendor/symfony/mailer/Transport/SendmailTransportFactory.php new file mode 100644 index 0000000..6d977e7 --- /dev/null +++ b/vendor/symfony/mailer/Transport/SendmailTransportFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; + +/** + * @author Konstantin Myakshin + */ +final class SendmailTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + if ('sendmail+smtp' === $dsn->getScheme() || 'sendmail' === $dsn->getScheme()) { + return new SendmailTransport($dsn->getOption('command'), $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn, 'sendmail', $this->getSupportedSchemes()); + } + + protected function getSupportedSchemes(): array + { + return ['sendmail', 'sendmail+smtp']; + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Auth/AuthenticatorInterface.php b/vendor/symfony/mailer/Transport/Smtp/Auth/AuthenticatorInterface.php new file mode 100644 index 0000000..98ea2d4 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Auth/AuthenticatorInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Auth; + +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; + +/** + * An Authentication mechanism. + * + * @author Chris Corbyn + */ +interface AuthenticatorInterface +{ + /** + * Tries to authenticate the user. + * + * @throws TransportExceptionInterface + */ + public function authenticate(EsmtpTransport $client): void; + + /** + * Gets the name of the AUTH mechanism this Authenticator handles. + */ + public function getAuthKeyword(): string; +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Auth/CramMd5Authenticator.php b/vendor/symfony/mailer/Transport/Smtp/Auth/CramMd5Authenticator.php new file mode 100644 index 0000000..79cddc4 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Auth/CramMd5Authenticator.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Auth; + +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; + +/** + * Handles CRAM-MD5 authentication. + * + * @author Chris Corbyn + */ +class CramMd5Authenticator implements AuthenticatorInterface +{ + public function getAuthKeyword(): string + { + return 'CRAM-MD5'; + } + + /** + * @see https://www.ietf.org/rfc/rfc4954.txt + */ + public function authenticate(EsmtpTransport $client): void + { + $challenge = $client->executeCommand("AUTH CRAM-MD5\r\n", [334]); + $challenge = base64_decode(substr($challenge, 4)); + $message = base64_encode($client->getUsername().' '.$this->getResponse($client->getPassword(), $challenge)); + $client->executeCommand(sprintf("%s\r\n", $message), [235]); + } + + /** + * Generates a CRAM-MD5 response from a server challenge. + */ + private function getResponse(#[\SensitiveParameter] string $secret, string $challenge): string + { + if (!$secret) { + throw new InvalidArgumentException('A non-empty secret is required.'); + } + + if (\strlen($secret) > 64) { + $secret = pack('H32', md5($secret)); + } + + if (\strlen($secret) < 64) { + $secret = str_pad($secret, 64, \chr(0)); + } + + $kipad = substr($secret, 0, 64) ^ str_repeat(\chr(0x36), 64); + $kopad = substr($secret, 0, 64) ^ str_repeat(\chr(0x5C), 64); + + $inner = pack('H32', md5($kipad.$challenge)); + $digest = md5($kopad.$inner); + + return $digest; + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Auth/LoginAuthenticator.php b/vendor/symfony/mailer/Transport/Smtp/Auth/LoginAuthenticator.php new file mode 100644 index 0000000..e0b7d57 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Auth/LoginAuthenticator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Auth; + +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; + +/** + * Handles LOGIN authentication. + * + * @author Chris Corbyn + */ +class LoginAuthenticator implements AuthenticatorInterface +{ + public function getAuthKeyword(): string + { + return 'LOGIN'; + } + + /** + * @see https://www.ietf.org/rfc/rfc4954.txt + */ + public function authenticate(EsmtpTransport $client): void + { + $client->executeCommand("AUTH LOGIN\r\n", [334]); + $client->executeCommand(sprintf("%s\r\n", base64_encode($client->getUsername())), [334]); + $client->executeCommand(sprintf("%s\r\n", base64_encode($client->getPassword())), [235]); + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Auth/PlainAuthenticator.php b/vendor/symfony/mailer/Transport/Smtp/Auth/PlainAuthenticator.php new file mode 100644 index 0000000..6b680b5 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Auth/PlainAuthenticator.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Auth; + +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; + +/** + * Handles PLAIN authentication. + * + * @author Chris Corbyn + */ +class PlainAuthenticator implements AuthenticatorInterface +{ + public function getAuthKeyword(): string + { + return 'PLAIN'; + } + + /** + * @see https://www.ietf.org/rfc/rfc4954.txt + */ + public function authenticate(EsmtpTransport $client): void + { + $client->executeCommand(sprintf("AUTH PLAIN %s\r\n", base64_encode($client->getUsername().\chr(0).$client->getUsername().\chr(0).$client->getPassword())), [235]); + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php b/vendor/symfony/mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php new file mode 100644 index 0000000..c3aaa49 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Auth; + +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; + +/** + * Handles XOAUTH2 authentication. + * + * @author xu.li + * + * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol + */ +class XOAuth2Authenticator implements AuthenticatorInterface +{ + public function getAuthKeyword(): string + { + return 'XOAUTH2'; + } + + /** + * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism + */ + public function authenticate(EsmtpTransport $client): void + { + $client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$client->getPassword()."\1\1")."\r\n", [235]); + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/EsmtpTransport.php b/vendor/symfony/mailer/Transport/Smtp/EsmtpTransport.php new file mode 100644 index 0000000..b341d34 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/EsmtpTransport.php @@ -0,0 +1,244 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Exception\UnexpectedResponseException; +use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface; +use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream; +use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; + +/** + * Sends Emails over SMTP with ESMTP support. + * + * @author Fabien Potencier + * @author Chris Corbyn + */ +class EsmtpTransport extends SmtpTransport +{ + private array $authenticators = []; + private string $username = ''; + private string $password = ''; + private array $capabilities; + private bool $autoTls = true; + + public function __construct(string $host = 'localhost', int $port = 0, ?bool $tls = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null, ?AbstractStream $stream = null, ?array $authenticators = null) + { + parent::__construct($stream, $dispatcher, $logger); + + if (null === $authenticators) { + // fallback to default authenticators + // order is important here (roughly most secure and popular first) + $authenticators = [ + new Auth\CramMd5Authenticator(), + new Auth\LoginAuthenticator(), + new Auth\PlainAuthenticator(), + new Auth\XOAuth2Authenticator(), + ]; + } + $this->setAuthenticators($authenticators); + + /** @var SocketStream $stream */ + $stream = $this->getStream(); + + if (null === $tls) { + if (465 === $port) { + $tls = true; + } else { + $tls = \defined('OPENSSL_VERSION_NUMBER') && 0 === $port && 'localhost' !== $host; + } + } + if (!$tls) { + $stream->disableTls(); + } + if (0 === $port) { + $port = $tls ? 465 : 25; + } + + $stream->setHost($host); + $stream->setPort($port); + } + + /** + * @return $this + */ + public function setUsername(string $username): static + { + $this->username = $username; + + return $this; + } + + public function getUsername(): string + { + return $this->username; + } + + /** + * @return $this + */ + public function setPassword(#[\SensitiveParameter] string $password): static + { + $this->password = $password; + + return $this; + } + + public function getPassword(): string + { + return $this->password; + } + + /** + * @return $this + */ + public function setAutoTls(bool $autoTls): static + { + $this->autoTls = $autoTls; + + return $this; + } + + public function isAutoTls(): bool + { + return $this->autoTls; + } + + public function setAuthenticators(array $authenticators): void + { + $this->authenticators = []; + foreach ($authenticators as $authenticator) { + $this->addAuthenticator($authenticator); + } + } + + public function addAuthenticator(AuthenticatorInterface $authenticator): void + { + $this->authenticators[] = $authenticator; + } + + public function executeCommand(string $command, array $codes): string + { + return [250] === $codes && str_starts_with($command, 'HELO ') ? $this->doEhloCommand() : parent::executeCommand($command, $codes); + } + + final protected function getCapabilities(): array + { + return $this->capabilities; + } + + private function doEhloCommand(): string + { + try { + $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]); + } catch (TransportExceptionInterface $e) { + try { + return parent::executeCommand(sprintf("HELO %s\r\n", $this->getLocalDomain()), [250]); + } catch (TransportExceptionInterface $ex) { + if (!$ex->getCode()) { + throw $e; + } + + throw $ex; + } + } + + $this->capabilities = $this->parseCapabilities($response); + + /** @var SocketStream $stream */ + $stream = $this->getStream(); + // WARNING: !$stream->isTLS() is right, 100% sure :) + // if you think that the ! should be removed, read the code again + // if doing so "fixes" your issue then it probably means your SMTP server behaves incorrectly or is wrongly configured + if ($this->autoTls && !$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $this->capabilities)) { + $this->executeCommand("STARTTLS\r\n", [220]); + + if (!$stream->startTLS()) { + throw new TransportException('Unable to connect with STARTTLS.'); + } + + $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]); + $this->capabilities = $this->parseCapabilities($response); + } + + if (\array_key_exists('AUTH', $this->capabilities)) { + $this->handleAuth($this->capabilities['AUTH']); + } + + return $response; + } + + private function parseCapabilities(string $ehloResponse): array + { + $capabilities = []; + $lines = explode("\r\n", trim($ehloResponse)); + array_shift($lines); + foreach ($lines as $line) { + if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) { + $value = strtoupper(ltrim($matches[2], ' =')); + $capabilities[strtoupper($matches[1])] = $value ? explode(' ', $value) : []; + } + } + + return $capabilities; + } + + private function handleAuth(array $modes): void + { + if (!$this->username) { + return; + } + + $code = null; + $authNames = []; + $errors = []; + $modes = array_map('strtolower', $modes); + foreach ($this->authenticators as $authenticator) { + if (!\in_array(strtolower($authenticator->getAuthKeyword()), $modes, true)) { + continue; + } + + $code = null; + $authNames[] = $authenticator->getAuthKeyword(); + try { + $authenticator->authenticate($this); + + return; + } catch (UnexpectedResponseException $e) { + $code = $e->getCode(); + + try { + $this->executeCommand("RSET\r\n", [250]); + } catch (TransportExceptionInterface) { + // ignore this exception as it probably means that the server error was final + } + + // keep the error message, but tries the other authenticators + $errors[$authenticator->getAuthKeyword()] = $e->getMessage(); + } + } + + if (!$authNames) { + throw new TransportException(sprintf('Failed to find an authenticator supported by the SMTP server, which currently supports: "%s".', implode('", "', $modes)), $code ?: 504); + } + + $message = sprintf('Failed to authenticate on SMTP server with username "%s" using the following authenticators: "%s".', $this->username, implode('", "', $authNames)); + foreach ($errors as $name => $error) { + $message .= sprintf(' Authenticator "%s" returned "%s".', $name, $error); + } + + throw new TransportException($message, $code ?: 535); + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/EsmtpTransportFactory.php b/vendor/symfony/mailer/Transport/Smtp/EsmtpTransportFactory.php new file mode 100644 index 0000000..9df0b95 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/EsmtpTransportFactory.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp; + +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class EsmtpTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + $autoTls = '' === $dsn->getOption('auto_tls') || filter_var($dsn->getOption('auto_tls', true), \FILTER_VALIDATE_BOOL); + $tls = 'smtps' === $dsn->getScheme() ? true : ($autoTls ? null : false); + $port = $dsn->getPort(0); + $host = $dsn->getHost(); + + $transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger); + $transport->setAutoTls($autoTls); + + /** @var SocketStream $stream */ + $stream = $transport->getStream(); + $streamOptions = $stream->getStreamOptions(); + + if ('' !== $dsn->getOption('verify_peer') && !filter_var($dsn->getOption('verify_peer', true), \FILTER_VALIDATE_BOOL)) { + $streamOptions['ssl']['verify_peer'] = false; + $streamOptions['ssl']['verify_peer_name'] = false; + } + + if (null !== $peerFingerprint = $dsn->getOption('peer_fingerprint')) { + $streamOptions['ssl']['peer_fingerprint'] = $peerFingerprint; + } + + $stream->setStreamOptions($streamOptions); + + if ($user = $dsn->getUser()) { + $transport->setUsername($user); + } + + if ($password = $dsn->getPassword()) { + $transport->setPassword($password); + } + + if (null !== ($localDomain = $dsn->getOption('local_domain'))) { + $transport->setLocalDomain($localDomain); + } + + if (null !== ($maxPerSecond = $dsn->getOption('max_per_second'))) { + $transport->setMaxPerSecond((float) $maxPerSecond); + } + + if (null !== ($restartThreshold = $dsn->getOption('restart_threshold'))) { + $transport->setRestartThreshold((int) $restartThreshold, (int) $dsn->getOption('restart_threshold_sleep', 0)); + } + + if (null !== ($pingThreshold = $dsn->getOption('ping_threshold'))) { + $transport->setPingThreshold((int) $pingThreshold); + } + + return $transport; + } + + protected function getSupportedSchemes(): array + { + return ['smtp', 'smtps']; + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/SmtpTransport.php b/vendor/symfony/mailer/Transport/Smtp/SmtpTransport.php new file mode 100644 index 0000000..2d820c1 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/SmtpTransport.php @@ -0,0 +1,384 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Exception\UnexpectedResponseException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\AbstractTransport; +use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream; +use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; +use Symfony\Component\Mime\RawMessage; + +/** + * Sends emails over SMTP. + * + * @author Fabien Potencier + * @author Chris Corbyn + */ +class SmtpTransport extends AbstractTransport +{ + private bool $started = false; + private int $restartThreshold = 100; + private int $restartThresholdSleep = 0; + private int $restartCounter = 0; + private int $pingThreshold = 100; + private float $lastMessageTime = 0; + private AbstractStream $stream; + private string $domain = '[127.0.0.1]'; + + public function __construct(?AbstractStream $stream = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) + { + parent::__construct($dispatcher, $logger); + + $this->stream = $stream ?? new SocketStream(); + } + + public function getStream(): AbstractStream + { + return $this->stream; + } + + /** + * Sets the maximum number of messages to send before re-starting the transport. + * + * By default, the threshold is set to 100 (and no sleep at restart). + * + * @param int $threshold The maximum number of messages (0 to disable) + * @param int $sleep The number of seconds to sleep between stopping and re-starting the transport + * + * @return $this + */ + public function setRestartThreshold(int $threshold, int $sleep = 0): static + { + $this->restartThreshold = $threshold; + $this->restartThresholdSleep = $sleep; + + return $this; + } + + /** + * Sets the minimum number of seconds required between two messages, before the server is pinged. + * If the transport wants to send a message and the time since the last message exceeds the specified threshold, + * the transport will ping the server first (NOOP command) to check if the connection is still alive. + * Otherwise the message will be sent without pinging the server first. + * + * Do not set the threshold too low, as the SMTP server may drop the connection if there are too many + * non-mail commands (like pinging the server with NOOP). + * + * By default, the threshold is set to 100 seconds. + * + * @param int $seconds The minimum number of seconds between two messages required to ping the server + * + * @return $this + */ + public function setPingThreshold(int $seconds): static + { + $this->pingThreshold = $seconds; + + return $this; + } + + /** + * Sets the name of the local domain that will be used in HELO. + * + * This should be a fully-qualified domain name and should be truly the domain + * you're using. + * + * If your server does not have a domain name, use the IP address. This will + * automatically be wrapped in square brackets as described in RFC 5321, + * section 4.1.3. + * + * @return $this + */ + public function setLocalDomain(string $domain): static + { + if ('' !== $domain && '[' !== $domain[0]) { + if (filter_var($domain, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + $domain = '['.$domain.']'; + } elseif (filter_var($domain, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + $domain = '[IPv6:'.$domain.']'; + } + } + + $this->domain = $domain; + + return $this; + } + + /** + * Gets the name of the domain that will be used in HELO. + * + * If an IP address was specified, this will be returned wrapped in square + * brackets as described in RFC 5321, section 4.1.3. + */ + public function getLocalDomain(): string + { + return $this->domain; + } + + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage + { + try { + $message = parent::send($message, $envelope); + } catch (TransportExceptionInterface $e) { + if ($this->started) { + try { + $this->executeCommand("RSET\r\n", [250]); + } catch (TransportExceptionInterface) { + // ignore this exception as it probably means that the server error was final + } + } + + throw $e; + } + + $this->checkRestartThreshold(); + + return $message; + } + + protected function parseMessageId(string $mtaResult): string + { + $regexps = [ + '/250 Ok (?P[0-9a-f-]+)\r?$/mis', + '/250 Ok:? queued as (?P[A-Z0-9]+)\r?$/mis', + ]; + $matches = []; + foreach ($regexps as $regexp) { + if (preg_match($regexp, $mtaResult, $matches)) { + return $matches['id']; + } + } + + return ''; + } + + public function __toString(): string + { + if ($this->stream instanceof SocketStream) { + $name = sprintf('smtp%s://%s', ($tls = $this->stream->isTLS()) ? 's' : '', $this->stream->getHost()); + $port = $this->stream->getPort(); + if (!(25 === $port || ($tls && 465 === $port))) { + $name .= ':'.$port; + } + + return $name; + } + + return 'smtp://sendmail'; + } + + /** + * Runs a command against the stream, expecting the given response codes. + * + * @param int[] $codes + * + * @throws TransportException when an invalid response if received + */ + public function executeCommand(string $command, array $codes): string + { + $this->stream->write($command); + $response = $this->getFullResponse(); + $this->assertResponseCode($response, $codes); + + return $response; + } + + protected function doSend(SentMessage $message): void + { + if (microtime(true) - $this->lastMessageTime > $this->pingThreshold) { + $this->ping(); + } + + if (!$this->started) { + $this->start(); + } + + try { + $envelope = $message->getEnvelope(); + $this->doMailFromCommand($envelope->getSender()->getEncodedAddress()); + foreach ($envelope->getRecipients() as $recipient) { + $this->doRcptToCommand($recipient->getEncodedAddress()); + } + + $this->executeCommand("DATA\r\n", [354]); + try { + foreach (AbstractStream::replace("\r\n.", "\r\n..", $message->toIterable()) as $chunk) { + $this->stream->write($chunk, false); + } + $this->stream->flush(); + } catch (TransportExceptionInterface $e) { + throw $e; + } catch (\Exception $e) { + $this->stream->terminate(); + $this->started = false; + $this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__)); + throw $e; + } + $mtaResult = $this->executeCommand("\r\n.\r\n", [250]); + $message->appendDebug($this->stream->getDebug()); + $this->lastMessageTime = microtime(true); + + if ($mtaResult && $messageId = $this->parseMessageId($mtaResult)) { + $message->setMessageId($messageId); + } + } catch (TransportExceptionInterface $e) { + $e->appendDebug($this->stream->getDebug()); + $this->lastMessageTime = 0; + throw $e; + } + } + + private function doHeloCommand(): void + { + $this->executeCommand(sprintf("HELO %s\r\n", $this->domain), [250]); + } + + private function doMailFromCommand(string $address): void + { + $this->executeCommand(sprintf("MAIL FROM:<%s>\r\n", $address), [250]); + } + + private function doRcptToCommand(string $address): void + { + $this->executeCommand(sprintf("RCPT TO:<%s>\r\n", $address), [250, 251, 252]); + } + + public function start(): void + { + if ($this->started) { + return; + } + + $this->getLogger()->debug(sprintf('Email transport "%s" starting', __CLASS__)); + + $this->stream->initialize(); + $this->assertResponseCode($this->getFullResponse(), [220]); + $this->doHeloCommand(); + $this->started = true; + $this->lastMessageTime = 0; + + $this->getLogger()->debug(sprintf('Email transport "%s" started', __CLASS__)); + } + + /** + * Manually disconnect from the SMTP server. + * + * In most cases this is not necessary since the disconnect happens automatically on termination. + * In cases of long-running scripts, this might however make sense to avoid keeping an open + * connection to the SMTP server in between sending emails. + */ + public function stop(): void + { + if (!$this->started) { + return; + } + + $this->getLogger()->debug(sprintf('Email transport "%s" stopping', __CLASS__)); + + try { + $this->executeCommand("QUIT\r\n", [221]); + } catch (TransportExceptionInterface) { + } finally { + $this->stream->terminate(); + $this->started = false; + $this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__)); + } + } + + private function ping(): void + { + if (!$this->started) { + return; + } + + try { + $this->executeCommand("NOOP\r\n", [250]); + } catch (TransportExceptionInterface) { + $this->stop(); + } + } + + /** + * @throws TransportException if a response code is incorrect + */ + private function assertResponseCode(string $response, array $codes): void + { + if (!$codes) { + throw new LogicException('You must set the expected response code.'); + } + + [$code] = sscanf($response, '%3d'); + $valid = \in_array($code, $codes); + + if (!$valid || !$response) { + $codeStr = $code ? sprintf('code "%s"', $code) : 'empty code'; + $responseStr = $response ? sprintf(', with message "%s"', trim($response)) : ''; + + throw new UnexpectedResponseException(sprintf('Expected response code "%s" but got ', implode('/', $codes)).$codeStr.$responseStr.'.', $code ?: 0); + } + } + + private function getFullResponse(): string + { + $response = ''; + do { + $line = $this->stream->readLine(); + $response .= $line; + } while ($line && isset($line[3]) && ' ' !== $line[3]); + + return $response; + } + + private function checkRestartThreshold(): void + { + // when using sendmail via non-interactive mode, the transport is never "started" + if (!$this->started) { + return; + } + + ++$this->restartCounter; + if ($this->restartCounter < $this->restartThreshold) { + return; + } + + $this->stop(); + if (0 < $sleep = $this->restartThresholdSleep) { + $this->getLogger()->debug(sprintf('Email transport "%s" sleeps for %d seconds after stopping', __CLASS__, $sleep)); + + sleep($sleep); + } + $this->start(); + $this->restartCounter = 0; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->stop(); + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Stream/AbstractStream.php b/vendor/symfony/mailer/Transport/Smtp/Stream/AbstractStream.php new file mode 100644 index 0000000..9a97660 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Stream/AbstractStream.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Stream; + +use Symfony\Component\Mailer\Exception\TransportException; + +/** + * A stream supporting remote sockets and local processes. + * + * @author Fabien Potencier + * @author Nicolas Grekas + * @author Chris Corbyn + * + * @internal + */ +abstract class AbstractStream +{ + /** @var resource|null */ + protected $stream; + /** @var resource|null */ + protected $in; + /** @var resource|null */ + protected $out; + protected $err; + + private string $debug = ''; + + public function write(string $bytes, bool $debug = true): void + { + if ($debug) { + $timestamp = date('c'); + foreach (explode("\n", trim($bytes)) as $line) { + $this->debug .= sprintf("[%s] > %s\n", $timestamp, $line); + } + } + + $bytesToWrite = \strlen($bytes); + $totalBytesWritten = 0; + while ($totalBytesWritten < $bytesToWrite) { + $bytesWritten = @fwrite($this->in, substr($bytes, $totalBytesWritten)); + if (false === $bytesWritten || 0 === $bytesWritten) { + throw new TransportException('Unable to write bytes on the wire.'); + } + + $totalBytesWritten += $bytesWritten; + } + } + + /** + * Flushes the contents of the stream (empty it) and set the internal pointer to the beginning. + */ + public function flush(): void + { + fflush($this->in); + } + + /** + * Performs any initialization needed. + */ + abstract public function initialize(): void; + + public function terminate(): void + { + $this->stream = $this->err = $this->out = $this->in = null; + } + + public function readLine(): string + { + if (feof($this->out)) { + return ''; + } + + $line = @fgets($this->out); + if ('' === $line || false === $line) { + $metas = stream_get_meta_data($this->out); + if ($metas['timed_out']) { + throw new TransportException(sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription())); + } + if ($metas['eof']) { + throw new TransportException(sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription())); + } + if (false === $line) { + throw new TransportException(sprintf('Unable to read from connection to "%s": ', $this->getReadConnectionDescription()).error_get_last()['message']); + } + } + + $this->debug .= sprintf('[%s] < %s', date('c'), $line); + + return $line; + } + + public function getDebug(): string + { + $debug = $this->debug; + $this->debug = ''; + + return $debug; + } + + public static function replace(string $from, string $to, iterable $chunks): \Generator + { + if ('' === $from) { + yield from $chunks; + + return; + } + + $carry = ''; + $fromLen = \strlen($from); + + foreach ($chunks as $chunk) { + if ('' === $chunk = $carry.$chunk) { + continue; + } + + if (str_contains($chunk, $from)) { + $chunk = explode($from, $chunk); + $carry = array_pop($chunk); + + yield implode($to, $chunk).$to; + } else { + $carry = $chunk; + } + + if (\strlen($carry) > $fromLen) { + yield substr($carry, 0, -$fromLen); + $carry = substr($carry, -$fromLen); + } + } + + if ('' !== $carry) { + yield $carry; + } + } + + abstract protected function getReadConnectionDescription(): string; +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Stream/ProcessStream.php b/vendor/symfony/mailer/Transport/Smtp/Stream/ProcessStream.php new file mode 100644 index 0000000..e635147 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Stream/ProcessStream.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Stream; + +use Symfony\Component\Mailer\Exception\TransportException; + +/** + * A stream supporting local processes. + * + * @author Fabien Potencier + * @author Chris Corbyn + * + * @internal + */ +final class ProcessStream extends AbstractStream +{ + private string $command; + private bool $interactive = false; + + public function setCommand(string $command): void + { + $this->command = $command; + } + + public function setInteractive(bool $interactive): void + { + $this->interactive = $interactive; + } + + public function initialize(): void + { + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', '\\' === \DIRECTORY_SEPARATOR ? 'a' : 'w'], + ]; + $pipes = []; + $this->stream = proc_open($this->command, $descriptorSpec, $pipes); + stream_set_blocking($pipes[2], false); + if ($err = stream_get_contents($pipes[2])) { + throw new TransportException('Process could not be started: '.$err); + } + $this->in = &$pipes[0]; + $this->out = &$pipes[1]; + $this->err = &$pipes[2]; + } + + public function terminate(): void + { + if (null !== $this->stream) { + fclose($this->in); + $out = stream_get_contents($this->out); + fclose($this->out); + $err = stream_get_contents($this->err); + fclose($this->err); + if (0 !== $exitCode = proc_close($this->stream)) { + $errorMessage = 'Process failed with exit code '.$exitCode.': '.$out.$err; + } + } + + parent::terminate(); + + if (!$this->interactive && isset($errorMessage)) { + throw new TransportException($errorMessage); + } + } + + protected function getReadConnectionDescription(): string + { + return 'process '.$this->command; + } +} diff --git a/vendor/symfony/mailer/Transport/Smtp/Stream/SocketStream.php b/vendor/symfony/mailer/Transport/Smtp/Stream/SocketStream.php new file mode 100644 index 0000000..49e6ea4 --- /dev/null +++ b/vendor/symfony/mailer/Transport/Smtp/Stream/SocketStream.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Stream; + +use Symfony\Component\Mailer\Exception\TransportException; + +/** + * A stream supporting remote sockets. + * + * @author Fabien Potencier + * @author Chris Corbyn + * + * @internal + */ +final class SocketStream extends AbstractStream +{ + private string $url; + private string $host = 'localhost'; + private int $port = 465; + private float $timeout; + private bool $tls = true; + private ?string $sourceIp = null; + private array $streamContextOptions = []; + + /** + * @return $this + */ + public function setTimeout(float $timeout): static + { + $this->timeout = $timeout; + + return $this; + } + + public function getTimeout(): float + { + return $this->timeout ?? (float) \ini_get('default_socket_timeout'); + } + + /** + * Literal IPv6 addresses should be wrapped in square brackets. + * + * @return $this + */ + public function setHost(string $host): static + { + $this->host = $host; + + return $this; + } + + public function getHost(): string + { + return $this->host; + } + + /** + * @return $this + */ + public function setPort(int $port): static + { + $this->port = $port; + + return $this; + } + + public function getPort(): int + { + return $this->port; + } + + /** + * Sets the TLS/SSL on the socket (disables STARTTLS). + * + * @return $this + */ + public function disableTls(): static + { + $this->tls = false; + + return $this; + } + + public function isTLS(): bool + { + return $this->tls; + } + + /** + * @return $this + */ + public function setStreamOptions(array $options): static + { + $this->streamContextOptions = $options; + + return $this; + } + + public function getStreamOptions(): array + { + return $this->streamContextOptions; + } + + /** + * Sets the source IP. + * + * IPv6 addresses should be wrapped in square brackets. + * + * @return $this + */ + public function setSourceIp(string $ip): static + { + $this->sourceIp = $ip; + + return $this; + } + + /** + * Returns the IP used to connect to the destination. + */ + public function getSourceIp(): ?string + { + return $this->sourceIp; + } + + public function initialize(): void + { + $this->url = $this->host.':'.$this->port; + if ($this->tls) { + $this->url = 'ssl://'.$this->url; + } + $options = []; + if ($this->sourceIp) { + $options['socket']['bindto'] = $this->sourceIp.':0'; + } + if ($this->streamContextOptions) { + $options = array_merge($options, $this->streamContextOptions); + } + // do it unconditionally as it will be used by STARTTLS as well if supported + $options['ssl']['crypto_method'] ??= \STREAM_CRYPTO_METHOD_TLS_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + $streamContext = stream_context_create($options); + + $timeout = $this->getTimeout(); + set_error_handler(function ($type, $msg) { + throw new TransportException(sprintf('Connection could not be established with host "%s": ', $this->url).$msg); + }); + try { + $this->stream = stream_socket_client($this->url, $errno, $errstr, $timeout, \STREAM_CLIENT_CONNECT, $streamContext); + } finally { + restore_error_handler(); + } + + stream_set_blocking($this->stream, true); + stream_set_timeout($this->stream, (int) $timeout, (int) (($timeout - (int) $timeout) * 1000000)); + $this->in = &$this->stream; + $this->out = &$this->stream; + } + + public function startTLS(): bool + { + set_error_handler(function ($type, $msg) { + throw new TransportException('Unable to connect with STARTTLS: '.$msg); + }); + try { + return stream_socket_enable_crypto($this->stream, true); + } finally { + restore_error_handler(); + } + } + + public function terminate(): void + { + if (null !== $this->stream) { + fclose($this->stream); + } + + parent::terminate(); + } + + protected function getReadConnectionDescription(): string + { + return $this->url; + } +} diff --git a/vendor/symfony/mailer/Transport/TransportFactoryInterface.php b/vendor/symfony/mailer/Transport/TransportFactoryInterface.php new file mode 100644 index 0000000..9785ae8 --- /dev/null +++ b/vendor/symfony/mailer/Transport/TransportFactoryInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\IncompleteDsnException; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; + +/** + * @author Konstantin Myakshin + */ +interface TransportFactoryInterface +{ + /** + * @throws UnsupportedSchemeException + * @throws IncompleteDsnException + */ + public function create(Dsn $dsn): TransportInterface; + + public function supports(Dsn $dsn): bool; +} diff --git a/vendor/symfony/mailer/Transport/TransportInterface.php b/vendor/symfony/mailer/Transport/TransportInterface.php new file mode 100644 index 0000000..01570ca --- /dev/null +++ b/vendor/symfony/mailer/Transport/TransportInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mime\RawMessage; + +/** + * Interface for all mailer transports. + * + * When sending emails, you should prefer MailerInterface implementations + * as they allow asynchronous sending. + * + * @author Fabien Potencier + */ +interface TransportInterface extends \Stringable +{ + /** + * @throws TransportExceptionInterface + */ + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage; +} diff --git a/vendor/symfony/mailer/Transport/Transports.php b/vendor/symfony/mailer/Transport/Transports.php new file mode 100644 index 0000000..f02b9bc --- /dev/null +++ b/vendor/symfony/mailer/Transport/Transports.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +/** + * @author Fabien Potencier + */ +final class Transports implements TransportInterface +{ + /** + * @var array + */ + private array $transports = []; + private TransportInterface $default; + + /** + * @param iterable $transports + */ + public function __construct(iterable $transports) + { + foreach ($transports as $name => $transport) { + $this->default ??= $transport; + $this->transports[$name] = $transport; + } + + if (!$this->transports) { + throw new LogicException(sprintf('"%s" must have at least one transport configured.', __CLASS__)); + } + } + + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage + { + /** @var Message $message */ + if (RawMessage::class === $message::class || !$message->getHeaders()->has('X-Transport')) { + return $this->default->send($message, $envelope); + } + + $headers = $message->getHeaders(); + $transport = $headers->get('X-Transport')->getBody(); + $headers->remove('X-Transport'); + + if (!isset($this->transports[$transport])) { + throw new InvalidArgumentException(sprintf('The "%s" transport does not exist (available transports: "%s").', $transport, implode('", "', array_keys($this->transports)))); + } + + try { + return $this->transports[$transport]->send($message, $envelope); + } catch (\Throwable $e) { + $headers->addTextHeader('X-Transport', $transport); + + throw $e; + } + } + + public function __toString(): string + { + return '['.implode(',', array_keys($this->transports)).']'; + } +} diff --git a/vendor/symfony/mailer/composer.json b/vendor/symfony/mailer/composer.json new file mode 100644 index 0000000..76e2d9d --- /dev/null +++ b/vendor/symfony/mailer/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/mailer", + "type": "library", + "description": "Helps sending emails", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "egulias/email-validator": "^2.1.10|^3|^4", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Mailer\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/maker-bundle/LICENSE b/vendor/symfony/maker-bundle/LICENSE new file mode 100644 index 0000000..9e936ec --- /dev/null +++ b/vendor/symfony/maker-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 Fabien Potencier + +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. diff --git a/vendor/symfony/maker-bundle/composer.json b/vendor/symfony/maker-bundle/composer.json new file mode 100644 index 0000000..3b9010d --- /dev/null +++ b/vendor/symfony/maker-bundle/composer.json @@ -0,0 +1,76 @@ +{ + "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", + "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", + "name": "symfony/maker-bundle", + "type": "symfony-bundle", + "license": "MIT", + "keywords": ["generator", "code generator", "scaffolding", "scaffold", "dev"], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=8.1", + "doctrine/inflector": "^2.0", + "nikic/php-parser": "^4.18|^5.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.2|^3", + "symfony/filesystem": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/doctrine-bundle": "^2.5.0", + "doctrine/orm": "^2.15|^3", + "symfony/http-client": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.0|^4.x-dev" + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + }, + "conflict": { + "doctrine/orm": "<2.15", + "doctrine/doctrine-bundle": "<2.10" + }, + "autoload": { + "psr-4": { "Symfony\\Bundle\\MakerBundle\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "Symfony\\Bundle\\MakerBundle\\Tests\\": "tests/" } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "tools:upgrade": [ + "@tools:upgrade:php-cs-fixer", + "@tools:upgrade:phpstan", + "@tools:upgrade:twigcs" + ], + "tools:upgrade:php-cs-fixer": "composer upgrade -W -d tools/php-cs-fixer", + "tools:upgrade:phpstan": "composer upgrade -W -d tools/phpstan", + "tools:upgrade:twigcs": "composer upgrade -W -d tools/twigcs", + "tools:run": [ + "@tools:run:php-cs-fixer", + "@tools:run:phpstan", + "@tools:run:twigcs" + ], + "tools:run:php-cs-fixer": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix", + "tools:run:phpstan": "tools/phpstan/vendor/bin/phpstan --memory-limit=1G", + "tools:run:twigcs": "tools/twigcs/vendor/bin/twigcs --config tools/twigcs/.twigcs.dist.php" + } +} diff --git a/vendor/symfony/maker-bundle/phpstan.dist.neon b/vendor/symfony/maker-bundle/phpstan.dist.neon new file mode 100644 index 0000000..50fff54 --- /dev/null +++ b/vendor/symfony/maker-bundle/phpstan.dist.neon @@ -0,0 +1,26 @@ +parameters: + level: 6 + bootstrapFiles: + - vendor/autoload.php + - tools/phpstan/includes/vendor/autoload.php + paths: + - src/Maker + - tests/Command + - tests/Docker + - tests/Maker + excludePaths: + - tests/Doctrine/fixtures + - tests/fixtures + - tests/Security/fixtures + - tests/Security/yaml_fixtures + - tests/tmp + - tests/Util/fixtures + - tests/Util/yaml_fixtures + ignoreErrors: + - + message: '#Property .+phpCompatUtil is never read, only written\.#' + path: src/Maker/* + + - + message: '#Class Symfony\\Bundle\\MakerBundle\\Maker\\LegacyApiTestCase not found#' + path: src/Maker/* diff --git a/vendor/symfony/maker-bundle/src/ApplicationAwareMakerInterface.php b/vendor/symfony/maker-bundle/src/ApplicationAwareMakerInterface.php new file mode 100644 index 0000000..81780b4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/ApplicationAwareMakerInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Component\Console\Application; + +/** + * Implement this interface if your Maker needs access to the Application. + * + * @author Ryan Weaver + */ +interface ApplicationAwareMakerInterface +{ + public function setApplication(Application $application); +} diff --git a/vendor/symfony/maker-bundle/src/Command/MakerCommand.php b/vendor/symfony/maker-bundle/src/Command/MakerCommand.php new file mode 100644 index 0000000..24e01b2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Command/MakerCommand.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Command; + +use Symfony\Bundle\MakerBundle\ApplicationAwareMakerInterface; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\MakerInterface; +use Symfony\Bundle\MakerBundle\Util\TemplateLinter; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Used as the Command class for the makers. + * + * @internal + */ +final class MakerCommand extends Command +{ + private InputConfiguration $inputConfig; + private ConsoleStyle $io; + private bool $checkDependencies = true; + + public function __construct( + private MakerInterface $maker, + private FileManager $fileManager, + private Generator $generator, + private TemplateLinter $linter, + ) { + $this->inputConfig = new InputConfiguration(); + + parent::__construct(); + } + + protected function configure(): void + { + $this->maker->configureCommand($this, $this->inputConfig); + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->io = new ConsoleStyle($input, $output); + $this->fileManager->setIO($this->io); + + if ($this->checkDependencies) { + $dependencies = new DependencyBuilder(); + $this->maker->configureDependencies($dependencies, $input); + + if ($missingPackagesMessage = $dependencies->getMissingPackagesMessage($this->getName())) { + throw new RuntimeCommandException($missingPackagesMessage); + } + } + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + if (!$this->fileManager->isNamespaceConfiguredToAutoload($this->generator->getRootNamespace())) { + $this->io->note([ + \sprintf('It looks like your app may be using a namespace other than "%s".', $this->generator->getRootNamespace()), + 'To configure this and make your life easier, see: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html#configuration', + ]); + } + + foreach ($this->getDefinition()->getArguments() as $argument) { + if ($input->getArgument($argument->getName())) { + continue; + } + + if (\in_array($argument->getName(), $this->inputConfig->getNonInteractiveArguments(), true)) { + continue; + } + + $value = $this->io->ask($argument->getDescription(), $argument->getDefault(), Validator::notBlank(...)); + $input->setArgument($argument->getName(), $value); + } + + $this->maker->interact($input, $this->io, $this); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($output->isVerbose()) { + $this->linter->writeLinterMessage($output); + } + + $this->maker->generate($input, $this->io, $this->generator); + + // sanity check for custom makers + if ($this->generator->hasPendingOperations()) { + throw new \LogicException('Make sure to call the writeChanges() method on the generator.'); + } + + $this->linter->lintFiles($this->generator->getGeneratedFiles()); + + return 0; + } + + public function setApplication(?Application $application = null): void + { + parent::setApplication($application); + + if ($this->maker instanceof ApplicationAwareMakerInterface) { + if (null === $application) { + throw new \RuntimeException('Application cannot be null.'); + } + + $this->maker->setApplication($application); + } + } + + /** + * @internal Used for testing commands + */ + public function setCheckDependencies(bool $checkDeps): void + { + $this->checkDependencies = $checkDeps; + } +} diff --git a/vendor/symfony/maker-bundle/src/Console/MigrationDiffFilteredOutput.php b/vendor/symfony/maker-bundle/src/Console/MigrationDiffFilteredOutput.php new file mode 100644 index 0000000..6f66f92 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Console/MigrationDiffFilteredOutput.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Console; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class MigrationDiffFilteredOutput implements OutputInterface +{ + private string $buffer = ''; + private bool $previousLineWasRemoved = false; + + public function __construct( + private OutputInterface $output, + ) { + } + + public function write($messages, bool $newline = false, $options = 0): void + { + $messages = $this->filterMessages($messages, $newline); + + $this->output->write($messages, $newline, $options); + } + + public function writeln($messages, int $options = 0): void + { + $messages = $this->filterMessages($messages, true); + + $this->output->writeln($messages, $options); + } + + public function setVerbosity(int $level): void + { + $this->output->setVerbosity($level); + } + + public function setDecorated(bool $decorated): void + { + $this->output->setDecorated($decorated); + } + + public function getVerbosity(): int + { + return $this->output->getVerbosity(); + } + + public function isQuiet(): bool + { + return $this->output->isQuiet(); + } + + public function isVerbose(): bool + { + return $this->output->isVerbose(); + } + + public function isVeryVerbose(): bool + { + return $this->output->isVeryVerbose(); + } + + public function isDebug(): bool + { + return $this->output->isDebug(); + } + + public function isDecorated(): bool + { + return $this->output->isDecorated(); + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + $this->output->setFormatter($formatter); + } + + public function getFormatter(): OutputFormatterInterface + { + return $this->output->getFormatter(); + } + + public function fetch(): string + { + return $this->buffer; + } + + private function filterMessages($messages, bool $newLine) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + $hiddenPhrases = [ + 'Generated new migration class', + 'To run just this migration', + 'To revert the migration you', + ]; + + foreach ($messages as $key => $message) { + $this->buffer .= $message; + + if ($newLine) { + $this->buffer .= \PHP_EOL; + } + + if ($this->previousLineWasRemoved && !trim($message)) { + // hide a blank line after a filtered line + unset($messages[$key]); + $this->previousLineWasRemoved = false; + + continue; + } + + $this->previousLineWasRemoved = false; + foreach ($hiddenPhrases as $hiddenPhrase) { + if (str_contains($message, $hiddenPhrase)) { + $this->previousLineWasRemoved = true; + unset($messages[$key]); + + break; + } + } + } + + return array_values($messages); + } +} diff --git a/vendor/symfony/maker-bundle/src/ConsoleStyle.php b/vendor/symfony/maker-bundle/src/ConsoleStyle.php new file mode 100644 index 0000000..dee9a88 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/ConsoleStyle.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class ConsoleStyle extends SymfonyStyle +{ + public function __construct( + InputInterface $input, + private OutputInterface $output, + ) { + parent::__construct($input, $output); + } + + public function success($message): void + { + $this->writeln('OK '.$message); + } + + public function comment($message): void + { + $this->text($message); + } + + public function getOutput(): OutputInterface + { + return $this->output; + } +} diff --git a/vendor/symfony/maker-bundle/src/DependencyBuilder.php b/vendor/symfony/maker-bundle/src/DependencyBuilder.php new file mode 100644 index 0000000..c1119c7 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/DependencyBuilder.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +final class DependencyBuilder +{ + private array $dependencies = []; + private array $devDependencies = []; + + /** + * Add a dependency that will be reported if the given class is missing. + * + * If the dependency is *optional*, then it will only be reported to + * the user if other required dependencies are missing. An example + * is the "validator" when trying to work with forms. + */ + public function addClassDependency(string $class, string $package, bool $required = true, bool $devDependency = false): void + { + if ($devDependency) { + $this->devDependencies[] = [ + 'class' => $class, + 'name' => $package, + 'required' => $required, + ]; + } else { + $this->dependencies[] = [ + 'class' => $class, + 'name' => $package, + 'required' => $required, + ]; + } + } + + public function requirePHP71(): void + { + trigger_deprecation('symfony/maker-bundle', 'v1.44.0', 'requirePHP71() is deprecated and will be removed in a future version.'); + } + + /** + * @internal + */ + public function getMissingDependencies(): array + { + return $this->calculateMissingDependencies($this->dependencies); + } + + /** + * @internal + */ + public function getMissingDevDependencies(): array + { + return $this->calculateMissingDependencies($this->devDependencies); + } + + /** + * @internal + */ + public function getAllRequiredDependencies(): array + { + return $this->getRequiredDependencyNames($this->dependencies); + } + + /** + * @internal + */ + public function getAllRequiredDevDependencies(): array + { + return $this->getRequiredDependencyNames($this->devDependencies); + } + + /** + * @internal + */ + public function getMissingPackagesMessage(string $commandName, $message = null): string + { + $packages = $this->getMissingDependencies(); + $packagesDev = $this->getMissingDevDependencies(); + + if (empty($packages) && empty($packagesDev)) { + return ''; + } + + $packagesCount = \count($packages) + \count($packagesDev); + + $message = \sprintf( + "Missing package%s: %s, run:\n", + $packagesCount > 1 ? 's' : '', + $message ?: \sprintf('to use the %s command', $commandName) + ); + + if (!empty($packages)) { + $message .= \sprintf("\ncomposer require %s", implode(' ', $packages)); + } + + if (!empty($packagesDev)) { + $message .= \sprintf("\ncomposer require %s --dev", implode(' ', $packagesDev)); + } + + return $message; + } + + private function getRequiredDependencyNames(array $dependencies): array + { + $packages = []; + foreach ($dependencies as $package) { + if (!$package['required']) { + continue; + } + $packages[] = $package['name']; + } + + return array_unique($packages); + } + + private function calculateMissingDependencies(array $dependencies): array + { + $missingPackages = []; + $missingOptionalPackages = []; + foreach ($dependencies as $package) { + if (class_exists($package['class']) || interface_exists($package['class']) || trait_exists($package['class'])) { + continue; + } + if (true === $package['required']) { + $missingPackages[] = $package['name']; + } else { + $missingOptionalPackages[] = $package['name']; + } + } + if (empty($missingPackages)) { + return []; + } + + return array_unique([...$missingPackages, ...$missingOptionalPackages]); + } +} diff --git a/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/MakeCommandRegistrationPass.php b/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/MakeCommandRegistrationPass.php new file mode 100644 index 0000000..a71c417 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/MakeCommandRegistrationPass.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass; + +use Symfony\Bundle\MakerBundle\Command\MakerCommand; +use Symfony\Bundle\MakerBundle\MakerInterface; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +class MakeCommandRegistrationPass implements CompilerPassInterface +{ + public const MAKER_TAG = 'maker.command'; + + public function process(ContainerBuilder $container): void + { + foreach ($container->findTaggedServiceIds(self::MAKER_TAG) as $id => $tags) { + $def = $container->getDefinition($id); + if ($def->isDeprecated()) { + continue; + } + + $class = $container->getParameterBag()->resolveValue($def->getClass()); + if (!is_subclass_of($class, MakerInterface::class)) { + throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, MakerInterface::class)); + } + + $commandDefinition = new ChildDefinition('maker.auto_command.abstract'); + $commandDefinition->setClass(MakerCommand::class); + $commandDefinition->replaceArgument(0, new Reference($id)); + + $tagAttributes = ['command' => $class::getCommandName()]; + + if (!method_exists($class, 'getCommandDescription')) { + // no-op + } elseif (class_exists(LazyCommand::class)) { + $tagAttributes['description'] = $class::getCommandDescription(); + } else { + $commandDefinition->addMethodCall('setDescription', [$class::getCommandDescription()]); + } + + $commandDefinition->addTag('console.command', $tagAttributes); + + /* + * @deprecated remove this block when removing make:unit-test and make:functional-test + */ + if (method_exists($class, 'getCommandAliases')) { + foreach ($class::getCommandAliases() as $alias) { + $commandDefinition->addTag('console.command', ['command' => $alias, 'description' => 'Deprecated alias of "make:test"']); + } + } + + /* + * @deprecated remove this block when removing make:subscriber + */ + if (method_exists($class, 'getCommandAlias')) { + $alias = $class::getCommandAlias(); + $commandDefinition->addTag('console.command', ['command' => $alias, 'description' => 'Deprecated alias of "make:listener"']); + } + + $container->setDefinition(\sprintf('maker.auto_command.%s', Str::asTwigVariable($class::getCommandName())), $commandDefinition); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/RemoveMissingParametersPass.php b/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/RemoveMissingParametersPass.php new file mode 100644 index 0000000..c418404 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/RemoveMissingParametersPass.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes injected parameter arguments if they don't exist in this app. + * + * @author Ryan Weaver + */ +class RemoveMissingParametersPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('twig.default_path')) { + $container->getDefinition('maker.file_manager') + ->replaceArgument(4, null); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/SetDoctrineAnnotatedPrefixesPass.php b/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/SetDoctrineAnnotatedPrefixesPass.php new file mode 100644 index 0000000..a30a1a8 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/DependencyInjection/CompilerPass/SetDoctrineAnnotatedPrefixesPass.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +class SetDoctrineAnnotatedPrefixesPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $annotatedPrefixes = null; + + foreach ($container->findTaggedServiceIds('doctrine.orm.configuration') as $id => $tags) { + $metadataDriverImpl = null; + foreach ($container->getDefinition($id)->getMethodCalls() as [$method, $arguments]) { + if ('setMetadataDriverImpl' === $method) { + $metadataDriverImpl = $container->getDefinition($arguments[0]); + break; + } + } + + if (null === $metadataDriverImpl || !preg_match('/^doctrine\.orm\.(.+)_configuration$/D', $id, $m)) { + continue; + } + + $managerName = $m[1]; + $methodCalls = $metadataDriverImpl->getMethodCalls(); + + foreach ($methodCalls as $i => [$method, $arguments]) { + if ('addDriver' !== $method) { + continue; + } + + if ($arguments[0] instanceof Definition) { + $class = $arguments[0]->getClass(); + + $id = \sprintf('.%d_doctrine_metadata_driver~%s', $i, ContainerBuilder::hash($arguments)); + $container->setDefinition($id, $arguments[0]); + $arguments[0] = new Reference($id); + $methodCalls[$i] = [$method, $arguments]; + } + + $annotatedPrefixes[$managerName][] = [ + $arguments[1], + new Reference($arguments[0]), + ]; + } + + $metadataDriverImpl->setMethodCalls($methodCalls); + } + + if (null !== $annotatedPrefixes) { + $container->getDefinition('maker.doctrine_helper')->setArgument(2, $annotatedPrefixes); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/DependencyInjection/Configuration.php b/vendor/symfony/maker-bundle/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..15c7183 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/DependencyInjection/Configuration.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class Configuration implements ConfigurationInterface +{ + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('maker'); + $rootNode = $treeBuilder->getRootNode(); + + $rootNode + ->children() + ->scalarNode('root_namespace')->defaultValue('App')->end() + ->booleanNode('generate_final_classes')->defaultTrue()->end() + ->booleanNode('generate_final_entities')->defaultFalse()->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/vendor/symfony/maker-bundle/src/DependencyInjection/MakerExtension.php b/vendor/symfony/maker-bundle/src/DependencyInjection/MakerExtension.php new file mode 100644 index 0000000..1de775b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/DependencyInjection/MakerExtension.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\DependencyInjection; + +use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\MakeCommandRegistrationPass; +use Symfony\Bundle\MakerBundle\MakerInterface; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** + * This is the class that loads and manages your bundle configuration. + * + * @see http://symfony.com/doc/current/cookbook/bundles/extension.html + */ +class MakerExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container): void + { + $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.xml'); + $loader->load('makers.xml'); + + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + $rootNamespace = trim($config['root_namespace'], '\\'); + + $autoloaderFinderDefinition = $container->getDefinition('maker.autoloader_finder'); + $autoloaderFinderDefinition->replaceArgument(0, $rootNamespace); + + $makeCommandDefinition = $container->getDefinition('maker.generator'); + $makeCommandDefinition->replaceArgument(1, $rootNamespace); + + $doctrineHelperDefinition = $container->getDefinition('maker.doctrine_helper'); + $doctrineHelperDefinition->replaceArgument(0, $rootNamespace.'\\Entity'); + + $componentGeneratorDefinition = $container->getDefinition('maker.template_component_generator'); + $componentGeneratorDefinition + ->replaceArgument(0, $config['generate_final_classes']) + ->replaceArgument(1, $config['generate_final_entities']) + ->replaceArgument(2, $rootNamespace) + ; + + $container->registerForAutoconfiguration(MakerInterface::class) + ->addTag(MakeCommandRegistrationPass::MAKER_TAG); + } +} diff --git a/vendor/symfony/maker-bundle/src/Docker/DockerDatabaseServices.php b/vendor/symfony/maker-bundle/src/Docker/DockerDatabaseServices.php new file mode 100644 index 0000000..890f4a6 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Docker/DockerDatabaseServices.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Docker; + +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class DockerDatabaseServices +{ + /** + * @throws RuntimeCommandException + */ + public static function getDatabaseSkeleton(string $name, string $version): array + { + switch ($name) { + case 'mariadb': + return [ + 'image' => \sprintf('mariadb:%s', $version), + 'environment' => [ + 'MYSQL_ROOT_PASSWORD' => 'password', + 'MYSQL_DATABASE' => 'main', + ], + ]; + case 'mysql': + return [ + 'image' => \sprintf('mysql:%s', $version), + 'environment' => [ + 'MYSQL_ROOT_PASSWORD' => 'password', + 'MYSQL_DATABASE' => 'main', + ], + ]; + case 'postgres': + return [ + 'image' => \sprintf('postgres:%s', $version), + 'environment' => [ + 'POSTGRES_PASSWORD' => 'main', + 'POSTGRES_USER' => 'main', + 'POSTGRES_DB' => 'main', + ], + ]; + } + + self::throwInvalidDatabase($name); + } + + /** + * @throws RuntimeCommandException + */ + public static function getDefaultPorts(string $name): array + { + switch ($name) { + case 'mariadb': + case 'mysql': + return ['3306']; + case 'postgres': + return ['5432']; + } + + self::throwInvalidDatabase($name); + } + + public static function getSuggestedServiceVersion(string $name): string + { + if ('postgres' === $name) { + return 'alpine'; + } + + return 'latest'; + } + + public static function getMissingExtensionName(string $name): ?string + { + $driver = match ($name) { + 'mariadb', 'mysql' => 'mysql', + 'postgres' => 'pgsql', + default => self::throwInvalidDatabase($name), + }; + + if (!\in_array($driver, \PDO::getAvailableDrivers(), true)) { + return $driver; + } + + return null; + } + + /** + * @throws RuntimeCommandException + */ + private static function throwInvalidDatabase(string $name): never + { + throw new RuntimeCommandException(\sprintf('%s is not a valid / supported docker database type.', $name)); + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/BaseCollectionRelation.php b/vendor/symfony/maker-bundle/src/Doctrine/BaseCollectionRelation.php new file mode 100644 index 0000000..9b9e9f4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/BaseCollectionRelation.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Symfony\Bundle\MakerBundle\Str; + +/** + * @internal + */ +abstract class BaseCollectionRelation extends BaseRelation +{ + abstract public function getTargetSetterMethodName(): string; + + public function getAdderMethodName(): string + { + return 'add'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getPropertyName())); + } + + public function getRemoverMethodName(): string + { + return 'remove'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getPropertyName())); + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/BaseRelation.php b/vendor/symfony/maker-bundle/src/Doctrine/BaseRelation.php new file mode 100644 index 0000000..5ab9a51 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/BaseRelation.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +/** + * @internal + */ +abstract class BaseRelation +{ + public function __construct( + private string $propertyName, + private string $targetClassName, + private ?string $targetPropertyName = null, + private bool $isSelfReferencing = false, + private bool $mapInverseRelation = true, + private bool $avoidSetter = false, + private bool $isCustomReturnTypeNullable = false, + private ?string $customReturnType = null, + private bool $isOwning = false, + private bool $orphanRemoval = false, + private bool $isNullable = false, + ) { + } + + public function getPropertyName(): string + { + return $this->propertyName; + } + + public function getTargetClassName(): string + { + return $this->targetClassName; + } + + public function getTargetPropertyName(): ?string + { + return $this->targetPropertyName; + } + + public function isSelfReferencing(): bool + { + return $this->isSelfReferencing; + } + + public function getMapInverseRelation(): bool + { + return $this->mapInverseRelation; + } + + public function shouldAvoidSetter(): bool + { + return $this->avoidSetter; + } + + public function getCustomReturnType(): ?string + { + return $this->customReturnType; + } + + public function isCustomReturnTypeNullable(): bool + { + return $this->isCustomReturnTypeNullable; + } + + public function isOwning(): bool + { + return $this->isOwning; + } + + public function getOrphanRemoval(): bool + { + return $this->orphanRemoval; + } + + public function isNullable(): bool + { + return $this->isNullable; + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/DoctrineHelper.php b/vendor/symfony/maker-bundle/src/Doctrine/DoctrineHelper.php new file mode 100644 index 0000000..21b4005 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/DoctrineHelper.php @@ -0,0 +1,369 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Doctrine\ORM\Mapping\MappingException as ORMMappingException; +use Doctrine\ORM\Mapping\NamingStrategy; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; +use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException; +use Doctrine\Persistence\Mapping\StaticReflectionService; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; + +/** + * @author Fabien Potencier + * @author Ryan Weaver + * @author Sadicov Vladimir + * + * @internal + */ +final class DoctrineHelper +{ + public function __construct( + private string $entityNamespace, + private ?ManagerRegistry $registry = null, + private ?array $mappingDriversByPrefix = null, + ) { + $this->entityNamespace = trim($entityNamespace, '\\'); + } + + public function getRegistry(): ManagerRegistry + { + // this should never happen: we will have checked for the + // DoctrineBundle dependency before calling this + if (null === $this->registry) { + throw new \Exception('Somehow the doctrine service is missing. Is DoctrineBundle installed?'); + } + + return $this->registry; + } + + private function isDoctrineInstalled(): bool + { + return null !== $this->registry; + } + + public function getEntityNamespace(): string + { + return $this->entityNamespace; + } + + public function doesClassUseDriver(string $className, string $driverClass): bool + { + try { + /** @var EntityManagerInterface $em */ + $em = $this->getRegistry()->getManagerForClass($className); + } catch (\ReflectionException) { + // this exception will be thrown by the registry if the class isn't created yet. + // an example case is the "make:entity" command, which needs to know which driver is used for the class to determine + // if the class should be generated with attributes or annotations. If this exception is thrown, we will check based on the + // namespaces for the given $className and compare it with the doctrine configuration to get the correct MappingDriver. + + // extract the new class's namespace from the full $className to check the namespace of the new class against the doctrine configuration. + $classNameComponents = explode('\\', $className); + if (1 < \count($classNameComponents)) { + array_pop($classNameComponents); + } + $classNamespace = implode('\\', $classNameComponents); + + return $this->isInstanceOf($this->getMappingDriverForNamespace($classNamespace), $driverClass); + } + + if (null === $em) { + throw new \InvalidArgumentException(\sprintf('Cannot find the entity manager for class "%s". Ensure entity uses attribute mapping.', $className)); + } + + if (null === $this->mappingDriversByPrefix) { + // doctrine-bundle <= 2.2 + $metadataDriver = $em->getConfiguration()->getMetadataDriverImpl(); + + if (!$this->isInstanceOf($metadataDriver, MappingDriverChain::class)) { + return $this->isInstanceOf($metadataDriver, $driverClass); + } + + foreach ($metadataDriver->getDrivers() as $namespace => $driver) { + if (str_starts_with($className, $namespace)) { + return $this->isInstanceOf($driver, $driverClass); + } + } + + return $this->isInstanceOf($metadataDriver->getDefaultDriver(), $driverClass); + } + + $managerName = array_search($em, $this->getRegistry()->getManagers(), true); + + foreach ($this->mappingDriversByPrefix[$managerName] as [$prefix, $prefixDriver]) { + if (str_starts_with($className, $prefix)) { + return $this->isInstanceOf($prefixDriver, $driverClass); + } + } + + return false; + } + + public function doesClassUsesAttributes(string $className): bool + { + return $this->doesClassUseDriver($className, AttributeDriver::class); + } + + public function isDoctrineSupportingAttributes(): bool + { + return $this->isDoctrineInstalled(); + } + + public function getEntitiesForAutocomplete(): array + { + $entities = []; + + if ($this->isDoctrineInstalled()) { + $allMetadata = $this->getMetadata(); + + foreach (array_keys($allMetadata) as $classname) { + $entityClassDetails = new ClassNameDetails($classname, $this->entityNamespace); + $entities[] = $entityClassDetails->getRelativeName(); + } + } + + sort($entities); + + return $entities; + } + + public function getMetadata(?string $classOrNamespace = null, bool $disconnected = false): array|ClassMetadata + { + // Invalidating the cached AttributeDriver::$classNames to find new Entity classes + foreach ($this->mappingDriversByPrefix ?? [] as $managerName => $prefixes) { + foreach ($prefixes as [$prefix, $attributeDriver]) { + if ($attributeDriver instanceof AttributeDriver) { + $classNames = (new \ReflectionClass(AttributeDriver::class))->getProperty('classNames'); + + $classNames->setAccessible(true); + $classNames->setValue($attributeDriver, null); + } + } + } + + $metadata = []; + + /** @var EntityManagerInterface $em */ + foreach ($this->getRegistry()->getManagers() as $em) { + $cmf = $em->getMetadataFactory(); + + if ($disconnected) { + try { + $loaded = $cmf->getAllMetadata(); + } catch (ORMMappingException|PersistenceMappingException) { + $loaded = $this->isInstanceOf($cmf, AbstractClassMetadataFactory::class) ? $cmf->getLoadedMetadata() : []; + } + + // Set the reflection service that was used in the now removed DisconnectedClassMetadataFactory::class + $cmf->setReflectionService(new StaticReflectionService()); + + foreach ($loaded as $m) { + $cmf->setMetadataFor($m->getName(), $m); + } + + if (null === $this->mappingDriversByPrefix) { + // Invalidating the cached AttributeDriver::$classNames to find new Entity classes + $metadataDriver = $em->getConfiguration()->getMetadataDriverImpl(); + + if ($this->isInstanceOf($metadataDriver, MappingDriverChain::class)) { + foreach ($metadataDriver->getDrivers() as $driver) { + if ($this->isInstanceOf($driver, AttributeDriver::class)) { + $classNames->setValue($driver, null); + } + } + } + } + } + + foreach ($cmf->getAllMetadata() as $m) { + if (null === $classOrNamespace) { + $metadata[$m->getName()] = $m; + } else { + if ($m->getName() === $classOrNamespace) { + return $m; + } + + if (str_starts_with($m->getName(), $classOrNamespace)) { + $metadata[$m->getName()] = $m; + } + } + } + } + + return $metadata; + } + + public function createDoctrineDetails(string $entityClassName): ?EntityDetails + { + $metadata = $this->getMetadata($entityClassName); + + if ($this->isInstanceOf($metadata, ClassMetadata::class)) { + return new EntityDetails($metadata); + } + + return null; + } + + public function isClassAMappedEntity(string $className): bool + { + if (!$this->isDoctrineInstalled()) { + return false; + } + + return (bool) $this->getMetadata($className); + } + + /** + * Determines if the property-type will make the column type redundant. + * + * See ClassMetadataInfo::validateAndCompleteTypedFieldMapping() + */ + public static function canColumnTypeBeInferredByPropertyType(string $columnType, string $propertyType): bool + { + // todo: guessing on enum's could be added + + return match ($propertyType) { + '\\'.\DateInterval::class => Types::DATEINTERVAL === $columnType, + '\\'.\DateTime::class => Types::DATETIME_MUTABLE === $columnType, + '\\'.\DateTimeImmutable::class => Types::DATETIME_IMMUTABLE === $columnType, + 'array' => Types::JSON === $columnType, + 'bool' => Types::BOOLEAN === $columnType, + 'float' => Types::FLOAT === $columnType, + 'int' => Types::INTEGER === $columnType, + 'string' => Types::STRING === $columnType, + default => false, + }; + } + + public static function getPropertyTypeForColumn(string $columnType): ?string + { + $propertyType = match ($columnType) { + Types::STRING, Types::TEXT, Types::GUID, Types::BIGINT, Types::DECIMAL => 'string', + 'array', Types::SIMPLE_ARRAY, Types::JSON => 'array', + Types::BOOLEAN => 'bool', + Types::INTEGER, Types::SMALLINT => 'int', + Types::FLOAT => 'float', + Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, Types::DATE_MUTABLE, Types::TIME_MUTABLE => '\\'.\DateTimeInterface::class, + Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_IMMUTABLE, Types::DATE_IMMUTABLE, Types::TIME_IMMUTABLE => '\\'.\DateTimeImmutable::class, + Types::DATEINTERVAL => '\\'.\DateInterval::class, + 'object' => 'object', + 'uuid' => '\\'.Uuid::class, + 'ulid' => '\\'.Ulid::class, + default => null, + }; + + if (null !== $propertyType || !($registry = Type::getTypeRegistry())->has($columnType)) { + return $propertyType; + } + + $reflection = new \ReflectionClass(($registry->get($columnType))::class); + + $returnType = $reflection->getMethod('convertToPHPValue')->getReturnType(); + + /* + * we do not support union and intersection types + */ + if (!$returnType instanceof \ReflectionNamedType) { + return null; + } + + return $returnType->isBuiltin() ? $returnType->getName() : '\\'.$returnType->getName(); + } + + /** + * Given the string "column type", this returns the "Types::STRING" constant. + * + * This is, effectively, a reverse lookup: given the final string, give us + * the constant to be used in the generated code. + */ + public static function getTypeConstant(string $columnType): ?string + { + $reflection = new \ReflectionClass(Types::class); + $constants = array_flip($reflection->getConstants()); + + if (!isset($constants[$columnType])) { + return null; + } + + return \sprintf('Types::%s', $constants[$columnType]); + } + + private function isInstanceOf($object, string $class): bool + { + if (!\is_object($object)) { + return false; + } + + return $object instanceof $class; + } + + public function getPotentialTableName(string $className): string + { + $entityManager = $this->getRegistry()->getManager(); + + if (!$entityManager instanceof EntityManagerInterface) { + throw new \RuntimeException('ObjectManager is not an EntityManagerInterface.'); + } + + /** @var NamingStrategy $namingStrategy */ + $namingStrategy = $entityManager->getConfiguration()->getNamingStrategy(); + + return $namingStrategy->classToTableName($className); + } + + public function isKeyword(string $name): bool + { + /** @var Connection $connection */ + $connection = $this->getRegistry()->getConnection(); + + return $connection->getDatabasePlatform()->getReservedKeywordsList()->isKeyword($name); + } + + /** + * this method tries to find the correct MappingDriver for the given namespace/class + * To determine which MappingDriver belongs to the class we check the prefixes configured in Doctrine and use the + * prefix that has the closest match to the given $namespace. + * + * this helper function is needed to create entities with the configuration of doctrine if they are not yet been registered + * in the ManagerRegistry + */ + private function getMappingDriverForNamespace(string $namespace): ?MappingDriver + { + $lowestCharacterDiff = null; + $foundDriver = null; + + foreach ($this->mappingDriversByPrefix ?? [] as $mappings) { + foreach ($mappings as [$prefix, $driver]) { + $diff = substr_compare($namespace, $prefix, 0); + + if ($diff >= 0 && (null === $lowestCharacterDiff || $diff < $lowestCharacterDiff)) { + $lowestCharacterDiff = $diff; + $foundDriver = $driver; + } + } + } + + return $foundDriver; + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/EntityClassGenerator.php b/vendor/symfony/maker-bundle/src/Doctrine/EntityClassGenerator.php new file mode 100644 index 0000000..a54790b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/EntityClassGenerator.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use ApiPlatform\Metadata\ApiResource; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Bridge\Doctrine\Types\UlidType; +use Symfony\Bridge\Doctrine\Types\UuidType; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\Maker\Common\EntityIdTypeEnum; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; +use Symfony\UX\Turbo\Attribute\Broadcast; + +/** + * @internal + */ +final class EntityClassGenerator +{ + public function __construct( + private Generator $generator, + private DoctrineHelper $doctrineHelper, + ) { + } + + public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $apiResource, bool $withPasswordUpgrade = false, bool $generateRepositoryClass = true, bool $broadcast = false, EntityIdTypeEnum $useUuidIdentifier = EntityIdTypeEnum::INT): string + { + $repoClassDetails = $this->generator->createClassNameDetails( + $entityClassDetails->getRelativeName(), + 'Repository\\', + 'Repository' + ); + + $tableName = $this->doctrineHelper->getPotentialTableName($entityClassDetails->getFullName()); + + $useStatements = new UseStatementGenerator([ + $repoClassDetails->getFullName(), + ['Doctrine\\ORM\\Mapping' => 'ORM'], + ]); + + if ($broadcast) { + $useStatements->addUseStatement(Broadcast::class); + } + + if ($apiResource) { + $useStatements->addUseStatement(ApiResource::class); + } + + if (EntityIdTypeEnum::UUID === $useUuidIdentifier) { + $useStatements->addUseStatement([ + Uuid::class, + UuidType::class, + ]); + } + + if (EntityIdTypeEnum::ULID === $useUuidIdentifier) { + $useStatements->addUseStatement([ + Ulid::class, + UlidType::class, + ]); + } + + $entityPath = $this->generator->generateClass( + $entityClassDetails->getFullName(), + 'doctrine/Entity.tpl.php', + [ + 'use_statements' => $useStatements, + 'repository_class_name' => $repoClassDetails->getShortName(), + 'api_resource' => $apiResource, + 'broadcast' => $broadcast, + 'should_escape_table_name' => $this->doctrineHelper->isKeyword($tableName), + 'table_name' => $tableName, + 'id_type' => $useUuidIdentifier, + ] + ); + + if ($generateRepositoryClass) { + $this->generateRepositoryClass( + $repoClassDetails->getFullName(), + $entityClassDetails->getFullName(), + $withPasswordUpgrade, + true + ); + } + + return $entityPath; + } + + public function generateRepositoryClass(string $repositoryClass, string $entityClass, bool $withPasswordUpgrade, bool $includeExampleComments = true): void + { + $shortEntityClass = Str::getShortClassName($entityClass); + $entityAlias = strtolower($shortEntityClass[0]); + + $passwordUserInterfaceName = UserInterface::class; + + if (interface_exists(PasswordAuthenticatedUserInterface::class)) { + $passwordUserInterfaceName = PasswordAuthenticatedUserInterface::class; + } + + $interfaceClassNameDetails = new ClassNameDetails($passwordUserInterfaceName, 'Symfony\Component\Security\Core\User'); + + $useStatements = new UseStatementGenerator([ + $entityClass, + ManagerRegistry::class, + ServiceEntityRepository::class, + ]); + + if ($withPasswordUpgrade) { + $useStatements->addUseStatement([ + $interfaceClassNameDetails->getFullName(), + PasswordUpgraderInterface::class, + UnsupportedUserException::class, + ]); + } + + $this->generator->generateClass( + $repositoryClass, + 'doctrine/Repository.tpl.php', + [ + 'use_statements' => $useStatements, + 'entity_class_name' => $shortEntityClass, + 'entity_alias' => $entityAlias, + 'with_password_upgrade' => $withPasswordUpgrade, + 'password_upgrade_user_interface' => $interfaceClassNameDetails, + 'include_example_comments' => $includeExampleComments, + ] + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/EntityDetails.php b/vendor/symfony/maker-bundle/src/Doctrine/EntityDetails.php new file mode 100644 index 0000000..e407e14 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/EntityDetails.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\Persistence\Mapping\ClassMetadata; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; + +/** + * @author Sadicov Vladimir + * + * @internal + */ +final class EntityDetails +{ + public function __construct( + private ClassMetadata $metadata, + ) { + } + + public function getRepositoryClass(): ?string + { + return $this->metadata->customRepositoryClassName; + } + + public function getIdentifier() + { + return $this->metadata->identifier[0]; + } + + public function getDisplayFields(): array + { + return $this->metadata->fieldMappings; + } + + public function getFormFields(): array + { + $fields = (array) $this->metadata->fieldNames; + // Remove the primary key field if it's not managed manually + if (!$this->metadata->isIdentifierNatural()) { + $fields = array_diff($fields, $this->metadata->identifier); + } + $fields = array_values($fields); + + if (!empty($this->metadata->embeddedClasses)) { + foreach (array_keys($this->metadata->embeddedClasses) as $embeddedClassKey) { + $fields = array_filter($fields, static fn ($v) => !str_starts_with($v, $embeddedClassKey.'.')); + } + } + + $fieldsWithTypes = []; + foreach ($fields as $field) { + $fieldsWithTypes[$field] = null; + } + + foreach ($this->metadata->fieldMappings as $fieldName => $fieldMapping) { + $propType = DoctrineHelper::getPropertyTypeForColumn($fieldMapping['type']); + if (($propType === '\\'.\DateTimeImmutable::class) + || ($propType === '\\'.\DateTimeInterface::class)) { + $fieldsWithTypes[$fieldName] = [ + 'type' => null, + 'options_code' => "'widget' => 'single_text'", + ]; + } + } + foreach ($this->metadata->associationMappings as $fieldName => $relation) { + if (\Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY === $relation['type']) { + continue; + } + $fieldsWithTypes[$fieldName] = [ + 'type' => EntityType::class, + 'options_code' => \sprintf('\'class\' => %s::class,', $relation['targetEntity']).\PHP_EOL.'\'choice_label\' => \'id\',', + 'extra_use_classes' => [$relation['targetEntity']], + ]; + if (\Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY === $relation['type']) { + $fieldsWithTypes[$fieldName]['options_code'] .= "\n'multiple' => true,"; + } + } + + return $fieldsWithTypes; + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/EntityRegenerator.php b/vendor/symfony/maker-bundle/src/Doctrine/EntityRegenerator.php new file mode 100644 index 0000000..cd5ccc7 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/EntityRegenerator.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\EmbeddedClassMapping; +use Doctrine\ORM\Mapping\MappingException; +use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; + +/** + * @internal + */ +final class EntityRegenerator +{ + public function __construct( + private DoctrineHelper $doctrineHelper, + private FileManager $fileManager, + private Generator $generator, + private EntityClassGenerator $entityClassGenerator, + private bool $overwrite, + ) { + } + + public function regenerateEntities(string $classOrNamespace): void + { + try { + $metadata = $this->doctrineHelper->getMetadata($classOrNamespace); + } catch (MappingException|PersistenceMappingException) { + $metadata = $this->doctrineHelper->getMetadata($classOrNamespace, true); + } + + if ($metadata instanceof ClassMetadata) { + $metadata = [$metadata]; + } elseif (class_exists($classOrNamespace)) { + throw new RuntimeCommandException(\sprintf('Could not find Doctrine metadata for "%s". Is it mapped as an entity?', $classOrNamespace)); + } elseif (empty($metadata)) { + throw new RuntimeCommandException(\sprintf('No entities were found in the "%s" namespace.', $classOrNamespace)); + } + + /** @var ClassSourceManipulator[] $operations */ + $operations = []; + foreach ($metadata as $classMetadata) { + if (!class_exists($classMetadata->name)) { + // the class needs to be generated for the first time! + $classPath = $this->generateClass($classMetadata); + } else { + $classPath = $this->getPathOfClass($classMetadata->name); + } + + $mappedFields = $this->getMappedFieldsInEntity($classMetadata); + + if ($classMetadata->customRepositoryClassName) { + $this->generateRepository($classMetadata); + } + + $manipulator = $this->createClassManipulator($classPath); + $operations[$classPath] = $manipulator; + + $embeddedClasses = []; + + foreach ($classMetadata->embeddedClasses as $fieldName => $mapping) { + if (str_contains($fieldName, '.')) { + continue; + } + + /** @legacy - Remove conditional when ORM 2.x is no longer supported. */ + $className = ($mapping instanceof EmbeddedClassMapping) ? $mapping->class : $mapping['class']; + + $embeddedClasses[$fieldName] = $this->getPathOfClass($className); + + $operations[$embeddedClasses[$fieldName]] = $this->createClassManipulator($embeddedClasses[$fieldName]); + + if (!\in_array($fieldName, $mappedFields)) { + continue; + } + + $manipulator->addEmbeddedEntity($fieldName, $className); + } + + foreach ($classMetadata->fieldMappings as $fieldName => $mapping) { + // skip embedded fields + if (str_contains($fieldName, '.')) { + [$fieldName, $embeddedFiledName] = explode('.', $fieldName); + + $property = ClassProperty::createFromObject($mapping); + $property->propertyName = $embeddedFiledName; + + $operations[$embeddedClasses[$fieldName]]->addEntityField($property); + + continue; + } + + if (!\in_array($fieldName, $mappedFields)) { + continue; + } + + $manipulator->addEntityField(ClassProperty::createFromObject($mapping)); + } + + foreach ($classMetadata->associationMappings as $fieldName => $mapping) { + if (!\in_array($fieldName, $mappedFields)) { + continue; + } + + match ($mapping['type']) { + ClassMetadata::MANY_TO_ONE => $manipulator->addManyToOneRelation(RelationManyToOne::createFromObject($mapping)), + ClassMetadata::ONE_TO_MANY => $manipulator->addOneToManyRelation(RelationOneToMany::createFromObject($mapping)), + ClassMetadata::MANY_TO_MANY => $manipulator->addManyToManyRelation(RelationManyToMany::createFromObject($mapping)), + ClassMetadata::ONE_TO_ONE => $manipulator->addOneToOneRelation(RelationOneToOne::createFromObject($mapping)), + default => throw new \Exception('Unknown association type.'), + }; + } + } + + foreach ($operations as $filename => $manipulator) { + $this->fileManager->dumpFile( + $filename, + $manipulator->getSourceCode() + ); + } + } + + private function generateClass(ClassMetadata $metadata): string + { + $path = $this->generator->generateClass( + $metadata->name, + 'Class.tpl.php', + [] + ); + $this->generator->writeChanges(); + + return $path; + } + + private function createClassManipulator(string $classPath): ClassSourceManipulator + { + return new ClassSourceManipulator( + sourceCode: $this->fileManager->getFileContents($classPath), + overwrite: $this->overwrite, + // if properties need to be generated then, by definition, + // some non-annotation config is being used (e.g. XML), and so, the + // properties should not have annotations added to them + useAttributesForDoctrineMapping: false + ); + } + + private function getPathOfClass(string $class): string + { + return (new \ReflectionClass($class))->getFileName(); + } + + private function generateRepository(ClassMetadata $metadata): void + { + if (!$metadata->customRepositoryClassName) { + return; + } + + if (class_exists($metadata->customRepositoryClassName)) { + // repository already exists + return; + } + + $this->entityClassGenerator->generateRepositoryClass( + $metadata->customRepositoryClassName, + $metadata->name, + false + ); + + $this->generator->writeChanges(); + } + + private function getMappedFieldsInEntity(ClassMetadata $classMetadata): array + { + /** @var \ReflectionClass $classReflection */ + $classReflection = $classMetadata->reflClass; + + $targetFields = [ + ...array_keys($classMetadata->fieldMappings), + ...array_keys($classMetadata->associationMappings), + ...array_keys($classMetadata->embeddedClasses), + ]; + + if ($classReflection) { + // exclude traits + $traitProperties = []; + + foreach ($classReflection->getTraits() as $trait) { + foreach ($trait->getProperties() as $property) { + $traitProperties[] = $property->getName(); + } + } + + $targetFields = array_diff($targetFields, $traitProperties); + + // exclude inherited properties + $targetFields = array_filter($targetFields, static fn ($field) => $classReflection->hasProperty($field) + && $classReflection->getProperty($field)->getDeclaringClass()->getName() === $classReflection->getName()); + } + + return $targetFields; + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/EntityRelation.php b/vendor/symfony/maker-bundle/src/Doctrine/EntityRelation.php new file mode 100644 index 0000000..be1b4a9 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/EntityRelation.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +/** + * @internal + */ +final class EntityRelation +{ + public const MANY_TO_ONE = 'ManyToOne'; + public const ONE_TO_MANY = 'OneToMany'; + public const MANY_TO_MANY = 'ManyToMany'; + public const ONE_TO_ONE = 'OneToOne'; + + private $owningProperty; + private $inverseProperty; + private bool $isNullable = false; + private bool $isSelfReferencing = false; + private bool $orphanRemoval = false; + private bool $mapInverseRelation = true; + + public function __construct( + private string $type, + private string $owningClass, + private string $inverseClass, + ) { + if (!\in_array($type, self::getValidRelationTypes())) { + throw new \Exception(\sprintf('Invalid relation type "%s"', $type)); + } + + if (self::ONE_TO_MANY === $type) { + throw new \Exception('Use ManyToOne instead of OneToMany'); + } + + $this->isSelfReferencing = $owningClass === $inverseClass; + } + + public function setOwningProperty(string $owningProperty): void + { + $this->owningProperty = $owningProperty; + } + + public function setInverseProperty(string $inverseProperty): void + { + if (!$this->mapInverseRelation) { + throw new \Exception('Cannot call setInverseProperty() when the inverse relation will not be mapped.'); + } + + $this->inverseProperty = $inverseProperty; + } + + public function setIsNullable(bool $isNullable): void + { + $this->isNullable = $isNullable; + } + + public function setOrphanRemoval(bool $orphanRemoval): void + { + $this->orphanRemoval = $orphanRemoval; + } + + public static function getValidRelationTypes(): array + { + return [ + self::MANY_TO_ONE, + self::ONE_TO_MANY, + self::MANY_TO_MANY, + self::ONE_TO_ONE, + ]; + } + + public function getOwningRelation(): RelationManyToMany|RelationOneToOne|RelationManyToOne + { + return match ($this->getType()) { + self::MANY_TO_ONE => (new RelationManyToOne( + propertyName: $this->owningProperty, + targetClassName: $this->inverseClass, + targetPropertyName: $this->inverseProperty, + isSelfReferencing: $this->isSelfReferencing, + mapInverseRelation: $this->mapInverseRelation, + isOwning: true, + isNullable: $this->isNullable, + )), + self::MANY_TO_MANY => (new RelationManyToMany( + propertyName: $this->owningProperty, + targetClassName: $this->inverseClass, + targetPropertyName: $this->inverseProperty, + isSelfReferencing: $this->isSelfReferencing, + mapInverseRelation: $this->mapInverseRelation, + isOwning: true, + )), + self::ONE_TO_ONE => (new RelationOneToOne( + propertyName: $this->owningProperty, + targetClassName: $this->inverseClass, + targetPropertyName: $this->inverseProperty, + isSelfReferencing: $this->isSelfReferencing, + mapInverseRelation: $this->mapInverseRelation, + isOwning: true, + isNullable: $this->isNullable, + )), + default => throw new \InvalidArgumentException('Invalid type'), + }; + } + + public function getInverseRelation(): RelationManyToMany|RelationOneToOne|RelationOneToMany + { + return match ($this->getType()) { + self::MANY_TO_ONE => (new RelationOneToMany( + propertyName: $this->inverseProperty, + targetClassName: $this->owningClass, + targetPropertyName: $this->owningProperty, + isSelfReferencing: $this->isSelfReferencing, + orphanRemoval: $this->orphanRemoval, + )), + self::MANY_TO_MANY => (new RelationManyToMany( + propertyName: $this->inverseProperty, + targetClassName: $this->owningClass, + targetPropertyName: $this->owningProperty, + isSelfReferencing: $this->isSelfReferencing + )), + self::ONE_TO_ONE => (new RelationOneToOne( + propertyName: $this->inverseProperty, + targetClassName: $this->owningClass, + targetPropertyName: $this->owningProperty, + isSelfReferencing: $this->isSelfReferencing, + isNullable: $this->isNullable, + )), + default => throw new \InvalidArgumentException('Invalid type'), + }; + } + + public function getType(): string + { + return $this->type; + } + + public function getOwningClass(): string + { + return $this->owningClass; + } + + public function getInverseClass(): string + { + return $this->inverseClass; + } + + public function getOwningProperty(): string + { + return $this->owningProperty; + } + + public function getInverseProperty(): string + { + return $this->inverseProperty; + } + + public function isNullable(): bool + { + return $this->isNullable; + } + + public function isSelfReferencing(): bool + { + return $this->isSelfReferencing; + } + + public function getMapInverseRelation(): bool + { + return $this->mapInverseRelation; + } + + public function setMapInverseRelation(bool $mapInverseRelation): void + { + if ($mapInverseRelation && $this->inverseProperty) { + throw new \Exception('Cannot set setMapInverseRelation() to true when the inverse relation property is set.'); + } + + $this->mapInverseRelation = $mapInverseRelation; + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/ORMDependencyBuilder.php b/vendor/symfony/maker-bundle/src/Doctrine/ORMDependencyBuilder.php new file mode 100644 index 0000000..5ef832c --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/ORMDependencyBuilder.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Doctrine\ORM\Mapping\Column; +use Symfony\Bundle\MakerBundle\DependencyBuilder; + +/** + * @internal + */ +final class ORMDependencyBuilder +{ + /** + * Central method to add dependencies needed for Doctrine ORM. + */ + public static function buildDependencies(DependencyBuilder $dependencies): void + { + $classes = [ + // guarantee DoctrineBundle + DoctrineBundle::class, + // guarantee ORM + Column::class, + ]; + + foreach ($classes as $class) { + $dependencies->addClassDependency( + $class, + 'orm' + ); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/RelationManyToMany.php b/vendor/symfony/maker-bundle/src/Doctrine/RelationManyToMany.php new file mode 100644 index 0000000..f9aa5ef --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/RelationManyToMany.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\ORM\Mapping\ManyToManyInverseSideMapping; +use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping; +use Symfony\Bundle\MakerBundle\Str; + +/** + * @internal + */ +final class RelationManyToMany extends BaseCollectionRelation +{ + public function getTargetSetterMethodName(): string + { + return 'add'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getTargetPropertyName())); + } + + public function getTargetRemoverMethodName(): string + { + return 'remove'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getTargetPropertyName())); + } + + public static function createFromObject(ManyToManyInverseSideMapping|ManyToManyOwningSideMapping|array $mapping): self + { + /* @legacy Remove conditional when ORM 2.x is no longer supported! */ + if (\is_array($mapping)) { + return new self( + propertyName: $mapping['fieldName'], + targetClassName: $mapping['targetEntity'], + targetPropertyName: $mapping['mappedBy'], + mapInverseRelation: !$mapping['isOwningSide'] || null !== $mapping['inversedBy'], + isOwning: $mapping['isOwningSide'], + ); + } + + if ($mapping instanceof ManyToManyOwningSideMapping) { + return new self( + propertyName: $mapping->fieldName, + targetClassName: $mapping->targetEntity, + targetPropertyName: $mapping->inversedBy, + mapInverseRelation: (null !== $mapping->inversedBy), + isOwning: $mapping->isOwningSide(), + ); + } + + return new self( + propertyName: $mapping->fieldName, + targetClassName: $mapping->targetEntity, + targetPropertyName: $mapping->mappedBy, + mapInverseRelation: true, + isOwning: $mapping->isOwningSide(), + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/RelationManyToOne.php b/vendor/symfony/maker-bundle/src/Doctrine/RelationManyToOne.php new file mode 100644 index 0000000..d7bfa6b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/RelationManyToOne.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\ORM\Mapping\ManyToOneAssociationMapping; + +/** + * @internal + */ +final class RelationManyToOne extends BaseRelation +{ + public static function createFromObject(ManyToOneAssociationMapping|array $mapping): self + { + /* @legacy Remove conditional when ORM 2.x is no longer supported! */ + if (\is_array($mapping)) { + return new self( + propertyName: $mapping['fieldName'], + targetClassName: $mapping['targetEntity'], + targetPropertyName: $mapping['inversedBy'], + mapInverseRelation: null !== $mapping['inversedBy'], + isOwning: true, + isNullable: $mapping['joinColumns'][0]['nullable'] ?? true, + ); + } + + return new self( + propertyName: $mapping->fieldName, + targetClassName: $mapping->targetEntity, + targetPropertyName: $mapping->inversedBy, + mapInverseRelation: null !== $mapping->inversedBy, + isOwning: true, + isNullable: $mapping->joinColumns[0]->nullable ?? true, + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/RelationOneToMany.php b/vendor/symfony/maker-bundle/src/Doctrine/RelationOneToMany.php new file mode 100644 index 0000000..7970f2f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/RelationOneToMany.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\ORM\Mapping\OneToManyAssociationMapping; +use Symfony\Bundle\MakerBundle\Str; + +/** + * @internal + */ +final class RelationOneToMany extends BaseCollectionRelation +{ + public function getTargetGetterMethodName(): string + { + return 'get'.Str::asCamelCase($this->getTargetPropertyName()); + } + + public function getTargetSetterMethodName(): string + { + return 'set'.Str::asCamelCase($this->getTargetPropertyName()); + } + + public function isMapInverseRelation(): bool + { + throw new \Exception('OneToMany IS the inverse side!'); + } + + public static function createFromObject(OneToManyAssociationMapping|array $mapping): self + { + /* @legacy Remove conditional when ORM 2.x is no longer supported! */ + if (\is_array($mapping)) { + return new self( + propertyName: $mapping['fieldName'], + targetClassName: $mapping['targetEntity'], + targetPropertyName: $mapping['mappedBy'], + orphanRemoval: $mapping['orphanRemoval'], + ); + } + + return new self( + propertyName: $mapping->fieldName, + targetClassName: $mapping->targetEntity, + targetPropertyName: $mapping->mappedBy, + orphanRemoval: $mapping->orphanRemoval, + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Doctrine/RelationOneToOne.php b/vendor/symfony/maker-bundle/src/Doctrine/RelationOneToOne.php new file mode 100644 index 0000000..72ecd5c --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Doctrine/RelationOneToOne.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Doctrine; + +use Doctrine\ORM\Mapping\OneToOneInverseSideMapping; +use Doctrine\ORM\Mapping\OneToOneOwningSideMapping; +use Symfony\Bundle\MakerBundle\Str; + +/** + * @internal + */ +final class RelationOneToOne extends BaseRelation +{ + public function getTargetGetterMethodName(): string + { + return 'get'.Str::asCamelCase($this->getTargetPropertyName()); + } + + public function getTargetSetterMethodName(): string + { + return 'set'.Str::asCamelCase($this->getTargetPropertyName()); + } + + public static function createFromObject(OneToOneInverseSideMapping|OneToOneOwningSideMapping|array $mapping): self + { + /* @legacy Remove conditional when ORM 2.x is no longer supported! */ + if (\is_array($mapping)) { + return new self( + propertyName: $mapping['fieldName'], + targetClassName: $mapping['targetEntity'], + targetPropertyName: $mapping['isOwningSide'] ? $mapping['inversedBy'] : $mapping['mappedBy'], + mapInverseRelation: !$mapping['isOwningSide'] || null !== $mapping['inversedBy'], + isOwning: $mapping['isOwningSide'], + isNullable: $mapping['joinColumns'][0]['nullable'] ?? true, + ); + } + + if ($mapping instanceof OneToOneOwningSideMapping) { + return new self( + propertyName: $mapping->fieldName, + targetClassName: $mapping->targetEntity, + targetPropertyName: $mapping->inversedBy, + mapInverseRelation: (null !== $mapping->inversedBy), + isOwning: true, + isNullable: $mapping->joinColumns[0]->nullable ?? true, + ); + } + + return new self( + propertyName: $mapping->fieldName, + targetClassName: $mapping->targetEntity, + targetPropertyName: $mapping->mappedBy, + mapInverseRelation: true, + isOwning: false, + isNullable: $mapping->joinColumns[0]->nullable ?? true, + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Event/ConsoleErrorSubscriber.php b/vendor/symfony/maker-bundle/src/Event/ConsoleErrorSubscriber.php new file mode 100644 index 0000000..c05c5f5 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Event/ConsoleErrorSubscriber.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Event; + +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Prints certain exceptions in a pretty way and silences normal exception handling. + * + * @author Ryan Weaver + */ +final class ConsoleErrorSubscriber implements EventSubscriberInterface +{ + private bool $setExitCode = false; + + public function onConsoleError(ConsoleErrorEvent $event): void + { + if (!$event->getError() instanceof RuntimeCommandException) { + return; + } + + // prevent any visual logging from appearing + $event->stopPropagation(); + // prevent the exception from actually being thrown + $event->setExitCode(0); + $this->setExitCode = true; + + $io = new SymfonyStyle($event->getInput(), $event->getOutput()); + $io->error($event->getError()->getMessage()); + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event): void + { + if (!$this->setExitCode) { + return; + } + + // finally set a non-zero exit code + $event->setExitCode(1); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::ERROR => 'onConsoleError', + ConsoleEvents::TERMINATE => 'onConsoleTerminate', + ]; + } +} diff --git a/vendor/symfony/maker-bundle/src/EventRegistry.php b/vendor/symfony/maker-bundle/src/EventRegistry.php new file mode 100644 index 0000000..8497f6d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/EventRegistry.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Security\Http\SecurityEvents; +use Symfony\Component\Workflow\WorkflowEvents; + +/** + * @internal + */ +class EventRegistry +{ + private static array $eventsMap = []; + + public function __construct( + private EventDispatcherInterface $eventDispatcher, + ) { + self::$eventsMap = array_flip([ + ...ConsoleEvents::ALIASES, + ...KernelEvents::ALIASES, + ...(class_exists(AuthenticationEvents::class) ? AuthenticationEvents::ALIASES : []), + ...(class_exists(SecurityEvents::class) ? SecurityEvents::ALIASES : []), + ...(class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : []), + ...(class_exists(FormEvents::class) ? FormEvents::ALIASES : []), + ]); + } + + /** + * Returns all known event names in the system. + */ + public function getAllActiveEvents(): array + { + $activeEvents = []; + foreach (self::$eventsMap as $eventName => $eventClass) { + if (!class_exists($eventClass)) { + continue; + } + + $activeEvents[] = $eventName; + } + + $listeners = $this->eventDispatcher->getListeners(); + + foreach (array_keys($listeners) as $listenerKey) { + if (!isset(self::$eventsMap[$listenerKey])) { + self::$eventsMap[$listenerKey] = $this->getEventClassName($listenerKey); + } + } + + $activeEvents = array_unique(array_merge($activeEvents, array_keys($listeners))); + + asort($activeEvents); + + return $activeEvents; + } + + /** + * Attempts to get the event class for a given event. + */ + public function getEventClassName(string $event): ?string + { + // if the event is already a class name, use it + if (class_exists($event)) { + return $event; + } + + if (isset(self::$eventsMap[$event])) { + return self::$eventsMap[$event]; + } + + $listeners = $this->eventDispatcher->getListeners($event); + if (empty($listeners)) { + return null; + } + + foreach ($listeners as $listener) { + if (!\is_array($listener) || 2 !== \count($listener)) { + continue; + } + + $reflectionMethod = new \ReflectionMethod($listener[0], $listener[1]); + $args = $reflectionMethod->getParameters(); + if (!$args) { + continue; + } + + if (null !== $type = $args[0]->getType()) { + $type = $type instanceof \ReflectionNamedType ? $type->getName() : null; + + // ignore an "object" type-hint + if ('object' === $type) { + continue; + } + + return $type; + } + } + + return null; + } + + public function listActiveEvents(array $events): array + { + foreach ($events as $key => $event) { + $events[$key] = \sprintf('%s (%s)', $event, self::$eventsMap[$event]); + } + + return $events; + } +} diff --git a/vendor/symfony/maker-bundle/src/Exception/RuntimeCommandException.php b/vendor/symfony/maker-bundle/src/Exception/RuntimeCommandException.php new file mode 100644 index 0000000..309164b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Exception/RuntimeCommandException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Exception; + +use Symfony\Component\Console\Exception\ExceptionInterface; + +/** + * An exception whose output is displayed as a clean error. + * + * @author Ryan Weaver + */ +final class RuntimeCommandException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/maker-bundle/src/FileManager.php b/vendor/symfony/maker-bundle/src/FileManager.php new file mode 100644 index 0000000..ba55501 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/FileManager.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Bundle\MakerBundle\Util\AutoloaderUtil; +use Symfony\Bundle\MakerBundle\Util\MakerFileLinkFormatter; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Filesystem\Filesystem; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + * + * @internal + */ +class FileManager +{ + private ?SymfonyStyle $io = null; + + public function __construct( + private Filesystem $fs, + private AutoloaderUtil $autoloaderUtil, + private MakerFileLinkFormatter $makerFileLinkFormatter, + private string $rootDirectory, + private ?string $twigDefaultPath = null, + ) { + $this->rootDirectory = rtrim($this->realPath($this->normalizeSlashes($rootDirectory)), '/'); + $this->twigDefaultPath = $twigDefaultPath ? rtrim($this->relativizePath($twigDefaultPath), '/') : null; + } + + public function setIO(SymfonyStyle $io): void + { + $this->io = $io; + } + + public function parseTemplate(string $templatePath, array $parameters): string + { + ob_start(); + extract($parameters, \EXTR_SKIP); + include $templatePath; + + return ob_get_clean(); + } + + public function dumpFile(string $filename, string $content): void + { + $absolutePath = $this->absolutizePath($filename); + $newFile = !$this->fileExists($filename); + $existingContent = $newFile ? '' : file_get_contents($absolutePath); + + $comment = $newFile ? 'created' : 'updated'; + if ($existingContent === $content) { + $comment = 'no change'; + } + + $this->fs->dumpFile($absolutePath, $content); + $relativePath = $this->relativizePath($filename); + + $this->io?->comment(\sprintf( + '%s: %s', + $comment, + $this->makerFileLinkFormatter->makeLinkedPath($absolutePath, $relativePath) + )); + } + + public function fileExists($path): bool + { + return file_exists($this->absolutizePath($path)); + } + + /** + * Attempts to make the path relative to the root directory. + * + * @throws \Exception + */ + public function relativizePath(string $absolutePath): string + { + $absolutePath = $this->normalizeSlashes($absolutePath); + + // see if the path is even in the root + if (!str_contains($absolutePath, $this->rootDirectory)) { + return $absolutePath; + } + + $absolutePath = $this->realPath($absolutePath); + + // str_replace but only the first occurrence + $relativePath = ltrim(implode('', explode($this->rootDirectory, $absolutePath, 2)), '/'); + if (str_starts_with($relativePath, './')) { + $relativePath = substr($relativePath, 2); + } + + return is_dir($absolutePath) ? rtrim($relativePath, '/').'/' : $relativePath; + } + + public function getFileContents(string $path): string + { + if (!$this->fileExists($path)) { + throw new \InvalidArgumentException(\sprintf('Cannot find file "%s"', $path)); + } + + return file_get_contents($this->absolutizePath($path)); + } + + public function isPathInVendor(string $path): bool + { + return str_starts_with( + $this->normalizeSlashes($path), + $this->normalizeSlashes($this->rootDirectory.'/vendor/') + ); + } + + public function absolutizePath($path): string + { + if (str_starts_with($path, '/')) { + return $path; + } + + // support windows drive paths: C:\ or C:/ + if (1 === strpos($path, ':\\') || 1 === strpos($path, ':/')) { + return $path; + } + + return \sprintf('%s/%s', $this->rootDirectory, $path); + } + + /** + * @throws \Exception + */ + public function getRelativePathForFutureClass(string $className): ?string + { + $path = $this->autoloaderUtil->getPathForFutureClass($className); + + return null === $path ? null : $this->relativizePath($path); + } + + public function getNamespacePrefixForClass(string $className): string + { + return $this->autoloaderUtil->getNamespacePrefixForClass($className); + } + + public function isNamespaceConfiguredToAutoload(string $namespace): bool + { + return $this->autoloaderUtil->isNamespaceConfiguredToAutoload($namespace); + } + + public function getRootDirectory(): string + { + return $this->rootDirectory; + } + + public function getPathForTemplate(string $filename): string + { + if (null === $this->twigDefaultPath) { + throw new \RuntimeException('Cannot get path for template: is Twig installed?'); + } + + return $this->twigDefaultPath.'/'.$filename; + } + + /** + * Resolve '../' in paths (like real_path), but for non-existent files. + */ + private function realPath(string $absolutePath): string + { + $finalParts = []; + $currentIndex = -1; + + $absolutePath = $this->normalizeSlashes($absolutePath); + foreach (explode('/', $absolutePath) as $pathPart) { + if ('..' === $pathPart) { + // we need to remove the previous entry + if (-1 === $currentIndex) { + throw new \Exception(\sprintf('Problem making path relative - is the path "%s" absolute?', $absolutePath)); + } + + unset($finalParts[$currentIndex]); + --$currentIndex; + + continue; + } + + $finalParts[] = $pathPart; + ++$currentIndex; + } + + $finalPath = implode('/', $finalParts); + + // Normalize: // => / + // Normalize: /./ => / + return str_replace(['//', '/./'], '/', $finalPath); + } + + private function normalizeSlashes(string $path): string + { + return str_replace('\\', '/', $path); + } +} diff --git a/vendor/symfony/maker-bundle/src/Generator.php b/vendor/symfony/maker-bundle/src/Generator.php new file mode 100644 index 0000000..315b334 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Generator.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; +use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil; +use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +class Generator +{ + private GeneratorTwigHelper $twigHelper; + private array $pendingOperations = []; + private array $generatedFiles = []; + + public function __construct( + private FileManager $fileManager, + private string $namespacePrefix, + ?PhpCompatUtil $phpCompatUtil = null, + private ?TemplateComponentGenerator $templateComponentGenerator = null, + ) { + $this->twigHelper = new GeneratorTwigHelper($fileManager); + $this->namespacePrefix = trim($namespacePrefix, '\\'); + + if (null !== $phpCompatUtil) { + trigger_deprecation('symfony/maker-bundle', 'v1.44.0', 'Initializing Generator while providing an instance of PhpCompatUtil is deprecated.'); + } + } + + /** + * Generate a new file for a class from a template. + * + * @param string $className The fully-qualified class name + * @param string $templateName Template name in Resources/skeleton to use + * @param array $variables Array of variables to pass to the template + * + * @return string The path where the file will be created + * + * @throws \Exception + */ + public function generateClass(string $className, string $templateName, array $variables = []): string + { + if (\array_key_exists('class_data', $variables) && $variables['class_data'] instanceof ClassData) { + $classData = $this->templateComponentGenerator->configureClass($variables['class_data']); + $className = $classData->getFullClassName(); + } + + $targetPath = $this->fileManager->getRelativePathForFutureClass($className); + + if (null === $targetPath) { + throw new \LogicException(\sprintf('Could not determine where to locate the new class "%s", maybe try with a full namespace like "\\My\\Full\\Namespace\\%s"', $className, Str::getShortClassName($className))); + } + + $variables = array_merge($variables, [ + 'class_name' => Str::getShortClassName($className), + 'namespace' => Str::getNamespace($className), + ]); + + $this->addOperation($targetPath, $templateName, $variables); + + return $targetPath; + } + + /** + * Generate a normal file from a template. + * + * @return void + */ + public function generateFile(string $targetPath, string $templateName, array $variables = []) + { + $variables = array_merge($variables, [ + 'helper' => $this->twigHelper, + ]); + + $this->addOperation($targetPath, $templateName, $variables); + } + + /** + * @return void + */ + public function dumpFile(string $targetPath, string $contents) + { + $this->pendingOperations[$targetPath] = [ + 'contents' => $contents, + ]; + } + + public function getFileContentsForPendingOperation(string $targetPath): string + { + if (!isset($this->pendingOperations[$targetPath])) { + throw new RuntimeCommandException(\sprintf('File "%s" is not in the Generator\'s pending operations', $targetPath)); + } + + $templatePath = $this->pendingOperations[$targetPath]['template']; + $parameters = $this->pendingOperations[$targetPath]['variables']; + + $templateParameters = array_merge($parameters, [ + 'relative_path' => $this->fileManager->relativizePath($targetPath), + ]); + + return $this->fileManager->parseTemplate($templatePath, $templateParameters); + } + + /** + * Creates a helper object to get data about a class name. + * + * Examples: + * + * // App\Entity\FeaturedProduct + * $gen->createClassNameDetails('FeaturedProduct', 'Entity'); + * $gen->createClassNameDetails('featured product', 'Entity'); + * + * // App\Controller\FooController + * $gen->createClassNameDetails('foo', 'Controller', 'Controller'); + * + * // App\Controller\Foo\AdminController + * $gen->createClassNameDetails('Foo\\Admin', 'Controller', 'Controller'); + * + * // App\Security\Voter\CoolVoter + * $gen->createClassNameDetails('Cool', 'Security\Voter', 'Voter'); + * + * // Full class names can also be passed. Imagine the user has an autoload + * // rule where Cool\Stuff lives in a "lib/" directory + * // Cool\Stuff\BalloonController + * $gen->createClassNameDetails('Cool\\Stuff\\Balloon', 'Controller', 'Controller'); + * + * @param string $name The short "name" that will be turned into the class name + * @param string $namespacePrefix Recommended namespace where this class should live, but *without* the "App\\" part + * @param string $suffix Optional suffix to guarantee is on the end of the class + */ + public function createClassNameDetails(string $name, string $namespacePrefix, string $suffix = '', string $validationErrorMessage = ''): ClassNameDetails + { + $fullNamespacePrefix = $this->namespacePrefix.'\\'.$namespacePrefix; + if ('\\' === $name[0]) { + // class is already "absolute" - leave it alone (but strip opening \) + $className = substr($name, 1); + } else { + $className = Str::asClassName($name, $suffix); + + try { + Validator::classDoesNotExist($className); + $className = rtrim($fullNamespacePrefix, '\\').'\\'.$className; + } catch (RuntimeCommandException) { + } + } + + Validator::validateClassName($className, $validationErrorMessage); + + // if this is a custom class, we may be completely different than the namespace prefix + // the best way can do, is find the PSR4 prefix and use that + if (!str_starts_with($className, $fullNamespacePrefix)) { + $fullNamespacePrefix = $this->fileManager->getNamespacePrefixForClass($className); + } + + return new ClassNameDetails($className, $fullNamespacePrefix, $suffix); + } + + public function getRootDirectory(): string + { + return $this->fileManager->getRootDirectory(); + } + + public function hasPendingOperations(): bool + { + return !empty($this->pendingOperations); + } + + /** + * Actually writes and file changes that are pending. + * + * @return void + */ + public function writeChanges() + { + foreach ($this->pendingOperations as $targetPath => $templateData) { + $this->generatedFiles[] = $targetPath; + + if (isset($templateData['contents'])) { + $this->fileManager->dumpFile($targetPath, $templateData['contents']); + + continue; + } + + $this->fileManager->dumpFile( + $targetPath, + $this->getFileContentsForPendingOperation($targetPath) + ); + } + + $this->pendingOperations = []; + } + + public function getRootNamespace(): string + { + return $this->namespacePrefix; + } + + public function generateController(string $controllerClassName, string $controllerTemplatePath, array $parameters = []): string + { + return $this->generateClass( + $controllerClassName, + $controllerTemplatePath, + $parameters + + [ + 'generator' => $this->templateComponentGenerator, + ] + ); + } + + /** + * Generate a template file. + * + * @return void + */ + public function generateTemplate(string $targetPath, string $templateName, array $variables = []) + { + $this->generateFile( + $this->fileManager->getPathForTemplate($targetPath), + $templateName, + $variables + ); + } + + /** + * Get the full path of each file created by the Generator. + */ + public function getGeneratedFiles(): array + { + return $this->generatedFiles; + } + + /** + * @deprecated MakerBundle only supports AbstractController::class. This method will be removed in the future. + */ + public static function getControllerBaseClass(): ClassNameDetails + { + trigger_deprecation('symfony/maker-bundle', 'v1.41.0', 'MakerBundle only supports AbstractController. This method will be removed in the future.'); + + return new ClassNameDetails(AbstractController::class, '\\'); + } + + private function addOperation(string $targetPath, string $templateName, array $variables): void + { + if ($this->fileManager->fileExists($targetPath)) { + throw new RuntimeCommandException(\sprintf('The file "%s" can\'t be generated because it already exists.', $this->fileManager->relativizePath($targetPath))); + } + + $variables['relative_path'] = $this->fileManager->relativizePath($targetPath); + + $templatePath = $templateName; + if (!file_exists($templatePath)) { + $templatePath = __DIR__.'/Resources/skeleton/'.$templateName; + + if (!file_exists($templatePath)) { + throw new \Exception(\sprintf('Cannot find template "%s"', $templateName)); + } + } + + $this->pendingOperations[$targetPath] = [ + 'template' => $templatePath, + 'variables' => $variables, + ]; + } +} diff --git a/vendor/symfony/maker-bundle/src/GeneratorTwigHelper.php b/vendor/symfony/maker-bundle/src/GeneratorTwigHelper.php new file mode 100644 index 0000000..60957b2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/GeneratorTwigHelper.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +/** + * @author Sadicov Vladimir + */ +final class GeneratorTwigHelper +{ + public function __construct( + private FileManager $fileManager, + ) { + } + + public function getEntityFieldPrintCode($entity, $field): string + { + $twigField = preg_replace_callback('/(?!^)_([a-z0-9])/', static fn ($s) => strtoupper($s[1]), $field['fieldName']); + $printCode = $entity.'.'.str_replace('_', '', $twigField); + + match ($field['type']) { + 'datetimetz_immutable', 'datetimetz' => $printCode .= ' ? '.$printCode.'|date(\'Y-m-d H:i:s T\') : \'\'', + 'datetime_immutable', 'datetime' => $printCode .= ' ? '.$printCode.'|date(\'Y-m-d H:i:s\') : \'\'', + 'dateinterval' => $printCode .= ' ? '.$printCode.'.format(\'%y year(s), %m month(s), %d day(s)\') : \'\'', + 'date_immutable', 'date' => $printCode .= ' ? '.$printCode.'|date(\'Y-m-d\') : \'\'', + 'time_immutable', 'time' => $printCode .= ' ? '.$printCode.'|date(\'H:i:s\') : \'\'', + 'json' => $printCode .= ' ? '.$printCode.'|json_encode : \'\'', + 'array' => $printCode .= ' ? '.$printCode.'|join(\', \') : \'\'', + 'boolean' => $printCode .= ' ? \'Yes\' : \'No\'', + default => $printCode, + }; + + return $printCode; + } + + public function getHeadPrintCode($title): string + { + if ($this->fileManager->fileExists($this->fileManager->getPathForTemplate('base.html.twig'))) { + return << + + $title + + HTML; + } + + public function getFileLink($path, $text = null, $line = 0): string + { + trigger_deprecation('symfony/maker-bundle', 'v1.53.0', 'getFileLink() is deprecated and will be removed in the future.'); + + $text = $text ?: $path; + + return "$text"; + } +} diff --git a/vendor/symfony/maker-bundle/src/InputAwareMakerInterface.php b/vendor/symfony/maker-bundle/src/InputAwareMakerInterface.php new file mode 100644 index 0000000..ac5c805 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/InputAwareMakerInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Component\Console\Input\InputInterface; + +/** + * Lets the configureDependencies method access to the command's input. + * + * @author Kévin Dunglas + */ +interface InputAwareMakerInterface extends MakerInterface +{ + public function configureDependencies(DependencyBuilder $dependencies, ?InputInterface $input = null); +} diff --git a/vendor/symfony/maker-bundle/src/InputConfiguration.php b/vendor/symfony/maker-bundle/src/InputConfiguration.php new file mode 100644 index 0000000..3d6dc05 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/InputConfiguration.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +final class InputConfiguration +{ + private array $nonInteractiveArguments = []; + + /** + * Call in MakerInterface::configureCommand() to disable the automatic interactive + * prompt for an argument. + */ + public function setArgumentAsNonInteractive(string $argumentName): void + { + $this->nonInteractiveArguments[] = $argumentName; + } + + public function getNonInteractiveArguments(): array + { + return $this->nonInteractiveArguments; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/AbstractMaker.php b/vendor/symfony/maker-bundle/src/Maker/AbstractMaker.php new file mode 100644 index 0000000..530bae0 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/AbstractMaker.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\MakerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; + +/** + * Convenient abstract class for makers. + */ +abstract class AbstractMaker implements MakerInterface +{ + /** + * @return void + */ + public function interact(InputInterface $input, ConsoleStyle $io, Command $command) + { + } + + /** + * @return void + */ + protected function writeSuccessMessage(ConsoleStyle $io) + { + $io->newLine(); + $io->writeln(' '); + $io->writeln(' Success! '); + $io->writeln(' '); + $io->newLine(); + } + + /** @param array $dependencies */ + protected function addDependencies(array $dependencies, ?string $message = null): string + { + $dependencyBuilder = new DependencyBuilder(); + + foreach ($dependencies as $class => $name) { + $dependencyBuilder->addClassDependency($class, $name); + } + + return $dependencyBuilder->getMissingPackagesMessage( + static::getCommandName(), + $message + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/Common/CanGenerateTestsTrait.php b/vendor/symfony/maker-bundle/src/Maker/Common/CanGenerateTestsTrait.php new file mode 100644 index 0000000..18d7566 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/Common/CanGenerateTestsTrait.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker\Common; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; + +/** + * @author Jesse Rushlow + * + * @internal + */ +trait CanGenerateTestsTrait +{ + private bool $generateTests = false; + + public function configureCommandWithTestsOption(Command $command): Command + { + $testsHelp = file_get_contents(\dirname(__DIR__, 2).'/Resources/help/_WithTests.txt'); + $help = $command->getHelp()."\n".$testsHelp; + + $command + ->addOption(name: 'with-tests', mode: InputOption::VALUE_NONE, description: 'Generate PHPUnit Tests') + ->setHelp($help) + ; + + return $command; + } + + public function interactSetGenerateTests(InputInterface $input, ConsoleStyle $io): void + { + // Sanity check for maker dev's - End user should never see this. + if (!$input->hasOption('with-tests')) { + throw new RuntimeCommandException('Whoops! "--with-tests" option does not exist. Call "addWithTestsOptions()" in the makers "configureCommand().'); + } + + $this->generateTests = $input->getOption('with-tests'); + + if (!$this->generateTests) { + $this->generateTests = $io->confirm('Do you want to generate PHPUnit tests? [Experimental]', false); + } + } + + public function shouldGenerateTests(): bool + { + return $this->generateTests; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/Common/EntityIdTypeEnum.php b/vendor/symfony/maker-bundle/src/Maker/Common/EntityIdTypeEnum.php new file mode 100644 index 0000000..9e8d6ff --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/Common/EntityIdTypeEnum.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker\Common; + +/** + * @author Jesse Rushlow + * + * @internal + */ +enum EntityIdTypeEnum +{ + case INT; + case UUID; + case ULID; +} diff --git a/vendor/symfony/maker-bundle/src/Maker/Common/InstallDependencyTrait.php b/vendor/symfony/maker-bundle/src/Maker/Common/InstallDependencyTrait.php new file mode 100644 index 0000000..1033809 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/Common/InstallDependencyTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker\Common; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Component\Process\Process; + +/** + * @author Jesse Rushlow + * + * @internal + */ +trait InstallDependencyTrait +{ + /** + * @param string $composerPackage Fully qualified composer package to install e.g. symfony/maker-bundle + */ + public function installDependencyIfNeeded(ConsoleStyle $io, string $expectedClassToExist, string $composerPackage): ConsoleStyle + { + if (class_exists($expectedClassToExist)) { + return $io; + } + + $io->writeln(\sprintf('Running: composer require %s', $composerPackage)); + + Process::fromShellCommandline(\sprintf('composer require %s', $composerPackage))->run(); + + $io->writeln(\sprintf('%s successfully installed!', $composerPackage)); + $io->newLine(); + + return $io; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/Common/UidTrait.php b/vendor/symfony/maker-bundle/src/Maker/Common/UidTrait.php new file mode 100644 index 0000000..25e8269 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/Common/UidTrait.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker\Common; + +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; + +/** + * @author Jesse Rushlow + * + * @internal + */ +trait UidTrait +{ + private bool $usesUuid = false; + private bool $usesUlid = false; + + /** + * Call this in a maker's configure() to consistently allow entity's with UUID's. + * This should be called after you calling "setHelp()" in the maker. + */ + protected function addWithUuidOption(Command $command): Command + { + $uidHelp = file_get_contents(\dirname(__DIR__, 2).'/Resources/help/_WithUid.txt'); + $help = $command->getHelp()."\n".$uidHelp; + + $command + ->addOption(name: 'with-uuid', mode: InputOption::VALUE_NONE, description: 'Use UUID for entity "id"') + ->addOption('with-ulid', mode: InputOption::VALUE_NONE, description: 'Use ULID for entity "id"') + ->setHelp($help) + ; + + return $command; + } + + /** + * Call this as early as possible in a maker's interact(). + */ + protected function checkIsUsingUid(InputInterface $input): void + { + if (($this->usesUuid = $input->getOption('with-uuid')) && !class_exists(Uuid::class)) { + throw new RuntimeCommandException('You must install symfony/uid to use Uuid\'s as "id" (composer require symfony/uid)'); + } + + if (($this->usesUlid = $input->getOption('with-ulid')) && !class_exists(Ulid::class)) { + throw new RuntimeCommandException('You must install symfony/uid to use Ulid\'s as "id" (composer require symfony/uid)'); + } + + if ($this->usesUuid && $this->usesUlid) { + throw new RuntimeCommandException('Setting --with-uuid & --with-ulid at the same time is not allowed. Please choose only one.'); + } + } + + protected function getIdType(): EntityIdTypeEnum + { + if ($this->usesUuid) { + return EntityIdTypeEnum::UUID; + } + + if ($this->usesUlid) { + return EntityIdTypeEnum::ULID; + } + + return EntityIdTypeEnum::INT; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeAuthenticator.php b/vendor/symfony/maker-bundle/src/Maker/MakeAuthenticator.php new file mode 100644 index 0000000..155f44f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeAuthenticator.php @@ -0,0 +1,471 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper; +use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater; +use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlManipulationFailedException; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; +use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\SecurityRequestAttributes; +use Symfony\Component\Security\Http\Util\TargetPathTrait; +use Symfony\Component\Yaml\Yaml; + +/** + * @deprecated since MakerBundle v1.59.0, use any of the Security/Make* instead. + * + * @author Ryan Weaver + * @author Jesse Rushlow + * + * @internal + */ +final class MakeAuthenticator extends AbstractMaker +{ + private const AUTH_TYPE_EMPTY_AUTHENTICATOR = 'empty-authenticator'; + private const AUTH_TYPE_FORM_LOGIN = 'form-login'; + + private const REMEMBER_ME_TYPE_ALWAYS = 'always'; + private const REMEMBER_ME_TYPE_CHECKBOX = 'checkbox'; + + public function __construct( + private FileManager $fileManager, + private SecurityConfigUpdater $configUpdater, + private Generator $generator, + private DoctrineHelper $doctrineHelper, + private SecurityControllerBuilder $securityControllerBuilder, + ) { + } + + public static function getCommandName(): string + { + return 'make:auth'; + } + + public static function getCommandDescription(): string + { + return 'Create a Guard authenticator of different flavors'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeAuth.txt')); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + trigger_deprecation('symfony/maker-bundle', 'v1.59.0', 'The "%s" class is deprecated, use any of the Security\Make* commands instead.', self::class); + + $io->caution('"make:auth" is deprecated, use any of the "make:security" commands instead.'); + + if (!$this->fileManager->fileExists($path = 'config/packages/security.yaml')) { + throw new RuntimeCommandException('The file "config/packages/security.yaml" does not exist. PHP & XML configuration formats are currently not supported.'); + } + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path)); + $securityData = $manipulator->getData(); + + // authenticator type + $authenticatorTypeValues = [ + 'Empty authenticator' => self::AUTH_TYPE_EMPTY_AUTHENTICATOR, + 'Login form authenticator' => self::AUTH_TYPE_FORM_LOGIN, + ]; + $command->addArgument('authenticator-type', InputArgument::REQUIRED); + $authenticatorType = $io->choice( + 'What style of authentication do you want?', + array_keys($authenticatorTypeValues), + key($authenticatorTypeValues) + ); + $input->setArgument( + 'authenticator-type', + $authenticatorTypeValues[$authenticatorType] + ); + + if (self::AUTH_TYPE_FORM_LOGIN === $input->getArgument('authenticator-type')) { + $neededDependencies = [TwigBundle::class => 'twig']; + $missingPackagesMessage = $this->addDependencies($neededDependencies, 'Twig must be installed to display the login form.'); + + if ($missingPackagesMessage) { + throw new RuntimeCommandException($missingPackagesMessage); + } + + if (!isset($securityData['security']['providers']) || !$securityData['security']['providers']) { + throw new RuntimeCommandException('To generate a form login authentication, you must configure at least one entry under "providers" in "security.yaml".'); + } + } + + // authenticator class + $command->addArgument('authenticator-class', InputArgument::REQUIRED); + $questionAuthenticatorClass = new Question('The class name of the authenticator to create (e.g. AppCustomAuthenticator)'); + $questionAuthenticatorClass->setValidator( + function ($answer) { + Validator::notBlank($answer); + + return Validator::classDoesNotExist( + $this->generator->createClassNameDetails($answer, 'Security\\', 'Authenticator')->getFullName() + ); + } + ); + $input->setArgument('authenticator-class', $io->askQuestion($questionAuthenticatorClass)); + + $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $command->addOption('firewall-name', null, InputOption::VALUE_OPTIONAL); + $input->setOption('firewall-name', $interactiveSecurityHelper->guessFirewallName($io, $securityData)); + + $command->addOption('entry-point', null, InputOption::VALUE_OPTIONAL); + + if (self::AUTH_TYPE_FORM_LOGIN === $input->getArgument('authenticator-type')) { + $command->addArgument('controller-class', InputArgument::REQUIRED); + $input->setArgument( + 'controller-class', + $io->ask( + 'Choose a name for the controller class (e.g. SecurityController)', + 'SecurityController', + Validator::validateClassName(...) + ) + ); + + $command->addArgument('user-class', InputArgument::REQUIRED); + $input->setArgument( + 'user-class', + $userClass = $interactiveSecurityHelper->guessUserClass($io, $securityData['security']['providers']) + ); + + $command->addArgument('username-field', InputArgument::REQUIRED); + $input->setArgument( + 'username-field', + $interactiveSecurityHelper->guessUserNameField($io, $userClass, $securityData['security']['providers']) + ); + + $command->addArgument('logout-setup', InputArgument::REQUIRED); + $input->setArgument( + 'logout-setup', + $io->confirm( + 'Do you want to generate a \'/logout\' URL?', + true + ) + ); + + $command->addArgument('support-remember-me', InputArgument::REQUIRED); + $input->setArgument( + 'support-remember-me', + $io->confirm( + 'Do you want to support remember me?', + true + ) + ); + + if ($input->getArgument('support-remember-me')) { + $supportRememberMeValues = [ + 'Activate when the user checks a box' => self::REMEMBER_ME_TYPE_CHECKBOX, + 'Always activate remember me' => self::REMEMBER_ME_TYPE_ALWAYS, + ]; + $command->addArgument('always-remember-me', InputArgument::REQUIRED); + + $supportRememberMeType = $io->choice( + 'How should remember me be activated?', + array_keys($supportRememberMeValues), + key($supportRememberMeValues) + ); + + $input->setArgument( + 'always-remember-me', + $supportRememberMeValues[$supportRememberMeType] + ); + } + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents('config/packages/security.yaml')); + $securityData = $manipulator->getData(); + + $supportRememberMe = $input->hasArgument('support-remember-me') ? $input->getArgument('support-remember-me') : false; + $alwaysRememberMe = $input->hasArgument('always-remember-me') && self::REMEMBER_ME_TYPE_ALWAYS === $input->getArgument('always-remember-me'); + + $this->generateAuthenticatorClass( + $securityData, + $input->getArgument('authenticator-type'), + $input->getArgument('authenticator-class'), + $input->hasArgument('user-class') ? $input->getArgument('user-class') : null, + $input->hasArgument('username-field') ? $input->getArgument('username-field') : null, + $supportRememberMe, + ); + + // update security.yaml with guard config + $securityYamlUpdated = false; + + $entryPoint = $input->getOption('entry-point'); + + if (self::AUTH_TYPE_FORM_LOGIN !== $input->getArgument('authenticator-type')) { + $entryPoint = false; + } + + try { + $newYaml = $this->configUpdater->updateForAuthenticator( + $this->fileManager->getFileContents($path = 'config/packages/security.yaml'), + $input->getOption('firewall-name'), + $entryPoint, + $input->getArgument('authenticator-class'), + $input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false, + $supportRememberMe, + $alwaysRememberMe + ); + $generator->dumpFile($path, $newYaml); + $securityYamlUpdated = true; + } catch (YamlManipulationFailedException) { + } + + if (self::AUTH_TYPE_FORM_LOGIN === $input->getArgument('authenticator-type')) { + $this->generateFormLoginFiles( + $input->getArgument('controller-class'), + $input->getArgument('username-field'), + $input->getArgument('logout-setup'), + $supportRememberMe, + $alwaysRememberMe, + ); + } + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text( + $this->generateNextMessage( + $securityYamlUpdated, + $input->getArgument('authenticator-type'), + $input->getArgument('authenticator-class'), + $input->hasArgument('user-class') ? $input->getArgument('user-class') : null, + $input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false, + $supportRememberMe, + $alwaysRememberMe + ) + ); + } + + /** @param array $securityData */ + private function generateAuthenticatorClass(array $securityData, string $authenticatorType, string $authenticatorClass, ?string $userClass, ?string $userNameField, bool $supportRememberMe): void + { + $useStatements = new UseStatementGenerator([ + Request::class, + Response::class, + TokenInterface::class, + Passport::class, + ]); + + // generate authenticator class + if (self::AUTH_TYPE_EMPTY_AUTHENTICATOR === $authenticatorType) { + $useStatements->addUseStatement([ + AuthenticationException::class, + AbstractAuthenticator::class, + ]); + + $this->generator->generateClass( + $authenticatorClass, + 'authenticator/EmptyAuthenticator.tpl.php', + ['use_statements' => $useStatements] + ); + + return; + } + + $useStatements->addUseStatement([ + RedirectResponse::class, + UrlGeneratorInterface::class, + AbstractLoginFormAuthenticator::class, + CsrfTokenBadge::class, + UserBadge::class, + PasswordCredentials::class, + TargetPathTrait::class, + SecurityRequestAttributes::class, + ]); + + if ($supportRememberMe) { + $useStatements->addUseStatement(RememberMeBadge::class); + } + + $userClassNameDetails = $this->generator->createClassNameDetails( + '\\'.$userClass, + 'Entity\\' + ); + + $this->generator->generateClass( + $authenticatorClass, + 'authenticator/LoginFormAuthenticator.tpl.php', + [ + 'use_statements' => $useStatements, + 'user_fully_qualified_class_name' => trim($userClassNameDetails->getFullName(), '\\'), + 'user_class_name' => $userClassNameDetails->getShortName(), + 'username_field' => $userNameField, + 'username_field_label' => Str::asHumanWords($userNameField), + 'username_field_var' => Str::asLowerCamelCase($userNameField), + 'user_needs_encoder' => $this->userClassHasEncoder($securityData, $userClass), + 'user_is_entity' => $this->doctrineHelper->isClassAMappedEntity($userClass), + 'remember_me_badge' => $supportRememberMe, + ] + ); + } + + private function generateFormLoginFiles(string $controllerClass, string $userNameField, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe): void + { + $controllerClassNameDetails = $this->generator->createClassNameDetails( + $controllerClass, + 'Controller\\', + 'Controller' + ); + + if (!class_exists($controllerClassNameDetails->getFullName())) { + $useStatements = new UseStatementGenerator([ + AbstractController::class, + Route::class, + AuthenticationUtils::class, + ]); + + $controllerPath = $this->generator->generateController( + $controllerClassNameDetails->getFullName(), + 'authenticator/EmptySecurityController.tpl.php', + ['use_statements' => $useStatements] + ); + + $controllerSourceCode = $this->generator->getFileContentsForPendingOperation($controllerPath); + } else { + $controllerPath = $this->fileManager->getRelativePathForFutureClass($controllerClassNameDetails->getFullName()); + $controllerSourceCode = $this->fileManager->getFileContents($controllerPath); + } + + if (method_exists($controllerClassNameDetails->getFullName(), 'login')) { + throw new RuntimeCommandException(\sprintf('Method "login" already exists on class %s', $controllerClassNameDetails->getFullName())); + } + + $manipulator = new ClassSourceManipulator( + sourceCode: $controllerSourceCode, + overwrite: true + ); + + $this->securityControllerBuilder->addLoginMethod($manipulator); + + if ($logoutSetup) { + $this->securityControllerBuilder->addLogoutMethod($manipulator); + } + + $this->generator->dumpFile($controllerPath, $manipulator->getSourceCode()); + + // create login form template + $this->generator->generateTemplate( + 'security/login.html.twig', + 'authenticator/login_form.tpl.php', + [ + 'username_field' => $userNameField, + 'username_is_email' => false !== stripos($userNameField, 'email'), + 'username_label' => ucfirst(Str::asHumanWords($userNameField)), + 'logout_setup' => $logoutSetup, + 'support_remember_me' => $supportRememberMe, + 'always_remember_me' => $alwaysRememberMe, + ] + ); + } + + /** @return string[] */ + private function generateNextMessage(bool $securityYamlUpdated, string $authenticatorType, string $authenticatorClass, ?string $userClass, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe): array + { + $nextTexts = ['Next:']; + $nextTexts[] = '- Customize your new authenticator.'; + + if (!$securityYamlUpdated) { + $yamlExample = $this->configUpdater->updateForAuthenticator( + 'security: {}', + 'main', + null, + $authenticatorClass, + $logoutSetup, + $supportRememberMe, + $alwaysRememberMe + ); + $nextTexts[] = "- Your security.yaml could not be updated automatically. You'll need to add the following config manually:\n\n".$yamlExample; + } + + if (self::AUTH_TYPE_FORM_LOGIN === $authenticatorType) { + $nextTexts[] = \sprintf('- Finish the redirect "TODO" in the %s::onAuthenticationSuccess() method.', $authenticatorClass); + + if (!$this->doctrineHelper->isClassAMappedEntity($userClass)) { + $nextTexts[] = \sprintf('- Review %s::getUser() to make sure it matches your needs.', $authenticatorClass); + } + + $nextTexts[] = '- Review & adapt the login template: '.$this->fileManager->getPathForTemplate('security/login.html.twig').'.'; + } + + return $nextTexts; + } + + /** @param array $securityData */ + private function userClassHasEncoder(array $securityData, string $userClass): bool + { + $userNeedsEncoder = false; + $hashersData = $securityData['security']['encoders'] ?? []; + + foreach ($hashersData as $userClassWithEncoder => $encoder) { + if ($userClass === $userClassWithEncoder || is_subclass_of($userClass, $userClassWithEncoder) || class_implements($userClass, $userClassWithEncoder)) { + $userNeedsEncoder = true; + } + } + + return $userNeedsEncoder; + } + + public function configureDependencies(DependencyBuilder $dependencies, ?InputInterface $input = null): void + { + $dependencies->addClassDependency( + SecurityBundle::class, + 'security' + ); + + // needed to update the YAML files + $dependencies->addClassDependency( + Yaml::class, + 'yaml' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeCommand.php b/vendor/symfony/maker-bundle/src/Maker/MakeCommand.php new file mode 100644 index 0000000..e8f586d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeCommand.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeCommand extends AbstractMaker +{ + public function __construct(private ?PhpCompatUtil $phpCompatUtil = null) + { + if (null !== $phpCompatUtil) { + @trigger_deprecation( + 'symfony/maker-bundle', + '1.55.0', + \sprintf('Initializing MakeCommand while providing an instance of "%s" is deprecated. The $phpCompatUtil param will be removed in a future version.', PhpCompatUtil::class), + ); + } + } + + public static function getCommandName(): string + { + return 'make:command'; + } + + public static function getCommandDescription(): string + { + return 'Create a new console command class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, \sprintf('Choose a command name (e.g. app:%s)', Str::asCommand(Str::getRandomTerm()))) + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeCommand.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $commandName = trim($input->getArgument('name')); + $commandNameHasAppPrefix = str_starts_with($commandName, 'app:'); + + $commandClassNameDetails = $generator->createClassNameDetails( + $commandNameHasAppPrefix ? substr($commandName, 4) : $commandName, + 'Command\\', + 'Command', + \sprintf('The "%s" command name is not valid because it would be implemented by "%s" class, which is not valid as a PHP class name (it must start with a letter or underscore, followed by any number of letters, numbers, or underscores).', $commandName, Str::asClassName($commandName, 'Command')) + ); + + $useStatements = new UseStatementGenerator([ + Command::class, + InputArgument::class, + InputInterface::class, + InputOption::class, + OutputInterface::class, + SymfonyStyle::class, + AsCommand::class, + ]); + + $generator->generateClass( + $commandClassNameDetails->getFullName(), + 'command/Command.tpl.php', + [ + 'use_statements' => $useStatements, + 'command_name' => $commandName, + 'set_description' => !class_exists(LazyCommand::class), + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $io->text([ + 'Next: open your new command class and customize it!', + 'Find the documentation at https://symfony.com/doc/current/console.html', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + Command::class, + 'console' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeController.php b/vendor/symfony/maker-bundle/src/Maker/MakeController.php new file mode 100644 index 0000000..4cf84e1 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeController.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeController extends AbstractMaker +{ + public function __construct(private ?PhpCompatUtil $phpCompatUtil = null) + { + if (null !== $phpCompatUtil) { + @trigger_deprecation( + 'symfony/maker-bundle', + '1.55.0', + \sprintf('Initializing MakeCommand while providing an instance of "%s" is deprecated. The $phpCompatUtil param will be removed in a future version.', PhpCompatUtil::class) + ); + } + } + + public static function getCommandName(): string + { + return 'make:controller'; + } + + public static function getCommandDescription(): string + { + return 'Create a new controller class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('controller-class', InputArgument::OPTIONAL, \sprintf('Choose a name for your controller class (e.g. %sController)', Str::asClassName(Str::getRandomTerm()))) + ->addOption('no-template', null, InputOption::VALUE_NONE, 'Use this option to disable template generation') + ->addOption('invokable', 'i', InputOption::VALUE_NONE, 'Use this option to create an invokable controller') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeController.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $controllerClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('controller-class'), + 'Controller\\', + 'Controller' + ); + + $withTemplate = $this->isTwigInstalled() && !$input->getOption('no-template'); + $isInvokable = (bool) $input->getOption('invokable'); + + $useStatements = new UseStatementGenerator([ + AbstractController::class, + $withTemplate ? Response::class : JsonResponse::class, + Route::class, + ]); + + $templateName = Str::asFilePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()) + .($isInvokable ? '.html.twig' : '/index.html.twig'); + + $controllerPath = $generator->generateController( + $controllerClassNameDetails->getFullName(), + 'controller/Controller.tpl.php', + [ + 'use_statements' => $useStatements, + 'route_path' => Str::asRoutePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()), + 'route_name' => Str::asRouteName($controllerClassNameDetails->getRelativeNameWithoutSuffix()), + 'method_name' => $isInvokable ? '__invoke' : 'index', + 'with_template' => $withTemplate, + 'template_name' => $templateName, + ] + ); + + if ($withTemplate) { + $generator->generateTemplate( + $templateName, + 'controller/twig_template.tpl.php', + [ + 'controller_path' => $controllerPath, + 'root_directory' => $generator->getRootDirectory(), + 'class_name' => $controllerClassNameDetails->getShortName(), + ] + ); + } + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $io->text('Next: Open your new controller class and add some pages!'); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + } + + private function isTwigInstalled(): bool + { + return class_exists(TwigBundle::class); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeCrud.php b/vendor/symfony/maker-bundle/src/Maker/MakeCrud.php new file mode 100644 index 0000000..0a37b2b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeCrud.php @@ -0,0 +1,327 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Doctrine\Inflector\Inflector; +use Doctrine\Inflector\InflectorFactory; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait; +use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Validator\Validation; + +/** + * @author Sadicov Vladimir + */ +final class MakeCrud extends AbstractMaker +{ + use CanGenerateTestsTrait; + + private Inflector $inflector; + private string $controllerClassName; + private bool $generateTests = false; + + public function __construct(private DoctrineHelper $doctrineHelper, private FormTypeRenderer $formTypeRenderer) + { + $this->inflector = InflectorFactory::create()->build(); + } + + public static function getCommandName(): string + { + return 'make:crud'; + } + + public static function getCommandDescription(): string + { + return 'Create CRUD for Doctrine entity class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('entity-class', InputArgument::OPTIONAL, \sprintf('The class name of the entity to create CRUD (e.g. %s)', Str::asClassName(Str::getRandomTerm()))) + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeCrud.txt')) + ; + + $inputConfig->setArgumentAsNonInteractive('entity-class'); + $this->configureCommandWithTestsOption($command); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (null === $input->getArgument('entity-class')) { + $argument = $command->getDefinition()->getArgument('entity-class'); + + $entities = $this->doctrineHelper->getEntitiesForAutocomplete(); + + $question = new Question($argument->getDescription()); + $question->setAutocompleterValues($entities); + + $value = $io->askQuestion($question); + + $input->setArgument('entity-class', $value); + } + + $defaultControllerClass = Str::asClassName(\sprintf('%s Controller', $input->getArgument('entity-class'))); + + $this->controllerClassName = $io->ask( + \sprintf('Choose a name for your controller class (e.g. %s)', $defaultControllerClass), + $defaultControllerClass + ); + + $this->interactSetGenerateTests($input, $io); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $entityClassDetails = $generator->createClassNameDetails( + Validator::entityExists($input->getArgument('entity-class'), $this->doctrineHelper->getEntitiesForAutocomplete()), + 'Entity\\' + ); + + $entityDoctrineDetails = $this->doctrineHelper->createDoctrineDetails($entityClassDetails->getFullName()); + + $repositoryVars = []; + $repositoryClassName = EntityManagerInterface::class; + + if (null !== $entityDoctrineDetails->getRepositoryClass()) { + $repositoryClassDetails = $generator->createClassNameDetails( + '\\'.$entityDoctrineDetails->getRepositoryClass(), + 'Repository\\', + 'Repository' + ); + + $repositoryClassName = $repositoryClassDetails->getFullName(); + + $repositoryVars = [ + 'repository_full_class_name' => $repositoryClassName, + 'repository_class_name' => $repositoryClassDetails->getShortName(), + 'repository_var' => lcfirst($this->inflector->singularize($repositoryClassDetails->getShortName())), + ]; + } + + $controllerClassDetails = $generator->createClassNameDetails( + $this->controllerClassName, + 'Controller\\', + 'Controller' + ); + + $iter = 0; + do { + $formClassDetails = $generator->createClassNameDetails( + $entityClassDetails->getRelativeNameWithoutSuffix().($iter ?: '').'Type', + 'Form\\', + 'Type' + ); + ++$iter; + } while (class_exists($formClassDetails->getFullName())); + + $controllerClassData = ClassData::create( + class: \sprintf('Controller\%s', $this->controllerClassName), + suffix: 'Controller', + extendsClass: AbstractController::class, + useStatements: [ + $entityClassDetails->getFullName(), + $formClassDetails->getFullName(), + $repositoryClassName, + AbstractController::class, + Request::class, + Response::class, + Route::class, + ], + ); + + $entityVarPlural = lcfirst($this->inflector->pluralize($entityClassDetails->getShortName())); + $entityVarSingular = lcfirst($this->inflector->singularize($entityClassDetails->getShortName())); + + $entityTwigVarPlural = Str::asTwigVariable($entityVarPlural); + $entityTwigVarSingular = Str::asTwigVariable($entityVarSingular); + + $routeName = Str::asRouteName($controllerClassDetails->getRelativeNameWithoutSuffix()); + $templatesPath = Str::asFilePath($controllerClassDetails->getRelativeNameWithoutSuffix()); + + if (EntityManagerInterface::class !== $repositoryClassName) { + $controllerClassData->addUseStatement(EntityManagerInterface::class); + } + + $generator->generateController( + $controllerClassData->getFullClassName(), + 'crud/controller/Controller.tpl.php', + array_merge([ + 'class_data' => $controllerClassData, + 'entity_class_name' => $entityClassDetails->getShortName(), + 'form_class_name' => $formClassDetails->getShortName(), + 'route_path' => Str::asRoutePath($controllerClassDetails->getRelativeNameWithoutSuffix()), + 'route_name' => $routeName, + 'templates_path' => $templatesPath, + 'entity_var_plural' => $entityVarPlural, + 'entity_twig_var_plural' => $entityTwigVarPlural, + 'entity_var_singular' => $entityVarSingular, + 'entity_twig_var_singular' => $entityTwigVarSingular, + 'entity_identifier' => $entityDoctrineDetails->getIdentifier(), + ], + $repositoryVars + ) + ); + + $this->formTypeRenderer->render( + $formClassDetails, + $entityDoctrineDetails->getFormFields(), + $entityClassDetails + ); + + $templates = [ + '_delete_form' => [ + 'route_name' => $routeName, + 'entity_twig_var_singular' => $entityTwigVarSingular, + 'entity_identifier' => $entityDoctrineDetails->getIdentifier(), + ], + '_form' => [], + 'edit' => [ + 'entity_class_name' => $entityClassDetails->getShortName(), + 'entity_twig_var_singular' => $entityTwigVarSingular, + 'entity_identifier' => $entityDoctrineDetails->getIdentifier(), + 'route_name' => $routeName, + 'templates_path' => $templatesPath, + ], + 'index' => [ + 'entity_class_name' => $entityClassDetails->getShortName(), + 'entity_twig_var_plural' => $entityTwigVarPlural, + 'entity_twig_var_singular' => $entityTwigVarSingular, + 'entity_identifier' => $entityDoctrineDetails->getIdentifier(), + 'entity_fields' => $entityDoctrineDetails->getDisplayFields(), + 'route_name' => $routeName, + ], + 'new' => [ + 'entity_class_name' => $entityClassDetails->getShortName(), + 'route_name' => $routeName, + 'templates_path' => $templatesPath, + ], + 'show' => [ + 'entity_class_name' => $entityClassDetails->getShortName(), + 'entity_twig_var_singular' => $entityTwigVarSingular, + 'entity_identifier' => $entityDoctrineDetails->getIdentifier(), + 'entity_fields' => $entityDoctrineDetails->getDisplayFields(), + 'route_name' => $routeName, + 'templates_path' => $templatesPath, + ], + ]; + + foreach ($templates as $template => $variables) { + $generator->generateTemplate( + $templatesPath.'/'.$template.'.html.twig', + 'crud/templates/'.$template.'.tpl.php', + $variables + ); + } + + if ($this->shouldGenerateTests()) { + $testClassData = ClassData::create( + class: \sprintf('Tests\Controller\%s', $entityClassDetails->getRelativeNameWithoutSuffix()), + suffix: 'ControllerTest', + extendsClass: WebTestCase::class, + useStatements: [ + $entityClassDetails->getFullName(), + WebTestCase::class, + KernelBrowser::class, + $repositoryClassName, + EntityRepository::class, + ], + ); + + if (EntityManagerInterface::class !== $repositoryClassName) { + $testClassData->addUseStatement(EntityManagerInterface::class); + } + + $generator->generateClass( + $testClassData->getFullClassName(), + 'crud/test/Test.EntityManager.tpl.php', + [ + 'class_data' => $testClassData, + 'entity_full_class_name' => $entityClassDetails->getFullName(), + 'entity_class_name' => $entityClassDetails->getShortName(), + 'entity_var_singular' => $entityVarSingular, + 'route_path' => Str::asRoutePath($controllerClassDetails->getRelativeNameWithoutSuffix()), + 'route_name' => $routeName, + 'form_fields' => $entityDoctrineDetails->getFormFields(), + 'repository_class_name' => EntityManagerInterface::class, + 'form_field_prefix' => strtolower(Str::asSnakeCase($entityTwigVarSingular)), + ] + ); + + if (!class_exists(WebTestCase::class)) { + $io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.'); + } + } + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text(\sprintf('Next: Check your new CRUD by going to %s/', Str::asRoutePath($controllerClassDetails->getRelativeNameWithoutSuffix()))); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + Route::class, + 'router' + ); + + $dependencies->addClassDependency( + AbstractType::class, + 'form' + ); + + $dependencies->addClassDependency( + Validation::class, + 'validator' + ); + + $dependencies->addClassDependency( + TwigBundle::class, + 'twig-bundle' + ); + + $dependencies->addClassDependency( + DoctrineBundle::class, + 'orm' + ); + + $dependencies->addClassDependency( + CsrfTokenManager::class, + 'security-csrf' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeDockerDatabase.php b/vendor/symfony/maker-bundle/src/Maker/MakeDockerDatabase.php new file mode 100644 index 0000000..bb83916 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeDockerDatabase.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Docker\DockerDatabaseServices; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\ComposeFileManipulator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Yaml\Yaml; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class MakeDockerDatabase extends AbstractMaker +{ + private string $composeFilePath; + private ?ComposeFileManipulator $composeFileManipulator = null; + + /** + * @var ?string type of database selected by the user + */ + private ?string $databaseChoice = null; + + /** + * @var string Service identifier to be set in compose.yaml + */ + private string $serviceName = 'database'; + + /** + * @var string Version set in compose.yaml for the service. e.g. latest + */ + private string $serviceVersion = 'latest'; + + public function __construct(private FileManager $fileManager) + { + } + + public static function getCommandName(): string + { + return 'make:docker:database'; + } + + public static function getCommandDescription(): string + { + return 'Add a database container to your compose.yaml file'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeDockerDatabase.txt')) + ; + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $io->section('- Docker Compose Setup-'); + + $this->composeFileManipulator = new ComposeFileManipulator($this->getComposeFileContents($io)); + + $io->newLine(); + + $this->databaseChoice = strtolower($io->choice( + 'Which database service will you be creating?', + ['MySQL', 'MariaDB', 'Postgres'] + )); + + $io->text([\sprintf( + 'For a list of supported versions, check out https://hub.docker.com/_/%s', + $this->databaseChoice + )]); + + $this->serviceVersion = $io->ask('What version would you like to use?', DockerDatabaseServices::getSuggestedServiceVersion($this->databaseChoice)); + + if ($this->composeFileManipulator->serviceExists($this->serviceName)) { + $io->comment(\sprintf('A "%s" service is already defined.', $this->serviceName)); + $io->newLine(); + + $serviceNameMsg[] = 'If you are using the Symfony Binary, it will expose the connection config for'; + $serviceNameMsg[] = 'this service as environment variables. The name of the service determines the'; + $serviceNameMsg[] = 'name of those environment variables.'; + $serviceNameMsg[] = ''; + $serviceNameMsg[] = 'For example, if you name the service database_alt, the binary will expose a'; + $serviceNameMsg[] = 'DATABASE_ALT_URL environment variable.'; + + $io->text($serviceNameMsg); + + $this->serviceName = $io->ask(\sprintf('What name should we call the new %s service? (e.g. database)', $this->serviceName), null, Validator::notBlank(...)); + } + + $this->checkForPDOSupport($this->databaseChoice, $io); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $io->newLine(); + + $service = DockerDatabaseServices::getDatabaseSkeleton($this->databaseChoice, $this->serviceVersion); + + $this->composeFileManipulator->addDockerService($this->serviceName, $service); + $this->composeFileManipulator->exposePorts($this->serviceName, DockerDatabaseServices::getDefaultPorts($this->databaseChoice)); + + $generator->dumpFile($this->composeFilePath, $this->composeFileManipulator->getDataString()); + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text(\sprintf('The new "%s" service is now ready!', $this->serviceName)); + $io->newLine(); + + $ports = DockerDatabaseServices::getDefaultPorts($this->databaseChoice); + $closing[] = 'Next:'; + $closing[] = \sprintf(' A) Run docker-compose up -d %s to start your database container', $this->serviceName); + $closing[] = ' or docker-compose up -d to start all of them.'; + $closing[] = ''; + $closing[] = ' B) If you are using the Symfony Binary, it will detect the new service automatically.'; + $closing[] = ' Run symfony var:export --multiline to see the environment variables the binary is exposing.'; + $closing[] = ' These will override any values you have in your .env files.'; + $closing[] = ''; + $closing[] = ' C) Run docker-compose stop will stop all the containers in compose.yaml.'; + $closing[] = ' docker-compose down will stop and destroy the containers.'; + $closing[] = ''; + $closing[] = \sprintf( + 'Port%s %s will be exposed to %s random port%s on your host machine.', + 1 === \count($ports) ? '' : 's', + implode(' ', $ports), + 1 === \count($ports) ? 'a' : '', + 1 === \count($ports) ? '' : 's' + ); + + $io->text($closing); + $io->newLine(); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + Yaml::class, + 'yaml' + ); + } + + private function checkForPDOSupport(string $databaseType, ConsoleStyle $io): void + { + $extension = DockerDatabaseServices::getMissingExtensionName($databaseType); + + if (null !== $extension) { + $io->note( + \sprintf('Cannot find PHP\'s pdo_%s extension. Be sure it\'s installed & enabled to talk to the database.', $extension) + ); + } + } + + /** + * Determines and sets the correct Compose File Path and retrieves its contents + * if the file exists else an empty string. + */ + private function getComposeFileContents(ConsoleStyle $io): string + { + $this->composeFilePath = \sprintf('%s/compose.yaml', $this->fileManager->getRootDirectory()); + + $composeFileExists = false; + $statusMessage = 'Existing compose.yaml not found: a new one will be generated!'; + $contents = ''; + + foreach (['.yml', '.yaml'] as $extension) { + $composeFilePath = \sprintf('%s/compose%s', $this->fileManager->getRootDirectory(), $extension); + + if (!$composeFileExists && $this->fileManager->fileExists($composeFilePath)) { + $composeFileExists = true; + + $statusMessage = \sprintf('We found your existing compose%s: Let\'s update it!', $extension); + + $this->composeFilePath = $composeFilePath; + $contents = $this->fileManager->getFileContents($composeFilePath); + } + } + + $io->text($statusMessage); + + return $contents; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeEntity.php b/vendor/symfony/maker-bundle/src/Maker/MakeEntity.php new file mode 100644 index 0000000..dfc2530 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeEntity.php @@ -0,0 +1,899 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use ApiPlatform\Metadata\ApiResource; +use Doctrine\DBAL\Types\Type; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Doctrine\EntityClassGenerator; +use Symfony\Bundle\MakerBundle\Doctrine\EntityRegenerator; +use Symfony\Bundle\MakerBundle\Doctrine\EntityRelation; +use Symfony\Bundle\MakerBundle\Doctrine\ORMDependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputAwareMakerInterface; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\Common\UidTrait; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassDetails; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\CliOutputHelper; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Bundle\MercureBundle\DependencyInjection\MercureExtension; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\UX\Turbo\Attribute\Broadcast; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + * @author Kévin Dunglas + */ +final class MakeEntity extends AbstractMaker implements InputAwareMakerInterface +{ + use UidTrait; + + private Generator $generator; + private EntityClassGenerator $entityClassGenerator; + + public function __construct( + private FileManager $fileManager, + private DoctrineHelper $doctrineHelper, + ?string $projectDirectory = null, + ?Generator $generator = null, + ?EntityClassGenerator $entityClassGenerator = null, + ) { + if (null !== $projectDirectory) { + @trigger_error('The $projectDirectory constructor argument is no longer used since 1.41.0', \E_USER_DEPRECATED); + } + + if (null === $generator) { + @trigger_error(\sprintf('Passing a "%s" instance as 4th argument is mandatory since version 1.5.', Generator::class), \E_USER_DEPRECATED); + $this->generator = new Generator($fileManager, 'App\\'); + } else { + $this->generator = $generator; + } + + if (null === $entityClassGenerator) { + @trigger_error(\sprintf('Passing a "%s" instance as 5th argument is mandatory since version 1.15.1', EntityClassGenerator::class), \E_USER_DEPRECATED); + $this->entityClassGenerator = new EntityClassGenerator($generator, $this->doctrineHelper); + } else { + $this->entityClassGenerator = $entityClassGenerator; + } + } + + public static function getCommandName(): string + { + return 'make:entity'; + } + + public static function getCommandDescription(): string + { + return 'Create or update a Doctrine entity class, and optionally an API Platform resource'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, \sprintf('Class name of the entity to create or update (e.g. %s)', Str::asClassName(Str::getRandomTerm()))) + ->addOption('api-resource', 'a', InputOption::VALUE_NONE, 'Mark this class as an API Platform resource (expose a CRUD API for it)') + ->addOption('broadcast', 'b', InputOption::VALUE_NONE, 'Add the ability to broadcast entity updates using Symfony UX Turbo?') + ->addOption('regenerate', null, InputOption::VALUE_NONE, 'Instead of adding new fields, simply generate the methods (e.g. getter/setter) for existing fields') + ->addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite any existing getter/setter methods') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeEntity.txt')) + ; + + $this->addWithUuidOption($command); + + $inputConfig->setArgumentAsNonInteractive('name'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (($entityClassName = $input->getArgument('name')) && empty($this->verifyEntityName($entityClassName))) { + return; + } + + if ($input->getOption('regenerate')) { + $io->block([ + 'This command will generate any missing methods (e.g. getters & setters) for a class or all classes in a namespace.', + 'To overwrite any existing methods, re-run this command with the --overwrite flag', + ], null, 'fg=yellow'); + $classOrNamespace = $io->ask('Enter a class or namespace to regenerate', $this->getEntityNamespace(), Validator::notBlank(...)); + + $input->setArgument('name', $classOrNamespace); + + return; + } + + $this->checkIsUsingUid($input); + + $argument = $command->getDefinition()->getArgument('name'); + $question = $this->createEntityClassQuestion($argument->getDescription()); + $entityClassName ??= $io->askQuestion($question); + + while ($dangerous = $this->verifyEntityName($entityClassName)) { + if ($io->confirm(\sprintf('"%s" contains one or more non-ASCII characters, which are potentially problematic with some database. It is recommended to use only ASCII characters for entity names. Continue anyway?', $entityClassName), false)) { + break; + } + + $entityClassName = $io->askQuestion($question); + } + + $input->setArgument('name', $entityClassName); + + if ( + !$input->getOption('api-resource') + && class_exists(ApiResource::class) + && !class_exists($this->generator->createClassNameDetails($entityClassName, 'Entity\\')->getFullName()) + ) { + $description = $command->getDefinition()->getOption('api-resource')->getDescription(); + $question = new ConfirmationQuestion($description, false); + $isApiResource = $io->askQuestion($question); + + $input->setOption('api-resource', $isApiResource); + } + + if ( + !$input->getOption('broadcast') + && class_exists(Broadcast::class) + && !class_exists($this->generator->createClassNameDetails($entityClassName, 'Entity\\')->getFullName()) + ) { + $description = $command->getDefinition()->getOption('broadcast')->getDescription(); + $question = new ConfirmationQuestion($description, false); + $isBroadcast = $io->askQuestion($question); + + // Mercure is needed + if ($isBroadcast && !class_exists(MercureExtension::class)) { + throw new RuntimeCommandException('Please run "composer require symfony/mercure-bundle". It is needed to broadcast entities.'); + } + + $input->setOption('broadcast', $isBroadcast); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $overwrite = $input->getOption('overwrite'); + + // the regenerate option has entirely custom behavior + if ($input->getOption('regenerate')) { + $this->regenerateEntities($input->getArgument('name'), $overwrite, $generator); + $this->writeSuccessMessage($io); + + return; + } + + $entityClassDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Entity\\' + ); + + $classExists = class_exists($entityClassDetails->getFullName()); + if (!$classExists) { + $broadcast = $input->getOption('broadcast'); + $entityPath = $this->entityClassGenerator->generateEntityClass( + entityClassDetails: $entityClassDetails, + apiResource: $input->getOption('api-resource'), + broadcast: $broadcast, + useUuidIdentifier: $this->getIdType(), + ); + + if ($broadcast) { + $shortName = $entityClassDetails->getShortName(); + $generator->generateTemplate( + \sprintf('broadcast/%s.stream.html.twig', $shortName), + 'doctrine/broadcast_twig_template.tpl.php', + [ + 'class_name' => Str::asSnakeCase($shortName), + 'class_name_plural' => Str::asSnakeCase(Str::singularCamelCaseToPluralCamelCase($shortName)), + ] + ); + } + + $generator->writeChanges(); + } + + if ($classExists) { + $entityPath = $this->getPathOfClass($entityClassDetails->getFullName()); + $io->text([ + 'Your entity already exists! So let\'s add some new fields!', + ]); + } else { + $io->text([ + '', + 'Entity generated! Now let\'s add some fields!', + 'You can always add more fields later manually or by re-running this command.', + ]); + } + + $currentFields = $this->getPropertyNames($entityClassDetails->getFullName()); + $manipulator = $this->createClassManipulator($entityPath, $io, $overwrite); + + $isFirstField = true; + while (true) { + $newField = $this->askForNextField($io, $currentFields, $entityClassDetails->getFullName(), $isFirstField); + $isFirstField = false; + + if (null === $newField) { + break; + } + + $fileManagerOperations = []; + $fileManagerOperations[$entityPath] = $manipulator; + + if ($newField instanceof ClassProperty) { + $manipulator->addEntityField($newField); + + $currentFields[] = $newField->propertyName; + } elseif ($newField instanceof EntityRelation) { + // both overridden below for OneToMany + $newFieldName = $newField->getOwningProperty(); + if ($newField->isSelfReferencing()) { + $otherManipulatorFilename = $entityPath; + $otherManipulator = $manipulator; + } else { + $otherManipulatorFilename = $this->getPathOfClass($newField->getInverseClass()); + $otherManipulator = $this->createClassManipulator($otherManipulatorFilename, $io, $overwrite); + } + switch ($newField->getType()) { + case EntityRelation::MANY_TO_ONE: + if ($newField->getOwningClass() === $entityClassDetails->getFullName()) { + // THIS class will receive the ManyToOne + $manipulator->addManyToOneRelation($newField->getOwningRelation()); + + if ($newField->getMapInverseRelation()) { + $otherManipulator->addOneToManyRelation($newField->getInverseRelation()); + } + } else { + // the new field being added to THIS entity is the inverse + $newFieldName = $newField->getInverseProperty(); + $otherManipulatorFilename = $this->getPathOfClass($newField->getOwningClass()); + $otherManipulator = $this->createClassManipulator($otherManipulatorFilename, $io, $overwrite); + + // The *other* class will receive the ManyToOne + $otherManipulator->addManyToOneRelation($newField->getOwningRelation()); + if (!$newField->getMapInverseRelation()) { + throw new \Exception('Somehow a OneToMany relationship is being created, but the inverse side will not be mapped?'); + } + $manipulator->addOneToManyRelation($newField->getInverseRelation()); + } + + break; + case EntityRelation::MANY_TO_MANY: + $manipulator->addManyToManyRelation($newField->getOwningRelation()); + if ($newField->getMapInverseRelation()) { + $otherManipulator->addManyToManyRelation($newField->getInverseRelation()); + } + + break; + case EntityRelation::ONE_TO_ONE: + $manipulator->addOneToOneRelation($newField->getOwningRelation()); + if ($newField->getMapInverseRelation()) { + $otherManipulator->addOneToOneRelation($newField->getInverseRelation()); + } + + break; + default: + throw new \Exception('Invalid relation type'); + } + + // save the inverse side if it's being mapped + if ($newField->getMapInverseRelation()) { + $fileManagerOperations[$otherManipulatorFilename] = $otherManipulator; + } + $currentFields[] = $newFieldName; + } else { + throw new \Exception('Invalid value'); + } + + foreach ($fileManagerOperations as $path => $manipulatorOrMessage) { + if (\is_string($manipulatorOrMessage)) { /* @phpstan-ignore-line - https://github.com/symfony/maker-bundle/issues/1509 */ + $io->comment($manipulatorOrMessage); + } else { + $this->fileManager->dumpFile($path, $manipulatorOrMessage->getSourceCode()); + } + } + } + + $this->writeSuccessMessage($io); + $io->text([ + \sprintf('Next: When you\'re ready, create a migration with %s make:migration', CliOutputHelper::getCommandPrefix()), + '', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies, ?InputInterface $input = null): void + { + if (null !== $input && $input->getOption('api-resource')) { + $dependencies->addClassDependency( + ApiResource::class, + 'api' + ); + } + + if (null !== $input && $input->getOption('broadcast')) { + $dependencies->addClassDependency( + Broadcast::class, + 'symfony/ux-turbo' + ); + } + + ORMDependencyBuilder::buildDependencies($dependencies); + } + + /** @param string[] $fields */ + private function askForNextField(ConsoleStyle $io, array $fields, string $entityClass, bool $isFirstField): EntityRelation|ClassProperty|null + { + $io->writeln(''); + + if ($isFirstField) { + $questionText = 'New property name (press to stop adding fields)'; + } else { + $questionText = 'Add another property? Enter the property name (or press to stop adding fields)'; + } + + $fieldName = $io->ask($questionText, null, function ($name) use ($fields) { + // allow it to be empty + if (!$name) { + return $name; + } + + if (\in_array($name, $fields)) { + throw new \InvalidArgumentException(\sprintf('The "%s" property already exists.', $name)); + } + + return Validator::validateDoctrineFieldName($name, $this->doctrineHelper->getRegistry()); + }); + + if (!$fieldName) { + return null; + } + + $defaultType = 'string'; + // try to guess the type by the field name prefix/suffix + // convert to snake case for simplicity + $snakeCasedField = Str::asSnakeCase($fieldName); + + if ('_at' === $suffix = substr($snakeCasedField, -3)) { + $defaultType = 'datetime_immutable'; + } elseif ('_id' === $suffix) { + $defaultType = 'integer'; + } elseif (str_starts_with($snakeCasedField, 'is_')) { + $defaultType = 'boolean'; + } elseif (str_starts_with($snakeCasedField, 'has_')) { + $defaultType = 'boolean'; + } elseif ('uuid' === $snakeCasedField) { + $defaultType = Type::hasType('uuid') ? 'uuid' : 'guid'; + } elseif ('guid' === $snakeCasedField) { + $defaultType = 'guid'; + } + + $type = null; + $types = $this->getTypesMap(); + + $allValidTypes = array_merge( + array_keys($types), + EntityRelation::getValidRelationTypes(), + ['relation', 'enum'] + ); + while (null === $type) { + $question = new Question('Field type (enter ? to see all types)', $defaultType); + $question->setAutocompleterValues($allValidTypes); + $type = $io->askQuestion($question); + + if ('?' === $type) { + $this->printAvailableTypes($io); + $io->writeln(''); + + $type = null; + } elseif (!\in_array($type, $allValidTypes)) { + $this->printAvailableTypes($io); + $io->error(\sprintf('Invalid type "%s".', $type)); + $io->writeln(''); + + $type = null; + } + } + + if ('relation' === $type || \in_array($type, EntityRelation::getValidRelationTypes())) { + return $this->askRelationDetails($io, $entityClass, $type, $fieldName); + } + + // this is a normal field + $classProperty = new ClassProperty(propertyName: $fieldName, type: $type); + + if ('string' === $type) { + // default to 255, avoid the question + $classProperty->length = $io->ask('Field length', '255', Validator::validateLength(...)); + } elseif ('decimal' === $type) { + // 10 is the default value given in \Doctrine\DBAL\Schema\Column::$_precision + $classProperty->precision = $io->ask('Precision (total number of digits stored: 100.00 would be 5)', '10', Validator::validatePrecision(...)); + + // 0 is the default value given in \Doctrine\DBAL\Schema\Column::$_scale + $classProperty->scale = $io->ask('Scale (number of decimals to store: 100.00 would be 2)', '0', Validator::validateScale(...)); + } elseif ('enum' === $type) { + // ask for valid backed enum class + $classProperty->enumType = $io->ask('Enum class', null, Validator::classIsBackedEnum(...)); + + // set type according to user decision + $classProperty->type = $io->confirm('Can this field store multiple enum values', false) ? 'simple_array' : 'string'; + } + + if ($io->confirm('Can this field be null in the database (nullable)', false)) { + $classProperty->nullable = true; + } + + return $classProperty; + } + + private function printAvailableTypes(ConsoleStyle $io): void + { + $allTypes = $this->getTypesMap(); + + $typesTable = [ + 'main' => [ + 'string' => ['ascii_string'], + 'text' => [], + 'boolean' => [], + 'integer' => ['smallint', 'bigint'], + 'float' => [], + ], + 'array_object' => [ + 'array' => ['simple_array'], + 'json' => [], + 'object' => [], + 'binary' => [], + 'blob' => [], + ], + 'date_time' => [ + 'datetime' => ['datetime_immutable'], + 'datetimetz' => ['datetimetz_immutable'], + 'date' => ['date_immutable'], + 'time' => ['time_immutable'], + 'dateinterval' => [], + ], + 'other' => [ + 'enum' => [], + ], + ]; + + $printSection = static function (array $sectionTypes) use ($io, &$allTypes) { + foreach ($sectionTypes as $mainType => $subTypes) { + if (!\array_key_exists($mainType, $allTypes)) { + // The type is not a valid DBAL Type - don't show it as an option + continue; + } + + foreach ($subTypes as $key => $potentialType) { + if (!\array_key_exists($potentialType, $allTypes)) { + // The type is not a valid DBAL Type - don't show it as an "or" option + unset($subTypes[$key]); + } + + // Remove type as not to show it again in "Other Types" + unset($allTypes[$potentialType]); + } + + // Remove type as not to show it again in "Other Types" + unset($allTypes[$mainType]); + + $line = \sprintf(' * %s', $mainType); + + if (!empty($subTypes)) { + $line .= \sprintf(' or %s', implode(' or ', array_map( + static fn ($subType) => \sprintf('%s', $subType), $subTypes)) + ); + } + + $io->writeln($line); + } + + $io->writeln(''); + }; + + $printRelationsSection = static function () use ($io) { + if ('Hyper' === getenv('TERM_PROGRAM')) { + $wizard = 'wizard 🧙'; + } else { + $wizard = '\\' === \DIRECTORY_SEPARATOR ? 'wizard' : 'wizard 🧙'; + } + + $io->writeln(\sprintf(' * relation a %s will help you build the relation', $wizard)); + + $relations = [EntityRelation::MANY_TO_ONE, EntityRelation::ONE_TO_MANY, EntityRelation::MANY_TO_MANY, EntityRelation::ONE_TO_ONE]; + foreach ($relations as $relation) { + $line = \sprintf(' * %s', $relation); + + $io->writeln($line); + } + + $io->writeln(''); + }; + + $io->writeln('Main Types'); + $printSection($typesTable['main']); + + $io->writeln('Relationships/Associations'); + $printRelationsSection(); + + $io->writeln('Array/Object Types'); + $printSection($typesTable['array_object']); + + $io->writeln('Date/Time Types'); + $printSection($typesTable['date_time']); + + $io->writeln('Other Types'); + // empty the values + $allTypes = array_map(static fn () => [], $allTypes); + $allTypes = [...$typesTable['other'], ...$allTypes]; + $printSection($allTypes); + } + + private function createEntityClassQuestion(string $questionText): Question + { + $question = new Question($questionText); + $question->setValidator(Validator::notBlank(...)); + $question->setAutocompleterValues($this->doctrineHelper->getEntitiesForAutocomplete()); + + return $question; + } + + private function askRelationDetails(ConsoleStyle $io, string $generatedEntityClass, string $type, string $newFieldName): EntityRelation + { + // ask the targetEntity + $targetEntityClass = null; + while (null === $targetEntityClass) { + $question = $this->createEntityClassQuestion('What class should this entity be related to?'); + + $answeredEntityClass = $io->askQuestion($question); + + // find the correct class name - but give priority over looking + // in the Entity namespace versus just checking the full class + // name to avoid issues with classes like "Directory" that exist + // in PHP's core. + if (class_exists($this->getEntityNamespace().'\\'.$answeredEntityClass)) { + $targetEntityClass = $this->getEntityNamespace().'\\'.$answeredEntityClass; + } elseif (class_exists($answeredEntityClass)) { + $targetEntityClass = $answeredEntityClass; + } else { + $io->error(\sprintf('Unknown class "%s"', $answeredEntityClass)); + } + } + + // help the user select the type + if ('relation' === $type) { + $type = $this->askRelationType($io, $generatedEntityClass, $targetEntityClass); + } + + $askFieldName = fn (string $targetClass, string $defaultValue) => $io->ask( + \sprintf('New field name inside %s', Str::getShortClassName($targetClass)), + $defaultValue, + function ($name) use ($targetClass) { + // it's still *possible* to create duplicate properties - by + // trying to generate the same property 2 times during the + // same make:entity run. property_exists() only knows about + // properties that *originally* existed on this class. + if (property_exists($targetClass, $name)) { + throw new \InvalidArgumentException(\sprintf('The "%s" class already has a "%s" property.', $targetClass, $name)); + } + + return Validator::validateDoctrineFieldName($name, $this->doctrineHelper->getRegistry()); + } + ); + + $askIsNullable = static fn (string $propertyName, string $targetClass) => $io->confirm(\sprintf( + 'Is the %s.%s property allowed to be null (nullable)?', + Str::getShortClassName($targetClass), + $propertyName + )); + + $askOrphanRemoval = static function (string $owningClass, string $inverseClass) use ($io) { + $io->text([ + 'Do you want to activate orphanRemoval on your relationship?', + \sprintf( + 'A %s is "orphaned" when it is removed from its related %s.', + Str::getShortClassName($owningClass), + Str::getShortClassName($inverseClass) + ), + \sprintf( + 'e.g. $%s->remove%s($%s)', + Str::asLowerCamelCase(Str::getShortClassName($inverseClass)), + Str::asCamelCase(Str::getShortClassName($owningClass)), + Str::asLowerCamelCase(Str::getShortClassName($owningClass)) + ), + '', + \sprintf( + 'NOTE: If a %s may *change* from one %s to another, answer "no".', + Str::getShortClassName($owningClass), + Str::getShortClassName($inverseClass) + ), + ]); + + return $io->confirm(\sprintf('Do you want to automatically delete orphaned %s objects (orphanRemoval)?', $owningClass), false); + }; + + $askInverseSide = function (EntityRelation $relation) use ($io) { + if ($this->isClassInVendor($relation->getInverseClass())) { + $relation->setMapInverseRelation(false); + + return; + } + + // recommend an inverse side, except for OneToOne, where it's inefficient + $recommendMappingInverse = EntityRelation::ONE_TO_ONE !== $relation->getType(); + + $getterMethodName = 'get'.Str::asCamelCase(Str::getShortClassName($relation->getOwningClass())); + if (EntityRelation::ONE_TO_ONE !== $relation->getType()) { + // pluralize! + $getterMethodName = Str::singularCamelCaseToPluralCamelCase($getterMethodName); + } + $mapInverse = $io->confirm( + \sprintf( + 'Do you want to add a new property to %s so that you can access/update %s objects from it - e.g. $%s->%s()?', + Str::getShortClassName($relation->getInverseClass()), + Str::getShortClassName($relation->getOwningClass()), + Str::asLowerCamelCase(Str::getShortClassName($relation->getInverseClass())), + $getterMethodName + ), + $recommendMappingInverse + ); + $relation->setMapInverseRelation($mapInverse); + }; + + switch ($type) { + case EntityRelation::MANY_TO_ONE: + $relation = new EntityRelation( + EntityRelation::MANY_TO_ONE, + $generatedEntityClass, + $targetEntityClass + ); + $relation->setOwningProperty($newFieldName); + + $relation->setIsNullable($askIsNullable( + $relation->getOwningProperty(), + $relation->getOwningClass() + )); + + $askInverseSide($relation); + if ($relation->getMapInverseRelation()) { + $io->comment(\sprintf( + 'A new property will also be added to the %s class so that you can access the related %s objects from it.', + Str::getShortClassName($relation->getInverseClass()), + Str::getShortClassName($relation->getOwningClass()) + )); + $relation->setInverseProperty($askFieldName( + $relation->getInverseClass(), + Str::singularCamelCaseToPluralCamelCase(Str::getShortClassName($relation->getOwningClass())) + )); + + // orphan removal only applies if the inverse relation is set + if (!$relation->isNullable()) { + $relation->setOrphanRemoval($askOrphanRemoval( + $relation->getOwningClass(), + $relation->getInverseClass() + )); + } + } + + break; + case EntityRelation::ONE_TO_MANY: + // we *actually* create a ManyToOne, but populate it differently + $relation = new EntityRelation( + EntityRelation::MANY_TO_ONE, + $targetEntityClass, + $generatedEntityClass + ); + $relation->setInverseProperty($newFieldName); + + $io->comment(\sprintf( + 'A new property will also be added to the %s class so that you can access and set the related %s object from it.', + Str::getShortClassName($relation->getOwningClass()), + Str::getShortClassName($relation->getInverseClass()) + )); + $relation->setOwningProperty($askFieldName( + $relation->getOwningClass(), + Str::asLowerCamelCase(Str::getShortClassName($relation->getInverseClass())) + )); + + $relation->setIsNullable($askIsNullable( + $relation->getOwningProperty(), + $relation->getOwningClass() + )); + + if (!$relation->isNullable()) { + $relation->setOrphanRemoval($askOrphanRemoval( + $relation->getOwningClass(), + $relation->getInverseClass() + )); + } + + break; + case EntityRelation::MANY_TO_MANY: + $relation = new EntityRelation( + EntityRelation::MANY_TO_MANY, + $generatedEntityClass, + $targetEntityClass + ); + $relation->setOwningProperty($newFieldName); + + $askInverseSide($relation); + if ($relation->getMapInverseRelation()) { + $io->comment(\sprintf( + 'A new property will also be added to the %s class so that you can access the related %s objects from it.', + Str::getShortClassName($relation->getInverseClass()), + Str::getShortClassName($relation->getOwningClass()) + )); + $relation->setInverseProperty($askFieldName( + $relation->getInverseClass(), + Str::singularCamelCaseToPluralCamelCase(Str::getShortClassName($relation->getOwningClass())) + )); + } + + break; + case EntityRelation::ONE_TO_ONE: + $relation = new EntityRelation( + EntityRelation::ONE_TO_ONE, + $generatedEntityClass, + $targetEntityClass + ); + $relation->setOwningProperty($newFieldName); + + $relation->setIsNullable($askIsNullable( + $relation->getOwningProperty(), + $relation->getOwningClass() + )); + + $askInverseSide($relation); + if ($relation->getMapInverseRelation()) { + $io->comment(\sprintf( + 'A new property will also be added to the %s class so that you can access the related %s object from it.', + Str::getShortClassName($relation->getInverseClass()), + Str::getShortClassName($relation->getOwningClass()) + )); + $relation->setInverseProperty($askFieldName( + $relation->getInverseClass(), + Str::asLowerCamelCase(Str::getShortClassName($relation->getOwningClass())) + )); + } + + break; + default: + throw new \InvalidArgumentException('Invalid type: '.$type); + } + + return $relation; + } + + private function askRelationType(ConsoleStyle $io, string $entityClass, string $targetEntityClass): string + { + $io->writeln('What type of relationship is this?'); + + $originalEntityShort = Str::getShortClassName($entityClass); + $targetEntityShort = Str::getShortClassName($targetEntityClass); + if ($originalEntityShort === $targetEntityShort) { + [$originalDiscriminator, $targetDiscriminator] = Str::getHumanDiscriminatorBetweenTwoClasses($entityClass, $targetEntityClass); + $originalEntityShort = trim($originalDiscriminator.'\\'.$originalEntityShort, '\\'); + $targetEntityShort = trim($targetDiscriminator.'\\'.$targetEntityShort, '\\'); + } + + $rows = []; + $rows[] = [ + EntityRelation::MANY_TO_ONE, + \sprintf("Each %s relates to (has) one %s.\nEach %s can relate to (can have) many %s objects.", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), + ]; + $rows[] = ['', '']; + $rows[] = [ + EntityRelation::ONE_TO_MANY, + \sprintf("Each %s can relate to (can have) many %s objects.\nEach %s relates to (has) one %s.", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), + ]; + $rows[] = ['', '']; + $rows[] = [ + EntityRelation::MANY_TO_MANY, + \sprintf("Each %s can relate to (can have) many %s objects.\nEach %s can also relate to (can also have) many %s objects.", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), + ]; + $rows[] = ['', '']; + $rows[] = [ + EntityRelation::ONE_TO_ONE, + \sprintf("Each %s relates to (has) exactly one %s.\nEach %s also relates to (has) exactly one %s.", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), + ]; + + $io->table([ + 'Type', + 'Description', + ], $rows); + + $question = new Question(\sprintf( + 'Relation type? [%s]', + implode(', ', EntityRelation::getValidRelationTypes()) + )); + $question->setAutocompleterValues(EntityRelation::getValidRelationTypes()); + $question->setValidator(function ($type) { + if (!\in_array($type, EntityRelation::getValidRelationTypes())) { + throw new \InvalidArgumentException(\sprintf('Invalid type: use one of: %s', implode(', ', EntityRelation::getValidRelationTypes()))); + } + + return $type; + }); + + return $io->askQuestion($question); + } + + /** @return string[] */ + private function verifyEntityName(string $entityName): array + { + preg_match('/([^\x00-\x7F]+)/u', $entityName, $matches); + + return $matches; + } + + private function createClassManipulator(string $path, ConsoleStyle $io, bool $overwrite): ClassSourceManipulator + { + $manipulator = new ClassSourceManipulator( + sourceCode: $this->fileManager->getFileContents($path), + overwrite: $overwrite, + ); + + $manipulator->setIo($io); + + return $manipulator; + } + + private function getPathOfClass(string $class): string + { + return (new ClassDetails($class))->getPath(); + } + + private function isClassInVendor(string $class): bool + { + $path = $this->getPathOfClass($class); + + return $this->fileManager->isPathInVendor($path); + } + + private function regenerateEntities(string $classOrNamespace, bool $overwrite, Generator $generator): void + { + $regenerator = new EntityRegenerator($this->doctrineHelper, $this->fileManager, $generator, $this->entityClassGenerator, $overwrite); + $regenerator->regenerateEntities($classOrNamespace); + } + + /** @return string[] */ + private function getPropertyNames(string $class): array + { + if (!class_exists($class)) { + return []; + } + + $reflClass = new \ReflectionClass($class); + + return array_map(static fn (\ReflectionProperty $prop) => $prop->getName(), $reflClass->getProperties()); + } + + private function getEntityNamespace(): string + { + return $this->doctrineHelper->getEntityNamespace(); + } + + /** @return string[] */ + private function getTypesMap(): array + { + return Type::getTypesMap(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeFixtures.php b/vendor/symfony/maker-bundle/src/Maker/MakeFixtures.php new file mode 100644 index 0000000..befc384 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeFixtures.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\ORM\Mapping\Column; +use Doctrine\Persistence\ObjectManager; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeFixtures extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:fixtures'; + } + + public static function getCommandDescription(): string + { + return 'Create a new class to load Doctrine fixtures'; + } + + /** @return void */ + public function configureCommand(Command $command, InputConfiguration $inputConf) + { + $command + ->addArgument('fixtures-class', InputArgument::OPTIONAL, 'The class name of the fixtures to create (e.g. AppFixtures)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeFixture.txt')) + ; + } + + /** @return void */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $fixturesClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('fixtures-class'), + 'DataFixtures\\' + ); + + $useStatements = new UseStatementGenerator([ + Fixture::class, + ObjectManager::class, + ]); + + $generator->generateClass( + $fixturesClassNameDetails->getFullName(), + 'doctrine/Fixtures.tpl.php', + [ + 'use_statements' => $useStatements, + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new fixtures class and start customizing it.', + \sprintf('Load your fixtures by running: php %s doctrine:fixtures:load', $_SERVER['PHP_SELF']), + 'Docs: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html', + ]); + } + + /** @return void */ + public function configureDependencies(DependencyBuilder $dependencies) + { + $dependencies->addClassDependency( + Column::class, + 'doctrine' + ); + $dependencies->addClassDependency( + Fixture::class, + 'orm-fixtures', + true, + true + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeForm.php b/vendor/symfony/maker-bundle/src/Maker/MakeForm.php new file mode 100644 index 0000000..4859064 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeForm.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassDetails; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Validator\Validation; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeForm extends AbstractMaker +{ + public function __construct(private DoctrineHelper $entityHelper, private FormTypeRenderer $formTypeRenderer) + { + } + + public static function getCommandName(): string + { + return 'make:form'; + } + + public static function getCommandDescription(): string + { + return 'Create a new form class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, \sprintf('The name of the form class (e.g. %sType)', Str::asClassName(Str::getRandomTerm()))) + ->addArgument('bound-class', InputArgument::OPTIONAL, 'The name of Entity or fully qualified model class name that the new form will be bound to (empty for none)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeForm.txt')) + ; + + $inputConfig->setArgumentAsNonInteractive('bound-class'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (null === $input->getArgument('bound-class')) { + $argument = $command->getDefinition()->getArgument('bound-class'); + + $entities = $this->entityHelper->getEntitiesForAutocomplete(); + + $question = new Question($argument->getDescription()); + $question->setValidator(fn ($answer) => Validator::existsOrNull($answer, $entities)); + $question->setAutocompleterValues($entities); + $question->setMaxAttempts(3); + + $input->setArgument('bound-class', $io->askQuestion($question)); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $formClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Form\\', + 'Type' + ); + + $formFields = ['field_name' => null]; + + $boundClass = $input->getArgument('bound-class'); + $boundClassDetails = null; + + if (null !== $boundClass) { + $boundClassDetails = $generator->createClassNameDetails( + $boundClass, + 'Entity\\' + ); + + $doctrineEntityDetails = $this->entityHelper->createDoctrineDetails($boundClassDetails->getFullName()); + + if (null !== $doctrineEntityDetails) { + $formFields = $doctrineEntityDetails->getFormFields(); + } else { + $classDetails = new ClassDetails($boundClassDetails->getFullName()); + $formFields = $classDetails->getFormFields(); + } + } + + $this->formTypeRenderer->render( + $formClassNameDetails, + $formFields, + $boundClassDetails + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Add fields to your form and start using it.', + 'Find the documentation at https://symfony.com/doc/current/forms.html', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + AbstractType::class, + // technically only form is needed, but the user will *probably* also want validation + 'form' + ); + + $dependencies->addClassDependency( + Validation::class, + 'validator', + // add as an optional dependency: the user *probably* wants validation + false + ); + + $dependencies->addClassDependency( + DoctrineBundle::class, + 'orm', + false + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeFunctionalTest.php b/vendor/symfony/maker-bundle/src/Maker/MakeFunctionalTest.php new file mode 100644 index 0000000..69277c5 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeFunctionalTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\FrameworkBundle\Test\WebTestAssertionsTrait; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\CssSelector\CssSelectorConverter; +use Symfony\Component\Panther\PantherTestCase; +use Symfony\Component\Panther\PantherTestCaseTrait; + +trigger_deprecation('symfony/maker-bundle', '1.29', 'The "%s" class is deprecated, use "%s" instead.', MakeFunctionalTest::class, MakeTest::class); + +/** + * @deprecated since MakerBundle 1.29, use Symfony\Bundle\MakerBundle\Maker\MakeTest instead. + * + * @author Javier Eguiluz + * @author Ryan Weaver + */ +class MakeFunctionalTest extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:functional-test'; + } + + public static function getCommandDescription(): string + { + return 'Create a new functional test class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the functional test class (e.g. DefaultControllerTest)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeFunctionalTest.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $testClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Tests\\', + 'Test' + ); + + $pantherAvailable = trait_exists(PantherTestCaseTrait::class); + + $useStatements = new UseStatementGenerator([ + $pantherAvailable ? PantherTestCase::class : WebTestCase::class, + ]); + + $generator->generateClass( + $testClassNameDetails->getFullName(), + 'test/Functional.tpl.php', + [ + 'use_statements' => $useStatements, + 'web_assertions_are_available' => trait_exists(WebTestAssertionsTrait::class), + 'panther_is_available' => $pantherAvailable, + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new test class and start customizing it.', + 'Find the documentation at https://symfony.com/doc/current/testing.html#functional-tests', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + History::class, + 'browser-kit', + true, + true + ); + $dependencies->addClassDependency( + CssSelectorConverter::class, + 'css-selector', + true, + true + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeListener.php b/vendor/symfony/maker-bundle/src/Maker/MakeListener.php new file mode 100644 index 0000000..298c419 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeListener.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\EventRegistry; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + * @author Steven Renaux + */ +final class MakeListener extends AbstractMaker +{ + private const ALL_TYPES = ['Listener', 'Subscriber']; + private bool $isSubscriber = false; + + public function __construct(private readonly EventRegistry $eventRegistry) + { + } + + public static function getCommandName(): string + { + return 'make:listener'; + } + + /** + * @deprecated remove this method when removing make:subscriber + */ + public static function getCommandAlias(): string + { + return 'make:subscriber'; + } + + public static function getCommandDescription(): string + { + return 'Creates a new event subscriber class or a new event listener class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your event listener or subscriber (e.g. ExceptionListener or ExceptionSubscriber)') + ->addArgument('event', InputArgument::OPTIONAL, 'What event do you want to listen to?') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeListener.txt')) + ; + + $inputConfig->setArgumentAsNonInteractive('event'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + /* @deprecated remove the following block when removing make:subscriber */ + $this->handleDeprecatedMakerCommands($input, $io); + + $io->writeln(''); + + $name = $input->getArgument('name'); + + if (!str_ends_with($name, 'Subscriber') && !str_ends_with($name, 'Listener')) { + $question = new ChoiceQuestion('Do you want to generate an event listener or subscriber?', self::ALL_TYPES, 0); + $classToGenerate = $io->askQuestion($question); + + $input->setArgument('name', $name.$classToGenerate); + } + + if (str_ends_with($input->getArgument('name'), 'Subscriber')) { + $this->isSubscriber = true; + } + + if (!$input->getArgument('event')) { + $events = $this->eventRegistry->getAllActiveEvents(); + + $io->writeln(' Suggested Events:'); + $io->listing($this->eventRegistry->listActiveEvents($events)); + $question = new Question(\sprintf(' %s', $command->getDefinition()->getArgument('event')->getDescription())); + $question->setAutocompleterValues($events); + $question->setValidator(Validator::notBlank(...)); + $event = $io->askQuestion($question); + $input->setArgument('event', $event); + } + + $event = $input->getArgument('event'); + if (null === $this->getEventConstant($event) && null === $this->eventRegistry->getEventClassName($event)) { + $eventList = $this->eventRegistry->getAllActiveEvents(); + $eventFQCNList = array_filter(array_map($this->eventRegistry->getEventClassName(...), $eventList), fn ($eventFQCN) => \is_string($eventFQCN)); + $eventIdAndFQCNList = array_unique(array_merge($eventList, $eventFQCNList)); + $suggestionList = []; + + foreach ($eventIdAndFQCNList as $eventSuggestion) { + if (levenshtein($event, Str::getShortClassName($eventSuggestion)) < 3) { + $suggestionList[] = $eventSuggestion; + } + } + + if (!$suggestionList) { + return; + } + + if (1 === \count($suggestionList)) { + $question = new ConfirmationQuestion(\sprintf('Did you mean "%s" ?', $suggestionList[0]), false); + $input->setArgument('event', $io->askQuestion($question) ? $suggestionList[0] : $event); + + return; + } + + $io->writeln(' Did you mean one of these events?'); + $io->listing($suggestionList); + $question = new Question(\sprintf(' %s', $command->getDefinition()->getArgument('event')->getDescription()), $event); + $question->setAutocompleterValues(array_merge($suggestionList, [$event])); + + $input->setArgument('event', $io->askQuestion($question)); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + if ($this->isSubscriber) { + $useStatements = new UseStatementGenerator([ + EventSubscriberInterface::class, + ]); + } else { + $useStatements = new UseStatementGenerator([ + AsEventListener::class, + ]); + } + + $event = $input->getArgument('event'); + $eventFullClassName = $this->eventRegistry->getEventClassName($event); + $eventClassName = $eventFullClassName ? Str::getShortClassName($eventFullClassName) : null; + + if (null !== ($eventConstant = $this->getEventConstant($event))) { + $useStatements->addUseStatement(KernelEvents::class); + $eventName = $eventConstant; + } else { + $eventName = class_exists($event) ? \sprintf('%s::class', $eventClassName) : \sprintf('\'%s\'', $event); + } + + if (null !== $eventFullClassName) { + $useStatements->addUseStatement($eventFullClassName); + } + + if ($this->isSubscriber) { + $this->generateSubscriberClass($input, $io, $generator, $useStatements, $event, $eventName, $eventClassName); + } else { + $this->generateListenerClass($input, $io, $generator, $useStatements, $event, $eventName, $eventClassName); + } + } + + /** @return void */ + public function configureDependencies(DependencyBuilder $dependencies) + { + } + + private function getEventConstant(string $event): ?string + { + $constants = (new \ReflectionClass(KernelEvents::class))->getConstants(); + + if (false !== ($name = array_search($event, $constants, true))) { + return \sprintf('KernelEvents::%s', $name); + } + + return null; + } + + private function generateSubscriberClass(InputInterface $input, ConsoleStyle $io, Generator $generator, UseStatementGenerator $useStatements, string $event, string $eventName, ?string $eventClassName): void + { + $subscriberClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'EventSubscriber\\', + 'Subscriber' + ); + + $generator->generateClass( + $subscriberClassNameDetails->getFullName(), + 'event/Subscriber.tpl.php', + [ + 'use_statements' => $useStatements, + 'event' => $eventName, + 'event_arg' => $eventClassName ? \sprintf('%s $event', $eventClassName) : '$event', + 'method_name' => class_exists($event) ? Str::asEventMethod($eventClassName) : Str::asEventMethod($event), + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new subscriber class and start customizing it.', + 'Find the documentation at https://symfony.com/doc/current/event_dispatcher.html#creating-an-event-subscriber', + ]); + } + + private function generateListenerClass(InputInterface $input, ConsoleStyle $io, Generator $generator, UseStatementGenerator $useStatements, string $event, string $eventName, ?string $eventClassName): void + { + $listenerClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'EventListener\\', + 'Listener' + ); + + $generator->generateClass( + $listenerClassNameDetails->getFullName(), + 'event/Listener.tpl.php', + [ + 'use_statements' => $useStatements, + 'event' => $eventName, + 'event_arg' => $eventClassName ? \sprintf('%s $event', $eventClassName) : '$event', + 'method_name' => class_exists($event) ? Str::asEventMethod($eventClassName) : Str::asEventMethod($event), + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new listener class and start customizing it.', + 'Find the documentation at https://symfony.com/doc/current/event_dispatcher.html#creating-an-event-listener', + ]); + } + + /** + * @deprecated + */ + private function handleDeprecatedMakerCommands(InputInterface $input, ConsoleStyle $io): void + { + $currentCommand = $input->getFirstArgument(); + $name = $input->getArgument('name'); + + if ('make:subscriber' === $currentCommand) { + if (!str_ends_with($name, 'Subscriber')) { + $input->setArgument('name', $name.'Subscriber'); + } + + $io->warning('The "make:subscriber" command is deprecated, use "make:listener" instead.'); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeMessage.php b/vendor/symfony/maker-bundle/src/Maker/MakeMessage.php new file mode 100644 index 0000000..682601a --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeMessage.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; +use Symfony\Component\Messenger\MessageBusInterface; + +/** + * @author Ryan Weaver + * @author Nicolas Philippe + * + * @internal + */ +final class MakeMessage extends AbstractMaker +{ + public function __construct(private FileManager $fileManager) + { + } + + public static function getCommandName(): string + { + return 'make:message'; + } + + public static function getCommandDescription(): string + { + return 'Create a new message and handler'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the message class (e.g. SendEmailMessage)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeMessage.txt')) + ; + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $command->addArgument('chosen-transport', InputArgument::OPTIONAL); + + $messengerData = []; + + try { + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents('config/packages/messenger.yaml')); + $messengerData = $manipulator->getData(); + } catch (\Exception) { + } + + if (!isset($messengerData['framework']['messenger']['transports'])) { + return; + } + + $transports = array_keys($messengerData['framework']['messenger']['transports']); + array_unshift($transports, $noTransport = '[no transport]'); + + $chosenTransport = $io->choice( + 'Which transport do you want to route your message to?', + $transports, + $noTransport + ); + + if ($noTransport !== $chosenTransport) { + $input->setArgument('chosen-transport', $chosenTransport); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $messageClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Message\\' + ); + + $handlerClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name').'Handler', + 'MessageHandler\\', + 'Handler' + ); + + $generator->generateClass( + $messageClassNameDetails->getFullName(), + 'message/Message.tpl.php' + ); + + $useStatements = new UseStatementGenerator([ + AsMessageHandler::class, + $messageClassNameDetails->getFullName(), + ]); + + $generator->generateClass( + $handlerClassNameDetails->getFullName(), + 'message/MessageHandler.tpl.php', + [ + 'use_statements' => $useStatements, + 'message_class_name' => $messageClassNameDetails->getShortName(), + ] + ); + + if (null !== $chosenTransport = $input->getArgument('chosen-transport')) { + $this->updateMessengerConfig($generator, $chosenTransport, $messageClassNameDetails->getFullName()); + } + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new message class and add the properties you need.', + ' Then, open the new message handler and do whatever work you want!', + 'Find the documentation at https://symfony.com/doc/current/messenger.html', + ]); + } + + private function updateMessengerConfig(Generator $generator, string $chosenTransport, string $messageClass): void + { + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($configFilePath = 'config/packages/messenger.yaml')); + $messengerData = $manipulator->getData(); + + if (!isset($messengerData['framework']['messenger']['routing'])) { + $messengerData['framework']['messenger']['routing'] = []; + } + + $messengerData['framework']['messenger']['routing'][$messageClass] = $chosenTransport; + + $manipulator->setData($messengerData); + $generator->dumpFile($configFilePath, $manipulator->getContents()); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + MessageBusInterface::class, + 'messenger' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeMessengerMiddleware.php b/vendor/symfony/maker-bundle/src/Maker/MakeMessengerMiddleware.php new file mode 100644 index 0000000..2b1c189 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeMessengerMiddleware.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Middleware\MiddlewareInterface; +use Symfony\Component\Messenger\Middleware\StackInterface; + +/** + * @author Imad ZAIRIG + * + * @internal + */ +final class MakeMessengerMiddleware extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:messenger-middleware'; + } + + public static function getCommandDescription(): string + { + return 'Create a new messenger middleware'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the middleware class (e.g. CustomMiddleware)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeMessage.txt')); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $middlewareClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Middleware\\', + 'Middleware' + ); + + $useStatements = new UseStatementGenerator([ + Envelope::class, + MiddlewareInterface::class, + StackInterface::class, + ]); + + $generator->generateClass( + $middlewareClassNameDetails->getFullName(), + 'middleware/Middleware.tpl.php', + [ + 'use_statements' => $useStatements, + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next:', + \sprintf('- Open the %s class and add the code you need', $middlewareClassNameDetails->getFullName()), + '- Add the middleware to your config/packages/messenger.yaml file', + 'Find the documentation at https://symfony.com/doc/current/messenger.html#middleware', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + MessageBusInterface::class, + 'messenger' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeMigration.php b/vendor/symfony/maker-bundle/src/Maker/MakeMigration.php new file mode 100644 index 0000000..5c0afc0 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeMigration.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Doctrine\Bundle\MigrationsBundle\Command\MigrationsDiffDoctrineCommand; +use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle; +use Symfony\Bundle\MakerBundle\ApplicationAwareMakerInterface; +use Symfony\Bundle\MakerBundle\Console\MigrationDiffFilteredOutput; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\CliOutputHelper; +use Symfony\Bundle\MakerBundle\Util\MakerFileLinkFormatter; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; + +/** + * @author Amrouche Hamza + * @author Ryan Weaver + */ +final class MakeMigration extends AbstractMaker implements ApplicationAwareMakerInterface +{ + private Application $application; + + public function __construct( + private string $projectDir, + private ?MakerFileLinkFormatter $makerFileLinkFormatter = null, + ) { + } + + public static function getCommandName(): string + { + return 'make:migration'; + } + + public static function getCommandDescription(): string + { + return 'Create a new migration based on database changes'; + } + + /** @return void */ + public function setApplication(Application $application) + { + $this->application = $application; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeMigration.txt')) + ; + + if (class_exists(MigrationsDiffDoctrineCommand::class)) { + // support for DoctrineMigrationsBundle 2.x + $command + ->addOption('db', null, InputOption::VALUE_REQUIRED, 'The database connection name') + ->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager name') + ->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection name') + ; + } + + $command + ->addOption('formatted', null, InputOption::VALUE_NONE, 'Format the generated SQL') + ->addOption('configuration', null, InputOption::VALUE_OPTIONAL, 'The path of doctrine configuration file') + ; + } + + /** @return void|int */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $options = ['doctrine:migrations:diff']; + + // DoctrineMigrationsBundle 2.x support + if ($input->hasOption('db') && null !== $input->getOption('db')) { + $options[] = '--db='.$input->getOption('db'); + } + if ($input->hasOption('em') && null !== $input->getOption('em')) { + $options[] = '--em='.$input->getOption('em'); + } + if ($input->hasOption('shard') && null !== $input->getOption('shard')) { + $options[] = '--shard='.$input->getOption('shard'); + } + // end 2.x support + + if ($input->getOption('formatted')) { + $options[] = '--formatted'; + } + + if (null !== $configuration = $input->getOption('configuration')) { + $options[] = '--configuration='.$configuration; + } + + $generateMigrationCommand = $this->application->find('doctrine:migrations:diff'); + $generateMigrationCommandInput = new ArgvInput($options); + + if (!$input->isInteractive()) { + $generateMigrationCommandInput->setInteractive(false); + } + + $commandOutput = new MigrationDiffFilteredOutput($io->getOutput()); + try { + $returnCode = $generateMigrationCommand->run($generateMigrationCommandInput, $commandOutput); + + // non-zero code would ideally mean the internal command has already printed an errror + // this happens if you "decline" generating a migration when you already + // have some available + if (0 !== $returnCode) { + return $returnCode; + } + + $migrationOutput = $commandOutput->fetch(); + + if (str_contains($migrationOutput, 'No changes detected')) { + $this->noChangesMessage($io); + + return; + } + } catch (\Doctrine\Migrations\Generator\Exception\NoChangesDetected) { + $this->noChangesMessage($io); + + return; + } + + $absolutePath = $this->getGeneratedMigrationFilename($migrationOutput); + $relativePath = str_replace($this->projectDir.'/', '', $absolutePath); + + $io->comment('created: '.($this->makerFileLinkFormatter?->makeLinkedPath($absolutePath, $relativePath) ?? $relativePath)); + + $this->writeSuccessMessage($io); + + $io->text([ + \sprintf('Review the new migration then run it with %s doctrine:migrations:migrate', CliOutputHelper::getCommandPrefix()), + 'See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html', + ]); + } + + private function noChangesMessage(ConsoleStyle $io): void + { + $io->warning([ + 'No database changes were detected.', + ]); + $io->text([ + 'The database schema and the application mapping information are already in sync.', + '', + ]); + } + + /** @return void */ + public function configureDependencies(DependencyBuilder $dependencies) + { + $dependencies->addClassDependency( + DoctrineMigrationsBundle::class, + 'doctrine/doctrine-migrations-bundle' + ); + } + + private function getGeneratedMigrationFilename(string $migrationOutput): string + { + preg_match('#"(.*?)"#', $migrationOutput, $matches); + + if (!isset($matches[1])) { + throw new \Exception('Your migration generated successfully, but an error occurred printing the summary of what occurred.'); + } + + return $matches[1]; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeRegistrationForm.php b/vendor/symfony/maker-bundle/src/Maker/MakeRegistrationForm.php new file mode 100644 index 0000000..eeb5237 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeRegistrationForm.php @@ -0,0 +1,603 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\Column; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait; +use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer; +use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper; +use Symfony\Bundle\MakerBundle\Security\Model\Authenticator; +use Symfony\Bundle\MakerBundle\Security\Model\AuthenticatorType; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassDetails; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\CliOutputHelper; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Address; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Translation\Translator; +use Symfony\Component\Validator\Validation; +use Symfony\Contracts\Translation\TranslatorInterface; +use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface; +use SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle; +use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelper; +use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface; + +/** + * @author Ryan Weaver + * @author Jesse Rushlow + * + * @internal + */ +final class MakeRegistrationForm extends AbstractMaker +{ + use CanGenerateTestsTrait; + + private string $userClass; + private string $usernameField; + private string $passwordField; + private bool $willVerifyEmail = false; + private bool $verifyEmailAnonymously = false; + private string $idGetter; + private string $emailGetter; + private string $fromEmailAddress; + private string $fromEmailName; + private ?Authenticator $autoLoginAuthenticator = null; + private string $redirectRouteName; + private bool $addUniqueEntityConstraint = false; + + public function __construct( + private FileManager $fileManager, + private FormTypeRenderer $formTypeRenderer, + private DoctrineHelper $doctrineHelper, + private ?RouterInterface $router = null, + ) { + } + + public static function getCommandName(): string + { + return 'make:registration-form'; + } + + public static function getCommandDescription(): string + { + return 'Create a new registration form system'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeRegistrationForm.txt')) + ; + + $this->configureCommandWithTestsOption($command); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $interactiveSecurityHelper = new InteractiveSecurityHelper(); + + if (null === $this->router) { + throw new RuntimeCommandException('Router have been explicitly disabled in your configuration. This command needs to use the router.'); + } + + if (!$this->fileManager->fileExists($path = 'config/packages/security.yaml')) { + throw new RuntimeCommandException('The file "config/packages/security.yaml" does not exist. PHP & XML configuration formats are currently not supported.'); + } + + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path)); + $securityData = $manipulator->getData(); + $providersData = $securityData['security']['providers'] ?? []; + + $this->userClass = $interactiveSecurityHelper->guessUserClass( + $io, + $providersData, + 'Enter the User class that you want to create during registration (e.g. App\\Entity\\User)' + ); + $io->text(\sprintf('Creating a registration form for %s', $this->userClass)); + + $this->usernameField = $interactiveSecurityHelper->guessUserNameField($io, $this->userClass, $providersData); + + $this->passwordField = $interactiveSecurityHelper->guessPasswordField($io, $this->userClass); + + // see if it makes sense to add the UniqueEntity constraint + $userClassDetails = new ClassDetails($this->userClass); + + if (!$userClassDetails->hasAttribute(UniqueEntity::class)) { + $this->addUniqueEntityConstraint = (bool) $io->confirm(\sprintf('Do you want to add a #[UniqueEntity] validation attribute to your %s class to make sure duplicate accounts aren\'t created?', Str::getShortClassName($this->userClass))); + } + + $this->willVerifyEmail = (bool) $io->confirm('Do you want to send an email to verify the user\'s email address after registration?'); + + if ($this->willVerifyEmail) { + $this->checkComponentsExist($io); + + $emailText[] = 'By default, users are required to be authenticated when they click the verification link that is emailed to them.'; + $emailText[] = 'This prevents the user from registering on their laptop, then clicking the link on their phone, without'; + $emailText[] = 'having to log in. To allow multi device email verification, we can embed a user id in the verification link.'; + $io->text($emailText); + $io->newLine(); + $this->verifyEmailAnonymously = (bool) $io->confirm('Would you like to include the user id in the verification link to allow anonymous email verification?', false); + + $this->idGetter = $interactiveSecurityHelper->guessIdGetter($io, $this->userClass); + $this->emailGetter = $interactiveSecurityHelper->guessEmailGetter($io, $this->userClass, 'email'); + + $this->fromEmailAddress = $io->ask( + 'What email address will be used to send registration confirmations? (e.g. mailer@your-domain.com)', + null, + Validator::validateEmailAddress(...) + ); + + $this->fromEmailName = $io->ask( + 'What "name" should be associated with that email address? (e.g. Acme Mail Bot)', + null, + Validator::notBlank(...) + ); + } + + if ($io->confirm('Do you want to automatically authenticate the user after registration?')) { + $this->interactAuthenticatorQuestions( + $io, + $interactiveSecurityHelper, + $securityData + ); + } + + if (!$this->autoLoginAuthenticator) { + $routeNames = array_keys($this->router->getRouteCollection()->all()); + $this->redirectRouteName = $io->choice('What route should the user be redirected to after registration?', $routeNames); + } + + $this->interactSetGenerateTests($input, $io); + } + + /** @param array $securityData */ + private function interactAuthenticatorQuestions(ConsoleStyle $io, InteractiveSecurityHelper $interactiveSecurityHelper, array $securityData): void + { + // get list of authenticators + $authenticators = $interactiveSecurityHelper->getAuthenticatorsFromConfig($securityData['security']['firewalls'] ?? []); + + if (empty($authenticators)) { + $io->note('No authenticators found - so your user won\'t be automatically authenticated after registering.'); + + return; + } + + $this->autoLoginAuthenticator = + 1 === \count($authenticators) ? $authenticators[0] : $io->choice( + 'Which authenticator should be used to login the user?', + $authenticators + ); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $userClassNameDetails = $generator->createClassNameDetails( + '\\'.$this->userClass, + 'Entity\\' + ); + + $userDoctrineDetails = $this->doctrineHelper->createDoctrineDetails($userClassNameDetails->getFullName()); + + $userRepoVars = [ + 'repository_full_class_name' => EntityManagerInterface::class, + 'repository_class_name' => 'EntityManagerInterface', + 'repository_var' => '$manager', + ]; + + $userRepository = $userDoctrineDetails->getRepositoryClass(); + + if (null !== $userRepository) { + $userRepoClassDetails = $generator->createClassNameDetails('\\'.$userRepository, 'Repository\\', 'Repository'); + + $userRepoVars = [ + 'repository_full_class_name' => $userRepoClassDetails->getFullName(), + 'repository_class_name' => $userRepoClassDetails->getShortName(), + 'repository_var' => \sprintf('$%s', lcfirst($userRepoClassDetails->getShortName())), + ]; + } + + $verifyEmailServiceClassNameDetails = $generator->createClassNameDetails( + 'EmailVerifier', + 'Security\\' + ); + + $verifyEmailVars = ['will_verify_email' => $this->willVerifyEmail]; + + if ($this->willVerifyEmail) { + $verifyEmailVars = [ + 'will_verify_email' => $this->willVerifyEmail, + 'email_verifier_class_details' => $verifyEmailServiceClassNameDetails, + 'verify_email_anonymously' => $this->verifyEmailAnonymously, + 'from_email' => $this->fromEmailAddress, + 'from_email_name' => addslashes($this->fromEmailName), + 'email_getter' => $this->emailGetter, + ]; + + $useStatements = new UseStatementGenerator([ + EntityManagerInterface::class, + TemplatedEmail::class, + Request::class, + MailerInterface::class, + UserInterface::class, + VerifyEmailExceptionInterface::class, + VerifyEmailHelperInterface::class, + $userClassNameDetails->getFullName(), + ]); + + $generator->generateClass( + $verifyEmailServiceClassNameDetails->getFullName(), + 'verifyEmail/EmailVerifier.tpl.php', + array_merge([ + 'use_statements' => $useStatements, + 'id_getter' => $this->idGetter, + 'email_getter' => $this->emailGetter, + 'verify_email_anonymously' => $this->verifyEmailAnonymously, + 'user_class_name' => $userClassNameDetails->getShortName(), + ], + $userRepoVars + ) + ); + + $generator->generateTemplate( + 'registration/confirmation_email.html.twig', + 'registration/twig_email.tpl.php' + ); + } + + // 1) Generate the form class + $usernameField = $this->usernameField; + $formClassDetails = $this->generateFormClass( + $userClassNameDetails, + $generator, + $usernameField + ); + + // 2) Generate the controller + $controllerClassNameDetails = $generator->createClassNameDetails( + 'RegistrationController', + 'Controller\\' + ); + + $useStatements = new UseStatementGenerator([ + AbstractController::class, + $formClassDetails->getFullName(), + $userClassNameDetails->getFullName(), + Request::class, + Response::class, + Route::class, + UserPasswordHasherInterface::class, + EntityManagerInterface::class, + ]); + + if ($this->willVerifyEmail) { + $useStatements->addUseStatement([ + $verifyEmailServiceClassNameDetails->getFullName(), + TemplatedEmail::class, + Address::class, + VerifyEmailExceptionInterface::class, + ]); + + if ($this->verifyEmailAnonymously) { + $useStatements->addUseStatement($userRepoVars['repository_full_class_name']); + } + } + + $autoLoginVars = [ + 'login_after_registration' => null !== $this->autoLoginAuthenticator, + ]; + + if (null !== $this->autoLoginAuthenticator) { + $useStatements->addUseStatement([ + Security::class, + ]); + + $autoLoginVars['firewall'] = $this->autoLoginAuthenticator->firewallName; + $autoLoginVars['authenticator'] = \sprintf('\'%s\'', $this->autoLoginAuthenticator->type->value); + + if (AuthenticatorType::CUSTOM === $this->autoLoginAuthenticator->type) { + $useStatements->addUseStatement($this->autoLoginAuthenticator->authenticatorClass); + $autoLoginVars['authenticator'] = \sprintf('%s::class', Str::getShortClassName($this->autoLoginAuthenticator->authenticatorClass)); + } + } + + if ($isTranslatorAvailable = class_exists(Translator::class)) { + $useStatements->addUseStatement(TranslatorInterface::class); + } + + $generator->generateController( + $controllerClassNameDetails->getFullName(), + 'registration/RegistrationController.tpl.php', + array_merge([ + 'use_statements' => $useStatements, + 'route_path' => '/register', + 'route_name' => 'app_register', + 'form_class_name' => $formClassDetails->getShortName(), + 'user_class_name' => $userClassNameDetails->getShortName(), + 'password_field' => $this->passwordField, + 'redirect_route_name' => $this->redirectRouteName ?? null, + 'translator_available' => $isTranslatorAvailable, + ], + $userRepoVars, + $autoLoginVars, + $verifyEmailVars, + ) + ); + + // 3) Generate the template + $generator->generateTemplate( + 'registration/register.html.twig', + 'registration/twig_template.tpl.php', + [ + 'username_field' => $usernameField, + 'will_verify_email' => $this->willVerifyEmail, + ] + ); + + // 4) Update the User class if necessary + if ($this->addUniqueEntityConstraint) { + $classDetails = new ClassDetails($this->userClass); + $userManipulator = new ClassSourceManipulator( + sourceCode: file_get_contents($classDetails->getPath()) + ); + $userManipulator->setIo($io); + + if ($this->doctrineHelper->isDoctrineSupportingAttributes()) { + $userManipulator->addAttributeToClass( + UniqueEntity::class, + ['fields' => [$usernameField], 'message' => \sprintf('There is already an account with this %s', $usernameField)] + ); + } + + $this->fileManager->dumpFile($classDetails->getPath(), $userManipulator->getSourceCode()); + } + + if ($this->willVerifyEmail) { + $classDetails = new ClassDetails($this->userClass); + $userManipulator = new ClassSourceManipulator( + sourceCode: file_get_contents($classDetails->getPath()), + overwrite: false, + ); + $userManipulator->setIo($io); + + $userManipulator->addProperty( + name: 'isVerified', + defaultValue: false, + attributes: [$userManipulator->buildAttributeNode(attributeClass: Column::class, options: [], attributePrefix: 'ORM')], + propertyType: 'bool' + ); + $userManipulator->addAccessorMethod('isVerified', 'isVerified', 'bool', false); + $userManipulator->addSetter('isVerified', 'bool', false); + + $this->fileManager->dumpFile($classDetails->getPath(), $userManipulator->getSourceCode()); + } + + // Generate PHPUnit Tests + if ($this->shouldGenerateTests()) { + $testClassDetails = $generator->createClassNameDetails( + 'RegistrationControllerTest', + 'Test\\' + ); + + $useStatements = new UseStatementGenerator([ + EntityManager::class, + KernelBrowser::class, + TemplatedEmail::class, + WebTestCase::class, + $userRepoVars['repository_full_class_name'], + ]); + + $generator->generateFile( + targetPath: \sprintf('tests/%s.php', $testClassDetails->getShortName()), + templateName: $this->willVerifyEmail ? 'registration/Test.WithVerify.tpl.php' : 'registration/Test.WithoutVerify.tpl.php', + variables: array_merge([ + 'use_statements' => $useStatements, + 'from_email' => $this->fromEmailAddress ?? null, + ], $userRepoVars) + ); + + if (!class_exists(WebTestCase::class)) { + $io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.'); + } + } + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $this->successMessage($io, $this->willVerifyEmail, $userClassNameDetails->getShortName()); + } + + private function successMessage(ConsoleStyle $io, bool $emailVerification, string $userClass): void + { + $closing[] = 'Next:'; + + if (!$emailVerification) { + $closing[] = 'Make any changes you need to the form, controller & template.'; + } else { + $index = 1; + if ($missingPackagesMessage = $this->getMissingComponentsComposerMessage()) { + $closing[] = '1) Install some missing packages:'; + $closing[] = \sprintf(' %s', $missingPackagesMessage); + ++$index; + } + + $closing[] = \sprintf('%d) In RegistrationController::verifyUserEmail():', $index++); + $closing[] = ' * Customize the last redirectToRoute() after a successful email verification.'; + $closing[] = ' * Make sure you\'re rendering success flash messages or change the $this->addFlash() line.'; + $closing[] = \sprintf('%d) Review and customize the form, controller, and templates as needed.', $index++); + $closing[] = \sprintf('%d) Run "%s make:migration" to generate a migration for the newly added %s::isVerified property.', $index++, CliOutputHelper::getCommandPrefix(), $userClass); + } + + $io->text($closing); + $io->newLine(); + $io->text('Then open your browser, go to "/register" and enjoy your new form!'); + $io->newLine(); + } + + private function checkComponentsExist(ConsoleStyle $io): void + { + $message = $this->getMissingComponentsComposerMessage(); + + if ($message) { + $io->warning([ + 'We\'re missing some important components. Don\'t forget to install these after you\'re finished.', + $message, + ]); + } + } + + private function getMissingComponentsComposerMessage(): ?string + { + $missing = false; + $composerMessage = 'composer require'; + + // verify-email-bundle 1.17.0 includes the new validateEmailConfirmationFromRequest method. + // we need to check that if the bundle is installed, it is version 1.17.0 or greater + if (class_exists(SymfonyCastsVerifyEmailBundle::class)) { + $reflectedComponents = new \ReflectionClass(VerifyEmailHelper::class); + + if (!$reflectedComponents->hasMethod('validateEmailConfirmationFromRequest')) { + throw new RuntimeCommandException('Please upgrade symfonycasts/verify-email-bundle to version 1.17.0 or greater.'); + } + } else { + $missing = true; + $composerMessage = \sprintf('%s symfonycasts/verify-email-bundle', $composerMessage); + } + + if (!interface_exists(MailerInterface::class)) { + $missing = true; + $composerMessage = \sprintf('%s symfony/mailer', $composerMessage); + } + + if (!$missing) { + return null; + } + + return $composerMessage; + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + AbstractType::class, + 'form' + ); + + $dependencies->addClassDependency( + Validation::class, + 'validator' + ); + + $dependencies->addClassDependency( + TwigBundle::class, + 'twig-bundle' + ); + + $dependencies->addClassDependency( + DoctrineBundle::class, + 'orm' + ); + + $dependencies->addClassDependency( + SecurityBundle::class, + 'security' + ); + } + + private function generateFormClass(ClassNameDetails $userClassDetails, Generator $generator, string $usernameField): ClassNameDetails + { + $formClassDetails = $generator->createClassNameDetails( + 'RegistrationFormType', + 'Form\\' + ); + + $formFields = [ + $usernameField => null, + 'agreeTerms' => [ + 'type' => CheckboxType::class, + 'options_code' => << false, + 'constraints' => [ + new IsTrue([ + 'message' => 'You should agree to our terms.', + ]), + ], + EOF + ], + 'plainPassword' => [ + 'type' => PasswordType::class, + 'options_code' => << false, + 'attr' => ['autocomplete' => 'new-password'], + 'constraints' => [ + new NotBlank([ + 'message' => 'Please enter a password', + ]), + new Length([ + 'min' => 6, + 'minMessage' => 'Your password should be at least {{ limit }} characters', + // max length allowed by Symfony for security reasons + 'max' => 4096, + ]), + ], + EOF + ], + ]; + + $this->formTypeRenderer->render( + $formClassDetails, + $formFields, + $userClassDetails, + [ + 'Symfony\Component\Validator\Constraints\IsTrue', + 'Symfony\Component\Validator\Constraints\Length', + 'Symfony\Component\Validator\Constraints\NotBlank', + ] + ); + + return $formClassDetails; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeResetPassword.php b/vendor/symfony/maker-bundle/src/Maker/MakeResetPassword.php new file mode 100644 index 0000000..38938f2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeResetPassword.php @@ -0,0 +1,553 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Doctrine\ORM\EntityManagerInterface; +use PhpParser\Builder\Param; +use Symfony\Bridge\Twig\AppVariable; +use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Doctrine\EntityClassGenerator; +use Symfony\Bundle\MakerBundle\Doctrine\ORMDependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToOne; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait; +use Symfony\Bundle\MakerBundle\Maker\Common\UidTrait; +use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\CliOutputHelper; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\RepeatedType; +use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Address; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Route as RouteObject; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Translation\Translator; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotCompromisedPassword; +use Symfony\Component\Validator\Constraints\PasswordStrength; +use Symfony\Component\Validator\Validation; +use Symfony\Component\Yaml\Yaml; +use Symfony\Contracts\Translation\TranslatorInterface; +use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait; +use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface; +use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface; +use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestTrait; +use SymfonyCasts\Bundle\ResetPassword\Persistence\Repository\ResetPasswordRequestRepositoryTrait; +use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepositoryInterface; +use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelper; +use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface; +use SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle; + +/** + * @author Romaric Drigon + * @author Jesse Rushlow + * @author Ryan Weaver + * @author Antoine Michelet + * + * @internal + * + * @final + */ +class MakeResetPassword extends AbstractMaker +{ + use CanGenerateTestsTrait; + use UidTrait; + + private string $fromEmailAddress; + private string $fromEmailName; + private string $controllerResetSuccessRedirect; + private ?RouteObject $controllerResetSuccessRoute = null; + private string $userClass; + private string $emailPropertyName; + private string $emailGetterMethodName; + private string $passwordSetterMethodName; + + public function __construct( + private FileManager $fileManager, + private DoctrineHelper $doctrineHelper, + private EntityClassGenerator $entityClassGenerator, + private ?RouterInterface $router = null, + ) { + } + + public static function getCommandName(): string + { + return 'make:reset-password'; + } + + public static function getCommandDescription(): string + { + return 'Create controller, entity, and repositories for use with symfonycasts/reset-password-bundle'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeResetPassword.txt')) + ; + + $this->addWithUuidOption($command); + $this->configureCommandWithTestsOption($command); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency(SymfonyCastsResetPasswordBundle::class, 'symfonycasts/reset-password-bundle'); + $dependencies->addClassDependency(MailerInterface::class, 'symfony/mailer'); + $dependencies->addClassDependency(Form::class, 'symfony/form'); + $dependencies->addClassDependency(Validation::class, 'symfony/validator'); + $dependencies->addClassDependency(SecurityBundle::class, 'security-bundle'); + $dependencies->addClassDependency(AppVariable::class, 'twig'); + + ORMDependencyBuilder::buildDependencies($dependencies); + + // reset-password-bundle 1.6 includes the ability to generate a fake token. + // we need to check that version 1.6 is installed + if (class_exists(ResetPasswordHelper::class) && !method_exists(ResetPasswordHelper::class, 'generateFakeResetToken')) { + throw new RuntimeCommandException('Please run "composer upgrade symfonycasts/reset-password-bundle". Version 1.6 or greater of this bundle is required.'); + } + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $io->title('Let\'s make a password reset feature!'); + + $this->checkIsUsingUid($input); + + $interactiveSecurityHelper = new InteractiveSecurityHelper(); + + if (!$this->fileManager->fileExists($path = 'config/packages/security.yaml')) { + throw new RuntimeCommandException('The file "config/packages/security.yaml" does not exist. PHP & XML configuration formats are currently not supported.'); + } + + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path)); + $securityData = $manipulator->getData(); + $providersData = $securityData['security']['providers'] ?? []; + + $this->userClass = $interactiveSecurityHelper->guessUserClass( + $io, + $providersData, + 'What is the User entity that should be used with the "forgotten password" feature? (e.g. App\\Entity\\User)' + ); + + $this->emailPropertyName = $interactiveSecurityHelper->guessEmailField($io, $this->userClass); + $this->emailGetterMethodName = $interactiveSecurityHelper->guessEmailGetter($io, $this->userClass, $this->emailPropertyName); + $this->passwordSetterMethodName = $interactiveSecurityHelper->guessPasswordSetter($io, $this->userClass); + + $io->text(\sprintf('Implementing reset password for %s', $this->userClass)); + + $io->section('- ResetPasswordController -'); + $io->text('A named route is used for redirecting after a successful reset. Even a route that does not exist yet can be used here.'); + + $this->controllerResetSuccessRedirect = $io->ask( + 'What route should users be redirected to after their password has been successfully reset?', + 'app_home', + Validator::notBlank(...) + ); + + if ($this->router instanceof RouterInterface) { + $this->controllerResetSuccessRoute = $this->router->getRouteCollection()->get($this->controllerResetSuccessRedirect); + } + + $io->section('- Email -'); + $emailText[] = 'These are used to generate the email code. Don\'t worry, you can change them in the code later!'; + $io->text($emailText); + + $this->fromEmailAddress = $io->ask( + 'What email address will be used to send reset confirmations? e.g. mailer@your-domain.com', + null, + Validator::validateEmailAddress(...) + ); + + $this->fromEmailName = $io->ask( + 'What "name" should be associated with that email address? e.g. "Acme Mail Bot"', + null, + Validator::notBlank(...) + ); + + $this->interactSetGenerateTests($input, $io); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $userClassNameDetails = $generator->createClassNameDetails( + '\\'.$this->userClass, + 'Entity\\' + ); + + $controllerClassNameDetails = $generator->createClassNameDetails( + 'ResetPasswordController', + 'Controller\\' + ); + + $requestClassNameDetails = $generator->createClassNameDetails( + 'ResetPasswordRequest', + 'Entity\\' + ); + + $repositoryClassNameDetails = $generator->createClassNameDetails( + 'ResetPasswordRequestRepository', + 'Repository\\' + ); + + $requestFormTypeClassNameDetails = $generator->createClassNameDetails( + 'ResetPasswordRequestFormType', + 'Form\\' + ); + + $changePasswordFormTypeClassNameDetails = $generator->createClassNameDetails( + 'ChangePasswordFormType', + 'Form\\' + ); + + $useStatements = new UseStatementGenerator([ + AbstractController::class, + $userClassNameDetails->getFullName(), + $changePasswordFormTypeClassNameDetails->getFullName(), + $requestFormTypeClassNameDetails->getFullName(), + TemplatedEmail::class, + RedirectResponse::class, + Request::class, + Response::class, + MailerInterface::class, + Address::class, + Route::class, + ResetPasswordControllerTrait::class, + ResetPasswordExceptionInterface::class, + ResetPasswordHelperInterface::class, + UserPasswordHasherInterface::class, + EntityManagerInterface::class, + ]); + + // Namespace for ResetPasswordExceptionInterface was imported above + $problemValidateMessageOrConstant = \defined('SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE') + ? 'ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE' + : "'There was a problem validating your password reset request'"; + $problemHandleMessageOrConstant = \defined('SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE') + ? 'ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE' + : "'There was a problem handling your password reset request'"; + + if ($isTranslatorAvailable = class_exists(Translator::class)) { + $useStatements->addUseStatement(TranslatorInterface::class); + } + + $generator->generateController( + $controllerClassNameDetails->getFullName(), + 'resetPassword/ResetPasswordController.tpl.php', + [ + 'use_statements' => $useStatements, + 'user_class_name' => $userClassNameDetails->getShortName(), + 'request_form_type_class_name' => $requestFormTypeClassNameDetails->getShortName(), + 'reset_form_type_class_name' => $changePasswordFormTypeClassNameDetails->getShortName(), + 'password_setter' => $this->passwordSetterMethodName, + 'success_redirect_route' => $this->controllerResetSuccessRedirect, + 'from_email' => $this->fromEmailAddress, + 'from_email_name' => $this->fromEmailName, + 'email_getter' => $this->emailGetterMethodName, + 'email_field' => $this->emailPropertyName, + 'problem_validate_message_or_constant' => $problemValidateMessageOrConstant, + 'problem_handle_message_or_constant' => $problemHandleMessageOrConstant, + 'translator_available' => $isTranslatorAvailable, + ] + ); + + $this->generateRequestEntity($generator, $requestClassNameDetails, $repositoryClassNameDetails, $userClassNameDetails); + + $this->setBundleConfig($io, $generator, $repositoryClassNameDetails->getFullName()); + + $useStatements = new UseStatementGenerator([ + AbstractType::class, + EmailType::class, + FormBuilderInterface::class, + OptionsResolver::class, + NotBlank::class, + ]); + + $generator->generateClass( + $requestFormTypeClassNameDetails->getFullName(), + 'resetPassword/ResetPasswordRequestFormType.tpl.php', + [ + 'use_statements' => $useStatements, + 'email_field' => $this->emailPropertyName, + ] + ); + + $useStatements = new UseStatementGenerator([ + AbstractType::class, + PasswordType::class, + RepeatedType::class, + FormBuilderInterface::class, + OptionsResolver::class, + Length::class, + NotBlank::class, + NotCompromisedPassword::class, + PasswordStrength::class, + ]); + + $generator->generateClass( + $changePasswordFormTypeClassNameDetails->getFullName(), + 'resetPassword/ChangePasswordFormType.tpl.php', + ['use_statements' => $useStatements] + ); + + $generator->generateTemplate( + 'reset_password/check_email.html.twig', + 'resetPassword/twig_check_email.tpl.php' + ); + + $generator->generateTemplate( + 'reset_password/email.html.twig', + 'resetPassword/twig_email.tpl.php' + ); + + $generator->generateTemplate( + 'reset_password/request.html.twig', + 'resetPassword/twig_request.tpl.php', + [ + 'email_field' => $this->emailPropertyName, + ] + ); + + $generator->generateTemplate( + 'reset_password/reset.html.twig', + 'resetPassword/twig_reset.tpl.php' + ); + + // Generate PHPUnit tests + if ($this->shouldGenerateTests()) { + $testClassDetails = $generator->createClassNameDetails( + 'ResetPasswordControllerTest', + 'Test\\', + ); + + $userRepositoryDetails = $generator->createClassNameDetails( + \sprintf('%sRepository', $userClassNameDetails->getShortName()), + 'Repository\\' + ); + + $useStatements = new UseStatementGenerator([ + $userClassNameDetails->getFullName(), + $userRepositoryDetails->getFullName(), + EntityManagerInterface::class, + KernelBrowser::class, + WebTestCase::class, + UserPasswordHasherInterface::class, + ]); + + $generator->generateFile( + targetPath: \sprintf('tests/%s.php', $testClassDetails->getShortName()), + templateName: 'resetPassword/Test.ResetPasswordController.tpl.php', + variables: [ + 'use_statements' => $useStatements, + 'user_short_name' => $userClassNameDetails->getShortName(), + 'user_repo_short_name' => $userRepositoryDetails->getShortName(), + 'success_route_path' => null !== $this->controllerResetSuccessRoute ? $this->controllerResetSuccessRoute->getPath() : '/', + 'from_email' => $this->fromEmailAddress, + ], + ); + + if (!class_exists(WebTestCase::class)) { + $io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.'); + } + } + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $this->successMessage($io, $requestClassNameDetails->getFullName()); + } + + private function setBundleConfig(ConsoleStyle $io, Generator $generator, string $repositoryClassFullName): void + { + $configFileExists = $this->fileManager->fileExists($path = 'config/packages/reset_password.yaml'); + + /* + * reset_password.yaml does not exist, we assume flex was present when + * the bundle was installed & a customized configuration is in use. + * Remind the developer to set the repository class accordingly. + */ + if (!$configFileExists) { + $io->text(\sprintf('We can\'t find %s. That\'s ok, you probably have a customized configuration.', $path)); + $io->text('Just remember to set the request_password_repository in your configuration.'); + $io->newLine(); + + return; + } + + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path)); + $data = $manipulator->getData(); + + $symfonyCastsKey = 'symfonycasts_reset_password'; + + /* + * reset_password.yaml exists, and was probably created by flex; + * Let's replace it with a "clean" file. + */ + if (1 >= (is_countable($data[$symfonyCastsKey]) ? \count($data[$symfonyCastsKey]) : 0)) { + $yaml = [ + $symfonyCastsKey => [ + 'request_password_repository' => $repositoryClassFullName, + ], + ]; + + $generator->dumpFile($path, Yaml::dump($yaml)); + + return; + } + + /* + * reset_password.yaml exists and appears to have been customized + * before running make:reset-password. Let's just change the repository + * value and preserve everything else. + */ + $data[$symfonyCastsKey]['request_password_repository'] = $repositoryClassFullName; + + $manipulator->setData($data); + + $generator->dumpFile($path, $manipulator->getContents()); + } + + private function successMessage(ConsoleStyle $io, string $requestClassName): void + { + $closing[] = 'Next:'; + $closing[] = \sprintf(' 1) Run "%s make:migration" to generate a migration for the new "%s" entity.', CliOutputHelper::getCommandPrefix(), $requestClassName); + $closing[] = ' 2) Review forms in "src/Form" to customize validation and labels.'; + $closing[] = ' 3) Review and customize the templates in `templates/reset_password`.'; + $closing[] = ' 4) Make sure your MAILER_DSN env var has the correct settings.'; + $closing[] = ' 5) Create a "forgot your password link" to the app_forgot_password_request route on your login form.'; + + $io->text($closing); + $io->newLine(); + $io->text('Then open your browser, go to "/reset-password" and enjoy!'); + $io->newLine(); + } + + private function generateRequestEntity(Generator $generator, ClassNameDetails $requestClassNameDetails, ClassNameDetails $repositoryClassNameDetails, ClassNameDetails $userClassDetails): void + { + // Generate ResetPasswordRequest Entity + $requestEntityPath = $this->entityClassGenerator->generateEntityClass( + entityClassDetails: $requestClassNameDetails, + apiResource: false, + generateRepositoryClass: false, + useUuidIdentifier: $this->getIdType() + ); + + $generator->writeChanges(); + + $manipulator = new ClassSourceManipulator( + sourceCode: $this->fileManager->getFileContents($requestEntityPath), + overwrite: false, + useAttributesForDoctrineMapping: $this->doctrineHelper->doesClassUsesAttributes($requestClassNameDetails->getFullName()), + ); + + $manipulator->addInterface(ResetPasswordRequestInterface::class); + + $manipulator->addTrait(ResetPasswordRequestTrait::class); + + $manipulator->addUseStatementIfNecessary($userClassDetails->getFullName()); + + $manipulator->addConstructor([ + (new Param('user'))->setType($userClassDetails->getShortName())->getNode(), + (new Param('expiresAt'))->setType('\DateTimeInterface')->getNode(), + (new Param('selector'))->setType('string')->getNode(), + (new Param('hashedToken'))->setType('string')->getNode(), + ], <<<'CODE' + user = $user; + $this->initialize($expiresAt, $selector, $hashedToken); + CODE + ); + + $manipulator->addManyToOneRelation(new RelationManyToOne( + propertyName: 'user', + targetClassName: $this->userClass, + mapInverseRelation: false, + avoidSetter: true, + isCustomReturnTypeNullable: false, + customReturnType: $userClassDetails->getShortName(), + isOwning: true, + )); + + $this->fileManager->dumpFile($requestEntityPath, $manipulator->getSourceCode()); + + $this->entityClassGenerator->generateRepositoryClass( + $repositoryClassNameDetails->getFullName(), + $requestClassNameDetails->getFullName(), + false, + false + ); + + $generator->writeChanges(); + + // Generate ResetPasswordRequestRepository + $pathRequestRepository = $this->fileManager->getRelativePathForFutureClass( + $repositoryClassNameDetails->getFullName() + ); + + $manipulator = new ClassSourceManipulator( + sourceCode: $this->fileManager->getFileContents($pathRequestRepository) + ); + + $manipulator->addInterface(ResetPasswordRequestRepositoryInterface::class); + + $manipulator->addTrait(ResetPasswordRequestRepositoryTrait::class); + + $methodBuilder = $manipulator->createMethodBuilder( + methodName: 'createResetPasswordRequest', + returnType: ResetPasswordRequestInterface::class, + isReturnTypeNullable: false, + commentLines: [\sprintf('@param %s $user', $userClassDetails->getShortName())] + ); + + $manipulator->addUseStatementIfNecessary($userClassDetails->getFullName()); + + $manipulator->addMethodBuilder($methodBuilder, [ + (new Param('user'))->setType('object')->getNode(), + (new Param('expiresAt'))->setType('\DateTimeInterface')->getNode(), + (new Param('selector'))->setType('string')->getNode(), + (new Param('hashedToken'))->setType('string')->getNode(), + ], <<<'CODE' + fileManager->dumpFile($pathRequestRepository, $manipulator->getSourceCode()); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeSchedule.php b/vendor/symfony/maker-bundle/src/Maker/MakeSchedule.php new file mode 100644 index 0000000..999fd00 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeSchedule.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Process\Process; +use Symfony\Component\Scheduler\Attribute\AsSchedule; +use Symfony\Component\Scheduler\RecurringMessage; +use Symfony\Component\Scheduler\Schedule; +use Symfony\Component\Scheduler\ScheduleProviderInterface; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class MakeSchedule extends AbstractMaker +{ + private string $scheduleName; + private ?string $message = null; + private ?string $transportName = null; + + public function __construct( + private FileManager $fileManager, + private Finder $finder = new Finder(), + ) { + } + + public static function getCommandName(): string + { + return 'make:schedule'; + } + + public static function getCommandDescription(): string + { + return 'Create a scheduler component'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeScheduler.txt')) + ; + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (!class_exists(AsSchedule::class)) { + $io->writeln('Running composer require symfony/scheduler'); + $process = Process::fromShellCommandline('composer require symfony/scheduler'); + $process->run(); + $io->writeln('Scheduler successfully installed!'); + } + + // Loop over existing src/Message/* and ask which message the user would like to schedule + $availableMessages = ['Empty Schedule']; + $messageDir = $this->fileManager->getRootDirectory().'/src/Message'; + + if ($this->fileManager->fileExists($messageDir)) { + $finder = $this->finder->in($this->fileManager->getRootDirectory().'/src/Message'); + + foreach ($finder->files() as $file) { + $availableMessages[] = $file->getFilenameWithoutExtension(); + } + } + + $this->transportName = $io->ask('What should we call the new transport? (To be used for the attribute #[AsSchedule(name)])'); + + $scheduleNameHint = 'MainSchedule'; + + // If the count is 1, no other messages were found - don't ask to create a message + if (1 !== \count($availableMessages)) { + $selectedMessage = $io->choice('Select which message', $availableMessages); + + if ('Empty Schedule' !== $selectedMessage) { + $this->message = $selectedMessage; + + // We don't want SomeMessageSchedule, so remove the "Message" suffix to give us SomeSchedule + $scheduleNameHint = \sprintf('%sSchedule', Str::removeSuffix($selectedMessage, 'Message')); + } + } + + // Ask the name of the new schedule + $this->scheduleName = $io->ask(question: 'What should we call the new schedule?', default: $scheduleNameHint); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $scheduleClassDetails = $generator->createClassNameDetails( + $this->scheduleName, + 'Scheduler\\', + ); + + $useStatements = new UseStatementGenerator([ + AsSchedule::class, + RecurringMessage::class, + Schedule::class, + ScheduleProviderInterface::class, + CacheInterface::class, + ]); + + if (null !== $this->message) { + $useStatements->addUseStatement('App\\Message\\'.$this->message); + } + + $generator->generateClass( + $scheduleClassDetails->getFullName(), + 'scheduler/Schedule.tpl.php', + [ + 'use_statements' => $useStatements, + 'has_custom_message' => null !== $this->message, + 'message_class_name' => $this->message, + 'has_transport_name' => null !== $this->transportName, + 'transport_name' => $this->transportName, + ], + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeSerializerEncoder.php b/vendor/symfony/maker-bundle/src/Maker/MakeSerializerEncoder.php new file mode 100644 index 0000000..7bdb69f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeSerializerEncoder.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Serializer; + +/** + * @author Piotr Grabski-Gradzinski + */ +final class MakeSerializerEncoder extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:serializer:encoder'; + } + + public static function getCommandDescription(): string + { + return 'Create a new serializer encoder class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your encoder (e.g. YamlEncoder)') + ->addArgument('format', InputArgument::OPTIONAL, 'Pick your format name (e.g. yaml)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeSerializerEncoder.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $encoderClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Serializer\\', + 'Encoder' + ); + $format = $input->getArgument('format'); + + $useStatements = new UseStatementGenerator([ + DecoderInterface::class, + EncoderInterface::class, + ]); + + /* @legacy - Remove "decoder_return_type" when Symfony 6.4 is no longer supported */ + $generator->generateClass( + $encoderClassNameDetails->getFullName(), + 'serializer/Encoder.tpl.php', + [ + 'use_statements' => $useStatements, + 'format' => $format, + 'use_decoder_return_type' => Kernel::VERSION_ID >= 70000, + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new serializer encoder class and start customizing it.', + 'Find the documentation at http://symfony.com/doc/current/serializer/custom_encoders.html', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + Serializer::class, + 'serializer' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeSerializerNormalizer.php b/vendor/symfony/maker-bundle/src/Maker/MakeSerializerNormalizer.php new file mode 100644 index 0000000..a23f24d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeSerializerNormalizer.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Serializer; + +/** + * @author Grégoire Pineau + */ +final class MakeSerializerNormalizer extends AbstractMaker +{ + public function __construct(private ?FileManager $fileManager = null) + { + if (null !== $this->fileManager) { + @trigger_deprecation( + 'symfony/maker-bundle', + '1.56.0', + \sprintf('Initializing MakeSerializerNormalizer while providing an instance of "%s" is deprecated. The $fileManager param will be removed in a future version.', FileManager::class) + ); + } + } + + public static function getCommandName(): string + { + return 'make:serializer:normalizer'; + } + + public static function getCommandDescription(): string + { + return 'Create a new serializer normalizer class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your normalizer (e.g. UserNormalizer)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeSerializerNormalizer.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $normalizerClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Serializer\\Normalizer\\', + \Normalizer::class + ); + + $useStatements = new UseStatementGenerator([ + NormalizerInterface::class, + Autowire::class, + \sprintf('App\Entity\%s', str_replace('Normalizer', '', $normalizerClassNameDetails->getShortName())), + ]); + + $entityDetails = $generator->createClassNameDetails( + str_replace('Normalizer', '', $normalizerClassNameDetails->getShortName()), + 'Entity\\', + ); + + if ($entityExists = class_exists($entityDetails->getFullName())) { + $useStatements->addUseStatement($entityDetails->getFullName()); + } + + $generator->generateClass($normalizerClassNameDetails->getFullName(), 'serializer/Normalizer.tpl.php', [ + 'use_statements' => $useStatements, + 'entity_exists' => $entityExists, + 'entity_name' => $entityDetails->getShortName(), + ]); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next:', + ' - Open your new serializer normalizer class and start customizing it.', + ' - Find the documentation at https://symfony.com/doc/current/serializer/custom_normalizer.html', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + Serializer::class, + 'serializer' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeStimulusController.php b/vendor/symfony/maker-bundle/src/Maker/MakeStimulusController.php new file mode 100644 index 0000000..95bee38 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeStimulusController.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\UX\StimulusBundle\StimulusBundle; +use Symfony\WebpackEncoreBundle\WebpackEncoreBundle; + +/** + * @author Abdelilah Jabri + * + * @internal + */ +final class MakeStimulusController extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:stimulus-controller'; + } + + public static function getCommandDescription(): string + { + return 'Create a new Stimulus controller'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::REQUIRED, 'The name of the Stimulus controller (e.g. hello)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeStimulusController.txt')); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $command->addArgument('extension', InputArgument::OPTIONAL); + $command->addArgument('targets', InputArgument::OPTIONAL); + $command->addArgument('values', InputArgument::OPTIONAL); + + $chosenExtension = $io->choice( + 'Language (JavaScript or TypeScript)', + [ + 'js' => 'JavaScript', + 'ts' => 'TypeScript', + ] + ); + + $input->setArgument('extension', $chosenExtension); + + if ($io->confirm('Do you want to include targets?')) { + $targets = []; + $isFirstTarget = true; + + while (true) { + $newTarget = $this->askForNextTarget($io, $targets, $isFirstTarget); + $isFirstTarget = false; + + if (null === $newTarget) { + break; + } + + $targets[] = $newTarget; + } + + $input->setArgument('targets', $targets); + } + + if ($io->confirm('Do you want to include values?')) { + $values = []; + $isFirstValue = true; + while (true) { + $newValue = $this->askForNextValue($io, $values, $isFirstValue); + $isFirstValue = false; + + if (null === $newValue) { + break; + } + + $values[$newValue['name']] = $newValue; + } + + $input->setArgument('values', $values); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $controllerName = Str::asSnakeCase($input->getArgument('name')); + $chosenExtension = $input->getArgument('extension'); + $targets = $input->getArgument('targets'); + $values = $input->getArgument('values'); + + $targets = empty($targets) ? $targets : \sprintf("['%s']", implode("', '", $targets)); + + $fileName = \sprintf('%s_controller.%s', $controllerName, $chosenExtension); + $filePath = \sprintf('assets/controllers/%s', $fileName); + + $generator->generateFile( + $filePath, + 'stimulus/Controller.tpl.php', + [ + 'targets' => $targets, + 'values' => $values, + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next:', + \sprintf('- Open %s and add the code you need', $filePath), + 'Find the documentation at https://github.com/symfony/stimulus-bridge', + ]); + } + + /** @param string[] $targets */ + private function askForNextTarget(ConsoleStyle $io, array $targets, bool $isFirstTarget): ?string + { + $questionText = 'New target name (press to stop adding targets)'; + + if (!$isFirstTarget) { + $questionText = 'Add another target? Enter the target name (or press to stop adding targets)'; + } + + $targetName = $io->ask($questionText, validator: function (?string $name) use ($targets) { + if (\in_array($name, $targets)) { + throw new \InvalidArgumentException(\sprintf('The "%s" target already exists.', $name)); + } + + return $name; + }); + + return !$targetName ? null : $targetName; + } + + /** + * @param array> $values + * + * @return array|null + */ + private function askForNextValue(ConsoleStyle $io, array $values, bool $isFirstValue): ?array + { + $questionText = 'New value name (press to stop adding values)'; + + if (!$isFirstValue) { + $questionText = 'Add another value? Enter the value name (or press to stop adding values)'; + } + + $valueName = $io->ask($questionText, null, function ($name) use ($values) { + if (\array_key_exists($name, $values)) { + throw new \InvalidArgumentException(\sprintf('The "%s" value already exists.', $name)); + } + + return $name; + }); + + if (!$valueName) { + return null; + } + + $defaultType = 'String'; + // try to guess the type by the value name prefix/suffix + // convert to snake case for simplicity + $snakeCasedField = Str::asSnakeCase($valueName); + + if (str_ends_with($snakeCasedField, '_id')) { + $defaultType = 'Number'; + } elseif (str_starts_with($snakeCasedField, 'is_')) { + $defaultType = 'Boolean'; + } elseif (str_starts_with($snakeCasedField, 'has_')) { + $defaultType = 'Boolean'; + } + + $type = null; + $types = $this->getValuesTypes(); + + while (null === $type) { + $question = new Question('Value type (enter ? to see all types)', $defaultType); + $question->setAutocompleterValues($types); + $type = $io->askQuestion($question); + + if ('?' === $type) { + $this->printAvailableTypes($io); + $io->writeln(''); + + $type = null; + } elseif (!\in_array($type, $types)) { + $this->printAvailableTypes($io); + $io->error(\sprintf('Invalid type "%s".', $type)); + $io->writeln(''); + + $type = null; + } + } + + return ['name' => $valueName, 'type' => $type]; + } + + private function printAvailableTypes(ConsoleStyle $io): void + { + foreach ($this->getValuesTypes() as $type) { + $io->writeln(\sprintf('%s', $type)); + } + } + + /** @return string[] */ + private function getValuesTypes(): array + { + return [ + 'Array', + 'Boolean', + 'Number', + 'Object', + 'String', + ]; + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + // lower than 8.1, allow WebpackEncoreBundle + if (\PHP_VERSION_ID < 80100) { + $dependencies->addClassDependency( + WebpackEncoreBundle::class, + 'symfony/webpack-encore-bundle' + ); + + return; + } + + // else: encourage StimulusBundle by requiring it + $dependencies->addClassDependency( + StimulusBundle::class, + 'symfony/stimulus-bundle' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeSubscriber.php b/vendor/symfony/maker-bundle/src/Maker/MakeSubscriber.php new file mode 100644 index 0000000..faba42d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeSubscriber.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\EventRegistry; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +trigger_deprecation('symfony/maker-bundle', '1.51', 'The "%s" class is deprecated, use "%s" instead.', MakeSubscriber::class, MakeListener::class); + +/** + * @deprecated since MakerBundle 1.51, use Symfony\Bundle\MakerBundle\Maker\MakeListener instead. + * + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeSubscriber extends AbstractMaker +{ + public function __construct(private EventRegistry $eventRegistry) + { + } + + public static function getCommandName(): string + { + return 'make:subscriber'; + } + + public static function getCommandDescription(): string + { + return 'Create a new event subscriber class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your event subscriber (e.g. ExceptionSubscriber)') + ->addArgument('event', InputArgument::OPTIONAL, 'What event do you want to subscribe to?') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeSubscriber.txt')) + ; + + $inputConfig->setArgumentAsNonInteractive('event'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (!$input->getArgument('event')) { + $events = $this->eventRegistry->getAllActiveEvents(); + + $io->writeln(' Suggested Events:'); + $io->listing($this->eventRegistry->listActiveEvents($events)); + $question = new Question(\sprintf(' %s', $command->getDefinition()->getArgument('event')->getDescription())); + $question->setAutocompleterValues($events); + $question->setValidator(Validator::notBlank(...)); + $event = $io->askQuestion($question); + $input->setArgument('event', $event); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $subscriberClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'EventSubscriber\\', + 'Subscriber' + ); + + $event = $input->getArgument('event'); + $eventFullClassName = $this->eventRegistry->getEventClassName($event); + $eventClassName = $eventFullClassName ? Str::getShortClassName($eventFullClassName) : null; + + $useStatements = new UseStatementGenerator([ + EventSubscriberInterface::class, + ]); + + // Determine if we use a KernelEvents::CONSTANT or custom even name + if (null !== ($eventConstant = $this->getEventConstant($event))) { + $useStatements->addUseStatement(KernelEvents::class); + $eventName = $eventConstant; + } else { + $eventName = class_exists($event) ? \sprintf('%s::class', $eventClassName) : \sprintf('\'%s\'', $event); + } + + if (null !== $eventFullClassName) { + $useStatements->addUseStatement($eventFullClassName); + } + + $generator->generateClass( + $subscriberClassNameDetails->getFullName(), + 'event/Subscriber.tpl.php', + [ + 'use_statements' => $useStatements, + 'event' => $eventName, + 'event_arg' => $eventClassName ? \sprintf('%s $event', $eventClassName) : '$event', + 'method_name' => class_exists($event) ? Str::asEventMethod($eventClassName) : Str::asEventMethod($event), + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new subscriber class and start customizing it.', + 'Find the documentation at https://symfony.com/doc/current/event_dispatcher.html#creating-an-event-subscriber', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + } + + private function getEventConstant(string $event): ?string + { + $constants = (new \ReflectionClass(KernelEvents::class))->getConstants(); + + if (false !== ($name = array_search($event, $constants, true))) { + return \sprintf('KernelEvents::%s', $name); + } + + return null; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeTest.php b/vendor/symfony/maker-bundle/src/Maker/MakeTest.php new file mode 100644 index 0000000..25ffa35 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeTest.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestAssertionsTrait; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputAwareMakerInterface; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\CssSelector\CssSelectorConverter; +use Symfony\Component\Panther\PantherTestCaseTrait; + +/** + * @author Kévin Dunglas + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeTest extends AbstractMaker implements InputAwareMakerInterface +{ + private const DESCRIPTIONS = [ + 'TestCase' => 'basic PHPUnit tests', + 'KernelTestCase' => 'basic tests that have access to Symfony services', + 'WebTestCase' => 'to run browser-like scenarios, but that don\'t execute JavaScript code', + 'ApiTestCase' => 'to run API-oriented scenarios', + 'PantherTestCase' => 'to run e2e scenarios, using a real-browser or HTTP client and a real web server', + ]; + private const DOCS = [ + 'TestCase' => 'https://symfony.com/doc/current/testing.html#unit-tests', + 'KernelTestCase' => 'https://symfony.com/doc/current/testing/database.html#functional-testing-of-a-doctrine-repository', + 'WebTestCase' => 'https://symfony.com/doc/current/testing.html#functional-tests', + 'ApiTestCase' => 'https://api-platform.com/docs/distribution/testing/', + 'PantherTestCase' => 'https://github.com/symfony/panther#testing-usage', + ]; + + public static function getCommandName(): string + { + return 'make:test'; + } + + /** + * @deprecated remove this method when removing make:unit-test and make:functional-test + * + * @return string[] + */ + public static function getCommandAliases(): iterable + { + yield 'make:unit-test'; + yield 'make:functional-test'; + } + + public static function getCommandDescription(): string + { + return 'Create a new test class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $typesDesc = []; + $typesHelp = []; + foreach (self::DESCRIPTIONS as $type => $desc) { + $typesDesc[] = \sprintf('%s (%s)', $type, $desc); + $typesHelp[] = \sprintf('* %s: %s', $type, $desc); + } + + $command + ->addArgument('type', InputArgument::OPTIONAL, 'The type of test: '.implode(', ', $typesDesc)) + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the test class (e.g. BlogPostTest)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeTest.txt').implode("\n", $typesHelp)); + + $inputConfig->setArgumentAsNonInteractive('name'); + $inputConfig->setArgumentAsNonInteractive('type'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + /* @deprecated remove the following block when removing make:unit-test and make:functional-test */ + $this->handleDeprecatedMakerCommands($input, $io); + + if (null !== $type = $input->getArgument('type')) { + if (!isset(self::DESCRIPTIONS[$type])) { + throw new RuntimeCommandException(\sprintf('The test type must be one of "%s", "%s" given.', implode('", "', array_keys(self::DESCRIPTIONS)), $type)); + } + } else { + $input->setArgument( + 'type', + $io->choice('Which test type would you like?', self::DESCRIPTIONS) + ); + } + + if ('ApiTestCase' === $input->getArgument('type') && !class_exists(ApiTestCase::class)) { + $io->warning([ + 'API Platform is required for this test type. Install it with', + 'composer require api', + ]); + } + + if ('PantherTestCase' === $input->getArgument('type') && !trait_exists(PantherTestCaseTrait::class)) { + $io->warning([ + 'symfony/panther is required for this test type. Install it with', + 'composer require symfony/panther --dev', + ]); + } + + if (null === $input->getArgument('name')) { + $io->writeln([ + '', + 'Choose a class name for your test, like:', + ' * UtilTest (to create tests/UtilTest.php)', + ' * Service\\UtilTest (to create tests/Service/UtilTest.php)', + ' * \\App\Tests\\Service\\UtilTest (to create tests/Service/UtilTest.php)', + ]); + + $nameArgument = $command->getDefinition()->getArgument('name'); + $value = $io->ask($nameArgument->getDescription(), $nameArgument->getDefault(), Validator::notBlank(...)); + $input->setArgument($nameArgument->getName(), $value); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $testClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Tests\\', + 'Test' + ); + + $type = $input->getArgument('type'); + + $generator->generateClass( + $testClassNameDetails->getFullName(), + "test/$type.tpl.php", + [ + 'web_assertions_are_available' => trait_exists(WebTestAssertionsTrait::class), + 'api_test_case_fqcn' => !class_exists(ApiTestCase::class) ? LegacyApiTestCase::class : ApiTestCase::class, + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new test class and start customizing it.', + \sprintf('Find the documentation at %s', self::DOCS[$type]), + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies, ?InputInterface $input = null): void + { + if (null === $input) { + return; + } + + switch ($input->getArgument('type')) { + case 'WebTestCase': + $dependencies->addClassDependency( + History::class, + 'browser-kit', + true, + true + ); + $dependencies->addClassDependency( + CssSelectorConverter::class, + 'css-selector', + true, + true + ); + + return; + + case 'ApiTestCase': + $dependencies->addClassDependency( + !class_exists(ApiTestCase::class) ? LegacyApiTestCase::class : ApiTestCase::class, + 'api', + true, + false + ); + + return; + + case 'PantherTestCase': + $dependencies->addClassDependency( + PantherTestCaseTrait::class, + 'panther', + true, + true + ); + + return; + } + } + + /** + * @deprecated + */ + private function handleDeprecatedMakerCommands(InputInterface $input, ConsoleStyle $io): void + { + $currentCommand = $input->getFirstArgument(); + switch ($currentCommand) { + case 'make:unit-test': + $input->setArgument('type', 'TestCase'); + $io->warning('The "make:unit-test" command is deprecated, use "make:test" instead.'); + break; + + case 'make:functional-test': + $input->setArgument('type', trait_exists(PantherTestCaseTrait::class) ? 'WebTestCase' : 'PantherTestCase'); + $io->warning('The "make:functional-test" command is deprecated, use "make:test" instead.'); + break; + } + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeTwigComponent.php b/vendor/symfony/maker-bundle/src/Maker/MakeTwigComponent.php new file mode 100644 index 0000000..91908eb --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeTwigComponent.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Yaml\Yaml; +use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; + +/** + * @author Kevin Bond + */ +final class MakeTwigComponent extends AbstractMaker +{ + private string $namespace = 'Twig\\Components'; + + public function __construct(private FileManager $fileManager) + { + } + + public static function getCommandName(): string + { + return 'make:twig-component'; + } + + public static function getCommandDescription(): string + { + return 'Create a twig (or live) component'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setDescription(self::getCommandDescription()) + ->addArgument('name', InputArgument::OPTIONAL, 'The name of your twig component (ie Notification)') + ->addOption('live', null, InputOption::VALUE_NONE, 'Whether to create a live twig component (requires symfony/ux-live-component)') + ; + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency(AsTwigComponent::class, 'symfony/ux-twig-component'); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $name = $input->getArgument('name'); + $live = $input->getOption('live'); + + if ($live && !class_exists(AsLiveComponent::class)) { + throw new \RuntimeException('You must install symfony/ux-live-component to create a live component (composer require symfony/ux-live-component)'); + } + + $factory = $generator->createClassNameDetails( + $name, + $this->namespace, + ); + + $templatePath = str_replace('\\', '/', $factory->getRelativeNameWithoutSuffix()); + $shortName = str_replace('\\', ':', $factory->getRelativeNameWithoutSuffix()); + + $generator->generateClass( + $factory->getFullName(), + \sprintf('%s/../Resources/skeleton/twig/%s', __DIR__, $live ? 'LiveComponent.tpl.php' : 'Component.tpl.php'), + [ + 'live' => $live, + ] + ); + $generator->generateTemplate( + "components/{$templatePath}.html.twig", + \sprintf('%s/../Resources/skeleton/twig/%s', __DIR__, 'component_template.tpl.php') + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $io->newLine(); + $io->writeln(" To render the component, use ."); + $io->newLine(); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (!$input->getOption('live')) { + $input->setOption('live', $io->confirm('Make this a live component?', false)); + } + + $path = 'config/packages/twig_component.yaml'; + + if (!$this->fileManager->fileExists($path)) { + throw new RuntimeCommandException(message: 'Unable to find twig_component.yaml'); + } + + try { + $value = Yaml::parse($this->fileManager->getFileContents($path)); + $this->namespace = substr(array_key_first($value['twig_component']['defaults']), 4); + } catch (\Throwable $throwable) { + throw new RuntimeCommandException(message: 'Unable to parse twig_component.yaml', previous: $throwable); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeTwigExtension.php b/vendor/symfony/maker-bundle/src/Maker/MakeTwigExtension.php new file mode 100644 index 0000000..a51ffe9 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeTwigExtension.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Twig\Extension\AbstractExtension; +use Twig\Extension\RuntimeExtensionInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeTwigExtension extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:twig-extension'; + } + + public static function getCommandDescription(): string + { + return 'Create a new Twig extension with its runtime class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the Twig extension class (e.g. AppExtension)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeTwigExtension.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $name = $input->getArgument('name'); + + $extensionClassNameDetails = $generator->createClassNameDetails( + $name, + 'Twig\\Extension\\', + 'Extension' + ); + + $runtimeClassNameDetails = $generator->createClassNameDetails( + $name, + 'Twig\\Runtime\\', + 'Runtime' + ); + + $useStatements = new UseStatementGenerator([ + AbstractExtension::class, + TwigFilter::class, + TwigFunction::class, + $runtimeClassNameDetails->getFullName(), + ]); + + $runtimeUseStatements = new UseStatementGenerator([ + RuntimeExtensionInterface::class, + ]); + + $generator->generateClass( + $extensionClassNameDetails->getFullName(), + 'twig/Extension.tpl.php', + ['use_statements' => $useStatements, 'runtime_class_name' => $runtimeClassNameDetails->getShortName()] + ); + + $generator->generateClass( + $runtimeClassNameDetails->getFullName(), + 'twig/Runtime.tpl.php', + ['use_statements' => $runtimeUseStatements] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new extension class and start customizing it.', + 'Find the documentation at http://symfony.com/doc/current/templating/twig_extension.html', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + AbstractExtension::class, + 'twig' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeUnitTest.php b/vendor/symfony/maker-bundle/src/Maker/MakeUnitTest.php new file mode 100644 index 0000000..d7ef272 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeUnitTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; + +trigger_deprecation('symfony/maker-bundle', '1.29', 'The "%s" class is deprecated, use "%s" instead.', MakeUnitTest::class, MakeTest::class); + +/** + * @deprecated since MakerBundle 1.29, use Symfony\Bundle\MakerBundle\Maker\MakeTest instead. + * + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeUnitTest extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:unit-test'; + } + + public static function getCommandDescription(): string + { + return 'Create a new unit test class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the unit test class (e.g. UtilTest)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeUnitTest.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $testClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Tests\\', + 'Test' + ); + + $generator->generateClass( + $testClassNameDetails->getFullName(), + 'test/Unit.tpl.php', + ['use_statements' => new UseStatementGenerator([TestCase::class])] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new test class and start customizing it.', + 'Find the documentation at https://symfony.com/doc/current/testing.html#unit-tests', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeUser.php b/vendor/symfony/maker-bundle/src/Maker/MakeUser.php new file mode 100644 index 0000000..8027672 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeUser.php @@ -0,0 +1,261 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Doctrine\EntityClassGenerator; +use Symfony\Bundle\MakerBundle\Doctrine\ORMDependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\Common\UidTrait; +use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater; +use Symfony\Bundle\MakerBundle\Security\UserClassBuilder; +use Symfony\Bundle\MakerBundle\Security\UserClassConfiguration; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlManipulationFailedException; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Yaml\Yaml; + +/** + * @author Ryan Weaver + * + * @internal + */ +final class MakeUser extends AbstractMaker +{ + use UidTrait; + + public function __construct( + private FileManager $fileManager, + private UserClassBuilder $userClassBuilder, + private SecurityConfigUpdater $configUpdater, + private EntityClassGenerator $entityClassGenerator, + private DoctrineHelper $doctrineHelper, + ) { + } + + public static function getCommandName(): string + { + return 'make:user'; + } + + public static function getCommandDescription(): string + { + return 'Create a new security user class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the security user class (e.g. User)') + ->addOption('is-entity', null, InputOption::VALUE_NONE, 'Do you want to store user data in the database (via Doctrine)?') + ->addOption('identity-property-name', null, InputOption::VALUE_REQUIRED, 'Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid)') + ->addOption('with-password', null, InputOption::VALUE_NONE, 'Will this app be responsible for checking the password? Choose No if the password is actually checked by some other system (e.g. a single sign-on server)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeUser.txt')); + + $this->addWithUuidOption($command); + + $inputConfig->setArgumentAsNonInteractive('name'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $this->checkIsUsingUid($input); + + if (null === $input->getArgument('name')) { + $name = $io->ask( + $command->getDefinition()->getArgument('name')->getDescription(), + 'User' + ); + $input->setArgument('name', $name); + } + + $userIsEntity = $io->confirm( + 'Do you want to store user data in the database (via Doctrine)?', + class_exists(DoctrineBundle::class) + ); + if ($userIsEntity) { + $dependencies = new DependencyBuilder(); + ORMDependencyBuilder::buildDependencies($dependencies); + + $missingPackagesMessage = $dependencies->getMissingPackagesMessage(self::getCommandName(), 'Doctrine must be installed to store user data in the database'); + if ($missingPackagesMessage) { + throw new RuntimeCommandException($missingPackagesMessage); + } + } + $input->setOption('is-entity', $userIsEntity); + + $identityFieldName = $io->ask('Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid)', 'email', Validator::validatePropertyName(...)); + $input->setOption('identity-property-name', $identityFieldName); + + $io->text('Will this app need to hash/check user passwords? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server).'); + $userWillHavePassword = $io->confirm('Does this app need to hash/check user passwords?'); + $input->setOption('with-password', $userWillHavePassword); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $userClassConfiguration = new UserClassConfiguration( + $input->getOption('is-entity'), + $input->getOption('identity-property-name'), + $input->getOption('with-password') + ); + + $userClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + $userClassConfiguration->isEntity() ? 'Entity\\' : 'Security\\' + ); + + // A) Generate the User class + if ($userClassConfiguration->isEntity()) { + $classPath = $this->entityClassGenerator->generateEntityClass( + entityClassDetails: $userClassNameDetails, + apiResource: false, // api resource + withPasswordUpgrade: $userClassConfiguration->hasPassword(), // security user + useUuidIdentifier: $this->getIdType() + ); + } else { + $classPath = $generator->generateClass($userClassNameDetails->getFullName(), 'Class.tpl.php'); + } + // need to write changes early so we can modify the contents below + $generator->writeChanges(); + + $entityUsesAttributes = ($isEntity = $userClassConfiguration->isEntity()) && $this->doctrineHelper->doesClassUsesAttributes($userClassNameDetails->getFullName()); + + if ($isEntity && !$entityUsesAttributes) { + throw new \RuntimeException('MakeUser only supports attribute mapping with doctrine entities.'); + } + + // B) Implement UserInterface + $manipulator = new ClassSourceManipulator( + sourceCode: $this->fileManager->getFileContents($classPath), + overwrite: true, + useAttributesForDoctrineMapping: $entityUsesAttributes + ); + + $manipulator->setIo($io); + + $this->userClassBuilder->addUserInterfaceImplementation($manipulator, $userClassConfiguration); + + $generator->dumpFile($classPath, $manipulator->getSourceCode()); + + // C) Generate a custom user provider, if necessary + if (!$userClassConfiguration->isEntity()) { + $userClassConfiguration->setUserProviderClass($generator->getRootNamespace().'\\Security\\UserProvider'); + + $useStatements = new UseStatementGenerator([ + UnsupportedUserException::class, + UserNotFoundException::class, + PasswordAuthenticatedUserInterface::class, + PasswordUpgraderInterface::class, + UserInterface::class, + UserProviderInterface::class, + ]); + + $customProviderPath = $generator->generateClass( + $userClassConfiguration->getUserProviderClass(), + 'security/UserProvider.tpl.php', + [ + 'use_statements' => $useStatements, + 'user_short_name' => $userClassNameDetails->getShortName(), + ] + ); + } + + // D) Update security.yaml + $securityYamlUpdated = false; + $path = 'config/packages/security.yaml'; + if ($this->fileManager->fileExists($path)) { + try { + $newYaml = $this->configUpdater->updateForUserClass( + $this->fileManager->getFileContents($path), + $userClassConfiguration, + $userClassNameDetails->getFullName() + ); + $generator->dumpFile($path, $newYaml); + $securityYamlUpdated = true; + } catch (YamlManipulationFailedException) { + } + } + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text('Next Steps:'); + $nextSteps = [ + \sprintf('Review your new %s class.', $userClassNameDetails->getFullName()), + ]; + if ($userClassConfiguration->isEntity()) { + $nextSteps[] = \sprintf( + 'Use make:entity to add more fields to your %s entity and then run make:migration.', + $userClassNameDetails->getShortName() + ); + } else { + $nextSteps[] = \sprintf( + 'Open %s to finish implementing your user provider.', + /* @phpstan-ignore-next-line - $customProviderPath is defined in this else statement */ + $this->fileManager->relativizePath($customProviderPath) + ); + } + + if (!$securityYamlUpdated) { + $yamlExample = $this->configUpdater->updateForUserClass( + 'security: {}', + $userClassConfiguration, + $userClassNameDetails->getFullName() + ); + $nextSteps[] = "Your security.yaml could not be updated automatically. You'll need to add the following config manually:\n\n".$yamlExample; + } + + $nextSteps[] = 'Create a way to authenticate! See https://symfony.com/doc/current/security.html'; + + $nextSteps = array_map(static fn ($step) => \sprintf(' - %s', $step), $nextSteps); + $io->text($nextSteps); + } + + public function configureDependencies(DependencyBuilder $dependencies, ?InputInterface $input = null): void + { + // checking for SecurityBundle guarantees security.yaml is present + $dependencies->addClassDependency( + SecurityBundle::class, + 'security' + ); + + // needed to update the YAML files + $dependencies->addClassDependency( + Yaml::class, + 'yaml' + ); + + if (null !== $input && $input->getOption('is-entity')) { + ORMDependencyBuilder::buildDependencies($dependencies); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeValidator.php b/vendor/symfony/maker-bundle/src/Maker/MakeValidator.php new file mode 100644 index 0000000..7fb12c2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeValidator.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Validator\Validation; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeValidator extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:validator'; + } + + public static function getCommandDescription(): string + { + return 'Create a new validator and constraint class'; + } + + /** @return void */ + public function configureCommand(Command $command, InputConfiguration $inputConf) + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the validator class (e.g. EnabledValidator)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeValidator.txt')) + ; + } + + /** @return void */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $validatorClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Validator\\', + 'Validator' + ); + + $constraintFullClassName = Str::removeSuffix($validatorClassNameDetails->getFullName(), 'Validator'); + + $generator->generateClass( + $validatorClassNameDetails->getFullName(), + 'validator/Validator.tpl.php', + [ + 'constraint_class_name' => Str::getShortClassName($constraintFullClassName), + ] + ); + + $generator->generateClass( + $constraintFullClassName, + 'validator/Constraint.tpl.php', + [] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your new constraint & validators and add your logic.', + 'Find the documentation at http://symfony.com/doc/current/validation/custom_constraint.html', + ]); + } + + /** @return void */ + public function configureDependencies(DependencyBuilder $dependencies) + { + $dependencies->addClassDependency( + Validation::class, + 'validator' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeVoter.php b/vendor/symfony/maker-bundle/src/Maker/MakeVoter.php new file mode 100644 index 0000000..faabc99 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeVoter.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class MakeVoter extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:voter'; + } + + public static function getCommandDescription(): string + { + return 'Create a new security voter class'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the security voter class (e.g. BlogPostVoter)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeVoter.txt')) + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $voterClassData = ClassData::create( + class: \sprintf('Security\Voter\%s', $input->getArgument('name')), + suffix: 'Voter', + extendsClass: Voter::class, + useStatements: [ + TokenInterface::class, + Voter::class, + UserInterface::class, + ] + ); + + $generator->generateClass( + $voterClassData->getFullClassName(), + 'security/Voter.tpl.php', + ['class_data' => $voterClassData] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your voter and add your logic.', + 'Find the documentation at https://symfony.com/doc/current/security/voters.html', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + Voter::class, + 'security' + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/MakeWebhook.php b/vendor/symfony/maker-bundle/src/Maker/MakeWebhook.php new file mode 100644 index 0000000..a0c77a0 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/MakeWebhook.php @@ -0,0 +1,307 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputAwareMakerInterface; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\Common\InstallDependencyTrait; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\ChainRequestMatcher; +use Symfony\Component\HttpFoundation\Exception\JsonException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\IpsRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PortRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\SchemeRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Client\AbstractRequestParser; +use Symfony\Component\Webhook\Exception\RejectWebhookException; +use Symfony\Component\Yaml\Yaml; + +/** + * @author Maelan LE BORGNE + * + * @internal + */ +final class MakeWebhook extends AbstractMaker implements InputAwareMakerInterface +{ + use InstallDependencyTrait; + + public const WEBHOOK_NAME_PATTERN = '/^[a-zA-Z_.\-\x80-\xff][a-zA-Z0-9_.\-\x80-\xff]*$/u'; + private const WEBHOOK_CONFIG_PATH = 'config/packages/webhook.yaml'; + + private ConsoleStyle $io; + + private YamlSourceManipulator $ysm; + private string $name; + + /** @var array */ + private array $requestMatchers = []; + + public function __construct( + private FileManager $fileManager, + private Generator $generator, + ) { + } + + public static function getCommandName(): string + { + return 'make:webhook'; + } + + public static function getCommandDescription(): string + { + return 'Create a new Webhook'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'Name of the webhook to create (e.g. github, stripe, ...)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeWebhook.txt')) + ; + + $inputConfig->setArgumentAsNonInteractive('name'); + } + + public function configureDependencies(DependencyBuilder $dependencies, ?InputInterface $input = null): void + { + $dependencies->addClassDependency( + Yaml::class, + 'yaml' + ); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $this->io = $io; + + $this->installDependencyIfNeeded($io, AbstractRequestParser::class, 'symfony/webhook'); + + if ($this->name = $input->getArgument('name') ?? '') { + if (!$this->verifyWebhookName($this->name)) { + throw new RuntimeCommandException('A webhook name can only have alphanumeric characters, underscores, dots, and dashes.'); + } + + return; + } + + $argument = $command->getDefinition()->getArgument('name'); + $question = new Question($argument->getDescription()); + $question->setValidator(Validator::notBlank(...)); + + $this->name = $this->io->askQuestion($question); + + while (!$this->verifyWebhookName($this->name)) { + $this->io->error('A webhook name can only have alphanumeric characters, underscores, dots, and dashes.'); + $this->name = $this->io->askQuestion($question); + } + + while (true) { + $newRequestMatcher = $this->askForNextRequestMatcher(isFirstMatcher: empty($this->requestMatchers)); + + if (null === $newRequestMatcher) { + break; + } + + $this->requestMatchers[] = $newRequestMatcher; + } + + if (\in_array(ExpressionRequestMatcher::class, $this->requestMatchers, true)) { + $this->installDependencyIfNeeded($this->io, Expression::class, 'symfony/expression-language'); + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $requestParserDetails = $this->generator->createClassNameDetails( + Str::asClassName($this->name.'RequestParser'), + 'Webhook\\' + ); + $remoteEventConsumerDetails = $this->generator->createClassNameDetails( + Str::asClassName($this->name.'WebhookConsumer'), + 'RemoteEvent\\' + ); + + $this->addToYamlConfig($this->name, $requestParserDetails); + + $this->generateRequestParser(requestParserDetails: $requestParserDetails); + + $this->generator->generateClass( + $remoteEventConsumerDetails->getFullName(), + 'webhook/WebhookConsumer.tpl.php', + [ + 'webhook_name' => $this->name, + ] + ); + + $this->generator->writeChanges(); + $this->fileManager->dumpFile(self::WEBHOOK_CONFIG_PATH, $this->ysm->getContents()); + + $this->writeSuccessMessage($io); + } + + private function verifyWebhookName(string $entityName): bool + { + return preg_match(self::WEBHOOK_NAME_PATTERN, $entityName); + } + + private function addToYamlConfig(string $webhookName, ClassNameDetails $requestParserDetails): void + { + $yamlConfig = Yaml::dump(['framework' => ['webhook' => ['routing' => []]]], 4, 2); + if ($this->fileManager->fileExists(self::WEBHOOK_CONFIG_PATH)) { + $yamlConfig = $this->fileManager->getFileContents(self::WEBHOOK_CONFIG_PATH); + } + + $this->ysm = new YamlSourceManipulator($yamlConfig); + $arrayConfig = $this->ysm->getData(); + + if (\array_key_exists($webhookName, $arrayConfig['framework']['webhook']['routing'] ?? [])) { + throw new \InvalidArgumentException('A webhook with this name already exists'); + } + + $arrayConfig['framework']['webhook']['routing'][$webhookName] = [ + 'service' => $requestParserDetails->getFullName(), + 'secret' => 'your_secret_here', + ]; + $this->ysm->setData( + $arrayConfig + ); + } + + /** + * @throws \Exception + */ + private function generateRequestParser(ClassNameDetails $requestParserDetails): void + { + $useStatements = new UseStatementGenerator([ + JsonException::class, + Request::class, + Response::class, + RemoteEvent::class, + AbstractRequestParser::class, + RejectWebhookException::class, + RequestMatcherInterface::class, + ]); + + // Use a ChainRequestMatcher if multiple matchers have been added OR if none (will be printed with an empty array) + $useChainRequestsMatcher = false; + + if (1 !== \count($this->requestMatchers)) { + $useChainRequestsMatcher = true; + $useStatements->addUseStatement(ChainRequestMatcher::class); + } + + $requestMatcherArguments = []; + + foreach ($this->requestMatchers as $requestMatcherClass) { + $useStatements->addUseStatement($requestMatcherClass); + $requestMatcherArguments[$requestMatcherClass] = $this->getRequestMatcherArguments(requestMatcherClass: $requestMatcherClass); + + if (ExpressionRequestMatcher::class === $requestMatcherClass) { + $useStatements->addUseStatement(Expression::class); + $useStatements->addUseStatement(ExpressionLanguage::class); + } + } + + $this->generator->generateClass( + $requestParserDetails->getFullName(), + 'webhook/RequestParser.tpl.php', + [ + 'use_statements' => $useStatements, + 'use_chained_requests_matcher' => $useChainRequestsMatcher, + 'request_matchers' => $this->requestMatchers, + 'request_matcher_arguments' => $requestMatcherArguments, + ] + ); + } + + private function askForNextRequestMatcher(bool $isFirstMatcher): ?string + { + $this->io->newLine(); + + $availableMatchers = $this->getAvailableRequestMatchers(); + $matcherName = null; + + while (null === $matcherName) { + if ($isFirstMatcher) { + $questionText = 'Add a RequestMatcher (press to skip this step)'; + } else { + $questionText = 'Add another RequestMatcher? Enter the RequestMatcher name (or press to stop adding matchers)'; + } + + $choices = array_diff($availableMatchers, $this->requestMatchers); + $question = new ChoiceQuestion($questionText, array_values([''] + $choices), 0); + $matcherName = $this->io->askQuestion($question); + + if ('' === $matcherName) { + return null; + } + } + + return $matcherName; + } + + /** @return string[] */ + private function getAvailableRequestMatchers(): array + { + return [ + AttributesRequestMatcher::class, + ExpressionRequestMatcher::class, + HostRequestMatcher::class, + IpsRequestMatcher::class, + IsJsonRequestMatcher::class, + MethodRequestMatcher::class, + PathRequestMatcher::class, + PortRequestMatcher::class, + SchemeRequestMatcher::class, + ]; + } + + private function getRequestMatcherArguments(string $requestMatcherClass): string + { + return match ($requestMatcherClass) { + AttributesRequestMatcher::class => '[\'attributeName\' => \'regex\']', + ExpressionRequestMatcher::class => 'new ExpressionLanguage(), new Expression(\'expression\')', + HostRequestMatcher::class, PathRequestMatcher::class => '\'regex\'', + IpsRequestMatcher::class => '[\'127.0.0.1\']', + IsJsonRequestMatcher::class => '', + MethodRequestMatcher::class => '\'POST\'', + PortRequestMatcher::class => '443', + SchemeRequestMatcher::class => '\'https\'', + default => '[]', + }; + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/Security/MakeCustomAuthenticator.php b/vendor/symfony/maker-bundle/src/Maker/Security/MakeCustomAuthenticator.php new file mode 100644 index 0000000..6cc1407 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/Security/MakeCustomAuthenticator.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker\Security; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\AbstractMaker; +use Symfony\Bundle\MakerBundle\Maker\Common\InstallDependencyTrait; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; +use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class MakeCustomAuthenticator extends AbstractMaker +{ + use InstallDependencyTrait; + + private const SECURITY_CONFIG_PATH = 'config/packages/security.yaml'; + + private ClassNameDetails $authenticatorClassName; + + public function __construct( + private FileManager $fileManager, + private Generator $generator, + ) { + } + + public static function getCommandName(): string + { + return 'make:security:custom'; + } + + public static function getCommandDescription(): string + { + return 'Create a custom security authenticator.'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../../Resources/help/security/MakeCustom.txt')) + ; + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $this->installDependencyIfNeeded( + io: $io, + expectedClassToExist: AbstractAuthenticator::class, + composerPackage: 'symfony/security-bundle' + ); + + if (!$this->fileManager->fileExists(self::SECURITY_CONFIG_PATH)) { + throw new RuntimeCommandException(\sprintf('The file "%s" does not exist. PHP & XML configuration formats are currently not supported.', self::SECURITY_CONFIG_PATH)); + } + + $name = $io->ask( + question: 'What is the class name of the authenticator (e.g. CustomAuthenticator)', + validator: static function (mixed $answer) { + return Validator::notBlank($answer); + } + ); + + $this->authenticatorClassName = $this->generator->createClassNameDetails( + name: $name, + namespacePrefix: 'Security\\', + suffix: 'Authenticator' + ); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + // Configure security to use custom authenticator + $securityConfig = ($ysm = new YamlSourceManipulator( + $this->fileManager->getFileContents(self::SECURITY_CONFIG_PATH) + ))->getData(); + + $securityConfig['security']['firewalls']['main']['custom_authenticators'] = [$this->authenticatorClassName->getFullName()]; + + $ysm->setData($securityConfig); + $generator->dumpFile(self::SECURITY_CONFIG_PATH, $ysm->getContents()); + + // Generate the new authenticator + $useStatements = new UseStatementGenerator([ + Request::class, + Response::class, + TokenInterface::class, + AuthenticationException::class, + AbstractAuthenticator::class, + Passport::class, + JsonResponse::class, + UserBadge::class, + CustomUserMessageAuthenticationException::class, + SelfValidatingPassport::class, + ]); + + $generator->generateClass( + className: $this->authenticatorClassName->getFullName(), + templateName: 'security/custom/Authenticator.tpl.php', + variables: [ + 'use_statements' => $useStatements, + 'class_short_name' => $this->authenticatorClassName->getShortName(), + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + } +} diff --git a/vendor/symfony/maker-bundle/src/Maker/Security/MakeFormLogin.php b/vendor/symfony/maker-bundle/src/Maker/Security/MakeFormLogin.php new file mode 100644 index 0000000..8ae6e01 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Maker/Security/MakeFormLogin.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker\Security; + +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Doctrine\ORM\EntityManager; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\AbstractMaker; +use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait; +use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper; +use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater; +use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Symfony\Component\Yaml\Yaml; + +/** + * Generate Form Login Security using SecurityBundle's Authenticator. + * + * @see https://symfony.com/doc/current/security.html#form-login + * + * @author Jesse Rushlow + * + * @internal + */ +final class MakeFormLogin extends AbstractMaker +{ + use CanGenerateTestsTrait; + + private const SECURITY_CONFIG_PATH = 'config/packages/security.yaml'; + private YamlSourceManipulator $ysm; + private string $controllerName; + private string $firewallToUpdate; + private string $userClass; + private string $userNameField; + private bool $willLogout; + + public function __construct( + private FileManager $fileManager, + private SecurityConfigUpdater $securityConfigUpdater, + private SecurityControllerBuilder $securityControllerBuilder, + ) { + } + + public static function getCommandName(): string + { + return 'make:security:form-login'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command->setHelp(file_get_contents(\dirname(__DIR__, 2).'/Resources/help/security/MakeFormLogin.txt')); + + $this->configureCommandWithTestsOption($command); + } + + public static function getCommandDescription(): string + { + return 'Generate the code needed for the form_login authenticator'; + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency( + SecurityBundle::class, + 'security' + ); + + $dependencies->addClassDependency(TwigBundle::class, 'twig'); + + // needed to update the YAML files + $dependencies->addClassDependency( + Yaml::class, + 'yaml' + ); + + $dependencies->addClassDependency(DoctrineBundle::class, 'orm'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (!$this->fileManager->fileExists(self::SECURITY_CONFIG_PATH)) { + throw new RuntimeCommandException(\sprintf('The file "%s" does not exist. PHP & XML configuration formats are currently not supported.', self::SECURITY_CONFIG_PATH)); + } + + $this->ysm = new YamlSourceManipulator($this->fileManager->getFileContents(self::SECURITY_CONFIG_PATH)); + $securityData = $this->ysm->getData(); + + if (!isset($securityData['security']['providers']) || !$securityData['security']['providers']) { + throw new RuntimeCommandException('To generate a form login authentication, you must configure at least one entry under "providers" in "security.yaml".'); + } + + $this->controllerName = $io->ask( + 'Choose a name for the controller class (e.g. SecurityController)', + 'SecurityController', + Validator::validateClassName(...) + ); + + $securityHelper = new InteractiveSecurityHelper(); + $this->firewallToUpdate = $securityHelper->guessFirewallName($io, $securityData); + $this->userClass = $securityHelper->guessUserClass($io, $securityData['security']['providers']); + $this->userNameField = $securityHelper->guessUserNameField($io, $this->userClass, $securityData['security']['providers']); + $this->willLogout = $io->confirm('Do you want to generate a \'/logout\' URL?'); + + $this->interactSetGenerateTests($input, $io); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $useStatements = new UseStatementGenerator([ + AbstractController::class, + Response::class, + Route::class, + AuthenticationUtils::class, + ]); + + $controllerNameDetails = $generator->createClassNameDetails($this->controllerName, 'Controller\\', 'Controller'); + $templatePath = strtolower($controllerNameDetails->getRelativeNameWithoutSuffix()); + + $controllerPath = $generator->generateController( + $controllerNameDetails->getFullName(), + 'security/formLogin/LoginController.tpl.php', + [ + 'use_statements' => $useStatements, + 'controller_name' => $controllerNameDetails->getShortName(), + 'template_path' => $templatePath, + ] + ); + + if ($this->willLogout) { + $manipulator = new ClassSourceManipulator($generator->getFileContentsForPendingOperation($controllerPath)); + + $this->securityControllerBuilder->addLogoutMethod($manipulator); + + $generator->dumpFile($controllerPath, $manipulator->getSourceCode()); + } + + $generator->generateTemplate( + \sprintf('%s/login.html.twig', $templatePath), + 'security/formLogin/login_form.tpl.php', + [ + 'logout_setup' => $this->willLogout, + 'username_label' => Str::asHumanWords($this->userNameField), + 'username_is_email' => false !== stripos($this->userNameField, 'email'), + ] + ); + + $securityData = $this->securityConfigUpdater->updateForFormLogin($this->ysm->getContents(), $this->firewallToUpdate, 'app_login', 'app_login'); + + if ($this->willLogout) { + $securityData = $this->securityConfigUpdater->updateForLogout($securityData, $this->firewallToUpdate); + } + + if ($this->shouldGenerateTests()) { + $userClassNameDetails = $generator->createClassNameDetails( + '\\'.$this->userClass, + 'Entity\\' + ); + + $testClassDetails = $generator->createClassNameDetails( + 'LoginControllerTest', + 'Test\\', + ); + + $useStatements = new UseStatementGenerator([ + $userClassNameDetails->getFullName(), + KernelBrowser::class, + EntityManager::class, + WebTestCase::class, + UserPasswordHasherInterface::class, + ]); + + $generator->generateFile( + targetPath: \sprintf('tests/%s.php', $testClassDetails->getShortName()), + templateName: 'security/formLogin/Test.LoginController.tpl.php', + variables: [ + 'use_statements' => $useStatements, + 'user_class' => $this->userClass, + 'user_short_name' => $userClassNameDetails->getShortName(), + ], + ); + + if (!class_exists(WebTestCase::class)) { + $io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.'); + } + } + + $generator->dumpFile(self::SECURITY_CONFIG_PATH, $securityData); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + \sprintf('Next: Review and adapt the login template: %s/login.html.twig to suit your needs.', $templatePath), + ]); + } +} diff --git a/vendor/symfony/maker-bundle/src/MakerBundle.php b/vendor/symfony/maker-bundle/src/MakerBundle.php new file mode 100644 index 0000000..427e210 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/MakerBundle.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\MakeCommandRegistrationPass; +use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\RemoveMissingParametersPass; +use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\SetDoctrineAnnotatedPrefixesPass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +class MakerBundle extends Bundle +{ + public function build(ContainerBuilder $container): void + { + // add a priority so we run before the core command pass + $container->addCompilerPass(new MakeCommandRegistrationPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); + $container->addCompilerPass(new RemoveMissingParametersPass()); + $container->addCompilerPass(new SetDoctrineAnnotatedPrefixesPass()); + } +} diff --git a/vendor/symfony/maker-bundle/src/MakerInterface.php b/vendor/symfony/maker-bundle/src/MakerInterface.php new file mode 100644 index 0000000..52f70f2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/MakerInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; + +/** + * Interface that all maker commands must implement. + * + * @method static string getCommandDescription() + * + * @author Ryan Weaver + */ +interface MakerInterface +{ + /** + * Return the command name for your maker (e.g. make:report). + */ + public static function getCommandName(): string; + + /** + * Configure the command: set description, input arguments, options, etc. + * + * By default, all arguments will be asked interactively. If you want + * to avoid that, use the $inputConfig->setArgumentAsNonInteractive() method. + */ + public function configureCommand(Command $command, InputConfiguration $inputConfig); + + /** + * Configure any library dependencies that your maker requires. + */ + public function configureDependencies(DependencyBuilder $dependencies); + + /** + * If necessary, you can use this method to interactively ask the user for input. + */ + public function interact(InputInterface $input, ConsoleStyle $io, Command $command); + + /** + * Called after normal code generation: allows you to do anything. + */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator); +} diff --git a/vendor/symfony/maker-bundle/src/Renderer/FormTypeRenderer.php b/vendor/symfony/maker-bundle/src/Renderer/FormTypeRenderer.php new file mode 100644 index 0000000..4b98643 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Renderer/FormTypeRenderer.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Renderer; + +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @internal + */ +final class FormTypeRenderer +{ + public function __construct( + private Generator $generator, + ) { + } + + public function render(ClassNameDetails $formClassDetails, array $formFields, ?ClassNameDetails $boundClassDetails = null, array $constraintClasses = [], array $extraUseClasses = []): void + { + $fieldTypeUseStatements = []; + $fields = []; + foreach ($formFields as $name => $fieldTypeOptions) { + $fieldTypeOptions ??= ['type' => null, 'options_code' => null]; + + if (isset($fieldTypeOptions['type'])) { + $fieldTypeUseStatements[] = $fieldTypeOptions['type']; + $fieldTypeOptions['type'] = Str::getShortClassName($fieldTypeOptions['type']); + if (\array_key_exists('extra_use_classes', $fieldTypeOptions) && \count($fieldTypeOptions['extra_use_classes']) > 0) { + $extraUseClasses = array_merge($extraUseClasses, $fieldTypeOptions['extra_use_classes'] ?? []); + $fieldTypeOptions['options_code'] = str_replace( + $fieldTypeOptions['extra_use_classes'], + array_map(fn ($class) => Str::getShortClassName($class), $fieldTypeOptions['extra_use_classes']), + $fieldTypeOptions['options_code'] + ); + } + } + + $fields[$name] = $fieldTypeOptions; + } + + $useStatements = new UseStatementGenerator(array_unique(array_merge( + $fieldTypeUseStatements, + $extraUseClasses, + $constraintClasses + ))); + + $useStatements->addUseStatement([ + AbstractType::class, + FormBuilderInterface::class, + OptionsResolver::class, + ]); + + if ($boundClassDetails) { + $useStatements->addUseStatement($boundClassDetails->getFullName()); + } + + $this->generator->generateClass( + $formClassDetails->getFullName(), + 'form/Type.tpl.php', + [ + 'use_statements' => $useStatements, + 'bounded_class_name' => $boundClassDetails ? $boundClassDetails->getShortName() : null, + 'form_fields' => $fields, + ] + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/bin/php-cs-fixer-v3.49.0.phar b/vendor/symfony/maker-bundle/src/Resources/bin/php-cs-fixer-v3.49.0.phar new file mode 100755 index 0000000..cbbbe58 Binary files /dev/null and b/vendor/symfony/maker-bundle/src/Resources/bin/php-cs-fixer-v3.49.0.phar differ diff --git a/vendor/symfony/maker-bundle/src/Resources/config/makers.xml b/vendor/symfony/maker-bundle/src/Resources/config/makers.xml new file mode 100644 index 0000000..ad7d454 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/config/makers.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + + + + + + + + + + + + + + + + + + The "%service_id%" service is deprecated, use "maker.maker.make_test" instead. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The "%service_id%" service is deprecated, use "maker.maker.make_listener" instead. + + + + + + + + + + + + + The "%service_id%" service is deprecated, use "maker.maker.make_test" instead. + + + + + + + + + + + + + + + + + + + + + %kernel.project_dir% + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/maker-bundle/src/Resources/config/php-cs-fixer.config.php b/vendor/symfony/maker-bundle/src/Resources/config/php-cs-fixer.config.php new file mode 100644 index 0000000..9b5ee28 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/config/php-cs-fixer.config.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return (new PhpCsFixer\Config()) + ->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + 'native_function_invocation' => false, + 'blank_line_before_statement' => ['statements' => ['break', 'case', 'continue', 'declare', 'default', 'do', 'exit', 'for', 'foreach', 'goto', 'if', 'include', 'include_once', 'phpdoc', 'require', 'require_once', 'return', 'switch', 'throw', 'try', 'while', 'yield', 'yield_from']], + 'array_indentation' => true, + ]) + ->setRiskyAllowed(true) +; diff --git a/vendor/symfony/maker-bundle/src/Resources/config/services.xml b/vendor/symfony/maker-bundle/src/Resources/config/services.xml new file mode 100644 index 0000000..5fbc043 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/config/services.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + %kernel.project_dir% + %twig.default_path% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %env(default::string:MAKER_PHP_CS_FIXER_BINARY_PATH)% + %env(default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH)% + + + + + + + + + + + + + null + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/maker-bundle/src/Resources/doc/index.rst b/vendor/symfony/maker-bundle/src/Resources/doc/index.rst new file mode 100644 index 0000000..1d56c1e --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/doc/index.rst @@ -0,0 +1,163 @@ +The Symfony MakerBundle +======================= + +Symfony Maker helps you create empty commands, controllers, form classes, +tests and more so you can forget about writing boilerplate code. This bundle +assumes you're using a standard Symfony 6.4 directory structure, but many +commands can generate code into any application. + +Installation +------------ + +Run this command to install and enable this bundle in your application: + +.. code-block:: terminal + + $ composer require --dev symfony/maker-bundle + +Usage +----- + +This bundle provides several commands under the ``make:`` namespace. List them +all executing this command: + +.. code-block:: terminal + + $ php bin/console list make + + make:command Creates a new console command class + make:controller Creates a new controller class + make:entity Creates a new Doctrine entity class + + [...] + + make:validator Creates a new validator and constraint class + make:voter Creates a new security voter class + +The names of the commands are self-explanatory, but some of them include +optional arguments and options. Check them out with the ``--help`` option: + +.. code-block:: terminal + + $ php bin/console make:controller --help + + +Linting Generated Code +______________________ + +MakerBundle uses php-cs-fixer to enforce coding standards when generating ``.php`` +files. When running a ``make`` command, MakerBundle will use a ``php-cs-fixer`` +version and configuration that is packaged with this bundle. + +You can explicitly set a custom path to a php-cs-fixer binary and/or configuration +file by their respective environment variables: + +- ``MAKER_PHP_CS_FIXER_BINARY_PATH`` e.g. tools/vendor/bin/php-cs-fixer +- ``MAKER_PHP_CS_FIXER_CONFIG_PATH`` e.g. .php-cs-fixer.config.php + + +.. tip:: + + Is PHP-CS-Fixer installed globally? To avoid needing to set these in every + project, you can instead set these on your operating system. + + +Configuration +------------- + +This bundle doesn't require any configuration. But, you *can* override the default +configuration: + +.. code-block:: yaml + + # config/packages/maker.yaml + when@dev: + maker: + root_namespace: 'App' + generate_final_classes: true + generate_final_entities: false + +root_namespace +~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``App`` + +The root namespace used when generating all of your classes +(e.g. ``App\Entity\Article``, ``App\Command\MyCommand``, etc). Changing +this to ``Acme`` would cause MakerBundle to create new classes like +(e.g. ``Acme\Entity\Article``, ``Acme\Command\MyCommand``, etc). + +generate_final_classes +~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +By default, MakerBundle will generate all of your classes with the +``final`` PHP keyword except for doctrine entities. Set this to ``false`` +to override this behavior for all maker commands. + +See https://www.php.net/manual/en/language.oop5.final.php + +.. code-block:: php + + final class MyVoter + { + ... + } + +.. versionadded:: 1.61 + + ``generate_final_classes`` was introduced in MakerBundle 1.61 + + +generate_final_entities +~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +By default, MakerBundle will not generate any of your doctrine entity +classes with the ``final`` PHP keyword. Set this to ``true`` +to override this behavior for all maker commands that create +entities. + +See https://www.php.net/manual/en/language.oop5.final.php + +.. code-block:: php + + #[ORM\Entity(repositoryClass: TaskRepository::class)] + final class Task extends AbstractEntity + { + ... + } + +.. versionadded:: 1.61 + + ``generate_final_entities`` was introduced in MakerBundle 1.61. + +Creating your Own Makers +------------------------ + +In case your applications need to generate custom boilerplate code, you can +create your own ``make:...`` command reusing the tools provided by this bundle. +To do that, you should create a class that extends +`AbstractMaker`_ in your ``src/Maker/`` +directory. And this is really it! + +For examples of how to complete your new maker command, see the `core maker commands`_. +Make sure your class is registered as a service and tagged with ``maker.command``. +If you're using the standard Symfony ``services.yaml`` configuration, this +will be done automatically. + +Overriding the Generated Code +----------------------------- + +Generated code can never be perfect for everyone. The MakerBundle tries to balance +adding "extension points" with keeping the library simple so that existing commands +can be improved and new commands can be added. + +For that reason, in general, the generated code cannot be modified. In many cases, +adding your *own* maker command is so easy, that we recommend that. However, if there +is some extension point that you'd like, please open an issue so we can discuss! + +.. _`AbstractMaker`: https://github.com/symfony/maker-bundle/blob/main/src/Maker/AbstractMaker.php +.. _`core maker commands`: https://github.com/symfony/maker-bundle/tree/main/src/Maker diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeAuth.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeAuth.txt new file mode 100644 index 0000000..579be4e --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeAuth.txt @@ -0,0 +1,8 @@ +The %command.name% command generates various authentication systems, +by asking questions. + +It can provide an empty authenticator, or a full login form authentication process. +In both cases it also updates your security.yaml. +For the login form, it also generates a controller and the twig template. + +php %command.full_name% diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeCommand.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeCommand.txt new file mode 100644 index 0000000..a88b9d4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeCommand.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new command: + +php %command.full_name% app:do-something + +If the argument is missing, the command will ask for the command name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeController.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeController.txt new file mode 100644 index 0000000..532c4b9 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeController.txt @@ -0,0 +1,14 @@ +The %command.name% command generates a new controller class. + +php %command.full_name% CoolStuffController + +If the argument is missing, the command will ask for the controller class name interactively. + +If you have the symfony/twig-bundle installed, a Twig template will also be +generated for the controller. + +composer require symfony/twig-bundle + +You can also generate the controller alone, without template with this option: + +php %command.full_name% --no-template diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeCrud.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeCrud.txt new file mode 100644 index 0000000..9ec8d05 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeCrud.txt @@ -0,0 +1,5 @@ +The %command.name% command generates crud controller with templates for selected entity. + +php %command.full_name% BlogPost + +If the argument is missing, the command will ask for the entity class name interactively. \ No newline at end of file diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeDockerDatabase.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeDockerDatabase.txt new file mode 100644 index 0000000..426f339 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeDockerDatabase.txt @@ -0,0 +1,5 @@ +The %command.name% command generates or updates databases services in compose.yaml + +php %command.full_name% + +Supports MySQL, MariaDB and PostgreSQL diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeEntity.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeEntity.txt new file mode 100644 index 0000000..f48d3ec --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeEntity.txt @@ -0,0 +1,24 @@ +The %command.name% command creates or updates an entity and repository class. + +php %command.full_name% BlogPost + +If the argument is missing, the command will ask for the entity class name interactively. + +You can also mark this class as an API Platform resource. A hypermedia CRUD API will +automatically be available for this entity class: + +php %command.full_name% --api-resource + +Symfony can also broadcast all changes made to the entity to the client using Symfony +UX Turbo. + +php %command.full_name% --broadcast + +You can also generate all the getter/setter/adder/remover methods +for the properties of existing entities: + +php %command.full_name% --regenerate + +You can also *overwrite* any existing methods: + +php %command.full_name% --regenerate --overwrite diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeFixture.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeFixture.txt new file mode 100644 index 0000000..4a4067b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeFixture.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new Doctrine fixtures class. + +php %command.full_name% AppFixtures + +If the argument is missing, the command will ask for a class interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeForm.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeForm.txt new file mode 100644 index 0000000..86c98c6 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeForm.txt @@ -0,0 +1,16 @@ +The %command.name% command generates a new form class. + +php %command.full_name% UserType + +If the argument is missing, the command will ask for the form class interactively. + +You can optionally specify the bound class in a second argument. +This can be the name of an entity like User + +php %command.full_name% UserType User + +You can also specify a fully qualified name to another class like \App\Dto\UserData. +Slashes must be escaped in the argument. + +php %command.full_name% UserType \\App\\Dto\\UserData + diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeFunctionalTest.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeFunctionalTest.txt new file mode 100644 index 0000000..354008e --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeFunctionalTest.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new functional test class. + +php %command.full_name% DefaultControllerTest + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeListener.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeListener.txt new file mode 100644 index 0000000..e97c2a0 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeListener.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new event subscriber class or a new event listener class. + +php %command.full_name% ExceptionListener + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeMessage.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeMessage.txt new file mode 100644 index 0000000..c8fca3e --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeMessage.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new message class & handler. + +php %command.full_name% EmailMessage + +If the argument is missing, the command will ask for the message class interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeMiddleware.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeMiddleware.txt new file mode 100644 index 0000000..51f32db --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeMiddleware.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new Middleware class. + +php %command.full_name% CustomMiddleware + +If the argument is missing, the command will ask for the message class interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeMigration.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeMigration.txt new file mode 100644 index 0000000..cc27e55 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeMigration.txt @@ -0,0 +1,7 @@ +The %command.name% command generates a new migration: + +php %command.full_name% + +You can also generate a formatted migration with this option: + +php %command.full_name% --formatted diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeRegistrationForm.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeRegistrationForm.txt new file mode 100644 index 0000000..b8514fa --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeRegistrationForm.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a complete registration form, controller & template. + +php %command.full_name% + +The command will ask for several pieces of information to build your form. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeResetPassword.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeResetPassword.txt new file mode 100644 index 0000000..810eaa4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeResetPassword.txt @@ -0,0 +1,18 @@ +The %command.name% command generates all the files needed to implement + a fully-functional & secure password reset system. + +The SymfonycastsResetPasswordBundle is required and can be added using composer: +composer require symfonycasts/reset-password-bundle + +For more information on the reset-password-bundle check out: +https://github.com/symfonycasts/reset-password-bundle + +%command.name% requires a user entity with an email property, +email getter method, and a password setter method. Maker will ask for these +interactively if they cannot be guessed. + +Maker will also update your reset-password.yaml configuration file +if one exists. If you have customized the configuration file, maker will attempt +to modify it accordingly but preserve your customizations. + +php %command.full_name% diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeScheduler.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeScheduler.txt new file mode 100644 index 0000000..fe006d9 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeScheduler.txt @@ -0,0 +1,8 @@ +The %command.name% command generates a schedule to automate repeated +tasks using Symfony's Scheduler Component. + +If the Scheduler Component is not installed, %command.name% will +install it automatically using composer. You can of course do this manually by +running composer require symfony/scheduler. + +php %command.full_name% diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeSerializerEncoder.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeSerializerEncoder.txt new file mode 100644 index 0000000..d4df238 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeSerializerEncoder.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new serializer encoder class. + +php %command.full_name% YamlEncoder + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeSerializerNormalizer.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeSerializerNormalizer.txt new file mode 100644 index 0000000..ef960c5 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeSerializerNormalizer.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new serializer normalizer class. + +php %command.full_name% UserNormalizer + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeStimulusController.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeStimulusController.txt new file mode 100644 index 0000000..0785f97 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeStimulusController.txt @@ -0,0 +1,5 @@ +The %command.name% command generates new Stimulus Controller. + +php %command.full_name% hello + +If the argument is missing, the command will ask for the controller name interactively. \ No newline at end of file diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeSubscriber.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeSubscriber.txt new file mode 100644 index 0000000..072de01 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeSubscriber.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new event subscriber class. + +php %command.full_name% ExceptionSubscriber + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeTest.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeTest.txt new file mode 100644 index 0000000..9f5859c --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeTest.txt @@ -0,0 +1,7 @@ +The %command.name% command generates a new test class. + +php %command.full_name% TestCase BlogPostTest + +If the first argument is missing, the command will ask for the test type interactively. + +If the second argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeTwigExtension.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeTwigExtension.txt new file mode 100644 index 0000000..6055152 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeTwigExtension.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new twig extension with its runtime class. + +php %command.full_name% AppExtension + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeUnitTest.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeUnitTest.txt new file mode 100644 index 0000000..0f42ec8 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeUnitTest.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new unit test class. + +php %command.full_name% UtilTest + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeUser.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeUser.txt new file mode 100644 index 0000000..d1f2b35 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeUser.txt @@ -0,0 +1,7 @@ +The %command.name% command generates a new user class for security +and updates your security.yaml file for it. It will also generate a user provider +class if your situation needs a custom class. + +php %command.full_name% User + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeValidator.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeValidator.txt new file mode 100644 index 0000000..64a7be7 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeValidator.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new validation constraint. + +php %command.full_name% EnabledValidator + +If the argument is missing, the command will ask for the constraint class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeVoter.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeVoter.txt new file mode 100644 index 0000000..71d10ac --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeVoter.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new security voter. + +php %command.full_name% BlogPostVoter + +If the argument is missing, the command will ask for the class name interactively. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/MakeWebhook.txt b/vendor/symfony/maker-bundle/src/Resources/help/MakeWebhook.txt new file mode 100644 index 0000000..52096f2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/MakeWebhook.txt @@ -0,0 +1,8 @@ +The %command.name% command creates a RequestParser, a WebhookHandler and adds the necessary configuration +for a new Webhook. + +php %command.full_name% stripe + +If the argument is missing, the command will ask for the webhook name interactively. + +It will also interactively ask for the RequestMatchers to use for the RequestParser's getRequestMatcher function. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/_WithTests.txt b/vendor/symfony/maker-bundle/src/Resources/help/_WithTests.txt new file mode 100644 index 0000000..7430a64 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/_WithTests.txt @@ -0,0 +1,6 @@ +To generate tailored PHPUnit tests, simply call: + +php %command.full_name% --with-tests + +This will generate a unit test in tests/ for you to review then use +to test the new functionality of your app. diff --git a/vendor/symfony/maker-bundle/src/Resources/help/_WithUid.txt b/vendor/symfony/maker-bundle/src/Resources/help/_WithUid.txt new file mode 100644 index 0000000..c980a72 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/_WithUid.txt @@ -0,0 +1,10 @@ +Instead of using the default "int" type for the entity's "id", you can use the +UUID type from Symfony's Uid component. +https://symfony.com/doc/current/components/uid.html#storing-uuids-in-databases + +php %command.full_name% --with-uuid + +Or you can use the ULID type from Symfony's Uid component. +https://symfony.com/doc/current/components/uid.html#storing-ulids-in-databases + +php %command.full_name% --with-ulid diff --git a/vendor/symfony/maker-bundle/src/Resources/help/security/MakeCustom.txt b/vendor/symfony/maker-bundle/src/Resources/help/security/MakeCustom.txt new file mode 100644 index 0000000..2c396e6 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/security/MakeCustom.txt @@ -0,0 +1,8 @@ +The %command.name% command generates a simple custom authenticator +class based off the example provided in: + +https://symfony.com/doc/current/security/custom_authenticator.html + +This will also update your security.yaml for the new custom authenticator. + +php %command.full_name% diff --git a/vendor/symfony/maker-bundle/src/Resources/help/security/MakeFormLogin.txt b/vendor/symfony/maker-bundle/src/Resources/help/security/MakeFormLogin.txt new file mode 100644 index 0000000..08ce359 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/help/security/MakeFormLogin.txt @@ -0,0 +1,9 @@ +The %command.name% command generates a controller and twig template +to allow users to login using the form_login authenticator. + +The controller name, and logout ability can be customized by answering the +questions asked when running %command.name%. + +This will also update your security.yaml for the new authenticator. + +php %command.full_name% diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/Class.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/Class.tpl.php new file mode 100644 index 0000000..7aff75a --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/Class.tpl.php @@ -0,0 +1,7 @@ + + +namespace ; + +class +{ +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/EmptyAuthenticator.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/EmptyAuthenticator.tpl.php new file mode 100644 index 0000000..bfa1e7f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/EmptyAuthenticator.tpl.php @@ -0,0 +1,39 @@ + + +namespace ; + + + +class extends AbstractAuthenticator +{ + public function supports(Request $request): ?bool + { + // TODO: Implement supports() method. + } + + public function authenticate(Request $request): Passport + { + // TODO: Implement authenticate() method. + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + // TODO: Implement onAuthenticationSuccess() method. + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + // TODO: Implement onAuthenticationFailure() method. + } + +// public function start(Request $request, AuthenticationException $authException = null): Response +// { +// /* +// * If you would like this class to control what happens when an anonymous user accesses a +// * protected page (e.g. redirect to /login), uncomment this method and make this class +// * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface. +// * +// * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point +// */ +// } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/EmptySecurityController.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/EmptySecurityController.tpl.php new file mode 100644 index 0000000..1813dde --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/EmptySecurityController.tpl.php @@ -0,0 +1,9 @@ + + +namespace ; + + + +class extends AbstractController +{ +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/LoginFormAuthenticator.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/LoginFormAuthenticator.tpl.php new file mode 100644 index 0000000..cafeaf4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/LoginFormAuthenticator.tpl.php @@ -0,0 +1,48 @@ + + +namespace ; + + + +class extends AbstractLoginFormAuthenticator +{ + use TargetPathTrait; + + public const LOGIN_ROUTE = 'app_login'; + + public function __construct(private UrlGeneratorInterface $urlGenerator) + { + } + + public function authenticate(Request $request): Passport + { + $ = $request->getPayload()->getString(''); + + $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $); + + return new Passport( + new UserBadge($), + new PasswordCredentials($request->getPayload()->getString('password')), + [ + new CsrfTokenBadge('authenticate', $request->getPayload()->getString('_csrf_token')), + ] + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { + return new RedirectResponse($targetPath); + } + + // For example: + // return new RedirectResponse($this->urlGenerator->generate('some_route')); + throw new \Exception('TODO: provide a valid redirect inside '.__FILE__); + } + + protected function getLoginUrl(Request $request): string + { + return $this->urlGenerator->generate(self::LOGIN_ROUTE); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/login_form.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/login_form.tpl.php new file mode 100644 index 0000000..2fdef97 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/authenticator/login_form.tpl.php @@ -0,0 +1,41 @@ +{% extends 'base.html.twig' %} + +{% block title %}Log in!{% endblock %} + +{% block body %} +
    + {% if error %} +
    {{ error.messageKey|trans(error.messageData, 'security') }}
    + {% endif %} + + + {% if app.user %} +
    + You are logged in as {{ app.user.userIdentifier }}, Logout +
    + {% endif %} + + +

    Please sign in

    + + + + + + + + +
    + +
    + + + +
    +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/command/Command.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/command/Command.tpl.php new file mode 100644 index 0000000..3050e1b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/command/Command.tpl.php @@ -0,0 +1,44 @@ + + +namespace ; + + + +#[AsCommand( + name: '', + description: 'Add a short description for your command', +)] +class extends Command +{ + public function __construct() + { + parent::__construct(); + } + + protected function configure(): void + { + $this +setDescription(self::\$defaultDescription)\n" : '' ?> + ->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description') + ->addOption('option1', null, InputOption::VALUE_NONE, 'Option description') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $arg1 = $input->getArgument('arg1'); + + if ($arg1) { + $io->note(sprintf('You passed an argument: %s', $arg1)); + } + + if ($input->getOption('option1')) { + // ... + } + + $io->success('You have a new command! Now make it your own! Pass --help to see your options.'); + + return Command::SUCCESS; + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/controller/Controller.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/controller/Controller.tpl.php new file mode 100644 index 0000000..8f509e6 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/controller/Controller.tpl.php @@ -0,0 +1,24 @@ + + +namespace ; + + + +class extends AbstractController +{ +generateRouteForControllerMethod($route_path, $route_name); ?> + public function (): ResponseJsonResponse + + { + + return $this->render('', [ + 'controller_name' => '', + ]); + + return $this->json([ + 'message' => 'Welcome to your new controller!', + 'path' => '', + ]); + + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/controller/twig_template.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/controller/twig_template.tpl.php new file mode 100644 index 0000000..e06dfc7 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/controller/twig_template.tpl.php @@ -0,0 +1,18 @@ +getHeadPrintCode("Hello $class_name!"); ?> + +{% block body %} + + +
    +

    Hello {{ controller_name }}! ✅

    + + This friendly message is coming from: +
      +
    • Your controller at /
    • +
    • Your template at /
    • +
    +
    +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/controller/Controller.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/controller/Controller.tpl.php new file mode 100644 index 0000000..0a4a84d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/controller/Controller.tpl.php @@ -0,0 +1,87 @@ + + +namespace getNamespace() ?>; + +getUseStatements(); ?> + +#[Route('')] +getClassDeclaration() ?> +{ +generateRouteForControllerMethod('', sprintf('%s_index', $route_name), ['GET']) ?> + + public function index( $): Response + { + return $this->render('/index.html.twig', [ + '' => $->findAll(), + ]); + } + + public function index(EntityManagerInterface $entityManager): Response + { + $ = $entityManager + ->getRepository(::class) + ->findAll(); + + return $this->render('/index.html.twig', [ + '' => $, + ]); + } + + +generateRouteForControllerMethod('/new', sprintf('%s_new', $route_name), ['GET', 'POST']) ?> + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $ = new (); + $form = $this->createForm(::class, $); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($); + $entityManager->flush(); + + return $this->redirectToRoute('_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('/new.html.twig', [ + '' => $, + 'form' => $form, + ]); + } + +generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_show', $route_name), ['GET']) ?> + public function show( $): Response + { + return $this->render('/show.html.twig', [ + '' => $, + ]); + } + +generateRouteForControllerMethod(sprintf('/{%s}/edit', $entity_identifier), sprintf('%s_edit', $route_name), ['GET', 'POST']) ?> + public function edit(Request $request, $, EntityManagerInterface $entityManager): Response + { + $form = $this->createForm(::class, $); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->flush(); + + return $this->redirectToRoute('_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('/edit.html.twig', [ + '' => $, + 'form' => $form, + ]); + } + +generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_delete', $route_name), ['POST']) ?> + public function delete(Request $request, $, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$->get(), $request->getPayload()->getString('_token'))) { + $entityManager->remove($); + $entityManager->flush(); + } + + return $this->redirectToRoute('_index', [], Response::HTTP_SEE_OTHER); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/_delete_form.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/_delete_form.tpl.php new file mode 100644 index 0000000..e5eef50 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/_delete_form.tpl.php @@ -0,0 +1,4 @@ +
    + + +
    diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/_form.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/_form.tpl.php new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/_form.tpl.php @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/edit.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/edit.tpl.php new file mode 100644 index 0000000..7101cbd --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/edit.tpl.php @@ -0,0 +1,11 @@ +getHeadPrintCode('Edit '.$entity_class_name) ?> + +{% block body %} +

    Edit

    + + {{ include('/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('/_delete_form.html.twig') }} +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/index.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/index.tpl.php new file mode 100644 index 0000000..d7c1d47 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/index.tpl.php @@ -0,0 +1,35 @@ +getHeadPrintCode($entity_class_name.' index'); ?> + +{% block body %} +

    index

    + + + + + + + + + + + + {% for in %} + + + + + + + {% else %} + + + + {% endfor %} + +
    actions
    {{ getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }} + show + edit +
    no records found
    + + Create new +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/new.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/new.tpl.php new file mode 100644 index 0000000..d59320b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/new.tpl.php @@ -0,0 +1,9 @@ +getHeadPrintCode('New '.$entity_class_name) ?> + +{% block body %} +

    Create new

    + + {{ include('/_form.html.twig') }} + + back to list +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/show.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/show.tpl.php new file mode 100644 index 0000000..8f439b4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/templates/show.tpl.php @@ -0,0 +1,22 @@ +getHeadPrintCode($entity_class_name) ?> + +{% block body %} +

    + + + + + + + + + + +
    {{ getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}
    + + back to list + + edit + + {{ include('/_delete_form.html.twig') }} +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/test/Test.EntityManager.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/test/Test.EntityManager.tpl.php new file mode 100644 index 0000000..36003d7 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/crud/test/Test.EntityManager.tpl.php @@ -0,0 +1,122 @@ + + + +namespace ; + +getUseStatements(); ?> + +getClassDeclaration() ?> +{ + private KernelBrowser $client; + private EntityManagerInterface $manager; + private EntityRepository $repository; + private string $path = '/'; + + protected function setUp(): void + { + $this->client = static::createClient(); + $this->manager = static::getContainer()->get('doctrine')->getManager(); + $this->repository = $this->manager->getRepository(::class); + + foreach ($this->repository->findAll() as $object) { + $this->manager->remove($object); + } + + $this->manager->flush(); + } + + public function testIndex(): void + { + $this->client->followRedirects(); + $crawler = $this->client->request('GET', $this->path); + + self::assertResponseStatusCodeSame(200); + self::assertPageTitleContains(' index'); + + // Use the $crawler to perform additional assertions e.g. + // self::assertSame('Some text on the page', $crawler->filter('.p')->first()); + } + + public function testNew(): void + { + $this->markTestIncomplete(); + $this->client->request('GET', sprintf('%snew', $this->path)); + + self::assertResponseStatusCodeSame(200); + + $this->client->submitForm('Save', [ + $typeOptions): ?> + '[]' => 'Testing', + + ]); + + self::assertResponseRedirects($this->path); + + self::assertSame(1, $this->repository->count([])); + } + + public function testShow(): void + { + $this->markTestIncomplete(); + $fixture = new (); + $typeOptions): ?> + $fixture->set('My Title'); + + + $this->manager->persist($fixture); + $this->manager->flush(); + + $this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId())); + + self::assertResponseStatusCodeSame(200); + self::assertPageTitleContains(''); + + // Use assertions to check that the properties are properly displayed. + } + + public function testEdit(): void + { + $this->markTestIncomplete(); + $fixture = new (); + $typeOptions): ?> + $fixture->set('Value'); + + + $this->manager->persist($fixture); + $this->manager->flush(); + + $this->client->request('GET', sprintf('%s%s/edit', $this->path, $fixture->getId())); + + $this->client->submitForm('Update', [ + $typeOptions): ?> + '[]' => 'Something New', + + ]); + + self::assertResponseRedirects('/'); + + $fixture = $this->repository->findAll(); + + $typeOptions): ?> + self::assertSame('Something New', $fixture[0]->get()); + + } + + public function testRemove(): void + { + $this->markTestIncomplete(); + $fixture = new (); + $typeOptions): ?> + $fixture->set('Value'); + + + $this->manager->persist($fixture); + $this->manager->flush(); + + $this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId())); + $this->client->submitForm('Delete'); + + self::assertResponseRedirects('/'); + self::assertSame(0, $this->repository->count([])); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Entity.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Entity.tpl.php new file mode 100644 index 0000000..00d08aa --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Entity.tpl.php @@ -0,0 +1,56 @@ + + + +namespace ; + + + +#[ORM\Entity(repositoryClass: ::class)] +#[ORM\Table(name: '``')] + + +#[ApiResource] + + +#[Broadcast] + +class +{ + + #[ORM\Id] + #[ORM\Column(type: UuidType::NAME, unique: true)] + #[ORM\GeneratedValue(strategy: 'CUSTOM')] + #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] + private ?Uuid $id = null; + + public function getId(): ?Uuid + { + return $this->id; + } + + #[ORM\Id] + #[ORM\Column(type: UlidType::NAME, unique: true)] + #[ORM\GeneratedValue(strategy: 'CUSTOM')] + #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')] + private ?Ulid $id = null; + + public function getId(): ?Ulid + { + return $this->id; + } + + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + public function getId(): ?int + { + return $this->id; + } + +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Fixtures.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Fixtures.tpl.php new file mode 100644 index 0000000..bdbb795 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Fixtures.tpl.php @@ -0,0 +1,16 @@ + + +namespace ; + + + +class extends Fixture +{ + public function load(ObjectManager $manager): void + { + // $product = new Product(); + // $manager->persist($product); + + $manager->flush(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Repository.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Repository.tpl.php new file mode 100644 index 0000000..8f603dc --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/Repository.tpl.php @@ -0,0 +1,61 @@ + + +namespace ; + + + +/** + * @extends ServiceEntityRepository<> + */ +class extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ::class); + } + + + + + /** + * Used to upgrade (rehash) the user's password automatically over time. + */ + public function upgradePassword(getShortName()); ?>$user, string $newHashedPassword): void + { + if (!$user instanceof ) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $user->setPassword($newHashedPassword); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } + + + +// /** +// * @return [] Returns an array of objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('') +// ->andWhere('.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ? +// { +// return $this->createQueryBuilder('') +// ->andWhere('.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } + +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/broadcast_twig_template.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/broadcast_twig_template.tpl.php new file mode 100644 index 0000000..0f09a51 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/doctrine/broadcast_twig_template.tpl.php @@ -0,0 +1,22 @@ +{# Learn how to use Turbo Streams: https://github.com/symfony/ux-turbo#broadcast-doctrine-entities-update #} +{% block create %} + + + +{% endblock %} + +{% block update %} + + + +{% endblock %} + +{% block remove %} + +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/event/Listener.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/event/Listener.tpl.php new file mode 100644 index 0000000..baa50de --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/event/Listener.tpl.php @@ -0,0 +1,14 @@ + + +namespace ; + + + +final class +{ + #[AsEventListener(event: )] + public function (): void + { + // ... + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/event/Subscriber.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/event/Subscriber.tpl.php new file mode 100644 index 0000000..9fe05f9 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/event/Subscriber.tpl.php @@ -0,0 +1,20 @@ + + +namespace ; + + + +class implements EventSubscriberInterface +{ + public function (): void + { + // ... + } + + public static function getSubscribedEvents(): array + { + return [ + => '', + ]; + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/form/Type.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/form/Type.tpl.php new file mode 100644 index 0000000..28a1963 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/form/Type.tpl.php @@ -0,0 +1,36 @@ + + +namespace ; + + + +class extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + $typeOptions): ?> + + ->add('') + + ->add('', ::class) + + ->add('', , [ + + ]) + + + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + + 'data_class' => ::class, + + // Configure your form options here + + ]); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/message/Message.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/message/Message.tpl.php new file mode 100644 index 0000000..358acc2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/message/Message.tpl.php @@ -0,0 +1,16 @@ + + +namespace ; + +final class +{ + /* + * Add whatever properties and methods you need + * to hold the data for this message class. + */ + + // public function __construct( + // public readonly string $name, + // ) { + // } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/message/MessageHandler.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/message/MessageHandler.tpl.php new file mode 100644 index 0000000..1c55c72 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/message/MessageHandler.tpl.php @@ -0,0 +1,14 @@ + + +namespace ; + + + +#[AsMessageHandler] +final class +{ + public function __invoke( $message): void + { + // do something with your message + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/middleware/Middleware.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/middleware/Middleware.tpl.php new file mode 100644 index 0000000..10062e1 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/middleware/Middleware.tpl.php @@ -0,0 +1,14 @@ + + +namespace ; + + + +final class implements MiddlewareInterface +{ + public function handle(Envelope $envelope, StackInterface $stack): Envelope + { + // ... + return $stack->next()->handle($envelope, $stack); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/RegistrationController.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/RegistrationController.tpl.php new file mode 100644 index 0000000..ad37079 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/RegistrationController.tpl.php @@ -0,0 +1,102 @@ + + +namespace ; + + + +class extends AbstractController +{ + + public function __construct(private getPropertyType($email_verifier_class_details) ?>$emailVerifier) + { + } + + +generateRouteForControllerMethod($route_path, $route_name) ?> + public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response + { + $user = new (); + $form = $this->createForm(::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var string $plainPassword */ + $plainPassword = $form->get('plainPassword')->getData(); + + // encode the plain password + $user->set($userPasswordHasher->hashPassword($user, $plainPassword)); + + $entityManager->persist($user); + $entityManager->flush(); + + + // generate a signed url and email it to the user + $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, + (new TemplatedEmail()) + ->from(new Address('', '')) + ->to((string) $user->()) + ->subject('Please Confirm your Email') + ->htmlTemplate('registration/confirmation_email.html.twig') + ); + + + // do anything else you need here, like send an email + + + return $security->login($user, , ''); + + return $this->redirectToRoute(''); + + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form, + ]); + } + + +generateRouteForControllerMethod('/verify/email', 'app_verify_email') ?> + public function verifyUserEmail(Request $request, TranslatorInterface $translator): Response + { + + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); + + $id = $request->query->get('id'); + + if (null === $id) { + return $this->redirectToRoute('app_register'); + } + + + $repository = $manager->getRepository(::class); + $user = $repository->find($id); + + + $user = ->find($id); + + + if (null === $user) { + return $this->redirectToRoute('app_register'); + } + + + // validate email confirmation link, sets User::isVerified=true and persists + try { + + /** @var $user */ + $user = $this->getUser(); + + $this->emailVerifier->handleEmailConfirmation($request, $user); + } catch (VerifyEmailExceptionInterface $exception) { + $this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle')$exception->getReason()); + + return $this->redirectToRoute(''); + } + + // @TODO Change the redirect on success and handle or remove the flash message in your templates + $this->addFlash('success', 'Your email address has been verified.'); + + return $this->redirectToRoute('app_register'); + } + +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/Test.WithVerify.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/Test.WithVerify.tpl.php new file mode 100644 index 0000000..1b911c4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/Test.WithVerify.tpl.php @@ -0,0 +1,75 @@ + +namespace App\Tests; + + + +class RegistrationControllerTest extends WebTestCase +{ + private KernelBrowser $client; + private $userRepository; + + protected function setUp(): void + { + $this->client = static::createClient(); + + // Ensure we have a clean database + $container = static::getContainer(); + + /** @var EntityManager $em */ + $em = $container->get('doctrine')->getManager(); + $this->userRepository = $container->get(::class); + + foreach ($this->userRepository->findAll() as $user) { + $em->remove($user); + } + + $em->flush(); + } + + public function testRegister(): void + { + // Register a new user + $this->client->request('GET', '/register'); + self::assertResponseIsSuccessful(); + self::assertPageTitleContains('Register'); + + $this->client->submitForm('Register', [ + 'registration_form[email]' => 'me@example.com', + 'registration_form[plainPassword]' => 'password', + 'registration_form[agreeTerms]' => true, + ]); + + // Ensure the response redirects after submitting the form, the user exists, and is not verified + // self::assertResponseRedirects('/'); @TODO: set the appropriate path that the user is redirected to. + self::assertCount(1, $this->userRepository->findAll()); + self::assertFalse(($user = $this->userRepository->findAll()[0])->isVerified()); + + // Ensure the verification email was sent + // Use either assertQueuedEmailCount() || assertEmailCount() depending on your mailer setup + // self::assertQueuedEmailCount(1); + self::assertEmailCount(1); + + self::assertCount(1, $messages = $this->getMailerMessages()); + self::assertEmailAddressContains($messages[0], 'from', ''); + self::assertEmailAddressContains($messages[0], 'to', 'me@example.com'); + self::assertEmailTextBodyContains($messages[0], 'This link will expire in 1 hour.'); + + // Login the new user + $this->client->followRedirect(); + $this->client->loginUser($user); + + // Get the verification link from the email + /** @var TemplatedEmail $templatedEmail */ + $templatedEmail = $messages[0]; + $messageBody = $templatedEmail->getHtmlBody(); + self::assertIsString($messageBody); + + preg_match('#(http://localhost/verify/email.+)">#', $messageBody, $resetLink); + + // "Click" the link and see if the user is verified + $this->client->request('GET', $resetLink[1]); + $this->client->followRedirect(); + + self::assertTrue(static::getContainer()->get(::class)->findAll()[0]->isVerified()); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/Test.WithoutVerify.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/Test.WithoutVerify.tpl.php new file mode 100644 index 0000000..9b94778 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/Test.WithoutVerify.tpl.php @@ -0,0 +1,46 @@ + +namespace App\Tests; + + + +class RegistrationControllerTest extends WebTestCase +{ + private KernelBrowser $client; + private $userRepository; + + protected function setUp(): void + { + $this->client = static::createClient(); + + // Ensure we have a clean database + $container = static::getContainer(); + + /** @var EntityManager $em */ + $em = $container->get('doctrine')->getManager(); + $this->userRepository = $container->get(::class); + + foreach ($this->userRepository->findAll() as $user) { + $em->remove($user); + } + + $em->flush(); + } + + public function testRegister(): void + { + // Register a new user + $this->client->request('GET', '/register'); + self::assertResponseIsSuccessful(); + self::assertPageTitleContains('Register'); + + $this->client->submitForm('Register', [ + 'registration_form[email]' => 'me@example.com', + 'registration_form[plainPassword]' => 'password', + 'registration_form[agreeTerms]' => true, + ]); + + // Ensure the response redirects after submitting the form, the user exists, and is not verified + // self::assertResponseRedirects('/'); @TODO: set the appropriate path that the user is redirected to. + self::assertCount(1, $this->userRepository->findAll()); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/twig_email.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/twig_email.tpl.php new file mode 100644 index 0000000..7c79d8a --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/twig_email.tpl.php @@ -0,0 +1,11 @@ +

    Hi! Please confirm your email!

    + +

    + Please confirm your email address by clicking the following link:

    + Confirm my Email. + This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}. +

    + +

    + Cheers! +

    diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/twig_template.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/twig_template.tpl.php new file mode 100644 index 0000000..1e2cc60 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/registration/twig_template.tpl.php @@ -0,0 +1,23 @@ +getHeadPrintCode('Register'); ?> + +{% block body %} + + {% for flash_error in app.flashes('verify_email_error') %} + + {% endfor %} + + +

    Register

    + + {{ form_errors(registrationForm) }} + + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.) }} + {{ form_row(registrationForm.plainPassword, { + label: 'Password' + }) }} + {{ form_row(registrationForm.agreeTerms) }} + + + {{ form_end(registrationForm) }} +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ChangePasswordFormType.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ChangePasswordFormType.tpl.php new file mode 100644 index 0000000..95eb741 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ChangePasswordFormType.tpl.php @@ -0,0 +1,50 @@ + + +namespace ; + + + +class extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('plainPassword', RepeatedType::class, [ + 'type' => PasswordType::class, + 'options' => [ + 'attr' => [ + 'autocomplete' => 'new-password', + ], + ], + 'first_options' => [ + 'constraints' => [ + new NotBlank([ + 'message' => 'Please enter a password', + ]), + new Length([ + 'min' => 12, + 'minMessage' => 'Your password should be at least {{ limit }} characters', + // max length allowed by Symfony for security reasons + 'max' => 4096, + ]), + new PasswordStrength(), + new NotCompromisedPassword(), + ], + 'label' => 'New password', + ], + 'second_options' => [ + 'label' => 'Repeat Password', + ], + 'invalid_message' => 'The password fields must match.', + // Instead of being set onto the object directly, + // this is read and encoded in the controller + 'mapped' => false, + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([]); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ResetPasswordController.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ResetPasswordController.tpl.php new file mode 100644 index 0000000..7ce8c55 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ResetPasswordController.tpl.php @@ -0,0 +1,158 @@ + + +namespace ; + + + +#[Route('/reset-password')] +class extends AbstractController +{ + use ResetPasswordControllerTrait; + + public function __construct( + private ResetPasswordHelperInterface $resetPasswordHelper, + private EntityManagerInterface $entityManager + ) { + } + + /** + * Display & process form to request a password reset. + */ + #[Route('', name: 'app_forgot_password_request')] + public function request(Request $request, MailerInterface $mailer, TranslatorInterface $translator): Response + { + $form = $this->createForm(::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var string $email */ + $email = $form->get('')->getData(); + + return $this->processSendingPasswordResetEmail($email, $mailer, $translator); + } + + return $this->render('reset_password/request.html.twig', [ + 'requestForm' => $form, + ]); + } + + /** + * Confirmation page after a user has requested a password reset. + */ + #[Route('/check-email', name: 'app_check_email')] + public function checkEmail(): Response + { + // Generate a fake token if the user does not exist or someone hit this page directly. + // This prevents exposing whether or not a user was found with the given email address or not + if (null === ($resetToken = $this->getTokenObjectFromSession())) { + $resetToken = $this->resetPasswordHelper->generateFakeResetToken(); + } + + return $this->render('reset_password/check_email.html.twig', [ + 'resetToken' => $resetToken, + ]); + } + + /** + * Validates and process the reset URL that the user clicked in their email. + */ + #[Route('/reset/{token}', name: 'app_reset_password')] + public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response + { + if ($token) { + // We store the token in session and remove it from the URL, to avoid the URL being + // loaded in a browser and potentially leaking the token to 3rd party JavaScript. + $this->storeTokenInSession($token); + + return $this->redirectToRoute('app_reset_password'); + } + + $token = $this->getTokenFromSession(); + if (null === $token) { + throw $this->createNotFoundException('No reset password token found in the URL or in the session.'); + } + + try { + /** @var $user */ + $user = $this->resetPasswordHelper->validateTokenAndFetchUser($token); + } catch (ResetPasswordExceptionInterface $e) { + $this->addFlash('reset_password_error', sprintf( + '%s - %s', + $translator->trans(, [], 'ResetPasswordBundle'), + $translator->trans($e->getReason(), [], 'ResetPasswordBundle')$e->getReason() + )); + + return $this->redirectToRoute('app_forgot_password_request'); + } + + // The token is valid; allow the user to change their password. + $form = $this->createForm(::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // A password reset token should be used only once, remove it. + $this->resetPasswordHelper->removeResetRequest($token); + + /** @var string $plainPassword */ + $plainPassword = $form->get('plainPassword')->getData(); + + // Encode(hash) the plain password, and set it. + $user->($passwordHasher->hashPassword($user, $plainPassword)); + $this->entityManager->flush(); + + // The session is cleaned up after the password has been changed. + $this->cleanSessionAfterReset(); + + return $this->redirectToRoute(''); + } + + return $this->render('reset_password/reset.html.twig', [ + 'resetForm' => $form, + ]); + } + + private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer, TranslatorInterface $translator): RedirectResponse + { + $user = $this->entityManager->getRepository(::class)->findOneBy([ + '' => $emailFormData, + ]); + + // Do not reveal whether a user account was found or not. + if (!$user) { + return $this->redirectToRoute('app_check_email'); + } + + try { + $resetToken = $this->resetPasswordHelper->generateResetToken($user); + } catch (ResetPasswordExceptionInterface $e) { + // If you want to tell the user why a reset email was not sent, uncomment + // the lines below and change the redirect to 'app_forgot_password_request'. + // Caution: This may reveal if a user is registered or not. + // + // $this->addFlash('reset_password_error', sprintf( + // '%s - %s', + // $translator->trans(, [], 'ResetPasswordBundle'), + // $translator->trans($e->getReason(), [], 'ResetPasswordBundle')$e->getReason() + // )); + + return $this->redirectToRoute('app_check_email'); + } + + $email = (new TemplatedEmail()) + ->from(new Address('', '')) + ->to((string) $user->()) + ->subject('Your password reset request') + ->htmlTemplate('reset_password/email.html.twig') + ->context([ + 'resetToken' => $resetToken, + ]) + ; + + $mailer->send($email); + + // Store the token object in session for retrieval in check-email route. + $this->setTokenObjectInSession($resetToken); + + return $this->redirectToRoute('app_check_email'); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ResetPasswordRequestFormType.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ResetPasswordRequestFormType.tpl.php new file mode 100644 index 0000000..fdb8b93 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/ResetPasswordRequestFormType.tpl.php @@ -0,0 +1,27 @@ + + +namespace ; + + + +class extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('', EmailType::class, [ + 'attr' => ['autocomplete' => 'email'], + 'constraints' => [ + new NotBlank([ + 'message' => 'Please enter your email', + ]), + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([]); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/Test.ResetPasswordController.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/Test.ResetPasswordController.tpl.php new file mode 100644 index 0000000..16a28b3 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/Test.ResetPasswordController.tpl.php @@ -0,0 +1,98 @@ + +namespace App\Tests; + + + +class ResetPasswordControllerTest extends WebTestCase +{ + private KernelBrowser $client; + private EntityManagerInterface $em; + private $userRepository; + + protected function setUp(): void + { + $this->client = static::createClient(); + + // Ensure we have a clean database + $container = static::getContainer(); + + /** @var EntityManagerInterface $em */ + $em = $container->get('doctrine')->getManager(); + $this->em = $em; + + $this->userRepository = $container->get(::class); + + foreach ($this->userRepository->findAll() as $user) { + $this->em->remove($user); + } + + $this->em->flush(); + } + + public function testResetPasswordController(): void + { + // Create a test user + $user = (new ()) + ->setEmail('me@example.com') + ->setPassword('a-test-password-that-will-be-changed-later') + ; + $this->em->persist($user); + $this->em->flush(); + + // Test Request reset password page + $this->client->request('GET', '/reset-password'); + + self::assertResponseIsSuccessful(); + self::assertPageTitleContains('Reset your password'); + + // Submit the reset password form and test email message is queued / sent + $this->client->submitForm('Send password reset email', [ + 'reset_password_request_form[email]' => 'me@example.com', + ]); + + // Ensure the reset password email was sent + // Use either assertQueuedEmailCount() || assertEmailCount() depending on your mailer setup + // self::assertQueuedEmailCount(1); + self::assertEmailCount(1); + + self::assertCount(1, $messages = $this->getMailerMessages()); + + self::assertEmailAddressContains($messages[0], 'from', ''); + self::assertEmailAddressContains($messages[0], 'to', 'me@example.com'); + self::assertEmailTextBodyContains($messages[0], 'This link will expire in 1 hour.'); + + self::assertResponseRedirects('/reset-password/check-email'); + + // Test check email landing page shows correct "expires at" time + $crawler = $this->client->followRedirect(); + + self::assertPageTitleContains('Password Reset Email Sent'); + self::assertStringContainsString('This link will expire in 1 hour', $crawler->html()); + + // Test the link sent in the email is valid + $email = $messages[0]->toString(); + preg_match('#(/reset-password/reset/[a-zA-Z0-9]+)#', $email, $resetLink); + + $this->client->request('GET', $resetLink[1]); + + self::assertResponseRedirects('/reset-password/reset'); + + $this->client->followRedirect(); + + // Test we can set a new password + $this->client->submitForm('Reset password', [ + 'change_password_form[plainPassword][first]' => 'newStrongPassword', + 'change_password_form[plainPassword][second]' => 'newStrongPassword', + ]); + + self::assertResponseRedirects(''); + + $user = $this->userRepository->findOneBy(['email' => 'me@example.com']); + + self::assertInstanceOf(::class, $user); + + /** @var UserPasswordHasherInterface $passwordHasher */ + $passwordHasher = static::getContainer()->get(UserPasswordHasherInterface::class); + self::assertTrue($passwordHasher->isPasswordValid($user, 'newStrongPassword')); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_check_email.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_check_email.tpl.php new file mode 100644 index 0000000..786819b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_check_email.tpl.php @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block title %}Password Reset Email Sent{% endblock %} + +{% block body %} +

    + If an account matching your email exists, then an email was just sent that contains a link that you can use to reset your password. + This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}. +

    +

    If you don't receive an email please check your spam folder or try again.

    +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_email.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_email.tpl.php new file mode 100644 index 0000000..824a218 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_email.tpl.php @@ -0,0 +1,9 @@ +

    Hi!

    + +

    To reset your password, please visit the following link

    + +{{ url('app_reset_password', {token: resetToken.token}) }} + +

    This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.

    + +

    Cheers!

    diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_request.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_request.tpl.php new file mode 100644 index 0000000..cce6d93 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_request.tpl.php @@ -0,0 +1,22 @@ +{% extends 'base.html.twig' %} + +{% block title %}Reset your password{% endblock %} + +{% block body %} + {% for flash_error in app.flashes('reset_password_error') %} + + {% endfor %} +

    Reset your password

    + + {{ form_start(requestForm) }} + {{ form_row(requestForm.) }} +
    + + Enter your email address, and we will send you a + link to reset your password. + +
    + + + {{ form_end(requestForm) }} +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_reset.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_reset.tpl.php new file mode 100644 index 0000000..799aa10 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/resetPassword/twig_reset.tpl.php @@ -0,0 +1,12 @@ +{% extends 'base.html.twig' %} + +{% block title %}Reset your password{% endblock %} + +{% block body %} +

    Reset your password

    + + {{ form_start(resetForm) }} + {{ form_row(resetForm.plainPassword) }} + + {{ form_end(resetForm) }} +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/scheduler/Schedule.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/scheduler/Schedule.tpl.php new file mode 100644 index 0000000..1c34b99 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/scheduler/Schedule.tpl.php @@ -0,0 +1,30 @@ + + +namespace ; + + + +#[AsSchedule('')] +final class implements ScheduleProviderInterface +{ + public function __construct( + private CacheInterface $cache, + ) { + } + + public function getSchedule(): Schedule + { + return (new Schedule()) + ->add( + + // @TODO - Modify the frequency to suite your needs + RecurringMessage::every('1 hour', new ()), + + // @TODO - Create a Message to schedule + // RecurringMessage::every('1 hour', new App\Message\Message()), + + ) + ->stateful($this->cache) + ; + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/security/UserProvider.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/UserProvider.tpl.php new file mode 100644 index 0000000..5228e66 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/UserProvider.tpl.php @@ -0,0 +1,74 @@ + + +namespace ; + + + +class implements UserProviderInterface, PasswordUpgraderInterface +{ + /** + * Symfony calls this method if you use features like switch_user + * or remember_me. + * + * If you're not using these features, you do not need to implement + * this method. + * + * @throws UserNotFoundException if the user is not found + */ + public function loadUserByIdentifier($identifier): UserInterface + { + // Load a User object from your data source or throw UserNotFoundException. + // The $identifier argument may not actually be a username: + // it is whatever value is being returned by the getUserIdentifier() + // method in your User class. + throw new \Exception('TODO: fill in loadUserByIdentifier() inside '.__FILE__); + } + + /** + * @deprecated since Symfony 5.3, loadUserByIdentifier() is used instead + */ + public function loadUserByUsername($username): UserInterface + { + return $this->loadUserByIdentifier($username); + } + + /** + * Refreshes the user after being reloaded from the session. + * + * When a user is logged in, at the beginning of each request, the + * User object is loaded from the session and then this method is + * called. Your job is to make sure the user's data is still fresh by, + * for example, re-querying for fresh User data. + * + * If your firewall is "stateless: true" (for a pure API), this + * method is not called. + */ + public function refreshUser(UserInterface $user): UserInterface + { + if (!$user instanceof ) { + throw new UnsupportedUserException(sprintf('Invalid user class "%s".', $user::class)); + } + + // Return a User object after making sure its data is "fresh". + // Or throw a UsernameNotFoundException if the user no longer exists. + throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__); + } + + /** + * Tells Symfony to use this provider for this User class. + */ + public function supportsClass(string $class): bool + { + return ::class === $class || is_subclass_of($class, ::class); + } + + /** + * Upgrades the hashed password of a user, typically for using a better hash algorithm. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + // TODO: when hashed passwords are in use, this method should: + // 1. persist the new password in the user storage + // 2. update the $user object with $user->setPassword($newHashedPassword); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/security/Voter.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/Voter.tpl.php new file mode 100644 index 0000000..431b3c5 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/Voter.tpl.php @@ -0,0 +1,42 @@ + + +namespace getNamespace(); ?>; + +getUseStatements(); ?> + +getClassDeclaration() ?> +{ + public const EDIT = 'POST_EDIT'; + public const VIEW = 'POST_VIEW'; + + protected function supports(string $attribute, mixed $subject): bool + { + // replace with your own logic + // https://symfony.com/doc/current/security/voters.html + return in_array($attribute, [self::EDIT, self::VIEW]) + && $subject instanceof \App\Entity\getClassName()) ?>; + } + + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + { + $user = $token->getUser(); + // if the user is anonymous, do not grant access + if (!$user instanceof UserInterface) { + return false; + } + + // ... (check conditions and return true to grant permission) ... + switch ($attribute) { + case self::EDIT: + // logic to determine if the user can EDIT + // return true or false + break; + case self::VIEW: + // logic to determine if the user can VIEW + // return true or false + break; + } + + return false; + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/security/custom/Authenticator.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/custom/Authenticator.tpl.php new file mode 100644 index 0000000..dacec9b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/custom/Authenticator.tpl.php @@ -0,0 +1,67 @@ + + +namespace ; + + + +/** +* @see https://symfony.com/doc/current/security/custom_authenticator.html +*/ +class extends AbstractAuthenticator +{ + /** + * Called on every request to decide if this authenticator should be + * used for the request. Returning `false` will cause this authenticator + * to be skipped. + */ + public function supports(Request $request): ?bool + { + // return $request->headers->has('X-AUTH-TOKEN'); + } + + public function authenticate(Request $request): Passport + { + // $apiToken = $request->headers->get('X-AUTH-TOKEN'); + // if (null === $apiToken) { + // The token header was empty, authentication fails with HTTP Status + // Code 401 "Unauthorized" + // throw new CustomUserMessageAuthenticationException('No API token provided'); + // } + + // implement your own logic to get the user identifier from `$apiToken` + // e.g. by looking up a user in the database using its API key + // $userIdentifier = /** ... */; + + // return new SelfValidatingPassport(new UserBadge($userIdentifier)); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + // on success, let the request continue + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $data = [ + // you may want to customize or obfuscate the message first + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()) + + // or to translate this message + // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) + ]; + + return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); + } + + // public function start(Request $request, AuthenticationException $authException = null): Response + // { + // /* + // * If you would like this class to control what happens when an anonymous user accesses a + // * protected page (e.g. redirect to /login), uncomment this method and make this class + // * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface. + // * + // * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point + // */ + // } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/LoginController.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/LoginController.tpl.php new file mode 100644 index 0000000..734db0b --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/LoginController.tpl.php @@ -0,0 +1,23 @@ + + +namespace ; + + + +class extends AbstractController +{ + #[Route(path: '/login', name: 'app_login')] + public function login(AuthenticationUtils $authenticationUtils): Response + { + // get the login error if there is one + $error = $authenticationUtils->getLastAuthenticationError(); + + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('/login.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/Test.LoginController.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/Test.LoginController.tpl.php new file mode 100644 index 0000000..a74fb45 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/Test.LoginController.tpl.php @@ -0,0 +1,79 @@ + +namespace App\Tests; + + + +class LoginControllerTest extends WebTestCase +{ + private KernelBrowser $client; + + protected function setUp(): void + { + $this->client = static::createClient(); + $container = static::getContainer(); + $em = $container->get('doctrine.orm.entity_manager'); + $userRepository = $em->getRepository(::class); + + // Remove any existing users from the test database + foreach ($userRepository->findAll() as $user) { + $em->remove($user); + } + + $em->flush(); + + // Create a fixture + /** @var UserPasswordHasherInterface $passwordHasher */ + $passwordHasher = $container->get('security.user_password_hasher'); + + $user = (new ())->setEmail('email@example.com'); + $user->setPassword($passwordHasher->hashPassword($user, 'password')); + + $em->persist($user); + $em->flush(); + } + + public function testLogin(): void + { + // Denied - Can't login with invalid email address. + $this->client->request('GET', '/login'); + self::assertResponseIsSuccessful(); + + $this->client->submitForm('Sign in', [ + '_username' => 'doesNotExist@example.com', + '_password' => 'password', + ]); + + self::assertResponseRedirects('/login'); + $this->client->followRedirect(); + + // Ensure we do not reveal if the user exists or not. + self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.'); + + // Denied - Can't login with invalid password. + $this->client->request('GET', '/login'); + self::assertResponseIsSuccessful(); + + $this->client->submitForm('Sign in', [ + '_username' => 'email@example.com', + '_password' => 'bad-password', + ]); + + self::assertResponseRedirects('/login'); + $this->client->followRedirect(); + + // Ensure we do not reveal the user exists but the password is wrong. + self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.'); + + // Success - Login with valid credentials is allowed. + $this->client->submitForm('Sign in', [ + '_username' => 'email@example.com', + '_password' => 'password', + ]); + + self::assertResponseRedirects('/'); + $this->client->followRedirect(); + + self::assertSelectorNotExists('.alert-danger'); + self::assertResponseIsSuccessful(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/login_form.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/login_form.tpl.php new file mode 100644 index 0000000..ca64abd --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/security/formLogin/login_form.tpl.php @@ -0,0 +1,43 @@ +{% extends 'base.html.twig' %} + +{% block title %}Log in!{% endblock %} + +{% block body %} +
    + {% if error %} +
    {{ error.messageKey|trans(error.messageData, 'security') }}
    + {% endif %} + + + {% if app.user %} +
    + You are logged in as {{ app.user.userIdentifier }}, Logout +
    + {% endif %} + + +

    Please sign in

    + + + + + + + + {# + Uncomment this section and add a remember_me option below your firewall to activate remember me functionality. + See https://symfony.com/doc/current/security/remember_me.html + +
    + + +
    + #} + + +
    +{% endblock %} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/serializer/Encoder.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/serializer/Encoder.tpl.php new file mode 100644 index 0000000..584b15d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/serializer/Encoder.tpl.php @@ -0,0 +1,32 @@ + + +namespace ; + + + +class implements EncoderInterface, DecoderInterface +{ + public const FORMAT = ''; + + public function encode(mixed $data, string $format, array $context = []): string + { + // TODO: return your encoded data + return ''; + } + + public function supportsEncoding(string $format): bool + { + return self::FORMAT === $format; + } + + public function decode(string $data, string $format, array $context = []): mixed + { + // TODO: return your decoded data + return ''; + } + + public function supportsDecoding(string $format): bool + { + return self::FORMAT === $format; + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/serializer/Normalizer.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/serializer/Normalizer.tpl.php new file mode 100644 index 0000000..8fdeee8 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/serializer/Normalizer.tpl.php @@ -0,0 +1,41 @@ + + +namespace ; + + + +class implements NormalizerInterface +{ + public function __construct( + #[Autowire(service: 'serializer.normalizer.object')] + private NormalizerInterface $normalizer + ) { + } + + public function normalize($object, string $format = null, array $context = []): array + { + $data = $this->normalizer->normalize($object, $format, $context); + + // TODO: add, edit, or delete some data + + return $data; + } + + public function supportsNormalization($data, string $format = null, array $context = []): bool + { + + return $data instanceof ; + + // TODO: return $data instanceof Object + + } + + public function getSupportedTypes(?string $format): array + { + + return [::class => true]; + + // TODO: return [Object::class => true]; + + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/stimulus/Controller.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/stimulus/Controller.tpl.php new file mode 100644 index 0000000..555e38d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/stimulus/Controller.tpl.php @@ -0,0 +1,18 @@ +import { Controller } from '@hotwired/stimulus'; + +/* +* The following line makes this controller "lazy": it won't be downloaded until needed +* See https://github.com/symfony/stimulus-bridge#lazy-controllers +*/ +/* stimulusFetch: 'lazy' */ +export default class extends Controller { + + + static values = { + + : , + + } + + // ... +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/test/ApiTestCase.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/ApiTestCase.tpl.php new file mode 100644 index 0000000..2f55519 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/ApiTestCase.tpl.php @@ -0,0 +1,16 @@ + + +namespace ; + +use ; + +class extends ApiTestCase +{ + public function testSomething(): void + { + $response = static::createClient()->request('GET', '/'); + + $this->assertResponseIsSuccessful(); + $this->assertJsonContains(['@id' => '/']); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/test/Functional.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/Functional.tpl.php new file mode 100644 index 0000000..d73e6c0 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/Functional.tpl.php @@ -0,0 +1,31 @@ + + + +namespace ; + + + +class extends +{ + public function testSomething(): void + { + + $client = static::createPantherClient(); + + $client = static::createClient(); + + $crawler = $client->request('GET', '/'); + + + + $this->assertResponseIsSuccessful(); + + $this->assertSelectorTextContains('h1', 'Hello World'); + + + $this->assertSame(200, $client->getResponse()->getStatusCode()); + + $this->assertStringContainsString('Hello World', $crawler->filter('h1')->text()); + + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/test/KernelTestCase.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/KernelTestCase.tpl.php new file mode 100644 index 0000000..808f6a0 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/KernelTestCase.tpl.php @@ -0,0 +1,17 @@ + + +namespace ; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + +class extends KernelTestCase +{ + public function testSomething(): void + { + $kernel = self::bootKernel(); + + $this->assertSame('test', $kernel->getEnvironment()); + // $routerService = static::getContainer()->get('router'); + // $myCustomService = static::getContainer()->get(CustomService::class); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/test/PantherTestCase.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/PantherTestCase.tpl.php new file mode 100644 index 0000000..ab42fdf --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/PantherTestCase.tpl.php @@ -0,0 +1,20 @@ + + +namespace ; + +use Symfony\Component\Panther\PantherTestCase; + +class extends PantherTestCase +{ + public function testSomething(): void + { + $client = static::createPantherClient(); + $crawler = $client->request('GET', '/'); + + + $this->assertSelectorTextContains('h1', 'Hello World'); + + $this->assertStringContainsString('Hello World', $crawler->filter('h1')->text()); + + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/test/TestCase.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/TestCase.tpl.php new file mode 100644 index 0000000..e4a9ad6 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/TestCase.tpl.php @@ -0,0 +1,13 @@ + + +namespace ; + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testSomething(): void + { + $this->assertTrue(true); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/test/Unit.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/Unit.tpl.php new file mode 100644 index 0000000..f4b19ad --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/Unit.tpl.php @@ -0,0 +1,14 @@ + + + +namespace ; + + + +class extends TestCase +{ + public function testSomething(): void + { + $this->assertTrue(true); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/test/WebTestCase.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/WebTestCase.tpl.php new file mode 100644 index 0000000..dcd2386 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/test/WebTestCase.tpl.php @@ -0,0 +1,22 @@ + + +namespace ; + +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class extends WebTestCase +{ + public function testSomething(): void + { + $client = static::createClient(); + $crawler = $client->request('GET', '/'); + + + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('h1', 'Hello World'); + + $this->assertSame(200, $client->getResponse()->getStatusCode()); + $this->assertStringContainsString('Hello World', $crawler->filter('h1')->text()); + + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Component.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Component.tpl.php new file mode 100644 index 0000000..f6e4a74 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Component.tpl.php @@ -0,0 +1,10 @@ + + +namespace ; + +use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; + +#[AsTwigComponent] +final class +{ +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Extension.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Extension.tpl.php new file mode 100644 index 0000000..7949821 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Extension.tpl.php @@ -0,0 +1,25 @@ + + +namespace ; + + + +class extends AbstractExtension +{ + public function getFilters(): array + { + return [ + // If your filter generates SAFE HTML, you should add a third + // parameter: ['is_safe' => ['html']] + // Reference: https://twig.symfony.com/doc/3.x/advanced.html#automatic-escaping + new TwigFilter('filter_name', [::class, 'doSomething']), + ]; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('function_name', [::class, 'doSomething']), + ]; + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/LiveComponent.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/LiveComponent.tpl.php new file mode 100644 index 0000000..ec6062c --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/LiveComponent.tpl.php @@ -0,0 +1,12 @@ + + +namespace ; + +use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\LiveComponent\DefaultActionTrait; + +#[AsLiveComponent] +final class +{ + use DefaultActionTrait; +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Runtime.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Runtime.tpl.php new file mode 100644 index 0000000..a9ac806 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/Runtime.tpl.php @@ -0,0 +1,18 @@ + + +namespace ; + + + +class implements RuntimeExtensionInterface +{ + public function __construct() + { + // Inject dependencies if needed + } + + public function doSomething($value) + { + // ... + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/component_template.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/component_template.tpl.php new file mode 100644 index 0000000..75ebedd --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/twig/component_template.tpl.php @@ -0,0 +1,3 @@ + + + diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/validator/Constraint.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/validator/Constraint.tpl.php new file mode 100644 index 0000000..7baafa4 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/validator/Constraint.tpl.php @@ -0,0 +1,20 @@ + + +namespace ; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class extends Constraint +{ + /* + * Any public properties become valid options for the annotation. + * Then, use these in your validator class. + */ + public string $message = 'The value "{{ value }}" is not valid.'; +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/validator/Validator.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/validator/Validator.tpl.php new file mode 100644 index 0000000..ccc1a00 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/validator/Validator.tpl.php @@ -0,0 +1,23 @@ + + +namespace ; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +class extends ConstraintValidator +{ + public function validate(mixed $value, Constraint $constraint): void + { + /** @var $constraint */ + + if (null === $value || '' === $value) { + return; + } + + // TODO: implement the validation here + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->addViolation(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/verifyEmail/EmailVerifier.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/verifyEmail/EmailVerifier.tpl.php new file mode 100644 index 0000000..f5e37f7 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/verifyEmail/EmailVerifier.tpl.php @@ -0,0 +1,51 @@ + + +namespace ; + + + +class +{ + public function __construct( + private VerifyEmailHelperInterface $verifyEmailHelper, + private MailerInterface $mailer, + private EntityManagerInterface $entityManager + ) { + } + + public function sendEmailConfirmation(string $verifyEmailRouteName, $user, TemplatedEmail $email): void + { + $signatureComponents = $this->verifyEmailHelper->generateSignature( + $verifyEmailRouteName, + (string) $user->(), + + (string) $user->(), + ['id' => $user->()] + + (string) $user->() + + ); + + $context = $email->getContext(); + $context['signedUrl'] = $signatureComponents->getSignedUrl(); + $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey(); + $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData(); + + $email->context($context); + + $this->mailer->send($email); + } + + /** + * @throws VerifyEmailExceptionInterface + */ + public function handleEmailConfirmation(Request $request, $user): void + { + $this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $user->(), (string) $user->()); + + $user->setVerified(true); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/webhook/RequestParser.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/webhook/RequestParser.tpl.php new file mode 100644 index 0000000..292b87e --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/webhook/RequestParser.tpl.php @@ -0,0 +1,52 @@ + + +namespace ; + + + +final class extends AbstractRequestParser +{ + protected function getRequestMatcher(): RequestMatcherInterface + { + + return new ChainRequestMatcher([ + + + + new (), + + ]); + + return new (); + + } + + /** + * @throws JsonException + */ + protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent + { + // TODO: Adapt or replace the content of this method to fit your need. + + // Validate the request against $secret. + $authToken = $request->headers->get('X-Authentication-Token'); + if ($authToken !== $secret) { + throw new RejectWebhookException(Response::HTTP_UNAUTHORIZED, 'Invalid authentication token.'); + } + + // Validate the request payload. + if (!$request->getPayload()->has('name') + || !$request->getPayload()->has('id')) { + throw new RejectWebhookException(Response::HTTP_BAD_REQUEST, 'Request payload does not contain required fields.'); + } + + // Parse the request payload and return a RemoteEvent object. + $payload = $request->getPayload(); + + return new RemoteEvent( + $payload->getString('name'), + $payload->getString('id'), + $payload->all(), + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Resources/skeleton/webhook/WebhookConsumer.tpl.php b/vendor/symfony/maker-bundle/src/Resources/skeleton/webhook/WebhookConsumer.tpl.php new file mode 100644 index 0000000..ab62712 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Resources/skeleton/webhook/WebhookConsumer.tpl.php @@ -0,0 +1,20 @@ + + +namespace ; + +use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer; +use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface; +use Symfony\Component\RemoteEvent\RemoteEvent; + +#[AsRemoteEventConsumer('')] +final class implements ConsumerInterface +{ + public function __construct() + { + } + + public function consume(RemoteEvent $event): void + { + // Implement your own logic here + } +} diff --git a/vendor/symfony/maker-bundle/src/Security/InteractiveSecurityHelper.php b/vendor/symfony/maker-bundle/src/Security/InteractiveSecurityHelper.php new file mode 100644 index 0000000..ae35e45 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Security/InteractiveSecurityHelper.php @@ -0,0 +1,289 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Security; + +use Symfony\Bundle\MakerBundle\Security\Model\Authenticator; +use Symfony\Bundle\MakerBundle\Security\Model\AuthenticatorType; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Validator; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * @internal + */ +final class InteractiveSecurityHelper +{ + public function guessFirewallName(SymfonyStyle $io, array $securityData, ?string $questionText = null): string + { + $realFirewalls = array_filter( + $securityData['security']['firewalls'] ?? [], + static fn ($item) => !isset($item['security']) || true === $item['security'] + ); + + if (0 === \count($realFirewalls)) { + return 'main'; + } + + if (1 === \count($realFirewalls)) { + return key($realFirewalls); + } + + return $io->choice( + $questionText ?? 'Which firewall do you want to update?', + array_keys($realFirewalls), + key($realFirewalls) + ); + } + + public function guessUserClass(SymfonyStyle $io, array $providers, ?string $questionText = null): string + { + if (1 === \count($providers) && isset(current($providers)['entity'])) { + $entityProvider = current($providers); + + return $entityProvider['entity']['class']; + } + + return $io->ask( + $questionText ?? 'Enter the User class that you want to authenticate (e.g. App\\Entity\\User)', + $this->guessUserClassDefault(), + Validator::classIsUserInterface(...) + ); + } + + private function guessUserClassDefault(): string + { + if (class_exists('App\\Entity\\User') && isset(class_implements('App\\Entity\\User')[UserInterface::class])) { + return 'App\\Entity\\User'; + } + + if (class_exists('App\\Security\\User') && isset(class_implements('App\\Security\\User')[UserInterface::class])) { + return 'App\\Security\\User'; + } + + return ''; + } + + public function guessUserNameField(SymfonyStyle $io, string $userClass, array $providers): string + { + if (1 === \count($providers) && isset(current($providers)['entity']) && isset(current($providers)['entity']['property'])) { + $entityProvider = current($providers); + + return $entityProvider['entity']['property']; + } + + if (property_exists($userClass, 'email') && !property_exists($userClass, 'username')) { + return 'email'; + } + + if (!property_exists($userClass, 'email') && property_exists($userClass, 'username')) { + return 'username'; + } + + $classProperties = []; + $reflectionClass = new \ReflectionClass($userClass); + foreach ($reflectionClass->getProperties() as $property) { + $classProperties[] = $property->name; + } + + if (empty($classProperties)) { + throw new \LogicException(\sprintf('No properties were found in "%s" entity', $userClass)); + } + + return $io->choice( + \sprintf('Which field on your %s class will people enter when logging in?', $userClass), + $classProperties, + property_exists($userClass, 'username') ? 'username' : (property_exists($userClass, 'email') ? 'email' : null) + ); + } + + public function guessEmailField(SymfonyStyle $io, string $userClass): string + { + if (property_exists($userClass, 'email')) { + return 'email'; + } + + $classProperties = []; + $reflectionClass = new \ReflectionClass($userClass); + foreach ($reflectionClass->getProperties() as $property) { + $classProperties[] = $property->name; + } + + return $io->choice( + \sprintf('Which field on your %s class holds the email address?', $userClass), + $classProperties + ); + } + + public function guessPasswordField(SymfonyStyle $io, string $userClass): string + { + if (property_exists($userClass, 'password')) { + return 'password'; + } + + $classProperties = []; + $reflectionClass = new \ReflectionClass($userClass); + foreach ($reflectionClass->getProperties() as $property) { + $classProperties[] = $property->name; + } + + return $io->choice( + \sprintf('Which field on your %s class holds the encoded password?', $userClass), + $classProperties + ); + } + + public function guessPasswordSetter(SymfonyStyle $io, string $userClass): string + { + if (null === ($methodChoices = $this->methodNameGuesser($userClass, 'setPassword'))) { + return 'setPassword'; + } + + return $io->choice( + \sprintf('Which method on your %s class can be used to set the encoded password (e.g. setPassword())?', $userClass), + $methodChoices + ); + } + + public function guessEmailGetter(SymfonyStyle $io, string $userClass, string $emailPropertyName): string + { + $supposedEmailMethodName = \sprintf('get%s', Str::asCamelCase($emailPropertyName)); + + if (null === ($methodChoices = $this->methodNameGuesser($userClass, $supposedEmailMethodName))) { + return $supposedEmailMethodName; + } + + return $io->choice( + \sprintf('Which method on your %s class can be used to get the email address (e.g. getEmail())?', $userClass), + $methodChoices + ); + } + + public function guessIdGetter(SymfonyStyle $io, string $userClass): string + { + if (null === ($methodChoices = $this->methodNameGuesser($userClass, 'getId'))) { + return 'getId'; + } + + return $io->choice( + \sprintf('Which method on your %s class can be used to get the unique user identifier (e.g. getId())?', $userClass), + $methodChoices + ); + } + + /** + * @param array> $firewalls Config data from security.firewalls + * + * @return Authenticator[] + */ + public function getAuthenticatorsFromConfig(array $firewalls): array + { + $authenticators = []; + + /* Iterate over each firewall that exists e.g. security.firewalls.main + * $firewallName could be "main" or "dev", etc... + * $firewallConfig should be an array of the firewalls params + */ + foreach ($firewalls as $firewallName => $firewallConfig) { + if (!\is_array($firewallConfig)) { + continue; + } + + $authenticators = [ + ...$authenticators, + ...$this->getAuthenticatorsFromConfigData($firewallConfig, $firewallName), + ]; + } + + return $authenticators; + } + + /** + * Pass in a firewalls config e.g. security.firewalls.main like: + * pattern: ^/path + * form_login: + * login_path: app_login + * custom_authenticator: + * - App\Security\MyAuthenticator + * + * @param array $firewallConfig + * + * @return Authenticator[] + */ + private function getAuthenticatorsFromConfigData(array $firewallConfig, string $firewallName): array + { + $authenticators = []; + + foreach ($firewallConfig as $potentialAuthenticator => $configData) { + // Check if $potentialAuthenticator is a supported authenticator or if its some other key. + if (null === ($authenticator = AuthenticatorType::tryFrom($potentialAuthenticator))) { + // $potentialAuthenticator is probably something like "pattern" or "lazy", not an authenticator + continue; + } + + // $potentialAuthenticator is a supported authenticator. Check if it's a custom_authenticator. + if (AuthenticatorType::CUSTOM !== $authenticator) { + // We found a "built in" authenticator - "form_login", "json_login", etc... + $authenticators[] = new Authenticator($authenticator, $firewallName); + + continue; + } + + /* + * $potentialAuthenticator = custom_authenticator. + * $configData is either [App\MyAuthenticator] or (string) App\MyAuthenticator + */ + $customAuthenticators = $this->getCustomAuthenticators($configData, $firewallName); + + $authenticators = [...$authenticators, ...$customAuthenticators]; + } + + return $authenticators; + } + + /** + * @param string|array $customAuthenticators A single entry from custom_authenticators or an array of authenticators + * + * @return Authenticator[] + */ + private function getCustomAuthenticators(string|array $customAuthenticators, string $firewallName): array + { + if (\is_string($customAuthenticators)) { + $customAuthenticators = [$customAuthenticators]; + } + + $authenticators = []; + + foreach ($customAuthenticators as $customAuthenticatorClass) { + $authenticators[] = new Authenticator(AuthenticatorType::CUSTOM, $firewallName, $customAuthenticatorClass); + } + + return $authenticators; + } + + private function methodNameGuesser(string $className, string $suspectedMethodName): ?array + { + $reflectionClass = new \ReflectionClass($className); + + if ($reflectionClass->hasMethod($suspectedMethodName)) { + return null; + } + + $classMethods = []; + + foreach ($reflectionClass->getMethods() as $method) { + $classMethods[] = $method->name; + } + + return $classMethods; + } +} diff --git a/vendor/symfony/maker-bundle/src/Security/Model/Authenticator.php b/vendor/symfony/maker-bundle/src/Security/Model/Authenticator.php new file mode 100644 index 0000000..307539e --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Security/Model/Authenticator.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Security\Model; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class Authenticator +{ + public function __construct( + public AuthenticatorType $type, + public string $firewallName, + public ?string $authenticatorClass = null, + ) { + } + + /** + * Useful for asking questions like "Which authenticator do you want to use?". + */ + public function __toString(): string + { + return \sprintf( + '"%s" in the "%s" firewall', + $this->authenticatorClass ?? $this->type->value, + $this->firewallName, + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Security/Model/AuthenticatorType.php b/vendor/symfony/maker-bundle/src/Security/Model/AuthenticatorType.php new file mode 100644 index 0000000..70dda1a --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Security/Model/AuthenticatorType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Security\Model; + +/** + * @author Jesse Rushlow + * + * @internal + */ +enum AuthenticatorType: string +{ + case FORM_LOGIN = 'form_login'; + case JSON_LOGIN = 'json_login'; + case HTTP_BASIC = 'http_basic'; + case LOGIN_LINK = 'login_link'; + case ACCESS_TOKEN = 'access_token'; + case X509 = 'x509'; + case REMOTE_USER = 'remote_user'; + + case CUSTOM = 'custom_authenticator'; +} diff --git a/vendor/symfony/maker-bundle/src/Security/SecurityConfigUpdater.php b/vendor/symfony/maker-bundle/src/Security/SecurityConfigUpdater.php new file mode 100644 index 0000000..e713bc1 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Security/SecurityConfigUpdater.php @@ -0,0 +1,318 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Security; + +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Component\HttpKernel\Log\Logger; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; + +/** + * @author Ryan Weaver + * @author Jesse Rushlow + * + * @internal + */ +final class SecurityConfigUpdater +{ + private ?YamlSourceManipulator $manipulator = null; + + public function __construct( + private ?Logger $ysmLogger = null, + ) { + } + + public function updateForFormLogin(string $yamlSource, string $firewallToUpdate, string $loginPath, string $checkPath): string + { + $newData = $this->createYamlSourceManipulator($yamlSource); + + $newData['security']['firewalls'][$firewallToUpdate]['form_login']['login_path'] = $loginPath; + $newData['security']['firewalls'][$firewallToUpdate]['form_login']['check_path'] = $checkPath; + $newData['security']['firewalls'][$firewallToUpdate]['form_login']['enable_csrf'] = true; + + return $this->getYamlContentsFromData($newData); + } + + public function updateForJsonLogin(string $yamlSource, string $firewallToUpdate, string $checkPath): string + { + $data = $this->createYamlSourceManipulator($yamlSource); + + $data['security']['firewalls'][$firewallToUpdate]['json_login']['check_path'] = $checkPath; + + return $this->getYamlContentsFromData($data); + } + + /** + * Updates security.yaml contents based on a new User class. + */ + public function updateForUserClass(string $yamlSource, UserClassConfiguration $userConfig, string $userClass): string + { + $this->createYamlSourceManipulator($yamlSource); + + $this->updateProviders($userConfig, $userClass); + + if ($userConfig->hasPassword()) { + $this->updatePasswordHashers($userClass); + } + + $contents = $this->manipulator->getContents(); + $this->manipulator = null; + + return $contents; + } + + public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe): string + { + $this->createYamlSourceManipulator($yamlSource); + + $newData = $this->manipulator->getData(); + + if (!isset($newData['security']['firewalls'])) { + if ($newData['security']) { + $newData['security']['_firewalls'] = $this->manipulator->createEmptyLine(); + } + + $newData['security']['firewalls'] = []; + } + + if (!isset($newData['security']['firewalls'][$firewallName])) { + $newData['security']['firewalls'][$firewallName] = ['lazy' => true]; + } + + $firewall = $newData['security']['firewalls'][$firewallName]; + + if (isset($firewall['custom_authenticator'])) { + if (\is_array($firewall['custom_authenticator'])) { + $firewall['custom_authenticator'][] = $authenticatorClass; + } else { + $stringValue = $firewall['custom_authenticator']; + $firewall['custom_authenticator'] = []; + $firewall['custom_authenticator'][] = $stringValue; + $firewall['custom_authenticator'][] = $authenticatorClass; + } + } else { + $firewall['custom_authenticator'] = $authenticatorClass; + } + + if (!isset($firewall['entry_point']) && $chosenEntryPoint) { + $firewall['entry_point_empty_line'] = $this->manipulator->createEmptyLine(); + $firewall['entry_point_comment'] = $this->manipulator->createCommentLine( + ' the entry_point start() method determines what happens when an anonymous user accesses a protected page' + ); + $firewall['entry_point'] = $authenticatorClass; + } + + if (!isset($firewall['logout']) && $logoutSetup) { + $firewall['logout'] = ['path' => 'app_logout']; + $firewall['logout'][] = $this->manipulator->createCommentLine( + ' where to redirect after logout' + ); + $firewall['logout'][] = $this->manipulator->createCommentLine( + ' target: app_any_route' + ); + } + + if ($supportRememberMe) { + if (!isset($firewall['remember_me'])) { + $firewall['remember_me_empty_line'] = $this->manipulator->createEmptyLine(); + $firewall['remember_me'] = [ + 'secret' => '%kernel.secret%', + 'lifetime' => 604800, + 'path' => '/', + ]; + if (!$alwaysRememberMe) { + $firewall['remember_me'][] = $this->manipulator->createCommentLine(' by default, the feature is enabled by checking a checkbox in the'); + $firewall['remember_me'][] = $this->manipulator->createCommentLine(' login form, uncomment the following line to always enable it.'); + } + } else { + $firewall['remember_me']['secret'] ??= '%kernel.secret%'; + $firewall['remember_me']['lifetime'] ??= 604800; + $firewall['remember_me']['path'] ??= '/'; + } + + if ($alwaysRememberMe) { + $firewall['remember_me']['always_remember_me'] = true; + } else { + $firewall['remember_me'][] = $this->manipulator->createCommentLine('always_remember_me: true'); + } + } + + $newData['security']['firewalls'][$firewallName] = $firewall; + + if (!isset($firewall['logout']) && $logoutSetup) { + $this->configureLogout($newData, $firewallName); + + return $this->manipulator->getContents(); + } + + $this->manipulator->setData($newData); + + return $this->manipulator->getContents(); + } + + public function updateForLogout(string $yamlSource, string $firewallName): string + { + $this->createYamlSourceManipulator($yamlSource); + + $this->configureLogout($this->manipulator->getData(), $firewallName); + + return $this->manipulator->getContents(); + } + + /** + * @legacy This can be removed once we deprecate/remove `make:auth` + */ + private function configureLogout(array $securityData, string $firewallName): void + { + $securityData['security']['firewalls'][$firewallName]['logout'] = ['path' => 'app_logout']; + $securityData['security']['firewalls'][$firewallName]['logout'][] = $this->manipulator->createCommentLine( + ' where to redirect after logout' + ); + $securityData['security']['firewalls'][$firewallName]['logout'][] = $this->manipulator->createCommentLine( + ' target: app_any_route' + ); + + $this->manipulator->setData($securityData); + } + + private function createYamlSourceManipulator(string $yamlSource): array + { + $this->manipulator = new YamlSourceManipulator($yamlSource); + + if (null !== $this->ysmLogger) { + $this->manipulator->setLogger($this->ysmLogger); + } + + $this->normalizeSecurityYamlFile(); + + return $this->manipulator->getData(); + } + + private function getYamlContentsFromData(array $yamlData): string + { + $this->manipulator->setData($yamlData); + + return $this->manipulator->getContents(); + } + + private function normalizeSecurityYamlFile(): void + { + if (!isset($this->manipulator->getData()['security'])) { + $newData = $this->manipulator->getData(); + $newData['security'] = []; + $this->manipulator->setData($newData); + } + } + + private function updateProviders(UserClassConfiguration $userConfig, string $userClass): void + { + $this->removeMemoryProviderIfIsSingleConfigured(); + + $newData = $this->manipulator->getData(); + if ($newData['security'] && !\array_key_exists('providers', $newData['security'])) { + $newData['security']['_providers'] = $this->manipulator->createEmptyLine(); + } + + $newData['security']['providers']['__'] = $this->manipulator->createCommentLine( + ' used to reload user from session & other features (e.g. switch_user)' + ); + if ($userConfig->isEntity()) { + $newData['security']['providers']['app_user_provider'] = [ + 'entity' => [ + 'class' => $userClass, + 'property' => $userConfig->getIdentityPropertyName(), + ], + ]; + } else { + if (!$userConfig->getUserProviderClass()) { + throw new \LogicException('User provider class must be set for non-entity user.'); + } + + $newData['security']['providers']['app_user_provider'] = [ + 'id' => $userConfig->getUserProviderClass(), + ]; + } + $this->manipulator->setData($newData); + } + + private function updatePasswordHashers(string $userClass): void + { + $newData = $this->manipulator->getData(); + + if (isset($newData['security']['encoders'])) { + throw new \RuntimeException('Password Encoders are no longer supported by MakerBundle. Please update your "config/packages/security.yaml" file to use Password Hashers instead.'); + } + + // The security-bundle recipe sets the password hasher via Flex. If it exists, move on... + if (isset($newData['security']['password_hashers'][PasswordAuthenticatedUserInterface::class])) { + return; + } + + // by convention, password_hashers are put before the user provider option + $providersIndex = array_search('providers', array_keys($newData['security'])); + + if (false === $providersIndex) { + $newData['security'] = ['password_hashers' => []] + $newData['security']; + } else { + $newData['security'] = array_merge( + \array_slice($newData['security'], 0, $providersIndex), + ['password_hashers' => []], + \array_slice($newData['security'], $providersIndex) + ); + } + + $newData['security']['password_hashers'][$userClass] = [ + 'algorithm' => 'auto', + ]; + + $newData['security']['password_hashers']['_'] = $this->manipulator->createEmptyLine(); + + $this->manipulator->setData($newData); + } + + private function removeMemoryProviderIfIsSingleConfigured(): void + { + if (!$this->isSingleInMemoryProviderConfigured()) { + return; + } + + $newData = $this->manipulator->getData(); + + $memoryProviderName = array_keys($newData['security']['providers'])[0]; + + $newData['security']['providers'] = []; + + foreach ($newData['security']['firewalls'] as &$firewall) { + if (($firewall['provider'] ?? null) === $memoryProviderName) { + $firewall['provider'] = 'app_user_provider'; + } + } + + $this->manipulator->setData($newData); + } + + private function isSingleInMemoryProviderConfigured(): bool + { + if (!isset($this->manipulator->getData()['security']['providers'])) { + return false; + } + + $providersConfig = $this->manipulator->getData()['security']['providers']; + + if (1 !== \count($providersConfig)) { + return false; + } + + $firstProviderConfig = array_values($providersConfig)[0]; + + return \array_key_exists('memory', $firstProviderConfig); + } +} diff --git a/vendor/symfony/maker-bundle/src/Security/SecurityControllerBuilder.php b/vendor/symfony/maker-bundle/src/Security/SecurityControllerBuilder.php new file mode 100644 index 0000000..5613e20 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Security/SecurityControllerBuilder.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Security; + +use PhpParser\Builder\Param; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; + +/** + * @internal + */ +final class SecurityControllerBuilder +{ + public function addLoginMethod(ClassSourceManipulator $manipulator): void + { + $loginMethodBuilder = $manipulator->createMethodBuilder('login', 'Response', false); + + $loginMethodBuilder->addAttribute($manipulator->buildAttributeNode(Route::class, ['path' => '/login', 'name' => 'app_login'])); + + $manipulator->addUseStatementIfNecessary(Response::class); + $manipulator->addUseStatementIfNecessary(Route::class); + $manipulator->addUseStatementIfNecessary(AuthenticationUtils::class); + + $loginMethodBuilder->addParam( + (new Param('authenticationUtils'))->setType('AuthenticationUtils') + ); + + $manipulator->addMethodBody($loginMethodBuilder, <<<'CODE' + getUser()) { + // return $this->redirectToRoute('target_path'); + // } + CODE + ); + $loginMethodBuilder->addStmt($manipulator->createMethodLevelBlankLine()); + $manipulator->addMethodBody($loginMethodBuilder, <<<'CODE' + getLastAuthenticationError(); + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + CODE + ); + $loginMethodBuilder->addStmt($manipulator->createMethodLevelBlankLine()); + $manipulator->addMethodBody($loginMethodBuilder, <<<'CODE' + render( + 'security/login.html.twig', + [ + 'last_username' => $lastUsername, + 'error' => $error, + ] + ); + CODE + ); + $manipulator->addMethodBuilder($loginMethodBuilder); + } + + public function addLogoutMethod(ClassSourceManipulator $manipulator): void + { + $logoutMethodBuilder = $manipulator->createMethodBuilder('logout', 'void', false); + + $logoutMethodBuilder->addAttribute($manipulator->buildAttributeNode(Route::class, ['path' => '/logout', 'name' => 'app_logout'])); + + $manipulator->addUseStatementIfNecessary(Route::class); + $manipulator->addMethodBody($logoutMethodBuilder, <<<'CODE' + addMethodBuilder($logoutMethodBuilder); + } +} diff --git a/vendor/symfony/maker-bundle/src/Security/UserClassBuilder.php b/vendor/symfony/maker-bundle/src/Security/UserClassBuilder.php new file mode 100644 index 0000000..e99af57 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Security/UserClassBuilder.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Security; + +use PhpParser\Node; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Adds logic to implement UserInterface to an existing User class. + * + * @internal + */ +final class UserClassBuilder +{ + public function addUserInterfaceImplementation(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void + { + $manipulator->addInterface(UserInterface::class); + + $this->addUniqueConstraint($manipulator, $userClassConfig); + + $this->addGetUsername($manipulator, $userClassConfig); + + $this->addGetRoles($manipulator, $userClassConfig); + + $this->addPasswordImplementation($manipulator, $userClassConfig); + + $this->addEraseCredentials($manipulator); + } + + private function addPasswordImplementation(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void + { + if (!$userClassConfig->hasPassword()) { + return; + } + + $manipulator->addInterface(PasswordAuthenticatedUserInterface::class); + + $this->addGetPassword($manipulator, $userClassConfig); + } + + private function addGetUsername(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void + { + if ($userClassConfig->isEntity()) { + // add entity property + $manipulator->addEntityField( + new ClassProperty( + propertyName: $userClassConfig->getIdentityPropertyName(), + type: 'string', + length: 180, + ) + ); + } else { + // add normal property + $manipulator->addProperty( + name: $userClassConfig->getIdentityPropertyName() + ); + + $manipulator->addGetter( + $userClassConfig->getIdentityPropertyName(), + 'string', + true + ); + + $manipulator->addSetter( + $userClassConfig->getIdentityPropertyName(), + 'string', + false + ); + } + + // define getUsername (if it was defined above, this will override) + $manipulator->addAccessorMethod( + $userClassConfig->getIdentityPropertyName(), + 'getUserIdentifier', + 'string', + false, + [ + 'A visual identifier that represents this user.', + '', + '@see UserInterface', + ], + true + ); + } + + private function addGetRoles(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void + { + if ($userClassConfig->isEntity()) { + // add entity property + $manipulator->addEntityField( + new ClassProperty(propertyName: 'roles', type: 'json', comments: ['@var list The user roles']) + ); + } else { + // add normal property + $manipulator->addProperty( + name: 'roles', + defaultValue: new Node\Expr\Array_([], ['kind' => Node\Expr\Array_::KIND_SHORT]), + comments: [ + '@var list The user roles', + ] + ); + + $manipulator->addGetter( + 'roles', + 'array', + false, + ); + } + + $manipulator->addSetter( + 'roles', + 'array', + false, + ['@param list $roles'] + ); + + // define getRoles (if it was defined above, this will override) + $builder = $manipulator->createMethodBuilder( + 'getRoles', + 'array', + false, + ['@see UserInterface', '@return list'] + ); + + // $roles = $this->roles + $builder->addStmt( + new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\Variable('roles'), + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), 'roles') + )) + ); + // comment line + $builder->addStmt( + $manipulator->createMethodLevelCommentNode( + 'guarantee every user at least has ROLE_USER' + ) + ); + // $roles[] = 'ROLE_USER'; + $builder->addStmt( + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\ArrayDimFetch( + new Node\Expr\Variable('roles') + ), + new Node\Scalar\String_('ROLE_USER') + ) + ) + ); + $builder->addStmt($manipulator->createMethodLevelBlankLine()); + // return array_unique($roles); + $builder->addStmt( + new Node\Stmt\Return_( + new Node\Expr\FuncCall( + new Node\Name('array_unique'), + [new Node\Expr\Variable('roles')] + ) + ) + ); + + $manipulator->addMethodBuilder($builder); + } + + private function addGetPassword(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void + { + if (!$userClassConfig->hasPassword()) { + // add an empty method only + $builder = $manipulator->createMethodBuilder( + 'getPassword', + 'string', + true, + [ + 'This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.', + '', + '@see PasswordAuthenticatedUserInterface', + ] + ); + + $builder->addStmt( + new Node\Stmt\Return_( + new Node\Expr\ConstFetch( + new Node\Name('null') + ) + ) + ); + + $manipulator->addMethodBuilder($builder); + + return; + } + + $propertyDocs = '@var string The hashed password'; + if ($userClassConfig->isEntity()) { + // add entity property + $manipulator->addEntityField( + new ClassProperty(propertyName: 'password', type: 'string', comments: [$propertyDocs]) + ); + } else { + // add normal property + $manipulator->addProperty( + name: 'password', + comments: [$propertyDocs] + ); + + $manipulator->addGetter( + 'password', + 'string', + true + ); + + $manipulator->addSetter( + 'password', + 'string', + false + ); + } + + // define getPassword (if it was defined above, this will override) + $manipulator->addAccessorMethod( + 'password', + 'getPassword', + 'string', + true, + [ + '@see PasswordAuthenticatedUserInterface', + ] + ); + } + + private function addEraseCredentials(ClassSourceManipulator $manipulator): void + { + // add eraseCredentials: always empty + $builder = $manipulator->createMethodBuilder( + 'eraseCredentials', + 'void', + false, + ['@see UserInterface'] + ); + $builder->addStmt( + $manipulator->createMethodLevelCommentNode( + 'If you store any temporary, sensitive data on the user, clear it here' + ) + ); + $builder->addStmt( + $manipulator->createMethodLevelCommentNode( + '$this->plainPassword = null;' + ) + ); + + $manipulator->addMethodBuilder($builder); + } + + private function addUniqueConstraint(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void + { + if (!$userClassConfig->isEntity()) { + return; + } + + $manipulator->addAttributeToClass( + 'ORM\\UniqueConstraint', + [ + 'name' => 'UNIQ_IDENTIFIER_'.strtoupper(Str::asSnakeCase($userClassConfig->getIdentityPropertyName())), + 'fields' => [$userClassConfig->getIdentityPropertyName()], + ] + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Security/UserClassConfiguration.php b/vendor/symfony/maker-bundle/src/Security/UserClassConfiguration.php new file mode 100644 index 0000000..fca1496 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Security/UserClassConfiguration.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Security; + +/** + * Configuration about the user's new User class. + * + * @internal + */ +final class UserClassConfiguration +{ + private string $userProviderClass; + + public function __construct( + private bool $isEntity, + private string $identityPropertyName, + private bool $hasPassword, + ) { + } + + public function isEntity(): bool + { + return $this->isEntity; + } + + public function getIdentityPropertyName(): string + { + return $this->identityPropertyName; + } + + public function hasPassword(): bool + { + return $this->hasPassword; + } + + public function getUserProviderClass(): string + { + return $this->userProviderClass; + } + + public function setUserProviderClass(string $userProviderClass): void + { + if ($this->isEntity()) { + throw new \LogicException('No custom user class allowed for entity user.'); + } + + $this->userProviderClass = $userProviderClass; + } +} diff --git a/vendor/symfony/maker-bundle/src/Str.php b/vendor/symfony/maker-bundle/src/Str.php new file mode 100644 index 0000000..c55920f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Str.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Doctrine\Inflector\Inflector; +use Doctrine\Inflector\InflectorFactory; +use Symfony\Component\DependencyInjection\Container; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + */ +final class Str +{ + private static ?Inflector $inflector = null; + + /** + * Looks for suffixes in strings in a case-insensitive way. + */ + public static function hasSuffix(string $value, string $suffix): bool + { + return 0 === strcasecmp($suffix, substr($value, -\strlen($suffix))); + } + + /** + * Ensures that the given string ends with the given suffix. If the string + * already contains the suffix, it's not added twice. It's case-insensitive + * (e.g. value: 'Foocommand' suffix: 'Command' -> result: 'FooCommand'). + */ + public static function addSuffix(string $value, string $suffix): string + { + return self::removeSuffix($value, $suffix).$suffix; + } + + /** + * Ensures that the given string doesn't end with the given suffix. If the + * string contains the suffix multiple times, only the last one is removed. + * It's case-insensitive (e.g. value: 'Foocommand' suffix: 'Command' -> result: 'Foo'. + */ + public static function removeSuffix(string $value, string $suffix): string + { + return self::hasSuffix($value, $suffix) ? substr($value, 0, -\strlen($suffix)) : $value; + } + + /** + * Transforms the given string into the format commonly used by PHP classes, + * (e.g. `app:do_this-and_that` -> `AppDoThisAndThat`) but it doesn't check + * the validity of the class name. + */ + public static function asClassName(string $value, string $suffix = ''): string + { + $value = trim($value); + $value = str_replace(['-', '_', '.', ':'], ' ', $value); + $value = ucwords($value); + $value = str_replace(' ', '', $value); + $value = ucfirst($value); + $value = self::addSuffix($value, $suffix); + + return $value; + } + + /** + * Transforms the given string into the format commonly used by Twig variables + * (e.g. `BlogPostType` -> `blog_post_type`). + */ + public static function asTwigVariable(string $value): string + { + $value = trim($value); + $value = preg_replace('/[^a-zA-Z0-9_]/', '_', $value); + $value = preg_replace('/(?<=\\w)([A-Z])/', '_$1', $value); + $value = preg_replace('/_{2,}/', '_', $value); + $value = strtolower($value); + + return $value; + } + + public static function asLowerCamelCase(string $str): string + { + return lcfirst(self::asCamelCase($str)); + } + + public static function asCamelCase(string $str): string + { + return strtr(ucwords(strtr($str, ['_' => ' ', '.' => ' ', '\\' => ' '])), [' ' => '']); + } + + public static function asRoutePath(string $value): string + { + return '/'.str_replace('_', '/', self::asTwigVariable($value)); + } + + public static function asRouteName(string $value): string + { + $routeName = self::asTwigVariable($value); + + return str_starts_with($routeName, 'app_') ? $routeName : 'app_'.$routeName; + } + + public static function asSnakeCase(string $value): string + { + return self::asTwigVariable($value); + } + + public static function asCommand(string $value): string + { + return str_replace('_', '-', self::asTwigVariable($value)); + } + + public static function asEventMethod(string $eventName): string + { + return \sprintf('on%s', self::asClassName($eventName)); + } + + public static function getShortClassName(string $fullClassName): string + { + if (empty(self::getNamespace($fullClassName))) { + return $fullClassName; + } + + return substr($fullClassName, strrpos($fullClassName, '\\') + 1); + } + + /** + * @return array{0: string, 1: string} + */ + public static function getHumanDiscriminatorBetweenTwoClasses(string $className, string $classNameOther): array + { + $namespace = self::getNamespace($className); + $namespaceOther = self::getNamespace($classNameOther); + if (empty($namespace) || empty($namespaceOther)) { + return [$namespace, $namespaceOther]; + } + + $namespaceParts = explode('\\', $namespace); + $namespacePartsOther = explode('\\', $namespaceOther); + + $min = min(\count($namespaceParts), \count($namespacePartsOther)); + for ($i = 0; $i < $min; ++$i) { + $part = $namespaceParts[$i]; + $partOther = $namespacePartsOther[$i]; + if ($part !== $partOther) { + break; + } + + $namespaceParts[$i] = null; + $namespacePartsOther[$i] = null; + } + + return [ + implode('\\', array_filter($namespaceParts)), + implode('\\', array_filter($namespacePartsOther)), + ]; + } + + public static function getNamespace(string $fullClassName): string + { + return substr($fullClassName, 0, strrpos($fullClassName, '\\')); + } + + public static function asFilePath(string $value): string + { + $value = Container::underscore(trim($value)); + $value = str_replace('\\', '/', $value); + + return $value; + } + + public static function singularCamelCaseToPluralCamelCase(string $camelCase): string + { + $snake = self::asSnakeCase($camelCase); + $words = explode('_', $snake); + $words[\count($words) - 1] = self::pluralize($words[\count($words) - 1]); + $reSnaked = implode('_', $words); + + return self::asLowerCamelCase($reSnaked); + } + + public static function pluralCamelCaseToSingular(string $camelCase): string + { + $snake = self::asSnakeCase($camelCase); + $words = explode('_', $snake); + $words[\count($words) - 1] = self::singularize($words[\count($words) - 1]); + $reSnaked = implode('_', $words); + + return self::asLowerCamelCase($reSnaked); + } + + public static function getRandomTerm(): string + { + $adjectives = [ + 'tiny', + 'delicious', + 'gentle', + 'agreeable', + 'brave', + 'orange', + 'grumpy', + 'fierce', + 'victorious', + ]; + $nouns = [ + 'elephant', + 'pizza', + 'popsicle', + 'chef', + 'puppy', + 'gnome', + 'kangaroo', + ]; + + return \sprintf('%s %s', $adjectives[array_rand($adjectives)], $nouns[array_rand($nouns)]); + } + + /** + * Checks if the given name is a valid PHP variable name. + * + * @see http://php.net/manual/en/language.variables.basics.php + * + * @param $name string + * + * @return bool + */ + public static function isValidPhpVariableName($name) + { + return (bool) preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name, $matches); + } + + /** + * @return bool + */ + public static function areClassesAlphabetical(string $class1, string $class2) + { + $arr1 = [$class1, $class2]; + $arr2 = [$class1, $class2]; + sort($arr2); + + return $arr1[0] == $arr2[0]; + } + + public static function asHumanWords(string $variableName): string + { + return str_replace(' ', ' ', ucfirst(trim(implode(' ', preg_split('/(?=[A-Z])/', $variableName))))); + } + + private static function pluralize(string $word): string + { + return static::getInflector()->pluralize($word); + } + + private static function singularize(string $word): string + { + return static::getInflector()->singularize($word); + } + + private static function getInflector(): Inflector + { + if (null === static::$inflector) { + static::$inflector = InflectorFactory::create()->build(); + } + + return static::$inflector; + } +} diff --git a/vendor/symfony/maker-bundle/src/Test/MakerTestCase.php b/vendor/symfony/maker-bundle/src/Test/MakerTestCase.php new file mode 100644 index 0000000..a677afc --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Test/MakerTestCase.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Test; + +use Composer\Semver\Semver; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\MakerBundle\MakerInterface; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Process\Process; + +abstract class MakerTestCase extends TestCase +{ + private ?KernelInterface $kernel = null; + + /** + * @dataProvider getTestDetails + * + * @return void + */ + public function testExecute(MakerTestDetails $makerTestDetails) + { + $this->executeMakerCommand($makerTestDetails); + } + + abstract public function getTestDetails(); + + abstract protected function getMakerClass(): string; + + protected function createMakerTest(): MakerTestDetails + { + return new MakerTestDetails($this->getMakerInstance($this->getMakerClass())); + } + + /** + * @return void + */ + protected function executeMakerCommand(MakerTestDetails $testDetails) + { + if (!class_exists(Process::class)) { + throw new \LogicException('The MakerTestCase cannot be run as the Process component is not installed. Try running "compose require --dev symfony/process".'); + } + + if ($testDetails->isTestSkipped() || !$testDetails->isSupportedByCurrentPhpVersion()) { + $this->markTestSkipped($testDetails->getSkippedTestMessage()); + } + + $testEnv = MakerTestEnvironment::create($testDetails); + + // prepare environment to test + $testEnv->prepareDirectory(); + + if (!$this->hasRequiredDependencyVersions($testDetails, $testEnv)) { + $this->markTestSkipped('Some dependencies versions are too low'); + } + + $makerRunner = new MakerTestRunner($testEnv); + foreach ($testDetails->getPreRunCallbacks() as $preRunCallback) { + $preRunCallback($makerRunner); + } + + $callback = $testDetails->getRunCallback(); + $callback($makerRunner); + + // run tests + $files = $testEnv->getGeneratedFilesFromOutputText(); + + foreach ($files as $file) { + $this->assertTrue($testEnv->fileExists($file), \sprintf('The file "%s" does not exist after generation', $file)); + + if (str_ends_with($file, '.twig')) { + $csProcess = $testEnv->runTwigCSLint($file); + + $this->assertTrue($csProcess->isSuccessful(), \sprintf('File "%s" has a twig-cs problem: %s', $file, $csProcess->getErrorOutput()."\n".$csProcess->getOutput())); + } + } + } + + /** + * @return void + */ + protected function assertContainsCount(string $needle, string $haystack, int $count) + { + $this->assertEquals(1, substr_count($haystack, $needle), \sprintf('Found more than %d occurrences of "%s" in "%s"', $count, $needle, $haystack)); + } + + private function getMakerInstance(string $makerClass): MakerInterface + { + if (null === $this->kernel) { + $this->kernel = $this->createKernel(); + $this->kernel->boot(); + } + + // a cheap way to guess the service id + $serviceId ??= \sprintf('maker.maker.%s', Str::asSnakeCase((new \ReflectionClass($makerClass))->getShortName())); + + return $this->kernel->getContainer()->get($serviceId); + } + + protected function createKernel(): KernelInterface + { + return new MakerTestKernel('dev', true); + } + + private function hasRequiredDependencyVersions(MakerTestDetails $testDetails, MakerTestEnvironment $testEnv): bool + { + if (empty($testDetails->getRequiredPackageVersions())) { + return true; + } + + $installedPackages = json_decode($testEnv->readFile('vendor/composer/installed.json'), true, 512, \JSON_THROW_ON_ERROR); + $packageVersions = []; + + foreach ($installedPackages['packages'] ?? $installedPackages as $installedPackage) { + $packageVersions[$installedPackage['name']] = $installedPackage['version_normalized']; + } + + foreach ($testDetails->getRequiredPackageVersions() as $requiredPackageData) { + $name = $requiredPackageData['name']; + $versionConstraint = $requiredPackageData['version_constraint']; + + if (!isset($packageVersions[$name])) { + throw new \Exception(\sprintf('Package "%s" is required in the test project at version "%s" but it is not installed?', $name, $versionConstraint)); + } + + if (!Semver::satisfies($packageVersions[$name], $versionConstraint)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/maker-bundle/src/Test/MakerTestDetails.php b/vendor/symfony/maker-bundle/src/Test/MakerTestDetails.php new file mode 100644 index 0000000..33cb7cc --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Test/MakerTestDetails.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Test; + +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\MakerInterface; + +final class MakerTestDetails +{ + private ?\Closure $runCallback = null; + private array $preRunCallbacks = []; + private array $extraDependencies = []; + private string $rootNamespace = 'App'; + private int $requiredPhpVersion = 80000; + private array $requiredPackageVersions = []; + private int $blockedPhpVersionUpper = 0; + private int $blockedPhpVersionLower = 0; + private bool $skipOnSymfony7 = false; + + /** + * @internal + */ + private bool $skipTest = false; + + /** + * @internal + */ + private string $skipTestMessage = ''; + + public function __construct( + private MakerInterface $maker, + ) { + } + + public function run(\Closure $callback): self + { + $this->runCallback = $callback; + + return $this; + } + + public function preRun(\Closure $callback): self + { + $this->preRunCallbacks[] = $callback; + + return $this; + } + + /** + * @return string + */ + public function getRootNamespace() + { + return $this->rootNamespace; + } + + public function changeRootNamespace(string $rootNamespace): self + { + $this->rootNamespace = trim($rootNamespace, '\\'); + + return $this; + } + + public function addExtraDependencies(string ...$packages): self + { + $this->extraDependencies = [...$this->extraDependencies, ...$packages]; + + return $this; + } + + public function setRequiredPhpVersion(int $version): self + { + @trigger_deprecation('symfony/maker-bundle', 'v1.44.0', 'setRequiredPhpVersion() is no longer used and will be removed in a future version.'); + + $this->requiredPhpVersion = $version; + + return $this; + } + + /** + * Skip a test from running between a range of PHP Versions. + * + * @param int $lowerLimit Versions below this value will be allowed + * @param int $upperLimit Versions above this value will be allowed + * + * @internal + */ + public function setSkippedPhpVersions(int $lowerLimit, int $upperLimit): self + { + $this->blockedPhpVersionUpper = $upperLimit; + $this->blockedPhpVersionLower = $lowerLimit; + + return $this; + } + + public function addRequiredPackageVersion(string $packageName, string $versionConstraint): self + { + $this->requiredPackageVersions[] = ['name' => $packageName, 'version_constraint' => $versionConstraint]; + + return $this; + } + + public function getUniqueCacheDirectoryName(): string + { + // for cache purposes, only the dependencies are important! + // You can change it ONLY if you don't have another way to implement it + return 'maker_'.strtolower($this->getRootNamespace()).'_'.md5(serialize($this->getDependencies())); + } + + public function getMaker(): MakerInterface + { + return $this->maker; + } + + public function getDependencies(): array + { + $depBuilder = $this->getDependencyBuilder(); + + return [ + ...$depBuilder->getAllRequiredDependencies(), + ...$depBuilder->getAllRequiredDevDependencies(), + ...$this->extraDependencies, + ]; + } + + public function getExtraDependencies(): array + { + return $this->extraDependencies; + } + + public function getDependencyBuilder(): DependencyBuilder + { + $depBuilder = new DependencyBuilder(); + $this->maker->configureDependencies($depBuilder); + + return $depBuilder; + } + + public function isSupportedByCurrentPhpVersion(): bool + { + $hasPhpVersionConstraint = $this->blockedPhpVersionLower > 0 && $this->blockedPhpVersionUpper > 0; + $isSupported = false; + + if (!$hasPhpVersionConstraint) { + $isSupported = true; + } + + if (\PHP_VERSION_ID > $this->blockedPhpVersionUpper) { + $isSupported = true; + } + + if (\PHP_VERSION_ID < $this->blockedPhpVersionLower) { + $isSupported = true; + } + + return $isSupported && \PHP_VERSION_ID >= $this->requiredPhpVersion; + } + + public function getRequiredPackageVersions(): array + { + return $this->requiredPackageVersions; + } + + public function getRunCallback(): \Closure + { + if (!$this->runCallback) { + throw new \Exception('Don\'t forget to call ->run()'); + } + + return $this->runCallback; + } + + /** + * @return \Closure[] + */ + public function getPreRunCallbacks(): array + { + return $this->preRunCallbacks; + } + + public function skipOnSymfony7(): self + { + @trigger_deprecation( + 'symfony/maker-bundle', + 'v1.53.0', + \sprintf('%s() will be removed in a future version, use MakerTestDetails::skipTest() instead.', __METHOD__) + ); + + $this->skipOnSymfony7 = true; + + return $this; + } + + public function getSkipOnSymfony7(): bool + { + @trigger_deprecation( + 'symfony/maker-bundle', + 'v1.53.0', + \sprintf('%s() will be removed in a future version, use MakerTestDetails::isTestSkipped() instead.', __METHOD__) + ); + + return $this->skipOnSymfony7; + } + + /** + * Skip an application test by calling this method and providing an optional + * message. + * + * This method should not be removed even if it is not being used, it may be + * needed in the future. + * + * @internal + */ + public function skipTest(string $message = '', bool $skipped = true): self + { + $this->skipTestMessage = $message; + $this->skipTest = $skipped; + + return $this; + } + + /** + * MakerTestCase uses this to determine if a test should be skipped. + * + * @internal + */ + public function isTestSkipped(): bool + { + return $this->skipTest; + } + + /** + * MakerTestCase uses this to get the skipped test message. + * + * @internal + */ + public function getSkippedTestMessage(): string + { + return $this->skipTestMessage; + } +} diff --git a/vendor/symfony/maker-bundle/src/Test/MakerTestEnvironment.php b/vendor/symfony/maker-bundle/src/Test/MakerTestEnvironment.php new file mode 100644 index 0000000..b021465 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Test/MakerTestEnvironment.php @@ -0,0 +1,453 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Test; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Process\InputStream; + +/** + * @author Sadicov Vladimir + * @author Nicolas Philippe + * + * @internal + */ +final class MakerTestEnvironment +{ + // Config used for creating tmp flex project and test app's + private const GIT_CONFIG = 'git config user.name "symfony" && git config user.email "test@symfony.com" && git config commit.gpgsign false && git config user.signingkey false'; + + public const GENERATED_FILES_REGEX = '#(?:created|updated):\s(?:.*\\\\)*(.*\.[a-z]{3,4}).*(?:\\\\n)?#ui'; + + private Filesystem $fs; + private bool|string $rootPath; + private string $cachePath; + private string $flexPath; + private string $path; + private MakerTestProcess $runnedMakerProcess; + private bool $isWindows; + + private function __construct( + private MakerTestDetails $testDetails, + ) { + $this->isWindows = str_contains(strtolower(\PHP_OS), 'win'); + + $this->fs = new Filesystem(); + $this->rootPath = realpath(__DIR__.'/../../'); + $cachePath = $this->rootPath.'/tests/tmp/cache'; + + if (!$this->fs->exists($cachePath)) { + $this->fs->mkdir($cachePath); + } + + $this->cachePath = realpath($cachePath); + $targetVersion = $this->getTargetSkeletonVersion(); + $this->flexPath = $this->cachePath.'/flex_project'.$targetVersion; + + $directoryName = $targetVersion ?: 'current'; + if (str_ends_with($directoryName, '.*')) { + $directoryName = substr($directoryName, 0, -2); + } + + $this->path = $this->cachePath.\DIRECTORY_SEPARATOR.$testDetails->getUniqueCacheDirectoryName().'_'.$directoryName; + } + + public static function create(MakerTestDetails $testDetails): self + { + return new self($testDetails); + } + + public function getPath(): string + { + return $this->path; + } + + public function readFile(string $path): string + { + if (!file_exists($this->path.'/'.$path)) { + throw new \InvalidArgumentException(\sprintf('Cannot find file "%s"', $path)); + } + + return file_get_contents($this->path.'/'.$path); + } + + private function changeRootNamespaceIfNeeded(): void + { + if ('App' === ($rootNamespace = $this->testDetails->getRootNamespace())) { + return; + } + + $replacements = [ + [ + 'filename' => 'composer.json', + 'find' => '"App\\\\": "src/"', + 'replace' => '"'.$rootNamespace.'\\\\": "src/"', + ], + [ + 'filename' => 'src/Kernel.php', + 'find' => 'namespace App', + 'replace' => 'namespace '.$rootNamespace, + ], + [ + 'filename' => 'bin/console', + 'find' => 'use App\\Kernel', + 'replace' => 'use '.$rootNamespace.'\\Kernel', + ], + [ + 'filename' => 'public/index.php', + 'find' => 'use App\\Kernel', + 'replace' => 'use '.$rootNamespace.'\\Kernel', + ], + [ + 'filename' => 'config/services.yaml', + 'find' => 'App\\', + 'replace' => $rootNamespace.'\\', + ], + [ + 'filename' => '.env.test', + 'find' => 'KERNEL_CLASS=\'App\Kernel\'', + 'replace' => 'KERNEL_CLASS=\''.$rootNamespace.'\Kernel\'', + ], + ]; + + if ($this->fs->exists($this->path.'/config/packages/doctrine.yaml')) { + $replacements[] = [ + 'filename' => 'config/packages/doctrine.yaml', + 'find' => 'App', + 'replace' => $rootNamespace, + ]; + } + + $this->processReplacements($replacements, $this->path); + $this->runCommand('composer dump-autoload'); + } + + public function prepareDirectory(): void + { + // Copy MakerBundle to a "repo" directory for tests + if (!file_exists($makerRepoPath = \sprintf('%s/maker-repo', $this->cachePath))) { + MakerTestProcess::create(\sprintf('git clone %s %s', $this->rootPath, $makerRepoPath), $this->cachePath)->run(); + } + + if (!$this->fs->exists($this->flexPath)) { + $this->buildFlexSkeleton(); + } + + if (!$this->fs->exists($this->path)) { + try { + // let's do some magic here git is faster than copy + MakerTestProcess::create( + '\\' === \DIRECTORY_SEPARATOR ? 'git clone %FLEX_PATH% %APP_PATH%' : 'git clone "$FLEX_PATH" "$APP_PATH"', + \dirname($this->flexPath), + [ + 'FLEX_PATH' => $this->flexPath, + 'APP_PATH' => $this->path, + ] + ) + ->run(); + + // In Window's we have to require MakerBundle in each project - git clone doesn't symlink well + if ($this->isWindows) { + $this->composerRequireMakerBundle($this->path); + } + + // install any missing dependencies + $dependencies = $this->determineMissingDependencies(); + if ($dependencies) { + // -v actually silences the "very" verbose output in case of an error + $composerProcess = MakerTestProcess::create(\sprintf('composer require %s -v', implode(' ', $dependencies)), $this->path) + ->run(true) + ; + + if (!$composerProcess->isSuccessful()) { + throw new \Exception(\sprintf('Error running command: composer require %s -v. Output: "%s". Error: "%s"', implode(' ', $dependencies), $composerProcess->getOutput(), $composerProcess->getErrorOutput())); + } + } + + $this->changeRootNamespaceIfNeeded(); + + file_put_contents($this->path.'/.gitignore', "var/cache/\nvendor/\n"); + + MakerTestProcess::create(\sprintf('git diff --quiet || ( %s && git add . && git commit -a -m "second commit" )', self::GIT_CONFIG), + $this->path + )->run(); + } catch (\Exception $e) { + $this->fs->remove($this->path); + + throw $e; + } + } else { + MakerTestProcess::create('git reset --hard && git clean -fd', $this->path)->run(); + $this->fs->remove($this->path.'/var/cache'); + } + } + + public function runCommand(string $command): MakerTestProcess + { + return MakerTestProcess::create($command, $this->path)->run(); + } + + public function runMaker(array $inputs, string $argumentsString = '', bool $allowedToFail = false, array $envVars = []): MakerTestProcess + { + // Let's remove cache + $this->fs->remove($this->path.'/var/cache'); + + $testProcess = $this->createInteractiveCommandProcess( + commandName: $this->testDetails->getMaker()::getCommandName(), + userInputs: $inputs, + argumentsString: $argumentsString, + envVars: $envVars, + ); + + $this->runnedMakerProcess = $testProcess->run($allowedToFail); + + return $this->runnedMakerProcess; + } + + public function getGeneratedFilesFromOutputText(): array + { + $output = $this->runnedMakerProcess->getOutput(); + + $matches = []; + + preg_match_all(self::GENERATED_FILES_REGEX, $output, $matches, \PREG_PATTERN_ORDER); + + return array_map('trim', $matches[1]); + } + + public function fileExists(string $file): bool + { + return $this->fs->exists($this->path.'/'.$file); + } + + public function runTwigCSLint(string $file): MakerTestProcess + { + if (!file_exists(__DIR__.'/../../tools/twigcs/vendor/bin/twigcs')) { + throw new \Exception('twigcs not found: run: "composer upgrade -W --working-dir=tools/twigcs".'); + } + + return MakerTestProcess::create(\sprintf('php tools/twigcs/vendor/bin/twigcs --config ./tools/twigcs/.twig_cs.dist %s', $this->path.'/'.$file), $this->rootPath) + ->run(true); + } + + private function buildFlexSkeleton(): void + { + $targetVersion = $this->getTargetSkeletonVersion(); + $versionString = $targetVersion ? \sprintf(':%s', $targetVersion) : ''; + + $flexProjectDir = \sprintf('flex_project%s', $targetVersion); + + MakerTestProcess::create( + \sprintf('composer create-project symfony/skeleton%s %s --prefer-dist --no-progress', $versionString, $flexProjectDir), + $this->cachePath + )->run(); + + $rootPath = str_replace('\\', '\\\\', realpath(__DIR__.'/../..')); + + $this->addMakerBundleRepoToComposer(\sprintf('%s/%s/composer.json', $this->cachePath, $flexProjectDir)); + + // In Linux, git plays well with symlinks - we can add maker to the flex skeleton. + if (!$this->isWindows) { + $this->composerRequireMakerBundle(\sprintf('%s/%s', $this->cachePath, $flexProjectDir)); + } + + if ($_SERVER['MAKER_ALLOW_DEV_DEPS_IN_APP'] ?? false) { + MakerTestProcess::create('composer config minimum-stability dev', $this->flexPath)->run(); + MakerTestProcess::create('composer config prefer-stable true', $this->flexPath)->run(); + } + + // fetch a few packages needed for testing + MakerTestProcess::create('composer require phpunit browser-kit symfony/css-selector --prefer-dist --no-progress --no-suggest', $this->flexPath) + ->run(); + + if ('\\' !== \DIRECTORY_SEPARATOR) { + $this->fs->remove($this->flexPath.'/vendor/symfony/phpunit-bridge'); + + $this->fs->symlink($rootPath.'/vendor/symfony/phpunit-bridge', $this->flexPath.'/vendor/symfony/phpunit-bridge'); + } + + $replacements = [ + // temporarily ignoring indirect deprecations - see #237 + [ + 'filename' => '.env.test', + 'find' => 'SYMFONY_DEPRECATIONS_HELPER=999999', + 'replace' => 'SYMFONY_DEPRECATIONS_HELPER=max[self]=0', + ], + // do not explicitly set the PHPUnit version + [ + 'filename' => 'phpunit.xml.dist', + 'find' => '', + 'replace' => '', + ], + ]; + $this->processReplacements($replacements, $this->flexPath); + // end of temp code + + file_put_contents($this->flexPath.'/.gitignore', "var/cache/\n"); + + // Force adding vendor/ dir to Git repo in case users exclude it in global .gitignore + MakerTestProcess::create(\sprintf('git init && %s && git add . && git add vendor/ -f && git commit -a -m "first commit"', self::GIT_CONFIG), + $this->flexPath + )->run(); + } + + private function processReplacements(array $replacements, string $rootDir): void + { + foreach ($replacements as $replacement) { + $this->processReplacement($rootDir, $replacement['filename'], $replacement['find'], $replacement['replace']); + } + } + + public function processReplacement(string $rootDir, string $filename, string $find, string $replace, bool $allowNotFound = false): void + { + $path = realpath($rootDir.'/'.$filename); + + if (!$this->fs->exists($path)) { + if ($allowNotFound) { + return; + } + + throw new \Exception(\sprintf('Could not find file "%s" to process replacements inside "%s"', $filename, $rootDir)); + } + + $contents = file_get_contents($path); + if (!str_contains($contents, $find)) { + if ($allowNotFound) { + return; + } + + throw new \Exception(\sprintf('Could not find "%s" inside "%s"', $find, $filename)); + } + + file_put_contents($path, str_replace($find, $replace, $contents)); + } + + public function createInteractiveCommandProcess(string $commandName, array $userInputs, string $argumentsString = '', array $envVars = []): MakerTestProcess + { + $envVars = array_merge(['SHELL_INTERACTIVE' => '1'], $envVars); + + // We don't need ansi coloring in tests! + $process = MakerTestProcess::create( + commandLine: \sprintf('php bin/console %s %s --no-ansi', $commandName, $argumentsString), + cwd: $this->path, + envVars: $envVars, + timeout: 30 + ); + + if ($userInputs) { + $inputStream = new InputStream(); + + // start the command with some input + $inputStream->write(current($userInputs)."\n"); + + $inputStream->onEmpty(function () use ($inputStream, &$userInputs) { + $nextInput = next($userInputs); + if (false === $nextInput) { + $inputStream->close(); + } else { + $inputStream->write($nextInput."\n"); + } + }); + + $process->setInput($inputStream); + } + + return $process; + } + + public function getSymfonyVersionInApp(): int + { + $contents = file_get_contents($this->getPath().'/vendor/symfony/http-kernel/Kernel.php'); + $position = strpos($contents, 'VERSION_ID = '); + + return (int) substr($contents, $position + 13, 5); + } + + public function doesClassExistInApp(string $class): bool + { + $classMap = require $this->getPath().'/vendor/composer/autoload_classmap.php'; + + return isset($classMap[$class]); + } + + /** + * Executes the DependencyBuilder for the Maker command inside the + * actual project, so we know exactly what dependencies we need or + * don't need. + */ + private function determineMissingDependencies(): array + { + $depBuilder = $this->testDetails->getDependencyBuilder(); + file_put_contents($this->path.'/dep_builder', serialize($depBuilder)); + file_put_contents($this->path.'/dep_runner.php', 'getMissingDependencies(), + $depBuilder->getMissingDevDependencies() +); +echo json_encode($missingDependencies); + '); + + $process = MakerTestProcess::create('php dep_runner.php', $this->path)->run(); + $data = json_decode($process->getOutput(), true, 512, \JSON_THROW_ON_ERROR); + + unlink($this->path.'/dep_builder'); + unlink($this->path.'/dep_runner.php'); + + return array_merge($data, $this->testDetails->getExtraDependencies()); + } + + public function getTargetSkeletonVersion(): ?string + { + return $_SERVER['SYMFONY_VERSION'] ?? ''; + } + + private function composerRequireMakerBundle(string $projectDirectory): void + { + MakerTestProcess::create('composer require --dev symfony/maker-bundle', $projectDirectory) + ->run() + ; + + $makerRepoSrcPath = \sprintf('%s/maker-repo/src', $this->cachePath); + + // DX - So we can test local changes without having to commit them. + if (!is_link($makerRepoSrcPath)) { + $this->fs->remove($makerRepoSrcPath); + $this->fs->symlink(\sprintf('%s/src', $this->rootPath), $makerRepoSrcPath); + } + } + + /** + * Adds Symfony/MakerBundle as a "path" repository to composer.json. + */ + private function addMakerBundleRepoToComposer(string $composerJsonPath): void + { + $composerJson = json_decode( + file_get_contents($composerJsonPath), true, 512, \JSON_THROW_ON_ERROR); + + // Require-dev is empty and composer complains about this being an array when we encode it again. + unset($composerJson['require-dev']); + + $composerJson['repositories']['symfony/maker-bundle'] = [ + 'type' => 'path', + 'url' => \sprintf('%s%smaker-repo', $this->cachePath, \DIRECTORY_SEPARATOR), + 'options' => [ + 'versions' => [ + 'symfony/maker-bundle' => '9999.99', // Arbitrary version to avoid stability conflicts + ], + ], + ]; + + file_put_contents($composerJsonPath, json_encode($composerJson, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + } +} diff --git a/vendor/symfony/maker-bundle/src/Test/MakerTestKernel.php b/vendor/symfony/maker-bundle/src/Test/MakerTestKernel.php new file mode 100644 index 0000000..1938dfe --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Test/MakerTestKernel.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Test; + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\MakeCommandRegistrationPass; +use Symfony\Bundle\MakerBundle\MakerBundle; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + +class MakerTestKernel extends Kernel implements CompilerPassInterface +{ + use MicroKernelTrait; + + private string $testRootDir; + + public function __construct(string $environment, bool $debug) + { + $this->testRootDir = sys_get_temp_dir().'/'.uniqid('sf_maker_', true); + + parent::__construct($environment, $debug); + } + + public function registerBundles(): iterable + { + return [ + new FrameworkBundle(), + new MakerBundle(), + ]; + } + + protected function configureRoutes(RoutingConfigurator $routes) + { + } + + protected function configureRouting(RoutingConfigurator $routes) + { + } + + protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) + { + $c->loadFromExtension('framework', [ + 'secret' => 123, + 'router' => [ + 'utf8' => true, + ], + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => [ + 'log' => true, + ], + ]); + } + + public function getProjectDir(): string + { + return $this->getRootDir(); + } + + public function getRootDir(): string + { + return $this->testRootDir; + } + + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + // makes all makers public to help the tests + foreach ($container->findTaggedServiceIds(MakeCommandRegistrationPass::MAKER_TAG) as $id => $tags) { + $defn = $container->getDefinition($id); + $defn->setPublic(true); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/Test/MakerTestProcess.php b/vendor/symfony/maker-bundle/src/Test/MakerTestProcess.php new file mode 100644 index 0000000..5d124c9 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Test/MakerTestProcess.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Test; + +use Symfony\Component\Process\Process; + +/** + * @author Sadicov Vladimir + * + * @internal + */ +final class MakerTestProcess +{ + private Process $process; + + private function __construct($commandLine, $cwd, array $envVars, $timeout) + { + $this->process = \is_string($commandLine) + ? Process::fromShellCommandline($commandLine, $cwd, null, null, $timeout) + : new Process($commandLine, $cwd, null, null, $timeout); + + $this->process->setEnv($envVars); + } + + public static function create($commandLine, $cwd, array $envVars = [], $timeout = null): self + { + return new self($commandLine, $cwd, $envVars, $timeout); + } + + public function setInput($input): self + { + $this->process->setInput($input); + + return $this; + } + + public function run($allowToFail = false, array $envVars = []): self + { + if (false !== ($timeout = getenv('MAKER_PROCESS_TIMEOUT'))) { + if ('null' === $timeout) { + $timeout = null; + } + + // Setting a value of null allows for step debugging + $this->process->setTimeout($timeout); + } + + $this->process->run(null, $envVars); + + if (!$allowToFail && !$this->process->isSuccessful()) { + throw new \Exception(\sprintf('Error running command: "%s". Output: "%s". Error: "%s"', $this->process->getCommandLine(), $this->process->getOutput(), $this->process->getErrorOutput())); + } + + return $this; + } + + public function isSuccessful(): bool + { + return $this->process->isSuccessful(); + } + + public function getOutput(): string + { + return $this->process->getOutput(); + } + + public function getErrorOutput(): string + { + return $this->process->getErrorOutput(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Test/MakerTestRunner.php b/vendor/symfony/maker-bundle/src/Test/MakerTestRunner.php new file mode 100644 index 0000000..0eb07a3 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Test/MakerTestRunner.php @@ -0,0 +1,266 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Test; + +use PHPUnit\Framework\ExpectationFailedException; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Yaml\Yaml; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; + +class MakerTestRunner +{ + private Filesystem $filesystem; + private ?MakerTestProcess $executedMakerProcess = null; + + public function __construct( + private MakerTestEnvironment $environment, + ) { + $this->filesystem = new Filesystem(); + } + + public function runMaker(array $inputs, string $argumentsString = '', bool $allowedToFail = false, array $envVars = []): string + { + $this->executedMakerProcess = $this->environment->runMaker($inputs, $argumentsString, $allowedToFail, $envVars); + + return $this->executedMakerProcess->getOutput(); + } + + /** + * @return void + */ + public function copy(string $source, string $destination) + { + $path = __DIR__.'/../../tests/fixtures/'.$source; + + if (!file_exists($path)) { + throw new \Exception(\sprintf('Cannot find file "%s"', $path)); + } + + if (is_file($path)) { + $this->filesystem->copy($path, $this->getPath($destination), true); + + return; + } + + // handle a directory copy + $finder = new Finder(); + $finder->in($path)->files(); + + foreach ($finder as $file) { + $this->filesystem->copy($file->getPathname(), $this->getPath($file->getRelativePathname()), true); + } + } + + public function renderTemplateFile(string $source, string $destination, array $variables): void + { + $twig = new Environment( + new FilesystemLoader(__DIR__.'/../../tests/fixtures') + ); + + $rendered = $twig->render($source, $variables); + + $this->filesystem->mkdir(\dirname($this->getPath($destination))); + file_put_contents($this->getPath($destination), $rendered); + } + + public function getPath(string $filename): string + { + return $this->environment->getPath().'/'.$filename; + } + + public function readYaml(string $filename): array + { + return Yaml::parse(file_get_contents($this->getPath($filename))); + } + + public function getExecutedMakerProcess(): MakerTestProcess + { + if (!$this->executedMakerProcess) { + throw new \Exception('Maker process has not been executed yet.'); + } + + return $this->executedMakerProcess; + } + + /** + * @return void + */ + public function modifyYamlFile(string $filename, \Closure $callback) + { + $path = $this->getPath($filename); + $manipulator = new YamlSourceManipulator(file_get_contents($path)); + + $newData = $callback($manipulator->getData()); + if (!\is_array($newData)) { + throw new \Exception('The modifyYamlFile() callback must return the final array of data'); + } + $manipulator->setData($newData); + + file_put_contents($path, $manipulator->getContents()); + } + + /** + * @return void + */ + public function runConsole(string $command, array $inputs, string $arguments = '') + { + $process = $this->environment->createInteractiveCommandProcess( + $command, + $inputs, + $arguments + ); + + $process->run(); + } + + public function runProcess(string $command): void + { + MakerTestProcess::create($command, $this->environment->getPath())->run(); + } + + public function replaceInFile(string $filename, string $find, string $replace, bool $allowNotFound = false): void + { + $this->environment->processReplacement( + $this->environment->getPath(), + $filename, + $find, + $replace, + $allowNotFound + ); + } + + public function removeFromFile(string $filename, string $find, bool $allowNotFound = false): void + { + $this->environment->processReplacement( + $this->environment->getPath(), + $filename, + $find, + '', + $allowNotFound + ); + } + + public function configureDatabase(bool $createSchema = true): void + { + $this->replaceInFile( + '.env', + 'postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8', + getenv('TEST_DATABASE_DSN') + ); + + // Flex includes a recipe to suffix the dbname w/ "_test" - lets keep + // things simple for these tests and not do that. + $this->modifyYamlFile('config/packages/doctrine.yaml', function (array $config) { + if (isset($config['when@test']['doctrine']['dbal']['dbname_suffix'])) { + unset($config['when@test']['doctrine']['dbal']['dbname_suffix']); + } + + return $config; + }); + + // this looks silly, but it's the only way to drop the database *for sure*, + // as doctrine:database:drop will error if there is no database + if (!$usingSqlite = str_starts_with(getenv('TEST_DATABASE_DSN'), 'sqlite')) { + // --if-not-exists not supported on SQLite + $this->runConsole('doctrine:database:create', [], '--env=test --if-not-exists'); + } + + $this->runConsole('doctrine:database:drop', [], '--env=test --force'); + + if (!$usingSqlite) { + // d:d:create not supported on SQLite + $this->runConsole('doctrine:database:create', [], '--env=test'); + } + + if ($createSchema) { + $this->runConsole('doctrine:schema:create', [], '--env=test'); + } + } + + public function updateSchema(): void + { + $this->runConsole('doctrine:schema:update', [], '--env=test --force'); + } + + public function runTests(): void + { + $internalTestProcess = MakerTestProcess::create( + \sprintf('php %s', $this->getPath('/bin/phpunit')), + $this->environment->getPath()) + ->run(true) + ; + + if ($internalTestProcess->isSuccessful()) { + return; + } + + throw new ExpectationFailedException(\sprintf("Error while running the PHPUnit tests *in* the project: \n\n %s \n\n Command Output: %s", $internalTestProcess->getErrorOutput()."\n".$internalTestProcess->getOutput(), $this->getExecutedMakerProcess()->getErrorOutput()."\n".$this->getExecutedMakerProcess()->getOutput())); + } + + public function writeFile(string $filename, string $contents): void + { + $this->filesystem->mkdir(\dirname($this->getPath($filename))); + file_put_contents($this->getPath($filename), $contents); + } + + /** + * @return void + */ + public function addToAutoloader(string $namespace, string $path) + { + $composerJson = json_decode( + json: file_get_contents($this->getPath('composer.json')), + associative: true, + flags: \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES + ); + + $composerJson['autoload-dev']['psr-4'][$namespace] = $path; + + $this->filesystem->dumpFile( + $this->getPath('composer.json'), + json_encode($composerJson, \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT | \JSON_THROW_ON_ERROR) + ); + + $this->environment->runCommand('composer dump-autoload'); + } + + public function deleteFile(string $filename): void + { + $this->filesystem->remove($this->getPath($filename)); + } + + public function manipulateClass(string $filename, \Closure $callback): void + { + $contents = file_get_contents($this->getPath($filename)); + $manipulator = new ClassSourceManipulator( + sourceCode: $contents, + overwrite: true, + ); + $callback($manipulator); + + file_put_contents($this->getPath($filename), $manipulator->getSourceCode()); + } + + public function getSymfonyVersion(): int + { + return $this->environment->getSymfonyVersionInApp(); + } + + public function doesClassExist(string $class): bool + { + return $this->environment->doesClassExistInApp($class); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/AutoloaderUtil.php b/vendor/symfony/maker-bundle/src/Util/AutoloaderUtil.php new file mode 100644 index 0000000..6265551 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/AutoloaderUtil.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Composer\Autoload\ClassLoader; + +/** + * @author Ryan Weaver + * + * @internal + */ +class AutoloaderUtil +{ + public function __construct( + private ComposerAutoloaderFinder $autoloaderFinder, + ) { + } + + /** + * Returns the relative path to where a new class should live. + * + * @throws \Exception + */ + public function getPathForFutureClass(string $className): ?string + { + $classLoader = $this->getClassLoader(); + + // lookup is obviously modeled off of Composer's autoload logic + foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) { + if (str_starts_with($className, $prefix)) { + return $paths[0].'/'.str_replace('\\', '/', substr($className, \strlen($prefix))).'.php'; + } + } + + foreach ($classLoader->getPrefixes() as $prefix => $paths) { + if (str_starts_with($className, $prefix)) { + return $paths[0].'/'.str_replace('\\', '/', $className).'.php'; + } + } + + if ($classLoader->getFallbackDirsPsr4()) { + return $classLoader->getFallbackDirsPsr4()[0].'/'.str_replace('\\', '/', $className).'.php'; + } + + if ($classLoader->getFallbackDirs()) { + return $classLoader->getFallbackDirs()[0].'/'.str_replace('\\', '/', $className).'.php'; + } + + return null; + } + + public function getNamespacePrefixForClass(string $className): string + { + foreach ($this->getClassLoader()->getPrefixesPsr4() as $prefix => $paths) { + if (str_starts_with($className, $prefix)) { + return $prefix; + } + } + + return ''; + } + + /** + * Returns if the namespace is configured by composer autoloader. + */ + public function isNamespaceConfiguredToAutoload(string $namespace): bool + { + $namespace = trim($namespace, '\\').'\\'; + $classLoader = $this->getClassLoader(); + + foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) { + if (str_starts_with($namespace, $prefix)) { + return true; + } + } + + foreach ($classLoader->getPrefixes() as $prefix => $paths) { + if (str_starts_with($namespace, $prefix)) { + return true; + } + } + + return false; + } + + private function getClassLoader(): ClassLoader + { + return $this->autoloaderFinder->getClassLoader(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ClassDetails.php b/vendor/symfony/maker-bundle/src/Util/ClassDetails.php new file mode 100644 index 0000000..1bfd7c2 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ClassDetails.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +/** + * @internal + */ +final class ClassDetails +{ + public function __construct( + private string $fullClassName, + ) { + } + + /** + * Get list of property names except "id" for use in a make:form context. + */ + public function getFormFields(): array + { + $properties = $this->getProperties(); + + $fields = array_diff($properties, ['id']); + + $fieldsWithTypes = []; + foreach ($fields as $field) { + $fieldsWithTypes[$field] = null; + } + + return $fieldsWithTypes; + } + + private function getProperties(): array + { + $reflect = new \ReflectionClass($this->fullClassName); + $props = $reflect->getProperties(); + + $propertiesList = []; + + foreach ($props as $prop) { + $propertiesList[] = $prop->getName(); + } + + return $propertiesList; + } + + public function getPath(): string + { + return (new \ReflectionClass($this->fullClassName))->getFileName(); + } + + public function hasAttribute(string $attributeClassName): bool + { + $reflected = new \ReflectionClass($this->fullClassName); + + foreach ($reflected->getAttributes($attributeClassName) as $reflectedAttribute) { + if ($reflectedAttribute->getName() === $attributeClassName) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ClassNameDetails.php b/vendor/symfony/maker-bundle/src/Util/ClassNameDetails.php new file mode 100644 index 0000000..3372c74 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ClassNameDetails.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Symfony\Bundle\MakerBundle\Str; + +final class ClassNameDetails +{ + public function __construct( + private string $fullClassName, + private string $namespacePrefix, + private ?string $suffix = null, + ) { + $this->namespacePrefix = trim($namespacePrefix, '\\'); + } + + public function getFullName(): string + { + return $this->fullClassName; + } + + public function getShortName(): string + { + return Str::getShortClassName($this->fullClassName); + } + + /** + * Returns the original class name the user entered (after + * being cleaned up). + * + * For example, assuming the namespace is App\Entity: + * App\Entity\Admin\User => Admin\User + */ + public function getRelativeName(): string + { + return str_replace($this->namespacePrefix.'\\', '', $this->fullClassName); + } + + public function getRelativeNameWithoutSuffix(): string + { + return Str::removeSuffix($this->getRelativeName(), $this->suffix); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ClassNameValue.php b/vendor/symfony/maker-bundle/src/Util/ClassNameValue.php new file mode 100644 index 0000000..72d87b9 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ClassNameValue.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Symfony\Bundle\MakerBundle\Str; + +/** + * @internal + */ +final class ClassNameValue implements \Stringable +{ + public function __construct( + private string $typeHint, + private string $fullClassName, + ) { + } + + public function getShortName(): string + { + if ($this->isSelf()) { + return Str::getShortClassName($this->fullClassName); + } + + return $this->typeHint; + } + + public function isSelf(): bool + { + return 'self' === $this->typeHint; + } + + public function __toString(): string + { + return $this->getShortName(); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ClassSource/Model/ClassData.php b/vendor/symfony/maker-bundle/src/Util/ClassSource/Model/ClassData.php new file mode 100644 index 0000000..988404f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ClassSource/Model/ClassData.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util\ClassSource\Model; + +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class ClassData +{ + private function __construct( + private string $className, + private string $namespace, + public readonly ?string $extends, + public readonly bool $isEntity, + private UseStatementGenerator $useStatementGenerator, + private bool $isFinal = true, + private string $rootNamespace = 'App', + ) { + } + + public static function create(string $class, ?string $suffix = null, ?string $extendsClass = null, bool $isEntity = false, array $useStatements = []): self + { + $className = Str::getShortClassName($class); + + if (null !== $suffix && !str_ends_with($className, $suffix)) { + $className = Str::asClassName(\sprintf('%s%s', $className, $suffix)); + } + + $useStatements = new UseStatementGenerator($useStatements); + + if ($extendsClass) { + $useStatements->addUseStatement($extendsClass); + } + + return new self( + className: Str::asClassName($className), + namespace: Str::getNamespace($class), + extends: null === $extendsClass ? null : Str::getShortClassName($extendsClass), + isEntity: $isEntity, + useStatementGenerator: $useStatements, + ); + } + + public function getClassName(): string + { + return $this->className; + } + + public function getNamespace(): string + { + if (empty($this->namespace)) { + return $this->rootNamespace; + } + + return \sprintf('%s\%s', $this->rootNamespace, $this->namespace); + } + + public function getFullClassName(): string + { + return \sprintf('%s\%s', $this->getNamespace(), $this->className); + } + + public function setRootNamespace(string $rootNamespace): self + { + $this->rootNamespace = $rootNamespace; + + return $this; + } + + public function getClassDeclaration(): string + { + $extendsDeclaration = ''; + + if (null !== $this->extends) { + $extendsDeclaration = \sprintf(' extends %s', $this->extends); + } + + return \sprintf('%sclass %s%s', + $this->isFinal ? 'final ' : '', + $this->className, + $extendsDeclaration, + ); + } + + public function setIsFinal(bool $isFinal): self + { + $this->isFinal = $isFinal; + + return $this; + } + + public function addUseStatement(array|string $useStatement): self + { + $this->useStatementGenerator->addUseStatement($useStatement); + + return $this; + } + + public function getUseStatements(): string + { + return (string) $this->useStatementGenerator; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ClassSource/Model/ClassProperty.php b/vendor/symfony/maker-bundle/src/Util/ClassSource/Model/ClassProperty.php new file mode 100644 index 0000000..a57406d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ClassSource/Model/ClassProperty.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util\ClassSource\Model; + +use Doctrine\ORM\Mapping\FieldMapping; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class ClassProperty +{ + public function __construct( + public string $propertyName, + public string $type, + public array $comments = [], + public ?int $length = null, + public ?bool $id = null, + public ?bool $nullable = null, + public array $options = [], + public ?int $precision = null, + public ?int $scale = null, + public bool $needsTypeHint = true, + public bool $unique = false, + public ?string $enumType = null, + ) { + } + + public function getAttributes(): array + { + $attributes = []; + + if ($this->needsTypeHint) { + $attributes['type'] = $this->type; + } + + if (!empty($this->options)) { + $attributes['options'] = $this->options; + } + + if ($this->unique) { + $attributes['unique'] = true; + } + + if ($this->enumType) { + $attributes['enumType'] = $this->enumType; + } + + foreach (['length', 'id', 'nullable', 'precision', 'scale'] as $property) { + if (null !== $this->$property) { + $attributes[$property] = $this->$property; + } + } + + return $attributes; + } + + public static function createFromObject(FieldMapping|array $data): self + { + if ($data instanceof FieldMapping) { + return new self( + propertyName: $data->fieldName, + type: $data->type, + length: $data->length, + id: $data->id ?? false, + nullable: $data->nullable ?? false, + options: $data->options ?? [], + precision: $data->precision, + scale: $data->scale, + unique: $data->unique ?? false, + enumType: $data->enumType, + ); + } + + /* @legacy Remove when ORM 2.x is no longer supported. */ + if (empty($data['fieldName']) || empty($data['type'])) { + throw new RuntimeCommandException('Cannot create property model - "fieldName" & "type" are required.'); + } + + return new self( + propertyName: $data['fieldName'], + type: $data['type'], + comments: $data['comments'] ?? [], + length: $data['length'] ?? null, + id: $data['id'] ?? false, + nullable: $data['nullable'] ?? false, + options: $data['options'] ?? [], + precision: $data['precision'] ?? null, + scale: $data['scale'] ?? null, + unique: $data['unique'] ?? false, + enumType: $data['enumType'] ?? null, + ); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ClassSourceManipulator.php b/vendor/symfony/maker-bundle/src/Util/ClassSourceManipulator.php new file mode 100644 index 0000000..25b57bd --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ClassSourceManipulator.php @@ -0,0 +1,1420 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Embedded; +use Doctrine\ORM\Mapping\JoinColumn; +use Doctrine\ORM\Mapping\ManyToMany; +use Doctrine\ORM\Mapping\ManyToOne; +use Doctrine\ORM\Mapping\OneToMany; +use Doctrine\ORM\Mapping\OneToOne; +use PhpParser\Builder; +use PhpParser\BuilderHelpers; +use PhpParser\Lexer; +use PhpParser\Node; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; +use PhpParser\Parser; +use PhpParser\PhpVersion; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\Doctrine\BaseCollectionRelation; +use Symfony\Bundle\MakerBundle\Doctrine\BaseRelation; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToMany; +use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToOne; +use Symfony\Bundle\MakerBundle\Doctrine\RelationOneToMany; +use Symfony\Bundle\MakerBundle\Doctrine\RelationOneToOne; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty; + +/** + * @internal + */ +final class ClassSourceManipulator +{ + private const CONTEXT_OUTSIDE_CLASS = 'outside_class'; + private const CONTEXT_CLASS = 'class'; + private const CONTEXT_CLASS_METHOD = 'class_method'; + private const DEFAULT_VALUE_NONE = '__default_value_none'; + + private Parser $parser; + private Lexer\Emulative $lexer; + private PrettyPrinter $printer; + private ?ConsoleStyle $io = null; + + private ?array $oldStmts = null; + private array $oldTokens = []; + private array $newStmts = []; + + private array $pendingComments = []; + + public function __construct( + private string $sourceCode, + private bool $overwrite = false, + private bool $useAttributesForDoctrineMapping = true, + ) { + /* @legacy Support for nikic/php-parser v4 */ + if (class_exists(PhpVersion::class)) { + $version = PhpVersion::fromString(\PHP_VERSION); + $this->lexer = new Lexer\Emulative($version); + $this->parser = new Parser\Php8($this->lexer, $version); + } else { + $this->lexer = new Lexer\Emulative([ + 'usedAttributes' => [ + 'comments', + 'startLine', 'endLine', + 'startTokenPos', 'endTokenPos', + ], + ]); + $this->parser = new Parser\Php7($this->lexer); + } + + $this->printer = new PrettyPrinter(); + + $this->setSourceCode($sourceCode); + } + + public function setIo(ConsoleStyle $io): void + { + $this->io = $io; + } + + public function getSourceCode(): string + { + return $this->sourceCode; + } + + public function addEntityField(ClassProperty $mapping): void + { + $typeHint = DoctrineHelper::getPropertyTypeForColumn($mapping->type); + if ($typeHint && DoctrineHelper::canColumnTypeBeInferredByPropertyType($mapping->type, $typeHint)) { + $mapping->needsTypeHint = false; + } + + if ($mapping->needsTypeHint) { + $typeConstant = DoctrineHelper::getTypeConstant($mapping->type); + if ($typeConstant) { + $this->addUseStatementIfNecessary(Types::class); + $mapping->type = $typeConstant; + } + } + + // 2) USE property type on property below, nullable + // 3) If default value, then NOT nullable + + $nullable = $mapping->nullable ?? false; + + $attributes[] = $this->buildAttributeNode(Column::class, $mapping->getAttributes(), 'ORM'); + + $defaultValue = null; + $commentLines = []; + + if (null !== $mapping->enumType) { + if ('array' === $typeHint) { + // still need to add the use statement + $this->addUseStatementIfNecessary($mapping->enumType); + + $commentLines = [\sprintf('@return %s[]', Str::getShortClassName($mapping->enumType))]; + if ($nullable) { + $commentLines[0] = \sprintf('%s|null', $commentLines[0]); + } else { + $defaultValue = new Node\Expr\Array_([], ['kind' => Node\Expr\Array_::KIND_SHORT]); + } + } else { + $typeHint = $this->addUseStatementIfNecessary($mapping->enumType); + } + } elseif ('array' === $typeHint && !$nullable) { + $defaultValue = new Node\Expr\Array_([], ['kind' => Node\Expr\Array_::KIND_SHORT]); + } elseif ($typeHint && '\\' === $typeHint[0] && false !== strpos($typeHint, '\\', 1)) { + $typeHint = $this->addUseStatementIfNecessary(substr($typeHint, 1)); + } + + $propertyType = $typeHint; + if ($propertyType && !$defaultValue) { + // all property types + $propertyType = '?'.$propertyType; + } + + $this->addProperty( + name: $mapping->propertyName, + defaultValue: $defaultValue, + attributes: $attributes, + comments: $mapping->comments, + propertyType: $propertyType + ); + + $this->addGetter( + $mapping->propertyName, + $typeHint, + // getter methods always have nullable return values + // because even though these are required in the db, they may not be set yet + // unless there is a default value + null === $defaultValue, + $commentLines + ); + + // don't generate setters for id fields + if (!($mapping->id ?? false)) { + $this->addSetter($mapping->propertyName, $typeHint, $nullable); + } + } + + public function addEmbeddedEntity(string $propertyName, string $className): void + { + $typeHint = $this->addUseStatementIfNecessary($className); + + $attributes = [ + $this->buildAttributeNode( + Embedded::class, + ['class' => new ClassNameValue($className, $typeHint)], + 'ORM' + ), + ]; + + $this->addProperty( + name: $propertyName, + attributes: $attributes, + propertyType: $typeHint, + ); + + // logic to avoid re-adding the same ArrayCollection line + $addEmbedded = true; + if ($this->getConstructorNode()) { + // We print the constructor to a string, then + // look for "$this->propertyName = " + + $constructorString = $this->printer->prettyPrint([$this->getConstructorNode()]); + if (str_contains($constructorString, \sprintf('$this->%s = ', $propertyName))) { + $addEmbedded = false; + } + } + + if ($addEmbedded) { + $this->addStatementToConstructor( + new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $propertyName), + new Node\Expr\New_(new Node\Name($typeHint)) + )) + ); + } + + $this->addGetter($propertyName, $typeHint, false); + $this->addSetter($propertyName, $typeHint, false); + } + + public function addManyToOneRelation(RelationManyToOne $manyToOne): void + { + $this->addSingularRelation($manyToOne); + } + + public function addOneToOneRelation(RelationOneToOne $oneToOne): void + { + $this->addSingularRelation($oneToOne); + } + + public function addOneToManyRelation(RelationOneToMany $oneToMany): void + { + $this->addCollectionRelation($oneToMany); + } + + public function addManyToManyRelation(RelationManyToMany $manyToMany): void + { + $this->addCollectionRelation($manyToMany); + } + + public function addInterface(string $interfaceName): void + { + $this->addUseStatementIfNecessary($interfaceName); + + $this->getClassNode()->implements[] = new Node\Name(Str::getShortClassName($interfaceName)); + $this->updateSourceCodeFromNewStmts(); + } + + /** + * @param string $trait the fully-qualified trait name + */ + public function addTrait(string $trait): void + { + $importedClassName = $this->addUseStatementIfNecessary($trait); + + /** @var Node\Stmt\TraitUse[] $traitNodes */ + $traitNodes = $this->findAllNodes(fn ($node) => $node instanceof Node\Stmt\TraitUse); + + foreach ($traitNodes as $node) { + if ($node->traits[0]->toString() === $importedClassName) { + return; + } + } + + $traitNodes[] = new Node\Stmt\TraitUse([new Node\Name($importedClassName)]); + + $classNode = $this->getClassNode(); + + if (!empty($classNode->stmts) && 1 === \count($traitNodes)) { + $traitNodes[] = $this->createBlankLineNode(self::CONTEXT_CLASS); + } + + // avoid all the use traits in class for unshift all the new UseTrait + // in the right order. + foreach ($classNode->stmts as $key => $node) { + if ($node instanceof Node\Stmt\TraitUse) { + unset($classNode->stmts[$key]); + } + } + + array_unshift($classNode->stmts, ...$traitNodes); + + $this->updateSourceCodeFromNewStmts(); + } + + public function addAccessorMethod(string $propertyName, string $methodName, $returnType, bool $isReturnTypeNullable, array $commentLines = [], $typeCast = null): void + { + $this->addCustomGetter($propertyName, $methodName, $returnType, $isReturnTypeNullable, $commentLines, $typeCast); + } + + public function addGetter(string $propertyName, $returnType, bool $isReturnTypeNullable, array $commentLines = []): void + { + $methodName = $this->getGetterName($propertyName, $returnType); + $this->addCustomGetter($propertyName, $methodName, $returnType, $isReturnTypeNullable, $commentLines); + } + + private function getGetterName(string $propertyName, $returnType): string + { + if ('bool' !== $returnType) { + return 'get'.Str::asCamelCase($propertyName); + } + + // exclude is & has from getter definition if already in property name + if (0 !== strncasecmp($propertyName, 'is', 2) && 0 !== strncasecmp($propertyName, 'has', 3)) { + return 'is'.Str::asCamelCase($propertyName); + } + + return Str::asLowerCamelCase($propertyName); + } + + public function addSetter(string $propertyName, ?string $type, bool $isNullable, array $commentLines = []): void + { + $builder = $this->createSetterNodeBuilder($propertyName, $type, $isNullable, $commentLines); + $builder->addStmt( + new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $propertyName), + new Node\Expr\Variable($propertyName) + )) + ); + $this->makeMethodFluent($builder); + $this->addMethod($builder->getNode()); + } + + /** + * @param Node[] $params + */ + public function addConstructor(array $params, string $methodBody): void + { + if (null !== $this->getConstructorNode()) { + throw new \LogicException('Constructor already exists.'); + } + + $methodBuilder = $this->createMethodBuilder('__construct', null, false); + + $this->addMethodParams($methodBuilder, $params); + + $this->addMethodBody($methodBuilder, $methodBody); + + $this->addNodeAfterProperties($methodBuilder->getNode()); + $this->updateSourceCodeFromNewStmts(); + } + + /** + * @param Node[] $params + */ + public function addMethodBuilder(Builder\Method $methodBuilder, array $params = [], ?string $methodBody = null): void + { + $this->addMethodParams($methodBuilder, $params); + + if ($methodBody) { + $this->addMethodBody($methodBuilder, $methodBody); + } + + $this->addMethod($methodBuilder->getNode()); + } + + public function addMethodBody(Builder\Method $methodBuilder, string $methodBody): void + { + $nodes = $this->parser->parse($methodBody); + $methodBuilder->addStmts($nodes); + } + + public function createMethodBuilder(string $methodName, $returnType, bool $isReturnTypeNullable, array $commentLines = []): Builder\Method + { + $methodNodeBuilder = (new Builder\Method($methodName)) + ->makePublic(); + + if (null !== $returnType) { + if (class_exists($returnType) || interface_exists($returnType)) { + $returnType = $this->addUseStatementIfNecessary($returnType); + } + $methodNodeBuilder->setReturnType($isReturnTypeNullable ? new Node\NullableType(new Node\Identifier($returnType)) : $returnType); + } + + if ($commentLines) { + $methodNodeBuilder->setDocComment($this->createDocBlock($commentLines)); + } + + return $methodNodeBuilder; + } + + public function createMethodLevelCommentNode(string $comment) + { + return $this->createSingleLineCommentNode($comment, self::CONTEXT_CLASS_METHOD); + } + + public function createMethodLevelBlankLine() + { + return $this->createBlankLineNode(self::CONTEXT_CLASS_METHOD); + } + + /** + * @param array $attributes + */ + public function addProperty(string $name, $defaultValue = self::DEFAULT_VALUE_NONE, array $attributes = [], array $comments = [], ?string $propertyType = null): void + { + if ($this->propertyExists($name)) { + // we never overwrite properties + return; + } + + $newPropertyBuilder = (new Builder\Property($name))->makePrivate(); + + if (null !== $propertyType) { + $newPropertyBuilder->setType($propertyType); + } + + if ($this->useAttributesForDoctrineMapping) { + foreach ($attributes as $attribute) { + $newPropertyBuilder->addAttribute($attribute); + } + } + + if ($comments) { + $newPropertyBuilder->setDocComment($this->createDocBlock($comments)); + } + + if (self::DEFAULT_VALUE_NONE !== $defaultValue) { + $newPropertyBuilder->setDefault($defaultValue); + } + $newPropertyNode = $newPropertyBuilder->getNode(); + + $this->addNodeAfterProperties($newPropertyNode); + } + + public function addAttributeToClass(string $attributeClass, array $options): void + { + $this->addUseStatementIfNecessary($attributeClass); + + $classNode = $this->getClassNode(); + + $attributePrefix = str_starts_with($attributeClass, 'ORM\\') ? 'ORM' : null; + + $classNode->attrGroups[] = new Node\AttributeGroup([$this->buildAttributeNode($attributeClass, $options, $attributePrefix)]); + + $this->updateSourceCodeFromNewStmts(); + } + + private function addCustomGetter(string $propertyName, string $methodName, $returnType, bool $isReturnTypeNullable, array $commentLines = [], $typeCast = null): void + { + $propertyFetch = new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $propertyName); + + if (null !== $typeCast) { + switch ($typeCast) { + case 'string': + $propertyFetch = new Node\Expr\Cast\String_($propertyFetch); + break; + default: + // implement other cases if/when the library needs them + throw new \Exception('Not implemented'); + } + } + + $getterNodeBuilder = (new Builder\Method($methodName)) + ->makePublic() + ->addStmt( + new Node\Stmt\Return_($propertyFetch) + ); + + if (null !== $returnType) { + $getterNodeBuilder->setReturnType($isReturnTypeNullable ? new Node\NullableType(new Node\Identifier($returnType)) : $returnType); + } + + if ($commentLines) { + $getterNodeBuilder->setDocComment($this->createDocBlock($commentLines)); + } + + $this->addMethod($getterNodeBuilder->getNode()); + } + + private function createSetterNodeBuilder(string $propertyName, $type, bool $isNullable, array $commentLines = []): Builder\Method + { + $methodName = $this->getSetterName($propertyName, $type); + $setterNodeBuilder = (new Builder\Method($methodName))->makePublic(); + + if ($commentLines) { + $setterNodeBuilder->setDocComment($this->createDocBlock($commentLines)); + } + + $paramBuilder = new Builder\Param($propertyName); + if (null !== $type) { + $paramBuilder->setType($isNullable ? new Node\NullableType(new Node\Identifier($type)) : $type); + } + $setterNodeBuilder->addParam($paramBuilder->getNode()); + + return $setterNodeBuilder; + } + + private function getSetterName(string $propertyName, $type): string + { + if ('bool' === $type && 0 === strncasecmp($propertyName, 'is', 2)) { + return 'set'.Str::asCamelCase(substr($propertyName, 2)); + } + + return 'set'.Str::asCamelCase($propertyName); + } + + private function addSingularRelation(BaseRelation $relation): void + { + $typeHint = $this->addUseStatementIfNecessary($relation->getTargetClassName()); + if ($relation->getTargetClassName() === $this->getThisFullClassName()) { + $typeHint = 'self'; + } + + $annotationOptions = []; + if ($relation->isOwning()) { + // sometimes, we don't map the inverse relation + if ($relation->getMapInverseRelation()) { + $annotationOptions['inversedBy'] = $relation->getTargetPropertyName(); + } + } else { + $annotationOptions['mappedBy'] = $relation->getTargetPropertyName(); + } + + if ('self' === $typeHint) { + // Doctrine does not currently resolve "self" correctly for targetEntity guessing + $annotationOptions['targetEntity'] = new ClassNameValue($typeHint, $relation->getTargetClassName()); + } + + if ($relation instanceof RelationOneToOne) { + $annotationOptions['cascade'] = ['persist', 'remove']; + } + + $attributes = [ + $this->buildAttributeNode( + $relation instanceof RelationManyToOne ? ManyToOne::class : OneToOne::class, + $annotationOptions, + 'ORM' + ), + ]; + + if (!$relation->isNullable() && $relation->isOwning()) { + $attributes[] = $this->buildAttributeNode(JoinColumn::class, ['nullable' => false], 'ORM'); + } + + $this->addProperty( + name: $relation->getPropertyName(), + defaultValue: null, + attributes: $attributes, + propertyType: '?'.$typeHint, + ); + + $this->addGetter( + $relation->getPropertyName(), + $relation->getCustomReturnType() ?? $typeHint, + // getter methods always have nullable return values + // unless this has been customized explicitly + !$relation->getCustomReturnType() || $relation->isCustomReturnTypeNullable() + ); + + if ($relation->shouldAvoidSetter()) { + return; + } + + $setterNodeBuilder = $this->createSetterNodeBuilder( + $relation->getPropertyName(), + $typeHint, + // make the type-hint nullable always for ManyToOne to allow the owning + // side to be set to null, which is needed for orphanRemoval + // (specifically: when you set the inverse side, the generated + // code will *also* set the owning side to null - so it needs to be allowed) + // e.g. $userAvatarPhoto->setUser(null); + $relation instanceof RelationOneToOne ? $relation->isNullable() : true + ); + + // set the *owning* side of the relation + // OneToOne is the only "singular" relation type that + // may be the inverse side + if ($relation instanceof RelationOneToOne && !$relation->isOwning()) { + $this->addNodesToSetOtherSideOfOneToOne($relation, $setterNodeBuilder); + } + + $setterNodeBuilder->addStmt( + new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $relation->getPropertyName()), + new Node\Expr\Variable($relation->getPropertyName()) + )) + ); + $this->makeMethodFluent($setterNodeBuilder); + $this->addMethod($setterNodeBuilder->getNode()); + } + + private function addCollectionRelation(BaseCollectionRelation $relation): void + { + $typeHint = $relation->isSelfReferencing() ? 'self' : $this->addUseStatementIfNecessary($relation->getTargetClassName()); + + $arrayCollectionTypeHint = $this->addUseStatementIfNecessary(ArrayCollection::class); + $collectionTypeHint = $this->addUseStatementIfNecessary(Collection::class); + + $annotationOptions = [ + 'targetEntity' => new ClassNameValue($typeHint, $relation->getTargetClassName()), + ]; + if ($relation->isOwning()) { + // sometimes, we don't map the inverse relation + if ($relation->getMapInverseRelation()) { + $annotationOptions['inversedBy'] = $relation->getTargetPropertyName(); + } + } else { + $annotationOptions['mappedBy'] = $relation->getTargetPropertyName(); + } + + if ($relation->getOrphanRemoval()) { + $annotationOptions['orphanRemoval'] = true; + } + + $attributes = [ + $this->buildAttributeNode( + $relation instanceof RelationManyToMany ? ManyToMany::class : OneToMany::class, + $annotationOptions, + 'ORM' + ), + ]; + + $this->addProperty( + name: $relation->getPropertyName(), + attributes: $attributes, + // add @var that advertises this as a collection of specific objects + comments: [\sprintf('@var %s', $collectionTypeHint, $typeHint)], + propertyType: $collectionTypeHint, + ); + + // logic to avoid re-adding the same ArrayCollection line + $addArrayCollection = true; + if ($this->getConstructorNode()) { + // We print the constructor to a string, then + // look for "$this->propertyName = " + + $constructorString = $this->printer->prettyPrint([$this->getConstructorNode()]); + if (str_contains($constructorString, \sprintf('$this->%s = ', $relation->getPropertyName()))) { + $addArrayCollection = false; + } + } + + if ($addArrayCollection) { + $this->addStatementToConstructor( + new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $relation->getPropertyName()), + new Node\Expr\New_(new Node\Name($arrayCollectionTypeHint)) + )) + ); + } + + $this->addGetter( + $relation->getPropertyName(), + $collectionTypeHint, + false, + // add @return that advertises this as a collection of specific objects + [\sprintf('@return %s', $collectionTypeHint, $typeHint)] + ); + + $argName = Str::pluralCamelCaseToSingular($relation->getPropertyName()); + + // adder method + $adderNodeBuilder = (new Builder\Method($relation->getAdderMethodName()))->makePublic(); + + $paramBuilder = new Builder\Param($argName); + $paramBuilder->setType($typeHint); + $adderNodeBuilder->addParam($paramBuilder->getNode()); + + // if (!$this->avatars->contains($avatar)) + $containsMethodCallNode = new Node\Expr\MethodCall( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $relation->getPropertyName()), + 'contains', + [new Node\Expr\Variable($argName)] + ); + $ifNotContainsStmt = new Node\Stmt\If_( + new Node\Expr\BooleanNot($containsMethodCallNode) + ); + $adderNodeBuilder->addStmt($ifNotContainsStmt); + + // append the item + $ifNotContainsStmt->stmts[] = new Node\Stmt\Expression( + new Node\Expr\MethodCall( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $relation->getPropertyName()), + 'add', + [new Node\Expr\Variable($argName)] + )); + + // set the owning side of the relationship + if (!$relation->isOwning()) { + $ifNotContainsStmt->stmts[] = new Node\Stmt\Expression( + new Node\Expr\MethodCall( + new Node\Expr\Variable($argName), + $relation->getTargetSetterMethodName(), + [new Node\Expr\Variable('this')] + ) + ); + } + + $this->makeMethodFluent($adderNodeBuilder); + $this->addMethod($adderNodeBuilder->getNode()); + + /* + * Remover + */ + $removerNodeBuilder = (new Builder\Method($relation->getRemoverMethodName()))->makePublic(); + + $paramBuilder = new Builder\Param($argName); + $paramBuilder->setType($typeHint); + $removerNodeBuilder->addParam($paramBuilder->getNode()); + + // $this->avatars->removeElement($avatar) + $removeElementCall = new Node\Expr\MethodCall( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $relation->getPropertyName()), + 'removeElement', + [new Node\Expr\Variable($argName)] + ); + + // set the owning side of the relationship + if ($relation->isOwning()) { + // $this->avatars->removeElement($avatar); + $removerNodeBuilder->addStmt(BuilderHelpers::normalizeStmt($removeElementCall)); + } else { + // if ($this->avatars->removeElement($avatar)) + $ifRemoveElementStmt = new Node\Stmt\If_($removeElementCall); + $removerNodeBuilder->addStmt($ifRemoveElementStmt); + if ($relation instanceof RelationOneToMany) { + // OneToMany: $student->setCourse(null); + /* + * // set the owning side to null (unless already changed) + * if ($student->getCourse() === $this) { + * $student->setCourse(null); + * } + */ + + $ifRemoveElementStmt->stmts[] = $this->createSingleLineCommentNode( + 'set the owning side to null (unless already changed)', + self::CONTEXT_CLASS_METHOD + ); + + // if ($student->getCourse() === $this) { + $ifNode = new Node\Stmt\If_(new Node\Expr\BinaryOp\Identical( + new Node\Expr\MethodCall( + new Node\Expr\Variable($argName), + $relation->getTargetGetterMethodName() + ), + new Node\Expr\Variable('this') + )); + + // $student->setCourse(null); + $ifNode->stmts = [ + new Node\Stmt\Expression(new Node\Expr\MethodCall( + new Node\Expr\Variable($argName), + $relation->getTargetSetterMethodName(), + [new Node\Arg($this->createNullConstant())] + )), + ]; + + $ifRemoveElementStmt->stmts[] = $ifNode; + } elseif ($relation instanceof RelationManyToMany) { + // $student->removeCourse($this); + $ifRemoveElementStmt->stmts[] = new Node\Stmt\Expression( + new Node\Expr\MethodCall( + new Node\Expr\Variable($argName), + $relation->getTargetRemoverMethodName(), + [new Node\Expr\Variable('this')] + ) + ); + } else { + throw new \Exception('Unknown relation type'); + } + } + + $this->makeMethodFluent($removerNodeBuilder); + $this->addMethod($removerNodeBuilder->getNode()); + } + + private function addStatementToConstructor(Node\Stmt $stmt): void + { + if (!$this->getConstructorNode()) { + $constructorNode = (new Builder\Method('__construct'))->makePublic()->getNode(); + + // add call to parent::__construct() if there is a need to + try { + $ref = new \ReflectionClass($this->getThisFullClassName()); + + if ($ref->getParentClass() && $ref->getParentClass()->getConstructor()) { + $constructorNode->stmts[] = new Node\Stmt\Expression( + new Node\Expr\StaticCall(new Node\Name('parent'), new Node\Identifier('__construct')) + ); + } + } catch (\ReflectionException) { + } + + $this->addNodeAfterProperties($constructorNode); + } + + $constructorNode = $this->getConstructorNode(); + $constructorNode->stmts[] = $stmt; + $this->updateSourceCodeFromNewStmts(); + } + + /** + * @throws \Exception + */ + private function getConstructorNode(): ?Node\Stmt\ClassMethod + { + foreach ($this->getClassNode()->stmts as $classNode) { + if ($classNode instanceof Node\Stmt\ClassMethod && '__construct' == $classNode->name) { + return $classNode; + } + } + + return null; + } + + /** + * @return string The alias to use when referencing this class + */ + public function addUseStatementIfNecessary(string $class): string + { + $shortClassName = Str::getShortClassName($class); + if ($this->isInSameNamespace($class)) { + return $shortClassName; + } + + $namespaceNode = $this->getNamespaceNode(); + + $targetIndex = null; + $addLineBreak = false; + $lastUseStmtIndex = null; + foreach ($namespaceNode->stmts as $index => $stmt) { + if ($stmt instanceof Node\Stmt\Use_) { + // I believe this is an array to account for use statements with {} + foreach ($stmt->uses as $use) { + $alias = $use->alias ? $use->alias->name : $use->name->getLast(); + + // the use statement already exists? Don't add it again + if ($class === (string) $use->name) { + return $alias; + } + + if (str_starts_with($class, $alias)) { + return $class; + } + + if ($alias === $shortClassName) { + // we have a conflicting alias! + // to be safe, use the fully-qualified class name + // everywhere and do not add another use statement + return '\\'.$class; + } + } + + // if $class is alphabetically before this use statement, place it before + // only set $targetIndex the first time you find it + if (null === $targetIndex && Str::areClassesAlphabetical($class, (string) $stmt->uses[0]->name)) { + $targetIndex = $index; + } + + $lastUseStmtIndex = $index; + } elseif ($stmt instanceof Node\Stmt\Class_) { + if (null !== $targetIndex) { + // we already found where to place the use statement + + break; + } + + // we hit the class! If there were any use statements, + // then put this at the bottom of the use statement list + if (null !== $lastUseStmtIndex) { + $targetIndex = $lastUseStmtIndex + 1; + } else { + $targetIndex = $index; + $addLineBreak = true; + } + + break; + } + } + + if (null === $targetIndex) { + throw new \Exception('Could not find a class!'); + } + + $newUseNode = (new Builder\Use_($class, Node\Stmt\Use_::TYPE_NORMAL))->getNode(); + array_splice( + $namespaceNode->stmts, + $targetIndex, + 0, + $addLineBreak ? [$newUseNode, $this->createBlankLineNode(self::CONTEXT_OUTSIDE_CLASS)] : [$newUseNode] + ); + + $this->updateSourceCodeFromNewStmts(); + + return $shortClassName; + } + + /** + * Builds a PHPParser attribute node. + * + * @param string $attributeClass The attribute class which should be used for the attribute E.g. #[Column()] + * @param array $options The named arguments for the attribute ($key = argument name, $value = argument value) + * @param ?string $attributePrefix If a prefix is provided, the node is built using the prefix. E.g. #[ORM\Column()] + */ + public function buildAttributeNode(string $attributeClass, array $options, ?string $attributePrefix = null): Node\Attribute + { + $options = $this->sortOptionsByClassConstructorParameters($options, $attributeClass); + + $context = $this; + $nodeArguments = array_map(static function (string $option, mixed $value) use ($context) { + if (null === $value) { + return new Node\NullableType(new Node\Identifier($option)); + } + + // Use the Doctrine Types constant + if ('type' === $option && str_starts_with($value, 'Types::')) { + return new Node\Arg( + new Node\Expr\ConstFetch(new Node\Name($value)), + false, + false, + [], + new Node\Identifier($option) + ); + } + + if ('enumType' === $option) { + return new Node\Arg( + new Node\Expr\ConstFetch(new Node\Name(Str::getShortClassName($value).'::class')), + false, + false, + [], + new Node\Identifier($option) + ); + } + + return new Node\Arg($context->buildNodeExprByValue($value), false, false, [], new Node\Identifier($option)); + }, array_keys($options), array_values($options)); + + $class = $attributePrefix ? \sprintf('%s\\%s', $attributePrefix, Str::getShortClassName($attributeClass)) : Str::getShortClassName($attributeClass); + + return new Node\Attribute( + new Node\Name($class), + $nodeArguments + ); + } + + private function updateSourceCodeFromNewStmts(): void + { + $newCode = $this->printer->printFormatPreserving( + $this->newStmts, + $this->oldStmts, + $this->oldTokens + ); + + // replace the 3 "fake" items that may be in the code (allowing for different indentation) + $newCode = preg_replace('/(\ |\t)*private\ \$__EXTRA__LINE;/', '', $newCode); + $newCode = preg_replace('/use __EXTRA__LINE;/', '', $newCode); + $newCode = preg_replace('/(\ |\t)*\$__EXTRA__LINE;/', '', $newCode); + + // process comment lines + foreach ($this->pendingComments as $i => $comment) { + // sanity check + $placeholder = \sprintf('$__COMMENT__VAR_%d;', $i); + if (!str_contains($newCode, $placeholder)) { + // this can happen if a comment is createSingleLineCommentNode() + // is called, but then that generated code is ultimately not added + continue; + } + + $newCode = str_replace($placeholder, '// '.$comment, $newCode); + } + $this->pendingComments = []; + + $this->setSourceCode($newCode); + } + + private function setSourceCode(string $sourceCode): void + { + $this->sourceCode = $sourceCode; + $this->oldStmts = $this->parser->parse($sourceCode); + + /* @legacy Support for nikic/php-parser v4 */ + if (\is_callable([$this->parser, 'getTokens'])) { + $this->oldTokens = $this->parser->getTokens(); + } elseif (\is_callable($this->lexer->getTokens(...))) { + $this->oldTokens = $this->lexer->getTokens(); + } + + $traverser = new NodeTraverser(); + $traverser->addVisitor(new NodeVisitor\CloningVisitor()); + $traverser->addVisitor(new NodeVisitor\NameResolver(null, [ + 'replaceNodes' => false, + ])); + $this->newStmts = $traverser->traverse($this->oldStmts); + } + + private function getClassNode(): Node\Stmt\Class_ + { + $node = $this->findFirstNode(fn ($node) => $node instanceof Node\Stmt\Class_); + + if (!$node) { + throw new \Exception('Could not find class node'); + } + + return $node; + } + + private function getNamespaceNode(): Node\Stmt\Namespace_ + { + $node = $this->findFirstNode(fn ($node) => $node instanceof Node\Stmt\Namespace_); + + if (!$node) { + throw new \Exception('Could not find namespace node'); + } + + return $node; + } + + private function findFirstNode(callable $filterCallback): ?Node + { + $traverser = new NodeTraverser(); + $visitor = new NodeVisitor\FirstFindingVisitor($filterCallback); + $traverser->addVisitor($visitor); + $traverser->traverse($this->newStmts); + + return $visitor->getFoundNode(); + } + + private function findLastNode(callable $filterCallback, array $ast): ?Node + { + $traverser = new NodeTraverser(); + $visitor = new NodeVisitor\FindingVisitor($filterCallback); + $traverser->addVisitor($visitor); + $traverser->traverse($ast); + + $nodes = $visitor->getFoundNodes(); + $node = end($nodes); + + return false === $node ? null : $node; + } + + /** + * @return Node[] + */ + private function findAllNodes(callable $filterCallback): array + { + $traverser = new NodeTraverser(); + $visitor = new NodeVisitor\FindingVisitor($filterCallback); + $traverser->addVisitor($visitor); + $traverser->traverse($this->newStmts); + + return $visitor->getFoundNodes(); + } + + private function createBlankLineNode(string $context): Node\Stmt\Use_|Node|Node\Stmt\Property|Node\Expr\Variable + { + return match ($context) { + self::CONTEXT_OUTSIDE_CLASS => (new Builder\Use_( + '__EXTRA__LINE', + Node\Stmt\Use_::TYPE_NORMAL + )) + ->getNode(), + self::CONTEXT_CLASS => (new Builder\Property('__EXTRA__LINE')) + ->makePrivate() + ->getNode(), + self::CONTEXT_CLASS_METHOD => new Node\Expr\Variable( + '__EXTRA__LINE' + ), + default => throw new \Exception('Unknown context: '.$context), + }; + } + + private function createSingleLineCommentNode(string $comment, string $context): Node\Stmt + { + $this->pendingComments[] = $comment; + switch ($context) { + case self::CONTEXT_OUTSIDE_CLASS: + // just not needed yet + throw new \Exception('not supported'); + case self::CONTEXT_CLASS: + // just not needed yet + throw new \Exception('not supported'); + case self::CONTEXT_CLASS_METHOD: + return BuilderHelpers::normalizeStmt(new Node\Expr\Variable(\sprintf('__COMMENT__VAR_%d', \count($this->pendingComments) - 1))); + default: + throw new \Exception('Unknown context: '.$context); + } + } + + private function createDocBlock(array $commentLines): string + { + $docBlock = "/**\n"; + foreach ($commentLines as $commentLine) { + if ($commentLine) { + $docBlock .= " * $commentLine\n"; + } else { + // avoid the empty, extra space on blank lines + $docBlock .= " *\n"; + } + } + $docBlock .= "\n */"; + + return $docBlock; + } + + private function addMethod(Node\Stmt\ClassMethod $methodNode): void + { + $classNode = $this->getClassNode(); + $methodName = $methodNode->name; + $existingIndex = null; + if ($this->methodExists($methodName)) { + if (!$this->overwrite) { + $this->writeNote(\sprintf( + 'Not generating %s::%s(): method already exists', + Str::getShortClassName($this->getThisFullClassName()), + $methodName + )); + + return; + } + + // record, so we can overwrite in the same place + $existingIndex = $this->getMethodIndex($methodName); + } + + $newStatements = []; + + // put new method always at the bottom + if (!empty($classNode->stmts)) { + $newStatements[] = $this->createBlankLineNode(self::CONTEXT_CLASS); + } + + $newStatements[] = $methodNode; + + if (null === $existingIndex) { + // add them to the end! + + $classNode->stmts = array_merge($classNode->stmts, $newStatements); + } else { + array_splice( + $classNode->stmts, + $existingIndex, + 1, + $newStatements + ); + } + + $this->updateSourceCodeFromNewStmts(); + } + + private function makeMethodFluent(Builder\Method $methodBuilder): void + { + $methodBuilder + ->addStmt($this->createBlankLineNode(self::CONTEXT_CLASS_METHOD)) + ->addStmt(new Node\Stmt\Return_(new Node\Expr\Variable('this'))); + $methodBuilder->setReturnType('static'); + } + + private function isInSameNamespace(string $class): bool + { + $namespace = substr($class, 0, strrpos($class, '\\')); + + return $this->getNamespaceNode()->name->toCodeString() === $namespace; + } + + private function getThisFullClassName(): string + { + return (string) $this->getClassNode()->namespacedName; + } + + /** + * Adds this new node where a new property should go. + * + * Useful for adding properties, or adding a constructor. + */ + private function addNodeAfterProperties(Node $newNode): void + { + $classNode = $this->getClassNode(); + + // try to add after last property + $targetNode = $this->findLastNode(fn ($node) => $node instanceof Node\Stmt\Property, [$classNode]); + + // otherwise, try to add after the last constant + if (!$targetNode) { + $targetNode = $this->findLastNode(fn ($node) => $node instanceof Node\Stmt\ClassConst, [$classNode]); + } + + // otherwise, try to add after the last trait + if (!$targetNode) { + $targetNode = $this->findLastNode(fn ($node) => $node instanceof Node\Stmt\TraitUse, [$classNode]); + } + + // add the new property after this node + if ($targetNode) { + $index = array_search($targetNode, $classNode->stmts); + + array_splice( + $classNode->stmts, + $index + 1, + 0, + [$this->createBlankLineNode(self::CONTEXT_CLASS), $newNode] + ); + + $this->updateSourceCodeFromNewStmts(); + + return; + } + + // put right at the beginning of the class + // add an empty line, unless the class is totally empty + if (!empty($classNode->stmts)) { + array_unshift($classNode->stmts, $this->createBlankLineNode(self::CONTEXT_CLASS)); + } + array_unshift($classNode->stmts, $newNode); + $this->updateSourceCodeFromNewStmts(); + } + + private function createNullConstant(): Node\Expr\ConstFetch + { + return new Node\Expr\ConstFetch(new Node\Name('null')); + } + + private function addNodesToSetOtherSideOfOneToOne(RelationOneToOne $relation, Builder\Method $setterNodeBuilder): void + { + if (!$relation->isNullable()) { + $setterNodeBuilder->addStmt($this->createSingleLineCommentNode( + 'set the owning side of the relation if necessary', + self::CONTEXT_CLASS_METHOD + )); + + // if ($user->getUserProfile() !== $this) { + $ifNode = new Node\Stmt\If_(new Node\Expr\BinaryOp\NotIdentical( + new Node\Expr\MethodCall( + new Node\Expr\Variable($relation->getPropertyName()), + $relation->getTargetGetterMethodName() + ), + new Node\Expr\Variable('this') + )); + + // $user->setUserProfile($this); + $ifNode->stmts = [ + new Node\Stmt\Expression(new Node\Expr\MethodCall( + new Node\Expr\Variable($relation->getPropertyName()), + $relation->getTargetSetterMethodName(), + [new Node\Arg(new Node\Expr\Variable('this'))] + )), + ]; + $setterNodeBuilder->addStmt($ifNode); + $setterNodeBuilder->addStmt($this->createBlankLineNode(self::CONTEXT_CLASS_METHOD)); + + return; + } + + // at this point, we know the relation is nullable + $setterNodeBuilder->addStmt($this->createSingleLineCommentNode( + 'unset the owning side of the relation if necessary', + self::CONTEXT_CLASS_METHOD + )); + + // if ($user !== null && $user->getUserProfile() !== $this) + $ifNode = new Node\Stmt\If_(new Node\Expr\BinaryOp\BooleanAnd( + new Node\Expr\BinaryOp\Identical( + new Node\Expr\Variable($relation->getPropertyName()), + $this->createNullConstant() + ), + new Node\Expr\BinaryOp\NotIdentical( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + $relation->getPropertyName() + ), + $this->createNullConstant() + ) + )); + $ifNode->stmts = [ + // $this->user->setUserProfile(null) + new Node\Stmt\Expression(new Node\Expr\MethodCall( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + $relation->getPropertyName() + ), + $relation->getTargetSetterMethodName(), + [new Node\Arg($this->createNullConstant())] + )), + ]; + $setterNodeBuilder->addStmt($ifNode); + + $setterNodeBuilder->addStmt($this->createBlankLineNode(self::CONTEXT_CLASS_METHOD)); + $setterNodeBuilder->addStmt($this->createSingleLineCommentNode( + 'set the owning side of the relation if necessary', + self::CONTEXT_CLASS_METHOD + )); + + // if ($user === null && $this->user !== null) + $ifNode = new Node\Stmt\If_(new Node\Expr\BinaryOp\BooleanAnd( + new Node\Expr\BinaryOp\NotIdentical( + new Node\Expr\Variable($relation->getPropertyName()), + $this->createNullConstant() + ), + new Node\Expr\BinaryOp\NotIdentical( + new Node\Expr\MethodCall( + new Node\Expr\Variable($relation->getPropertyName()), + $relation->getTargetGetterMethodName() + ), + new Node\Expr\Variable('this') + ) + )); + $ifNode->stmts = [ + new Node\Stmt\Expression(new Node\Expr\MethodCall( + new Node\Expr\Variable($relation->getPropertyName()), + $relation->getTargetSetterMethodName(), + [new Node\Arg(new Node\Expr\Variable('this'))] + )), + ]; + $setterNodeBuilder->addStmt($ifNode); + + $setterNodeBuilder->addStmt($this->createBlankLineNode(self::CONTEXT_CLASS_METHOD)); + } + + private function methodExists(string $methodName): bool + { + return false !== $this->getMethodIndex($methodName); + } + + private function getMethodIndex(string $methodName) + { + foreach ($this->getClassNode()->stmts as $i => $node) { + if ($node instanceof Node\Stmt\ClassMethod && strtolower($node->name->toString()) === strtolower($methodName)) { + return $i; + } + } + + return false; + } + + private function propertyExists(string $propertyName): bool + { + foreach ($this->getClassNode()->stmts as $i => $node) { + if ($node instanceof Node\Stmt\Property && $node->props[0]->name->toString() === $propertyName) { + return true; + } + } + + return false; + } + + private function writeNote(string $note): void + { + if (null !== $this->io) { + $this->io->text($note); + } + } + + private function addMethodParams(Builder\Method $methodBuilder, array $params): void + { + foreach ($params as $param) { + $methodBuilder->addParam($param); + } + } + + /** + * builds a PHPParser Expr Node based on the value given in $value + * throws an Exception when the given $value is not resolvable by this method. + * + * @throws \Exception + */ + private function buildNodeExprByValue(mixed $value): Node\Expr + { + switch (\gettype($value)) { + case 'string': + $nodeValue = new Node\Scalar\String_($value); + break; + case 'integer': + $nodeValue = new Node\Scalar\LNumber($value); + break; + case 'double': + $nodeValue = new Node\Scalar\DNumber($value); + break; + case 'boolean': + $nodeValue = new Node\Expr\ConstFetch(new Node\Name($value ? 'true' : 'false')); + break; + case 'array': + $context = $this; + $arrayItems = array_map(static fn ($key, $value) => new Node\Expr\ArrayItem( + $context->buildNodeExprByValue($value), + !\is_int($key) ? $context->buildNodeExprByValue($key) : null + ), array_keys($value), array_values($value)); + $nodeValue = new Node\Expr\Array_($arrayItems, ['kind' => Node\Expr\Array_::KIND_SHORT]); + break; + default: + $nodeValue = null; + } + + if (null === $nodeValue) { + if ($value instanceof ClassNameValue) { + $nodeValue = new Node\Expr\ConstFetch( + new Node\Name( + \sprintf('%s::class', $value->isSelf() ? 'self' : $value->getShortName()) + ) + ); + } else { + throw new \Exception(\sprintf('Cannot build a node expr for value of type "%s"', \gettype($value))); + } + } + + return $nodeValue; + } + + /** + * sort the given options based on the constructor parameters for the given $classString + * this prevents code inspections warnings for IDEs like intellij/phpstorm. + * + * option keys that are not found in the constructor will be added at the end of the sorted array + */ + private function sortOptionsByClassConstructorParameters(array $options, string $classString): array + { + if (str_starts_with($classString, 'ORM\\')) { + $classString = \sprintf('Doctrine\\ORM\\Mapping\\%s', substr($classString, 4)); + } + + $constructorParameterNames = array_map(static fn (\ReflectionParameter $reflectionParameter) => $reflectionParameter->getName(), (new \ReflectionClass($classString))->getConstructor()->getParameters()); + + $sorted = []; + foreach ($constructorParameterNames as $name) { + if (\array_key_exists($name, $options)) { + $sorted[$name] = $options[$name]; + unset($options[$name]); + } + } + + return array_merge($sorted, $options); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/CliOutputHelper.php b/vendor/symfony/maker-bundle/src/Util/CliOutputHelper.php new file mode 100644 index 0000000..8a62381 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/CliOutputHelper.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +/** + * Tools used to enhance maker command output. + * + * For additional context with Symfony CLI EnvVars, see + * https://github.com/symfony-cli/symfony-cli/pull/231 + * + * @author Jesse Rushlow + * + * @internal + */ +final class CliOutputHelper +{ + /** + * EnvVars exposed by Symfony's CLI. + */ + public const ENV_VERSION = 'SYMFONY_CLI_VERSION'; // Current CLI Version + public const ENV_BIN_NAME = 'SYMFONY_CLI_BINARY_NAME'; // Name of the binary e.g. "symfony" + + /** + * Get the correct command prefix based on Symfony CLI usage. + */ + public static function getCommandPrefix(): string + { + $prompt = 'php bin/console'; + + $binaryNameEnvVar = getenv(self::ENV_BIN_NAME); + + if (false !== $binaryNameEnvVar && false !== getenv(self::ENV_VERSION)) { + $prompt = \sprintf('%s console', $binaryNameEnvVar); + } + + return $prompt; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ComposeFileManipulator.php b/vendor/symfony/maker-bundle/src/Util/ComposeFileManipulator.php new file mode 100644 index 0000000..b619ef1 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ComposeFileManipulator.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Component\Yaml\Dumper; + +/** + * Manipulate Docker Compose Files. + * + * @author Jesse Rushlow + * + * @internal + * + * @final + */ +class ComposeFileManipulator +{ + public const COMPOSE_FILE_VERSION = '3.7'; + + private YamlSourceManipulator $manipulator; + + public function __construct(string $contents) + { + if ('' === $contents) { + $this->manipulator = new YamlSourceManipulator( + (new Dumper())->dump($this->getBasicStructure(), 2) + ); + } else { + $this->manipulator = new YamlSourceManipulator($contents); + } + + $this->checkComposeFileVersion(); + } + + public function getComposeData(): array + { + return $this->manipulator->getData(); + } + + public function getDataString(): string + { + return $this->manipulator->getContents(); + } + + public function serviceExists(string $name): bool + { + $data = $this->manipulator->getData(); + + if (\array_key_exists('services', $data)) { + return \array_key_exists($name, $data['services']); + } + + return false; + } + + public function addDockerService(string $name, array $details): void + { + $data = $this->manipulator->getData(); + + $data['services'][$name] = $details; + + $this->manipulator->setData($data); + } + + public function removeDockerService(string $name): void + { + $data = $this->manipulator->getData(); + + unset($data['services'][$name]); + + $this->manipulator->setData($data); + } + + public function exposePorts(string $service, array $ports): void + { + $portData = []; + $portData[] = \sprintf('%s To allow the host machine to access the ports below, modify the lines below.', YamlSourceManipulator::COMMENT_PLACEHOLDER_VALUE); + $portData[] = \sprintf('%s For example, to allow the host to connect to port 3306 on the container, you would change', YamlSourceManipulator::COMMENT_PLACEHOLDER_VALUE); + $portData[] = \sprintf('%s "3306" to "3306:3306". Where the first port is exposed to the host and the second is the container port.', YamlSourceManipulator::COMMENT_PLACEHOLDER_VALUE); + $portData[] = \sprintf('%s See https://docs.docker.com/compose/compose-file/compose-file-v3/#ports for more information.', YamlSourceManipulator::COMMENT_PLACEHOLDER_VALUE); + + foreach ($ports as $port) { + $portData[] = $port; + } + + $data = $this->manipulator->getData(); + + $data['services'][$service]['ports'] = $portData; + + $this->manipulator->setData($data); + } + + public function addVolume(string $service, string $hostPath, string $containerPath): void + { + $data = $this->manipulator->getData(); + + $data['services'][$service]['volumes'][] = \sprintf('%s:%s', $hostPath, $containerPath); + + $this->manipulator->setData($data); + } + + private function getBasicStructure(string $version = self::COMPOSE_FILE_VERSION): array + { + return [ + 'version' => $version, + 'services' => [], + ]; + } + + private function checkComposeFileVersion(): void + { + $data = $this->manipulator->getData(); + + if (empty($data['version'])) { + throw new RuntimeCommandException('compose.yaml file version is not set.'); + } + + if (2.0 > (float) $data['version']) { + throw new RuntimeCommandException(\sprintf('compose.yaml version %s is not supported. Please update your compose.yaml file to the latest version.', $data['version'])); + } + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/ComposerAutoloaderFinder.php b/vendor/symfony/maker-bundle/src/Util/ComposerAutoloaderFinder.php new file mode 100644 index 0000000..ad307df --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/ComposerAutoloaderFinder.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\ErrorHandler\DebugClassLoader as ErrorHandlerDebugClassLoader; + +/** + * @internal + */ +class ComposerAutoloaderFinder +{ + private array $rootNamespace; + private ?ClassLoader $classLoader = null; + + public function __construct(string $rootNamespace) + { + $this->rootNamespace = [ + 'psr0' => rtrim($rootNamespace, '\\'), + 'psr4' => rtrim($rootNamespace, '\\').'\\', + ]; + } + + public function getClassLoader(): ClassLoader + { + if (null === $this->classLoader) { + $this->classLoader = $this->findComposerClassLoader(); + } + + if (null === $this->classLoader) { + throw new \Exception("Could not find a Composer autoloader that autoloads from '{$this->rootNamespace['psr4']}'"); + } + + return $this->classLoader; + } + + private function findComposerClassLoader(): ?ClassLoader + { + $autoloadFunctions = spl_autoload_functions(); + + foreach ($autoloadFunctions as $autoloader) { + if (!\is_array($autoloader)) { + continue; + } + + $classLoader = $this->extractComposerClassLoader($autoloader); + if (null === $classLoader) { + continue; + } + + $finalClassLoader = $this->locateMatchingClassLoader($classLoader); + if (null !== $finalClassLoader) { + return $finalClassLoader; + } + } + + return null; + } + + private function extractComposerClassLoader(array $autoloader): ?ClassLoader + { + if (isset($autoloader[0]) && \is_object($autoloader[0])) { + if ($autoloader[0] instanceof ClassLoader) { + return $autoloader[0]; + } + if ( + ($autoloader[0] instanceof DebugClassLoader + || $autoloader[0] instanceof ErrorHandlerDebugClassLoader) + && \is_array($autoloader[0]->getClassLoader()) + && $autoloader[0]->getClassLoader()[0] instanceof ClassLoader) { + return $autoloader[0]->getClassLoader()[0]; + } + } + + return null; + } + + private function locateMatchingClassLoader(ClassLoader $classLoader): ?ClassLoader + { + $makerClassLoader = null; + foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) { + if ('Symfony\\Bundle\\MakerBundle\\' === $prefix) { + $makerClassLoader = $classLoader; + } + if (str_starts_with($this->rootNamespace['psr4'], $prefix)) { + return $classLoader; + } + } + + foreach ($classLoader->getPrefixes() as $prefix => $paths) { + if (str_starts_with($this->rootNamespace['psr0'], $prefix)) { + return $classLoader; + } + } + + // Nothing found? Try the class loader where we found MakerBundle + return $makerClassLoader; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/MakerFileLinkFormatter.php b/vendor/symfony/maker-bundle/src/Util/MakerFileLinkFormatter.php new file mode 100644 index 0000000..7acf53f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/MakerFileLinkFormatter.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter as LegacyFileLinkFormatter; + +/** + * @internal + */ +final class MakerFileLinkFormatter +{ + public function __construct( + private FileLinkFormatter|LegacyFileLinkFormatter|null $fileLinkFormatter = null, + ) { + } + + public function makeLinkedPath(string $absolutePath, string $relativePath): string + { + if (!$this->fileLinkFormatter) { + return $relativePath; + } + + if (!$formatted = $this->fileLinkFormatter->format($absolutePath, 1)) { + return $relativePath; + } + + // workaround for difficulties parsing linked file paths in appveyor + if (getenv('MAKER_DISABLE_FILE_LINKS')) { + return $relativePath; + } + + $outputFormatterStyle = new OutputFormatterStyle(); + + if (method_exists(OutputFormatterStyle::class, 'setHref')) { + $outputFormatterStyle->setHref($formatted); + } + + return $outputFormatterStyle->apply($relativePath); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/PhpCompatUtil.php b/vendor/symfony/maker-bundle/src/Util/PhpCompatUtil.php new file mode 100644 index 0000000..5636d8f --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/PhpCompatUtil.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Symfony\Bundle\MakerBundle\FileManager; + +/** + * @author Jesse Rushlow + * + * @internal + */ +class PhpCompatUtil +{ + public function __construct(private FileManager $fileManager) + { + } + + protected function getPhpVersion(): string + { + $rootDirectory = $this->fileManager->getRootDirectory(); + + $composerLockPath = \sprintf('%s/composer.lock', $rootDirectory); + + if (!$this->fileManager->fileExists($composerLockPath)) { + return \PHP_VERSION; + } + + $lockFileContents = json_decode($this->fileManager->getFileContents($composerLockPath), true); + + if (empty($lockFileContents['platform-overrides']) || empty($lockFileContents['platform-overrides']['php'])) { + return \PHP_VERSION; + } + + return $lockFileContents['platform-overrides']['php']; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/PrettyPrinter.php b/vendor/symfony/maker-bundle/src/Util/PrettyPrinter.php new file mode 100644 index 0000000..9b93dbf --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/PrettyPrinter.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use PhpParser\Node\Stmt; +use PhpParser\PrettyPrinter\Standard; + +/** + * @internal + */ +final class PrettyPrinter extends Standard +{ + /** + * Overridden to fix indentation problem with tabs. + * + * If the original source code uses tabs, then the tokenizer + * will see this as "1" indent level, and will indent new lines + * with just 1 space. By changing 1 indent to 4, we effectively + * "correct" this problem when printing. + * + * For code that is even further indented (e.g. 8 spaces), + * the printer uses the first indentation (here corrected + * from 1 space to 4) and already (without needing any other + * changes) adds 4 spaces onto that. This is why we don't + * also need to handle indent levels of 5, 9, etc: these + * do not occur (at least in the code we generate); + */ + protected function setIndentLevel(int $level): void + { + if (1 === $level) { + $level = 4; + } + + parent::setIndentLevel($level); + } + + /** + * Overridden to change coding standards. + * + * Before: + * public function getFoo() : string + * + * After + * public function getFoo(): string + */ + protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string + { + $classMethod = parent::pStmt_ClassMethod($node); + + if ($node->returnType) { + $classMethod = str_replace(') :', '):', $classMethod); + } + + return $classMethod; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/TemplateComponentGenerator.php b/vendor/symfony/maker-bundle/src/Util/TemplateComponentGenerator.php new file mode 100644 index 0000000..de8fc16 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/TemplateComponentGenerator.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class TemplateComponentGenerator +{ + public function __construct( + private bool $generateFinalClasses, + private bool $generateFinalEntities, + private string $rootNamespace, + ) { + } + + /** + * @param string|null $routePath passing an empty string/null will create a route attribute without the "path" argument + */ + public function generateRouteForControllerMethod(?string $routePath, string $routeName, array $methods = [], bool $indent = true, bool $trailingNewLine = true): string + { + if (!empty($routePath)) { + $path = \sprintf('\'%s\', ', $routePath); + } + + $attribute = \sprintf('%s#[Route(%sname: \'%s\'', $indent ? ' ' : null, $path ?? null, $routeName); + + if (!empty($methods)) { + $attribute .= ', methods: ['; + + foreach ($methods as $method) { + $attribute .= \sprintf('\'%s\', ', $method); + } + + $attribute = rtrim($attribute, ', '); + + $attribute .= ']'; + } + + $attribute .= \sprintf(')]%s', $trailingNewLine ? "\n" : null); + + return $attribute; + } + + public function getPropertyType(ClassNameDetails $classNameDetails): ?string + { + return \sprintf('%s ', $classNameDetails->getShortName()); + } + + public function configureClass(ClassData $classMetadata): ClassData + { + $classMetadata->setRootNamespace($this->rootNamespace); + + if ($classMetadata->isEntity) { + return $classMetadata->setIsFinal($this->generateFinalEntities); + } + + return $classMetadata->setIsFinal($this->generateFinalClasses); + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/TemplateLinter.php b/vendor/symfony/maker-bundle/src/Util/TemplateLinter.php new file mode 100644 index 0000000..1d29d19 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/TemplateLinter.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; + +/** + * Linters used by make:* commands to cleanup the generated files. + * + * @author Jesse Rushlow + * + * @internal + */ +final class TemplateLinter +{ + // Version must match bundled version file name. e.g. php-cs-fixer-v3.49.9.phar + public const BUNDLED_PHP_CS_FIXER_VERSION = '3.49.0'; + + private bool $usingBundledPhpCsFixer = true; + private bool $usingBundledPhpCsFixerConfig = true; + private bool $needsPhpCmdPrefix = true; + + public function __construct( + private ?string $phpCsFixerBinaryPath = null, + private ?string $phpCsFixerConfigPath = null, + ) { + $this->setBinary(); + $this->setConfig(); + } + + public function lintFiles(array $templateFilePaths): void + { + $phpFiles = []; + + foreach ($templateFilePaths as $filePath) { + if (str_ends_with($filePath, '.php')) { + $phpFiles[] = $filePath; + } + } + + $this->lintPhpTemplate($phpFiles); + } + + public function lintPhpTemplate(string|array $templateFilePath): void + { + if (\is_string($templateFilePath)) { + $templateFilePath = [$templateFilePath]; + } + + $isWindows = \defined('PHP_WINDOWS_VERSION_MAJOR'); + $ignoreEnv = $isWindows ? 'set PHP_CS_FIXER_IGNORE_ENV=1& ' : 'PHP_CS_FIXER_IGNORE_ENV=1 '; + + $cmdPrefix = $this->needsPhpCmdPrefix ? 'php ' : ''; + + foreach ($templateFilePath as $filePath) { + Process::fromShellCommandline(\sprintf( + '%s%s%s --config=%s --using-cache=no fix %s', + $ignoreEnv, + $cmdPrefix, + $this->phpCsFixerBinaryPath, + $this->phpCsFixerConfigPath, + $filePath + )) + ->run() + ; + } + } + + public function writeLinterMessage(OutputInterface $output): void + { + $output->writeln('Linting Generated Files With:'); + + $fixerMessage = $this->usingBundledPhpCsFixer ? + 'Bundled PHP-CS-Fixer & ' : + \sprintf('System PHP-CS-Fixer (%s) & ', $this->phpCsFixerBinaryPath) + + ; + + $fixerMessage .= $this->usingBundledPhpCsFixerConfig ? + 'Bundled PHP-CS-Fixer Configuration' : + \sprintf('System PHP-CS-Fixer Configuration (%s)', $this->phpCsFixerConfigPath) + ; + + $output->writeln([$fixerMessage, '']); // Empty string so we have an empty line + } + + private function setBinary(): void + { + // Use Bundled PHP-CS-Fixer + if (null === $this->phpCsFixerBinaryPath) { + $this->phpCsFixerBinaryPath = \sprintf('%s/Resources/bin/php-cs-fixer-v%s.phar', \dirname(__DIR__), self::BUNDLED_PHP_CS_FIXER_VERSION); + + return; + } + + // Path to PHP-CS-Fixer provided + if (is_file($this->phpCsFixerBinaryPath)) { + $this->usingBundledPhpCsFixer = false; + + return; + } + + // PHP-CS-Fixer in the system path? + if (null !== $path = (new ExecutableFinder())->find($this->phpCsFixerBinaryPath)) { + $this->phpCsFixerBinaryPath = $path; + + $this->needsPhpCmdPrefix = false; + $this->usingBundledPhpCsFixer = false; + + return; + } + + // PHP-CS-Fixer provided is not a file and is not in the system path. + throw new RuntimeCommandException(\sprintf('The MAKER_PHP_CS_FIXER_BINARY_PATH provided: %s does not exist.', $this->phpCsFixerBinaryPath)); + } + + private function setConfig(): void + { + // No config provided, but there is a dist config file in the project dir + if (null === $this->phpCsFixerConfigPath && file_exists($defaultConfigPath = '.php-cs-fixer.dist.php')) { + $this->phpCsFixerConfigPath = $defaultConfigPath; + + $this->usingBundledPhpCsFixerConfig = false; + + return; + } + + // No config provided and no project dist config - use our config + if (null === $this->phpCsFixerConfigPath) { + $this->phpCsFixerConfigPath = \dirname(__DIR__).'/Resources/config/php-cs-fixer.config.php'; + + return; + } + + // The config path provided doesn't exist... + if (!file_exists($this->phpCsFixerConfigPath)) { + throw new RuntimeCommandException(\sprintf('The MAKER_PHP_CS_FIXER_CONFIG_PATH provided: %s does not exist.', $this->phpCsFixerConfigPath)); + } + + $this->usingBundledPhpCsFixerConfig = false; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/UseStatementGenerator.php b/vendor/symfony/maker-bundle/src/Util/UseStatementGenerator.php new file mode 100644 index 0000000..17cf47d --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/UseStatementGenerator.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +/** + * Converts fully qualified class names into sorted use statements for templates. + * + * @author Jesse Rushlow + * + * @internal + */ +final class UseStatementGenerator implements \Stringable +{ + /** + * For use statements that contain aliases, the $classesToBeImported array + * may contain an array(s) like [\Some\Class::class => 'ZYX']. The generated + * use statement would appear as "use Some\Class::class as 'ZXY'". It is ok + * to mix non-aliases classes with aliases. + * + * @param string[]|array $classesToBeImported + */ + public function __construct( + private array $classesToBeImported, + ) { + } + + public function __toString(): string + { + $transformed = []; + $aliases = []; + + foreach ($this->classesToBeImported as $key => $class) { + if (\is_array($class)) { + $aliasClass = key($class); + $aliases[$aliasClass] = $class[$aliasClass]; + $class = $aliasClass; + } + + $transformedClass = str_replace('\\', ' ', $class); + // Let's not add the class again if it already exists. + if (!\in_array($transformedClass, $transformed, true)) { + $transformed[$key] = $transformedClass; + } + } + + asort($transformed); + + $statements = ''; + + foreach ($transformed as $key => $class) { + $importedClass = $this->classesToBeImported[$key]; + + if (!\is_array($importedClass)) { + $statements .= \sprintf("use %s;\n", $importedClass); + continue; + } + + $aliasClass = key($importedClass); + $statements .= \sprintf("use %s as %s;\n", $aliasClass, $aliases[$aliasClass]); + } + + return $statements; + } + + /** + * @param string|string[]|array $className + */ + public function addUseStatement(array|string $className): void + { + if (\is_array($className)) { + $this->classesToBeImported = array_merge($this->classesToBeImported, $className); + + return; + } + + // Let's not add the class again if it already exists. + if (\in_array($className, $this->classesToBeImported, true)) { + return; + } + + $this->classesToBeImported[] = $className; + } +} diff --git a/vendor/symfony/maker-bundle/src/Util/YamlManipulationFailedException.php b/vendor/symfony/maker-bundle/src/Util/YamlManipulationFailedException.php new file mode 100644 index 0000000..19e0182 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/YamlManipulationFailedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +/** + * Thrown whenever YamlSourceManipulator cannot change contents successfully. + */ +class YamlManipulationFailedException extends \RuntimeException +{ +} diff --git a/vendor/symfony/maker-bundle/src/Util/YamlSourceManipulator.php b/vendor/symfony/maker-bundle/src/Util/YamlSourceManipulator.php new file mode 100644 index 0000000..bdd6506 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Util/YamlSourceManipulator.php @@ -0,0 +1,1331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +/** + * A class that modifies YAML source, while keeping comments & formatting. + * + * This is designed to work for the most common syntaxes, but not + * all YAML syntaxes. If content cannot be updated safely, an + * exception is thrown. + */ +class YamlSourceManipulator +{ + public const EMPTY_LINE_PLACEHOLDER_VALUE = '__EMPTY_LINE__'; + public const COMMENT_PLACEHOLDER_VALUE = '__COMMENT__'; + + public const UNSET_KEY_FLAG = '__MAKER_VALUE_UNSET'; + public const ARRAY_FORMAT_MULTILINE = 'multi'; + public const ARRAY_FORMAT_INLINE = 'inline'; + + public const ARRAY_TYPE_SEQUENCE = 'sequence'; + public const ARRAY_TYPE_HASH = 'hash'; + + private ?LoggerInterface $logger = null; + private $currentData; + + private int $currentPosition = 0; + private array $previousPath = []; + private array $currentPath = []; + private int $depth = 0; + private array $indentationForDepths = []; + private array $arrayFormatForDepths = []; + private array $arrayTypeForDepths = []; + + public function __construct( + private string $contents, + ) { + $this->currentData = Yaml::parse($contents); + + if (!\is_array($this->currentData)) { + throw new \InvalidArgumentException('Only YAML with a top-level array structure is supported'); + } + } + + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function getData(): array + { + return $this->currentData; + } + + public function getContents(): string + { + return $this->contents; + } + + public function setData(array $newData) + { + $this->currentPath = []; + $this->previousPath = []; + $this->currentPosition = 0; + $this->depth = -1; + $this->indentationForDepths = []; + $this->arrayFormatForDepths = []; + $this->arrayTypeForDepths = []; + + $this->updateData($newData); + $this->replaceSpecialMetadataCharacters(); + // update the data now that the special chars have been removed + $this->currentData = Yaml::parse($this->contents); + + // remove special metadata keys that were replaced + $newData = $this->removeMetadataKeys($newData); + + // Before comparing, re-index any sequences on the new data. + // The current data will already use sequential indexes + $newData = $this->normalizeSequences($newData); + + if ($newData !== $this->currentData) { + throw new YamlManipulationFailedException(\sprintf('Failed updating YAML contents: the process was successful, but something was not updated. Expected new data: %s. Actual new data: %s', var_export($newData, true), var_export($this->currentData, true))); + } + } + + public function createEmptyLine(): string + { + return self::EMPTY_LINE_PLACEHOLDER_VALUE; + } + + public function createCommentLine(string $comment): string + { + return self::COMMENT_PLACEHOLDER_VALUE.$comment; + } + + private function updateData(array $newData): void + { + ++$this->depth; + if (0 === $this->depth) { + $this->indentationForDepths[$this->depth] = 0; + $this->arrayFormatForDepths[$this->depth] = self::ARRAY_FORMAT_MULTILINE; + } else { + // match the current indentation to start + $this->indentationForDepths[$this->depth] = $this->indentationForDepths[$this->depth - 1]; + // advancing is especially important if this is an inline array: + // get into the [] or {} + $this->arrayFormatForDepths[$this->depth] = $this->guessNextArrayTypeAndAdvance(); + } + + $currentData = $this->getCurrentData(); + + $this->arrayTypeForDepths[$this->depth] = $this->isHash($currentData) ? self::ARRAY_TYPE_HASH : self::ARRAY_TYPE_SEQUENCE; + + $this->log(\sprintf( + 'Changing array type & format via updateData() (type=%s, format=%s)', + $this->arrayTypeForDepths[$this->depth], + $this->arrayFormatForDepths[$this->depth] + )); + + foreach ($currentData as $key => $currentVal) { + // path setting is mostly duplicated at the bottom of this method + $this->previousPath = $this->currentPath; + if (!isset($this->previousPath[$this->depth])) { + // if there is no previous flag at this level, mark it with a null + $this->previousPath[$this->depth] = null; + } + $this->currentPath[$this->depth] = $key; + + // advance from the end of the previous value to the + // start of the key, which may include whitespace or, for + // example, some closing array syntax - } or ] - from the + // previous value + $this->advanceBeyondEndOfPreviousKey($key); + + $this->log('START key', true); + + // 1) was this key removed from the new data? + if (!\array_key_exists($key, $newData)) { + $this->log('Removing key'); + $this->removeKeyFromYaml($key, $currentData[$key]); + + // manually update our current data now that the key is gone + unset($currentData[$key]); + + // because the item was removed, reset the current path + // to the previous path, so the next iteration doesn't + // expect the previous path to have this removed key + $this->currentPath = $this->previousPath; + + continue; + } + + /* + * 2) are there new keys in the new data before this key? + * + * To determine this, we look at the position of the key inside the + * current data and compare it to the position of that same key in + * the new data. While they are not equal, we loop. Inside the loop, + * the new key is added to the current data *before* $key. Thanks to + * this, on each loop, the currentDataIndex will increase until it + * matches the new data + */ + while (($currentDataIndex = array_search($key, array_keys($currentData))) !== array_search($key, array_keys($newData))) { + // loop until the current key is found at the same position in current & new data + $newKey = array_keys($newData)[$currentDataIndex]; + $newVal = $newData[$newKey]; + $this->log('Adding new key: '.$newKey); + + $this->addNewKeyToYaml($newKey, $newVal); + + // refresh the current array data because we added an item + // we can't just add the key manually, as it may have been + // we can't just add the key manually, as it may have been + // added in the middle + $currentData = $this->getCurrentData(1); + } + + // 3) Key already exists in YAML + // advance the position to the end of this key + $this->advanceBeyondKey($key); + $newVal = $newData[$key]; + + // if the current data is an array, we should keep + // walking through that data, even if it didn't change, + // so that we can advance the current position + if (\is_array($currentData[$key]) && \is_array($newVal)) { + $this->log('Calling updateData() on next level'); + $this->updateData($newVal); + + continue; + } + + // 3a) value did NOT change + if ($currentData[$key] === $newVal) { + $this->log('value did not change'); + $this->advanceBeyondValue($newVal); + + continue; + } + + // 3b) value DID change + $this->log(\sprintf('updating value to {%s}', \is_array($newVal) ? '' : $newVal)); + $this->changeValueInYaml($newVal); + } + + // Bonus! are there new keys in the data after this key... + // and this is the final key? + + // Edge case: if the last item on a multi-line array has a comment, + // we want to move to the end of the line, beyond that comment + if (\count($currentData) < \count($newData) && $this->isCurrentArrayMultiline()) { + $this->advanceBeyondMultilineArrayLastItem(); + } + + if (0 === $this->indentationForDepths[$this->depth] && $this->depth > 1) { + $ident = $this->getPreferredIndentationSize(); + $previousDepth = $this->depth - 1; + + $this->indentationForDepths[$this->depth] = ($ident + $this->indentationForDepths[$previousDepth]); + } + + while (\count($currentData) < \count($newData)) { + $newKey = array_keys($newData)[\count($currentData)]; + + // manually move the paths forward + // mostly duplicated above + $this->previousPath = $this->currentPath; + if (!isset($this->previousPath[$this->depth])) { + // if there is no previous flag at this level, mark it with a null + $this->previousPath[$this->depth] = null; + } + $this->currentPath[$this->depth] = $newKey; + + $newVal = $newData[$newKey]; + $this->log('Adding new key to end of array'); + + $this->addNewKeyToYaml($newKey, $newVal); + + // refresh manually so the while sees it above + $currentData = $this->getCurrentData(1); + } + + $this->decrementDepth(); + } + + /** + * Adds a new key to current position in the YAML. + * + * The position should be set *right* where this new key + * should be inserted. + */ + private function addNewKeyToYaml(int|string $key, $value): void + { + $extraOffset = 0; + $firstItemInArray = false; + if (empty($this->getCurrentData(1))) { + // The array that we're appending is empty: + + // First, fix the "type" - it could be changing from a sequence to a hash or vice versa + $this->arrayTypeForDepths[$this->depth] = \is_int($key) ? self::ARRAY_TYPE_SEQUENCE : self::ARRAY_TYPE_HASH; + + // we prefer multi-line, so let's convert to it! + $this->arrayFormatForDepths[$this->depth] = self::ARRAY_FORMAT_MULTILINE; + + // if this is an inline empty array (is there any other), we need to + // remove the empty array characters = {} or [] + + // we are already 1 character beyond the starting { or [ - so, rewind before it + --$this->currentPosition; + // now, rewind any spaces to get back to the : after the key + while (' ' === substr($this->contents, $this->currentPosition - 1, 1)) { + --$this->currentPosition; + } + + // determine an extra offset to "skip" when reconstructing the string + $endingArrayPosition = $this->findPositionOfNextCharacter(['}', ']']); + $extraOffset = $endingArrayPosition - $this->currentPosition; + + // increase the indentation of *this* level + $this->manuallyIncrementIndentation(); + + $firstItemInArray = true; + } elseif ($this->isPositionAtBeginningOfArray()) { + $firstItemInArray = true; + + // the array is not empty, but we are prepending an element + if ($this->isCurrentArrayMultiline()) { + // indentation will be set to low, except for root level + if ($this->depth > 0) { + $this->manuallyIncrementIndentation(); + } + } else { + // we're at the start of an inline array + // advance beyond any whitespace so that our new key + // uses the same whitespace that was originally after + // the { or [ + $this->advanceBeyondWhitespace(); + } + } + + if (\is_int($key)) { + if ($this->isCurrentArrayMultiline()) { + if ($this->isCurrentArraySequence()) { + $newYamlValue = '- '.$this->convertToYaml($value); + } else { + // this is an associative array, but an indexed key + // is being added. We can't use the "- " format + $newYamlValue = \sprintf( + '%s: %s', + $key, + $this->convertToYaml($value) + ); + } + } else { + $newYamlValue = $this->convertToYaml($value); + } + } else { + $newYamlValue = $this->convertToYaml([$key => $value]); + } + + if (0 === $this->currentPosition) { + // if we're at the beginning of the file, the situation is special: + // no previous blank line is needed, but we DO need to add a blank + // line after, because the remainder of the content expects the + // current position the start at the beginning of a new line + $newYamlValue .= "\n"; + } else { + if ($this->isCurrentArrayMultiline()) { + // because we're inside a multi-line array, put this item + // onto the *next* line & indent it + + $newYamlValue = "\n".$this->indentMultilineYamlArray($newYamlValue); + } else { + if ($firstItemInArray) { + // avoid the starting "," if first item in array + // but, DO add an ending "," + $newYamlValue .= ', '; + } else { + $newYamlValue = ', '.$newYamlValue; + } + } + } + + $newContents = substr($this->contents, 0, $this->currentPosition) + .$newYamlValue + .substr($this->contents, $this->currentPosition + $extraOffset); + // manually bump the position: we didn't really move forward + // any in the existing string, we just added our own new content + $this->currentPosition += \strlen($newYamlValue); + + if (0 === $this->depth) { + $newData = $this->currentData; + $newData = $this->appendToArrayAtCurrentPath($key, $value, $newData); + } else { + // first, append to the "local" array: the little array we're currently working on + $newLocalData = $this->getCurrentData(1); + $newLocalData = $this->appendToArrayAtCurrentPath($key, $value, $newLocalData); + // second, set this new array inside the full data + $newData = $this->currentData; + $newData = $this->setValueAtCurrentPath($newLocalData, $newData, 1); + } + + $this->updateContents( + $newContents, + $newData, + $this->currentPosition + ); + } + + private function removeKeyFromYaml($key, $currentVal): void + { + $endKeyPosition = $this->getEndOfKeyPosition($key); + + $endKeyPosition = $this->findEndPositionOfValue($currentVal, $endKeyPosition); + + if ($this->isCurrentArrayMultiline()) { + $nextNewLine = $this->findNextLineBreak($endKeyPosition); + // it's possible we're at the end of the file so there are no more \n + if (false !== $nextNewLine) { + $endKeyPosition = $nextNewLine; + } + } else { + // find next ending character - , } or ] + while (!\in_array($currentChar = substr($this->contents, $endKeyPosition, 1), [',', ']', '}'])) { + ++$endKeyPosition; + } + + // if a sequence or hash is ending, and the character before it is a space, keep that + if ((']' === $currentChar || '}' === $currentChar) && ' ' === substr($this->contents, $endKeyPosition - 1, 1)) { + --$endKeyPosition; + } + } + + $newPositionBump = 0; + $extraContent = ''; + if (1 === \count($this->getCurrentData(1))) { + // the key being removed is the *only* key + // we need to close the new, empty array + $extraContent = ' []'; + // when processing arrays normally, the position is set + // after the opening character. Move this here manually + $newPositionBump = 2; + + // if it *was* multiline, the indentation is now lost + if ($this->isCurrentArrayMultiline()) { + $this->indentationForDepths[$this->depth] = $this->indentationForDepths[$this->depth - 1]; + } + // it is now definitely a sequence + $this->arrayTypeForDepths[$this->depth] = self::ARRAY_TYPE_SEQUENCE; + // it is now inline + $this->arrayFormatForDepths[$this->depth] = self::ARRAY_FORMAT_INLINE; + } + + $newContents = substr($this->contents, 0, $this->currentPosition) + .$extraContent + .substr($this->contents, $endKeyPosition); + + $newData = $this->currentData; + $newData = $this->removeKeyAtCurrentPath($newData); + + // instead of passing the new +2 position below, we do it here + // manually. This is because this it's not a real position move, + // we manually (above) added some new chars that didn't exist before + $this->currentPosition += $newPositionBump; + + $this->updateContents( + $newContents, + $newData, + // position is unchanged: just some content was removed + $this->currentPosition + ); + } + + /** + * Replaces the value at the current position with this value. + * + * The position should be set right at the start of this value + * (i.e. after its key). + * + * @param mixed $value The new value to set into YAML + */ + private function changeValueInYaml(mixed $value): void + { + $originalVal = $this->getCurrentData(); + + $endValuePosition = $this->findEndPositionOfValue($originalVal); + + $isMultilineValue = null !== $this->findPositionOfMultilineCharInLine($this->currentPosition); + + // In case of multiline, $value is converted as plain string like "Foo\nBar" + // We need to keep it "as is" + $newYamlValue = $isMultilineValue ? rtrim($value, "\n") : $this->convertToYaml($value); + if ((!\is_array($originalVal) && \is_array($value)) + || ($this->isMultilineString($originalVal) && $this->isMultilineString($value)) + ) { + // we're converting from a scalar to a (multiline) array + // this means we need to break onto the next line + + // increase(override) the indentation + $newYamlValue = "\n".$this->indentMultilineYamlArray($newYamlValue, $this->indentationForDepths[$this->depth] + $this->getPreferredIndentationSize()); + } elseif ($this->isCurrentArrayMultiline() && $this->isCurrentArraySequence()) { + // we are a multi-line sequence, so drop to next line, indent and add "- " in front + $newYamlValue = "\n".$this->indentMultilineYamlArray('- '.$newYamlValue); + } else { + // empty space between key & value + $newYamlValue = ' '.$newYamlValue; + } + + $newPosition = $this->currentPosition + \strlen($newYamlValue); + $isNextContentComment = $this->isPreviousLineComment($newPosition); + if ($isNextContentComment) { + ++$newPosition; + } + + if ($isMultilineValue) { + // strlen(" |") + $newPosition -= 2; + } + + $newContents = substr($this->contents, 0, $this->currentPosition) + .($isMultilineValue ? ' |' : '') + .$newYamlValue + /* + * If the next line is a comment, this means we probably had + * a structure that looks like this: + * access_control: + * # - { path: ^/admin, roles: ROLE_ADMIN } + * + * In this odd case, we need to know that the next line + * is a comment, so we can add an extra line break. + * Otherwise, the result is something like: + * access_control: + * - { path: /foo, roles: ROLE_USER } # - { path: ^/admin, roles: ROLE_ADMIN } + */ + .($isNextContentComment ? "\n" : '') + .substr($this->contents, $endValuePosition); + + $newData = $this->currentData; + $newData = $this->setValueAtCurrentPath($value, $newData); + + $this->updateContents( + $newContents, + $newData, + $newPosition + ); + } + + private function advanceBeyondKey(int|string $key): void + { + $this->log(\sprintf('Advancing position beyond key "%s"', $key)); + $this->advanceCurrentPosition($this->getEndOfKeyPosition($key)); + } + + private function advanceBeyondEndOfPreviousKey($key): void + { + $this->log('Advancing position beyond PREV key'); + $this->advanceCurrentPosition($this->getEndOfPreviousKeyPosition($key)); + } + + private function advanceBeyondMultilineArrayLastItem(): void + { + $this->log('Trying to advance beyond the last item in a multiline array'); + $this->advanceBeyondWhitespace(); + + if ('#' === substr($this->contents, $this->currentPosition, 1)) { + $this->log('The line ends with a comment, going to EOL'); + $this->advanceToEndOfLine(); + + return; + } + + $nextLineBreak = $this->findNextLineBreak($this->currentPosition); + if ('}' === trim(substr($this->contents, $this->currentPosition, $nextLineBreak - $this->currentPosition))) { + $this->log('The line ends with an array closing brace, going to EOL'); + $this->advanceToEndOfLine(); + } + } + + private function advanceBeyondValue($value): void + { + if (\is_array($value)) { + throw new \LogicException('Do not pass an array to this method'); + } + + $this->log(\sprintf('Advancing position beyond value "%s"', $value)); + $this->advanceCurrentPosition($this->findEndPositionOfValue($value)); + } + + private function getEndOfKeyPosition($key): int + { + preg_match($this->getKeyRegex($key), $this->contents, $matches, \PREG_OFFSET_CAPTURE, $this->currentPosition); + + if (empty($matches)) { + // for integers, the key may not be explicitly printed + if (\is_int($key)) { + return $this->currentPosition; + } + + throw new YamlManipulationFailedException(\sprintf('Cannot find the key "%s"', $key)); + } + + return $matches[0][1] + \strlen($matches[0][0]); + } + + /** + * Finds the end position of the key that comes *before* this key. + */ + private function getEndOfPreviousKeyPosition($key): int + { + preg_match($this->getKeyRegex($key), $this->contents, $matches, \PREG_OFFSET_CAPTURE, $this->currentPosition); + + if (empty($matches)) { + // for integers, the key may not be explicitly printed + if (\is_int($key)) { + return $this->currentPosition; + } + + $cursor = $this->currentPosition; + + while ('-' !== substr($this->contents, $cursor - 1, 1) && -1 !== $cursor) { + --$cursor; + } + + if ($cursor >= 0) { + return $cursor; + } + + throw new YamlManipulationFailedException(\sprintf('Cannot find the key "%s"', $key)); + } + + $startOfKey = $matches[0][1]; + + // if we're at the start of the file, we're done! + if (0 === $startOfKey) { + return 0; + } + + /* + * Now, walk backwards: so that the position is before any + * whitespace, commas or line breaks. Basically, we want to go + * back to the first character *after* the previous key started. + */ + // walk back any spaces + while (' ' === substr($this->contents, $startOfKey - 1, 1)) { + --$startOfKey; + } + + // find either a line break or a , that is the end of the previous key + while (\in_array($char = substr($this->contents, $startOfKey - 1, 1), [',', "\n"])) { + --$startOfKey; + } + + // look for \r\n + if ("\r" === substr($this->contents, $startOfKey - 1, 1)) { + --$startOfKey; + } + + // if we're at the start of a line, if the prev line is a comment, move before it + if ($this->isCharLineBreak(substr($this->contents, $startOfKey, 1))) { + // move one (or two) forward so the code below finds the *previous* line + ++$startOfKey; + + if ($this->isCharLineBreak(substr($this->contents, $startOfKey, 1))) { + ++$startOfKey; + } + + /* + * In a multi-line array, the previous line(s) could be 100% comments. + * In that situation, we want to rewind to *before* the comments, so + * that those comments are attached to the current key and move with it. + */ + while ($this->isPreviousLineComment($startOfKey)) { + --$startOfKey; + // if this is a \n\r, we need to go back an extra char + if ("\r" === substr($this->contents, $startOfKey - 1, 1)) { + --$startOfKey; + } + + while (!$this->isCharLineBreak(substr($this->contents, $startOfKey - 1, 1))) { + --$startOfKey; + + // we've reached the start of the file! + if (0 === $startOfKey) { + break; + } + } + } + + if (0 !== $startOfKey) { + // move backwards one onto the previous line + --$startOfKey; + } + + // look for \n\r situation + if ("\r" === substr($this->contents, $startOfKey - 1, 1)) { + --$startOfKey; + } + } + + return $startOfKey; + } + + private function getKeyRegex($key): string + { + return \sprintf('#(?log('updateContents()'); + + // validate the data + try { + $parsedContentsData = Yaml::parse($newContents); + + // normalize indexes on sequences to avoid comparison problems + $parsedContentsData = $this->normalizeSequences($parsedContentsData); + $newData = $this->normalizeSequences($newData); + if ($parsedContentsData !== $newData) { + throw new YamlManipulationFailedException(\sprintf('Content was updated, but updated content does not match expected data. Original source: "%s", updated source: "%s", updated data: %s', $this->contents, $newContents, var_export($newData, true))); + } + } catch (ParseException) { + throw new YamlManipulationFailedException(\sprintf('Could not update YAML: a parse error occurred in the new content: "%s"', $newContents)); + } + + // must be called before changing the contents + $this->advanceCurrentPosition($newPosition); + $this->contents = $newContents; + $this->currentData = $newData; + } + + private function convertToYaml($data): string + { + $indent = $this->depth > 0 && isset($this->indentationForDepths[$this->depth]) + ? intdiv($this->indentationForDepths[$this->depth], $this->depth) + : 4; + + $newDataString = Yaml::dump($data, 4, $indent); + // new line is appended: remove it + $newDataString = rtrim($newDataString, "\n"); + + return $newDataString; + } + + /** + * Adds a new item (with the given key) to the $data array at the correct position. + * + * The $data should be the simple array that should be updated and that + * the current path is pointing to. The current path is used + * to determine *where* in the array to put the new item (so that it's + * placed in the middle when necessary). + */ + private function appendToArrayAtCurrentPath(string|int $key, $value, array $data): array + { + if ($this->isPositionAtBeginningOfArray()) { + // this should be prepended + return [$key => $value] + $data; + } + + $offset = array_search($this->previousPath[$this->depth], array_keys($data)); + + // if the target is currently the end of the array, just append + if ($offset === (\count($data) - 1)) { + $data[$key] = $value; + + return $data; + } + + return array_merge( + \array_slice($data, 0, $offset + 1), + [$key => $value], + \array_slice($data, $offset + 1, null) + ); + } + + private function setValueAtCurrentPath($value, array $data, int $limitLevels = 0) + { + // create a reference + $dataRef = &$data; + + // start depth at $limitLevels (instead of 0) to properly detect when to set the key + $depth = $limitLevels; + $path = \array_slice($this->currentPath, 0, \count($this->currentPath) - $limitLevels); + foreach ($path as $key) { + if (!\array_key_exists($key, $dataRef)) { + throw new \LogicException(\sprintf('Could not find the key "%s" from the current path "%s" in data "%s"', $key, implode(', ', $path), var_export($data, true))); + } + + if ($depth === $this->depth) { + // we're at the correct depth! + if (self::UNSET_KEY_FLAG === $value) { + unset($dataRef[$key]); + + // if this is a sequence, reindex the keys + if ($this->isCurrentArraySequence()) { + $dataRef = array_values($dataRef); + } + } else { + $dataRef[$key] = $value; + } + + return $data; + } + + // get a deeper reference + $dataRef = &$dataRef[$key]; + + ++$depth; + } + + throw new \LogicException('The value was not updated.'); + } + + private function removeKeyAtCurrentPath(array $data): array + { + return $this->setValueAtCurrentPath(self::UNSET_KEY_FLAG, $data); + } + + /** + * Returns the value in the current data that is currently + * being looked at. + * + * This could fail if the currentPath is for new data. + * + * @param int $limitLevels If set to 1, the data 1 level up will be returned + */ + private function getCurrentData(int $limitLevels = 0) + { + $data = $this->currentData; + $path = \array_slice($this->currentPath, 0, \count($this->currentPath) - $limitLevels); + foreach ($path as $key) { + if (!\array_key_exists($key, $data)) { + throw new \LogicException(\sprintf('Could not find the key "%s" from the current path "%s" in data "%s"', $key, implode(', ', $path), var_export($this->currentData, true))); + } + + $data = $data[$key]; + } + + return $data; + } + + private function findEndPositionOfValue($value, $offset = null) + { + if (\is_array($value)) { + $currentPosition = $this->currentPosition; + $this->log('Walking across array to find end position of array'); + $this->updateData($value); + $endKeyPosition = $this->currentPosition; + $this->currentPosition = $currentPosition; + + return $endKeyPosition; + } + + if (\is_scalar($value) || null === $value) { + $offset ??= $this->currentPosition; + + if (\is_bool($value)) { + // (?i) & (?-i) opens/closes case insensitive match + $pattern = \sprintf('(?i)%s(?-i)', $value ? 'true' : 'false'); + } elseif (null === $value) { + $pattern = '(~|NULL|null|\n)'; + } else { + // Multiline value ends with \n. + // If we remove this character, the next property will ne merged with this value + $quotedValue = preg_quote(rtrim($value, "\n"), '#'); + $patternValue = $quotedValue; + + // Iterates until we find a new line char or we reach end of file + if (null !== $this->findPositionOfMultilineCharInLine($offset)) { + $patternValue = str_replace(["\r\n", "\n"], '\r?\n\s*', $quotedValue); + } + + $pattern = \sprintf('\'?"?%s\'?"?', $patternValue); + } + + // a value like "foo:" can simply end a file + // this means the value is null + if ($offset === \strlen($this->contents)) { + return $offset; + } + + preg_match(\sprintf('#%s#', $pattern), $this->contents, $matches, \PREG_OFFSET_CAPTURE, $offset); + if (empty($matches)) { + throw new YamlManipulationFailedException(\sprintf('Cannot find the original value "%s"', $value)); + } + + $position = $matches[0][1] + \strlen($matches[0][0]); + + // edge case where there is a comment between the current position + // and the value we're looking for AND that comment contains an + // exact string match for the value we're looking for + if ($this->isFinalLineComment(substr($this->contents, $this->currentPosition, $position - $this->currentPosition))) { + return $this->findEndPositionOfValue($value, $position); + } + + if (null === $value && "\n" === $matches[0][0] && !$this->isCurrentLineComment($position)) { + $this->log('Zero-length null value, next line not a comment, take a step back'); + --$position; + } + + return $position; + } + + // there are other possible values, but we don't support them + throw new YamlManipulationFailedException(\sprintf('Unsupported Yaml value of type "%s"', \gettype($value))); + } + + private function advanceCurrentPosition(int $newPosition): void + { + $this->log(\sprintf('advanceCurrentPosition() from %d to %d', $this->currentPosition, $newPosition), true); + $originalPosition = $this->currentPosition; + $this->currentPosition = $newPosition; + + // if we're not changing, or moving backwards, don't count indent + // changes + if ($newPosition <= $originalPosition) { + return; + } + + /* + * A bit of a workaround. At times, this function will be called when the + * position is at the beginning of the line: so, one character *after* + * a line break. In that case, if there are a group of spaces at the + * beginning of this first line, they *should* be used to calculate the new + * indentation. To force this, if we detect this situation, we move one + * character backwards, so that the first line is considered a valid line + * to look for indentation. + */ + if ($this->isCharLineBreak(substr($this->contents, $originalPosition - 1, 1))) { + --$originalPosition; + } + + // look for empty lines and track the current indentation + $advancedContent = substr($this->contents, $originalPosition, $newPosition - $originalPosition); + $previousIndentation = $this->indentationForDepths[$this->depth]; + $newIndentation = $previousIndentation; + + if ("\n" === $advancedContent) { + $this->log('Just a linebreak, no indent changes'); + + return; + } + + if (str_starts_with($advancedContent, "\n#") || str_starts_with($advancedContent, "\r\n#")) { + $this->log('A linebreak followed by a root-level comment, no indent changes'); + + return; + } + + if (str_contains($advancedContent, "\n")) { + $lines = explode("\n", $advancedContent); + if (!empty($lines)) { + $lastLine = $lines[\count($lines) - 1]; + $lastLine = trim($lastLine, "\r"); + $indentation = 0; + while (' ' === substr($lastLine, $indentation, 1)) { + ++$indentation; + } + + $newIndentation = $indentation; + } + } + + $this->log(\sprintf('Calculating new indentation: changing from %d to %d', $this->indentationForDepths[$this->depth], $newIndentation), true); + $this->indentationForDepths[$this->depth] = $newIndentation; + } + + private function decrementDepth(): void + { + $this->log('Moving up 1 level of depth'); + unset($this->indentationForDepths[$this->depth]); + unset($this->arrayFormatForDepths[$this->depth]); + unset($this->arrayTypeForDepths[$this->depth]); + unset($this->currentPath[$this->depth]); + unset($this->previousPath[$this->depth]); + --$this->depth; + } + + private function getCurrentIndentation(?int $override = null): string + { + $indent = $override ?? $this->indentationForDepths[$this->depth]; + + return str_repeat(' ', $indent); + } + + private function log(string $message, bool $includeContent = false): void + { + if (null === $this->logger) { + return; + } + + $context = [ + 'key' => $this->currentPath[$this->depth] ?? 'n/a', + 'depth' => $this->depth, + 'position' => $this->currentPosition, + 'indentation' => $this->indentationForDepths[$this->depth], + 'type' => $this->arrayTypeForDepths[$this->depth], + 'format' => $this->arrayFormatForDepths[$this->depth], + ]; + + if ($includeContent) { + $context['content'] = \sprintf( + '>%s<', + str_replace(["\r\n", "\n"], ['\r\n', '\n'], substr($this->contents, $this->currentPosition, 50)) + ); + } + + $this->logger->debug($message, $context); + } + + private function isCurrentArrayMultiline(): bool + { + return self::ARRAY_FORMAT_MULTILINE === $this->arrayFormatForDepths[$this->depth]; + } + + private function isCurrentArraySequence(): bool + { + return self::ARRAY_TYPE_SEQUENCE === $this->arrayTypeForDepths[$this->depth]; + } + + /** + * Attempts to guess if the array at the current position + * is a multi-line array or an inline array. + */ + private function guessNextArrayTypeAndAdvance(): string + { + while (true) { + if ($this->isEOF()) { + throw new \LogicException('Could not determine array type'); + } + + // get the next char & advance immediately + $nextCharacter = substr($this->contents, $this->currentPosition, 1); + // advance, but without advanceCurrentPosition() + // because we are either moving along one line until [ { + // or we are finding a line break and stopping: indentation + // should not be calculated + ++$this->currentPosition; + + if ($this->isCharLineBreak($nextCharacter)) { + return self::ARRAY_FORMAT_MULTILINE; + } + + if ('[' === $nextCharacter || '{' === $nextCharacter) { + return self::ARRAY_FORMAT_INLINE; + } + } + } + + /** + * Advance until you find *one* of the characters in $chars. + */ + private function findPositionOfNextCharacter(array $chars) + { + $currentPosition = $this->currentPosition; + while (true) { + if ($this->isEOF($currentPosition)) { + throw new \LogicException(\sprintf('Could not find any characters: %s', implode(', ', $chars))); + } + + // get the next char & advance immediately + $nextCharacter = substr($this->contents, $currentPosition, 1); + ++$currentPosition; + + if (\in_array($nextCharacter, $chars)) { + return $currentPosition; + } + } + } + + private function advanceBeyondWhitespace(): void + { + while (' ' === substr($this->contents, $this->currentPosition, 1)) { + if ($this->isEOF()) { + return; + } + + ++$this->currentPosition; + } + } + + private function advanceToEndOfLine(): void + { + $newPosition = $this->currentPosition; + while (!$this->isCharLineBreak(substr($this->contents, $newPosition, 1))) { + if ($this->isEOF($newPosition)) { + // found the end of the file! + break; + } + + ++$newPosition; + } + + $this->advanceCurrentPosition($newPosition); + } + + /** + * Duplicated from Symfony's Inline::isHash(). + * + * Returns true if the value must be rendered as a hash, + * which includes an indexed array, if the indexes are + * not sequential. + */ + private function isHash($value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + $expectedKey = 0; + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + private function normalizeSequences(array $data): array + { + // https://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential/4254008#4254008 + $hasStringKeys = fn (array $array): bool => \count(array_filter(array_keys($array), 'is_string')) > 0; + + foreach ($data as $key => $val) { + if (!\is_array($val)) { + continue; + } + + if (!$hasStringKeys($val)) { + // avoid indexed arrays with non-sequential keys + // e.g. if a key was removed. This causes comparison issues + $val = array_values($val); + $data[$key] = $val; + } + + $data[$key] = $this->normalizeSequences($val); + } + + return $data; + } + + private function removeMetadataKeys(array $data): array + { + foreach ($data as $key => $val) { + if (\is_array($val)) { + $data[$key] = $this->removeMetadataKeys($val); + + continue; + } + + if (self::EMPTY_LINE_PLACEHOLDER_VALUE === $val) { + unset($data[$key]); + } + + if (null !== $val && str_starts_with($val, self::COMMENT_PLACEHOLDER_VALUE)) { + unset($data[$key]); + } + } + + return $data; + } + + private function replaceSpecialMetadataCharacters(): void + { + while (preg_match('#\n.*'.self::EMPTY_LINE_PLACEHOLDER_VALUE.'.*\n#', $this->contents, $matches)) { + $this->contents = str_replace($matches[0], "\n\n", $this->contents); + } + + while (preg_match('#\n(\s*).*\''.self::COMMENT_PLACEHOLDER_VALUE.'(.*)\'#', $this->contents, $matches)) { + $fullMatch = $matches[0]; + $indentation = $matches[1]; + $comment = $matches[2]; + + $this->contents = str_replace( + $fullMatch, + \sprintf("\n%s#%s", $indentation, $comment), + $this->contents + ); + } + } + + /** + * Try to guess a preferred indentation level. + */ + private function getPreferredIndentationSize(): int + { + return isset($this->indentationForDepths[1]) && $this->indentationForDepths[1] > 0 ? $this->indentationForDepths[1] : 4; + } + + /** + * For the array currently being processed, are we currently + * handling the first key inside of it? + */ + private function isPositionAtBeginningOfArray(): bool + { + return null === $this->previousPath[$this->depth]; + } + + private function manuallyIncrementIndentation(): void + { + $this->indentationForDepths[$this->depth] += $this->getPreferredIndentationSize(); + } + + private function isEOF(?int $position = null): bool + { + $position ??= $this->currentPosition; + + return $position === \strlen($this->contents); + } + + private function isPreviousLineComment(int $position): bool + { + $line = $this->getPreviousLine($position); + + if (null === $line) { + return false; + } + + return $this->isLineComment($line); + } + + private function isCurrentLineComment(int $position): bool + { + $line = $this->getCurrentLine($position); + + return $this->isLineComment($line); + } + + private function isLineComment(string $line): bool + { + // adopted from Parser::isCurrentLineComment() from symfony/yaml + $ltrimmedLine = ltrim($line, ' '); + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isFinalLineComment(string $content): bool + { + if (!$content) { + return false; + } + + $content = str_replace("\r", "\n", $content); + + $lines = explode("\n", $content); + $line = end($lines); + + return $this->isLineComment($line); + } + + private function getPreviousLine(int $position): ?string + { + // find the previous \n by finding the last one in the content up to the position + $endPos = strrpos(substr($this->contents, 0, $position), "\n"); + if (false === $endPos) { + // there is no previous line + return null; + } + + $startPos = strrpos(substr($this->contents, 0, $endPos), "\n"); + if (false === $startPos) { + // we're at the beginning of the file + $startPos = 0; + } else { + // move 1 past the line break + ++$startPos; + } + + $previousLine = substr($this->contents, $startPos, $endPos - $startPos); + + return trim($previousLine, "\r"); + } + + private function getCurrentLine(int $position): string + { + $startPos = strrpos(substr($this->contents, 0, $position), "\n") + 1; + $endPos = strpos($this->contents, "\n", $startPos); + + $this->log(\sprintf('Looking for current line from %d to %d', $startPos, $endPos)); + + $line = substr($this->contents, $startPos, $endPos - $startPos); + + return trim($line, "\r"); + } + + private function findNextLineBreak(int $position) + { + $nextNPos = strpos($this->contents, "\n", $position); + $nextRPos = strpos($this->contents, "\r", $position); + + if (false === $nextNPos) { + return false; + } + + if (false === $nextRPos) { + return $nextNPos; + } + + // find the first possible line break character + $nextLinePos = min($nextNPos, $nextRPos); + + // check for a \r\n situation + if (($nextLinePos + 1) === $nextNPos) { + ++$nextLinePos; + } + + return $nextLinePos; + } + + private function isCharLineBreak(string $char): bool + { + return "\n" === $char || "\r" === $char; + } + + /** + * Takes an unindented multi-line YAML string and indents it so + * it can be inserted into the current position. + * + * Usually an empty line needs to be prepended to this result before + * adding to the content. + */ + private function indentMultilineYamlArray(string $yaml, ?int $indentOverride = null): string + { + $indent = $this->getCurrentIndentation($indentOverride); + + // But, if the *value* is an array, then ITS children will + // also need to be indented artificially by the same amount + $yaml = str_replace("\n", "\n".$indent, $yaml); + + if ($this->isMultilineString($yaml)) { + // Remove extra indentation in case of blank line in multiline string + $yaml = str_replace("\n".$indent."\n", "\n\n", $yaml); + } + + // now indent this level + return $indent.$yaml; + } + + private function findPositionOfMultilineCharInLine(int $position): ?int + { + $cursor = $position; + while (!$this->isCharLineBreak($currentChar = substr($this->contents, $cursor + 1, 1)) && !$this->isEOF($cursor)) { + if ('|' === $currentChar) { + return $cursor; + } + + ++$cursor; + } + + return null; + } + + private function isMultilineString($value): bool + { + return \is_string($value) && str_contains($value, "\n"); + } +} diff --git a/vendor/symfony/maker-bundle/src/Validator.php b/vendor/symfony/maker-bundle/src/Validator.php new file mode 100644 index 0000000..4682624 --- /dev/null +++ b/vendor/symfony/maker-bundle/src/Validator.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle; + +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * @author Javier Eguiluz + * @author Ryan Weaver + * + * @internal + */ +final class Validator +{ + public static function validateClassName(string $className, string $errorMessage = ''): string + { + // remove potential opening slash so we don't match on it + $pieces = explode('\\', ltrim($className, '\\')); + $shortClassName = Str::getShortClassName($className); + + $reservedKeywords = ['__halt_compiler', 'abstract', 'and', 'array', + 'as', 'break', 'callable', 'case', 'catch', 'class', + 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', + 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', + 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', + 'exit', 'extends', 'final', 'finally', 'fn', 'for', 'foreach', 'function', + 'global', 'goto', 'if', 'implements', 'include', + 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', + 'list', 'match', 'namespace', 'new', 'or', 'print', 'private', + 'protected', 'public', 'readonly', 'require', 'require_once', 'return', + 'static', 'switch', 'throw', 'trait', 'try', 'unset', + 'use', 'var', 'while', 'xor', 'yield', + 'int', 'float', 'bool', 'string', 'true', 'false', 'null', 'void', + 'iterable', 'object', '__file__', '__line__', '__dir__', '__function__', '__class__', + '__method__', '__namespace__', '__trait__', 'self', 'parent', 'collection', + ]; + + foreach ($pieces as $piece) { + if (!mb_check_encoding($piece, 'UTF-8')) { + $errorMessage = $errorMessage ?: \sprintf('"%s" is not a UTF-8-encoded string.', $piece); + + throw new RuntimeCommandException($errorMessage); + } + + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $piece)) { + $errorMessage = $errorMessage ?: \sprintf('"%s" is not valid as a PHP class name (it must start with a letter or underscore, followed by any number of letters, numbers, or underscores)', $className); + + throw new RuntimeCommandException($errorMessage); + } + + if (\in_array(strtolower($shortClassName), $reservedKeywords, true)) { + throw new RuntimeCommandException(\sprintf('"%s" is a reserved keyword and thus cannot be used as class name in PHP.', $shortClassName)); + } + } + + // return original class name + return $className; + } + + public static function notBlank(?string $value = null): string + { + if (null === $value || '' === $value) { + throw new RuntimeCommandException('This value cannot be blank.'); + } + + return $value; + } + + public static function validateLength($length) + { + if (!$length) { + return $length; + } + + $result = filter_var($length, \FILTER_VALIDATE_INT, [ + 'options' => ['min_range' => 1], + ]); + + if (false === $result) { + throw new RuntimeCommandException(\sprintf('Invalid length "%s".', $length)); + } + + return $result; + } + + public static function validatePrecision($precision) + { + if (!$precision) { + return $precision; + } + + $result = filter_var($precision, \FILTER_VALIDATE_INT, [ + 'options' => ['min_range' => 1, 'max_range' => 65], + ]); + + if (false === $result) { + throw new RuntimeCommandException(\sprintf('Invalid precision "%s".', $precision)); + } + + return $result; + } + + public static function validateScale($scale) + { + if (!$scale) { + return $scale; + } + + $result = filter_var($scale, \FILTER_VALIDATE_INT, [ + 'options' => ['min_range' => 0, 'max_range' => 30], + ]); + + if (false === $result) { + throw new RuntimeCommandException(\sprintf('Invalid scale "%s".', $scale)); + } + + return $result; + } + + public static function validateBoolean($value) + { + if ('yes' == $value) { + return true; + } + + if ('no' == $value) { + return false; + } + + if (null === $valueAsBool = filter_var($value, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE)) { + throw new RuntimeCommandException(\sprintf('Invalid bool value "%s".', $value)); + } + + return $valueAsBool; + } + + public static function validatePropertyName(string $name): string + { + // check for valid PHP variable name + if (!Str::isValidPhpVariableName($name)) { + throw new \InvalidArgumentException(\sprintf('"%s" is not a valid PHP property name.', $name)); + } + + return $name; + } + + public static function validateDoctrineFieldName(string $name, ManagerRegistry $registry): string + { + // check reserved words + if ($registry->getConnection()->getDatabasePlatform()->getReservedKeywordsList()->isKeyword($name)) { + throw new \InvalidArgumentException(\sprintf('Name "%s" is a reserved word.', $name)); + } + + self::validatePropertyName($name); + + return $name; + } + + public static function validateEmailAddress(?string $email): string + { + if (!filter_var($email, \FILTER_VALIDATE_EMAIL)) { + throw new RuntimeCommandException(\sprintf('"%s" is not a valid email address.', $email)); + } + + return $email; + } + + public static function existsOrNull(?string $className = null, array $entities = []): ?string + { + if (null !== $className) { + self::validateClassName($className); + + if (str_starts_with($className, '\\')) { + self::classExists($className); + } else { + self::entityExists($className, $entities); + } + } + + return $className; + } + + public static function classExists(string $className, string $errorMessage = ''): string + { + self::notBlank($className); + + if (!class_exists($className)) { + $errorMessage = $errorMessage ?: \sprintf('Class "%s" doesn\'t exist; please enter an existing full class name.', $className); + + throw new RuntimeCommandException($errorMessage); + } + + return $className; + } + + public static function entityExists(?string $className = null, array $entities = []): string + { + self::notBlank($className); + + if (empty($entities)) { + throw new RuntimeCommandException('There are no registered entities; please create an entity before using this command.'); + } + + if (str_starts_with($className, '\\')) { + self::classExists($className, \sprintf('Entity "%s" doesn\'t exist; please enter an existing one or create a new one.', $className)); + } + + if (!\in_array($className, $entities) && !\in_array(ltrim($className, '\\'), $entities)) { + throw new RuntimeCommandException(\sprintf('Entity "%s" doesn\'t exist; please enter an existing one or create a new one.', $className)); + } + + return $className; + } + + public static function classDoesNotExist($className): string + { + self::notBlank($className); + + if (class_exists($className)) { + throw new RuntimeCommandException(\sprintf('Class "%s" already exists.', $className)); + } + + return $className; + } + + public static function classIsUserInterface($userClassName): string + { + self::classExists($userClassName); + + if (!isset(class_implements($userClassName)[UserInterface::class])) { + throw new RuntimeCommandException(\sprintf('The class "%s" must implement "%s".', $userClassName, UserInterface::class)); + } + + return $userClassName; + } + + public static function classIsBackedEnum($backedEnum): string + { + self::classExists($backedEnum); + + if (!isset(class_implements($backedEnum)[\BackedEnum::class])) { + throw new RuntimeCommandException(\sprintf('The class "%s" is not a valid BackedEnum.', $backedEnum)); + } + + return $backedEnum; + } +} diff --git a/vendor/symfony/mime/Address.php b/vendor/symfony/mime/Address.php new file mode 100644 index 0000000..ce57f77 --- /dev/null +++ b/vendor/symfony/mime/Address.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Egulias\EmailValidator\EmailValidator; +use Egulias\EmailValidator\Validation\MessageIDValidation; +use Egulias\EmailValidator\Validation\RFCValidation; +use Symfony\Component\Mime\Encoder\IdnAddressEncoder; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\LogicException; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * @author Fabien Potencier + */ +final class Address +{ + /** + * A regex that matches a structure like 'Name '. + * It matches anything between the first < and last > as email address. + * This allows to use a single string to construct an Address, which can be convenient to use in + * config, and allows to have more readable config. + * This does not try to cover all edge cases for address. + */ + private const FROM_STRING_PATTERN = '~(?[^<]*)<(?.*)>[^>]*~'; + + private static EmailValidator $validator; + private static IdnAddressEncoder $encoder; + + private string $address; + private string $name; + + public function __construct(string $address, string $name = '') + { + if (!class_exists(EmailValidator::class)) { + throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s". Try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class)); + } + + self::$validator ??= new EmailValidator(); + + $this->address = trim($address); + $this->name = trim(str_replace(["\n", "\r"], '', $name)); + + if (!self::$validator->isValid($this->address, class_exists(MessageIDValidation::class) ? new MessageIDValidation() : new RFCValidation())) { + throw new RfcComplianceException(sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address)); + } + } + + public function getAddress(): string + { + return $this->address; + } + + public function getName(): string + { + return $this->name; + } + + public function getEncodedAddress(): string + { + self::$encoder ??= new IdnAddressEncoder(); + + return self::$encoder->encodeString($this->address); + } + + public function toString(): string + { + return ($n = $this->getEncodedName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress(); + } + + public function getEncodedName(): string + { + if ('' === $this->getName()) { + return ''; + } + + return sprintf('"%s"', preg_replace('/"/u', '\"', $this->getName())); + } + + public static function create(self|string $address): self + { + if ($address instanceof self) { + return $address; + } + + if (!str_contains($address, '<')) { + return new self($address); + } + + if (!preg_match(self::FROM_STRING_PATTERN, $address, $matches)) { + throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $address, self::class)); + } + + return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"')); + } + + /** + * @param array $addresses + * + * @return Address[] + */ + public static function createArray(array $addresses): array + { + $addrs = []; + foreach ($addresses as $address) { + $addrs[] = self::create($address); + } + + return $addrs; + } +} diff --git a/vendor/symfony/mime/BodyRendererInterface.php b/vendor/symfony/mime/BodyRendererInterface.php new file mode 100644 index 0000000..d692172 --- /dev/null +++ b/vendor/symfony/mime/BodyRendererInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * @author Fabien Potencier + */ +interface BodyRendererInterface +{ + public function render(Message $message): void; +} diff --git a/vendor/symfony/mime/CHANGELOG.md b/vendor/symfony/mime/CHANGELOG.md new file mode 100644 index 0000000..8d593e6 --- /dev/null +++ b/vendor/symfony/mime/CHANGELOG.md @@ -0,0 +1,57 @@ +CHANGELOG +========= + +7.0 +--- + + * Remove `Email::attachPart()`, use `Email::addPart()` instead + * Argument `$body` is now required (at least null) in `Message::setBody()` + * Require explicit argument when calling `Message::setBody()` + +6.3 +--- + + * Support detection of related parts if `Content-Id` is used instead of the name + * Add `TextPart::getDisposition()` + +6.2 +--- + + * Add `File` + * Deprecate `Email::attachPart()`, use `addPart()` instead + * Deprecate calling `Message::setBody()` without arguments + +6.1 +--- + + * Add `DataPart::getFilename()` and `DataPart::getContentType()` + +6.0 +--- + + * Remove `Address::fromString()`, use `Address::create()` instead + * Remove `Serializable` interface from `RawMessage` + +5.2.0 +----- + + * Add support for DKIM + * Deprecated `Address::fromString()`, use `Address::create()` instead + +4.4.0 +----- + + * [BC BREAK] Removed `NamedAddress` (`Address` now supports a name) + * Added PHPUnit constraints + * Added `AbstractPart::asDebugString()` + * Added `Address::fromString()` + +4.3.3 +----- + + * [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`. + +4.3.0 +----- + + * Introduced the component as experimental diff --git a/vendor/symfony/mime/CharacterStream.php b/vendor/symfony/mime/CharacterStream.php new file mode 100644 index 0000000..572cf82 --- /dev/null +++ b/vendor/symfony/mime/CharacterStream.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * @author Fabien Potencier + * @author Xavier De Cock + * + * @internal + */ +final class CharacterStream +{ + /** Pre-computed for optimization */ + private const UTF8_LENGTH_MAP = [ + "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1, + "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1, + "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1, + "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1, + "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1, + "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1, + "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1, + "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1, + "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1, + "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1, + "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1, + "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1, + "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1, + "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1, + "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1, + "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1, + "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0, + "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0, + "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0, + "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0, + "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0, + "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0, + "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0, + "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0, + "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2, + "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2, + "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2, + "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2, + "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3, + "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3, + "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4, + "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0, + ]; + + private string $data = ''; + private int $dataSize = 0; + private array $map = []; + private int $charCount = 0; + private int $currentPos = 0; + private int $fixedWidth = 0; + + /** + * @param resource|string $input + */ + public function __construct($input, ?string $charset = 'utf-8') + { + $charset = strtolower(trim($charset)) ?: 'utf-8'; + if ('utf-8' === $charset || 'utf8' === $charset) { + $this->fixedWidth = 0; + $this->map = ['p' => [], 'i' => []]; + } else { + $this->fixedWidth = match ($charset) { + // 16 bits + 'ucs2', + 'ucs-2', + 'utf16', + 'utf-16' => 2, + // 32 bits + 'ucs4', + 'ucs-4', + 'utf32', + 'utf-32' => 4, + // 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh, + // koi-?7, koi-?8-?.+, mik, (cork|t1), v?iscii + // and fallback + default => 1, + }; + } + if (\is_resource($input)) { + $blocks = 16372; + while (false !== $read = fread($input, $blocks)) { + $this->write($read); + } + } else { + $this->write($input); + } + } + + public function read(int $length): ?string + { + if ($this->currentPos >= $this->charCount) { + return null; + } + $length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length; + if ($this->fixedWidth > 0) { + $len = $length * $this->fixedWidth; + $ret = substr($this->data, $this->currentPos * $this->fixedWidth, $len); + $this->currentPos += $length; + } else { + $end = $this->currentPos + $length; + $end = min($end, $this->charCount); + $ret = ''; + $start = 0; + if ($this->currentPos > 0) { + $start = $this->map['p'][$this->currentPos - 1]; + } + $to = $start; + for (; $this->currentPos < $end; ++$this->currentPos) { + if (isset($this->map['i'][$this->currentPos])) { + $ret .= substr($this->data, $start, $to - $start).'?'; + $start = $this->map['p'][$this->currentPos]; + } else { + $to = $this->map['p'][$this->currentPos]; + } + } + $ret .= substr($this->data, $start, $to - $start); + } + + return $ret; + } + + public function readBytes(int $length): ?array + { + if (null !== $read = $this->read($length)) { + return array_map('ord', str_split($read, 1)); + } + + return null; + } + + public function setPointer(int $charOffset): void + { + if ($this->charCount < $charOffset) { + $charOffset = $this->charCount; + } + $this->currentPos = $charOffset; + } + + public function write(string $chars): void + { + $ignored = ''; + $this->data .= $chars; + if ($this->fixedWidth > 0) { + $strlen = \strlen($chars); + $ignoredL = $strlen % $this->fixedWidth; + $ignored = $ignoredL ? substr($chars, -$ignoredL) : ''; + $this->charCount += ($strlen - $ignoredL) / $this->fixedWidth; + } else { + $this->charCount += $this->getUtf8CharPositions($chars, $this->dataSize, $ignored); + } + $this->dataSize = \strlen($this->data) - \strlen($ignored); + } + + private function getUtf8CharPositions(string $string, int $startOffset, string &$ignoredChars): int + { + $strlen = \strlen($string); + $charPos = \count($this->map['p']); + $foundChars = 0; + $invalid = false; + for ($i = 0; $i < $strlen; ++$i) { + $char = $string[$i]; + $size = self::UTF8_LENGTH_MAP[$char]; + if (0 == $size) { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue; + } + + if ($invalid) { + /* We mark the chars as invalid and start a new char */ + $this->map['p'][$charPos + $foundChars] = $startOffset + $i; + $this->map['i'][$charPos + $foundChars] = true; + ++$foundChars; + $invalid = false; + } + if (($i + $size) > $strlen) { + $ignoredChars = substr($string, $i); + break; + } + for ($j = 1; $j < $size; ++$j) { + $char = $string[$i + $j]; + if ($char > "\x7F" && $char < "\xC0") { + // Valid - continue parsing + } else { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue 2; + } + } + /* Ok we got a complete char here */ + $this->map['p'][$charPos + $foundChars] = $startOffset + $i + $size; + $i += $j - 1; + ++$foundChars; + } + + return $foundChars; + } +} diff --git a/vendor/symfony/mime/Crypto/DkimOptions.php b/vendor/symfony/mime/Crypto/DkimOptions.php new file mode 100644 index 0000000..cee4e7c --- /dev/null +++ b/vendor/symfony/mime/Crypto/DkimOptions.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +/** + * A helper providing autocompletion for available DkimSigner options. + * + * @author Fabien Potencier + */ +final class DkimOptions +{ + private array $options = []; + + public function toArray(): array + { + return $this->options; + } + + /** + * @return $this + */ + public function algorithm(string $algo): static + { + $this->options['algorithm'] = $algo; + + return $this; + } + + /** + * @return $this + */ + public function signatureExpirationDelay(int $show): static + { + $this->options['signature_expiration_delay'] = $show; + + return $this; + } + + /** + * @return $this + */ + public function bodyMaxLength(int $max): static + { + $this->options['body_max_length'] = $max; + + return $this; + } + + /** + * @return $this + */ + public function bodyShowLength(bool $show): static + { + $this->options['body_show_length'] = $show; + + return $this; + } + + /** + * @return $this + */ + public function headerCanon(string $canon): static + { + $this->options['header_canon'] = $canon; + + return $this; + } + + /** + * @return $this + */ + public function bodyCanon(string $canon): static + { + $this->options['body_canon'] = $canon; + + return $this; + } + + /** + * @return $this + */ + public function headersToIgnore(array $headers): static + { + $this->options['headers_to_ignore'] = $headers; + + return $this; + } +} diff --git a/vendor/symfony/mime/Crypto/DkimSigner.php b/vendor/symfony/mime/Crypto/DkimSigner.php new file mode 100644 index 0000000..93a59b4 --- /dev/null +++ b/vendor/symfony/mime/Crypto/DkimSigner.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Header\UnstructuredHeader; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\Part\AbstractPart; + +/** + * @author Fabien Potencier + * + * RFC 6376 and 8301 + */ +final class DkimSigner +{ + public const CANON_SIMPLE = 'simple'; + public const CANON_RELAXED = 'relaxed'; + + public const ALGO_SHA256 = 'rsa-sha256'; + public const ALGO_ED25519 = 'ed25519-sha256'; // RFC 8463 + + private \OpenSSLAsymmetricKey $key; + + /** + * @param string $pk The private key as a string or the path to the file containing the private key, should be prefixed with file:// (in PEM format) + * @param string $passphrase A passphrase of the private key (if any) + */ + public function __construct( + string $pk, + private string $domainName, + private string $selector, + private array $defaultOptions = [], + string $passphrase = '', + ) { + if (!\extension_loaded('openssl')) { + throw new \LogicException('PHP extension "openssl" is required to use DKIM.'); + } + $this->key = openssl_pkey_get_private($pk, $passphrase) ?: throw new InvalidArgumentException('Unable to load DKIM private key: '.openssl_error_string()); + $this->defaultOptions += [ + 'algorithm' => self::ALGO_SHA256, + 'signature_expiration_delay' => 0, + 'body_max_length' => \PHP_INT_MAX, + 'body_show_length' => false, + 'header_canon' => self::CANON_RELAXED, + 'body_canon' => self::CANON_RELAXED, + 'headers_to_ignore' => [], + ]; + } + + public function sign(Message $message, array $options = []): Message + { + $options += $this->defaultOptions; + if (!\in_array($options['algorithm'], [self::ALGO_SHA256, self::ALGO_ED25519], true)) { + throw new InvalidArgumentException(sprintf('Invalid DKIM signing algorithm "%s".', $options['algorithm'])); + } + $headersToIgnore['return-path'] = true; + $headersToIgnore['x-transport'] = true; + foreach ($options['headers_to_ignore'] as $name) { + $headersToIgnore[strtolower($name)] = true; + } + unset($headersToIgnore['from']); + $signedHeaderNames = []; + $headerCanonData = ''; + $headers = $message->getPreparedHeaders(); + foreach ($headers->getNames() as $name) { + foreach ($headers->all($name) as $header) { + if (isset($headersToIgnore[strtolower($header->getName())])) { + continue; + } + + if ('' !== $header->getBodyAsString()) { + $headerCanonData .= $this->canonicalizeHeader($header->toString(), $options['header_canon']); + $signedHeaderNames[] = $header->getName(); + } + } + } + + [$bodyHash, $bodyLength] = $this->hashBody($message->getBody(), $options['body_canon'], $options['body_max_length']); + + $params = [ + 'v' => '1', + 'q' => 'dns/txt', + 'a' => $options['algorithm'], + 'bh' => base64_encode($bodyHash), + 'd' => $this->domainName, + 'h' => implode(': ', $signedHeaderNames), + 'i' => '@'.$this->domainName, + 's' => $this->selector, + 't' => time(), + 'c' => $options['header_canon'].'/'.$options['body_canon'], + ]; + + if ($options['body_show_length']) { + $params['l'] = $bodyLength; + } + if ($options['signature_expiration_delay']) { + $params['x'] = $params['t'] + $options['signature_expiration_delay']; + } + $value = ''; + foreach ($params as $k => $v) { + $value .= $k.'='.$v.'; '; + } + $value = trim($value); + $header = new UnstructuredHeader('DKIM-Signature', $value); + $headerCanonData .= rtrim($this->canonicalizeHeader($header->toString()."\r\n b=", $options['header_canon'])); + if (self::ALGO_SHA256 === $options['algorithm']) { + if (!openssl_sign($headerCanonData, $signature, $this->key, \OPENSSL_ALGO_SHA256)) { + throw new RuntimeException('Unable to sign DKIM hash: '.openssl_error_string()); + } + } else { + throw new \RuntimeException(sprintf('The "%s" DKIM signing algorithm is not supported yet.', self::ALGO_ED25519)); + } + $header->setValue($value.' b='.trim(chunk_split(base64_encode($signature), 73, ' '))); + $headers->add($header); + + return new Message($headers, $message->getBody()); + } + + private function canonicalizeHeader(string $header, string $headerCanon): string + { + if (self::CANON_RELAXED !== $headerCanon) { + return $header."\r\n"; + } + + $exploded = explode(':', $header, 2); + $name = strtolower(trim($exploded[0])); + $value = str_replace("\r\n", '', $exploded[1]); + $value = trim(preg_replace("/[ \t][ \t]+/", ' ', $value)); + + return $name.':'.$value."\r\n"; + } + + private function hashBody(AbstractPart $body, string $bodyCanon, int $maxLength): array + { + $hash = hash_init('sha256'); + $relaxed = self::CANON_RELAXED === $bodyCanon; + $currentLine = ''; + $emptyCounter = 0; + $isSpaceSequence = false; + $length = 0; + foreach ($body->bodyToIterable() as $chunk) { + $canon = ''; + for ($i = 0, $len = \strlen($chunk); $i < $len; ++$i) { + switch ($chunk[$i]) { + case "\r": + break; + case "\n": + // previous char is always \r + if ($relaxed) { + $isSpaceSequence = false; + } + if ('' === $currentLine) { + ++$emptyCounter; + } else { + $currentLine = ''; + $canon .= "\r\n"; + } + break; + case ' ': + case "\t": + if ($relaxed) { + $isSpaceSequence = true; + break; + } + // no break + default: + if ($emptyCounter > 0) { + $canon .= str_repeat("\r\n", $emptyCounter); + $emptyCounter = 0; + } + if ($isSpaceSequence) { + $currentLine .= ' '; + $canon .= ' '; + $isSpaceSequence = false; + } + $currentLine .= $chunk[$i]; + $canon .= $chunk[$i]; + } + } + + if ($length + \strlen($canon) >= $maxLength) { + $canon = substr($canon, 0, $maxLength - $length); + $length += \strlen($canon); + hash_update($hash, $canon); + + break; + } + + $length += \strlen($canon); + hash_update($hash, $canon); + } + + // Add trailing Line return if last line is non empty + if ('' !== $currentLine) { + hash_update($hash, "\r\n"); + $length += \strlen("\r\n"); + } + + if (!$relaxed && 0 === $length) { + hash_update($hash, "\r\n"); + $length = 2; + } + + return [hash_final($hash, true), $length]; + } +} diff --git a/vendor/symfony/mime/Crypto/SMime.php b/vendor/symfony/mime/Crypto/SMime.php new file mode 100644 index 0000000..cba95f2 --- /dev/null +++ b/vendor/symfony/mime/Crypto/SMime.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Part\SMimePart; + +/** + * @author Sebastiaan Stok + * + * @internal + */ +abstract class SMime +{ + protected function normalizeFilePath(string $path): string + { + if (!file_exists($path)) { + throw new RuntimeException(sprintf('File does not exist: "%s".', $path)); + } + + return 'file://'.str_replace('\\', '/', realpath($path)); + } + + protected function iteratorToFile(iterable $iterator, $stream): void + { + foreach ($iterator as $chunk) { + fwrite($stream, $chunk); + } + } + + protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart + { + rewind($stream); + + $headers = ''; + + while (!feof($stream)) { + $buffer = fread($stream, 78); + $headers .= $buffer; + + // Detect ending of header list + if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) { + $headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]); + + break; + } + } + + $headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd))); + + fseek($stream, $headersPosEnd + \strlen($headerBodySeparator)); + + return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type'])); + } + + protected function getStreamIterator($stream): iterable + { + while (!feof($stream)) { + yield str_replace("\n", "\r\n", str_replace("\r\n", "\n", fread($stream, 16372))); + } + } + + private function getMessageHeaders(string $headerData): array + { + $headers = []; + $headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData))); + $currentHeaderName = ''; + + // Transform header lines into an associative array + foreach ($headerLines as $headerLine) { + // Empty lines between headers indicate a new mime-entity + if ('' === $headerLine) { + break; + } + + // Handle headers that span multiple lines + if (!str_contains($headerLine, ':')) { + $headers[$currentHeaderName] .= ' '.trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + return $headers; + } + + private function getParametersFromHeader(string $header): array + { + $params = []; + + preg_match_all('/(?P[a-z-0-9]+)=(?P"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches); + + foreach ($matches['value'] as $pos => $paramValue) { + $params[$matches['name'][$pos]] = trim($paramValue, '"'); + } + + return $params; + } +} diff --git a/vendor/symfony/mime/Crypto/SMimeEncrypter.php b/vendor/symfony/mime/Crypto/SMimeEncrypter.php new file mode 100644 index 0000000..c7c0545 --- /dev/null +++ b/vendor/symfony/mime/Crypto/SMimeEncrypter.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Message; + +/** + * @author Sebastiaan Stok + */ +final class SMimeEncrypter extends SMime +{ + private string|array $certs; + private int $cipher; + + /** + * @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s) + * @param int|null $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php + */ + public function __construct(string|array $certificate, ?int $cipher = null) + { + if (!\extension_loaded('openssl')) { + throw new \LogicException('PHP extension "openssl" is required to use SMime.'); + } + + if (\is_array($certificate)) { + $this->certs = array_map($this->normalizeFilePath(...), $certificate); + } else { + $this->certs = $this->normalizeFilePath($certificate); + } + + $this->cipher = $cipher ?? \OPENSSL_CIPHER_AES_256_CBC; + } + + public function encrypt(Message $message): Message + { + $bufferFile = tmpfile(); + $outputFile = tmpfile(); + + $this->iteratorToFile($message->toIterable(), $bufferFile); + + if (!@openssl_pkcs7_encrypt(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->certs, [], 0, $this->cipher)) { + throw new RuntimeException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string())); + } + + $mimePart = $this->convertMessageToSMimePart($outputFile, 'application', 'pkcs7-mime'); + $mimePart->getHeaders() + ->addTextHeader('Content-Transfer-Encoding', 'base64') + ->addParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'smime.p7m']) + ; + + return new Message($message->getHeaders(), $mimePart); + } +} diff --git a/vendor/symfony/mime/Crypto/SMimeSigner.php b/vendor/symfony/mime/Crypto/SMimeSigner.php new file mode 100644 index 0000000..eaa423d --- /dev/null +++ b/vendor/symfony/mime/Crypto/SMimeSigner.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Message; + +/** + * @author Sebastiaan Stok + */ +final class SMimeSigner extends SMime +{ + private string $signCertificate; + private string|array $signPrivateKey; + private int $signOptions; + private ?string $extraCerts; + + /** + * @param string $certificate The path of the file containing the signing certificate (in PEM format) + * @param string $privateKey The path of the file containing the private key (in PEM format) + * @param string|null $privateKeyPassphrase A passphrase of the private key (if any) + * @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate + * @param int|null $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php) + */ + public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, ?int $signOptions = null) + { + if (!\extension_loaded('openssl')) { + throw new \LogicException('PHP extension "openssl" is required to use SMime.'); + } + + $this->signCertificate = $this->normalizeFilePath($certificate); + + if (null !== $privateKeyPassphrase) { + $this->signPrivateKey = [$this->normalizeFilePath($privateKey), $privateKeyPassphrase]; + } else { + $this->signPrivateKey = $this->normalizeFilePath($privateKey); + } + + $this->signOptions = $signOptions ?? \PKCS7_DETACHED; + $this->extraCerts = $extraCerts ? realpath($extraCerts) : null; + } + + public function sign(Message $message): Message + { + $bufferFile = tmpfile(); + $outputFile = tmpfile(); + + $this->iteratorToFile($message->getBody()->toIterable(), $bufferFile); + + if (!@openssl_pkcs7_sign(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->signCertificate, $this->signPrivateKey, [], $this->signOptions, $this->extraCerts)) { + throw new RuntimeException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string())); + } + + return new Message($message->getHeaders(), $this->convertMessageToSMimePart($outputFile, 'multipart', 'signed')); + } +} diff --git a/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php b/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php new file mode 100644 index 0000000..897743f --- /dev/null +++ b/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers custom mime types guessers. + * + * @author Fabien Potencier + */ +class AddMimeTypeGuesserPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if ($container->has('mime_types')) { + $definition = $container->findDefinition('mime_types'); + foreach ($container->findTaggedServiceIds('mime.mime_type_guesser', true) as $id => $attributes) { + $definition->addMethodCall('registerGuesser', [new Reference($id)]); + } + } + } +} diff --git a/vendor/symfony/mime/DraftEmail.php b/vendor/symfony/mime/DraftEmail.php new file mode 100644 index 0000000..82ce763 --- /dev/null +++ b/vendor/symfony/mime/DraftEmail.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Part\AbstractPart; + +/** + * @author Kevin Bond + */ +class DraftEmail extends Email +{ + public function __construct(?Headers $headers = null, ?AbstractPart $body = null) + { + parent::__construct($headers, $body); + + $this->getHeaders()->addTextHeader('X-Unsent', '1'); + } + + /** + * Override default behavior as draft emails do not require From/Sender/Date/Message-ID headers. + * These are added by the client that actually sends the email. + */ + public function getPreparedHeaders(): Headers + { + $headers = clone $this->getHeaders(); + + if (!$headers->has('MIME-Version')) { + $headers->addTextHeader('MIME-Version', '1.0'); + } + + $headers->remove('Bcc'); + + return $headers; + } +} diff --git a/vendor/symfony/mime/Email.php b/vendor/symfony/mime/Email.php new file mode 100644 index 0000000..233afc0 --- /dev/null +++ b/vendor/symfony/mime/Email.php @@ -0,0 +1,576 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\File; +use Symfony\Component\Mime\Part\Multipart\AlternativePart; +use Symfony\Component\Mime\Part\Multipart\MixedPart; +use Symfony\Component\Mime\Part\Multipart\RelatedPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * @author Fabien Potencier + */ +class Email extends Message +{ + public const PRIORITY_HIGHEST = 1; + public const PRIORITY_HIGH = 2; + public const PRIORITY_NORMAL = 3; + public const PRIORITY_LOW = 4; + public const PRIORITY_LOWEST = 5; + + private const PRIORITY_MAP = [ + self::PRIORITY_HIGHEST => 'Highest', + self::PRIORITY_HIGH => 'High', + self::PRIORITY_NORMAL => 'Normal', + self::PRIORITY_LOW => 'Low', + self::PRIORITY_LOWEST => 'Lowest', + ]; + + /** + * @var resource|string|null + */ + private $text; + + private ?string $textCharset = null; + + /** + * @var resource|string|null + */ + private $html; + + private ?string $htmlCharset = null; + private array $attachments = []; + private ?AbstractPart $cachedBody = null; // Used to avoid wrong body hash in DKIM signatures with multiple parts (e.g. HTML + TEXT) due to multiple boundaries. + + /** + * @return $this + */ + public function subject(string $subject): static + { + return $this->setHeaderBody('Text', 'Subject', $subject); + } + + public function getSubject(): ?string + { + return $this->getHeaders()->getHeaderBody('Subject'); + } + + /** + * @return $this + */ + public function date(\DateTimeInterface $dateTime): static + { + return $this->setHeaderBody('Date', 'Date', $dateTime); + } + + public function getDate(): ?\DateTimeImmutable + { + return $this->getHeaders()->getHeaderBody('Date'); + } + + /** + * @return $this + */ + public function returnPath(Address|string $address): static + { + return $this->setHeaderBody('Path', 'Return-Path', Address::create($address)); + } + + public function getReturnPath(): ?Address + { + return $this->getHeaders()->getHeaderBody('Return-Path'); + } + + /** + * @return $this + */ + public function sender(Address|string $address): static + { + return $this->setHeaderBody('Mailbox', 'Sender', Address::create($address)); + } + + public function getSender(): ?Address + { + return $this->getHeaders()->getHeaderBody('Sender'); + } + + /** + * @return $this + */ + public function addFrom(Address|string ...$addresses): static + { + return $this->addListAddressHeaderBody('From', $addresses); + } + + /** + * @return $this + */ + public function from(Address|string ...$addresses): static + { + if (!$addresses) { + throw new LogicException('"from()" must be called with at least one address.'); + } + + return $this->setListAddressHeaderBody('From', $addresses); + } + + /** + * @return Address[] + */ + public function getFrom(): array + { + return $this->getHeaders()->getHeaderBody('From') ?: []; + } + + /** + * @return $this + */ + public function addReplyTo(Address|string ...$addresses): static + { + return $this->addListAddressHeaderBody('Reply-To', $addresses); + } + + /** + * @return $this + */ + public function replyTo(Address|string ...$addresses): static + { + return $this->setListAddressHeaderBody('Reply-To', $addresses); + } + + /** + * @return Address[] + */ + public function getReplyTo(): array + { + return $this->getHeaders()->getHeaderBody('Reply-To') ?: []; + } + + /** + * @return $this + */ + public function addTo(Address|string ...$addresses): static + { + return $this->addListAddressHeaderBody('To', $addresses); + } + + /** + * @return $this + */ + public function to(Address|string ...$addresses): static + { + return $this->setListAddressHeaderBody('To', $addresses); + } + + /** + * @return Address[] + */ + public function getTo(): array + { + return $this->getHeaders()->getHeaderBody('To') ?: []; + } + + /** + * @return $this + */ + public function addCc(Address|string ...$addresses): static + { + return $this->addListAddressHeaderBody('Cc', $addresses); + } + + /** + * @return $this + */ + public function cc(Address|string ...$addresses): static + { + return $this->setListAddressHeaderBody('Cc', $addresses); + } + + /** + * @return Address[] + */ + public function getCc(): array + { + return $this->getHeaders()->getHeaderBody('Cc') ?: []; + } + + /** + * @return $this + */ + public function addBcc(Address|string ...$addresses): static + { + return $this->addListAddressHeaderBody('Bcc', $addresses); + } + + /** + * @return $this + */ + public function bcc(Address|string ...$addresses): static + { + return $this->setListAddressHeaderBody('Bcc', $addresses); + } + + /** + * @return Address[] + */ + public function getBcc(): array + { + return $this->getHeaders()->getHeaderBody('Bcc') ?: []; + } + + /** + * Sets the priority of this message. + * + * The value is an integer where 1 is the highest priority and 5 is the lowest. + * + * @return $this + */ + public function priority(int $priority): static + { + if ($priority > 5) { + $priority = 5; + } elseif ($priority < 1) { + $priority = 1; + } + + return $this->setHeaderBody('Text', 'X-Priority', sprintf('%d (%s)', $priority, self::PRIORITY_MAP[$priority])); + } + + /** + * Get the priority of this message. + * + * The returned value is an integer where 1 is the highest priority and 5 + * is the lowest. + */ + public function getPriority(): int + { + [$priority] = sscanf($this->getHeaders()->getHeaderBody('X-Priority') ?? '', '%[1-5]'); + + return $priority ?? 3; + } + + /** + * @param resource|string|null $body + * + * @return $this + */ + public function text($body, string $charset = 'utf-8'): static + { + if (null !== $body && !\is_string($body) && !\is_resource($body)) { + throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body))); + } + + $this->cachedBody = null; + $this->text = $body; + $this->textCharset = $charset; + + return $this; + } + + /** + * @return resource|string|null + */ + public function getTextBody() + { + return $this->text; + } + + public function getTextCharset(): ?string + { + return $this->textCharset; + } + + /** + * @param resource|string|null $body + * + * @return $this + */ + public function html($body, string $charset = 'utf-8'): static + { + if (null !== $body && !\is_string($body) && !\is_resource($body)) { + throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body))); + } + + $this->cachedBody = null; + $this->html = $body; + $this->htmlCharset = $charset; + + return $this; + } + + /** + * @return resource|string|null + */ + public function getHtmlBody() + { + return $this->html; + } + + public function getHtmlCharset(): ?string + { + return $this->htmlCharset; + } + + /** + * @param resource|string $body + * + * @return $this + */ + public function attach($body, ?string $name = null, ?string $contentType = null): static + { + return $this->addPart(new DataPart($body, $name, $contentType)); + } + + /** + * @return $this + */ + public function attachFromPath(string $path, ?string $name = null, ?string $contentType = null): static + { + return $this->addPart(new DataPart(new File($path), $name, $contentType)); + } + + /** + * @param resource|string $body + * + * @return $this + */ + public function embed($body, ?string $name = null, ?string $contentType = null): static + { + return $this->addPart((new DataPart($body, $name, $contentType))->asInline()); + } + + /** + * @return $this + */ + public function embedFromPath(string $path, ?string $name = null, ?string $contentType = null): static + { + return $this->addPart((new DataPart(new File($path), $name, $contentType))->asInline()); + } + + /** + * @return $this + */ + public function addPart(DataPart $part): static + { + $this->cachedBody = null; + $this->attachments[] = $part; + + return $this; + } + + /** + * @return DataPart[] + */ + public function getAttachments(): array + { + return $this->attachments; + } + + public function getBody(): AbstractPart + { + if (null !== $body = parent::getBody()) { + return $body; + } + + return $this->generateBody(); + } + + public function ensureValidity(): void + { + $this->ensureBodyValid(); + + if ('1' === $this->getHeaders()->getHeaderBody('X-Unsent')) { + throw new LogicException('Cannot send messages marked as "draft".'); + } + + parent::ensureValidity(); + } + + private function ensureBodyValid(): void + { + if (null === $this->text && null === $this->html && !$this->attachments) { + throw new LogicException('A message must have a text or an HTML part or attachments.'); + } + } + + /** + * Generates an AbstractPart based on the raw body of a message. + * + * The most "complex" part generated by this method is when there is text and HTML bodies + * with related images for the HTML part and some attachments: + * + * multipart/mixed + * | + * |------------> multipart/related + * | | + * | |------------> multipart/alternative + * | | | + * | | ------------> text/plain (with content) + * | | | + * | | ------------> text/html (with content) + * | | + * | ------------> image/png (with content) + * | + * ------------> application/pdf (with content) + */ + private function generateBody(): AbstractPart + { + if (null !== $this->cachedBody) { + return $this->cachedBody; + } + + $this->ensureBodyValid(); + + [$htmlPart, $otherParts, $relatedParts] = $this->prepareParts(); + + $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset); + if (null !== $htmlPart) { + if (null !== $part) { + $part = new AlternativePart($part, $htmlPart); + } else { + $part = $htmlPart; + } + } + + if ($relatedParts) { + $part = new RelatedPart($part, ...$relatedParts); + } + + if ($otherParts) { + if ($part) { + $part = new MixedPart($part, ...$otherParts); + } else { + $part = new MixedPart(...$otherParts); + } + } + + return $this->cachedBody = $part; + } + + private function prepareParts(): ?array + { + $names = []; + $htmlPart = null; + $html = $this->html; + if (null !== $html) { + $htmlPart = new TextPart($html, $this->htmlCharset, 'html'); + $html = $htmlPart->getBody(); + + $regexes = [ + ']*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+))', + '<\w+\s+[^>]*background\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+))', + ]; + $tmpMatches = []; + foreach ($regexes as $regex) { + preg_match_all('/'.$regex.'/i', $html, $tmpMatches); + $names = array_merge($names, $tmpMatches[2], $tmpMatches[3]); + } + $names = array_filter(array_unique($names)); + } + + $otherParts = $relatedParts = []; + foreach ($this->attachments as $part) { + foreach ($names as $name) { + if ($name !== $part->getName() && (!$part->hasContentId() || $name !== $part->getContentId())) { + continue; + } + if (isset($relatedParts[$name])) { + continue 2; + } + + if ($name !== $part->getContentId()) { + $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count); + } + $relatedParts[$name] = $part; + $part->setName($part->getContentId())->asInline(); + + continue 2; + } + + $otherParts[] = $part; + } + if (null !== $htmlPart) { + $htmlPart = new TextPart($html, $this->htmlCharset, 'html'); + } + + return [$htmlPart, $otherParts, array_values($relatedParts)]; + } + + /** + * @return $this + */ + private function setHeaderBody(string $type, string $name, $body): static + { + $this->getHeaders()->setHeaderBody($type, $name, $body); + + return $this; + } + + /** + * @return $this + */ + private function addListAddressHeaderBody(string $name, array $addresses): static + { + if (!$header = $this->getHeaders()->get($name)) { + return $this->setListAddressHeaderBody($name, $addresses); + } + $header->addAddresses(Address::createArray($addresses)); + + return $this; + } + + /** + * @return $this + */ + private function setListAddressHeaderBody(string $name, array $addresses): static + { + $addresses = Address::createArray($addresses); + $headers = $this->getHeaders(); + if ($header = $headers->get($name)) { + $header->setAddresses($addresses); + } else { + $headers->addMailboxListHeader($name, $addresses); + } + + return $this; + } + + /** + * @internal + */ + public function __serialize(): array + { + if (\is_resource($this->text)) { + $this->text = (new TextPart($this->text))->getBody(); + } + + if (\is_resource($this->html)) { + $this->html = (new TextPart($this->html))->getBody(); + } + + return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data; + + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/mime/Encoder/AddressEncoderInterface.php b/vendor/symfony/mime/Encoder/AddressEncoderInterface.php new file mode 100644 index 0000000..de477d8 --- /dev/null +++ b/vendor/symfony/mime/Encoder/AddressEncoderInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\Exception\AddressEncoderException; + +/** + * @author Christian Schmidt + */ +interface AddressEncoderInterface +{ + /** + * Encodes an email address. + * + * @throws AddressEncoderException if the email cannot be represented in + * the encoding implemented by this class + */ + public function encodeString(string $address): string; +} diff --git a/vendor/symfony/mime/Encoder/Base64ContentEncoder.php b/vendor/symfony/mime/Encoder/Base64ContentEncoder.php new file mode 100644 index 0000000..440868a --- /dev/null +++ b/vendor/symfony/mime/Encoder/Base64ContentEncoder.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\Exception\RuntimeException; + +/** + * @author Fabien Potencier + */ +final class Base64ContentEncoder extends Base64Encoder implements ContentEncoderInterface +{ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable + { + if (!\is_resource($stream)) { + throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__)); + } + + $filter = stream_filter_append($stream, 'convert.base64-encode', \STREAM_FILTER_READ, [ + 'line-length' => 0 >= $maxLineLength || 76 < $maxLineLength ? 76 : $maxLineLength, + 'line-break-chars' => "\r\n", + ]); + if (!\is_resource($filter)) { + throw new RuntimeException('Unable to set the base64 content encoder to the filter.'); + } + + while (!feof($stream)) { + yield fread($stream, 16372); + } + stream_filter_remove($filter); + } + + public function getName(): string + { + return 'base64'; + } +} diff --git a/vendor/symfony/mime/Encoder/Base64Encoder.php b/vendor/symfony/mime/Encoder/Base64Encoder.php new file mode 100644 index 0000000..7106478 --- /dev/null +++ b/vendor/symfony/mime/Encoder/Base64Encoder.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +class Base64Encoder implements EncoderInterface +{ + /** + * Takes an unencoded string and produces a Base64 encoded string from it. + * + * Base64 encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + if (0 >= $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $encodedString = base64_encode($string); + $firstLine = ''; + if (0 !== $firstLineOffset) { + $firstLine = substr($encodedString, 0, $maxLineLength - $firstLineOffset)."\r\n"; + $encodedString = substr($encodedString, $maxLineLength - $firstLineOffset); + } + + return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n")); + } +} diff --git a/vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php b/vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php new file mode 100644 index 0000000..5c06f6d --- /dev/null +++ b/vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +final class Base64MimeHeaderEncoder extends Base64Encoder implements MimeHeaderEncoderInterface +{ + public function getName(): string + { + return 'B'; + } + + /** + * Takes an unencoded string and produces a Base64 encoded string from it. + * + * If the charset is iso-2022-jp, it uses mb_encode_mimeheader instead of + * default encodeString, otherwise pass to the parent method. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + if ('iso-2022-jp' === strtolower($charset)) { + $old = mb_internal_encoding(); + mb_internal_encoding('utf-8'); + $newstring = mb_encode_mimeheader($string, 'iso-2022-jp', $this->getName(), "\r\n"); + mb_internal_encoding($old); + + return $newstring; + } + + return parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength); + } +} diff --git a/vendor/symfony/mime/Encoder/ContentEncoderInterface.php b/vendor/symfony/mime/Encoder/ContentEncoderInterface.php new file mode 100644 index 0000000..a45ad04 --- /dev/null +++ b/vendor/symfony/mime/Encoder/ContentEncoderInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +interface ContentEncoderInterface extends EncoderInterface +{ + /** + * Encodes the stream to a Generator. + * + * @param resource $stream + */ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable; + + /** + * Gets the MIME name of this content encoding scheme. + */ + public function getName(): string; +} diff --git a/vendor/symfony/mime/Encoder/EightBitContentEncoder.php b/vendor/symfony/mime/Encoder/EightBitContentEncoder.php new file mode 100644 index 0000000..8283120 --- /dev/null +++ b/vendor/symfony/mime/Encoder/EightBitContentEncoder.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Fabien Potencier + */ +final class EightBitContentEncoder implements ContentEncoderInterface +{ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable + { + while (!feof($stream)) { + yield fread($stream, 16372); + } + } + + public function getName(): string + { + return '8bit'; + } + + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + return $string; + } +} diff --git a/vendor/symfony/mime/Encoder/EncoderInterface.php b/vendor/symfony/mime/Encoder/EncoderInterface.php new file mode 100644 index 0000000..bbf6d48 --- /dev/null +++ b/vendor/symfony/mime/Encoder/EncoderInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +interface EncoderInterface +{ + /** + * Encode a given string to produce an encoded string. + * + * @param int $firstLineOffset if first line needs to be shorter + * @param int $maxLineLength - 0 indicates the default length for this encoding + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string; +} diff --git a/vendor/symfony/mime/Encoder/IdnAddressEncoder.php b/vendor/symfony/mime/Encoder/IdnAddressEncoder.php new file mode 100644 index 0000000..b56e7e3 --- /dev/null +++ b/vendor/symfony/mime/Encoder/IdnAddressEncoder.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * An IDN email address encoder. + * + * Encodes the domain part of an address using IDN. This is compatible will all + * SMTP servers. + * + * Note: It leaves the local part as is. In case there are non-ASCII characters + * in the local part then it depends on the SMTP Server if this is supported. + * + * @author Christian Schmidt + */ +final class IdnAddressEncoder implements AddressEncoderInterface +{ + /** + * Encodes the domain part of an address using IDN. + */ + public function encodeString(string $address): string + { + $i = strrpos($address, '@'); + if (false !== $i) { + $local = substr($address, 0, $i); + $domain = substr($address, $i + 1); + + if (preg_match('/[^\x00-\x7F]/', $domain)) { + $address = sprintf('%s@%s', $local, idn_to_ascii($domain, \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46)); + } + } + + return $address; + } +} diff --git a/vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php b/vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php new file mode 100644 index 0000000..fff2c78 --- /dev/null +++ b/vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +interface MimeHeaderEncoderInterface +{ + /** + * Get the MIME name of this content encoding scheme. + */ + public function getName(): string; +} diff --git a/vendor/symfony/mime/Encoder/QpContentEncoder.php b/vendor/symfony/mime/Encoder/QpContentEncoder.php new file mode 100644 index 0000000..6f420ff --- /dev/null +++ b/vendor/symfony/mime/Encoder/QpContentEncoder.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Lars Strojny + */ +final class QpContentEncoder implements ContentEncoderInterface +{ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable + { + if (!\is_resource($stream)) { + throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__)); + } + + // we don't use PHP stream filters here as the content should be small enough + yield $this->encodeString(stream_get_contents($stream), 'utf-8', 0, $maxLineLength); + } + + public function getName(): string + { + return 'quoted-printable'; + } + + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + return $this->standardize(quoted_printable_encode($string)); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + */ + private function standardize(string $string): string + { + // transform CR or LF to CRLF + $string = preg_replace('~=0D(?!=0A)|(? substr_replace($string, '=09', -1), + 0x20 => substr_replace($string, '=20', -1), + default => $string, + }; + } +} diff --git a/vendor/symfony/mime/Encoder/QpEncoder.php b/vendor/symfony/mime/Encoder/QpEncoder.php new file mode 100644 index 0000000..160dde3 --- /dev/null +++ b/vendor/symfony/mime/Encoder/QpEncoder.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\CharacterStream; + +/** + * @author Chris Corbyn + */ +class QpEncoder implements EncoderInterface +{ + /** + * Pre-computed QP for HUGE optimization. + */ + private const QP_MAP = [ + 0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04', + 5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09', + 10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E', + 15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13', + 20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18', + 25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D', + 30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22', + 35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27', + 40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C', + 45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31', + 50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36', + 55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B', + 60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40', + 65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45', + 70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A', + 75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F', + 80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54', + 85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59', + 90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E', + 95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63', + 100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68', + 105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D', + 110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72', + 115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77', + 120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C', + 125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81', + 130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86', + 135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B', + 140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90', + 145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95', + 150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A', + 155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F', + 160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4', + 165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9', + 170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE', + 175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3', + 180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8', + 185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD', + 190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2', + 195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7', + 200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC', + 205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1', + 210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6', + 215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB', + 220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0', + 225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5', + 230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA', + 235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF', + 240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4', + 245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9', + 250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE', + 255 => '=FF', + ]; + + private static array $safeMapShare = []; + + /** + * A map of non-encoded ascii characters. + * + * @var string[] + * + * @internal + */ + protected array $safeMap = []; + + public function __construct() + { + $id = static::class; + if (!isset(self::$safeMapShare[$id])) { + $this->initSafeMap(); + self::$safeMapShare[$id] = $this->safeMap; + } else { + $this->safeMap = self::$safeMapShare[$id]; + } + } + + protected function initSafeMap(): void + { + foreach (array_merge([0x09, 0x20], range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) { + $this->safeMap[$byte] = \chr($byte); + } + } + + /** + * Takes an unencoded string and produces a QP encoded string from it. + * + * QP encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + if ($maxLineLength > 76 || $maxLineLength <= 0) { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $lines = []; + $lNo = 0; + $lines[$lNo] = ''; + $currentLine = &$lines[$lNo++]; + $size = $lineLen = 0; + $charStream = new CharacterStream($string, $charset); + + // Fetching more than 4 chars at one is slower, as is fetching fewer bytes + // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6 + // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes + while (null !== $bytes = $charStream->readBytes(4)) { + $enc = $this->encodeByteSequence($bytes, $size); + + $i = strpos($enc, '=0D=0A'); + $newLineLength = $lineLen + (false === $i ? $size : $i); + + if ($currentLine && $newLineLength >= $thisLineLength) { + $lines[$lNo] = ''; + $currentLine = &$lines[$lNo++]; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + + $currentLine .= $enc; + + if (false === $i) { + $lineLen += $size; + } else { + // 6 is the length of '=0D=0A'. + $lineLen = $size - strrpos($enc, '=0D=0A') - 6; + } + } + + return $this->standardize(implode("=\r\n", $lines)); + } + + /** + * Encode the given byte array into a verbatim QP form. + */ + private function encodeByteSequence(array $bytes, int &$size): string + { + $ret = ''; + $size = 0; + foreach ($bytes as $b) { + if (isset($this->safeMap[$b])) { + $ret .= $this->safeMap[$b]; + ++$size; + } else { + $ret .= self::QP_MAP[$b]; + $size += 3; + } + } + + return $ret; + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + */ + private function standardize(string $string): string + { + $string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string); + + return match ($end = \ord(substr($string, -1))) { + 0x09, + 0x20 => substr_replace($string, self::QP_MAP[$end], -1), + default => $string, + }; + } +} diff --git a/vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php b/vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php new file mode 100644 index 0000000..d1d3837 --- /dev/null +++ b/vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +final class QpMimeHeaderEncoder extends QpEncoder implements MimeHeaderEncoderInterface +{ + protected function initSafeMap(): void + { + foreach (array_merge( + range(0x61, 0x7A), range(0x41, 0x5A), + range(0x30, 0x39), [0x20, 0x21, 0x2A, 0x2B, 0x2D, 0x2F] + ) as $byte) { + $this->safeMap[$byte] = \chr($byte); + } + } + + public function getName(): string + { + return 'Q'; + } + + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + return str_replace([' ', '=20', "=\r\n"], ['_', '_', "\r\n"], + parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength) + ); + } +} diff --git a/vendor/symfony/mime/Encoder/Rfc2231Encoder.php b/vendor/symfony/mime/Encoder/Rfc2231Encoder.php new file mode 100644 index 0000000..4d93dc6 --- /dev/null +++ b/vendor/symfony/mime/Encoder/Rfc2231Encoder.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\CharacterStream; + +/** + * @author Chris Corbyn + */ +final class Rfc2231Encoder implements EncoderInterface +{ + /** + * Takes an unencoded string and produces a string encoded according to RFC 2231 from it. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + $lines = []; + $lineCount = 0; + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + + if (0 >= $maxLineLength) { + $maxLineLength = 75; + } + + $charStream = new CharacterStream($string, $charset); + $thisLineLength = $maxLineLength - $firstLineOffset; + + while (null !== $char = $charStream->read(4)) { + $encodedChar = rawurlencode($char); + if ('' !== $currentLine && \strlen($currentLine.$encodedChar) > $thisLineLength) { + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + $thisLineLength = $maxLineLength; + } + $currentLine .= $encodedChar; + } + + return implode("\r\n", $lines); + } +} diff --git a/vendor/symfony/mime/Exception/AddressEncoderException.php b/vendor/symfony/mime/Exception/AddressEncoderException.php new file mode 100644 index 0000000..51ee2e0 --- /dev/null +++ b/vendor/symfony/mime/Exception/AddressEncoderException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class AddressEncoderException extends RfcComplianceException +{ +} diff --git a/vendor/symfony/mime/Exception/ExceptionInterface.php b/vendor/symfony/mime/Exception/ExceptionInterface.php new file mode 100644 index 0000000..1193390 --- /dev/null +++ b/vendor/symfony/mime/Exception/ExceptionInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/mime/Exception/InvalidArgumentException.php b/vendor/symfony/mime/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..e89ebae --- /dev/null +++ b/vendor/symfony/mime/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/mime/Exception/LogicException.php b/vendor/symfony/mime/Exception/LogicException.php new file mode 100644 index 0000000..0508ee7 --- /dev/null +++ b/vendor/symfony/mime/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/mime/Exception/RfcComplianceException.php b/vendor/symfony/mime/Exception/RfcComplianceException.php new file mode 100644 index 0000000..26e4a50 --- /dev/null +++ b/vendor/symfony/mime/Exception/RfcComplianceException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class RfcComplianceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/mime/Exception/RuntimeException.php b/vendor/symfony/mime/Exception/RuntimeException.php new file mode 100644 index 0000000..fb018b0 --- /dev/null +++ b/vendor/symfony/mime/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php b/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php new file mode 100644 index 0000000..363ac81 --- /dev/null +++ b/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\LogicException; + +/** + * Guesses the MIME type with the binary "file" (only available on *nix). + * + * @author Bernhard Schussek + */ +class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface +{ + /** + * The $cmd pattern must contain a "%s" string that will be replaced + * with the file name to guess. + * + * The command output must start with the MIME type of the file. + * + * @param string $cmd The command to run to get the MIME type of a file + */ + public function __construct( + private string $cmd = 'file -b --mime -- %s 2>/dev/null', + ) { + } + + public function isGuesserSupported(): bool + { + static $supported = null; + + if (null !== $supported) { + return $supported; + } + + if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) { + return $supported = false; + } + + ob_start(); + passthru('command -v file', $exitStatus); + $binPath = trim(ob_get_clean()); + + return $supported = 0 === $exitStatus && '' !== $binPath; + } + + public function guessMimeType(string $path): ?string + { + if (!is_file($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path)); + } + + if (!$this->isGuesserSupported()) { + throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__)); + } + + ob_start(); + + // need to use --mime instead of -i. see #6641 + passthru(sprintf($this->cmd, escapeshellarg((str_starts_with($path, '-') ? './' : '').$path)), $return); + if ($return > 0) { + ob_end_clean(); + + return null; + } + + $type = trim(ob_get_clean()); + + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) { + // it's not a type, but an error message + return null; + } + + return $match[1]; + } +} diff --git a/vendor/symfony/mime/FileinfoMimeTypeGuesser.php b/vendor/symfony/mime/FileinfoMimeTypeGuesser.php new file mode 100644 index 0000000..89a9d3e --- /dev/null +++ b/vendor/symfony/mime/FileinfoMimeTypeGuesser.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\LogicException; + +/** + * Guesses the MIME type using the PECL extension FileInfo. + * + * @author Bernhard Schussek + */ +class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface +{ + /** + * @param string|null $magicFile A magic file to use with the finfo instance + * + * @see http://www.php.net/manual/en/function.finfo-open.php + */ + public function __construct( + private ?string $magicFile = null, + ) { + } + + public function isGuesserSupported(): bool + { + return \function_exists('finfo_open'); + } + + public function guessMimeType(string $path): ?string + { + if (!is_file($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path)); + } + + if (!$this->isGuesserSupported()) { + throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__)); + } + + if (false === $finfo = new \finfo(\FILEINFO_MIME_TYPE, $this->magicFile)) { + return null; + } + $mimeType = $finfo->file($path); + + if ($mimeType && 0 === (\strlen($mimeType) % 2)) { + $mimeStart = substr($mimeType, 0, \strlen($mimeType) >> 1); + $mimeType = $mimeStart.$mimeStart === $mimeType ? $mimeStart : $mimeType; + } + + return $mimeType; + } +} diff --git a/vendor/symfony/mime/Header/AbstractHeader.php b/vendor/symfony/mime/Header/AbstractHeader.php new file mode 100644 index 0000000..e6cb604 --- /dev/null +++ b/vendor/symfony/mime/Header/AbstractHeader.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Encoder\QpMimeHeaderEncoder; + +/** + * An abstract base MIME Header. + * + * @author Chris Corbyn + */ +abstract class AbstractHeader implements HeaderInterface +{ + public const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)'; + + private static QpMimeHeaderEncoder $encoder; + + private string $name; + private int $lineLength = 76; + private ?string $lang = null; + private string $charset = 'utf-8'; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function setCharset(string $charset): void + { + $this->charset = $charset; + } + + public function getCharset(): ?string + { + return $this->charset; + } + + /** + * Set the language used in this Header. + * + * For example, for US English, 'en-us'. + */ + public function setLanguage(string $lang): void + { + $this->lang = $lang; + } + + public function getLanguage(): ?string + { + return $this->lang; + } + + public function getName(): string + { + return $this->name; + } + + public function setMaxLineLength(int $lineLength): void + { + $this->lineLength = $lineLength; + } + + public function getMaxLineLength(): int + { + return $this->lineLength; + } + + public function toString(): string + { + return $this->tokensToString($this->toTokens()); + } + + /** + * Produces a compliant, formatted RFC 2822 'phrase' based on the string given. + * + * @param string $string as displayed + * @param bool $shorten the first line to make remove for header name + */ + protected function createPhrase(HeaderInterface $header, string $string, string $charset, bool $shorten = false): string + { + // Treat token as exactly what was given + $phraseStr = $string; + + // If it's not valid + if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) { + // .. but it is just ascii text, try escaping some characters + // and make it a quoted-string + if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) { + foreach (['\\', '"'] as $char) { + $phraseStr = str_replace($char, '\\'.$char, $phraseStr); + } + $phraseStr = '"'.$phraseStr.'"'; + } else { + // ... otherwise it needs encoding + // Determine space remaining on line if first line + if ($shorten) { + $usedLength = \strlen($header->getName().': '); + } else { + $usedLength = 0; + } + $phraseStr = $this->encodeWords($header, $string, $usedLength); + } + } elseif (str_contains($phraseStr, '(')) { + foreach (['\\', '"'] as $char) { + $phraseStr = str_replace($char, '\\'.$char, $phraseStr); + } + $phraseStr = '"'.$phraseStr.'"'; + } + + return $phraseStr; + } + + /** + * Encode needed word tokens within a string of input. + */ + protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string + { + $value = ''; + $tokens = $this->getEncodableWordTokens($input); + foreach ($tokens as $token) { + // See RFC 2822, Sect 2.2 (really 2.2 ??) + if ($this->tokenNeedsEncoding($token)) { + // Don't encode starting WSP + $firstChar = substr($token, 0, 1); + switch ($firstChar) { + case ' ': + case "\t": + $value .= $firstChar; + $token = substr($token, 1); + } + + if (-1 == $usedLength) { + $usedLength = \strlen($header->getName().': ') + \strlen($value); + } + $value .= $this->getTokenAsEncodedWord($token, $usedLength); + } else { + $value .= $token; + } + } + + return $value; + } + + protected function tokenNeedsEncoding(string $token): bool + { + return (bool) preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); + } + + /** + * Splits a string into tokens in blocks of words which can be encoded quickly. + * + * @return string[] + */ + protected function getEncodableWordTokens(string $string): array + { + $tokens = []; + $encodedToken = ''; + // Split at all whitespace boundaries + foreach (preg_split('~(?=[\t ])~', $string) as $token) { + if ($this->tokenNeedsEncoding($token)) { + $encodedToken .= $token; + } else { + if ('' !== $encodedToken) { + $tokens[] = $encodedToken; + $encodedToken = ''; + } + $tokens[] = $token; + } + } + if ('' !== $encodedToken) { + $tokens[] = $encodedToken; + } + + return $tokens; + } + + /** + * Get a token as an encoded word for safe insertion into headers. + */ + protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string + { + self::$encoder ??= new QpMimeHeaderEncoder(); + + // Adjust $firstLineOffset to account for space needed for syntax + $charsetDecl = $this->charset; + if (null !== $this->lang) { + $charsetDecl .= '*'.$this->lang; + } + $encodingWrapperLength = \strlen('=?'.$charsetDecl.'?'.self::$encoder->getName().'??='); + + if ($firstLineOffset >= 75) { + // Does this logic need to be here? + $firstLineOffset = 0; + } + + $encodedTextLines = explode("\r\n", + self::$encoder->encodeString($token, $this->charset, $firstLineOffset, 75 - $encodingWrapperLength) + ); + + if ('iso-2022-jp' !== strtolower($this->charset)) { + // special encoding for iso-2022-jp using mb_encode_mimeheader + foreach ($encodedTextLines as $lineNum => $line) { + $encodedTextLines[$lineNum] = '=?'.$charsetDecl.'?'.self::$encoder->getName().'?'.$line.'?='; + } + } + + return implode("\r\n ", $encodedTextLines); + } + + /** + * Generates tokens from the given string which include CRLF as individual tokens. + * + * @return string[] + */ + protected function generateTokenLines(string $token): array + { + return preg_split('~(\r\n)~', $token, -1, \PREG_SPLIT_DELIM_CAPTURE); + } + + /** + * Generate a list of all tokens in the final header. + */ + protected function toTokens(?string $string = null): array + { + $string ??= $this->getBodyAsString(); + + $tokens = []; + // Generate atoms; split at all invisible boundaries followed by WSP + foreach (preg_split('~(?=[ \t])~', $string) as $token) { + $newTokens = $this->generateTokenLines($token); + foreach ($newTokens as $newToken) { + $tokens[] = $newToken; + } + } + + return $tokens; + } + + /** + * Takes an array of tokens which appear in the header and turns them into + * an RFC 2822 compliant string, adding FWSP where needed. + * + * @param string[] $tokens + */ + private function tokensToString(array $tokens): string + { + $lineCount = 0; + $headerLines = []; + $headerLines[] = $this->name.': '; + $currentLine = &$headerLines[$lineCount++]; + + // Build all tokens back into compliant header + foreach ($tokens as $i => $token) { + // Line longer than specified maximum or token was just a new line + if (("\r\n" === $token) + || ($i > 0 && \strlen($currentLine.$token) > $this->lineLength) + && '' !== $currentLine) { + $headerLines[] = ''; + $currentLine = &$headerLines[$lineCount++]; + } + + // Append token to the line + if ("\r\n" !== $token) { + $currentLine .= $token; + } + } + + // Implode with FWS (RFC 2822, 2.2.3) + return implode("\r\n", $headerLines); + } +} diff --git a/vendor/symfony/mime/Header/DateHeader.php b/vendor/symfony/mime/Header/DateHeader.php new file mode 100644 index 0000000..2b36180 --- /dev/null +++ b/vendor/symfony/mime/Header/DateHeader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +/** + * A Date MIME Header. + * + * @author Chris Corbyn + */ +final class DateHeader extends AbstractHeader +{ + private \DateTimeImmutable $dateTime; + + public function __construct(string $name, \DateTimeInterface $date) + { + parent::__construct($name); + + $this->setDateTime($date); + } + + /** + * @param \DateTimeInterface $body + */ + public function setBody(mixed $body): void + { + $this->setDateTime($body); + } + + public function getBody(): \DateTimeImmutable + { + return $this->getDateTime(); + } + + public function getDateTime(): \DateTimeImmutable + { + return $this->dateTime; + } + + /** + * Set the date-time of the Date in this Header. + * + * If a DateTime instance is provided, it is converted to DateTimeImmutable. + */ + public function setDateTime(\DateTimeInterface $dateTime): void + { + $this->dateTime = \DateTimeImmutable::createFromInterface($dateTime); + } + + public function getBodyAsString(): string + { + return $this->dateTime->format(\DateTimeInterface::RFC2822); + } +} diff --git a/vendor/symfony/mime/Header/HeaderInterface.php b/vendor/symfony/mime/Header/HeaderInterface.php new file mode 100644 index 0000000..6bb1d5d --- /dev/null +++ b/vendor/symfony/mime/Header/HeaderInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +/** + * A MIME Header. + * + * @author Chris Corbyn + */ +interface HeaderInterface +{ + /** + * Sets the body. + * + * The type depends on the Header concrete class. + */ + public function setBody(mixed $body): void; + + /** + * Gets the body. + * + * The return type depends on the Header concrete class. + */ + public function getBody(): mixed; + + public function setCharset(string $charset): void; + + public function getCharset(): ?string; + + public function setLanguage(string $lang): void; + + public function getLanguage(): ?string; + + public function getName(): string; + + public function setMaxLineLength(int $lineLength): void; + + public function getMaxLineLength(): int; + + /** + * Gets this Header rendered as a compliant string. + */ + public function toString(): string; + + /** + * Gets the header's body, prepared for folding into a final header value. + * + * This is not necessarily RFC 2822 compliant since folding white space is + * not added at this stage (see {@link toString()} for that). + */ + public function getBodyAsString(): string; +} diff --git a/vendor/symfony/mime/Header/Headers.php b/vendor/symfony/mime/Header/Headers.php new file mode 100644 index 0000000..164f4ac --- /dev/null +++ b/vendor/symfony/mime/Header/Headers.php @@ -0,0 +1,316 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\LogicException; + +/** + * A collection of headers. + * + * @author Fabien Potencier + */ +final class Headers +{ + private const UNIQUE_HEADERS = [ + 'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc', + 'message-id', 'in-reply-to', 'references', 'subject', + ]; + private const HEADER_CLASS_MAP = [ + 'date' => DateHeader::class, + 'from' => MailboxListHeader::class, + 'sender' => MailboxHeader::class, + 'reply-to' => MailboxListHeader::class, + 'to' => MailboxListHeader::class, + 'cc' => MailboxListHeader::class, + 'bcc' => MailboxListHeader::class, + 'message-id' => IdentificationHeader::class, + 'in-reply-to' => [UnstructuredHeader::class, IdentificationHeader::class], // `In-Reply-To` and `References` are less strict than RFC 2822 (3.6.4) to allow users entering the original email's ... + 'references' => [UnstructuredHeader::class, IdentificationHeader::class], // ... `Message-ID`, even if that is no valid `msg-id` + 'return-path' => PathHeader::class, + ]; + + /** + * @var HeaderInterface[][] + */ + private array $headers = []; + private int $lineLength = 76; + + public function __construct(HeaderInterface ...$headers) + { + foreach ($headers as $header) { + $this->add($header); + } + } + + public function __clone() + { + foreach ($this->headers as $name => $collection) { + foreach ($collection as $i => $header) { + $this->headers[$name][$i] = clone $header; + } + } + } + + public function setMaxLineLength(int $lineLength): void + { + $this->lineLength = $lineLength; + foreach ($this->all() as $header) { + $header->setMaxLineLength($lineLength); + } + } + + public function getMaxLineLength(): int + { + return $this->lineLength; + } + + /** + * @param array $addresses + * + * @return $this + */ + public function addMailboxListHeader(string $name, array $addresses): static + { + return $this->add(new MailboxListHeader($name, Address::createArray($addresses))); + } + + /** + * @return $this + */ + public function addMailboxHeader(string $name, Address|string $address): static + { + return $this->add(new MailboxHeader($name, Address::create($address))); + } + + /** + * @return $this + */ + public function addIdHeader(string $name, string|array $ids): static + { + return $this->add(new IdentificationHeader($name, $ids)); + } + + /** + * @return $this + */ + public function addPathHeader(string $name, Address|string $path): static + { + return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path))); + } + + /** + * @return $this + */ + public function addDateHeader(string $name, \DateTimeInterface $dateTime): static + { + return $this->add(new DateHeader($name, $dateTime)); + } + + /** + * @return $this + */ + public function addTextHeader(string $name, string $value): static + { + return $this->add(new UnstructuredHeader($name, $value)); + } + + /** + * @return $this + */ + public function addParameterizedHeader(string $name, string $value, array $params = []): static + { + return $this->add(new ParameterizedHeader($name, $value, $params)); + } + + /** + * @return $this + */ + public function addHeader(string $name, mixed $argument, array $more = []): static + { + $headerClass = self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class; + if (\is_array($headerClass)) { + $headerClass = $headerClass[0]; + } + $parts = explode('\\', $headerClass); + $method = 'add'.ucfirst(array_pop($parts)); + if ('addUnstructuredHeader' === $method) { + $method = 'addTextHeader'; + } elseif ('addIdentificationHeader' === $method) { + $method = 'addIdHeader'; + } elseif ('addMailboxListHeader' === $method && !\is_array($argument)) { + $argument = [$argument]; + } + + return $this->$method($name, $argument, $more); + } + + public function has(string $name): bool + { + return isset($this->headers[strtolower($name)]); + } + + /** + * @return $this + */ + public function add(HeaderInterface $header): static + { + self::checkHeaderClass($header); + + $header->setMaxLineLength($this->lineLength); + $name = strtolower($header->getName()); + + if (\in_array($name, self::UNIQUE_HEADERS, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) { + throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName())); + } + + $this->headers[$name][] = $header; + + return $this; + } + + public function get(string $name): ?HeaderInterface + { + $name = strtolower($name); + if (!isset($this->headers[$name])) { + return null; + } + + $values = array_values($this->headers[$name]); + + return array_shift($values); + } + + public function all(?string $name = null): iterable + { + if (null === $name) { + foreach ($this->headers as $name => $collection) { + foreach ($collection as $header) { + yield $name => $header; + } + } + } elseif (isset($this->headers[strtolower($name)])) { + foreach ($this->headers[strtolower($name)] as $header) { + yield $header; + } + } + } + + public function getNames(): array + { + return array_keys($this->headers); + } + + public function remove(string $name): void + { + unset($this->headers[strtolower($name)]); + } + + public static function isUniqueHeader(string $name): bool + { + return \in_array(strtolower($name), self::UNIQUE_HEADERS, true); + } + + /** + * @throws LogicException if the header name and class are not compatible + */ + public static function checkHeaderClass(HeaderInterface $header): void + { + $name = strtolower($header->getName()); + $headerClasses = self::HEADER_CLASS_MAP[$name] ?? []; + if (!\is_array($headerClasses)) { + $headerClasses = [$headerClasses]; + } + + if (!$headerClasses) { + return; + } + + foreach ($headerClasses as $c) { + if ($header instanceof $c) { + return; + } + } + + throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), implode('" or "', $headerClasses), get_debug_type($header))); + } + + public function toString(): string + { + $string = ''; + foreach ($this->toArray() as $str) { + $string .= $str."\r\n"; + } + + return $string; + } + + public function toArray(): array + { + $arr = []; + foreach ($this->all() as $header) { + if ('' !== $header->getBodyAsString()) { + $arr[] = $header->toString(); + } + } + + return $arr; + } + + public function getHeaderBody(string $name): mixed + { + return $this->has($name) ? $this->get($name)->getBody() : null; + } + + /** + * @internal + */ + public function setHeaderBody(string $type, string $name, mixed $body): void + { + if ($this->has($name)) { + $this->get($name)->setBody($body); + } else { + $this->{'add'.$type.'Header'}($name, $body); + } + } + + public function getHeaderParameter(string $name, string $parameter): ?string + { + if (!$this->has($name)) { + return null; + } + + $header = $this->get($name); + if (!$header instanceof ParameterizedHeader) { + throw new LogicException(sprintf('Unable to get parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class)); + } + + return $header->getParameter($parameter); + } + + /** + * @internal + */ + public function setHeaderParameter(string $name, string $parameter, ?string $value): void + { + if (!$this->has($name)) { + throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not defined.', $parameter, $name)); + } + + $header = $this->get($name); + if (!$header instanceof ParameterizedHeader) { + throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class)); + } + + $header->setParameter($parameter, $value); + } +} diff --git a/vendor/symfony/mime/Header/IdentificationHeader.php b/vendor/symfony/mime/Header/IdentificationHeader.php new file mode 100644 index 0000000..14e18bf --- /dev/null +++ b/vendor/symfony/mime/Header/IdentificationHeader.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * An ID MIME Header for something like Message-ID or Content-ID (one or more addresses). + * + * @author Chris Corbyn + */ +final class IdentificationHeader extends AbstractHeader +{ + private array $ids = []; + private array $idsAsAddresses = []; + + public function __construct(string $name, string|array $ids) + { + parent::__construct($name); + + $this->setId($ids); + } + + /** + * @param string|string[] $body a string ID or an array of IDs + * + * @throws RfcComplianceException + */ + public function setBody(mixed $body): void + { + $this->setId($body); + } + + public function getBody(): array + { + return $this->getIds(); + } + + /** + * Set the ID used in the value of this header. + * + * @param string|string[] $id + * + * @throws RfcComplianceException + */ + public function setId(string|array $id): void + { + $this->setIds(\is_array($id) ? $id : [$id]); + } + + /** + * Get the ID used in the value of this Header. + * + * If multiple IDs are set only the first is returned. + */ + public function getId(): ?string + { + return $this->ids[0] ?? null; + } + + /** + * Set a collection of IDs to use in the value of this Header. + * + * @param string[] $ids + * + * @throws RfcComplianceException + */ + public function setIds(array $ids): void + { + $this->ids = []; + $this->idsAsAddresses = []; + foreach ($ids as $id) { + $this->idsAsAddresses[] = new Address($id); + $this->ids[] = $id; + } + } + + /** + * Get the list of IDs used in this Header. + * + * @return string[] + */ + public function getIds(): array + { + return $this->ids; + } + + public function getBodyAsString(): string + { + $addrs = []; + foreach ($this->idsAsAddresses as $address) { + $addrs[] = '<'.$address->toString().'>'; + } + + return implode(' ', $addrs); + } +} diff --git a/vendor/symfony/mime/Header/MailboxHeader.php b/vendor/symfony/mime/Header/MailboxHeader.php new file mode 100644 index 0000000..8ba964b --- /dev/null +++ b/vendor/symfony/mime/Header/MailboxHeader.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * A Mailbox MIME Header for something like Sender (one named address). + * + * @author Fabien Potencier + */ +final class MailboxHeader extends AbstractHeader +{ + private Address $address; + + public function __construct(string $name, Address $address) + { + parent::__construct($name); + + $this->setAddress($address); + } + + /** + * @param Address $body + * + * @throws RfcComplianceException + */ + public function setBody(mixed $body): void + { + $this->setAddress($body); + } + + /** + * @throws RfcComplianceException + */ + public function getBody(): Address + { + return $this->getAddress(); + } + + /** + * @throws RfcComplianceException + */ + public function setAddress(Address $address): void + { + $this->address = $address; + } + + public function getAddress(): Address + { + return $this->address; + } + + public function getBodyAsString(): string + { + $str = $this->address->getEncodedAddress(); + if ($name = $this->address->getName()) { + $str = $this->createPhrase($this, $name, $this->getCharset(), true).' <'.$str.'>'; + } + + return $str; + } + + /** + * Redefine the encoding requirements for an address. + * + * All "specials" must be encoded as the full header value will not be quoted + * + * @see RFC 2822 3.2.1 + */ + protected function tokenNeedsEncoding(string $token): bool + { + return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token); + } +} diff --git a/vendor/symfony/mime/Header/MailboxListHeader.php b/vendor/symfony/mime/Header/MailboxListHeader.php new file mode 100644 index 0000000..8d902fb --- /dev/null +++ b/vendor/symfony/mime/Header/MailboxListHeader.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * A Mailbox list MIME Header for something like From, To, Cc, and Bcc (one or more named addresses). + * + * @author Chris Corbyn + */ +final class MailboxListHeader extends AbstractHeader +{ + private array $addresses = []; + + /** + * @param Address[] $addresses + */ + public function __construct(string $name, array $addresses) + { + parent::__construct($name); + + $this->setAddresses($addresses); + } + + /** + * @param Address[] $body + * + * @throws RfcComplianceException + */ + public function setBody(mixed $body): void + { + $this->setAddresses($body); + } + + /** + * @return Address[] + * + * @throws RfcComplianceException + */ + public function getBody(): array + { + return $this->getAddresses(); + } + + /** + * Sets a list of addresses to be shown in this Header. + * + * @param Address[] $addresses + * + * @throws RfcComplianceException + */ + public function setAddresses(array $addresses): void + { + $this->addresses = []; + $this->addAddresses($addresses); + } + + /** + * Sets a list of addresses to be shown in this Header. + * + * @param Address[] $addresses + * + * @throws RfcComplianceException + */ + public function addAddresses(array $addresses): void + { + foreach ($addresses as $address) { + $this->addAddress($address); + } + } + + /** + * @throws RfcComplianceException + */ + public function addAddress(Address $address): void + { + $this->addresses[] = $address; + } + + /** + * @return Address[] + */ + public function getAddresses(): array + { + return $this->addresses; + } + + /** + * Gets the full mailbox list of this Header as an array of valid RFC 2822 strings. + * + * @return string[] + * + * @throws RfcComplianceException + */ + public function getAddressStrings(): array + { + $strings = []; + foreach ($this->addresses as $address) { + $str = $address->getEncodedAddress(); + if ($name = $address->getName()) { + $str = $this->createPhrase($this, $name, $this->getCharset(), !$strings).' <'.$str.'>'; + } + $strings[] = $str; + } + + return $strings; + } + + public function getBodyAsString(): string + { + return implode(', ', $this->getAddressStrings()); + } + + /** + * Redefine the encoding requirements for addresses. + * + * All "specials" must be encoded as the full header value will not be quoted + * + * @see RFC 2822 3.2.1 + */ + protected function tokenNeedsEncoding(string $token): bool + { + return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token); + } +} diff --git a/vendor/symfony/mime/Header/ParameterizedHeader.php b/vendor/symfony/mime/Header/ParameterizedHeader.php new file mode 100644 index 0000000..5ef4f21 --- /dev/null +++ b/vendor/symfony/mime/Header/ParameterizedHeader.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Encoder\Rfc2231Encoder; + +/** + * @author Chris Corbyn + */ +final class ParameterizedHeader extends UnstructuredHeader +{ + /** + * RFC 2231's definition of a token. + * + * @var string + */ + public const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)'; + + private ?Rfc2231Encoder $encoder = null; + private array $parameters = []; + + public function __construct(string $name, string $value, array $parameters = []) + { + parent::__construct($name, $value); + + foreach ($parameters as $k => $v) { + $this->setParameter($k, $v); + } + + if ('content-type' !== strtolower($name)) { + $this->encoder = new Rfc2231Encoder(); + } + } + + public function setParameter(string $parameter, ?string $value): void + { + $this->setParameters(array_merge($this->getParameters(), [$parameter => $value])); + } + + public function getParameter(string $parameter): string + { + return $this->getParameters()[$parameter] ?? ''; + } + + /** + * @param string[] $parameters + */ + public function setParameters(array $parameters): void + { + $this->parameters = $parameters; + } + + /** + * @return string[] + */ + public function getParameters(): array + { + return $this->parameters; + } + + public function getBodyAsString(): string + { + $body = parent::getBodyAsString(); + foreach ($this->parameters as $name => $value) { + if (null !== $value) { + $body .= '; '.$this->createParameter($name, $value); + } + } + + return $body; + } + + /** + * Generate a list of all tokens in the final header. + * + * This doesn't need to be overridden in theory, but it is for implementation + * reasons to prevent potential breakage of attributes. + */ + protected function toTokens(?string $string = null): array + { + $tokens = parent::toTokens(parent::getBodyAsString()); + + // Try creating any parameters + foreach ($this->parameters as $name => $value) { + if (null !== $value) { + // Add the semi-colon separator + $tokens[\count($tokens) - 1] .= ';'; + $tokens = array_merge($tokens, $this->generateTokenLines(' '.$this->createParameter($name, $value))); + } + } + + return $tokens; + } + + /** + * Render an RFC 2047 compliant header parameter from the $name and $value. + */ + private function createParameter(string $name, string $value): string + { + $origValue = $value; + + $encoded = false; + // Allow room for parameter name, indices, "=" and DQUOTEs + $maxValueLength = $this->getMaxLineLength() - \strlen($name.'=*N"";') - 1; + $firstLineOffset = 0; + + // If it's not already a valid parameter value... + if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + // TODO: text, or something else?? + // ... and it's not ascii + if (!preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $value)) { + $encoded = true; + // Allow space for the indices, charset and language + $maxValueLength = $this->getMaxLineLength() - \strlen($name.'*N*="";') - 1; + $firstLineOffset = \strlen($this->getCharset()."'".$this->getLanguage()."'"); + } + + if (\in_array($name, ['name', 'filename'], true) && 'form-data' === $this->getValue() && 'content-disposition' === strtolower($this->getName()) && preg_match('//u', $value)) { + // WHATWG HTML living standard 4.10.21.8 2 specifies: + // For field names and filenames for file fields, the result of the + // encoding in the previous bullet point must be escaped by replacing + // any 0x0A (LF) bytes with the byte sequence `%0A`, 0x0D (CR) with `%0D` + // and 0x22 (") with `%22`. + // The user agent must not perform any other escapes. + $value = str_replace(['"', "\r", "\n"], ['%22', '%0D', '%0A'], $value); + + if (\strlen($value) <= $maxValueLength) { + return $name.'="'.$value.'"'; + } + + $value = $origValue; + } + } + + // Encode if we need to + if ($encoded || \strlen($value) > $maxValueLength) { + if (null !== $this->encoder) { + $value = $this->encoder->encodeString($origValue, $this->getCharset(), $firstLineOffset, $maxValueLength); + } else { + // We have to go against RFC 2183/2231 in some areas for interoperability + $value = $this->getTokenAsEncodedWord($origValue); + $encoded = false; + } + } + + $valueLines = $this->encoder ? explode("\r\n", $value) : [$value]; + + // Need to add indices + if (\count($valueLines) > 1) { + $paramLines = []; + foreach ($valueLines as $i => $line) { + $paramLines[] = $name.'*'.$i.$this->getEndOfParameterValue($line, true, 0 === $i); + } + + return implode(";\r\n ", $paramLines); + } else { + return $name.$this->getEndOfParameterValue($valueLines[0], $encoded, true); + } + } + + /** + * Returns the parameter value from the "=" and beyond. + * + * @param string $value to append + */ + private function getEndOfParameterValue(string $value, bool $encoded = false, bool $firstLine = false): string + { + $forceHttpQuoting = 'form-data' === $this->getValue() && 'content-disposition' === strtolower($this->getName()); + if ($forceHttpQuoting || !preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + $value = '"'.$value.'"'; + } + $prepend = '='; + if ($encoded) { + $prepend = '*='; + if ($firstLine) { + $prepend = '*='.$this->getCharset()."'".$this->getLanguage()."'"; + } + } + + return $prepend.$value; + } +} diff --git a/vendor/symfony/mime/Header/PathHeader.php b/vendor/symfony/mime/Header/PathHeader.php new file mode 100644 index 0000000..63eb30a --- /dev/null +++ b/vendor/symfony/mime/Header/PathHeader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * A Path Header, such a Return-Path (one address). + * + * @author Chris Corbyn + */ +final class PathHeader extends AbstractHeader +{ + private Address $address; + + public function __construct(string $name, Address $address) + { + parent::__construct($name); + + $this->setAddress($address); + } + + /** + * @param Address $body + * + * @throws RfcComplianceException + */ + public function setBody(mixed $body): void + { + $this->setAddress($body); + } + + public function getBody(): Address + { + return $this->getAddress(); + } + + public function setAddress(Address $address): void + { + $this->address = $address; + } + + public function getAddress(): Address + { + return $this->address; + } + + public function getBodyAsString(): string + { + return '<'.$this->address->toString().'>'; + } +} diff --git a/vendor/symfony/mime/Header/UnstructuredHeader.php b/vendor/symfony/mime/Header/UnstructuredHeader.php new file mode 100644 index 0000000..88ed466 --- /dev/null +++ b/vendor/symfony/mime/Header/UnstructuredHeader.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +/** + * A Simple MIME Header. + * + * @author Chris Corbyn + */ +class UnstructuredHeader extends AbstractHeader +{ + private string $value; + + public function __construct(string $name, string $value) + { + parent::__construct($name); + + $this->setValue($value); + } + + /** + * @param string $body + */ + public function setBody(mixed $body): void + { + $this->setValue($body); + } + + public function getBody(): string + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Set the (unencoded) value of this header. + */ + public function setValue(string $value): void + { + $this->value = $value; + } + + /** + * Get the value of this header prepared for rendering. + */ + public function getBodyAsString(): string + { + return $this->encodeWords($this, $this->value); + } +} diff --git a/vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php b/vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php new file mode 100644 index 0000000..2aaf8e6 --- /dev/null +++ b/vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\HtmlToTextConverter; + +/** + * @author Fabien Potencier + */ +class DefaultHtmlToTextConverter implements HtmlToTextConverterInterface +{ + public function convert(string $html, string $charset): string + { + return strip_tags(preg_replace('{<(head|style)\b.*?}is', '', $html)); + } +} diff --git a/vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php b/vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php new file mode 100644 index 0000000..696f37c --- /dev/null +++ b/vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\HtmlToTextConverter; + +/** + * @author Fabien Potencier + */ +interface HtmlToTextConverterInterface +{ + /** + * Converts an HTML representation of a Message to a text representation. + * + * The output must use the same charset as the HTML one. + */ + public function convert(string $html, string $charset): string; +} diff --git a/vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php b/vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php new file mode 100644 index 0000000..253a7b1 --- /dev/null +++ b/vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\HtmlToTextConverter; + +use League\HTMLToMarkdown\HtmlConverter; +use League\HTMLToMarkdown\HtmlConverterInterface; + +/** + * @author Fabien Potencier + */ +class LeagueHtmlToMarkdownConverter implements HtmlToTextConverterInterface +{ + public function __construct( + private HtmlConverterInterface $converter = new HtmlConverter([ + 'hard_break' => true, + 'strip_tags' => true, + 'remove_nodes' => 'head style', + ]), + ) { + } + + public function convert(string $html, string $charset): string + { + return $this->converter->convert($html); + } +} diff --git a/vendor/symfony/mime/LICENSE b/vendor/symfony/mime/LICENSE new file mode 100644 index 0000000..4dd83ce --- /dev/null +++ b/vendor/symfony/mime/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-present Fabien Potencier + +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. diff --git a/vendor/symfony/mime/Message.php b/vendor/symfony/mime/Message.php new file mode 100644 index 0000000..0374ae7 --- /dev/null +++ b/vendor/symfony/mime/Message.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * @author Fabien Potencier + */ +class Message extends RawMessage +{ + private Headers $headers; + + public function __construct( + ?Headers $headers = null, + private ?AbstractPart $body = null, + ) { + $this->headers = $headers ? clone $headers : new Headers(); + } + + public function __clone() + { + $this->headers = clone $this->headers; + + if (null !== $this->body) { + $this->body = clone $this->body; + } + } + + /** + * @return $this + */ + public function setBody(?AbstractPart $body): static + { + $this->body = $body; + + return $this; + } + + public function getBody(): ?AbstractPart + { + return $this->body; + } + + /** + * @return $this + */ + public function setHeaders(Headers $headers): static + { + $this->headers = $headers; + + return $this; + } + + public function getHeaders(): Headers + { + return $this->headers; + } + + public function getPreparedHeaders(): Headers + { + $headers = clone $this->headers; + + if (!$headers->has('From')) { + if (!$headers->has('Sender')) { + throw new LogicException('An email must have a "From" or a "Sender" header.'); + } + $headers->addMailboxListHeader('From', [$headers->get('Sender')->getAddress()]); + } + + if (!$headers->has('MIME-Version')) { + $headers->addTextHeader('MIME-Version', '1.0'); + } + + if (!$headers->has('Date')) { + $headers->addDateHeader('Date', new \DateTimeImmutable()); + } + + // determine the "real" sender + if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) { + $headers->addMailboxHeader('Sender', $froms[0]); + } + + if (!$headers->has('Message-ID')) { + $headers->addIdHeader('Message-ID', $this->generateMessageId()); + } + + // remove the Bcc field which should NOT be part of the sent message + $headers->remove('Bcc'); + + return $headers; + } + + public function toString(): string + { + if (null === $body = $this->getBody()) { + $body = new TextPart(''); + } + + return $this->getPreparedHeaders()->toString().$body->toString(); + } + + public function toIterable(): iterable + { + if (null === $body = $this->getBody()) { + $body = new TextPart(''); + } + + yield $this->getPreparedHeaders()->toString(); + yield from $body->toIterable(); + } + + public function ensureValidity(): void + { + if (!$this->headers->get('To')?->getBody() && !$this->headers->get('Cc')?->getBody() && !$this->headers->get('Bcc')?->getBody()) { + throw new LogicException('An email must have a "To", "Cc", or "Bcc" header.'); + } + + if (!$this->headers->get('From')?->getBody() && !$this->headers->get('Sender')?->getBody()) { + throw new LogicException('An email must have a "From" or a "Sender" header.'); + } + + parent::ensureValidity(); + } + + public function generateMessageId(): string + { + if ($this->headers->has('Sender')) { + $sender = $this->headers->get('Sender')->getAddress(); + } elseif ($this->headers->has('From')) { + if (!$froms = $this->headers->get('From')->getAddresses()) { + throw new LogicException('A "From" header must have at least one email address.'); + } + $sender = $froms[0]; + } else { + throw new LogicException('An email must have a "From" or a "Sender" header.'); + } + + return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@'); + } + + public function __serialize(): array + { + return [$this->headers, $this->body]; + } + + public function __unserialize(array $data): void + { + [$this->headers, $this->body] = $data; + } +} diff --git a/vendor/symfony/mime/MessageConverter.php b/vendor/symfony/mime/MessageConverter.php new file mode 100644 index 0000000..bdce921 --- /dev/null +++ b/vendor/symfony/mime/MessageConverter.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\AlternativePart; +use Symfony\Component\Mime\Part\Multipart\MixedPart; +use Symfony\Component\Mime\Part\Multipart\RelatedPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * @author Fabien Potencier + */ +final class MessageConverter +{ + /** + * @throws RuntimeException when unable to convert the message to an email + */ + public static function toEmail(Message $message): Email + { + if ($message instanceof Email) { + return $message; + } + + // try to convert to a "simple" Email instance + $body = $message->getBody(); + if ($body instanceof TextPart) { + return self::createEmailFromTextPart($message, $body); + } + + if ($body instanceof AlternativePart) { + return self::createEmailFromAlternativePart($message, $body); + } + + if ($body instanceof RelatedPart) { + return self::createEmailFromRelatedPart($message, $body); + } + + if ($body instanceof MixedPart) { + $parts = $body->getParts(); + if ($parts[0] instanceof RelatedPart) { + $email = self::createEmailFromRelatedPart($message, $parts[0]); + } elseif ($parts[0] instanceof AlternativePart) { + $email = self::createEmailFromAlternativePart($message, $parts[0]); + } elseif ($parts[0] instanceof TextPart) { + $email = self::createEmailFromTextPart($message, $parts[0]); + } else { + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message))); + } + + return self::addParts($email, \array_slice($parts, 1)); + } + + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message))); + } + + private static function createEmailFromTextPart(Message $message, TextPart $part): Email + { + if ('text' === $part->getMediaType() && 'plain' === $part->getMediaSubtype()) { + return (new Email(clone $message->getHeaders()))->text($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8'); + } + if ('text' === $part->getMediaType() && 'html' === $part->getMediaSubtype()) { + return (new Email(clone $message->getHeaders()))->html($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8'); + } + + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message))); + } + + private static function createEmailFromAlternativePart(Message $message, AlternativePart $part): Email + { + $parts = $part->getParts(); + if ( + 2 === \count($parts) + && $parts[0] instanceof TextPart && 'text' === $parts[0]->getMediaType() && 'plain' === $parts[0]->getMediaSubtype() + && $parts[1] instanceof TextPart && 'text' === $parts[1]->getMediaType() && 'html' === $parts[1]->getMediaSubtype() + ) { + return (new Email(clone $message->getHeaders())) + ->text($parts[0]->getBody(), $parts[0]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8') + ->html($parts[1]->getBody(), $parts[1]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8') + ; + } + + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message))); + } + + private static function createEmailFromRelatedPart(Message $message, RelatedPart $part): Email + { + $parts = $part->getParts(); + if ($parts[0] instanceof AlternativePart) { + $email = self::createEmailFromAlternativePart($message, $parts[0]); + } elseif ($parts[0] instanceof TextPart) { + $email = self::createEmailFromTextPart($message, $parts[0]); + } else { + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message))); + } + + return self::addParts($email, \array_slice($parts, 1)); + } + + private static function addParts(Email $email, array $parts): Email + { + foreach ($parts as $part) { + if (!$part instanceof DataPart) { + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($email))); + } + + $email->addPart($part); + } + + return $email; + } +} diff --git a/vendor/symfony/mime/MimeTypeGuesserInterface.php b/vendor/symfony/mime/MimeTypeGuesserInterface.php new file mode 100644 index 0000000..30ee3b6 --- /dev/null +++ b/vendor/symfony/mime/MimeTypeGuesserInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * Guesses the MIME type of a file. + * + * @author Fabien Potencier + */ +interface MimeTypeGuesserInterface +{ + /** + * Returns true if this guesser is supported. + */ + public function isGuesserSupported(): bool; + + /** + * Guesses the MIME type of the file with the given path. + * + * @throws \LogicException If the guesser is not supported + * @throws \InvalidArgumentException If the file does not exist or is not readable + */ + public function guessMimeType(string $path): ?string; +} diff --git a/vendor/symfony/mime/MimeTypes.php b/vendor/symfony/mime/MimeTypes.php new file mode 100644 index 0000000..59fd2d9 --- /dev/null +++ b/vendor/symfony/mime/MimeTypes.php @@ -0,0 +1,3703 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; + +/** + * Manages MIME types and file extensions. + * + * For MIME type guessing, you can register custom guessers + * by calling the registerGuesser() method. + * Custom guessers are always called before any default ones: + * + * $guesser = new MimeTypes(); + * $guesser->registerGuesser(new MyCustomMimeTypeGuesser()); + * + * If you want to change the order of the default guessers, just re-register your + * preferred one as a custom one. The last registered guesser is preferred over + * previously registered ones. + * + * Re-registering a built-in guesser also allows you to configure it: + * + * $guesser = new MimeTypes(); + * $guesser->registerGuesser(new FileinfoMimeTypeGuesser('/path/to/magic/file')); + * + * @author Fabien Potencier + */ +final class MimeTypes implements MimeTypesInterface +{ + private array $extensions = []; + private array $mimeTypes = []; + + /** + * @var MimeTypeGuesserInterface[] + */ + private array $guessers = []; + private static MimeTypes $default; + + public function __construct(array $map = []) + { + foreach ($map as $mimeType => $extensions) { + $this->extensions[$mimeType] = $extensions; + + foreach ($extensions as $extension) { + $this->mimeTypes[$extension][] = $mimeType; + } + } + $this->registerGuesser(new FileBinaryMimeTypeGuesser()); + $this->registerGuesser(new FileinfoMimeTypeGuesser()); + } + + public static function setDefault(self $default): void + { + self::$default = $default; + } + + public static function getDefault(): self + { + return self::$default ??= new self(); + } + + /** + * Registers a MIME type guesser. + * + * The last registered guesser has precedence over the other ones. + */ + public function registerGuesser(MimeTypeGuesserInterface $guesser): void + { + array_unshift($this->guessers, $guesser); + } + + public function getExtensions(string $mimeType): array + { + if ($this->extensions) { + $extensions = $this->extensions[$mimeType] ?? $this->extensions[$lcMimeType = strtolower($mimeType)] ?? null; + } + + return $extensions ?? self::MAP[$mimeType] ?? self::MAP[$lcMimeType ?? strtolower($mimeType)] ?? []; + } + + public function getMimeTypes(string $ext): array + { + if ($this->mimeTypes) { + $mimeTypes = $this->mimeTypes[$ext] ?? $this->mimeTypes[$lcExt = strtolower($ext)] ?? null; + } + + return $mimeTypes ?? self::REVERSE_MAP[$ext] ?? self::REVERSE_MAP[$lcExt ?? strtolower($ext)] ?? []; + } + + public function isGuesserSupported(): bool + { + foreach ($this->guessers as $guesser) { + if ($guesser->isGuesserSupported()) { + return true; + } + } + + return false; + } + + /** + * The file is passed to each registered MIME type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not null, this method terminates and returns the + * value. + */ + public function guessMimeType(string $path): ?string + { + foreach ($this->guessers as $guesser) { + if (!$guesser->isGuesserSupported()) { + continue; + } + + if (null !== $mimeType = $guesser->guessMimeType($path)) { + return $mimeType; + } + } + + if (!$this->isGuesserSupported()) { + throw new LogicException('Unable to guess the MIME type as no guessers are available (have you enabled the php_fileinfo extension?).'); + } + + return null; + } + + /** + * A map of MIME types and their default extensions. + * + * Updated from upstream on 2024-03-17. + * + * @see Resources/bin/update_mime_types.php + */ + private const MAP = [ + 'application/acrobat' => ['pdf'], + 'application/andrew-inset' => ['ez'], + 'application/annodex' => ['anx'], + 'application/appinstaller' => ['appinstaller'], + 'application/applixware' => ['aw'], + 'application/appx' => ['appx'], + 'application/appxbundle' => ['appxbundle'], + 'application/atom+xml' => ['atom'], + 'application/atomcat+xml' => ['atomcat'], + 'application/atomdeleted+xml' => ['atomdeleted'], + 'application/atomsvc+xml' => ['atomsvc'], + 'application/atsc-dwd+xml' => ['dwd'], + 'application/atsc-held+xml' => ['held'], + 'application/atsc-rsat+xml' => ['rsat'], + 'application/bat' => ['bat'], + 'application/bdoc' => ['bdoc'], + 'application/bzip2' => ['bz2', 'bz'], + 'application/calendar+xml' => ['xcs'], + 'application/cbor' => ['cbor'], + 'application/ccxml+xml' => ['ccxml'], + 'application/cdfx+xml' => ['cdfx'], + 'application/cdmi-capability' => ['cdmia'], + 'application/cdmi-container' => ['cdmic'], + 'application/cdmi-domain' => ['cdmid'], + 'application/cdmi-object' => ['cdmio'], + 'application/cdmi-queue' => ['cdmiq'], + 'application/cdr' => ['cdr'], + 'application/coreldraw' => ['cdr'], + 'application/cpl+xml' => ['cpl'], + 'application/csv' => ['csv'], + 'application/cu-seeme' => ['cu'], + 'application/dash+xml' => ['mpd'], + 'application/dash-patch+xml' => ['mpp'], + 'application/davmount+xml' => ['davmount'], + 'application/dbase' => ['dbf'], + 'application/dbf' => ['dbf'], + 'application/dicom' => ['dcm'], + 'application/docbook+xml' => ['dbk', 'docbook'], + 'application/dssc+der' => ['dssc'], + 'application/dssc+xml' => ['xdssc'], + 'application/ecmascript' => ['ecma', 'es'], + 'application/emf' => ['emf'], + 'application/emma+xml' => ['emma'], + 'application/emotionml+xml' => ['emotionml'], + 'application/epub+zip' => ['epub'], + 'application/exi' => ['exi'], + 'application/express' => ['exp'], + 'application/fdt+xml' => ['fdt'], + 'application/fits' => ['fits', 'fit', 'fts'], + 'application/font-tdpfr' => ['pfr'], + 'application/font-woff' => ['woff'], + 'application/futuresplash' => ['swf', 'spl'], + 'application/geo+json' => ['geojson', 'geo.json'], + 'application/gml+xml' => ['gml'], + 'application/gnunet-directory' => ['gnd'], + 'application/gpx' => ['gpx'], + 'application/gpx+xml' => ['gpx'], + 'application/gxf' => ['gxf'], + 'application/gzip' => ['gz'], + 'application/hjson' => ['hjson'], + 'application/hta' => ['hta'], + 'application/hyperstudio' => ['stk'], + 'application/ico' => ['ico'], + 'application/ics' => ['vcs', 'ics'], + 'application/illustrator' => ['ai'], + 'application/inkml+xml' => ['ink', 'inkml'], + 'application/ipfix' => ['ipfix'], + 'application/its+xml' => ['its'], + 'application/java' => ['class'], + 'application/java-archive' => ['jar', 'war', 'ear'], + 'application/java-byte-code' => ['class'], + 'application/java-serialized-object' => ['ser'], + 'application/java-vm' => ['class'], + 'application/javascript' => ['js', 'mjs', 'jsm'], + 'application/jrd+json' => ['jrd'], + 'application/json' => ['json', 'map'], + 'application/json-patch+json' => ['json-patch'], + 'application/json5' => ['json5'], + 'application/jsonml+json' => ['jsonml'], + 'application/ld+json' => ['jsonld'], + 'application/lgr+xml' => ['lgr'], + 'application/lost+xml' => ['lostxml'], + 'application/lotus123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/m3u' => ['m3u', 'm3u8', 'vlc'], + 'application/mac-binhex40' => ['hqx'], + 'application/mac-compactpro' => ['cpt'], + 'application/mads+xml' => ['mads'], + 'application/manifest+json' => ['webmanifest'], + 'application/marc' => ['mrc'], + 'application/marcxml+xml' => ['mrcx'], + 'application/mathematica' => ['ma', 'nb', 'mb'], + 'application/mathml+xml' => ['mathml', 'mml'], + 'application/mbox' => ['mbox'], + 'application/mdb' => ['mdb'], + 'application/media-policy-dataset+xml' => ['mpf'], + 'application/mediaservercontrol+xml' => ['mscml'], + 'application/metalink+xml' => ['metalink'], + 'application/metalink4+xml' => ['meta4'], + 'application/mets+xml' => ['mets'], + 'application/microsoftpatch' => ['msp'], + 'application/microsoftupdate' => ['msu'], + 'application/mmt-aei+xml' => ['maei'], + 'application/mmt-usd+xml' => ['musd'], + 'application/mods+xml' => ['mods'], + 'application/mp21' => ['m21', 'mp21'], + 'application/mp4' => ['mp4s', 'm4p'], + 'application/mrb-consumer+xml' => ['xdf'], + 'application/mrb-publish+xml' => ['xdf'], + 'application/ms-tnef' => ['tnef', 'tnf'], + 'application/msaccess' => ['mdb'], + 'application/msexcel' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'], + 'application/msix' => ['msix'], + 'application/msixbundle' => ['msixbundle'], + 'application/mspowerpoint' => ['ppz', 'ppt', 'pps', 'pot'], + 'application/msword' => ['doc', 'dot'], + 'application/msword-template' => ['dot'], + 'application/mxf' => ['mxf'], + 'application/n-quads' => ['nq'], + 'application/n-triples' => ['nt'], + 'application/nappdf' => ['pdf'], + 'application/node' => ['cjs'], + 'application/octet-stream' => ['bin', 'dms', 'lrf', 'mar', 'so', 'dist', 'distz', 'pkg', 'bpk', 'dump', 'elc', 'deploy', 'exe', 'dll', 'deb', 'dmg', 'iso', 'img', 'msi', 'msp', 'msm', 'buffer'], + 'application/oda' => ['oda'], + 'application/oebps-package+xml' => ['opf'], + 'application/ogg' => ['ogx'], + 'application/omdoc+xml' => ['omdoc'], + 'application/onenote' => ['onetoc', 'onetoc2', 'onetmp', 'onepkg'], + 'application/ovf' => ['ova'], + 'application/owl+xml' => ['owx'], + 'application/oxps' => ['oxps'], + 'application/p2p-overlay+xml' => ['relo'], + 'application/patch-ops-error+xml' => ['xer'], + 'application/pcap' => ['pcap', 'cap', 'dmp'], + 'application/pdf' => ['pdf'], + 'application/pgp' => ['pgp', 'gpg', 'asc'], + 'application/pgp-encrypted' => ['pgp', 'gpg', 'asc'], + 'application/pgp-keys' => ['asc', 'skr', 'pkr', 'pgp', 'gpg', 'key'], + 'application/pgp-signature' => ['asc', 'sig', 'pgp', 'gpg'], + 'application/photoshop' => ['psd'], + 'application/pics-rules' => ['prf'], + 'application/pkcs10' => ['p10'], + 'application/pkcs12' => ['p12', 'pfx'], + 'application/pkcs7-mime' => ['p7m', 'p7c'], + 'application/pkcs7-signature' => ['p7s'], + 'application/pkcs8' => ['p8'], + 'application/pkcs8-encrypted' => ['p8e'], + 'application/pkix-attr-cert' => ['ac'], + 'application/pkix-cert' => ['cer'], + 'application/pkix-crl' => ['crl'], + 'application/pkix-pkipath' => ['pkipath'], + 'application/pkixcmp' => ['pki'], + 'application/pls' => ['pls'], + 'application/pls+xml' => ['pls'], + 'application/postscript' => ['ai', 'eps', 'ps'], + 'application/powerpoint' => ['ppz', 'ppt', 'pps', 'pot'], + 'application/provenance+xml' => ['provx'], + 'application/prs.cww' => ['cww'], + 'application/prs.wavefront-obj' => ['obj'], + 'application/pskc+xml' => ['pskcxml'], + 'application/ram' => ['ram'], + 'application/raml+yaml' => ['raml'], + 'application/rdf+xml' => ['rdf', 'owl', 'rdfs'], + 'application/reginfo+xml' => ['rif'], + 'application/relax-ng-compact-syntax' => ['rnc'], + 'application/resource-lists+xml' => ['rl'], + 'application/resource-lists-diff+xml' => ['rld'], + 'application/rls-services+xml' => ['rs'], + 'application/route-apd+xml' => ['rapd'], + 'application/route-s-tsid+xml' => ['sls'], + 'application/route-usd+xml' => ['rusd'], + 'application/rpki-ghostbusters' => ['gbr'], + 'application/rpki-manifest' => ['mft'], + 'application/rpki-roa' => ['roa'], + 'application/rsd+xml' => ['rsd'], + 'application/rss+xml' => ['rss'], + 'application/rtf' => ['rtf'], + 'application/sbml+xml' => ['sbml'], + 'application/schema+json' => ['json'], + 'application/scvp-cv-request' => ['scq'], + 'application/scvp-cv-response' => ['scs'], + 'application/scvp-vp-request' => ['spq'], + 'application/scvp-vp-response' => ['spp'], + 'application/sdp' => ['sdp'], + 'application/senml+xml' => ['senmlx'], + 'application/sensml+xml' => ['sensmlx'], + 'application/set-payment-initiation' => ['setpay'], + 'application/set-registration-initiation' => ['setreg'], + 'application/shf+xml' => ['shf'], + 'application/sieve' => ['siv', 'sieve'], + 'application/smil' => ['smil', 'smi', 'sml', 'kino'], + 'application/smil+xml' => ['smi', 'smil', 'sml', 'kino'], + 'application/sparql-query' => ['rq', 'qs'], + 'application/sparql-results+xml' => ['srx'], + 'application/sql' => ['sql'], + 'application/srgs' => ['gram'], + 'application/srgs+xml' => ['grxml'], + 'application/sru+xml' => ['sru'], + 'application/ssdl+xml' => ['ssdl'], + 'application/ssml+xml' => ['ssml'], + 'application/stuffit' => ['sit', 'hqx'], + 'application/swid+xml' => ['swidtag'], + 'application/tei+xml' => ['tei', 'teicorpus'], + 'application/tga' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'application/thraud+xml' => ['tfi'], + 'application/timestamped-data' => ['tsd'], + 'application/toml' => ['toml'], + 'application/trig' => ['trig'], + 'application/ttml+xml' => ['ttml'], + 'application/ubjson' => ['ubj'], + 'application/urc-ressheet+xml' => ['rsheet'], + 'application/urc-targetdesc+xml' => ['td'], + 'application/vnd.1000minds.decision-model+xml' => ['1km'], + 'application/vnd.3gpp.pic-bw-large' => ['plb'], + 'application/vnd.3gpp.pic-bw-small' => ['psb'], + 'application/vnd.3gpp.pic-bw-var' => ['pvb'], + 'application/vnd.3gpp2.tcap' => ['tcap'], + 'application/vnd.3m.post-it-notes' => ['pwn'], + 'application/vnd.accpac.simply.aso' => ['aso'], + 'application/vnd.accpac.simply.imp' => ['imp'], + 'application/vnd.acucobol' => ['acu'], + 'application/vnd.acucorp' => ['atc', 'acutc'], + 'application/vnd.adobe.air-application-installer-package+zip' => ['air'], + 'application/vnd.adobe.flash.movie' => ['swf', 'spl'], + 'application/vnd.adobe.formscentral.fcdt' => ['fcdt'], + 'application/vnd.adobe.fxp' => ['fxp', 'fxpl'], + 'application/vnd.adobe.illustrator' => ['ai'], + 'application/vnd.adobe.xdp+xml' => ['xdp'], + 'application/vnd.adobe.xfdf' => ['xfdf'], + 'application/vnd.age' => ['age'], + 'application/vnd.ahead.space' => ['ahead'], + 'application/vnd.airzip.filesecure.azf' => ['azf'], + 'application/vnd.airzip.filesecure.azs' => ['azs'], + 'application/vnd.amazon.ebook' => ['azw'], + 'application/vnd.amazon.mobi8-ebook' => ['azw3', 'kfx'], + 'application/vnd.americandynamics.acc' => ['acc'], + 'application/vnd.amiga.ami' => ['ami'], + 'application/vnd.android.package-archive' => ['apk'], + 'application/vnd.anser-web-certificate-issue-initiation' => ['cii'], + 'application/vnd.anser-web-funds-transfer-initiation' => ['fti'], + 'application/vnd.antix.game-component' => ['atx'], + 'application/vnd.appimage' => ['appimage'], + 'application/vnd.apple.installer+xml' => ['mpkg'], + 'application/vnd.apple.keynote' => ['key', 'keynote'], + 'application/vnd.apple.mpegurl' => ['m3u8', 'm3u'], + 'application/vnd.apple.numbers' => ['numbers'], + 'application/vnd.apple.pages' => ['pages'], + 'application/vnd.apple.pkpass' => ['pkpass'], + 'application/vnd.aristanetworks.swi' => ['swi'], + 'application/vnd.astraea-software.iota' => ['iota'], + 'application/vnd.audiograph' => ['aep'], + 'application/vnd.balsamiq.bmml+xml' => ['bmml'], + 'application/vnd.blueice.multipass' => ['mpm'], + 'application/vnd.bmi' => ['bmi'], + 'application/vnd.businessobjects' => ['rep'], + 'application/vnd.chemdraw+xml' => ['cdxml'], + 'application/vnd.chess-pgn' => ['pgn'], + 'application/vnd.chipnuts.karaoke-mmd' => ['mmd'], + 'application/vnd.cinderella' => ['cdy'], + 'application/vnd.citationstyles.style+xml' => ['csl'], + 'application/vnd.claymore' => ['cla'], + 'application/vnd.cloanto.rp9' => ['rp9'], + 'application/vnd.clonk.c4group' => ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'], + 'application/vnd.cluetrust.cartomobile-config' => ['c11amc'], + 'application/vnd.cluetrust.cartomobile-config-pkg' => ['c11amz'], + 'application/vnd.coffeescript' => ['coffee'], + 'application/vnd.comicbook+zip' => ['cbz'], + 'application/vnd.comicbook-rar' => ['cbr'], + 'application/vnd.commonspace' => ['csp'], + 'application/vnd.contact.cmsg' => ['cdbcmsg'], + 'application/vnd.corel-draw' => ['cdr'], + 'application/vnd.cosmocaller' => ['cmc'], + 'application/vnd.crick.clicker' => ['clkx'], + 'application/vnd.crick.clicker.keyboard' => ['clkk'], + 'application/vnd.crick.clicker.palette' => ['clkp'], + 'application/vnd.crick.clicker.template' => ['clkt'], + 'application/vnd.crick.clicker.wordbank' => ['clkw'], + 'application/vnd.criticaltools.wbs+xml' => ['wbs'], + 'application/vnd.ctc-posml' => ['pml'], + 'application/vnd.cups-ppd' => ['ppd'], + 'application/vnd.curl.car' => ['car'], + 'application/vnd.curl.pcurl' => ['pcurl'], + 'application/vnd.dart' => ['dart'], + 'application/vnd.data-vision.rdz' => ['rdz'], + 'application/vnd.dbf' => ['dbf'], + 'application/vnd.debian.binary-package' => ['deb', 'udeb'], + 'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'], + 'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'], + 'application/vnd.dece.unspecified' => ['uvx', 'uvvx'], + 'application/vnd.dece.zip' => ['uvz', 'uvvz'], + 'application/vnd.denovo.fcselayout-link' => ['fe_launch'], + 'application/vnd.dna' => ['dna'], + 'application/vnd.dolby.mlp' => ['mlp'], + 'application/vnd.dpgraph' => ['dpg'], + 'application/vnd.dreamfactory' => ['dfac'], + 'application/vnd.ds-keypoint' => ['kpxx'], + 'application/vnd.dvb.ait' => ['ait'], + 'application/vnd.dvb.service' => ['svc'], + 'application/vnd.dynageo' => ['geo'], + 'application/vnd.ecowin.chart' => ['mag'], + 'application/vnd.efi.img' => ['raw-disk-image', 'img'], + 'application/vnd.efi.iso' => ['iso', 'iso9660'], + 'application/vnd.emusic-emusic_package' => ['emp'], + 'application/vnd.enliven' => ['nml'], + 'application/vnd.epson.esf' => ['esf'], + 'application/vnd.epson.msf' => ['msf'], + 'application/vnd.epson.quickanime' => ['qam'], + 'application/vnd.epson.salt' => ['slt'], + 'application/vnd.epson.ssf' => ['ssf'], + 'application/vnd.eszigno3+xml' => ['es3', 'et3'], + 'application/vnd.etsi.asic-e+zip' => ['asice'], + 'application/vnd.ezpix-album' => ['ez2'], + 'application/vnd.ezpix-package' => ['ez3'], + 'application/vnd.fdf' => ['fdf'], + 'application/vnd.fdsn.mseed' => ['mseed'], + 'application/vnd.fdsn.seed' => ['seed', 'dataless'], + 'application/vnd.flatpak' => ['flatpak', 'xdgapp'], + 'application/vnd.flatpak.ref' => ['flatpakref'], + 'application/vnd.flatpak.repo' => ['flatpakrepo'], + 'application/vnd.flographit' => ['gph'], + 'application/vnd.fluxtime.clip' => ['ftc'], + 'application/vnd.framemaker' => ['fm', 'frame', 'maker', 'book'], + 'application/vnd.frogans.fnc' => ['fnc'], + 'application/vnd.frogans.ltf' => ['ltf'], + 'application/vnd.fsc.weblaunch' => ['fsc'], + 'application/vnd.fujitsu.oasys' => ['oas'], + 'application/vnd.fujitsu.oasys2' => ['oa2'], + 'application/vnd.fujitsu.oasys3' => ['oa3'], + 'application/vnd.fujitsu.oasysgp' => ['fg5'], + 'application/vnd.fujitsu.oasysprs' => ['bh2'], + 'application/vnd.fujixerox.ddd' => ['ddd'], + 'application/vnd.fujixerox.docuworks' => ['xdw'], + 'application/vnd.fujixerox.docuworks.binder' => ['xbd'], + 'application/vnd.fuzzysheet' => ['fzs'], + 'application/vnd.genomatix.tuxedo' => ['txd'], + 'application/vnd.geo+json' => ['geojson', 'geo.json'], + 'application/vnd.geogebra.file' => ['ggb'], + 'application/vnd.geogebra.tool' => ['ggt'], + 'application/vnd.geometry-explorer' => ['gex', 'gre'], + 'application/vnd.geonext' => ['gxt'], + 'application/vnd.geoplan' => ['g2w'], + 'application/vnd.geospace' => ['g3w'], + 'application/vnd.gerber' => ['gbr'], + 'application/vnd.gmx' => ['gmx'], + 'application/vnd.google-apps.document' => ['gdoc'], + 'application/vnd.google-apps.presentation' => ['gslides'], + 'application/vnd.google-apps.spreadsheet' => ['gsheet'], + 'application/vnd.google-earth.kml+xml' => ['kml'], + 'application/vnd.google-earth.kmz' => ['kmz'], + 'application/vnd.grafeq' => ['gqf', 'gqs'], + 'application/vnd.groove-account' => ['gac'], + 'application/vnd.groove-help' => ['ghf'], + 'application/vnd.groove-identity-message' => ['gim'], + 'application/vnd.groove-injector' => ['grv'], + 'application/vnd.groove-tool-message' => ['gtm'], + 'application/vnd.groove-tool-template' => ['tpl'], + 'application/vnd.groove-vcard' => ['vcg'], + 'application/vnd.haansoft-hwp' => ['hwp'], + 'application/vnd.haansoft-hwt' => ['hwt'], + 'application/vnd.hal+xml' => ['hal'], + 'application/vnd.handheld-entertainment+xml' => ['zmm'], + 'application/vnd.hbci' => ['hbci'], + 'application/vnd.hhe.lesson-player' => ['les'], + 'application/vnd.hp-hpgl' => ['hpgl'], + 'application/vnd.hp-hpid' => ['hpid'], + 'application/vnd.hp-hps' => ['hps'], + 'application/vnd.hp-jlyt' => ['jlt'], + 'application/vnd.hp-pcl' => ['pcl'], + 'application/vnd.hp-pclxl' => ['pclxl'], + 'application/vnd.hydrostatix.sof-data' => ['sfd-hdstx'], + 'application/vnd.ibm.minipay' => ['mpy'], + 'application/vnd.ibm.modcap' => ['afp', 'listafp', 'list3820'], + 'application/vnd.ibm.rights-management' => ['irm'], + 'application/vnd.ibm.secure-container' => ['sc'], + 'application/vnd.iccprofile' => ['icc', 'icm'], + 'application/vnd.igloader' => ['igl'], + 'application/vnd.immervision-ivp' => ['ivp'], + 'application/vnd.immervision-ivu' => ['ivu'], + 'application/vnd.insors.igm' => ['igm'], + 'application/vnd.intercon.formnet' => ['xpw', 'xpx'], + 'application/vnd.intergeo' => ['i2g'], + 'application/vnd.intu.qbo' => ['qbo'], + 'application/vnd.intu.qfx' => ['qfx'], + 'application/vnd.ipunplugged.rcprofile' => ['rcprofile'], + 'application/vnd.irepository.package+xml' => ['irp'], + 'application/vnd.is-xpr' => ['xpr'], + 'application/vnd.isac.fcs' => ['fcs'], + 'application/vnd.jam' => ['jam'], + 'application/vnd.jcp.javame.midlet-rms' => ['rms'], + 'application/vnd.jisp' => ['jisp'], + 'application/vnd.joost.joda-archive' => ['joda'], + 'application/vnd.kahootz' => ['ktz', 'ktr'], + 'application/vnd.kde.karbon' => ['karbon'], + 'application/vnd.kde.kchart' => ['chrt'], + 'application/vnd.kde.kformula' => ['kfo'], + 'application/vnd.kde.kivio' => ['flw'], + 'application/vnd.kde.kontour' => ['kon'], + 'application/vnd.kde.kpresenter' => ['kpr', 'kpt'], + 'application/vnd.kde.kspread' => ['ksp'], + 'application/vnd.kde.kword' => ['kwd', 'kwt'], + 'application/vnd.kenameaapp' => ['htke'], + 'application/vnd.kidspiration' => ['kia'], + 'application/vnd.kinar' => ['kne', 'knp'], + 'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'], + 'application/vnd.kodak-descriptor' => ['sse'], + 'application/vnd.las.las+xml' => ['lasxml'], + 'application/vnd.llamagraphics.life-balance.desktop' => ['lbd'], + 'application/vnd.llamagraphics.life-balance.exchange+xml' => ['lbe'], + 'application/vnd.lotus-1-2-3' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/vnd.lotus-approach' => ['apr'], + 'application/vnd.lotus-freelance' => ['pre'], + 'application/vnd.lotus-notes' => ['nsf'], + 'application/vnd.lotus-organizer' => ['org'], + 'application/vnd.lotus-screencam' => ['scm'], + 'application/vnd.lotus-wordpro' => ['lwp'], + 'application/vnd.macports.portpkg' => ['portpkg'], + 'application/vnd.mapbox-vector-tile' => ['mvt'], + 'application/vnd.mcd' => ['mcd'], + 'application/vnd.medcalcdata' => ['mc1'], + 'application/vnd.mediastation.cdkey' => ['cdkey'], + 'application/vnd.mfer' => ['mwf'], + 'application/vnd.mfmp' => ['mfm'], + 'application/vnd.micrografx.flo' => ['flo'], + 'application/vnd.micrografx.igx' => ['igx'], + 'application/vnd.microsoft.portable-executable' => ['exe', 'dll', 'cpl', 'drv', 'scr', 'efi', 'ocx', 'sys', 'lib'], + 'application/vnd.mif' => ['mif'], + 'application/vnd.mobius.daf' => ['daf'], + 'application/vnd.mobius.dis' => ['dis'], + 'application/vnd.mobius.mbk' => ['mbk'], + 'application/vnd.mobius.mqy' => ['mqy'], + 'application/vnd.mobius.msl' => ['msl'], + 'application/vnd.mobius.plc' => ['plc'], + 'application/vnd.mobius.txf' => ['txf'], + 'application/vnd.mophun.application' => ['mpn'], + 'application/vnd.mophun.certificate' => ['mpc'], + 'application/vnd.mozilla.xul+xml' => ['xul'], + 'application/vnd.ms-3mfdocument' => ['3mf'], + 'application/vnd.ms-access' => ['mdb'], + 'application/vnd.ms-artgalry' => ['cil'], + 'application/vnd.ms-asf' => ['asf'], + 'application/vnd.ms-cab-compressed' => ['cab'], + 'application/vnd.ms-excel' => ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw', 'xll', 'xld'], + 'application/vnd.ms-excel.addin.macroenabled.12' => ['xlam'], + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => ['xlsb'], + 'application/vnd.ms-excel.sheet.macroenabled.12' => ['xlsm'], + 'application/vnd.ms-excel.template.macroenabled.12' => ['xltm'], + 'application/vnd.ms-fontobject' => ['eot'], + 'application/vnd.ms-htmlhelp' => ['chm'], + 'application/vnd.ms-ims' => ['ims'], + 'application/vnd.ms-lrm' => ['lrm'], + 'application/vnd.ms-officetheme' => ['thmx'], + 'application/vnd.ms-outlook' => ['msg'], + 'application/vnd.ms-pki.seccat' => ['cat'], + 'application/vnd.ms-pki.stl' => ['stl'], + 'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot', 'ppz'], + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => ['ppam'], + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => ['pptm'], + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => ['sldm'], + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => ['ppsm'], + 'application/vnd.ms-powerpoint.template.macroenabled.12' => ['potm'], + 'application/vnd.ms-project' => ['mpp', 'mpt'], + 'application/vnd.ms-publisher' => ['pub'], + 'application/vnd.ms-tnef' => ['tnef', 'tnf'], + 'application/vnd.ms-visio.drawing.macroenabled.main+xml' => ['vsdm'], + 'application/vnd.ms-visio.drawing.main+xml' => ['vsdx'], + 'application/vnd.ms-visio.stencil.macroenabled.main+xml' => ['vssm'], + 'application/vnd.ms-visio.stencil.main+xml' => ['vssx'], + 'application/vnd.ms-visio.template.macroenabled.main+xml' => ['vstm'], + 'application/vnd.ms-visio.template.main+xml' => ['vstx'], + 'application/vnd.ms-word' => ['doc'], + 'application/vnd.ms-word.document.macroenabled.12' => ['docm'], + 'application/vnd.ms-word.template.macroenabled.12' => ['dotm'], + 'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb', 'xlr'], + 'application/vnd.ms-wpl' => ['wpl'], + 'application/vnd.ms-xpsdocument' => ['xps'], + 'application/vnd.msaccess' => ['mdb'], + 'application/vnd.mseq' => ['mseq'], + 'application/vnd.musician' => ['mus'], + 'application/vnd.muvee.style' => ['msty'], + 'application/vnd.mynfc' => ['taglet'], + 'application/vnd.neurolanguage.nlu' => ['nlu'], + 'application/vnd.nintendo.snes.rom' => ['sfc', 'smc'], + 'application/vnd.nitf' => ['ntf', 'nitf'], + 'application/vnd.noblenet-directory' => ['nnd'], + 'application/vnd.noblenet-sealer' => ['nns'], + 'application/vnd.noblenet-web' => ['nnw'], + 'application/vnd.nokia.n-gage.ac+xml' => ['ac'], + 'application/vnd.nokia.n-gage.data' => ['ngdat'], + 'application/vnd.nokia.n-gage.symbian.install' => ['n-gage'], + 'application/vnd.nokia.radio-preset' => ['rpst'], + 'application/vnd.nokia.radio-presets' => ['rpss'], + 'application/vnd.novadigm.edm' => ['edm'], + 'application/vnd.novadigm.edx' => ['edx'], + 'application/vnd.novadigm.ext' => ['ext'], + 'application/vnd.oasis.docbook+xml' => ['dbk', 'docbook'], + 'application/vnd.oasis.opendocument.chart' => ['odc'], + 'application/vnd.oasis.opendocument.chart-template' => ['otc'], + 'application/vnd.oasis.opendocument.database' => ['odb'], + 'application/vnd.oasis.opendocument.formula' => ['odf'], + 'application/vnd.oasis.opendocument.formula-template' => ['odft', 'otf'], + 'application/vnd.oasis.opendocument.graphics' => ['odg'], + 'application/vnd.oasis.opendocument.graphics-flat-xml' => ['fodg'], + 'application/vnd.oasis.opendocument.graphics-template' => ['otg'], + 'application/vnd.oasis.opendocument.image' => ['odi'], + 'application/vnd.oasis.opendocument.image-template' => ['oti'], + 'application/vnd.oasis.opendocument.presentation' => ['odp'], + 'application/vnd.oasis.opendocument.presentation-flat-xml' => ['fodp'], + 'application/vnd.oasis.opendocument.presentation-template' => ['otp'], + 'application/vnd.oasis.opendocument.spreadsheet' => ['ods'], + 'application/vnd.oasis.opendocument.spreadsheet-flat-xml' => ['fods'], + 'application/vnd.oasis.opendocument.spreadsheet-template' => ['ots'], + 'application/vnd.oasis.opendocument.text' => ['odt'], + 'application/vnd.oasis.opendocument.text-flat-xml' => ['fodt'], + 'application/vnd.oasis.opendocument.text-master' => ['odm'], + 'application/vnd.oasis.opendocument.text-template' => ['ott'], + 'application/vnd.oasis.opendocument.text-web' => ['oth'], + 'application/vnd.olpc-sugar' => ['xo'], + 'application/vnd.oma.dd2+xml' => ['dd2'], + 'application/vnd.openblox.game+xml' => ['obgx'], + 'application/vnd.openofficeorg.extension' => ['oxt'], + 'application/vnd.openstreetmap.data+xml' => ['osm'], + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['pptx'], + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => ['sldx'], + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => ['ppsx'], + 'application/vnd.openxmlformats-officedocument.presentationml.template' => ['potx'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => ['xltx'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => ['dotx'], + 'application/vnd.osgeo.mapguide.package' => ['mgp'], + 'application/vnd.osgi.dp' => ['dp'], + 'application/vnd.osgi.subsystem' => ['esa'], + 'application/vnd.palm' => ['pdb', 'pqa', 'oprc', 'prc'], + 'application/vnd.pawaafile' => ['paw'], + 'application/vnd.pg.format' => ['str'], + 'application/vnd.pg.osasli' => ['ei6'], + 'application/vnd.picsel' => ['efif'], + 'application/vnd.pmi.widget' => ['wg'], + 'application/vnd.pocketlearn' => ['plf'], + 'application/vnd.powerbuilder6' => ['pbd'], + 'application/vnd.previewsystems.box' => ['box'], + 'application/vnd.proteus.magazine' => ['mgz'], + 'application/vnd.publishare-delta-tree' => ['qps'], + 'application/vnd.pvi.ptid1' => ['ptid'], + 'application/vnd.quark.quarkxpress' => ['qxd', 'qxt', 'qwd', 'qwt', 'qxl', 'qxb'], + 'application/vnd.rar' => ['rar'], + 'application/vnd.realvnc.bed' => ['bed'], + 'application/vnd.recordare.musicxml' => ['mxl'], + 'application/vnd.recordare.musicxml+xml' => ['musicxml'], + 'application/vnd.rig.cryptonote' => ['cryptonote'], + 'application/vnd.rim.cod' => ['cod'], + 'application/vnd.rn-realmedia' => ['rm', 'rmj', 'rmm', 'rms', 'rmx', 'rmvb'], + 'application/vnd.rn-realmedia-vbr' => ['rmvb', 'rm', 'rmj', 'rmm', 'rms', 'rmx'], + 'application/vnd.route66.link66+xml' => ['link66'], + 'application/vnd.sailingtracker.track' => ['st'], + 'application/vnd.sdp' => ['sdp'], + 'application/vnd.seemail' => ['see'], + 'application/vnd.sema' => ['sema'], + 'application/vnd.semd' => ['semd'], + 'application/vnd.semf' => ['semf'], + 'application/vnd.shana.informed.formdata' => ['ifm'], + 'application/vnd.shana.informed.formtemplate' => ['itp'], + 'application/vnd.shana.informed.interchange' => ['iif'], + 'application/vnd.shana.informed.package' => ['ipk'], + 'application/vnd.simtech-mindmapper' => ['twd', 'twds'], + 'application/vnd.smaf' => ['mmf', 'smaf'], + 'application/vnd.smart.teacher' => ['teacher'], + 'application/vnd.snap' => ['snap'], + 'application/vnd.software602.filler.form+xml' => ['fo'], + 'application/vnd.solent.sdkm+xml' => ['sdkm', 'sdkd'], + 'application/vnd.spotfire.dxp' => ['dxp'], + 'application/vnd.spotfire.sfs' => ['sfs'], + 'application/vnd.sqlite3' => ['sqlite3'], + 'application/vnd.squashfs' => ['sqsh'], + 'application/vnd.stardivision.calc' => ['sdc'], + 'application/vnd.stardivision.chart' => ['sds'], + 'application/vnd.stardivision.draw' => ['sda'], + 'application/vnd.stardivision.impress' => ['sdd', 'sdp'], + 'application/vnd.stardivision.mail' => ['smd'], + 'application/vnd.stardivision.math' => ['smf'], + 'application/vnd.stardivision.writer' => ['sdw', 'vor', 'sgl'], + 'application/vnd.stardivision.writer-global' => ['sgl', 'sdw', 'vor'], + 'application/vnd.stepmania.package' => ['smzip'], + 'application/vnd.stepmania.stepchart' => ['sm'], + 'application/vnd.sun.wadl+xml' => ['wadl'], + 'application/vnd.sun.xml.base' => ['odb'], + 'application/vnd.sun.xml.calc' => ['sxc'], + 'application/vnd.sun.xml.calc.template' => ['stc'], + 'application/vnd.sun.xml.draw' => ['sxd'], + 'application/vnd.sun.xml.draw.template' => ['std'], + 'application/vnd.sun.xml.impress' => ['sxi'], + 'application/vnd.sun.xml.impress.template' => ['sti'], + 'application/vnd.sun.xml.math' => ['sxm'], + 'application/vnd.sun.xml.writer' => ['sxw'], + 'application/vnd.sun.xml.writer.global' => ['sxg'], + 'application/vnd.sun.xml.writer.template' => ['stw'], + 'application/vnd.sus-calendar' => ['sus', 'susp'], + 'application/vnd.svd' => ['svd'], + 'application/vnd.symbian.install' => ['sis', 'sisx'], + 'application/vnd.syncml+xml' => ['xsm'], + 'application/vnd.syncml.dm+wbxml' => ['bdm'], + 'application/vnd.syncml.dm+xml' => ['xdm'], + 'application/vnd.syncml.dmddf+xml' => ['ddf'], + 'application/vnd.tao.intent-module-archive' => ['tao'], + 'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'], + 'application/vnd.tmobile-livetv' => ['tmo'], + 'application/vnd.trid.tpt' => ['tpt'], + 'application/vnd.triscape.mxs' => ['mxs'], + 'application/vnd.trueapp' => ['tra'], + 'application/vnd.truedoc' => ['pfr'], + 'application/vnd.ufdl' => ['ufd', 'ufdl'], + 'application/vnd.uiq.theme' => ['utz'], + 'application/vnd.umajin' => ['umj'], + 'application/vnd.unity' => ['unityweb'], + 'application/vnd.uoml+xml' => ['uoml'], + 'application/vnd.vcx' => ['vcx'], + 'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'], + 'application/vnd.visionary' => ['vis'], + 'application/vnd.vsf' => ['vsf'], + 'application/vnd.wap.wbxml' => ['wbxml'], + 'application/vnd.wap.wmlc' => ['wmlc'], + 'application/vnd.wap.wmlscriptc' => ['wmlsc'], + 'application/vnd.webturbo' => ['wtb'], + 'application/vnd.wolfram.player' => ['nbp'], + 'application/vnd.wordperfect' => ['wpd', 'wp', 'wp4', 'wp5', 'wp6', 'wpp'], + 'application/vnd.wqd' => ['wqd'], + 'application/vnd.wt.stf' => ['stf'], + 'application/vnd.xara' => ['xar'], + 'application/vnd.xdgapp' => ['flatpak', 'xdgapp'], + 'application/vnd.xfdl' => ['xfdl'], + 'application/vnd.yamaha.hv-dic' => ['hvd'], + 'application/vnd.yamaha.hv-script' => ['hvs'], + 'application/vnd.yamaha.hv-voice' => ['hvp'], + 'application/vnd.yamaha.openscoreformat' => ['osf'], + 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => ['osfpvg'], + 'application/vnd.yamaha.smaf-audio' => ['saf'], + 'application/vnd.yamaha.smaf-phrase' => ['spf'], + 'application/vnd.yellowriver-custom-menu' => ['cmp'], + 'application/vnd.youtube.yt' => ['yt'], + 'application/vnd.zul' => ['zir', 'zirz'], + 'application/vnd.zzazz.deck+xml' => ['zaz'], + 'application/voicexml+xml' => ['vxml'], + 'application/wasm' => ['wasm'], + 'application/watcherinfo+xml' => ['wif'], + 'application/widget' => ['wgt'], + 'application/winhlp' => ['hlp'], + 'application/wk1' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/wmf' => ['wmf'], + 'application/wordperfect' => ['wp', 'wp4', 'wp5', 'wp6', 'wpd', 'wpp'], + 'application/wsdl+xml' => ['wsdl'], + 'application/wspolicy+xml' => ['wspolicy'], + 'application/wwf' => ['wwf'], + 'application/x-123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/x-7z-compressed' => ['7z', '7z.001'], + 'application/x-abiword' => ['abw', 'abw.CRASHED', 'abw.gz', 'zabw'], + 'application/x-ace' => ['ace'], + 'application/x-ace-compressed' => ['ace'], + 'application/x-alz' => ['alz'], + 'application/x-amiga-disk-format' => ['adf'], + 'application/x-amipro' => ['sam'], + 'application/x-annodex' => ['anx'], + 'application/x-aportisdoc' => ['pdb', 'pdc'], + 'application/x-apple-diskimage' => ['dmg'], + 'application/x-apple-systemprofiler+xml' => ['spx'], + 'application/x-appleworks-document' => ['cwk'], + 'application/x-applix-spreadsheet' => ['as'], + 'application/x-applix-word' => ['aw'], + 'application/x-archive' => ['a', 'ar'], + 'application/x-arj' => ['arj'], + 'application/x-asar' => ['asar'], + 'application/x-asp' => ['asp'], + 'application/x-atari-2600-rom' => ['a26'], + 'application/x-atari-7800-rom' => ['a78'], + 'application/x-atari-lynx-rom' => ['lnx'], + 'application/x-authorware-bin' => ['aab', 'x32', 'u32', 'vox'], + 'application/x-authorware-map' => ['aam'], + 'application/x-authorware-seg' => ['aas'], + 'application/x-awk' => ['awk'], + 'application/x-bat' => ['bat'], + 'application/x-bcpio' => ['bcpio'], + 'application/x-bdoc' => ['bdoc'], + 'application/x-bittorrent' => ['torrent'], + 'application/x-blender' => ['blend', 'BLEND', 'blender'], + 'application/x-blorb' => ['blb', 'blorb'], + 'application/x-bps-patch' => ['bps'], + 'application/x-bsdiff' => ['bsdiff'], + 'application/x-bz2' => ['bz2'], + 'application/x-bzdvi' => ['dvi.bz2'], + 'application/x-bzip' => ['bz', 'bz2'], + 'application/x-bzip-compressed-tar' => ['tar.bz2', 'tbz2', 'tb2'], + 'application/x-bzip1' => ['bz'], + 'application/x-bzip1-compressed-tar' => ['tar.bz', 'tbz'], + 'application/x-bzip2' => ['bz2', 'boz'], + 'application/x-bzip2-compressed-tar' => ['tar.bz2', 'tbz2', 'tb2'], + 'application/x-bzip3' => ['bz3'], + 'application/x-bzip3-compressed-tar' => ['tar.bz3', 'tbz3'], + 'application/x-bzpdf' => ['pdf.bz2'], + 'application/x-bzpostscript' => ['ps.bz2'], + 'application/x-cb7' => ['cb7'], + 'application/x-cbr' => ['cbr', 'cba', 'cbt', 'cbz', 'cb7'], + 'application/x-cbt' => ['cbt'], + 'application/x-cbz' => ['cbz'], + 'application/x-ccmx' => ['ccmx'], + 'application/x-cd-image' => ['iso', 'iso9660'], + 'application/x-cdlink' => ['vcd'], + 'application/x-cdr' => ['cdr'], + 'application/x-cdrdao-toc' => ['toc'], + 'application/x-cfs-compressed' => ['cfs'], + 'application/x-chat' => ['chat'], + 'application/x-chess-pgn' => ['pgn'], + 'application/x-chm' => ['chm'], + 'application/x-chrome-extension' => ['crx'], + 'application/x-cisco-vpn-settings' => ['pcf'], + 'application/x-cocoa' => ['cco'], + 'application/x-compress' => ['Z'], + 'application/x-compressed-iso' => ['cso'], + 'application/x-compressed-tar' => ['tar.gz', 'tgz'], + 'application/x-conference' => ['nsc'], + 'application/x-coreldraw' => ['cdr'], + 'application/x-cpio' => ['cpio'], + 'application/x-cpio-compressed' => ['cpio.gz'], + 'application/x-csh' => ['csh'], + 'application/x-cue' => ['cue'], + 'application/x-dar' => ['dar'], + 'application/x-dbase' => ['dbf'], + 'application/x-dbf' => ['dbf'], + 'application/x-dc-rom' => ['dc'], + 'application/x-deb' => ['deb', 'udeb'], + 'application/x-debian-package' => ['deb', 'udeb'], + 'application/x-designer' => ['ui'], + 'application/x-desktop' => ['desktop', 'kdelnk'], + 'application/x-dgc-compressed' => ['dgc'], + 'application/x-dia-diagram' => ['dia'], + 'application/x-dia-shape' => ['shape'], + 'application/x-director' => ['dir', 'dcr', 'dxr', 'cst', 'cct', 'cxt', 'w3d', 'fgd', 'swa'], + 'application/x-discjuggler-cd-image' => ['cdi'], + 'application/x-docbook+xml' => ['dbk', 'docbook'], + 'application/x-doom' => ['wad'], + 'application/x-doom-wad' => ['wad'], + 'application/x-dosexec' => ['exe'], + 'application/x-dreamcast-rom' => ['iso'], + 'application/x-dtbncx+xml' => ['ncx'], + 'application/x-dtbook+xml' => ['dtb'], + 'application/x-dtbresource+xml' => ['res'], + 'application/x-dvi' => ['dvi'], + 'application/x-e-theme' => ['etheme'], + 'application/x-egon' => ['egon'], + 'application/x-emf' => ['emf'], + 'application/x-envoy' => ['evy'], + 'application/x-eris-link+cbor' => ['eris'], + 'application/x-eva' => ['eva'], + 'application/x-excellon' => ['drl'], + 'application/x-fd-file' => ['fd', 'qd'], + 'application/x-fds-disk' => ['fds'], + 'application/x-fictionbook' => ['fb2'], + 'application/x-fictionbook+xml' => ['fb2'], + 'application/x-fishscript' => ['fish'], + 'application/x-flash-video' => ['flv'], + 'application/x-fluid' => ['fl'], + 'application/x-font-afm' => ['afm'], + 'application/x-font-bdf' => ['bdf'], + 'application/x-font-ghostscript' => ['gsf'], + 'application/x-font-linux-psf' => ['psf'], + 'application/x-font-otf' => ['otf'], + 'application/x-font-pcf' => ['pcf', 'pcf.Z', 'pcf.gz'], + 'application/x-font-snf' => ['snf'], + 'application/x-font-speedo' => ['spd'], + 'application/x-font-truetype' => ['ttf'], + 'application/x-font-ttf' => ['ttf'], + 'application/x-font-ttx' => ['ttx'], + 'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm', 'gsf'], + 'application/x-font-woff' => ['woff'], + 'application/x-frame' => ['fm'], + 'application/x-freearc' => ['arc'], + 'application/x-futuresplash' => ['spl'], + 'application/x-gameboy-color-rom' => ['gbc', 'cgb'], + 'application/x-gameboy-rom' => ['gb', 'sgb'], + 'application/x-gamecube-iso-image' => ['iso'], + 'application/x-gamecube-rom' => ['iso'], + 'application/x-gamegear-rom' => ['gg'], + 'application/x-gba-rom' => ['gba', 'agb'], + 'application/x-gca-compressed' => ['gca'], + 'application/x-gd-rom-cue' => ['gdi'], + 'application/x-gdscript' => ['gd'], + 'application/x-gedcom' => ['ged', 'gedcom'], + 'application/x-genesis-32x-rom' => ['32x', 'mdx'], + 'application/x-genesis-rom' => ['gen', 'smd', 'sgd'], + 'application/x-gerber' => ['gbr'], + 'application/x-gerber-job' => ['gbrjob'], + 'application/x-gettext' => ['po'], + 'application/x-gettext-translation' => ['gmo', 'mo'], + 'application/x-glade' => ['glade'], + 'application/x-glulx' => ['ulx'], + 'application/x-gnome-app-info' => ['desktop', 'kdelnk'], + 'application/x-gnucash' => ['gnucash', 'gnc', 'xac'], + 'application/x-gnumeric' => ['gnumeric'], + 'application/x-gnuplot' => ['gp', 'gplt', 'gnuplot'], + 'application/x-go-sgf' => ['sgf'], + 'application/x-godot-resource' => ['res', 'tres'], + 'application/x-godot-scene' => ['scn', 'tscn', 'escn'], + 'application/x-godot-shader' => ['gdshader'], + 'application/x-gpx' => ['gpx'], + 'application/x-gpx+xml' => ['gpx'], + 'application/x-gramps-xml' => ['gramps'], + 'application/x-graphite' => ['gra'], + 'application/x-gtar' => ['gtar', 'tar', 'gem'], + 'application/x-gtk-builder' => ['ui'], + 'application/x-gz-font-linux-psf' => ['psf.gz'], + 'application/x-gzdvi' => ['dvi.gz'], + 'application/x-gzip' => ['gz'], + 'application/x-gzpdf' => ['pdf.gz'], + 'application/x-gzpostscript' => ['ps.gz'], + 'application/x-hdf' => ['hdf', 'hdf4', 'h4', 'hdf5', 'h5'], + 'application/x-hfe-file' => ['hfe'], + 'application/x-hfe-floppy-image' => ['hfe'], + 'application/x-httpd-php' => ['php'], + 'application/x-hwp' => ['hwp'], + 'application/x-hwt' => ['hwt'], + 'application/x-ica' => ['ica'], + 'application/x-install-instructions' => ['install'], + 'application/x-ips-patch' => ['ips'], + 'application/x-ipynb+json' => ['ipynb'], + 'application/x-iso9660-appimage' => ['appimage'], + 'application/x-iso9660-image' => ['iso', 'iso9660'], + 'application/x-it87' => ['it87'], + 'application/x-iwork-keynote-sffkey' => ['key'], + 'application/x-iwork-numbers-sffnumbers' => ['numbers'], + 'application/x-iwork-pages-sffpages' => ['pages'], + 'application/x-jar' => ['jar'], + 'application/x-java' => ['class'], + 'application/x-java-archive' => ['jar'], + 'application/x-java-archive-diff' => ['jardiff'], + 'application/x-java-class' => ['class'], + 'application/x-java-jce-keystore' => ['jceks'], + 'application/x-java-jnlp-file' => ['jnlp'], + 'application/x-java-keystore' => ['jks', 'ks'], + 'application/x-java-pack200' => ['pack'], + 'application/x-java-vm' => ['class'], + 'application/x-javascript' => ['js', 'jsm', 'mjs'], + 'application/x-jbuilder-project' => ['jpr', 'jpx'], + 'application/x-karbon' => ['karbon'], + 'application/x-kchart' => ['chrt'], + 'application/x-keepass2' => ['kdbx'], + 'application/x-kexi-connectiondata' => ['kexic'], + 'application/x-kexiproject-shortcut' => ['kexis'], + 'application/x-kexiproject-sqlite' => ['kexi'], + 'application/x-kexiproject-sqlite2' => ['kexi'], + 'application/x-kexiproject-sqlite3' => ['kexi'], + 'application/x-kformula' => ['kfo'], + 'application/x-killustrator' => ['kil'], + 'application/x-kivio' => ['flw'], + 'application/x-kontour' => ['kon'], + 'application/x-kpovmodeler' => ['kpm'], + 'application/x-kpresenter' => ['kpr', 'kpt'], + 'application/x-krita' => ['kra', 'krz'], + 'application/x-kspread' => ['ksp'], + 'application/x-kugar' => ['kud'], + 'application/x-kword' => ['kwd', 'kwt'], + 'application/x-latex' => ['latex'], + 'application/x-lha' => ['lha', 'lzh'], + 'application/x-lhz' => ['lhz'], + 'application/x-linguist' => ['ts'], + 'application/x-lmdb' => ['mdb', 'lmdb'], + 'application/x-lotus123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/x-lrzip' => ['lrz'], + 'application/x-lrzip-compressed-tar' => ['tar.lrz', 'tlrz'], + 'application/x-lua-bytecode' => ['luac'], + 'application/x-lyx' => ['lyx'], + 'application/x-lz4' => ['lz4'], + 'application/x-lz4-compressed-tar' => ['tar.lz4'], + 'application/x-lzh-compressed' => ['lzh', 'lha'], + 'application/x-lzip' => ['lz'], + 'application/x-lzip-compressed-tar' => ['tar.lz'], + 'application/x-lzma' => ['lzma'], + 'application/x-lzma-compressed-tar' => ['tar.lzma', 'tlz'], + 'application/x-lzop' => ['lzo'], + 'application/x-lzpdf' => ['pdf.lz'], + 'application/x-m4' => ['m4'], + 'application/x-magicpoint' => ['mgp'], + 'application/x-makeself' => ['run'], + 'application/x-mame-chd' => ['chd'], + 'application/x-markaby' => ['mab'], + 'application/x-mathematica' => ['nb'], + 'application/x-mdb' => ['mdb'], + 'application/x-mie' => ['mie'], + 'application/x-mif' => ['mif'], + 'application/x-mimearchive' => ['mhtml', 'mht'], + 'application/x-mobi8-ebook' => ['azw3', 'kfx'], + 'application/x-mobipocket-ebook' => ['prc', 'mobi'], + 'application/x-modrinth-modpack+zip' => ['mrpack'], + 'application/x-ms-application' => ['application'], + 'application/x-ms-asx' => ['asx', 'wax', 'wvx', 'wmx'], + 'application/x-ms-dos-executable' => ['exe', 'dll', 'cpl', 'drv', 'scr'], + 'application/x-ms-ne-executable' => ['exe', 'dll', 'cpl', 'drv', 'scr'], + 'application/x-ms-pdb' => ['pdb'], + 'application/x-ms-shortcut' => ['lnk'], + 'application/x-ms-wim' => ['wim', 'swm'], + 'application/x-ms-wmd' => ['wmd'], + 'application/x-ms-wmz' => ['wmz'], + 'application/x-ms-xbap' => ['xbap'], + 'application/x-msaccess' => ['mdb'], + 'application/x-msbinder' => ['obd'], + 'application/x-mscardfile' => ['crd'], + 'application/x-msclip' => ['clp'], + 'application/x-msdos-program' => ['exe'], + 'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi', 'cpl', 'drv', 'scr'], + 'application/x-msexcel' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'], + 'application/x-msi' => ['msi'], + 'application/x-msmediaview' => ['mvb', 'm13', 'm14'], + 'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'], + 'application/x-msmoney' => ['mny'], + 'application/x-mspowerpoint' => ['ppz', 'ppt', 'pps', 'pot'], + 'application/x-mspublisher' => ['pub'], + 'application/x-msschedule' => ['scd'], + 'application/x-msterminal' => ['trm'], + 'application/x-mswinurl' => ['url'], + 'application/x-msword' => ['doc'], + 'application/x-mswrite' => ['wri'], + 'application/x-msx-rom' => ['msx'], + 'application/x-n64-rom' => ['n64', 'z64', 'v64'], + 'application/x-navi-animation' => ['ani'], + 'application/x-neo-geo-pocket-color-rom' => ['ngc'], + 'application/x-neo-geo-pocket-rom' => ['ngp'], + 'application/x-nes-rom' => ['nes', 'nez', 'unf', 'unif'], + 'application/x-netcdf' => ['nc', 'cdf'], + 'application/x-netshow-channel' => ['nsc'], + 'application/x-nintendo-3ds-executable' => ['3dsx'], + 'application/x-nintendo-3ds-rom' => ['3ds', 'cci'], + 'application/x-nintendo-ds-rom' => ['nds'], + 'application/x-ns-proxy-autoconfig' => ['pac'], + 'application/x-nuscript' => ['nu'], + 'application/x-nzb' => ['nzb'], + 'application/x-object' => ['o', 'mod'], + 'application/x-ogg' => ['ogx'], + 'application/x-oleo' => ['oleo'], + 'application/x-openvpn-profile' => ['openvpn', 'ovpn'], + 'application/x-openzim' => ['zim'], + 'application/x-pagemaker' => ['p65', 'pm', 'pm6', 'pmd'], + 'application/x-pak' => ['pak'], + 'application/x-palm-database' => ['prc', 'pdb', 'pqa', 'oprc'], + 'application/x-par2' => ['PAR2', 'par2'], + 'application/x-partial-download' => ['wkdownload', 'crdownload', 'part'], + 'application/x-pc-engine-rom' => ['pce'], + 'application/x-pcap' => ['pcap', 'cap', 'dmp'], + 'application/x-pdf' => ['pdf'], + 'application/x-perl' => ['pl', 'pm', 'PL', 'al', 'perl', 'pod', 't'], + 'application/x-photoshop' => ['psd'], + 'application/x-php' => ['php', 'php3', 'php4', 'php5', 'phps'], + 'application/x-pilot' => ['prc', 'pdb'], + 'application/x-pkcs12' => ['p12', 'pfx'], + 'application/x-pkcs7-certificates' => ['p7b', 'spc'], + 'application/x-pkcs7-certreqresp' => ['p7r'], + 'application/x-planperfect' => ['pln'], + 'application/x-pocket-word' => ['psw'], + 'application/x-powershell' => ['ps1'], + 'application/x-pw' => ['pw'], + 'application/x-pyspread-bz-spreadsheet' => ['pys'], + 'application/x-pyspread-spreadsheet' => ['pysu'], + 'application/x-python-bytecode' => ['pyc', 'pyo'], + 'application/x-qed-disk' => ['qed'], + 'application/x-qemu-disk' => ['qcow2', 'qcow'], + 'application/x-qpress' => ['qp'], + 'application/x-qtiplot' => ['qti', 'qti.gz'], + 'application/x-quattropro' => ['wb1', 'wb2', 'wb3', 'qpw'], + 'application/x-quicktime-media-link' => ['qtl'], + 'application/x-quicktimeplayer' => ['qtl'], + 'application/x-qw' => ['qif'], + 'application/x-rar' => ['rar'], + 'application/x-rar-compressed' => ['rar'], + 'application/x-raw-disk-image' => ['raw-disk-image', 'img'], + 'application/x-raw-disk-image-xz-compressed' => ['raw-disk-image.xz', 'img.xz'], + 'application/x-raw-floppy-disk-image' => ['fd', 'qd'], + 'application/x-redhat-package-manager' => ['rpm'], + 'application/x-reject' => ['rej'], + 'application/x-research-info-systems' => ['ris'], + 'application/x-rnc' => ['rnc'], + 'application/x-rpm' => ['rpm'], + 'application/x-ruby' => ['rb'], + 'application/x-rzip' => ['rz'], + 'application/x-rzip-compressed-tar' => ['tar.rz', 'trz'], + 'application/x-sami' => ['smi', 'sami'], + 'application/x-sap-file' => ['sap'], + 'application/x-saturn-rom' => ['iso'], + 'application/x-sdp' => ['sdp'], + 'application/x-sea' => ['sea'], + 'application/x-sega-cd-rom' => ['iso'], + 'application/x-sega-pico-rom' => ['iso'], + 'application/x-sg1000-rom' => ['sg'], + 'application/x-sh' => ['sh'], + 'application/x-shar' => ['shar'], + 'application/x-shared-library-la' => ['la'], + 'application/x-sharedlib' => ['so'], + 'application/x-shellscript' => ['sh'], + 'application/x-shockwave-flash' => ['swf', 'spl'], + 'application/x-shorten' => ['shn'], + 'application/x-siag' => ['siag'], + 'application/x-silverlight-app' => ['xap'], + 'application/x-sit' => ['sit'], + 'application/x-sitx' => ['sitx'], + 'application/x-smaf' => ['mmf', 'smaf'], + 'application/x-sms-rom' => ['sms'], + 'application/x-snes-rom' => ['sfc', 'smc'], + 'application/x-source-rpm' => ['src.rpm', 'spm'], + 'application/x-spss-por' => ['por'], + 'application/x-spss-sav' => ['sav', 'zsav'], + 'application/x-spss-savefile' => ['sav', 'zsav'], + 'application/x-sql' => ['sql'], + 'application/x-sqlite2' => ['sqlite2'], + 'application/x-sqlite3' => ['sqlite3'], + 'application/x-srt' => ['srt'], + 'application/x-stuffit' => ['sit'], + 'application/x-stuffitx' => ['sitx'], + 'application/x-subrip' => ['srt'], + 'application/x-sv4cpio' => ['sv4cpio'], + 'application/x-sv4crc' => ['sv4crc'], + 'application/x-t3vm-image' => ['t3'], + 'application/x-t602' => ['602'], + 'application/x-tads' => ['gam'], + 'application/x-tar' => ['tar', 'gtar', 'gem'], + 'application/x-targa' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'application/x-tarz' => ['tar.Z', 'taz'], + 'application/x-tcl' => ['tcl', 'tk'], + 'application/x-tex' => ['tex', 'ltx', 'sty', 'cls', 'dtx', 'ins', 'latex'], + 'application/x-tex-gf' => ['gf'], + 'application/x-tex-pk' => ['pk'], + 'application/x-tex-tfm' => ['tfm'], + 'application/x-texinfo' => ['texinfo', 'texi'], + 'application/x-tga' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'application/x-tgif' => ['obj'], + 'application/x-theme' => ['theme'], + 'application/x-thomson-cartridge-memo7' => ['m7'], + 'application/x-thomson-cassette' => ['k7'], + 'application/x-thomson-sap-image' => ['sap'], + 'application/x-tiled-tmx' => ['tmx'], + 'application/x-tiled-tsx' => ['tsx'], + 'application/x-trash' => ['bak', 'old', 'sik'], + 'application/x-trig' => ['trig'], + 'application/x-troff' => ['tr', 'roff', 't'], + 'application/x-troff-man' => ['man'], + 'application/x-tzo' => ['tar.lzo', 'tzo'], + 'application/x-ufraw' => ['ufraw'], + 'application/x-ustar' => ['ustar'], + 'application/x-vdi-disk' => ['vdi'], + 'application/x-vhd-disk' => ['vhd', 'vpc'], + 'application/x-vhdx-disk' => ['vhdx'], + 'application/x-virtual-boy-rom' => ['vb'], + 'application/x-virtualbox-hdd' => ['hdd'], + 'application/x-virtualbox-ova' => ['ova'], + 'application/x-virtualbox-ovf' => ['ovf'], + 'application/x-virtualbox-vbox' => ['vbox'], + 'application/x-virtualbox-vbox-extpack' => ['vbox-extpack'], + 'application/x-virtualbox-vdi' => ['vdi'], + 'application/x-virtualbox-vhd' => ['vhd', 'vpc'], + 'application/x-virtualbox-vhdx' => ['vhdx'], + 'application/x-virtualbox-vmdk' => ['vmdk'], + 'application/x-vmdk-disk' => ['vmdk'], + 'application/x-vnd.kde.kexi' => ['kexi'], + 'application/x-wais-source' => ['src'], + 'application/x-wbfs' => ['iso'], + 'application/x-web-app-manifest+json' => ['webapp'], + 'application/x-wia' => ['iso'], + 'application/x-wii-iso-image' => ['iso'], + 'application/x-wii-rom' => ['iso'], + 'application/x-wii-wad' => ['wad'], + 'application/x-win-lnk' => ['lnk'], + 'application/x-windows-themepack' => ['themepack'], + 'application/x-wmf' => ['wmf'], + 'application/x-wonderswan-color-rom' => ['wsc'], + 'application/x-wonderswan-rom' => ['ws'], + 'application/x-wordperfect' => ['wp', 'wp4', 'wp5', 'wp6', 'wpd', 'wpp'], + 'application/x-wpg' => ['wpg'], + 'application/x-wwf' => ['wwf'], + 'application/x-x509-ca-cert' => ['der', 'crt', 'pem', 'cert'], + 'application/x-xar' => ['xar', 'pkg'], + 'application/x-xbel' => ['xbel'], + 'application/x-xfig' => ['fig'], + 'application/x-xliff' => ['xlf', 'xliff'], + 'application/x-xliff+xml' => ['xlf'], + 'application/x-xpinstall' => ['xpi'], + 'application/x-xspf+xml' => ['xspf'], + 'application/x-xz' => ['xz'], + 'application/x-xz-compressed-tar' => ['tar.xz', 'txz'], + 'application/x-xzpdf' => ['pdf.xz'], + 'application/x-yaml' => ['yaml', 'yml'], + 'application/x-zip' => ['zip', 'zipx'], + 'application/x-zip-compressed' => ['zip', 'zipx'], + 'application/x-zip-compressed-fb2' => ['fb2.zip'], + 'application/x-zmachine' => ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'], + 'application/x-zoo' => ['zoo'], + 'application/x-zpaq' => ['zpaq'], + 'application/x-zstd-compressed-tar' => ['tar.zst', 'tzst'], + 'application/xaml+xml' => ['xaml'], + 'application/xcap-att+xml' => ['xav'], + 'application/xcap-caps+xml' => ['xca'], + 'application/xcap-diff+xml' => ['xdf'], + 'application/xcap-el+xml' => ['xel'], + 'application/xcap-error+xml' => ['xer'], + 'application/xcap-ns+xml' => ['xns'], + 'application/xenc+xml' => ['xenc'], + 'application/xhtml+xml' => ['xhtml', 'xht', 'html', 'htm'], + 'application/xliff+xml' => ['xlf', 'xliff'], + 'application/xml' => ['xml', 'xsl', 'xsd', 'rng', 'xbl'], + 'application/xml-dtd' => ['dtd'], + 'application/xml-external-parsed-entity' => ['ent'], + 'application/xop+xml' => ['xop'], + 'application/xproc+xml' => ['xpl'], + 'application/xps' => ['xps'], + 'application/xslt+xml' => ['xsl', 'xslt'], + 'application/xspf+xml' => ['xspf'], + 'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'], + 'application/yaml' => ['yaml', 'yml'], + 'application/yang' => ['yang'], + 'application/yin+xml' => ['yin'], + 'application/zip' => ['zip', 'zipx'], + 'application/zlib' => ['zz'], + 'application/zstd' => ['zst'], + 'audio/3gpp' => ['3gpp', '3gp', '3ga'], + 'audio/3gpp-encrypted' => ['3gp', '3gpp', '3ga'], + 'audio/3gpp2' => ['3g2', '3gp2', '3gpp2'], + 'audio/aac' => ['aac', 'adts', 'ass'], + 'audio/ac3' => ['ac3'], + 'audio/adpcm' => ['adp'], + 'audio/amr' => ['amr'], + 'audio/amr-encrypted' => ['amr'], + 'audio/amr-wb' => ['awb'], + 'audio/amr-wb-encrypted' => ['awb'], + 'audio/annodex' => ['axa'], + 'audio/basic' => ['au', 'snd'], + 'audio/dff' => ['dff'], + 'audio/dsd' => ['dsf'], + 'audio/dsf' => ['dsf'], + 'audio/flac' => ['flac'], + 'audio/imelody' => ['imy', 'ime'], + 'audio/m3u' => ['m3u', 'm3u8', 'vlc'], + 'audio/m4a' => ['m4a', 'f4a'], + 'audio/midi' => ['mid', 'midi', 'kar', 'rmi'], + 'audio/mobile-xmf' => ['mxmf'], + 'audio/mp2' => ['mp2'], + 'audio/mp3' => ['mp3', 'mpga'], + 'audio/mp4' => ['m4a', 'mp4a', 'f4a'], + 'audio/mpeg' => ['mp3', 'mpga', 'mp2', 'mp2a', 'm2a', 'm3a'], + 'audio/mpegurl' => ['m3u', 'm3u8', 'vlc'], + 'audio/ogg' => ['ogg', 'oga', 'spx', 'opus'], + 'audio/prs.sid' => ['sid', 'psid'], + 'audio/s3m' => ['s3m'], + 'audio/scpls' => ['pls'], + 'audio/silk' => ['sil'], + 'audio/tta' => ['tta'], + 'audio/usac' => ['loas', 'xhe'], + 'audio/vnd.audible' => ['aa', 'aax'], + 'audio/vnd.audible.aax' => ['aax'], + 'audio/vnd.audible.aaxc' => ['aaxc'], + 'audio/vnd.dece.audio' => ['uva', 'uvva'], + 'audio/vnd.digital-winds' => ['eol'], + 'audio/vnd.dra' => ['dra'], + 'audio/vnd.dts' => ['dts'], + 'audio/vnd.dts.hd' => ['dtshd'], + 'audio/vnd.lucent.voice' => ['lvp'], + 'audio/vnd.m-realaudio' => ['ra', 'rax'], + 'audio/vnd.ms-playready.media.pya' => ['pya'], + 'audio/vnd.nokia.mobile-xmf' => ['mxmf'], + 'audio/vnd.nuera.ecelp4800' => ['ecelp4800'], + 'audio/vnd.nuera.ecelp7470' => ['ecelp7470'], + 'audio/vnd.nuera.ecelp9600' => ['ecelp9600'], + 'audio/vnd.rip' => ['rip'], + 'audio/vnd.rn-realaudio' => ['ra', 'rax'], + 'audio/vnd.wave' => ['wav'], + 'audio/vorbis' => ['oga', 'ogg'], + 'audio/wav' => ['wav'], + 'audio/wave' => ['wav'], + 'audio/webm' => ['weba'], + 'audio/wma' => ['wma'], + 'audio/x-aac' => ['aac', 'adts', 'ass'], + 'audio/x-aifc' => ['aifc', 'aiffc'], + 'audio/x-aiff' => ['aif', 'aiff', 'aifc'], + 'audio/x-aiffc' => ['aifc', 'aiffc'], + 'audio/x-amzxml' => ['amz'], + 'audio/x-annodex' => ['axa'], + 'audio/x-ape' => ['ape'], + 'audio/x-caf' => ['caf'], + 'audio/x-dff' => ['dff'], + 'audio/x-dsd' => ['dsf'], + 'audio/x-dsf' => ['dsf'], + 'audio/x-dts' => ['dts'], + 'audio/x-dtshd' => ['dtshd'], + 'audio/x-flac' => ['flac'], + 'audio/x-flac+ogg' => ['oga', 'ogg'], + 'audio/x-gsm' => ['gsm'], + 'audio/x-hx-aac-adts' => ['aac', 'adts', 'ass'], + 'audio/x-imelody' => ['imy', 'ime'], + 'audio/x-iriver-pla' => ['pla'], + 'audio/x-it' => ['it'], + 'audio/x-m3u' => ['m3u', 'm3u8', 'vlc'], + 'audio/x-m4a' => ['m4a', 'f4a'], + 'audio/x-m4b' => ['m4b', 'f4b'], + 'audio/x-m4r' => ['m4r'], + 'audio/x-matroska' => ['mka'], + 'audio/x-midi' => ['mid', 'midi', 'kar'], + 'audio/x-minipsf' => ['minipsf'], + 'audio/x-mo3' => ['mo3'], + 'audio/x-mod' => ['mod', 'ult', 'uni', 'm15', 'mtm', '669', 'med'], + 'audio/x-mp2' => ['mp2'], + 'audio/x-mp3' => ['mp3', 'mpga'], + 'audio/x-mp3-playlist' => ['m3u', 'm3u8', 'vlc'], + 'audio/x-mpeg' => ['mp3', 'mpga'], + 'audio/x-mpegurl' => ['m3u', 'm3u8', 'vlc'], + 'audio/x-mpg' => ['mp3', 'mpga'], + 'audio/x-ms-asx' => ['asx', 'wax', 'wvx', 'wmx'], + 'audio/x-ms-wax' => ['wax'], + 'audio/x-ms-wma' => ['wma'], + 'audio/x-ms-wmv' => ['wmv'], + 'audio/x-musepack' => ['mpc', 'mpp', 'mp+'], + 'audio/x-ogg' => ['oga', 'ogg', 'opus'], + 'audio/x-oggflac' => ['oga', 'ogg'], + 'audio/x-opus+ogg' => ['opus'], + 'audio/x-pn-audibleaudio' => ['aa', 'aax'], + 'audio/x-pn-realaudio' => ['ram', 'ra', 'rax'], + 'audio/x-pn-realaudio-plugin' => ['rmp'], + 'audio/x-psf' => ['psf'], + 'audio/x-psflib' => ['psflib'], + 'audio/x-realaudio' => ['ra'], + 'audio/x-rn-3gpp-amr' => ['3gp', '3gpp', '3ga'], + 'audio/x-rn-3gpp-amr-encrypted' => ['3gp', '3gpp', '3ga'], + 'audio/x-rn-3gpp-amr-wb' => ['3gp', '3gpp', '3ga'], + 'audio/x-rn-3gpp-amr-wb-encrypted' => ['3gp', '3gpp', '3ga'], + 'audio/x-s3m' => ['s3m'], + 'audio/x-scpls' => ['pls'], + 'audio/x-shorten' => ['shn'], + 'audio/x-speex' => ['spx'], + 'audio/x-speex+ogg' => ['oga', 'ogg', 'spx'], + 'audio/x-stm' => ['stm'], + 'audio/x-tak' => ['tak'], + 'audio/x-tta' => ['tta'], + 'audio/x-voc' => ['voc'], + 'audio/x-vorbis' => ['oga', 'ogg'], + 'audio/x-vorbis+ogg' => ['oga', 'ogg'], + 'audio/x-wav' => ['wav'], + 'audio/x-wavpack' => ['wv', 'wvp'], + 'audio/x-wavpack-correction' => ['wvc'], + 'audio/x-xi' => ['xi'], + 'audio/x-xm' => ['xm'], + 'audio/x-xmf' => ['xmf'], + 'audio/xm' => ['xm'], + 'audio/xmf' => ['xmf'], + 'chemical/x-cdx' => ['cdx'], + 'chemical/x-cif' => ['cif'], + 'chemical/x-cmdf' => ['cmdf'], + 'chemical/x-cml' => ['cml'], + 'chemical/x-csml' => ['csml'], + 'chemical/x-pdb' => ['pdb', 'brk'], + 'chemical/x-xyz' => ['xyz'], + 'flv-application/octet-stream' => ['flv'], + 'font/collection' => ['ttc'], + 'font/otf' => ['otf'], + 'font/ttf' => ['ttf'], + 'font/woff' => ['woff'], + 'font/woff2' => ['woff2'], + 'image/aces' => ['exr'], + 'image/apng' => ['apng', 'png'], + 'image/astc' => ['astc'], + 'image/avci' => ['avci'], + 'image/avcs' => ['avcs'], + 'image/avif' => ['avif', 'avifs'], + 'image/avif-sequence' => ['avif', 'avifs'], + 'image/bmp' => ['bmp', 'dib'], + 'image/cdr' => ['cdr'], + 'image/cgm' => ['cgm'], + 'image/dicom-rle' => ['drle'], + 'image/emf' => ['emf'], + 'image/fax-g3' => ['g3'], + 'image/fits' => ['fits', 'fit', 'fts'], + 'image/g3fax' => ['g3'], + 'image/gif' => ['gif'], + 'image/heic' => ['heic', 'heif', 'hif'], + 'image/heic-sequence' => ['heics', 'heic', 'heif', 'hif'], + 'image/heif' => ['heif', 'heic', 'hif'], + 'image/heif-sequence' => ['heifs', 'heic', 'heif', 'hif'], + 'image/hej2k' => ['hej2'], + 'image/hsj2' => ['hsj2'], + 'image/ico' => ['ico'], + 'image/icon' => ['ico'], + 'image/ief' => ['ief'], + 'image/jls' => ['jls'], + 'image/jp2' => ['jp2', 'jpg2'], + 'image/jpeg' => ['jpg', 'jpeg', 'jpe', 'jfif'], + 'image/jpeg2000' => ['jp2', 'jpg2'], + 'image/jpeg2000-image' => ['jp2', 'jpg2'], + 'image/jph' => ['jph'], + 'image/jphc' => ['jhc'], + 'image/jpm' => ['jpm', 'jpgm'], + 'image/jpx' => ['jpx', 'jpf'], + 'image/jxl' => ['jxl'], + 'image/jxr' => ['jxr', 'hdp', 'wdp'], + 'image/jxra' => ['jxra'], + 'image/jxrs' => ['jxrs'], + 'image/jxs' => ['jxs'], + 'image/jxsc' => ['jxsc'], + 'image/jxsi' => ['jxsi'], + 'image/jxss' => ['jxss'], + 'image/ktx' => ['ktx'], + 'image/ktx2' => ['ktx2'], + 'image/openraster' => ['ora'], + 'image/pdf' => ['pdf'], + 'image/photoshop' => ['psd'], + 'image/pjpeg' => ['jpg', 'jpeg', 'jpe', 'jfif'], + 'image/png' => ['png'], + 'image/prs.btif' => ['btif'], + 'image/prs.pti' => ['pti'], + 'image/psd' => ['psd'], + 'image/qoi' => ['qoi'], + 'image/rle' => ['rle'], + 'image/sgi' => ['sgi'], + 'image/svg' => ['svg'], + 'image/svg+xml' => ['svg', 'svgz'], + 'image/svg+xml-compressed' => ['svgz', 'svg.gz'], + 'image/t38' => ['t38'], + 'image/targa' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'image/tga' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'image/tiff' => ['tif', 'tiff'], + 'image/tiff-fx' => ['tfx'], + 'image/vnd.adobe.photoshop' => ['psd'], + 'image/vnd.airzip.accelerator.azv' => ['azv'], + 'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'], + 'image/vnd.djvu' => ['djvu', 'djv'], + 'image/vnd.djvu+multipage' => ['djvu', 'djv'], + 'image/vnd.dvb.subtitle' => ['sub'], + 'image/vnd.dwg' => ['dwg'], + 'image/vnd.dxf' => ['dxf'], + 'image/vnd.fastbidsheet' => ['fbs'], + 'image/vnd.fpx' => ['fpx'], + 'image/vnd.fst' => ['fst'], + 'image/vnd.fujixerox.edmics-mmr' => ['mmr'], + 'image/vnd.fujixerox.edmics-rlc' => ['rlc'], + 'image/vnd.microsoft.icon' => ['ico'], + 'image/vnd.mozilla.apng' => ['apng', 'png'], + 'image/vnd.ms-dds' => ['dds'], + 'image/vnd.ms-modi' => ['mdi'], + 'image/vnd.ms-photo' => ['wdp', 'jxr', 'hdp'], + 'image/vnd.net-fpx' => ['npx'], + 'image/vnd.pco.b16' => ['b16'], + 'image/vnd.rn-realpix' => ['rp'], + 'image/vnd.tencent.tap' => ['tap'], + 'image/vnd.valve.source.texture' => ['vtf'], + 'image/vnd.wap.wbmp' => ['wbmp'], + 'image/vnd.xiff' => ['xif'], + 'image/vnd.zbrush.pcx' => ['pcx'], + 'image/webp' => ['webp'], + 'image/wmf' => ['wmf'], + 'image/x-3ds' => ['3ds'], + 'image/x-adobe-dng' => ['dng'], + 'image/x-applix-graphics' => ['ag'], + 'image/x-bmp' => ['bmp', 'dib'], + 'image/x-bzeps' => ['eps.bz2', 'epsi.bz2', 'epsf.bz2'], + 'image/x-canon-cr2' => ['cr2'], + 'image/x-canon-cr3' => ['cr3'], + 'image/x-canon-crw' => ['crw'], + 'image/x-cdr' => ['cdr'], + 'image/x-cmu-raster' => ['ras'], + 'image/x-cmx' => ['cmx'], + 'image/x-compressed-xcf' => ['xcf.gz', 'xcf.bz2'], + 'image/x-dds' => ['dds'], + 'image/x-djvu' => ['djvu', 'djv'], + 'image/x-emf' => ['emf'], + 'image/x-eps' => ['eps', 'epsi', 'epsf'], + 'image/x-exr' => ['exr'], + 'image/x-fits' => ['fits', 'fit', 'fts'], + 'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'], + 'image/x-fuji-raf' => ['raf'], + 'image/x-gimp-gbr' => ['gbr'], + 'image/x-gimp-gih' => ['gih'], + 'image/x-gimp-pat' => ['pat'], + 'image/x-gzeps' => ['eps.gz', 'epsi.gz', 'epsf.gz'], + 'image/x-icb' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'image/x-icns' => ['icns'], + 'image/x-ico' => ['ico'], + 'image/x-icon' => ['ico'], + 'image/x-iff' => ['iff', 'ilbm', 'lbm'], + 'image/x-ilbm' => ['iff', 'ilbm', 'lbm'], + 'image/x-jng' => ['jng'], + 'image/x-jp2-codestream' => ['j2c', 'j2k', 'jpc'], + 'image/x-jpeg2000-image' => ['jp2', 'jpg2'], + 'image/x-kodak-dcr' => ['dcr'], + 'image/x-kodak-k25' => ['k25'], + 'image/x-kodak-kdc' => ['kdc'], + 'image/x-lwo' => ['lwo', 'lwob'], + 'image/x-lws' => ['lws'], + 'image/x-macpaint' => ['pntg'], + 'image/x-minolta-mrw' => ['mrw'], + 'image/x-mrsid-image' => ['sid'], + 'image/x-ms-bmp' => ['bmp', 'dib'], + 'image/x-msod' => ['msod'], + 'image/x-nikon-nef' => ['nef'], + 'image/x-nikon-nrw' => ['nrw'], + 'image/x-olympus-orf' => ['orf'], + 'image/x-panasonic-raw' => ['raw'], + 'image/x-panasonic-raw2' => ['rw2'], + 'image/x-panasonic-rw' => ['raw'], + 'image/x-panasonic-rw2' => ['rw2'], + 'image/x-pcx' => ['pcx'], + 'image/x-pentax-pef' => ['pef'], + 'image/x-photo-cd' => ['pcd'], + 'image/x-photoshop' => ['psd'], + 'image/x-pict' => ['pic', 'pct', 'pict', 'pict1', 'pict2'], + 'image/x-portable-anymap' => ['pnm'], + 'image/x-portable-bitmap' => ['pbm'], + 'image/x-portable-graymap' => ['pgm'], + 'image/x-portable-pixmap' => ['ppm'], + 'image/x-psd' => ['psd'], + 'image/x-quicktime' => ['qtif', 'qif'], + 'image/x-rgb' => ['rgb'], + 'image/x-sgi' => ['sgi'], + 'image/x-sigma-x3f' => ['x3f'], + 'image/x-skencil' => ['sk', 'sk1'], + 'image/x-sony-arw' => ['arw'], + 'image/x-sony-sr2' => ['sr2'], + 'image/x-sony-srf' => ['srf'], + 'image/x-sun-raster' => ['sun'], + 'image/x-targa' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'image/x-tga' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'image/x-win-bitmap' => ['cur'], + 'image/x-win-metafile' => ['wmf'], + 'image/x-wmf' => ['wmf'], + 'image/x-xbitmap' => ['xbm'], + 'image/x-xcf' => ['xcf'], + 'image/x-xfig' => ['fig'], + 'image/x-xpixmap' => ['xpm'], + 'image/x-xpm' => ['xpm'], + 'image/x-xwindowdump' => ['xwd'], + 'image/x.djvu' => ['djvu', 'djv'], + 'message/disposition-notification' => ['disposition-notification'], + 'message/global' => ['u8msg'], + 'message/global-delivery-status' => ['u8dsn'], + 'message/global-disposition-notification' => ['u8mdn'], + 'message/global-headers' => ['u8hdr'], + 'message/rfc822' => ['eml', 'mime'], + 'message/vnd.wfa.wsc' => ['wsc'], + 'model/3mf' => ['3mf'], + 'model/gltf+json' => ['gltf'], + 'model/gltf-binary' => ['glb'], + 'model/iges' => ['igs', 'iges'], + 'model/mesh' => ['msh', 'mesh', 'silo'], + 'model/mtl' => ['mtl'], + 'model/obj' => ['obj'], + 'model/step+xml' => ['stpx'], + 'model/step+zip' => ['stpz'], + 'model/step-xml+zip' => ['stpxz'], + 'model/stl' => ['stl'], + 'model/vnd.collada+xml' => ['dae'], + 'model/vnd.dwf' => ['dwf'], + 'model/vnd.gdl' => ['gdl'], + 'model/vnd.gtw' => ['gtw'], + 'model/vnd.mts' => ['mts'], + 'model/vnd.opengex' => ['ogex'], + 'model/vnd.parasolid.transmit.binary' => ['x_b'], + 'model/vnd.parasolid.transmit.text' => ['x_t'], + 'model/vnd.sap.vds' => ['vds'], + 'model/vnd.usdz+zip' => ['usdz'], + 'model/vnd.valve.source.compiled-map' => ['bsp'], + 'model/vnd.vtu' => ['vtu'], + 'model/vrml' => ['wrl', 'vrml', 'vrm'], + 'model/x.stl-ascii' => ['stl'], + 'model/x.stl-binary' => ['stl'], + 'model/x3d+binary' => ['x3db', 'x3dbz'], + 'model/x3d+fastinfoset' => ['x3db'], + 'model/x3d+vrml' => ['x3dv', 'x3dvz'], + 'model/x3d+xml' => ['x3d', 'x3dz'], + 'model/x3d-vrml' => ['x3dv'], + 'text/cache-manifest' => ['appcache', 'manifest'], + 'text/calendar' => ['ics', 'ifb', 'vcs'], + 'text/coffeescript' => ['coffee', 'litcoffee'], + 'text/crystal' => ['cr'], + 'text/css' => ['css'], + 'text/csv' => ['csv'], + 'text/csv-schema' => ['csvs'], + 'text/directory' => ['vcard', 'vcf', 'vct', 'gcrd'], + 'text/ecmascript' => ['es'], + 'text/gedcom' => ['ged', 'gedcom'], + 'text/google-video-pointer' => ['gvp'], + 'text/html' => ['html', 'htm', 'shtml'], + 'text/ico' => ['ico'], + 'text/jade' => ['jade'], + 'text/javascript' => ['js', 'jsm', 'mjs'], + 'text/jscript' => ['js', 'jsm', 'mjs'], + 'text/jscript.encode' => ['jse'], + 'text/jsx' => ['jsx'], + 'text/julia' => ['jl'], + 'text/less' => ['less'], + 'text/markdown' => ['md', 'markdown', 'mkd'], + 'text/mathml' => ['mml'], + 'text/mdx' => ['mdx'], + 'text/n3' => ['n3'], + 'text/org' => ['org'], + 'text/plain' => ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini', 'asc'], + 'text/prs.lines.tag' => ['dsc'], + 'text/rdf' => ['rdf', 'rdfs', 'owl'], + 'text/richtext' => ['rtx'], + 'text/rss' => ['rss'], + 'text/rtf' => ['rtf'], + 'text/rust' => ['rs'], + 'text/sgml' => ['sgml', 'sgm'], + 'text/shex' => ['shex'], + 'text/slim' => ['slim', 'slm'], + 'text/spdx' => ['spdx'], + 'text/spreadsheet' => ['sylk', 'slk'], + 'text/stylus' => ['stylus', 'styl'], + 'text/tab-separated-values' => ['tsv'], + 'text/tcl' => ['tcl', 'tk'], + 'text/troff' => ['t', 'tr', 'roff', 'man', 'me', 'ms'], + 'text/turtle' => ['ttl'], + 'text/uri-list' => ['uri', 'uris', 'urls'], + 'text/vbs' => ['vbs'], + 'text/vbscript' => ['vbs'], + 'text/vbscript.encode' => ['vbe'], + 'text/vcard' => ['vcard', 'vcf', 'vct', 'gcrd'], + 'text/vnd.curl' => ['curl'], + 'text/vnd.curl.dcurl' => ['dcurl'], + 'text/vnd.curl.mcurl' => ['mcurl'], + 'text/vnd.curl.scurl' => ['scurl'], + 'text/vnd.dvb.subtitle' => ['sub'], + 'text/vnd.familysearch.gedcom' => ['ged', 'gedcom'], + 'text/vnd.fly' => ['fly'], + 'text/vnd.fmi.flexstor' => ['flx'], + 'text/vnd.graphviz' => ['gv', 'dot'], + 'text/vnd.in3d.3dml' => ['3dml'], + 'text/vnd.in3d.spot' => ['spot'], + 'text/vnd.qt.linguist' => ['ts'], + 'text/vnd.rn-realtext' => ['rt'], + 'text/vnd.senx.warpscript' => ['mc2'], + 'text/vnd.sun.j2me.app-descriptor' => ['jad'], + 'text/vnd.trolltech.linguist' => ['ts'], + 'text/vnd.wap.wml' => ['wml'], + 'text/vnd.wap.wmlscript' => ['wmls'], + 'text/vtt' => ['vtt'], + 'text/x-adasrc' => ['adb', 'ads'], + 'text/x-asm' => ['s', 'asm'], + 'text/x-basic' => ['bas'], + 'text/x-bibtex' => ['bib'], + 'text/x-blueprint' => ['blp'], + 'text/x-c' => ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'], + 'text/x-c++hdr' => ['hh', 'hp', 'hpp', 'h++', 'hxx'], + 'text/x-c++src' => ['cpp', 'cxx', 'cc', 'C', 'c++'], + 'text/x-chdr' => ['h'], + 'text/x-cmake' => ['cmake'], + 'text/x-cobol' => ['cbl', 'cob'], + 'text/x-comma-separated-values' => ['csv'], + 'text/x-common-lisp' => ['asd', 'fasl', 'lisp', 'ros'], + 'text/x-component' => ['htc'], + 'text/x-crystal' => ['cr'], + 'text/x-csharp' => ['cs'], + 'text/x-csrc' => ['c'], + 'text/x-csv' => ['csv'], + 'text/x-dart' => ['dart'], + 'text/x-dbus-service' => ['service'], + 'text/x-dcl' => ['dcl'], + 'text/x-devicetree-binary' => ['dtb'], + 'text/x-devicetree-source' => ['dts', 'dtsi'], + 'text/x-diff' => ['diff', 'patch'], + 'text/x-dsl' => ['dsl'], + 'text/x-dsrc' => ['d', 'di'], + 'text/x-dtd' => ['dtd'], + 'text/x-eiffel' => ['e', 'eif'], + 'text/x-elixir' => ['ex', 'exs'], + 'text/x-emacs-lisp' => ['el'], + 'text/x-erlang' => ['erl'], + 'text/x-fish' => ['fish'], + 'text/x-fortran' => ['f', 'for', 'f77', 'f90', 'f95'], + 'text/x-gcode-gx' => ['gx'], + 'text/x-genie' => ['gs'], + 'text/x-gettext-translation' => ['po'], + 'text/x-gettext-translation-template' => ['pot'], + 'text/x-gherkin' => ['feature'], + 'text/x-go' => ['go'], + 'text/x-google-video-pointer' => ['gvp'], + 'text/x-gradle' => ['gradle'], + 'text/x-groovy' => ['groovy', 'gvy', 'gy', 'gsh'], + 'text/x-handlebars-template' => ['hbs'], + 'text/x-haskell' => ['hs'], + 'text/x-idl' => ['idl'], + 'text/x-imelody' => ['imy', 'ime'], + 'text/x-iptables' => ['iptables'], + 'text/x-java' => ['java'], + 'text/x-java-source' => ['java'], + 'text/x-kaitai-struct' => ['ksy'], + 'text/x-kotlin' => ['kt'], + 'text/x-ldif' => ['ldif'], + 'text/x-lilypond' => ['ly'], + 'text/x-literate-haskell' => ['lhs'], + 'text/x-log' => ['log'], + 'text/x-lua' => ['lua'], + 'text/x-lyx' => ['lyx'], + 'text/x-makefile' => ['mk', 'mak'], + 'text/x-markdown' => ['md', 'mkd', 'markdown'], + 'text/x-matlab' => ['m'], + 'text/x-microdvd' => ['sub'], + 'text/x-moc' => ['moc'], + 'text/x-modelica' => ['mo'], + 'text/x-mof' => ['mof'], + 'text/x-mpl2' => ['mpl'], + 'text/x-mpsub' => ['sub'], + 'text/x-mrml' => ['mrml', 'mrl'], + 'text/x-ms-regedit' => ['reg'], + 'text/x-mup' => ['mup', 'not'], + 'text/x-nfo' => ['nfo'], + 'text/x-nim' => ['nim'], + 'text/x-nimscript' => ['nims', 'nimble'], + 'text/x-nu' => ['nu'], + 'text/x-objc++src' => ['mm'], + 'text/x-objcsrc' => ['m'], + 'text/x-ocaml' => ['ml', 'mli'], + 'text/x-ocl' => ['ocl'], + 'text/x-octave' => ['m'], + 'text/x-ooc' => ['ooc'], + 'text/x-opencl-src' => ['cl'], + 'text/x-opml' => ['opml'], + 'text/x-opml+xml' => ['opml'], + 'text/x-org' => ['org'], + 'text/x-pascal' => ['p', 'pas'], + 'text/x-patch' => ['diff', 'patch'], + 'text/x-perl' => ['pl', 'PL', 'pm', 'al', 'perl', 'pod', 't'], + 'text/x-po' => ['po'], + 'text/x-pot' => ['pot'], + 'text/x-processing' => ['pde'], + 'text/x-python' => ['py', 'pyx', 'wsgi'], + 'text/x-python3' => ['py', 'py3', 'py3x', 'pyi'], + 'text/x-qml' => ['qml', 'qmltypes', 'qmlproject'], + 'text/x-reject' => ['rej'], + 'text/x-rpm-spec' => ['spec'], + 'text/x-rst' => ['rst'], + 'text/x-sagemath' => ['sage'], + 'text/x-sass' => ['sass'], + 'text/x-scala' => ['scala', 'sc'], + 'text/x-scheme' => ['scm', 'ss'], + 'text/x-scss' => ['scss'], + 'text/x-setext' => ['etx'], + 'text/x-sfv' => ['sfv'], + 'text/x-sh' => ['sh'], + 'text/x-sql' => ['sql'], + 'text/x-ssa' => ['ssa', 'ass'], + 'text/x-subviewer' => ['sub'], + 'text/x-suse-ymp' => ['ymp'], + 'text/x-svhdr' => ['svh'], + 'text/x-svsrc' => ['sv'], + 'text/x-systemd-unit' => ['automount', 'device', 'mount', 'path', 'scope', 'service', 'slice', 'socket', 'swap', 'target', 'timer'], + 'text/x-tcl' => ['tcl', 'tk'], + 'text/x-tex' => ['tex', 'ltx', 'sty', 'cls', 'dtx', 'ins', 'latex'], + 'text/x-texinfo' => ['texi', 'texinfo'], + 'text/x-troff' => ['tr', 'roff', 't'], + 'text/x-troff-me' => ['me'], + 'text/x-troff-mm' => ['mm'], + 'text/x-troff-ms' => ['ms'], + 'text/x-twig' => ['twig'], + 'text/x-txt2tags' => ['t2t'], + 'text/x-typst' => ['typ'], + 'text/x-uil' => ['uil'], + 'text/x-uuencode' => ['uu', 'uue'], + 'text/x-vala' => ['vala', 'vapi'], + 'text/x-vb' => ['vb'], + 'text/x-vcalendar' => ['vcs', 'ics'], + 'text/x-vcard' => ['vcf', 'vcard', 'vct', 'gcrd'], + 'text/x-verilog' => ['v'], + 'text/x-vhdl' => ['vhd', 'vhdl'], + 'text/x-xmi' => ['xmi'], + 'text/x-xslfo' => ['fo', 'xslfo'], + 'text/x-yaml' => ['yaml', 'yml'], + 'text/x.gcode' => ['gcode'], + 'text/xml' => ['xml', 'xbl', 'xsd', 'rng'], + 'text/xml-external-parsed-entity' => ['ent'], + 'text/yaml' => ['yaml', 'yml'], + 'video/3gp' => ['3gp', '3gpp', '3ga'], + 'video/3gpp' => ['3gp', '3gpp', '3ga'], + 'video/3gpp-encrypted' => ['3gp', '3gpp', '3ga'], + 'video/3gpp2' => ['3g2', '3gp2', '3gpp2'], + 'video/annodex' => ['axv'], + 'video/avi' => ['avi', 'avf', 'divx'], + 'video/divx' => ['avi', 'avf', 'divx'], + 'video/dv' => ['dv'], + 'video/fli' => ['fli', 'flc'], + 'video/flv' => ['flv'], + 'video/h261' => ['h261'], + 'video/h263' => ['h263'], + 'video/h264' => ['h264'], + 'video/iso.segment' => ['m4s'], + 'video/jpeg' => ['jpgv'], + 'video/jpm' => ['jpm', 'jpgm'], + 'video/mj2' => ['mj2', 'mjp2'], + 'video/mp2t' => ['ts', 'm2t', 'm2ts', 'mts', 'cpi', 'clpi', 'mpl', 'mpls', 'bdm', 'bdmv'], + 'video/mp4' => ['mp4', 'mp4v', 'mpg4', 'm4v', 'f4v', 'lrv'], + 'video/mp4v-es' => ['mp4', 'm4v', 'f4v', 'lrv'], + 'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v', 'mp2', 'vob'], + 'video/mpeg-system' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/msvideo' => ['avi', 'avf', 'divx'], + 'video/ogg' => ['ogv', 'ogg'], + 'video/quicktime' => ['mov', 'qt', 'moov', 'qtvr'], + 'video/vivo' => ['viv', 'vivo'], + 'video/vnd.avi' => ['avi', 'avf', 'divx'], + 'video/vnd.dece.hd' => ['uvh', 'uvvh'], + 'video/vnd.dece.mobile' => ['uvm', 'uvvm'], + 'video/vnd.dece.pd' => ['uvp', 'uvvp'], + 'video/vnd.dece.sd' => ['uvs', 'uvvs'], + 'video/vnd.dece.video' => ['uvv', 'uvvv'], + 'video/vnd.divx' => ['avi', 'avf', 'divx'], + 'video/vnd.dvb.file' => ['dvb'], + 'video/vnd.fvt' => ['fvt'], + 'video/vnd.mpegurl' => ['mxu', 'm4u', 'm1u'], + 'video/vnd.ms-playready.media.pyv' => ['pyv'], + 'video/vnd.radgamettools.bink' => ['bik', 'bk2'], + 'video/vnd.radgamettools.smacker' => ['smk'], + 'video/vnd.rn-realvideo' => ['rv', 'rvx'], + 'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'], + 'video/vnd.vivo' => ['viv', 'vivo'], + 'video/vnd.youtube.yt' => ['yt'], + 'video/webm' => ['webm'], + 'video/x-anim' => ['anim1', 'anim2', 'anim3', 'anim4', 'anim5', 'anim6', 'anim7', 'anim8', 'anim9', 'animj'], + 'video/x-annodex' => ['axv'], + 'video/x-avi' => ['avi', 'avf', 'divx'], + 'video/x-f4v' => ['f4v'], + 'video/x-fli' => ['fli', 'flc'], + 'video/x-flic' => ['fli', 'flc'], + 'video/x-flv' => ['flv'], + 'video/x-javafx' => ['fxm'], + 'video/x-m4v' => ['m4v', 'mp4', 'f4v', 'lrv'], + 'video/x-matroska' => ['mkv', 'mk3d', 'mks'], + 'video/x-matroska-3d' => ['mk3d'], + 'video/x-mjpeg' => ['mjpeg', 'mjpg'], + 'video/x-mng' => ['mng'], + 'video/x-mpeg' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/x-mpeg-system' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/x-mpeg2' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/x-mpegurl' => ['m1u', 'm4u', 'mxu'], + 'video/x-ms-asf' => ['asf', 'asx'], + 'video/x-ms-asf-plugin' => ['asf'], + 'video/x-ms-vob' => ['vob'], + 'video/x-ms-wax' => ['asx', 'wax', 'wvx', 'wmx'], + 'video/x-ms-wm' => ['wm', 'asf'], + 'video/x-ms-wmv' => ['wmv'], + 'video/x-ms-wmx' => ['wmx', 'asx', 'wax', 'wvx'], + 'video/x-ms-wvx' => ['wvx', 'asx', 'wax', 'wmx'], + 'video/x-msvideo' => ['avi', 'avf', 'divx'], + 'video/x-nsv' => ['nsv'], + 'video/x-ogg' => ['ogv', 'ogg'], + 'video/x-ogm' => ['ogm'], + 'video/x-ogm+ogg' => ['ogm'], + 'video/x-real-video' => ['rv', 'rvx'], + 'video/x-sgi-movie' => ['movie'], + 'video/x-smv' => ['smv'], + 'video/x-theora' => ['ogg'], + 'video/x-theora+ogg' => ['ogg'], + 'x-conference/x-cooltalk' => ['ice'], + 'x-epoc/x-sisx-app' => ['sisx'], + 'zz-application/zz-winassoc-123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'zz-application/zz-winassoc-cab' => ['cab'], + 'zz-application/zz-winassoc-cdr' => ['cdr'], + 'zz-application/zz-winassoc-doc' => ['doc'], + 'zz-application/zz-winassoc-hlp' => ['hlp'], + 'zz-application/zz-winassoc-mdb' => ['mdb'], + 'zz-application/zz-winassoc-uu' => ['uue'], + 'zz-application/zz-winassoc-xls' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'], + ]; + + private const REVERSE_MAP = [ + '123' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + '1km' => ['application/vnd.1000minds.decision-model+xml'], + '32x' => ['application/x-genesis-32x-rom'], + '3dml' => ['text/vnd.in3d.3dml'], + '3ds' => ['application/x-nintendo-3ds-rom', 'image/x-3ds'], + '3dsx' => ['application/x-nintendo-3ds-executable'], + '3g2' => ['audio/3gpp2', 'video/3gpp2'], + '3ga' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'], + '3gp' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'], + '3gp2' => ['audio/3gpp2', 'video/3gpp2'], + '3gpp' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'], + '3gpp2' => ['audio/3gpp2', 'video/3gpp2'], + '3mf' => ['application/vnd.ms-3mfdocument', 'model/3mf'], + '602' => ['application/x-t602'], + '669' => ['audio/x-mod'], + '7z' => ['application/x-7z-compressed'], + '7z.001' => ['application/x-7z-compressed'], + 'BLEND' => ['application/x-blender'], + 'C' => ['text/x-c++src'], + 'PAR2' => ['application/x-par2'], + 'PL' => ['application/x-perl', 'text/x-perl'], + 'Z' => ['application/x-compress'], + 'a' => ['application/x-archive'], + 'a26' => ['application/x-atari-2600-rom'], + 'a78' => ['application/x-atari-7800-rom'], + 'aa' => ['audio/vnd.audible', 'audio/x-pn-audibleaudio'], + 'aab' => ['application/x-authorware-bin'], + 'aac' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'], + 'aam' => ['application/x-authorware-map'], + 'aas' => ['application/x-authorware-seg'], + 'aax' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'], + 'aaxc' => ['audio/vnd.audible.aaxc'], + 'abw' => ['application/x-abiword'], + 'abw.CRASHED' => ['application/x-abiword'], + 'abw.gz' => ['application/x-abiword'], + 'ac' => ['application/pkix-attr-cert', 'application/vnd.nokia.n-gage.ac+xml'], + 'ac3' => ['audio/ac3'], + 'acc' => ['application/vnd.americandynamics.acc'], + 'ace' => ['application/x-ace', 'application/x-ace-compressed'], + 'acu' => ['application/vnd.acucobol'], + 'acutc' => ['application/vnd.acucorp'], + 'adb' => ['text/x-adasrc'], + 'adf' => ['application/x-amiga-disk-format'], + 'adp' => ['audio/adpcm'], + 'ads' => ['text/x-adasrc'], + 'adts' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'], + 'aep' => ['application/vnd.audiograph'], + 'afm' => ['application/x-font-afm', 'application/x-font-type1'], + 'afp' => ['application/vnd.ibm.modcap'], + 'ag' => ['image/x-applix-graphics'], + 'agb' => ['application/x-gba-rom'], + 'age' => ['application/vnd.age'], + 'ahead' => ['application/vnd.ahead.space'], + 'ai' => ['application/illustrator', 'application/postscript', 'application/vnd.adobe.illustrator'], + 'aif' => ['audio/x-aiff'], + 'aifc' => ['audio/x-aifc', 'audio/x-aiff', 'audio/x-aiffc'], + 'aiff' => ['audio/x-aiff'], + 'aiffc' => ['audio/x-aifc', 'audio/x-aiffc'], + 'air' => ['application/vnd.adobe.air-application-installer-package+zip'], + 'ait' => ['application/vnd.dvb.ait'], + 'al' => ['application/x-perl', 'text/x-perl'], + 'alz' => ['application/x-alz'], + 'ami' => ['application/vnd.amiga.ami'], + 'amr' => ['audio/amr', 'audio/amr-encrypted'], + 'amz' => ['audio/x-amzxml'], + 'ani' => ['application/x-navi-animation'], + 'anim1' => ['video/x-anim'], + 'anim2' => ['video/x-anim'], + 'anim3' => ['video/x-anim'], + 'anim4' => ['video/x-anim'], + 'anim5' => ['video/x-anim'], + 'anim6' => ['video/x-anim'], + 'anim7' => ['video/x-anim'], + 'anim8' => ['video/x-anim'], + 'anim9' => ['video/x-anim'], + 'animj' => ['video/x-anim'], + 'anx' => ['application/annodex', 'application/x-annodex'], + 'ape' => ['audio/x-ape'], + 'apk' => ['application/vnd.android.package-archive'], + 'apng' => ['image/apng', 'image/vnd.mozilla.apng'], + 'appcache' => ['text/cache-manifest'], + 'appimage' => ['application/vnd.appimage', 'application/x-iso9660-appimage'], + 'appinstaller' => ['application/appinstaller'], + 'application' => ['application/x-ms-application'], + 'appx' => ['application/appx'], + 'appxbundle' => ['application/appxbundle'], + 'apr' => ['application/vnd.lotus-approach'], + 'ar' => ['application/x-archive'], + 'arc' => ['application/x-freearc'], + 'arj' => ['application/x-arj'], + 'arw' => ['image/x-sony-arw'], + 'as' => ['application/x-applix-spreadsheet'], + 'asar' => ['application/x-asar'], + 'asc' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature', 'text/plain'], + 'asd' => ['text/x-common-lisp'], + 'asf' => ['application/vnd.ms-asf', 'video/x-ms-asf', 'video/x-ms-asf-plugin', 'video/x-ms-wm'], + 'asice' => ['application/vnd.etsi.asic-e+zip'], + 'asm' => ['text/x-asm'], + 'aso' => ['application/vnd.accpac.simply.aso'], + 'asp' => ['application/x-asp'], + 'ass' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts', 'text/x-ssa'], + 'astc' => ['image/astc'], + 'asx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-asf', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'atc' => ['application/vnd.acucorp'], + 'atom' => ['application/atom+xml'], + 'atomcat' => ['application/atomcat+xml'], + 'atomdeleted' => ['application/atomdeleted+xml'], + 'atomsvc' => ['application/atomsvc+xml'], + 'atx' => ['application/vnd.antix.game-component'], + 'au' => ['audio/basic'], + 'automount' => ['text/x-systemd-unit'], + 'avci' => ['image/avci'], + 'avcs' => ['image/avcs'], + 'avf' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.avi', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'], + 'avi' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.avi', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'], + 'avif' => ['image/avif', 'image/avif-sequence'], + 'avifs' => ['image/avif', 'image/avif-sequence'], + 'aw' => ['application/applixware', 'application/x-applix-word'], + 'awb' => ['audio/amr-wb', 'audio/amr-wb-encrypted'], + 'awk' => ['application/x-awk'], + 'axa' => ['audio/annodex', 'audio/x-annodex'], + 'axv' => ['video/annodex', 'video/x-annodex'], + 'azf' => ['application/vnd.airzip.filesecure.azf'], + 'azs' => ['application/vnd.airzip.filesecure.azs'], + 'azv' => ['image/vnd.airzip.accelerator.azv'], + 'azw' => ['application/vnd.amazon.ebook'], + 'azw3' => ['application/vnd.amazon.mobi8-ebook', 'application/x-mobi8-ebook'], + 'b16' => ['image/vnd.pco.b16'], + 'bak' => ['application/x-trash'], + 'bas' => ['text/x-basic'], + 'bat' => ['application/bat', 'application/x-bat', 'application/x-msdownload'], + 'bcpio' => ['application/x-bcpio'], + 'bdf' => ['application/x-font-bdf'], + 'bdm' => ['application/vnd.syncml.dm+wbxml', 'video/mp2t'], + 'bdmv' => ['video/mp2t'], + 'bdoc' => ['application/bdoc', 'application/x-bdoc'], + 'bed' => ['application/vnd.realvnc.bed'], + 'bh2' => ['application/vnd.fujitsu.oasysprs'], + 'bib' => ['text/x-bibtex'], + 'bik' => ['video/vnd.radgamettools.bink'], + 'bin' => ['application/octet-stream'], + 'bk2' => ['video/vnd.radgamettools.bink'], + 'blb' => ['application/x-blorb'], + 'blend' => ['application/x-blender'], + 'blender' => ['application/x-blender'], + 'blorb' => ['application/x-blorb'], + 'blp' => ['text/x-blueprint'], + 'bmi' => ['application/vnd.bmi'], + 'bmml' => ['application/vnd.balsamiq.bmml+xml'], + 'bmp' => ['image/bmp', 'image/x-bmp', 'image/x-ms-bmp'], + 'book' => ['application/vnd.framemaker'], + 'box' => ['application/vnd.previewsystems.box'], + 'boz' => ['application/x-bzip2'], + 'bps' => ['application/x-bps-patch'], + 'brk' => ['chemical/x-pdb'], + 'bsdiff' => ['application/x-bsdiff'], + 'bsp' => ['model/vnd.valve.source.compiled-map'], + 'btif' => ['image/prs.btif'], + 'bz' => ['application/bzip2', 'application/x-bzip', 'application/x-bzip1'], + 'bz2' => ['application/x-bz2', 'application/bzip2', 'application/x-bzip', 'application/x-bzip2'], + 'bz3' => ['application/x-bzip3'], + 'c' => ['text/x-c', 'text/x-csrc'], + 'c++' => ['text/x-c++src'], + 'c11amc' => ['application/vnd.cluetrust.cartomobile-config'], + 'c11amz' => ['application/vnd.cluetrust.cartomobile-config-pkg'], + 'c4d' => ['application/vnd.clonk.c4group'], + 'c4f' => ['application/vnd.clonk.c4group'], + 'c4g' => ['application/vnd.clonk.c4group'], + 'c4p' => ['application/vnd.clonk.c4group'], + 'c4u' => ['application/vnd.clonk.c4group'], + 'cab' => ['application/vnd.ms-cab-compressed', 'zz-application/zz-winassoc-cab'], + 'caf' => ['audio/x-caf'], + 'cap' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'], + 'car' => ['application/vnd.curl.car'], + 'cat' => ['application/vnd.ms-pki.seccat'], + 'cb7' => ['application/x-cb7', 'application/x-cbr'], + 'cba' => ['application/x-cbr'], + 'cbl' => ['text/x-cobol'], + 'cbor' => ['application/cbor'], + 'cbr' => ['application/vnd.comicbook-rar', 'application/x-cbr'], + 'cbt' => ['application/x-cbr', 'application/x-cbt'], + 'cbz' => ['application/vnd.comicbook+zip', 'application/x-cbr', 'application/x-cbz'], + 'cc' => ['text/x-c', 'text/x-c++src'], + 'cci' => ['application/x-nintendo-3ds-rom'], + 'ccmx' => ['application/x-ccmx'], + 'cco' => ['application/x-cocoa'], + 'cct' => ['application/x-director'], + 'ccxml' => ['application/ccxml+xml'], + 'cdbcmsg' => ['application/vnd.contact.cmsg'], + 'cdf' => ['application/x-netcdf'], + 'cdfx' => ['application/cdfx+xml'], + 'cdi' => ['application/x-discjuggler-cd-image'], + 'cdkey' => ['application/vnd.mediastation.cdkey'], + 'cdmia' => ['application/cdmi-capability'], + 'cdmic' => ['application/cdmi-container'], + 'cdmid' => ['application/cdmi-domain'], + 'cdmio' => ['application/cdmi-object'], + 'cdmiq' => ['application/cdmi-queue'], + 'cdr' => ['application/cdr', 'application/coreldraw', 'application/vnd.corel-draw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'], + 'cdx' => ['chemical/x-cdx'], + 'cdxml' => ['application/vnd.chemdraw+xml'], + 'cdy' => ['application/vnd.cinderella'], + 'cer' => ['application/pkix-cert'], + 'cert' => ['application/x-x509-ca-cert'], + 'cfs' => ['application/x-cfs-compressed'], + 'cgb' => ['application/x-gameboy-color-rom'], + 'cgm' => ['image/cgm'], + 'chat' => ['application/x-chat'], + 'chd' => ['application/x-mame-chd'], + 'chm' => ['application/vnd.ms-htmlhelp', 'application/x-chm'], + 'chrt' => ['application/vnd.kde.kchart', 'application/x-kchart'], + 'cif' => ['chemical/x-cif'], + 'cii' => ['application/vnd.anser-web-certificate-issue-initiation'], + 'cil' => ['application/vnd.ms-artgalry'], + 'cjs' => ['application/node'], + 'cl' => ['text/x-opencl-src'], + 'cla' => ['application/vnd.claymore'], + 'class' => ['application/java', 'application/java-byte-code', 'application/java-vm', 'application/x-java', 'application/x-java-class', 'application/x-java-vm'], + 'clkk' => ['application/vnd.crick.clicker.keyboard'], + 'clkp' => ['application/vnd.crick.clicker.palette'], + 'clkt' => ['application/vnd.crick.clicker.template'], + 'clkw' => ['application/vnd.crick.clicker.wordbank'], + 'clkx' => ['application/vnd.crick.clicker'], + 'clp' => ['application/x-msclip'], + 'clpi' => ['video/mp2t'], + 'cls' => ['application/x-tex', 'text/x-tex'], + 'cmake' => ['text/x-cmake'], + 'cmc' => ['application/vnd.cosmocaller'], + 'cmdf' => ['chemical/x-cmdf'], + 'cml' => ['chemical/x-cml'], + 'cmp' => ['application/vnd.yellowriver-custom-menu'], + 'cmx' => ['image/x-cmx'], + 'cob' => ['text/x-cobol'], + 'cod' => ['application/vnd.rim.cod'], + 'coffee' => ['application/vnd.coffeescript', 'text/coffeescript'], + 'com' => ['application/x-msdownload'], + 'conf' => ['text/plain'], + 'cpi' => ['video/mp2t'], + 'cpio' => ['application/x-cpio'], + 'cpio.gz' => ['application/x-cpio-compressed'], + 'cpl' => ['application/cpl+xml', 'application/vnd.microsoft.portable-executable', 'application/x-ms-dos-executable', 'application/x-ms-ne-executable', 'application/x-msdownload'], + 'cpp' => ['text/x-c', 'text/x-c++src'], + 'cpt' => ['application/mac-compactpro'], + 'cr' => ['text/crystal', 'text/x-crystal'], + 'cr2' => ['image/x-canon-cr2'], + 'cr3' => ['image/x-canon-cr3'], + 'crd' => ['application/x-mscardfile'], + 'crdownload' => ['application/x-partial-download'], + 'crl' => ['application/pkix-crl'], + 'crt' => ['application/x-x509-ca-cert'], + 'crw' => ['image/x-canon-crw'], + 'crx' => ['application/x-chrome-extension'], + 'cryptonote' => ['application/vnd.rig.cryptonote'], + 'cs' => ['text/x-csharp'], + 'csh' => ['application/x-csh'], + 'csl' => ['application/vnd.citationstyles.style+xml'], + 'csml' => ['chemical/x-csml'], + 'cso' => ['application/x-compressed-iso'], + 'csp' => ['application/vnd.commonspace'], + 'css' => ['text/css'], + 'cst' => ['application/x-director'], + 'csv' => ['text/csv', 'application/csv', 'text/x-comma-separated-values', 'text/x-csv'], + 'csvs' => ['text/csv-schema'], + 'cu' => ['application/cu-seeme'], + 'cue' => ['application/x-cue'], + 'cur' => ['image/x-win-bitmap'], + 'curl' => ['text/vnd.curl'], + 'cwk' => ['application/x-appleworks-document'], + 'cww' => ['application/prs.cww'], + 'cxt' => ['application/x-director'], + 'cxx' => ['text/x-c', 'text/x-c++src'], + 'd' => ['text/x-dsrc'], + 'dae' => ['model/vnd.collada+xml'], + 'daf' => ['application/vnd.mobius.daf'], + 'dar' => ['application/x-dar'], + 'dart' => ['application/vnd.dart', 'text/x-dart'], + 'dataless' => ['application/vnd.fdsn.seed'], + 'davmount' => ['application/davmount+xml'], + 'dbf' => ['application/dbase', 'application/dbf', 'application/vnd.dbf', 'application/x-dbase', 'application/x-dbf'], + 'dbk' => ['application/docbook+xml', 'application/vnd.oasis.docbook+xml', 'application/x-docbook+xml'], + 'dc' => ['application/x-dc-rom'], + 'dcl' => ['text/x-dcl'], + 'dcm' => ['application/dicom'], + 'dcr' => ['application/x-director', 'image/x-kodak-dcr'], + 'dcurl' => ['text/vnd.curl.dcurl'], + 'dd2' => ['application/vnd.oma.dd2+xml'], + 'ddd' => ['application/vnd.fujixerox.ddd'], + 'ddf' => ['application/vnd.syncml.dmddf+xml'], + 'dds' => ['image/vnd.ms-dds', 'image/x-dds'], + 'deb' => ['application/vnd.debian.binary-package', 'application/x-deb', 'application/x-debian-package'], + 'def' => ['text/plain'], + 'der' => ['application/x-x509-ca-cert'], + 'desktop' => ['application/x-desktop', 'application/x-gnome-app-info'], + 'device' => ['text/x-systemd-unit'], + 'dfac' => ['application/vnd.dreamfactory'], + 'dff' => ['audio/dff', 'audio/x-dff'], + 'dgc' => ['application/x-dgc-compressed'], + 'di' => ['text/x-dsrc'], + 'dia' => ['application/x-dia-diagram'], + 'dib' => ['image/bmp', 'image/x-bmp', 'image/x-ms-bmp'], + 'dic' => ['text/x-c'], + 'diff' => ['text/x-diff', 'text/x-patch'], + 'dir' => ['application/x-director'], + 'dis' => ['application/vnd.mobius.dis'], + 'disposition-notification' => ['message/disposition-notification'], + 'divx' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.avi', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'], + 'djv' => ['image/vnd.djvu', 'image/vnd.djvu+multipage', 'image/x-djvu', 'image/x.djvu'], + 'djvu' => ['image/vnd.djvu', 'image/vnd.djvu+multipage', 'image/x-djvu', 'image/x.djvu'], + 'dll' => ['application/vnd.microsoft.portable-executable', 'application/x-ms-dos-executable', 'application/x-ms-ne-executable', 'application/x-msdownload'], + 'dmg' => ['application/x-apple-diskimage'], + 'dmp' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'], + 'dna' => ['application/vnd.dna'], + 'dng' => ['image/x-adobe-dng'], + 'doc' => ['application/msword', 'application/vnd.ms-word', 'application/x-msword', 'zz-application/zz-winassoc-doc'], + 'docbook' => ['application/docbook+xml', 'application/vnd.oasis.docbook+xml', 'application/x-docbook+xml'], + 'docm' => ['application/vnd.ms-word.document.macroenabled.12'], + 'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'], + 'dot' => ['application/msword', 'application/msword-template', 'text/vnd.graphviz'], + 'dotm' => ['application/vnd.ms-word.template.macroenabled.12'], + 'dotx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.template'], + 'dp' => ['application/vnd.osgi.dp'], + 'dpg' => ['application/vnd.dpgraph'], + 'dra' => ['audio/vnd.dra'], + 'drl' => ['application/x-excellon'], + 'drle' => ['image/dicom-rle'], + 'drv' => ['application/vnd.microsoft.portable-executable', 'application/x-ms-dos-executable', 'application/x-ms-ne-executable', 'application/x-msdownload'], + 'dsc' => ['text/prs.lines.tag'], + 'dsf' => ['audio/dsd', 'audio/dsf', 'audio/x-dsd', 'audio/x-dsf'], + 'dsl' => ['text/x-dsl'], + 'dssc' => ['application/dssc+der'], + 'dtb' => ['application/x-dtbook+xml', 'text/x-devicetree-binary'], + 'dtd' => ['application/xml-dtd', 'text/x-dtd'], + 'dts' => ['audio/vnd.dts', 'audio/x-dts', 'text/x-devicetree-source'], + 'dtshd' => ['audio/vnd.dts.hd', 'audio/x-dtshd'], + 'dtsi' => ['text/x-devicetree-source'], + 'dtx' => ['application/x-tex', 'text/x-tex'], + 'dv' => ['video/dv'], + 'dvb' => ['video/vnd.dvb.file'], + 'dvi' => ['application/x-dvi'], + 'dvi.bz2' => ['application/x-bzdvi'], + 'dvi.gz' => ['application/x-gzdvi'], + 'dwd' => ['application/atsc-dwd+xml'], + 'dwf' => ['model/vnd.dwf'], + 'dwg' => ['image/vnd.dwg'], + 'dxf' => ['image/vnd.dxf'], + 'dxp' => ['application/vnd.spotfire.dxp'], + 'dxr' => ['application/x-director'], + 'e' => ['text/x-eiffel'], + 'ear' => ['application/java-archive'], + 'ecelp4800' => ['audio/vnd.nuera.ecelp4800'], + 'ecelp7470' => ['audio/vnd.nuera.ecelp7470'], + 'ecelp9600' => ['audio/vnd.nuera.ecelp9600'], + 'ecma' => ['application/ecmascript'], + 'edm' => ['application/vnd.novadigm.edm'], + 'edx' => ['application/vnd.novadigm.edx'], + 'efi' => ['application/vnd.microsoft.portable-executable'], + 'efif' => ['application/vnd.picsel'], + 'egon' => ['application/x-egon'], + 'ei6' => ['application/vnd.pg.osasli'], + 'eif' => ['text/x-eiffel'], + 'el' => ['text/x-emacs-lisp'], + 'emf' => ['application/emf', 'application/x-emf', 'application/x-msmetafile', 'image/emf', 'image/x-emf'], + 'eml' => ['message/rfc822'], + 'emma' => ['application/emma+xml'], + 'emotionml' => ['application/emotionml+xml'], + 'emp' => ['application/vnd.emusic-emusic_package'], + 'emz' => ['application/x-msmetafile'], + 'ent' => ['application/xml-external-parsed-entity', 'text/xml-external-parsed-entity'], + 'eol' => ['audio/vnd.digital-winds'], + 'eot' => ['application/vnd.ms-fontobject'], + 'eps' => ['application/postscript', 'image/x-eps'], + 'eps.bz2' => ['image/x-bzeps'], + 'eps.gz' => ['image/x-gzeps'], + 'epsf' => ['image/x-eps'], + 'epsf.bz2' => ['image/x-bzeps'], + 'epsf.gz' => ['image/x-gzeps'], + 'epsi' => ['image/x-eps'], + 'epsi.bz2' => ['image/x-bzeps'], + 'epsi.gz' => ['image/x-gzeps'], + 'epub' => ['application/epub+zip'], + 'eris' => ['application/x-eris-link+cbor'], + 'erl' => ['text/x-erlang'], + 'es' => ['application/ecmascript', 'text/ecmascript'], + 'es3' => ['application/vnd.eszigno3+xml'], + 'esa' => ['application/vnd.osgi.subsystem'], + 'escn' => ['application/x-godot-scene'], + 'esf' => ['application/vnd.epson.esf'], + 'et3' => ['application/vnd.eszigno3+xml'], + 'etheme' => ['application/x-e-theme'], + 'etx' => ['text/x-setext'], + 'eva' => ['application/x-eva'], + 'evy' => ['application/x-envoy'], + 'ex' => ['text/x-elixir'], + 'exe' => ['application/vnd.microsoft.portable-executable', 'application/x-dosexec', 'application/x-ms-dos-executable', 'application/x-ms-ne-executable', 'application/x-msdos-program', 'application/x-msdownload'], + 'exi' => ['application/exi'], + 'exp' => ['application/express'], + 'exr' => ['image/aces', 'image/x-exr'], + 'exs' => ['text/x-elixir'], + 'ext' => ['application/vnd.novadigm.ext'], + 'ez' => ['application/andrew-inset'], + 'ez2' => ['application/vnd.ezpix-album'], + 'ez3' => ['application/vnd.ezpix-package'], + 'f' => ['text/x-fortran'], + 'f4a' => ['audio/m4a', 'audio/mp4', 'audio/x-m4a'], + 'f4b' => ['audio/x-m4b'], + 'f4v' => ['video/mp4', 'video/mp4v-es', 'video/x-f4v', 'video/x-m4v'], + 'f77' => ['text/x-fortran'], + 'f90' => ['text/x-fortran'], + 'f95' => ['text/x-fortran'], + 'fasl' => ['text/x-common-lisp'], + 'fb2' => ['application/x-fictionbook', 'application/x-fictionbook+xml'], + 'fb2.zip' => ['application/x-zip-compressed-fb2'], + 'fbs' => ['image/vnd.fastbidsheet'], + 'fcdt' => ['application/vnd.adobe.formscentral.fcdt'], + 'fcs' => ['application/vnd.isac.fcs'], + 'fd' => ['application/x-fd-file', 'application/x-raw-floppy-disk-image'], + 'fdf' => ['application/vnd.fdf'], + 'fds' => ['application/x-fds-disk'], + 'fdt' => ['application/fdt+xml'], + 'fe_launch' => ['application/vnd.denovo.fcselayout-link'], + 'feature' => ['text/x-gherkin'], + 'fg5' => ['application/vnd.fujitsu.oasysgp'], + 'fgd' => ['application/x-director'], + 'fh' => ['image/x-freehand'], + 'fh4' => ['image/x-freehand'], + 'fh5' => ['image/x-freehand'], + 'fh7' => ['image/x-freehand'], + 'fhc' => ['image/x-freehand'], + 'fig' => ['application/x-xfig', 'image/x-xfig'], + 'fish' => ['application/x-fishscript', 'text/x-fish'], + 'fit' => ['application/fits', 'image/fits', 'image/x-fits'], + 'fits' => ['application/fits', 'image/fits', 'image/x-fits'], + 'fl' => ['application/x-fluid'], + 'flac' => ['audio/flac', 'audio/x-flac'], + 'flatpak' => ['application/vnd.flatpak', 'application/vnd.xdgapp'], + 'flatpakref' => ['application/vnd.flatpak.ref'], + 'flatpakrepo' => ['application/vnd.flatpak.repo'], + 'flc' => ['video/fli', 'video/x-fli', 'video/x-flic'], + 'fli' => ['video/fli', 'video/x-fli', 'video/x-flic'], + 'flo' => ['application/vnd.micrografx.flo'], + 'flv' => ['video/x-flv', 'application/x-flash-video', 'flv-application/octet-stream', 'video/flv'], + 'flw' => ['application/vnd.kde.kivio', 'application/x-kivio'], + 'flx' => ['text/vnd.fmi.flexstor'], + 'fly' => ['text/vnd.fly'], + 'fm' => ['application/vnd.framemaker', 'application/x-frame'], + 'fnc' => ['application/vnd.frogans.fnc'], + 'fo' => ['application/vnd.software602.filler.form+xml', 'text/x-xslfo'], + 'fodg' => ['application/vnd.oasis.opendocument.graphics-flat-xml'], + 'fodp' => ['application/vnd.oasis.opendocument.presentation-flat-xml'], + 'fods' => ['application/vnd.oasis.opendocument.spreadsheet-flat-xml'], + 'fodt' => ['application/vnd.oasis.opendocument.text-flat-xml'], + 'for' => ['text/x-fortran'], + 'fpx' => ['image/vnd.fpx'], + 'frame' => ['application/vnd.framemaker'], + 'fsc' => ['application/vnd.fsc.weblaunch'], + 'fst' => ['image/vnd.fst'], + 'ftc' => ['application/vnd.fluxtime.clip'], + 'fti' => ['application/vnd.anser-web-funds-transfer-initiation'], + 'fts' => ['application/fits', 'image/fits', 'image/x-fits'], + 'fvt' => ['video/vnd.fvt'], + 'fxm' => ['video/x-javafx'], + 'fxp' => ['application/vnd.adobe.fxp'], + 'fxpl' => ['application/vnd.adobe.fxp'], + 'fzs' => ['application/vnd.fuzzysheet'], + 'g2w' => ['application/vnd.geoplan'], + 'g3' => ['image/fax-g3', 'image/g3fax'], + 'g3w' => ['application/vnd.geospace'], + 'gac' => ['application/vnd.groove-account'], + 'gam' => ['application/x-tads'], + 'gb' => ['application/x-gameboy-rom'], + 'gba' => ['application/x-gba-rom'], + 'gbc' => ['application/x-gameboy-color-rom'], + 'gbr' => ['application/rpki-ghostbusters', 'application/vnd.gerber', 'application/x-gerber', 'image/x-gimp-gbr'], + 'gbrjob' => ['application/x-gerber-job'], + 'gca' => ['application/x-gca-compressed'], + 'gcode' => ['text/x.gcode'], + 'gcrd' => ['text/directory', 'text/vcard', 'text/x-vcard'], + 'gd' => ['application/x-gdscript'], + 'gdi' => ['application/x-gd-rom-cue'], + 'gdl' => ['model/vnd.gdl'], + 'gdoc' => ['application/vnd.google-apps.document'], + 'gdshader' => ['application/x-godot-shader'], + 'ged' => ['application/x-gedcom', 'text/gedcom', 'text/vnd.familysearch.gedcom'], + 'gedcom' => ['application/x-gedcom', 'text/gedcom', 'text/vnd.familysearch.gedcom'], + 'gem' => ['application/x-gtar', 'application/x-tar'], + 'gen' => ['application/x-genesis-rom'], + 'geo' => ['application/vnd.dynageo'], + 'geo.json' => ['application/geo+json', 'application/vnd.geo+json'], + 'geojson' => ['application/geo+json', 'application/vnd.geo+json'], + 'gex' => ['application/vnd.geometry-explorer'], + 'gf' => ['application/x-tex-gf'], + 'gg' => ['application/x-gamegear-rom'], + 'ggb' => ['application/vnd.geogebra.file'], + 'ggt' => ['application/vnd.geogebra.tool'], + 'ghf' => ['application/vnd.groove-help'], + 'gif' => ['image/gif'], + 'gih' => ['image/x-gimp-gih'], + 'gim' => ['application/vnd.groove-identity-message'], + 'glade' => ['application/x-glade'], + 'glb' => ['model/gltf-binary'], + 'gltf' => ['model/gltf+json'], + 'gml' => ['application/gml+xml'], + 'gmo' => ['application/x-gettext-translation'], + 'gmx' => ['application/vnd.gmx'], + 'gnc' => ['application/x-gnucash'], + 'gnd' => ['application/gnunet-directory'], + 'gnucash' => ['application/x-gnucash'], + 'gnumeric' => ['application/x-gnumeric'], + 'gnuplot' => ['application/x-gnuplot'], + 'go' => ['text/x-go'], + 'gp' => ['application/x-gnuplot'], + 'gpg' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature'], + 'gph' => ['application/vnd.flographit'], + 'gplt' => ['application/x-gnuplot'], + 'gpx' => ['application/gpx', 'application/gpx+xml', 'application/x-gpx', 'application/x-gpx+xml'], + 'gqf' => ['application/vnd.grafeq'], + 'gqs' => ['application/vnd.grafeq'], + 'gra' => ['application/x-graphite'], + 'gradle' => ['text/x-gradle'], + 'gram' => ['application/srgs'], + 'gramps' => ['application/x-gramps-xml'], + 'gre' => ['application/vnd.geometry-explorer'], + 'groovy' => ['text/x-groovy'], + 'grv' => ['application/vnd.groove-injector'], + 'grxml' => ['application/srgs+xml'], + 'gs' => ['text/x-genie'], + 'gsf' => ['application/x-font-ghostscript', 'application/x-font-type1'], + 'gsh' => ['text/x-groovy'], + 'gsheet' => ['application/vnd.google-apps.spreadsheet'], + 'gslides' => ['application/vnd.google-apps.presentation'], + 'gsm' => ['audio/x-gsm'], + 'gtar' => ['application/x-gtar', 'application/x-tar'], + 'gtm' => ['application/vnd.groove-tool-message'], + 'gtw' => ['model/vnd.gtw'], + 'gv' => ['text/vnd.graphviz'], + 'gvp' => ['text/google-video-pointer', 'text/x-google-video-pointer'], + 'gvy' => ['text/x-groovy'], + 'gx' => ['text/x-gcode-gx'], + 'gxf' => ['application/gxf'], + 'gxt' => ['application/vnd.geonext'], + 'gy' => ['text/x-groovy'], + 'gz' => ['application/x-gzip', 'application/gzip'], + 'h' => ['text/x-c', 'text/x-chdr'], + 'h++' => ['text/x-c++hdr'], + 'h261' => ['video/h261'], + 'h263' => ['video/h263'], + 'h264' => ['video/h264'], + 'h4' => ['application/x-hdf'], + 'h5' => ['application/x-hdf'], + 'hal' => ['application/vnd.hal+xml'], + 'hbci' => ['application/vnd.hbci'], + 'hbs' => ['text/x-handlebars-template'], + 'hdd' => ['application/x-virtualbox-hdd'], + 'hdf' => ['application/x-hdf'], + 'hdf4' => ['application/x-hdf'], + 'hdf5' => ['application/x-hdf'], + 'hdp' => ['image/jxr', 'image/vnd.ms-photo'], + 'heic' => ['image/heic', 'image/heic-sequence', 'image/heif', 'image/heif-sequence'], + 'heics' => ['image/heic-sequence'], + 'heif' => ['image/heic', 'image/heic-sequence', 'image/heif', 'image/heif-sequence'], + 'heifs' => ['image/heif-sequence'], + 'hej2' => ['image/hej2k'], + 'held' => ['application/atsc-held+xml'], + 'hfe' => ['application/x-hfe-file', 'application/x-hfe-floppy-image'], + 'hh' => ['text/x-c', 'text/x-c++hdr'], + 'hif' => ['image/heic', 'image/heic-sequence', 'image/heif', 'image/heif-sequence'], + 'hjson' => ['application/hjson'], + 'hlp' => ['application/winhlp', 'zz-application/zz-winassoc-hlp'], + 'hp' => ['text/x-c++hdr'], + 'hpgl' => ['application/vnd.hp-hpgl'], + 'hpid' => ['application/vnd.hp-hpid'], + 'hpp' => ['text/x-c++hdr'], + 'hps' => ['application/vnd.hp-hps'], + 'hqx' => ['application/stuffit', 'application/mac-binhex40'], + 'hs' => ['text/x-haskell'], + 'hsj2' => ['image/hsj2'], + 'hta' => ['application/hta'], + 'htc' => ['text/x-component'], + 'htke' => ['application/vnd.kenameaapp'], + 'htm' => ['text/html', 'application/xhtml+xml'], + 'html' => ['text/html', 'application/xhtml+xml'], + 'hvd' => ['application/vnd.yamaha.hv-dic'], + 'hvp' => ['application/vnd.yamaha.hv-voice'], + 'hvs' => ['application/vnd.yamaha.hv-script'], + 'hwp' => ['application/vnd.haansoft-hwp', 'application/x-hwp'], + 'hwt' => ['application/vnd.haansoft-hwt', 'application/x-hwt'], + 'hxx' => ['text/x-c++hdr'], + 'i2g' => ['application/vnd.intergeo'], + 'ica' => ['application/x-ica'], + 'icb' => ['application/tga', 'application/x-targa', 'application/x-tga', 'image/targa', 'image/tga', 'image/x-icb', 'image/x-targa', 'image/x-tga'], + 'icc' => ['application/vnd.iccprofile'], + 'ice' => ['x-conference/x-cooltalk'], + 'icm' => ['application/vnd.iccprofile'], + 'icns' => ['image/x-icns'], + 'ico' => ['application/ico', 'image/ico', 'image/icon', 'image/vnd.microsoft.icon', 'image/x-ico', 'image/x-icon', 'text/ico'], + 'ics' => ['application/ics', 'text/calendar', 'text/x-vcalendar'], + 'idl' => ['text/x-idl'], + 'ief' => ['image/ief'], + 'ifb' => ['text/calendar'], + 'iff' => ['image/x-iff', 'image/x-ilbm'], + 'ifm' => ['application/vnd.shana.informed.formdata'], + 'iges' => ['model/iges'], + 'igl' => ['application/vnd.igloader'], + 'igm' => ['application/vnd.insors.igm'], + 'igs' => ['model/iges'], + 'igx' => ['application/vnd.micrografx.igx'], + 'iif' => ['application/vnd.shana.informed.interchange'], + 'ilbm' => ['image/x-iff', 'image/x-ilbm'], + 'ime' => ['audio/imelody', 'audio/x-imelody', 'text/x-imelody'], + 'img' => ['application/vnd.efi.img', 'application/x-raw-disk-image'], + 'img.xz' => ['application/x-raw-disk-image-xz-compressed'], + 'imp' => ['application/vnd.accpac.simply.imp'], + 'ims' => ['application/vnd.ms-ims'], + 'imy' => ['audio/imelody', 'audio/x-imelody', 'text/x-imelody'], + 'in' => ['text/plain'], + 'ini' => ['text/plain'], + 'ink' => ['application/inkml+xml'], + 'inkml' => ['application/inkml+xml'], + 'ins' => ['application/x-tex', 'text/x-tex'], + 'install' => ['application/x-install-instructions'], + 'iota' => ['application/vnd.astraea-software.iota'], + 'ipfix' => ['application/ipfix'], + 'ipk' => ['application/vnd.shana.informed.package'], + 'ips' => ['application/x-ips-patch'], + 'iptables' => ['text/x-iptables'], + 'ipynb' => ['application/x-ipynb+json'], + 'irm' => ['application/vnd.ibm.rights-management'], + 'irp' => ['application/vnd.irepository.package+xml'], + 'iso' => ['application/vnd.efi.iso', 'application/x-cd-image', 'application/x-dreamcast-rom', 'application/x-gamecube-iso-image', 'application/x-gamecube-rom', 'application/x-iso9660-image', 'application/x-saturn-rom', 'application/x-sega-cd-rom', 'application/x-sega-pico-rom', 'application/x-wbfs', 'application/x-wia', 'application/x-wii-iso-image', 'application/x-wii-rom'], + 'iso9660' => ['application/vnd.efi.iso', 'application/x-cd-image', 'application/x-iso9660-image'], + 'it' => ['audio/x-it'], + 'it87' => ['application/x-it87'], + 'itp' => ['application/vnd.shana.informed.formtemplate'], + 'its' => ['application/its+xml'], + 'ivp' => ['application/vnd.immervision-ivp'], + 'ivu' => ['application/vnd.immervision-ivu'], + 'j2c' => ['image/x-jp2-codestream'], + 'j2k' => ['image/x-jp2-codestream'], + 'jad' => ['text/vnd.sun.j2me.app-descriptor'], + 'jade' => ['text/jade'], + 'jam' => ['application/vnd.jam'], + 'jar' => ['application/x-java-archive', 'application/java-archive', 'application/x-jar'], + 'jardiff' => ['application/x-java-archive-diff'], + 'java' => ['text/x-java', 'text/x-java-source'], + 'jceks' => ['application/x-java-jce-keystore'], + 'jfif' => ['image/jpeg', 'image/pjpeg'], + 'jhc' => ['image/jphc'], + 'jisp' => ['application/vnd.jisp'], + 'jks' => ['application/x-java-keystore'], + 'jl' => ['text/julia'], + 'jls' => ['image/jls'], + 'jlt' => ['application/vnd.hp-jlyt'], + 'jng' => ['image/x-jng'], + 'jnlp' => ['application/x-java-jnlp-file'], + 'joda' => ['application/vnd.joost.joda-archive'], + 'jp2' => ['image/jp2', 'image/jpeg2000', 'image/jpeg2000-image', 'image/x-jpeg2000-image'], + 'jpc' => ['image/x-jp2-codestream'], + 'jpe' => ['image/jpeg', 'image/pjpeg'], + 'jpeg' => ['image/jpeg', 'image/pjpeg'], + 'jpf' => ['image/jpx'], + 'jpg' => ['image/jpeg', 'image/pjpeg'], + 'jpg2' => ['image/jp2', 'image/jpeg2000', 'image/jpeg2000-image', 'image/x-jpeg2000-image'], + 'jpgm' => ['image/jpm', 'video/jpm'], + 'jpgv' => ['video/jpeg'], + 'jph' => ['image/jph'], + 'jpm' => ['image/jpm', 'video/jpm'], + 'jpr' => ['application/x-jbuilder-project'], + 'jpx' => ['application/x-jbuilder-project', 'image/jpx'], + 'jrd' => ['application/jrd+json'], + 'js' => ['text/javascript', 'application/javascript', 'application/x-javascript', 'text/jscript'], + 'jse' => ['text/jscript.encode'], + 'jsm' => ['application/javascript', 'application/x-javascript', 'text/javascript', 'text/jscript'], + 'json' => ['application/json', 'application/schema+json'], + 'json-patch' => ['application/json-patch+json'], + 'json5' => ['application/json5'], + 'jsonld' => ['application/ld+json'], + 'jsonml' => ['application/jsonml+json'], + 'jsx' => ['text/jsx'], + 'jxl' => ['image/jxl'], + 'jxr' => ['image/jxr', 'image/vnd.ms-photo'], + 'jxra' => ['image/jxra'], + 'jxrs' => ['image/jxrs'], + 'jxs' => ['image/jxs'], + 'jxsc' => ['image/jxsc'], + 'jxsi' => ['image/jxsi'], + 'jxss' => ['image/jxss'], + 'k25' => ['image/x-kodak-k25'], + 'k7' => ['application/x-thomson-cassette'], + 'kar' => ['audio/midi', 'audio/x-midi'], + 'karbon' => ['application/vnd.kde.karbon', 'application/x-karbon'], + 'kdbx' => ['application/x-keepass2'], + 'kdc' => ['image/x-kodak-kdc'], + 'kdelnk' => ['application/x-desktop', 'application/x-gnome-app-info'], + 'kexi' => ['application/x-kexiproject-sqlite', 'application/x-kexiproject-sqlite2', 'application/x-kexiproject-sqlite3', 'application/x-vnd.kde.kexi'], + 'kexic' => ['application/x-kexi-connectiondata'], + 'kexis' => ['application/x-kexiproject-shortcut'], + 'key' => ['application/vnd.apple.keynote', 'application/pgp-keys', 'application/x-iwork-keynote-sffkey'], + 'keynote' => ['application/vnd.apple.keynote'], + 'kfo' => ['application/vnd.kde.kformula', 'application/x-kformula'], + 'kfx' => ['application/vnd.amazon.mobi8-ebook', 'application/x-mobi8-ebook'], + 'kia' => ['application/vnd.kidspiration'], + 'kil' => ['application/x-killustrator'], + 'kino' => ['application/smil', 'application/smil+xml'], + 'kml' => ['application/vnd.google-earth.kml+xml'], + 'kmz' => ['application/vnd.google-earth.kmz'], + 'kne' => ['application/vnd.kinar'], + 'knp' => ['application/vnd.kinar'], + 'kon' => ['application/vnd.kde.kontour', 'application/x-kontour'], + 'kpm' => ['application/x-kpovmodeler'], + 'kpr' => ['application/vnd.kde.kpresenter', 'application/x-kpresenter'], + 'kpt' => ['application/vnd.kde.kpresenter', 'application/x-kpresenter'], + 'kpxx' => ['application/vnd.ds-keypoint'], + 'kra' => ['application/x-krita'], + 'krz' => ['application/x-krita'], + 'ks' => ['application/x-java-keystore'], + 'ksp' => ['application/vnd.kde.kspread', 'application/x-kspread'], + 'ksy' => ['text/x-kaitai-struct'], + 'kt' => ['text/x-kotlin'], + 'ktr' => ['application/vnd.kahootz'], + 'ktx' => ['image/ktx'], + 'ktx2' => ['image/ktx2'], + 'ktz' => ['application/vnd.kahootz'], + 'kud' => ['application/x-kugar'], + 'kwd' => ['application/vnd.kde.kword', 'application/x-kword'], + 'kwt' => ['application/vnd.kde.kword', 'application/x-kword'], + 'la' => ['application/x-shared-library-la'], + 'lasxml' => ['application/vnd.las.las+xml'], + 'latex' => ['application/x-latex', 'application/x-tex', 'text/x-tex'], + 'lbd' => ['application/vnd.llamagraphics.life-balance.desktop'], + 'lbe' => ['application/vnd.llamagraphics.life-balance.exchange+xml'], + 'lbm' => ['image/x-iff', 'image/x-ilbm'], + 'ldif' => ['text/x-ldif'], + 'les' => ['application/vnd.hhe.lesson-player'], + 'less' => ['text/less'], + 'lgr' => ['application/lgr+xml'], + 'lha' => ['application/x-lha', 'application/x-lzh-compressed'], + 'lhs' => ['text/x-literate-haskell'], + 'lhz' => ['application/x-lhz'], + 'lib' => ['application/vnd.microsoft.portable-executable'], + 'link66' => ['application/vnd.route66.link66+xml'], + 'lisp' => ['text/x-common-lisp'], + 'list' => ['text/plain'], + 'list3820' => ['application/vnd.ibm.modcap'], + 'listafp' => ['application/vnd.ibm.modcap'], + 'litcoffee' => ['text/coffeescript'], + 'lmdb' => ['application/x-lmdb'], + 'lnk' => ['application/x-ms-shortcut', 'application/x-win-lnk'], + 'lnx' => ['application/x-atari-lynx-rom'], + 'loas' => ['audio/usac'], + 'log' => ['text/plain', 'text/x-log'], + 'lostxml' => ['application/lost+xml'], + 'lrm' => ['application/vnd.ms-lrm'], + 'lrv' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'], + 'lrz' => ['application/x-lrzip'], + 'ltf' => ['application/vnd.frogans.ltf'], + 'ltx' => ['application/x-tex', 'text/x-tex'], + 'lua' => ['text/x-lua'], + 'luac' => ['application/x-lua-bytecode'], + 'lvp' => ['audio/vnd.lucent.voice'], + 'lwo' => ['image/x-lwo'], + 'lwob' => ['image/x-lwo'], + 'lwp' => ['application/vnd.lotus-wordpro'], + 'lws' => ['image/x-lws'], + 'ly' => ['text/x-lilypond'], + 'lyx' => ['application/x-lyx', 'text/x-lyx'], + 'lz' => ['application/x-lzip'], + 'lz4' => ['application/x-lz4'], + 'lzh' => ['application/x-lha', 'application/x-lzh-compressed'], + 'lzma' => ['application/x-lzma'], + 'lzo' => ['application/x-lzop'], + 'm' => ['text/x-matlab', 'text/x-objcsrc', 'text/x-octave'], + 'm13' => ['application/x-msmediaview'], + 'm14' => ['application/x-msmediaview'], + 'm15' => ['audio/x-mod'], + 'm1u' => ['video/vnd.mpegurl', 'video/x-mpegurl'], + 'm1v' => ['video/mpeg'], + 'm21' => ['application/mp21'], + 'm2a' => ['audio/mpeg'], + 'm2t' => ['video/mp2t'], + 'm2ts' => ['video/mp2t'], + 'm2v' => ['video/mpeg'], + 'm3a' => ['audio/mpeg'], + 'm3u' => ['audio/x-mpegurl', 'application/m3u', 'application/vnd.apple.mpegurl', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist'], + 'm3u8' => ['application/m3u', 'application/vnd.apple.mpegurl', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist', 'audio/x-mpegurl'], + 'm4' => ['application/x-m4'], + 'm4a' => ['audio/mp4', 'audio/m4a', 'audio/x-m4a'], + 'm4b' => ['audio/x-m4b'], + 'm4p' => ['application/mp4'], + 'm4r' => ['audio/x-m4r'], + 'm4s' => ['video/iso.segment'], + 'm4u' => ['video/vnd.mpegurl', 'video/x-mpegurl'], + 'm4v' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'], + 'm7' => ['application/x-thomson-cartridge-memo7'], + 'ma' => ['application/mathematica'], + 'mab' => ['application/x-markaby'], + 'mads' => ['application/mads+xml'], + 'maei' => ['application/mmt-aei+xml'], + 'mag' => ['application/vnd.ecowin.chart'], + 'mak' => ['text/x-makefile'], + 'maker' => ['application/vnd.framemaker'], + 'man' => ['application/x-troff-man', 'text/troff'], + 'manifest' => ['text/cache-manifest'], + 'map' => ['application/json'], + 'markdown' => ['text/markdown', 'text/x-markdown'], + 'mathml' => ['application/mathml+xml'], + 'mb' => ['application/mathematica'], + 'mbk' => ['application/vnd.mobius.mbk'], + 'mbox' => ['application/mbox'], + 'mc1' => ['application/vnd.medcalcdata'], + 'mc2' => ['text/vnd.senx.warpscript'], + 'mcd' => ['application/vnd.mcd'], + 'mcurl' => ['text/vnd.curl.mcurl'], + 'md' => ['text/markdown', 'text/x-markdown'], + 'mdb' => ['application/x-msaccess', 'application/mdb', 'application/msaccess', 'application/vnd.ms-access', 'application/vnd.msaccess', 'application/x-lmdb', 'application/x-mdb', 'zz-application/zz-winassoc-mdb'], + 'mdi' => ['image/vnd.ms-modi'], + 'mdx' => ['application/x-genesis-32x-rom', 'text/mdx'], + 'me' => ['text/troff', 'text/x-troff-me'], + 'med' => ['audio/x-mod'], + 'mesh' => ['model/mesh'], + 'meta4' => ['application/metalink4+xml'], + 'metalink' => ['application/metalink+xml'], + 'mets' => ['application/mets+xml'], + 'mfm' => ['application/vnd.mfmp'], + 'mft' => ['application/rpki-manifest'], + 'mgp' => ['application/vnd.osgeo.mapguide.package', 'application/x-magicpoint'], + 'mgz' => ['application/vnd.proteus.magazine'], + 'mht' => ['application/x-mimearchive'], + 'mhtml' => ['application/x-mimearchive'], + 'mid' => ['audio/midi', 'audio/x-midi'], + 'midi' => ['audio/midi', 'audio/x-midi'], + 'mie' => ['application/x-mie'], + 'mif' => ['application/vnd.mif', 'application/x-mif'], + 'mime' => ['message/rfc822'], + 'minipsf' => ['audio/x-minipsf'], + 'mj2' => ['video/mj2'], + 'mjp2' => ['video/mj2'], + 'mjpeg' => ['video/x-mjpeg'], + 'mjpg' => ['video/x-mjpeg'], + 'mjs' => ['application/javascript', 'application/x-javascript', 'text/javascript', 'text/jscript'], + 'mk' => ['text/x-makefile'], + 'mk3d' => ['video/x-matroska', 'video/x-matroska-3d'], + 'mka' => ['audio/x-matroska'], + 'mkd' => ['text/markdown', 'text/x-markdown'], + 'mks' => ['video/x-matroska'], + 'mkv' => ['video/x-matroska'], + 'ml' => ['text/x-ocaml'], + 'mli' => ['text/x-ocaml'], + 'mlp' => ['application/vnd.dolby.mlp'], + 'mm' => ['text/x-objc++src', 'text/x-troff-mm'], + 'mmd' => ['application/vnd.chipnuts.karaoke-mmd'], + 'mmf' => ['application/vnd.smaf', 'application/x-smaf'], + 'mml' => ['application/mathml+xml', 'text/mathml'], + 'mmr' => ['image/vnd.fujixerox.edmics-mmr'], + 'mng' => ['video/x-mng'], + 'mny' => ['application/x-msmoney'], + 'mo' => ['application/x-gettext-translation', 'text/x-modelica'], + 'mo3' => ['audio/x-mo3'], + 'mobi' => ['application/x-mobipocket-ebook'], + 'moc' => ['text/x-moc'], + 'mod' => ['application/x-object', 'audio/x-mod'], + 'mods' => ['application/mods+xml'], + 'mof' => ['text/x-mof'], + 'moov' => ['video/quicktime'], + 'mount' => ['text/x-systemd-unit'], + 'mov' => ['video/quicktime'], + 'movie' => ['video/x-sgi-movie'], + 'mp+' => ['audio/x-musepack'], + 'mp2' => ['audio/mp2', 'audio/mpeg', 'audio/x-mp2', 'video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mp21' => ['application/mp21'], + 'mp2a' => ['audio/mpeg'], + 'mp3' => ['audio/mpeg', 'audio/mp3', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'], + 'mp4' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'], + 'mp4a' => ['audio/mp4'], + 'mp4s' => ['application/mp4'], + 'mp4v' => ['video/mp4'], + 'mpc' => ['application/vnd.mophun.certificate', 'audio/x-musepack'], + 'mpd' => ['application/dash+xml'], + 'mpe' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mpeg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mpf' => ['application/media-policy-dataset+xml'], + 'mpg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mpg4' => ['video/mp4'], + 'mpga' => ['audio/mp3', 'audio/mpeg', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'], + 'mpkg' => ['application/vnd.apple.installer+xml'], + 'mpl' => ['text/x-mpl2', 'video/mp2t'], + 'mpls' => ['video/mp2t'], + 'mpm' => ['application/vnd.blueice.multipass'], + 'mpn' => ['application/vnd.mophun.application'], + 'mpp' => ['application/dash-patch+xml', 'application/vnd.ms-project', 'audio/x-musepack'], + 'mpt' => ['application/vnd.ms-project'], + 'mpy' => ['application/vnd.ibm.minipay'], + 'mqy' => ['application/vnd.mobius.mqy'], + 'mrc' => ['application/marc'], + 'mrcx' => ['application/marcxml+xml'], + 'mrl' => ['text/x-mrml'], + 'mrml' => ['text/x-mrml'], + 'mrpack' => ['application/x-modrinth-modpack+zip'], + 'mrw' => ['image/x-minolta-mrw'], + 'ms' => ['text/troff', 'text/x-troff-ms'], + 'mscml' => ['application/mediaservercontrol+xml'], + 'mseed' => ['application/vnd.fdsn.mseed'], + 'mseq' => ['application/vnd.mseq'], + 'msf' => ['application/vnd.epson.msf'], + 'msg' => ['application/vnd.ms-outlook'], + 'msh' => ['model/mesh'], + 'msi' => ['application/x-msdownload', 'application/x-msi'], + 'msix' => ['application/msix'], + 'msixbundle' => ['application/msixbundle'], + 'msl' => ['application/vnd.mobius.msl'], + 'msod' => ['image/x-msod'], + 'msp' => ['application/microsoftpatch'], + 'msty' => ['application/vnd.muvee.style'], + 'msu' => ['application/microsoftupdate'], + 'msx' => ['application/x-msx-rom'], + 'mtl' => ['model/mtl'], + 'mtm' => ['audio/x-mod'], + 'mts' => ['model/vnd.mts', 'video/mp2t'], + 'mup' => ['text/x-mup'], + 'mus' => ['application/vnd.musician'], + 'musd' => ['application/mmt-usd+xml'], + 'musicxml' => ['application/vnd.recordare.musicxml+xml'], + 'mvb' => ['application/x-msmediaview'], + 'mvt' => ['application/vnd.mapbox-vector-tile'], + 'mwf' => ['application/vnd.mfer'], + 'mxf' => ['application/mxf'], + 'mxl' => ['application/vnd.recordare.musicxml'], + 'mxmf' => ['audio/mobile-xmf', 'audio/vnd.nokia.mobile-xmf'], + 'mxml' => ['application/xv+xml'], + 'mxs' => ['application/vnd.triscape.mxs'], + 'mxu' => ['video/vnd.mpegurl', 'video/x-mpegurl'], + 'n-gage' => ['application/vnd.nokia.n-gage.symbian.install'], + 'n3' => ['text/n3'], + 'n64' => ['application/x-n64-rom'], + 'nb' => ['application/mathematica', 'application/x-mathematica'], + 'nbp' => ['application/vnd.wolfram.player'], + 'nc' => ['application/x-netcdf'], + 'ncx' => ['application/x-dtbncx+xml'], + 'nds' => ['application/x-nintendo-ds-rom'], + 'nef' => ['image/x-nikon-nef'], + 'nes' => ['application/x-nes-rom'], + 'nez' => ['application/x-nes-rom'], + 'nfo' => ['text/x-nfo'], + 'ngc' => ['application/x-neo-geo-pocket-color-rom'], + 'ngdat' => ['application/vnd.nokia.n-gage.data'], + 'ngp' => ['application/x-neo-geo-pocket-rom'], + 'nim' => ['text/x-nim'], + 'nimble' => ['text/x-nimscript'], + 'nims' => ['text/x-nimscript'], + 'nitf' => ['application/vnd.nitf'], + 'nlu' => ['application/vnd.neurolanguage.nlu'], + 'nml' => ['application/vnd.enliven'], + 'nnd' => ['application/vnd.noblenet-directory'], + 'nns' => ['application/vnd.noblenet-sealer'], + 'nnw' => ['application/vnd.noblenet-web'], + 'not' => ['text/x-mup'], + 'npx' => ['image/vnd.net-fpx'], + 'nq' => ['application/n-quads'], + 'nrw' => ['image/x-nikon-nrw'], + 'nsc' => ['application/x-conference', 'application/x-netshow-channel'], + 'nsf' => ['application/vnd.lotus-notes'], + 'nsv' => ['video/x-nsv'], + 'nt' => ['application/n-triples'], + 'ntf' => ['application/vnd.nitf'], + 'nu' => ['application/x-nuscript', 'text/x-nu'], + 'numbers' => ['application/vnd.apple.numbers', 'application/x-iwork-numbers-sffnumbers'], + 'nzb' => ['application/x-nzb'], + 'o' => ['application/x-object'], + 'oa2' => ['application/vnd.fujitsu.oasys2'], + 'oa3' => ['application/vnd.fujitsu.oasys3'], + 'oas' => ['application/vnd.fujitsu.oasys'], + 'obd' => ['application/x-msbinder'], + 'obgx' => ['application/vnd.openblox.game+xml'], + 'obj' => ['application/prs.wavefront-obj', 'application/x-tgif', 'model/obj'], + 'ocl' => ['text/x-ocl'], + 'ocx' => ['application/vnd.microsoft.portable-executable'], + 'oda' => ['application/oda'], + 'odb' => ['application/vnd.oasis.opendocument.database', 'application/vnd.sun.xml.base'], + 'odc' => ['application/vnd.oasis.opendocument.chart'], + 'odf' => ['application/vnd.oasis.opendocument.formula'], + 'odft' => ['application/vnd.oasis.opendocument.formula-template'], + 'odg' => ['application/vnd.oasis.opendocument.graphics'], + 'odi' => ['application/vnd.oasis.opendocument.image'], + 'odm' => ['application/vnd.oasis.opendocument.text-master'], + 'odp' => ['application/vnd.oasis.opendocument.presentation'], + 'ods' => ['application/vnd.oasis.opendocument.spreadsheet'], + 'odt' => ['application/vnd.oasis.opendocument.text'], + 'oga' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg'], + 'ogex' => ['model/vnd.opengex'], + 'ogg' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg', 'video/ogg', 'video/x-ogg', 'video/x-theora', 'video/x-theora+ogg'], + 'ogm' => ['video/x-ogm', 'video/x-ogm+ogg'], + 'ogv' => ['video/ogg', 'video/x-ogg'], + 'ogx' => ['application/ogg', 'application/x-ogg'], + 'old' => ['application/x-trash'], + 'oleo' => ['application/x-oleo'], + 'omdoc' => ['application/omdoc+xml'], + 'onepkg' => ['application/onenote'], + 'onetmp' => ['application/onenote'], + 'onetoc' => ['application/onenote'], + 'onetoc2' => ['application/onenote'], + 'ooc' => ['text/x-ooc'], + 'openvpn' => ['application/x-openvpn-profile'], + 'opf' => ['application/oebps-package+xml'], + 'opml' => ['text/x-opml', 'text/x-opml+xml'], + 'oprc' => ['application/vnd.palm', 'application/x-palm-database'], + 'opus' => ['audio/ogg', 'audio/x-ogg', 'audio/x-opus+ogg'], + 'ora' => ['image/openraster'], + 'orf' => ['image/x-olympus-orf'], + 'org' => ['application/vnd.lotus-organizer', 'text/org', 'text/x-org'], + 'osf' => ['application/vnd.yamaha.openscoreformat'], + 'osfpvg' => ['application/vnd.yamaha.openscoreformat.osfpvg+xml'], + 'osm' => ['application/vnd.openstreetmap.data+xml'], + 'otc' => ['application/vnd.oasis.opendocument.chart-template'], + 'otf' => ['application/vnd.oasis.opendocument.formula-template', 'application/x-font-otf', 'font/otf'], + 'otg' => ['application/vnd.oasis.opendocument.graphics-template'], + 'oth' => ['application/vnd.oasis.opendocument.text-web'], + 'oti' => ['application/vnd.oasis.opendocument.image-template'], + 'otp' => ['application/vnd.oasis.opendocument.presentation-template'], + 'ots' => ['application/vnd.oasis.opendocument.spreadsheet-template'], + 'ott' => ['application/vnd.oasis.opendocument.text-template'], + 'ova' => ['application/ovf', 'application/x-virtualbox-ova'], + 'ovf' => ['application/x-virtualbox-ovf'], + 'ovpn' => ['application/x-openvpn-profile'], + 'owl' => ['application/rdf+xml', 'text/rdf'], + 'owx' => ['application/owl+xml'], + 'oxps' => ['application/oxps'], + 'oxt' => ['application/vnd.openofficeorg.extension'], + 'p' => ['text/x-pascal'], + 'p10' => ['application/pkcs10'], + 'p12' => ['application/pkcs12', 'application/x-pkcs12'], + 'p65' => ['application/x-pagemaker'], + 'p7b' => ['application/x-pkcs7-certificates'], + 'p7c' => ['application/pkcs7-mime'], + 'p7m' => ['application/pkcs7-mime'], + 'p7r' => ['application/x-pkcs7-certreqresp'], + 'p7s' => ['application/pkcs7-signature'], + 'p8' => ['application/pkcs8'], + 'p8e' => ['application/pkcs8-encrypted'], + 'pac' => ['application/x-ns-proxy-autoconfig'], + 'pack' => ['application/x-java-pack200'], + 'pages' => ['application/vnd.apple.pages', 'application/x-iwork-pages-sffpages'], + 'pak' => ['application/x-pak'], + 'par2' => ['application/x-par2'], + 'part' => ['application/x-partial-download'], + 'pas' => ['text/x-pascal'], + 'pat' => ['image/x-gimp-pat'], + 'patch' => ['text/x-diff', 'text/x-patch'], + 'path' => ['text/x-systemd-unit'], + 'paw' => ['application/vnd.pawaafile'], + 'pbd' => ['application/vnd.powerbuilder6'], + 'pbm' => ['image/x-portable-bitmap'], + 'pcap' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'], + 'pcd' => ['image/x-photo-cd'], + 'pce' => ['application/x-pc-engine-rom'], + 'pcf' => ['application/x-cisco-vpn-settings', 'application/x-font-pcf'], + 'pcf.Z' => ['application/x-font-pcf'], + 'pcf.gz' => ['application/x-font-pcf'], + 'pcl' => ['application/vnd.hp-pcl'], + 'pclxl' => ['application/vnd.hp-pclxl'], + 'pct' => ['image/x-pict'], + 'pcurl' => ['application/vnd.curl.pcurl'], + 'pcx' => ['image/vnd.zbrush.pcx', 'image/x-pcx'], + 'pdb' => ['application/vnd.palm', 'application/x-aportisdoc', 'application/x-ms-pdb', 'application/x-palm-database', 'application/x-pilot', 'chemical/x-pdb'], + 'pdc' => ['application/x-aportisdoc'], + 'pde' => ['text/x-processing'], + 'pdf' => ['application/pdf', 'application/acrobat', 'application/nappdf', 'application/x-pdf', 'image/pdf'], + 'pdf.bz2' => ['application/x-bzpdf'], + 'pdf.gz' => ['application/x-gzpdf'], + 'pdf.lz' => ['application/x-lzpdf'], + 'pdf.xz' => ['application/x-xzpdf'], + 'pef' => ['image/x-pentax-pef'], + 'pem' => ['application/x-x509-ca-cert'], + 'perl' => ['application/x-perl', 'text/x-perl'], + 'pfa' => ['application/x-font-type1'], + 'pfb' => ['application/x-font-type1'], + 'pfm' => ['application/x-font-type1'], + 'pfr' => ['application/font-tdpfr', 'application/vnd.truedoc'], + 'pfx' => ['application/pkcs12', 'application/x-pkcs12'], + 'pgm' => ['image/x-portable-graymap'], + 'pgn' => ['application/vnd.chess-pgn', 'application/x-chess-pgn'], + 'pgp' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature'], + 'php' => ['application/x-php', 'application/x-httpd-php'], + 'php3' => ['application/x-php'], + 'php4' => ['application/x-php'], + 'php5' => ['application/x-php'], + 'phps' => ['application/x-php'], + 'pic' => ['image/x-pict'], + 'pict' => ['image/x-pict'], + 'pict1' => ['image/x-pict'], + 'pict2' => ['image/x-pict'], + 'pk' => ['application/x-tex-pk'], + 'pkg' => ['application/x-xar'], + 'pki' => ['application/pkixcmp'], + 'pkipath' => ['application/pkix-pkipath'], + 'pkpass' => ['application/vnd.apple.pkpass'], + 'pkr' => ['application/pgp-keys'], + 'pl' => ['application/x-perl', 'text/x-perl'], + 'pla' => ['audio/x-iriver-pla'], + 'plb' => ['application/vnd.3gpp.pic-bw-large'], + 'plc' => ['application/vnd.mobius.plc'], + 'plf' => ['application/vnd.pocketlearn'], + 'pln' => ['application/x-planperfect'], + 'pls' => ['application/pls', 'application/pls+xml', 'audio/scpls', 'audio/x-scpls'], + 'pm' => ['application/x-pagemaker', 'application/x-perl', 'text/x-perl'], + 'pm6' => ['application/x-pagemaker'], + 'pmd' => ['application/x-pagemaker'], + 'pml' => ['application/vnd.ctc-posml'], + 'png' => ['image/png', 'image/apng', 'image/vnd.mozilla.apng'], + 'pnm' => ['image/x-portable-anymap'], + 'pntg' => ['image/x-macpaint'], + 'po' => ['application/x-gettext', 'text/x-gettext-translation', 'text/x-po'], + 'pod' => ['application/x-perl', 'text/x-perl'], + 'por' => ['application/x-spss-por'], + 'portpkg' => ['application/vnd.macports.portpkg'], + 'pot' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint', 'text/x-gettext-translation-template', 'text/x-pot'], + 'potm' => ['application/vnd.ms-powerpoint.template.macroenabled.12'], + 'potx' => ['application/vnd.openxmlformats-officedocument.presentationml.template'], + 'ppam' => ['application/vnd.ms-powerpoint.addin.macroenabled.12'], + 'ppd' => ['application/vnd.cups-ppd'], + 'ppm' => ['image/x-portable-pixmap'], + 'pps' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint'], + 'ppsm' => ['application/vnd.ms-powerpoint.slideshow.macroenabled.12'], + 'ppsx' => ['application/vnd.openxmlformats-officedocument.presentationml.slideshow'], + 'ppt' => ['application/vnd.ms-powerpoint', 'application/mspowerpoint', 'application/powerpoint', 'application/x-mspowerpoint'], + 'pptm' => ['application/vnd.ms-powerpoint.presentation.macroenabled.12'], + 'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'], + 'ppz' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint'], + 'pqa' => ['application/vnd.palm', 'application/x-palm-database'], + 'prc' => ['application/vnd.palm', 'application/x-mobipocket-ebook', 'application/x-palm-database', 'application/x-pilot'], + 'pre' => ['application/vnd.lotus-freelance'], + 'prf' => ['application/pics-rules'], + 'provx' => ['application/provenance+xml'], + 'ps' => ['application/postscript'], + 'ps.bz2' => ['application/x-bzpostscript'], + 'ps.gz' => ['application/x-gzpostscript'], + 'ps1' => ['application/x-powershell'], + 'psb' => ['application/vnd.3gpp.pic-bw-small'], + 'psd' => ['application/photoshop', 'application/x-photoshop', 'image/photoshop', 'image/psd', 'image/vnd.adobe.photoshop', 'image/x-photoshop', 'image/x-psd'], + 'psf' => ['application/x-font-linux-psf', 'audio/x-psf'], + 'psf.gz' => ['application/x-gz-font-linux-psf'], + 'psflib' => ['audio/x-psflib'], + 'psid' => ['audio/prs.sid'], + 'pskcxml' => ['application/pskc+xml'], + 'psw' => ['application/x-pocket-word'], + 'pti' => ['image/prs.pti'], + 'ptid' => ['application/vnd.pvi.ptid1'], + 'pub' => ['application/vnd.ms-publisher', 'application/x-mspublisher'], + 'pvb' => ['application/vnd.3gpp.pic-bw-var'], + 'pw' => ['application/x-pw'], + 'pwn' => ['application/vnd.3m.post-it-notes'], + 'py' => ['text/x-python', 'text/x-python3'], + 'py3' => ['text/x-python3'], + 'py3x' => ['text/x-python3'], + 'pya' => ['audio/vnd.ms-playready.media.pya'], + 'pyc' => ['application/x-python-bytecode'], + 'pyi' => ['text/x-python3'], + 'pyo' => ['application/x-python-bytecode'], + 'pys' => ['application/x-pyspread-bz-spreadsheet'], + 'pysu' => ['application/x-pyspread-spreadsheet'], + 'pyv' => ['video/vnd.ms-playready.media.pyv'], + 'pyx' => ['text/x-python'], + 'qam' => ['application/vnd.epson.quickanime'], + 'qbo' => ['application/vnd.intu.qbo'], + 'qcow' => ['application/x-qemu-disk'], + 'qcow2' => ['application/x-qemu-disk'], + 'qd' => ['application/x-fd-file', 'application/x-raw-floppy-disk-image'], + 'qed' => ['application/x-qed-disk'], + 'qfx' => ['application/vnd.intu.qfx'], + 'qif' => ['application/x-qw', 'image/x-quicktime'], + 'qml' => ['text/x-qml'], + 'qmlproject' => ['text/x-qml'], + 'qmltypes' => ['text/x-qml'], + 'qoi' => ['image/qoi'], + 'qp' => ['application/x-qpress'], + 'qps' => ['application/vnd.publishare-delta-tree'], + 'qpw' => ['application/x-quattropro'], + 'qs' => ['application/sparql-query'], + 'qt' => ['video/quicktime'], + 'qti' => ['application/x-qtiplot'], + 'qti.gz' => ['application/x-qtiplot'], + 'qtif' => ['image/x-quicktime'], + 'qtl' => ['application/x-quicktime-media-link', 'application/x-quicktimeplayer'], + 'qtvr' => ['video/quicktime'], + 'qwd' => ['application/vnd.quark.quarkxpress'], + 'qwt' => ['application/vnd.quark.quarkxpress'], + 'qxb' => ['application/vnd.quark.quarkxpress'], + 'qxd' => ['application/vnd.quark.quarkxpress'], + 'qxl' => ['application/vnd.quark.quarkxpress'], + 'qxt' => ['application/vnd.quark.quarkxpress'], + 'ra' => ['audio/vnd.m-realaudio', 'audio/vnd.rn-realaudio', 'audio/x-pn-realaudio', 'audio/x-realaudio'], + 'raf' => ['image/x-fuji-raf'], + 'ram' => ['application/ram', 'audio/x-pn-realaudio'], + 'raml' => ['application/raml+yaml'], + 'rapd' => ['application/route-apd+xml'], + 'rar' => ['application/x-rar-compressed', 'application/vnd.rar', 'application/x-rar'], + 'ras' => ['image/x-cmu-raster'], + 'raw' => ['image/x-panasonic-raw', 'image/x-panasonic-rw'], + 'raw-disk-image' => ['application/vnd.efi.img', 'application/x-raw-disk-image'], + 'raw-disk-image.xz' => ['application/x-raw-disk-image-xz-compressed'], + 'rax' => ['audio/vnd.m-realaudio', 'audio/vnd.rn-realaudio', 'audio/x-pn-realaudio'], + 'rb' => ['application/x-ruby'], + 'rcprofile' => ['application/vnd.ipunplugged.rcprofile'], + 'rdf' => ['application/rdf+xml', 'text/rdf'], + 'rdfs' => ['application/rdf+xml', 'text/rdf'], + 'rdz' => ['application/vnd.data-vision.rdz'], + 'reg' => ['text/x-ms-regedit'], + 'rej' => ['application/x-reject', 'text/x-reject'], + 'relo' => ['application/p2p-overlay+xml'], + 'rep' => ['application/vnd.businessobjects'], + 'res' => ['application/x-dtbresource+xml', 'application/x-godot-resource'], + 'rgb' => ['image/x-rgb'], + 'rif' => ['application/reginfo+xml'], + 'rip' => ['audio/vnd.rip'], + 'ris' => ['application/x-research-info-systems'], + 'rl' => ['application/resource-lists+xml'], + 'rlc' => ['image/vnd.fujixerox.edmics-rlc'], + 'rld' => ['application/resource-lists-diff+xml'], + 'rle' => ['image/rle'], + 'rm' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmi' => ['audio/midi'], + 'rmj' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmm' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmp' => ['audio/x-pn-realaudio-plugin'], + 'rms' => ['application/vnd.jcp.javame.midlet-rms', 'application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmvb' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmx' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rnc' => ['application/relax-ng-compact-syntax', 'application/x-rnc'], + 'rng' => ['application/xml', 'text/xml'], + 'roa' => ['application/rpki-roa'], + 'roff' => ['application/x-troff', 'text/troff', 'text/x-troff'], + 'ros' => ['text/x-common-lisp'], + 'rp' => ['image/vnd.rn-realpix'], + 'rp9' => ['application/vnd.cloanto.rp9'], + 'rpm' => ['application/x-redhat-package-manager', 'application/x-rpm'], + 'rpss' => ['application/vnd.nokia.radio-presets'], + 'rpst' => ['application/vnd.nokia.radio-preset'], + 'rq' => ['application/sparql-query'], + 'rs' => ['application/rls-services+xml', 'text/rust'], + 'rsat' => ['application/atsc-rsat+xml'], + 'rsd' => ['application/rsd+xml'], + 'rsheet' => ['application/urc-ressheet+xml'], + 'rss' => ['application/rss+xml', 'text/rss'], + 'rst' => ['text/x-rst'], + 'rt' => ['text/vnd.rn-realtext'], + 'rtf' => ['application/rtf', 'text/rtf'], + 'rtx' => ['text/richtext'], + 'run' => ['application/x-makeself'], + 'rusd' => ['application/route-usd+xml'], + 'rv' => ['video/vnd.rn-realvideo', 'video/x-real-video'], + 'rvx' => ['video/vnd.rn-realvideo', 'video/x-real-video'], + 'rw2' => ['image/x-panasonic-raw2', 'image/x-panasonic-rw2'], + 'rz' => ['application/x-rzip'], + 's' => ['text/x-asm'], + 's3m' => ['audio/s3m', 'audio/x-s3m'], + 'saf' => ['application/vnd.yamaha.smaf-audio'], + 'sage' => ['text/x-sagemath'], + 'sam' => ['application/x-amipro'], + 'sami' => ['application/x-sami'], + 'sap' => ['application/x-sap-file', 'application/x-thomson-sap-image'], + 'sass' => ['text/x-sass'], + 'sav' => ['application/x-spss-sav', 'application/x-spss-savefile'], + 'sbml' => ['application/sbml+xml'], + 'sc' => ['application/vnd.ibm.secure-container', 'text/x-scala'], + 'scala' => ['text/x-scala'], + 'scd' => ['application/x-msschedule'], + 'scm' => ['application/vnd.lotus-screencam', 'text/x-scheme'], + 'scn' => ['application/x-godot-scene'], + 'scope' => ['text/x-systemd-unit'], + 'scq' => ['application/scvp-cv-request'], + 'scr' => ['application/vnd.microsoft.portable-executable', 'application/x-ms-dos-executable', 'application/x-ms-ne-executable', 'application/x-msdownload'], + 'scs' => ['application/scvp-cv-response'], + 'scss' => ['text/x-scss'], + 'scurl' => ['text/vnd.curl.scurl'], + 'sda' => ['application/vnd.stardivision.draw'], + 'sdc' => ['application/vnd.stardivision.calc'], + 'sdd' => ['application/vnd.stardivision.impress'], + 'sdkd' => ['application/vnd.solent.sdkm+xml'], + 'sdkm' => ['application/vnd.solent.sdkm+xml'], + 'sdp' => ['application/sdp', 'application/vnd.sdp', 'application/vnd.stardivision.impress', 'application/x-sdp'], + 'sds' => ['application/vnd.stardivision.chart'], + 'sdw' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'], + 'sea' => ['application/x-sea'], + 'see' => ['application/vnd.seemail'], + 'seed' => ['application/vnd.fdsn.seed'], + 'sema' => ['application/vnd.sema'], + 'semd' => ['application/vnd.semd'], + 'semf' => ['application/vnd.semf'], + 'senmlx' => ['application/senml+xml'], + 'sensmlx' => ['application/sensml+xml'], + 'ser' => ['application/java-serialized-object'], + 'service' => ['text/x-dbus-service', 'text/x-systemd-unit'], + 'setpay' => ['application/set-payment-initiation'], + 'setreg' => ['application/set-registration-initiation'], + 'sfc' => ['application/vnd.nintendo.snes.rom', 'application/x-snes-rom'], + 'sfd-hdstx' => ['application/vnd.hydrostatix.sof-data'], + 'sfs' => ['application/vnd.spotfire.sfs'], + 'sfv' => ['text/x-sfv'], + 'sg' => ['application/x-sg1000-rom'], + 'sgb' => ['application/x-gameboy-rom'], + 'sgd' => ['application/x-genesis-rom'], + 'sgf' => ['application/x-go-sgf'], + 'sgi' => ['image/sgi', 'image/x-sgi'], + 'sgl' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'], + 'sgm' => ['text/sgml'], + 'sgml' => ['text/sgml'], + 'sh' => ['application/x-sh', 'application/x-shellscript', 'text/x-sh'], + 'shape' => ['application/x-dia-shape'], + 'shar' => ['application/x-shar'], + 'shex' => ['text/shex'], + 'shf' => ['application/shf+xml'], + 'shn' => ['application/x-shorten', 'audio/x-shorten'], + 'shtml' => ['text/html'], + 'siag' => ['application/x-siag'], + 'sid' => ['audio/prs.sid', 'image/x-mrsid-image'], + 'sieve' => ['application/sieve'], + 'sig' => ['application/pgp-signature'], + 'sik' => ['application/x-trash'], + 'sil' => ['audio/silk'], + 'silo' => ['model/mesh'], + 'sis' => ['application/vnd.symbian.install'], + 'sisx' => ['application/vnd.symbian.install', 'x-epoc/x-sisx-app'], + 'sit' => ['application/x-stuffit', 'application/stuffit', 'application/x-sit'], + 'sitx' => ['application/x-sitx', 'application/x-stuffitx'], + 'siv' => ['application/sieve'], + 'sk' => ['image/x-skencil'], + 'sk1' => ['image/x-skencil'], + 'skd' => ['application/vnd.koan'], + 'skm' => ['application/vnd.koan'], + 'skp' => ['application/vnd.koan'], + 'skr' => ['application/pgp-keys'], + 'skt' => ['application/vnd.koan'], + 'sldm' => ['application/vnd.ms-powerpoint.slide.macroenabled.12'], + 'sldx' => ['application/vnd.openxmlformats-officedocument.presentationml.slide'], + 'slice' => ['text/x-systemd-unit'], + 'slim' => ['text/slim'], + 'slk' => ['text/spreadsheet'], + 'slm' => ['text/slim'], + 'sls' => ['application/route-s-tsid+xml'], + 'slt' => ['application/vnd.epson.salt'], + 'sm' => ['application/vnd.stepmania.stepchart'], + 'smaf' => ['application/vnd.smaf', 'application/x-smaf'], + 'smc' => ['application/vnd.nintendo.snes.rom', 'application/x-snes-rom'], + 'smd' => ['application/vnd.stardivision.mail', 'application/x-genesis-rom'], + 'smf' => ['application/vnd.stardivision.math'], + 'smi' => ['application/smil', 'application/smil+xml', 'application/x-sami'], + 'smil' => ['application/smil', 'application/smil+xml'], + 'smk' => ['video/vnd.radgamettools.smacker'], + 'sml' => ['application/smil', 'application/smil+xml'], + 'sms' => ['application/x-sms-rom'], + 'smv' => ['video/x-smv'], + 'smzip' => ['application/vnd.stepmania.package'], + 'snap' => ['application/vnd.snap'], + 'snd' => ['audio/basic'], + 'snf' => ['application/x-font-snf'], + 'so' => ['application/x-sharedlib'], + 'socket' => ['text/x-systemd-unit'], + 'spc' => ['application/x-pkcs7-certificates'], + 'spd' => ['application/x-font-speedo'], + 'spdx' => ['text/spdx'], + 'spec' => ['text/x-rpm-spec'], + 'spf' => ['application/vnd.yamaha.smaf-phrase'], + 'spl' => ['application/futuresplash', 'application/vnd.adobe.flash.movie', 'application/x-futuresplash', 'application/x-shockwave-flash'], + 'spm' => ['application/x-source-rpm'], + 'spot' => ['text/vnd.in3d.spot'], + 'spp' => ['application/scvp-vp-response'], + 'spq' => ['application/scvp-vp-request'], + 'spx' => ['application/x-apple-systemprofiler+xml', 'audio/ogg', 'audio/x-speex', 'audio/x-speex+ogg'], + 'sql' => ['application/sql', 'application/x-sql', 'text/x-sql'], + 'sqlite2' => ['application/x-sqlite2'], + 'sqlite3' => ['application/vnd.sqlite3', 'application/x-sqlite3'], + 'sqsh' => ['application/vnd.squashfs'], + 'sr2' => ['image/x-sony-sr2'], + 'src' => ['application/x-wais-source'], + 'src.rpm' => ['application/x-source-rpm'], + 'srf' => ['image/x-sony-srf'], + 'srt' => ['application/x-srt', 'application/x-subrip'], + 'sru' => ['application/sru+xml'], + 'srx' => ['application/sparql-results+xml'], + 'ss' => ['text/x-scheme'], + 'ssa' => ['text/x-ssa'], + 'ssdl' => ['application/ssdl+xml'], + 'sse' => ['application/vnd.kodak-descriptor'], + 'ssf' => ['application/vnd.epson.ssf'], + 'ssml' => ['application/ssml+xml'], + 'st' => ['application/vnd.sailingtracker.track'], + 'stc' => ['application/vnd.sun.xml.calc.template'], + 'std' => ['application/vnd.sun.xml.draw.template'], + 'stf' => ['application/vnd.wt.stf'], + 'sti' => ['application/vnd.sun.xml.impress.template'], + 'stk' => ['application/hyperstudio'], + 'stl' => ['application/vnd.ms-pki.stl', 'model/stl', 'model/x.stl-ascii', 'model/x.stl-binary'], + 'stm' => ['audio/x-stm'], + 'stpx' => ['model/step+xml'], + 'stpxz' => ['model/step-xml+zip'], + 'stpz' => ['model/step+zip'], + 'str' => ['application/vnd.pg.format'], + 'stw' => ['application/vnd.sun.xml.writer.template'], + 'sty' => ['application/x-tex', 'text/x-tex'], + 'styl' => ['text/stylus'], + 'stylus' => ['text/stylus'], + 'sub' => ['image/vnd.dvb.subtitle', 'text/vnd.dvb.subtitle', 'text/x-microdvd', 'text/x-mpsub', 'text/x-subviewer'], + 'sun' => ['image/x-sun-raster'], + 'sus' => ['application/vnd.sus-calendar'], + 'susp' => ['application/vnd.sus-calendar'], + 'sv' => ['text/x-svsrc'], + 'sv4cpio' => ['application/x-sv4cpio'], + 'sv4crc' => ['application/x-sv4crc'], + 'svc' => ['application/vnd.dvb.service'], + 'svd' => ['application/vnd.svd'], + 'svg' => ['image/svg+xml', 'image/svg'], + 'svg.gz' => ['image/svg+xml-compressed'], + 'svgz' => ['image/svg+xml', 'image/svg+xml-compressed'], + 'svh' => ['text/x-svhdr'], + 'swa' => ['application/x-director'], + 'swap' => ['text/x-systemd-unit'], + 'swf' => ['application/futuresplash', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash'], + 'swi' => ['application/vnd.aristanetworks.swi'], + 'swidtag' => ['application/swid+xml'], + 'swm' => ['application/x-ms-wim'], + 'sxc' => ['application/vnd.sun.xml.calc'], + 'sxd' => ['application/vnd.sun.xml.draw'], + 'sxg' => ['application/vnd.sun.xml.writer.global'], + 'sxi' => ['application/vnd.sun.xml.impress'], + 'sxm' => ['application/vnd.sun.xml.math'], + 'sxw' => ['application/vnd.sun.xml.writer'], + 'sylk' => ['text/spreadsheet'], + 'sys' => ['application/vnd.microsoft.portable-executable'], + 't' => ['application/x-perl', 'application/x-troff', 'text/troff', 'text/x-perl', 'text/x-troff'], + 't2t' => ['text/x-txt2tags'], + 't3' => ['application/x-t3vm-image'], + 't38' => ['image/t38'], + 'taglet' => ['application/vnd.mynfc'], + 'tak' => ['audio/x-tak'], + 'tao' => ['application/vnd.tao.intent-module-archive'], + 'tap' => ['image/vnd.tencent.tap'], + 'tar' => ['application/x-tar', 'application/x-gtar'], + 'tar.Z' => ['application/x-tarz'], + 'tar.bz' => ['application/x-bzip1-compressed-tar'], + 'tar.bz2' => ['application/x-bzip-compressed-tar', 'application/x-bzip2-compressed-tar'], + 'tar.bz3' => ['application/x-bzip3-compressed-tar'], + 'tar.gz' => ['application/x-compressed-tar'], + 'tar.lrz' => ['application/x-lrzip-compressed-tar'], + 'tar.lz' => ['application/x-lzip-compressed-tar'], + 'tar.lz4' => ['application/x-lz4-compressed-tar'], + 'tar.lzma' => ['application/x-lzma-compressed-tar'], + 'tar.lzo' => ['application/x-tzo'], + 'tar.rz' => ['application/x-rzip-compressed-tar'], + 'tar.xz' => ['application/x-xz-compressed-tar'], + 'tar.zst' => ['application/x-zstd-compressed-tar'], + 'target' => ['text/x-systemd-unit'], + 'taz' => ['application/x-tarz'], + 'tb2' => ['application/x-bzip-compressed-tar', 'application/x-bzip2-compressed-tar'], + 'tbz' => ['application/x-bzip1-compressed-tar'], + 'tbz2' => ['application/x-bzip-compressed-tar', 'application/x-bzip2-compressed-tar'], + 'tbz3' => ['application/x-bzip3-compressed-tar'], + 'tcap' => ['application/vnd.3gpp2.tcap'], + 'tcl' => ['application/x-tcl', 'text/tcl', 'text/x-tcl'], + 'td' => ['application/urc-targetdesc+xml'], + 'teacher' => ['application/vnd.smart.teacher'], + 'tei' => ['application/tei+xml'], + 'teicorpus' => ['application/tei+xml'], + 'tex' => ['application/x-tex', 'text/x-tex'], + 'texi' => ['application/x-texinfo', 'text/x-texinfo'], + 'texinfo' => ['application/x-texinfo', 'text/x-texinfo'], + 'text' => ['text/plain'], + 'tfi' => ['application/thraud+xml'], + 'tfm' => ['application/x-tex-tfm'], + 'tfx' => ['image/tiff-fx'], + 'tga' => ['application/tga', 'application/x-targa', 'application/x-tga', 'image/targa', 'image/tga', 'image/x-icb', 'image/x-targa', 'image/x-tga'], + 'tgz' => ['application/x-compressed-tar'], + 'theme' => ['application/x-theme'], + 'themepack' => ['application/x-windows-themepack'], + 'thmx' => ['application/vnd.ms-officetheme'], + 'tif' => ['image/tiff'], + 'tiff' => ['image/tiff'], + 'timer' => ['text/x-systemd-unit'], + 'tk' => ['application/x-tcl', 'text/tcl', 'text/x-tcl'], + 'tlrz' => ['application/x-lrzip-compressed-tar'], + 'tlz' => ['application/x-lzma-compressed-tar'], + 'tmo' => ['application/vnd.tmobile-livetv'], + 'tmx' => ['application/x-tiled-tmx'], + 'tnef' => ['application/ms-tnef', 'application/vnd.ms-tnef'], + 'tnf' => ['application/ms-tnef', 'application/vnd.ms-tnef'], + 'toc' => ['application/x-cdrdao-toc'], + 'toml' => ['application/toml'], + 'torrent' => ['application/x-bittorrent'], + 'tpic' => ['application/tga', 'application/x-targa', 'application/x-tga', 'image/targa', 'image/tga', 'image/x-icb', 'image/x-targa', 'image/x-tga'], + 'tpl' => ['application/vnd.groove-tool-template'], + 'tpt' => ['application/vnd.trid.tpt'], + 'tr' => ['application/x-troff', 'text/troff', 'text/x-troff'], + 'tra' => ['application/vnd.trueapp'], + 'tres' => ['application/x-godot-resource'], + 'trig' => ['application/trig', 'application/x-trig'], + 'trm' => ['application/x-msterminal'], + 'trz' => ['application/x-rzip-compressed-tar'], + 'ts' => ['application/x-linguist', 'text/vnd.qt.linguist', 'text/vnd.trolltech.linguist', 'video/mp2t'], + 'tscn' => ['application/x-godot-scene'], + 'tsd' => ['application/timestamped-data'], + 'tsv' => ['text/tab-separated-values'], + 'tsx' => ['application/x-tiled-tsx'], + 'tta' => ['audio/tta', 'audio/x-tta'], + 'ttc' => ['font/collection'], + 'ttf' => ['application/x-font-truetype', 'application/x-font-ttf', 'font/ttf'], + 'ttl' => ['text/turtle'], + 'ttml' => ['application/ttml+xml'], + 'ttx' => ['application/x-font-ttx'], + 'twd' => ['application/vnd.simtech-mindmapper'], + 'twds' => ['application/vnd.simtech-mindmapper'], + 'twig' => ['text/x-twig'], + 'txd' => ['application/vnd.genomatix.tuxedo'], + 'txf' => ['application/vnd.mobius.txf'], + 'txt' => ['text/plain'], + 'txz' => ['application/x-xz-compressed-tar'], + 'typ' => ['text/x-typst'], + 'tzo' => ['application/x-tzo'], + 'tzst' => ['application/x-zstd-compressed-tar'], + 'u32' => ['application/x-authorware-bin'], + 'u8dsn' => ['message/global-delivery-status'], + 'u8hdr' => ['message/global-headers'], + 'u8mdn' => ['message/global-disposition-notification'], + 'u8msg' => ['message/global'], + 'ubj' => ['application/ubjson'], + 'udeb' => ['application/vnd.debian.binary-package', 'application/x-deb', 'application/x-debian-package'], + 'ufd' => ['application/vnd.ufdl'], + 'ufdl' => ['application/vnd.ufdl'], + 'ufraw' => ['application/x-ufraw'], + 'ui' => ['application/x-designer', 'application/x-gtk-builder'], + 'uil' => ['text/x-uil'], + 'ult' => ['audio/x-mod'], + 'ulx' => ['application/x-glulx'], + 'umj' => ['application/vnd.umajin'], + 'unf' => ['application/x-nes-rom'], + 'uni' => ['audio/x-mod'], + 'unif' => ['application/x-nes-rom'], + 'unityweb' => ['application/vnd.unity'], + 'uoml' => ['application/vnd.uoml+xml'], + 'uri' => ['text/uri-list'], + 'uris' => ['text/uri-list'], + 'url' => ['application/x-mswinurl'], + 'urls' => ['text/uri-list'], + 'usdz' => ['model/vnd.usdz+zip'], + 'ustar' => ['application/x-ustar'], + 'utz' => ['application/vnd.uiq.theme'], + 'uu' => ['text/x-uuencode'], + 'uue' => ['text/x-uuencode', 'zz-application/zz-winassoc-uu'], + 'uva' => ['audio/vnd.dece.audio'], + 'uvd' => ['application/vnd.dece.data'], + 'uvf' => ['application/vnd.dece.data'], + 'uvg' => ['image/vnd.dece.graphic'], + 'uvh' => ['video/vnd.dece.hd'], + 'uvi' => ['image/vnd.dece.graphic'], + 'uvm' => ['video/vnd.dece.mobile'], + 'uvp' => ['video/vnd.dece.pd'], + 'uvs' => ['video/vnd.dece.sd'], + 'uvt' => ['application/vnd.dece.ttml+xml'], + 'uvu' => ['video/vnd.uvvu.mp4'], + 'uvv' => ['video/vnd.dece.video'], + 'uvva' => ['audio/vnd.dece.audio'], + 'uvvd' => ['application/vnd.dece.data'], + 'uvvf' => ['application/vnd.dece.data'], + 'uvvg' => ['image/vnd.dece.graphic'], + 'uvvh' => ['video/vnd.dece.hd'], + 'uvvi' => ['image/vnd.dece.graphic'], + 'uvvm' => ['video/vnd.dece.mobile'], + 'uvvp' => ['video/vnd.dece.pd'], + 'uvvs' => ['video/vnd.dece.sd'], + 'uvvt' => ['application/vnd.dece.ttml+xml'], + 'uvvu' => ['video/vnd.uvvu.mp4'], + 'uvvv' => ['video/vnd.dece.video'], + 'uvvx' => ['application/vnd.dece.unspecified'], + 'uvvz' => ['application/vnd.dece.zip'], + 'uvx' => ['application/vnd.dece.unspecified'], + 'uvz' => ['application/vnd.dece.zip'], + 'v' => ['text/x-verilog'], + 'v64' => ['application/x-n64-rom'], + 'vala' => ['text/x-vala'], + 'vapi' => ['text/x-vala'], + 'vb' => ['application/x-virtual-boy-rom', 'text/x-vb'], + 'vbe' => ['text/vbscript.encode'], + 'vbox' => ['application/x-virtualbox-vbox'], + 'vbox-extpack' => ['application/x-virtualbox-vbox-extpack'], + 'vbs' => ['text/vbs', 'text/vbscript'], + 'vcard' => ['text/directory', 'text/vcard', 'text/x-vcard'], + 'vcd' => ['application/x-cdlink'], + 'vcf' => ['text/x-vcard', 'text/directory', 'text/vcard'], + 'vcg' => ['application/vnd.groove-vcard'], + 'vcs' => ['application/ics', 'text/calendar', 'text/x-vcalendar'], + 'vct' => ['text/directory', 'text/vcard', 'text/x-vcard'], + 'vcx' => ['application/vnd.vcx'], + 'vda' => ['application/tga', 'application/x-targa', 'application/x-tga', 'image/targa', 'image/tga', 'image/x-icb', 'image/x-targa', 'image/x-tga'], + 'vdi' => ['application/x-vdi-disk', 'application/x-virtualbox-vdi'], + 'vds' => ['model/vnd.sap.vds'], + 'vhd' => ['application/x-vhd-disk', 'application/x-virtualbox-vhd', 'text/x-vhdl'], + 'vhdl' => ['text/x-vhdl'], + 'vhdx' => ['application/x-vhdx-disk', 'application/x-virtualbox-vhdx'], + 'vis' => ['application/vnd.visionary'], + 'viv' => ['video/vivo', 'video/vnd.vivo'], + 'vivo' => ['video/vivo', 'video/vnd.vivo'], + 'vlc' => ['application/m3u', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist', 'audio/x-mpegurl'], + 'vmdk' => ['application/x-virtualbox-vmdk', 'application/x-vmdk-disk'], + 'vob' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2', 'video/x-ms-vob'], + 'voc' => ['audio/x-voc'], + 'vor' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'], + 'vox' => ['application/x-authorware-bin'], + 'vpc' => ['application/x-vhd-disk', 'application/x-virtualbox-vhd'], + 'vrm' => ['model/vrml'], + 'vrml' => ['model/vrml'], + 'vsd' => ['application/vnd.visio'], + 'vsdm' => ['application/vnd.ms-visio.drawing.macroenabled.main+xml'], + 'vsdx' => ['application/vnd.ms-visio.drawing.main+xml'], + 'vsf' => ['application/vnd.vsf'], + 'vss' => ['application/vnd.visio'], + 'vssm' => ['application/vnd.ms-visio.stencil.macroenabled.main+xml'], + 'vssx' => ['application/vnd.ms-visio.stencil.main+xml'], + 'vst' => ['application/tga', 'application/vnd.visio', 'application/x-targa', 'application/x-tga', 'image/targa', 'image/tga', 'image/x-icb', 'image/x-targa', 'image/x-tga'], + 'vstm' => ['application/vnd.ms-visio.template.macroenabled.main+xml'], + 'vstx' => ['application/vnd.ms-visio.template.main+xml'], + 'vsw' => ['application/vnd.visio'], + 'vtf' => ['image/vnd.valve.source.texture'], + 'vtt' => ['text/vtt'], + 'vtu' => ['model/vnd.vtu'], + 'vxml' => ['application/voicexml+xml'], + 'w3d' => ['application/x-director'], + 'wad' => ['application/x-doom', 'application/x-doom-wad', 'application/x-wii-wad'], + 'wadl' => ['application/vnd.sun.wadl+xml'], + 'war' => ['application/java-archive'], + 'wasm' => ['application/wasm'], + 'wav' => ['audio/wav', 'audio/vnd.wave', 'audio/wave', 'audio/x-wav'], + 'wax' => ['application/x-ms-asx', 'audio/x-ms-asx', 'audio/x-ms-wax', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'wb1' => ['application/x-quattropro'], + 'wb2' => ['application/x-quattropro'], + 'wb3' => ['application/x-quattropro'], + 'wbmp' => ['image/vnd.wap.wbmp'], + 'wbs' => ['application/vnd.criticaltools.wbs+xml'], + 'wbxml' => ['application/vnd.wap.wbxml'], + 'wcm' => ['application/vnd.ms-works'], + 'wdb' => ['application/vnd.ms-works'], + 'wdp' => ['image/jxr', 'image/vnd.ms-photo'], + 'weba' => ['audio/webm'], + 'webapp' => ['application/x-web-app-manifest+json'], + 'webm' => ['video/webm'], + 'webmanifest' => ['application/manifest+json'], + 'webp' => ['image/webp'], + 'wg' => ['application/vnd.pmi.widget'], + 'wgt' => ['application/widget'], + 'wif' => ['application/watcherinfo+xml'], + 'wim' => ['application/x-ms-wim'], + 'wk1' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wk3' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wk4' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wkdownload' => ['application/x-partial-download'], + 'wks' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/vnd.ms-works', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wm' => ['video/x-ms-wm'], + 'wma' => ['audio/x-ms-wma', 'audio/wma'], + 'wmd' => ['application/x-ms-wmd'], + 'wmf' => ['application/wmf', 'application/x-msmetafile', 'application/x-wmf', 'image/wmf', 'image/x-win-metafile', 'image/x-wmf'], + 'wml' => ['text/vnd.wap.wml'], + 'wmlc' => ['application/vnd.wap.wmlc'], + 'wmls' => ['text/vnd.wap.wmlscript'], + 'wmlsc' => ['application/vnd.wap.wmlscriptc'], + 'wmv' => ['audio/x-ms-wmv', 'video/x-ms-wmv'], + 'wmx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'wmz' => ['application/x-ms-wmz', 'application/x-msmetafile'], + 'woff' => ['application/font-woff', 'application/x-font-woff', 'font/woff'], + 'woff2' => ['font/woff2'], + 'wp' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wp4' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wp5' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wp6' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wpd' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wpg' => ['application/x-wpg'], + 'wpl' => ['application/vnd.ms-wpl'], + 'wpp' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wps' => ['application/vnd.ms-works'], + 'wqd' => ['application/vnd.wqd'], + 'wri' => ['application/x-mswrite'], + 'wrl' => ['model/vrml'], + 'ws' => ['application/x-wonderswan-rom'], + 'wsc' => ['application/x-wonderswan-color-rom', 'message/vnd.wfa.wsc'], + 'wsdl' => ['application/wsdl+xml'], + 'wsgi' => ['text/x-python'], + 'wspolicy' => ['application/wspolicy+xml'], + 'wtb' => ['application/vnd.webturbo'], + 'wv' => ['audio/x-wavpack'], + 'wvc' => ['audio/x-wavpack-correction'], + 'wvp' => ['audio/x-wavpack'], + 'wvx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'wwf' => ['application/wwf', 'application/x-wwf'], + 'x32' => ['application/x-authorware-bin'], + 'x3d' => ['model/x3d+xml'], + 'x3db' => ['model/x3d+binary', 'model/x3d+fastinfoset'], + 'x3dbz' => ['model/x3d+binary'], + 'x3dv' => ['model/x3d+vrml', 'model/x3d-vrml'], + 'x3dvz' => ['model/x3d+vrml'], + 'x3dz' => ['model/x3d+xml'], + 'x3f' => ['image/x-sigma-x3f'], + 'x_b' => ['model/vnd.parasolid.transmit.binary'], + 'x_t' => ['model/vnd.parasolid.transmit.text'], + 'xac' => ['application/x-gnucash'], + 'xaml' => ['application/xaml+xml'], + 'xap' => ['application/x-silverlight-app'], + 'xar' => ['application/vnd.xara', 'application/x-xar'], + 'xav' => ['application/xcap-att+xml'], + 'xbap' => ['application/x-ms-xbap'], + 'xbd' => ['application/vnd.fujixerox.docuworks.binder'], + 'xbel' => ['application/x-xbel'], + 'xbl' => ['application/xml', 'text/xml'], + 'xbm' => ['image/x-xbitmap'], + 'xca' => ['application/xcap-caps+xml'], + 'xcf' => ['image/x-xcf'], + 'xcf.bz2' => ['image/x-compressed-xcf'], + 'xcf.gz' => ['image/x-compressed-xcf'], + 'xcs' => ['application/calendar+xml'], + 'xdf' => ['application/mrb-consumer+xml', 'application/mrb-publish+xml', 'application/xcap-diff+xml'], + 'xdgapp' => ['application/vnd.flatpak', 'application/vnd.xdgapp'], + 'xdm' => ['application/vnd.syncml.dm+xml'], + 'xdp' => ['application/vnd.adobe.xdp+xml'], + 'xdssc' => ['application/dssc+xml'], + 'xdw' => ['application/vnd.fujixerox.docuworks'], + 'xel' => ['application/xcap-el+xml'], + 'xenc' => ['application/xenc+xml'], + 'xer' => ['application/patch-ops-error+xml', 'application/xcap-error+xml'], + 'xfdf' => ['application/vnd.adobe.xfdf'], + 'xfdl' => ['application/vnd.xfdl'], + 'xhe' => ['audio/usac'], + 'xht' => ['application/xhtml+xml'], + 'xhtml' => ['application/xhtml+xml'], + 'xhvml' => ['application/xv+xml'], + 'xi' => ['audio/x-xi'], + 'xif' => ['image/vnd.xiff'], + 'xla' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlam' => ['application/vnd.ms-excel.addin.macroenabled.12'], + 'xlc' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xld' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlf' => ['application/x-xliff', 'application/x-xliff+xml', 'application/xliff+xml'], + 'xliff' => ['application/x-xliff', 'application/xliff+xml'], + 'xll' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlm' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlr' => ['application/vnd.ms-works'], + 'xls' => ['application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlsb' => ['application/vnd.ms-excel.sheet.binary.macroenabled.12'], + 'xlsm' => ['application/vnd.ms-excel.sheet.macroenabled.12'], + 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], + 'xlt' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xltm' => ['application/vnd.ms-excel.template.macroenabled.12'], + 'xltx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.template'], + 'xlw' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xm' => ['audio/x-xm', 'audio/xm'], + 'xmf' => ['audio/x-xmf', 'audio/xmf'], + 'xmi' => ['text/x-xmi'], + 'xml' => ['application/xml', 'text/xml'], + 'xns' => ['application/xcap-ns+xml'], + 'xo' => ['application/vnd.olpc-sugar'], + 'xop' => ['application/xop+xml'], + 'xpi' => ['application/x-xpinstall'], + 'xpl' => ['application/xproc+xml'], + 'xpm' => ['image/x-xpixmap', 'image/x-xpm'], + 'xpr' => ['application/vnd.is-xpr'], + 'xps' => ['application/vnd.ms-xpsdocument', 'application/xps'], + 'xpw' => ['application/vnd.intercon.formnet'], + 'xpx' => ['application/vnd.intercon.formnet'], + 'xsd' => ['application/xml', 'text/xml'], + 'xsl' => ['application/xml', 'application/xslt+xml'], + 'xslfo' => ['text/x-xslfo'], + 'xslt' => ['application/xslt+xml'], + 'xsm' => ['application/vnd.syncml+xml'], + 'xspf' => ['application/x-xspf+xml', 'application/xspf+xml'], + 'xul' => ['application/vnd.mozilla.xul+xml'], + 'xvm' => ['application/xv+xml'], + 'xvml' => ['application/xv+xml'], + 'xwd' => ['image/x-xwindowdump'], + 'xyz' => ['chemical/x-xyz'], + 'xz' => ['application/x-xz'], + 'yaml' => ['application/yaml', 'application/x-yaml', 'text/x-yaml', 'text/yaml'], + 'yang' => ['application/yang'], + 'yin' => ['application/yin+xml'], + 'yml' => ['application/yaml', 'application/x-yaml', 'text/x-yaml', 'text/yaml'], + 'ymp' => ['text/x-suse-ymp'], + 'yt' => ['application/vnd.youtube.yt', 'video/vnd.youtube.yt'], + 'z1' => ['application/x-zmachine'], + 'z2' => ['application/x-zmachine'], + 'z3' => ['application/x-zmachine'], + 'z4' => ['application/x-zmachine'], + 'z5' => ['application/x-zmachine'], + 'z6' => ['application/x-zmachine'], + 'z64' => ['application/x-n64-rom'], + 'z7' => ['application/x-zmachine'], + 'z8' => ['application/x-zmachine'], + 'zabw' => ['application/x-abiword'], + 'zaz' => ['application/vnd.zzazz.deck+xml'], + 'zim' => ['application/x-openzim'], + 'zip' => ['application/zip', 'application/x-zip', 'application/x-zip-compressed'], + 'zipx' => ['application/x-zip', 'application/x-zip-compressed', 'application/zip'], + 'zir' => ['application/vnd.zul'], + 'zirz' => ['application/vnd.zul'], + 'zmm' => ['application/vnd.handheld-entertainment+xml'], + 'zoo' => ['application/x-zoo'], + 'zpaq' => ['application/x-zpaq'], + 'zsav' => ['application/x-spss-sav', 'application/x-spss-savefile'], + 'zst' => ['application/zstd'], + 'zz' => ['application/zlib'], + ]; +} diff --git a/vendor/symfony/mime/MimeTypesInterface.php b/vendor/symfony/mime/MimeTypesInterface.php new file mode 100644 index 0000000..17d45ad --- /dev/null +++ b/vendor/symfony/mime/MimeTypesInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * @author Fabien Potencier + */ +interface MimeTypesInterface extends MimeTypeGuesserInterface +{ + /** + * Gets the extensions for the given MIME type in decreasing order of preference. + * + * @return string[] + */ + public function getExtensions(string $mimeType): array; + + /** + * Gets the MIME types for the given extension in decreasing order of preference. + * + * @return string[] + */ + public function getMimeTypes(string $ext): array; +} diff --git a/vendor/symfony/mime/Part/AbstractMultipartPart.php b/vendor/symfony/mime/Part/AbstractMultipartPart.php new file mode 100644 index 0000000..1da0ddf --- /dev/null +++ b/vendor/symfony/mime/Part/AbstractMultipartPart.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Fabien Potencier + */ +abstract class AbstractMultipartPart extends AbstractPart +{ + private ?string $boundary = null; + private array $parts = []; + + public function __construct(AbstractPart ...$parts) + { + parent::__construct(); + + foreach ($parts as $part) { + $this->parts[] = $part; + } + } + + /** + * @return AbstractPart[] + */ + public function getParts(): array + { + return $this->parts; + } + + public function getMediaType(): string + { + return 'multipart'; + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + $headers->setHeaderParameter('Content-Type', 'boundary', $this->getBoundary()); + + return $headers; + } + + public function bodyToString(): string + { + $parts = $this->getParts(); + $string = ''; + foreach ($parts as $part) { + $string .= '--'.$this->getBoundary()."\r\n".$part->toString()."\r\n"; + } + $string .= '--'.$this->getBoundary()."--\r\n"; + + return $string; + } + + public function bodyToIterable(): iterable + { + $parts = $this->getParts(); + foreach ($parts as $part) { + yield '--'.$this->getBoundary()."\r\n"; + yield from $part->toIterable(); + yield "\r\n"; + } + yield '--'.$this->getBoundary()."--\r\n"; + } + + public function asDebugString(): string + { + $str = parent::asDebugString(); + foreach ($this->getParts() as $part) { + $lines = explode("\n", $part->asDebugString()); + $str .= "\n â”” ".array_shift($lines); + foreach ($lines as $line) { + $str .= "\n |".$line; + } + } + + return $str; + } + + private function getBoundary(): string + { + return $this->boundary ??= strtr(base64_encode(random_bytes(6)), '+/', '-_'); + } +} diff --git a/vendor/symfony/mime/Part/AbstractPart.php b/vendor/symfony/mime/Part/AbstractPart.php new file mode 100644 index 0000000..130106d --- /dev/null +++ b/vendor/symfony/mime/Part/AbstractPart.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Fabien Potencier + */ +abstract class AbstractPart +{ + private Headers $headers; + + public function __construct() + { + $this->headers = new Headers(); + } + + public function getHeaders(): Headers + { + return $this->headers; + } + + public function getPreparedHeaders(): Headers + { + $headers = clone $this->headers; + $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype()); + + return $headers; + } + + public function toString(): string + { + return $this->getPreparedHeaders()->toString()."\r\n".$this->bodyToString(); + } + + public function toIterable(): iterable + { + yield $this->getPreparedHeaders()->toString(); + yield "\r\n"; + yield from $this->bodyToIterable(); + } + + public function asDebugString(): string + { + return $this->getMediaType().'/'.$this->getMediaSubtype(); + } + + abstract public function bodyToString(): string; + + abstract public function bodyToIterable(): iterable; + + abstract public function getMediaType(): string; + + abstract public function getMediaSubtype(): string; +} diff --git a/vendor/symfony/mime/Part/DataPart.php b/vendor/symfony/mime/Part/DataPart.php new file mode 100644 index 0000000..b1c581e --- /dev/null +++ b/vendor/symfony/mime/Part/DataPart.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Fabien Potencier + */ +class DataPart extends TextPart +{ + /** @internal */ + protected array $_parent; + + private ?string $filename = null; + private string $mediaType; + private ?string $cid = null; + + /** + * @param resource|string|File $body Use a File instance to defer loading the file until rendering + */ + public function __construct($body, ?string $filename = null, ?string $contentType = null, ?string $encoding = null) + { + if ($body instanceof File && !$filename) { + $filename = $body->getFilename(); + } + + $contentType ??= $body instanceof File ? $body->getContentType() : 'application/octet-stream'; + [$this->mediaType, $subtype] = explode('/', $contentType); + + parent::__construct($body, null, $subtype, $encoding); + + if (null !== $filename) { + $this->filename = $filename; + $this->setName($filename); + } + $this->setDisposition('attachment'); + } + + public static function fromPath(string $path, ?string $name = null, ?string $contentType = null): self + { + return new self(new File($path), $name, $contentType); + } + + /** + * @return $this + */ + public function asInline(): static + { + return $this->setDisposition('inline'); + } + + /** + * @return $this + */ + public function setContentId(string $cid): static + { + if (!str_contains($cid, '@')) { + throw new InvalidArgumentException(sprintf('Invalid cid "%s".', $cid)); + } + + $this->cid = $cid; + + return $this; + } + + public function getContentId(): string + { + return $this->cid ?: $this->cid = $this->generateContentId(); + } + + public function hasContentId(): bool + { + return null !== $this->cid; + } + + public function getMediaType(): string + { + return $this->mediaType; + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + + if (null !== $this->cid) { + $headers->setHeaderBody('Id', 'Content-ID', $this->cid); + } + + if (null !== $this->filename) { + $headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename); + } + + return $headers; + } + + public function asDebugString(): string + { + $str = parent::asDebugString(); + if (null !== $this->filename) { + $str .= ' filename: '.$this->filename; + } + + return $str; + } + + public function getFilename(): ?string + { + return $this->filename; + } + + public function getContentType(): string + { + return implode('/', [$this->getMediaType(), $this->getMediaSubtype()]); + } + + private function generateContentId(): string + { + return bin2hex(random_bytes(16)).'@symfony'; + } + + public function __sleep(): array + { + // converts the body to a string + parent::__sleep(); + + $this->_parent = []; + foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) { + $r = new \ReflectionProperty(TextPart::class, $name); + $this->_parent[$name] = $r->getValue($this); + } + $this->_headers = $this->getHeaders(); + + return ['_headers', '_parent', 'filename', 'mediaType']; + } + + public function __wakeup(): void + { + $r = new \ReflectionProperty(AbstractPart::class, 'headers'); + $r->setValue($this, $this->_headers); + unset($this->_headers); + + if (!\is_array($this->_parent)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) { + if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name]) && !$this->_parent[$name] instanceof File) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + $r = new \ReflectionProperty(TextPart::class, $name); + $r->setValue($this, $this->_parent[$name]); + } + unset($this->_parent); + } +} diff --git a/vendor/symfony/mime/Part/File.php b/vendor/symfony/mime/Part/File.php new file mode 100644 index 0000000..cd05a3d --- /dev/null +++ b/vendor/symfony/mime/Part/File.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\MimeTypes; + +/** + * @author Fabien Potencier + */ +class File +{ + private static MimeTypes $mimeTypes; + + public function __construct( + private string $path, + private ?string $filename = null, + ) { + } + + public function getPath(): string + { + return $this->path; + } + + public function getContentType(): string + { + $ext = strtolower(pathinfo($this->path, \PATHINFO_EXTENSION)); + self::$mimeTypes ??= new MimeTypes(); + + return self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream'; + } + + public function getSize(): int + { + return filesize($this->path); + } + + public function getFilename(): string + { + return $this->filename ??= basename($this->getPath()); + } +} diff --git a/vendor/symfony/mime/Part/MessagePart.php b/vendor/symfony/mime/Part/MessagePart.php new file mode 100644 index 0000000..6a4c494 --- /dev/null +++ b/vendor/symfony/mime/Part/MessagePart.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +/** + * @final + * + * @author Fabien Potencier + */ +class MessagePart extends DataPart +{ + public function __construct( + private RawMessage $message, + ) { + if ($message instanceof Message) { + $name = $message->getHeaders()->getHeaderBody('Subject').'.eml'; + } else { + $name = 'email.eml'; + } + parent::__construct('', $name); + } + + public function getMediaType(): string + { + return 'message'; + } + + public function getMediaSubtype(): string + { + return 'rfc822'; + } + + public function getBody(): string + { + return $this->message->toString(); + } + + public function bodyToString(): string + { + return $this->getBody(); + } + + public function bodyToIterable(): iterable + { + return $this->message->toIterable(); + } + + public function __sleep(): array + { + return ['message']; + } + + public function __wakeup(): void + { + $this->__construct($this->message); + } +} diff --git a/vendor/symfony/mime/Part/Multipart/AlternativePart.php b/vendor/symfony/mime/Part/Multipart/AlternativePart.php new file mode 100644 index 0000000..fd75423 --- /dev/null +++ b/vendor/symfony/mime/Part/Multipart/AlternativePart.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; + +/** + * @author Fabien Potencier + */ +final class AlternativePart extends AbstractMultipartPart +{ + public function getMediaSubtype(): string + { + return 'alternative'; + } +} diff --git a/vendor/symfony/mime/Part/Multipart/DigestPart.php b/vendor/symfony/mime/Part/Multipart/DigestPart.php new file mode 100644 index 0000000..27537f1 --- /dev/null +++ b/vendor/symfony/mime/Part/Multipart/DigestPart.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; +use Symfony\Component\Mime\Part\MessagePart; + +/** + * @author Fabien Potencier + */ +final class DigestPart extends AbstractMultipartPart +{ + public function __construct(MessagePart ...$parts) + { + parent::__construct(...$parts); + } + + public function getMediaSubtype(): string + { + return 'digest'; + } +} diff --git a/vendor/symfony/mime/Part/Multipart/FormDataPart.php b/vendor/symfony/mime/Part/Multipart/FormDataPart.php new file mode 100644 index 0000000..ed9c6e1 --- /dev/null +++ b/vendor/symfony/mime/Part/Multipart/FormDataPart.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Part\AbstractMultipartPart; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * Implements RFC 7578. + * + * @author Fabien Potencier + */ +final class FormDataPart extends AbstractMultipartPart +{ + /** + * @param array $fields + */ + public function __construct( + private array $fields = [], + ) { + parent::__construct(); + + // HTTP does not support \r\n in header values + $this->getHeaders()->setMaxLineLength(\PHP_INT_MAX); + } + + public function getMediaSubtype(): string + { + return 'form-data'; + } + + public function getParts(): array + { + return $this->prepareFields($this->fields); + } + + private function prepareFields(array $fields): array + { + $values = []; + + $prepare = function ($item, $key, $root = null) use (&$values, &$prepare) { + if (null === $root && \is_int($key) && \is_array($item)) { + if (1 !== \count($item)) { + throw new InvalidArgumentException(sprintf('Form field values with integer keys can only have one array element, the key being the field name and the value being the field value, %d provided.', \count($item))); + } + + $key = key($item); + $item = $item[$key]; + } + + $fieldName = null !== $root ? sprintf('%s[%s]', $root, $key) : $key; + + if (\is_array($item)) { + array_walk($item, $prepare, $fieldName); + + return; + } + + if (!\is_string($item) && !$item instanceof TextPart) { + throw new InvalidArgumentException(sprintf('The value of the form field "%s" can only be a string, an array, or an instance of TextPart, "%s" given.', $fieldName, get_debug_type($item))); + } + + $values[] = $this->preparePart($fieldName, $item); + }; + + array_walk($fields, $prepare); + + return $values; + } + + private function preparePart(string $name, string|TextPart $value): TextPart + { + if (\is_string($value)) { + return $this->configurePart($name, new TextPart($value, 'utf-8', 'plain', '8bit')); + } + + return $this->configurePart($name, $value); + } + + private function configurePart(string $name, TextPart $part): TextPart + { + static $r; + + $r ??= new \ReflectionProperty(TextPart::class, 'encoding'); + + $part->setDisposition('form-data'); + $part->setName($name); + // HTTP does not support \r\n in header values + $part->getHeaders()->setMaxLineLength(\PHP_INT_MAX); + $r->setValue($part, '8bit'); + + return $part; + } +} diff --git a/vendor/symfony/mime/Part/Multipart/MixedPart.php b/vendor/symfony/mime/Part/Multipart/MixedPart.php new file mode 100644 index 0000000..c8d7028 --- /dev/null +++ b/vendor/symfony/mime/Part/Multipart/MixedPart.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; + +/** + * @author Fabien Potencier + */ +final class MixedPart extends AbstractMultipartPart +{ + public function getMediaSubtype(): string + { + return 'mixed'; + } +} diff --git a/vendor/symfony/mime/Part/Multipart/RelatedPart.php b/vendor/symfony/mime/Part/Multipart/RelatedPart.php new file mode 100644 index 0000000..18ca9f1 --- /dev/null +++ b/vendor/symfony/mime/Part/Multipart/RelatedPart.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; +use Symfony\Component\Mime\Part\AbstractPart; + +/** + * @author Fabien Potencier + */ +final class RelatedPart extends AbstractMultipartPart +{ + public function __construct( + private AbstractPart $mainPart, + AbstractPart $part, + AbstractPart ...$parts, + ) { + $this->prepareParts($part, ...$parts); + + parent::__construct($part, ...$parts); + } + + public function getParts(): array + { + return array_merge([$this->mainPart], parent::getParts()); + } + + public function getMediaSubtype(): string + { + return 'related'; + } + + private function generateContentId(): string + { + return bin2hex(random_bytes(16)).'@symfony'; + } + + private function prepareParts(AbstractPart ...$parts): void + { + foreach ($parts as $part) { + if (!$part->getHeaders()->has('Content-ID')) { + $part->getHeaders()->setHeaderBody('Id', 'Content-ID', $this->generateContentId()); + } + } + } +} diff --git a/vendor/symfony/mime/Part/SMimePart.php b/vendor/symfony/mime/Part/SMimePart.php new file mode 100644 index 0000000..6444d31 --- /dev/null +++ b/vendor/symfony/mime/Part/SMimePart.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Sebastiaan Stok + */ +class SMimePart extends AbstractPart +{ + /** @internal */ + protected Headers $_headers; + + public function __construct( + private iterable|string $body, + private string $type, + private string $subtype, + private array $parameters, + ) { + parent::__construct(); + } + + public function getMediaType(): string + { + return $this->type; + } + + public function getMediaSubtype(): string + { + return $this->subtype; + } + + public function bodyToString(): string + { + if (\is_string($this->body)) { + return $this->body; + } + + $body = ''; + foreach ($this->body as $chunk) { + $body .= $chunk; + } + $this->body = $body; + + return $body; + } + + public function bodyToIterable(): iterable + { + if (\is_string($this->body)) { + yield $this->body; + + return; + } + + $body = ''; + foreach ($this->body as $chunk) { + $body .= $chunk; + yield $chunk; + } + $this->body = $body; + } + + public function getPreparedHeaders(): Headers + { + $headers = clone parent::getHeaders(); + + $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype()); + + foreach ($this->parameters as $name => $value) { + $headers->setHeaderParameter('Content-Type', $name, $value); + } + + return $headers; + } + + public function __sleep(): array + { + // convert iterables to strings for serialization + if (is_iterable($this->body)) { + $this->body = $this->bodyToString(); + } + + $this->_headers = $this->getHeaders(); + + return ['_headers', 'body', 'type', 'subtype', 'parameters']; + } + + public function __wakeup(): void + { + $r = new \ReflectionProperty(AbstractPart::class, 'headers'); + $r->setValue($this, $this->_headers); + unset($this->_headers); + } +} diff --git a/vendor/symfony/mime/Part/TextPart.php b/vendor/symfony/mime/Part/TextPart.php new file mode 100644 index 0000000..1054c25 --- /dev/null +++ b/vendor/symfony/mime/Part/TextPart.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Encoder\Base64ContentEncoder; +use Symfony\Component\Mime\Encoder\ContentEncoderInterface; +use Symfony\Component\Mime\Encoder\EightBitContentEncoder; +use Symfony\Component\Mime\Encoder\QpContentEncoder; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Fabien Potencier + */ +class TextPart extends AbstractPart +{ + /** @internal */ + protected Headers $_headers; + + private static array $encoders = []; + + /** @var resource|string|File */ + private $body; + private ?string $charset; + private string $subtype; + private ?string $disposition = null; + private ?string $name = null; + private string $encoding; + private ?bool $seekable = null; + + /** + * @param resource|string|File $body Use a File instance to defer loading the file until rendering + */ + public function __construct($body, ?string $charset = 'utf-8', string $subtype = 'plain', ?string $encoding = null) + { + parent::__construct(); + + if (!\is_string($body) && !\is_resource($body) && !$body instanceof File) { + throw new \TypeError(sprintf('The body of "%s" must be a string, a resource, or an instance of "%s" (got "%s").', self::class, File::class, get_debug_type($body))); + } + + if ($body instanceof File) { + $path = $body->getPath(); + if ((is_file($path) && !is_readable($path)) || is_dir($path)) { + throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path)); + } + } + + $this->body = $body; + $this->charset = $charset; + $this->subtype = $subtype; + $this->seekable = \is_resource($body) ? stream_get_meta_data($body)['seekable'] && 0 === fseek($body, 0, \SEEK_CUR) : null; + + if (null === $encoding) { + $this->encoding = $this->chooseEncoding(); + } else { + if ('quoted-printable' !== $encoding && 'base64' !== $encoding && '8bit' !== $encoding) { + throw new InvalidArgumentException(sprintf('The encoding must be one of "quoted-printable", "base64", or "8bit" ("%s" given).', $encoding)); + } + $this->encoding = $encoding; + } + } + + public function getMediaType(): string + { + return 'text'; + } + + public function getMediaSubtype(): string + { + return $this->subtype; + } + + /** + * @param string $disposition one of attachment, inline, or form-data + * + * @return $this + */ + public function setDisposition(string $disposition): static + { + $this->disposition = $disposition; + + return $this; + } + + /** + * @return ?string null or one of attachment, inline, or form-data + */ + public function getDisposition(): ?string + { + return $this->disposition; + } + + /** + * Sets the name of the file (used by FormDataPart). + * + * @return $this + */ + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + /** + * Gets the name of the file. + */ + public function getName(): ?string + { + return $this->name; + } + + public function getBody(): string + { + if ($this->body instanceof File) { + if (false === $ret = @file_get_contents($this->body->getPath())) { + throw new InvalidArgumentException(error_get_last()['message']); + } + + return $ret; + } + + if (null === $this->seekable) { + return $this->body; + } + + if ($this->seekable) { + rewind($this->body); + } + + return stream_get_contents($this->body) ?: ''; + } + + public function bodyToString(): string + { + return $this->getEncoder()->encodeString($this->getBody(), $this->charset); + } + + public function bodyToIterable(): iterable + { + if ($this->body instanceof File) { + $path = $this->body->getPath(); + if (false === $handle = @fopen($path, 'r', false)) { + throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path)); + } + + yield from $this->getEncoder()->encodeByteStream($handle); + } elseif (null !== $this->seekable) { + if ($this->seekable) { + rewind($this->body); + } + yield from $this->getEncoder()->encodeByteStream($this->body); + } else { + yield $this->getEncoder()->encodeString($this->body); + } + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + + $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype()); + if ($this->charset) { + $headers->setHeaderParameter('Content-Type', 'charset', $this->charset); + } + if ($this->name && 'form-data' !== $this->disposition) { + $headers->setHeaderParameter('Content-Type', 'name', $this->name); + } + $headers->setHeaderBody('Text', 'Content-Transfer-Encoding', $this->encoding); + + if (!$headers->has('Content-Disposition') && null !== $this->disposition) { + $headers->setHeaderBody('Parameterized', 'Content-Disposition', $this->disposition); + if ($this->name) { + $headers->setHeaderParameter('Content-Disposition', 'name', $this->name); + } + } + + return $headers; + } + + public function asDebugString(): string + { + $str = parent::asDebugString(); + if (null !== $this->charset) { + $str .= ' charset: '.$this->charset; + } + if (null !== $this->disposition) { + $str .= ' disposition: '.$this->disposition; + } + + return $str; + } + + private function getEncoder(): ContentEncoderInterface + { + if ('8bit' === $this->encoding) { + return self::$encoders[$this->encoding] ??= new EightBitContentEncoder(); + } + + if ('quoted-printable' === $this->encoding) { + return self::$encoders[$this->encoding] ??= new QpContentEncoder(); + } + + return self::$encoders[$this->encoding] ??= new Base64ContentEncoder(); + } + + private function chooseEncoding(): string + { + if (null === $this->charset) { + return 'base64'; + } + + return 'quoted-printable'; + } + + public function __sleep(): array + { + // convert resources to strings for serialization + if (null !== $this->seekable) { + $this->body = $this->getBody(); + $this->seekable = null; + } + + $this->_headers = $this->getHeaders(); + + return ['_headers', 'body', 'charset', 'subtype', 'disposition', 'name', 'encoding']; + } + + public function __wakeup(): void + { + $r = new \ReflectionProperty(AbstractPart::class, 'headers'); + $r->setValue($this, $this->_headers); + unset($this->_headers); + } +} diff --git a/vendor/symfony/mime/README.md b/vendor/symfony/mime/README.md new file mode 100644 index 0000000..8e4d5c7 --- /dev/null +++ b/vendor/symfony/mime/README.md @@ -0,0 +1,13 @@ +MIME Component +============== + +The MIME component allows manipulating MIME messages. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/mime.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/mime/RawMessage.php b/vendor/symfony/mime/RawMessage.php new file mode 100644 index 0000000..f791211 --- /dev/null +++ b/vendor/symfony/mime/RawMessage.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; + +/** + * @author Fabien Potencier + */ +class RawMessage +{ + private bool $isGeneratorClosed; + + /** + * @param iterable|string|resource $message + */ + public function __construct( + private $message, + ) { + } + + public function __destruct() + { + if (\is_resource($this->message)) { + fclose($this->message); + } + } + + public function toString(): string + { + if (\is_string($this->message)) { + return $this->message; + } + + if (\is_resource($this->message)) { + return stream_get_contents($this->message, -1, 0); + } + + $message = ''; + foreach ($this->message as $chunk) { + $message .= $chunk; + } + + return $this->message = $message; + } + + public function toIterable(): iterable + { + if ($this->isGeneratorClosed ?? false) { + throw new LogicException('Unable to send the email as its generator is already closed.'); + } + + if (\is_string($this->message)) { + yield $this->message; + + return; + } + + if (\is_resource($this->message)) { + rewind($this->message); + while ($line = fgets($this->message)) { + yield $line; + } + + return; + } + + if ($this->message instanceof \Generator) { + $message = fopen('php://temp', 'w+'); + foreach ($this->message as $chunk) { + fwrite($message, $chunk); + yield $chunk; + } + $this->isGeneratorClosed = !$this->message->valid(); + $this->message = $message; + + return; + } + + foreach ($this->message as $chunk) { + yield $chunk; + } + } + + /** + * @throws LogicException if the message is not valid + */ + public function ensureValidity(): void + { + } + + public function __serialize(): array + { + return [$this->toString()]; + } + + public function __unserialize(array $data): void + { + [$this->message] = $data; + } +} diff --git a/vendor/symfony/mime/Test/Constraint/EmailAddressContains.php b/vendor/symfony/mime/Test/Constraint/EmailAddressContains.php new file mode 100644 index 0000000..6e1e250 --- /dev/null +++ b/vendor/symfony/mime/Test/Constraint/EmailAddressContains.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Header\MailboxHeader; +use Symfony\Component\Mime\Header\MailboxListHeader; +use Symfony\Component\Mime\RawMessage; + +final class EmailAddressContains extends Constraint +{ + public function __construct( + private string $headerName, + private string $expectedValue, + ) { + } + + public function toString(): string + { + return sprintf('contains address "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === $message::class) { + throw new \LogicException('Unable to test a message address on a RawMessage instance.'); + } + + $header = $message->getHeaders()->get($this->headerName); + if ($header instanceof MailboxHeader) { + return $this->expectedValue === $header->getAddress()->getAddress(); + } elseif ($header instanceof MailboxListHeader) { + foreach ($header->getAddresses() as $address) { + if ($this->expectedValue === $address->getAddress()) { + return true; + } + } + + return false; + } + + throw new \LogicException('Unable to test a message address on a non-address header.'); + } + + /** + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return sprintf('the Email %s (value is %s)', $this->toString(), $message->getHeaders()->get($this->headerName)->getBodyAsString()); + } +} diff --git a/vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php b/vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php new file mode 100644 index 0000000..aa29a05 --- /dev/null +++ b/vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +final class EmailAttachmentCount extends Constraint +{ + public function __construct( + private int $expectedValue, + private ?string $transport = null, + ) { + } + + public function toString(): string + { + return sprintf('has sent "%d" attachment(s)', $this->expectedValue); + } + + /** + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === $message::class || Message::class === $message::class) { + throw new \LogicException('Unable to test a message attachment on a RawMessage or Message instance.'); + } + + return $this->expectedValue === \count($message->getAttachments()); + } + + /** + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return 'the Email '.$this->toString(); + } +} diff --git a/vendor/symfony/mime/Test/Constraint/EmailHasHeader.php b/vendor/symfony/mime/Test/Constraint/EmailHasHeader.php new file mode 100644 index 0000000..378426e --- /dev/null +++ b/vendor/symfony/mime/Test/Constraint/EmailHasHeader.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\RawMessage; + +final class EmailHasHeader extends Constraint +{ + public function __construct( + private string $headerName, + ) { + } + + public function toString(): string + { + return sprintf('has header "%s"', $this->headerName); + } + + /** + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === $message::class) { + throw new \LogicException('Unable to test a message header on a RawMessage instance.'); + } + + return $message->getHeaders()->has($this->headerName); + } + + /** + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return 'the Email '.$this->toString(); + } +} diff --git a/vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php b/vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php new file mode 100644 index 0000000..e79ddf8 --- /dev/null +++ b/vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Header\UnstructuredHeader; +use Symfony\Component\Mime\RawMessage; + +final class EmailHeaderSame extends Constraint +{ + public function __construct( + private string $headerName, + private string $expectedValue, + ) { + } + + public function toString(): string + { + return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === $message::class) { + throw new \LogicException('Unable to test a message header on a RawMessage instance.'); + } + + return $this->expectedValue === $this->getHeaderValue($message); + } + + /** + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return sprintf('the Email %s (value is %s)', $this->toString(), $this->getHeaderValue($message) ?? 'null'); + } + + private function getHeaderValue($message): ?string + { + if (null === $header = $message->getHeaders()->get($this->headerName)) { + return null; + } + + return $header instanceof UnstructuredHeader ? $header->getValue() : $header->getBodyAsString(); + } +} diff --git a/vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php b/vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php new file mode 100644 index 0000000..fa4f053 --- /dev/null +++ b/vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +final class EmailHtmlBodyContains extends Constraint +{ + public function __construct( + private string $expectedText, + ) { + } + + public function toString(): string + { + return sprintf('contains "%s"', $this->expectedText); + } + + /** + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === $message::class || Message::class === $message::class) { + throw new \LogicException('Unable to test a message HTML body on a RawMessage or Message instance.'); + } + + return str_contains($message->getHtmlBody(), $this->expectedText); + } + + /** + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return 'the Email HTML body '.$this->toString(); + } +} diff --git a/vendor/symfony/mime/Test/Constraint/EmailSubjectContains.php b/vendor/symfony/mime/Test/Constraint/EmailSubjectContains.php new file mode 100644 index 0000000..310bc1d --- /dev/null +++ b/vendor/symfony/mime/Test/Constraint/EmailSubjectContains.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Email; + +final class EmailSubjectContains extends Constraint +{ + public function __construct( + private readonly string $expectedSubjectValue, + ) { + } + + public function toString(): string + { + return sprintf('contains subject with value "%s"', $this->expectedSubjectValue); + } + + protected function matches($other): bool + { + if (!$other instanceof Email) { + throw new \LogicException('Can only test a message subject on an Email instance.'); + } + + return str_contains((string) $other->getSubject(), $this->expectedSubjectValue); + } + + protected function failureDescription($other): string + { + $message = 'The email subject '.$this->toString(); + if ($other instanceof Email) { + $message .= sprintf('. The subject was: "%s"', $other->getSubject() ?? ''); + } + + return $message; + } +} diff --git a/vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php b/vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php new file mode 100644 index 0000000..79b1847 --- /dev/null +++ b/vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +final class EmailTextBodyContains extends Constraint +{ + public function __construct( + private string $expectedText, + ) { + } + + public function toString(): string + { + return sprintf('contains "%s"', $this->expectedText); + } + + /** + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === $message::class || Message::class === $message::class) { + throw new \LogicException('Unable to test a message text body on a RawMessage or Message instance.'); + } + + return str_contains($message->getTextBody(), $this->expectedText); + } + + /** + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return 'the Email text body '.$this->toString(); + } +} diff --git a/vendor/symfony/mime/composer.json b/vendor/symfony/mime/composer.json new file mode 100644 index 0000000..5304bdf --- /dev/null +++ b/vendor/symfony/mime/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/mime", + "type": "library", + "description": "Allows manipulating MIME messages", + "keywords": ["mime", "mime-type"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Mime\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/options-resolver/CHANGELOG.md b/vendor/symfony/options-resolver/CHANGELOG.md new file mode 100644 index 0000000..f4de6d0 --- /dev/null +++ b/vendor/symfony/options-resolver/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG +========= + +6.4 +--- + +* Improve message with full path on invalid type in nested option + +6.3 +--- + + * Add `OptionsResolver::setIgnoreUndefined()` and `OptionConfigurator::ignoreUndefined()` to ignore not defined options while resolving + +6.0 +--- + + * Remove `OptionsResolverIntrospector::getDeprecationMessage()` + +5.3 +--- + + * Add prototype definition for nested options + +5.1.0 +----- + + * added fluent configuration of options using `OptionResolver::define()` + * added `setInfo()` and `getInfo()` methods + * updated the signature of method `OptionsResolver::setDeprecated()` to `OptionsResolver::setDeprecation(string $option, string $package, string $version, $message)` + * deprecated `OptionsResolverIntrospector::getDeprecationMessage()`, use `OptionsResolverIntrospector::getDeprecation()` instead + +5.0.0 +----- + + * added argument `$triggerDeprecation` to `OptionsResolver::offsetGet()` + +4.3.0 +----- + + * added `OptionsResolver::addNormalizer` method + +4.2.0 +----- + + * added support for nested options definition + * added `setDeprecated` and `isDeprecated` methods + +3.4.0 +----- + + * added `OptionsResolverIntrospector` to inspect options definitions inside an `OptionsResolver` instance + * added array of types support in allowed types (e.g int[]) + +2.6.0 +----- + + * deprecated OptionsResolverInterface + * [BC BREAK] removed "array" type hint from OptionsResolverInterface methods + setRequired(), setAllowedValues(), addAllowedValues(), setAllowedTypes() and + addAllowedTypes() + * added OptionsResolver::setDefault() + * added OptionsResolver::hasDefault() + * added OptionsResolver::setNormalizer() + * added OptionsResolver::isRequired() + * added OptionsResolver::getRequiredOptions() + * added OptionsResolver::isMissing() + * added OptionsResolver::getMissingOptions() + * added OptionsResolver::setDefined() + * added OptionsResolver::isDefined() + * added OptionsResolver::getDefinedOptions() + * added OptionsResolver::remove() + * added OptionsResolver::clear() + * deprecated OptionsResolver::replaceDefaults() + * deprecated OptionsResolver::setOptional() in favor of setDefined() + * deprecated OptionsResolver::isKnown() in favor of isDefined() + * [BC BREAK] OptionsResolver::isRequired() returns true now if a required + option has a default value set + * [BC BREAK] merged Options into OptionsResolver and turned Options into an + interface + * deprecated Options::overload() (now in OptionsResolver) + * deprecated Options::set() (now in OptionsResolver) + * deprecated Options::get() (now in OptionsResolver) + * deprecated Options::has() (now in OptionsResolver) + * deprecated Options::replace() (now in OptionsResolver) + * [BC BREAK] Options::get() (now in OptionsResolver) can only be used within + lazy option/normalizer closures now + * [BC BREAK] removed Traversable interface from Options since using within + lazy option/normalizer closures resulted in exceptions + * [BC BREAK] removed Options::all() since using within lazy option/normalizer + closures resulted in exceptions + * [BC BREAK] OptionDefinitionException now extends LogicException instead of + RuntimeException + * [BC BREAK] normalizers are not executed anymore for unset options + * normalizers are executed after validating the options now + * [BC BREAK] an UndefinedOptionsException is now thrown instead of an + InvalidOptionsException when non-existing options are passed diff --git a/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php new file mode 100644 index 0000000..f55ab14 --- /dev/null +++ b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Debug; + +use Symfony\Component\OptionsResolver\Exception\NoConfigurationException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Maxime Steinhausser + * + * @final + */ +class OptionsResolverIntrospector +{ + private \Closure $get; + + public function __construct(OptionsResolver $optionsResolver) + { + $this->get = \Closure::bind(function ($property, $option, $message) { + /** @var OptionsResolver $this */ + if (!$this->isDefined($option)) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist.', $option)); + } + + if (!\array_key_exists($option, $this->{$property})) { + throw new NoConfigurationException($message); + } + + return $this->{$property}[$option]; + }, $optionsResolver, $optionsResolver); + } + + /** + * @throws NoConfigurationException on no configured value + */ + public function getDefault(string $option): mixed + { + return ($this->get)('defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); + } + + /** + * @return \Closure[] + * + * @throws NoConfigurationException on no configured closures + */ + public function getLazyClosures(string $option): array + { + return ($this->get)('lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); + } + + /** + * @return string[] + * + * @throws NoConfigurationException on no configured types + */ + public function getAllowedTypes(string $option): array + { + return ($this->get)('allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); + } + + /** + * @return mixed[] + * + * @throws NoConfigurationException on no configured values + */ + public function getAllowedValues(string $option): array + { + return ($this->get)('allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); + } + + /** + * @throws NoConfigurationException on no configured normalizer + */ + public function getNormalizer(string $option): \Closure + { + return current($this->getNormalizers($option)); + } + + /** + * @throws NoConfigurationException when no normalizer is configured + */ + public function getNormalizers(string $option): array + { + return ($this->get)('normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); + } + + /** + * @throws NoConfigurationException on no configured deprecation + */ + public function getDeprecation(string $option): array + { + return ($this->get)('deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option)); + } +} diff --git a/vendor/symfony/options-resolver/Exception/AccessException.php b/vendor/symfony/options-resolver/Exception/AccessException.php new file mode 100644 index 0000000..c12b680 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/AccessException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option outside of or write it inside of + * {@link \Symfony\Component\OptionsResolver\Options::resolve()}. + * + * @author Bernhard Schussek + */ +class AccessException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/ExceptionInterface.php b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php new file mode 100644 index 0000000..ea99d05 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Marker interface for all exceptions thrown by the OptionsResolver component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..6d421d6 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when an argument is invalid. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php new file mode 100644 index 0000000..6fd4f12 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when the value of an option does not match its validation rules. + * + * You should make sure a valid value is passed to the option. + * + * @author Bernhard Schussek + */ +class InvalidOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/MissingOptionsException.php b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php new file mode 100644 index 0000000..faa487f --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Exception thrown when a required option is missing. + * + * Add the option to the passed options array. + * + * @author Bernhard Schussek + */ +class MissingOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoConfigurationException.php b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php new file mode 100644 index 0000000..6693ec1 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; + +/** + * Thrown when trying to introspect an option definition property + * for which no value was configured inside the OptionsResolver instance. + * + * @see OptionsResolverIntrospector + * + * @author Maxime Steinhausser + */ +class NoConfigurationException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php new file mode 100644 index 0000000..4c3280f --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option that has no value set. + * + * When accessing optional options from within a lazy option or normalizer you should first + * check whether the optional option is set. You can do this with `isset($options['optional'])`. + * In contrast to the {@link UndefinedOptionsException}, this is a runtime exception that can + * occur when evaluating lazy options. + * + * @author Tobias Schultze + */ +class NoSuchOptionException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php new file mode 100644 index 0000000..e8e339d --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when two lazy options have a cyclic dependency. + * + * @author Bernhard Schussek + */ +class OptionDefinitionException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php new file mode 100644 index 0000000..6ca3fce --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Exception thrown when an undefined option is passed. + * + * You should remove the options in question from your code or define them + * beforehand. + * + * @author Bernhard Schussek + */ +class UndefinedOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/LICENSE b/vendor/symfony/options-resolver/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/options-resolver/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/options-resolver/OptionConfigurator.php b/vendor/symfony/options-resolver/OptionConfigurator.php new file mode 100644 index 0000000..e708c2c --- /dev/null +++ b/vendor/symfony/options-resolver/OptionConfigurator.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; + +final class OptionConfigurator +{ + public function __construct( + private string $name, + private OptionsResolver $resolver, + ) { + $this->resolver->setDefined($name); + } + + /** + * Adds allowed types for this option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function allowedTypes(string ...$types): static + { + $this->resolver->setAllowedTypes($this->name, $types); + + return $this; + } + + /** + * Sets allowed values for this option. + * + * @param mixed ...$values One or more acceptable values/closures + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function allowedValues(mixed ...$values): static + { + $this->resolver->setAllowedValues($this->name, $values); + + return $this; + } + + /** + * Sets the default value for this option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function default(mixed $value): static + { + $this->resolver->setDefault($this->name, $value); + + return $this; + } + + /** + * Defines an option configurator with the given name. + */ + public function define(string $option): self + { + return $this->resolver->define($option); + } + + /** + * Marks this option as deprecated. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string|\Closure $message The deprecation message to use + * + * @return $this + */ + public function deprecated(string $package, string $version, string|\Closure $message = 'The option "%name%" is deprecated.'): static + { + $this->resolver->setDeprecated($this->name, $package, $version, $message); + + return $this; + } + + /** + * Sets the normalizer for this option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function normalize(\Closure $normalizer): static + { + $this->resolver->setNormalizer($this->name, $normalizer); + + return $this; + } + + /** + * Marks this option as required. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function required(): static + { + $this->resolver->setRequired($this->name); + + return $this; + } + + /** + * Sets an info message for an option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function info(string $info): static + { + $this->resolver->setInfo($this->name, $info); + + return $this; + } + + /** + * Sets whether ignore undefined options. + * + * @return $this + */ + public function ignoreUndefined(bool $ignore = true): static + { + $this->resolver->setIgnoreUndefined($ignore); + + return $this; + } +} diff --git a/vendor/symfony/options-resolver/Options.php b/vendor/symfony/options-resolver/Options.php new file mode 100644 index 0000000..d444ec4 --- /dev/null +++ b/vendor/symfony/options-resolver/Options.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +/** + * Contains resolved option values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + */ +interface Options extends \ArrayAccess, \Countable +{ +} diff --git a/vendor/symfony/options-resolver/OptionsResolver.php b/vendor/symfony/options-resolver/OptionsResolver.php new file mode 100644 index 0000000..fc378ce --- /dev/null +++ b/vendor/symfony/options-resolver/OptionsResolver.php @@ -0,0 +1,1317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; +use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; +use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException; +use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; + +/** + * Validates options and merges them with default values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + */ +class OptionsResolver implements Options +{ + private const VALIDATION_FUNCTIONS = [ + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'int' => 'is_int', + 'integer' => 'is_int', + 'long' => 'is_int', + 'float' => 'is_float', + 'double' => 'is_float', + 'real' => 'is_float', + 'numeric' => 'is_numeric', + 'string' => 'is_string', + 'scalar' => 'is_scalar', + 'array' => 'is_array', + 'iterable' => 'is_iterable', + 'countable' => 'is_countable', + 'callable' => 'is_callable', + 'object' => 'is_object', + 'resource' => 'is_resource', + ]; + + /** + * The names of all defined options. + */ + private array $defined = []; + + /** + * The default option values. + */ + private array $defaults = []; + + /** + * A list of closure for nested options. + * + * @var \Closure[][] + */ + private array $nested = []; + + /** + * The names of required options. + */ + private array $required = []; + + /** + * The resolved option values. + */ + private array $resolved = []; + + /** + * A list of normalizer closures. + * + * @var \Closure[][] + */ + private array $normalizers = []; + + /** + * A list of accepted values for each option. + */ + private array $allowedValues = []; + + /** + * A list of accepted types for each option. + */ + private array $allowedTypes = []; + + /** + * A list of info messages for each option. + */ + private array $info = []; + + /** + * A list of closures for evaluating lazy options. + */ + private array $lazy = []; + + /** + * A list of lazy options whose closure is currently being called. + * + * This list helps detecting circular dependencies between lazy options. + */ + private array $calling = []; + + /** + * A list of deprecated options. + */ + private array $deprecated = []; + + /** + * The list of options provided by the user. + */ + private array $given = []; + + /** + * Whether the instance is locked for reading. + * + * Once locked, the options cannot be changed anymore. This is + * necessary in order to avoid inconsistencies during the resolving + * process. If any option is changed after being read, all evaluated + * lazy options that depend on this option would become invalid. + */ + private bool $locked = false; + + private array $parentsOptions = []; + + /** + * Whether the whole options definition is marked as array prototype. + */ + private ?bool $prototype = null; + + /** + * The prototype array's index that is being read. + */ + private int|string|null $prototypeIndex = null; + + /** + * Whether to ignore undefined options. + */ + private bool $ignoreUndefined = false; + + /** + * Sets the default value of a given option. + * + * If the default value should be set based on other options, you can pass + * a closure with the following signature: + * + * function (Options $options) { + * // ... + * } + * + * The closure will be evaluated when {@link resolve()} is called. The + * closure has access to the resolved values of other options through the + * passed {@link Options} instance: + * + * function (Options $options) { + * if (isset($options['port'])) { + * // ... + * } + * } + * + * If you want to access the previously set default value, add a second + * argument to the closure's signature: + * + * $options->setDefault('name', 'Default Name'); + * + * $options->setDefault('name', function (Options $options, $previousValue) { + * // 'Default Name' === $previousValue + * }); + * + * This is mostly useful if the configuration of the {@link Options} object + * is spread across different locations of your code, such as base and + * sub-classes. + * + * If you want to define nested options, you can pass a closure with the + * following signature: + * + * $options->setDefault('database', function (OptionsResolver $resolver) { + * $resolver->setDefined(['dbname', 'host', 'port', 'user', 'pass']); + * } + * + * To get access to the parent options, add a second argument to the closure's + * signature: + * + * function (OptionsResolver $resolver, Options $parent) { + * // 'default' === $parent['connection'] + * } + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefault(string $option, mixed $value): static + { + // Setting is not possible once resolving starts, because then lazy + // options could manipulate the state of the object, leading to + // inconsistent results. + if ($this->locked) { + throw new AccessException('Default values cannot be set from a lazy option or normalizer.'); + } + + // If an option is a closure that should be evaluated lazily, store it + // in the "lazy" property. + if ($value instanceof \Closure) { + $reflClosure = new \ReflectionFunction($value); + $params = $reflClosure->getParameters(); + + if (isset($params[0]) && Options::class === $this->getParameterClassName($params[0])) { + // Initialize the option if no previous value exists + if (!isset($this->defaults[$option])) { + $this->defaults[$option] = null; + } + + // Ignore previous lazy options if the closure has no second parameter + if (!isset($this->lazy[$option]) || !isset($params[1])) { + $this->lazy[$option] = []; + } + + // Store closure for later evaluation + $this->lazy[$option][] = $value; + $this->defined[$option] = true; + + // Make sure the option is processed and is not nested anymore + unset($this->resolved[$option], $this->nested[$option]); + + return $this; + } + + if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) { + // Store closure for later evaluation + $this->nested[$option][] = $value; + $this->defaults[$option] = []; + $this->defined[$option] = true; + + // Make sure the option is processed and is not lazy anymore + unset($this->resolved[$option], $this->lazy[$option]); + + return $this; + } + } + + // This option is not lazy nor nested anymore + unset($this->lazy[$option], $this->nested[$option]); + + // Yet undefined options can be marked as resolved, because we only need + // to resolve options with lazy closures, normalizers or validation + // rules, none of which can exist for undefined options + // If the option was resolved before, update the resolved value + if (!isset($this->defined[$option]) || \array_key_exists($option, $this->resolved)) { + $this->resolved[$option] = $value; + } + + $this->defaults[$option] = $value; + $this->defined[$option] = true; + + return $this; + } + + /** + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefaults(array $defaults): static + { + foreach ($defaults as $option => $value) { + $this->setDefault($option, $value); + } + + return $this; + } + + /** + * Returns whether a default value is set for an option. + * + * Returns true if {@link setDefault()} was called for this option. + * An option is also considered set if it was set to null. + */ + public function hasDefault(string $option): bool + { + return \array_key_exists($option, $this->defaults); + } + + /** + * Marks one or more options as required. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setRequired(string|array $optionNames): static + { + if ($this->locked) { + throw new AccessException('Options cannot be made required from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + $this->required[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is required. + * + * An option is required if it was passed to {@link setRequired()}. + */ + public function isRequired(string $option): bool + { + return isset($this->required[$option]); + } + + /** + * Returns the names of all required options. + * + * @return string[] + * + * @see isRequired() + */ + public function getRequiredOptions(): array + { + return array_keys($this->required); + } + + /** + * Returns whether an option is missing a default value. + * + * An option is missing if it was passed to {@link setRequired()}, but not + * to {@link setDefault()}. This option must be passed explicitly to + * {@link resolve()}, otherwise an exception will be thrown. + */ + public function isMissing(string $option): bool + { + return isset($this->required[$option]) && !\array_key_exists($option, $this->defaults); + } + + /** + * Returns the names of all options missing a default value. + * + * @return string[] + */ + public function getMissingOptions(): array + { + return array_keys(array_diff_key($this->required, $this->defaults)); + } + + /** + * Defines a valid option name. + * + * Defines an option name without setting a default value. The option will + * be accepted when passed to {@link resolve()}. When not passed, the + * option will not be included in the resolved options. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefined(string|array $optionNames): static + { + if ($this->locked) { + throw new AccessException('Options cannot be defined from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is defined. + * + * Returns true for any option passed to {@link setDefault()}, + * {@link setRequired()} or {@link setDefined()}. + */ + public function isDefined(string $option): bool + { + return isset($this->defined[$option]); + } + + /** + * Returns the names of all defined options. + * + * @return string[] + * + * @see isDefined() + */ + public function getDefinedOptions(): array + { + return array_keys($this->defined); + } + + public function isNested(string $option): bool + { + return isset($this->nested[$option]); + } + + /** + * Deprecates an option, allowed types or values. + * + * Instead of passing the message, you may also pass a closure with the + * following signature: + * + * function (Options $options, $value): string { + * // ... + * } + * + * The closure receives the value as argument and should return a string. + * Return an empty string to ignore the option deprecation. + * + * The closure is invoked when {@link resolve()} is called. The parameter + * passed to the closure is the value of the option after validating it + * and before normalizing it. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string|\Closure $message The deprecation message to use + * + * @return $this + */ + public function setDeprecated(string $option, string $package, string $version, string|\Closure $message = 'The option "%name%" is deprecated.'): static + { + if ($this->locked) { + throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if (!\is_string($message) && !$message instanceof \Closure) { + throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', get_debug_type($message))); + } + + // ignore if empty string + if ('' === $message) { + return $this; + } + + $this->deprecated[$option] = [ + 'package' => $package, + 'version' => $version, + 'message' => $message, + ]; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + public function isDeprecated(string $option): bool + { + return isset($this->deprecated[$option]); + } + + /** + * Sets the normalizer for an option. + * + * The normalizer should be a closure with the following signature: + * + * function (Options $options, $value) { + * // ... + * } + * + * The closure is invoked when {@link resolve()} is called. The closure + * has access to the resolved values of other options through the passed + * {@link Options} instance. + * + * The second parameter passed to the closure is the value of + * the option. + * + * The resolved option value is set to the return value of the closure. + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setNormalizer(string $option, \Closure $normalizer): static + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->normalizers[$option] = [$normalizer]; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds a normalizer for an option. + * + * The normalizer should be a closure with the following signature: + * + * function (Options $options, $value): mixed { + * // ... + * } + * + * The closure is invoked when {@link resolve()} is called. The closure + * has access to the resolved values of other options through the passed + * {@link Options} instance. + * + * The second parameter passed to the closure is the value of + * the option. + * + * The resolved option value is set to the return value of the closure. + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addNormalizer(string $option, \Closure $normalizer, bool $forcePrepend = false): static + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if ($forcePrepend) { + $this->normalizers[$option] ??= []; + array_unshift($this->normalizers[$option], $normalizer); + } else { + $this->normalizers[$option][] = $normalizer; + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed values for an option. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedValues(string $option, mixed $allowedValues): static + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues]; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed values for an option. + * + * The values are merged with the allowed values defined previously. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedValues(string $option, mixed $allowedValues): static + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if (!\is_array($allowedValues)) { + $allowedValues = [$allowedValues]; + } + + if (!isset($this->allowedValues[$option])) { + $this->allowedValues[$option] = $allowedValues; + } else { + $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed types for an option. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedTypes(string $option, string|array $allowedTypes): static + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->allowedTypes[$option] = (array) $allowedTypes; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed types for an option. + * + * The types are merged with the allowed types defined previously. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedTypes(string $option, string|array $allowedTypes): static + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if (!isset($this->allowedTypes[$option])) { + $this->allowedTypes[$option] = (array) $allowedTypes; + } else { + $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Defines an option configurator with the given name. + */ + public function define(string $option): OptionConfigurator + { + if (isset($this->defined[$option])) { + throw new OptionDefinitionException(sprintf('The option "%s" is already defined.', $option)); + } + + return new OptionConfigurator($option, $this); + } + + /** + * Sets an info message for an option. + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setInfo(string $option, string $info): static + { + if ($this->locked) { + throw new AccessException('The Info message cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->info[$option] = $info; + + return $this; + } + + /** + * Gets the info message for an option. + */ + public function getInfo(string $option): ?string + { + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + return $this->info[$option] ?? null; + } + + /** + * Marks the whole options definition as array prototype. + * + * @return $this + * + * @throws AccessException If called from a lazy option, a normalizer or a root definition + */ + public function setPrototype(bool $prototype): static + { + if ($this->locked) { + throw new AccessException('The prototype property cannot be set from a lazy option or normalizer.'); + } + + if (null === $this->prototype && $prototype) { + throw new AccessException('The prototype property cannot be set from a root definition.'); + } + + $this->prototype = $prototype; + + return $this; + } + + public function isPrototype(): bool + { + return $this->prototype ?? false; + } + + /** + * Removes the option with the given name. + * + * Undefined options are ignored. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function remove(string|array $optionNames): static + { + if ($this->locked) { + throw new AccessException('Options cannot be removed from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]); + unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option], $this->info[$option]); + } + + return $this; + } + + /** + * Removes all options. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function clear(): static + { + if ($this->locked) { + throw new AccessException('Options cannot be cleared from a lazy option or normalizer.'); + } + + $this->defined = []; + $this->defaults = []; + $this->nested = []; + $this->required = []; + $this->resolved = []; + $this->lazy = []; + $this->normalizers = []; + $this->allowedTypes = []; + $this->allowedValues = []; + $this->deprecated = []; + $this->info = []; + + return $this; + } + + /** + * Merges options with the default values stored in the container and + * validates them. + * + * Exceptions are thrown if: + * + * - Undefined options are passed; + * - Required options are missing; + * - Options have invalid types; + * - Options have invalid values. + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the + * specified validation rules + * @throws MissingOptionsException If a required option is missing + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + * @throws NoSuchOptionException If a lazy option reads an unavailable option + * @throws AccessException If called from a lazy option or normalizer + */ + public function resolve(array $options = []): array + { + if ($this->locked) { + throw new AccessException('Options cannot be resolved from a lazy option or normalizer.'); + } + + // Allow this method to be called multiple times + $clone = clone $this; + + // Make sure that no unknown options are passed + $diff = $this->ignoreUndefined ? [] : array_diff_key($options, $clone->defined); + + if (\count($diff) > 0) { + ksort($clone->defined); + ksort($diff); + + throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', $this->formatOptions(array_keys($diff)), implode('", "', array_keys($clone->defined)))); + } + + // Override options set by the user + foreach ($options as $option => $value) { + if ($this->ignoreUndefined && !isset($clone->defined[$option])) { + continue; + } + + $clone->given[$option] = true; + $clone->defaults[$option] = $value; + unset($clone->resolved[$option], $clone->lazy[$option]); + } + + // Check whether any required option is missing + $diff = array_diff_key($clone->required, $clone->defaults); + + if (\count($diff) > 0) { + ksort($diff); + + throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptions(array_keys($diff)))); + } + + // Lock the container + $clone->locked = true; + + // Now process the individual options. Use offsetGet(), which resolves + // the option itself and any options that the option depends on + foreach ($clone->defaults as $option => $_) { + $clone->offsetGet($option); + } + + return $clone->resolved; + } + + /** + * Returns the resolved value of an option. + * + * @param bool $triggerDeprecation Whether to trigger the deprecation or not (true by default) + * + * @throws AccessException If accessing this method outside of + * {@link resolve()} + * @throws NoSuchOptionException If the option is not set + * @throws InvalidOptionsException If the option doesn't fulfill the + * specified validation rules + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + */ + public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + // Shortcut for resolved options + if (isset($this->resolved[$option]) || \array_key_exists($option, $this->resolved)) { + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option]['message'])) { + trigger_deprecation($this->deprecated[$option]['package'], $this->deprecated[$option]['version'], strtr($this->deprecated[$option]['message'], ['%name%' => $option])); + } + + return $this->resolved[$option]; + } + + // Check whether the option is set at all + if (!isset($this->defaults[$option]) && !\array_key_exists($option, $this->defaults)) { + if (!isset($this->defined[$option])) { + throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptions([$option]))); + } + + $value = $this->defaults[$option]; + + // Resolve the option if it is a nested definition + if (isset($this->nested[$option])) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + if (!\is_array($value)) { + throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), get_debug_type($value))); + } + + // The following section must be protected from cyclic calls. + $this->calling[$option] = true; + try { + $resolver = new self(); + $resolver->prototype = false; + $resolver->parentsOptions = $this->parentsOptions; + $resolver->parentsOptions[] = $option; + foreach ($this->nested[$option] as $closure) { + $closure($resolver, $this); + } + + if ($resolver->prototype) { + $values = []; + foreach ($value as $index => $prototypeValue) { + if (!\is_array($prototypeValue)) { + throw new InvalidOptionsException(sprintf('The value of the option "%s" is expected to be of type array of array, but is of type array of "%s".', $this->formatOptions([$option]), get_debug_type($prototypeValue))); + } + + $resolver->prototypeIndex = $index; + $values[$index] = $resolver->resolve($prototypeValue); + } + $value = $values; + } else { + $value = $resolver->resolve($value); + } + } finally { + $resolver->prototypeIndex = null; + unset($this->calling[$option]); + } + } + + // Resolve the option if the default value is lazily evaluated + if (isset($this->lazy[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + foreach ($this->lazy[$option] as $closure) { + $value = $closure($this, $value); + } + } finally { + unset($this->calling[$option]); + } + // END + } + + // Validate the type of the resolved option + if (isset($this->allowedTypes[$option])) { + $valid = true; + $invalidTypes = []; + + foreach ($this->allowedTypes[$option] as $type) { + if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) { + break; + } + } + + if (!$valid) { + $fmtActualValue = $this->formatValue($value); + $fmtAllowedTypes = implode('" or "', $this->allowedTypes[$option]); + $fmtProvidedTypes = implode('|', array_keys($invalidTypes)); + $allowedContainsArrayType = \count(array_filter($this->allowedTypes[$option], static fn ($item) => str_ends_with($item, '[]'))) > 0; + + if (\is_array($value) && $allowedContainsArrayType) { + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + } + + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + } + } + + // Validate the value of the resolved option + if (isset($this->allowedValues[$option])) { + $success = false; + $printableAllowedValues = []; + + foreach ($this->allowedValues[$option] as $allowedValue) { + if ($allowedValue instanceof \Closure) { + if ($allowedValue($value)) { + $success = true; + break; + } + + // Don't include closures in the exception message + continue; + } + + if ($value === $allowedValue) { + $success = true; + break; + } + + $printableAllowedValues[] = $allowedValue; + } + + if (!$success) { + $message = sprintf( + 'The option "%s" with value %s is invalid.', + $this->formatOptions([$option]), + $this->formatValue($value) + ); + + if (\count($printableAllowedValues) > 0) { + $message .= sprintf( + ' Accepted values are: %s.', + $this->formatValues($printableAllowedValues) + ); + } + + if (isset($this->info[$option])) { + $message .= sprintf(' Info: %s.', $this->info[$option]); + } + + throw new InvalidOptionsException($message); + } + } + + // Check whether the option is deprecated + // and it is provided by the user or is being called from a lazy evaluation + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || ($this->calling && \is_string($this->deprecated[$option]['message'])))) { + $deprecation = $this->deprecated[$option]; + $message = $this->deprecated[$option]['message']; + + if ($message instanceof \Closure) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + $this->calling[$option] = true; + try { + if (!\is_string($message = $message($this, $value))) { + throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', get_debug_type($message))); + } + } finally { + unset($this->calling[$option]); + } + } + + if ('' !== $message) { + trigger_deprecation($deprecation['package'], $deprecation['version'], strtr($message, ['%name%' => $option])); + } + } + + // Normalize the validated option + if (isset($this->normalizers[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + foreach ($this->normalizers[$option] as $normalizer) { + $value = $normalizer($this, $value); + } + } finally { + unset($this->calling[$option]); + } + // END + } + + // Mark as resolved + $this->resolved[$option] = $value; + + return $value; + } + + private function verifyTypes(string $type, mixed $value, array &$invalidTypes, int $level = 0): bool + { + if (\is_array($value) && str_ends_with($type, '[]')) { + $type = substr($type, 0, -2); + $valid = true; + + foreach ($value as $val) { + if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) { + $valid = false; + } + } + + return $valid; + } + + if (('null' === $type && null === $value) || (isset(self::VALIDATION_FUNCTIONS[$type]) ? self::VALIDATION_FUNCTIONS[$type]($value) : $value instanceof $type)) { + return true; + } + + if (!$invalidTypes || $level > 0) { + $invalidTypes[get_debug_type($value)] = true; + } + + return false; + } + + /** + * Returns whether a resolved option with the given name exists. + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \ArrayAccess::offsetExists() + */ + public function offsetExists(mixed $option): bool + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + return \array_key_exists($option, $this->defaults); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetSet(mixed $option, mixed $value): void + { + throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetUnset(mixed $option): void + { + throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); + } + + /** + * Returns the number of set options. + * + * This may be only a subset of the defined options. + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \Countable::count() + */ + public function count(): int + { + if (!$this->locked) { + throw new AccessException('Counting is only supported within closures of lazy options and normalizers.'); + } + + return \count($this->defaults); + } + + /** + * Sets whether ignore undefined options. + * + * @return $this + */ + public function setIgnoreUndefined(bool $ignore = true): static + { + $this->ignoreUndefined = $ignore; + + return $this; + } + + /** + * Returns a string representation of the value. + * + * This method returns the equivalent PHP tokens for most scalar types + * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped + * in double quotes ("). + */ + private function formatValue(mixed $value): string + { + if (\is_object($value)) { + return $value::class; + } + + if (\is_array($value)) { + return 'array'; + } + + if (\is_string($value)) { + return '"'.$value.'"'; + } + + if (\is_resource($value)) { + return 'resource'; + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + /** + * Returns a string representation of a list of values. + * + * Each of the values is converted to a string using + * {@link formatValue()}. The values are then concatenated with commas. + * + * @see formatValue() + */ + private function formatValues(array $values): string + { + foreach ($values as $key => $value) { + $values[$key] = $this->formatValue($value); + } + + return implode(', ', $values); + } + + private function formatOptions(array $options): string + { + if ($this->parentsOptions) { + $prefix = array_shift($this->parentsOptions); + if ($this->parentsOptions) { + $prefix .= sprintf('[%s]', implode('][', $this->parentsOptions)); + } + + if ($this->prototype && null !== $this->prototypeIndex) { + $prefix .= sprintf('[%s]', $this->prototypeIndex); + } + + $options = array_map(static fn (string $option): string => sprintf('%s[%s]', $prefix, $option), $options); + } + + return implode('", "', $options); + } + + private function getParameterClassName(\ReflectionParameter $parameter): ?string + { + if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) { + return null; + } + + return $type->getName(); + } +} diff --git a/vendor/symfony/options-resolver/README.md b/vendor/symfony/options-resolver/README.md new file mode 100644 index 0000000..c63b900 --- /dev/null +++ b/vendor/symfony/options-resolver/README.md @@ -0,0 +1,15 @@ +OptionsResolver Component +========================= + +The OptionsResolver component is `array_replace` on steroids. It allows you to +create an options system with required options, defaults, validation (type, +value), normalization and more. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/options_resolver.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/options-resolver/composer.json b/vendor/symfony/options-resolver/composer.json new file mode 100644 index 0000000..e70640d --- /dev/null +++ b/vendor/symfony/options-resolver/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/options-resolver", + "type": "library", + "description": "Provides an improved replacement for the array_replace PHP function", + "keywords": ["options", "config", "configuration"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/password-hasher/CHANGELOG.md b/vendor/symfony/password-hasher/CHANGELOG.md new file mode 100644 index 0000000..8258c30 --- /dev/null +++ b/vendor/symfony/password-hasher/CHANGELOG.md @@ -0,0 +1,13 @@ +CHANGELOG +========= + +6.2 +--- + + * Use `SensitiveParameter` attribute to redact sensitive values in back traces + +5.3 +--- + + * Add the component + * Use `bcrypt` as default algorithm in `NativePasswordHasher` diff --git a/vendor/symfony/password-hasher/Command/UserPasswordHashCommand.php b/vendor/symfony/password-hasher/Command/UserPasswordHashCommand.php new file mode 100644 index 0000000..1838892 --- /dev/null +++ b/vendor/symfony/password-hasher/Command/UserPasswordHashCommand.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * Hashes a user's password. + * + * @author Sarah Khalil + * @author Robin Chalas + * + * @final + */ +#[AsCommand(name: 'security:hash-password', description: 'Hash a user password')] +class UserPasswordHashCommand extends Command +{ + public function __construct( + private PasswordHasherFactoryInterface $hasherFactory, + private array $userClasses = [], + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('password', InputArgument::OPTIONAL, 'The plain password to hash.') + ->addArgument('user-class', InputArgument::OPTIONAL, 'The User entity class path associated with the hasher used to hash the password.') + ->addOption('empty-salt', null, InputOption::VALUE_NONE, 'Do not generate a salt or let the hasher generate one.') + ->setHelp(<<%command.name% command hashes passwords according to your +security configuration. This command is mainly used to generate passwords for +the in_memory user provider type and for changing passwords +in the database while developing the application. + +Suppose that you have the following security configuration in your application: + + +# config/packages/security.yml +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + App\Entity\User: auto + + +If you execute the command non-interactively, the first available configured +user class under the security.password_hashers key is used and a random salt is +generated to hash the password: + + php %command.full_name% --no-interaction [password] + +Pass the full user class path as the second argument to hash passwords for +your own entities: + + php %command.full_name% --no-interaction [password] 'App\Entity\User' + +Executing the command interactively allows you to generate a random salt for +hashing the password: + + php %command.full_name% [password] 'App\Entity\User' + +In case your hasher doesn't require a salt, add the empty-salt option: + + php %command.full_name% --empty-salt [password] 'App\Entity\User' + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io; + + $input->isInteractive() ? $errorIo->title('Symfony Password Hash Utility') : $errorIo->newLine(); + + $password = $input->getArgument('password'); + $userClass = $this->getUserClass($input, $io); + $emptySalt = $input->getOption('empty-salt'); + + $hasher = $this->hasherFactory->getPasswordHasher($userClass); + $saltlessWithoutEmptySalt = !$emptySalt && !$hasher instanceof LegacyPasswordHasherInterface; + + if ($saltlessWithoutEmptySalt) { + $emptySalt = true; + } + + if (!$password) { + if (!$input->isInteractive()) { + $errorIo->error('The password must not be empty.'); + + return 1; + } + $passwordQuestion = $this->createPasswordQuestion(); + $password = $errorIo->askQuestion($passwordQuestion); + } + + $salt = null; + + if ($input->isInteractive() && !$emptySalt) { + $emptySalt = true; + + $errorIo->note('The command will take care of generating a salt for you. Be aware that some hashers advise to let them generate their own salt. If you\'re using one of those hashers, please answer \'no\' to the question below. '.\PHP_EOL.'Provide the \'empty-salt\' option in order to let the hasher handle the generation itself.'); + + if ($errorIo->confirm('Confirm salt generation ?')) { + $salt = $this->generateSalt(); + $emptySalt = false; + } + } elseif (!$emptySalt) { + $salt = $this->generateSalt(); + } + + $hashedPassword = $hasher->hash($password, $salt); + + $rows = [ + ['Hasher used', $hasher::class], + ['Password hash', $hashedPassword], + ]; + if (!$emptySalt) { + $rows[] = ['Generated salt', $salt]; + } + $io->table(['Key', 'Value'], $rows); + + if (!$emptySalt) { + $errorIo->note(sprintf('Make sure that your salt storage field fits the salt length: %s chars', \strlen($salt))); + } elseif ($saltlessWithoutEmptySalt) { + $errorIo->note('Self-salting hasher used: the hasher generated its own built-in salt.'); + } + + $errorIo->success('Password hashing succeeded'); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('user-class')) { + $suggestions->suggestValues($this->userClasses); + + return; + } + } + + /** + * Create the password question to ask the user for the password to be hashed. + */ + private function createPasswordQuestion(): Question + { + $passwordQuestion = new Question('Type in your password to be hashed'); + + return $passwordQuestion->setValidator(function ($value) { + if ('' === trim($value)) { + throw new InvalidArgumentException('The password must not be empty.'); + } + + return $value; + })->setHidden(true)->setMaxAttempts(20); + } + + private function generateSalt(): string + { + return base64_encode(random_bytes(30)); + } + + private function getUserClass(InputInterface $input, SymfonyStyle $io): string + { + if (null !== $userClass = $input->getArgument('user-class')) { + return $userClass; + } + + if (!$this->userClasses) { + throw new RuntimeException('There are no configured password hashers for the "security" extension.'); + } + + if (!$input->isInteractive() || 1 === \count($this->userClasses)) { + return reset($this->userClasses); + } + + $userClasses = $this->userClasses; + natcasesort($userClasses); + $userClasses = array_values($userClasses); + + return $io->choice('For which user class would you like to hash a password?', $userClasses, reset($userClasses)); + } +} diff --git a/vendor/symfony/password-hasher/Exception/ExceptionInterface.php b/vendor/symfony/password-hasher/Exception/ExceptionInterface.php new file mode 100644 index 0000000..2d80d8a --- /dev/null +++ b/vendor/symfony/password-hasher/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Exception; + +/** + * Interface for exceptions thrown by the password-hasher component. + * + * @author Robin Chalas + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/password-hasher/Exception/InvalidPasswordException.php b/vendor/symfony/password-hasher/Exception/InvalidPasswordException.php new file mode 100644 index 0000000..c70a4d5 --- /dev/null +++ b/vendor/symfony/password-hasher/Exception/InvalidPasswordException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Exception; + +/** + * @author Robin Chalas + */ +class InvalidPasswordException extends \RuntimeException implements ExceptionInterface +{ + public function __construct(string $message = 'Invalid password.', int $code = 0, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/symfony/password-hasher/Exception/LogicException.php b/vendor/symfony/password-hasher/Exception/LogicException.php new file mode 100644 index 0000000..f4d9f31 --- /dev/null +++ b/vendor/symfony/password-hasher/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Exception; + +/** + * @author Robin Chalas + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/password-hasher/Hasher/CheckPasswordLengthTrait.php b/vendor/symfony/password-hasher/Hasher/CheckPasswordLengthTrait.php new file mode 100644 index 0000000..9721b41 --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/CheckPasswordLengthTrait.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * @author Robin Chalas + */ +trait CheckPasswordLengthTrait +{ + private function isPasswordTooLong(#[\SensitiveParameter] string $password): bool + { + return PasswordHasherInterface::MAX_PASSWORD_LENGTH < \strlen($password); + } +} diff --git a/vendor/symfony/password-hasher/Hasher/MessageDigestPasswordHasher.php b/vendor/symfony/password-hasher/Hasher/MessageDigestPasswordHasher.php new file mode 100644 index 0000000..a6166b9 --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/MessageDigestPasswordHasher.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * MessageDigestPasswordHasher uses a message digest algorithm. + * + * @author Fabien Potencier + */ +class MessageDigestPasswordHasher implements LegacyPasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private int $hashLength = -1; + + /** + * @param string $algorithm The digest algorithm to use + * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash + * @param int $iterations The number of iterations to use to stretch the password hash + */ + public function __construct( + private string $algorithm = 'sha512', + private bool $encodeHashAsBase64 = true, + private int $iterations = 5000, + ) { + try { + $this->hashLength = \strlen($this->hash('', 'salt')); + } catch (\LogicException) { + // ignore algorithm not supported + } + } + + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + if (!\in_array($this->algorithm, hash_algos(), true)) { + throw new LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); + } + + $salted = $this->mergePasswordAndSalt($plainPassword, $salt); + $digest = hash($this->algorithm, $salted, true); + + // "stretch" hash + for ($i = 1; $i < $this->iterations; ++$i) { + $digest = hash($this->algorithm, $digest.$salted, true); + } + + return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); + } + + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool + { + if (\strlen($hashedPassword) !== $this->hashLength || str_contains($hashedPassword, '$')) { + return false; + } + + return !$this->isPasswordTooLong($plainPassword) && hash_equals($hashedPassword, $this->hash($plainPassword, $salt)); + } + + public function needsRehash(string $hashedPassword): bool + { + return false; + } + + private function mergePasswordAndSalt(#[\SensitiveParameter] string $password, ?string $salt): string + { + if (!$salt) { + return $password; + } + + if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) { + throw new \InvalidArgumentException('Cannot use { or } in salt.'); + } + + return $password.'{'.$salt.'}'; + } +} diff --git a/vendor/symfony/password-hasher/Hasher/MigratingPasswordHasher.php b/vendor/symfony/password-hasher/Hasher/MigratingPasswordHasher.php new file mode 100644 index 0000000..c958b50 --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/MigratingPasswordHasher.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * Hashes passwords using the best available hasher. + * Verifies them using a chain of hashers. + * + * /!\ Don't put a PlaintextPasswordHasher in the list as that'd mean a leaked hash + * could be used to authenticate successfully without knowing the cleartext password. + * + * @author Nicolas Grekas + */ +final class MigratingPasswordHasher implements PasswordHasherInterface +{ + private array $extraHashers; + + public function __construct( + private PasswordHasherInterface $bestHasher, + PasswordHasherInterface ...$extraHashers, + ) { + $this->extraHashers = $extraHashers; + } + + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string + { + return $this->bestHasher->hash($plainPassword, $salt); + } + + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool + { + if ($this->bestHasher->verify($hashedPassword, $plainPassword, $salt)) { + return true; + } + + if (!$this->bestHasher->needsRehash($hashedPassword)) { + return false; + } + + foreach ($this->extraHashers as $hasher) { + if ($hasher->verify($hashedPassword, $plainPassword, $salt)) { + return true; + } + } + + return false; + } + + public function needsRehash(string $hashedPassword): bool + { + return $this->bestHasher->needsRehash($hashedPassword); + } +} diff --git a/vendor/symfony/password-hasher/Hasher/NativePasswordHasher.php b/vendor/symfony/password-hasher/Hasher/NativePasswordHasher.php new file mode 100644 index 0000000..413bcec --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/NativePasswordHasher.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * Hashes passwords using password_hash(). + * + * @author Elnur Abdurrakhimov + * @author Terje BrÃ¥ten + * @author Nicolas Grekas + */ +final class NativePasswordHasher implements PasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private string $algorithm = \PASSWORD_BCRYPT; + private array $options; + + /** + * @param string|null $algorithm An algorithm supported by password_hash() or null to use the best available algorithm + */ + public function __construct(?int $opsLimit = null, ?int $memLimit = null, ?int $cost = null, ?string $algorithm = null) + { + $cost ??= 13; + $opsLimit ??= max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); + $memLimit ??= max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); + + if (3 > $opsLimit) { + throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); + } + + if (10 * 1024 > $memLimit) { + throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); + } + + if ($cost < 4 || 31 < $cost) { + throw new \InvalidArgumentException('$cost must be in the range of 4-31.'); + } + + if (null !== $algorithm) { + $algorithms = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; + + if (\defined('PASSWORD_ARGON2I')) { + $algorithms[2] = $algorithms['argon2i'] = \PASSWORD_ARGON2I; + } + + if (\defined('PASSWORD_ARGON2ID')) { + $algorithms[3] = $algorithms['argon2id'] = \PASSWORD_ARGON2ID; + } + + $this->algorithm = $algorithms[$algorithm] ?? $algorithm; + } + + $this->options = [ + 'cost' => $cost, + 'time_cost' => $opsLimit, + 'memory_cost' => $memLimit >> 10, + 'threads' => 1, + ]; + } + + public function hash(#[\SensitiveParameter] string $plainPassword): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + if (\PASSWORD_BCRYPT === $this->algorithm && (72 < \strlen($plainPassword) || str_contains($plainPassword, "\0"))) { + $plainPassword = base64_encode(hash('sha512', $plainPassword, true)); + } + + return password_hash($plainPassword, $this->algorithm, $this->options); + } + + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword): bool + { + if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) { + return false; + } + + if (!str_starts_with($hashedPassword, '$argon')) { + // Bcrypt cuts on NUL chars and after 72 bytes + if (str_starts_with($hashedPassword, '$2') && (72 < \strlen($plainPassword) || str_contains($plainPassword, "\0"))) { + $plainPassword = base64_encode(hash('sha512', $plainPassword, true)); + } + + return password_verify($plainPassword, $hashedPassword); + } + + if (\extension_loaded('sodium') && version_compare(\SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) { + return sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + if (\extension_loaded('libsodium') && version_compare(phpversion('libsodium'), '1.0.14', '>=')) { + return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + return password_verify($plainPassword, $hashedPassword); + } + + public function needsRehash(string $hashedPassword): bool + { + return password_needs_rehash($hashedPassword, $this->algorithm, $this->options); + } +} diff --git a/vendor/symfony/password-hasher/Hasher/PasswordHasherAwareInterface.php b/vendor/symfony/password-hasher/Hasher/PasswordHasherAwareInterface.php new file mode 100644 index 0000000..58046bc --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/PasswordHasherAwareInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +/** + * @author Christophe Coevoet + */ +interface PasswordHasherAwareInterface +{ + /** + * Gets the name of the password hasher used to hash the password. + * + * If the method returns null, the standard way to retrieve the password hasher + * will be used instead. + */ + public function getPasswordHasherName(): ?string; +} diff --git a/vendor/symfony/password-hasher/Hasher/PasswordHasherFactory.php b/vendor/symfony/password-hasher/Hasher/PasswordHasherFactory.php new file mode 100644 index 0000000..63928df --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/PasswordHasherFactory.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; + +/** + * A generic hasher factory implementation. + * + * @author Nicolas Grekas + * @author Robin Chalas + */ +class PasswordHasherFactory implements PasswordHasherFactoryInterface +{ + /** + * @param array $passwordHashers + */ + public function __construct( + private array $passwordHashers, + ) { + } + + public function getPasswordHasher(string|PasswordAuthenticatedUserInterface|PasswordHasherAwareInterface $user): PasswordHasherInterface + { + $hasherKey = null; + + if ($user instanceof PasswordHasherAwareInterface && null !== $hasherName = $user->getPasswordHasherName()) { + if (!\array_key_exists($hasherName, $this->passwordHashers)) { + throw new \RuntimeException(sprintf('The password hasher "%s" was not configured.', $hasherName)); + } + + $hasherKey = $hasherName; + } else { + foreach ($this->passwordHashers as $class => $hasher) { + if ((\is_object($user) && $user instanceof $class) || (!\is_object($user) && (is_subclass_of($user, $class) || $user == $class))) { + $hasherKey = $class; + break; + } + } + } + + if (null === $hasherKey) { + throw new \RuntimeException(sprintf('No password hasher has been configured for account "%s".', \is_object($user) ? get_debug_type($user) : $user)); + } + + if (!$this->passwordHashers[$hasherKey] instanceof PasswordHasherInterface) { + $this->passwordHashers[$hasherKey] = $this->createHasher($this->passwordHashers[$hasherKey]); + } + + return $this->passwordHashers[$hasherKey]; + } + + /** + * Creates the actual hasher instance. + * + * @throws \InvalidArgumentException + */ + private function createHasher(array $config, bool $isExtra = false): PasswordHasherInterface + { + if (isset($config['instance'])) { + if (!isset($config['migrate_from'])) { + return $config['instance']; + } + + $config = $this->getMigratingPasswordConfig($config); + } + + if (isset($config['algorithm'])) { + $rawConfig = $config; + $config = $this->getHasherConfigFromAlgorithm($config); + } + if (!isset($config['class'])) { + throw new \InvalidArgumentException('"class" must be set in '.json_encode($config)); + } + if (!isset($config['arguments'])) { + throw new \InvalidArgumentException('"arguments" must be set in '.json_encode($config)); + } + + $hasher = new $config['class'](...$config['arguments']); + + if ($isExtra || !\in_array($config['class'], [NativePasswordHasher::class, SodiumPasswordHasher::class], true)) { + return $hasher; + } + + if ($rawConfig ?? null) { + $extrapasswordHashers = array_map(function (string $algo) use ($rawConfig): PasswordHasherInterface { + $rawConfig['algorithm'] = $algo; + + return $this->createHasher($rawConfig); + }, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']); + } else { + $extrapasswordHashers = [new Pbkdf2PasswordHasher(), new MessageDigestPasswordHasher()]; + } + + return new MigratingPasswordHasher($hasher, ...$extrapasswordHashers); + } + + private function getHasherConfigFromAlgorithm(array $config): array + { + if ('auto' === $config['algorithm']) { + // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly + if (SodiumPasswordHasher::isSupported()) { + $algorithms = ['native', 'sodium', 'pbkdf2']; + } else { + $algorithms = ['native', 'pbkdf2']; + } + + if ($config['hash_algorithm'] ?? '') { + $algorithms[] = $config['hash_algorithm']; + } + + $hasherChain = []; + foreach ($algorithms as $algorithm) { + $config['algorithm'] = $algorithm; + $hasherChain[] = $this->createHasher($config, true); + } + + return [ + 'class' => MigratingPasswordHasher::class, + 'arguments' => $hasherChain, + ]; + } + + if ($config['migrate_from'] ?? false) { + return $this->getMigratingPasswordConfig($config); + } + + switch ($config['algorithm']) { + case 'plaintext': + return [ + 'class' => PlaintextPasswordHasher::class, + 'arguments' => [$config['ignore_case'] ?? false], + ]; + + case 'pbkdf2': + return [ + 'class' => Pbkdf2PasswordHasher::class, + 'arguments' => [ + $config['hash_algorithm'] ?? 'sha512', + $config['encode_as_base64'] ?? true, + $config['iterations'] ?? 1000, + $config['key_length'] ?? 40, + ], + ]; + + case 'bcrypt': + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_BCRYPT; + + return $this->getHasherConfigFromAlgorithm($config); + + case 'native': + return [ + 'class' => NativePasswordHasher::class, + 'arguments' => [ + $config['time_cost'] ?? null, + (($config['memory_cost'] ?? 0) << 10) ?: null, + $config['cost'] ?? null, + ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []), + ]; + + case 'sodium': + return [ + 'class' => SodiumPasswordHasher::class, + 'arguments' => [ + $config['time_cost'] ?? null, + (($config['memory_cost'] ?? 0) << 10) ?: null, + ], + ]; + + case 'argon2i': + if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + $config['algorithm'] = 'sodium'; + } elseif (\defined('PASSWORD_ARGON2I')) { + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_ARGON2I; + } else { + throw new LogicException(sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id" or "auto' : 'auto')); + } + + return $this->getHasherConfigFromAlgorithm($config); + + case 'argon2id': + if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + $config['algorithm'] = 'sodium'; + } elseif (\defined('PASSWORD_ARGON2ID')) { + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_ARGON2ID; + } else { + throw new LogicException(sprintf('Algorithm "argon2id" is not available; use "%s" or libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); + } + + return $this->getHasherConfigFromAlgorithm($config); + } + + return [ + 'class' => MessageDigestPasswordHasher::class, + 'arguments' => [ + $config['algorithm'], + $config['encode_as_base64'] ?? true, + $config['iterations'] ?? 5000, + ], + ]; + } + + private function getMigratingPasswordConfig(array $config): array + { + $frompasswordHashers = $config['migrate_from']; + unset($config['migrate_from']); + $hasherChain = [$this->createHasher($config, true)]; + + foreach ($frompasswordHashers as $name) { + if ($hasher = $this->passwordHashers[$name] ?? false) { + $hasher = $hasher instanceof PasswordHasherInterface ? $hasher : $this->createHasher($hasher, true); + } else { + $hasher = $this->createHasher(['algorithm' => $name], true); + } + + $hasherChain[] = $hasher; + } + + return [ + 'class' => MigratingPasswordHasher::class, + 'arguments' => $hasherChain, + ]; + } +} diff --git a/vendor/symfony/password-hasher/Hasher/PasswordHasherFactoryInterface.php b/vendor/symfony/password-hasher/Hasher/PasswordHasherFactoryInterface.php new file mode 100644 index 0000000..6dc158f --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/PasswordHasherFactoryInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; + +/** + * PasswordHasherFactoryInterface to support different password hashers for different user accounts. + * + * @author Robin Chalas + * @author Johannes M. Schmitt + */ +interface PasswordHasherFactoryInterface +{ + /** + * Returns the password hasher to use for the given user. + * + * @throws \RuntimeException When no password hasher could be found for the user + */ + public function getPasswordHasher(string|PasswordAuthenticatedUserInterface|PasswordHasherAwareInterface $user): PasswordHasherInterface; +} diff --git a/vendor/symfony/password-hasher/Hasher/Pbkdf2PasswordHasher.php b/vendor/symfony/password-hasher/Hasher/Pbkdf2PasswordHasher.php new file mode 100644 index 0000000..2eac5c5 --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/Pbkdf2PasswordHasher.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * Pbkdf2PasswordHasher uses the PBKDF2 (Password-Based Key Derivation Function 2). + * + * Providing a high level of Cryptographic security, + * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST). + * + * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process. + * PBKDF2 should be used with caution and care. + * + * @author Sebastiaan Stok + * @author Andrew Johnson + * @author Fabien Potencier + */ +final class Pbkdf2PasswordHasher implements LegacyPasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private int $encodedLength = -1; + + /** + * @param string $algorithm The digest algorithm to use + * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash + * @param int $iterations The number of iterations to use to stretch the password hash + * @param int $length Length of derived key to create + */ + public function __construct( + private string $algorithm = 'sha512', + private bool $encodeHashAsBase64 = true, + private int $iterations = 1000, + private int $length = 40, + ) { + try { + $this->encodedLength = \strlen($this->hash('', 'salt')); + } catch (\LogicException) { + // ignore unsupported algorithm + } + } + + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + if (!\in_array($this->algorithm, hash_algos(), true)) { + throw new LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); + } + + $digest = hash_pbkdf2($this->algorithm, $plainPassword, $salt ?? '', $this->iterations, $this->length, true); + + return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); + } + + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool + { + if (\strlen($hashedPassword) !== $this->encodedLength || str_contains($hashedPassword, '$')) { + return false; + } + + return !$this->isPasswordTooLong($plainPassword) && hash_equals($hashedPassword, $this->hash($plainPassword, $salt)); + } + + public function needsRehash(string $hashedPassword): bool + { + return false; + } +} diff --git a/vendor/symfony/password-hasher/Hasher/PlaintextPasswordHasher.php b/vendor/symfony/password-hasher/Hasher/PlaintextPasswordHasher.php new file mode 100644 index 0000000..e326dc4 --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/PlaintextPasswordHasher.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; + +/** + * PlaintextPasswordHasher does not do any hashing but is useful in testing environments. + * + * As this hasher is not cryptographically secure, usage of it in production environments is discouraged. + * + * @author Fabien Potencier + */ +class PlaintextPasswordHasher implements LegacyPasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + /** + * @param bool $ignorePasswordCase Compare password case-insensitive + */ + public function __construct( + private bool $ignorePasswordCase = false, + ) { + } + + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + return $this->mergePasswordAndSalt($plainPassword, $salt); + } + + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool + { + if ($this->isPasswordTooLong($plainPassword)) { + return false; + } + + $pass2 = $this->mergePasswordAndSalt($plainPassword, $salt); + + if (!$this->ignorePasswordCase) { + return hash_equals($hashedPassword, $pass2); + } + + return hash_equals(strtolower($hashedPassword), strtolower($pass2)); + } + + public function needsRehash(string $hashedPassword): bool + { + return false; + } + + private function mergePasswordAndSalt(#[\SensitiveParameter] string $password, ?string $salt): string + { + if (!$salt) { + return $password; + } + + if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) { + throw new \InvalidArgumentException('Cannot use { or } in salt.'); + } + + return $password.'{'.$salt.'}'; + } +} diff --git a/vendor/symfony/password-hasher/Hasher/SodiumPasswordHasher.php b/vendor/symfony/password-hasher/Hasher/SodiumPasswordHasher.php new file mode 100644 index 0000000..ae6c03f --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/SodiumPasswordHasher.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; +use Symfony\Component\PasswordHasher\Exception\LogicException; +use Symfony\Component\PasswordHasher\PasswordHasherInterface; + +/** + * Hashes passwords using libsodium. + * + * @author Robin Chalas + * @author Zan Baldwin + * @author Dominik Müller + */ +final class SodiumPasswordHasher implements PasswordHasherInterface +{ + use CheckPasswordLengthTrait; + + private int $opsLimit; + private int $memLimit; + + public function __construct(?int $opsLimit = null, ?int $memLimit = null) + { + if (!self::isSupported()) { + throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); + } + + $this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); + $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); + + if (3 > $this->opsLimit) { + throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); + } + + if (10 * 1024 > $this->memLimit) { + throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); + } + } + + public static function isSupported(): bool + { + return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>='); + } + + public function hash(#[\SensitiveParameter] string $plainPassword): string + { + if ($this->isPasswordTooLong($plainPassword)) { + throw new InvalidPasswordException(); + } + + if (\function_exists('sodium_crypto_pwhash_str')) { + return sodium_crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit); + } + + if (\extension_loaded('libsodium')) { + return \Sodium\crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit); + } + + throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); + } + + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword): bool + { + if ('' === $plainPassword) { + return false; + } + + if ($this->isPasswordTooLong($plainPassword)) { + return false; + } + + if (!str_starts_with($hashedPassword, '$argon')) { + if (str_starts_with($hashedPassword, '$2') && (72 < \strlen($plainPassword) || str_contains($plainPassword, "\0"))) { + $plainPassword = base64_encode(hash('sha512', $plainPassword, true)); + } + + // Accept validating non-argon passwords for seamless migrations + return password_verify($plainPassword, $hashedPassword); + } + + if (\function_exists('sodium_crypto_pwhash_str_verify')) { + return sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + if (\extension_loaded('libsodium')) { + return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword); + } + + return false; + } + + public function needsRehash(string $hashedPassword): bool + { + if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) { + return sodium_crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit); + } + + if (\extension_loaded('libsodium')) { + return \Sodium\crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit); + } + + throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); + } +} diff --git a/vendor/symfony/password-hasher/Hasher/UserPasswordHasher.php b/vendor/symfony/password-hasher/Hasher/UserPasswordHasher.php new file mode 100644 index 0000000..770cc3d --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/UserPasswordHasher.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; + +/** + * Hashes passwords based on the user and the PasswordHasherFactory. + * + * @author Ariel Ferrandini + * + * @final + */ +class UserPasswordHasher implements UserPasswordHasherInterface +{ + public function __construct( + private PasswordHasherFactoryInterface $hasherFactory, + ) { + } + + public function hashPassword(PasswordAuthenticatedUserInterface $user, #[\SensitiveParameter] string $plainPassword): string + { + $salt = null; + if ($user instanceof LegacyPasswordAuthenticatedUserInterface) { + $salt = $user->getSalt(); + } + + $hasher = $this->hasherFactory->getPasswordHasher($user); + + return $hasher->hash($plainPassword, $salt); + } + + public function isPasswordValid(PasswordAuthenticatedUserInterface $user, #[\SensitiveParameter] string $plainPassword): bool + { + $salt = null; + if ($user instanceof LegacyPasswordAuthenticatedUserInterface) { + $salt = $user->getSalt(); + } + + if (null === $user->getPassword()) { + return false; + } + + $hasher = $this->hasherFactory->getPasswordHasher($user); + + return $hasher->verify($user->getPassword(), $plainPassword, $salt); + } + + public function needsRehash(PasswordAuthenticatedUserInterface $user): bool + { + if (null === $user->getPassword()) { + return false; + } + + $hasher = $this->hasherFactory->getPasswordHasher($user); + + return $hasher->needsRehash($user->getPassword()); + } +} diff --git a/vendor/symfony/password-hasher/Hasher/UserPasswordHasherInterface.php b/vendor/symfony/password-hasher/Hasher/UserPasswordHasherInterface.php new file mode 100644 index 0000000..8d4cc1e --- /dev/null +++ b/vendor/symfony/password-hasher/Hasher/UserPasswordHasherInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher\Hasher; + +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; + +/** + * Interface for the user password hasher service. + * + * @author Ariel Ferrandini + */ +interface UserPasswordHasherInterface +{ + /** + * Hashes the plain password for the given user. + */ + public function hashPassword(PasswordAuthenticatedUserInterface $user, #[\SensitiveParameter] string $plainPassword): string; + + /** + * Checks if the plaintext password matches the user's password. + */ + public function isPasswordValid(PasswordAuthenticatedUserInterface $user, #[\SensitiveParameter] string $plainPassword): bool; + + /** + * Checks if an encoded password would benefit from rehashing. + */ + public function needsRehash(PasswordAuthenticatedUserInterface $user): bool; +} diff --git a/vendor/symfony/password-hasher/LICENSE b/vendor/symfony/password-hasher/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/password-hasher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/password-hasher/LegacyPasswordHasherInterface.php b/vendor/symfony/password-hasher/LegacyPasswordHasherInterface.php new file mode 100644 index 0000000..8efef37 --- /dev/null +++ b/vendor/symfony/password-hasher/LegacyPasswordHasherInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; + +/** + * Provides password hashing and verification capabilities for "legacy" hashers that require external salts. + * + * @author Fabien Potencier + * @author Nicolas Grekas + * @author Robin Chalas + */ +interface LegacyPasswordHasherInterface extends PasswordHasherInterface +{ + /** + * Hashes a plain password. + * + * @throws InvalidPasswordException If the plain password is invalid, e.g. excessively long + */ + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string; + + /** + * Checks that a plain password and a salt match a password hash. + */ + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool; +} diff --git a/vendor/symfony/password-hasher/PasswordHasherInterface.php b/vendor/symfony/password-hasher/PasswordHasherInterface.php new file mode 100644 index 0000000..6e09db3 --- /dev/null +++ b/vendor/symfony/password-hasher/PasswordHasherInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PasswordHasher; + +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; + +/** + * Provides password hashing capabilities. + * + * @author Robin Chalas + * @author Fabien Potencier + * @author Nicolas Grekas + */ +interface PasswordHasherInterface +{ + public const MAX_PASSWORD_LENGTH = 4096; + + /** + * Hashes a plain password. + * + * @throws InvalidPasswordException When the plain password is invalid, e.g. excessively long + */ + public function hash(#[\SensitiveParameter] string $plainPassword): string; + + /** + * Verifies a plain password against a hash. + */ + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword): bool; + + /** + * Checks if a password hash would benefit from rehashing. + */ + public function needsRehash(string $hashedPassword): bool; +} diff --git a/vendor/symfony/password-hasher/README.md b/vendor/symfony/password-hasher/README.md new file mode 100644 index 0000000..ca93044 --- /dev/null +++ b/vendor/symfony/password-hasher/README.md @@ -0,0 +1,40 @@ +PasswordHasher Component +======================== + +The PasswordHasher component provides secure password hashing utilities. + +Getting Started +--------------- + +```bash +composer require symfony/password-hasher +``` + +```php +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; + +// Configure different password hashers via the factory +$factory = new PasswordHasherFactory([ + 'common' => ['algorithm' => 'bcrypt'], + 'memory-hard' => ['algorithm' => 'sodium'], +]); + +// Retrieve the right password hasher by its name +$passwordHasher = $factory->getPasswordHasher('common'); + +// Hash a plain password +$hash = $passwordHasher->hash('plain'); // returns a bcrypt hash + +// Verify that a given plain password matches the hash +$passwordHasher->verify($hash, 'wrong'); // returns false +$passwordHasher->verify($hash, 'plain'); // returns true (valid) +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/security.html#c-hashing-passwords) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/password-hasher/composer.json b/vendor/symfony/password-hasher/composer.json new file mode 100644 index 0000000..ebcb51b --- /dev/null +++ b/vendor/symfony/password-hasher/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/password-hasher", + "type": "library", + "description": "Provides password hashing utilities", + "keywords": ["password", "hashing"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/security-core": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0" + }, + "conflict": { + "symfony/security-core": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\PasswordHasher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/polyfill-intl-grapheme/Grapheme.php b/vendor/symfony/polyfill-intl-grapheme/Grapheme.php new file mode 100644 index 0000000..5373f16 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/Grapheme.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Grapheme; + +\define('SYMFONY_GRAPHEME_CLUSTER_RX', ((float) \PCRE_VERSION < 10 ? (float) \PCRE_VERSION >= 8.32 : (float) \PCRE_VERSION >= 10.39) ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX); + +/** + * Partial intl implementation in pure PHP. + * + * Implemented: + * - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8 + * - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string + * - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack + * - grapheme_strlen - Get string length in grapheme units + * - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string + * - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string + * - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string + * - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack + * - grapheme_substr - Return part of a string + * + * @author Nicolas Grekas + * + * @internal + */ +final class Grapheme +{ + // (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control]) + // This regular expression is a work around for http://bugs.exim.org/1279 + public const GRAPHEME_CLUSTER_RX = '(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[á„€-á…Ÿ]*(?:[가개갸걔거게겨계고과괘괴êµêµ¬ê¶ˆê¶¤ê·€ê·œê·¸ê¸”기까깨꺄꺠꺼께껴ê¼ê¼¬ê½ˆê½¤ê¾€ê¾œê¾¸ê¿”꿰뀌뀨ë„ë ë¼ë‚˜ë‚´ëƒëƒ¬ë„ˆë„¤ë…€ë…œë…¸ë†”놰뇌뇨누눠눼뉘뉴ëŠëŠ¬ë‹ˆë‹¤ëŒ€ëŒœëŒ¸ë”ë°ëŽŒëŽ¨ë„ë ë¼ë˜ë´ë‘둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴ë˜ë˜¬ë™ˆë™¤ëš€ëšœëš¸ë›”뛰뜌뜨ë„ë ë¼ëž˜ëž´ëŸëŸ¬ë ˆë ¤ë¡€ë¡œë¡¸ë¢”뢰료루뤄뤠뤼류르ë¦ë¦¬ë§ˆë§¤ë¨€ë¨œë¨¸ë©”며몌모뫄뫠뫼묘무ë­ë­¬ë®ˆë®¤ë¯€ë¯œë¯¸ë°”배뱌뱨버베벼볘보ë´ë´¬ëµˆëµ¤ë¶€ë¶œë¶¸ë·”뷰브븨비빠빼뺘뺴ë»ë»¬ë¼ˆë¼¤ë½€ë½œë½¸ë¾”뾰뿌뿨쀄쀠쀼ì˜ì´ì‚사새샤섀서세셔셰소솨쇄쇠쇼수숴ì‰ì‰¬ìŠˆìŠ¤ì‹€ì‹œì‹¸ìŒ”ìŒ°ìŒì¨ìŽ„ìŽ ìŽ¼ì˜ì´ìì¬ì‘ˆì‘¤ì’€ì’œì’¸ì“”쓰씌씨아애야얘어ì—여예오와왜외요우워웨위유으ì˜ì´ìžìž¬ìŸˆìŸ¤ì €ì œì ¸ì¡”조좌좨죄죠주줘줴ì¥ì¥¬ì¦ˆì¦¤ì§€ì§œì§¸ì¨”쨰쩌쩨쪄쪠쪼쫘쫴ì¬ì¬¬ì­ˆì­¤ì®€ì®œì®¸ì¯”쯰찌차채챠챼처체ì³ì³¬ì´ˆì´¤ìµ€ìµœìµ¸ì¶”춰췌취츄츠츼치카ìºìº¬ì»ˆì»¤ì¼€ì¼œì¼¸ì½”콰쾌쾨쿄쿠쿼퀘퀴íí¬í‚ˆí‚¤íƒ€íƒœíƒ¸í„”터테텨톄토톼퇘퇴íˆíˆ¬í‰ˆí‰¤íŠ€íŠœíŠ¸í‹”í‹°íŒŒíŒ¨í„í í¼íŽ˜íŽ´íí¬íˆí¤í‘€í‘œí‘¸í’”풰퓌퓨프픠피하해í–햬허헤혀혜호화홰회효후훠훼휘휴íí¬ížˆ]?[á… -ᆢ]+|[ê°€-힣])[ᆨ-ᇹ]*|[á„€-á…Ÿ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; + + private const CASE_FOLD = [ + ['µ', 'Å¿', "\xCD\x85", 'Ï‚', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'Ï€', 'κ', 'Ï', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0) + { + if (0 > $start) { + $start = \strlen($s) + $start; + } + + if (!\is_scalar($s)) { + $hasError = false; + set_error_handler(function () use (&$hasError) { $hasError = true; }); + $next = substr($s, $start); + restore_error_handler(); + if ($hasError) { + substr($s, $start); + $s = ''; + } else { + $s = $next; + } + } else { + $s = substr($s, $start); + } + $size = (int) $size; + $type = (int) $type; + $start = (int) $start; + + if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS'); + } + + if (!isset($s[0]) || 0 > $size || 0 > $start) { + return false; + } + if (0 === $size) { + return ''; + } + + $next = $start; + + $s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); + + if (!isset($s[1])) { + return false; + } + + $i = 1; + $ret = ''; + + do { + if (\GRAPHEME_EXTR_COUNT === $type) { + --$size; + } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) { + $size -= \strlen($s[$i]); + } else { + $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE'); + } + + if ($size >= 0) { + $ret .= $s[$i]; + } + } while (isset($s[++$i]) && $size > 0); + + $next += \strlen($ret); + + return $ret; + } + + public static function grapheme_strlen($s) + { + preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len); + + return 0 === $len && '' !== $s ? null : $len; + } + + public static function grapheme_substr($s, $start, $len = null) + { + if (null === $len) { + $len = 2147483647; + } + + preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s); + + $slen = \count($s[0]); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + if (\PHP_VERSION_ID < 80000) { + return false; + } + + $start = 0; + } + if ($start >= $slen) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + + $rem = $slen - $start; + + if (0 > $len) { + $len += $rem; + } + if (0 === $len) { + return ''; + } + if (0 > $len) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + if ($len > $rem) { + $len = $rem; + } + + return implode('', \array_slice($s[0], $start, $len)); + } + + public static function grapheme_strpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 0); + } + + public static function grapheme_stripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 1); + } + + public static function grapheme_strrpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 2); + } + + public static function grapheme_strripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 3); + } + + public static function grapheme_stristr($s, $needle, $beforeNeedle = false) + { + return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + public static function grapheme_strstr($s, $needle, $beforeNeedle = false) + { + return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + private static function grapheme_position($s, $needle, $offset, $mode) + { + $needle = (string) $needle; + if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) { + return false; + } + $s = (string) $s; + if (!preg_match('/./us', $s)) { + return false; + } + if ($offset > 0) { + $s = self::grapheme_substr($s, $offset); + } elseif ($offset < 0) { + if (2 > $mode) { + $offset += self::grapheme_strlen($s); + $s = self::grapheme_substr($s, $offset); + if (0 > $offset) { + $offset = 0; + } + } elseif (0 > $offset += self::grapheme_strlen($needle)) { + $s = self::grapheme_substr($s, 0, $offset); + $offset = 0; + } else { + $offset = 0; + } + } + + // As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8, + // we can use normal binary string functions here. For case-insensitive searches, + // case fold the strings first. + $caseInsensitive = $mode & 1; + $reverse = $mode & 2; + if ($caseInsensitive) { + // Use the same case folding mode as mbstring does for mb_stripos(). + // Stick to SIMPLE case folding to avoid changing the length of the string, which + // might result in offsets being shifted. + $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER; + $s = mb_convert_case($s, $mode, 'UTF-8'); + $needle = mb_convert_case($needle, $mode, 'UTF-8'); + + if (!\defined('MB_CASE_FOLD_SIMPLE')) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + $needle = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle); + } + } + if ($reverse) { + $needlePos = strrpos($s, $needle); + } else { + $needlePos = strpos($s, $needle); + } + + return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false; + } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/LICENSE b/vendor/symfony/polyfill-intl-grapheme/LICENSE new file mode 100644 index 0000000..6e3afce --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-intl-grapheme/README.md b/vendor/symfony/polyfill-intl-grapheme/README.md new file mode 100644 index 0000000..f55d92c --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/README.md @@ -0,0 +1,31 @@ +Symfony Polyfill / Intl: Grapheme +================================= + +This component provides a partial, native PHP implementation of the +[Grapheme functions](https://php.net/intl.grapheme) from the +[Intl](https://php.net/intl) extension. + +- [`grapheme_extract`](https://php.net/grapheme_extract): Extract a sequence of grapheme + clusters from a text buffer, which must be encoded in UTF-8 +- [`grapheme_stripos`](https://php.net/grapheme_stripos): Find position (in grapheme units) + of first occurrence of a case-insensitive string +- [`grapheme_stristr`](https://php.net/grapheme_stristr): Returns part of haystack string + from the first occurrence of case-insensitive needle to the end of haystack +- [`grapheme_strlen`](https://php.net/grapheme_strlen): Get string length in grapheme units +- [`grapheme_strpos`](https://php.net/grapheme_strpos): Find position (in grapheme units) + of first occurrence of a string +- [`grapheme_strripos`](https://php.net/grapheme_strripos): Find position (in grapheme units) + of last occurrence of a case-insensitive string +- [`grapheme_strrpos`](https://php.net/grapheme_strrpos): Find position (in grapheme units) + of last occurrence of a string +- [`grapheme_strstr`](https://php.net/grapheme_strstr): Returns part of haystack string from + the first occurrence of needle to the end of haystack +- [`grapheme_substr`](https://php.net/grapheme_substr): Return part of a string + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-grapheme/bootstrap.php b/vendor/symfony/polyfill-intl-grapheme/bootstrap.php new file mode 100644 index 0000000..a9ea03c --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/bootstrap.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php b/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php new file mode 100644 index 0000000..b8c0786 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract(?string $haystack, ?int $size, ?int $type = GRAPHEME_EXTR_COUNT, ?int $offset = 0, &$next = null): string|false { return p\Grapheme::grapheme_extract((string) $haystack, (int) $size, (int) $type, (int) $offset, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, $length); } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/composer.json b/vendor/symfony/polyfill-intl-grapheme/composer.json new file mode 100644 index 0000000..0eea417 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/polyfill-intl-grapheme", + "type": "library", + "description": "Symfony polyfill for intl's grapheme_* functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "grapheme"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-intl-icu/Collator.php b/vendor/symfony/polyfill-intl-icu/Collator.php new file mode 100644 index 0000000..2f952cd --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Collator.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu; + +use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentValueNotImplementedException; +use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException; + +/** + * Replacement for PHP's native {@link \Collator} class. + * + * The only methods currently supported in this class are: + * + * - {@link \__construct} + * - {@link create} + * - {@link asort} + * - {@link getErrorCode} + * - {@link getErrorMessage} + * - {@link getLocale} + * + * @author Igor Wiedler + * @author Bernhard Schussek + * + * @internal + */ +abstract class Collator +{ + /* Attribute constants */ + public const FRENCH_COLLATION = 0; + public const ALTERNATE_HANDLING = 1; + public const CASE_FIRST = 2; + public const CASE_LEVEL = 3; + public const NORMALIZATION_MODE = 4; + public const STRENGTH = 5; + public const HIRAGANA_QUATERNARY_MODE = 6; + public const NUMERIC_COLLATION = 7; + + /* Attribute constants values */ + public const DEFAULT_VALUE = -1; + + public const PRIMARY = 0; + public const SECONDARY = 1; + public const TERTIARY = 2; + public const DEFAULT_STRENGTH = 2; + public const QUATERNARY = 3; + public const IDENTICAL = 15; + + public const OFF = 16; + public const ON = 17; + + public const SHIFTED = 20; + public const NON_IGNORABLE = 21; + + public const LOWER_FIRST = 24; + public const UPPER_FIRST = 25; + + /* Sorting options */ + public const SORT_REGULAR = 0; + public const SORT_NUMERIC = 2; + public const SORT_STRING = 1; + + /** + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + */ + public function __construct(?string $locale) + { + if ('en' !== $locale && null !== $locale) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); + } + } + + /** + * Static constructor. + * + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * + * @return static + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + */ + public static function create(?string $locale) + { + return new static($locale); + } + + /** + * Sort array maintaining index association. + * + * @param array &$array Input array + * @param int $flags Flags for sorting, can be one of the following: + * Collator::SORT_REGULAR - compare items normally (don't change types) + * Collator::SORT_NUMERIC - compare items numerically + * Collator::SORT_STRING - compare items as strings + * + * @return bool True on success or false on failure + */ + public function asort(array &$array, int $flags = self::SORT_REGULAR) + { + $intlToPlainFlagMap = [ + self::SORT_REGULAR => \SORT_REGULAR, + self::SORT_NUMERIC => \SORT_NUMERIC, + self::SORT_STRING => \SORT_STRING, + ]; + + $plainSortFlag = $intlToPlainFlagMap[$flags] ?? self::SORT_REGULAR; + + return asort($array, $plainSortFlag); + } + + /** + * Not supported. Compare two Unicode strings. + * + * @return int|false + * + * @see https://php.net/collator.compare + * + * @throws MethodNotImplementedException + */ + public function compare(string $string1, string $string2) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Get a value of an integer collator attribute. + * + * @return int|false The attribute value on success or false on error + * + * @see https://php.net/collator.getattribute + * + * @throws MethodNotImplementedException + */ + public function getAttribute(int $attribute) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns collator's last error code. Always returns the U_ZERO_ERROR class constant value. + * + * @return int|false The error code from last collator call + */ + public function getErrorCode() + { + return Icu::U_ZERO_ERROR; + } + + /** + * Returns collator's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value. + * + * @return string|false The error message from last collator call + */ + public function getErrorMessage() + { + return 'U_ZERO_ERROR'; + } + + /** + * Returns the collator's locale. + * + * @return string|false The locale used to create the collator. Currently + * always returns "en". + */ + public function getLocale(int $type = Locale::ACTUAL_LOCALE) + { + return 'en'; + } + + /** + * Not supported. Get sorting key for a string. + * + * @return string|false The collation key for $string + * + * @see https://php.net/collator.getsortkey + * + * @throws MethodNotImplementedException + */ + public function getSortKey(string $string) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Get current collator's strength. + * + * @return int The current collator's strength or false on failure + * + * @see https://php.net/collator.getstrength + * + * @throws MethodNotImplementedException + */ + public function getStrength() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set a collator's attribute. + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.setattribute + * + * @throws MethodNotImplementedException + */ + public function setAttribute(int $attribute, int $value) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set the collator's strength. + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.setstrength + * + * @throws MethodNotImplementedException + */ + public function setStrength(int $strength) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Sort array using specified collator and sort keys. + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.sortwithsortkeys + * + * @throws MethodNotImplementedException + */ + public function sortWithSortKeys(array &$array) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Sort array using specified collator. + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.sort + * + * @throws MethodNotImplementedException + */ + public function sort(array &$array, int $flags = self::SORT_REGULAR) + { + throw new MethodNotImplementedException(__METHOD__); + } +} diff --git a/vendor/symfony/polyfill-intl-icu/Currencies.php b/vendor/symfony/polyfill-intl-icu/Currencies.php new file mode 100644 index 0000000..90b1efa --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Currencies.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Currencies +{ + private static $data; + + public static function getSymbol(string $currency): ?string + { + $data = self::$data ?? self::$data = require __DIR__.'/Resources/currencies.php'; + + return $data[$currency][0] ?? $data[strtoupper($currency)][0] ?? null; + } + + public static function getFractionDigits(string $currency): int + { + $data = self::$data ?? self::$data = require __DIR__.'/Resources/currencies.php'; + + return $data[$currency][1] ?? $data[strtoupper($currency)][1] ?? $data['DEFAULT'][1]; + } + + public static function getRoundingIncrement(string $currency): int + { + $data = self::$data ?? self::$data = require __DIR__.'/Resources/currencies.php'; + + return $data[$currency][2] ?? $data[strtoupper($currency)][2] ?? $data['DEFAULT'][2]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/AmPmTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/AmPmTransformer.php new file mode 100644 index 0000000..196c604 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/AmPmTransformer.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for AM/PM markers format. + * + * @author Igor Wiedler + * + * @internal + */ +class AmPmTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + return $dateTime->format('A'); + } + + public function getReverseMatchingRegExp(int $length): string + { + return 'AM|PM'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'marker' => $matched, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/DayOfWeekTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/DayOfWeekTransformer.php new file mode 100644 index 0000000..6eedd24 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/DayOfWeekTransformer.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for day of week format. + * + * @author Igor Wiedler + * + * @internal + */ +class DayOfWeekTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + $dayOfWeek = $dateTime->format('l'); + switch ($length) { + case 4: + return $dayOfWeek; + case 5: + return $dayOfWeek[0]; + case 6: + return substr($dayOfWeek, 0, 2); + default: + return substr($dayOfWeek, 0, 3); + } + } + + public function getReverseMatchingRegExp(int $length): string + { + switch ($length) { + case 4: + return 'Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday'; + case 5: + return '[MTWFS]'; + case 6: + return 'Mo|Tu|We|Th|Fr|Sa|Su'; + default: + return 'Mon|Tue|Wed|Thu|Fri|Sat|Sun'; + } + } + + public function extractDateOptions(string $matched, int $length): array + { + return []; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/DayOfYearTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/DayOfYearTransformer.php new file mode 100644 index 0000000..ed78853 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/DayOfYearTransformer.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for day of year format. + * + * @author Igor Wiedler + * + * @internal + */ +class DayOfYearTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + $dayOfYear = (int) $dateTime->format('z') + 1; + + return $this->padLeft($dayOfYear, $length); + } + + public function getReverseMatchingRegExp(int $length): string + { + return '\d{'.$length.'}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return []; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/DayTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/DayTransformer.php new file mode 100644 index 0000000..bdce79e --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/DayTransformer.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for day format. + * + * @author Igor Wiedler + * + * @internal + */ +class DayTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + return $this->padLeft($dateTime->format('j'), $length); + } + + public function getReverseMatchingRegExp(int $length): string + { + return 1 === $length ? '\d{1,2}' : '\d{1,'.$length.'}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'day' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/FullTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/FullTransformer.php new file mode 100644 index 0000000..02d071d --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/FullTransformer.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +use Symfony\Polyfill\Intl\Icu\Exception\NotImplementedException; +use Symfony\Polyfill\Intl\Icu\Icu; + +/** + * Parser and formatter for date formats. + * + * @author Igor Wiedler + * + * @internal + */ +class FullTransformer +{ + private $quoteMatch = "'(?:[^']+|'')*'"; + private $implementedChars = 'MLydQqhDEaHkKmsz'; + private $notImplementedChars = 'GYuwWFgecSAZvVW'; + private $regExp; + + /** + * @var Transformer[] + */ + private $transformers; + + private $pattern; + private $timezone; + + /** + * @param string $pattern The pattern to be used to format and/or parse values + * @param string $timezone The timezone to perform the date/time calculations + */ + public function __construct(string $pattern, string $timezone) + { + $this->pattern = $pattern; + $this->timezone = $timezone; + + $implementedCharsMatch = $this->buildCharsMatch($this->implementedChars); + $notImplementedCharsMatch = $this->buildCharsMatch($this->notImplementedChars); + $this->regExp = "/($this->quoteMatch|$implementedCharsMatch|$notImplementedCharsMatch)/"; + + $this->transformers = [ + 'M' => new MonthTransformer(), + 'L' => new MonthTransformer(), + 'y' => new YearTransformer(), + 'd' => new DayTransformer(), + 'q' => new QuarterTransformer(), + 'Q' => new QuarterTransformer(), + 'h' => new Hour1201Transformer(), + 'D' => new DayOfYearTransformer(), + 'E' => new DayOfWeekTransformer(), + 'a' => new AmPmTransformer(), + 'H' => new Hour2400Transformer(), + 'K' => new Hour1200Transformer(), + 'k' => new Hour2401Transformer(), + 'm' => new MinuteTransformer(), + 's' => new SecondTransformer(), + 'z' => new TimezoneTransformer(), + ]; + } + + /** + * Format a DateTime using ICU dateformat pattern. + * + * @return string The formatted value + */ + public function format(\DateTime $dateTime): string + { + $formatted = preg_replace_callback($this->regExp, function ($matches) use ($dateTime) { + return $this->formatReplace($matches[0], $dateTime); + }, $this->pattern); + + return $formatted; + } + + /** + * Return the formatted ICU value for the matched date characters. + * + * @throws NotImplementedException When it encounters a not implemented date character + */ + private function formatReplace(string $dateChars, \DateTime $dateTime): string + { + $length = \strlen($dateChars); + + if ($this->isQuoteMatch($dateChars)) { + return $this->replaceQuoteMatch($dateChars); + } + + if (isset($this->transformers[$dateChars[0]])) { + $transformer = $this->transformers[$dateChars[0]]; + + return $transformer->format($dateTime, $length); + } + + // handle unimplemented characters + if (false !== strpos($this->notImplementedChars, $dateChars[0])) { + throw new NotImplementedException(sprintf('Unimplemented date character "%s" in format "%s".', $dateChars[0], $this->pattern)); + } + + return ''; + } + + /** + * Parse a pattern based string to a timestamp value. + * + * @param \DateTime $dateTime A configured DateTime object to use to perform the date calculation + * @param string $value String to convert to a time value + * + * @return int|false The corresponding Unix timestamp + * + * @throws \InvalidArgumentException When the value can not be matched with pattern + */ + public function parse(\DateTime $dateTime, string $value) + { + $reverseMatchingRegExp = $this->getReverseMatchingRegExp($this->pattern); + $reverseMatchingRegExp = '/^'.$reverseMatchingRegExp.'$/'; + + $options = []; + + if (preg_match($reverseMatchingRegExp, $value, $matches)) { + $matches = $this->normalizeArray($matches); + + foreach ($this->transformers as $char => $transformer) { + if (isset($matches[$char])) { + $length = \strlen($matches[$char]['pattern']); + $options = array_merge($options, $transformer->extractDateOptions($matches[$char]['value'], $length)); + } + } + + // reset error code and message + Icu::setError(Icu::U_ZERO_ERROR); + + return $this->calculateUnixTimestamp($dateTime, $options); + } + + // behave like the intl extension + Icu::setError(Icu::U_PARSE_ERROR, 'Date parsing failed'); + + return false; + } + + /** + * Retrieve a regular expression to match with a formatted value. + * + * @return string The reverse matching regular expression with named captures being formed by the + * transformer index in the $transformer array + */ + private function getReverseMatchingRegExp(string $pattern): string + { + $escapedPattern = preg_quote($pattern, '/'); + + // ICU 4.8 recognizes slash ("/") in a value to be parsed as a dash ("-") and vice-versa + // when parsing a date/time value + $escapedPattern = preg_replace('/\\\[\-|\/]/', '[\/\-]', $escapedPattern); + + $reverseMatchingRegExp = preg_replace_callback($this->regExp, function ($matches) { + $length = \strlen($matches[0]); + $transformerIndex = $matches[0][0]; + + $dateChars = $matches[0]; + if ($this->isQuoteMatch($dateChars)) { + return $this->replaceQuoteMatch($dateChars); + } + + if (isset($this->transformers[$transformerIndex])) { + $transformer = $this->transformers[$transformerIndex]; + $captureName = str_repeat($transformerIndex, $length); + + return "(?P<$captureName>".$transformer->getReverseMatchingRegExp($length).')'; + } + + return null; + }, $escapedPattern); + + return $reverseMatchingRegExp; + } + + /** + * Check if the first char of a string is a single quote. + */ + private function isQuoteMatch(string $quoteMatch): bool + { + return "'" === $quoteMatch[0]; + } + + /** + * Replaces single quotes at the start or end of a string with two single quotes. + */ + private function replaceQuoteMatch(string $quoteMatch): string + { + if (preg_match("/^'+$/", $quoteMatch)) { + return str_replace("''", "'", $quoteMatch); + } + + return str_replace("''", "'", substr($quoteMatch, 1, -1)); + } + + /** + * Builds a chars match regular expression. + */ + private function buildCharsMatch(string $specialChars): string + { + $specialCharsArray = str_split($specialChars); + + $specialCharsMatch = implode('|', array_map(function ($char) { + return $char.'+'; + }, $specialCharsArray)); + + return $specialCharsMatch; + } + + /** + * Normalize a preg_replace match array, removing the numeric keys and returning an associative array + * with the value and pattern values for the matched Transformer. + */ + private function normalizeArray(array $data): array + { + $ret = []; + + foreach ($data as $key => $value) { + if (!\is_string($key)) { + continue; + } + + $ret[$key[0]] = [ + 'value' => $value, + 'pattern' => $key, + ]; + } + + return $ret; + } + + /** + * Calculates the Unix timestamp based on the matched values by the reverse matching regular + * expression of parse(). + * + * @return bool|int The calculated timestamp or false if matched date is invalid + */ + private function calculateUnixTimestamp(\DateTime $dateTime, array $options) + { + $options = $this->getDefaultValueForOptions($options); + + $year = $options['year']; + $month = $options['month']; + $day = $options['day']; + $hour = $options['hour']; + $hourInstance = $options['hourInstance']; + $minute = $options['minute']; + $second = $options['second']; + $marker = $options['marker']; + $timezone = $options['timezone']; + + // If month is false, return immediately (intl behavior) + if (false === $month) { + Icu::setError(Icu::U_PARSE_ERROR, 'Date parsing failed'); + + return false; + } + + // Normalize hour + if ($hourInstance instanceof HourTransformer) { + $hour = $hourInstance->normalizeHour($hour, $marker); + } + + // Set the timezone if different from the default one + if (null !== $timezone && $timezone !== $this->timezone) { + $dateTime->setTimezone(new \DateTimeZone($timezone)); + } + + // Normalize yy year + preg_match_all($this->regExp, $this->pattern, $matches); + if (\in_array('yy', $matches[0])) { + $dateTime->setTimestamp(time()); + $year = $year > (int) $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year; + } + + $dateTime->setDate($year, $month, $day); + $dateTime->setTime($hour, $minute, $second); + + return $dateTime->getTimestamp(); + } + + /** + * Add sensible default values for missing items in the extracted date/time options array. The values + * are base in the beginning of the Unix era. + */ + private function getDefaultValueForOptions(array $options): array + { + return [ + 'year' => $options['year'] ?? 1970, + 'month' => $options['month'] ?? 1, + 'day' => $options['day'] ?? 1, + 'hour' => $options['hour'] ?? 0, + 'hourInstance' => $options['hourInstance'] ?? null, + 'minute' => $options['minute'] ?? 0, + 'second' => $options['second'] ?? 0, + 'marker' => $options['marker'] ?? null, + 'timezone' => $options['timezone'] ?? null, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/Hour1200Transformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour1200Transformer.php new file mode 100644 index 0000000..68891a7 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour1200Transformer.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for 12 hour format (0-11). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour1200Transformer extends HourTransformer +{ + public function format(\DateTime $dateTime, int $length): string + { + $hourOfDay = $dateTime->format('g'); + $hourOfDay = '12' === $hourOfDay ? '0' : $hourOfDay; + + return $this->padLeft($hourOfDay, $length); + } + + public function normalizeHour(int $hour, ?string $marker = null): int + { + if ('PM' === $marker) { + $hour += 12; + } + + return $hour; + } + + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/Hour1201Transformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour1201Transformer.php new file mode 100644 index 0000000..4ac9b2a --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour1201Transformer.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for 12 hour format (1-12). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour1201Transformer extends HourTransformer +{ + public function format(\DateTime $dateTime, int $length): string + { + return $this->padLeft($dateTime->format('g'), $length); + } + + public function normalizeHour(int $hour, ?string $marker = null): int + { + if ('PM' !== $marker && 12 === $hour) { + $hour = 0; + } elseif ('PM' === $marker && 12 !== $hour) { + // If PM and hour is not 12 (1-12), sum 12 hour + $hour += 12; + } + + return $hour; + } + + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/Hour2400Transformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour2400Transformer.php new file mode 100644 index 0000000..bc259e2 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour2400Transformer.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for 24 hour format (0-23). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour2400Transformer extends HourTransformer +{ + public function format(\DateTime $dateTime, int $length): string + { + return $this->padLeft($dateTime->format('G'), $length); + } + + public function normalizeHour(int $hour, ?string $marker = null): int + { + if ('AM' === $marker) { + $hour = 0; + } elseif ('PM' === $marker) { + $hour = 12; + } + + return $hour; + } + + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/Hour2401Transformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour2401Transformer.php new file mode 100644 index 0000000..f8d3367 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/Hour2401Transformer.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for 24 hour format (1-24). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour2401Transformer extends HourTransformer +{ + public function format(\DateTime $dateTime, int $length): string + { + $hourOfDay = $dateTime->format('G'); + $hourOfDay = '0' === $hourOfDay ? '24' : $hourOfDay; + + return $this->padLeft($hourOfDay, $length); + } + + public function normalizeHour(int $hour, ?string $marker = null): int + { + if ((null === $marker && 24 === $hour) || 'AM' === $marker) { + $hour = 0; + } elseif ('PM' === $marker) { + $hour = 12; + } + + return $hour; + } + + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/HourTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/HourTransformer.php new file mode 100644 index 0000000..e973db1 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/HourTransformer.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Base class for hour transformers. + * + * @author Eriksen Costa + * + * @internal + */ +abstract class HourTransformer extends Transformer +{ + /** + * Returns a normalized hour value suitable for the hour transformer type. + * + * @param int $hour The hour value + * @param string $marker An optional AM/PM marker + * + * @return int The normalized hour value + */ + abstract public function normalizeHour(int $hour, ?string $marker = null): int; +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/MinuteTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/MinuteTransformer.php new file mode 100644 index 0000000..e8bddc6 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/MinuteTransformer.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for minute format. + * + * @author Igor Wiedler + * + * @internal + */ +class MinuteTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + $minuteOfHour = (int) $dateTime->format('i'); + + return $this->padLeft($minuteOfHour, $length); + } + + public function getReverseMatchingRegExp(int $length): string + { + return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'minute' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/MonthTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/MonthTransformer.php new file mode 100644 index 0000000..6712ed2 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/MonthTransformer.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for month format. + * + * @author Igor Wiedler + * + * @internal + */ +class MonthTransformer extends Transformer +{ + protected static $months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + /** + * Short months names (first 3 letters). + */ + protected static $shortMonths = []; + + /** + * Flipped $months array, $name => $index. + */ + protected static $flippedMonths = []; + + /** + * Flipped $shortMonths array, $name => $index. + */ + protected static $flippedShortMonths = []; + + public function __construct() + { + if (0 === \count(self::$shortMonths)) { + self::$shortMonths = array_map(function ($month) { + return substr($month, 0, 3); + }, self::$months); + + self::$flippedMonths = array_flip(self::$months); + self::$flippedShortMonths = array_flip(self::$shortMonths); + } + } + + public function format(\DateTime $dateTime, int $length): string + { + $matchLengthMap = [ + 1 => 'n', + 2 => 'm', + 3 => 'M', + 4 => 'F', + ]; + + if (isset($matchLengthMap[$length])) { + return $dateTime->format($matchLengthMap[$length]); + } + + if (5 === $length) { + return substr($dateTime->format('M'), 0, 1); + } + + return $this->padLeft($dateTime->format('m'), $length); + } + + public function getReverseMatchingRegExp(int $length): string + { + switch ($length) { + case 1: + $regExp = '\d{1,2}'; + break; + case 3: + $regExp = implode('|', self::$shortMonths); + break; + case 4: + $regExp = implode('|', self::$months); + break; + case 5: + $regExp = '[JFMASOND]'; + break; + default: + $regExp = '\d{1,'.$length.'}'; + break; + } + + return $regExp; + } + + public function extractDateOptions(string $matched, int $length): array + { + if (!is_numeric($matched)) { + if (3 === $length) { + $matched = self::$flippedShortMonths[$matched] + 1; + } elseif (4 === $length) { + $matched = self::$flippedMonths[$matched] + 1; + } elseif (5 === $length) { + // IntlDateFormatter::parse() always returns false for MMMMM or LLLLL + $matched = false; + } + } else { + $matched = (int) $matched; + } + + return [ + 'month' => $matched, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/QuarterTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/QuarterTransformer.php new file mode 100644 index 0000000..a549dee --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/QuarterTransformer.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for quarter format. + * + * @author Igor Wiedler + * + * @internal + */ +class QuarterTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + $month = (int) $dateTime->format('n'); + $quarter = (int) floor(($month - 1) / 3) + 1; + switch ($length) { + case 1: + case 2: + return $this->padLeft($quarter, $length); + case 3: + return 'Q'.$quarter; + case 4: + $map = [1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter']; + + return $map[$quarter]; + default: + if (\defined('INTL_ICU_VERSION') && version_compare(\INTL_ICU_VERSION, '70.1', '<')) { + $map = [1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter']; + + return $map[$quarter]; + } else { + return $quarter; + } + } + } + + public function getReverseMatchingRegExp(int $length): string + { + switch ($length) { + case 1: + case 2: + return '\d{'.$length.'}'; + case 3: + return 'Q\d'; + default: + return '(?:1st|2nd|3rd|4th) quarter'; + } + } + + public function extractDateOptions(string $matched, int $length): array + { + return []; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/SecondTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/SecondTransformer.php new file mode 100644 index 0000000..fcb1028 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/SecondTransformer.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for the second format. + * + * @author Igor Wiedler + * + * @internal + */ +class SecondTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + $secondOfMinute = (int) $dateTime->format('s'); + + return $this->padLeft($secondOfMinute, $length); + } + + public function getReverseMatchingRegExp(int $length): string + { + return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'second' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/TimezoneTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/TimezoneTransformer.php new file mode 100644 index 0000000..bab7a96 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/TimezoneTransformer.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +use Symfony\Polyfill\Intl\Icu\Exception\NotImplementedException; + +/** + * Parser and formatter for time zone format. + * + * @author Igor Wiedler + * + * @internal + */ +class TimezoneTransformer extends Transformer +{ + /** + * @throws NotImplementedException When time zone is different than UTC or GMT (Etc/GMT) + */ + public function format(\DateTime $dateTime, int $length): string + { + $timeZone = substr($dateTime->getTimezone()->getName(), 0, 3); + + if (!\in_array($timeZone, ['Etc', 'UTC', 'GMT'])) { + throw new NotImplementedException('Time zone different than GMT or UTC is not supported as a formatting output.'); + } + + if ('Etc' === $timeZone) { + // i.e. Etc/GMT+1, Etc/UTC, Etc/Zulu + $timeZone = substr($dateTime->getTimezone()->getName(), 4); + } + + // From ICU >= 59.1 GMT and UTC are no longer unified + if (\in_array($timeZone, ['UTC', 'UCT', 'Universal', 'Zulu'])) { + // offset is not supported with UTC + return $length > 3 ? 'Coordinated Universal Time' : 'UTC'; + } + + $offset = (int) $dateTime->format('O'); + + // From ICU >= 4.8, the zero offset is no more used, example: GMT instead of GMT+00:00 + if (0 === $offset) { + return $length > 3 ? 'Greenwich Mean Time' : 'GMT'; + } + + if ($length > 3) { + return $dateTime->format('\G\M\TP'); + } + + return sprintf('GMT%s%d', $offset >= 0 ? '+' : '', $offset / 100); + } + + public function getReverseMatchingRegExp(int $length): string + { + return 'GMT[+-]\d{2}:?\d{2}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'timezone' => self::getEtcTimeZoneId($matched), + ]; + } + + /** + * Get an Etc/GMT timezone identifier for the specified timezone. + * + * The PHP documentation for timezones states to not use the 'Other' time zones because them exists + * "for backwards compatibility". However all Etc/GMT time zones are in the tz database 'etcetera' file, + * which indicates they are not deprecated (neither are old names). + * + * Only GMT, Etc/Universal, Etc/Zulu, Etc/Greenwich, Etc/GMT-0, Etc/GMT+0 and Etc/GMT0 are old names and + * are linked to Etc/GMT or Etc/UTC. + * + * @param string $formattedTimeZone A GMT timezone string (GMT-03:00, e.g.) + * + * @return string A timezone identifier + * + * @see https://php.net/timezones.others + * + * @throws NotImplementedException When the GMT time zone have minutes offset different than zero + * @throws \InvalidArgumentException When the value can not be matched with pattern + */ + public static function getEtcTimeZoneId(string $formattedTimeZone): string + { + if (preg_match('/GMT(?P[+-])(?P\d{2}):?(?P\d{2})/', $formattedTimeZone, $matches)) { + $hours = (int) $matches['hours']; + $minutes = (int) $matches['minutes']; + $signal = '-' === $matches['signal'] ? '+' : '-'; + + if (0 < $minutes) { + throw new NotImplementedException(sprintf('It is not possible to use a GMT time zone with minutes offset different than zero (0). GMT time zone tried: "%s".', $formattedTimeZone)); + } + + return 'Etc/GMT'.(0 !== $hours ? $signal.$hours : ''); + } + + throw new \InvalidArgumentException(sprintf('The GMT time zone "%s" does not match with the supported formats GMT[+-]HH:MM or GMT[+-]HHMM.', $formattedTimeZone)); + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/Transformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/Transformer.php new file mode 100644 index 0000000..7f8bf25 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/Transformer.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for date formats. + * + * @author Igor Wiedler + * + * @internal + */ +abstract class Transformer +{ + /** + * Format a value using a configured DateTime as date/time source. + * + * @param \DateTime $dateTime A DateTime object to be used to generate the formatted value + * @param int $length The formatted value string length + * + * @return string The formatted value + */ + abstract public function format(\DateTime $dateTime, int $length): string; + + /** + * Returns a reverse matching regular expression of a string generated by format(). + * + * @param int $length The length of the value to be reverse matched + * + * @return string The reverse matching regular expression + */ + abstract public function getReverseMatchingRegExp(int $length): string; + + /** + * Extract date options from a matched value returned by the processing of the reverse matching + * regular expression. + * + * @param string $matched The matched value + * @param int $length The length of the Transformer pattern string + * + * @return array An associative array + */ + abstract public function extractDateOptions(string $matched, int $length): array; + + /** + * Pad a string with zeros to the left. + * + * @param string $value The string to be padded + * @param int $length The length to pad + * + * @return string The padded string + */ + protected function padLeft(string $value, int $length): string + { + return str_pad($value, $length, '0', \STR_PAD_LEFT); + } +} diff --git a/vendor/symfony/polyfill-intl-icu/DateFormat/YearTransformer.php b/vendor/symfony/polyfill-intl-icu/DateFormat/YearTransformer.php new file mode 100644 index 0000000..a27ce85 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/DateFormat/YearTransformer.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\DateFormat; + +/** + * Parser and formatter for year format. + * + * @author Igor Wiedler + * + * @internal + */ +class YearTransformer extends Transformer +{ + public function format(\DateTime $dateTime, int $length): string + { + if (2 === $length) { + return $dateTime->format('y'); + } + + return $this->padLeft($dateTime->format('Y'), $length); + } + + public function getReverseMatchingRegExp(int $length): string + { + return 2 === $length ? '\d{2}' : '\d{1,4}'; + } + + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'year' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/Exception/ExceptionInterface.php b/vendor/symfony/polyfill-intl-icu/Exception/ExceptionInterface.php new file mode 100644 index 0000000..a453b5e --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\Exception; + +/** + * Base ExceptionInterface for the Intl component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/polyfill-intl-icu/Exception/MethodArgumentNotImplementedException.php b/vendor/symfony/polyfill-intl-icu/Exception/MethodArgumentNotImplementedException.php new file mode 100644 index 0000000..db120a3 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Exception/MethodArgumentNotImplementedException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\Exception; + +/** + * @author Eriksen Costa + */ +class MethodArgumentNotImplementedException extends NotImplementedException +{ + /** + * @param string $methodName The method name that raised the exception + * @param string $argName The argument name that is not implemented + */ + public function __construct(string $methodName, string $argName) + { + $message = sprintf('The %s() method\'s argument $%s behavior is not implemented.', $methodName, $argName); + parent::__construct($message); + } +} diff --git a/vendor/symfony/polyfill-intl-icu/Exception/MethodArgumentValueNotImplementedException.php b/vendor/symfony/polyfill-intl-icu/Exception/MethodArgumentValueNotImplementedException.php new file mode 100644 index 0000000..bd92042 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Exception/MethodArgumentValueNotImplementedException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\Exception; + +/** + * @author Eriksen Costa + */ +class MethodArgumentValueNotImplementedException extends NotImplementedException +{ + /** + * @param string $methodName The method name that raised the exception + * @param string $argName The argument name + * @param mixed $argValue The argument value that is not implemented + * @param string $additionalMessage An optional additional message to append to the exception message + */ + public function __construct(string $methodName, string $argName, $argValue, string $additionalMessage = '') + { + $message = sprintf( + 'The %s() method\'s argument $%s value %s behavior is not implemented.%s', + $methodName, + $argName, + var_export($argValue, true), + '' !== $additionalMessage ? ' '.$additionalMessage.'. ' : '' + ); + + parent::__construct($message); + } +} diff --git a/vendor/symfony/polyfill-intl-icu/Exception/MethodNotImplementedException.php b/vendor/symfony/polyfill-intl-icu/Exception/MethodNotImplementedException.php new file mode 100644 index 0000000..9e1a439 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Exception/MethodNotImplementedException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\Exception; + +/** + * @author Eriksen Costa + */ +class MethodNotImplementedException extends NotImplementedException +{ + /** + * @param string $methodName The name of the method + */ + public function __construct(string $methodName) + { + parent::__construct(sprintf('The %s() is not implemented.', $methodName)); + } +} diff --git a/vendor/symfony/polyfill-intl-icu/Exception/NotImplementedException.php b/vendor/symfony/polyfill-intl-icu/Exception/NotImplementedException.php new file mode 100644 index 0000000..929b933 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Exception/NotImplementedException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\Exception; + +/** + * Base exception class for not implemented behaviors of the intl extension in the Locale component. + * + * @author Eriksen Costa + */ +class NotImplementedException extends RuntimeException +{ + public const INTL_INSTALL_MESSAGE = 'Please install the "intl" extension for full localization capabilities.'; + + /** + * @param string $message The exception message. A note to install the intl extension is appended to this string + */ + public function __construct(string $message) + { + parent::__construct($message.' '.self::INTL_INSTALL_MESSAGE); + } +} diff --git a/vendor/symfony/polyfill-intl-icu/Exception/RuntimeException.php b/vendor/symfony/polyfill-intl-icu/Exception/RuntimeException.php new file mode 100644 index 0000000..ceedffe --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu\Exception; + +/** + * RuntimeException for the Intl component. + * + * @author Bernhard Schussek + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/polyfill-intl-icu/Icu.php b/vendor/symfony/polyfill-intl-icu/Icu.php new file mode 100644 index 0000000..b9590f4 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Icu.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu; + +/** + * Provides fake static versions of the global functions in the intl extension. + * + * @author Bernhard Schussek + * + * @internal + */ +abstract class Icu +{ + /** + * Indicates that no error occurred. + */ + public const U_ZERO_ERROR = 0; + + /** + * Indicates that an invalid argument was passed. + */ + public const U_ILLEGAL_ARGUMENT_ERROR = 1; + + /** + * Indicates that the parse() operation failed. + */ + public const U_PARSE_ERROR = 9; + + /** + * All known error codes. + */ + private static $errorCodes = [ + self::U_ZERO_ERROR => 'U_ZERO_ERROR', + self::U_ILLEGAL_ARGUMENT_ERROR => 'U_ILLEGAL_ARGUMENT_ERROR', + self::U_PARSE_ERROR => 'U_PARSE_ERROR', + ]; + + /** + * The error code of the last operation. + */ + private static $errorCode = self::U_ZERO_ERROR; + + /** + * The error code of the last operation. + */ + private static $errorMessage = 'U_ZERO_ERROR'; + + /** + * Returns whether the error code indicates a failure. + * + * @param int $errorCode The error code returned by Icu::getErrorCode() + */ + public static function isFailure(int $errorCode): bool + { + return isset(self::$errorCodes[$errorCode]) + && $errorCode > self::U_ZERO_ERROR; + } + + /** + * Returns the error code of the last operation. + * + * Returns Icu::U_ZERO_ERROR if no error occurred. + * + * @return int + */ + public static function getErrorCode() + { + return self::$errorCode; + } + + /** + * Returns the error message of the last operation. + * + * Returns "U_ZERO_ERROR" if no error occurred. + */ + public static function getErrorMessage(): string + { + return self::$errorMessage; + } + + /** + * Returns the symbolic name for a given error code. + * + * @param int $code The error code returned by Icu::getErrorCode() + */ + public static function getErrorName(int $code): string + { + return self::$errorCodes[$code] ?? '[BOGUS UErrorCode]'; + } + + /** + * Sets the current error. + * + * @param int $code One of the error constants in this class + * @param string $message The ICU class error message + * + * @throws \InvalidArgumentException If the code is not one of the error constants in this class + */ + public static function setError(int $code, string $message = '') + { + if (!isset(self::$errorCodes[$code])) { + throw new \InvalidArgumentException(sprintf('No such error code: "%s".', $code)); + } + + self::$errorMessage = $message ? sprintf('%s: %s', $message, self::$errorCodes[$code]) : self::$errorCodes[$code]; + self::$errorCode = $code; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/IntlDateFormatter.php b/vendor/symfony/polyfill-intl-icu/IntlDateFormatter.php new file mode 100644 index 0000000..b2674f9 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/IntlDateFormatter.php @@ -0,0 +1,645 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu; + +use Symfony\Polyfill\Intl\Icu\DateFormat\FullTransformer; +use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentNotImplementedException; +use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentValueNotImplementedException; +use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException; + +/** + * Replacement for PHP's native {@link \IntlDateFormatter} class. + * + * The only methods currently supported in this class are: + * + * - {@link __construct} + * - {@link create} + * - {@link format} + * - {@link getCalendar} + * - {@link getDateType} + * - {@link getErrorCode} + * - {@link getErrorMessage} + * - {@link getLocale} + * - {@link getPattern} + * - {@link getTimeType} + * - {@link getTimeZoneId} + * - {@link isLenient} + * - {@link parse} + * - {@link setLenient} + * - {@link setPattern} + * - {@link setTimeZoneId} + * - {@link setTimeZone} + * + * @author Igor Wiedler + * @author Bernhard Schussek + * + * @internal + */ +abstract class IntlDateFormatter +{ + /** + * The error code from the last operation. + * + * @var int + */ + protected $errorCode = Icu::U_ZERO_ERROR; + + /** + * The error message from the last operation. + * + * @var string + */ + protected $errorMessage = 'U_ZERO_ERROR'; + + /* date/time format types */ + public const NONE = -1; + public const FULL = 0; + public const LONG = 1; + public const MEDIUM = 2; + public const SHORT = 3; + + /* date format types */ + public const RELATIVE_FULL = 128; + public const RELATIVE_LONG = 129; + public const RELATIVE_MEDIUM = 130; + public const RELATIVE_SHORT = 131; + + /* calendar formats */ + public const TRADITIONAL = 0; + public const GREGORIAN = 1; + + /** + * Patterns used to format the date when no pattern is provided. + */ + private $defaultDateFormats = [ + self::NONE => '', + self::FULL => 'EEEE, MMMM d, y', + self::LONG => 'MMMM d, y', + self::MEDIUM => 'MMM d, y', + self::SHORT => 'M/d/yy', + self::RELATIVE_FULL => 'EEEE, MMMM d, y', + self::RELATIVE_LONG => 'MMMM d, y', + self::RELATIVE_MEDIUM => 'MMM d, y', + self::RELATIVE_SHORT => 'M/d/yy', + ]; + + /** + * Patterns used to format the time when no pattern is provided. + */ + private $defaultTimeFormats = [ + self::FULL => 'h:mm:ss a zzzz', + self::LONG => 'h:mm:ss a z', + self::MEDIUM => 'h:mm:ss a', + self::SHORT => 'h:mm a', + ]; + + private $dateType; + private $timeType; + + /** + * @var string + */ + private $pattern; + + /** + * @var \DateTimeZone + */ + private $dateTimeZone; + + /** + * @var bool + */ + private $uninitializedTimeZoneId = false; + + /** + * @var string + */ + private $timezoneId; + + /** + * @var bool + */ + private $isRelativeDateType = false; + + /** + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier + * @param \IntlCalendar|int|null $calendar Calendar to use for formatting or parsing. The only currently + * supported value is IntlDateFormatter::GREGORIAN (or null using the default calendar, i.e. "GREGORIAN") + * + * @see https://php.net/intldateformatter.create + * @see http://userguide.icu-project.org/formatparse/datetime + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When $calendar different than GREGORIAN is passed + */ + public function __construct(?string $locale, ?int $dateType, ?int $timeType, $timezone = null, $calendar = null, ?string $pattern = '') + { + if ('en' !== $locale && null !== $locale) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); + } + + if (self::GREGORIAN !== $calendar && null !== $calendar) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'calendar', $calendar, 'Only the GREGORIAN calendar is supported'); + } + + if (\PHP_VERSION_ID >= 80100) { + if (null === $dateType) { + @trigger_error('Passing null to parameter #2 ($dateType) of type int is deprecated', \E_USER_DEPRECATED); + } + + if (null === $timeType) { + @trigger_error('Passing null to parameter #3 ($timeType) of type int is deprecated', \E_USER_DEPRECATED); + } + } + + $this->dateType = $dateType ?? self::FULL; + $this->timeType = $timeType ?? self::FULL; + + if ('' === ($pattern ?? '')) { + $pattern = $this->getDefaultPattern(); + } + + $this->setPattern($pattern); + $this->setTimeZone($timezone); + + if (\in_array($this->dateType, [self::RELATIVE_FULL, self::RELATIVE_LONG, self::RELATIVE_MEDIUM, self::RELATIVE_SHORT], true)) { + $this->isRelativeDateType = true; + } + } + + /** + * Static constructor. + * + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier + * @param \IntlCalendar|int|null $calendar Calendar to use for formatting or parsing; default is Gregorian + * One of the calendar constants + * + * @return static + * + * @see https://php.net/intldateformatter.create + * @see http://userguide.icu-project.org/formatparse/datetime + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When $calendar different than GREGORIAN is passed + */ + public static function create(?string $locale, ?int $dateType, ?int $timeType, $timezone = null, ?int $calendar = null, ?string $pattern = '') + { + return new static($locale, $dateType, $timeType, $timezone, $calendar, $pattern); + } + + /** + * Format the date/time value (timestamp) as a string. + * + * @param int|string|\DateTimeInterface $datetime The timestamp to format + * + * @return string|false The formatted value or false if formatting failed + * + * @see https://php.net/intldateformatter.format + * + * @throws MethodArgumentValueNotImplementedException If one of the formatting characters is not implemented + */ + public function format($datetime) + { + // intl allows timestamps to be passed as arrays - we don't + if (\is_array($datetime)) { + $message = 'Only Unix timestamps and DateTime objects are supported'; + + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'datetime', $datetime, $message); + } + + if (\is_string($datetime) && $dt = \DateTime::createFromFormat('U', $datetime)) { + $datetime = $dt; + } + + // behave like the intl extension + $argumentError = null; + if (!\is_int($datetime) && !$datetime instanceof \DateTimeInterface) { + $argumentError = sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $datetime); + } + + if (null !== $argumentError) { + Icu::setError(Icu::U_ILLEGAL_ARGUMENT_ERROR, $argumentError); + $this->errorCode = Icu::getErrorCode(); + $this->errorMessage = Icu::getErrorMessage(); + + return false; + } + + if ($datetime instanceof \DateTimeInterface) { + $datetime = $datetime->format('U'); + } + + $pattern = $this->getPattern(); + $formatted = ''; + + if ($this->isRelativeDateType && $formatted = $this->getRelativeDateFormat($datetime)) { + if (self::NONE === $this->timeType) { + $pattern = ''; + } else { + $pattern = $this->defaultTimeFormats[$this->timeType]; + if (\in_array($this->dateType, [self::RELATIVE_MEDIUM, self::RELATIVE_SHORT], true)) { + $formatted .= ', '; + } else { + $formatted .= ' at '; + } + } + } + + $transformer = new FullTransformer($pattern, $this->getTimeZoneId()); + $formatted .= $transformer->format($this->createDateTime($datetime)); + + // behave like the intl extension + Icu::setError(Icu::U_ZERO_ERROR); + $this->errorCode = Icu::getErrorCode(); + $this->errorMessage = Icu::getErrorMessage(); + + return $formatted; + } + + /** + * Not supported. Formats an object. + * + * @return string The formatted value + * + * @see https://php.net/intldateformatter.formatobject + * + * @throws MethodNotImplementedException + */ + public static function formatObject($datetime, $format = null, ?string $locale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns the formatter's calendar. + * + * @return int The calendar being used by the formatter. Currently always returns + * IntlDateFormatter::GREGORIAN. + * + * @see https://php.net/intldateformatter.getcalendar + */ + public function getCalendar() + { + return self::GREGORIAN; + } + + /** + * Not supported. Returns the formatter's calendar object. + * + * @return object The calendar's object being used by the formatter + * + * @see https://php.net/intldateformatter.getcalendarobject + * + * @throws MethodNotImplementedException + */ + public function getCalendarObject() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns the formatter's datetype. + * + * @return int The current value of the formatter + * + * @see https://php.net/intldateformatter.getdatetype + */ + public function getDateType() + { + return $this->dateType; + } + + /** + * Returns formatter's last error code. Always returns the U_ZERO_ERROR class constant value. + * + * @return int The error code from last formatter call + * + * @see https://php.net/intldateformatter.geterrorcode + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * Returns formatter's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value. + * + * @return string The error message from last formatter call + * + * @see https://php.net/intldateformatter.geterrormessage + */ + public function getErrorMessage() + { + return $this->errorMessage; + } + + /** + * Returns the formatter's locale. + * + * @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE) + * + * @return string The locale used to create the formatter. Currently always + * returns "en". + * + * @see https://php.net/intldateformatter.getlocale + */ + public function getLocale(int $type = Locale::ACTUAL_LOCALE) + { + return 'en'; + } + + /** + * Returns the formatter's pattern. + * + * @return string The pattern string used by the formatter + * + * @see https://php.net/intldateformatter.getpattern + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Returns the formatter's time type. + * + * @return int The time type used by the formatter + * + * @see https://php.net/intldateformatter.gettimetype + */ + public function getTimeType() + { + return $this->timeType; + } + + /** + * Returns the formatter's timezone identifier. + * + * @return string The timezone identifier used by the formatter + * + * @see https://php.net/intldateformatter.gettimezoneid + */ + public function getTimeZoneId() + { + if (!$this->uninitializedTimeZoneId) { + return $this->timezoneId; + } + + return date_default_timezone_get(); + } + + /** + * Not supported. Returns the formatter's timezone. + * + * @return mixed The timezone used by the formatter + * + * @see https://php.net/intldateformatter.gettimezone + * + * @throws MethodNotImplementedException + */ + public function getTimeZone() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns whether the formatter is lenient. + * + * @return bool Currently always returns false + * + * @see https://php.net/intldateformatter.islenient + * + * @throws MethodNotImplementedException + */ + public function isLenient() + { + return false; + } + + /** + * Not supported. Parse string to a field-based time value. + * + * @return string Localtime compatible array of integers: contains 24 hour clock value in tm_hour field + * + * @see https://php.net/intldateformatter.localtime + * + * @throws MethodNotImplementedException + */ + public function localtime(string $string, &$offset = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Parse string to a timestamp value. + * + * @return int|false Parsed value as a timestamp + * + * @see https://php.net/intldateformatter.parse + * + * @throws MethodArgumentNotImplementedException When $offset different than null, behavior not implemented + */ + public function parse(string $string, &$offset = null) + { + // We don't calculate the position when parsing the value + if (null !== $offset) { + throw new MethodArgumentNotImplementedException(__METHOD__, 'offset'); + } + + $dateTime = $this->createDateTime(0); + $transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId()); + + $timestamp = $transformer->parse($dateTime, $string); + + // behave like the intl extension. FullTransformer::parse() set the proper error + $this->errorCode = Icu::getErrorCode(); + $this->errorMessage = Icu::getErrorMessage(); + + return $timestamp; + } + + /** + * Not supported. Set the formatter's calendar. + * + * @param \IntlCalendar|int|null $calendar + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.setcalendar + * + * @throws MethodNotImplementedException + */ + public function setCalendar($calendar) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Set the leniency of the parser. + * + * Define if the parser is strict or lenient in interpreting inputs that do not match the pattern + * exactly. Enabling lenient parsing allows the parser to accept otherwise flawed date or time + * patterns, parsing as much as possible to obtain a value. Extra space, unrecognized tokens, or + * invalid values ("February 30th") are not accepted. + * + * @param bool $lenient Sets whether the parser is lenient or not. Currently + * only false (strict) is supported. + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.setlenient + * + * @throws MethodArgumentValueNotImplementedException When $lenient is true + */ + public function setLenient(bool $lenient) + { + if ($lenient) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'lenient', $lenient, 'Only the strict parser is supported'); + } + + return true; + } + + /** + * Set the formatter's pattern. + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.setpattern + * @see http://userguide.icu-project.org/formatparse/datetime + */ + public function setPattern(string $pattern) + { + $this->pattern = $pattern; + + return true; + } + + /** + * Sets formatterʼs timezone. + * + * @param \IntlTimeZone|\DateTimeZone|string|null $timezone + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.settimezone + */ + public function setTimeZone($timezone) + { + if ($timezone instanceof \IntlTimeZone) { + $timezone = $timezone->getID(); + } + + if ($timezone instanceof \DateTimeZone) { + $timezone = $timezone->getName(); + + // DateTimeZone returns the GMT offset timezones without the leading GMT, while our parsing requires it. + if (!empty($timezone) && ('+' === $timezone[0] || '-' === $timezone[0])) { + $timezone = 'GMT'.$timezone; + } + } + + if (null === $timezone) { + $timezone = date_default_timezone_get(); + + $this->uninitializedTimeZoneId = true; + } + + // Backup original passed time zone + $timezoneId = $timezone; + + // Get an Etc/GMT time zone that is accepted for \DateTimeZone + if ('GMT' !== $timezone && 0 === strpos($timezone, 'GMT')) { + try { + $timezone = DateFormat\TimezoneTransformer::getEtcTimeZoneId($timezone); + } catch (\InvalidArgumentException $e) { + // Does nothing, will fallback to UTC + } + } + + try { + $this->dateTimeZone = new \DateTimeZone($timezone); + if ('GMT' !== $timezone && $this->dateTimeZone->getName() !== $timezone) { + $timezoneId = $this->getTimeZoneId(); + } + } catch (\Exception $e) { + $timezoneId = $timezone = $this->getTimeZoneId(); + $this->dateTimeZone = new \DateTimeZone($timezone); + } + + $this->timezoneId = $timezoneId; + + return true; + } + + /** + * Create and returns a DateTime object with the specified timestamp and with the + * current time zone. + * + * @return \DateTime + */ + protected function createDateTime($timestamp) + { + $dateTime = \DateTime::createFromFormat('U', $timestamp); + $dateTime->setTimezone($this->dateTimeZone); + + return $dateTime; + } + + /** + * Returns a pattern string based in the datetype and timetype values. + * + * @return string + */ + protected function getDefaultPattern() + { + $pattern = ''; + if (self::NONE !== $this->dateType) { + $pattern = $this->defaultDateFormats[$this->dateType]; + } + if (self::NONE !== $this->timeType) { + if (\in_array($this->dateType, [self::FULL, self::LONG, self::RELATIVE_FULL, self::RELATIVE_LONG], true)) { + $pattern .= ' \'at\' '; + } elseif (self::NONE !== $this->dateType) { + $pattern .= ', '; + } + $pattern .= $this->defaultTimeFormats[$this->timeType]; + } + + return $pattern; + } + + private function getRelativeDateFormat(int $timestamp): string + { + $today = $this->createDateTime(time()); + $today->setTime(0, 0, 0); + + $datetime = $this->createDateTime($timestamp); + $datetime->setTime(0, 0, 0); + + $interval = $today->diff($datetime); + + if (false !== $interval) { + if (0 === $interval->days) { + return 'today'; + } + + if (1 === $interval->days) { + return 1 === $interval->invert ? 'yesterday' : 'tomorrow'; + } + } + + return ''; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/LICENSE b/vendor/symfony/polyfill-intl-icu/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-intl-icu/Locale.php b/vendor/symfony/polyfill-intl-icu/Locale.php new file mode 100644 index 0000000..f449fd5 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Locale.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu; + +use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException; + +/** + * Replacement for PHP's native {@link \Locale} class. + * + * The only methods supported in this class are `getDefault` and `canonicalize`. + * All other methods will throw an exception when used. + * + * @author Eriksen Costa + * @author Bernhard Schussek + * + * @internal + */ +abstract class Locale +{ + public const DEFAULT_LOCALE = null; + + /* Locale method constants */ + public const ACTUAL_LOCALE = 0; + public const VALID_LOCALE = 1; + + /* Language tags constants */ + public const LANG_TAG = 'language'; + public const EXTLANG_TAG = 'extlang'; + public const SCRIPT_TAG = 'script'; + public const REGION_TAG = 'region'; + public const VARIANT_TAG = 'variant'; + public const GRANDFATHERED_LANG_TAG = 'grandfathered'; + public const PRIVATE_TAG = 'private'; + + /** + * Not supported. Returns the best available locale based on HTTP "Accept-Language" header according to RFC 2616. + * + * @return string The corresponding locale code + * + * @see https://php.net/locale.acceptfromhttp + * + * @throws MethodNotImplementedException + */ + public static function acceptFromHttp(string $header) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns a canonicalized locale string. + * + * This polyfill doesn't implement the full-spec algorithm. It only + * canonicalizes locale strings handled by the `LocaleBundle` class. + * + * @return string + */ + public static function canonicalize(string $locale) + { + if ('' === $locale || '.' === $locale[0]) { + return self::getDefault(); + } + + if (!preg_match('/^([a-z]{2})[-_]([a-z]{2})(?:([a-z]{2})(?:[-_]([a-z]{2}))?)?(?:\..*)?$/i', $locale, $m)) { + return $locale; + } + + if (!empty($m[4])) { + return strtolower($m[1]).'_'.ucfirst(strtolower($m[2].$m[3])).'_'.strtoupper($m[4]); + } + + if (!empty($m[3])) { + return strtolower($m[1]).'_'.ucfirst(strtolower($m[2].$m[3])); + } + + return strtolower($m[1]).'_'.strtoupper($m[2]); + } + + /** + * Not supported. Returns a correctly ordered and delimited locale code. + * + * @return string The corresponding locale code + * + * @see https://php.net/locale.composelocale + * + * @throws MethodNotImplementedException + */ + public static function composeLocale(array $subtags) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Checks if a language tag filter matches with locale. + * + * @return string The corresponding locale code + * + * @see https://php.net/locale.filtermatches + * + * @throws MethodNotImplementedException + */ + public static function filterMatches(string $languageTag, string $locale, bool $canonicalize = false) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the variants for the input locale. + * + * @return array The locale variants + * + * @see https://php.net/locale.getallvariants + * + * @throws MethodNotImplementedException + */ + public static function getAllVariants(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns the default locale. + * + * @return string The default locale code. Always returns 'en' + * + * @see https://php.net/locale.getdefault + */ + public static function getDefault() + { + return 'en'; + } + + /** + * Not supported. Returns the localized display name for the locale language. + * + * @return string The localized language display name + * + * @see https://php.net/locale.getdisplaylanguage + * + * @throws MethodNotImplementedException + */ + public static function getDisplayLanguage(string $locale, ?string $displayLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale. + * + * @return string The localized locale display name + * + * @see https://php.net/locale.getdisplayname + * + * @throws MethodNotImplementedException + */ + public static function getDisplayName(string $locale, ?string $displayLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale region. + * + * @return string The localized region display name + * + * @see https://php.net/locale.getdisplayregion + * + * @throws MethodNotImplementedException + */ + public static function getDisplayRegion(string $locale, ?string $displayLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale script. + * + * @return string The localized script display name + * + * @see https://php.net/locale.getdisplayscript + * + * @throws MethodNotImplementedException + */ + public static function getDisplayScript(string $locale, ?string $displayLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale variant. + * + * @return string The localized variant display name + * + * @see https://php.net/locale.getdisplayvariant + * + * @throws MethodNotImplementedException + */ + public static function getDisplayVariant(string $locale, ?string $displayLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the keywords for the locale. + * + * @return array Associative array with the extracted variants + * + * @see https://php.net/locale.getkeywords + * + * @throws MethodNotImplementedException + */ + public static function getKeywords(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the primary language for the locale. + * + * @return string|null The extracted language code or null in case of error + * + * @see https://php.net/locale.getprimarylanguage + * + * @throws MethodNotImplementedException + */ + public static function getPrimaryLanguage(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the region for the locale. + * + * @return string|null The extracted region code or null if not present + * + * @see https://php.net/locale.getregion + * + * @throws MethodNotImplementedException + */ + public static function getRegion(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the script for the locale. + * + * @return string|null The extracted script code or null if not present + * + * @see https://php.net/locale.getscript + * + * @throws MethodNotImplementedException + */ + public static function getScript(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the closest language tag for the locale. + * + * @see https://php.net/locale.lookup + * + * @throws MethodNotImplementedException + */ + public static function lookup(array $languageTag, string $locale, bool $canonicalize = false, ?string $defaultLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns an associative array of locale identifier subtags. + * + * @return array|null Associative array with the extracted subtags + * + * @see https://php.net/locale.parselocale + * + * @throws MethodNotImplementedException + */ + public static function parseLocale(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Sets the default runtime locale. + * + * @return bool true on success or false on failure + * + * @see https://php.net/locale.setdefault + * + * @throws MethodNotImplementedException + */ + public static function setDefault(string $locale) + { + if ('en' !== $locale) { + throw new MethodNotImplementedException(__METHOD__); + } + + return true; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/NumberFormatter.php b/vendor/symfony/polyfill-intl-icu/NumberFormatter.php new file mode 100644 index 0000000..5b24706 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/NumberFormatter.php @@ -0,0 +1,835 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Icu; + +use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentNotImplementedException; +use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentValueNotImplementedException; +use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException; +use Symfony\Polyfill\Intl\Icu\Exception\NotImplementedException; + +/** + * Replacement for PHP's native {@link \NumberFormatter} class. + * + * The only methods currently supported in this class are: + * + * - {@link __construct} + * - {@link create} + * - {@link formatCurrency} + * - {@link format} + * - {@link getAttribute} + * - {@link getErrorCode} + * - {@link getErrorMessage} + * - {@link getLocale} + * - {@link parse} + * - {@link setAttribute} + * + * @author Eriksen Costa + * @author Bernhard Schussek + * + * @internal + */ +abstract class NumberFormatter +{ + /* Format style constants */ + public const PATTERN_DECIMAL = 0; + public const DECIMAL = 1; + public const CURRENCY = 2; + public const PERCENT = 3; + public const SCIENTIFIC = 4; + public const SPELLOUT = 5; + public const ORDINAL = 6; + public const DURATION = 7; + public const PATTERN_RULEBASED = 9; + public const IGNORE = 0; + public const DEFAULT_STYLE = 1; + + /* Format type constants */ + public const TYPE_DEFAULT = 0; + public const TYPE_INT32 = 1; + public const TYPE_INT64 = 2; + public const TYPE_DOUBLE = 3; + public const TYPE_CURRENCY = 4; + + /* Numeric attribute constants */ + public const PARSE_INT_ONLY = 0; + public const GROUPING_USED = 1; + public const DECIMAL_ALWAYS_SHOWN = 2; + public const MAX_INTEGER_DIGITS = 3; + public const MIN_INTEGER_DIGITS = 4; + public const INTEGER_DIGITS = 5; + public const MAX_FRACTION_DIGITS = 6; + public const MIN_FRACTION_DIGITS = 7; + public const FRACTION_DIGITS = 8; + public const MULTIPLIER = 9; + public const GROUPING_SIZE = 10; + public const ROUNDING_MODE = 11; + public const ROUNDING_INCREMENT = 12; + public const FORMAT_WIDTH = 13; + public const PADDING_POSITION = 14; + public const SECONDARY_GROUPING_SIZE = 15; + public const SIGNIFICANT_DIGITS_USED = 16; + public const MIN_SIGNIFICANT_DIGITS = 17; + public const MAX_SIGNIFICANT_DIGITS = 18; + public const LENIENT_PARSE = 19; + + /* Text attribute constants */ + public const POSITIVE_PREFIX = 0; + public const POSITIVE_SUFFIX = 1; + public const NEGATIVE_PREFIX = 2; + public const NEGATIVE_SUFFIX = 3; + public const PADDING_CHARACTER = 4; + public const CURRENCY_CODE = 5; + public const DEFAULT_RULESET = 6; + public const PUBLIC_RULESETS = 7; + + /* Format symbol constants */ + public const DECIMAL_SEPARATOR_SYMBOL = 0; + public const GROUPING_SEPARATOR_SYMBOL = 1; + public const PATTERN_SEPARATOR_SYMBOL = 2; + public const PERCENT_SYMBOL = 3; + public const ZERO_DIGIT_SYMBOL = 4; + public const DIGIT_SYMBOL = 5; + public const MINUS_SIGN_SYMBOL = 6; + public const PLUS_SIGN_SYMBOL = 7; + public const CURRENCY_SYMBOL = 8; + public const INTL_CURRENCY_SYMBOL = 9; + public const MONETARY_SEPARATOR_SYMBOL = 10; + public const EXPONENTIAL_SYMBOL = 11; + public const PERMILL_SYMBOL = 12; + public const PAD_ESCAPE_SYMBOL = 13; + public const INFINITY_SYMBOL = 14; + public const NAN_SYMBOL = 15; + public const SIGNIFICANT_DIGIT_SYMBOL = 16; + public const MONETARY_GROUPING_SEPARATOR_SYMBOL = 17; + + /* Rounding mode values used by NumberFormatter::setAttribute() with NumberFormatter::ROUNDING_MODE attribute */ + public const ROUND_CEILING = 0; + public const ROUND_FLOOR = 1; + public const ROUND_DOWN = 2; + public const ROUND_UP = 3; + public const ROUND_HALFEVEN = 4; + public const ROUND_HALFDOWN = 5; + public const ROUND_HALFUP = 6; + + /* Pad position values used by NumberFormatter::setAttribute() with NumberFormatter::PADDING_POSITION attribute */ + public const PAD_BEFORE_PREFIX = 0; + public const PAD_AFTER_PREFIX = 1; + public const PAD_BEFORE_SUFFIX = 2; + public const PAD_AFTER_SUFFIX = 3; + + /** + * The error code from the last operation. + * + * @var int + */ + protected $errorCode = Icu::U_ZERO_ERROR; + + /** + * The error message from the last operation. + * + * @var string + */ + protected $errorMessage = 'U_ZERO_ERROR'; + + /** + * @var int + */ + private $style; + + /** + * Default values for the en locale. + */ + private $attributes = [ + self::FRACTION_DIGITS => 0, + self::GROUPING_USED => 1, + self::ROUNDING_MODE => self::ROUND_HALFEVEN, + ]; + + /** + * Holds the initialized attributes code. + */ + private $initializedAttributes = []; + + /** + * The supported styles to the constructor $styles argument. + */ + private static $supportedStyles = [ + 'CURRENCY' => self::CURRENCY, + 'DECIMAL' => self::DECIMAL, + ]; + + /** + * Supported attributes to the setAttribute() $attr argument. + */ + private static $supportedAttributes = [ + 'FRACTION_DIGITS' => self::FRACTION_DIGITS, + 'GROUPING_USED' => self::GROUPING_USED, + 'ROUNDING_MODE' => self::ROUNDING_MODE, + ]; + + /** + * The available rounding modes for setAttribute() usage with + * NumberFormatter::ROUNDING_MODE. NumberFormatter::ROUND_DOWN + * and NumberFormatter::ROUND_UP does not have a PHP only equivalent. + */ + private static $roundingModes = [ + 'ROUND_HALFEVEN' => self::ROUND_HALFEVEN, + 'ROUND_HALFDOWN' => self::ROUND_HALFDOWN, + 'ROUND_HALFUP' => self::ROUND_HALFUP, + 'ROUND_CEILING' => self::ROUND_CEILING, + 'ROUND_FLOOR' => self::ROUND_FLOOR, + 'ROUND_DOWN' => self::ROUND_DOWN, + 'ROUND_UP' => self::ROUND_UP, + ]; + + /** + * The mapping between NumberFormatter rounding modes to the available + * modes in PHP's round() function. + * + * @see https://php.net/round + */ + private static $phpRoundingMap = [ + self::ROUND_HALFDOWN => \PHP_ROUND_HALF_DOWN, + self::ROUND_HALFEVEN => \PHP_ROUND_HALF_EVEN, + self::ROUND_HALFUP => \PHP_ROUND_HALF_UP, + ]; + + /** + * The list of supported rounding modes which aren't available modes in + * PHP's round() function, but there's an equivalent. Keys are rounding + * modes, values does not matter. + */ + private static $customRoundingList = [ + self::ROUND_CEILING => true, + self::ROUND_FLOOR => true, + self::ROUND_DOWN => true, + self::ROUND_UP => true, + ]; + + /** + * The maximum value of the integer type in 32 bit platforms. + */ + private static $int32Max = 2147483647; + + /** + * The maximum value of the integer type in 64 bit platforms. + * + * @var int|float + */ + private static $int64Max = 9223372036854775807; + + private static $enSymbols = [ + self::DECIMAL => ['.', ',', ';', '%', '0', '#', '-', '+', '¤', '¤¤', '.', 'E', '‰', '*', '∞', 'NaN', '@', ','], + self::CURRENCY => ['.', ',', ';', '%', '0', '#', '-', '+', '¤', '¤¤', '.', 'E', '‰', '*', '∞', 'NaN', '@', ','], + ]; + + private static $enTextAttributes = [ + self::DECIMAL => ['', '', '-', '', ' ', 'XXX', ''], + self::CURRENCY => ['¤', '', '-¤', '', ' ', 'XXX'], + ]; + + /** + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * @param int $style Style of the formatting, one of the format style constants. + * The only supported styles are NumberFormatter::DECIMAL + * and NumberFormatter::CURRENCY. + * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or + * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax + * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation + * + * @see https://php.net/numberformatter.create + * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1DecimalFormat.html#details + * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1RuleBasedNumberFormat.html#details + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When the $style is not supported + * @throws MethodArgumentNotImplementedException When the pattern value is different than null + */ + public function __construct(?string $locale = 'en', ?int $style = null, ?string $pattern = null) + { + if ('en' !== $locale && null !== $locale) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); + } + + if (!\in_array($style, self::$supportedStyles)) { + $message = sprintf('The available styles are: %s.', implode(', ', array_keys(self::$supportedStyles))); + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'style', $style, $message); + } + + if (null !== $pattern) { + throw new MethodArgumentNotImplementedException(__METHOD__, 'pattern'); + } + + $this->style = $style; + } + + /** + * Static constructor. + * + * @param string|null $locale The locale code. The only supported locale is "en" (or null using the default locale, i.e. "en") + * @param int $style Style of the formatting, one of the format style constants. + * The only currently supported styles are NumberFormatter::DECIMAL + * and NumberFormatter::CURRENCY. + * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or + * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax + * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation + * + * @return static + * + * @see https://php.net/numberformatter.create + * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details + * @see http://www.icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html#_details + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When the $style is not supported + * @throws MethodArgumentNotImplementedException When the pattern value is different than null + */ + public static function create(?string $locale = 'en', ?int $style = null, ?string $pattern = null) + { + return new static($locale, $style, $pattern); + } + + /** + * Format a currency value. + * + * @return string The formatted currency value + * + * @see https://php.net/numberformatter.formatcurrency + * @see https://en.wikipedia.org/wiki/ISO_4217#Active_codes + */ + public function formatCurrency(float $amount, string $currency) + { + if (self::DECIMAL === $this->style) { + return $this->format($amount); + } + + if (null === $symbol = Currencies::getSymbol($currency)) { + return false; + } + $fractionDigits = Currencies::getFractionDigits($currency); + + $amount = $this->roundCurrency($amount, $currency); + + $negative = false; + if (0 > $amount) { + $negative = true; + $amount *= -1; + } + + $amount = $this->formatNumber($amount, $fractionDigits); + + // There's a non-breaking space after the currency code (i.e. CRC 100), but not if the currency has a symbol (i.e. £100). + $ret = $symbol.(mb_strlen($symbol, 'UTF-8') > 2 ? "\xc2\xa0" : '').$amount; + + return $negative ? '-'.$ret : $ret; + } + + /** + * Format a number. + * + * @param int|float $num The value to format + * @param int $type Type of the formatting, one of the format type constants. + * Only type NumberFormatter::TYPE_DEFAULT is currently supported. + * + * @return bool|string The formatted value or false on error + * + * @see https://php.net/numberformatter.format + * + * @throws NotImplementedException If the method is called with the class $style 'CURRENCY' + * @throws MethodArgumentValueNotImplementedException If the $type is different than TYPE_DEFAULT + */ + public function format($num, int $type = self::TYPE_DEFAULT) + { + // The original NumberFormatter does not support this format type + if (self::TYPE_CURRENCY === $type) { + if (\PHP_VERSION_ID >= 80000) { + throw new \ValueError(sprintf('The format type must be a NumberFormatter::TYPE_* constant (%s given).', $type)); + } + + trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING); + + return false; + } + + if (self::CURRENCY === $this->style) { + throw new NotImplementedException(sprintf('"%s()" method does not support the formatting of currencies (instance with CURRENCY style). "%s".', __METHOD__, NotImplementedException::INTL_INSTALL_MESSAGE)); + } + + // Only the default type is supported. + if (self::TYPE_DEFAULT !== $type) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'type', $type, 'Only TYPE_DEFAULT is supported'); + } + + $fractionDigits = $this->getAttribute(self::FRACTION_DIGITS); + + $num = $this->round($num, $fractionDigits); + $num = $this->formatNumber($num, $fractionDigits); + + // behave like the intl extension + $this->resetError(); + + return $num; + } + + /** + * Returns an attribute value. + * + * @return int|false The attribute value on success or false on error + * + * @see https://php.net/numberformatter.getattribute + */ + public function getAttribute(int $attribute) + { + return $this->attributes[$attribute] ?? null; + } + + /** + * Returns formatter's last error code. Always returns the U_ZERO_ERROR class constant value. + * + * @return int The error code from last formatter call + * + * @see https://php.net/numberformatter.geterrorcode + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * Returns formatter's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value. + * + * @return string The error message from last formatter call + * + * @see https://php.net/numberformatter.geterrormessage + */ + public function getErrorMessage() + { + return $this->errorMessage; + } + + /** + * Returns the formatter's locale. + * + * The parameter $type is currently ignored. + * + * @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE) + * + * @return string The locale used to create the formatter. Currently always + * returns "en". + * + * @see https://php.net/numberformatter.getlocale + */ + public function getLocale(int $type = Locale::ACTUAL_LOCALE) + { + return 'en'; + } + + /** + * Not supported. Returns the formatter's pattern. + * + * @return string|false The pattern string used by the formatter or false on error + * + * @see https://php.net/numberformatter.getpattern + * + * @throws MethodNotImplementedException + */ + public function getPattern() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns a formatter symbol value. + * + * @return string|false The symbol value or false on error + * + * @see https://php.net/numberformatter.getsymbol + */ + public function getSymbol(int $symbol) + { + return \array_key_exists($this->style, self::$enSymbols) && \array_key_exists($symbol, self::$enSymbols[$this->style]) ? self::$enSymbols[$this->style][$symbol] : false; + } + + /** + * Not supported. Returns a formatter text attribute value. + * + * @return string|false The attribute value or false on error + * + * @see https://php.net/numberformatter.gettextattribute + */ + public function getTextAttribute(int $attribute) + { + return \array_key_exists($this->style, self::$enTextAttributes) && \array_key_exists($attribute, self::$enTextAttributes[$this->style]) ? self::$enTextAttributes[$this->style][$attribute] : false; + } + + /** + * Not supported. Parse a currency number. + * + * @return float|false The parsed numeric value or false on error + * + * @see https://php.net/numberformatter.parsecurrency + * + * @throws MethodNotImplementedException + */ + public function parseCurrency(string $string, &$currency, &$offset = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Parse a number. + * + * @return int|float|false The parsed value or false on error + * + * @see https://php.net/numberformatter.parse + */ + public function parse(string $string, int $type = self::TYPE_DOUBLE, &$offset = null) + { + if (self::TYPE_DEFAULT === $type || self::TYPE_CURRENCY === $type) { + if (\PHP_VERSION_ID >= 80000) { + throw new \ValueError(sprintf('The format type must be a NumberFormatter::TYPE_* constant (%d given).', $type)); + } + + trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING); + + return false; + } + + // Any invalid number at the end of the string is removed. + // Only numbers and the fraction separator is expected in the string. + // If grouping is used, grouping separator also becomes a valid character. + $groupingMatch = $this->getAttribute(self::GROUPING_USED) ? '|(?P\d++(,{1}\d+)++(\.\d*+)?)' : ''; + if (preg_match("/^-?(?:\.\d++{$groupingMatch}|\d++(\.\d*+)?)/", $string, $matches)) { + $string = $matches[0]; + $offset = \strlen($string); + // value is not valid if grouping is used, but digits are not grouped in groups of three + if ($error = isset($matches['grouping']) && !preg_match('/^-?(?:\d{1,3}+)?(?:(?:,\d{3})++|\d*+)(?:\.\d*+)?$/', $string)) { + // the position on error is 0 for positive and 1 for negative numbers + $offset = 0 === strpos($string, '-') ? 1 : 0; + } + } else { + $error = true; + $offset = 0; + } + + if ($error) { + Icu::setError(Icu::U_PARSE_ERROR, 'Number parsing failed'); + $this->errorCode = Icu::getErrorCode(); + $this->errorMessage = Icu::getErrorMessage(); + + return false; + } + + $string = str_replace(',', '', $string); + $string = $this->convertValueDataType($string, $type); + + // behave like the intl extension + $this->resetError(); + + return $string; + } + + /** + * Set an attribute. + * + * @param int|float $value + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.setattribute + * + * @throws MethodArgumentValueNotImplementedException When the $attribute is not supported + * @throws MethodArgumentValueNotImplementedException When the $value is not supported + */ + public function setAttribute(int $attribute, $value) + { + if (!\in_array($attribute, self::$supportedAttributes)) { + $message = sprintf( + 'The available attributes are: %s', + implode(', ', array_keys(self::$supportedAttributes)) + ); + + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attribute', $value, $message); + } + + if (self::$supportedAttributes['ROUNDING_MODE'] === $attribute && $this->isInvalidRoundingMode($value)) { + $message = sprintf( + 'The supported values for ROUNDING_MODE are: %s', + implode(', ', array_keys(self::$roundingModes)) + ); + + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attribute', $value, $message); + } + + if (self::$supportedAttributes['GROUPING_USED'] === $attribute) { + $value = $this->normalizeGroupingUsedValue($value); + } + + if (self::$supportedAttributes['FRACTION_DIGITS'] === $attribute) { + $value = $this->normalizeFractionDigitsValue($value); + if ($value < 0) { + // ignore negative values but do not raise an error + return true; + } + } + + $this->attributes[$attribute] = $value; + $this->initializedAttributes[$attribute] = true; + + return true; + } + + /** + * Not supported. Set the formatter's pattern. + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.setpattern + * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details + * + * @throws MethodNotImplementedException + */ + public function setPattern(string $pattern) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set the formatter's symbol. + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.setsymbol + * + * @throws MethodNotImplementedException + */ + public function setSymbol(int $symbol, string $value) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set a text attribute. + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.settextattribute + * + * @throws MethodNotImplementedException + */ + public function setTextAttribute(int $attribute, string $value) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Set the error to the default U_ZERO_ERROR. + */ + protected function resetError() + { + Icu::setError(Icu::U_ZERO_ERROR); + $this->errorCode = Icu::getErrorCode(); + $this->errorMessage = Icu::getErrorMessage(); + } + + /** + * Rounds a currency value, applying increment rounding if applicable. + * + * When a currency have a rounding increment, an extra round is made after the first one. The rounding factor is + * determined in the ICU data and is explained as of: + * + * "the rounding increment is given in units of 10^(-fraction_digits)" + * + * The only actual rounding data as of this writing, is CHF. + * + * @see http://en.wikipedia.org/wiki/Swedish_rounding + * @see http://www.docjar.com/html/api/com/ibm/icu/util/Currency.java.html#1007 + */ + private function roundCurrency(float $value, string $currency): float + { + $fractionDigits = Currencies::getFractionDigits($currency); + $roundingIncrement = Currencies::getRoundingIncrement($currency); + + // Round with the formatter rounding mode + $value = $this->round($value, $fractionDigits); + + // Swiss rounding + if (0 < $roundingIncrement && 0 < $fractionDigits) { + $roundingFactor = $roundingIncrement / 10 ** $fractionDigits; + $value = round($value / $roundingFactor) * $roundingFactor; + } + + return $value; + } + + /** + * Rounds a value. + * + * @param int|float $value The value to round + * + * @return int|float The rounded value + */ + private function round($value, int $precision) + { + $precision = $this->getUninitializedPrecision($value, $precision); + + $roundingModeAttribute = $this->getAttribute(self::ROUNDING_MODE); + if (isset(self::$phpRoundingMap[$roundingModeAttribute])) { + $value = round($value, $precision, self::$phpRoundingMap[$roundingModeAttribute]); + } elseif (isset(self::$customRoundingList[$roundingModeAttribute])) { + $roundingCoef = 10 ** $precision; + $value *= $roundingCoef; + $value = (float) (string) $value; + + switch ($roundingModeAttribute) { + case self::ROUND_CEILING: + $value = ceil($value); + break; + case self::ROUND_FLOOR: + $value = floor($value); + break; + case self::ROUND_UP: + $value = $value > 0 ? ceil($value) : floor($value); + break; + case self::ROUND_DOWN: + $value = $value > 0 ? floor($value) : ceil($value); + break; + } + + $value /= $roundingCoef; + } + + return $value; + } + + /** + * Formats a number. + * + * @param int|float $value The numeric value to format + */ + private function formatNumber($value, int $precision): string + { + $precision = $this->getUninitializedPrecision($value, $precision); + + return number_format($value, $precision, '.', $this->getAttribute(self::GROUPING_USED) ? ',' : ''); + } + + /** + * Returns the precision value if the DECIMAL style is being used and the FRACTION_DIGITS attribute is uninitialized. + * + * @param int|float $value The value to get the precision from if the FRACTION_DIGITS attribute is uninitialized + */ + private function getUninitializedPrecision($value, int $precision): int + { + if (self::CURRENCY === $this->style) { + return $precision; + } + + if (!$this->isInitializedAttribute(self::FRACTION_DIGITS)) { + preg_match('/.*\.(.*)/', (string) $value, $digits); + if (isset($digits[1])) { + $precision = \strlen($digits[1]); + } + } + + return $precision; + } + + /** + * Check if the attribute is initialized (value set by client code). + */ + private function isInitializedAttribute(string $attr): bool + { + return isset($this->initializedAttributes[$attr]); + } + + /** + * Returns the numeric value using the $type to convert to the right data type. + * + * @param mixed $value The value to be converted + * + * @return int|float|false The converted value + */ + private function convertValueDataType($value, int $type) + { + if (self::TYPE_DOUBLE === $type) { + $value = (float) $value; + } elseif (self::TYPE_INT32 === $type) { + $value = $this->getInt32Value($value); + } elseif (self::TYPE_INT64 === $type) { + $value = $this->getInt64Value($value); + } + + return $value; + } + + /** + * Convert the value data type to int or returns false if the value is out of the integer value range. + * + * @return int|false The converted value + */ + private function getInt32Value($value) + { + if ($value > self::$int32Max || $value < -self::$int32Max - 1) { + return false; + } + + return (int) $value; + } + + /** + * Convert the value data type to int or returns false if the value is out of the integer value range. + * + * @return int|float|false The converted value + */ + private function getInt64Value($value) + { + if ($value > self::$int64Max || $value < -self::$int64Max - 1) { + return false; + } + + if (\PHP_INT_SIZE !== 8 && ($value > self::$int32Max || $value < -self::$int32Max - 1)) { + return (float) $value; + } + + return (int) $value; + } + + /** + * Check if the rounding mode is invalid. + */ + private function isInvalidRoundingMode(int $value): bool + { + if (\in_array($value, self::$roundingModes, true)) { + return false; + } + + return true; + } + + /** + * Returns the normalized value for the GROUPING_USED attribute. Any value that can be converted to int will be + * cast to Boolean and then to int again. This way, negative values are converted to 1 and string values to 0. + */ + private function normalizeGroupingUsedValue($value): int + { + return (int) (bool) (int) $value; + } + + /** + * Returns the normalized value for the FRACTION_DIGITS attribute. + */ + private function normalizeFractionDigitsValue($value): int + { + return (int) $value; + } +} diff --git a/vendor/symfony/polyfill-intl-icu/README.md b/vendor/symfony/polyfill-intl-icu/README.md new file mode 100644 index 0000000..b7faedc --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/README.md @@ -0,0 +1,23 @@ +Symfony Polyfill / Intl: ICU +============================ + +This package provides fallback implementations when the +[Intl](https://php.net/intl) extension is not installed. +It is limited to the "en" locale and to: + +- [`intl_is_failure()`](https://php.net/intl-is-failure) +- [`intl_get_error_code()`](https://php.net/intl-get-error-code) +- [`intl_get_error_message()`](https://php.net/intl-get-error-message) +- [`intl_error_name()`](https://php.net/intl-error-name) +- [`Collator`](https://php.net/Collator) +- [`NumberFormatter`](https://php.net/NumberFormatter) +- [`Locale`](https://php.net/Locale) +- [`IntlDateFormatter`](https://php.net/IntlDateFormatter) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-icu/Resources/currencies.php b/vendor/symfony/polyfill-intl-icu/Resources/currencies.php new file mode 100644 index 0000000..f802b7a --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Resources/currencies.php @@ -0,0 +1,1321 @@ + + array ( + 0 => 'ADP', + 1 => 0, + 2 => 0, + ), + 'AED' => + array ( + 0 => 'AED', + ), + 'AFA' => + array ( + 0 => 'AFA', + ), + 'AFN' => + array ( + 0 => 'AFN', + 1 => 0, + 2 => 0, + ), + 'ALK' => + array ( + 0 => 'ALK', + ), + 'ALL' => + array ( + 0 => 'ALL', + 1 => 0, + 2 => 0, + ), + 'AMD' => + array ( + 0 => 'AMD', + 1 => 2, + 2 => 0, + ), + 'ANG' => + array ( + 0 => 'ANG', + ), + 'AOA' => + array ( + 0 => 'AOA', + ), + 'AOK' => + array ( + 0 => 'AOK', + ), + 'AON' => + array ( + 0 => 'AON', + ), + 'AOR' => + array ( + 0 => 'AOR', + ), + 'ARA' => + array ( + 0 => 'ARA', + ), + 'ARL' => + array ( + 0 => 'ARL', + ), + 'ARM' => + array ( + 0 => 'ARM', + ), + 'ARP' => + array ( + 0 => 'ARP', + ), + 'ARS' => + array ( + 0 => 'ARS', + ), + 'ATS' => + array ( + 0 => 'ATS', + ), + 'AUD' => + array ( + 0 => 'A$', + ), + 'AWG' => + array ( + 0 => 'AWG', + ), + 'AZM' => + array ( + 0 => 'AZM', + ), + 'AZN' => + array ( + 0 => 'AZN', + ), + 'BAD' => + array ( + 0 => 'BAD', + ), + 'BAM' => + array ( + 0 => 'BAM', + ), + 'BAN' => + array ( + 0 => 'BAN', + ), + 'BBD' => + array ( + 0 => 'BBD', + ), + 'BDT' => + array ( + 0 => 'BDT', + ), + 'BEC' => + array ( + 0 => 'BEC', + ), + 'BEF' => + array ( + 0 => 'BEF', + ), + 'BEL' => + array ( + 0 => 'BEL', + ), + 'BGL' => + array ( + 0 => 'BGL', + ), + 'BGM' => + array ( + 0 => 'BGM', + ), + 'BGN' => + array ( + 0 => 'BGN', + ), + 'BGO' => + array ( + 0 => 'BGO', + ), + 'BHD' => + array ( + 0 => 'BHD', + 1 => 3, + 2 => 0, + ), + 'BIF' => + array ( + 0 => 'BIF', + 1 => 0, + 2 => 0, + ), + 'BMD' => + array ( + 0 => 'BMD', + ), + 'BND' => + array ( + 0 => 'BND', + ), + 'BOB' => + array ( + 0 => 'BOB', + ), + 'BOL' => + array ( + 0 => 'BOL', + ), + 'BOP' => + array ( + 0 => 'BOP', + ), + 'BOV' => + array ( + 0 => 'BOV', + ), + 'BRB' => + array ( + 0 => 'BRB', + ), + 'BRC' => + array ( + 0 => 'BRC', + ), + 'BRE' => + array ( + 0 => 'BRE', + ), + 'BRL' => + array ( + 0 => 'R$', + ), + 'BRN' => + array ( + 0 => 'BRN', + ), + 'BRR' => + array ( + 0 => 'BRR', + ), + 'BRZ' => + array ( + 0 => 'BRZ', + ), + 'BSD' => + array ( + 0 => 'BSD', + ), + 'BTN' => + array ( + 0 => 'BTN', + ), + 'BUK' => + array ( + 0 => 'BUK', + ), + 'BWP' => + array ( + 0 => 'BWP', + ), + 'BYB' => + array ( + 0 => 'BYB', + ), + 'BYN' => + array ( + 0 => 'BYN', + 1 => 2, + 2 => 0, + ), + 'BYR' => + array ( + 0 => 'BYR', + 1 => 0, + 2 => 0, + ), + 'BZD' => + array ( + 0 => 'BZD', + ), + 'CAD' => + array ( + 0 => 'CA$', + 1 => 2, + 2 => 0, + ), + 'CDF' => + array ( + 0 => 'CDF', + ), + 'CHE' => + array ( + 0 => 'CHE', + ), + 'CHF' => + array ( + 0 => 'CHF', + 1 => 2, + 2 => 0, + ), + 'CHW' => + array ( + 0 => 'CHW', + ), + 'CLE' => + array ( + 0 => 'CLE', + ), + 'CLF' => + array ( + 0 => 'CLF', + 1 => 4, + 2 => 0, + ), + 'CLP' => + array ( + 0 => 'CLP', + 1 => 0, + 2 => 0, + ), + 'CNH' => + array ( + 0 => 'CNH', + ), + 'CNX' => + array ( + 0 => 'CNX', + ), + 'CNY' => + array ( + 0 => 'CNÂ¥', + ), + 'COP' => + array ( + 0 => 'COP', + 1 => 2, + 2 => 0, + ), + 'COU' => + array ( + 0 => 'COU', + ), + 'CRC' => + array ( + 0 => 'CRC', + 1 => 2, + 2 => 0, + ), + 'CSD' => + array ( + 0 => 'CSD', + ), + 'CSK' => + array ( + 0 => 'CSK', + ), + 'CUC' => + array ( + 0 => 'CUC', + ), + 'CUP' => + array ( + 0 => 'CUP', + ), + 'CVE' => + array ( + 0 => 'CVE', + ), + 'CYP' => + array ( + 0 => 'CYP', + ), + 'CZK' => + array ( + 0 => 'CZK', + 1 => 2, + 2 => 0, + ), + 'DDM' => + array ( + 0 => 'DDM', + ), + 'DEM' => + array ( + 0 => 'DEM', + ), + 'DJF' => + array ( + 0 => 'DJF', + 1 => 0, + 2 => 0, + ), + 'DKK' => + array ( + 0 => 'DKK', + 1 => 2, + 2 => 0, + ), + 'DOP' => + array ( + 0 => 'DOP', + ), + 'DZD' => + array ( + 0 => 'DZD', + ), + 'ECS' => + array ( + 0 => 'ECS', + ), + 'ECV' => + array ( + 0 => 'ECV', + ), + 'EEK' => + array ( + 0 => 'EEK', + ), + 'EGP' => + array ( + 0 => 'EGP', + ), + 'ERN' => + array ( + 0 => 'ERN', + ), + 'ESA' => + array ( + 0 => 'ESA', + ), + 'ESB' => + array ( + 0 => 'ESB', + ), + 'ESP' => + array ( + 0 => 'ESP', + 1 => 0, + 2 => 0, + ), + 'ETB' => + array ( + 0 => 'ETB', + ), + 'EUR' => + array ( + 0 => '€', + ), + 'FIM' => + array ( + 0 => 'FIM', + ), + 'FJD' => + array ( + 0 => 'FJD', + ), + 'FKP' => + array ( + 0 => 'FKP', + ), + 'FRF' => + array ( + 0 => 'FRF', + ), + 'GBP' => + array ( + 0 => '£', + ), + 'GEK' => + array ( + 0 => 'GEK', + ), + 'GEL' => + array ( + 0 => 'GEL', + ), + 'GHC' => + array ( + 0 => 'GHC', + ), + 'GHS' => + array ( + 0 => 'GHS', + ), + 'GIP' => + array ( + 0 => 'GIP', + ), + 'GMD' => + array ( + 0 => 'GMD', + ), + 'GNF' => + array ( + 0 => 'GNF', + 1 => 0, + 2 => 0, + ), + 'GNS' => + array ( + 0 => 'GNS', + ), + 'GQE' => + array ( + 0 => 'GQE', + ), + 'GRD' => + array ( + 0 => 'GRD', + ), + 'GTQ' => + array ( + 0 => 'GTQ', + ), + 'GWE' => + array ( + 0 => 'GWE', + ), + 'GWP' => + array ( + 0 => 'GWP', + ), + 'GYD' => + array ( + 0 => 'GYD', + 1 => 2, + 2 => 0, + ), + 'HKD' => + array ( + 0 => 'HK$', + ), + 'HNL' => + array ( + 0 => 'HNL', + ), + 'HRD' => + array ( + 0 => 'HRD', + ), + 'HRK' => + array ( + 0 => 'HRK', + ), + 'HTG' => + array ( + 0 => 'HTG', + ), + 'HUF' => + array ( + 0 => 'HUF', + 1 => 2, + 2 => 0, + ), + 'IDR' => + array ( + 0 => 'IDR', + 1 => 2, + 2 => 0, + ), + 'IEP' => + array ( + 0 => 'IEP', + ), + 'ILP' => + array ( + 0 => 'ILP', + ), + 'ILR' => + array ( + 0 => 'ILR', + ), + 'ILS' => + array ( + 0 => '₪', + ), + 'INR' => + array ( + 0 => '₹', + ), + 'IQD' => + array ( + 0 => 'IQD', + 1 => 0, + 2 => 0, + ), + 'IRR' => + array ( + 0 => 'IRR', + 1 => 0, + 2 => 0, + ), + 'ISJ' => + array ( + 0 => 'ISJ', + ), + 'ISK' => + array ( + 0 => 'ISK', + 1 => 0, + 2 => 0, + ), + 'ITL' => + array ( + 0 => 'ITL', + 1 => 0, + 2 => 0, + ), + 'JMD' => + array ( + 0 => 'JMD', + ), + 'JOD' => + array ( + 0 => 'JOD', + 1 => 3, + 2 => 0, + ), + 'JPY' => + array ( + 0 => 'Â¥', + 1 => 0, + 2 => 0, + ), + 'KES' => + array ( + 0 => 'KES', + ), + 'KGS' => + array ( + 0 => 'KGS', + ), + 'KHR' => + array ( + 0 => 'KHR', + ), + 'KMF' => + array ( + 0 => 'KMF', + 1 => 0, + 2 => 0, + ), + 'KPW' => + array ( + 0 => 'KPW', + 1 => 0, + 2 => 0, + ), + 'KRH' => + array ( + 0 => 'KRH', + ), + 'KRO' => + array ( + 0 => 'KRO', + ), + 'KRW' => + array ( + 0 => 'â‚©', + 1 => 0, + 2 => 0, + ), + 'KWD' => + array ( + 0 => 'KWD', + 1 => 3, + 2 => 0, + ), + 'KYD' => + array ( + 0 => 'KYD', + ), + 'KZT' => + array ( + 0 => 'KZT', + ), + 'LAK' => + array ( + 0 => 'LAK', + 1 => 0, + 2 => 0, + ), + 'LBP' => + array ( + 0 => 'LBP', + 1 => 0, + 2 => 0, + ), + 'LKR' => + array ( + 0 => 'LKR', + ), + 'LRD' => + array ( + 0 => 'LRD', + ), + 'LSL' => + array ( + 0 => 'LSL', + ), + 'LTL' => + array ( + 0 => 'LTL', + ), + 'LTT' => + array ( + 0 => 'LTT', + ), + 'LUC' => + array ( + 0 => 'LUC', + ), + 'LUF' => + array ( + 0 => 'LUF', + 1 => 0, + 2 => 0, + ), + 'LUL' => + array ( + 0 => 'LUL', + ), + 'LVL' => + array ( + 0 => 'LVL', + ), + 'LVR' => + array ( + 0 => 'LVR', + ), + 'LYD' => + array ( + 0 => 'LYD', + 1 => 3, + 2 => 0, + ), + 'MAD' => + array ( + 0 => 'MAD', + ), + 'MAF' => + array ( + 0 => 'MAF', + ), + 'MCF' => + array ( + 0 => 'MCF', + ), + 'MDC' => + array ( + 0 => 'MDC', + ), + 'MDL' => + array ( + 0 => 'MDL', + ), + 'MGA' => + array ( + 0 => 'MGA', + 1 => 0, + 2 => 0, + ), + 'MGF' => + array ( + 0 => 'MGF', + 1 => 0, + 2 => 0, + ), + 'MKD' => + array ( + 0 => 'MKD', + ), + 'MKN' => + array ( + 0 => 'MKN', + ), + 'MLF' => + array ( + 0 => 'MLF', + ), + 'MMK' => + array ( + 0 => 'MMK', + 1 => 0, + 2 => 0, + ), + 'MNT' => + array ( + 0 => 'MNT', + 1 => 2, + 2 => 0, + ), + 'MOP' => + array ( + 0 => 'MOP', + ), + 'MRO' => + array ( + 0 => 'MRO', + 1 => 0, + 2 => 0, + ), + 'MRU' => + array ( + 0 => 'MRU', + ), + 'MTL' => + array ( + 0 => 'MTL', + ), + 'MTP' => + array ( + 0 => 'MTP', + ), + 'MUR' => + array ( + 0 => 'MUR', + 1 => 2, + 2 => 0, + ), + 'MVP' => + array ( + 0 => 'MVP', + ), + 'MVR' => + array ( + 0 => 'MVR', + ), + 'MWK' => + array ( + 0 => 'MWK', + ), + 'MXN' => + array ( + 0 => 'MX$', + ), + 'MXP' => + array ( + 0 => 'MXP', + ), + 'MXV' => + array ( + 0 => 'MXV', + ), + 'MYR' => + array ( + 0 => 'MYR', + ), + 'MZE' => + array ( + 0 => 'MZE', + ), + 'MZM' => + array ( + 0 => 'MZM', + ), + 'MZN' => + array ( + 0 => 'MZN', + ), + 'NAD' => + array ( + 0 => 'NAD', + ), + 'NGN' => + array ( + 0 => 'NGN', + ), + 'NIC' => + array ( + 0 => 'NIC', + ), + 'NIO' => + array ( + 0 => 'NIO', + ), + 'NLG' => + array ( + 0 => 'NLG', + ), + 'NOK' => + array ( + 0 => 'NOK', + 1 => 2, + 2 => 0, + ), + 'NPR' => + array ( + 0 => 'NPR', + ), + 'NZD' => + array ( + 0 => 'NZ$', + ), + 'OMR' => + array ( + 0 => 'OMR', + 1 => 3, + 2 => 0, + ), + 'PAB' => + array ( + 0 => 'PAB', + ), + 'PEI' => + array ( + 0 => 'PEI', + ), + 'PEN' => + array ( + 0 => 'PEN', + ), + 'PES' => + array ( + 0 => 'PES', + ), + 'PGK' => + array ( + 0 => 'PGK', + ), + 'PHP' => + array ( + 0 => '₱', + ), + 'PKR' => + array ( + 0 => 'PKR', + 1 => 2, + 2 => 0, + ), + 'PLN' => + array ( + 0 => 'PLN', + ), + 'PLZ' => + array ( + 0 => 'PLZ', + ), + 'PTE' => + array ( + 0 => 'PTE', + ), + 'PYG' => + array ( + 0 => 'PYG', + 1 => 0, + 2 => 0, + ), + 'QAR' => + array ( + 0 => 'QAR', + ), + 'RHD' => + array ( + 0 => 'RHD', + ), + 'ROL' => + array ( + 0 => 'ROL', + ), + 'RON' => + array ( + 0 => 'RON', + ), + 'RSD' => + array ( + 0 => 'RSD', + 1 => 0, + 2 => 0, + ), + 'RUB' => + array ( + 0 => 'RUB', + ), + 'RUR' => + array ( + 0 => 'RUR', + ), + 'RWF' => + array ( + 0 => 'RWF', + 1 => 0, + 2 => 0, + ), + 'SAR' => + array ( + 0 => 'SAR', + ), + 'SBD' => + array ( + 0 => 'SBD', + ), + 'SCR' => + array ( + 0 => 'SCR', + ), + 'SDD' => + array ( + 0 => 'SDD', + ), + 'SDG' => + array ( + 0 => 'SDG', + ), + 'SDP' => + array ( + 0 => 'SDP', + ), + 'SEK' => + array ( + 0 => 'SEK', + 1 => 2, + 2 => 0, + ), + 'SGD' => + array ( + 0 => 'SGD', + ), + 'SHP' => + array ( + 0 => 'SHP', + ), + 'SIT' => + array ( + 0 => 'SIT', + ), + 'SKK' => + array ( + 0 => 'SKK', + ), + 'SLE' => + array ( + 0 => 'SLE', + 1 => 2, + 2 => 0, + ), + 'SLL' => + array ( + 0 => 'SLL', + 1 => 0, + 2 => 0, + ), + 'SOS' => + array ( + 0 => 'SOS', + 1 => 0, + 2 => 0, + ), + 'SRD' => + array ( + 0 => 'SRD', + ), + 'SRG' => + array ( + 0 => 'SRG', + ), + 'SSP' => + array ( + 0 => 'SSP', + ), + 'STD' => + array ( + 0 => 'STD', + 1 => 0, + 2 => 0, + ), + 'STN' => + array ( + 0 => 'STN', + ), + 'SUR' => + array ( + 0 => 'SUR', + ), + 'SVC' => + array ( + 0 => 'SVC', + ), + 'SYP' => + array ( + 0 => 'SYP', + 1 => 0, + 2 => 0, + ), + 'SZL' => + array ( + 0 => 'SZL', + ), + 'THB' => + array ( + 0 => 'THB', + ), + 'TJR' => + array ( + 0 => 'TJR', + ), + 'TJS' => + array ( + 0 => 'TJS', + ), + 'TMM' => + array ( + 0 => 'TMM', + 1 => 0, + 2 => 0, + ), + 'TMT' => + array ( + 0 => 'TMT', + ), + 'TND' => + array ( + 0 => 'TND', + 1 => 3, + 2 => 0, + ), + 'TOP' => + array ( + 0 => 'TOP', + ), + 'TPE' => + array ( + 0 => 'TPE', + ), + 'TRL' => + array ( + 0 => 'TRL', + 1 => 0, + 2 => 0, + ), + 'TRY' => + array ( + 0 => 'TRY', + ), + 'TTD' => + array ( + 0 => 'TTD', + ), + 'TWD' => + array ( + 0 => 'NT$', + 1 => 2, + 2 => 0, + ), + 'TZS' => + array ( + 0 => 'TZS', + 1 => 2, + 2 => 0, + ), + 'UAH' => + array ( + 0 => 'UAH', + ), + 'UAK' => + array ( + 0 => 'UAK', + ), + 'UGS' => + array ( + 0 => 'UGS', + ), + 'UGX' => + array ( + 0 => 'UGX', + 1 => 0, + 2 => 0, + ), + 'USD' => + array ( + 0 => '$', + ), + 'USN' => + array ( + 0 => 'USN', + ), + 'USS' => + array ( + 0 => 'USS', + ), + 'UYI' => + array ( + 0 => 'UYI', + 1 => 0, + 2 => 0, + ), + 'UYP' => + array ( + 0 => 'UYP', + ), + 'UYU' => + array ( + 0 => 'UYU', + ), + 'UYW' => + array ( + 0 => 'UYW', + 1 => 4, + 2 => 0, + ), + 'UZS' => + array ( + 0 => 'UZS', + 1 => 2, + 2 => 0, + ), + 'VEB' => + array ( + 0 => 'VEB', + ), + 'VED' => + array ( + 0 => 'VED', + ), + 'VEF' => + array ( + 0 => 'VEF', + 1 => 2, + 2 => 0, + ), + 'VES' => + array ( + 0 => 'VES', + ), + 'VND' => + array ( + 0 => 'â‚«', + 1 => 0, + 2 => 0, + ), + 'VNN' => + array ( + 0 => 'VNN', + ), + 'VUV' => + array ( + 0 => 'VUV', + 1 => 0, + 2 => 0, + ), + 'WST' => + array ( + 0 => 'WST', + ), + 'XAF' => + array ( + 0 => 'FCFA', + 1 => 0, + 2 => 0, + ), + 'XCD' => + array ( + 0 => 'EC$', + ), + 'XEU' => + array ( + 0 => 'XEU', + ), + 'XFO' => + array ( + 0 => 'XFO', + ), + 'XFU' => + array ( + 0 => 'XFU', + ), + 'XOF' => + array ( + 0 => 'F CFA', + 1 => 0, + 2 => 0, + ), + 'XPF' => + array ( + 0 => 'CFPF', + 1 => 0, + 2 => 0, + ), + 'XRE' => + array ( + 0 => 'XRE', + ), + 'YDD' => + array ( + 0 => 'YDD', + ), + 'YER' => + array ( + 0 => 'YER', + 1 => 0, + 2 => 0, + ), + 'YUD' => + array ( + 0 => 'YUD', + ), + 'YUM' => + array ( + 0 => 'YUM', + ), + 'YUN' => + array ( + 0 => 'YUN', + ), + 'YUR' => + array ( + 0 => 'YUR', + ), + 'ZAL' => + array ( + 0 => 'ZAL', + ), + 'ZAR' => + array ( + 0 => 'ZAR', + ), + 'ZMK' => + array ( + 0 => 'ZMK', + 1 => 0, + 2 => 0, + ), + 'ZMW' => + array ( + 0 => 'ZMW', + ), + 'ZRN' => + array ( + 0 => 'ZRN', + ), + 'ZRZ' => + array ( + 0 => 'ZRZ', + ), + 'ZWD' => + array ( + 0 => 'ZWD', + 1 => 0, + 2 => 0, + ), + 'ZWL' => + array ( + 0 => 'ZWL', + ), + 'ZWR' => + array ( + 0 => 'ZWR', + ), + 'DEFAULT' => + array ( + 1 => 2, + 2 => 0, + ), +); diff --git a/vendor/symfony/polyfill-intl-icu/Resources/stubs/Collator.php b/vendor/symfony/polyfill-intl-icu/Resources/stubs/Collator.php new file mode 100644 index 0000000..a1efbcb --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Resources/stubs/Collator.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Icu\Collator as CollatorPolyfill; + +/** + * Stub implementation for the Collator class of the intl extension. + * + * @author Bernhard Schussek + */ +class Collator extends CollatorPolyfill +{ +} diff --git a/vendor/symfony/polyfill-intl-icu/Resources/stubs/IntlDateFormatter.php b/vendor/symfony/polyfill-intl-icu/Resources/stubs/IntlDateFormatter.php new file mode 100644 index 0000000..e701200 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Resources/stubs/IntlDateFormatter.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Icu\IntlDateFormatter as IntlDateFormatterPolyfill; + +/** + * Stub implementation for the IntlDateFormatter class of the intl extension. + * + * @author Bernhard Schussek + */ +class IntlDateFormatter extends IntlDateFormatterPolyfill +{ +} diff --git a/vendor/symfony/polyfill-intl-icu/Resources/stubs/Locale.php b/vendor/symfony/polyfill-intl-icu/Resources/stubs/Locale.php new file mode 100644 index 0000000..f1b951e --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Resources/stubs/Locale.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Icu\Locale as LocalePolyfill; + +/** + * Stub implementation for the Locale class of the intl extension. + * + * @author Bernhard Schussek + */ +class Locale extends LocalePolyfill +{ +} diff --git a/vendor/symfony/polyfill-intl-icu/Resources/stubs/NumberFormatter.php b/vendor/symfony/polyfill-intl-icu/Resources/stubs/NumberFormatter.php new file mode 100644 index 0000000..9288b9d --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/Resources/stubs/NumberFormatter.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Icu\NumberFormatter as NumberFormatterPolyfill; + +/** + * Stub implementation for the NumberFormatter class of the intl extension. + * + * @author Bernhard Schussek + * + * @see IntlNumberFormatter + */ +class NumberFormatter extends NumberFormatterPolyfill +{ +} diff --git a/vendor/symfony/polyfill-intl-icu/bootstrap.php b/vendor/symfony/polyfill-intl-icu/bootstrap.php new file mode 100644 index 0000000..77d7543 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/bootstrap.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Icu as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('intl_is_failure')) { + function intl_is_failure($errorCode) { return p\Icu::isFailure($errorCode); } +} +if (!function_exists('intl_get_error_code')) { + function intl_get_error_code() { return p\Icu::getErrorCode(); } +} +if (!function_exists('intl_get_error_message')) { + function intl_get_error_message() { return p\Icu::getErrorMessage(); } +} +if (!function_exists('intl_error_name')) { + function intl_error_name($errorCode) { return p\Icu::getErrorName($errorCode); } +} diff --git a/vendor/symfony/polyfill-intl-icu/bootstrap80.php b/vendor/symfony/polyfill-intl-icu/bootstrap80.php new file mode 100644 index 0000000..ee1653a --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/bootstrap80.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Icu as p; + +if (!function_exists('intl_is_failure')) { + function intl_is_failure(?int $errorCode): bool { return p\Icu::isFailure((int) $errorCode); } +} +if (!function_exists('intl_get_error_code')) { + function intl_get_error_code(): int { return p\Icu::getErrorCode(); } +} +if (!function_exists('intl_get_error_message')) { + function intl_get_error_message(): string { return p\Icu::getErrorMessage(); } +} +if (!function_exists('intl_error_name')) { + function intl_error_name(?int $errorCode): string { return p\Icu::getErrorName((int) $errorCode); } +} diff --git a/vendor/symfony/polyfill-intl-icu/composer.json b/vendor/symfony/polyfill-intl-icu/composer.json new file mode 100644 index 0000000..33c74ab --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/polyfill-intl-icu", + "type": "library", + "description": "Symfony polyfill for intl's ICU-related data and classes", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "icu"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "files": [ "bootstrap.php" ], + "psr-4": { "Symfony\\Polyfill\\Intl\\Icu\\": "" }, + "classmap": [ "Resources/stubs" ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "suggest": { + "ext-intl": "For best performance and support of other locales than \"en\"" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-intl-idn/Idn.php b/vendor/symfony/polyfill-intl-idn/Idn.php new file mode 100644 index 0000000..334f8ee --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Idn.php @@ -0,0 +1,933 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn; + +use Symfony\Polyfill\Intl\Idn\Resources\unidata\DisallowedRanges; +use Symfony\Polyfill\Intl\Idn\Resources\unidata\Regex; + +/** + * @see https://www.unicode.org/reports/tr46/ + * + * @internal + */ +final class Idn +{ + public const ERROR_EMPTY_LABEL = 1; + public const ERROR_LABEL_TOO_LONG = 2; + public const ERROR_DOMAIN_NAME_TOO_LONG = 4; + public const ERROR_LEADING_HYPHEN = 8; + public const ERROR_TRAILING_HYPHEN = 0x10; + public const ERROR_HYPHEN_3_4 = 0x20; + public const ERROR_LEADING_COMBINING_MARK = 0x40; + public const ERROR_DISALLOWED = 0x80; + public const ERROR_PUNYCODE = 0x100; + public const ERROR_LABEL_HAS_DOT = 0x200; + public const ERROR_INVALID_ACE_LABEL = 0x400; + public const ERROR_BIDI = 0x800; + public const ERROR_CONTEXTJ = 0x1000; + public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; + public const ERROR_CONTEXTO_DIGITS = 0x4000; + + public const INTL_IDNA_VARIANT_2003 = 0; + public const INTL_IDNA_VARIANT_UTS46 = 1; + + public const IDNA_DEFAULT = 0; + public const IDNA_ALLOW_UNASSIGNED = 1; + public const IDNA_USE_STD3_RULES = 2; + public const IDNA_CHECK_BIDI = 4; + public const IDNA_CHECK_CONTEXTJ = 8; + public const IDNA_NONTRANSITIONAL_TO_ASCII = 16; + public const IDNA_NONTRANSITIONAL_TO_UNICODE = 32; + + public const MAX_DOMAIN_SIZE = 253; + public const MAX_LABEL_SIZE = 63; + + public const BASE = 36; + public const TMIN = 1; + public const TMAX = 26; + public const SKEW = 38; + public const DAMP = 700; + public const INITIAL_BIAS = 72; + public const INITIAL_N = 128; + public const DELIMITER = '-'; + public const MAX_INT = 2147483647; + + /** + * Contains the numeric value of a basic code point (for use in representing integers) in the + * range 0 to BASE-1, or -1 if b is does not represent a value. + * + * @var array + */ + private static $basicToDigit = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, + + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + ]; + + /** + * @var array + */ + private static $virama; + + /** + * @var array + */ + private static $mapped; + + /** + * @var array + */ + private static $ignored; + + /** + * @var array + */ + private static $deviation; + + /** + * @var array + */ + private static $disallowed; + + /** + * @var array + */ + private static $disallowed_STD3_mapped; + + /** + * @var array + */ + private static $disallowed_STD3_valid; + + /** + * @var bool + */ + private static $mappingTableLoaded = false; + + /** + * @see https://www.unicode.org/reports/tr46/#ToASCII + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + + $options = [ + 'CheckHyphens' => true, + 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), + 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), + 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), + 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_ASCII), + 'VerifyDnsLength' => true, + ]; + $info = new Info(); + $labels = self::process((string) $domainName, $options, $info); + + foreach ($labels as $i => $label) { + // Only convert labels to punycode that contain non-ASCII code points + if (1 === preg_match('/[^\x00-\x7F]/', $label)) { + try { + $label = 'xn--'.self::punycodeEncode($label); + } catch (\Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + } + + $labels[$i] = $label; + } + } + + if ($options['VerifyDnsLength']) { + self::validateDomainAndLabelLength($labels, $info); + } + + $idna_info = [ + 'result' => implode('.', $labels), + 'isTransitionalDifferent' => $info->transitionalDifferent, + 'errors' => $info->errors, + ]; + + return 0 === $info->errors ? $idna_info['result'] : false; + } + + /** + * @see https://www.unicode.org/reports/tr46/#ToUnicode + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_utf8($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + + $info = new Info(); + $labels = self::process((string) $domainName, [ + 'CheckHyphens' => true, + 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), + 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), + 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), + 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_UNICODE), + ], $info); + $idna_info = [ + 'result' => implode('.', $labels), + 'isTransitionalDifferent' => $info->transitionalDifferent, + 'errors' => $info->errors, + ]; + + return 0 === $info->errors ? $idna_info['result'] : false; + } + + /** + * @param string $label + * + * @return bool + */ + private static function isValidContextJ(array $codePoints, $label) + { + if (!isset(self::$virama)) { + self::$virama = require __DIR__.\DIRECTORY_SEPARATOR.'Resources'.\DIRECTORY_SEPARATOR.'unidata'.\DIRECTORY_SEPARATOR.'virama.php'; + } + + $offset = 0; + + foreach ($codePoints as $i => $codePoint) { + if (0x200C !== $codePoint && 0x200D !== $codePoint) { + continue; + } + + if (!isset($codePoints[$i - 1])) { + return false; + } + + // If Canonical_Combining_Class(Before(cp)) .eq. Virama Then True; + if (isset(self::$virama[$codePoints[$i - 1]])) { + continue; + } + + // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then + // True; + // Generated RegExp = ([Joining_Type:{L,D}][Joining_Type:T]*\u200C[Joining_Type:T]*)[Joining_Type:{R,D}] + if (0x200C === $codePoint && 1 === preg_match(Regex::ZWNJ, $label, $matches, \PREG_OFFSET_CAPTURE, $offset)) { + $offset += \strlen($matches[1][0]); + + continue; + } + + return false; + } + + return true; + } + + /** + * @see https://www.unicode.org/reports/tr46/#ProcessingStepMap + * + * @param string $input + * @param array $options + * + * @return string + */ + private static function mapCodePoints($input, array $options, Info $info) + { + $str = ''; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + $transitional = $options['Transitional_Processing']; + + foreach (self::utf8Decode($input) as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + + switch ($data['status']) { + case 'disallowed': + case 'valid': + $str .= mb_chr($codePoint, 'utf-8'); + + break; + + case 'ignored': + // Do nothing. + break; + + case 'mapped': + $str .= $transitional && 0x1E9E === $codePoint ? 'ss' : $data['mapping']; + + break; + + case 'deviation': + $info->transitionalDifferent = true; + $str .= ($transitional ? $data['mapping'] : mb_chr($codePoint, 'utf-8')); + + break; + } + } + + return $str; + } + + /** + * @see https://www.unicode.org/reports/tr46/#Processing + * + * @param string $domain + * @param array $options + * + * @return array + */ + private static function process($domain, array $options, Info $info) + { + // If VerifyDnsLength is not set, we are doing ToUnicode otherwise we are doing ToASCII and + // we need to respect the VerifyDnsLength option. + $checkForEmptyLabels = !isset($options['VerifyDnsLength']) || $options['VerifyDnsLength']; + + if ($checkForEmptyLabels && '' === $domain) { + $info->errors |= self::ERROR_EMPTY_LABEL; + + return [$domain]; + } + + // Step 1. Map each code point in the domain name string + $domain = self::mapCodePoints($domain, $options, $info); + + // Step 2. Normalize the domain name string to Unicode Normalization Form C. + if (!\Normalizer::isNormalized($domain, \Normalizer::FORM_C)) { + $domain = \Normalizer::normalize($domain, \Normalizer::FORM_C); + } + + // Step 3. Break the string into labels at U+002E (.) FULL STOP. + $labels = explode('.', $domain); + $lastLabelIndex = \count($labels) - 1; + + // Step 4. Convert and validate each label in the domain name string. + foreach ($labels as $i => $label) { + $validationOptions = $options; + + if ('xn--' === substr($label, 0, 4)) { + // Step 4.1. If the label contains any non-ASCII code point (i.e., a code point greater than U+007F), + // record that there was an error, and continue with the next label. + if (preg_match('/[^\x00-\x7F]/', $label)) { + $info->errors |= self::ERROR_PUNYCODE; + + continue; + } + + // Step 4.2. Attempt to convert the rest of the label to Unicode according to Punycode [RFC3492]. If + // that conversion fails, record that there was an error, and continue + // with the next label. Otherwise replace the original label in the string by the results of the + // conversion. + try { + $label = self::punycodeDecode(substr($label, 4)); + } catch (\Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + + continue; + } + + $validationOptions['Transitional_Processing'] = false; + $labels[$i] = $label; + } + + self::validateLabel($label, $info, $validationOptions, $i > 0 && $i === $lastLabelIndex); + } + + if ($info->bidiDomain && !$info->validBidiDomain) { + $info->errors |= self::ERROR_BIDI; + } + + // Any input domain name string that does not record an error has been successfully + // processed according to this specification. Conversely, if an input domain_name string + // causes an error, then the processing of the input domain_name string fails. Determining + // what to do with error input is up to the caller, and not in the scope of this document. + return $labels; + } + + /** + * @see https://tools.ietf.org/html/rfc5893#section-2 + * + * @param string $label + */ + private static function validateBidiLabel($label, Info $info) + { + if (1 === preg_match(Regex::RTL_LABEL, $label)) { + $info->bidiDomain = true; + + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the R or AL property, it is an RTL label + if (1 !== preg_match(Regex::BIDI_STEP_1_RTL, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, + // CS, ET, ON, BN, or NSM are allowed. + if (1 === preg_match(Regex::BIDI_STEP_2, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 3. In an RTL label, the end of the label must be a character with Bidi property + // R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM. + if (1 !== preg_match(Regex::BIDI_STEP_3, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 4. In an RTL label, if an EN is present, no AN may be present, and vice versa. + if (1 === preg_match(Regex::BIDI_STEP_4_AN, $label) && 1 === preg_match(Regex::BIDI_STEP_4_EN, $label)) { + $info->validBidiDomain = false; + + return; + } + + return; + } + + // We are a LTR label + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the L property, it is an LTR label. + if (1 !== preg_match(Regex::BIDI_STEP_1_LTR, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 5. In an LTR label, only characters with the Bidi properties L, EN, + // ES, CS, ET, ON, BN, or NSM are allowed. + if (1 === preg_match(Regex::BIDI_STEP_5, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 6.In an LTR label, the end of the label must be a character with Bidi property L or + // EN, followed by zero or more characters with Bidi property NSM. + if (1 !== preg_match(Regex::BIDI_STEP_6, $label)) { + $info->validBidiDomain = false; + + return; + } + } + + /** + * @param array $labels + */ + private static function validateDomainAndLabelLength(array $labels, Info $info) + { + $maxDomainSize = self::MAX_DOMAIN_SIZE; + $length = \count($labels); + + // Number of "." delimiters. + $domainLength = $length - 1; + + // If the last label is empty and it is not the first label, then it is the root label. + // Increase the max size by 1, making it 254, to account for the root label's "." + // delimiter. This also means we don't need to check the last label's length for being too + // long. + if ($length > 1 && '' === $labels[$length - 1]) { + ++$maxDomainSize; + --$length; + } + + for ($i = 0; $i < $length; ++$i) { + $bytes = \strlen($labels[$i]); + $domainLength += $bytes; + + if ($bytes > self::MAX_LABEL_SIZE) { + $info->errors |= self::ERROR_LABEL_TOO_LONG; + } + } + + if ($domainLength > $maxDomainSize) { + $info->errors |= self::ERROR_DOMAIN_NAME_TOO_LONG; + } + } + + /** + * @see https://www.unicode.org/reports/tr46/#Validity_Criteria + * + * @param string $label + * @param array $options + * @param bool $canBeEmpty + */ + private static function validateLabel($label, Info $info, array $options, $canBeEmpty) + { + if ('' === $label) { + if (!$canBeEmpty && (!isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'])) { + $info->errors |= self::ERROR_EMPTY_LABEL; + } + + return; + } + + // Step 1. The label must be in Unicode Normalization Form C. + if (!\Normalizer::isNormalized($label, \Normalizer::FORM_C)) { + $info->errors |= self::ERROR_INVALID_ACE_LABEL; + } + + $codePoints = self::utf8Decode($label); + + if ($options['CheckHyphens']) { + // Step 2. If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character + // in both the thrid and fourth positions. + if (isset($codePoints[2], $codePoints[3]) && 0x002D === $codePoints[2] && 0x002D === $codePoints[3]) { + $info->errors |= self::ERROR_HYPHEN_3_4; + } + + // Step 3. If CheckHyphens, the label must neither begin nor end with a U+002D + // HYPHEN-MINUS character. + if ('-' === substr($label, 0, 1)) { + $info->errors |= self::ERROR_LEADING_HYPHEN; + } + + if ('-' === substr($label, -1, 1)) { + $info->errors |= self::ERROR_TRAILING_HYPHEN; + } + } elseif ('xn--' === substr($label, 0, 4)) { + $info->errors |= self::ERROR_PUNYCODE; + } + + // Step 4. The label must not contain a U+002E (.) FULL STOP. + if (false !== strpos($label, '.')) { + $info->errors |= self::ERROR_LABEL_HAS_DOT; + } + + // Step 5. The label must not begin with a combining mark, that is: General_Category=Mark. + if (1 === preg_match(Regex::COMBINING_MARK, $label)) { + $info->errors |= self::ERROR_LEADING_COMBINING_MARK; + } + + // Step 6. Each code point in the label must only have certain status values according to + // Section 5, IDNA Mapping Table: + $transitional = $options['Transitional_Processing']; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + + foreach ($codePoints as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + $status = $data['status']; + + if ('valid' === $status || (!$transitional && 'deviation' === $status)) { + continue; + } + + $info->errors |= self::ERROR_DISALLOWED; + + break; + } + + // Step 7. If CheckJoiners, the label must satisify the ContextJ rules from Appendix A, in + // The Unicode Code Points and Internationalized Domain Names for Applications (IDNA) + // [IDNA2008]. + if ($options['CheckJoiners'] && !self::isValidContextJ($codePoints, $label)) { + $info->errors |= self::ERROR_CONTEXTJ; + } + + // Step 8. If CheckBidi, and if the domain name is a Bidi domain name, then the label must + // satisfy all six of the numbered conditions in [IDNA2008] RFC 5893, Section 2. + if ($options['CheckBidi'] && (!$info->bidiDomain || $info->validBidiDomain)) { + self::validateBidiLabel($label, $info); + } + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.2 + * + * @param string $input + * + * @return string + */ + private static function punycodeDecode($input) + { + $n = self::INITIAL_N; + $out = 0; + $i = 0; + $bias = self::INITIAL_BIAS; + $lastDelimIndex = strrpos($input, self::DELIMITER); + $b = false === $lastDelimIndex ? 0 : $lastDelimIndex; + $inputLength = \strlen($input); + $output = []; + $bytes = array_map('ord', str_split($input)); + + for ($j = 0; $j < $b; ++$j) { + if ($bytes[$j] > 0x7F) { + throw new \Exception('Invalid input'); + } + + $output[$out++] = $input[$j]; + } + + if ($b > 0) { + ++$b; + } + + for ($in = $b; $in < $inputLength; ++$out) { + $oldi = $i; + $w = 1; + + for ($k = self::BASE; /* no condition */; $k += self::BASE) { + if ($in >= $inputLength) { + throw new \Exception('Invalid input'); + } + + $digit = self::$basicToDigit[$bytes[$in++] & 0xFF]; + + if ($digit < 0) { + throw new \Exception('Invalid input'); + } + + if ($digit > intdiv(self::MAX_INT - $i, $w)) { + throw new \Exception('Integer overflow'); + } + + $i += $digit * $w; + + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + + if ($digit < $t) { + break; + } + + $baseMinusT = self::BASE - $t; + + if ($w > intdiv(self::MAX_INT, $baseMinusT)) { + throw new \Exception('Integer overflow'); + } + + $w *= $baseMinusT; + } + + $outPlusOne = $out + 1; + $bias = self::adaptBias($i - $oldi, $outPlusOne, 0 === $oldi); + + if (intdiv($i, $outPlusOne) > self::MAX_INT - $n) { + throw new \Exception('Integer overflow'); + } + + $n += intdiv($i, $outPlusOne); + $i %= $outPlusOne; + array_splice($output, $i++, 0, [mb_chr($n, 'utf-8')]); + } + + return implode('', $output); + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.3 + * + * @param string $input + * + * @return string + */ + private static function punycodeEncode($input) + { + $n = self::INITIAL_N; + $delta = 0; + $out = 0; + $bias = self::INITIAL_BIAS; + $inputLength = 0; + $output = ''; + $iter = self::utf8Decode($input); + + foreach ($iter as $codePoint) { + ++$inputLength; + + if ($codePoint < 0x80) { + $output .= \chr($codePoint); + ++$out; + } + } + + $h = $out; + $b = $out; + + if ($b > 0) { + $output .= self::DELIMITER; + ++$out; + } + + while ($h < $inputLength) { + $m = self::MAX_INT; + + foreach ($iter as $codePoint) { + if ($codePoint >= $n && $codePoint < $m) { + $m = $codePoint; + } + } + + if ($m - $n > intdiv(self::MAX_INT - $delta, $h + 1)) { + throw new \Exception('Integer overflow'); + } + + $delta += ($m - $n) * ($h + 1); + $n = $m; + + foreach ($iter as $codePoint) { + if ($codePoint < $n && 0 === ++$delta) { + throw new \Exception('Integer overflow'); + } + + if ($codePoint === $n) { + $q = $delta; + + for ($k = self::BASE; /* no condition */; $k += self::BASE) { + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + + if ($q < $t) { + break; + } + + $qMinusT = $q - $t; + $baseMinusT = self::BASE - $t; + $output .= self::encodeDigit($t + $qMinusT % $baseMinusT, false); + ++$out; + $q = intdiv($qMinusT, $baseMinusT); + } + + $output .= self::encodeDigit($q, false); + ++$out; + $bias = self::adaptBias($delta, $h + 1, $h === $b); + $delta = 0; + ++$h; + } + } + + ++$delta; + ++$n; + } + + return $output; + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.1 + * + * @param int $delta + * @param int $numPoints + * @param bool $firstTime + * + * @return int + */ + private static function adaptBias($delta, $numPoints, $firstTime) + { + // xxx >> 1 is a faster way of doing intdiv(xxx, 2) + $delta = $firstTime ? intdiv($delta, self::DAMP) : $delta >> 1; + $delta += intdiv($delta, $numPoints); + $k = 0; + + while ($delta > ((self::BASE - self::TMIN) * self::TMAX) >> 1) { + $delta = intdiv($delta, self::BASE - self::TMIN); + $k += self::BASE; + } + + return $k + intdiv((self::BASE - self::TMIN + 1) * $delta, $delta + self::SKEW); + } + + /** + * @param int $d + * @param bool $flag + * + * @return string + */ + private static function encodeDigit($d, $flag) + { + return \chr($d + 22 + 75 * ($d < 26 ? 1 : 0) - (($flag ? 1 : 0) << 5)); + } + + /** + * Takes a UTF-8 encoded string and converts it into a series of integer code points. Any + * invalid byte sequences will be replaced by a U+FFFD replacement code point. + * + * @see https://encoding.spec.whatwg.org/#utf-8-decoder + * + * @param string $input + * + * @return array + */ + private static function utf8Decode($input) + { + $bytesSeen = 0; + $bytesNeeded = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + $codePoint = 0; + $codePoints = []; + $length = \strlen($input); + + for ($i = 0; $i < $length; ++$i) { + $byte = \ord($input[$i]); + + if (0 === $bytesNeeded) { + if ($byte >= 0x00 && $byte <= 0x7F) { + $codePoints[] = $byte; + + continue; + } + + if ($byte >= 0xC2 && $byte <= 0xDF) { + $bytesNeeded = 1; + $codePoint = $byte & 0x1F; + } elseif ($byte >= 0xE0 && $byte <= 0xEF) { + if (0xE0 === $byte) { + $lowerBoundary = 0xA0; + } elseif (0xED === $byte) { + $upperBoundary = 0x9F; + } + + $bytesNeeded = 2; + $codePoint = $byte & 0xF; + } elseif ($byte >= 0xF0 && $byte <= 0xF4) { + if (0xF0 === $byte) { + $lowerBoundary = 0x90; + } elseif (0xF4 === $byte) { + $upperBoundary = 0x8F; + } + + $bytesNeeded = 3; + $codePoint = $byte & 0x7; + } else { + $codePoints[] = 0xFFFD; + } + + continue; + } + + if ($byte < $lowerBoundary || $byte > $upperBoundary) { + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + --$i; + $codePoints[] = 0xFFFD; + + continue; + } + + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + $codePoint = ($codePoint << 6) | ($byte & 0x3F); + + if (++$bytesSeen !== $bytesNeeded) { + continue; + } + + $codePoints[] = $codePoint; + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + } + + // String unexpectedly ended, so append a U+FFFD code point. + if (0 !== $bytesNeeded) { + $codePoints[] = 0xFFFD; + } + + return $codePoints; + } + + /** + * @param int $codePoint + * @param bool $useSTD3ASCIIRules + * + * @return array{status: string, mapping?: string} + */ + private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules) + { + if (!self::$mappingTableLoaded) { + self::$mappingTableLoaded = true; + self::$mapped = require __DIR__.'/Resources/unidata/mapped.php'; + self::$ignored = require __DIR__.'/Resources/unidata/ignored.php'; + self::$deviation = require __DIR__.'/Resources/unidata/deviation.php'; + self::$disallowed = require __DIR__.'/Resources/unidata/disallowed.php'; + self::$disallowed_STD3_mapped = require __DIR__.'/Resources/unidata/disallowed_STD3_mapped.php'; + self::$disallowed_STD3_valid = require __DIR__.'/Resources/unidata/disallowed_STD3_valid.php'; + } + + if (isset(self::$mapped[$codePoint])) { + return ['status' => 'mapped', 'mapping' => self::$mapped[$codePoint]]; + } + + if (isset(self::$ignored[$codePoint])) { + return ['status' => 'ignored']; + } + + if (isset(self::$deviation[$codePoint])) { + return ['status' => 'deviation', 'mapping' => self::$deviation[$codePoint]]; + } + + if (isset(self::$disallowed[$codePoint]) || DisallowedRanges::inRange($codePoint)) { + return ['status' => 'disallowed']; + } + + $isDisallowedMapped = isset(self::$disallowed_STD3_mapped[$codePoint]); + + if ($isDisallowedMapped || isset(self::$disallowed_STD3_valid[$codePoint])) { + $status = 'disallowed'; + + if (!$useSTD3ASCIIRules) { + $status = $isDisallowedMapped ? 'mapped' : 'valid'; + } + + if ($isDisallowedMapped) { + return ['status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]]; + } + + return ['status' => $status]; + } + + return ['status' => 'valid']; + } +} diff --git a/vendor/symfony/polyfill-intl-idn/Info.php b/vendor/symfony/polyfill-intl-idn/Info.php new file mode 100644 index 0000000..25c3582 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Info.php @@ -0,0 +1,23 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn; + +/** + * @internal + */ +class Info +{ + public $bidiDomain = false; + public $errors = 0; + public $validBidiDomain = true; + public $transitionalDifferent = false; +} diff --git a/vendor/symfony/polyfill-intl-idn/LICENSE b/vendor/symfony/polyfill-intl-idn/LICENSE new file mode 100644 index 0000000..fd0a062 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier and Trevor Rowbotham + +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. diff --git a/vendor/symfony/polyfill-intl-idn/README.md b/vendor/symfony/polyfill-intl-idn/README.md new file mode 100644 index 0000000..cae5517 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Intl: Idn +============================ + +This component provides [`idn_to_ascii`](https://php.net/idn-to-ascii) and [`idn_to_utf8`](https://php.net/idn-to-utf8) functions to users who run php versions without the [Intl](https://php.net/intl) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php new file mode 100644 index 0000000..d285acd --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php @@ -0,0 +1,384 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn\Resources\unidata; + +/** + * @internal + */ +final class DisallowedRanges +{ + /** + * @param int $codePoint + * + * @return bool + */ + public static function inRange($codePoint) + { + if ($codePoint >= 128 && $codePoint <= 159) { + return true; + } + + if ($codePoint >= 2155 && $codePoint <= 2207) { + return true; + } + + if ($codePoint >= 3676 && $codePoint <= 3712) { + return true; + } + + if ($codePoint >= 3808 && $codePoint <= 3839) { + return true; + } + + if ($codePoint >= 4059 && $codePoint <= 4095) { + return true; + } + + if ($codePoint >= 4256 && $codePoint <= 4293) { + return true; + } + + if ($codePoint >= 6849 && $codePoint <= 6911) { + return true; + } + + if ($codePoint >= 11859 && $codePoint <= 11903) { + return true; + } + + if ($codePoint >= 42955 && $codePoint <= 42996) { + return true; + } + + if ($codePoint >= 55296 && $codePoint <= 57343) { + return true; + } + + if ($codePoint >= 57344 && $codePoint <= 63743) { + return true; + } + + if ($codePoint >= 64218 && $codePoint <= 64255) { + return true; + } + + if ($codePoint >= 64976 && $codePoint <= 65007) { + return true; + } + + if ($codePoint >= 65630 && $codePoint <= 65663) { + return true; + } + + if ($codePoint >= 65953 && $codePoint <= 65999) { + return true; + } + + if ($codePoint >= 66046 && $codePoint <= 66175) { + return true; + } + + if ($codePoint >= 66518 && $codePoint <= 66559) { + return true; + } + + if ($codePoint >= 66928 && $codePoint <= 67071) { + return true; + } + + if ($codePoint >= 67432 && $codePoint <= 67583) { + return true; + } + + if ($codePoint >= 67760 && $codePoint <= 67807) { + return true; + } + + if ($codePoint >= 67904 && $codePoint <= 67967) { + return true; + } + + if ($codePoint >= 68256 && $codePoint <= 68287) { + return true; + } + + if ($codePoint >= 68528 && $codePoint <= 68607) { + return true; + } + + if ($codePoint >= 68681 && $codePoint <= 68735) { + return true; + } + + if ($codePoint >= 68922 && $codePoint <= 69215) { + return true; + } + + if ($codePoint >= 69298 && $codePoint <= 69375) { + return true; + } + + if ($codePoint >= 69466 && $codePoint <= 69551) { + return true; + } + + if ($codePoint >= 70207 && $codePoint <= 70271) { + return true; + } + + if ($codePoint >= 70517 && $codePoint <= 70655) { + return true; + } + + if ($codePoint >= 70874 && $codePoint <= 71039) { + return true; + } + + if ($codePoint >= 71134 && $codePoint <= 71167) { + return true; + } + + if ($codePoint >= 71370 && $codePoint <= 71423) { + return true; + } + + if ($codePoint >= 71488 && $codePoint <= 71679) { + return true; + } + + if ($codePoint >= 71740 && $codePoint <= 71839) { + return true; + } + + if ($codePoint >= 72026 && $codePoint <= 72095) { + return true; + } + + if ($codePoint >= 72441 && $codePoint <= 72703) { + return true; + } + + if ($codePoint >= 72887 && $codePoint <= 72959) { + return true; + } + + if ($codePoint >= 73130 && $codePoint <= 73439) { + return true; + } + + if ($codePoint >= 73465 && $codePoint <= 73647) { + return true; + } + + if ($codePoint >= 74650 && $codePoint <= 74751) { + return true; + } + + if ($codePoint >= 75076 && $codePoint <= 77823) { + return true; + } + + if ($codePoint >= 78905 && $codePoint <= 82943) { + return true; + } + + if ($codePoint >= 83527 && $codePoint <= 92159) { + return true; + } + + if ($codePoint >= 92784 && $codePoint <= 92879) { + return true; + } + + if ($codePoint >= 93072 && $codePoint <= 93759) { + return true; + } + + if ($codePoint >= 93851 && $codePoint <= 93951) { + return true; + } + + if ($codePoint >= 94112 && $codePoint <= 94175) { + return true; + } + + if ($codePoint >= 101590 && $codePoint <= 101631) { + return true; + } + + if ($codePoint >= 101641 && $codePoint <= 110591) { + return true; + } + + if ($codePoint >= 110879 && $codePoint <= 110927) { + return true; + } + + if ($codePoint >= 111356 && $codePoint <= 113663) { + return true; + } + + if ($codePoint >= 113828 && $codePoint <= 118783) { + return true; + } + + if ($codePoint >= 119366 && $codePoint <= 119519) { + return true; + } + + if ($codePoint >= 119673 && $codePoint <= 119807) { + return true; + } + + if ($codePoint >= 121520 && $codePoint <= 122879) { + return true; + } + + if ($codePoint >= 122923 && $codePoint <= 123135) { + return true; + } + + if ($codePoint >= 123216 && $codePoint <= 123583) { + return true; + } + + if ($codePoint >= 123648 && $codePoint <= 124927) { + return true; + } + + if ($codePoint >= 125143 && $codePoint <= 125183) { + return true; + } + + if ($codePoint >= 125280 && $codePoint <= 126064) { + return true; + } + + if ($codePoint >= 126133 && $codePoint <= 126208) { + return true; + } + + if ($codePoint >= 126270 && $codePoint <= 126463) { + return true; + } + + if ($codePoint >= 126652 && $codePoint <= 126703) { + return true; + } + + if ($codePoint >= 126706 && $codePoint <= 126975) { + return true; + } + + if ($codePoint >= 127406 && $codePoint <= 127461) { + return true; + } + + if ($codePoint >= 127590 && $codePoint <= 127743) { + return true; + } + + if ($codePoint >= 129202 && $codePoint <= 129279) { + return true; + } + + if ($codePoint >= 129751 && $codePoint <= 129791) { + return true; + } + + if ($codePoint >= 129995 && $codePoint <= 130031) { + return true; + } + + if ($codePoint >= 130042 && $codePoint <= 131069) { + return true; + } + + if ($codePoint >= 173790 && $codePoint <= 173823) { + return true; + } + + if ($codePoint >= 191457 && $codePoint <= 194559) { + return true; + } + + if ($codePoint >= 195102 && $codePoint <= 196605) { + return true; + } + + if ($codePoint >= 201547 && $codePoint <= 262141) { + return true; + } + + if ($codePoint >= 262144 && $codePoint <= 327677) { + return true; + } + + if ($codePoint >= 327680 && $codePoint <= 393213) { + return true; + } + + if ($codePoint >= 393216 && $codePoint <= 458749) { + return true; + } + + if ($codePoint >= 458752 && $codePoint <= 524285) { + return true; + } + + if ($codePoint >= 524288 && $codePoint <= 589821) { + return true; + } + + if ($codePoint >= 589824 && $codePoint <= 655357) { + return true; + } + + if ($codePoint >= 655360 && $codePoint <= 720893) { + return true; + } + + if ($codePoint >= 720896 && $codePoint <= 786429) { + return true; + } + + if ($codePoint >= 786432 && $codePoint <= 851965) { + return true; + } + + if ($codePoint >= 851968 && $codePoint <= 917501) { + return true; + } + + if ($codePoint >= 917536 && $codePoint <= 917631) { + return true; + } + + if ($codePoint >= 917632 && $codePoint <= 917759) { + return true; + } + + if ($codePoint >= 918000 && $codePoint <= 983037) { + return true; + } + + if ($codePoint >= 983040 && $codePoint <= 1048573) { + return true; + } + + if ($codePoint >= 1048576 && $codePoint <= 1114109) { + return true; + } + + return false; + } +} diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php new file mode 100644 index 0000000..3c6af0c --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn\Resources\unidata; + +/** + * @internal + */ +final class Regex +{ + const COMBINING_MARK = '/^[\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{0903}\x{093A}\x{093B}\x{093C}\x{093E}-\x{0940}\x{0941}-\x{0948}\x{0949}-\x{094C}\x{094D}\x{094E}-\x{094F}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{0982}-\x{0983}\x{09BC}\x{09BE}-\x{09C0}\x{09C1}-\x{09C4}\x{09C7}-\x{09C8}\x{09CB}-\x{09CC}\x{09CD}\x{09D7}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A03}\x{0A3C}\x{0A3E}-\x{0A40}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0A83}\x{0ABC}\x{0ABE}-\x{0AC0}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0AC9}\x{0ACB}-\x{0ACC}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B02}-\x{0B03}\x{0B3C}\x{0B3E}\x{0B3F}\x{0B40}\x{0B41}-\x{0B44}\x{0B47}-\x{0B48}\x{0B4B}-\x{0B4C}\x{0B4D}\x{0B55}-\x{0B56}\x{0B57}\x{0B62}-\x{0B63}\x{0B82}\x{0BBE}-\x{0BBF}\x{0BC0}\x{0BC1}-\x{0BC2}\x{0BC6}-\x{0BC8}\x{0BCA}-\x{0BCC}\x{0BCD}\x{0BD7}\x{0C00}\x{0C01}-\x{0C03}\x{0C04}\x{0C3E}-\x{0C40}\x{0C41}-\x{0C44}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0C82}-\x{0C83}\x{0CBC}\x{0CBE}\x{0CBF}\x{0CC0}-\x{0CC4}\x{0CC6}\x{0CC7}-\x{0CC8}\x{0CCA}-\x{0CCB}\x{0CCC}-\x{0CCD}\x{0CD5}-\x{0CD6}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D02}-\x{0D03}\x{0D3B}-\x{0D3C}\x{0D3E}-\x{0D40}\x{0D41}-\x{0D44}\x{0D46}-\x{0D48}\x{0D4A}-\x{0D4C}\x{0D4D}\x{0D57}\x{0D62}-\x{0D63}\x{0D81}\x{0D82}-\x{0D83}\x{0DCA}\x{0DCF}-\x{0DD1}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0DD8}-\x{0DDF}\x{0DF2}-\x{0DF3}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3E}-\x{0F3F}\x{0F71}-\x{0F7E}\x{0F7F}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102B}-\x{102C}\x{102D}-\x{1030}\x{1031}\x{1032}-\x{1037}\x{1038}\x{1039}-\x{103A}\x{103B}-\x{103C}\x{103D}-\x{103E}\x{1056}-\x{1057}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106D}\x{1071}-\x{1074}\x{1082}\x{1083}-\x{1084}\x{1085}-\x{1086}\x{1087}-\x{108C}\x{108D}\x{108F}\x{109A}-\x{109C}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B6}\x{17B7}-\x{17BD}\x{17BE}-\x{17C5}\x{17C6}\x{17C7}-\x{17C8}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1923}-\x{1926}\x{1927}-\x{1928}\x{1929}-\x{192B}\x{1930}-\x{1931}\x{1932}\x{1933}-\x{1938}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A19}-\x{1A1A}\x{1A1B}\x{1A55}\x{1A56}\x{1A57}\x{1A58}-\x{1A5E}\x{1A60}\x{1A61}\x{1A62}\x{1A63}-\x{1A64}\x{1A65}-\x{1A6C}\x{1A6D}-\x{1A72}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B04}\x{1B34}\x{1B35}\x{1B36}-\x{1B3A}\x{1B3B}\x{1B3C}\x{1B3D}-\x{1B41}\x{1B42}\x{1B43}-\x{1B44}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1B82}\x{1BA1}\x{1BA2}-\x{1BA5}\x{1BA6}-\x{1BA7}\x{1BA8}-\x{1BA9}\x{1BAA}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE7}\x{1BE8}-\x{1BE9}\x{1BEA}-\x{1BEC}\x{1BED}\x{1BEE}\x{1BEF}-\x{1BF1}\x{1BF2}-\x{1BF3}\x{1C24}-\x{1C2B}\x{1C2C}-\x{1C33}\x{1C34}-\x{1C35}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE1}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF7}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{302E}-\x{302F}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A823}-\x{A824}\x{A825}-\x{A826}\x{A827}\x{A82C}\x{A880}-\x{A881}\x{A8B4}-\x{A8C3}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A952}-\x{A953}\x{A980}-\x{A982}\x{A983}\x{A9B3}\x{A9B4}-\x{A9B5}\x{A9B6}-\x{A9B9}\x{A9BA}-\x{A9BB}\x{A9BC}-\x{A9BD}\x{A9BE}-\x{A9C0}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA2F}-\x{AA30}\x{AA31}-\x{AA32}\x{AA33}-\x{AA34}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA4D}\x{AA7B}\x{AA7C}\x{AA7D}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEB}\x{AAEC}-\x{AAED}\x{AAEE}-\x{AAEF}\x{AAF5}\x{AAF6}\x{ABE3}-\x{ABE4}\x{ABE5}\x{ABE6}-\x{ABE7}\x{ABE8}\x{ABE9}-\x{ABEA}\x{ABEC}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11000}\x{11001}\x{11002}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{11082}\x{110B0}-\x{110B2}\x{110B3}-\x{110B6}\x{110B7}-\x{110B8}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112C}\x{1112D}-\x{11134}\x{11145}-\x{11146}\x{11173}\x{11180}-\x{11181}\x{11182}\x{111B3}-\x{111B5}\x{111B6}-\x{111BE}\x{111BF}-\x{111C0}\x{111C9}-\x{111CC}\x{111CE}\x{111CF}\x{1122C}-\x{1122E}\x{1122F}-\x{11231}\x{11232}-\x{11233}\x{11234}\x{11235}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E0}-\x{112E2}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{11302}-\x{11303}\x{1133B}-\x{1133C}\x{1133E}-\x{1133F}\x{11340}\x{11341}-\x{11344}\x{11347}-\x{11348}\x{1134B}-\x{1134D}\x{11357}\x{11362}-\x{11363}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11435}-\x{11437}\x{11438}-\x{1143F}\x{11440}-\x{11441}\x{11442}-\x{11444}\x{11445}\x{11446}\x{1145E}\x{114B0}-\x{114B2}\x{114B3}-\x{114B8}\x{114B9}\x{114BA}\x{114BB}-\x{114BE}\x{114BF}-\x{114C0}\x{114C1}\x{114C2}-\x{114C3}\x{115AF}-\x{115B1}\x{115B2}-\x{115B5}\x{115B8}-\x{115BB}\x{115BC}-\x{115BD}\x{115BE}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11630}-\x{11632}\x{11633}-\x{1163A}\x{1163B}-\x{1163C}\x{1163D}\x{1163E}\x{1163F}-\x{11640}\x{116AB}\x{116AC}\x{116AD}\x{116AE}-\x{116AF}\x{116B0}-\x{116B5}\x{116B6}\x{116B7}\x{1171D}-\x{1171F}\x{11720}-\x{11721}\x{11722}-\x{11725}\x{11726}\x{11727}-\x{1172B}\x{1182C}-\x{1182E}\x{1182F}-\x{11837}\x{11838}\x{11839}-\x{1183A}\x{11930}-\x{11935}\x{11937}-\x{11938}\x{1193B}-\x{1193C}\x{1193D}\x{1193E}\x{11940}\x{11942}\x{11943}\x{119D1}-\x{119D3}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119DC}-\x{119DF}\x{119E0}\x{119E4}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A39}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A57}-\x{11A58}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A97}\x{11A98}-\x{11A99}\x{11C2F}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3E}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CA9}\x{11CAA}-\x{11CB0}\x{11CB1}\x{11CB2}-\x{11CB3}\x{11CB4}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D8A}-\x{11D8E}\x{11D90}-\x{11D91}\x{11D93}-\x{11D94}\x{11D95}\x{11D96}\x{11D97}\x{11EF3}-\x{11EF4}\x{11EF5}-\x{11EF6}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F51}-\x{16F87}\x{16F8F}-\x{16F92}\x{16FE4}\x{16FF0}-\x{16FF1}\x{1BC9D}-\x{1BC9E}\x{1D165}-\x{1D166}\x{1D167}-\x{1D169}\x{1D16D}-\x{1D172}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]/u'; + + const RTL_LABEL = '/[\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08E2}\x{200F}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u'; + + const BIDI_STEP_1_LTR = '/^[^\x{0000}-\x{0008}\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{000E}-\x{001B}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{0030}-\x{0039}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0085}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B2}-\x{00B3}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00B9}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{1680}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{2000}-\x{200A}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{205F}\x{2060}-\x{2064}\x{2065}\x{2066}\x{2067}\x{2068}\x{2069}\x{206A}-\x{206F}\x{2070}\x{2074}-\x{2079}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{2080}-\x{2089}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20BF}\x{20C0}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{2488}-\x{249B}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3000}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}\x{FDFE}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF10}-\x{FF19}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{102E1}-\x{102FB}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1D7CE}-\x{1D7FF}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F100}-\x{1F10A}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6E0}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1F978}\x{1F97A}-\x{1F9CB}\x{1F9CD}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7A}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAA8}\x{1FAB0}-\x{1FAB6}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FBF0}-\x{1FBF9}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}]/u'; + const BIDI_STEP_1_RTL = '/^[\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{200F}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u'; + const BIDI_STEP_2 = '/[^\x{0000}-\x{0008}\x{000E}-\x{001B}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{0030}-\x{0039}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B2}-\x{00B3}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00B9}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{2060}-\x{2064}\x{2065}\x{206A}-\x{206F}\x{2070}\x{2074}-\x{2079}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{2080}-\x{2089}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20BF}\x{20C0}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{2488}-\x{249B}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}\x{FDFE}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF10}-\x{FF19}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{102E1}-\x{102FB}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1D7CE}-\x{1D7FF}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F100}-\x{1F10A}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6E0}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1F978}\x{1F97A}-\x{1F9CB}\x{1F9CD}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7A}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAA8}\x{1FAB0}-\x{1FAB6}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FBF0}-\x{1FBF9}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}]/u'; + const BIDI_STEP_3 = '/[\x{0030}-\x{0039}\x{00B2}-\x{00B3}\x{00B9}\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08E2}\x{200F}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2488}-\x{249B}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FF10}-\x{FF19}\x{102E1}-\x{102FB}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1D7CE}-\x{1D7FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F100}-\x{1F10A}\x{1FBF0}-\x{1FBF9}][\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1D167}-\x{1D169}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]*$/u'; + const BIDI_STEP_4_AN = '/[\x{0600}-\x{0605}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{06DD}\x{08E2}\x{10D30}-\x{10D39}\x{10E60}-\x{10E7E}]/u'; + const BIDI_STEP_4_EN = '/[\x{0030}-\x{0039}\x{00B2}-\x{00B3}\x{00B9}\x{06F0}-\x{06F9}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2488}-\x{249B}\x{FF10}-\x{FF19}\x{102E1}-\x{102FB}\x{1D7CE}-\x{1D7FF}\x{1F100}-\x{1F10A}\x{1FBF0}-\x{1FBF9}]/u'; + const BIDI_STEP_5 = '/[\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0085}\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08E2}\x{1680}\x{2000}-\x{200A}\x{200F}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{205F}\x{2066}\x{2067}\x{2068}\x{2069}\x{3000}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u'; + const BIDI_STEP_6 = '/[^\x{0000}-\x{0008}\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{000E}-\x{001B}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0085}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{1680}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{2000}-\x{200A}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{205F}\x{2060}-\x{2064}\x{2065}\x{2066}\x{2067}\x{2068}\x{2069}\x{206A}-\x{206F}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20BF}\x{20C0}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3000}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}\x{FDFE}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6E0}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1F978}\x{1F97A}-\x{1F9CB}\x{1F9CD}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7A}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAA8}\x{1FAB0}-\x{1FAB6}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}][\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1D167}-\x{1D169}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]*$/u'; + + const ZWNJ = '/([\x{A872}\x{10ACD}\x{10AD7}\x{10D00}\x{10FCB}\x{0620}\x{0626}\x{0628}\x{062A}-\x{062E}\x{0633}-\x{063F}\x{0641}-\x{0647}\x{0649}-\x{064A}\x{066E}-\x{066F}\x{0678}-\x{0687}\x{069A}-\x{06BF}\x{06C1}-\x{06C2}\x{06CC}\x{06CE}\x{06D0}-\x{06D1}\x{06FA}-\x{06FC}\x{06FF}\x{0712}-\x{0714}\x{071A}-\x{071D}\x{071F}-\x{0727}\x{0729}\x{072B}\x{072D}-\x{072E}\x{074E}-\x{0758}\x{075C}-\x{076A}\x{076D}-\x{0770}\x{0772}\x{0775}-\x{0777}\x{077A}-\x{077F}\x{07CA}-\x{07EA}\x{0841}-\x{0845}\x{0848}\x{084A}-\x{0853}\x{0855}\x{0860}\x{0862}-\x{0865}\x{0868}\x{08A0}-\x{08A9}\x{08AF}-\x{08B0}\x{08B3}-\x{08B4}\x{08B6}-\x{08B8}\x{08BA}-\x{08C7}\x{1807}\x{1820}-\x{1842}\x{1843}\x{1844}-\x{1878}\x{1887}-\x{18A8}\x{18AA}\x{A840}-\x{A871}\x{10AC0}-\x{10AC4}\x{10AD3}-\x{10AD6}\x{10AD8}-\x{10ADC}\x{10ADE}-\x{10AE0}\x{10AEB}-\x{10AEE}\x{10B80}\x{10B82}\x{10B86}-\x{10B88}\x{10B8A}-\x{10B8B}\x{10B8D}\x{10B90}\x{10BAD}-\x{10BAE}\x{10D01}-\x{10D21}\x{10D23}\x{10F30}-\x{10F32}\x{10F34}-\x{10F44}\x{10F51}-\x{10F53}\x{10FB0}\x{10FB2}-\x{10FB3}\x{10FB8}\x{10FBB}-\x{10FBC}\x{10FBE}-\x{10FBF}\x{10FC1}\x{10FC4}\x{10FCA}\x{1E900}-\x{1E943}][\x{00AD}\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{061C}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{200B}\x{200E}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{206A}-\x{206F}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{FEFF}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{13430}-\x{13438}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1E94B}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}]*\x{200C}[\x{00AD}\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{061C}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{200B}\x{200E}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{206A}-\x{206F}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{FEFF}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{13430}-\x{13438}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1E94B}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}]*)[\x{0622}-\x{0625}\x{0627}\x{0629}\x{062F}-\x{0632}\x{0648}\x{0671}-\x{0673}\x{0675}-\x{0677}\x{0688}-\x{0699}\x{06C0}\x{06C3}-\x{06CB}\x{06CD}\x{06CF}\x{06D2}-\x{06D3}\x{06D5}\x{06EE}-\x{06EF}\x{0710}\x{0715}-\x{0719}\x{071E}\x{0728}\x{072A}\x{072C}\x{072F}\x{074D}\x{0759}-\x{075B}\x{076B}-\x{076C}\x{0771}\x{0773}-\x{0774}\x{0778}-\x{0779}\x{0840}\x{0846}-\x{0847}\x{0849}\x{0854}\x{0856}-\x{0858}\x{0867}\x{0869}-\x{086A}\x{08AA}-\x{08AC}\x{08AE}\x{08B1}-\x{08B2}\x{08B9}\x{10AC5}\x{10AC7}\x{10AC9}-\x{10ACA}\x{10ACE}-\x{10AD2}\x{10ADD}\x{10AE1}\x{10AE4}\x{10AEF}\x{10B81}\x{10B83}-\x{10B85}\x{10B89}\x{10B8C}\x{10B8E}-\x{10B8F}\x{10B91}\x{10BA9}-\x{10BAC}\x{10D22}\x{10F33}\x{10F54}\x{10FB4}-\x{10FB6}\x{10FB9}-\x{10FBA}\x{10FBD}\x{10FC2}-\x{10FC3}\x{10FC9}\x{0620}\x{0626}\x{0628}\x{062A}-\x{062E}\x{0633}-\x{063F}\x{0641}-\x{0647}\x{0649}-\x{064A}\x{066E}-\x{066F}\x{0678}-\x{0687}\x{069A}-\x{06BF}\x{06C1}-\x{06C2}\x{06CC}\x{06CE}\x{06D0}-\x{06D1}\x{06FA}-\x{06FC}\x{06FF}\x{0712}-\x{0714}\x{071A}-\x{071D}\x{071F}-\x{0727}\x{0729}\x{072B}\x{072D}-\x{072E}\x{074E}-\x{0758}\x{075C}-\x{076A}\x{076D}-\x{0770}\x{0772}\x{0775}-\x{0777}\x{077A}-\x{077F}\x{07CA}-\x{07EA}\x{0841}-\x{0845}\x{0848}\x{084A}-\x{0853}\x{0855}\x{0860}\x{0862}-\x{0865}\x{0868}\x{08A0}-\x{08A9}\x{08AF}-\x{08B0}\x{08B3}-\x{08B4}\x{08B6}-\x{08B8}\x{08BA}-\x{08C7}\x{1807}\x{1820}-\x{1842}\x{1843}\x{1844}-\x{1878}\x{1887}-\x{18A8}\x{18AA}\x{A840}-\x{A871}\x{10AC0}-\x{10AC4}\x{10AD3}-\x{10AD6}\x{10AD8}-\x{10ADC}\x{10ADE}-\x{10AE0}\x{10AEB}-\x{10AEE}\x{10B80}\x{10B82}\x{10B86}-\x{10B88}\x{10B8A}-\x{10B8B}\x{10B8D}\x{10B90}\x{10BAD}-\x{10BAE}\x{10D01}-\x{10D21}\x{10D23}\x{10F30}-\x{10F32}\x{10F34}-\x{10F44}\x{10F51}-\x{10F53}\x{10FB0}\x{10FB2}-\x{10FB3}\x{10FB8}\x{10FBB}-\x{10FBC}\x{10FBE}-\x{10FBF}\x{10FC1}\x{10FC4}\x{10FCA}\x{1E900}-\x{1E943}]/u'; +} diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php new file mode 100644 index 0000000..0bbd335 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php @@ -0,0 +1,8 @@ + 'ss', + 962 => 'σ', + 8204 => '', + 8205 => '', +); diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php new file mode 100644 index 0000000..25a5f56 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php @@ -0,0 +1,2638 @@ + true, + 889 => true, + 896 => true, + 897 => true, + 898 => true, + 899 => true, + 907 => true, + 909 => true, + 930 => true, + 1216 => true, + 1328 => true, + 1367 => true, + 1368 => true, + 1419 => true, + 1420 => true, + 1424 => true, + 1480 => true, + 1481 => true, + 1482 => true, + 1483 => true, + 1484 => true, + 1485 => true, + 1486 => true, + 1487 => true, + 1515 => true, + 1516 => true, + 1517 => true, + 1518 => true, + 1525 => true, + 1526 => true, + 1527 => true, + 1528 => true, + 1529 => true, + 1530 => true, + 1531 => true, + 1532 => true, + 1533 => true, + 1534 => true, + 1535 => true, + 1536 => true, + 1537 => true, + 1538 => true, + 1539 => true, + 1540 => true, + 1541 => true, + 1564 => true, + 1565 => true, + 1757 => true, + 1806 => true, + 1807 => true, + 1867 => true, + 1868 => true, + 1970 => true, + 1971 => true, + 1972 => true, + 1973 => true, + 1974 => true, + 1975 => true, + 1976 => true, + 1977 => true, + 1978 => true, + 1979 => true, + 1980 => true, + 1981 => true, + 1982 => true, + 1983 => true, + 2043 => true, + 2044 => true, + 2094 => true, + 2095 => true, + 2111 => true, + 2140 => true, + 2141 => true, + 2143 => true, + 2229 => true, + 2248 => true, + 2249 => true, + 2250 => true, + 2251 => true, + 2252 => true, + 2253 => true, + 2254 => true, + 2255 => true, + 2256 => true, + 2257 => true, + 2258 => true, + 2274 => true, + 2436 => true, + 2445 => true, + 2446 => true, + 2449 => true, + 2450 => true, + 2473 => true, + 2481 => true, + 2483 => true, + 2484 => true, + 2485 => true, + 2490 => true, + 2491 => true, + 2501 => true, + 2502 => true, + 2505 => true, + 2506 => true, + 2511 => true, + 2512 => true, + 2513 => true, + 2514 => true, + 2515 => true, + 2516 => true, + 2517 => true, + 2518 => true, + 2520 => true, + 2521 => true, + 2522 => true, + 2523 => true, + 2526 => true, + 2532 => true, + 2533 => true, + 2559 => true, + 2560 => true, + 2564 => true, + 2571 => true, + 2572 => true, + 2573 => true, + 2574 => true, + 2577 => true, + 2578 => true, + 2601 => true, + 2609 => true, + 2612 => true, + 2615 => true, + 2618 => true, + 2619 => true, + 2621 => true, + 2627 => true, + 2628 => true, + 2629 => true, + 2630 => true, + 2633 => true, + 2634 => true, + 2638 => true, + 2639 => true, + 2640 => true, + 2642 => true, + 2643 => true, + 2644 => true, + 2645 => true, + 2646 => true, + 2647 => true, + 2648 => true, + 2653 => true, + 2655 => true, + 2656 => true, + 2657 => true, + 2658 => true, + 2659 => true, + 2660 => true, + 2661 => true, + 2679 => true, + 2680 => true, + 2681 => true, + 2682 => true, + 2683 => true, + 2684 => true, + 2685 => true, + 2686 => true, + 2687 => true, + 2688 => true, + 2692 => true, + 2702 => true, + 2706 => true, + 2729 => true, + 2737 => true, + 2740 => true, + 2746 => true, + 2747 => true, + 2758 => true, + 2762 => true, + 2766 => true, + 2767 => true, + 2769 => true, + 2770 => true, + 2771 => true, + 2772 => true, + 2773 => true, + 2774 => true, + 2775 => true, + 2776 => true, + 2777 => true, + 2778 => true, + 2779 => true, + 2780 => true, + 2781 => true, + 2782 => true, + 2783 => true, + 2788 => true, + 2789 => true, + 2802 => true, + 2803 => true, + 2804 => true, + 2805 => true, + 2806 => true, + 2807 => true, + 2808 => true, + 2816 => true, + 2820 => true, + 2829 => true, + 2830 => true, + 2833 => true, + 2834 => true, + 2857 => true, + 2865 => true, + 2868 => true, + 2874 => true, + 2875 => true, + 2885 => true, + 2886 => true, + 2889 => true, + 2890 => true, + 2894 => true, + 2895 => true, + 2896 => true, + 2897 => true, + 2898 => true, + 2899 => true, + 2900 => true, + 2904 => true, + 2905 => true, + 2906 => true, + 2907 => true, + 2910 => true, + 2916 => true, + 2917 => true, + 2936 => true, + 2937 => true, + 2938 => true, + 2939 => true, + 2940 => true, + 2941 => true, + 2942 => true, + 2943 => true, + 2944 => true, + 2945 => true, + 2948 => true, + 2955 => true, + 2956 => true, + 2957 => true, + 2961 => true, + 2966 => true, + 2967 => true, + 2968 => true, + 2971 => true, + 2973 => true, + 2976 => true, + 2977 => true, + 2978 => true, + 2981 => true, + 2982 => true, + 2983 => true, + 2987 => true, + 2988 => true, + 2989 => true, + 3002 => true, + 3003 => true, + 3004 => true, + 3005 => true, + 3011 => true, + 3012 => true, + 3013 => true, + 3017 => true, + 3022 => true, + 3023 => true, + 3025 => true, + 3026 => true, + 3027 => true, + 3028 => true, + 3029 => true, + 3030 => true, + 3032 => true, + 3033 => true, + 3034 => true, + 3035 => true, + 3036 => true, + 3037 => true, + 3038 => true, + 3039 => true, + 3040 => true, + 3041 => true, + 3042 => true, + 3043 => true, + 3044 => true, + 3045 => true, + 3067 => true, + 3068 => true, + 3069 => true, + 3070 => true, + 3071 => true, + 3085 => true, + 3089 => true, + 3113 => true, + 3130 => true, + 3131 => true, + 3132 => true, + 3141 => true, + 3145 => true, + 3150 => true, + 3151 => true, + 3152 => true, + 3153 => true, + 3154 => true, + 3155 => true, + 3156 => true, + 3159 => true, + 3163 => true, + 3164 => true, + 3165 => true, + 3166 => true, + 3167 => true, + 3172 => true, + 3173 => true, + 3184 => true, + 3185 => true, + 3186 => true, + 3187 => true, + 3188 => true, + 3189 => true, + 3190 => true, + 3213 => true, + 3217 => true, + 3241 => true, + 3252 => true, + 3258 => true, + 3259 => true, + 3269 => true, + 3273 => true, + 3278 => true, + 3279 => true, + 3280 => true, + 3281 => true, + 3282 => true, + 3283 => true, + 3284 => true, + 3287 => true, + 3288 => true, + 3289 => true, + 3290 => true, + 3291 => true, + 3292 => true, + 3293 => true, + 3295 => true, + 3300 => true, + 3301 => true, + 3312 => true, + 3315 => true, + 3316 => true, + 3317 => true, + 3318 => true, + 3319 => true, + 3320 => true, + 3321 => true, + 3322 => true, + 3323 => true, + 3324 => true, + 3325 => true, + 3326 => true, + 3327 => true, + 3341 => true, + 3345 => true, + 3397 => true, + 3401 => true, + 3408 => true, + 3409 => true, + 3410 => true, + 3411 => true, + 3428 => true, + 3429 => true, + 3456 => true, + 3460 => true, + 3479 => true, + 3480 => true, + 3481 => true, + 3506 => true, + 3516 => true, + 3518 => true, + 3519 => true, + 3527 => true, + 3528 => true, + 3529 => true, + 3531 => true, + 3532 => true, + 3533 => true, + 3534 => true, + 3541 => true, + 3543 => true, + 3552 => true, + 3553 => true, + 3554 => true, + 3555 => true, + 3556 => true, + 3557 => true, + 3568 => true, + 3569 => true, + 3573 => true, + 3574 => true, + 3575 => true, + 3576 => true, + 3577 => true, + 3578 => true, + 3579 => true, + 3580 => true, + 3581 => true, + 3582 => true, + 3583 => true, + 3584 => true, + 3643 => true, + 3644 => true, + 3645 => true, + 3646 => true, + 3715 => true, + 3717 => true, + 3723 => true, + 3748 => true, + 3750 => true, + 3774 => true, + 3775 => true, + 3781 => true, + 3783 => true, + 3790 => true, + 3791 => true, + 3802 => true, + 3803 => true, + 3912 => true, + 3949 => true, + 3950 => true, + 3951 => true, + 3952 => true, + 3992 => true, + 4029 => true, + 4045 => true, + 4294 => true, + 4296 => true, + 4297 => true, + 4298 => true, + 4299 => true, + 4300 => true, + 4302 => true, + 4303 => true, + 4447 => true, + 4448 => true, + 4681 => true, + 4686 => true, + 4687 => true, + 4695 => true, + 4697 => true, + 4702 => true, + 4703 => true, + 4745 => true, + 4750 => true, + 4751 => true, + 4785 => true, + 4790 => true, + 4791 => true, + 4799 => true, + 4801 => true, + 4806 => true, + 4807 => true, + 4823 => true, + 4881 => true, + 4886 => true, + 4887 => true, + 4955 => true, + 4956 => true, + 4989 => true, + 4990 => true, + 4991 => true, + 5018 => true, + 5019 => true, + 5020 => true, + 5021 => true, + 5022 => true, + 5023 => true, + 5110 => true, + 5111 => true, + 5118 => true, + 5119 => true, + 5760 => true, + 5789 => true, + 5790 => true, + 5791 => true, + 5881 => true, + 5882 => true, + 5883 => true, + 5884 => true, + 5885 => true, + 5886 => true, + 5887 => true, + 5901 => true, + 5909 => true, + 5910 => true, + 5911 => true, + 5912 => true, + 5913 => true, + 5914 => true, + 5915 => true, + 5916 => true, + 5917 => true, + 5918 => true, + 5919 => true, + 5943 => true, + 5944 => true, + 5945 => true, + 5946 => true, + 5947 => true, + 5948 => true, + 5949 => true, + 5950 => true, + 5951 => true, + 5972 => true, + 5973 => true, + 5974 => true, + 5975 => true, + 5976 => true, + 5977 => true, + 5978 => true, + 5979 => true, + 5980 => true, + 5981 => true, + 5982 => true, + 5983 => true, + 5997 => true, + 6001 => true, + 6004 => true, + 6005 => true, + 6006 => true, + 6007 => true, + 6008 => true, + 6009 => true, + 6010 => true, + 6011 => true, + 6012 => true, + 6013 => true, + 6014 => true, + 6015 => true, + 6068 => true, + 6069 => true, + 6110 => true, + 6111 => true, + 6122 => true, + 6123 => true, + 6124 => true, + 6125 => true, + 6126 => true, + 6127 => true, + 6138 => true, + 6139 => true, + 6140 => true, + 6141 => true, + 6142 => true, + 6143 => true, + 6150 => true, + 6158 => true, + 6159 => true, + 6170 => true, + 6171 => true, + 6172 => true, + 6173 => true, + 6174 => true, + 6175 => true, + 6265 => true, + 6266 => true, + 6267 => true, + 6268 => true, + 6269 => true, + 6270 => true, + 6271 => true, + 6315 => true, + 6316 => true, + 6317 => true, + 6318 => true, + 6319 => true, + 6390 => true, + 6391 => true, + 6392 => true, + 6393 => true, + 6394 => true, + 6395 => true, + 6396 => true, + 6397 => true, + 6398 => true, + 6399 => true, + 6431 => true, + 6444 => true, + 6445 => true, + 6446 => true, + 6447 => true, + 6460 => true, + 6461 => true, + 6462 => true, + 6463 => true, + 6465 => true, + 6466 => true, + 6467 => true, + 6510 => true, + 6511 => true, + 6517 => true, + 6518 => true, + 6519 => true, + 6520 => true, + 6521 => true, + 6522 => true, + 6523 => true, + 6524 => true, + 6525 => true, + 6526 => true, + 6527 => true, + 6572 => true, + 6573 => true, + 6574 => true, + 6575 => true, + 6602 => true, + 6603 => true, + 6604 => true, + 6605 => true, + 6606 => true, + 6607 => true, + 6619 => true, + 6620 => true, + 6621 => true, + 6684 => true, + 6685 => true, + 6751 => true, + 6781 => true, + 6782 => true, + 6794 => true, + 6795 => true, + 6796 => true, + 6797 => true, + 6798 => true, + 6799 => true, + 6810 => true, + 6811 => true, + 6812 => true, + 6813 => true, + 6814 => true, + 6815 => true, + 6830 => true, + 6831 => true, + 6988 => true, + 6989 => true, + 6990 => true, + 6991 => true, + 7037 => true, + 7038 => true, + 7039 => true, + 7156 => true, + 7157 => true, + 7158 => true, + 7159 => true, + 7160 => true, + 7161 => true, + 7162 => true, + 7163 => true, + 7224 => true, + 7225 => true, + 7226 => true, + 7242 => true, + 7243 => true, + 7244 => true, + 7305 => true, + 7306 => true, + 7307 => true, + 7308 => true, + 7309 => true, + 7310 => true, + 7311 => true, + 7355 => true, + 7356 => true, + 7368 => true, + 7369 => true, + 7370 => true, + 7371 => true, + 7372 => true, + 7373 => true, + 7374 => true, + 7375 => true, + 7419 => true, + 7420 => true, + 7421 => true, + 7422 => true, + 7423 => true, + 7674 => true, + 7958 => true, + 7959 => true, + 7966 => true, + 7967 => true, + 8006 => true, + 8007 => true, + 8014 => true, + 8015 => true, + 8024 => true, + 8026 => true, + 8028 => true, + 8030 => true, + 8062 => true, + 8063 => true, + 8117 => true, + 8133 => true, + 8148 => true, + 8149 => true, + 8156 => true, + 8176 => true, + 8177 => true, + 8181 => true, + 8191 => true, + 8206 => true, + 8207 => true, + 8228 => true, + 8229 => true, + 8230 => true, + 8232 => true, + 8233 => true, + 8234 => true, + 8235 => true, + 8236 => true, + 8237 => true, + 8238 => true, + 8289 => true, + 8290 => true, + 8291 => true, + 8293 => true, + 8294 => true, + 8295 => true, + 8296 => true, + 8297 => true, + 8298 => true, + 8299 => true, + 8300 => true, + 8301 => true, + 8302 => true, + 8303 => true, + 8306 => true, + 8307 => true, + 8335 => true, + 8349 => true, + 8350 => true, + 8351 => true, + 8384 => true, + 8385 => true, + 8386 => true, + 8387 => true, + 8388 => true, + 8389 => true, + 8390 => true, + 8391 => true, + 8392 => true, + 8393 => true, + 8394 => true, + 8395 => true, + 8396 => true, + 8397 => true, + 8398 => true, + 8399 => true, + 8433 => true, + 8434 => true, + 8435 => true, + 8436 => true, + 8437 => true, + 8438 => true, + 8439 => true, + 8440 => true, + 8441 => true, + 8442 => true, + 8443 => true, + 8444 => true, + 8445 => true, + 8446 => true, + 8447 => true, + 8498 => true, + 8579 => true, + 8588 => true, + 8589 => true, + 8590 => true, + 8591 => true, + 9255 => true, + 9256 => true, + 9257 => true, + 9258 => true, + 9259 => true, + 9260 => true, + 9261 => true, + 9262 => true, + 9263 => true, + 9264 => true, + 9265 => true, + 9266 => true, + 9267 => true, + 9268 => true, + 9269 => true, + 9270 => true, + 9271 => true, + 9272 => true, + 9273 => true, + 9274 => true, + 9275 => true, + 9276 => true, + 9277 => true, + 9278 => true, + 9279 => true, + 9291 => true, + 9292 => true, + 9293 => true, + 9294 => true, + 9295 => true, + 9296 => true, + 9297 => true, + 9298 => true, + 9299 => true, + 9300 => true, + 9301 => true, + 9302 => true, + 9303 => true, + 9304 => true, + 9305 => true, + 9306 => true, + 9307 => true, + 9308 => true, + 9309 => true, + 9310 => true, + 9311 => true, + 9352 => true, + 9353 => true, + 9354 => true, + 9355 => true, + 9356 => true, + 9357 => true, + 9358 => true, + 9359 => true, + 9360 => true, + 9361 => true, + 9362 => true, + 9363 => true, + 9364 => true, + 9365 => true, + 9366 => true, + 9367 => true, + 9368 => true, + 9369 => true, + 9370 => true, + 9371 => true, + 11124 => true, + 11125 => true, + 11158 => true, + 11311 => true, + 11359 => true, + 11508 => true, + 11509 => true, + 11510 => true, + 11511 => true, + 11512 => true, + 11558 => true, + 11560 => true, + 11561 => true, + 11562 => true, + 11563 => true, + 11564 => true, + 11566 => true, + 11567 => true, + 11624 => true, + 11625 => true, + 11626 => true, + 11627 => true, + 11628 => true, + 11629 => true, + 11630 => true, + 11633 => true, + 11634 => true, + 11635 => true, + 11636 => true, + 11637 => true, + 11638 => true, + 11639 => true, + 11640 => true, + 11641 => true, + 11642 => true, + 11643 => true, + 11644 => true, + 11645 => true, + 11646 => true, + 11671 => true, + 11672 => true, + 11673 => true, + 11674 => true, + 11675 => true, + 11676 => true, + 11677 => true, + 11678 => true, + 11679 => true, + 11687 => true, + 11695 => true, + 11703 => true, + 11711 => true, + 11719 => true, + 11727 => true, + 11735 => true, + 11743 => true, + 11930 => true, + 12020 => true, + 12021 => true, + 12022 => true, + 12023 => true, + 12024 => true, + 12025 => true, + 12026 => true, + 12027 => true, + 12028 => true, + 12029 => true, + 12030 => true, + 12031 => true, + 12246 => true, + 12247 => true, + 12248 => true, + 12249 => true, + 12250 => true, + 12251 => true, + 12252 => true, + 12253 => true, + 12254 => true, + 12255 => true, + 12256 => true, + 12257 => true, + 12258 => true, + 12259 => true, + 12260 => true, + 12261 => true, + 12262 => true, + 12263 => true, + 12264 => true, + 12265 => true, + 12266 => true, + 12267 => true, + 12268 => true, + 12269 => true, + 12270 => true, + 12271 => true, + 12272 => true, + 12273 => true, + 12274 => true, + 12275 => true, + 12276 => true, + 12277 => true, + 12278 => true, + 12279 => true, + 12280 => true, + 12281 => true, + 12282 => true, + 12283 => true, + 12284 => true, + 12285 => true, + 12286 => true, + 12287 => true, + 12352 => true, + 12439 => true, + 12440 => true, + 12544 => true, + 12545 => true, + 12546 => true, + 12547 => true, + 12548 => true, + 12592 => true, + 12644 => true, + 12687 => true, + 12772 => true, + 12773 => true, + 12774 => true, + 12775 => true, + 12776 => true, + 12777 => true, + 12778 => true, + 12779 => true, + 12780 => true, + 12781 => true, + 12782 => true, + 12783 => true, + 12831 => true, + 13250 => true, + 13255 => true, + 13272 => true, + 40957 => true, + 40958 => true, + 40959 => true, + 42125 => true, + 42126 => true, + 42127 => true, + 42183 => true, + 42184 => true, + 42185 => true, + 42186 => true, + 42187 => true, + 42188 => true, + 42189 => true, + 42190 => true, + 42191 => true, + 42540 => true, + 42541 => true, + 42542 => true, + 42543 => true, + 42544 => true, + 42545 => true, + 42546 => true, + 42547 => true, + 42548 => true, + 42549 => true, + 42550 => true, + 42551 => true, + 42552 => true, + 42553 => true, + 42554 => true, + 42555 => true, + 42556 => true, + 42557 => true, + 42558 => true, + 42559 => true, + 42744 => true, + 42745 => true, + 42746 => true, + 42747 => true, + 42748 => true, + 42749 => true, + 42750 => true, + 42751 => true, + 42944 => true, + 42945 => true, + 43053 => true, + 43054 => true, + 43055 => true, + 43066 => true, + 43067 => true, + 43068 => true, + 43069 => true, + 43070 => true, + 43071 => true, + 43128 => true, + 43129 => true, + 43130 => true, + 43131 => true, + 43132 => true, + 43133 => true, + 43134 => true, + 43135 => true, + 43206 => true, + 43207 => true, + 43208 => true, + 43209 => true, + 43210 => true, + 43211 => true, + 43212 => true, + 43213 => true, + 43226 => true, + 43227 => true, + 43228 => true, + 43229 => true, + 43230 => true, + 43231 => true, + 43348 => true, + 43349 => true, + 43350 => true, + 43351 => true, + 43352 => true, + 43353 => true, + 43354 => true, + 43355 => true, + 43356 => true, + 43357 => true, + 43358 => true, + 43389 => true, + 43390 => true, + 43391 => true, + 43470 => true, + 43482 => true, + 43483 => true, + 43484 => true, + 43485 => true, + 43519 => true, + 43575 => true, + 43576 => true, + 43577 => true, + 43578 => true, + 43579 => true, + 43580 => true, + 43581 => true, + 43582 => true, + 43583 => true, + 43598 => true, + 43599 => true, + 43610 => true, + 43611 => true, + 43715 => true, + 43716 => true, + 43717 => true, + 43718 => true, + 43719 => true, + 43720 => true, + 43721 => true, + 43722 => true, + 43723 => true, + 43724 => true, + 43725 => true, + 43726 => true, + 43727 => true, + 43728 => true, + 43729 => true, + 43730 => true, + 43731 => true, + 43732 => true, + 43733 => true, + 43734 => true, + 43735 => true, + 43736 => true, + 43737 => true, + 43738 => true, + 43767 => true, + 43768 => true, + 43769 => true, + 43770 => true, + 43771 => true, + 43772 => true, + 43773 => true, + 43774 => true, + 43775 => true, + 43776 => true, + 43783 => true, + 43784 => true, + 43791 => true, + 43792 => true, + 43799 => true, + 43800 => true, + 43801 => true, + 43802 => true, + 43803 => true, + 43804 => true, + 43805 => true, + 43806 => true, + 43807 => true, + 43815 => true, + 43823 => true, + 43884 => true, + 43885 => true, + 43886 => true, + 43887 => true, + 44014 => true, + 44015 => true, + 44026 => true, + 44027 => true, + 44028 => true, + 44029 => true, + 44030 => true, + 44031 => true, + 55204 => true, + 55205 => true, + 55206 => true, + 55207 => true, + 55208 => true, + 55209 => true, + 55210 => true, + 55211 => true, + 55212 => true, + 55213 => true, + 55214 => true, + 55215 => true, + 55239 => true, + 55240 => true, + 55241 => true, + 55242 => true, + 55292 => true, + 55293 => true, + 55294 => true, + 55295 => true, + 64110 => true, + 64111 => true, + 64263 => true, + 64264 => true, + 64265 => true, + 64266 => true, + 64267 => true, + 64268 => true, + 64269 => true, + 64270 => true, + 64271 => true, + 64272 => true, + 64273 => true, + 64274 => true, + 64280 => true, + 64281 => true, + 64282 => true, + 64283 => true, + 64284 => true, + 64311 => true, + 64317 => true, + 64319 => true, + 64322 => true, + 64325 => true, + 64450 => true, + 64451 => true, + 64452 => true, + 64453 => true, + 64454 => true, + 64455 => true, + 64456 => true, + 64457 => true, + 64458 => true, + 64459 => true, + 64460 => true, + 64461 => true, + 64462 => true, + 64463 => true, + 64464 => true, + 64465 => true, + 64466 => true, + 64832 => true, + 64833 => true, + 64834 => true, + 64835 => true, + 64836 => true, + 64837 => true, + 64838 => true, + 64839 => true, + 64840 => true, + 64841 => true, + 64842 => true, + 64843 => true, + 64844 => true, + 64845 => true, + 64846 => true, + 64847 => true, + 64912 => true, + 64913 => true, + 64968 => true, + 64969 => true, + 64970 => true, + 64971 => true, + 64972 => true, + 64973 => true, + 64974 => true, + 64975 => true, + 65022 => true, + 65023 => true, + 65042 => true, + 65049 => true, + 65050 => true, + 65051 => true, + 65052 => true, + 65053 => true, + 65054 => true, + 65055 => true, + 65072 => true, + 65106 => true, + 65107 => true, + 65127 => true, + 65132 => true, + 65133 => true, + 65134 => true, + 65135 => true, + 65141 => true, + 65277 => true, + 65278 => true, + 65280 => true, + 65440 => true, + 65471 => true, + 65472 => true, + 65473 => true, + 65480 => true, + 65481 => true, + 65488 => true, + 65489 => true, + 65496 => true, + 65497 => true, + 65501 => true, + 65502 => true, + 65503 => true, + 65511 => true, + 65519 => true, + 65520 => true, + 65521 => true, + 65522 => true, + 65523 => true, + 65524 => true, + 65525 => true, + 65526 => true, + 65527 => true, + 65528 => true, + 65529 => true, + 65530 => true, + 65531 => true, + 65532 => true, + 65533 => true, + 65534 => true, + 65535 => true, + 65548 => true, + 65575 => true, + 65595 => true, + 65598 => true, + 65614 => true, + 65615 => true, + 65787 => true, + 65788 => true, + 65789 => true, + 65790 => true, + 65791 => true, + 65795 => true, + 65796 => true, + 65797 => true, + 65798 => true, + 65844 => true, + 65845 => true, + 65846 => true, + 65935 => true, + 65949 => true, + 65950 => true, + 65951 => true, + 66205 => true, + 66206 => true, + 66207 => true, + 66257 => true, + 66258 => true, + 66259 => true, + 66260 => true, + 66261 => true, + 66262 => true, + 66263 => true, + 66264 => true, + 66265 => true, + 66266 => true, + 66267 => true, + 66268 => true, + 66269 => true, + 66270 => true, + 66271 => true, + 66300 => true, + 66301 => true, + 66302 => true, + 66303 => true, + 66340 => true, + 66341 => true, + 66342 => true, + 66343 => true, + 66344 => true, + 66345 => true, + 66346 => true, + 66347 => true, + 66348 => true, + 66379 => true, + 66380 => true, + 66381 => true, + 66382 => true, + 66383 => true, + 66427 => true, + 66428 => true, + 66429 => true, + 66430 => true, + 66431 => true, + 66462 => true, + 66500 => true, + 66501 => true, + 66502 => true, + 66503 => true, + 66718 => true, + 66719 => true, + 66730 => true, + 66731 => true, + 66732 => true, + 66733 => true, + 66734 => true, + 66735 => true, + 66772 => true, + 66773 => true, + 66774 => true, + 66775 => true, + 66812 => true, + 66813 => true, + 66814 => true, + 66815 => true, + 66856 => true, + 66857 => true, + 66858 => true, + 66859 => true, + 66860 => true, + 66861 => true, + 66862 => true, + 66863 => true, + 66916 => true, + 66917 => true, + 66918 => true, + 66919 => true, + 66920 => true, + 66921 => true, + 66922 => true, + 66923 => true, + 66924 => true, + 66925 => true, + 66926 => true, + 67383 => true, + 67384 => true, + 67385 => true, + 67386 => true, + 67387 => true, + 67388 => true, + 67389 => true, + 67390 => true, + 67391 => true, + 67414 => true, + 67415 => true, + 67416 => true, + 67417 => true, + 67418 => true, + 67419 => true, + 67420 => true, + 67421 => true, + 67422 => true, + 67423 => true, + 67590 => true, + 67591 => true, + 67593 => true, + 67638 => true, + 67641 => true, + 67642 => true, + 67643 => true, + 67645 => true, + 67646 => true, + 67670 => true, + 67743 => true, + 67744 => true, + 67745 => true, + 67746 => true, + 67747 => true, + 67748 => true, + 67749 => true, + 67750 => true, + 67827 => true, + 67830 => true, + 67831 => true, + 67832 => true, + 67833 => true, + 67834 => true, + 67868 => true, + 67869 => true, + 67870 => true, + 67898 => true, + 67899 => true, + 67900 => true, + 67901 => true, + 67902 => true, + 68024 => true, + 68025 => true, + 68026 => true, + 68027 => true, + 68048 => true, + 68049 => true, + 68100 => true, + 68103 => true, + 68104 => true, + 68105 => true, + 68106 => true, + 68107 => true, + 68116 => true, + 68120 => true, + 68150 => true, + 68151 => true, + 68155 => true, + 68156 => true, + 68157 => true, + 68158 => true, + 68169 => true, + 68170 => true, + 68171 => true, + 68172 => true, + 68173 => true, + 68174 => true, + 68175 => true, + 68185 => true, + 68186 => true, + 68187 => true, + 68188 => true, + 68189 => true, + 68190 => true, + 68191 => true, + 68327 => true, + 68328 => true, + 68329 => true, + 68330 => true, + 68343 => true, + 68344 => true, + 68345 => true, + 68346 => true, + 68347 => true, + 68348 => true, + 68349 => true, + 68350 => true, + 68351 => true, + 68406 => true, + 68407 => true, + 68408 => true, + 68438 => true, + 68439 => true, + 68467 => true, + 68468 => true, + 68469 => true, + 68470 => true, + 68471 => true, + 68498 => true, + 68499 => true, + 68500 => true, + 68501 => true, + 68502 => true, + 68503 => true, + 68504 => true, + 68509 => true, + 68510 => true, + 68511 => true, + 68512 => true, + 68513 => true, + 68514 => true, + 68515 => true, + 68516 => true, + 68517 => true, + 68518 => true, + 68519 => true, + 68520 => true, + 68787 => true, + 68788 => true, + 68789 => true, + 68790 => true, + 68791 => true, + 68792 => true, + 68793 => true, + 68794 => true, + 68795 => true, + 68796 => true, + 68797 => true, + 68798 => true, + 68799 => true, + 68851 => true, + 68852 => true, + 68853 => true, + 68854 => true, + 68855 => true, + 68856 => true, + 68857 => true, + 68904 => true, + 68905 => true, + 68906 => true, + 68907 => true, + 68908 => true, + 68909 => true, + 68910 => true, + 68911 => true, + 69247 => true, + 69290 => true, + 69294 => true, + 69295 => true, + 69416 => true, + 69417 => true, + 69418 => true, + 69419 => true, + 69420 => true, + 69421 => true, + 69422 => true, + 69423 => true, + 69580 => true, + 69581 => true, + 69582 => true, + 69583 => true, + 69584 => true, + 69585 => true, + 69586 => true, + 69587 => true, + 69588 => true, + 69589 => true, + 69590 => true, + 69591 => true, + 69592 => true, + 69593 => true, + 69594 => true, + 69595 => true, + 69596 => true, + 69597 => true, + 69598 => true, + 69599 => true, + 69623 => true, + 69624 => true, + 69625 => true, + 69626 => true, + 69627 => true, + 69628 => true, + 69629 => true, + 69630 => true, + 69631 => true, + 69710 => true, + 69711 => true, + 69712 => true, + 69713 => true, + 69744 => true, + 69745 => true, + 69746 => true, + 69747 => true, + 69748 => true, + 69749 => true, + 69750 => true, + 69751 => true, + 69752 => true, + 69753 => true, + 69754 => true, + 69755 => true, + 69756 => true, + 69757 => true, + 69758 => true, + 69821 => true, + 69826 => true, + 69827 => true, + 69828 => true, + 69829 => true, + 69830 => true, + 69831 => true, + 69832 => true, + 69833 => true, + 69834 => true, + 69835 => true, + 69836 => true, + 69837 => true, + 69838 => true, + 69839 => true, + 69865 => true, + 69866 => true, + 69867 => true, + 69868 => true, + 69869 => true, + 69870 => true, + 69871 => true, + 69882 => true, + 69883 => true, + 69884 => true, + 69885 => true, + 69886 => true, + 69887 => true, + 69941 => true, + 69960 => true, + 69961 => true, + 69962 => true, + 69963 => true, + 69964 => true, + 69965 => true, + 69966 => true, + 69967 => true, + 70007 => true, + 70008 => true, + 70009 => true, + 70010 => true, + 70011 => true, + 70012 => true, + 70013 => true, + 70014 => true, + 70015 => true, + 70112 => true, + 70133 => true, + 70134 => true, + 70135 => true, + 70136 => true, + 70137 => true, + 70138 => true, + 70139 => true, + 70140 => true, + 70141 => true, + 70142 => true, + 70143 => true, + 70162 => true, + 70279 => true, + 70281 => true, + 70286 => true, + 70302 => true, + 70314 => true, + 70315 => true, + 70316 => true, + 70317 => true, + 70318 => true, + 70319 => true, + 70379 => true, + 70380 => true, + 70381 => true, + 70382 => true, + 70383 => true, + 70394 => true, + 70395 => true, + 70396 => true, + 70397 => true, + 70398 => true, + 70399 => true, + 70404 => true, + 70413 => true, + 70414 => true, + 70417 => true, + 70418 => true, + 70441 => true, + 70449 => true, + 70452 => true, + 70458 => true, + 70469 => true, + 70470 => true, + 70473 => true, + 70474 => true, + 70478 => true, + 70479 => true, + 70481 => true, + 70482 => true, + 70483 => true, + 70484 => true, + 70485 => true, + 70486 => true, + 70488 => true, + 70489 => true, + 70490 => true, + 70491 => true, + 70492 => true, + 70500 => true, + 70501 => true, + 70509 => true, + 70510 => true, + 70511 => true, + 70748 => true, + 70754 => true, + 70755 => true, + 70756 => true, + 70757 => true, + 70758 => true, + 70759 => true, + 70760 => true, + 70761 => true, + 70762 => true, + 70763 => true, + 70764 => true, + 70765 => true, + 70766 => true, + 70767 => true, + 70768 => true, + 70769 => true, + 70770 => true, + 70771 => true, + 70772 => true, + 70773 => true, + 70774 => true, + 70775 => true, + 70776 => true, + 70777 => true, + 70778 => true, + 70779 => true, + 70780 => true, + 70781 => true, + 70782 => true, + 70783 => true, + 70856 => true, + 70857 => true, + 70858 => true, + 70859 => true, + 70860 => true, + 70861 => true, + 70862 => true, + 70863 => true, + 71094 => true, + 71095 => true, + 71237 => true, + 71238 => true, + 71239 => true, + 71240 => true, + 71241 => true, + 71242 => true, + 71243 => true, + 71244 => true, + 71245 => true, + 71246 => true, + 71247 => true, + 71258 => true, + 71259 => true, + 71260 => true, + 71261 => true, + 71262 => true, + 71263 => true, + 71277 => true, + 71278 => true, + 71279 => true, + 71280 => true, + 71281 => true, + 71282 => true, + 71283 => true, + 71284 => true, + 71285 => true, + 71286 => true, + 71287 => true, + 71288 => true, + 71289 => true, + 71290 => true, + 71291 => true, + 71292 => true, + 71293 => true, + 71294 => true, + 71295 => true, + 71353 => true, + 71354 => true, + 71355 => true, + 71356 => true, + 71357 => true, + 71358 => true, + 71359 => true, + 71451 => true, + 71452 => true, + 71468 => true, + 71469 => true, + 71470 => true, + 71471 => true, + 71923 => true, + 71924 => true, + 71925 => true, + 71926 => true, + 71927 => true, + 71928 => true, + 71929 => true, + 71930 => true, + 71931 => true, + 71932 => true, + 71933 => true, + 71934 => true, + 71943 => true, + 71944 => true, + 71946 => true, + 71947 => true, + 71956 => true, + 71959 => true, + 71990 => true, + 71993 => true, + 71994 => true, + 72007 => true, + 72008 => true, + 72009 => true, + 72010 => true, + 72011 => true, + 72012 => true, + 72013 => true, + 72014 => true, + 72015 => true, + 72104 => true, + 72105 => true, + 72152 => true, + 72153 => true, + 72165 => true, + 72166 => true, + 72167 => true, + 72168 => true, + 72169 => true, + 72170 => true, + 72171 => true, + 72172 => true, + 72173 => true, + 72174 => true, + 72175 => true, + 72176 => true, + 72177 => true, + 72178 => true, + 72179 => true, + 72180 => true, + 72181 => true, + 72182 => true, + 72183 => true, + 72184 => true, + 72185 => true, + 72186 => true, + 72187 => true, + 72188 => true, + 72189 => true, + 72190 => true, + 72191 => true, + 72264 => true, + 72265 => true, + 72266 => true, + 72267 => true, + 72268 => true, + 72269 => true, + 72270 => true, + 72271 => true, + 72355 => true, + 72356 => true, + 72357 => true, + 72358 => true, + 72359 => true, + 72360 => true, + 72361 => true, + 72362 => true, + 72363 => true, + 72364 => true, + 72365 => true, + 72366 => true, + 72367 => true, + 72368 => true, + 72369 => true, + 72370 => true, + 72371 => true, + 72372 => true, + 72373 => true, + 72374 => true, + 72375 => true, + 72376 => true, + 72377 => true, + 72378 => true, + 72379 => true, + 72380 => true, + 72381 => true, + 72382 => true, + 72383 => true, + 72713 => true, + 72759 => true, + 72774 => true, + 72775 => true, + 72776 => true, + 72777 => true, + 72778 => true, + 72779 => true, + 72780 => true, + 72781 => true, + 72782 => true, + 72783 => true, + 72813 => true, + 72814 => true, + 72815 => true, + 72848 => true, + 72849 => true, + 72872 => true, + 72967 => true, + 72970 => true, + 73015 => true, + 73016 => true, + 73017 => true, + 73019 => true, + 73022 => true, + 73032 => true, + 73033 => true, + 73034 => true, + 73035 => true, + 73036 => true, + 73037 => true, + 73038 => true, + 73039 => true, + 73050 => true, + 73051 => true, + 73052 => true, + 73053 => true, + 73054 => true, + 73055 => true, + 73062 => true, + 73065 => true, + 73103 => true, + 73106 => true, + 73113 => true, + 73114 => true, + 73115 => true, + 73116 => true, + 73117 => true, + 73118 => true, + 73119 => true, + 73649 => true, + 73650 => true, + 73651 => true, + 73652 => true, + 73653 => true, + 73654 => true, + 73655 => true, + 73656 => true, + 73657 => true, + 73658 => true, + 73659 => true, + 73660 => true, + 73661 => true, + 73662 => true, + 73663 => true, + 73714 => true, + 73715 => true, + 73716 => true, + 73717 => true, + 73718 => true, + 73719 => true, + 73720 => true, + 73721 => true, + 73722 => true, + 73723 => true, + 73724 => true, + 73725 => true, + 73726 => true, + 74863 => true, + 74869 => true, + 74870 => true, + 74871 => true, + 74872 => true, + 74873 => true, + 74874 => true, + 74875 => true, + 74876 => true, + 74877 => true, + 74878 => true, + 74879 => true, + 78895 => true, + 78896 => true, + 78897 => true, + 78898 => true, + 78899 => true, + 78900 => true, + 78901 => true, + 78902 => true, + 78903 => true, + 78904 => true, + 92729 => true, + 92730 => true, + 92731 => true, + 92732 => true, + 92733 => true, + 92734 => true, + 92735 => true, + 92767 => true, + 92778 => true, + 92779 => true, + 92780 => true, + 92781 => true, + 92910 => true, + 92911 => true, + 92918 => true, + 92919 => true, + 92920 => true, + 92921 => true, + 92922 => true, + 92923 => true, + 92924 => true, + 92925 => true, + 92926 => true, + 92927 => true, + 92998 => true, + 92999 => true, + 93000 => true, + 93001 => true, + 93002 => true, + 93003 => true, + 93004 => true, + 93005 => true, + 93006 => true, + 93007 => true, + 93018 => true, + 93026 => true, + 93048 => true, + 93049 => true, + 93050 => true, + 93051 => true, + 93052 => true, + 94027 => true, + 94028 => true, + 94029 => true, + 94030 => true, + 94088 => true, + 94089 => true, + 94090 => true, + 94091 => true, + 94092 => true, + 94093 => true, + 94094 => true, + 94181 => true, + 94182 => true, + 94183 => true, + 94184 => true, + 94185 => true, + 94186 => true, + 94187 => true, + 94188 => true, + 94189 => true, + 94190 => true, + 94191 => true, + 94194 => true, + 94195 => true, + 94196 => true, + 94197 => true, + 94198 => true, + 94199 => true, + 94200 => true, + 94201 => true, + 94202 => true, + 94203 => true, + 94204 => true, + 94205 => true, + 94206 => true, + 94207 => true, + 100344 => true, + 100345 => true, + 100346 => true, + 100347 => true, + 100348 => true, + 100349 => true, + 100350 => true, + 100351 => true, + 110931 => true, + 110932 => true, + 110933 => true, + 110934 => true, + 110935 => true, + 110936 => true, + 110937 => true, + 110938 => true, + 110939 => true, + 110940 => true, + 110941 => true, + 110942 => true, + 110943 => true, + 110944 => true, + 110945 => true, + 110946 => true, + 110947 => true, + 110952 => true, + 110953 => true, + 110954 => true, + 110955 => true, + 110956 => true, + 110957 => true, + 110958 => true, + 110959 => true, + 113771 => true, + 113772 => true, + 113773 => true, + 113774 => true, + 113775 => true, + 113789 => true, + 113790 => true, + 113791 => true, + 113801 => true, + 113802 => true, + 113803 => true, + 113804 => true, + 113805 => true, + 113806 => true, + 113807 => true, + 113818 => true, + 113819 => true, + 119030 => true, + 119031 => true, + 119032 => true, + 119033 => true, + 119034 => true, + 119035 => true, + 119036 => true, + 119037 => true, + 119038 => true, + 119039 => true, + 119079 => true, + 119080 => true, + 119155 => true, + 119156 => true, + 119157 => true, + 119158 => true, + 119159 => true, + 119160 => true, + 119161 => true, + 119162 => true, + 119273 => true, + 119274 => true, + 119275 => true, + 119276 => true, + 119277 => true, + 119278 => true, + 119279 => true, + 119280 => true, + 119281 => true, + 119282 => true, + 119283 => true, + 119284 => true, + 119285 => true, + 119286 => true, + 119287 => true, + 119288 => true, + 119289 => true, + 119290 => true, + 119291 => true, + 119292 => true, + 119293 => true, + 119294 => true, + 119295 => true, + 119540 => true, + 119541 => true, + 119542 => true, + 119543 => true, + 119544 => true, + 119545 => true, + 119546 => true, + 119547 => true, + 119548 => true, + 119549 => true, + 119550 => true, + 119551 => true, + 119639 => true, + 119640 => true, + 119641 => true, + 119642 => true, + 119643 => true, + 119644 => true, + 119645 => true, + 119646 => true, + 119647 => true, + 119893 => true, + 119965 => true, + 119968 => true, + 119969 => true, + 119971 => true, + 119972 => true, + 119975 => true, + 119976 => true, + 119981 => true, + 119994 => true, + 119996 => true, + 120004 => true, + 120070 => true, + 120075 => true, + 120076 => true, + 120085 => true, + 120093 => true, + 120122 => true, + 120127 => true, + 120133 => true, + 120135 => true, + 120136 => true, + 120137 => true, + 120145 => true, + 120486 => true, + 120487 => true, + 120780 => true, + 120781 => true, + 121484 => true, + 121485 => true, + 121486 => true, + 121487 => true, + 121488 => true, + 121489 => true, + 121490 => true, + 121491 => true, + 121492 => true, + 121493 => true, + 121494 => true, + 121495 => true, + 121496 => true, + 121497 => true, + 121498 => true, + 121504 => true, + 122887 => true, + 122905 => true, + 122906 => true, + 122914 => true, + 122917 => true, + 123181 => true, + 123182 => true, + 123183 => true, + 123198 => true, + 123199 => true, + 123210 => true, + 123211 => true, + 123212 => true, + 123213 => true, + 123642 => true, + 123643 => true, + 123644 => true, + 123645 => true, + 123646 => true, + 125125 => true, + 125126 => true, + 125260 => true, + 125261 => true, + 125262 => true, + 125263 => true, + 125274 => true, + 125275 => true, + 125276 => true, + 125277 => true, + 126468 => true, + 126496 => true, + 126499 => true, + 126501 => true, + 126502 => true, + 126504 => true, + 126515 => true, + 126520 => true, + 126522 => true, + 126524 => true, + 126525 => true, + 126526 => true, + 126527 => true, + 126528 => true, + 126529 => true, + 126531 => true, + 126532 => true, + 126533 => true, + 126534 => true, + 126536 => true, + 126538 => true, + 126540 => true, + 126544 => true, + 126547 => true, + 126549 => true, + 126550 => true, + 126552 => true, + 126554 => true, + 126556 => true, + 126558 => true, + 126560 => true, + 126563 => true, + 126565 => true, + 126566 => true, + 126571 => true, + 126579 => true, + 126584 => true, + 126589 => true, + 126591 => true, + 126602 => true, + 126620 => true, + 126621 => true, + 126622 => true, + 126623 => true, + 126624 => true, + 126628 => true, + 126634 => true, + 127020 => true, + 127021 => true, + 127022 => true, + 127023 => true, + 127124 => true, + 127125 => true, + 127126 => true, + 127127 => true, + 127128 => true, + 127129 => true, + 127130 => true, + 127131 => true, + 127132 => true, + 127133 => true, + 127134 => true, + 127135 => true, + 127151 => true, + 127152 => true, + 127168 => true, + 127184 => true, + 127222 => true, + 127223 => true, + 127224 => true, + 127225 => true, + 127226 => true, + 127227 => true, + 127228 => true, + 127229 => true, + 127230 => true, + 127231 => true, + 127232 => true, + 127491 => true, + 127492 => true, + 127493 => true, + 127494 => true, + 127495 => true, + 127496 => true, + 127497 => true, + 127498 => true, + 127499 => true, + 127500 => true, + 127501 => true, + 127502 => true, + 127503 => true, + 127548 => true, + 127549 => true, + 127550 => true, + 127551 => true, + 127561 => true, + 127562 => true, + 127563 => true, + 127564 => true, + 127565 => true, + 127566 => true, + 127567 => true, + 127570 => true, + 127571 => true, + 127572 => true, + 127573 => true, + 127574 => true, + 127575 => true, + 127576 => true, + 127577 => true, + 127578 => true, + 127579 => true, + 127580 => true, + 127581 => true, + 127582 => true, + 127583 => true, + 128728 => true, + 128729 => true, + 128730 => true, + 128731 => true, + 128732 => true, + 128733 => true, + 128734 => true, + 128735 => true, + 128749 => true, + 128750 => true, + 128751 => true, + 128765 => true, + 128766 => true, + 128767 => true, + 128884 => true, + 128885 => true, + 128886 => true, + 128887 => true, + 128888 => true, + 128889 => true, + 128890 => true, + 128891 => true, + 128892 => true, + 128893 => true, + 128894 => true, + 128895 => true, + 128985 => true, + 128986 => true, + 128987 => true, + 128988 => true, + 128989 => true, + 128990 => true, + 128991 => true, + 129004 => true, + 129005 => true, + 129006 => true, + 129007 => true, + 129008 => true, + 129009 => true, + 129010 => true, + 129011 => true, + 129012 => true, + 129013 => true, + 129014 => true, + 129015 => true, + 129016 => true, + 129017 => true, + 129018 => true, + 129019 => true, + 129020 => true, + 129021 => true, + 129022 => true, + 129023 => true, + 129036 => true, + 129037 => true, + 129038 => true, + 129039 => true, + 129096 => true, + 129097 => true, + 129098 => true, + 129099 => true, + 129100 => true, + 129101 => true, + 129102 => true, + 129103 => true, + 129114 => true, + 129115 => true, + 129116 => true, + 129117 => true, + 129118 => true, + 129119 => true, + 129160 => true, + 129161 => true, + 129162 => true, + 129163 => true, + 129164 => true, + 129165 => true, + 129166 => true, + 129167 => true, + 129198 => true, + 129199 => true, + 129401 => true, + 129484 => true, + 129620 => true, + 129621 => true, + 129622 => true, + 129623 => true, + 129624 => true, + 129625 => true, + 129626 => true, + 129627 => true, + 129628 => true, + 129629 => true, + 129630 => true, + 129631 => true, + 129646 => true, + 129647 => true, + 129653 => true, + 129654 => true, + 129655 => true, + 129659 => true, + 129660 => true, + 129661 => true, + 129662 => true, + 129663 => true, + 129671 => true, + 129672 => true, + 129673 => true, + 129674 => true, + 129675 => true, + 129676 => true, + 129677 => true, + 129678 => true, + 129679 => true, + 129705 => true, + 129706 => true, + 129707 => true, + 129708 => true, + 129709 => true, + 129710 => true, + 129711 => true, + 129719 => true, + 129720 => true, + 129721 => true, + 129722 => true, + 129723 => true, + 129724 => true, + 129725 => true, + 129726 => true, + 129727 => true, + 129731 => true, + 129732 => true, + 129733 => true, + 129734 => true, + 129735 => true, + 129736 => true, + 129737 => true, + 129738 => true, + 129739 => true, + 129740 => true, + 129741 => true, + 129742 => true, + 129743 => true, + 129939 => true, + 131070 => true, + 131071 => true, + 177973 => true, + 177974 => true, + 177975 => true, + 177976 => true, + 177977 => true, + 177978 => true, + 177979 => true, + 177980 => true, + 177981 => true, + 177982 => true, + 177983 => true, + 178206 => true, + 178207 => true, + 183970 => true, + 183971 => true, + 183972 => true, + 183973 => true, + 183974 => true, + 183975 => true, + 183976 => true, + 183977 => true, + 183978 => true, + 183979 => true, + 183980 => true, + 183981 => true, + 183982 => true, + 183983 => true, + 194664 => true, + 194676 => true, + 194847 => true, + 194911 => true, + 195007 => true, + 196606 => true, + 196607 => true, + 262142 => true, + 262143 => true, + 327678 => true, + 327679 => true, + 393214 => true, + 393215 => true, + 458750 => true, + 458751 => true, + 524286 => true, + 524287 => true, + 589822 => true, + 589823 => true, + 655358 => true, + 655359 => true, + 720894 => true, + 720895 => true, + 786430 => true, + 786431 => true, + 851966 => true, + 851967 => true, + 917502 => true, + 917503 => true, + 917504 => true, + 917505 => true, + 917506 => true, + 917507 => true, + 917508 => true, + 917509 => true, + 917510 => true, + 917511 => true, + 917512 => true, + 917513 => true, + 917514 => true, + 917515 => true, + 917516 => true, + 917517 => true, + 917518 => true, + 917519 => true, + 917520 => true, + 917521 => true, + 917522 => true, + 917523 => true, + 917524 => true, + 917525 => true, + 917526 => true, + 917527 => true, + 917528 => true, + 917529 => true, + 917530 => true, + 917531 => true, + 917532 => true, + 917533 => true, + 917534 => true, + 917535 => true, + 983038 => true, + 983039 => true, + 1048574 => true, + 1048575 => true, + 1114110 => true, + 1114111 => true, +); diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php new file mode 100644 index 0000000..54f21cc --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php @@ -0,0 +1,308 @@ + ' ', + 168 => ' ̈', + 175 => ' Ì„', + 180 => ' Ì', + 184 => ' ̧', + 728 => ' ̆', + 729 => ' ̇', + 730 => ' ÌŠ', + 731 => ' ̨', + 732 => ' ̃', + 733 => ' Ì‹', + 890 => ' ι', + 894 => ';', + 900 => ' Ì', + 901 => ' ̈Ì', + 8125 => ' Ì“', + 8127 => ' Ì“', + 8128 => ' Í‚', + 8129 => ' ̈͂', + 8141 => ' ̓̀', + 8142 => ' Ì“Ì', + 8143 => ' ̓͂', + 8157 => ' ̔̀', + 8158 => ' Ì”Ì', + 8159 => ' ̔͂', + 8173 => ' ̈̀', + 8174 => ' ̈Ì', + 8175 => '`', + 8189 => ' Ì', + 8190 => ' Ì”', + 8192 => ' ', + 8193 => ' ', + 8194 => ' ', + 8195 => ' ', + 8196 => ' ', + 8197 => ' ', + 8198 => ' ', + 8199 => ' ', + 8200 => ' ', + 8201 => ' ', + 8202 => ' ', + 8215 => ' ̳', + 8239 => ' ', + 8252 => '!!', + 8254 => ' Ì…', + 8263 => '??', + 8264 => '?!', + 8265 => '!?', + 8287 => ' ', + 8314 => '+', + 8316 => '=', + 8317 => '(', + 8318 => ')', + 8330 => '+', + 8332 => '=', + 8333 => '(', + 8334 => ')', + 8448 => 'a/c', + 8449 => 'a/s', + 8453 => 'c/o', + 8454 => 'c/u', + 9332 => '(1)', + 9333 => '(2)', + 9334 => '(3)', + 9335 => '(4)', + 9336 => '(5)', + 9337 => '(6)', + 9338 => '(7)', + 9339 => '(8)', + 9340 => '(9)', + 9341 => '(10)', + 9342 => '(11)', + 9343 => '(12)', + 9344 => '(13)', + 9345 => '(14)', + 9346 => '(15)', + 9347 => '(16)', + 9348 => '(17)', + 9349 => '(18)', + 9350 => '(19)', + 9351 => '(20)', + 9372 => '(a)', + 9373 => '(b)', + 9374 => '(c)', + 9375 => '(d)', + 9376 => '(e)', + 9377 => '(f)', + 9378 => '(g)', + 9379 => '(h)', + 9380 => '(i)', + 9381 => '(j)', + 9382 => '(k)', + 9383 => '(l)', + 9384 => '(m)', + 9385 => '(n)', + 9386 => '(o)', + 9387 => '(p)', + 9388 => '(q)', + 9389 => '(r)', + 9390 => '(s)', + 9391 => '(t)', + 9392 => '(u)', + 9393 => '(v)', + 9394 => '(w)', + 9395 => '(x)', + 9396 => '(y)', + 9397 => '(z)', + 10868 => '::=', + 10869 => '==', + 10870 => '===', + 12288 => ' ', + 12443 => ' ã‚™', + 12444 => ' ゚', + 12800 => '(á„€)', + 12801 => '(á„‚)', + 12802 => '(ᄃ)', + 12803 => '(á„…)', + 12804 => '(ᄆ)', + 12805 => '(ᄇ)', + 12806 => '(ᄉ)', + 12807 => '(á„‹)', + 12808 => '(ᄌ)', + 12809 => '(ᄎ)', + 12810 => '(á„)', + 12811 => '(á„)', + 12812 => '(á„‘)', + 12813 => '(á„’)', + 12814 => '(ê°€)', + 12815 => '(나)', + 12816 => '(다)', + 12817 => '(ë¼)', + 12818 => '(마)', + 12819 => '(ë°”)', + 12820 => '(사)', + 12821 => '(ì•„)', + 12822 => '(ìž)', + 12823 => '(ì°¨)', + 12824 => '(ì¹´)', + 12825 => '(타)', + 12826 => '(파)', + 12827 => '(하)', + 12828 => '(주)', + 12829 => '(오전)', + 12830 => '(오후)', + 12832 => '(一)', + 12833 => '(二)', + 12834 => '(三)', + 12835 => '(å››)', + 12836 => '(五)', + 12837 => '(å…­)', + 12838 => '(七)', + 12839 => '(å…«)', + 12840 => '(ä¹)', + 12841 => '(å)', + 12842 => '(月)', + 12843 => '(ç«)', + 12844 => '(æ°´)', + 12845 => '(木)', + 12846 => '(金)', + 12847 => '(土)', + 12848 => '(æ—¥)', + 12849 => '(æ ª)', + 12850 => '(有)', + 12851 => '(社)', + 12852 => '(å)', + 12853 => '(特)', + 12854 => '(財)', + 12855 => '(ç¥)', + 12856 => '(労)', + 12857 => '(代)', + 12858 => '(呼)', + 12859 => '(å­¦)', + 12860 => '(監)', + 12861 => '(ä¼)', + 12862 => '(資)', + 12863 => '(å”)', + 12864 => '(祭)', + 12865 => '(休)', + 12866 => '(自)', + 12867 => '(至)', + 64297 => '+', + 64606 => ' ٌّ', + 64607 => ' ÙÙ‘', + 64608 => ' ÙŽÙ‘', + 64609 => ' ÙÙ‘', + 64610 => ' ÙÙ‘', + 64611 => ' ّٰ', + 65018 => 'صلى الله عليه وسلم', + 65019 => 'جل جلاله', + 65040 => ',', + 65043 => ':', + 65044 => ';', + 65045 => '!', + 65046 => '?', + 65075 => '_', + 65076 => '_', + 65077 => '(', + 65078 => ')', + 65079 => '{', + 65080 => '}', + 65095 => '[', + 65096 => ']', + 65097 => ' Ì…', + 65098 => ' Ì…', + 65099 => ' Ì…', + 65100 => ' Ì…', + 65101 => '_', + 65102 => '_', + 65103 => '_', + 65104 => ',', + 65108 => ';', + 65109 => ':', + 65110 => '?', + 65111 => '!', + 65113 => '(', + 65114 => ')', + 65115 => '{', + 65116 => '}', + 65119 => '#', + 65120 => '&', + 65121 => '*', + 65122 => '+', + 65124 => '<', + 65125 => '>', + 65126 => '=', + 65128 => '\\', + 65129 => '$', + 65130 => '%', + 65131 => '@', + 65136 => ' Ù‹', + 65138 => ' ÙŒ', + 65140 => ' Ù', + 65142 => ' ÙŽ', + 65144 => ' Ù', + 65146 => ' Ù', + 65148 => ' Ù‘', + 65150 => ' Ù’', + 65281 => '!', + 65282 => '"', + 65283 => '#', + 65284 => '$', + 65285 => '%', + 65286 => '&', + 65287 => '\'', + 65288 => '(', + 65289 => ')', + 65290 => '*', + 65291 => '+', + 65292 => ',', + 65295 => '/', + 65306 => ':', + 65307 => ';', + 65308 => '<', + 65309 => '=', + 65310 => '>', + 65311 => '?', + 65312 => '@', + 65339 => '[', + 65340 => '\\', + 65341 => ']', + 65342 => '^', + 65343 => '_', + 65344 => '`', + 65371 => '{', + 65372 => '|', + 65373 => '}', + 65374 => '~', + 65507 => ' Ì„', + 127233 => '0,', + 127234 => '1,', + 127235 => '2,', + 127236 => '3,', + 127237 => '4,', + 127238 => '5,', + 127239 => '6,', + 127240 => '7,', + 127241 => '8,', + 127242 => '9,', + 127248 => '(a)', + 127249 => '(b)', + 127250 => '(c)', + 127251 => '(d)', + 127252 => '(e)', + 127253 => '(f)', + 127254 => '(g)', + 127255 => '(h)', + 127256 => '(i)', + 127257 => '(j)', + 127258 => '(k)', + 127259 => '(l)', + 127260 => '(m)', + 127261 => '(n)', + 127262 => '(o)', + 127263 => '(p)', + 127264 => '(q)', + 127265 => '(r)', + 127266 => '(s)', + 127267 => '(t)', + 127268 => '(u)', + 127269 => '(v)', + 127270 => '(w)', + 127271 => '(x)', + 127272 => '(y)', + 127273 => '(z)', +); diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php new file mode 100644 index 0000000..223396e --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php @@ -0,0 +1,71 @@ + true, + 1 => true, + 2 => true, + 3 => true, + 4 => true, + 5 => true, + 6 => true, + 7 => true, + 8 => true, + 9 => true, + 10 => true, + 11 => true, + 12 => true, + 13 => true, + 14 => true, + 15 => true, + 16 => true, + 17 => true, + 18 => true, + 19 => true, + 20 => true, + 21 => true, + 22 => true, + 23 => true, + 24 => true, + 25 => true, + 26 => true, + 27 => true, + 28 => true, + 29 => true, + 30 => true, + 31 => true, + 32 => true, + 33 => true, + 34 => true, + 35 => true, + 36 => true, + 37 => true, + 38 => true, + 39 => true, + 40 => true, + 41 => true, + 42 => true, + 43 => true, + 44 => true, + 47 => true, + 58 => true, + 59 => true, + 60 => true, + 61 => true, + 62 => true, + 63 => true, + 64 => true, + 91 => true, + 92 => true, + 93 => true, + 94 => true, + 95 => true, + 96 => true, + 123 => true, + 124 => true, + 125 => true, + 126 => true, + 127 => true, + 8800 => true, + 8814 => true, + 8815 => true, +); diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php new file mode 100644 index 0000000..b377844 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php @@ -0,0 +1,273 @@ + true, + 847 => true, + 6155 => true, + 6156 => true, + 6157 => true, + 8203 => true, + 8288 => true, + 8292 => true, + 65024 => true, + 65025 => true, + 65026 => true, + 65027 => true, + 65028 => true, + 65029 => true, + 65030 => true, + 65031 => true, + 65032 => true, + 65033 => true, + 65034 => true, + 65035 => true, + 65036 => true, + 65037 => true, + 65038 => true, + 65039 => true, + 65279 => true, + 113824 => true, + 113825 => true, + 113826 => true, + 113827 => true, + 917760 => true, + 917761 => true, + 917762 => true, + 917763 => true, + 917764 => true, + 917765 => true, + 917766 => true, + 917767 => true, + 917768 => true, + 917769 => true, + 917770 => true, + 917771 => true, + 917772 => true, + 917773 => true, + 917774 => true, + 917775 => true, + 917776 => true, + 917777 => true, + 917778 => true, + 917779 => true, + 917780 => true, + 917781 => true, + 917782 => true, + 917783 => true, + 917784 => true, + 917785 => true, + 917786 => true, + 917787 => true, + 917788 => true, + 917789 => true, + 917790 => true, + 917791 => true, + 917792 => true, + 917793 => true, + 917794 => true, + 917795 => true, + 917796 => true, + 917797 => true, + 917798 => true, + 917799 => true, + 917800 => true, + 917801 => true, + 917802 => true, + 917803 => true, + 917804 => true, + 917805 => true, + 917806 => true, + 917807 => true, + 917808 => true, + 917809 => true, + 917810 => true, + 917811 => true, + 917812 => true, + 917813 => true, + 917814 => true, + 917815 => true, + 917816 => true, + 917817 => true, + 917818 => true, + 917819 => true, + 917820 => true, + 917821 => true, + 917822 => true, + 917823 => true, + 917824 => true, + 917825 => true, + 917826 => true, + 917827 => true, + 917828 => true, + 917829 => true, + 917830 => true, + 917831 => true, + 917832 => true, + 917833 => true, + 917834 => true, + 917835 => true, + 917836 => true, + 917837 => true, + 917838 => true, + 917839 => true, + 917840 => true, + 917841 => true, + 917842 => true, + 917843 => true, + 917844 => true, + 917845 => true, + 917846 => true, + 917847 => true, + 917848 => true, + 917849 => true, + 917850 => true, + 917851 => true, + 917852 => true, + 917853 => true, + 917854 => true, + 917855 => true, + 917856 => true, + 917857 => true, + 917858 => true, + 917859 => true, + 917860 => true, + 917861 => true, + 917862 => true, + 917863 => true, + 917864 => true, + 917865 => true, + 917866 => true, + 917867 => true, + 917868 => true, + 917869 => true, + 917870 => true, + 917871 => true, + 917872 => true, + 917873 => true, + 917874 => true, + 917875 => true, + 917876 => true, + 917877 => true, + 917878 => true, + 917879 => true, + 917880 => true, + 917881 => true, + 917882 => true, + 917883 => true, + 917884 => true, + 917885 => true, + 917886 => true, + 917887 => true, + 917888 => true, + 917889 => true, + 917890 => true, + 917891 => true, + 917892 => true, + 917893 => true, + 917894 => true, + 917895 => true, + 917896 => true, + 917897 => true, + 917898 => true, + 917899 => true, + 917900 => true, + 917901 => true, + 917902 => true, + 917903 => true, + 917904 => true, + 917905 => true, + 917906 => true, + 917907 => true, + 917908 => true, + 917909 => true, + 917910 => true, + 917911 => true, + 917912 => true, + 917913 => true, + 917914 => true, + 917915 => true, + 917916 => true, + 917917 => true, + 917918 => true, + 917919 => true, + 917920 => true, + 917921 => true, + 917922 => true, + 917923 => true, + 917924 => true, + 917925 => true, + 917926 => true, + 917927 => true, + 917928 => true, + 917929 => true, + 917930 => true, + 917931 => true, + 917932 => true, + 917933 => true, + 917934 => true, + 917935 => true, + 917936 => true, + 917937 => true, + 917938 => true, + 917939 => true, + 917940 => true, + 917941 => true, + 917942 => true, + 917943 => true, + 917944 => true, + 917945 => true, + 917946 => true, + 917947 => true, + 917948 => true, + 917949 => true, + 917950 => true, + 917951 => true, + 917952 => true, + 917953 => true, + 917954 => true, + 917955 => true, + 917956 => true, + 917957 => true, + 917958 => true, + 917959 => true, + 917960 => true, + 917961 => true, + 917962 => true, + 917963 => true, + 917964 => true, + 917965 => true, + 917966 => true, + 917967 => true, + 917968 => true, + 917969 => true, + 917970 => true, + 917971 => true, + 917972 => true, + 917973 => true, + 917974 => true, + 917975 => true, + 917976 => true, + 917977 => true, + 917978 => true, + 917979 => true, + 917980 => true, + 917981 => true, + 917982 => true, + 917983 => true, + 917984 => true, + 917985 => true, + 917986 => true, + 917987 => true, + 917988 => true, + 917989 => true, + 917990 => true, + 917991 => true, + 917992 => true, + 917993 => true, + 917994 => true, + 917995 => true, + 917996 => true, + 917997 => true, + 917998 => true, + 917999 => true, +); diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php new file mode 100644 index 0000000..9b85fe9 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php @@ -0,0 +1,5778 @@ + 'a', + 66 => 'b', + 67 => 'c', + 68 => 'd', + 69 => 'e', + 70 => 'f', + 71 => 'g', + 72 => 'h', + 73 => 'i', + 74 => 'j', + 75 => 'k', + 76 => 'l', + 77 => 'm', + 78 => 'n', + 79 => 'o', + 80 => 'p', + 81 => 'q', + 82 => 'r', + 83 => 's', + 84 => 't', + 85 => 'u', + 86 => 'v', + 87 => 'w', + 88 => 'x', + 89 => 'y', + 90 => 'z', + 170 => 'a', + 178 => '2', + 179 => '3', + 181 => 'μ', + 185 => '1', + 186 => 'o', + 188 => '1â„4', + 189 => '1â„2', + 190 => '3â„4', + 192 => 'à', + 193 => 'á', + 194 => 'â', + 195 => 'ã', + 196 => 'ä', + 197 => 'Ã¥', + 198 => 'æ', + 199 => 'ç', + 200 => 'è', + 201 => 'é', + 202 => 'ê', + 203 => 'ë', + 204 => 'ì', + 205 => 'í', + 206 => 'î', + 207 => 'ï', + 208 => 'ð', + 209 => 'ñ', + 210 => 'ò', + 211 => 'ó', + 212 => 'ô', + 213 => 'õ', + 214 => 'ö', + 216 => 'ø', + 217 => 'ù', + 218 => 'ú', + 219 => 'û', + 220 => 'ü', + 221 => 'ý', + 222 => 'þ', + 256 => 'Ä', + 258 => 'ă', + 260 => 'Ä…', + 262 => 'ć', + 264 => 'ĉ', + 266 => 'Ä‹', + 268 => 'Ä', + 270 => 'Ä', + 272 => 'Ä‘', + 274 => 'Ä“', + 276 => 'Ä•', + 278 => 'Ä—', + 280 => 'Ä™', + 282 => 'Ä›', + 284 => 'Ä', + 286 => 'ÄŸ', + 288 => 'Ä¡', + 290 => 'Ä£', + 292 => 'Ä¥', + 294 => 'ħ', + 296 => 'Ä©', + 298 => 'Ä«', + 300 => 'Ä­', + 302 => 'į', + 304 => 'i̇', + 306 => 'ij', + 307 => 'ij', + 308 => 'ĵ', + 310 => 'Ä·', + 313 => 'ĺ', + 315 => 'ļ', + 317 => 'ľ', + 319 => 'l·', + 320 => 'l·', + 321 => 'Å‚', + 323 => 'Å„', + 325 => 'ņ', + 327 => 'ň', + 329 => 'ʼn', + 330 => 'Å‹', + 332 => 'Å', + 334 => 'Å', + 336 => 'Å‘', + 338 => 'Å“', + 340 => 'Å•', + 342 => 'Å—', + 344 => 'Å™', + 346 => 'Å›', + 348 => 'Å', + 350 => 'ÅŸ', + 352 => 'Å¡', + 354 => 'Å£', + 356 => 'Å¥', + 358 => 'ŧ', + 360 => 'Å©', + 362 => 'Å«', + 364 => 'Å­', + 366 => 'ů', + 368 => 'ű', + 370 => 'ų', + 372 => 'ŵ', + 374 => 'Å·', + 376 => 'ÿ', + 377 => 'ź', + 379 => 'ż', + 381 => 'ž', + 383 => 's', + 385 => 'É“', + 386 => 'ƃ', + 388 => 'Æ…', + 390 => 'É”', + 391 => 'ƈ', + 393 => 'É–', + 394 => 'É—', + 395 => 'ÆŒ', + 398 => 'Ç', + 399 => 'É™', + 400 => 'É›', + 401 => 'Æ’', + 403 => 'É ', + 404 => 'É£', + 406 => 'É©', + 407 => 'ɨ', + 408 => 'Æ™', + 412 => 'ɯ', + 413 => 'ɲ', + 415 => 'ɵ', + 416 => 'Æ¡', + 418 => 'Æ£', + 420 => 'Æ¥', + 422 => 'Ê€', + 423 => 'ƨ', + 425 => 'ʃ', + 428 => 'Æ­', + 430 => 'ʈ', + 431 => 'ư', + 433 => 'ÊŠ', + 434 => 'Ê‹', + 435 => 'Æ´', + 437 => 'ƶ', + 439 => 'Ê’', + 440 => 'ƹ', + 444 => 'ƽ', + 452 => 'dž', + 453 => 'dž', + 454 => 'dž', + 455 => 'lj', + 456 => 'lj', + 457 => 'lj', + 458 => 'nj', + 459 => 'nj', + 460 => 'nj', + 461 => 'ÇŽ', + 463 => 'Ç', + 465 => 'Ç’', + 467 => 'Ç”', + 469 => 'Ç–', + 471 => 'ǘ', + 473 => 'Çš', + 475 => 'Çœ', + 478 => 'ÇŸ', + 480 => 'Ç¡', + 482 => 'Ç£', + 484 => 'Ç¥', + 486 => 'ǧ', + 488 => 'Ç©', + 490 => 'Ç«', + 492 => 'Ç­', + 494 => 'ǯ', + 497 => 'dz', + 498 => 'dz', + 499 => 'dz', + 500 => 'ǵ', + 502 => 'Æ•', + 503 => 'Æ¿', + 504 => 'ǹ', + 506 => 'Ç»', + 508 => 'ǽ', + 510 => 'Ç¿', + 512 => 'È', + 514 => 'ȃ', + 516 => 'È…', + 518 => 'ȇ', + 520 => 'ȉ', + 522 => 'È‹', + 524 => 'È', + 526 => 'È', + 528 => 'È‘', + 530 => 'È“', + 532 => 'È•', + 534 => 'È—', + 536 => 'È™', + 538 => 'È›', + 540 => 'È', + 542 => 'ÈŸ', + 544 => 'Æž', + 546 => 'È£', + 548 => 'È¥', + 550 => 'ȧ', + 552 => 'È©', + 554 => 'È«', + 556 => 'È­', + 558 => 'ȯ', + 560 => 'ȱ', + 562 => 'ȳ', + 570 => 'â±¥', + 571 => 'ȼ', + 573 => 'Æš', + 574 => 'ⱦ', + 577 => 'É‚', + 579 => 'Æ€', + 580 => 'ʉ', + 581 => 'ÊŒ', + 582 => 'ɇ', + 584 => 'ɉ', + 586 => 'É‹', + 588 => 'É', + 590 => 'É', + 688 => 'h', + 689 => 'ɦ', + 690 => 'j', + 691 => 'r', + 692 => 'ɹ', + 693 => 'É»', + 694 => 'Ê', + 695 => 'w', + 696 => 'y', + 736 => 'É£', + 737 => 'l', + 738 => 's', + 739 => 'x', + 740 => 'Ê•', + 832 => 'Ì€', + 833 => 'Ì', + 835 => 'Ì“', + 836 => '̈Ì', + 837 => 'ι', + 880 => 'ͱ', + 882 => 'ͳ', + 884 => 'ʹ', + 886 => 'Í·', + 895 => 'ϳ', + 902 => 'ά', + 903 => '·', + 904 => 'έ', + 905 => 'ή', + 906 => 'ί', + 908 => 'ÏŒ', + 910 => 'Ï', + 911 => 'ÏŽ', + 913 => 'α', + 914 => 'β', + 915 => 'γ', + 916 => 'δ', + 917 => 'ε', + 918 => 'ζ', + 919 => 'η', + 920 => 'θ', + 921 => 'ι', + 922 => 'κ', + 923 => 'λ', + 924 => 'μ', + 925 => 'ν', + 926 => 'ξ', + 927 => 'ο', + 928 => 'Ï€', + 929 => 'Ï', + 931 => 'σ', + 932 => 'Ï„', + 933 => 'Ï…', + 934 => 'φ', + 935 => 'χ', + 936 => 'ψ', + 937 => 'ω', + 938 => 'ÏŠ', + 939 => 'Ï‹', + 975 => 'Ï—', + 976 => 'β', + 977 => 'θ', + 978 => 'Ï…', + 979 => 'Ï', + 980 => 'Ï‹', + 981 => 'φ', + 982 => 'Ï€', + 984 => 'Ï™', + 986 => 'Ï›', + 988 => 'Ï', + 990 => 'ÏŸ', + 992 => 'Ï¡', + 994 => 'Ï£', + 996 => 'Ï¥', + 998 => 'ϧ', + 1000 => 'Ï©', + 1002 => 'Ï«', + 1004 => 'Ï­', + 1006 => 'ϯ', + 1008 => 'κ', + 1009 => 'Ï', + 1010 => 'σ', + 1012 => 'θ', + 1013 => 'ε', + 1015 => 'ϸ', + 1017 => 'σ', + 1018 => 'Ï»', + 1021 => 'Í»', + 1022 => 'ͼ', + 1023 => 'ͽ', + 1024 => 'Ñ', + 1025 => 'Ñ‘', + 1026 => 'Ñ’', + 1027 => 'Ñ“', + 1028 => 'Ñ”', + 1029 => 'Ñ•', + 1030 => 'Ñ–', + 1031 => 'Ñ—', + 1032 => 'ј', + 1033 => 'Ñ™', + 1034 => 'Ñš', + 1035 => 'Ñ›', + 1036 => 'Ñœ', + 1037 => 'Ñ', + 1038 => 'Ñž', + 1039 => 'ÑŸ', + 1040 => 'а', + 1041 => 'б', + 1042 => 'в', + 1043 => 'г', + 1044 => 'д', + 1045 => 'е', + 1046 => 'ж', + 1047 => 'з', + 1048 => 'и', + 1049 => 'й', + 1050 => 'к', + 1051 => 'л', + 1052 => 'м', + 1053 => 'н', + 1054 => 'о', + 1055 => 'п', + 1056 => 'Ñ€', + 1057 => 'Ñ', + 1058 => 'Ñ‚', + 1059 => 'у', + 1060 => 'Ñ„', + 1061 => 'Ñ…', + 1062 => 'ц', + 1063 => 'ч', + 1064 => 'ш', + 1065 => 'щ', + 1066 => 'ÑŠ', + 1067 => 'Ñ‹', + 1068 => 'ÑŒ', + 1069 => 'Ñ', + 1070 => 'ÑŽ', + 1071 => 'Ñ', + 1120 => 'Ñ¡', + 1122 => 'Ñ£', + 1124 => 'Ñ¥', + 1126 => 'ѧ', + 1128 => 'Ñ©', + 1130 => 'Ñ«', + 1132 => 'Ñ­', + 1134 => 'ѯ', + 1136 => 'ѱ', + 1138 => 'ѳ', + 1140 => 'ѵ', + 1142 => 'Ñ·', + 1144 => 'ѹ', + 1146 => 'Ñ»', + 1148 => 'ѽ', + 1150 => 'Ñ¿', + 1152 => 'Ò', + 1162 => 'Ò‹', + 1164 => 'Ò', + 1166 => 'Ò', + 1168 => 'Ò‘', + 1170 => 'Ò“', + 1172 => 'Ò•', + 1174 => 'Ò—', + 1176 => 'Ò™', + 1178 => 'Ò›', + 1180 => 'Ò', + 1182 => 'ÒŸ', + 1184 => 'Ò¡', + 1186 => 'Ò£', + 1188 => 'Ò¥', + 1190 => 'Ò§', + 1192 => 'Ò©', + 1194 => 'Ò«', + 1196 => 'Ò­', + 1198 => 'Ò¯', + 1200 => 'Ò±', + 1202 => 'Ò³', + 1204 => 'Òµ', + 1206 => 'Ò·', + 1208 => 'Ò¹', + 1210 => 'Ò»', + 1212 => 'Ò½', + 1214 => 'Ò¿', + 1217 => 'Ó‚', + 1219 => 'Ó„', + 1221 => 'Ó†', + 1223 => 'Óˆ', + 1225 => 'ÓŠ', + 1227 => 'ÓŒ', + 1229 => 'ÓŽ', + 1232 => 'Ó‘', + 1234 => 'Ó“', + 1236 => 'Ó•', + 1238 => 'Ó—', + 1240 => 'Ó™', + 1242 => 'Ó›', + 1244 => 'Ó', + 1246 => 'ÓŸ', + 1248 => 'Ó¡', + 1250 => 'Ó£', + 1252 => 'Ó¥', + 1254 => 'Ó§', + 1256 => 'Ó©', + 1258 => 'Ó«', + 1260 => 'Ó­', + 1262 => 'Ó¯', + 1264 => 'Ó±', + 1266 => 'Ó³', + 1268 => 'Óµ', + 1270 => 'Ó·', + 1272 => 'Ó¹', + 1274 => 'Ó»', + 1276 => 'Ó½', + 1278 => 'Ó¿', + 1280 => 'Ô', + 1282 => 'Ôƒ', + 1284 => 'Ô…', + 1286 => 'Ô‡', + 1288 => 'Ô‰', + 1290 => 'Ô‹', + 1292 => 'Ô', + 1294 => 'Ô', + 1296 => 'Ô‘', + 1298 => 'Ô“', + 1300 => 'Ô•', + 1302 => 'Ô—', + 1304 => 'Ô™', + 1306 => 'Ô›', + 1308 => 'Ô', + 1310 => 'ÔŸ', + 1312 => 'Ô¡', + 1314 => 'Ô£', + 1316 => 'Ô¥', + 1318 => 'Ô§', + 1320 => 'Ô©', + 1322 => 'Ô«', + 1324 => 'Ô­', + 1326 => 'Ô¯', + 1329 => 'Õ¡', + 1330 => 'Õ¢', + 1331 => 'Õ£', + 1332 => 'Õ¤', + 1333 => 'Õ¥', + 1334 => 'Õ¦', + 1335 => 'Õ§', + 1336 => 'Õ¨', + 1337 => 'Õ©', + 1338 => 'Õª', + 1339 => 'Õ«', + 1340 => 'Õ¬', + 1341 => 'Õ­', + 1342 => 'Õ®', + 1343 => 'Õ¯', + 1344 => 'Õ°', + 1345 => 'Õ±', + 1346 => 'Õ²', + 1347 => 'Õ³', + 1348 => 'Õ´', + 1349 => 'Õµ', + 1350 => 'Õ¶', + 1351 => 'Õ·', + 1352 => 'Õ¸', + 1353 => 'Õ¹', + 1354 => 'Õº', + 1355 => 'Õ»', + 1356 => 'Õ¼', + 1357 => 'Õ½', + 1358 => 'Õ¾', + 1359 => 'Õ¿', + 1360 => 'Ö€', + 1361 => 'Ö', + 1362 => 'Ö‚', + 1363 => 'Öƒ', + 1364 => 'Ö„', + 1365 => 'Ö…', + 1366 => 'Ö†', + 1415 => 'Õ¥Ö‚', + 1653 => 'اٴ', + 1654 => 'وٴ', + 1655 => 'Û‡Ù´', + 1656 => 'يٴ', + 2392 => 'क़', + 2393 => 'ख़', + 2394 => 'ग़', + 2395 => 'ज़', + 2396 => 'ड़', + 2397 => 'ढ़', + 2398 => 'फ़', + 2399 => 'य़', + 2524 => 'ড়', + 2525 => 'ঢ়', + 2527 => 'য়', + 2611 => 'ਲ਼', + 2614 => 'ਸ਼', + 2649 => 'ਖ਼', + 2650 => 'ਗ਼', + 2651 => 'ਜ਼', + 2654 => 'ਫ਼', + 2908 => 'ଡ଼', + 2909 => 'ଢ଼', + 3635 => 'à¹à¸²', + 3763 => 'à»àº²', + 3804 => 'ຫນ', + 3805 => 'ຫມ', + 3852 => '་', + 3907 => 'གྷ', + 3917 => 'ཌྷ', + 3922 => 'དྷ', + 3927 => 'བྷ', + 3932 => 'ཛྷ', + 3945 => 'ཀྵ', + 3955 => 'ཱི', + 3957 => 'ཱུ', + 3958 => 'ྲྀ', + 3959 => 'ྲཱྀ', + 3960 => 'ླྀ', + 3961 => 'ླཱྀ', + 3969 => 'ཱྀ', + 3987 => 'ྒྷ', + 3997 => 'ྜྷ', + 4002 => 'ྡྷ', + 4007 => 'ྦྷ', + 4012 => 'ྫྷ', + 4025 => 'à¾à¾µ', + 4295 => 'â´§', + 4301 => 'â´­', + 4348 => 'ნ', + 5112 => 'á°', + 5113 => 'á±', + 5114 => 'á²', + 5115 => 'á³', + 5116 => 'á´', + 5117 => 'áµ', + 7296 => 'в', + 7297 => 'д', + 7298 => 'о', + 7299 => 'Ñ', + 7300 => 'Ñ‚', + 7301 => 'Ñ‚', + 7302 => 'ÑŠ', + 7303 => 'Ñ£', + 7304 => 'ꙋ', + 7312 => 'áƒ', + 7313 => 'ბ', + 7314 => 'გ', + 7315 => 'დ', + 7316 => 'ე', + 7317 => 'ვ', + 7318 => 'ზ', + 7319 => 'თ', + 7320 => 'ი', + 7321 => 'კ', + 7322 => 'ლ', + 7323 => 'მ', + 7324 => 'ნ', + 7325 => 'áƒ', + 7326 => 'პ', + 7327 => 'ჟ', + 7328 => 'რ', + 7329 => 'ს', + 7330 => 'ტ', + 7331 => 'უ', + 7332 => 'ფ', + 7333 => 'ქ', + 7334 => 'ღ', + 7335 => 'ყ', + 7336 => 'შ', + 7337 => 'ჩ', + 7338 => 'ც', + 7339 => 'ძ', + 7340 => 'წ', + 7341 => 'ჭ', + 7342 => 'ხ', + 7343 => 'ჯ', + 7344 => 'ჰ', + 7345 => 'ჱ', + 7346 => 'ჲ', + 7347 => 'ჳ', + 7348 => 'ჴ', + 7349 => 'ჵ', + 7350 => 'ჶ', + 7351 => 'ჷ', + 7352 => 'ჸ', + 7353 => 'ჹ', + 7354 => 'ჺ', + 7357 => 'ჽ', + 7358 => 'ჾ', + 7359 => 'ჿ', + 7468 => 'a', + 7469 => 'æ', + 7470 => 'b', + 7472 => 'd', + 7473 => 'e', + 7474 => 'Ç', + 7475 => 'g', + 7476 => 'h', + 7477 => 'i', + 7478 => 'j', + 7479 => 'k', + 7480 => 'l', + 7481 => 'm', + 7482 => 'n', + 7484 => 'o', + 7485 => 'È£', + 7486 => 'p', + 7487 => 'r', + 7488 => 't', + 7489 => 'u', + 7490 => 'w', + 7491 => 'a', + 7492 => 'É', + 7493 => 'É‘', + 7494 => 'á´‚', + 7495 => 'b', + 7496 => 'd', + 7497 => 'e', + 7498 => 'É™', + 7499 => 'É›', + 7500 => 'Éœ', + 7501 => 'g', + 7503 => 'k', + 7504 => 'm', + 7505 => 'Å‹', + 7506 => 'o', + 7507 => 'É”', + 7508 => 'á´–', + 7509 => 'á´—', + 7510 => 'p', + 7511 => 't', + 7512 => 'u', + 7513 => 'á´', + 7514 => 'ɯ', + 7515 => 'v', + 7516 => 'á´¥', + 7517 => 'β', + 7518 => 'γ', + 7519 => 'δ', + 7520 => 'φ', + 7521 => 'χ', + 7522 => 'i', + 7523 => 'r', + 7524 => 'u', + 7525 => 'v', + 7526 => 'β', + 7527 => 'γ', + 7528 => 'Ï', + 7529 => 'φ', + 7530 => 'χ', + 7544 => 'н', + 7579 => 'É’', + 7580 => 'c', + 7581 => 'É•', + 7582 => 'ð', + 7583 => 'Éœ', + 7584 => 'f', + 7585 => 'ÉŸ', + 7586 => 'É¡', + 7587 => 'É¥', + 7588 => 'ɨ', + 7589 => 'É©', + 7590 => 'ɪ', + 7591 => 'áµ»', + 7592 => 'Ê', + 7593 => 'É­', + 7594 => 'á¶…', + 7595 => 'ÊŸ', + 7596 => 'ɱ', + 7597 => 'ɰ', + 7598 => 'ɲ', + 7599 => 'ɳ', + 7600 => 'É´', + 7601 => 'ɵ', + 7602 => 'ɸ', + 7603 => 'Ê‚', + 7604 => 'ʃ', + 7605 => 'Æ«', + 7606 => 'ʉ', + 7607 => 'ÊŠ', + 7608 => 'á´œ', + 7609 => 'Ê‹', + 7610 => 'ÊŒ', + 7611 => 'z', + 7612 => 'Ê', + 7613 => 'Ê‘', + 7614 => 'Ê’', + 7615 => 'θ', + 7680 => 'á¸', + 7682 => 'ḃ', + 7684 => 'ḅ', + 7686 => 'ḇ', + 7688 => 'ḉ', + 7690 => 'ḋ', + 7692 => 'á¸', + 7694 => 'á¸', + 7696 => 'ḑ', + 7698 => 'ḓ', + 7700 => 'ḕ', + 7702 => 'ḗ', + 7704 => 'ḙ', + 7706 => 'ḛ', + 7708 => 'á¸', + 7710 => 'ḟ', + 7712 => 'ḡ', + 7714 => 'ḣ', + 7716 => 'ḥ', + 7718 => 'ḧ', + 7720 => 'ḩ', + 7722 => 'ḫ', + 7724 => 'ḭ', + 7726 => 'ḯ', + 7728 => 'ḱ', + 7730 => 'ḳ', + 7732 => 'ḵ', + 7734 => 'ḷ', + 7736 => 'ḹ', + 7738 => 'ḻ', + 7740 => 'ḽ', + 7742 => 'ḿ', + 7744 => 'á¹', + 7746 => 'ṃ', + 7748 => 'á¹…', + 7750 => 'ṇ', + 7752 => 'ṉ', + 7754 => 'ṋ', + 7756 => 'á¹', + 7758 => 'á¹', + 7760 => 'ṑ', + 7762 => 'ṓ', + 7764 => 'ṕ', + 7766 => 'á¹—', + 7768 => 'á¹™', + 7770 => 'á¹›', + 7772 => 'á¹', + 7774 => 'ṟ', + 7776 => 'ṡ', + 7778 => 'á¹£', + 7780 => 'á¹¥', + 7782 => 'á¹§', + 7784 => 'ṩ', + 7786 => 'ṫ', + 7788 => 'á¹­', + 7790 => 'ṯ', + 7792 => 'á¹±', + 7794 => 'á¹³', + 7796 => 'á¹µ', + 7798 => 'á¹·', + 7800 => 'á¹¹', + 7802 => 'á¹»', + 7804 => 'á¹½', + 7806 => 'ṿ', + 7808 => 'áº', + 7810 => 'ẃ', + 7812 => 'ẅ', + 7814 => 'ẇ', + 7816 => 'ẉ', + 7818 => 'ẋ', + 7820 => 'áº', + 7822 => 'áº', + 7824 => 'ẑ', + 7826 => 'ẓ', + 7828 => 'ẕ', + 7834 => 'aʾ', + 7835 => 'ṡ', + 7838 => 'ss', + 7840 => 'ạ', + 7842 => 'ả', + 7844 => 'ấ', + 7846 => 'ầ', + 7848 => 'ẩ', + 7850 => 'ẫ', + 7852 => 'ậ', + 7854 => 'ắ', + 7856 => 'ằ', + 7858 => 'ẳ', + 7860 => 'ẵ', + 7862 => 'ặ', + 7864 => 'ẹ', + 7866 => 'ẻ', + 7868 => 'ẽ', + 7870 => 'ế', + 7872 => 'á»', + 7874 => 'ể', + 7876 => 'á»…', + 7878 => 'ệ', + 7880 => 'ỉ', + 7882 => 'ị', + 7884 => 'á»', + 7886 => 'á»', + 7888 => 'ố', + 7890 => 'ồ', + 7892 => 'ổ', + 7894 => 'á»—', + 7896 => 'á»™', + 7898 => 'á»›', + 7900 => 'á»', + 7902 => 'ở', + 7904 => 'ỡ', + 7906 => 'ợ', + 7908 => 'ụ', + 7910 => 'á»§', + 7912 => 'ứ', + 7914 => 'ừ', + 7916 => 'á»­', + 7918 => 'ữ', + 7920 => 'á»±', + 7922 => 'ỳ', + 7924 => 'ỵ', + 7926 => 'á»·', + 7928 => 'ỹ', + 7930 => 'á»»', + 7932 => 'ỽ', + 7934 => 'ỿ', + 7944 => 'á¼€', + 7945 => 'á¼', + 7946 => 'ἂ', + 7947 => 'ἃ', + 7948 => 'ἄ', + 7949 => 'á¼…', + 7950 => 'ἆ', + 7951 => 'ἇ', + 7960 => 'á¼', + 7961 => 'ἑ', + 7962 => 'á¼’', + 7963 => 'ἓ', + 7964 => 'á¼”', + 7965 => 'ἕ', + 7976 => 'á¼ ', + 7977 => 'ἡ', + 7978 => 'á¼¢', + 7979 => 'á¼£', + 7980 => 'ἤ', + 7981 => 'á¼¥', + 7982 => 'ἦ', + 7983 => 'á¼§', + 7992 => 'á¼°', + 7993 => 'á¼±', + 7994 => 'á¼²', + 7995 => 'á¼³', + 7996 => 'á¼´', + 7997 => 'á¼µ', + 7998 => 'á¼¶', + 7999 => 'á¼·', + 8008 => 'á½€', + 8009 => 'á½', + 8010 => 'ὂ', + 8011 => 'ὃ', + 8012 => 'ὄ', + 8013 => 'á½…', + 8025 => 'ὑ', + 8027 => 'ὓ', + 8029 => 'ὕ', + 8031 => 'á½—', + 8040 => 'á½ ', + 8041 => 'ὡ', + 8042 => 'á½¢', + 8043 => 'á½£', + 8044 => 'ὤ', + 8045 => 'á½¥', + 8046 => 'ὦ', + 8047 => 'á½§', + 8049 => 'ά', + 8051 => 'έ', + 8053 => 'ή', + 8055 => 'ί', + 8057 => 'ÏŒ', + 8059 => 'Ï', + 8061 => 'ÏŽ', + 8064 => 'ἀι', + 8065 => 'á¼Î¹', + 8066 => 'ἂι', + 8067 => 'ἃι', + 8068 => 'ἄι', + 8069 => 'ἅι', + 8070 => 'ἆι', + 8071 => 'ἇι', + 8072 => 'ἀι', + 8073 => 'á¼Î¹', + 8074 => 'ἂι', + 8075 => 'ἃι', + 8076 => 'ἄι', + 8077 => 'ἅι', + 8078 => 'ἆι', + 8079 => 'ἇι', + 8080 => 'ἠι', + 8081 => 'ἡι', + 8082 => 'ἢι', + 8083 => 'ἣι', + 8084 => 'ἤι', + 8085 => 'ἥι', + 8086 => 'ἦι', + 8087 => 'ἧι', + 8088 => 'ἠι', + 8089 => 'ἡι', + 8090 => 'ἢι', + 8091 => 'ἣι', + 8092 => 'ἤι', + 8093 => 'ἥι', + 8094 => 'ἦι', + 8095 => 'ἧι', + 8096 => 'ὠι', + 8097 => 'ὡι', + 8098 => 'ὢι', + 8099 => 'ὣι', + 8100 => 'ὤι', + 8101 => 'ὥι', + 8102 => 'ὦι', + 8103 => 'ὧι', + 8104 => 'ὠι', + 8105 => 'ὡι', + 8106 => 'ὢι', + 8107 => 'ὣι', + 8108 => 'ὤι', + 8109 => 'ὥι', + 8110 => 'ὦι', + 8111 => 'ὧι', + 8114 => 'ὰι', + 8115 => 'αι', + 8116 => 'άι', + 8119 => 'ᾶι', + 8120 => 'á¾°', + 8121 => 'á¾±', + 8122 => 'á½°', + 8123 => 'ά', + 8124 => 'αι', + 8126 => 'ι', + 8130 => 'ὴι', + 8131 => 'ηι', + 8132 => 'ήι', + 8135 => 'ῆι', + 8136 => 'á½²', + 8137 => 'έ', + 8138 => 'á½´', + 8139 => 'ή', + 8140 => 'ηι', + 8147 => 'Î', + 8152 => 'á¿', + 8153 => 'á¿‘', + 8154 => 'á½¶', + 8155 => 'ί', + 8163 => 'ΰ', + 8168 => 'á¿ ', + 8169 => 'á¿¡', + 8170 => 'ὺ', + 8171 => 'Ï', + 8172 => 'á¿¥', + 8178 => 'ὼι', + 8179 => 'ωι', + 8180 => 'ώι', + 8183 => 'ῶι', + 8184 => 'ὸ', + 8185 => 'ÏŒ', + 8186 => 'á½¼', + 8187 => 'ÏŽ', + 8188 => 'ωι', + 8209 => 'â€', + 8243 => '′′', + 8244 => '′′′', + 8246 => '‵‵', + 8247 => '‵‵‵', + 8279 => '′′′′', + 8304 => '0', + 8305 => 'i', + 8308 => '4', + 8309 => '5', + 8310 => '6', + 8311 => '7', + 8312 => '8', + 8313 => '9', + 8315 => '−', + 8319 => 'n', + 8320 => '0', + 8321 => '1', + 8322 => '2', + 8323 => '3', + 8324 => '4', + 8325 => '5', + 8326 => '6', + 8327 => '7', + 8328 => '8', + 8329 => '9', + 8331 => '−', + 8336 => 'a', + 8337 => 'e', + 8338 => 'o', + 8339 => 'x', + 8340 => 'É™', + 8341 => 'h', + 8342 => 'k', + 8343 => 'l', + 8344 => 'm', + 8345 => 'n', + 8346 => 'p', + 8347 => 's', + 8348 => 't', + 8360 => 'rs', + 8450 => 'c', + 8451 => '°c', + 8455 => 'É›', + 8457 => '°f', + 8458 => 'g', + 8459 => 'h', + 8460 => 'h', + 8461 => 'h', + 8462 => 'h', + 8463 => 'ħ', + 8464 => 'i', + 8465 => 'i', + 8466 => 'l', + 8467 => 'l', + 8469 => 'n', + 8470 => 'no', + 8473 => 'p', + 8474 => 'q', + 8475 => 'r', + 8476 => 'r', + 8477 => 'r', + 8480 => 'sm', + 8481 => 'tel', + 8482 => 'tm', + 8484 => 'z', + 8486 => 'ω', + 8488 => 'z', + 8490 => 'k', + 8491 => 'Ã¥', + 8492 => 'b', + 8493 => 'c', + 8495 => 'e', + 8496 => 'e', + 8497 => 'f', + 8499 => 'm', + 8500 => 'o', + 8501 => '×', + 8502 => 'ב', + 8503 => '×’', + 8504 => 'ד', + 8505 => 'i', + 8507 => 'fax', + 8508 => 'Ï€', + 8509 => 'γ', + 8510 => 'γ', + 8511 => 'Ï€', + 8512 => '∑', + 8517 => 'd', + 8518 => 'd', + 8519 => 'e', + 8520 => 'i', + 8521 => 'j', + 8528 => '1â„7', + 8529 => '1â„9', + 8530 => '1â„10', + 8531 => '1â„3', + 8532 => '2â„3', + 8533 => '1â„5', + 8534 => '2â„5', + 8535 => '3â„5', + 8536 => '4â„5', + 8537 => '1â„6', + 8538 => '5â„6', + 8539 => '1â„8', + 8540 => '3â„8', + 8541 => '5â„8', + 8542 => '7â„8', + 8543 => '1â„', + 8544 => 'i', + 8545 => 'ii', + 8546 => 'iii', + 8547 => 'iv', + 8548 => 'v', + 8549 => 'vi', + 8550 => 'vii', + 8551 => 'viii', + 8552 => 'ix', + 8553 => 'x', + 8554 => 'xi', + 8555 => 'xii', + 8556 => 'l', + 8557 => 'c', + 8558 => 'd', + 8559 => 'm', + 8560 => 'i', + 8561 => 'ii', + 8562 => 'iii', + 8563 => 'iv', + 8564 => 'v', + 8565 => 'vi', + 8566 => 'vii', + 8567 => 'viii', + 8568 => 'ix', + 8569 => 'x', + 8570 => 'xi', + 8571 => 'xii', + 8572 => 'l', + 8573 => 'c', + 8574 => 'd', + 8575 => 'm', + 8585 => '0â„3', + 8748 => '∫∫', + 8749 => '∫∫∫', + 8751 => '∮∮', + 8752 => '∮∮∮', + 9001 => '〈', + 9002 => '〉', + 9312 => '1', + 9313 => '2', + 9314 => '3', + 9315 => '4', + 9316 => '5', + 9317 => '6', + 9318 => '7', + 9319 => '8', + 9320 => '9', + 9321 => '10', + 9322 => '11', + 9323 => '12', + 9324 => '13', + 9325 => '14', + 9326 => '15', + 9327 => '16', + 9328 => '17', + 9329 => '18', + 9330 => '19', + 9331 => '20', + 9398 => 'a', + 9399 => 'b', + 9400 => 'c', + 9401 => 'd', + 9402 => 'e', + 9403 => 'f', + 9404 => 'g', + 9405 => 'h', + 9406 => 'i', + 9407 => 'j', + 9408 => 'k', + 9409 => 'l', + 9410 => 'm', + 9411 => 'n', + 9412 => 'o', + 9413 => 'p', + 9414 => 'q', + 9415 => 'r', + 9416 => 's', + 9417 => 't', + 9418 => 'u', + 9419 => 'v', + 9420 => 'w', + 9421 => 'x', + 9422 => 'y', + 9423 => 'z', + 9424 => 'a', + 9425 => 'b', + 9426 => 'c', + 9427 => 'd', + 9428 => 'e', + 9429 => 'f', + 9430 => 'g', + 9431 => 'h', + 9432 => 'i', + 9433 => 'j', + 9434 => 'k', + 9435 => 'l', + 9436 => 'm', + 9437 => 'n', + 9438 => 'o', + 9439 => 'p', + 9440 => 'q', + 9441 => 'r', + 9442 => 's', + 9443 => 't', + 9444 => 'u', + 9445 => 'v', + 9446 => 'w', + 9447 => 'x', + 9448 => 'y', + 9449 => 'z', + 9450 => '0', + 10764 => '∫∫∫∫', + 10972 => 'â«Ì¸', + 11264 => 'â°°', + 11265 => 'â°±', + 11266 => 'â°²', + 11267 => 'â°³', + 11268 => 'â°´', + 11269 => 'â°µ', + 11270 => 'â°¶', + 11271 => 'â°·', + 11272 => 'â°¸', + 11273 => 'â°¹', + 11274 => 'â°º', + 11275 => 'â°»', + 11276 => 'â°¼', + 11277 => 'â°½', + 11278 => 'â°¾', + 11279 => 'â°¿', + 11280 => 'â±€', + 11281 => 'â±', + 11282 => 'ⱂ', + 11283 => 'ⱃ', + 11284 => 'ⱄ', + 11285 => 'â±…', + 11286 => 'ⱆ', + 11287 => 'ⱇ', + 11288 => 'ⱈ', + 11289 => 'ⱉ', + 11290 => 'ⱊ', + 11291 => 'ⱋ', + 11292 => 'ⱌ', + 11293 => 'â±', + 11294 => 'ⱎ', + 11295 => 'â±', + 11296 => 'â±', + 11297 => 'ⱑ', + 11298 => 'â±’', + 11299 => 'ⱓ', + 11300 => 'â±”', + 11301 => 'ⱕ', + 11302 => 'â±–', + 11303 => 'â±—', + 11304 => 'ⱘ', + 11305 => 'â±™', + 11306 => 'ⱚ', + 11307 => 'â±›', + 11308 => 'ⱜ', + 11309 => 'â±', + 11310 => 'ⱞ', + 11360 => 'ⱡ', + 11362 => 'É«', + 11363 => 'áµ½', + 11364 => 'ɽ', + 11367 => 'ⱨ', + 11369 => 'ⱪ', + 11371 => 'ⱬ', + 11373 => 'É‘', + 11374 => 'ɱ', + 11375 => 'É', + 11376 => 'É’', + 11378 => 'â±³', + 11381 => 'â±¶', + 11388 => 'j', + 11389 => 'v', + 11390 => 'È¿', + 11391 => 'É€', + 11392 => 'â²', + 11394 => 'ⲃ', + 11396 => 'â²…', + 11398 => 'ⲇ', + 11400 => 'ⲉ', + 11402 => 'ⲋ', + 11404 => 'â²', + 11406 => 'â²', + 11408 => 'ⲑ', + 11410 => 'ⲓ', + 11412 => 'ⲕ', + 11414 => 'â²—', + 11416 => 'â²™', + 11418 => 'â²›', + 11420 => 'â²', + 11422 => 'ⲟ', + 11424 => 'ⲡ', + 11426 => 'â²£', + 11428 => 'â²¥', + 11430 => 'â²§', + 11432 => 'ⲩ', + 11434 => 'ⲫ', + 11436 => 'â²­', + 11438 => 'ⲯ', + 11440 => 'â²±', + 11442 => 'â²³', + 11444 => 'â²µ', + 11446 => 'â²·', + 11448 => 'â²¹', + 11450 => 'â²»', + 11452 => 'â²½', + 11454 => 'ⲿ', + 11456 => 'â³', + 11458 => 'ⳃ', + 11460 => 'â³…', + 11462 => 'ⳇ', + 11464 => 'ⳉ', + 11466 => 'ⳋ', + 11468 => 'â³', + 11470 => 'â³', + 11472 => 'ⳑ', + 11474 => 'ⳓ', + 11476 => 'ⳕ', + 11478 => 'â³—', + 11480 => 'â³™', + 11482 => 'â³›', + 11484 => 'â³', + 11486 => 'ⳟ', + 11488 => 'ⳡ', + 11490 => 'â³£', + 11499 => 'ⳬ', + 11501 => 'â³®', + 11506 => 'â³³', + 11631 => 'ⵡ', + 11935 => 'æ¯', + 12019 => '龟', + 12032 => '一', + 12033 => '丨', + 12034 => '丶', + 12035 => '丿', + 12036 => 'ä¹™', + 12037 => '亅', + 12038 => '二', + 12039 => '亠', + 12040 => '人', + 12041 => 'å„¿', + 12042 => 'å…¥', + 12043 => 'å…«', + 12044 => '冂', + 12045 => '冖', + 12046 => '冫', + 12047 => '几', + 12048 => '凵', + 12049 => '刀', + 12050 => '力', + 12051 => '勹', + 12052 => '匕', + 12053 => '匚', + 12054 => '匸', + 12055 => 'å', + 12056 => 'åœ', + 12057 => 'å©', + 12058 => '厂', + 12059 => '厶', + 12060 => 'åˆ', + 12061 => 'å£', + 12062 => 'å›—', + 12063 => '土', + 12064 => '士', + 12065 => '夂', + 12066 => '夊', + 12067 => '夕', + 12068 => '大', + 12069 => '女', + 12070 => 'å­', + 12071 => '宀', + 12072 => '寸', + 12073 => 'å°', + 12074 => 'å°¢', + 12075 => 'å°¸', + 12076 => 'å±®', + 12077 => 'å±±', + 12078 => 'å·›', + 12079 => 'å·¥', + 12080 => 'å·±', + 12081 => 'å·¾', + 12082 => 'å¹²', + 12083 => '幺', + 12084 => '广', + 12085 => 'å»´', + 12086 => '廾', + 12087 => '弋', + 12088 => '弓', + 12089 => 'å½', + 12090 => '彡', + 12091 => 'å½³', + 12092 => '心', + 12093 => '戈', + 12094 => '戶', + 12095 => '手', + 12096 => '支', + 12097 => 'æ”´', + 12098 => 'æ–‡', + 12099 => 'æ–—', + 12100 => 'æ–¤', + 12101 => 'æ–¹', + 12102 => 'æ— ', + 12103 => 'æ—¥', + 12104 => 'æ›°', + 12105 => '月', + 12106 => '木', + 12107 => '欠', + 12108 => 'æ­¢', + 12109 => 'æ­¹', + 12110 => '殳', + 12111 => '毋', + 12112 => '比', + 12113 => '毛', + 12114 => 'æ°', + 12115 => 'æ°”', + 12116 => 'æ°´', + 12117 => 'ç«', + 12118 => '爪', + 12119 => '父', + 12120 => '爻', + 12121 => '爿', + 12122 => '片', + 12123 => '牙', + 12124 => '牛', + 12125 => '犬', + 12126 => '玄', + 12127 => '玉', + 12128 => '瓜', + 12129 => '瓦', + 12130 => '甘', + 12131 => '生', + 12132 => '用', + 12133 => 'ç”°', + 12134 => 'ç–‹', + 12135 => 'ç–’', + 12136 => 'ç™¶', + 12137 => '白', + 12138 => 'çš®', + 12139 => 'çš¿', + 12140 => 'ç›®', + 12141 => '矛', + 12142 => '矢', + 12143 => '石', + 12144 => '示', + 12145 => '禸', + 12146 => '禾', + 12147 => 'ç©´', + 12148 => 'ç«‹', + 12149 => '竹', + 12150 => 'ç±³', + 12151 => '糸', + 12152 => 'ç¼¶', + 12153 => '网', + 12154 => '羊', + 12155 => 'ç¾½', + 12156 => 'è€', + 12157 => '而', + 12158 => '耒', + 12159 => '耳', + 12160 => 'è¿', + 12161 => '肉', + 12162 => '臣', + 12163 => '自', + 12164 => '至', + 12165 => '臼', + 12166 => '舌', + 12167 => '舛', + 12168 => '舟', + 12169 => '艮', + 12170 => '色', + 12171 => '艸', + 12172 => 'è™', + 12173 => '虫', + 12174 => 'è¡€', + 12175 => '行', + 12176 => 'è¡£', + 12177 => '襾', + 12178 => '見', + 12179 => 'è§’', + 12180 => '言', + 12181 => 'è°·', + 12182 => '豆', + 12183 => '豕', + 12184 => '豸', + 12185 => 'è²', + 12186 => '赤', + 12187 => 'èµ°', + 12188 => 'è¶³', + 12189 => '身', + 12190 => '車', + 12191 => 'è¾›', + 12192 => 'è¾°', + 12193 => 'è¾µ', + 12194 => 'é‚‘', + 12195 => 'é…‰', + 12196 => '釆', + 12197 => '里', + 12198 => '金', + 12199 => 'é•·', + 12200 => 'é–€', + 12201 => '阜', + 12202 => 'éš¶', + 12203 => 'éš¹', + 12204 => '雨', + 12205 => 'é‘', + 12206 => 'éž', + 12207 => 'é¢', + 12208 => 'é©', + 12209 => '韋', + 12210 => '韭', + 12211 => '音', + 12212 => 'é ', + 12213 => '風', + 12214 => '飛', + 12215 => '食', + 12216 => '首', + 12217 => '香', + 12218 => '馬', + 12219 => '骨', + 12220 => '高', + 12221 => '髟', + 12222 => '鬥', + 12223 => '鬯', + 12224 => '鬲', + 12225 => '鬼', + 12226 => 'é­š', + 12227 => 'é³¥', + 12228 => 'é¹µ', + 12229 => '鹿', + 12230 => '麥', + 12231 => '麻', + 12232 => '黃', + 12233 => 'é»', + 12234 => '黑', + 12235 => '黹', + 12236 => '黽', + 12237 => '鼎', + 12238 => '鼓', + 12239 => 'é¼ ', + 12240 => 'é¼»', + 12241 => '齊', + 12242 => 'é½’', + 12243 => 'é¾', + 12244 => '龜', + 12245 => 'é¾ ', + 12290 => '.', + 12342 => '〒', + 12344 => 'å', + 12345 => 'å„', + 12346 => 'å…', + 12447 => 'より', + 12543 => 'コト', + 12593 => 'á„€', + 12594 => 'á„', + 12595 => 'ᆪ', + 12596 => 'á„‚', + 12597 => 'ᆬ', + 12598 => 'ᆭ', + 12599 => 'ᄃ', + 12600 => 'á„„', + 12601 => 'á„…', + 12602 => 'ᆰ', + 12603 => 'ᆱ', + 12604 => 'ᆲ', + 12605 => 'ᆳ', + 12606 => 'ᆴ', + 12607 => 'ᆵ', + 12608 => 'ᄚ', + 12609 => 'ᄆ', + 12610 => 'ᄇ', + 12611 => 'ᄈ', + 12612 => 'á„¡', + 12613 => 'ᄉ', + 12614 => 'ᄊ', + 12615 => 'á„‹', + 12616 => 'ᄌ', + 12617 => 'á„', + 12618 => 'ᄎ', + 12619 => 'á„', + 12620 => 'á„', + 12621 => 'á„‘', + 12622 => 'á„’', + 12623 => 'á…¡', + 12624 => 'á…¢', + 12625 => 'á…£', + 12626 => 'á…¤', + 12627 => 'á…¥', + 12628 => 'á…¦', + 12629 => 'á…§', + 12630 => 'á…¨', + 12631 => 'á…©', + 12632 => 'á…ª', + 12633 => 'á…«', + 12634 => 'á…¬', + 12635 => 'á…­', + 12636 => 'á…®', + 12637 => 'á…¯', + 12638 => 'á…°', + 12639 => 'á…±', + 12640 => 'á…²', + 12641 => 'á…³', + 12642 => 'á…´', + 12643 => 'á…µ', + 12645 => 'á„”', + 12646 => 'á„•', + 12647 => 'ᇇ', + 12648 => 'ᇈ', + 12649 => 'ᇌ', + 12650 => 'ᇎ', + 12651 => 'ᇓ', + 12652 => 'ᇗ', + 12653 => 'ᇙ', + 12654 => 'ᄜ', + 12655 => 'á‡', + 12656 => 'ᇟ', + 12657 => 'á„', + 12658 => 'ᄞ', + 12659 => 'á„ ', + 12660 => 'á„¢', + 12661 => 'á„£', + 12662 => 'á„§', + 12663 => 'á„©', + 12664 => 'á„«', + 12665 => 'ᄬ', + 12666 => 'á„­', + 12667 => 'á„®', + 12668 => 'ᄯ', + 12669 => 'ᄲ', + 12670 => 'á„¶', + 12671 => 'á…€', + 12672 => 'á…‡', + 12673 => 'á…Œ', + 12674 => 'ᇱ', + 12675 => 'ᇲ', + 12676 => 'á…—', + 12677 => 'á…˜', + 12678 => 'á…™', + 12679 => 'ᆄ', + 12680 => 'ᆅ', + 12681 => 'ᆈ', + 12682 => 'ᆑ', + 12683 => 'ᆒ', + 12684 => 'ᆔ', + 12685 => 'ᆞ', + 12686 => 'ᆡ', + 12690 => '一', + 12691 => '二', + 12692 => '三', + 12693 => 'å››', + 12694 => '上', + 12695 => '中', + 12696 => '下', + 12697 => '甲', + 12698 => 'ä¹™', + 12699 => '丙', + 12700 => 'ä¸', + 12701 => '天', + 12702 => '地', + 12703 => '人', + 12868 => 'å•', + 12869 => 'å¹¼', + 12870 => 'æ–‡', + 12871 => 'ç®', + 12880 => 'pte', + 12881 => '21', + 12882 => '22', + 12883 => '23', + 12884 => '24', + 12885 => '25', + 12886 => '26', + 12887 => '27', + 12888 => '28', + 12889 => '29', + 12890 => '30', + 12891 => '31', + 12892 => '32', + 12893 => '33', + 12894 => '34', + 12895 => '35', + 12896 => 'á„€', + 12897 => 'á„‚', + 12898 => 'ᄃ', + 12899 => 'á„…', + 12900 => 'ᄆ', + 12901 => 'ᄇ', + 12902 => 'ᄉ', + 12903 => 'á„‹', + 12904 => 'ᄌ', + 12905 => 'ᄎ', + 12906 => 'á„', + 12907 => 'á„', + 12908 => 'á„‘', + 12909 => 'á„’', + 12910 => 'ê°€', + 12911 => '나', + 12912 => '다', + 12913 => 'ë¼', + 12914 => '마', + 12915 => 'ë°”', + 12916 => '사', + 12917 => 'ì•„', + 12918 => 'ìž', + 12919 => 'ì°¨', + 12920 => 'ì¹´', + 12921 => '타', + 12922 => '파', + 12923 => '하', + 12924 => '참고', + 12925 => '주ì˜', + 12926 => 'ìš°', + 12928 => '一', + 12929 => '二', + 12930 => '三', + 12931 => 'å››', + 12932 => '五', + 12933 => 'å…­', + 12934 => '七', + 12935 => 'å…«', + 12936 => 'ä¹', + 12937 => 'å', + 12938 => '月', + 12939 => 'ç«', + 12940 => 'æ°´', + 12941 => '木', + 12942 => '金', + 12943 => '土', + 12944 => 'æ—¥', + 12945 => 'æ ª', + 12946 => '有', + 12947 => '社', + 12948 => 'å', + 12949 => '特', + 12950 => '財', + 12951 => 'ç¥', + 12952 => '労', + 12953 => '秘', + 12954 => 'ç”·', + 12955 => '女', + 12956 => 'é©', + 12957 => '優', + 12958 => 'å°', + 12959 => '注', + 12960 => 'é …', + 12961 => '休', + 12962 => '写', + 12963 => 'æ­£', + 12964 => '上', + 12965 => '中', + 12966 => '下', + 12967 => 'å·¦', + 12968 => 'å³', + 12969 => '医', + 12970 => 'å®—', + 12971 => 'å­¦', + 12972 => '監', + 12973 => 'ä¼', + 12974 => '資', + 12975 => 'å”', + 12976 => '夜', + 12977 => '36', + 12978 => '37', + 12979 => '38', + 12980 => '39', + 12981 => '40', + 12982 => '41', + 12983 => '42', + 12984 => '43', + 12985 => '44', + 12986 => '45', + 12987 => '46', + 12988 => '47', + 12989 => '48', + 12990 => '49', + 12991 => '50', + 12992 => '1月', + 12993 => '2月', + 12994 => '3月', + 12995 => '4月', + 12996 => '5月', + 12997 => '6月', + 12998 => '7月', + 12999 => '8月', + 13000 => '9月', + 13001 => '10月', + 13002 => '11月', + 13003 => '12月', + 13004 => 'hg', + 13005 => 'erg', + 13006 => 'ev', + 13007 => 'ltd', + 13008 => 'ã‚¢', + 13009 => 'イ', + 13010 => 'ウ', + 13011 => 'エ', + 13012 => 'オ', + 13013 => 'ã‚«', + 13014 => 'ã‚­', + 13015 => 'ク', + 13016 => 'ケ', + 13017 => 'コ', + 13018 => 'サ', + 13019 => 'ã‚·', + 13020 => 'ス', + 13021 => 'ã‚»', + 13022 => 'ソ', + 13023 => 'ã‚¿', + 13024 => 'ãƒ', + 13025 => 'ツ', + 13026 => 'テ', + 13027 => 'ト', + 13028 => 'ナ', + 13029 => 'ニ', + 13030 => 'ヌ', + 13031 => 'ãƒ', + 13032 => 'ノ', + 13033 => 'ãƒ', + 13034 => 'ヒ', + 13035 => 'フ', + 13036 => 'ヘ', + 13037 => 'ホ', + 13038 => 'マ', + 13039 => 'ミ', + 13040 => 'ム', + 13041 => 'メ', + 13042 => 'モ', + 13043 => 'ヤ', + 13044 => 'ユ', + 13045 => 'ヨ', + 13046 => 'ラ', + 13047 => 'リ', + 13048 => 'ル', + 13049 => 'レ', + 13050 => 'ロ', + 13051 => 'ワ', + 13052 => 'ヰ', + 13053 => 'ヱ', + 13054 => 'ヲ', + 13055 => '令和', + 13056 => 'アパート', + 13057 => 'アルファ', + 13058 => 'アンペア', + 13059 => 'アール', + 13060 => 'イニング', + 13061 => 'インãƒ', + 13062 => 'ウォン', + 13063 => 'エスクード', + 13064 => 'エーカー', + 13065 => 'オンス', + 13066 => 'オーム', + 13067 => 'カイリ', + 13068 => 'カラット', + 13069 => 'カロリー', + 13070 => 'ガロン', + 13071 => 'ガンマ', + 13072 => 'ギガ', + 13073 => 'ギニー', + 13074 => 'キュリー', + 13075 => 'ギルダー', + 13076 => 'キロ', + 13077 => 'キログラム', + 13078 => 'キロメートル', + 13079 => 'キロワット', + 13080 => 'グラム', + 13081 => 'グラムトン', + 13082 => 'クルゼイロ', + 13083 => 'クローãƒ', + 13084 => 'ケース', + 13085 => 'コルナ', + 13086 => 'コーãƒ', + 13087 => 'サイクル', + 13088 => 'サンãƒãƒ¼ãƒ ', + 13089 => 'シリング', + 13090 => 'センãƒ', + 13091 => 'セント', + 13092 => 'ダース', + 13093 => 'デシ', + 13094 => 'ドル', + 13095 => 'トン', + 13096 => 'ナノ', + 13097 => 'ノット', + 13098 => 'ãƒã‚¤ãƒ„', + 13099 => 'パーセント', + 13100 => 'パーツ', + 13101 => 'ãƒãƒ¼ãƒ¬ãƒ«', + 13102 => 'ピアストル', + 13103 => 'ピクル', + 13104 => 'ピコ', + 13105 => 'ビル', + 13106 => 'ファラッド', + 13107 => 'フィート', + 13108 => 'ブッシェル', + 13109 => 'フラン', + 13110 => 'ヘクタール', + 13111 => 'ペソ', + 13112 => 'ペニヒ', + 13113 => 'ヘルツ', + 13114 => 'ペンス', + 13115 => 'ページ', + 13116 => 'ベータ', + 13117 => 'ãƒã‚¤ãƒ³ãƒˆ', + 13118 => 'ボルト', + 13119 => 'ホン', + 13120 => 'ãƒãƒ³ãƒ‰', + 13121 => 'ホール', + 13122 => 'ホーン', + 13123 => 'マイクロ', + 13124 => 'マイル', + 13125 => 'マッãƒ', + 13126 => 'マルク', + 13127 => 'マンション', + 13128 => 'ミクロン', + 13129 => 'ミリ', + 13130 => 'ミリãƒãƒ¼ãƒ«', + 13131 => 'メガ', + 13132 => 'メガトン', + 13133 => 'メートル', + 13134 => 'ヤード', + 13135 => 'ヤール', + 13136 => 'ユアン', + 13137 => 'リットル', + 13138 => 'リラ', + 13139 => 'ルピー', + 13140 => 'ルーブル', + 13141 => 'レム', + 13142 => 'レントゲン', + 13143 => 'ワット', + 13144 => '0点', + 13145 => '1点', + 13146 => '2点', + 13147 => '3点', + 13148 => '4点', + 13149 => '5点', + 13150 => '6点', + 13151 => '7点', + 13152 => '8点', + 13153 => '9点', + 13154 => '10点', + 13155 => '11点', + 13156 => '12点', + 13157 => '13点', + 13158 => '14点', + 13159 => '15点', + 13160 => '16点', + 13161 => '17点', + 13162 => '18点', + 13163 => '19点', + 13164 => '20点', + 13165 => '21点', + 13166 => '22点', + 13167 => '23点', + 13168 => '24点', + 13169 => 'hpa', + 13170 => 'da', + 13171 => 'au', + 13172 => 'bar', + 13173 => 'ov', + 13174 => 'pc', + 13175 => 'dm', + 13176 => 'dm2', + 13177 => 'dm3', + 13178 => 'iu', + 13179 => 'å¹³æˆ', + 13180 => '昭和', + 13181 => '大正', + 13182 => '明治', + 13183 => 'æ ªå¼ä¼šç¤¾', + 13184 => 'pa', + 13185 => 'na', + 13186 => 'μa', + 13187 => 'ma', + 13188 => 'ka', + 13189 => 'kb', + 13190 => 'mb', + 13191 => 'gb', + 13192 => 'cal', + 13193 => 'kcal', + 13194 => 'pf', + 13195 => 'nf', + 13196 => 'μf', + 13197 => 'μg', + 13198 => 'mg', + 13199 => 'kg', + 13200 => 'hz', + 13201 => 'khz', + 13202 => 'mhz', + 13203 => 'ghz', + 13204 => 'thz', + 13205 => 'μl', + 13206 => 'ml', + 13207 => 'dl', + 13208 => 'kl', + 13209 => 'fm', + 13210 => 'nm', + 13211 => 'μm', + 13212 => 'mm', + 13213 => 'cm', + 13214 => 'km', + 13215 => 'mm2', + 13216 => 'cm2', + 13217 => 'm2', + 13218 => 'km2', + 13219 => 'mm3', + 13220 => 'cm3', + 13221 => 'm3', + 13222 => 'km3', + 13223 => 'm∕s', + 13224 => 'm∕s2', + 13225 => 'pa', + 13226 => 'kpa', + 13227 => 'mpa', + 13228 => 'gpa', + 13229 => 'rad', + 13230 => 'rad∕s', + 13231 => 'rad∕s2', + 13232 => 'ps', + 13233 => 'ns', + 13234 => 'μs', + 13235 => 'ms', + 13236 => 'pv', + 13237 => 'nv', + 13238 => 'μv', + 13239 => 'mv', + 13240 => 'kv', + 13241 => 'mv', + 13242 => 'pw', + 13243 => 'nw', + 13244 => 'μw', + 13245 => 'mw', + 13246 => 'kw', + 13247 => 'mw', + 13248 => 'kω', + 13249 => 'mω', + 13251 => 'bq', + 13252 => 'cc', + 13253 => 'cd', + 13254 => 'c∕kg', + 13256 => 'db', + 13257 => 'gy', + 13258 => 'ha', + 13259 => 'hp', + 13260 => 'in', + 13261 => 'kk', + 13262 => 'km', + 13263 => 'kt', + 13264 => 'lm', + 13265 => 'ln', + 13266 => 'log', + 13267 => 'lx', + 13268 => 'mb', + 13269 => 'mil', + 13270 => 'mol', + 13271 => 'ph', + 13273 => 'ppm', + 13274 => 'pr', + 13275 => 'sr', + 13276 => 'sv', + 13277 => 'wb', + 13278 => 'v∕m', + 13279 => 'a∕m', + 13280 => '1æ—¥', + 13281 => '2æ—¥', + 13282 => '3æ—¥', + 13283 => '4æ—¥', + 13284 => '5æ—¥', + 13285 => '6æ—¥', + 13286 => '7æ—¥', + 13287 => '8æ—¥', + 13288 => '9æ—¥', + 13289 => '10æ—¥', + 13290 => '11æ—¥', + 13291 => '12æ—¥', + 13292 => '13æ—¥', + 13293 => '14æ—¥', + 13294 => '15æ—¥', + 13295 => '16æ—¥', + 13296 => '17æ—¥', + 13297 => '18æ—¥', + 13298 => '19æ—¥', + 13299 => '20æ—¥', + 13300 => '21æ—¥', + 13301 => '22æ—¥', + 13302 => '23æ—¥', + 13303 => '24æ—¥', + 13304 => '25æ—¥', + 13305 => '26æ—¥', + 13306 => '27æ—¥', + 13307 => '28æ—¥', + 13308 => '29æ—¥', + 13309 => '30æ—¥', + 13310 => '31æ—¥', + 13311 => 'gal', + 42560 => 'ê™', + 42562 => 'ꙃ', + 42564 => 'ê™…', + 42566 => 'ꙇ', + 42568 => 'ꙉ', + 42570 => 'ꙋ', + 42572 => 'ê™', + 42574 => 'ê™', + 42576 => 'ꙑ', + 42578 => 'ꙓ', + 42580 => 'ꙕ', + 42582 => 'ê™—', + 42584 => 'ê™™', + 42586 => 'ê™›', + 42588 => 'ê™', + 42590 => 'ꙟ', + 42592 => 'ꙡ', + 42594 => 'ꙣ', + 42596 => 'ꙥ', + 42598 => 'ê™§', + 42600 => 'ꙩ', + 42602 => 'ꙫ', + 42604 => 'ê™­', + 42624 => 'êš', + 42626 => 'ꚃ', + 42628 => 'êš…', + 42630 => 'ꚇ', + 42632 => 'ꚉ', + 42634 => 'êš‹', + 42636 => 'êš', + 42638 => 'êš', + 42640 => 'êš‘', + 42642 => 'êš“', + 42644 => 'êš•', + 42646 => 'êš—', + 42648 => 'êš™', + 42650 => 'êš›', + 42652 => 'ÑŠ', + 42653 => 'ÑŒ', + 42786 => 'ꜣ', + 42788 => 'ꜥ', + 42790 => 'ꜧ', + 42792 => 'ꜩ', + 42794 => 'ꜫ', + 42796 => 'ꜭ', + 42798 => 'ꜯ', + 42802 => 'ꜳ', + 42804 => 'ꜵ', + 42806 => 'ꜷ', + 42808 => 'ꜹ', + 42810 => 'ꜻ', + 42812 => 'ꜽ', + 42814 => 'ꜿ', + 42816 => 'ê', + 42818 => 'êƒ', + 42820 => 'ê…', + 42822 => 'ê‡', + 42824 => 'ê‰', + 42826 => 'ê‹', + 42828 => 'ê', + 42830 => 'ê', + 42832 => 'ê‘', + 42834 => 'ê“', + 42836 => 'ê•', + 42838 => 'ê—', + 42840 => 'ê™', + 42842 => 'ê›', + 42844 => 'ê', + 42846 => 'êŸ', + 42848 => 'ê¡', + 42850 => 'ê£', + 42852 => 'ê¥', + 42854 => 'ê§', + 42856 => 'ê©', + 42858 => 'ê«', + 42860 => 'ê­', + 42862 => 'ê¯', + 42864 => 'ê¯', + 42873 => 'êº', + 42875 => 'ê¼', + 42877 => 'áµ¹', + 42878 => 'ê¿', + 42880 => 'êž', + 42882 => 'ꞃ', + 42884 => 'êž…', + 42886 => 'ꞇ', + 42891 => 'ꞌ', + 42893 => 'É¥', + 42896 => 'êž‘', + 42898 => 'êž“', + 42902 => 'êž—', + 42904 => 'êž™', + 42906 => 'êž›', + 42908 => 'êž', + 42910 => 'ꞟ', + 42912 => 'êž¡', + 42914 => 'ꞣ', + 42916 => 'ꞥ', + 42918 => 'êž§', + 42920 => 'êž©', + 42922 => 'ɦ', + 42923 => 'Éœ', + 42924 => 'É¡', + 42925 => 'ɬ', + 42926 => 'ɪ', + 42928 => 'Êž', + 42929 => 'ʇ', + 42930 => 'Ê', + 42931 => 'ê­“', + 42932 => 'êžµ', + 42934 => 'êž·', + 42936 => 'êž¹', + 42938 => 'êž»', + 42940 => 'êž½', + 42942 => 'êž¿', + 42946 => 'ꟃ', + 42948 => 'êž”', + 42949 => 'Ê‚', + 42950 => 'á¶Ž', + 42951 => 'ꟈ', + 42953 => 'ꟊ', + 42997 => 'ꟶ', + 43000 => 'ħ', + 43001 => 'Å“', + 43868 => 'ꜧ', + 43869 => 'ꬷ', + 43870 => 'É«', + 43871 => 'ê­’', + 43881 => 'Ê', + 43888 => 'Ꭰ', + 43889 => 'Ꭱ', + 43890 => 'Ꭲ', + 43891 => 'Ꭳ', + 43892 => 'Ꭴ', + 43893 => 'Ꭵ', + 43894 => 'Ꭶ', + 43895 => 'Ꭷ', + 43896 => 'Ꭸ', + 43897 => 'Ꭹ', + 43898 => 'Ꭺ', + 43899 => 'Ꭻ', + 43900 => 'Ꭼ', + 43901 => 'Ꭽ', + 43902 => 'Ꭾ', + 43903 => 'Ꭿ', + 43904 => 'Ꮀ', + 43905 => 'Ꮁ', + 43906 => 'Ꮂ', + 43907 => 'Ꮃ', + 43908 => 'Ꮄ', + 43909 => 'Ꮅ', + 43910 => 'Ꮆ', + 43911 => 'Ꮇ', + 43912 => 'Ꮈ', + 43913 => 'Ꮉ', + 43914 => 'Ꮊ', + 43915 => 'Ꮋ', + 43916 => 'Ꮌ', + 43917 => 'Ꮍ', + 43918 => 'Ꮎ', + 43919 => 'Ꮏ', + 43920 => 'á€', + 43921 => 'á', + 43922 => 'á‚', + 43923 => 'áƒ', + 43924 => 'á„', + 43925 => 'á…', + 43926 => 'á†', + 43927 => 'á‡', + 43928 => 'áˆ', + 43929 => 'á‰', + 43930 => 'áŠ', + 43931 => 'á‹', + 43932 => 'áŒ', + 43933 => 'á', + 43934 => 'áŽ', + 43935 => 'á', + 43936 => 'á', + 43937 => 'á‘', + 43938 => 'á’', + 43939 => 'á“', + 43940 => 'á”', + 43941 => 'á•', + 43942 => 'á–', + 43943 => 'á—', + 43944 => 'á˜', + 43945 => 'á™', + 43946 => 'áš', + 43947 => 'á›', + 43948 => 'áœ', + 43949 => 'á', + 43950 => 'áž', + 43951 => 'áŸ', + 43952 => 'á ', + 43953 => 'á¡', + 43954 => 'á¢', + 43955 => 'á£', + 43956 => 'á¤', + 43957 => 'á¥', + 43958 => 'á¦', + 43959 => 'á§', + 43960 => 'á¨', + 43961 => 'á©', + 43962 => 'áª', + 43963 => 'á«', + 43964 => 'á¬', + 43965 => 'á­', + 43966 => 'á®', + 43967 => 'á¯', + 63744 => '豈', + 63745 => 'æ›´', + 63746 => '車', + 63747 => '賈', + 63748 => '滑', + 63749 => '串', + 63750 => 'å¥', + 63751 => '龜', + 63752 => '龜', + 63753 => '契', + 63754 => '金', + 63755 => 'å–‡', + 63756 => '奈', + 63757 => '懶', + 63758 => '癩', + 63759 => 'ç¾…', + 63760 => '蘿', + 63761 => '螺', + 63762 => '裸', + 63763 => 'é‚', + 63764 => '樂', + 63765 => 'æ´›', + 63766 => '烙', + 63767 => 'çž', + 63768 => 'è½', + 63769 => 'é…ª', + 63770 => 'é§±', + 63771 => '亂', + 63772 => 'åµ', + 63773 => '欄', + 63774 => '爛', + 63775 => '蘭', + 63776 => '鸞', + 63777 => 'åµ', + 63778 => 'æ¿«', + 63779 => 'è—', + 63780 => '襤', + 63781 => '拉', + 63782 => '臘', + 63783 => 'è Ÿ', + 63784 => '廊', + 63785 => '朗', + 63786 => '浪', + 63787 => '狼', + 63788 => '郎', + 63789 => '來', + 63790 => '冷', + 63791 => '勞', + 63792 => 'æ“„', + 63793 => 'æ«“', + 63794 => 'çˆ', + 63795 => 'ç›§', + 63796 => 'è€', + 63797 => '蘆', + 63798 => '虜', + 63799 => 'è·¯', + 63800 => '露', + 63801 => 'é­¯', + 63802 => 'é·º', + 63803 => '碌', + 63804 => '祿', + 63805 => 'ç¶ ', + 63806 => 'è‰', + 63807 => '錄', + 63808 => '鹿', + 63809 => 'è«–', + 63810 => '壟', + 63811 => '弄', + 63812 => 'ç± ', + 63813 => 'è¾', + 63814 => '牢', + 63815 => '磊', + 63816 => '賂', + 63817 => 'é›·', + 63818 => '壘', + 63819 => 'å±¢', + 63820 => '樓', + 63821 => 'æ·š', + 63822 => 'æ¼', + 63823 => 'ç´¯', + 63824 => '縷', + 63825 => '陋', + 63826 => 'å‹’', + 63827 => 'è‚‹', + 63828 => '凜', + 63829 => '凌', + 63830 => '稜', + 63831 => 'ç¶¾', + 63832 => 'è±', + 63833 => '陵', + 63834 => '讀', + 63835 => 'æ‹', + 63836 => '樂', + 63837 => '諾', + 63838 => '丹', + 63839 => '寧', + 63840 => '怒', + 63841 => '率', + 63842 => 'ç•°', + 63843 => '北', + 63844 => '磻', + 63845 => '便', + 63846 => '復', + 63847 => 'ä¸', + 63848 => '泌', + 63849 => '數', + 63850 => 'ç´¢', + 63851 => 'åƒ', + 63852 => '塞', + 63853 => 'çœ', + 63854 => '葉', + 63855 => '說', + 63856 => '殺', + 63857 => 'è¾°', + 63858 => '沈', + 63859 => '拾', + 63860 => 'è‹¥', + 63861 => '掠', + 63862 => 'ç•¥', + 63863 => '亮', + 63864 => 'å…©', + 63865 => '凉', + 63866 => 'æ¢', + 63867 => 'ç³§', + 63868 => '良', + 63869 => 'è«’', + 63870 => 'é‡', + 63871 => '勵', + 63872 => 'å‘‚', + 63873 => '女', + 63874 => '廬', + 63875 => 'æ—…', + 63876 => '濾', + 63877 => '礪', + 63878 => 'é–­', + 63879 => '驪', + 63880 => '麗', + 63881 => '黎', + 63882 => '力', + 63883 => '曆', + 63884 => 'æ­·', + 63885 => 'è½¢', + 63886 => 'å¹´', + 63887 => 'æ†', + 63888 => '戀', + 63889 => 'æ’š', + 63890 => 'æ¼£', + 63891 => 'ç…‰', + 63892 => 'ç’‰', + 63893 => 'ç§Š', + 63894 => 'ç·´', + 63895 => 'è¯', + 63896 => '輦', + 63897 => 'è“®', + 63898 => '連', + 63899 => 'éŠ', + 63900 => '列', + 63901 => '劣', + 63902 => 'å’½', + 63903 => '烈', + 63904 => '裂', + 63905 => '說', + 63906 => '廉', + 63907 => '念', + 63908 => 'æ»', + 63909 => 'æ®®', + 63910 => 'ç°¾', + 63911 => 'çµ', + 63912 => '令', + 63913 => '囹', + 63914 => '寧', + 63915 => '嶺', + 63916 => '怜', + 63917 => '玲', + 63918 => 'ç‘©', + 63919 => '羚', + 63920 => 'è†', + 63921 => '鈴', + 63922 => 'é›¶', + 63923 => 'éˆ', + 63924 => 'é ˜', + 63925 => '例', + 63926 => '禮', + 63927 => '醴', + 63928 => '隸', + 63929 => '惡', + 63930 => '了', + 63931 => '僚', + 63932 => '寮', + 63933 => 'å°¿', + 63934 => 'æ–™', + 63935 => '樂', + 63936 => '燎', + 63937 => '療', + 63938 => '蓼', + 63939 => 'é¼', + 63940 => 'é¾', + 63941 => '暈', + 63942 => '阮', + 63943 => '劉', + 63944 => 'æ»', + 63945 => '柳', + 63946 => 'æµ', + 63947 => '溜', + 63948 => 'ç‰', + 63949 => 'ç•™', + 63950 => 'ç¡«', + 63951 => 'ç´', + 63952 => '類', + 63953 => 'å…­', + 63954 => '戮', + 63955 => '陸', + 63956 => '倫', + 63957 => 'å´™', + 63958 => 'æ·ª', + 63959 => '輪', + 63960 => '律', + 63961 => 'æ…„', + 63962 => 'æ —', + 63963 => '率', + 63964 => '隆', + 63965 => '利', + 63966 => 'å', + 63967 => 'å±¥', + 63968 => '易', + 63969 => 'æŽ', + 63970 => '梨', + 63971 => 'æ³¥', + 63972 => 'ç†', + 63973 => 'ç—¢', + 63974 => 'ç½¹', + 63975 => 'è£', + 63976 => '裡', + 63977 => '里', + 63978 => '離', + 63979 => '匿', + 63980 => '溺', + 63981 => 'å', + 63982 => 'ç‡', + 63983 => 'ç’˜', + 63984 => 'è—º', + 63985 => '隣', + 63986 => 'é±—', + 63987 => '麟', + 63988 => 'æž—', + 63989 => 'æ·‹', + 63990 => '臨', + 63991 => 'ç«‹', + 63992 => '笠', + 63993 => 'ç²’', + 63994 => 'ç‹€', + 63995 => 'ç‚™', + 63996 => 'è­˜', + 63997 => '什', + 63998 => '茶', + 63999 => '刺', + 64000 => '切', + 64001 => '度', + 64002 => 'æ‹“', + 64003 => 'ç³–', + 64004 => 'å®…', + 64005 => 'æ´ž', + 64006 => 'æš´', + 64007 => 'è¼»', + 64008 => '行', + 64009 => 'é™', + 64010 => '見', + 64011 => '廓', + 64012 => 'å…€', + 64013 => 'å—€', + 64016 => '塚', + 64018 => 'æ™´', + 64021 => '凞', + 64022 => '猪', + 64023 => '益', + 64024 => '礼', + 64025 => '神', + 64026 => '祥', + 64027 => 'ç¦', + 64028 => 'é–', + 64029 => 'ç²¾', + 64030 => 'ç¾½', + 64032 => '蘒', + 64034 => '諸', + 64037 => '逸', + 64038 => '都', + 64042 => '飯', + 64043 => '飼', + 64044 => '館', + 64045 => 'é¶´', + 64046 => '郞', + 64047 => 'éš·', + 64048 => 'ä¾®', + 64049 => '僧', + 64050 => 'å…', + 64051 => '勉', + 64052 => '勤', + 64053 => 'å‘', + 64054 => 'å–', + 64055 => '嘆', + 64056 => '器', + 64057 => 'å¡€', + 64058 => '墨', + 64059 => '層', + 64060 => 'å±®', + 64061 => 'æ‚”', + 64062 => 'æ…¨', + 64063 => '憎', + 64064 => '懲', + 64065 => 'æ•', + 64066 => 'æ—¢', + 64067 => 'æš‘', + 64068 => '梅', + 64069 => 'æµ·', + 64070 => '渚', + 64071 => 'æ¼¢', + 64072 => 'ç…®', + 64073 => '爫', + 64074 => 'ç¢', + 64075 => '碑', + 64076 => '社', + 64077 => '祉', + 64078 => '祈', + 64079 => 'ç¥', + 64080 => '祖', + 64081 => 'ç¥', + 64082 => 'ç¦', + 64083 => '禎', + 64084 => 'ç©€', + 64085 => 'çª', + 64086 => '節', + 64087 => 'ç·´', + 64088 => '縉', + 64089 => 'ç¹', + 64090 => 'ç½²', + 64091 => '者', + 64092 => '臭', + 64093 => '艹', + 64094 => '艹', + 64095 => 'è‘—', + 64096 => 'è¤', + 64097 => '視', + 64098 => 'è¬', + 64099 => '謹', + 64100 => '賓', + 64101 => 'è´ˆ', + 64102 => 'è¾¶', + 64103 => '逸', + 64104 => '難', + 64105 => '響', + 64106 => 'é »', + 64107 => 'æµ', + 64108 => '𤋮', + 64109 => '舘', + 64112 => '並', + 64113 => '况', + 64114 => 'å…¨', + 64115 => 'ä¾€', + 64116 => 'å……', + 64117 => '冀', + 64118 => '勇', + 64119 => '勺', + 64120 => 'å–', + 64121 => 'å••', + 64122 => 'å–™', + 64123 => 'å—¢', + 64124 => '塚', + 64125 => '墳', + 64126 => '奄', + 64127 => '奔', + 64128 => 'å©¢', + 64129 => '嬨', + 64130 => 'å»’', + 64131 => 'å»™', + 64132 => '彩', + 64133 => 'å¾­', + 64134 => '惘', + 64135 => 'æ…Ž', + 64136 => '愈', + 64137 => '憎', + 64138 => 'æ… ', + 64139 => '懲', + 64140 => '戴', + 64141 => 'æ„', + 64142 => 'æœ', + 64143 => 'æ‘’', + 64144 => 'æ•–', + 64145 => 'æ™´', + 64146 => '朗', + 64147 => '望', + 64148 => 'æ–', + 64149 => 'æ­¹', + 64150 => '殺', + 64151 => 'æµ', + 64152 => 'æ»›', + 64153 => '滋', + 64154 => 'æ¼¢', + 64155 => '瀞', + 64156 => 'ç…®', + 64157 => 'çž§', + 64158 => '爵', + 64159 => '犯', + 64160 => '猪', + 64161 => '瑱', + 64162 => '甆', + 64163 => 'ç”»', + 64164 => 'ç˜', + 64165 => '瘟', + 64166 => '益', + 64167 => 'ç››', + 64168 => 'ç›´', + 64169 => 'çŠ', + 64170 => 'ç€', + 64171 => '磌', + 64172 => '窱', + 64173 => '節', + 64174 => 'ç±»', + 64175 => 'çµ›', + 64176 => 'ç·´', + 64177 => 'ç¼¾', + 64178 => '者', + 64179 => 'è’', + 64180 => 'è¯', + 64181 => 'è¹', + 64182 => 'è¥', + 64183 => '覆', + 64184 => '視', + 64185 => '調', + 64186 => '諸', + 64187 => 'è«‹', + 64188 => 'è¬', + 64189 => '諾', + 64190 => 'è«­', + 64191 => '謹', + 64192 => '變', + 64193 => 'è´ˆ', + 64194 => '輸', + 64195 => 'é²', + 64196 => '醙', + 64197 => '鉶', + 64198 => '陼', + 64199 => '難', + 64200 => 'é–', + 64201 => '韛', + 64202 => '響', + 64203 => 'é ‹', + 64204 => 'é »', + 64205 => '鬒', + 64206 => '龜', + 64207 => '𢡊', + 64208 => '𢡄', + 64209 => 'ð£•', + 64210 => 'ã®', + 64211 => '䀘', + 64212 => '䀹', + 64213 => '𥉉', + 64214 => 'ð¥³', + 64215 => '𧻓', + 64216 => '齃', + 64217 => '龎', + 64256 => 'ff', + 64257 => 'fi', + 64258 => 'fl', + 64259 => 'ffi', + 64260 => 'ffl', + 64261 => 'st', + 64262 => 'st', + 64275 => 'Õ´Õ¶', + 64276 => 'Õ´Õ¥', + 64277 => 'Õ´Õ«', + 64278 => 'Õ¾Õ¶', + 64279 => 'Õ´Õ­', + 64285 => '×™Ö´', + 64287 => 'ײַ', + 64288 => '×¢', + 64289 => '×', + 64290 => 'ד', + 64291 => '×”', + 64292 => '×›', + 64293 => 'ל', + 64294 => '×', + 64295 => 'ר', + 64296 => 'ת', + 64298 => 'ש×', + 64299 => 'שׂ', + 64300 => 'שּ×', + 64301 => 'שּׂ', + 64302 => '×Ö·', + 64303 => '×Ö¸', + 64304 => '×Ö¼', + 64305 => 'בּ', + 64306 => '×’Ö¼', + 64307 => 'דּ', + 64308 => '×”Ö¼', + 64309 => 'וּ', + 64310 => '×–Ö¼', + 64312 => 'טּ', + 64313 => '×™Ö¼', + 64314 => 'ךּ', + 64315 => '×›Ö¼', + 64316 => 'לּ', + 64318 => 'מּ', + 64320 => '× Ö¼', + 64321 => 'סּ', + 64323 => '×£Ö¼', + 64324 => 'פּ', + 64326 => 'צּ', + 64327 => '×§Ö¼', + 64328 => 'רּ', + 64329 => 'שּ', + 64330 => 'תּ', + 64331 => 'וֹ', + 64332 => 'בֿ', + 64333 => '×›Ö¿', + 64334 => 'פֿ', + 64335 => '×ל', + 64336 => 'Ù±', + 64337 => 'Ù±', + 64338 => 'Ù»', + 64339 => 'Ù»', + 64340 => 'Ù»', + 64341 => 'Ù»', + 64342 => 'Ù¾', + 64343 => 'Ù¾', + 64344 => 'Ù¾', + 64345 => 'Ù¾', + 64346 => 'Ú€', + 64347 => 'Ú€', + 64348 => 'Ú€', + 64349 => 'Ú€', + 64350 => 'Ùº', + 64351 => 'Ùº', + 64352 => 'Ùº', + 64353 => 'Ùº', + 64354 => 'Ù¿', + 64355 => 'Ù¿', + 64356 => 'Ù¿', + 64357 => 'Ù¿', + 64358 => 'Ù¹', + 64359 => 'Ù¹', + 64360 => 'Ù¹', + 64361 => 'Ù¹', + 64362 => 'Ú¤', + 64363 => 'Ú¤', + 64364 => 'Ú¤', + 64365 => 'Ú¤', + 64366 => 'Ú¦', + 64367 => 'Ú¦', + 64368 => 'Ú¦', + 64369 => 'Ú¦', + 64370 => 'Ú„', + 64371 => 'Ú„', + 64372 => 'Ú„', + 64373 => 'Ú„', + 64374 => 'Úƒ', + 64375 => 'Úƒ', + 64376 => 'Úƒ', + 64377 => 'Úƒ', + 64378 => 'Ú†', + 64379 => 'Ú†', + 64380 => 'Ú†', + 64381 => 'Ú†', + 64382 => 'Ú‡', + 64383 => 'Ú‡', + 64384 => 'Ú‡', + 64385 => 'Ú‡', + 64386 => 'Ú', + 64387 => 'Ú', + 64388 => 'ÚŒ', + 64389 => 'ÚŒ', + 64390 => 'ÚŽ', + 64391 => 'ÚŽ', + 64392 => 'Úˆ', + 64393 => 'Úˆ', + 64394 => 'Ú˜', + 64395 => 'Ú˜', + 64396 => 'Ú‘', + 64397 => 'Ú‘', + 64398 => 'Ú©', + 64399 => 'Ú©', + 64400 => 'Ú©', + 64401 => 'Ú©', + 64402 => 'Ú¯', + 64403 => 'Ú¯', + 64404 => 'Ú¯', + 64405 => 'Ú¯', + 64406 => 'Ú³', + 64407 => 'Ú³', + 64408 => 'Ú³', + 64409 => 'Ú³', + 64410 => 'Ú±', + 64411 => 'Ú±', + 64412 => 'Ú±', + 64413 => 'Ú±', + 64414 => 'Úº', + 64415 => 'Úº', + 64416 => 'Ú»', + 64417 => 'Ú»', + 64418 => 'Ú»', + 64419 => 'Ú»', + 64420 => 'Û€', + 64421 => 'Û€', + 64422 => 'Û', + 64423 => 'Û', + 64424 => 'Û', + 64425 => 'Û', + 64426 => 'Ú¾', + 64427 => 'Ú¾', + 64428 => 'Ú¾', + 64429 => 'Ú¾', + 64430 => 'Û’', + 64431 => 'Û’', + 64432 => 'Û“', + 64433 => 'Û“', + 64467 => 'Ú­', + 64468 => 'Ú­', + 64469 => 'Ú­', + 64470 => 'Ú­', + 64471 => 'Û‡', + 64472 => 'Û‡', + 64473 => 'Û†', + 64474 => 'Û†', + 64475 => 'Ûˆ', + 64476 => 'Ûˆ', + 64477 => 'Û‡Ù´', + 64478 => 'Û‹', + 64479 => 'Û‹', + 64480 => 'Û…', + 64481 => 'Û…', + 64482 => 'Û‰', + 64483 => 'Û‰', + 64484 => 'Û', + 64485 => 'Û', + 64486 => 'Û', + 64487 => 'Û', + 64488 => 'Ù‰', + 64489 => 'Ù‰', + 64490 => 'ئا', + 64491 => 'ئا', + 64492 => 'ئە', + 64493 => 'ئە', + 64494 => 'ئو', + 64495 => 'ئو', + 64496 => 'ئۇ', + 64497 => 'ئۇ', + 64498 => 'ئۆ', + 64499 => 'ئۆ', + 64500 => 'ئۈ', + 64501 => 'ئۈ', + 64502 => 'ئÛ', + 64503 => 'ئÛ', + 64504 => 'ئÛ', + 64505 => 'ئى', + 64506 => 'ئى', + 64507 => 'ئى', + 64508 => 'ÛŒ', + 64509 => 'ÛŒ', + 64510 => 'ÛŒ', + 64511 => 'ÛŒ', + 64512 => 'ئج', + 64513 => 'ئح', + 64514 => 'ئم', + 64515 => 'ئى', + 64516 => 'ئي', + 64517 => 'بج', + 64518 => 'بح', + 64519 => 'بخ', + 64520 => 'بم', + 64521 => 'بى', + 64522 => 'بي', + 64523 => 'تج', + 64524 => 'تح', + 64525 => 'تخ', + 64526 => 'تم', + 64527 => 'تى', + 64528 => 'تي', + 64529 => 'ثج', + 64530 => 'ثم', + 64531 => 'ثى', + 64532 => 'ثي', + 64533 => 'جح', + 64534 => 'جم', + 64535 => 'حج', + 64536 => 'حم', + 64537 => 'خج', + 64538 => 'خح', + 64539 => 'خم', + 64540 => 'سج', + 64541 => 'سح', + 64542 => 'سخ', + 64543 => 'سم', + 64544 => 'صح', + 64545 => 'صم', + 64546 => 'ضج', + 64547 => 'ضح', + 64548 => 'ضخ', + 64549 => 'ضم', + 64550 => 'طح', + 64551 => 'طم', + 64552 => 'ظم', + 64553 => 'عج', + 64554 => 'عم', + 64555 => 'غج', + 64556 => 'غم', + 64557 => 'ÙØ¬', + 64558 => 'ÙØ­', + 64559 => 'ÙØ®', + 64560 => 'ÙÙ…', + 64561 => 'ÙÙ‰', + 64562 => 'ÙÙŠ', + 64563 => 'قح', + 64564 => 'قم', + 64565 => 'قى', + 64566 => 'قي', + 64567 => 'كا', + 64568 => 'كج', + 64569 => 'كح', + 64570 => 'كخ', + 64571 => 'كل', + 64572 => 'كم', + 64573 => 'كى', + 64574 => 'كي', + 64575 => 'لج', + 64576 => 'لح', + 64577 => 'لخ', + 64578 => 'لم', + 64579 => 'لى', + 64580 => 'لي', + 64581 => 'مج', + 64582 => 'مح', + 64583 => 'مخ', + 64584 => 'مم', + 64585 => 'مى', + 64586 => 'مي', + 64587 => 'نج', + 64588 => 'نح', + 64589 => 'نخ', + 64590 => 'نم', + 64591 => 'نى', + 64592 => 'ني', + 64593 => 'هج', + 64594 => 'هم', + 64595 => 'هى', + 64596 => 'هي', + 64597 => 'يج', + 64598 => 'يح', + 64599 => 'يخ', + 64600 => 'يم', + 64601 => 'يى', + 64602 => 'يي', + 64603 => 'ذٰ', + 64604 => 'رٰ', + 64605 => 'ىٰ', + 64612 => 'ئر', + 64613 => 'ئز', + 64614 => 'ئم', + 64615 => 'ئن', + 64616 => 'ئى', + 64617 => 'ئي', + 64618 => 'بر', + 64619 => 'بز', + 64620 => 'بم', + 64621 => 'بن', + 64622 => 'بى', + 64623 => 'بي', + 64624 => 'تر', + 64625 => 'تز', + 64626 => 'تم', + 64627 => 'تن', + 64628 => 'تى', + 64629 => 'تي', + 64630 => 'ثر', + 64631 => 'ثز', + 64632 => 'ثم', + 64633 => 'ثن', + 64634 => 'ثى', + 64635 => 'ثي', + 64636 => 'ÙÙ‰', + 64637 => 'ÙÙŠ', + 64638 => 'قى', + 64639 => 'قي', + 64640 => 'كا', + 64641 => 'كل', + 64642 => 'كم', + 64643 => 'كى', + 64644 => 'كي', + 64645 => 'لم', + 64646 => 'لى', + 64647 => 'لي', + 64648 => 'ما', + 64649 => 'مم', + 64650 => 'نر', + 64651 => 'نز', + 64652 => 'نم', + 64653 => 'نن', + 64654 => 'نى', + 64655 => 'ني', + 64656 => 'ىٰ', + 64657 => 'ير', + 64658 => 'يز', + 64659 => 'يم', + 64660 => 'ين', + 64661 => 'يى', + 64662 => 'يي', + 64663 => 'ئج', + 64664 => 'ئح', + 64665 => 'ئخ', + 64666 => 'ئم', + 64667 => 'ئه', + 64668 => 'بج', + 64669 => 'بح', + 64670 => 'بخ', + 64671 => 'بم', + 64672 => 'به', + 64673 => 'تج', + 64674 => 'تح', + 64675 => 'تخ', + 64676 => 'تم', + 64677 => 'ته', + 64678 => 'ثم', + 64679 => 'جح', + 64680 => 'جم', + 64681 => 'حج', + 64682 => 'حم', + 64683 => 'خج', + 64684 => 'خم', + 64685 => 'سج', + 64686 => 'سح', + 64687 => 'سخ', + 64688 => 'سم', + 64689 => 'صح', + 64690 => 'صخ', + 64691 => 'صم', + 64692 => 'ضج', + 64693 => 'ضح', + 64694 => 'ضخ', + 64695 => 'ضم', + 64696 => 'طح', + 64697 => 'ظم', + 64698 => 'عج', + 64699 => 'عم', + 64700 => 'غج', + 64701 => 'غم', + 64702 => 'ÙØ¬', + 64703 => 'ÙØ­', + 64704 => 'ÙØ®', + 64705 => 'ÙÙ…', + 64706 => 'قح', + 64707 => 'قم', + 64708 => 'كج', + 64709 => 'كح', + 64710 => 'كخ', + 64711 => 'كل', + 64712 => 'كم', + 64713 => 'لج', + 64714 => 'لح', + 64715 => 'لخ', + 64716 => 'لم', + 64717 => 'له', + 64718 => 'مج', + 64719 => 'مح', + 64720 => 'مخ', + 64721 => 'مم', + 64722 => 'نج', + 64723 => 'نح', + 64724 => 'نخ', + 64725 => 'نم', + 64726 => 'نه', + 64727 => 'هج', + 64728 => 'هم', + 64729 => 'هٰ', + 64730 => 'يج', + 64731 => 'يح', + 64732 => 'يخ', + 64733 => 'يم', + 64734 => 'يه', + 64735 => 'ئم', + 64736 => 'ئه', + 64737 => 'بم', + 64738 => 'به', + 64739 => 'تم', + 64740 => 'ته', + 64741 => 'ثم', + 64742 => 'ثه', + 64743 => 'سم', + 64744 => 'سه', + 64745 => 'شم', + 64746 => 'شه', + 64747 => 'كل', + 64748 => 'كم', + 64749 => 'لم', + 64750 => 'نم', + 64751 => 'نه', + 64752 => 'يم', + 64753 => 'يه', + 64754 => 'Ù€ÙŽÙ‘', + 64755 => 'Ù€ÙÙ‘', + 64756 => 'Ù€ÙÙ‘', + 64757 => 'طى', + 64758 => 'طي', + 64759 => 'عى', + 64760 => 'عي', + 64761 => 'غى', + 64762 => 'غي', + 64763 => 'سى', + 64764 => 'سي', + 64765 => 'شى', + 64766 => 'شي', + 64767 => 'حى', + 64768 => 'حي', + 64769 => 'جى', + 64770 => 'جي', + 64771 => 'خى', + 64772 => 'خي', + 64773 => 'صى', + 64774 => 'صي', + 64775 => 'ضى', + 64776 => 'ضي', + 64777 => 'شج', + 64778 => 'شح', + 64779 => 'شخ', + 64780 => 'شم', + 64781 => 'شر', + 64782 => 'سر', + 64783 => 'صر', + 64784 => 'ضر', + 64785 => 'طى', + 64786 => 'طي', + 64787 => 'عى', + 64788 => 'عي', + 64789 => 'غى', + 64790 => 'غي', + 64791 => 'سى', + 64792 => 'سي', + 64793 => 'شى', + 64794 => 'شي', + 64795 => 'حى', + 64796 => 'حي', + 64797 => 'جى', + 64798 => 'جي', + 64799 => 'خى', + 64800 => 'خي', + 64801 => 'صى', + 64802 => 'صي', + 64803 => 'ضى', + 64804 => 'ضي', + 64805 => 'شج', + 64806 => 'شح', + 64807 => 'شخ', + 64808 => 'شم', + 64809 => 'شر', + 64810 => 'سر', + 64811 => 'صر', + 64812 => 'ضر', + 64813 => 'شج', + 64814 => 'شح', + 64815 => 'شخ', + 64816 => 'شم', + 64817 => 'سه', + 64818 => 'شه', + 64819 => 'طم', + 64820 => 'سج', + 64821 => 'سح', + 64822 => 'سخ', + 64823 => 'شج', + 64824 => 'شح', + 64825 => 'شخ', + 64826 => 'طم', + 64827 => 'ظم', + 64828 => 'اً', + 64829 => 'اً', + 64848 => 'تجم', + 64849 => 'تحج', + 64850 => 'تحج', + 64851 => 'تحم', + 64852 => 'تخم', + 64853 => 'تمج', + 64854 => 'تمح', + 64855 => 'تمخ', + 64856 => 'جمح', + 64857 => 'جمح', + 64858 => 'حمي', + 64859 => 'حمى', + 64860 => 'سحج', + 64861 => 'سجح', + 64862 => 'سجى', + 64863 => 'سمح', + 64864 => 'سمح', + 64865 => 'سمج', + 64866 => 'سمم', + 64867 => 'سمم', + 64868 => 'صحح', + 64869 => 'صحح', + 64870 => 'صمم', + 64871 => 'شحم', + 64872 => 'شحم', + 64873 => 'شجي', + 64874 => 'شمخ', + 64875 => 'شمخ', + 64876 => 'شمم', + 64877 => 'شمم', + 64878 => 'ضحى', + 64879 => 'ضخم', + 64880 => 'ضخم', + 64881 => 'طمح', + 64882 => 'طمح', + 64883 => 'طمم', + 64884 => 'طمي', + 64885 => 'عجم', + 64886 => 'عمم', + 64887 => 'عمم', + 64888 => 'عمى', + 64889 => 'غمم', + 64890 => 'غمي', + 64891 => 'غمى', + 64892 => 'ÙØ®Ù…', + 64893 => 'ÙØ®Ù…', + 64894 => 'قمح', + 64895 => 'قمم', + 64896 => 'لحم', + 64897 => 'لحي', + 64898 => 'لحى', + 64899 => 'لجج', + 64900 => 'لجج', + 64901 => 'لخم', + 64902 => 'لخم', + 64903 => 'لمح', + 64904 => 'لمح', + 64905 => 'محج', + 64906 => 'محم', + 64907 => 'محي', + 64908 => 'مجح', + 64909 => 'مجم', + 64910 => 'مخج', + 64911 => 'مخم', + 64914 => 'مجخ', + 64915 => 'همج', + 64916 => 'همم', + 64917 => 'نحم', + 64918 => 'نحى', + 64919 => 'نجم', + 64920 => 'نجم', + 64921 => 'نجى', + 64922 => 'نمي', + 64923 => 'نمى', + 64924 => 'يمم', + 64925 => 'يمم', + 64926 => 'بخي', + 64927 => 'تجي', + 64928 => 'تجى', + 64929 => 'تخي', + 64930 => 'تخى', + 64931 => 'تمي', + 64932 => 'تمى', + 64933 => 'جمي', + 64934 => 'جحى', + 64935 => 'جمى', + 64936 => 'سخى', + 64937 => 'صحي', + 64938 => 'شحي', + 64939 => 'ضحي', + 64940 => 'لجي', + 64941 => 'لمي', + 64942 => 'يحي', + 64943 => 'يجي', + 64944 => 'يمي', + 64945 => 'ممي', + 64946 => 'قمي', + 64947 => 'نحي', + 64948 => 'قمح', + 64949 => 'لحم', + 64950 => 'عمي', + 64951 => 'كمي', + 64952 => 'نجح', + 64953 => 'مخي', + 64954 => 'لجم', + 64955 => 'كمم', + 64956 => 'لجم', + 64957 => 'نجح', + 64958 => 'جحي', + 64959 => 'حجي', + 64960 => 'مجي', + 64961 => 'Ùمي', + 64962 => 'بحي', + 64963 => 'كمم', + 64964 => 'عجم', + 64965 => 'صمم', + 64966 => 'سخي', + 64967 => 'نجي', + 65008 => 'صلے', + 65009 => 'قلے', + 65010 => 'الله', + 65011 => 'اكبر', + 65012 => 'محمد', + 65013 => 'صلعم', + 65014 => 'رسول', + 65015 => 'عليه', + 65016 => 'وسلم', + 65017 => 'صلى', + 65020 => 'ریال', + 65041 => 'ã€', + 65047 => '〖', + 65048 => '〗', + 65073 => '—', + 65074 => '–', + 65081 => '〔', + 65082 => '〕', + 65083 => 'ã€', + 65084 => '】', + 65085 => '《', + 65086 => '》', + 65087 => '〈', + 65088 => '〉', + 65089 => '「', + 65090 => 'ã€', + 65091 => '『', + 65092 => 'ã€', + 65105 => 'ã€', + 65112 => '—', + 65117 => '〔', + 65118 => '〕', + 65123 => '-', + 65137 => 'ـً', + 65143 => 'Ù€ÙŽ', + 65145 => 'Ù€Ù', + 65147 => 'Ù€Ù', + 65149 => 'ـّ', + 65151 => 'ـْ', + 65152 => 'Ø¡', + 65153 => 'Ø¢', + 65154 => 'Ø¢', + 65155 => 'Ø£', + 65156 => 'Ø£', + 65157 => 'ؤ', + 65158 => 'ؤ', + 65159 => 'Ø¥', + 65160 => 'Ø¥', + 65161 => 'ئ', + 65162 => 'ئ', + 65163 => 'ئ', + 65164 => 'ئ', + 65165 => 'ا', + 65166 => 'ا', + 65167 => 'ب', + 65168 => 'ب', + 65169 => 'ب', + 65170 => 'ب', + 65171 => 'Ø©', + 65172 => 'Ø©', + 65173 => 'ت', + 65174 => 'ت', + 65175 => 'ت', + 65176 => 'ت', + 65177 => 'Ø«', + 65178 => 'Ø«', + 65179 => 'Ø«', + 65180 => 'Ø«', + 65181 => 'ج', + 65182 => 'ج', + 65183 => 'ج', + 65184 => 'ج', + 65185 => 'Ø­', + 65186 => 'Ø­', + 65187 => 'Ø­', + 65188 => 'Ø­', + 65189 => 'Ø®', + 65190 => 'Ø®', + 65191 => 'Ø®', + 65192 => 'Ø®', + 65193 => 'د', + 65194 => 'د', + 65195 => 'ذ', + 65196 => 'ذ', + 65197 => 'ر', + 65198 => 'ر', + 65199 => 'ز', + 65200 => 'ز', + 65201 => 'س', + 65202 => 'س', + 65203 => 'س', + 65204 => 'س', + 65205 => 'Ø´', + 65206 => 'Ø´', + 65207 => 'Ø´', + 65208 => 'Ø´', + 65209 => 'ص', + 65210 => 'ص', + 65211 => 'ص', + 65212 => 'ص', + 65213 => 'ض', + 65214 => 'ض', + 65215 => 'ض', + 65216 => 'ض', + 65217 => 'Ø·', + 65218 => 'Ø·', + 65219 => 'Ø·', + 65220 => 'Ø·', + 65221 => 'ظ', + 65222 => 'ظ', + 65223 => 'ظ', + 65224 => 'ظ', + 65225 => 'ع', + 65226 => 'ع', + 65227 => 'ع', + 65228 => 'ع', + 65229 => 'غ', + 65230 => 'غ', + 65231 => 'غ', + 65232 => 'غ', + 65233 => 'Ù', + 65234 => 'Ù', + 65235 => 'Ù', + 65236 => 'Ù', + 65237 => 'Ù‚', + 65238 => 'Ù‚', + 65239 => 'Ù‚', + 65240 => 'Ù‚', + 65241 => 'Ùƒ', + 65242 => 'Ùƒ', + 65243 => 'Ùƒ', + 65244 => 'Ùƒ', + 65245 => 'Ù„', + 65246 => 'Ù„', + 65247 => 'Ù„', + 65248 => 'Ù„', + 65249 => 'Ù…', + 65250 => 'Ù…', + 65251 => 'Ù…', + 65252 => 'Ù…', + 65253 => 'Ù†', + 65254 => 'Ù†', + 65255 => 'Ù†', + 65256 => 'Ù†', + 65257 => 'Ù‡', + 65258 => 'Ù‡', + 65259 => 'Ù‡', + 65260 => 'Ù‡', + 65261 => 'Ùˆ', + 65262 => 'Ùˆ', + 65263 => 'Ù‰', + 65264 => 'Ù‰', + 65265 => 'ÙŠ', + 65266 => 'ÙŠ', + 65267 => 'ÙŠ', + 65268 => 'ÙŠ', + 65269 => 'لآ', + 65270 => 'لآ', + 65271 => 'لأ', + 65272 => 'لأ', + 65273 => 'لإ', + 65274 => 'لإ', + 65275 => 'لا', + 65276 => 'لا', + 65293 => '-', + 65294 => '.', + 65296 => '0', + 65297 => '1', + 65298 => '2', + 65299 => '3', + 65300 => '4', + 65301 => '5', + 65302 => '6', + 65303 => '7', + 65304 => '8', + 65305 => '9', + 65313 => 'a', + 65314 => 'b', + 65315 => 'c', + 65316 => 'd', + 65317 => 'e', + 65318 => 'f', + 65319 => 'g', + 65320 => 'h', + 65321 => 'i', + 65322 => 'j', + 65323 => 'k', + 65324 => 'l', + 65325 => 'm', + 65326 => 'n', + 65327 => 'o', + 65328 => 'p', + 65329 => 'q', + 65330 => 'r', + 65331 => 's', + 65332 => 't', + 65333 => 'u', + 65334 => 'v', + 65335 => 'w', + 65336 => 'x', + 65337 => 'y', + 65338 => 'z', + 65345 => 'a', + 65346 => 'b', + 65347 => 'c', + 65348 => 'd', + 65349 => 'e', + 65350 => 'f', + 65351 => 'g', + 65352 => 'h', + 65353 => 'i', + 65354 => 'j', + 65355 => 'k', + 65356 => 'l', + 65357 => 'm', + 65358 => 'n', + 65359 => 'o', + 65360 => 'p', + 65361 => 'q', + 65362 => 'r', + 65363 => 's', + 65364 => 't', + 65365 => 'u', + 65366 => 'v', + 65367 => 'w', + 65368 => 'x', + 65369 => 'y', + 65370 => 'z', + 65375 => '⦅', + 65376 => '⦆', + 65377 => '.', + 65378 => '「', + 65379 => 'ã€', + 65380 => 'ã€', + 65381 => '・', + 65382 => 'ヲ', + 65383 => 'ã‚¡', + 65384 => 'ã‚£', + 65385 => 'ã‚¥', + 65386 => 'ã‚§', + 65387 => 'ã‚©', + 65388 => 'ャ', + 65389 => 'ュ', + 65390 => 'ョ', + 65391 => 'ッ', + 65392 => 'ー', + 65393 => 'ã‚¢', + 65394 => 'イ', + 65395 => 'ウ', + 65396 => 'エ', + 65397 => 'オ', + 65398 => 'ã‚«', + 65399 => 'ã‚­', + 65400 => 'ク', + 65401 => 'ケ', + 65402 => 'コ', + 65403 => 'サ', + 65404 => 'ã‚·', + 65405 => 'ス', + 65406 => 'ã‚»', + 65407 => 'ソ', + 65408 => 'ã‚¿', + 65409 => 'ãƒ', + 65410 => 'ツ', + 65411 => 'テ', + 65412 => 'ト', + 65413 => 'ナ', + 65414 => 'ニ', + 65415 => 'ヌ', + 65416 => 'ãƒ', + 65417 => 'ノ', + 65418 => 'ãƒ', + 65419 => 'ヒ', + 65420 => 'フ', + 65421 => 'ヘ', + 65422 => 'ホ', + 65423 => 'マ', + 65424 => 'ミ', + 65425 => 'ム', + 65426 => 'メ', + 65427 => 'モ', + 65428 => 'ヤ', + 65429 => 'ユ', + 65430 => 'ヨ', + 65431 => 'ラ', + 65432 => 'リ', + 65433 => 'ル', + 65434 => 'レ', + 65435 => 'ロ', + 65436 => 'ワ', + 65437 => 'ン', + 65438 => 'ã‚™', + 65439 => '゚', + 65441 => 'á„€', + 65442 => 'á„', + 65443 => 'ᆪ', + 65444 => 'á„‚', + 65445 => 'ᆬ', + 65446 => 'ᆭ', + 65447 => 'ᄃ', + 65448 => 'á„„', + 65449 => 'á„…', + 65450 => 'ᆰ', + 65451 => 'ᆱ', + 65452 => 'ᆲ', + 65453 => 'ᆳ', + 65454 => 'ᆴ', + 65455 => 'ᆵ', + 65456 => 'ᄚ', + 65457 => 'ᄆ', + 65458 => 'ᄇ', + 65459 => 'ᄈ', + 65460 => 'á„¡', + 65461 => 'ᄉ', + 65462 => 'ᄊ', + 65463 => 'á„‹', + 65464 => 'ᄌ', + 65465 => 'á„', + 65466 => 'ᄎ', + 65467 => 'á„', + 65468 => 'á„', + 65469 => 'á„‘', + 65470 => 'á„’', + 65474 => 'á…¡', + 65475 => 'á…¢', + 65476 => 'á…£', + 65477 => 'á…¤', + 65478 => 'á…¥', + 65479 => 'á…¦', + 65482 => 'á…§', + 65483 => 'á…¨', + 65484 => 'á…©', + 65485 => 'á…ª', + 65486 => 'á…«', + 65487 => 'á…¬', + 65490 => 'á…­', + 65491 => 'á…®', + 65492 => 'á…¯', + 65493 => 'á…°', + 65494 => 'á…±', + 65495 => 'á…²', + 65498 => 'á…³', + 65499 => 'á…´', + 65500 => 'á…µ', + 65504 => '¢', + 65505 => '£', + 65506 => '¬', + 65508 => '¦', + 65509 => 'Â¥', + 65510 => 'â‚©', + 65512 => '│', + 65513 => 'â†', + 65514 => '↑', + 65515 => '→', + 65516 => '↓', + 65517 => 'â– ', + 65518 => 'â—‹', + 66560 => 'ð¨', + 66561 => 'ð©', + 66562 => 'ðª', + 66563 => 'ð«', + 66564 => 'ð¬', + 66565 => 'ð­', + 66566 => 'ð®', + 66567 => 'ð¯', + 66568 => 'ð°', + 66569 => 'ð±', + 66570 => 'ð²', + 66571 => 'ð³', + 66572 => 'ð´', + 66573 => 'ðµ', + 66574 => 'ð¶', + 66575 => 'ð·', + 66576 => 'ð¸', + 66577 => 'ð¹', + 66578 => 'ðº', + 66579 => 'ð»', + 66580 => 'ð¼', + 66581 => 'ð½', + 66582 => 'ð¾', + 66583 => 'ð¿', + 66584 => 'ð‘€', + 66585 => 'ð‘', + 66586 => 'ð‘‚', + 66587 => 'ð‘ƒ', + 66588 => 'ð‘„', + 66589 => 'ð‘…', + 66590 => 'ð‘†', + 66591 => 'ð‘‡', + 66592 => 'ð‘ˆ', + 66593 => 'ð‘‰', + 66594 => 'ð‘Š', + 66595 => 'ð‘‹', + 66596 => 'ð‘Œ', + 66597 => 'ð‘', + 66598 => 'ð‘Ž', + 66599 => 'ð‘', + 66736 => 'ð“˜', + 66737 => 'ð“™', + 66738 => 'ð“š', + 66739 => 'ð“›', + 66740 => 'ð“œ', + 66741 => 'ð“', + 66742 => 'ð“ž', + 66743 => 'ð“Ÿ', + 66744 => 'ð“ ', + 66745 => 'ð“¡', + 66746 => 'ð“¢', + 66747 => 'ð“£', + 66748 => 'ð“¤', + 66749 => 'ð“¥', + 66750 => 'ð“¦', + 66751 => 'ð“§', + 66752 => 'ð“¨', + 66753 => 'ð“©', + 66754 => 'ð“ª', + 66755 => 'ð“«', + 66756 => 'ð“¬', + 66757 => 'ð“­', + 66758 => 'ð“®', + 66759 => 'ð“¯', + 66760 => 'ð“°', + 66761 => 'ð“±', + 66762 => 'ð“²', + 66763 => 'ð“³', + 66764 => 'ð“´', + 66765 => 'ð“µ', + 66766 => 'ð“¶', + 66767 => 'ð“·', + 66768 => 'ð“¸', + 66769 => 'ð“¹', + 66770 => 'ð“º', + 66771 => 'ð“»', + 68736 => 'ð³€', + 68737 => 'ð³', + 68738 => 'ð³‚', + 68739 => 'ð³ƒ', + 68740 => 'ð³„', + 68741 => 'ð³…', + 68742 => 'ð³†', + 68743 => 'ð³‡', + 68744 => 'ð³ˆ', + 68745 => 'ð³‰', + 68746 => 'ð³Š', + 68747 => 'ð³‹', + 68748 => 'ð³Œ', + 68749 => 'ð³', + 68750 => 'ð³Ž', + 68751 => 'ð³', + 68752 => 'ð³', + 68753 => 'ð³‘', + 68754 => 'ð³’', + 68755 => 'ð³“', + 68756 => 'ð³”', + 68757 => 'ð³•', + 68758 => 'ð³–', + 68759 => 'ð³—', + 68760 => 'ð³˜', + 68761 => 'ð³™', + 68762 => 'ð³š', + 68763 => 'ð³›', + 68764 => 'ð³œ', + 68765 => 'ð³', + 68766 => 'ð³ž', + 68767 => 'ð³Ÿ', + 68768 => 'ð³ ', + 68769 => 'ð³¡', + 68770 => 'ð³¢', + 68771 => 'ð³£', + 68772 => 'ð³¤', + 68773 => 'ð³¥', + 68774 => 'ð³¦', + 68775 => 'ð³§', + 68776 => 'ð³¨', + 68777 => 'ð³©', + 68778 => 'ð³ª', + 68779 => 'ð³«', + 68780 => 'ð³¬', + 68781 => 'ð³­', + 68782 => 'ð³®', + 68783 => 'ð³¯', + 68784 => 'ð³°', + 68785 => 'ð³±', + 68786 => 'ð³²', + 71840 => 'ð‘£€', + 71841 => 'ð‘£', + 71842 => '𑣂', + 71843 => '𑣃', + 71844 => '𑣄', + 71845 => 'ð‘£…', + 71846 => '𑣆', + 71847 => '𑣇', + 71848 => '𑣈', + 71849 => '𑣉', + 71850 => '𑣊', + 71851 => '𑣋', + 71852 => '𑣌', + 71853 => 'ð‘£', + 71854 => '𑣎', + 71855 => 'ð‘£', + 71856 => 'ð‘£', + 71857 => '𑣑', + 71858 => 'ð‘£’', + 71859 => '𑣓', + 71860 => 'ð‘£”', + 71861 => '𑣕', + 71862 => 'ð‘£–', + 71863 => 'ð‘£—', + 71864 => '𑣘', + 71865 => 'ð‘£™', + 71866 => '𑣚', + 71867 => 'ð‘£›', + 71868 => '𑣜', + 71869 => 'ð‘£', + 71870 => '𑣞', + 71871 => '𑣟', + 93760 => 'ð–¹ ', + 93761 => '𖹡', + 93762 => 'ð–¹¢', + 93763 => 'ð–¹£', + 93764 => '𖹤', + 93765 => 'ð–¹¥', + 93766 => '𖹦', + 93767 => 'ð–¹§', + 93768 => '𖹨', + 93769 => '𖹩', + 93770 => '𖹪', + 93771 => '𖹫', + 93772 => '𖹬', + 93773 => 'ð–¹­', + 93774 => 'ð–¹®', + 93775 => '𖹯', + 93776 => 'ð–¹°', + 93777 => 'ð–¹±', + 93778 => 'ð–¹²', + 93779 => 'ð–¹³', + 93780 => 'ð–¹´', + 93781 => 'ð–¹µ', + 93782 => 'ð–¹¶', + 93783 => 'ð–¹·', + 93784 => '𖹸', + 93785 => 'ð–¹¹', + 93786 => '𖹺', + 93787 => 'ð–¹»', + 93788 => 'ð–¹¼', + 93789 => 'ð–¹½', + 93790 => 'ð–¹¾', + 93791 => '𖹿', + 119134 => 'ð…—ð…¥', + 119135 => 'ð…˜ð…¥', + 119136 => 'ð…˜ð…¥ð…®', + 119137 => 'ð…˜ð…¥ð…¯', + 119138 => 'ð…˜ð…¥ð…°', + 119139 => 'ð…˜ð…¥ð…±', + 119140 => 'ð…˜ð…¥ð…²', + 119227 => 'ð†¹ð…¥', + 119228 => 'ð†ºð…¥', + 119229 => 'ð†¹ð…¥ð…®', + 119230 => 'ð†ºð…¥ð…®', + 119231 => 'ð†¹ð…¥ð…¯', + 119232 => 'ð†ºð…¥ð…¯', + 119808 => 'a', + 119809 => 'b', + 119810 => 'c', + 119811 => 'd', + 119812 => 'e', + 119813 => 'f', + 119814 => 'g', + 119815 => 'h', + 119816 => 'i', + 119817 => 'j', + 119818 => 'k', + 119819 => 'l', + 119820 => 'm', + 119821 => 'n', + 119822 => 'o', + 119823 => 'p', + 119824 => 'q', + 119825 => 'r', + 119826 => 's', + 119827 => 't', + 119828 => 'u', + 119829 => 'v', + 119830 => 'w', + 119831 => 'x', + 119832 => 'y', + 119833 => 'z', + 119834 => 'a', + 119835 => 'b', + 119836 => 'c', + 119837 => 'd', + 119838 => 'e', + 119839 => 'f', + 119840 => 'g', + 119841 => 'h', + 119842 => 'i', + 119843 => 'j', + 119844 => 'k', + 119845 => 'l', + 119846 => 'm', + 119847 => 'n', + 119848 => 'o', + 119849 => 'p', + 119850 => 'q', + 119851 => 'r', + 119852 => 's', + 119853 => 't', + 119854 => 'u', + 119855 => 'v', + 119856 => 'w', + 119857 => 'x', + 119858 => 'y', + 119859 => 'z', + 119860 => 'a', + 119861 => 'b', + 119862 => 'c', + 119863 => 'd', + 119864 => 'e', + 119865 => 'f', + 119866 => 'g', + 119867 => 'h', + 119868 => 'i', + 119869 => 'j', + 119870 => 'k', + 119871 => 'l', + 119872 => 'm', + 119873 => 'n', + 119874 => 'o', + 119875 => 'p', + 119876 => 'q', + 119877 => 'r', + 119878 => 's', + 119879 => 't', + 119880 => 'u', + 119881 => 'v', + 119882 => 'w', + 119883 => 'x', + 119884 => 'y', + 119885 => 'z', + 119886 => 'a', + 119887 => 'b', + 119888 => 'c', + 119889 => 'd', + 119890 => 'e', + 119891 => 'f', + 119892 => 'g', + 119894 => 'i', + 119895 => 'j', + 119896 => 'k', + 119897 => 'l', + 119898 => 'm', + 119899 => 'n', + 119900 => 'o', + 119901 => 'p', + 119902 => 'q', + 119903 => 'r', + 119904 => 's', + 119905 => 't', + 119906 => 'u', + 119907 => 'v', + 119908 => 'w', + 119909 => 'x', + 119910 => 'y', + 119911 => 'z', + 119912 => 'a', + 119913 => 'b', + 119914 => 'c', + 119915 => 'd', + 119916 => 'e', + 119917 => 'f', + 119918 => 'g', + 119919 => 'h', + 119920 => 'i', + 119921 => 'j', + 119922 => 'k', + 119923 => 'l', + 119924 => 'm', + 119925 => 'n', + 119926 => 'o', + 119927 => 'p', + 119928 => 'q', + 119929 => 'r', + 119930 => 's', + 119931 => 't', + 119932 => 'u', + 119933 => 'v', + 119934 => 'w', + 119935 => 'x', + 119936 => 'y', + 119937 => 'z', + 119938 => 'a', + 119939 => 'b', + 119940 => 'c', + 119941 => 'd', + 119942 => 'e', + 119943 => 'f', + 119944 => 'g', + 119945 => 'h', + 119946 => 'i', + 119947 => 'j', + 119948 => 'k', + 119949 => 'l', + 119950 => 'm', + 119951 => 'n', + 119952 => 'o', + 119953 => 'p', + 119954 => 'q', + 119955 => 'r', + 119956 => 's', + 119957 => 't', + 119958 => 'u', + 119959 => 'v', + 119960 => 'w', + 119961 => 'x', + 119962 => 'y', + 119963 => 'z', + 119964 => 'a', + 119966 => 'c', + 119967 => 'd', + 119970 => 'g', + 119973 => 'j', + 119974 => 'k', + 119977 => 'n', + 119978 => 'o', + 119979 => 'p', + 119980 => 'q', + 119982 => 's', + 119983 => 't', + 119984 => 'u', + 119985 => 'v', + 119986 => 'w', + 119987 => 'x', + 119988 => 'y', + 119989 => 'z', + 119990 => 'a', + 119991 => 'b', + 119992 => 'c', + 119993 => 'd', + 119995 => 'f', + 119997 => 'h', + 119998 => 'i', + 119999 => 'j', + 120000 => 'k', + 120001 => 'l', + 120002 => 'm', + 120003 => 'n', + 120005 => 'p', + 120006 => 'q', + 120007 => 'r', + 120008 => 's', + 120009 => 't', + 120010 => 'u', + 120011 => 'v', + 120012 => 'w', + 120013 => 'x', + 120014 => 'y', + 120015 => 'z', + 120016 => 'a', + 120017 => 'b', + 120018 => 'c', + 120019 => 'd', + 120020 => 'e', + 120021 => 'f', + 120022 => 'g', + 120023 => 'h', + 120024 => 'i', + 120025 => 'j', + 120026 => 'k', + 120027 => 'l', + 120028 => 'm', + 120029 => 'n', + 120030 => 'o', + 120031 => 'p', + 120032 => 'q', + 120033 => 'r', + 120034 => 's', + 120035 => 't', + 120036 => 'u', + 120037 => 'v', + 120038 => 'w', + 120039 => 'x', + 120040 => 'y', + 120041 => 'z', + 120042 => 'a', + 120043 => 'b', + 120044 => 'c', + 120045 => 'd', + 120046 => 'e', + 120047 => 'f', + 120048 => 'g', + 120049 => 'h', + 120050 => 'i', + 120051 => 'j', + 120052 => 'k', + 120053 => 'l', + 120054 => 'm', + 120055 => 'n', + 120056 => 'o', + 120057 => 'p', + 120058 => 'q', + 120059 => 'r', + 120060 => 's', + 120061 => 't', + 120062 => 'u', + 120063 => 'v', + 120064 => 'w', + 120065 => 'x', + 120066 => 'y', + 120067 => 'z', + 120068 => 'a', + 120069 => 'b', + 120071 => 'd', + 120072 => 'e', + 120073 => 'f', + 120074 => 'g', + 120077 => 'j', + 120078 => 'k', + 120079 => 'l', + 120080 => 'm', + 120081 => 'n', + 120082 => 'o', + 120083 => 'p', + 120084 => 'q', + 120086 => 's', + 120087 => 't', + 120088 => 'u', + 120089 => 'v', + 120090 => 'w', + 120091 => 'x', + 120092 => 'y', + 120094 => 'a', + 120095 => 'b', + 120096 => 'c', + 120097 => 'd', + 120098 => 'e', + 120099 => 'f', + 120100 => 'g', + 120101 => 'h', + 120102 => 'i', + 120103 => 'j', + 120104 => 'k', + 120105 => 'l', + 120106 => 'm', + 120107 => 'n', + 120108 => 'o', + 120109 => 'p', + 120110 => 'q', + 120111 => 'r', + 120112 => 's', + 120113 => 't', + 120114 => 'u', + 120115 => 'v', + 120116 => 'w', + 120117 => 'x', + 120118 => 'y', + 120119 => 'z', + 120120 => 'a', + 120121 => 'b', + 120123 => 'd', + 120124 => 'e', + 120125 => 'f', + 120126 => 'g', + 120128 => 'i', + 120129 => 'j', + 120130 => 'k', + 120131 => 'l', + 120132 => 'm', + 120134 => 'o', + 120138 => 's', + 120139 => 't', + 120140 => 'u', + 120141 => 'v', + 120142 => 'w', + 120143 => 'x', + 120144 => 'y', + 120146 => 'a', + 120147 => 'b', + 120148 => 'c', + 120149 => 'd', + 120150 => 'e', + 120151 => 'f', + 120152 => 'g', + 120153 => 'h', + 120154 => 'i', + 120155 => 'j', + 120156 => 'k', + 120157 => 'l', + 120158 => 'm', + 120159 => 'n', + 120160 => 'o', + 120161 => 'p', + 120162 => 'q', + 120163 => 'r', + 120164 => 's', + 120165 => 't', + 120166 => 'u', + 120167 => 'v', + 120168 => 'w', + 120169 => 'x', + 120170 => 'y', + 120171 => 'z', + 120172 => 'a', + 120173 => 'b', + 120174 => 'c', + 120175 => 'd', + 120176 => 'e', + 120177 => 'f', + 120178 => 'g', + 120179 => 'h', + 120180 => 'i', + 120181 => 'j', + 120182 => 'k', + 120183 => 'l', + 120184 => 'm', + 120185 => 'n', + 120186 => 'o', + 120187 => 'p', + 120188 => 'q', + 120189 => 'r', + 120190 => 's', + 120191 => 't', + 120192 => 'u', + 120193 => 'v', + 120194 => 'w', + 120195 => 'x', + 120196 => 'y', + 120197 => 'z', + 120198 => 'a', + 120199 => 'b', + 120200 => 'c', + 120201 => 'd', + 120202 => 'e', + 120203 => 'f', + 120204 => 'g', + 120205 => 'h', + 120206 => 'i', + 120207 => 'j', + 120208 => 'k', + 120209 => 'l', + 120210 => 'm', + 120211 => 'n', + 120212 => 'o', + 120213 => 'p', + 120214 => 'q', + 120215 => 'r', + 120216 => 's', + 120217 => 't', + 120218 => 'u', + 120219 => 'v', + 120220 => 'w', + 120221 => 'x', + 120222 => 'y', + 120223 => 'z', + 120224 => 'a', + 120225 => 'b', + 120226 => 'c', + 120227 => 'd', + 120228 => 'e', + 120229 => 'f', + 120230 => 'g', + 120231 => 'h', + 120232 => 'i', + 120233 => 'j', + 120234 => 'k', + 120235 => 'l', + 120236 => 'm', + 120237 => 'n', + 120238 => 'o', + 120239 => 'p', + 120240 => 'q', + 120241 => 'r', + 120242 => 's', + 120243 => 't', + 120244 => 'u', + 120245 => 'v', + 120246 => 'w', + 120247 => 'x', + 120248 => 'y', + 120249 => 'z', + 120250 => 'a', + 120251 => 'b', + 120252 => 'c', + 120253 => 'd', + 120254 => 'e', + 120255 => 'f', + 120256 => 'g', + 120257 => 'h', + 120258 => 'i', + 120259 => 'j', + 120260 => 'k', + 120261 => 'l', + 120262 => 'm', + 120263 => 'n', + 120264 => 'o', + 120265 => 'p', + 120266 => 'q', + 120267 => 'r', + 120268 => 's', + 120269 => 't', + 120270 => 'u', + 120271 => 'v', + 120272 => 'w', + 120273 => 'x', + 120274 => 'y', + 120275 => 'z', + 120276 => 'a', + 120277 => 'b', + 120278 => 'c', + 120279 => 'd', + 120280 => 'e', + 120281 => 'f', + 120282 => 'g', + 120283 => 'h', + 120284 => 'i', + 120285 => 'j', + 120286 => 'k', + 120287 => 'l', + 120288 => 'm', + 120289 => 'n', + 120290 => 'o', + 120291 => 'p', + 120292 => 'q', + 120293 => 'r', + 120294 => 's', + 120295 => 't', + 120296 => 'u', + 120297 => 'v', + 120298 => 'w', + 120299 => 'x', + 120300 => 'y', + 120301 => 'z', + 120302 => 'a', + 120303 => 'b', + 120304 => 'c', + 120305 => 'd', + 120306 => 'e', + 120307 => 'f', + 120308 => 'g', + 120309 => 'h', + 120310 => 'i', + 120311 => 'j', + 120312 => 'k', + 120313 => 'l', + 120314 => 'm', + 120315 => 'n', + 120316 => 'o', + 120317 => 'p', + 120318 => 'q', + 120319 => 'r', + 120320 => 's', + 120321 => 't', + 120322 => 'u', + 120323 => 'v', + 120324 => 'w', + 120325 => 'x', + 120326 => 'y', + 120327 => 'z', + 120328 => 'a', + 120329 => 'b', + 120330 => 'c', + 120331 => 'd', + 120332 => 'e', + 120333 => 'f', + 120334 => 'g', + 120335 => 'h', + 120336 => 'i', + 120337 => 'j', + 120338 => 'k', + 120339 => 'l', + 120340 => 'm', + 120341 => 'n', + 120342 => 'o', + 120343 => 'p', + 120344 => 'q', + 120345 => 'r', + 120346 => 's', + 120347 => 't', + 120348 => 'u', + 120349 => 'v', + 120350 => 'w', + 120351 => 'x', + 120352 => 'y', + 120353 => 'z', + 120354 => 'a', + 120355 => 'b', + 120356 => 'c', + 120357 => 'd', + 120358 => 'e', + 120359 => 'f', + 120360 => 'g', + 120361 => 'h', + 120362 => 'i', + 120363 => 'j', + 120364 => 'k', + 120365 => 'l', + 120366 => 'm', + 120367 => 'n', + 120368 => 'o', + 120369 => 'p', + 120370 => 'q', + 120371 => 'r', + 120372 => 's', + 120373 => 't', + 120374 => 'u', + 120375 => 'v', + 120376 => 'w', + 120377 => 'x', + 120378 => 'y', + 120379 => 'z', + 120380 => 'a', + 120381 => 'b', + 120382 => 'c', + 120383 => 'd', + 120384 => 'e', + 120385 => 'f', + 120386 => 'g', + 120387 => 'h', + 120388 => 'i', + 120389 => 'j', + 120390 => 'k', + 120391 => 'l', + 120392 => 'm', + 120393 => 'n', + 120394 => 'o', + 120395 => 'p', + 120396 => 'q', + 120397 => 'r', + 120398 => 's', + 120399 => 't', + 120400 => 'u', + 120401 => 'v', + 120402 => 'w', + 120403 => 'x', + 120404 => 'y', + 120405 => 'z', + 120406 => 'a', + 120407 => 'b', + 120408 => 'c', + 120409 => 'd', + 120410 => 'e', + 120411 => 'f', + 120412 => 'g', + 120413 => 'h', + 120414 => 'i', + 120415 => 'j', + 120416 => 'k', + 120417 => 'l', + 120418 => 'm', + 120419 => 'n', + 120420 => 'o', + 120421 => 'p', + 120422 => 'q', + 120423 => 'r', + 120424 => 's', + 120425 => 't', + 120426 => 'u', + 120427 => 'v', + 120428 => 'w', + 120429 => 'x', + 120430 => 'y', + 120431 => 'z', + 120432 => 'a', + 120433 => 'b', + 120434 => 'c', + 120435 => 'd', + 120436 => 'e', + 120437 => 'f', + 120438 => 'g', + 120439 => 'h', + 120440 => 'i', + 120441 => 'j', + 120442 => 'k', + 120443 => 'l', + 120444 => 'm', + 120445 => 'n', + 120446 => 'o', + 120447 => 'p', + 120448 => 'q', + 120449 => 'r', + 120450 => 's', + 120451 => 't', + 120452 => 'u', + 120453 => 'v', + 120454 => 'w', + 120455 => 'x', + 120456 => 'y', + 120457 => 'z', + 120458 => 'a', + 120459 => 'b', + 120460 => 'c', + 120461 => 'd', + 120462 => 'e', + 120463 => 'f', + 120464 => 'g', + 120465 => 'h', + 120466 => 'i', + 120467 => 'j', + 120468 => 'k', + 120469 => 'l', + 120470 => 'm', + 120471 => 'n', + 120472 => 'o', + 120473 => 'p', + 120474 => 'q', + 120475 => 'r', + 120476 => 's', + 120477 => 't', + 120478 => 'u', + 120479 => 'v', + 120480 => 'w', + 120481 => 'x', + 120482 => 'y', + 120483 => 'z', + 120484 => 'ı', + 120485 => 'È·', + 120488 => 'α', + 120489 => 'β', + 120490 => 'γ', + 120491 => 'δ', + 120492 => 'ε', + 120493 => 'ζ', + 120494 => 'η', + 120495 => 'θ', + 120496 => 'ι', + 120497 => 'κ', + 120498 => 'λ', + 120499 => 'μ', + 120500 => 'ν', + 120501 => 'ξ', + 120502 => 'ο', + 120503 => 'Ï€', + 120504 => 'Ï', + 120505 => 'θ', + 120506 => 'σ', + 120507 => 'Ï„', + 120508 => 'Ï…', + 120509 => 'φ', + 120510 => 'χ', + 120511 => 'ψ', + 120512 => 'ω', + 120513 => '∇', + 120514 => 'α', + 120515 => 'β', + 120516 => 'γ', + 120517 => 'δ', + 120518 => 'ε', + 120519 => 'ζ', + 120520 => 'η', + 120521 => 'θ', + 120522 => 'ι', + 120523 => 'κ', + 120524 => 'λ', + 120525 => 'μ', + 120526 => 'ν', + 120527 => 'ξ', + 120528 => 'ο', + 120529 => 'Ï€', + 120530 => 'Ï', + 120531 => 'σ', + 120532 => 'σ', + 120533 => 'Ï„', + 120534 => 'Ï…', + 120535 => 'φ', + 120536 => 'χ', + 120537 => 'ψ', + 120538 => 'ω', + 120539 => '∂', + 120540 => 'ε', + 120541 => 'θ', + 120542 => 'κ', + 120543 => 'φ', + 120544 => 'Ï', + 120545 => 'Ï€', + 120546 => 'α', + 120547 => 'β', + 120548 => 'γ', + 120549 => 'δ', + 120550 => 'ε', + 120551 => 'ζ', + 120552 => 'η', + 120553 => 'θ', + 120554 => 'ι', + 120555 => 'κ', + 120556 => 'λ', + 120557 => 'μ', + 120558 => 'ν', + 120559 => 'ξ', + 120560 => 'ο', + 120561 => 'Ï€', + 120562 => 'Ï', + 120563 => 'θ', + 120564 => 'σ', + 120565 => 'Ï„', + 120566 => 'Ï…', + 120567 => 'φ', + 120568 => 'χ', + 120569 => 'ψ', + 120570 => 'ω', + 120571 => '∇', + 120572 => 'α', + 120573 => 'β', + 120574 => 'γ', + 120575 => 'δ', + 120576 => 'ε', + 120577 => 'ζ', + 120578 => 'η', + 120579 => 'θ', + 120580 => 'ι', + 120581 => 'κ', + 120582 => 'λ', + 120583 => 'μ', + 120584 => 'ν', + 120585 => 'ξ', + 120586 => 'ο', + 120587 => 'Ï€', + 120588 => 'Ï', + 120589 => 'σ', + 120590 => 'σ', + 120591 => 'Ï„', + 120592 => 'Ï…', + 120593 => 'φ', + 120594 => 'χ', + 120595 => 'ψ', + 120596 => 'ω', + 120597 => '∂', + 120598 => 'ε', + 120599 => 'θ', + 120600 => 'κ', + 120601 => 'φ', + 120602 => 'Ï', + 120603 => 'Ï€', + 120604 => 'α', + 120605 => 'β', + 120606 => 'γ', + 120607 => 'δ', + 120608 => 'ε', + 120609 => 'ζ', + 120610 => 'η', + 120611 => 'θ', + 120612 => 'ι', + 120613 => 'κ', + 120614 => 'λ', + 120615 => 'μ', + 120616 => 'ν', + 120617 => 'ξ', + 120618 => 'ο', + 120619 => 'Ï€', + 120620 => 'Ï', + 120621 => 'θ', + 120622 => 'σ', + 120623 => 'Ï„', + 120624 => 'Ï…', + 120625 => 'φ', + 120626 => 'χ', + 120627 => 'ψ', + 120628 => 'ω', + 120629 => '∇', + 120630 => 'α', + 120631 => 'β', + 120632 => 'γ', + 120633 => 'δ', + 120634 => 'ε', + 120635 => 'ζ', + 120636 => 'η', + 120637 => 'θ', + 120638 => 'ι', + 120639 => 'κ', + 120640 => 'λ', + 120641 => 'μ', + 120642 => 'ν', + 120643 => 'ξ', + 120644 => 'ο', + 120645 => 'Ï€', + 120646 => 'Ï', + 120647 => 'σ', + 120648 => 'σ', + 120649 => 'Ï„', + 120650 => 'Ï…', + 120651 => 'φ', + 120652 => 'χ', + 120653 => 'ψ', + 120654 => 'ω', + 120655 => '∂', + 120656 => 'ε', + 120657 => 'θ', + 120658 => 'κ', + 120659 => 'φ', + 120660 => 'Ï', + 120661 => 'Ï€', + 120662 => 'α', + 120663 => 'β', + 120664 => 'γ', + 120665 => 'δ', + 120666 => 'ε', + 120667 => 'ζ', + 120668 => 'η', + 120669 => 'θ', + 120670 => 'ι', + 120671 => 'κ', + 120672 => 'λ', + 120673 => 'μ', + 120674 => 'ν', + 120675 => 'ξ', + 120676 => 'ο', + 120677 => 'Ï€', + 120678 => 'Ï', + 120679 => 'θ', + 120680 => 'σ', + 120681 => 'Ï„', + 120682 => 'Ï…', + 120683 => 'φ', + 120684 => 'χ', + 120685 => 'ψ', + 120686 => 'ω', + 120687 => '∇', + 120688 => 'α', + 120689 => 'β', + 120690 => 'γ', + 120691 => 'δ', + 120692 => 'ε', + 120693 => 'ζ', + 120694 => 'η', + 120695 => 'θ', + 120696 => 'ι', + 120697 => 'κ', + 120698 => 'λ', + 120699 => 'μ', + 120700 => 'ν', + 120701 => 'ξ', + 120702 => 'ο', + 120703 => 'Ï€', + 120704 => 'Ï', + 120705 => 'σ', + 120706 => 'σ', + 120707 => 'Ï„', + 120708 => 'Ï…', + 120709 => 'φ', + 120710 => 'χ', + 120711 => 'ψ', + 120712 => 'ω', + 120713 => '∂', + 120714 => 'ε', + 120715 => 'θ', + 120716 => 'κ', + 120717 => 'φ', + 120718 => 'Ï', + 120719 => 'Ï€', + 120720 => 'α', + 120721 => 'β', + 120722 => 'γ', + 120723 => 'δ', + 120724 => 'ε', + 120725 => 'ζ', + 120726 => 'η', + 120727 => 'θ', + 120728 => 'ι', + 120729 => 'κ', + 120730 => 'λ', + 120731 => 'μ', + 120732 => 'ν', + 120733 => 'ξ', + 120734 => 'ο', + 120735 => 'Ï€', + 120736 => 'Ï', + 120737 => 'θ', + 120738 => 'σ', + 120739 => 'Ï„', + 120740 => 'Ï…', + 120741 => 'φ', + 120742 => 'χ', + 120743 => 'ψ', + 120744 => 'ω', + 120745 => '∇', + 120746 => 'α', + 120747 => 'β', + 120748 => 'γ', + 120749 => 'δ', + 120750 => 'ε', + 120751 => 'ζ', + 120752 => 'η', + 120753 => 'θ', + 120754 => 'ι', + 120755 => 'κ', + 120756 => 'λ', + 120757 => 'μ', + 120758 => 'ν', + 120759 => 'ξ', + 120760 => 'ο', + 120761 => 'Ï€', + 120762 => 'Ï', + 120763 => 'σ', + 120764 => 'σ', + 120765 => 'Ï„', + 120766 => 'Ï…', + 120767 => 'φ', + 120768 => 'χ', + 120769 => 'ψ', + 120770 => 'ω', + 120771 => '∂', + 120772 => 'ε', + 120773 => 'θ', + 120774 => 'κ', + 120775 => 'φ', + 120776 => 'Ï', + 120777 => 'Ï€', + 120778 => 'Ï', + 120779 => 'Ï', + 120782 => '0', + 120783 => '1', + 120784 => '2', + 120785 => '3', + 120786 => '4', + 120787 => '5', + 120788 => '6', + 120789 => '7', + 120790 => '8', + 120791 => '9', + 120792 => '0', + 120793 => '1', + 120794 => '2', + 120795 => '3', + 120796 => '4', + 120797 => '5', + 120798 => '6', + 120799 => '7', + 120800 => '8', + 120801 => '9', + 120802 => '0', + 120803 => '1', + 120804 => '2', + 120805 => '3', + 120806 => '4', + 120807 => '5', + 120808 => '6', + 120809 => '7', + 120810 => '8', + 120811 => '9', + 120812 => '0', + 120813 => '1', + 120814 => '2', + 120815 => '3', + 120816 => '4', + 120817 => '5', + 120818 => '6', + 120819 => '7', + 120820 => '8', + 120821 => '9', + 120822 => '0', + 120823 => '1', + 120824 => '2', + 120825 => '3', + 120826 => '4', + 120827 => '5', + 120828 => '6', + 120829 => '7', + 120830 => '8', + 120831 => '9', + 125184 => '𞤢', + 125185 => '𞤣', + 125186 => '𞤤', + 125187 => '𞤥', + 125188 => '𞤦', + 125189 => '𞤧', + 125190 => '𞤨', + 125191 => '𞤩', + 125192 => '𞤪', + 125193 => '𞤫', + 125194 => '𞤬', + 125195 => '𞤭', + 125196 => '𞤮', + 125197 => '𞤯', + 125198 => '𞤰', + 125199 => '𞤱', + 125200 => '𞤲', + 125201 => '𞤳', + 125202 => '𞤴', + 125203 => '𞤵', + 125204 => '𞤶', + 125205 => '𞤷', + 125206 => '𞤸', + 125207 => '𞤹', + 125208 => '𞤺', + 125209 => '𞤻', + 125210 => '𞤼', + 125211 => '𞤽', + 125212 => '𞤾', + 125213 => '𞤿', + 125214 => '𞥀', + 125215 => 'ðž¥', + 125216 => '𞥂', + 125217 => '𞥃', + 126464 => 'ا', + 126465 => 'ب', + 126466 => 'ج', + 126467 => 'د', + 126469 => 'Ùˆ', + 126470 => 'ز', + 126471 => 'Ø­', + 126472 => 'Ø·', + 126473 => 'ÙŠ', + 126474 => 'Ùƒ', + 126475 => 'Ù„', + 126476 => 'Ù…', + 126477 => 'Ù†', + 126478 => 'س', + 126479 => 'ع', + 126480 => 'Ù', + 126481 => 'ص', + 126482 => 'Ù‚', + 126483 => 'ر', + 126484 => 'Ø´', + 126485 => 'ت', + 126486 => 'Ø«', + 126487 => 'Ø®', + 126488 => 'ذ', + 126489 => 'ض', + 126490 => 'ظ', + 126491 => 'غ', + 126492 => 'Ù®', + 126493 => 'Úº', + 126494 => 'Ú¡', + 126495 => 'Ù¯', + 126497 => 'ب', + 126498 => 'ج', + 126500 => 'Ù‡', + 126503 => 'Ø­', + 126505 => 'ÙŠ', + 126506 => 'Ùƒ', + 126507 => 'Ù„', + 126508 => 'Ù…', + 126509 => 'Ù†', + 126510 => 'س', + 126511 => 'ع', + 126512 => 'Ù', + 126513 => 'ص', + 126514 => 'Ù‚', + 126516 => 'Ø´', + 126517 => 'ت', + 126518 => 'Ø«', + 126519 => 'Ø®', + 126521 => 'ض', + 126523 => 'غ', + 126530 => 'ج', + 126535 => 'Ø­', + 126537 => 'ÙŠ', + 126539 => 'Ù„', + 126541 => 'Ù†', + 126542 => 'س', + 126543 => 'ع', + 126545 => 'ص', + 126546 => 'Ù‚', + 126548 => 'Ø´', + 126551 => 'Ø®', + 126553 => 'ض', + 126555 => 'غ', + 126557 => 'Úº', + 126559 => 'Ù¯', + 126561 => 'ب', + 126562 => 'ج', + 126564 => 'Ù‡', + 126567 => 'Ø­', + 126568 => 'Ø·', + 126569 => 'ÙŠ', + 126570 => 'Ùƒ', + 126572 => 'Ù…', + 126573 => 'Ù†', + 126574 => 'س', + 126575 => 'ع', + 126576 => 'Ù', + 126577 => 'ص', + 126578 => 'Ù‚', + 126580 => 'Ø´', + 126581 => 'ت', + 126582 => 'Ø«', + 126583 => 'Ø®', + 126585 => 'ض', + 126586 => 'ظ', + 126587 => 'غ', + 126588 => 'Ù®', + 126590 => 'Ú¡', + 126592 => 'ا', + 126593 => 'ب', + 126594 => 'ج', + 126595 => 'د', + 126596 => 'Ù‡', + 126597 => 'Ùˆ', + 126598 => 'ز', + 126599 => 'Ø­', + 126600 => 'Ø·', + 126601 => 'ÙŠ', + 126603 => 'Ù„', + 126604 => 'Ù…', + 126605 => 'Ù†', + 126606 => 'س', + 126607 => 'ع', + 126608 => 'Ù', + 126609 => 'ص', + 126610 => 'Ù‚', + 126611 => 'ر', + 126612 => 'Ø´', + 126613 => 'ت', + 126614 => 'Ø«', + 126615 => 'Ø®', + 126616 => 'ذ', + 126617 => 'ض', + 126618 => 'ظ', + 126619 => 'غ', + 126625 => 'ب', + 126626 => 'ج', + 126627 => 'د', + 126629 => 'Ùˆ', + 126630 => 'ز', + 126631 => 'Ø­', + 126632 => 'Ø·', + 126633 => 'ÙŠ', + 126635 => 'Ù„', + 126636 => 'Ù…', + 126637 => 'Ù†', + 126638 => 'س', + 126639 => 'ع', + 126640 => 'Ù', + 126641 => 'ص', + 126642 => 'Ù‚', + 126643 => 'ر', + 126644 => 'Ø´', + 126645 => 'ت', + 126646 => 'Ø«', + 126647 => 'Ø®', + 126648 => 'ذ', + 126649 => 'ض', + 126650 => 'ظ', + 126651 => 'غ', + 127274 => '〔s〕', + 127275 => 'c', + 127276 => 'r', + 127277 => 'cd', + 127278 => 'wz', + 127280 => 'a', + 127281 => 'b', + 127282 => 'c', + 127283 => 'd', + 127284 => 'e', + 127285 => 'f', + 127286 => 'g', + 127287 => 'h', + 127288 => 'i', + 127289 => 'j', + 127290 => 'k', + 127291 => 'l', + 127292 => 'm', + 127293 => 'n', + 127294 => 'o', + 127295 => 'p', + 127296 => 'q', + 127297 => 'r', + 127298 => 's', + 127299 => 't', + 127300 => 'u', + 127301 => 'v', + 127302 => 'w', + 127303 => 'x', + 127304 => 'y', + 127305 => 'z', + 127306 => 'hv', + 127307 => 'mv', + 127308 => 'sd', + 127309 => 'ss', + 127310 => 'ppv', + 127311 => 'wc', + 127338 => 'mc', + 127339 => 'md', + 127340 => 'mr', + 127376 => 'dj', + 127488 => 'ã»ã‹', + 127489 => 'ココ', + 127490 => 'サ', + 127504 => '手', + 127505 => 'å­—', + 127506 => 'åŒ', + 127507 => 'デ', + 127508 => '二', + 127509 => '多', + 127510 => 'è§£', + 127511 => '天', + 127512 => '交', + 127513 => '映', + 127514 => 'ç„¡', + 127515 => 'æ–™', + 127516 => 'å‰', + 127517 => '後', + 127518 => 'å†', + 127519 => 'æ–°', + 127520 => 'åˆ', + 127521 => '終', + 127522 => '生', + 127523 => '販', + 127524 => '声', + 127525 => 'å¹', + 127526 => 'æ¼”', + 127527 => '投', + 127528 => 'æ•', + 127529 => '一', + 127530 => '三', + 127531 => 'éŠ', + 127532 => 'å·¦', + 127533 => '中', + 127534 => 'å³', + 127535 => '指', + 127536 => 'èµ°', + 127537 => '打', + 127538 => 'ç¦', + 127539 => '空', + 127540 => 'åˆ', + 127541 => '満', + 127542 => '有', + 127543 => '月', + 127544 => '申', + 127545 => '割', + 127546 => 'å–¶', + 127547 => 'é…', + 127552 => '〔本〕', + 127553 => '〔三〕', + 127554 => '〔二〕', + 127555 => '〔安〕', + 127556 => '〔点〕', + 127557 => '〔打〕', + 127558 => '〔盗〕', + 127559 => '〔å‹ã€•', + 127560 => '〔敗〕', + 127568 => 'å¾—', + 127569 => 'å¯', + 130032 => '0', + 130033 => '1', + 130034 => '2', + 130035 => '3', + 130036 => '4', + 130037 => '5', + 130038 => '6', + 130039 => '7', + 130040 => '8', + 130041 => '9', + 194560 => '丽', + 194561 => '丸', + 194562 => 'ä¹', + 194563 => 'ð „¢', + 194564 => 'ä½ ', + 194565 => 'ä¾®', + 194566 => 'ä¾»', + 194567 => '倂', + 194568 => 'åº', + 194569 => 'å‚™', + 194570 => '僧', + 194571 => 'åƒ', + 194572 => 'ã’ž', + 194573 => '𠘺', + 194574 => 'å…', + 194575 => 'å…”', + 194576 => 'å…¤', + 194577 => 'å…·', + 194578 => '𠔜', + 194579 => 'ã’¹', + 194580 => 'å…§', + 194581 => 'å†', + 194582 => 'ð •‹', + 194583 => '冗', + 194584 => '冤', + 194585 => '仌', + 194586 => '冬', + 194587 => '况', + 194588 => '𩇟', + 194589 => '凵', + 194590 => '刃', + 194591 => '㓟', + 194592 => '刻', + 194593 => '剆', + 194594 => '割', + 194595 => '剷', + 194596 => '㔕', + 194597 => '勇', + 194598 => '勉', + 194599 => '勤', + 194600 => '勺', + 194601 => '包', + 194602 => '匆', + 194603 => '北', + 194604 => 'å‰', + 194605 => 'å‘', + 194606 => 'åš', + 194607 => 'å³', + 194608 => 'å½', + 194609 => 'å¿', + 194610 => 'å¿', + 194611 => 'å¿', + 194612 => '𠨬', + 194613 => 'ç°', + 194614 => 'åŠ', + 194615 => 'åŸ', + 194616 => 'ð ­£', + 194617 => 'å«', + 194618 => 'å±', + 194619 => 'å†', + 194620 => 'å’ž', + 194621 => 'å¸', + 194622 => '呈', + 194623 => '周', + 194624 => 'å’¢', + 194625 => 'å“¶', + 194626 => 'å”', + 194627 => 'å•“', + 194628 => 'å•£', + 194629 => 'å–„', + 194630 => 'å–„', + 194631 => 'å–™', + 194632 => 'å–«', + 194633 => 'å–³', + 194634 => 'å—‚', + 194635 => '圖', + 194636 => '嘆', + 194637 => '圗', + 194638 => '噑', + 194639 => 'å™´', + 194640 => '切', + 194641 => '壮', + 194642 => '城', + 194643 => '埴', + 194644 => 'å ', + 194645 => 'åž‹', + 194646 => 'å ²', + 194647 => 'å ±', + 194648 => '墬', + 194649 => '𡓤', + 194650 => '売', + 194651 => '壷', + 194652 => '夆', + 194653 => '多', + 194654 => '夢', + 194655 => '奢', + 194656 => '𡚨', + 194657 => '𡛪', + 194658 => '姬', + 194659 => '娛', + 194660 => '娧', + 194661 => '姘', + 194662 => '婦', + 194663 => 'ã›®', + 194665 => '嬈', + 194666 => '嬾', + 194667 => '嬾', + 194668 => '𡧈', + 194669 => '寃', + 194670 => '寘', + 194671 => '寧', + 194672 => '寳', + 194673 => '𡬘', + 194674 => '寿', + 194675 => 'å°†', + 194677 => 'å°¢', + 194678 => 'ãž', + 194679 => 'å± ', + 194680 => 'å±®', + 194681 => 'å³€', + 194682 => 'å²', + 194683 => 'ð¡·¤', + 194684 => '嵃', + 194685 => 'ð¡·¦', + 194686 => 'åµ®', + 194687 => '嵫', + 194688 => 'åµ¼', + 194689 => 'å·¡', + 194690 => 'å·¢', + 194691 => 'ã ¯', + 194692 => 'å·½', + 194693 => '帨', + 194694 => '帽', + 194695 => '幩', + 194696 => 'ã¡¢', + 194697 => '𢆃', + 194698 => '㡼', + 194699 => '庰', + 194700 => '庳', + 194701 => '庶', + 194702 => '廊', + 194703 => '𪎒', + 194704 => '廾', + 194705 => '𢌱', + 194706 => '𢌱', + 194707 => 'èˆ', + 194708 => 'å¼¢', + 194709 => 'å¼¢', + 194710 => '㣇', + 194711 => '𣊸', + 194712 => '𦇚', + 194713 => 'å½¢', + 194714 => '彫', + 194715 => '㣣', + 194716 => '徚', + 194717 => 'å¿', + 194718 => 'å¿—', + 194719 => '忹', + 194720 => 'æ‚', + 194721 => '㤺', + 194722 => '㤜', + 194723 => 'æ‚”', + 194724 => '𢛔', + 194725 => '惇', + 194726 => 'æ…ˆ', + 194727 => 'æ…Œ', + 194728 => 'æ…Ž', + 194729 => 'æ…Œ', + 194730 => 'æ…º', + 194731 => '憎', + 194732 => '憲', + 194733 => '憤', + 194734 => '憯', + 194735 => '懞', + 194736 => '懲', + 194737 => '懶', + 194738 => 'æˆ', + 194739 => '戛', + 194740 => 'æ‰', + 194741 => '抱', + 194742 => 'æ‹”', + 194743 => 'æ', + 194744 => '𢬌', + 194745 => '挽', + 194746 => '拼', + 194747 => 'æ¨', + 194748 => '掃', + 194749 => 'æ¤', + 194750 => '𢯱', + 194751 => 'æ¢', + 194752 => 'æ…', + 194753 => '掩', + 194754 => '㨮', + 194755 => 'æ‘©', + 194756 => '摾', + 194757 => 'æ’', + 194758 => 'æ‘·', + 194759 => '㩬', + 194760 => 'æ•', + 194761 => '敬', + 194762 => '𣀊', + 194763 => 'æ—£', + 194764 => '書', + 194765 => '晉', + 194766 => '㬙', + 194767 => 'æš‘', + 194768 => '㬈', + 194769 => '㫤', + 194770 => '冒', + 194771 => '冕', + 194772 => '最', + 194773 => 'æšœ', + 194774 => 'è‚­', + 194775 => 'ä™', + 194776 => '朗', + 194777 => '望', + 194778 => '朡', + 194779 => 'æž', + 194780 => 'æ“', + 194781 => 'ð£ƒ', + 194782 => 'ã­‰', + 194783 => '柺', + 194784 => 'æž…', + 194785 => 'æ¡’', + 194786 => '梅', + 194787 => '𣑭', + 194788 => '梎', + 194789 => 'æ Ÿ', + 194790 => '椔', + 194791 => 'ã®', + 194792 => '楂', + 194793 => '榣', + 194794 => '槪', + 194795 => '檨', + 194796 => '𣚣', + 194797 => 'æ«›', + 194798 => 'ã°˜', + 194799 => '次', + 194800 => '𣢧', + 194801 => 'æ­”', + 194802 => '㱎', + 194803 => 'æ­²', + 194804 => '殟', + 194805 => '殺', + 194806 => 'æ®»', + 194807 => 'ð£ª', + 194808 => 'ð¡´‹', + 194809 => '𣫺', + 194810 => '汎', + 194811 => '𣲼', + 194812 => '沿', + 194813 => 'æ³', + 194814 => 'æ±§', + 194815 => 'æ´–', + 194816 => 'æ´¾', + 194817 => 'æµ·', + 194818 => 'æµ', + 194819 => '浩', + 194820 => '浸', + 194821 => 'æ¶…', + 194822 => '𣴞', + 194823 => 'æ´´', + 194824 => '港', + 194825 => 'æ¹®', + 194826 => 'ã´³', + 194827 => '滋', + 194828 => '滇', + 194829 => '𣻑', + 194830 => 'æ·¹', + 194831 => 'æ½®', + 194832 => '𣽞', + 194833 => '𣾎', + 194834 => '濆', + 194835 => '瀹', + 194836 => '瀞', + 194837 => '瀛', + 194838 => 'ã¶–', + 194839 => 'çŠ', + 194840 => 'ç½', + 194841 => 'ç·', + 194842 => 'ç‚­', + 194843 => '𠔥', + 194844 => 'ç……', + 194845 => '𤉣', + 194846 => '熜', + 194848 => '爨', + 194849 => '爵', + 194850 => 'ç‰', + 194851 => '𤘈', + 194852 => '犀', + 194853 => '犕', + 194854 => '𤜵', + 194855 => '𤠔', + 194856 => 'çº', + 194857 => '王', + 194858 => '㺬', + 194859 => '玥', + 194860 => '㺸', + 194861 => '㺸', + 194862 => '瑇', + 194863 => '瑜', + 194864 => '瑱', + 194865 => 'ç’…', + 194866 => '瓊', + 194867 => 'ã¼›', + 194868 => '甤', + 194869 => '𤰶', + 194870 => '甾', + 194871 => '𤲒', + 194872 => 'ç•°', + 194873 => '𢆟', + 194874 => 'ç˜', + 194875 => '𤾡', + 194876 => '𤾸', + 194877 => 'ð¥„', + 194878 => '㿼', + 194879 => '䀈', + 194880 => 'ç›´', + 194881 => '𥃳', + 194882 => '𥃲', + 194883 => '𥄙', + 194884 => '𥄳', + 194885 => '眞', + 194886 => '真', + 194887 => '真', + 194888 => 'çŠ', + 194889 => '䀹', + 194890 => 'çž‹', + 194891 => 'ä†', + 194892 => 'ä‚–', + 194893 => 'ð¥', + 194894 => '硎', + 194895 => '碌', + 194896 => '磌', + 194897 => '䃣', + 194898 => '𥘦', + 194899 => '祖', + 194900 => '𥚚', + 194901 => '𥛅', + 194902 => 'ç¦', + 194903 => 'ç§«', + 194904 => '䄯', + 194905 => 'ç©€', + 194906 => '穊', + 194907 => 'ç©', + 194908 => '𥥼', + 194909 => '𥪧', + 194910 => '𥪧', + 194912 => '䈂', + 194913 => '𥮫', + 194914 => '篆', + 194915 => '築', + 194916 => '䈧', + 194917 => '𥲀', + 194918 => 'ç³’', + 194919 => '䊠', + 194920 => '糨', + 194921 => 'ç³£', + 194922 => 'ç´€', + 194923 => '𥾆', + 194924 => 'çµ£', + 194925 => 'äŒ', + 194926 => 'ç·‡', + 194927 => '縂', + 194928 => 'ç¹…', + 194929 => '䌴', + 194930 => '𦈨', + 194931 => '𦉇', + 194932 => 'ä™', + 194933 => '𦋙', + 194934 => '罺', + 194935 => '𦌾', + 194936 => '羕', + 194937 => '翺', + 194938 => '者', + 194939 => '𦓚', + 194940 => '𦔣', + 194941 => 'è ', + 194942 => '𦖨', + 194943 => 'è°', + 194944 => 'ð£Ÿ', + 194945 => 'ä•', + 194946 => '育', + 194947 => '脃', + 194948 => 'ä‹', + 194949 => '脾', + 194950 => '媵', + 194951 => '𦞧', + 194952 => '𦞵', + 194953 => '𣎓', + 194954 => '𣎜', + 194955 => 'èˆ', + 194956 => '舄', + 194957 => '辞', + 194958 => 'ä‘«', + 194959 => '芑', + 194960 => '芋', + 194961 => 'èŠ', + 194962 => '劳', + 194963 => '花', + 194964 => '芳', + 194965 => '芽', + 194966 => '苦', + 194967 => '𦬼', + 194968 => 'è‹¥', + 194969 => 'èŒ', + 194970 => 'è£', + 194971 => '莭', + 194972 => '茣', + 194973 => '莽', + 194974 => 'è§', + 194975 => 'è‘—', + 194976 => 'è“', + 194977 => 'èŠ', + 194978 => 'èŒ', + 194979 => 'èœ', + 194980 => '𦰶', + 194981 => '𦵫', + 194982 => '𦳕', + 194983 => '䔫', + 194984 => '蓱', + 194985 => '蓳', + 194986 => 'è”–', + 194987 => 'ð§Š', + 194988 => '蕤', + 194989 => '𦼬', + 194990 => 'ä•', + 194991 => 'ä•¡', + 194992 => '𦾱', + 194993 => '𧃒', + 194994 => 'ä•«', + 194995 => 'è™', + 194996 => '虜', + 194997 => 'è™§', + 194998 => '虩', + 194999 => 'èš©', + 195000 => '蚈', + 195001 => '蜎', + 195002 => '蛢', + 195003 => 'è¹', + 195004 => '蜨', + 195005 => 'è«', + 195006 => '螆', + 195008 => '蟡', + 195009 => 'è ', + 195010 => 'ä—¹', + 195011 => 'è¡ ', + 195012 => 'è¡£', + 195013 => 'ð§™§', + 195014 => '裗', + 195015 => '裞', + 195016 => '䘵', + 195017 => '裺', + 195018 => 'ã’»', + 195019 => 'ð§¢®', + 195020 => '𧥦', + 195021 => 'äš¾', + 195022 => '䛇', + 195023 => '誠', + 195024 => 'è«­', + 195025 => '變', + 195026 => '豕', + 195027 => '𧲨', + 195028 => '貫', + 195029 => 'è³', + 195030 => 'è´›', + 195031 => 'èµ·', + 195032 => '𧼯', + 195033 => 'ð  „', + 195034 => 'è·‹', + 195035 => 'è¶¼', + 195036 => 'è·°', + 195037 => '𠣞', + 195038 => 'è»”', + 195039 => '輸', + 195040 => '𨗒', + 195041 => '𨗭', + 195042 => 'é‚”', + 195043 => '郱', + 195044 => 'é„‘', + 195045 => '𨜮', + 195046 => 'é„›', + 195047 => '鈸', + 195048 => 'é‹—', + 195049 => '鋘', + 195050 => '鉼', + 195051 => 'é¹', + 195052 => 'é•', + 195053 => '𨯺', + 195054 => 'é–‹', + 195055 => '䦕', + 195056 => 'é–·', + 195057 => '𨵷', + 195058 => '䧦', + 195059 => '雃', + 195060 => 'å¶²', + 195061 => '霣', + 195062 => 'ð©……', + 195063 => '𩈚', + 195064 => 'ä©®', + 195065 => 'ä©¶', + 195066 => '韠', + 195067 => 'ð©Š', + 195068 => '䪲', + 195069 => 'ð©’–', + 195070 => 'é ‹', + 195071 => 'é ‹', + 195072 => 'é ©', + 195073 => 'ð©–¶', + 195074 => '飢', + 195075 => '䬳', + 195076 => '餩', + 195077 => '馧', + 195078 => 'é§‚', + 195079 => 'é§¾', + 195080 => '䯎', + 195081 => '𩬰', + 195082 => '鬒', + 195083 => 'é±€', + 195084 => 'é³½', + 195085 => '䳎', + 195086 => 'ä³­', + 195087 => 'éµ§', + 195088 => '𪃎', + 195089 => '䳸', + 195090 => '𪄅', + 195091 => '𪈎', + 195092 => '𪊑', + 195093 => '麻', + 195094 => 'äµ–', + 195095 => '黹', + 195096 => '黾', + 195097 => 'é¼…', + 195098 => 'é¼', + 195099 => 'é¼–', + 195100 => 'é¼»', + 195101 => '𪘀', +); diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php new file mode 100644 index 0000000..1958e37 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php @@ -0,0 +1,65 @@ + 9, + 2509 => 9, + 2637 => 9, + 2765 => 9, + 2893 => 9, + 3021 => 9, + 3149 => 9, + 3277 => 9, + 3387 => 9, + 3388 => 9, + 3405 => 9, + 3530 => 9, + 3642 => 9, + 3770 => 9, + 3972 => 9, + 4153 => 9, + 4154 => 9, + 5908 => 9, + 5940 => 9, + 6098 => 9, + 6752 => 9, + 6980 => 9, + 7082 => 9, + 7083 => 9, + 7154 => 9, + 7155 => 9, + 11647 => 9, + 43014 => 9, + 43052 => 9, + 43204 => 9, + 43347 => 9, + 43456 => 9, + 43766 => 9, + 44013 => 9, + 68159 => 9, + 69702 => 9, + 69759 => 9, + 69817 => 9, + 69939 => 9, + 69940 => 9, + 70080 => 9, + 70197 => 9, + 70378 => 9, + 70477 => 9, + 70722 => 9, + 70850 => 9, + 71103 => 9, + 71231 => 9, + 71350 => 9, + 71467 => 9, + 71737 => 9, + 71997 => 9, + 71998 => 9, + 72160 => 9, + 72244 => 9, + 72263 => 9, + 72345 => 9, + 72767 => 9, + 73028 => 9, + 73029 => 9, + 73111 => 9, +); diff --git a/vendor/symfony/polyfill-intl-idn/bootstrap.php b/vendor/symfony/polyfill-intl-idn/bootstrap.php new file mode 100644 index 0000000..57c7835 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/bootstrap.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_2003')) { + define('INTL_IDNA_VARIANT_2003', 0); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (\PHP_VERSION_ID < 70400) { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} else { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} diff --git a/vendor/symfony/polyfill-intl-idn/bootstrap80.php b/vendor/symfony/polyfill-intl-idn/bootstrap80.php new file mode 100644 index 0000000..a62c2d6 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/bootstrap80.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (!function_exists('idn_to_ascii')) { + function idn_to_ascii(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_ascii((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} +if (!function_exists('idn_to_utf8')) { + function idn_to_utf8(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_utf8((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} diff --git a/vendor/symfony/polyfill-intl-idn/composer.json b/vendor/symfony/polyfill-intl-idn/composer.json new file mode 100644 index 0000000..760debc --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-intl-idn", + "type": "library", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "idn"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Idn\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/LICENSE b/vendor/symfony/polyfill-intl-normalizer/LICENSE new file mode 100644 index 0000000..6e3afce --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-intl-normalizer/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php new file mode 100644 index 0000000..81704ab --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Normalizer; + +/** + * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension. + * + * It has been validated with Unicode 6.3 Normalization Conformance Test. + * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations. + * + * @author Nicolas Grekas + * + * @internal + */ +class Normalizer +{ + public const FORM_D = \Normalizer::FORM_D; + public const FORM_KD = \Normalizer::FORM_KD; + public const FORM_C = \Normalizer::FORM_C; + public const FORM_KC = \Normalizer::FORM_KC; + public const NFD = \Normalizer::NFD; + public const NFKD = \Normalizer::NFKD; + public const NFC = \Normalizer::NFC; + public const NFKC = \Normalizer::NFKC; + + private static $C; + private static $D; + private static $KD; + private static $cC; + private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + public static function isNormalized(string $s, int $form = self::FORM_C) + { + if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { + return false; + } + if (!isset($s[strspn($s, self::$ASCII)])) { + return true; + } + if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { + return true; + } + + return self::normalize($s, $form) === $s; + } + + public static function normalize(string $s, int $form = self::FORM_C) + { + if (!preg_match('//u', $s)) { + return false; + } + + switch ($form) { + case self::NFC: $C = true; $K = false; break; + case self::NFD: $C = false; $K = false; break; + case self::NFKC: $C = true; $K = true; break; + case self::NFKD: $C = false; $K = true; break; + default: + if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) { + return $s; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); + } + + if ('' === $s) { + return ''; + } + + if ($K && null === self::$KD) { + self::$KD = self::getData('compatibilityDecomposition'); + } + + if (null === self::$D) { + self::$D = self::getData('canonicalDecomposition'); + self::$cC = self::getData('combiningClass'); + } + + if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) { + mb_internal_encoding('8bit'); + } + + $r = self::decompose($s, $K); + + if ($C) { + if (null === self::$C) { + self::$C = self::getData('canonicalComposition'); + } + + $r = self::recompose($r); + } + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return $r; + } + + private static function recompose($s) + { + $ASCII = self::$ASCII; + $compMap = self::$C; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + + $result = $tail = ''; + + $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"]; + $len = \strlen($s); + + $lastUchr = substr($s, 0, $i); + $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + if ($j = strspn($s, $ASCII, $i + 1)) { + $lastUchr .= substr($s, $i, $j); + $i += $j; + } + + $result .= $lastUchr; + $lastUchr = $s[$i]; + $lastUcls = 0; + ++$i; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + + if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr + || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr + || $lastUcls) { + // Table lookup and combining chars composition + + $ucls = $combClass[$uchr] ?? 0; + + if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { + $lastUchr = $compMap[$lastUchr.$uchr]; + } elseif ($lastUcls = $ucls) { + $tail .= $uchr; + } else { + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + $result .= $lastUchr; + $lastUchr = $uchr; + } + } else { + // Hangul chars + + $L = \ord($lastUchr[2]) - 0x80; + $V = \ord($uchr[2]) - 0xA1; + $T = 0; + + $uchr = substr($s, $i + $ulen, 3); + + if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") { + $T = \ord($uchr[2]) - 0xA7; + 0 > $T && $T += 0x40; + $ulen += 3; + } + + $L = 0xAC00 + ($L * 21 + $V) * 28 + $T; + $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F); + } + + $i += $ulen; + } + + return $result.$lastUchr.$tail; + } + + private static function decompose($s, $c) + { + $result = ''; + + $ASCII = self::$ASCII; + $decompMap = self::$D; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + if ($c) { + $compatMap = self::$KD; + } + + $c = []; + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $j = 1 + strspn($s, $ASCII, $i + 1); + $result .= substr($s, $i, $j); + $i += $j; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { + // Table lookup + + if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) { + $uchr = $j; + + $j = \strlen($uchr); + $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"]; + + if ($ulen != $j) { + // Put trailing chars in $s + + $j -= $ulen; + $i -= $j; + + if (0 > $i) { + $s = str_repeat(' ', -$i).$s; + $len -= $i; + $i = 0; + } + + while ($j--) { + $s[$i + $j] = $uchr[$ulen + $j]; + } + + $uchr = substr($uchr, 0, $ulen); + } + } + if (isset($combClass[$uchr])) { + // Combining chars, for sorting + + if (!isset($c[$combClass[$uchr]])) { + $c[$combClass[$uchr]] = ''; + } + $c[$combClass[$uchr]] .= $uchr; + continue; + } + } else { + // Hangul chars + + $uchr = unpack('C*', $uchr); + $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80; + + $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588)) + ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28)); + + if ($j %= 28) { + $uchr .= $j < 25 + ? ("\xE1\x86".\chr(0xA7 + $j)) + : ("\xE1\x87".\chr(0x67 + $j)); + } + } + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $result .= $uchr; + } + + if ($c) { + ksort($c); + $result .= implode('', $c); + } + + return $result; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/README.md b/vendor/symfony/polyfill-intl-normalizer/README.md new file mode 100644 index 0000000..b9b762e --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/README.md @@ -0,0 +1,14 @@ +Symfony Polyfill / Intl: Normalizer +=================================== + +This component provides a fallback implementation for the +[`Normalizer`](https://php.net/Normalizer) class provided +by the [Intl](https://php.net/intl) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php new file mode 100644 index 0000000..0fdfc89 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php @@ -0,0 +1,17 @@ + 'À', + 'AÌ' => 'Ã', + 'AÌ‚' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'AÌŠ' => 'Ã…', + 'Ç' => 'Ç', + 'EÌ€' => 'È', + 'EÌ' => 'É', + 'EÌ‚' => 'Ê', + 'Ë' => 'Ë', + 'IÌ€' => 'ÃŒ', + 'IÌ' => 'Ã', + 'IÌ‚' => 'ÃŽ', + 'Ï' => 'Ã', + 'Ñ' => 'Ñ', + 'OÌ€' => 'Ã’', + 'OÌ' => 'Ó', + 'OÌ‚' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'UÌ€' => 'Ù', + 'UÌ' => 'Ú', + 'UÌ‚' => 'Û', + 'Ü' => 'Ü', + 'YÌ' => 'Ã', + 'aÌ€' => 'à', + 'aÌ' => 'á', + 'aÌ‚' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'aÌŠ' => 'Ã¥', + 'ç' => 'ç', + 'eÌ€' => 'è', + 'eÌ' => 'é', + 'eÌ‚' => 'ê', + 'ë' => 'ë', + 'iÌ€' => 'ì', + 'iÌ' => 'í', + 'iÌ‚' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'oÌ€' => 'ò', + 'oÌ' => 'ó', + 'oÌ‚' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'uÌ€' => 'ù', + 'uÌ' => 'ú', + 'uÌ‚' => 'û', + 'ü' => 'ü', + 'yÌ' => 'ý', + 'ÿ' => 'ÿ', + 'AÌ„' => 'Ä€', + 'aÌ„' => 'Ä', + 'Ă' => 'Ä‚', + 'ă' => 'ă', + 'Ą' => 'Ä„', + 'ą' => 'Ä…', + 'CÌ' => 'Ć', + 'cÌ' => 'ć', + 'CÌ‚' => 'Ĉ', + 'cÌ‚' => 'ĉ', + 'Ċ' => 'ÄŠ', + 'ċ' => 'Ä‹', + 'CÌŒ' => 'ÄŒ', + 'cÌŒ' => 'Ä', + 'DÌŒ' => 'ÄŽ', + 'dÌŒ' => 'Ä', + 'EÌ„' => 'Ä’', + 'eÌ„' => 'Ä“', + 'Ĕ' => 'Ä”', + 'ĕ' => 'Ä•', + 'Ė' => 'Ä–', + 'ė' => 'Ä—', + 'Ę' => 'Ę', + 'ę' => 'Ä™', + 'EÌŒ' => 'Äš', + 'eÌŒ' => 'Ä›', + 'GÌ‚' => 'Äœ', + 'gÌ‚' => 'Ä', + 'Ğ' => 'Äž', + 'ğ' => 'ÄŸ', + 'Ġ' => 'Ä ', + 'ġ' => 'Ä¡', + 'Ģ' => 'Ä¢', + 'ģ' => 'Ä£', + 'HÌ‚' => 'Ĥ', + 'hÌ‚' => 'Ä¥', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'Ä©', + 'IÌ„' => 'Ī', + 'iÌ„' => 'Ä«', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'Ä­', + 'Į' => 'Ä®', + 'į' => 'į', + 'İ' => 'İ', + 'JÌ‚' => 'Ä´', + 'jÌ‚' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'Ä·', + 'LÌ' => 'Ĺ', + 'lÌ' => 'ĺ', + 'Ļ' => 'Ä»', + 'ļ' => 'ļ', + 'LÌŒ' => 'Ľ', + 'lÌŒ' => 'ľ', + 'NÌ' => 'Ń', + 'nÌ' => 'Å„', + 'Ņ' => 'Å…', + 'ņ' => 'ņ', + 'NÌŒ' => 'Ň', + 'nÌŒ' => 'ň', + 'OÌ„' => 'ÅŒ', + 'oÌ„' => 'Å', + 'Ŏ' => 'ÅŽ', + 'ŏ' => 'Å', + 'OÌ‹' => 'Å', + 'oÌ‹' => 'Å‘', + 'RÌ' => 'Å”', + 'rÌ' => 'Å•', + 'Ŗ' => 'Å–', + 'ŗ' => 'Å—', + 'RÌŒ' => 'Ř', + 'rÌŒ' => 'Å™', + 'SÌ' => 'Åš', + 'sÌ' => 'Å›', + 'SÌ‚' => 'Åœ', + 'sÌ‚' => 'Å', + 'Ş' => 'Åž', + 'ş' => 'ÅŸ', + 'SÌŒ' => 'Å ', + 'sÌŒ' => 'Å¡', + 'Ţ' => 'Å¢', + 'ţ' => 'Å£', + 'TÌŒ' => 'Ť', + 'tÌŒ' => 'Å¥', + 'Ũ' => 'Ũ', + 'ũ' => 'Å©', + 'UÌ„' => 'Ū', + 'uÌ„' => 'Å«', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'Å­', + 'UÌŠ' => 'Å®', + 'uÌŠ' => 'ů', + 'UÌ‹' => 'Ű', + 'uÌ‹' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'WÌ‚' => 'Å´', + 'wÌ‚' => 'ŵ', + 'YÌ‚' => 'Ŷ', + 'yÌ‚' => 'Å·', + 'Ÿ' => 'Ÿ', + 'ZÌ' => 'Ź', + 'zÌ' => 'ź', + 'Ż' => 'Å»', + 'ż' => 'ż', + 'ZÌŒ' => 'Ž', + 'zÌŒ' => 'ž', + 'OÌ›' => 'Æ ', + 'oÌ›' => 'Æ¡', + 'UÌ›' => 'Ư', + 'uÌ›' => 'ư', + 'AÌŒ' => 'Ç', + 'aÌŒ' => 'ÇŽ', + 'IÌŒ' => 'Ç', + 'iÌŒ' => 'Ç', + 'OÌŒ' => 'Ç‘', + 'oÌŒ' => 'Ç’', + 'UÌŒ' => 'Ç“', + 'uÌŒ' => 'Ç”', + 'Ǖ' => 'Ç•', + 'ǖ' => 'Ç–', + 'ÜÌ' => 'Ç—', + 'üÌ' => 'ǘ', + 'Ǚ' => 'Ç™', + 'ǚ' => 'Çš', + 'Ǜ' => 'Ç›', + 'ǜ' => 'Çœ', + 'Ǟ' => 'Çž', + 'ǟ' => 'ÇŸ', + 'Ǡ' => 'Ç ', + 'ǡ' => 'Ç¡', + 'Ǣ' => 'Ç¢', + 'ǣ' => 'Ç£', + 'GÌŒ' => 'Ǧ', + 'gÌŒ' => 'ǧ', + 'KÌŒ' => 'Ǩ', + 'kÌŒ' => 'Ç©', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'Ç«', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'Ç­', + 'Æ·ÌŒ' => 'Ç®', + 'Ê’ÌŒ' => 'ǯ', + 'jÌŒ' => 'ǰ', + 'GÌ' => 'Ç´', + 'gÌ' => 'ǵ', + 'NÌ€' => 'Ǹ', + 'nÌ€' => 'ǹ', + 'Ã…Ì' => 'Ǻ', + 'Ã¥Ì' => 'Ç»', + 'ÆÌ' => 'Ǽ', + 'æÌ' => 'ǽ', + 'ØÌ' => 'Ǿ', + 'øÌ' => 'Ç¿', + 'AÌ' => 'È€', + 'aÌ' => 'È', + 'AÌ‘' => 'È‚', + 'aÌ‘' => 'ȃ', + 'EÌ' => 'È„', + 'eÌ' => 'È…', + 'EÌ‘' => 'Ȇ', + 'eÌ‘' => 'ȇ', + 'IÌ' => 'Ȉ', + 'iÌ' => 'ȉ', + 'IÌ‘' => 'ÈŠ', + 'iÌ‘' => 'È‹', + 'OÌ' => 'ÈŒ', + 'oÌ' => 'È', + 'OÌ‘' => 'ÈŽ', + 'oÌ‘' => 'È', + 'RÌ' => 'È', + 'rÌ' => 'È‘', + 'RÌ‘' => 'È’', + 'rÌ‘' => 'È“', + 'UÌ' => 'È”', + 'uÌ' => 'È•', + 'UÌ‘' => 'È–', + 'uÌ‘' => 'È—', + 'Ș' => 'Ș', + 'ș' => 'È™', + 'Ț' => 'Èš', + 'ț' => 'È›', + 'HÌŒ' => 'Èž', + 'hÌŒ' => 'ÈŸ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'È©', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'È«', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'È­', + 'Ȯ' => 'È®', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'YÌ„' => 'Ȳ', + 'yÌ„' => 'ȳ', + '¨Ì' => 'Î…', + 'ΑÌ' => 'Ά', + 'ΕÌ' => 'Έ', + 'ΗÌ' => 'Ή', + 'ΙÌ' => 'Ί', + 'ΟÌ' => 'ÎŒ', + 'Î¥Ì' => 'ÎŽ', + 'ΩÌ' => 'Î', + 'ÏŠÌ' => 'Î', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'αÌ' => 'ά', + 'εÌ' => 'έ', + 'ηÌ' => 'ή', + 'ιÌ' => 'ί', + 'Ï‹Ì' => 'ΰ', + 'ϊ' => 'ÏŠ', + 'ϋ' => 'Ï‹', + 'οÌ' => 'ÏŒ', + 'Ï…Ì' => 'Ï', + 'ωÌ' => 'ÏŽ', + 'Ï’Ì' => 'Ï“', + 'ϔ' => 'Ï”', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ð', + 'ГÌ' => 'Ѓ', + 'Ї' => 'Ї', + 'КÌ' => 'ÐŒ', + 'Ѝ' => 'Ð', + 'Ў' => 'ÐŽ', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'Ñ', + 'ё' => 'Ñ‘', + 'гÌ' => 'Ñ“', + 'ї' => 'Ñ—', + 'кÌ' => 'Ñœ', + 'ѝ' => 'Ñ', + 'ў' => 'Ñž', + 'Ñ´Ì' => 'Ѷ', + 'ѵÌ' => 'Ñ·', + 'Ӂ' => 'Ó', + 'ӂ' => 'Ó‚', + 'Ð̆' => 'Ó', + 'ӑ' => 'Ó‘', + 'Ð̈' => 'Ó’', + 'ӓ' => 'Ó“', + 'Ӗ' => 'Ó–', + 'ӗ' => 'Ó—', + 'Ӛ' => 'Óš', + 'ӛ' => 'Ó›', + 'Ӝ' => 'Óœ', + 'ӝ' => 'Ó', + 'Ӟ' => 'Óž', + 'ӟ' => 'ÓŸ', + 'Ӣ' => 'Ó¢', + 'ӣ' => 'Ó£', + 'Ӥ' => 'Ó¤', + 'ӥ' => 'Ó¥', + 'Ӧ' => 'Ó¦', + 'ӧ' => 'Ó§', + 'Ӫ' => 'Óª', + 'ӫ' => 'Ó«', + 'Ӭ' => 'Ó¬', + 'Ñ̈' => 'Ó­', + 'Ӯ' => 'Ó®', + 'ӯ' => 'Ó¯', + 'Ӱ' => 'Ó°', + 'ӱ' => 'Ó±', + 'Ӳ' => 'Ó²', + 'ӳ' => 'Ó³', + 'Ӵ' => 'Ó´', + 'ӵ' => 'Óµ', + 'Ӹ' => 'Ó¸', + 'ӹ' => 'Ó¹', + 'آ' => 'Ø¢', + 'أ' => 'Ø£', + 'ÙˆÙ”' => 'ؤ', + 'إ' => 'Ø¥', + 'ÙŠÙ”' => 'ئ', + 'Û•Ù”' => 'Û€', + 'ÛÙ”' => 'Û‚', + 'Û’Ù”' => 'Û“', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'ো' => 'à§‹', + 'ৌ' => 'à§Œ', + 'ୈ' => 'à­ˆ', + 'ୋ' => 'à­‹', + 'ୌ' => 'à­Œ', + 'ஔ' => 'à®”', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'à³€', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'à·š', + 'à·™à·' => 'à·œ', + 'ෝ' => 'à·', + 'ෞ' => 'à·ž', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'á¬á¬µ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'á­€', + 'ᭁ' => 'á­', + 'ᭃ' => 'á­ƒ', + 'AÌ¥' => 'Ḁ', + 'aÌ¥' => 'á¸', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'BÌ£' => 'Ḅ', + 'bÌ£' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'ÇÌ' => 'Ḉ', + 'çÌ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'DÌ£' => 'Ḍ', + 'dÌ£' => 'á¸', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'á¸', + 'Ḑ' => 'á¸', + 'ḑ' => 'ḑ', + 'DÌ­' => 'Ḓ', + 'dÌ­' => 'ḓ', + 'Ä’Ì€' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ä’Ì' => 'Ḗ', + 'Ä“Ì' => 'ḗ', + 'EÌ­' => 'Ḙ', + 'eÌ­' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'á¸', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'GÌ„' => 'Ḡ', + 'gÌ„' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'HÌ£' => 'Ḥ', + 'hÌ£' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'HÌ®' => 'Ḫ', + 'hÌ®' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'ÃÌ' => 'Ḯ', + 'ïÌ' => 'ḯ', + 'KÌ' => 'Ḱ', + 'kÌ' => 'ḱ', + 'KÌ£' => 'Ḳ', + 'kÌ£' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'LÌ£' => 'Ḷ', + 'lÌ£' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'LÌ­' => 'Ḽ', + 'lÌ­' => 'ḽ', + 'MÌ' => 'Ḿ', + 'mÌ' => 'ḿ', + 'Ṁ' => 'á¹€', + 'ṁ' => 'á¹', + 'MÌ£' => 'Ṃ', + 'mÌ£' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'á¹…', + 'NÌ£' => 'Ṇ', + 'nÌ£' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'NÌ­' => 'Ṋ', + 'nÌ­' => 'ṋ', + 'ÕÌ' => 'Ṍ', + 'õÌ' => 'á¹', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'á¹', + 'Ṑ' => 'á¹', + 'ÅÌ€' => 'ṑ', + 'ÅŒÌ' => 'á¹’', + 'ÅÌ' => 'ṓ', + 'PÌ' => 'á¹”', + 'pÌ' => 'ṕ', + 'Ṗ' => 'á¹–', + 'ṗ' => 'á¹—', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'á¹™', + 'RÌ£' => 'Ṛ', + 'rÌ£' => 'á¹›', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'á¹', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'á¹ ', + 'ṡ' => 'ṡ', + 'SÌ£' => 'á¹¢', + 'sÌ£' => 'á¹£', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'á¹¥', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'á¹§', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'TÌ£' => 'Ṭ', + 'tÌ£' => 'á¹­', + 'Ṯ' => 'á¹®', + 'ṯ' => 'ṯ', + 'TÌ­' => 'á¹°', + 'tÌ­' => 'á¹±', + 'Ṳ' => 'á¹²', + 'ṳ' => 'á¹³', + 'Ṵ' => 'á¹´', + 'ṵ' => 'á¹µ', + 'UÌ­' => 'á¹¶', + 'uÌ­' => 'á¹·', + 'ŨÌ' => 'Ṹ', + 'Å©Ì' => 'á¹¹', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'á¹»', + 'Ṽ' => 'á¹¼', + 'ṽ' => 'á¹½', + 'VÌ£' => 'á¹¾', + 'vÌ£' => 'ṿ', + 'WÌ€' => 'Ẁ', + 'wÌ€' => 'áº', + 'WÌ' => 'Ẃ', + 'wÌ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'WÌ£' => 'Ẉ', + 'wÌ£' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'áº', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'áº', + 'ZÌ‚' => 'áº', + 'zÌ‚' => 'ẑ', + 'ZÌ£' => 'Ẓ', + 'zÌ£' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'wÌŠ' => 'ẘ', + 'yÌŠ' => 'ẙ', + 'ẛ' => 'ẛ', + 'AÌ£' => 'Ạ', + 'aÌ£' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'ÂÌ' => 'Ấ', + 'âÌ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ä‚Ì' => 'Ắ', + 'ăÌ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'EÌ£' => 'Ẹ', + 'eÌ£' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'ÊÌ' => 'Ế', + 'êÌ' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'á»', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'á»…', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'IÌ£' => 'Ị', + 'iÌ£' => 'ị', + 'OÌ£' => 'Ọ', + 'oÌ£' => 'á»', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'á»', + 'ÔÌ' => 'á»', + 'ôÌ' => 'ố', + 'Ồ' => 'á»’', + 'ồ' => 'ồ', + 'Ổ' => 'á»”', + 'ổ' => 'ổ', + 'Ỗ' => 'á»–', + 'ỗ' => 'á»—', + 'Ộ' => 'Ộ', + 'á»Ì‚' => 'á»™', + 'Æ Ì' => 'Ớ', + 'Æ¡Ì' => 'á»›', + 'Ờ' => 'Ờ', + 'ờ' => 'á»', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'á» ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'UÌ£' => 'Ụ', + 'uÌ£' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'á»§', + 'ƯÌ' => 'Ứ', + 'ưÌ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'á»­', + 'Ữ' => 'á»®', + 'ữ' => 'ữ', + 'Ự' => 'á»°', + 'ự' => 'á»±', + 'YÌ€' => 'Ỳ', + 'yÌ€' => 'ỳ', + 'YÌ£' => 'á»´', + 'yÌ£' => 'ỵ', + 'Ỷ' => 'á»¶', + 'ỷ' => 'á»·', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'á¼€', + 'ἁ' => 'á¼', + 'ἂ' => 'ἂ', + 'á¼Ì€' => 'ἃ', + 'á¼€Ì' => 'ἄ', + 'á¼Ì' => 'á¼…', + 'ἆ' => 'ἆ', + 'á¼Í‚' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'ἈÌ' => 'Ἄ', + 'ἉÌ' => 'á¼', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'á¼', + 'ἐ' => 'á¼', + 'ἑ' => 'ἑ', + 'á¼Ì€' => 'á¼’', + 'ἓ' => 'ἓ', + 'á¼Ì' => 'á¼”', + 'ἑÌ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'á¼™', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'á¼›', + 'ἘÌ' => 'Ἔ', + 'á¼™Ì' => 'á¼', + 'ἠ' => 'á¼ ', + 'ἡ' => 'ἡ', + 'ἢ' => 'á¼¢', + 'ἣ' => 'á¼£', + 'á¼ Ì' => 'ἤ', + 'ἡÌ' => 'á¼¥', + 'á¼ Í‚' => 'ἦ', + 'ἧ' => 'á¼§', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'ἨÌ' => 'Ἤ', + 'ἩÌ' => 'á¼­', + 'Ἦ' => 'á¼®', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'á¼°', + 'ἱ' => 'á¼±', + 'á¼°Ì€' => 'á¼²', + 'ἳ' => 'á¼³', + 'á¼°Ì' => 'á¼´', + 'á¼±Ì' => 'á¼µ', + 'á¼°Í‚' => 'á¼¶', + 'ἷ' => 'á¼·', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'á¼¹', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'á¼»', + 'ἸÌ' => 'á¼¼', + 'á¼¹Ì' => 'á¼½', + 'Ἶ' => 'á¼¾', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'á½€', + 'ὁ' => 'á½', + 'ὂ' => 'ὂ', + 'á½Ì€' => 'ὃ', + 'á½€Ì' => 'ὄ', + 'á½Ì' => 'á½…', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'ὈÌ' => 'Ὄ', + 'ὉÌ' => 'á½', + 'Ï…Ì“' => 'á½', + 'Ï…Ì”' => 'ὑ', + 'á½Ì€' => 'á½’', + 'ὓ' => 'ὓ', + 'á½Ì' => 'á½”', + 'ὑÌ' => 'ὕ', + 'á½Í‚' => 'á½–', + 'ὗ' => 'á½—', + 'Ὑ' => 'á½™', + 'Ὓ' => 'á½›', + 'á½™Ì' => 'á½', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'á½ ', + 'ὡ' => 'ὡ', + 'ὢ' => 'á½¢', + 'ὣ' => 'á½£', + 'á½ Ì' => 'ὤ', + 'ὡÌ' => 'á½¥', + 'á½ Í‚' => 'ὦ', + 'ὧ' => 'á½§', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'ὨÌ' => 'Ὤ', + 'ὩÌ' => 'á½­', + 'Ὦ' => 'á½®', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'á½°', + 'ὲ' => 'á½²', + 'ὴ' => 'á½´', + 'ὶ' => 'á½¶', + 'ὸ' => 'ὸ', + 'Ï…Ì€' => 'ὺ', + 'ὼ' => 'á½¼', + 'ᾀ' => 'á¾€', + 'á¼Í…' => 'á¾', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'á¼…Í…' => 'á¾…', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'á¼Í…' => 'á¾', + 'ᾎ' => 'ᾎ', + 'á¼Í…' => 'á¾', + 'á¼ Í…' => 'á¾', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'á¾’', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'á¾”', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'á¾–', + 'á¼§Í…' => 'á¾—', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'á¾™', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'á¾›', + 'ᾜ' => 'ᾜ', + 'á¼­Í…' => 'á¾', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'á½ Í…' => 'á¾ ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'á¾¢', + 'ᾣ' => 'á¾£', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'á¾¥', + 'ᾦ' => 'ᾦ', + 'á½§Í…' => 'á¾§', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'á½­Í…' => 'á¾­', + 'ᾮ' => 'á¾®', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'á¾°', + 'ᾱ' => 'á¾±', + 'á½°Í…' => 'á¾²', + 'ᾳ' => 'á¾³', + 'ᾴ' => 'á¾´', + 'ᾶ' => 'á¾¶', + 'á¾¶Í…' => 'á¾·', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'á¾¹', + 'Ὰ' => 'Ὰ', + 'ᾼ' => 'á¾¼', + '῁' => 'á¿', + 'á½´Í…' => 'á¿‚', + 'ῃ' => 'ῃ', + 'ῄ' => 'á¿„', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Ὴ' => 'Ὴ', + 'ῌ' => 'ῌ', + '῍' => 'á¿', + '᾿Ì' => '῎', + '῏' => 'á¿', + 'ῐ' => 'á¿', + 'ῑ' => 'á¿‘', + 'ÏŠÌ€' => 'á¿’', + 'ῖ' => 'á¿–', + 'ÏŠÍ‚' => 'á¿—', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'á¿™', + 'Ὶ' => 'Ὶ', + '῝' => 'á¿', + '῾Ì' => '῞', + '῟' => '῟', + 'ῠ' => 'á¿ ', + 'Ï…Ì„' => 'á¿¡', + 'ῢ' => 'á¿¢', + 'ÏÌ“' => 'ῤ', + 'ÏÌ”' => 'á¿¥', + 'Ï…Í‚' => 'ῦ', + 'ῧ' => 'á¿§', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'á¿©', + 'Ὺ' => 'Ὺ', + 'Ῥ' => 'Ῥ', + '῭' => 'á¿­', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ÏŽÍ…' => 'á¿´', + 'ῶ' => 'á¿¶', + 'á¿¶Í…' => 'á¿·', + 'Ὸ' => 'Ὸ', + 'Ὼ' => 'Ὼ', + 'ῼ' => 'ῼ', + 'â†Ì¸' => '↚', + '↛' => '↛', + '↮' => '↮', + 'â‡Ì¸' => 'â‡', + '⇎' => '⇎', + '⇏' => 'â‡', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => 'â‰', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + 'â‰Ì¸' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => 'âŠ', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => 'â‹ ', + '⋡' => 'â‹¡', + '⋢' => 'â‹¢', + '⋣' => 'â‹£', + '⋪' => '⋪', + '⋫' => 'â‹«', + '⋬' => '⋬', + '⋭' => 'â‹­', + 'ã‹ã‚™' => 'ãŒ', + 'ãã‚™' => 'ãŽ', + 'ãã‚™' => 'ã', + 'ã‘ã‚™' => 'ã’', + 'ã“ã‚™' => 'ã”', + 'ã•ã‚™' => 'ã–', + 'ã—ã‚™' => 'ã˜', + 'ã™ã‚™' => 'ãš', + 'ã›ã‚™' => 'ãœ', + 'ãã‚™' => 'ãž', + 'ãŸã‚™' => 'ã ', + 'ã¡ã‚™' => 'ã¢', + 'ã¤ã‚™' => 'ã¥', + 'ã¦ã‚™' => 'ã§', + 'ã¨ã‚™' => 'ã©', + 'ã¯ã‚™' => 'ã°', + 'ã¯ã‚š' => 'ã±', + 'ã²ã‚™' => 'ã³', + 'ã²ã‚š' => 'ã´', + 'ãµã‚™' => 'ã¶', + 'ãµã‚š' => 'ã·', + 'ã¸ã‚™' => 'ã¹', + 'ã¸ã‚š' => 'ãº', + 'ã»ã‚™' => 'ã¼', + 'ã»ã‚š' => 'ã½', + 'ã†ã‚™' => 'ã‚”', + 'ã‚ã‚™' => 'ゞ', + 'ã‚«ã‚™' => 'ガ', + 'ã‚­ã‚™' => 'ã‚®', + 'グ' => 'ã‚°', + 'ゲ' => 'ゲ', + 'ゴ' => 'ã‚´', + 'ザ' => 'ã‚¶', + 'ã‚·ã‚™' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ã‚¿ã‚™' => 'ダ', + 'ãƒã‚™' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'ãƒã‚™' => 'ãƒ', + 'ãƒã‚š' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ãƒ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '𑂚' => 'ð‘‚š', + '𑂜' => 'ð‘‚œ', + '𑂫' => 'ð‘‚«', + '𑄮' => 'ð‘„®', + '𑄯' => '𑄯', + 'ð‘‡ð‘Œ¾' => 'ð‘‹', + 'ð‘‡ð‘—' => 'ð‘Œ', + '𑒻' => 'ð‘’»', + '𑒼' => 'ð‘’¼', + '𑒾' => 'ð‘’¾', + '𑖺' => 'ð‘–º', + '𑖻' => 'ð‘–»', + '𑤸' => '𑤸', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php new file mode 100644 index 0000000..5a3e8e0 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php @@ -0,0 +1,2065 @@ + 'AÌ€', + 'Ã' => 'AÌ', + 'Â' => 'AÌ‚', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Ã…' => 'AÌŠ', + 'Ç' => 'Ç', + 'È' => 'EÌ€', + 'É' => 'EÌ', + 'Ê' => 'EÌ‚', + 'Ë' => 'Ë', + 'ÃŒ' => 'IÌ€', + 'Ã' => 'IÌ', + 'ÃŽ' => 'IÌ‚', + 'Ã' => 'Ï', + 'Ñ' => 'Ñ', + 'Ã’' => 'OÌ€', + 'Ó' => 'OÌ', + 'Ô' => 'OÌ‚', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'UÌ€', + 'Ú' => 'UÌ', + 'Û' => 'UÌ‚', + 'Ü' => 'Ü', + 'Ã' => 'YÌ', + 'à' => 'aÌ€', + 'á' => 'aÌ', + 'â' => 'aÌ‚', + 'ã' => 'ã', + 'ä' => 'ä', + 'Ã¥' => 'aÌŠ', + 'ç' => 'ç', + 'è' => 'eÌ€', + 'é' => 'eÌ', + 'ê' => 'eÌ‚', + 'ë' => 'ë', + 'ì' => 'iÌ€', + 'í' => 'iÌ', + 'î' => 'iÌ‚', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'oÌ€', + 'ó' => 'oÌ', + 'ô' => 'oÌ‚', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'uÌ€', + 'ú' => 'uÌ', + 'û' => 'uÌ‚', + 'ü' => 'ü', + 'ý' => 'yÌ', + 'ÿ' => 'ÿ', + 'Ä€' => 'AÌ„', + 'Ä' => 'aÌ„', + 'Ä‚' => 'Ă', + 'ă' => 'ă', + 'Ä„' => 'Ą', + 'Ä…' => 'ą', + 'Ć' => 'CÌ', + 'ć' => 'cÌ', + 'Ĉ' => 'CÌ‚', + 'ĉ' => 'cÌ‚', + 'ÄŠ' => 'Ċ', + 'Ä‹' => 'ċ', + 'ÄŒ' => 'CÌŒ', + 'Ä' => 'cÌŒ', + 'ÄŽ' => 'DÌŒ', + 'Ä' => 'dÌŒ', + 'Ä’' => 'EÌ„', + 'Ä“' => 'eÌ„', + 'Ä”' => 'Ĕ', + 'Ä•' => 'ĕ', + 'Ä–' => 'Ė', + 'Ä—' => 'ė', + 'Ę' => 'Ę', + 'Ä™' => 'ę', + 'Äš' => 'EÌŒ', + 'Ä›' => 'eÌŒ', + 'Äœ' => 'GÌ‚', + 'Ä' => 'gÌ‚', + 'Äž' => 'Ğ', + 'ÄŸ' => 'ğ', + 'Ä ' => 'Ġ', + 'Ä¡' => 'ġ', + 'Ä¢' => 'Ģ', + 'Ä£' => 'ģ', + 'Ĥ' => 'HÌ‚', + 'Ä¥' => 'hÌ‚', + 'Ĩ' => 'Ĩ', + 'Ä©' => 'ĩ', + 'Ī' => 'IÌ„', + 'Ä«' => 'iÌ„', + 'Ĭ' => 'Ĭ', + 'Ä­' => 'ĭ', + 'Ä®' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ä´' => 'JÌ‚', + 'ĵ' => 'jÌ‚', + 'Ķ' => 'Ķ', + 'Ä·' => 'ķ', + 'Ĺ' => 'LÌ', + 'ĺ' => 'lÌ', + 'Ä»' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'LÌŒ', + 'ľ' => 'lÌŒ', + 'Ń' => 'NÌ', + 'Å„' => 'nÌ', + 'Å…' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'NÌŒ', + 'ň' => 'nÌŒ', + 'ÅŒ' => 'OÌ„', + 'Å' => 'oÌ„', + 'ÅŽ' => 'Ŏ', + 'Å' => 'ŏ', + 'Å' => 'OÌ‹', + 'Å‘' => 'oÌ‹', + 'Å”' => 'RÌ', + 'Å•' => 'rÌ', + 'Å–' => 'Ŗ', + 'Å—' => 'ŗ', + 'Ř' => 'RÌŒ', + 'Å™' => 'rÌŒ', + 'Åš' => 'SÌ', + 'Å›' => 'sÌ', + 'Åœ' => 'SÌ‚', + 'Å' => 'sÌ‚', + 'Åž' => 'Ş', + 'ÅŸ' => 'ş', + 'Å ' => 'SÌŒ', + 'Å¡' => 'sÌŒ', + 'Å¢' => 'Ţ', + 'Å£' => 'ţ', + 'Ť' => 'TÌŒ', + 'Å¥' => 'tÌŒ', + 'Ũ' => 'Ũ', + 'Å©' => 'ũ', + 'Ū' => 'UÌ„', + 'Å«' => 'uÌ„', + 'Ŭ' => 'Ŭ', + 'Å­' => 'ŭ', + 'Å®' => 'UÌŠ', + 'ů' => 'uÌŠ', + 'Ű' => 'UÌ‹', + 'ű' => 'uÌ‹', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Å´' => 'WÌ‚', + 'ŵ' => 'wÌ‚', + 'Ŷ' => 'YÌ‚', + 'Å·' => 'yÌ‚', + 'Ÿ' => 'Ÿ', + 'Ź' => 'ZÌ', + 'ź' => 'zÌ', + 'Å»' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'ZÌŒ', + 'ž' => 'zÌŒ', + 'Æ ' => 'OÌ›', + 'Æ¡' => 'oÌ›', + 'Ư' => 'UÌ›', + 'ư' => 'uÌ›', + 'Ç' => 'AÌŒ', + 'ÇŽ' => 'aÌŒ', + 'Ç' => 'IÌŒ', + 'Ç' => 'iÌŒ', + 'Ç‘' => 'OÌŒ', + 'Ç’' => 'oÌŒ', + 'Ç“' => 'UÌŒ', + 'Ç”' => 'uÌŒ', + 'Ç•' => 'Ǖ', + 'Ç–' => 'ǖ', + 'Ç—' => 'ÜÌ', + 'ǘ' => 'üÌ', + 'Ç™' => 'Ǚ', + 'Çš' => 'ǚ', + 'Ç›' => 'Ǜ', + 'Çœ' => 'ǜ', + 'Çž' => 'Ǟ', + 'ÇŸ' => 'ǟ', + 'Ç ' => 'Ǡ', + 'Ç¡' => 'ǡ', + 'Ç¢' => 'Ǣ', + 'Ç£' => 'ǣ', + 'Ǧ' => 'GÌŒ', + 'ǧ' => 'gÌŒ', + 'Ǩ' => 'KÌŒ', + 'Ç©' => 'kÌŒ', + 'Ǫ' => 'Ǫ', + 'Ç«' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'Ç­' => 'ǭ', + 'Ç®' => 'Æ·ÌŒ', + 'ǯ' => 'Ê’ÌŒ', + 'ǰ' => 'jÌŒ', + 'Ç´' => 'GÌ', + 'ǵ' => 'gÌ', + 'Ǹ' => 'NÌ€', + 'ǹ' => 'nÌ€', + 'Ǻ' => 'AÌŠÌ', + 'Ç»' => 'aÌŠÌ', + 'Ǽ' => 'ÆÌ', + 'ǽ' => 'æÌ', + 'Ǿ' => 'ØÌ', + 'Ç¿' => 'øÌ', + 'È€' => 'AÌ', + 'È' => 'aÌ', + 'È‚' => 'AÌ‘', + 'ȃ' => 'aÌ‘', + 'È„' => 'EÌ', + 'È…' => 'eÌ', + 'Ȇ' => 'EÌ‘', + 'ȇ' => 'eÌ‘', + 'Ȉ' => 'IÌ', + 'ȉ' => 'iÌ', + 'ÈŠ' => 'IÌ‘', + 'È‹' => 'iÌ‘', + 'ÈŒ' => 'OÌ', + 'È' => 'oÌ', + 'ÈŽ' => 'OÌ‘', + 'È' => 'oÌ‘', + 'È' => 'RÌ', + 'È‘' => 'rÌ', + 'È’' => 'RÌ‘', + 'È“' => 'rÌ‘', + 'È”' => 'UÌ', + 'È•' => 'uÌ', + 'È–' => 'UÌ‘', + 'È—' => 'uÌ‘', + 'Ș' => 'Ș', + 'È™' => 'ș', + 'Èš' => 'Ț', + 'È›' => 'ț', + 'Èž' => 'HÌŒ', + 'ÈŸ' => 'hÌŒ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'È©' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'È«' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'È­' => 'ȭ', + 'È®' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'YÌ„', + 'ȳ' => 'yÌ„', + 'Í€' => 'Ì€', + 'Í' => 'Ì', + '̓' => 'Ì“', + 'Í„' => '̈Ì', + 'Í´' => 'ʹ', + ';' => ';', + 'Î…' => '¨Ì', + 'Ά' => 'ΑÌ', + '·' => '·', + 'Έ' => 'ΕÌ', + 'Ή' => 'ΗÌ', + 'Ί' => 'ΙÌ', + 'ÎŒ' => 'ΟÌ', + 'ÎŽ' => 'Î¥Ì', + 'Î' => 'ΩÌ', + 'Î' => 'ϊÌ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'αÌ', + 'έ' => 'εÌ', + 'ή' => 'ηÌ', + 'ί' => 'ιÌ', + 'ΰ' => 'ϋÌ', + 'ÏŠ' => 'ϊ', + 'Ï‹' => 'ϋ', + 'ÏŒ' => 'οÌ', + 'Ï' => 'Ï…Ì', + 'ÏŽ' => 'ωÌ', + 'Ï“' => 'Ï’Ì', + 'Ï”' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ð' => 'Ё', + 'Ѓ' => 'ГÌ', + 'Ї' => 'Ї', + 'ÐŒ' => 'КÌ', + 'Ð' => 'Ѝ', + 'ÐŽ' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'Ñ' => 'ѐ', + 'Ñ‘' => 'ё', + 'Ñ“' => 'гÌ', + 'Ñ—' => 'ї', + 'Ñœ' => 'кÌ', + 'Ñ' => 'ѝ', + 'Ñž' => 'ў', + 'Ѷ' => 'Ñ´Ì', + 'Ñ·' => 'ѵÌ', + 'Ó' => 'Ӂ', + 'Ó‚' => 'ӂ', + 'Ó' => 'Ð̆', + 'Ó‘' => 'ӑ', + 'Ó’' => 'Ð̈', + 'Ó“' => 'ӓ', + 'Ó–' => 'Ӗ', + 'Ó—' => 'ӗ', + 'Óš' => 'Ӛ', + 'Ó›' => 'ӛ', + 'Óœ' => 'Ӝ', + 'Ó' => 'ӝ', + 'Óž' => 'Ӟ', + 'ÓŸ' => 'ӟ', + 'Ó¢' => 'Ӣ', + 'Ó£' => 'ӣ', + 'Ó¤' => 'Ӥ', + 'Ó¥' => 'ӥ', + 'Ó¦' => 'Ӧ', + 'Ó§' => 'ӧ', + 'Óª' => 'Ӫ', + 'Ó«' => 'ӫ', + 'Ó¬' => 'Ӭ', + 'Ó­' => 'Ñ̈', + 'Ó®' => 'Ӯ', + 'Ó¯' => 'ӯ', + 'Ó°' => 'Ӱ', + 'Ó±' => 'ӱ', + 'Ó²' => 'Ӳ', + 'Ó³' => 'ӳ', + 'Ó´' => 'Ӵ', + 'Óµ' => 'ӵ', + 'Ó¸' => 'Ӹ', + 'Ó¹' => 'ӹ', + 'Ø¢' => 'آ', + 'Ø£' => 'أ', + 'ؤ' => 'ÙˆÙ”', + 'Ø¥' => 'إ', + 'ئ' => 'ÙŠÙ”', + 'Û€' => 'Û•Ù”', + 'Û‚' => 'ÛÙ”', + 'Û“' => 'Û’Ù”', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'क़' => 'क़', + 'ख़' => 'ख़', + 'ग़' => 'ग़', + 'ज़' => 'ज़', + 'ड़' => 'ड़', + 'à¥' => 'ढ़', + 'फ़' => 'फ़', + 'य़' => 'य़', + 'à§‹' => 'ো', + 'à§Œ' => 'ৌ', + 'à§œ' => 'ড়', + 'à§' => 'ঢ়', + 'à§Ÿ' => 'য়', + 'ਲ਼' => 'ਲ਼', + 'ਸ਼' => 'ਸ਼', + 'à©™' => 'ਖ਼', + 'ਗ਼' => 'ਗ਼', + 'à©›' => 'ਜ਼', + 'ਫ਼' => 'ਫ਼', + 'à­ˆ' => 'ୈ', + 'à­‹' => 'ୋ', + 'à­Œ' => 'ୌ', + 'à­œ' => 'ଡ଼', + 'à­' => 'ଢ଼', + 'à®”' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'à³€' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'à·š' => 'ේ', + 'à·œ' => 'à·™à·', + 'à·' => 'à·™à·à·Š', + 'à·ž' => 'ෞ', + 'གྷ' => 'གྷ', + 'à½' => 'ཌྷ', + 'དྷ' => 'དྷ', + 'བྷ' => 'བྷ', + 'ཛྷ' => 'ཛྷ', + 'ཀྵ' => 'ཀྵ', + 'ཱི' => 'ཱི', + 'ཱུ' => 'ཱུ', + 'ྲྀ' => 'ྲྀ', + 'ླྀ' => 'ླྀ', + 'à¾' => 'ཱྀ', + 'ྒྷ' => 'ྒྷ', + 'à¾' => 'ྜྷ', + 'ྡྷ' => 'ྡྷ', + 'ྦྷ' => 'ྦྷ', + 'ྫྷ' => 'ྫྷ', + 'ྐྵ' => 'à¾à¾µ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'á¬á¬µ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'á­€' => 'ᭀ', + 'á­' => 'ᭁ', + 'á­ƒ' => 'ᭃ', + 'Ḁ' => 'AÌ¥', + 'á¸' => 'aÌ¥', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'BÌ£', + 'ḅ' => 'bÌ£', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'ÇÌ', + 'ḉ' => 'çÌ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'DÌ£', + 'á¸' => 'dÌ£', + 'Ḏ' => 'Ḏ', + 'á¸' => 'ḏ', + 'á¸' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'DÌ­', + 'ḓ' => 'dÌ­', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'EÌ„Ì', + 'ḗ' => 'eÌ„Ì', + 'Ḙ' => 'EÌ­', + 'ḙ' => 'eÌ­', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'á¸' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'GÌ„', + 'ḡ' => 'gÌ„', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'HÌ£', + 'ḥ' => 'hÌ£', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'HÌ®', + 'ḫ' => 'hÌ®', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'ÏÌ', + 'ḯ' => 'ïÌ', + 'Ḱ' => 'KÌ', + 'ḱ' => 'kÌ', + 'Ḳ' => 'KÌ£', + 'ḳ' => 'kÌ£', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'LÌ£', + 'ḷ' => 'lÌ£', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'LÌ­', + 'ḽ' => 'lÌ­', + 'Ḿ' => 'MÌ', + 'ḿ' => 'mÌ', + 'á¹€' => 'Ṁ', + 'á¹' => 'ṁ', + 'Ṃ' => 'MÌ£', + 'ṃ' => 'mÌ£', + 'Ṅ' => 'Ṅ', + 'á¹…' => 'ṅ', + 'Ṇ' => 'NÌ£', + 'ṇ' => 'nÌ£', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'NÌ­', + 'ṋ' => 'nÌ­', + 'Ṍ' => 'ÕÌ', + 'á¹' => 'õÌ', + 'Ṏ' => 'Ṏ', + 'á¹' => 'ṏ', + 'á¹' => 'Ṑ', + 'ṑ' => 'ṑ', + 'á¹’' => 'OÌ„Ì', + 'ṓ' => 'oÌ„Ì', + 'á¹”' => 'PÌ', + 'ṕ' => 'pÌ', + 'á¹–' => 'Ṗ', + 'á¹—' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'á¹™' => 'ṙ', + 'Ṛ' => 'RÌ£', + 'á¹›' => 'rÌ£', + 'Ṝ' => 'Ṝ', + 'á¹' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'á¹ ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'á¹¢' => 'SÌ£', + 'á¹£' => 'sÌ£', + 'Ṥ' => 'SÌ̇', + 'á¹¥' => 'sÌ̇', + 'Ṧ' => 'Ṧ', + 'á¹§' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'TÌ£', + 'á¹­' => 'tÌ£', + 'á¹®' => 'Ṯ', + 'ṯ' => 'ṯ', + 'á¹°' => 'TÌ­', + 'á¹±' => 'tÌ­', + 'á¹²' => 'Ṳ', + 'á¹³' => 'ṳ', + 'á¹´' => 'Ṵ', + 'á¹µ' => 'ṵ', + 'á¹¶' => 'UÌ­', + 'á¹·' => 'uÌ­', + 'Ṹ' => 'ŨÌ', + 'á¹¹' => 'ũÌ', + 'Ṻ' => 'Ṻ', + 'á¹»' => 'ṻ', + 'á¹¼' => 'Ṽ', + 'á¹½' => 'ṽ', + 'á¹¾' => 'VÌ£', + 'ṿ' => 'vÌ£', + 'Ẁ' => 'WÌ€', + 'áº' => 'wÌ€', + 'Ẃ' => 'WÌ', + 'ẃ' => 'wÌ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'WÌ£', + 'ẉ' => 'wÌ£', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'áº' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'áº' => 'ẏ', + 'áº' => 'ZÌ‚', + 'ẑ' => 'zÌ‚', + 'Ẓ' => 'ZÌ£', + 'ẓ' => 'zÌ£', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'wÌŠ', + 'ẙ' => 'yÌŠ', + 'ẛ' => 'ẛ', + 'Ạ' => 'AÌ£', + 'ạ' => 'aÌ£', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'AÌ‚Ì', + 'ấ' => 'aÌ‚Ì', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'ĂÌ', + 'ắ' => 'ăÌ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'EÌ£', + 'ẹ' => 'eÌ£', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'EÌ‚Ì', + 'ế' => 'eÌ‚Ì', + 'Ề' => 'Ề', + 'á»' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'á»…' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'IÌ£', + 'ị' => 'iÌ£', + 'Ọ' => 'OÌ£', + 'á»' => 'oÌ£', + 'Ỏ' => 'Ỏ', + 'á»' => 'ỏ', + 'á»' => 'OÌ‚Ì', + 'ố' => 'oÌ‚Ì', + 'á»’' => 'Ồ', + 'ồ' => 'ồ', + 'á»”' => 'Ổ', + 'ổ' => 'ổ', + 'á»–' => 'Ỗ', + 'á»—' => 'ỗ', + 'Ộ' => 'Ộ', + 'á»™' => 'ộ', + 'Ớ' => 'OÌ›Ì', + 'á»›' => 'oÌ›Ì', + 'Ờ' => 'Ờ', + 'á»' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'á» ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'UÌ£', + 'ụ' => 'uÌ£', + 'Ủ' => 'Ủ', + 'á»§' => 'ủ', + 'Ứ' => 'UÌ›Ì', + 'ứ' => 'uÌ›Ì', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'á»­' => 'ử', + 'á»®' => 'Ữ', + 'ữ' => 'ữ', + 'á»°' => 'Ự', + 'á»±' => 'ự', + 'Ỳ' => 'YÌ€', + 'ỳ' => 'yÌ€', + 'á»´' => 'YÌ£', + 'ỵ' => 'yÌ£', + 'á»¶' => 'Ỷ', + 'á»·' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'á¼€' => 'ἀ', + 'á¼' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἀÌ', + 'á¼…' => 'ἁÌ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'ἈÌ', + 'á¼' => 'ἉÌ', + 'Ἆ' => 'Ἆ', + 'á¼' => 'Ἇ', + 'á¼' => 'ἐ', + 'ἑ' => 'ἑ', + 'á¼’' => 'ἒ', + 'ἓ' => 'ἓ', + 'á¼”' => 'ἐÌ', + 'ἕ' => 'ἑÌ', + 'Ἐ' => 'Ἐ', + 'á¼™' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'á¼›' => 'Ἓ', + 'Ἔ' => 'ἘÌ', + 'á¼' => 'ἙÌ', + 'á¼ ' => 'ἠ', + 'ἡ' => 'ἡ', + 'á¼¢' => 'ἢ', + 'á¼£' => 'ἣ', + 'ἤ' => 'ἠÌ', + 'á¼¥' => 'ἡÌ', + 'ἦ' => 'ἦ', + 'á¼§' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'ἨÌ', + 'á¼­' => 'ἩÌ', + 'á¼®' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'á¼°' => 'ἰ', + 'á¼±' => 'ἱ', + 'á¼²' => 'ἲ', + 'á¼³' => 'ἳ', + 'á¼´' => 'ἰÌ', + 'á¼µ' => 'ἱÌ', + 'á¼¶' => 'ἶ', + 'á¼·' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'á¼¹' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'á¼»' => 'Ἳ', + 'á¼¼' => 'ἸÌ', + 'á¼½' => 'ἹÌ', + 'á¼¾' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'á½€' => 'ὀ', + 'á½' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὀÌ', + 'á½…' => 'ὁÌ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'ὈÌ', + 'á½' => 'ὉÌ', + 'á½' => 'Ï…Ì“', + 'ὑ' => 'Ï…Ì”', + 'á½’' => 'ὒ', + 'ὓ' => 'ὓ', + 'á½”' => 'Ï…Ì“Ì', + 'ὕ' => 'Ï…Ì”Ì', + 'á½–' => 'ὖ', + 'á½—' => 'ὗ', + 'á½™' => 'Ὑ', + 'á½›' => 'Ὓ', + 'á½' => 'ὙÌ', + 'Ὗ' => 'Ὗ', + 'á½ ' => 'ὠ', + 'ὡ' => 'ὡ', + 'á½¢' => 'ὢ', + 'á½£' => 'ὣ', + 'ὤ' => 'ὠÌ', + 'á½¥' => 'ὡÌ', + 'ὦ' => 'ὦ', + 'á½§' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'ὨÌ', + 'á½­' => 'ὩÌ', + 'á½®' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'á½°' => 'ὰ', + 'á½±' => 'αÌ', + 'á½²' => 'ὲ', + 'á½³' => 'εÌ', + 'á½´' => 'ὴ', + 'á½µ' => 'ηÌ', + 'á½¶' => 'ὶ', + 'á½·' => 'ιÌ', + 'ὸ' => 'ὸ', + 'á½¹' => 'οÌ', + 'ὺ' => 'Ï…Ì€', + 'á½»' => 'Ï…Ì', + 'á½¼' => 'ὼ', + 'á½½' => 'ωÌ', + 'á¾€' => 'ᾀ', + 'á¾' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ἀÌÍ…', + 'á¾…' => 'ἁÌÍ…', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ἈÌÍ…', + 'á¾' => 'ἉÌÍ…', + 'ᾎ' => 'ᾎ', + 'á¾' => 'ᾏ', + 'á¾' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'á¾’' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'á¾”' => 'ἠÌÍ…', + 'ᾕ' => 'ἡÌÍ…', + 'á¾–' => 'ᾖ', + 'á¾—' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'á¾™' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'á¾›' => 'ᾛ', + 'ᾜ' => 'ἨÌÍ…', + 'á¾' => 'ἩÌÍ…', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'á¾ ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'á¾¢' => 'ᾢ', + 'á¾£' => 'ᾣ', + 'ᾤ' => 'ὠÌÍ…', + 'á¾¥' => 'ὡÌÍ…', + 'ᾦ' => 'ᾦ', + 'á¾§' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ὨÌÍ…', + 'á¾­' => 'ὩÌÍ…', + 'á¾®' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'á¾°' => 'ᾰ', + 'á¾±' => 'ᾱ', + 'á¾²' => 'ᾲ', + 'á¾³' => 'ᾳ', + 'á¾´' => 'αÌÍ…', + 'á¾¶' => 'ᾶ', + 'á¾·' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'á¾¹' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'á¾»' => 'ΑÌ', + 'á¾¼' => 'ᾼ', + 'á¾¾' => 'ι', + 'á¿' => '῁', + 'á¿‚' => 'ῂ', + 'ῃ' => 'ῃ', + 'á¿„' => 'ηÌÍ…', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Έ' => 'ΕÌ', + 'Ὴ' => 'Ὴ', + 'á¿‹' => 'ΗÌ', + 'ῌ' => 'ῌ', + 'á¿' => '῍', + '῎' => '᾿Ì', + 'á¿' => '῏', + 'á¿' => 'ῐ', + 'á¿‘' => 'ῑ', + 'á¿’' => 'ῒ', + 'á¿“' => 'ϊÌ', + 'á¿–' => 'ῖ', + 'á¿—' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'á¿™' => 'Ῑ', + 'Ὶ' => 'Ὶ', + 'á¿›' => 'ΙÌ', + 'á¿' => '῝', + '῞' => '῾Ì', + '῟' => '῟', + 'á¿ ' => 'ῠ', + 'á¿¡' => 'Ï…Ì„', + 'á¿¢' => 'ῢ', + 'á¿£' => 'ϋÌ', + 'ῤ' => 'ÏÌ“', + 'á¿¥' => 'ÏÌ”', + 'ῦ' => 'Ï…Í‚', + 'á¿§' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'á¿©' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'á¿«' => 'Î¥Ì', + 'Ῥ' => 'Ῥ', + 'á¿­' => '῭', + 'á¿®' => '¨Ì', + '`' => '`', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'á¿´' => 'ωÌÍ…', + 'á¿¶' => 'ῶ', + 'á¿·' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ό' => 'ΟÌ', + 'Ὼ' => 'Ὼ', + 'á¿»' => 'ΩÌ', + 'ῼ' => 'ῼ', + '´' => '´', + ' ' => ' ', + 'â€' => ' ', + 'Ω' => 'Ω', + 'K' => 'K', + 'â„«' => 'AÌŠ', + '↚' => 'â†Ì¸', + '↛' => '↛', + '↮' => '↮', + 'â‡' => 'â‡Ì¸', + '⇎' => '⇎', + 'â‡' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + 'â‰' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => 'â‰Ì¸', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + 'âŠ' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + 'â‹ ' => '⋠', + 'â‹¡' => '⋡', + 'â‹¢' => '⋢', + 'â‹£' => '⋣', + '⋪' => '⋪', + 'â‹«' => '⋫', + '⋬' => '⋬', + 'â‹­' => '⋭', + '〈' => '〈', + '〉' => '〉', + '⫝̸' => 'â«Ì¸', + 'ãŒ' => 'ã‹ã‚™', + 'ãŽ' => 'ãã‚™', + 'ã' => 'ãã‚™', + 'ã’' => 'ã‘ã‚™', + 'ã”' => 'ã“ã‚™', + 'ã–' => 'ã•ã‚™', + 'ã˜' => 'ã—ã‚™', + 'ãš' => 'ã™ã‚™', + 'ãœ' => 'ã›ã‚™', + 'ãž' => 'ãã‚™', + 'ã ' => 'ãŸã‚™', + 'ã¢' => 'ã¡ã‚™', + 'ã¥' => 'ã¤ã‚™', + 'ã§' => 'ã¦ã‚™', + 'ã©' => 'ã¨ã‚™', + 'ã°' => 'ã¯ã‚™', + 'ã±' => 'ã¯ã‚š', + 'ã³' => 'ã²ã‚™', + 'ã´' => 'ã²ã‚š', + 'ã¶' => 'ãµã‚™', + 'ã·' => 'ãµã‚š', + 'ã¹' => 'ã¸ã‚™', + 'ãº' => 'ã¸ã‚š', + 'ã¼' => 'ã»ã‚™', + 'ã½' => 'ã»ã‚š', + 'ã‚”' => 'ã†ã‚™', + 'ゞ' => 'ã‚ã‚™', + 'ガ' => 'ã‚«ã‚™', + 'ã‚®' => 'ã‚­ã‚™', + 'ã‚°' => 'グ', + 'ゲ' => 'ゲ', + 'ã‚´' => 'ゴ', + 'ã‚¶' => 'ザ', + 'ジ' => 'ã‚·ã‚™', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ã‚¿ã‚™', + 'ヂ' => 'ãƒã‚™', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'ãƒ' => 'ãƒã‚™', + 'パ' => 'ãƒã‚š', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ãƒ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '豈' => '豈', + 'ï¤' => 'æ›´', + '車' => '車', + '賈' => '賈', + '滑' => '滑', + '串' => '串', + '句' => 'å¥', + '龜' => '龜', + '龜' => '龜', + '契' => '契', + '金' => '金', + '喇' => 'å–‡', + '奈' => '奈', + 'ï¤' => '懶', + '癩' => '癩', + 'ï¤' => 'ç¾…', + 'ï¤' => '蘿', + '螺' => '螺', + '裸' => '裸', + '邏' => 'é‚', + '樂' => '樂', + '洛' => 'æ´›', + '烙' => '烙', + '珞' => 'çž', + '落' => 'è½', + '酪' => 'é…ª', + '駱' => 'é§±', + '亂' => '亂', + '卵' => 'åµ', + 'ï¤' => '欄', + '爛' => '爛', + '蘭' => '蘭', + '鸞' => '鸞', + '嵐' => 'åµ', + '濫' => 'æ¿«', + '藍' => 'è—', + '襤' => '襤', + '拉' => '拉', + '臘' => '臘', + '蠟' => 'è Ÿ', + '廊' => '廊', + '朗' => '朗', + '浪' => '浪', + '狼' => '狼', + '郎' => '郎', + '來' => '來', + '冷' => '冷', + '勞' => '勞', + '擄' => 'æ“„', + '櫓' => 'æ«“', + '爐' => 'çˆ', + '盧' => 'ç›§', + '老' => 'è€', + '蘆' => '蘆', + '虜' => '虜', + '路' => 'è·¯', + '露' => '露', + '魯' => 'é­¯', + '鷺' => 'é·º', + '碌' => '碌', + '祿' => '祿', + '綠' => 'ç¶ ', + '菉' => 'è‰', + '錄' => '錄', + '鹿' => '鹿', + 'ï¥' => 'è«–', + '壟' => '壟', + '弄' => '弄', + '籠' => 'ç± ', + '聾' => 'è¾', + '牢' => '牢', + '磊' => '磊', + '賂' => '賂', + '雷' => 'é›·', + '壘' => '壘', + '屢' => 'å±¢', + '樓' => '樓', + 'ï¥' => 'æ·š', + '漏' => 'æ¼', + 'ï¥' => 'ç´¯', + 'ï¥' => '縷', + '陋' => '陋', + '勒' => 'å‹’', + '肋' => 'è‚‹', + '凜' => '凜', + '凌' => '凌', + '稜' => '稜', + '綾' => 'ç¶¾', + '菱' => 'è±', + '陵' => '陵', + '讀' => '讀', + '拏' => 'æ‹', + '樂' => '樂', + 'ï¥' => '諾', + '丹' => '丹', + '寧' => '寧', + '怒' => '怒', + '率' => '率', + '異' => 'ç•°', + '北' => '北', + '磻' => '磻', + '便' => '便', + '復' => '復', + '不' => 'ä¸', + '泌' => '泌', + '數' => '數', + '索' => 'ç´¢', + '參' => 'åƒ', + '塞' => '塞', + '省' => 'çœ', + '葉' => '葉', + '說' => '說', + '殺' => '殺', + '辰' => 'è¾°', + '沈' => '沈', + '拾' => '拾', + '若' => 'è‹¥', + '掠' => '掠', + '略' => 'ç•¥', + '亮' => '亮', + '兩' => 'å…©', + '凉' => '凉', + '梁' => 'æ¢', + '糧' => 'ç³§', + '良' => '良', + '諒' => 'è«’', + '量' => 'é‡', + '勵' => '勵', + '呂' => 'å‘‚', + 'ï¦' => '女', + '廬' => '廬', + '旅' => 'æ—…', + '濾' => '濾', + '礪' => '礪', + '閭' => 'é–­', + '驪' => '驪', + '麗' => '麗', + '黎' => '黎', + '力' => '力', + '曆' => '曆', + '歷' => 'æ­·', + 'ï¦' => 'è½¢', + '年' => 'å¹´', + 'ï¦' => 'æ†', + 'ï¦' => '戀', + '撚' => 'æ’š', + '漣' => 'æ¼£', + '煉' => 'ç…‰', + '璉' => 'ç’‰', + '秊' => 'ç§Š', + '練' => 'ç·´', + '聯' => 'è¯', + '輦' => '輦', + '蓮' => 'è“®', + '連' => '連', + '鍊' => 'éŠ', + '列' => '列', + 'ï¦' => '劣', + '咽' => 'å’½', + '烈' => '烈', + '裂' => '裂', + '說' => '說', + '廉' => '廉', + '念' => '念', + '捻' => 'æ»', + '殮' => 'æ®®', + '簾' => 'ç°¾', + '獵' => 'çµ', + '令' => '令', + '囹' => '囹', + '寧' => '寧', + '嶺' => '嶺', + '怜' => '怜', + '玲' => '玲', + '瑩' => 'ç‘©', + '羚' => '羚', + '聆' => 'è†', + '鈴' => '鈴', + '零' => 'é›¶', + '靈' => 'éˆ', + '領' => 'é ˜', + '例' => '例', + '禮' => '禮', + '醴' => '醴', + '隸' => '隸', + '惡' => '惡', + '了' => '了', + '僚' => '僚', + '寮' => '寮', + '尿' => 'å°¿', + '料' => 'æ–™', + '樂' => '樂', + 'ï§€' => '燎', + 'ï§' => '療', + 'ï§‚' => '蓼', + '遼' => 'é¼', + 'ï§„' => 'é¾', + 'ï§…' => '暈', + '阮' => '阮', + '劉' => '劉', + '杻' => 'æ»', + '柳' => '柳', + 'ï§Š' => 'æµ', + 'ï§‹' => '溜', + 'ï§Œ' => 'ç‰', + 'ï§' => 'ç•™', + 'ï§Ž' => 'ç¡«', + 'ï§' => 'ç´', + 'ï§' => '類', + 'ï§‘' => 'å…­', + 'ï§’' => '戮', + 'ï§“' => '陸', + 'ï§”' => '倫', + 'ï§•' => 'å´™', + 'ï§–' => 'æ·ª', + 'ï§—' => '輪', + '律' => '律', + 'ï§™' => 'æ…„', + 'ï§š' => 'æ —', + 'ï§›' => '率', + 'ï§œ' => '隆', + 'ï§' => '利', + 'ï§ž' => 'å', + 'ï§Ÿ' => 'å±¥', + 'ï§ ' => '易', + 'ï§¡' => 'æŽ', + 'ï§¢' => '梨', + 'ï§£' => 'æ³¥', + '理' => 'ç†', + 'ï§¥' => 'ç—¢', + '罹' => 'ç½¹', + 'ï§§' => 'è£', + '裡' => '裡', + 'ï§©' => '里', + '離' => '離', + 'ï§«' => '匿', + '溺' => '溺', + 'ï§­' => 'å', + 'ï§®' => 'ç‡', + '璘' => 'ç’˜', + 'ï§°' => 'è—º', + 'ï§±' => '隣', + 'ï§²' => 'é±—', + 'ï§³' => '麟', + 'ï§´' => 'æž—', + 'ï§µ' => 'æ·‹', + 'ï§¶' => '臨', + 'ï§·' => 'ç«‹', + '笠' => '笠', + 'ï§¹' => 'ç²’', + '狀' => 'ç‹€', + 'ï§»' => 'ç‚™', + 'ï§¼' => 'è­˜', + 'ï§½' => '什', + 'ï§¾' => '茶', + 'ï§¿' => '刺', + '切' => '切', + 'ï¨' => '度', + '拓' => 'æ‹“', + '糖' => 'ç³–', + '宅' => 'å®…', + '洞' => 'æ´ž', + '暴' => 'æš´', + '輻' => 'è¼»', + '行' => '行', + '降' => 'é™', + '見' => '見', + '廓' => '廓', + '兀' => 'å…€', + 'ï¨' => 'å—€', + 'ï¨' => '塚', + '晴' => 'æ™´', + '凞' => '凞', + '猪' => '猪', + '益' => '益', + '礼' => '礼', + '神' => '神', + '祥' => '祥', + '福' => 'ç¦', + '靖' => 'é–', + 'ï¨' => 'ç²¾', + '羽' => 'ç¾½', + '蘒' => '蘒', + '諸' => '諸', + '逸' => '逸', + '都' => '都', + '飯' => '飯', + '飼' => '飼', + '館' => '館', + '鶴' => 'é¶´', + '郞' => '郞', + '隷' => 'éš·', + '侮' => 'ä¾®', + '僧' => '僧', + '免' => 'å…', + '勉' => '勉', + '勤' => '勤', + '卑' => 'å‘', + '喝' => 'å–', + '嘆' => '嘆', + '器' => '器', + '塀' => 'å¡€', + '墨' => '墨', + '層' => '層', + '屮' => 'å±®', + '悔' => 'æ‚”', + '慨' => 'æ…¨', + '憎' => '憎', + 'ï©€' => '懲', + 'ï©' => 'æ•', + 'ï©‚' => 'æ—¢', + '暑' => 'æš‘', + 'ï©„' => '梅', + 'ï©…' => 'æµ·', + '渚' => '渚', + '漢' => 'æ¼¢', + '煮' => 'ç…®', + '爫' => '爫', + '琢' => 'ç¢', + 'ï©‹' => '碑', + '社' => '社', + 'ï©' => '祉', + '祈' => '祈', + 'ï©' => 'ç¥', + 'ï©' => '祖', + 'ï©‘' => 'ç¥', + 'ï©’' => 'ç¦', + 'ï©“' => '禎', + 'ï©”' => 'ç©€', + 'ï©•' => 'çª', + 'ï©–' => '節', + 'ï©—' => 'ç·´', + '縉' => '縉', + 'ï©™' => 'ç¹', + '署' => 'ç½²', + 'ï©›' => '者', + '臭' => '臭', + 'ï©' => '艹', + '艹' => '艹', + '著' => 'è‘—', + 'ï© ' => 'è¤', + 'ï©¡' => '視', + 'ï©¢' => 'è¬', + 'ï©£' => '謹', + '賓' => '賓', + 'ï©¥' => 'è´ˆ', + '辶' => 'è¾¶', + 'ï©§' => '逸', + '難' => '難', + 'ï©©' => '響', + '頻' => 'é »', + 'ï©«' => 'æµ', + '𤋮' => '𤋮', + 'ï©­' => '舘', + 'ï©°' => '並', + '况' => '况', + '全' => 'å…¨', + '侀' => 'ä¾€', + 'ï©´' => 'å……', + '冀' => '冀', + 'ï©¶' => '勇', + 'ï©·' => '勺', + '喝' => 'å–', + '啕' => 'å••', + '喙' => 'å–™', + 'ï©»' => 'å—¢', + '塚' => '塚', + '墳' => '墳', + '奄' => '奄', + 'ï©¿' => '奔', + '婢' => 'å©¢', + 'ïª' => '嬨', + '廒' => 'å»’', + '廙' => 'å»™', + '彩' => '彩', + '徭' => 'å¾­', + '惘' => '惘', + '慎' => 'æ…Ž', + '愈' => '愈', + '憎' => '憎', + '慠' => 'æ… ', + '懲' => '懲', + '戴' => '戴', + 'ïª' => 'æ„', + '搜' => 'æœ', + 'ïª' => 'æ‘’', + 'ïª' => 'æ•–', + '晴' => 'æ™´', + '朗' => '朗', + '望' => '望', + '杖' => 'æ–', + '歹' => 'æ­¹', + '殺' => '殺', + '流' => 'æµ', + '滛' => 'æ»›', + '滋' => '滋', + '漢' => 'æ¼¢', + '瀞' => '瀞', + '煮' => 'ç…®', + 'ïª' => 'çž§', + '爵' => '爵', + '犯' => '犯', + '猪' => '猪', + '瑱' => '瑱', + '甆' => '甆', + '画' => 'ç”»', + '瘝' => 'ç˜', + '瘟' => '瘟', + '益' => '益', + '盛' => 'ç››', + '直' => 'ç›´', + '睊' => 'çŠ', + '着' => 'ç€', + '磌' => '磌', + '窱' => '窱', + '節' => '節', + '类' => 'ç±»', + '絛' => 'çµ›', + '練' => 'ç·´', + '缾' => 'ç¼¾', + '者' => '者', + '荒' => 'è’', + '華' => 'è¯', + '蝹' => 'è¹', + '襁' => 'è¥', + '覆' => '覆', + '視' => '視', + '調' => '調', + '諸' => '諸', + '請' => 'è«‹', + '謁' => 'è¬', + '諾' => '諾', + '諭' => 'è«­', + '謹' => '謹', + 'ï«€' => '變', + 'ï«' => 'è´ˆ', + 'ï«‚' => '輸', + '遲' => 'é²', + 'ï«„' => '醙', + 'ï«…' => '鉶', + '陼' => '陼', + '難' => '難', + '靖' => 'é–', + '韛' => '韛', + '響' => '響', + 'ï«‹' => 'é ‹', + '頻' => 'é »', + 'ï«' => '鬒', + '龜' => '龜', + 'ï«' => '𢡊', + 'ï«' => '𢡄', + 'ï«‘' => 'ð£•', + 'ï«’' => 'ã®', + 'ï«“' => '䀘', + 'ï«”' => '䀹', + 'ï«•' => '𥉉', + 'ï«–' => 'ð¥³', + 'ï«—' => '𧻓', + '齃' => '齃', + 'ï«™' => '龎', + 'ï¬' => '×™Ö´', + 'ײַ' => 'ײַ', + 'שׁ' => 'ש×', + 'שׂ' => 'שׂ', + 'שּׁ' => 'שּ×', + 'שּׂ' => 'שּׂ', + 'אַ' => '×Ö·', + 'אָ' => '×Ö¸', + 'אּ' => '×Ö¼', + 'בּ' => 'בּ', + 'גּ' => '×’Ö¼', + 'דּ' => 'דּ', + 'הּ' => '×”Ö¼', + 'וּ' => 'וּ', + 'זּ' => '×–Ö¼', + 'טּ' => 'טּ', + 'יּ' => '×™Ö¼', + 'ךּ' => 'ךּ', + 'כּ' => '×›Ö¼', + 'לּ' => 'לּ', + 'מּ' => 'מּ', + 'ï­€' => '× Ö¼', + 'ï­' => 'סּ', + 'ï­ƒ' => '×£Ö¼', + 'ï­„' => 'פּ', + 'ï­†' => 'צּ', + 'ï­‡' => '×§Ö¼', + 'ï­ˆ' => 'רּ', + 'ï­‰' => 'שּ', + 'ï­Š' => 'תּ', + 'ï­‹' => 'וֹ', + 'ï­Œ' => 'בֿ', + 'ï­' => '×›Ö¿', + 'ï­Ž' => 'פֿ', + 'ð‘‚š' => '𑂚', + 'ð‘‚œ' => '𑂜', + 'ð‘‚«' => '𑂫', + 'ð‘„®' => '𑄮', + '𑄯' => '𑄯', + 'ð‘‹' => 'ð‘‡ð‘Œ¾', + 'ð‘Œ' => 'ð‘‡ð‘—', + 'ð‘’»' => '𑒻', + 'ð‘’¼' => '𑒼', + 'ð‘’¾' => '𑒾', + 'ð‘–º' => '𑖺', + 'ð‘–»' => '𑖻', + '𑤸' => '𑤸', + 'ð…ž' => 'ð…—ð…¥', + 'ð…Ÿ' => 'ð…˜ð…¥', + 'ð… ' => 'ð…˜ð…¥ð…®', + 'ð…¡' => 'ð…˜ð…¥ð…¯', + 'ð…¢' => 'ð…˜ð…¥ð…°', + 'ð…£' => 'ð…˜ð…¥ð…±', + 'ð…¤' => 'ð…˜ð…¥ð…²', + 'ð†»' => 'ð†¹ð…¥', + 'ð†¼' => 'ð†ºð…¥', + 'ð†½' => 'ð†¹ð…¥ð…®', + 'ð†¾' => 'ð†ºð…¥ð…®', + 'ð†¿' => 'ð†¹ð…¥ð…¯', + 'ð‡€' => 'ð†ºð…¥ð…¯', + '丽' => '丽', + 'ð¯ ' => '丸', + '乁' => 'ä¹', + '𠄢' => 'ð „¢', + '你' => 'ä½ ', + '侮' => 'ä¾®', + '侻' => 'ä¾»', + '倂' => '倂', + '偺' => 'åº', + '備' => 'å‚™', + '僧' => '僧', + '像' => 'åƒ', + '㒞' => 'ã’ž', + 'ð¯ ' => '𠘺', + '免' => 'å…', + 'ð¯ ' => 'å…”', + 'ð¯ ' => 'å…¤', + '具' => 'å…·', + '𠔜' => '𠔜', + '㒹' => 'ã’¹', + '內' => 'å…§', + '再' => 'å†', + '𠕋' => 'ð •‹', + '冗' => '冗', + '冤' => '冤', + '仌' => '仌', + '冬' => '冬', + '况' => '况', + '𩇟' => '𩇟', + 'ð¯ ' => '凵', + '刃' => '刃', + '㓟' => '㓟', + '刻' => '刻', + '剆' => '剆', + '割' => '割', + '剷' => '剷', + '㔕' => '㔕', + '勇' => '勇', + '勉' => '勉', + '勤' => '勤', + '勺' => '勺', + '包' => '包', + '匆' => '匆', + '北' => '北', + '卉' => 'å‰', + '卑' => 'å‘', + '博' => 'åš', + '即' => 'å³', + '卽' => 'å½', + '卿' => 'å¿', + '卿' => 'å¿', + '卿' => 'å¿', + '𠨬' => '𠨬', + '灰' => 'ç°', + '及' => 'åŠ', + '叟' => 'åŸ', + '𠭣' => 'ð ­£', + '叫' => 'å«', + '叱' => 'å±', + '吆' => 'å†', + '咞' => 'å’ž', + '吸' => 'å¸', + '呈' => '呈', + '周' => '周', + '咢' => 'å’¢', + 'ð¯¡' => 'å“¶', + '唐' => 'å”', + '啓' => 'å•“', + '啣' => 'å•£', + '善' => 'å–„', + '善' => 'å–„', + '喙' => 'å–™', + '喫' => 'å–«', + '喳' => 'å–³', + '嗂' => 'å—‚', + '圖' => '圖', + '嘆' => '嘆', + 'ð¯¡' => '圗', + '噑' => '噑', + 'ð¯¡' => 'å™´', + 'ð¯¡' => '切', + '壮' => '壮', + '城' => '城', + '埴' => '埴', + '堍' => 'å ', + '型' => 'åž‹', + '堲' => 'å ²', + '報' => 'å ±', + '墬' => '墬', + '𡓤' => '𡓤', + '売' => '売', + '壷' => '壷', + '夆' => '夆', + 'ð¯¡' => '多', + '夢' => '夢', + '奢' => '奢', + '𡚨' => '𡚨', + '𡛪' => '𡛪', + '姬' => '姬', + '娛' => '娛', + '娧' => '娧', + '姘' => '姘', + '婦' => '婦', + '㛮' => 'ã›®', + '㛼' => '㛼', + '嬈' => '嬈', + '嬾' => '嬾', + '嬾' => '嬾', + '𡧈' => '𡧈', + '寃' => '寃', + '寘' => '寘', + '寧' => '寧', + '寳' => '寳', + '𡬘' => '𡬘', + '寿' => '寿', + '将' => 'å°†', + '当' => '当', + '尢' => 'å°¢', + '㞁' => 'ãž', + '屠' => 'å± ', + '屮' => 'å±®', + '峀' => 'å³€', + '岍' => 'å²', + '𡷤' => 'ð¡·¤', + '嵃' => '嵃', + '𡷦' => 'ð¡·¦', + '嵮' => 'åµ®', + '嵫' => '嵫', + '嵼' => 'åµ¼', + 'ð¯¢' => 'å·¡', + '巢' => 'å·¢', + '㠯' => 'ã ¯', + '巽' => 'å·½', + '帨' => '帨', + '帽' => '帽', + '幩' => '幩', + '㡢' => 'ã¡¢', + '𢆃' => '𢆃', + '㡼' => '㡼', + '庰' => '庰', + '庳' => '庳', + 'ð¯¢' => '庶', + '廊' => '廊', + 'ð¯¢' => '𪎒', + 'ð¯¢' => '廾', + '𢌱' => '𢌱', + '𢌱' => '𢌱', + '舁' => 'èˆ', + '弢' => 'å¼¢', + '弢' => 'å¼¢', + '㣇' => '㣇', + '𣊸' => '𣊸', + '𦇚' => '𦇚', + '形' => 'å½¢', + '彫' => '彫', + '㣣' => '㣣', + '徚' => '徚', + 'ð¯¢' => 'å¿', + '志' => 'å¿—', + '忹' => '忹', + '悁' => 'æ‚', + '㤺' => '㤺', + '㤜' => '㤜', + '悔' => 'æ‚”', + '𢛔' => '𢛔', + '惇' => '惇', + '慈' => 'æ…ˆ', + '慌' => 'æ…Œ', + '慎' => 'æ…Ž', + '慌' => 'æ…Œ', + '慺' => 'æ…º', + '憎' => '憎', + '憲' => '憲', + '憤' => '憤', + '憯' => '憯', + '懞' => '懞', + '懲' => '懲', + '懶' => '懶', + '成' => 'æˆ', + '戛' => '戛', + '扝' => 'æ‰', + '抱' => '抱', + '拔' => 'æ‹”', + '捐' => 'æ', + '𢬌' => '𢬌', + '挽' => '挽', + '拼' => '拼', + '捨' => 'æ¨', + '掃' => '掃', + '揤' => 'æ¤', + '𢯱' => '𢯱', + '搢' => 'æ¢', + '揅' => 'æ…', + 'ð¯£' => '掩', + '㨮' => '㨮', + '摩' => 'æ‘©', + '摾' => '摾', + '撝' => 'æ’', + '摷' => 'æ‘·', + '㩬' => '㩬', + '敏' => 'æ•', + '敬' => '敬', + '𣀊' => '𣀊', + '旣' => 'æ—£', + '書' => '書', + 'ð¯£' => '晉', + '㬙' => '㬙', + 'ð¯£' => 'æš‘', + 'ð¯£' => '㬈', + '㫤' => '㫤', + '冒' => '冒', + '冕' => '冕', + '最' => '最', + '暜' => 'æšœ', + '肭' => 'è‚­', + '䏙' => 'ä™', + '朗' => '朗', + '望' => '望', + '朡' => '朡', + '杞' => 'æž', + '杓' => 'æ“', + 'ð¯£' => 'ð£ƒ', + '㭉' => 'ã­‰', + '柺' => '柺', + '枅' => 'æž…', + '桒' => 'æ¡’', + '梅' => '梅', + '𣑭' => '𣑭', + '梎' => '梎', + '栟' => 'æ Ÿ', + '椔' => '椔', + '㮝' => 'ã®', + '楂' => '楂', + '榣' => '榣', + '槪' => '槪', + '檨' => '檨', + '𣚣' => '𣚣', + '櫛' => 'æ«›', + '㰘' => 'ã°˜', + '次' => '次', + '𣢧' => '𣢧', + '歔' => 'æ­”', + '㱎' => '㱎', + '歲' => 'æ­²', + '殟' => '殟', + '殺' => '殺', + '殻' => 'æ®»', + '𣪍' => 'ð£ª', + '𡴋' => 'ð¡´‹', + '𣫺' => '𣫺', + '汎' => '汎', + '𣲼' => '𣲼', + '沿' => '沿', + '泍' => 'æ³', + '汧' => 'æ±§', + '洖' => 'æ´–', + '派' => 'æ´¾', + 'ð¯¤' => 'æµ·', + '流' => 'æµ', + '浩' => '浩', + '浸' => '浸', + '涅' => 'æ¶…', + '𣴞' => '𣴞', + '洴' => 'æ´´', + '港' => '港', + '湮' => 'æ¹®', + '㴳' => 'ã´³', + '滋' => '滋', + '滇' => '滇', + 'ð¯¤' => '𣻑', + '淹' => 'æ·¹', + 'ð¯¤' => 'æ½®', + 'ð¯¤' => '𣽞', + '𣾎' => '𣾎', + '濆' => '濆', + '瀹' => '瀹', + '瀞' => '瀞', + '瀛' => '瀛', + '㶖' => 'ã¶–', + '灊' => 'çŠ', + '災' => 'ç½', + '灷' => 'ç·', + '炭' => 'ç‚­', + '𠔥' => '𠔥', + '煅' => 'ç……', + 'ð¯¤' => '𤉣', + '熜' => '熜', + '𤎫' => '𤎫', + '爨' => '爨', + '爵' => '爵', + '牐' => 'ç‰', + '𤘈' => '𤘈', + '犀' => '犀', + '犕' => '犕', + '𤜵' => '𤜵', + '𤠔' => '𤠔', + '獺' => 'çº', + '王' => '王', + '㺬' => '㺬', + '玥' => '玥', + '㺸' => '㺸', + '㺸' => '㺸', + '瑇' => '瑇', + '瑜' => '瑜', + '瑱' => '瑱', + '璅' => 'ç’…', + '瓊' => '瓊', + '㼛' => 'ã¼›', + '甤' => '甤', + '𤰶' => '𤰶', + '甾' => '甾', + '𤲒' => '𤲒', + '異' => 'ç•°', + '𢆟' => '𢆟', + '瘐' => 'ç˜', + '𤾡' => '𤾡', + '𤾸' => '𤾸', + '𥁄' => 'ð¥„', + '㿼' => '㿼', + '䀈' => '䀈', + '直' => 'ç›´', + 'ð¯¥' => '𥃳', + '𥃲' => '𥃲', + '𥄙' => '𥄙', + '𥄳' => '𥄳', + '眞' => '眞', + '真' => '真', + '真' => '真', + '睊' => 'çŠ', + '䀹' => '䀹', + '瞋' => 'çž‹', + '䁆' => 'ä†', + '䂖' => 'ä‚–', + 'ð¯¥' => 'ð¥', + '硎' => '硎', + 'ð¯¥' => '碌', + 'ð¯¥' => '磌', + '䃣' => '䃣', + '𥘦' => '𥘦', + '祖' => '祖', + '𥚚' => '𥚚', + '𥛅' => '𥛅', + '福' => 'ç¦', + '秫' => 'ç§«', + '䄯' => '䄯', + '穀' => 'ç©€', + '穊' => '穊', + '穏' => 'ç©', + '𥥼' => '𥥼', + 'ð¯¥' => '𥪧', + '𥪧' => '𥪧', + '竮' => 'ç«®', + '䈂' => '䈂', + '𥮫' => '𥮫', + '篆' => '篆', + '築' => '築', + '䈧' => '䈧', + '𥲀' => '𥲀', + '糒' => 'ç³’', + '䊠' => '䊠', + '糨' => '糨', + '糣' => 'ç³£', + '紀' => 'ç´€', + '𥾆' => '𥾆', + '絣' => 'çµ£', + '䌁' => 'äŒ', + '緇' => 'ç·‡', + '縂' => '縂', + '繅' => 'ç¹…', + '䌴' => '䌴', + '𦈨' => '𦈨', + '𦉇' => '𦉇', + '䍙' => 'ä™', + '𦋙' => '𦋙', + '罺' => '罺', + '𦌾' => '𦌾', + '羕' => '羕', + '翺' => '翺', + '者' => '者', + '𦓚' => '𦓚', + '𦔣' => '𦔣', + '聠' => 'è ', + '𦖨' => '𦖨', + '聰' => 'è°', + '𣍟' => 'ð£Ÿ', + 'ð¯¦' => 'ä•', + '育' => '育', + '脃' => '脃', + '䐋' => 'ä‹', + '脾' => '脾', + '媵' => '媵', + '𦞧' => '𦞧', + '𦞵' => '𦞵', + '𣎓' => '𣎓', + '𣎜' => '𣎜', + '舁' => 'èˆ', + '舄' => '舄', + 'ð¯¦' => '辞', + '䑫' => 'ä‘«', + 'ð¯¦' => '芑', + 'ð¯¦' => '芋', + '芝' => 'èŠ', + '劳' => '劳', + '花' => '花', + '芳' => '芳', + '芽' => '芽', + '苦' => '苦', + '𦬼' => '𦬼', + '若' => 'è‹¥', + '茝' => 'èŒ', + '荣' => 'è£', + '莭' => '莭', + '茣' => '茣', + 'ð¯¦' => '莽', + '菧' => 'è§', + '著' => 'è‘—', + '荓' => 'è“', + '菊' => 'èŠ', + '菌' => 'èŒ', + '菜' => 'èœ', + '𦰶' => '𦰶', + '𦵫' => '𦵫', + '𦳕' => '𦳕', + '䔫' => '䔫', + '蓱' => '蓱', + '蓳' => '蓳', + '蔖' => 'è”–', + '𧏊' => 'ð§Š', + '蕤' => '蕤', + '𦼬' => '𦼬', + '䕝' => 'ä•', + '䕡' => 'ä•¡', + '𦾱' => '𦾱', + '𧃒' => '𧃒', + '䕫' => 'ä•«', + '虐' => 'è™', + '虜' => '虜', + '虧' => 'è™§', + '虩' => '虩', + '蚩' => 'èš©', + '蚈' => '蚈', + '蜎' => '蜎', + '蛢' => '蛢', + '蝹' => 'è¹', + '蜨' => '蜨', + '蝫' => 'è«', + '螆' => '螆', + '䗗' => 'ä——', + '蟡' => '蟡', + 'ð¯§' => 'è ', + '䗹' => 'ä—¹', + '衠' => 'è¡ ', + '衣' => 'è¡£', + '𧙧' => 'ð§™§', + '裗' => '裗', + '裞' => '裞', + '䘵' => '䘵', + '裺' => '裺', + '㒻' => 'ã’»', + '𧢮' => 'ð§¢®', + '𧥦' => '𧥦', + 'ð¯§' => 'äš¾', + '䛇' => '䛇', + 'ð¯§' => '誠', + 'ð¯§' => 'è«­', + '變' => '變', + '豕' => '豕', + '𧲨' => '𧲨', + '貫' => '貫', + '賁' => 'è³', + '贛' => 'è´›', + '起' => 'èµ·', + '𧼯' => '𧼯', + '𠠄' => 'ð  „', + '跋' => 'è·‹', + '趼' => 'è¶¼', + '跰' => 'è·°', + 'ð¯§' => '𠣞', + '軔' => 'è»”', + '輸' => '輸', + '𨗒' => '𨗒', + '𨗭' => '𨗭', + '邔' => 'é‚”', + '郱' => '郱', + '鄑' => 'é„‘', + '𨜮' => '𨜮', + '鄛' => 'é„›', + '鈸' => '鈸', + '鋗' => 'é‹—', + '鋘' => '鋘', + '鉼' => '鉼', + '鏹' => 'é¹', + '鐕' => 'é•', + '𨯺' => '𨯺', + '開' => 'é–‹', + '䦕' => '䦕', + '閷' => 'é–·', + '𨵷' => '𨵷', + '䧦' => '䧦', + '雃' => '雃', + '嶲' => 'å¶²', + '霣' => '霣', + '𩅅' => 'ð©……', + '𩈚' => '𩈚', + '䩮' => 'ä©®', + '䩶' => 'ä©¶', + '韠' => '韠', + '𩐊' => 'ð©Š', + '䪲' => '䪲', + '𩒖' => 'ð©’–', + '頋' => 'é ‹', + '頋' => 'é ‹', + '頩' => 'é ©', + 'ð¯¨' => 'ð©–¶', + '飢' => '飢', + '䬳' => '䬳', + '餩' => '餩', + '馧' => '馧', + '駂' => 'é§‚', + '駾' => 'é§¾', + '䯎' => '䯎', + '𩬰' => '𩬰', + '鬒' => '鬒', + '鱀' => 'é±€', + '鳽' => 'é³½', + 'ð¯¨' => '䳎', + '䳭' => 'ä³­', + 'ð¯¨' => 'éµ§', + 'ð¯¨' => '𪃎', + '䳸' => '䳸', + '𪄅' => '𪄅', + '𪈎' => '𪈎', + '𪊑' => '𪊑', + '麻' => '麻', + '䵖' => 'äµ–', + '黹' => '黹', + '黾' => '黾', + '鼅' => 'é¼…', + '鼏' => 'é¼', + '鼖' => 'é¼–', + '鼻' => 'é¼»', + 'ð¯¨' => '𪘀', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php new file mode 100644 index 0000000..ec90f36 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php @@ -0,0 +1,876 @@ + 230, + 'Ì' => 230, + 'Ì‚' => 230, + '̃' => 230, + 'Ì„' => 230, + 'Ì…' => 230, + '̆' => 230, + '̇' => 230, + '̈' => 230, + '̉' => 230, + 'ÌŠ' => 230, + 'Ì‹' => 230, + 'ÌŒ' => 230, + 'Ì' => 230, + 'ÌŽ' => 230, + 'Ì' => 230, + 'Ì' => 230, + 'Ì‘' => 230, + 'Ì’' => 230, + 'Ì“' => 230, + 'Ì”' => 230, + 'Ì•' => 232, + 'Ì–' => 220, + 'Ì—' => 220, + '̘' => 220, + 'Ì™' => 220, + 'Ìš' => 232, + 'Ì›' => 216, + 'Ìœ' => 220, + 'Ì' => 220, + 'Ìž' => 220, + 'ÌŸ' => 220, + 'Ì ' => 220, + 'Ì¡' => 202, + 'Ì¢' => 202, + 'Ì£' => 220, + '̤' => 220, + 'Ì¥' => 220, + '̦' => 220, + '̧' => 202, + '̨' => 202, + 'Ì©' => 220, + '̪' => 220, + 'Ì«' => 220, + '̬' => 220, + 'Ì­' => 220, + 'Ì®' => 220, + '̯' => 220, + '̰' => 220, + '̱' => 220, + '̲' => 220, + '̳' => 220, + 'Ì´' => 1, + '̵' => 1, + '̶' => 1, + 'Ì·' => 1, + '̸' => 1, + '̹' => 220, + '̺' => 220, + 'Ì»' => 220, + '̼' => 220, + '̽' => 230, + '̾' => 230, + 'Ì¿' => 230, + 'Í€' => 230, + 'Í' => 230, + 'Í‚' => 230, + '̓' => 230, + 'Í„' => 230, + 'Í…' => 240, + '͆' => 230, + '͇' => 220, + '͈' => 220, + '͉' => 220, + 'ÍŠ' => 230, + 'Í‹' => 230, + 'ÍŒ' => 230, + 'Í' => 220, + 'ÍŽ' => 220, + 'Í' => 230, + 'Í‘' => 230, + 'Í’' => 230, + 'Í“' => 220, + 'Í”' => 220, + 'Í•' => 220, + 'Í–' => 220, + 'Í—' => 230, + '͘' => 232, + 'Í™' => 220, + 'Íš' => 220, + 'Í›' => 230, + 'Íœ' => 233, + 'Í' => 234, + 'Íž' => 234, + 'ÍŸ' => 233, + 'Í ' => 234, + 'Í¡' => 234, + 'Í¢' => 233, + 'Í£' => 230, + 'ͤ' => 230, + 'Í¥' => 230, + 'ͦ' => 230, + 'ͧ' => 230, + 'ͨ' => 230, + 'Í©' => 230, + 'ͪ' => 230, + 'Í«' => 230, + 'ͬ' => 230, + 'Í­' => 230, + 'Í®' => 230, + 'ͯ' => 230, + 'Òƒ' => 230, + 'Ò„' => 230, + 'Ò…' => 230, + 'Ò†' => 230, + 'Ò‡' => 230, + 'Ö‘' => 220, + 'Ö’' => 230, + 'Ö“' => 230, + 'Ö”' => 230, + 'Ö•' => 230, + 'Ö–' => 220, + 'Ö—' => 230, + 'Ö˜' => 230, + 'Ö™' => 230, + 'Öš' => 222, + 'Ö›' => 220, + 'Öœ' => 230, + 'Ö' => 230, + 'Öž' => 230, + 'ÖŸ' => 230, + 'Ö ' => 230, + 'Ö¡' => 230, + 'Ö¢' => 220, + 'Ö£' => 220, + 'Ö¤' => 220, + 'Ö¥' => 220, + 'Ö¦' => 220, + 'Ö§' => 220, + 'Ö¨' => 230, + 'Ö©' => 230, + 'Öª' => 220, + 'Ö«' => 230, + 'Ö¬' => 230, + 'Ö­' => 222, + 'Ö®' => 228, + 'Ö¯' => 230, + 'Ö°' => 10, + 'Ö±' => 11, + 'Ö²' => 12, + 'Ö³' => 13, + 'Ö´' => 14, + 'Öµ' => 15, + 'Ö¶' => 16, + 'Ö·' => 17, + 'Ö¸' => 18, + 'Ö¹' => 19, + 'Öº' => 19, + 'Ö»' => 20, + 'Ö¼' => 21, + 'Ö½' => 22, + 'Ö¿' => 23, + '×' => 24, + 'ׂ' => 25, + 'ׄ' => 230, + '×…' => 220, + 'ׇ' => 18, + 'Ø' => 230, + 'Ø‘' => 230, + 'Ø’' => 230, + 'Ø“' => 230, + 'Ø”' => 230, + 'Ø•' => 230, + 'Ø–' => 230, + 'Ø—' => 230, + 'ؘ' => 30, + 'Ø™' => 31, + 'Øš' => 32, + 'Ù‹' => 27, + 'ÙŒ' => 28, + 'Ù' => 29, + 'ÙŽ' => 30, + 'Ù' => 31, + 'Ù' => 32, + 'Ù‘' => 33, + 'Ù’' => 34, + 'Ù“' => 230, + 'Ù”' => 230, + 'Ù•' => 220, + 'Ù–' => 220, + 'Ù—' => 230, + 'Ù˜' => 230, + 'Ù™' => 230, + 'Ùš' => 230, + 'Ù›' => 230, + 'Ùœ' => 220, + 'Ù' => 230, + 'Ùž' => 230, + 'ÙŸ' => 220, + 'Ù°' => 35, + 'Û–' => 230, + 'Û—' => 230, + 'Û˜' => 230, + 'Û™' => 230, + 'Ûš' => 230, + 'Û›' => 230, + 'Ûœ' => 230, + 'ÛŸ' => 230, + 'Û ' => 230, + 'Û¡' => 230, + 'Û¢' => 230, + 'Û£' => 220, + 'Û¤' => 230, + 'Û§' => 230, + 'Û¨' => 230, + 'Ûª' => 220, + 'Û«' => 230, + 'Û¬' => 230, + 'Û­' => 220, + 'Ü‘' => 36, + 'ܰ' => 230, + 'ܱ' => 220, + 'ܲ' => 230, + 'ܳ' => 230, + 'Ü´' => 220, + 'ܵ' => 230, + 'ܶ' => 230, + 'Ü·' => 220, + 'ܸ' => 220, + 'ܹ' => 220, + 'ܺ' => 230, + 'Ü»' => 220, + 'ܼ' => 220, + 'ܽ' => 230, + 'ܾ' => 220, + 'Ü¿' => 230, + 'Ý€' => 230, + 'Ý' => 230, + 'Ý‚' => 220, + '݃' => 230, + 'Ý„' => 220, + 'Ý…' => 230, + '݆' => 220, + '݇' => 230, + '݈' => 220, + '݉' => 230, + 'ÝŠ' => 230, + 'ß«' => 230, + '߬' => 230, + 'ß­' => 230, + 'ß®' => 230, + '߯' => 230, + 'ß°' => 230, + 'ß±' => 230, + 'ß²' => 220, + 'ß³' => 230, + 'ß½' => 220, + 'à –' => 230, + 'à —' => 230, + 'à ˜' => 230, + 'à ™' => 230, + 'à ›' => 230, + 'à œ' => 230, + 'à ' => 230, + 'à ž' => 230, + 'à Ÿ' => 230, + 'à  ' => 230, + 'à ¡' => 230, + 'à ¢' => 230, + 'à £' => 230, + 'à ¥' => 230, + 'à ¦' => 230, + 'à §' => 230, + 'à ©' => 230, + 'à ª' => 230, + 'à «' => 230, + 'à ¬' => 230, + 'à ­' => 230, + 'à¡™' => 220, + '࡚' => 220, + 'à¡›' => 220, + '࣓' => 220, + 'ࣔ' => 230, + 'ࣕ' => 230, + 'ࣖ' => 230, + 'ࣗ' => 230, + 'ࣘ' => 230, + 'ࣙ' => 230, + 'ࣚ' => 230, + 'ࣛ' => 230, + 'ࣜ' => 230, + 'à£' => 230, + 'ࣞ' => 230, + 'ࣟ' => 230, + '࣠' => 230, + '࣡' => 230, + 'ࣣ' => 220, + 'ࣤ' => 230, + 'ࣥ' => 230, + 'ࣦ' => 220, + 'ࣧ' => 230, + 'ࣨ' => 230, + 'ࣩ' => 220, + '࣪' => 230, + '࣫' => 230, + '࣬' => 230, + '࣭' => 220, + '࣮' => 220, + '࣯' => 220, + 'ࣰ' => 27, + 'ࣱ' => 28, + 'ࣲ' => 29, + 'ࣳ' => 230, + 'ࣴ' => 230, + 'ࣵ' => 230, + 'ࣶ' => 220, + 'ࣷ' => 230, + 'ࣸ' => 230, + 'ࣹ' => 220, + 'ࣺ' => 220, + 'ࣻ' => 230, + 'ࣼ' => 230, + 'ࣽ' => 230, + 'ࣾ' => 230, + 'ࣿ' => 230, + '़' => 7, + 'à¥' => 9, + '॑' => 230, + '॒' => 220, + '॓' => 230, + '॔' => 230, + '়' => 7, + 'à§' => 9, + 'à§¾' => 230, + '਼' => 7, + 'à©' => 9, + '઼' => 7, + 'à«' => 9, + '଼' => 7, + 'à­' => 9, + 'à¯' => 9, + 'à±' => 9, + 'ౕ' => 84, + 'à±–' => 91, + '಼' => 7, + 'à³' => 9, + 'à´»' => 9, + 'à´¼' => 9, + 'àµ' => 9, + 'à·Š' => 9, + 'ุ' => 103, + 'ู' => 103, + 'ฺ' => 9, + '่' => 107, + '้' => 107, + '๊' => 107, + '๋' => 107, + 'ຸ' => 118, + 'ູ' => 118, + '຺' => 9, + '່' => 122, + '້' => 122, + '໊' => 122, + '໋' => 122, + '༘' => 220, + '༙' => 220, + '༵' => 220, + '༷' => 220, + '༹' => 216, + 'ཱ' => 129, + 'ི' => 130, + 'ུ' => 132, + 'ེ' => 130, + 'ཻ' => 130, + 'ོ' => 130, + 'ཽ' => 130, + 'ྀ' => 130, + 'ྂ' => 230, + 'ྃ' => 230, + '྄' => 9, + '྆' => 230, + '྇' => 230, + '࿆' => 220, + '့' => 7, + '္' => 9, + '်' => 9, + 'á‚' => 220, + 'á' => 230, + 'áž' => 230, + 'áŸ' => 230, + '᜔' => 9, + '᜴' => 9, + '្' => 9, + 'áŸ' => 230, + 'ᢩ' => 228, + '᤹' => 222, + '᤺' => 230, + '᤻' => 220, + 'ᨗ' => 230, + 'ᨘ' => 220, + 'á© ' => 9, + '᩵' => 230, + 'á©¶' => 230, + 'á©·' => 230, + '᩸' => 230, + '᩹' => 230, + '᩺' => 230, + 'á©»' => 230, + '᩼' => 230, + 'á©¿' => 220, + '᪰' => 230, + '᪱' => 230, + '᪲' => 230, + '᪳' => 230, + '᪴' => 230, + '᪵' => 220, + '᪶' => 220, + '᪷' => 220, + '᪸' => 220, + '᪹' => 220, + '᪺' => 220, + '᪻' => 230, + '᪼' => 230, + '᪽' => 220, + 'ᪿ' => 220, + 'á«€' => 220, + '᬴' => 7, + 'á­„' => 9, + 'á­«' => 230, + 'á­¬' => 220, + 'á­­' => 230, + 'á­®' => 230, + 'á­¯' => 230, + 'á­°' => 230, + 'á­±' => 230, + 'á­²' => 230, + 'á­³' => 230, + '᮪' => 9, + '᮫' => 9, + '᯦' => 7, + '᯲' => 9, + '᯳' => 9, + 'á°·' => 7, + 'á³' => 230, + '᳑' => 230, + 'á³’' => 230, + 'á³”' => 1, + '᳕' => 220, + 'á³–' => 220, + 'á³—' => 220, + '᳘' => 220, + 'á³™' => 220, + '᳚' => 230, + 'á³›' => 230, + '᳜' => 220, + 'á³' => 220, + '᳞' => 220, + '᳟' => 220, + 'á³ ' => 230, + 'á³¢' => 1, + 'á³£' => 1, + '᳤' => 1, + 'á³¥' => 1, + '᳦' => 1, + 'á³§' => 1, + '᳨' => 1, + 'á³­' => 220, + 'á³´' => 230, + '᳸' => 230, + 'á³¹' => 230, + 'á·€' => 230, + 'á·' => 230, + 'á·‚' => 220, + 'á·ƒ' => 230, + 'á·„' => 230, + 'á·…' => 230, + 'á·†' => 230, + 'á·‡' => 230, + 'á·ˆ' => 230, + 'á·‰' => 230, + 'á·Š' => 220, + 'á·‹' => 230, + 'á·Œ' => 230, + 'á·' => 234, + 'á·Ž' => 214, + 'á·' => 220, + 'á·' => 202, + 'á·‘' => 230, + 'á·’' => 230, + 'á·“' => 230, + 'á·”' => 230, + 'á·•' => 230, + 'á·–' => 230, + 'á·—' => 230, + 'á·˜' => 230, + 'á·™' => 230, + 'á·š' => 230, + 'á·›' => 230, + 'á·œ' => 230, + 'á·' => 230, + 'á·ž' => 230, + 'á·Ÿ' => 230, + 'á· ' => 230, + 'á·¡' => 230, + 'á·¢' => 230, + 'á·£' => 230, + 'á·¤' => 230, + 'á·¥' => 230, + 'á·¦' => 230, + 'á·§' => 230, + 'á·¨' => 230, + 'á·©' => 230, + 'á·ª' => 230, + 'á·«' => 230, + 'á·¬' => 230, + 'á·­' => 230, + 'á·®' => 230, + 'á·¯' => 230, + 'á·°' => 230, + 'á·±' => 230, + 'á·²' => 230, + 'á·³' => 230, + 'á·´' => 230, + 'á·µ' => 230, + 'á·¶' => 232, + 'á··' => 228, + 'á·¸' => 228, + 'á·¹' => 220, + 'á·»' => 230, + 'á·¼' => 233, + 'á·½' => 220, + 'á·¾' => 230, + 'á·¿' => 220, + 'âƒ' => 230, + '⃑' => 230, + '⃒' => 1, + '⃓' => 1, + '⃔' => 230, + '⃕' => 230, + '⃖' => 230, + '⃗' => 230, + '⃘' => 1, + '⃙' => 1, + '⃚' => 1, + '⃛' => 230, + '⃜' => 230, + '⃡' => 230, + '⃥' => 1, + '⃦' => 1, + '⃧' => 230, + '⃨' => 220, + '⃩' => 230, + '⃪' => 1, + '⃫' => 1, + '⃬' => 220, + '⃭' => 220, + '⃮' => 220, + '⃯' => 220, + '⃰' => 230, + '⳯' => 230, + 'â³°' => 230, + 'â³±' => 230, + '⵿' => 9, + 'â· ' => 230, + 'â·¡' => 230, + 'â·¢' => 230, + 'â·£' => 230, + 'â·¤' => 230, + 'â·¥' => 230, + 'â·¦' => 230, + 'â·§' => 230, + 'â·¨' => 230, + 'â·©' => 230, + 'â·ª' => 230, + 'â·«' => 230, + 'â·¬' => 230, + 'â·­' => 230, + 'â·®' => 230, + 'â·¯' => 230, + 'â·°' => 230, + 'â·±' => 230, + 'â·²' => 230, + 'â·³' => 230, + 'â·´' => 230, + 'â·µ' => 230, + 'â·¶' => 230, + 'â··' => 230, + 'â·¸' => 230, + 'â·¹' => 230, + 'â·º' => 230, + 'â·»' => 230, + 'â·¼' => 230, + 'â·½' => 230, + 'â·¾' => 230, + 'â·¿' => 230, + '〪' => 218, + '〫' => 228, + '〬' => 232, + '〭' => 222, + '〮' => 224, + '〯' => 224, + 'ã‚™' => 8, + '゚' => 8, + '꙯' => 230, + 'ê™´' => 230, + 'ꙵ' => 230, + 'ê™¶' => 230, + 'ê™·' => 230, + 'ꙸ' => 230, + 'ꙹ' => 230, + 'ꙺ' => 230, + 'ê™»' => 230, + '꙼' => 230, + '꙽' => 230, + 'êšž' => 230, + 'ꚟ' => 230, + 'ê›°' => 230, + 'ê›±' => 230, + 'ê †' => 9, + 'ê ¬' => 9, + '꣄' => 9, + '꣠' => 230, + '꣡' => 230, + '꣢' => 230, + '꣣' => 230, + '꣤' => 230, + '꣥' => 230, + '꣦' => 230, + '꣧' => 230, + '꣨' => 230, + '꣩' => 230, + '꣪' => 230, + '꣫' => 230, + '꣬' => 230, + '꣭' => 230, + '꣮' => 230, + '꣯' => 230, + '꣰' => 230, + '꣱' => 230, + '꤫' => 220, + '꤬' => 220, + '꤭' => 220, + '꥓' => 9, + '꦳' => 7, + 'ê§€' => 9, + 'ꪰ' => 230, + 'ꪲ' => 230, + 'ꪳ' => 230, + 'ꪴ' => 220, + 'ꪷ' => 230, + 'ꪸ' => 230, + 'ꪾ' => 230, + '꪿' => 230, + 'ê«' => 230, + 'ê«¶' => 9, + '꯭' => 9, + 'ﬞ' => 26, + '︠' => 230, + '︡' => 230, + '︢' => 230, + '︣' => 230, + '︤' => 230, + '︥' => 230, + '︦' => 230, + '︧' => 220, + '︨' => 220, + '︩' => 220, + '︪' => 220, + '︫' => 220, + '︬' => 220, + '︭' => 220, + '︮' => 230, + '︯' => 230, + 'ð‡½' => 220, + 'ð‹ ' => 220, + 'ð¶' => 230, + 'ð·' => 230, + 'ð¸' => 230, + 'ð¹' => 230, + 'ðº' => 230, + 'ð¨' => 220, + 'ð¨' => 230, + 'ð¨¸' => 230, + 'ð¨¹' => 1, + 'ð¨º' => 220, + 'ð¨¿' => 9, + 'ð«¥' => 230, + 'ð«¦' => 220, + 'ð´¤' => 230, + 'ð´¥' => 230, + 'ð´¦' => 230, + 'ð´§' => 230, + 'ðº«' => 230, + 'ðº¬' => 230, + 'ð½†' => 220, + 'ð½‡' => 220, + 'ð½ˆ' => 230, + 'ð½‰' => 230, + 'ð½Š' => 230, + 'ð½‹' => 220, + 'ð½Œ' => 230, + 'ð½' => 220, + 'ð½Ž' => 220, + 'ð½' => 220, + 'ð½' => 220, + 'ð‘†' => 9, + 'ð‘¿' => 9, + 'ð‘‚¹' => 9, + '𑂺' => 7, + 'ð‘„€' => 230, + 'ð‘„' => 230, + 'ð‘„‚' => 230, + 'ð‘„³' => 9, + 'ð‘„´' => 9, + 'ð‘…³' => 7, + '𑇀' => 9, + '𑇊' => 7, + '𑈵' => 9, + '𑈶' => 7, + 'ð‘‹©' => 7, + '𑋪' => 9, + '𑌻' => 7, + '𑌼' => 7, + 'ð‘' => 9, + 'ð‘¦' => 230, + 'ð‘§' => 230, + 'ð‘¨' => 230, + 'ð‘©' => 230, + 'ð‘ª' => 230, + 'ð‘«' => 230, + 'ð‘¬' => 230, + 'ð‘°' => 230, + 'ð‘±' => 230, + 'ð‘²' => 230, + 'ð‘³' => 230, + 'ð‘´' => 230, + 'ð‘‘‚' => 9, + '𑑆' => 7, + 'ð‘‘ž' => 230, + 'ð‘“‚' => 9, + '𑓃' => 7, + 'ð‘–¿' => 9, + 'ð‘—€' => 7, + '𑘿' => 9, + '𑚶' => 9, + '𑚷' => 7, + '𑜫' => 9, + 'ð‘ ¹' => 9, + 'ð‘ º' => 7, + '𑤽' => 9, + '𑤾' => 9, + '𑥃' => 7, + 'ð‘§ ' => 9, + '𑨴' => 9, + '𑩇' => 9, + '𑪙' => 9, + 'ð‘°¿' => 9, + '𑵂' => 7, + '𑵄' => 9, + '𑵅' => 9, + 'ð‘¶—' => 9, + 'ð–«°' => 1, + 'ð–«±' => 1, + 'ð–«²' => 1, + 'ð–«³' => 1, + 'ð–«´' => 1, + 'ð–¬°' => 230, + '𖬱' => 230, + '𖬲' => 230, + '𖬳' => 230, + 'ð–¬´' => 230, + '𖬵' => 230, + '𖬶' => 230, + 'ð–¿°' => 6, + 'ð–¿±' => 6, + '𛲞' => 1, + 'ð…¥' => 216, + 'ð…¦' => 216, + 'ð…§' => 1, + 'ð…¨' => 1, + 'ð…©' => 1, + 'ð…­' => 226, + 'ð…®' => 216, + 'ð…¯' => 216, + 'ð…°' => 216, + 'ð…±' => 216, + 'ð…²' => 216, + 'ð…»' => 220, + 'ð…¼' => 220, + 'ð…½' => 220, + 'ð…¾' => 220, + 'ð…¿' => 220, + 'ð†€' => 220, + 'ð†' => 220, + 'ð†‚' => 220, + 'ð†…' => 230, + 'ð††' => 230, + 'ð†‡' => 230, + 'ð†ˆ' => 230, + 'ð†‰' => 230, + 'ð†Š' => 220, + 'ð†‹' => 220, + 'ð†ª' => 230, + 'ð†«' => 230, + 'ð†¬' => 230, + 'ð†­' => 230, + 'ð‰‚' => 230, + 'ð‰ƒ' => 230, + 'ð‰„' => 230, + '𞀀' => 230, + 'ðž€' => 230, + '𞀂' => 230, + '𞀃' => 230, + '𞀄' => 230, + '𞀅' => 230, + '𞀆' => 230, + '𞀈' => 230, + '𞀉' => 230, + '𞀊' => 230, + '𞀋' => 230, + '𞀌' => 230, + 'ðž€' => 230, + '𞀎' => 230, + 'ðž€' => 230, + 'ðž€' => 230, + '𞀑' => 230, + '𞀒' => 230, + '𞀓' => 230, + '𞀔' => 230, + '𞀕' => 230, + '𞀖' => 230, + '𞀗' => 230, + '𞀘' => 230, + '𞀛' => 230, + '𞀜' => 230, + 'ðž€' => 230, + '𞀞' => 230, + '𞀟' => 230, + '𞀠' => 230, + '𞀡' => 230, + '𞀣' => 230, + '𞀤' => 230, + '𞀦' => 230, + '𞀧' => 230, + '𞀨' => 230, + '𞀩' => 230, + '𞀪' => 230, + 'ðž„°' => 230, + '𞄱' => 230, + '𞄲' => 230, + '𞄳' => 230, + 'ðž„´' => 230, + '𞄵' => 230, + 'ðž„¶' => 230, + '𞋬' => 230, + 'ðž‹­' => 230, + 'ðž‹®' => 230, + '𞋯' => 230, + 'ðž£' => 220, + '𞣑' => 220, + '𞣒' => 220, + '𞣓' => 220, + '𞣔' => 220, + '𞣕' => 220, + '𞣖' => 220, + '𞥄' => 230, + '𞥅' => 230, + '𞥆' => 230, + '𞥇' => 230, + '𞥈' => 230, + '𞥉' => 230, + '𞥊' => 7, +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php new file mode 100644 index 0000000..1574902 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php @@ -0,0 +1,3695 @@ + ' ', + '¨' => ' ̈', + 'ª' => 'a', + '¯' => ' Ì„', + '²' => '2', + '³' => '3', + '´' => ' Ì', + 'µ' => 'μ', + '¸' => ' ̧', + '¹' => '1', + 'º' => 'o', + '¼' => '1â„4', + '½' => '1â„2', + '¾' => '3â„4', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ä¿' => 'L·', + 'Å€' => 'l·', + 'ʼn' => 'ʼn', + 'Å¿' => 's', + 'Ç„' => 'DZÌŒ', + 'Ç…' => 'DzÌŒ', + 'dž' => 'dzÌŒ', + 'LJ' => 'LJ', + 'Lj' => 'Lj', + 'lj' => 'lj', + 'ÇŠ' => 'NJ', + 'Ç‹' => 'Nj', + 'ÇŒ' => 'nj', + 'DZ' => 'DZ', + 'Dz' => 'Dz', + 'dz' => 'dz', + 'ʰ' => 'h', + 'ʱ' => 'ɦ', + 'ʲ' => 'j', + 'ʳ' => 'r', + 'Ê´' => 'ɹ', + 'ʵ' => 'É»', + 'ʶ' => 'Ê', + 'Ê·' => 'w', + 'ʸ' => 'y', + '˘' => ' ̆', + 'Ë™' => ' ̇', + 'Ëš' => ' ÌŠ', + 'Ë›' => ' ̨', + 'Ëœ' => ' ̃', + 'Ë' => ' Ì‹', + 'Ë ' => 'É£', + 'Ë¡' => 'l', + 'Ë¢' => 's', + 'Ë£' => 'x', + 'ˤ' => 'Ê•', + 'ͺ' => ' Í…', + '΄' => ' Ì', + 'Î…' => ' ̈Ì', + 'Ï' => 'β', + 'Ï‘' => 'θ', + 'Ï’' => 'Î¥', + 'Ï“' => 'Î¥Ì', + 'Ï”' => 'Ϋ', + 'Ï•' => 'φ', + 'Ï–' => 'Ï€', + 'ϰ' => 'κ', + 'ϱ' => 'Ï', + 'ϲ' => 'Ï‚', + 'Ï´' => 'Θ', + 'ϵ' => 'ε', + 'Ϲ' => 'Σ', + 'Ö‡' => 'Õ¥Ö‚', + 'Ùµ' => 'اٴ', + 'Ù¶' => 'وٴ', + 'Ù·' => 'Û‡Ù´', + 'Ù¸' => 'يٴ', + 'ำ' => 'à¹à¸²', + 'ຳ' => 'à»àº²', + 'ໜ' => 'ຫນ', + 'à»' => 'ຫມ', + '༌' => '་', + 'ཷ' => 'ྲཱྀ', + 'ཹ' => 'ླཱྀ', + 'ჼ' => 'ნ', + 'á´¬' => 'A', + 'á´­' => 'Æ', + 'á´®' => 'B', + 'á´°' => 'D', + 'á´±' => 'E', + 'á´²' => 'ÆŽ', + 'á´³' => 'G', + 'á´´' => 'H', + 'á´µ' => 'I', + 'á´¶' => 'J', + 'á´·' => 'K', + 'á´¸' => 'L', + 'á´¹' => 'M', + 'á´º' => 'N', + 'á´¼' => 'O', + 'á´½' => 'È¢', + 'á´¾' => 'P', + 'á´¿' => 'R', + 'áµ€' => 'T', + 'áµ' => 'U', + 'ᵂ' => 'W', + 'ᵃ' => 'a', + 'ᵄ' => 'É', + 'áµ…' => 'É‘', + 'ᵆ' => 'á´‚', + 'ᵇ' => 'b', + 'ᵈ' => 'd', + 'ᵉ' => 'e', + 'ᵊ' => 'É™', + 'ᵋ' => 'É›', + 'ᵌ' => 'Éœ', + 'áµ' => 'g', + 'áµ' => 'k', + 'áµ' => 'm', + 'ᵑ' => 'Å‹', + 'áµ’' => 'o', + 'ᵓ' => 'É”', + 'áµ”' => 'á´–', + 'ᵕ' => 'á´—', + 'áµ–' => 'p', + 'áµ—' => 't', + 'ᵘ' => 'u', + 'áµ™' => 'á´', + 'ᵚ' => 'ɯ', + 'áµ›' => 'v', + 'ᵜ' => 'á´¥', + 'áµ' => 'β', + 'ᵞ' => 'γ', + 'ᵟ' => 'δ', + 'áµ ' => 'φ', + 'ᵡ' => 'χ', + 'áµ¢' => 'i', + 'áµ£' => 'r', + 'ᵤ' => 'u', + 'áµ¥' => 'v', + 'ᵦ' => 'β', + 'áµ§' => 'γ', + 'ᵨ' => 'Ï', + 'ᵩ' => 'φ', + 'ᵪ' => 'χ', + 'ᵸ' => 'н', + 'á¶›' => 'É’', + 'á¶œ' => 'c', + 'á¶' => 'É•', + 'á¶ž' => 'ð', + 'á¶Ÿ' => 'Éœ', + 'á¶ ' => 'f', + 'á¶¡' => 'ÉŸ', + 'á¶¢' => 'É¡', + 'á¶£' => 'É¥', + 'ᶤ' => 'ɨ', + 'á¶¥' => 'É©', + 'ᶦ' => 'ɪ', + 'á¶§' => 'áµ»', + 'ᶨ' => 'Ê', + 'á¶©' => 'É­', + 'ᶪ' => 'á¶…', + 'á¶«' => 'ÊŸ', + 'ᶬ' => 'ɱ', + 'á¶­' => 'ɰ', + 'á¶®' => 'ɲ', + 'ᶯ' => 'ɳ', + 'á¶°' => 'É´', + 'á¶±' => 'ɵ', + 'á¶²' => 'ɸ', + 'á¶³' => 'Ê‚', + 'á¶´' => 'ʃ', + 'á¶µ' => 'Æ«', + 'á¶¶' => 'ʉ', + 'á¶·' => 'ÊŠ', + 'ᶸ' => 'á´œ', + 'á¶¹' => 'Ê‹', + 'ᶺ' => 'ÊŒ', + 'á¶»' => 'z', + 'á¶¼' => 'Ê', + 'á¶½' => 'Ê‘', + 'á¶¾' => 'Ê’', + 'á¶¿' => 'θ', + 'ẚ' => 'aʾ', + 'ẛ' => 'ṡ', + 'á¾½' => ' Ì“', + '᾿' => ' Ì“', + 'á¿€' => ' Í‚', + 'á¿' => ' ̈͂', + 'á¿' => ' ̓̀', + '῎' => ' Ì“Ì', + 'á¿' => ' ̓͂', + 'á¿' => ' ̔̀', + '῞' => ' Ì”Ì', + '῟' => ' ̔͂', + 'á¿­' => ' ̈̀', + 'á¿®' => ' ̈Ì', + '´' => ' Ì', + '῾' => ' Ì”', + ' ' => ' ', + 'â€' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + '‑' => 'â€', + '‗' => ' ̳', + '․' => '.', + '‥' => '..', + '…' => '...', + ' ' => ' ', + '″' => '′′', + '‴' => '′′′', + '‶' => '‵‵', + '‷' => '‵‵‵', + '‼' => '!!', + '‾' => ' Ì…', + 'â‡' => '??', + 'âˆ' => '?!', + 'â‰' => '!?', + 'â—' => '′′′′', + 'âŸ' => ' ', + 'â°' => '0', + 'â±' => 'i', + 'â´' => '4', + 'âµ' => '5', + 'â¶' => '6', + 'â·' => '7', + 'â¸' => '8', + 'â¹' => '9', + 'âº' => '+', + 'â»' => '−', + 'â¼' => '=', + 'â½' => '(', + 'â¾' => ')', + 'â¿' => 'n', + 'â‚€' => '0', + 'â‚' => '1', + 'â‚‚' => '2', + '₃' => '3', + 'â‚„' => '4', + 'â‚…' => '5', + '₆' => '6', + '₇' => '7', + '₈' => '8', + '₉' => '9', + '₊' => '+', + 'â‚‹' => '−', + '₌' => '=', + 'â‚' => '(', + '₎' => ')', + 'â‚' => 'a', + 'â‚‘' => 'e', + 'â‚’' => 'o', + 'â‚“' => 'x', + 'â‚”' => 'É™', + 'â‚•' => 'h', + 'â‚–' => 'k', + 'â‚—' => 'l', + 'ₘ' => 'm', + 'â‚™' => 'n', + 'ₚ' => 'p', + 'â‚›' => 's', + 'ₜ' => 't', + '₨' => 'Rs', + 'â„€' => 'a/c', + 'â„' => 'a/s', + 'â„‚' => 'C', + '℃' => '°C', + 'â„…' => 'c/o', + '℆' => 'c/u', + 'ℇ' => 'Æ', + '℉' => '°F', + 'ℊ' => 'g', + 'â„‹' => 'H', + 'ℌ' => 'H', + 'â„' => 'H', + 'ℎ' => 'h', + 'â„' => 'ħ', + 'â„' => 'I', + 'â„‘' => 'I', + 'â„’' => 'L', + 'â„“' => 'l', + 'â„•' => 'N', + 'â„–' => 'No', + 'â„™' => 'P', + 'ℚ' => 'Q', + 'â„›' => 'R', + 'ℜ' => 'R', + 'â„' => 'R', + 'â„ ' => 'SM', + 'â„¡' => 'TEL', + 'â„¢' => 'TM', + 'ℤ' => 'Z', + 'ℨ' => 'Z', + 'ℬ' => 'B', + 'â„­' => 'C', + 'ℯ' => 'e', + 'â„°' => 'E', + 'ℱ' => 'F', + 'ℳ' => 'M', + 'â„´' => 'o', + 'ℵ' => '×', + 'â„¶' => 'ב', + 'â„·' => '×’', + 'ℸ' => 'ד', + 'ℹ' => 'i', + 'â„»' => 'FAX', + 'ℼ' => 'Ï€', + 'ℽ' => 'γ', + 'ℾ' => 'Γ', + 'â„¿' => 'Π', + 'â…€' => '∑', + 'â……' => 'D', + 'â…†' => 'd', + 'â…‡' => 'e', + 'â…ˆ' => 'i', + 'â…‰' => 'j', + 'â…' => '1â„7', + 'â…‘' => '1â„9', + 'â…’' => '1â„10', + 'â…“' => '1â„3', + 'â…”' => '2â„3', + 'â…•' => '1â„5', + 'â…–' => '2â„5', + 'â…—' => '3â„5', + 'â…˜' => '4â„5', + 'â…™' => '1â„6', + 'â…š' => '5â„6', + 'â…›' => '1â„8', + 'â…œ' => '3â„8', + 'â…' => '5â„8', + 'â…ž' => '7â„8', + 'â…Ÿ' => '1â„', + 'â… ' => 'I', + 'â…¡' => 'II', + 'â…¢' => 'III', + 'â…£' => 'IV', + 'â…¤' => 'V', + 'â…¥' => 'VI', + 'â…¦' => 'VII', + 'â…§' => 'VIII', + 'â…¨' => 'IX', + 'â…©' => 'X', + 'â…ª' => 'XI', + 'â…«' => 'XII', + 'â…¬' => 'L', + 'â…­' => 'C', + 'â…®' => 'D', + 'â…¯' => 'M', + 'â…°' => 'i', + 'â…±' => 'ii', + 'â…²' => 'iii', + 'â…³' => 'iv', + 'â…´' => 'v', + 'â…µ' => 'vi', + 'â…¶' => 'vii', + 'â…·' => 'viii', + 'â…¸' => 'ix', + 'â…¹' => 'x', + 'â…º' => 'xi', + 'â…»' => 'xii', + 'â…¼' => 'l', + 'â…½' => 'c', + 'â…¾' => 'd', + 'â…¿' => 'm', + '↉' => '0â„3', + '∬' => '∫∫', + '∭' => '∫∫∫', + '∯' => '∮∮', + '∰' => '∮∮∮', + 'â‘ ' => '1', + 'â‘¡' => '2', + 'â‘¢' => '3', + 'â‘£' => '4', + '⑤' => '5', + 'â‘¥' => '6', + '⑦' => '7', + 'â‘§' => '8', + '⑨' => '9', + 'â‘©' => '10', + '⑪' => '11', + 'â‘«' => '12', + '⑬' => '13', + 'â‘­' => '14', + 'â‘®' => '15', + '⑯' => '16', + 'â‘°' => '17', + '⑱' => '18', + '⑲' => '19', + '⑳' => '20', + 'â‘´' => '(1)', + '⑵' => '(2)', + 'â‘¶' => '(3)', + 'â‘·' => '(4)', + '⑸' => '(5)', + '⑹' => '(6)', + '⑺' => '(7)', + 'â‘»' => '(8)', + '⑼' => '(9)', + '⑽' => '(10)', + '⑾' => '(11)', + 'â‘¿' => '(12)', + 'â’€' => '(13)', + 'â’' => '(14)', + 'â’‚' => '(15)', + 'â’ƒ' => '(16)', + 'â’„' => '(17)', + 'â’…' => '(18)', + 'â’†' => '(19)', + 'â’‡' => '(20)', + 'â’ˆ' => '1.', + 'â’‰' => '2.', + 'â’Š' => '3.', + 'â’‹' => '4.', + 'â’Œ' => '5.', + 'â’' => '6.', + 'â’Ž' => '7.', + 'â’' => '8.', + 'â’' => '9.', + 'â’‘' => '10.', + 'â’’' => '11.', + 'â’“' => '12.', + 'â’”' => '13.', + 'â’•' => '14.', + 'â’–' => '15.', + 'â’—' => '16.', + 'â’˜' => '17.', + 'â’™' => '18.', + 'â’š' => '19.', + 'â’›' => '20.', + 'â’œ' => '(a)', + 'â’' => '(b)', + 'â’ž' => '(c)', + 'â’Ÿ' => '(d)', + 'â’ ' => '(e)', + 'â’¡' => '(f)', + 'â’¢' => '(g)', + 'â’£' => '(h)', + 'â’¤' => '(i)', + 'â’¥' => '(j)', + 'â’¦' => '(k)', + 'â’§' => '(l)', + 'â’¨' => '(m)', + 'â’©' => '(n)', + 'â’ª' => '(o)', + 'â’«' => '(p)', + 'â’¬' => '(q)', + 'â’­' => '(r)', + 'â’®' => '(s)', + 'â’¯' => '(t)', + 'â’°' => '(u)', + 'â’±' => '(v)', + 'â’²' => '(w)', + 'â’³' => '(x)', + 'â’´' => '(y)', + 'â’µ' => '(z)', + 'â’¶' => 'A', + 'â’·' => 'B', + 'â’¸' => 'C', + 'â’¹' => 'D', + 'â’º' => 'E', + 'â’»' => 'F', + 'â’¼' => 'G', + 'â’½' => 'H', + 'â’¾' => 'I', + 'â’¿' => 'J', + 'â“€' => 'K', + 'â“' => 'L', + 'â“‚' => 'M', + 'Ⓝ' => 'N', + 'â“„' => 'O', + 'â“…' => 'P', + 'Ⓠ' => 'Q', + 'Ⓡ' => 'R', + 'Ⓢ' => 'S', + 'Ⓣ' => 'T', + 'Ⓤ' => 'U', + 'â“‹' => 'V', + 'Ⓦ' => 'W', + 'â“' => 'X', + 'Ⓨ' => 'Y', + 'â“' => 'Z', + 'â“' => 'a', + 'â“‘' => 'b', + 'â“’' => 'c', + 'â““' => 'd', + 'â“”' => 'e', + 'â“•' => 'f', + 'â“–' => 'g', + 'â“—' => 'h', + 'ⓘ' => 'i', + 'â“™' => 'j', + 'ⓚ' => 'k', + 'â“›' => 'l', + 'ⓜ' => 'm', + 'â“' => 'n', + 'ⓞ' => 'o', + 'ⓟ' => 'p', + 'â“ ' => 'q', + 'â“¡' => 'r', + 'â“¢' => 's', + 'â“£' => 't', + 'ⓤ' => 'u', + 'â“¥' => 'v', + 'ⓦ' => 'w', + 'â“§' => 'x', + 'ⓨ' => 'y', + 'â“©' => 'z', + '⓪' => '0', + '⨌' => '∫∫∫∫', + 'â©´' => '::=', + '⩵' => '==', + 'â©¶' => '===', + 'â±¼' => 'j', + 'â±½' => 'V', + 'ⵯ' => 'ⵡ', + '⺟' => 'æ¯', + '⻳' => '龟', + 'â¼€' => '一', + 'â¼' => '丨', + '⼂' => '丶', + '⼃' => '丿', + '⼄' => 'ä¹™', + 'â¼…' => '亅', + '⼆' => '二', + '⼇' => '亠', + '⼈' => '人', + '⼉' => 'å„¿', + '⼊' => 'å…¥', + '⼋' => 'å…«', + '⼌' => '冂', + 'â¼' => '冖', + '⼎' => '冫', + 'â¼' => '几', + 'â¼' => '凵', + '⼑' => '刀', + 'â¼’' => '力', + '⼓' => '勹', + 'â¼”' => '匕', + '⼕' => '匚', + 'â¼–' => '匸', + 'â¼—' => 'å', + '⼘' => 'åœ', + 'â¼™' => 'å©', + '⼚' => '厂', + 'â¼›' => '厶', + '⼜' => 'åˆ', + 'â¼' => 'å£', + '⼞' => 'å›—', + '⼟' => '土', + 'â¼ ' => '士', + '⼡' => '夂', + 'â¼¢' => '夊', + 'â¼£' => '夕', + '⼤' => '大', + 'â¼¥' => '女', + '⼦' => 'å­', + 'â¼§' => '宀', + '⼨' => '寸', + '⼩' => 'å°', + '⼪' => 'å°¢', + '⼫' => 'å°¸', + '⼬' => 'å±®', + 'â¼­' => 'å±±', + 'â¼®' => 'å·›', + '⼯' => 'å·¥', + 'â¼°' => 'å·±', + 'â¼±' => 'å·¾', + 'â¼²' => 'å¹²', + 'â¼³' => '幺', + 'â¼´' => '广', + 'â¼µ' => 'å»´', + 'â¼¶' => '廾', + 'â¼·' => '弋', + '⼸' => '弓', + 'â¼¹' => 'å½', + '⼺' => '彡', + 'â¼»' => 'å½³', + 'â¼¼' => '心', + 'â¼½' => '戈', + 'â¼¾' => '戶', + '⼿' => '手', + 'â½€' => '支', + 'â½' => 'æ”´', + '⽂' => 'æ–‡', + '⽃' => 'æ–—', + '⽄' => 'æ–¤', + 'â½…' => 'æ–¹', + '⽆' => 'æ— ', + '⽇' => 'æ—¥', + '⽈' => 'æ›°', + '⽉' => '月', + '⽊' => '木', + '⽋' => '欠', + '⽌' => 'æ­¢', + 'â½' => 'æ­¹', + '⽎' => '殳', + 'â½' => '毋', + 'â½' => '比', + '⽑' => '毛', + 'â½’' => 'æ°', + '⽓' => 'æ°”', + 'â½”' => 'æ°´', + '⽕' => 'ç«', + 'â½–' => '爪', + 'â½—' => '父', + '⽘' => '爻', + 'â½™' => '爿', + '⽚' => '片', + 'â½›' => '牙', + '⽜' => '牛', + 'â½' => '犬', + '⽞' => '玄', + '⽟' => '玉', + 'â½ ' => '瓜', + '⽡' => '瓦', + 'â½¢' => '甘', + 'â½£' => '生', + '⽤' => '用', + 'â½¥' => 'ç”°', + '⽦' => 'ç–‹', + 'â½§' => 'ç–’', + '⽨' => 'ç™¶', + '⽩' => '白', + '⽪' => 'çš®', + '⽫' => 'çš¿', + '⽬' => 'ç›®', + 'â½­' => '矛', + 'â½®' => '矢', + '⽯' => '石', + 'â½°' => '示', + 'â½±' => '禸', + 'â½²' => '禾', + 'â½³' => 'ç©´', + 'â½´' => 'ç«‹', + 'â½µ' => '竹', + 'â½¶' => 'ç±³', + 'â½·' => '糸', + '⽸' => 'ç¼¶', + 'â½¹' => '网', + '⽺' => '羊', + 'â½»' => 'ç¾½', + 'â½¼' => 'è€', + 'â½½' => '而', + 'â½¾' => '耒', + '⽿' => '耳', + 'â¾€' => 'è¿', + 'â¾' => '肉', + '⾂' => '臣', + '⾃' => '自', + '⾄' => '至', + 'â¾…' => '臼', + '⾆' => '舌', + '⾇' => '舛', + '⾈' => '舟', + '⾉' => '艮', + '⾊' => '色', + '⾋' => '艸', + '⾌' => 'è™', + 'â¾' => '虫', + '⾎' => 'è¡€', + 'â¾' => '行', + 'â¾' => 'è¡£', + '⾑' => '襾', + 'â¾’' => '見', + '⾓' => 'è§’', + 'â¾”' => '言', + '⾕' => 'è°·', + 'â¾–' => '豆', + 'â¾—' => '豕', + '⾘' => '豸', + 'â¾™' => 'è²', + '⾚' => '赤', + 'â¾›' => 'èµ°', + '⾜' => 'è¶³', + 'â¾' => '身', + '⾞' => '車', + '⾟' => 'è¾›', + 'â¾ ' => 'è¾°', + '⾡' => 'è¾µ', + 'â¾¢' => 'é‚‘', + 'â¾£' => 'é…‰', + '⾤' => '釆', + 'â¾¥' => '里', + '⾦' => '金', + 'â¾§' => 'é•·', + '⾨' => 'é–€', + '⾩' => '阜', + '⾪' => 'éš¶', + '⾫' => 'éš¹', + '⾬' => '雨', + 'â¾­' => 'é‘', + 'â¾®' => 'éž', + '⾯' => 'é¢', + 'â¾°' => 'é©', + 'â¾±' => '韋', + 'â¾²' => '韭', + 'â¾³' => '音', + 'â¾´' => 'é ', + 'â¾µ' => '風', + 'â¾¶' => '飛', + 'â¾·' => '食', + '⾸' => '首', + 'â¾¹' => '香', + '⾺' => '馬', + 'â¾»' => '骨', + 'â¾¼' => '高', + 'â¾½' => '髟', + 'â¾¾' => '鬥', + '⾿' => '鬯', + 'â¿€' => '鬲', + 'â¿' => '鬼', + 'â¿‚' => 'é­š', + '⿃' => 'é³¥', + 'â¿„' => 'é¹µ', + 'â¿…' => '鹿', + '⿆' => '麥', + '⿇' => '麻', + '⿈' => '黃', + '⿉' => 'é»', + '⿊' => '黑', + 'â¿‹' => '黹', + '⿌' => '黽', + 'â¿' => '鼎', + '⿎' => '鼓', + 'â¿' => 'é¼ ', + 'â¿' => 'é¼»', + 'â¿‘' => '齊', + 'â¿’' => 'é½’', + 'â¿“' => 'é¾', + 'â¿”' => '龜', + 'â¿•' => 'é¾ ', + ' ' => ' ', + '〶' => '〒', + '〸' => 'å', + '〹' => 'å„', + '〺' => 'å…', + 'ã‚›' => ' ã‚™', + '゜' => ' ゚', + 'ゟ' => 'より', + 'ヿ' => 'コト', + 'ㄱ' => 'á„€', + 'ㄲ' => 'á„', + 'ㄳ' => 'ᆪ', + 'ã„´' => 'á„‚', + 'ㄵ' => 'ᆬ', + 'ã„¶' => 'ᆭ', + 'ã„·' => 'ᄃ', + 'ㄸ' => 'á„„', + 'ㄹ' => 'á„…', + 'ㄺ' => 'ᆰ', + 'ã„»' => 'ᆱ', + 'ㄼ' => 'ᆲ', + 'ㄽ' => 'ᆳ', + 'ㄾ' => 'ᆴ', + 'ã„¿' => 'ᆵ', + 'ã…€' => 'ᄚ', + 'ã…' => 'ᄆ', + 'ã…‚' => 'ᄇ', + 'ã…ƒ' => 'ᄈ', + 'ã…„' => 'á„¡', + 'ã……' => 'ᄉ', + 'ã…†' => 'ᄊ', + 'ã…‡' => 'á„‹', + 'ã…ˆ' => 'ᄌ', + 'ã…‰' => 'á„', + 'ã…Š' => 'ᄎ', + 'ã…‹' => 'á„', + 'ã…Œ' => 'á„', + 'ã…' => 'á„‘', + 'ã…Ž' => 'á„’', + 'ã…' => 'á…¡', + 'ã…' => 'á…¢', + 'ã…‘' => 'á…£', + 'ã…’' => 'á…¤', + 'ã…“' => 'á…¥', + 'ã…”' => 'á…¦', + 'ã…•' => 'á…§', + 'ã…–' => 'á…¨', + 'ã…—' => 'á…©', + 'ã…˜' => 'á…ª', + 'ã…™' => 'á…«', + 'ã…š' => 'á…¬', + 'ã…›' => 'á…­', + 'ã…œ' => 'á…®', + 'ã…' => 'á…¯', + 'ã…ž' => 'á…°', + 'ã…Ÿ' => 'á…±', + 'ã… ' => 'á…²', + 'ã…¡' => 'á…³', + 'ã…¢' => 'á…´', + 'ã…£' => 'á…µ', + 'ã…¤' => 'á… ', + 'ã…¥' => 'á„”', + 'ã…¦' => 'á„•', + 'ã…§' => 'ᇇ', + 'ã…¨' => 'ᇈ', + 'ã…©' => 'ᇌ', + 'ã…ª' => 'ᇎ', + 'ã…«' => 'ᇓ', + 'ã…¬' => 'ᇗ', + 'ã…­' => 'ᇙ', + 'ã…®' => 'ᄜ', + 'ã…¯' => 'á‡', + 'ã…°' => 'ᇟ', + 'ã…±' => 'á„', + 'ã…²' => 'ᄞ', + 'ã…³' => 'á„ ', + 'ã…´' => 'á„¢', + 'ã…µ' => 'á„£', + 'ã…¶' => 'á„§', + 'ã…·' => 'á„©', + 'ã…¸' => 'á„«', + 'ã…¹' => 'ᄬ', + 'ã…º' => 'á„­', + 'ã…»' => 'á„®', + 'ã…¼' => 'ᄯ', + 'ã…½' => 'ᄲ', + 'ã…¾' => 'á„¶', + 'ã…¿' => 'á…€', + 'ㆀ' => 'á…‡', + 'ã†' => 'á…Œ', + 'ㆂ' => 'ᇱ', + 'ㆃ' => 'ᇲ', + 'ㆄ' => 'á…—', + 'ㆅ' => 'á…˜', + 'ㆆ' => 'á…™', + 'ㆇ' => 'ᆄ', + 'ㆈ' => 'ᆅ', + 'ㆉ' => 'ᆈ', + 'ㆊ' => 'ᆑ', + 'ㆋ' => 'ᆒ', + 'ㆌ' => 'ᆔ', + 'ã†' => 'ᆞ', + 'ㆎ' => 'ᆡ', + '㆒' => '一', + '㆓' => '二', + '㆔' => '三', + '㆕' => 'å››', + '㆖' => '上', + '㆗' => '中', + '㆘' => '下', + '㆙' => '甲', + '㆚' => 'ä¹™', + '㆛' => '丙', + '㆜' => 'ä¸', + 'ã†' => '天', + '㆞' => '地', + '㆟' => '人', + '㈀' => '(á„€)', + 'ãˆ' => '(á„‚)', + '㈂' => '(ᄃ)', + '㈃' => '(á„…)', + '㈄' => '(ᄆ)', + '㈅' => '(ᄇ)', + '㈆' => '(ᄉ)', + '㈇' => '(á„‹)', + '㈈' => '(ᄌ)', + '㈉' => '(ᄎ)', + '㈊' => '(á„)', + '㈋' => '(á„)', + '㈌' => '(á„‘)', + 'ãˆ' => '(á„’)', + '㈎' => '(가)', + 'ãˆ' => '(á„‚á…¡)', + 'ãˆ' => '(다)', + '㈑' => '(á„…á…¡)', + '㈒' => '(마)', + '㈓' => '(바)', + '㈔' => '(사)', + '㈕' => '(á„‹á…¡)', + '㈖' => '(자)', + '㈗' => '(차)', + '㈘' => '(á„á…¡)', + '㈙' => '(á„á…¡)', + '㈚' => '(á„‘á…¡)', + '㈛' => '(á„’á…¡)', + '㈜' => '(주)', + 'ãˆ' => '(오전)', + '㈞' => '(á„‹á…©á„’á…®)', + '㈠' => '(一)', + '㈡' => '(二)', + '㈢' => '(三)', + '㈣' => '(å››)', + '㈤' => '(五)', + '㈥' => '(å…­)', + '㈦' => '(七)', + '㈧' => '(å…«)', + '㈨' => '(ä¹)', + '㈩' => '(å)', + '㈪' => '(月)', + '㈫' => '(ç«)', + '㈬' => '(æ°´)', + '㈭' => '(木)', + '㈮' => '(金)', + '㈯' => '(土)', + '㈰' => '(æ—¥)', + '㈱' => '(æ ª)', + '㈲' => '(有)', + '㈳' => '(社)', + '㈴' => '(å)', + '㈵' => '(特)', + '㈶' => '(財)', + '㈷' => '(ç¥)', + '㈸' => '(労)', + '㈹' => '(代)', + '㈺' => '(呼)', + '㈻' => '(å­¦)', + '㈼' => '(監)', + '㈽' => '(ä¼)', + '㈾' => '(資)', + '㈿' => '(å”)', + '㉀' => '(祭)', + 'ã‰' => '(休)', + '㉂' => '(自)', + '㉃' => '(至)', + '㉄' => 'å•', + '㉅' => 'å¹¼', + '㉆' => 'æ–‡', + '㉇' => 'ç®', + 'ã‰' => 'PTE', + '㉑' => '21', + '㉒' => '22', + '㉓' => '23', + '㉔' => '24', + '㉕' => '25', + '㉖' => '26', + '㉗' => '27', + '㉘' => '28', + '㉙' => '29', + '㉚' => '30', + '㉛' => '31', + '㉜' => '32', + 'ã‰' => '33', + '㉞' => '34', + '㉟' => '35', + '㉠' => 'á„€', + '㉡' => 'á„‚', + '㉢' => 'ᄃ', + '㉣' => 'á„…', + '㉤' => 'ᄆ', + '㉥' => 'ᄇ', + '㉦' => 'ᄉ', + '㉧' => 'á„‹', + '㉨' => 'ᄌ', + '㉩' => 'ᄎ', + '㉪' => 'á„', + '㉫' => 'á„', + '㉬' => 'á„‘', + '㉭' => 'á„’', + '㉮' => '가', + '㉯' => 'á„‚á…¡', + '㉰' => '다', + '㉱' => 'á„…á…¡', + '㉲' => '마', + '㉳' => '바', + '㉴' => '사', + '㉵' => 'á„‹á…¡', + '㉶' => '자', + '㉷' => '차', + '㉸' => 'á„á…¡', + '㉹' => 'á„á…¡', + '㉺' => 'á„‘á…¡', + '㉻' => 'á„’á…¡', + '㉼' => '참고', + '㉽' => '주의', + '㉾' => 'á„‹á…®', + '㊀' => '一', + 'ãŠ' => '二', + '㊂' => '三', + '㊃' => 'å››', + '㊄' => '五', + '㊅' => 'å…­', + '㊆' => '七', + '㊇' => 'å…«', + '㊈' => 'ä¹', + '㊉' => 'å', + '㊊' => '月', + '㊋' => 'ç«', + '㊌' => 'æ°´', + 'ãŠ' => '木', + '㊎' => '金', + 'ãŠ' => '土', + 'ãŠ' => 'æ—¥', + '㊑' => 'æ ª', + '㊒' => '有', + '㊓' => '社', + '㊔' => 'å', + '㊕' => '特', + '㊖' => '財', + '㊗' => 'ç¥', + '㊘' => '労', + '㊙' => '秘', + '㊚' => 'ç”·', + '㊛' => '女', + '㊜' => 'é©', + 'ãŠ' => '優', + '㊞' => 'å°', + '㊟' => '注', + '㊠' => 'é …', + '㊡' => '休', + '㊢' => '写', + '㊣' => 'æ­£', + '㊤' => '上', + '㊥' => '中', + '㊦' => '下', + '㊧' => 'å·¦', + '㊨' => 'å³', + '㊩' => '医', + '㊪' => 'å®—', + '㊫' => 'å­¦', + '㊬' => '監', + '㊭' => 'ä¼', + '㊮' => '資', + '㊯' => 'å”', + '㊰' => '夜', + '㊱' => '36', + '㊲' => '37', + '㊳' => '38', + '㊴' => '39', + '㊵' => '40', + '㊶' => '41', + '㊷' => '42', + '㊸' => '43', + '㊹' => '44', + '㊺' => '45', + '㊻' => '46', + '㊼' => '47', + '㊽' => '48', + '㊾' => '49', + '㊿' => '50', + 'ã‹€' => '1月', + 'ã‹' => '2月', + 'ã‹‚' => '3月', + '㋃' => '4月', + 'ã‹„' => '5月', + 'ã‹…' => '6月', + '㋆' => '7月', + '㋇' => '8月', + '㋈' => '9月', + '㋉' => '10月', + '㋊' => '11月', + 'ã‹‹' => '12月', + '㋌' => 'Hg', + 'ã‹' => 'erg', + '㋎' => 'eV', + 'ã‹' => 'LTD', + 'ã‹' => 'ã‚¢', + 'ã‹‘' => 'イ', + 'ã‹’' => 'ウ', + 'ã‹“' => 'エ', + 'ã‹”' => 'オ', + 'ã‹•' => 'ã‚«', + 'ã‹–' => 'ã‚­', + 'ã‹—' => 'ク', + '㋘' => 'ケ', + 'ã‹™' => 'コ', + '㋚' => 'サ', + 'ã‹›' => 'ã‚·', + '㋜' => 'ス', + 'ã‹' => 'ã‚»', + '㋞' => 'ソ', + '㋟' => 'ã‚¿', + 'ã‹ ' => 'ãƒ', + 'ã‹¡' => 'ツ', + 'ã‹¢' => 'テ', + 'ã‹£' => 'ト', + '㋤' => 'ナ', + 'ã‹¥' => 'ニ', + '㋦' => 'ヌ', + 'ã‹§' => 'ãƒ', + '㋨' => 'ノ', + 'ã‹©' => 'ãƒ', + '㋪' => 'ヒ', + 'ã‹«' => 'フ', + '㋬' => 'ヘ', + 'ã‹­' => 'ホ', + 'ã‹®' => 'マ', + '㋯' => 'ミ', + 'ã‹°' => 'ム', + '㋱' => 'メ', + '㋲' => 'モ', + '㋳' => 'ヤ', + 'ã‹´' => 'ユ', + '㋵' => 'ヨ', + 'ã‹¶' => 'ラ', + 'ã‹·' => 'リ', + '㋸' => 'ル', + '㋹' => 'レ', + '㋺' => 'ロ', + 'ã‹»' => 'ワ', + '㋼' => 'ヰ', + '㋽' => 'ヱ', + '㋾' => 'ヲ', + 'ã‹¿' => '令和', + '㌀' => 'ã‚¢ãƒã‚šãƒ¼ãƒˆ', + 'ãŒ' => 'アルファ', + '㌂' => 'アンペア', + '㌃' => 'アール', + '㌄' => 'イニング', + '㌅' => 'インãƒ', + '㌆' => 'ウォン', + '㌇' => 'エスクード', + '㌈' => 'エーカー', + '㌉' => 'オンス', + '㌊' => 'オーム', + '㌋' => 'カイリ', + '㌌' => 'カラット', + 'ãŒ' => 'カロリー', + '㌎' => 'ガロン', + 'ãŒ' => 'ガンマ', + 'ãŒ' => 'ギガ', + '㌑' => 'ギニー', + '㌒' => 'キュリー', + '㌓' => 'ギルダー', + '㌔' => 'キロ', + '㌕' => 'キログラム', + '㌖' => 'キロメートル', + '㌗' => 'キロワット', + '㌘' => 'グラム', + '㌙' => 'グラムトン', + '㌚' => 'クルゼイロ', + '㌛' => 'クローãƒ', + '㌜' => 'ケース', + 'ãŒ' => 'コルナ', + '㌞' => 'コーポ', + '㌟' => 'サイクル', + '㌠' => 'サンãƒãƒ¼ãƒ ', + '㌡' => 'シリング', + '㌢' => 'センãƒ', + '㌣' => 'セント', + '㌤' => 'ダース', + '㌥' => 'デシ', + '㌦' => 'ドル', + '㌧' => 'トン', + '㌨' => 'ナノ', + '㌩' => 'ノット', + '㌪' => 'ãƒã‚¤ãƒ„', + '㌫' => 'ãƒã‚šãƒ¼ã‚»ãƒ³ãƒˆ', + '㌬' => 'ãƒã‚šãƒ¼ãƒ„', + '㌭' => 'ãƒã‚™ãƒ¼ãƒ¬ãƒ«', + '㌮' => 'ピアストル', + '㌯' => 'ピクル', + '㌰' => 'ピコ', + '㌱' => 'ビル', + '㌲' => 'ファラッド', + '㌳' => 'フィート', + '㌴' => 'ブッシェル', + '㌵' => 'フラン', + '㌶' => 'ヘクタール', + '㌷' => 'ペソ', + '㌸' => 'ペニヒ', + '㌹' => 'ヘルツ', + '㌺' => 'ペンス', + '㌻' => 'ページ', + '㌼' => 'ベータ', + '㌽' => 'ポイント', + '㌾' => 'ボルト', + '㌿' => 'ホン', + 'ã€' => 'ポンド', + 'ã' => 'ホール', + 'ã‚' => 'ホーン', + 'ãƒ' => 'マイクロ', + 'ã„' => 'マイル', + 'ã…' => 'マッãƒ', + 'ã†' => 'マルク', + 'ã‡' => 'マンション', + 'ãˆ' => 'ミクロン', + 'ã‰' => 'ミリ', + 'ãŠ' => 'ミリãƒã‚™ãƒ¼ãƒ«', + 'ã‹' => 'メガ', + 'ãŒ' => 'メガトン', + 'ã' => 'メートル', + 'ãŽ' => 'ヤード', + 'ã' => 'ヤール', + 'ã' => 'ユアン', + 'ã‘' => 'リットル', + 'ã’' => 'リラ', + 'ã“' => 'ルピー', + 'ã”' => 'ルーブル', + 'ã•' => 'レム', + 'ã–' => 'レントゲン', + 'ã—' => 'ワット', + 'ã˜' => '0点', + 'ã™' => '1点', + 'ãš' => '2点', + 'ã›' => '3点', + 'ãœ' => '4点', + 'ã' => '5点', + 'ãž' => '6点', + 'ãŸ' => '7点', + 'ã ' => '8点', + 'ã¡' => '9点', + 'ã¢' => '10点', + 'ã£' => '11点', + 'ã¤' => '12点', + 'ã¥' => '13点', + 'ã¦' => '14点', + 'ã§' => '15点', + 'ã¨' => '16点', + 'ã©' => '17点', + 'ãª' => '18点', + 'ã«' => '19点', + 'ã¬' => '20点', + 'ã­' => '21点', + 'ã®' => '22点', + 'ã¯' => '23点', + 'ã°' => '24点', + 'ã±' => 'hPa', + 'ã²' => 'da', + 'ã³' => 'AU', + 'ã´' => 'bar', + 'ãµ' => 'oV', + 'ã¶' => 'pc', + 'ã·' => 'dm', + 'ã¸' => 'dm2', + 'ã¹' => 'dm3', + 'ãº' => 'IU', + 'ã»' => 'å¹³æˆ', + 'ã¼' => '昭和', + 'ã½' => '大正', + 'ã¾' => '明治', + 'ã¿' => 'æ ªå¼ä¼šç¤¾', + '㎀' => 'pA', + 'ãŽ' => 'nA', + '㎂' => 'μA', + '㎃' => 'mA', + '㎄' => 'kA', + '㎅' => 'KB', + '㎆' => 'MB', + '㎇' => 'GB', + '㎈' => 'cal', + '㎉' => 'kcal', + '㎊' => 'pF', + '㎋' => 'nF', + '㎌' => 'μF', + 'ãŽ' => 'μg', + '㎎' => 'mg', + 'ãŽ' => 'kg', + 'ãŽ' => 'Hz', + '㎑' => 'kHz', + '㎒' => 'MHz', + '㎓' => 'GHz', + '㎔' => 'THz', + '㎕' => 'μl', + '㎖' => 'ml', + '㎗' => 'dl', + '㎘' => 'kl', + '㎙' => 'fm', + '㎚' => 'nm', + '㎛' => 'μm', + '㎜' => 'mm', + 'ãŽ' => 'cm', + '㎞' => 'km', + '㎟' => 'mm2', + '㎠' => 'cm2', + '㎡' => 'm2', + '㎢' => 'km2', + '㎣' => 'mm3', + '㎤' => 'cm3', + '㎥' => 'm3', + '㎦' => 'km3', + '㎧' => 'm∕s', + '㎨' => 'm∕s2', + '㎩' => 'Pa', + '㎪' => 'kPa', + '㎫' => 'MPa', + '㎬' => 'GPa', + '㎭' => 'rad', + '㎮' => 'rad∕s', + '㎯' => 'rad∕s2', + '㎰' => 'ps', + '㎱' => 'ns', + '㎲' => 'μs', + '㎳' => 'ms', + '㎴' => 'pV', + '㎵' => 'nV', + '㎶' => 'μV', + '㎷' => 'mV', + '㎸' => 'kV', + '㎹' => 'MV', + '㎺' => 'pW', + '㎻' => 'nW', + '㎼' => 'μW', + '㎽' => 'mW', + '㎾' => 'kW', + '㎿' => 'MW', + 'ã€' => 'kΩ', + 'ã' => 'MΩ', + 'ã‚' => 'a.m.', + 'ãƒ' => 'Bq', + 'ã„' => 'cc', + 'ã…' => 'cd', + 'ã†' => 'C∕kg', + 'ã‡' => 'Co.', + 'ãˆ' => 'dB', + 'ã‰' => 'Gy', + 'ãŠ' => 'ha', + 'ã‹' => 'HP', + 'ãŒ' => 'in', + 'ã' => 'KK', + 'ãŽ' => 'KM', + 'ã' => 'kt', + 'ã' => 'lm', + 'ã‘' => 'ln', + 'ã’' => 'log', + 'ã“' => 'lx', + 'ã”' => 'mb', + 'ã•' => 'mil', + 'ã–' => 'mol', + 'ã—' => 'PH', + 'ã˜' => 'p.m.', + 'ã™' => 'PPM', + 'ãš' => 'PR', + 'ã›' => 'sr', + 'ãœ' => 'Sv', + 'ã' => 'Wb', + 'ãž' => 'V∕m', + 'ãŸ' => 'A∕m', + 'ã ' => '1æ—¥', + 'ã¡' => '2æ—¥', + 'ã¢' => '3æ—¥', + 'ã£' => '4æ—¥', + 'ã¤' => '5æ—¥', + 'ã¥' => '6æ—¥', + 'ã¦' => '7æ—¥', + 'ã§' => '8æ—¥', + 'ã¨' => '9æ—¥', + 'ã©' => '10æ—¥', + 'ãª' => '11æ—¥', + 'ã«' => '12æ—¥', + 'ã¬' => '13æ—¥', + 'ã­' => '14æ—¥', + 'ã®' => '15æ—¥', + 'ã¯' => '16æ—¥', + 'ã°' => '17æ—¥', + 'ã±' => '18æ—¥', + 'ã²' => '19æ—¥', + 'ã³' => '20æ—¥', + 'ã´' => '21æ—¥', + 'ãµ' => '22æ—¥', + 'ã¶' => '23æ—¥', + 'ã·' => '24æ—¥', + 'ã¸' => '25æ—¥', + 'ã¹' => '26æ—¥', + 'ãº' => '27æ—¥', + 'ã»' => '28æ—¥', + 'ã¼' => '29æ—¥', + 'ã½' => '30æ—¥', + 'ã¾' => '31æ—¥', + 'ã¿' => 'gal', + 'êšœ' => 'ÑŠ', + 'êš' => 'ÑŒ', + 'ê°' => 'ê¯', + 'ꟸ' => 'Ħ', + 'ꟹ' => 'Å“', + 'ê­œ' => 'ꜧ', + 'ê­' => 'ꬷ', + 'ê­ž' => 'É«', + 'ê­Ÿ' => 'ê­’', + 'ê­©' => 'Ê', + 'ff' => 'ff', + 'ï¬' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'Õ´Õ¶', + 'ﬔ' => 'Õ´Õ¥', + 'ﬕ' => 'Õ´Õ«', + 'ﬖ' => 'Õ¾Õ¶', + 'ﬗ' => 'Õ´Õ­', + 'ﬠ' => '×¢', + 'ﬡ' => '×', + 'ﬢ' => 'ד', + 'ﬣ' => '×”', + 'ﬤ' => '×›', + 'ﬥ' => 'ל', + 'ﬦ' => '×', + 'ﬧ' => 'ר', + 'ﬨ' => 'ת', + '﬩' => '+', + 'ï­' => '×ל', + 'ï­' => 'Ù±', + 'ï­‘' => 'Ù±', + 'ï­’' => 'Ù»', + 'ï­“' => 'Ù»', + 'ï­”' => 'Ù»', + 'ï­•' => 'Ù»', + 'ï­–' => 'Ù¾', + 'ï­—' => 'Ù¾', + 'ï­˜' => 'Ù¾', + 'ï­™' => 'Ù¾', + 'ï­š' => 'Ú€', + 'ï­›' => 'Ú€', + 'ï­œ' => 'Ú€', + 'ï­' => 'Ú€', + 'ï­ž' => 'Ùº', + 'ï­Ÿ' => 'Ùº', + 'ï­ ' => 'Ùº', + 'ï­¡' => 'Ùº', + 'ï­¢' => 'Ù¿', + 'ï­£' => 'Ù¿', + 'ï­¤' => 'Ù¿', + 'ï­¥' => 'Ù¿', + 'ï­¦' => 'Ù¹', + 'ï­§' => 'Ù¹', + 'ï­¨' => 'Ù¹', + 'ï­©' => 'Ù¹', + 'ï­ª' => 'Ú¤', + 'ï­«' => 'Ú¤', + 'ï­¬' => 'Ú¤', + 'ï­­' => 'Ú¤', + 'ï­®' => 'Ú¦', + 'ï­¯' => 'Ú¦', + 'ï­°' => 'Ú¦', + 'ï­±' => 'Ú¦', + 'ï­²' => 'Ú„', + 'ï­³' => 'Ú„', + 'ï­´' => 'Ú„', + 'ï­µ' => 'Ú„', + 'ï­¶' => 'Úƒ', + 'ï­·' => 'Úƒ', + 'ï­¸' => 'Úƒ', + 'ï­¹' => 'Úƒ', + 'ï­º' => 'Ú†', + 'ï­»' => 'Ú†', + 'ï­¼' => 'Ú†', + 'ï­½' => 'Ú†', + 'ï­¾' => 'Ú‡', + 'ï­¿' => 'Ú‡', + 'ﮀ' => 'Ú‡', + 'ï®' => 'Ú‡', + 'ﮂ' => 'Ú', + 'ﮃ' => 'Ú', + 'ﮄ' => 'ÚŒ', + 'ï®…' => 'ÚŒ', + 'ﮆ' => 'ÚŽ', + 'ﮇ' => 'ÚŽ', + 'ﮈ' => 'Úˆ', + 'ﮉ' => 'Úˆ', + 'ﮊ' => 'Ú˜', + 'ﮋ' => 'Ú˜', + 'ﮌ' => 'Ú‘', + 'ï®' => 'Ú‘', + 'ﮎ' => 'Ú©', + 'ï®' => 'Ú©', + 'ï®' => 'Ú©', + 'ﮑ' => 'Ú©', + 'ï®’' => 'Ú¯', + 'ﮓ' => 'Ú¯', + 'ï®”' => 'Ú¯', + 'ﮕ' => 'Ú¯', + 'ï®–' => 'Ú³', + 'ï®—' => 'Ú³', + 'ﮘ' => 'Ú³', + 'ï®™' => 'Ú³', + 'ﮚ' => 'Ú±', + 'ï®›' => 'Ú±', + 'ﮜ' => 'Ú±', + 'ï®' => 'Ú±', + 'ﮞ' => 'Úº', + 'ﮟ' => 'Úº', + 'ï® ' => 'Ú»', + 'ﮡ' => 'Ú»', + 'ﮢ' => 'Ú»', + 'ﮣ' => 'Ú»', + 'ﮤ' => 'Û•Ù”', + 'ﮥ' => 'Û•Ù”', + 'ﮦ' => 'Û', + 'ï®§' => 'Û', + 'ﮨ' => 'Û', + 'ﮩ' => 'Û', + 'ﮪ' => 'Ú¾', + 'ﮫ' => 'Ú¾', + 'ﮬ' => 'Ú¾', + 'ï®­' => 'Ú¾', + 'ï®®' => 'Û’', + 'ﮯ' => 'Û’', + 'ï®°' => 'Û’Ù”', + 'ï®±' => 'Û’Ù”', + 'ﯓ' => 'Ú­', + 'ﯔ' => 'Ú­', + 'ﯕ' => 'Ú­', + 'ﯖ' => 'Ú­', + 'ﯗ' => 'Û‡', + 'ﯘ' => 'Û‡', + 'ﯙ' => 'Û†', + 'ﯚ' => 'Û†', + 'ﯛ' => 'Ûˆ', + 'ﯜ' => 'Ûˆ', + 'ï¯' => 'Û‡Ù´', + 'ﯞ' => 'Û‹', + 'ﯟ' => 'Û‹', + 'ﯠ' => 'Û…', + 'ﯡ' => 'Û…', + 'ﯢ' => 'Û‰', + 'ﯣ' => 'Û‰', + 'ﯤ' => 'Û', + 'ﯥ' => 'Û', + 'ﯦ' => 'Û', + 'ﯧ' => 'Û', + 'ﯨ' => 'Ù‰', + 'ﯩ' => 'Ù‰', + 'ﯪ' => 'ئا', + 'ﯫ' => 'ئا', + 'ﯬ' => 'ÙŠÙ”Û•', + 'ﯭ' => 'ÙŠÙ”Û•', + 'ﯮ' => 'ÙŠÙ”Ùˆ', + 'ﯯ' => 'ÙŠÙ”Ùˆ', + 'ﯰ' => 'ÙŠÙ”Û‡', + 'ﯱ' => 'ÙŠÙ”Û‡', + 'ﯲ' => 'ÙŠÙ”Û†', + 'ﯳ' => 'ÙŠÙ”Û†', + 'ﯴ' => 'ÙŠÙ”Ûˆ', + 'ﯵ' => 'ÙŠÙ”Ûˆ', + 'ﯶ' => 'ÙŠÙ”Û', + 'ﯷ' => 'ÙŠÙ”Û', + 'ﯸ' => 'ÙŠÙ”Û', + 'ﯹ' => 'ÙŠÙ”Ù‰', + 'ﯺ' => 'ÙŠÙ”Ù‰', + 'ﯻ' => 'ÙŠÙ”Ù‰', + 'ﯼ' => 'ÛŒ', + 'ﯽ' => 'ÛŒ', + 'ﯾ' => 'ÛŒ', + 'ﯿ' => 'ÛŒ', + 'ï°€' => 'ئج', + 'ï°' => 'ئح', + 'ï°‚' => 'ÙŠÙ”Ù…', + 'ï°ƒ' => 'ÙŠÙ”Ù‰', + 'ï°„' => 'ÙŠÙ”ÙŠ', + 'ï°…' => 'بج', + 'ï°†' => 'بح', + 'ï°‡' => 'بخ', + 'ï°ˆ' => 'بم', + 'ï°‰' => 'بى', + 'ï°Š' => 'بي', + 'ï°‹' => 'تج', + 'ï°Œ' => 'تح', + 'ï°' => 'تخ', + 'ï°Ž' => 'تم', + 'ï°' => 'تى', + 'ï°' => 'تي', + 'ï°‘' => 'ثج', + 'ï°’' => 'ثم', + 'ï°“' => 'ثى', + 'ï°”' => 'ثي', + 'ï°•' => 'جح', + 'ï°–' => 'جم', + 'ï°—' => 'حج', + 'ï°˜' => 'حم', + 'ï°™' => 'خج', + 'ï°š' => 'خح', + 'ï°›' => 'خم', + 'ï°œ' => 'سج', + 'ï°' => 'سح', + 'ï°ž' => 'سخ', + 'ï°Ÿ' => 'سم', + 'ï° ' => 'صح', + 'ï°¡' => 'صم', + 'ï°¢' => 'ضج', + 'ï°£' => 'ضح', + 'ï°¤' => 'ضخ', + 'ï°¥' => 'ضم', + 'ï°¦' => 'طح', + 'ï°§' => 'طم', + 'ï°¨' => 'ظم', + 'ï°©' => 'عج', + 'ï°ª' => 'عم', + 'ï°«' => 'غج', + 'ï°¬' => 'غم', + 'ï°­' => 'ÙØ¬', + 'ï°®' => 'ÙØ­', + 'ï°¯' => 'ÙØ®', + 'ï°°' => 'ÙÙ…', + 'ï°±' => 'ÙÙ‰', + 'ï°²' => 'ÙÙŠ', + 'ï°³' => 'قح', + 'ï°´' => 'قم', + 'ï°µ' => 'قى', + 'ï°¶' => 'قي', + 'ï°·' => 'كا', + 'ï°¸' => 'كج', + 'ï°¹' => 'كح', + 'ï°º' => 'كخ', + 'ï°»' => 'كل', + 'ï°¼' => 'كم', + 'ï°½' => 'كى', + 'ï°¾' => 'كي', + 'ï°¿' => 'لج', + 'ï±€' => 'لح', + 'ï±' => 'لخ', + 'ﱂ' => 'لم', + 'ﱃ' => 'لى', + 'ﱄ' => 'لي', + 'ï±…' => 'مج', + 'ﱆ' => 'مح', + 'ﱇ' => 'مخ', + 'ﱈ' => 'مم', + 'ﱉ' => 'مى', + 'ﱊ' => 'مي', + 'ﱋ' => 'نج', + 'ﱌ' => 'نح', + 'ï±' => 'نخ', + 'ﱎ' => 'نم', + 'ï±' => 'نى', + 'ï±' => 'ني', + 'ﱑ' => 'هج', + 'ï±’' => 'هم', + 'ﱓ' => 'هى', + 'ï±”' => 'هي', + 'ﱕ' => 'يج', + 'ï±–' => 'يح', + 'ï±—' => 'يخ', + 'ﱘ' => 'يم', + 'ï±™' => 'يى', + 'ﱚ' => 'يي', + 'ï±›' => 'ذٰ', + 'ﱜ' => 'رٰ', + 'ï±' => 'ىٰ', + 'ﱞ' => ' ٌّ', + 'ﱟ' => ' ÙÙ‘', + 'ï± ' => ' ÙŽÙ‘', + 'ﱡ' => ' ÙÙ‘', + 'ï±¢' => ' ÙÙ‘', + 'ï±£' => ' ّٰ', + 'ﱤ' => 'ئر', + 'ï±¥' => 'ئز', + 'ﱦ' => 'ÙŠÙ”Ù…', + 'ï±§' => 'ÙŠÙ”Ù†', + 'ﱨ' => 'ÙŠÙ”Ù‰', + 'ﱩ' => 'ÙŠÙ”ÙŠ', + 'ﱪ' => 'بر', + 'ﱫ' => 'بز', + 'ﱬ' => 'بم', + 'ï±­' => 'بن', + 'ï±®' => 'بى', + 'ﱯ' => 'بي', + 'ï±°' => 'تر', + 'ï±±' => 'تز', + 'ï±²' => 'تم', + 'ï±³' => 'تن', + 'ï±´' => 'تى', + 'ï±µ' => 'تي', + 'ï±¶' => 'ثر', + 'ï±·' => 'ثز', + 'ﱸ' => 'ثم', + 'ï±¹' => 'ثن', + 'ﱺ' => 'ثى', + 'ï±»' => 'ثي', + 'ï±¼' => 'ÙÙ‰', + 'ï±½' => 'ÙÙŠ', + 'ï±¾' => 'قى', + 'ﱿ' => 'قي', + 'ï²€' => 'كا', + 'ï²' => 'كل', + 'ﲂ' => 'كم', + 'ﲃ' => 'كى', + 'ﲄ' => 'كي', + 'ï²…' => 'لم', + 'ﲆ' => 'لى', + 'ﲇ' => 'لي', + 'ﲈ' => 'ما', + 'ﲉ' => 'مم', + 'ﲊ' => 'نر', + 'ﲋ' => 'نز', + 'ﲌ' => 'نم', + 'ï²' => 'نن', + 'ﲎ' => 'نى', + 'ï²' => 'ني', + 'ï²' => 'ىٰ', + 'ﲑ' => 'ير', + 'ï²’' => 'يز', + 'ﲓ' => 'يم', + 'ï²”' => 'ين', + 'ﲕ' => 'يى', + 'ï²–' => 'يي', + 'ï²—' => 'ئج', + 'ﲘ' => 'ئح', + 'ï²™' => 'ئخ', + 'ﲚ' => 'ÙŠÙ”Ù…', + 'ï²›' => 'ÙŠÙ”Ù‡', + 'ﲜ' => 'بج', + 'ï²' => 'بح', + 'ﲞ' => 'بخ', + 'ﲟ' => 'بم', + 'ï² ' => 'به', + 'ﲡ' => 'تج', + 'ï²¢' => 'تح', + 'ï²£' => 'تخ', + 'ﲤ' => 'تم', + 'ï²¥' => 'ته', + 'ﲦ' => 'ثم', + 'ï²§' => 'جح', + 'ﲨ' => 'جم', + 'ﲩ' => 'حج', + 'ﲪ' => 'حم', + 'ﲫ' => 'خج', + 'ﲬ' => 'خم', + 'ï²­' => 'سج', + 'ï²®' => 'سح', + 'ﲯ' => 'سخ', + 'ï²°' => 'سم', + 'ï²±' => 'صح', + 'ï²²' => 'صخ', + 'ï²³' => 'صم', + 'ï²´' => 'ضج', + 'ï²µ' => 'ضح', + 'ï²¶' => 'ضخ', + 'ï²·' => 'ضم', + 'ﲸ' => 'طح', + 'ï²¹' => 'ظم', + 'ﲺ' => 'عج', + 'ï²»' => 'عم', + 'ï²¼' => 'غج', + 'ï²½' => 'غم', + 'ï²¾' => 'ÙØ¬', + 'ﲿ' => 'ÙØ­', + 'ï³€' => 'ÙØ®', + 'ï³' => 'ÙÙ…', + 'ﳂ' => 'قح', + 'ﳃ' => 'قم', + 'ﳄ' => 'كج', + 'ï³…' => 'كح', + 'ﳆ' => 'كخ', + 'ﳇ' => 'كل', + 'ﳈ' => 'كم', + 'ﳉ' => 'لج', + 'ﳊ' => 'لح', + 'ﳋ' => 'لخ', + 'ﳌ' => 'لم', + 'ï³' => 'له', + 'ﳎ' => 'مج', + 'ï³' => 'مح', + 'ï³' => 'مخ', + 'ﳑ' => 'مم', + 'ï³’' => 'نج', + 'ﳓ' => 'نح', + 'ï³”' => 'نخ', + 'ﳕ' => 'نم', + 'ï³–' => 'نه', + 'ï³—' => 'هج', + 'ﳘ' => 'هم', + 'ï³™' => 'هٰ', + 'ﳚ' => 'يج', + 'ï³›' => 'يح', + 'ﳜ' => 'يخ', + 'ï³' => 'يم', + 'ﳞ' => 'يه', + 'ﳟ' => 'ÙŠÙ”Ù…', + 'ï³ ' => 'ÙŠÙ”Ù‡', + 'ﳡ' => 'بم', + 'ï³¢' => 'به', + 'ï³£' => 'تم', + 'ﳤ' => 'ته', + 'ï³¥' => 'ثم', + 'ﳦ' => 'ثه', + 'ï³§' => 'سم', + 'ﳨ' => 'سه', + 'ﳩ' => 'شم', + 'ﳪ' => 'شه', + 'ﳫ' => 'كل', + 'ﳬ' => 'كم', + 'ï³­' => 'لم', + 'ï³®' => 'نم', + 'ﳯ' => 'نه', + 'ï³°' => 'يم', + 'ï³±' => 'يه', + 'ï³²' => 'Ù€ÙŽÙ‘', + 'ï³³' => 'Ù€ÙÙ‘', + 'ï³´' => 'Ù€ÙÙ‘', + 'ï³µ' => 'طى', + 'ï³¶' => 'طي', + 'ï³·' => 'عى', + 'ﳸ' => 'عي', + 'ï³¹' => 'غى', + 'ﳺ' => 'غي', + 'ï³»' => 'سى', + 'ï³¼' => 'سي', + 'ï³½' => 'شى', + 'ï³¾' => 'شي', + 'ﳿ' => 'حى', + 'ï´€' => 'حي', + 'ï´' => 'جى', + 'ï´‚' => 'جي', + 'ï´ƒ' => 'خى', + 'ï´„' => 'خي', + 'ï´…' => 'صى', + 'ï´†' => 'صي', + 'ï´‡' => 'ضى', + 'ï´ˆ' => 'ضي', + 'ï´‰' => 'شج', + 'ï´Š' => 'شح', + 'ï´‹' => 'شخ', + 'ï´Œ' => 'شم', + 'ï´' => 'شر', + 'ï´Ž' => 'سر', + 'ï´' => 'صر', + 'ï´' => 'ضر', + 'ï´‘' => 'طى', + 'ï´’' => 'طي', + 'ï´“' => 'عى', + 'ï´”' => 'عي', + 'ï´•' => 'غى', + 'ï´–' => 'غي', + 'ï´—' => 'سى', + 'ï´˜' => 'سي', + 'ï´™' => 'شى', + 'ï´š' => 'شي', + 'ï´›' => 'حى', + 'ï´œ' => 'حي', + 'ï´' => 'جى', + 'ï´ž' => 'جي', + 'ï´Ÿ' => 'خى', + 'ï´ ' => 'خي', + 'ï´¡' => 'صى', + 'ï´¢' => 'صي', + 'ï´£' => 'ضى', + 'ï´¤' => 'ضي', + 'ï´¥' => 'شج', + 'ï´¦' => 'شح', + 'ï´§' => 'شخ', + 'ï´¨' => 'شم', + 'ï´©' => 'شر', + 'ï´ª' => 'سر', + 'ï´«' => 'صر', + 'ï´¬' => 'ضر', + 'ï´­' => 'شج', + 'ï´®' => 'شح', + 'ï´¯' => 'شخ', + 'ï´°' => 'شم', + 'ï´±' => 'سه', + 'ï´²' => 'شه', + 'ï´³' => 'طم', + 'ï´´' => 'سج', + 'ï´µ' => 'سح', + 'ï´¶' => 'سخ', + 'ï´·' => 'شج', + 'ï´¸' => 'شح', + 'ï´¹' => 'شخ', + 'ï´º' => 'طم', + 'ï´»' => 'ظم', + 'ï´¼' => 'اً', + 'ï´½' => 'اً', + 'ïµ' => 'تجم', + 'ﵑ' => 'تحج', + 'ïµ’' => 'تحج', + 'ﵓ' => 'تحم', + 'ïµ”' => 'تخم', + 'ﵕ' => 'تمج', + 'ïµ–' => 'تمح', + 'ïµ—' => 'تمخ', + 'ﵘ' => 'جمح', + 'ïµ™' => 'جمح', + 'ﵚ' => 'حمي', + 'ïµ›' => 'حمى', + 'ﵜ' => 'سحج', + 'ïµ' => 'سجح', + 'ﵞ' => 'سجى', + 'ﵟ' => 'سمح', + 'ïµ ' => 'سمح', + 'ﵡ' => 'سمج', + 'ïµ¢' => 'سمم', + 'ïµ£' => 'سمم', + 'ﵤ' => 'صحح', + 'ïµ¥' => 'صحح', + 'ﵦ' => 'صمم', + 'ïµ§' => 'شحم', + 'ﵨ' => 'شحم', + 'ﵩ' => 'شجي', + 'ﵪ' => 'شمخ', + 'ﵫ' => 'شمخ', + 'ﵬ' => 'شمم', + 'ïµ­' => 'شمم', + 'ïµ®' => 'ضحى', + 'ﵯ' => 'ضخم', + 'ïµ°' => 'ضخم', + 'ïµ±' => 'طمح', + 'ïµ²' => 'طمح', + 'ïµ³' => 'طمم', + 'ïµ´' => 'طمي', + 'ïµµ' => 'عجم', + 'ïµ¶' => 'عمم', + 'ïµ·' => 'عمم', + 'ﵸ' => 'عمى', + 'ïµ¹' => 'غمم', + 'ﵺ' => 'غمي', + 'ïµ»' => 'غمى', + 'ïµ¼' => 'ÙØ®Ù…', + 'ïµ½' => 'ÙØ®Ù…', + 'ïµ¾' => 'قمح', + 'ﵿ' => 'قمم', + 'ï¶€' => 'لحم', + 'ï¶' => 'لحي', + 'ï¶‚' => 'لحى', + 'ﶃ' => 'لجج', + 'ï¶„' => 'لجج', + 'ï¶…' => 'لخم', + 'ﶆ' => 'لخم', + 'ﶇ' => 'لمح', + 'ﶈ' => 'لمح', + 'ﶉ' => 'محج', + 'ï¶Š' => 'محم', + 'ï¶‹' => 'محي', + 'ï¶Œ' => 'مجح', + 'ï¶' => 'مجم', + 'ï¶Ž' => 'مخج', + 'ï¶' => 'مخم', + 'ï¶’' => 'مجخ', + 'ï¶“' => 'همج', + 'ï¶”' => 'همم', + 'ï¶•' => 'نحم', + 'ï¶–' => 'نحى', + 'ï¶—' => 'نجم', + 'ﶘ' => 'نجم', + 'ï¶™' => 'نجى', + 'ï¶š' => 'نمي', + 'ï¶›' => 'نمى', + 'ï¶œ' => 'يمم', + 'ï¶' => 'يمم', + 'ï¶ž' => 'بخي', + 'ï¶Ÿ' => 'تجي', + 'ï¶ ' => 'تجى', + 'ï¶¡' => 'تخي', + 'ï¶¢' => 'تخى', + 'ï¶£' => 'تمي', + 'ﶤ' => 'تمى', + 'ï¶¥' => 'جمي', + 'ﶦ' => 'جحى', + 'ï¶§' => 'جمى', + 'ﶨ' => 'سخى', + 'ï¶©' => 'صحي', + 'ﶪ' => 'شحي', + 'ï¶«' => 'ضحي', + 'ﶬ' => 'لجي', + 'ï¶­' => 'لمي', + 'ï¶®' => 'يحي', + 'ﶯ' => 'يجي', + 'ï¶°' => 'يمي', + 'ï¶±' => 'ممي', + 'ï¶²' => 'قمي', + 'ï¶³' => 'نحي', + 'ï¶´' => 'قمح', + 'ï¶µ' => 'لحم', + 'ï¶¶' => 'عمي', + 'ï¶·' => 'كمي', + 'ﶸ' => 'نجح', + 'ï¶¹' => 'مخي', + 'ﶺ' => 'لجم', + 'ï¶»' => 'كمم', + 'ï¶¼' => 'لجم', + 'ï¶½' => 'نجح', + 'ï¶¾' => 'جحي', + 'ï¶¿' => 'حجي', + 'ï·€' => 'مجي', + 'ï·' => 'Ùمي', + 'ï·‚' => 'بحي', + 'ï·ƒ' => 'كمم', + 'ï·„' => 'عجم', + 'ï·…' => 'صمم', + 'ï·†' => 'سخي', + 'ï·‡' => 'نجي', + 'ï·°' => 'صلے', + 'ï·±' => 'قلے', + 'ï·²' => 'الله', + 'ï·³' => 'اكبر', + 'ï·´' => 'محمد', + 'ï·µ' => 'صلعم', + 'ï·¶' => 'رسول', + 'ï··' => 'عليه', + 'ï·¸' => 'وسلم', + 'ï·¹' => 'صلى', + 'ï·º' => 'صلى الله عليه وسلم', + 'ï·»' => 'جل جلاله', + 'ï·¼' => 'ریال', + 'ï¸' => ',', + '︑' => 'ã€', + '︒' => '。', + '︓' => ':', + '︔' => ';', + '︕' => '!', + '︖' => '?', + '︗' => '〖', + '︘' => '〗', + '︙' => '...', + '︰' => '..', + '︱' => '—', + '︲' => '–', + '︳' => '_', + '︴' => '_', + '︵' => '(', + '︶' => ')', + '︷' => '{', + '︸' => '}', + '︹' => '〔', + '︺' => '〕', + '︻' => 'ã€', + '︼' => '】', + '︽' => '《', + '︾' => '》', + '︿' => '〈', + 'ï¹€' => '〉', + 'ï¹' => '「', + '﹂' => 'ã€', + '﹃' => '『', + '﹄' => 'ã€', + '﹇' => '[', + '﹈' => ']', + '﹉' => ' Ì…', + '﹊' => ' Ì…', + '﹋' => ' Ì…', + '﹌' => ' Ì…', + 'ï¹' => '_', + '﹎' => '_', + 'ï¹' => '_', + 'ï¹' => ',', + '﹑' => 'ã€', + 'ï¹’' => '.', + 'ï¹”' => ';', + '﹕' => ':', + 'ï¹–' => '?', + 'ï¹—' => '!', + '﹘' => '—', + 'ï¹™' => '(', + '﹚' => ')', + 'ï¹›' => '{', + '﹜' => '}', + 'ï¹' => '〔', + '﹞' => '〕', + '﹟' => '#', + 'ï¹ ' => '&', + '﹡' => '*', + 'ï¹¢' => '+', + 'ï¹£' => '-', + '﹤' => '<', + 'ï¹¥' => '>', + '﹦' => '=', + '﹨' => '\\', + '﹩' => '$', + '﹪' => '%', + '﹫' => '@', + 'ï¹°' => ' Ù‹', + 'ï¹±' => 'ـً', + 'ï¹²' => ' ÙŒ', + 'ï¹´' => ' Ù', + 'ï¹¶' => ' ÙŽ', + 'ï¹·' => 'Ù€ÙŽ', + 'ﹸ' => ' Ù', + 'ï¹¹' => 'Ù€Ù', + 'ﹺ' => ' Ù', + 'ï¹»' => 'Ù€Ù', + 'ï¹¼' => ' Ù‘', + 'ï¹½' => 'ـّ', + 'ï¹¾' => ' Ù’', + 'ﹿ' => 'ـْ', + 'ﺀ' => 'Ø¡', + 'ïº' => 'آ', + 'ﺂ' => 'آ', + 'ﺃ' => 'أ', + 'ﺄ' => 'أ', + 'ﺅ' => 'ÙˆÙ”', + 'ﺆ' => 'ÙˆÙ”', + 'ﺇ' => 'إ', + 'ﺈ' => 'إ', + 'ﺉ' => 'ÙŠÙ”', + 'ﺊ' => 'ÙŠÙ”', + 'ﺋ' => 'ÙŠÙ”', + 'ﺌ' => 'ÙŠÙ”', + 'ïº' => 'ا', + 'ﺎ' => 'ا', + 'ïº' => 'ب', + 'ïº' => 'ب', + 'ﺑ' => 'ب', + 'ﺒ' => 'ب', + 'ﺓ' => 'Ø©', + 'ﺔ' => 'Ø©', + 'ﺕ' => 'ت', + 'ﺖ' => 'ت', + 'ﺗ' => 'ت', + 'ﺘ' => 'ت', + 'ﺙ' => 'Ø«', + 'ﺚ' => 'Ø«', + 'ﺛ' => 'Ø«', + 'ﺜ' => 'Ø«', + 'ïº' => 'ج', + 'ﺞ' => 'ج', + 'ﺟ' => 'ج', + 'ﺠ' => 'ج', + 'ﺡ' => 'Ø­', + 'ﺢ' => 'Ø­', + 'ﺣ' => 'Ø­', + 'ﺤ' => 'Ø­', + 'ﺥ' => 'Ø®', + 'ﺦ' => 'Ø®', + 'ﺧ' => 'Ø®', + 'ﺨ' => 'Ø®', + 'ﺩ' => 'د', + 'ﺪ' => 'د', + 'ﺫ' => 'ذ', + 'ﺬ' => 'ذ', + 'ﺭ' => 'ر', + 'ﺮ' => 'ر', + 'ﺯ' => 'ز', + 'ﺰ' => 'ز', + 'ﺱ' => 'س', + 'ﺲ' => 'س', + 'ﺳ' => 'س', + 'ﺴ' => 'س', + 'ﺵ' => 'Ø´', + 'ﺶ' => 'Ø´', + 'ﺷ' => 'Ø´', + 'ﺸ' => 'Ø´', + 'ﺹ' => 'ص', + 'ﺺ' => 'ص', + 'ﺻ' => 'ص', + 'ﺼ' => 'ص', + 'ﺽ' => 'ض', + 'ﺾ' => 'ض', + 'ﺿ' => 'ض', + 'ﻀ' => 'ض', + 'ï»' => 'Ø·', + 'ﻂ' => 'Ø·', + 'ﻃ' => 'Ø·', + 'ﻄ' => 'Ø·', + 'ï»…' => 'ظ', + 'ﻆ' => 'ظ', + 'ﻇ' => 'ظ', + 'ﻈ' => 'ظ', + 'ﻉ' => 'ع', + 'ﻊ' => 'ع', + 'ﻋ' => 'ع', + 'ﻌ' => 'ع', + 'ï»' => 'غ', + 'ﻎ' => 'غ', + 'ï»' => 'غ', + 'ï»' => 'غ', + 'ﻑ' => 'Ù', + 'ï»’' => 'Ù', + 'ﻓ' => 'Ù', + 'ï»”' => 'Ù', + 'ﻕ' => 'Ù‚', + 'ï»–' => 'Ù‚', + 'ï»—' => 'Ù‚', + 'ﻘ' => 'Ù‚', + 'ï»™' => 'Ùƒ', + 'ﻚ' => 'Ùƒ', + 'ï»›' => 'Ùƒ', + 'ﻜ' => 'Ùƒ', + 'ï»' => 'Ù„', + 'ﻞ' => 'Ù„', + 'ﻟ' => 'Ù„', + 'ï» ' => 'Ù„', + 'ﻡ' => 'Ù…', + 'ﻢ' => 'Ù…', + 'ﻣ' => 'Ù…', + 'ﻤ' => 'Ù…', + 'ﻥ' => 'Ù†', + 'ﻦ' => 'Ù†', + 'ï»§' => 'Ù†', + 'ﻨ' => 'Ù†', + 'ﻩ' => 'Ù‡', + 'ﻪ' => 'Ù‡', + 'ﻫ' => 'Ù‡', + 'ﻬ' => 'Ù‡', + 'ï»­' => 'Ùˆ', + 'ï»®' => 'Ùˆ', + 'ﻯ' => 'Ù‰', + 'ï»°' => 'Ù‰', + 'ï»±' => 'ÙŠ', + 'ﻲ' => 'ÙŠ', + 'ﻳ' => 'ÙŠ', + 'ï»´' => 'ÙŠ', + 'ﻵ' => 'لآ', + 'ï»¶' => 'لآ', + 'ï»·' => 'لأ', + 'ﻸ' => 'لأ', + 'ﻹ' => 'لإ', + 'ﻺ' => 'لإ', + 'ï»»' => 'لا', + 'ﻼ' => 'لا', + 'ï¼' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + 'ï¼…' => '%', + '&' => '&', + ''' => '\'', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + 'ï¼' => '-', + '.' => '.', + 'ï¼' => '/', + 'ï¼' => '0', + '1' => '1', + 'ï¼’' => '2', + '3' => '3', + 'ï¼”' => '4', + '5' => '5', + 'ï¼–' => '6', + 'ï¼—' => '7', + '8' => '8', + 'ï¼™' => '9', + ':' => ':', + 'ï¼›' => ';', + '<' => '<', + 'ï¼' => '=', + '>' => '>', + '?' => '?', + 'ï¼ ' => '@', + 'A' => 'A', + 'ï¼¢' => 'B', + 'ï¼£' => 'C', + 'D' => 'D', + 'ï¼¥' => 'E', + 'F' => 'F', + 'ï¼§' => 'G', + 'H' => 'H', + 'I' => 'I', + 'J' => 'J', + 'K' => 'K', + 'L' => 'L', + 'ï¼­' => 'M', + 'ï¼®' => 'N', + 'O' => 'O', + 'ï¼°' => 'P', + 'ï¼±' => 'Q', + 'ï¼²' => 'R', + 'ï¼³' => 'S', + 'ï¼´' => 'T', + 'ï¼µ' => 'U', + 'ï¼¶' => 'V', + 'ï¼·' => 'W', + 'X' => 'X', + 'ï¼¹' => 'Y', + 'Z' => 'Z', + 'ï¼»' => '[', + 'ï¼¼' => '\\', + 'ï¼½' => ']', + 'ï¼¾' => '^', + '_' => '_', + 'ï½€' => '`', + 'ï½' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', + 'ï½…' => 'e', + 'f' => 'f', + 'g' => 'g', + 'h' => 'h', + 'i' => 'i', + 'j' => 'j', + 'k' => 'k', + 'l' => 'l', + 'ï½' => 'm', + 'n' => 'n', + 'ï½' => 'o', + 'ï½' => 'p', + 'q' => 'q', + 'ï½’' => 'r', + 's' => 's', + 'ï½”' => 't', + 'u' => 'u', + 'ï½–' => 'v', + 'ï½—' => 'w', + 'x' => 'x', + 'ï½™' => 'y', + 'z' => 'z', + 'ï½›' => '{', + '|' => '|', + 'ï½' => '}', + '~' => '~', + '⦅' => '⦅', + 'ï½ ' => '⦆', + '。' => '。', + 'ï½¢' => '「', + 'ï½£' => 'ã€', + '、' => 'ã€', + 'ï½¥' => '・', + 'ヲ' => 'ヲ', + 'ï½§' => 'ã‚¡', + 'ィ' => 'ã‚£', + 'ゥ' => 'ã‚¥', + 'ェ' => 'ã‚§', + 'ォ' => 'ã‚©', + 'ャ' => 'ャ', + 'ï½­' => 'ュ', + 'ï½®' => 'ョ', + 'ッ' => 'ッ', + 'ï½°' => 'ー', + 'ï½±' => 'ã‚¢', + 'ï½²' => 'イ', + 'ï½³' => 'ウ', + 'ï½´' => 'エ', + 'ï½µ' => 'オ', + 'ï½¶' => 'ã‚«', + 'ï½·' => 'ã‚­', + 'ク' => 'ク', + 'ï½¹' => 'ケ', + 'コ' => 'コ', + 'ï½»' => 'サ', + 'ï½¼' => 'ã‚·', + 'ï½½' => 'ス', + 'ï½¾' => 'ã‚»', + 'ソ' => 'ソ', + 'ï¾€' => 'ã‚¿', + 'ï¾' => 'ãƒ', + 'ツ' => 'ツ', + 'テ' => 'テ', + 'ト' => 'ト', + 'ï¾…' => 'ナ', + 'ニ' => 'ニ', + 'ヌ' => 'ヌ', + 'ネ' => 'ãƒ', + 'ノ' => 'ノ', + 'ハ' => 'ãƒ', + 'ヒ' => 'ヒ', + 'フ' => 'フ', + 'ï¾' => 'ヘ', + 'ホ' => 'ホ', + 'ï¾' => 'マ', + 'ï¾' => 'ミ', + 'ム' => 'ム', + 'ï¾’' => 'メ', + 'モ' => 'モ', + 'ï¾”' => 'ヤ', + 'ユ' => 'ユ', + 'ï¾–' => 'ヨ', + 'ï¾—' => 'ラ', + 'リ' => 'リ', + 'ï¾™' => 'ル', + 'レ' => 'レ', + 'ï¾›' => 'ロ', + 'ワ' => 'ワ', + 'ï¾' => 'ン', + '゙' => 'ã‚™', + '゚' => '゚', + 'ï¾ ' => 'á… ', + 'ᄀ' => 'á„€', + 'ï¾¢' => 'á„', + 'ï¾£' => 'ᆪ', + 'ᄂ' => 'á„‚', + 'ï¾¥' => 'ᆬ', + 'ᆭ' => 'ᆭ', + 'ï¾§' => 'ᄃ', + 'ᄄ' => 'á„„', + 'ᄅ' => 'á„…', + 'ᆰ' => 'ᆰ', + 'ᆱ' => 'ᆱ', + 'ᆲ' => 'ᆲ', + 'ï¾­' => 'ᆳ', + 'ï¾®' => 'ᆴ', + 'ᆵ' => 'ᆵ', + 'ï¾°' => 'ᄚ', + 'ï¾±' => 'ᄆ', + 'ï¾²' => 'ᄇ', + 'ï¾³' => 'ᄈ', + 'ï¾´' => 'á„¡', + 'ï¾µ' => 'ᄉ', + 'ï¾¶' => 'ᄊ', + 'ï¾·' => 'á„‹', + 'ᄌ' => 'ᄌ', + 'ï¾¹' => 'á„', + 'ᄎ' => 'ᄎ', + 'ï¾»' => 'á„', + 'ï¾¼' => 'á„', + 'ï¾½' => 'á„‘', + 'ï¾¾' => 'á„’', + 'ï¿‚' => 'á…¡', + 'ᅢ' => 'á…¢', + 'ï¿„' => 'á…£', + 'ï¿…' => 'á…¤', + 'ᅥ' => 'á…¥', + 'ᅦ' => 'á…¦', + 'ᅧ' => 'á…§', + 'ï¿‹' => 'á…¨', + 'ᅩ' => 'á…©', + 'ï¿' => 'á…ª', + 'ᅫ' => 'á…«', + 'ï¿' => 'á…¬', + 'ï¿’' => 'á…­', + 'ï¿“' => 'á…®', + 'ï¿”' => 'á…¯', + 'ï¿•' => 'á…°', + 'ï¿–' => 'á…±', + 'ï¿—' => 'á…²', + 'ᅳ' => 'á…³', + 'ï¿›' => 'á…´', + 'ᅵ' => 'á…µ', + 'ï¿ ' => '¢', + 'ï¿¡' => '£', + 'ï¿¢' => '¬', + 'ï¿£' => ' Ì„', + '¦' => '¦', + 'ï¿¥' => 'Â¥', + '₩' => 'â‚©', + '│' => '│', + 'ï¿©' => 'â†', + '↑' => '↑', + 'ï¿«' => '→', + '↓' => '↓', + 'ï¿­' => 'â– ', + 'ï¿®' => 'â—‹', + 'ð€' => 'A', + 'ð' => 'B', + 'ð‚' => 'C', + 'ðƒ' => 'D', + 'ð„' => 'E', + 'ð…' => 'F', + 'ð†' => 'G', + 'ð‡' => 'H', + 'ðˆ' => 'I', + 'ð‰' => 'J', + 'ðŠ' => 'K', + 'ð‹' => 'L', + 'ðŒ' => 'M', + 'ð' => 'N', + 'ðŽ' => 'O', + 'ð' => 'P', + 'ð' => 'Q', + 'ð‘' => 'R', + 'ð’' => 'S', + 'ð“' => 'T', + 'ð”' => 'U', + 'ð•' => 'V', + 'ð–' => 'W', + 'ð—' => 'X', + 'ð˜' => 'Y', + 'ð™' => 'Z', + 'ðš' => 'a', + 'ð›' => 'b', + 'ðœ' => 'c', + 'ð' => 'd', + 'ðž' => 'e', + 'ðŸ' => 'f', + 'ð ' => 'g', + 'ð¡' => 'h', + 'ð¢' => 'i', + 'ð£' => 'j', + 'ð¤' => 'k', + 'ð¥' => 'l', + 'ð¦' => 'm', + 'ð§' => 'n', + 'ð¨' => 'o', + 'ð©' => 'p', + 'ðª' => 'q', + 'ð«' => 'r', + 'ð¬' => 's', + 'ð­' => 't', + 'ð®' => 'u', + 'ð¯' => 'v', + 'ð°' => 'w', + 'ð±' => 'x', + 'ð²' => 'y', + 'ð³' => 'z', + 'ð´' => 'A', + 'ðµ' => 'B', + 'ð¶' => 'C', + 'ð·' => 'D', + 'ð¸' => 'E', + 'ð¹' => 'F', + 'ðº' => 'G', + 'ð»' => 'H', + 'ð¼' => 'I', + 'ð½' => 'J', + 'ð¾' => 'K', + 'ð¿' => 'L', + 'ð‘€' => 'M', + 'ð‘' => 'N', + 'ð‘‚' => 'O', + 'ð‘ƒ' => 'P', + 'ð‘„' => 'Q', + 'ð‘…' => 'R', + 'ð‘†' => 'S', + 'ð‘‡' => 'T', + 'ð‘ˆ' => 'U', + 'ð‘‰' => 'V', + 'ð‘Š' => 'W', + 'ð‘‹' => 'X', + 'ð‘Œ' => 'Y', + 'ð‘' => 'Z', + 'ð‘Ž' => 'a', + 'ð‘' => 'b', + 'ð‘' => 'c', + 'ð‘‘' => 'd', + 'ð‘’' => 'e', + 'ð‘“' => 'f', + 'ð‘”' => 'g', + 'ð‘–' => 'i', + 'ð‘—' => 'j', + 'ð‘˜' => 'k', + 'ð‘™' => 'l', + 'ð‘š' => 'm', + 'ð‘›' => 'n', + 'ð‘œ' => 'o', + 'ð‘' => 'p', + 'ð‘ž' => 'q', + 'ð‘Ÿ' => 'r', + 'ð‘ ' => 's', + 'ð‘¡' => 't', + 'ð‘¢' => 'u', + 'ð‘£' => 'v', + 'ð‘¤' => 'w', + 'ð‘¥' => 'x', + 'ð‘¦' => 'y', + 'ð‘§' => 'z', + 'ð‘¨' => 'A', + 'ð‘©' => 'B', + 'ð‘ª' => 'C', + 'ð‘«' => 'D', + 'ð‘¬' => 'E', + 'ð‘­' => 'F', + 'ð‘®' => 'G', + 'ð‘¯' => 'H', + 'ð‘°' => 'I', + 'ð‘±' => 'J', + 'ð‘²' => 'K', + 'ð‘³' => 'L', + 'ð‘´' => 'M', + 'ð‘µ' => 'N', + 'ð‘¶' => 'O', + 'ð‘·' => 'P', + 'ð‘¸' => 'Q', + 'ð‘¹' => 'R', + 'ð‘º' => 'S', + 'ð‘»' => 'T', + 'ð‘¼' => 'U', + 'ð‘½' => 'V', + 'ð‘¾' => 'W', + 'ð‘¿' => 'X', + 'ð’€' => 'Y', + 'ð’' => 'Z', + 'ð’‚' => 'a', + 'ð’ƒ' => 'b', + 'ð’„' => 'c', + 'ð’…' => 'd', + 'ð’†' => 'e', + 'ð’‡' => 'f', + 'ð’ˆ' => 'g', + 'ð’‰' => 'h', + 'ð’Š' => 'i', + 'ð’‹' => 'j', + 'ð’Œ' => 'k', + 'ð’' => 'l', + 'ð’Ž' => 'm', + 'ð’' => 'n', + 'ð’' => 'o', + 'ð’‘' => 'p', + 'ð’’' => 'q', + 'ð’“' => 'r', + 'ð’”' => 's', + 'ð’•' => 't', + 'ð’–' => 'u', + 'ð’—' => 'v', + 'ð’˜' => 'w', + 'ð’™' => 'x', + 'ð’š' => 'y', + 'ð’›' => 'z', + 'ð’œ' => 'A', + 'ð’ž' => 'C', + 'ð’Ÿ' => 'D', + 'ð’¢' => 'G', + 'ð’¥' => 'J', + 'ð’¦' => 'K', + 'ð’©' => 'N', + 'ð’ª' => 'O', + 'ð’«' => 'P', + 'ð’¬' => 'Q', + 'ð’®' => 'S', + 'ð’¯' => 'T', + 'ð’°' => 'U', + 'ð’±' => 'V', + 'ð’²' => 'W', + 'ð’³' => 'X', + 'ð’´' => 'Y', + 'ð’µ' => 'Z', + 'ð’¶' => 'a', + 'ð’·' => 'b', + 'ð’¸' => 'c', + 'ð’¹' => 'd', + 'ð’»' => 'f', + 'ð’½' => 'h', + 'ð’¾' => 'i', + 'ð’¿' => 'j', + 'ð“€' => 'k', + 'ð“' => 'l', + 'ð“‚' => 'm', + 'ð“ƒ' => 'n', + 'ð“…' => 'p', + 'ð“†' => 'q', + 'ð“‡' => 'r', + 'ð“ˆ' => 's', + 'ð“‰' => 't', + 'ð“Š' => 'u', + 'ð“‹' => 'v', + 'ð“Œ' => 'w', + 'ð“' => 'x', + 'ð“Ž' => 'y', + 'ð“' => 'z', + 'ð“' => 'A', + 'ð“‘' => 'B', + 'ð“’' => 'C', + 'ð““' => 'D', + 'ð“”' => 'E', + 'ð“•' => 'F', + 'ð“–' => 'G', + 'ð“—' => 'H', + 'ð“˜' => 'I', + 'ð“™' => 'J', + 'ð“š' => 'K', + 'ð“›' => 'L', + 'ð“œ' => 'M', + 'ð“' => 'N', + 'ð“ž' => 'O', + 'ð“Ÿ' => 'P', + 'ð“ ' => 'Q', + 'ð“¡' => 'R', + 'ð“¢' => 'S', + 'ð“£' => 'T', + 'ð“¤' => 'U', + 'ð“¥' => 'V', + 'ð“¦' => 'W', + 'ð“§' => 'X', + 'ð“¨' => 'Y', + 'ð“©' => 'Z', + 'ð“ª' => 'a', + 'ð“«' => 'b', + 'ð“¬' => 'c', + 'ð“­' => 'd', + 'ð“®' => 'e', + 'ð“¯' => 'f', + 'ð“°' => 'g', + 'ð“±' => 'h', + 'ð“²' => 'i', + 'ð“³' => 'j', + 'ð“´' => 'k', + 'ð“µ' => 'l', + 'ð“¶' => 'm', + 'ð“·' => 'n', + 'ð“¸' => 'o', + 'ð“¹' => 'p', + 'ð“º' => 'q', + 'ð“»' => 'r', + 'ð“¼' => 's', + 'ð“½' => 't', + 'ð“¾' => 'u', + 'ð“¿' => 'v', + 'ð”€' => 'w', + 'ð”' => 'x', + 'ð”‚' => 'y', + 'ð”ƒ' => 'z', + 'ð”„' => 'A', + 'ð”…' => 'B', + 'ð”‡' => 'D', + 'ð”ˆ' => 'E', + 'ð”‰' => 'F', + 'ð”Š' => 'G', + 'ð”' => 'J', + 'ð”Ž' => 'K', + 'ð”' => 'L', + 'ð”' => 'M', + 'ð”‘' => 'N', + 'ð”’' => 'O', + 'ð”“' => 'P', + 'ð””' => 'Q', + 'ð”–' => 'S', + 'ð”—' => 'T', + 'ð”˜' => 'U', + 'ð”™' => 'V', + 'ð”š' => 'W', + 'ð”›' => 'X', + 'ð”œ' => 'Y', + 'ð”ž' => 'a', + 'ð”Ÿ' => 'b', + 'ð” ' => 'c', + 'ð”¡' => 'd', + 'ð”¢' => 'e', + 'ð”£' => 'f', + 'ð”¤' => 'g', + 'ð”¥' => 'h', + 'ð”¦' => 'i', + 'ð”§' => 'j', + 'ð”¨' => 'k', + 'ð”©' => 'l', + 'ð”ª' => 'm', + 'ð”«' => 'n', + 'ð”¬' => 'o', + 'ð”­' => 'p', + 'ð”®' => 'q', + 'ð”¯' => 'r', + 'ð”°' => 's', + 'ð”±' => 't', + 'ð”²' => 'u', + 'ð”³' => 'v', + 'ð”´' => 'w', + 'ð”µ' => 'x', + 'ð”¶' => 'y', + 'ð”·' => 'z', + 'ð”¸' => 'A', + 'ð”¹' => 'B', + 'ð”»' => 'D', + 'ð”¼' => 'E', + 'ð”½' => 'F', + 'ð”¾' => 'G', + 'ð•€' => 'I', + 'ð•' => 'J', + 'ð•‚' => 'K', + 'ð•ƒ' => 'L', + 'ð•„' => 'M', + 'ð•†' => 'O', + 'ð•Š' => 'S', + 'ð•‹' => 'T', + 'ð•Œ' => 'U', + 'ð•' => 'V', + 'ð•Ž' => 'W', + 'ð•' => 'X', + 'ð•' => 'Y', + 'ð•’' => 'a', + 'ð•“' => 'b', + 'ð•”' => 'c', + 'ð••' => 'd', + 'ð•–' => 'e', + 'ð•—' => 'f', + 'ð•˜' => 'g', + 'ð•™' => 'h', + 'ð•š' => 'i', + 'ð•›' => 'j', + 'ð•œ' => 'k', + 'ð•' => 'l', + 'ð•ž' => 'm', + 'ð•Ÿ' => 'n', + 'ð• ' => 'o', + 'ð•¡' => 'p', + 'ð•¢' => 'q', + 'ð•£' => 'r', + 'ð•¤' => 's', + 'ð•¥' => 't', + 'ð•¦' => 'u', + 'ð•§' => 'v', + 'ð•¨' => 'w', + 'ð•©' => 'x', + 'ð•ª' => 'y', + 'ð•«' => 'z', + 'ð•¬' => 'A', + 'ð•­' => 'B', + 'ð•®' => 'C', + 'ð•¯' => 'D', + 'ð•°' => 'E', + 'ð•±' => 'F', + 'ð•²' => 'G', + 'ð•³' => 'H', + 'ð•´' => 'I', + 'ð•µ' => 'J', + 'ð•¶' => 'K', + 'ð•·' => 'L', + 'ð•¸' => 'M', + 'ð•¹' => 'N', + 'ð•º' => 'O', + 'ð•»' => 'P', + 'ð•¼' => 'Q', + 'ð•½' => 'R', + 'ð•¾' => 'S', + 'ð•¿' => 'T', + 'ð–€' => 'U', + 'ð–' => 'V', + 'ð–‚' => 'W', + 'ð–ƒ' => 'X', + 'ð–„' => 'Y', + 'ð–…' => 'Z', + 'ð–†' => 'a', + 'ð–‡' => 'b', + 'ð–ˆ' => 'c', + 'ð–‰' => 'd', + 'ð–Š' => 'e', + 'ð–‹' => 'f', + 'ð–Œ' => 'g', + 'ð–' => 'h', + 'ð–Ž' => 'i', + 'ð–' => 'j', + 'ð–' => 'k', + 'ð–‘' => 'l', + 'ð–’' => 'm', + 'ð–“' => 'n', + 'ð–”' => 'o', + 'ð–•' => 'p', + 'ð––' => 'q', + 'ð–—' => 'r', + 'ð–˜' => 's', + 'ð–™' => 't', + 'ð–š' => 'u', + 'ð–›' => 'v', + 'ð–œ' => 'w', + 'ð–' => 'x', + 'ð–ž' => 'y', + 'ð–Ÿ' => 'z', + 'ð– ' => 'A', + 'ð–¡' => 'B', + 'ð–¢' => 'C', + 'ð–£' => 'D', + 'ð–¤' => 'E', + 'ð–¥' => 'F', + 'ð–¦' => 'G', + 'ð–§' => 'H', + 'ð–¨' => 'I', + 'ð–©' => 'J', + 'ð–ª' => 'K', + 'ð–«' => 'L', + 'ð–¬' => 'M', + 'ð–­' => 'N', + 'ð–®' => 'O', + 'ð–¯' => 'P', + 'ð–°' => 'Q', + 'ð–±' => 'R', + 'ð–²' => 'S', + 'ð–³' => 'T', + 'ð–´' => 'U', + 'ð–µ' => 'V', + 'ð–¶' => 'W', + 'ð–·' => 'X', + 'ð–¸' => 'Y', + 'ð–¹' => 'Z', + 'ð–º' => 'a', + 'ð–»' => 'b', + 'ð–¼' => 'c', + 'ð–½' => 'd', + 'ð–¾' => 'e', + 'ð–¿' => 'f', + 'ð—€' => 'g', + 'ð—' => 'h', + 'ð—‚' => 'i', + 'ð—ƒ' => 'j', + 'ð—„' => 'k', + 'ð—…' => 'l', + 'ð—†' => 'm', + 'ð—‡' => 'n', + 'ð—ˆ' => 'o', + 'ð—‰' => 'p', + 'ð—Š' => 'q', + 'ð—‹' => 'r', + 'ð—Œ' => 's', + 'ð—' => 't', + 'ð—Ž' => 'u', + 'ð—' => 'v', + 'ð—' => 'w', + 'ð—‘' => 'x', + 'ð—’' => 'y', + 'ð—“' => 'z', + 'ð—”' => 'A', + 'ð—•' => 'B', + 'ð—–' => 'C', + 'ð——' => 'D', + 'ð—˜' => 'E', + 'ð—™' => 'F', + 'ð—š' => 'G', + 'ð—›' => 'H', + 'ð—œ' => 'I', + 'ð—' => 'J', + 'ð—ž' => 'K', + 'ð—Ÿ' => 'L', + 'ð— ' => 'M', + 'ð—¡' => 'N', + 'ð—¢' => 'O', + 'ð—£' => 'P', + 'ð—¤' => 'Q', + 'ð—¥' => 'R', + 'ð—¦' => 'S', + 'ð—§' => 'T', + 'ð—¨' => 'U', + 'ð—©' => 'V', + 'ð—ª' => 'W', + 'ð—«' => 'X', + 'ð—¬' => 'Y', + 'ð—­' => 'Z', + 'ð—®' => 'a', + 'ð—¯' => 'b', + 'ð—°' => 'c', + 'ð—±' => 'd', + 'ð—²' => 'e', + 'ð—³' => 'f', + 'ð—´' => 'g', + 'ð—µ' => 'h', + 'ð—¶' => 'i', + 'ð—·' => 'j', + 'ð—¸' => 'k', + 'ð—¹' => 'l', + 'ð—º' => 'm', + 'ð—»' => 'n', + 'ð—¼' => 'o', + 'ð—½' => 'p', + 'ð—¾' => 'q', + 'ð—¿' => 'r', + 'ð˜€' => 's', + 'ð˜' => 't', + 'ð˜‚' => 'u', + 'ð˜ƒ' => 'v', + 'ð˜„' => 'w', + 'ð˜…' => 'x', + 'ð˜†' => 'y', + 'ð˜‡' => 'z', + 'ð˜ˆ' => 'A', + 'ð˜‰' => 'B', + 'ð˜Š' => 'C', + 'ð˜‹' => 'D', + 'ð˜Œ' => 'E', + 'ð˜' => 'F', + 'ð˜Ž' => 'G', + 'ð˜' => 'H', + 'ð˜' => 'I', + 'ð˜‘' => 'J', + 'ð˜’' => 'K', + 'ð˜“' => 'L', + 'ð˜”' => 'M', + 'ð˜•' => 'N', + 'ð˜–' => 'O', + 'ð˜—' => 'P', + 'ð˜˜' => 'Q', + 'ð˜™' => 'R', + 'ð˜š' => 'S', + 'ð˜›' => 'T', + 'ð˜œ' => 'U', + 'ð˜' => 'V', + 'ð˜ž' => 'W', + 'ð˜Ÿ' => 'X', + 'ð˜ ' => 'Y', + 'ð˜¡' => 'Z', + 'ð˜¢' => 'a', + 'ð˜£' => 'b', + 'ð˜¤' => 'c', + 'ð˜¥' => 'd', + 'ð˜¦' => 'e', + 'ð˜§' => 'f', + 'ð˜¨' => 'g', + 'ð˜©' => 'h', + 'ð˜ª' => 'i', + 'ð˜«' => 'j', + 'ð˜¬' => 'k', + 'ð˜­' => 'l', + 'ð˜®' => 'm', + 'ð˜¯' => 'n', + 'ð˜°' => 'o', + 'ð˜±' => 'p', + 'ð˜²' => 'q', + 'ð˜³' => 'r', + 'ð˜´' => 's', + 'ð˜µ' => 't', + 'ð˜¶' => 'u', + 'ð˜·' => 'v', + 'ð˜¸' => 'w', + 'ð˜¹' => 'x', + 'ð˜º' => 'y', + 'ð˜»' => 'z', + 'ð˜¼' => 'A', + 'ð˜½' => 'B', + 'ð˜¾' => 'C', + 'ð˜¿' => 'D', + 'ð™€' => 'E', + 'ð™' => 'F', + 'ð™‚' => 'G', + 'ð™ƒ' => 'H', + 'ð™„' => 'I', + 'ð™…' => 'J', + 'ð™†' => 'K', + 'ð™‡' => 'L', + 'ð™ˆ' => 'M', + 'ð™‰' => 'N', + 'ð™Š' => 'O', + 'ð™‹' => 'P', + 'ð™Œ' => 'Q', + 'ð™' => 'R', + 'ð™Ž' => 'S', + 'ð™' => 'T', + 'ð™' => 'U', + 'ð™‘' => 'V', + 'ð™’' => 'W', + 'ð™“' => 'X', + 'ð™”' => 'Y', + 'ð™•' => 'Z', + 'ð™–' => 'a', + 'ð™—' => 'b', + 'ð™˜' => 'c', + 'ð™™' => 'd', + 'ð™š' => 'e', + 'ð™›' => 'f', + 'ð™œ' => 'g', + 'ð™' => 'h', + 'ð™ž' => 'i', + 'ð™Ÿ' => 'j', + 'ð™ ' => 'k', + 'ð™¡' => 'l', + 'ð™¢' => 'm', + 'ð™£' => 'n', + 'ð™¤' => 'o', + 'ð™¥' => 'p', + 'ð™¦' => 'q', + 'ð™§' => 'r', + 'ð™¨' => 's', + 'ð™©' => 't', + 'ð™ª' => 'u', + 'ð™«' => 'v', + 'ð™¬' => 'w', + 'ð™­' => 'x', + 'ð™®' => 'y', + 'ð™¯' => 'z', + 'ð™°' => 'A', + 'ð™±' => 'B', + 'ð™²' => 'C', + 'ð™³' => 'D', + 'ð™´' => 'E', + 'ð™µ' => 'F', + 'ð™¶' => 'G', + 'ð™·' => 'H', + 'ð™¸' => 'I', + 'ð™¹' => 'J', + 'ð™º' => 'K', + 'ð™»' => 'L', + 'ð™¼' => 'M', + 'ð™½' => 'N', + 'ð™¾' => 'O', + 'ð™¿' => 'P', + 'ðš€' => 'Q', + 'ðš' => 'R', + 'ðš‚' => 'S', + 'ðšƒ' => 'T', + 'ðš„' => 'U', + 'ðš…' => 'V', + 'ðš†' => 'W', + 'ðš‡' => 'X', + 'ðšˆ' => 'Y', + 'ðš‰' => 'Z', + 'ðšŠ' => 'a', + 'ðš‹' => 'b', + 'ðšŒ' => 'c', + 'ðš' => 'd', + 'ðšŽ' => 'e', + 'ðš' => 'f', + 'ðš' => 'g', + 'ðš‘' => 'h', + 'ðš’' => 'i', + 'ðš“' => 'j', + 'ðš”' => 'k', + 'ðš•' => 'l', + 'ðš–' => 'm', + 'ðš—' => 'n', + 'ðš˜' => 'o', + 'ðš™' => 'p', + 'ðšš' => 'q', + 'ðš›' => 'r', + 'ðšœ' => 's', + 'ðš' => 't', + 'ðšž' => 'u', + 'ðšŸ' => 'v', + 'ðš ' => 'w', + 'ðš¡' => 'x', + 'ðš¢' => 'y', + 'ðš£' => 'z', + 'ðš¤' => 'ı', + 'ðš¥' => 'È·', + 'ðš¨' => 'Α', + 'ðš©' => 'Î’', + 'ðšª' => 'Γ', + 'ðš«' => 'Δ', + 'ðš¬' => 'Ε', + 'ðš­' => 'Ζ', + 'ðš®' => 'Η', + 'ðš¯' => 'Θ', + 'ðš°' => 'Ι', + 'ðš±' => 'Κ', + 'ðš²' => 'Λ', + 'ðš³' => 'Μ', + 'ðš´' => 'Î', + 'ðšµ' => 'Ξ', + 'ðš¶' => 'Ο', + 'ðš·' => 'Π', + 'ðš¸' => 'Ρ', + 'ðš¹' => 'Θ', + 'ðšº' => 'Σ', + 'ðš»' => 'Τ', + 'ðš¼' => 'Î¥', + 'ðš½' => 'Φ', + 'ðš¾' => 'Χ', + 'ðš¿' => 'Ψ', + 'ð›€' => 'Ω', + 'ð›' => '∇', + 'ð›‚' => 'α', + 'ð›ƒ' => 'β', + 'ð›„' => 'γ', + 'ð›…' => 'δ', + 'ð›†' => 'ε', + 'ð›‡' => 'ζ', + 'ð›ˆ' => 'η', + 'ð›‰' => 'θ', + 'ð›Š' => 'ι', + 'ð›‹' => 'κ', + 'ð›Œ' => 'λ', + 'ð›' => 'μ', + 'ð›Ž' => 'ν', + 'ð›' => 'ξ', + 'ð›' => 'ο', + 'ð›‘' => 'Ï€', + 'ð›’' => 'Ï', + 'ð›“' => 'Ï‚', + 'ð›”' => 'σ', + 'ð›•' => 'Ï„', + 'ð›–' => 'Ï…', + 'ð›—' => 'φ', + 'ð›˜' => 'χ', + 'ð›™' => 'ψ', + 'ð›š' => 'ω', + 'ð››' => '∂', + 'ð›œ' => 'ε', + 'ð›' => 'θ', + 'ð›ž' => 'κ', + 'ð›Ÿ' => 'φ', + 'ð› ' => 'Ï', + 'ð›¡' => 'Ï€', + 'ð›¢' => 'Α', + 'ð›£' => 'Î’', + 'ð›¤' => 'Γ', + 'ð›¥' => 'Δ', + 'ð›¦' => 'Ε', + 'ð›§' => 'Ζ', + 'ð›¨' => 'Η', + 'ð›©' => 'Θ', + 'ð›ª' => 'Ι', + 'ð›«' => 'Κ', + 'ð›¬' => 'Λ', + 'ð›­' => 'Μ', + 'ð›®' => 'Î', + 'ð›¯' => 'Ξ', + 'ð›°' => 'Ο', + 'ð›±' => 'Π', + 'ð›²' => 'Ρ', + 'ð›³' => 'Θ', + 'ð›´' => 'Σ', + 'ð›µ' => 'Τ', + 'ð›¶' => 'Î¥', + 'ð›·' => 'Φ', + 'ð›¸' => 'Χ', + 'ð›¹' => 'Ψ', + 'ð›º' => 'Ω', + 'ð›»' => '∇', + 'ð›¼' => 'α', + 'ð›½' => 'β', + 'ð›¾' => 'γ', + 'ð›¿' => 'δ', + 'ðœ€' => 'ε', + 'ðœ' => 'ζ', + 'ðœ‚' => 'η', + 'ðœƒ' => 'θ', + 'ðœ„' => 'ι', + 'ðœ…' => 'κ', + 'ðœ†' => 'λ', + 'ðœ‡' => 'μ', + 'ðœˆ' => 'ν', + 'ðœ‰' => 'ξ', + 'ðœŠ' => 'ο', + 'ðœ‹' => 'Ï€', + 'ðœŒ' => 'Ï', + 'ðœ' => 'Ï‚', + 'ðœŽ' => 'σ', + 'ðœ' => 'Ï„', + 'ðœ' => 'Ï…', + 'ðœ‘' => 'φ', + 'ðœ’' => 'χ', + 'ðœ“' => 'ψ', + 'ðœ”' => 'ω', + 'ðœ•' => '∂', + 'ðœ–' => 'ε', + 'ðœ—' => 'θ', + 'ðœ˜' => 'κ', + 'ðœ™' => 'φ', + 'ðœš' => 'Ï', + 'ðœ›' => 'Ï€', + 'ðœœ' => 'Α', + 'ðœ' => 'Î’', + 'ðœž' => 'Γ', + 'ðœŸ' => 'Δ', + 'ðœ ' => 'Ε', + 'ðœ¡' => 'Ζ', + 'ðœ¢' => 'Η', + 'ðœ£' => 'Θ', + 'ðœ¤' => 'Ι', + 'ðœ¥' => 'Κ', + 'ðœ¦' => 'Λ', + 'ðœ§' => 'Μ', + 'ðœ¨' => 'Î', + 'ðœ©' => 'Ξ', + 'ðœª' => 'Ο', + 'ðœ«' => 'Π', + 'ðœ¬' => 'Ρ', + 'ðœ­' => 'Θ', + 'ðœ®' => 'Σ', + 'ðœ¯' => 'Τ', + 'ðœ°' => 'Î¥', + 'ðœ±' => 'Φ', + 'ðœ²' => 'Χ', + 'ðœ³' => 'Ψ', + 'ðœ´' => 'Ω', + 'ðœµ' => '∇', + 'ðœ¶' => 'α', + 'ðœ·' => 'β', + 'ðœ¸' => 'γ', + 'ðœ¹' => 'δ', + 'ðœº' => 'ε', + 'ðœ»' => 'ζ', + 'ðœ¼' => 'η', + 'ðœ½' => 'θ', + 'ðœ¾' => 'ι', + 'ðœ¿' => 'κ', + 'ð€' => 'λ', + 'ð' => 'μ', + 'ð‚' => 'ν', + 'ðƒ' => 'ξ', + 'ð„' => 'ο', + 'ð…' => 'Ï€', + 'ð†' => 'Ï', + 'ð‡' => 'Ï‚', + 'ðˆ' => 'σ', + 'ð‰' => 'Ï„', + 'ðŠ' => 'Ï…', + 'ð‹' => 'φ', + 'ðŒ' => 'χ', + 'ð' => 'ψ', + 'ðŽ' => 'ω', + 'ð' => '∂', + 'ð' => 'ε', + 'ð‘' => 'θ', + 'ð’' => 'κ', + 'ð“' => 'φ', + 'ð”' => 'Ï', + 'ð•' => 'Ï€', + 'ð–' => 'Α', + 'ð—' => 'Î’', + 'ð˜' => 'Γ', + 'ð™' => 'Δ', + 'ðš' => 'Ε', + 'ð›' => 'Ζ', + 'ðœ' => 'Η', + 'ð' => 'Θ', + 'ðž' => 'Ι', + 'ðŸ' => 'Κ', + 'ð ' => 'Λ', + 'ð¡' => 'Μ', + 'ð¢' => 'Î', + 'ð£' => 'Ξ', + 'ð¤' => 'Ο', + 'ð¥' => 'Π', + 'ð¦' => 'Ρ', + 'ð§' => 'Θ', + 'ð¨' => 'Σ', + 'ð©' => 'Τ', + 'ðª' => 'Î¥', + 'ð«' => 'Φ', + 'ð¬' => 'Χ', + 'ð­' => 'Ψ', + 'ð®' => 'Ω', + 'ð¯' => '∇', + 'ð°' => 'α', + 'ð±' => 'β', + 'ð²' => 'γ', + 'ð³' => 'δ', + 'ð´' => 'ε', + 'ðµ' => 'ζ', + 'ð¶' => 'η', + 'ð·' => 'θ', + 'ð¸' => 'ι', + 'ð¹' => 'κ', + 'ðº' => 'λ', + 'ð»' => 'μ', + 'ð¼' => 'ν', + 'ð½' => 'ξ', + 'ð¾' => 'ο', + 'ð¿' => 'Ï€', + 'ðž€' => 'Ï', + 'ðž' => 'Ï‚', + 'ðž‚' => 'σ', + 'ðžƒ' => 'Ï„', + 'ðž„' => 'Ï…', + 'ðž…' => 'φ', + 'ðž†' => 'χ', + 'ðž‡' => 'ψ', + 'ðžˆ' => 'ω', + 'ðž‰' => '∂', + 'ðžŠ' => 'ε', + 'ðž‹' => 'θ', + 'ðžŒ' => 'κ', + 'ðž' => 'φ', + 'ðžŽ' => 'Ï', + 'ðž' => 'Ï€', + 'ðž' => 'Α', + 'ðž‘' => 'Î’', + 'ðž’' => 'Γ', + 'ðž“' => 'Δ', + 'ðž”' => 'Ε', + 'ðž•' => 'Ζ', + 'ðž–' => 'Η', + 'ðž—' => 'Θ', + 'ðž˜' => 'Ι', + 'ðž™' => 'Κ', + 'ðžš' => 'Λ', + 'ðž›' => 'Μ', + 'ðžœ' => 'Î', + 'ðž' => 'Ξ', + 'ðžž' => 'Ο', + 'ðžŸ' => 'Π', + 'ðž ' => 'Ρ', + 'ðž¡' => 'Θ', + 'ðž¢' => 'Σ', + 'ðž£' => 'Τ', + 'ðž¤' => 'Î¥', + 'ðž¥' => 'Φ', + 'ðž¦' => 'Χ', + 'ðž§' => 'Ψ', + 'ðž¨' => 'Ω', + 'ðž©' => '∇', + 'ðžª' => 'α', + 'ðž«' => 'β', + 'ðž¬' => 'γ', + 'ðž­' => 'δ', + 'ðž®' => 'ε', + 'ðž¯' => 'ζ', + 'ðž°' => 'η', + 'ðž±' => 'θ', + 'ðž²' => 'ι', + 'ðž³' => 'κ', + 'ðž´' => 'λ', + 'ðžµ' => 'μ', + 'ðž¶' => 'ν', + 'ðž·' => 'ξ', + 'ðž¸' => 'ο', + 'ðž¹' => 'Ï€', + 'ðžº' => 'Ï', + 'ðž»' => 'Ï‚', + 'ðž¼' => 'σ', + 'ðž½' => 'Ï„', + 'ðž¾' => 'Ï…', + 'ðž¿' => 'φ', + 'ðŸ€' => 'χ', + 'ðŸ' => 'ψ', + 'ðŸ‚' => 'ω', + 'ðŸƒ' => '∂', + 'ðŸ„' => 'ε', + 'ðŸ…' => 'θ', + 'ðŸ†' => 'κ', + 'ðŸ‡' => 'φ', + 'ðŸˆ' => 'Ï', + 'ðŸ‰' => 'Ï€', + 'ðŸŠ' => 'Ïœ', + 'ðŸ‹' => 'Ï', + 'ðŸŽ' => '0', + 'ðŸ' => '1', + 'ðŸ' => '2', + 'ðŸ‘' => '3', + 'ðŸ’' => '4', + 'ðŸ“' => '5', + 'ðŸ”' => '6', + 'ðŸ•' => '7', + 'ðŸ–' => '8', + 'ðŸ—' => '9', + 'ðŸ˜' => '0', + 'ðŸ™' => '1', + 'ðŸš' => '2', + 'ðŸ›' => '3', + 'ðŸœ' => '4', + 'ðŸ' => '5', + 'ðŸž' => '6', + 'ðŸŸ' => '7', + 'ðŸ ' => '8', + 'ðŸ¡' => '9', + 'ðŸ¢' => '0', + 'ðŸ£' => '1', + 'ðŸ¤' => '2', + 'ðŸ¥' => '3', + 'ðŸ¦' => '4', + 'ðŸ§' => '5', + 'ðŸ¨' => '6', + 'ðŸ©' => '7', + 'ðŸª' => '8', + 'ðŸ«' => '9', + 'ðŸ¬' => '0', + 'ðŸ­' => '1', + 'ðŸ®' => '2', + 'ðŸ¯' => '3', + 'ðŸ°' => '4', + 'ðŸ±' => '5', + 'ðŸ²' => '6', + 'ðŸ³' => '7', + 'ðŸ´' => '8', + 'ðŸµ' => '9', + 'ðŸ¶' => '0', + 'ðŸ·' => '1', + 'ðŸ¸' => '2', + 'ðŸ¹' => '3', + 'ðŸº' => '4', + 'ðŸ»' => '5', + 'ðŸ¼' => '6', + 'ðŸ½' => '7', + 'ðŸ¾' => '8', + 'ðŸ¿' => '9', + '𞸀' => 'ا', + 'ðž¸' => 'ب', + '𞸂' => 'ج', + '𞸃' => 'د', + '𞸅' => 'Ùˆ', + '𞸆' => 'ز', + '𞸇' => 'Ø­', + '𞸈' => 'Ø·', + '𞸉' => 'ÙŠ', + '𞸊' => 'Ùƒ', + '𞸋' => 'Ù„', + '𞸌' => 'Ù…', + 'ðž¸' => 'Ù†', + '𞸎' => 'س', + 'ðž¸' => 'ع', + 'ðž¸' => 'Ù', + '𞸑' => 'ص', + '𞸒' => 'Ù‚', + '𞸓' => 'ر', + '𞸔' => 'Ø´', + '𞸕' => 'ت', + '𞸖' => 'Ø«', + '𞸗' => 'Ø®', + '𞸘' => 'ذ', + '𞸙' => 'ض', + '𞸚' => 'ظ', + '𞸛' => 'غ', + '𞸜' => 'Ù®', + 'ðž¸' => 'Úº', + '𞸞' => 'Ú¡', + '𞸟' => 'Ù¯', + '𞸡' => 'ب', + '𞸢' => 'ج', + '𞸤' => 'Ù‡', + '𞸧' => 'Ø­', + '𞸩' => 'ÙŠ', + '𞸪' => 'Ùƒ', + '𞸫' => 'Ù„', + '𞸬' => 'Ù…', + '𞸭' => 'Ù†', + '𞸮' => 'س', + '𞸯' => 'ع', + '𞸰' => 'Ù', + '𞸱' => 'ص', + '𞸲' => 'Ù‚', + '𞸴' => 'Ø´', + '𞸵' => 'ت', + '𞸶' => 'Ø«', + '𞸷' => 'Ø®', + '𞸹' => 'ض', + '𞸻' => 'غ', + '𞹂' => 'ج', + '𞹇' => 'Ø­', + '𞹉' => 'ÙŠ', + '𞹋' => 'Ù„', + 'ðž¹' => 'Ù†', + '𞹎' => 'س', + 'ðž¹' => 'ع', + '𞹑' => 'ص', + 'ðž¹’' => 'Ù‚', + 'ðž¹”' => 'Ø´', + 'ðž¹—' => 'Ø®', + 'ðž¹™' => 'ض', + 'ðž¹›' => 'غ', + 'ðž¹' => 'Úº', + '𞹟' => 'Ù¯', + '𞹡' => 'ب', + 'ðž¹¢' => 'ج', + '𞹤' => 'Ù‡', + 'ðž¹§' => 'Ø­', + '𞹨' => 'Ø·', + '𞹩' => 'ÙŠ', + '𞹪' => 'Ùƒ', + '𞹬' => 'Ù…', + 'ðž¹­' => 'Ù†', + 'ðž¹®' => 'س', + '𞹯' => 'ع', + 'ðž¹°' => 'Ù', + 'ðž¹±' => 'ص', + 'ðž¹²' => 'Ù‚', + 'ðž¹´' => 'Ø´', + 'ðž¹µ' => 'ت', + 'ðž¹¶' => 'Ø«', + 'ðž¹·' => 'Ø®', + 'ðž¹¹' => 'ض', + '𞹺' => 'ظ', + 'ðž¹»' => 'غ', + 'ðž¹¼' => 'Ù®', + 'ðž¹¾' => 'Ú¡', + '𞺀' => 'ا', + 'ðžº' => 'ب', + '𞺂' => 'ج', + '𞺃' => 'د', + '𞺄' => 'Ù‡', + '𞺅' => 'Ùˆ', + '𞺆' => 'ز', + '𞺇' => 'Ø­', + '𞺈' => 'Ø·', + '𞺉' => 'ÙŠ', + '𞺋' => 'Ù„', + '𞺌' => 'Ù…', + 'ðžº' => 'Ù†', + '𞺎' => 'س', + 'ðžº' => 'ع', + 'ðžº' => 'Ù', + '𞺑' => 'ص', + '𞺒' => 'Ù‚', + '𞺓' => 'ر', + '𞺔' => 'Ø´', + '𞺕' => 'ت', + '𞺖' => 'Ø«', + '𞺗' => 'Ø®', + '𞺘' => 'ذ', + '𞺙' => 'ض', + '𞺚' => 'ظ', + '𞺛' => 'غ', + '𞺡' => 'ب', + '𞺢' => 'ج', + '𞺣' => 'د', + '𞺥' => 'Ùˆ', + '𞺦' => 'ز', + '𞺧' => 'Ø­', + '𞺨' => 'Ø·', + '𞺩' => 'ÙŠ', + '𞺫' => 'Ù„', + '𞺬' => 'Ù…', + '𞺭' => 'Ù†', + '𞺮' => 'س', + '𞺯' => 'ع', + '𞺰' => 'Ù', + '𞺱' => 'ص', + '𞺲' => 'Ù‚', + '𞺳' => 'ر', + '𞺴' => 'Ø´', + '𞺵' => 'ت', + '𞺶' => 'Ø«', + '𞺷' => 'Ø®', + '𞺸' => 'ذ', + '𞺹' => 'ض', + '𞺺' => 'ظ', + '𞺻' => 'غ', + '🄀' => '0.', + 'ðŸ„' => '0,', + '🄂' => '1,', + '🄃' => '2,', + '🄄' => '3,', + '🄅' => '4,', + '🄆' => '5,', + '🄇' => '6,', + '🄈' => '7,', + '🄉' => '8,', + '🄊' => '9,', + 'ðŸ„' => '(A)', + '🄑' => '(B)', + '🄒' => '(C)', + '🄓' => '(D)', + '🄔' => '(E)', + '🄕' => '(F)', + '🄖' => '(G)', + '🄗' => '(H)', + '🄘' => '(I)', + '🄙' => '(J)', + '🄚' => '(K)', + '🄛' => '(L)', + '🄜' => '(M)', + 'ðŸ„' => '(N)', + '🄞' => '(O)', + '🄟' => '(P)', + '🄠' => '(Q)', + '🄡' => '(R)', + '🄢' => '(S)', + '🄣' => '(T)', + '🄤' => '(U)', + '🄥' => '(V)', + '🄦' => '(W)', + '🄧' => '(X)', + '🄨' => '(Y)', + '🄩' => '(Z)', + '🄪' => '〔S〕', + '🄫' => 'C', + '🄬' => 'R', + '🄭' => 'CD', + '🄮' => 'WZ', + '🄰' => 'A', + '🄱' => 'B', + '🄲' => 'C', + '🄳' => 'D', + '🄴' => 'E', + '🄵' => 'F', + '🄶' => 'G', + '🄷' => 'H', + '🄸' => 'I', + '🄹' => 'J', + '🄺' => 'K', + '🄻' => 'L', + '🄼' => 'M', + '🄽' => 'N', + '🄾' => 'O', + '🄿' => 'P', + '🅀' => 'Q', + 'ðŸ…' => 'R', + '🅂' => 'S', + '🅃' => 'T', + '🅄' => 'U', + '🅅' => 'V', + '🅆' => 'W', + '🅇' => 'X', + '🅈' => 'Y', + '🅉' => 'Z', + '🅊' => 'HV', + '🅋' => 'MV', + '🅌' => 'SD', + 'ðŸ…' => 'SS', + '🅎' => 'PPV', + 'ðŸ…' => 'WC', + '🅪' => 'MC', + '🅫' => 'MD', + '🅬' => 'MR', + 'ðŸ†' => 'DJ', + '🈀' => 'ã»ã‹', + 'ðŸˆ' => 'ココ', + '🈂' => 'サ', + 'ðŸˆ' => '手', + '🈑' => 'å­—', + '🈒' => 'åŒ', + '🈓' => 'デ', + '🈔' => '二', + '🈕' => '多', + '🈖' => 'è§£', + '🈗' => '天', + '🈘' => '交', + '🈙' => '映', + '🈚' => 'ç„¡', + '🈛' => 'æ–™', + '🈜' => 'å‰', + 'ðŸˆ' => '後', + '🈞' => 'å†', + '🈟' => 'æ–°', + '🈠' => 'åˆ', + '🈡' => '終', + '🈢' => '生', + '🈣' => '販', + '🈤' => '声', + '🈥' => 'å¹', + '🈦' => 'æ¼”', + '🈧' => '投', + '🈨' => 'æ•', + '🈩' => '一', + '🈪' => '三', + '🈫' => 'éŠ', + '🈬' => 'å·¦', + '🈭' => '中', + '🈮' => 'å³', + '🈯' => '指', + '🈰' => 'èµ°', + '🈱' => '打', + '🈲' => 'ç¦', + '🈳' => '空', + '🈴' => 'åˆ', + '🈵' => '満', + '🈶' => '有', + '🈷' => '月', + '🈸' => '申', + '🈹' => '割', + '🈺' => 'å–¶', + '🈻' => 'é…', + '🉀' => '〔本〕', + 'ðŸ‰' => '〔三〕', + '🉂' => '〔二〕', + '🉃' => '〔安〕', + '🉄' => '〔点〕', + '🉅' => '〔打〕', + '🉆' => '〔盗〕', + '🉇' => '〔å‹ã€•', + '🉈' => '〔敗〕', + 'ðŸ‰' => 'å¾—', + '🉑' => 'å¯', + '🯰' => '0', + '🯱' => '1', + '🯲' => '2', + '🯳' => '3', + '🯴' => '4', + '🯵' => '5', + '🯶' => '6', + '🯷' => '7', + '🯸' => '8', + '🯹' => '9', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php new file mode 100644 index 0000000..3608e5c --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php new file mode 100644 index 0000000..e36d1a9 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/composer.json b/vendor/symfony/polyfill-intl-normalizer/composer.json new file mode 100644 index 0000000..9bd04e8 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-intl-normalizer", + "type": "library", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "normalizer"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..6e3afce --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..3d45c9d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,1045 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * - mb_ucfirst - Make a string's first character uppercase + * - mb_lcfirst - Make a string's first character lowercase + * - mb_trim - Strip whitespace (or other characters) from the beginning and end of a string + * - mb_ltrim - Strip whitespace (or other characters) from the beginning of a string + * - mb_rtrim - Strip whitespace (or other characters) from the end of a string + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const SIMPLE_CASE_FOLD = [ + ['µ', 'Å¿', "\xCD\x85", 'Ï‚', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'Ï€', 'κ', 'Ï', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($s)) { + $r = []; + foreach ($s as $str) { + $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding); + } + + return $r; + } + + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = strtr($s, $caseFolding); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return false; + } + } + + return true; + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ + self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), + self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), + ]); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + + $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string + { + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given'); + } + + if (self::mb_strlen($pad_string, $encoding) <= 0) { + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + $paddingRequired = $length - self::mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + public static function mb_ucfirst(string $string, ?string $encoding = null): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given'); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + public static function mb_lcfirst(string $string, ?string $encoding = null): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given'); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } + + public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string + { + return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string + { + return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); + } + + public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string + { + return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__); + } + + private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given'); + } + + if ('' === $characters) { + return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding); + } + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $string)) { + $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string); + } + if (null !== $characters && !preg_match('//u', $characters)) { + $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters); + } + } else { + $string = iconv($encoding, 'UTF-8//IGNORE', $string); + + if (null !== $characters) { + $characters = iconv($encoding, 'UTF-8//IGNORE', $characters); + } + } + + if (null === $characters) { + $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; + } else { + $characters = preg_quote($characters); + } + + $string = preg_replace(sprintf($regex, $characters), '', $string); + + if (null === $encoding) { + return $string; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $string); + } + + private static function assertEncoding(string $encoding, string $errorFormat): void + { + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(sprintf($errorFormat, $encoding)); + } + + // BC for PHP 7.3 and lower + if (!$validEncoding) { + throw new \ValueError(sprintf($errorFormat, $encoding)); + } + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..478b40d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 0000000..512bba0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,119 @@ + 'i̇', + 'µ' => 'μ', + 'Å¿' => 's', + 'Í…' => 'ι', + 'Ï‚' => 'σ', + 'Ï' => 'β', + 'Ï‘' => 'θ', + 'Ï•' => 'φ', + 'Ï–' => 'Ï€', + 'ϰ' => 'κ', + 'ϱ' => 'Ï', + 'ϵ' => 'ε', + 'ẛ' => 'ṡ', + 'á¾¾' => 'ι', + 'ß' => 'ss', + 'ʼn' => 'ʼn', + 'ǰ' => 'ǰ', + 'Î' => 'Î', + 'ΰ' => 'ΰ', + 'Ö‡' => 'Õ¥Ö‚', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', + 'ẞ' => 'ss', + 'á½' => 'á½', + 'á½’' => 'á½’', + 'á½”' => 'á½”', + 'á½–' => 'á½–', + 'á¾€' => 'ἀι', + 'á¾' => 'á¼Î¹', + 'ᾂ' => 'ἂι', + 'ᾃ' => 'ἃι', + 'ᾄ' => 'ἄι', + 'á¾…' => 'ἅι', + 'ᾆ' => 'ἆι', + 'ᾇ' => 'ἇι', + 'ᾈ' => 'ἀι', + 'ᾉ' => 'á¼Î¹', + 'ᾊ' => 'ἂι', + 'ᾋ' => 'ἃι', + 'ᾌ' => 'ἄι', + 'á¾' => 'ἅι', + 'ᾎ' => 'ἆι', + 'á¾' => 'ἇι', + 'á¾' => 'ἠι', + 'ᾑ' => 'ἡι', + 'á¾’' => 'ἢι', + 'ᾓ' => 'ἣι', + 'á¾”' => 'ἤι', + 'ᾕ' => 'ἥι', + 'á¾–' => 'ἦι', + 'á¾—' => 'ἧι', + 'ᾘ' => 'ἠι', + 'á¾™' => 'ἡι', + 'ᾚ' => 'ἢι', + 'á¾›' => 'ἣι', + 'ᾜ' => 'ἤι', + 'á¾' => 'ἥι', + 'ᾞ' => 'ἦι', + 'ᾟ' => 'ἧι', + 'á¾ ' => 'ὠι', + 'ᾡ' => 'ὡι', + 'á¾¢' => 'ὢι', + 'á¾£' => 'ὣι', + 'ᾤ' => 'ὤι', + 'á¾¥' => 'ὥι', + 'ᾦ' => 'ὦι', + 'á¾§' => 'ὧι', + 'ᾨ' => 'ὠι', + 'ᾩ' => 'ὡι', + 'ᾪ' => 'ὢι', + 'ᾫ' => 'ὣι', + 'ᾬ' => 'ὤι', + 'á¾­' => 'ὥι', + 'á¾®' => 'ὦι', + 'ᾯ' => 'ὧι', + 'á¾²' => 'ὰι', + 'á¾³' => 'αι', + 'á¾´' => 'άι', + 'á¾¶' => 'á¾¶', + 'á¾·' => 'ᾶι', + 'á¾¼' => 'αι', + 'á¿‚' => 'ὴι', + 'ῃ' => 'ηι', + 'á¿„' => 'ήι', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῆι', + 'ῌ' => 'ηι', + 'á¿’' => 'á¿’', + 'á¿–' => 'á¿–', + 'á¿—' => 'á¿—', + 'á¿¢' => 'á¿¢', + 'ῤ' => 'ῤ', + 'ῦ' => 'ῦ', + 'á¿§' => 'á¿§', + 'ῲ' => 'ὼι', + 'ῳ' => 'ωι', + 'á¿´' => 'ώι', + 'á¿¶' => 'á¿¶', + 'á¿·' => 'ῶι', + 'ῼ' => 'ωι', + 'ff' => 'ff', + 'ï¬' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'Õ´Õ¶', + 'ﬔ' => 'Õ´Õ¥', + 'ﬕ' => 'Õ´Õ«', + 'ﬖ' => 'Õ¾Õ¶', + 'ﬗ' => 'Õ´Õ­', +]; diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..fac60b0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Ã' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Ã…' => 'Ã¥', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'ÃŒ' => 'ì', + 'Ã' => 'í', + 'ÃŽ' => 'î', + 'Ã' => 'ï', + 'Ã' => 'ð', + 'Ñ' => 'ñ', + 'Ã’' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ã' => 'ý', + 'Þ' => 'þ', + 'Ä€' => 'Ä', + 'Ä‚' => 'ă', + 'Ä„' => 'Ä…', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'ÄŠ' => 'Ä‹', + 'ÄŒ' => 'Ä', + 'ÄŽ' => 'Ä', + 'Ä' => 'Ä‘', + 'Ä’' => 'Ä“', + 'Ä”' => 'Ä•', + 'Ä–' => 'Ä—', + 'Ę' => 'Ä™', + 'Äš' => 'Ä›', + 'Äœ' => 'Ä', + 'Äž' => 'ÄŸ', + 'Ä ' => 'Ä¡', + 'Ä¢' => 'Ä£', + 'Ĥ' => 'Ä¥', + 'Ħ' => 'ħ', + 'Ĩ' => 'Ä©', + 'Ī' => 'Ä«', + 'Ĭ' => 'Ä­', + 'Ä®' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ä´' => 'ĵ', + 'Ķ' => 'Ä·', + 'Ĺ' => 'ĺ', + 'Ä»' => 'ļ', + 'Ľ' => 'ľ', + 'Ä¿' => 'Å€', + 'Å' => 'Å‚', + 'Ń' => 'Å„', + 'Å…' => 'ņ', + 'Ň' => 'ň', + 'ÅŠ' => 'Å‹', + 'ÅŒ' => 'Å', + 'ÅŽ' => 'Å', + 'Å' => 'Å‘', + 'Å’' => 'Å“', + 'Å”' => 'Å•', + 'Å–' => 'Å—', + 'Ř' => 'Å™', + 'Åš' => 'Å›', + 'Åœ' => 'Å', + 'Åž' => 'ÅŸ', + 'Å ' => 'Å¡', + 'Å¢' => 'Å£', + 'Ť' => 'Å¥', + 'Ŧ' => 'ŧ', + 'Ũ' => 'Å©', + 'Ū' => 'Å«', + 'Ŭ' => 'Å­', + 'Å®' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Å´' => 'ŵ', + 'Ŷ' => 'Å·', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Å»' => 'ż', + 'Ž' => 'ž', + 'Æ' => 'É“', + 'Æ‚' => 'ƃ', + 'Æ„' => 'Æ…', + 'Ɔ' => 'É”', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'É–', + 'ÆŠ' => 'É—', + 'Æ‹' => 'ÆŒ', + 'ÆŽ' => 'Ç', + 'Æ' => 'É™', + 'Æ' => 'É›', + 'Æ‘' => 'Æ’', + 'Æ“' => 'É ', + 'Æ”' => 'É£', + 'Æ–' => 'É©', + 'Æ—' => 'ɨ', + 'Ƙ' => 'Æ™', + 'Æœ' => 'ɯ', + 'Æ' => 'ɲ', + 'ÆŸ' => 'ɵ', + 'Æ ' => 'Æ¡', + 'Æ¢' => 'Æ£', + 'Ƥ' => 'Æ¥', + 'Ʀ' => 'Ê€', + 'Ƨ' => 'ƨ', + 'Æ©' => 'ʃ', + 'Ƭ' => 'Æ­', + 'Æ®' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ÊŠ', + 'Ʋ' => 'Ê‹', + 'Ƴ' => 'Æ´', + 'Ƶ' => 'ƶ', + 'Æ·' => 'Ê’', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'Ç„' => 'dž', + 'Ç…' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'ÇŠ' => 'ÇŒ', + 'Ç‹' => 'ÇŒ', + 'Ç' => 'ÇŽ', + 'Ç' => 'Ç', + 'Ç‘' => 'Ç’', + 'Ç“' => 'Ç”', + 'Ç•' => 'Ç–', + 'Ç—' => 'ǘ', + 'Ç™' => 'Çš', + 'Ç›' => 'Çœ', + 'Çž' => 'ÇŸ', + 'Ç ' => 'Ç¡', + 'Ç¢' => 'Ç£', + 'Ǥ' => 'Ç¥', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'Ç©', + 'Ǫ' => 'Ç«', + 'Ǭ' => 'Ç­', + 'Ç®' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ç´' => 'ǵ', + 'Ƕ' => 'Æ•', + 'Ç·' => 'Æ¿', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'Ç»', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'Ç¿', + 'È€' => 'È', + 'È‚' => 'ȃ', + 'È„' => 'È…', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'ÈŠ' => 'È‹', + 'ÈŒ' => 'È', + 'ÈŽ' => 'È', + 'È' => 'È‘', + 'È’' => 'È“', + 'È”' => 'È•', + 'È–' => 'È—', + 'Ș' => 'È™', + 'Èš' => 'È›', + 'Èœ' => 'È', + 'Èž' => 'ÈŸ', + 'È ' => 'Æž', + 'È¢' => 'È£', + 'Ȥ' => 'È¥', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'È©', + 'Ȫ' => 'È«', + 'Ȭ' => 'È­', + 'È®' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'â±¥', + 'È»' => 'ȼ', + 'Ƚ' => 'Æš', + 'Ⱦ' => 'ⱦ', + 'É' => 'É‚', + 'Ƀ' => 'Æ€', + 'É„' => 'ʉ', + 'É…' => 'ÊŒ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'ÉŠ' => 'É‹', + 'ÉŒ' => 'É', + 'ÉŽ' => 'É', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'Í·', + 'Í¿' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'ÎŒ' => 'ÏŒ', + 'ÎŽ' => 'Ï', + 'Î' => 'ÏŽ', + 'Α' => 'α', + 'Î’' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Î' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'Ï€', + 'Ρ' => 'Ï', + 'Σ' => 'σ', + 'Τ' => 'Ï„', + 'Î¥' => 'Ï…', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ÏŠ', + 'Ϋ' => 'Ï‹', + 'Ï' => 'Ï—', + 'Ϙ' => 'Ï™', + 'Ïš' => 'Ï›', + 'Ïœ' => 'Ï', + 'Ïž' => 'ÏŸ', + 'Ï ' => 'Ï¡', + 'Ï¢' => 'Ï£', + 'Ϥ' => 'Ï¥', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'Ï©', + 'Ϫ' => 'Ï«', + 'Ϭ' => 'Ï­', + 'Ï®' => 'ϯ', + 'Ï´' => 'θ', + 'Ï·' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'Ï»', + 'Ͻ' => 'Í»', + 'Ͼ' => 'ͼ', + 'Ï¿' => 'ͽ', + 'Ѐ' => 'Ñ', + 'Ð' => 'Ñ‘', + 'Ђ' => 'Ñ’', + 'Ѓ' => 'Ñ“', + 'Є' => 'Ñ”', + 'Ð…' => 'Ñ•', + 'І' => 'Ñ–', + 'Ї' => 'Ñ—', + 'Ј' => 'ј', + 'Љ' => 'Ñ™', + 'Њ' => 'Ñš', + 'Ћ' => 'Ñ›', + 'ÐŒ' => 'Ñœ', + 'Ð' => 'Ñ', + 'ÐŽ' => 'Ñž', + 'Ð' => 'ÑŸ', + 'Ð' => 'а', + 'Б' => 'б', + 'Ð’' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Ð' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'Ñ€', + 'С' => 'Ñ', + 'Т' => 'Ñ‚', + 'У' => 'у', + 'Ф' => 'Ñ„', + 'Ð¥' => 'Ñ…', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ÑŠ', + 'Ы' => 'Ñ‹', + 'Ь' => 'ÑŒ', + 'Э' => 'Ñ', + 'Ю' => 'ÑŽ', + 'Я' => 'Ñ', + 'Ñ ' => 'Ñ¡', + 'Ñ¢' => 'Ñ£', + 'Ѥ' => 'Ñ¥', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'Ñ©', + 'Ѫ' => 'Ñ«', + 'Ѭ' => 'Ñ­', + 'Ñ®' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ñ´' => 'ѵ', + 'Ѷ' => 'Ñ·', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'Ñ»', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'Ñ¿', + 'Ò€' => 'Ò', + 'ÒŠ' => 'Ò‹', + 'ÒŒ' => 'Ò', + 'ÒŽ' => 'Ò', + 'Ò' => 'Ò‘', + 'Ò’' => 'Ò“', + 'Ò”' => 'Ò•', + 'Ò–' => 'Ò—', + 'Ò˜' => 'Ò™', + 'Òš' => 'Ò›', + 'Òœ' => 'Ò', + 'Òž' => 'ÒŸ', + 'Ò ' => 'Ò¡', + 'Ò¢' => 'Ò£', + 'Ò¤' => 'Ò¥', + 'Ò¦' => 'Ò§', + 'Ò¨' => 'Ò©', + 'Òª' => 'Ò«', + 'Ò¬' => 'Ò­', + 'Ò®' => 'Ò¯', + 'Ò°' => 'Ò±', + 'Ò²' => 'Ò³', + 'Ò´' => 'Òµ', + 'Ò¶' => 'Ò·', + 'Ò¸' => 'Ò¹', + 'Òº' => 'Ò»', + 'Ò¼' => 'Ò½', + 'Ò¾' => 'Ò¿', + 'Ó€' => 'Ó', + 'Ó' => 'Ó‚', + 'Óƒ' => 'Ó„', + 'Ó…' => 'Ó†', + 'Ó‡' => 'Óˆ', + 'Ó‰' => 'ÓŠ', + 'Ó‹' => 'ÓŒ', + 'Ó' => 'ÓŽ', + 'Ó' => 'Ó‘', + 'Ó’' => 'Ó“', + 'Ó”' => 'Ó•', + 'Ó–' => 'Ó—', + 'Ó˜' => 'Ó™', + 'Óš' => 'Ó›', + 'Óœ' => 'Ó', + 'Óž' => 'ÓŸ', + 'Ó ' => 'Ó¡', + 'Ó¢' => 'Ó£', + 'Ó¤' => 'Ó¥', + 'Ó¦' => 'Ó§', + 'Ó¨' => 'Ó©', + 'Óª' => 'Ó«', + 'Ó¬' => 'Ó­', + 'Ó®' => 'Ó¯', + 'Ó°' => 'Ó±', + 'Ó²' => 'Ó³', + 'Ó´' => 'Óµ', + 'Ó¶' => 'Ó·', + 'Ó¸' => 'Ó¹', + 'Óº' => 'Ó»', + 'Ó¼' => 'Ó½', + 'Ó¾' => 'Ó¿', + 'Ô€' => 'Ô', + 'Ô‚' => 'Ôƒ', + 'Ô„' => 'Ô…', + 'Ô†' => 'Ô‡', + 'Ôˆ' => 'Ô‰', + 'ÔŠ' => 'Ô‹', + 'ÔŒ' => 'Ô', + 'ÔŽ' => 'Ô', + 'Ô' => 'Ô‘', + 'Ô’' => 'Ô“', + 'Ô”' => 'Ô•', + 'Ô–' => 'Ô—', + 'Ô˜' => 'Ô™', + 'Ôš' => 'Ô›', + 'Ôœ' => 'Ô', + 'Ôž' => 'ÔŸ', + 'Ô ' => 'Ô¡', + 'Ô¢' => 'Ô£', + 'Ô¤' => 'Ô¥', + 'Ô¦' => 'Ô§', + 'Ô¨' => 'Ô©', + 'Ôª' => 'Ô«', + 'Ô¬' => 'Ô­', + 'Ô®' => 'Ô¯', + 'Ô±' => 'Õ¡', + 'Ô²' => 'Õ¢', + 'Ô³' => 'Õ£', + 'Ô´' => 'Õ¤', + 'Ôµ' => 'Õ¥', + 'Ô¶' => 'Õ¦', + 'Ô·' => 'Õ§', + 'Ô¸' => 'Õ¨', + 'Ô¹' => 'Õ©', + 'Ôº' => 'Õª', + 'Ô»' => 'Õ«', + 'Ô¼' => 'Õ¬', + 'Ô½' => 'Õ­', + 'Ô¾' => 'Õ®', + 'Ô¿' => 'Õ¯', + 'Õ€' => 'Õ°', + 'Õ' => 'Õ±', + 'Õ‚' => 'Õ²', + 'Õƒ' => 'Õ³', + 'Õ„' => 'Õ´', + 'Õ…' => 'Õµ', + 'Õ†' => 'Õ¶', + 'Õ‡' => 'Õ·', + 'Õˆ' => 'Õ¸', + 'Õ‰' => 'Õ¹', + 'ÕŠ' => 'Õº', + 'Õ‹' => 'Õ»', + 'ÕŒ' => 'Õ¼', + 'Õ' => 'Õ½', + 'ÕŽ' => 'Õ¾', + 'Õ' => 'Õ¿', + 'Õ' => 'Ö€', + 'Õ‘' => 'Ö', + 'Õ’' => 'Ö‚', + 'Õ“' => 'Öƒ', + 'Õ”' => 'Ö„', + 'Õ•' => 'Ö…', + 'Õ–' => 'Ö†', + 'á‚ ' => 'â´€', + 'á‚¡' => 'â´', + 'á‚¢' => 'â´‚', + 'á‚£' => 'â´ƒ', + 'Ⴄ' => 'â´„', + 'á‚¥' => 'â´…', + 'Ⴆ' => 'â´†', + 'á‚§' => 'â´‡', + 'Ⴈ' => 'â´ˆ', + 'á‚©' => 'â´‰', + 'Ⴊ' => 'â´Š', + 'á‚«' => 'â´‹', + 'Ⴌ' => 'â´Œ', + 'á‚­' => 'â´', + 'á‚®' => 'â´Ž', + 'Ⴏ' => 'â´', + 'á‚°' => 'â´', + 'Ⴑ' => 'â´‘', + 'Ⴒ' => 'â´’', + 'Ⴓ' => 'â´“', + 'á‚´' => 'â´”', + 'Ⴕ' => 'â´•', + 'á‚¶' => 'â´–', + 'á‚·' => 'â´—', + 'Ⴘ' => 'â´˜', + 'Ⴙ' => 'â´™', + 'Ⴚ' => 'â´š', + 'á‚»' => 'â´›', + 'Ⴜ' => 'â´œ', + 'Ⴝ' => 'â´', + 'Ⴞ' => 'â´ž', + 'á‚¿' => 'â´Ÿ', + 'Ⴠ' => 'â´ ', + 'áƒ' => 'â´¡', + 'Ⴢ' => 'â´¢', + 'Ⴣ' => 'â´£', + 'Ⴤ' => 'â´¤', + 'Ⴥ' => 'â´¥', + 'Ⴧ' => 'â´§', + 'áƒ' => 'â´­', + 'Ꭰ' => 'ê­°', + 'Ꭱ' => 'ê­±', + 'Ꭲ' => 'ê­²', + 'Ꭳ' => 'ê­³', + 'Ꭴ' => 'ê­´', + 'Ꭵ' => 'ê­µ', + 'Ꭶ' => 'ê­¶', + 'Ꭷ' => 'ê­·', + 'Ꭸ' => 'ê­¸', + 'Ꭹ' => 'ê­¹', + 'Ꭺ' => 'ê­º', + 'Ꭻ' => 'ê­»', + 'Ꭼ' => 'ê­¼', + 'Ꭽ' => 'ê­½', + 'Ꭾ' => 'ê­¾', + 'Ꭿ' => 'ê­¿', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ê®', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ê®…', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ê®', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ê®', + 'á€' => 'ê®', + 'á' => 'ꮑ', + 'á‚' => 'ê®’', + 'áƒ' => 'ꮓ', + 'á„' => 'ê®”', + 'á…' => 'ꮕ', + 'á†' => 'ê®–', + 'á‡' => 'ê®—', + 'áˆ' => 'ꮘ', + 'á‰' => 'ê®™', + 'áŠ' => 'ꮚ', + 'á‹' => 'ê®›', + 'áŒ' => 'ꮜ', + 'á' => 'ê®', + 'áŽ' => 'ꮞ', + 'á' => 'ꮟ', + 'á' => 'ê® ', + 'á‘' => 'ꮡ', + 'á’' => 'ꮢ', + 'á“' => 'ꮣ', + 'á”' => 'ꮤ', + 'á•' => 'ꮥ', + 'á–' => 'ꮦ', + 'á—' => 'ê®§', + 'á˜' => 'ꮨ', + 'á™' => 'ꮩ', + 'áš' => 'ꮪ', + 'á›' => 'ꮫ', + 'áœ' => 'ꮬ', + 'á' => 'ê®­', + 'áž' => 'ê®®', + 'áŸ' => 'ꮯ', + 'á ' => 'ê®°', + 'á¡' => 'ê®±', + 'á¢' => 'ꮲ', + 'á£' => 'ꮳ', + 'á¤' => 'ê®´', + 'á¥' => 'ꮵ', + 'á¦' => 'ê®¶', + 'á§' => 'ê®·', + 'á¨' => 'ꮸ', + 'á©' => 'ꮹ', + 'áª' => 'ꮺ', + 'á«' => 'ê®»', + 'á¬' => 'ꮼ', + 'á­' => 'ꮽ', + 'á®' => 'ꮾ', + 'á¯' => 'ꮿ', + 'á°' => 'á¸', + 'á±' => 'á¹', + 'á²' => 'áº', + 'á³' => 'á»', + 'á´' => 'á¼', + 'áµ' => 'á½', + 'á²' => 'áƒ', + 'Ბ' => 'ბ', + 'á²’' => 'გ', + 'Დ' => 'დ', + 'á²”' => 'ე', + 'Ვ' => 'ვ', + 'á²–' => 'ზ', + 'á²—' => 'თ', + 'Ი' => 'ი', + 'á²™' => 'კ', + 'Ლ' => 'ლ', + 'á²›' => 'მ', + 'Ნ' => 'ნ', + 'á²' => 'áƒ', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'á² ' => 'რ', + 'Ს' => 'ს', + 'á²¢' => 'ტ', + 'á²£' => 'უ', + 'Ფ' => 'ფ', + 'á²¥' => 'ქ', + 'Ღ' => 'ღ', + 'á²§' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'á²­' => 'ჭ', + 'á²®' => 'ხ', + 'Ჯ' => 'ჯ', + 'á²°' => 'ჰ', + 'á²±' => 'ჱ', + 'á²²' => 'ჲ', + 'á²³' => 'ჳ', + 'á²´' => 'ჴ', + 'á²µ' => 'ჵ', + 'á²¶' => 'ჶ', + 'á²·' => 'ჷ', + 'Ჸ' => 'ჸ', + 'á²¹' => 'ჹ', + 'Ჺ' => 'ჺ', + 'á²½' => 'ჽ', + 'á²¾' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'á¸', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'á¸', + 'Ḏ' => 'á¸', + 'á¸' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'á¸', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'á¹€' => 'á¹', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'á¹…', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'á¹', + 'Ṏ' => 'á¹', + 'á¹' => 'ṑ', + 'á¹’' => 'ṓ', + 'á¹”' => 'ṕ', + 'á¹–' => 'á¹—', + 'Ṙ' => 'á¹™', + 'Ṛ' => 'á¹›', + 'Ṝ' => 'á¹', + 'Ṟ' => 'ṟ', + 'á¹ ' => 'ṡ', + 'á¹¢' => 'á¹£', + 'Ṥ' => 'á¹¥', + 'Ṧ' => 'á¹§', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'á¹­', + 'á¹®' => 'ṯ', + 'á¹°' => 'á¹±', + 'á¹²' => 'á¹³', + 'á¹´' => 'á¹µ', + 'á¹¶' => 'á¹·', + 'Ṹ' => 'á¹¹', + 'Ṻ' => 'á¹»', + 'á¹¼' => 'á¹½', + 'á¹¾' => 'ṿ', + 'Ẁ' => 'áº', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'áº', + 'Ẏ' => 'áº', + 'áº' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'á»', + 'Ể' => 'ể', + 'Ễ' => 'á»…', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'á»', + 'Ỏ' => 'á»', + 'á»' => 'ố', + 'á»’' => 'ồ', + 'á»”' => 'ổ', + 'á»–' => 'á»—', + 'Ộ' => 'á»™', + 'Ớ' => 'á»›', + 'Ờ' => 'á»', + 'Ở' => 'ở', + 'á» ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'á»§', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'á»­', + 'á»®' => 'ữ', + 'á»°' => 'á»±', + 'Ỳ' => 'ỳ', + 'á»´' => 'ỵ', + 'á»¶' => 'á»·', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'á»»', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'á¼€', + 'Ἁ' => 'á¼', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'á¼' => 'á¼…', + 'Ἆ' => 'ἆ', + 'á¼' => 'ἇ', + 'Ἐ' => 'á¼', + 'á¼™' => 'ἑ', + 'Ἒ' => 'á¼’', + 'á¼›' => 'ἓ', + 'Ἔ' => 'á¼”', + 'á¼' => 'ἕ', + 'Ἠ' => 'á¼ ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'á¼¢', + 'Ἣ' => 'á¼£', + 'Ἤ' => 'ἤ', + 'á¼­' => 'á¼¥', + 'á¼®' => 'ἦ', + 'Ἧ' => 'á¼§', + 'Ἰ' => 'á¼°', + 'á¼¹' => 'á¼±', + 'Ἲ' => 'á¼²', + 'á¼»' => 'á¼³', + 'á¼¼' => 'á¼´', + 'á¼½' => 'á¼µ', + 'á¼¾' => 'á¼¶', + 'Ἷ' => 'á¼·', + 'Ὀ' => 'á½€', + 'Ὁ' => 'á½', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'á½' => 'á½…', + 'á½™' => 'ὑ', + 'á½›' => 'ὓ', + 'á½' => 'ὕ', + 'Ὗ' => 'á½—', + 'Ὠ' => 'á½ ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'á½¢', + 'Ὣ' => 'á½£', + 'Ὤ' => 'ὤ', + 'á½­' => 'á½¥', + 'á½®' => 'ὦ', + 'Ὧ' => 'á½§', + 'ᾈ' => 'á¾€', + 'ᾉ' => 'á¾', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'á¾' => 'á¾…', + 'ᾎ' => 'ᾆ', + 'á¾' => 'ᾇ', + 'ᾘ' => 'á¾', + 'á¾™' => 'ᾑ', + 'ᾚ' => 'á¾’', + 'á¾›' => 'ᾓ', + 'ᾜ' => 'á¾”', + 'á¾' => 'ᾕ', + 'ᾞ' => 'á¾–', + 'ᾟ' => 'á¾—', + 'ᾨ' => 'á¾ ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'á¾¢', + 'ᾫ' => 'á¾£', + 'ᾬ' => 'ᾤ', + 'á¾­' => 'á¾¥', + 'á¾®' => 'ᾦ', + 'ᾯ' => 'á¾§', + 'Ᾰ' => 'á¾°', + 'á¾¹' => 'á¾±', + 'Ὰ' => 'á½°', + 'á¾»' => 'á½±', + 'á¾¼' => 'á¾³', + 'Ὲ' => 'á½²', + 'Έ' => 'á½³', + 'Ὴ' => 'á½´', + 'á¿‹' => 'á½µ', + 'ῌ' => 'ῃ', + 'Ῐ' => 'á¿', + 'á¿™' => 'á¿‘', + 'Ὶ' => 'á½¶', + 'á¿›' => 'á½·', + 'Ῠ' => 'á¿ ', + 'á¿©' => 'á¿¡', + 'Ὺ' => 'ὺ', + 'á¿«' => 'á½»', + 'Ῥ' => 'á¿¥', + 'Ὸ' => 'ὸ', + 'Ό' => 'á½¹', + 'Ὼ' => 'á½¼', + 'á¿»' => 'á½½', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'â„«' => 'Ã¥', + 'Ⅎ' => 'â…Ž', + 'â… ' => 'â…°', + 'â…¡' => 'â…±', + 'â…¢' => 'â…²', + 'â…£' => 'â…³', + 'â…¤' => 'â…´', + 'â…¥' => 'â…µ', + 'â…¦' => 'â…¶', + 'â…§' => 'â…·', + 'â…¨' => 'â…¸', + 'â…©' => 'â…¹', + 'â…ª' => 'â…º', + 'â…«' => 'â…»', + 'â…¬' => 'â…¼', + 'â…­' => 'â…½', + 'â…®' => 'â…¾', + 'â…¯' => 'â…¿', + 'Ↄ' => 'ↄ', + 'â’¶' => 'â“', + 'â’·' => 'â“‘', + 'â’¸' => 'â“’', + 'â’¹' => 'â““', + 'â’º' => 'â“”', + 'â’»' => 'â“•', + 'â’¼' => 'â“–', + 'â’½' => 'â“—', + 'â’¾' => 'ⓘ', + 'â’¿' => 'â“™', + 'â“€' => 'ⓚ', + 'â“' => 'â“›', + 'â“‚' => 'ⓜ', + 'Ⓝ' => 'â“', + 'â“„' => 'ⓞ', + 'â“…' => 'ⓟ', + 'Ⓠ' => 'â“ ', + 'Ⓡ' => 'â“¡', + 'Ⓢ' => 'â“¢', + 'Ⓣ' => 'â“£', + 'Ⓤ' => 'ⓤ', + 'â“‹' => 'â“¥', + 'Ⓦ' => 'ⓦ', + 'â“' => 'â“§', + 'Ⓨ' => 'ⓨ', + 'â“' => 'â“©', + 'â°€' => 'â°°', + 'â°' => 'â°±', + 'â°‚' => 'â°²', + 'â°ƒ' => 'â°³', + 'â°„' => 'â°´', + 'â°…' => 'â°µ', + 'â°†' => 'â°¶', + 'â°‡' => 'â°·', + 'â°ˆ' => 'â°¸', + 'â°‰' => 'â°¹', + 'â°Š' => 'â°º', + 'â°‹' => 'â°»', + 'â°Œ' => 'â°¼', + 'â°' => 'â°½', + 'â°Ž' => 'â°¾', + 'â°' => 'â°¿', + 'â°' => 'â±€', + 'â°‘' => 'â±', + 'â°’' => 'ⱂ', + 'â°“' => 'ⱃ', + 'â°”' => 'ⱄ', + 'â°•' => 'â±…', + 'â°–' => 'ⱆ', + 'â°—' => 'ⱇ', + 'â°˜' => 'ⱈ', + 'â°™' => 'ⱉ', + 'â°š' => 'ⱊ', + 'â°›' => 'ⱋ', + 'â°œ' => 'ⱌ', + 'â°' => 'â±', + 'â°ž' => 'ⱎ', + 'â°Ÿ' => 'â±', + 'â° ' => 'â±', + 'â°¡' => 'ⱑ', + 'â°¢' => 'â±’', + 'â°£' => 'ⱓ', + 'â°¤' => 'â±”', + 'â°¥' => 'ⱕ', + 'â°¦' => 'â±–', + 'â°§' => 'â±—', + 'â°¨' => 'ⱘ', + 'â°©' => 'â±™', + 'â°ª' => 'ⱚ', + 'â°«' => 'â±›', + 'â°¬' => 'ⱜ', + 'â°­' => 'â±', + 'â°®' => 'ⱞ', + 'â± ' => 'ⱡ', + 'â±¢' => 'É«', + 'â±£' => 'áµ½', + 'Ɽ' => 'ɽ', + 'â±§' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'â±­' => 'É‘', + 'â±®' => 'ɱ', + 'Ɐ' => 'É', + 'â±°' => 'É’', + 'â±²' => 'â±³', + 'â±µ' => 'â±¶', + 'â±¾' => 'È¿', + 'Ɀ' => 'É€', + 'â²€' => 'â²', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'â²…', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'â²', + 'Ⲏ' => 'â²', + 'â²' => 'ⲑ', + 'â²’' => 'ⲓ', + 'â²”' => 'ⲕ', + 'â²–' => 'â²—', + 'Ⲙ' => 'â²™', + 'Ⲛ' => 'â²›', + 'Ⲝ' => 'â²', + 'Ⲟ' => 'ⲟ', + 'â² ' => 'ⲡ', + 'â²¢' => 'â²£', + 'Ⲥ' => 'â²¥', + 'Ⲧ' => 'â²§', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'â²­', + 'â²®' => 'ⲯ', + 'â²°' => 'â²±', + 'â²²' => 'â²³', + 'â²´' => 'â²µ', + 'â²¶' => 'â²·', + 'Ⲹ' => 'â²¹', + 'Ⲻ' => 'â²»', + 'â²¼' => 'â²½', + 'â²¾' => 'ⲿ', + 'â³€' => 'â³', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'â³…', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'â³', + 'Ⳏ' => 'â³', + 'â³' => 'ⳑ', + 'â³’' => 'ⳓ', + 'â³”' => 'ⳕ', + 'â³–' => 'â³—', + 'Ⳙ' => 'â³™', + 'Ⳛ' => 'â³›', + 'Ⳝ' => 'â³', + 'Ⳟ' => 'ⳟ', + 'â³ ' => 'ⳡ', + 'â³¢' => 'â³£', + 'Ⳬ' => 'ⳬ', + 'â³­' => 'â³®', + 'â³²' => 'â³³', + 'Ꙁ' => 'ê™', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ê™…', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ê™', + 'Ꙏ' => 'ê™', + 'ê™' => 'ꙑ', + 'ê™’' => 'ꙓ', + 'ê™”' => 'ꙕ', + 'ê™–' => 'ê™—', + 'Ꙙ' => 'ê™™', + 'Ꙛ' => 'ê™›', + 'Ꙝ' => 'ê™', + 'Ꙟ' => 'ꙟ', + 'ê™ ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ê™§', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ê™­', + 'Ꚁ' => 'êš', + 'êš‚' => 'ꚃ', + 'êš„' => 'êš…', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'êš‹', + 'Ꚍ' => 'êš', + 'Ꚏ' => 'êš', + 'êš' => 'êš‘', + 'êš’' => 'êš“', + 'êš”' => 'êš•', + 'êš–' => 'êš—', + 'Ꚙ' => 'êš™', + 'êšš' => 'êš›', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'ê€' => 'ê', + 'ê‚' => 'êƒ', + 'ê„' => 'ê…', + 'ê†' => 'ê‡', + 'êˆ' => 'ê‰', + 'êŠ' => 'ê‹', + 'êŒ' => 'ê', + 'êŽ' => 'ê', + 'ê' => 'ê‘', + 'ê’' => 'ê“', + 'ê”' => 'ê•', + 'ê–' => 'ê—', + 'ê˜' => 'ê™', + 'êš' => 'ê›', + 'êœ' => 'ê', + 'êž' => 'êŸ', + 'ê ' => 'ê¡', + 'ê¢' => 'ê£', + 'ê¤' => 'ê¥', + 'ê¦' => 'ê§', + 'ê¨' => 'ê©', + 'êª' => 'ê«', + 'ê¬' => 'ê­', + 'ê®' => 'ê¯', + 'ê¹' => 'êº', + 'ê»' => 'ê¼', + 'ê½' => 'áµ¹', + 'ê¾' => 'ê¿', + 'Ꞁ' => 'êž', + 'êž‚' => 'ꞃ', + 'êž„' => 'êž…', + 'Ꞇ' => 'ꞇ', + 'êž‹' => 'ꞌ', + 'êž' => 'É¥', + 'êž' => 'êž‘', + 'êž’' => 'êž“', + 'êž–' => 'êž—', + 'Ꞙ' => 'êž™', + 'êžš' => 'êž›', + 'êžœ' => 'êž', + 'êžž' => 'ꞟ', + 'êž ' => 'êž¡', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'êž§', + 'Ꞩ' => 'êž©', + 'Ɦ' => 'ɦ', + 'êž«' => 'Éœ', + 'Ɡ' => 'É¡', + 'êž­' => 'ɬ', + 'êž®' => 'ɪ', + 'êž°' => 'Êž', + 'êž±' => 'ʇ', + 'êž²' => 'Ê', + 'êž³' => 'ê­“', + 'êž´' => 'êžµ', + 'êž¶' => 'êž·', + 'Ꞹ' => 'êž¹', + 'Ꞻ' => 'êž»', + 'êž¼' => 'êž½', + 'êž¾' => 'êž¿', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'êž”', + 'Ʂ' => 'Ê‚', + 'Ᶎ' => 'á¶Ž', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'ï½', + 'ï¼¢' => 'b', + 'ï¼£' => 'c', + 'D' => 'd', + 'ï¼¥' => 'ï½…', + 'F' => 'f', + 'ï¼§' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'ï¼­' => 'ï½', + 'ï¼®' => 'n', + 'O' => 'ï½', + 'ï¼°' => 'ï½', + 'ï¼±' => 'q', + 'ï¼²' => 'ï½’', + 'ï¼³' => 's', + 'ï¼´' => 'ï½”', + 'ï¼µ' => 'u', + 'ï¼¶' => 'ï½–', + 'ï¼·' => 'ï½—', + 'X' => 'x', + 'ï¼¹' => 'ï½™', + 'Z' => 'z', + 'ð€' => 'ð¨', + 'ð' => 'ð©', + 'ð‚' => 'ðª', + 'ðƒ' => 'ð«', + 'ð„' => 'ð¬', + 'ð…' => 'ð­', + 'ð†' => 'ð®', + 'ð‡' => 'ð¯', + 'ðˆ' => 'ð°', + 'ð‰' => 'ð±', + 'ðŠ' => 'ð²', + 'ð‹' => 'ð³', + 'ðŒ' => 'ð´', + 'ð' => 'ðµ', + 'ðŽ' => 'ð¶', + 'ð' => 'ð·', + 'ð' => 'ð¸', + 'ð‘' => 'ð¹', + 'ð’' => 'ðº', + 'ð“' => 'ð»', + 'ð”' => 'ð¼', + 'ð•' => 'ð½', + 'ð–' => 'ð¾', + 'ð—' => 'ð¿', + 'ð˜' => 'ð‘€', + 'ð™' => 'ð‘', + 'ðš' => 'ð‘‚', + 'ð›' => 'ð‘ƒ', + 'ðœ' => 'ð‘„', + 'ð' => 'ð‘…', + 'ðž' => 'ð‘†', + 'ðŸ' => 'ð‘‡', + 'ð ' => 'ð‘ˆ', + 'ð¡' => 'ð‘‰', + 'ð¢' => 'ð‘Š', + 'ð£' => 'ð‘‹', + 'ð¤' => 'ð‘Œ', + 'ð¥' => 'ð‘', + 'ð¦' => 'ð‘Ž', + 'ð§' => 'ð‘', + 'ð’°' => 'ð“˜', + 'ð’±' => 'ð“™', + 'ð’²' => 'ð“š', + 'ð’³' => 'ð“›', + 'ð’´' => 'ð“œ', + 'ð’µ' => 'ð“', + 'ð’¶' => 'ð“ž', + 'ð’·' => 'ð“Ÿ', + 'ð’¸' => 'ð“ ', + 'ð’¹' => 'ð“¡', + 'ð’º' => 'ð“¢', + 'ð’»' => 'ð“£', + 'ð’¼' => 'ð“¤', + 'ð’½' => 'ð“¥', + 'ð’¾' => 'ð“¦', + 'ð’¿' => 'ð“§', + 'ð“€' => 'ð“¨', + 'ð“' => 'ð“©', + 'ð“‚' => 'ð“ª', + 'ð“ƒ' => 'ð“«', + 'ð“„' => 'ð“¬', + 'ð“…' => 'ð“­', + 'ð“†' => 'ð“®', + 'ð“‡' => 'ð“¯', + 'ð“ˆ' => 'ð“°', + 'ð“‰' => 'ð“±', + 'ð“Š' => 'ð“²', + 'ð“‹' => 'ð“³', + 'ð“Œ' => 'ð“´', + 'ð“' => 'ð“µ', + 'ð“Ž' => 'ð“¶', + 'ð“' => 'ð“·', + 'ð“' => 'ð“¸', + 'ð“‘' => 'ð“¹', + 'ð“’' => 'ð“º', + 'ð““' => 'ð“»', + 'ð²€' => 'ð³€', + 'ð²' => 'ð³', + 'ð²‚' => 'ð³‚', + 'ð²ƒ' => 'ð³ƒ', + 'ð²„' => 'ð³„', + 'ð²…' => 'ð³…', + 'ð²†' => 'ð³†', + 'ð²‡' => 'ð³‡', + 'ð²ˆ' => 'ð³ˆ', + 'ð²‰' => 'ð³‰', + 'ð²Š' => 'ð³Š', + 'ð²‹' => 'ð³‹', + 'ð²Œ' => 'ð³Œ', + 'ð²' => 'ð³', + 'ð²Ž' => 'ð³Ž', + 'ð²' => 'ð³', + 'ð²' => 'ð³', + 'ð²‘' => 'ð³‘', + 'ð²’' => 'ð³’', + 'ð²“' => 'ð³“', + 'ð²”' => 'ð³”', + 'ð²•' => 'ð³•', + 'ð²–' => 'ð³–', + 'ð²—' => 'ð³—', + 'ð²˜' => 'ð³˜', + 'ð²™' => 'ð³™', + 'ð²š' => 'ð³š', + 'ð²›' => 'ð³›', + 'ð²œ' => 'ð³œ', + 'ð²' => 'ð³', + 'ð²ž' => 'ð³ž', + 'ð²Ÿ' => 'ð³Ÿ', + 'ð² ' => 'ð³ ', + 'ð²¡' => 'ð³¡', + 'ð²¢' => 'ð³¢', + 'ð²£' => 'ð³£', + 'ð²¤' => 'ð³¤', + 'ð²¥' => 'ð³¥', + 'ð²¦' => 'ð³¦', + 'ð²§' => 'ð³§', + 'ð²¨' => 'ð³¨', + 'ð²©' => 'ð³©', + 'ð²ª' => 'ð³ª', + 'ð²«' => 'ð³«', + 'ð²¬' => 'ð³¬', + 'ð²­' => 'ð³­', + 'ð²®' => 'ð³®', + 'ð²¯' => 'ð³¯', + 'ð²°' => 'ð³°', + 'ð²±' => 'ð³±', + 'ð²²' => 'ð³²', + 'ð‘¢ ' => 'ð‘£€', + '𑢡' => 'ð‘£', + 'ð‘¢¢' => '𑣂', + 'ð‘¢£' => '𑣃', + '𑢤' => '𑣄', + 'ð‘¢¥' => 'ð‘£…', + '𑢦' => '𑣆', + 'ð‘¢§' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + 'ð‘¢­' => 'ð‘£', + 'ð‘¢®' => '𑣎', + '𑢯' => 'ð‘£', + 'ð‘¢°' => 'ð‘£', + 'ð‘¢±' => '𑣑', + 'ð‘¢²' => 'ð‘£’', + 'ð‘¢³' => '𑣓', + 'ð‘¢´' => 'ð‘£”', + 'ð‘¢µ' => '𑣕', + 'ð‘¢¶' => 'ð‘£–', + 'ð‘¢·' => 'ð‘£—', + '𑢸' => '𑣘', + 'ð‘¢¹' => 'ð‘£™', + '𑢺' => '𑣚', + 'ð‘¢»' => 'ð‘£›', + 'ð‘¢¼' => '𑣜', + 'ð‘¢½' => 'ð‘£', + 'ð‘¢¾' => '𑣞', + '𑢿' => '𑣟', + 'ð–¹€' => 'ð–¹ ', + 'ð–¹' => '𖹡', + '𖹂' => 'ð–¹¢', + '𖹃' => 'ð–¹£', + '𖹄' => '𖹤', + 'ð–¹…' => 'ð–¹¥', + '𖹆' => '𖹦', + '𖹇' => 'ð–¹§', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + 'ð–¹' => 'ð–¹­', + '𖹎' => 'ð–¹®', + 'ð–¹' => '𖹯', + 'ð–¹' => 'ð–¹°', + '𖹑' => 'ð–¹±', + 'ð–¹’' => 'ð–¹²', + '𖹓' => 'ð–¹³', + 'ð–¹”' => 'ð–¹´', + '𖹕' => 'ð–¹µ', + 'ð–¹–' => 'ð–¹¶', + 'ð–¹—' => 'ð–¹·', + '𖹘' => '𖹸', + 'ð–¹™' => 'ð–¹¹', + '𖹚' => '𖹺', + 'ð–¹›' => 'ð–¹»', + '𖹜' => 'ð–¹¼', + 'ð–¹' => 'ð–¹½', + '𖹞' => 'ð–¹¾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + 'ðž¤' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + 'ðž¤' => '𞤯', + '𞤎' => '𞤰', + 'ðž¤' => '𞤱', + 'ðž¤' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + 'ðž¤' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => 'ðž¥', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Ã', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'Ã¥' => 'Ã…', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'ÃŒ', + 'í' => 'Ã', + 'î' => 'ÃŽ', + 'ï' => 'Ã', + 'ð' => 'Ã', + 'ñ' => 'Ñ', + 'ò' => 'Ã’', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ã', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'Ä' => 'Ä€', + 'ă' => 'Ä‚', + 'Ä…' => 'Ä„', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'Ä‹' => 'ÄŠ', + 'Ä' => 'ÄŒ', + 'Ä' => 'ÄŽ', + 'Ä‘' => 'Ä', + 'Ä“' => 'Ä’', + 'Ä•' => 'Ä”', + 'Ä—' => 'Ä–', + 'Ä™' => 'Ę', + 'Ä›' => 'Äš', + 'Ä' => 'Äœ', + 'ÄŸ' => 'Äž', + 'Ä¡' => 'Ä ', + 'Ä£' => 'Ä¢', + 'Ä¥' => 'Ĥ', + 'ħ' => 'Ħ', + 'Ä©' => 'Ĩ', + 'Ä«' => 'Ī', + 'Ä­' => 'Ĭ', + 'į' => 'Ä®', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ä´', + 'Ä·' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ä»', + 'ľ' => 'Ľ', + 'Å€' => 'Ä¿', + 'Å‚' => 'Å', + 'Å„' => 'Ń', + 'ņ' => 'Å…', + 'ň' => 'Ň', + 'Å‹' => 'ÅŠ', + 'Å' => 'ÅŒ', + 'Å' => 'ÅŽ', + 'Å‘' => 'Å', + 'Å“' => 'Å’', + 'Å•' => 'Å”', + 'Å—' => 'Å–', + 'Å™' => 'Ř', + 'Å›' => 'Åš', + 'Å' => 'Åœ', + 'ÅŸ' => 'Åž', + 'Å¡' => 'Å ', + 'Å£' => 'Å¢', + 'Å¥' => 'Ť', + 'ŧ' => 'Ŧ', + 'Å©' => 'Ũ', + 'Å«' => 'Ū', + 'Å­' => 'Ŭ', + 'ů' => 'Å®', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Å´', + 'Å·' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Å»', + 'ž' => 'Ž', + 'Å¿' => 'S', + 'Æ€' => 'Ƀ', + 'ƃ' => 'Æ‚', + 'Æ…' => 'Æ„', + 'ƈ' => 'Ƈ', + 'ÆŒ' => 'Æ‹', + 'Æ’' => 'Æ‘', + 'Æ•' => 'Ƕ', + 'Æ™' => 'Ƙ', + 'Æš' => 'Ƚ', + 'Æž' => 'È ', + 'Æ¡' => 'Æ ', + 'Æ£' => 'Æ¢', + 'Æ¥' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'Æ­' => 'Ƭ', + 'ư' => 'Ư', + 'Æ´' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'Æ¿' => 'Ç·', + 'Ç…' => 'Ç„', + 'dž' => 'Ç„', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Ç‹' => 'ÇŠ', + 'ÇŒ' => 'ÇŠ', + 'ÇŽ' => 'Ç', + 'Ç' => 'Ç', + 'Ç’' => 'Ç‘', + 'Ç”' => 'Ç“', + 'Ç–' => 'Ç•', + 'ǘ' => 'Ç—', + 'Çš' => 'Ç™', + 'Çœ' => 'Ç›', + 'Ç' => 'ÆŽ', + 'ÇŸ' => 'Çž', + 'Ç¡' => 'Ç ', + 'Ç£' => 'Ç¢', + 'Ç¥' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'Ç©' => 'Ǩ', + 'Ç«' => 'Ǫ', + 'Ç­' => 'Ǭ', + 'ǯ' => 'Ç®', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ç´', + 'ǹ' => 'Ǹ', + 'Ç»' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'Ç¿' => 'Ǿ', + 'È' => 'È€', + 'ȃ' => 'È‚', + 'È…' => 'È„', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'È‹' => 'ÈŠ', + 'È' => 'ÈŒ', + 'È' => 'ÈŽ', + 'È‘' => 'È', + 'È“' => 'È’', + 'È•' => 'È”', + 'È—' => 'È–', + 'È™' => 'Ș', + 'È›' => 'Èš', + 'È' => 'Èœ', + 'ÈŸ' => 'Èž', + 'È£' => 'È¢', + 'È¥' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'È©' => 'Ȩ', + 'È«' => 'Ȫ', + 'È­' => 'Ȭ', + 'ȯ' => 'È®', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'È»', + 'È¿' => 'â±¾', + 'É€' => 'Ɀ', + 'É‚' => 'É', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'É‹' => 'ÉŠ', + 'É' => 'ÉŒ', + 'É' => 'ÉŽ', + 'É' => 'Ɐ', + 'É‘' => 'â±­', + 'É’' => 'â±°', + 'É“' => 'Æ', + 'É”' => 'Ɔ', + 'É–' => 'Ɖ', + 'É—' => 'ÆŠ', + 'É™' => 'Æ', + 'É›' => 'Æ', + 'Éœ' => 'êž«', + 'É ' => 'Æ“', + 'É¡' => 'Ɡ', + 'É£' => 'Æ”', + 'É¥' => 'êž', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Æ—', + 'É©' => 'Æ–', + 'ɪ' => 'êž®', + 'É«' => 'â±¢', + 'ɬ' => 'êž­', + 'ɯ' => 'Æœ', + 'ɱ' => 'â±®', + 'ɲ' => 'Æ', + 'ɵ' => 'ÆŸ', + 'ɽ' => 'Ɽ', + 'Ê€' => 'Ʀ', + 'Ê‚' => 'Ʂ', + 'ʃ' => 'Æ©', + 'ʇ' => 'êž±', + 'ʈ' => 'Æ®', + 'ʉ' => 'É„', + 'ÊŠ' => 'Ʊ', + 'Ê‹' => 'Ʋ', + 'ÊŒ' => 'É…', + 'Ê’' => 'Æ·', + 'Ê' => 'êž²', + 'Êž' => 'êž°', + 'Í…' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'Í·' => 'Ͷ', + 'Í»' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ï¿', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Î’', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Î', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'Ï€' => 'Π', + 'Ï' => 'Ρ', + 'Ï‚' => 'Σ', + 'σ' => 'Σ', + 'Ï„' => 'Τ', + 'Ï…' => 'Î¥', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ÏŠ' => 'Ϊ', + 'Ï‹' => 'Ϋ', + 'ÏŒ' => 'ÎŒ', + 'Ï' => 'ÎŽ', + 'ÏŽ' => 'Î', + 'Ï' => 'Î’', + 'Ï‘' => 'Θ', + 'Ï•' => 'Φ', + 'Ï–' => 'Π', + 'Ï—' => 'Ï', + 'Ï™' => 'Ϙ', + 'Ï›' => 'Ïš', + 'Ï' => 'Ïœ', + 'ÏŸ' => 'Ïž', + 'Ï¡' => 'Ï ', + 'Ï£' => 'Ï¢', + 'Ï¥' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'Ï©' => 'Ϩ', + 'Ï«' => 'Ϫ', + 'Ï­' => 'Ϭ', + 'ϯ' => 'Ï®', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Í¿', + 'ϵ' => 'Ε', + 'ϸ' => 'Ï·', + 'Ï»' => 'Ϻ', + 'а' => 'Ð', + 'б' => 'Б', + 'в' => 'Ð’', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Ð', + 'о' => 'О', + 'п' => 'П', + 'Ñ€' => 'Р', + 'Ñ' => 'С', + 'Ñ‚' => 'Т', + 'у' => 'У', + 'Ñ„' => 'Ф', + 'Ñ…' => 'Ð¥', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ÑŠ' => 'Ъ', + 'Ñ‹' => 'Ы', + 'ÑŒ' => 'Ь', + 'Ñ' => 'Э', + 'ÑŽ' => 'Ю', + 'Ñ' => 'Я', + 'Ñ' => 'Ѐ', + 'Ñ‘' => 'Ð', + 'Ñ’' => 'Ђ', + 'Ñ“' => 'Ѓ', + 'Ñ”' => 'Є', + 'Ñ•' => 'Ð…', + 'Ñ–' => 'І', + 'Ñ—' => 'Ї', + 'ј' => 'Ј', + 'Ñ™' => 'Љ', + 'Ñš' => 'Њ', + 'Ñ›' => 'Ћ', + 'Ñœ' => 'ÐŒ', + 'Ñ' => 'Ð', + 'Ñž' => 'ÐŽ', + 'ÑŸ' => 'Ð', + 'Ñ¡' => 'Ñ ', + 'Ñ£' => 'Ñ¢', + 'Ñ¥' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'Ñ©' => 'Ѩ', + 'Ñ«' => 'Ѫ', + 'Ñ­' => 'Ѭ', + 'ѯ' => 'Ñ®', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ñ´', + 'Ñ·' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'Ñ»' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'Ñ¿' => 'Ѿ', + 'Ò' => 'Ò€', + 'Ò‹' => 'ÒŠ', + 'Ò' => 'ÒŒ', + 'Ò' => 'ÒŽ', + 'Ò‘' => 'Ò', + 'Ò“' => 'Ò’', + 'Ò•' => 'Ò”', + 'Ò—' => 'Ò–', + 'Ò™' => 'Ò˜', + 'Ò›' => 'Òš', + 'Ò' => 'Òœ', + 'ÒŸ' => 'Òž', + 'Ò¡' => 'Ò ', + 'Ò£' => 'Ò¢', + 'Ò¥' => 'Ò¤', + 'Ò§' => 'Ò¦', + 'Ò©' => 'Ò¨', + 'Ò«' => 'Òª', + 'Ò­' => 'Ò¬', + 'Ò¯' => 'Ò®', + 'Ò±' => 'Ò°', + 'Ò³' => 'Ò²', + 'Òµ' => 'Ò´', + 'Ò·' => 'Ò¶', + 'Ò¹' => 'Ò¸', + 'Ò»' => 'Òº', + 'Ò½' => 'Ò¼', + 'Ò¿' => 'Ò¾', + 'Ó‚' => 'Ó', + 'Ó„' => 'Óƒ', + 'Ó†' => 'Ó…', + 'Óˆ' => 'Ó‡', + 'ÓŠ' => 'Ó‰', + 'ÓŒ' => 'Ó‹', + 'ÓŽ' => 'Ó', + 'Ó' => 'Ó€', + 'Ó‘' => 'Ó', + 'Ó“' => 'Ó’', + 'Ó•' => 'Ó”', + 'Ó—' => 'Ó–', + 'Ó™' => 'Ó˜', + 'Ó›' => 'Óš', + 'Ó' => 'Óœ', + 'ÓŸ' => 'Óž', + 'Ó¡' => 'Ó ', + 'Ó£' => 'Ó¢', + 'Ó¥' => 'Ó¤', + 'Ó§' => 'Ó¦', + 'Ó©' => 'Ó¨', + 'Ó«' => 'Óª', + 'Ó­' => 'Ó¬', + 'Ó¯' => 'Ó®', + 'Ó±' => 'Ó°', + 'Ó³' => 'Ó²', + 'Óµ' => 'Ó´', + 'Ó·' => 'Ó¶', + 'Ó¹' => 'Ó¸', + 'Ó»' => 'Óº', + 'Ó½' => 'Ó¼', + 'Ó¿' => 'Ó¾', + 'Ô' => 'Ô€', + 'Ôƒ' => 'Ô‚', + 'Ô…' => 'Ô„', + 'Ô‡' => 'Ô†', + 'Ô‰' => 'Ôˆ', + 'Ô‹' => 'ÔŠ', + 'Ô' => 'ÔŒ', + 'Ô' => 'ÔŽ', + 'Ô‘' => 'Ô', + 'Ô“' => 'Ô’', + 'Ô•' => 'Ô”', + 'Ô—' => 'Ô–', + 'Ô™' => 'Ô˜', + 'Ô›' => 'Ôš', + 'Ô' => 'Ôœ', + 'ÔŸ' => 'Ôž', + 'Ô¡' => 'Ô ', + 'Ô£' => 'Ô¢', + 'Ô¥' => 'Ô¤', + 'Ô§' => 'Ô¦', + 'Ô©' => 'Ô¨', + 'Ô«' => 'Ôª', + 'Ô­' => 'Ô¬', + 'Ô¯' => 'Ô®', + 'Õ¡' => 'Ô±', + 'Õ¢' => 'Ô²', + 'Õ£' => 'Ô³', + 'Õ¤' => 'Ô´', + 'Õ¥' => 'Ôµ', + 'Õ¦' => 'Ô¶', + 'Õ§' => 'Ô·', + 'Õ¨' => 'Ô¸', + 'Õ©' => 'Ô¹', + 'Õª' => 'Ôº', + 'Õ«' => 'Ô»', + 'Õ¬' => 'Ô¼', + 'Õ­' => 'Ô½', + 'Õ®' => 'Ô¾', + 'Õ¯' => 'Ô¿', + 'Õ°' => 'Õ€', + 'Õ±' => 'Õ', + 'Õ²' => 'Õ‚', + 'Õ³' => 'Õƒ', + 'Õ´' => 'Õ„', + 'Õµ' => 'Õ…', + 'Õ¶' => 'Õ†', + 'Õ·' => 'Õ‡', + 'Õ¸' => 'Õˆ', + 'Õ¹' => 'Õ‰', + 'Õº' => 'ÕŠ', + 'Õ»' => 'Õ‹', + 'Õ¼' => 'ÕŒ', + 'Õ½' => 'Õ', + 'Õ¾' => 'ÕŽ', + 'Õ¿' => 'Õ', + 'Ö€' => 'Õ', + 'Ö' => 'Õ‘', + 'Ö‚' => 'Õ’', + 'Öƒ' => 'Õ“', + 'Ö„' => 'Õ”', + 'Ö…' => 'Õ•', + 'Ö†' => 'Õ–', + 'áƒ' => 'á²', + 'ბ' => 'Ბ', + 'გ' => 'á²’', + 'დ' => 'Დ', + 'ე' => 'á²”', + 'ვ' => 'Ვ', + 'ზ' => 'á²–', + 'თ' => 'á²—', + 'ი' => 'Ი', + 'კ' => 'á²™', + 'ლ' => 'Ლ', + 'მ' => 'á²›', + 'ნ' => 'Ნ', + 'áƒ' => 'á²', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'á² ', + 'ს' => 'Ს', + 'ტ' => 'á²¢', + 'უ' => 'á²£', + 'ფ' => 'Ფ', + 'ქ' => 'á²¥', + 'ღ' => 'Ღ', + 'ყ' => 'á²§', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'á²­', + 'ხ' => 'á²®', + 'ჯ' => 'Ჯ', + 'ჰ' => 'á²°', + 'ჱ' => 'á²±', + 'ჲ' => 'á²²', + 'ჳ' => 'á²³', + 'ჴ' => 'á²´', + 'ჵ' => 'á²µ', + 'ჶ' => 'á²¶', + 'ჷ' => 'á²·', + 'ჸ' => 'Ჸ', + 'ჹ' => 'á²¹', + 'ჺ' => 'Ჺ', + 'ჽ' => 'á²½', + 'ჾ' => 'á²¾', + 'ჿ' => 'Ჿ', + 'á¸' => 'á°', + 'á¹' => 'á±', + 'áº' => 'á²', + 'á»' => 'á³', + 'á¼' => 'á´', + 'á½' => 'áµ', + 'á²€' => 'Ð’', + 'á²' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'á²…' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ñ¢', + 'ᲈ' => 'Ꙋ', + 'áµ¹' => 'ê½', + 'áµ½' => 'â±£', + 'á¶Ž' => 'Ᶎ', + 'á¸' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'á¸' => 'Ḍ', + 'á¸' => 'Ḏ', + 'ḑ' => 'á¸', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'á¸' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'á¹' => 'á¹€', + 'ṃ' => 'Ṃ', + 'á¹…' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'á¹' => 'Ṍ', + 'á¹' => 'Ṏ', + 'ṑ' => 'á¹', + 'ṓ' => 'á¹’', + 'ṕ' => 'á¹”', + 'á¹—' => 'á¹–', + 'á¹™' => 'Ṙ', + 'á¹›' => 'Ṛ', + 'á¹' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'á¹ ', + 'á¹£' => 'á¹¢', + 'á¹¥' => 'Ṥ', + 'á¹§' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'á¹­' => 'Ṭ', + 'ṯ' => 'á¹®', + 'á¹±' => 'á¹°', + 'á¹³' => 'á¹²', + 'á¹µ' => 'á¹´', + 'á¹·' => 'á¹¶', + 'á¹¹' => 'Ṹ', + 'á¹»' => 'Ṻ', + 'á¹½' => 'á¹¼', + 'ṿ' => 'á¹¾', + 'áº' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'áº' => 'Ẍ', + 'áº' => 'Ẏ', + 'ẑ' => 'áº', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'á¹ ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'á»' => 'Ề', + 'ể' => 'Ể', + 'á»…' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'á»' => 'Ọ', + 'á»' => 'Ỏ', + 'ố' => 'á»', + 'ồ' => 'á»’', + 'ổ' => 'á»”', + 'á»—' => 'á»–', + 'á»™' => 'Ộ', + 'á»›' => 'Ớ', + 'á»' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'á» ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'á»§' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'á»­' => 'Ử', + 'ữ' => 'á»®', + 'á»±' => 'á»°', + 'ỳ' => 'Ỳ', + 'ỵ' => 'á»´', + 'á»·' => 'á»¶', + 'ỹ' => 'Ỹ', + 'á»»' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'á¼€' => 'Ἀ', + 'á¼' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'á¼…' => 'á¼', + 'ἆ' => 'Ἆ', + 'ἇ' => 'á¼', + 'á¼' => 'Ἐ', + 'ἑ' => 'á¼™', + 'á¼’' => 'Ἒ', + 'ἓ' => 'á¼›', + 'á¼”' => 'Ἔ', + 'ἕ' => 'á¼', + 'á¼ ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'á¼¢' => 'Ἢ', + 'á¼£' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'á¼¥' => 'á¼­', + 'ἦ' => 'á¼®', + 'á¼§' => 'Ἧ', + 'á¼°' => 'Ἰ', + 'á¼±' => 'á¼¹', + 'á¼²' => 'Ἲ', + 'á¼³' => 'á¼»', + 'á¼´' => 'á¼¼', + 'á¼µ' => 'á¼½', + 'á¼¶' => 'á¼¾', + 'á¼·' => 'Ἷ', + 'á½€' => 'Ὀ', + 'á½' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'á½…' => 'á½', + 'ὑ' => 'á½™', + 'ὓ' => 'á½›', + 'ὕ' => 'á½', + 'á½—' => 'Ὗ', + 'á½ ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'á½¢' => 'Ὢ', + 'á½£' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'á½¥' => 'á½­', + 'ὦ' => 'á½®', + 'á½§' => 'Ὧ', + 'á½°' => 'Ὰ', + 'á½±' => 'á¾»', + 'á½²' => 'Ὲ', + 'á½³' => 'Έ', + 'á½´' => 'Ὴ', + 'á½µ' => 'á¿‹', + 'á½¶' => 'Ὶ', + 'á½·' => 'á¿›', + 'ὸ' => 'Ὸ', + 'á½¹' => 'Ό', + 'ὺ' => 'Ὺ', + 'á½»' => 'á¿«', + 'á½¼' => 'Ὼ', + 'á½½' => 'á¿»', + 'á¾€' => 'ἈΙ', + 'á¾' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'á¾…' => 'á¼Î™', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'á¼Î™', + 'á¾' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'á¾’' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'á¾”' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'á¾–' => 'ἮΙ', + 'á¾—' => 'ἯΙ', + 'á¾ ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'á¾¢' => 'ὪΙ', + 'á¾£' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'á¾¥' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'á¾§' => 'ὯΙ', + 'á¾°' => 'Ᾰ', + 'á¾±' => 'á¾¹', + 'á¾³' => 'ΑΙ', + 'á¾¾' => 'Ι', + 'ῃ' => 'ΗΙ', + 'á¿' => 'Ῐ', + 'á¿‘' => 'á¿™', + 'á¿ ' => 'Ῠ', + 'á¿¡' => 'á¿©', + 'á¿¥' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'â…Ž' => 'Ⅎ', + 'â…°' => 'â… ', + 'â…±' => 'â…¡', + 'â…²' => 'â…¢', + 'â…³' => 'â…£', + 'â…´' => 'â…¤', + 'â…µ' => 'â…¥', + 'â…¶' => 'â…¦', + 'â…·' => 'â…§', + 'â…¸' => 'â…¨', + 'â…¹' => 'â…©', + 'â…º' => 'â…ª', + 'â…»' => 'â…«', + 'â…¼' => 'â…¬', + 'â…½' => 'â…­', + 'â…¾' => 'â…®', + 'â…¿' => 'â…¯', + 'ↄ' => 'Ↄ', + 'â“' => 'â’¶', + 'â“‘' => 'â’·', + 'â“’' => 'â’¸', + 'â““' => 'â’¹', + 'â“”' => 'â’º', + 'â“•' => 'â’»', + 'â“–' => 'â’¼', + 'â“—' => 'â’½', + 'ⓘ' => 'â’¾', + 'â“™' => 'â’¿', + 'ⓚ' => 'â“€', + 'â“›' => 'â“', + 'ⓜ' => 'â“‚', + 'â“' => 'Ⓝ', + 'ⓞ' => 'â“„', + 'ⓟ' => 'â“…', + 'â“ ' => 'Ⓠ', + 'â“¡' => 'Ⓡ', + 'â“¢' => 'Ⓢ', + 'â“£' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'â“¥' => 'â“‹', + 'ⓦ' => 'Ⓦ', + 'â“§' => 'â“', + 'ⓨ' => 'Ⓨ', + 'â“©' => 'â“', + 'â°°' => 'â°€', + 'â°±' => 'â°', + 'â°²' => 'â°‚', + 'â°³' => 'â°ƒ', + 'â°´' => 'â°„', + 'â°µ' => 'â°…', + 'â°¶' => 'â°†', + 'â°·' => 'â°‡', + 'â°¸' => 'â°ˆ', + 'â°¹' => 'â°‰', + 'â°º' => 'â°Š', + 'â°»' => 'â°‹', + 'â°¼' => 'â°Œ', + 'â°½' => 'â°', + 'â°¾' => 'â°Ž', + 'â°¿' => 'â°', + 'â±€' => 'â°', + 'â±' => 'â°‘', + 'ⱂ' => 'â°’', + 'ⱃ' => 'â°“', + 'ⱄ' => 'â°”', + 'â±…' => 'â°•', + 'ⱆ' => 'â°–', + 'ⱇ' => 'â°—', + 'ⱈ' => 'â°˜', + 'ⱉ' => 'â°™', + 'ⱊ' => 'â°š', + 'ⱋ' => 'â°›', + 'ⱌ' => 'â°œ', + 'â±' => 'â°', + 'ⱎ' => 'â°ž', + 'â±' => 'â°Ÿ', + 'â±' => 'â° ', + 'ⱑ' => 'â°¡', + 'â±’' => 'â°¢', + 'ⱓ' => 'â°£', + 'â±”' => 'â°¤', + 'ⱕ' => 'â°¥', + 'â±–' => 'â°¦', + 'â±—' => 'â°§', + 'ⱘ' => 'â°¨', + 'â±™' => 'â°©', + 'ⱚ' => 'â°ª', + 'â±›' => 'â°«', + 'ⱜ' => 'â°¬', + 'â±' => 'â°­', + 'ⱞ' => 'â°®', + 'ⱡ' => 'â± ', + 'â±¥' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'â±§', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'â±³' => 'â±²', + 'â±¶' => 'â±µ', + 'â²' => 'â²€', + 'ⲃ' => 'Ⲃ', + 'â²…' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'â²' => 'Ⲍ', + 'â²' => 'Ⲏ', + 'ⲑ' => 'â²', + 'ⲓ' => 'â²’', + 'ⲕ' => 'â²”', + 'â²—' => 'â²–', + 'â²™' => 'Ⲙ', + 'â²›' => 'Ⲛ', + 'â²' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'â² ', + 'â²£' => 'â²¢', + 'â²¥' => 'Ⲥ', + 'â²§' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'â²­' => 'Ⲭ', + 'ⲯ' => 'â²®', + 'â²±' => 'â²°', + 'â²³' => 'â²²', + 'â²µ' => 'â²´', + 'â²·' => 'â²¶', + 'â²¹' => 'Ⲹ', + 'â²»' => 'Ⲻ', + 'â²½' => 'â²¼', + 'ⲿ' => 'â²¾', + 'â³' => 'â³€', + 'ⳃ' => 'Ⳃ', + 'â³…' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'â³' => 'Ⳍ', + 'â³' => 'Ⳏ', + 'ⳑ' => 'â³', + 'ⳓ' => 'â³’', + 'ⳕ' => 'â³”', + 'â³—' => 'â³–', + 'â³™' => 'Ⳙ', + 'â³›' => 'Ⳛ', + 'â³' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'â³ ', + 'â³£' => 'â³¢', + 'ⳬ' => 'Ⳬ', + 'â³®' => 'â³­', + 'â³³' => 'â³²', + 'â´€' => 'á‚ ', + 'â´' => 'á‚¡', + 'â´‚' => 'á‚¢', + 'â´ƒ' => 'á‚£', + 'â´„' => 'Ⴄ', + 'â´…' => 'á‚¥', + 'â´†' => 'Ⴆ', + 'â´‡' => 'á‚§', + 'â´ˆ' => 'Ⴈ', + 'â´‰' => 'á‚©', + 'â´Š' => 'Ⴊ', + 'â´‹' => 'á‚«', + 'â´Œ' => 'Ⴌ', + 'â´' => 'á‚­', + 'â´Ž' => 'á‚®', + 'â´' => 'Ⴏ', + 'â´' => 'á‚°', + 'â´‘' => 'Ⴑ', + 'â´’' => 'Ⴒ', + 'â´“' => 'Ⴓ', + 'â´”' => 'á‚´', + 'â´•' => 'Ⴕ', + 'â´–' => 'á‚¶', + 'â´—' => 'á‚·', + 'â´˜' => 'Ⴘ', + 'â´™' => 'Ⴙ', + 'â´š' => 'Ⴚ', + 'â´›' => 'á‚»', + 'â´œ' => 'Ⴜ', + 'â´' => 'Ⴝ', + 'â´ž' => 'Ⴞ', + 'â´Ÿ' => 'á‚¿', + 'â´ ' => 'Ⴠ', + 'â´¡' => 'áƒ', + 'â´¢' => 'Ⴢ', + 'â´£' => 'Ⴣ', + 'â´¤' => 'Ⴤ', + 'â´¥' => 'Ⴥ', + 'â´§' => 'Ⴧ', + 'â´­' => 'áƒ', + 'ê™' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ê™…' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ê™' => 'Ꙍ', + 'ê™' => 'Ꙏ', + 'ꙑ' => 'ê™', + 'ꙓ' => 'ê™’', + 'ꙕ' => 'ê™”', + 'ê™—' => 'ê™–', + 'ê™™' => 'Ꙙ', + 'ê™›' => 'Ꙛ', + 'ê™' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'ê™ ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ê™§' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ê™­' => 'Ꙭ', + 'êš' => 'Ꚁ', + 'ꚃ' => 'êš‚', + 'êš…' => 'êš„', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'êš‹' => 'Ꚋ', + 'êš' => 'Ꚍ', + 'êš' => 'Ꚏ', + 'êš‘' => 'êš', + 'êš“' => 'êš’', + 'êš•' => 'êš”', + 'êš—' => 'êš–', + 'êš™' => 'Ꚙ', + 'êš›' => 'êšš', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ê' => 'ê€', + 'êƒ' => 'ê‚', + 'ê…' => 'ê„', + 'ê‡' => 'ê†', + 'ê‰' => 'êˆ', + 'ê‹' => 'êŠ', + 'ê' => 'êŒ', + 'ê' => 'êŽ', + 'ê‘' => 'ê', + 'ê“' => 'ê’', + 'ê•' => 'ê”', + 'ê—' => 'ê–', + 'ê™' => 'ê˜', + 'ê›' => 'êš', + 'ê' => 'êœ', + 'êŸ' => 'êž', + 'ê¡' => 'ê ', + 'ê£' => 'ê¢', + 'ê¥' => 'ê¤', + 'ê§' => 'ê¦', + 'ê©' => 'ê¨', + 'ê«' => 'êª', + 'ê­' => 'ê¬', + 'ê¯' => 'ê®', + 'êº' => 'ê¹', + 'ê¼' => 'ê»', + 'ê¿' => 'ê¾', + 'êž' => 'Ꞁ', + 'ꞃ' => 'êž‚', + 'êž…' => 'êž„', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'êž‹', + 'êž‘' => 'êž', + 'êž“' => 'êž’', + 'êž”' => 'Ꞔ', + 'êž—' => 'êž–', + 'êž™' => 'Ꞙ', + 'êž›' => 'êžš', + 'êž' => 'êžœ', + 'ꞟ' => 'êžž', + 'êž¡' => 'êž ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'êž§' => 'Ꞧ', + 'êž©' => 'Ꞩ', + 'êžµ' => 'êž´', + 'êž·' => 'êž¶', + 'êž¹' => 'Ꞹ', + 'êž»' => 'Ꞻ', + 'êž½' => 'êž¼', + 'êž¿' => 'êž¾', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ê­“' => 'êž³', + 'ê­°' => 'Ꭰ', + 'ê­±' => 'Ꭱ', + 'ê­²' => 'Ꭲ', + 'ê­³' => 'Ꭳ', + 'ê­´' => 'Ꭴ', + 'ê­µ' => 'Ꭵ', + 'ê­¶' => 'Ꭶ', + 'ê­·' => 'Ꭷ', + 'ê­¸' => 'Ꭸ', + 'ê­¹' => 'Ꭹ', + 'ê­º' => 'Ꭺ', + 'ê­»' => 'Ꭻ', + 'ê­¼' => 'Ꭼ', + 'ê­½' => 'Ꭽ', + 'ê­¾' => 'Ꭾ', + 'ê­¿' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ê®' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ê®…' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ê®' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ê®' => 'Ꮏ', + 'ê®' => 'á€', + 'ꮑ' => 'á', + 'ê®’' => 'á‚', + 'ꮓ' => 'áƒ', + 'ê®”' => 'á„', + 'ꮕ' => 'á…', + 'ê®–' => 'á†', + 'ê®—' => 'á‡', + 'ꮘ' => 'áˆ', + 'ê®™' => 'á‰', + 'ꮚ' => 'áŠ', + 'ê®›' => 'á‹', + 'ꮜ' => 'áŒ', + 'ê®' => 'á', + 'ꮞ' => 'áŽ', + 'ꮟ' => 'á', + 'ê® ' => 'á', + 'ꮡ' => 'á‘', + 'ꮢ' => 'á’', + 'ꮣ' => 'á“', + 'ꮤ' => 'á”', + 'ꮥ' => 'á•', + 'ꮦ' => 'á–', + 'ê®§' => 'á—', + 'ꮨ' => 'á˜', + 'ꮩ' => 'á™', + 'ꮪ' => 'áš', + 'ꮫ' => 'á›', + 'ꮬ' => 'áœ', + 'ê®­' => 'á', + 'ê®®' => 'áž', + 'ꮯ' => 'áŸ', + 'ê®°' => 'á ', + 'ê®±' => 'á¡', + 'ꮲ' => 'á¢', + 'ꮳ' => 'á£', + 'ê®´' => 'á¤', + 'ꮵ' => 'á¥', + 'ê®¶' => 'á¦', + 'ê®·' => 'á§', + 'ꮸ' => 'á¨', + 'ꮹ' => 'á©', + 'ꮺ' => 'áª', + 'ê®»' => 'á«', + 'ꮼ' => 'á¬', + 'ꮽ' => 'á­', + 'ꮾ' => 'á®', + 'ꮿ' => 'á¯', + 'ï½' => 'A', + 'b' => 'ï¼¢', + 'c' => 'ï¼£', + 'd' => 'D', + 'ï½…' => 'ï¼¥', + 'f' => 'F', + 'g' => 'ï¼§', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'ï½' => 'ï¼­', + 'n' => 'ï¼®', + 'ï½' => 'O', + 'ï½' => 'ï¼°', + 'q' => 'ï¼±', + 'ï½’' => 'ï¼²', + 's' => 'ï¼³', + 'ï½”' => 'ï¼´', + 'u' => 'ï¼µ', + 'ï½–' => 'ï¼¶', + 'ï½—' => 'ï¼·', + 'x' => 'X', + 'ï½™' => 'ï¼¹', + 'z' => 'Z', + 'ð¨' => 'ð€', + 'ð©' => 'ð', + 'ðª' => 'ð‚', + 'ð«' => 'ðƒ', + 'ð¬' => 'ð„', + 'ð­' => 'ð…', + 'ð®' => 'ð†', + 'ð¯' => 'ð‡', + 'ð°' => 'ðˆ', + 'ð±' => 'ð‰', + 'ð²' => 'ðŠ', + 'ð³' => 'ð‹', + 'ð´' => 'ðŒ', + 'ðµ' => 'ð', + 'ð¶' => 'ðŽ', + 'ð·' => 'ð', + 'ð¸' => 'ð', + 'ð¹' => 'ð‘', + 'ðº' => 'ð’', + 'ð»' => 'ð“', + 'ð¼' => 'ð”', + 'ð½' => 'ð•', + 'ð¾' => 'ð–', + 'ð¿' => 'ð—', + 'ð‘€' => 'ð˜', + 'ð‘' => 'ð™', + 'ð‘‚' => 'ðš', + 'ð‘ƒ' => 'ð›', + 'ð‘„' => 'ðœ', + 'ð‘…' => 'ð', + 'ð‘†' => 'ðž', + 'ð‘‡' => 'ðŸ', + 'ð‘ˆ' => 'ð ', + 'ð‘‰' => 'ð¡', + 'ð‘Š' => 'ð¢', + 'ð‘‹' => 'ð£', + 'ð‘Œ' => 'ð¤', + 'ð‘' => 'ð¥', + 'ð‘Ž' => 'ð¦', + 'ð‘' => 'ð§', + 'ð“˜' => 'ð’°', + 'ð“™' => 'ð’±', + 'ð“š' => 'ð’²', + 'ð“›' => 'ð’³', + 'ð“œ' => 'ð’´', + 'ð“' => 'ð’µ', + 'ð“ž' => 'ð’¶', + 'ð“Ÿ' => 'ð’·', + 'ð“ ' => 'ð’¸', + 'ð“¡' => 'ð’¹', + 'ð“¢' => 'ð’º', + 'ð“£' => 'ð’»', + 'ð“¤' => 'ð’¼', + 'ð“¥' => 'ð’½', + 'ð“¦' => 'ð’¾', + 'ð“§' => 'ð’¿', + 'ð“¨' => 'ð“€', + 'ð“©' => 'ð“', + 'ð“ª' => 'ð“‚', + 'ð“«' => 'ð“ƒ', + 'ð“¬' => 'ð“„', + 'ð“­' => 'ð“…', + 'ð“®' => 'ð“†', + 'ð“¯' => 'ð“‡', + 'ð“°' => 'ð“ˆ', + 'ð“±' => 'ð“‰', + 'ð“²' => 'ð“Š', + 'ð“³' => 'ð“‹', + 'ð“´' => 'ð“Œ', + 'ð“µ' => 'ð“', + 'ð“¶' => 'ð“Ž', + 'ð“·' => 'ð“', + 'ð“¸' => 'ð“', + 'ð“¹' => 'ð“‘', + 'ð“º' => 'ð“’', + 'ð“»' => 'ð““', + 'ð³€' => 'ð²€', + 'ð³' => 'ð²', + 'ð³‚' => 'ð²‚', + 'ð³ƒ' => 'ð²ƒ', + 'ð³„' => 'ð²„', + 'ð³…' => 'ð²…', + 'ð³†' => 'ð²†', + 'ð³‡' => 'ð²‡', + 'ð³ˆ' => 'ð²ˆ', + 'ð³‰' => 'ð²‰', + 'ð³Š' => 'ð²Š', + 'ð³‹' => 'ð²‹', + 'ð³Œ' => 'ð²Œ', + 'ð³' => 'ð²', + 'ð³Ž' => 'ð²Ž', + 'ð³' => 'ð²', + 'ð³' => 'ð²', + 'ð³‘' => 'ð²‘', + 'ð³’' => 'ð²’', + 'ð³“' => 'ð²“', + 'ð³”' => 'ð²”', + 'ð³•' => 'ð²•', + 'ð³–' => 'ð²–', + 'ð³—' => 'ð²—', + 'ð³˜' => 'ð²˜', + 'ð³™' => 'ð²™', + 'ð³š' => 'ð²š', + 'ð³›' => 'ð²›', + 'ð³œ' => 'ð²œ', + 'ð³' => 'ð²', + 'ð³ž' => 'ð²ž', + 'ð³Ÿ' => 'ð²Ÿ', + 'ð³ ' => 'ð² ', + 'ð³¡' => 'ð²¡', + 'ð³¢' => 'ð²¢', + 'ð³£' => 'ð²£', + 'ð³¤' => 'ð²¤', + 'ð³¥' => 'ð²¥', + 'ð³¦' => 'ð²¦', + 'ð³§' => 'ð²§', + 'ð³¨' => 'ð²¨', + 'ð³©' => 'ð²©', + 'ð³ª' => 'ð²ª', + 'ð³«' => 'ð²«', + 'ð³¬' => 'ð²¬', + 'ð³­' => 'ð²­', + 'ð³®' => 'ð²®', + 'ð³¯' => 'ð²¯', + 'ð³°' => 'ð²°', + 'ð³±' => 'ð²±', + 'ð³²' => 'ð²²', + 'ð‘£€' => 'ð‘¢ ', + 'ð‘£' => '𑢡', + '𑣂' => 'ð‘¢¢', + '𑣃' => 'ð‘¢£', + '𑣄' => '𑢤', + 'ð‘£…' => 'ð‘¢¥', + '𑣆' => '𑢦', + '𑣇' => 'ð‘¢§', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + 'ð‘£' => 'ð‘¢­', + '𑣎' => 'ð‘¢®', + 'ð‘£' => '𑢯', + 'ð‘£' => 'ð‘¢°', + '𑣑' => 'ð‘¢±', + 'ð‘£’' => 'ð‘¢²', + '𑣓' => 'ð‘¢³', + 'ð‘£”' => 'ð‘¢´', + '𑣕' => 'ð‘¢µ', + 'ð‘£–' => 'ð‘¢¶', + 'ð‘£—' => 'ð‘¢·', + '𑣘' => '𑢸', + 'ð‘£™' => 'ð‘¢¹', + '𑣚' => '𑢺', + 'ð‘£›' => 'ð‘¢»', + '𑣜' => 'ð‘¢¼', + 'ð‘£' => 'ð‘¢½', + '𑣞' => 'ð‘¢¾', + '𑣟' => '𑢿', + 'ð–¹ ' => 'ð–¹€', + '𖹡' => 'ð–¹', + 'ð–¹¢' => '𖹂', + 'ð–¹£' => '𖹃', + '𖹤' => '𖹄', + 'ð–¹¥' => 'ð–¹…', + '𖹦' => '𖹆', + 'ð–¹§' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + 'ð–¹­' => 'ð–¹', + 'ð–¹®' => '𖹎', + '𖹯' => 'ð–¹', + 'ð–¹°' => 'ð–¹', + 'ð–¹±' => '𖹑', + 'ð–¹²' => 'ð–¹’', + 'ð–¹³' => '𖹓', + 'ð–¹´' => 'ð–¹”', + 'ð–¹µ' => '𖹕', + 'ð–¹¶' => 'ð–¹–', + 'ð–¹·' => 'ð–¹—', + '𖹸' => '𖹘', + 'ð–¹¹' => 'ð–¹™', + '𖹺' => '𖹚', + 'ð–¹»' => 'ð–¹›', + 'ð–¹¼' => '𖹜', + 'ð–¹½' => 'ð–¹', + 'ð–¹¾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => 'ðž¤', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => 'ðž¤', + '𞤰' => '𞤎', + '𞤱' => 'ðž¤', + '𞤲' => 'ðž¤', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => 'ðž¤', + '𞥀' => '𞤞', + 'ðž¥' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'ï¬' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'Ö‡' => 'ÔµÕ’', + 'ﬓ' => 'Õ„Õ†', + 'ﬔ' => 'Õ„Ôµ', + 'ﬕ' => 'Õ„Ô»', + 'ﬖ' => 'ÕŽÕ†', + 'ﬗ' => 'Õ„Ô½', + 'ʼn' => 'ʼN', + 'Î' => 'ΪÌ', + 'ΰ' => 'ΫÌ', + 'ǰ' => 'JÌŒ', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'WÌŠ', + 'ẙ' => 'YÌŠ', + 'ẚ' => 'Aʾ', + 'á½' => 'Υ̓', + 'á½’' => 'Υ̓̀', + 'á½”' => 'Υ̓Ì', + 'á½–' => 'Υ̓͂', + 'á¾¶' => 'Α͂', + 'ῆ' => 'Η͂', + 'á¿’' => 'Ϊ̀', + 'á¿“' => 'ΪÌ', + 'á¿–' => 'Ι͂', + 'á¿—' => 'Ϊ͂', + 'á¿¢' => 'Ϋ̀', + 'á¿£' => 'ΫÌ', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'á¿§' => 'Ϋ͂', + 'á¿¶' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'á¾' => 'á¼Î™', + 'ᾎ' => 'ἎΙ', + 'á¾' => 'á¼Î™', + 'ᾘ' => 'ἨΙ', + 'á¾™' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'á¾›' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'á¾' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'á¾­' => 'ὭΙ', + 'á¾®' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'á¾¼' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'á¾²' => 'ᾺΙ', + 'á¾´' => 'ΆΙ', + 'á¿‚' => 'ῊΙ', + 'á¿„' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'á¿´' => 'ÎΙ', + 'á¾·' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'á¿·' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..ff51ae0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + +if (!function_exists('mb_trim')) { + function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); } +} + + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..5be7d20 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false|null { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + +if (!function_exists('mb_trim')) { + function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..4ed241a --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php83/LICENSE b/vendor/symfony/polyfill-php83/LICENSE new file mode 100644 index 0000000..733c826 --- /dev/null +++ b/vendor/symfony/polyfill-php83/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022-present Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-php83/Php83.php b/vendor/symfony/polyfill-php83/Php83.php new file mode 100644 index 0000000..3d94b6c --- /dev/null +++ b/vendor/symfony/polyfill-php83/Php83.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php83; + +/** + * @author Ion Bazan + * @author Pierre Ambroise + * + * @internal + */ +final class Php83 +{ + private const JSON_MAX_DEPTH = 0x7FFFFFFF; // see https://www.php.net/manual/en/function.json-decode.php + + public static function json_validate(string $json, int $depth = 512, int $flags = 0): bool + { + if (0 !== $flags && \defined('JSON_INVALID_UTF8_IGNORE') && \JSON_INVALID_UTF8_IGNORE !== $flags) { + throw new \ValueError('json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)'); + } + + if ($depth <= 0) { + throw new \ValueError('json_validate(): Argument #2 ($depth) must be greater than 0'); + } + + if ($depth > self::JSON_MAX_DEPTH) { + throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH)); + } + + json_decode($json, null, $depth, $flags); + + return \JSON_ERROR_NONE === json_last_error(); + } + + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string + { + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + try { + $validEncoding = @mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + // BC for PHP 7.3 and lower + if (!$validEncoding) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + if (mb_strlen($pad_string, $encoding) <= 0) { + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + $paddingRequired = $length - mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + public static function str_increment(string $string): string + { + if ('' === $string) { + throw new \ValueError('str_increment(): Argument #1 ($string) cannot be empty'); + } + + if (!preg_match('/^[a-zA-Z0-9]+$/', $string)) { + throw new \ValueError('str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters'); + } + + if (is_numeric($string)) { + $offset = stripos($string, 'e'); + if (false !== $offset) { + $char = $string[$offset]; + ++$char; + $string[$offset] = $char; + ++$string; + + switch ($string[$offset]) { + case 'f': + $string[$offset] = 'e'; + break; + case 'F': + $string[$offset] = 'E'; + break; + case 'g': + $string[$offset] = 'f'; + break; + case 'G': + $string[$offset] = 'F'; + break; + } + + return $string; + } + } + + return ++$string; + } + + public static function str_decrement(string $string): string + { + if ('' === $string) { + throw new \ValueError('str_decrement(): Argument #1 ($string) cannot be empty'); + } + + if (!preg_match('/^[a-zA-Z0-9]+$/', $string)) { + throw new \ValueError('str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters'); + } + + if (preg_match('/\A(?:0[aA0]?|[aA])\z/', $string)) { + throw new \ValueError(sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string)); + } + + if (!\in_array(substr($string, -1), ['A', 'a', '0'], true)) { + return implode('', \array_slice(str_split($string), 0, -1)).\chr(\ord(substr($string, -1)) - 1); + } + + $carry = ''; + $decremented = ''; + + for ($i = \strlen($string) - 1; $i >= 0; --$i) { + $char = $string[$i]; + + switch ($char) { + case 'A': + if ('' !== $carry) { + $decremented = $carry.$decremented; + $carry = ''; + } + $carry = 'Z'; + + break; + case 'a': + if ('' !== $carry) { + $decremented = $carry.$decremented; + $carry = ''; + } + $carry = 'z'; + + break; + case '0': + if ('' !== $carry) { + $decremented = $carry.$decremented; + $carry = ''; + } + $carry = '9'; + + break; + case '1': + if ('' !== $carry) { + $decremented = $carry.$decremented; + $carry = ''; + } + + break; + default: + if ('' !== $carry) { + $decremented = $carry.$decremented; + $carry = ''; + } + + if (!\in_array($char, ['A', 'a', '0'], true)) { + $decremented = \chr(\ord($char) - 1).$decremented; + } + } + } + + return $decremented; + } +} diff --git a/vendor/symfony/polyfill-php83/README.md b/vendor/symfony/polyfill-php83/README.md new file mode 100644 index 0000000..f298776 --- /dev/null +++ b/vendor/symfony/polyfill-php83/README.md @@ -0,0 +1,22 @@ +Symfony Polyfill / Php83 +======================== + +This component provides features added to PHP 8.3 core: + +- [`json_validate`](https://wiki.php.net/rfc/json_validate) +- [`Override`](https://wiki.php.net/rfc/marking_overriden_methods) +- [`mb_str_pad`](https://wiki.php.net/rfc/mb_str_pad) +- [`ldap_exop_sync`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures) +- [`ldap_connect_wallet`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures) +- [`stream_context_set_options`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures) +- [`str_increment` and `str_decrement`](https://wiki.php.net/rfc/saner-inc-dec-operators) +- [`Date*Exception/Error classes`](https://wiki.php.net/rfc/datetime-exceptions) +- [`SQLite3Exception`](https://wiki.php.net/rfc/sqlite3_exceptions) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php new file mode 100644 index 0000000..6e7ed8c --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php new file mode 100644 index 0000000..041710a --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateException extends Exception + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php new file mode 100644 index 0000000..e2e9dfc --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateInvalidOperationException extends DateException + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php new file mode 100644 index 0000000..75bcd26 --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateInvalidTimeZoneException extends DateException + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php new file mode 100644 index 0000000..af91b8e --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateMalformedIntervalStringException extends DateException + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php new file mode 100644 index 0000000..9b6d276 --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateMalformedPeriodStringException extends DateException + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php new file mode 100644 index 0000000..7ad0484 --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateMalformedStringException extends DateException + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php new file mode 100644 index 0000000..11f0edc --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateObjectError extends DateError + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php b/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php new file mode 100644 index 0000000..98e6703 --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateRangeError extends DateError + { + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/Override.php b/vendor/symfony/polyfill-php83/Resources/stubs/Override.php new file mode 100644 index 0000000..d3e6b3e --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/Override.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + #[Attribute(Attribute::TARGET_METHOD)] + final class Override + { + public function __construct() + { + } + } +} diff --git a/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php b/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php new file mode 100644 index 0000000..ecb7c98 --- /dev/null +++ b/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class SQLite3Exception extends Exception + { + } +} diff --git a/vendor/symfony/polyfill-php83/bootstrap.php b/vendor/symfony/polyfill-php83/bootstrap.php new file mode 100644 index 0000000..a92799c --- /dev/null +++ b/vendor/symfony/polyfill-php83/bootstrap.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php83 as p; + +if (\PHP_VERSION_ID >= 80300) { + return; +} + +if (!function_exists('json_validate')) { + function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); } +} + +if (extension_loaded('mbstring')) { + if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } + } +} + +if (!function_exists('stream_context_set_options')) { + function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); } +} + +if (!function_exists('str_increment')) { + function str_increment(string $string): string { return p\Php83::str_increment($string); } +} + +if (!function_exists('str_decrement')) { + function str_decrement(string $string): string { return p\Php83::str_decrement($string); } +} + +if (\PHP_VERSION_ID >= 80100) { + return require __DIR__.'/bootstrap81.php'; +} + +if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) { + function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); } +} + +if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) { + function ldap_connect_wallet(?string $uri, string $wallet, string $password, int $auth_mode = \GSLC_SSL_NO_AUTH) { return ldap_connect($uri, $wallet, $password, $auth_mode); } +} diff --git a/vendor/symfony/polyfill-php83/bootstrap81.php b/vendor/symfony/polyfill-php83/bootstrap81.php new file mode 100644 index 0000000..68395b4 --- /dev/null +++ b/vendor/symfony/polyfill-php83/bootstrap81.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID >= 80300) { + return; +} + +if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) { + function ldap_exop_sync(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); } +} + +if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) { + function ldap_connect_wallet(?string $uri, string $wallet, #[\SensitiveParameter] string $password, int $auth_mode = \GSLC_SSL_NO_AUTH): \LDAP\Connection|false { return ldap_connect($uri, $wallet, $password, $auth_mode); } +} diff --git a/vendor/symfony/polyfill-php83/composer.json b/vendor/symfony/polyfill-php83/composer.json new file mode 100644 index 0000000..a8b8ba7 --- /dev/null +++ b/vendor/symfony/polyfill-php83/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php83", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php83\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md new file mode 100644 index 0000000..f7b68b5 --- /dev/null +++ b/vendor/symfony/process/CHANGELOG.md @@ -0,0 +1,129 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `Process::setIgnoredSignals()` to disable signal propagation to the child process + +6.4 +--- + + * Add `PhpSubprocess` to handle PHP subprocesses that take over the + configuration from their parent + * Add `RunProcessMessage` and `RunProcessMessageHandler` + * Support using `Process::findExecutable()` independently of `open_basedir` + +5.2.0 +----- + + * added `Process::setOptions()` to set `Process` specific options + * added option `create_new_console` to allow a subprocess to continue + to run after the main script exited, both on Linux and on Windows + +5.1.0 +----- + + * added `Process::getStartTime()` to retrieve the start time of the process as float + +5.0.0 +----- + + * removed `Process::inheritEnvironmentVariables()` + * removed `PhpProcess::setPhpBinary()` + * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell + * removed `Process::setCommandLine()` + +4.4.0 +----- + + * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited. + * added `Process::getLastOutputTime()` method + +4.2.0 +----- + + * added the `Process::fromShellCommandline()` to run commands in a shell wrapper + * deprecated passing a command as string when creating a `Process` instance + * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods + * added the `Process::waitUntil()` method to wait for the process only for a + specific output, then continue the normal execution of your application + +4.1.0 +----- + + * added the `Process::isTtySupported()` method that allows to check for TTY support + * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary + * added the `ProcessSignaledException` class to properly catch signaled process errors + +4.0.0 +----- + + * environment variables will always be inherited + * added a second `array $env = []` argument to the `start()`, `run()`, + `mustRun()`, and `restart()` methods of the `Process` class + * added a second `array $env = []` argument to the `start()` method of the + `PhpProcess` class + * the `ProcessUtils::escapeArgument()` method has been removed + * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()` + methods of the `Process` class have been removed + * support for passing `proc_open()` options has been removed + * removed the `ProcessBuilder` class, use the `Process` class instead + * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class + * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not + supported anymore + +3.4.0 +----- + + * deprecated the ProcessBuilder class + * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor) + +3.3.0 +----- + + * added command line arrays in the `Process` class + * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods + * deprecated the `ProcessUtils::escapeArgument()` method + * deprecated not inheriting environment variables + * deprecated configuring `proc_open()` options + * deprecated configuring enhanced Windows compatibility + * deprecated configuring enhanced sigchild compatibility + +2.5.0 +----- + + * added support for PTY mode + * added the convenience method "mustRun" + * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() + * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() + * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types + +2.4.0 +----- + + * added the ability to define an idle timeout + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 0000000..bd4a604 --- /dev/null +++ b/vendor/symfony/process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..926ee21 --- /dev/null +++ b/vendor/symfony/process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php new file mode 100644 index 0000000..be3d490 --- /dev/null +++ b/vendor/symfony/process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 0000000..499809e --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessFailedException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private Process $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getWorkingDirectory() + ); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getOutput(), + $process->getErrorOutput() + ); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess(): Process + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Exception/ProcessSignaledException.php b/vendor/symfony/process/Exception/ProcessSignaledException.php new file mode 100644 index 0000000..0fed8ac --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessSignaledException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process has been signaled. + * + * @author Sullivan Senechal + */ +final class ProcessSignaledException extends RuntimeException +{ + private Process $process; + + public function __construct(Process $process) + { + $this->process = $process; + + parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal())); + } + + public function getProcess(): Process + { + return $this->process; + } + + public function getSignal(): int + { + return $this->getProcess()->getTermSignal(); + } +} diff --git a/vendor/symfony/process/Exception/ProcessStartFailedException.php b/vendor/symfony/process/Exception/ProcessStartFailedException.php new file mode 100644 index 0000000..9bd5a03 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessStartFailedException.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for processes failed during startup. + */ +class ProcessStartFailedException extends ProcessFailedException +{ + private Process $process; + + public function __construct(Process $process, ?string $message) + { + if ($process->isStarted()) { + throw new InvalidArgumentException('Expected a process that failed during startup, but the given process was started successfully.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nWorking directory: %s\n\nError: %s", + $process->getCommandLine(), + $process->getWorkingDirectory(), + $message ?? 'unknown' + ); + + // Skip parent constructor + RuntimeException::__construct($error); + + $this->process = $process; + } + + public function getProcess(): Process + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 0000000..252e111 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process times out. + * + * @author Johannes M. Schmitt + */ +class ProcessTimedOutException extends RuntimeException +{ + public const TYPE_GENERAL = 1; + public const TYPE_IDLE = 2; + + private Process $process; + private int $timeoutType; + + public function __construct(Process $process, int $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf( + 'The process "%s" exceeded the timeout of %s seconds.', + $process->getCommandLine(), + $this->getExceededTimeout() + )); + } + + public function getProcess(): Process + { + return $this->process; + } + + public function isGeneralTimeout(): bool + { + return self::TYPE_GENERAL === $this->timeoutType; + } + + public function isIdleTimeout(): bool + { + return self::TYPE_IDLE === $this->timeoutType; + } + + public function getExceededTimeout(): ?float + { + return match ($this->timeoutType) { + self::TYPE_GENERAL => $this->process->getTimeout(), + self::TYPE_IDLE => $this->process->getIdleTimeout(), + default => throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)), + }; + } +} diff --git a/vendor/symfony/process/Exception/RunProcessFailedException.php b/vendor/symfony/process/Exception/RunProcessFailedException.php new file mode 100644 index 0000000..e7219d3 --- /dev/null +++ b/vendor/symfony/process/Exception/RunProcessFailedException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Messenger\RunProcessContext; + +/** + * @author Kevin Bond + */ +final class RunProcessFailedException extends RuntimeException +{ + public function __construct(ProcessFailedException $exception, public readonly RunProcessContext $context) + { + parent::__construct($exception->getMessage(), $exception->getCode()); + } +} diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php new file mode 100644 index 0000000..adead25 --- /dev/null +++ b/vendor/symfony/process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php new file mode 100644 index 0000000..ceb7a55 --- /dev/null +++ b/vendor/symfony/process/ExecutableFinder.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private array $suffixes = ['.exe', '.bat', '.cmd', '.com']; + + /** + * Replaces default suffixes of executable. + */ + public function setSuffixes(array $suffixes): void + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + */ + public function addSuffix(string $suffix): void + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string|null $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + */ + public function find(string $name, ?string $default = null, array $extraDirs = []): ?string + { + $dirs = array_merge( + explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + + $suffixes = ['']; + if ('\\' === \DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { + return $file; + } + + if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) { + return $dir; + } + } + } + + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; + if (\function_exists('exec') && ($executablePath = strtok(@exec($command.' '.escapeshellarg($name)), \PHP_EOL)) && @is_executable($executablePath)) { + return $executablePath; + } + + return $default; + } +} diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php new file mode 100644 index 0000000..cd91029 --- /dev/null +++ b/vendor/symfony/process/InputStream.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Provides a way to continuously write to the input of a Process until the InputStream is closed. + * + * @author Nicolas Grekas + * + * @implements \IteratorAggregate + */ +class InputStream implements \IteratorAggregate +{ + private ?\Closure $onEmpty = null; + private array $input = []; + private bool $open = true; + + /** + * Sets a callback that is called when the write buffer becomes empty. + */ + public function onEmpty(?callable $onEmpty = null): void + { + $this->onEmpty = null !== $onEmpty ? $onEmpty(...) : null; + } + + /** + * Appends an input to the write buffer. + * + * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar, + * stream resource or \Traversable + */ + public function write(mixed $input): void + { + if (null === $input) { + return; + } + if ($this->isClosed()) { + throw new RuntimeException(sprintf('"%s" is closed.', static::class)); + } + $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); + } + + /** + * Closes the write buffer. + */ + public function close(): void + { + $this->open = false; + } + + /** + * Tells whether the write buffer is closed or not. + */ + public function isClosed(): bool + { + return !$this->open; + } + + public function getIterator(): \Traversable + { + $this->open = true; + + while ($this->open || $this->input) { + if (!$this->input) { + yield ''; + continue; + } + $current = array_shift($this->input); + + if ($current instanceof \Iterator) { + yield from $current; + } else { + yield $current; + } + if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { + $this->write($onEmpty($this)); + } + } + } +} diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/process/Messenger/RunProcessContext.php b/vendor/symfony/process/Messenger/RunProcessContext.php new file mode 100644 index 0000000..5e22304 --- /dev/null +++ b/vendor/symfony/process/Messenger/RunProcessContext.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +use Symfony\Component\Process\Process; + +/** + * @author Kevin Bond + */ +final class RunProcessContext +{ + public readonly ?int $exitCode; + public readonly ?string $output; + public readonly ?string $errorOutput; + + public function __construct( + public readonly RunProcessMessage $message, + Process $process, + ) { + $this->exitCode = $process->getExitCode(); + $this->output = !$process->isStarted() || $process->isOutputDisabled() ? null : $process->getOutput(); + $this->errorOutput = !$process->isStarted() || $process->isOutputDisabled() ? null : $process->getErrorOutput(); + } +} diff --git a/vendor/symfony/process/Messenger/RunProcessMessage.php b/vendor/symfony/process/Messenger/RunProcessMessage.php new file mode 100644 index 0000000..b2c33fe --- /dev/null +++ b/vendor/symfony/process/Messenger/RunProcessMessage.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +/** + * @author Kevin Bond + */ +class RunProcessMessage implements \Stringable +{ + public function __construct( + public readonly array $command, + public readonly ?string $cwd = null, + public readonly ?array $env = null, + public readonly mixed $input = null, + public readonly ?float $timeout = 60.0, + ) { + } + + public function __toString(): string + { + return implode(' ', $this->command); + } +} diff --git a/vendor/symfony/process/Messenger/RunProcessMessageHandler.php b/vendor/symfony/process/Messenger/RunProcessMessageHandler.php new file mode 100644 index 0000000..41c1934 --- /dev/null +++ b/vendor/symfony/process/Messenger/RunProcessMessageHandler.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\RunProcessFailedException; +use Symfony\Component\Process\Process; + +/** + * @author Kevin Bond + */ +final class RunProcessMessageHandler +{ + public function __invoke(RunProcessMessage $message): RunProcessContext + { + $process = new Process($message->command, $message->cwd, $message->env, $message->input, $message->timeout); + + try { + return new RunProcessContext($message, $process->mustRun()); + } catch (ProcessFailedException $e) { + throw new RunProcessFailedException($e, new RunProcessContext($message, $e->getProcess())); + } + } +} diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php new file mode 100644 index 0000000..4a882e0 --- /dev/null +++ b/vendor/symfony/process/PhpExecutableFinder.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private ExecutableFinder $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + */ + public function find(bool $includeArgs = true): string|false + { + if ($php = getenv('PHP_BINARY')) { + if (!is_executable($php)) { + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; + if (\function_exists('exec') && $php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { + if (!is_executable($php)) { + return false; + } + } else { + return false; + } + } + + if (@is_dir($php)) { + return false; + } + + return $php; + } + + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + + // PHP_BINARY return the current sapi executable + if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) { + return \PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!@is_executable($php) || @is_dir($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (@is_executable($php) && !@is_dir($php)) { + return $php; + } + } + + if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) { + return $php; + } + + $dirs = [\PHP_BINDIR]; + if ('\\' === \DIRECTORY_SEPARATOR) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } + + /** + * Finds the PHP executable arguments. + */ + public function findArguments(): array + { + $arguments = []; + if ('phpdbg' === \PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php new file mode 100644 index 0000000..01d8895 --- /dev/null +++ b/vendor/symfony/process/PhpProcess.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + */ +class PhpProcess extends Process +{ + /** + * @param string $script The PHP script to run (as a string) + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array|null $php Path to the PHP binary to use with any additional arguments + */ + public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) + { + if (null === $php) { + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); + } + if ('phpdbg' === \PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php[] = $file; + $script = null; + } + + parent::__construct($php, $cwd, $env, $script, $timeout); + } + + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + public function start(?callable $callback = null, array $env = []): void + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } +} diff --git a/vendor/symfony/process/PhpSubprocess.php b/vendor/symfony/process/PhpSubprocess.php new file mode 100644 index 0000000..a97f8b2 --- /dev/null +++ b/vendor/symfony/process/PhpSubprocess.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpSubprocess runs a PHP command as a subprocess while keeping the original php.ini settings. + * + * For this, it generates a temporary php.ini file taking over all the current settings and disables + * loading additional .ini files. Basically, your command gets prefixed using "php -n -c /tmp/temp.ini". + * + * Given your php.ini contains "memory_limit=-1" and you have a "MemoryTest.php" with the following content: + * + * run(); + * print $p->getOutput()."\n"; + * + * This will output "string(2) "-1", because the process is started with the default php.ini settings. + * + * $p = new PhpSubprocess(['MemoryTest.php'], null, null, 60, ['php', '-d', 'memory_limit=256M']); + * $p->run(); + * print $p->getOutput()."\n"; + * + * This will output "string(4) "256M"", because the process is started with the temporarily created php.ini settings. + * + * @author Yanick Witschi + * @author Partially copied and heavily inspired from composer/xdebug-handler by John Stevenson + */ +class PhpSubprocess extends Process +{ + /** + * @param array $command The command to run and its arguments listed as separate entries. They will automatically + * get prefixed with the PHP binary + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array|null $php Path to the PHP binary to use with any additional arguments + */ + public function __construct(array $command, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) + { + if (null === $php) { + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); + } + + if (null === $php) { + throw new RuntimeException('Unable to find PHP binary.'); + } + + $tmpIni = $this->writeTmpIni($this->getAllIniFiles(), sys_get_temp_dir()); + + $php = array_merge($php, ['-n', '-c', $tmpIni]); + register_shutdown_function('unlink', $tmpIni); + + $command = array_merge($php, $command); + + parent::__construct($command, $cwd, $env, null, $timeout); + } + + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + public function start(?callable $callback = null, array $env = []): void + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } + + private function writeTmpIni(array $iniFiles, string $tmpDir): string + { + if (false === $tmpfile = @tempnam($tmpDir, '')) { + throw new RuntimeException('Unable to create temporary ini file.'); + } + + // $iniFiles has at least one item and it may be empty + if ('' === $iniFiles[0]) { + array_shift($iniFiles); + } + + $content = ''; + + foreach ($iniFiles as $file) { + // Check for inaccessible ini files + if (($data = @file_get_contents($file)) === false) { + throw new RuntimeException('Unable to read ini: '.$file); + } + // Check and remove directives after HOST and PATH sections + if (preg_match('/^\s*\[(?:PATH|HOST)\s*=/mi', $data, $matches)) { + $data = substr($data, 0, $matches[0][1]); + } + + $content .= $data."\n"; + } + + // Merge loaded settings into our ini content, if it is valid + $config = parse_ini_string($content); + $loaded = ini_get_all(null, false); + + if (false === $config || false === $loaded) { + throw new RuntimeException('Unable to parse ini data.'); + } + + $content .= $this->mergeLoadedConfig($loaded, $config); + + // Work-around for https://bugs.php.net/bug.php?id=75932 + $content .= "opcache.enable_cli=0\n"; + + if (false === @file_put_contents($tmpfile, $content)) { + throw new RuntimeException('Unable to write temporary ini file.'); + } + + return $tmpfile; + } + + private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string + { + $content = ''; + + foreach ($loadedConfig as $name => $value) { + if (!\is_string($value)) { + continue; + } + + if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { + // Double-quote escape each value + $content .= $name.'="'.addcslashes($value, '\\"')."\"\n"; + } + } + + return $content; + } + + private function getAllIniFiles(): array + { + $paths = [(string) php_ini_loaded_file()]; + + if (false !== $scanned = php_ini_scanned_files()) { + $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); + } + + return $paths; + } +} diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 0000000..cbbb727 --- /dev/null +++ b/vendor/symfony/process/Pipes/AbstractPipes.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * @author Romain Neutron + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + public array $pipes = []; + + private string $inputBuffer = ''; + /** @var resource|string|\Iterator */ + private $input; + private bool $blocked = true; + private ?string $lastError = null; + + /** + * @param resource|string|\Iterator $input + */ + public function __construct($input) + { + if (\is_resource($input) || $input instanceof \Iterator) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function close(): void + { + foreach ($this->pipes as $pipe) { + if (\is_resource($pipe)) { + fclose($pipe); + } + } + $this->pipes = []; + } + + /** + * Returns true if a system call has been interrupted. + */ + protected function hasSystemCallBeenInterrupted(): bool + { + $lastError = $this->lastError; + $this->lastError = null; + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); + } + + /** + * Unblocks streams. + */ + protected function unblock(): void + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (\is_resource($this->input)) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } + + /** + * Writes input to stdin. + * + * @throws InvalidArgumentException When an input iterator yields a non supported value + */ + protected function write(): ?array + { + if (!isset($this->pipes[0])) { + return null; + } + $input = $this->input; + + if ($input instanceof \Iterator) { + if (!$input->valid()) { + $input = null; + } elseif (\is_resource($input = $input->current())) { + stream_set_blocking($input, 0); + } elseif (!isset($this->inputBuffer[0])) { + if (!\is_string($input)) { + if (!\is_scalar($input)) { + throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input))); + } + $input = (string) $input; + } + $this->inputBuffer = $input; + $this->input->next(); + $input = null; + } else { + $input = null; + } + } + + $r = $e = []; + $w = [$this->pipes[0]]; + + // let's have a look if something changed in streams + if (false === @stream_select($r, $w, $e, 0, 0)) { + return null; + } + + foreach ($w as $stdin) { + if (isset($this->inputBuffer[0])) { + $written = fwrite($stdin, $this->inputBuffer); + $this->inputBuffer = substr($this->inputBuffer, $written); + if (isset($this->inputBuffer[0])) { + return [$this->pipes[0]]; + } + } + + if ($input) { + while (true) { + $data = fread($input, self::CHUNK_SIZE); + if (!isset($data[0])) { + break; + } + $written = fwrite($stdin, $data); + $data = substr($data, $written); + if (isset($data[0])) { + $this->inputBuffer = $data; + + return [$this->pipes[0]]; + } + } + if (feof($input)) { + if ($this->input instanceof \Iterator) { + $this->input->next(); + } else { + $this->input = null; + } + } + } + } + + // no input to read on resource, buffer is empty + if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { + $this->input = null; + fclose($this->pipes[0]); + unset($this->pipes[0]); + } elseif (!$w) { + return [$this->pipes[0]]; + } + + return null; + } + + /** + * @internal + */ + public function handleError(int $type, string $msg): void + { + $this->lastError = $msg; + } +} diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 0000000..967f8de --- /dev/null +++ b/vendor/symfony/process/Pipes/PipesInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +/** + * PipesInterface manages descriptors and pipes for the use of proc_open. + * + * @author Romain Neutron + * + * @internal + */ +interface PipesInterface +{ + public const CHUNK_SIZE = 16384; + + /** + * Returns an array of descriptors for the use of proc_open. + */ + public function getDescriptors(): array; + + /** + * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. + * + * @return string[] + */ + public function getFiles(): array; + + /** + * Reads data in file handles and pipes. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close pipes if they've reached EOF + * + * @return string[] An array of read data indexed by their fd + */ + public function readAndWrite(bool $blocking, bool $close = false): array; + + /** + * Returns if the current state has open file handles or pipes. + */ + public function areOpen(): bool; + + /** + * Returns if pipes are able to read output. + */ + public function haveReadSupport(): bool; + + /** + * Closes file handles and pipes. + */ + public function close(): void; +} diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 0000000..7bd0db0 --- /dev/null +++ b/vendor/symfony/process/Pipes/UnixPipes.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + private ?bool $ttyMode; + private bool $ptyMode; + private bool $haveReadSupport; + + public function __construct(?bool $ttyMode, bool $ptyMode, mixed $input, bool $haveReadSupport) + { + $this->ttyMode = $ttyMode; + $this->ptyMode = $ptyMode; + $this->haveReadSupport = $haveReadSupport; + + parent::__construct($input); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + public function getFiles(): array + { + return []; + } + + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + + $read = $e = []; + $r = $this->pipes; + unset($r[0]); + + // let's have a look if something changed in streams + set_error_handler($this->handleError(...)); + if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + restore_error_handler(); + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + restore_error_handler(); + + foreach ($r as $pipe) { + // prior PHP 5.4 the array passed to stream_select is modified and + // lose key association, we have to find back the key + $read[$type = array_search($pipe, $this->pipes, true)] = ''; + + do { + $data = @fread($pipe, self::CHUNK_SIZE); + $read[$type] .= $data; + } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); + + if (!isset($read[$type][0])) { + unset($read[$type]); + } + + if ($close && feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + + return $read; + } + + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + public function areOpen(): bool + { + return (bool) $this->pipes; + } +} diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 0000000..8033442 --- /dev/null +++ b/vendor/symfony/process/Pipes/WindowsPipes.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Process; + +/** + * WindowsPipes implementation uses temporary files as handles. + * + * @see https://bugs.php.net/51800 + * @see https://bugs.php.net/65650 + * + * @author Romain Neutron + * + * @internal + */ +class WindowsPipes extends AbstractPipes +{ + private array $files = []; + private array $fileHandles = []; + private array $lockHandles = []; + private array $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + private bool $haveReadSupport; + + public function __construct(mixed $input, bool $haveReadSupport) + { + $this->haveReadSupport = $haveReadSupport; + + if ($this->haveReadSupport) { + // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + // Workaround for this problem is to use temporary files instead of pipes on Windows platform. + // + // @see https://bugs.php.net/51800 + $pipes = [ + Process::STDOUT => Process::OUT, + Process::STDERR => Process::ERR, + ]; + $tmpDir = sys_get_temp_dir(); + $lastError = 'unknown reason'; + set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); + for ($i = 0;; ++$i) { + foreach ($pipes as $pipe => $name) { + $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); + + if (!$h = fopen($file.'.lock', 'w')) { + if (file_exists($file.'.lock')) { + continue 2; + } + restore_error_handler(); + throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError); + } + if (!flock($h, \LOCK_EX | \LOCK_NB)) { + continue 2; + } + if (isset($this->lockHandles[$pipe])) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + } + $this->lockHandles[$pipe] = $h; + + if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + unset($this->lockHandles[$pipe]); + continue 2; + } + $this->fileHandles[$pipe] = $h; + $this->files[$pipe] = $file; + } + break; + } + restore_error_handler(); + } + + parent::__construct($input); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) + // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 + // So we redirect output within the commandline and pass the nul device to the process + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + public function getFiles(): array + { + return $this->files; + } + + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + $read = $r = $e = []; + + if ($blocking) { + if ($w) { + @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); + } elseif ($this->fileHandles) { + usleep((int) (Process::TIMEOUT_PRECISION * 1E6)); + } + } + foreach ($this->fileHandles as $type => $fileHandle) { + $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); + + if (isset($data[0])) { + $this->readBytes[$type] += \strlen($data); + $read[$type] = $data; + } + if ($close) { + ftruncate($fileHandle, 0); + fclose($fileHandle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + unset($this->fileHandles[$type], $this->lockHandles[$type]); + } + } + + return $read; + } + + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + public function areOpen(): bool + { + return $this->pipes && $this->fileHandles; + } + + public function close(): void + { + parent::close(); + foreach ($this->fileHandles as $type => $handle) { + ftruncate($handle, 0); + fclose($handle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + } + $this->fileHandles = $this->lockHandles = []; + } +} diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php new file mode 100644 index 0000000..fd3ad87 --- /dev/null +++ b/vendor/symfony/process/Process.php @@ -0,0 +1,1654 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessSignaledException; +use Symfony\Component\Process\Exception\ProcessStartFailedException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier + * @author Romain Neutron + * + * @implements \IteratorAggregate + */ +class Process implements \IteratorAggregate +{ + public const ERR = 'err'; + public const OUT = 'out'; + + public const STATUS_READY = 'ready'; + public const STATUS_STARTED = 'started'; + public const STATUS_TERMINATED = 'terminated'; + + public const STDIN = 0; + public const STDOUT = 1; + public const STDERR = 2; + + // Timeout Precision in seconds. + public const TIMEOUT_PRECISION = 0.2; + + public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private ?\Closure $callback = null; + private array|string $commandline; + private ?string $cwd; + private array $env = []; + /** @var resource|string|\Iterator|null */ + private $input; + private ?float $starttime = null; + private ?float $lastOutputTime = null; + private ?float $timeout = null; + private ?float $idleTimeout = null; + private ?int $exitcode = null; + private array $fallbackStatus = []; + private array $processInformation; + private bool $outputDisabled = false; + /** @var resource */ + private $stdout; + /** @var resource */ + private $stderr; + /** @var resource|null */ + private $process; + private string $status = self::STATUS_READY; + private int $incrementalOutputOffset = 0; + private int $incrementalErrorOutputOffset = 0; + private bool $tty = false; + private bool $pty; + private array $options = ['suppress_errors' => true, 'bypass_shell' => true]; + private array $ignoredSignals = []; + + private WindowsPipes|UnixPipes $processPipes; + + private ?int $latestSignal = null; + private ?int $cachedExitCode = null; + + private static ?bool $sigchild = null; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + */ + public static array $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * @param array $command The command to run and its arguments listed as separate entries + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @throws LogicException When proc_open is not installed + */ + public function __construct(array $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60) + { + if (!\function_exists('proc_open')) { + throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $command; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/51800 + // @see : https://bugs.php.net/50524 + if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->setInput($input); + $this->setTimeout($timeout); + $this->pty = false; + } + + /** + * Creates a Process instance as a command-line to be run in a shell wrapper. + * + * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.) + * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the + * shell wrapper and not to your commands. + * + * In order to inject dynamic values into command-lines, we strongly recommend using placeholders. + * This will save escaping values, which is not portable nor secure anyway: + * + * $process = Process::fromShellCommandline('my_command "${:MY_VAR}"'); + * $process->run(null, ['MY_VAR' => $theValue]); + * + * @param string $command The command line to pass to the shell of the OS + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @throws LogicException When proc_open is not installed + */ + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static + { + $process = new static([], $cwd, $env, $input, $timeout); + $process->commandline = $command; + + return $process; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if ($this->options['create_new_console'] ?? false) { + $this->processPipes->close(); + } else { + $this->stop(0); + } + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return int The exit status code + * + * @throws ProcessStartFailedException When process can't be launched + * @throws RuntimeException When process is already running + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + * + * @final + */ + public function run(?callable $callback = null, array $env = []): int + { + $this->start($callback, $env); + + return $this->wait(); + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @return $this + * + * @throws ProcessFailedException if the process didn't terminate successfully + * + * @final + */ + public function mustRun(?callable $callback = null, array $env = []): static + { + if (0 !== $this->run($callback, $env)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws ProcessStartFailedException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start(?callable $callback = null, array $env = []): void + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(null !== $callback); + + if ($this->env) { + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env; + } + + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv(); + + if (\is_array($commandline = $this->commandline)) { + $commandline = array_values(array_map(strval(...), $commandline)); + } else { + $commandline = $this->replacePlaceholders($commandline, $env); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $commandline = $this->prepareWindowsCommandLine($commandline, $env); + } elseif ($this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = ['pipe', 'w']; + + if (\is_array($commandline)) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$this->buildShellCommandline($commandline); + } + + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + $envPairs = []; + foreach ($env as $k => $v) { + if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) { + $envPairs[] = $k.'='.$v; + } + } + + if (!is_dir($this->cwd)) { + throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); + } + + $lastError = null; + set_error_handler(function ($type, $msg) use (&$lastError) { + $lastError = $msg; + + return true; + }); + + $oldMask = []; + + if ($this->ignoredSignals && \function_exists('pcntl_sigprocmask')) { + // we block signals we want to ignore, as proc_open will use fork / posix_spawn which will copy the signal mask this allow to block + // signals in the child process + pcntl_sigprocmask(\SIG_BLOCK, $this->ignoredSignals, $oldMask); + } + + try { + $process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); + } finally { + if ($this->ignoredSignals && \function_exists('pcntl_sigprocmask')) { + // we restore the signal mask here to avoid any side effects + pcntl_sigprocmask(\SIG_SETMASK, $oldMask); + } + + restore_error_handler(); + } + + if (!\is_resource($process)) { + throw new ProcessStartFailedException($this, $lastError); + } + $this->process = $process; + $this->status = self::STATUS_STARTED; + + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws ProcessStartFailedException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + * + * @final + */ + public function restart(?callable $callback = null, array $env = []): static + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $process = clone $this; + $process->start($callback, $env); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait(?callable $callback = null): int + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + + if (null !== $callback) { + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".'); + } + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen()); + $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + } while ($running); + + while ($this->isRunning()) { + $this->checkTimeout(); + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new ProcessSignaledException($this); + } + + return $this->exitcode; + } + + /** + * Waits until the callback returns true. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @throws RuntimeException When process timed out + * @throws LogicException When process is not yet started + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function waitUntil(callable $callback): bool + { + $this->requireProcessIsStarted(__FUNCTION__); + $this->updateStatus(false); + + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); + } + $callback = $this->buildCallback($callback); + + $ready = false; + while (true) { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + foreach ($output as $type => $data) { + if (3 !== $type) { + $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready; + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + if ($ready) { + return true; + } + if (!$running) { + return false; + } + + usleep(1000); + } + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + */ + public function getPid(): ?int + { + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * + * @return $this + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + public function signal(int $signal): static + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput(): static + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output cannot be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput(): static + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + */ + public function isOutputDisabled(): bool + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = ftell($this->stdout); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). + * + * @param int $flags A bit field of Process::ITER_* flags + * + * @return \Generator + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIterator(int $flags = 0): \Generator + { + $this->readPipesForOutput(__FUNCTION__, false); + + $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); + $blocking = !(self::ITER_NON_BLOCKING & $flags); + $yieldOut = !(self::ITER_SKIP_OUT & $flags); + $yieldErr = !(self::ITER_SKIP_ERR & $flags); + + while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { + if ($yieldOut) { + $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + + if (isset($out[0])) { + if ($clearOutput) { + $this->clearOutput(); + } else { + $this->incrementalOutputOffset = ftell($this->stdout); + } + + yield self::OUT => $out; + } + } + + if ($yieldErr) { + $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + + if (isset($err[0])) { + if ($clearOutput) { + $this->clearErrorOutput(); + } else { + $this->incrementalErrorOutputOffset = ftell($this->stderr); + } + + yield self::ERR => $err; + } + } + + if (!$blocking && !isset($out[0]) && !isset($err[0])) { + yield self::OUT => ''; + } + + $this->checkTimeout(); + $this->readPipesForOutput(__FUNCTION__, $blocking); + } + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearOutput(): static + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalErrorOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = ftell($this->stderr); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearErrorOutput(): static + { + ftruncate($this->stderr, 0); + fseek($this->stderr, 0); + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return int|null The exit status code, null if the Process is not terminated + */ + public function getExitCode(): ?int + { + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string|null A string representation for the exit status code, null if the Process is not terminated + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText(): ?string + { + if (null === $exitcode = $this->getExitCode()) { + return null; + } + + return self::$exitCodes[$exitcode] ?? 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + */ + public function isSuccessful(): bool + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled(): bool + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal(): int + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped(): bool + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal(): int + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + */ + public function isRunning(): bool + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + */ + public function isStarted(): bool + { + return self::STATUS_READY != $this->status; + } + + /** + * Checks if the process is terminated. + */ + public function isTerminated(): bool + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + */ + public function getStatus(): string + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int|null $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int|null The exit-code of the process or null if it's not running + */ + public function stop(float $timeout = 10, ?int $signal = null): ?int + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + if ($this->isRunning()) { + if (isset($this->fallbackStatus['pid'])) { + unset($this->fallbackStatus['pid']); + + return $this->stop(0, $signal); + } + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @internal + */ + public function addOutput(string $line): void + { + $this->lastOutputTime = microtime(true); + + fseek($this->stdout, 0, \SEEK_END); + fwrite($this->stdout, $line); + fseek($this->stdout, $this->incrementalOutputOffset); + } + + /** + * Adds a line to the STDERR stream. + * + * @internal + */ + public function addErrorOutput(string $line): void + { + $this->lastOutputTime = microtime(true); + + fseek($this->stderr, 0, \SEEK_END); + fwrite($this->stderr, $line); + fseek($this->stderr, $this->incrementalErrorOutputOffset); + } + + /** + * Gets the last output time in seconds. + */ + public function getLastOutputTime(): ?float + { + return $this->lastOutputTime; + } + + /** + * Gets the command line to be executed. + */ + public function getCommandLine(): string + { + return $this->buildShellCommandline($this->commandline); + } + + /** + * Gets the process timeout in seconds (max. runtime). + */ + public function getTimeout(): ?float + { + return $this->timeout; + } + + /** + * Gets the process idle timeout in seconds (max. time since last output). + */ + public function getIdleTimeout(): ?float + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime) in seconds. + * + * To disable the timeout, set this value to null. + * + * @return $this + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout(?float $timeout): static + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output) in seconds. + * + * To disable the timeout, set this value to null. + * + * @return $this + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout(?float $timeout): static + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout cannot be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @return $this + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty(bool $tty): static + { + if ('\\' === \DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + + if ($tty && !self::isTtySupported()) { + throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); + } + + $this->tty = $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + */ + public function isTty(): bool + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @return $this + */ + public function setPty(bool $bool): static + { + $this->pty = $bool; + + return $this; + } + + /** + * Returns PTY state. + */ + public function isPty(): bool + { + return $this->pty; + } + + /** + * Gets the working directory. + */ + public function getWorkingDirectory(): ?string + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @return $this + */ + public function setWorkingDirectory(string $cwd): static + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + */ + public function getEnv(): array + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * @param array $env The new environment variables + * + * @return $this + */ + public function setEnv(array $env): static + { + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param string|resource|\Traversable|self|null $input The content + * + * @return $this + * + * @throws LogicException In case the process is running + */ + public function setInput(mixed $input): static + { + if ($this->isRunning()) { + throw new LogicException('Input cannot be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout(): void + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * @throws LogicException in case process is not started + */ + public function getStartTime(): float + { + if (!$this->isStarted()) { + throw new LogicException('Start time is only available after process start.'); + } + + return $this->starttime; + } + + /** + * Defines options to pass to the underlying proc_open(). + * + * @see https://php.net/proc_open for the options supported by PHP. + * + * Enabling the "create_new_console" option allows a subprocess to continue + * to run after the main process exited, on both Windows and *nix + */ + public function setOptions(array $options): void + { + if ($this->isRunning()) { + throw new RuntimeException('Setting options while the process is running is not possible.'); + } + + $defaultOptions = $this->options; + $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console']; + + foreach ($options as $key => $value) { + if (!\in_array($key, $existingOptions)) { + $this->options = $defaultOptions; + throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions))); + } + $this->options[$key] = $value; + } + } + + /** + * Defines a list of posix signals that will not be propagated to the process. + * + * @param list<\SIG*> $signals + */ + public function setIgnoredSignals(array $signals): void + { + if ($this->isRunning()) { + throw new RuntimeException('Setting ignored signals while the process is running is not possible.'); + } + + $this->ignoredSignals = $signals; + } + + /** + * Returns whether TTY is supported on the current operating system. + */ + public static function isTtySupported(): bool + { + static $isTtySupported; + + return $isTtySupported ??= ('/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT) && @is_writable('/dev/tty')); + } + + /** + * Returns whether PTY is supported on the current operating system. + */ + public static function isPtySupported(): bool + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return $result = false; + } + + return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); + } + + /** + * Creates the descriptors needed by the proc_open. + */ + private function getDescriptors(bool $hasCallback): array + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $hasCallback); + } + + return $this->processPipes->getDescriptors(); + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + */ + protected function buildCallback(?callable $callback = null): \Closure + { + if ($this->outputDisabled) { + return fn ($type, $data): bool => null !== $callback && $callback($type, $data); + } + + $out = self::OUT; + + return function ($type, $data) use ($callback, $out): bool { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + return null !== $callback && $callback($type, $data); + }; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call + */ + protected function updateStatus(bool $blocking): void + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + // In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call. + // Subsequent calls return -1 as the process is discarded. This workaround caches the first + // retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior. + if (\PHP_VERSION_ID < 80300) { + if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) { + $this->cachedExitCode = $this->processInformation['exitcode']; + } + + if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) { + $this->processInformation['exitcode'] = $this->cachedExitCode; + } + } + + $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + if ($this->fallbackStatus && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + + if (!$running) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + */ + protected function isSigchildEnabled(): bool + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(\INFO_GENERAL); + + return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Reads pipes for the freshest output. + * + * @param string $caller The name of the method that needs fresh outputs + * @param bool $blocking Whether to use blocking calls or not + * + * @throws LogicException in case output has been disabled or process is not started + */ + private function readPipesForOutput(string $caller, bool $blocking = false): void + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted($caller); + + $this->updateStatus($blocking); + } + + /** + * Validates and returns the filtered timeout. + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout(?float $timeout): ?float + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close file handles or not + */ + private function readPipes(bool $blocking, bool $close): void + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 !== $type) { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close(): int + { + $this->processPipes->close(); + if (\is_resource($this->process)) { + proc_close($this->process); + } + $this->exitcode = $this->processInformation['exitcode']; + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } + } + + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData(): void + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = []; + $this->processInformation = []; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + private function doSignal(int $signal, bool $throwException): bool + { + // Signal seems to be send when sigchild is enable, this allow blocking the signal correctly in this case + if ($this->isSigchildEnabled() && \in_array($signal, $this->ignoredSignals)) { + return false; + } + + if (null === $pid = $this->getPid()) { + if ($throwException) { + throw new LogicException('Cannot send signal on a non running process.'); + } + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } else { + if (!$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (\function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { + $ok = false === fgets($pipes[2]); + } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal)); + } + + return false; + } + } + + $this->latestSignal = $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; + + return true; + } + + private function buildShellCommandline(string|array $commandline): string + { + if (\is_string($commandline)) { + return $commandline; + } + + return implode(' ', array_map($this->escapeArgument(...), $commandline)); + } + + private function prepareWindowsCommandLine(string|array $cmd, array &$env): string + { + $cmd = $this->buildShellCommandline($cmd); + $uid = uniqid('', true); + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$env, $uid) { + static $varCount = 0; + static $varCache = []; + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (str_contains($value = $m[1], "\0")) { + $value = str_replace("\0", '?', $value); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"'.$value.'"'; + } + + $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); + $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; + $var = $uid.++$varCount; + + $env[$var] = $value; + + return $varCache[$m[0]] = '!'.$var.'!'; + }, + $cmd + ); + + $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $cmd .= ' '.$offset.'>"'.$filename.'"'; + } + + return $cmd; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @throws LogicException if the process has not run + */ + private function requireProcessIsStarted(string $functionName): void + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated". + * + * @throws LogicException if the process is not yet terminated + */ + private function requireProcessIsTerminated(string $functionName): void + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); + } + } + + /** + * Escapes a string to be used as a shell argument. + */ + private function escapeArgument(?string $argument): string + { + if ('' === $argument || null === $argument) { + return '""'; + } + if ('\\' !== \DIRECTORY_SEPARATOR) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + if (str_contains($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); + } + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + return $argument; + } + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"'; + } + + private function replacePlaceholders(string $commandline, array $env): string + { + return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { + if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { + throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline); + } + + return $this->escapeArgument($env[$matches[1]]); + }, $commandline); + } + + private function getDefaultEnv(): array + { + $env = getenv(); + $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env; + + return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env); + } +} diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php new file mode 100644 index 0000000..092c5cc --- /dev/null +++ b/vendor/symfony/process/ProcessUtils.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň + */ +class ProcessUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Validates and normalizes a Process input. + * + * @param string $caller The name of method call that validates the input + * @param mixed $input The input to validate + * + * @throws InvalidArgumentException In case the input is not valid + */ + public static function validateInput(string $caller, mixed $input): mixed + { + if (null !== $input) { + if (\is_resource($input)) { + return $input; + } + if (\is_scalar($input)) { + return (string) $input; + } + if ($input instanceof Process) { + return $input->getIterator($input::ITER_SKIP_ERR); + } + if ($input instanceof \Iterator) { + return $input; + } + if ($input instanceof \Traversable) { + return new \IteratorIterator($input); + } + + throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); + } + + return $input; + } +} diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md new file mode 100644 index 0000000..afce5e4 --- /dev/null +++ b/vendor/symfony/process/README.md @@ -0,0 +1,13 @@ +Process Component +================= + +The Process component executes commands in sub-processes. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/process.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json new file mode 100644 index 0000000..dda5575 --- /dev/null +++ b/vendor/symfony/process/composer.json @@ -0,0 +1,28 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Executes commands in sub-processes", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/property-access/CHANGELOG.md b/vendor/symfony/property-access/CHANGELOG.md new file mode 100644 index 0000000..0dacd60 --- /dev/null +++ b/vendor/symfony/property-access/CHANGELOG.md @@ -0,0 +1,94 @@ +CHANGELOG +========= + +7.0 +--- + + * Add method `isNullSafe()` to `PropertyPathInterface` + * Require explicit argument when calling `PropertyAccessorBuilder::setCacheItemPool()` + +6.3 +--- + + * Allow escaping `.` and `[` with `\` in `PropertyPath` + +6.2 +--- + + * Deprecate calling `PropertyAccessorBuilder::setCacheItemPool()` without arguments + * Added method `isNullSafe()` to `PropertyPathInterface`, implementing the interface without implementing this method + is deprecated + * Add support for the null-coalesce operator in property paths + +6.0 +--- + + * make `PropertyAccessor::__construct()` accept a combination of bitwise flags as first and second arguments + +5.3.0 +----- + + * deprecate passing a boolean as the second argument of `PropertyAccessor::__construct()`, expecting a combination of bitwise flags instead + +5.2.0 +----- + + * deprecated passing a boolean as the first argument of `PropertyAccessor::__construct()`, expecting a combination of bitwise flags instead + * added the ability to disable usage of the magic `__get` & `__set` methods + +5.1.0 +----- + + * Added an `UninitializedPropertyException` + * Linking to PropertyInfo extractor to remove a lot of duplicate code + +4.4.0 +----- + + * deprecated passing `null` as `$defaultLifetime` 2nd argument of `PropertyAccessor::createCache()` method, + pass `0` instead + +4.3.0 +----- + + * added a `$throwExceptionOnInvalidPropertyPath` argument to the PropertyAccessor constructor. + * added `enableExceptionOnInvalidPropertyPath()`, `disableExceptionOnInvalidPropertyPath()` and + `isExceptionOnInvalidPropertyPath()` methods to `PropertyAccessorBuilder` + +4.0.0 +----- + + * removed the `StringUtil` class, use `Symfony\Component\Inflector\Inflector` + +3.1.0 +----- + + * deprecated the `StringUtil` class, use `Symfony\Component\Inflector\Inflector` + instead + +2.7.0 +------ + + * `UnexpectedTypeException` now expects three constructor arguments: The invalid property value, + the `PropertyPathInterface` object and the current index of the property path. + +2.5.0 +------ + + * allowed non alpha numeric characters in second level and deeper object properties names + * [BC BREAK] when accessing an index on an object that does not implement + ArrayAccess, a NoSuchIndexException is now thrown instead of the + semantically wrong NoSuchPropertyException + * [BC BREAK] added isReadable() and isWritable() to PropertyAccessorInterface + +2.3.0 +------ + + * added PropertyAccessorBuilder, to enable or disable the support of "__call" + * added support for "__call" in the PropertyAccessor (disabled by default) + * [BC BREAK] changed PropertyAccessor to continue its search for a property or + method even if a non-public match was found. Before, a PropertyAccessDeniedException + was thrown in this case. Class PropertyAccessDeniedException was removed + now. + * deprecated PropertyAccess::getPropertyAccessor + * added PropertyAccess::createPropertyAccessor and PropertyAccess::createPropertyAccessorBuilder diff --git a/vendor/symfony/property-access/Exception/AccessException.php b/vendor/symfony/property-access/Exception/AccessException.php new file mode 100644 index 0000000..b3a8546 --- /dev/null +++ b/vendor/symfony/property-access/Exception/AccessException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Thrown when a property path is not available. + * + * @author Stéphane Escandell + */ +class AccessException extends RuntimeException +{ +} diff --git a/vendor/symfony/property-access/Exception/ExceptionInterface.php b/vendor/symfony/property-access/Exception/ExceptionInterface.php new file mode 100644 index 0000000..fabf9a0 --- /dev/null +++ b/vendor/symfony/property-access/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Marker interface for the PropertyAccess component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/property-access/Exception/InvalidArgumentException.php b/vendor/symfony/property-access/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..47bc7e1 --- /dev/null +++ b/vendor/symfony/property-access/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Base InvalidArgumentException for the PropertyAccess component. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/property-access/Exception/InvalidPropertyPathException.php b/vendor/symfony/property-access/Exception/InvalidPropertyPathException.php new file mode 100644 index 0000000..69de31c --- /dev/null +++ b/vendor/symfony/property-access/Exception/InvalidPropertyPathException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Thrown when a property path is malformed. + * + * @author Bernhard Schussek + */ +class InvalidPropertyPathException extends RuntimeException +{ +} diff --git a/vendor/symfony/property-access/Exception/InvalidTypeException.php b/vendor/symfony/property-access/Exception/InvalidTypeException.php new file mode 100644 index 0000000..07f6070 --- /dev/null +++ b/vendor/symfony/property-access/Exception/InvalidTypeException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Thrown when a type of given value does not match an expected type. + * + * @author Farhad Safarov + */ +class InvalidTypeException extends InvalidArgumentException +{ + public function __construct( + public readonly string $expectedType, + public readonly string $actualType, + public readonly string $propertyPath, + ?\Throwable $previous = null, + ) { + parent::__construct( + sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), + previous: $previous, + ); + } +} diff --git a/vendor/symfony/property-access/Exception/NoSuchIndexException.php b/vendor/symfony/property-access/Exception/NoSuchIndexException.php new file mode 100644 index 0000000..597b990 --- /dev/null +++ b/vendor/symfony/property-access/Exception/NoSuchIndexException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Thrown when an index cannot be found. + * + * @author Stéphane Escandell + */ +class NoSuchIndexException extends AccessException +{ +} diff --git a/vendor/symfony/property-access/Exception/NoSuchPropertyException.php b/vendor/symfony/property-access/Exception/NoSuchPropertyException.php new file mode 100644 index 0000000..1c7eda5 --- /dev/null +++ b/vendor/symfony/property-access/Exception/NoSuchPropertyException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Thrown when a property cannot be found. + * + * @author Bernhard Schussek + */ +class NoSuchPropertyException extends AccessException +{ +} diff --git a/vendor/symfony/property-access/Exception/OutOfBoundsException.php b/vendor/symfony/property-access/Exception/OutOfBoundsException.php new file mode 100644 index 0000000..a3c4559 --- /dev/null +++ b/vendor/symfony/property-access/Exception/OutOfBoundsException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Base OutOfBoundsException for the PropertyAccess component. + * + * @author Bernhard Schussek + */ +class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/property-access/Exception/RuntimeException.php b/vendor/symfony/property-access/Exception/RuntimeException.php new file mode 100644 index 0000000..9fe843e --- /dev/null +++ b/vendor/symfony/property-access/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Base RuntimeException for the PropertyAccess component. + * + * @author Bernhard Schussek + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/property-access/Exception/UnexpectedTypeException.php b/vendor/symfony/property-access/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..aa59937 --- /dev/null +++ b/vendor/symfony/property-access/Exception/UnexpectedTypeException.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * Thrown when a value does not match an expected type. + * + * @author Bernhard Schussek + */ +class UnexpectedTypeException extends RuntimeException +{ + /** + * @param mixed $value The unexpected value found while traversing property path + * @param int $pathIndex The property path index when the unexpected value was found + */ + public function __construct(mixed $value, PropertyPathInterface $path, int $pathIndex) + { + $message = sprintf( + 'PropertyAccessor requires a graph of objects or arrays to operate on, '. + 'but it found type "%s" while trying to traverse path "%s" at property "%s".', + \gettype($value), + (string) $path, + $path->getElement($pathIndex) + ); + + parent::__construct($message); + } +} diff --git a/vendor/symfony/property-access/Exception/UninitializedPropertyException.php b/vendor/symfony/property-access/Exception/UninitializedPropertyException.php new file mode 100644 index 0000000..c0d6973 --- /dev/null +++ b/vendor/symfony/property-access/Exception/UninitializedPropertyException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Exception; + +/** + * Thrown when a property is not initialized. + * + * @author Jules Pietri + */ +class UninitializedPropertyException extends AccessException +{ +} diff --git a/vendor/symfony/property-access/LICENSE b/vendor/symfony/property-access/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/property-access/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/property-access/PropertyAccess.php b/vendor/symfony/property-access/PropertyAccess.php new file mode 100644 index 0000000..1953ac0 --- /dev/null +++ b/vendor/symfony/property-access/PropertyAccess.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +/** + * Entry point of the PropertyAccess component. + * + * @author Bernhard Schussek + */ +final class PropertyAccess +{ + /** + * Creates a property accessor with the default configuration. + */ + public static function createPropertyAccessor(): PropertyAccessor + { + return self::createPropertyAccessorBuilder()->getPropertyAccessor(); + } + + public static function createPropertyAccessorBuilder(): PropertyAccessorBuilder + { + return new PropertyAccessorBuilder(); + } + + /** + * This class cannot be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/property-access/PropertyAccessor.php b/vendor/symfony/property-access/PropertyAccessor.php new file mode 100644 index 0000000..c1401f4 --- /dev/null +++ b/vendor/symfony/property-access/PropertyAccessor.php @@ -0,0 +1,697 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\PropertyAccess\Exception\AccessException; +use Symfony\Component\PropertyAccess\Exception\InvalidTypeException; +use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; +use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyReadInfo; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfo; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; + +/** + * Default implementation of {@link PropertyAccessorInterface}. + * + * @author Bernhard Schussek + * @author Kévin Dunglas + * @author Nicolas Grekas + */ +class PropertyAccessor implements PropertyAccessorInterface +{ + /** @var int Allow none of the magic methods */ + public const DISALLOW_MAGIC_METHODS = ReflectionExtractor::DISALLOW_MAGIC_METHODS; + /** @var int Allow magic __get methods */ + public const MAGIC_GET = ReflectionExtractor::ALLOW_MAGIC_GET; + /** @var int Allow magic __set methods */ + public const MAGIC_SET = ReflectionExtractor::ALLOW_MAGIC_SET; + /** @var int Allow magic __call methods */ + public const MAGIC_CALL = ReflectionExtractor::ALLOW_MAGIC_CALL; + + public const DO_NOT_THROW = 0; + public const THROW_ON_INVALID_INDEX = 1; + public const THROW_ON_INVALID_PROPERTY_PATH = 2; + + private const VALUE = 0; + private const REF = 1; + private const IS_REF_CHAINED = 2; + private const CACHE_PREFIX_READ = 'r'; + private const CACHE_PREFIX_WRITE = 'w'; + private const CACHE_PREFIX_PROPERTY_PATH = 'p'; + private const RESULT_PROTO = [self::VALUE => null]; + + private bool $ignoreInvalidIndices; + private bool $ignoreInvalidProperty; + private ?CacheItemPoolInterface $cacheItemPool; + private array $propertyPathCache = []; + private PropertyReadInfoExtractorInterface $readInfoExtractor; + private PropertyWriteInfoExtractorInterface $writeInfoExtractor; + private array $readPropertyCache = []; + private array $writePropertyCache = []; + + /** + * Should not be used by application code. Use + * {@link PropertyAccess::createPropertyAccessor()} instead. + * + * @param int $magicMethods A bitwise combination of the MAGIC_* constants + * to specify the allowed magic methods (__get, __set, __call) + * or self::DISALLOW_MAGIC_METHODS for none + * @param int $throw A bitwise combination of the THROW_* constants + * to specify when exceptions should be thrown + */ + public function __construct( + private int $magicMethodsFlags = self::MAGIC_GET | self::MAGIC_SET, + int $throw = self::THROW_ON_INVALID_PROPERTY_PATH, + ?CacheItemPoolInterface $cacheItemPool = null, + ?PropertyReadInfoExtractorInterface $readInfoExtractor = null, + ?PropertyWriteInfoExtractorInterface $writeInfoExtractor = null, + ) { + $this->ignoreInvalidIndices = 0 === ($throw & self::THROW_ON_INVALID_INDEX); + $this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value + $this->ignoreInvalidProperty = 0 === ($throw & self::THROW_ON_INVALID_PROPERTY_PATH); + $this->readInfoExtractor = $readInfoExtractor ?? new ReflectionExtractor([], null, null, false); + $this->writeInfoExtractor = $writeInfoExtractor ?? new ReflectionExtractor(['set'], null, null, false); + } + + public function getValue(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): mixed + { + $zval = [ + self::VALUE => $objectOrArray, + ]; + + if (\is_object($objectOrArray) && (false === strpbrk((string) $propertyPath, '.[?') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray, $propertyPath))) { + return $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty)[self::VALUE]; + } + + $propertyPath = $this->getPropertyPath($propertyPath); + + $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices); + + return $propertyValues[\count($propertyValues) - 1][self::VALUE]; + } + + public function setValue(object|array &$objectOrArray, string|PropertyPathInterface $propertyPath, mixed $value): void + { + if (\is_object($objectOrArray) && (false === strpbrk((string) $propertyPath, '.[') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray, $propertyPath))) { + $zval = [ + self::VALUE => $objectOrArray, + ]; + + try { + $this->writeProperty($zval, $propertyPath, $value); + + return; + } catch (\TypeError $e) { + self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e); + // It wasn't thrown in this class so rethrow it + throw $e; + } + } + + $propertyPath = $this->getPropertyPath($propertyPath); + + $zval = [ + self::VALUE => $objectOrArray, + self::REF => &$objectOrArray, + ]; + $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1); + $overwrite = true; + + try { + for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) { + $zval = $propertyValues[$i]; + unset($propertyValues[$i]); + + // You only need set value for current element if: + // 1. it's the parent of the last index element + // OR + // 2. its child is not passed by reference + // + // This may avoid unnecessary value setting process for array elements. + // For example: + // '[a][b][c]' => 'old-value' + // If you want to change its value to 'new-value', + // you only need set value for '[a][b][c]' and it's safe to ignore '[a][b]' and '[a]' + if ($overwrite) { + $property = $propertyPath->getElement($i); + + if ($propertyPath->isIndex($i)) { + if ($overwrite = !isset($zval[self::REF])) { + $ref = &$zval[self::REF]; + $ref = $zval[self::VALUE]; + } + $this->writeIndex($zval, $property, $value); + if ($overwrite) { + $zval[self::VALUE] = $zval[self::REF]; + } + } else { + $this->writeProperty($zval, $property, $value); + } + + // if current element is an object + // OR + // if current element's reference chain is not broken - current element + // as well as all its ancients in the property path are all passed by reference, + // then there is no need to continue the value setting process + if (\is_object($zval[self::VALUE]) || isset($zval[self::IS_REF_CHAINED])) { + break; + } + } + + $value = $zval[self::VALUE]; + } + } catch (\TypeError $e) { + self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e); + + // It wasn't thrown in this class so rethrow it + throw $e; + } + } + + private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, ?\Throwable $previous = null): void + { + if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) { + return; + } + if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/', $message, $matches)) { + [, $expectedType, $actualType] = $matches; + + throw new InvalidTypeException($expectedType, $actualType, $propertyPath, $previous); + } + if (preg_match('/^Cannot assign (\S+) to property \S+::\$\S+ of type (\S+)$/', $message, $matches)) { + [, $actualType, $expectedType] = $matches; + + throw new InvalidTypeException($expectedType, $actualType, $propertyPath, $previous); + } + } + + public function isReadable(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): bool + { + if (!$propertyPath instanceof PropertyPathInterface) { + $propertyPath = new PropertyPath($propertyPath); + } + + try { + $zval = [ + self::VALUE => $objectOrArray, + ]; + + // handle stdClass with properties with a dot in the name + if ($objectOrArray instanceof \stdClass && str_contains($propertyPath, '.') && property_exists($objectOrArray, $propertyPath)) { + $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty); + } else { + $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices); + } + + return true; + } catch (AccessException|UnexpectedTypeException) { + return false; + } + } + + public function isWritable(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): bool + { + $propertyPath = $this->getPropertyPath($propertyPath); + + try { + $zval = [ + self::VALUE => $objectOrArray, + ]; + + // handle stdClass with properties with a dot in the name + if ($objectOrArray instanceof \stdClass && str_contains($propertyPath, '.') && property_exists($objectOrArray, $propertyPath)) { + $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty); + + return true; + } + + $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1); + + for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) { + $zval = $propertyValues[$i]; + unset($propertyValues[$i]); + + if ($propertyPath->isIndex($i)) { + if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) { + return false; + } + } elseif (!\is_object($zval[self::VALUE]) || !$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) { + return false; + } + + if (\is_object($zval[self::VALUE])) { + return true; + } + } + + return true; + } catch (AccessException|UnexpectedTypeException) { + return false; + } + } + + /** + * Reads the path from an object up to a given path index. + * + * @throws UnexpectedTypeException if a value within the path is neither object nor array + * @throws NoSuchIndexException If a non-existing index is accessed + */ + private function readPropertiesUntil(array $zval, PropertyPathInterface $propertyPath, int $lastIndex, bool $ignoreInvalidIndices = true): array + { + if (!\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) { + throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, 0); + } + + // Add the root object to the list + $propertyValues = [$zval]; + + for ($i = 0; $i < $lastIndex; ++$i) { + $property = $propertyPath->getElement($i); + $isIndex = $propertyPath->isIndex($i); + $isNullSafe = $propertyPath->isNullSafe($i); + + if ($isIndex) { + // Create missing nested arrays on demand + if (($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property)) + || (\is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property, $zval[self::VALUE])) + ) { + if (!$ignoreInvalidIndices && !$isNullSafe) { + if (!\is_array($zval[self::VALUE])) { + if (!$zval[self::VALUE] instanceof \Traversable) { + throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".', $property, (string) $propertyPath)); + } + + $zval[self::VALUE] = iterator_to_array($zval[self::VALUE]); + } + + throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".', $property, (string) $propertyPath, print_r(array_keys($zval[self::VALUE]), true))); + } + + if ($i + 1 < $propertyPath->getLength()) { + if (isset($zval[self::REF])) { + $zval[self::VALUE][$property] = []; + $zval[self::REF] = $zval[self::VALUE]; + } else { + $zval[self::VALUE] = [$property => []]; + } + } + } + + $zval = $this->readIndex($zval, $property); + } elseif ($isNullSafe && !\is_object($zval[self::VALUE])) { + $zval[self::VALUE] = null; + } else { + $zval = $this->readProperty($zval, $property, $this->ignoreInvalidProperty, $isNullSafe); + } + + // the final value of the path must not be validated + if ($i + 1 < $propertyPath->getLength() && !\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE]) && !$isNullSafe) { + throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, $i + 1); + } + + if (isset($zval[self::REF]) && (0 === $i || isset($propertyValues[$i - 1][self::IS_REF_CHAINED]))) { + // Set the IS_REF_CHAINED flag to true if: + // current property is passed by reference and + // it is the first element in the property path or + // the IS_REF_CHAINED flag of its parent element is true + // Basically, this flag is true only when the reference chain from the top element to current element is not broken + $zval[self::IS_REF_CHAINED] = true; + } + + $propertyValues[] = $zval; + + if ($isNullSafe && null === $zval[self::VALUE]) { + break; + } + } + + return $propertyValues; + } + + /** + * Reads a key from an array-like structure. + * + * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array + */ + private function readIndex(array $zval, string|int $index): array + { + if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) { + throw new NoSuchIndexException(sprintf('Cannot read index "%s" from object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, get_debug_type($zval[self::VALUE]))); + } + + $result = self::RESULT_PROTO; + + if (isset($zval[self::VALUE][$index])) { + $result[self::VALUE] = $zval[self::VALUE][$index]; + + if (!isset($zval[self::REF])) { + // Save creating references when doing read-only lookups + } elseif (\is_array($zval[self::VALUE])) { + $result[self::REF] = &$zval[self::REF][$index]; + } elseif (\is_object($result[self::VALUE])) { + $result[self::REF] = $result[self::VALUE]; + } + } + + return $result; + } + + /** + * Reads the value of a property from an object. + * + * @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public + */ + private function readProperty(array $zval, string $property, bool $ignoreInvalidProperty = false, bool $isNullSafe = false): array + { + if (!\is_object($zval[self::VALUE])) { + throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%1$s]" instead.', $property)); + } + + $result = self::RESULT_PROTO; + $object = $zval[self::VALUE]; + $class = $object::class; + $access = $this->getReadInfo($class, $property); + + if (null !== $access) { + $name = $access->getName(); + $type = $access->getType(); + + try { + if (PropertyReadInfo::TYPE_METHOD === $type) { + try { + $result[self::VALUE] = $object->$name(); + } catch (\TypeError $e) { + [$trace] = $e->getTrace(); + + // handle uninitialized properties in PHP >= 7 + if (__FILE__ === ($trace['file'] ?? null) + && $name === $trace['function'] + && $object instanceof $trace['class'] + && preg_match('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/', $e->getMessage(), $matches) + ) { + throw new UninitializedPropertyException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', get_debug_type($object), $name, $matches[1]), 0, $e); + } + + throw $e; + } + } elseif (PropertyReadInfo::TYPE_PROPERTY === $type) { + if (!isset($object->$name) && !\array_key_exists($name, (array) $object)) { + try { + $r = new \ReflectionProperty($class, $name); + + if ($r->isPublic() && !$r->hasType()) { + throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $class, $name)); + } + } catch (\ReflectionException $e) { + if (!$ignoreInvalidProperty) { + throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class)); + } + } + } + + $result[self::VALUE] = $object->$name; + + if (isset($zval[self::REF]) && $access->canBeReference()) { + $result[self::REF] = &$object->$name; + } + } + } catch (\Error $e) { + // handle uninitialized properties in PHP >= 7.4 + if (preg_match('/^Typed property ([\w\\\\@]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches) || preg_match('/^Cannot access uninitialized non-nullable property ([\w\\\\@]+)::\$(\w+) by reference$/', $e->getMessage(), $matches)) { + $r = new \ReflectionProperty(str_contains($matches[1], '@anonymous') ? $class : $matches[1], $matches[2]); + $type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type; + + throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $matches[1], $r->getName(), $type), 0, $e); + } + + throw $e; + } + } elseif (property_exists($object, $property) && \array_key_exists($property, (array) $object)) { + $result[self::VALUE] = $object->$property; + if (isset($zval[self::REF])) { + $result[self::REF] = &$object->$property; + } + } elseif ($isNullSafe) { + $result[self::VALUE] = null; + } elseif (!$ignoreInvalidProperty) { + throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class)); + } + + // Objects are always passed around by reference + if (isset($zval[self::REF]) && \is_object($result[self::VALUE])) { + $result[self::REF] = $result[self::VALUE]; + } + + return $result; + } + + /** + * Guesses how to read the property value. + */ + private function getReadInfo(string $class, string $property): ?PropertyReadInfo + { + $key = str_replace('\\', '.', $class).'..'.$property; + + if (isset($this->readPropertyCache[$key])) { + return $this->readPropertyCache[$key]; + } + + if ($this->cacheItemPool) { + $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.rawurlencode($key)); + if ($item->isHit()) { + return $this->readPropertyCache[$key] = $item->get(); + } + } + + $accessor = $this->readInfoExtractor->getReadInfo($class, $property, [ + 'enable_getter_setter_extraction' => true, + 'enable_magic_methods_extraction' => $this->magicMethodsFlags, + 'enable_constructor_extraction' => false, + ]); + + if (isset($item)) { + $this->cacheItemPool->save($item->set($accessor)); + } + + return $this->readPropertyCache[$key] = $accessor; + } + + /** + * Sets the value of an index in a given array-accessible value. + * + * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array + */ + private function writeIndex(array $zval, string|int $index, mixed $value): void + { + if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) { + throw new NoSuchIndexException(sprintf('Cannot modify index "%s" in object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, get_debug_type($zval[self::VALUE]))); + } + + $zval[self::REF][$index] = $value; + } + + /** + * Sets the value of a property in the given object. + * + * @throws NoSuchPropertyException if the property does not exist or is not public + */ + private function writeProperty(array $zval, string $property, mixed $value, bool $recursive = false): void + { + if (!\is_object($zval[self::VALUE])) { + throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%1$s]" instead?', $property)); + } + + $object = $zval[self::VALUE]; + $class = $object::class; + $mutator = $this->getWriteInfo($class, $property, $value); + + try { + if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) { + $type = $mutator->getType(); + + if (PropertyWriteInfo::TYPE_METHOD === $type) { + $object->{$mutator->getName()}($value); + } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) { + $object->{$mutator->getName()} = $value; + } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) { + $this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo()); + } + } elseif ($object instanceof \stdClass && property_exists($object, $property)) { + $object->$property = $value; + } elseif (!$this->ignoreInvalidProperty) { + if ($mutator->hasErrors()) { + throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()).'.'); + } + + throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_debug_type($object))); + } + } catch (\TypeError $e) { + if ($recursive || !$value instanceof \DateTimeInterface || !\in_array($value::class, ['DateTime', 'DateTimeImmutable'], true) || __FILE__ !== ($e->getTrace()[0]['file'] ?? null)) { + throw $e; + } + + $value = $value instanceof \DateTimeImmutable ? \DateTime::createFromImmutable($value) : \DateTimeImmutable::createFromMutable($value); + try { + $this->writeProperty($zval, $property, $value, true); + } catch (\TypeError) { + throw $e; // throw the previous error + } + } + } + + /** + * Adjusts a collection-valued property by calling add*() and remove*() methods. + */ + private function writeCollection(array $zval, string $property, iterable $collection, PropertyWriteInfo $addMethod, PropertyWriteInfo $removeMethod): void + { + // At this point the add and remove methods have been found + $previousValue = $this->readProperty($zval, $property); + $previousValue = $previousValue[self::VALUE]; + + $removeMethodName = $removeMethod->getName(); + $addMethodName = $addMethod->getName(); + + if ($previousValue instanceof \Traversable) { + $previousValue = iterator_to_array($previousValue); + } + if ($previousValue && \is_array($previousValue)) { + if (\is_object($collection)) { + $collection = iterator_to_array($collection); + } + foreach ($previousValue as $key => $item) { + if (!\in_array($item, $collection, true)) { + unset($previousValue[$key]); + $zval[self::VALUE]->$removeMethodName($item); + } + } + } else { + $previousValue = false; + } + + foreach ($collection as $item) { + if (!$previousValue || !\in_array($item, $previousValue, true)) { + $zval[self::VALUE]->$addMethodName($item); + } + } + } + + private function getWriteInfo(string $class, string $property, mixed $value): PropertyWriteInfo + { + $useAdderAndRemover = is_iterable($value); + $key = str_replace('\\', '.', $class).'..'.$property.'..'.(int) $useAdderAndRemover; + + if (isset($this->writePropertyCache[$key])) { + return $this->writePropertyCache[$key]; + } + + if ($this->cacheItemPool) { + $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.rawurlencode($key)); + if ($item->isHit()) { + return $this->writePropertyCache[$key] = $item->get(); + } + } + + $mutator = $this->writeInfoExtractor->getWriteInfo($class, $property, [ + 'enable_getter_setter_extraction' => true, + 'enable_magic_methods_extraction' => $this->magicMethodsFlags, + 'enable_constructor_extraction' => false, + 'enable_adder_remover_extraction' => $useAdderAndRemover, + ]); + + if (isset($item)) { + $this->cacheItemPool->save($item->set($mutator)); + } + + return $this->writePropertyCache[$key] = $mutator; + } + + /** + * Returns whether a property is writable in the given object. + */ + private function isPropertyWritable(object $object, string $property): bool + { + $mutatorForArray = $this->getWriteInfo($object::class, $property, []); + + if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || ($object instanceof \stdClass && property_exists($object, $property))) { + return true; + } + + $mutator = $this->getWriteInfo($object::class, $property, ''); + + return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || ($object instanceof \stdClass && property_exists($object, $property)); + } + + /** + * Gets a PropertyPath instance and caches it. + */ + private function getPropertyPath(string|PropertyPath $propertyPath): PropertyPath + { + if ($propertyPath instanceof PropertyPathInterface) { + // Don't call the copy constructor has it is not needed here + return $propertyPath; + } + + if (isset($this->propertyPathCache[$propertyPath])) { + return $this->propertyPathCache[$propertyPath]; + } + + if ($this->cacheItemPool) { + $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH.rawurlencode($propertyPath)); + if ($item->isHit()) { + return $this->propertyPathCache[$propertyPath] = $item->get(); + } + } + + $propertyPathInstance = new PropertyPath($propertyPath); + if (isset($item)) { + $item->set($propertyPathInstance); + $this->cacheItemPool->save($item); + } + + return $this->propertyPathCache[$propertyPath] = $propertyPathInstance; + } + + /** + * Creates the APCu adapter if applicable. + * + * @throws \LogicException When the Cache Component isn't available + */ + public static function createCache(string $namespace, int $defaultLifetime, string $version, ?LoggerInterface $logger = null): AdapterInterface + { + if (!class_exists(ApcuAdapter::class)) { + throw new \LogicException(sprintf('The Symfony Cache component must be installed to use "%s()".', __METHOD__)); + } + + if (!ApcuAdapter::isSupported()) { + return new NullAdapter(); + } + + $apcu = new ApcuAdapter($namespace, $defaultLifetime / 5, $version); + if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) { + $apcu->setLogger(new NullLogger()); + } elseif (null !== $logger) { + $apcu->setLogger($logger); + } + + return $apcu; + } +} diff --git a/vendor/symfony/property-access/PropertyAccessorBuilder.php b/vendor/symfony/property-access/PropertyAccessorBuilder.php new file mode 100644 index 0000000..c860485 --- /dev/null +++ b/vendor/symfony/property-access/PropertyAccessorBuilder.php @@ -0,0 +1,291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; + +/** + * A configurable builder to create a PropertyAccessor. + * + * @author Jérémie Augustin + */ +class PropertyAccessorBuilder +{ + private int $magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET; + private bool $throwExceptionOnInvalidIndex = false; + private bool $throwExceptionOnInvalidPropertyPath = true; + private ?CacheItemPoolInterface $cacheItemPool = null; + private ?PropertyReadInfoExtractorInterface $readInfoExtractor = null; + private ?PropertyWriteInfoExtractorInterface $writeInfoExtractor = null; + + /** + * Enables the use of all magic methods by the PropertyAccessor. + * + * @return $this + */ + public function enableMagicMethods(): static + { + $this->magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET | PropertyAccessor::MAGIC_CALL; + + return $this; + } + + /** + * Disable the use of all magic methods by the PropertyAccessor. + * + * @return $this + */ + public function disableMagicMethods(): static + { + $this->magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS; + + return $this; + } + + /** + * Enables the use of "__call" by the PropertyAccessor. + * + * @return $this + */ + public function enableMagicCall(): static + { + $this->magicMethods |= PropertyAccessor::MAGIC_CALL; + + return $this; + } + + /** + * Enables the use of "__get" by the PropertyAccessor. + */ + public function enableMagicGet(): self + { + $this->magicMethods |= PropertyAccessor::MAGIC_GET; + + return $this; + } + + /** + * Enables the use of "__set" by the PropertyAccessor. + * + * @return $this + */ + public function enableMagicSet(): static + { + $this->magicMethods |= PropertyAccessor::MAGIC_SET; + + return $this; + } + + /** + * Disables the use of "__call" by the PropertyAccessor. + * + * @return $this + */ + public function disableMagicCall(): static + { + $this->magicMethods &= ~PropertyAccessor::MAGIC_CALL; + + return $this; + } + + /** + * Disables the use of "__get" by the PropertyAccessor. + * + * @return $this + */ + public function disableMagicGet(): static + { + $this->magicMethods &= ~PropertyAccessor::MAGIC_GET; + + return $this; + } + + /** + * Disables the use of "__set" by the PropertyAccessor. + * + * @return $this + */ + public function disableMagicSet(): static + { + $this->magicMethods &= ~PropertyAccessor::MAGIC_SET; + + return $this; + } + + /** + * @return bool whether the use of "__call" by the PropertyAccessor is enabled + */ + public function isMagicCallEnabled(): bool + { + return (bool) ($this->magicMethods & PropertyAccessor::MAGIC_CALL); + } + + /** + * @return bool whether the use of "__get" by the PropertyAccessor is enabled + */ + public function isMagicGetEnabled(): bool + { + return $this->magicMethods & PropertyAccessor::MAGIC_GET; + } + + /** + * @return bool whether the use of "__set" by the PropertyAccessor is enabled + */ + public function isMagicSetEnabled(): bool + { + return $this->magicMethods & PropertyAccessor::MAGIC_SET; + } + + /** + * Enables exceptions when reading a non-existing index. + * + * This has no influence on writing non-existing indices with PropertyAccessorInterface::setValue() + * which are always created on-the-fly. + * + * @return $this + */ + public function enableExceptionOnInvalidIndex(): static + { + $this->throwExceptionOnInvalidIndex = true; + + return $this; + } + + /** + * Disables exceptions when reading a non-existing index. + * + * Instead, null is returned when calling PropertyAccessorInterface::getValue() on a non-existing index. + * + * @return $this + */ + public function disableExceptionOnInvalidIndex(): static + { + $this->throwExceptionOnInvalidIndex = false; + + return $this; + } + + /** + * @return bool whether an exception is thrown or null is returned when reading a non-existing index + */ + public function isExceptionOnInvalidIndexEnabled(): bool + { + return $this->throwExceptionOnInvalidIndex; + } + + /** + * Enables exceptions when reading a non-existing property. + * + * This has no influence on writing non-existing indices with PropertyAccessorInterface::setValue() + * which are always created on-the-fly. + * + * @return $this + */ + public function enableExceptionOnInvalidPropertyPath(): static + { + $this->throwExceptionOnInvalidPropertyPath = true; + + return $this; + } + + /** + * Disables exceptions when reading a non-existing index. + * + * Instead, null is returned when calling PropertyAccessorInterface::getValue() on a non-existing index. + * + * @return $this + */ + public function disableExceptionOnInvalidPropertyPath(): static + { + $this->throwExceptionOnInvalidPropertyPath = false; + + return $this; + } + + /** + * @return bool whether an exception is thrown or null is returned when reading a non-existing property + */ + public function isExceptionOnInvalidPropertyPath(): bool + { + return $this->throwExceptionOnInvalidPropertyPath; + } + + /** + * Sets a cache system. + * + * @return $this + */ + public function setCacheItemPool(?CacheItemPoolInterface $cacheItemPool): static + { + $this->cacheItemPool = $cacheItemPool; + + return $this; + } + + /** + * Gets the used cache system. + */ + public function getCacheItemPool(): ?CacheItemPoolInterface + { + return $this->cacheItemPool; + } + + /** + * @return $this + */ + public function setReadInfoExtractor(?PropertyReadInfoExtractorInterface $readInfoExtractor): static + { + $this->readInfoExtractor = $readInfoExtractor; + + return $this; + } + + public function getReadInfoExtractor(): ?PropertyReadInfoExtractorInterface + { + return $this->readInfoExtractor; + } + + /** + * @return $this + */ + public function setWriteInfoExtractor(?PropertyWriteInfoExtractorInterface $writeInfoExtractor): static + { + $this->writeInfoExtractor = $writeInfoExtractor; + + return $this; + } + + public function getWriteInfoExtractor(): ?PropertyWriteInfoExtractorInterface + { + return $this->writeInfoExtractor; + } + + /** + * Builds and returns a new PropertyAccessor object. + */ + public function getPropertyAccessor(): PropertyAccessorInterface + { + $throw = PropertyAccessor::DO_NOT_THROW; + + if ($this->throwExceptionOnInvalidIndex) { + $throw |= PropertyAccessor::THROW_ON_INVALID_INDEX; + } + + if ($this->throwExceptionOnInvalidPropertyPath) { + $throw |= PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH; + } + + return new PropertyAccessor($this->magicMethods, $throw, $this->cacheItemPool, $this->readInfoExtractor, $this->writeInfoExtractor); + } +} diff --git a/vendor/symfony/property-access/PropertyAccessorInterface.php b/vendor/symfony/property-access/PropertyAccessorInterface.php new file mode 100644 index 0000000..59f204f --- /dev/null +++ b/vendor/symfony/property-access/PropertyAccessorInterface.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +/** + * Writes and reads values to/from an object/array graph. + * + * @author Bernhard Schussek + */ +interface PropertyAccessorInterface +{ + /** + * Sets the value at the end of the property path of the object graph. + * + * Example: + * + * use Symfony\Component\PropertyAccess\PropertyAccess; + * + * $propertyAccessor = PropertyAccess::createPropertyAccessor(); + * + * echo $propertyAccessor->setValue($object, 'child.name', 'Fabien'); + * // equals echo $object->getChild()->setName('Fabien'); + * + * This method first tries to find a public setter for each property in the + * path. The name of the setter must be the camel-cased property name + * prefixed with "set". + * + * If the setter does not exist, this method tries to find a public + * property. The value of the property is then changed. + * + * If neither is found, an exception is thrown. + * + * @return void + * + * @throws Exception\InvalidArgumentException If the property path is invalid + * @throws Exception\AccessException If a property/index does not exist or is not public + * @throws Exception\UnexpectedTypeException If a value within the path is neither object nor array + */ + public function setValue(object|array &$objectOrArray, string|PropertyPathInterface $propertyPath, mixed $value): void; + + /** + * Returns the value at the end of the property path of the object graph. + * + * Example: + * + * use Symfony\Component\PropertyAccess\PropertyAccess; + * + * $propertyAccessor = PropertyAccess::createPropertyAccessor(); + * + * echo $propertyAccessor->getValue($object, 'child.name'); + * // equals echo $object->getChild()->getName(); + * + * This method first tries to find a public getter for each property in the + * path. The name of the getter must be the camel-cased property name + * prefixed with "get", "is", or "has". + * + * If the getter does not exist, this method tries to find a public + * property. The value of the property is then returned. + * + * If none of them are found, an exception is thrown. + * + * @throws Exception\InvalidArgumentException If the property path is invalid + * @throws Exception\AccessException If a property/index does not exist or is not public + * @throws Exception\UnexpectedTypeException If a value within the path is neither object + * nor array + */ + public function getValue(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): mixed; + + /** + * Returns whether a value can be written at a given property path. + * + * Whenever this method returns true, {@link setValue()} is guaranteed not + * to throw an exception when called with the same arguments. + * + * @throws Exception\InvalidArgumentException If the property path is invalid + */ + public function isWritable(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): bool; + + /** + * Returns whether a property path can be read from an object graph. + * + * Whenever this method returns true, {@link getValue()} is guaranteed not + * to throw an exception when called with the same arguments. + * + * @throws Exception\InvalidArgumentException If the property path is invalid + */ + public function isReadable(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): bool; +} diff --git a/vendor/symfony/property-access/PropertyPath.php b/vendor/symfony/property-access/PropertyPath.php new file mode 100644 index 0000000..a94e960 --- /dev/null +++ b/vendor/symfony/property-access/PropertyPath.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; +use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; +use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException; + +/** + * Default implementation of {@link PropertyPathInterface}. + * + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class PropertyPath implements \IteratorAggregate, PropertyPathInterface +{ + /** + * Character used for separating between plural and singular of an element. + */ + public const SINGULAR_SEPARATOR = '|'; + + /** + * The elements of the property path. + * + * @var list + */ + private array $elements = []; + + /** + * The number of elements in the property path. + */ + private int $length; + + /** + * Contains a Boolean for each property in $elements denoting whether this + * element is an index. It is a property otherwise. + * + * @var array + */ + private array $isIndex = []; + + /** + * Contains a Boolean for each property in $elements denoting whether this + * element is optional or not. + * + * @var array + */ + private array $isNullSafe = []; + + /** + * String representation of the path. + */ + private string $pathAsString; + + /** + * Constructs a property path from a string. + * + * @throws InvalidArgumentException If the given path is not a string + * @throws InvalidPropertyPathException If the syntax of the property path is not valid + */ + public function __construct(self|string $propertyPath) + { + // Can be used as copy constructor + if ($propertyPath instanceof self) { + /* @var PropertyPath $propertyPath */ + $this->elements = $propertyPath->elements; + $this->length = $propertyPath->length; + $this->isIndex = $propertyPath->isIndex; + $this->isNullSafe = $propertyPath->isNullSafe; + $this->pathAsString = $propertyPath->pathAsString; + + return; + } + + if ('' === $propertyPath) { + throw new InvalidPropertyPathException('The property path should not be empty.'); + } + + $this->pathAsString = $propertyPath; + $position = 0; + $remaining = $propertyPath; + + // first element is evaluated differently - no leading dot for properties + $pattern = '/^(((?:[^\\\\.\[]|\\\\.)++)|\[([^\]]++)\])(.*)/'; + + while (preg_match($pattern, $remaining, $matches)) { + if ('' !== $matches[2]) { + $element = $matches[2]; + $this->isIndex[] = false; + } else { + $element = $matches[3]; + $this->isIndex[] = true; + } + + // Mark as optional when last character is "?". + if (str_ends_with($element, '?')) { + $this->isNullSafe[] = true; + $element = substr($element, 0, -1); + } else { + $this->isNullSafe[] = false; + } + + $element = preg_replace('/\\\([.[])/', '$1', $element); + if (str_ends_with($element, '\\\\')) { + $element = substr($element, 0, -1); + } + $this->elements[] = $element; + + $position += \strlen($matches[1]); + $remaining = $matches[4]; + $pattern = '/^(\.((?:[^\\\\.\[]|\\\\.)++)|\[([^\]]++)\])(.*)/'; + } + + if ('' !== $remaining) { + throw new InvalidPropertyPathException(sprintf('Could not parse property path "%s". Unexpected token "%s" at position %d.', $propertyPath, $remaining[0], $position)); + } + + $this->length = \count($this->elements); + } + + public function __toString(): string + { + return $this->pathAsString; + } + + public function getLength(): int + { + return $this->length; + } + + public function getParent(): ?PropertyPathInterface + { + if ($this->length <= 1) { + return null; + } + + $parent = clone $this; + + --$parent->length; + $parent->pathAsString = substr($parent->pathAsString, 0, max(strrpos($parent->pathAsString, '.'), strrpos($parent->pathAsString, '['))); + array_pop($parent->elements); + array_pop($parent->isIndex); + array_pop($parent->isNullSafe); + + return $parent; + } + + /** + * Returns a new iterator for this path. + */ + public function getIterator(): PropertyPathIteratorInterface + { + return new PropertyPathIterator($this); + } + + public function getElements(): array + { + return $this->elements; + } + + public function getElement(int $index): string + { + if (!isset($this->elements[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index)); + } + + return $this->elements[$index]; + } + + public function isProperty(int $index): bool + { + if (!isset($this->isIndex[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index)); + } + + return !$this->isIndex[$index]; + } + + public function isIndex(int $index): bool + { + if (!isset($this->isIndex[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index)); + } + + return $this->isIndex[$index]; + } + + public function isNullSafe(int $index): bool + { + if (!isset($this->isNullSafe[$index])) { + throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index)); + } + + return $this->isNullSafe[$index]; + } +} diff --git a/vendor/symfony/property-access/PropertyPathBuilder.php b/vendor/symfony/property-access/PropertyPathBuilder.php new file mode 100644 index 0000000..2c439f0 --- /dev/null +++ b/vendor/symfony/property-access/PropertyPathBuilder.php @@ -0,0 +1,261 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException; + +/** + * @author Bernhard Schussek + */ +class PropertyPathBuilder +{ + private array $elements = []; + private array $isIndex = []; + + public function __construct(PropertyPathInterface|string|null $path = null) + { + if (null !== $path) { + $this->append($path); + } + } + + /** + * Appends a (sub-) path to the current path. + * + * @param int $offset The offset where the appended piece starts in $path + * @param int $length The length of the appended piece; if 0, the full path is appended + */ + public function append(PropertyPathInterface|string $path, int $offset = 0, int $length = 0): void + { + if (\is_string($path)) { + $path = new PropertyPath($path); + } + + if (0 === $length) { + $end = $path->getLength(); + } else { + $end = $offset + $length; + } + + for (; $offset < $end; ++$offset) { + $this->elements[] = $path->getElement($offset); + $this->isIndex[] = $path->isIndex($offset); + } + } + + /** + * Appends an index element to the current path. + */ + public function appendIndex(string $name): void + { + $this->elements[] = $name; + $this->isIndex[] = true; + } + + /** + * Appends a property element to the current path. + */ + public function appendProperty(string $name): void + { + $this->elements[] = $name; + $this->isIndex[] = false; + } + + /** + * Removes elements from the current path. + * + * @throws OutOfBoundsException if offset is invalid + */ + public function remove(int $offset, int $length = 1): void + { + if (!isset($this->elements[$offset])) { + throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset)); + } + + $this->resize($offset, $length, 0); + } + + /** + * Replaces a sub-path by a different (sub-) path. + * + * @param int $pathOffset The offset where the inserted piece starts in $path + * @param int $pathLength The length of the inserted piece; if 0, the full path is inserted + * + * @throws OutOfBoundsException If the offset is invalid + */ + public function replace(int $offset, int $length, PropertyPathInterface|string $path, int $pathOffset = 0, int $pathLength = 0): void + { + if (\is_string($path)) { + $path = new PropertyPath($path); + } + + if ($offset < 0 && abs($offset) <= $this->getLength()) { + $offset = $this->getLength() + $offset; + } elseif (!isset($this->elements[$offset])) { + throw new OutOfBoundsException('The offset '.$offset.' is not within the property path'); + } + + if (0 === $pathLength) { + $pathLength = $path->getLength() - $pathOffset; + } + + $this->resize($offset, $length, $pathLength); + + for ($i = 0; $i < $pathLength; ++$i) { + $this->elements[$offset + $i] = $path->getElement($pathOffset + $i); + $this->isIndex[$offset + $i] = $path->isIndex($pathOffset + $i); + } + ksort($this->elements); + } + + /** + * Replaces a property element by an index element. + * + * @throws OutOfBoundsException If the offset is invalid + */ + public function replaceByIndex(int $offset, ?string $name = null): void + { + if (!isset($this->elements[$offset])) { + throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset)); + } + + if (null !== $name) { + $this->elements[$offset] = $name; + } + + $this->isIndex[$offset] = true; + } + + /** + * Replaces an index element by a property element. + * + * @throws OutOfBoundsException If the offset is invalid + */ + public function replaceByProperty(int $offset, ?string $name = null): void + { + if (!isset($this->elements[$offset])) { + throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset)); + } + + if (null !== $name) { + $this->elements[$offset] = $name; + } + + $this->isIndex[$offset] = false; + } + + /** + * Returns the length of the current path. + */ + public function getLength(): int + { + return \count($this->elements); + } + + /** + * Returns the current property path. + */ + public function getPropertyPath(): ?PropertyPathInterface + { + $pathAsString = $this->__toString(); + + return '' !== $pathAsString ? new PropertyPath($pathAsString) : null; + } + + /** + * Returns the current property path as string. + */ + public function __toString(): string + { + $string = ''; + + foreach ($this->elements as $offset => $element) { + if ($this->isIndex[$offset]) { + $element = '['.$element.']'; + } elseif ('' !== $string) { + $string .= '.'; + } + + $string .= $element; + } + + return $string; + } + + /** + * Resizes the path so that a chunk of length $cutLength is + * removed at $offset and another chunk of length $insertionLength + * can be inserted. + */ + private function resize(int $offset, int $cutLength, int $insertionLength): void + { + // Nothing else to do in this case + if ($insertionLength === $cutLength) { + return; + } + + $length = \count($this->elements); + + if ($cutLength > $insertionLength) { + // More elements should be removed than inserted + $diff = $cutLength - $insertionLength; + $newLength = $length - $diff; + + // Shift elements to the left (left-to-right until the new end) + // Max allowed offset to be shifted is such that + // $offset + $diff < $length (otherwise invalid index access) + // i.e. $offset < $length - $diff = $newLength + for ($i = $offset; $i < $newLength; ++$i) { + $this->elements[$i] = $this->elements[$i + $diff]; + $this->isIndex[$i] = $this->isIndex[$i + $diff]; + } + + // All remaining elements should be removed + $this->elements = \array_slice($this->elements, 0, $i); + $this->isIndex = \array_slice($this->isIndex, 0, $i); + } else { + $diff = $insertionLength - $cutLength; + + $newLength = $length + $diff; + $indexAfterInsertion = $offset + $insertionLength; + + // $diff <= $insertionLength + // $indexAfterInsertion >= $insertionLength + // => $diff <= $indexAfterInsertion + + // In each of the following loops, $i >= $diff must hold, + // otherwise ($i - $diff) becomes negative. + + // Shift old elements to the right to make up space for the + // inserted elements. This needs to be done left-to-right in + // order to preserve an ascending array index order + // Since $i = max($length, $indexAfterInsertion) and $indexAfterInsertion >= $diff, + // $i >= $diff is guaranteed. + for ($i = max($length, $indexAfterInsertion); $i < $newLength; ++$i) { + $this->elements[$i] = $this->elements[$i - $diff]; + $this->isIndex[$i] = $this->isIndex[$i - $diff]; + } + + // Shift remaining elements to the right. Do this right-to-left + // so we don't overwrite elements before copying them + // The last written index is the immediate index after the inserted + // string, because the indices before that will be overwritten + // anyway. + // Since $i >= $indexAfterInsertion and $indexAfterInsertion >= $diff, + // $i >= $diff is guaranteed. + for ($i = $length - 1; $i >= $indexAfterInsertion; --$i) { + $this->elements[$i] = $this->elements[$i - $diff]; + $this->isIndex[$i] = $this->isIndex[$i - $diff]; + } + } + } +} diff --git a/vendor/symfony/property-access/PropertyPathInterface.php b/vendor/symfony/property-access/PropertyPathInterface.php new file mode 100644 index 0000000..729a1c8 --- /dev/null +++ b/vendor/symfony/property-access/PropertyPathInterface.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +/** + * A sequence of property names or array indices. + * + * @author Bernhard Schussek + * + * @extends \Traversable + */ +interface PropertyPathInterface extends \Traversable, \Stringable +{ + /** + * Returns the string representation of the property path. + */ + public function __toString(): string; + + /** + * Returns the length of the property path, i.e. the number of elements. + */ + public function getLength(): int; + + /** + * Returns the parent property path. + * + * The parent property path is the one that contains the same items as + * this one except for the last one. + * + * If this property path only contains one item, null is returned. + */ + public function getParent(): ?self; + + /** + * Returns the elements of the property path as array. + * + * @return list + */ + public function getElements(): array; + + /** + * Returns the element at the given index in the property path. + * + * @param int $index The index key + * + * @throws Exception\OutOfBoundsException If the offset is invalid + */ + public function getElement(int $index): string; + + /** + * Returns whether the element at the given index is a property. + * + * @param int $index The index in the property path + * + * @throws Exception\OutOfBoundsException If the offset is invalid + */ + public function isProperty(int $index): bool; + + /** + * Returns whether the element at the given index is an array index. + * + * @param int $index The index in the property path + * + * @throws Exception\OutOfBoundsException If the offset is invalid + */ + public function isIndex(int $index): bool; + + /** + * Returns whether the element at the given index is null safe. + */ + public function isNullSafe(int $index): bool; +} diff --git a/vendor/symfony/property-access/PropertyPathIterator.php b/vendor/symfony/property-access/PropertyPathIterator.php new file mode 100644 index 0000000..f5ebd78 --- /dev/null +++ b/vendor/symfony/property-access/PropertyPathIterator.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +/** + * Traverses a property path and provides additional methods to find out + * information about the current element. + * + * @author Bernhard Schussek + * + * @extends \ArrayIterator + */ +class PropertyPathIterator extends \ArrayIterator implements PropertyPathIteratorInterface +{ + public function __construct( + protected PropertyPathInterface $path, + ) { + parent::__construct($path->getElements()); + } + + public function isIndex(): bool + { + return $this->path->isIndex($this->key()); + } + + public function isProperty(): bool + { + return $this->path->isProperty($this->key()); + } +} diff --git a/vendor/symfony/property-access/PropertyPathIteratorInterface.php b/vendor/symfony/property-access/PropertyPathIteratorInterface.php new file mode 100644 index 0000000..4704b36 --- /dev/null +++ b/vendor/symfony/property-access/PropertyPathIteratorInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess; + +/** + * @author Bernhard Schussek + * + * @extends \SeekableIterator + */ +interface PropertyPathIteratorInterface extends \SeekableIterator +{ + /** + * Returns whether the current element in the property path is an array + * index. + */ + public function isIndex(): bool; + + /** + * Returns whether the current element in the property path is a property + * name. + */ + public function isProperty(): bool; +} diff --git a/vendor/symfony/property-access/README.md b/vendor/symfony/property-access/README.md new file mode 100644 index 0000000..29cb233 --- /dev/null +++ b/vendor/symfony/property-access/README.md @@ -0,0 +1,14 @@ +PropertyAccess Component +======================== + +The PropertyAccess component provides functions to read and write from/to an +object or array using a simple string notation. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/property_access.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/property-access/composer.json b/vendor/symfony/property-access/composer.json new file mode 100644 index 0000000..376ee7e --- /dev/null +++ b/vendor/symfony/property-access/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/property-access", + "type": "library", + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "keywords": ["property", "index", "access", "object", "array", "extraction", "injection", "reflection", "property-path"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\PropertyAccess\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/property-info/CHANGELOG.md b/vendor/symfony/property-info/CHANGELOG.md new file mode 100644 index 0000000..490dab4 --- /dev/null +++ b/vendor/symfony/property-info/CHANGELOG.md @@ -0,0 +1,64 @@ +CHANGELOG +========= + +7.1 +--- + + * Introduce `PropertyDocBlockExtractorInterface` to extract a property's doc block + * Restrict access to `PhpStanExtractor` based on visibility + * Add `PropertyTypeExtractorInterface::getType()` as experimental + +6.4 +--- + + * Make properties writable when a setter in camelCase exists, similar to the camelCase getter + +6.1 +--- + + * Add support for phpDocumentor and PHPStan pseudo-types + * Add PHP 8.0 promoted properties `@param` mutation support to `PhpDocExtractor` + * Add PHP 8.0 promoted properties `@param` mutation support to `PhpStanExtractor` + +6.0 +--- + + * Remove the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead + * Remove the `enable_magic_call_extraction` context option in `ReflectionExtractor::getWriteInfo()` and `ReflectionExtractor::getReadInfo()` in favor of `enable_magic_methods_extraction` + +5.4 +--- + + * Add PhpStanExtractor + +5.3 +--- + + * Add support for multiple types for collection keys & values + * Deprecate the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead + +5.2.0 +----- + + * deprecated the `enable_magic_call_extraction` context option in `ReflectionExtractor::getWriteInfo()` and `ReflectionExtractor::getReadInfo()` in favor of `enable_magic_methods_extraction` + +5.1.0 +----- + + * Add support for extracting accessor and mutator via PHP Reflection + +4.3.0 +----- + + * Added the ability to extract private and protected properties and methods on `ReflectionExtractor` + * Added the ability to extract property type based on its initial value + +4.2.0 +----- + + * added `PropertyInitializableExtractorInterface` to test if a property can be initialized through the constructor (implemented by `ReflectionExtractor`) + +3.3.0 +----- + + * Added `PropertyInfoPass` diff --git a/vendor/symfony/property-info/DependencyInjection/PropertyInfoConstructorPass.php b/vendor/symfony/property-info/DependencyInjection/PropertyInfoConstructorPass.php new file mode 100644 index 0000000..6c77538 --- /dev/null +++ b/vendor/symfony/property-info/DependencyInjection/PropertyInfoConstructorPass.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Adds extractors to the property_info.constructor_extractor service. + * + * @author Dmitrii Poddubnyi + */ +final class PropertyInfoConstructorPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('property_info.constructor_extractor')) { + return; + } + $definition = $container->getDefinition('property_info.constructor_extractor'); + + $listExtractors = $this->findAndSortTaggedServices('property_info.constructor_extractor', $container); + $definition->replaceArgument(0, new IteratorArgument($listExtractors)); + } +} diff --git a/vendor/symfony/property-info/DependencyInjection/PropertyInfoPass.php b/vendor/symfony/property-info/DependencyInjection/PropertyInfoPass.php new file mode 100644 index 0000000..de2a374 --- /dev/null +++ b/vendor/symfony/property-info/DependencyInjection/PropertyInfoPass.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Adds extractors to the property_info service. + * + * @author Kévin Dunglas + */ +class PropertyInfoPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('property_info')) { + return; + } + + $definition = $container->getDefinition('property_info'); + + $listExtractors = $this->findAndSortTaggedServices('property_info.list_extractor', $container); + $definition->replaceArgument(0, new IteratorArgument($listExtractors)); + + $typeExtractors = $this->findAndSortTaggedServices('property_info.type_extractor', $container); + $definition->replaceArgument(1, new IteratorArgument($typeExtractors)); + + $descriptionExtractors = $this->findAndSortTaggedServices('property_info.description_extractor', $container); + $definition->replaceArgument(2, new IteratorArgument($descriptionExtractors)); + + $accessExtractors = $this->findAndSortTaggedServices('property_info.access_extractor', $container); + $definition->replaceArgument(3, new IteratorArgument($accessExtractors)); + + $initializableExtractors = $this->findAndSortTaggedServices('property_info.initializable_extractor', $container); + $definition->setArgument(4, new IteratorArgument($initializableExtractors)); + } +} diff --git a/vendor/symfony/property-info/Extractor/ConstructorArgumentTypeExtractorInterface.php b/vendor/symfony/property-info/Extractor/ConstructorArgumentTypeExtractorInterface.php new file mode 100644 index 0000000..571b6fa --- /dev/null +++ b/vendor/symfony/property-info/Extractor/ConstructorArgumentTypeExtractorInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Extractor; + +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; + +/** + * Infers the constructor argument type. + * + * @author Dmitrii Poddubnyi + * + * @internal + */ +interface ConstructorArgumentTypeExtractorInterface +{ + /** + * Gets types of an argument from constructor. + * + * @return LegacyType[]|null + * + * @internal + */ + public function getTypesFromConstructor(string $class, string $property): ?array; + + /** + * Gets type of an argument from constructor. + * + * @param class-string $class + * + * @internal + */ + public function getTypeFromConstructor(string $class, string $property): ?Type; +} diff --git a/vendor/symfony/property-info/Extractor/ConstructorExtractor.php b/vendor/symfony/property-info/Extractor/ConstructorExtractor.php new file mode 100644 index 0000000..ea17722 --- /dev/null +++ b/vendor/symfony/property-info/Extractor/ConstructorExtractor.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Extractor; + +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\TypeInfo\Type; + +/** + * Extracts the constructor argument type using ConstructorArgumentTypeExtractorInterface implementations. + * + * @author Dmitrii Poddubnyi + */ +final class ConstructorExtractor implements PropertyTypeExtractorInterface +{ + /** + * @param iterable $extractors + */ + public function __construct( + private readonly iterable $extractors = [], + ) { + } + + /** + * @experimental + */ + public function getType(string $class, string $property, array $context = []): ?Type + { + foreach ($this->extractors as $extractor) { + if (null !== $value = $extractor->getTypeFromConstructor($class, $property)) { + return $value; + } + } + + return null; + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + foreach ($this->extractors as $extractor) { + $value = $extractor->getTypesFromConstructor($class, $property); + if (null !== $value) { + return $value; + } + } + + return null; + } +} diff --git a/vendor/symfony/property-info/Extractor/PhpDocExtractor.php b/vendor/symfony/property-info/Extractor/PhpDocExtractor.php new file mode 100644 index 0000000..34ad9f3 --- /dev/null +++ b/vendor/symfony/property-info/Extractor/PhpDocExtractor.php @@ -0,0 +1,446 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Extractor; + +use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; +use phpDocumentor\Reflection\DocBlockFactory; +use phpDocumentor\Reflection\DocBlockFactoryInterface; +use phpDocumentor\Reflection\Types\Context; +use phpDocumentor\Reflection\Types\ContextFactory; +use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyDocBlockExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper; +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; + +/** + * Extracts data using a PHPDoc parser. + * + * @author Kévin Dunglas + * + * @final + */ +class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface, PropertyDocBlockExtractorInterface +{ + public const PROPERTY = 0; + public const ACCESSOR = 1; + public const MUTATOR = 2; + + /** + * @var array + */ + private array $docBlocks = []; + + /** + * @var Context[] + */ + private array $contexts = []; + + private DocBlockFactoryInterface $docBlockFactory; + private ContextFactory $contextFactory; + private TypeContextFactory $typeContextFactory; + private PhpDocTypeHelper $phpDocTypeHelper; + private array $mutatorPrefixes; + private array $accessorPrefixes; + private array $arrayMutatorPrefixes; + + /** + * @param string[]|null $mutatorPrefixes + * @param string[]|null $accessorPrefixes + * @param string[]|null $arrayMutatorPrefixes + */ + public function __construct(?DocBlockFactoryInterface $docBlockFactory = null, ?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null) + { + if (!class_exists(DocBlockFactory::class)) { + throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', __CLASS__)); + } + + $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); + $this->contextFactory = new ContextFactory(); + $this->typeContextFactory = new TypeContextFactory(); + $this->phpDocTypeHelper = new PhpDocTypeHelper(); + $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; + $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; + $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; + } + + public function getShortDescription(string $class, string $property, array $context = []): ?string + { + /** @var $docBlock DocBlock */ + [$docBlock] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $shortDescription = $docBlock->getSummary(); + + if ($shortDescription) { + return $shortDescription; + } + + foreach ($docBlock->getTagsByName('var') as $var) { + if ($var && !$var instanceof InvalidTag) { + $varDescription = $var->getDescription()->render(); + + if ($varDescription) { + return $varDescription; + } + } + } + + return null; + } + + public function getLongDescription(string $class, string $property, array $context = []): ?string + { + /** @var $docBlock DocBlock */ + [$docBlock] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $contents = $docBlock->getDescription()->render(); + + return '' === $contents ? null : $contents; + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + /** @var $docBlock DocBlock */ + [$docBlock, $source, $prefix] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $tag = match ($source) { + self::PROPERTY => 'var', + self::ACCESSOR => 'return', + self::MUTATOR => 'param', + }; + + $parentClass = null; + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName($tag) as $tag) { + if ($tag && !$tag instanceof InvalidTag && null !== $tag->getType()) { + foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) { + switch ($type->getClassName()) { + case 'self': + case 'static': + $resolvedClass = $class; + break; + + case 'parent': + if (false !== $resolvedClass = $parentClass ??= get_parent_class($class)) { + break; + } + // no break + + default: + $types[] = $type; + continue 2; + } + + $types[] = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); + } + } + } + + if (!isset($types[0])) { + return null; + } + + if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { + return $types; + } + + return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $types[0])]; + } + + public function getTypesFromConstructor(string $class, string $property): ?array + { + $docBlock = $this->getDocBlockFromConstructor($class, $property); + + if (!$docBlock) { + return null; + } + + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName('param') as $tag) { + if ($tag && null !== $tag->getType()) { + $types[] = $this->phpDocTypeHelper->getTypes($tag->getType()); + } + } + + if (!isset($types[0]) || [] === $types[0]) { + return null; + } + + return array_merge([], ...$types); + } + + /** + * @experimental + */ + public function getType(string $class, string $property, array $context = []): ?Type + { + /** @var $docBlock DocBlock */ + [$docBlock, $source, $prefix] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $tag = match ($source) { + self::PROPERTY => 'var', + self::ACCESSOR => 'return', + self::MUTATOR => 'param', + }; + + $types = []; + $typeContext = $this->typeContextFactory->createFromClassName($class); + + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName($tag) as $tag) { + if ($tag instanceof InvalidTag || !$tagType = $tag->getType()) { + continue; + } + + $type = $this->phpDocTypeHelper->getType($tagType); + + if (!$type instanceof ObjectType) { + $types[] = $type; + + continue; + } + + $normalizedClassName = match ($type->getClassName()) { + 'self' => $typeContext->getDeclaringClass(), + 'static' => $typeContext->getCalledClass(), + default => $type->getClassName(), + }; + + if ('parent' === $normalizedClassName) { + try { + $normalizedClassName = $typeContext->getParentClass(); + } catch (LogicException) { + // if there is no parent for the current class, we keep the "parent" raw string + } + } + + $types[] = $type->isNullable() ? Type::nullable(Type::object($normalizedClassName)) : Type::object($normalizedClassName); + } + + if (null === $type = $types[0] ?? null) { + return null; + } + + if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { + return $type; + } + + return Type::list($type); + } + + /** + * @experimental + */ + public function getTypeFromConstructor(string $class, string $property): ?Type + { + if (!$docBlock = $this->getDocBlockFromConstructor($class, $property)) { + return null; + } + + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName('param') as $tag) { + if ($tag instanceof InvalidTag || !$tagType = $tag->getType()) { + continue; + } + + $types[] = $this->phpDocTypeHelper->getType($tagType); + } + + return $types[0] ?? null; + } + + public function getDocBlock(string $class, string $property): ?DocBlock + { + $output = $this->findDocBlock($class, $property); + + return $output[0]; + } + + private function getDocBlockFromConstructor(string $class, string $property): ?DocBlock + { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + $reflectionConstructor = $reflectionClass->getConstructor(); + if (!$reflectionConstructor) { + return null; + } + + try { + $docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor)); + + return $this->filterDocBlockParams($docBlock, $property); + } catch (\InvalidArgumentException) { + return null; + } + } + + private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam): DocBlock + { + $tags = array_values(array_filter($docBlock->getTagsByName('param'), fn ($tag) => $tag instanceof DocBlock\Tags\Param && $allowedParam === $tag->getVariableName())); + + return new DocBlock($docBlock->getSummary(), $docBlock->getDescription(), $tags, $docBlock->getContext(), + $docBlock->getLocation(), $docBlock->isTemplateStart(), $docBlock->isTemplateEnd()); + } + + /** + * @return array{DocBlock|null, int|null, string|null} + */ + private function findDocBlock(string $class, string $property): array + { + $propertyHash = sprintf('%s::%s', $class, $property); + + if (isset($this->docBlocks[$propertyHash])) { + return $this->docBlocks[$propertyHash]; + } + + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + } catch (\ReflectionException) { + $reflectionProperty = null; + } + + $ucFirstProperty = ucfirst($property); + + switch (true) { + case $reflectionProperty?->isPromoted() && $docBlock = $this->getDocBlockFromConstructor($class, $property): + $data = [$docBlock, self::MUTATOR, null]; + break; + + case $docBlock = $this->getDocBlockFromProperty($class, $property): + $data = [$docBlock, self::PROPERTY, null]; + break; + + case [$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR): + $data = [$docBlock, self::ACCESSOR, null]; + break; + + case [$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR): + $data = [$docBlock, self::MUTATOR, $prefix]; + break; + + default: + $data = [null, null, null]; + } + + return $this->docBlocks[$propertyHash] = $data; + } + + private function getDocBlockFromProperty(string $class, string $property): ?DocBlock + { + // Use a ReflectionProperty instead of $class to get the parent class if applicable + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + } catch (\ReflectionException) { + return null; + } + + $reflector = $reflectionProperty->getDeclaringClass(); + + foreach ($reflector->getTraits() as $trait) { + if ($trait->hasProperty($property)) { + return $this->getDocBlockFromProperty($trait->getName(), $property); + } + } + + try { + return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector)); + } catch (\InvalidArgumentException|\RuntimeException) { + return null; + } + } + + /** + * @return array{DocBlock, string}|null + */ + private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array + { + $prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes; + $prefix = null; + + foreach ($prefixes as $prefix) { + $methodName = $prefix.$ucFirstProperty; + + try { + $reflectionMethod = new \ReflectionMethod($class, $methodName); + if ($reflectionMethod->isStatic()) { + continue; + } + + if ( + (self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) + || (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) + ) { + break; + } + } catch (\ReflectionException) { + // Try the next prefix if the method doesn't exist + } + } + + if (!isset($reflectionMethod)) { + return null; + } + + $reflector = $reflectionMethod->getDeclaringClass(); + + foreach ($reflector->getTraits() as $trait) { + if ($trait->hasMethod($methodName)) { + return $this->getDocBlockFromMethod($trait->getName(), $ucFirstProperty, $type); + } + } + + try { + return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix]; + } catch (\InvalidArgumentException|\RuntimeException) { + return null; + } + } + + /** + * Prevents a lot of redundant calls to ContextFactory::createForNamespace(). + */ + private function createFromReflector(\ReflectionClass $reflector): Context + { + $cacheKey = $reflector->getNamespaceName().':'.$reflector->getFileName(); + + if (isset($this->contexts[$cacheKey])) { + return $this->contexts[$cacheKey]; + } + + $this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector); + + return $this->contexts[$cacheKey]; + } +} diff --git a/vendor/symfony/property-info/Extractor/PhpStanExtractor.php b/vendor/symfony/property-info/Extractor/PhpStanExtractor.php new file mode 100644 index 0000000..016f406 --- /dev/null +++ b/vendor/symfony/property-info/Extractor/PhpStanExtractor.php @@ -0,0 +1,404 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Extractor; + +use phpDocumentor\Reflection\Types\ContextFactory; +use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; +use Symfony\Component\PropertyInfo\PhpStan\NameScopeFactory; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\PropertyInfo\Util\PhpStanTypeHelper; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; +use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver; + +/** + * Extracts data using PHPStan parser. + * + * @author Baptiste Leduc + */ +final class PhpStanExtractor implements PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface +{ + private const PROPERTY = 0; + private const ACCESSOR = 1; + private const MUTATOR = 2; + + private PhpDocParser $phpDocParser; + private Lexer $lexer; + private NameScopeFactory $nameScopeFactory; + + private StringTypeResolver $stringTypeResolver; + private TypeContextFactory $typeContextFactory; + + /** @var array */ + private array $docBlocks = []; + private PhpStanTypeHelper $phpStanTypeHelper; + private array $mutatorPrefixes; + private array $accessorPrefixes; + private array $arrayMutatorPrefixes; + + /** + * @param list|null $mutatorPrefixes + * @param list|null $accessorPrefixes + * @param list|null $arrayMutatorPrefixes + */ + public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null, private bool $allowPrivateAccess = true) + { + if (!class_exists(ContextFactory::class)) { + throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/type-resolver" package is not installed. Try running composer require "phpdocumentor/type-resolver".', __CLASS__)); + } + + if (!class_exists(PhpDocParser::class)) { + throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpstan/phpdoc-parser" package is not installed. Try running composer require "phpstan/phpdoc-parser".', __CLASS__)); + } + + $this->phpStanTypeHelper = new PhpStanTypeHelper(); + $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; + $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; + $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; + + $this->phpDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser()); + $this->lexer = new Lexer(); + $this->nameScopeFactory = new NameScopeFactory(); + $this->stringTypeResolver = new StringTypeResolver(); + $this->typeContextFactory = new TypeContextFactory($this->stringTypeResolver); + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + /** @var PhpDocNode|null $docNode */ + [$docNode, $source, $prefix, $declaringClass] = $this->getDocBlock($class, $property); + $nameScope = $this->nameScopeFactory->create($class, $declaringClass); + if (null === $docNode) { + return null; + } + + switch ($source) { + case self::PROPERTY: + $tag = '@var'; + break; + + case self::ACCESSOR: + $tag = '@return'; + break; + + case self::MUTATOR: + $tag = '@param'; + break; + } + + $parentClass = null; + $types = []; + foreach ($docNode->getTagsByName($tag) as $tagDocNode) { + if ($tagDocNode->value instanceof InvalidTagValueNode) { + continue; + } + + if ( + $tagDocNode->value instanceof ParamTagValueNode + && null === $prefix + && $tagDocNode->value->parameterName !== '$'.$property + ) { + continue; + } + + foreach ($this->phpStanTypeHelper->getTypes($tagDocNode->value, $nameScope) as $type) { + switch ($type->getClassName()) { + case 'self': + case 'static': + $resolvedClass = $class; + break; + + case 'parent': + if (false !== $resolvedClass = $parentClass ??= get_parent_class($class)) { + break; + } + // no break + + default: + $types[] = $type; + continue 2; + } + + $types[] = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); + } + } + + if (!isset($types[0])) { + return null; + } + + if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { + return $types; + } + + return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $types[0])]; + } + + /** + * @return LegacyType[]|null + */ + public function getTypesFromConstructor(string $class, string $property): ?array + { + if (null === $tagDocNode = $this->getDocBlockFromConstructor($class, $property)) { + return null; + } + + $types = []; + foreach ($this->phpStanTypeHelper->getTypes($tagDocNode, $this->nameScopeFactory->create($class)) as $type) { + $types[] = $type; + } + + if (!isset($types[0])) { + return null; + } + + return $types; + } + + /** + * @experimental + */ + public function getType(string $class, string $property, array $context = []): ?Type + { + /** @var PhpDocNode|null $docNode */ + [$docNode, $source, $prefix, $declaringClass] = $this->getDocBlock($class, $property); + + if (null === $docNode) { + return null; + } + + $typeContext = $this->typeContextFactory->createFromClassName($class, $declaringClass); + + $tag = match ($source) { + self::PROPERTY => '@var', + self::ACCESSOR => '@return', + self::MUTATOR => '@param', + default => 'invalid', + }; + + $types = []; + + foreach ($docNode->getTagsByName($tag) as $tagDocNode) { + if ($tagDocNode->value instanceof InvalidTagValueNode) { + continue; + } + + if ($tagDocNode->value instanceof ParamTagValueNode && null === $prefix && $tagDocNode->value->parameterName !== '$'.$property) { + continue; + } + + try { + $types[] = $this->stringTypeResolver->resolve((string) $tagDocNode->value->type, $typeContext); + } catch (UnsupportedException) { + } + } + + if (!$type = $types[0] ?? null) { + return null; + } + + if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { + return $type; + } + + return Type::list($type); + } + + /** + * @experimental + */ + public function getTypeFromConstructor(string $class, string $property): ?Type + { + if (!$tagDocNode = $this->getDocBlockFromConstructor($class, $property)) { + return null; + } + + $typeContext = $this->typeContextFactory->createFromClassName($class); + + return $this->stringTypeResolver->resolve((string) $tagDocNode->type, $typeContext); + } + + private function getDocBlockFromConstructor(string $class, string $property): ?ParamTagValueNode + { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + + if (null === $reflectionConstructor = $reflectionClass->getConstructor()) { + return null; + } + + if (!$rawDocNode = $reflectionConstructor->getDocComment()) { + return null; + } + + $phpDocNode = $this->getPhpDocNode($rawDocNode); + + return $this->filterDocBlockParams($phpDocNode, $property); + } + + private function filterDocBlockParams(PhpDocNode $docNode, string $allowedParam): ?ParamTagValueNode + { + $tags = array_values(array_filter($docNode->getTagsByName('@param'), fn ($tagNode) => $tagNode instanceof PhpDocTagNode && ('$'.$allowedParam) === $tagNode->value->parameterName)); + + if (!$tags) { + return null; + } + + return $tags[0]->value; + } + + /** + * @return array{PhpDocNode|null, int|null, string|null, string|null} + */ + private function getDocBlock(string $class, string $property): array + { + $propertyHash = $class.'::'.$property; + + if (isset($this->docBlocks[$propertyHash])) { + return $this->docBlocks[$propertyHash]; + } + + $ucFirstProperty = ucfirst($property); + + if ([$docBlock, $source, $declaringClass] = $this->getDocBlockFromProperty($class, $property)) { + $data = [$docBlock, $source, null, $declaringClass]; + } elseif ([$docBlock, $_, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR)) { + $data = [$docBlock, self::ACCESSOR, null, $declaringClass]; + } elseif ([$docBlock, $prefix, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR)) { + $data = [$docBlock, self::MUTATOR, $prefix, $declaringClass]; + } else { + $data = [null, null, null, null]; + } + + return $this->docBlocks[$propertyHash] = $data; + } + + /** + * @return array{PhpDocNode, int, string}|null + */ + private function getDocBlockFromProperty(string $class, string $property): ?array + { + // Use a ReflectionProperty instead of $class to get the parent class if applicable + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + } catch (\ReflectionException) { + return null; + } + + if (!$this->canAccessMemberBasedOnItsVisibility($reflectionProperty)) { + return null; + } + + $reflector = $reflectionProperty->getDeclaringClass(); + + foreach ($reflector->getTraits() as $trait) { + if ($trait->hasProperty($property)) { + return $this->getDocBlockFromProperty($trait->getName(), $property); + } + + } + + // Type can be inside property docblock as `@var` + $rawDocNode = $reflectionProperty->getDocComment(); + $phpDocNode = $rawDocNode ? $this->getPhpDocNode($rawDocNode) : null; + $source = self::PROPERTY; + + if (!$phpDocNode?->getTagsByName('@var')) { + $phpDocNode = null; + } + + // or in the constructor as `@param` for promoted properties + if (!$phpDocNode && $reflectionProperty->isPromoted()) { + $constructor = new \ReflectionMethod($class, '__construct'); + $rawDocNode = $constructor->getDocComment(); + $phpDocNode = $rawDocNode ? $this->getPhpDocNode($rawDocNode) : null; + $source = self::MUTATOR; + } + + if (!$phpDocNode) { + return null; + } + + return [$phpDocNode, $source, $reflectionProperty->class]; + } + + /** + * @return array{PhpDocNode, string, string}|null + */ + private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array + { + $prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes; + $prefix = null; + + foreach ($prefixes as $prefix) { + $methodName = $prefix.$ucFirstProperty; + + try { + $reflectionMethod = new \ReflectionMethod($class, $methodName); + if ($reflectionMethod->isStatic()) { + continue; + } + + if ( + ( + (self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) + || (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) + ) + && $this->canAccessMemberBasedOnItsVisibility($reflectionMethod) + ) { + break; + } + } catch (\ReflectionException) { + // Try the next prefix if the method doesn't exist + } + } + + if (!isset($reflectionMethod)) { + return null; + } + + if (null === $rawDocNode = $reflectionMethod->getDocComment() ?: null) { + return null; + } + + $phpDocNode = $this->getPhpDocNode($rawDocNode); + + return [$phpDocNode, $prefix, $reflectionMethod->class]; + } + + private function getPhpDocNode(string $rawDocNode): PhpDocNode + { + $tokens = new TokenIterator($this->lexer->tokenize($rawDocNode)); + $phpDocNode = $this->phpDocParser->parse($tokens); + $tokens->consumeTokenType(Lexer::TOKEN_END); + + return $phpDocNode; + } + + private function canAccessMemberBasedOnItsVisibility(\ReflectionProperty|\ReflectionMethod $member): bool + { + return $this->allowPrivateAccess || $member->isPublic(); + } +} diff --git a/vendor/symfony/property-info/Extractor/ReflectionExtractor.php b/vendor/symfony/property-info/Extractor/ReflectionExtractor.php new file mode 100644 index 0000000..40e40bc --- /dev/null +++ b/vendor/symfony/property-info/Extractor/ReflectionExtractor.php @@ -0,0 +1,968 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Extractor; + +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfo; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfo; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\String\Inflector\EnglishInflector; +use Symfony\Component\String\Inflector\InflectorInterface; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\TypeIdentifier; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface; + +/** + * Extracts data using the reflection API. + * + * @author Kévin Dunglas + * + * @final + */ +class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, PropertyReadInfoExtractorInterface, PropertyWriteInfoExtractorInterface, ConstructorArgumentTypeExtractorInterface +{ + /** + * @internal + */ + public static array $defaultMutatorPrefixes = ['add', 'remove', 'set']; + + /** + * @internal + */ + public static array $defaultAccessorPrefixes = ['get', 'is', 'has', 'can']; + + /** + * @internal + */ + public static array $defaultArrayMutatorPrefixes = ['add', 'remove']; + + public const ALLOW_PRIVATE = 1; + public const ALLOW_PROTECTED = 2; + public const ALLOW_PUBLIC = 4; + + /** @var int Allow none of the magic methods */ + public const DISALLOW_MAGIC_METHODS = 0; + /** @var int Allow magic __get methods */ + public const ALLOW_MAGIC_GET = 1 << 0; + /** @var int Allow magic __set methods */ + public const ALLOW_MAGIC_SET = 1 << 1; + /** @var int Allow magic __call methods */ + public const ALLOW_MAGIC_CALL = 1 << 2; + + private const MAP_TYPES = [ + 'integer' => TypeIdentifier::INT->value, + 'boolean' => TypeIdentifier::BOOL->value, + 'double' => TypeIdentifier::FLOAT->value, + ]; + + private array $mutatorPrefixes; + private array $accessorPrefixes; + private array $arrayMutatorPrefixes; + private bool $enableConstructorExtraction; + private int $methodReflectionFlags; + private int $magicMethodsFlags; + private int $propertyReflectionFlags; + private InflectorInterface $inflector; + private array $arrayMutatorPrefixesFirst; + private array $arrayMutatorPrefixesLast; + private TypeResolverInterface $typeResolver; + + /** + * @param string[]|null $mutatorPrefixes + * @param string[]|null $accessorPrefixes + * @param string[]|null $arrayMutatorPrefixes + */ + public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC, ?InflectorInterface $inflector = null, int $magicMethodsFlags = self::ALLOW_MAGIC_GET | self::ALLOW_MAGIC_SET) + { + $this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes; + $this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes; + $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes; + $this->enableConstructorExtraction = $enableConstructorExtraction; + $this->methodReflectionFlags = $this->getMethodsFlags($accessFlags); + $this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags); + $this->magicMethodsFlags = $magicMethodsFlags; + $this->inflector = $inflector ?? new EnglishInflector(); + $this->typeResolver = TypeResolver::create(); + + $this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes)); + $this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst); + } + + public function getProperties(string $class, array $context = []): ?array + { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + + $reflectionProperties = $reflectionClass->getProperties(); + + $properties = []; + foreach ($reflectionProperties as $reflectionProperty) { + if ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags) { + $properties[$reflectionProperty->name] = $reflectionProperty->name; + } + } + + foreach ($reflectionClass->getMethods($this->methodReflectionFlags) as $reflectionMethod) { + if ($reflectionMethod->isStatic()) { + continue; + } + + $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties); + if (!$propertyName || isset($properties[$propertyName])) { + continue; + } + if ($reflectionClass->hasProperty($lowerCasedPropertyName = lcfirst($propertyName)) || (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName))) { + $propertyName = $lowerCasedPropertyName; + } + $properties[$propertyName] = $propertyName; + } + + return $properties ? array_values($properties) : null; + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + if ($fromMutator = $this->extractFromMutator($class, $property)) { + return $fromMutator; + } + + if ($fromAccessor = $this->extractFromAccessor($class, $property)) { + return $fromAccessor; + } + + if ( + ($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) + && $fromConstructor = $this->extractFromConstructor($class, $property) + ) { + return $fromConstructor; + } + + if ($fromPropertyDeclaration = $this->extractFromPropertyDeclaration($class, $property)) { + return $fromPropertyDeclaration; + } + + return null; + } + + /** + * @return LegacyType[]|null + */ + public function getTypesFromConstructor(string $class, string $property): ?array + { + try { + $reflection = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + if (!$reflectionConstructor = $reflection->getConstructor()) { + return null; + } + if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) { + return null; + } + if (!$reflectionType = $reflectionParameter->getType()) { + return null; + } + if (!$types = $this->extractFromReflectionType($reflectionType, $reflectionConstructor->getDeclaringClass())) { + return null; + } + + return $types; + } + + /** + * @experimental + */ + public function getType(string $class, string $property, array $context = []): ?Type + { + [$mutatorReflection, $prefix] = $this->getMutatorMethod($class, $property); + + if ($mutatorReflection) { + try { + $type = $this->typeResolver->resolve($mutatorReflection->getParameters()[0]); + + if (!$type instanceof CollectionType && \in_array($prefix, $this->arrayMutatorPrefixes, true)) { + $type = $this->isNullableProperty($class, $property) ? Type::nullable(Type::list($type)) : Type::list($type); + } + + return $type; + } catch (UnsupportedException) { + } + } + + [$accessorReflection, $prefix] = $this->getAccessorMethod($class, $property); + if ($accessorReflection) { + try { + return $this->typeResolver->resolve($accessorReflection); + } catch (UnsupportedException) { + } + } + + if ($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) { + try { + $reflectionClass = new \ReflectionClass($class); + if ($type = $this->extractTypeFromConstructor($reflectionClass, $property)) { + return $type; + } + } catch (\ReflectionException) { + } + } + + try { + $reflectionClass = new \ReflectionClass($class); + $reflectionProperty = $reflectionClass->getProperty($property); + } catch (\ReflectionException) { + return null; + } + + try { + return $this->typeResolver->resolve($reflectionProperty); + } catch (UnsupportedException) { + } + + if (null === $defaultValue = ($reflectionClass->getDefaultProperties()[$property] ?? null)) { + return null; + } + + $typeIdentifier = TypeIdentifier::from(static::MAP_TYPES[\gettype($defaultValue)] ?? \gettype($defaultValue)); + $type = 'array' === $typeIdentifier->value ? Type::array() : Type::builtin($typeIdentifier); + + if ($this->isNullableProperty($class, $property)) { + $type = Type::nullable($type); + } + + return $type; + } + + /** + * @experimental + */ + public function getTypeFromConstructor(string $class, string $property): ?Type + { + try { + $reflection = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + + if (!$reflectionConstructor = $reflection->getConstructor()) { + return null; + } + if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) { + return null; + } + + try { + return $this->typeResolver->resolve($reflectionParameter); + } catch (UnsupportedException) { + return null; + } + } + + private function getReflectionParameterFromConstructor(string $property, \ReflectionMethod $reflectionConstructor): ?\ReflectionParameter + { + foreach ($reflectionConstructor->getParameters() as $reflectionParameter) { + if ($reflectionParameter->getName() === $property) { + return $reflectionParameter; + } + } + + return null; + } + + public function isReadable(string $class, string $property, array $context = []): ?bool + { + if ($this->isAllowedProperty($class, $property)) { + return true; + } + + return null !== $this->getReadInfo($class, $property, $context); + } + + public function isWritable(string $class, string $property, array $context = []): ?bool + { + if ($this->isAllowedProperty($class, $property, true)) { + return true; + } + + // First test with the camelized property name + [$reflectionMethod] = $this->getMutatorMethod($class, $this->camelize($property)); + if (null !== $reflectionMethod) { + return true; + } + + // Otherwise check for the old way + [$reflectionMethod] = $this->getMutatorMethod($class, $property); + + return null !== $reflectionMethod; + } + + public function isInitializable(string $class, string $property, array $context = []): ?bool + { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + + if (!$reflectionClass->isInstantiable()) { + return false; + } + + if ($constructor = $reflectionClass->getConstructor()) { + foreach ($constructor->getParameters() as $parameter) { + if ($property === $parameter->name) { + return true; + } + } + } elseif ($parentClass = $reflectionClass->getParentClass()) { + return $this->isInitializable($parentClass->getName(), $property); + } + + return false; + } + + public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo + { + try { + $reflClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + + $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false; + $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags; + $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL); + $allowMagicGet = (bool) ($magicMethods & self::ALLOW_MAGIC_GET); + $hasProperty = $reflClass->hasProperty($property); + $camelProp = $this->camelize($property); + $getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item) + + foreach ($this->accessorPrefixes as $prefix) { + $methodName = $prefix.$camelProp; + + if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) { + $method = $reflClass->getMethod($methodName); + + return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), false); + } + } + + if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) { + $method = $reflClass->getMethod($getsetter); + + return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false); + } + + if ($allowMagicGet && $reflClass->hasMethod('__get') && (($r = $reflClass->getMethod('__get'))->getModifiers() & $this->methodReflectionFlags)) { + return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, $r->returnsReference()); + } + + if ($hasProperty && (($r = $reflClass->getProperty($property))->getModifiers() & $this->propertyReflectionFlags)) { + return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($r), $r->isStatic(), true); + } + + if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) { + return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, 'get'.$camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, false, false); + } + + return null; + } + + public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo + { + try { + $reflClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + + $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false; + $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags; + $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL); + $allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET); + $allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction; + $allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true; + + $camelized = $this->camelize($property); + $constructor = $reflClass->getConstructor(); + $singulars = $this->inflector->singularize($camelized); + $errors = []; + + if (null !== $constructor && $allowConstruct) { + foreach ($constructor->getParameters() as $parameter) { + if ($parameter->getName() === $property) { + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR, $property); + } + } + } + + [$adderAccessName, $removerAccessName, $adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass, $singulars); + if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) { + $adderMethod = $reflClass->getMethod($adderAccessName); + $removerMethod = $reflClass->getMethod($removerAccessName); + + $mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER); + $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic())); + $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic())); + + return $mutator; + } + + $errors[] = $adderAndRemoverErrors; + + foreach ($this->mutatorPrefixes as $mutatorPrefix) { + $methodName = $mutatorPrefix.$camelized; + + [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1); + if (!$accessible) { + $errors[] = $methodAccessibleErrors; + continue; + } + + $method = $reflClass->getMethod($methodName); + + if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) { + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + } + } + + $getsetter = lcfirst($camelized); + + if ($allowGetterSetter) { + [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1); + if ($accessible) { + $method = $reflClass->getMethod($getsetter); + + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + } + + $errors[] = $methodAccessibleErrors; + } + + if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) { + $reflProperty = $reflClass->getProperty($property); + if (!$reflProperty->isReadOnly()) { + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic()); + } + + $errors[] = [sprintf('The property "%s" in class "%s" is a promoted readonly property.', $property, $reflClass->getName())]; + $allowMagicSet = $allowMagicCall = false; + } + + if ($allowMagicSet) { + [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2); + if ($accessible) { + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, false); + } + + $errors[] = $methodAccessibleErrors; + } + + if ($allowMagicCall) { + [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2); + if ($accessible) { + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, 'set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, false); + } + + $errors[] = $methodAccessibleErrors; + } + + if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) { + $errors[] = [sprintf( + 'The property "%s" in class "%s" can be defined with the methods "%s()" but '. + 'the new value must be an array or an instance of \Traversable', + $property, + $reflClass->getName(), + implode('()", "', [$adderAccessName, $removerAccessName]) + )]; + } + + $noneProperty = new PropertyWriteInfo(); + $noneProperty->setErrors(array_merge([], ...$errors)); + + return $noneProperty; + } + + /** + * @return LegacyType[]|null + */ + private function extractFromMutator(string $class, string $property): ?array + { + [$reflectionMethod, $prefix] = $this->getMutatorMethod($class, $property); + if (null === $reflectionMethod) { + return null; + } + + $reflectionParameters = $reflectionMethod->getParameters(); + $reflectionParameter = $reflectionParameters[0]; + + if (!$reflectionType = $reflectionParameter->getType()) { + return null; + } + $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); + + if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes, true)) { + $type = [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $this->isNullableProperty($class, $property), null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $type[0])]; + } + + return $type; + } + + /** + * Tries to extract type information from accessors. + * + * @return LegacyType[]|null + */ + private function extractFromAccessor(string $class, string $property): ?array + { + [$reflectionMethod, $prefix] = $this->getAccessorMethod($class, $property); + if (null === $reflectionMethod) { + return null; + } + + if ($reflectionType = $reflectionMethod->getReturnType()) { + return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); + } + + if (\in_array($prefix, ['is', 'can', 'has'])) { + return [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)]; + } + + return null; + } + + /** + * Tries to extract type information from constructor. + * + * @return LegacyType[]|null + */ + private function extractFromConstructor(string $class, string $property): ?array + { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + + $constructor = $reflectionClass->getConstructor(); + + if (!$constructor) { + return null; + } + + foreach ($constructor->getParameters() as $parameter) { + if ($property !== $parameter->name) { + continue; + } + $reflectionType = $parameter->getType(); + + return $reflectionType ? $this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass()) : null; + } + + if ($parentClass = $reflectionClass->getParentClass()) { + return $this->extractFromConstructor($parentClass->getName(), $property); + } + + return null; + } + + private function extractFromPropertyDeclaration(string $class, string $property): ?array + { + try { + $reflectionClass = new \ReflectionClass($class); + + $reflectionProperty = $reflectionClass->getProperty($property); + $reflectionPropertyType = $reflectionProperty->getType(); + + if (null !== $reflectionPropertyType && $types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass())) { + return $types; + } + } catch (\ReflectionException) { + return null; + } + + $defaultValue = $reflectionClass->getDefaultProperties()[$property] ?? null; + + if (null === $defaultValue) { + return null; + } + + $type = \gettype($defaultValue); + $type = static::MAP_TYPES[$type] ?? $type; + + return [new LegacyType($type, $this->isNullableProperty($class, $property), null, LegacyType::BUILTIN_TYPE_ARRAY === $type)]; + } + + private function extractTypeFromConstructor(\ReflectionClass $reflectionClass, string $property): ?Type + { + if (!$constructor = $reflectionClass->getConstructor()) { + return null; + } + + foreach ($constructor->getParameters() as $parameter) { + if ($property !== $parameter->name) { + continue; + } + + try { + return $this->typeResolver->resolve($parameter); + } catch (UnsupportedException) { + } + } + + if ($parentClass = $reflectionClass->getParentClass()) { + return $this->extractTypeFromConstructor($parentClass, $property); + } + + return null; + } + + private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array + { + $types = []; + $nullable = $reflectionType->allowsNull(); + + foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) { + if (!$type instanceof \ReflectionNamedType) { + // Nested composite types are not supported yet. + return []; + } + + $phpTypeOrClass = $type->getName(); + if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) { + continue; + } + + if (LegacyType::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) { + $types[] = new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true); + } elseif ('void' === $phpTypeOrClass) { + $types[] = new LegacyType(LegacyType::BUILTIN_TYPE_NULL, $nullable); + } elseif ($type->isBuiltin()) { + $types[] = new LegacyType($phpTypeOrClass, $nullable); + } else { + $types[] = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass)); + } + } + + return $types; + } + + private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string + { + if ('self' === $lcName = strtolower($name)) { + return $declaringClass->name; + } + if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) { + return $parent->name; + } + + return $name; + } + + private function isNullableProperty(string $class, string $property): bool + { + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + + $reflectionPropertyType = $reflectionProperty->getType(); + + return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull(); + } catch (\ReflectionException) { + // Return false if the property doesn't exist + } + + return false; + } + + private function isAllowedProperty(string $class, string $property, bool $writeAccessRequired = false): bool + { + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + + if ($writeAccessRequired && $reflectionProperty->isReadOnly()) { + return false; + } + + return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags); + } catch (\ReflectionException) { + // Return false if the property doesn't exist + } + + return false; + } + + /** + * Gets the accessor method. + * + * Returns an array with a the instance of \ReflectionMethod as first key + * and the prefix of the method as second or null if not found. + */ + private function getAccessorMethod(string $class, string $property): ?array + { + $ucProperty = ucfirst($property); + + foreach ($this->accessorPrefixes as $prefix) { + try { + $reflectionMethod = new \ReflectionMethod($class, $prefix.$ucProperty); + if ($reflectionMethod->isStatic()) { + continue; + } + + if (0 === $reflectionMethod->getNumberOfRequiredParameters()) { + return [$reflectionMethod, $prefix]; + } + } catch (\ReflectionException) { + // Return null if the property doesn't exist + } + } + + return null; + } + + /** + * Returns an array with a the instance of \ReflectionMethod as first key + * and the prefix of the method as second or null if not found. + */ + private function getMutatorMethod(string $class, string $property): ?array + { + $ucProperty = ucfirst($property); + $ucSingulars = $this->inflector->singularize($ucProperty); + + $mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst; + + foreach ($mutatorPrefixes as $prefix) { + $names = [$ucProperty]; + if (\in_array($prefix, $this->arrayMutatorPrefixes, true)) { + $names = array_merge($names, $ucSingulars); + } + + foreach ($names as $name) { + try { + $reflectionMethod = new \ReflectionMethod($class, $prefix.$name); + if ($reflectionMethod->isStatic()) { + continue; + } + + // Parameter can be optional to allow things like: method(?array $foo = null) + if ($reflectionMethod->getNumberOfParameters() >= 1) { + return [$reflectionMethod, $prefix]; + } + } catch (\ReflectionException) { + // Try the next prefix if the method doesn't exist + } + } + } + + return null; + } + + private function getPropertyName(string $methodName, array $reflectionProperties): ?string + { + $pattern = implode('|', array_merge($this->accessorPrefixes, $this->mutatorPrefixes)); + + if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) { + if (!\in_array($matches[1], $this->arrayMutatorPrefixes, true)) { + return $matches[2]; + } + + foreach ($reflectionProperties as $reflectionProperty) { + foreach ($this->inflector->singularize($reflectionProperty->name) as $name) { + if (strtolower($name) === strtolower($matches[2])) { + return $reflectionProperty->name; + } + } + } + + return $matches[2]; + } + + return null; + } + + /** + * Searches for add and remove methods. + * + * @param \ReflectionClass $reflClass The reflection class for the given object + * @param array $singulars The singular form of the property name or null + * + * @return array An array containing the adder and remover when found and errors + */ + private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array + { + if (2 !== \count($this->arrayMutatorPrefixes)) { + return [null, null, []]; + } + + [$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes; + $errors = []; + + foreach ($singulars as $singular) { + $addMethod = $addPrefix.$singular; + $removeMethod = $removePrefix.$singular; + + [$addMethodFound, $addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $addMethod, 1); + [$removeMethodFound, $removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $removeMethod, 1); + $errors[] = $addMethodAccessibleErrors; + $errors[] = $removeMethodAccessibleErrors; + + if ($addMethodFound && $removeMethodFound) { + return [$addMethod, $removeMethod, []]; + } + + if ($addMethodFound && !$removeMethodFound) { + $errors[] = [sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $addMethod, $reflClass->getName(), $removeMethod)]; + } elseif (!$addMethodFound && $removeMethodFound) { + $errors[] = [sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $removeMethod, $reflClass->getName(), $addMethod)]; + } + } + + return [null, null, array_merge([], ...$errors)]; + } + + /** + * Returns whether a method is public and has the number of required parameters and errors. + */ + private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): array + { + $errors = []; + + if ($class->hasMethod($methodName)) { + $method = $class->getMethod($methodName); + + if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) { + $errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.', $methodName, $class->getName()); + } elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) { + $errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.', $methodName, $class->getName(), $method->getNumberOfRequiredParameters(), $parameters); + } else { + return [true, $errors]; + } + } + + return [false, $errors]; + } + + /** + * Camelizes a given string. + */ + private function camelize(string $string): string + { + return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); + } + + /** + * Return allowed reflection method flags. + */ + private function getMethodsFlags(int $accessFlags): int + { + $methodFlags = 0; + + if ($accessFlags & self::ALLOW_PUBLIC) { + $methodFlags |= \ReflectionMethod::IS_PUBLIC; + } + + if ($accessFlags & self::ALLOW_PRIVATE) { + $methodFlags |= \ReflectionMethod::IS_PRIVATE; + } + + if ($accessFlags & self::ALLOW_PROTECTED) { + $methodFlags |= \ReflectionMethod::IS_PROTECTED; + } + + return $methodFlags; + } + + /** + * Return allowed reflection property flags. + */ + private function getPropertyFlags(int $accessFlags): int + { + $propertyFlags = 0; + + if ($accessFlags & self::ALLOW_PUBLIC) { + $propertyFlags |= \ReflectionProperty::IS_PUBLIC; + } + + if ($accessFlags & self::ALLOW_PRIVATE) { + $propertyFlags |= \ReflectionProperty::IS_PRIVATE; + } + + if ($accessFlags & self::ALLOW_PROTECTED) { + $propertyFlags |= \ReflectionProperty::IS_PROTECTED; + } + + return $propertyFlags; + } + + private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string + { + if ($reflectionProperty->isPrivate()) { + return PropertyReadInfo::VISIBILITY_PRIVATE; + } + + if ($reflectionProperty->isProtected()) { + return PropertyReadInfo::VISIBILITY_PROTECTED; + } + + return PropertyReadInfo::VISIBILITY_PUBLIC; + } + + private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string + { + if ($reflectionMethod->isPrivate()) { + return PropertyReadInfo::VISIBILITY_PRIVATE; + } + + if ($reflectionMethod->isProtected()) { + return PropertyReadInfo::VISIBILITY_PROTECTED; + } + + return PropertyReadInfo::VISIBILITY_PUBLIC; + } + + private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string + { + if ($reflectionProperty->isPrivate()) { + return PropertyWriteInfo::VISIBILITY_PRIVATE; + } + + if ($reflectionProperty->isProtected()) { + return PropertyWriteInfo::VISIBILITY_PROTECTED; + } + + return PropertyWriteInfo::VISIBILITY_PUBLIC; + } + + private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string + { + if ($reflectionMethod->isPrivate()) { + return PropertyWriteInfo::VISIBILITY_PRIVATE; + } + + if ($reflectionMethod->isProtected()) { + return PropertyWriteInfo::VISIBILITY_PROTECTED; + } + + return PropertyWriteInfo::VISIBILITY_PUBLIC; + } +} diff --git a/vendor/symfony/property-info/Extractor/SerializerExtractor.php b/vendor/symfony/property-info/Extractor/SerializerExtractor.php new file mode 100644 index 0000000..620032f --- /dev/null +++ b/vendor/symfony/property-info/Extractor/SerializerExtractor.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Extractor; + +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; + +/** + * Lists available properties using Symfony Serializer Component metadata. + * + * @author Kévin Dunglas + * + * @final + */ +class SerializerExtractor implements PropertyListExtractorInterface +{ + public function __construct( + private readonly ClassMetadataFactoryInterface $classMetadataFactory, + ) { + } + + public function getProperties(string $class, array $context = []): ?array + { + if (!\array_key_exists('serializer_groups', $context) || (null !== $context['serializer_groups'] && !\is_array($context['serializer_groups']))) { + return null; + } + + if (!$this->classMetadataFactory->getMetadataFor($class)) { + return null; + } + + $groups = $context['serializer_groups'] ?? []; + $groupsHasBeenDefined = [] !== $groups; + $groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); + + $properties = []; + $serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class); + + foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { + if (!$serializerAttributeMetadata->isIgnored() && (!$groupsHasBeenDefined || array_intersect(array_merge($serializerAttributeMetadata->getGroups(), ['*']), $groups))) { + $properties[] = $serializerAttributeMetadata->getName(); + } + } + + return $properties; + } +} diff --git a/vendor/symfony/property-info/LICENSE b/vendor/symfony/property-info/LICENSE new file mode 100644 index 0000000..6e3afce --- /dev/null +++ b/vendor/symfony/property-info/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +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. diff --git a/vendor/symfony/property-info/PhpStan/NameScope.php b/vendor/symfony/property-info/PhpStan/NameScope.php new file mode 100644 index 0000000..e4e713c --- /dev/null +++ b/vendor/symfony/property-info/PhpStan/NameScope.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\PhpStan; + +/** + * NameScope class adapted from PHPStan code. + * + * @copyright Copyright (c) 2016, PHPStan https://github.com/phpstan/phpstan-src + * @copyright Copyright (c) 2016, OndÅ™ej Mirtes + * @author Baptiste Leduc + * + * @internal + */ +final class NameScope +{ + private string $calledClassName; + private string $namespace; + /** @var array alias(string) => fullName(string) */ + private array $uses; + + public function __construct(string $calledClassName, string $namespace, array $uses = []) + { + $this->calledClassName = $calledClassName; + $this->namespace = $namespace; + $this->uses = $uses; + } + + public function resolveStringName(string $name): string + { + if (str_starts_with($name, '\\')) { + return ltrim($name, '\\'); + } + + $nameParts = explode('\\', $name); + $firstNamePart = $nameParts[0]; + if (isset($this->uses[$firstNamePart])) { + if (1 === \count($nameParts)) { + return $this->uses[$firstNamePart]; + } + array_shift($nameParts); + + return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts)); + } + + return sprintf('%s\\%s', $this->namespace, $name); + } + + public function resolveRootClass(): string + { + return $this->resolveStringName($this->calledClassName); + } +} diff --git a/vendor/symfony/property-info/PhpStan/NameScopeFactory.php b/vendor/symfony/property-info/PhpStan/NameScopeFactory.php new file mode 100644 index 0000000..162a72a --- /dev/null +++ b/vendor/symfony/property-info/PhpStan/NameScopeFactory.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\PhpStan; + +use phpDocumentor\Reflection\Types\ContextFactory; + +/** + * @author Baptiste Leduc + * + * @internal + */ +final class NameScopeFactory +{ + public function create(string $calledClassName, ?string $declaringClassName = null): NameScope + { + $declaringClassName ??= $calledClassName; + + $path = explode('\\', $calledClassName); + $calledClassName = array_pop($path); + + $declaringReflection = new \ReflectionClass($declaringClassName); + [$declaringNamespace, $declaringUses] = $this->extractFromFullClassName($declaringReflection); + $declaringUses = array_merge($declaringUses, $this->collectUses($declaringReflection)); + + return new NameScope($calledClassName, $declaringNamespace, $declaringUses); + } + + private function collectUses(\ReflectionClass $reflection): array + { + $uses = [$this->extractFromFullClassName($reflection)[1]]; + + foreach ($reflection->getTraits() as $traitReflection) { + $uses[] = $this->extractFromFullClassName($traitReflection)[1]; + } + + if (false !== $parentClass = $reflection->getParentClass()) { + $uses[] = $this->collectUses($parentClass); + } + + return $uses ? array_merge(...$uses) : []; + } + + private function extractFromFullClassName(\ReflectionClass $reflection): array + { + $namespace = trim($reflection->getNamespaceName(), '\\'); + $fileName = $reflection->getFileName(); + + if (\is_string($fileName) && is_file($fileName)) { + if (false === $contents = file_get_contents($fileName)) { + throw new \RuntimeException(sprintf('Unable to read file "%s".', $fileName)); + } + + $factory = new ContextFactory(); + $context = $factory->createForNamespace($namespace, $contents); + + return [$namespace, $context->getNamespaceAliases()]; + } + + return [$namespace, []]; + } +} diff --git a/vendor/symfony/property-info/PropertyAccessExtractorInterface.php b/vendor/symfony/property-info/PropertyAccessExtractorInterface.php new file mode 100644 index 0000000..290d81c --- /dev/null +++ b/vendor/symfony/property-info/PropertyAccessExtractorInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Guesses if the property can be accessed or mutated. + * + * @author Kévin Dunglas + */ +interface PropertyAccessExtractorInterface +{ + /** + * Is the property readable? + */ + public function isReadable(string $class, string $property, array $context = []): ?bool; + + /** + * Is the property writable? + */ + public function isWritable(string $class, string $property, array $context = []): ?bool; +} diff --git a/vendor/symfony/property-info/PropertyDescriptionExtractorInterface.php b/vendor/symfony/property-info/PropertyDescriptionExtractorInterface.php new file mode 100644 index 0000000..a779d15 --- /dev/null +++ b/vendor/symfony/property-info/PropertyDescriptionExtractorInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Guesses the property's human readable description. + * + * @author Kévin Dunglas + */ +interface PropertyDescriptionExtractorInterface +{ + /** + * Gets the short description of the property. + */ + public function getShortDescription(string $class, string $property, array $context = []): ?string; + + /** + * Gets the long description of the property. + */ + public function getLongDescription(string $class, string $property, array $context = []): ?string; +} diff --git a/vendor/symfony/property-info/PropertyDocBlockExtractorInterface.php b/vendor/symfony/property-info/PropertyDocBlockExtractorInterface.php new file mode 100644 index 0000000..4a51d7b --- /dev/null +++ b/vendor/symfony/property-info/PropertyDocBlockExtractorInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +use phpDocumentor\Reflection\DocBlock; + +/** + * Extract a property's doc block. + * + * A property's doc block may be located on a constructor promoted argument, on + * the property or on a mutator for that property. + * + * @author Tobias Nyholm + */ +interface PropertyDocBlockExtractorInterface +{ + /** + * Gets the first available doc block for a property. It finds the doc block + * by the following priority: + * - constructor promoted argument + * - the class property + * - a mutator method for that property + * + * If no doc block is found, it will return null. + */ + public function getDocBlock(string $class, string $property): ?DocBlock; +} diff --git a/vendor/symfony/property-info/PropertyInfoCacheExtractor.php b/vendor/symfony/property-info/PropertyInfoCacheExtractor.php new file mode 100644 index 0000000..38b9c68 --- /dev/null +++ b/vendor/symfony/property-info/PropertyInfoCacheExtractor.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\TypeInfo\Type; + +/** + * Adds a PSR-6 cache layer on top of an extractor. + * + * @author Kévin Dunglas + * + * @final + */ +class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface +{ + private array $arrayCache = []; + + public function __construct( + private readonly PropertyInfoExtractorInterface $propertyInfoExtractor, + private readonly CacheItemPoolInterface $cacheItemPool, + ) { + } + + public function isReadable(string $class, string $property, array $context = []): ?bool + { + return $this->extract('isReadable', [$class, $property, $context]); + } + + public function isWritable(string $class, string $property, array $context = []): ?bool + { + return $this->extract('isWritable', [$class, $property, $context]); + } + + public function getShortDescription(string $class, string $property, array $context = []): ?string + { + return $this->extract('getShortDescription', [$class, $property, $context]); + } + + public function getLongDescription(string $class, string $property, array $context = []): ?string + { + return $this->extract('getLongDescription', [$class, $property, $context]); + } + + public function getProperties(string $class, array $context = []): ?array + { + return $this->extract('getProperties', [$class, $context]); + } + + /** + * @experimental + */ + public function getType(string $class, string $property, array $context = []): ?Type + { + return $this->extract('getType', [$class, $property, $context]); + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + return $this->extract('getTypes', [$class, $property, $context]); + } + + public function isInitializable(string $class, string $property, array $context = []): ?bool + { + return $this->extract('isInitializable', [$class, $property, $context]); + } + + /** + * Retrieves the cached data if applicable or delegates to the decorated extractor. + */ + private function extract(string $method, array $arguments): mixed + { + try { + $serializedArguments = serialize($arguments); + } catch (\Exception) { + // If arguments are not serializable, skip the cache + return $this->propertyInfoExtractor->{$method}(...$arguments); + } + + // Calling rawurlencode escapes special characters not allowed in PSR-6's keys + $key = rawurlencode($method.'.'.$serializedArguments); + + if (\array_key_exists($key, $this->arrayCache)) { + return $this->arrayCache[$key]; + } + + $item = $this->cacheItemPool->getItem($key); + + if ($item->isHit()) { + return $this->arrayCache[$key] = $item->get(); + } + + $value = $this->propertyInfoExtractor->{$method}(...$arguments); + $item->set($value); + $this->cacheItemPool->save($item); + + return $this->arrayCache[$key] = $value; + } +} diff --git a/vendor/symfony/property-info/PropertyInfoExtractor.php b/vendor/symfony/property-info/PropertyInfoExtractor.php new file mode 100644 index 0000000..8e8952c --- /dev/null +++ b/vendor/symfony/property-info/PropertyInfoExtractor.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +use Symfony\Component\TypeInfo\Type; + +/** + * Default {@see PropertyInfoExtractorInterface} implementation. + * + * @author Kévin Dunglas + * + * @final + */ +class PropertyInfoExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface +{ + /** + * @param iterable $listExtractors + * @param iterable $typeExtractors + * @param iterable $descriptionExtractors + * @param iterable $accessExtractors + * @param iterable $initializableExtractors + */ + public function __construct( + private readonly iterable $listExtractors = [], + private readonly iterable $typeExtractors = [], + private readonly iterable $descriptionExtractors = [], + private readonly iterable $accessExtractors = [], + private readonly iterable $initializableExtractors = [], + ) { + } + + public function getProperties(string $class, array $context = []): ?array + { + return $this->extract($this->listExtractors, 'getProperties', [$class, $context]); + } + + public function getShortDescription(string $class, string $property, array $context = []): ?string + { + return $this->extract($this->descriptionExtractors, 'getShortDescription', [$class, $property, $context]); + } + + public function getLongDescription(string $class, string $property, array $context = []): ?string + { + return $this->extract($this->descriptionExtractors, 'getLongDescription', [$class, $property, $context]); + } + + /** + * @experimental + */ + public function getType(string $class, string $property, array $context = []): ?Type + { + return $this->extract($this->typeExtractors, 'getType', [$class, $property, $context]); + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + return $this->extract($this->typeExtractors, 'getTypes', [$class, $property, $context]); + } + + public function isReadable(string $class, string $property, array $context = []): ?bool + { + return $this->extract($this->accessExtractors, 'isReadable', [$class, $property, $context]); + } + + public function isWritable(string $class, string $property, array $context = []): ?bool + { + return $this->extract($this->accessExtractors, 'isWritable', [$class, $property, $context]); + } + + public function isInitializable(string $class, string $property, array $context = []): ?bool + { + return $this->extract($this->initializableExtractors, 'isInitializable', [$class, $property, $context]); + } + + /** + * Iterates over registered extractors and return the first value found. + * + * @param iterable $extractors + * @param list $arguments + */ + private function extract(iterable $extractors, string $method, array $arguments): mixed + { + foreach ($extractors as $extractor) { + if (null !== $value = $extractor->{$method}(...$arguments)) { + return $value; + } + } + + return null; + } +} diff --git a/vendor/symfony/property-info/PropertyInfoExtractorInterface.php b/vendor/symfony/property-info/PropertyInfoExtractorInterface.php new file mode 100644 index 0000000..8893018 --- /dev/null +++ b/vendor/symfony/property-info/PropertyInfoExtractorInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Gets info about PHP class properties. + * + * A convenient interface inheriting all specific info interfaces. + * + * @author Kévin Dunglas + */ +interface PropertyInfoExtractorInterface extends PropertyTypeExtractorInterface, PropertyDescriptionExtractorInterface, PropertyAccessExtractorInterface, PropertyListExtractorInterface +{ +} diff --git a/vendor/symfony/property-info/PropertyInitializableExtractorInterface.php b/vendor/symfony/property-info/PropertyInitializableExtractorInterface.php new file mode 100644 index 0000000..13248fc --- /dev/null +++ b/vendor/symfony/property-info/PropertyInitializableExtractorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Guesses if the property can be initialized through the constructor. + * + * @author Kévin Dunglas + */ +interface PropertyInitializableExtractorInterface +{ + /** + * Is the property initializable? Returns true if a constructor's parameter matches the given property name. + */ + public function isInitializable(string $class, string $property, array $context = []): ?bool; +} diff --git a/vendor/symfony/property-info/PropertyListExtractorInterface.php b/vendor/symfony/property-info/PropertyListExtractorInterface.php new file mode 100644 index 0000000..ae7c6b6 --- /dev/null +++ b/vendor/symfony/property-info/PropertyListExtractorInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Extracts the list of properties available for the given class. + * + * @author Kévin Dunglas + */ +interface PropertyListExtractorInterface +{ + /** + * Gets the list of properties available for the given class. + * + * @return string[]|null + */ + public function getProperties(string $class, array $context = []): ?array; +} diff --git a/vendor/symfony/property-info/PropertyReadInfo.php b/vendor/symfony/property-info/PropertyReadInfo.php new file mode 100644 index 0000000..8de070d --- /dev/null +++ b/vendor/symfony/property-info/PropertyReadInfo.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * The property read info tells how a property can be read. + * + * @author Joel Wurtz + * + * @internal + */ +final class PropertyReadInfo +{ + public const TYPE_METHOD = 'method'; + public const TYPE_PROPERTY = 'property'; + + public const VISIBILITY_PUBLIC = 'public'; + public const VISIBILITY_PROTECTED = 'protected'; + public const VISIBILITY_PRIVATE = 'private'; + + public function __construct( + private readonly string $type, + private readonly string $name, + private readonly string $visibility, + private readonly bool $static, + private readonly bool $byRef, + ) { + } + + /** + * Get type of access. + */ + public function getType(): string + { + return $this->type; + } + + /** + * Get name of the access, which can be a method name or a property name, depending on the type. + */ + public function getName(): string + { + return $this->name; + } + + public function getVisibility(): string + { + return $this->visibility; + } + + public function isStatic(): bool + { + return $this->static; + } + + /** + * Whether this accessor can be accessed by reference. + */ + public function canBeReference(): bool + { + return $this->byRef; + } +} diff --git a/vendor/symfony/property-info/PropertyReadInfoExtractorInterface.php b/vendor/symfony/property-info/PropertyReadInfoExtractorInterface.php new file mode 100644 index 0000000..816b282 --- /dev/null +++ b/vendor/symfony/property-info/PropertyReadInfoExtractorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Extract read information for the property of a class. + * + * @author Joel Wurtz + */ +interface PropertyReadInfoExtractorInterface +{ + /** + * Get read information object for a given property of a class. + */ + public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo; +} diff --git a/vendor/symfony/property-info/PropertyTypeExtractorInterface.php b/vendor/symfony/property-info/PropertyTypeExtractorInterface.php new file mode 100644 index 0000000..c986aaf --- /dev/null +++ b/vendor/symfony/property-info/PropertyTypeExtractorInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; + +/** + * Type Extractor Interface. + * + * @author Kévin Dunglas + * + * @method Type|null getType(string $class, string $property, array $context = []) + */ +interface PropertyTypeExtractorInterface +{ + /** + * Gets types of a property. + * + * @return LegacyType[]|null + */ + public function getTypes(string $class, string $property, array $context = []): ?array; +} diff --git a/vendor/symfony/property-info/PropertyWriteInfo.php b/vendor/symfony/property-info/PropertyWriteInfo.php new file mode 100644 index 0000000..6bc7abc --- /dev/null +++ b/vendor/symfony/property-info/PropertyWriteInfo.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * The write mutator defines how a property can be written. + * + * @author Joel Wurtz + * + * @internal + */ +final class PropertyWriteInfo +{ + public const TYPE_NONE = 'none'; + public const TYPE_METHOD = 'method'; + public const TYPE_PROPERTY = 'property'; + public const TYPE_ADDER_AND_REMOVER = 'adder_and_remover'; + public const TYPE_CONSTRUCTOR = 'constructor'; + + public const VISIBILITY_PUBLIC = 'public'; + public const VISIBILITY_PROTECTED = 'protected'; + public const VISIBILITY_PRIVATE = 'private'; + + private ?self $adderInfo = null; + private ?self $removerInfo = null; + private array $errors = []; + + public function __construct( + private readonly string $type = self::TYPE_NONE, + private readonly ?string $name = null, + private readonly ?string $visibility = null, + private readonly ?bool $static = null, + ) { + } + + public function getType(): string + { + return $this->type; + } + + public function getName(): string + { + if (null === $this->name) { + throw new \LogicException("Calling getName() when having a mutator of type {$this->type} is not tolerated."); + } + + return $this->name; + } + + public function setAdderInfo(self $adderInfo): void + { + $this->adderInfo = $adderInfo; + } + + public function getAdderInfo(): self + { + if (null === $this->adderInfo) { + throw new \LogicException("Calling getAdderInfo() when having a mutator of type {$this->type} is not tolerated."); + } + + return $this->adderInfo; + } + + public function setRemoverInfo(self $removerInfo): void + { + $this->removerInfo = $removerInfo; + } + + public function getRemoverInfo(): self + { + if (null === $this->removerInfo) { + throw new \LogicException("Calling getRemoverInfo() when having a mutator of type {$this->type} is not tolerated."); + } + + return $this->removerInfo; + } + + public function getVisibility(): string + { + if (null === $this->visibility) { + throw new \LogicException("Calling getVisibility() when having a mutator of type {$this->type} is not tolerated."); + } + + return $this->visibility; + } + + public function isStatic(): bool + { + if (null === $this->static) { + throw new \LogicException("Calling isStatic() when having a mutator of type {$this->type} is not tolerated."); + } + + return $this->static; + } + + public function setErrors(array $errors): void + { + $this->errors = $errors; + } + + public function getErrors(): array + { + return $this->errors; + } + + public function hasErrors(): bool + { + return (bool) \count($this->errors); + } +} diff --git a/vendor/symfony/property-info/PropertyWriteInfoExtractorInterface.php b/vendor/symfony/property-info/PropertyWriteInfoExtractorInterface.php new file mode 100644 index 0000000..f113463 --- /dev/null +++ b/vendor/symfony/property-info/PropertyWriteInfoExtractorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Extract write information for the property of a class. + * + * @author Joel Wurtz + */ +interface PropertyWriteInfoExtractorInterface +{ + /** + * Get write information object for a given property of a class. + */ + public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo; +} diff --git a/vendor/symfony/property-info/README.md b/vendor/symfony/property-info/README.md new file mode 100644 index 0000000..da3514f --- /dev/null +++ b/vendor/symfony/property-info/README.md @@ -0,0 +1,14 @@ +PropertyInfo Component +====================== + +The PropertyInfo component extracts information about PHP class' properties +using metadata of popular sources. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/property_info.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/property-info/Type.php b/vendor/symfony/property-info/Type.php new file mode 100644 index 0000000..1ce7130 --- /dev/null +++ b/vendor/symfony/property-info/Type.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo; + +/** + * Type value object (immutable). + * + * @author Kévin Dunglas + * + * @final + */ +class Type +{ + public const BUILTIN_TYPE_INT = 'int'; + public const BUILTIN_TYPE_FLOAT = 'float'; + public const BUILTIN_TYPE_STRING = 'string'; + public const BUILTIN_TYPE_BOOL = 'bool'; + public const BUILTIN_TYPE_RESOURCE = 'resource'; + public const BUILTIN_TYPE_OBJECT = 'object'; + public const BUILTIN_TYPE_ARRAY = 'array'; + public const BUILTIN_TYPE_NULL = 'null'; + public const BUILTIN_TYPE_FALSE = 'false'; + public const BUILTIN_TYPE_TRUE = 'true'; + public const BUILTIN_TYPE_CALLABLE = 'callable'; + public const BUILTIN_TYPE_ITERABLE = 'iterable'; + + /** + * List of PHP builtin types. + * + * @var string[] + */ + public static array $builtinTypes = [ + self::BUILTIN_TYPE_INT, + self::BUILTIN_TYPE_FLOAT, + self::BUILTIN_TYPE_STRING, + self::BUILTIN_TYPE_BOOL, + self::BUILTIN_TYPE_RESOURCE, + self::BUILTIN_TYPE_OBJECT, + self::BUILTIN_TYPE_ARRAY, + self::BUILTIN_TYPE_CALLABLE, + self::BUILTIN_TYPE_FALSE, + self::BUILTIN_TYPE_TRUE, + self::BUILTIN_TYPE_NULL, + self::BUILTIN_TYPE_ITERABLE, + ]; + + /** + * List of PHP builtin collection types. + * + * @var string[] + */ + public static array $builtinCollectionTypes = [ + self::BUILTIN_TYPE_ARRAY, + self::BUILTIN_TYPE_ITERABLE, + ]; + + private string $builtinType; + private bool $nullable; + private ?string $class; + private bool $collection; + private array $collectionKeyType; + private array $collectionValueType; + + /** + * @param Type[]|Type|null $collectionKeyType + * @param Type[]|Type|null $collectionValueType + * + * @throws \InvalidArgumentException + */ + public function __construct(string $builtinType, bool $nullable = false, ?string $class = null, bool $collection = false, array|self|null $collectionKeyType = null, array|self|null $collectionValueType = null) + { + if (!\in_array($builtinType, self::$builtinTypes, true)) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid PHP type.', $builtinType)); + } + + $this->builtinType = $builtinType; + $this->nullable = $nullable; + $this->class = $class; + $this->collection = $collection; + $this->collectionKeyType = $this->validateCollectionArgument($collectionKeyType, 5, '$collectionKeyType') ?? []; + $this->collectionValueType = $this->validateCollectionArgument($collectionValueType, 6, '$collectionValueType') ?? []; + } + + private function validateCollectionArgument(array|self|null $collectionArgument, int $argumentIndex, string $argumentName): ?array + { + if (null === $collectionArgument) { + return null; + } + + if (\is_array($collectionArgument)) { + foreach ($collectionArgument as $type) { + if (!$type instanceof self) { + throw new \TypeError(sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", array value "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, get_debug_type($collectionArgument))); + } + } + + return $collectionArgument; + } + + return [$collectionArgument]; + } + + /** + * Gets built-in type. + * + * Can be bool, int, float, string, array, object, resource, null, callback or iterable. + */ + public function getBuiltinType(): string + { + return $this->builtinType; + } + + public function isNullable(): bool + { + return $this->nullable; + } + + /** + * Gets the class name. + * + * Only applicable if the built-in type is object. + */ + public function getClassName(): ?string + { + return $this->class; + } + + public function isCollection(): bool + { + return $this->collection; + } + + /** + * Gets collection key types. + * + * Only applicable for a collection type. + * + * @return Type[] + */ + public function getCollectionKeyTypes(): array + { + return $this->collectionKeyType; + } + + /** + * Gets collection value types. + * + * Only applicable for a collection type. + * + * @return Type[] + */ + public function getCollectionValueTypes(): array + { + return $this->collectionValueType; + } +} diff --git a/vendor/symfony/property-info/Util/PhpDocTypeHelper.php b/vendor/symfony/property-info/Util/PhpDocTypeHelper.php new file mode 100644 index 0000000..65b5397 --- /dev/null +++ b/vendor/symfony/property-info/Util/PhpDocTypeHelper.php @@ -0,0 +1,350 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Util; + +use phpDocumentor\Reflection\PseudoType; +use phpDocumentor\Reflection\PseudoTypes\ConstExpression; +use phpDocumentor\Reflection\PseudoTypes\List_; +use phpDocumentor\Reflection\Type as DocType; +use phpDocumentor\Reflection\Types\Array_; +use phpDocumentor\Reflection\Types\Collection; +use phpDocumentor\Reflection\Types\Compound; +use phpDocumentor\Reflection\Types\Integer; +use phpDocumentor\Reflection\Types\Null_; +use phpDocumentor\Reflection\Types\Nullable; +use phpDocumentor\Reflection\Types\String_; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +// Workaround for phpdocumentor/type-resolver < 1.6 +// We trigger the autoloader here, so we don't need to trigger it inside the loop later. +class_exists(List_::class); + +/** + * Transforms a php doc type to a {@link Type} instance. + * + * @author Kévin Dunglas + * @author Guilhem N. + */ +final class PhpDocTypeHelper +{ + /** + * Creates a {@see LegacyType} from a PHPDoc type. + * + * @return LegacyType[] + */ + public function getTypes(DocType $varType): array + { + if ($varType instanceof ConstExpression) { + // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment + return []; + } + + $types = []; + $nullable = false; + + if ($varType instanceof Nullable) { + $nullable = true; + $varType = $varType->getActualType(); + } + + if (!$varType instanceof Compound) { + if ($varType instanceof Null_) { + $nullable = true; + } + + $type = $this->createLegacyType($varType, $nullable); + if (null !== $type) { + $types[] = $type; + } + + return $types; + } + + $varTypes = []; + for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) { + $type = $varType->get($typeIndex); + + if ($type instanceof ConstExpression) { + // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment + return []; + } + + // If null is present, all types are nullable + if ($type instanceof Null_) { + $nullable = true; + continue; + } + + if ($type instanceof Nullable) { + $nullable = true; + $type = $type->getActualType(); + } + + $varTypes[] = $type; + } + + foreach ($varTypes as $varType) { + $type = $this->createLegacyType($varType, $nullable); + if (null !== $type) { + $types[] = $type; + } + } + + return $types; + } + + /** + * Creates a {@see Type} from a PHPDoc type. + * + * @experimental + */ + public function getType(DocType $varType): ?Type + { + if ($varType instanceof ConstExpression) { + // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment + return null; + } + + $nullable = false; + + if ($varType instanceof Nullable) { + $nullable = true; + $varType = $varType->getActualType(); + } + + if (!$varType instanceof Compound) { + if ($varType instanceof Null_) { + $nullable = true; + } + + return $this->createType($varType, $nullable); + } + + $varTypes = []; + for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) { + $type = $varType->get($typeIndex); + + if ($type instanceof ConstExpression) { + // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment + return null; + } + + // If null is present, all types are nullable + if ($type instanceof Null_) { + $nullable = true; + continue; + } + + if ($type instanceof Nullable) { + $nullable = true; + $type = $type->getActualType(); + } + + $varTypes[] = $type; + } + + $unionTypes = []; + foreach ($varTypes as $varType) { + $t = $this->createType($varType, $nullable); + if (null !== $t) { + $unionTypes[] = $t; + } + } + + $type = 1 === \count($unionTypes) ? $unionTypes[0] : Type::union(...$unionTypes); + + return $nullable ? Type::nullable($type) : $type; + } + + /** + * Creates a {@see LegacyType} from a PHPDoc type. + */ + private function createLegacyType(DocType $type, bool $nullable): ?LegacyType + { + $docType = (string) $type; + + if ($type instanceof Collection) { + $fqsen = $type->getFqsen(); + if ($fqsen && 'list' === $fqsen->getName() && !class_exists(List_::class, false) && !class_exists((string) $fqsen)) { + // Workaround for phpdocumentor/type-resolver < 1.6 + return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $this->getTypes($type->getValueType())); + } + + [$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen); + + $collection = \is_a($class, \Traversable::class, true) || \is_a($class, \ArrayAccess::class, true); + + // it's safer to fall back to other extractors if the generic type is too abstract + if (!$collection && !class_exists($class)) { + return null; + } + + $keys = $this->getTypes($type->getKeyType()); + $values = $this->getTypes($type->getValueType()); + + return new LegacyType($phpType, $nullable, $class, $collection, $keys, $values); + } + + // Cannot guess + if (!$docType || 'mixed' === $docType) { + return null; + } + + if (str_ends_with($docType, '[]') && $type instanceof Array_) { + $collectionKeyTypes = new LegacyType(LegacyType::BUILTIN_TYPE_INT); + $collectionValueTypes = $this->getTypes($type->getValueType()); + + return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes); + } + + if ((str_starts_with($docType, 'list<') || str_starts_with($docType, 'array<')) && $type instanceof Array_) { + // array is converted to x[] which is handled above + // so it's only necessary to handle array here + $collectionKeyTypes = $this->getTypes($type->getKeyType()); + $collectionValueTypes = $this->getTypes($type->getValueType()); + + return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes); + } + + if ($type instanceof PseudoType) { + if ($type->underlyingType() instanceof Integer) { + return new LegacyType(LegacyType::BUILTIN_TYPE_INT, $nullable, null); + } elseif ($type->underlyingType() instanceof String_) { + return new LegacyType(LegacyType::BUILTIN_TYPE_STRING, $nullable, null); + } + } + + $docType = $this->normalizeType($docType); + [$phpType, $class] = $this->getPhpTypeAndClass($docType); + + if ('array' === $docType) { + return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null); + } + + return new LegacyType($phpType, $nullable, $class); + } + + /** + * Creates a {@see Type} from a PHPDoc type. + */ + private function createType(DocType $docType, bool $nullable): ?Type + { + $docTypeString = (string) $docType; + + if ($docType instanceof Collection) { + $fqsen = $docType->getFqsen(); + if ($fqsen && 'list' === $fqsen->getName() && !class_exists(List_::class, false) && !class_exists((string) $fqsen)) { + // Workaround for phpdocumentor/type-resolver < 1.6 + return Type::list($this->getType($docType->getValueType())); + } + + [$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen); + + $variableTypes = []; + + if (null !== $valueType = $this->getType($docType->getValueType())) { + $variableTypes[] = $valueType; + } + + if (null !== $keyType = $this->getType($docType->getKeyType())) { + $variableTypes[] = $keyType; + } + + $type = null !== $class ? Type::object($class) : Type::builtin($phpType); + $type = Type::collection($type, ...$variableTypes); + + return $nullable ? Type::nullable($type) : $type; + } + + if (!$docTypeString) { + return null; + } + + if (str_ends_with($docTypeString, '[]') && $docType instanceof Array_) { + return Type::list($this->getType($docType->getValueType())); + } + + if (str_starts_with($docTypeString, 'list<') && $docType instanceof Array_) { + $collectionValueType = $this->getType($docType->getValueType()); + $type = Type::list($collectionValueType); + + return $nullable ? Type::nullable($type) : $type; + } + + if (str_starts_with($docTypeString, 'array<') && $docType instanceof Array_) { + // array is converted to x[] which is handled above + // so it's only necessary to handle array here + $collectionKeyType = $this->getType($docType->getKeyType()); + $collectionValueType = $this->getType($docType->getValueType()); + + $type = Type::array($collectionValueType, $collectionKeyType); + + return $nullable ? Type::nullable($type) : $type; + } + + if ($docType instanceof PseudoType) { + if ($docType->underlyingType() instanceof Integer) { + return $nullable ? Type::nullable(Type::int()) : Type::int(); + } elseif ($docType->underlyingType() instanceof String_) { + return $nullable ? Type::nullable(Type::string()) : Type::string(); + } + } + + $docTypeString = match ($docTypeString) { + 'integer' => 'int', + 'boolean' => 'bool', + // real is not part of the PHPDoc standard, so we ignore it + 'double' => 'float', + 'callback' => 'callable', + 'void' => 'null', + default => $docTypeString, + }; + + [$phpType, $class] = $this->getPhpTypeAndClass($docTypeString); + + if ('array' === $docTypeString) { + return $nullable ? Type::nullable(Type::array()) : Type::array(); + } + + $type = null !== $class ? Type::object($class) : Type::builtin($phpType); + + return $nullable ? Type::nullable($type) : $type; + } + + private function normalizeType(string $docType): string + { + return match ($docType) { + 'integer' => 'int', + 'boolean' => 'bool', + // real is not part of the PHPDoc standard, so we ignore it + 'double' => 'float', + 'callback' => 'callable', + 'void' => 'null', + default => $docType, + }; + } + + private function getPhpTypeAndClass(string $docType): array + { + if (\in_array($docType, TypeIdentifier::values(), true)) { + return [$docType, null]; + } + + if (\in_array($docType, ['parent', 'self', 'static'], true)) { + return ['object', $docType]; + } + + return ['object', ltrim($docType, '\\')]; + } +} diff --git a/vendor/symfony/property-info/Util/PhpStanTypeHelper.php b/vendor/symfony/property-info/Util/PhpStanTypeHelper.php new file mode 100644 index 0000000..6d06d8b --- /dev/null +++ b/vendor/symfony/property-info/Util/PhpStanTypeHelper.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Util; + +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; +use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; +use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; +use Symfony\Component\PropertyInfo\PhpStan\NameScope; +use Symfony\Component\PropertyInfo\Type; + +/** + * Transforms a php doc tag value to a {@link Type} instance. + * + * @author Baptiste Leduc + * + * @internal + */ +final class PhpStanTypeHelper +{ + /** + * Creates a {@see Type} from a PhpDocTagValueNode type. + * + * @return Type[] + */ + public function getTypes(PhpDocTagValueNode $node, NameScope $nameScope): array + { + if ($node instanceof ParamTagValueNode || $node instanceof ReturnTagValueNode || $node instanceof VarTagValueNode) { + return $this->compressNullableType($this->extractTypes($node->type, $nameScope)); + } + + return []; + } + + /** + * Because PhpStan extract null as a separated type when Symfony / PHP compress it in the first available type we + * need this method to mimic how Symfony want null types. + * + * @param Type[] $types + * + * @return Type[] + */ + private function compressNullableType(array $types): array + { + $firstTypeIndex = null; + $nullableTypeIndex = null; + + foreach ($types as $k => $type) { + if (null === $firstTypeIndex && Type::BUILTIN_TYPE_NULL !== $type->getBuiltinType() && !$type->isNullable()) { + $firstTypeIndex = $k; + } + + if (null === $nullableTypeIndex && Type::BUILTIN_TYPE_NULL === $type->getBuiltinType()) { + $nullableTypeIndex = $k; + } + + if (null !== $firstTypeIndex && null !== $nullableTypeIndex) { + break; + } + } + + if (null !== $firstTypeIndex && null !== $nullableTypeIndex) { + $firstType = $types[$firstTypeIndex]; + $types[$firstTypeIndex] = new Type( + $firstType->getBuiltinType(), + true, + $firstType->getClassName(), + $firstType->isCollection(), + $firstType->getCollectionKeyTypes(), + $firstType->getCollectionValueTypes() + ); + unset($types[$nullableTypeIndex]); + } + + return array_values($types); + } + + /** + * @return Type[] + */ + private function extractTypes(TypeNode $node, NameScope $nameScope): array + { + if ($node instanceof UnionTypeNode) { + $types = []; + foreach ($node->types as $type) { + if ($type instanceof ConstTypeNode) { + // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment + return []; + } + foreach ($this->extractTypes($type, $nameScope) as $subType) { + $types[] = $subType; + } + } + + return $this->compressNullableType($types); + } + if ($node instanceof GenericTypeNode) { + if ('class-string' === $node->type->name) { + return [new Type(Type::BUILTIN_TYPE_STRING)]; + } + + [$mainType] = $this->extractTypes($node->type, $nameScope); + + if (Type::BUILTIN_TYPE_INT === $mainType->getBuiltinType()) { + return [$mainType]; + } + + $collection = $mainType->isCollection() || \is_a($mainType->getClassName(), \Traversable::class, true) || \is_a($mainType->getClassName(), \ArrayAccess::class, true); + + // it's safer to fall back to other extractors if the generic type is too abstract + if (!$collection && !class_exists($mainType->getClassName())) { + return []; + } + + $collectionKeyTypes = $mainType->getCollectionKeyTypes(); + $collectionKeyValues = []; + if (1 === \count($node->genericTypes)) { + foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $subType) { + $collectionKeyValues[] = $subType; + } + } elseif (2 === \count($node->genericTypes)) { + foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $keySubType) { + $collectionKeyTypes[] = $keySubType; + } + foreach ($this->extractTypes($node->genericTypes[1], $nameScope) as $valueSubType) { + $collectionKeyValues[] = $valueSubType; + } + } + + return [new Type($mainType->getBuiltinType(), $mainType->isNullable(), $mainType->getClassName(), $collection, $collectionKeyTypes, $collectionKeyValues)]; + } + if ($node instanceof ArrayShapeNode) { + return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]; + } + if ($node instanceof ArrayTypeNode) { + return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], $this->extractTypes($node->type, $nameScope))]; + } + if ($node instanceof CallableTypeNode || $node instanceof CallableTypeParameterNode) { + return [new Type(Type::BUILTIN_TYPE_CALLABLE)]; + } + if ($node instanceof NullableTypeNode) { + $subTypes = $this->extractTypes($node->type, $nameScope); + if (\count($subTypes) > 1) { + $subTypes[] = new Type(Type::BUILTIN_TYPE_NULL); + + return $subTypes; + } + + return [new Type($subTypes[0]->getBuiltinType(), true, $subTypes[0]->getClassName(), $subTypes[0]->isCollection(), $subTypes[0]->getCollectionKeyTypes(), $subTypes[0]->getCollectionValueTypes())]; + } + if ($node instanceof ThisTypeNode) { + return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())]; + } + if ($node instanceof IdentifierTypeNode) { + if (\in_array($node->name, Type::$builtinTypes, true)) { + return [new Type($node->name, false, null, \in_array($node->name, Type::$builtinCollectionTypes, true))]; + } + + return match ($node->name) { + 'integer', + 'positive-int', + 'negative-int' => [new Type(Type::BUILTIN_TYPE_INT)], + 'double' => [new Type(Type::BUILTIN_TYPE_FLOAT)], + 'list', + 'non-empty-list' => [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))], + 'non-empty-array' => [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)], + 'mixed' => [], // mixed seems to be ignored in all other extractors + 'parent' => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $node->name)], + 'static', + 'self' => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())], + 'class-string', + 'html-escaped-string', + 'lowercase-string', + 'non-empty-lowercase-string', + 'non-empty-string', + 'numeric-string', + 'trait-string', + 'interface-string', + 'literal-string' => [new Type(Type::BUILTIN_TYPE_STRING)], + 'void' => [new Type(Type::BUILTIN_TYPE_NULL)], + 'scalar' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)], + 'number' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)], + 'numeric' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)], + 'array-key' => [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], + default => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveStringName($node->name))], + }; + } + + return []; + } +} diff --git a/vendor/symfony/property-info/composer.json b/vendor/symfony/property-info/composer.json new file mode 100644 index 0000000..2e468e6 --- /dev/null +++ b/vendor/symfony/property-info/composer.json @@ -0,0 +1,50 @@ +{ + "name": "symfony/property-info", + "type": "library", + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "keywords": [ + "property", + "type", + "phpdoc", + "symfony", + "validator", + "doctrine" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "^7.1" + }, + "require-dev": { + "symfony/serializer": "^6.4|^7.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\PropertyInfo\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/routing/Alias.php b/vendor/symfony/routing/Alias.php new file mode 100644 index 0000000..7627f12 --- /dev/null +++ b/vendor/symfony/routing/Alias.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Routing\Exception\InvalidArgumentException; + +class Alias +{ + private string $id; + private array $deprecation = []; + + public function __construct(string $id) + { + $this->id = $id; + } + + public function withId(string $id): static + { + $new = clone $this; + + $new->id = $id; + + return $new; + } + + /** + * Returns the target name of this alias. + * + * @return string The target name + */ + public function getId(): string + { + return $this->id; + } + + /** + * Whether this alias is deprecated, that means it should not be referenced anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function setDeprecated(string $package, string $version, string $message): static + { + if ('' !== $message) { + if (preg_match('#[\r\n]|\*/#', $message)) { + throw new InvalidArgumentException('Invalid characters found in deprecation template.'); + } + + if (!str_contains($message, '%alias_id%')) { + throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.'); + } + } + + $this->deprecation = [ + 'package' => $package, + 'version' => $version, + 'message' => $message ?: 'The "%alias_id%" route alias is deprecated. You should stop using it, as it will be removed in the future.', + ]; + + return $this; + } + + public function isDeprecated(): bool + { + return (bool) $this->deprecation; + } + + /** + * @param string $name Route name relying on this alias + */ + public function getDeprecation(string $name): array + { + return [ + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => str_replace('%alias_id%', $name, $this->deprecation['message']), + ]; + } +} diff --git a/vendor/symfony/routing/Annotation/Route.php b/vendor/symfony/routing/Annotation/Route.php new file mode 100644 index 0000000..dda3bda --- /dev/null +++ b/vendor/symfony/routing/Annotation/Route.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Annotation; + +// do not deprecate in 6.4/7.0, to make it easier for the ecosystem to support 6.4, 7.4 and 8.0 simultaneously + +class_exists(\Symfony\Component\Routing\Attribute\Route::class); + +if (false) { + #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] + class Route extends \Symfony\Component\Routing\Attribute\Route + { + } +} diff --git a/vendor/symfony/routing/Attribute/Route.php b/vendor/symfony/routing/Attribute/Route.php new file mode 100644 index 0000000..07abc55 --- /dev/null +++ b/vendor/symfony/routing/Attribute/Route.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Attribute; + +/** + * @author Fabien Potencier + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] +class Route +{ + private ?string $path = null; + private array $localizedPaths = []; + private array $methods; + private array $schemes; + + /** + * @param string|array|null $path The route path (i.e. "/user/login") + * @param string|null $name The route name (i.e. "app_user_login") + * @param array $requirements Requirements for the route attributes, @see https://symfony.com/doc/current/routing.html#parameters-validation + * @param array $options Options for the route (i.e. ['prefix' => '/api']) + * @param array $defaults Default values for the route attributes and query parameters + * @param string|null $host The host for which this route should be active (i.e. "localhost") + * @param string|string[] $methods The list of HTTP methods allowed by this route + * @param string|string[] $schemes The list of schemes allowed by this route (i.e. "https") + * @param string|null $condition An expression that must evaluate to true for the route to be matched, @see https://symfony.com/doc/current/routing.html#matching-expressions + * @param int|null $priority The priority of the route if multiple ones are defined for the same path + * @param string|null $locale The locale accepted by the route + * @param string|null $format The format returned by the route (i.e. "json", "xml") + * @param bool|null $utf8 Whether the route accepts UTF-8 in its parameters + * @param bool|null $stateless Whether the route is defined as stateless or stateful, @see https://symfony.com/doc/current/routing.html#stateless-routes + * @param string|null $env The env in which the route is defined (i.e. "dev", "test", "prod") + */ + public function __construct( + string|array|null $path = null, + private ?string $name = null, + private array $requirements = [], + private array $options = [], + private array $defaults = [], + private ?string $host = null, + array|string $methods = [], + array|string $schemes = [], + private ?string $condition = null, + private ?int $priority = null, + ?string $locale = null, + ?string $format = null, + ?bool $utf8 = null, + ?bool $stateless = null, + private ?string $env = null, + ) { + if (\is_array($path)) { + $this->localizedPaths = $path; + } else { + $this->path = $path; + } + $this->setMethods($methods); + $this->setSchemes($schemes); + + if (null !== $locale) { + $this->defaults['_locale'] = $locale; + } + + if (null !== $format) { + $this->defaults['_format'] = $format; + } + + if (null !== $utf8) { + $this->options['utf8'] = $utf8; + } + + if (null !== $stateless) { + $this->defaults['_stateless'] = $stateless; + } + } + + public function setPath(string $path): void + { + $this->path = $path; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function setLocalizedPaths(array $localizedPaths): void + { + $this->localizedPaths = $localizedPaths; + } + + public function getLocalizedPaths(): array + { + return $this->localizedPaths; + } + + public function setHost(string $pattern): void + { + $this->host = $pattern; + } + + public function getHost(): ?string + { + return $this->host; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setRequirements(array $requirements): void + { + $this->requirements = $requirements; + } + + public function getRequirements(): array + { + return $this->requirements; + } + + public function setOptions(array $options): void + { + $this->options = $options; + } + + public function getOptions(): array + { + return $this->options; + } + + public function setDefaults(array $defaults): void + { + $this->defaults = $defaults; + } + + public function getDefaults(): array + { + return $this->defaults; + } + + public function setSchemes(array|string $schemes): void + { + $this->schemes = (array) $schemes; + } + + public function getSchemes(): array + { + return $this->schemes; + } + + public function setMethods(array|string $methods): void + { + $this->methods = (array) $methods; + } + + public function getMethods(): array + { + return $this->methods; + } + + public function setCondition(?string $condition): void + { + $this->condition = $condition; + } + + public function getCondition(): ?string + { + return $this->condition; + } + + public function setPriority(int $priority): void + { + $this->priority = $priority; + } + + public function getPriority(): ?int + { + return $this->priority; + } + + public function setEnv(?string $env): void + { + $this->env = $env; + } + + public function getEnv(): ?string + { + return $this->env; + } +} + +if (!class_exists(\Symfony\Component\Routing\Annotation\Route::class, false)) { + class_alias(Route::class, \Symfony\Component\Routing\Annotation\Route::class); +} diff --git a/vendor/symfony/routing/CHANGELOG.md b/vendor/symfony/routing/CHANGELOG.md new file mode 100644 index 0000000..bb4f4ba --- /dev/null +++ b/vendor/symfony/routing/CHANGELOG.md @@ -0,0 +1,340 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `{foo:bar}` syntax to define a mapping between a route parameter and its corresponding request attribute + +7.0 +--- + + * Add argument `$routeParameters` to `UrlMatcher::handleRouteRequirements()` + * Remove Doctrine annotations support in favor of native attributes + * Remove `AnnotationClassLoader`, use `AttributeClassLoader` instead + * Remove `AnnotationDirectoryLoader`, use `AttributeDirectoryLoader` instead + * Remove `AnnotationFileLoader`, use `AttributeFileLoader` instead + +6.4 +--- + + * Add FQCN and FQCN::method aliases for routes loaded from attributes/annotations when applicable + * Add native return type to `AnnotationClassLoader::setResolver()` + * Deprecate Doctrine annotations support in favor of native attributes + * Change the constructor signature of `AnnotationClassLoader` to `__construct(?string $env = null)`, passing an annotation reader as first argument is deprecated + * Deprecate `AnnotationClassLoader`, use `AttributeClassLoader` instead + * Deprecate `AnnotationDirectoryLoader`, use `AttributeDirectoryLoader` instead + * Deprecate `AnnotationFileLoader`, use `AttributeFileLoader` instead + * Add `AddExpressionLanguageProvidersPass` (moved from `FrameworkBundle`) + * Add aliases for all classes in the `Annotation` namespace to `Attribute` + +6.2 +--- + + * Add `Requirement::POSITIVE_INT` for common ids and pagination + +6.1 +--- + + * Add `getMissingParameters` and `getRouteName` methods on `MissingMandatoryParametersException` + * Allow using UTF-8 parameter names + * Support the `attribute` type (alias of `annotation`) in annotation loaders + * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs (query parameters) + * Add `EnumRequirement` to help generate route requirements from a `\BackedEnum` + * Add `Requirement`, a collection of universal regular-expression constants to use as route parameter requirements + * Add `params` variable to condition expression + * Deprecate not passing route parameters as the fourth argument to `UrlMatcher::handleRouteRequirements()` + +5.3 +--- + + * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs + * Add support for per-env configuration in XML and Yaml loaders + * Deprecate creating instances of the `Route` annotation class by passing an array of parameters + * Add `RoutingConfigurator::env()` to get the current environment + +5.2.0 +----- + + * Added support for inline definition of requirements and defaults for host + * Added support for `\A` and `\z` as regex start and end for route requirement + * Added support for `#[Route]` attributes + +5.1.0 +----- + + * added the protected method `PhpFileLoader::callConfigurator()` as extension point to ease custom routing configuration + * deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`. + * added "priority" option to annotated routes + * added argument `$priority` to `RouteCollection::add()` + * deprecated the `RouteCompiler::REGEX_DELIMITER` constant + * added `ExpressionLanguageProvider` to expose extra functions to route conditions + * added support for a `stateless` keyword for configuring route stateless in PHP, YAML and XML configurations. + * added the "hosts" option to be able to configure the host per locale. + * added `RequestContext::fromUri()` to ease building the default context + +5.0.0 +----- + + * removed `PhpGeneratorDumper` and `PhpMatcherDumper` + * removed `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options + * `Serializable` implementing methods for `Route` and `CompiledRoute` are final + * removed referencing service route loaders with a single colon + * Removed `ServiceRouterLoader` and `ObjectRouteLoader`. + +4.4.0 +----- + + * Deprecated `ServiceRouterLoader` in favor of `ContainerLoader`. + * Deprecated `ObjectRouteLoader` in favor of `ObjectLoader`. + * Added a way to exclude patterns of resources from being imported by the `import()` method + +4.3.0 +----- + + * added `CompiledUrlMatcher` and `CompiledUrlMatcherDumper` + * added `CompiledUrlGenerator` and `CompiledUrlGeneratorDumper` + * deprecated `PhpGeneratorDumper` and `PhpMatcherDumper` + * deprecated `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options + * `Serializable` implementing methods for `Route` and `CompiledRoute` are marked as `@internal` and `@final`. + Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible + with the new serialization methods in PHP 7.4. + * exposed `utf8` Route option, defaults "locale" and "format" in configuration loaders and configurators + * added support for invokable service route loaders + +4.2.0 +----- + + * added fallback to cultureless locale for internationalized routes + +4.0.0 +----- + + * dropped support for using UTF-8 route patterns without using the `utf8` option + * dropped support for using UTF-8 route requirements without using the `utf8` option + +3.4.0 +----- + + * Added `NoConfigurationException`. + * Added the possibility to define a prefix for all routes of a controller via @Route(name="prefix_") + * Added support for prioritized routing loaders. + * Add matched and default parameters to redirect responses + * Added support for a `controller` keyword for configuring route controllers in YAML and XML configurations. + +3.3.0 +----- + + * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0. + * router.options.generator_class + * router.options.generator_base_class + * router.options.generator_dumper_class + * router.options.matcher_class + * router.options.matcher_base_class + * router.options.matcher_dumper_class + * router.options.matcher.cache_class + * router.options.generator.cache_class + +3.2.0 +----- + + * Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations. + * Added support for UTF-8 requirements + +2.8.0 +----- + + * allowed specifying a directory to recursively load all routing configuration files it contains + * Added ObjectRouteLoader and ServiceRouteLoader that allow routes to be loaded + by calling a method on an object/service. + * [DEPRECATION] Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method. + Use the constants defined in the `UrlGeneratorInterface` instead. + + Before: + + ```php + $router->generate('blog_show', ['slug' => 'my-blog-post'], true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + $router->generate('blog_show', ['slug' => 'my-blog-post'], UrlGeneratorInterface::ABSOLUTE_URL); + ``` + +2.5.0 +----- + + * [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and + will be removed in Symfony 3.0, since the performance gains were minimal and + it's hard to replicate the behavior of PHP implementation. + +2.3.0 +----- + + * added RequestContext::getQueryString() + +2.2.0 +----- + + * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0): + + * The `pattern` setting for a route has been deprecated in favor of `path` + * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings + + Before: + + ```yaml + article_edit: + pattern: /article/{id} + requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' } + ``` + + ```xml + + POST|PUT + https + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPattern('/article/{id}'); + $route->setRequirement('_method', 'POST|PUT'); + $route->setRequirement('_scheme', 'https'); + ``` + + After: + + ```yaml + article_edit: + path: /article/{id} + methods: [POST, PUT] + schemes: https + requirements: { 'id': '\d+' } + ``` + + ```xml + + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPath('/article/{id}'); + $route->setMethods(['POST', 'PUT']); + $route->setSchemes('https'); + ``` + + * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as + a flat array of Routes. So when using PHP to build the RouteCollection, you must + make sure to add routes to the sub-collection before adding it to the parent + collection (this is not relevant when using YAML or XML for Route definitions). + + Before: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $rootCollection->addCollection($subCollection); + $subCollection->add('foo', new Route('/foo')); + ``` + + After: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $subCollection->add('foo', new Route('/foo')); + $rootCollection->addCollection($subCollection); + ``` + + Also one must call `addCollection` from the bottom to the top hierarchy. + So the correct sequence is the following (and not the reverse): + + ```php + $childCollection->addCollection($grandchildCollection); + $rootCollection->addCollection($childCollection); + ``` + + * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()` + have been deprecated and will be removed in Symfony 2.3. + * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements + or options without adding a prefix is not supported anymore. So if you called `addPrefix` + with an empty prefix or `/` only (both have no relevance), like + `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` + you need to use the new dedicated methods `addDefaults($defaultsArray)`, + `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead. + * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated + because adding options has nothing to do with adding a path prefix. If you want to add options + to all child routes of a RouteCollection, you can use `addOptions()`. + * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated + because it suggested that all routes in the collection would have this prefix, which is + not necessarily true. On top of that, since there is no tree structure anymore, this method + is also useless. Don't worry about performance, prefix optimization for matching is still done + in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities. + * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be + used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options` + will still work, but have been deprecated. The `addPrefix` method should be used for this + use-case instead. + Before: `$parentCollection->addCollection($collection, '/prefix', [...], [...])` + After: + ```php + $collection->addPrefix('/prefix', [...], [...]); + $parentCollection->addCollection($collection); + ``` + * added support for the method default argument values when defining a @Route + * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`. + * Characters that function as separator between placeholders are now whitelisted + to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`. + * [BC BREAK] The default requirement of a variable has been changed slightly. + Previously it disallowed the previous and the next char around a variable. Now + it disallows the slash (`/`) and the next char. Using the previous char added + no value and was problematic because the route `/index.{_format}` would be + matched by `/index.ht/ml`. + * The default requirement now uses possessive quantifiers when possible which + improves matching performance by up to 20% because it prevents backtracking + when it's not needed. + * The ConfigurableRequirementsInterface can now also be used to disable the requirements + check on URL generation completely by calling `setStrictRequirements(null)`. It + improves performance in production environment as you should know that params always + pass the requirements (otherwise it would break your link anyway). + * There is no restriction on the route name anymore. So non-alphanumeric characters + are now also allowed. + * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static + (only relevant if you implemented your own RouteCompiler). + * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g. + "../parent-file" and "//example.com/dir/file". The third parameter in + `UrlGeneratorInterface::generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)` + now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for + claritiy. The old method calls with a Boolean parameter will continue to work because they + equal the signature using the constants. + +2.1.0 +----- + + * added RequestMatcherInterface + * added RequestContext::fromRequest() + * the UrlMatcher does not throw a \LogicException anymore when the required + scheme is not the current one + * added TraceableUrlMatcher + * added the possibility to define options, default values and requirements + for placeholders in prefix, including imported routes + * added RouterInterface::getRouteCollection + * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they + were decoded twice before. Note that the `urldecode()` calls have been + changed for a single `rawurldecode()` in order to support `+` for input + paths. + * added RouteCollection::getRoot method to retrieve the root of a + RouteCollection tree + * [BC BREAK] made RouteCollection::setParent private which could not have + been used anyway without creating inconsistencies + * [BC BREAK] RouteCollection::remove also removes a route from parent + collections (not only from its children) + * added ConfigurableRequirementsInterface that allows to disable exceptions + (and generate empty URLs instead) when generating a route with an invalid + parameter value diff --git a/vendor/symfony/routing/CompiledRoute.php b/vendor/symfony/routing/CompiledRoute.php new file mode 100644 index 0000000..398e5cb --- /dev/null +++ b/vendor/symfony/routing/CompiledRoute.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * CompiledRoutes are returned by the RouteCompiler class. + * + * @author Fabien Potencier + */ +class CompiledRoute implements \Serializable +{ + /** + * @param string $staticPrefix The static prefix of the compiled route + * @param string $regex The regular expression to use to match this route + * @param array $tokens An array of tokens to use to generate URL for this route + * @param array $pathVariables An array of path variables + * @param string|null $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + */ + public function __construct( + private string $staticPrefix, + private string $regex, + private array $tokens, + private array $pathVariables, + private ?string $hostRegex = null, + private array $hostTokens = [], + private array $hostVariables = [], + private array $variables = [], + ) { + } + + public function __serialize(): array + { + return [ + 'vars' => $this->variables, + 'path_prefix' => $this->staticPrefix, + 'path_regex' => $this->regex, + 'path_tokens' => $this->tokens, + 'path_vars' => $this->pathVariables, + 'host_regex' => $this->hostRegex, + 'host_tokens' => $this->hostTokens, + 'host_vars' => $this->hostVariables, + ]; + } + + /** + * @internal + */ + final public function serialize(): string + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __unserialize(array $data): void + { + $this->variables = $data['vars']; + $this->staticPrefix = $data['path_prefix']; + $this->regex = $data['path_regex']; + $this->tokens = $data['path_tokens']; + $this->pathVariables = $data['path_vars']; + $this->hostRegex = $data['host_regex']; + $this->hostTokens = $data['host_tokens']; + $this->hostVariables = $data['host_vars']; + } + + /** + * @internal + */ + final public function unserialize(string $serialized): void + { + $this->__unserialize(unserialize($serialized, ['allowed_classes' => false])); + } + + /** + * Returns the static prefix. + */ + public function getStaticPrefix(): string + { + return $this->staticPrefix; + } + + /** + * Returns the regex. + */ + public function getRegex(): string + { + return $this->regex; + } + + /** + * Returns the host regex. + */ + public function getHostRegex(): ?string + { + return $this->hostRegex; + } + + /** + * Returns the tokens. + */ + public function getTokens(): array + { + return $this->tokens; + } + + /** + * Returns the host tokens. + */ + public function getHostTokens(): array + { + return $this->hostTokens; + } + + /** + * Returns the variables. + */ + public function getVariables(): array + { + return $this->variables; + } + + /** + * Returns the path variables. + */ + public function getPathVariables(): array + { + return $this->pathVariables; + } + + /** + * Returns the host variables. + */ + public function getHostVariables(): array + { + return $this->hostVariables; + } +} diff --git a/vendor/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php b/vendor/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php new file mode 100644 index 0000000..619fa67 --- /dev/null +++ b/vendor/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers the expression language providers. + * + * @author Fabien Potencier + */ +class AddExpressionLanguageProvidersPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->has('router.default')) { + return; + } + + $definition = $container->findDefinition('router.default'); + foreach ($container->findTaggedServiceIds('routing.expression_language_provider', true) as $id => $attributes) { + $definition->addMethodCall('addExpressionLanguageProvider', [new Reference($id)]); + } + } +} diff --git a/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php b/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php new file mode 100644 index 0000000..16769d5 --- /dev/null +++ b/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged routing.loader services to routing.resolver service. + * + * @author Fabien Potencier + */ +class RoutingResolverPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (false === $container->hasDefinition('routing.resolver')) { + return; + } + + $definition = $container->getDefinition('routing.resolver'); + + foreach ($this->findAndSortTaggedServices('routing.loader', $container) as $id) { + $definition->addMethodCall('addLoader', [new Reference($id)]); + } + } +} diff --git a/vendor/symfony/routing/Exception/ExceptionInterface.php b/vendor/symfony/routing/Exception/ExceptionInterface.php new file mode 100644 index 0000000..22e72b1 --- /dev/null +++ b/vendor/symfony/routing/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * ExceptionInterface. + * + * @author Alexandre Salomé + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/routing/Exception/InvalidArgumentException.php b/vendor/symfony/routing/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..950b9b1 --- /dev/null +++ b/vendor/symfony/routing/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/InvalidParameterException.php b/vendor/symfony/routing/Exception/InvalidParameterException.php new file mode 100644 index 0000000..94d841f --- /dev/null +++ b/vendor/symfony/routing/Exception/InvalidParameterException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a parameter is not valid. + * + * @author Alexandre Salomé + */ +class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/LogicException.php b/vendor/symfony/routing/Exception/LogicException.php new file mode 100644 index 0000000..16ed58e --- /dev/null +++ b/vendor/symfony/routing/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +class LogicException extends \LogicException +{ +} diff --git a/vendor/symfony/routing/Exception/MethodNotAllowedException.php b/vendor/symfony/routing/Exception/MethodNotAllowedException.php new file mode 100644 index 0000000..31f482f --- /dev/null +++ b/vendor/symfony/routing/Exception/MethodNotAllowedException.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was found but the request method is not allowed. + * + * This exception should trigger an HTTP 405 response in your application code. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface +{ + protected array $allowedMethods = []; + + /** + * @param string[] $allowedMethods + */ + public function __construct(array $allowedMethods, string $message = '', int $code = 0, ?\Throwable $previous = null) + { + $this->allowedMethods = array_map('strtoupper', $allowedMethods); + + parent::__construct($message, $code, $previous); + } + + /** + * Gets the allowed HTTP methods. + * + * @return string[] + */ + public function getAllowedMethods(): array + { + return $this->allowedMethods; + } +} diff --git a/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php new file mode 100644 index 0000000..59d446e --- /dev/null +++ b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route cannot be generated because of missing + * mandatory parameters. + * + * @author Alexandre Salomé + */ +class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface +{ + private string $routeName = ''; + private array $missingParameters = []; + + /** + * @param string[] $missingParameters + */ + public function __construct(string $routeName = '', array $missingParameters = [], int $code = 0, ?\Throwable $previous = null) + { + $this->routeName = $routeName; + $this->missingParameters = $missingParameters; + $message = sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', $missingParameters), $routeName); + + parent::__construct($message, $code, $previous); + } + + /** + * @return string[] + */ + public function getMissingParameters(): array + { + return $this->missingParameters; + } + + public function getRouteName(): string + { + return $this->routeName; + } +} diff --git a/vendor/symfony/routing/Exception/NoConfigurationException.php b/vendor/symfony/routing/Exception/NoConfigurationException.php new file mode 100644 index 0000000..333bc74 --- /dev/null +++ b/vendor/symfony/routing/Exception/NoConfigurationException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when no routes are configured. + * + * @author Yonel Ceruto + */ +class NoConfigurationException extends ResourceNotFoundException +{ +} diff --git a/vendor/symfony/routing/Exception/ResourceNotFoundException.php b/vendor/symfony/routing/Exception/ResourceNotFoundException.php new file mode 100644 index 0000000..ccbca15 --- /dev/null +++ b/vendor/symfony/routing/Exception/ResourceNotFoundException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was not found. + * + * This exception should trigger an HTTP 404 response in your application code. + * + * @author Kris Wallsmith + */ +class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/RouteCircularReferenceException.php b/vendor/symfony/routing/Exception/RouteCircularReferenceException.php new file mode 100644 index 0000000..841e359 --- /dev/null +++ b/vendor/symfony/routing/Exception/RouteCircularReferenceException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +class RouteCircularReferenceException extends RuntimeException +{ + public function __construct(string $routeId, array $path) + { + parent::__construct(sprintf('Circular reference detected for route "%s", path: "%s".', $routeId, implode(' -> ', $path))); + } +} diff --git a/vendor/symfony/routing/Exception/RouteNotFoundException.php b/vendor/symfony/routing/Exception/RouteNotFoundException.php new file mode 100644 index 0000000..24ab0b4 --- /dev/null +++ b/vendor/symfony/routing/Exception/RouteNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route does not exist. + * + * @author Alexandre Salomé + */ +class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/RuntimeException.php b/vendor/symfony/routing/Exception/RuntimeException.php new file mode 100644 index 0000000..48da62e --- /dev/null +++ b/vendor/symfony/routing/Exception/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Generator/CompiledUrlGenerator.php b/vendor/symfony/routing/Generator/CompiledUrlGenerator.php new file mode 100644 index 0000000..f59c914 --- /dev/null +++ b/vendor/symfony/routing/Generator/CompiledUrlGenerator.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContext; + +/** + * Generates URLs based on rules dumped by CompiledUrlGeneratorDumper. + */ +class CompiledUrlGenerator extends UrlGenerator +{ + private array $compiledRoutes = []; + + public function __construct( + array $compiledRoutes, + RequestContext $context, + ?LoggerInterface $logger = null, + private ?string $defaultLocale = null, + ) { + $this->compiledRoutes = $compiledRoutes; + $this->context = $context; + $this->logger = $logger; + } + + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string + { + $locale = $parameters['_locale'] + ?? $this->context->getParameter('_locale') + ?: $this->defaultLocale; + + if (null !== $locale) { + do { + if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) { + $name .= '.'.$locale; + break; + } + } while (false !== $locale = strstr($locale, '_', true)); + } + + if (!isset($this->compiledRoutes[$name])) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + [$variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes, $deprecations] = $this->compiledRoutes[$name] + [6 => []]; + + foreach ($deprecations as $deprecation) { + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { + if (!\in_array('_locale', $variables, true)) { + unset($parameters['_locale']); + } elseif (!isset($parameters['_locale'])) { + $parameters['_locale'] = $defaults['_locale']; + } + } + + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); + } +} diff --git a/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php new file mode 100644 index 0000000..b99e949 --- /dev/null +++ b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +/** + * ConfigurableRequirementsInterface must be implemented by URL generators that + * can be configured whether an exception should be generated when the parameters + * do not match the requirements. It is also possible to disable the requirements + * check for URL generation completely. + * + * The possible configurations and use-cases: + * - setStrictRequirements(true): Throw an exception for mismatching requirements. This + * is mostly useful in development environment. + * - setStrictRequirements(false): Don't throw an exception but return an empty string as URL for + * mismatching requirements and log the problem. Useful when you cannot control all + * params because they come from third party libs but don't want to have a 404 in + * production environment. It should log the mismatch so one can review it. + * - setStrictRequirements(null): Return the URL with the given parameters without + * checking the requirements at all. When generating a URL you should either trust + * your params or you validated them beforehand because otherwise it would break your + * link anyway. So in production environment you should know that params always pass + * the requirements. Thus this option allows to disable the check on URL generation for + * performance reasons (saving a preg_match for each requirement every time a URL is + * generated). + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface ConfigurableRequirementsInterface +{ + /** + * Enables or disables the exception on incorrect parameters. + * Passing null will deactivate the requirements check completely. + */ + public function setStrictRequirements(?bool $enabled): void; + + /** + * Returns whether to throw an exception on incorrect parameters. + * Null means the requirements check is deactivated completely. + */ + public function isStrictRequirements(): ?bool; +} diff --git a/vendor/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php new file mode 100644 index 0000000..1144fed --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\Exception\RouteCircularReferenceException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; + +/** + * CompiledUrlGeneratorDumper creates a PHP array to be used with CompiledUrlGenerator. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Nicolas Grekas + */ +class CompiledUrlGeneratorDumper extends GeneratorDumper +{ + public function getCompiledRoutes(): array + { + $compiledRoutes = []; + foreach ($this->getRoutes()->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $compiledRoutes[$name] = [ + $compiledRoute->getVariables(), + $route->getDefaults(), + $route->getRequirements(), + $compiledRoute->getTokens(), + $compiledRoute->getHostTokens(), + $route->getSchemes(), + [], + ]; + } + + return $compiledRoutes; + } + + public function getCompiledAliases(): array + { + $routes = $this->getRoutes(); + $compiledAliases = []; + foreach ($routes->getAliases() as $name => $alias) { + $deprecations = $alias->isDeprecated() ? [$alias->getDeprecation($name)] : []; + $currentId = $alias->getId(); + $visited = []; + while (null !== $alias = $routes->getAlias($currentId) ?? null) { + if (false !== $searchKey = array_search($currentId, $visited)) { + $visited[] = $currentId; + + throw new RouteCircularReferenceException($currentId, \array_slice($visited, $searchKey)); + } + + if ($alias->isDeprecated()) { + $deprecations[] = $deprecation = $alias->getDeprecation($currentId); + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + $visited[] = $currentId; + $currentId = $alias->getId(); + } + + if (null === $target = $routes->get($currentId)) { + throw new RouteNotFoundException(sprintf('Target route "%s" for alias "%s" does not exist.', $currentId, $name)); + } + + $compiledTarget = $target->compile(); + + $compiledAliases[$name] = [ + $compiledTarget->getVariables(), + $target->getDefaults(), + $target->getRequirements(), + $compiledTarget->getTokens(), + $compiledTarget->getHostTokens(), + $target->getSchemes(), + $deprecations, + ]; + } + + return $compiledAliases; + } + + public function dump(array $options = []): string + { + return <<generateDeclaredRoutes()} +]; + +EOF; + } + + /** + * Generates PHP code representing an array of defined routes + * together with the routes properties (e.g. requirements). + */ + private function generateDeclaredRoutes(): string + { + $routes = ''; + foreach ($this->getCompiledRoutes() as $name => $properties) { + $routes .= sprintf("\n '%s' => %s,", $name, CompiledUrlMatcherDumper::export($properties)); + } + + foreach ($this->getCompiledAliases() as $alias => $properties) { + $routes .= sprintf("\n '%s' => %s,", $alias, CompiledUrlMatcherDumper::export($properties)); + } + + return $routes; + } +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php new file mode 100644 index 0000000..e8abaaf --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumper is the base class for all built-in generator dumpers. + * + * @author Fabien Potencier + */ +abstract class GeneratorDumper implements GeneratorDumperInterface +{ + public function __construct( + private RouteCollection $routes, + ) { + } + + public function getRoutes(): RouteCollection + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php new file mode 100644 index 0000000..d3294ce --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumperInterface is the interface that all generator dumper classes must implement. + * + * @author Fabien Potencier + */ +interface GeneratorDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to generate a URL of such a route. + */ + public function dump(array $options = []): string; + + /** + * Gets the routes to dump. + */ + public function getRoutes(): RouteCollection; +} diff --git a/vendor/symfony/routing/Generator/UrlGenerator.php b/vendor/symfony/routing/Generator/UrlGenerator.php new file mode 100644 index 0000000..9b88a24 --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGenerator.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; + +/** + * UrlGenerator can generate a URL or a path for any route in the RouteCollection + * based on the passed parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface +{ + private const QUERY_FRAGMENT_DECODED = [ + // RFC 3986 explicitly allows those in the query/fragment to reference other URIs unencoded + '%2F' => '/', + '%252F' => '%2F', + '%3F' => '?', + // reserved chars that have no special meaning for HTTP URIs in a query or fragment + // this excludes esp. "&", "=" and also "+" because PHP would treat it as a space (form-encoded) + '%40' => '@', + '%3A' => ':', + '%21' => '!', + '%3B' => ';', + '%2C' => ',', + '%2A' => '*', + ]; + + protected ?bool $strictRequirements = true; + + /** + * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. + * + * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars + * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. + * "?" and "#" (would be interpreted wrongly as query and fragment identifier), + * "'" and """ (are used as delimiters in HTML). + */ + protected array $decodedChars = [ + // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning + // some webservers don't allow the slash in encoded form in the path for security reasons anyway + // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss + '%2F' => '/', + '%252F' => '%2F', + // the following chars are general delimiters in the URI specification but have only special meaning in the authority component + // so they can safely be used in the path in unencoded form + '%40' => '@', + '%3A' => ':', + // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally + // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + ]; + + public function __construct( + protected RouteCollection $routes, + protected RequestContext $context, + protected ?LoggerInterface $logger = null, + private ?string $defaultLocale = null, + ) { + } + + public function setContext(RequestContext $context): void + { + $this->context = $context; + } + + public function getContext(): RequestContext + { + return $this->context; + } + + public function setStrictRequirements(?bool $enabled): void + { + $this->strictRequirements = $enabled; + } + + public function isStrictRequirements(): ?bool + { + return $this->strictRequirements; + } + + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string + { + $route = null; + $locale = $parameters['_locale'] ?? $this->context->getParameter('_locale') ?: $this->defaultLocale; + + if (null !== $locale) { + do { + if (null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) { + break; + } + } while (false !== $locale = strstr($locale, '_', true)); + } + + if (null === $route ??= $this->routes->get($name)) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + // the Route has a cache of its own and is not recompiled as long as it does not get modified + $compiledRoute = $route->compile(); + + $defaults = $route->getDefaults(); + $variables = $compiledRoute->getVariables(); + + if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { + if (!\in_array('_locale', $variables, true)) { + unset($parameters['_locale']); + } elseif (!isset($parameters['_locale'])) { + $parameters['_locale'] = $defaults['_locale']; + } + } + + return $this->doGenerate($variables, $defaults, $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); + } + + /** + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + protected function doGenerate(array $variables, array $defaults, array $requirements, array $tokens, array $parameters, string $name, int $referenceType, array $hostTokens, array $requiredSchemes = []): string + { + $variables = array_flip($variables); + $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); + + // all params must be given + if ($diff = array_diff_key($variables, $mergedParams)) { + throw new MissingMandatoryParametersException($name, array_keys($diff)); + } + + $url = ''; + $optional = true; + $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.'; + foreach ($tokens as $token) { + if ('variable' === $token[0]) { + $varName = $token[3]; + // variable is not important by default + $important = $token[5] ?? false; + + if (!$optional || $important || !\array_key_exists($varName, $defaults) || (null !== $mergedParams[$varName] && (string) $mergedParams[$varName] !== (string) $defaults[$varName])) { + // check requirement (while ignoring look-around patterns) + if (null !== $this->strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|strictRequirements) { + throw new InvalidParameterException(strtr($message, ['{parameter}' => $varName, '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$varName]])); + } + + $this->logger?->error($message, ['parameter' => $varName, 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$varName]]); + + return ''; + } + + $url = $token[1].$mergedParams[$varName].$url; + $optional = false; + } + } else { + // static text + $url = $token[1].$url; + $optional = false; + } + } + + if ('' === $url) { + $url = '/'; + } + + // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = strtr(rawurlencode($url), $this->decodedChars); + + // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 + // so we need to encode them as they are not used for this purpose here + // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route + $url = strtr($url, ['/../' => '/%2E%2E/', '/./' => '/%2E/']); + if (str_ends_with($url, '/..')) { + $url = substr($url, 0, -2).'%2E%2E'; + } elseif (str_ends_with($url, '/.')) { + $url = substr($url, 0, -1).'%2E'; + } + + $schemeAuthority = ''; + $host = $this->context->getHost(); + $scheme = $this->context->getScheme(); + + if ($requiredSchemes) { + if (!\in_array($scheme, $requiredSchemes, true)) { + $referenceType = self::ABSOLUTE_URL; + $scheme = current($requiredSchemes); + } + } + + if ($hostTokens) { + $routeHost = ''; + foreach ($hostTokens as $token) { + if ('variable' === $token[0]) { + // check requirement (while ignoring look-around patterns) + if (null !== $this->strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|strictRequirements) { + throw new InvalidParameterException(strtr($message, ['{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]])); + } + + $this->logger?->error($message, ['parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]]); + + return ''; + } + + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; + } else { + $routeHost = $token[1].$routeHost; + } + } + + if ($routeHost !== $host) { + $host = $routeHost; + if (self::ABSOLUTE_URL !== $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + } + + if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { + if ('' !== $host || ('' !== $scheme && 'http' !== $scheme && 'https' !== $scheme)) { + $port = ''; + if ('http' === $scheme && 80 !== $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $schemeAuthority = self::NETWORK_PATH === $referenceType || '' === $scheme ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } + } + + if (self::RELATIVE_PATH === $referenceType) { + $url = self::getRelativePath($this->context->getPathInfo(), $url); + } else { + $url = $schemeAuthority.$this->context->getBaseUrl().$url; + } + + // add a query string if needed + $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, fn ($a, $b) => $a == $b ? 0 : 1); + + array_walk_recursive($extra, $caster = static function (&$v) use (&$caster) { + if (\is_object($v)) { + if ($vars = get_object_vars($v)) { + array_walk_recursive($vars, $caster); + $v = $vars; + } elseif (method_exists($v, '__toString')) { + $v = (string) $v; + } + } + }); + + // extract fragment + $fragment = $defaults['_fragment'] ?? ''; + + if (isset($extra['_fragment'])) { + $fragment = $extra['_fragment']; + unset($extra['_fragment']); + } + + if ($extra && $query = http_build_query($extra, '', '&', \PHP_QUERY_RFC3986)) { + $url .= '?'.strtr($query, self::QUERY_FRAGMENT_DECODED); + } + + if ('' !== $fragment) { + $url .= '#'.strtr(rawurlencode($fragment), self::QUERY_FRAGMENT_DECODED); + } + + return $url; + } + + /** + * Returns the target path as relative reference from the base path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $basePath The base path + * @param string $targetPath The target path + */ + public static function getRelativePath(string $basePath, string $targetPath): string + { + if ($basePath === $targetPath) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return '' === $path || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } +} diff --git a/vendor/symfony/routing/Generator/UrlGeneratorInterface.php b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php new file mode 100644 index 0000000..51210b4 --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlGeneratorInterface is the interface that all URL generator classes must implement. + * + * The constants in this interface define the different types of resource references that + * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986 + * We are using the term "URL" instead of "URI" as this is more common in web applications + * and we do not need to distinguish them as the difference is mostly semantical and + * less technical. Generating URIs, i.e. representation-independent resource identifiers, + * is also possible. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface UrlGeneratorInterface extends RequestContextAwareInterface +{ + /** + * Generates an absolute URL, e.g. "http://example.com/dir/file". + */ + public const ABSOLUTE_URL = 0; + + /** + * Generates an absolute path, e.g. "/dir/file". + */ + public const ABSOLUTE_PATH = 1; + + /** + * Generates a relative path based on the current request path, e.g. "../parent-file". + * + * @see UrlGenerator::getRelativePath() + */ + public const RELATIVE_PATH = 2; + + /** + * Generates a network path, e.g. "//example.com/dir/file". + * Such reference reuses the current scheme but specifies the host. + */ + public const NETWORK_PATH = 3; + + /** + * Generates a URL or path for a specific route based on the given parameters. + * + * Parameters that reference placeholders in the route pattern will substitute them in the + * path or host. Extra params are added as query string to the URL. + * + * When the passed reference type cannot be generated for the route because it requires a different + * host or scheme than the current one, the method will return a more comprehensive reference + * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH + * but the route requires the https scheme whereas the current scheme is http, it will instead return an + * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches + * the route in any case. + * + * If there is no route with the given name, the generator must throw the RouteNotFoundException. + * + * The special parameter _fragment will be used as the document fragment suffixed to the final URL. + * + * @throws RouteNotFoundException If the named route doesn't exist + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string; +} diff --git a/vendor/symfony/routing/LICENSE b/vendor/symfony/routing/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/routing/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/routing/Loader/AttributeClassLoader.php b/vendor/symfony/routing/Loader/AttributeClassLoader.php new file mode 100644 index 0000000..8372d90 --- /dev/null +++ b/vendor/symfony/routing/Loader/AttributeClassLoader.php @@ -0,0 +1,348 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Loader\LoaderResolverInterface; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Attribute\Route as RouteAnnotation; +use Symfony\Component\Routing\Exception\LogicException; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * AttributeClassLoader loads routing information from a PHP class and its methods. + * + * You need to define an implementation for the configureRoute() method. Most of the + * time, this method should define some PHP callable to be called for the route + * (a controller in MVC speak). + * + * The #[Route] attribute can be set on the class (for global parameters), + * and on each method. + * + * The #[Route] attribute main value is the route path. The attribute also + * recognizes several parameters: requirements, options, defaults, schemes, + * methods, host, and name. The name parameter is mandatory. + * Here is an example of how you should be able to use it: + * + * #[Route('/Blog')] + * class Blog + * { + * #[Route('/', name: 'blog_index')] + * public function index() + * { + * } + * #[Route('/{id}', name: 'blog_post', requirements: ["id" => '\d+'])] + * public function show() + * { + * } + * } + * + * @author Fabien Potencier + * @author Alexander M. Turek + * @author Alexandre Daubois + */ +abstract class AttributeClassLoader implements LoaderInterface +{ + protected string $routeAnnotationClass = RouteAnnotation::class; + protected int $defaultRouteIndex = 0; + + public function __construct( + protected readonly ?string $env = null, + ) { + } + + /** + * Sets the annotation class to read route properties from. + */ + public function setRouteAnnotationClass(string $class): void + { + $this->routeAnnotationClass = $class; + } + + /** + * @throws \InvalidArgumentException When route can't be parsed + */ + public function load(mixed $class, ?string $type = null): RouteCollection + { + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + $class = new \ReflectionClass($class); + if ($class->isAbstract()) { + throw new \InvalidArgumentException(sprintf('Attributes from class "%s" cannot be read as it is abstract.', $class->getName())); + } + + $globals = $this->getGlobals($class); + $collection = new RouteCollection(); + $collection->addResource(new FileResource($class->getFileName())); + if ($globals['env'] && $this->env !== $globals['env']) { + return $collection; + } + $fqcnAlias = false; + foreach ($class->getMethods() as $method) { + $this->defaultRouteIndex = 0; + $routeNamesBefore = array_keys($collection->all()); + foreach ($this->getAnnotations($method) as $annot) { + $this->addRoute($collection, $annot, $globals, $class, $method); + if ('__invoke' === $method->name) { + $fqcnAlias = true; + } + } + + if (1 === $collection->count() - \count($routeNamesBefore)) { + $newRouteName = current(array_diff(array_keys($collection->all()), $routeNamesBefore)); + if ($newRouteName !== $aliasName = sprintf('%s::%s', $class->name, $method->name)) { + $collection->addAlias($aliasName, $newRouteName); + } + } + } + if (0 === $collection->count() && $class->hasMethod('__invoke')) { + $globals = $this->resetGlobals(); + foreach ($this->getAnnotations($class) as $annot) { + $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); + $fqcnAlias = true; + } + } + if ($fqcnAlias && 1 === $collection->count()) { + $invokeRouteName = key($collection->all()); + if ($invokeRouteName !== $class->name) { + $collection->addAlias($class->name, $invokeRouteName); + } + + if ($invokeRouteName !== $aliasName = sprintf('%s::__invoke', $class->name)) { + $collection->addAlias($aliasName, $invokeRouteName); + } + } + + return $collection; + } + + /** + * @param RouteAnnotation $annot or an object that exposes a similar interface + */ + protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method): void + { + if ($annot->getEnv() && $annot->getEnv() !== $this->env) { + return; + } + + $name = $annot->getName() ?? $this->getDefaultRouteName($class, $method); + $name = $globals['name'].$name; + + $requirements = $annot->getRequirements(); + + foreach ($requirements as $placeholder => $requirement) { + if (\is_int($placeholder)) { + throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName())); + } + } + + $defaults = array_replace($globals['defaults'], $annot->getDefaults()); + $requirements = array_replace($globals['requirements'], $requirements); + $options = array_replace($globals['options'], $annot->getOptions()); + $schemes = array_unique(array_merge($globals['schemes'], $annot->getSchemes())); + $methods = array_unique(array_merge($globals['methods'], $annot->getMethods())); + + $host = $annot->getHost() ?? $globals['host']; + $condition = $annot->getCondition() ?? $globals['condition']; + $priority = $annot->getPriority() ?? $globals['priority']; + + $path = $annot->getLocalizedPaths() ?: $annot->getPath(); + $prefix = $globals['localized_paths'] ?: $globals['path']; + $paths = []; + + if (\is_array($path)) { + if (!\is_array($prefix)) { + foreach ($path as $locale => $localePath) { + $paths[$locale] = $prefix.$localePath; + } + } elseif ($missing = array_diff_key($prefix, $path)) { + throw new \LogicException(sprintf('Route to "%s" is missing paths for locale(s) "%s".', $class->name.'::'.$method->name, implode('", "', array_keys($missing)))); + } else { + foreach ($path as $locale => $localePath) { + if (!isset($prefix[$locale])) { + throw new \LogicException(sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".', $method->name, $locale, $class->name)); + } + + $paths[$locale] = $prefix[$locale].$localePath; + } + } + } elseif (\is_array($prefix)) { + foreach ($prefix as $locale => $localePrefix) { + $paths[$locale] = $localePrefix.$path; + } + } else { + $paths[] = $prefix.$path; + } + + foreach ($method->getParameters() as $param) { + if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) { + continue; + } + foreach ($paths as $locale => $path) { + if (preg_match(sprintf('/\{%s(?:<.*?>)?\}/', preg_quote($param->name)), $path)) { + if (\is_scalar($defaultValue = $param->getDefaultValue()) || null === $defaultValue) { + $defaults[$param->name] = $defaultValue; + } elseif ($defaultValue instanceof \BackedEnum) { + $defaults[$param->name] = $defaultValue->value; + } + break; + } + } + } + + foreach ($paths as $locale => $path) { + $route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + $this->configureRoute($route, $class, $method, $annot); + if (0 !== $locale) { + $route->setDefault('_locale', $locale); + $route->setRequirement('_locale', preg_quote($locale)); + $route->setDefault('_canonical_route', $name); + $collection->add($name.'.'.$locale, $route, $priority); + } else { + $collection->add($name, $route, $priority); + } + } + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'attribute' === $type); + } + + public function setResolver(LoaderResolverInterface $resolver): void + { + } + + public function getResolver(): LoaderResolverInterface + { + throw new LogicException(sprintf('The "%s()" method must not be called.', __METHOD__)); + } + + /** + * Gets the default route name for a class method. + * + * @return string + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) + { + $name = str_replace('\\', '_', $class->name).'_'.$method->name; + $name = \function_exists('mb_strtolower') && preg_match('//u', $name) ? mb_strtolower($name, 'UTF-8') : strtolower($name); + if ($this->defaultRouteIndex > 0) { + $name .= '_'.$this->defaultRouteIndex; + } + ++$this->defaultRouteIndex; + + return $name; + } + + /** + * @return array + */ + protected function getGlobals(\ReflectionClass $class): array + { + $globals = $this->resetGlobals(); + + if ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) { + $annot = $attribute->newInstance(); + + if (null !== $annot->getName()) { + $globals['name'] = $annot->getName(); + } + + if (null !== $annot->getPath()) { + $globals['path'] = $annot->getPath(); + } + + $globals['localized_paths'] = $annot->getLocalizedPaths(); + + if (null !== $annot->getRequirements()) { + $globals['requirements'] = $annot->getRequirements(); + } + + if (null !== $annot->getOptions()) { + $globals['options'] = $annot->getOptions(); + } + + if (null !== $annot->getDefaults()) { + $globals['defaults'] = $annot->getDefaults(); + } + + if (null !== $annot->getSchemes()) { + $globals['schemes'] = $annot->getSchemes(); + } + + if (null !== $annot->getMethods()) { + $globals['methods'] = $annot->getMethods(); + } + + if (null !== $annot->getHost()) { + $globals['host'] = $annot->getHost(); + } + + if (null !== $annot->getCondition()) { + $globals['condition'] = $annot->getCondition(); + } + + $globals['priority'] = $annot->getPriority() ?? 0; + $globals['env'] = $annot->getEnv(); + + foreach ($globals['requirements'] as $placeholder => $requirement) { + if (\is_int($placeholder)) { + throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" in "%s"?', $placeholder, $requirement, $class->getName())); + } + } + } + + return $globals; + } + + private function resetGlobals(): array + { + return [ + 'path' => null, + 'localized_paths' => [], + 'requirements' => [], + 'options' => [], + 'defaults' => [], + 'schemes' => [], + 'methods' => [], + 'host' => '', + 'condition' => '', + 'name' => '', + 'priority' => 0, + 'env' => null, + ]; + } + + protected function createRoute(string $path, array $defaults, array $requirements, array $options, ?string $host, array $schemes, array $methods, ?string $condition): Route + { + return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + } + + /** + * @return void + */ + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot); + + /** + * @return iterable + */ + private function getAnnotations(\ReflectionClass|\ReflectionMethod $reflection): iterable + { + foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + yield $attribute->newInstance(); + } + } +} diff --git a/vendor/symfony/routing/Loader/AttributeDirectoryLoader.php b/vendor/symfony/routing/Loader/AttributeDirectoryLoader.php new file mode 100644 index 0000000..8bb5982 --- /dev/null +++ b/vendor/symfony/routing/Loader/AttributeDirectoryLoader.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * AttributeDirectoryLoader loads routing information from attributes set + * on PHP classes and methods. + * + * @author Fabien Potencier + * @author Alexandre Daubois + */ +class AttributeDirectoryLoader extends AttributeFileLoader +{ + /** + * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed + */ + public function load(mixed $path, ?string $type = null): ?RouteCollection + { + if (!is_dir($dir = $this->locator->locate($path))) { + return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection(); + } + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($dir, '/\.php$/')); + $files = iterator_to_array(new \RecursiveIteratorIterator( + new \RecursiveCallbackFilterIterator( + new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + fn (\SplFileInfo $current) => !str_starts_with($current->getBasename(), '.') + ), + \RecursiveIteratorIterator::LEAVES_ONLY + )); + usort($files, fn (\SplFileInfo $a, \SplFileInfo $b) => (string) $a > (string) $b ? 1 : -1); + + foreach ($files as $file) { + if (!$file->isFile() || !str_ends_with($file->getFilename(), '.php')) { + continue; + } + + if ($class = $this->findClass($file)) { + $refl = new \ReflectionClass($class); + if ($refl->isAbstract()) { + continue; + } + + $collection->addCollection($this->loader->load($class, $type)); + } + } + + return $collection; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + if (!\is_string($resource)) { + return false; + } + + if ('attribute' === $type) { + return true; + } + + if ($type) { + return false; + } + + try { + return is_dir($this->locator->locate($resource)); + } catch (\Exception) { + return false; + } + } +} diff --git a/vendor/symfony/routing/Loader/AttributeFileLoader.php b/vendor/symfony/routing/Loader/AttributeFileLoader.php new file mode 100644 index 0000000..8cc74ec --- /dev/null +++ b/vendor/symfony/routing/Loader/AttributeFileLoader.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * AttributeFileLoader loads routing information from attributes set + * on a PHP class and its methods. + * + * @author Fabien Potencier + * @author Alexandre Daubois + */ +class AttributeFileLoader extends FileLoader +{ + public function __construct( + FileLocatorInterface $locator, + protected AttributeClassLoader $loader, + ) { + if (!\function_exists('token_get_all')) { + throw new \LogicException('The Tokenizer extension is required for the routing attribute loader.'); + } + + parent::__construct($locator); + } + + /** + * Loads from attributes from a file. + * + * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed + */ + public function load(mixed $file, ?string $type = null): ?RouteCollection + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + if ($class = $this->findClass($path)) { + $refl = new \ReflectionClass($class); + if ($refl->isAbstract()) { + return null; + } + + $collection->addResource(new FileResource($path)); + $collection->addCollection($this->loader->load($class, $type)); + } + + gc_mem_caches(); + + return $collection; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'attribute' === $type); + } + + /** + * Returns the full class name for the first class in the file. + */ + protected function findClass(string $file): string|false + { + $class = false; + $namespace = false; + $tokens = token_get_all(file_get_contents($file)); + + if (1 === \count($tokens) && \T_INLINE_HTML === $tokens[0][0]) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forget to add the " true, \T_STRING => true]; + if (\defined('T_NAME_QUALIFIED')) { + $nsTokens[\T_NAME_QUALIFIED] = true; + } + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1])) { + continue; + } + + if (true === $class && \T_STRING === $token[0]) { + return $namespace.'\\'.$token[1]; + } + + if (true === $namespace && isset($nsTokens[$token[0]])) { + $namespace = $token[1]; + while (isset($tokens[++$i][1], $nsTokens[$tokens[$i][0]])) { + $namespace .= $tokens[$i][1]; + } + $token = $tokens[$i]; + } + + if (\T_CLASS === $token[0]) { + // Skip usage of ::class constant and anonymous classes + $skipClassToken = false; + for ($j = $i - 1; $j > 0; --$j) { + if (!isset($tokens[$j][1])) { + if ('(' === $tokens[$j] || ',' === $tokens[$j]) { + $skipClassToken = true; + } + break; + } + + if (\T_DOUBLE_COLON === $tokens[$j][0] || \T_NEW === $tokens[$j][0]) { + $skipClassToken = true; + break; + } elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) { + break; + } + } + + if (!$skipClassToken) { + $class = true; + } + } + + if (\T_NAMESPACE === $token[0]) { + $namespace = true; + } + } + + return false; + } +} diff --git a/vendor/symfony/routing/Loader/ClosureLoader.php b/vendor/symfony/routing/Loader/ClosureLoader.php new file mode 100644 index 0000000..dcc5ee3 --- /dev/null +++ b/vendor/symfony/routing/Loader/ClosureLoader.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Routing\RouteCollection; + +/** + * ClosureLoader loads routes from a PHP closure. + * + * The Closure must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class ClosureLoader extends Loader +{ + /** + * Loads a Closure. + */ + public function load(mixed $closure, ?string $type = null): RouteCollection + { + return $closure($this->env); + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return $resource instanceof \Closure && (!$type || 'closure' === $type); + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/AliasConfigurator.php b/vendor/symfony/routing/Loader/Configurator/AliasConfigurator.php new file mode 100644 index 0000000..e36f8ce --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/AliasConfigurator.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\Routing\Alias; + +class AliasConfigurator +{ + public function __construct( + private Alias $alias, + ) { + } + + /** + * Whether this alias is deprecated, that means it should not be called anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function deprecate(string $package, string $version, string $message): static + { + $this->alias->setDeprecated($package, $version, $message); + + return $this; + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php b/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php new file mode 100644 index 0000000..8d303f6 --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class CollectionConfigurator +{ + use Traits\AddTrait; + use Traits\HostTrait; + use Traits\RouteTrait; + + private string|array|null $host = null; + + public function __construct( + private RouteCollection $parent, + string $name, + private ?self $parentConfigurator = null, // for GC control + private ?array $parentPrefixes = null, + ) { + $this->name = $name; + $this->collection = new RouteCollection(); + $this->route = new Route(''); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (null === $this->prefixes) { + $this->collection->addPrefix($this->route->getPath()); + } + if (null !== $this->host) { + $this->addHost($this->collection, $this->host); + } + + $this->parent->addCollection($this->collection); + } + + /** + * Creates a sub-collection. + */ + final public function collection(string $name = ''): self + { + return new self($this->collection, $this->name.$name, $this, $this->prefixes); + } + + /** + * Sets the prefix to add to the path of all child routes. + * + * @param string|array $prefix the prefix, or the localized prefixes + * + * @return $this + */ + final public function prefix(string|array $prefix): static + { + if (\is_array($prefix)) { + if (null === $this->parentPrefixes) { + // no-op + } elseif ($missing = array_diff_key($this->parentPrefixes, $prefix)) { + throw new \LogicException(sprintf('Collection "%s" is missing prefixes for locale(s) "%s".', $this->name, implode('", "', array_keys($missing)))); + } else { + foreach ($prefix as $locale => $localePrefix) { + if (!isset($this->parentPrefixes[$locale])) { + throw new \LogicException(sprintf('Collection "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $this->name, $locale)); + } + + $prefix[$locale] = $this->parentPrefixes[$locale].$localePrefix; + } + } + $this->prefixes = $prefix; + $this->route->setPath('/'); + } else { + $this->prefixes = null; + $this->route->setPath($prefix); + } + + return $this; + } + + /** + * Sets the host to use for all child routes. + * + * @param string|array $host the host, or the localized hosts + * + * @return $this + */ + final public function host(string|array $host): static + { + $this->host = $host; + + return $this; + } + + /** + * This method overrides the one from LocalizedRouteTrait. + */ + private function createRoute(string $path): Route + { + return (clone $this->route)->setPath($path); + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php b/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php new file mode 100644 index 0000000..45d1f6d --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class ImportConfigurator +{ + use Traits\HostTrait; + use Traits\PrefixTrait; + use Traits\RouteTrait; + + public function __construct( + private RouteCollection $parent, + RouteCollection $route, + ) { + $this->route = $route; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->parent->addCollection($this->route); + } + + /** + * Sets the prefix to add to the path of all child routes. + * + * @param string|array $prefix the prefix, or the localized prefixes + * + * @return $this + */ + final public function prefix(string|array $prefix, bool $trailingSlashOnRoot = true): static + { + $this->addPrefix($this->route, $prefix, $trailingSlashOnRoot); + + return $this; + } + + /** + * Sets the prefix to add to the name of all child routes. + * + * @return $this + */ + final public function namePrefix(string $namePrefix): static + { + $this->route->addNamePrefix($namePrefix); + + return $this; + } + + /** + * Sets the host to use for all child routes. + * + * @param string|array $host the host, or the localized hosts + * + * @return $this + */ + final public function host(string|array $host): static + { + $this->addHost($this->route, $host); + + return $this; + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php b/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php new file mode 100644 index 0000000..f242ad8 --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class RouteConfigurator +{ + use Traits\AddTrait; + use Traits\HostTrait; + use Traits\RouteTrait; + + public function __construct( + RouteCollection $collection, + RouteCollection $route, + string $name = '', + protected ?CollectionConfigurator $parentConfigurator = null, // for GC control + ?array $prefixes = null, + ) { + $this->collection = $collection; + $this->route = $route; + $this->name = $name; + $this->prefixes = $prefixes; + } + + /** + * Sets the host to use for all child routes. + * + * @param string|array $host the host, or the localized hosts + * + * @return $this + */ + final public function host(string|array $host): static + { + $this->addHost($this->route, $host); + + return $this; + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php b/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php new file mode 100644 index 0000000..2ff5e3e --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\Loader\PhpFileLoader; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class RoutingConfigurator +{ + use Traits\AddTrait; + + public function __construct( + RouteCollection $collection, + private PhpFileLoader $loader, + private string $path, + private string $file, + private ?string $env = null, + ) { + $this->collection = $collection; + } + + /** + * @param string|string[]|null $exclude Glob patterns to exclude from the import + */ + final public function import(string|array $resource, ?string $type = null, bool $ignoreErrors = false, string|array|null $exclude = null): ImportConfigurator + { + $this->loader->setCurrentDir(\dirname($this->path)); + + $imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file, $exclude) ?: []; + if (!\is_array($imported)) { + return new ImportConfigurator($this->collection, $imported); + } + + $mergedCollection = new RouteCollection(); + foreach ($imported as $subCollection) { + $mergedCollection->addCollection($subCollection); + } + + return new ImportConfigurator($this->collection, $mergedCollection); + } + + final public function collection(string $name = ''): CollectionConfigurator + { + return new CollectionConfigurator($this->collection, $name); + } + + /** + * Get the current environment to be able to write conditional configuration. + */ + final public function env(): ?string + { + return $this->env; + } + + final public function withPath(string $path): static + { + $clone = clone $this; + $clone->path = $clone->file = $path; + + return $clone; + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php b/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php new file mode 100644 index 0000000..5668ab0 --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Loader\Configurator\AliasConfigurator; +use Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator; +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +trait AddTrait +{ + use LocalizedRouteTrait; + + protected RouteCollection $collection; + protected string $name = ''; + protected ?array $prefixes = null; + + /** + * Adds a route. + * + * @param string|array $path the path, or the localized paths of the route + */ + public function add(string $name, string|array $path): RouteConfigurator + { + $parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null); + $route = $this->createLocalizedRoute($this->collection, $name, $path, $this->name, $this->prefixes); + + return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes); + } + + public function alias(string $name, string $alias): AliasConfigurator + { + return new AliasConfigurator($this->collection->addAlias($name, $alias)); + } + + /** + * Adds a route. + * + * @param string|array $path the path, or the localized paths of the route + */ + public function __invoke(string $name, string|array $path): RouteConfigurator + { + return $this->add($name, $path); + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/Traits/HostTrait.php b/vendor/symfony/routing/Loader/Configurator/Traits/HostTrait.php new file mode 100644 index 0000000..d275f6c --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/Traits/HostTrait.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\RouteCollection; + +/** + * @internal + */ +trait HostTrait +{ + final protected function addHost(RouteCollection $routes, string|array $hosts): void + { + if (!$hosts || !\is_array($hosts)) { + $routes->setHost($hosts ?: ''); + + return; + } + + foreach ($routes->all() as $name => $route) { + if (null === $locale = $route->getDefault('_locale')) { + $routes->remove($name); + foreach ($hosts as $locale => $host) { + $localizedRoute = clone $route; + $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setRequirement('_locale', preg_quote($locale)); + $localizedRoute->setDefault('_canonical_route', $name); + $localizedRoute->setHost($host); + $routes->add($name.'.'.$locale, $localizedRoute); + } + } elseif (!isset($hosts[$locale])) { + throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding host in its parent collection.', $name, $locale)); + } else { + $route->setHost($hosts[$locale]); + $route->setRequirement('_locale', preg_quote($locale)); + $routes->add($name, $route); + } + } + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php b/vendor/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php new file mode 100644 index 0000000..a26a734 --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @internal + * + * @author Nicolas Grekas + * @author Jules Pietri + */ +trait LocalizedRouteTrait +{ + /** + * Creates one or many routes. + * + * @param string|array $path the path, or the localized paths of the route + */ + final protected function createLocalizedRoute(RouteCollection $collection, string $name, string|array $path, string $namePrefix = '', ?array $prefixes = null): RouteCollection + { + $paths = []; + + $routes = new RouteCollection(); + + if (\is_array($path)) { + if (null === $prefixes) { + $paths = $path; + } elseif ($missing = array_diff_key($prefixes, $path)) { + throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing)))); + } else { + foreach ($path as $locale => $localePath) { + if (!isset($prefixes[$locale])) { + throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); + } + + $paths[$locale] = $prefixes[$locale].$localePath; + } + } + } elseif (null !== $prefixes) { + foreach ($prefixes as $locale => $prefix) { + $paths[$locale] = $prefix.$path; + } + } else { + $routes->add($namePrefix.$name, $route = $this->createRoute($path)); + $collection->add($namePrefix.$name, $route); + + return $routes; + } + + foreach ($paths as $locale => $path) { + $routes->add($name.'.'.$locale, $route = $this->createRoute($path)); + $collection->add($namePrefix.$name.'.'.$locale, $route); + $route->setDefault('_locale', $locale); + $route->setRequirement('_locale', preg_quote($locale)); + $route->setDefault('_canonical_route', $namePrefix.$name); + } + + return $routes; + } + + private function createRoute(string $path): Route + { + return new Route($path); + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php b/vendor/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php new file mode 100644 index 0000000..89a65d8 --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @internal + * + * @author Nicolas Grekas + */ +trait PrefixTrait +{ + final protected function addPrefix(RouteCollection $routes, string|array $prefix, bool $trailingSlashOnRoot): void + { + if (\is_array($prefix)) { + foreach ($prefix as $locale => $localePrefix) { + $prefix[$locale] = trim(trim($localePrefix), '/'); + } + foreach ($routes->all() as $name => $route) { + if (null === $locale = $route->getDefault('_locale')) { + $priority = $routes->getPriority($name) ?? 0; + $routes->remove($name); + foreach ($prefix as $locale => $localePrefix) { + $localizedRoute = clone $route; + $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setRequirement('_locale', preg_quote($locale)); + $localizedRoute->setDefault('_canonical_route', $name); + $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); + $routes->add($name.'.'.$locale, $localizedRoute, $priority); + } + } elseif (!isset($prefix[$locale])) { + throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); + } else { + $route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); + $routes->add($name, $route, $routes->getPriority($name) ?? 0); + } + } + + return; + } + + $routes->addPrefix($prefix); + if (!$trailingSlashOnRoot) { + $rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath(); + foreach ($routes->all() as $route) { + if ($route->getPath() === $rootPath) { + $route->setPath(rtrim($rootPath, '/')); + } + } + } + } +} diff --git a/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php b/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php new file mode 100644 index 0000000..0e93aa6 --- /dev/null +++ b/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +trait RouteTrait +{ + protected RouteCollection|Route $route; + + /** + * Adds defaults. + * + * @return $this + */ + final public function defaults(array $defaults): static + { + $this->route->addDefaults($defaults); + + return $this; + } + + /** + * Adds requirements. + * + * @return $this + */ + final public function requirements(array $requirements): static + { + $this->route->addRequirements($requirements); + + return $this; + } + + /** + * Adds options. + * + * @return $this + */ + final public function options(array $options): static + { + $this->route->addOptions($options); + + return $this; + } + + /** + * Whether paths should accept utf8 encoding. + * + * @return $this + */ + final public function utf8(bool $utf8 = true): static + { + $this->route->addOptions(['utf8' => $utf8]); + + return $this; + } + + /** + * Sets the condition. + * + * @return $this + */ + final public function condition(string $condition): static + { + $this->route->setCondition($condition); + + return $this; + } + + /** + * Sets the pattern for the host. + * + * @return $this + */ + final public function host(string $pattern): static + { + $this->route->setHost($pattern); + + return $this; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @param string[] $schemes + * + * @return $this + */ + final public function schemes(array $schemes): static + { + $this->route->setSchemes($schemes); + + return $this; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * @param string[] $methods + * + * @return $this + */ + final public function methods(array $methods): static + { + $this->route->setMethods($methods); + + return $this; + } + + /** + * Adds the "_controller" entry to defaults. + * + * @param callable|string|array $controller a callable or parseable pseudo-callable + * + * @return $this + */ + final public function controller(callable|string|array $controller): static + { + $this->route->addDefaults(['_controller' => $controller]); + + return $this; + } + + /** + * Adds the "_locale" entry to defaults. + * + * @return $this + */ + final public function locale(string $locale): static + { + $this->route->addDefaults(['_locale' => $locale]); + + return $this; + } + + /** + * Adds the "_format" entry to defaults. + * + * @return $this + */ + final public function format(string $format): static + { + $this->route->addDefaults(['_format' => $format]); + + return $this; + } + + /** + * Adds the "_stateless" entry to defaults. + * + * @return $this + */ + final public function stateless(bool $stateless = true): static + { + $this->route->addDefaults(['_stateless' => $stateless]); + + return $this; + } +} diff --git a/vendor/symfony/routing/Loader/ContainerLoader.php b/vendor/symfony/routing/Loader/ContainerLoader.php new file mode 100644 index 0000000..7513dae --- /dev/null +++ b/vendor/symfony/routing/Loader/ContainerLoader.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Psr\Container\ContainerInterface; + +/** + * A route loader that executes a service from a PSR-11 container to load the routes. + * + * @author Ryan Weaver + */ +class ContainerLoader extends ObjectLoader +{ + public function __construct( + private ContainerInterface $container, + ?string $env = null, + ) { + parent::__construct($env); + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return 'service' === $type && \is_string($resource); + } + + protected function getObject(string $id): object + { + return $this->container->get($id); + } +} diff --git a/vendor/symfony/routing/Loader/DirectoryLoader.php b/vendor/symfony/routing/Loader/DirectoryLoader.php new file mode 100644 index 0000000..6c6c48e --- /dev/null +++ b/vendor/symfony/routing/Loader/DirectoryLoader.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Routing\RouteCollection; + +class DirectoryLoader extends FileLoader +{ + public function load(mixed $file, ?string $type = null): mixed + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($path)); + + foreach (scandir($path) as $dir) { + if ('.' !== $dir[0]) { + $this->setCurrentDir($path); + $subPath = $path.'/'.$dir; + $subType = null; + + if (is_dir($subPath)) { + $subPath .= '/'; + $subType = 'directory'; + } + + $subCollection = $this->import($subPath, $subType, false, $path); + $collection->addCollection($subCollection); + } + } + + return $collection; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + // only when type is forced to directory, not to conflict with AttributeLoader + + return 'directory' === $type; + } +} diff --git a/vendor/symfony/routing/Loader/GlobFileLoader.php b/vendor/symfony/routing/Loader/GlobFileLoader.php new file mode 100644 index 0000000..65afa5a --- /dev/null +++ b/vendor/symfony/routing/Loader/GlobFileLoader.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Routing\RouteCollection; + +/** + * GlobFileLoader loads files from a glob pattern. + * + * @author Nicolas Grekas + */ +class GlobFileLoader extends FileLoader +{ + public function load(mixed $resource, ?string $type = null): mixed + { + $collection = new RouteCollection(); + + foreach ($this->glob($resource, false, $globResource) as $path => $info) { + $collection->addCollection($this->import($path)); + } + + $collection->addResource($globResource); + + return $collection; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return 'glob' === $type; + } +} diff --git a/vendor/symfony/routing/Loader/ObjectLoader.php b/vendor/symfony/routing/Loader/ObjectLoader.php new file mode 100644 index 0000000..c2ad6a0 --- /dev/null +++ b/vendor/symfony/routing/Loader/ObjectLoader.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * A route loader that calls a method on an object to load the routes. + * + * @author Ryan Weaver + */ +abstract class ObjectLoader extends Loader +{ + /** + * Returns the object that the method will be called on to load routes. + * + * For example, if your application uses a service container, + * the $id may be a service id. + */ + abstract protected function getObject(string $id): object; + + /** + * Calls the object method that will load the routes. + */ + public function load(mixed $resource, ?string $type = null): RouteCollection + { + if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) { + throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object')); + } + + $parts = explode('::', $resource); + $method = $parts[1] ?? '__invoke'; + + $loaderObject = $this->getObject($parts[0]); + + if (!\is_object($loaderObject)) { + throw new \TypeError(sprintf('"%s:getObject()" must return an object: "%s" returned.', static::class, get_debug_type($loaderObject))); + } + + if (!\is_callable([$loaderObject, $method])) { + throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, get_debug_type($loaderObject), $resource)); + } + + $routeCollection = $loaderObject->$method($this, $this->env); + + if (!$routeCollection instanceof RouteCollection) { + $type = get_debug_type($routeCollection); + + throw new \LogicException(sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', get_debug_type($loaderObject), $method, $type)); + } + + // make the object file tracked so that if it changes, the cache rebuilds + $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); + + return $routeCollection; + } + + private function addClassResource(\ReflectionClass $class, RouteCollection $collection): void + { + do { + if (is_file($class->getFileName())) { + $collection->addResource(new FileResource($class->getFileName())); + } + } while ($class = $class->getParentClass()); + } +} diff --git a/vendor/symfony/routing/Loader/PhpFileLoader.php b/vendor/symfony/routing/Loader/PhpFileLoader.php new file mode 100644 index 0000000..adf7eed --- /dev/null +++ b/vendor/symfony/routing/Loader/PhpFileLoader.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpFileLoader loads routes from a PHP file. + * + * The file must return a RouteCollection instance. + * + * @author Fabien Potencier + * @author Nicolas grekas + * @author Jules Pietri + */ +class PhpFileLoader extends FileLoader +{ + /** + * Loads a PHP file. + */ + public function load(mixed $file, ?string $type = null): RouteCollection + { + $path = $this->locator->locate($file); + $this->setCurrentDir(\dirname($path)); + + // the closure forbids access to the private scope in the included file + $loader = $this; + $load = \Closure::bind(static function ($file) use ($loader) { + return include $file; + }, null, ProtectedPhpFileLoader::class); + + $result = $load($path); + + if (\is_object($result) && \is_callable($result)) { + $collection = $this->callConfigurator($result, $path, $file); + } else { + $collection = $result; + } + + $collection->addResource(new FileResource($path)); + + return $collection; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type); + } + + protected function callConfigurator(callable $result, string $path, string $file): RouteCollection + { + $collection = new RouteCollection(); + + $result(new RoutingConfigurator($collection, $this, $path, $file, $this->env)); + + return $collection; + } +} + +/** + * @internal + */ +final class ProtectedPhpFileLoader extends PhpFileLoader +{ +} diff --git a/vendor/symfony/routing/Loader/Psr4DirectoryLoader.php b/vendor/symfony/routing/Loader/Psr4DirectoryLoader.php new file mode 100644 index 0000000..738b56f --- /dev/null +++ b/vendor/symfony/routing/Loader/Psr4DirectoryLoader.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\DirectoryAwareLoaderInterface; +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * A loader that discovers controller classes in a directory that follows PSR-4. + * + * @author Alexander M. Turek + */ +final class Psr4DirectoryLoader extends Loader implements DirectoryAwareLoaderInterface +{ + private ?string $currentDirectory = null; + + public function __construct( + private readonly FileLocatorInterface $locator, + ) { + // PSR-4 directory loader has no env-aware logic, so we drop the $env constructor parameter. + parent::__construct(); + } + + /** + * @param array{path: string, namespace: string} $resource + */ + public function load(mixed $resource, ?string $type = null): ?RouteCollection + { + $path = $this->locator->locate($resource['path'], $this->currentDirectory); + if (!is_dir($path)) { + return new RouteCollection(); + } + + return $this->loadFromDirectory($path, trim($resource['namespace'], '\\')); + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return 'attribute' === $type && \is_array($resource) && isset($resource['path'], $resource['namespace']); + } + + public function forDirectory(string $currentDirectory): static + { + $loader = clone $this; + $loader->currentDirectory = $currentDirectory; + + return $loader; + } + + private function loadFromDirectory(string $directory, string $psr4Prefix): RouteCollection + { + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($directory, '/\.php$/')); + $files = iterator_to_array(new \RecursiveIteratorIterator( + new \RecursiveCallbackFilterIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + fn (\SplFileInfo $current) => !str_starts_with($current->getBasename(), '.') + ), + \RecursiveIteratorIterator::SELF_FIRST + )); + usort($files, fn (\SplFileInfo $a, \SplFileInfo $b) => (string) $a > (string) $b ? 1 : -1); + + /** @var \SplFileInfo $file */ + foreach ($files as $file) { + if ($file->isDir()) { + $collection->addCollection($this->loadFromDirectory($file->getPathname(), $psr4Prefix.'\\'.$file->getFilename())); + + continue; + } + if ('php' !== $file->getExtension() || !class_exists($className = $psr4Prefix.'\\'.$file->getBasename('.php')) || (new \ReflectionClass($className))->isAbstract()) { + continue; + } + + $collection->addCollection($this->import($className, 'attribute')); + } + + return $collection; + } +} diff --git a/vendor/symfony/routing/Loader/XmlFileLoader.php b/vendor/symfony/routing/Loader/XmlFileLoader.php new file mode 100644 index 0000000..296c2fe --- /dev/null +++ b/vendor/symfony/routing/Loader/XmlFileLoader.php @@ -0,0 +1,464 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; +use Symfony\Component\Routing\RouteCollection; + +/** + * XmlFileLoader loads XML routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class XmlFileLoader extends FileLoader +{ + use HostTrait; + use LocalizedRouteTrait; + use PrefixTrait; + + public const NAMESPACE_URI = 'http://symfony.com/schema/routing'; + public const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; + + /** + * @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be + * parsed because it does not validate against the scheme + */ + public function load(mixed $file, ?string $type = null): RouteCollection + { + $path = $this->locator->locate($file); + + $xml = $this->loadFile($path); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // process routes and imports + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + + $this->parseNode($collection, $node, $path, $file); + } + + return $collection; + } + + /** + * Parses a node from a loaded XML file. + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseNode(RouteCollection $collection, \DOMElement $node, string $path, string $file): void + { + if (self::NAMESPACE_URI !== $node->namespaceURI) { + return; + } + + switch ($node->localName) { + case 'route': + $this->parseRoute($collection, $node, $path); + break; + case 'import': + $this->parseImport($collection, $node, $path, $file); + break; + case 'when': + if (!$this->env || $node->getAttribute('env') !== $this->env) { + break; + } + foreach ($node->childNodes as $node) { + if ($node instanceof \DOMElement) { + $this->parseNode($collection, $node, $path, $file); + } + } + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); + } + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return \is_string($resource) && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseRoute(RouteCollection $collection, \DOMElement $node, string $path): void + { + if ('' === $id = $node->getAttribute('id')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" attribute.', $path)); + } + + if ('' !== $alias = $node->getAttribute('alias')) { + $alias = $collection->addAlias($id, $alias); + + if ($deprecationInfo = $this->parseDeprecation($node, $path)) { + $alias->setDeprecated($deprecationInfo['package'], $deprecationInfo['version'], $deprecationInfo['message']); + } + + return; + } + + $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY); + $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY); + + [$defaults, $requirements, $options, $condition, $paths, /* $prefixes */, $hosts] = $this->parseConfigs($node, $path); + + if (!$paths && '' === $node->getAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "path" attribute or child nodes.', $path)); + } + + if ($paths && '' !== $node->getAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must not have both a "path" attribute and child nodes.', $path)); + } + + $routes = $this->createLocalizedRoute($collection, $id, $paths ?: $node->getAttribute('path')); + $routes->addDefaults($defaults); + $routes->addRequirements($requirements); + $routes->addOptions($options); + $routes->setSchemes($schemes); + $routes->setMethods($methods); + $routes->setCondition($condition); + + if (null !== $hosts) { + $this->addHost($routes, $hosts); + } + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseImport(RouteCollection $collection, \DOMElement $node, string $path, string $file): void + { + /** @var \DOMElement $resourceElement */ + if (!($resource = $node->getAttribute('resource') ?: null) && $resourceElement = $node->getElementsByTagName('resource')[0] ?? null) { + $resource = []; + /** @var \DOMAttr $attribute */ + foreach ($resourceElement->attributes as $attribute) { + $resource[$attribute->name] = $attribute->value; + } + } + + if (!$resource) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute or element.', $path)); + } + + $type = $node->getAttribute('type'); + $prefix = $node->getAttribute('prefix'); + $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY) : null; + $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY) : null; + $trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : true; + $namePrefix = $node->getAttribute('name-prefix') ?: null; + + [$defaults, $requirements, $options, $condition, /* $paths */, $prefixes, $hosts] = $this->parseConfigs($node, $path); + + if ('' !== $prefix && $prefixes) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must not have both a "prefix" attribute and child nodes.', $path)); + } + + $exclude = []; + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement && $child->localName === $exclude && self::NAMESPACE_URI === $child->namespaceURI) { + $exclude[] = $child->nodeValue; + } + } + + if ($node->hasAttribute('exclude')) { + if ($exclude) { + throw new \InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $exclude = [$node->getAttribute('exclude')]; + } + + $this->setCurrentDir(\dirname($path)); + + /** @var RouteCollection[] $imported */ + $imported = $this->import($resource, '' !== $type ? $type : null, false, $file, $exclude) ?: []; + + if (!\is_array($imported)) { + $imported = [$imported]; + } + + foreach ($imported as $subCollection) { + $this->addPrefix($subCollection, $prefixes ?: $prefix, $trailingSlashOnRoot); + + if (null !== $hosts) { + $this->addHost($subCollection, $hosts); + } + + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + if (null !== $namePrefix) { + $subCollection->addNamePrefix($namePrefix); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + } + + /** + * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors + * or when the XML structure is not as expected by the scheme - + * see validate() + */ + protected function loadFile(string $file): \DOMDocument + { + return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); + } + + /** + * Parses the config elements (default, requirement, option). + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseConfigs(\DOMElement $node, string $path): array + { + $defaults = []; + $requirements = []; + $options = []; + $condition = null; + $prefixes = []; + $paths = []; + $hosts = []; + + /** @var \DOMElement $n */ + foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { + if ($node !== $n->parentNode) { + continue; + } + + switch ($n->localName) { + case 'path': + $paths[$n->getAttribute('locale')] = trim($n->textContent); + break; + case 'host': + $hosts[$n->getAttribute('locale')] = trim($n->textContent); + break; + case 'prefix': + $prefixes[$n->getAttribute('locale')] = trim($n->textContent); + break; + case 'default': + if ($this->isElementValueNull($n)) { + $defaults[$n->getAttribute('key')] = null; + } else { + $defaults[$n->getAttribute('key')] = $this->parseDefaultsConfig($n, $path); + } + + break; + case 'requirement': + $requirements[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'option': + $options[$n->getAttribute('key')] = XmlUtils::phpize(trim($n->textContent)); + break; + case 'condition': + $condition = trim($n->textContent); + break; + case 'resource': + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path)); + } + } + + if ($controller = $node->getAttribute('controller')) { + if (isset($defaults['_controller'])) { + $name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName); + + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for ', $path).$name); + } + + $defaults['_controller'] = $controller; + } + if ($node->hasAttribute('locale')) { + $defaults['_locale'] = $node->getAttribute('locale'); + } + if ($node->hasAttribute('format')) { + $defaults['_format'] = $node->getAttribute('format'); + } + if ($node->hasAttribute('utf8')) { + $options['utf8'] = XmlUtils::phpize($node->getAttribute('utf8')); + } + if ($stateless = $node->getAttribute('stateless')) { + if (isset($defaults['_stateless'])) { + $name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName); + + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" attribute and the defaults key "_stateless" for ', $path).$name); + } + + $defaults['_stateless'] = XmlUtils::phpize($stateless); + } + + if (!$hosts) { + $hosts = $node->hasAttribute('host') ? $node->getAttribute('host') : null; + } + + return [$defaults, $requirements, $options, $condition, $paths, $prefixes, $hosts]; + } + + /** + * Parses the "default" elements. + */ + private function parseDefaultsConfig(\DOMElement $element, string $path): array|bool|float|int|string|null + { + if ($this->isElementValueNull($element)) { + return null; + } + + // Check for existing element nodes in the default element. There can + // only be a single element inside a default element. So this element + // (if one was found) can safely be returned. + foreach ($element->childNodes as $child) { + if (!$child instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $child->namespaceURI) { + continue; + } + + return $this->parseDefaultNode($child, $path); + } + + // If the default element doesn't contain a nested "bool", "int", "float", + // "string", "list", or "map" element, the element contents will be treated + // as the string value of the associated default option. + return trim($element->textContent); + } + + /** + * Recursively parses the value of a "default" element. + * + * @throws \InvalidArgumentException when the XML is invalid + */ + private function parseDefaultNode(\DOMElement $node, string $path): array|bool|float|int|string|null + { + if ($this->isElementValueNull($node)) { + return null; + } + + switch ($node->localName) { + case 'bool': + return 'true' === trim($node->nodeValue) || '1' === trim($node->nodeValue); + case 'int': + return (int) trim($node->nodeValue); + case 'float': + return (float) trim($node->nodeValue); + case 'string': + return trim($node->nodeValue); + case 'list': + $list = []; + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $list[] = $this->parseDefaultNode($element, $path); + } + + return $list; + case 'map': + $map = []; + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $map[$element->getAttribute('key')] = $this->parseDefaultNode($element, $path); + } + + return $map; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".', $node->localName, $path)); + } + } + + private function isElementValueNull(\DOMElement $element): bool + { + $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + + if (!$element->hasAttributeNS($namespaceUri, 'nil')) { + return false; + } + + return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil'); + } + + /** + * Parses the deprecation elements. + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseDeprecation(\DOMElement $node, string $path): array + { + $deprecatedNode = null; + foreach ($node->childNodes as $child) { + if (!$child instanceof \DOMElement || self::NAMESPACE_URI !== $child->namespaceURI) { + continue; + } + if ('deprecated' !== $child->localName) { + throw new \InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $node->getAttribute('id'), $path)); + } + + $deprecatedNode = $child; + } + + if (null === $deprecatedNode) { + return []; + } + + if (!$deprecatedNode->hasAttribute('package')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "package" attribute.', $path)); + } + if (!$deprecatedNode->hasAttribute('version')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "version" attribute.', $path)); + } + + return [ + 'package' => $deprecatedNode->getAttribute('package'), + 'version' => $deprecatedNode->getAttribute('version'), + 'message' => trim($deprecatedNode->nodeValue), + ]; + } +} diff --git a/vendor/symfony/routing/Loader/YamlFileLoader.php b/vendor/symfony/routing/Loader/YamlFileLoader.php new file mode 100644 index 0000000..f5ea8e8 --- /dev/null +++ b/vendor/symfony/routing/Loader/YamlFileLoader.php @@ -0,0 +1,296 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileLoader loads Yaml routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class YamlFileLoader extends FileLoader +{ + use HostTrait; + use LocalizedRouteTrait; + use PrefixTrait; + + private const AVAILABLE_KEYS = [ + 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless', + ]; + private YamlParser $yamlParser; + + /** + * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid + */ + public function load(mixed $file, ?string $type = null): RouteCollection + { + $path = $this->locator->locate($file); + + if (!stream_is_local($path)) { + throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path)); + } + + if (!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path)); + } + + $this->yamlParser ??= new YamlParser(); + + try { + $parsedConfig = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT); + } catch (ParseException $e) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $path).$e->getMessage(), 0, $e); + } + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // empty file + if (null === $parsedConfig) { + return $collection; + } + + // not an array + if (!\is_array($parsedConfig)) { + throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path)); + } + + foreach ($parsedConfig as $name => $config) { + if (str_starts_with($name, 'when@')) { + if (!$this->env || 'when@'.$this->env !== $name) { + continue; + } + + foreach ($config as $name => $config) { + $this->validate($config, $name.'" when "@'.$this->env, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + continue; + } + + $this->validate($config, $name, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + return $collection; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return \is_string($resource) && \in_array(pathinfo($resource, \PATHINFO_EXTENSION), ['yml', 'yaml'], true) && (!$type || 'yaml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + */ + protected function parseRoute(RouteCollection $collection, string $name, array $config, string $path): void + { + if (isset($config['alias'])) { + $alias = $collection->addAlias($name, $config['alias']); + $deprecation = $config['deprecated'] ?? null; + if (null !== $deprecation) { + $alias->setDeprecated( + $deprecation['package'], + $deprecation['version'], + $deprecation['message'] ?? '' + ); + } + + return; + } + + $defaults = $config['defaults'] ?? []; + $requirements = $config['requirements'] ?? []; + $options = $config['options'] ?? []; + + foreach ($requirements as $placeholder => $requirement) { + if (\is_int($placeholder)) { + throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s"?', $placeholder, $requirement, $name, $path)); + } + } + + if (isset($config['controller'])) { + $defaults['_controller'] = $config['controller']; + } + if (isset($config['locale'])) { + $defaults['_locale'] = $config['locale']; + } + if (isset($config['format'])) { + $defaults['_format'] = $config['format']; + } + if (isset($config['utf8'])) { + $options['utf8'] = $config['utf8']; + } + if (isset($config['stateless'])) { + $defaults['_stateless'] = $config['stateless']; + } + + $routes = $this->createLocalizedRoute($collection, $name, $config['path']); + $routes->addDefaults($defaults); + $routes->addRequirements($requirements); + $routes->addOptions($options); + $routes->setSchemes($config['schemes'] ?? []); + $routes->setMethods($config['methods'] ?? []); + $routes->setCondition($config['condition'] ?? null); + + if (isset($config['host'])) { + $this->addHost($routes, $config['host']); + } + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + */ + protected function parseImport(RouteCollection $collection, array $config, string $path, string $file): void + { + $type = $config['type'] ?? null; + $prefix = $config['prefix'] ?? ''; + $defaults = $config['defaults'] ?? []; + $requirements = $config['requirements'] ?? []; + $options = $config['options'] ?? []; + $host = $config['host'] ?? null; + $condition = $config['condition'] ?? null; + $schemes = $config['schemes'] ?? null; + $methods = $config['methods'] ?? null; + $trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true; + $namePrefix = $config['name_prefix'] ?? null; + $exclude = $config['exclude'] ?? null; + + if (isset($config['controller'])) { + $defaults['_controller'] = $config['controller']; + } + if (isset($config['locale'])) { + $defaults['_locale'] = $config['locale']; + } + if (isset($config['format'])) { + $defaults['_format'] = $config['format']; + } + if (isset($config['utf8'])) { + $options['utf8'] = $config['utf8']; + } + if (isset($config['stateless'])) { + $defaults['_stateless'] = $config['stateless']; + } + + $this->setCurrentDir(\dirname($path)); + + /** @var RouteCollection[] $imported */ + $imported = $this->import($config['resource'], $type, false, $file, $exclude) ?: []; + + if (!\is_array($imported)) { + $imported = [$imported]; + } + + foreach ($imported as $subCollection) { + $this->addPrefix($subCollection, $prefix, $trailingSlashOnRoot); + + if (null !== $host) { + $this->addHost($subCollection, $host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + if (null !== $namePrefix) { + $subCollection->addNamePrefix($namePrefix); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + } + + /** + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + protected function validate(mixed $config, string $name, string $path): void + { + if (!\is_array($config)) { + throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); + } + if (isset($config['alias'])) { + $this->validateAlias($config, $name, $path); + + return; + } + if ($extraKeys = array_diff(array_keys($config), self::AVAILABLE_KEYS)) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, implode('", "', $extraKeys), implode('", "', self::AVAILABLE_KEYS))); + } + if (isset($config['resource']) && isset($config['path'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', $path, $name)); + } + if (!isset($config['resource']) && isset($config['type'])) { + throw new \InvalidArgumentException(sprintf('The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', $name, $path)); + } + if (!isset($config['resource']) && !isset($config['path'])) { + throw new \InvalidArgumentException(sprintf('You must define a "path" for the route "%s" in file "%s".', $name, $path)); + } + if (isset($config['controller']) && isset($config['defaults']['_controller'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name)); + } + if (isset($config['stateless']) && isset($config['defaults']['_stateless'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" key and the defaults key "_stateless" for "%s".', $path, $name)); + } + } + + /** + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + private function validateAlias(array $config, string $name, string $path): void + { + foreach ($config as $key => $value) { + if (!\in_array($key, ['alias', 'deprecated'], true)) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify other keys than "alias" and "deprecated" for "%s".', $path, $name)); + } + + if ('deprecated' === $key) { + if (!isset($value['package'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "package" of the "deprecated" option for "%s".', $path, $name)); + } + + if (!isset($value['version'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "version" of the "deprecated" option for "%s".', $path, $name)); + } + } + } + } +} diff --git a/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd new file mode 100644 index 0000000..1b24dfd --- /dev/null +++ b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Matcher/CompiledUrlMatcher.php b/vendor/symfony/routing/Matcher/CompiledUrlMatcher.php new file mode 100644 index 0000000..ae13fd7 --- /dev/null +++ b/vendor/symfony/routing/Matcher/CompiledUrlMatcher.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherTrait; +use Symfony\Component\Routing\RequestContext; + +/** + * Matches URLs based on rules dumped by CompiledUrlMatcherDumper. + * + * @author Nicolas Grekas + */ +class CompiledUrlMatcher extends UrlMatcher +{ + use CompiledUrlMatcherTrait; + + public function __construct(array $compiledRoutes, RequestContext $context) + { + $this->context = $context; + [$this->matchHost, $this->staticRoutes, $this->regexpList, $this->dynamicRoutes, $this->checkCondition] = $compiledRoutes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php new file mode 100644 index 0000000..254bad1 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php @@ -0,0 +1,498 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * CompiledUrlMatcherDumper creates PHP arrays to be used with CompiledUrlMatcher. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Arnaud Le Blanc + * @author Nicolas Grekas + */ +class CompiledUrlMatcherDumper extends MatcherDumper +{ + private ExpressionLanguage $expressionLanguage; + private ?\Exception $signalingException = null; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private array $expressionLanguageProviders = []; + + public function dump(array $options = []): string + { + return <<generateCompiledRoutes()}]; + +EOF; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider): void + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Generates the arrays for CompiledUrlMatcher's constructor. + */ + public function getCompiledRoutes(bool $forDump = false): array + { + // Group hosts by same-suffix, re-order when possible + $matchHost = false; + $routes = new StaticPrefixCollection(); + foreach ($this->getRoutes()->all() as $name => $route) { + if ($host = $route->getHost()) { + $matchHost = true; + $host = '/'.strtr(strrev($host), '}.{', '(/)'); + } + + $routes->addRoute($host ?: '/(.*)', [$name, $route]); + } + + if ($matchHost) { + $compiledRoutes = [true]; + $routes = $routes->populateCollection(new RouteCollection()); + } else { + $compiledRoutes = [false]; + $routes = $this->getRoutes(); + } + + [$staticRoutes, $dynamicRoutes] = $this->groupStaticRoutes($routes); + + $conditions = [null]; + $compiledRoutes[] = $this->compileStaticRoutes($staticRoutes, $conditions); + $chunkLimit = \count($dynamicRoutes); + + while (true) { + try { + $this->signalingException = new \RuntimeException('Compilation failed: regular expression is too large'); + $compiledRoutes = array_merge($compiledRoutes, $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit, $conditions)); + + break; + } catch (\Exception $e) { + if (1 < $chunkLimit && $this->signalingException === $e) { + $chunkLimit = 1 + ($chunkLimit >> 1); + continue; + } + throw $e; + } + } + + if ($forDump) { + $compiledRoutes[2] = $compiledRoutes[4]; + } + unset($conditions[0]); + + if ($conditions) { + foreach ($conditions as $expression => $condition) { + $conditions[$expression] = "case {$condition}: return {$expression};"; + } + + $checkConditionCode = <<indent(implode("\n", $conditions), 3)} + } + } +EOF; + $compiledRoutes[4] = $forDump ? $checkConditionCode.",\n" : eval('return '.$checkConditionCode.';'); + } else { + $compiledRoutes[4] = $forDump ? " null, // \$checkCondition\n" : null; + } + + return $compiledRoutes; + } + + private function generateCompiledRoutes(): string + { + [$matchHost, $staticRoutes, $regexpCode, $dynamicRoutes, $checkConditionCode] = $this->getCompiledRoutes(true); + + $code = self::export($matchHost).', // $matchHost'."\n"; + + $code .= '[ // $staticRoutes'."\n"; + foreach ($staticRoutes as $path => $routes) { + $code .= sprintf(" %s => [\n", self::export($path)); + foreach ($routes as $route) { + $code .= vsprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", array_map([__CLASS__, 'export'], $route)); + } + $code .= " ],\n"; + } + $code .= "],\n"; + + $code .= sprintf("[ // \$regexpList%s\n],\n", $regexpCode); + + $code .= '[ // $dynamicRoutes'."\n"; + foreach ($dynamicRoutes as $path => $routes) { + $code .= sprintf(" %s => [\n", self::export($path)); + foreach ($routes as $route) { + $code .= vsprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", array_map([__CLASS__, 'export'], $route)); + } + $code .= " ],\n"; + } + $code .= "],\n"; + $code = preg_replace('/ => \[\n (\[.+?),\n \],/', ' => [$1],', $code); + + return $this->indent($code, 1).$checkConditionCode; + } + + /** + * Splits static routes from dynamic routes, so that they can be matched first, using a simple switch. + */ + private function groupStaticRoutes(RouteCollection $collection): array + { + $staticRoutes = $dynamicRegex = []; + $dynamicRoutes = new RouteCollection(); + + foreach ($collection->all() as $name => $route) { + $compiledRoute = $route->compile(); + $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/'); + $hostRegex = $compiledRoute->getHostRegex(); + $regex = $compiledRoute->getRegex(); + if ($hasTrailingSlash = '/' !== $route->getPath()) { + $pos = strrpos($regex, '$'); + $hasTrailingSlash = '/' === $regex[$pos - 1]; + $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); + } + + if (!$compiledRoute->getPathVariables()) { + $host = !$compiledRoute->getHostVariables() ? $route->getHost() : ''; + $url = $route->getPath(); + if ($hasTrailingSlash) { + $url = substr($url, 0, -1); + } + foreach ($dynamicRegex as [$hostRx, $rx, $prefix]) { + if (('' === $prefix || str_starts_with($url, $prefix)) && (preg_match($rx, $url) || preg_match($rx, $url.'/')) && (!$host || !$hostRx || preg_match($hostRx, $host))) { + $dynamicRegex[] = [$hostRegex, $regex, $staticPrefix]; + $dynamicRoutes->add($name, $route); + continue 2; + } + } + + $staticRoutes[$url][$name] = [$route, $hasTrailingSlash]; + } else { + $dynamicRegex[] = [$hostRegex, $regex, $staticPrefix]; + $dynamicRoutes->add($name, $route); + } + } + + return [$staticRoutes, $dynamicRoutes]; + } + + /** + * Compiles static routes in a switch statement. + * + * Condition-less paths are put in a static array in the switch's default, with generic matching logic. + * Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases. + * + * @throws \LogicException + */ + private function compileStaticRoutes(array $staticRoutes, array &$conditions): array + { + if (!$staticRoutes) { + return []; + } + $compiledRoutes = []; + + foreach ($staticRoutes as $url => $routes) { + $compiledRoutes[$url] = []; + foreach ($routes as $name => [$route, $hasTrailingSlash]) { + $compiledRoutes[$url][] = $this->compileRoute($route, $name, (!$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex()) ?: null, $hasTrailingSlash, false, $conditions); + } + } + + return $compiledRoutes; + } + + /** + * Compiles a regular expression followed by a switch statement to match dynamic routes. + * + * The regular expression matches both the host and the pathinfo at the same time. For stellar performance, + * it is built as a tree of patterns, with re-ordering logic to group same-prefix routes together when possible. + * + * Patterns are named so that we know which one matched (https://pcre.org/current/doc/html/pcre2syntax.html#SEC23). + * This name is used to "switch" to the additional logic required to match the final route. + * + * Condition-less paths are put in a static array in the switch's default, with generic matching logic. + * Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases. + * + * Last but not least: + * - Because it is not possible to mix unicode/non-unicode patterns in a single regexp, several of them can be generated. + * - The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the + * matching-but-failing subpattern is excluded by replacing its name by "(*F)", which forces a failure-to-match. + * To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur. + */ + private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit, array &$conditions): array + { + if (!$collection->all()) { + return [[], [], '']; + } + $regexpList = []; + $code = ''; + $state = (object) [ + 'regexMark' => 0, + 'regex' => [], + 'routes' => [], + 'mark' => 0, + 'markTail' => 0, + 'hostVars' => [], + 'vars' => [], + ]; + $state->getVars = static function ($m) use ($state) { + if ('_route' === $m[1]) { + return '?:'; + } + + $state->vars[] = $m[1]; + + return ''; + }; + + $chunkSize = 0; + $prev = null; + $perModifiers = []; + foreach ($collection->all() as $name => $route) { + preg_match('#[a-zA-Z]*$#', $route->compile()->getRegex(), $rx); + if ($chunkLimit < ++$chunkSize || $prev !== $rx[0] && $route->compile()->getPathVariables()) { + $chunkSize = 1; + $routes = new RouteCollection(); + $perModifiers[] = [$rx[0], $routes]; + $prev = $rx[0]; + } + $routes->add($name, $route); + } + + foreach ($perModifiers as [$modifiers, $routes]) { + $prev = false; + $perHost = []; + foreach ($routes->all() as $name => $route) { + $regex = $route->compile()->getHostRegex(); + if ($prev !== $regex) { + $routes = new RouteCollection(); + $perHost[] = [$regex, $routes]; + $prev = $regex; + } + $routes->add($name, $route); + } + $prev = false; + $rx = '{^(?'; + $code .= "\n {$state->mark} => ".self::export($rx); + $startingMark = $state->mark; + $state->mark += \strlen($rx); + $state->regex = $rx; + + foreach ($perHost as [$hostRegex, $routes]) { + if ($matchHost) { + if ($hostRegex) { + preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $hostRegex, $rx); + $state->vars = []; + $hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')\.'; + $state->hostVars = $state->vars; + } else { + $hostRegex = '(?:(?:[^./]*+\.)++)'; + $state->hostVars = []; + } + $state->mark += \strlen($rx = ($prev ? ')' : '')."|{$hostRegex}(?"); + $code .= "\n .".self::export($rx); + $state->regex .= $rx; + $prev = true; + } + + $tree = new StaticPrefixCollection(); + foreach ($routes->all() as $name => $route) { + preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $route->compile()->getRegex(), $rx); + + $state->vars = []; + $regex = preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]); + if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) { + $regex = substr($regex, 0, -1); + } + $hasTrailingVar = (bool) preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath()); + + $tree->addRoute($regex, [$name, $regex, $state->vars, $route, $hasTrailingSlash, $hasTrailingVar]); + } + + $code .= $this->compileStaticPrefixCollection($tree, $state, 0, $conditions); + } + if ($matchHost) { + $code .= "\n .')'"; + $state->regex .= ')'; + } + $rx = ")/?$}{$modifiers}"; + $code .= "\n .'{$rx}',"; + $state->regex .= $rx; + $state->markTail = 0; + + // if the regex is too large, throw a signaling exception to recompute with smaller chunk size + set_error_handler(fn ($type, $message) => throw str_contains($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message)); + try { + preg_match($state->regex, ''); + } finally { + restore_error_handler(); + } + + $regexpList[$startingMark] = $state->regex; + } + + $state->routes[$state->mark][] = [null, null, null, null, false, false, 0]; + unset($state->getVars); + + return [$regexpList, $state->routes, $code]; + } + + /** + * Compiles a regexp tree of subpatterns that matches nested same-prefix routes. + * + * @param \stdClass $state A simple state object that keeps track of the progress of the compilation, + * and gathers the generated switch's "case" and "default" statements + */ + private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \stdClass $state, int $prefixLen, array &$conditions): string + { + $code = ''; + $prevRegex = null; + $routes = $tree->getRoutes(); + + foreach ($routes as $i => $route) { + if ($route instanceof StaticPrefixCollection) { + $prevRegex = null; + $prefix = substr($route->getPrefix(), $prefixLen); + $state->mark += \strlen($rx = "|{$prefix}(?"); + $code .= "\n .".self::export($rx); + $state->regex .= $rx; + $code .= $this->indent($this->compileStaticPrefixCollection($route, $state, $prefixLen + \strlen($prefix), $conditions)); + $code .= "\n .')'"; + $state->regex .= ')'; + ++$state->markTail; + continue; + } + + [$name, $regex, $vars, $route, $hasTrailingSlash, $hasTrailingVar] = $route; + $compiledRoute = $route->compile(); + $vars = array_merge($state->hostVars, $vars); + + if ($compiledRoute->getRegex() === $prevRegex) { + $state->routes[$state->mark][] = $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions); + continue; + } + + $state->mark += 3 + $state->markTail + \strlen($regex) - $prefixLen; + $state->markTail = 2 + \strlen($state->mark); + $rx = sprintf('|%s(*:%s)', substr($regex, $prefixLen), $state->mark); + $code .= "\n .".self::export($rx); + $state->regex .= $rx; + + $prevRegex = $compiledRoute->getRegex(); + $state->routes[$state->mark] = [$this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions)]; + } + + return $code; + } + + /** + * Compiles a single Route to PHP code used to match it against the path info. + */ + private function compileRoute(Route $route, string $name, string|array|null $vars, bool $hasTrailingSlash, bool $hasTrailingVar, array &$conditions): array + { + $defaults = $route->getDefaults(); + + if (isset($defaults['_canonical_route'])) { + $name = $defaults['_canonical_route']; + unset($defaults['_canonical_route']); + } + + if ($condition = $route->getCondition()) { + $condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request', 'params']); + $condition = $conditions[$condition] ??= (str_contains($condition, '$request') ? 1 : -1) * \count($conditions); + } else { + $condition = null; + } + + return [ + ['_route' => $name] + $defaults, + $vars, + array_flip($route->getMethods()) ?: null, + array_flip($route->getSchemes()) ?: null, + $hasTrailingSlash, + $hasTrailingVar, + $condition, + ]; + } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (!isset($this->expressionLanguage)) { + if (!class_exists(ExpressionLanguage::class)) { + throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } + + private function indent(string $code, int $level = 1): string + { + return preg_replace('/^./m', str_repeat(' ', $level).'$0', $code); + } + + /** + * @internal + */ + public static function export(mixed $value): string + { + if (null === $value) { + return 'null'; + } + if (!\is_array($value)) { + if (\is_object($value)) { + throw new \InvalidArgumentException('Symfony\Component\Routing\Route cannot contain objects.'); + } + + return str_replace("\n", '\'."\n".\'', var_export($value, true)); + } + if (!$value) { + return '[]'; + } + + $i = 0; + $export = '['; + + foreach ($value as $k => $v) { + if ($i === $k) { + ++$i; + } else { + $export .= self::export($k).' => '; + + if (\is_int($k) && $i < $k) { + $i = 1 + $k; + } + } + + $export .= self::export($v).', '; + } + + return substr_replace($export, ']', -2); + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php b/vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php new file mode 100644 index 0000000..50abf45 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; +use Symfony\Component\Routing\RequestContext; + +/** + * @author Nicolas Grekas + * + * @internal + * + * @property RequestContext $context + */ +trait CompiledUrlMatcherTrait +{ + private bool $matchHost = false; + private array $staticRoutes = []; + private array $regexpList = []; + private array $dynamicRoutes = []; + private ?\Closure $checkCondition; + + public function match(string $pathinfo): array + { + $allow = $allowSchemes = []; + if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) { + return $ret; + } + if ($allow) { + throw new MethodNotAllowedException(array_keys($allow)); + } + if (!$this instanceof RedirectableUrlMatcherInterface) { + throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], true)) { + // no-op + } elseif ($allowSchemes) { + redirect_scheme: + $scheme = $this->context->getScheme(); + $this->context->setScheme(key($allowSchemes)); + try { + if ($ret = $this->doMatch($pathinfo)) { + return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret; + } + } finally { + $this->context->setScheme($scheme); + } + } elseif ('/' !== $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') { + $pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo; + if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) { + return $this->redirect($pathinfo, $ret['_route']) + $ret; + } + if ($allowSchemes) { + goto redirect_scheme; + } + } + + throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + + private function doMatch(string $pathinfo, array &$allow = [], array &$allowSchemes = []): array + { + $allow = $allowSchemes = []; + $pathinfo = rawurldecode($pathinfo) ?: '/'; + $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/'; + $context = $this->context; + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ($this->matchHost) { + $host = strtolower($context->getHost()); + } + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + $supportsRedirections = 'GET' === $canonicalMethod && $this instanceof RedirectableUrlMatcherInterface; + + foreach ($this->staticRoutes[$trimmedPathinfo] ?? [] as [$ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, , $condition]) { + if ($requiredHost) { + if ('{' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) { + continue; + } + if ('{' === $requiredHost[0] && $hostMatches) { + $hostMatches['_route'] = $ret['_route']; + $ret = $this->mergeDefaults($hostMatches, $ret); + } + } + + if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null, $ret)) { + continue; + } + + if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) { + return $allow = $allowSchemes = []; + } + continue; + } + + $hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]); + if ($hasRequiredScheme && $requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { + $allow += $requiredMethods; + continue; + } + + if (!$hasRequiredScheme) { + $allowSchemes += $requiredSchemes; + continue; + } + + return $ret; + } + + $matchedPathinfo = $this->matchHost ? $host.'.'.$pathinfo : $pathinfo; + + foreach ($this->regexpList as $offset => $regex) { + while (preg_match($regex, $matchedPathinfo, $matches)) { + foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as [$ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $hasTrailingVar, $condition]) { + if (0 === $condition) { // marks the last route in the regexp + continue 3; + } + + $hasTrailingVar = $trimmedPathinfo !== $pathinfo && $hasTrailingVar; + + if ($hasTrailingVar && ($hasTrailingSlash || (null === $n = $matches[\count($vars)] ?? null) || '/' !== ($n[-1] ?? '/')) && preg_match($regex, $this->matchHost ? $host.'.'.$trimmedPathinfo : $trimmedPathinfo, $n) && $m === (int) $n['MARK']) { + if ($hasTrailingSlash) { + $matches = $n; + } else { + $hasTrailingVar = false; + } + } + + foreach ($vars as $i => $v) { + if (isset($matches[1 + $i])) { + $ret[$v] = $matches[1 + $i]; + } + } + + if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null, $ret)) { + continue; + } + + if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) { + return $allow = $allowSchemes = []; + } + continue; + } + + if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { + $allowSchemes += $requiredSchemes; + continue; + } + + if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { + $allow += $requiredMethods; + continue; + } + + return $ret; + } + + $regex = substr_replace($regex, 'F', $m - $offset, 1 + \strlen($m)); + $offset += \strlen($m); + } + } + + if ('/' === $pathinfo && !$allow && !$allowSchemes) { + throw new NoConfigurationException(); + } + + return []; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php new file mode 100644 index 0000000..b763fd5 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumper is the abstract class for all built-in matcher dumpers. + * + * @author Fabien Potencier + */ +abstract class MatcherDumper implements MatcherDumperInterface +{ + public function __construct( + private RouteCollection $routes, + ) { + } + + public function getRoutes(): RouteCollection + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php new file mode 100644 index 0000000..92cc4db --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumperInterface is the interface that all matcher dumper classes must implement. + * + * @author Fabien Potencier + */ +interface MatcherDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to match a request against these routes. + */ + public function dump(array $options = []): string; + + /** + * Gets the routes to dump. + */ + public function getRoutes(): RouteCollection; +} diff --git a/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php b/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php new file mode 100644 index 0000000..42ca799 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * Prefix tree of routes preserving routes order. + * + * @author Frank de Jonge + * @author Nicolas Grekas + * + * @internal + */ +class StaticPrefixCollection +{ + private string $prefix; + + /** + * @var string[] + */ + private array $staticPrefixes = []; + + /** + * @var string[] + */ + private array $prefixes = []; + + /** + * @var array[]|self[] + */ + private array $items = []; + + public function __construct(string $prefix = '/') + { + $this->prefix = $prefix; + } + + public function getPrefix(): string + { + return $this->prefix; + } + + /** + * @return array[]|self[] + */ + public function getRoutes(): array + { + return $this->items; + } + + /** + * Adds a route to a group. + */ + public function addRoute(string $prefix, array|self $route): void + { + [$prefix, $staticPrefix] = $this->getCommonPrefix($prefix, $prefix); + + for ($i = \count($this->items) - 1; 0 <= $i; --$i) { + $item = $this->items[$i]; + + [$commonPrefix, $commonStaticPrefix] = $this->getCommonPrefix($prefix, $this->prefixes[$i]); + + if ($this->prefix === $commonPrefix) { + // the new route and a previous one have no common prefix, let's see if they are exclusive to each others + + if ($this->prefix !== $staticPrefix && $this->prefix !== $this->staticPrefixes[$i]) { + // the new route and the previous one have exclusive static prefixes + continue; + } + + if ($this->prefix === $staticPrefix && $this->prefix === $this->staticPrefixes[$i]) { + // the new route and the previous one have no static prefix + break; + } + + if ($this->prefixes[$i] !== $this->staticPrefixes[$i] && $this->prefix === $this->staticPrefixes[$i]) { + // the previous route is non-static and has no static prefix + break; + } + + if ($prefix !== $staticPrefix && $this->prefix === $staticPrefix) { + // the new route is non-static and has no static prefix + break; + } + + continue; + } + + if ($item instanceof self && $this->prefixes[$i] === $commonPrefix) { + // the new route is a child of a previous one, let's nest it + $item->addRoute($prefix, $route); + } else { + // the new route and a previous one have a common prefix, let's merge them + $child = new self($commonPrefix); + [$child->prefixes[0], $child->staticPrefixes[0]] = $child->getCommonPrefix($this->prefixes[$i], $this->prefixes[$i]); + [$child->prefixes[1], $child->staticPrefixes[1]] = $child->getCommonPrefix($prefix, $prefix); + $child->items = [$this->items[$i], $route]; + + $this->staticPrefixes[$i] = $commonStaticPrefix; + $this->prefixes[$i] = $commonPrefix; + $this->items[$i] = $child; + } + + return; + } + + // No optimised case was found, in this case we simple add the route for possible + // grouping when new routes are added. + $this->staticPrefixes[] = $staticPrefix; + $this->prefixes[] = $prefix; + $this->items[] = $route; + } + + /** + * Linearizes back a set of nested routes into a collection. + */ + public function populateCollection(RouteCollection $routes): RouteCollection + { + foreach ($this->items as $route) { + if ($route instanceof self) { + $route->populateCollection($routes); + } else { + $routes->add(...$route); + } + } + + return $routes; + } + + /** + * Gets the full and static common prefixes between two route patterns. + * + * The static prefix stops at last at the first opening bracket. + */ + private function getCommonPrefix(string $prefix, string $anotherPrefix): array + { + $baseLength = \strlen($this->prefix); + $end = min(\strlen($prefix), \strlen($anotherPrefix)); + $staticLength = null; + set_error_handler(self::handleError(...)); + + try { + for ($i = $baseLength; $i < $end && $prefix[$i] === $anotherPrefix[$i]; ++$i) { + if ('(' === $prefix[$i]) { + $staticLength ??= $i; + for ($j = 1 + $i, $n = 1; $j < $end && 0 < $n; ++$j) { + if ($prefix[$j] !== $anotherPrefix[$j]) { + break 2; + } + if ('(' === $prefix[$j]) { + ++$n; + } elseif (')' === $prefix[$j]) { + --$n; + } elseif ('\\' === $prefix[$j] && (++$j === $end || $prefix[$j] !== $anotherPrefix[$j])) { + --$j; + break; + } + } + if (0 < $n) { + break; + } + if (('?' === ($prefix[$j] ?? '') || '?' === ($anotherPrefix[$j] ?? '')) && ($prefix[$j] ?? '') !== ($anotherPrefix[$j] ?? '')) { + break; + } + $subPattern = substr($prefix, $i, $j - $i); + if ($prefix !== $anotherPrefix && !preg_match('/^\(\[[^\]]++\]\+\+\)$/', $subPattern) && !preg_match('{(?> 6) && preg_match('//u', $prefix.' '.$anotherPrefix)) { + do { + // Prevent cutting in the middle of an UTF-8 characters + --$i; + } while (0b10 === (\ord($prefix[$i]) >> 6)); + } + + return [substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i)]; + } + + public static function handleError(int $type, string $msg): bool + { + return str_contains($msg, 'Compilation failed: lookbehind assertion is not fixed length') + || str_contains($msg, 'Compilation failed: length of lookbehind assertion is not limited'); + } +} diff --git a/vendor/symfony/routing/Matcher/ExpressionLanguageProvider.php b/vendor/symfony/routing/Matcher/ExpressionLanguageProvider.php new file mode 100644 index 0000000..e9cbd3a --- /dev/null +++ b/vendor/symfony/routing/Matcher/ExpressionLanguageProvider.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\ExpressionLanguage\ExpressionFunction; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * Exposes functions defined in the request context to route conditions. + * + * @author Ahmed TAILOULOUTE + */ +class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface +{ + public function __construct( + private ServiceProviderInterface $functions, + ) { + } + + public function getFunctions(): array + { + $functions = []; + + foreach ($this->functions->getProvidedServices() as $function => $type) { + $functions[] = new ExpressionFunction( + $function, + static fn (...$args) => sprintf('($context->getParameter(\'_functions\')->get(%s)(%s))', var_export($function, true), implode(', ', $args)), + fn ($values, ...$args) => $values['context']->getParameter('_functions')->get($function)(...$args) + ); + } + + return $functions; + } + + public function get(string $function): callable + { + return $this->functions->get($function); + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php new file mode 100644 index 0000000..8d1ad4f --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\ExceptionInterface; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; + +/** + * @author Fabien Potencier + */ +abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function match(string $pathinfo): array + { + try { + return parent::match($pathinfo); + } catch (ResourceNotFoundException $e) { + if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], true)) { + throw $e; + } + + if ($this->allowSchemes) { + redirect_scheme: + $scheme = $this->context->getScheme(); + $this->context->setScheme(current($this->allowSchemes)); + try { + $ret = parent::match($pathinfo); + + return $this->redirect($pathinfo, $ret['_route'] ?? null, $this->context->getScheme()) + $ret; + } catch (ExceptionInterface) { + throw $e; + } finally { + $this->context->setScheme($scheme); + } + } elseif ('/' === $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') { + throw $e; + } else { + try { + $pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo; + $ret = parent::match($pathinfo); + + return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret; + } catch (ExceptionInterface) { + if ($this->allowSchemes) { + goto redirect_scheme; + } + throw $e; + } + } + } + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php new file mode 100644 index 0000000..e4bcedd --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +/** + * RedirectableUrlMatcherInterface knows how to redirect the user. + * + * @author Fabien Potencier + */ +interface RedirectableUrlMatcherInterface +{ + /** + * Redirects the user to another URL and returns the parameters for the redirection. + * + * @param string $path The path info to redirect to + * @param string $route The route name that matched + * @param string|null $scheme The URL scheme (null to keep the current one) + */ + public function redirect(string $path, string $route, ?string $scheme = null): array; +} diff --git a/vendor/symfony/routing/Matcher/RequestMatcherInterface.php b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php new file mode 100644 index 0000000..febba95 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; + +/** + * RequestMatcherInterface is the interface that all request matcher classes must implement. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Tries to match a request with a set of routes. + * + * If the matcher cannot find information, it must throw one of the exceptions documented + * below. + * + * @throws NoConfigurationException If no routing configuration could be found + * @throws ResourceNotFoundException If no matching resource could be found + * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed + */ + public function matchRequest(Request $request): array; +} diff --git a/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php new file mode 100644 index 0000000..b7aa2b6 --- /dev/null +++ b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ExceptionInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * TraceableUrlMatcher helps debug path info matching by tracing the match. + * + * @author Fabien Potencier + */ +class TraceableUrlMatcher extends UrlMatcher +{ + public const ROUTE_DOES_NOT_MATCH = 0; + public const ROUTE_ALMOST_MATCHES = 1; + public const ROUTE_MATCHES = 2; + + protected array $traces; + + public function getTraces(string $pathinfo): array + { + $this->traces = []; + + try { + $this->match($pathinfo); + } catch (ExceptionInterface) { + } + + return $this->traces; + } + + public function getTracesForRequest(Request $request): array + { + $this->request = $request; + $traces = $this->getTraces($request->getPathInfo()); + $this->request = null; + + return $traces; + } + + protected function matchCollection(string $pathinfo, RouteCollection $routes): array + { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + $supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface; + $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/'; + + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/'); + $requiredMethods = $route->getMethods(); + + // check the static prefix of the URL first. Only use the more expensive preg_match when it matches + if ('' !== $staticPrefix && !str_starts_with($trimmedPathinfo, $staticPrefix)) { + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + continue; + } + $regex = $compiledRoute->getRegex(); + + $pos = strrpos($regex, '$'); + $hasTrailingSlash = '/' === $regex[$pos - 1]; + $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); + + if (!preg_match($regex, $pathinfo, $matches)) { + // does it match without any requirements? + $r = new Route($route->getPath(), $route->getDefaults(), [], $route->getOptions()); + $cr = $r->compile(); + if (!preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + + continue; + } + + foreach ($route->getRequirements() as $n => $regex) { + $r = new Route($route->getPath(), $route->getDefaults(), [$n => $regex], $route->getOptions()); + $cr = $r->compile(); + + if (\in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue 2; + } + } + + continue; + } + + $hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath()); + + if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) { + if ($hasTrailingSlash) { + $matches = $m; + } else { + $hasTrailingVar = false; + } + } + + $hostMatches = []; + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); + continue; + } + + $attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); + + $status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes); + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $route->getCondition()), self::ROUTE_ALMOST_MATCHES, $name, $route); + continue; + } + + if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods, true))) { + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + + return $this->allow = $this->allowSchemes = []; + } + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + continue; + } + + if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) { + $this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes()); + $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s)', $this->context->getScheme(), implode(', ', $route->getSchemes())), self::ROUTE_ALMOST_MATCHES, $name, $route); + continue; + } + + if ($requiredMethods && !\in_array($method, $requiredMethods, true)) { + $this->allow = array_merge($this->allow, $requiredMethods); + $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); + continue; + } + + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + + return array_replace($attributes, $status[1] ?? []); + } + + return []; + } + + private function addTrace(string $log, int $level = self::ROUTE_DOES_NOT_MATCH, ?string $name = null, ?Route $route = null): void + { + $this->traces[] = [ + 'log' => $log, + 'name' => $name, + 'level' => $level, + 'path' => $route?->getPath(), + ]; + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcher.php b/vendor/symfony/routing/Matcher/UrlMatcher.php new file mode 100644 index 0000000..09c1d29 --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcher.php @@ -0,0 +1,266 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * UrlMatcher matches URL based on a set of routes. + * + * @author Fabien Potencier + */ +class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface +{ + public const REQUIREMENT_MATCH = 0; + public const REQUIREMENT_MISMATCH = 1; + public const ROUTE_MATCH = 2; + + /** + * Collects HTTP methods that would be allowed for the request. + */ + protected array $allow = []; + + /** + * Collects URI schemes that would be allowed for the request. + * + * @internal + */ + protected array $allowSchemes = []; + protected ?Request $request = null; + protected ExpressionLanguage $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + protected array $expressionLanguageProviders = []; + + public function __construct( + protected RouteCollection $routes, + protected RequestContext $context, + ) { + } + + public function setContext(RequestContext $context): void + { + $this->context = $context; + } + + public function getContext(): RequestContext + { + return $this->context; + } + + public function match(string $pathinfo): array + { + $this->allow = $this->allowSchemes = []; + + if ($ret = $this->matchCollection(rawurldecode($pathinfo) ?: '/', $this->routes)) { + return $ret; + } + + if ('/' === $pathinfo && !$this->allow && !$this->allowSchemes) { + throw new NoConfigurationException(); + } + + throw 0 < \count($this->allow) ? new MethodNotAllowedException(array_unique($this->allow)) : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + + public function matchRequest(Request $request): array + { + $this->request = $request; + + $ret = $this->match($request->getPathInfo()); + + $this->request = null; + + return $ret; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider): void + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Tries to match a URL with a set of routes. + * + * @param string $pathinfo The path info to be parsed + * + * @throws NoConfigurationException If no routing configuration could be found + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + protected function matchCollection(string $pathinfo, RouteCollection $routes): array + { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + $supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface; + $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/'; + + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/'); + $requiredMethods = $route->getMethods(); + + // check the static prefix of the URL first. Only use the more expensive preg_match when it matches + if ('' !== $staticPrefix && !str_starts_with($trimmedPathinfo, $staticPrefix)) { + continue; + } + $regex = $compiledRoute->getRegex(); + + $pos = strrpos($regex, '$'); + $hasTrailingSlash = '/' === $regex[$pos - 1]; + $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); + + if (!preg_match($regex, $pathinfo, $matches)) { + continue; + } + + $hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath()); + + if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) { + if ($hasTrailingSlash) { + $matches = $m; + } else { + $hasTrailingVar = false; + } + } + + $hostMatches = []; + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + continue; + } + + $attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); + + $status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes); + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + continue; + } + + if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods, true))) { + return $this->allow = $this->allowSchemes = []; + } + continue; + } + + if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) { + $this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes()); + continue; + } + + if ($requiredMethods && !\in_array($method, $requiredMethods, true)) { + $this->allow = array_merge($this->allow, $requiredMethods); + continue; + } + + return array_replace($attributes, $status[1] ?? []); + } + + return []; + } + + /** + * Returns an array of values to use as request attributes. + * + * As this method requires the Route object, it is not available + * in matchers that do not have access to the matched Route instance + * (like the PHP and Apache matcher dumpers). + */ + protected function getAttributes(Route $route, string $name, array $attributes): array + { + $defaults = $route->getDefaults(); + if (isset($defaults['_canonical_route'])) { + $name = $defaults['_canonical_route']; + unset($defaults['_canonical_route']); + } + $attributes['_route'] = $name; + + if ($mapping = $route->getOption('mapping')) { + $attributes['_route_mapping'] = $mapping; + } + + return $this->mergeDefaults($attributes, $defaults); + } + + /** + * Handles specific route requirements. + * + * @return array The first element represents the status, the second contains additional information + */ + protected function handleRouteRequirements(string $pathinfo, string $name, Route $route, array $routeParameters): array + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), [ + 'context' => $this->context, + 'request' => $this->request ?: $this->createRequest($pathinfo), + 'params' => $routeParameters, + ])) { + return [self::REQUIREMENT_MISMATCH, null]; + } + + return [self::REQUIREMENT_MATCH, null]; + } + + /** + * Get merged default parameters. + */ + protected function mergeDefaults(array $params, array $defaults): array + { + foreach ($params as $key => $value) { + if (!\is_int($key) && null !== $value) { + $defaults[$key] = $value; + } + } + + return $defaults; + } + + protected function getExpressionLanguage(): ExpressionLanguage + { + if (!isset($this->expressionLanguage)) { + if (!class_exists(ExpressionLanguage::class)) { + throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } + + /** + * @internal + */ + protected function createRequest(string $pathinfo): ?Request + { + if (!class_exists(Request::class)) { + return null; + } + + return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), [], [], [ + 'SCRIPT_FILENAME' => $this->context->getBaseUrl(), + 'SCRIPT_NAME' => $this->context->getBaseUrl(), + ]); + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcherInterface.php b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php new file mode 100644 index 0000000..68a3737 --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlMatcherInterface is the interface that all URL matcher classes must implement. + * + * @author Fabien Potencier + */ +interface UrlMatcherInterface extends RequestContextAwareInterface +{ + /** + * Tries to match a URL path with a set of routes. + * + * If the matcher cannot find information, it must throw one of the exceptions documented + * below. + * + * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) + * + * @throws NoConfigurationException If no routing configuration could be found + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + public function match(string $pathinfo): array; +} diff --git a/vendor/symfony/routing/README.md b/vendor/symfony/routing/README.md new file mode 100644 index 0000000..7558036 --- /dev/null +++ b/vendor/symfony/routing/README.md @@ -0,0 +1,66 @@ +Routing Component +================= + +The Routing component maps an HTTP request to a set of configuration variables. + +Getting Started +--------------- + +```bash +composer require symfony/routing +``` + +```php +use App\Controller\BlogController; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +$route = new Route('/blog/{slug}', ['_controller' => BlogController::class]); +$routes = new RouteCollection(); +$routes->add('blog_show', $route); + +$context = new RequestContext(); + +// Routing can match routes with incoming requests +$matcher = new UrlMatcher($routes, $context); +$parameters = $matcher->match('/blog/lorem-ipsum'); +// $parameters = [ +// '_controller' => 'App\Controller\BlogController', +// 'slug' => 'lorem-ipsum', +// '_route' => 'blog_show' +// ] + +// Routing can also generate URLs for a given route +$generator = new UrlGenerator($routes, $context); +$url = $generator->generate('blog_show', [ + 'slug' => 'my-blog-post', +]); +// $url = '/blog/my-blog-post' +``` + +Sponsor +------- + +The Routing component for Symfony 7.1 is [backed][1] by [redirection.io][2]. + +redirection.io logs all your website’s HTTP traffic, and lets you fix errors +with redirect rules in seconds. Give your marketing, SEO and IT teams the +right tool to manage your website traffic efficiently! + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/routing.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://redirection.io +[3]: https://symfony.com/sponsor diff --git a/vendor/symfony/routing/RequestContext.php b/vendor/symfony/routing/RequestContext.php new file mode 100644 index 0000000..e3f4831 --- /dev/null +++ b/vendor/symfony/routing/RequestContext.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Holds information about the current request. + * + * This class implements a fluent interface. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RequestContext +{ + private string $baseUrl; + private string $pathInfo; + private string $method; + private string $host; + private string $scheme; + private int $httpPort; + private int $httpsPort; + private string $queryString; + private array $parameters = []; + + public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '') + { + $this->setBaseUrl($baseUrl); + $this->setMethod($method); + $this->setHost($host); + $this->setScheme($scheme); + $this->setHttpPort($httpPort); + $this->setHttpsPort($httpsPort); + $this->setPathInfo($path); + $this->setQueryString($queryString); + } + + public static function fromUri(string $uri, string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443): self + { + $uri = parse_url($uri); + $scheme = $uri['scheme'] ?? $scheme; + $host = $uri['host'] ?? $host; + + if (isset($uri['port'])) { + if ('http' === $scheme) { + $httpPort = $uri['port']; + } elseif ('https' === $scheme) { + $httpsPort = $uri['port']; + } + } + + return new self($uri['path'] ?? '', 'GET', $host, $scheme, $httpPort, $httpsPort); + } + + /** + * Updates the RequestContext information based on a HttpFoundation Request. + * + * @return $this + */ + public function fromRequest(Request $request): static + { + $this->setBaseUrl($request->getBaseUrl()); + $this->setPathInfo($request->getPathInfo()); + $this->setMethod($request->getMethod()); + $this->setHost($request->getHost()); + $this->setScheme($request->getScheme()); + $this->setHttpPort($request->isSecure() || null === $request->getPort() ? $this->httpPort : $request->getPort()); + $this->setHttpsPort($request->isSecure() && null !== $request->getPort() ? $request->getPort() : $this->httpsPort); + $this->setQueryString($request->server->get('QUERY_STRING', '')); + + return $this; + } + + /** + * Gets the base URL. + */ + public function getBaseUrl(): string + { + return $this->baseUrl; + } + + /** + * Sets the base URL. + * + * @return $this + */ + public function setBaseUrl(string $baseUrl): static + { + $this->baseUrl = rtrim($baseUrl, '/'); + + return $this; + } + + /** + * Gets the path info. + */ + public function getPathInfo(): string + { + return $this->pathInfo; + } + + /** + * Sets the path info. + * + * @return $this + */ + public function setPathInfo(string $pathInfo): static + { + $this->pathInfo = $pathInfo; + + return $this; + } + + /** + * Gets the HTTP method. + * + * The method is always an uppercased string. + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * Sets the HTTP method. + * + * @return $this + */ + public function setMethod(string $method): static + { + $this->method = strtoupper($method); + + return $this; + } + + /** + * Gets the HTTP host. + * + * The host is always lowercased because it must be treated case-insensitive. + */ + public function getHost(): string + { + return $this->host; + } + + /** + * Sets the HTTP host. + * + * @return $this + */ + public function setHost(string $host): static + { + $this->host = strtolower($host); + + return $this; + } + + /** + * Gets the HTTP scheme. + */ + public function getScheme(): string + { + return $this->scheme; + } + + /** + * Sets the HTTP scheme. + * + * @return $this + */ + public function setScheme(string $scheme): static + { + $this->scheme = strtolower($scheme); + + return $this; + } + + /** + * Gets the HTTP port. + */ + public function getHttpPort(): int + { + return $this->httpPort; + } + + /** + * Sets the HTTP port. + * + * @return $this + */ + public function setHttpPort(int $httpPort): static + { + $this->httpPort = $httpPort; + + return $this; + } + + /** + * Gets the HTTPS port. + */ + public function getHttpsPort(): int + { + return $this->httpsPort; + } + + /** + * Sets the HTTPS port. + * + * @return $this + */ + public function setHttpsPort(int $httpsPort): static + { + $this->httpsPort = $httpsPort; + + return $this; + } + + /** + * Gets the query string without the "?". + */ + public function getQueryString(): string + { + return $this->queryString; + } + + /** + * Sets the query string. + * + * @return $this + */ + public function setQueryString(?string $queryString): static + { + // string cast to be fault-tolerant, accepting null + $this->queryString = (string) $queryString; + + return $this; + } + + /** + * Returns the parameters. + */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * Sets the parameters. + * + * @param array $parameters The parameters + * + * @return $this + */ + public function setParameters(array $parameters): static + { + $this->parameters = $parameters; + + return $this; + } + + /** + * Gets a parameter value. + */ + public function getParameter(string $name): mixed + { + return $this->parameters[$name] ?? null; + } + + /** + * Checks if a parameter value is set for the given parameter. + */ + public function hasParameter(string $name): bool + { + return \array_key_exists($name, $this->parameters); + } + + /** + * Sets a parameter value. + * + * @return $this + */ + public function setParameter(string $name, mixed $parameter): static + { + $this->parameters[$name] = $parameter; + + return $this; + } + + public function isSecure(): bool + { + return 'https' === $this->scheme; + } +} diff --git a/vendor/symfony/routing/RequestContextAwareInterface.php b/vendor/symfony/routing/RequestContextAwareInterface.php new file mode 100644 index 0000000..cbe453a --- /dev/null +++ b/vendor/symfony/routing/RequestContextAwareInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +interface RequestContextAwareInterface +{ + /** + * Sets the request context. + */ + public function setContext(RequestContext $context): void; + + /** + * Gets the request context. + */ + public function getContext(): RequestContext; +} diff --git a/vendor/symfony/routing/Requirement/EnumRequirement.php b/vendor/symfony/routing/Requirement/EnumRequirement.php new file mode 100644 index 0000000..3ab2ed3 --- /dev/null +++ b/vendor/symfony/routing/Requirement/EnumRequirement.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Requirement; + +use Symfony\Component\Routing\Exception\InvalidArgumentException; + +final class EnumRequirement implements \Stringable +{ + private string $requirement; + + /** + * @template T of \BackedEnum + * + * @param class-string|list $cases + */ + public function __construct(string|array $cases = []) + { + if (\is_string($cases)) { + if (!is_subclass_of($cases, \BackedEnum::class, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a "BackedEnum" class.', $cases)); + } + + $cases = $cases::cases(); + } else { + $class = null; + + foreach ($cases as $case) { + if (!$case instanceof \BackedEnum) { + throw new InvalidArgumentException(sprintf('Case must be a "BackedEnum" instance, "%s" given.', get_debug_type($case))); + } + + $class ??= $case::class; + + if (!$case instanceof $class) { + throw new InvalidArgumentException(sprintf('"%s::%s" is not a case of "%s".', get_debug_type($case), $case->name, $class)); + } + } + } + + $this->requirement = implode('|', array_map(static fn ($e) => preg_quote($e->value), $cases)); + } + + public function __toString(): string + { + return $this->requirement; + } +} diff --git a/vendor/symfony/routing/Requirement/Requirement.php b/vendor/symfony/routing/Requirement/Requirement.php new file mode 100644 index 0000000..54ad86b --- /dev/null +++ b/vendor/symfony/routing/Requirement/Requirement.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Requirement; + +/* + * A collection of universal regular-expression constants to use as route parameter requirements. + */ +enum Requirement +{ + public const ASCII_SLUG = '[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*'; // symfony/string AsciiSlugger default implementation + public const CATCH_ALL = '.+'; + public const DATE_YMD = '[0-9]{4}-(?:0[1-9]|1[012])-(?:0[1-9]|[12][0-9]|(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * A Route describes a route and its parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class Route implements \Serializable +{ + private string $path = '/'; + private string $host = ''; + private array $schemes = []; + private array $methods = []; + private array $defaults = []; + private array $requirements = []; + private array $options = []; + private string $condition = ''; + private ?CompiledRoute $compiled = null; + + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * * utf8: Whether UTF-8 matching is enforced ot not + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string|null $host The host pattern to match + * @param string|string[] $schemes A required URI scheme or an array of restricted schemes + * @param string|string[] $methods A required HTTP method or an array of restricted methods + * @param string|null $condition A condition that should evaluate to true for the route to match + */ + public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host = '', string|array $schemes = [], string|array $methods = [], ?string $condition = '') + { + $this->setPath($path); + $this->addDefaults($defaults); + $this->addRequirements($requirements); + $this->setOptions($options); + $this->setHost($host); + $this->setSchemes($schemes); + $this->setMethods($methods); + $this->setCondition($condition); + } + + public function __serialize(): array + { + return [ + 'path' => $this->path, + 'host' => $this->host, + 'defaults' => $this->defaults, + 'requirements' => $this->requirements, + 'options' => $this->options, + 'schemes' => $this->schemes, + 'methods' => $this->methods, + 'condition' => $this->condition, + 'compiled' => $this->compiled, + ]; + } + + /** + * @internal + */ + final public function serialize(): string + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __unserialize(array $data): void + { + $this->path = $data['path']; + $this->host = $data['host']; + $this->defaults = $data['defaults']; + $this->requirements = $data['requirements']; + $this->options = $data['options']; + $this->schemes = $data['schemes']; + $this->methods = $data['methods']; + + if (isset($data['condition'])) { + $this->condition = $data['condition']; + } + if (isset($data['compiled'])) { + $this->compiled = $data['compiled']; + } + } + + /** + * @internal + */ + final public function unserialize(string $serialized): void + { + $this->__unserialize(unserialize($serialized)); + } + + public function getPath(): string + { + return $this->path; + } + + /** + * @return $this + */ + public function setPath(string $pattern): static + { + $pattern = $this->extractInlineDefaultsAndRequirements($pattern); + + // A pattern must start with a slash and must not have multiple slashes at the beginning because the + // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. + $this->path = '/'.ltrim(trim($pattern), '/'); + $this->compiled = null; + + return $this; + } + + public function getHost(): string + { + return $this->host; + } + + /** + * @return $this + */ + public function setHost(?string $pattern): static + { + $this->host = $this->extractInlineDefaultsAndRequirements((string) $pattern); + $this->compiled = null; + + return $this; + } + + /** + * Returns the lowercased schemes this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @return string[] + */ + public function getSchemes(): array + { + return $this->schemes; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @param string|string[] $schemes The scheme or an array of schemes + * + * @return $this + */ + public function setSchemes(string|array $schemes): static + { + $this->schemes = array_map('strtolower', (array) $schemes); + $this->compiled = null; + + return $this; + } + + /** + * Checks if a scheme requirement has been set. + */ + public function hasScheme(string $scheme): bool + { + return \in_array(strtolower($scheme), $this->schemes, true); + } + + /** + * Returns the uppercased HTTP methods this route is restricted to. + * So an empty array means that any method is allowed. + * + * @return string[] + */ + public function getMethods(): array + { + return $this->methods; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * @param string|string[] $methods The method or an array of methods + * + * @return $this + */ + public function setMethods(string|array $methods): static + { + $this->methods = array_map('strtoupper', (array) $methods); + $this->compiled = null; + + return $this; + } + + public function getOptions(): array + { + return $this->options; + } + + /** + * @return $this + */ + public function setOptions(array $options): static + { + $this->options = [ + 'compiler_class' => RouteCompiler::class, + ]; + + return $this->addOptions($options); + } + + /** + * @return $this + */ + public function addOptions(array $options): static + { + foreach ($options as $name => $option) { + $this->options[$name] = $option; + } + $this->compiled = null; + + return $this; + } + + /** + * Sets an option value. + * + * @return $this + */ + public function setOption(string $name, mixed $value): static + { + $this->options[$name] = $value; + $this->compiled = null; + + return $this; + } + + /** + * Returns the option value or null when not found. + */ + public function getOption(string $name): mixed + { + return $this->options[$name] ?? null; + } + + public function hasOption(string $name): bool + { + return \array_key_exists($name, $this->options); + } + + public function getDefaults(): array + { + return $this->defaults; + } + + /** + * @return $this + */ + public function setDefaults(array $defaults): static + { + $this->defaults = []; + + return $this->addDefaults($defaults); + } + + /** + * @return $this + */ + public function addDefaults(array $defaults): static + { + if (isset($defaults['_locale']) && $this->isLocalized()) { + unset($defaults['_locale']); + } + + foreach ($defaults as $name => $default) { + $this->defaults[$name] = $default; + } + $this->compiled = null; + + return $this; + } + + public function getDefault(string $name): mixed + { + return $this->defaults[$name] ?? null; + } + + public function hasDefault(string $name): bool + { + return \array_key_exists($name, $this->defaults); + } + + /** + * @return $this + */ + public function setDefault(string $name, mixed $default): static + { + if ('_locale' === $name && $this->isLocalized()) { + return $this; + } + + $this->defaults[$name] = $default; + $this->compiled = null; + + return $this; + } + + public function getRequirements(): array + { + return $this->requirements; + } + + /** + * @return $this + */ + public function setRequirements(array $requirements): static + { + $this->requirements = []; + + return $this->addRequirements($requirements); + } + + /** + * @return $this + */ + public function addRequirements(array $requirements): static + { + if (isset($requirements['_locale']) && $this->isLocalized()) { + unset($requirements['_locale']); + } + + foreach ($requirements as $key => $regex) { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + } + $this->compiled = null; + + return $this; + } + + public function getRequirement(string $key): ?string + { + return $this->requirements[$key] ?? null; + } + + public function hasRequirement(string $key): bool + { + return \array_key_exists($key, $this->requirements); + } + + /** + * @return $this + */ + public function setRequirement(string $key, string $regex): static + { + if ('_locale' === $key && $this->isLocalized()) { + return $this; + } + + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + $this->compiled = null; + + return $this; + } + + public function getCondition(): string + { + return $this->condition; + } + + /** + * @return $this + */ + public function setCondition(?string $condition): static + { + $this->condition = (string) $condition; + $this->compiled = null; + + return $this; + } + + /** + * Compiles the route. + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + * + * @see RouteCompiler which is responsible for the compilation process + */ + public function compile(): CompiledRoute + { + if (null !== $this->compiled) { + return $this->compiled; + } + + $class = $this->getOption('compiler_class'); + + return $this->compiled = $class::compile($this); + } + + private function extractInlineDefaultsAndRequirements(string $pattern): string + { + if (false === strpbrk($pattern, '?<:')) { + return $pattern; + } + + $mapping = $this->getDefault('_route_mapping') ?? []; + + $pattern = preg_replace_callback('#\{(!?)([\w\x80-\xFF]++)(:[\w\x80-\xFF]++)?(<.*?>)?(\?[^\}]*+)?\}#', function ($m) use (&$mapping) { + if (isset($m[5][0])) { + $this->setDefault($m[2], '?' !== $m[5] ? substr($m[5], 1) : null); + } + if (isset($m[4][0])) { + $this->setRequirement($m[2], substr($m[4], 1, -1)); + } + if (isset($m[3][0])) { + $mapping[$m[2]] = substr($m[3], 1); + } + + return '{'.$m[1].$m[2].'}'; + }, $pattern); + + if ($mapping) { + $this->setDefault('_route_mapping', $mapping); + } + + return $pattern; + } + + private function sanitizeRequirement(string $key, string $regex): string + { + if ('' !== $regex) { + if ('^' === $regex[0]) { + $regex = substr($regex, 1); + } elseif (str_starts_with($regex, '\\A')) { + $regex = substr($regex, 2); + } + } + + if (str_ends_with($regex, '$')) { + $regex = substr($regex, 0, -1); + } elseif (\strlen($regex) - 2 === strpos($regex, '\\z')) { + $regex = substr($regex, 0, -2); + } + + if ('' === $regex) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key)); + } + + return $regex; + } + + private function isLocalized(): bool + { + return isset($this->defaults['_locale']) && isset($this->defaults['_canonical_route']) && ($this->requirements['_locale'] ?? null) === preg_quote($this->defaults['_locale']); + } +} diff --git a/vendor/symfony/routing/RouteCollection.php b/vendor/symfony/routing/RouteCollection.php new file mode 100644 index 0000000..df8e337 --- /dev/null +++ b/vendor/symfony/routing/RouteCollection.php @@ -0,0 +1,388 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Routing\Exception\InvalidArgumentException; +use Symfony\Component\Routing\Exception\RouteCircularReferenceException; + +/** + * A RouteCollection represents a set of Route instances. + * + * When adding a route at the end of the collection, an existing route + * with the same name is removed first. So there can only be one route + * with a given name. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @implements \IteratorAggregate + */ +class RouteCollection implements \IteratorAggregate, \Countable +{ + /** + * @var array + */ + private array $routes = []; + + /** + * @var array + */ + private array $aliases = []; + + /** + * @var array + */ + private array $resources = []; + + /** + * @var array + */ + private array $priorities = []; + + public function __clone() + { + foreach ($this->routes as $name => $route) { + $this->routes[$name] = clone $route; + } + + foreach ($this->aliases as $name => $alias) { + $this->aliases[$name] = clone $alias; + } + } + + /** + * Gets the current RouteCollection as an Iterator that includes all routes. + * + * It implements \IteratorAggregate. + * + * @see all() + * + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->all()); + } + + /** + * Gets the number of Routes in this collection. + */ + public function count(): int + { + return \count($this->routes); + } + + public function add(string $name, Route $route, int $priority = 0): void + { + unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); + + $this->routes[$name] = $route; + + if ($priority) { + $this->priorities[$name] = $priority; + } + } + + /** + * Returns all routes in this collection. + * + * @return array + */ + public function all(): array + { + if ($this->priorities) { + $priorities = $this->priorities; + $keysOrder = array_flip(array_keys($this->routes)); + uksort($this->routes, static fn ($n1, $n2) => (($priorities[$n2] ?? 0) <=> ($priorities[$n1] ?? 0)) ?: ($keysOrder[$n1] <=> $keysOrder[$n2])); + } + + return $this->routes; + } + + /** + * Gets a route by name. + */ + public function get(string $name): ?Route + { + $visited = []; + while (null !== $alias = $this->aliases[$name] ?? null) { + if (false !== $searchKey = array_search($name, $visited)) { + $visited[] = $name; + + throw new RouteCircularReferenceException($name, \array_slice($visited, $searchKey)); + } + + if ($alias->isDeprecated()) { + $deprecation = $alias->getDeprecation($name); + + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + $visited[] = $name; + $name = $alias->getId(); + } + + return $this->routes[$name] ?? null; + } + + /** + * Removes a route or an array of routes by name from the collection. + * + * @param string|string[] $name The route name or an array of route names + */ + public function remove(string|array $name): void + { + $routes = []; + foreach ((array) $name as $n) { + if (isset($this->routes[$n])) { + $routes[] = $n; + } + + unset($this->routes[$n], $this->priorities[$n], $this->aliases[$n]); + } + + if (!$routes) { + return; + } + + foreach ($this->aliases as $k => $alias) { + if (\in_array($alias->getId(), $routes, true)) { + unset($this->aliases[$k]); + } + } + } + + /** + * Adds a route collection at the end of the current set by appending all + * routes of the added collection. + */ + public function addCollection(self $collection): void + { + // we need to remove all routes with the same names first because just replacing them + // would not place the new route at the end of the merged array + foreach ($collection->all() as $name => $route) { + unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); + $this->routes[$name] = $route; + + if (isset($collection->priorities[$name])) { + $this->priorities[$name] = $collection->priorities[$name]; + } + } + + foreach ($collection->getAliases() as $name => $alias) { + unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); + + $this->aliases[$name] = $alias; + } + + foreach ($collection->getResources() as $resource) { + $this->addResource($resource); + } + } + + /** + * Adds a prefix to the path of all child routes. + */ + public function addPrefix(string $prefix, array $defaults = [], array $requirements = []): void + { + $prefix = trim(trim($prefix), '/'); + + if ('' === $prefix) { + return; + } + + foreach ($this->routes as $route) { + $route->setPath('/'.$prefix.$route->getPath()); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Adds a prefix to the name of all the routes within in the collection. + */ + public function addNamePrefix(string $prefix): void + { + $prefixedRoutes = []; + $prefixedPriorities = []; + $prefixedAliases = []; + + foreach ($this->routes as $name => $route) { + $prefixedRoutes[$prefix.$name] = $route; + if (null !== $canonicalName = $route->getDefault('_canonical_route')) { + $route->setDefault('_canonical_route', $prefix.$canonicalName); + } + if (isset($this->priorities[$name])) { + $prefixedPriorities[$prefix.$name] = $this->priorities[$name]; + } + } + + foreach ($this->aliases as $name => $alias) { + $prefixedAliases[$prefix.$name] = $alias->withId($prefix.$alias->getId()); + } + + $this->routes = $prefixedRoutes; + $this->priorities = $prefixedPriorities; + $this->aliases = $prefixedAliases; + } + + /** + * Sets the host pattern on all routes. + */ + public function setHost(?string $pattern, array $defaults = [], array $requirements = []): void + { + foreach ($this->routes as $route) { + $route->setHost($pattern); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets a condition on all routes. + * + * Existing conditions will be overridden. + */ + public function setCondition(?string $condition): void + { + foreach ($this->routes as $route) { + $route->setCondition($condition); + } + } + + /** + * Adds defaults to all routes. + * + * An existing default value under the same name in a route will be overridden. + */ + public function addDefaults(array $defaults): void + { + if ($defaults) { + foreach ($this->routes as $route) { + $route->addDefaults($defaults); + } + } + } + + /** + * Adds requirements to all routes. + * + * An existing requirement under the same name in a route will be overridden. + */ + public function addRequirements(array $requirements): void + { + if ($requirements) { + foreach ($this->routes as $route) { + $route->addRequirements($requirements); + } + } + } + + /** + * Adds options to all routes. + * + * An existing option value under the same name in a route will be overridden. + */ + public function addOptions(array $options): void + { + if ($options) { + foreach ($this->routes as $route) { + $route->addOptions($options); + } + } + } + + /** + * Sets the schemes (e.g. 'https') all child routes are restricted to. + * + * @param string|string[] $schemes The scheme or an array of schemes + */ + public function setSchemes(string|array $schemes): void + { + foreach ($this->routes as $route) { + $route->setSchemes($schemes); + } + } + + /** + * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to. + * + * @param string|string[] $methods The method or an array of methods + */ + public function setMethods(string|array $methods): void + { + foreach ($this->routes as $route) { + $route->setMethods($methods); + } + } + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] + */ + public function getResources(): array + { + return array_values($this->resources); + } + + /** + * Adds a resource for this collection. If the resource already exists + * it is not added. + */ + public function addResource(ResourceInterface $resource): void + { + $key = (string) $resource; + + if (!isset($this->resources[$key])) { + $this->resources[$key] = $resource; + } + } + + /** + * Sets an alias for an existing route. + * + * @param string $name The alias to create + * @param string $alias The route to alias + * + * @throws InvalidArgumentException if the alias is for itself + */ + public function addAlias(string $name, string $alias): Alias + { + if ($name === $alias) { + throw new InvalidArgumentException(sprintf('Route alias "%s" can not reference itself.', $name)); + } + + unset($this->routes[$name], $this->priorities[$name]); + + return $this->aliases[$name] = new Alias($alias); + } + + /** + * @return array + */ + public function getAliases(): array + { + return $this->aliases; + } + + public function getAlias(string $name): ?Alias + { + return $this->aliases[$name] ?? null; + } + + public function getPriority(string $name): ?int + { + return $this->priorities[$name] ?? null; + } +} diff --git a/vendor/symfony/routing/RouteCompiler.php b/vendor/symfony/routing/RouteCompiler.php new file mode 100644 index 0000000..330639f --- /dev/null +++ b/vendor/symfony/routing/RouteCompiler.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompiler compiles Route instances to CompiledRoute instances. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCompiler implements RouteCompilerInterface +{ + /** + * This string defines the characters that are automatically considered separators in front of + * optional placeholders (with default and no static text following). Such a single separator + * can be left out together with the optional placeholder from matching and generating URLs. + */ + public const SEPARATORS = '/,;.:-_~+*=@|'; + + /** + * The maximum supported length of a PCRE subpattern name + * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16. + * + * @internal + */ + public const VARIABLE_MAXIMUM_LENGTH = 32; + + /** + * @throws \InvalidArgumentException if a path variable is named _fragment + * @throws \LogicException if a variable is referenced more than once + * @throws \DomainException if a variable name starts with a digit or if it is too long to be successfully used as + * a PCRE subpattern + */ + public static function compile(Route $route): CompiledRoute + { + $hostVariables = []; + $variables = []; + $hostRegex = null; + $hostTokens = []; + + if ('' !== $host = $route->getHost()) { + $result = self::compilePattern($route, $host, true); + + $hostVariables = $result['variables']; + $variables = $hostVariables; + + $hostTokens = $result['tokens']; + $hostRegex = $result['regex']; + } + + $locale = $route->getDefault('_locale'); + if (null !== $locale && null !== $route->getDefault('_canonical_route') && preg_quote($locale) === $route->getRequirement('_locale')) { + $requirements = $route->getRequirements(); + unset($requirements['_locale']); + $route->setRequirements($requirements); + $route->setPath(str_replace('{_locale}', $locale, $route->getPath())); + } + + $path = $route->getPath(); + + $result = self::compilePattern($route, $path, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + + foreach ($pathVariables as $pathParam) { + if ('_fragment' === $pathParam) { + throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath())); + } + } + + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + $pathVariables, + $hostRegex, + $hostTokens, + $hostVariables, + array_unique($variables) + ); + } + + private static function compilePattern(Route $route, string $pattern, bool $isHost): array + { + $tokens = []; + $variables = []; + $matches = []; + $pos = 0; + $defaultSeparator = $isHost ? '.' : '/'; + $useUtf8 = preg_match('//u', $pattern); + $needsUtf8 = $route->getOption('utf8'); + + if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) { + throw new \LogicException(sprintf('Cannot use UTF-8 route patterns without setting the "utf8" option for route "%s".', $route->getPath())); + } + if (!$useUtf8 && $needsUtf8) { + throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern)); + } + + // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable + // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. + preg_match_all('#\{(!)?([\w\x80-\xFF]+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER); + foreach ($matches as $match) { + $important = $match[1][1] >= 0; + $varName = $match[2][0]; + // get all static text preceding the current variable + $precedingText = substr($pattern, $pos, $match[0][1] - $pos); + $pos = $match[0][1] + \strlen($match[0][0]); + + if (!\strlen($precedingText)) { + $precedingChar = ''; + } elseif ($useUtf8) { + preg_match('/.$/u', $precedingText, $precedingChar); + $precedingChar = $precedingChar[0]; + } else { + $precedingChar = substr($precedingText, -1); + } + $isSeparator = '' !== $precedingChar && str_contains(static::SEPARATORS, $precedingChar); + + // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the + // variable would not be usable as a Controller action argument. + if (preg_match('/^\d/', $varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern)); + } + if (\in_array($varName, $variables)) { + throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); + } + + if (\strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) { + throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %d characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern)); + } + + if ($isSeparator && $precedingText !== $precedingChar) { + $tokens[] = ['text', substr($precedingText, 0, -\strlen($precedingChar))]; + } elseif (!$isSeparator && '' !== $precedingText) { + $tokens[] = ['text', $precedingText]; + } + + $regexp = $route->getRequirement($varName); + if (null === $regexp) { + $followingPattern = (string) substr($pattern, $pos); + // Find the next static character after the variable that functions as a separator. By default, this separator and '/' + // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all + // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are + // the same that will be matched. Example: new Route('/{page}.{_format}', ['_format' => 'html']) + // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. + // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally + // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. + $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator) : '' + ); + if (('' !== $nextSeparator && !preg_match('#^\{[\w\x80-\xFF]+\}#', $followingPattern)) || '' === $followingPattern) { + // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive + // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. + // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow + // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is + // directly adjacent, e.g. '/{x}{y}'. + $regexp .= '+'; + } + } else { + if (!preg_match('//u', $regexp)) { + $useUtf8 = false; + } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?= 0; --$i) { + $token = $tokens[$i]; + // variable is optional when it is not important and has a default value + if ('variable' === $token[0] && !($token[5] ?? false) && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } + } + } + + // compute the matching regexp + $regexp = ''; + for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) { + $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + } + $regexp = '{^'.$regexp.'$}sD'.($isHost ? 'i' : ''); + + // enable Utf8 matching if really required + if ($needsUtf8) { + $regexp .= 'u'; + for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) { + if ('variable' === $tokens[$i][0]) { + $tokens[$i][4] = true; + } + } + } + + return [ + 'staticPrefix' => self::determineStaticPrefix($route, $tokens), + 'regex' => $regexp, + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ]; + } + + /** + * Determines the longest static prefix possible for a route. + */ + private static function determineStaticPrefix(Route $route, array $tokens): string + { + if ('text' !== $tokens[0][0]) { + return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1]; + } + + $prefix = $tokens[0][1]; + + if (isset($tokens[1][1]) && '/' !== $tokens[1][1] && false === $route->hasDefault($tokens[1][3])) { + $prefix .= $tokens[1][1]; + } + + return $prefix; + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator (or the empty string when none available). + */ + private static function findNextSeparator(string $pattern, bool $useUtf8): string + { + if ('' == $pattern) { + // return empty string if pattern is empty or false (false which can be returned by substr) + return ''; + } + // first remove all placeholders from the pattern so we can find the next real static character + if ('' === $pattern = preg_replace('#\{[\w\x80-\xFF]+\}#', '', $pattern)) { + return ''; + } + if ($useUtf8) { + preg_match('/^./u', $pattern, $pattern); + } + + return str_contains(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + } + + /** + * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * + * @param array $tokens The route tokens + * @param int $index The index of the current token + * @param int $firstOptional The index of the first optional token + */ + private static function computeRegexp(array $tokens, int $index, int $firstOptional): string + { + $token = $tokens[$index]; + if ('text' === $token[0]) { + // Text tokens + return preg_quote($token[1]); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1]), $token[3], $token[2]); + } else { + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1]), $token[3], $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + $nbTokens = \count($tokens); + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); + } + } + + return $regexp; + } + } + } + + private static function transformCapturingGroupsToNonCapturings(string $regexp): string + { + for ($i = 0; $i < \strlen($regexp); ++$i) { + if ('\\' === $regexp[$i]) { + ++$i; + continue; + } + if ('(' !== $regexp[$i] || !isset($regexp[$i + 2])) { + continue; + } + if ('*' === $regexp[++$i] || '?' === $regexp[$i]) { + ++$i; + continue; + } + $regexp = substr_replace($regexp, '?:', $i, 0); + ++$i; + } + + return $regexp; + } +} diff --git a/vendor/symfony/routing/RouteCompilerInterface.php b/vendor/symfony/routing/RouteCompilerInterface.php new file mode 100644 index 0000000..6215611 --- /dev/null +++ b/vendor/symfony/routing/RouteCompilerInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompilerInterface is the interface that all RouteCompiler classes must implement. + * + * @author Fabien Potencier + */ +interface RouteCompilerInterface +{ + /** + * Compiles the current route instance. + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + */ + public static function compile(Route $route): CompiledRoute; +} diff --git a/vendor/symfony/routing/Router.php b/vendor/symfony/routing/Router.php new file mode 100644 index 0000000..3aa9b4b --- /dev/null +++ b/vendor/symfony/routing/Router.php @@ -0,0 +1,308 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Config\ConfigCacheFactory; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\CompiledUrlGenerator; +use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; +use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; +use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\CompiledUrlMatcher; +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; +use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * The Router class is an example of the integration of all pieces of the + * routing system for easier use. + * + * @author Fabien Potencier + */ +class Router implements RouterInterface, RequestMatcherInterface +{ + protected UrlMatcherInterface|RequestMatcherInterface $matcher; + protected UrlGeneratorInterface $generator; + protected RequestContext $context; + protected LoaderInterface $loader; + protected RouteCollection $collection; + protected mixed $resource; + protected array $options = []; + protected ?LoggerInterface $logger; + protected ?string $defaultLocale; + + private ConfigCacheFactoryInterface $configCacheFactory; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private array $expressionLanguageProviders = []; + + private static ?array $cache = []; + + public function __construct(LoaderInterface $loader, mixed $resource, array $options = [], ?RequestContext $context = null, ?LoggerInterface $logger = null, ?string $defaultLocale = null) + { + $this->loader = $loader; + $this->resource = $resource; + $this->logger = $logger; + $this->context = $context ?? new RequestContext(); + $this->setOptions($options); + $this->defaultLocale = $defaultLocale; + } + + /** + * Sets options. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * generator_class: The name of a UrlGeneratorInterface implementation + * * generator_dumper_class: The name of a GeneratorDumperInterface implementation + * * matcher_class: The name of a UrlMatcherInterface implementation + * * matcher_dumper_class: The name of a MatcherDumperInterface implementation + * * resource_type: Type hint for the main resource (optional) + * * strict_requirements: Configure strict requirement checking for generators + * implementing ConfigurableRequirementsInterface (default is true) + * + * @throws \InvalidArgumentException When unsupported option is provided + */ + public function setOptions(array $options): void + { + $this->options = [ + 'cache_dir' => null, + 'debug' => false, + 'generator_class' => CompiledUrlGenerator::class, + 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, + 'matcher_class' => CompiledUrlMatcher::class, + 'matcher_dumper_class' => CompiledUrlMatcherDumper::class, + 'resource_type' => null, + 'strict_requirements' => true, + ]; + + // check option names and live merge, if errors are encountered Exception will be thrown + $invalid = []; + foreach ($options as $key => $value) { + if (\array_key_exists($key, $this->options)) { + $this->options[$key] = $value; + } else { + $invalid[] = $key; + } + } + + if ($invalid) { + throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid))); + } + } + + /** + * Sets an option. + * + * @throws \InvalidArgumentException + */ + public function setOption(string $key, mixed $value): void + { + if (!\array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + $this->options[$key] = $value; + } + + /** + * Gets an option value. + * + * @throws \InvalidArgumentException + */ + public function getOption(string $key): mixed + { + if (!\array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + return $this->options[$key]; + } + + public function getRouteCollection(): RouteCollection + { + return $this->collection ??= $this->loader->load($this->resource, $this->options['resource_type']); + } + + public function setContext(RequestContext $context): void + { + $this->context = $context; + + if (isset($this->matcher)) { + $this->getMatcher()->setContext($context); + } + if (isset($this->generator)) { + $this->getGenerator()->setContext($context); + } + } + + public function getContext(): RequestContext + { + return $this->context; + } + + /** + * Sets the ConfigCache factory to use. + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory): void + { + $this->configCacheFactory = $configCacheFactory; + } + + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string + { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } + + public function match(string $pathinfo): array + { + return $this->getMatcher()->match($pathinfo); + } + + public function matchRequest(Request $request): array + { + $matcher = $this->getMatcher(); + if (!$matcher instanceof RequestMatcherInterface) { + // fallback to the default UrlMatcherInterface + return $matcher->match($request->getPathInfo()); + } + + return $matcher->matchRequest($request); + } + + /** + * Gets the UrlMatcher or RequestMatcher instance associated with this Router. + */ + public function getMatcher(): UrlMatcherInterface|RequestMatcherInterface + { + if (isset($this->matcher)) { + return $this->matcher; + } + + if (null === $this->options['cache_dir']) { + $routes = $this->getRouteCollection(); + $compiled = is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true); + if ($compiled) { + $routes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes(); + } + $this->matcher = new $this->options['matcher_class']($routes, $this->context); + if (method_exists($this->matcher, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $this->matcher->addExpressionLanguageProvider($provider); + } + } + + return $this->matcher; + } + + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/url_matching_routes.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getMatcherDumperInstance(); + if (method_exists($dumper, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $dumper->addExpressionLanguageProvider($provider); + } + } + + $cache->write($dumper->dump(), $this->getRouteCollection()->getResources()); + unset(self::$cache[$cache->getPath()]); + } + ); + + return $this->matcher = new $this->options['matcher_class'](self::getCompiledRoutes($cache->getPath()), $this->context); + } + + /** + * Gets the UrlGenerator instance associated with this Router. + */ + public function getGenerator(): UrlGeneratorInterface + { + if (isset($this->generator)) { + return $this->generator; + } + + if (null === $this->options['cache_dir']) { + $routes = $this->getRouteCollection(); + $compiled = is_a($this->options['generator_class'], CompiledUrlGenerator::class, true); + if ($compiled) { + $generatorDumper = new CompiledUrlGeneratorDumper($routes); + $routes = array_merge($generatorDumper->getCompiledRoutes(), $generatorDumper->getCompiledAliases()); + } + $this->generator = new $this->options['generator_class']($routes, $this->context, $this->logger, $this->defaultLocale); + } else { + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/url_generating_routes.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getGeneratorDumperInstance(); + + $cache->write($dumper->dump(), $this->getRouteCollection()->getResources()); + unset(self::$cache[$cache->getPath()]); + } + ); + + $this->generator = new $this->options['generator_class'](self::getCompiledRoutes($cache->getPath()), $this->context, $this->logger, $this->defaultLocale); + } + + if ($this->generator instanceof ConfigurableRequirementsInterface) { + $this->generator->setStrictRequirements($this->options['strict_requirements']); + } + + return $this->generator; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider): void + { + $this->expressionLanguageProviders[] = $provider; + } + + protected function getGeneratorDumperInstance(): GeneratorDumperInterface + { + return new $this->options['generator_dumper_class']($this->getRouteCollection()); + } + + protected function getMatcherDumperInstance(): MatcherDumperInterface + { + return new $this->options['matcher_dumper_class']($this->getRouteCollection()); + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + */ + private function getConfigCacheFactory(): ConfigCacheFactoryInterface + { + return $this->configCacheFactory ??= new ConfigCacheFactory($this->options['debug']); + } + + private static function getCompiledRoutes(string $path): array + { + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $path; + } + + return self::$cache[$path] ??= require $path; + } +} diff --git a/vendor/symfony/routing/RouterInterface.php b/vendor/symfony/routing/RouterInterface.php new file mode 100644 index 0000000..5800f85 --- /dev/null +++ b/vendor/symfony/routing/RouterInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * RouterInterface is the interface that all Router classes must implement. + * + * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. + * + * @author Fabien Potencier + */ +interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface +{ + /** + * Gets the RouteCollection instance associated with this Router. + * + * WARNING: This method should never be used at runtime as it is SLOW. + * You might use it in a cache warmer though. + */ + public function getRouteCollection(): RouteCollection; +} diff --git a/vendor/symfony/routing/composer.json b/vendor/symfony/routing/composer.json new file mode 100644 index 0000000..59e30be --- /dev/null +++ b/vendor/symfony/routing/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/routing", + "type": "library", + "description": "Maps an HTTP request to a set of configuration variables", + "keywords": ["routing", "router", "url", "uri"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Routing\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/runtime/CHANGELOG.md b/vendor/symfony/runtime/CHANGELOG.md new file mode 100644 index 0000000..1a608b4 --- /dev/null +++ b/vendor/symfony/runtime/CHANGELOG.md @@ -0,0 +1,19 @@ +CHANGELOG +========= + +6.4 +--- + + * Add argument `bool $debug = false` to `HttpKernelRunner::__construct()` + +5.4 +--- + + * The component is not experimental anymore + * Add options "env_var_name" and "debug_var_name" to `GenericRuntime` and `SymfonyRuntime` + * Add option "dotenv_overload" to `SymfonyRuntime` + +5.3.0 +----- + + * Add the component diff --git a/vendor/symfony/runtime/GenericRuntime.php b/vendor/symfony/runtime/GenericRuntime.php new file mode 100644 index 0000000..789d0bc --- /dev/null +++ b/vendor/symfony/runtime/GenericRuntime.php @@ -0,0 +1,223 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime; + +use Symfony\Component\Runtime\Internal\BasicErrorHandler; +use Symfony\Component\Runtime\Resolver\ClosureResolver; +use Symfony\Component\Runtime\Resolver\DebugClosureResolver; +use Symfony\Component\Runtime\Runner\ClosureRunner; + +// Help opcache.preload discover always-needed symbols +class_exists(ClosureResolver::class); + +/** + * A runtime to do bare-metal PHP without using superglobals. + * + * It supports the following options: + * - "debug" toggles displaying errors and defaults + * to the "APP_DEBUG" environment variable; + * - "runtimes" maps types to a GenericRuntime implementation + * that knows how to deal with each of them; + * - "error_handler" defines the class to use to handle PHP errors; + * - "env_var_name" and "debug_var_name" define the name of the env + * vars that hold the Symfony env and the debug flag respectively. + * + * The app-callable can declare arguments among either: + * - "array $context" to get a local array similar to $_SERVER; + * - "array $argv" to get the command line arguments when running on the CLI; + * - "array $request" to get a local array with keys "query", "body", "files" and + * "session", which map to $_GET, $_POST, $FILES and &$_SESSION respectively. + * + * It should return a Closure():int|string|null or an instance of RunnerInterface. + * + * In debug mode, the runtime registers a strict error handler + * that throws exceptions when a PHP warning/notice is raised. + * + * @author Nicolas Grekas + */ +class GenericRuntime implements RuntimeInterface +{ + protected array $options; + + /** + * @param array { + * debug?: ?bool, + * runtimes?: ?array, + * error_handler?: string|false, + * env_var_name?: string, + * debug_var_name?: string, + * } $options + */ + public function __construct(array $options = []) + { + $options['env_var_name'] ??= 'APP_ENV'; + $debugKey = $options['debug_var_name'] ??= 'APP_DEBUG'; + + $debug = $options['debug'] ?? $_SERVER[$debugKey] ?? $_ENV[$debugKey] ?? true; + + if (!\is_bool($debug)) { + $debug = filter_var($debug, \FILTER_VALIDATE_BOOL); + } + + if ($debug) { + umask(0000); + $_SERVER[$debugKey] = $_ENV[$debugKey] = '1'; + + if (false !== $errorHandler = ($options['error_handler'] ?? BasicErrorHandler::class)) { + $errorHandler::register($debug); + $options['error_handler'] = false; + } + } else { + $_SERVER[$debugKey] = $_ENV[$debugKey] = '0'; + } + + $this->options = $options; + } + + public function getResolver(callable $callable, ?\ReflectionFunction $reflector = null): ResolverInterface + { + $callable = $callable(...); + $parameters = ($reflector ?? new \ReflectionFunction($callable))->getParameters(); + $arguments = function () use ($parameters) { + $arguments = []; + + try { + foreach ($parameters as $parameter) { + $type = $parameter->getType(); + $arguments[] = $this->getArgument($parameter, $type instanceof \ReflectionNamedType ? $type->getName() : null); + } + } catch (\InvalidArgumentException $e) { + if (!$parameter->isOptional()) { + throw $e; + } + } + + return $arguments; + }; + + if ($_SERVER[$this->options['debug_var_name']]) { + return new DebugClosureResolver($callable, $arguments); + } + + return new ClosureResolver($callable, $arguments); + } + + public function getRunner(?object $application): RunnerInterface + { + $application ??= static fn () => 0; + + if ($application instanceof RunnerInterface) { + return $application; + } + + if (!$application instanceof \Closure) { + if ($runtime = $this->resolveRuntime($application::class)) { + return $runtime->getRunner($application); + } + + if (!\is_callable($application)) { + throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application))); + } + + $application = $application(...); + } + + if ($_SERVER[$this->options['debug_var_name']] && ($r = new \ReflectionFunction($application)) && $r->getNumberOfRequiredParameters()) { + throw new \ArgumentCountError(sprintf('Zero argument should be required by the runner callable, but at least one is in "%s" on line "%d.', $r->getFileName(), $r->getStartLine())); + } + + return new ClosureRunner($application); + } + + protected function getArgument(\ReflectionParameter $parameter, ?string $type): mixed + { + if ('array' === $type) { + switch ($parameter->name) { + case 'context': + $context = $_SERVER; + + if ($_ENV && !isset($_SERVER['PATH']) && !isset($_SERVER['Path'])) { + $context += $_ENV; + } + + return $context; + + case 'argv': + return $_SERVER['argv'] ?? []; + + case 'request': + return [ + 'query' => $_GET, + 'body' => $_POST, + 'files' => $_FILES, + 'session' => &$_SESSION, + ]; + } + } + + if (RuntimeInterface::class === $type) { + return $this; + } + + if (!$runtime = $this->getRuntime($type)) { + $r = $parameter->getDeclaringFunction(); + + throw new \InvalidArgumentException(sprintf('Cannot resolve argument "%s $%s" in "%s" on line "%d": "%s" supports only arguments "array $context", "array $argv" and "array $request", or a runtime named "Symfony\Runtime\%1$sRuntime".', $type, $parameter->name, $r->getFileName(), $r->getStartLine(), get_debug_type($this))); + } + + return $runtime->getArgument($parameter, $type); + } + + protected static function register(self $runtime): self + { + return $runtime; + } + + private function getRuntime(string $type): ?self + { + if (null === $runtime = ($this->options['runtimes'][$type] ?? null)) { + $runtime = 'Symfony\Runtime\\'.$type.'Runtime'; + $runtime = class_exists($runtime) ? $runtime : $this->options['runtimes'][$type] = false; + } + + if (\is_string($runtime)) { + $runtime = $runtime::register($this); + } + + if ($this === $runtime) { + return null; + } + + return $runtime ?: null; + } + + private function resolveRuntime(string $class): ?self + { + if ($runtime = $this->getRuntime($class)) { + return $runtime; + } + + foreach (class_parents($class) as $type) { + if ($runtime = $this->getRuntime($type)) { + return $runtime; + } + } + + foreach (class_implements($class) as $type) { + if ($runtime = $this->getRuntime($type)) { + return $runtime; + } + } + + return null; + } +} diff --git a/vendor/symfony/runtime/Internal/BasicErrorHandler.php b/vendor/symfony/runtime/Internal/BasicErrorHandler.php new file mode 100644 index 0000000..3567c99 --- /dev/null +++ b/vendor/symfony/runtime/Internal/BasicErrorHandler.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class BasicErrorHandler +{ + public static function register(bool $debug): void + { + error_reporting(-1); + + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + ini_set('display_errors', $debug); + } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOL) || \ini_get('error_log')) { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + + if (0 <= \ini_get('zend.assertions')) { + ini_set('zend.assertions', 1); + ini_set('assert.active', $debug); + ini_set('assert.exception', 1); + } + + set_error_handler(new self()); + } + + public function __invoke(int $type, string $message, string $file, int $line): bool + { + if ((\E_DEPRECATED | \E_USER_DEPRECATED) & $type) { + return true; + } + + if ((error_reporting() | \E_ERROR | \E_RECOVERABLE_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR) & $type) { + throw new \ErrorException($message, 0, $type, $file, $line); + } + + return false; + } +} diff --git a/vendor/symfony/runtime/Internal/ComposerPlugin.php b/vendor/symfony/runtime/Internal/ComposerPlugin.php new file mode 100644 index 0000000..4f49e2b --- /dev/null +++ b/vendor/symfony/runtime/Internal/ComposerPlugin.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Internal; + +use Composer\Composer; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Plugin\PluginInterface; +use Composer\Script\ScriptEvents; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class ComposerPlugin implements PluginInterface, EventSubscriberInterface +{ + private Composer $composer; + private IOInterface $io; + + private static bool $activated = false; + + public function activate(Composer $composer, IOInterface $io): void + { + self::$activated = true; + $this->composer = $composer; + $this->io = $io; + } + + public function deactivate(Composer $composer, IOInterface $io): void + { + self::$activated = false; + } + + public function uninstall(Composer $composer, IOInterface $io): void + { + @unlink($composer->getConfig()->get('vendor-dir').'/autoload_runtime.php'); + } + + public function updateAutoloadFile(): void + { + $vendorDir = realpath($this->composer->getConfig()->get('vendor-dir')); + + if (!is_file($autoloadFile = $vendorDir.'/autoload.php') + || false === $extra = $this->composer->getPackage()->getExtra()['runtime'] ?? [] + ) { + return; + } + + $fs = new Filesystem(); + $projectDir = \dirname(realpath(Factory::getComposerFile())); + + if (null === $autoloadTemplate = $extra['autoload_template'] ?? null) { + $autoloadTemplate = __DIR__.'/autoload_runtime.template'; + } else { + if (!$fs->isAbsolutePath($autoloadTemplate)) { + $autoloadTemplate = $projectDir.'/'.$autoloadTemplate; + } + + if (!is_file($autoloadTemplate)) { + throw new \InvalidArgumentException(sprintf('File "%s" defined under "extra.runtime.autoload_template" in your composer.json file not found.', $this->composer->getPackage()->getExtra()['runtime']['autoload_template'])); + } + } + + $projectDir = $fs->makePathRelative($projectDir, $vendorDir); + $nestingLevel = 0; + + while (str_starts_with($projectDir, '../')) { + ++$nestingLevel; + $projectDir = substr($projectDir, 3); + } + + if (!$nestingLevel) { + $projectDir = '__'.'DIR__.'.var_export('/'.$projectDir, true); + } else { + $projectDir = 'dirname(__'."DIR__, $nestingLevel)".('' !== $projectDir ? '.'.var_export('/'.$projectDir, true) : ''); + } + + $runtimeClass = $extra['class'] ?? SymfonyRuntime::class; + + unset($extra['class'], $extra['autoload_template']); + + $code = strtr(file_get_contents($autoloadTemplate), [ + '%project_dir%' => $projectDir, + '%runtime_class%' => var_export($runtimeClass, true), + '%runtime_options%' => '['.substr(var_export($extra, true), 7, -1)." 'project_dir' => {$projectDir},\n]", + ]); + + // could use Composer\Util\Filesystem::filePutContentsIfModified once Composer 1.x support is dropped for this plugin + $path = substr_replace($autoloadFile, '_runtime', -4, 0); + $currentContent = @file_exists($path) ? @file_get_contents($path) : false; + if (false === $currentContent || $currentContent !== $code) { + file_put_contents($path, $code); + } + } + + public static function getSubscribedEvents(): array + { + if (!self::$activated) { + return []; + } + + return [ + ScriptEvents::POST_AUTOLOAD_DUMP => 'updateAutoloadFile', + ]; + } +} diff --git a/vendor/symfony/runtime/Internal/Console/ApplicationRuntime.php b/vendor/symfony/runtime/Internal/Console/ApplicationRuntime.php new file mode 100644 index 0000000..de6b1d9 --- /dev/null +++ b/vendor/symfony/runtime/Internal/Console/ApplicationRuntime.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Runtime\Symfony\Component\Console; + +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @internal + */ +class ApplicationRuntime extends SymfonyRuntime +{ +} diff --git a/vendor/symfony/runtime/Internal/Console/Command/CommandRuntime.php b/vendor/symfony/runtime/Internal/Console/Command/CommandRuntime.php new file mode 100644 index 0000000..9cc198e --- /dev/null +++ b/vendor/symfony/runtime/Internal/Console/Command/CommandRuntime.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Runtime\Symfony\Component\Console\Command; + +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @internal + */ +class CommandRuntime extends SymfonyRuntime +{ +} diff --git a/vendor/symfony/runtime/Internal/Console/Input/InputInterfaceRuntime.php b/vendor/symfony/runtime/Internal/Console/Input/InputInterfaceRuntime.php new file mode 100644 index 0000000..44360bf --- /dev/null +++ b/vendor/symfony/runtime/Internal/Console/Input/InputInterfaceRuntime.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Runtime\Symfony\Component\Console\Input; + +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @internal + */ +class InputInterfaceRuntime extends SymfonyRuntime +{ +} diff --git a/vendor/symfony/runtime/Internal/Console/Output/OutputInterfaceRuntime.php b/vendor/symfony/runtime/Internal/Console/Output/OutputInterfaceRuntime.php new file mode 100644 index 0000000..7c59186 --- /dev/null +++ b/vendor/symfony/runtime/Internal/Console/Output/OutputInterfaceRuntime.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Runtime\Symfony\Component\Console\Output; + +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @internal + */ +class OutputInterfaceRuntime extends SymfonyRuntime +{ +} diff --git a/vendor/symfony/runtime/Internal/HttpFoundation/RequestRuntime.php b/vendor/symfony/runtime/Internal/HttpFoundation/RequestRuntime.php new file mode 100644 index 0000000..2a3a0bb --- /dev/null +++ b/vendor/symfony/runtime/Internal/HttpFoundation/RequestRuntime.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Runtime\Symfony\Component\HttpFoundation; + +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @internal + */ +class RequestRuntime extends SymfonyRuntime +{ +} diff --git a/vendor/symfony/runtime/Internal/HttpFoundation/ResponseRuntime.php b/vendor/symfony/runtime/Internal/HttpFoundation/ResponseRuntime.php new file mode 100644 index 0000000..c70fbff --- /dev/null +++ b/vendor/symfony/runtime/Internal/HttpFoundation/ResponseRuntime.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Runtime\Symfony\Component\HttpFoundation; + +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @internal + */ +class ResponseRuntime extends SymfonyRuntime +{ +} diff --git a/vendor/symfony/runtime/Internal/HttpKernel/HttpKernelInterfaceRuntime.php b/vendor/symfony/runtime/Internal/HttpKernel/HttpKernelInterfaceRuntime.php new file mode 100644 index 0000000..08601ab --- /dev/null +++ b/vendor/symfony/runtime/Internal/HttpKernel/HttpKernelInterfaceRuntime.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Runtime\Symfony\Component\HttpKernel; + +use Symfony\Component\Runtime\SymfonyRuntime; + +/** + * @internal + */ +class HttpKernelInterfaceRuntime extends SymfonyRuntime +{ +} diff --git a/vendor/symfony/runtime/Internal/MissingDotenv.php b/vendor/symfony/runtime/Internal/MissingDotenv.php new file mode 100644 index 0000000..8968656 --- /dev/null +++ b/vendor/symfony/runtime/Internal/MissingDotenv.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Internal; + +/** + * @internal class that should be loaded only when symfony/dotenv is not installed + */ +class MissingDotenv +{ +} diff --git a/vendor/symfony/runtime/Internal/SymfonyErrorHandler.php b/vendor/symfony/runtime/Internal/SymfonyErrorHandler.php new file mode 100644 index 0000000..40c125a --- /dev/null +++ b/vendor/symfony/runtime/Internal/SymfonyErrorHandler.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Internal; + +use Symfony\Component\ErrorHandler\BufferingLogger; +use Symfony\Component\ErrorHandler\DebugClassLoader; +use Symfony\Component\ErrorHandler\ErrorHandler; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class SymfonyErrorHandler +{ + public static function register(bool $debug): void + { + BasicErrorHandler::register($debug); + + if (class_exists(ErrorHandler::class)) { + DebugClassLoader::enable(); + restore_error_handler(); + ErrorHandler::register(new ErrorHandler(new BufferingLogger(), $debug)); + } + } +} diff --git a/vendor/symfony/runtime/Internal/autoload_runtime.template b/vendor/symfony/runtime/Internal/autoload_runtime.template new file mode 100644 index 0000000..68af945 --- /dev/null +++ b/vendor/symfony/runtime/Internal/autoload_runtime.template @@ -0,0 +1,28 @@ +getResolver($app) + ->resolve(); + +$app = $app(...$args); + +exit( + $runtime + ->getRunner($app) + ->run() +); diff --git a/vendor/symfony/runtime/LICENSE b/vendor/symfony/runtime/LICENSE new file mode 100644 index 0000000..99c6bdf --- /dev/null +++ b/vendor/symfony/runtime/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021-present Fabien Potencier + +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. diff --git a/vendor/symfony/runtime/README.md b/vendor/symfony/runtime/README.md new file mode 100644 index 0000000..006e7a2 --- /dev/null +++ b/vendor/symfony/runtime/README.md @@ -0,0 +1,13 @@ +Runtime Component +================= + +Symfony Runtime enables decoupling applications from global state. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/runtime.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/runtime/Resolver/ClosureResolver.php b/vendor/symfony/runtime/Resolver/ClosureResolver.php new file mode 100644 index 0000000..c58cf2f --- /dev/null +++ b/vendor/symfony/runtime/Resolver/ClosureResolver.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Resolver; + +use Symfony\Component\Runtime\ResolverInterface; + +/** + * @author Nicolas Grekas + */ +class ClosureResolver implements ResolverInterface +{ + public function __construct( + private readonly \Closure $closure, + private readonly \Closure $arguments, + ) { + } + + public function resolve(): array + { + return [$this->closure, ($this->arguments)()]; + } +} diff --git a/vendor/symfony/runtime/Resolver/DebugClosureResolver.php b/vendor/symfony/runtime/Resolver/DebugClosureResolver.php new file mode 100644 index 0000000..923ae90 --- /dev/null +++ b/vendor/symfony/runtime/Resolver/DebugClosureResolver.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Resolver; + +/** + * @author Nicolas Grekas + */ +class DebugClosureResolver extends ClosureResolver +{ + public function resolve(): array + { + [$closure, $arguments] = parent::resolve(); + + return [ + static function (...$arguments) use ($closure) { + if (\is_object($app = $closure(...$arguments)) || null === $app) { + return $app; + } + + $r = new \ReflectionFunction($closure); + + throw new \TypeError(sprintf('Unexpected value of type "%s" returned, "object" expected from "%s" on line "%d".', get_debug_type($app), $r->getFileName(), $r->getStartLine())); + }, + $arguments, + ]; + } +} diff --git a/vendor/symfony/runtime/ResolverInterface.php b/vendor/symfony/runtime/ResolverInterface.php new file mode 100644 index 0000000..f6fa598 --- /dev/null +++ b/vendor/symfony/runtime/ResolverInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime; + +/** + * @author Nicolas Grekas + */ +interface ResolverInterface +{ + /** + * @return array{0: callable, 1: mixed[]} + */ + public function resolve(): array; +} diff --git a/vendor/symfony/runtime/Runner/ClosureRunner.php b/vendor/symfony/runtime/Runner/ClosureRunner.php new file mode 100644 index 0000000..4b12ff1 --- /dev/null +++ b/vendor/symfony/runtime/Runner/ClosureRunner.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Runner; + +use Symfony\Component\Runtime\RunnerInterface; + +/** + * @author Nicolas Grekas + */ +class ClosureRunner implements RunnerInterface +{ + public function __construct( + private readonly \Closure $closure, + ) { + } + + public function run(): int + { + $exitStatus = ($this->closure)(); + + if (\is_string($exitStatus)) { + echo $exitStatus; + + return 0; + } + + if (null !== $exitStatus && !\is_int($exitStatus)) { + $r = new \ReflectionFunction($this->closure); + + throw new \TypeError(sprintf('Unexpected value of type "%s" returned, "string|int|null" expected from "%s" on line "%d".', get_debug_type($exitStatus), $r->getFileName(), $r->getStartLine())); + } + + return $exitStatus ?? 0; + } +} diff --git a/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php b/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php new file mode 100644 index 0000000..d64bc1c --- /dev/null +++ b/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Runner\Symfony; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Runtime\RunnerInterface; + +/** + * @author Nicolas Grekas + */ +class ConsoleApplicationRunner implements RunnerInterface +{ + public function __construct( + private readonly Application $application, + private readonly ?string $defaultEnv, + private readonly InputInterface $input, + private readonly ?OutputInterface $output = null, + ) { + } + + public function run(): int + { + if (null === $this->defaultEnv) { + return $this->application->run($this->input, $this->output); + } + + $definition = $this->application->getDefinition(); + + if (!$definition->hasOption('env') && !$definition->hasOption('e') && !$definition->hasShortcut('e')) { + $definition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $this->defaultEnv)); + } + + if (!$definition->hasOption('no-debug')) { + $definition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.')); + } + + return $this->application->run($this->input, $this->output); + } +} diff --git a/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php b/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php new file mode 100644 index 0000000..13c9037 --- /dev/null +++ b/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Runner\Symfony; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\Runtime\RunnerInterface; + +/** + * @author Nicolas Grekas + */ +class HttpKernelRunner implements RunnerInterface +{ + public function __construct( + private readonly HttpKernelInterface $kernel, + private readonly Request $request, + private readonly bool $debug = false, + ) { + } + + public function run(): int + { + $response = $this->kernel->handle($this->request); + + if (Kernel::VERSION_ID >= 60400) { + $response->send(false); + + if (\function_exists('fastcgi_finish_request') && !$this->debug) { + fastcgi_finish_request(); + } elseif (\function_exists('litespeed_finish_request') && !$this->debug) { + litespeed_finish_request(); + } else { + Response::closeOutputBuffers(0, true); + flush(); + } + } else { + $response->send(); + } + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($this->request, $response); + } + + return 0; + } +} diff --git a/vendor/symfony/runtime/Runner/Symfony/ResponseRunner.php b/vendor/symfony/runtime/Runner/Symfony/ResponseRunner.php new file mode 100644 index 0000000..04e5a3f --- /dev/null +++ b/vendor/symfony/runtime/Runner/Symfony/ResponseRunner.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime\Runner\Symfony; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Runtime\RunnerInterface; + +/** + * @author Nicolas Grekas + */ +class ResponseRunner implements RunnerInterface +{ + public function __construct( + private readonly Response $response, + ) { + } + + public function run(): int + { + $this->response->send(); + + return 0; + } +} diff --git a/vendor/symfony/runtime/RunnerInterface.php b/vendor/symfony/runtime/RunnerInterface.php new file mode 100644 index 0000000..9001ff4 --- /dev/null +++ b/vendor/symfony/runtime/RunnerInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime; + +/** + * @author Nicolas Grekas + */ +interface RunnerInterface +{ + public function run(): int; +} diff --git a/vendor/symfony/runtime/RuntimeInterface.php b/vendor/symfony/runtime/RuntimeInterface.php new file mode 100644 index 0000000..f151757 --- /dev/null +++ b/vendor/symfony/runtime/RuntimeInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime; + +/** + * Enables decoupling applications from global state. + * + * @author Nicolas Grekas + */ +interface RuntimeInterface +{ + /** + * Returns a resolver that should compute the arguments of a callable. + * + * The callable itself should return an object that represents the application to pass to the getRunner() method. + */ + public function getResolver(callable $callable, ?\ReflectionFunction $reflector = null): ResolverInterface; + + /** + * Returns a callable that knows how to run the passed object and that returns its exit status as int. + * + * The passed object is typically created by calling ResolverInterface::resolve(). + */ + public function getRunner(?object $application): RunnerInterface; +} diff --git a/vendor/symfony/runtime/SymfonyRuntime.php b/vendor/symfony/runtime/SymfonyRuntime.php new file mode 100644 index 0000000..e88e1e0 --- /dev/null +++ b/vendor/symfony/runtime/SymfonyRuntime.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Runtime; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Dotenv\Dotenv; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Runtime\Internal\MissingDotenv; +use Symfony\Component\Runtime\Internal\SymfonyErrorHandler; +use Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner; +use Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner; +use Symfony\Component\Runtime\Runner\Symfony\ResponseRunner; + +// Help opcache.preload discover always-needed symbols +class_exists(MissingDotenv::class, false) || class_exists(Dotenv::class) || class_exists(MissingDotenv::class); + +/** + * Knows the basic conventions to run Symfony apps. + * + * In addition to the options managed by GenericRuntime, it accepts the following options: + * - "env" to define the name of the environment the app runs in; + * - "disable_dotenv" to disable looking for .env files; + * - "dotenv_path" to define the path of dot-env files - defaults to ".env"; + * - "prod_envs" to define the names of the production envs - defaults to ["prod"]; + * - "test_envs" to define the names of the test envs - defaults to ["test"]; + * - "use_putenv" to tell Dotenv to set env vars using putenv() (NOT RECOMMENDED.) + * - "dotenv_overload" to tell Dotenv to override existing vars + * + * When the "debug" / "env" options are not defined, they will fallback to the + * "APP_DEBUG" / "APP_ENV" environment variables, and to the "--env|-e" / "--no-debug" + * command line arguments if "symfony/console" is installed. + * + * When the "symfony/dotenv" component is installed, .env files are loaded. + * When "symfony/error-handler" is installed, it is registered in debug mode. + * + * On top of the base arguments provided by GenericRuntime, + * this runtime can feed the app-callable with arguments of type: + * - Request from "symfony/http-foundation" if the component is installed; + * - Application, Command, InputInterface and/or OutputInterface + * from "symfony/console" if the component is installed. + * + * This runtime can handle app-callables that return instances of either: + * - HttpKernelInterface, + * - Response, + * - Application, + * - Command, + * - int|string|null as handled by GenericRuntime. + * + * @author Nicolas Grekas + */ +class SymfonyRuntime extends GenericRuntime +{ + private readonly ArgvInput $input; + private readonly ConsoleOutput $output; + private readonly Application $console; + private readonly Command $command; + + /** + * @param array { + * debug?: ?bool, + * env?: ?string, + * disable_dotenv?: ?bool, + * project_dir?: ?string, + * prod_envs?: ?string[], + * dotenv_path?: ?string, + * test_envs?: ?string[], + * use_putenv?: ?bool, + * runtimes?: ?array, + * error_handler?: string|false, + * env_var_name?: string, + * debug_var_name?: string, + * dotenv_overload?: ?bool, + * } $options + */ + public function __construct(array $options = []) + { + $envKey = $options['env_var_name'] ??= 'APP_ENV'; + $debugKey = $options['debug_var_name'] ??= 'APP_DEBUG'; + + if (isset($options['env'])) { + $_SERVER[$envKey] = $options['env']; + } elseif (isset($_SERVER['argv']) && class_exists(ArgvInput::class)) { + $this->options = $options; + $this->getInput(); + } + + if (!($options['disable_dotenv'] ?? false) && isset($options['project_dir']) && !class_exists(MissingDotenv::class, false)) { + (new Dotenv($envKey, $debugKey)) + ->setProdEnvs((array) ($options['prod_envs'] ?? ['prod'])) + ->usePutenv($options['use_putenv'] ?? false) + ->bootEnv($options['project_dir'].'/'.($options['dotenv_path'] ?? '.env'), 'dev', (array) ($options['test_envs'] ?? ['test']), $options['dotenv_overload'] ?? false); + + if (isset($this->input) && ($options['dotenv_overload'] ?? false)) { + if ($this->input->getParameterOption(['--env', '-e'], $_SERVER[$envKey], true) !== $_SERVER[$envKey]) { + throw new \LogicException(sprintf('Cannot use "--env" or "-e" when the "%s" file defines "%s" and the "dotenv_overload" runtime option is true.', $options['dotenv_path'] ?? '.env', $envKey)); + } + + if ($_SERVER[$debugKey] && $this->input->hasParameterOption('--no-debug', true)) { + putenv($debugKey.'='.$_SERVER[$debugKey] = $_ENV[$debugKey] = '0'); + } + } + + $options['debug'] ??= '1' === $_SERVER[$debugKey]; + $options['disable_dotenv'] = true; + } else { + $_SERVER[$envKey] ??= $_ENV[$envKey] ?? 'dev'; + $_SERVER[$debugKey] ??= $_ENV[$debugKey] ?? !\in_array($_SERVER[$envKey], (array) ($options['prod_envs'] ?? ['prod']), true); + } + + $options['error_handler'] ??= SymfonyErrorHandler::class; + + parent::__construct($options); + } + + public function getRunner(?object $application): RunnerInterface + { + if ($application instanceof HttpKernelInterface) { + return new HttpKernelRunner($application, Request::createFromGlobals(), $this->options['debug'] ?? false); + } + + if ($application instanceof Response) { + return new ResponseRunner($application); + } + + if ($application instanceof Command) { + $console = $this->console ??= new Application(); + $console->setName($application->getName() ?: $console->getName()); + + if (!$application->getName() || !$console->has($application->getName())) { + $application->setName($_SERVER['argv'][0]); + $console->add($application); + } + + $console->setDefaultCommand($application->getName(), true); + $console->getDefinition()->addOptions($application->getDefinition()->getOptions()); + + return $this->getRunner($console); + } + + if ($application instanceof Application) { + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.\PHP_SAPI.' SAPI'.\PHP_EOL; + } + + set_time_limit(0); + $defaultEnv = !isset($this->options['env']) ? ($_SERVER[$this->options['env_var_name']] ?? 'dev') : null; + $output = $this->output ??= new ConsoleOutput(); + + return new ConsoleApplicationRunner($application, $defaultEnv, $this->getInput(), $output); + } + + if (isset($this->command)) { + $this->getInput()->bind($this->command->getDefinition()); + } + + return parent::getRunner($application); + } + + protected function getArgument(\ReflectionParameter $parameter, ?string $type): mixed + { + return match ($type) { + Request::class => Request::createFromGlobals(), + InputInterface::class => $this->getInput(), + OutputInterface::class => $this->output ??= new ConsoleOutput(), + Application::class => $this->console ??= new Application(), + Command::class => $this->command ??= new Command(), + default => parent::getArgument($parameter, $type), + }; + } + + protected static function register(GenericRuntime $runtime): GenericRuntime + { + $self = new self($runtime->options + ['runtimes' => []]); + $self->options['runtimes'] += [ + HttpKernelInterface::class => $self, + Request::class => $self, + Response::class => $self, + Application::class => $self, + Command::class => $self, + InputInterface::class => $self, + OutputInterface::class => $self, + ]; + $runtime->options = $self->options; + + return $self; + } + + private function getInput(): ArgvInput + { + if (isset($this->input)) { + return $this->input; + } + + $input = new ArgvInput(); + + if (isset($this->options['env'])) { + return $this->input = $input; + } + + if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) { + putenv($this->options['env_var_name'].'='.$_SERVER[$this->options['env_var_name']] = $_ENV[$this->options['env_var_name']] = $env); + } + + if ($input->hasParameterOption('--no-debug', true)) { + putenv($this->options['debug_var_name'].'='.$_SERVER[$this->options['debug_var_name']] = $_ENV[$this->options['debug_var_name']] = '0'); + } + + return $this->input = $input; + } +} diff --git a/vendor/symfony/runtime/composer.json b/vendor/symfony/runtime/composer.json new file mode 100644 index 0000000..fa9c2cb --- /dev/null +++ b/vendor/symfony/runtime/composer.json @@ -0,0 +1,45 @@ +{ + "name": "symfony/runtime", + "type": "composer-plugin", + "description": "Enables decoupling PHP applications from global state", + "keywords": ["runtime"], + "homepage": "https://symfony.com", + "license" : "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "composer-plugin-api": "^1.0|^2.0" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "conflict": { + "symfony/dotenv": "<6.4" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + } +} diff --git a/vendor/symfony/security-bundle/CHANGELOG.md b/vendor/symfony/security-bundle/CHANGELOG.md new file mode 100644 index 0000000..abc0c49 --- /dev/null +++ b/vendor/symfony/security-bundle/CHANGELOG.md @@ -0,0 +1,358 @@ +CHANGELOG +========= + +7.1 +--- + + * Mark class `ExpressionCacheWarmer` as `final` + * Support multiple signature algorithms for OIDC Token + * Support JWK or JWKSet for OIDC Token + +7.0 +--- + + * Enabling SecurityBundle and not configuring it is not allowed + * Remove the `enable_authenticator_manager` config option + * Remove the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead + * Remove the `require_previous_session` config option from authenticators + +6.4 +--- + + * Deprecate `Security::ACCESS_DENIED_ERROR`, `AUTHENTICATION_ERROR` and `LAST_USERNAME` constants, use the ones on `SecurityRequestAttributes` instead + * Allow an array of `pattern` in firewall configuration + * Add `$badges` argument to `Security::login` + * Deprecate the `require_previous_session` config option. Setting it has no effect anymore + * Add `LogoutRouteLoader` + +6.3 +--- + + * Deprecate enabling bundle and not configuring it + * Add `_stateless` attribute to the request when firewall is stateless and the attribute is not already set + * Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider + * Modify "icon.svg" to improve accessibility for blind/low vision users + * Make `Security::login()` return the authenticator response + * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead + * Make firewalls event dispatcher traceable on debug mode + * Add `TokenHandlerFactoryInterface`, `OidcUserInfoTokenHandlerFactory`, `OidcTokenHandlerFactory` and `ServiceTokenHandlerFactory` for `AccessTokenFactory` + +6.2 +--- + + * Add the `Security` helper class + * Deprecate the `Symfony\Component\Security\Core\Security` service alias, use `Symfony\Bundle\SecurityBundle\Security` instead + * Add `Security::getFirewallConfig()` to help to get the firewall configuration associated to the Request + * Add `Security::login()` to login programmatically + * Add `Security::logout()` to logout programmatically + * Add `security.firewalls.logout.enable_csrf` to enable CSRF protection using the default CSRF token generator + * Add RFC6750 Access Token support to allow token-based authentication + * Add `security.firewalls.switch_user.target_route` option to configure redirect target route on switch user + * Deprecate the `security.enable_authenticator_manager` config option + +6.1 +--- + + * The `security.access_control` now accepts a `RequestMatcherInterface` under the `request_matcher` option as scope configuration + * The `security.access_control` now accepts an `attributes` array to match request attributes in the `RequestMatcher` + * The `security.access_control` now accepts a `route` option to match request route in the `RequestMatcher` + * Display the inherited roles of the logged-in user in the Web Debug Toolbar + +6.0 +--- + + * The `security.authorization_checker` and `security.token_storage` services are now private + * Remove `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command, + use `UserPasswordHashCommand` and `user:hash-password` instead + * Remove the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases, + use `security.password_hasher_factory` and `Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface` instead + * Remove the `security.user_password_encoder.generic` service, the `security.password_encoder` and the `Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface` aliases, + use `security.user_password_hasher`, `security.password_hasher` and `Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface` instead + * Remove the `logout.success_handler` and `logout.handlers` config options, register a listener on the `LogoutEvent` event instead + * Remove `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead + +5.4 +--- + + * Deprecate `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead + * Deprecate `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, the logic is moved into the + `HttpBasicAuthenticator` and `ChannelListener` respectively + * Deprecate `FirewallConfig::allowsAnonymous()` and the `allows_anonymous` from the data collector data, there will be no anonymous concept as of version 6. + * Deprecate not setting `$authenticatorManagerEnabled` to `true` in `SecurityDataCollector` and `DebugFirewallCommand` + * Deprecate `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of + `AuthenticatorFactoryInterface` and `SecurityExtension::addAuthenticatorFactory()` + * Add `AuthenticatorFactoryInterface::getPriority()` which replaces `SecurityFactoryInterface::getPosition()` + * Deprecate passing an array of arrays as 1st argument to `MainConfiguration`, pass a sorted flat array of + factories instead. + * Deprecate the `always_authenticate_before_granting` option + * Display the roles of the logged-in user in the Web Debug Toolbar + * Add the `security.access_decision_manager.strategy_service` option + * Deprecate not configuring explicitly a provider for custom_authenticators when there is more than one registered provider + + +5.3 +--- + + * The authenticator system is no longer experimental + * Login Link functionality is no longer experimental + * Add `required_badges` firewall config option + * [BC break] Add `login_throttling.lock_factory` setting defaulting to `null` (instead of `lock.factory`) + * Add a `login_throttling.interval` (in `security.firewalls`) option to change the default throttling interval. + * Add the `debug:firewall` command. + * Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command, + use `UserPasswordHashCommand` and `user:hash-password` instead + * Deprecate the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases, + use `security.password_hasher_factory` and `Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface` instead + * Deprecate the `security.user_password_encoder.generic` service, the `security.password_encoder` and the `Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface` aliases, + use `security.user_password_hasher`, `security.password_hasher` and `Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface` instead + * Deprecate the public `security.authorization_checker` and `security.token_storage` services to private + * Not setting the `enable_authenticator_manager` config option to `true` is deprecated + * Deprecate the `security.authentication.provider.*` services, use the new authenticator system instead + * Deprecate the `security.authentication.listener.*` services, use the new authenticator system instead + * Deprecate the Guard component integration, use the new authenticator system instead + * Add `form_login.form_only` option + +5.2.0 +----- + + * Added `FirewallListenerFactoryInterface`, which can be implemented by security factories to add firewall listeners + * Added `SortFirewallListenersPass` to make the execution order of firewall listeners configurable by + leveraging `Symfony\Component\Security\Http\Firewall\FirewallListenerInterface` + * Added ability to use comma separated ip address list for `security.access_control` + * [BC break] Removed `EntryPointFactoryInterface`, authenticators must now implement `AuthenticationEntryPointInterface` if + they require autoregistration of a Security entry point. + +5.1.0 +----- + + * Added XSD for configuration + * Added security configuration for priority-based access decision strategy + * Marked the `AnonymousFactory`, `FormLoginFactory`, `FormLoginLdapFactory`, `GuardAuthenticationFactory`, `HttpBasicFactory`, `HttpBasicLdapFactory`, `JsonLoginFactory`, `JsonLoginLdapFactory`, `RememberMeFactory`, `RemoteUserFactory` and `X509Factory` as `@internal` + * Renamed method `AbstractFactory#createEntryPoint()` to `AbstractFactory#createDefaultEntryPoint()` + +5.0.0 +----- + + * The `switch_user.stateless` firewall option has been removed. + * Removed the ability to configure encoders using `argon2i` or `bcrypt` as algorithm, use `auto` instead + * The `simple_form` and `simple_preauth` authentication listeners have been removed, + use Guard instead. + * The `SimpleFormFactory` and `SimplePreAuthenticationFactory` classes have been removed, + use Guard instead. + * Removed `LogoutUrlHelper` and `SecurityHelper` templating helpers, use Twig instead + * Removed the `logout_on_user_change` firewall option + * Removed the `threads` encoder option + * Removed the `security.authentication.trust_resolver.anonymous_class` parameter + * Removed the `security.authentication.trust_resolver.rememberme_class` parameter + * Removed the `security.user.provider.in_memory.user` service. + +4.4.0 +----- + + * Added `anonymous: lazy` mode to firewalls to make them (not) start the session as late as possible + * Added `migrate_from` option to encoders configuration. + * Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.) + * Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories. + * Marked the `SecurityDataCollector` class as `@final`. + +4.3.0 +----- + + * Added new encoder types: `auto` (recommended), `native` and `sodium` + * The normalization of the cookie names configured in the `logout.delete_cookies` + option is deprecated and will be disabled in Symfony 5.0. This affects to cookies + with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie` + name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore). + +4.2.0 +----- + + * Using the `security.authentication.trust_resolver.anonymous_class` and + `security.authentication.trust_resolver.rememberme_class` parameters to define + the token classes is deprecated. To use custom tokens extend the existing + `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken`. + or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`. + * Added `Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass` + * Added `json_login_ldap` authentication provider to use LDAP authentication with a REST API. + * Made remember-me cookies inherit their default config from `framework.session.cookie_*` + and added an "auto" mode to their "secure" config option to make them secure on HTTPS automatically. + * Deprecated the `simple_form` and `simple_preauth` authentication listeners, use Guard instead. + * Deprecated the `SimpleFormFactory` and `SimplePreAuthenticationFactory` classes, use Guard instead. + * Added `port` in access_control + * Added individual voter decisions to the profiler + +4.1.0 +----- + + * The `switch_user.stateless` firewall option is deprecated, use the `stateless` option instead. + * The `logout_on_user_change` firewall option is deprecated. + * deprecated `SecurityUserValueResolver`, use + `Symfony\Component\Security\Http\Controller\UserValueResolver` instead. + +4.0.0 +----- + + * removed `FirewallContext::getContext()` + * made `FirewallMap::$container` and `::$map` private + * made the first `UserPasswordEncoderCommand::_construct()` argument mandatory + * `UserPasswordEncoderCommand` does not extend `ContainerAwareCommand` anymore + * removed support for voters that don't implement the `VoterInterface` + * removed HTTP digest authentication + * removed command `acl:set` along with `SetAclCommand` class + * removed command `init:acl` along with `InitAclCommand` class + * removed `acl` configuration key and related services, use symfony/acl-bundle instead + * removed auto picking the first registered provider when no configured provider on a firewall and ambiguous + * the firewall option `logout_on_user_change` is now always true, which will trigger a logout if the user changes + between requests + * the `switch_user.stateless` firewall option is `true` for stateless firewalls + +3.4.0 +----- + + * Added new `security.helper` service that is an instance of `Symfony\Component\Security\Core\Security` + and provides shortcuts for common security tasks. + * Tagging voters with the `security.voter` tag without implementing the + `VoterInterface` on the class is now deprecated and will be removed in 4.0. + * [BC BREAK] `FirewallContext::getListeners()` now returns `\Traversable|array` + * added info about called security listeners in profiler + * Added `logout_on_user_change` to the firewall options. This config item will + trigger a logout when the user has changed. Should be set to true to avoid + deprecations in the configuration. + * deprecated HTTP digest authentication + * deprecated command `acl:set` along with `SetAclCommand` class + * deprecated command `init:acl` along with `InitAclCommand` class + * Added support for the new Argon2i password encoder + * added `stateless` option to the `switch_user` listener + * deprecated auto picking the first registered provider when no configured provider on a firewall and ambiguous + +3.3.0 +----- + + * Deprecated instantiating `UserPasswordEncoderCommand` without its constructor + arguments fully provided. + * Deprecated `UserPasswordEncoderCommand::getContainer()` and relying on the + `ContainerAwareCommand` sub class or `ContainerAwareInterface` implementation for this command. + * Deprecated the `FirewallMap::$map` and `$container` properties. + * [BC BREAK] Keys of the `users` node for `in_memory` user provider are no longer normalized. + * deprecated `FirewallContext::getListeners()` + +3.2.0 +----- + + * Added the `SecurityUserValueResolver` to inject the security users in actions via + `Symfony\Component\Security\Core\User\UserInterface` in the method signature. + +3.0.0 +----- + + * Removed the `security.context` service. + +2.8.0 +----- + + * deprecated the `key` setting of `anonymous`, `remember_me` and `http_digest` + in favor of the `secret` setting. + * deprecated the `intention` firewall listener setting in favor of the `csrf_token_id`. + +2.6.0 +----- + + * Added the possibility to override the default success/failure handler + to get the provider key and the options injected + * Deprecated the `security.context` service for the `security.token_storage` and + `security.authorization_checker` services. + +2.4.0 +----- + + * Added 'host' option to firewall configuration + * Added 'csrf_token_generator' and 'csrf_token_id' options to firewall logout + listener configuration to supersede/alias 'csrf_provider' and 'intention' + respectively + * Moved 'security.secure_random' service configuration to FrameworkBundle + +2.3.0 +----- + + * allowed for multiple IP address in security access_control rules + +2.2.0 +----- + + * Added PBKDF2 Password encoder + * Added BCrypt password encoder + +2.1.0 +----- + + * [BC BREAK] The custom factories for the firewall configuration are now + registered during the build method of bundles instead of being registered + by the end-user (you need to remove the 'factories' keys in your security + configuration). + + * [BC BREAK] The Firewall listener is now registered after the Router one. This + means that specific Firewall URLs (like /login_check and /logout must now + have proper route defined in your routing configuration) + + * [BC BREAK] refactored the user provider configuration. The configuration + changed for the chain provider and the memory provider: + + Before: + + ``` yaml + security: + providers: + my_chain_provider: + providers: [my_memory_provider, my_doctrine_provider] + my_memory_provider: + users: + toto: { password: foobar, roles: [ROLE_USER] } + foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] } + ``` + + After: + + ``` yaml + security: + providers: + my_chain_provider: + chain: + providers: [my_memory_provider, my_doctrine_provider] + my_memory_provider: + memory: + users: + toto: { password: foobar, roles: [ROLE_USER] } + foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] } + ``` + + * [BC BREAK] Method `equals` was removed from `UserInterface` to its own new + `EquatableInterface`. The user class can now implement this interface to override + the default implementation of users equality test. + + * added a validator for the user password + * added 'erase_credentials' as a configuration key (true by default) + * added new events: `security.authentication.success` and `security.authentication.failure` + fired on authentication success/failure, regardless of authentication method, + events are defined in new event class: `Symfony\Component\Security\Core\AuthenticationEvents`. + + * Added optional CSRF protection to LogoutListener: + + ``` yaml + security: + firewalls: + default: + logout: + path: /logout_path + target: / + csrf_parameter: _csrf_token # Optional (defaults to "_csrf_token") + csrf_provider: security.csrf.token_generator # Required to enable protection + intention: logout # Optional (defaults to "logout") + ``` + + If the LogoutListener has CSRF protection enabled but cannot validate a token, + then a LogoutException will be thrown. + + * Added `logout_url` templating helper and Twig extension, which may be used to + generate logout URL's within templates. The security firewall's config key + must be specified. If a firewall's logout listener has CSRF protection + enabled, a token will be automatically added to the generated URL. diff --git a/vendor/symfony/security-bundle/CacheWarmer/ExpressionCacheWarmer.php b/vendor/symfony/security-bundle/CacheWarmer/ExpressionCacheWarmer.php new file mode 100644 index 0000000..5b14687 --- /dev/null +++ b/vendor/symfony/security-bundle/CacheWarmer/ExpressionCacheWarmer.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\CacheWarmer; + +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; + +/** + * @final since Symfony 7.1 + */ +class ExpressionCacheWarmer implements CacheWarmerInterface +{ + private iterable $expressions; + private ExpressionLanguage $expressionLanguage; + + /** + * @param iterable $expressions + */ + public function __construct(iterable $expressions, ExpressionLanguage $expressionLanguage) + { + $this->expressions = $expressions; + $this->expressionLanguage = $expressionLanguage; + } + + public function isOptional(): bool + { + return true; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + foreach ($this->expressions as $expression) { + $this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']); + } + + return []; + } +} diff --git a/vendor/symfony/security-bundle/Command/DebugFirewallCommand.php b/vendor/symfony/security-bundle/Command/DebugFirewallCommand.php new file mode 100644 index 0000000..ffc3035 --- /dev/null +++ b/vendor/symfony/security-bundle/Command/DebugFirewallCommand.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Command; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallContext; +use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; + +/** + * @author Timo Bakx + */ +#[AsCommand(name: 'debug:firewall', description: 'Display information about your security firewall(s)')] +final class DebugFirewallCommand extends Command +{ + private array $firewallNames; + private ContainerInterface $contexts; + private ContainerInterface $eventDispatchers; + private array $authenticators; + + /** + * @param string[] $firewallNames + * @param AuthenticatorInterface[][] $authenticators + */ + public function __construct(array $firewallNames, ContainerInterface $contexts, ContainerInterface $eventDispatchers, array $authenticators) + { + $this->firewallNames = $firewallNames; + $this->contexts = $contexts; + $this->eventDispatchers = $eventDispatchers; + $this->authenticators = $authenticators; + + parent::__construct(); + } + + protected function configure(): void + { + $exampleName = $this->getExampleName(); + + $this + ->setHelp(<<%command.name% command displays the firewalls that are configured +in your application: + + php %command.full_name% + +You can pass a firewall name to display more detailed information about +a specific firewall: + + php %command.full_name% $exampleName + +To include all events and event listeners for a specific firewall, use the +events option: + + php %command.full_name% --events $exampleName + +EOF + ) + ->setDefinition([ + new InputArgument('name', InputArgument::OPTIONAL, sprintf('A firewall name (for example "%s")', $exampleName)), + new InputOption('events', null, InputOption::VALUE_NONE, 'Include a list of event listeners (only available in combination with the "name" argument)'), + ]); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $name = $input->getArgument('name'); + + if (null === $name) { + $this->displayFirewallList($io); + + return 0; + } + + $serviceId = sprintf('security.firewall.map.context.%s', $name); + + if (!$this->contexts->has($serviceId)) { + $io->error(sprintf('Firewall %s was not found. Available firewalls are: %s', $name, implode(', ', $this->firewallNames))); + + return 1; + } + + /** @var FirewallContext $context */ + $context = $this->contexts->get($serviceId); + + $io->title(sprintf('Firewall "%s"', $name)); + + $this->displayFirewallSummary($name, $context, $io); + + $this->displaySwitchUser($context, $io); + + if ($input->getOption('events')) { + $this->displayEventListeners($name, $context, $io); + } + + $this->displayAuthenticators($name, $io); + + return 0; + } + + protected function displayFirewallList(SymfonyStyle $io): void + { + $io->title('Firewalls'); + $io->text('The following firewalls are defined:'); + + $io->listing($this->firewallNames); + + $io->comment(sprintf('To view details of a specific firewall, re-run this command with a firewall name. (e.g. debug:firewall %s)', $this->getExampleName())); + } + + protected function displayFirewallSummary(string $name, FirewallContext $context, SymfonyStyle $io): void + { + if (null === $context->getConfig()) { + return; + } + + $rows = [ + ['Name', $name], + ['Context', $context->getConfig()->getContext()], + ['Lazy', $context instanceof LazyFirewallContext ? 'Yes' : 'No'], + ['Stateless', $context->getConfig()->isStateless() ? 'Yes' : 'No'], + ['User Checker', $context->getConfig()->getUserChecker()], + ['Provider', $context->getConfig()->getProvider()], + ['Entry Point', $context->getConfig()->getEntryPoint()], + ['Access Denied URL', $context->getConfig()->getAccessDeniedUrl()], + ['Access Denied Handler', $context->getConfig()->getAccessDeniedHandler()], + ]; + + $io->table( + ['Option', 'Value'], + $rows + ); + } + + private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io): void + { + if ((null === $config = $context->getConfig()) || (null === $switchUser = $config->getSwitchUser())) { + return; + } + + $io->section('User switching'); + + $io->table(['Option', 'Value'], [ + ['Parameter', $switchUser['parameter'] ?? ''], + ['Provider', $switchUser['provider'] ?? $config->getProvider()], + ['User Role', $switchUser['role'] ?? ''], + ]); + } + + protected function displayEventListeners(string $name, FirewallContext $context, SymfonyStyle $io): void + { + $io->title(sprintf('Event listeners for firewall "%s"', $name)); + + $dispatcherId = sprintf('security.event_dispatcher.%s', $name); + + if (!$this->eventDispatchers->has($dispatcherId)) { + $io->text('No event dispatcher has been registered for this firewall.'); + + return; + } + + /** @var EventDispatcherInterface $dispatcher */ + $dispatcher = $this->eventDispatchers->get($dispatcherId); + + foreach ($dispatcher->getListeners() as $event => $listeners) { + $io->section(sprintf('"%s" event', $event)); + + $rows = []; + foreach ($listeners as $order => $listener) { + $rows[] = [ + sprintf('#%d', $order + 1), + $this->formatCallable($listener), + $dispatcher->getListenerPriority($event, $listener), + ]; + } + + $io->table( + ['Order', 'Callable', 'Priority'], + $rows + ); + } + } + + private function displayAuthenticators(string $name, SymfonyStyle $io): void + { + $io->title(sprintf('Authenticators for firewall "%s"', $name)); + + $authenticators = $this->authenticators[$name] ?? []; + + if (0 === \count($authenticators)) { + $io->text('No authenticators have been registered for this firewall.'); + + return; + } + + $io->table( + ['Classname'], + array_map( + fn ($authenticator) => [$authenticator::class], + $authenticators + ) + ); + } + + private function formatCallable(mixed $callable): string + { + if (\is_array($callable)) { + if (\is_object($callable[0])) { + return sprintf('%s::%s()', $callable[0]::class, $callable[1]); + } + + return sprintf('%s::%s()', $callable[0], $callable[1]); + } + + if (\is_string($callable)) { + return sprintf('%s()', $callable); + } + + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + if ($r->isAnonymous()) { + return 'Closure()'; + } + if ($class = $r->getClosureCalledClass()) { + return sprintf('%s::%s()', $class->name, $r->name); + } + + return $r->name.'()'; + } + + if (method_exists($callable, '__invoke')) { + return sprintf('%s::__invoke()', $callable::class); + } + + throw new \InvalidArgumentException('Callable is not describable.'); + } + + private function getExampleName(): string + { + $name = 'main'; + + if (!\in_array($name, $this->firewallNames, true)) { + $name = reset($this->firewallNames); + } + + return $name; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues($this->firewallNames); + } + } +} diff --git a/vendor/symfony/security-bundle/DataCollector/SecurityDataCollector.php b/vendor/symfony/security-bundle/DataCollector/SecurityDataCollector.php new file mode 100644 index 0000000..2c0562e --- /dev/null +++ b/vendor/symfony/security-bundle/DataCollector/SecurityDataCollector.php @@ -0,0 +1,352 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DataCollector; + +use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; +use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter; +use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; +use Symfony\Component\Security\Http\Firewall\SwitchUserListener; +use Symfony\Component\Security\Http\FirewallMapInterface; +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Fabien Potencier + * + * @final + */ +class SecurityDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private ?TokenStorageInterface $tokenStorage; + private ?RoleHierarchyInterface $roleHierarchy; + private ?LogoutUrlGenerator $logoutUrlGenerator; + private ?AccessDecisionManagerInterface $accessDecisionManager; + private ?FirewallMapInterface $firewallMap; + private ?TraceableFirewallListener $firewall; + private bool $hasVarDumper; + + public function __construct(?TokenStorageInterface $tokenStorage = null, ?RoleHierarchyInterface $roleHierarchy = null, ?LogoutUrlGenerator $logoutUrlGenerator = null, ?AccessDecisionManagerInterface $accessDecisionManager = null, ?FirewallMapInterface $firewallMap = null, ?TraceableFirewallListener $firewall = null) + { + $this->tokenStorage = $tokenStorage; + $this->roleHierarchy = $roleHierarchy; + $this->logoutUrlGenerator = $logoutUrlGenerator; + $this->accessDecisionManager = $accessDecisionManager; + $this->firewallMap = $firewallMap; + $this->firewall = $firewall; + $this->hasVarDumper = class_exists(ClassStub::class); + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + if (null === $this->tokenStorage) { + $this->data = [ + 'enabled' => false, + 'authenticated' => false, + 'impersonated' => false, + 'impersonator_user' => null, + 'impersonation_exit_path' => null, + 'token' => null, + 'token_class' => null, + 'logout_url' => null, + 'user' => '', + 'roles' => [], + 'inherited_roles' => [], + 'supports_role_hierarchy' => null !== $this->roleHierarchy, + ]; + } elseif (null === $token = $this->tokenStorage->getToken()) { + $this->data = [ + 'enabled' => true, + 'authenticated' => false, + 'impersonated' => false, + 'impersonator_user' => null, + 'impersonation_exit_path' => null, + 'token' => null, + 'token_class' => null, + 'logout_url' => null, + 'user' => '', + 'roles' => [], + 'inherited_roles' => [], + 'supports_role_hierarchy' => null !== $this->roleHierarchy, + ]; + } else { + $inheritedRoles = []; + $assignedRoles = $token->getRoleNames(); + + $impersonatorUser = null; + if ($token instanceof SwitchUserToken) { + $originalToken = $token->getOriginalToken(); + $impersonatorUser = $originalToken->getUserIdentifier(); + } + + if (null !== $this->roleHierarchy) { + foreach ($this->roleHierarchy->getReachableRoleNames($assignedRoles) as $role) { + if (!\in_array($role, $assignedRoles, true)) { + $inheritedRoles[] = $role; + } + } + } + + $logoutUrl = null; + try { + $logoutUrl = $this->logoutUrlGenerator?->getLogoutPath(); + } catch (\Exception) { + // fail silently when the logout URL cannot be generated + } + + $this->data = [ + 'enabled' => true, + 'authenticated' => (bool) $token->getUser(), + 'impersonated' => null !== $impersonatorUser, + 'impersonator_user' => $impersonatorUser, + 'impersonation_exit_path' => null, + 'token' => $token, + 'token_class' => $this->hasVarDumper ? new ClassStub($token::class) : $token::class, + 'logout_url' => $logoutUrl, + 'user' => $token->getUserIdentifier(), + 'roles' => $assignedRoles, + 'inherited_roles' => array_unique($inheritedRoles), + 'supports_role_hierarchy' => null !== $this->roleHierarchy, + ]; + } + + // collect voters and access decision manager information + if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) { + $this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy(); + $this->data['voters'] = []; + + foreach ($this->accessDecisionManager->getVoters() as $voter) { + if ($voter instanceof TraceableVoter) { + $voter = $voter->getDecoratedVoter(); + } + + $this->data['voters'][] = $this->hasVarDumper ? new ClassStub($voter::class) : $voter::class; + } + + // collect voter details + $decisionLog = $this->accessDecisionManager->getDecisionLog(); + foreach ($decisionLog as $key => $log) { + $decisionLog[$key]['voter_details'] = []; + foreach ($log['voterDetails'] as $voterDetail) { + $voterClass = $voterDetail['voter']::class; + $classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass; + $decisionLog[$key]['voter_details'][] = [ + 'class' => $classData, + 'attributes' => $voterDetail['attributes'], // Only displayed for unanimous strategy + 'vote' => $voterDetail['vote'], + ]; + } + unset($decisionLog[$key]['voterDetails']); + } + + $this->data['access_decision_log'] = $decisionLog; + } else { + $this->data['access_decision_log'] = []; + $this->data['voter_strategy'] = 'unknown'; + $this->data['voters'] = []; + } + + // collect firewall context information + $this->data['firewall'] = null; + if ($this->firewallMap instanceof FirewallMap) { + $firewallConfig = $this->firewallMap->getFirewallConfig($request); + if (null !== $firewallConfig) { + $this->data['firewall'] = [ + 'name' => $firewallConfig->getName(), + 'request_matcher' => $firewallConfig->getRequestMatcher(), + 'security_enabled' => $firewallConfig->isSecurityEnabled(), + 'stateless' => $firewallConfig->isStateless(), + 'provider' => $firewallConfig->getProvider(), + 'context' => $firewallConfig->getContext(), + 'entry_point' => $firewallConfig->getEntryPoint(), + 'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(), + 'access_denied_url' => $firewallConfig->getAccessDeniedUrl(), + 'user_checker' => $firewallConfig->getUserChecker(), + 'authenticators' => $firewallConfig->getAuthenticators(), + ]; + + // generate exit impersonation path from current request + if ($this->data['impersonated'] && null !== $switchUserConfig = $firewallConfig->getSwitchUser()) { + $exitPath = $request->getRequestUri(); + $exitPath .= null === $request->getQueryString() ? '?' : '&'; + $exitPath .= sprintf('%s=%s', urlencode($switchUserConfig['parameter']), SwitchUserListener::EXIT_VALUE); + + $this->data['impersonation_exit_path'] = $exitPath; + } + } + } + + // collect firewall listeners information + $this->data['listeners'] = []; + if ($this->firewall) { + $this->data['listeners'] = $this->firewall->getWrappedListeners(); + } + + $this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : []; + } + + public function reset(): void + { + $this->data = []; + } + + public function lateCollect(): void + { + $this->data = $this->cloneVar($this->data); + } + + /** + * Checks if security is enabled. + */ + public function isEnabled(): bool + { + return $this->data['enabled']; + } + + /** + * Gets the user. + */ + public function getUser(): string + { + return $this->data['user']; + } + + /** + * Gets the roles of the user. + */ + public function getRoles(): array|Data + { + return $this->data['roles']; + } + + /** + * Gets the inherited roles of the user. + */ + public function getInheritedRoles(): array|Data + { + return $this->data['inherited_roles']; + } + + /** + * Checks if the data contains information about inherited roles. Still the inherited + * roles can be an empty array. + */ + public function supportsRoleHierarchy(): bool + { + return $this->data['supports_role_hierarchy']; + } + + /** + * Checks if the user is authenticated or not. + */ + public function isAuthenticated(): bool + { + return $this->data['authenticated']; + } + + public function isImpersonated(): bool + { + return $this->data['impersonated']; + } + + public function getImpersonatorUser(): ?string + { + return $this->data['impersonator_user']; + } + + public function getImpersonationExitPath(): ?string + { + return $this->data['impersonation_exit_path']; + } + + /** + * Get the class name of the security token. + */ + public function getTokenClass(): string|Data|null + { + return $this->data['token_class']; + } + + /** + * Get the full security token class as Data object. + */ + public function getToken(): ?Data + { + return $this->data['token']; + } + + /** + * Get the logout URL. + */ + public function getLogoutUrl(): ?string + { + return $this->data['logout_url']; + } + + /** + * Returns the FQCN of the security voters enabled in the application. + * + * @return string[]|Data + */ + public function getVoters(): array|Data + { + return $this->data['voters']; + } + + /** + * Returns the strategy configured for the security voters. + */ + public function getVoterStrategy(): string + { + return $this->data['voter_strategy']; + } + + /** + * Returns the log of the security decisions made by the access decision manager. + */ + public function getAccessDecisionLog(): array|Data + { + return $this->data['access_decision_log']; + } + + /** + * Returns the configuration of the current firewall context. + */ + public function getFirewall(): array|Data|null + { + return $this->data['firewall']; + } + + public function getListeners(): array|Data + { + return $this->data['listeners']; + } + + public function getAuthenticators(): array|Data + { + return $this->data['authenticators']; + } + + public function getName(): string + { + return 'security'; + } +} diff --git a/vendor/symfony/security-bundle/Debug/TraceableFirewallListener.php b/vendor/symfony/security-bundle/Debug/TraceableFirewallListener.php new file mode 100644 index 0000000..1680500 --- /dev/null +++ b/vendor/symfony/security-bundle/Debug/TraceableFirewallListener.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Debug; + +use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; +use Symfony\Bundle\SecurityBundle\Security\FirewallContext; +use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Firewall collecting called security listeners and authenticators. + * + * @author Robin Chalas + */ +final class TraceableFirewallListener extends FirewallListener implements ResetInterface +{ + private array $wrappedListeners = []; + private array $authenticatorsInfo = []; + + public function getWrappedListeners(): array + { + return $this->wrappedListeners; + } + + public function getAuthenticatorsInfo(): array + { + return $this->authenticatorsInfo; + } + + public function reset(): void + { + $this->wrappedListeners = []; + $this->authenticatorsInfo = []; + } + + protected function callListeners(RequestEvent $event, iterable $listeners): void + { + $wrappedListeners = []; + $wrappedLazyListeners = []; + $authenticatorManagerListener = null; + + foreach ($listeners as $listener) { + if ($listener instanceof LazyFirewallContext) { + \Closure::bind(function () use (&$wrappedLazyListeners, &$wrappedListeners, &$authenticatorManagerListener) { + $listeners = []; + foreach ($this->listeners as $listener) { + if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) { + $authenticatorManagerListener = $listener; + } + if ($listener instanceof FirewallListenerInterface) { + $listener = new WrappedLazyListener($listener); + $listeners[] = $listener; + $wrappedLazyListeners[] = $listener; + } else { + $listeners[] = function (RequestEvent $event) use ($listener, &$wrappedListeners) { + $wrappedListener = new WrappedListener($listener); + $wrappedListener($event); + $wrappedListeners[] = $wrappedListener->getInfo(); + }; + } + } + $this->listeners = $listeners; + }, $listener, FirewallContext::class)(); + + $listener($event); + } else { + $wrappedListener = $listener instanceof FirewallListenerInterface ? new WrappedLazyListener($listener) : new WrappedListener($listener); + $wrappedListener($event); + $wrappedListeners[] = $wrappedListener->getInfo(); + if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) { + $authenticatorManagerListener = $listener; + } + } + + if ($event->hasResponse()) { + break; + } + } + + if ($wrappedLazyListeners) { + foreach ($wrappedLazyListeners as $lazyListener) { + $this->wrappedListeners[] = $lazyListener->getInfo(); + } + } + + $this->wrappedListeners = array_merge($this->wrappedListeners, $wrappedListeners); + + if ($authenticatorManagerListener) { + $this->authenticatorsInfo = $authenticatorManagerListener->getAuthenticatorsInfo(); + } + } +} diff --git a/vendor/symfony/security-bundle/Debug/TraceableListenerTrait.php b/vendor/symfony/security-bundle/Debug/TraceableListenerTrait.php new file mode 100644 index 0000000..0c4ff9e --- /dev/null +++ b/vendor/symfony/security-bundle/Debug/TraceableListenerTrait.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Debug; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; +use Symfony\Component\VarDumper\Caster\ClassStub; + +/** + * @author Robin Chalas + * + * @internal + */ +trait TraceableListenerTrait +{ + private ?Response $response = null; + private mixed $listener; + private ?float $time = null; + private object $stub; + + /** + * Proxies all method calls to the original listener. + */ + public function __call(string $method, array $arguments): mixed + { + return $this->listener->{$method}(...$arguments); + } + + public function getWrappedListener(): mixed + { + return $this->listener; + } + + public function getInfo(): array + { + return [ + 'response' => $this->response, + 'time' => $this->time, + 'stub' => $this->stub ??= ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener), + ]; + } +} diff --git a/vendor/symfony/security-bundle/Debug/WrappedLazyListener.php b/vendor/symfony/security-bundle/Debug/WrappedLazyListener.php new file mode 100644 index 0000000..55c70ec --- /dev/null +++ b/vendor/symfony/security-bundle/Debug/WrappedLazyListener.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Exception\LazyResponseException; +use Symfony\Component\Security\Http\Firewall\AbstractListener; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; + +/** + * Wraps a lazy security listener. + * + * @author Robin Chalas + * + * @internal + */ +final class WrappedLazyListener extends AbstractListener +{ + use TraceableListenerTrait; + + public function __construct(FirewallListenerInterface $listener) + { + $this->listener = $listener; + } + + public function supports(Request $request): ?bool + { + return $this->listener->supports($request); + } + + public function authenticate(RequestEvent $event): void + { + $startTime = microtime(true); + + try { + $this->listener->authenticate($event); + } catch (LazyResponseException $e) { + $this->response = $e->getResponse(); + + throw $e; + } finally { + $this->time = microtime(true) - $startTime; + } + + $this->response = $event->getResponse(); + } +} diff --git a/vendor/symfony/security-bundle/Debug/WrappedListener.php b/vendor/symfony/security-bundle/Debug/WrappedListener.php new file mode 100644 index 0000000..7a39419 --- /dev/null +++ b/vendor/symfony/security-bundle/Debug/WrappedListener.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Debug; + +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * Wraps a security listener for calls record. + * + * @author Robin Chalas + * + * @internal + */ +final class WrappedListener +{ + use TraceableListenerTrait; + + /** + * @param callable(RequestEvent):void $listener + */ + public function __construct(callable $listener) + { + $this->listener = $listener; + } + + public function __invoke(RequestEvent $event): void + { + $startTime = microtime(true); + ($this->listener)($event); + $this->time = microtime(true) - $startTime; + $this->response = $event->getResponse(); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php new file mode 100644 index 0000000..1dd53d5 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers the expression language providers. + * + * @author Fabien Potencier + */ +class AddExpressionLanguageProvidersPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if ($container->has('security.expression_language')) { + $definition = $container->findDefinition('security.expression_language'); + foreach ($container->findTaggedServiceIds('security.expression_language_provider', true) as $id => $attributes) { + $definition->addMethodCall('registerProvider', [new Reference($id)]); + } + } + + if (!$container->hasDefinition('cache.system')) { + $container->removeDefinition('cache.security_expression_language'); + $container->removeDefinition('cache.security_is_granted_attribute_expression_language'); + } + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddSecurityVotersPass.php new file mode 100644 index 0000000..1664f8e --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddSecurityVotersPass.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +/** + * Adds all configured security voters to the access decision manager. + * + * @author Johannes M. Schmitt + */ +class AddSecurityVotersPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('security.access.decision_manager')) { + return; + } + + $voters = $this->findAndSortTaggedServices('security.voter', $container); + if (!$voters) { + throw new LogicException('No security voters found. You need to tag at least one with "security.voter".'); + } + + $debug = $container->getParameter('kernel.debug'); + $voterServices = []; + foreach ($voters as $voter) { + $voterServiceId = (string) $voter; + $definition = $container->getDefinition($voterServiceId); + + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (!is_a($class, VoterInterface::class, true)) { + throw new LogicException(sprintf('"%s" must implement the "%s" when used as a voter.', $class, VoterInterface::class)); + } + + if ($debug) { + $voterServices[] = new Reference($debugVoterServiceId = '.debug.security.voter.'.$voterServiceId); + + $container + ->register($debugVoterServiceId, TraceableVoter::class) + ->addArgument($voter) + ->addArgument(new Reference('event_dispatcher')); + } else { + $voterServices[] = $voter; + } + } + + $container->getDefinition('security.access.decision_manager') + ->replaceArgument(0, new IteratorArgument($voterServices)); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php new file mode 100644 index 0000000..8bab747 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Uses the session domain to restrict allowed redirection targets. + * + * @author Nicolas Grekas + */ +class AddSessionDomainConstraintPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) { + return; + } + + $sessionOptions = $container->getParameter('session.storage.options'); + $domainRegexp = empty($sessionOptions['cookie_domain']) ? '%%s' : sprintf('(?:%%%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.'))); + + if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) { + $secureDomainRegexp = sprintf('{^https://%s$}i', $domainRegexp); + $domainRegexp = 'https?://'.$domainRegexp; + } else { + $secureDomainRegexp = null; + $domainRegexp = (empty($sessionOptions['cookie_secure']) ? 'https?://' : 'https://').$domainRegexp; + } + + $container->findDefinition('security.http_utils') + ->addArgument(sprintf('{^%s$}i', $domainRegexp)) + ->addArgument($secureDomainRegexp); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php new file mode 100644 index 0000000..465bdbe --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Cleans up the remember me verifier cache if cache is missing. + * + * @author Jordi Boggiano + */ +class CleanRememberMeVerifierPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('cache.system')) { + $container->removeDefinition('cache.security_token_verifier'); + } + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php new file mode 100644 index 0000000..d786317 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; + +/** + * @author Mathieu Lechat + */ +class MakeFirewallsEventDispatcherTraceablePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { + return; + } + + if (!$container->getParameter('kernel.debug') || !$container->has('debug.stopwatch')) { + return; + } + + $dispatchersId = []; + + foreach ($container->getParameter('security.firewalls') as $firewallName) { + $dispatcherId = 'security.event_dispatcher.'.$firewallName; + + if (!$container->has($dispatcherId)) { + continue; + } + + $dispatchersId[$dispatcherId] = 'debug.'.$dispatcherId; + + $container->register($dispatchersId[$dispatcherId], TraceableEventDispatcher::class) + ->setDecoratedService($dispatcherId) + ->setArguments([ + new Reference($dispatchersId[$dispatcherId].'.inner'), + new Reference('debug.stopwatch'), + new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), + new Reference('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]) + ->addTag('monolog.logger', ['channel' => 'event']) + ->addTag('kernel.reset', ['method' => 'reset']); + } + + foreach (['kernel.event_subscriber', 'kernel.event_listener'] as $tagName) { + foreach ($container->findTaggedServiceIds($tagName) as $taggedServiceId => $tags) { + $taggedServiceDefinition = $container->findDefinition($taggedServiceId); + $taggedServiceDefinition->clearTag($tagName); + + foreach ($tags as $tag) { + if ($dispatcherId = $tag['dispatcher'] ?? null) { + $tag['dispatcher'] = $dispatchersId[$dispatcherId] ?? $dispatcherId; + } + $taggedServiceDefinition->addTag($tagName, $tag); + } + } + } + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php new file mode 100644 index 0000000..1d2c0f8 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; +use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener; +use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener; +use Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener; + +/** + * @author Christian Flothmann + * @author Wouter de Jong + * + * @internal + */ +class RegisterCsrfFeaturesPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $this->registerCsrfProtectionListener($container); + $this->registerLogoutHandler($container); + } + + private function registerCsrfProtectionListener(ContainerBuilder $container): void + { + if (!$container->hasDefinition('cache.system')) { + $container->removeDefinition('cache.security_is_csrf_token_valid_attribute_expression_language'); + } + + if (!$container->has('security.authenticator.manager') || !$container->has('security.csrf.token_manager')) { + return; + } + + $container->register('security.listener.csrf_protection', CsrfProtectionListener::class) + ->addArgument(new Reference('security.csrf.token_manager')) + ->addTag('kernel.event_subscriber'); + + $container->register('controller.is_csrf_token_valid_attribute_listener', IsCsrfTokenValidAttributeListener::class) + ->addArgument(new Reference('security.csrf.token_manager')) + ->addArgument(new Reference('security.is_csrf_token_valid_attribute_expression_language', ContainerInterface::NULL_ON_INVALID_REFERENCE)) + ->addTag('kernel.event_subscriber'); + } + + protected function registerLogoutHandler(ContainerBuilder $container): void + { + if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) { + return; + } + + $csrfTokenStorage = $container->findDefinition('security.csrf.token_storage'); + $csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass()); + + if (!is_subclass_of($csrfTokenStorageClass, ClearableTokenStorageInterface::class)) { + return; + } + + $container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class) + ->addArgument(new Reference('security.csrf.token_storage')) + ->addTag('kernel.event_subscriber'); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterEntryPointPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterEntryPointPass.php new file mode 100644 index 0000000..4dc4c4c --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterEntryPointPass.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; + +/** + * @author Wouter de Jong + */ +class RegisterEntryPointPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('security.firewalls')) { + return; + } + + $firewalls = $container->getParameter('security.firewalls'); + foreach ($firewalls as $firewallName) { + if (!$container->hasDefinition('security.authenticator.manager.'.$firewallName) || !$container->hasParameter('security.'.$firewallName.'._indexed_authenticators')) { + continue; + } + + $entryPoints = []; + $indexedAuthenticators = $container->getParameter('security.'.$firewallName.'._indexed_authenticators'); + // this is a compile-only parameter, removing it cleans up space and avoids unintended usage + $container->getParameterBag()->remove('security.'.$firewallName.'._indexed_authenticators'); + foreach ($indexedAuthenticators as $key => $authenticatorId) { + if (!$container->has($authenticatorId)) { + continue; + } + + // because this pass runs before ResolveChildDefinitionPass, child definitions didn't inherit the parent class yet + $definition = $container->findDefinition($authenticatorId); + while (!($authenticatorClass = $definition->getClass()) && $definition instanceof ChildDefinition) { + $definition = $container->findDefinition($definition->getParent()); + } + + if (is_a($authenticatorClass, AuthenticationEntryPointInterface::class, true)) { + $entryPoints[$key] = $authenticatorId; + } + } + + if (!$entryPoints) { + continue; + } + + $config = $container->getDefinition('security.firewall.map.config.'.$firewallName); + $configuredEntryPoint = $config->getArgument(7); + + if (null !== $configuredEntryPoint) { + // allow entry points to be configured by authenticator key (e.g. "http_basic") + $entryPoint = $entryPoints[$configuredEntryPoint] ?? $configuredEntryPoint; + } elseif (1 === \count($entryPoints)) { + $entryPoint = array_shift($entryPoints); + } else { + $entryPointNames = []; + foreach ($entryPoints as $key => $serviceId) { + $entryPointNames[] = is_numeric($key) ? $serviceId : $key; + } + + throw new InvalidConfigurationException(sprintf('Because you have multiple authenticators in firewall "%s", you need to set the "entry_point" key to one of your authenticators ("%s") or a service ID implementing "%s". The "entry_point" determines what should happen (e.g. redirect to "/login") when an anonymous user tries to access a protected page.', $firewallName, implode('", "', $entryPointNames), AuthenticationEntryPointInterface::class)); + } + + $config->replaceArgument(7, $entryPoint); + $container->getDefinition('security.exception_listener.'.$firewallName)->replaceArgument(4, new Reference($entryPoint)); + } + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php new file mode 100644 index 0000000..5d581aa --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; +use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\Event\LoginFailureEvent; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent; +use Symfony\Component\Security\Http\SecurityEvents; + +/** + * Makes sure all event listeners on the global dispatcher are also listening + * to events on the firewall-specific dispatchers. + * + * This compiler pass must be run after RegisterListenersPass of the + * EventDispatcher component. + * + * @author Wouter de Jong + * + * @internal + */ +class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface +{ + private const EVENT_BUBBLING_EVENTS = [ + CheckPassportEvent::class, + LoginFailureEvent::class, + LoginSuccessEvent::class, + LogoutEvent::class, + AuthenticationTokenCreatedEvent::class, + AuthenticationSuccessEvent::class, + InteractiveLoginEvent::class, + TokenDeauthenticatedEvent::class, + + // When events are registered by their name + AuthenticationEvents::AUTHENTICATION_SUCCESS, + SecurityEvents::INTERACTIVE_LOGIN, + ]; + + public function process(ContainerBuilder $container): void + { + if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { + return; + } + + $firewallDispatchers = []; + foreach ($container->getParameter('security.firewalls') as $firewallName) { + if (!$container->has('security.event_dispatcher.'.$firewallName)) { + continue; + } + + $firewallDispatchers[] = $container->findDefinition('security.event_dispatcher.'.$firewallName); + } + + $globalDispatcher = $container->findDefinition('event_dispatcher'); + foreach ($globalDispatcher->getMethodCalls() as $methodCall) { + if ('addListener' !== $methodCall[0]) { + continue; + } + + $methodCallArguments = $methodCall[1]; + if (!\in_array($methodCallArguments[0], self::EVENT_BUBBLING_EVENTS, true)) { + continue; + } + + foreach ($firewallDispatchers as $firewallDispatcher) { + $firewallDispatcher->addMethodCall('addListener', $methodCallArguments); + } + } + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php new file mode 100644 index 0000000..8221aa5 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; + +/** + * @author Wouter de Jong + * + * @internal + */ +class RegisterLdapLocatorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $definition = $container->setDefinition('security.ldap_locator', new Definition(ServiceLocator::class)); + + $locators = []; + foreach ($container->findTaggedServiceIds('ldap') as $serviceId => $tags) { + $locators[$serviceId] = new ServiceClosureArgument(new Reference($serviceId)); + } + + $definition->addArgument($locators); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php new file mode 100644 index 0000000..1d1e47c --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Monolog\Processor\ProcessorInterface; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; + +/** + * Injects the session tracker enabler in "security.context_listener" + binds "security.untracked_token_storage" to ProcessorInterface instances. + * + * @author Nicolas Grekas + * + * @internal + */ +class RegisterTokenUsageTrackingPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->has('security.untracked_token_storage')) { + return; + } + + $processorAutoconfiguration = $container->registerForAutoconfiguration(ProcessorInterface::class); + $processorAutoconfiguration->setBindings($processorAutoconfiguration->getBindings() + [ + TokenStorageInterface::class => new BoundArgument(new Reference('security.untracked_token_storage'), false), + ]); + + if (!$container->has('session.factory')) { + $container->setAlias('security.token_storage', 'security.untracked_token_storage')->setPublic(true); + $container->getDefinition('security.untracked_token_storage')->addTag('kernel.reset', ['method' => 'reset']); + } elseif ($container->hasDefinition('security.context_listener')) { + $tokenStorageClass = $container->getParameterBag()->resolveValue($container->findDefinition('security.token_storage')->getClass()); + + if (method_exists($tokenStorageClass, 'enableUsageTracking')) { + $container->getDefinition('security.context_listener') + ->setArgument(6, [new Reference('security.token_storage'), 'enableUsageTracking']); + } + } + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php new file mode 100644 index 0000000..4727e62 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Replaces the DecoratedRememberMeHandler services with the real definition. + * + * @author Wouter de Jong + * + * @internal + */ +final class ReplaceDecoratedRememberMeHandlerPass implements CompilerPassInterface +{ + private const HANDLER_TAG = 'security.remember_me_handler'; + + public function process(ContainerBuilder $container): void + { + $handledFirewalls = []; + foreach ($container->findTaggedServiceIds(self::HANDLER_TAG) as $definitionId => $rememberMeHandlerTags) { + $definition = $container->findDefinition($definitionId); + if (DecoratedRememberMeHandler::class !== $definition->getClass()) { + continue; + } + + // get the actual custom remember me handler definition (passed to the decorator) + $realRememberMeHandler = $container->findDefinition((string) $definition->getArgument(0)); + if (null === $realRememberMeHandler) { + throw new \LogicException(sprintf('Invalid service definition for custom remember me handler; no service found with ID "%s".', (string) $definition->getArgument(0))); + } + + foreach ($rememberMeHandlerTags as $rememberMeHandlerTag) { + // some custom handlers may be used on multiple firewalls in the same application + if (\in_array($rememberMeHandlerTag['firewall'], $handledFirewalls, true)) { + continue; + } + + $rememberMeHandler = clone $realRememberMeHandler; + $rememberMeHandler->addTag(self::HANDLER_TAG, $rememberMeHandlerTag); + $container->setDefinition('security.authenticator.remember_me_handler.'.$rememberMeHandlerTag['firewall'], $rememberMeHandler); + + $handledFirewalls[] = $rememberMeHandlerTag['firewall']; + } + } + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Compiler/SortFirewallListenersPass.php b/vendor/symfony/security-bundle/DependencyInjection/Compiler/SortFirewallListenersPass.php new file mode 100644 index 0000000..7f0301a --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Compiler/SortFirewallListenersPass.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; + +/** + * Sorts firewall listeners based on the execution order provided by FirewallListenerInterface::getPriority(). + * + * @author Christian Scheb + */ +class SortFirewallListenersPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('security.firewalls')) { + return; + } + + foreach ($container->getParameter('security.firewalls') as $firewallName) { + $firewallContextDefinition = $container->getDefinition('security.firewall.map.context.'.$firewallName); + $this->sortFirewallContextListeners($firewallContextDefinition, $container); + } + } + + private function sortFirewallContextListeners(Definition $definition, ContainerBuilder $container): void + { + /** @var IteratorArgument $listenerIteratorArgument */ + $listenerIteratorArgument = $definition->getArgument(0); + $prioritiesByServiceId = $this->getListenerPriorities($listenerIteratorArgument, $container); + + $listeners = $listenerIteratorArgument->getValues(); + usort($listeners, fn (Reference $a, Reference $b) => $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]); + + $listenerIteratorArgument->setValues(array_values($listeners)); + } + + private function getListenerPriorities(IteratorArgument $listeners, ContainerBuilder $container): array + { + $priorities = []; + + foreach ($listeners->getValues() as $reference) { + $id = (string) $reference; + $def = $container->getDefinition($id); + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $def->getClass(); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + $priority = 0; + if ($r->isSubclassOf(FirewallListenerInterface::class)) { + $priority = $r->getMethod('getPriority')->invoke(null); + } + + $priorities[$id] = $priority; + } + + return $priorities; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/MainConfiguration.php b/vendor/symfony/security-bundle/DependencyInjection/MainConfiguration.php new file mode 100644 index 0000000..bfd96d7 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/MainConfiguration.php @@ -0,0 +1,463 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection; + +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; + +/** + * SecurityExtension configuration structure. + * + * @author Johannes M. Schmitt + */ +class MainConfiguration implements ConfigurationInterface +{ + /** @internal */ + public const STRATEGY_AFFIRMATIVE = 'affirmative'; + /** @internal */ + public const STRATEGY_CONSENSUS = 'consensus'; + /** @internal */ + public const STRATEGY_UNANIMOUS = 'unanimous'; + /** @internal */ + public const STRATEGY_PRIORITY = 'priority'; + + private array $factories; + private array $userProviderFactories; + + /** + * @param array $factories + */ + public function __construct(array $factories, array $userProviderFactories) + { + $this->factories = $factories; + $this->userProviderFactories = $userProviderFactories; + } + + /** + * Generates the configuration tree builder. + */ + public function getConfigTreeBuilder(): TreeBuilder + { + $tb = new TreeBuilder('security'); + $rootNode = $tb->getRootNode(); + + $rootNode + ->children() + ->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end() + ->enumNode('session_fixation_strategy') + ->values([SessionAuthenticationStrategy::NONE, SessionAuthenticationStrategy::MIGRATE, SessionAuthenticationStrategy::INVALIDATE]) + ->defaultValue(SessionAuthenticationStrategy::MIGRATE) + ->end() + ->booleanNode('hide_user_not_found')->defaultTrue()->end() + ->booleanNode('erase_credentials')->defaultTrue()->end() + ->arrayNode('access_decision_manager') + ->addDefaultsIfNotSet() + ->children() + ->enumNode('strategy') + ->values($this->getAccessDecisionStrategies()) + ->end() + ->scalarNode('service')->end() + ->scalarNode('strategy_service')->end() + ->booleanNode('allow_if_all_abstain')->defaultFalse()->end() + ->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end() + ->end() + ->validate() + ->ifTrue(fn ($v) => isset($v['strategy'], $v['service'])) + ->thenInvalid('"strategy" and "service" cannot be used together.') + ->end() + ->validate() + ->ifTrue(fn ($v) => isset($v['strategy'], $v['strategy_service'])) + ->thenInvalid('"strategy" and "strategy_service" cannot be used together.') + ->end() + ->validate() + ->ifTrue(fn ($v) => isset($v['service'], $v['strategy_service'])) + ->thenInvalid('"service" and "strategy_service" cannot be used together.') + ->end() + ->end() + ->end() + ; + + $this->addPasswordHashersSection($rootNode); + $this->addProvidersSection($rootNode); + $this->addFirewallsSection($rootNode, $this->factories); + $this->addAccessControlSection($rootNode); + $this->addRoleHierarchySection($rootNode); + + return $tb; + } + + private function addRoleHierarchySection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('role', 'role_hierarchy') + ->children() + ->arrayNode('role_hierarchy') + ->useAttributeAsKey('id') + ->prototype('array') + ->performNoDeepMerging() + ->beforeNormalization()->ifString()->then(fn ($v) => ['value' => $v])->end() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && isset($v['value'])) + ->then(fn ($v) => preg_split('/\s*,\s*/', $v['value'])) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ; + } + + private function addAccessControlSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('rule', 'access_control') + ->children() + ->arrayNode('access_control') + ->cannotBeOverwritten() + ->prototype('array') + ->fixXmlConfig('ip') + ->fixXmlConfig('method') + ->fixXmlConfig('attribute') + ->children() + ->scalarNode('request_matcher')->defaultNull()->end() + ->scalarNode('requires_channel')->defaultNull()->end() + ->scalarNode('path') + ->defaultNull() + ->info('use the urldecoded format') + ->example('^/path to resource/') + ->end() + ->scalarNode('host')->defaultNull()->end() + ->integerNode('port')->defaultNull()->end() + ->arrayNode('ips') + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('attributes') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->scalarNode('route')->defaultNull()->end() + ->arrayNode('methods') + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() + ->prototype('scalar')->end() + ->end() + ->scalarNode('allow_if')->defaultNull()->end() + ->end() + ->fixXmlConfig('role') + ->children() + ->arrayNode('roles') + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + /** + * @param array $factories + */ + private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories): void + { + $firewallNodeBuilder = $rootNode + ->fixXmlConfig('firewall') + ->children() + ->arrayNode('firewalls') + ->isRequired() + ->requiresAtLeastOneElement() + ->disallowNewKeysInSubsequentConfigs() + ->useAttributeAsKey('name') + ->prototype('array') + ->fixXmlConfig('required_badge') + ->children() + ; + + $firewallNodeBuilder + ->scalarNode('pattern') + ->beforeNormalization() + ->ifArray() + ->then(fn ($v) => sprintf('(?:%s)', implode('|', $v))) + ->end() + ->end() + ->scalarNode('host')->end() + ->arrayNode('methods') + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() + ->prototype('scalar')->end() + ->end() + ->booleanNode('security')->defaultTrue()->end() + ->scalarNode('user_checker') + ->defaultValue('security.user_checker') + ->treatNullLike('security.user_checker') + ->info('The UserChecker to use when authenticating users in this firewall.') + ->end() + ->scalarNode('request_matcher')->end() + ->scalarNode('access_denied_url')->end() + ->scalarNode('access_denied_handler')->end() + ->scalarNode('entry_point') + ->info(sprintf('An enabled authenticator name or a service id that implements "%s"', AuthenticationEntryPointInterface::class)) + ->end() + ->scalarNode('provider')->end() + ->booleanNode('stateless')->defaultFalse()->end() + ->booleanNode('lazy')->defaultFalse()->end() + ->scalarNode('context')->cannotBeEmpty()->end() + ->arrayNode('logout') + ->treatTrueLike([]) + ->canBeUnset() + ->beforeNormalization() + ->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_manager']) xor isset($v['enable_csrf']))) + ->then(function (array $v): array { + if (isset($v['csrf_token_manager'])) { + $v['enable_csrf'] = true; + } elseif ($v['enable_csrf']) { + $v['csrf_token_manager'] = 'security.csrf.token_manager'; + } + + return $v; + }) + ->end() + ->children() + ->booleanNode('enable_csrf')->defaultNull()->end() + ->scalarNode('csrf_token_id')->defaultValue('logout')->end() + ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end() + ->scalarNode('csrf_token_manager')->end() + ->scalarNode('path')->defaultValue('/logout')->end() + ->scalarNode('target')->defaultValue('/')->end() + ->booleanNode('invalidate_session')->defaultTrue()->end() + ->arrayNode('clear_site_data') + ->performNoDeepMerging() + ->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end() + ->enumPrototype() + ->values([ + '*', 'cache', 'cookies', 'storage', 'executionContexts', + ]) + ->end() + ->end() + ->end() + ->fixXmlConfig('delete_cookie') + ->children() + ->arrayNode('delete_cookies') + ->normalizeKeys(false) + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && \is_int(key($v))) + ->then(fn ($v) => array_map(fn ($v) => ['name' => $v], $v)) + ->end() + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->scalarNode('path')->defaultNull()->end() + ->scalarNode('domain')->defaultNull()->end() + ->scalarNode('secure')->defaultFalse()->end() + ->scalarNode('samesite')->defaultNull()->end() + ->scalarNode('partitioned')->defaultFalse()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('switch_user') + ->canBeUnset() + ->children() + ->scalarNode('provider')->end() + ->scalarNode('parameter')->defaultValue('_switch_user')->end() + ->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end() + ->scalarNode('target_route')->defaultValue(null)->end() + ->end() + ->end() + ->arrayNode('required_badges') + ->info('A list of badges that must be present on the authenticated passport.') + ->validate() + ->always() + ->then(function ($requiredBadges) { + return array_map(function ($requiredBadge) { + if (class_exists($requiredBadge)) { + return $requiredBadge; + } + + if (!str_contains($requiredBadge, '\\')) { + $fqcn = 'Symfony\Component\Security\Http\Authenticator\Passport\Badge\\'.$requiredBadge; + if (class_exists($fqcn)) { + return $fqcn; + } + } + + throw new InvalidConfigurationException(sprintf('Undefined security Badge class "%s" set in "security.firewall.required_badges".', $requiredBadge)); + }, $requiredBadges); + }) + ->end() + ->prototype('scalar')->end() + ->end() + ; + + $abstractFactoryKeys = []; + foreach ($factories as $factory) { + $name = str_replace('-', '_', $factory->getKey()); + $factoryNode = $firewallNodeBuilder->arrayNode($name) + ->canBeUnset() + ; + + if ($factory instanceof AbstractFactory) { + $abstractFactoryKeys[] = $name; + } + + $factory->addConfiguration($factoryNode); + } + + // check for unreachable check paths + $firewallNodeBuilder + ->end() + ->validate() + ->ifTrue(fn ($v) => true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher'])) + ->then(function ($firewall) use ($abstractFactoryKeys) { + foreach ($abstractFactoryKeys as $k) { + if (!isset($firewall[$k]['check_path'])) { + continue; + } + + if (str_contains($firewall[$k]['check_path'], '/') && !preg_match('#'.$firewall['pattern'].'#', $firewall[$k]['check_path'])) { + throw new \LogicException(sprintf('The check_path "%s" for login method "%s" is not matched by the firewall pattern "%s".', $firewall[$k]['check_path'], $k, $firewall['pattern'])); + } + } + + return $firewall; + }) + ->end() + ; + } + + private function addProvidersSection(ArrayNodeDefinition $rootNode): void + { + $providerNodeBuilder = $rootNode + ->fixXmlConfig('provider') + ->children() + ->arrayNode('providers') + ->example([ + 'my_memory_provider' => [ + 'memory' => [ + 'users' => [ + 'foo' => ['password' => 'foo', 'roles' => 'ROLE_USER'], + 'bar' => ['password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]'], + ], + ], + ], + 'my_entity_provider' => ['entity' => ['class' => 'SecurityBundle:User', 'property' => 'username']], + ]) + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array') + ; + + $providerNodeBuilder + ->children() + ->scalarNode('id')->end() + ->arrayNode('chain') + ->fixXmlConfig('provider') + ->children() + ->arrayNode('providers') + ->beforeNormalization() + ->ifString() + ->then(fn ($v) => preg_split('/\s*,\s*/', $v)) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ; + + foreach ($this->userProviderFactories as $factory) { + $name = str_replace('-', '_', $factory->getKey()); + $factoryNode = $providerNodeBuilder->children()->arrayNode($name)->canBeUnset(); + + $factory->addConfiguration($factoryNode); + } + + $providerNodeBuilder + ->validate() + ->ifTrue(fn ($v) => \count($v) > 1) + ->thenInvalid('You cannot set multiple provider types for the same provider') + ->end() + ->validate() + ->ifTrue(fn ($v) => 0 === \count($v)) + ->thenInvalid('You must set a provider definition for the provider.') + ->end() + ; + } + + private function addPasswordHashersSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('password_hasher') + ->children() + ->arrayNode('password_hashers') + ->example([ + 'App\Entity\User1' => 'auto', + 'App\Entity\User2' => [ + 'algorithm' => 'auto', + 'time_cost' => 8, + 'cost' => 13, + ], + ]) + ->requiresAtLeastOneElement() + ->useAttributeAsKey('class') + ->prototype('array') + ->canBeUnset() + ->performNoDeepMerging() + ->beforeNormalization()->ifString()->then(fn ($v) => ['algorithm' => $v])->end() + ->children() + ->scalarNode('algorithm') + ->cannotBeEmpty() + ->validate() + ->ifTrue(fn ($v) => !\is_string($v)) + ->thenInvalid('You must provide a string value.') + ->end() + ->end() + ->arrayNode('migrate_from') + ->prototype('scalar')->end() + ->beforeNormalization()->castToArray()->end() + ->end() + ->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end() + ->scalarNode('key_length')->defaultValue(40)->end() + ->booleanNode('ignore_case')->defaultFalse()->end() + ->booleanNode('encode_as_base64')->defaultTrue()->end() + ->scalarNode('iterations')->defaultValue(5000)->end() + ->integerNode('cost') + ->min(4) + ->max(31) + ->defaultNull() + ->end() + ->scalarNode('memory_cost')->defaultNull()->end() + ->scalarNode('time_cost')->defaultNull()->end() + ->scalarNode('id')->end() + ->end() + ->end() + ->end() + ->end(); + } + + private function getAccessDecisionStrategies(): array + { + return [ + self::STRATEGY_AFFIRMATIVE, + self::STRATEGY_CONSENSUS, + self::STRATEGY_UNANIMOUS, + self::STRATEGY_PRIORITY, + ]; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php new file mode 100644 index 0000000..a0c2ca0 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Http\AccessToken\Cas\Cas2Handler; + +class CasTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $container->setDefinition($id, new ChildDefinition('security.access_token_handler.cas')); + + $container + ->register('security.access_token_handler.cas', Cas2Handler::class) + ->setArguments([ + new Reference('request_stack'), + $config['validation_url'], + $config['prefix'], + $config['http_client'] ? new Reference($config['http_client']) : null, + ]); + } + + public function getKey(): string + { + return 'cas'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node + ->arrayNode($this->getKey()) + ->fixXmlConfig($this->getKey()) + ->children() + ->scalarNode('validation_url') + ->info('CAS server validation URL') + ->isRequired() + ->end() + ->scalarNode('prefix') + ->info('CAS prefix') + ->defaultValue('cas') + ->end() + ->scalarNode('http_client') + ->info('HTTP Client service') + ->defaultNull() + ->end() + ->end() + ->end(); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php new file mode 100644 index 0000000..a1b4181 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Jose\Component\Core\Algorithm; +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * Configures a token handler for decoding and validating an OIDC token. + */ +class OidcTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $tokenHandlerDefinition = $container->setDefinition($id, (new ChildDefinition('security.access_token_handler.oidc')) + ->replaceArgument(2, $config['audience']) + ->replaceArgument(3, $config['issuers']) + ->replaceArgument(4, $config['claim']) + ); + + if (!ContainerBuilder::willBeAvailable('web-token/jwt-library', Algorithm::class, ['symfony/security-bundle'])) { + throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-library" is not installed. Try running "composer require web-token/jwt-library".'); + } + + $tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature')) + ->replaceArgument(0, $config['algorithms'])); + + $tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset')) + ->replaceArgument(0, $config['keyset']) + ); + } + + public function getKey(): string + { + return 'oidc'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node + ->arrayNode($this->getKey()) + ->fixXmlConfig($this->getKey()) + ->validate() + ->ifTrue(static fn ($v) => !isset($v['algorithm']) && !isset($v['algorithms'])) + ->thenInvalid('You must set either "algorithm" or "algorithms".') + ->end() + ->validate() + ->ifTrue(static fn ($v) => !isset($v['key']) && !isset($v['keyset'])) + ->thenInvalid('You must set either "key" or "keyset".') + ->end() + ->beforeNormalization() + ->ifTrue(static fn ($v) => isset($v['algorithm']) && \is_string($v['algorithm'])) + ->then(static function ($v) { + if (isset($v['algorithms'])) { + throw new InvalidConfigurationException('You cannot use both "algorithm" and "algorithms" at the same time.'); + } + $v['algorithms'] = [$v['algorithm']]; + unset($v['algorithm']); + + return $v; + }) + ->end() + ->beforeNormalization() + ->ifTrue(static fn ($v) => isset($v['key']) && \is_string($v['key'])) + ->then(static function ($v) { + if (isset($v['keyset'])) { + throw new InvalidConfigurationException('You cannot use both "key" and "keyset" at the same time.'); + } + $v['keyset'] = sprintf('{"keys":[%s]}', $v['key']); + + return $v; + }) + ->end() + ->children() + ->scalarNode('claim') + ->info('Claim which contains the user identifier (e.g.: sub, email..).') + ->defaultValue('sub') + ->end() + ->scalarNode('audience') + ->info('Audience set in the token, for validation purpose.') + ->isRequired() + ->end() + ->arrayNode('issuers') + ->info('Issuers allowed to generate the token, for validation purpose.') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->arrayNode('algorithm') + ->info('Algorithm used to sign the token.') + ->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "algorithms" option instead.') + ->end() + ->arrayNode('algorithms') + ->info('Algorithms used to sign the token.') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->scalarNode('key') + ->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).') + ->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "keyset" option instead.') + ->end() + ->scalarNode('keyset') + ->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).') + ->isRequired() + ->end() + ->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php new file mode 100644 index 0000000..3e30aca --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * Configures a token handler for an OIDC server. + */ +class OidcUserInfoTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $clientDefinition = (new ChildDefinition('security.access_token_handler.oidc_user_info.http_client')) + ->replaceArgument(0, ['base_uri' => $config['base_uri']]); + + if (isset($config['client'])) { + $clientDefinition->setFactory([new Reference($config['client']), 'withOptions']); + } elseif (!ContainerBuilder::willBeAvailable('symfony/http-client', HttpClientInterface::class, ['symfony/security-bundle'])) { + throw new LogicException('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".'); + } + + $container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc_user_info')) + ->replaceArgument(0, $clientDefinition) + ->replaceArgument(2, $config['claim']); + } + + public function getKey(): string + { + return 'oidc_user_info'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node + ->arrayNode($this->getKey()) + ->fixXmlConfig($this->getKey()) + ->beforeNormalization() + ->ifString() + ->then(fn ($v) => ['claim' => 'sub', 'base_uri' => $v]) + ->end() + ->children() + ->scalarNode('base_uri') + ->info('Base URI of the userinfo endpoint on the OIDC server.') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('claim') + ->info('Claim which contains the user identifier (e.g. sub, email, etc.).') + ->defaultValue('sub') + ->cannotBeEmpty() + ->end() + ->scalarNode('client') + ->info('HttpClient service id to use to call the OIDC server.') + ->end() + ->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/ServiceTokenHandlerFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/ServiceTokenHandlerFactory.php new file mode 100644 index 0000000..77789db --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/ServiceTokenHandlerFactory.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Configures a token handler from a service id. + * + * @see \Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory\AccessTokenFactoryTest + */ +class ServiceTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $container->setDefinition($id, new ChildDefinition($config)); + } + + public function getKey(): string + { + return 'id'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node->scalarNode($this->getKey())->end(); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/TokenHandlerFactoryInterface.php b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/TokenHandlerFactoryInterface.php new file mode 100644 index 0000000..ccb6e09 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/AccessToken/TokenHandlerFactoryInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Allows creating configurable token handlers. + */ +interface TokenHandlerFactoryInterface +{ + /** + * Creates a generic token handler service. + */ + public function create(ContainerBuilder $container, string $id, array|string $config): void; + + /** + * Gets a generic token handler configuration key. + */ + public function getKey(): string; + + /** + * Adds a generic token handler configuration. + */ + public function addConfiguration(NodeBuilder $node): void; +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AbstractFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AbstractFactory.php new file mode 100644 index 0000000..dee0531 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Fabien Potencier + * @author Lukas Kahwe Smith + * @author Johannes M. Schmitt + */ +abstract class AbstractFactory implements AuthenticatorFactoryInterface +{ + protected array $options = [ + 'check_path' => '/login_check', + 'use_forward' => false, + 'login_path' => '/login', + ]; + + protected array $defaultSuccessHandlerOptions = [ + 'always_use_default_target_path' => false, + 'default_target_path' => '/', + 'login_path' => '/login', + 'target_path_parameter' => '_target_path', + 'use_referer' => false, + ]; + + protected array $defaultFailureHandlerOptions = [ + 'failure_path' => null, + 'failure_forward' => false, + 'login_path' => '/login', + 'failure_path_parameter' => '_failure_path', + ]; + + final public function addOption(string $name, mixed $default = null): void + { + $this->options[$name] = $default; + } + + public function addConfiguration(NodeDefinition $node): void + { + $builder = $node->children(); + + $builder + ->scalarNode('provider')->end() + ->booleanNode('remember_me')->defaultTrue()->end() + ->scalarNode('success_handler')->end() + ->scalarNode('failure_handler')->end() + ; + + foreach (array_merge($this->options, $this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) { + if (\is_bool($default)) { + $builder->booleanNode($name)->defaultValue($default); + } else { + $builder->scalarNode($name)->defaultValue($default); + } + } + } + + protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config): string + { + $successHandlerId = $this->getSuccessHandlerId($id); + $options = array_intersect_key($config, $this->defaultSuccessHandlerOptions); + + if (isset($config['success_handler'])) { + $successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.custom_success_handler')); + $successHandler->replaceArgument(0, new ChildDefinition($config['success_handler'])); + $successHandler->replaceArgument(1, $options); + $successHandler->replaceArgument(2, $id); + } else { + $successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.success_handler')); + $successHandler->addMethodCall('setOptions', [$options]); + $successHandler->addMethodCall('setFirewallName', [$id]); + } + + return $successHandlerId; + } + + protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config): string + { + $id = $this->getFailureHandlerId($id); + $options = array_intersect_key($config, $this->defaultFailureHandlerOptions); + + if (isset($config['failure_handler'])) { + $failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.custom_failure_handler')); + $failureHandler->replaceArgument(0, new ChildDefinition($config['failure_handler'])); + $failureHandler->replaceArgument(1, $options); + } else { + $failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.failure_handler')); + $failureHandler->addMethodCall('setOptions', [$options]); + } + + return $id; + } + + protected function getSuccessHandlerId(string $id): string + { + return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); + } + + protected function getFailureHandlerId(string $id): string + { + return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AccessTokenFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AccessTokenFactory.php new file mode 100644 index 0000000..5039552 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AccessTokenFactory.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\TokenHandlerFactoryInterface; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * AccessTokenFactory creates services for Access Token authentication. + * + * @author Florent Morselli + * + * @internal + */ +final class AccessTokenFactory extends AbstractFactory implements StatelessAuthenticatorFactoryInterface +{ + private const PRIORITY = -40; + + /** + * @param array $tokenHandlerFactories + */ + public function __construct(private readonly array $tokenHandlerFactories) + { + $this->options = []; + $this->defaultFailureHandlerOptions = []; + $this->defaultSuccessHandlerOptions = []; + } + + public function addConfiguration(NodeDefinition $node): void + { + parent::addConfiguration($node); + + $builder = $node->children(); + $builder + ->scalarNode('realm')->defaultNull()->end() + ->arrayNode('token_extractors') + ->fixXmlConfig('token_extractors') + ->beforeNormalization() + ->ifString() + ->then(fn ($v) => [$v]) + ->end() + ->cannotBeEmpty() + ->defaultValue([ + 'security.access_token_extractor.header', + ]) + ->scalarPrototype()->end() + ->end() + ; + + $tokenHandlerNodeBuilder = $builder + ->arrayNode('token_handler') + ->example([ + 'id' => 'App\Security\CustomTokenHandler', + ]) + + ->beforeNormalization() + ->ifString() + ->then(fn ($v) => ['id' => $v]) + ->end() + + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && 1 < \count($v)) + ->then(fn () => throw new InvalidConfigurationException('You cannot configure multiple token handlers.')) + ->end() + + // "isRequired" must be set otherwise the following custom validation is not called + ->isRequired() + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && !$v) + ->then(fn () => throw new InvalidConfigurationException('You must set a token handler.')) + ->end() + + ->children() + ; + + foreach ($this->tokenHandlerFactories as $factory) { + $factory->addConfiguration($tokenHandlerNodeBuilder); + } + + $tokenHandlerNodeBuilder->end(); + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'access_token'; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string + { + $successHandler = isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null; + $failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null; + $authenticatorId = sprintf('security.authenticator.access_token.%s', $firewallName); + $extractorId = $this->createExtractor($container, $firewallName, $config['token_extractors']); + $tokenHandlerId = $this->createTokenHandler($container, $firewallName, $config['token_handler'], $userProviderId); + + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token')) + ->replaceArgument(0, new Reference($tokenHandlerId)) + ->replaceArgument(1, new Reference($extractorId)) + ->replaceArgument(2, $userProviderId ? new Reference($userProviderId) : null) + ->replaceArgument(3, $successHandler) + ->replaceArgument(4, $failureHandler) + ->replaceArgument(5, $config['realm']) + ; + + return $authenticatorId; + } + + /** + * @param array $extractors + */ + private function createExtractor(ContainerBuilder $container, string $firewallName, array $extractors): string + { + $aliases = [ + 'query_string' => 'security.access_token_extractor.query_string', + 'request_body' => 'security.access_token_extractor.request_body', + 'header' => 'security.access_token_extractor.header', + ]; + $extractors = array_map(fn ($extractor) => $aliases[$extractor] ?? $extractor, $extractors); + + if (1 === \count($extractors)) { + return current($extractors); + } + $extractorId = sprintf('security.authenticator.access_token.chain_extractor.%s', $firewallName); + $container + ->setDefinition($extractorId, new ChildDefinition('security.authenticator.access_token.chain_extractor')) + ->replaceArgument(0, array_map(fn (string $extractorId): Reference => new Reference($extractorId), $extractors)) + ; + + return $extractorId; + } + + private function createTokenHandler(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string + { + $key = array_keys($config)[0]; + $id = sprintf('security.access_token_handler.%s', $firewallName); + + foreach ($this->tokenHandlerFactories as $factory) { + if ($key !== $factory->getKey()) { + continue; + } + + $factory->create($container, $id, $config[$key], $userProviderId); + } + + return $id; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php new file mode 100644 index 0000000..c9a3a02 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Wouter de Jong + */ +interface AuthenticatorFactoryInterface +{ + /** + * Defines the priority at which the authenticator is called. + */ + public function getPriority(): int; + + /** + * Defines the configuration key used to reference the provider + * in the firewall configuration. + */ + public function getKey(): string; + + public function addConfiguration(NodeDefinition $builder): void; + + /** + * Creates the authenticator service(s) for the provided configuration. + * + * @param array $config + * + * @return string|string[] The authenticator service ID(s) to be used by the firewall + */ + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string|array; +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php new file mode 100644 index 0000000..e443122 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Wouter de Jong + * + * @internal + */ +class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface +{ + public function getPriority(): int + { + return 0; + } + + public function getKey(): string + { + return 'custom_authenticators'; + } + + /** + * @param ArrayNodeDefinition $builder + */ + public function addConfiguration(NodeDefinition $builder): void + { + $builder + ->info('An array of service ids for all of your "authenticators"') + ->requiresAtLeastOneElement() + ->prototype('scalar')->end(); + + // get the parent array node builder ("firewalls") from inside the children builder + $factoryRootNode = $builder->end()->end(); + $factoryRootNode + ->fixXmlConfig('custom_authenticator') + ->validate() + ->ifTrue(fn ($v) => isset($v['custom_authenticators']) && empty($v['custom_authenticators'])) + ->then(function ($v) { + unset($v['custom_authenticators']); + + return $v; + }) + ->end() + ; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array + { + return $config; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php new file mode 100644 index 0000000..443ced6 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Can be implemented by a security factory to add a listener to the firewall. + * + * @author Christian Scheb + */ +interface FirewallListenerFactoryInterface +{ + /** + * Creates the firewall listener services for the provided configuration. + * + * @param array $config + * + * @return string[] The listener service IDs to be used by the firewall + */ + public function createListeners(ContainerBuilder $container, string $firewallName, array $config): array; +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginFactory.php new file mode 100644 index 0000000..fdcdb3a --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * FormLoginFactory creates services for form login authentication. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + * + * @internal + */ +class FormLoginFactory extends AbstractFactory +{ + public const PRIORITY = -30; + + public function __construct() + { + $this->addOption('username_parameter', '_username'); + $this->addOption('password_parameter', '_password'); + $this->addOption('csrf_parameter', '_csrf_token'); + $this->addOption('csrf_token_id', 'authenticate'); + $this->addOption('enable_csrf', false); + $this->addOption('post_only', true); + $this->addOption('form_only', false); + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'form-login'; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $authenticatorId = 'security.authenticator.form_login.'.$firewallName; + $options = array_intersect_key($config, $this->options); + $authenticator = $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login')) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config))) + ->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config))) + ->replaceArgument(4, $options); + + if ($options['use_forward'] ?? false) { + $authenticator->addMethodCall('setHttpKernel', [new Reference('http_kernel')]); + } + + return $authenticatorId; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php new file mode 100644 index 0000000..53a778c --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; + +/** + * FormLoginLdapFactory creates services for form login ldap authentication. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + * + * @internal + */ +class FormLoginLdapFactory extends FormLoginFactory +{ + use LdapFactoryTrait; + + public function addConfiguration(NodeDefinition $node): void + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->defaultValue('ldap')->end() + ->scalarNode('dn_string')->defaultValue('{user_identifier}')->end() + ->scalarNode('query_string')->end() + ->scalarNode('search_dn')->defaultValue('')->end() + ->scalarNode('search_password')->defaultValue('')->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicFactory.php new file mode 100644 index 0000000..45d7850 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicFactory.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * HttpBasicFactory creates services for HTTP basic authentication. + * + * @author Fabien Potencier + * + * @internal + */ +class HttpBasicFactory implements AuthenticatorFactoryInterface +{ + public const PRIORITY = -50; + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $authenticatorId = 'security.authenticator.http_basic.'.$firewallName; + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.http_basic')) + ->replaceArgument(0, $config['realm']) + ->replaceArgument(1, new Reference($userProviderId)); + + return $authenticatorId; + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'http-basic'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->children() + ->scalarNode('provider')->end() + ->scalarNode('realm')->defaultValue('Secured Area')->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php new file mode 100644 index 0000000..2889b6f --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Core\Exception\LogicException; + +/** + * HttpBasicFactory creates services for HTTP basic authentication. + * + * @author Fabien Potencier + * @author Grégoire Pineau + * @author Charles Sarrazin + * + * @internal + */ +class HttpBasicLdapFactory extends HttpBasicFactory +{ + use LdapFactoryTrait; + + public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array + { + $provider = 'security.authentication.provider.ldap_bind.'.$id; + $definition = $container + ->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind')) + ->replaceArgument(0, new Reference($userProvider)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) + ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference($config['service'])) + ->replaceArgument(4, $config['dn_string']) + ->replaceArgument(6, $config['search_dn']) + ->replaceArgument(7, $config['search_password']) + ; + + // entry point + $entryPointId = $defaultEntryPoint; + + if (null === $entryPointId) { + $entryPointId = 'security.authentication.basic_entry_point.'.$id; + $container + ->setDefinition($entryPointId, new ChildDefinition('security.authentication.basic_entry_point')) + ->addArgument($config['realm']); + } + + if (!empty($config['query_string'])) { + if ('' === $config['search_dn'] || '' === $config['search_password']) { + throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); + } + $definition->addMethodCall('setQueryString', [$config['query_string']]); + } + + // listener + $listenerId = 'security.authentication.listener.basic.'.$id; + $listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.basic')); + $listener->replaceArgument(2, $id); + $listener->replaceArgument(3, new Reference($entryPointId)); + + return [$provider, $listenerId, $entryPointId]; + } + + public function addConfiguration(NodeDefinition $node): void + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->defaultValue('ldap')->end() + ->scalarNode('dn_string')->defaultValue('{user_identifier}')->end() + ->scalarNode('query_string')->end() + ->scalarNode('search_dn')->defaultValue('')->end() + ->scalarNode('search_password')->defaultValue('')->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginFactory.php new file mode 100644 index 0000000..303ae2a --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginFactory.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * JsonLoginFactory creates services for JSON login authentication. + * + * @author Kévin Dunglas + * + * @internal + */ +class JsonLoginFactory extends AbstractFactory +{ + public const PRIORITY = -40; + + public function __construct() + { + $this->addOption('username_path', 'username'); + $this->addOption('password_path', 'password'); + $this->defaultFailureHandlerOptions = []; + $this->defaultSuccessHandlerOptions = []; + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'json-login'; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $authenticatorId = 'security.authenticator.json_login.'.$firewallName; + $options = array_intersect_key($config, $this->options); + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.json_login')) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(2, isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null) + ->replaceArgument(3, isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null) + ->replaceArgument(4, $options); + + return $authenticatorId; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php new file mode 100644 index 0000000..7e0ceb6 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; + +/** + * JsonLoginLdapFactory creates services for json login ldap authentication. + * + * @internal + */ +class JsonLoginLdapFactory extends JsonLoginFactory +{ + use LdapFactoryTrait; + + public function addConfiguration(NodeDefinition $node): void + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->defaultValue('ldap')->end() + ->scalarNode('dn_string')->defaultValue('{user_identifier}')->end() + ->scalarNode('query_string')->end() + ->scalarNode('search_dn')->defaultValue('')->end() + ->scalarNode('search_password')->defaultValue('')->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LdapFactoryTrait.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LdapFactoryTrait.php new file mode 100644 index 0000000..deccbb3 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LdapFactoryTrait.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Ldap\Security\CheckLdapCredentialsListener; +use Symfony\Component\Ldap\Security\LdapAuthenticator; + +/** + * A trait decorating the authenticator with LDAP functionality. + * + * @author Wouter de Jong + * + * @internal + */ +trait LdapFactoryTrait +{ + public function getKey(): string + { + return parent::getKey().'-ldap'; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $key = str_replace('-', '_', $this->getKey()); + $authenticatorId = parent::createAuthenticator($container, $firewallName, $config, $userProviderId); + + $container->setDefinition('security.listener.'.$key.'.'.$firewallName, new Definition(CheckLdapCredentialsListener::class)) + ->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName]) + ->addArgument(new Reference('security.ldap_locator')) + ; + + $ldapAuthenticatorId = 'security.authenticator.'.$key.'.'.$firewallName; + $definition = $container->setDefinition($ldapAuthenticatorId, new Definition(LdapAuthenticator::class)) + ->setArguments([ + new Reference($authenticatorId), + $config['service'], + $config['dn_string'], + $config['search_dn'], + $config['search_password'], + ]); + + if (!empty($config['query_string'])) { + if ('' === $config['search_dn'] || '' === $config['search_password']) { + throw new InvalidConfigurationException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); + } + + $definition->addArgument($config['query_string']); + } + + return $ldapAuthenticatorId; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginLinkFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginLinkFactory.php new file mode 100644 index 0000000..9a03a0f --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginLinkFactory.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; + +/** + * @internal + */ +class LoginLinkFactory extends AbstractFactory +{ + public const PRIORITY = -20; + + public function addConfiguration(NodeDefinition $node): void + { + /** @var NodeBuilder $builder */ + $builder = $node->fixXmlConfig('signature_property', 'signature_properties')->children(); + + $builder + ->scalarNode('check_route') + ->isRequired() + ->info('Route that will validate the login link - e.g. "app_login_link_verify".') + ->end() + ->scalarNode('check_post_only') + ->defaultFalse() + ->info('If true, only HTTP POST requests to "check_route" will be handled by the authenticator.') + ->end() + ->arrayNode('signature_properties') + ->isRequired() + ->prototype('scalar')->end() + ->requiresAtLeastOneElement() + ->info('An array of properties on your User that are used to sign the link. If any of these change, all existing links will become invalid.') + ->example(['email', 'password']) + ->end() + ->integerNode('lifetime') + ->defaultValue(600) + ->info('The lifetime of the login link in seconds.') + ->end() + ->integerNode('max_uses') + ->defaultNull() + ->info('Max number of times a login link can be used - null means unlimited within lifetime.') + ->end() + ->scalarNode('used_link_cache') + ->info('Cache service id used to expired links of max_uses is set.') + ->end() + ->scalarNode('success_handler') + ->info(sprintf('A service id that implements %s.', AuthenticationSuccessHandlerInterface::class)) + ->end() + ->scalarNode('failure_handler') + ->info(sprintf('A service id that implements %s.', AuthenticationFailureHandlerInterface::class)) + ->end() + ->scalarNode('provider') + ->info('The user provider to load users from.') + ->end() + ; + + foreach (array_merge($this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) { + if (\is_bool($default)) { + $builder->booleanNode($name)->defaultValue($default); + } else { + $builder->scalarNode($name)->defaultValue($default); + } + } + } + + public function getKey(): string + { + return 'login-link'; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + if (!$container->hasDefinition('security.authenticator.login_link')) { + $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../../Resources/config')); + $loader->load('security_authenticator_login_link.php'); + } + + if (null !== $config['max_uses'] && !isset($config['used_link_cache'])) { + $config['used_link_cache'] = 'security.authenticator.cache.expired_links'; + $defaultCacheDefinition = $container->getDefinition($config['used_link_cache']); + if (!$defaultCacheDefinition->hasTag('cache.pool')) { + $defaultCacheDefinition->addTag('cache.pool'); + } + } + + $expiredStorageId = null; + if (isset($config['used_link_cache'])) { + $expiredStorageId = 'security.authenticator.expired_login_link_storage.'.$firewallName; + $container + ->setDefinition($expiredStorageId, new ChildDefinition('security.authenticator.expired_login_link_storage')) + ->replaceArgument(0, new Reference($config['used_link_cache'])) + ->replaceArgument(1, $config['lifetime']); + } + + $signatureHasherId = 'security.authenticator.login_link_signature_hasher.'.$firewallName; + $container + ->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.abstract_login_link_signature_hasher')) + ->replaceArgument(1, $config['signature_properties']) + ->replaceArgument(3, $expiredStorageId ? new Reference($expiredStorageId) : null) + ->replaceArgument(4, $config['max_uses'] ?? null) + ; + + $linkerId = 'security.authenticator.login_link_handler.'.$firewallName; + $linkerOptions = [ + 'route_name' => $config['check_route'], + 'lifetime' => $config['lifetime'], + ]; + $container + ->setDefinition($linkerId, new ChildDefinition('security.authenticator.abstract_login_link_handler')) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(2, new Reference($signatureHasherId)) + ->replaceArgument(3, $linkerOptions) + ->addTag('security.authenticator.login_linker', ['firewall' => $firewallName]) + ; + + $authenticatorId = 'security.authenticator.login_link.'.$firewallName; + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.login_link')) + ->replaceArgument(0, new Reference($linkerId)) + ->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config))) + ->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config))) + ->replaceArgument(4, [ + 'check_route' => $config['check_route'], + 'check_post_only' => $config['check_post_only'], + ]); + + return $authenticatorId; + } + + public function getPriority(): int + { + return self::PRIORITY; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php new file mode 100644 index 0000000..bb96484 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface; +use Symfony\Component\Lock\LockInterface; +use Symfony\Component\RateLimiter\RateLimiterFactory; +use Symfony\Component\RateLimiter\Storage\CacheStorage; +use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter; + +/** + * @author Wouter de Jong + * + * @internal + */ +class LoginThrottlingFactory implements AuthenticatorFactoryInterface +{ + public function getPriority(): int + { + // this factory doesn't register any authenticators, this priority doesn't matter + return 0; + } + + public function getKey(): string + { + return 'login_throttling'; + } + + /** + * @param ArrayNodeDefinition $builder + */ + public function addConfiguration(NodeDefinition $builder): void + { + $builder + ->children() + ->scalarNode('limiter')->info(sprintf('A service id implementing "%s".', RequestRateLimiterInterface::class))->end() + ->integerNode('max_attempts')->defaultValue(5)->end() + ->scalarNode('interval')->defaultValue('1 minute')->end() + ->scalarNode('lock_factory')->info('The service ID of the lock factory used by the login rate limiter (or null to disable locking)')->defaultNull()->end() + ->end(); + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array + { + if (!class_exists(RateLimiterFactory::class)) { + throw new \LogicException('Login throttling requires the Rate Limiter component. Try running "composer require symfony/rate-limiter".'); + } + + if (!isset($config['limiter'])) { + $limiterOptions = [ + 'policy' => 'fixed_window', + 'limit' => $config['max_attempts'], + 'interval' => $config['interval'], + 'lock_factory' => $config['lock_factory'], + ]; + $this->registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions); + + $limiterOptions['limit'] = 5 * $config['max_attempts']; + $this->registerRateLimiter($container, $globalId = '_login_global_'.$firewallName, $limiterOptions); + + $container->register($config['limiter'] = 'security.login_throttling.'.$firewallName.'.limiter', DefaultLoginRateLimiter::class) + ->addArgument(new Reference('limiter.'.$globalId)) + ->addArgument(new Reference('limiter.'.$localId)) + ->addArgument(new Parameter('container.build_hash')) + ; + } + + $container + ->setDefinition('security.listener.login_throttling.'.$firewallName, new ChildDefinition('security.listener.login_throttling')) + ->replaceArgument(1, new Reference($config['limiter'])) + ->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName]); + + return []; + } + + private function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig): void + { + // default configuration (when used by other DI extensions) + $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; + + $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); + + if (null !== $limiterConfig['lock_factory']) { + if (!interface_exists(LockInterface::class)) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name)); + } + + $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); + } + unset($limiterConfig['lock_factory']); + + if (null === $storageId = $limiterConfig['storage_service'] ?? null) { + $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); + } + + $limiter->replaceArgument(1, new Reference($storageId)); + unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']); + + $limiterConfig['id'] = $name; + $limiter->replaceArgument(0, $limiterConfig); + + $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/RememberMeFactory.php new file mode 100644 index 0000000..d474e96 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; +use Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\Security\Core\Authentication\RememberMe\CacheTokenVerifier; + +/** + * @internal + */ +class RememberMeFactory implements AuthenticatorFactoryInterface, PrependExtensionInterface +{ + public const PRIORITY = -50; + + protected array $options = [ + 'name' => 'REMEMBERME', + 'lifetime' => 31536000, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'samesite' => null, + 'always_remember_me' => false, + 'remember_me_parameter' => '_remember_me', + ]; + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + if (!$container->hasDefinition('security.authenticator.remember_me')) { + $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../../Resources/config')); + $loader->load('security_authenticator_remember_me.php'); + } + + if ('auto' === $config['secure']) { + $config['secure'] = null; + } + + // create remember me handler (which manage the remember-me cookies) + $rememberMeHandlerId = 'security.authenticator.remember_me_handler.'.$firewallName; + if (isset($config['service']) && isset($config['token_provider'])) { + throw new InvalidConfigurationException(sprintf('You cannot use both "service" and "token_provider" in "security.firewalls.%s.remember_me".', $firewallName)); + } + + if (isset($config['service'])) { + $container->register($rememberMeHandlerId, DecoratedRememberMeHandler::class) + ->addArgument(new Reference($config['service'])) + ->addTag('security.remember_me_handler', ['firewall' => $firewallName]); + } elseif (isset($config['token_provider'])) { + $tokenProviderId = $this->createTokenProvider($container, $firewallName, $config['token_provider']); + $tokenVerifier = $this->createTokenVerifier($container, $firewallName, $config['token_verifier'] ?? null); + $container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.persistent_remember_me_handler')) + ->replaceArgument(0, new Reference($tokenProviderId)) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(3, $config) + ->replaceArgument(5, $tokenVerifier) + ->addTag('security.remember_me_handler', ['firewall' => $firewallName]); + } else { + $signatureHasherId = 'security.authenticator.remember_me_signature_hasher.'.$firewallName; + $container->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.remember_me_signature_hasher')) + ->replaceArgument(1, $config['signature_properties']) + ->replaceArgument(2, $config['secret']) + ; + + $container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.signature_remember_me_handler')) + ->replaceArgument(0, new Reference($signatureHasherId)) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(3, $config) + ->addTag('security.remember_me_handler', ['firewall' => $firewallName]); + } + + // create check remember me conditions listener (which checks if a remember-me cookie is supported and requested) + $rememberMeConditionsListenerId = 'security.listener.check_remember_me_conditions.'.$firewallName; + $container->setDefinition($rememberMeConditionsListenerId, new ChildDefinition('security.listener.check_remember_me_conditions')) + ->replaceArgument(0, array_intersect_key($config, ['always_remember_me' => true, 'remember_me_parameter' => true])) + ->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName]) + ; + + // create remember me listener (which executes the remember me services for other authenticators and logout) + $rememberMeListenerId = 'security.listener.remember_me.'.$firewallName; + $container->setDefinition($rememberMeListenerId, new ChildDefinition('security.listener.remember_me')) + ->replaceArgument(0, new Reference($rememberMeHandlerId)) + ->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName]) + ; + + // create remember me authenticator (which re-authenticates the user based on the remember-me cookie) + $authenticatorId = 'security.authenticator.remember_me.'.$firewallName; + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remember_me')) + ->replaceArgument(0, new Reference($rememberMeHandlerId)) + ->replaceArgument(3, $config['name'] ?? $this->options['name']) + ; + + return $authenticatorId; + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'remember-me'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $builder = $node + ->fixXmlConfig('user_provider') + ->children() + ; + + $builder + ->scalarNode('secret') + ->cannotBeEmpty() + ->defaultValue('%kernel.secret%') + ->end() + ->scalarNode('service')->end() + ->arrayNode('user_providers') + ->beforeNormalization() + ->ifString()->then(fn ($v) => [$v]) + ->end() + ->prototype('scalar')->end() + ->end() + ->booleanNode('catch_exceptions')->defaultTrue()->end() + ->arrayNode('signature_properties') + ->prototype('scalar')->end() + ->requiresAtLeastOneElement() + ->info('An array of properties on your User that are used to sign the remember-me cookie. If any of these change, all existing cookies will become invalid.') + ->example(['email', 'password']) + ->defaultValue(['password']) + ->end() + ->arrayNode('token_provider') + ->beforeNormalization() + ->ifString()->then(fn ($v) => ['service' => $v]) + ->end() + ->children() + ->scalarNode('service')->info('The service ID of a custom rememberme token provider.')->end() + ->arrayNode('doctrine') + ->canBeEnabled() + ->children() + ->scalarNode('connection')->defaultNull()->end() + ->end() + ->end() + ->end() + ->end() + ->scalarNode('token_verifier') + ->info('The service ID of a custom rememberme token verifier.') + ->end(); + + foreach ($this->options as $name => $value) { + if ('secure' === $name) { + $builder->enumNode($name)->values([true, false, 'auto'])->defaultValue('auto' === $value ? null : $value); + } elseif ('samesite' === $name) { + $builder->enumNode($name)->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultValue($value); + } elseif (\is_bool($value)) { + $builder->booleanNode($name)->defaultValue($value); + } elseif (\is_int($value)) { + $builder->integerNode($name)->defaultValue($value); + } else { + $builder->scalarNode($name)->defaultValue($value); + } + } + } + + private function createTokenProvider(ContainerBuilder $container, string $firewallName, array $config): string + { + $tokenProviderId = $config['service'] ?? false; + if ($config['doctrine']['enabled'] ?? false) { + if (!class_exists(DoctrineTokenProvider::class)) { + throw new InvalidConfigurationException('Cannot use the "doctrine" token provider for "remember_me" because the Doctrine Bridge is not installed. Try running "composer require symfony/doctrine-bridge".'); + } + + if (null === $config['doctrine']['connection']) { + $connectionId = 'database_connection'; + } else { + $connectionId = 'doctrine.dbal.'.$config['doctrine']['connection'].'_connection'; + } + + $tokenProviderId = 'security.remember_me.doctrine_token_provider.'.$firewallName; + $container->register($tokenProviderId, DoctrineTokenProvider::class) + ->addArgument(new Reference($connectionId)); + } + + if (!$tokenProviderId) { + throw new InvalidConfigurationException(sprintf('No token provider was set for firewall "%s". Either configure a service ID or set "remember_me.token_provider.doctrine" to true.', $firewallName)); + } + + return $tokenProviderId; + } + + private function createTokenVerifier(ContainerBuilder $container, string $firewallName, ?string $serviceId): Reference + { + if ($serviceId) { + return new Reference($serviceId); + } + + $tokenVerifierId = 'security.remember_me.token_verifier.'.$firewallName; + + $container->register($tokenVerifierId, CacheTokenVerifier::class) + ->addArgument(new Reference('cache.security_token_verifier', ContainerInterface::NULL_ON_INVALID_REFERENCE)) + ->addArgument(60) + ->addArgument('rememberme-'.$firewallName.'-stale-'); + + return new Reference($tokenVerifierId, ContainerInterface::NULL_ON_INVALID_REFERENCE); + } + + public function prepend(ContainerBuilder $container): void + { + $rememberMeSecureDefault = false; + $rememberMeSameSiteDefault = null; + + if (!isset($container->getExtensions()['framework'])) { + return; + } + + foreach ($container->getExtensionConfig('framework') as $config) { + if (isset($config['session']) && \is_array($config['session'])) { + $rememberMeSecureDefault = $config['session']['cookie_secure'] ?? $rememberMeSecureDefault; + $rememberMeSameSiteDefault = \array_key_exists('cookie_samesite', $config['session']) ? $config['session']['cookie_samesite'] : $rememberMeSameSiteDefault; + } + } + + $this->options['secure'] = $rememberMeSecureDefault; + $this->options['samesite'] = $rememberMeSameSiteDefault; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/RemoteUserFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/RemoteUserFactory.php new file mode 100644 index 0000000..97d5000 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/RemoteUserFactory.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * RemoteUserFactory creates services for REMOTE_USER based authentication. + * + * @author Fabien Potencier + * @author Maxime Douailin + * + * @internal + */ +class RemoteUserFactory implements AuthenticatorFactoryInterface +{ + public const PRIORITY = -10; + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $authenticatorId = 'security.authenticator.remote_user.'.$firewallName; + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remote_user')) + ->replaceArgument(0, new Reference($userProviderId)) + ->replaceArgument(2, $firewallName) + ->replaceArgument(3, $config['user']) + ; + + return $authenticatorId; + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'remote-user'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->children() + ->scalarNode('provider')->end() + ->scalarNode('user')->defaultValue('REMOTE_USER')->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php new file mode 100644 index 0000000..4d53601 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Stateless authenticators are authenticators that can work without a user provider. + * + * This situation can only occur in stateless firewalls, as statefull firewalls + * need the user provider to refresh the user in each subsequent request. A + * stateless authenticator can be used on both stateless and statefull authenticators. + * + * @author Wouter de Jong + */ +interface StatelessAuthenticatorFactoryInterface extends AuthenticatorFactoryInterface +{ + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string|array; +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/X509Factory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/X509Factory.php new file mode 100644 index 0000000..2d28e2b --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/Factory/X509Factory.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * X509Factory creates services for X509 certificate authentication. + * + * @author Fabien Potencier + * + * @internal + */ +class X509Factory implements AuthenticatorFactoryInterface +{ + public const PRIORITY = -10; + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $authenticatorId = 'security.authenticator.x509.'.$firewallName; + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.x509')) + ->replaceArgument(0, new Reference($userProviderId)) + ->replaceArgument(2, $firewallName) + ->replaceArgument(3, $config['user']) + ->replaceArgument(4, $config['credentials']) + ->replaceArgument(6, $config['user_identifier']) + ; + + return $authenticatorId; + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'x509'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->children() + ->scalarNode('provider')->end() + ->scalarNode('user')->defaultValue('SSL_CLIENT_S_DN_Email')->end() + ->scalarNode('credentials')->defaultValue('SSL_CLIENT_S_DN')->end() + ->scalarNode('user_identifier')->defaultValue('emailAddress')->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php new file mode 100644 index 0000000..0521f90 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Parameter; + +/** + * InMemoryFactory creates services for the memory provider. + * + * @author Fabien Potencier + * @author Christophe Coevoet + */ +class InMemoryFactory implements UserProviderFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array $config): void + { + $definition = $container->setDefinition($id, new ChildDefinition('security.user.provider.in_memory')); + $defaultPassword = new Parameter('container.build_id'); + $users = []; + + foreach ($config['users'] as $username => $user) { + $users[$username] = ['password' => null !== $user['password'] ? (string) $user['password'] : $defaultPassword, 'roles' => $user['roles']]; + } + + $definition->addArgument($users); + } + + public function getKey(): string + { + return 'memory'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->fixXmlConfig('user') + ->children() + ->arrayNode('users') + ->useAttributeAsKey('identifier') + ->normalizeKeys(false) + ->prototype('array') + ->children() + ->scalarNode('password')->defaultNull()->end() + ->arrayNode('roles') + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/LdapFactory.php b/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/LdapFactory.php new file mode 100644 index 0000000..b8d442f --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/LdapFactory.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * LdapFactory creates services for Ldap user provider. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + */ +class LdapFactory implements UserProviderFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array $config): void + { + $container + ->setDefinition($id, new ChildDefinition('security.user.provider.ldap')) + ->replaceArgument(0, new Reference($config['service'])) + ->replaceArgument(1, $config['base_dn']) + ->replaceArgument(2, $config['search_dn']) + ->replaceArgument(3, $config['search_password']) + ->replaceArgument(4, $config['default_roles']) + ->replaceArgument(5, $config['uid_key']) + ->replaceArgument(6, $config['filter']) + ->replaceArgument(7, $config['password_attribute']) + ->replaceArgument(8, $config['extra_fields']) + ; + } + + public function getKey(): string + { + return 'ldap'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->fixXmlConfig('extra_field') + ->fixXmlConfig('default_role') + ->children() + ->scalarNode('service')->isRequired()->cannotBeEmpty()->defaultValue('ldap')->end() + ->scalarNode('base_dn')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('search_dn')->defaultNull()->end() + ->scalarNode('search_password')->defaultNull()->end() + ->arrayNode('extra_fields') + ->prototype('scalar')->end() + ->end() + ->arrayNode('default_roles') + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->scalarNode('uid_key')->defaultValue('sAMAccountName')->end() + ->scalarNode('filter')->defaultValue('({uid_key}={user_identifier})')->end() + ->scalarNode('password_attribute')->defaultNull()->end() + ->end() + ; + } +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php b/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php new file mode 100644 index 0000000..d5a15ac --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * UserProviderFactoryInterface is the interface for all user provider factories. + * + * @author Fabien Potencier + * @author Christophe Coevoet + */ +interface UserProviderFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array $config): void; + + public function getKey(): string; + + public function addConfiguration(NodeDefinition $builder): void; +} diff --git a/vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php b/vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php new file mode 100644 index 0000000..aafd975 --- /dev/null +++ b/vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php @@ -0,0 +1,1105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection; + +use Symfony\Bridge\Twig\Extension\LogoutUrlExtension; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\StatelessAuthenticatorFactoryInterface; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\Application; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Form\Extension\PasswordHasher\PasswordHasherExtension; +use Symfony\Component\HttpFoundation\ChainRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\IpsRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PortRequestMatcher; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; +use Symfony\Component\Routing\Loader\ContainerLoader; +use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; +use Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy; +use Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy; +use Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +use Symfony\Component\Security\Core\User\ChainUserChecker; +use Symfony\Component\Security\Core\User\ChainUserProvider; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Flex\Command\InstallRecipesCommand; + +/** + * SecurityExtension. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class SecurityExtension extends Extension implements PrependExtensionInterface +{ + private array $requestMatchers = []; + private array $expressions = []; + private array $contextListeners = []; + /** @var list */ + private array $factories = []; + /** @var AuthenticatorFactoryInterface[] */ + private array $sortedFactories = []; + private array $userProviderFactories = []; + + public function prepend(ContainerBuilder $container): void + { + foreach ($this->getSortedFactories() as $factory) { + if ($factory instanceof PrependExtensionInterface) { + $factory->prepend($container); + } + } + } + + public function load(array $configs, ContainerBuilder $container): void + { + if (!array_filter($configs)) { + $hint = class_exists(InstallRecipesCommand::class) ? 'Try running "composer symfony:recipes:install symfony/security-bundle".' : 'Please define your settings for the "security" config section.'; + + throw new InvalidConfigurationException('The SecurityBundle is enabled but is not configured. '.$hint); + } + + $mainConfig = $this->getConfiguration($configs, $container); + + $config = $this->processConfiguration($mainConfig, $configs); + + // load services + $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); + + $loader->load('security.php'); + $loader->load('password_hasher.php'); + $loader->load('security_listeners.php'); + $loader->load('security_authenticator.php'); + $loader->load('security_authenticator_access_token.php'); + + if ($container::willBeAvailable('symfony/twig-bridge', LogoutUrlExtension::class, ['symfony/security-bundle'])) { + $loader->load('templating_twig.php'); + } + + $loader->load('collectors.php'); + + if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) { + $loader->load('security_debug.php'); + } + + if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'])) { + $container->removeDefinition('security.expression_language'); + $container->removeDefinition('security.access.expression_voter'); + $container->removeDefinition('security.is_granted_attribute_expression_language'); + $container->removeDefinition('security.is_csrf_token_valid_attribute_expression_language'); + } + + if (!class_exists(PasswordHasherExtension::class)) { + $container->removeDefinition('form.listener.password_hasher'); + $container->removeDefinition('form.type_extension.form.password_hasher'); + $container->removeDefinition('form.type_extension.password.password_hasher'); + } + + // set some global scalars + $container->setParameter('security.access.denied_url', $config['access_denied_url']); + $container->setParameter('security.authentication.manager.erase_credentials', $config['erase_credentials']); + $container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_strategy']); + + if (isset($config['access_decision_manager']['service'])) { + $container->setAlias('security.access.decision_manager', $config['access_decision_manager']['service']); + } elseif (isset($config['access_decision_manager']['strategy_service'])) { + $container + ->getDefinition('security.access.decision_manager') + ->addArgument(new Reference($config['access_decision_manager']['strategy_service'])); + } else { + $container + ->getDefinition('security.access.decision_manager') + ->addArgument($this->createStrategyDefinition( + $config['access_decision_manager']['strategy'] ?? MainConfiguration::STRATEGY_AFFIRMATIVE, + $config['access_decision_manager']['allow_if_all_abstain'], + $config['access_decision_manager']['allow_if_equal_granted_denied'] + )); + } + + $container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']); + + if (class_exists(Application::class)) { + $loader->load('debug_console.php'); + } + + $this->createFirewalls($config, $container); + + if ($container::willBeAvailable('symfony/routing', ContainerLoader::class, ['symfony/security-bundle'])) { + $this->createLogoutUrisParameter($config['firewalls'] ?? [], $container); + } else { + $container->removeDefinition('security.route_loader.logout'); + } + + $this->createAuthorization($config, $container); + $this->createRoleHierarchy($config, $container); + + if ($config['password_hashers']) { + $this->createHashers($config['password_hashers'], $container); + } + + if (class_exists(Application::class)) { + $loader->load('console.php'); + + $container->getDefinition('security.command.user_password_hash')->replaceArgument(1, array_keys($config['password_hashers'])); + } + + $container->registerForAutoconfiguration(VoterInterface::class) + ->addTag('security.voter'); + } + + private function createStrategyDefinition(string $strategy, bool $allowIfAllAbstainDecisions, bool $allowIfEqualGrantedDeniedDecisions): Definition + { + return match ($strategy) { + MainConfiguration::STRATEGY_AFFIRMATIVE => new Definition(AffirmativeStrategy::class, [$allowIfAllAbstainDecisions]), + MainConfiguration::STRATEGY_CONSENSUS => new Definition(ConsensusStrategy::class, [$allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions]), + MainConfiguration::STRATEGY_UNANIMOUS => new Definition(UnanimousStrategy::class, [$allowIfAllAbstainDecisions]), + MainConfiguration::STRATEGY_PRIORITY => new Definition(PriorityStrategy::class, [$allowIfAllAbstainDecisions]), + default => throw new InvalidConfigurationException(sprintf('The strategy "%s" is not supported.', $strategy)), + }; + } + + private function createRoleHierarchy(array $config, ContainerBuilder $container): void + { + if (!isset($config['role_hierarchy']) || 0 === \count($config['role_hierarchy'])) { + $container->removeDefinition('security.access.role_hierarchy_voter'); + + return; + } + + $container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']); + $container->removeDefinition('security.access.simple_role_voter'); + } + + private function createAuthorization(array $config, ContainerBuilder $container): void + { + foreach ($config['access_control'] as $access) { + if (isset($access['request_matcher'])) { + if ($access['path'] || $access['host'] || $access['port'] || $access['ips'] || $access['methods'] || $access['attributes'] || $access['route']) { + throw new InvalidConfigurationException('The "request_matcher" option should not be specified alongside other options. Consider integrating your constraints inside your RequestMatcher directly.'); + } + $matcher = new Reference($access['request_matcher']); + } else { + $attributes = $access['attributes']; + + if ($access['route']) { + if (\array_key_exists('_route', $attributes)) { + throw new InvalidConfigurationException('The "route" option should not be specified alongside "attributes._route" option. Use just one of the options.'); + } + $attributes['_route'] = $access['route']; + } + + $matcher = $this->createRequestMatcher( + $container, + $access['path'], + $access['host'], + $access['port'], + $access['methods'], + $access['ips'], + $attributes + ); + } + + $roles = $access['roles']; + if ($access['allow_if']) { + $roles[] = $this->createExpression($container, $access['allow_if']); + } + + $emptyAccess = 0 === \count(array_filter($access)); + + if ($emptyAccess) { + throw new InvalidConfigurationException('One or more access control items are empty. Did you accidentally add lines only containing a "-" under "security.access_control"?'); + } + + $container->getDefinition('security.access_map') + ->addMethodCall('add', [$matcher, $roles, $access['requires_channel']]); + } + + // allow cache warm-up for expressions + if (\count($this->expressions)) { + $container->getDefinition('security.cache_warmer.expression') + ->replaceArgument(0, new IteratorArgument(array_values($this->expressions))); + } else { + $container->removeDefinition('security.cache_warmer.expression'); + } + } + + private function createFirewalls(array $config, ContainerBuilder $container): void + { + if (!isset($config['firewalls'])) { + return; + } + + $firewalls = $config['firewalls']; + $providerIds = $this->createUserProviders($config, $container); + + $container->setParameter('security.firewalls', array_keys($firewalls)); + + // make the ContextListener aware of the configured user providers + $contextListenerDefinition = $container->getDefinition('security.context_listener'); + $arguments = $contextListenerDefinition->getArguments(); + $userProviders = []; + foreach ($providerIds as $userProviderId) { + $userProviders[] = new Reference($userProviderId); + } + $arguments[1] = $userProviderIteratorsArgument = new IteratorArgument($userProviders); + $contextListenerDefinition->setArguments($arguments); + $nbUserProviders = \count($userProviders); + + if ($nbUserProviders > 1) { + $container->setDefinition('security.user_providers', new Definition(ChainUserProvider::class, [$userProviderIteratorsArgument])); + } elseif (0 === $nbUserProviders) { + $container->removeDefinition('security.listener.user_provider'); + } else { + $container->setAlias('security.user_providers', new Alias(current($providerIds))); + } + + if (1 === \count($providerIds)) { + $container->setAlias(UserProviderInterface::class, current($providerIds)); + } + + $customUserChecker = false; + + // load firewall map + $mapDef = $container->getDefinition('security.firewall.map'); + $map = $authenticationProviders = $contextRefs = $authenticators = []; + foreach ($firewalls as $name => $firewall) { + if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) { + $customUserChecker = true; + } + + $configId = 'security.firewall.map.config.'.$name; + + [$matcher, $listeners, $exceptionListener, $logoutListener, $firewallAuthenticators] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); + + if (!$firewallAuthenticators) { + $authenticators[$name] = null; + } else { + $firewallAuthenticatorRefs = []; + foreach ($firewallAuthenticators as $authenticatorId) { + $firewallAuthenticatorRefs[$authenticatorId] = new Reference($authenticatorId); + } + $authenticators[$name] = ServiceLocatorTagPass::register($container, $firewallAuthenticatorRefs); + } + $contextId = 'security.firewall.map.context.'.$name; + $isLazy = !$firewall['stateless'] && (!empty($firewall['anonymous']['lazy']) || $firewall['lazy']); + $context = new ChildDefinition($isLazy ? 'security.firewall.lazy_context' : 'security.firewall.context'); + $context = $container->setDefinition($contextId, $context); + $context + ->replaceArgument(0, new IteratorArgument($listeners)) + ->replaceArgument(1, $exceptionListener) + ->replaceArgument(2, $logoutListener) + ->replaceArgument(3, new Reference($configId)) + ; + + $contextRefs[$contextId] = new Reference($contextId); + $map[$contextId] = $matcher; + } + $container + ->getDefinition('security.helper') + ->replaceArgument(1, $authenticators) + ; + + $container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs)); + + $mapDef->replaceArgument(0, new Reference('security.firewall.context_locator')); + $mapDef->replaceArgument(1, new IteratorArgument($map)); + + // register an autowire alias for the UserCheckerInterface if no custom user checker service is configured + if (!$customUserChecker) { + $container->setAlias(UserCheckerInterface::class, new Alias('security.user_checker', false)); + } + } + + private function createFirewall(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, array $providerIds, string $configId): array + { + $config = $container->setDefinition($configId, new ChildDefinition('security.firewall.config')); + $config->replaceArgument(0, $id); + $config->replaceArgument(1, $firewall['user_checker']); + + // Matcher + $matcher = null; + if (isset($firewall['request_matcher'])) { + $matcher = new Reference($firewall['request_matcher']); + } elseif (isset($firewall['pattern']) || isset($firewall['host'])) { + $pattern = $firewall['pattern'] ?? null; + $host = $firewall['host'] ?? null; + $methods = $firewall['methods'] ?? []; + $matcher = $this->createRequestMatcher($container, $pattern, $host, null, $methods); + } + + $config->replaceArgument(2, $matcher ? (string) $matcher : null); + $config->replaceArgument(3, $firewall['security']); + + // Security disabled? + if (false === $firewall['security']) { + return [$matcher, [], null, null, []]; + } + + $config->replaceArgument(4, $firewall['stateless']); + + $firewallEventDispatcherId = 'security.event_dispatcher.'.$id; + + // Provider id (must be configured explicitly per firewall/authenticator if more than one provider is set) + $defaultProvider = null; + if (isset($firewall['provider'])) { + if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall['provider'])])) { + throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall['provider'])); + } + $defaultProvider = $providerIds[$normalizedName]; + + $container->setDefinition('security.listener.'.$id.'.user_provider', new ChildDefinition('security.listener.user_provider.abstract')) + ->addTag('kernel.event_listener', ['dispatcher' => $firewallEventDispatcherId, 'event' => CheckPassportEvent::class, 'priority' => 2048, 'method' => 'checkPassport']) + ->replaceArgument(0, new Reference($defaultProvider)); + } elseif (1 === \count($providerIds)) { + $defaultProvider = reset($providerIds); + } + + $config->replaceArgument(5, $defaultProvider); + + // Register Firewall-specific event dispatcher + $container->register($firewallEventDispatcherId, EventDispatcher::class) + ->addTag('event_dispatcher.dispatcher', ['name' => $firewallEventDispatcherId]); + + $eventDispatcherLocator = $container->getDefinition('security.firewall.event_dispatcher_locator'); + $eventDispatcherLocator + ->replaceArgument(0, array_merge($eventDispatcherLocator->getArgument(0), [ + $id => new ServiceClosureArgument(new Reference($firewallEventDispatcherId)), + ])) + ; + + // Register Firewall-specific chained user checker + $container->register('security.user_checker.chain.'.$id, ChainUserChecker::class) + ->addArgument(new TaggedIteratorArgument('security.user_checker.'.$id)); + + // Register listeners + $listeners = []; + $listenerKeys = []; + + // Channel listener + $listeners[] = new Reference('security.channel_listener'); + + $contextKey = null; + // Context serializer listener + if (false === $firewall['stateless']) { + $contextKey = $firewall['context'] ?? $id; + $listeners[] = new Reference($this->createContextListener($container, $contextKey, $firewallEventDispatcherId)); + $sessionStrategyId = 'security.authentication.session_strategy'; + + $container + ->setDefinition('security.listener.session.'.$id, new ChildDefinition('security.listener.session')) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + } else { + $sessionStrategyId = 'security.authentication.session_strategy_noop'; + } + $container->setAlias(new Alias('security.authentication.session_strategy.'.$id, false), $sessionStrategyId); + + $config->replaceArgument(6, $contextKey); + + // Logout listener + $logoutListenerId = null; + if (isset($firewall['logout'])) { + $logoutListenerId = 'security.logout_listener.'.$id; + $logoutListener = $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener')); + $logoutListener->replaceArgument(2, new Reference($firewallEventDispatcherId)); + $logoutListener->replaceArgument(3, [ + 'csrf_parameter' => $firewall['logout']['csrf_parameter'], + 'csrf_token_id' => $firewall['logout']['csrf_token_id'], + 'logout_path' => $firewall['logout']['path'], + ]); + + $container->setDefinition('security.logout.listener.default.'.$id, new ChildDefinition('security.logout.listener.default')) + ->replaceArgument(1, $firewall['logout']['target']) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + + // add CSRF provider + if ($firewall['logout']['enable_csrf']) { + $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_manager'])); + } + + // add session logout listener + if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) { + $container->setDefinition('security.logout.listener.session.'.$id, new ChildDefinition('security.logout.listener.session')) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + } + + // add cookie logout listener + if (\count($firewall['logout']['delete_cookies']) > 0) { + $container->setDefinition('security.logout.listener.cookie_clearing.'.$id, new ChildDefinition('security.logout.listener.cookie_clearing')) + ->addArgument($firewall['logout']['delete_cookies']) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + } + + // add clear site data listener + if ($firewall['logout']['clear_site_data'] ?? false) { + $container->setDefinition('security.logout.listener.clear_site_data.'.$id, new ChildDefinition('security.logout.listener.clear_site_data')) + ->addArgument($firewall['logout']['clear_site_data']) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + } + + // register with LogoutUrlGenerator + $container + ->getDefinition('security.logout_url_generator') + ->addMethodCall('registerListener', [ + $id, + $firewall['logout']['path'], + $firewall['logout']['csrf_token_id'], + $firewall['logout']['csrf_parameter'], + isset($firewall['logout']['csrf_token_manager']) ? new Reference($firewall['logout']['csrf_token_manager']) : null, + false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null, + ]) + ; + + $config->replaceArgument(12, $firewall['logout']); + } + + // Determine default entry point + $configuredEntryPoint = $firewall['entry_point'] ?? null; + + // Authentication listeners + $firewallAuthenticationProviders = []; + [$authListeners, $defaultEntryPoint] = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint); + + // $configuredEntryPoint is resolved into a service ID and stored in $defaultEntryPoint + $configuredEntryPoint = $defaultEntryPoint; + + // authenticator manager + $authenticators = array_map(fn ($id) => new Reference($id), $firewallAuthenticationProviders); + $container + ->setDefinition($managerId = 'security.authenticator.manager.'.$id, new ChildDefinition('security.authenticator.manager')) + ->replaceArgument(0, $authenticators) + ->replaceArgument(2, new Reference($firewallEventDispatcherId)) + ->replaceArgument(3, $id) + ->replaceArgument(7, $firewall['required_badges'] ?? []) + ->addTag('monolog.logger', ['channel' => 'security']) + ; + + $managerLocator = $container->getDefinition('security.authenticator.managers_locator'); + $managerLocator->replaceArgument(0, array_merge($managerLocator->getArgument(0), [$id => new ServiceClosureArgument(new Reference($managerId))])); + + // authenticator manager listener + $container + ->setDefinition('security.firewall.authenticator.'.$id, new ChildDefinition('security.firewall.authenticator')) + ->replaceArgument(0, new Reference($managerId)) + ; + + if ($container->hasDefinition('debug.security.firewall')) { + $container + ->register('debug.security.firewall.authenticator.'.$id, TraceableAuthenticatorManagerListener::class) + ->setDecoratedService('security.firewall.authenticator.'.$id) + ->setArguments([new Reference('debug.security.firewall.authenticator.'.$id.'.inner')]) + ->addTag('kernel.reset', ['method' => 'reset']) + ; + } + + // user checker listener + $container + ->setDefinition('security.listener.user_checker.'.$id, new ChildDefinition('security.listener.user_checker')) + ->replaceArgument(0, new Reference('security.user_checker.'.$id)) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + + $listeners[] = new Reference('security.firewall.authenticator.'.$id); + + // Add authenticators to the debug:firewall command + if ($container->hasDefinition('security.command.debug_firewall')) { + $debugCommand = $container->getDefinition('security.command.debug_firewall'); + $debugCommand->replaceArgument(3, array_merge($debugCommand->getArgument(3), [$id => $authenticators])); + } + + $config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint); + + $listeners = array_merge($listeners, $authListeners); + + // Switch user listener + if (isset($firewall['switch_user'])) { + $listenerKeys[] = 'switch_user'; + $listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider, $firewall['stateless'])); + } + + // Access listener + $listeners[] = new Reference('security.access_listener'); + + // Exception listener + $exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless'])); + + $config->replaceArgument(8, $firewall['access_denied_handler'] ?? null); + $config->replaceArgument(9, $firewall['access_denied_url'] ?? null); + + $container->setAlias('security.user_checker.'.$id, new Alias($firewall['user_checker'], false)); + + $userCheckerLocator = $container->getDefinition('security.user_checker_locator'); + $userCheckerLocator->replaceArgument(0, array_merge($userCheckerLocator->getArgument(0), [$id => new ServiceClosureArgument(new Reference('security.user_checker.'.$id))])); + + foreach ($this->getSortedFactories() as $factory) { + $key = str_replace('-', '_', $factory->getKey()); + if ('custom_authenticators' !== $key && \array_key_exists($key, $firewall)) { + $listenerKeys[] = $key; + } + } + + if ($firewall['custom_authenticators'] ?? false) { + foreach ($firewall['custom_authenticators'] as $customAuthenticatorId) { + $listenerKeys[] = $customAuthenticatorId; + } + } + + $config->replaceArgument(10, $listenerKeys); + $config->replaceArgument(11, $firewall['switch_user'] ?? null); + + return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null, $firewallAuthenticationProviders]; + } + + private function createContextListener(ContainerBuilder $container, string $contextKey, ?string $firewallEventDispatcherId): string + { + if (isset($this->contextListeners[$contextKey])) { + return $this->contextListeners[$contextKey]; + } + + $listenerId = 'security.context_listener.'.\count($this->contextListeners); + $listener = $container->setDefinition($listenerId, new ChildDefinition('security.context_listener')); + $listener->replaceArgument(2, $contextKey); + if (null !== $firewallEventDispatcherId) { + $listener->replaceArgument(4, new Reference($firewallEventDispatcherId)); + $listener->addTag('kernel.event_listener', ['event' => KernelEvents::RESPONSE, 'method' => 'onKernelResponse']); + } + + return $this->contextListeners[$contextKey] = $listenerId; + } + + private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint): array + { + $listeners = []; + $entryPoints = []; + + foreach ($this->getSortedFactories() as $factory) { + $key = str_replace('-', '_', $factory->getKey()); + + if (isset($firewall[$key])) { + $userProvider = $this->getUserProvider($container, $id, $firewall, $key, $defaultProvider, $providerIds); + + if (!$factory instanceof AuthenticatorFactoryInterface) { + throw new InvalidConfigurationException(sprintf('Authenticator factory "%s" ("%s") must implement "%s".', get_debug_type($factory), $key, AuthenticatorFactoryInterface::class)); + } + + if (null === $userProvider && !$factory instanceof StatelessAuthenticatorFactoryInterface) { + $userProvider = $this->createMissingUserProvider($container, $id, $key); + } + + $authenticators = $factory->createAuthenticator($container, $id, $firewall[$key], $userProvider); + if (\is_array($authenticators)) { + foreach ($authenticators as $authenticator) { + $authenticationProviders[] = $authenticator; + $entryPoints[] = $authenticator; + } + } else { + $authenticationProviders[] = $authenticators; + $entryPoints[$key] = $authenticators; + } + + if ($factory instanceof FirewallListenerFactoryInterface) { + $firewallListenerIds = $factory->createListeners($container, $id, $firewall[$key]); + foreach ($firewallListenerIds as $firewallListenerId) { + $listeners[] = new Reference($firewallListenerId); + } + } + } + } + + // the actual entry point is configured by the RegisterEntryPointPass + $container->setParameter('security.'.$id.'._indexed_authenticators', $entryPoints); + + return [$listeners, $defaultEntryPoint]; + } + + private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds): ?string + { + if (isset($firewall[$factoryKey]['provider'])) { + if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) { + throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$factoryKey]['provider'])); + } + + return $providerIds[$normalizedName]; + } + + if ($defaultProvider) { + return $defaultProvider; + } + + if (!$providerIds) { + if ($firewall['stateless'] ?? false) { + return null; + } + + return $this->createMissingUserProvider($container, $id, $factoryKey); + } + + if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) { + return 'security.user_providers'; + } + + throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" authenticator on "%s" firewall is ambiguous as there is more than one registered provider. Set the "provider" key to one of the configured providers, even if your custom authenticators don\'t use it.', $factoryKey, $id)); + } + + private function createMissingUserProvider(ContainerBuilder $container, string $id, string $factoryKey): string + { + $userProvider = sprintf('security.user.provider.missing.%s', $factoryKey); + $container->setDefinition( + $userProvider, + (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id) + ); + + return $userProvider; + } + + private function createHashers(array $hashers, ContainerBuilder $container): void + { + $hasherMap = []; + foreach ($hashers as $class => $hasher) { + $hasherMap[$class] = $this->createHasher($hasher); + } + + $container + ->getDefinition('security.password_hasher_factory') + ->setArguments([$hasherMap]) + ; + } + + /** + * @param array $config + * + * @return Reference|array + */ + private function createHasher(array $config): Reference|array + { + // a custom hasher service + if (isset($config['id'])) { + return $config['migrate_from'] ?? false ? [ + 'instance' => new Reference($config['id']), + 'migrate_from' => $config['migrate_from'], + ] : new Reference($config['id']); + } + + if ($config['migrate_from'] ?? false) { + return $config; + } + + // plaintext hasher + if ('plaintext' === $config['algorithm']) { + $arguments = [$config['ignore_case']]; + + return [ + 'class' => PlaintextPasswordHasher::class, + 'arguments' => $arguments, + ]; + } + + // pbkdf2 hasher + if ('pbkdf2' === $config['algorithm']) { + return [ + 'class' => Pbkdf2PasswordHasher::class, + 'arguments' => [ + $config['hash_algorithm'], + $config['encode_as_base64'], + $config['iterations'], + $config['key_length'], + ], + ]; + } + + // bcrypt hasher + if ('bcrypt' === $config['algorithm']) { + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_BCRYPT; + + return $this->createHasher($config); + } + + // Argon2i hasher + if ('argon2i' === $config['algorithm']) { + if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + $config['algorithm'] = 'sodium'; + } elseif (\defined('PASSWORD_ARGON2I')) { + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_ARGON2I; + } else { + throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available; use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id" or "auto' : 'auto')); + } + + return $this->createHasher($config); + } + + if ('argon2id' === $config['algorithm']) { + if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + $config['algorithm'] = 'sodium'; + } elseif (\defined('PASSWORD_ARGON2ID')) { + $config['algorithm'] = 'native'; + $config['native_algorithm'] = \PASSWORD_ARGON2ID; + } else { + throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available; use "%s" or libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); + } + + return $this->createHasher($config); + } + + if ('native' === $config['algorithm']) { + return [ + 'class' => NativePasswordHasher::class, + 'arguments' => [ + $config['time_cost'], + (($config['memory_cost'] ?? 0) << 10) ?: null, + $config['cost'], + ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []), + ]; + } + + if ('sodium' === $config['algorithm']) { + if (!SodiumPasswordHasher::isSupported()) { + throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.'); + } + + return [ + 'class' => SodiumPasswordHasher::class, + 'arguments' => [ + $config['time_cost'], + (($config['memory_cost'] ?? 0) << 10) ?: null, + ], + ]; + } + + // run-time configured hasher + return $config; + } + + // Parses user providers and returns an array of their ids + private function createUserProviders(array $config, ContainerBuilder $container): array + { + $providerIds = []; + foreach ($config['providers'] as $name => $provider) { + $id = $this->createUserDaoProvider($name, $provider, $container); + $providerIds[str_replace('-', '_', $name)] = $id; + } + + return $providerIds; + } + + // Parses a tag and returns the id for the related user provider service + private function createUserDaoProvider(string $name, array $provider, ContainerBuilder $container): string + { + $name = $this->getUserProviderId($name); + + // Doctrine Entity and In-memory DAO provider are managed by factories + foreach ($this->userProviderFactories as $factory) { + $key = str_replace('-', '_', $factory->getKey()); + + if (!empty($provider[$key])) { + $factory->create($container, $name, $provider[$key]); + + return $name; + } + } + + // Existing DAO service provider + if (isset($provider['id'])) { + $container->setAlias($name, new Alias($provider['id'], false)); + + return $provider['id']; + } + + // Chain provider + if (isset($provider['chain'])) { + $providers = []; + foreach ($provider['chain']['providers'] as $providerName) { + $providers[] = new Reference($this->getUserProviderId($providerName)); + } + + $container + ->setDefinition($name, new ChildDefinition('security.user.provider.chain')) + ->addArgument(new IteratorArgument($providers)); + + return $name; + } + + throw new InvalidConfigurationException(sprintf('Unable to create definition for "%s" user provider.', $name)); + } + + private function getUserProviderId(string $name): string + { + return 'security.user.provider.concrete.'.strtolower($name); + } + + private function createExceptionListener(ContainerBuilder $container, array $config, string $id, ?string $defaultEntryPoint, bool $stateless): string + { + $exceptionListenerId = 'security.exception_listener.'.$id; + $listener = $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener')); + $listener->replaceArgument(3, $id); + $listener->replaceArgument(4, null === $defaultEntryPoint ? null : new Reference($defaultEntryPoint)); + $listener->replaceArgument(8, $stateless); + + // access denied handler setup + if (isset($config['access_denied_handler'])) { + $listener->replaceArgument(6, new Reference($config['access_denied_handler'])); + } elseif (isset($config['access_denied_url'])) { + $listener->replaceArgument(5, $config['access_denied_url']); + } + + return $exceptionListenerId; + } + + private function createSwitchUserListener(ContainerBuilder $container, string $id, array $config, ?string $defaultProvider, bool $stateless): string + { + $userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider; + + if (!$userProvider) { + throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "switch_user" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $id)); + } + if ($stateless && null !== $config['target_route']) { + throw new InvalidConfigurationException(sprintf('Cannot set a "target_route" for the "switch_user" listener on the "%s" firewall as it is stateless.', $id)); + } + + $switchUserListenerId = 'security.authentication.switchuser_listener.'.$id; + $listener = $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener')); + $listener->replaceArgument(1, new Reference($userProvider)); + $listener->replaceArgument(2, new Reference('security.user_checker.'.$id)); + $listener->replaceArgument(3, $id); + $listener->replaceArgument(6, $config['parameter']); + $listener->replaceArgument(7, $config['role']); + $listener->replaceArgument(9, $stateless); + $listener->replaceArgument(11, $config['target_route']); + + return $switchUserListenerId; + } + + private function createExpression(ContainerBuilder $container, string $expression): Reference + { + if (isset($this->expressions[$id = '.security.expression.'.ContainerBuilder::hash($expression)])) { + return $this->expressions[$id]; + } + + if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'])) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + + $container + ->register($id, Expression::class) + ->addArgument($expression) + ; + + return $this->expressions[$id] = new Reference($id); + } + + private function createRequestMatcher(ContainerBuilder $container, ?string $path = null, ?string $host = null, ?int $port = null, array $methods = [], ?array $ips = null, array $attributes = []): Reference + { + if ($methods) { + $methods = array_map('strtoupper', $methods); + } + + if ($ips) { + foreach ($ips as $ip) { + $container->resolveEnvPlaceholders($ip, null, $usedEnvs); + + if (!$usedEnvs && !$this->isValidIps($ip)) { + throw new \LogicException(sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.', $ip)); + } + + $usedEnvs = null; + } + } + + $id = '.security.request_matcher.'.ContainerBuilder::hash([ChainRequestMatcher::class, $path, $host, $port, $methods, $ips, $attributes]); + + if (isset($this->requestMatchers[$id])) { + return $this->requestMatchers[$id]; + } + + $arguments = []; + if ($methods) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([MethodRequestMatcher::class, $methods]))) { + $container->register($lid, MethodRequestMatcher::class)->setArguments([$methods]); + } + $arguments[] = new Reference($lid); + } + + if ($path) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([PathRequestMatcher::class, $path]))) { + $container->register($lid, PathRequestMatcher::class)->setArguments([$path]); + } + $arguments[] = new Reference($lid); + } + + if ($host) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([HostRequestMatcher::class, $host]))) { + $container->register($lid, HostRequestMatcher::class)->setArguments([$host]); + } + $arguments[] = new Reference($lid); + } + + if ($ips) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([IpsRequestMatcher::class, $ips]))) { + $container->register($lid, IpsRequestMatcher::class)->setArguments([$ips]); + } + $arguments[] = new Reference($lid); + } + + if ($attributes) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([AttributesRequestMatcher::class, $attributes]))) { + $container->register($lid, AttributesRequestMatcher::class)->setArguments([$attributes]); + } + $arguments[] = new Reference($lid); + } + + if ($port) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([PortRequestMatcher::class, $port]))) { + $container->register($lid, PortRequestMatcher::class)->setArguments([$port]); + } + $arguments[] = new Reference($lid); + } + + $container + ->register($id, ChainRequestMatcher::class) + ->setArguments([$arguments]) + ; + + return $this->requestMatchers[$id] = new Reference($id); + } + + public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory): void + { + $this->factories[] = [$factory->getPriority(), $factory]; + $this->sortedFactories = []; + } + + public function addUserProviderFactory(UserProviderFactoryInterface $factory): void + { + $this->userProviderFactories[] = $factory; + } + + public function getXsdValidationBasePath(): string|false + { + return __DIR__.'/../Resources/config/schema'; + } + + public function getNamespace(): string + { + return 'http://symfony.com/schema/dic/security'; + } + + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + { + // first assemble the factories + return new MainConfiguration($this->getSortedFactories(), $this->userProviderFactories); + } + + private function isValidIps(string|array $ips): bool + { + $ipsList = array_reduce((array) $ips, fn ($ips, $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); + + if (!$ipsList) { + return false; + } + + foreach ($ipsList as $cidr) { + if (!$this->isValidIp($cidr)) { + return false; + } + } + + return true; + } + + private function isValidIp(string $cidr): bool + { + $cidrParts = explode('/', $cidr); + + if (1 === \count($cidrParts)) { + return false !== filter_var($cidrParts[0], \FILTER_VALIDATE_IP); + } + + $ip = $cidrParts[0]; + $netmask = $cidrParts[1]; + + if (!ctype_digit($netmask)) { + return false; + } + + if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + return $netmask <= 32; + } + + if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return $netmask <= 128; + } + + return false; + } + + /** + * @return array + */ + private function getSortedFactories(): array + { + if (!$this->sortedFactories) { + $factories = []; + foreach ($this->factories as $i => $factory) { + $factories[] = array_merge($factory, [$i]); + } + + usort($factories, fn ($a, $b) => $b[0] <=> $a[0] ?: $a[2] <=> $b[2]); + + $this->sortedFactories = array_column($factories, 1); + } + + return $this->sortedFactories; + } + + private function createLogoutUrisParameter(array $firewallsConfig, ContainerBuilder $container): void + { + $logoutUris = []; + foreach ($firewallsConfig as $name => $config) { + if (!$logoutPath = $config['logout']['path'] ?? null) { + continue; + } + + if ('/' === $logoutPath[0]) { + $logoutUris[$name] = $logoutPath; + } + } + + $container->setParameter('security.logout_uris', $logoutUris); + } +} diff --git a/vendor/symfony/security-bundle/EventListener/FirewallListener.php b/vendor/symfony/security-bundle/EventListener/FirewallListener.php new file mode 100644 index 0000000..4c63ec1 --- /dev/null +++ b/vendor/symfony/security-bundle/EventListener/FirewallListener.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\EventListener; + +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Security\Http\FirewallMapInterface; +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * @author Maxime Steinhausser + */ +class FirewallListener extends Firewall +{ + private FirewallMapInterface $map; + private LogoutUrlGenerator $logoutUrlGenerator; + + public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher, LogoutUrlGenerator $logoutUrlGenerator) + { + $this->map = $map; + $this->logoutUrlGenerator = $logoutUrlGenerator; + + parent::__construct($map, $dispatcher); + } + + public function configureLogoutUrlGenerator(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + if ($this->map instanceof FirewallMap && $config = $this->map->getFirewallConfig($event->getRequest())) { + $this->logoutUrlGenerator->setCurrentFirewall($config->getName(), $config->getContext()); + } + } + + public function onKernelFinishRequest(FinishRequestEvent $event): void + { + if ($event->isMainRequest()) { + $this->logoutUrlGenerator->setCurrentFirewall(null); + } + + parent::onKernelFinishRequest($event); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [ + ['configureLogoutUrlGenerator', 8], + ['onKernelRequest', 8], + ], + KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest', + ]; + } +} diff --git a/vendor/symfony/security-bundle/EventListener/VoteListener.php b/vendor/symfony/security-bundle/EventListener/VoteListener.php new file mode 100644 index 0000000..34ca91c --- /dev/null +++ b/vendor/symfony/security-bundle/EventListener/VoteListener.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; +use Symfony\Component\Security\Core\Event\VoteEvent; + +/** + * Listen to vote events from traceable voters. + * + * @author Laurent VOULLEMIER + * + * @internal + */ +class VoteListener implements EventSubscriberInterface +{ + private TraceableAccessDecisionManager $traceableAccessDecisionManager; + + public function __construct(TraceableAccessDecisionManager $traceableAccessDecisionManager) + { + $this->traceableAccessDecisionManager = $traceableAccessDecisionManager; + } + + public function onVoterVote(VoteEvent $event): void + { + $this->traceableAccessDecisionManager->addVoterVote($event->getVoter(), $event->getAttributes(), $event->getVote()); + } + + public static function getSubscribedEvents(): array + { + return ['debug.security.authorization.vote' => 'onVoterVote']; + } +} diff --git a/vendor/symfony/security-bundle/LICENSE b/vendor/symfony/security-bundle/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/security-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/security-bundle/LoginLink/FirewallAwareLoginLinkHandler.php b/vendor/symfony/security-bundle/LoginLink/FirewallAwareLoginLinkHandler.php new file mode 100644 index 0000000..2bcbcad --- /dev/null +++ b/vendor/symfony/security-bundle/LoginLink/FirewallAwareLoginLinkHandler.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\LoginLink; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\LoginLink\LoginLinkDetails; +use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; + +/** + * Decorates the login link handler for the current firewall. + * + * @author Ryan Weaver + */ +class FirewallAwareLoginLinkHandler implements LoginLinkHandlerInterface +{ + use FirewallAwareTrait; + + private const FIREWALL_OPTION = 'login_link'; + + public function __construct(FirewallMap $firewallMap, ContainerInterface $loginLinkHandlerLocator, RequestStack $requestStack) + { + $this->firewallMap = $firewallMap; + $this->locator = $loginLinkHandlerLocator; + $this->requestStack = $requestStack; + } + + public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails + { + return $this->getForFirewall()->createLoginLink($user, $request, $lifetime); + } + + public function consumeLoginLink(Request $request): UserInterface + { + return $this->getForFirewall()->consumeLoginLink($request); + } +} diff --git a/vendor/symfony/security-bundle/README.md b/vendor/symfony/security-bundle/README.md new file mode 100644 index 0000000..63b502f --- /dev/null +++ b/vendor/symfony/security-bundle/README.md @@ -0,0 +1,13 @@ +SecurityBundle +============== + +SecurityBundle provides a tight integration of the Security component into the +Symfony full-stack framework. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/security-bundle/RememberMe/DecoratedRememberMeHandler.php b/vendor/symfony/security-bundle/RememberMe/DecoratedRememberMeHandler.php new file mode 100644 index 0000000..ed6d0ed --- /dev/null +++ b/vendor/symfony/security-bundle/RememberMe/DecoratedRememberMeHandler.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\RememberMe; + +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\RememberMe\RememberMeDetails; +use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; + +/** + * Used as a "workaround" for tagging aliases in the RememberMeFactory. + * + * @author Wouter de Jong + * + * @internal + */ +final class DecoratedRememberMeHandler implements RememberMeHandlerInterface +{ + private RememberMeHandlerInterface $handler; + + public function __construct(RememberMeHandlerInterface $handler) + { + $this->handler = $handler; + } + + public function createRememberMeCookie(UserInterface $user): void + { + $this->handler->createRememberMeCookie($user); + } + + public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface + { + return $this->handler->consumeRememberMeCookie($rememberMeDetails); + } + + public function clearRememberMeCookie(): void + { + $this->handler->clearRememberMeCookie(); + } +} diff --git a/vendor/symfony/security-bundle/RememberMe/FirewallAwareRememberMeHandler.php b/vendor/symfony/security-bundle/RememberMe/FirewallAwareRememberMeHandler.php new file mode 100644 index 0000000..ca7450f --- /dev/null +++ b/vendor/symfony/security-bundle/RememberMe/FirewallAwareRememberMeHandler.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\RememberMe; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\RememberMe\RememberMeDetails; +use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; + +/** + * Decorates {@see RememberMeHandlerInterface} for the current firewall. + * + * @author Wouter de Jong + */ +final class FirewallAwareRememberMeHandler implements RememberMeHandlerInterface +{ + use FirewallAwareTrait; + + private const FIREWALL_OPTION = 'remember_me'; + + public function __construct(FirewallMap $firewallMap, ContainerInterface $rememberMeHandlerLocator, RequestStack $requestStack) + { + $this->firewallMap = $firewallMap; + $this->locator = $rememberMeHandlerLocator; + $this->requestStack = $requestStack; + } + + public function createRememberMeCookie(UserInterface $user): void + { + $this->getForFirewall()->createRememberMeCookie($user); + } + + public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface + { + return $this->getForFirewall()->consumeRememberMeCookie($rememberMeDetails); + } + + public function clearRememberMeCookie(): void + { + $this->getForFirewall()->clearRememberMeCookie(); + } +} diff --git a/vendor/symfony/security-bundle/Resources/config/collectors.php b/vendor/symfony/security-bundle/Resources/config/collectors.php new file mode 100644 index 0000000..3619779 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/collectors.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.security', SecurityDataCollector::class) + ->args([ + service('security.untracked_token_storage'), + service('security.role_hierarchy'), + service('security.logout_url_generator'), + service('security.access.decision_manager'), + service('security.firewall.map'), + service('debug.security.firewall')->nullOnInvalid(), + ]) + ->tag('data_collector', [ + 'template' => '@Security/Collector/security.html.twig', + 'id' => 'security', + 'priority' => 270, + ]) + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/console.php b/vendor/symfony/security-bundle/Resources/config/console.php new file mode 100644 index 0000000..0fa0f9d --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/console.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.command.user_password_hash', UserPasswordHashCommand::class) + ->args([ + service('security.password_hasher_factory'), + abstract_arg('list of user classes'), + ]) + ->tag('console.command') + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/debug_console.php b/vendor/symfony/security-bundle/Resources/config/debug_console.php new file mode 100644 index 0000000..74fa434 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/debug_console.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\Command\DebugFirewallCommand; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.command.debug_firewall', DebugFirewallCommand::class) + ->args([ + param('security.firewalls'), + service('security.firewall.context_locator'), + tagged_locator('event_dispatcher.dispatcher', 'name'), + [], + false, + ]) + ->tag('console.command', ['command' => 'debug:firewall']) + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/password_hasher.php b/vendor/symfony/security-bundle/Resources/config/password_hasher.php new file mode 100644 index 0000000..2df037a --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/password_hasher.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; +use Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension; +use Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.password_hasher_factory', PasswordHasherFactory::class) + ->args([[]]) + ->alias(PasswordHasherFactoryInterface::class, 'security.password_hasher_factory') + + ->set('security.user_password_hasher', UserPasswordHasher::class) + ->args([service('security.password_hasher_factory')]) + ->alias('security.password_hasher', 'security.user_password_hasher') + ->alias(UserPasswordHasherInterface::class, 'security.password_hasher') + + ->set('form.listener.password_hasher', PasswordHasherListener::class) + ->args([ + service('security.password_hasher'), + service('property_accessor')->nullOnInvalid(), + ]) + + ->set('form.type_extension.form.password_hasher', FormTypePasswordHasherExtension::class) + ->args([ + service('form.listener.password_hasher'), + ]) + ->tag('form.type_extension', ['extended-type' => FormType::class]) + + ->set('form.type_extension.password.password_hasher', PasswordTypePasswordHasherExtension::class) + ->args([ + service('form.listener.password_hasher'), + ]) + ->tag('form.type_extension', ['extended-type' => PasswordType::class]) + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/schema/security-1.0.xsd b/vendor/symfony/security-bundle/Resources/config/schema/security-1.0.xsd new file mode 100644 index 0000000..ef10635 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/schema/security-1.0.xsd @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/security-bundle/Resources/config/security.php b/vendor/symfony/security-bundle/Resources/config/security.php new file mode 100644 index 0000000..7411c6d --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/security.php @@ -0,0 +1,317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\CacheWarmer\ExpressionCacheWarmer; +use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; +use Symfony\Bundle\SecurityBundle\Routing\LogoutRouteLoader; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Bundle\SecurityBundle\Security\FirewallContext; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; +use Symfony\Component\Ldap\Security\LdapUserProvider; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; +use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter; +use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; +use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; +use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; +use Symfony\Component\Security\Core\User\ChainUserProvider; +use Symfony\Component\Security\Core\User\InMemoryUserChecker; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; +use Symfony\Component\Security\Core\User\MissingUserProvider; +use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver; +use Symfony\Component\Security\Http\Controller\UserValueResolver; +use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener; +use Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Security\Http\FirewallMapInterface; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator; +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('security.role_hierarchy.roles', []) + ; + + $container->services() + ->set('security.authorization_checker', AuthorizationChecker::class) + ->args([ + service('security.token_storage'), + service('security.access.decision_manager'), + ]) + ->alias(AuthorizationCheckerInterface::class, 'security.authorization_checker') + + ->set('security.token_storage', UsageTrackingTokenStorage::class) + ->args([ + service('security.untracked_token_storage'), + service_locator([ + 'request_stack' => service('request_stack'), + ]), + ]) + ->tag('kernel.reset', ['method' => 'disableUsageTracking']) + ->tag('kernel.reset', ['method' => 'setToken']) + ->alias(TokenStorageInterface::class, 'security.token_storage') + + ->set('security.untracked_token_storage', TokenStorage::class) + + ->set('security.helper', Security::class) + ->args([ + service_locator([ + 'security.token_storage' => service('security.token_storage'), + 'security.authorization_checker' => service('security.authorization_checker'), + 'security.authenticator.managers_locator' => service('security.authenticator.managers_locator')->ignoreOnInvalid(), + 'request_stack' => service('request_stack'), + 'security.firewall.map' => service('security.firewall.map'), + 'security.user_checker_locator' => service('security.user_checker_locator'), + 'security.firewall.event_dispatcher_locator' => service('security.firewall.event_dispatcher_locator'), + 'security.csrf.token_manager' => service('security.csrf.token_manager')->ignoreOnInvalid(), + ]), + abstract_arg('authenticators'), + ]) + ->alias(Security::class, 'security.helper') + + ->set('security.user_value_resolver', UserValueResolver::class) + ->args([ + service('security.token_storage'), + ]) + ->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => UserValueResolver::class]) + + ->set('security.security_token_value_resolver', SecurityTokenValueResolver::class) + ->args([ + service('security.token_storage'), + ]) + ->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => SecurityTokenValueResolver::class]) + + // Authentication related services + ->set('security.authentication.trust_resolver', AuthenticationTrustResolver::class) + + ->set('security.authentication.session_strategy', SessionAuthenticationStrategy::class) + ->args([ + param('security.authentication.session_strategy.strategy'), + service('security.csrf.token_storage')->ignoreOnInvalid(), + ]) + ->alias(SessionAuthenticationStrategyInterface::class, 'security.authentication.session_strategy') + + ->set('security.authentication.session_strategy_noop', SessionAuthenticationStrategy::class) + ->args(['none']) + + ->set('security.user_checker', InMemoryUserChecker::class) + ->set('security.user_checker_locator', ServiceLocator::class) + ->args([[]]) + + ->set('security.expression_language', ExpressionLanguage::class) + ->args([service('cache.security_expression_language')->nullOnInvalid()]) + + ->set('security.authentication_utils', AuthenticationUtils::class) + ->args([service('request_stack')]) + ->alias(AuthenticationUtils::class, 'security.authentication_utils') + + // Authorization related services + ->set('security.access.decision_manager', AccessDecisionManager::class) + ->args([[]]) + ->alias(AccessDecisionManagerInterface::class, 'security.access.decision_manager') + + ->set('security.role_hierarchy', RoleHierarchy::class) + ->args([param('security.role_hierarchy.roles')]) + ->alias(RoleHierarchyInterface::class, 'security.role_hierarchy') + + // Security Voters + ->set('security.access.simple_role_voter', RoleVoter::class) + ->tag('security.voter', ['priority' => 245]) + + ->set('security.access.authenticated_voter', AuthenticatedVoter::class) + ->args([service('security.authentication.trust_resolver')]) + ->tag('security.voter', ['priority' => 250]) + + ->set('security.access.role_hierarchy_voter', RoleHierarchyVoter::class) + ->args([service('security.role_hierarchy')]) + ->tag('security.voter', ['priority' => 245]) + + ->set('security.access.expression_voter', ExpressionVoter::class) + ->args([ + service('security.expression_language'), + service('security.authentication.trust_resolver'), + service('security.authorization_checker'), + service('security.role_hierarchy')->nullOnInvalid(), + ]) + ->tag('security.voter', ['priority' => 245]) + + ->set('security.impersonate_url_generator', ImpersonateUrlGenerator::class) + ->args([ + service('request_stack'), + service('security.firewall.map'), + service('security.token_storage'), + ]) + + // Firewall related services + ->set('security.firewall', FirewallListener::class) + ->args([ + service('security.firewall.map'), + service('event_dispatcher'), + service('security.logout_url_generator'), + ]) + ->tag('kernel.event_subscriber') + ->alias(Firewall::class, 'security.firewall') + + ->set('security.firewall.map', FirewallMap::class) + ->args([ + abstract_arg('Firewall context locator'), + abstract_arg('Request matchers'), + ]) + ->alias(FirewallMapInterface::class, 'security.firewall.map') + + ->set('security.firewall.context', FirewallContext::class) + ->abstract() + ->args([ + [], + service('security.exception_listener'), + abstract_arg('LogoutListener'), + abstract_arg('FirewallConfig'), + ]) + + ->set('security.firewall.lazy_context', LazyFirewallContext::class) + ->abstract() + ->args([ + [], + service('security.exception_listener'), + abstract_arg('LogoutListener'), + abstract_arg('FirewallConfig'), + service('security.untracked_token_storage'), + ]) + + ->set('security.firewall.config', FirewallConfig::class) + ->abstract() + ->args([ + abstract_arg('name'), + abstract_arg('user_checker'), + abstract_arg('request_matcher'), + false, // security enabled + false, // stateless + null, + null, + null, + null, + null, + [], // listeners + null, // switch_user + null, // logout + ]) + + ->set('security.logout_url_generator', LogoutUrlGenerator::class) + ->args([ + service('request_stack')->nullOnInvalid(), + service('router')->nullOnInvalid(), + service('security.token_storage')->nullOnInvalid(), + ]) + + ->set('security.route_loader.logout', LogoutRouteLoader::class) + ->args([ + '%security.logout_uris%', + 'security.logout_uris', + ]) + ->tag('routing.route_loader') + + // Provisioning + ->set('security.user.provider.missing', MissingUserProvider::class) + ->abstract() + ->args([ + abstract_arg('firewall'), + ]) + + ->set('security.user.provider.in_memory', InMemoryUserProvider::class) + ->abstract() + + ->set('security.user.provider.ldap', LdapUserProvider::class) + ->abstract() + ->args([ + abstract_arg('security.ldap.ldap'), + abstract_arg('base dn'), + abstract_arg('search dn'), + abstract_arg('search password'), + abstract_arg('default_roles'), + abstract_arg('uid key'), + abstract_arg('filter'), + abstract_arg('password_attribute'), + abstract_arg('extra_fields (email etc)'), + ]) + + ->set('security.user.provider.chain', ChainUserProvider::class) + ->abstract() + + ->set('security.http_utils', HttpUtils::class) + ->args([ + service('router')->nullOnInvalid(), + service('router')->nullOnInvalid(), + ]) + ->alias(HttpUtils::class, 'security.http_utils') + + // Validator + ->set('security.validator.user_password', UserPasswordValidator::class) + ->args([ + service('security.token_storage'), + service('security.password_hasher_factory'), + ]) + ->tag('validator.constraint_validator', ['alias' => 'security.validator.user_password']) + + // Cache + ->set('cache.security_expression_language') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + // Cache Warmers + ->set('security.cache_warmer.expression', ExpressionCacheWarmer::class) + ->args([ + [], + service('security.expression_language'), + ]) + ->tag('kernel.cache_warmer') + + ->set('controller.is_granted_attribute_listener', IsGrantedAttributeListener::class) + ->args([ + service('security.authorization_checker'), + service('security.is_granted_attribute_expression_language')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + + ->set('security.is_granted_attribute_expression_language', BaseExpressionLanguage::class) + ->args([service('cache.security_is_granted_attribute_expression_language')->nullOnInvalid()]) + + ->set('cache.security_is_granted_attribute_expression_language') + ->parent('cache.system') + ->tag('cache.pool') + + ->set('security.is_csrf_token_valid_attribute_expression_language', BaseExpressionLanguage::class) + ->args([service('cache.security_is_csrf_token_valid_attribute_expression_language')->nullOnInvalid()]) + + ->set('cache.security_is_csrf_token_valid_attribute_expression_language') + ->parent('cache.system') + ->tag('cache.pool') + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/security_authenticator.php b/vendor/symfony/security-bundle/Resources/config/security_authenticator.php new file mode 100644 index 0000000..92c91e9 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/security_authenticator.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\Security\UserAuthenticator; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Security\Http\Authentication\AuthenticatorManager; +use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator; +use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator; +use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator; +use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator; +use Symfony\Component\Security\Http\Authenticator\X509Authenticator; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Component\Security\Http\EventListener\CheckCredentialsListener; +use Symfony\Component\Security\Http\EventListener\LoginThrottlingListener; +use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener; +use Symfony\Component\Security\Http\EventListener\SessionStrategyListener; +use Symfony\Component\Security\Http\EventListener\UserCheckerListener; +use Symfony\Component\Security\Http\EventListener\UserProviderListener; +use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener; + +return static function (ContainerConfigurator $container) { + $container->services() + + // Manager + ->set('security.authenticator.manager', AuthenticatorManager::class) + ->abstract() + ->args([ + abstract_arg('authenticators'), + service('security.token_storage'), + service('event_dispatcher'), + abstract_arg('provider key'), + service('logger')->nullOnInvalid(), + param('security.authentication.manager.erase_credentials'), + param('security.authentication.hide_user_not_found'), + abstract_arg('required badges'), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authenticator.managers_locator', ServiceLocator::class) + ->args([[]]) + + ->set('security.user_authenticator', UserAuthenticator::class) + ->args([ + service('security.firewall.map'), + service('security.authenticator.managers_locator'), + service('request_stack'), + ]) + ->alias(UserAuthenticatorInterface::class, 'security.user_authenticator') + + ->set('security.firewall.authenticator', AuthenticatorManagerListener::class) + ->abstract() + ->args([ + abstract_arg('authenticator manager'), + ]) + + // Listeners + ->set('security.listener.check_authenticator_credentials', CheckCredentialsListener::class) + ->args([ + service('security.password_hasher_factory'), + ]) + ->tag('kernel.event_subscriber') + + ->set('security.listener.user_provider', UserProviderListener::class) + ->args([ + service('security.user_providers'), + ]) + ->tag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 1024, 'method' => 'checkPassport']) + + ->set('security.listener.user_provider.abstract', UserProviderListener::class) + ->abstract() + ->args([ + abstract_arg('user provider'), + ]) + + ->set('security.listener.password_migrating', PasswordMigratingListener::class) + ->args([ + service('security.password_hasher_factory'), + ]) + ->tag('kernel.event_subscriber') + + ->set('security.listener.user_checker', UserCheckerListener::class) + ->abstract() + ->args([ + abstract_arg('user checker'), + ]) + + ->set('security.listener.session', SessionStrategyListener::class) + ->abstract() + ->args([ + service('security.authentication.session_strategy'), + ]) + + ->set('security.listener.login_throttling', LoginThrottlingListener::class) + ->abstract() + ->args([ + service('request_stack'), + abstract_arg('request rate limiter'), + ]) + + // Authenticators + ->set('security.authenticator.http_basic', HttpBasicAuthenticator::class) + ->abstract() + ->args([ + abstract_arg('realm name'), + abstract_arg('user provider'), + service('logger')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authenticator.form_login', FormLoginAuthenticator::class) + ->abstract() + ->args([ + service('security.http_utils'), + abstract_arg('user provider'), + abstract_arg('authentication success handler'), + abstract_arg('authentication failure handler'), + abstract_arg('options'), + ]) + + ->set('security.authenticator.json_login', JsonLoginAuthenticator::class) + ->abstract() + ->args([ + service('security.http_utils'), + abstract_arg('user provider'), + abstract_arg('authentication success handler'), + abstract_arg('authentication failure handler'), + abstract_arg('options'), + service('property_accessor')->nullOnInvalid(), + ]) + ->call('setTranslator', [service('translator')->ignoreOnInvalid()]) + + ->set('security.authenticator.x509', X509Authenticator::class) + ->abstract() + ->args([ + abstract_arg('user provider'), + service('security.token_storage'), + abstract_arg('firewall name'), + abstract_arg('user key'), + abstract_arg('credentials key'), + service('logger')->nullOnInvalid(), + abstract_arg('credentials user identifier'), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authenticator.remote_user', RemoteUserAuthenticator::class) + ->abstract() + ->args([ + abstract_arg('user provider'), + service('security.token_storage'), + abstract_arg('firewall name'), + abstract_arg('user key'), + service('logger')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/security_authenticator_access_token.php b/vendor/symfony/security-bundle/Resources/config/security_authenticator_access_token.php new file mode 100644 index 0000000..c0fced4 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/security_authenticator_access_token.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Jose\Component\Core\AlgorithmManager; +use Jose\Component\Core\AlgorithmManagerFactory; +use Jose\Component\Core\JWK; +use Jose\Component\Core\JWKSet; +use Jose\Component\Signature\Algorithm\ES256; +use Jose\Component\Signature\Algorithm\ES384; +use Jose\Component\Signature\Algorithm\ES512; +use Jose\Component\Signature\Algorithm\PS256; +use Jose\Component\Signature\Algorithm\PS384; +use Jose\Component\Signature\Algorithm\PS512; +use Jose\Component\Signature\Algorithm\RS256; +use Jose\Component\Signature\Algorithm\RS384; +use Jose\Component\Signature\Algorithm\RS512; +use Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor; +use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor; +use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor; +use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler; +use Symfony\Component\Security\Http\AccessToken\Oidc\OidcUserInfoTokenHandler; +use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor; +use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.access_token_extractor.header', HeaderAccessTokenExtractor::class) + ->set('security.access_token_extractor.query_string', QueryAccessTokenExtractor::class) + ->set('security.access_token_extractor.request_body', FormEncodedBodyExtractor::class) + + ->set('security.authenticator.access_token', AccessTokenAuthenticator::class) + ->abstract() + ->args([ + abstract_arg('access token handler'), + abstract_arg('access token extractor'), + null, + null, + null, + null, + ]) + + ->set('security.authenticator.access_token.chain_extractor', ChainAccessTokenExtractor::class) + ->abstract() + ->args([ + abstract_arg('access token extractors'), + ]) + + // OIDC + ->set('security.access_token_handler.oidc_user_info.http_client', HttpClientInterface::class) + ->abstract() + ->factory([service('http_client'), 'withOptions']) + ->args([abstract_arg('http client options')]) + + ->set('security.access_token_handler.oidc_user_info', OidcUserInfoTokenHandler::class) + ->abstract() + ->args([ + abstract_arg('http client'), + service('logger')->nullOnInvalid(), + abstract_arg('claim'), + ]) + + ->set('security.access_token_handler.oidc', OidcTokenHandler::class) + ->abstract() + ->args([ + abstract_arg('signature algorithm'), + abstract_arg('signature key'), + abstract_arg('audience'), + abstract_arg('issuers'), + 'sub', + service('logger')->nullOnInvalid(), + service('clock'), + ]) + + ->set('security.access_token_handler.oidc.jwk', JWK::class) + ->abstract() + ->deprecate('symfony/security-http', '7.1', 'The "%service_id%" service is deprecated. Please use "security.access_token_handler.oidc.jwkset" instead') + ->factory([JWK::class, 'createFromJson']) + ->args([ + abstract_arg('signature key'), + ]) + + ->set('security.access_token_handler.oidc.jwkset', JWKSet::class) + ->abstract() + ->factory([JWKSet::class, 'createFromJson']) + ->args([ + abstract_arg('signature keyset'), + ]) + + ->set('security.access_token_handler.oidc.algorithm_manager_factory', AlgorithmManagerFactory::class) + ->args([ + tagged_iterator('security.access_token_handler.oidc.signature_algorithm'), + ]) + + ->set('security.access_token_handler.oidc.signature', AlgorithmManager::class) + ->abstract() + ->factory([service('security.access_token_handler.oidc.algorithm_manager_factory'), 'create']) + ->args([ + abstract_arg('signature algorithms'), + ]) + + ->set('security.access_token_handler.oidc.signature.ES256', ES256::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.ES384', ES384::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.ES512', ES512::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.RS256', RS256::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.RS384', RS384::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.RS512', RS512::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.PS256', PS256::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.PS384', PS384::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + + ->set('security.access_token_handler.oidc.signature.PS512', PS512::class) + ->tag('security.access_token_handler.oidc.signature_algorithm') + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/security_authenticator_login_link.php b/vendor/symfony/security-bundle/Resources/config/security_authenticator_login_link.php new file mode 100644 index 0000000..9a46a09 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/security_authenticator_login_link.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\LoginLink\FirewallAwareLoginLinkHandler; +use Symfony\Component\Security\Core\Signature\ExpiredSignatureStorage; +use Symfony\Component\Security\Core\Signature\SignatureHasher; +use Symfony\Component\Security\Http\Authenticator\LoginLinkAuthenticator; +use Symfony\Component\Security\Http\LoginLink\LoginLinkHandler; +use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.authenticator.login_link', LoginLinkAuthenticator::class) + ->abstract() + ->args([ + abstract_arg('the login link handler instance'), + service('security.http_utils'), + abstract_arg('authentication success handler'), + abstract_arg('authentication failure handler'), + abstract_arg('options'), + ]) + + ->set('security.authenticator.abstract_login_link_handler', LoginLinkHandler::class) + ->abstract() + ->args([ + service('router'), + abstract_arg('user provider'), + abstract_arg('signature hasher'), + abstract_arg('options'), + ]) + + ->set('security.authenticator.abstract_login_link_signature_hasher', SignatureHasher::class) + ->args([ + service('property_accessor'), + abstract_arg('signature properties'), + '%kernel.secret%', + abstract_arg('expired signature storage'), + abstract_arg('max signature uses'), + ]) + + ->set('security.authenticator.expired_login_link_storage', ExpiredSignatureStorage::class) + ->abstract() + ->args([ + abstract_arg('cache pool service'), + abstract_arg('expired login link storage'), + ]) + + ->set('security.authenticator.cache.expired_links') + ->parent('cache.app') + ->private() + + ->set('security.authenticator.firewall_aware_login_link_handler', FirewallAwareLoginLinkHandler::class) + ->args([ + service('security.firewall.map'), + tagged_locator('security.authenticator.login_linker', 'firewall'), + service('request_stack'), + ]) + ->alias(LoginLinkHandlerInterface::class, 'security.authenticator.firewall_aware_login_link_handler') + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/security_authenticator_remember_me.php b/vendor/symfony/security-bundle/Resources/config/security_authenticator_remember_me.php new file mode 100644 index 0000000..b861d0d --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/security_authenticator_remember_me.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\RememberMe\FirewallAwareRememberMeHandler; +use Symfony\Component\Security\Core\Signature\SignatureHasher; +use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator; +use Symfony\Component\Security\Http\EventListener\CheckRememberMeConditionsListener; +use Symfony\Component\Security\Http\EventListener\RememberMeListener; +use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler; +use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; +use Symfony\Component\Security\Http\RememberMe\ResponseListener; +use Symfony\Component\Security\Http\RememberMe\SignatureRememberMeHandler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.rememberme.response_listener', ResponseListener::class) + ->tag('kernel.event_subscriber') + + ->set('security.authenticator.remember_me_signature_hasher', SignatureHasher::class) + ->args([ + service('property_accessor'), + abstract_arg('signature properties'), + '%kernel.secret%', + null, + null, + ]) + + ->set('security.authenticator.signature_remember_me_handler', SignatureRememberMeHandler::class) + ->abstract() + ->args([ + abstract_arg('signature hasher'), + abstract_arg('user provider'), + service('request_stack'), + abstract_arg('options'), + service('logger')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authenticator.persistent_remember_me_handler', PersistentRememberMeHandler::class) + ->abstract() + ->args([ + abstract_arg('token provider'), + abstract_arg('user provider'), + service('request_stack'), + abstract_arg('options'), + service('logger')->nullOnInvalid(), + abstract_arg('token verifier'), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authenticator.firewall_aware_remember_me_handler', FirewallAwareRememberMeHandler::class) + ->args([ + service('security.firewall.map'), + tagged_locator('security.remember_me_handler', 'firewall'), + service('request_stack'), + ]) + ->alias(RememberMeHandlerInterface::class, 'security.authenticator.firewall_aware_remember_me_handler') + + ->set('security.listener.check_remember_me_conditions', CheckRememberMeConditionsListener::class) + ->abstract() + ->args([ + abstract_arg('options'), + service('logger')->nullOnInvalid(), + ]) + + ->set('security.listener.remember_me', RememberMeListener::class) + ->abstract() + ->args([ + abstract_arg('remember me handler'), + service('logger')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authenticator.remember_me', RememberMeAuthenticator::class) + ->abstract() + ->args([ + abstract_arg('remember me handler'), + param('kernel.secret'), + service('security.token_storage'), + abstract_arg('options'), + service('logger')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + // Cache + ->set('cache.security_token_verifier') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/security_debug.php b/vendor/symfony/security-bundle/Resources/config/security_debug.php new file mode 100644 index 0000000..c98e3a6 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/security_debug.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener; +use Symfony\Bundle\SecurityBundle\EventListener\VoteListener; +use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.security.access.decision_manager', TraceableAccessDecisionManager::class) + ->decorate('security.access.decision_manager') + ->args([ + service('debug.security.access.decision_manager.inner'), + ]) + + ->set('debug.security.voter.vote_listener', VoteListener::class) + ->args([ + service('debug.security.access.decision_manager'), + ]) + ->tag('kernel.event_subscriber') + + ->set('debug.security.firewall', TraceableFirewallListener::class) + ->args([ + service('security.firewall.map'), + service('event_dispatcher'), + service('security.logout_url_generator'), + ]) + ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => 'reset']) + ->alias('security.firewall', 'debug.security.firewall') + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/security_listeners.php b/vendor/symfony/security-bundle/Resources/config/security_listeners.php new file mode 100644 index 0000000..952b1d7 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/security_listeners.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Security\Http\AccessMap; +use Symfony\Component\Security\Http\Authentication\CustomAuthenticationFailureHandler; +use Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; +use Symfony\Component\Security\Http\EventListener\ClearSiteDataLogoutListener; +use Symfony\Component\Security\Http\EventListener\CookieClearingLogoutListener; +use Symfony\Component\Security\Http\EventListener\DefaultLogoutListener; +use Symfony\Component\Security\Http\EventListener\SessionLogoutListener; +use Symfony\Component\Security\Http\Firewall\AccessListener; +use Symfony\Component\Security\Http\Firewall\ChannelListener; +use Symfony\Component\Security\Http\Firewall\ContextListener; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; +use Symfony\Component\Security\Http\Firewall\SwitchUserListener; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('security.channel_listener', ChannelListener::class) + ->args([ + service('security.access_map'), + service('logger')->nullOnInvalid(), + inline_service('int')->factory([service('router.request_context'), 'getHttpPort']), + inline_service('int')->factory([service('router.request_context'), 'getHttpsPort']), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.access_map', AccessMap::class) + + ->set('security.context_listener', ContextListener::class) + ->args([ + service('security.untracked_token_storage'), + [], + abstract_arg('Provider Key'), + service('logger')->nullOnInvalid(), + service('event_dispatcher')->nullOnInvalid(), + service('security.authentication.trust_resolver'), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.logout_listener', LogoutListener::class) + ->abstract() + ->args([ + service('security.token_storage'), + service('security.http_utils'), + abstract_arg('event dispatcher'), + [], // Options + ]) + + ->set('security.logout.listener.session', SessionLogoutListener::class) + ->abstract() + + ->set('security.logout.listener.clear_site_data', ClearSiteDataLogoutListener::class) + ->abstract() + + ->set('security.logout.listener.cookie_clearing', CookieClearingLogoutListener::class) + ->abstract() + + ->set('security.logout.listener.default', DefaultLogoutListener::class) + ->abstract() + ->args([ + service('security.http_utils'), + abstract_arg('target url'), + ]) + + ->set('security.authentication.listener.abstract') + ->abstract() + ->args([ + service('security.token_storage'), + service('security.authentication.manager'), + service('security.authentication.session_strategy'), + service('security.http_utils'), + abstract_arg('Provider-shared Key'), + service('security.authentication.success_handler'), + service('security.authentication.failure_handler'), + [], + service('logger')->nullOnInvalid(), + service('event_dispatcher')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authentication.custom_success_handler', CustomAuthenticationSuccessHandler::class) + ->abstract() + ->args([ + abstract_arg('The custom success handler service'), + [], // Options + abstract_arg('Provider-shared Key'), + ]) + + ->set('security.authentication.success_handler', DefaultAuthenticationSuccessHandler::class) + ->abstract() + ->args([ + service('security.http_utils'), + [], // Options + service('logger')->nullOnInvalid(), + ]) + + ->set('security.authentication.custom_failure_handler', CustomAuthenticationFailureHandler::class) + ->abstract() + ->args([ + abstract_arg('The custom failure handler service'), + [], // Options + ]) + + ->set('security.authentication.failure_handler', DefaultAuthenticationFailureHandler::class) + ->abstract() + ->args([ + service('http_kernel'), + service('security.http_utils'), + [], // Options + service('logger')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.exception_listener', ExceptionListener::class) + ->abstract() + ->args([ + service('security.token_storage'), + service('security.authentication.trust_resolver'), + service('security.http_utils'), + abstract_arg('Provider-shared Key'), + service('security.authentication.entry_point')->nullOnInvalid(), + param('security.access.denied_url'), + service('security.access.denied_handler')->nullOnInvalid(), + service('logger')->nullOnInvalid(), + false, // Stateless + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.authentication.switchuser_listener', SwitchUserListener::class) + ->abstract() + ->args([ + service('security.token_storage'), + abstract_arg('User Provider'), + abstract_arg('User Checker'), + abstract_arg('Provider Key'), + service('security.access.decision_manager'), + service('logger')->nullOnInvalid(), + '_switch_user', + 'ROLE_ALLOWED_TO_SWITCH', + service('event_dispatcher')->nullOnInvalid(), + false, // Stateless + service('router')->nullOnInvalid(), + abstract_arg('Target Route'), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.access_listener', AccessListener::class) + ->args([ + service('security.token_storage'), + service('security.access.decision_manager'), + service('security.access_map'), + ]) + ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.firewall.event_dispatcher_locator', ServiceLocator::class) + ->args([[]]) + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/config/templating_twig.php b/vendor/symfony/security-bundle/Resources/config/templating_twig.php new file mode 100644 index 0000000..05a74d0 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/config/templating_twig.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\LogoutUrlExtension; +use Symfony\Bridge\Twig\Extension\SecurityExtension; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig.extension.logout_url', LogoutUrlExtension::class) + ->args([ + service('security.logout_url_generator'), + ]) + ->tag('twig.extension') + + ->set('twig.extension.security', SecurityExtension::class) + ->args([ + service('security.authorization_checker')->ignoreOnInvalid(), + service('security.impersonate_url_generator')->ignoreOnInvalid(), + ]) + ->tag('twig.extension') + ; +}; diff --git a/vendor/symfony/security-bundle/Resources/views/Collector/icon.svg b/vendor/symfony/security-bundle/Resources/views/Collector/icon.svg new file mode 100644 index 0000000..10cc243 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/views/Collector/icon.svg @@ -0,0 +1,6 @@ + + Security + + + + diff --git a/vendor/symfony/security-bundle/Resources/views/Collector/security.html.twig b/vendor/symfony/security-bundle/Resources/views/Collector/security.html.twig new file mode 100644 index 0000000..4dd0b02 --- /dev/null +++ b/vendor/symfony/security-bundle/Resources/views/Collector/security.html.twig @@ -0,0 +1,506 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block page_title 'Security' %} + +{% block head %} + {{ parent() }} + + +{% endblock %} + +{% block toolbar %} + {% if collector.firewall %} + {% set icon %} + {{ source('@Security/Collector/icon.svg') }} + {{ collector.user|default('n/a') }} + {% endset %} + + {% set text %} + {% if collector.impersonated %} +
    +
    + Impersonator + {{ collector.impersonatorUser }} +
    +
    + {% endif %} + +
    + {% if collector.enabled %} + {% if collector.token %} +
    + Logged in as + {{ collector.user }} +
    + +
    + Authenticated + {{ collector.authenticated ? 'Yes' : 'No' }} +
    + +
    + Roles + + {% set remainingRoles = collector.roles|slice(1) %} + {{ collector.roles|first }} + {% if remainingRoles is not empty %} + + + + {{ remainingRoles|length }} more + + {% endif %} + +
    + + {% if collector.supportsRoleHierarchy %} +
    + Inherited Roles + + {% if collector.inheritedRoles is empty %} + none + {% else %} + {% set remainingRoles = collector.inheritedRoles|slice(1) %} + {{ collector.inheritedRoles|first }} + {% if remainingRoles is not empty %} + + + + {{ remainingRoles|length }} more + + {% endif %} + {% endif %} + +
    + {% endif %} + +
    + Token class + {{ collector.tokenClass|abbr_class }} +
    + {% else %} +
    + Authenticated + No +
    + {% endif %} + + {% if collector.firewall %} +
    + Firewall name + {{ collector.firewall.name }} +
    + {% endif %} + + {% if collector.token and collector.logoutUrl %} +
    + Actions + + Logout + {% if collector.impersonated and collector.impersonationExitPath %} + | Exit impersonation + {% endif %} + +
    + {% endif %} + {% else %} +
    + The security is disabled. +
    + {% endif %} +
    + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }} + {% endif %} +{% endblock %} + +{% block menu %} + + {{ source('@Security/Collector/icon.svg') }} + Security + +{% endblock %} + +{% block panel %} +

    Security

    + {% if collector.enabled %} +
    +
    +

    Token

    + +
    + {% if collector.token %} +
    +
    + {{ collector.user }} + Username +
    + +
    + {{ source('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} + Authenticated +
    +
    + + + + + + + + + + + + + + + {% if collector.supportsRoleHierarchy %} + + + + + {% endif %} + + {% if collector.token %} + + + + + {% endif %} + +
    PropertyValue
    Roles + {{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }} + + {% if not collector.authenticated and collector.roles is empty %} +

    User is not authenticated probably because they have no roles.

    + {% endif %} +
    Inherited Roles{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}
    Token{{ profiler_dump(collector.token) }}
    + {% elseif collector.enabled %} +
    +

    There is no security token.

    +
    + {% endif %} +
    +
    + +
    +

    Firewall

    +
    + {% if collector.firewall %} +
    +
    + {{ collector.firewall.name }} + Name +
    +
    + {{ source('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }} + Security enabled +
    +
    + {{ source('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }} + Stateless +
    +
    + + {% if collector.firewall.security_enabled %} +

    Configuration

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyValue
    provider{{ collector.firewall.provider ?: '(none)' }}
    context{{ collector.firewall.context ?: '(none)' }}
    entry_point{{ collector.firewall.entry_point ?: '(none)' }}
    user_checker{{ collector.firewall.user_checker ?: '(none)' }}
    access_denied_handler{{ collector.firewall.access_denied_handler ?: '(none)' }}
    access_denied_url{{ collector.firewall.access_denied_url ?: '(none)' }}
    authenticators{{ collector.firewall.authenticators is empty ? '(none)' : profiler_dump(collector.firewall.authenticators, maxDepth=1) }}
    + {% endif %} + {% endif %} +
    +
    + +
    +

    Listeners

    +
    + {% if collector.listeners|default([]) is empty %} +
    +

    No security listeners have been recorded. Check that debugging is enabled in the kernel.

    +
    + {% else %} + + + + + + + + + + {% set previous_event = (collector.listeners|first) %} + {% for listener in collector.listeners %} + {% if loop.first or listener != previous_event %} + {% if not loop.first %} + + {% endif %} + + {% set previous_event = listener %} + {% endif %} + + + + + + + + {% if loop.last %} + + {% endif %} + {% endfor %} +
    ListenerDurationResponse
    {{ profiler_dump(listener.stub) }}{{ '%0.2f'|format(listener.time * 1000) }} ms{{ listener.response ? profiler_dump(listener.response) : '(none)' }}
    + {% endif %} +
    +
    + +
    +

    Authenticators

    +
    + {% if collector.authenticators|default([]) is not empty %} + + + + + + + + + + + + + {% set previous_event = (collector.listeners|first) %} + {% for authenticator in collector.authenticators %} + {% if loop.first or authenticator != previous_event %} + {% if not loop.first %} + + {% endif %} + + + {% set previous_event = authenticator %} + {% endif %} + + + + + + + + + + + {% if loop.last %} + + {% endif %} + {% endfor %} +
    AuthenticatorSupportsAuthenticatedDurationPassportBadges
    {{ profiler_dump(authenticator.stub) }}{{ source('@WebProfiler/Icon/' ~ (authenticator.supports ? 'yes' : 'no') ~ '.svg') }}{{ authenticator.authenticated is not null ? source('@WebProfiler/Icon/' ~ (authenticator.authenticated ? 'yes' : 'no') ~ '.svg') : '' }}{{ '%0.2f'|format(authenticator.duration * 1000) }} ms{{ authenticator.passport ? profiler_dump(authenticator.passport) : '(none)' }} + {% for badge in authenticator.badges ?? [] %} + + {{ badge.stub|abbr_class }} + + {% else %} + (none) + {% endfor %} +
    + {% else %} +
    +

    No authenticators have been recorded. Check previous profiles on your authentication endpoint.

    +
    + {% endif %} +
    +
    + +
    +

    Access Decision

    +
    + {% if collector.voters|default([]) is not empty %} +
    +
    + {{ collector.voterStrategy|default('unknown') }} + Strategy +
    +
    + + + + + + + + + + + {% for voter in collector.voters %} + + + + + {% endfor %} + +
    #Voter class
    {{ loop.index }}{{ profiler_dump(voter) }}
    + {% endif %} + {% if collector.accessDecisionLog|default([]) is not empty %} +

    Access decision log

    + + + + + + + + + + + + + + + + + + {% for decision in collector.accessDecisionLog %} + + + + + + + + + + + {% endfor %} + +
    #ResultAttributesObject
    {{ loop.index }} + {{ decision.result + ? 'GRANTED' + : 'DENIED' + }} + + {% if decision.attributes|length == 1 %} + {% set attribute = decision.attributes|first %} + {% if attribute.expression is defined %} + Expression:
    {{ attribute.expression }}
    + {% elseif attribute.type == 'string' %} + {{ attribute }} + {% else %} + {{ profiler_dump(attribute) }} + {% endif %} + {% else %} + {{ profiler_dump(decision.attributes) }} + {% endif %} +
    {{ profiler_dump(decision.seek('object')) }}
    + {% if decision.voter_details is not empty %} + {% set voter_details_id = 'voter-details-' ~ loop.index %} +
    + + + {% for voter_detail in decision.voter_details %} + + + {% if collector.voterStrategy == 'unanimous' %} + + {% endif %} + + + {% endfor %} + +
    {{ profiler_dump(voter_detail['class']) }}attribute {{ voter_detail['attributes'][0] }} + {% if voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_GRANTED') %} + ACCESS GRANTED + {% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_ABSTAIN') %} + ACCESS ABSTAIN + {% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_DENIED') %} + ACCESS DENIED + {% else %} + unknown ({{ voter_detail['vote'] }}) + {% endif %} +
    +
    + Show voter details + {% endif %} +
    +
    + {% endif %} +
    +
    + {% endif %} +{% endblock %} diff --git a/vendor/symfony/security-bundle/Routing/LogoutRouteLoader.php b/vendor/symfony/security-bundle/Routing/LogoutRouteLoader.php new file mode 100644 index 0000000..637b80e --- /dev/null +++ b/vendor/symfony/security-bundle/Routing/LogoutRouteLoader.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Routing; + +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +final class LogoutRouteLoader +{ + /** + * @param array $logoutUris Logout URIs indexed by the corresponding firewall name + * @param string $parameterName Name of the container parameter containing {@see $logoutUris} value + */ + public function __construct( + private readonly array $logoutUris, + private readonly string $parameterName, + ) { + } + + public function __invoke(): RouteCollection + { + $collection = new RouteCollection(); + $collection->addResource(new ContainerParametersResource([$this->parameterName => $this->logoutUris])); + + $routeNames = []; + foreach ($this->logoutUris as $firewallName => $logoutPath) { + $routeName = '_logout_'.$firewallName; + + if (isset($routeNames[$logoutPath])) { + $collection->addAlias($routeName, $routeNames[$logoutPath]); + } else { + $routeNames[$logoutPath] = $routeName; + $collection->add($routeName, new Route($logoutPath)); + } + } + + return $collection; + } +} diff --git a/vendor/symfony/security-bundle/Security.php b/vendor/symfony/security-bundle/Security.php new file mode 100644 index 0000000..e0aa004 --- /dev/null +++ b/vendor/symfony/security-bundle/Security.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\LogicException; +use Symfony\Component\Security\Core\Exception\LogoutException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * Helper class for commonly-needed security tasks. + * + * @author Ryan Weaver + * @author Robin Chalas + * @author Arnaud Frézet + * + * @final + */ +class Security implements AuthorizationCheckerInterface +{ + public function __construct( + private readonly ContainerInterface $container, + private readonly array $authenticators = [], + ) { + } + + public function getUser(): ?UserInterface + { + if (!$token = $this->getToken()) { + return null; + } + + return $token->getUser(); + } + + /** + * Checks if the attributes are granted against the current authentication token and optionally supplied subject. + */ + public function isGranted(mixed $attributes, mixed $subject = null): bool + { + return $this->container->get('security.authorization_checker') + ->isGranted($attributes, $subject); + } + + public function getToken(): ?TokenInterface + { + return $this->container->get('security.token_storage')->getToken(); + } + + public function getFirewallConfig(Request $request): ?FirewallConfig + { + return $this->container->get('security.firewall.map')->getFirewallConfig($request); + } + + /** + * @param UserInterface $user The user to authenticate + * @param string|null $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured + * @param string|null $firewallName The firewall name - required only if multiple firewalls are configured + * @param BadgeInterface[] $badges Badges to add to the user's passport + * + * @return Response|null The authenticator success response if any + */ + public function login(UserInterface $user, ?string $authenticatorName = null, ?string $firewallName = null, array $badges = []): ?Response + { + $request = $this->container->get('request_stack')->getCurrentRequest(); + if (null === $request) { + throw new LogicException('Unable to login without a request context.'); + } + + $firewallName ??= $this->getFirewallConfig($request)?->getName(); + + if (!$firewallName) { + throw new LogicException('Unable to login as the current route is not covered by any firewall.'); + } + + $authenticator = $this->getAuthenticator($authenticatorName, $firewallName); + + $userCheckerLocator = $this->container->get('security.user_checker_locator'); + $userCheckerLocator->get($firewallName)->checkPreAuth($user); + + return $this->container->get('security.authenticator.managers_locator')->get($firewallName)->authenticateUser($user, $authenticator, $request, $badges); + } + + /** + * Logout the current user by dispatching the LogoutEvent. + * + * @param bool $validateCsrfToken Whether to look for a valid CSRF token based on the `logout` listener configuration + * + * @return Response|null The LogoutEvent's Response if any + * + * @throws LogoutException When $validateCsrfToken is true and the CSRF token is not found or invalid + */ + public function logout(bool $validateCsrfToken = true): ?Response + { + $request = $this->container->get('request_stack')->getMainRequest(); + if (null === $request) { + throw new LogicException('Unable to logout without a request context.'); + } + + /** @var TokenStorageInterface $tokenStorage */ + $tokenStorage = $this->container->get('security.token_storage'); + + if (!($token = $tokenStorage->getToken()) || !$token->getUser()) { + throw new LogicException('Unable to logout as there is no logged-in user.'); + } + + if (!$firewallConfig = $this->container->get('security.firewall.map')->getFirewallConfig($request)) { + throw new LogicException('Unable to logout as the request is not behind a firewall.'); + } + + if ($validateCsrfToken) { + if (!$this->container->has('security.csrf.token_manager') || !$logoutConfig = $firewallConfig->getLogout()) { + throw new LogicException(sprintf('Unable to logout with CSRF token validation. Either make sure that CSRF protection is enabled and "logout" is configured on the "%s" firewall, or bypass CSRF token validation explicitly by passing false to the $validateCsrfToken argument of this method.', $firewallConfig->getName())); + } + $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $logoutConfig['csrf_parameter']); + if (!\is_string($csrfToken) || !$this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) { + throw new LogoutException('Invalid CSRF token.'); + } + } + + $logoutEvent = new LogoutEvent($request, $token); + $this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent); + + $tokenStorage->setToken(null); + + return $logoutEvent->getResponse(); + } + + private function getAuthenticator(?string $authenticatorName, string $firewallName): AuthenticatorInterface + { + if (!isset($this->authenticators[$firewallName])) { + throw new LogicException(sprintf('No authenticators found for firewall "%s".', $firewallName)); + } + + /** @var ServiceProviderInterface $firewallAuthenticatorLocator */ + $firewallAuthenticatorLocator = $this->authenticators[$firewallName]; + + if (!$authenticatorName) { + $authenticatorIds = array_keys($firewallAuthenticatorLocator->getProvidedServices()); + + if (!$authenticatorIds) { + throw new LogicException(sprintf('No authenticator was found for the firewall "%s".', $firewallName)); + } + if (1 < \count($authenticatorIds)) { + throw new LogicException(sprintf('Too many authenticators were found for the current firewall "%s". You must provide an instance of "%s" to login programmatically. The available authenticators for the firewall "%s" are "%s".', $firewallName, AuthenticatorInterface::class, $firewallName, implode('" ,"', $authenticatorIds))); + } + + return $firewallAuthenticatorLocator->get($authenticatorIds[0]); + } + + if ($firewallAuthenticatorLocator->has($authenticatorName)) { + return $firewallAuthenticatorLocator->get($authenticatorName); + } + + $authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName; + + if (!$firewallAuthenticatorLocator->has($authenticatorId)) { + throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".', $authenticatorName, $firewallName, implode('", "', array_keys($firewallAuthenticatorLocator->getProvidedServices())))); + } + + return $firewallAuthenticatorLocator->get($authenticatorId); + } +} diff --git a/vendor/symfony/security-bundle/Security/FirewallAwareTrait.php b/vendor/symfony/security-bundle/Security/FirewallAwareTrait.php new file mode 100644 index 0000000..c5f0451 --- /dev/null +++ b/vendor/symfony/security-bundle/Security/FirewallAwareTrait.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Provides basic functionality for services mapped by the firewall name + * in a container locator. + * + * @author Wouter de Jong + * + * @internal + */ +trait FirewallAwareTrait +{ + private ContainerInterface $locator; + private RequestStack $requestStack; + private FirewallMap $firewallMap; + + private function getForFirewall(): object + { + $serviceIdentifier = str_replace('FirewallAware', '', static::class); + if (null === $request = $this->requestStack->getCurrentRequest()) { + throw new \LogicException('Cannot determine the correct '.$serviceIdentifier.' to use: there is no active Request and so, the firewall cannot be determined. Try using a specific '.$serviceIdentifier.' service.'); + } + + $firewall = $this->firewallMap->getFirewallConfig($request); + if (!$firewall) { + throw new \LogicException('No '.$serviceIdentifier.' found as the current route is not covered by a firewall.'); + } + + $firewallName = $firewall->getName(); + if (!$this->locator->has($firewallName)) { + $message = 'No '.$serviceIdentifier.' found for this firewall.'; + if (\defined(static::class.'::FIREWALL_OPTION')) { + $message .= sprintf(' Did you forget to add a "'.static::FIREWALL_OPTION.'" key under your "%s" firewall?', $firewallName); + } + + throw new \LogicException($message); + } + + return $this->locator->get($firewallName); + } +} diff --git a/vendor/symfony/security-bundle/Security/FirewallConfig.php b/vendor/symfony/security-bundle/Security/FirewallConfig.php new file mode 100644 index 0000000..16edc63 --- /dev/null +++ b/vendor/symfony/security-bundle/Security/FirewallConfig.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +/** + * @author Robin Chalas + */ +final class FirewallConfig +{ + public function __construct( + private readonly string $name, + private readonly string $userChecker, + private readonly ?string $requestMatcher = null, + private readonly bool $securityEnabled = true, + private readonly bool $stateless = false, + private readonly ?string $provider = null, + private readonly ?string $context = null, + private readonly ?string $entryPoint = null, + private readonly ?string $accessDeniedHandler = null, + private readonly ?string $accessDeniedUrl = null, + private readonly array $authenticators = [], + private readonly ?array $switchUser = null, + private readonly ?array $logout = null, + ) { + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return string|null The request matcher service id or null if neither the request matcher, pattern or host + * options were provided + */ + public function getRequestMatcher(): ?string + { + return $this->requestMatcher; + } + + public function isSecurityEnabled(): bool + { + return $this->securityEnabled; + } + + public function isStateless(): bool + { + return $this->stateless; + } + + public function getProvider(): ?string + { + return $this->provider; + } + + /** + * @return string|null The context key (will be null if the firewall is stateless) + */ + public function getContext(): ?string + { + return $this->context; + } + + public function getEntryPoint(): ?string + { + return $this->entryPoint; + } + + public function getUserChecker(): string + { + return $this->userChecker; + } + + public function getAccessDeniedHandler(): ?string + { + return $this->accessDeniedHandler; + } + + public function getAccessDeniedUrl(): ?string + { + return $this->accessDeniedUrl; + } + + public function getAuthenticators(): array + { + return $this->authenticators; + } + + public function getSwitchUser(): ?array + { + return $this->switchUser; + } + + public function getLogout(): ?array + { + return $this->logout; + } +} diff --git a/vendor/symfony/security-bundle/Security/FirewallContext.php b/vendor/symfony/security-bundle/Security/FirewallContext.php new file mode 100644 index 0000000..c100d35 --- /dev/null +++ b/vendor/symfony/security-bundle/Security/FirewallContext.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; + +/** + * This is a wrapper around the actual firewall configuration which allows us + * to lazy load the context for one specific firewall only when we need it. + * + * @author Johannes M. Schmitt + */ +class FirewallContext +{ + private iterable $listeners; + private ?ExceptionListener $exceptionListener; + private ?LogoutListener $logoutListener; + private ?FirewallConfig $config; + + /** + * @param iterable $listeners + */ + public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null, ?FirewallConfig $config = null) + { + $this->listeners = $listeners; + $this->exceptionListener = $exceptionListener; + $this->logoutListener = $logoutListener; + $this->config = $config; + } + + public function getConfig(): ?FirewallConfig + { + return $this->config; + } + + /** + * @return iterable + */ + public function getListeners(): iterable + { + return $this->listeners; + } + + public function getExceptionListener(): ?ExceptionListener + { + return $this->exceptionListener; + } + + public function getLogoutListener(): ?LogoutListener + { + return $this->logoutListener; + } +} diff --git a/vendor/symfony/security-bundle/Security/FirewallMap.php b/vendor/symfony/security-bundle/Security/FirewallMap.php new file mode 100644 index 0000000..2c4f85c --- /dev/null +++ b/vendor/symfony/security-bundle/Security/FirewallMap.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Http\FirewallMapInterface; + +/** + * This is a lazy-loading firewall map implementation. + * + * Listeners will only be initialized if we really need them. + * + * @author Johannes M. Schmitt + */ +class FirewallMap implements FirewallMapInterface +{ + private ContainerInterface $container; + private iterable $map; + + public function __construct(ContainerInterface $container, iterable $map) + { + $this->container = $container; + $this->map = $map; + } + + public function getListeners(Request $request): array + { + $context = $this->getFirewallContext($request); + + if (null === $context) { + return [[], null, null]; + } + + return [$context->getListeners(), $context->getExceptionListener(), $context->getLogoutListener()]; + } + + public function getFirewallConfig(Request $request): ?FirewallConfig + { + return $this->getFirewallContext($request)?->getConfig(); + } + + private function getFirewallContext(Request $request): ?FirewallContext + { + if ($request->attributes->has('_firewall_context')) { + $storedContextId = $request->attributes->get('_firewall_context'); + foreach ($this->map as $contextId => $requestMatcher) { + if ($contextId === $storedContextId) { + return $this->container->get($contextId); + } + } + + $request->attributes->remove('_firewall_context'); + } + + foreach ($this->map as $contextId => $requestMatcher) { + if (null === $requestMatcher || $requestMatcher->matches($request)) { + $request->attributes->set('_firewall_context', $contextId); + + return $this->container->get($contextId); + } + } + + return null; + } +} diff --git a/vendor/symfony/security-bundle/Security/LazyFirewallContext.php b/vendor/symfony/security-bundle/Security/LazyFirewallContext.php new file mode 100644 index 0000000..500b29b --- /dev/null +++ b/vendor/symfony/security-bundle/Security/LazyFirewallContext.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Http\Event\LazyResponseEvent; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; +use Symfony\Component\Security\Http\Firewall\LogoutListener; + +/** + * Lazily calls authentication listeners when actually required by the access listener. + * + * @author Nicolas Grekas + */ +class LazyFirewallContext extends FirewallContext +{ + private TokenStorage $tokenStorage; + + public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage) + { + parent::__construct($listeners, $exceptionListener, $logoutListener, $config); + + $this->tokenStorage = $tokenStorage; + } + + public function getListeners(): iterable + { + return [$this]; + } + + public function __invoke(RequestEvent $event): void + { + $listeners = []; + $request = $event->getRequest(); + $lazy = $request->isMethodCacheable(); + + foreach (parent::getListeners() as $listener) { + if (!$lazy || !$listener instanceof FirewallListenerInterface) { + $listeners[] = $listener; + $lazy = $lazy && $listener instanceof FirewallListenerInterface; + } elseif (false !== $supports = $listener->supports($request)) { + $listeners[] = [$listener, 'authenticate']; + $lazy = null === $supports; + } + } + + if (!$lazy) { + foreach ($listeners as $listener) { + $listener($event); + + if ($event->hasResponse()) { + return; + } + } + + return; + } + + $this->tokenStorage->setInitializer(function () use ($event, $listeners) { + $event = new LazyResponseEvent($event); + foreach ($listeners as $listener) { + $listener($event); + } + }); + } +} diff --git a/vendor/symfony/security-bundle/Security/UserAuthenticator.php b/vendor/symfony/security-bundle/Security/UserAuthenticator.php new file mode 100644 index 0000000..7864578 --- /dev/null +++ b/vendor/symfony/security-bundle/Security/UserAuthenticator.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; + +/** + * A decorator that delegates all method calls to the authenticator + * manager of the current firewall. + * + * @author Wouter de Jong + * + * @final + */ +class UserAuthenticator implements UserAuthenticatorInterface +{ + use FirewallAwareTrait; + + public function __construct(FirewallMap $firewallMap, ContainerInterface $userAuthenticators, RequestStack $requestStack) + { + $this->firewallMap = $firewallMap; + $this->locator = $userAuthenticators; + $this->requestStack = $requestStack; + } + + public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response + { + return $this->getForFirewall()->authenticateUser($user, $authenticator, $request, $badges); + } +} diff --git a/vendor/symfony/security-bundle/SecurityBundle.php b/vendor/symfony/security-bundle/SecurityBundle.php new file mode 100644 index 0000000..3247ff1 --- /dev/null +++ b/vendor/symfony/security-bundle/SecurityBundle.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle; + +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\CleanRememberMeVerifierPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\MakeFirewallsEventDispatcherTraceablePass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterEntryPointPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterLdapLocatorPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\CasTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\ServiceTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginLdapFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginLinkFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginThrottlingFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Security\Http\SecurityEvents; + +/** + * Bundle. + * + * @author Fabien Potencier + */ +class SecurityBundle extends Bundle +{ + public function build(ContainerBuilder $container): void + { + parent::build($container); + + /** @var SecurityExtension $extension */ + $extension = $container->getExtension('security'); + $extension->addAuthenticatorFactory(new FormLoginFactory()); + $extension->addAuthenticatorFactory(new FormLoginLdapFactory()); + $extension->addAuthenticatorFactory(new JsonLoginFactory()); + $extension->addAuthenticatorFactory(new JsonLoginLdapFactory()); + $extension->addAuthenticatorFactory(new HttpBasicFactory()); + $extension->addAuthenticatorFactory(new HttpBasicLdapFactory()); + $extension->addAuthenticatorFactory(new RememberMeFactory()); + $extension->addAuthenticatorFactory(new X509Factory()); + $extension->addAuthenticatorFactory(new RemoteUserFactory()); + $extension->addAuthenticatorFactory(new CustomAuthenticatorFactory()); + $extension->addAuthenticatorFactory(new LoginThrottlingFactory()); + $extension->addAuthenticatorFactory(new LoginLinkFactory()); + $extension->addAuthenticatorFactory(new AccessTokenFactory([ + new ServiceTokenHandlerFactory(), + new OidcUserInfoTokenHandlerFactory(), + new OidcTokenHandlerFactory(), + new CasTokenHandlerFactory(), + ])); + + $extension->addUserProviderFactory(new InMemoryFactory()); + $extension->addUserProviderFactory(new LdapFactory()); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + $container->addCompilerPass(new AddSecurityVotersPass()); + $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new CleanRememberMeVerifierPass()); + $container->addCompilerPass(new RegisterCsrfFeaturesPass()); + $container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200); + $container->addCompilerPass(new RegisterLdapLocatorPass()); + $container->addCompilerPass(new RegisterEntryPointPass()); + // must be registered after RegisterListenersPass (in the FrameworkBundle) + $container->addCompilerPass(new RegisterGlobalSecurityEventListenersPass(), PassConfig::TYPE_BEFORE_REMOVING, -200); + // execute after ResolveChildDefinitionsPass optimization pass, to ensure class names are set + $container->addCompilerPass(new SortFirewallListenersPass(), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new ReplaceDecoratedRememberMeHandlerPass(), PassConfig::TYPE_OPTIMIZE); + + $container->addCompilerPass(new AddEventAliasesPass(array_merge( + AuthenticationEvents::ALIASES, + SecurityEvents::ALIASES + ))); + + // must be registered before DecoratorServicePass + $container->addCompilerPass(new MakeFirewallsEventDispatcherTraceablePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); + } +} diff --git a/vendor/symfony/security-bundle/composer.json b/vendor/symfony/security-bundle/composer.json new file mode 100644 index 0000000..5c9cd95 --- /dev/null +++ b/vendor/symfony/security-bundle/composer.json @@ -0,0 +1,73 @@ +{ + "name": "symfony/security-bundle", + "type": "symfony-bundle", + "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4.11|^7.1.4", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^7.1", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.0.4", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/console": "<6.4", + "symfony/framework-bundle": "<6.4", + "symfony/http-client": "<6.4", + "symfony/ldap": "<6.4", + "symfony/serializer": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/security-core/Authentication/AuthenticationTrustResolver.php b/vendor/symfony/security-core/Authentication/AuthenticationTrustResolver.php new file mode 100644 index 0000000..513f0d5 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/AuthenticationTrustResolver.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication; + +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * The default implementation of the authentication trust resolver. + * + * @author Johannes M. Schmitt + */ +class AuthenticationTrustResolver implements AuthenticationTrustResolverInterface +{ + public function isAuthenticated(?TokenInterface $token = null): bool + { + return $token && $token->getUser(); + } + + public function isRememberMe(?TokenInterface $token = null): bool + { + return $token && $token instanceof RememberMeToken; + } + + public function isFullFledged(?TokenInterface $token = null): bool + { + return $this->isAuthenticated($token) && !$this->isRememberMe($token); + } +} diff --git a/vendor/symfony/security-core/Authentication/AuthenticationTrustResolverInterface.php b/vendor/symfony/security-core/Authentication/AuthenticationTrustResolverInterface.php new file mode 100644 index 0000000..b508290 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/AuthenticationTrustResolverInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Interface for resolving the authentication status of a given token. + * + * @author Johannes M. Schmitt + */ +interface AuthenticationTrustResolverInterface +{ + /** + * Resolves whether the passed token implementation is authenticated. + */ + public function isAuthenticated(?TokenInterface $token = null): bool; + + /** + * Resolves whether the passed token implementation is authenticated + * using remember-me capabilities. + */ + public function isRememberMe(?TokenInterface $token = null): bool; + + /** + * Resolves whether the passed token implementation is fully authenticated. + */ + public function isFullFledged(?TokenInterface $token = null): bool; +} diff --git a/vendor/symfony/security-core/Authentication/RememberMe/CacheTokenVerifier.php b/vendor/symfony/security-core/Authentication/RememberMe/CacheTokenVerifier.php new file mode 100644 index 0000000..e4f1362 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/RememberMe/CacheTokenVerifier.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Jordi Boggiano + */ +class CacheTokenVerifier implements TokenVerifierInterface +{ + private CacheItemPoolInterface $cache; + private int $outdatedTokenTtl; + private string $cacheKeyPrefix; + + /** + * @param int $outdatedTokenTtl How long the outdated token should still be considered valid. Defaults + * to 60, which matches how often the PersistentRememberMeHandler will at + * most refresh tokens. Increasing to more than that is not recommended, + * but you may use a lower value. + */ + public function __construct(CacheItemPoolInterface $cache, int $outdatedTokenTtl = 60, string $cacheKeyPrefix = 'rememberme-stale-') + { + $this->cache = $cache; + $this->outdatedTokenTtl = $outdatedTokenTtl; + $this->cacheKeyPrefix = $cacheKeyPrefix; + } + + public function verifyToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue): bool + { + if (hash_equals($token->getTokenValue(), $tokenValue)) { + return true; + } + + $cacheKey = $this->getCacheKey($token); + $item = $this->cache->getItem($cacheKey); + if (!$item->isHit()) { + return false; + } + + $outdatedToken = $item->get(); + + return hash_equals($outdatedToken, $tokenValue); + } + + public function updateExistingToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void + { + // When a token gets updated, persist the outdated token for $outdatedTokenTtl seconds so we can + // still accept it as valid in verifyToken + $item = $this->cache->getItem($this->getCacheKey($token)); + $item->set($token->getTokenValue()); + $item->expiresAfter($this->outdatedTokenTtl); + $this->cache->save($item); + } + + private function getCacheKey(PersistentTokenInterface $token): string + { + return $this->cacheKeyPrefix.rawurlencode($token->getSeries()); + } +} diff --git a/vendor/symfony/security-core/Authentication/RememberMe/InMemoryTokenProvider.php b/vendor/symfony/security-core/Authentication/RememberMe/InMemoryTokenProvider.php new file mode 100644 index 0000000..d7fbb9c --- /dev/null +++ b/vendor/symfony/security-core/Authentication/RememberMe/InMemoryTokenProvider.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; + +/** + * This class is used for testing purposes, and is not really suited for production. + * + * @author Johannes M. Schmitt + */ +final class InMemoryTokenProvider implements TokenProviderInterface +{ + private array $tokens = []; + + public function loadTokenBySeries(string $series): PersistentTokenInterface + { + if (!isset($this->tokens[$series])) { + throw new TokenNotFoundException('No token found.'); + } + + return $this->tokens[$series]; + } + + public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void + { + if (!isset($this->tokens[$series])) { + throw new TokenNotFoundException('No token found.'); + } + + $token = new PersistentToken( + $this->tokens[$series]->getClass(), + $this->tokens[$series]->getUserIdentifier(), + $series, + $tokenValue, + $lastUsed + ); + $this->tokens[$series] = $token; + } + + public function deleteTokenBySeries(string $series): void + { + unset($this->tokens[$series]); + } + + public function createNewToken(PersistentTokenInterface $token): void + { + $this->tokens[$token->getSeries()] = $token; + } +} diff --git a/vendor/symfony/security-core/Authentication/RememberMe/PersistentToken.php b/vendor/symfony/security-core/Authentication/RememberMe/PersistentToken.php new file mode 100644 index 0000000..c1f1e1a --- /dev/null +++ b/vendor/symfony/security-core/Authentication/RememberMe/PersistentToken.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +/** + * @author Johannes M. Schmitt + * + * @internal + */ +final class PersistentToken implements PersistentTokenInterface +{ + private string $class; + private string $userIdentifier; + private string $series; + private string $tokenValue; + private \DateTimeImmutable $lastUsed; + + public function __construct(string $class, string $userIdentifier, string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed) + { + if (!$class) { + throw new \InvalidArgumentException('$class must not be empty.'); + } + if ('' === $userIdentifier) { + throw new \InvalidArgumentException('$userIdentifier must not be empty.'); + } + if (!$series) { + throw new \InvalidArgumentException('$series must not be empty.'); + } + if (!$tokenValue) { + throw new \InvalidArgumentException('$tokenValue must not be empty.'); + } + + $this->class = $class; + $this->userIdentifier = $userIdentifier; + $this->series = $series; + $this->tokenValue = $tokenValue; + $this->lastUsed = \DateTimeImmutable::createFromInterface($lastUsed); + } + + public function getClass(): string + { + return $this->class; + } + + public function getUserIdentifier(): string + { + return $this->userIdentifier; + } + + public function getSeries(): string + { + return $this->series; + } + + public function getTokenValue(): string + { + return $this->tokenValue; + } + + public function getLastUsed(): \DateTime + { + return \DateTime::createFromImmutable($this->lastUsed); + } +} diff --git a/vendor/symfony/security-core/Authentication/RememberMe/PersistentTokenInterface.php b/vendor/symfony/security-core/Authentication/RememberMe/PersistentTokenInterface.php new file mode 100644 index 0000000..f5c0617 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/RememberMe/PersistentTokenInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +/** + * Interface to be implemented by persistent token classes (such as + * Doctrine entities representing a remember-me token). + * + * @author Johannes M. Schmitt + */ +interface PersistentTokenInterface +{ + /** + * Returns the class of the user. + */ + public function getClass(): string; + + /** + * Returns the series. + */ + public function getSeries(): string; + + /** + * Returns the token value. + */ + public function getTokenValue(): string; + + /** + * Returns the time the token was last used. + * + * Each call SHOULD return a new distinct DateTime instance. + */ + public function getLastUsed(): \DateTime; + + /** + * Returns the identifier used to authenticate (e.g. their email address or username). + */ + public function getUserIdentifier(): string; +} diff --git a/vendor/symfony/security-core/Authentication/RememberMe/TokenProviderInterface.php b/vendor/symfony/security-core/Authentication/RememberMe/TokenProviderInterface.php new file mode 100644 index 0000000..bfe4901 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/RememberMe/TokenProviderInterface.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; + +/** + * Interface for TokenProviders. + * + * @author Johannes M. Schmitt + */ +interface TokenProviderInterface +{ + /** + * Loads the active token for the given series. + * + * @return PersistentTokenInterface + * + * @throws TokenNotFoundException if the token is not found + */ + public function loadTokenBySeries(string $series); + + /** + * Deletes all tokens belonging to series. + * + * @return void + */ + public function deleteTokenBySeries(string $series); + + /** + * Updates the token according to this data. + * + * @return void + * + * @throws TokenNotFoundException if the token is not found + */ + public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed); + + /** + * Creates a new token. + * + * @return void + */ + public function createNewToken(PersistentTokenInterface $token); +} diff --git a/vendor/symfony/security-core/Authentication/RememberMe/TokenVerifierInterface.php b/vendor/symfony/security-core/Authentication/RememberMe/TokenVerifierInterface.php new file mode 100644 index 0000000..a323175 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/RememberMe/TokenVerifierInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +/** + * @author Jordi Boggiano + */ +interface TokenVerifierInterface +{ + /** + * Verifies that the given $token is valid. + * + * This lets you override the token check logic to for example accept slightly outdated tokens. + * + * Do not forget to implement token comparisons using hash_equals for a secure implementation. + */ + public function verifyToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue): bool; + + /** + * Updates an existing token with a new token value and lastUsed time. + */ + public function updateExistingToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void; +} diff --git a/vendor/symfony/security-core/Authentication/Token/AbstractToken.php b/vendor/symfony/security-core/Authentication/Token/AbstractToken.php new file mode 100644 index 0000000..36d6476 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/AbstractToken.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Base class for Token instances. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +abstract class AbstractToken implements TokenInterface, \Serializable +{ + private ?UserInterface $user = null; + private array $roleNames = []; + private array $attributes = []; + + /** + * @param string[] $roles An array of roles + * + * @throws \InvalidArgumentException + */ + public function __construct(array $roles = []) + { + foreach ($roles as $role) { + $this->roleNames[] = $role; + } + } + + public function getRoleNames(): array + { + return $this->roleNames; + } + + public function getUserIdentifier(): string + { + return $this->user ? $this->user->getUserIdentifier() : ''; + } + + public function getUser(): ?UserInterface + { + return $this->user; + } + + public function setUser(UserInterface $user): void + { + $this->user = $user; + } + + public function eraseCredentials(): void + { + if ($this->getUser() instanceof UserInterface) { + $this->getUser()->eraseCredentials(); + } + } + + /** + * Returns all the necessary state of the object for serialization purposes. + * + * There is no need to serialize any entry, they should be returned as-is. + * If you extend this method, keep in mind you MUST guarantee parent data is present in the state. + * Here is an example of how to extend this method: + * + * public function __serialize(): array + * { + * return [$this->childAttribute, parent::__serialize()]; + * } + * + * + * @see __unserialize() + */ + public function __serialize(): array + { + return [$this->user, true, null, $this->attributes, $this->roleNames]; + } + + /** + * Restores the object state from an array given by __serialize(). + * + * There is no need to unserialize any entry in $data, they are already ready-to-use. + * If you extend this method, keep in mind you MUST pass the parent data to its respective class. + * Here is an example of how to extend this method: + * + * public function __unserialize(array $data): void + * { + * [$this->childAttribute, $parentData] = $data; + * parent::__unserialize($parentData); + * } + * + * + * @see __serialize() + */ + public function __unserialize(array $data): void + { + [$user, , , $this->attributes, $this->roleNames] = $data; + $this->user = \is_string($user) ? new InMemoryUser($user, '', $this->roleNames, false) : $user; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + public function setAttributes(array $attributes): void + { + $this->attributes = $attributes; + } + + public function hasAttribute(string $name): bool + { + return \array_key_exists($name, $this->attributes); + } + + public function getAttribute(string $name): mixed + { + if (!\array_key_exists($name, $this->attributes)) { + throw new \InvalidArgumentException(sprintf('This token has no "%s" attribute.', $name)); + } + + return $this->attributes[$name]; + } + + public function setAttribute(string $name, mixed $value): void + { + $this->attributes[$name] = $value; + } + + public function __toString(): string + { + $class = static::class; + $class = substr($class, strrpos($class, '\\') + 1); + + $roles = []; + foreach ($this->roleNames as $role) { + $roles[] = $role; + } + + return sprintf('%s(user="%s", roles="%s")', $class, $this->getUserIdentifier(), implode(', ', $roles)); + } + + /** + * @internal + */ + final public function serialize(): string + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + /** + * @internal + */ + final public function unserialize(string $serialized): void + { + $this->__unserialize(unserialize($serialized)); + } +} diff --git a/vendor/symfony/security-core/Authentication/Token/NullToken.php b/vendor/symfony/security-core/Authentication/Token/NullToken.php new file mode 100644 index 0000000..9c2e489 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/NullToken.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * @author Wouter de Jong + */ +class NullToken implements TokenInterface +{ + public function __toString(): string + { + return ''; + } + + public function getRoleNames(): array + { + return []; + } + + public function getUser(): ?UserInterface + { + return null; + } + + public function setUser(UserInterface $user): never + { + throw new \BadMethodCallException('Cannot set user on a NullToken.'); + } + + public function getUserIdentifier(): string + { + return ''; + } + + public function eraseCredentials(): void + { + } + + public function getAttributes(): array + { + return []; + } + + public function setAttributes(array $attributes): never + { + throw new \BadMethodCallException('Cannot set attributes of NullToken.'); + } + + public function hasAttribute(string $name): bool + { + return false; + } + + public function getAttribute(string $name): mixed + { + return null; + } + + public function setAttribute(string $name, mixed $value): never + { + throw new \BadMethodCallException('Cannot add attribute to NullToken.'); + } + + public function __serialize(): array + { + return []; + } + + public function __unserialize(array $data): void + { + } +} diff --git a/vendor/symfony/security-core/Authentication/Token/PreAuthenticatedToken.php b/vendor/symfony/security-core/Authentication/Token/PreAuthenticatedToken.php new file mode 100644 index 0000000..a216d4c --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/PreAuthenticatedToken.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * PreAuthenticatedToken implements a pre-authenticated token. + * + * @author Fabien Potencier + */ +class PreAuthenticatedToken extends AbstractToken +{ + private string $firewallName; + + /** + * @param string[] $roles + */ + public function __construct(UserInterface $user, string $firewallName, array $roles = []) + { + parent::__construct($roles); + + if ('' === $firewallName) { + throw new \InvalidArgumentException('$firewallName must not be empty.'); + } + + $this->setUser($user); + $this->firewallName = $firewallName; + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function __serialize(): array + { + return [null, $this->firewallName, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [, $this->firewallName, $parentData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/Authentication/Token/RememberMeToken.php b/vendor/symfony/security-core/Authentication/Token/RememberMeToken.php new file mode 100644 index 0000000..ad218f1 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/RememberMeToken.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Authentication Token for "Remember-Me". + * + * @author Johannes M. Schmitt + */ +class RememberMeToken extends AbstractToken +{ + private string $secret; + private string $firewallName; + + /** + * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client + * + * @throws \InvalidArgumentException + */ + public function __construct(UserInterface $user, string $firewallName, #[\SensitiveParameter] string $secret) + { + parent::__construct($user->getRoles()); + + if (!$secret) { + throw new InvalidArgumentException('A non-empty secret is required.'); + } + + if (!$firewallName) { + throw new InvalidArgumentException('$firewallName must not be empty.'); + } + + $this->firewallName = $firewallName; + $this->secret = $secret; + + $this->setUser($user); + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function getSecret(): string + { + return $this->secret; + } + + public function __serialize(): array + { + return [$this->secret, $this->firewallName, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [$this->secret, $this->firewallName, $parentData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorage.php b/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorage.php new file mode 100644 index 0000000..42234f8 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorage.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token\Storage; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * TokenStorage contains a TokenInterface. + * + * It gives access to the token representing the current user authentication. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class TokenStorage implements TokenStorageInterface, ResetInterface +{ + private ?TokenInterface $token = null; + private ?\Closure $initializer = null; + + public function getToken(): ?TokenInterface + { + if ($initializer = $this->initializer) { + $this->initializer = null; + $initializer(); + } + + return $this->token; + } + + public function setToken(?TokenInterface $token): void + { + if ($token) { + // ensure any initializer is called + $this->getToken(); + } + + $this->initializer = null; + $this->token = $token; + } + + public function setInitializer(?callable $initializer): void + { + $this->initializer = null === $initializer ? null : $initializer(...); + } + + public function reset(): void + { + $this->setToken(null); + } +} diff --git a/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorageInterface.php b/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorageInterface.php new file mode 100644 index 0000000..0f611ca --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/Storage/TokenStorageInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token\Storage; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * The TokenStorageInterface. + * + * @author Johannes M. Schmitt + */ +interface TokenStorageInterface +{ + /** + * Returns the current security token. + */ + public function getToken(): ?TokenInterface; + + /** + * Sets the authentication token. + * + * @param TokenInterface|null $token A TokenInterface token, or null if no further authentication information should be stored + */ + public function setToken(?TokenInterface $token): void; +} diff --git a/vendor/symfony/security-core/Authentication/Token/Storage/UsageTrackingTokenStorage.php b/vendor/symfony/security-core/Authentication/Token/Storage/UsageTrackingTokenStorage.php new file mode 100644 index 0000000..8a4069e --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/Storage/UsageTrackingTokenStorage.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token\Storage; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * A token storage that increments the session usage index when the token is accessed. + * + * @author Nicolas Grekas + */ +final class UsageTrackingTokenStorage implements TokenStorageInterface, ServiceSubscriberInterface +{ + private TokenStorageInterface $storage; + private ContainerInterface $container; + private bool $enableUsageTracking = false; + + public function __construct(TokenStorageInterface $storage, ContainerInterface $container) + { + $this->storage = $storage; + $this->container = $container; + } + + public function getToken(): ?TokenInterface + { + if ($this->shouldTrackUsage()) { + // increments the internal session usage index + $this->getSession()->getMetadataBag(); + } + + return $this->storage->getToken(); + } + + public function setToken(?TokenInterface $token = null): void + { + $this->storage->setToken($token); + + if ($token && $this->shouldTrackUsage()) { + // increments the internal session usage index + $this->getSession()->getMetadataBag(); + } + } + + public function enableUsageTracking(): void + { + $this->enableUsageTracking = true; + } + + public function disableUsageTracking(): void + { + $this->enableUsageTracking = false; + } + + public static function getSubscribedServices(): array + { + return [ + 'request_stack' => RequestStack::class, + ]; + } + + private function getSession(): SessionInterface + { + return $this->container->get('request_stack')->getSession(); + } + + private function shouldTrackUsage(): bool + { + return $this->enableUsageTracking && $this->container->get('request_stack')->getMainRequest(); + } +} diff --git a/vendor/symfony/security-core/Authentication/Token/SwitchUserToken.php b/vendor/symfony/security-core/Authentication/Token/SwitchUserToken.php new file mode 100644 index 0000000..fb632a6 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/SwitchUserToken.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Token representing a user who temporarily impersonates another one. + * + * @author Christian Flothmann + */ +class SwitchUserToken extends UsernamePasswordToken +{ + private TokenInterface $originalToken; + private ?string $originatedFromUri = null; + + /** + * @param $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method + * @param $originatedFromUri The URI where was the user at the switch + * + * @throws \InvalidArgumentException + */ + public function __construct(UserInterface $user, string $firewallName, array $roles, TokenInterface $originalToken, ?string $originatedFromUri = null) + { + parent::__construct($user, $firewallName, $roles); + + $this->originalToken = $originalToken; + $this->originatedFromUri = $originatedFromUri; + } + + public function getOriginalToken(): TokenInterface + { + return $this->originalToken; + } + + public function getOriginatedFromUri(): ?string + { + return $this->originatedFromUri; + } + + public function __serialize(): array + { + return [$this->originalToken, $this->originatedFromUri, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + if (3 > \count($data)) { + // Support for tokens serialized with version 5.1 or lower of symfony/security-core. + [$this->originalToken, $parentData] = $data; + } else { + [$this->originalToken, $this->originatedFromUri, $parentData] = $data; + } + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/Authentication/Token/TokenInterface.php b/vendor/symfony/security-core/Authentication/Token/TokenInterface.php new file mode 100644 index 0000000..1e67b1e --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/TokenInterface.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * TokenInterface is the interface for the user authentication information. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +interface TokenInterface extends \Stringable +{ + /** + * Returns a string representation of the Token. + * + * This is only to be used for debugging purposes. + */ + public function __toString(): string; + + /** + * Returns the user identifier used during authentication (e.g. a user's email address or username). + */ + public function getUserIdentifier(): string; + + /** + * Returns the user roles. + * + * @return string[] + */ + public function getRoleNames(): array; + + /** + * Returns a user representation. + * + * @see AbstractToken::setUser() + */ + public function getUser(): ?UserInterface; + + /** + * Sets the authenticated user in the token. + * + * @throws \InvalidArgumentException + */ + public function setUser(UserInterface $user): void; + + /** + * Removes sensitive information from the token. + */ + public function eraseCredentials(): void; + + public function getAttributes(): array; + + /** + * @param array $attributes The token attributes + */ + public function setAttributes(array $attributes): void; + + public function hasAttribute(string $name): bool; + + /** + * @throws \InvalidArgumentException When attribute doesn't exist for this token + */ + public function getAttribute(string $name): mixed; + + public function setAttribute(string $name, mixed $value): void; + + /** + * Returns all the necessary state of the object for serialization purposes. + */ + public function __serialize(): array; + + /** + * Restores the object state from an array given by __serialize(). + */ + public function __unserialize(array $data): void; +} diff --git a/vendor/symfony/security-core/Authentication/Token/UsernamePasswordToken.php b/vendor/symfony/security-core/Authentication/Token/UsernamePasswordToken.php new file mode 100644 index 0000000..74e24a2 --- /dev/null +++ b/vendor/symfony/security-core/Authentication/Token/UsernamePasswordToken.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * UsernamePasswordToken implements a username and password token. + * + * @author Fabien Potencier + */ +class UsernamePasswordToken extends AbstractToken +{ + private string $firewallName; + + public function __construct(UserInterface $user, string $firewallName, array $roles = []) + { + parent::__construct($roles); + + if ('' === $firewallName) { + throw new \InvalidArgumentException('$firewallName must not be empty.'); + } + + $this->setUser($user); + $this->firewallName = $firewallName; + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function __serialize(): array + { + return [null, $this->firewallName, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [, $this->firewallName, $parentData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/AuthenticationEvents.php b/vendor/symfony/security-core/AuthenticationEvents.php new file mode 100644 index 0000000..a1c3e5d --- /dev/null +++ b/vendor/symfony/security-core/AuthenticationEvents.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core; + +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; + +final class AuthenticationEvents +{ + /** + * The AUTHENTICATION_SUCCESS event occurs after a user is authenticated + * by one provider. + * + * @Event("Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent") + */ + public const AUTHENTICATION_SUCCESS = 'security.authentication.success'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + AuthenticationSuccessEvent::class => self::AUTHENTICATION_SUCCESS, + ]; +} diff --git a/vendor/symfony/security-core/Authorization/AccessDecisionManager.php b/vendor/symfony/security-core/Authorization/AccessDecisionManager.php new file mode 100644 index 0000000..4a56f94 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/AccessDecisionManager.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; +use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; +use Symfony\Component\Security\Core\Authorization\Voter\CacheableVoterInterface; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; + +/** + * AccessDecisionManager is the base class for all access decision managers + * that use decision voters. + * + * @author Fabien Potencier + */ +final class AccessDecisionManager implements AccessDecisionManagerInterface +{ + private const VALID_VOTES = [ + VoterInterface::ACCESS_GRANTED => true, + VoterInterface::ACCESS_DENIED => true, + VoterInterface::ACCESS_ABSTAIN => true, + ]; + + private iterable $voters; + private array $votersCacheAttributes = []; + private array $votersCacheObject = []; + private AccessDecisionStrategyInterface $strategy; + + /** + * @param iterable $voters An array or an iterator of VoterInterface instances + */ + public function __construct(iterable $voters = [], ?AccessDecisionStrategyInterface $strategy = null) + { + $this->voters = $voters; + $this->strategy = $strategy ?? new AffirmativeStrategy(); + } + + /** + * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array + */ + public function decide(TokenInterface $token, array $attributes, mixed $object = null, bool $allowMultipleAttributes = false): bool + { + // Special case for AccessListener, do not remove the right side of the condition before 6.0 + if (\count($attributes) > 1 && !$allowMultipleAttributes) { + throw new InvalidArgumentException(sprintf('Passing more than one Security attribute to "%s()" is not supported.', __METHOD__)); + } + + return $this->strategy->decide( + $this->collectResults($token, $attributes, $object) + ); + } + + /** + * @return \Traversable + */ + private function collectResults(TokenInterface $token, array $attributes, mixed $object): \Traversable + { + foreach ($this->getVoters($attributes, $object) as $voter) { + $result = $voter->vote($token, $object, $attributes); + if (!\is_int($result) || !(self::VALID_VOTES[$result] ?? false)) { + throw new \LogicException(sprintf('"%s::vote()" must return one of "%s" constants ("ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN"), "%s" returned.', get_debug_type($voter), VoterInterface::class, var_export($result, true))); + } + + yield $result; + } + } + + /** + * @return iterable + */ + private function getVoters(array $attributes, $object = null): iterable + { + $keyAttributes = []; + foreach ($attributes as $attribute) { + $keyAttributes[] = \is_string($attribute) ? $attribute : null; + } + // use `get_class` to handle anonymous classes + $keyObject = \is_object($object) ? $object::class : get_debug_type($object); + foreach ($this->voters as $key => $voter) { + if (!$voter instanceof CacheableVoterInterface) { + yield $voter; + continue; + } + + $supports = true; + // The voter supports the attributes if it supports at least one attribute of the list + foreach ($keyAttributes as $keyAttribute) { + if (null === $keyAttribute) { + $supports = true; + } elseif (!isset($this->votersCacheAttributes[$keyAttribute][$key])) { + $this->votersCacheAttributes[$keyAttribute][$key] = $supports = $voter->supportsAttribute($keyAttribute); + } else { + $supports = $this->votersCacheAttributes[$keyAttribute][$key]; + } + if ($supports) { + break; + } + } + if (!$supports) { + continue; + } + + if (!isset($this->votersCacheObject[$keyObject][$key])) { + $this->votersCacheObject[$keyObject][$key] = $supports = $voter->supportsType($keyObject); + } else { + $supports = $this->votersCacheObject[$keyObject][$key]; + } + if (!$supports) { + continue; + } + yield $voter; + } + } +} diff --git a/vendor/symfony/security-core/Authorization/AccessDecisionManagerInterface.php b/vendor/symfony/security-core/Authorization/AccessDecisionManagerInterface.php new file mode 100644 index 0000000..f25c7e1 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/AccessDecisionManagerInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * AccessDecisionManagerInterface makes authorization decisions. + * + * @author Fabien Potencier + */ +interface AccessDecisionManagerInterface +{ + /** + * Decides whether the access is possible or not. + * + * @param array $attributes An array of attributes associated with the method being invoked + * @param mixed $object The object to secure + */ + public function decide(TokenInterface $token, array $attributes, mixed $object = null): bool; +} diff --git a/vendor/symfony/security-core/Authorization/AuthorizationChecker.php b/vendor/symfony/security-core/Authorization/AuthorizationChecker.php new file mode 100644 index 0000000..c748697 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/AuthorizationChecker.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization; + +use Symfony\Component\Security\Core\Authentication\Token\NullToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; + +/** + * AuthorizationChecker is the main authorization point of the Security component. + * + * It gives access to the token representing the current user authentication. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class AuthorizationChecker implements AuthorizationCheckerInterface +{ + public function __construct( + private TokenStorageInterface $tokenStorage, + private AccessDecisionManagerInterface $accessDecisionManager, + ) { + } + + final public function isGranted(mixed $attribute, mixed $subject = null): bool + { + $token = $this->tokenStorage->getToken(); + + if (!$token || !$token->getUser()) { + $token = new NullToken(); + } + + return $this->accessDecisionManager->decide($token, [$attribute], $subject); + } +} diff --git a/vendor/symfony/security-core/Authorization/AuthorizationCheckerInterface.php b/vendor/symfony/security-core/Authorization/AuthorizationCheckerInterface.php new file mode 100644 index 0000000..6f5a602 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/AuthorizationCheckerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization; + +/** + * The AuthorizationCheckerInterface. + * + * @author Johannes M. Schmitt + */ +interface AuthorizationCheckerInterface +{ + /** + * Checks if the attribute is granted against the current authentication token and optionally supplied subject. + * + * @param mixed $attribute A single attribute to vote on (can be of any type, string and instance of Expression are supported by the core) + */ + public function isGranted(mixed $attribute, mixed $subject = null): bool; +} diff --git a/vendor/symfony/security-core/Authorization/ExpressionLanguage.php b/vendor/symfony/security-core/Authorization/ExpressionLanguage.php new file mode 100644 index 0000000..a48d814 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/ExpressionLanguage.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; + +if (!class_exists(BaseExpressionLanguage::class)) { + throw new \LogicException(sprintf('The "%s" class requires the "ExpressionLanguage" component. Try running "composer require symfony/expression-language".', ExpressionLanguage::class)); +} else { + // Help opcache.preload discover always-needed symbols + class_exists(ExpressionLanguageProvider::class); + + /** + * Adds some function to the default ExpressionLanguage. + * + * @author Fabien Potencier + * + * @see ExpressionLanguageProvider + */ + class ExpressionLanguage extends BaseExpressionLanguage + { + public function __construct(?CacheItemPoolInterface $cache = null, array $providers = []) + { + // prepend the default provider to let users override it easily + array_unshift($providers, new ExpressionLanguageProvider()); + + parent::__construct($cache, $providers); + } + } +} diff --git a/vendor/symfony/security-core/Authorization/ExpressionLanguageProvider.php b/vendor/symfony/security-core/Authorization/ExpressionLanguageProvider.php new file mode 100644 index 0000000..d3e2dac --- /dev/null +++ b/vendor/symfony/security-core/Authorization/ExpressionLanguageProvider.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization; + +use Symfony\Component\ExpressionLanguage\ExpressionFunction; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * Define some ExpressionLanguage functions. + * + * @author Fabien Potencier + */ +class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface +{ + public function getFunctions(): array + { + return [ + new ExpressionFunction('is_authenticated', fn () => '$auth_checker->isGranted("IS_AUTHENTICATED")', fn (array $variables) => $variables['auth_checker']->isGranted('IS_AUTHENTICATED')), + + new ExpressionFunction('is_fully_authenticated', fn () => '$token && $auth_checker->isGranted("IS_AUTHENTICATED_FULLY")', fn (array $variables) => $variables['token'] && $variables['auth_checker']->isGranted('IS_AUTHENTICATED_FULLY')), + + new ExpressionFunction('is_granted', fn ($attributes, $object = 'null') => sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)), + + new ExpressionFunction('is_remember_me', fn () => '$token && $auth_checker->isGranted("IS_REMEMBERED")', fn (array $variables) => $variables['token'] && $variables['auth_checker']->isGranted('IS_REMEMBERED')), + ]; + } +} diff --git a/vendor/symfony/security-core/Authorization/Strategy/AccessDecisionStrategyInterface.php b/vendor/symfony/security-core/Authorization/Strategy/AccessDecisionStrategyInterface.php new file mode 100644 index 0000000..0023837 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Strategy/AccessDecisionStrategyInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Strategy; + +/** + * A strategy for turning a stream of votes into a final decision. + * + * @author Alexander M. Turek + */ +interface AccessDecisionStrategyInterface +{ + /** + * @param \Traversable $results + */ + public function decide(\Traversable $results): bool; +} diff --git a/vendor/symfony/security-core/Authorization/Strategy/AffirmativeStrategy.php b/vendor/symfony/security-core/Authorization/Strategy/AffirmativeStrategy.php new file mode 100644 index 0000000..ecd74b2 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Strategy/AffirmativeStrategy.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Strategy; + +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +/** + * Grants access if any voter returns an affirmative response. + * + * If all voters abstained from voting, the decision will be based on the + * allowIfAllAbstainDecisions property value (defaults to false). + * + * @author Fabien Potencier + * @author Alexander M. Turek + */ +final class AffirmativeStrategy implements AccessDecisionStrategyInterface, \Stringable +{ + private bool $allowIfAllAbstainDecisions; + + public function __construct(bool $allowIfAllAbstainDecisions = false) + { + $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; + } + + public function decide(\Traversable $results): bool + { + $deny = 0; + foreach ($results as $result) { + if (VoterInterface::ACCESS_GRANTED === $result) { + return true; + } + + if (VoterInterface::ACCESS_DENIED === $result) { + ++$deny; + } + } + + if ($deny > 0) { + return false; + } + + return $this->allowIfAllAbstainDecisions; + } + + public function __toString(): string + { + return 'affirmative'; + } +} diff --git a/vendor/symfony/security-core/Authorization/Strategy/ConsensusStrategy.php b/vendor/symfony/security-core/Authorization/Strategy/ConsensusStrategy.php new file mode 100644 index 0000000..489b342 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Strategy/ConsensusStrategy.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Strategy; + +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +/** + * Grants access if there is consensus of granted against denied responses. + * + * Consensus means majority-rule (ignoring abstains) rather than unanimous + * agreement (ignoring abstains). If you require unanimity, see + * UnanimousBased. + * + * If there were an equal number of grant and deny votes, the decision will + * be based on the allowIfEqualGrantedDeniedDecisions property value + * (defaults to true). + * + * If all voters abstained from voting, the decision will be based on the + * allowIfAllAbstainDecisions property value (defaults to false). + * + * @author Fabien Potencier + * @author Alexander M. Turek + */ +final class ConsensusStrategy implements AccessDecisionStrategyInterface, \Stringable +{ + private bool $allowIfAllAbstainDecisions; + private bool $allowIfEqualGrantedDeniedDecisions; + + public function __construct(bool $allowIfAllAbstainDecisions = false, bool $allowIfEqualGrantedDeniedDecisions = true) + { + $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; + $this->allowIfEqualGrantedDeniedDecisions = $allowIfEqualGrantedDeniedDecisions; + } + + public function decide(\Traversable $results): bool + { + $grant = 0; + $deny = 0; + foreach ($results as $result) { + if (VoterInterface::ACCESS_GRANTED === $result) { + ++$grant; + } elseif (VoterInterface::ACCESS_DENIED === $result) { + ++$deny; + } + } + + if ($grant > $deny) { + return true; + } + + if ($deny > $grant) { + return false; + } + + if ($grant > 0) { + return $this->allowIfEqualGrantedDeniedDecisions; + } + + return $this->allowIfAllAbstainDecisions; + } + + public function __toString(): string + { + return 'consensus'; + } +} diff --git a/vendor/symfony/security-core/Authorization/Strategy/PriorityStrategy.php b/vendor/symfony/security-core/Authorization/Strategy/PriorityStrategy.php new file mode 100644 index 0000000..9599950 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Strategy/PriorityStrategy.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Strategy; + +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +/** + * Grant or deny access depending on the first voter that does not abstain. + * The priority of voters can be used to overrule a decision. + * + * If all voters abstained from voting, the decision will be based on the + * allowIfAllAbstainDecisions property value (defaults to false). + * + * @author Fabien Potencier + * @author Alexander M. Turek + */ +final class PriorityStrategy implements AccessDecisionStrategyInterface, \Stringable +{ + private bool $allowIfAllAbstainDecisions; + + public function __construct(bool $allowIfAllAbstainDecisions = false) + { + $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; + } + + public function decide(\Traversable $results): bool + { + foreach ($results as $result) { + if (VoterInterface::ACCESS_GRANTED === $result) { + return true; + } + + if (VoterInterface::ACCESS_DENIED === $result) { + return false; + } + } + + return $this->allowIfAllAbstainDecisions; + } + + public function __toString(): string + { + return 'priority'; + } +} diff --git a/vendor/symfony/security-core/Authorization/Strategy/UnanimousStrategy.php b/vendor/symfony/security-core/Authorization/Strategy/UnanimousStrategy.php new file mode 100644 index 0000000..1f3b85c --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Strategy/UnanimousStrategy.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Strategy; + +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +/** + * Grants access if only grant (or abstain) votes were received. + * + * If all voters abstained from voting, the decision will be based on the + * allowIfAllAbstainDecisions property value (defaults to false). + * + * @author Fabien Potencier + * @author Alexander M. Turek + */ +final class UnanimousStrategy implements AccessDecisionStrategyInterface, \Stringable +{ + private bool $allowIfAllAbstainDecisions; + + public function __construct(bool $allowIfAllAbstainDecisions = false) + { + $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; + } + + public function decide(\Traversable $results): bool + { + $grant = 0; + foreach ($results as $result) { + if (VoterInterface::ACCESS_DENIED === $result) { + return false; + } + + if (VoterInterface::ACCESS_GRANTED === $result) { + ++$grant; + } + } + + // no deny votes + if ($grant > 0) { + return true; + } + + return $this->allowIfAllAbstainDecisions; + } + + public function __toString(): string + { + return 'unanimous'; + } +} diff --git a/vendor/symfony/security-core/Authorization/TraceableAccessDecisionManager.php b/vendor/symfony/security-core/Authorization/TraceableAccessDecisionManager.php new file mode 100644 index 0000000..cb44dce --- /dev/null +++ b/vendor/symfony/security-core/Authorization/TraceableAccessDecisionManager.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +/** + * Decorates the original AccessDecisionManager class to log information + * about the security voters and the decisions made by them. + * + * @author Javier Eguiluz + * + * @internal + */ +class TraceableAccessDecisionManager implements AccessDecisionManagerInterface +{ + private AccessDecisionManagerInterface $manager; + private ?AccessDecisionStrategyInterface $strategy = null; + /** @var iterable */ + private iterable $voters = []; + private array $decisionLog = []; // All decision logs + private array $currentLog = []; // Logs being filled in + + public function __construct(AccessDecisionManagerInterface $manager) + { + $this->manager = $manager; + + // The strategy and voters are stored in a private properties of the decorated service + if (property_exists($manager, 'strategy')) { + $reflection = new \ReflectionProperty($manager::class, 'strategy'); + $this->strategy = $reflection->getValue($manager); + } + if (property_exists($manager, 'voters')) { + $reflection = new \ReflectionProperty($manager::class, 'voters'); + $this->voters = $reflection->getValue($manager); + } + } + + public function decide(TokenInterface $token, array $attributes, mixed $object = null, bool $allowMultipleAttributes = false): bool + { + $currentDecisionLog = [ + 'attributes' => $attributes, + 'object' => $object, + 'voterDetails' => [], + ]; + + $this->currentLog[] = &$currentDecisionLog; + + $result = $this->manager->decide($token, $attributes, $object, $allowMultipleAttributes); + + $currentDecisionLog['result'] = $result; + + $this->decisionLog[] = array_pop($this->currentLog); // Using a stack since decide can be called by voters + + return $result; + } + + /** + * Adds voter vote and class to the voter details. + * + * @param array $attributes attributes used for the vote + * @param int $vote vote of the voter + */ + public function addVoterVote(VoterInterface $voter, array $attributes, int $vote): void + { + $currentLogIndex = \count($this->currentLog) - 1; + $this->currentLog[$currentLogIndex]['voterDetails'][] = [ + 'voter' => $voter, + 'attributes' => $attributes, + 'vote' => $vote, + ]; + } + + public function getStrategy(): string + { + if (null === $this->strategy) { + return '-'; + } + if (method_exists($this->strategy, '__toString')) { + return (string) $this->strategy; + } + + return get_debug_type($this->strategy); + } + + /** + * @return iterable + */ + public function getVoters(): iterable + { + return $this->voters; + } + + public function getDecisionLog(): array + { + return $this->decisionLog; + } +} diff --git a/vendor/symfony/security-core/Authorization/Voter/AuthenticatedVoter.php b/vendor/symfony/security-core/Authorization/Voter/AuthenticatedVoter.php new file mode 100644 index 0000000..d7b2b22 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/AuthenticatedVoter.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * AuthenticatedVoter votes if an attribute like IS_AUTHENTICATED_FULLY, + * IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED is present. + * + * This list is most restrictive to least restrictive checking. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class AuthenticatedVoter implements CacheableVoterInterface +{ + public const IS_AUTHENTICATED_FULLY = 'IS_AUTHENTICATED_FULLY'; + public const IS_AUTHENTICATED_REMEMBERED = 'IS_AUTHENTICATED_REMEMBERED'; + public const IS_AUTHENTICATED = 'IS_AUTHENTICATED'; + public const IS_IMPERSONATOR = 'IS_IMPERSONATOR'; + public const IS_REMEMBERED = 'IS_REMEMBERED'; + public const PUBLIC_ACCESS = 'PUBLIC_ACCESS'; + + private AuthenticationTrustResolverInterface $authenticationTrustResolver; + + public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver) + { + $this->authenticationTrustResolver = $authenticationTrustResolver; + } + + public function vote(TokenInterface $token, mixed $subject, array $attributes): int + { + if ($attributes === [self::PUBLIC_ACCESS]) { + return VoterInterface::ACCESS_GRANTED; + } + + $result = VoterInterface::ACCESS_ABSTAIN; + foreach ($attributes as $attribute) { + if (null === $attribute || (self::IS_AUTHENTICATED_FULLY !== $attribute + && self::IS_AUTHENTICATED_REMEMBERED !== $attribute + && self::IS_AUTHENTICATED !== $attribute + && self::IS_IMPERSONATOR !== $attribute + && self::IS_REMEMBERED !== $attribute)) { + continue; + } + + $result = VoterInterface::ACCESS_DENIED; + + if (self::IS_AUTHENTICATED_FULLY === $attribute + && $this->authenticationTrustResolver->isFullFledged($token)) { + return VoterInterface::ACCESS_GRANTED; + } + + if (self::IS_AUTHENTICATED_REMEMBERED === $attribute + && ($this->authenticationTrustResolver->isRememberMe($token) + || $this->authenticationTrustResolver->isFullFledged($token))) { + return VoterInterface::ACCESS_GRANTED; + } + + if (self::IS_AUTHENTICATED === $attribute && $this->authenticationTrustResolver->isAuthenticated($token)) { + return VoterInterface::ACCESS_GRANTED; + } + + if (self::IS_REMEMBERED === $attribute && $this->authenticationTrustResolver->isRememberMe($token)) { + return VoterInterface::ACCESS_GRANTED; + } + + if (self::IS_IMPERSONATOR === $attribute && $token instanceof SwitchUserToken) { + return VoterInterface::ACCESS_GRANTED; + } + } + + return $result; + } + + public function supportsAttribute(string $attribute): bool + { + return \in_array($attribute, [ + self::IS_AUTHENTICATED_FULLY, + self::IS_AUTHENTICATED_REMEMBERED, + self::IS_AUTHENTICATED, + self::IS_IMPERSONATOR, + self::IS_REMEMBERED, + self::PUBLIC_ACCESS, + ], true); + } + + public function supportsType(string $subjectType): bool + { + return true; + } +} diff --git a/vendor/symfony/security-core/Authorization/Voter/CacheableVoterInterface.php b/vendor/symfony/security-core/Authorization/Voter/CacheableVoterInterface.php new file mode 100644 index 0000000..875aad6 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/CacheableVoterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +/** + * Let voters expose the attributes and types they care about. + * + * By returning false to either `supportsAttribute` or `supportsType`, the + * voter will never be called for the specified attribute or subject. + * + * @author Jérémy Derussé + */ +interface CacheableVoterInterface extends VoterInterface +{ + public function supportsAttribute(string $attribute): bool; + + /** + * @param string $subjectType The type of the subject inferred by `get_class` or `get_debug_type` + */ + public function supportsType(string $subjectType): bool; +} diff --git a/vendor/symfony/security-core/Authorization/Voter/ExpressionVoter.php b/vendor/symfony/security-core/Authorization/Voter/ExpressionVoter.php new file mode 100644 index 0000000..6de9c95 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/ExpressionVoter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; +use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; + +/** + * ExpressionVoter votes based on the evaluation of an expression. + * + * @author Fabien Potencier + */ +class ExpressionVoter implements CacheableVoterInterface +{ + private ExpressionLanguage $expressionLanguage; + private AuthenticationTrustResolverInterface $trustResolver; + private AuthorizationCheckerInterface $authChecker; + private ?RoleHierarchyInterface $roleHierarchy; + + public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, ?RoleHierarchyInterface $roleHierarchy = null) + { + $this->expressionLanguage = $expressionLanguage; + $this->trustResolver = $trustResolver; + $this->authChecker = $authChecker; + $this->roleHierarchy = $roleHierarchy; + } + + public function supportsAttribute(string $attribute): bool + { + return false; + } + + public function supportsType(string $subjectType): bool + { + return true; + } + + public function vote(TokenInterface $token, mixed $subject, array $attributes): int + { + $result = VoterInterface::ACCESS_ABSTAIN; + $variables = null; + foreach ($attributes as $attribute) { + if (!$attribute instanceof Expression) { + continue; + } + + $variables ??= $this->getVariables($token, $subject); + + $result = VoterInterface::ACCESS_DENIED; + if ($this->expressionLanguage->evaluate($attribute, $variables)) { + return VoterInterface::ACCESS_GRANTED; + } + } + + return $result; + } + + private function getVariables(TokenInterface $token, mixed $subject): array + { + $roleNames = $token->getRoleNames(); + + if (null !== $this->roleHierarchy) { + $roleNames = $this->roleHierarchy->getReachableRoleNames($roleNames); + } + + $variables = [ + 'token' => $token, + 'user' => $token->getUser(), + 'object' => $subject, + 'subject' => $subject, + 'role_names' => $roleNames, + 'trust_resolver' => $this->trustResolver, + 'auth_checker' => $this->authChecker, + ]; + + // this is mainly to propose a better experience when the expression is used + // in an access control rule, as the developer does not know that it's going + // to be handled by this voter + if ($subject instanceof Request) { + $variables['request'] = $subject; + } + + return $variables; + } +} diff --git a/vendor/symfony/security-core/Authorization/Voter/RoleHierarchyVoter.php b/vendor/symfony/security-core/Authorization/Voter/RoleHierarchyVoter.php new file mode 100644 index 0000000..3535ca1 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/RoleHierarchyVoter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; + +/** + * RoleHierarchyVoter uses a RoleHierarchy to determine the roles granted to + * the user before voting. + * + * @author Fabien Potencier + */ +class RoleHierarchyVoter extends RoleVoter +{ + private RoleHierarchyInterface $roleHierarchy; + + public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_') + { + $this->roleHierarchy = $roleHierarchy; + + parent::__construct($prefix); + } + + protected function extractRoles(TokenInterface $token): array + { + return $this->roleHierarchy->getReachableRoleNames($token->getRoleNames()); + } +} diff --git a/vendor/symfony/security-core/Authorization/Voter/RoleVoter.php b/vendor/symfony/security-core/Authorization/Voter/RoleVoter.php new file mode 100644 index 0000000..76de3a3 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/RoleVoter.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * RoleVoter votes if any attribute starts with a given prefix. + * + * @author Fabien Potencier + */ +class RoleVoter implements CacheableVoterInterface +{ + private string $prefix; + + public function __construct(string $prefix = 'ROLE_') + { + $this->prefix = $prefix; + } + + public function vote(TokenInterface $token, mixed $subject, array $attributes): int + { + $result = VoterInterface::ACCESS_ABSTAIN; + $roles = $this->extractRoles($token); + + foreach ($attributes as $attribute) { + if (!\is_string($attribute) || !str_starts_with($attribute, $this->prefix)) { + continue; + } + + $result = VoterInterface::ACCESS_DENIED; + if (\in_array($attribute, $roles, true)) { + return VoterInterface::ACCESS_GRANTED; + } + } + + return $result; + } + + public function supportsAttribute(string $attribute): bool + { + return str_starts_with($attribute, $this->prefix); + } + + public function supportsType(string $subjectType): bool + { + return true; + } + + protected function extractRoles(TokenInterface $token): array + { + return $token->getRoleNames(); + } +} diff --git a/vendor/symfony/security-core/Authorization/Voter/TraceableVoter.php b/vendor/symfony/security-core/Authorization/Voter/TraceableVoter.php new file mode 100644 index 0000000..412bb97 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/TraceableVoter.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Event\VoteEvent; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * Decorates voter classes to send result events. + * + * @author Laurent VOULLEMIER + * + * @internal + */ +class TraceableVoter implements CacheableVoterInterface +{ + private VoterInterface $voter; + private EventDispatcherInterface $eventDispatcher; + + public function __construct(VoterInterface $voter, EventDispatcherInterface $eventDispatcher) + { + $this->voter = $voter; + $this->eventDispatcher = $eventDispatcher; + } + + public function vote(TokenInterface $token, mixed $subject, array $attributes): int + { + $result = $this->voter->vote($token, $subject, $attributes); + + $this->eventDispatcher->dispatch(new VoteEvent($this->voter, $subject, $attributes, $result), 'debug.security.authorization.vote'); + + return $result; + } + + public function getDecoratedVoter(): VoterInterface + { + return $this->voter; + } + + public function supportsAttribute(string $attribute): bool + { + return !$this->voter instanceof CacheableVoterInterface || $this->voter->supportsAttribute($attribute); + } + + public function supportsType(string $subjectType): bool + { + return !$this->voter instanceof CacheableVoterInterface || $this->voter->supportsType($subjectType); + } +} diff --git a/vendor/symfony/security-core/Authorization/Voter/Voter.php b/vendor/symfony/security-core/Authorization/Voter/Voter.php new file mode 100644 index 0000000..1f76a42 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/Voter.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Voter is an abstract default implementation of a voter. + * + * @author Roman MarintÅ¡enko + * @author Grégoire Pineau + * + * @template TAttribute of string + * @template TSubject of mixed + */ +abstract class Voter implements VoterInterface, CacheableVoterInterface +{ + public function vote(TokenInterface $token, mixed $subject, array $attributes): int + { + // abstain vote by default in case none of the attributes are supported + $vote = self::ACCESS_ABSTAIN; + + foreach ($attributes as $attribute) { + try { + if (!$this->supports($attribute, $subject)) { + continue; + } + } catch (\TypeError $e) { + if (str_contains($e->getMessage(), 'supports(): Argument #1')) { + continue; + } + + throw $e; + } + + // as soon as at least one attribute is supported, default is to deny access + $vote = self::ACCESS_DENIED; + + if ($this->voteOnAttribute($attribute, $subject, $token)) { + // grant access as soon as at least one attribute returns a positive response + return self::ACCESS_GRANTED; + } + } + + return $vote; + } + + /** + * Return false if your voter doesn't support the given attribute. Symfony will cache + * that decision and won't call your voter again for that attribute. + */ + public function supportsAttribute(string $attribute): bool + { + return true; + } + + /** + * Return false if your voter doesn't support the given subject type. Symfony will cache + * that decision and won't call your voter again for that subject type. + * + * @param string $subjectType The type of the subject inferred by `get_class()` or `get_debug_type()` + */ + public function supportsType(string $subjectType): bool + { + return true; + } + + /** + * Determines if the attribute and subject are supported by this voter. + * + * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type + * + * @psalm-assert-if-true TSubject $subject + * @psalm-assert-if-true TAttribute $attribute + */ + abstract protected function supports(string $attribute, mixed $subject): bool; + + /** + * Perform a single access check operation on a given attribute, subject and token. + * It is safe to assume that $attribute and $subject already passed the "supports()" method check. + * + * @param TAttribute $attribute + * @param TSubject $subject + */ + abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool; +} diff --git a/vendor/symfony/security-core/Authorization/Voter/VoterInterface.php b/vendor/symfony/security-core/Authorization/Voter/VoterInterface.php new file mode 100644 index 0000000..5255c88 --- /dev/null +++ b/vendor/symfony/security-core/Authorization/Voter/VoterInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * VoterInterface is the interface implemented by all voters. + * + * @author Fabien Potencier + */ +interface VoterInterface +{ + public const ACCESS_GRANTED = 1; + public const ACCESS_ABSTAIN = 0; + public const ACCESS_DENIED = -1; + + /** + * Returns the vote for the given parameters. + * + * This method must return one of the following constants: + * ACCESS_GRANTED, ACCESS_DENIED, or ACCESS_ABSTAIN. + * + * @param mixed $subject The subject to secure + * @param array $attributes An array of attributes associated with the method being invoked + * + * @return self::ACCESS_* + */ + public function vote(TokenInterface $token, mixed $subject, array $attributes): int; +} diff --git a/vendor/symfony/security-core/CHANGELOG.md b/vendor/symfony/security-core/CHANGELOG.md new file mode 100644 index 0000000..47b4a21 --- /dev/null +++ b/vendor/symfony/security-core/CHANGELOG.md @@ -0,0 +1,73 @@ +CHANGELOG +========= + + +7.0 +--- + + * Remove the `Security` class, use `Symfony\Bundle\SecurityBundle\Security` instead + * Require explicit argument when calling `TokenStorage::setToken()` + * Change argument `$lastUsed` of `TokenProviderInterface::updateToken()` to accept `DateTimeInterface` + +6.4 +--- + + * Make `PersistentToken` immutable + * Deprecate accepting only `DateTime` for `TokenProviderInterface::updateToken()`, use `DateTimeInterface` instead + +6.3 +--- + + * Add `AttributesBasedUserProviderInterface` to allow `$attributes` optional argument on `loadUserByIdentifier` + * Add `OidcUser` with OIDC support for `OidcUserInfoTokenHandler` + +6.2 +--- + + * Deprecate the `Security` class, use `Symfony\Bundle\SecurityBundle\Security` instead + * Change the signature of `TokenStorageInterface::setToken()` to `setToken(?TokenInterface $token)` + * Deprecate calling `TokenStorage::setToken()` without arguments + * Add a `ChainUserChecker` to allow calling multiple user checkers for a firewall + +6.0 +--- + + * `TokenInterface` does not extend `Serializable` anymore + * Remove all classes in the `Core\Encoder\` sub-namespace, use the `PasswordHasher` component instead + * Remove methods `getPassword()` and `getSalt()` from `UserInterface`, use `PasswordAuthenticatedUserInterface` + or `LegacyPasswordAuthenticatedUserInterface` instead +* `AccessDecisionManager` requires the strategy to be passed as in instance of `AccessDecisionStrategyInterface` + +5.4.21 +------ + + * [BC BREAK] `AccessDecisionStrategyTestCase::provideStrategyTests()` is now static + +5.4 +--- + + * Add a `CacheableVoterInterface` for voters that vote only on identified attributes and subjects + * Deprecate `AuthenticationEvents::AUTHENTICATION_FAILURE`, use the `LoginFailureEvent` instead + * Deprecate `AnonymousToken`, as the related authenticator was deprecated in 5.3 + * Deprecate `Token::getCredentials()`, tokens should no longer contain credentials (as they represent authenticated sessions) + * Deprecate returning `string|\Stringable` from `Token::getUser()` (it must return a `UserInterface`) + * Deprecate `AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY` and `AuthenticatedVoter::IS_ANONYMOUS`, + use `AuthenticatedVoter::IS_AUTHENTICATED_FULLY` or `AuthenticatedVoter::IS_AUTHENTICATED` instead. + * Deprecate `AuthenticationTrustResolverInterface::isAnonymous()` and the `is_anonymous()` expression + function as anonymous no longer exists in version 6, use the `isFullFledged()` or the new + `isAuthenticated()` instead if you want to check if the request is (fully) authenticated. + * Deprecate the `$authenticationManager` argument of the `AuthorizationChecker` constructor + * Deprecate setting the `$alwaysAuthenticate` argument to `true` and not setting the + `$exceptionOnNoToken` argument to `false` of `AuthorizationChecker` + * Deprecate methods `TokenInterface::isAuthenticated()` and `setAuthenticated`, + return null from "getUser()" instead when a token is not authenticated + * Add `AccessDecisionStrategyInterface` to allow custom access decision strategies + * Add access decision strategies `AffirmativeStrategy`, `ConsensusStrategy`, `PriorityStrategy`, `UnanimousStrategy` + * Deprecate passing the strategy as string to `AccessDecisionManager`, + pass an instance of `AccessDecisionStrategyInterface` instead + * Flag `AccessDecisionManager` as `@final` + +5.3 +--- + +The CHANGELOG for version 5.3 and earlier can be found at https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/Security/CHANGELOG.md diff --git a/vendor/symfony/security-core/Event/AuthenticationEvent.php b/vendor/symfony/security-core/Event/AuthenticationEvent.php new file mode 100644 index 0000000..6fca50d --- /dev/null +++ b/vendor/symfony/security-core/Event/AuthenticationEvent.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Event; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This is a general purpose authentication event. + * + * @author Johannes M. Schmitt + */ +class AuthenticationEvent extends Event +{ + private TokenInterface $authenticationToken; + + public function __construct(TokenInterface $token) + { + $this->authenticationToken = $token; + } + + public function getAuthenticationToken(): TokenInterface + { + return $this->authenticationToken; + } +} diff --git a/vendor/symfony/security-core/Event/AuthenticationSuccessEvent.php b/vendor/symfony/security-core/Event/AuthenticationSuccessEvent.php new file mode 100644 index 0000000..50034d7 --- /dev/null +++ b/vendor/symfony/security-core/Event/AuthenticationSuccessEvent.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Event; + +final class AuthenticationSuccessEvent extends AuthenticationEvent +{ +} diff --git a/vendor/symfony/security-core/Event/VoteEvent.php b/vendor/symfony/security-core/Event/VoteEvent.php new file mode 100644 index 0000000..1b1d6a3 --- /dev/null +++ b/vendor/symfony/security-core/Event/VoteEvent.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Event; + +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This event is dispatched on voter vote. + * + * @author Laurent VOULLEMIER + * + * @internal + */ +final class VoteEvent extends Event +{ + private VoterInterface $voter; + private mixed $subject; + private array $attributes; + private int $vote; + + public function __construct(VoterInterface $voter, mixed $subject, array $attributes, int $vote) + { + $this->voter = $voter; + $this->subject = $subject; + $this->attributes = $attributes; + $this->vote = $vote; + } + + public function getVoter(): VoterInterface + { + return $this->voter; + } + + public function getSubject(): mixed + { + return $this->subject; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + public function getVote(): int + { + return $this->vote; + } +} diff --git a/vendor/symfony/security-core/Exception/AccessDeniedException.php b/vendor/symfony/security-core/Exception/AccessDeniedException.php new file mode 100644 index 0000000..93c3869 --- /dev/null +++ b/vendor/symfony/security-core/Exception/AccessDeniedException.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + +/** + * AccessDeniedException is thrown when the account has not the required role. + * + * @author Fabien Potencier + */ +#[WithHttpStatus(403)] +class AccessDeniedException extends RuntimeException +{ + private array $attributes = []; + private mixed $subject = null; + + public function __construct(string $message = 'Access Denied.', ?\Throwable $previous = null, int $code = 403) + { + parent::__construct($message, $code, $previous); + } + + public function getAttributes(): array + { + return $this->attributes; + } + + public function setAttributes(array|string $attributes): void + { + $this->attributes = (array) $attributes; + } + + public function getSubject(): mixed + { + return $this->subject; + } + + public function setSubject(mixed $subject): void + { + $this->subject = $subject; + } +} diff --git a/vendor/symfony/security-core/Exception/AccountExpiredException.php b/vendor/symfony/security-core/Exception/AccountExpiredException.php new file mode 100644 index 0000000..91ea122 --- /dev/null +++ b/vendor/symfony/security-core/Exception/AccountExpiredException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * AccountExpiredException is thrown when the user account has expired. + * + * @author Fabien Potencier + * @author Alexander + */ +class AccountExpiredException extends AccountStatusException +{ + public function getMessageKey(): string + { + return 'Account has expired.'; + } +} diff --git a/vendor/symfony/security-core/Exception/AccountStatusException.php b/vendor/symfony/security-core/Exception/AccountStatusException.php new file mode 100644 index 0000000..c0176e0 --- /dev/null +++ b/vendor/symfony/security-core/Exception/AccountStatusException.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * AccountStatusException is the base class for authentication exceptions + * caused by the user account status. + * + * @author Fabien Potencier + * @author Alexander + */ +abstract class AccountStatusException extends AuthenticationException +{ + private ?UserInterface $user = null; + + /** + * Get the user. + */ + public function getUser(): ?UserInterface + { + return $this->user; + } + + public function setUser(UserInterface $user): void + { + $this->user = $user; + } + + public function __serialize(): array + { + return [$this->user, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [$this->user, $parentData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/Exception/AuthenticationCredentialsNotFoundException.php b/vendor/symfony/security-core/Exception/AuthenticationCredentialsNotFoundException.php new file mode 100644 index 0000000..fc28e4e --- /dev/null +++ b/vendor/symfony/security-core/Exception/AuthenticationCredentialsNotFoundException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * AuthenticationCredentialsNotFoundException is thrown when an authentication is rejected + * because no Token is available. + * + * @author Fabien Potencier + * @author Alexander + */ +class AuthenticationCredentialsNotFoundException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Authentication credentials could not be found.'; + } +} diff --git a/vendor/symfony/security-core/Exception/AuthenticationException.php b/vendor/symfony/security-core/Exception/AuthenticationException.php new file mode 100644 index 0000000..5d4c443 --- /dev/null +++ b/vendor/symfony/security-core/Exception/AuthenticationException.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * AuthenticationException is the base class for all authentication exceptions. + * + * @author Fabien Potencier + * @author Alexander + */ +#[WithHttpStatus(401)] +class AuthenticationException extends RuntimeException +{ + private ?TokenInterface $token = null; + + public function getToken(): ?TokenInterface + { + return $this->token; + } + + public function setToken(TokenInterface $token): void + { + $this->token = $token; + } + + /** + * Returns all the necessary state of the object for serialization purposes. + * + * There is no need to serialize any entry, they should be returned as-is. + * If you extend this method, keep in mind you MUST guarantee parent data is present in the state. + * Here is an example of how to extend this method: + * + * public function __serialize(): array + * { + * return [$this->childAttribute, parent::__serialize()]; + * } + * + * + * @see __unserialize() + */ + public function __serialize(): array + { + return [$this->token, $this->code, $this->message, $this->file, $this->line]; + } + + /** + * Restores the object state from an array given by __serialize(). + * + * There is no need to unserialize any entry in $data, they are already ready-to-use. + * If you extend this method, keep in mind you MUST pass the parent data to its respective class. + * Here is an example of how to extend this method: + * + * public function __unserialize(array $data): void + * { + * [$this->childAttribute, $parentData] = $data; + * parent::__unserialize($parentData); + * } + * + * + * @see __serialize() + */ + public function __unserialize(array $data): void + { + [$this->token, $this->code, $this->message, $this->file, $this->line] = $data; + } + + /** + * Message key to be used by the translation component. + */ + public function getMessageKey(): string + { + return 'An authentication exception occurred.'; + } + + /** + * Message data to be used by the translation component. + */ + public function getMessageData(): array + { + return []; + } +} diff --git a/vendor/symfony/security-core/Exception/AuthenticationExpiredException.php b/vendor/symfony/security-core/Exception/AuthenticationExpiredException.php new file mode 100644 index 0000000..1d04c5e --- /dev/null +++ b/vendor/symfony/security-core/Exception/AuthenticationExpiredException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * AuthenticationExpiredException is thrown when an authentication token becomes un-authenticated between requests. + * + * In practice, this is due to the User changing between requests (e.g. password changes), + * causes the token to become un-authenticated. + * + * @author Ryan Weaver + */ +class AuthenticationExpiredException extends AccountStatusException +{ + public function getMessageKey(): string + { + return 'Authentication expired because your account information has changed.'; + } +} diff --git a/vendor/symfony/security-core/Exception/AuthenticationServiceException.php b/vendor/symfony/security-core/Exception/AuthenticationServiceException.php new file mode 100644 index 0000000..fa5042e --- /dev/null +++ b/vendor/symfony/security-core/Exception/AuthenticationServiceException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * AuthenticationServiceException is thrown when an authentication request could not be processed due to a system problem. + * + * @author Fabien Potencier + * @author Alexander + */ +class AuthenticationServiceException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Authentication request could not be processed due to a system problem.'; + } +} diff --git a/vendor/symfony/security-core/Exception/BadCredentialsException.php b/vendor/symfony/security-core/Exception/BadCredentialsException.php new file mode 100644 index 0000000..6aeed7b --- /dev/null +++ b/vendor/symfony/security-core/Exception/BadCredentialsException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * BadCredentialsException is thrown when the user credentials are invalid. + * + * @author Fabien Potencier + * @author Alexander + */ +class BadCredentialsException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Invalid credentials.'; + } +} diff --git a/vendor/symfony/security-core/Exception/CookieTheftException.php b/vendor/symfony/security-core/Exception/CookieTheftException.php new file mode 100644 index 0000000..a32f30d --- /dev/null +++ b/vendor/symfony/security-core/Exception/CookieTheftException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * This exception is thrown when the RememberMeServices implementation + * detects that a presented cookie has already been used by someone else. + * + * @author Johannes M. Schmitt + * @author Alexander + */ +class CookieTheftException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Cookie has already been used by someone else.'; + } +} diff --git a/vendor/symfony/security-core/Exception/CredentialsExpiredException.php b/vendor/symfony/security-core/Exception/CredentialsExpiredException.php new file mode 100644 index 0000000..5018377 --- /dev/null +++ b/vendor/symfony/security-core/Exception/CredentialsExpiredException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * CredentialsExpiredException is thrown when the user account credentials have expired. + * + * @author Fabien Potencier + * @author Alexander + */ +class CredentialsExpiredException extends AccountStatusException +{ + public function getMessageKey(): string + { + return 'Credentials have expired.'; + } +} diff --git a/vendor/symfony/security-core/Exception/CustomUserMessageAccountStatusException.php b/vendor/symfony/security-core/Exception/CustomUserMessageAccountStatusException.php new file mode 100644 index 0000000..f59eff9 --- /dev/null +++ b/vendor/symfony/security-core/Exception/CustomUserMessageAccountStatusException.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * An authentication exception caused by the user account status + * where you can control the message shown to the user. + * + * Be sure that the message passed to this exception is something that + * can be shown safely to your user. In other words, avoid catching + * other exceptions and passing their message directly to this class. + * + * @author Vincent Langlet + */ +class CustomUserMessageAccountStatusException extends AccountStatusException +{ + private string $messageKey; + private array $messageData = []; + + public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->setSafeMessage($message, $messageData); + } + + /** + * Sets a message that will be shown to the user. + * + * @param string $messageKey The message or message key + * @param array $messageData Data to be passed into the translator + */ + public function setSafeMessage(string $messageKey, array $messageData = []): void + { + $this->messageKey = $messageKey; + $this->messageData = $messageData; + } + + public function getMessageKey(): string + { + return $this->messageKey; + } + + public function getMessageData(): array + { + return $this->messageData; + } + + public function __serialize(): array + { + return [parent::__serialize(), $this->messageKey, $this->messageData]; + } + + public function __unserialize(array $data): void + { + [$parentData, $this->messageKey, $this->messageData] = $data; + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/Exception/CustomUserMessageAuthenticationException.php b/vendor/symfony/security-core/Exception/CustomUserMessageAuthenticationException.php new file mode 100644 index 0000000..eae66c4 --- /dev/null +++ b/vendor/symfony/security-core/Exception/CustomUserMessageAuthenticationException.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * An authentication exception where you can control the message shown to the user. + * + * Be sure that the message passed to this exception is something that + * can be shown safely to your user. In other words, avoid catching + * other exceptions and passing their message directly to this class. + * + * @author Ryan Weaver + */ +class CustomUserMessageAuthenticationException extends AuthenticationException +{ + private string $messageKey; + private array $messageData = []; + + public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->setSafeMessage($message, $messageData); + } + + /** + * Set a message that will be shown to the user. + * + * @param string $messageKey The message or message key + * @param array $messageData Data to be passed into the translator + */ + public function setSafeMessage(string $messageKey, array $messageData = []): void + { + $this->messageKey = $messageKey; + $this->messageData = $messageData; + } + + public function getMessageKey(): string + { + return $this->messageKey; + } + + public function getMessageData(): array + { + return $this->messageData; + } + + public function __serialize(): array + { + return [parent::__serialize(), $this->messageKey, $this->messageData]; + } + + public function __unserialize(array $data): void + { + [$parentData, $this->messageKey, $this->messageData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/Exception/DisabledException.php b/vendor/symfony/security-core/Exception/DisabledException.php new file mode 100644 index 0000000..b82067c --- /dev/null +++ b/vendor/symfony/security-core/Exception/DisabledException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * DisabledException is thrown when the user account is disabled. + * + * @author Fabien Potencier + * @author Alexander + */ +class DisabledException extends AccountStatusException +{ + public function getMessageKey(): string + { + return 'Account is disabled.'; + } +} diff --git a/vendor/symfony/security-core/Exception/ExceptionInterface.php b/vendor/symfony/security-core/Exception/ExceptionInterface.php new file mode 100644 index 0000000..7bc2b91 --- /dev/null +++ b/vendor/symfony/security-core/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * Base ExceptionInterface for the Security component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/security-core/Exception/InsufficientAuthenticationException.php b/vendor/symfony/security-core/Exception/InsufficientAuthenticationException.php new file mode 100644 index 0000000..0221dfd --- /dev/null +++ b/vendor/symfony/security-core/Exception/InsufficientAuthenticationException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * InsufficientAuthenticationException is thrown if the user credentials are not sufficiently trusted. + * + * This is the case when a user is anonymous and the resource to be displayed has an access role. + * + * @author Fabien Potencier + * @author Alexander + */ +class InsufficientAuthenticationException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Not privileged to request the resource.'; + } +} diff --git a/vendor/symfony/security-core/Exception/InvalidArgumentException.php b/vendor/symfony/security-core/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..6f85e95 --- /dev/null +++ b/vendor/symfony/security-core/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * Base InvalidArgumentException for the Security component. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/security-core/Exception/InvalidCsrfTokenException.php b/vendor/symfony/security-core/Exception/InvalidCsrfTokenException.php new file mode 100644 index 0000000..2041cf6 --- /dev/null +++ b/vendor/symfony/security-core/Exception/InvalidCsrfTokenException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * This exception is thrown when the csrf token is invalid. + * + * @author Johannes M. Schmitt + * @author Alexander + */ +class InvalidCsrfTokenException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Invalid CSRF token.'; + } +} diff --git a/vendor/symfony/security-core/Exception/LazyResponseException.php b/vendor/symfony/security-core/Exception/LazyResponseException.php new file mode 100644 index 0000000..e26a334 --- /dev/null +++ b/vendor/symfony/security-core/Exception/LazyResponseException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +use Symfony\Component\HttpFoundation\Response; + +/** + * A signaling exception that wraps a lazily computed response. + * + * @author Nicolas Grekas + */ +class LazyResponseException extends \Exception implements ExceptionInterface +{ + private Response $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse(): Response + { + return $this->response; + } +} diff --git a/vendor/symfony/security-core/Exception/LockedException.php b/vendor/symfony/security-core/Exception/LockedException.php new file mode 100644 index 0000000..fb81cb0 --- /dev/null +++ b/vendor/symfony/security-core/Exception/LockedException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * LockedException is thrown if the user account is locked. + * + * @author Fabien Potencier + * @author Alexander + */ +class LockedException extends AccountStatusException +{ + public function getMessageKey(): string + { + return 'Account is locked.'; + } +} diff --git a/vendor/symfony/security-core/Exception/LogicException.php b/vendor/symfony/security-core/Exception/LogicException.php new file mode 100644 index 0000000..b9c63e9 --- /dev/null +++ b/vendor/symfony/security-core/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * Base LogicException for the Security component. + * + * @author Iltar van der Berg + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/security-core/Exception/LogoutException.php b/vendor/symfony/security-core/Exception/LogoutException.php new file mode 100644 index 0000000..20efdd2 --- /dev/null +++ b/vendor/symfony/security-core/Exception/LogoutException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * LogoutException is thrown when the account cannot be logged out. + * + * @author Jeremy Mikola + */ +class LogoutException extends RuntimeException +{ + public function __construct(string $message = 'Logout Exception', ?\Throwable $previous = null) + { + parent::__construct($message, 403, $previous); + } +} diff --git a/vendor/symfony/security-core/Exception/ProviderNotFoundException.php b/vendor/symfony/security-core/Exception/ProviderNotFoundException.php new file mode 100644 index 0000000..e4daf4e --- /dev/null +++ b/vendor/symfony/security-core/Exception/ProviderNotFoundException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * ProviderNotFoundException is thrown when no AuthenticationProviderInterface instance + * supports an authentication Token. + * + * @author Fabien Potencier + * @author Alexander + */ +class ProviderNotFoundException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'No authentication provider found to support the authentication token.'; + } +} diff --git a/vendor/symfony/security-core/Exception/RuntimeException.php b/vendor/symfony/security-core/Exception/RuntimeException.php new file mode 100644 index 0000000..95edec8 --- /dev/null +++ b/vendor/symfony/security-core/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * Base RuntimeException for the Security component. + * + * @author Bernhard Schussek + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/security-core/Exception/SessionUnavailableException.php b/vendor/symfony/security-core/Exception/SessionUnavailableException.php new file mode 100644 index 0000000..eec069c --- /dev/null +++ b/vendor/symfony/security-core/Exception/SessionUnavailableException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * This exception is thrown when no session is available. + * + * Possible reasons for this are: + * + * a) The session timed out because the user waited too long. + * b) The user has disabled cookies, and a new session is started on each + * request. + * + * @author Johannes M. Schmitt + * @author Alexander + */ +class SessionUnavailableException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'No session available, it either timed out or cookies are not enabled.'; + } +} diff --git a/vendor/symfony/security-core/Exception/TokenNotFoundException.php b/vendor/symfony/security-core/Exception/TokenNotFoundException.php new file mode 100644 index 0000000..a18f0d0 --- /dev/null +++ b/vendor/symfony/security-core/Exception/TokenNotFoundException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * TokenNotFoundException is thrown if a Token cannot be found. + * + * @author Johannes M. Schmitt + * @author Alexander + */ +class TokenNotFoundException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'No token could be found.'; + } +} diff --git a/vendor/symfony/security-core/Exception/TooManyLoginAttemptsAuthenticationException.php b/vendor/symfony/security-core/Exception/TooManyLoginAttemptsAuthenticationException.php new file mode 100644 index 0000000..da1a1a7 --- /dev/null +++ b/vendor/symfony/security-core/Exception/TooManyLoginAttemptsAuthenticationException.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * This exception is thrown if there where too many failed login attempts in + * this session. + * + * @author Wouter de Jong + */ +class TooManyLoginAttemptsAuthenticationException extends AuthenticationException +{ + private ?int $threshold; + + public function __construct(?int $threshold = null) + { + $this->threshold = $threshold; + } + + public function getMessageData(): array + { + return [ + '%minutes%' => $this->threshold, + '%count%' => (int) $this->threshold, + ]; + } + + public function getMessageKey(): string + { + return 'Too many failed login attempts, please try again '.($this->threshold ? 'in %minutes% minute'.($this->threshold > 1 ? 's' : '').'.' : 'later.'); + } + + public function __serialize(): array + { + return [$this->threshold, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [$this->threshold, $parentData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/Exception/UnsupportedUserException.php b/vendor/symfony/security-core/Exception/UnsupportedUserException.php new file mode 100644 index 0000000..6529fa9 --- /dev/null +++ b/vendor/symfony/security-core/Exception/UnsupportedUserException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * This exception is thrown when an account is reloaded from a provider which + * doesn't support the passed implementation of UserInterface. + * + * @author Johannes M. Schmitt + */ +class UnsupportedUserException extends AuthenticationServiceException +{ +} diff --git a/vendor/symfony/security-core/Exception/UserNotFoundException.php b/vendor/symfony/security-core/Exception/UserNotFoundException.php new file mode 100644 index 0000000..6cd9b71 --- /dev/null +++ b/vendor/symfony/security-core/Exception/UserNotFoundException.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * UserNotFoundException is thrown if a User cannot be found for the given identifier. + * + * @author Fabien Potencier + * @author Alexander + */ +class UserNotFoundException extends AuthenticationException +{ + private ?string $identifier = null; + + public function getMessageKey(): string + { + return 'Username could not be found.'; + } + + /** + * Get the user identifier (e.g. username or email address). + */ + public function getUserIdentifier(): ?string + { + return $this->identifier; + } + + /** + * Set the user identifier (e.g. username or email address). + */ + public function setUserIdentifier(string $identifier): void + { + $this->identifier = $identifier; + } + + public function getMessageData(): array + { + return ['{{ username }}' => $this->identifier, '{{ user_identifier }}' => $this->identifier]; + } + + public function __serialize(): array + { + return [$this->identifier, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [$this->identifier, $parentData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-core/LICENSE b/vendor/symfony/security-core/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/security-core/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/security-core/README.md b/vendor/symfony/security-core/README.md new file mode 100644 index 0000000..fc50dcc --- /dev/null +++ b/vendor/symfony/security-core/README.md @@ -0,0 +1,63 @@ +Security Component - Core +========================= + +Security provides an infrastructure for sophisticated authorization systems, +which makes it possible to easily separate the actual authorization logic from +so called user providers that hold the users credentials. + +Getting Started +--------------- + +```bash +composer require symfony/security-core +``` + +```php +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; +use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; +use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Role\RoleHierarchy; + +$accessDecisionManager = new AccessDecisionManager([ + new AuthenticatedVoter(new AuthenticationTrustResolver()), + new RoleVoter(), + new RoleHierarchyVoter(new RoleHierarchy([ + 'ROLE_ADMIN' => ['ROLE_USER'], + ])) +]); + +$user = new \App\Entity\User(...); +$token = new UsernamePasswordToken($user, 'main', $user->getRoles()); + +if (!$accessDecisionManager->decide($token, ['ROLE_ADMIN'])) { + throw new AccessDeniedException(); +} +``` + +Sponsor +------- + +The Security component for Symfony 7.1 is [backed][1] by [SymfonyCasts][2]. + +Learn Symfony faster by watching real projects being built and actively coding +along with them. SymfonyCasts bridges that learning gap, bringing you video +tutorials and coding challenges. Code on! + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/security.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://symfonycasts.com +[3]: https://symfony.com/sponsor diff --git a/vendor/symfony/security-core/Resources/translations/security.af.xlf b/vendor/symfony/security-core/Resources/translations/security.af.xlf new file mode 100644 index 0000000..7bcb920 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.af.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + 'n Verifikasie probleem het voorgekom. + + + Authentication credentials could not be found. + Verifikasiebewyse kon nie gevind word nie. + + + Authentication request could not be processed due to a system problem. + Verifikasieversoek kon weens 'n stelselprobleem nie verwerk word nie. + + + Invalid credentials. + Ongedige verifikasiebewyse. + + + Cookie has already been used by someone else. + Die koekie is alreeds deur iemand anders gebruik. + + + Not privileged to request the resource. + Nie bevoorreg om die hulpbron aan te vra nie. + + + Invalid CSRF token. + Ongeldige CSRF-teken. + + + No authentication provider found to support the authentication token. + Geen verifikasieverskaffer is gevind wat die verifikasietoken kan ondersteun nie. + + + No session available, it either timed out or cookies are not enabled. + Geen sessie is beskikbaar, die het verval of koekies is nie geaktiveer nie. + + + No token could be found. + Geen teken kon gevind word nie. + + + Username could not be found. + Gebruikersnaam kon nie gevind word nie. + + + Account has expired. + Rekening het verval. + + + Credentials have expired. + Verifikasiebewyse het verval. + + + Account is disabled. + Rekening is deaktiveer. + + + Account is locked. + Rekening is gesluit. + + + Too many failed login attempts, please try again later. + Te veel mislukte aanmeldpogings, probeer asseblief later weer. + + + Invalid or expired login link. + Ongeldige of vervalde aanmeldskakel. + + + Too many failed login attempts, please try again in %minutes% minute. + Te veel mislukte aanmeldpogings, probeer asseblief weer oor %minutes% minuut. + + + Too many failed login attempts, please try again in %minutes% minutes. + Te veel mislukte aanmeldpogings, probeer asseblief weer oor %minutes% minute. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.ar.xlf b/vendor/symfony/security-core/Resources/translations/security.ar.xlf new file mode 100644 index 0000000..f75eb12 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.ar.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + حدث خطأ اثناء الدخول. + + + Authentication credentials could not be found. + لم استطع العثور على معلومات الدخول. + + + Authentication request could not be processed due to a system problem. + لم يكتمل طلب الدخول نتيجه عطل ÙÙ‰ النظام. + + + Invalid credentials. + معلومات الدخول خاطئة. + + + Cookie has already been used by someone else. + Ù…Ù„ÙØ§Øª تعري٠الارتباط(cookies) تم استخدامها من قبل شخص اخر. + + + Not privileged to request the resource. + ليست لديك الصلاحيات الكاÙية لهذا الطلب. + + + Invalid CSRF token. + رمز الموقع غير صحيح. + + + No authentication provider found to support the authentication token. + لا يوجد معر٠للدخول يدعم الرمز المستخدم للدخول. + + + No session available, it either timed out or cookies are not enabled. + لا يوجد صلة بينك Ùˆ بين الموقع اما انها انتهت او ان Ù…ØªØµÙØ­Ùƒ لا يدعم خاصية Ù…Ù„ÙØ§Øª تعري٠الارتباط (cookies). + + + No token could be found. + لم استطع العثور على الرمز. + + + Username could not be found. + لم استطع العثور على اسم الدخول. + + + Account has expired. + انتهت صلاحية الحساب. + + + Credentials have expired. + انتهت صلاحية معلومات الدخول. + + + Account is disabled. + الحساب موقوÙ. + + + Account is locked. + الحساب مغلق. + + + Too many failed login attempts, please try again later. + العديد من محاولات الدخول Ø§Ù„ÙØ§Ø´Ù„ة، يرجى المحاولة مرة أخرى ÙÙŠ وقت لاحق. + + + Invalid or expired login link. + رابط تسجيل الدخول غير صالح أو منتهي الصلاحية. + + + Too many failed login attempts, please try again in %minutes% minute. + العديد من محاولات الدخول Ø§Ù„ÙØ§Ø´Ù„ة، يرجى اعادة المحاولة بعد %minutes% دقيقة. + + + Too many failed login attempts, please try again in %minutes% minutes. + العديد من محاولات الدخول Ø§Ù„ÙØ§Ø´Ù„Ø© ØŒ يرجى اعادة المحاولة بعد %minutes% دقائق. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.az.xlf b/vendor/symfony/security-core/Resources/translations/security.az.xlf new file mode 100644 index 0000000..25cb860 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.az.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + DoÄŸrulama istisnası baÅŸ verdi. + + + Authentication credentials could not be found. + DoÄŸrulama mÉ™lumatları tapılmadı. + + + Authentication request could not be processed due to a system problem. + Sistem xÉ™tası sÉ™bÉ™bilÉ™ doÄŸrulama istÉ™yi emal edilÉ™ bilmÉ™di. + + + Invalid credentials. + Yanlış mÉ™lumat. + + + Cookie has already been used by someone else. + Kuki baÅŸqası tÉ™rÉ™findÉ™n istifadÉ™ edilib. + + + Not privileged to request the resource. + Resurs istÉ™yi üçün imtiyaz yoxdur. + + + Invalid CSRF token. + Yanlış CSRF niÅŸanı. + + + No authentication provider found to support the authentication token. + DoÄŸrulama niÅŸanını dÉ™stÉ™klÉ™yÉ™cÉ™k provayder tapılmadı. + + + No session available, it either timed out or cookies are not enabled. + UyÄŸun seans yoxdur, vaxtı keçib vÉ™ ya kuki aktiv deyil. + + + No token could be found. + NiÅŸan tapılmadı. + + + Username could not be found. + İstifadəçi adı tapılmadı. + + + Account has expired. + Hesabın istifadÉ™ müddÉ™ti bitib. + + + Credentials have expired. + MÉ™lumatların istifadÉ™ müddÉ™ti bitib. + + + Account is disabled. + Hesab qeyri-aktiv edilib. + + + Account is locked. + Hesab kilitlÉ™nib. + + + Too many failed login attempts, please try again later. + Çoxlu uÄŸursuz giriÅŸ təşəbbüsü, zÉ™hmÉ™t olmasa daha sonra yeniden yoxlayın. + + + Invalid or expired login link. + Yanlış vÉ™ ya müddÉ™ti keçmiÅŸ giriÅŸ keçidi. + + + Too many failed login attempts, please try again in %minutes% minute. + HÉ™ddindÉ™n artıq uÄŸursuz giriÅŸ cÉ™hdi, lütfÉ™n %minutes% dÉ™qiqÉ™ É™rzindÉ™ yenidÉ™n yoxlayın. + + + Too many failed login attempts, please try again in %minutes% minutes. + Çox sayda uÄŸursuz giriÅŸ cÉ™hdi, zÉ™hmÉ™t olmasa %minutes% dÉ™qiqÉ™ sonra yenidÉ™n cÉ™hd edin.|Çox sayda uÄŸursuz giriÅŸ cÉ™hdi, zÉ™hmÉ™t olmasa %minutes% dÉ™qiqÉ™ sonra yenidÉ™n cÉ™hd edin. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.be.xlf b/vendor/symfony/security-core/Resources/translations/security.be.xlf new file mode 100644 index 0000000..1943929 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.be.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Памылка аўтÑнтыфікацыі. + + + Authentication credentials could not be found. + Ð”Ð°Ð´Ð·ÐµÐ½Ñ‹Ñ Ð°ÑžÑ‚Ñнтыфікацыі не знойдзены. + + + Authentication request could not be processed due to a system problem. + Запыт аўтÑнтыфікацыі не можа быць апрацаваны Ñž ÑувÑзі з праблемай у ÑÑ–ÑÑ‚Ñме. + + + Invalid credentials. + ÐеÑÐ°Ð¿Ñ€Ð°ÑžÐ´Ð½Ñ‹Ñ Ð´Ð°Ð´Ð·ÐµÐ½Ñ‹Ñ Ð°ÑžÑ‚Ñнтыфікацыі. + + + Cookie has already been used by someone else. + Ðехта іншы ўжо выкарыÑтаў гÑÑ‚Ñ‹Ñ ÐºÑƒÐºÑ– (cookie). + + + Not privileged to request the resource. + ÐдÑутнічаюць правы на запыт гÑтага Ñ€ÑÑурÑу. + + + Invalid CSRF token. + ÐеÑапраўдны CSRF-токен. + + + No authentication provider found to support the authentication token. + Ðе знойдзен правайдар аўтÑнтыфікацыі, Ñкі можа падтрымліваць гÑты токен аўтÑнтыфікацыі. + + + No session available, it either timed out or cookies are not enabled. + СеÑÑ–Ñ Ð½Ðµ даÑтупна, Ñе Ñ‡Ð°Ñ ÑкончыўÑÑ, або кукі (cookies) выключаны. + + + No token could be found. + Токен не знойдзен. + + + Username could not be found. + Ð†Ð¼Ñ ÐºÐ°Ñ€Ñ‹Ñтальніка не знойдзена. + + + Account has expired. + СкончыўÑÑ Ñ‚Ñрмін дзеÑÐ½Ð½Ñ Ð°ÐºÐ°ÑžÐ½Ñ‚Ð°. + + + Credentials have expired. + СкончыўÑÑ Ñ‚Ñрмін дзеÑÐ½Ð½Ñ Ð´Ð°Ð´Ð·ÐµÐ½Ñ‹Ñ… аўтÑнтыфікацыі. + + + Account is disabled. + Ðкаўнт адключан. + + + Account is locked. + Ðкаўнт заблакіраван. + + + Too many failed login attempts, please try again later. + Зашмат нÑўдалых Ñпроб уваходу, калі лаÑка, паÑпрабуйце пазней. + + + Invalid or expired login link. + СпаÑылка Ð´Ð»Ñ ÑžÐ²Ð°Ñ…Ð¾Ð´Ñƒ неÑÐ°Ð¿Ñ€Ð°ÑžÐ´Ð½Ð°Ñ Ð°Ð±Ð¾ пратÑрмінаванаÑ. + + + Too many failed login attempts, please try again in %minutes% minute. + Занадта шмат нÑўдалых Ñпроб уваходу Ñž ÑÑ–ÑÑ‚Ñму, паÑпрабуйце Ñпробу праз %minutes% хвіліну. + + + Too many failed login attempts, please try again in %minutes% minutes. + Занадта шмат нÑўдалых Ñпробаў уваходу, калі лаÑка, паÑпрабуйце зноў праз %minutes% хвіліну.|Занадта шмат нÑўдалых Ñпробаў уваходу, калі лаÑка, паÑпрабуйце зноў праз %minutes% хвіліны.|Занадта шмат нÑўдалых Ñпробаў уваходу, калі лаÑка, паÑпрабуйце зноў праз %minutes% хвілін. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.bg.xlf b/vendor/symfony/security-core/Resources/translations/security.bg.xlf new file mode 100644 index 0000000..7fdd425 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.bg.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Грешка при автентикациÑ. + + + Authentication credentials could not be found. + УдоÑтоверението за Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ðµ е открито. + + + Authentication request could not be processed due to a system problem. + ЗаÑвката за Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ðµ може да бъде обработената поради ÑиÑтемна грешка. + + + Invalid credentials. + Ðевалидно удоÑтоверение за автентикациÑ. + + + Cookie has already been used by someone else. + Тази биÑквитка вече Ñе ползва от нÑкой друг. + + + Not privileged to request the resource. + ÐÑмате права за доÑтъп до този реÑурÑ. + + + Invalid CSRF token. + Ðевалиден CSRF токен. + + + No authentication provider found to support the authentication token. + Ðе е открит провайдър, който да поддържа този токен за автентикациÑ. + + + No session available, it either timed out or cookies are not enabled. + СеÑиÑта не е доÑтъпна, или времето за доÑтъп е изтекло, или биÑквитките не Ñа разрешени. + + + No token could be found. + Токенът не е открит. + + + Username could not be found. + ПотребителÑкото име не е открито. + + + Account has expired. + Ðкаунтът е изтекъл. + + + Credentials have expired. + УдоÑтоверението за Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸ÐºÐ°Ñ†Ð¸Ñ Ðµ изтекло. + + + Account is disabled. + Ðкаунтът е деактивиран. + + + Account is locked. + Ðкаунтът е заключен. + + + Too many failed login attempts, please try again later. + Твърде много неуÑпешни опити за вход, Ð¼Ð¾Ð»Ñ Ð¾Ð¿Ð¸Ñ‚Ð°Ð¹Ñ‚Ðµ по-къÑно. + + + Invalid or expired login link. + Ðевалиден или изтекъл линк за вход. + + + Too many failed login attempts, please try again in %minutes% minute. + Твърде много неуÑпешни опити за вход, Ð¼Ð¾Ð»Ñ Ð¾Ð¿Ð¸Ñ‚Ð°Ð¹Ñ‚Ðµ отново Ñлед %minutes% минута. + + + Too many failed login attempts, please try again in %minutes% minutes. + Твърде много неуÑпешни опити за вход, Ð¼Ð¾Ð»Ñ Ð¾Ð¿Ð¸Ñ‚Ð°Ð¹Ñ‚Ðµ отново Ñлед %minutes% минути. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.bs.xlf b/vendor/symfony/security-core/Resources/translations/security.bs.xlf new file mode 100644 index 0000000..f58dce0 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.bs.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + DoÅ¡lo je do autentifikacijskog izuzetka (exception). + + + Authentication credentials could not be found. + Autentifikacijski podaci nisu pronaÄ‘eni. + + + Authentication request could not be processed due to a system problem. + Autentifikacijski zahtjev ne može biti obraÄ‘en zbog sistemskog problema. + + + Invalid credentials. + Autentifikacijski podaci su neispravni. + + + Cookie has already been used by someone else. + Neko drugi je već iskoristio ovaj kolaÄić (cookie). + + + Not privileged to request the resource. + Nemate privilegije potrebne za pristup ovom resursu. + + + Invalid CSRF token. + CSRF žeton (token) je neispravan. + + + No authentication provider found to support the authentication token. + Nije pronaÄ‘en autentifikacijski provajder koji bi podržao dati autentifikacijski žeton (token). + + + No session available, it either timed out or cookies are not enabled. + Nema dostupnih sesija; ili je istekla ili su kolaÄići (cookies) iksljuÄeni. + + + No token could be found. + Nije pronaÄ‘en nijedan žeton (token). + + + Username could not be found. + KorisniÄko ime nije pronaÄ‘eno. + + + Account has expired. + Nalog je istekao. + + + Credentials have expired. + Autentifikacijski podaci su istekli. + + + Account is disabled. + Nalog je onemogućen. + + + Account is locked. + Nalog je zakljuÄan. + + + Too many failed login attempts, please try again later. + PreviÅ¡e neuspjeÅ¡nih pokuÅ¡aja prijavljivanja, molim pokuÅ¡ajte ponovo kasnije. + + + Invalid or expired login link. + Link za prijavljivanje je istekao ili je neispravan. + + + Too many failed login attempts, please try again in %minutes% minute. + PreviÅ¡e neuspjelih pokuÅ¡aja prijave, pokuÅ¡ajte ponovo za %minutes% minuta. + + + Too many failed login attempts, please try again in %minutes% minutes. + PreviÅ¡e neuspjeÅ¡nih pokuÅ¡aja prijave, pokuÅ¡ajte ponovo za %minutes% minut.|PreviÅ¡e neuspjeÅ¡nih pokuÅ¡aja prijave, pokuÅ¡ajte ponovo za %minutes% minuta. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.ca.xlf b/vendor/symfony/security-core/Resources/translations/security.ca.xlf new file mode 100644 index 0000000..93ff24f --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.ca.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ha succeït un error d'autenticació. + + + Authentication credentials could not be found. + No s'han trobat les credencials d'autenticació. + + + Authentication request could not be processed due to a system problem. + La solicitud d'autenticació no s'ha pogut processar per un problema del sistema. + + + Invalid credentials. + Credencials no vàlides. + + + Cookie has already been used by someone else. + La cookie ja ha estat utilitzada per una altra persona. + + + Not privileged to request the resource. + No té privilegis per solicitar el recurs. + + + Invalid CSRF token. + Token CSRF no vàlid. + + + No authentication provider found to support the authentication token. + No s'ha trobat un proveïdor d'autenticació que suporti el token d'autenticació. + + + No session available, it either timed out or cookies are not enabled. + No hi ha sessió disponible, ha expirat o les cookies no estan habilitades. + + + No token could be found. + No s'ha trobat cap token. + + + Username could not be found. + No s'ha trobat el nom d'usuari. + + + Account has expired. + El compte ha expirat. + + + Credentials have expired. + Les credencials han expirat. + + + Account is disabled. + El compte està deshabilitat. + + + Account is locked. + El compte està bloquejat. + + + Too many failed login attempts, please try again later. + Massa intents d'inici de sessió fallits, si us plau torneu-ho a provar més tard. + + + Invalid or expired login link. + Enllaç d'inici de sessió no vàlid o caducat. + + + Too many failed login attempts, please try again in %minutes% minute. + Massa intents d'inici de sessió fallits, si us plau torneu-ho a provar en %minutes% minut. + + + Too many failed login attempts, please try again in %minutes% minutes. + Massa intents d'inici de sessió fallits, si us plau torneu-ho a provar en %minutes% minuts. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.cs.xlf b/vendor/symfony/security-core/Resources/translations/security.cs.xlf new file mode 100644 index 0000000..a37e34e --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.cs.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + PÅ™i ověřování doÅ¡lo k chybÄ›. + + + Authentication credentials could not be found. + Ověřovací údaje nebyly nalezeny. + + + Authentication request could not be processed due to a system problem. + Požadavek na ověření nemohl být zpracován kvůli systémové chybÄ›. + + + Invalid credentials. + Neplatné pÅ™ihlaÅ¡ovací údaje. + + + Cookie has already been used by someone else. + Cookie již bylo použité nÄ›kým jiným. + + + Not privileged to request the resource. + Nemáte oprávnÄ›ní pÅ™istupovat k prostÅ™edku. + + + Invalid CSRF token. + Neplatný CSRF token. + + + No authentication provider found to support the authentication token. + Poskytovatel pro ověřovací token nebyl nalezen. + + + No session available, it either timed out or cookies are not enabled. + Session není k dispozici, vyprÅ¡ela její platnost, nebo jsou zakázané cookies. + + + No token could be found. + Token nebyl nalezen. + + + Username could not be found. + PÅ™ihlaÅ¡ovací jméno nebylo nalezeno. + + + Account has expired. + Platnost úÄtu vyprÅ¡ela. + + + Credentials have expired. + Platnost pÅ™ihlaÅ¡ovacích údajů vyprÅ¡ela. + + + Account is disabled. + ÚÄet je zakázaný. + + + Account is locked. + ÚÄet je zablokovaný. + + + Too many failed login attempts, please try again later. + PříliÅ¡ mnoho nepovedených pokusů pÅ™ihlášení. Zkuste to prosím pozdÄ›ji. + + + Invalid or expired login link. + Neplatný nebo expirovaný odkaz na pÅ™ihlášení. + + + Too many failed login attempts, please try again in %minutes% minute. + PříliÅ¡ mnoho neúspěšných pokusů o pÅ™ihlášení, zkuste to prosím znovu za %minutes% minutu. + + + Too many failed login attempts, please try again in %minutes% minutes. + PříliÅ¡ mnoho neúspěšných pokusů o pÅ™ihlášení, zkuste to prosím znovu za %minutes% minutu.|PříliÅ¡ mnoho neúspěšných pokusů o pÅ™ihlášení, zkuste to prosím znovu za %minutes% minuty.|PříliÅ¡ mnoho neúspěšných pokusů o pÅ™ihlášení, zkuste to prosím znovu za %minutes% minut. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.cy.xlf b/vendor/symfony/security-core/Resources/translations/security.cy.xlf new file mode 100644 index 0000000..ddb4709 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.cy.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Digwyddodd eithriad dilysu. + + + Authentication credentials could not be found. + Ni ellid dod o hyd i ddogfennau dilysu. + + + Authentication request could not be processed due to a system problem. + Ni ellid prosesu cais dilysu oherwydd problem gyda'r system. + + + Invalid credentials. + Dogfennau annilys. + + + Cookie has already been used by someone else. + Mae rhywun arall eisoes wedi defnyddio'r cwcis. + + + Not privileged to request the resource. + Heb y fraint i ofyn am yr adnodd. + + + Invalid CSRF token. + Tocyn CSRF annilys. + + + No authentication provider found to support the authentication token. + Heb ddod o hyd i ddarparwr dilysu i gefnogi'r tocyn dilysu. + + + No session available, it either timed out or cookies are not enabled. + Dim sesiwn ar gael, naill ai mae wedi dod i ben neu nid yw cwcis wedi'u galluogi. + + + No token could be found. + Heb ddod o hyd i docyn. + + + Username could not be found. + Heb ddod o hyd i enw defnyddiwr. + + + Account has expired. + Mae'r cyfrif wedi dod i ben. + + + Credentials have expired. + Mae'r dogfennau wedi dod i ben. + + + Account is disabled. + Mae'r cyfrif wedi'i analluogi. + + + Account is locked. + Mae'r cyfrif wedi'i gloi. + + + Too many failed login attempts, please try again later. + Gormod o ymdrechion mewngofnodi wedi methu, ceisiwch eto'n hwyrach. + + + Invalid or expired login link. + Dolen mewngofnodi annilys neu wedi dod i ben. + + + Too many failed login attempts, please try again in %minutes% minute. + Gormod o ymdrechion mewngofnodi wedi methu, ceisiwch eto ymhen %minutes% munud. + + + Too many failed login attempts, please try again in %minutes% minutes. + Gormod o ymdrechion mewngofnodi wedi methu, rhowch gynnig arall arni mewn %minutes% munud.|Gormod o ymdrechion mewngofnodi wedi methu, rhowch gynnig arall arni mewn %minutes% munud. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.da.xlf b/vendor/symfony/security-core/Resources/translations/security.da.xlf new file mode 100644 index 0000000..564f0ee --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.da.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + En fejl indtraf ved godkendelse. + + + Authentication credentials could not be found. + Loginoplysninger kunne ikke findes. + + + Authentication request could not be processed due to a system problem. + Godkendelsesanmodningen kunne ikke behandles pÃ¥ grund af en systemfejl. + + + Invalid credentials. + Ugyldige loginoplysninger. + + + Cookie has already been used by someone else. + Cookie er allerede blevet brugt af en anden. + + + Not privileged to request the resource. + Ingen adgang til at forespørge ressourcen. + + + Invalid CSRF token. + Ugyldig CSRF-token. + + + No authentication provider found to support the authentication token. + Ingen godkendelsesudbyder blev fundet til at understøtte godkendelsestoken. + + + No session available, it either timed out or cookies are not enabled. + Ingen session er tilgængelig. Den er enten udløbet eller cookies er ikke aktiveret. + + + No token could be found. + Ingen token kunne findes. + + + Username could not be found. + Brugernavn kunne ikke findes. + + + Account has expired. + Brugerkonto er udløbet. + + + Credentials have expired. + Loginoplysninger er udløbet. + + + Account is disabled. + Brugerkonto er deaktiveret. + + + Account is locked. + Brugerkonto er lÃ¥st. + + + Too many failed login attempts, please try again later. + For mange mislykkede loginforsøg. Prøv venligst igen senere. + + + Invalid or expired login link. + Ugyldigt eller udløbet login-link. + + + Too many failed login attempts, please try again in %minutes% minute. + For mange mislykkede loginforsøg. Prøv venligst igen om %minutes% minut. + + + Too many failed login attempts, please try again in %minutes% minutes. + For mange mislykkede loginforsøg, prøv igen om %minutes% minutter. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.de.xlf b/vendor/symfony/security-core/Resources/translations/security.de.xlf new file mode 100644 index 0000000..c1c457a --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.de.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Es ist ein Fehler bei der Authentifikation aufgetreten. + + + Authentication credentials could not be found. + Es konnten keine Zugangsdaten gefunden werden. + + + Authentication request could not be processed due to a system problem. + Die Authentifikation konnte wegen eines Systemproblems nicht bearbeitet werden. + + + Invalid credentials. + Fehlerhafte Zugangsdaten. + + + Cookie has already been used by someone else. + Cookie wurde bereits von jemand anderem verwendet. + + + Not privileged to request the resource. + Keine Rechte, um die Ressource anzufragen. + + + Invalid CSRF token. + Ungültiges CSRF-Token. + + + No authentication provider found to support the authentication token. + Es wurde kein Authentifizierungs-Provider gefunden, der das Authentifizierungs-Token unterstützt. + + + No session available, it either timed out or cookies are not enabled. + Keine Session verfügbar, entweder ist diese abgelaufen oder Cookies sind nicht aktiviert. + + + No token could be found. + Es wurde kein Token gefunden. + + + Username could not be found. + Der Benutzername wurde nicht gefunden. + + + Account has expired. + Der Account ist abgelaufen. + + + Credentials have expired. + Die Zugangsdaten sind abgelaufen. + + + Account is disabled. + Der Account ist deaktiviert. + + + Account is locked. + Der Account ist gesperrt. + + + Too many failed login attempts, please try again later. + Zu viele fehlgeschlagene Anmeldeversuche, bitte versuchen Sie es später noch einmal. + + + Invalid or expired login link. + Ungültiger oder abgelaufener Anmelde-Link. + + + Too many failed login attempts, please try again in %minutes% minute. + Zu viele fehlgeschlagene Anmeldeversuche, bitte versuchen Sie es in einer Minute noch einmal. + + + Too many failed login attempts, please try again in %minutes% minutes. + Zu viele fehlgeschlagene Anmeldeversuche, bitte versuchen Sie es in %minutes% Minuten noch einmal. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.el.xlf b/vendor/symfony/security-core/Resources/translations/security.el.xlf new file mode 100644 index 0000000..25cfb43 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.el.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Συνέβη ένα σφάλμα πιστοποίησης. + + + Authentication credentials could not be found. + Τα στοιχεία πιστοποίησης δε βÏέθηκαν. + + + Authentication request could not be processed due to a system problem. + Το αίτημα πιστοποίησης δε μποÏεί να επεξεÏγαστεί λόγω σφάλματος του συστήματος. + + + Invalid credentials. + Λανθασμένα στοιχεία σÏνδεσης. + + + Cookie has already been used by someone else. + Το Cookie έχει ήδη χÏησιμοποιηθεί από κάποιον άλλο. + + + Not privileged to request the resource. + Δεν είστε εξουσιοδοτημένος για Ï€Ïόσβαση στο συγκεκÏιμένο πεÏιεχόμενο. + + + Invalid CSRF token. + Μη έγκυÏο CSRF token. + + + No authentication provider found to support the authentication token. + Δε βÏέθηκε κάποιος πάÏοχος πιστοποίησης που να υποστηÏίζει το token πιστοποίησης. + + + No session available, it either timed out or cookies are not enabled. + Δεν υπάÏχει ενεÏγή σÏνοδος (session), είτε έχει λήξει ή τα cookies δεν είναι ενεÏγοποιημένα. + + + No token could be found. + Δεν ήταν δυνατόν να βÏεθεί κάποιο token. + + + Username could not be found. + Το όνομα χÏήστη δε βÏέθηκε. + + + Account has expired. + Ο λογαÏιασμός έχει λήξει. + + + Credentials have expired. + Τα στοιχεία σÏνδεσης έχουν λήξει. + + + Account is disabled. + Ο λογαÏιασμός είναι απενεÏγοποιημένος. + + + Account is locked. + Ο λογαÏιασμός είναι κλειδωμένος. + + + Too many failed login attempts, please try again later. + Πολλαπλές αποτυχημένες απόπειÏες σÏνδεσης, παÏακαλοÏμε ξαναδοκιμάστε αÏγότεÏα. + + + Invalid or expired login link. + Μη έγκυÏος ή ληγμένος σÏνδεσμος σÏνδεσης. + + + Too many failed login attempts, please try again in %minutes% minute. + Πολλαπλές αποτυχημένες απόπειÏες σÏνδεσης, παÏακαλοÏμε ξαναδοκιμάστε σε %minutes% λεπτό. + + + Too many failed login attempts, please try again in %minutes% minutes. + Πολλές αποτυχημένες Ï€Ïοσπάθειες σÏνδεσης, δοκιμάστε ξανά σε %minutes% λεπτά. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.en.xlf b/vendor/symfony/security-core/Resources/translations/security.en.xlf new file mode 100644 index 0000000..dffde89 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.en.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + An authentication exception occurred. + + + Authentication credentials could not be found. + Authentication credentials could not be found. + + + Authentication request could not be processed due to a system problem. + Authentication request could not be processed due to a system problem. + + + Invalid credentials. + Invalid credentials. + + + Cookie has already been used by someone else. + Cookie has already been used by someone else. + + + Not privileged to request the resource. + Not privileged to request the resource. + + + Invalid CSRF token. + Invalid CSRF token. + + + No authentication provider found to support the authentication token. + No authentication provider found to support the authentication token. + + + No session available, it either timed out or cookies are not enabled. + No session available, it either timed out or cookies are not enabled. + + + No token could be found. + No token could be found. + + + Username could not be found. + Username could not be found. + + + Account has expired. + Account has expired. + + + Credentials have expired. + Credentials have expired. + + + Account is disabled. + Account is disabled. + + + Account is locked. + Account is locked. + + + Too many failed login attempts, please try again later. + Too many failed login attempts, please try again later. + + + Invalid or expired login link. + Invalid or expired login link. + + + Too many failed login attempts, please try again in %minutes% minute. + Too many failed login attempts, please try again in %minutes% minute. + + + Too many failed login attempts, please try again in %minutes% minutes. + Too many failed login attempts, please try again in %minutes% minutes. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.es.xlf b/vendor/symfony/security-core/Resources/translations/security.es.xlf new file mode 100644 index 0000000..e8af87e --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.es.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ocurrió un error de autenticación. + + + Authentication credentials could not be found. + No se encontraron las credenciales de autenticación. + + + Authentication request could not be processed due to a system problem. + La solicitud de autenticación no se pudo procesar debido a un problema del sistema. + + + Invalid credentials. + Credenciales no válidas. + + + Cookie has already been used by someone else. + La cookie ya ha sido usada por otra persona. + + + Not privileged to request the resource. + No tiene privilegios para solicitar el recurso. + + + Invalid CSRF token. + Token CSRF no válido. + + + No authentication provider found to support the authentication token. + No se encontró un proveedor de autenticación que soporte el token de autenticación. + + + No session available, it either timed out or cookies are not enabled. + No hay ninguna sesión disponible, ha expirado o las cookies no están habilitados. + + + No token could be found. + No se encontró ningún token. + + + Username could not be found. + No se encontró el nombre de usuario. + + + Account has expired. + La cuenta ha expirado. + + + Credentials have expired. + Las credenciales han expirado. + + + Account is disabled. + La cuenta está deshabilitada. + + + Account is locked. + La cuenta está bloqueada. + + + Too many failed login attempts, please try again later. + Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo más tarde. + + + Invalid or expired login link. + Enlace de inicio de sesión inválido o expirado. + + + Too many failed login attempts, please try again in %minutes% minute. + Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo en %minutes% minuto. + + + Too many failed login attempts, please try again in %minutes% minutes. + Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo en %minutes% minutos. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.et.xlf b/vendor/symfony/security-core/Resources/translations/security.et.xlf new file mode 100644 index 0000000..b87cb71 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.et.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Autentimisel juhtus ootamatu viga. + + + Authentication credentials could not be found. + Autentimisandmeid ei leitud. + + + Authentication request could not be processed due to a system problem. + Autentimispäring ei õnnestunud süsteemi probleemi tõttu. + + + Invalid credentials. + Vigased autentimisandmed. + + + Cookie has already been used by someone else. + Küpsis on juba kellegi teise poolt kasutuses. + + + Not privileged to request the resource. + Ressursi pärimiseks pole piisavalt õiguseid. + + + Invalid CSRF token. + Vigane CSRF märgis. + + + No authentication provider found to support the authentication token. + Ei leitud sobivat autentimismeetodit, mis toetaks autentimismärgist. + + + No session available, it either timed out or cookies are not enabled. + Seanss puudub, see on kas aegunud või pole küpsised lubatud. + + + No token could be found. + Identsustõendit ei leitud. + + + Username could not be found. + Kasutajanime ei leitud. + + + Account has expired. + Kasutajakonto on aegunud. + + + Credentials have expired. + Autentimistunnused on aegunud. + + + Account is disabled. + Kasutajakonto on keelatud. + + + Account is locked. + Kasutajakonto on lukustatud. + + + Too many failed login attempts, please try again later. + Liiga palju ebaõnnestunud autentimise katseid, palun proovi hiljem uuesti. + + + Invalid or expired login link. + Vigane või aegunud sisselogimise link. + + + Too many failed login attempts, please try again in %minutes% minute. + Liiga palju ebaõnnestunud autentimise katseid, palun proovi uuesti %minutes% minuti pärast. + + + Too many failed login attempts, please try again in %minutes% minutes. + Liiga palju nurjunud sisselogimiskatseid, proovige uuesti %minutes% minuti pärast. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.eu.xlf b/vendor/symfony/security-core/Resources/translations/security.eu.xlf new file mode 100644 index 0000000..0f0a713 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.eu.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Autentifikazio-errorea gertatu da. + + + Authentication credentials could not be found. + Ez dira aurkitu autentifikazio-kredentzialak. + + + Authentication request could not be processed due to a system problem. + Ezin izan da autentifikazio-eskaera prozesatu, sistema-arazo bat gertatu da eta. + + + Invalid credentials. + Kredentzialak okerrak dira. + + + Cookie has already been used by someone else. + Dagoeneko beste pertsona batek erabili du cookiea. + + + Not privileged to request the resource. + Ez duzu baliabidea eskatzeko aukerarik. + + + Invalid CSRF token. + CSRF tokena okerra da. + + + No authentication provider found to support the authentication token. + Ez da aurkitu autentifikazio-tokena eutsi dezakeen autentifikazio-hornitzailerik. + + + No session available, it either timed out or cookies are not enabled. + Ez dago saiorik erabilgarri, iraungi egin da edo cookieak ez daude gaituta. + + + No token could be found. + Ez da tokenik aurkitu. + + + Username could not be found. + Ez da erabiltzaile-izena aurkitu. + + + Account has expired. + Kontua iraungi da. + + + Credentials have expired. + Kredentzialak iraungi dira. + + + Account is disabled. + Kontua desgaituta dago. + + + Account is locked. + Kontua blokeatuta dago. + + + Too many failed login attempts, please try again later. + Saioa hasteko saio huts gehiegi, saiatu berriro geroago. + + + Invalid or expired login link. + Sartzeko esteka baliogabea edo iraungia. + + + Too many failed login attempts, please try again in %minutes% minute. + Saioa hasteko huts gehiegi egin dira, saiatu berriro minutu %minutes% geroago. + + + Too many failed login attempts, please try again in %minutes% minutes. + Saioa hasteko saiakera huts gehiegi, saiatu berriro %minutes% minututan. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.fa.xlf b/vendor/symfony/security-core/Resources/translations/security.fa.xlf new file mode 100644 index 0000000..897c34b --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.fa.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + خطایی هنگام احراز هویت رخ داده است. + + + Authentication credentials could not be found. + شرایط احراز هویت ÛŒØ§ÙØª نشد. + + + Authentication request could not be processed due to a system problem. + درخواست احراز هویت به دلیل وجود مشکل در سیستم قابل پردازش نمی باشد. + + + Invalid credentials. + احراز هویت نامعتبر Ù…ÛŒ باشد. + + + Cookie has already been used by someone else. + Cookie قبلا توسط شخص دیگری Ø§Ø³ØªÙØ§Ø¯Ù‡ گردیده است. + + + Not privileged to request the resource. + دسترسی لازم برای درخواست از این منبع را دارا نمی باشید. + + + Invalid CSRF token. + توکن CSRF معتبر نمی باشد. + + + No authentication provider found to support the authentication token. + هیچ ارائه دهنده احراز هویتی برای پشتیبانی از توکن احراز هویت پیدا نشد. + + + No session available, it either timed out or cookies are not enabled. + هیچ جلسه‌ای در دسترس نمی باشد. این میتواند به دلیل پایان ÛŒØ§ÙØªÙ† زمان Ùˆ یا ÙØ¹Ø§Ù„ نبودن Ú©ÙˆÚ©ÛŒ ها باشد. + + + No token could be found. + هیچ توکنی پیدا نشد. + + + Username could not be found. + نام ‌کاربری پیدا نشد. + + + Account has expired. + حساب کاربری منقضی گردیده است. + + + Credentials have expired. + مجوزهای احراز هویت منقضی گردیده‌اند. + + + Account is disabled. + حساب کاربری ØºÛŒØ±ÙØ¹Ø§Ù„ Ù…ÛŒ باشد. + + + Account is locked. + حساب کاربری Ù‚ÙÙ„ گردیده است. + + + Too many failed login attempts, please try again later. + تلاش‌های ناموÙÙ‚ زیادی برای ورود صورت Ú¯Ø±ÙØªÙ‡ است، Ù„Ø·ÙØ§Ù‹ بعداً دوباره امتحان کنید. + + + Invalid or expired login link. + لینک ورود نامعتبر یا تاریخ‌گذشته است. + + + Too many failed login attempts, please try again in %minutes% minute. + تلاش‌های ناموÙÙ‚ زیادی برای ورود صورت Ú¯Ø±ÙØªÙ‡ است، Ù„Ø·ÙØ§Ù‹ %minutes% دقیقه دیگر دوباره امتحان کنید. + + + Too many failed login attempts, please try again in %minutes% minutes. + تعداد Ø¯ÙØ¹Ø§Øª تلاش برای ورود بیش از حد زیاد است، Ù„Ø·ÙØ§ پس از %minutes% دقیقه دوباره تلاش کنید. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.fi.xlf b/vendor/symfony/security-core/Resources/translations/security.fi.xlf new file mode 100644 index 0000000..7df4a19 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.fi.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Autentikointi poikkeus tapahtui. + + + Authentication credentials could not be found. + Autentikoinnin tunnistetietoja ei löydetty. + + + Authentication request could not be processed due to a system problem. + Autentikointipyyntöä ei voitu käsitellä järjestelmäongelman vuoksi. + + + Invalid credentials. + Virheelliset tunnistetiedot. + + + Cookie has already been used by someone else. + Eväste on jo jonkin muun käytössä. + + + Not privileged to request the resource. + Ei oikeutta resurssiin. + + + Invalid CSRF token. + Virheellinen CSRF tunnus. + + + No authentication provider found to support the authentication token. + Autentikointi tunnukselle ei löydetty tuettua autentikointi tarjoajaa. + + + No session available, it either timed out or cookies are not enabled. + Sessio ei ole saatavilla, se on joko vanhentunut tai evästeet eivät ole käytössä. + + + No token could be found. + Tunnusta ei löytynyt. + + + Username could not be found. + Käyttäjätunnusta ei löydetty. + + + Account has expired. + Tili on vanhentunut. + + + Credentials have expired. + Tunnistetiedot ovat vanhentuneet. + + + Account is disabled. + Tili on poistettu käytöstä. + + + Account is locked. + Tili on lukittu. + + + Too many failed login attempts, please try again later. + Liian monta epäonnistunutta kirjautumisyritystä, yritä myöhemmin uudelleen. + + + Invalid or expired login link. + Virheellinen tai vanhentunut kirjautumislinkki. + + + Too many failed login attempts, please try again in %minutes% minute. + Liian monta epäonnistunutta kirjautumisyritystä, yritä uudelleen %minutes% minuutin kuluttua. + + + Too many failed login attempts, please try again in %minutes% minutes. + Liian monta epäonnistunutta kirjautumisyritystä, yritä uudelleen %minutes% minuutin kuluttua. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.fr.xlf b/vendor/symfony/security-core/Resources/translations/security.fr.xlf new file mode 100644 index 0000000..058ad94 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.fr.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Une exception d'authentification s'est produite. + + + Authentication credentials could not be found. + Les identifiants d'authentification n'ont pas pu être trouvés. + + + Authentication request could not be processed due to a system problem. + La requête d'authentification n'a pas pu être executée à cause d'un problème système. + + + Invalid credentials. + Identifiants invalides. + + + Cookie has already been used by someone else. + Le cookie a déjà été utilisé par quelqu'un d'autre. + + + Not privileged to request the resource. + Privilèges insuffisants pour accéder à la ressource. + + + Invalid CSRF token. + Jeton CSRF invalide. + + + No authentication provider found to support the authentication token. + Aucun fournisseur d'authentification n'a été trouvé pour supporter le jeton d'authentification. + + + No session available, it either timed out or cookies are not enabled. + Aucune session disponible, celle-ci a expiré ou les cookies ne sont pas activés. + + + No token could be found. + Aucun jeton n'a pu être trouvé. + + + Username could not be found. + Le nom d'utilisateur n'a pas pu être trouvé. + + + Account has expired. + Le compte a expiré. + + + Credentials have expired. + Les identifiants ont expiré. + + + Account is disabled. + Le compte est désactivé. + + + Account is locked. + Le compte est bloqué. + + + Too many failed login attempts, please try again later. + Plusieurs tentatives de connexion ont échoué, veuillez réessayer plus tard. + + + Invalid or expired login link. + Lien de connexion invalide ou expiré. + + + Too many failed login attempts, please try again in %minutes% minute. + Plusieurs tentatives de connexion ont échoué, veuillez réessayer dans %minutes% minute. + + + Too many failed login attempts, please try again in %minutes% minutes. + Trop de tentatives de connexion échouées, veuillez réessayer dans %minutes% minutes. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.gl.xlf b/vendor/symfony/security-core/Resources/translations/security.gl.xlf new file mode 100644 index 0000000..49f48db --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.gl.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ocorreu un erro de autenticación. + + + Authentication credentials could not be found. + Non se atoparon as credenciais de autenticación. + + + Authentication request could not be processed due to a system problem. + A solicitude de autenticación no puido ser procesada debido a un problema do sistema. + + + Invalid credentials. + Credenciais non válidas. + + + Cookie has already been used by someone else. + A cookie xa foi empregado por outro usuario. + + + Not privileged to request the resource. + Non ten privilexios para solicitar o recurso. + + + Invalid CSRF token. + Token CSRF non válido. + + + No authentication provider found to support the authentication token. + Non se atopou un provedor de autenticación que soporte o token de autenticación. + + + No session available, it either timed out or cookies are not enabled. + Non hai ningunha sesión dispoñible, expirou ou as cookies non están habilitadas. + + + No token could be found. + Non se atopou ningún token. + + + Username could not be found. + Non se atopou o nome de usuario. + + + Account has expired. + A conta expirou. + + + Credentials have expired. + As credenciais expiraron. + + + Account is disabled. + A conta está deshabilitada. + + + Account is locked. + A conta está bloqueada. + + + Too many failed login attempts, please try again later. + Demasiados intentos de inicio de sesión fallados. Téntao de novo máis tarde. + + + Invalid or expired login link. + Ligazón de inicio de sesión non válida ou caducada. + + + Too many failed login attempts, please try again in %minutes% minute. + Demasiados intentos de inicio de sesión errados, por favor, ténteo de novo en %minutes% minuto. + + + Too many failed login attempts, please try again in %minutes% minutes. + Demasiados intentos fallidos de inicio de sesión, inténtao de novo en %minutes% minutos. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.he.xlf b/vendor/symfony/security-core/Resources/translations/security.he.xlf new file mode 100644 index 0000000..b1d6afd --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.he.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + שגי××” ב×ימות + + + Authentication credentials could not be found. + פרטי זיהוי ×œ× × ×ž×¦×ו. + + + Authentication request could not be processed due to a system problem. + ×œ× × ×™×ª×Ÿ ×”×™×” לעבד ×ת בקשת ×ימות בגלל בעיית מערכת. + + + Invalid credentials. + ×©× ×ž×©×ª×ž×© ×ו ×¡×™×¡×ž× ×©×’×•×™×™×. + + + Cookie has already been used by someone else. + עוגיה כבר שומשה. + + + Not privileged to request the resource. + ×ין הרש××” מת×ימה. + + + Invalid CSRF token. + ×סימון CSRF ×œ× ×—×•×§×™. + + + No authentication provider found to support the authentication token. + ×œ× × ×ž×¦× ×¡×¤×§ ×ימות המת×ימה לבקשה. + + + No session available, it either timed out or cookies are not enabled. + ×ין סיישן זמין, ×ו ×©×ª× ×”×–×ž×Ÿ הקצוב ×ו העוגיות ×ינן מופעלות. + + + No token could be found. + הטוקן ×œ× × ×ž×¦×. + + + Username could not be found. + ×©× ×ž×©×ª×ž×© ×œ× × ×ž×¦×. + + + Account has expired. + החשבון פג תוקף. + + + Credentials have expired. + פרטי התחברות פקעו תוקף. + + + Account is disabled. + החשבון מבוטל. + + + Account is locked. + החשבון נעול. + + + Too many failed login attempts, please try again later. + יותר מדי ניסיונות כניסה כושלי×, ×× × × ×¡×” שוב מ×וחר יותר. + + + Invalid or expired login link. + קישור כניסה ×œ× ×—×•×§×™ ×ו שפג תוקפו. + + + Too many failed login attempts, please try again in %minutes% minute. + יותר מדי ניסיונות כניסה כושלי×, ×× × × ×¡×” שוב בוד %minutes% דקה. + + + Too many failed login attempts, please try again in %minutes% minutes. + יותר מדי ניסיונות כניסה כושלי×, ×× × × ×¡×” שוב בעוד %minutes% דקות. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.hr.xlf b/vendor/symfony/security-core/Resources/translations/security.hr.xlf new file mode 100644 index 0000000..f3b5a25 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.hr.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Dogodila se autentifikacijske iznimka. + + + Authentication credentials could not be found. + Autentifikacijski podaci nisu pronaÄ‘eni. + + + Authentication request could not be processed due to a system problem. + Autentifikacijski zahtjev nije moguće provesti uslijed sistemskog problema. + + + Invalid credentials. + Neispravni akreditacijski podaci. + + + Cookie has already been used by someone else. + Cookie je već netko drugi iskoristio. + + + Not privileged to request the resource. + Nemate privilegije zahtijevati resurs. + + + Invalid CSRF token. + Neispravan CSRF token. + + + No authentication provider found to support the authentication token. + Nije pronaÄ‘en autentifikacijski provider koji bi podržao autentifikacijski token. + + + No session available, it either timed out or cookies are not enabled. + Sesija nije dostupna, ili je istekla ili cookies nisu omogućeni. + + + No token could be found. + Token nije pronaÄ‘en. + + + Username could not be found. + KorisniÄko ime nije pronaÄ‘eno. + + + Account has expired. + RaÄun je isteko. + + + Credentials have expired. + Akreditacijski podaci su istekli. + + + Account is disabled. + RaÄun je onemogućen. + + + Account is locked. + RaÄun je zakljuÄan. + + + Too many failed login attempts, please try again later. + PreviÅ¡e neuspjelih pokuÅ¡aja prijave, molim pokuÅ¡ajte ponovo kasnije. + + + Invalid or expired login link. + Link za prijavu je isteako ili je neispravan. + + + Too many failed login attempts, please try again in %minutes% minute. + PreviÅ¡e neuspjelih pokuÅ¡aja prijave, molim pokuÅ¡ajte ponovo za %minutes% minutu. + + + Too many failed login attempts, please try again in %minutes% minutes. + PreviÅ¡e neuspjelih pokuÅ¡aja prijave, pokuÅ¡ajte ponovo za %minutes% minutu.|PreviÅ¡e neuspjelih pokuÅ¡aja prijave, pokuÅ¡ajte ponovo za %minutes% minute.|PreviÅ¡e neuspjelih pokuÅ¡aja prijave, pokuÅ¡ajte ponovo za %minutes% minuta. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.hu.xlf b/vendor/symfony/security-core/Resources/translations/security.hu.xlf new file mode 100644 index 0000000..06096dc --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.hu.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Hitelesítési hiba lépett fel. + + + Authentication credentials could not be found. + Nem találhatók hitelesítési információk. + + + Authentication request could not be processed due to a system problem. + A hitelesítési kérést rendszerhiba miatt nem lehet feldolgozni. + + + Invalid credentials. + Érvénytelen hitelesítési információk. + + + Cookie has already been used by someone else. + Ezt a sütit valaki más már felhasználta. + + + Not privileged to request the resource. + Nem rendelkezik az erÅ‘forrás eléréséhez szükséges jogosultsággal. + + + Invalid CSRF token. + Érvénytelen CSRF token. + + + No authentication provider found to support the authentication token. + Nem található a hitelesítési tokent támogató hitelesítési szolgáltatás. + + + No session available, it either timed out or cookies are not enabled. + Munkamenet nem áll rendelkezésre, túllépte az idÅ‘keretet vagy a sütik le vannak tiltva. + + + No token could be found. + Nem található token. + + + Username could not be found. + A felhasználónév nem található. + + + Account has expired. + A fiók lejárt. + + + Credentials have expired. + A hitelesítési információk lejártak. + + + Account is disabled. + Felfüggesztett fiók. + + + Account is locked. + Zárolt fiók. + + + Too many failed login attempts, please try again later. + Túl sok sikertelen bejelentkezési kísérlet, kérjük próbálja újra késÅ‘bb. + + + Invalid or expired login link. + Érvénytelen vagy lejárt bejelentkezési link. + + + Too many failed login attempts, please try again in %minutes% minute. + Túl sok sikertelen bejelentkezési kísérlet, kérjük próbálja újra %minutes% perc múlva. + + + Too many failed login attempts, please try again in %minutes% minutes. + Túl sok sikertelen bejelentkezési kísérlet, kérjük, próbálja újra %minutes% perc múlva. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.hy.xlf b/vendor/symfony/security-core/Resources/translations/security.hy.xlf new file mode 100644 index 0000000..e506c91 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.hy.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Õ†Õ¸Ö‚ÕµÕ¶Õ¡Õ¯Õ¡Õ¶Õ¡ÖÕ´Õ¡Õ¶ Õ½Õ­Õ¡Õ¬Ö‰ + + + Authentication credentials could not be found. + Õ†Õ¸Ö‚ÕµÕ¶Õ¡Õ¯Õ¡Õ¶Õ¡ÖÕ´Õ¡Õ¶ Õ¿Õ¾ÕµÕ¡Õ¬Õ¶Õ¥Ö€Õ¨ Õ¹Õ¥Õ¶ Õ£Õ¿Õ¶Õ¾Õ¥Õ¬Ö‰ + + + Authentication request could not be processed due to a system problem. + Õ€Õ¡Õ´Õ¡Õ¯Õ¡Ö€Õ£Õ¡ÕµÕ«Õ¶ Õ½Õ­Õ¡Õ¬Õ Õ¶Õ¸Ö‚ÕµÕ¶Õ¡Õ¯Õ¡Õ¶Õ¡ÖÕ´Õ¡Õ¶ Õ°Õ¡ÖÖ€Õ´Õ¡Õ¶ ÕºÖ€Õ¸ÖÕ¥Õ½Õ«Õ¶Õ£Õ« ÕªÕ¡Õ´Õ¡Õ¶Õ¡Õ¯Ö‰ + + + Invalid credentials. + ÕÕ­Õ¡Õ¬ Õ´Õ¸Ö‚Õ¿Ö„Õ¡ÕµÕ«Õ¶ Õ¿Õ¾ÕµÕ¡Õ¬Õ¶Õ¥Ö€ + + + Cookie has already been used by someone else. + Cookie-Õ¶ Õ¡Ö€Õ¤Õ¥Õ¶ Ö…Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¾Õ¸Ö‚Õ´ Õ§ Õ¸Ö‚Ö€Õ«Õ·Õ« Õ¯Õ¸Õ²Õ´Õ«ÖÖ‰ + + + Not privileged to request the resource. + Ռեսուրսի Õ°Õ¡Ö€ÖÕ´Õ¡Õ¶ Õ°Õ¡Õ´Õ¡Ö€ Õ¹Õ¯Õ¡ Õ©Õ¸Ö‚ÕµÕ¬Õ¡Õ¿Õ¾Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶Ö‰ + + + Invalid CSRF token. + Ô±Õ¶Õ¾Õ¡Õ¾Õ¥Ö€ CSRF Õ©Õ¸Ö„Õ¥Õ¶Ö‰ + + + No authentication provider found to support the authentication token. + Õ†Õ¸Ö‚ÕµÕ¶Õ¡Õ¯Õ¡Õ¶Õ¡ÖÕ´Õ¡Õ¶ Õ¸Õ¹ Õ´Õ« Õ´Õ¡Õ¿Õ¡Õ¯Õ¡Ö€Õ¡Ö€ Õ¹Õ« Õ£Õ¿Õ¶Õ¾Õ¥Õ¬, Õ¸Ö€ Õ¡Õ»Õ¡Õ¯ÖÕ« Õ¶Õ¸Ö‚ÕµÕ¶Õ¡Õ¯Õ¡Õ¶Õ¡ÖÕ´Õ¡Õ¶ Õ©Õ¸Ö„Õ¥Õ¶Õ¨Ö‰ + + + No session available, it either timed out or cookies are not enabled. + Õ€Õ¡Õ½Õ¡Õ¶Õ¥Õ¬Õ« Õ½Õ¥Õ½Õ«Õ¡ Õ¹Õ¯Õ¡, Õ¯Õ¡Õ´ Õ¡ÕµÕ¶ Õ½ÕºÕ¡Õ¼Õ¾Õ¥Õ¬ Õ§ Õ¯Õ¡Õ´ cookie-Õ¶Õ¥Ö€Õ¨ Õ¡Õ¶Õ»Õ¡Õ¿Õ¾Õ¡Õ® Õ¥Õ¶: + + + No token could be found. + Ô¹Õ¸Ö„Õ¥Õ¶Õ¨ Õ¹Õ« Õ£Õ¿Õ¶Õ¾Õ¥Õ¬Ö‰ + + + Username could not be found. + Õ•Õ£Õ¿Õ¡Õ¶Õ¸Ö‚Õ¶Õ¨ Õ¹Õ« Õ£Õ¿Õ¶Õ¾Õ¥Õ¬Ö‰ + + + Account has expired. + Õ€Õ¡Õ·Õ«Õ¾Õ¨ ÕªÕ¡Õ´Õ¯Õ¥Õ¿Õ¡Õ¶Ö Õ§Ö‰ + + + Credentials have expired. + Õ„Õ¸Ö‚Õ¿Ö„Õ¡ÕµÕ«Õ¶ Õ¿Õ¾ÕµÕ¡Õ¬Õ¶Õ¥Ö€Õ¨ ÕªÕ¡Õ´Õ¯Õ¥Õ¿Õ¡Õ¶Ö Õ¥Õ¶Ö‰ + + + Account is disabled. + Õ€Õ¡Õ·Õ«Õ¾Õ¨ Õ¤Õ¥Õ¯Õ¡Õ¿Õ«Õ¾Õ¡ÖÕ¾Õ¡Õ® Õ§Ö‰ + + + Account is locked. + Õ€Õ¡Õ·Õ«Õ¾Õ¶ Õ¡Ö€Õ£Õ¥Õ¬Õ¡ÖƒÕ¡Õ¯Õ¾Õ¡Õ® Õ§Ö‰ + + + Too many failed login attempts, please try again later. + Õ‰Õ¡ÖƒÕ«Ö Õ·Õ¡Õ¿ Õ´Õ¸Ö‚Õ¿Ö„Õ« ÖƒÕ¸Ö€Õ±Õ¥Ö€, Õ­Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ ÖƒÕ¸Ö€Õ±Õ¥Õ¬ Õ´Õ« ÖƒÕ¸Ö„Ö€ Õ¸Ö‚Õ· + + + Invalid or expired login link. + Ô±Õ¶Õ¾Õ¡Õ¾Õ¥Ö€ Õ¯Õ¡Õ´ ÕªÕ¡Õ´Õ¯Õ¥Õ¿Õ¡Õ¶Ö Õ´Õ¸Ö‚Õ¿Ö„Õ« Õ°Õ²Õ¸Ö‚Õ´Ö‰ + + + Too many failed login attempts, please try again in %minutes% minute. + Õ„Õ¸Ö‚Õ¿Ö„Õ« Õ¹Õ¡ÖƒÕ¡Õ¦Õ¡Õ¶Ö Õ·Õ¡Õ¿ Õ¡Õ¶Õ°Õ¡Õ»Õ¸Õ² ÖƒÕ¸Ö€Õ±Õ¥Ö€: Ô½Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ Õ¯Ö€Õ¯Õ«Õ¶ ÖƒÕ¸Ö€Õ±Õ¥Õ¬ %minutes Ö€Õ¸ÕºÕ¥: + + + Too many failed login attempts, please try again in %minutes% minutes. + Õ‰Õ¡ÖƒÕ¡Õ¦Õ¡Õ¶Ö Õ·Õ¡Õ¿ Õ¡Õ¶Õ°Õ¡Õ»Õ¸Õ² Õ´Õ¸Ö‚Õ¿Ö„Õ« ÖƒÕ¸Ö€Õ±Õ¥Ö€, Õ­Õ¶Õ¤Ö€Õ¸Ö‚Õ´ Õ¥Õ¶Ö„ ÖƒÕ¸Ö€Õ±Õ¥Õ¬ Õ¯Ö€Õ¯Õ«Õ¶ %minutes% Ö€Õ¸ÕºÕ¥Õ«Ö. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.id.xlf b/vendor/symfony/security-core/Resources/translations/security.id.xlf new file mode 100644 index 0000000..4c1cd99 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.id.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Terjadi kesalahan otentikasi. + + + Authentication credentials could not be found. + Kredensial otentikasi tidak bisa ditemukan. + + + Authentication request could not be processed due to a system problem. + Permintaan otentikasi tidak bisa diproses karena masalah sistem. + + + Invalid credentials. + Kredensial tidak valid. + + + Cookie has already been used by someone else. + Cookie sudah digunakan oleh orang lain. + + + Not privileged to request the resource. + Tidak berhak untuk meminta sumber daya. + + + Invalid CSRF token. + Token CSRF tidak valid. + + + No authentication provider found to support the authentication token. + Tidak ditemukan penyedia otentikasi untuk mendukung token otentikasi. + + + No session available, it either timed out or cookies are not enabled. + Tidak ada sesi yang tersedia, mungkin waktu sudah habis atau cookie tidak diaktifkan + + + No token could be found. + Tidak ada token yang bisa ditemukan. + + + Username could not be found. + Username tidak bisa ditemukan. + + + Account has expired. + Akun telah berakhir. + + + Credentials have expired. + Kredensial telah berakhir. + + + Account is disabled. + Akun dinonaktifkan. + + + Account is locked. + Akun terkunci. + + + Too many failed login attempts, please try again later. + Terlalu banyak percobaan login yang gagal, silahkan coba lagi nanti. + + + Invalid or expired login link. + Link login tidak valid atau sudah kedaluwarsa. + + + Too many failed login attempts, please try again in %minutes% minute. + Terlalu banyak percobaan login yang gagal, silahkan coba lagi dalam %minutes% menit. + + + Too many failed login attempts, please try again in %minutes% minutes. + Terlalu banyak upaya login yang gagal, silakan coba lagi dalam beberapa %minutes% menit. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.it.xlf b/vendor/symfony/security-core/Resources/translations/security.it.xlf new file mode 100644 index 0000000..72eace2 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.it.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Si è verificato un errore di autenticazione. + + + Authentication credentials could not be found. + Impossibile trovare le credenziali di autenticazione. + + + Authentication request could not be processed due to a system problem. + La richiesta di autenticazione non può essere processata a causa di un errore di sistema. + + + Invalid credentials. + Credenziali non valide. + + + Cookie has already been used by someone else. + Il cookie è già stato usato da qualcun altro. + + + Not privileged to request the resource. + Non hai i privilegi per richiedere questa risorsa. + + + Invalid CSRF token. + CSRF token non valido. + + + No authentication provider found to support the authentication token. + Non è stato trovato un valido fornitore di autenticazione per supportare il token. + + + No session available, it either timed out or cookies are not enabled. + Nessuna sessione disponibile, può essere scaduta o i cookie non sono abilitati. + + + No token could be found. + Nessun token trovato. + + + Username could not be found. + Username non trovato. + + + Account has expired. + Account scaduto. + + + Credentials have expired. + Credenziali scadute. + + + Account is disabled. + L'account è disabilitato. + + + Account is locked. + L'account è bloccato. + + + Too many failed login attempts, please try again later. + Troppi tentativi di login falliti, riprova tra un po'. + + + Invalid or expired login link. + Link di login scaduto o non valido. + + + Too many failed login attempts, please try again in %minutes% minute. + Troppi tentativi di login falliti, riprova tra %minutes% minuto. + + + Too many failed login attempts, please try again in %minutes% minutes. + Troppi tentativi di login falliti, riprova tra %minutes% minuti. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.ja.xlf b/vendor/symfony/security-core/Resources/translations/security.ja.xlf new file mode 100644 index 0000000..bc3a18a --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.ja.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + èªè¨¼ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ + + + Authentication credentials could not be found. + èªè¨¼è³‡æ ¼ãŒã‚りã¾ã›ã‚“。 + + + Authentication request could not be processed due to a system problem. + システムã®å•題ã«ã‚ˆã‚Šèªè¨¼è¦æ±‚を処ç†ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ + + + Invalid credentials. + 資格ãŒç„¡åйã§ã™ã€‚ + + + Cookie has already been used by someone else. + Cookie ãŒåˆ¥ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚ + + + Not privileged to request the resource. + リソースをリクエストã™ã‚‹æ¨©é™ãŒã‚りã¾ã›ã‚“。 + + + Invalid CSRF token. + CSRF トークンãŒç„¡åйã§ã™ã€‚ + + + No authentication provider found to support the authentication token. + èªè¨¼ãƒˆãƒ¼ã‚¯ãƒ³ã‚’サãƒãƒ¼ãƒˆã™ã‚‹èªè¨¼ãƒ—ロãƒã‚¤ãƒ€ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。 + + + No session available, it either timed out or cookies are not enabled. + 利用å¯èƒ½ãªã‚»ãƒƒã‚·ãƒ§ãƒ³ãŒã‚りã¾ã›ã‚“。タイムアウトã—ãŸã‹ã€Cookie ãŒç„¡åйã«ãªã£ã¦ã„ã¾ã™ã€‚ + + + No token could be found. + トークンãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。 + + + Username could not be found. + ユーザーåãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。 + + + Account has expired. + ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒæœ‰åŠ¹æœŸé™åˆ‡ã‚Œã§ã™ã€‚ + + + Credentials have expired. + è³‡æ ¼ãŒæœ‰åŠ¹æœŸé™åˆ‡ã‚Œã§ã™ã€‚ + + + Account is disabled. + アカウントãŒç„¡åйã§ã™ã€‚ + + + Account is locked. + アカウントã¯ãƒ­ãƒƒã‚¯ã•れã¦ã„ã¾ã™ã€‚ + + + Too many failed login attempts, please try again later. + ログイン試行回数を超ãˆã¾ã—ãŸã€‚ã—ã°ã‚‰ãã—ã¦å†åº¦ãŠè©¦ã—ãã ã•ã„。 + + + Invalid or expired login link. + ãƒ­ã‚°ã‚¤ãƒ³ãƒªãƒ³ã‚¯ãŒæœ‰åŠ¹æœŸé™åˆ‡ã‚Œã€ã‚‚ã—ãã¯ç„¡åйã§ã™ã€‚ + + + Too many failed login attempts, please try again in %minutes% minute. + ログイン試行回数ãŒå¤šã™ãŽã¾ã™ã€‚%minutes%分後ã«å†åº¦ãŠè©¦ã—ãã ã•ã„。 + + + Too many failed login attempts, please try again in %minutes% minutes. + ログイン試行回数ãŒå¤šã™ãŽã¾ã™ã€‚%minutes%分後ã«å†åº¦ãŠè©¦ã—ãã ã•ã„。 + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.lb.xlf b/vendor/symfony/security-core/Resources/translations/security.lb.xlf new file mode 100644 index 0000000..181ef24 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.lb.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Bei der Authentifikatioun ass e Feeler opgetrueden. + + + Authentication credentials could not be found. + Et konnte keng Zouganksdate fonnt ginn. + + + Authentication request could not be processed due to a system problem. + D'Ufro fir eng Authentifikatioun konnt wéinst engem Problem vum System net beaarbecht ginn. + + + Invalid credentials. + Ongëlteg Zouganksdaten. + + + Cookie has already been used by someone else. + De Cookie gouf scho vun engem anere benotzt. + + + Not privileged to request the resource. + Keng Rechter fir d'Ressource unzefroen. + + + Invalid CSRF token. + Ongëltegen CSRF-Token. + + + No authentication provider found to support the authentication token. + Et gouf keen Authentifizéierungs-Provider fonnt deen den Authentifizéierungs-Token ënnerstëtzt. + + + No session available, it either timed out or cookies are not enabled. + Keng Sëtzung disponibel. Entweder ass se ofgelaf oder Cookies sinn net aktivéiert. + + + No token could be found. + Et konnt keen Token fonnt ginn. + + + Username could not be found. + De Benotzernumm konnt net fonnt ginn. + + + Account has expired. + Den Account ass ofgelaf. + + + Credentials have expired. + D'Zouganksdate sinn ofgelaf. + + + Account is disabled. + De Konto ass deaktivéiert. + + + Account is locked. + De Konto ass gespaart. + + + Too many failed login attempts, please try again later. + Ze vill mësslonge Login-Versich, w.e.g. méi spéit nach emol probéieren. + + + Invalid or expired login link. + Ongëltegen oder ofgelafene Login-Link. + + + Too many failed login attempts, please try again in %minutes% minute. + Zu vill fehlgeschloen Loginversich, w. e. g. probéiert nach am %minutes% Minutt. + + + Too many failed login attempts, please try again in %minutes% minutes. + Ze vill Feeler beim Umellen, versicht weg erëm an %minutes% Minutten. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.lt.xlf b/vendor/symfony/security-core/Resources/translations/security.lt.xlf new file mode 100644 index 0000000..8053d0d --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.lt.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ä®vyko autentifikacijos klaida. + + + Authentication credentials could not be found. + Nepavyko rasti autentifikacijos duomenų. + + + Authentication request could not be processed due to a system problem. + Autentifikacijos užklausos nepavyko įvykdyti dÄ—l sistemos klaidų. + + + Invalid credentials. + Klaidingi duomenys. + + + Cookie has already been used by someone else. + Slapukas buvo panaudotas kažkam kitam. + + + Not privileged to request the resource. + Neturite teisių pasiektį resursÄ…. + + + Invalid CSRF token. + Neteisingas CSRF raktas. + + + No authentication provider found to support the authentication token. + Nerastas autentifikacijos tiekÄ—jas, kuris palaikytų autentifikacijos raktÄ…. + + + No session available, it either timed out or cookies are not enabled. + Sesija yra nepasiekiama, pasibaigÄ— galiojimo laikas arba slapukai yra iÅ¡jungti. + + + No token could be found. + Nepavyko rasti rakto. + + + Username could not be found. + Tokio naudotojo vardo nepavyko rasti. + + + Account has expired. + Paskyros galiojimo laikas baigÄ—si. + + + Credentials have expired. + Autentifikacijos duomenų galiojimo laikas baigÄ—si. + + + Account is disabled. + Paskyra yra iÅ¡jungta. + + + Account is locked. + Paskyra yra užblokuota. + + + Too many failed login attempts, please try again later. + Per daug nepavykusių prisijungimo bandymų, pabandykite dar kartÄ… vÄ—liau. + + + Invalid or expired login link. + Netinkama arba pasibaigusio galiojimo laiko prisijungimo nuoroda. + + + Too many failed login attempts, please try again in %minutes% minute. + Per daug nepavykusių prisijungimo bandymų, pabandykite dar kartÄ… po %minutes% minutÄ—s. + + + Too many failed login attempts, please try again in %minutes% minutes. + Per daug nesÄ—kmingų prisijungimo bandymų, bandykite vÄ—l po %minutes% minutÄ—s.|Per daug nesÄ—kmingų prisijungimo bandymų, bandykite vÄ—l po %minutes% minuÄių. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.lv.xlf b/vendor/symfony/security-core/Resources/translations/security.lv.xlf new file mode 100644 index 0000000..fdf0a09 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.lv.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + RadÄs autentifikÄcijas kļūda. + + + Authentication credentials could not be found. + AutentifikÄcijas dati nav atrasti. + + + Authentication request could not be processed due to a system problem. + AutentifikÄcijas pieprasÄ«jums nevar tikt apstrÄdÄts sistÄ“mas problÄ“mas dēļ. + + + Invalid credentials. + NederÄ«gi autentifikÄcijas dati. + + + Cookie has already been used by someone else. + KÄds cits jau izmantoja sÄ«kdatni. + + + Not privileged to request the resource. + Nav tiesÄ«bu šī resursa izsaukÅ¡anai. + + + Invalid CSRF token. + NederÄ«gs CSRF talons. + + + No authentication provider found to support the authentication token. + Nav atrasts, autentifikÄcijas talonu atbalstoÅ¡s, autentifikÄcijas sniedzÄ“js. + + + No session available, it either timed out or cookies are not enabled. + Sesija nav pieejama - vai nu tÄ beidzÄs, vai nu sÄ«kdatnes nav iespÄ“jotas. + + + No token could be found. + Nevar atrast nevienu talonu. + + + Username could not be found. + Nevar atrast lietotÄjvÄrdu. + + + Account has expired. + Konta derÄ«guma termiņš ir beidzies. + + + Credentials have expired. + AutentifikÄcijas datu derÄ«guma termiņš ir beidzies. + + + Account is disabled. + Konts ir atspÄ“jots. + + + Account is locked. + Konts ir slÄ“gts. + + + Too many failed login attempts, please try again later. + PÄrÄk daudz atteiktu autentifikÄcijas mēģinÄjumu, lÅ«dzu, mēģiniet vÄ“lreiz vÄ“lÄk. + + + Invalid or expired login link. + AutentifikÄcijas saite ir nederÄ«ga vai arÄ« tai ir beidzies derÄ«guma termiņš. + + + Too many failed login attempts, please try again in %minutes% minute. + PÄrÄk daudz nesekmÄ«gu autentifikÄcijas mēģinÄjumu, lÅ«dzu mēģiniet vÄ“lreiz pÄ“c %minutes% minÅ«tes. + + + Too many failed login attempts, please try again in %minutes% minutes. + PÄrÄk daudz neveiksmÄ«gu autentifikÄcijas mēģinÄjumu, lÅ«dzu, mēģiniet vÄ“lreiz pÄ“c %minutes% minÅ«tes.|PÄrÄk daudz neveiksmÄ«gu autentifikÄcijas mēģinÄjumu, lÅ«dzu, mēģiniet vÄ“lreiz pÄ“c %minutes% minÅ«tÄ“m. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.mk.xlf b/vendor/symfony/security-core/Resources/translations/security.mk.xlf new file mode 100644 index 0000000..ba046ec --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.mk.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + ÐаÑтана грешка во автентикацијата. + + + Authentication credentials could not be found. + Ðкредитивите за автентикација не Ñе пронајдени. + + + Authentication request could not be processed due to a system problem. + Барањето за автентикација не можеше да биде процеÑуирано заради ÑиÑтемÑки проблем. + + + Invalid credentials. + Ðевалидни акредитиви. + + + Cookie has already been used by someone else. + Колачето е веќе кориÑтено од некој друг. + + + Not privileged to request the resource. + Ðемате привилегии за да го побарате реÑурÑот. + + + Invalid CSRF token. + Ðевалиден CSRF токен. + + + No authentication provider found to support the authentication token. + Ðе е пронајден провајдер за автентикација кој го поддржува токенот за автентикација. + + + No session available, it either timed out or cookies are not enabled. + СеÑијата е недоÑтапна, или е иÑтечена, или колачињата не Ñе овозможени. + + + No token could be found. + Токенот не е најден. + + + Username could not be found. + КориÑничкото име не е најдено. + + + Account has expired. + КориÑничката Ñметка е иÑтечена. + + + Credentials have expired. + Ðкредитивите Ñе иÑтечени. + + + Account is disabled. + КориÑничката Ñметка е деактивирана. + + + Account is locked. + КориÑничката Ñметка е заклучена. + + + Too many failed login attempts, please try again later. + Премногу неуÑпешни обиди за најавување, ве молиме обидете Ñе повторно подоцна. + + + Invalid or expired login link. + Ðеважечка или иÑтечена врÑка за најавување. + + + Too many failed login attempts, please try again in %minutes% minute. + Премногу неуÑпешни обиди за најавување, обидете Ñе повторно за %minutes% минута. + + + Too many failed login attempts, please try again in %minutes% minutes. + Претерано многу неуÑпешни обиди за најавување, ве молиме обидете Ñе повторно за %minutes% минута.|Претерано многу неуÑпешни обиди за најавување, ве молиме обидете Ñе повторно за %minutes% минути. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.mn.xlf b/vendor/symfony/security-core/Resources/translations/security.mn.xlf new file mode 100644 index 0000000..33a9ffd --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.mn.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + ÐÑвтрÑÑ… Ñ…Ò¯ÑÑлтийн алдаа гарав. + + + Authentication credentials could not be found. + ÐÑвтрÑÑ… Ñрхийн мÑдÑÑлÑл олдÑонгүй. + + + Authentication request could not be processed due to a system problem. + СиÑтемийн Ð°Ð»Ð´Ð°Ð°Ð½Ð°Ð°Ñ Ð±Ð¾Ð»Ð¾Ð½ нÑвтрÑÑ… Ñ…Ò¯ÑÑлтийг гүйцÑтгÑÑ… боломжгүй байна. + + + Invalid credentials. + Буруу нÑвтрÑÑ… Ñрхийн мÑдÑÑлÑл. + + + Cookie has already been used by someone else. + Күүки файлыг аль Ñ…Ñдийн Ó©Ó©Ñ€ хүн Ñ…ÑÑ€ÑглÑж байна. + + + Not privileged to request the resource. + ЭнÑÑ…Ò¯Ò¯ мÑдÑÑллийг авах Ñрх хүрÑхгүй байна. + + + Invalid CSRF token. + Тохиромжгүй CSRF токен. + + + No authentication provider found to support the authentication token. + ÐÑвтрÑÑ… токенг дÑмжих нÑвтрÑÑ… Ñрхийн хангагч олдÑонгүй. + + + No session available, it either timed out or cookies are not enabled. + Ð¥ÑÑ€ÑглÑгчийн session олдÑонгүй, хугацаа нь дууÑÑан ÑÑвÑл күүки идÑвхижүүлÑÑгүй байна. + + + No token could be found. + Токен олдÑонгүй. + + + Username could not be found. + ÐÑвтрÑÑ… нÑÑ€ олÑонгүй. + + + Account has expired. + БүртгÑлийн хугацаа дууÑÑан байна. + + + Credentials have expired. + ÐÑвтрÑÑ… Ñрхийн хугацаа дууÑÑан байна. + + + Account is disabled. + БүртгÑлийг хааÑан байна. + + + Account is locked. + БүртгÑлийг цоожилÑон байна. + + + Too many failed login attempts, please try again later. + Ð¥ÑÑ‚Ñрхий олон амжилтгүй оролдлого, түр хүлÑÑгÑÑд дахин оролдоно уу. + + + Invalid or expired login link. + Буруу ÑÑвÑл хугацаа нь дууÑÑан нÑвтрÑÑ… зам. + + + Too many failed login attempts, please try again in %minutes% minute. + ÐÑвтрÑÑ… оролдлого ихÑÑÑ€ амжилтгүй болÑон, %minutes% минутын дараа дахин оролдоно уу. + + + Too many failed login attempts, please try again in %minutes% minutes. + Ð¥ÑÑ‚ олон бүтÑлгүй нÑвтрÑÑ… оролдлого, %minutes% минутын дараа дахин оролдоно уу. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.my.xlf b/vendor/symfony/security-core/Resources/translations/security.my.xlf new file mode 100644 index 0000000..8550e74 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.my.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + အသုံးပြုá€á€½á€„့် á€á€¼á€½á€„်းá€á€»á€€á€ºá€á€…်á€á€¯á€–ြစ်သွားသည်ዠ+ + + Authentication credentials could not be found. + အသုံးပြုá€á€½á€„့် အထောက်အထားများ ရှာမá€á€½á€±á€·á€•ါዠ+ + + Authentication request could not be processed due to a system problem. + System ပြဿနာအá€á€€á€ºá€¡á€á€²á€›á€¾á€­ နေပါသဖြင့် အသုံးပြုá€á€½á€„့်á€á€±á€¬á€„်းဆိုá€á€»á€€á€ºá€€á€­á€¯ ဆောင်ရွက်áမရ နိုင်ပါዠ+ + + Invalid credentials. + သင့်လျှော်သော် အထောက်အထားမဟုá€á€ºá€•ါዠ+ + + Cookie has already been used by someone else. + Cookie ကို á€á€…်စုံá€á€…်ယောက်မှ အသုံးပြုပြီးဖြစ်သည်ዠ+ + + Not privileged to request the resource. + အရင်းအမြစ်ကိုá€á€±á€¬á€„်းဆိုရန်အá€á€½á€„့်ထူးမရပါዠ+ + + Invalid CSRF token. + သင့်လျှော်သော် CSRF token မဟုá€á€ºá€•ါዠ+ + + No authentication provider found to support the authentication token. + အထောက်အထားစိစစ်á€á€¼á€„်းသင်္ကေá€á€€á€­á€¯á€•ံ့ပိုးရန် မည်သည့်အထောက်အထားစိစစ်ရေး á€á€”်ဆောင်မှုမှမá€á€½á€±á€·á€•ါዠ+ + + No session available, it either timed out or cookies are not enabled. + Session မအားလပ်ပါዠSession အá€á€»á€­á€”်ကုန်သွားá€á€¼á€„်း (သို့မဟုá€á€º) cookies များကိုဖွင့်ထားá€á€¼á€„်းမရှိပါዠ+ + + No token could be found. + Toke ရှာမá€á€½á€±á€·á€•ါዠ+ + + Username could not be found. + အသုံးပြုသူအမည် ရှာဖွေá€á€½á€±á€·á€›á€¾á€­á€á€»á€„်းမရှိပါዠ+ + + Account has expired. + အကောင့် သက်á€á€™á€ºá€¸á€€á€¯á€”်လွန်သွားပါပြီዠ+ + + Credentials have expired. + အထောက်အထားသက်á€á€”်း ကုန်လွန်သွားပါပြီዠ+ + + Account is disabled. + အကောင့်ပိá€á€ºá€‘ားပါသည်ዠ+ + + Account is locked. + အကောင့် လောá€á€ºá€€á€»á€žá€½á€¬á€¸á€•ါပြီዠ+ + + Too many failed login attempts, please try again later. + Login á€á€„်ရန်ကြိုးစားမှုများလွန်းပါသည်አကျေးဇူးပြုá နောက်မှထပ်ကြိုးစားပါዠ+ + + Invalid or expired login link. + မသင့်လျှော်သော် (သို့မဟုá€á€º) သက်á€á€”်းကုန်သော login link ဖြစ်ပါသည်ዠ+ + + Too many failed login attempts, please try again in %minutes% minute. + Login á€á€„်ရန်ကြိုးစားမှုများလွန်းပါသည်አကျေးဇူးပြုá နောက် %minutes% မှထပ်မံကြိုးစားပါዠ+ + + Too many failed login attempts, please try again in %minutes% minutes. + á€á€„်ရောက်မှု မအောင်မြင်သော ကြိုးပမ်းမှုများအá€á€½á€€á€º á€á€…်á€á€«á€á€Šá€ºá€¸ ပြန်လုပ်မည်ዠထပ်မံကြိုးစားကြည့်ပါዠ%minutes% မိနစ်အá€á€½á€„်း|á€á€„်ရောက်မှု မအောင်မြင်သော ကြိုးပမ်းမှုများအá€á€½á€€á€º á€á€…်á€á€«á€á€Šá€ºá€¸ ပြန်လုပ်မည်ዠထပ်မံကြိုးစားကြည့်ပါዠ%minutes% မိနစ်အá€á€½á€„်း + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.nb.xlf b/vendor/symfony/security-core/Resources/translations/security.nb.xlf new file mode 100644 index 0000000..9ace014 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.nb.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + En autentiseringsfeil har skjedd. + + + Authentication credentials could not be found. + PÃ¥loggingsinformasjonen kunne ikke bli funnet. + + + Authentication request could not be processed due to a system problem. + Autentiserings forespørselen kunne ikke bli prosessert grunnet en system feil. + + + Invalid credentials. + Ugyldig pÃ¥loggingsinformasjon. + + + Cookie has already been used by someone else. + Cookie har allerede blitt brukt av noen andre. + + + Not privileged to request the resource. + Ingen tilgang til Ã¥ be om gitt ressurs. + + + Invalid CSRF token. + Ugyldig CSRF token. + + + No authentication provider found to support the authentication token. + Ingen autentiserings tilbyder funnet som støtter gitt autentiserings token. + + + No session available, it either timed out or cookies are not enabled. + Ingen sesjon tilgjengelig, sesjonen er enten utløpt eller cookies ikke skrudd pÃ¥. + + + No token could be found. + Ingen token kunne bli funnet. + + + Username could not be found. + Brukernavn kunne ikke bli funnet. + + + Account has expired. + Brukerkonto har utgÃ¥tt. + + + Credentials have expired. + PÃ¥loggingsinformasjon har utløpt. + + + Account is disabled. + Brukerkonto er deaktivert. + + + Account is locked. + Brukerkonto er sperret. + + + Too many failed login attempts, please try again later. + For mange mislykkede pÃ¥loggingsforsøk. Prøv igjen senere. + + + Invalid or expired login link. + Ugyldig eller utløpt pÃ¥loggingskobling. + + + Too many failed login attempts, please try again in %minutes% minute. + For mange mislykkede pÃ¥loggingsforsøk, prøv igjen om %minutes% minutt. + + + Too many failed login attempts, please try again in %minutes% minutes. + For mange mislykkede pÃ¥loggingsforsøk, prøv igjen om %minutes% minutter. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.nl.xlf b/vendor/symfony/security-core/Resources/translations/security.nl.xlf new file mode 100644 index 0000000..49b7aa7 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.nl.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Er heeft zich een authenticatieprobleem voorgedaan. + + + Authentication credentials could not be found. + Authenticatiegegevens konden niet worden gevonden. + + + Authentication request could not be processed due to a system problem. + Authenticatieaanvraag kon niet worden verwerkt door een technisch probleem. + + + Invalid credentials. + Ongeldige inloggegevens. + + + Cookie has already been used by someone else. + Cookie is al door een ander persoon gebruikt. + + + Not privileged to request the resource. + Onvoldoende rechten om de aanvraag te verwerken. + + + Invalid CSRF token. + CSRF-code is ongeldig. + + + No authentication provider found to support the authentication token. + Geen authenticatieprovider gevonden die de authenticatietoken ondersteunt. + + + No session available, it either timed out or cookies are not enabled. + Geen sessie beschikbaar, mogelijk is deze verlopen of cookies zijn uitgeschakeld. + + + No token could be found. + Er kon geen authenticatietoken worden gevonden. + + + Username could not be found. + Gebruikersnaam kon niet worden gevonden. + + + Account has expired. + Account is verlopen. + + + Credentials have expired. + Authenticatiegegevens zijn verlopen. + + + Account is disabled. + Account is gedeactiveerd. + + + Account is locked. + Account is geblokkeerd. + + + Too many failed login attempts, please try again later. + Te veel onjuiste inlogpogingen, probeer het later nogmaals. + + + Invalid or expired login link. + Ongeldige of verlopen inloglink. + + + Too many failed login attempts, please try again in %minutes% minute. + Te veel onjuiste inlogpogingen, probeer het opnieuw over %minutes% minuut. + + + Too many failed login attempts, please try again in %minutes% minutes. + Te veel onjuiste inlogpogingen, probeer het opnieuw over %minutes% minuten. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.nn.xlf b/vendor/symfony/security-core/Resources/translations/security.nn.xlf new file mode 100644 index 0000000..1a4c32b --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.nn.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Innlogginga har feila. + + + Authentication credentials could not be found. + Innloggingsinformasjonen vart ikkje funnen. + + + Authentication request could not be processed due to a system problem. + Innlogginga vart ikkje fullført pÃ¥ grunn av ein systemfeil. + + + Invalid credentials. + Ugyldig innloggingsinformasjon. + + + Cookie has already been used by someone else. + Informasjonskapselen er allereie brukt av ein annan brukar. + + + Not privileged to request the resource. + Du har ikkje Ã¥tgang til Ã¥ be om denne ressursen. + + + Invalid CSRF token. + Ugyldig CSRF-teikn. + + + No authentication provider found to support the authentication token. + Fann ingen innloggingstilbydar som støttar dette innloggingsteiknet. + + + No session available, it either timed out or cookies are not enabled. + Ingen sesjon tilgjengeleg. Sesjonen er anten ikkje lenger gyldig, eller informasjonskapslar er ikkje skrudd pÃ¥ i nettlesaren. + + + No token could be found. + Fann ingen innloggingsteikn. + + + Username could not be found. + Fann ikkje brukarnamnet. + + + Account has expired. + Brukarkontoen er utgjengen. + + + Credentials have expired. + Innloggingsinformasjonen er utgjengen. + + + Account is disabled. + Brukarkontoen er sperra. + + + Account is locked. + Brukarkontoen er sperra. + + + Too many failed login attempts, please try again later. + For mange innloggingsforsøk har feila, prøv igjen seinare. + + + Invalid or expired login link. + Innloggingslenka er ugyldig eller utgjengen. + + + Too many failed login attempts, please try again in %minutes% minute. + For mange mislykkede pÃ¥loggingsforsøk, prøv igjen om %minutes% minutt. + + + Too many failed login attempts, please try again in %minutes% minutes. + For mange mislukka innloggingsforsøk, prøv igjen om %minutes% minutt. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.no.xlf b/vendor/symfony/security-core/Resources/translations/security.no.xlf new file mode 100644 index 0000000..9ace014 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.no.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + En autentiseringsfeil har skjedd. + + + Authentication credentials could not be found. + PÃ¥loggingsinformasjonen kunne ikke bli funnet. + + + Authentication request could not be processed due to a system problem. + Autentiserings forespørselen kunne ikke bli prosessert grunnet en system feil. + + + Invalid credentials. + Ugyldig pÃ¥loggingsinformasjon. + + + Cookie has already been used by someone else. + Cookie har allerede blitt brukt av noen andre. + + + Not privileged to request the resource. + Ingen tilgang til Ã¥ be om gitt ressurs. + + + Invalid CSRF token. + Ugyldig CSRF token. + + + No authentication provider found to support the authentication token. + Ingen autentiserings tilbyder funnet som støtter gitt autentiserings token. + + + No session available, it either timed out or cookies are not enabled. + Ingen sesjon tilgjengelig, sesjonen er enten utløpt eller cookies ikke skrudd pÃ¥. + + + No token could be found. + Ingen token kunne bli funnet. + + + Username could not be found. + Brukernavn kunne ikke bli funnet. + + + Account has expired. + Brukerkonto har utgÃ¥tt. + + + Credentials have expired. + PÃ¥loggingsinformasjon har utløpt. + + + Account is disabled. + Brukerkonto er deaktivert. + + + Account is locked. + Brukerkonto er sperret. + + + Too many failed login attempts, please try again later. + For mange mislykkede pÃ¥loggingsforsøk. Prøv igjen senere. + + + Invalid or expired login link. + Ugyldig eller utløpt pÃ¥loggingskobling. + + + Too many failed login attempts, please try again in %minutes% minute. + For mange mislykkede pÃ¥loggingsforsøk, prøv igjen om %minutes% minutt. + + + Too many failed login attempts, please try again in %minutes% minutes. + For mange mislykkede pÃ¥loggingsforsøk, prøv igjen om %minutes% minutter. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.pl.xlf b/vendor/symfony/security-core/Resources/translations/security.pl.xlf new file mode 100644 index 0000000..0cfc58b --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.pl.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + WystÄ…piÅ‚ błąd uwierzytelniania. + + + Authentication credentials could not be found. + Dane uwierzytelniania nie zostaÅ‚y znalezione. + + + Authentication request could not be processed due to a system problem. + Żądanie uwierzytelniania nie mogÅ‚o zostać pomyÅ›lnie zakoÅ„czone z powodu problemu z systemem. + + + Invalid credentials. + NieprawidÅ‚owe dane. + + + Cookie has already been used by someone else. + To ciasteczko jest używane przez kogoÅ› innego. + + + Not privileged to request the resource. + Brak uprawnieÅ„ dla żądania wskazanego zasobu. + + + Invalid CSRF token. + NieprawidÅ‚owy token CSRF. + + + No authentication provider found to support the authentication token. + Nie znaleziono mechanizmu uwierzytelniania zdolnego do obsÅ‚ugi przesÅ‚anego tokenu. + + + No session available, it either timed out or cookies are not enabled. + Brak danych sesji, sesja wygasÅ‚a lub ciasteczka nie sÄ… włączone. + + + No token could be found. + Nie znaleziono tokenu. + + + Username could not be found. + Użytkownik o podanej nazwie nie istnieje. + + + Account has expired. + Konto wygasÅ‚o. + + + Credentials have expired. + Dane uwierzytelniania wygasÅ‚y. + + + Account is disabled. + Konto jest wyłączone. + + + Account is locked. + Konto jest zablokowane. + + + Too many failed login attempts, please try again later. + Zbyt dużo nieudanych prób logowania, proszÄ™ spróbować ponownie później. + + + Invalid or expired login link. + NieprawidÅ‚owy lub wygasÅ‚y link logowania. + + + Too many failed login attempts, please try again in %minutes% minute. + Zbyt wiele nieudanych prób logowania, spróbuj ponownie po upÅ‚ywie %minutes% minut. + + + Too many failed login attempts, please try again in %minutes% minutes. + Zbyt wiele nieudanych prób logowania, spróbuj ponownie za %minutes% minutÄ™.|Zbyt wiele nieudanych prób logowania, spróbuj ponownie za %minutes% minuty.|Zbyt wiele nieudanych prób logowania, spróbuj ponownie za %minutes% minut. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.pt.xlf b/vendor/symfony/security-core/Resources/translations/security.pt.xlf new file mode 100644 index 0000000..f9fda8d --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.pt.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ocorreu uma exceção durante a autenticação. + + + Authentication credentials could not be found. + As credenciais de autenticação não foram encontradas. + + + Authentication request could not be processed due to a system problem. + A autenticação não foi concluída devido a um problema no sistema. + + + Invalid credentials. + Credenciais inválidas. + + + Cookie has already been used by someone else. + Este cookie já está em uso. + + + Not privileged to request the resource. + Sem privilégios para solicitar este recurso. + + + Invalid CSRF token. + Token CSRF inválido. + + + No authentication provider found to support the authentication token. + Nenhum fornecedor de autenticação encontrado para suportar o token de autenticação. + + + No session available, it either timed out or cookies are not enabled. + Nenhuma sessão disponível, esta expirou ou os cookies estão desativados. + + + No token could be found. + O token não foi encontrado. + + + Username could not be found. + Nome de usuário não encontrado. + + + Account has expired. + A conta expirou. + + + Credentials have expired. + As credenciais expiraram. + + + Account is disabled. + Conta desativada. + + + Account is locked. + A conta está bloqueada. + + + Too many failed login attempts, please try again later. + Muitas tentativas de login sem sucesso, por favor, tente mais tarde. + + + Invalid or expired login link. + Ligação de login inválida ou expirada. + + + Too many failed login attempts, please try again in %minutes% minute. + Muitas tentativas de login sem sucesso, por favor, tente novamente novamente em 1 minuto. + + + Too many failed login attempts, please try again in %minutes% minutes. + Muitas tentativas de login sem sucesso, por favor, tente novamente em %minutes% minutos. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.pt_BR.xlf b/vendor/symfony/security-core/Resources/translations/security.pt_BR.xlf new file mode 100644 index 0000000..e3d7631 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.pt_BR.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Uma exceção ocorreu durante a autenticação. + + + Authentication credentials could not be found. + As credenciais de autenticação não foram encontradas. + + + Authentication request could not be processed due to a system problem. + A solicitação de autenticação não pôde ser processada devido a um problema no sistema. + + + Invalid credentials. + Credenciais inválidas. + + + Cookie has already been used by someone else. + Este cookie já foi usado por outra pessoa. + + + Not privileged to request the resource. + Sem privilégio para solicitar o recurso. + + + Invalid CSRF token. + Token CSRF inválido. + + + No authentication provider found to support the authentication token. + Nenhum provedor de autenticação encontrado para suportar o token de autenticação. + + + No session available, it either timed out or cookies are not enabled. + Nenhuma sessão disponível, ela expirou ou os cookies não estão habilitados. + + + No token could be found. + Nenhum token foi encontrado. + + + Username could not be found. + Nome de usuário não encontrado. + + + Account has expired. + A conta está expirada. + + + Credentials have expired. + As credenciais estão expiradas. + + + Account is disabled. + Conta desativada. + + + Account is locked. + A conta está travada. + + + Too many failed login attempts, please try again later. + Muitas tentativas de login malsucedidas, por favor, tente novamente mais tarde. + + + Invalid or expired login link. + Link de login inválido ou expirado. + + + Too many failed login attempts, please try again in %minutes% minute. + Muitas tentativas de login inválidas, por favor, tente novamente em um minuto. + + + Too many failed login attempts, please try again in %minutes% minutes. + Muitas tentativas de login sem sucesso, por favor, tente novamente em %minutes% minutos. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.ro.xlf b/vendor/symfony/security-core/Resources/translations/security.ro.xlf new file mode 100644 index 0000000..3316275 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.ro.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + A apărut o eroare de autentificare. + + + Authentication credentials could not be found. + InformaÈ›iile de autentificare nu au fost găsite. + + + Authentication request could not be processed due to a system problem. + Sistemul nu a putut procesa cererea de autentificare din cauza unei erori. + + + Invalid credentials. + Date de autentificare invalide. + + + Cookie has already been used by someone else. + Cookie este folosit deja de altcineva. + + + Not privileged to request the resource. + Permisiuni insuficiente pentru resursa cerută. + + + Invalid CSRF token. + Token CSRF este invalid. + + + No authentication provider found to support the authentication token. + Nu a fost găsit nici un agent de autentificare pentru tokenul specificat. + + + No session available, it either timed out or cookies are not enabled. + Sesiunea nu mai este disponibilă, a expirat sau suportul pentru cookies nu este activat. + + + No token could be found. + Tokenul nu a putut fi găsit. + + + Username could not be found. + Numele de utilizator nu a fost găsit. + + + Account has expired. + Contul a expirat. + + + Credentials have expired. + Datele de autentificare au expirat. + + + Account is disabled. + Contul este dezactivat. + + + Account is locked. + Contul este blocat. + + + Too many failed login attempts, please try again later. + Prea multe încercări de autentificare eÈ™uate, vă rugăm să încercaÈ›i mai târziu. + + + Invalid or expired login link. + Link de autentificare invalid sau expirat. + + + Too many failed login attempts, please try again in %minutes% minute. + Prea multe încercări nereuÈ™ite, încearcă din nou în %minutes% minut. + + + Too many failed login attempts, please try again in %minutes% minutes. + Prea multe încercări eÈ™uate de autentificare, vă rugăm să încercaÈ›i din nou peste %minutes% minut.|Prea multe încercări eÈ™uate de autentificare, vă rugăm să încercaÈ›i din nou peste %minutes% minute. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.ru.xlf b/vendor/symfony/security-core/Resources/translations/security.ru.xlf new file mode 100644 index 0000000..8705a24 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.ru.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ошибка аутентификации. + + + Authentication credentials could not be found. + Ðутентификационные данные не найдены. + + + Authentication request could not be processed due to a system problem. + Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ð¸ не может быть обработан в ÑвÑзи Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¾Ð¹ в ÑиÑтеме. + + + Invalid credentials. + ÐедейÑтвительные аутентификационные данные. + + + Cookie has already been used by someone else. + Cookie уже был иÑпользован кем-то другим. + + + Not privileged to request the resource. + ОтÑутÑтвуют права на Ð·Ð°Ð¿Ñ€Ð¾Ñ Ñтого реÑурÑа. + + + Invalid CSRF token. + ÐедейÑтвительный токен CSRF. + + + No authentication provider found to support the authentication token. + Ðе найден провайдер аутентификации, поддерживающий токен аутентификации. + + + No session available, it either timed out or cookies are not enabled. + СеÑÑÐ¸Ñ Ð½Ðµ найдена, ее Ð²Ñ€ÐµÐ¼Ñ Ð¸Ñтекло, либо cookies не включены. + + + No token could be found. + Токен не найден. + + + Username could not be found. + Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ найдено. + + + Account has expired. + Ð’Ñ€ÐµÐ¼Ñ Ð´ÐµÐ¹ÑÑ‚Ð²Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð¾Ð¹ запиÑи иÑтекло. + + + Credentials have expired. + Ð’Ñ€ÐµÐ¼Ñ Ð´ÐµÐ¹ÑÑ‚Ð²Ð¸Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ð¾Ð½Ð½Ñ‹Ñ… данных иÑтекло. + + + Account is disabled. + Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ отключена. + + + Account is locked. + Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ заблокирована. + + + Too many failed login attempts, please try again later. + Слишком много неудачных попыток входа, пожалуйÑта, попробуйте позже. + + + Invalid or expired login link. + СÑылка Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð° недейÑтвительна или проÑрочена. + + + Too many failed login attempts, please try again in %minutes% minute. + Слишком много неудачных попыток входа, повторите попытку через %minutes% минуту. + + + Too many failed login attempts, please try again in %minutes% minutes. + Слишком много неудачных попыток входа, повторите попытку через %minutes% минуту.|Слишком много неудачных попыток входа, повторите попытку через %minutes% минуты.|Слишком много неудачных попыток входа, повторите попытку через %minutes% минут. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.sk.xlf b/vendor/symfony/security-core/Resources/translations/security.sk.xlf new file mode 100644 index 0000000..b08757d --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.sk.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Pri overovaní doÅ¡lo k chybe. + + + Authentication credentials could not be found. + Overovacie údaje neboli nájdené. + + + Authentication request could not be processed due to a system problem. + Požiadavok na overenie nemohol byÅ¥ spracovaný kvôli systémovej chybe. + + + Invalid credentials. + Neplatné prihlasovacie údaje. + + + Cookie has already been used by someone else. + Cookie už bolo použité niekým iným. + + + Not privileged to request the resource. + Nemáte oprávnenie pristupovaÅ¥ k prostriedku. + + + Invalid CSRF token. + Neplatný CSRF token. + + + No authentication provider found to support the authentication token. + Poskytovateľ pre overovací token nebol nájdený. + + + No session available, it either timed out or cookies are not enabled. + Session nie je k dispozíci, vyprÅ¡ala jej platnosÅ¥, alebo sú zakázané cookies. + + + No token could be found. + Token nebol nájdený. + + + Username could not be found. + Prihlasovacie meno nebolo nájdené. + + + Account has expired. + PlatnosÅ¥ úÄtu skonÄila. + + + Credentials have expired. + PlatnosÅ¥ prihlasovacích údajov skonÄila. + + + Account is disabled. + ÚÄet je zakázaný. + + + Account is locked. + ÚÄet je zablokovaný. + + + Too many failed login attempts, please try again later. + PríliÅ¡ mnoho neúspeÅ¡ných pokusov o prihlásenie. Skúste to prosím znovu neskôr. + + + Invalid or expired login link. + Neplatný alebo expirovaný odkaz na prihlásenie. + + + Too many failed login attempts, please try again in %minutes% minute. + PríliÅ¡ veľa neúspeÅ¡ných pokusov o prihlásenie. Skúste to znova o %minutes% minútu. + + + Too many failed login attempts, please try again in %minutes% minutes. + PríliÅ¡ veľa neúspeÅ¡ných pokusov o prihlásenie, skúste to prosím znova o %minutes% minútu.|PríliÅ¡ veľa neúspeÅ¡ných pokusov o prihlásenie, skúste to prosím znova o %minutes% minúty.|PríliÅ¡ veľa neúspeÅ¡ných pokusov o prihlásenie, skúste to prosím znova o %minutes% minút. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.sl.xlf b/vendor/symfony/security-core/Resources/translations/security.sl.xlf new file mode 100644 index 0000000..7d05140 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.sl.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + PriÅ¡lo je do izjeme pri preverjanju avtentikacije. + + + Authentication credentials could not be found. + Poverilnic za avtentikacijo ni bilo mogoÄe najti. + + + Authentication request could not be processed due to a system problem. + Zahteve za avtentikacijo ni bilo mogoÄe izvesti zaradi sistemske težave. + + + Invalid credentials. + Neveljavne pravice. + + + Cookie has already been used by someone else. + PiÅ¡kotek je uporabil že nekdo drug. + + + Not privileged to request the resource. + Nimate privilegijev za zahtevani vir. + + + Invalid CSRF token. + Neveljaven CSRF žeton. + + + No authentication provider found to support the authentication token. + Ponudnika avtentikacije za podporo prijavnega žetona ni bilo mogoÄe najti. + + + No session available, it either timed out or cookies are not enabled. + Seja ni na voljo, ali je potekla ali pa piÅ¡kotki niso omogoÄeni. + + + No token could be found. + Žetona ni bilo mogoÄe najti. + + + Username could not be found. + UporabniÅ¡kega imena ni bilo mogoÄe najti. + + + Account has expired. + RaÄun je potekel. + + + Credentials have expired. + Poverilnice so potekle. + + + Account is disabled. + RaÄun je onemogoÄen. + + + Account is locked. + RaÄun je zaklenjen. + + + Too many failed login attempts, please try again later. + PreveÄ neuspelih poskusov prijave, poskusite znova pozneje. + + + Invalid or expired login link. + Neveljavna ali potekla povezava prijave. + + + Too many failed login attempts, please try again in %minutes% minute. + PreveÄ neuspelih poskusov prijave, poskusite znova Äez %minutes% minuto. + + + Too many failed login attempts, please try again in %minutes% minutes. + PreveÄ neuspeÅ¡nih poskusov prijave, poskusite znova Äez %minutes% minuto.|PreveÄ neuspeÅ¡nih poskusov prijave, poskusite znova Äez %minutes% minut. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.sq.xlf b/vendor/symfony/security-core/Resources/translations/security.sq.xlf new file mode 100644 index 0000000..44f129e --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.sq.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ndodhi një problem në autentikim. + + + Authentication credentials could not be found. + Kredencialet e autentikimit nuk mund të gjendeshin. + + + Authentication request could not be processed due to a system problem. + Kërkesa për autentikim nuk mund të përpunohej për shkak të një problemi në sistem. + + + Invalid credentials. + Kredenciale të pavlefshme. + + + Cookie has already been used by someone else. + Cookie është përdorur tashmë nga dikush tjetër. + + + Not privileged to request the resource. + Nuk është i privilegjuar të kërkojë burimin. + + + Invalid CSRF token. + Identifikues i pavlefshëm CSRF. + + + No authentication provider found to support the authentication token. + Asnjë ofrues i vërtetimit nuk u gjet që të mbështesë simbolin e vërtetimit. + + + No session available, it either timed out or cookies are not enabled. + Nuk ka asnjë sesion të vlefshëm, i ka skaduar koha ose cookies nuk janë aktivizuar. + + + No token could be found. + Asnjë simbol identifikimi nuk mund të gjendej. + + + Username could not be found. + Emri i përdoruesit nuk mund të gjendej. + + + Account has expired. + Llogaria ka skaduar. + + + Credentials have expired. + Kredencialet kanë skaduar. + + + Account is disabled. + Llogaria është çaktivizuar. + + + Account is locked. + Llogaria është e kyçur. + + + Too many failed login attempts, please try again later. + Shumë përpjekje të dështuara autentikimi, provo përsëri më vonë. + + + Invalid or expired login link. + Link hyrje i pavlefshëm ose i skaduar. + + + Too many failed login attempts, please try again in %minutes% minute. + Shumë përpjekje të dështuara për identifikim; provo sërish pas %minutes% minutë. + + + Too many failed login attempts, please try again in %minutes% minutes. + Shumë përpjekje të pasuksesshme për t'u identifikuar, ju lutemi provoni përsëri pas %minutes% minutash. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.sr_Cyrl.xlf b/vendor/symfony/security-core/Resources/translations/security.sr_Cyrl.xlf new file mode 100644 index 0000000..2192fe6 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.sr_Cyrl.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Изузетак при аутентификацији. + + + Authentication credentials could not be found. + Ðутентификациони подаци ниÑу пронађени. + + + Authentication request could not be processed due to a system problem. + Захтев за аутентификацију не може бити обрађен због ÑиÑтемÑких проблема. + + + Invalid credentials. + Ðевалидни подаци за аутентификацију. + + + Cookie has already been used by someone else. + Колачић је већ иÑкоришћен од Ñтране неког другог. + + + Not privileged to request the resource. + Ðемате права приÑтупа овом реÑурÑу. + + + Invalid CSRF token. + Ðевалидан CSRF токен. + + + No authentication provider found to support the authentication token. + Ðутентификациони провајдер за подршку токена није пронађен. + + + No session available, it either timed out or cookies are not enabled. + СеÑија није доÑтупна, иÑтекла је или Ñу колачићи иÑкључени. + + + No token could be found. + Токен не може бити пронађен. + + + Username could not be found. + КориÑничко име не може бити пронађено. + + + Account has expired. + Ðалог је иÑтекао. + + + Credentials have expired. + Подаци за аутентификацију Ñу иÑтекли. + + + Account is disabled. + Ðалог је онемогућен. + + + Account is locked. + Ðалог је закључан. + + + Too many failed login attempts, please try again later. + Превише неуÑпешних покушаја пријављивања, молим покушајте поново каÑније. + + + Invalid or expired login link. + Линк за пријављивање је иÑтекао или је неиÑправан. + + + Too many failed login attempts, please try again in %minutes% minute. + Превише неуÑпешних покушаја пријављивања, молим покушајте поново за %minutes% минут. + + + Too many failed login attempts, please try again in %minutes% minutes. + Превише неуÑпешних покушаја пријављивања, покушајте поново за %minutes% минут.|Превише неуÑпешних покушаја пријављивања, покушајте поново за %minutes% минута. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.sr_Latn.xlf b/vendor/symfony/security-core/Resources/translations/security.sr_Latn.xlf new file mode 100644 index 0000000..6a925c5 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.sr_Latn.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Izuzetak pri autentifikaciji. + + + Authentication credentials could not be found. + Autentifikacioni podaci nisu pronaÄ‘eni. + + + Authentication request could not be processed due to a system problem. + Zahtev za autentifikaciju ne može biti obraÄ‘en zbog sistemskih problema. + + + Invalid credentials. + Nevalidni podaci za autentifikaciju. + + + Cookie has already been used by someone else. + KolaÄić je već iskorišćen od strane nekog drugog. + + + Not privileged to request the resource. + Nemate prava pristupa ovom resursu. + + + Invalid CSRF token. + Nevalidan CSRF token. + + + No authentication provider found to support the authentication token. + Autentifikacioni provajder za podrÅ¡ku tokena nije pronaÄ‘en. + + + No session available, it either timed out or cookies are not enabled. + Sesija nije dostupna, istekla je ili su kolaÄići iskljuÄeni. + + + No token could be found. + Token ne može biti pronaÄ‘en. + + + Username could not be found. + KorisniÄko ime ne može biti pronaÄ‘eno. + + + Account has expired. + Nalog je istekao. + + + Credentials have expired. + Podaci za autentifikaciju su istekli. + + + Account is disabled. + Nalog je onemogućen. + + + Account is locked. + Nalog je zakljuÄan. + + + Too many failed login attempts, please try again later. + PreviÅ¡e neuspeÅ¡nih pokuÅ¡aja prijavljivanja, molim pokuÅ¡ajte ponovo kasnije. + + + Invalid or expired login link. + Link za prijavljivanje je istekao ili je neispravan. + + + Too many failed login attempts, please try again in %minutes% minute. + PreviÅ¡e neuspeÅ¡nih pokuÅ¡aja prijavljivanja, molim pokuÅ¡ajte ponovo za %minutes% minut. + + + Too many failed login attempts, please try again in %minutes% minutes. + PreviÅ¡e neuspeÅ¡nih pokuÅ¡aja prijavljivanja, pokuÅ¡ajte ponovo za %minutes% minut.|PreviÅ¡e neuspeÅ¡nih pokuÅ¡aja prijavljivanja, pokuÅ¡ajte ponovo za %minutes% minuta. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.sv.xlf b/vendor/symfony/security-core/Resources/translations/security.sv.xlf new file mode 100644 index 0000000..dffe36d --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.sv.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Ett autentiseringsfel har inträffat. + + + Authentication credentials could not be found. + Uppgifterna för autentisering kunde inte hittas. + + + Authentication request could not be processed due to a system problem. + Autentiseringen kunde inte genomföras pÃ¥ grund av systemfel. + + + Invalid credentials. + Felaktiga uppgifter. + + + Cookie has already been used by someone else. + Cookien har redan använts av nÃ¥gon annan. + + + Not privileged to request the resource. + Saknar rättigheter för resursen. + + + Invalid CSRF token. + Ogiltig CSRF-token. + + + No authentication provider found to support the authentication token. + Ingen leverantör för autentisering hittades för angiven autentiseringstoken. + + + No session available, it either timed out or cookies are not enabled. + Ingen session finns tillgänglig, antingen har den förfallit eller är cookies inte aktiverat. + + + No token could be found. + Ingen token kunde hittas. + + + Username could not be found. + Användarnamnet kunde inte hittas. + + + Account has expired. + Kontot har förfallit. + + + Credentials have expired. + Uppgifterna har förfallit. + + + Account is disabled. + Kontot är inaktiverat. + + + Account is locked. + Kontot är lÃ¥st. + + + Too many failed login attempts, please try again later. + För mÃ¥nga misslyckade inloggningsförsök, försök igen senare. + + + Invalid or expired login link. + Ogiltig eller utgÃ¥ngen inloggningslänk. + + + Too many failed login attempts, please try again in %minutes% minute. + För mÃ¥nga misslyckade inloggningsförsök, försök igen om %minutes% minut. + + + Too many failed login attempts, please try again in %minutes% minutes. + För mÃ¥nga misslyckade inloggningsförsök, vänligen försök igen om %minutes% minuter. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.th.xlf b/vendor/symfony/security-core/Resources/translations/security.th.xlf new file mode 100644 index 0000000..0209b4c --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.th.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + พบความผิดพลาดในà¸à¸²à¸£à¸£à¸±à¸šà¸£à¸­à¸‡à¸•ัวตน + + + Authentication credentials could not be found. + ไม่พบข้อมูลในà¸à¸²à¸£à¸£à¸±à¸šà¸£à¸­à¸‡à¸•ัวตน (credentials) + + + Authentication request could not be processed due to a system problem. + คำร้องในà¸à¸²à¸£à¸£à¸±à¸šà¸£à¸­à¸‡à¸•ัวตนไม่สามารถดำเนินà¸à¸²à¸£à¹„ด้ เนื่องมาจาà¸à¸›à¸±à¸à¸«à¸²à¸‚องระบบ + + + Invalid credentials. + ข้อมูลà¸à¸²à¸£à¸£à¸±à¸šà¸£à¸­à¸‡à¸•ัวตนไม่ถูà¸à¸•้อง + + + Cookie has already been used by someone else. + Cookie ถูà¸à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¹„ปà¹à¸¥à¹‰à¸§à¸”้วยผู้อื่น + + + Not privileged to request the resource. + ไม่ได้รับสิทธิ์ให้ใช้งานส่วนนี้ได้ + + + Invalid CSRF token. + CSRF token ไม่ถูà¸à¸•้อง + + + No authentication provider found to support the authentication token. + ไม่พบ authentication provider ที่รองรับสำหรับ authentication token + + + No session available, it either timed out or cookies are not enabled. + ไม่มี session ที่พร้อมใช้งาน, Session หมดอายุไปà¹à¸¥à¹‰à¸§à¸«à¸£à¸·à¸­ cookies ไม่ถูà¸à¹€à¸›à¸´à¸”ใช้งาน + + + No token could be found. + ไม่พบ token + + + Username could not be found. + ไม่พบ Username + + + Account has expired. + บัà¸à¸Šà¸µà¸«à¸¡à¸”อายุไปà¹à¸¥à¹‰à¸§ + + + Credentials have expired. + ข้อมูลà¸à¸²à¸£à¸£à¸°à¸šà¸¸à¸•ัวตนหมดอายุà¹à¸¥à¹‰à¸§ + + + Account is disabled. + บัà¸à¸Šà¸µà¸–ูà¸à¸£à¸°à¸‡à¸±à¸šà¹à¸¥à¹‰à¸§ + + + Account is locked. + บัà¸à¸Šà¸µà¸–ูà¸à¸¥à¹‡à¸­à¸à¹à¸¥à¹‰à¸§ + + + Too many failed login attempts, please try again later. + มีความพยายามเข้าสู่ระบบล้มเหลวมาà¸à¹€à¸à¸´à¸™à¹„ป à¸à¸£à¸¸à¸“าลองใหม่ภายหลัง + + + Invalid or expired login link. + ลิงค์เข้าสู่ระบบไม่ถูà¸à¸•้องหรือหมดอายุไปà¹à¸¥à¹‰à¸§ + + + Too many failed login attempts, please try again in %minutes% minute. + มีความพยายามเข้าสู่ระบบล้มเหลวมาà¸à¹€à¸à¸´à¸™à¹„ป โปรดลองอีà¸à¸„รั้งใน %minutes% นาที + + + Too many failed login attempts, please try again in %minutes% minutes. + มีความพยายามในà¸à¸²à¸£à¹€à¸‚้าสู่ระบบล้มเหลวมาà¸à¹€à¸à¸´à¸™à¹„ป โปรดลองอีà¸à¸„รั้งใน %minutes% นาที.|มีความพยายามในà¸à¸²à¸£à¹€à¸‚้าสู่ระบบล้มเหลวมาà¸à¹€à¸à¸´à¸™à¹„ป โปรดลองอีà¸à¸„รั้งใน %minutes% นาที. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.tl.xlf b/vendor/symfony/security-core/Resources/translations/security.tl.xlf new file mode 100644 index 0000000..c02222d --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.tl.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Nagkaroon ng isang pagbubukod sa pagpapatotoo. + + + Authentication credentials could not be found. + Hindi matagpuan ang mga kredensyal ng pagpapatotoo. + + + Authentication request could not be processed due to a system problem. + Ang kahilingan sa pagpapatotoo ay hindi naproseso dahil sa isang problema sa system. + + + Invalid credentials. + Di-wastong mga kredensyal. + + + Cookie has already been used by someone else. + Ang Cookie ay ginamit na ng ibang tao. + + + Not privileged to request the resource. + Walang pribilehiyo upang humingi ng mga bagong mapagkukunan. + + + Invalid CSRF token. + Di-wastong token ng CSRF. + + + No authentication provider found to support the authentication token. + Walang nahanap na provider ng pagpapatotoo upang suportahan ang token ng pagpapatotoo. + + + No session available, it either timed out or cookies are not enabled. + Walang magagamit na session, alinman sa nag-time out o ang cookies ay hindi pinagana. + + + No token could be found. + Walang makitang token. + + + Username could not be found. + Hindi makita ang username. + + + Account has expired. + Nag-expire na ang account. + + + Credentials have expired. + Nag-expire na ang mga kredensyal. + + + Account is disabled. + Ang account ay hindi pinagana. + + + Account is locked. + Ang account ay naka-lock. + + + Too many failed login attempts, please try again later. + Napakaraming nabigong mga pagtatangka sa pag-login, mangyaring subukang muli sa ibang pagkakataon. + + + Invalid or expired login link. + Inbalido o nagexpire na ang link para makapaglogin. + + + Too many failed login attempts, please try again in %minutes% minute. + Napakaraming nabigong mga pagtatangka sa pag-login, pakisubukan ulit sa% minuto% minuto. + + + Too many failed login attempts, please try again in %minutes% minutes. + Napakaraming nabigong pagtatangka ng pag-login, mangyaring subukang muli sa loob ng %minutes% minuto.|Napakaraming nabigong pagtatangka ng pag-login, mangyaring subukang muli sa loob ng %minutes% minuto. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.tr.xlf b/vendor/symfony/security-core/Resources/translations/security.tr.xlf new file mode 100644 index 0000000..4cfc1cb --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.tr.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Bir yetkilendirme istisnası oluÅŸtu. + + + Authentication credentials could not be found. + Kimlik bilgileri bulunamadı. + + + Authentication request could not be processed due to a system problem. + Bir sistem hatası nedeniyle yetkilendirme isteÄŸi iÅŸleme alınamıyor. + + + Invalid credentials. + Geçersiz kimlik bilgileri. + + + Cookie has already been used by someone else. + Çerez bir baÅŸkası tarafından zaten kullanılmıştı. + + + Not privileged to request the resource. + Kaynak talebi için imtiyaz bulunamadı. + + + Invalid CSRF token. + Geçersiz CSRF fiÅŸi. + + + No authentication provider found to support the authentication token. + Yetkilendirme fiÅŸini destekleyecek yetkilendirme saÄŸlayıcısı bulunamadı. + + + No session available, it either timed out or cookies are not enabled. + Oturum bulunamadı, zaman aşımına uÄŸradı veya çerezler etkin deÄŸil. + + + No token could be found. + FiÅŸ bulunamadı. + + + Username could not be found. + Kullanıcı adı bulunamadı. + + + Account has expired. + Hesap zaman aşımına uÄŸradı. + + + Credentials have expired. + Kimlik bilgileri zaman aşımına uÄŸradı. + + + Account is disabled. + Hesap engellenmiÅŸ. + + + Account is locked. + Hesap kilitlenmiÅŸ. + + + Too many failed login attempts, please try again later. + Çok fazla baÅŸarısız giriÅŸ denemesi, lütfen daha sonra tekrar deneyin. + + + Invalid or expired login link. + Geçersiz veya süresi dolmuÅŸ oturum açma baÄŸlantısı. + + + Too many failed login attempts, please try again in %minutes% minute. + Çok fazla baÅŸarısız giriÅŸ denemesi, lütfen %minutes% dakika sonra tekrar deneyin. + + + Too many failed login attempts, please try again in %minutes% minutes. + Çok fazla baÅŸarısız giriÅŸ denemesi, lütfen %minutes% dakika sonra tekrar deneyin.|Çok fazla baÅŸarısız giriÅŸ denemesi, lütfen %minutes% dakika sonra tekrar deneyin. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.uk.xlf b/vendor/symfony/security-core/Resources/translations/security.uk.xlf new file mode 100644 index 0000000..6b27de7 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.uk.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Помилка автентифікації. + + + Authentication credentials could not be found. + Ðвтентифікаційні дані не знайдено. + + + Authentication request could not be processed due to a system problem. + Запит на автентифікацію не може бути опрацьовано у зв’Ñзку з проблемою в ÑиÑтемі. + + + Invalid credentials. + Ðевірні автентифікаційні дані. + + + Cookie has already been used by someone else. + ХтоÑÑŒ інший вже викориÑтав цей Ñookie. + + + Not privileged to request the resource. + ВідÑутні права на запит цього реÑурÑу. + + + Invalid CSRF token. + Ðевірний токен CSRF. + + + No authentication provider found to support the authentication token. + Ðе знайдено провайдера автентифікації, що підтримує токен автентифікаціії. + + + No session available, it either timed out or cookies are not enabled. + СеÑÑ–Ñ Ð½ÐµÐ´Ð¾Ñтупна, Ñ—Ñ— Ñ‡Ð°Ñ Ð²Ð¸Ð¹ÑˆÐ¾Ð², або cookies вимкнено. + + + No token could be found. + Токен не знайдено. + + + Username could not be found. + Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не знайдено. + + + Account has expired. + Термін дії облікового запиÑу вичерпано. + + + Credentials have expired. + Термін дії автентифікаційних даних вичерпано. + + + Account is disabled. + Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¾. + + + Account is locked. + Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾. + + + Too many failed login attempts, please try again later. + Забагато невдалих Ñпроб входу. Будь лаÑка, Ñпробуйте пізніше. + + + Invalid or expired login link. + ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ недійÑне, або термін його дії закінчивÑÑ. + + + Too many failed login attempts, please try again in %minutes% minute. + Забагато невдалих Ñпроб входу. Будь лаÑка, Ñпробуйте знову через %minutes% хвилину. + + + Too many failed login attempts, please try again in %minutes% minutes. + Забагато невдалих Ñпроб входу, будь лаÑка, Ñпробуйте ще раз через %minutes% хвилину.|Забагато невдалих Ñпроб входу, будь лаÑка, Ñпробуйте ще раз через %minutes% хвилини.|Забагато невдалих Ñпроб входу, будь лаÑка, Ñпробуйте ще раз через %minutes% хвилин. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.ur.xlf b/vendor/symfony/security-core/Resources/translations/security.ur.xlf new file mode 100644 index 0000000..5c705cd --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.ur.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + ایک تصدیقي خرابی پیش آگئی Û“ + + + Authentication credentials could not be found. + درج کردھ ریکارڈ Ù†Ûیں مل سکا + + + Authentication request could not be processed due to a system problem. + سسٹم Ú©ÛŒ خرابی Ú©ÛŒ ÙˆØ¬Û Ø³Û’ تصدیق Ú©ÛŒ درخواست پر کارروائی Ù†Ûیں ÛÙˆ سکی + + + Invalid credentials. + غلط ڈیٹا + + + Cookie has already been used by someone else. + Ú©ÙˆÚ©ÛŒ Ù¾ÛÙ„Û’ ÛÛŒ کسی اور Ú©Û’ Ø°Ø±ÛŒØ¹Û Ø§Ø³ØªØ¹Ù…Ø§Ù„ ÛÙˆ Ú†Ú©ÛŒ ÛÛ’ + + + Not privileged to request the resource. + وسائل Ú©ÛŒ درخواست کرنے کا اختیار Ù†Ûیں ÛÛ’ + + + Invalid CSRF token. + ٹوکن غلط ÛÛ’ CSRF + + + No authentication provider found to support the authentication token. + تصدیقی ٹوکن Ú©Ùˆ سپورٹ کرنے Ú©Û’ لیے کوئی تصدیقی Ú©Ù†Ù†Ø¯Û Ù†Ûیں ملا + + + No session available, it either timed out or cookies are not enabled. + کوئی سیشن دستیاب Ù†Ûیں ÛÛ’ØŒ یا تو اس کا وقت ختم ÛÙˆ گیا ÛÛ’ یا کوکیز ÙØ¹Ø§Ù„ Ù†Ûیں Ûیں + + + No token could be found. + کوئی ٹوکن Ù†Ûیں مل سکا + + + Username could not be found. + يوذر Ù†Ûیں مل سکا + + + Account has expired. + اکاؤنٹ Ú©ÛŒ میعاد ختم ÛÙˆ گئی ÛÛ’ + + + Credentials have expired. + اسناد Ú©ÛŒ میعاد ختم ÛÙˆ Ú†Ú©ÛŒ ÛÛ’ + + + Account is disabled. + اکاؤنٹ بند کر دیا گیا ÛÛ’ + + + Account is locked. + اکاؤنٹ لاک ÛÛ’ + + + Too many failed login attempts, please try again later. + لاگ ان Ú©ÛŒ Ø¨ÛØª Ø²ÛŒØ§Ø¯Û Ù†Ø§Ú©Ø§Ù… کوششیں ÛÙˆ Ú†Ú©ÛŒ Ûیں، براۓ کرم بعد میں Ø¯ÙˆØ¨Ø§Ø±Û Ú©ÙˆØ´Ø´ کریں + + + Invalid or expired login link. + غلط یا ختم شدھ لاگ ان لنک + + + Too many failed login attempts, please try again in %minutes% minute. + منٹ باد %minutes% لاگ ان Ú©ÛŒ Ø¨ÛØª Ø²ÛŒØ§Ø¯Û Ù†Ø§Ú©Ø§Ù… کوششیں ÛÙˆ Ú†Ú©ÛŒ Ûیں، براۓ کرم دوبارھ کوشيش کريں + + + Too many failed login attempts, please try again in %minutes% minutes. + Ø¨ÛØª Ø²ÛŒØ§Ø¯Û Ù†Ø§Ú©Ø§Ù… لاگ ان کوششیں، Ø¨Ø±Ø§Û Ú©Ø±Ù… %minutes% منٹ میں Ø¯ÙˆØ¨Ø§Ø±Û Ú©ÙˆØ´Ø´ کریں۔ + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.uz.xlf b/vendor/symfony/security-core/Resources/translations/security.uz.xlf new file mode 100644 index 0000000..ec690c5 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.uz.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Autentifikatsiyada xatolik. + + + Authentication credentials could not be found. + Autentifikatsiya ma'lumotlari topilmadi. + + + Authentication request could not be processed due to a system problem. + Tizimdagi muammo tufayli autentifikatsiya so'rovi bajarilmadi. + + + Invalid credentials. + Noto'g'ri ma'lumot. + + + Cookie has already been used by someone else. + Cookie faylini allaqachon kimdir ishlatgan. + + + Not privileged to request the resource. + Sizda ushbu manbani talab qilishga ruxsat yo'q.. + + + Invalid CSRF token. + Noto'g'ri CSRF belgisi. + + + No authentication provider found to support the authentication token. + Haqiqiylikni tasdiqlovchi belgini qo'llab-quvvatlovchi biron bir autentifikatsiya provayderi topilmadi. + + + No session available, it either timed out or cookies are not enabled. + Sessiya topilmadi, muddati tugamadi yoki cookie-fayllar yoqilmagan. + + + No token could be found. + To'ken topilmadi. + + + Username could not be found. + Foydalanuvchi nomi topilmadi. + + + Account has expired. + Akkunt muddati tugagan. + + + Credentials have expired. + Autentifikatsiya ma'lumotlari muddati tugagan. + + + Account is disabled. + Akkunt o'chirilgan. + + + Account is locked. + Akkunt bloklangan. + + + Too many failed login attempts, please try again later. + Kirish urinishlari muvaffaqiyatsiz tugadi, keyinroq qayta urinib ko'ring. + + + Invalid or expired login link. + Kirish havolasi yaroqsiz yoki muddati tugagan. + + + Too many failed login attempts, please try again in %minutes% minute. + Kirish uchun muvaffaqiyatsiz urinishlar, %minutes% daqiqadan so'ng qayta urinib ko'ring. + + + Too many failed login attempts, please try again in %minutes% minutes. + KoÊ»plab muvaffaqiyatsiz kirish urinishlari, iltimos, %minutes% daqiqadan so'ng qayta urinib koÊ»ring.|KoÊ»plab muvaffaqiyatsiz kirish urinishlari, iltimos, %minutes% daqiqadan so'ng qayta urinib koÊ»ring. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.vi.xlf b/vendor/symfony/security-core/Resources/translations/security.vi.xlf new file mode 100644 index 0000000..fc4595c --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.vi.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + Có lá»—i trong quá trình xác thá»±c. + + + Authentication credentials could not be found. + Thông tin dùng để xác thá»±c không tìm thấy. + + + Authentication request could not be processed due to a system problem. + Yêu cầu xác thá»±c không thể thá»±c hiện do lá»—i cá»§a hệ thống. + + + Invalid credentials. + Thông tin dùng để xác thá»±c không hợp lệ. + + + Cookie has already been used by someone else. + Cookie đã được dùng bởi ngưá»i dùng khác. + + + Not privileged to request the resource. + Không được phép yêu cầu tài nguyên. + + + Invalid CSRF token. + Mã CSRF không hợp lệ. + + + No authentication provider found to support the authentication token. + Không tìm thấy nhà cung cấp dịch vụ xác thá»±c nào cho mã xác thá»±c mà bạn sá»­ dụng. + + + No session available, it either timed out or cookies are not enabled. + Không tìm thấy phiên làm việc. Phiên làm việc hoặc cookie có thể bị tắt. + + + No token could be found. + Không tìm thấy mã token. + + + Username could not be found. + Không tìm thấy tên ngưá»i dùng. + + + Account has expired. + Tài khoản đã hết hạn. + + + Credentials have expired. + Thông tin xác thá»±c đã hết hạn. + + + Account is disabled. + Tài khoản bị tạm ngừng. + + + Account is locked. + Tài khoản bị khóa. + + + Too many failed login attempts, please try again later. + Äăng nhập sai quaÌ nhiều lần, vui loÌ€ng thử laÌ£i lần nữa. + + + Invalid or expired login link. + Liên kêÌt đăng nhập không hợp lệ hoặc quaÌ haÌ£n. + + + Too many failed login attempts, please try again in %minutes% minute. + Quá nhiá»u lần thá»­ đăng nhập không thành công, vui lòng thá»­ lại sau %minutes% phút. + + + Too many failed login attempts, please try again in %minutes% minutes. + Quá nhiá»u lần đăng nhập không thành công, vui lòng thá»­ lại sau %minutes% phút.|Quá nhiá»u lần đăng nhập không thành công, vui lòng thá»­ lại sau %minutes% phút. + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.zh_CN.xlf b/vendor/symfony/security-core/Resources/translations/security.zh_CN.xlf new file mode 100644 index 0000000..9954d86 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.zh_CN.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + 身份验è¯å‘生异常。 + + + Authentication credentials could not be found. + 没有找到身份验è¯çš„凭è¯ã€‚ + + + Authentication request could not be processed due to a system problem. + 由于系统故障,身份验è¯çš„请求无法被处ç†ã€‚ + + + Invalid credentials. + 无效的凭è¯ã€‚ + + + Cookie has already been used by someone else. + Cookie å·²ç»è¢«å…¶ä»–人使用。 + + + Not privileged to request the resource. + 没有æƒé™è¯·æ±‚此资æºã€‚ + + + Invalid CSRF token. + 无效的 CSRF token 。 + + + No authentication provider found to support the authentication token. + æ²¡æœ‰æ‰¾åˆ°æ”¯æŒæ­¤ token çš„èº«ä»½éªŒè¯æœåŠ¡æä¾›æ–¹ã€‚ + + + No session available, it either timed out or cookies are not enabled. + Session ä¸å¯ç”¨ã€‚会è¯è¶…时或没有å¯ç”¨ cookies 。 + + + No token could be found. + 找ä¸åˆ° token 。 + + + Username could not be found. + 找ä¸åˆ°ç”¨æˆ·å。 + + + Account has expired. + å¸å·å·²è¿‡æœŸã€‚ + + + Credentials have expired. + 凭è¯å·²è¿‡æœŸã€‚ + + + Account is disabled. + å¸å·å·²è¢«ç¦ç”¨ã€‚ + + + Account is locked. + å¸å·å·²è¢«é”定。 + + + Too many failed login attempts, please try again later. + 登入失败的次数过多,请ç¨åŽå†è¯•。 + + + Invalid or expired login link. + 失效或过期的登入链接。 + + + Too many failed login attempts, please try again in %minutes% minute. + 登入失败的次数过多,请在%minutes%分钟åŽå†è¯•。 + + + Too many failed login attempts, please try again in %minutes% minutes. + 登录å°è¯•失败次数过多,请在 %minutes% 分钟åŽå†è¯•。|登录å°è¯•失败次数过多,请在 %minutes% 分钟åŽå†è¯•。 + + + + diff --git a/vendor/symfony/security-core/Resources/translations/security.zh_TW.xlf b/vendor/symfony/security-core/Resources/translations/security.zh_TW.xlf new file mode 100644 index 0000000..097ce99 --- /dev/null +++ b/vendor/symfony/security-core/Resources/translations/security.zh_TW.xlf @@ -0,0 +1,83 @@ + + + + + + An authentication exception occurred. + 身份驗證發生異常。 + + + Authentication credentials could not be found. + 沒有找到身份驗證的憑證。 + + + Authentication request could not be processed due to a system problem. + 由於系統故障,身份驗證的請求無法被處ç†ã€‚ + + + Invalid credentials. + 無效的憑證。 + + + Cookie has already been used by someone else. + Cookie 已經被其他人使用。 + + + Not privileged to request the resource. + 沒有權é™è«‹æ±‚此資æºã€‚ + + + Invalid CSRF token. + 無效的 CSRF token 。 + + + No authentication provider found to support the authentication token. + æ²’æœ‰æ‰¾åˆ°æ”¯æŒæ­¤ token 的身份驗證æœå‹™æä¾›æ–¹ã€‚ + + + No session available, it either timed out or cookies are not enabled. + Session ä¸å¯ç”¨ã€‚回話超時或沒有啓用 cookies 。 + + + No token could be found. + 找ä¸åˆ° token 。 + + + Username could not be found. + 找ä¸åˆ°ç”¨æˆ¶å。 + + + Account has expired. + 賬號已逾期。 + + + Credentials have expired. + 憑證已逾期。 + + + Account is disabled. + 賬號已被ç¦ç”¨ã€‚ + + + Account is locked. + 賬號已被鎖定。 + + + Too many failed login attempts, please try again later. + 登入失敗的次數éŽå¤šï¼Œè«‹ç¨å¾Œå†è©¦ã€‚ + + + Invalid or expired login link. + å¤±æ•ˆæˆ–éŽæœŸçš„ç™»å…¥éˆæŽ¥ã€‚ + + + Too many failed login attempts, please try again in %minutes% minute. + 登錄失敗的次數éŽå¤šï¼Œè«‹åœ¨%minutes%分é˜å¾Œå†è©¦ã€‚ + + + Too many failed login attempts, please try again in %minutes% minutes. + 嘗試登入失敗次數éŽå¤šï¼Œè«‹ %minutes% 分é˜å¾Œå†è©¦ã€‚|嘗試登入失敗次數éŽå¤šï¼Œè«‹ %minutes% 分é˜å¾Œå†è©¦ã€‚ + + + + diff --git a/vendor/symfony/security-core/Role/Role.php b/vendor/symfony/security-core/Role/Role.php new file mode 100644 index 0000000..374eb59 --- /dev/null +++ b/vendor/symfony/security-core/Role/Role.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Role; + +/** + * Allows migrating session payloads from v4. + * + * @internal + */ +class Role +{ + private $role; + + private function __construct() + { + } + + public function __toString(): string + { + return $this->role; + } +} diff --git a/vendor/symfony/security-core/Role/RoleHierarchy.php b/vendor/symfony/security-core/Role/RoleHierarchy.php new file mode 100644 index 0000000..15c5750 --- /dev/null +++ b/vendor/symfony/security-core/Role/RoleHierarchy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Role; + +/** + * RoleHierarchy defines a role hierarchy. + * + * @author Fabien Potencier + */ +class RoleHierarchy implements RoleHierarchyInterface +{ + /** @var array> */ + protected array $map; + + private array $hierarchy; + + /** + * @param array> $hierarchy + */ + public function __construct(array $hierarchy) + { + $this->hierarchy = $hierarchy; + + $this->buildRoleMap(); + } + + public function getReachableRoleNames(array $roles): array + { + $reachableRoles = $roles; + + foreach ($roles as $role) { + if (!isset($this->map[$role])) { + continue; + } + + foreach ($this->map[$role] as $r) { + $reachableRoles[] = $r; + } + } + + return array_values(array_unique($reachableRoles)); + } + + protected function buildRoleMap(): void + { + $this->map = []; + foreach ($this->hierarchy as $main => $roles) { + $this->map[$main] = $roles; + $visited = []; + $additionalRoles = $roles; + while ($role = array_shift($additionalRoles)) { + if (!isset($this->hierarchy[$role])) { + continue; + } + + $visited[] = $role; + + foreach ($this->hierarchy[$role] as $roleToAdd) { + $this->map[$main][] = $roleToAdd; + } + + foreach (array_diff($this->hierarchy[$role], $visited) as $additionalRole) { + $additionalRoles[] = $additionalRole; + } + } + + $this->map[$main] = array_unique($this->map[$main]); + } + } +} diff --git a/vendor/symfony/security-core/Role/RoleHierarchyInterface.php b/vendor/symfony/security-core/Role/RoleHierarchyInterface.php new file mode 100644 index 0000000..6e8fa81 --- /dev/null +++ b/vendor/symfony/security-core/Role/RoleHierarchyInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Role; + +/** + * RoleHierarchyInterface is the interface for a role hierarchy. + * + * @author Fabien Potencier + */ +interface RoleHierarchyInterface +{ + /** + * @param string[] $roles + * + * @return string[] + */ + public function getReachableRoleNames(array $roles): array; +} diff --git a/vendor/symfony/security-core/Role/SwitchUserRole.php b/vendor/symfony/security-core/Role/SwitchUserRole.php new file mode 100644 index 0000000..6a29fb4 --- /dev/null +++ b/vendor/symfony/security-core/Role/SwitchUserRole.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Role; + +/** + * Allows migrating session payloads from v4. + * + * @internal + */ +class SwitchUserRole extends Role +{ + private $deprecationTriggered; + private $source; +} diff --git a/vendor/symfony/security-core/Signature/Exception/ExpiredSignatureException.php b/vendor/symfony/security-core/Signature/Exception/ExpiredSignatureException.php new file mode 100644 index 0000000..8986c62 --- /dev/null +++ b/vendor/symfony/security-core/Signature/Exception/ExpiredSignatureException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Signature\Exception; + +use Symfony\Component\Security\Core\Exception\RuntimeException; + +/** + * @author Wouter de Jong + */ +class ExpiredSignatureException extends RuntimeException +{ +} diff --git a/vendor/symfony/security-core/Signature/Exception/InvalidSignatureException.php b/vendor/symfony/security-core/Signature/Exception/InvalidSignatureException.php new file mode 100644 index 0000000..72102fe --- /dev/null +++ b/vendor/symfony/security-core/Signature/Exception/InvalidSignatureException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Signature\Exception; + +use Symfony\Component\Security\Core\Exception\RuntimeException; + +/** + * @author Wouter de Jong + */ +class InvalidSignatureException extends RuntimeException +{ +} diff --git a/vendor/symfony/security-core/Signature/ExpiredSignatureStorage.php b/vendor/symfony/security-core/Signature/ExpiredSignatureStorage.php new file mode 100644 index 0000000..20803b9 --- /dev/null +++ b/vendor/symfony/security-core/Signature/ExpiredSignatureStorage.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Signature; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Ryan Weaver + */ +final class ExpiredSignatureStorage +{ + private CacheItemPoolInterface $cache; + private int $lifetime; + + public function __construct(CacheItemPoolInterface $cache, int $lifetime) + { + $this->cache = $cache; + $this->lifetime = $lifetime; + } + + public function countUsages(string $hash): int + { + $key = rawurlencode($hash); + if (!$this->cache->hasItem($key)) { + return 0; + } + + return $this->cache->getItem($key)->get(); + } + + public function incrementUsages(string $hash): void + { + $item = $this->cache->getItem(rawurlencode($hash)); + + if (!$item->isHit()) { + $item->expiresAfter($this->lifetime); + } + + $item->set($this->countUsages($hash) + 1); + $this->cache->save($item); + } +} diff --git a/vendor/symfony/security-core/Signature/SignatureHasher.php b/vendor/symfony/security-core/Signature/SignatureHasher.php new file mode 100644 index 0000000..3f86fce --- /dev/null +++ b/vendor/symfony/security-core/Signature/SignatureHasher.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Signature; + +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; +use Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Creates and validates secure hashes used in login links and remember-me cookies. + * + * @author Wouter de Jong + * @author Ryan Weaver + */ +class SignatureHasher +{ + private PropertyAccessorInterface $propertyAccessor; + private array $signatureProperties; + private string $secret; + private ?ExpiredSignatureStorage $expiredSignaturesStorage; + private ?int $maxUses; + + /** + * @param array $signatureProperties Properties of the User; the hash is invalidated if these properties change + * @param ExpiredSignatureStorage|null $expiredSignaturesStorage If provided, secures a sequence of hashes that are expired + * @param int|null $maxUses Used together with $expiredSignatureStorage to allow a maximum usage of a hash + */ + public function __construct(PropertyAccessorInterface $propertyAccessor, array $signatureProperties, #[\SensitiveParameter] string $secret, ?ExpiredSignatureStorage $expiredSignaturesStorage = null, ?int $maxUses = null) + { + if (!$secret) { + throw new InvalidArgumentException('A non-empty secret is required.'); + } + + $this->propertyAccessor = $propertyAccessor; + $this->signatureProperties = $signatureProperties; + $this->secret = $secret; + $this->expiredSignaturesStorage = $expiredSignaturesStorage; + $this->maxUses = $maxUses; + } + + /** + * Verifies the hash using the provided user identifier and expire time. + * + * This method must be called before the user object is loaded from a provider. + * + * @param int $expires The expiry time as a unix timestamp + * @param string $hash The plaintext hash provided by the request + * + * @throws InvalidSignatureException If the signature does not match the provided parameters + * @throws ExpiredSignatureException If the signature is no longer valid + */ + public function acceptSignatureHash(string $userIdentifier, int $expires, string $hash): void + { + if ($expires < time()) { + throw new ExpiredSignatureException('Signature has expired.'); + } + $hmac = substr($hash, 0, 44); + $payload = substr($hash, 44).':'.$expires.':'.$userIdentifier; + + if (!hash_equals($hmac, $this->generateHash($payload))) { + throw new InvalidSignatureException('Invalid or expired signature.'); + } + } + + /** + * Verifies the hash using the provided user and expire time. + * + * @param int $expires The expiry time as a unix timestamp + * @param string $hash The plaintext hash provided by the request + * + * @throws InvalidSignatureException If the signature does not match the provided parameters + * @throws ExpiredSignatureException If the signature is no longer valid + */ + public function verifySignatureHash(UserInterface $user, int $expires, string $hash): void + { + if ($expires < time()) { + throw new ExpiredSignatureException('Signature has expired.'); + } + + if (!hash_equals($hash, $this->computeSignatureHash($user, $expires))) { + throw new InvalidSignatureException('Invalid or expired signature.'); + } + + if ($this->expiredSignaturesStorage && $this->maxUses) { + if ($this->expiredSignaturesStorage->countUsages($hash) >= $this->maxUses) { + throw new ExpiredSignatureException(sprintf('Signature can only be used "%d" times.', $this->maxUses)); + } + + $this->expiredSignaturesStorage->incrementUsages($hash); + } + } + + /** + * Computes the secure hash for the provided user and expire time. + * + * @param int $expires The expiry time as a unix timestamp + */ + public function computeSignatureHash(UserInterface $user, int $expires): string + { + $userIdentifier = $user->getUserIdentifier(); + $fieldsHash = hash_init('sha256'); + + foreach ($this->signatureProperties as $property) { + $value = $this->propertyAccessor->getValue($user, $property) ?? ''; + if ($value instanceof \DateTimeInterface) { + $value = $value->format('c'); + } + + if (!\is_scalar($value) && !$value instanceof \Stringable) { + throw new \InvalidArgumentException(sprintf('The property path "%s" on the user object "%s" must return a value that can be cast to a string, but "%s" was returned.', $property, $user::class, get_debug_type($value))); + } + hash_update($fieldsHash, ':'.base64_encode($value)); + } + + $fieldsHash = strtr(base64_encode(hash_final($fieldsHash, true)), '+/=', '-_~'); + + return $this->generateHash($fieldsHash.':'.$expires.':'.$userIdentifier).$fieldsHash; + } + + private function generateHash(string $tokenValue): string + { + return strtr(base64_encode(hash_hmac('sha256', $tokenValue, $this->secret, true)), '+/=', '-_~'); + } +} diff --git a/vendor/symfony/security-core/Test/AccessDecisionStrategyTestCase.php b/vendor/symfony/security-core/Test/AccessDecisionStrategyTestCase.php new file mode 100644 index 0000000..bf2a2b9 --- /dev/null +++ b/vendor/symfony/security-core/Test/AccessDecisionStrategyTestCase.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; +use Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +/** + * Abstract test case for access decision strategies. + * + * @author Alexander M. Turek + */ +abstract class AccessDecisionStrategyTestCase extends TestCase +{ + /** + * @dataProvider provideStrategyTests + * + * @param VoterInterface[] $voters + */ + final public function testDecide(AccessDecisionStrategyInterface $strategy, array $voters, bool $expected) + { + $token = $this->createMock(TokenInterface::class); + $manager = new AccessDecisionManager($voters, $strategy); + + $this->assertSame($expected, $manager->decide($token, ['ROLE_FOO'])); + } + + /** + * @return iterable + */ + abstract public static function provideStrategyTests(): iterable; + + /** + * @return VoterInterface[] + */ + final protected static function getVoters(int $grants, int $denies, int $abstains): array + { + $voters = []; + for ($i = 0; $i < $grants; ++$i) { + $voters[] = static::getVoter(VoterInterface::ACCESS_GRANTED); + } + for ($i = 0; $i < $denies; ++$i) { + $voters[] = static::getVoter(VoterInterface::ACCESS_DENIED); + } + for ($i = 0; $i < $abstains; ++$i) { + $voters[] = static::getVoter(VoterInterface::ACCESS_ABSTAIN); + } + + return $voters; + } + + final protected static function getVoter(int $vote): VoterInterface + { + return new class($vote) implements VoterInterface { + private int $vote; + + public function __construct(int $vote) + { + $this->vote = $vote; + } + + public function vote(TokenInterface $token, $subject, array $attributes): int + { + return $this->vote; + } + }; + } +} diff --git a/vendor/symfony/security-core/User/AttributesBasedUserProviderInterface.php b/vendor/symfony/security-core/User/AttributesBasedUserProviderInterface.php new file mode 100644 index 0000000..9d79422 --- /dev/null +++ b/vendor/symfony/security-core/User/AttributesBasedUserProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\UserNotFoundException; + +/** + * Overrides UserProviderInterface to add an "attributes" argument on loadUserByIdentifier. + * This is particularly useful with self-contained access tokens. + * + * @template-covariant TUser of UserInterface + * + * @template-extends UserProviderInterface + */ +interface AttributesBasedUserProviderInterface extends UserProviderInterface +{ + /** + * Loads the user for the given user identifier (e.g. username or email) and attributes. + * + * This method must throw UserNotFoundException if the user is not found. + * + * @return TUser + * + * @throws UserNotFoundException + */ + public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface; +} diff --git a/vendor/symfony/security-core/User/ChainUserChecker.php b/vendor/symfony/security-core/User/ChainUserChecker.php new file mode 100644 index 0000000..f889d35 --- /dev/null +++ b/vendor/symfony/security-core/User/ChainUserChecker.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +final class ChainUserChecker implements UserCheckerInterface +{ + /** + * @param iterable $checkers + */ + public function __construct(private readonly iterable $checkers) + { + } + + public function checkPreAuth(UserInterface $user): void + { + foreach ($this->checkers as $checker) { + $checker->checkPreAuth($user); + } + } + + public function checkPostAuth(UserInterface $user): void + { + foreach ($this->checkers as $checker) { + $checker->checkPostAuth($user); + } + } +} diff --git a/vendor/symfony/security-core/User/ChainUserProvider.php b/vendor/symfony/security-core/User/ChainUserProvider.php new file mode 100644 index 0000000..cef93a2 --- /dev/null +++ b/vendor/symfony/security-core/User/ChainUserProvider.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; + +/** + * Chain User Provider. + * + * This provider calls several leaf providers in a chain until one is able to + * handle the request. + * + * @author Johannes M. Schmitt + * + * @template-implements UserProviderInterface + */ +class ChainUserProvider implements UserProviderInterface, PasswordUpgraderInterface +{ + private iterable $providers; + + /** + * @param iterable $providers + */ + public function __construct(iterable $providers) + { + $this->providers = $providers; + } + + /** + * @return UserProviderInterface[] + */ + public function getProviders(): array + { + if ($this->providers instanceof \Traversable) { + return iterator_to_array($this->providers); + } + + return $this->providers; + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + foreach ($this->providers as $provider) { + try { + return $provider->loadUserByIdentifier($identifier); + } catch (UserNotFoundException) { + // try next one + } + } + + $ex = new UserNotFoundException(sprintf('There is no user with identifier "%s".', $identifier)); + $ex->setUserIdentifier($identifier); + throw $ex; + } + + public function refreshUser(UserInterface $user): UserInterface + { + $supportedUserFound = false; + + foreach ($this->providers as $provider) { + try { + if (!$provider->supportsClass(get_debug_type($user))) { + continue; + } + + return $provider->refreshUser($user); + } catch (UnsupportedUserException) { + // try next one + } catch (UserNotFoundException) { + $supportedUserFound = true; + // try next one + } + } + + if ($supportedUserFound) { + $username = $user->getUserIdentifier(); + $e = new UserNotFoundException(sprintf('There is no user with name "%s".', $username)); + $e->setUserIdentifier($username); + throw $e; + } else { + throw new UnsupportedUserException(sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', get_debug_type($user))); + } + } + + public function supportsClass(string $class): bool + { + foreach ($this->providers as $provider) { + if ($provider->supportsClass($class)) { + return true; + } + } + + return false; + } + + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + foreach ($this->providers as $provider) { + if ($provider instanceof PasswordUpgraderInterface) { + try { + $provider->upgradePassword($user, $newHashedPassword); + } catch (UnsupportedUserException) { + // ignore: password upgrades are opportunistic + } + } + } + } +} diff --git a/vendor/symfony/security-core/User/EquatableInterface.php b/vendor/symfony/security-core/User/EquatableInterface.php new file mode 100644 index 0000000..3fa9e48 --- /dev/null +++ b/vendor/symfony/security-core/User/EquatableInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * EquatableInterface used to test if two objects are equal in security + * and re-authentication context. + * + * @author Dariusz Górecki + */ +interface EquatableInterface +{ + /** + * The equality comparison should neither be done by referential equality + * nor by comparing identities (i.e. getId() === getId()). + * + * However, you do not need to compare every attribute, but only those that + * are relevant for assessing whether re-authentication is required. + */ + public function isEqualTo(UserInterface $user): bool; +} diff --git a/vendor/symfony/security-core/User/InMemoryUser.php b/vendor/symfony/security-core/User/InMemoryUser.php new file mode 100644 index 0000000..c319e1f --- /dev/null +++ b/vendor/symfony/security-core/User/InMemoryUser.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * UserInterface implementation used by the in-memory user provider. + * + * This should not be used for anything else. + * + * @author Robin Chalas + * @author Fabien Potencier + */ +final class InMemoryUser implements UserInterface, PasswordAuthenticatedUserInterface, EquatableInterface, \Stringable +{ + private string $username; + private ?string $password; + private bool $enabled; + private array $roles; + + public function __construct(?string $username, ?string $password, array $roles = [], bool $enabled = true) + { + if ('' === $username || null === $username) { + throw new \InvalidArgumentException('The username cannot be empty.'); + } + + $this->username = $username; + $this->password = $password; + $this->enabled = $enabled; + $this->roles = $roles; + } + + public function __toString(): string + { + return $this->getUserIdentifier(); + } + + public function getRoles(): array + { + return $this->roles; + } + + public function getPassword(): ?string + { + return $this->password; + } + + /** + * Returns the identifier for this user (e.g. its username or email address). + */ + public function getUserIdentifier(): string + { + return $this->username; + } + + /** + * Checks whether the user is enabled. + * + * Internally, if this method returns false, the authentication system + * will throw a DisabledException and prevent login. + * + * @return bool true if the user is enabled, false otherwise + * + * @see DisabledException + */ + public function isEnabled(): bool + { + return $this->enabled; + } + + public function eraseCredentials(): void + { + } + + public function isEqualTo(UserInterface $user): bool + { + if (!$user instanceof self) { + return false; + } + + if ($this->getPassword() !== $user->getPassword()) { + return false; + } + + $currentRoles = array_map('strval', (array) $this->getRoles()); + $newRoles = array_map('strval', (array) $user->getRoles()); + $rolesChanged = \count($currentRoles) !== \count($newRoles) || \count($currentRoles) !== \count(array_intersect($currentRoles, $newRoles)); + if ($rolesChanged) { + return false; + } + + if ($this->getUserIdentifier() !== $user->getUserIdentifier()) { + return false; + } + + if ($this->isEnabled() !== $user->isEnabled()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/security-core/User/InMemoryUserChecker.php b/vendor/symfony/security-core/User/InMemoryUserChecker.php new file mode 100644 index 0000000..61367c2 --- /dev/null +++ b/vendor/symfony/security-core/User/InMemoryUserChecker.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\DisabledException; + +/** + * Checks the state of the in-memory user account. + * + * @author Fabien Potencier + */ +class InMemoryUserChecker implements UserCheckerInterface +{ + public function checkPreAuth(UserInterface $user): void + { + if (!$user instanceof InMemoryUser) { + return; + } + + if (!$user->isEnabled()) { + $ex = new DisabledException('User account is disabled.'); + $ex->setUser($user); + throw $ex; + } + } + + public function checkPostAuth(UserInterface $user): void + { + } +} diff --git a/vendor/symfony/security-core/User/InMemoryUserProvider.php b/vendor/symfony/security-core/User/InMemoryUserProvider.php new file mode 100644 index 0000000..04bf682 --- /dev/null +++ b/vendor/symfony/security-core/User/InMemoryUserProvider.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; + +/** + * InMemoryUserProvider is a simple non persistent user provider. + * + * Useful for testing, demonstration, prototyping, and for simple needs + * (a backend with a unique admin for instance) + * + * @author Fabien Potencier + * + * @template-implements UserProviderInterface + */ +class InMemoryUserProvider implements UserProviderInterface +{ + /** + * @var array + */ + private array $users = []; + + /** + * The user array is a hash where the keys are usernames and the values are + * an array of attributes: 'password', 'enabled', and 'roles'. + * + * @param array}> $users An array of users + */ + public function __construct(array $users = []) + { + foreach ($users as $username => $attributes) { + $password = $attributes['password'] ?? null; + $enabled = $attributes['enabled'] ?? true; + $roles = $attributes['roles'] ?? []; + $user = new InMemoryUser($username, $password, $roles, $enabled); + + $this->createUser($user); + } + } + + /** + * Adds a new User to the provider. + */ + public function createUser(UserInterface $user): void + { + if (!$user instanceof InMemoryUser) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); + } + + $userIdentifier = strtolower($user->getUserIdentifier()); + if (isset($this->users[$userIdentifier])) { + throw new \LogicException('Another user with the same username already exists.'); + } + + $this->users[$userIdentifier] = $user; + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + $user = $this->getUser($identifier); + + return new InMemoryUser($user->getUserIdentifier(), $user->getPassword(), $user->getRoles(), $user->isEnabled()); + } + + public function refreshUser(UserInterface $user): UserInterface + { + if (!$user instanceof InMemoryUser) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); + } + + $storedUser = $this->getUser($user->getUserIdentifier()); + $userIdentifier = $storedUser->getUserIdentifier(); + + return new InMemoryUser($userIdentifier, $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); + } + + public function supportsClass(string $class): bool + { + return InMemoryUser::class == $class; + } + + /** + * Returns the user by given user. + * + * @throws UserNotFoundException if user whose given username does not exist + */ + private function getUser(string $username): InMemoryUser + { + if (!isset($this->users[strtolower($username)])) { + $ex = new UserNotFoundException(sprintf('Username "%s" does not exist.', $username)); + $ex->setUserIdentifier($username); + + throw $ex; + } + + return $this->users[strtolower($username)]; + } +} diff --git a/vendor/symfony/security-core/User/LegacyPasswordAuthenticatedUserInterface.php b/vendor/symfony/security-core/User/LegacyPasswordAuthenticatedUserInterface.php new file mode 100644 index 0000000..fcffe0b --- /dev/null +++ b/vendor/symfony/security-core/User/LegacyPasswordAuthenticatedUserInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * For users that can be authenticated using a password/salt couple. + * + * Once all password hashes have been upgraded to a modern algorithm via password migrations, + * implement {@see PasswordAuthenticatedUserInterface} instead. + * + * @author Robin Chalas + */ +interface LegacyPasswordAuthenticatedUserInterface extends PasswordAuthenticatedUserInterface +{ + /** + * Returns the salt that was originally used to hash the password. + */ + public function getSalt(): ?string; +} diff --git a/vendor/symfony/security-core/User/MissingUserProvider.php b/vendor/symfony/security-core/User/MissingUserProvider.php new file mode 100644 index 0000000..cf6102a --- /dev/null +++ b/vendor/symfony/security-core/User/MissingUserProvider.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * MissingUserProvider is a dummy user provider used to throw proper exception + * when a firewall requires a user provider but none was defined. + * + * @internal + * + * @template-implements UserProviderInterface + */ +class MissingUserProvider implements UserProviderInterface +{ + /** + * @param string $firewall the firewall missing a provider + */ + public function __construct(string $firewall) + { + throw new InvalidConfigurationException(sprintf('"%s" firewall requires a user provider but none was defined.', $firewall)); + } + + public function loadUserByUsername(string $username): UserInterface + { + throw new \BadMethodCallException(); + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + throw new \BadMethodCallException(); + } + + public function refreshUser(UserInterface $user): UserInterface + { + throw new \BadMethodCallException(); + } + + public function supportsClass(string $class): bool + { + throw new \BadMethodCallException(); + } +} diff --git a/vendor/symfony/security-core/User/OidcUser.php b/vendor/symfony/security-core/User/OidcUser.php new file mode 100644 index 0000000..bcce363 --- /dev/null +++ b/vendor/symfony/security-core/User/OidcUser.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * UserInterface implementation used by the access-token security workflow with an OIDC server. + */ +class OidcUser implements UserInterface +{ + private array $additionalClaims = []; + + public function __construct( + private ?string $userIdentifier = null, + private array $roles = ['ROLE_USER'], + + // Standard Claims (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) + private ?string $sub = null, + private ?string $name = null, + private ?string $givenName = null, + private ?string $familyName = null, + private ?string $middleName = null, + private ?string $nickname = null, + private ?string $preferredUsername = null, + private ?string $profile = null, + private ?string $picture = null, + private ?string $website = null, + private ?string $email = null, + private ?bool $emailVerified = null, + private ?string $gender = null, + private ?string $birthdate = null, + private ?string $zoneinfo = null, + private ?string $locale = null, + private ?string $phoneNumber = null, + private ?bool $phoneNumberVerified = null, + private ?array $address = null, + private ?\DateTimeInterface $updatedAt = null, + + // Additional Claims (https://openid.net/specs/openid-connect-core-1_0.html#AdditionalClaims) + ...$additionalClaims, + ) { + if (null === $sub || '' === $sub) { + throw new \InvalidArgumentException('The "sub" claim cannot be empty.'); + } + + $this->additionalClaims = $additionalClaims['additionalClaims'] ?? $additionalClaims; + } + + /** + * OIDC or OAuth specs don't have any "role" notion. + * + * If you want to implement "roles" from your OIDC server, + * send a "roles" constructor argument to this object + * (e.g.: using a custom UserProvider). + */ + public function getRoles(): array + { + return $this->roles; + } + + public function getUserIdentifier(): string + { + return (string) ($this->userIdentifier ?? $this->getSub()); + } + + public function eraseCredentials(): void + { + } + + public function getSub(): ?string + { + return $this->sub; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getGivenName(): ?string + { + return $this->givenName; + } + + public function getFamilyName(): ?string + { + return $this->familyName; + } + + public function getMiddleName(): ?string + { + return $this->middleName; + } + + public function getNickname(): ?string + { + return $this->nickname; + } + + public function getPreferredUsername(): ?string + { + return $this->preferredUsername; + } + + public function getProfile(): ?string + { + return $this->profile; + } + + public function getPicture(): ?string + { + return $this->picture; + } + + public function getWebsite(): ?string + { + return $this->website; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function getEmailVerified(): ?bool + { + return $this->emailVerified; + } + + public function getGender(): ?string + { + return $this->gender; + } + + public function getBirthdate(): ?string + { + return $this->birthdate; + } + + public function getZoneinfo(): ?string + { + return $this->zoneinfo; + } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function getPhoneNumber(): ?string + { + return $this->phoneNumber; + } + + public function getphoneNumberVerified(): ?bool + { + return $this->phoneNumberVerified; + } + + public function getAddress(): ?array + { + return $this->address; + } + + public function getUpdatedAt(): ?\DateTimeInterface + { + return $this->updatedAt; + } + + public function getAdditionalClaims(): array + { + return $this->additionalClaims; + } +} diff --git a/vendor/symfony/security-core/User/PasswordAuthenticatedUserInterface.php b/vendor/symfony/security-core/User/PasswordAuthenticatedUserInterface.php new file mode 100644 index 0000000..478c9e3 --- /dev/null +++ b/vendor/symfony/security-core/User/PasswordAuthenticatedUserInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * For users that can be authenticated using a password. + * + * @author Robin Chalas + * @author Wouter de Jong + */ +interface PasswordAuthenticatedUserInterface +{ + /** + * Returns the hashed password used to authenticate the user. + * + * Usually on authentication, a plain-text password will be compared to this value. + */ + public function getPassword(): ?string; +} diff --git a/vendor/symfony/security-core/User/PasswordUpgraderInterface.php b/vendor/symfony/security-core/User/PasswordUpgraderInterface.php new file mode 100644 index 0000000..fd21f14 --- /dev/null +++ b/vendor/symfony/security-core/User/PasswordUpgraderInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * @author Nicolas Grekas + */ +interface PasswordUpgraderInterface +{ + /** + * Upgrades the hashed password of a user, typically for using a better hash algorithm. + * + * This method should persist the new password in the user storage and update the $user object accordingly. + * Because you don't want your users not being able to log in, this method should be opportunistic: + * it's fine if it does nothing or if it fails without throwing any exception. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void; +} diff --git a/vendor/symfony/security-core/User/UserCheckerInterface.php b/vendor/symfony/security-core/User/UserCheckerInterface.php new file mode 100644 index 0000000..480ba7b --- /dev/null +++ b/vendor/symfony/security-core/User/UserCheckerInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\AccountStatusException; + +/** + * Implement to throw AccountStatusException during the authentication process. + * + * Can be used when you want to check the account status, e.g when the account is + * disabled or blocked. This should not be used to make authentication decisions. + * + * @author Fabien Potencier + */ +interface UserCheckerInterface +{ + /** + * Checks the user account before authentication. + * + * @throws AccountStatusException + */ + public function checkPreAuth(UserInterface $user): void; + + /** + * Checks the user account after authentication. + * + * @throws AccountStatusException + */ + public function checkPostAuth(UserInterface $user): void; +} diff --git a/vendor/symfony/security-core/User/UserInterface.php b/vendor/symfony/security-core/User/UserInterface.php new file mode 100644 index 0000000..50f8fb0 --- /dev/null +++ b/vendor/symfony/security-core/User/UserInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * Represents the interface that all user classes must implement. + * + * This interface is useful because the authentication layer can deal with + * the object through its lifecycle, using the object to get the hashed + * password (for checking against a submitted password), assigning roles + * and so on. + * + * Regardless of how your users are loaded or where they come from (a database, + * configuration, web service, etc.), you will have a class that implements + * this interface. Objects that implement this interface are created and + * loaded by different objects that implement UserProviderInterface. + * + * @see UserProviderInterface + * + * @author Fabien Potencier + */ +interface UserInterface +{ + /** + * Returns the roles granted to the user. + * + * public function getRoles() + * { + * return ['ROLE_USER']; + * } + * + * Alternatively, the roles might be stored in a ``roles`` property, + * and populated in any number of different ways when the user object + * is created. + * + * @return string[] + */ + public function getRoles(): array; + + /** + * Removes sensitive data from the user. + * + * This is important if, at any given point, sensitive information like + * the plain-text password is stored on this object. + */ + public function eraseCredentials(): void; + + /** + * Returns the identifier for this user (e.g. username or email address). + */ + public function getUserIdentifier(): string; +} diff --git a/vendor/symfony/security-core/User/UserProviderInterface.php b/vendor/symfony/security-core/User/UserProviderInterface.php new file mode 100644 index 0000000..9a5e676 --- /dev/null +++ b/vendor/symfony/security-core/User/UserProviderInterface.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; + +/** + * Represents a class that loads UserInterface objects from some source for the authentication system. + * + * In a typical authentication configuration, a user identifier (e.g. a + * username or email address) credential enters the system (via form login, or + * any method). The user provider that is configured with that authentication + * method is asked to load the UserInterface object for the given identifier (via + * loadUserByIdentifier) so that the rest of the process can continue. + * + * Internally, a user provider can load users from any source (databases, + * configuration, web service). This is totally independent of how the authentication + * information is submitted or what the UserInterface object looks like. + * + * @author Fabien Potencier + * + * @template-covariant TUser of UserInterface + */ +interface UserProviderInterface +{ + /** + * Refreshes the user. + * + * It is up to the implementation to decide if the user data should be + * totally reloaded (e.g. from the database), or if the UserInterface + * object can just be merged into some internal array of users / identity + * map. + * + * @psalm-return TUser + * + * @throws UnsupportedUserException if the user is not supported + * @throws UserNotFoundException if the user is not found + */ + public function refreshUser(UserInterface $user): UserInterface; + + /** + * Whether this provider supports the given user class. + */ + public function supportsClass(string $class): bool; + + /** + * Loads the user for the given user identifier (e.g. username or email). + * + * This method must throw UserNotFoundException if the user is not found. + * + * @return TUser + * + * @throws UserNotFoundException + */ + public function loadUserByIdentifier(string $identifier): UserInterface; +} diff --git a/vendor/symfony/security-core/Validator/Constraints/UserPassword.php b/vendor/symfony/security-core/Validator/Constraints/UserPassword.php new file mode 100644 index 0000000..e6741a4 --- /dev/null +++ b/vendor/symfony/security-core/Validator/Constraints/UserPassword.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class UserPassword extends Constraint +{ + public const INVALID_PASSWORD_ERROR = '2d2a8bb4-ddc8-45e4-9b0f-8670d3a3e290'; + + protected const ERROR_NAMES = [ + self::INVALID_PASSWORD_ERROR => 'INVALID_PASSWORD_ERROR', + ]; + + public string $message = 'This value should be the user\'s current password.'; + public string $service = 'security.validator.user_password'; + + public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $groups = null, mixed $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->service = $service ?? $this->service; + } + + public function validatedBy(): string + { + return $this->service; + } +} diff --git a/vendor/symfony/security-core/Validator/Constraints/UserPasswordValidator.php b/vendor/symfony/security-core/Validator/Constraints/UserPasswordValidator.php new file mode 100644 index 0000000..3d6c763 --- /dev/null +++ b/vendor/symfony/security-core/Validator/Constraints/UserPasswordValidator.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Validator\Constraints; + +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +class UserPasswordValidator extends ConstraintValidator +{ + private TokenStorageInterface $tokenStorage; + private PasswordHasherFactoryInterface $hasherFactory; + + public function __construct(TokenStorageInterface $tokenStorage, PasswordHasherFactoryInterface $hasherFactory) + { + $this->tokenStorage = $tokenStorage; + $this->hasherFactory = $hasherFactory; + } + + public function validate(mixed $password, Constraint $constraint): void + { + if (!$constraint instanceof UserPassword) { + throw new UnexpectedTypeException($constraint, UserPassword::class); + } + + if (null === $password || '' === $password) { + $this->context->buildViolation($constraint->message) + ->setCode(UserPassword::INVALID_PASSWORD_ERROR) + ->addViolation(); + + return; + } + + if (!\is_string($password)) { + throw new UnexpectedTypeException($password, 'string'); + } + + $user = $this->tokenStorage->getToken()->getUser(); + + if (!$user instanceof PasswordAuthenticatedUserInterface) { + throw new ConstraintDefinitionException(sprintf('The "%s" class must implement the "%s" interface.', PasswordAuthenticatedUserInterface::class, get_debug_type($user))); + } + + $hasher = $this->hasherFactory->getPasswordHasher($user); + + if (null === $user->getPassword() || !$hasher->verify($user->getPassword(), $password, $user instanceof LegacyPasswordAuthenticatedUserInterface ? $user->getSalt() : null)) { + $this->context->buildViolation($constraint->message) + ->setCode(UserPassword::INVALID_PASSWORD_ERROR) + ->addViolation(); + } + } +} diff --git a/vendor/symfony/security-core/composer.json b/vendor/symfony/security-core/composer.json new file mode 100644 index 0000000..a923768 --- /dev/null +++ b/vendor/symfony/security-core/composer.json @@ -0,0 +1,53 @@ +{ + "name": "symfony/security-core", + "type": "library", + "description": "Symfony Security Component - Core Library", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/password-hasher": "^6.4|^7.0" + }, + "require-dev": { + "psr/container": "^1.1|^2.0", + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/validator": "^6.4|^7.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/validator": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Security\\Core\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/security-csrf/CHANGELOG.md b/vendor/symfony/security-csrf/CHANGELOG.md new file mode 100644 index 0000000..1476c99 --- /dev/null +++ b/vendor/symfony/security-csrf/CHANGELOG.md @@ -0,0 +1,13 @@ +CHANGELOG +========= + +6.0 +--- + + * Remove the `SessionInterface $session` constructor argument of `SessionTokenStorage`, inject a `\Symfony\Component\HttpFoundation\RequestStack $requestStack` instead + * Using `SessionTokenStorage` outside a request context throws a `SessionNotFoundException` + +5.3 +--- + +The CHANGELOG for version 5.3 and earlier can be found at https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/Security/CHANGELOG.md diff --git a/vendor/symfony/security-csrf/CsrfToken.php b/vendor/symfony/security-csrf/CsrfToken.php new file mode 100644 index 0000000..57f972e --- /dev/null +++ b/vendor/symfony/security-csrf/CsrfToken.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf; + +/** + * A CSRF token. + * + * @author Bernhard Schussek + */ +class CsrfToken +{ + private string $id; + private string $value; + + public function __construct(string $id, #[\SensitiveParameter] ?string $value) + { + $this->id = $id; + $this->value = $value ?? ''; + } + + /** + * Returns the ID of the CSRF token. + */ + public function getId(): string + { + return $this->id; + } + + /** + * Returns the value of the CSRF token. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Returns the value of the CSRF token. + */ + public function __toString(): string + { + return $this->value; + } +} diff --git a/vendor/symfony/security-csrf/CsrfTokenManager.php b/vendor/symfony/security-csrf/CsrfTokenManager.php new file mode 100644 index 0000000..96ecc54 --- /dev/null +++ b/vendor/symfony/security-csrf/CsrfTokenManager.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; + +/** + * Default implementation of {@link CsrfTokenManagerInterface}. + * + * @author Bernhard Schussek + * @author Kévin Dunglas + */ +class CsrfTokenManager implements CsrfTokenManagerInterface +{ + private TokenGeneratorInterface $generator; + private TokenStorageInterface $storage; + private \Closure|string $namespace; + + /** + * @param $namespace + * * null: generates a namespace using $_SERVER['HTTPS'] + * * string: uses the given string + * * RequestStack: generates a namespace using the current main request + * * callable: uses the result of this callable (must return a string) + */ + public function __construct(?TokenGeneratorInterface $generator = null, ?TokenStorageInterface $storage = null, string|RequestStack|callable|null $namespace = null) + { + $this->generator = $generator ?? new UriSafeTokenGenerator(); + $this->storage = $storage ?? new NativeSessionTokenStorage(); + + $superGlobalNamespaceGenerator = fn () => !empty($_SERVER['HTTPS']) && 'off' !== strtolower($_SERVER['HTTPS']) ? 'https-' : ''; + + if (null === $namespace) { + $this->namespace = $superGlobalNamespaceGenerator; + } elseif ($namespace instanceof RequestStack) { + $this->namespace = function () use ($namespace, $superGlobalNamespaceGenerator) { + if ($request = $namespace->getMainRequest()) { + return $request->isSecure() ? 'https-' : ''; + } + + return $superGlobalNamespaceGenerator(); + }; + } elseif ($namespace instanceof \Closure || \is_string($namespace)) { + $this->namespace = $namespace; + } elseif (\is_callable($namespace)) { + $this->namespace = $namespace(...); + } else { + throw new InvalidArgumentException(sprintf('$namespace must be a string, a callable returning a string, null or an instance of "RequestStack". "%s" given.', get_debug_type($namespace))); + } + } + + public function getToken(string $tokenId): CsrfToken + { + $namespacedId = $this->getNamespace().$tokenId; + if ($this->storage->hasToken($namespacedId)) { + $value = $this->storage->getToken($namespacedId); + } else { + $value = $this->generator->generateToken(); + + $this->storage->setToken($namespacedId, $value); + } + + return new CsrfToken($tokenId, $this->randomize($value)); + } + + public function refreshToken(string $tokenId): CsrfToken + { + $namespacedId = $this->getNamespace().$tokenId; + $value = $this->generator->generateToken(); + + $this->storage->setToken($namespacedId, $value); + + return new CsrfToken($tokenId, $this->randomize($value)); + } + + public function removeToken(string $tokenId): ?string + { + return $this->storage->removeToken($this->getNamespace().$tokenId); + } + + public function isTokenValid(CsrfToken $token): bool + { + $namespacedId = $this->getNamespace().$token->getId(); + if (!$this->storage->hasToken($namespacedId)) { + return false; + } + + return hash_equals($this->storage->getToken($namespacedId), $this->derandomize($token->getValue())); + } + + private function getNamespace(): string + { + return \is_callable($ns = $this->namespace) ? $ns() : $ns; + } + + private function randomize(string $value): string + { + $key = random_bytes(32); + $value = $this->xor($value, $key); + + return sprintf('%s.%s.%s', substr(hash('xxh128', $key), 0, 1 + (\ord($key[0]) % 32)), rtrim(strtr(base64_encode($key), '+/', '-_'), '='), rtrim(strtr(base64_encode($value), '+/', '-_'), '=')); + } + + private function derandomize(string $value): string + { + $parts = explode('.', $value); + if (3 !== \count($parts)) { + return $value; + } + $key = base64_decode(strtr($parts[1], '-_', '+/')); + if ('' === $key || false === $key) { + return $value; + } + $value = base64_decode(strtr($parts[2], '-_', '+/')); + + return $this->xor($value, $key); + } + + private function xor(string $value, string $key): string + { + if (\strlen($value) > \strlen($key)) { + $key = str_repeat($key, ceil(\strlen($value) / \strlen($key))); + } + + return $value ^ $key; + } +} diff --git a/vendor/symfony/security-csrf/CsrfTokenManagerInterface.php b/vendor/symfony/security-csrf/CsrfTokenManagerInterface.php new file mode 100644 index 0000000..14984a9 --- /dev/null +++ b/vendor/symfony/security-csrf/CsrfTokenManagerInterface.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf; + +/** + * Manages CSRF tokens. + * + * @author Bernhard Schussek + */ +interface CsrfTokenManagerInterface +{ + /** + * Returns a CSRF token for the given ID. + * + * If previously no token existed for the given ID, a new token is + * generated. Otherwise the existing token is returned (with the same value, + * not the same instance). + * + * @param string $tokenId The token ID. You may choose an arbitrary value + * for the ID + */ + public function getToken(string $tokenId): CsrfToken; + + /** + * Generates a new token value for the given ID. + * + * This method will generate a new token for the given token ID, independent + * of whether a token value previously existed or not. It can be used to + * enforce once-only tokens in environments with high security needs. + * + * @param string $tokenId The token ID. You may choose an arbitrary value + * for the ID + */ + public function refreshToken(string $tokenId): CsrfToken; + + /** + * Invalidates the CSRF token with the given ID, if one exists. + * + * @return string|null Returns the removed token value if one existed, NULL + * otherwise + */ + public function removeToken(string $tokenId): ?string; + + /** + * Returns whether the given CSRF token is valid. + */ + public function isTokenValid(CsrfToken $token): bool; +} diff --git a/vendor/symfony/security-csrf/Exception/TokenNotFoundException.php b/vendor/symfony/security-csrf/Exception/TokenNotFoundException.php new file mode 100644 index 0000000..936afde --- /dev/null +++ b/vendor/symfony/security-csrf/Exception/TokenNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\Exception; + +use Symfony\Component\Security\Core\Exception\RuntimeException; + +/** + * @author Bernhard Schussek + */ +class TokenNotFoundException extends RuntimeException +{ +} diff --git a/vendor/symfony/security-csrf/LICENSE b/vendor/symfony/security-csrf/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/security-csrf/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/security-csrf/README.md b/vendor/symfony/security-csrf/README.md new file mode 100644 index 0000000..79277e6 --- /dev/null +++ b/vendor/symfony/security-csrf/README.md @@ -0,0 +1,29 @@ +Security Component - CSRF +========================= + +The Security CSRF (cross-site request forgery) component provides a class +`CsrfTokenManager` for generating and validating CSRF tokens. + +Sponsor +------- + +The Security component for Symfony 7.1 is [backed][1] by [SymfonyCasts][2]. + +Learn Symfony faster by watching real projects being built and actively coding +along with them. SymfonyCasts bridges that learning gap, bringing you video +tutorials and coding challenges. Code on! + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/security.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://symfonycasts.com +[3]: https://symfony.com/sponsor diff --git a/vendor/symfony/security-csrf/TokenGenerator/TokenGeneratorInterface.php b/vendor/symfony/security-csrf/TokenGenerator/TokenGeneratorInterface.php new file mode 100644 index 0000000..9874092 --- /dev/null +++ b/vendor/symfony/security-csrf/TokenGenerator/TokenGeneratorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenGenerator; + +/** + * Generates CSRF tokens. + * + * @author Bernhard Schussek + */ +interface TokenGeneratorInterface +{ + /** + * Generates a CSRF token. + */ + public function generateToken(): string; +} diff --git a/vendor/symfony/security-csrf/TokenGenerator/UriSafeTokenGenerator.php b/vendor/symfony/security-csrf/TokenGenerator/UriSafeTokenGenerator.php new file mode 100644 index 0000000..a315944 --- /dev/null +++ b/vendor/symfony/security-csrf/TokenGenerator/UriSafeTokenGenerator.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenGenerator; + +/** + * Generates CSRF tokens. + * + * @author Bernhard Schussek + */ +class UriSafeTokenGenerator implements TokenGeneratorInterface +{ + private int $entropy; + + /** + * Generates URI-safe CSRF tokens. + * + * @param int $entropy The amount of entropy collected for each token (in bits) + */ + public function __construct(int $entropy = 256) + { + if ($entropy <= 7) { + throw new \InvalidArgumentException('Entropy should be greater than 7.'); + } + + $this->entropy = $entropy; + } + + public function generateToken(): string + { + // Generate an URI safe base64 encoded string that does not contain "+", + // "/" or "=" which need to be URL encoded and make URLs unnecessarily + // longer. + $bytes = random_bytes(intdiv($this->entropy, 8)); + + return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '='); + } +} diff --git a/vendor/symfony/security-csrf/TokenStorage/ClearableTokenStorageInterface.php b/vendor/symfony/security-csrf/TokenStorage/ClearableTokenStorageInterface.php new file mode 100644 index 0000000..2f6d96b --- /dev/null +++ b/vendor/symfony/security-csrf/TokenStorage/ClearableTokenStorageInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +/** + * @author Christian Flothmann + */ +interface ClearableTokenStorageInterface extends TokenStorageInterface +{ + /** + * Removes all CSRF tokens. + */ + public function clear(): void; +} diff --git a/vendor/symfony/security-csrf/TokenStorage/NativeSessionTokenStorage.php b/vendor/symfony/security-csrf/TokenStorage/NativeSessionTokenStorage.php new file mode 100644 index 0000000..fa202e4 --- /dev/null +++ b/vendor/symfony/security-csrf/TokenStorage/NativeSessionTokenStorage.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; + +/** + * Token storage that uses PHP's native session handling. + * + * @author Bernhard Schussek + */ +class NativeSessionTokenStorage implements ClearableTokenStorageInterface +{ + /** + * The namespace used to store values in the session. + */ + public const SESSION_NAMESPACE = '_csrf'; + + private bool $sessionStarted = false; + private string $namespace; + + /** + * Initializes the storage with a session namespace. + * + * @param string $namespace The namespace under which the token is stored in the session + */ + public function __construct(string $namespace = self::SESSION_NAMESPACE) + { + $this->namespace = $namespace; + } + + public function getToken(string $tokenId): string + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + if (!isset($_SESSION[$this->namespace][$tokenId])) { + throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.'); + } + + return (string) $_SESSION[$this->namespace][$tokenId]; + } + + public function setToken(string $tokenId, #[\SensitiveParameter] string $token): void + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + $_SESSION[$this->namespace][$tokenId] = $token; + } + + public function hasToken(string $tokenId): bool + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + return isset($_SESSION[$this->namespace][$tokenId]); + } + + public function removeToken(string $tokenId): ?string + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + if (!isset($_SESSION[$this->namespace][$tokenId])) { + return null; + } + + $token = (string) $_SESSION[$this->namespace][$tokenId]; + + unset($_SESSION[$this->namespace][$tokenId]); + + if (!$_SESSION[$this->namespace]) { + unset($_SESSION[$this->namespace]); + } + + return $token; + } + + public function clear(): void + { + unset($_SESSION[$this->namespace]); + } + + private function startSession(): void + { + if (\PHP_SESSION_NONE === session_status()) { + session_start(); + } + + $this->sessionStarted = true; + } +} diff --git a/vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php b/vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php new file mode 100644 index 0000000..faad201 --- /dev/null +++ b/vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; + +/** + * Token storage that uses a Symfony Session object. + * + * @author Bernhard Schussek + */ +class SessionTokenStorage implements ClearableTokenStorageInterface +{ + /** + * The namespace used to store values in the session. + */ + public const SESSION_NAMESPACE = '_csrf'; + + private RequestStack $requestStack; + private string $namespace; + + /** + * Initializes the storage with a RequestStack object and a session namespace. + * + * @param string $namespace The namespace under which the token is stored in the requestStack + */ + public function __construct(RequestStack $requestStack, string $namespace = self::SESSION_NAMESPACE) + { + $this->requestStack = $requestStack; + $this->namespace = $namespace; + } + + public function getToken(string $tokenId): string + { + $session = $this->getSession(); + if (!$session->isStarted()) { + $session->start(); + } + + if (!$session->has($this->namespace.'/'.$tokenId)) { + throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.'); + } + + return (string) $session->get($this->namespace.'/'.$tokenId); + } + + public function setToken(string $tokenId, #[\SensitiveParameter] string $token): void + { + $session = $this->getSession(); + if (!$session->isStarted()) { + $session->start(); + } + + $session->set($this->namespace.'/'.$tokenId, $token); + } + + public function hasToken(string $tokenId): bool + { + $session = $this->getSession(); + if (!$session->isStarted()) { + $session->start(); + } + + return $session->has($this->namespace.'/'.$tokenId); + } + + public function removeToken(string $tokenId): ?string + { + $session = $this->getSession(); + if (!$session->isStarted()) { + $session->start(); + } + + return $session->remove($this->namespace.'/'.$tokenId); + } + + public function clear(): void + { + $session = $this->getSession(); + foreach (array_keys($session->all()) as $key) { + if (str_starts_with($key, $this->namespace.'/')) { + $session->remove($key); + } + } + } + + /** + * @throws SessionNotFoundException + */ + private function getSession(): SessionInterface + { + return $this->requestStack->getSession(); + } +} diff --git a/vendor/symfony/security-csrf/TokenStorage/TokenStorageInterface.php b/vendor/symfony/security-csrf/TokenStorage/TokenStorageInterface.php new file mode 100644 index 0000000..804b6a4 --- /dev/null +++ b/vendor/symfony/security-csrf/TokenStorage/TokenStorageInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +/** + * Stores CSRF tokens. + * + * @author Bernhard Schussek + */ +interface TokenStorageInterface +{ + /** + * Reads a stored CSRF token. + * + * @throws \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException If the token ID does not exist + */ + public function getToken(string $tokenId): string; + + /** + * Stores a CSRF token. + */ + public function setToken(string $tokenId, #[\SensitiveParameter] string $token): void; + + /** + * Removes a CSRF token. + * + * @return string|null Returns the removed token if one existed, NULL + * otherwise + */ + public function removeToken(string $tokenId): ?string; + + /** + * Checks whether a token with the given token ID exists. + */ + public function hasToken(string $tokenId): bool; +} diff --git a/vendor/symfony/security-csrf/composer.json b/vendor/symfony/security-csrf/composer.json new file mode 100644 index 0000000..e93fc47 --- /dev/null +++ b/vendor/symfony/security-csrf/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/security-csrf", + "type": "library", + "description": "Symfony Security Component - CSRF Library", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/security-core": "^6.4|^7.0" + }, + "require-dev": { + "symfony/http-foundation": "^6.4|^7.0" + }, + "conflict": { + "symfony/http-foundation": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Security\\Csrf\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/security-http/AccessMap.php b/vendor/symfony/security-http/AccessMap.php new file mode 100644 index 0000000..4913e46 --- /dev/null +++ b/vendor/symfony/security-http/AccessMap.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * AccessMap allows configuration of different access control rules for + * specific parts of the website. + * + * @author Fabien Potencier + */ +class AccessMap implements AccessMapInterface +{ + private array $map = []; + + /** + * @param array $attributes An array of attributes to pass to the access decision manager (like roles) + * @param string|null $channel The channel to enforce (http, https, or null) + */ + public function add(RequestMatcherInterface $requestMatcher, array $attributes = [], ?string $channel = null): void + { + $this->map[] = [$requestMatcher, $attributes, $channel]; + } + + public function getPatterns(Request $request): array + { + foreach ($this->map as $elements) { + if (null === $elements[0] || $elements[0]->matches($request)) { + return [$elements[1], $elements[2]]; + } + } + + return [null, null]; + } +} diff --git a/vendor/symfony/security-http/AccessMapInterface.php b/vendor/symfony/security-http/AccessMapInterface.php new file mode 100644 index 0000000..7002c6b --- /dev/null +++ b/vendor/symfony/security-http/AccessMapInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\HttpFoundation\Request; + +/** + * AccessMap allows configuration of different access control rules for + * specific parts of the website. + * + * @author Fabien Potencier + * @author Kris Wallsmith + */ +interface AccessMapInterface +{ + /** + * Returns security attributes and required channel for the supplied request. + * + * @return array{0: array|null, 1: string|null} A tuple of security attributes and the required channel + */ + public function getPatterns(Request $request): array; +} diff --git a/vendor/symfony/security-http/AccessToken/AccessTokenExtractorInterface.php b/vendor/symfony/security-http/AccessToken/AccessTokenExtractorInterface.php new file mode 100644 index 0000000..dcd48a3 --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/AccessTokenExtractorInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken; + +use Symfony\Component\HttpFoundation\Request; + +/** + * The token extractor retrieves the token from a request. + * + * @author Florent Morselli + */ +interface AccessTokenExtractorInterface +{ + public function extractAccessToken(Request $request): ?string; +} diff --git a/vendor/symfony/security-http/AccessToken/AccessTokenHandlerInterface.php b/vendor/symfony/security-http/AccessToken/AccessTokenHandlerInterface.php new file mode 100644 index 0000000..5cbc857 --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/AccessTokenHandlerInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + +/** + * The token handler retrieves the user identifier from the token. + * In order to get the user identifier, implementations may need to load and validate the token (e.g. revocation, expiration time, digital signature...). + * + * @author Florent Morselli + */ +interface AccessTokenHandlerInterface +{ + /** + * @throws AuthenticationException + */ + public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge; +} diff --git a/vendor/symfony/security-http/AccessToken/Cas/Cas2Handler.php b/vendor/symfony/security-http/AccessToken/Cas/Cas2Handler.php new file mode 100644 index 0000000..ab08b8d --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/Cas/Cas2Handler.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken\Cas; + +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @see https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-V2-Specification.html + * + * @author Nicolas Attard + */ +final class Cas2Handler implements AccessTokenHandlerInterface +{ + public function __construct( + private readonly RequestStack $requestStack, + private readonly string $validationUrl, + private readonly string $prefix = 'cas', + private ?HttpClientInterface $client = null, + ) { + if (null === $client) { + if (!class_exists(HttpClient::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); + } + + $this->client = HttpClient::create(); + } + } + + /** + * @throws AuthenticationException + */ + public function getUserBadgeFrom(string $accessToken): UserBadge + { + $response = $this->client->request('GET', $this->getValidationUrl($accessToken)); + + $xml = new \SimpleXMLElement($response->getContent(), 0, false, $this->prefix, true); + + if (isset($xml->authenticationSuccess)) { + return new UserBadge((string) $xml->authenticationSuccess->user); + } + + if (isset($xml->authenticationFailure)) { + throw new AuthenticationException('CAS Authentication Failure: '.trim((string) $xml->authenticationFailure)); + } + + throw new AuthenticationException('Invalid CAS response.'); + } + + private function getValidationUrl(string $accessToken): string + { + $request = $this->requestStack->getCurrentRequest(); + + if (null === $request) { + throw new \LogicException('Request should exist so it can be processed for error.'); + } + + $query = $request->query->all(); + + if (!isset($query['ticket'])) { + throw new AuthenticationException('No ticket found in request.'); + } + unset($query['ticket']); + $queryString = $query ? '?'.http_build_query($query) : ''; + + return sprintf('%s?ticket=%s&service=%s', + $this->validationUrl, + urlencode($accessToken), + urlencode($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$queryString) + ); + } +} diff --git a/vendor/symfony/security-http/AccessToken/ChainAccessTokenExtractor.php b/vendor/symfony/security-http/AccessToken/ChainAccessTokenExtractor.php new file mode 100644 index 0000000..ff16b91 --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/ChainAccessTokenExtractor.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken; + +use Symfony\Component\HttpFoundation\Request; + +/** + * The token extractor retrieves the token from a request. + * + * @author Florent Morselli + */ +final class ChainAccessTokenExtractor implements AccessTokenExtractorInterface +{ + /** + * @param AccessTokenExtractorInterface[] $accessTokenExtractors + */ + public function __construct( + private readonly iterable $accessTokenExtractors, + ) { + } + + public function extractAccessToken(Request $request): ?string + { + foreach ($this->accessTokenExtractors as $extractor) { + if ($accessToken = $extractor->extractAccessToken($request)) { + return $accessToken; + } + } + + return null; + } +} diff --git a/vendor/symfony/security-http/AccessToken/FormEncodedBodyExtractor.php b/vendor/symfony/security-http/AccessToken/FormEncodedBodyExtractor.php new file mode 100644 index 0000000..6661e38 --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/FormEncodedBodyExtractor.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Extracts a token from the body request. + * + * WARNING! + * Because of the security weaknesses associated with this method, + * the request body method SHOULD NOT be used except in application contexts + * where participating browsers do not have access to the "Authorization" request header field. + * + * @author Florent Morselli + * + * @see https://datatracker.ietf.org/doc/html/rfc6750#section-2.2 + */ +final class FormEncodedBodyExtractor implements AccessTokenExtractorInterface +{ + public function __construct( + private readonly string $parameter = 'access_token', + ) { + } + + public function extractAccessToken(Request $request): ?string + { + if ( + Request::METHOD_POST !== $request->getMethod() + || !str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') + ) { + return null; + } + $parameter = $request->request->get($this->parameter); + + return \is_string($parameter) ? $parameter : null; + } +} diff --git a/vendor/symfony/security-http/AccessToken/HeaderAccessTokenExtractor.php b/vendor/symfony/security-http/AccessToken/HeaderAccessTokenExtractor.php new file mode 100644 index 0000000..f73648d --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/HeaderAccessTokenExtractor.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Extracts a token from the request header. + * + * @author Florent Morselli + * + * @see https://datatracker.ietf.org/doc/html/rfc6750#section-2.1 + */ +final class HeaderAccessTokenExtractor implements AccessTokenExtractorInterface +{ + private string $regex; + + public function __construct( + private readonly string $headerParameter = 'Authorization', + private readonly string $tokenType = 'Bearer', + ) { + $this->regex = sprintf( + '/^%s([a-zA-Z0-9\-_\+~\/\.]+=*)$/', + '' === $this->tokenType ? '' : preg_quote($this->tokenType).'\s+' + ); + } + + public function extractAccessToken(Request $request): ?string + { + if (!$request->headers->has($this->headerParameter) || !\is_string($header = $request->headers->get($this->headerParameter))) { + return null; + } + + if (preg_match($this->regex, $header, $matches)) { + return $matches[1]; + } + + return null; + } +} diff --git a/vendor/symfony/security-http/AccessToken/Oidc/Exception/InvalidSignatureException.php b/vendor/symfony/security-http/AccessToken/Oidc/Exception/InvalidSignatureException.php new file mode 100644 index 0000000..56f362e --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/Oidc/Exception/InvalidSignatureException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken\Oidc\Exception; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * This exception is thrown when the token signature is invalid. + */ +class InvalidSignatureException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Invalid token signature.'; + } +} diff --git a/vendor/symfony/security-http/AccessToken/Oidc/Exception/MissingClaimException.php b/vendor/symfony/security-http/AccessToken/Oidc/Exception/MissingClaimException.php new file mode 100644 index 0000000..e178f2b --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/Oidc/Exception/MissingClaimException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken\Oidc\Exception; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * This exception is thrown when the user is invalid on the OIDC server (e.g.: "email" property is not in the scope). + */ +class MissingClaimException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Missing claim.'; + } +} diff --git a/vendor/symfony/security-http/AccessToken/Oidc/OidcTokenHandler.php b/vendor/symfony/security-http/AccessToken/Oidc/OidcTokenHandler.php new file mode 100644 index 0000000..37447bc --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/Oidc/OidcTokenHandler.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken\Oidc; + +use Jose\Component\Checker; +use Jose\Component\Checker\ClaimCheckerManager; +use Jose\Component\Core\Algorithm; +use Jose\Component\Core\AlgorithmManager; +use Jose\Component\Core\JWK; +use Jose\Component\Core\JWKSet; +use Jose\Component\Signature\JWSTokenSupport; +use Jose\Component\Signature\JWSVerifier; +use Jose\Component\Signature\Serializer\CompactSerializer; +use Jose\Component\Signature\Serializer\JWSSerializerManager; +use Psr\Clock\ClockInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Clock\Clock; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\AccessToken\Oidc\Exception\InvalidSignatureException; +use Symfony\Component\Security\Http\AccessToken\Oidc\Exception\MissingClaimException; +use Symfony\Component\Security\Http\Authenticator\FallbackUserLoader; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + +/** + * The token handler decodes and validates the token, and retrieves the user identifier from it. + */ +final class OidcTokenHandler implements AccessTokenHandlerInterface +{ + use OidcTrait; + + public function __construct( + private Algorithm|AlgorithmManager $signatureAlgorithm, + private JWK|JWKSet $jwkset, + private string $audience, + private array $issuers, + private string $claim = 'sub', + private ?LoggerInterface $logger = null, + private ClockInterface $clock = new Clock(), + ) { + if ($signatureAlgorithm instanceof Algorithm) { + trigger_deprecation('symfony/security-http', '7.1', 'First argument must be instance of %s, %s given.', AlgorithmManager::class, Algorithm::class); + $this->signatureAlgorithm = new AlgorithmManager([$signatureAlgorithm]); + } + if ($jwkset instanceof JWK) { + trigger_deprecation('symfony/security-http', '7.1', 'Second argument must be instance of %s, %s given.', JWKSet::class, JWK::class); + $this->jwkset = new JWKSet([$jwkset]); + } + } + + public function getUserBadgeFrom(string $accessToken): UserBadge + { + if (!class_exists(JWSVerifier::class) || !class_exists(Checker\HeaderCheckerManager::class)) { + throw new \LogicException('You cannot use the "oidc" token handler since "web-token/jwt-signature" and "web-token/jwt-checker" are not installed. Try running "composer require web-token/jwt-signature web-token/jwt-checker".'); + } + + try { + // Decode the token + $jwsVerifier = new JWSVerifier($this->signatureAlgorithm); + $serializerManager = new JWSSerializerManager([new CompactSerializer()]); + $jws = $serializerManager->unserialize($accessToken); + $claims = json_decode($jws->getPayload(), true); + + // Verify the signature + if (!$jwsVerifier->verifyWithKeySet($jws, $this->jwkset, 0)) { + throw new InvalidSignatureException(); + } + + // Verify the headers + $headerCheckerManager = new Checker\HeaderCheckerManager([ + new Checker\AlgorithmChecker($this->signatureAlgorithm->list()), + ], [ + new JWSTokenSupport(), + ]); + // if this check fails, an InvalidHeaderException is thrown + $headerCheckerManager->check($jws, 0); + + // Verify the claims + $checkers = [ + new Checker\IssuedAtChecker(clock: $this->clock, allowedTimeDrift: 0, protectedHeaderOnly: false), + new Checker\NotBeforeChecker(clock: $this->clock, allowedTimeDrift: 0, protectedHeaderOnly: false), + new Checker\ExpirationTimeChecker(clock: $this->clock, allowedTimeDrift: 0, protectedHeaderOnly: false), + new Checker\AudienceChecker($this->audience), + new Checker\IssuerChecker($this->issuers), + ]; + $claimCheckerManager = new ClaimCheckerManager($checkers); + // if this check fails, an InvalidClaimException is thrown + $claimCheckerManager->check($claims); + + if (empty($claims[$this->claim])) { + throw new MissingClaimException(sprintf('"%s" claim not found.', $this->claim)); + } + + // UserLoader argument can be overridden by a UserProvider on AccessTokenAuthenticator::authenticate + return new UserBadge($claims[$this->claim], new FallbackUserLoader(fn () => $this->createUser($claims)), $claims); + } catch (\Exception $e) { + $this->logger?->error('An error occurred while decoding and validating the token.', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + throw new BadCredentialsException('Invalid credentials.', $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/security-http/AccessToken/Oidc/OidcTrait.php b/vendor/symfony/security-http/AccessToken/Oidc/OidcTrait.php new file mode 100644 index 0000000..d5ab61f --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/Oidc/OidcTrait.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken\Oidc; + +use Symfony\Component\Security\Core\User\OidcUser; + +use function Symfony\Component\String\u; + +/** + * Creates {@see OidcUser} from claims. + * + * @internal + */ +trait OidcTrait +{ + private function createUser(array $claims): OidcUser + { + if (!\function_exists('Symfony\Component\String\u')) { + throw new \LogicException('You cannot use the "OidcUserInfoTokenHandler" since the String component is not installed. Try running "composer require symfony/string".'); + } + + foreach ($claims as $claim => $value) { + unset($claims[$claim]); + if ('' === $value || null === $value) { + continue; + } + $claims[u($claim)->camel()->toString()] = $value; + } + + if (isset($claims['updatedAt']) && '' !== $claims['updatedAt']) { + $claims['updatedAt'] = (new \DateTimeImmutable())->setTimestamp($claims['updatedAt']); + } + + if (\array_key_exists('emailVerified', $claims) && null !== $claims['emailVerified'] && '' !== $claims['emailVerified']) { + $claims['emailVerified'] = (bool) $claims['emailVerified']; + } + + if (\array_key_exists('phoneNumberVerified', $claims) && null !== $claims['phoneNumberVerified'] && '' !== $claims['phoneNumberVerified']) { + $claims['phoneNumberVerified'] = (bool) $claims['phoneNumberVerified']; + } + + return new OidcUser(...$claims); + } +} diff --git a/vendor/symfony/security-http/AccessToken/Oidc/OidcUserInfoTokenHandler.php b/vendor/symfony/security-http/AccessToken/Oidc/OidcUserInfoTokenHandler.php new file mode 100644 index 0000000..9bb17ab --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/Oidc/OidcUserInfoTokenHandler.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken\Oidc; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\AccessToken\Oidc\Exception\MissingClaimException; +use Symfony\Component\Security\Http\Authenticator\FallbackUserLoader; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * The token handler validates the token on the OIDC server and retrieves the user identifier. + */ +final class OidcUserInfoTokenHandler implements AccessTokenHandlerInterface +{ + use OidcTrait; + + public function __construct( + private HttpClientInterface $client, + private ?LoggerInterface $logger = null, + private string $claim = 'sub', + ) { + } + + public function getUserBadgeFrom(string $accessToken): UserBadge + { + try { + // Call the OIDC server to retrieve the user info + // If the token is invalid or expired, the OIDC server will return an error + $claims = $this->client->request('GET', '', [ + 'auth_bearer' => $accessToken, + ])->toArray(); + + if (empty($claims[$this->claim])) { + throw new MissingClaimException(sprintf('"%s" claim not found on OIDC server response.', $this->claim)); + } + + // UserLoader argument can be overridden by a UserProvider on AccessTokenAuthenticator::authenticate + return new UserBadge($claims[$this->claim], new FallbackUserLoader(fn () => $this->createUser($claims)), $claims); + } catch (\Exception $e) { + $this->logger?->error('An error occurred on OIDC server.', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + throw new BadCredentialsException('Invalid credentials.', $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/security-http/AccessToken/QueryAccessTokenExtractor.php b/vendor/symfony/security-http/AccessToken/QueryAccessTokenExtractor.php new file mode 100644 index 0000000..ff55890 --- /dev/null +++ b/vendor/symfony/security-http/AccessToken/QueryAccessTokenExtractor.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\AccessToken; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Extracts a token from a query string parameter. + * + * WARNING! + * Because of the security weaknesses associated with the URI method, + * including the high likelihood that the URL containing the access token will be logged, + * it SHOULD NOT be used unless it is impossible to transport the access token in the + * request header field. + * + * @author Florent Morselli + * + * @see https://datatracker.ietf.org/doc/html/rfc6750#section-2.3 + */ +final class QueryAccessTokenExtractor implements AccessTokenExtractorInterface +{ + public const PARAMETER = 'access_token'; + + public function __construct( + private readonly string $parameter = self::PARAMETER, + ) { + } + + public function extractAccessToken(Request $request): ?string + { + $parameter = $request->query->get($this->parameter); + + return \is_string($parameter) ? $parameter : null; + } +} diff --git a/vendor/symfony/security-http/Attribute/CurrentUser.php b/vendor/symfony/security-http/Attribute/CurrentUser.php new file mode 100644 index 0000000..ed17c48 --- /dev/null +++ b/vendor/symfony/security-http/Attribute/CurrentUser.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Attribute; + +use Symfony\Component\HttpKernel\Attribute\ValueResolver; +use Symfony\Component\Security\Http\Controller\UserValueResolver; + +/** + * Indicates that a controller argument should receive the current logged user. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class CurrentUser extends ValueResolver +{ + /** + * @param bool $disabled Whether this value resolver is disabled, which allows to enable a value resolver globally while disabling it in specific cases + * @param string $resolver The class name of the resolver to use + */ + public function __construct(bool $disabled = false, string $resolver = UserValueResolver::class) + { + parent::__construct($resolver, $disabled); + } +} diff --git a/vendor/symfony/security-http/Attribute/IsCsrfTokenValid.php b/vendor/symfony/security-http/Attribute/IsCsrfTokenValid.php new file mode 100644 index 0000000..ef598df --- /dev/null +++ b/vendor/symfony/security-http/Attribute/IsCsrfTokenValid.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Attribute; + +use Symfony\Component\ExpressionLanguage\Expression; + +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +final class IsCsrfTokenValid +{ + public function __construct( + /** + * Sets the id, or an Expression evaluated to the id, used when generating the token. + */ + public string|Expression $id, + + /** + * Sets the key of the request that contains the actual token value that should be validated. + */ + public ?string $tokenKey = '_token', + ) { + } +} diff --git a/vendor/symfony/security-http/Attribute/IsGranted.php b/vendor/symfony/security-http/Attribute/IsGranted.php new file mode 100644 index 0000000..c69ab01 --- /dev/null +++ b/vendor/symfony/security-http/Attribute/IsGranted.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Attribute; + +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * Checks if user has permission to access to some resource using security roles and voters. + * + * @see https://symfony.com/doc/current/security.html#roles + * + * @author Ryan Weaver + */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +final class IsGranted +{ + /** + * @param string|Expression $attribute The attribute that will be checked against a given authentication token and optional subject + * @param array|string|Expression|null $subject An optional subject - e.g. the current object being voted on + * @param string|null $message A custom message when access is not granted + * @param int|null $statusCode If set, will throw HttpKernel's HttpException with the given $statusCode; if null, Security\Core's AccessDeniedException will be used + * @param int|null $exceptionCode If set, will add the exception code to thrown exception + */ + public function __construct( + public string|Expression $attribute, + public array|string|Expression|null $subject = null, + public ?string $message = null, + public ?int $statusCode = null, + public ?int $exceptionCode = null, + ) { + } +} diff --git a/vendor/symfony/security-http/Authentication/AuthenticationFailureHandlerInterface.php b/vendor/symfony/security-http/Authentication/AuthenticationFailureHandlerInterface.php new file mode 100644 index 0000000..faf5979 --- /dev/null +++ b/vendor/symfony/security-http/Authentication/AuthenticationFailureHandlerInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * Interface for custom authentication failure handlers. + * + * If you want to customize the failure handling process, instead of + * overwriting the respective listener globally, you can set a custom failure + * handler which implements this interface. + * + * @author Johannes M. Schmitt + */ +interface AuthenticationFailureHandlerInterface +{ + /** + * This is called when an interactive authentication attempt fails. + */ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response; +} diff --git a/vendor/symfony/security-http/Authentication/AuthenticationSuccessHandlerInterface.php b/vendor/symfony/security-http/Authentication/AuthenticationSuccessHandlerInterface.php new file mode 100644 index 0000000..e440b36 --- /dev/null +++ b/vendor/symfony/security-http/Authentication/AuthenticationSuccessHandlerInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Interface for a custom authentication success handler. + * + * If you want to customize the success handling process, instead of + * overwriting the respective listener globally, you can set a custom success + * handler which implements this interface. + * + * @author Johannes M. Schmitt + */ +interface AuthenticationSuccessHandlerInterface +{ + /** + * Usually called by AuthenticatorInterface::onAuthenticationSuccess() implementations. + */ + public function onAuthenticationSuccess(Request $request, TokenInterface $token): ?Response; +} diff --git a/vendor/symfony/security-http/Authentication/AuthenticationUtils.php b/vendor/symfony/security-http/Authentication/AuthenticationUtils.php new file mode 100644 index 0000000..e9e29b2 --- /dev/null +++ b/vendor/symfony/security-http/Authentication/AuthenticationUtils.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\SecurityRequestAttributes; + +/** + * Extracts Security Errors from Request. + * + * @author Boris Vujicic + */ +class AuthenticationUtils +{ + private RequestStack $requestStack; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + public function getLastAuthenticationError(bool $clearSession = true): ?AuthenticationException + { + $request = $this->getRequest(); + $authenticationException = null; + + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $authenticationException = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); + } elseif ($request->hasSession() && ($session = $request->getSession())->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $authenticationException = $session->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); + + if ($clearSession) { + $session->remove(SecurityRequestAttributes::AUTHENTICATION_ERROR); + } + } + + return $authenticationException; + } + + public function getLastUsername(): string + { + $request = $this->getRequest(); + + if ($request->attributes->has(SecurityRequestAttributes::LAST_USERNAME)) { + return $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME) ?? ''; + } + + return $request->hasSession() ? ($request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME) ?? '') : ''; + } + + /** + * @throws \LogicException + */ + private function getRequest(): Request + { + $request = $this->requestStack->getCurrentRequest(); + + if (null === $request) { + throw new \LogicException('Request should exist so it can be processed for error.'); + } + + return $request; + } +} diff --git a/vendor/symfony/security-http/Authentication/AuthenticatorManager.php b/vendor/symfony/security-http/Authentication/AuthenticatorManager.php new file mode 100644 index 0000000..b61081c --- /dev/null +++ b/vendor/symfony/security-http/Authentication/AuthenticatorManager.php @@ -0,0 +1,269 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; +use Symfony\Component\Security\Core\Exception\AccountStatusException; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator; +use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\Event\LoginFailureEvent; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\Security\Http\SecurityEvents; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * @author Wouter de Jong + * @author Ryan Weaver + * @author Amaury Leroux de Lens + */ +class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthenticatorInterface +{ + private iterable $authenticators; + private TokenStorageInterface $tokenStorage; + private EventDispatcherInterface $eventDispatcher; + private bool $eraseCredentials; + private ?LoggerInterface $logger; + private string $firewallName; + private bool $hideUserNotFoundExceptions; + private array $requiredBadges; + + /** + * @param iterable $authenticators + */ + public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, bool $hideUserNotFoundExceptions = true, array $requiredBadges = []) + { + $this->authenticators = $authenticators; + $this->tokenStorage = $tokenStorage; + $this->eventDispatcher = $eventDispatcher; + $this->firewallName = $firewallName; + $this->logger = $logger; + $this->eraseCredentials = $eraseCredentials; + $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; + $this->requiredBadges = $requiredBadges; + } + + /** + * @param BadgeInterface[] $badges Optionally, pass some Passport badges to use for the manual login + */ + public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response + { + // create an authentication token for the User + $passport = new SelfValidatingPassport(new UserBadge($user->getUserIdentifier(), fn () => $user), $badges); + $token = $authenticator->createToken($passport, $this->firewallName); + + // announce the authentication token + $token = $this->eventDispatcher->dispatch(new AuthenticationTokenCreatedEvent($token, $passport))->getAuthenticatedToken(); + + // authenticate this in the system + return $this->handleAuthenticationSuccess($token, $passport, $request, $authenticator, $this->tokenStorage->getToken()); + } + + public function supports(Request $request): ?bool + { + if (null !== $this->logger) { + $context = ['firewall_name' => $this->firewallName]; + + if (is_countable($this->authenticators)) { + $context['authenticators'] = \count($this->authenticators); + } + + $this->logger->debug('Checking for authenticator support.', $context); + } + + $authenticators = []; + $skippedAuthenticators = []; + $lazy = true; + foreach ($this->authenticators as $authenticator) { + $this->logger?->debug('Checking support on authenticator.', ['firewall_name' => $this->firewallName, 'authenticator' => $authenticator::class]); + + if (!$authenticator instanceof AuthenticatorInterface) { + throw new \InvalidArgumentException(sprintf('Authenticator "%s" must implement "%s".', get_debug_type($authenticator), AuthenticatorInterface::class)); + } + + if (false !== $supports = $authenticator->supports($request)) { + $authenticators[] = $authenticator; + $lazy = $lazy && null === $supports; + } else { + $this->logger?->debug('Authenticator does not support the request.', ['firewall_name' => $this->firewallName, 'authenticator' => $authenticator::class]); + $skippedAuthenticators[] = $authenticator; + } + } + + if (!$authenticators) { + return false; + } + + $request->attributes->set('_security_authenticators', $authenticators); + $request->attributes->set('_security_skipped_authenticators', $skippedAuthenticators); + + return $lazy ? null : true; + } + + public function authenticateRequest(Request $request): ?Response + { + $authenticators = $request->attributes->get('_security_authenticators'); + $request->attributes->remove('_security_authenticators'); + $request->attributes->remove('_security_skipped_authenticators'); + + if (!$authenticators) { + return null; + } + + return $this->executeAuthenticators($authenticators, $request); + } + + /** + * @param AuthenticatorInterface[] $authenticators + */ + private function executeAuthenticators(array $authenticators, Request $request): ?Response + { + foreach ($authenticators as $authenticator) { + // recheck if the authenticator still supports the listener. supports() is called + // eagerly (before token storage is initialized), whereas authenticate() is called + // lazily (after initialization). + if (false === $authenticator->supports($request)) { + $this->logger?->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); + + continue; + } + + $response = $this->executeAuthenticator($authenticator, $request); + if (null !== $response) { + $this->logger?->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); + + return $response; + } + } + + return null; + } + + private function executeAuthenticator(AuthenticatorInterface $authenticator, Request $request): ?Response + { + $passport = null; + $previousToken = $this->tokenStorage->getToken(); + + try { + // get the passport from the Authenticator + $passport = $authenticator->authenticate($request); + + // check the passport (e.g. password checking) + $event = new CheckPassportEvent($authenticator, $passport); + $this->eventDispatcher->dispatch($event); + + // check if all badges are resolved + $resolvedBadges = []; + foreach ($passport->getBadges() as $badge) { + if (!$badge->isResolved()) { + throw new BadCredentialsException(sprintf('Authentication failed: Security badge "%s" is not resolved, did you forget to register the correct listeners?', get_debug_type($badge))); + } + + $resolvedBadges[] = $badge::class; + } + + $missingRequiredBadges = array_diff($this->requiredBadges, $resolvedBadges); + if ($missingRequiredBadges) { + throw new BadCredentialsException(sprintf('Authentication failed; Some badges marked as required by the firewall config are not available on the passport: "%s".', implode('", "', $missingRequiredBadges))); + } + + // create the authentication token + $authenticatedToken = $authenticator->createToken($passport, $this->firewallName); + + // announce the authentication token + $authenticatedToken = $this->eventDispatcher->dispatch(new AuthenticationTokenCreatedEvent($authenticatedToken, $passport))->getAuthenticatedToken(); + + if (true === $this->eraseCredentials) { + $authenticatedToken->eraseCredentials(); + } + + $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS); + + $this->logger?->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); + } catch (AuthenticationException $e) { + // oh no! Authentication failed! + $response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport); + if ($response instanceof Response) { + return $response; + } + + return null; + } + + // success! (sets the token on the token storage, etc) + $response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator, $previousToken); + if ($response instanceof Response) { + return $response; + } + + $this->logger?->debug('Authenticator set no success response: request continues.', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); + + return null; + } + + private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, Passport $passport, Request $request, AuthenticatorInterface $authenticator, ?TokenInterface $previousToken): ?Response + { + $this->tokenStorage->setToken($authenticatedToken); + + $response = $authenticator->onAuthenticationSuccess($request, $authenticatedToken, $this->firewallName); + if ($authenticator instanceof InteractiveAuthenticatorInterface && $authenticator->isInteractive()) { + $loginEvent = new InteractiveLoginEvent($request, $authenticatedToken); + $this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); + } + + $this->eventDispatcher->dispatch($loginSuccessEvent = new LoginSuccessEvent($authenticator, $passport, $authenticatedToken, $request, $response, $this->firewallName, $previousToken)); + + return $loginSuccessEvent->getResponse(); + } + + /** + * Handles an authentication failure and returns the Response for the authenticator. + */ + private function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator, ?Passport $passport): ?Response + { + $this->logger?->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); + + // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status) + // to prevent user enumeration via response content comparison + if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UserNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) { + $authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException); + } + + $response = $authenticator->onAuthenticationFailure($request, $authenticationException); + if (null !== $response && null !== $this->logger) { + $this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); + } + + $this->eventDispatcher->dispatch($loginFailureEvent = new LoginFailureEvent($authenticationException, $authenticator, $request, $response, $this->firewallName, $passport)); + + // returning null is ok, it means they want the request to continue + return $loginFailureEvent->getResponse(); + } +} diff --git a/vendor/symfony/security-http/Authentication/AuthenticatorManagerInterface.php b/vendor/symfony/security-http/Authentication/AuthenticatorManagerInterface.php new file mode 100644 index 0000000..4bac7b7 --- /dev/null +++ b/vendor/symfony/security-http/Authentication/AuthenticatorManagerInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; + +/** + * @author Wouter de Jong + * @author Ryan Weaver + */ +interface AuthenticatorManagerInterface +{ + /** + * Called to see if authentication should be attempted on this request. + * + * @see FirewallListenerInterface::supports() + */ + public function supports(Request $request): ?bool; + + /** + * Tries to authenticate the request and returns a response - if any authenticator set one. + */ + public function authenticateRequest(Request $request): ?Response; +} diff --git a/vendor/symfony/security-http/Authentication/CustomAuthenticationFailureHandler.php b/vendor/symfony/security-http/Authentication/CustomAuthenticationFailureHandler.php new file mode 100644 index 0000000..8fc4ba3 --- /dev/null +++ b/vendor/symfony/security-http/Authentication/CustomAuthenticationFailureHandler.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * @author Fabien Potencier + */ +class CustomAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface +{ + private AuthenticationFailureHandlerInterface $handler; + + /** + * @param array $options Options for processing a successful authentication attempt + */ + public function __construct(AuthenticationFailureHandlerInterface $handler, array $options) + { + $this->handler = $handler; + if (method_exists($handler, 'setOptions')) { + $this->handler->setOptions($options); + } + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + return $this->handler->onAuthenticationFailure($request, $exception); + } +} diff --git a/vendor/symfony/security-http/Authentication/CustomAuthenticationSuccessHandler.php b/vendor/symfony/security-http/Authentication/CustomAuthenticationSuccessHandler.php new file mode 100644 index 0000000..bfa3bda --- /dev/null +++ b/vendor/symfony/security-http/Authentication/CustomAuthenticationSuccessHandler.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * @author Fabien Potencier + */ +class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface +{ + private AuthenticationSuccessHandlerInterface $handler; + + /** + * @param array $options Options for processing a successful authentication attempt + */ + public function __construct(AuthenticationSuccessHandlerInterface $handler, array $options, string $firewallName) + { + $this->handler = $handler; + if (method_exists($handler, 'setOptions')) { + $this->handler->setOptions($options); + } + + if (method_exists($handler, 'setFirewallName')) { + $this->handler->setFirewallName($firewallName); + } + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token): ?Response + { + return $this->handler->onAuthenticationSuccess($request, $token); + } +} diff --git a/vendor/symfony/security-http/Authentication/DefaultAuthenticationFailureHandler.php b/vendor/symfony/security-http/Authentication/DefaultAuthenticationFailureHandler.php new file mode 100644 index 0000000..a2eabbe --- /dev/null +++ b/vendor/symfony/security-http/Authentication/DefaultAuthenticationFailureHandler.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; + +/** + * Class with the default authentication failure handling logic. + * + * Can be optionally be extended from by the developer to alter the behavior + * while keeping the default behavior. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + * @author Alexander + */ +class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface +{ + protected HttpKernelInterface $httpKernel; + protected HttpUtils $httpUtils; + protected array $options; + protected ?LoggerInterface $logger; + protected array $defaultOptions = [ + 'failure_path' => null, + 'failure_forward' => false, + 'login_path' => '/login', + 'failure_path_parameter' => '_failure_path', + ]; + + public function __construct(HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options = [], ?LoggerInterface $logger = null) + { + $this->httpKernel = $httpKernel; + $this->httpUtils = $httpUtils; + $this->logger = $logger; + $this->setOptions($options); + } + + /** + * Gets the options. + */ + public function getOptions(): array + { + return $this->options; + } + + public function setOptions(array $options): void + { + $this->options = array_merge($this->defaultOptions, $options); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + $options = $this->options; + $failureUrl = ParameterBagUtils::getRequestParameterValue($request, $options['failure_path_parameter']); + + if (\is_string($failureUrl) && (str_starts_with($failureUrl, '/') || str_starts_with($failureUrl, 'http'))) { + $options['failure_path'] = $failureUrl; + } elseif ($this->logger && $failureUrl) { + $this->logger->debug(sprintf('Ignoring query parameter "%s": not a valid URL.', $options['failure_path_parameter'])); + } + + $options['failure_path'] ??= $options['login_path']; + + if ($options['failure_forward']) { + $this->logger?->debug('Authentication failure, forward triggered.', ['failure_path' => $options['failure_path']]); + + $subRequest = $this->httpUtils->createRequest($request, $options['failure_path']); + $subRequest->attributes->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $exception); + + return $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + } + + $this->logger?->debug('Authentication failure, redirect triggered.', ['failure_path' => $options['failure_path']]); + + if (!$request->attributes->getBoolean('_stateless')) { + $request->getSession()->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $exception); + } + + return $this->httpUtils->createRedirectResponse($request, $options['failure_path']); + } +} diff --git a/vendor/symfony/security-http/Authentication/DefaultAuthenticationSuccessHandler.php b/vendor/symfony/security-http/Authentication/DefaultAuthenticationSuccessHandler.php new file mode 100644 index 0000000..a491adb --- /dev/null +++ b/vendor/symfony/security-http/Authentication/DefaultAuthenticationSuccessHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Component\Security\Http\Util\TargetPathTrait; + +/** + * Class with the default authentication success handling logic. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + * @author Alexander + */ +class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface +{ + use TargetPathTrait; + + protected HttpUtils $httpUtils; + protected array $options; + protected ?LoggerInterface $logger; + protected ?string $firewallName = null; + protected array $defaultOptions = [ + 'always_use_default_target_path' => false, + 'default_target_path' => '/', + 'login_path' => '/login', + 'target_path_parameter' => '_target_path', + 'use_referer' => false, + ]; + + /** + * @param array $options Options for processing a successful authentication attempt + */ + public function __construct(HttpUtils $httpUtils, array $options = [], ?LoggerInterface $logger = null) + { + $this->httpUtils = $httpUtils; + $this->logger = $logger; + $this->setOptions($options); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token): ?Response + { + return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request)); + } + + /** + * Gets the options. + */ + public function getOptions(): array + { + return $this->options; + } + + public function setOptions(array $options): void + { + $this->options = array_merge($this->defaultOptions, $options); + } + + public function getFirewallName(): ?string + { + return $this->firewallName; + } + + public function setFirewallName(string $firewallName): void + { + $this->firewallName = $firewallName; + } + + /** + * Builds the target URL according to the defined options. + */ + protected function determineTargetUrl(Request $request): string + { + if ($this->options['always_use_default_target_path']) { + return $this->options['default_target_path']; + } + + $targetUrl = ParameterBagUtils::getRequestParameterValue($request, $this->options['target_path_parameter']); + + if (\is_string($targetUrl) && (str_starts_with($targetUrl, '/') || str_starts_with($targetUrl, 'http'))) { + return $targetUrl; + } + + if ($this->logger && $targetUrl) { + $this->logger->debug(sprintf('Ignoring query parameter "%s": not a valid URL.', $this->options['target_path_parameter'])); + } + + $firewallName = $this->getFirewallName(); + if (null !== $firewallName && !$request->attributes->getBoolean('_stateless') && $targetUrl = $this->getTargetPath($request->getSession(), $firewallName)) { + $this->removeTargetPath($request->getSession(), $firewallName); + + return $targetUrl; + } + + if ($this->options['use_referer'] && $targetUrl = $request->headers->get('Referer')) { + if (false !== $pos = strpos($targetUrl, '?')) { + $targetUrl = substr($targetUrl, 0, $pos); + } + if ($targetUrl && $targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) { + return $targetUrl; + } + } + + return $this->options['default_target_path']; + } +} diff --git a/vendor/symfony/security-http/Authentication/UserAuthenticatorInterface.php b/vendor/symfony/security-http/Authentication/UserAuthenticatorInterface.php new file mode 100644 index 0000000..a59a792 --- /dev/null +++ b/vendor/symfony/security-http/Authentication/UserAuthenticatorInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authentication; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; + +/** + * @author Wouter de Jong + */ +interface UserAuthenticatorInterface +{ + /** + * Convenience method to programmatically login a user and return a + * Response *if any* for success. + * + * @param BadgeInterface[] $badges Optionally, pass some Passport badges to use for the manual login + */ + public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response; +} diff --git a/vendor/symfony/security-http/Authenticator/AbstractAuthenticator.php b/vendor/symfony/security-http/Authenticator/AbstractAuthenticator.php new file mode 100644 index 0000000..a7cee7a --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/AbstractAuthenticator.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; + +/** + * An optional base class that creates the necessary tokens for you. + * + * @author Ryan Weaver + */ +abstract class AbstractAuthenticator implements AuthenticatorInterface +{ + /** + * Shortcut to create a PostAuthenticationToken for you, if you don't really + * care about which authenticated token you're using. + */ + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new PostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); + } +} diff --git a/vendor/symfony/security-http/Authenticator/AbstractLoginFormAuthenticator.php b/vendor/symfony/security-http/Authenticator/AbstractLoginFormAuthenticator.php new file mode 100644 index 0000000..21835bd --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/AbstractLoginFormAuthenticator.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\SecurityRequestAttributes; + +/** + * A base class to make form login authentication easier! + * + * @author Ryan Weaver + */ +abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface +{ + /** + * Return the URL to the login page. + */ + abstract protected function getLoginUrl(Request $request): string; + + /** + * Override to change the request conditions that have to be + * matched in order to handle the login form submit. + * + * This default implementation handles all POST requests to the + * login path (@see getLoginUrl()). + */ + public function supports(Request $request): bool + { + return $request->isMethod('POST') && $this->getLoginUrl($request) === $request->getBaseUrl().$request->getPathInfo(); + } + + /** + * Override to change what happens after a bad username/password is submitted. + */ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + if ($request->hasSession()) { + $request->getSession()->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $exception); + } + + $url = $this->getLoginUrl($request); + + return new RedirectResponse($url); + } + + /** + * Override to control what happens when the user hits a secure page + * but isn't logged in yet. + */ + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + $url = $this->getLoginUrl($request); + + return new RedirectResponse($url); + } + + public function isInteractive(): bool + { + return true; + } +} diff --git a/vendor/symfony/security-http/Authenticator/AbstractPreAuthenticatedAuthenticator.php b/vendor/symfony/security-http/Authenticator/AbstractPreAuthenticatedAuthenticator.php new file mode 100644 index 0000000..535a539 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/AbstractPreAuthenticatedAuthenticator.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; + +/** + * The base authenticator for authenticators to use pre-authenticated + * requests (e.g. using certificates). + * + * @author Wouter de Jong + * @author Fabien Potencier + * + * @internal + */ +abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface +{ + private UserProviderInterface $userProvider; + private TokenStorageInterface $tokenStorage; + private string $firewallName; + private ?LoggerInterface $logger; + + public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, ?LoggerInterface $logger = null) + { + $this->userProvider = $userProvider; + $this->tokenStorage = $tokenStorage; + $this->firewallName = $firewallName; + $this->logger = $logger; + } + + /** + * Returns the username of the pre-authenticated user. + * + * This authenticator is skipped if null is returned or a custom + * BadCredentialsException is thrown. + */ + abstract protected function extractUsername(Request $request): ?string; + + public function supports(Request $request): ?bool + { + try { + $username = $this->extractUsername($request); + } catch (BadCredentialsException $e) { + $this->clearToken($e); + + $this->logger?->debug('Skipping pre-authenticated authenticator as a BadCredentialsException is thrown.', ['exception' => $e, 'authenticator' => static::class]); + + return false; + } + + if (null === $username) { + $this->logger?->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => static::class]); + + return false; + } + + // do not overwrite already stored tokens from the same user (i.e. from the session) + $token = $this->tokenStorage->getToken(); + + if ($token instanceof PreAuthenticatedToken && $this->firewallName === $token->getFirewallName() && $token->getUserIdentifier() === $username) { + $this->logger?->debug('Skipping pre-authenticated authenticator as the user already has an existing session.', ['authenticator' => static::class]); + + return false; + } + + $request->attributes->set('_pre_authenticated_username', $username); + + return true; + } + + public function authenticate(Request $request): Passport + { + $userBadge = new UserBadge($request->attributes->get('_pre_authenticated_username'), $this->userProvider->loadUserByIdentifier(...)); + + return new SelfValidatingPassport($userBadge, [new PreAuthenticatedUserBadge()]); + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new PreAuthenticatedToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; // let the original request continue + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $this->clearToken($exception); + + return null; + } + + public function isInteractive(): bool + { + return true; + } + + private function clearToken(AuthenticationException $exception): void + { + $token = $this->tokenStorage->getToken(); + if ($token instanceof PreAuthenticatedToken && $this->firewallName === $token->getFirewallName()) { + $this->tokenStorage->setToken(null); + + $this->logger?->info('Cleared pre-authenticated token due to an exception.', ['exception' => $exception]); + } + } +} diff --git a/vendor/symfony/security-http/Authenticator/AccessTokenAuthenticator.php b/vendor/symfony/security-http/Authenticator/AccessTokenAuthenticator.php new file mode 100644 index 0000000..4acadf4 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/AccessTokenAuthenticator.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\AccessToken\AccessTokenExtractorInterface; +use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Provides an implementation of the RFC6750 of an authentication via + * an access token. + * + * @author Florent Morselli + */ +class AccessTokenAuthenticator implements AuthenticatorInterface +{ + private ?TranslatorInterface $translator = null; + + public function __construct( + private readonly AccessTokenHandlerInterface $accessTokenHandler, + private readonly AccessTokenExtractorInterface $accessTokenExtractor, + private readonly ?UserProviderInterface $userProvider = null, + private readonly ?AuthenticationSuccessHandlerInterface $successHandler = null, + private readonly ?AuthenticationFailureHandlerInterface $failureHandler = null, + private readonly ?string $realm = null, + ) { + } + + public function supports(Request $request): ?bool + { + return null === $this->accessTokenExtractor->extractAccessToken($request) ? false : null; + } + + public function authenticate(Request $request): Passport + { + $accessToken = $this->accessTokenExtractor->extractAccessToken($request); + if (!$accessToken) { + throw new BadCredentialsException('Invalid credentials.'); + } + + $userBadge = $this->accessTokenHandler->getUserBadgeFrom($accessToken); + if ($this->userProvider && (null === $userBadge->getUserLoader() || $userBadge->getUserLoader() instanceof FallbackUserLoader)) { + $userBadge->setUserLoader($this->userProvider->loadUserByIdentifier(...)); + } + + return new SelfValidatingPassport($userBadge); + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new PostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return $this->successHandler?->onAuthenticationSuccess($request, $token); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + if (null !== $this->failureHandler) { + return $this->failureHandler->onAuthenticationFailure($request, $exception); + } + + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security'); + } else { + $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData()); + } + + return new Response( + null, + Response::HTTP_UNAUTHORIZED, + ['WWW-Authenticate' => $this->getAuthenticateHeader($errorMessage)] + ); + } + + public function setTranslator(?TranslatorInterface $translator): void + { + $this->translator = $translator; + } + + /** + * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3 + */ + private function getAuthenticateHeader(?string $errorDescription = null): string + { + $data = [ + 'realm' => $this->realm, + 'error' => 'invalid_token', + 'error_description' => $errorDescription, + ]; + $values = []; + foreach ($data as $k => $v) { + if (null === $v || '' === $v) { + continue; + } + $values[] = sprintf('%s="%s"', $k, $v); + } + + return sprintf('Bearer %s', implode(',', $values)); + } +} diff --git a/vendor/symfony/security-http/Authenticator/AuthenticatorInterface.php b/vendor/symfony/security-http/Authenticator/AuthenticatorInterface.php new file mode 100644 index 0000000..124e0bf --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/AuthenticatorInterface.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; + +/** + * The interface for all authenticators. + * + * @author Ryan Weaver + * @author Amaury Leroux de Lens + * @author Wouter de Jong + */ +interface AuthenticatorInterface +{ + /** + * Does the authenticator support the given Request? + * + * If this returns true, authenticate() will be called. If false, the authenticator will be skipped. + * + * Returning null means authenticate() can be called lazily when accessing the token storage. + */ + public function supports(Request $request): ?bool; + + /** + * Create a passport for the current request. + * + * The passport contains the user, credentials and any additional information + * that has to be checked by the Symfony Security system. For example, a login + * form authenticator will probably return a passport containing the user, the + * presented password and the CSRF token value. + * + * You may throw any AuthenticationException in this method in case of error (e.g. + * a UserNotFoundException when the user cannot be found). + * + * @throws AuthenticationException + */ + public function authenticate(Request $request): Passport; + + /** + * Create an authenticated token for the given user. + * + * If you don't care about which token class is used or don't really + * understand what a "token" is, you can skip this method by extending + * the AbstractAuthenticator class from your authenticator. + * + * @see AbstractAuthenticator + * + * @param Passport $passport The passport returned from authenticate() + */ + public function createToken(Passport $passport, string $firewallName): TokenInterface; + + /** + * Called when authentication executed and was successful! + * + * This should return the Response sent back to the user, like a + * RedirectResponse to the last page they visited. + * + * If you return null, the current request will continue, and the user + * will be authenticated. This makes sense, for example, with an API. + */ + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response; + + /** + * Called when authentication executed, but failed (e.g. wrong username password). + * + * This should return the Response sent back to the user, like a + * RedirectResponse to the login page or a 403 response. + * + * If you return null, the request will continue, but the user will + * not be authenticated. This is probably not what you want to do. + */ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response; +} diff --git a/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticator.php b/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticator.php new file mode 100644 index 0000000..34c3c62 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticator.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException; +use Symfony\Component\VarDumper\Caster\ClassStub; + +/** + * Collects info about an authenticator for debugging purposes. + * + * @author Robin Chalas + */ +final class TraceableAuthenticator implements AuthenticatorInterface, InteractiveAuthenticatorInterface, AuthenticationEntryPointInterface +{ + private ?Passport $passport = null; + private ?float $duration = null; + private ClassStub|string $stub; + private ?bool $authenticated = null; + + public function __construct(private AuthenticatorInterface $authenticator) + { + } + + public function getInfo(): array + { + return [ + 'supports' => true, + 'passport' => $this->passport, + 'duration' => $this->duration, + 'stub' => $this->stub ??= class_exists(ClassStub::class) ? new ClassStub($this->authenticator::class) : $this->authenticator::class, + 'authenticated' => $this->authenticated, + 'badges' => array_map( + static function (BadgeInterface $badge): array { + return [ + 'stub' => class_exists(ClassStub::class) ? new ClassStub($badge::class) : $badge::class, + 'resolved' => $badge->isResolved(), + ]; + }, + $this->passport?->getBadges() ?? [], + ), + ]; + } + + public function supports(Request $request): ?bool + { + return $this->authenticator->supports($request); + } + + public function authenticate(Request $request): Passport + { + $startTime = microtime(true); + $this->passport = $this->authenticator->authenticate($request); + $this->duration = microtime(true) - $startTime; + + return $this->passport; + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return $this->authenticator->createToken($passport, $firewallName); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + $this->authenticated = true; + + return $this->authenticator->onAuthenticationSuccess($request, $token, $firewallName); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $this->authenticated = false; + + return $this->authenticator->onAuthenticationFailure($request, $exception); + } + + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + if (!$this->authenticator instanceof AuthenticationEntryPointInterface) { + throw new NotAnEntryPointException(); + } + + return $this->authenticator->start($request, $authException); + } + + public function isInteractive(): bool + { + return $this->authenticator instanceof InteractiveAuthenticatorInterface && $this->authenticator->isInteractive(); + } + + public function getAuthenticator(): AuthenticatorInterface + { + return $this->authenticator; + } + + public function __call($method, $args): mixed + { + return $this->authenticator->{$method}(...$args); + } +} diff --git a/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php b/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php new file mode 100644 index 0000000..eefba06 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Http\Firewall\AbstractListener; +use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Decorates the AuthenticatorManagerListener to collect information about security authenticators. + * + * @author Robin Chalas + */ +final class TraceableAuthenticatorManagerListener extends AbstractListener implements ResetInterface +{ + private AuthenticatorManagerListener $authenticationManagerListener; + private array $authenticatorsInfo = []; + private bool $hasVardumper; + + public function __construct(AuthenticatorManagerListener $authenticationManagerListener) + { + $this->authenticationManagerListener = $authenticationManagerListener; + $this->hasVardumper = class_exists(ClassStub::class); + } + + public function supports(Request $request): ?bool + { + return $this->authenticationManagerListener->supports($request); + } + + public function authenticate(RequestEvent $event): void + { + $request = $event->getRequest(); + + if (!$authenticators = $request->attributes->get('_security_authenticators')) { + return; + } + + foreach ($request->attributes->get('_security_skipped_authenticators') as $skippedAuthenticator) { + $this->authenticatorsInfo[] = [ + 'supports' => false, + 'stub' => $this->hasVardumper ? new ClassStub($skippedAuthenticator::class) : $skippedAuthenticator::class, + 'passport' => null, + 'duration' => 0, + 'authenticated' => null, + 'badges' => [], + ]; + } + + foreach ($authenticators as $key => $authenticator) { + $authenticators[$key] = new TraceableAuthenticator($authenticator); + } + + $request->attributes->set('_security_authenticators', $authenticators); + + $this->authenticationManagerListener->authenticate($event); + + foreach ($authenticators as $authenticator) { + $this->authenticatorsInfo[] = $authenticator->getInfo(); + } + } + + public function getAuthenticatorManagerListener(): AuthenticatorManagerListener + { + return $this->authenticationManagerListener; + } + + public function getAuthenticatorsInfo(): array + { + return $this->authenticatorsInfo; + } + + public function reset(): void + { + $this->authenticatorsInfo = []; + } +} diff --git a/vendor/symfony/security-http/Authenticator/FallbackUserLoader.php b/vendor/symfony/security-http/Authenticator/FallbackUserLoader.php new file mode 100644 index 0000000..6539278 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/FallbackUserLoader.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * This wrapper serves as a marker interface to indicate badge user loaders that should not be overridden by the + * default user provider. + * + * @internal + */ +final class FallbackUserLoader +{ + public function __construct(private $inner) + { + } + + public function __invoke(mixed ...$args): ?UserInterface + { + return ($this->inner)(...$args); + } +} diff --git a/vendor/symfony/security-http/Authenticator/FormLoginAuthenticator.php b/vendor/symfony/security-http/Authenticator/FormLoginAuthenticator.php new file mode 100644 index 0000000..7109ff2 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/FormLoginAuthenticator.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; + +/** + * @author Wouter de Jong + * @author Fabien Potencier + * + * @final + */ +class FormLoginAuthenticator extends AbstractLoginFormAuthenticator +{ + private HttpUtils $httpUtils; + private UserProviderInterface $userProvider; + private AuthenticationSuccessHandlerInterface $successHandler; + private AuthenticationFailureHandlerInterface $failureHandler; + private array $options; + private HttpKernelInterface $httpKernel; + + public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options) + { + $this->httpUtils = $httpUtils; + $this->userProvider = $userProvider; + $this->successHandler = $successHandler; + $this->failureHandler = $failureHandler; + $this->options = array_merge([ + 'username_parameter' => '_username', + 'password_parameter' => '_password', + 'check_path' => '/login_check', + 'post_only' => true, + 'form_only' => false, + 'enable_csrf' => false, + 'csrf_parameter' => '_csrf_token', + 'csrf_token_id' => 'authenticate', + ], $options); + } + + protected function getLoginUrl(Request $request): string + { + return $this->httpUtils->generateUri($request, $this->options['login_path']); + } + + public function supports(Request $request): bool + { + return ($this->options['post_only'] ? $request->isMethod('POST') : true) + && $this->httpUtils->checkRequestPath($request, $this->options['check_path']) + && ($this->options['form_only'] ? 'form' === $request->getContentTypeFormat() : true); + } + + public function authenticate(Request $request): Passport + { + $credentials = $this->getCredentials($request); + + $userBadge = new UserBadge($credentials['username'], $this->userProvider->loadUserByIdentifier(...)); + $passport = new Passport($userBadge, new PasswordCredentials($credentials['password']), [new RememberMeBadge()]); + + if ($this->options['enable_csrf']) { + $passport->addBadge(new CsrfTokenBadge($this->options['csrf_token_id'], $credentials['csrf_token'])); + } + + if ($this->userProvider instanceof PasswordUpgraderInterface) { + $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider)); + } + + return $passport; + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new UsernamePasswordToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return $this->successHandler->onAuthenticationSuccess($request, $token); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + return $this->failureHandler->onAuthenticationFailure($request, $exception); + } + + private function getCredentials(Request $request): array + { + $credentials = []; + $credentials['csrf_token'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); + + if ($this->options['post_only']) { + $credentials['username'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']); + $credentials['password'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']) ?? ''; + } else { + $credentials['username'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']); + $credentials['password'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']) ?? ''; + } + + if (!\is_string($credentials['username']) && !$credentials['username'] instanceof \Stringable) { + throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($credentials['username']))); + } + + $credentials['username'] = trim($credentials['username']); + + if ('' === $credentials['username']) { + throw new BadCredentialsException(sprintf('The key "%s" must be a non-empty string.', $this->options['username_parameter'])); + } + + $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $credentials['username']); + + if (!\is_string($credentials['password']) && (!\is_object($credentials['password']) || !method_exists($credentials['password'], '__toString'))) { + throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['password_parameter'], \gettype($credentials['password']))); + } + + if ('' === (string) $credentials['password']) { + throw new BadCredentialsException(sprintf('The key "%s" must be a non-empty string.', $this->options['password_parameter'])); + } + + if (!\is_string($credentials['csrf_token'] ?? '') && (!\is_object($credentials['csrf_token']) || !method_exists($credentials['csrf_token'], '__toString'))) { + throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['csrf_parameter'], \gettype($credentials['csrf_token']))); + } + + return $credentials; + } + + public function setHttpKernel(HttpKernelInterface $httpKernel): void + { + $this->httpKernel = $httpKernel; + } + + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + if (!$this->options['use_forward']) { + return parent::start($request, $authException); + } + + $subRequest = $this->httpUtils->createRequest($request, $this->options['login_path']); + $response = $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + if (200 === $response->getStatusCode()) { + $response->setStatusCode(401); + } + + return $response; + } +} diff --git a/vendor/symfony/security-http/Authenticator/HttpBasicAuthenticator.php b/vendor/symfony/security-http/Authenticator/HttpBasicAuthenticator.php new file mode 100644 index 0000000..f143674 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/HttpBasicAuthenticator.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; + +/** + * @author Wouter de Jong + * @author Fabien Potencier + * + * @final + */ +class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface +{ + private string $realmName; + private UserProviderInterface $userProvider; + private ?LoggerInterface $logger; + + public function __construct(string $realmName, UserProviderInterface $userProvider, ?LoggerInterface $logger = null) + { + $this->realmName = $realmName; + $this->userProvider = $userProvider; + $this->logger = $logger; + } + + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + $response = new Response(); + $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName)); + $response->setStatusCode(401); + + return $response; + } + + public function supports(Request $request): ?bool + { + return $request->headers->has('PHP_AUTH_USER'); + } + + public function authenticate(Request $request): Passport + { + $username = $request->headers->get('PHP_AUTH_USER'); + $password = $request->headers->get('PHP_AUTH_PW', ''); + + $userBadge = new UserBadge($username, $this->userProvider->loadUserByIdentifier(...)); + $passport = new Passport($userBadge, new PasswordCredentials($password)); + + if ($this->userProvider instanceof PasswordUpgraderInterface) { + $passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider)); + } + + return $passport; + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new UsernamePasswordToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $this->logger?->info('Basic authentication failed for user.', ['username' => $request->headers->get('PHP_AUTH_USER'), 'exception' => $exception]); + + return $this->start($request, $exception); + } +} diff --git a/vendor/symfony/security-http/Authenticator/InteractiveAuthenticatorInterface.php b/vendor/symfony/security-http/Authenticator/InteractiveAuthenticatorInterface.php new file mode 100644 index 0000000..ce125f0 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/InteractiveAuthenticatorInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +/** + * This is an extension of the authenticator interface that may + * be used by interactive authenticators. + * + * Interactive login requires explicit user action (e.g. a login + * form). Implementing this interface will dispatch the InteractiveLoginEvent + * upon successful login. + * + * @author Wouter de Jong + */ +interface InteractiveAuthenticatorInterface extends AuthenticatorInterface +{ + /** + * Should return true to make this authenticator perform + * an interactive login. + */ + public function isInteractive(): bool; +} diff --git a/vendor/symfony/security-http/Authenticator/JsonLoginAuthenticator.php b/vendor/symfony/security-http/Authenticator/JsonLoginAuthenticator.php new file mode 100644 index 0000000..4c15232 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/JsonLoginAuthenticator.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\PropertyAccess\Exception\AccessException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Provides a stateless implementation of an authentication via + * a JSON document composed of a username and a password. + * + * @author Kévin Dunglas + * @author Wouter de Jong + * + * @final + */ +class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface +{ + private array $options; + private HttpUtils $httpUtils; + private UserProviderInterface $userProvider; + private PropertyAccessorInterface $propertyAccessor; + private ?AuthenticationSuccessHandlerInterface $successHandler; + private ?AuthenticationFailureHandlerInterface $failureHandler; + private ?TranslatorInterface $translator = null; + + public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, ?AuthenticationSuccessHandlerInterface $successHandler = null, ?AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], ?PropertyAccessorInterface $propertyAccessor = null) + { + $this->options = array_merge(['username_path' => 'username', 'password_path' => 'password'], $options); + $this->httpUtils = $httpUtils; + $this->successHandler = $successHandler; + $this->failureHandler = $failureHandler; + $this->userProvider = $userProvider; + $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + } + + public function supports(Request $request): ?bool + { + if ( + !str_contains($request->getRequestFormat() ?? '', 'json') + && !str_contains($request->getContentTypeFormat() ?? '', 'json') + ) { + return false; + } + + if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) { + return false; + } + + return true; + } + + public function authenticate(Request $request): Passport + { + try { + $data = json_decode($request->getContent()); + if (!$data instanceof \stdClass) { + throw new BadRequestHttpException('Invalid JSON.'); + } + + $credentials = $this->getCredentials($data); + } catch (BadRequestHttpException $e) { + $request->setRequestFormat('json'); + + throw $e; + } + + $userBadge = new UserBadge($credentials['username'], $this->userProvider->loadUserByIdentifier(...)); + $passport = new Passport($userBadge, new PasswordCredentials($credentials['password']), [new RememberMeBadge((array) $data)]); + + if ($this->userProvider instanceof PasswordUpgraderInterface) { + $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider)); + } + + return $passport; + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new UsernamePasswordToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + if (null === $this->successHandler) { + return null; // let the original request continue + } + + return $this->successHandler->onAuthenticationSuccess($request, $token); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + if (null === $this->failureHandler) { + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security'); + } else { + $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData()); + } + + return new JsonResponse(['error' => $errorMessage], JsonResponse::HTTP_UNAUTHORIZED); + } + + return $this->failureHandler->onAuthenticationFailure($request, $exception); + } + + public function isInteractive(): bool + { + return true; + } + + public function setTranslator(TranslatorInterface $translator): void + { + $this->translator = $translator; + } + + private function getCredentials(\stdClass $data): array + { + $credentials = []; + try { + $credentials['username'] = $this->propertyAccessor->getValue($data, $this->options['username_path']); + + if (!\is_string($credentials['username']) || '' === $credentials['username']) { + throw new BadRequestHttpException(sprintf('The key "%s" must be a non-empty string.', $this->options['username_path'])); + } + } catch (AccessException $e) { + throw new BadRequestHttpException(sprintf('The key "%s" must be provided.', $this->options['username_path']), $e); + } + + try { + $credentials['password'] = $this->propertyAccessor->getValue($data, $this->options['password_path']); + $this->propertyAccessor->setValue($data, $this->options['password_path'], null); + + if (!\is_string($credentials['password']) || '' === $credentials['password']) { + throw new BadRequestHttpException(sprintf('The key "%s" must be a non-empty string.', $this->options['password_path'])); + } + } catch (AccessException $e) { + throw new BadRequestHttpException(sprintf('The key "%s" must be provided.', $this->options['password_path']), $e); + } + + return $credentials; + } +} diff --git a/vendor/symfony/security-http/Authenticator/LoginLinkAuthenticator.php b/vendor/symfony/security-http/Authenticator/LoginLinkAuthenticator.php new file mode 100644 index 0000000..a39676c --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/LoginLinkAuthenticator.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\LoginLink\Exception\InvalidLoginLinkAuthenticationException; +use Symfony\Component\Security\Http\LoginLink\Exception\InvalidLoginLinkExceptionInterface; +use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; + +/** + * @author Ryan Weaver + */ +final class LoginLinkAuthenticator extends AbstractAuthenticator implements InteractiveAuthenticatorInterface +{ + private LoginLinkHandlerInterface $loginLinkHandler; + private HttpUtils $httpUtils; + private AuthenticationSuccessHandlerInterface $successHandler; + private AuthenticationFailureHandlerInterface $failureHandler; + private array $options; + + public function __construct(LoginLinkHandlerInterface $loginLinkHandler, HttpUtils $httpUtils, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options) + { + $this->loginLinkHandler = $loginLinkHandler; + $this->httpUtils = $httpUtils; + $this->successHandler = $successHandler; + $this->failureHandler = $failureHandler; + $this->options = $options + ['check_post_only' => false]; + } + + public function supports(Request $request): ?bool + { + return ($this->options['check_post_only'] ? $request->isMethod('POST') : true) + && $this->httpUtils->checkRequestPath($request, $this->options['check_route']); + } + + public function authenticate(Request $request): Passport + { + if (!$username = $request->get('user')) { + throw new InvalidLoginLinkAuthenticationException('Missing user from link.'); + } + + $userBadge = new UserBadge($username, function () use ($request) { + try { + $user = $this->loginLinkHandler->consumeLoginLink($request); + } catch (InvalidLoginLinkExceptionInterface $e) { + throw new InvalidLoginLinkAuthenticationException('Login link could not be validated.', 0, $e); + } + + return $user; + }); + + return new SelfValidatingPassport($userBadge, [new RememberMeBadge()]); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return $this->successHandler->onAuthenticationSuccess($request, $token); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + return $this->failureHandler->onAuthenticationFailure($request, $exception); + } + + public function isInteractive(): bool + { + return true; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Badge/BadgeInterface.php b/vendor/symfony/security-http/Authenticator/Passport/Badge/BadgeInterface.php new file mode 100644 index 0000000..009449f --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Badge/BadgeInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge; + +/** + * Passport badges allow to add more information to a passport (e.g. a CSRF token). + * + * @author Wouter de Jong + */ +interface BadgeInterface +{ + /** + * Checks if this badge is resolved by the security system. + * + * After authentication, all badges must return `true` in this method in order + * for the authentication to succeed. + */ + public function isResolved(): bool; +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Badge/CsrfTokenBadge.php b/vendor/symfony/security-http/Authenticator/Passport/Badge/CsrfTokenBadge.php new file mode 100644 index 0000000..52aeafe --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Badge/CsrfTokenBadge.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge; + +use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener; + +/** + * Adds automatic CSRF tokens checking capabilities to this authenticator. + * + * @see CsrfProtectionListener + * + * @author Wouter de Jong + * + * @final + */ +class CsrfTokenBadge implements BadgeInterface +{ + private bool $resolved = false; + private string $csrfTokenId; + private ?string $csrfToken; + + /** + * @param string $csrfTokenId An arbitrary string used to generate the value of the CSRF token. + * Using a different string for each authenticator improves its security. + * @param string|null $csrfToken The CSRF token presented in the request, if any + */ + public function __construct(string $csrfTokenId, #[\SensitiveParameter] ?string $csrfToken) + { + $this->csrfTokenId = $csrfTokenId; + $this->csrfToken = $csrfToken; + } + + public function getCsrfTokenId(): string + { + return $this->csrfTokenId; + } + + public function getCsrfToken(): ?string + { + return $this->csrfToken; + } + + /** + * @internal + */ + public function markResolved(): void + { + $this->resolved = true; + } + + public function isResolved(): bool + { + return $this->resolved; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php b/vendor/symfony/security-http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php new file mode 100644 index 0000000..7dd5ad3 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge; + +use Symfony\Component\Security\Core\Exception\LogicException; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; + +/** + * Adds automatic password migration, if enabled and required in the password encoder. + * + * @see PasswordUpgraderInterface + * + * @author Wouter de Jong + * + * @final + */ +class PasswordUpgradeBadge implements BadgeInterface +{ + private ?string $plaintextPassword = null; + private ?PasswordUpgraderInterface $passwordUpgrader; + + /** + * @param string $plaintextPassword The presented password, used in the rehash + * @param PasswordUpgraderInterface|null $passwordUpgrader The password upgrader, defaults to the UserProvider if null + */ + public function __construct(#[\SensitiveParameter] string $plaintextPassword, ?PasswordUpgraderInterface $passwordUpgrader = null) + { + $this->plaintextPassword = $plaintextPassword; + $this->passwordUpgrader = $passwordUpgrader; + } + + public function getAndErasePlaintextPassword(): string + { + $password = $this->plaintextPassword; + if (null === $password) { + throw new LogicException('The password is erased as another listener already used this badge.'); + } + + $this->plaintextPassword = null; + + return $password; + } + + public function getPasswordUpgrader(): ?PasswordUpgraderInterface + { + return $this->passwordUpgrader; + } + + public function isResolved(): bool + { + return true; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php b/vendor/symfony/security-http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php new file mode 100644 index 0000000..642f83f --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge; + +use Symfony\Component\Security\Http\Authenticator\AbstractPreAuthenticatedAuthenticator; + +/** + * Marks the authentication as being pre-authenticated. + * + * This disables pre-authentication user checkers. + * + * @see AbstractPreAuthenticatedAuthenticator + * + * @author Wouter de Jong + * + * @final + */ +class PreAuthenticatedUserBadge implements BadgeInterface +{ + public function isResolved(): bool + { + return true; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Badge/RememberMeBadge.php b/vendor/symfony/security-http/Authenticator/Passport/Badge/RememberMeBadge.php new file mode 100644 index 0000000..3b35ff4 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Badge/RememberMeBadge.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge; + +use Symfony\Component\Security\Http\EventListener\CheckRememberMeConditionsListener; + +/** + * Adds support for remember me to this authenticator. + * + * The presence of this badge doesn't create the remember-me cookie. The actual + * cookie is only created if this badge is enabled. By default, this is done + * by the {@see CheckRememberMeConditionsListener} if all conditions are met. + * + * @author Wouter de Jong + * + * @final + */ +class RememberMeBadge implements BadgeInterface +{ + private bool $enabled = false; + + public function __construct( + public readonly array $parameters = [], + ) { + } + + /** + * Enables remember-me cookie creation. + * + * In most cases, {@see CheckRememberMeConditionsListener} enables this + * automatically if always_remember_me is true or the remember_me_parameter + * exists in the request. + * + * @return $this + */ + public function enable(): static + { + $this->enabled = true; + + return $this; + } + + /** + * Disables remember-me cookie creation. + * + * The default is disabled, this can be called to suppress creation + * after it was enabled. + * + * @return $this + */ + public function disable(): static + { + $this->enabled = false; + + return $this; + } + + public function isEnabled(): bool + { + return $this->enabled; + } + + public function isResolved(): bool + { + return true; // remember me does not need to be explicitly resolved + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Badge/UserBadge.php b/vendor/symfony/security-http/Authenticator/Passport/Badge/UserBadge.php new file mode 100644 index 0000000..73ccf4c --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Badge/UserBadge.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\EventListener\UserProviderListener; + +/** + * Represents the user in the authentication process. + * + * It uses an identifier (e.g. email, or username) and + * "user loader" to load the related User object. + * + * @author Wouter de Jong + */ +class UserBadge implements BadgeInterface +{ + public const MAX_USERNAME_LENGTH = 4096; + + private string $userIdentifier; + /** @var callable|null */ + private $userLoader; + private UserInterface $user; + private ?array $attributes; + + /** + * Initializes the user badge. + * + * You must provide a $userIdentifier. This is a unique string representing the + * user for this authentication (e.g. the email if authentication is done using + * email + password; or a string combining email+company if authentication is done + * based on email *and* company name). This string can be used for e.g. login throttling. + * + * Optionally, you may pass a user loader. This callable receives the $userIdentifier + * as argument and must return a UserInterface object (otherwise an AuthenticationServiceException + * is thrown). If this is not set, the default user provider will be used with + * $userIdentifier as username. + */ + public function __construct(string $userIdentifier, ?callable $userLoader = null, ?array $attributes = null) + { + if (\strlen($userIdentifier) > self::MAX_USERNAME_LENGTH) { + throw new BadCredentialsException('Username too long.'); + } + + $this->userIdentifier = $userIdentifier; + $this->userLoader = $userLoader; + $this->attributes = $attributes; + } + + public function getUserIdentifier(): string + { + return $this->userIdentifier; + } + + public function getAttributes(): ?array + { + return $this->attributes; + } + + /** + * @throws AuthenticationException when the user cannot be found + */ + public function getUser(): UserInterface + { + if (isset($this->user)) { + return $this->user; + } + + if (null === $this->userLoader) { + throw new \LogicException(sprintf('No user loader is configured, did you forget to register the "%s" listener?', UserProviderListener::class)); + } + + if (null === $this->getAttributes()) { + $user = ($this->userLoader)($this->userIdentifier); + } else { + $user = ($this->userLoader)($this->userIdentifier, $this->getAttributes()); + } + + // No user has been found via the $this->userLoader callback + if (null === $user) { + $exception = new UserNotFoundException(); + $exception->setUserIdentifier($this->userIdentifier); + + throw $exception; + } + + if (!$user instanceof UserInterface) { + throw new AuthenticationServiceException(sprintf('The user provider must return a UserInterface object, "%s" given.', get_debug_type($user))); + } + + return $this->user = $user; + } + + public function getUserLoader(): ?callable + { + return $this->userLoader; + } + + public function setUserLoader(callable $userLoader): void + { + $this->userLoader = $userLoader; + } + + public function isResolved(): bool + { + return true; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Credentials/CredentialsInterface.php b/vendor/symfony/security-http/Authenticator/Passport/Credentials/CredentialsInterface.php new file mode 100644 index 0000000..c22af0c --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Credentials/CredentialsInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Credentials; + +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; + +/** + * Credentials are a special badge used to explicitly mark the + * credential check of an authenticator. + * + * @author Wouter de Jong + */ +interface CredentialsInterface extends BadgeInterface +{ +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Credentials/CustomCredentials.php b/vendor/symfony/security-http/Authenticator/Passport/Credentials/CustomCredentials.php new file mode 100644 index 0000000..8ec0bb8 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Credentials/CustomCredentials.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Credentials; + +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Implements credentials checking using a custom checker function. + * + * @author Wouter de Jong + * + * @final + */ +class CustomCredentials implements CredentialsInterface +{ + private \Closure $customCredentialsChecker; + private mixed $credentials; + private bool $resolved = false; + + /** + * @param callable $customCredentialsChecker the check function. If this function does not return `true`, a + * BadCredentialsException is thrown. You may also throw a more + * specific exception in the function. + */ + public function __construct(callable $customCredentialsChecker, mixed $credentials) + { + $this->customCredentialsChecker = $customCredentialsChecker(...); + $this->credentials = $credentials; + } + + public function executeCustomChecker(UserInterface $user): void + { + $checker = $this->customCredentialsChecker; + + if (true !== $checker($this->credentials, $user)) { + throw new BadCredentialsException('Credentials check failed as the callable passed to CustomCredentials did not return "true".'); + } + + $this->resolved = true; + } + + public function isResolved(): bool + { + return $this->resolved; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Credentials/PasswordCredentials.php b/vendor/symfony/security-http/Authenticator/Passport/Credentials/PasswordCredentials.php new file mode 100644 index 0000000..9c86b12 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Credentials/PasswordCredentials.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport\Credentials; + +use Symfony\Component\Security\Core\Exception\LogicException; + +/** + * Implements password credentials. + * + * These plaintext passwords are checked by the UserPasswordHasher during + * authentication. + * + * @author Wouter de Jong + * + * @final + */ +class PasswordCredentials implements CredentialsInterface +{ + private ?string $password = null; + private bool $resolved = false; + + public function __construct(#[\SensitiveParameter] string $password) + { + $this->password = $password; + } + + public function getPassword(): string + { + if (null === $this->password) { + throw new LogicException('The credentials are erased as another listener already verified these credentials.'); + } + + return $this->password; + } + + /** + * @internal + */ + public function markResolved(): void + { + $this->resolved = true; + $this->password = null; + } + + public function isResolved(): bool + { + return $this->resolved; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/Passport.php b/vendor/symfony/security-http/Authenticator/Passport/Passport.php new file mode 100644 index 0000000..2772080 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/Passport.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport; + +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CredentialsInterface; + +/** + * A Passport contains all security-related information that needs to be + * validated during authentication. + * + * A passport badge can be used to add any additional information to the + * passport. + * + * @author Wouter de Jong + */ +class Passport +{ + protected UserInterface $user; + + private array $badges = []; + private array $attributes = []; + + /** + * @param CredentialsInterface $credentials The credentials to check for this authentication, use + * SelfValidatingPassport if no credentials should be checked + * @param BadgeInterface[] $badges + */ + public function __construct(UserBadge $userBadge, CredentialsInterface $credentials, array $badges = []) + { + $this->addBadge($userBadge); + $this->addBadge($credentials); + foreach ($badges as $badge) { + $this->addBadge($badge); + } + } + + public function getUser(): UserInterface + { + if (!isset($this->user)) { + if (!$this->hasBadge(UserBadge::class)) { + throw new \LogicException('Cannot get the Security user, no username or UserBadge configured for this passport.'); + } + + $this->user = $this->getBadge(UserBadge::class)->getUser(); + } + + return $this->user; + } + + /** + * Adds a new security badge. + * + * A passport can hold only one instance of the same security badge. + * This method replaces the current badge if it is already set on this + * passport. + * + * @param string|null $badgeFqcn A FQCN to which the badge should be mapped to. + * This allows replacing a built-in badge by a custom one using + * e.g. addBadge(new MyCustomUserBadge(), UserBadge::class) + * + * @return $this + */ + public function addBadge(BadgeInterface $badge, ?string $badgeFqcn = null): static + { + $badgeFqcn ??= $badge::class; + + $this->badges[$badgeFqcn] = $badge; + + return $this; + } + + public function hasBadge(string $badgeFqcn): bool + { + return isset($this->badges[$badgeFqcn]); + } + + /** + * @template TBadge of BadgeInterface + * + * @param class-string $badgeFqcn + * + * @return TBadge|null + */ + public function getBadge(string $badgeFqcn): ?BadgeInterface + { + return $this->badges[$badgeFqcn] ?? null; + } + + /** + * @return array, BadgeInterface> + */ + public function getBadges(): array + { + return $this->badges; + } + + public function setAttribute(string $name, mixed $value): void + { + $this->attributes[$name] = $value; + } + + public function getAttribute(string $name, mixed $default = null): mixed + { + return $this->attributes[$name] ?? $default; + } + + public function getAttributes(): array + { + return $this->attributes; + } +} diff --git a/vendor/symfony/security-http/Authenticator/Passport/SelfValidatingPassport.php b/vendor/symfony/security-http/Authenticator/Passport/SelfValidatingPassport.php new file mode 100644 index 0000000..23e7600 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Passport/SelfValidatingPassport.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Passport; + +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + +/** + * An implementation used when there are no credentials to be checked (e.g. + * API token authentication). + * + * @author Wouter de Jong + */ +class SelfValidatingPassport extends Passport +{ + /** + * @param BadgeInterface[] $badges + */ + public function __construct(UserBadge $userBadge, array $badges = []) + { + $this->addBadge($userBadge); + foreach ($badges as $badge) { + $this->addBadge($badge); + } + } +} diff --git a/vendor/symfony/security-http/Authenticator/RememberMeAuthenticator.php b/vendor/symfony/security-http/Authenticator/RememberMeAuthenticator.php new file mode 100644 index 0000000..0e6db6a --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/RememberMeAuthenticator.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\CookieTheftException; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Component\Security\Http\RememberMe\RememberMeDetails; +use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; +use Symfony\Component\Security\Http\RememberMe\ResponseListener; + +/** + * The RememberMe *Authenticator* performs remember me authentication. + * + * This authenticator is executed whenever a user's session + * expired and a remember-me cookie was found. This authenticator + * then "re-authenticates" the user using the information in the + * cookie. + * + * @author Johannes M. Schmitt + * @author Wouter de Jong + * + * @final + */ +class RememberMeAuthenticator implements InteractiveAuthenticatorInterface +{ + private RememberMeHandlerInterface $rememberMeHandler; + private string $secret; + private TokenStorageInterface $tokenStorage; + private string $cookieName; + private ?LoggerInterface $logger; + + public function __construct(RememberMeHandlerInterface $rememberMeHandler, #[\SensitiveParameter] string $secret, TokenStorageInterface $tokenStorage, string $cookieName, ?LoggerInterface $logger = null) + { + if (!$secret) { + throw new InvalidArgumentException('A non-empty secret is required.'); + } + + $this->rememberMeHandler = $rememberMeHandler; + $this->secret = $secret; + $this->tokenStorage = $tokenStorage; + $this->cookieName = $cookieName; + $this->logger = $logger; + } + + public function supports(Request $request): ?bool + { + // do not overwrite already stored tokens (i.e. from the session) + if (null !== $this->tokenStorage->getToken()) { + return false; + } + + if (($cookie = $request->attributes->get(ResponseListener::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) { + return false; + } + + if (!$request->cookies->has($this->cookieName) || !\is_scalar($request->cookies->all()[$this->cookieName] ?: null)) { + return false; + } + + $this->logger?->debug('Remember-me cookie detected.'); + + // the `null` return value indicates that this authenticator supports lazy firewalls + return null; + } + + public function authenticate(Request $request): Passport + { + if (!$rawCookie = $request->cookies->get($this->cookieName)) { + throw new \LogicException('No remember-me cookie is found.'); + } + + $rememberMeCookie = RememberMeDetails::fromRawCookie($rawCookie); + + $userBadge = new UserBadge($rememberMeCookie->getUserIdentifier(), fn () => $this->rememberMeHandler->consumeRememberMeCookie($rememberMeCookie)); + + return new SelfValidatingPassport($userBadge); + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new RememberMeToken($passport->getUser(), $firewallName, $this->secret); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; // let the original request continue + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + if (null !== $this->logger) { + if ($exception instanceof UserNotFoundException) { + $this->logger->info('User for remember-me cookie not found.', ['exception' => $exception]); + } elseif ($exception instanceof UnsupportedUserException) { + $this->logger->warning('User class for remember-me cookie not supported.', ['exception' => $exception]); + } elseif (!$exception instanceof CookieTheftException) { + $this->logger->debug('Remember me authentication failed.', ['exception' => $exception]); + } + } + + return null; + } + + public function isInteractive(): bool + { + return true; + } +} diff --git a/vendor/symfony/security-http/Authenticator/RemoteUserAuthenticator.php b/vendor/symfony/security-http/Authenticator/RemoteUserAuthenticator.php new file mode 100644 index 0000000..9514df3 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/RemoteUserAuthenticator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * This authenticator authenticates a remote user. + * + * @author Wouter de Jong + * @author Fabien Potencier + * @author Maxime Douailin + * + * @internal + */ +final class RemoteUserAuthenticator extends AbstractPreAuthenticatedAuthenticator +{ + private string $userKey; + + public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'REMOTE_USER', ?LoggerInterface $logger = null) + { + parent::__construct($userProvider, $tokenStorage, $firewallName, $logger); + + $this->userKey = $userKey; + } + + protected function extractUsername(Request $request): ?string + { + if (!$request->server->has($this->userKey)) { + throw new BadCredentialsException(sprintf('User key was not found: "%s".', $this->userKey)); + } + + return $request->server->get($this->userKey); + } +} diff --git a/vendor/symfony/security-http/Authenticator/Token/PostAuthenticationToken.php b/vendor/symfony/security-http/Authenticator/Token/PostAuthenticationToken.php new file mode 100644 index 0000000..5a9c08d --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/Token/PostAuthenticationToken.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator\Token; + +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; +use Symfony\Component\Security\Core\User\UserInterface; + +class PostAuthenticationToken extends AbstractToken +{ + private string $firewallName; + + /** + * @param string[] $roles An array of roles + * + * @throws \InvalidArgumentException + */ + public function __construct(UserInterface $user, string $firewallName, array $roles) + { + parent::__construct($roles); + + if ('' === $firewallName) { + throw new \InvalidArgumentException('$firewallName must not be empty.'); + } + + $this->setUser($user); + $this->firewallName = $firewallName; + } + + /** + * This is meant to be only a token, where credentials + * have already been used and are thus cleared. + */ + public function getCredentials(): mixed + { + return []; + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function __serialize(): array + { + return [$this->firewallName, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [$this->firewallName, $parentData] = $data; + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/security-http/Authenticator/X509Authenticator.php b/vendor/symfony/security-http/Authenticator/X509Authenticator.php new file mode 100644 index 0000000..c990ba3 --- /dev/null +++ b/vendor/symfony/security-http/Authenticator/X509Authenticator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authenticator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * This authenticator authenticates pre-authenticated (by the + * webserver) X.509 certificates. + * + * @author Wouter de Jong + * @author Fabien Potencier + * + * @final + */ +class X509Authenticator extends AbstractPreAuthenticatedAuthenticator +{ + private string $userKey; + private string $credentialsKey; + private string $credentialUserIdentifier; + + public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialsKey = 'SSL_CLIENT_S_DN', ?LoggerInterface $logger = null, string $credentialUserIdentifier = 'emailAddress') + { + parent::__construct($userProvider, $tokenStorage, $firewallName, $logger); + + $this->userKey = $userKey; + $this->credentialsKey = $credentialsKey; + $this->credentialUserIdentifier = $credentialUserIdentifier; + } + + protected function extractUsername(Request $request): string + { + $username = null; + if ($request->server->has($this->userKey)) { + $username = $request->server->get($this->userKey); + } elseif ( + $request->server->has($this->credentialsKey) + && preg_match('#'.preg_quote($this->credentialUserIdentifier, '#').'=([^,/]++)#', $request->server->get($this->credentialsKey), $matches) + ) { + $username = trim($matches[1]); + } + + if (null === $username) { + throw new BadCredentialsException(sprintf('SSL credentials not found: "%s", "%s".', $this->userKey, $this->credentialsKey)); + } + + return $username; + } +} diff --git a/vendor/symfony/security-http/Authorization/AccessDeniedHandlerInterface.php b/vendor/symfony/security-http/Authorization/AccessDeniedHandlerInterface.php new file mode 100644 index 0000000..bd5e818 --- /dev/null +++ b/vendor/symfony/security-http/Authorization/AccessDeniedHandlerInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Authorization; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; + +/** + * This is used by the ExceptionListener to translate an AccessDeniedException + * to a Response object. + * + * @author Johannes M. Schmitt + */ +interface AccessDeniedHandlerInterface +{ + /** + * Handles an access denied failure. + */ + public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response; +} diff --git a/vendor/symfony/security-http/CHANGELOG.md b/vendor/symfony/security-http/CHANGELOG.md new file mode 100644 index 0000000..a8e7105 --- /dev/null +++ b/vendor/symfony/security-http/CHANGELOG.md @@ -0,0 +1,73 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `#[IsCsrfTokenValid]` attribute + * Add CAS 2.0 access token handler + * Make empty username or empty password on form login attempts return Bad Request (400) + +7.0 +--- + + * Add argument `$badgeFqcn` to `Passport::addBadge()` + * Add argument `$lifetime` to `LoginLinkHandlerInterface::createLoginLink()` + * Throw when calling the constructor of `DefaultLoginRateLimiter` with an empty secret + +6.4 +--- + + * `UserValueResolver` no longer implements `ArgumentValueResolverInterface` + * Deprecate calling the constructor of `DefaultLoginRateLimiter` with an empty secret + +6.3 +--- + + * Add `RememberMeBadge` to `JsonLoginAuthenticator` and enable reading parameter in JSON request body + * Add argument `$exceptionCode` to `#[IsGranted]` + * Deprecate passing a secret as the 2nd argument to the constructor of `Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler` + * Add `OidcUserInfoTokenHandler` and `OidcTokenHandler` with OIDC support for `AccessTokenAuthenticator` + * Add `attributes` optional array argument in `UserBadge` + * Call `UserBadge::userLoader` with attributes if the argument is set + * Allow to override badge fqcn on `Passport::addBadge` + * Add `SecurityTokenValueResolver` to inject token as controller argument + +6.2 +--- + + * Add maximum username length enforcement of 4096 characters in `UserBadge` + * Add `#[IsGranted()]` + * Deprecate empty username or password when using when using `JsonLoginAuthenticator` + * Set custom lifetime for login link + * Add `$lifetime` parameter to `LoginLinkHandlerInterface::createLoginLink()` + * Add RFC6750 Access Token support to allow token-based authentication + * Allow using expressions as `#[IsGranted()]` attribute and subject + +6.0 +--- + + * Remove `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface`, register a listener on the `LogoutEvent` event instead + * Remove `CookieClearingLogoutHandler`, `SessionLogoutHandler` and `CsrfTokenClearingLogoutHandler`. + Use `CookieClearingLogoutListener`, `SessionLogoutListener` and `CsrfTokenClearingLogoutListener` instead + +5.4 +--- + + * Deprecate the `$authenticationEntryPoint` argument of `ChannelListener`, and add `$httpPort` and `$httpsPort` arguments + * Deprecate `RetryAuthenticationEntryPoint`, this code is now inlined in the `ChannelListener` + * Deprecate `FormAuthenticationEntryPoint` and `BasicAuthenticationEntryPoint`, in the new system the `FormLoginAuthenticator` + and `HttpBasicAuthenticator` should be used instead + * Deprecate `AbstractRememberMeServices`, `PersistentTokenBasedRememberMeServices`, `RememberMeServicesInterface`, + `TokenBasedRememberMeServices`, use the remember me handler alternatives instead + * Deprecate the `$authManager` argument of `AccessListener` + * Deprecate not setting the `$exceptionOnNoToken` argument of `AccessListener` to `false` + * Deprecate `DeauthenticatedEvent`, use `TokenDeauthenticatedEvent` instead + * Deprecate `CookieClearingLogoutHandler`, `SessionLogoutHandler` and `CsrfTokenClearingLogoutHandler`. + Use `CookieClearingLogoutListener`, `SessionLogoutListener` and `CsrfTokenClearingLogoutListener` instead + * Deprecate `PassportInterface`, `UserPassportInterface` and `PassportTrait`, use `Passport` instead + +5.3 +--- + +The CHANGELOG for version 5.3 and earlier can be found at https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/Security/CHANGELOG.md diff --git a/vendor/symfony/security-http/Controller/SecurityTokenValueResolver.php b/vendor/symfony/security-http/Controller/SecurityTokenValueResolver.php new file mode 100644 index 0000000..23c482b --- /dev/null +++ b/vendor/symfony/security-http/Controller/SecurityTokenValueResolver.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * @author Konstantin Myakshin + */ +final class SecurityTokenValueResolver implements ValueResolverInterface +{ + public function __construct(private readonly TokenStorageInterface $tokenStorage) + { + } + + /** + * @return TokenInterface[] + */ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!($type = $argument->getType()) || (TokenInterface::class !== $type && !is_subclass_of($type, TokenInterface::class))) { + return []; + } + + if (null !== $token = $this->tokenStorage->getToken()) { + return [$token]; + } + + if ($argument->isNullable()) { + return []; + } + + throw new HttpException(Response::HTTP_UNAUTHORIZED, 'A security token is required but the token storage is empty.'); + } +} diff --git a/vendor/symfony/security-http/Controller/UserValueResolver.php b/vendor/symfony/security-http/Controller/UserValueResolver.php new file mode 100644 index 0000000..e35c2a7 --- /dev/null +++ b/vendor/symfony/security-http/Controller/UserValueResolver.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Attribute\CurrentUser; + +/** + * Supports the argument type of {@see UserInterface}. + * + * @author Iltar van der Berg + */ +final class UserValueResolver implements ValueResolverInterface +{ + private TokenStorageInterface $tokenStorage; + + public function __construct(TokenStorageInterface $tokenStorage) + { + $this->tokenStorage = $tokenStorage; + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + // with the attribute, the type can be any UserInterface implementation + // otherwise, the type must be UserInterface + if (UserInterface::class !== $argument->getType() && !$argument->getAttributesOfType(CurrentUser::class, ArgumentMetadata::IS_INSTANCEOF)) { + return []; + } + + if (null === $user = $this->tokenStorage->getToken()?->getUser()) { + // if no user is present but a default value exists we use it to prevent the EntityValueResolver or others + // from attempting resolution of the User as the current logged in user was requested here + if ($argument->hasDefaultValue()) { + return [$argument->getDefaultValue()]; + } + + if (!$argument->isNullable()) { + throw new AccessDeniedException(sprintf('There is no logged-in user to pass to $%s, make the argument nullable if you want to allow anonymous access to the action.', $argument->getName())); + } + + return [null]; + } + + if (null === $argument->getType() || $user instanceof ($argument->getType())) { + return [$user]; + } + + throw new AccessDeniedException(sprintf('The logged-in user is an instance of "%s" but a user of type "%s" is expected.', $user::class, $argument->getType())); + } +} diff --git a/vendor/symfony/security-http/EntryPoint/AuthenticationEntryPointInterface.php b/vendor/symfony/security-http/EntryPoint/AuthenticationEntryPointInterface.php new file mode 100644 index 0000000..d02b78e --- /dev/null +++ b/vendor/symfony/security-http/EntryPoint/AuthenticationEntryPointInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EntryPoint; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * Implement this interface for any classes that will be called to "start" + * the authentication process (see method for more details). + * + * @author Fabien Potencier + */ +interface AuthenticationEntryPointInterface +{ + /** + * Returns a response that directs the user to authenticate. + * + * This is called when an anonymous request accesses a resource that + * requires authentication. The job of this method is to return some + * response that "helps" the user start into the authentication process. + * + * Examples: + * + * - For a form login, you might redirect to the login page + * + * return new RedirectResponse('/login'); + * + * - For an API token authentication system, you return a 401 response + * + * return new Response('Auth header required', 401); + */ + public function start(Request $request, ?AuthenticationException $authException = null): Response; +} diff --git a/vendor/symfony/security-http/EntryPoint/Exception/NotAnEntryPointException.php b/vendor/symfony/security-http/EntryPoint/Exception/NotAnEntryPointException.php new file mode 100644 index 0000000..80a6fb6 --- /dev/null +++ b/vendor/symfony/security-http/EntryPoint/Exception/NotAnEntryPointException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EntryPoint\Exception; + +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + +/** + * Thrown by generic decorators when a decorated authenticator does not implement + * {@see AuthenticationEntryPointInterface}. + * + * @author Robin Chalas + */ +#[WithHttpStatus(401)] +class NotAnEntryPointException extends \RuntimeException +{ +} diff --git a/vendor/symfony/security-http/Event/AuthenticationTokenCreatedEvent.php b/vendor/symfony/security-http/Event/AuthenticationTokenCreatedEvent.php new file mode 100644 index 0000000..bf16af0 --- /dev/null +++ b/vendor/symfony/security-http/Event/AuthenticationTokenCreatedEvent.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * When a newly authenticated security token was created, before it becomes effective in the security system. + * + * @author Christian Scheb + */ +class AuthenticationTokenCreatedEvent extends Event +{ + private TokenInterface $authenticatedToken; + private Passport $passport; + + public function __construct(TokenInterface $token, Passport $passport) + { + $this->authenticatedToken = $token; + $this->passport = $passport; + } + + public function getAuthenticatedToken(): TokenInterface + { + return $this->authenticatedToken; + } + + public function setAuthenticatedToken(TokenInterface $authenticatedToken): void + { + $this->authenticatedToken = $authenticatedToken; + } + + public function getPassport(): Passport + { + return $this->passport; + } +} diff --git a/vendor/symfony/security-http/Event/CheckPassportEvent.php b/vendor/symfony/security-http/Event/CheckPassportEvent.php new file mode 100644 index 0000000..5e3be93 --- /dev/null +++ b/vendor/symfony/security-http/Event/CheckPassportEvent.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This event is dispatched when the credentials have to be checked. + * + * Listeners to this event must validate the user and the + * credentials (e.g. default listeners do password verification and + * user checking) + * + * @author Wouter de Jong + */ +class CheckPassportEvent extends Event +{ + private AuthenticatorInterface $authenticator; + private Passport $passport; + + public function __construct(AuthenticatorInterface $authenticator, Passport $passport) + { + $this->authenticator = $authenticator; + $this->passport = $passport; + } + + public function getAuthenticator(): AuthenticatorInterface + { + return $this->authenticator instanceof TraceableAuthenticator ? $this->authenticator->getAuthenticator() : $this->authenticator; + } + + public function getPassport(): Passport + { + return $this->passport; + } +} diff --git a/vendor/symfony/security-http/Event/InteractiveLoginEvent.php b/vendor/symfony/security-http/Event/InteractiveLoginEvent.php new file mode 100644 index 0000000..cb96c73 --- /dev/null +++ b/vendor/symfony/security-http/Event/InteractiveLoginEvent.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Fabien Potencier + */ +final class InteractiveLoginEvent extends Event +{ + private Request $request; + private TokenInterface $authenticationToken; + + public function __construct(Request $request, TokenInterface $authenticationToken) + { + $this->request = $request; + $this->authenticationToken = $authenticationToken; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getAuthenticationToken(): TokenInterface + { + return $this->authenticationToken; + } +} diff --git a/vendor/symfony/security-http/Event/LazyResponseEvent.php b/vendor/symfony/security-http/Event/LazyResponseEvent.php new file mode 100644 index 0000000..b9ea71c --- /dev/null +++ b/vendor/symfony/security-http/Event/LazyResponseEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Core\Exception\LazyResponseException; + +/** + * Wraps a lazily computed response in a signaling exception. + * + * @author Nicolas Grekas + */ +final class LazyResponseEvent extends RequestEvent +{ + private parent $event; + + public function __construct(parent $event) + { + $this->event = $event; + } + + public function setResponse(Response $response): never + { + $this->stopPropagation(); + $this->event->stopPropagation(); + + throw new LazyResponseException($response); + } + + public function getKernel(): HttpKernelInterface + { + return $this->event->getKernel(); + } + + public function getRequest(): Request + { + return $this->event->getRequest(); + } + + public function getRequestType(): int + { + return $this->event->getRequestType(); + } + + public function isMainRequest(): bool + { + return $this->event->isMainRequest(); + } +} diff --git a/vendor/symfony/security-http/Event/LoginFailureEvent.php b/vendor/symfony/security-http/Event/LoginFailureEvent.php new file mode 100644 index 0000000..81f685b --- /dev/null +++ b/vendor/symfony/security-http/Event/LoginFailureEvent.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This event is dispatched after an error during authentication. + * + * Listeners to this event can change state based on authentication + * failure (e.g. to implement login throttling). + * + * @author Wouter de Jong + */ +class LoginFailureEvent extends Event +{ + private AuthenticationException $exception; + private AuthenticatorInterface $authenticator; + private Request $request; + private ?Response $response; + private string $firewallName; + private ?Passport $passport; + + public function __construct(AuthenticationException $exception, AuthenticatorInterface $authenticator, Request $request, ?Response $response, string $firewallName, ?Passport $passport = null) + { + $this->exception = $exception; + $this->authenticator = $authenticator; + $this->request = $request; + $this->response = $response; + $this->firewallName = $firewallName; + $this->passport = $passport; + } + + public function getException(): AuthenticationException + { + return $this->exception; + } + + public function getAuthenticator(): AuthenticatorInterface + { + return $this->authenticator instanceof TraceableAuthenticator ? $this->authenticator->getAuthenticator() : $this->authenticator; + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function setResponse(?Response $response): void + { + $this->response = $response; + } + + public function getResponse(): ?Response + { + return $this->response; + } + + public function getPassport(): ?Passport + { + return $this->passport; + } +} diff --git a/vendor/symfony/security-http/Event/LoginSuccessEvent.php b/vendor/symfony/security-http/Event/LoginSuccessEvent.php new file mode 100644 index 0000000..ff93f00 --- /dev/null +++ b/vendor/symfony/security-http/Event/LoginSuccessEvent.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This event is dispatched after authentication has successfully completed. + * + * At this stage, the authenticator created a token and + * generated an authentication success response. Listeners to + * this event can do actions related to successful authentication + * (such as migrating the password). + * + * @author Wouter de Jong + */ +class LoginSuccessEvent extends Event +{ + private AuthenticatorInterface $authenticator; + private Passport $passport; + private TokenInterface $authenticatedToken; + private ?TokenInterface $previousToken; + private Request $request; + private ?Response $response; + private string $firewallName; + + public function __construct(AuthenticatorInterface $authenticator, Passport $passport, TokenInterface $authenticatedToken, Request $request, ?Response $response, string $firewallName, ?TokenInterface $previousToken = null) + { + $this->authenticator = $authenticator; + $this->passport = $passport; + $this->authenticatedToken = $authenticatedToken; + $this->previousToken = $previousToken; + $this->request = $request; + $this->response = $response; + $this->firewallName = $firewallName; + } + + public function getAuthenticator(): AuthenticatorInterface + { + return $this->authenticator instanceof TraceableAuthenticator ? $this->authenticator->getAuthenticator() : $this->authenticator; + } + + public function getPassport(): Passport + { + return $this->passport; + } + + public function getUser(): UserInterface + { + return $this->passport->getUser(); + } + + public function getAuthenticatedToken(): TokenInterface + { + return $this->authenticatedToken; + } + + public function getPreviousToken(): ?TokenInterface + { + return $this->previousToken; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function setResponse(?Response $response): void + { + $this->response = $response; + } + + public function getResponse(): ?Response + { + return $this->response; + } +} diff --git a/vendor/symfony/security-http/Event/LogoutEvent.php b/vendor/symfony/security-http/Event/LogoutEvent.php new file mode 100644 index 0000000..5b5c156 --- /dev/null +++ b/vendor/symfony/security-http/Event/LogoutEvent.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Wouter de Jong + */ +class LogoutEvent extends Event +{ + private Request $request; + private ?Response $response = null; + private ?TokenInterface $token; + + public function __construct(Request $request, ?TokenInterface $token) + { + $this->request = $request; + $this->token = $token; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getToken(): ?TokenInterface + { + return $this->token; + } + + public function setResponse(Response $response): void + { + $this->response = $response; + } + + public function getResponse(): ?Response + { + return $this->response; + } +} diff --git a/vendor/symfony/security-http/Event/SwitchUserEvent.php b/vendor/symfony/security-http/Event/SwitchUserEvent.php new file mode 100644 index 0000000..f0506a2 --- /dev/null +++ b/vendor/symfony/security-http/Event/SwitchUserEvent.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * SwitchUserEvent. + * + * @author Fabien Potencier + */ +final class SwitchUserEvent extends Event +{ + private Request $request; + private UserInterface $targetUser; + private ?TokenInterface $token; + + public function __construct(Request $request, UserInterface $targetUser, ?TokenInterface $token = null) + { + $this->request = $request; + $this->targetUser = $targetUser; + $this->token = $token; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getTargetUser(): UserInterface + { + return $this->targetUser; + } + + public function getToken(): ?TokenInterface + { + return $this->token; + } + + public function setToken(TokenInterface $token): void + { + $this->token = $token; + } +} diff --git a/vendor/symfony/security-http/Event/TokenDeauthenticatedEvent.php b/vendor/symfony/security-http/Event/TokenDeauthenticatedEvent.php new file mode 100644 index 0000000..b75c60e --- /dev/null +++ b/vendor/symfony/security-http/Event/TokenDeauthenticatedEvent.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This event is dispatched when the current security token is deauthenticated + * when trying to reference the token. + * + * This includes changes in the user ({@see DeauthenticatedEvent}), but + * also cases where there is no user provider available to refresh the user. + * + * Use this event if you want to trigger some actions whenever a user is + * deauthenticated and redirected back to the authentication entry point + * (e.g. clearing all remember-me cookies). + * + * @author Wouter de Jong + */ +final class TokenDeauthenticatedEvent extends Event +{ + private TokenInterface $originalToken; + private Request $request; + + public function __construct(TokenInterface $originalToken, Request $request) + { + $this->originalToken = $originalToken; + $this->request = $request; + } + + public function getOriginalToken(): TokenInterface + { + return $this->originalToken; + } + + public function getRequest(): Request + { + return $this->request; + } +} diff --git a/vendor/symfony/security-http/EventListener/CheckCredentialsListener.php b/vendor/symfony/security-http/EventListener/CheckCredentialsListener.php new file mode 100644 index 0000000..2276d63 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/CheckCredentialsListener.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; + +/** + * This listeners uses the interfaces of authenticators to + * determine how to check credentials. + * + * @author Wouter de Jong + * + * @final + */ +class CheckCredentialsListener implements EventSubscriberInterface +{ + private PasswordHasherFactoryInterface $hasherFactory; + + public function __construct(PasswordHasherFactoryInterface $hasherFactory) + { + $this->hasherFactory = $hasherFactory; + } + + public function checkPassport(CheckPassportEvent $event): void + { + $passport = $event->getPassport(); + if ($passport->hasBadge(PasswordCredentials::class)) { + // Use the password hasher to validate the credentials + $user = $passport->getUser(); + + if (!$user instanceof PasswordAuthenticatedUserInterface) { + throw new \LogicException(sprintf('Class "%s" must implement "%s" for using password-based authentication.', get_debug_type($user), PasswordAuthenticatedUserInterface::class)); + } + + /** @var PasswordCredentials $badge */ + $badge = $passport->getBadge(PasswordCredentials::class); + + if ($badge->isResolved()) { + return; + } + + $presentedPassword = $badge->getPassword(); + if ('' === $presentedPassword) { + throw new BadCredentialsException('The presented password cannot be empty.'); + } + + if (null === $user->getPassword()) { + throw new BadCredentialsException('The presented password is invalid.'); + } + + if (!$this->hasherFactory->getPasswordHasher($user)->verify($user->getPassword(), $presentedPassword, $user instanceof LegacyPasswordAuthenticatedUserInterface ? $user->getSalt() : null)) { + throw new BadCredentialsException('The presented password is invalid.'); + } + + $badge->markResolved(); + + if (!$passport->hasBadge(PasswordUpgradeBadge::class)) { + $passport->addBadge(new PasswordUpgradeBadge($presentedPassword)); + } + + return; + } + + if ($passport->hasBadge(CustomCredentials::class)) { + /** @var CustomCredentials $badge */ + $badge = $passport->getBadge(CustomCredentials::class); + if ($badge->isResolved()) { + return; + } + + $badge->executeCustomChecker($passport->getUser()); + + return; + } + } + + public static function getSubscribedEvents(): array + { + return [CheckPassportEvent::class => 'checkPassport']; + } +} diff --git a/vendor/symfony/security-http/EventListener/CheckRememberMeConditionsListener.php b/vendor/symfony/security-http/EventListener/CheckRememberMeConditionsListener.php new file mode 100644 index 0000000..0556638 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/CheckRememberMeConditionsListener.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\Security\Http\ParameterBagUtils; + +/** + * Checks if all conditions are met for remember me. + * + * The conditions that must be met for this listener to enable remember me: + * A) This badge is present in the Passport + * B) The remember_me key under your firewall is configured + * C) The "remember me" functionality is activated. This is usually + * done by having a _remember_me checkbox in your form, but + * can be configured by the "always_remember_me" and "remember_me_parameter" + * parameters under the "remember_me" firewall key (or "always_remember_me" + * is enabled) + * + * @author Wouter de Jong + * + * @final + */ +class CheckRememberMeConditionsListener implements EventSubscriberInterface +{ + private array $options; + private ?LoggerInterface $logger; + + public function __construct(array $options = [], ?LoggerInterface $logger = null) + { + $this->options = $options + ['always_remember_me' => false, 'remember_me_parameter' => '_remember_me']; + $this->logger = $logger; + } + + public function onSuccessfulLogin(LoginSuccessEvent $event): void + { + $passport = $event->getPassport(); + if (!$passport->hasBadge(RememberMeBadge::class)) { + return; + } + + /** @var RememberMeBadge $badge */ + $badge = $passport->getBadge(RememberMeBadge::class); + if (!$this->options['always_remember_me']) { + $parameter = ParameterBagUtils::getRequestParameterValue($event->getRequest(), $this->options['remember_me_parameter'], $badge->parameters); + if (!filter_var($parameter, \FILTER_VALIDATE_BOOL)) { + $this->logger?->debug('Remember me disabled; request does not contain remember me parameter ("{parameter}").', ['parameter' => $this->options['remember_me_parameter']]); + + return; + } + } + + $badge->enable(); + } + + public static function getSubscribedEvents(): array + { + return [LoginSuccessEvent::class => ['onSuccessfulLogin', -32]]; + } +} diff --git a/vendor/symfony/security-http/EventListener/ClearSiteDataLogoutListener.php b/vendor/symfony/security-http/EventListener/ClearSiteDataLogoutListener.php new file mode 100644 index 0000000..77ca07a --- /dev/null +++ b/vendor/symfony/security-http/EventListener/ClearSiteDataLogoutListener.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; + +/** + * Handler for Clear-Site-Data header during logout. + * + * @author Max Beckers + * + * @final + */ +class ClearSiteDataLogoutListener implements EventSubscriberInterface +{ + private const HEADER_NAME = 'Clear-Site-Data'; + + /** + * @param string[] $cookieValue The value for the Clear-Site-Data header. + * Can be '*' or a subset of 'cache', 'cookies', 'storage', 'executionContexts'. + */ + public function __construct(private readonly array $cookieValue) + { + } + + public function onLogout(LogoutEvent $event): void + { + if (!$event->getResponse()?->headers->has(static::HEADER_NAME)) { + $event->getResponse()->headers->set(static::HEADER_NAME, implode(', ', array_map(fn ($v) => '"'.$v.'"', $this->cookieValue))); + } + } + + public static function getSubscribedEvents(): array + { + return [ + LogoutEvent::class => 'onLogout', + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/CookieClearingLogoutListener.php b/vendor/symfony/security-http/EventListener/CookieClearingLogoutListener.php new file mode 100644 index 0000000..cbc8599 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/CookieClearingLogoutListener.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; + +/** + * This listener clears the passed cookies when a user logs out. + * + * @author Johannes M. Schmitt + * + * @final + */ +class CookieClearingLogoutListener implements EventSubscriberInterface +{ + private array $cookies; + + /** + * @param array $cookies An array of cookies (keys are names, values contain path and domain) to unset + */ + public function __construct(array $cookies) + { + $this->cookies = $cookies; + } + + public function onLogout(LogoutEvent $event): void + { + if (!$response = $event->getResponse()) { + return; + } + + foreach ($this->cookies as $cookieName => $cookieData) { + $response->headers->clearCookie($cookieName, $cookieData['path'], $cookieData['domain'], $cookieData['secure'] ?? false, true, $cookieData['samesite'] ?? null, $cookieData['partitioned'] ?? false); + } + } + + public static function getSubscribedEvents(): array + { + return [ + LogoutEvent::class => ['onLogout', -255], + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/CsrfProtectionListener.php b/vendor/symfony/security-http/EventListener/CsrfProtectionListener.php new file mode 100644 index 0000000..c36ec58 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/CsrfProtectionListener.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; + +/** + * @author Wouter de Jong + * + * @final + */ +class CsrfProtectionListener implements EventSubscriberInterface +{ + private CsrfTokenManagerInterface $csrfTokenManager; + + public function __construct(CsrfTokenManagerInterface $csrfTokenManager) + { + $this->csrfTokenManager = $csrfTokenManager; + } + + public function checkPassport(CheckPassportEvent $event): void + { + $passport = $event->getPassport(); + if (!$passport->hasBadge(CsrfTokenBadge::class)) { + return; + } + + /** @var CsrfTokenBadge $badge */ + $badge = $passport->getBadge(CsrfTokenBadge::class); + if ($badge->isResolved()) { + return; + } + + $csrfToken = new CsrfToken($badge->getCsrfTokenId(), $badge->getCsrfToken()); + + if (false === $this->csrfTokenManager->isTokenValid($csrfToken)) { + throw new InvalidCsrfTokenException('Invalid CSRF token.'); + } + + $badge->markResolved(); + } + + public static function getSubscribedEvents(): array + { + return [CheckPassportEvent::class => ['checkPassport', 512]]; + } +} diff --git a/vendor/symfony/security-http/EventListener/CsrfTokenClearingLogoutListener.php b/vendor/symfony/security-http/EventListener/CsrfTokenClearingLogoutListener.php new file mode 100644 index 0000000..ec00bc1 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/CsrfTokenClearingLogoutListener.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Http\Event\LogoutEvent; + +/** + * @author Christian Flothmann + * + * @final + */ +class CsrfTokenClearingLogoutListener implements EventSubscriberInterface +{ + private ClearableTokenStorageInterface $csrfTokenStorage; + + public function __construct(ClearableTokenStorageInterface $csrfTokenStorage) + { + $this->csrfTokenStorage = $csrfTokenStorage; + } + + public function onLogout(LogoutEvent $event): void + { + if ($this->csrfTokenStorage instanceof SessionTokenStorage && !$event->getRequest()->hasPreviousSession()) { + return; + } + + $this->csrfTokenStorage->clear(); + } + + public static function getSubscribedEvents(): array + { + return [ + LogoutEvent::class => 'onLogout', + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/DefaultLogoutListener.php b/vendor/symfony/security-http/EventListener/DefaultLogoutListener.php new file mode 100644 index 0000000..aa5496e --- /dev/null +++ b/vendor/symfony/security-http/EventListener/DefaultLogoutListener.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Component\Security\Http\HttpUtils; + +/** + * Default logout listener will redirect users to a configured path. + * + * @author Fabien Potencier + * @author Alexander + * + * @final + */ +class DefaultLogoutListener implements EventSubscriberInterface +{ + private HttpUtils $httpUtils; + private string $targetUrl; + + public function __construct(HttpUtils $httpUtils, string $targetUrl = '/') + { + $this->httpUtils = $httpUtils; + $this->targetUrl = $targetUrl; + } + + public function onLogout(LogoutEvent $event): void + { + if (null !== $event->getResponse()) { + return; + } + + $event->setResponse($this->httpUtils->createRedirectResponse($event->getRequest(), $this->targetUrl)); + } + + public static function getSubscribedEvents(): array + { + return [ + LogoutEvent::class => ['onLogout', 64], + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/IsCsrfTokenValidAttributeListener.php b/vendor/symfony/security-http/EventListener/IsCsrfTokenValidAttributeListener.php new file mode 100644 index 0000000..3e05c71 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/IsCsrfTokenValidAttributeListener.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid; + +/** + * Handles the IsCsrfTokenValid attribute on controllers. + */ +final class IsCsrfTokenValidAttributeListener implements EventSubscriberInterface +{ + public function __construct( + private readonly CsrfTokenManagerInterface $csrfTokenManager, + private ?ExpressionLanguage $expressionLanguage = null, + ) { + } + + public function onKernelControllerArguments(ControllerArgumentsEvent $event): void + { + /** @var IsCsrfTokenValid[] $attributes */ + if (!\is_array($attributes = $event->getAttributes()[IsCsrfTokenValid::class] ?? null)) { + return; + } + + $request = $event->getRequest(); + $arguments = $event->getNamedArguments(); + + foreach ($attributes as $attribute) { + $id = $this->getTokenId($attribute->id, $request, $arguments); + + if (!$this->csrfTokenManager->isTokenValid(new CsrfToken($id, $request->getPayload()->getString($attribute->tokenKey)))) { + throw new InvalidCsrfTokenException('Invalid CSRF token.'); + } + } + } + + public static function getSubscribedEvents(): array + { + return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 25]]; + } + + private function getTokenId(string|Expression $id, Request $request, array $arguments): string + { + if (!$id instanceof Expression) { + return $id; + } + + $this->expressionLanguage ??= new ExpressionLanguage(); + + return (string) $this->expressionLanguage->evaluate($id, [ + 'request' => $request, + 'args' => $arguments, + ]); + } +} diff --git a/vendor/symfony/security-http/EventListener/IsGrantedAttributeListener.php b/vendor/symfony/security-http/EventListener/IsGrantedAttributeListener.php new file mode 100644 index 0000000..8cfbd2f --- /dev/null +++ b/vendor/symfony/security-http/EventListener/IsGrantedAttributeListener.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Exception\RuntimeException; +use Symfony\Component\Security\Http\Attribute\IsGranted; + +/** + * Handles the IsGranted attribute on controllers. + * + * @author Ryan Weaver + */ +class IsGrantedAttributeListener implements EventSubscriberInterface +{ + public function __construct( + private readonly AuthorizationCheckerInterface $authChecker, + private ?ExpressionLanguage $expressionLanguage = null, + ) { + } + + public function onKernelControllerArguments(ControllerArgumentsEvent $event): void + { + /** @var IsGranted[] $attributes */ + if (!\is_array($attributes = $event->getAttributes()[IsGranted::class] ?? null)) { + return; + } + + $request = $event->getRequest(); + $arguments = $event->getNamedArguments(); + + foreach ($attributes as $attribute) { + $subject = null; + + if ($subjectRef = $attribute->subject) { + if (\is_array($subjectRef)) { + foreach ($subjectRef as $refKey => $ref) { + $subject[\is_string($refKey) ? $refKey : (string) $ref] = $this->getIsGrantedSubject($ref, $request, $arguments); + } + } else { + $subject = $this->getIsGrantedSubject($subjectRef, $request, $arguments); + } + } + + if (!$this->authChecker->isGranted($attribute->attribute, $subject)) { + $message = $attribute->message ?: sprintf('Access Denied by #[IsGranted(%s)] on controller', $this->getIsGrantedString($attribute)); + + if ($statusCode = $attribute->statusCode) { + throw new HttpException($statusCode, $message, code: $attribute->exceptionCode ?? 0); + } + + $accessDeniedException = new AccessDeniedException($message, code: $attribute->exceptionCode ?? 403); + $accessDeniedException->setAttributes($attribute->attribute); + $accessDeniedException->setSubject($subject); + + throw $accessDeniedException; + } + } + } + + public static function getSubscribedEvents(): array + { + return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 20]]; + } + + private function getIsGrantedSubject(string|Expression $subjectRef, Request $request, array $arguments): mixed + { + if ($subjectRef instanceof Expression) { + $this->expressionLanguage ??= new ExpressionLanguage(); + + return $this->expressionLanguage->evaluate($subjectRef, [ + 'request' => $request, + 'args' => $arguments, + ]); + } + + if (!\array_key_exists($subjectRef, $arguments)) { + throw new RuntimeException(sprintf('Could not find the subject "%s" for the #[IsGranted] attribute. Try adding a "$%s" argument to your controller method.', $subjectRef, $subjectRef)); + } + + return $arguments[$subjectRef]; + } + + private function getIsGrantedString(IsGranted $isGranted): string + { + $processValue = fn ($value) => sprintf($value instanceof Expression ? 'new Expression("%s")' : '"%s"', $value); + + $argsString = $processValue($isGranted->attribute); + + if (null !== $subject = $isGranted->subject) { + $subject = !\is_array($subject) ? $processValue($subject) : array_map(function ($key, $value) use ($processValue) { + $value = $processValue($value); + + return \is_string($key) ? sprintf('"%s" => %s', $key, $value) : $value; + }, array_keys($subject), $subject); + + $argsString .= ', '.(!\is_array($subject) ? $subject : '['.implode(', ', $subject).']'); + } + + return $argsString; + } +} diff --git a/vendor/symfony/security-http/EventListener/LoginThrottlingListener.php b/vendor/symfony/security-http/EventListener/LoginThrottlingListener.php new file mode 100644 index 0000000..6d2df42 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/LoginThrottlingListener.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RateLimiter\PeekableRequestRateLimiterInterface; +use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\TooManyLoginAttemptsAuthenticationException; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Component\Security\Http\Event\LoginFailureEvent; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\Security\Http\SecurityRequestAttributes; + +/** + * @author Wouter de Jong + */ +final class LoginThrottlingListener implements EventSubscriberInterface +{ + private RequestStack $requestStack; + private RequestRateLimiterInterface $limiter; + + public function __construct(RequestStack $requestStack, RequestRateLimiterInterface $limiter) + { + $this->requestStack = $requestStack; + $this->limiter = $limiter; + } + + public function checkPassport(CheckPassportEvent $event): void + { + $passport = $event->getPassport(); + if (!$passport->hasBadge(UserBadge::class)) { + return; + } + + $request = $this->requestStack->getMainRequest(); + $request->attributes->set(SecurityRequestAttributes::LAST_USERNAME, $passport->getBadge(UserBadge::class)->getUserIdentifier()); + + if ($this->limiter instanceof PeekableRequestRateLimiterInterface) { + $limit = $this->limiter->peek($request); + // Checking isAccepted here is not enough as peek consumes 0 token, it will + // be accepted even if there are 0 tokens remaining to be consumed. We check both + // anyway for safety in case third party implementations behave unexpectedly. + if (!$limit->isAccepted() || 0 === $limit->getRemainingTokens()) { + throw new TooManyLoginAttemptsAuthenticationException(ceil(($limit->getRetryAfter()->getTimestamp() - time()) / 60)); + } + } else { + $limit = $this->limiter->consume($request); + if (!$limit->isAccepted()) { + throw new TooManyLoginAttemptsAuthenticationException(ceil(($limit->getRetryAfter()->getTimestamp() - time()) / 60)); + } + } + } + + public function onSuccessfulLogin(LoginSuccessEvent $event): void + { + if (!$this->limiter instanceof PeekableRequestRateLimiterInterface) { + $this->limiter->reset($event->getRequest()); + } + } + + public function onFailedLogin(LoginFailureEvent $event): void + { + if ($this->limiter instanceof PeekableRequestRateLimiterInterface) { + $this->limiter->consume($event->getRequest()); + } + } + + public static function getSubscribedEvents(): array + { + return [ + CheckPassportEvent::class => ['checkPassport', 2080], + LoginFailureEvent::class => 'onFailedLogin', + LoginSuccessEvent::class => 'onSuccessfulLogin', + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/PasswordMigratingListener.php b/vendor/symfony/security-http/EventListener/PasswordMigratingListener.php new file mode 100644 index 0000000..06cabf0 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/PasswordMigratingListener.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; +use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; + +/** + * @author Wouter de Jong + * + * @final + */ +class PasswordMigratingListener implements EventSubscriberInterface +{ + private PasswordHasherFactoryInterface $hasherFactory; + + public function __construct(PasswordHasherFactoryInterface $hasherFactory) + { + $this->hasherFactory = $hasherFactory; + } + + public function onLoginSuccess(LoginSuccessEvent $event): void + { + $passport = $event->getPassport(); + if (!$passport->hasBadge(PasswordUpgradeBadge::class)) { + return; + } + + /** @var PasswordUpgradeBadge $badge */ + $badge = $passport->getBadge(PasswordUpgradeBadge::class); + $plaintextPassword = $badge->getAndErasePlaintextPassword(); + + if ('' === $plaintextPassword) { + return; + } + + $user = $passport->getUser(); + if (!$user instanceof PasswordAuthenticatedUserInterface || null === $user->getPassword()) { + return; + } + + $passwordHasher = $this->hasherFactory->getPasswordHasher($user); + if (!$passwordHasher->needsRehash($user->getPassword())) { + return; + } + + $passwordUpgrader = $badge->getPasswordUpgrader(); + + if (null === $passwordUpgrader) { + if (!$passport->hasBadge(UserBadge::class)) { + return; + } + + /** @var UserBadge $userBadge */ + $userBadge = $passport->getBadge(UserBadge::class); + $userLoader = $userBadge->getUserLoader(); + if (\is_array($userLoader) && $userLoader[0] instanceof PasswordUpgraderInterface) { + $passwordUpgrader = $userLoader[0]; + } elseif (!$userLoader instanceof \Closure + || !($passwordUpgrader = (new \ReflectionFunction($userLoader))->getClosureThis()) instanceof PasswordUpgraderInterface + ) { + return; + } + } + + $salt = null; + if ($user instanceof LegacyPasswordAuthenticatedUserInterface) { + $salt = $user->getSalt(); + } + + $passwordUpgrader->upgradePassword($user, $passwordHasher->hash($plaintextPassword, $salt)); + } + + public static function getSubscribedEvents(): array + { + return [LoginSuccessEvent::class => 'onLoginSuccess']; + } +} diff --git a/vendor/symfony/security-http/EventListener/RememberMeListener.php b/vendor/symfony/security-http/EventListener/RememberMeListener.php new file mode 100644 index 0000000..2dd5aa1 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/RememberMeListener.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; +use Symfony\Component\Security\Http\Event\LoginFailureEvent; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent; +use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; + +/** + * The RememberMe *listener* creates and deletes remember-me cookies. + * + * Upon login success or failure and support for remember me + * in the firewall and authenticator, this listener will create + * a remember-me cookie. + * Upon login failure, all remember-me cookies are removed. + * + * @author Wouter de Jong + * + * @final + */ +class RememberMeListener implements EventSubscriberInterface +{ + private RememberMeHandlerInterface $rememberMeHandler; + private ?LoggerInterface $logger; + + public function __construct(RememberMeHandlerInterface $rememberMeHandler, ?LoggerInterface $logger = null) + { + $this->rememberMeHandler = $rememberMeHandler; + $this->logger = $logger; + } + + public function onSuccessfulLogin(LoginSuccessEvent $event): void + { + $passport = $event->getPassport(); + if (!$passport->hasBadge(RememberMeBadge::class)) { + $this->logger?->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => $event->getAuthenticator()::class]); + + return; + } + + // Make sure any old remember-me cookies are cancelled + $this->rememberMeHandler->clearRememberMeCookie(); + + /** @var RememberMeBadge $badge */ + $badge = $passport->getBadge(RememberMeBadge::class); + if (!$badge->isEnabled()) { + $this->logger?->debug('Remember me skipped: the RememberMeBadge is not enabled.'); + + return; + } + + $this->logger?->debug('Remember-me was requested; setting cookie.'); + + $this->rememberMeHandler->createRememberMeCookie($event->getUser()); + } + + public function clearCookie(): void + { + $this->rememberMeHandler->clearRememberMeCookie(); + } + + public static function getSubscribedEvents(): array + { + return [ + LoginSuccessEvent::class => ['onSuccessfulLogin', -64], + LoginFailureEvent::class => 'clearCookie', + LogoutEvent::class => 'clearCookie', + TokenDeauthenticatedEvent::class => 'clearCookie', + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/SessionLogoutListener.php b/vendor/symfony/security-http/EventListener/SessionLogoutListener.php new file mode 100644 index 0000000..06b84d6 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/SessionLogoutListener.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; + +/** + * Handler for clearing invalidating the current session. + * + * @author Johannes M. Schmitt + * + * @final + */ +class SessionLogoutListener implements EventSubscriberInterface +{ + public function onLogout(LogoutEvent $event): void + { + if ($event->getRequest()->hasSession()) { + $event->getRequest()->getSession()->invalidate(); + } + } + + public static function getSubscribedEvents(): array + { + return [ + LogoutEvent::class => 'onLogout', + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/SessionStrategyListener.php b/vendor/symfony/security-http/EventListener/SessionStrategyListener.php new file mode 100644 index 0000000..fe65d8b --- /dev/null +++ b/vendor/symfony/security-http/EventListener/SessionStrategyListener.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; + +/** + * Migrates/invalidates the session after successful login. + * + * This should be registered as subscriber to any "stateful" firewalls. + * + * @see SessionAuthenticationStrategy + * + * @author Wouter de Jong + */ +class SessionStrategyListener implements EventSubscriberInterface +{ + private SessionAuthenticationStrategyInterface $sessionAuthenticationStrategy; + + public function __construct(SessionAuthenticationStrategyInterface $sessionAuthenticationStrategy) + { + $this->sessionAuthenticationStrategy = $sessionAuthenticationStrategy; + } + + public function onSuccessfulLogin(LoginSuccessEvent $event): void + { + $request = $event->getRequest(); + $token = $event->getAuthenticatedToken(); + + if (!$request->hasPreviousSession()) { + return; + } + + if ($previousToken = $event->getPreviousToken()) { + $user = $token->getUserIdentifier(); + $previousUser = $previousToken->getUserIdentifier(); + + if ('' !== ($user ?? '') && $user === $previousUser && $token::class === $previousToken::class) { + return; + } + } + + $this->sessionAuthenticationStrategy->onAuthentication($request, $token); + } + + public static function getSubscribedEvents(): array + { + return [LoginSuccessEvent::class => 'onSuccessfulLogin']; + } +} diff --git a/vendor/symfony/security-http/EventListener/UserCheckerListener.php b/vendor/symfony/security-http/EventListener/UserCheckerListener.php new file mode 100644 index 0000000..6a1d4c5 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/UserCheckerListener.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; + +/** + * @author Wouter de Jong + * + * @final + */ +class UserCheckerListener implements EventSubscriberInterface +{ + private UserCheckerInterface $userChecker; + + public function __construct(UserCheckerInterface $userChecker) + { + $this->userChecker = $userChecker; + } + + public function preCheckCredentials(CheckPassportEvent $event): void + { + $passport = $event->getPassport(); + if ($passport->hasBadge(PreAuthenticatedUserBadge::class)) { + return; + } + + $this->userChecker->checkPreAuth($passport->getUser()); + } + + public function postCheckCredentials(AuthenticationSuccessEvent $event): void + { + $user = $event->getAuthenticationToken()->getUser(); + if (!$user instanceof UserInterface) { + return; + } + + $this->userChecker->checkPostAuth($user); + } + + public static function getSubscribedEvents(): array + { + return [ + CheckPassportEvent::class => ['preCheckCredentials', 256], + AuthenticationSuccessEvent::class => ['postCheckCredentials', 256], + ]; + } +} diff --git a/vendor/symfony/security-http/EventListener/UserProviderListener.php b/vendor/symfony/security-http/EventListener/UserProviderListener.php new file mode 100644 index 0000000..c122fe3 --- /dev/null +++ b/vendor/symfony/security-http/EventListener/UserProviderListener.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Event\CheckPassportEvent; + +/** + * Configures the user provider as user loader, if no user load + * has been explicitly set. + * + * @author Wouter de Jong + * + * @final + */ +class UserProviderListener +{ + private UserProviderInterface $userProvider; + + public function __construct(UserProviderInterface $userProvider) + { + $this->userProvider = $userProvider; + } + + public function checkPassport(CheckPassportEvent $event): void + { + $passport = $event->getPassport(); + if (!$passport->hasBadge(UserBadge::class)) { + return; + } + + /** @var UserBadge $badge */ + $badge = $passport->getBadge(UserBadge::class); + if (null !== $badge->getUserLoader()) { + return; + } + + $badge->setUserLoader($this->userProvider->loadUserByIdentifier(...)); + } +} diff --git a/vendor/symfony/security-http/Firewall.php b/vendor/symfony/security-http/Firewall.php new file mode 100644 index 0000000..f2f86a5 --- /dev/null +++ b/vendor/symfony/security-http/Firewall.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * Firewall uses a FirewallMap to register security listeners for the given + * request. + * + * It allows for different security strategies within the same application + * (a Basic authentication for the /api, and a web based authentication for + * everything else for instance). + * + * @author Fabien Potencier + */ +class Firewall implements EventSubscriberInterface +{ + private FirewallMapInterface $map; + private EventDispatcherInterface $dispatcher; + + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $exceptionListeners; + + public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher) + { + $this->map = $map; + $this->dispatcher = $dispatcher; + $this->exceptionListeners = new \SplObjectStorage(); + } + + /** + * @return void + */ + public function onKernelRequest(RequestEvent $event) + { + if (!$event->isMainRequest()) { + return; + } + + // register listeners for this firewall + $listeners = $this->map->getListeners($event->getRequest()); + + $authenticationListeners = $listeners[0]; + $exceptionListener = $listeners[1]; + $logoutListener = $listeners[2]; + + if (null !== $exceptionListener) { + $this->exceptionListeners[$event->getRequest()] = $exceptionListener; + $exceptionListener->register($this->dispatcher); + } + + // Authentication listeners are pre-sorted by SortFirewallListenersPass + $authenticationListeners = function () use ($authenticationListeners, $logoutListener) { + if (null !== $logoutListener) { + $logoutListenerPriority = $this->getListenerPriority($logoutListener); + } + + foreach ($authenticationListeners as $listener) { + $listenerPriority = $this->getListenerPriority($listener); + + // Yielding the LogoutListener at the correct position + if (null !== $logoutListener && $listenerPriority < $logoutListenerPriority) { + yield $logoutListener; + $logoutListener = null; + } + + yield $listener; + } + + // When LogoutListener has the lowest priority of all listeners + if (null !== $logoutListener) { + yield $logoutListener; + } + }; + + $this->callListeners($event, $authenticationListeners()); + } + + /** + * @return void + */ + public function onKernelFinishRequest(FinishRequestEvent $event) + { + $request = $event->getRequest(); + + if (isset($this->exceptionListeners[$request])) { + $this->exceptionListeners[$request]->unregister($this->dispatcher); + unset($this->exceptionListeners[$request]); + } + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + KernelEvents::REQUEST => ['onKernelRequest', 8], + KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest', + ]; + } + + /** + * @return void + */ + protected function callListeners(RequestEvent $event, iterable $listeners) + { + foreach ($listeners as $listener) { + $listener($event); + + if ($event->hasResponse()) { + break; + } + } + } + + private function getListenerPriority(object $logoutListener): int + { + return $logoutListener instanceof FirewallListenerInterface ? $logoutListener->getPriority() : 0; + } +} diff --git a/vendor/symfony/security-http/Firewall/AbstractListener.php b/vendor/symfony/security-http/Firewall/AbstractListener.php new file mode 100644 index 0000000..b5349e5 --- /dev/null +++ b/vendor/symfony/security-http/Firewall/AbstractListener.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * A base class for listeners that can tell whether they should authenticate incoming requests. + * + * @author Nicolas Grekas + */ +abstract class AbstractListener implements FirewallListenerInterface +{ + final public function __invoke(RequestEvent $event): void + { + if (false !== $this->supports($event->getRequest())) { + $this->authenticate($event); + } + } + + public static function getPriority(): int + { + return 0; // Default + } +} diff --git a/vendor/symfony/security-http/Firewall/AccessListener.php b/vendor/symfony/security-http/Firewall/AccessListener.php new file mode 100644 index 0000000..9f43379 --- /dev/null +++ b/vendor/symfony/security-http/Firewall/AccessListener.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Http\AccessMapInterface; +use Symfony\Component\Security\Http\Event\LazyResponseEvent; + +/** + * AccessListener enforces access control rules. + * + * @author Fabien Potencier + * + * @final + */ +class AccessListener extends AbstractListener +{ + private TokenStorageInterface $tokenStorage; + private AccessDecisionManagerInterface $accessDecisionManager; + private AccessMapInterface $map; + + public function __construct(TokenStorageInterface $tokenStorage, AccessDecisionManagerInterface $accessDecisionManager, AccessMapInterface $map, bool $exceptionOnNoToken = false) + { + if (false !== $exceptionOnNoToken) { + throw new \LogicException(sprintf('Argument $exceptionOnNoToken of "%s()" must be set to "false".', __METHOD__)); + } + + $this->tokenStorage = $tokenStorage; + $this->accessDecisionManager = $accessDecisionManager; + $this->map = $map; + } + + public function supports(Request $request): ?bool + { + [$attributes] = $this->map->getPatterns($request); + $request->attributes->set('_access_control_attributes', $attributes); + + if ($attributes && [AuthenticatedVoter::PUBLIC_ACCESS] !== $attributes) { + return true; + } + + return null; + } + + /** + * Handles access authorization. + * + * @throws AccessDeniedException + */ + public function authenticate(RequestEvent $event): void + { + $request = $event->getRequest(); + + $attributes = $request->attributes->get('_access_control_attributes'); + $request->attributes->remove('_access_control_attributes'); + + if (!$attributes || ( + [AuthenticatedVoter::PUBLIC_ACCESS] === $attributes && $event instanceof LazyResponseEvent + )) { + return; + } + + $token = $this->tokenStorage->getToken() ?? new NullToken(); + + if (!$this->accessDecisionManager->decide($token, $attributes, $request, true)) { + throw $this->createAccessDeniedException($request, $attributes); + } + } + + private function createAccessDeniedException(Request $request, array $attributes): AccessDeniedException + { + $exception = new AccessDeniedException(); + $exception->setAttributes($attributes); + $exception->setSubject($request); + + return $exception; + } + + public static function getPriority(): int + { + return -255; + } +} diff --git a/vendor/symfony/security-http/Firewall/AuthenticatorManagerListener.php b/vendor/symfony/security-http/Firewall/AuthenticatorManagerListener.php new file mode 100644 index 0000000..2e77b95 --- /dev/null +++ b/vendor/symfony/security-http/Firewall/AuthenticatorManagerListener.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Http\Authentication\AuthenticatorManagerInterface; + +/** + * Firewall authentication listener that delegates to the authenticator system. + * + * @author Wouter de Jong + */ +class AuthenticatorManagerListener extends AbstractListener +{ + private AuthenticatorManagerInterface $authenticatorManager; + + public function __construct(AuthenticatorManagerInterface $authenticationManager) + { + $this->authenticatorManager = $authenticationManager; + } + + public function supports(Request $request): ?bool + { + return $this->authenticatorManager->supports($request); + } + + public function authenticate(RequestEvent $event): void + { + $request = $event->getRequest(); + $response = $this->authenticatorManager->authenticateRequest($request); + if (null === $response) { + return; + } + + $event->setResponse($response); + } +} diff --git a/vendor/symfony/security-http/Firewall/ChannelListener.php b/vendor/symfony/security-http/Firewall/ChannelListener.php new file mode 100644 index 0000000..6e020bb --- /dev/null +++ b/vendor/symfony/security-http/Firewall/ChannelListener.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Http\AccessMapInterface; + +/** + * ChannelListener switches the HTTP protocol based on the access control + * configuration. + * + * @author Fabien Potencier + * + * @final + */ +class ChannelListener extends AbstractListener +{ + private AccessMapInterface $map; + private ?LoggerInterface $logger; + private int $httpPort; + private int $httpsPort; + + public function __construct(AccessMapInterface $map, ?LoggerInterface $logger = null, int $httpPort = 80, int $httpsPort = 443) + { + $this->map = $map; + $this->logger = $logger; + $this->httpPort = $httpPort; + $this->httpsPort = $httpsPort; + } + + /** + * Handles channel management. + */ + public function supports(Request $request): ?bool + { + [, $channel] = $this->map->getPatterns($request); + + if ('https' === $channel && !$request->isSecure()) { + if (null !== $this->logger) { + if ('https' === $request->headers->get('X-Forwarded-Proto')) { + $this->logger->info('Redirecting to HTTPS. ("X-Forwarded-Proto" header is set to "https" - did you set "trusted_proxies" correctly?)'); + } elseif (str_contains($request->headers->get('Forwarded', ''), 'proto=https')) { + $this->logger->info('Redirecting to HTTPS. ("Forwarded" header is set to "proto=https" - did you set "trusted_proxies" correctly?)'); + } else { + $this->logger->info('Redirecting to HTTPS.'); + } + } + + return true; + } + + if ('http' === $channel && $request->isSecure()) { + $this->logger?->info('Redirecting to HTTP.'); + + return true; + } + + return false; + } + + public function authenticate(RequestEvent $event): void + { + $request = $event->getRequest(); + + $event->setResponse($this->createRedirectResponse($request)); + } + + private function createRedirectResponse(Request $request): RedirectResponse + { + $scheme = $request->isSecure() ? 'http' : 'https'; + if ('http' === $scheme && 80 != $this->httpPort) { + $port = ':'.$this->httpPort; + } elseif ('https' === $scheme && 443 != $this->httpsPort) { + $port = ':'.$this->httpsPort; + } else { + $port = ''; + } + + $qs = $request->getQueryString(); + if (null !== $qs) { + $qs = '?'.$qs; + } + + $url = $scheme.'://'.$request->getHost().$port.$request->getBaseUrl().$request->getPathInfo().$qs; + + return new RedirectResponse($url, 301); + } +} diff --git a/vendor/symfony/security-http/Firewall/ContextListener.php b/vendor/symfony/security-http/Firewall/ContextListener.php new file mode 100644 index 0000000..1b3ff9d --- /dev/null +++ b/vendor/symfony/security-http/Firewall/ContextListener.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\EquatableInterface; +use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * ContextListener manages the SecurityContext persistence through a session. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + * + * @final + */ +class ContextListener extends AbstractListener +{ + private TokenStorageInterface $tokenStorage; + private string $sessionKey; + private ?LoggerInterface $logger; + private iterable $userProviders; + private ?EventDispatcherInterface $dispatcher; + private bool $registered = false; + private AuthenticationTrustResolverInterface $trustResolver; + private ?\Closure $sessionTrackerEnabler; + + /** + * @param iterable $userProviders + */ + public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, ?AuthenticationTrustResolverInterface $trustResolver = null, ?callable $sessionTrackerEnabler = null) + { + if (!$contextKey) { + throw new \InvalidArgumentException('$contextKey must not be empty.'); + } + + $this->tokenStorage = $tokenStorage; + $this->userProviders = $userProviders; + $this->sessionKey = '_security_'.$contextKey; + $this->logger = $logger; + $this->dispatcher = $dispatcher; + + $this->trustResolver = $trustResolver ?? new AuthenticationTrustResolver(); + $this->sessionTrackerEnabler = null === $sessionTrackerEnabler ? null : $sessionTrackerEnabler(...); + } + + public function supports(Request $request): ?bool + { + return null; // always run authenticate() lazily with lazy firewalls + } + + /** + * Reads the Security Token from the session. + */ + public function authenticate(RequestEvent $event): void + { + if (!$this->registered && null !== $this->dispatcher && $event->isMainRequest()) { + $this->dispatcher->addListener(KernelEvents::RESPONSE, $this->onKernelResponse(...)); + $this->registered = true; + } + + $request = $event->getRequest(); + $session = $request->hasPreviousSession() ? $request->getSession() : null; + + $request->attributes->set('_security_firewall_run', $this->sessionKey); + + if (null !== $session) { + $usageIndexValue = $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : 0; + $usageIndexReference = \PHP_INT_MIN; + $sessionId = $request->cookies->all()[$session->getName()] ?? null; + $token = $session->get($this->sessionKey); + + // sessionId = true is used in the tests + if ($this->sessionTrackerEnabler && \in_array($sessionId, [true, $session->getId()], true)) { + $usageIndexReference = $usageIndexValue; + } else { + $usageIndexReference = $usageIndexReference - \PHP_INT_MIN + $usageIndexValue; + } + } + + if (null === $session || null === $token) { + if ($this->sessionTrackerEnabler) { + ($this->sessionTrackerEnabler)(); + } + + $this->tokenStorage->setToken(null); + + return; + } + + $token = $this->safelyUnserialize($token); + + $this->logger?->debug('Read existing security token from the session.', [ + 'key' => $this->sessionKey, + 'token_class' => \is_object($token) ? $token::class : null, + ]); + + if ($token instanceof TokenInterface) { + $originalToken = $token; + $token = $this->refreshUser($token); + + if (!$token) { + $this->logger?->debug('Token was deauthenticated after trying to refresh it.'); + + $this->dispatcher?->dispatch(new TokenDeauthenticatedEvent($originalToken, $request)); + } + } elseif (null !== $token) { + $this->logger?->warning('Expected a security token from the session, got something else.', ['key' => $this->sessionKey, 'received' => $token]); + + $token = null; + } + + if ($this->sessionTrackerEnabler) { + ($this->sessionTrackerEnabler)(); + } + + $this->tokenStorage->setToken($token); + } + + /** + * Writes the security token into the session. + */ + public function onKernelResponse(ResponseEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $request = $event->getRequest(); + + if (!$request->hasSession() || $request->attributes->get('_security_firewall_run') !== $this->sessionKey) { + return; + } + + $this->dispatcher?->removeListener(KernelEvents::RESPONSE, $this->onKernelResponse(...)); + $this->registered = false; + $session = $request->getSession(); + $sessionId = $session->getId(); + $usageIndexValue = $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : null; + $token = $this->tokenStorage->getToken(); + + if (!$this->trustResolver->isAuthenticated($token)) { + if ($request->hasPreviousSession()) { + $session->remove($this->sessionKey); + } + } else { + $session->set($this->sessionKey, serialize($token)); + + $this->logger?->debug('Stored the security token in the session.', ['key' => $this->sessionKey]); + } + + if ($this->sessionTrackerEnabler && $session->getId() === $sessionId) { + $usageIndexReference = $usageIndexValue; + } + } + + /** + * Refreshes the user by reloading it from the user provider. + * + * @throws \RuntimeException + */ + protected function refreshUser(TokenInterface $token): ?TokenInterface + { + $user = $token->getUser(); + + $userNotFoundByProvider = false; + $userDeauthenticated = false; + $userClass = $user::class; + + foreach ($this->userProviders as $provider) { + if (!$provider instanceof UserProviderInterface) { + throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "%s".', get_debug_type($provider), UserProviderInterface::class)); + } + + if (!$provider->supportsClass($userClass)) { + continue; + } + + try { + $refreshedUser = $provider->refreshUser($user); + $newToken = clone $token; + $newToken->setUser($refreshedUser, false); + + // tokens can be deauthenticated if the user has been changed. + if ($token instanceof AbstractToken && $this->hasUserChanged($user, $newToken)) { + $userDeauthenticated = true; + + $this->logger?->debug('Cannot refresh token because user has changed.', ['username' => $refreshedUser->getUserIdentifier(), 'provider' => $provider::class]); + + continue; + } + + $token->setUser($refreshedUser); + + if (null !== $this->logger) { + $context = ['provider' => $provider::class, 'username' => $refreshedUser->getUserIdentifier()]; + + if ($token instanceof SwitchUserToken) { + $originalToken = $token->getOriginalToken(); + $context['impersonator_username'] = $originalToken->getUserIdentifier(); + } + + $this->logger->debug('User was reloaded from a user provider.', $context); + } + + return $token; + } catch (UnsupportedUserException) { + // let's try the next user provider + } catch (UserNotFoundException $e) { + $this->logger?->info('Username could not be found in the selected user provider.', ['username' => $e->getUserIdentifier(), 'provider' => $provider::class]); + + $userNotFoundByProvider = true; + } + } + + if ($userDeauthenticated) { + return null; + } + + if ($userNotFoundByProvider) { + return null; + } + + throw new \RuntimeException(sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', $userClass)); + } + + private function safelyUnserialize(string $serializedToken): mixed + { + $token = null; + $prevUnserializeHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler) { + if (__FILE__ === $file && !\in_array($type, [\E_DEPRECATED, \E_USER_DEPRECATED], true)) { + throw new \ErrorException($msg, 0x37313BC, $type, $file, $line); + } + + return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false; + }); + + try { + $token = unserialize($serializedToken); + } catch (\ErrorException $e) { + if (0x37313BC !== $e->getCode()) { + throw $e; + } + $this->logger?->warning('Failed to unserialize the security token from the session.', ['key' => $this->sessionKey, 'received' => $serializedToken, 'exception' => $e]); + } finally { + restore_error_handler(); + ini_set('unserialize_callback_func', $prevUnserializeHandler); + } + + return $token; + } + + private static function hasUserChanged(UserInterface $originalUser, TokenInterface $refreshedToken): bool + { + $refreshedUser = $refreshedToken->getUser(); + + if ($originalUser instanceof EquatableInterface) { + return !$originalUser->isEqualTo($refreshedUser); + } + + if ($originalUser instanceof PasswordAuthenticatedUserInterface || $refreshedUser instanceof PasswordAuthenticatedUserInterface) { + if (!$originalUser instanceof PasswordAuthenticatedUserInterface || !$refreshedUser instanceof PasswordAuthenticatedUserInterface || $originalUser->getPassword() !== $refreshedUser->getPassword()) { + return true; + } + + if ($originalUser instanceof LegacyPasswordAuthenticatedUserInterface xor $refreshedUser instanceof LegacyPasswordAuthenticatedUserInterface) { + return true; + } + + if ($originalUser instanceof LegacyPasswordAuthenticatedUserInterface && $refreshedUser instanceof LegacyPasswordAuthenticatedUserInterface && $originalUser->getSalt() !== $refreshedUser->getSalt()) { + return true; + } + } + + $userRoles = array_map('strval', (array) $refreshedUser->getRoles()); + + if ( + \count($userRoles) !== \count($refreshedToken->getRoleNames()) + || \count($userRoles) !== \count(array_intersect($userRoles, $refreshedToken->getRoleNames())) + ) { + return true; + } + + if ($originalUser->getUserIdentifier() !== $refreshedUser->getUserIdentifier()) { + return true; + } + + return false; + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class): never + { + throw new \ErrorException('Class not found: '.$class, 0x37313BC); + } +} diff --git a/vendor/symfony/security-http/Firewall/ExceptionListener.php b/vendor/symfony/security-http/Firewall/ExceptionListener.php new file mode 100644 index 0000000..8a2ce8b --- /dev/null +++ b/vendor/symfony/security-http/Firewall/ExceptionListener.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Exception\AccountStatusException; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException; +use Symfony\Component\Security\Core\Exception\LazyResponseException; +use Symfony\Component\Security\Core\Exception\LogoutException; +use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; +use Symfony\Component\Security\Http\Util\TargetPathTrait; + +/** + * ExceptionListener catches authentication exception and converts them to + * Response instances. + * + * @author Fabien Potencier + * + * @final + */ +class ExceptionListener +{ + use TargetPathTrait; + + private TokenStorageInterface $tokenStorage; + private string $firewallName; + private ?AccessDeniedHandlerInterface $accessDeniedHandler; + private ?AuthenticationEntryPointInterface $authenticationEntryPoint; + private AuthenticationTrustResolverInterface $authenticationTrustResolver; + private ?string $errorPage; + private ?LoggerInterface $logger; + private HttpUtils $httpUtils; + private bool $stateless; + + public function __construct(TokenStorageInterface $tokenStorage, AuthenticationTrustResolverInterface $trustResolver, HttpUtils $httpUtils, string $firewallName, ?AuthenticationEntryPointInterface $authenticationEntryPoint = null, ?string $errorPage = null, ?AccessDeniedHandlerInterface $accessDeniedHandler = null, ?LoggerInterface $logger = null, bool $stateless = false) + { + $this->tokenStorage = $tokenStorage; + $this->accessDeniedHandler = $accessDeniedHandler; + $this->httpUtils = $httpUtils; + $this->firewallName = $firewallName; + $this->authenticationEntryPoint = $authenticationEntryPoint; + $this->authenticationTrustResolver = $trustResolver; + $this->errorPage = $errorPage; + $this->logger = $logger; + $this->stateless = $stateless; + } + + /** + * Registers a onKernelException listener to take care of security exceptions. + */ + public function register(EventDispatcherInterface $dispatcher): void + { + $dispatcher->addListener(KernelEvents::EXCEPTION, $this->onKernelException(...), 1); + } + + /** + * Unregisters the dispatcher. + */ + public function unregister(EventDispatcherInterface $dispatcher): void + { + $dispatcher->removeListener(KernelEvents::EXCEPTION, $this->onKernelException(...)); + } + + /** + * Handles security related exceptions. + */ + public function onKernelException(ExceptionEvent $event): void + { + $exception = $event->getThrowable(); + do { + if ($exception instanceof AuthenticationException) { + $this->handleAuthenticationException($event, $exception); + + return; + } + + if ($exception instanceof AccessDeniedException) { + $this->handleAccessDeniedException($event, $exception); + + return; + } + + if ($exception instanceof LazyResponseException) { + $event->setResponse($exception->getResponse()); + + return; + } + + if ($exception instanceof LogoutException) { + $this->handleLogoutException($event, $exception); + + return; + } + } while (null !== $exception = $exception->getPrevious()); + } + + private function handleAuthenticationException(ExceptionEvent $event, AuthenticationException $exception): void + { + $this->logger?->info('An AuthenticationException was thrown; redirecting to authentication entry point.', ['exception' => $exception]); + + try { + $event->setResponse($this->startAuthentication($event->getRequest(), $exception)); + $event->allowCustomResponseCode(); + } catch (\Exception $e) { + $event->setThrowable($e); + } + } + + private function handleAccessDeniedException(ExceptionEvent $event, AccessDeniedException $exception): void + { + $event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception)); + + $token = $this->tokenStorage->getToken(); + if (!$this->authenticationTrustResolver->isFullFledged($token)) { + $this->logger?->debug('Access denied, the user is not fully authenticated; redirecting to authentication entry point.', ['exception' => $exception]); + + try { + $insufficientAuthenticationException = new InsufficientAuthenticationException('Full authentication is required to access this resource.', 0, $exception); + if (null !== $token) { + $insufficientAuthenticationException->setToken($token); + } + + $event->setResponse($this->startAuthentication($event->getRequest(), $insufficientAuthenticationException)); + } catch (\Exception $e) { + $event->setThrowable($e); + } + + return; + } + + $this->logger?->debug('Access denied, the user is neither anonymous, nor remember-me.', ['exception' => $exception]); + + try { + if (null !== $this->accessDeniedHandler) { + $response = $this->accessDeniedHandler->handle($event->getRequest(), $exception); + + if ($response instanceof Response) { + $event->setResponse($response); + } + } elseif (null !== $this->errorPage) { + $subRequest = $this->httpUtils->createRequest($event->getRequest(), $this->errorPage); + $subRequest->attributes->set(SecurityRequestAttributes::ACCESS_DENIED_ERROR, $exception); + + $event->setResponse($event->getKernel()->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true)); + $event->allowCustomResponseCode(); + } + } catch (\Exception $e) { + $this->logger?->error('An exception was thrown when handling an AccessDeniedException.', ['exception' => $e]); + + $event->setThrowable(new \RuntimeException('Exception thrown when handling an exception.', 0, $e)); + } + } + + private function handleLogoutException(ExceptionEvent $event, LogoutException $exception): void + { + $event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception)); + + $this->logger?->info('A LogoutException was thrown; wrapping with AccessDeniedHttpException', ['exception' => $exception]); + } + + private function startAuthentication(Request $request, AuthenticationException $authException): Response + { + if (null === $this->authenticationEntryPoint) { + $this->throwUnauthorizedException($authException); + } + + $this->logger?->debug('Calling Authentication entry point.', ['entry_point' => $this->authenticationEntryPoint]); + + if (!$this->stateless) { + $this->setTargetPath($request); + } + + if ($authException instanceof AccountStatusException) { + // remove the security token to prevent infinite redirect loops + $this->tokenStorage->setToken(null); + + $this->logger?->info('The security token was removed due to an AccountStatusException.', ['exception' => $authException]); + } + + try { + $response = $this->authenticationEntryPoint->start($request, $authException); + } catch (NotAnEntryPointException) { + $this->throwUnauthorizedException($authException); + } + + if (!$response instanceof Response) { + $given = get_debug_type($response); + + throw new \LogicException(sprintf('The "%s::start()" method must return a Response object ("%s" returned).', get_debug_type($this->authenticationEntryPoint), $given)); + } + + return $response; + } + + protected function setTargetPath(Request $request): void + { + // session isn't required when using HTTP basic authentication mechanism for example + if ($request->hasSession() && $request->isMethodSafe() && !$request->isXmlHttpRequest()) { + $this->saveTargetPath($request->getSession(), $this->firewallName, $request->getUri()); + } + } + + private function throwUnauthorizedException(AuthenticationException $authException): never + { + $this->logger?->notice(sprintf('No Authentication entry point configured, returning a %s HTTP response. Configure "entry_point" on the firewall "%s" if you want to modify the response.', Response::HTTP_UNAUTHORIZED, $this->firewallName)); + + throw new HttpException(Response::HTTP_UNAUTHORIZED, $authException->getMessage(), $authException, [], $authException->getCode()); + } +} diff --git a/vendor/symfony/security-http/Firewall/FirewallListenerInterface.php b/vendor/symfony/security-http/Firewall/FirewallListenerInterface.php new file mode 100644 index 0000000..27f1e1b --- /dev/null +++ b/vendor/symfony/security-http/Firewall/FirewallListenerInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * Can be implemented by firewall listeners. + * + * @author Christian Scheb + * @author Nicolas Grekas + * @author Robin Chalas + */ +interface FirewallListenerInterface +{ + /** + * Tells whether the authenticate() method should be called or not depending on the incoming request. + * + * Returning null means authenticate() can be called lazily when accessing the token storage. + */ + public function supports(Request $request): ?bool; + + /** + * Does whatever is required to authenticate the request, typically calling $event->setResponse() internally. + */ + public function authenticate(RequestEvent $event): void; + + /** + * Defines the priority of the listener. + * The higher the number, the earlier a listener is executed. + */ + public static function getPriority(): int; +} diff --git a/vendor/symfony/security-http/Firewall/LogoutListener.php b/vendor/symfony/security-http/Firewall/LogoutListener.php new file mode 100644 index 0000000..86c7375 --- /dev/null +++ b/vendor/symfony/security-http/Firewall/LogoutListener.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\LogoutException; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * LogoutListener logout users. + * + * @author Fabien Potencier + * + * @final + */ +class LogoutListener extends AbstractListener +{ + private TokenStorageInterface $tokenStorage; + private array $options; + private HttpUtils $httpUtils; + private ?CsrfTokenManagerInterface $csrfTokenManager; + private EventDispatcherInterface $eventDispatcher; + + /** + * @param array $options An array of options to process a logout attempt + */ + public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $httpUtils, EventDispatcherInterface $eventDispatcher, array $options = [], ?CsrfTokenManagerInterface $csrfTokenManager = null) + { + $this->tokenStorage = $tokenStorage; + $this->httpUtils = $httpUtils; + $this->options = array_merge([ + 'csrf_parameter' => '_csrf_token', + 'csrf_token_id' => 'logout', + 'logout_path' => '/logout', + ], $options); + $this->csrfTokenManager = $csrfTokenManager; + $this->eventDispatcher = $eventDispatcher; + } + + public function supports(Request $request): ?bool + { + return $this->requiresLogout($request); + } + + /** + * Performs the logout if requested. + * + * If a CsrfTokenManagerInterface instance is available, it will be used to + * validate the request. + * + * @throws LogoutException if the CSRF token is invalid + * @throws \RuntimeException if the LogoutEvent listener does not set a response + */ + public function authenticate(RequestEvent $event): void + { + $request = $event->getRequest(); + + if (null !== $this->csrfTokenManager) { + $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); + + if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { + throw new LogoutException('Invalid CSRF token.'); + } + } + + $logoutEvent = new LogoutEvent($request, $this->tokenStorage->getToken()); + $this->eventDispatcher->dispatch($logoutEvent); + + if (!$response = $logoutEvent->getResponse()) { + throw new \RuntimeException('No logout listener set the Response, make sure at least the DefaultLogoutListener is registered.'); + } + + $this->tokenStorage->setToken(null); + + $event->setResponse($response); + } + + /** + * Whether this request is asking for logout. + * + * The default implementation only processed requests to a specific path, + * but a subclass could change this to logout requests where + * certain parameters is present. + */ + protected function requiresLogout(Request $request): bool + { + return isset($this->options['logout_path']) && $this->httpUtils->checkRequestPath($request, $this->options['logout_path']); + } + + public static function getPriority(): int + { + return -127; + } +} diff --git a/vendor/symfony/security-http/Firewall/SwitchUserListener.php b/vendor/symfony/security-http/Firewall/SwitchUserListener.php new file mode 100644 index 0000000..a8c7a65 --- /dev/null +++ b/vendor/symfony/security-http/Firewall/SwitchUserListener.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Event\SwitchUserEvent; +use Symfony\Component\Security\Http\SecurityEvents; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * SwitchUserListener allows a user to impersonate another one temporarily + * (like the Unix su command). + * + * @author Fabien Potencier + * + * @final + */ +class SwitchUserListener extends AbstractListener +{ + public const EXIT_VALUE = '_exit'; + + private TokenStorageInterface $tokenStorage; + private UserProviderInterface $provider; + private UserCheckerInterface $userChecker; + private string $firewallName; + private AccessDecisionManagerInterface $accessDecisionManager; + private string $usernameParameter; + private string $role; + private ?LoggerInterface $logger; + private ?EventDispatcherInterface $dispatcher; + private bool $stateless; + private ?UrlGeneratorInterface $urlGenerator; + private ?string $targetRoute; + + public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $firewallName, AccessDecisionManagerInterface $accessDecisionManager, ?LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', ?EventDispatcherInterface $dispatcher = null, bool $stateless = false, ?UrlGeneratorInterface $urlGenerator = null, ?string $targetRoute = null) + { + if ('' === $firewallName) { + throw new \InvalidArgumentException('$firewallName must not be empty.'); + } + + $this->tokenStorage = $tokenStorage; + $this->provider = $provider; + $this->userChecker = $userChecker; + $this->firewallName = $firewallName; + $this->accessDecisionManager = $accessDecisionManager; + $this->usernameParameter = $usernameParameter; + $this->role = $role; + $this->logger = $logger; + $this->dispatcher = $dispatcher; + $this->stateless = $stateless; + $this->urlGenerator = $urlGenerator; + $this->targetRoute = $targetRoute; + } + + public function supports(Request $request): ?bool + { + // usernames can be falsy + $username = $request->get($this->usernameParameter); + + if (null === $username || '' === $username) { + $username = $request->headers->get($this->usernameParameter); + } + + // if it's still "empty", nothing to do. + if (null === $username || '' === $username) { + return false; + } + + $request->attributes->set('_switch_user_username', $username); + + return true; + } + + /** + * Handles the switch to another user. + * + * @throws \LogicException if switching to a user failed + */ + public function authenticate(RequestEvent $event): void + { + $request = $event->getRequest(); + + $username = $request->attributes->get('_switch_user_username'); + $request->attributes->remove('_switch_user_username'); + + if (null === $this->tokenStorage->getToken()) { + throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); + } + + if (self::EXIT_VALUE === $username) { + $this->tokenStorage->setToken($this->attemptExitUser($request)); + } else { + try { + $this->tokenStorage->setToken($this->attemptSwitchUser($request, $username)); + } catch (AuthenticationException $e) { + // Generate 403 in any conditions to prevent user enumeration vulnerabilities + throw new AccessDeniedException('Switch User failed: '.$e->getMessage(), $e); + } + } + + if (!$this->stateless) { + $request->query->remove($this->usernameParameter); + $request->server->set('QUERY_STRING', http_build_query($request->query->all(), '', '&')); + $response = new RedirectResponse($this->urlGenerator && $this->targetRoute ? $this->urlGenerator->generate($this->targetRoute) : $request->getUri(), 302); + + $event->setResponse($response); + } + } + + /** + * Attempts to switch to another user and returns the new token if successfully switched. + * + * @throws \LogicException + * @throws AccessDeniedException + */ + private function attemptSwitchUser(Request $request, string $username): ?TokenInterface + { + $token = $this->tokenStorage->getToken(); + $originalToken = $this->getOriginalToken($token); + + if (null !== $originalToken) { + if ($token->getUserIdentifier() === $username) { + return $token; + } + + // User already switched, exit before seamlessly switching to another user + $token = $this->attemptExitUser($request); + } + + $currentUsername = $token->getUserIdentifier(); + $nonExistentUsername = '_'.hash('xxh128', random_bytes(8).$username); + + // To protect against user enumeration via timing measurements + // we always load both successfully and unsuccessfully + try { + $user = $this->provider->loadUserByIdentifier($username); + + try { + $this->provider->loadUserByIdentifier($nonExistentUsername); + } catch (\Exception) { + } + } catch (AuthenticationException $e) { + $this->provider->loadUserByIdentifier($currentUsername); + + throw $e; + } + + if (false === $this->accessDecisionManager->decide($token, [$this->role], $user)) { + $exception = new AccessDeniedException(); + $exception->setAttributes($this->role); + + throw $exception; + } + + $this->logger?->info('Attempting to switch to user.', ['username' => $username]); + + $this->userChecker->checkPostAuth($user); + + $roles = $user->getRoles(); + $originatedFromUri = str_replace('/&', '/?', preg_replace('#[&?]'.$this->usernameParameter.'=[^&]*#', '', $request->getRequestUri())); + $token = new SwitchUserToken($user, $this->firewallName, $roles, $token, $originatedFromUri); + + if (null !== $this->dispatcher) { + $switchEvent = new SwitchUserEvent($request, $token->getUser(), $token); + $this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER); + // use the token from the event in case any listeners have replaced it. + $token = $switchEvent->getToken(); + } + + return $token; + } + + /** + * Attempts to exit from an already switched user and returns the original token. + * + * @throws AuthenticationCredentialsNotFoundException + */ + private function attemptExitUser(Request $request): TokenInterface + { + if (null === ($currentToken = $this->tokenStorage->getToken()) || null === $original = $this->getOriginalToken($currentToken)) { + throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); + } + + if (null !== $this->dispatcher && $original->getUser() instanceof UserInterface) { + $user = $this->provider->refreshUser($original->getUser()); + $original->setUser($user); + $switchEvent = new SwitchUserEvent($request, $user, $original); + $this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER); + $original = $switchEvent->getToken(); + } + + return $original; + } + + private function getOriginalToken(TokenInterface $token): ?TokenInterface + { + if ($token instanceof SwitchUserToken) { + return $token->getOriginalToken(); + } + + return null; + } +} diff --git a/vendor/symfony/security-http/FirewallMap.php b/vendor/symfony/security-http/FirewallMap.php new file mode 100644 index 0000000..3b01cbd --- /dev/null +++ b/vendor/symfony/security-http/FirewallMap.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; + +/** + * FirewallMap allows configuration of different firewalls for specific parts + * of the website. + * + * @author Fabien Potencier + */ +class FirewallMap implements FirewallMapInterface +{ + /** + * @var list, ExceptionListener|null, LogoutListener|null}> + */ + private array $map = []; + + /** + * @param list $listeners + */ + public function add(?RequestMatcherInterface $requestMatcher = null, array $listeners = [], ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null): void + { + $this->map[] = [$requestMatcher, $listeners, $exceptionListener, $logoutListener]; + } + + public function getListeners(Request $request): array + { + foreach ($this->map as $elements) { + if (null === $elements[0] || $elements[0]->matches($request)) { + return [$elements[1], $elements[2], $elements[3]]; + } + } + + return [[], null, null]; + } +} diff --git a/vendor/symfony/security-http/FirewallMapInterface.php b/vendor/symfony/security-http/FirewallMapInterface.php new file mode 100644 index 0000000..fa43d6a --- /dev/null +++ b/vendor/symfony/security-http/FirewallMapInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; + +/** + * This interface must be implemented by firewall maps. + * + * @author Johannes M. Schmitt + */ +interface FirewallMapInterface +{ + /** + * Returns the authentication listeners, and the exception listener to use + * for the given request. + * + * If there are no authentication listeners, the first inner array must be + * empty. + * + * If there is no exception listener, the second element of the outer array + * must be null. + * + * If there is no logout listener, the third element of the outer array + * must be null. + * + * @return array{iterable, ExceptionListener, LogoutListener} + */ + public function getListeners(Request $request): array; +} diff --git a/vendor/symfony/security-http/HttpUtils.php b/vendor/symfony/security-http/HttpUtils.php new file mode 100644 index 0000000..eef4d8d --- /dev/null +++ b/vendor/symfony/security-http/HttpUtils.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * Encapsulates the logic needed to create sub-requests, redirect the user, and match URLs. + * + * @author Fabien Potencier + */ +class HttpUtils +{ + private ?UrlGeneratorInterface $urlGenerator; + private UrlMatcherInterface|RequestMatcherInterface|null $urlMatcher; + private ?string $domainRegexp; + private ?string $secureDomainRegexp; + + /** + * @param $domainRegexp A regexp the target of HTTP redirections must match, scheme included + * @param $secureDomainRegexp A regexp the target of HTTP redirections must match when the scheme is "https" + * + * @throws \InvalidArgumentException + */ + public function __construct(?UrlGeneratorInterface $urlGenerator = null, UrlMatcherInterface|RequestMatcherInterface|null $urlMatcher = null, ?string $domainRegexp = null, ?string $secureDomainRegexp = null) + { + $this->urlGenerator = $urlGenerator; + $this->urlMatcher = $urlMatcher; + $this->domainRegexp = $domainRegexp; + $this->secureDomainRegexp = $secureDomainRegexp; + } + + /** + * Creates a redirect Response. + * + * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) + * @param int $status The HTTP status code (302 "Found" by default) + */ + public function createRedirectResponse(Request $request, string $path, int $status = 302): RedirectResponse + { + if (null !== $this->secureDomainRegexp && 'https' === $this->urlMatcher->getContext()->getScheme() && preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !preg_match(sprintf($this->secureDomainRegexp, preg_quote($request->getHttpHost())), $host[0])) { + $path = '/'; + } + if (null !== $this->domainRegexp && preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) { + $path = '/'; + } + + return new RedirectResponse($this->generateUri($request, $path), $status); + } + + /** + * Creates a Request. + * + * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) + */ + public function createRequest(Request $request, string $path): Request + { + $newRequest = Request::create($this->generateUri($request, $path), 'get', [], $request->cookies->all(), [], $request->server->all()); + + static $setSession; + + $setSession ??= \Closure::bind(static function ($newRequest, $request) { $newRequest->session = $request->session; }, null, Request::class); + $setSession($newRequest, $request); + + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $newRequest->attributes->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR)); + } + if ($request->attributes->has(SecurityRequestAttributes::ACCESS_DENIED_ERROR)) { + $newRequest->attributes->set(SecurityRequestAttributes::ACCESS_DENIED_ERROR, $request->attributes->get(SecurityRequestAttributes::ACCESS_DENIED_ERROR)); + } + if ($request->attributes->has(SecurityRequestAttributes::LAST_USERNAME)) { + $newRequest->attributes->set(SecurityRequestAttributes::LAST_USERNAME, $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME)); + } + + if ($request->get('_format')) { + $newRequest->attributes->set('_format', $request->get('_format')); + } + if ($request->getDefaultLocale() !== $request->getLocale()) { + $newRequest->setLocale($request->getLocale()); + } + + return $newRequest; + } + + /** + * Checks that a given path matches the Request. + * + * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) + * + * @return bool true if the path is the same as the one from the Request, false otherwise + */ + public function checkRequestPath(Request $request, string $path): bool + { + if ('/' !== $path[0]) { + // Shortcut if request has already been matched before + if ($request->attributes->has('_route')) { + return $path === $request->attributes->get('_route'); + } + + try { + // matching a request is more powerful than matching a URL path + context, so try that first + if ($this->urlMatcher instanceof RequestMatcherInterface) { + $parameters = $this->urlMatcher->matchRequest($request); + } else { + $parameters = $this->urlMatcher->match($request->getPathInfo()); + } + + return isset($parameters['_route']) && $path === $parameters['_route']; + } catch (MethodNotAllowedException|ResourceNotFoundException) { + return false; + } + } + + return $path === rawurldecode($request->getPathInfo()); + } + + /** + * Generates a URI, based on the given path or absolute URL. + * + * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) + * + * @throws \LogicException + */ + public function generateUri(Request $request, string $path): string + { + $url = parse_url($path); + + if ('' === $path || isset($url['scheme'], $url['host'])) { + return $path; + } + + if ('/' === $path[0]) { + return $request->getUriForPath($path); + } + + if (null === $this->urlGenerator) { + throw new \LogicException('You must provide a UrlGeneratorInterface instance to be able to use routes.'); + } + + $url = $this->urlGenerator->generate($path, $request->attributes->all(), UrlGeneratorInterface::ABSOLUTE_URL); + + // unnecessary query string parameters must be removed from URL + // (ie. query parameters that are presents in $attributes) + // fortunately, they all are, so we have to remove entire query string + $position = strpos($url, '?'); + if (false !== $position) { + $fragment = parse_url($url, \PHP_URL_FRAGMENT); + $url = substr($url, 0, $position); + // fragment must be preserved + if ($fragment) { + $url .= "#$fragment"; + } + } + + return $url; + } +} diff --git a/vendor/symfony/security-http/Impersonate/ImpersonateUrlGenerator.php b/vendor/symfony/security-http/Impersonate/ImpersonateUrlGenerator.php new file mode 100644 index 0000000..98bf711 --- /dev/null +++ b/vendor/symfony/security-http/Impersonate/ImpersonateUrlGenerator.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Impersonate; + +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; +use Symfony\Component\Security\Http\Firewall\SwitchUserListener; + +/** + * Provides generator functions for the impersonation urls. + * + * @author Amrouche Hamza + * @author Damien Fayet + */ +class ImpersonateUrlGenerator +{ + private RequestStack $requestStack; + private TokenStorageInterface $tokenStorage; + private FirewallMap $firewallMap; + + public function __construct(RequestStack $requestStack, FirewallMap $firewallMap, TokenStorageInterface $tokenStorage) + { + $this->requestStack = $requestStack; + $this->tokenStorage = $tokenStorage; + $this->firewallMap = $firewallMap; + } + + public function generateImpersonationPath(string $identifier): string + { + return $this->buildPath(null, $identifier); + } + + public function generateImpersonationUrl(string $identifier): string + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return ''; + } + + return $request->getUriForPath($this->buildPath(null, $identifier)); + } + + public function generateExitPath(?string $targetUri = null): string + { + return $this->buildPath($targetUri); + } + + public function generateExitUrl(?string $targetUri = null): string + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return ''; + } + + return $request->getUriForPath($this->buildPath($targetUri)); + } + + private function isImpersonatedUser(): bool + { + return $this->tokenStorage->getToken() instanceof SwitchUserToken; + } + + private function buildPath(?string $targetUri = null, string $identifier = SwitchUserListener::EXIT_VALUE): string + { + if (null === ($request = $this->requestStack->getCurrentRequest())) { + return ''; + } + + if (!$this->isImpersonatedUser() && SwitchUserListener::EXIT_VALUE == $identifier) { + return ''; + } + + if (null === $switchUserConfig = $this->firewallMap->getFirewallConfig($request)->getSwitchUser()) { + throw new \LogicException('Unable to generate the impersonate URLs without a firewall configured for the user switch.'); + } + + $targetUri ??= $request->getRequestUri(); + + $targetUri .= (parse_url($targetUri, \PHP_URL_QUERY) ? '&' : '?').http_build_query([$switchUserConfig['parameter'] => $identifier], '', '&'); + + return $targetUri; + } +} diff --git a/vendor/symfony/security-http/LICENSE b/vendor/symfony/security-http/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/security-http/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/security-http/LoginLink/Exception/ExpiredLoginLinkException.php b/vendor/symfony/security-http/LoginLink/Exception/ExpiredLoginLinkException.php new file mode 100644 index 0000000..7971ddb --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/Exception/ExpiredLoginLinkException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink\Exception; + +use Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; + +/** + * @author Ryan Weaver + */ +class ExpiredLoginLinkException extends ExpiredSignatureException implements InvalidLoginLinkExceptionInterface +{ +} diff --git a/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php b/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php new file mode 100644 index 0000000..f2debd9 --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink\Exception; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * Thrown when a login link is invalid. + * + * @author Ryan Weaver + */ +class InvalidLoginLinkAuthenticationException extends AuthenticationException +{ + public function getMessageKey(): string + { + return 'Invalid or expired login link.'; + } +} diff --git a/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkException.php b/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkException.php new file mode 100644 index 0000000..5683c18 --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink\Exception; + +/** + * @author Ryan Weaver + */ +class InvalidLoginLinkException extends \RuntimeException implements InvalidLoginLinkExceptionInterface +{ +} diff --git a/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php b/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php new file mode 100644 index 0000000..e32e8dc --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink\Exception; + +/** + * @author Ryan Weaver + */ +interface InvalidLoginLinkExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/security-http/LoginLink/LoginLinkDetails.php b/vendor/symfony/security-http/LoginLink/LoginLinkDetails.php new file mode 100644 index 0000000..dba98de --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/LoginLinkDetails.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink; + +/** + * @author Ryan Weaver + */ +class LoginLinkDetails +{ + private string $url; + private \DateTimeImmutable $expiresAt; + + public function __construct(string $url, \DateTimeImmutable $expiresAt) + { + $this->url = $url; + $this->expiresAt = $expiresAt; + } + + public function getUrl(): string + { + return $this->url; + } + + public function getExpiresAt(): \DateTimeImmutable + { + return $this->expiresAt; + } + + public function __toString(): string + { + return $this->url; + } +} diff --git a/vendor/symfony/security-http/LoginLink/LoginLinkHandler.php b/vendor/symfony/security-http/LoginLink/LoginLinkHandler.php new file mode 100644 index 0000000..176d316 --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/LoginLinkHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; +use Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException; +use Symfony\Component\Security\Core\Signature\SignatureHasher; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\LoginLink\Exception\ExpiredLoginLinkException; +use Symfony\Component\Security\Http\LoginLink\Exception\InvalidLoginLinkException; + +/** + * @author Ryan Weaver + */ +final class LoginLinkHandler implements LoginLinkHandlerInterface +{ + private UrlGeneratorInterface $urlGenerator; + private UserProviderInterface $userProvider; + private array $options; + private SignatureHasher $signatureHasher; + + public function __construct(UrlGeneratorInterface $urlGenerator, UserProviderInterface $userProvider, SignatureHasher $signatureHasher, array $options) + { + $this->urlGenerator = $urlGenerator; + $this->userProvider = $userProvider; + $this->signatureHasher = $signatureHasher; + $this->options = array_merge([ + 'route_name' => null, + 'lifetime' => 600, + ], $options); + } + + public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails + { + $expires = time() + ($lifetime ?: $this->options['lifetime']); + $expiresAt = new \DateTimeImmutable('@'.$expires); + + $parameters = [ + 'user' => $user->getUserIdentifier(), + 'expires' => $expires, + 'hash' => $this->signatureHasher->computeSignatureHash($user, $expires), + ]; + + if ($request) { + $currentRequestContext = $this->urlGenerator->getContext(); + $this->urlGenerator->setContext( + (new RequestContext()) + ->fromRequest($request) + ->setParameter('_locale', $request->getLocale()) + ); + } + + try { + $url = $this->urlGenerator->generate( + $this->options['route_name'], + $parameters, + UrlGeneratorInterface::ABSOLUTE_URL + ); + } finally { + if ($request) { + $this->urlGenerator->setContext($currentRequestContext); + } + } + + return new LoginLinkDetails($url, $expiresAt); + } + + public function consumeLoginLink(Request $request): UserInterface + { + $userIdentifier = $request->get('user'); + + if (!$hash = $request->get('hash')) { + throw new InvalidLoginLinkException('Missing "hash" parameter.'); + } + if (!$expires = $request->get('expires')) { + throw new InvalidLoginLinkException('Missing "expires" parameter.'); + } + + try { + $this->signatureHasher->acceptSignatureHash($userIdentifier, $expires, $hash); + + $user = $this->userProvider->loadUserByIdentifier($userIdentifier); + + $this->signatureHasher->verifySignatureHash($user, $expires, $hash); + } catch (UserNotFoundException $e) { + throw new InvalidLoginLinkException('User not found.', 0, $e); + } catch (ExpiredSignatureException $e) { + throw new ExpiredLoginLinkException(ucfirst(str_ireplace('signature', 'login link', $e->getMessage())), 0, $e); + } catch (InvalidSignatureException $e) { + throw new InvalidLoginLinkException(ucfirst(str_ireplace('signature', 'login link', $e->getMessage())), 0, $e); + } + + return $user; + } +} diff --git a/vendor/symfony/security-http/LoginLink/LoginLinkHandlerInterface.php b/vendor/symfony/security-http/LoginLink/LoginLinkHandlerInterface.php new file mode 100644 index 0000000..8a682e5 --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/LoginLinkHandlerInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * A class that is able to create and handle "magic" login links. + * + * @author Ryan Weaver + */ +interface LoginLinkHandlerInterface +{ + /** + * Generate a link that can be used to authenticate as the given user. + * + * @param int|null $lifetime When not null, the argument overrides any default lifetime previously set + */ + public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails; + + /** + * Validates if this request contains a login link and returns the associated User. + * + * Throw InvalidLoginLinkExceptionInterface if the link is invalid. + */ + public function consumeLoginLink(Request $request): UserInterface; +} diff --git a/vendor/symfony/security-http/LoginLink/LoginLinkNotification.php b/vendor/symfony/security-http/LoginLink/LoginLinkNotification.php new file mode 100644 index 0000000..6a126f8 --- /dev/null +++ b/vendor/symfony/security-http/LoginLink/LoginLinkNotification.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\LoginLink; + +use Symfony\Bridge\Twig\Mime\NotificationEmail; +use Symfony\Component\Notifier\Message\EmailMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Notification\EmailNotificationInterface; +use Symfony\Component\Notifier\Notification\Notification; +use Symfony\Component\Notifier\Notification\SmsNotificationInterface; +use Symfony\Component\Notifier\Recipient\EmailRecipientInterface; +use Symfony\Component\Notifier\Recipient\SmsRecipientInterface; + +/** + * Use this notification to ease sending login link + * emails/SMS using the Notifier component. + * + * @author Wouter de Jong + */ +class LoginLinkNotification extends Notification implements EmailNotificationInterface, SmsNotificationInterface +{ + private LoginLinkDetails $loginLinkDetails; + + public function __construct(LoginLinkDetails $loginLinkDetails, string $subject, array $channels = []) + { + parent::__construct($subject, $channels); + + $this->loginLinkDetails = $loginLinkDetails; + } + + public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): ?EmailMessage + { + if (!class_exists(NotificationEmail::class)) { + throw new \LogicException(sprintf('The "%s" method requires "symfony/twig-bridge:>4.4".', __METHOD__)); + } + + $email = NotificationEmail::asPublicEmail() + ->to($recipient->getEmail()) + ->subject($this->getSubject()) + ->content($this->getContent() ?: $this->getDefaultContent('button below')) + ->action('Sign in', $this->loginLinkDetails->getUrl()) + ; + + return new EmailMessage($email); + } + + public function asSmsMessage(SmsRecipientInterface $recipient, ?string $transport = null): ?SmsMessage + { + return new SmsMessage($recipient->getPhone(), $this->getDefaultContent('link').' '.$this->loginLinkDetails->getUrl()); + } + + private function getDefaultContent(string $target): string + { + $duration = $this->loginLinkDetails->getExpiresAt()->getTimestamp() - time(); + $durationString = floor($duration / 60).' minute'.($duration > 60 ? 's' : ''); + if (($hours = $duration / 3600) >= 1) { + $durationString = floor($hours).' hour'.($hours >= 2 ? 's' : ''); + } + + return sprintf('Click on the %s to confirm you want to sign in. This link will expire in %s.', $target, $durationString); + } +} diff --git a/vendor/symfony/security-http/Logout/LogoutUrlGenerator.php b/vendor/symfony/security-http/Logout/LogoutUrlGenerator.php new file mode 100644 index 0000000..ae09053 --- /dev/null +++ b/vendor/symfony/security-http/Logout/LogoutUrlGenerator.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Logout; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + +/** + * Provides generator functions for the logout URL. + * + * @author Fabien Potencier + * @author Jeremy Mikola + */ +class LogoutUrlGenerator +{ + private ?RequestStack $requestStack; + private ?UrlGeneratorInterface $router; + private ?TokenStorageInterface $tokenStorage; + private array $listeners = []; + private ?string $currentFirewallName = null; + private ?string $currentFirewallContext = null; + + public function __construct(?RequestStack $requestStack = null, ?UrlGeneratorInterface $router = null, ?TokenStorageInterface $tokenStorage = null) + { + $this->requestStack = $requestStack; + $this->router = $router; + $this->tokenStorage = $tokenStorage; + } + + /** + * Registers a firewall's LogoutListener, allowing its URL to be generated. + * + * @param string $key The firewall key + * @param string $logoutPath The path that starts the logout process + * @param string|null $csrfTokenId The ID of the CSRF token + * @param string|null $csrfParameter The CSRF token parameter name + * @param string|null $context The listener context + */ + public function registerListener(string $key, string $logoutPath, ?string $csrfTokenId, ?string $csrfParameter, ?CsrfTokenManagerInterface $csrfTokenManager = null, ?string $context = null): void + { + $this->listeners[$key] = [$logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager, $context]; + } + + /** + * Generates the absolute logout path for the firewall. + */ + public function getLogoutPath(?string $key = null): string + { + return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * Generates the absolute logout URL for the firewall. + */ + public function getLogoutUrl(?string $key = null): string + { + return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_URL); + } + + public function setCurrentFirewall(?string $key, ?string $context = null): void + { + $this->currentFirewallName = $key; + $this->currentFirewallContext = $context; + } + + /** + * Generates the logout URL for the firewall. + */ + private function generateLogoutUrl(?string $key, int $referenceType): string + { + [$logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager] = $this->getListener($key); + + if (null === $logoutPath) { + throw new \LogicException('Unable to generate the logout URL without a path.'); + } + + $parameters = null !== $csrfTokenManager ? [$csrfParameter => (string) $csrfTokenManager->getToken($csrfTokenId)] : []; + + if ('/' === $logoutPath[0]) { + if (!$this->requestStack) { + throw new \LogicException('Unable to generate the logout URL without a RequestStack.'); + } + + $request = $this->requestStack->getCurrentRequest(); + + if (!$request) { + throw new \LogicException('Unable to generate the logout URL without a Request.'); + } + + $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBaseUrl().$logoutPath; + + if ($parameters) { + $url .= '?'.http_build_query($parameters, '', '&'); + } + } else { + if (!$this->router) { + throw new \LogicException('Unable to generate the logout URL without a Router.'); + } + + $url = $this->router->generate($logoutPath, $parameters, $referenceType); + } + + return $url; + } + + /** + * @throws \InvalidArgumentException if no LogoutListener is registered for the key or could not be found automatically + */ + private function getListener(?string $key): array + { + if (null !== $key) { + if (isset($this->listeners[$key])) { + return $this->listeners[$key]; + } + + throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key)); + } + + // Fetch the current provider key from token, if possible + if (null !== $this->tokenStorage) { + $token = $this->tokenStorage->getToken(); + + if (null !== $token && method_exists($token, 'getFirewallName')) { + $key = $token->getFirewallName(); + + if (isset($this->listeners[$key])) { + return $this->listeners[$key]; + } + } + } + + // Fetch from injected current firewall information, if possible + if (isset($this->listeners[$this->currentFirewallName])) { + return $this->listeners[$this->currentFirewallName]; + } + + foreach ($this->listeners as $listener) { + if (isset($listener[4]) && $this->currentFirewallContext === $listener[4]) { + return $listener; + } + } + + if (null === $this->currentFirewallName) { + throw new \InvalidArgumentException('This request is not behind a firewall, pass the firewall name manually to generate a logout URL.'); + } + + throw new \InvalidArgumentException('Unable to find logout in the current firewall, pass the firewall name manually to generate a logout URL.'); + } +} diff --git a/vendor/symfony/security-http/ParameterBagUtils.php b/vendor/symfony/security-http/ParameterBagUtils.php new file mode 100644 index 0000000..429103e --- /dev/null +++ b/vendor/symfony/security-http/ParameterBagUtils.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\PropertyAccess\Exception\AccessException; +use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +/** + * @internal + */ +final class ParameterBagUtils +{ + private static PropertyAccessorInterface $propertyAccessor; + + /** + * Returns a "parameter" value. + * + * Paths like foo[bar] will be evaluated to find deeper items in nested data structures. + * + * @throws InvalidArgumentException when the given path is malformed + */ + public static function getParameterBagValue(ParameterBag $parameters, string $path): mixed + { + if (false === $pos = strpos($path, '[')) { + return $parameters->all()[$path] ?? null; + } + + $root = substr($path, 0, $pos); + + if (null === $value = $parameters->all()[$root] ?? null) { + return null; + } + + self::$propertyAccessor ??= PropertyAccess::createPropertyAccessor(); + + try { + return self::$propertyAccessor->getValue($value, substr($path, $pos)); + } catch (AccessException) { + return null; + } + } + + /** + * Returns a request "parameter" value. + * + * Paths like foo[bar] will be evaluated to find deeper items in nested data structures. + * + * @throws InvalidArgumentException when the given path is malformed + */ + public static function getRequestParameterValue(Request $request, string $path, array $parameters = []): mixed + { + if (false === $pos = strpos($path, '[')) { + return $parameters[$path] ?? $request->get($path); + } + + $root = substr($path, 0, $pos); + + if (null === $value = $parameters[$root] ?? $request->get($root)) { + return null; + } + + self::$propertyAccessor ??= PropertyAccess::createPropertyAccessor(); + + try { + $value = self::$propertyAccessor->getValue($value, substr($path, $pos)); + + if (null === $value && isset($parameters[$root]) && null !== $value = $request->get($root)) { + $value = self::$propertyAccessor->getValue($value, substr($path, $pos)); + } + + return $value; + } catch (AccessException) { + return null; + } + } +} diff --git a/vendor/symfony/security-http/README.md b/vendor/symfony/security-http/README.md new file mode 100644 index 0000000..93fce30 --- /dev/null +++ b/vendor/symfony/security-http/README.md @@ -0,0 +1,37 @@ +Security Component - HTTP Integration +===================================== + +The Security HTTP component provides an HTTP integration of the Security Core +component. It allows securing (parts of) your application using firewalls and +provides authenticators to authenticate visitors. + +Getting Started +--------------- + +```bash +composer require symfony/security-http +``` + +Sponsor +------- + +The Security component for Symfony 7.1 is [backed][1] by [SymfonyCasts][2]. + +Learn Symfony faster by watching real projects being built and actively coding +along with them. SymfonyCasts bridges that learning gap, bringing you video +tutorials and coding challenges. Code on! + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/security.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://symfonycasts.com +[3]: https://symfony.com/sponsor diff --git a/vendor/symfony/security-http/RateLimiter/DefaultLoginRateLimiter.php b/vendor/symfony/security-http/RateLimiter/DefaultLoginRateLimiter.php new file mode 100644 index 0000000..bfd0b01 --- /dev/null +++ b/vendor/symfony/security-http/RateLimiter/DefaultLoginRateLimiter.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\RateLimiter; + +use Symfony\Component\HttpFoundation\RateLimiter\AbstractRequestRateLimiter; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\RateLimiterFactory; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Security\Http\SecurityRequestAttributes; + +/** + * A default login throttling limiter. + * + * This limiter prevents breadth-first attacks by enforcing + * a limit on username+IP and a (higher) limit on IP. + * + * @author Wouter de Jong + */ +final class DefaultLoginRateLimiter extends AbstractRequestRateLimiter +{ + private RateLimiterFactory $globalFactory; + private RateLimiterFactory $localFactory; + private string $secret; + + /** + * @param non-empty-string $secret A secret to use for hashing the IP address and username + */ + public function __construct(RateLimiterFactory $globalFactory, RateLimiterFactory $localFactory, #[\SensitiveParameter] string $secret) + { + if (!$secret) { + throw new InvalidArgumentException('A non-empty secret is required.'); + } + + $this->globalFactory = $globalFactory; + $this->localFactory = $localFactory; + $this->secret = $secret; + } + + protected function getLimiters(Request $request): array + { + $username = $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME, ''); + $username = preg_match('//u', $username) ? mb_strtolower($username, 'UTF-8') : strtolower($username); + + return [ + $this->globalFactory->create($this->hash($request->getClientIp())), + $this->localFactory->create($this->hash($username.'-'.$request->getClientIp())), + ]; + } + + private function hash(string $data): string + { + return strtr(substr(base64_encode(hash_hmac('sha256', $data, $this->secret, true)), 0, 8), '/+', '._'); + } +} diff --git a/vendor/symfony/security-http/RememberMe/AbstractRememberMeHandler.php b/vendor/symfony/security-http/RememberMe/AbstractRememberMeHandler.php new file mode 100644 index 0000000..3ea69b8 --- /dev/null +++ b/vendor/symfony/security-http/RememberMe/AbstractRememberMeHandler.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\RememberMe; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * @author Wouter de Jong + */ +abstract class AbstractRememberMeHandler implements RememberMeHandlerInterface +{ + protected RequestStack $requestStack; + protected array $options; + protected ?LoggerInterface $logger; + + private UserProviderInterface $userProvider; + + public function __construct(UserProviderInterface $userProvider, RequestStack $requestStack, array $options = [], ?LoggerInterface $logger = null) + { + $this->userProvider = $userProvider; + $this->requestStack = $requestStack; + $this->options = $options + [ + 'name' => 'REMEMBERME', + 'lifetime' => 31536000, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'samesite' => null, + 'always_remember_me' => false, + 'remember_me_parameter' => '_remember_me', + ]; + $this->logger = $logger; + } + + /** + * Checks if the RememberMeDetails is a valid cookie to login the given User. + * + * This method should also: + * - Create a new remember-me cookie to be sent with the response (using {@see createCookie()}); + * - If you store the token somewhere else (e.g. in a database), invalidate the stored token. + * + * @throws AuthenticationException If the remember-me details are not accepted + */ + abstract protected function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user): void; + + public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface + { + try { + $user = $this->userProvider->loadUserByIdentifier($rememberMeDetails->getUserIdentifier()); + } catch (AuthenticationException $e) { + throw $e; + } + + if (!$user instanceof UserInterface) { + throw new \LogicException(sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', get_debug_type($user))); + } + + $this->processRememberMe($rememberMeDetails, $user); + + $this->logger?->info('Remember-me cookie accepted.'); + + return $user; + } + + public function clearRememberMeCookie(): void + { + $this->logger?->debug('Clearing remember-me cookie.', ['name' => $this->options['name']]); + + $this->createCookie(null); + } + + /** + * Creates the remember-me cookie using the correct configuration. + * + * @param RememberMeDetails|null $rememberMeDetails The details for the cookie, or null to clear the remember-me cookie + */ + protected function createCookie(?RememberMeDetails $rememberMeDetails): void + { + $request = $this->requestStack->getMainRequest(); + if (!$request) { + throw new \LogicException('Cannot create the remember-me cookie; no master request available.'); + } + + // the ResponseListener configures the cookie saved in this attribute on the final response object + $request->attributes->set(ResponseListener::COOKIE_ATTR_NAME, new Cookie( + $this->options['name'], + $rememberMeDetails?->toString(), + $rememberMeDetails?->getExpires() ?? 1, + $this->options['path'], + $this->options['domain'], + $this->options['secure'] ?? $request->isSecure(), + $this->options['httponly'], + false, + $this->options['samesite'] + )); + } +} diff --git a/vendor/symfony/security-http/RememberMe/PersistentRememberMeHandler.php b/vendor/symfony/security-http/RememberMe/PersistentRememberMeHandler.php new file mode 100644 index 0000000..bb5d386 --- /dev/null +++ b/vendor/symfony/security-http/RememberMe/PersistentRememberMeHandler.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\RememberMe; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; +use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; +use Symfony\Component\Security\Core\Authentication\RememberMe\TokenVerifierInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\CookieTheftException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * Implements remember-me tokens using a {@see TokenProviderInterface}. + * + * This requires storing remember-me tokens in a database. This allows + * more control over the invalidation of remember-me tokens. See + * {@see SignatureRememberMeHandler} if you don't want to use a database. + * + * @author Wouter de Jong + */ +final class PersistentRememberMeHandler extends AbstractRememberMeHandler +{ + private TokenProviderInterface $tokenProvider; + private ?TokenVerifierInterface $tokenVerifier; + + public function __construct(TokenProviderInterface $tokenProvider, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, ?LoggerInterface $logger = null, ?TokenVerifierInterface $tokenVerifier = null) + { + parent::__construct($userProvider, $requestStack, $options, $logger); + + if (!$tokenVerifier && $tokenProvider instanceof TokenVerifierInterface) { + $tokenVerifier = $tokenProvider; + } + $this->tokenProvider = $tokenProvider; + $this->tokenVerifier = $tokenVerifier; + } + + public function createRememberMeCookie(UserInterface $user): void + { + $series = random_bytes(66); + $tokenValue = strtr(base64_encode(substr($series, 33)), '+/=', '-_~'); + $series = strtr(base64_encode(substr($series, 0, 33)), '+/=', '-_~'); + $token = new PersistentToken($user::class, $user->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable()); + + $this->tokenProvider->createNewToken($token); + $this->createCookie(RememberMeDetails::fromPersistentToken($token, time() + $this->options['lifetime'])); + } + + public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface + { + if (!str_contains($rememberMeDetails->getValue(), ':')) { + throw new AuthenticationException('The cookie is incorrectly formatted.'); + } + + [$series, $tokenValue] = explode(':', $rememberMeDetails->getValue()); + $persistentToken = $this->tokenProvider->loadTokenBySeries($series); + + if ($this->tokenVerifier) { + $isTokenValid = $this->tokenVerifier->verifyToken($persistentToken, $tokenValue); + } else { + $isTokenValid = hash_equals($persistentToken->getTokenValue(), $tokenValue); + } + if (!$isTokenValid) { + throw new CookieTheftException('This token was already used. The account is possibly compromised.'); + } + + if ($persistentToken->getLastUsed()->getTimestamp() + $this->options['lifetime'] < time()) { + throw new AuthenticationException('The cookie has expired.'); + } + + return parent::consumeRememberMeCookie($rememberMeDetails->withValue($persistentToken->getLastUsed()->getTimestamp().':'.$rememberMeDetails->getValue().':'.$persistentToken->getClass())); + } + + public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user): void + { + [$lastUsed, $series, $tokenValue, $class] = explode(':', $rememberMeDetails->getValue(), 4); + $persistentToken = new PersistentToken($class, $rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable('@'.$lastUsed)); + + // if a token was regenerated less than a minute ago, there is no need to regenerate it + // if multiple concurrent requests reauthenticate a user we do not want to update the token several times + if ($persistentToken->getLastUsed()->getTimestamp() + 60 >= time()) { + return; + } + + $tokenValue = strtr(base64_encode(random_bytes(33)), '+/=', '-_~'); + $tokenLastUsed = new \DateTime(); + $this->tokenVerifier?->updateExistingToken($persistentToken, $tokenValue, $tokenLastUsed); + $this->tokenProvider->updateToken($series, $tokenValue, $tokenLastUsed); + + $this->createCookie($rememberMeDetails->withValue($series.':'.$tokenValue)); + } + + public function clearRememberMeCookie(): void + { + parent::clearRememberMeCookie(); + + $cookie = $this->requestStack->getMainRequest()->cookies->get($this->options['name']); + if (null === $cookie) { + return; + } + + $rememberMeDetails = RememberMeDetails::fromRawCookie($cookie); + [$series] = explode(':', $rememberMeDetails->getValue()); + $this->tokenProvider->deleteTokenBySeries($series); + } + + /** + * @internal + */ + public function getTokenProvider(): TokenProviderInterface + { + return $this->tokenProvider; + } +} diff --git a/vendor/symfony/security-http/RememberMe/RememberMeDetails.php b/vendor/symfony/security-http/RememberMe/RememberMeDetails.php new file mode 100644 index 0000000..0ae8bc0 --- /dev/null +++ b/vendor/symfony/security-http/RememberMe/RememberMeDetails.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\RememberMe; + +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * @author Wouter de Jong + */ +class RememberMeDetails +{ + public const COOKIE_DELIMITER = ':'; + + private string $userFqcn; + private string $userIdentifier; + private int $expires; + private string $value; + + public function __construct(string $userFqcn, string $userIdentifier, int $expires, string $value) + { + $this->userFqcn = $userFqcn; + $this->userIdentifier = $userIdentifier; + $this->expires = $expires; + $this->value = $value; + } + + public static function fromRawCookie(string $rawCookie): self + { + if (!str_contains($rawCookie, self::COOKIE_DELIMITER)) { + $rawCookie = base64_decode($rawCookie); + } + $cookieParts = explode(self::COOKIE_DELIMITER, $rawCookie, 4); + if (4 !== \count($cookieParts)) { + throw new AuthenticationException('The cookie contains invalid data.'); + } + if (false === $cookieParts[1] = base64_decode(strtr($cookieParts[1], '-_~', '+/='), true)) { + throw new AuthenticationException('The user identifier contains a character from outside the base64 alphabet.'); + } + $cookieParts[0] = strtr($cookieParts[0], '.', '\\'); + + return new static(...$cookieParts); + } + + public static function fromPersistentToken(PersistentToken $persistentToken, int $expires): self + { + return new static($persistentToken->getClass(), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getSeries().':'.$persistentToken->getTokenValue()); + } + + public function withValue(string $value): self + { + $details = clone $this; + $details->value = $value; + + return $details; + } + + public function getUserFqcn(): string + { + return $this->userFqcn; + } + + public function getUserIdentifier(): string + { + return $this->userIdentifier; + } + + public function getExpires(): int + { + return $this->expires; + } + + public function getValue(): string + { + return $this->value; + } + + public function toString(): string + { + // $userIdentifier is encoded because it might contain COOKIE_DELIMITER, we assume other values don't + return implode(self::COOKIE_DELIMITER, [strtr($this->userFqcn, '\\', '.'), strtr(base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]); + } +} diff --git a/vendor/symfony/security-http/RememberMe/RememberMeHandlerInterface.php b/vendor/symfony/security-http/RememberMe/RememberMeHandlerInterface.php new file mode 100644 index 0000000..046fddb --- /dev/null +++ b/vendor/symfony/security-http/RememberMe/RememberMeHandlerInterface.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\RememberMe; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Handles creating and validating remember-me cookies. + * + * If you want to add a custom implementation, you want to extend from + * {@see AbstractRememberMeHandler} instead. + * + * @author Wouter de Jong + */ +interface RememberMeHandlerInterface +{ + /** + * Creates a remember-me cookie. + * + * The actual cookie should be set as an attribute on the main request, + * which is transformed into a response cookie by {@see ResponseListener}. + */ + public function createRememberMeCookie(UserInterface $user): void; + + /** + * Validates the remember-me cookie and returns the associated User. + * + * Every cookie should only be used once. This means that this method should also: + * - Create a new remember-me cookie to be sent with the response (using the + * {@see ResponseListener::COOKIE_ATTR_NAME} request attribute); + * - If you store the token somewhere else (e.g. in a database), invalidate the + * stored token. + * + * @throws AuthenticationException + */ + public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface; + + /** + * Clears the remember-me cookie. + * + * This should set a cookie with a `null` value on the request attribute. + */ + public function clearRememberMeCookie(): void; +} diff --git a/vendor/symfony/security-http/RememberMe/ResponseListener.php b/vendor/symfony/security-http/RememberMe/ResponseListener.php new file mode 100644 index 0000000..e251a6c --- /dev/null +++ b/vendor/symfony/security-http/RememberMe/ResponseListener.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\RememberMe; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Adds remember-me cookies to the Response. + * + * @author Johannes M. Schmitt + * + * @final + */ +class ResponseListener implements EventSubscriberInterface +{ + /** + * This attribute name can be used by the implementation if it needs to set + * a cookie on the Request when there is no actual Response, yet. + */ + public const COOKIE_ATTR_NAME = '_security_remember_me_cookie'; + + public function onKernelResponse(ResponseEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $request = $event->getRequest(); + $response = $event->getResponse(); + + if ($request->attributes->has(self::COOKIE_ATTR_NAME)) { + $response->headers->setCookie($request->attributes->get(self::COOKIE_ATTR_NAME)); + } + } + + public static function getSubscribedEvents(): array + { + return [KernelEvents::RESPONSE => 'onKernelResponse']; + } +} diff --git a/vendor/symfony/security-http/RememberMe/SignatureRememberMeHandler.php b/vendor/symfony/security-http/RememberMe/SignatureRememberMeHandler.php new file mode 100644 index 0000000..f62cb25 --- /dev/null +++ b/vendor/symfony/security-http/RememberMe/SignatureRememberMeHandler.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\RememberMe; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; +use Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException; +use Symfony\Component\Security\Core\Signature\SignatureHasher; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * Implements safe remember-me cookies using the {@see SignatureHasher}. + * + * This handler doesn't require a database for the remember-me tokens. + * However, it cannot invalidate a specific user session, all sessions for + * that user will be invalidated instead. Use {@see PersistentRememberMeHandler} + * if you need this. + * + * @author Wouter de Jong + */ +final class SignatureRememberMeHandler extends AbstractRememberMeHandler +{ + private SignatureHasher $signatureHasher; + + public function __construct(SignatureHasher $signatureHasher, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, ?LoggerInterface $logger = null) + { + parent::__construct($userProvider, $requestStack, $options, $logger); + + $this->signatureHasher = $signatureHasher; + } + + public function createRememberMeCookie(UserInterface $user): void + { + $expires = time() + $this->options['lifetime']; + $value = $this->signatureHasher->computeSignatureHash($user, $expires); + + $details = new RememberMeDetails($user::class, $user->getUserIdentifier(), $expires, $value); + $this->createCookie($details); + } + + public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface + { + try { + $this->signatureHasher->acceptSignatureHash($rememberMeDetails->getUserIdentifier(), $rememberMeDetails->getExpires(), $rememberMeDetails->getValue()); + } catch (InvalidSignatureException $e) { + throw new AuthenticationException('The cookie\'s hash is invalid.', 0, $e); + } catch (ExpiredSignatureException $e) { + throw new AuthenticationException('The cookie has expired.', 0, $e); + } + + return parent::consumeRememberMeCookie($rememberMeDetails); + } + + public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user): void + { + try { + $this->signatureHasher->verifySignatureHash($user, $rememberMeDetails->getExpires(), $rememberMeDetails->getValue()); + } catch (InvalidSignatureException $e) { + throw new AuthenticationException('The cookie\'s hash is invalid.', 0, $e); + } catch (ExpiredSignatureException $e) { + throw new AuthenticationException('The cookie has expired.', 0, $e); + } + + $this->createRememberMeCookie($user); + } +} diff --git a/vendor/symfony/security-http/SecurityEvents.php b/vendor/symfony/security-http/SecurityEvents.php new file mode 100644 index 0000000..2e3755e --- /dev/null +++ b/vendor/symfony/security-http/SecurityEvents.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\Event\SwitchUserEvent; + +final class SecurityEvents +{ + /** + * The INTERACTIVE_LOGIN event occurs after a user has actively logged + * into your website. It is important to distinguish this action from + * non-interactive authentication methods, such as: + * - authentication based on your session. + * - authentication using an HTTP basic or HTTP digest header. + * + * @Event("Symfony\Component\Security\Http\Event\InteractiveLoginEvent") + */ + public const INTERACTIVE_LOGIN = 'security.interactive_login'; + + /** + * The SWITCH_USER event occurs before switch to another user and + * before exit from an already switched user. + * + * @Event("Symfony\Component\Security\Http\Event\SwitchUserEvent") + */ + public const SWITCH_USER = 'security.switch_user'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + InteractiveLoginEvent::class => self::INTERACTIVE_LOGIN, + SwitchUserEvent::class => self::SWITCH_USER, + ]; +} diff --git a/vendor/symfony/security-http/SecurityRequestAttributes.php b/vendor/symfony/security-http/SecurityRequestAttributes.php new file mode 100644 index 0000000..1c71ea0 --- /dev/null +++ b/vendor/symfony/security-http/SecurityRequestAttributes.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +/** + * List of request attributes used along the security flow. + * + * @author Robin Chalas + */ +final class SecurityRequestAttributes +{ + public const ACCESS_DENIED_ERROR = '_security.403_error'; + public const AUTHENTICATION_ERROR = '_security.last_error'; + public const LAST_USERNAME = '_security.last_username'; +} diff --git a/vendor/symfony/security-http/Session/SessionAuthenticationStrategy.php b/vendor/symfony/security-http/Session/SessionAuthenticationStrategy.php new file mode 100644 index 0000000..1f51c78 --- /dev/null +++ b/vendor/symfony/security-http/Session/SessionAuthenticationStrategy.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Session; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; + +/** + * The default session strategy implementation. + * + * Supports the following strategies: + * NONE: the session is not changed + * MIGRATE: the session id is updated, attributes are kept + * INVALIDATE: the session id is updated, attributes are lost + * + * @author Johannes M. Schmitt + */ +class SessionAuthenticationStrategy implements SessionAuthenticationStrategyInterface +{ + public const NONE = 'none'; + public const MIGRATE = 'migrate'; + public const INVALIDATE = 'invalidate'; + + private string $strategy; + private ?ClearableTokenStorageInterface $csrfTokenStorage = null; + + public function __construct(string $strategy, ?ClearableTokenStorageInterface $csrfTokenStorage = null) + { + $this->strategy = $strategy; + + if (self::MIGRATE === $strategy) { + $this->csrfTokenStorage = $csrfTokenStorage; + } + } + + public function onAuthentication(Request $request, TokenInterface $token): void + { + switch ($this->strategy) { + case self::NONE: + return; + + case self::MIGRATE: + $request->getSession()->migrate(true); + $this->csrfTokenStorage?->clear(); + + return; + + case self::INVALIDATE: + $request->getSession()->invalidate(); + + return; + + default: + throw new \RuntimeException(sprintf('Invalid session authentication strategy "%s".', $this->strategy)); + } + } +} diff --git a/vendor/symfony/security-http/Session/SessionAuthenticationStrategyInterface.php b/vendor/symfony/security-http/Session/SessionAuthenticationStrategyInterface.php new file mode 100644 index 0000000..3eb3a4b --- /dev/null +++ b/vendor/symfony/security-http/Session/SessionAuthenticationStrategyInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Session; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * SessionAuthenticationStrategyInterface. + * + * Implementation are responsible for updating the session after an interactive + * authentication attempt was successful. + * + * @author Johannes M. Schmitt + */ +interface SessionAuthenticationStrategyInterface +{ + /** + * This performs any necessary changes to the session. + * + * This method should be called before the TokenStorage is populated with a + * Token. It should be used by authentication listeners when a session is used. + */ + public function onAuthentication(Request $request, TokenInterface $token): void; +} diff --git a/vendor/symfony/security-http/Util/TargetPathTrait.php b/vendor/symfony/security-http/Util/TargetPathTrait.php new file mode 100644 index 0000000..c9ec541 --- /dev/null +++ b/vendor/symfony/security-http/Util/TargetPathTrait.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Util; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Trait to get (and set) the URL the user last visited before being forced to authenticate. + */ +trait TargetPathTrait +{ + /** + * Sets the target path the user should be redirected to after authentication. + * + * Usually, you do not need to set this directly. + */ + private function saveTargetPath(SessionInterface $session, string $firewallName, string $uri): void + { + $session->set('_security.'.$firewallName.'.target_path', $uri); + } + + /** + * Returns the URL (if any) the user visited that forced them to login. + */ + private function getTargetPath(SessionInterface $session, string $firewallName): ?string + { + return $session->get('_security.'.$firewallName.'.target_path'); + } + + /** + * Removes the target path from the session. + */ + private function removeTargetPath(SessionInterface $session, string $firewallName): void + { + $session->remove('_security.'.$firewallName.'.target_path'); + } +} diff --git a/vendor/symfony/security-http/composer.json b/vendor/symfony/security-http/composer.json new file mode 100644 index 0000000..a9c2246 --- /dev/null +++ b/vendor/symfony/security-http/composer.json @@ -0,0 +1,55 @@ +{ + "name": "symfony/security-http", + "type": "library", + "description": "Symfony Security Component - HTTP Integration", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/http-client-contracts": "^3.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "psr/log": "^1|^2|^3", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "conflict": { + "symfony/clock": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-client-contracts": "<3.0", + "symfony/security-bundle": "<6.4", + "symfony/security-csrf": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Security\\Http\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/service-contracts/Attribute/Required.php b/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000..9df8511 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000..f850b84 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * For use as the return value for {@see ServiceSubscriberInterface}. + * + * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi')) + * + * Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** @var object[] */ + public array $attributes; + + /** + * @param string|null $key The key to use for the service + * @param class-string|null $type The service class + * @param bool $nullable Whether the service is optional + * @param object|object[] $attributes One or more dependency injection attributes to use + */ + public function __construct( + public ?string $key = null, + public ?string $type = null, + public bool $nullable = false, + array|object $attributes = [], + ) { + $this->attributes = \is_array($attributes) ? $attributes : [$attributes]; + } +} diff --git a/vendor/symfony/service-contracts/CHANGELOG.md b/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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. diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000..42841a5 --- /dev/null +++ b/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000..a4f389b --- /dev/null +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + /** + * @return void + */ + public function reset(); +} diff --git a/vendor/symfony/service-contracts/ServiceCollectionInterface.php b/vendor/symfony/service-contracts/ServiceCollectionInterface.php new file mode 100644 index 0000000..2333139 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceCollectionInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceProviderInterface that is also countable and iterable. + * + * @author Kevin Bond + * + * @template-covariant T of mixed + * + * @extends ServiceProviderInterface + * @extends \IteratorAggregate + */ +interface ServiceCollectionInterface extends ServiceProviderInterface, \Countable, \IteratorAggregate +{ +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000..b62ec3e --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private array $factories; + private array $loading = []; + private array $providedTypes; + + /** + * @param array $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + public function has(string $id): bool + { + return isset($this->factories[$id]); + } + + public function get(string $id): mixed + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + public function getProvidedServices(): array + { + if (!isset($this->providedTypes)) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php new file mode 100644 index 0000000..0d89d9f --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services + * from methods that have the #[SubscribedService] attribute. + * + * Service ids are available as "ClassName::methodName" so that the implementation + * of subscriber methods can be just `return $this->container->get(__METHOD__);`. + * + * @author Kevin Bond + */ +trait ServiceMethodsSubscriberTrait +{ + protected ContainerInterface $container; + + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $returnType->allowsNull(); + + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + } + } + + return $services; + } + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $ret = null; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + $ret = parent::setContainer($container); + } + + $this->container = $container; + + return $ret; + } +} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000..2e71f00 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + * + * @template-covariant T of mixed + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * @return T + */ + public function get(string $id): mixed; + + public function has(string $id): bool; + + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return array The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000..3da1916 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types (or {@see SubscribedService} objects) required + * by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * additionally, an array of {@see SubscribedService}'s can be returned: + * + * * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)] + * * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)] + * * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))] + * + * @return string[]|SubscribedService[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000..cc3bc32 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class); + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services + * from methods that have the #[SubscribedService] attribute. + * + * Service ids are available as "ClassName::methodName" so that the implementation + * of subscriber methods can be just `return $this->container->get(__METHOD__);`. + * + * @property ContainerInterface $container + * + * @author Kevin Bond + * + * @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead + */ +trait ServiceSubscriberTrait +{ + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $returnType->allowsNull(); + + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + } + } + + return $services; + } + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $ret = null; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + $ret = parent::setContainer($container); + } + + $this->container = $container; + + return $ret; + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000..07d12b4 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +class_alias(ServiceLocatorTestCase::class, ServiceLocatorTest::class); + +if (false) { + /** + * @deprecated since PHPUnit 9.6 + */ + class ServiceLocatorTest + { + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php new file mode 100644 index 0000000..65a3fe3 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTestCase extends TestCase +{ + protected function getServiceLocator(array $factories): ContainerInterface + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + fn () => 'dummy', + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + if (!$this->getExpectedException()) { + $this->expectException(NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $this->expectException(ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + + $locator->get('foo'); + } +} diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000..fc8674a --- /dev/null +++ b/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/stopwatch/CHANGELOG.md b/vendor/symfony/stopwatch/CHANGELOG.md new file mode 100644 index 0000000..f2fd7d0 --- /dev/null +++ b/vendor/symfony/stopwatch/CHANGELOG.md @@ -0,0 +1,24 @@ +CHANGELOG +========= + +5.2 +--- + + * Add `name` argument to the `StopWatchEvent` constructor, accessible via a new `StopwatchEvent::getName()` + +5.0.0 +----- + + * Removed support for passing `null` as 1st (`$id`) argument of `Section::get()` method, pass a valid child section identifier instead. + +4.4.0 +----- + + * Deprecated passing `null` as 1st (`$id`) argument of `Section::get()` method, pass a valid child section identifier instead. + +3.4.0 +----- + + * added the `Stopwatch::reset()` method + * allowed to measure sub-millisecond times by introducing an argument to the + constructor of `Stopwatch` diff --git a/vendor/symfony/stopwatch/LICENSE b/vendor/symfony/stopwatch/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/stopwatch/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/stopwatch/README.md b/vendor/symfony/stopwatch/README.md new file mode 100644 index 0000000..824ddfd --- /dev/null +++ b/vendor/symfony/stopwatch/README.md @@ -0,0 +1,42 @@ +Stopwatch Component +=================== + +The Stopwatch component provides a way to profile code. + +Getting Started +--------------- + +```bash +composer require symfony/stopwatch +``` + +```php +use Symfony\Component\Stopwatch\Stopwatch; + +$stopwatch = new Stopwatch(); + +// optionally group events into sections (e.g. phases of the execution) +$stopwatch->openSection(); + +// starts event named 'eventName' +$stopwatch->start('eventName'); + +// ... run your code here + +// optionally, start a new "lap" time +$stopwatch->lap('foo'); + +// ... run your code here + +$event = $stopwatch->stop('eventName'); + +$stopwatch->stopSection('phase_1'); +``` + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/stopwatch/Section.php b/vendor/symfony/stopwatch/Section.php new file mode 100644 index 0000000..912e391 --- /dev/null +++ b/vendor/symfony/stopwatch/Section.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +/** + * Stopwatch section. + * + * @author Fabien Potencier + */ +class Section +{ + /** + * @var StopwatchEvent[] + */ + private array $events = []; + + private ?string $id = null; + + /** + * @var Section[] + */ + private array $children = []; + + /** + * @param float|null $origin Set the origin of the events in this section, use null to set their origin to their start time + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + */ + public function __construct( + private ?float $origin = null, + private bool $morePrecision = false, + ) { + } + + /** + * Returns the child section. + */ + public function get(string $id): ?self + { + foreach ($this->children as $child) { + if ($id === $child->getId()) { + return $child; + } + } + + return null; + } + + /** + * Creates or re-opens a child section. + * + * @param string|null $id Null to create a new section, the identifier to re-open an existing one + */ + public function open(?string $id): self + { + if (null === $id || null === $session = $this->get($id)) { + $session = $this->children[] = new self(microtime(true) * 1000, $this->morePrecision); + } + + return $session; + } + + public function getId(): ?string + { + return $this->id; + } + + /** + * Sets the session identifier. + * + * @return $this + */ + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + /** + * Starts an event. + */ + public function startEvent(string $name, ?string $category): StopwatchEvent + { + if (!isset($this->events[$name])) { + $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category, $this->morePrecision, $name); + } + + return $this->events[$name]->start(); + } + + /** + * Checks if the event was started. + */ + public function isEventStarted(string $name): bool + { + return isset($this->events[$name]) && $this->events[$name]->isStarted(); + } + + /** + * Stops an event. + * + * @throws \LogicException When the event has not been started + */ + public function stopEvent(string $name): StopwatchEvent + { + if (!isset($this->events[$name])) { + throw new \LogicException(sprintf('Event "%s" is not started.', $name)); + } + + return $this->events[$name]->stop(); + } + + /** + * Stops then restarts an event. + * + * @throws \LogicException When the event has not been started + */ + public function lap(string $name): StopwatchEvent + { + return $this->stopEvent($name)->start(); + } + + /** + * Returns a specific event by name. + * + * @throws \LogicException When the event is not known + */ + public function getEvent(string $name): StopwatchEvent + { + if (!isset($this->events[$name])) { + throw new \LogicException(sprintf('Event "%s" is not known.', $name)); + } + + return $this->events[$name]; + } + + /** + * Returns the events from this section. + * + * @return StopwatchEvent[] + */ + public function getEvents(): array + { + return $this->events; + } +} diff --git a/vendor/symfony/stopwatch/Stopwatch.php b/vendor/symfony/stopwatch/Stopwatch.php new file mode 100644 index 0000000..53ef69f --- /dev/null +++ b/vendor/symfony/stopwatch/Stopwatch.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Section::class); + +/** + * Stopwatch provides a way to profile code. + * + * @author Fabien Potencier + */ +class Stopwatch implements ResetInterface +{ + /** + * @var Section[] + */ + private array $sections; + + /** + * @var Section[] + */ + private array $activeSections; + + /** + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + */ + public function __construct( + private bool $morePrecision = false, + ) { + $this->reset(); + } + + /** + * @return Section[] + */ + public function getSections(): array + { + return $this->sections; + } + + /** + * Creates a new section or re-opens an existing section. + * + * @param string|null $id The id of the session to re-open, null to create a new one + * + * @throws \LogicException When the section to re-open is not reachable + */ + public function openSection(?string $id = null): void + { + $current = end($this->activeSections); + + if (null !== $id && null === $current->get($id)) { + throw new \LogicException(sprintf('The section "%s" has been started at an other level and cannot be opened.', $id)); + } + + $this->start('__section__.child', 'section'); + $this->activeSections[] = $current->open($id); + $this->start('__section__'); + } + + /** + * Stops the last started section. + * + * The id parameter is used to retrieve the events from this section. + * + * @see getSectionEvents() + * + * @throws \LogicException When there's no started section to be stopped + */ + public function stopSection(string $id): void + { + $this->stop('__section__'); + + if (1 == \count($this->activeSections)) { + throw new \LogicException('There is no started section to stop.'); + } + + $this->sections[$id] = array_pop($this->activeSections)->setId($id); + $this->stop('__section__.child'); + } + + /** + * Starts an event. + */ + public function start(string $name, ?string $category = null): StopwatchEvent + { + return end($this->activeSections)->startEvent($name, $category); + } + + /** + * Checks if the event was started. + */ + public function isStarted(string $name): bool + { + return end($this->activeSections)->isEventStarted($name); + } + + /** + * Stops an event. + */ + public function stop(string $name): StopwatchEvent + { + return end($this->activeSections)->stopEvent($name); + } + + /** + * Stops then restarts an event. + */ + public function lap(string $name): StopwatchEvent + { + return end($this->activeSections)->stopEvent($name)->start(); + } + + /** + * Returns a specific event by name. + */ + public function getEvent(string $name): StopwatchEvent + { + return end($this->activeSections)->getEvent($name); + } + + /** + * Gets all events for a given section. + * + * @return StopwatchEvent[] + */ + public function getSectionEvents(string $id): array + { + return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : []; + } + + /** + * Resets the stopwatch to its original state. + */ + public function reset(): void + { + $this->sections = $this->activeSections = ['__root__' => new Section(null, $this->morePrecision)]; + } +} diff --git a/vendor/symfony/stopwatch/StopwatchEvent.php b/vendor/symfony/stopwatch/StopwatchEvent.php new file mode 100644 index 0000000..08be489 --- /dev/null +++ b/vendor/symfony/stopwatch/StopwatchEvent.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +/** + * Represents an Event managed by Stopwatch. + * + * @author Fabien Potencier + */ +class StopwatchEvent +{ + /** + * @var StopwatchPeriod[] + */ + private array $periods = []; + + private float $origin; + private string $category; + + /** + * @var float[] + */ + private array $started = []; + + private string $name; + + /** + * @param float $origin The origin time in milliseconds + * @param string|null $category The event category or null to use the default + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + * @param string|null $name The event name or null to define the name as default + * + * @throws \InvalidArgumentException When the raw time is not valid + */ + public function __construct( + float $origin, + ?string $category = null, + private bool $morePrecision = false, + ?string $name = null, + ) { + $this->origin = $this->formatTime($origin); + $this->category = \is_string($category) ? $category : 'default'; + $this->name = $name ?? 'default'; + } + + /** + * Gets the category. + */ + public function getCategory(): string + { + return $this->category; + } + + /** + * Gets the origin in milliseconds. + */ + public function getOrigin(): float + { + return $this->origin; + } + + /** + * Starts a new event period. + * + * @return $this + */ + public function start(): static + { + $this->started[] = $this->getNow(); + + return $this; + } + + /** + * Stops the last started event period. + * + * @return $this + * + * @throws \LogicException When stop() is called without a matching call to start() + */ + public function stop(): static + { + if (!\count($this->started)) { + throw new \LogicException('stop() called but start() has not been called before.'); + } + + $this->periods[] = new StopwatchPeriod(array_pop($this->started), $this->getNow(), $this->morePrecision); + + return $this; + } + + /** + * Checks if the event was started. + */ + public function isStarted(): bool + { + return (bool) $this->started; + } + + /** + * Stops the current period and then starts a new one. + * + * @return $this + */ + public function lap(): static + { + return $this->stop()->start(); + } + + /** + * Stops all non already stopped periods. + */ + public function ensureStopped(): void + { + while (\count($this->started)) { + $this->stop(); + } + } + + /** + * Gets all event periods. + * + * @return StopwatchPeriod[] + */ + public function getPeriods(): array + { + return $this->periods; + } + + /** + * Gets the relative time of the start of the first period in milliseconds. + */ + public function getStartTime(): int|float + { + if (isset($this->periods[0])) { + return $this->periods[0]->getStartTime(); + } + + if ($this->started) { + return $this->started[0]; + } + + return 0; + } + + /** + * Gets the relative time of the end of the last period in milliseconds. + */ + public function getEndTime(): int|float + { + $count = \count($this->periods); + + return $count ? $this->periods[$count - 1]->getEndTime() : 0; + } + + /** + * Gets the duration of the events in milliseconds (including all periods). + */ + public function getDuration(): int|float + { + $periods = $this->periods; + $left = \count($this->started); + + for ($i = $left - 1; $i >= 0; --$i) { + $periods[] = new StopwatchPeriod($this->started[$i], $this->getNow(), $this->morePrecision); + } + + $total = 0; + foreach ($periods as $period) { + $total += $period->getDuration(); + } + + return $total; + } + + /** + * Gets the max memory usage of all periods in bytes. + */ + public function getMemory(): int + { + $memory = 0; + foreach ($this->periods as $period) { + if ($period->getMemory() > $memory) { + $memory = $period->getMemory(); + } + } + + return $memory; + } + + /** + * Return the current time relative to origin in milliseconds. + */ + protected function getNow(): float + { + return $this->formatTime(microtime(true) * 1000 - $this->origin); + } + + /** + * Formats a time. + * + * @throws \InvalidArgumentException When the raw time is not valid + */ + private function formatTime(float $time): float + { + return round($time, 1); + } + + /** + * Gets the event name. + */ + public function getName(): string + { + return $this->name; + } + + public function __toString(): string + { + return sprintf('%s/%s: %.2F MiB - %d ms', $this->getCategory(), $this->getName(), $this->getMemory() / 1024 / 1024, $this->getDuration()); + } +} diff --git a/vendor/symfony/stopwatch/StopwatchPeriod.php b/vendor/symfony/stopwatch/StopwatchPeriod.php new file mode 100644 index 0000000..41e8dcc --- /dev/null +++ b/vendor/symfony/stopwatch/StopwatchPeriod.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +/** + * Represents an Period for an Event. + * + * @author Fabien Potencier + */ +class StopwatchPeriod +{ + private int|float $start; + private int|float $end; + private int $memory; + + /** + * @param int|float $start The relative time of the start of the period (in milliseconds) + * @param int|float $end The relative time of the end of the period (in milliseconds) + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + */ + public function __construct(int|float $start, int|float $end, bool $morePrecision = false) + { + $this->start = $morePrecision ? (float) $start : (int) $start; + $this->end = $morePrecision ? (float) $end : (int) $end; + $this->memory = memory_get_usage(true); + } + + /** + * Gets the relative time of the start of the period in milliseconds. + */ + public function getStartTime(): int|float + { + return $this->start; + } + + /** + * Gets the relative time of the end of the period in milliseconds. + */ + public function getEndTime(): int|float + { + return $this->end; + } + + /** + * Gets the time spent in this period in milliseconds. + */ + public function getDuration(): int|float + { + return $this->end - $this->start; + } + + /** + * Gets the memory usage in bytes. + */ + public function getMemory(): int + { + return $this->memory; + } + + public function __toString(): string + { + return sprintf('%.2F MiB - %d ms', $this->getMemory() / 1024 / 1024, $this->getDuration()); + } +} diff --git a/vendor/symfony/stopwatch/composer.json b/vendor/symfony/stopwatch/composer.json new file mode 100644 index 0000000..3556869 --- /dev/null +++ b/vendor/symfony/stopwatch/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/stopwatch", + "type": "library", + "description": "Provides a way to profile code", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Stopwatch\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/string/AbstractString.php b/vendor/symfony/string/AbstractString.php new file mode 100644 index 0000000..253d2dc --- /dev/null +++ b/vendor/symfony/string/AbstractString.php @@ -0,0 +1,702 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement doesn't care about the exact variant it deals with. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +abstract class AbstractString implements \Stringable, \JsonSerializable +{ + public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER; + public const PREG_SET_ORDER = \PREG_SET_ORDER; + public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE; + public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL; + + public const PREG_SPLIT = 0; + public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY; + public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE; + public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE; + + protected string $string = ''; + protected ?bool $ignoreCase = false; + + abstract public function __construct(string $string = ''); + + /** + * Unwraps instances of AbstractString back to strings. + * + * @return string[]|array + */ + public static function unwrap(array $values): array + { + foreach ($values as $k => $v) { + if ($v instanceof self) { + $values[$k] = $v->__toString(); + } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) { + $values[$k] = $v; + } + } + + return $values; + } + + /** + * Wraps (and normalizes) strings in instances of AbstractString. + * + * @return static[]|array + */ + public static function wrap(array $values): array + { + $i = 0; + $keys = null; + + foreach ($values as $k => $v) { + if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) { + $keys ??= array_keys($values); + $keys[$i] = $j; + } + + if (\is_string($v)) { + $values[$k] = new static($v); + } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) { + $values[$k] = $v; + } + + ++$i; + } + + return null !== $keys ? array_combine($keys, $values) : $values; + } + + /** + * @param string|string[] $needle + */ + public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = \PHP_INT_MAX; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + /** + * @param string|string[] $needle + */ + public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = null; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + abstract public function append(string ...$suffix): static; + + /** + * @param string|string[] $needle + */ + public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = \PHP_INT_MAX; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @param string|string[] $needle + */ + public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = null; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @return int[] + */ + public function bytesAt(int $offset): array + { + $str = $this->slice($offset, 1); + + return '' === $str->string ? [] : array_values(unpack('C*', $str->string)); + } + + abstract public function camel(): static; + + /** + * @return static[] + */ + abstract public function chunk(int $length = 1): array; + + public function collapseWhitespace(): static + { + $str = clone $this; + $str->string = trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $str->string), " \n\r\t\x0C"); + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function containsAny(string|iterable $needle): bool + { + return null !== $this->indexOf($needle); + } + + /** + * @param string|string[] $suffix + */ + public function endsWith(string|iterable $suffix): bool + { + if (\is_string($suffix)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($suffix as $s) { + if ($this->endsWith((string) $s)) { + return true; + } + } + + return false; + } + + public function ensureEnd(string $suffix): static + { + if (!$this->endsWith($suffix)) { + return $this->append($suffix); + } + + $suffix = preg_quote($suffix); + $regex = '{('.$suffix.')(?:'.$suffix.')++$}D'; + + return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1'); + } + + public function ensureStart(string $prefix): static + { + $prefix = new static($prefix); + + if (!$this->startsWith($prefix)) { + return $this->prepend($prefix); + } + + $str = clone $this; + $i = $prefixLen = $prefix->length(); + + while ($this->indexOf($prefix, $i) === $i) { + $str = $str->slice($prefixLen); + $i += $prefixLen; + } + + return $str; + } + + /** + * @param string|string[] $string + */ + public function equalsTo(string|iterable $string): bool + { + if (\is_string($string)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($string as $s) { + if ($this->equalsTo((string) $s)) { + return true; + } + } + + return false; + } + + abstract public function folded(): static; + + public function ignoreCase(): static + { + $str = clone $this; + $str->ignoreCase = true; + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function indexOf(string|iterable $needle, int $offset = 0): ?int + { + if (\is_string($needle)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = \PHP_INT_MAX; + + foreach ($needle as $n) { + $j = $this->indexOf((string) $n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + } + } + + return \PHP_INT_MAX === $i ? null : $i; + } + + /** + * @param string|string[] $needle + */ + public function indexOfLast(string|iterable $needle, int $offset = 0): ?int + { + if (\is_string($needle)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = null; + + foreach ($needle as $n) { + $j = $this->indexOfLast((string) $n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + } + } + + return $i; + } + + public function isEmpty(): bool + { + return '' === $this->string; + } + + abstract public function join(array $strings, ?string $lastGlue = null): static; + + public function jsonSerialize(): string + { + return $this->string; + } + + abstract public function length(): int; + + abstract public function lower(): static; + + /** + * Matches the string using a regular expression. + * + * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression. + * + * @return array All matches in a multi-dimensional array ordered according to flags + */ + abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array; + + abstract public function padBoth(int $length, string $padStr = ' '): static; + + abstract public function padEnd(int $length, string $padStr = ' '): static; + + abstract public function padStart(int $length, string $padStr = ' '): static; + + abstract public function prepend(string ...$prefix): static; + + public function repeat(int $multiplier): static + { + if (0 > $multiplier) { + throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier)); + } + + $str = clone $this; + $str->string = str_repeat($str->string, $multiplier); + + return $str; + } + + abstract public function replace(string $from, string $to): static; + + abstract public function replaceMatches(string $fromRegexp, string|callable $to): static; + + abstract public function reverse(): static; + + abstract public function slice(int $start = 0, ?int $length = null): static; + + abstract public function snake(): static; + + abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static; + + /** + * @return static[] + */ + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (null === $flags) { + throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.'); + } + + if ($this->ignoreCase) { + $delimiter .= 'i'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { + throw new RuntimeException('Splitting failed with error: '.preg_last_error_msg()); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + + if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) { + foreach ($chunks as &$chunk) { + $str->string = $chunk[0]; + $chunk[0] = clone $str; + } + } else { + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + } + + return $chunks; + } + + /** + * @param string|string[] $prefix + */ + public function startsWith(string|iterable $prefix): bool + { + if (\is_string($prefix)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($prefix as $prefix) { + if ($this->startsWith((string) $prefix)) { + return true; + } + } + + return false; + } + + abstract public function title(bool $allWords = false): static; + + public function toByteString(?string $toEncoding = null): ByteString + { + $b = new ByteString(); + + $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding; + + if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') { + $b->string = $this->string; + + return $b; + } + + try { + $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); + } catch (\ValueError $e) { + if (!\function_exists('iconv')) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + $b->string = iconv('UTF-8', $toEncoding, $this->string); + } + + return $b; + } + + public function toCodePointString(): CodePointString + { + return new CodePointString($this->string); + } + + public function toString(): string + { + return $this->string; + } + + public function toUnicodeString(): UnicodeString + { + return new UnicodeString($this->string); + } + + abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; + + abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; + + /** + * @param string|string[] $prefix + */ + public function trimPrefix($prefix): static + { + if (\is_array($prefix) || $prefix instanceof \Traversable) { // don't use is_iterable(), it's slow + foreach ($prefix as $s) { + $t = $this->trimPrefix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($prefix instanceof self) { + $prefix = $prefix->string; + } else { + $prefix = (string) $prefix; + } + + if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) { + $str->string = substr($this->string, \strlen($prefix)); + } + + return $str; + } + + abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; + + /** + * @param string|string[] $suffix + */ + public function trimSuffix($suffix): static + { + if (\is_array($suffix) || $suffix instanceof \Traversable) { // don't use is_iterable(), it's slow + foreach ($suffix as $s) { + $t = $this->trimSuffix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($suffix instanceof self) { + $suffix = $suffix->string; + } else { + $suffix = (string) $suffix; + } + + if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) { + $str->string = substr($this->string, 0, -\strlen($suffix)); + } + + return $str; + } + + public function truncate(int $length, string $ellipsis = '', bool $cut = true): static + { + $stringLength = $this->length(); + + if ($stringLength <= $length) { + return clone $this; + } + + $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0; + + if ($length < $ellipsisLength) { + $ellipsisLength = 0; + } + + if (!$cut) { + if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { + return clone $this; + } + + $length += $ellipsisLength; + } + + $str = $this->slice(0, $length - $ellipsisLength); + + return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; + } + + abstract public function upper(): static; + + /** + * Returns the printable length on a terminal. + */ + abstract public function width(bool $ignoreAnsiDecoration = true): int; + + public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): static + { + $lines = '' !== $break ? $this->split($break) : [clone $this]; + $chars = []; + $mask = ''; + + if (1 === \count($lines) && '' === $lines[0]->string) { + return $lines[0]; + } + + foreach ($lines as $i => $line) { + if ($i) { + $chars[] = $break; + $mask .= '#'; + } + + foreach ($line->chunk() as $char) { + $chars[] = $char->string; + $mask .= ' ' === $char->string ? ' ' : '?'; + } + } + + $string = ''; + $j = 0; + $b = $i = -1; + $mask = wordwrap($mask, $width, '#', $cut); + + while (false !== $b = strpos($mask, '#', $b + 1)) { + for (++$i; $i < $b; ++$i) { + $string .= $chars[$j]; + unset($chars[$j++]); + } + + if ($break === $chars[$j] || ' ' === $chars[$j]) { + unset($chars[$j++]); + } + + $string .= $break; + } + + $str = clone $this; + $str->string = $string.implode('', $chars); + + return $str; + } + + public function __sleep(): array + { + return ['string']; + } + + public function __clone() + { + $this->ignoreCase = false; + } + + public function __toString(): string + { + return $this->string; + } +} diff --git a/vendor/symfony/string/AbstractUnicodeString.php b/vendor/symfony/string/AbstractUnicodeString.php new file mode 100644 index 0000000..2cb2917 --- /dev/null +++ b/vendor/symfony/string/AbstractUnicodeString.php @@ -0,0 +1,664 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract Unicode characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement is Unicode-aware but doesn't care about code points vs grapheme clusters. + * + * @author Nicolas Grekas + * + * @throws ExceptionInterface + */ +abstract class AbstractUnicodeString extends AbstractString +{ + public const NFC = \Normalizer::NFC; + public const NFD = \Normalizer::NFD; + public const NFKC = \Normalizer::NFKC; + public const NFKD = \Normalizer::NFKD; + + // all ASCII letters sorted by typical frequency of occurrence + private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + // the subset of folded case mappings that is not in lower case mappings + private const FOLD_FROM = ['İ', 'µ', 'Å¿', "\xCD\x85", 'Ï‚', 'Ï', 'Ï‘', 'Ï•', 'Ï–', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'ʼn', 'ǰ', 'Î', 'ΰ', 'Ö‡', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'á½', 'á½’', 'á½”', 'á½–', 'á¾€', 'á¾', 'ᾂ', 'ᾃ', 'ᾄ', 'á¾…', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'á¾', 'ᾎ', 'á¾', 'á¾', 'ᾑ', 'á¾’', 'ᾓ', 'á¾”', 'ᾕ', 'á¾–', 'á¾—', 'ᾘ', 'á¾™', 'ᾚ', 'á¾›', 'ᾜ', 'á¾', 'ᾞ', 'ᾟ', 'á¾ ', 'ᾡ', 'á¾¢', 'á¾£', 'ᾤ', 'á¾¥', 'ᾦ', 'á¾§', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'á¾­', 'á¾®', 'ᾯ', 'á¾²', 'á¾³', 'á¾´', 'á¾¶', 'á¾·', 'á¾¼', 'á¿‚', 'ῃ', 'á¿„', 'ῆ', 'ῇ', 'ῌ', 'á¿’', 'á¿–', 'á¿—', 'á¿¢', 'ῤ', 'ῦ', 'á¿§', 'ῲ', 'ῳ', 'á¿´', 'á¿¶', 'á¿·', 'ῼ', 'ff', 'ï¬', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ']; + private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'Ï€', 'κ', 'Ï', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'Î', 'ΰ', 'Õ¥Ö‚', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'á½', 'á½’', 'á½”', 'á½–', 'ἀι', 'á¼Î¹', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'á¼Î¹', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'á¾¶', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'á¿’', 'á¿–', 'á¿—', 'á¿¢', 'ῤ', 'ῦ', 'á¿§', 'ὼι', 'ωι', 'ώι', 'á¿¶', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'Õ´Õ¶', 'Õ´Õ¥', 'Õ´Õ«', 'Õ¾Õ¶', 'Õ´Õ­']; + + // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD + private const TRANSLIT_FROM = ['Æ', 'Ã', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Ä', 'Ä‘', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ä¿', 'Å€', 'Å', 'Å‚', 'ʼn', 'ÅŠ', 'Å‹', 'Å’', 'Å“', 'Ŧ', 'ŧ', 'Æ€', 'Æ', 'Æ‚', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'ÆŠ', 'Æ‹', 'ÆŒ', 'Æ', 'Æ‘', 'Æ’', 'Æ“', 'Æ•', 'Æ–', 'Æ—', 'Ƙ', 'Æ™', 'Æš', 'Æ', 'Æž', 'Æ¢', 'Æ£', 'Ƥ', 'Æ¥', 'Æ«', 'Ƭ', 'Æ­', 'Æ®', 'Ʋ', 'Ƴ', 'Æ´', 'Ƶ', 'ƶ', 'Ç„', 'Ç…', 'dž', 'Ǥ', 'Ç¥', 'È¡', 'Ȥ', 'È¥', 'È´', 'ȵ', 'ȶ', 'È·', 'ȸ', 'ȹ', 'Ⱥ', 'È»', 'ȼ', 'Ƚ', 'Ⱦ', 'È¿', 'É€', 'Ƀ', 'É„', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'ÉŒ', 'É', 'ÉŽ', 'É', 'É“', 'É•', 'É–', 'É—', 'É›', 'ÉŸ', 'É ', 'É¡', 'É¢', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'É«', 'ɬ', 'É­', 'ɱ', 'ɲ', 'ɳ', 'É´', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'Ê€', 'Ê‚', 'ʈ', 'ʉ', 'Ê‹', 'Ê', 'Ê', 'Ê‘', 'Ê™', 'Ê›', 'Êœ', 'Ê', 'ÊŸ', 'Ê ', 'Ê£', 'Ê¥', 'ʦ', 'ʪ', 'Ê«', 'á´€', 'á´', 'á´ƒ', 'á´„', 'á´…', 'á´†', 'á´‡', 'á´Š', 'á´‹', 'á´Œ', 'á´', 'á´', 'á´˜', 'á´›', 'á´œ', 'á´ ', 'á´¡', 'á´¢', 'ᵫ', 'ᵬ', 'áµ­', 'áµ®', 'ᵯ', 'áµ°', 'áµ±', 'áµ²', 'áµ³', 'áµ´', 'áµµ', 'áµ¶', 'ᵺ', 'áµ»', 'áµ½', 'áµ¾', 'á¶€', 'á¶', 'á¶‚', 'ᶃ', 'á¶„', 'á¶…', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'á¶Š', 'á¶Œ', 'á¶', 'á¶Ž', 'á¶', 'á¶‘', 'á¶’', 'á¶“', 'á¶–', 'á¶™', 'ẚ', 'ẜ', 'áº', 'ẞ', 'Ỻ', 'á»»', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', 'â‚ ', 'â‚¢', 'â‚£', '₤', 'â‚§', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', 'ã†', 'ã—', 'ãž', 'ãŸ', '¼', '½', '¾', 'â…“', 'â…”', 'â…•', 'â…–', 'â…—', 'â…˜', 'â…™', 'â…š', 'â…›', 'â…œ', 'â…', 'â…ž', 'â…Ÿ', '〇', '‘', '’', '‚', '‛', '“', 'â€', '„', '‟', '′', '″', 'ã€', '〞', '«', '»', '‹', '›', 'â€', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', 'â„', 'â…', 'â†', 'âŽ', 'ã€', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', 'ï¹€', '﹑', 'ï¹', '﹞', '⦅', 'ï½ ', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆']; + private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; + + private static array $transliterators = []; + private static array $tableZero; + private static array $tableWide; + + public static function fromCodePoints(int ...$codes): static + { + $string = ''; + + foreach ($codes as $code) { + if (0x80 > $code %= 0x200000) { + $string .= \chr($code); + } elseif (0x800 > $code) { + $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + } + + return new static($string); + } + + /** + * Generic UTF-8 to ASCII transliteration. + * + * Install the intl extension for best results. + * + * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs() + */ + public function ascii(array $rules = []): self + { + $str = clone $this; + $s = $str->string; + $str->string = ''; + + array_unshift($rules, 'nfd'); + $rules[] = 'latin-ascii'; + + if (\function_exists('transliterator_transliterate')) { + $rules[] = 'any-latin/bgn'; + } + + $rules[] = 'nfkd'; + $rules[] = '[:nonspacing mark:] remove'; + + while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) { + if (0 < --$i) { + $str->string .= substr($s, 0, $i); + $s = substr($s, $i); + } + + if (!$rule = array_shift($rules)) { + $rules = []; // An empty rule interrupts the next ones + } + + if ($rule instanceof \Transliterator) { + $s = $rule->transliterate($s); + } elseif ($rule instanceof \Closure) { + $s = $rule($s); + } elseif ($rule) { + if ('nfd' === $rule = strtolower($rule)) { + normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD); + } elseif ('nfkd' === $rule) { + normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD); + } elseif ('[:nonspacing mark:] remove' === $rule) { + $s = preg_replace('/\p{Mn}++/u', '', $s); + } elseif ('latin-ascii' === $rule) { + $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s); + } elseif ('de-ascii' === $rule) { + $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s); + $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s); + } elseif (\function_exists('transliterator_transliterate')) { + if (null === $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule)) { + if ('any-latin/bgn' === $rule) { + $rule = 'any-latin'; + $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule); + } + + if (null === $transliterator) { + throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule)); + } + + self::$transliterators['any-latin/bgn'] = $transliterator; + } + + $s = $transliterator->transliterate($s); + } + } elseif (!\function_exists('iconv')) { + $s = preg_replace('/[^\x00-\x7F]/u', '?', $s); + } else { + $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) { + $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); + + if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { + throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); + } + + return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); + }, $s); + } + } + + $str->string .= $s; + + return $str; + } + + public function camel(): static + { + $str = clone $this; + $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?!\p{Lu})/u', static function ($m) { + static $i = 0; + + return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); + }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string))); + + return $str; + } + + /** + * @return int[] + */ + public function codePointsAt(int $offset): array + { + $str = $this->slice($offset, 1); + + if ('' === $str->string) { + return []; + } + + $codePoints = []; + + foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoints[] = mb_ord($c, 'UTF-8'); + } + + return $codePoints; + } + + public function folded(bool $compat = true): static + { + $str = clone $this; + + if (!$compat || !\defined('Normalizer::NFKC_CF')) { + $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC); + $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $str->string), 'UTF-8'); + } else { + $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF); + } + + return $str; + } + + public function join(array $strings, ?string $lastGlue = null): static + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function lower(): static + { + $str = clone $this; + $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8'); + + return $str; + } + + /** + * @param string $locale In the format language_region (e.g. tr_TR) + */ + public function localeLower(string $locale): static + { + if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Lower')) { + $str = clone $this; + $str->string = $transliterator->transliterate($str->string); + + return $str; + } + + return $this->lower(); + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + throw new RuntimeException('Matching failed with error: '.preg_last_error_msg()); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + public function normalize(int $form = self::NFC): static + { + if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } + + $str = clone $this; + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + + return $str; + } + + public function padBoth(int $length, string $padStr = ' '): static + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_BOTH); + } + + public function padEnd(int $length, string $padStr = ' '): static + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_RIGHT); + } + + public function padStart(int $length, string $padStr = ' '): static + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_LEFT); + } + + public function replaceMatches(string $fromRegexp, string|callable $to): static + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to) || $to instanceof \Closure) { + $replace = 'preg_replace_callback'; + $to = static function (array $m) use ($to): string { + $to = $to($m); + + if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) { + throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.'); + } + + return $to; + }; + } elseif ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } else { + $replace = 'preg_replace'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): static + { + $str = clone $this; + $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY))); + + return $str; + } + + public function snake(): static + { + $str = $this->camel(); + $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8'); + + return $str; + } + + public function title(bool $allWords = false): static + { + $str = clone $this; + + $limit = $allWords ? -1 : 1; + + $str->string = preg_replace_callback('/\b./u', static fn (array $m): string => mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'), $str->string, $limit); + + return $str; + } + + /** + * @param string $locale In the format language_region (e.g. tr_TR) + */ + public function localeTitle(string $locale): static + { + if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Title')) { + $str = clone $this; + $str->string = $transliterator->transliterate($str->string); + + return $str; + } + + return $this->title(); + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimPrefix($prefix): static + { + if (!$this->ignoreCase) { + return parent::trimPrefix($prefix); + } + + $str = clone $this; + + if ($prefix instanceof \Traversable) { + $prefix = iterator_to_array($prefix, false); + } elseif ($prefix instanceof parent) { + $prefix = $prefix->string; + } + + $prefix = implode('|', array_map('preg_quote', (array) $prefix)); + $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++}uD", '', $str->string); + + return $str; + } + + public function trimSuffix($suffix): static + { + if (!$this->ignoreCase) { + return parent::trimSuffix($suffix); + } + + $str = clone $this; + + if ($suffix instanceof \Traversable) { + $suffix = iterator_to_array($suffix, false); + } elseif ($suffix instanceof parent) { + $suffix = $suffix->string; + } + + $suffix = implode('|', array_map('preg_quote', (array) $suffix)); + $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string); + + return $str; + } + + public function upper(): static + { + $str = clone $this; + $str->string = mb_strtoupper($str->string, 'UTF-8'); + + return $str; + } + + /** + * @param string $locale In the format language_region (e.g. tr_TR) + */ + public function localeUpper(string $locale): static + { + if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Upper')) { + $str = clone $this; + $str->string = $transliterator->transliterate($str->string); + + return $str; + } + + return $this->upper(); + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $width = 0; + $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string); + + if (str_contains($s, "\r")) { + $s = str_replace(["\r\n", "\r"], "\n", $s); + } + + if (!$ignoreAnsiDecoration) { + $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s); + } + + foreach (explode("\n", $s) as $s) { + if ($ignoreAnsiDecoration) { + $s = preg_replace('/(?:\x1B(?: + \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [\x40-\x7E] + | [P\]X^_] .*? \x1B\\\\ + | [\x41-\x7E] + )|[\p{Cc}\x7F]++)/xu', '', $s); + } + + $lineWidth = $this->wcswidth($s); + + if ($lineWidth > $width) { + $width = $lineWidth; + } + } + + return $width; + } + + private function pad(int $len, self $pad, int $type): static + { + $sLen = $this->length(); + + if ($len <= $sLen) { + return clone $this; + } + + $padLen = $pad->length(); + $freeLen = $len - $sLen; + $len = $freeLen % $padLen; + + switch ($type) { + case \STR_PAD_RIGHT: + return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_LEFT: + return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_BOTH: + $freeLen /= 2; + + $rightLen = ceil($freeLen); + $len = $rightLen % $padLen; + $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + $leftLen = floor($freeLen); + $len = $leftLen % $padLen; + + return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + default: + throw new InvalidArgumentException('Invalid padding type.'); + } + } + + /** + * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c. + */ + private function wcswidth(string $string): int + { + $width = 0; + + foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoint = mb_ord($c, 'UTF-8'); + + if (0 === $codePoint // NULL + || 0x034F === $codePoint // COMBINING GRAPHEME JOINER + || (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK + || 0x2028 === $codePoint // LINE SEPARATOR + || 0x2029 === $codePoint // PARAGRAPH SEPARATOR + || (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE + || (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR + ) { + continue; + } + + // Non printable characters + if (32 > $codePoint // C0 control characters + || (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL + ) { + return -1; + } + + self::$tableZero ??= require __DIR__.'/Resources/data/wcswidth_table_zero.php'; + + if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableZero[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableZero[$mid][0]) { + $ubound = $mid - 1; + } else { + continue 2; + } + } + } + + self::$tableWide ??= require __DIR__.'/Resources/data/wcswidth_table_wide.php'; + + if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableWide[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableWide[$mid][0]) { + $ubound = $mid - 1; + } else { + $width += 2; + + continue 2; + } + } + } + + ++$width; + } + + return $width; + } + + private function getLocaleTransliterator(string $locale, string $id): ?\Transliterator + { + $rule = $locale.'-'.$id; + if (\array_key_exists($rule, self::$transliterators)) { + return self::$transliterators[$rule]; + } + + if (null !== $transliterator = self::$transliterators[$rule] = \Transliterator::create($rule)) { + return $transliterator; + } + + // Try to find a parent locale (nl_BE -> nl) + if (false === $i = strpos($locale, '_')) { + return null; + } + + $parentRule = substr_replace($locale, '-'.$id, $i); + + // Parent locale was already cached, return and store as current locale + if (\array_key_exists($parentRule, self::$transliterators)) { + return self::$transliterators[$rule] = self::$transliterators[$parentRule]; + } + + // Create transliterator based on parent locale and cache the result on both initial and parent locale values + $transliterator = \Transliterator::create($parentRule); + + return self::$transliterators[$rule] = self::$transliterators[$parentRule] = $transliterator; + } +} diff --git a/vendor/symfony/string/ByteString.php b/vendor/symfony/string/ByteString.php new file mode 100644 index 0000000..e6b56ae --- /dev/null +++ b/vendor/symfony/string/ByteString.php @@ -0,0 +1,490 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Random\Randomizer; +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a binary-safe string of bytes. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class ByteString extends AbstractString +{ + private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + + public function __construct(string $string = '') + { + $this->string = $string; + } + + /* + * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03) + * + * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16 + * + * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE). + * + * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/) + */ + + public static function fromRandom(int $length = 16, ?string $alphabet = null): self + { + if ($length <= 0) { + throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); + } + + $alphabet ??= self::ALPHABET_ALPHANUMERIC; + $alphabetSize = \strlen($alphabet); + $bits = (int) ceil(log($alphabetSize, 2.0)); + if ($bits <= 0 || $bits > 56) { + throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.'); + } + + if (\PHP_VERSION_ID >= 80300) { + return new static((new Randomizer())->getBytesFromString($alphabet, $length)); + } + + $ret = ''; + while ($length > 0) { + $urandomLength = (int) ceil(2 * $length * $bits / 8.0); + $data = random_bytes($urandomLength); + $unpackedData = 0; + $unpackedBits = 0; + for ($i = 0; $i < $urandomLength && $length > 0; ++$i) { + // Unpack 8 bits + $unpackedData = ($unpackedData << 8) | \ord($data[$i]); + $unpackedBits += 8; + + // While we have enough bits to select a character from the alphabet, keep + // consuming the random data + for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) { + $index = ($unpackedData & ((1 << $bits) - 1)); + $unpackedData >>= $bits; + // Unfortunately, the alphabet size is not necessarily a power of two. + // Worst case, it is 2^k + 1, which means we need (k+1) bits and we + // have around a 50% chance of missing as k gets larger + if ($index < $alphabetSize) { + $ret .= $alphabet[$index]; + --$length; + } + } + } + } + + return new static($ret); + } + + public function bytesAt(int $offset): array + { + $str = $this->string[$offset] ?? ''; + + return '' === $str ? [] : [\ord($str)]; + } + + public function append(string ...$suffix): static + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + return $str; + } + + public function camel(): static + { + $str = clone $this; + + $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); + $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]); + $str->string = implode('', $parts); + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $str = clone $this; + $chunks = []; + + foreach (str_split($this->string, $length) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith(string|iterable|AbstractString $suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (!\is_string($suffix)) { + return parent::endsWith($suffix); + } + + return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); + } + + public function equalsTo(string|iterable|AbstractString $string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (!\is_string($string)) { + return parent::equalsTo($string); + } + + if ('' !== $string && $this->ignoreCase) { + return 0 === strcasecmp($string, $this->string); + } + + return $string === $this->string; + } + + public function folded(): static + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOf($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOfLast($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function isUtf8(): bool + { + return '' === $this->string || preg_match('//u', $this->string); + } + + public function join(array $strings, ?string $lastGlue = null): static + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + return $str; + } + + public function length(): int + { + return \strlen($this->string); + } + + public function lower(): static + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + throw new RuntimeException('Matching failed with error: '.preg_last_error_msg()); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + public function padBoth(int $length, string $padStr = ' '): static + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); + + return $str; + } + + public function padEnd(int $length, string $padStr = ' '): static + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); + + return $str; + } + + public function padStart(int $length, string $padStr = ' '): static + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); + + return $str; + } + + public function prepend(string ...$prefix): static + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string; + + return $str; + } + + public function replace(string $from, string $to): static + { + $str = clone $this; + + if ('' !== $from) { + $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string); + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, string|callable $to): static + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (null === $string = $replace($fromRegexp, $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): static + { + $str = clone $this; + $str->string = strrev($str->string); + + return $str; + } + + public function slice(int $start = 0, ?int $length = null): static + { + $str = clone $this; + $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function snake(): static + { + $str = $this->camel(); + $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string)); + + return $str; + } + + public function splice(string $replacement, int $start = 0, ?int $length = null): static + { + $str = clone $this; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (1 > $limit ??= \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter, $limit, $flags); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith(string|iterable|AbstractString $prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix))); + } + + public function title(bool $allWords = false): static + { + $str = clone $this; + $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string); + + return $str; + } + + public function toUnicodeString(?string $fromEncoding = null): UnicodeString + { + return new UnicodeString($this->toCodePointString($fromEncoding)->string); + } + + public function toCodePointString(?string $fromEncoding = null): CodePointString + { + $u = new CodePointString(); + + if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) { + $u->string = $this->string; + + return $u; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + try { + $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true); + } catch (InvalidArgumentException $e) { + if (!\function_exists('iconv')) { + throw $e; + } + + $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string); + + return $u; + } + } finally { + restore_error_handler(); + } + + if (!$validEncoding) { + throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); + } + + $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252'); + + return $u; + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C"): static + { + $str = clone $this; + $str->string = trim($str->string, $chars); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): static + { + $str = clone $this; + $str->string = rtrim($str->string, $chars); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): static + { + $str = clone $this; + $str->string = ltrim($str->string, $chars); + + return $str; + } + + public function upper(): static + { + $str = clone $this; + $str->string = strtoupper($str->string); + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string); + + return (new CodePointString($string))->width($ignoreAnsiDecoration); + } +} diff --git a/vendor/symfony/string/CHANGELOG.md b/vendor/symfony/string/CHANGELOG.md new file mode 100644 index 0000000..621cedf --- /dev/null +++ b/vendor/symfony/string/CHANGELOG.md @@ -0,0 +1,45 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `localeLower()`, `localeUpper()`, `localeTitle()` methods to `AbstractUnicodeString` + +6.2 +--- + + * Add support for emoji in `AsciiSlugger` + +5.4 +--- + + * Add `trimSuffix()` and `trimPrefix()` methods + +5.3 +--- + + * Made `AsciiSlugger` fallback to parent locale's symbolsMap + +5.2.0 +----- + + * added a `FrenchInflector` class + +5.1.0 +----- + + * added the `AbstractString::reverse()` method + * made `AbstractString::width()` follow POSIX.1-2001 + * added `LazyString` which provides memoizing stringable objects + * The component is not marked as `@experimental` anymore + * added the `s()` helper method to get either an `UnicodeString` or `ByteString` instance, + depending of the input string UTF-8 compliancy + * added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()` + * added `AbstractString::containsAny()` + * allow passing a string of custom characters to `ByteString::fromRandom()` + +5.0.0 +----- + + * added the component as experimental diff --git a/vendor/symfony/string/CodePointString.php b/vendor/symfony/string/CodePointString.php new file mode 100644 index 0000000..337bfc1 --- /dev/null +++ b/vendor/symfony/string/CodePointString.php @@ -0,0 +1,260 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode code points encoded as UTF-8. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class CodePointString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + if ('' !== $string && !preg_match('//u', $string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $this->string = $string; + } + + public function append(string ...$suffix): static + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '.{65535}'; + $length -= 65535; + } + $rx .= '.{'.$length.'})/us'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function codePointsAt(int $offset): array + { + $str = $offset ? $this->slice($offset, 1) : $this; + + return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')]; + } + + public function endsWith(string|iterable|AbstractString $suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (!\is_string($suffix)) { + return parent::endsWith($suffix); + } + + if ('' === $suffix || !preg_match('//u', $suffix)) { + return false; + } + + if ($this->ignoreCase) { + return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string); + } + + return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix)); + } + + public function equalsTo(string|iterable|AbstractString $string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (!\is_string($string)) { + return parent::equalsTo($string); + } + + if ('' !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOf($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOfLast($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function length(): int + { + return mb_strlen($this->string, 'UTF-8'); + } + + public function prepend(string ...$prefix): static + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function replace(string $from, string $to): static + { + $str = clone $this; + + if ('' === $from || !preg_match('//u', $from)) { + return $str; + } + + if ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + if ($this->ignoreCase) { + $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string)); + } else { + $str->string = str_replace($from, $to, $this->string); + } + + return $str; + } + + public function slice(int $start = 0, ?int $length = null): static + { + $str = clone $this; + $str->string = mb_substr($this->string, $start, $length, 'UTF-8'); + + return $str; + } + + public function splice(string $replacement, int $start = 0, ?int $length = null): static + { + if (!preg_match('//u', $replacement)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str = clone $this; + $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0; + $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (1 > $limit ??= \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + if (!preg_match('//u', $delimiter)) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith(string|iterable|AbstractString $prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + if ('' === $prefix || !preg_match('//u', $prefix)) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8'); + } + + return 0 === strncmp($this->string, $prefix, \strlen($prefix)); + } +} diff --git a/vendor/symfony/string/Exception/ExceptionInterface.php b/vendor/symfony/string/Exception/ExceptionInterface.php new file mode 100644 index 0000000..3619786 --- /dev/null +++ b/vendor/symfony/string/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/string/Exception/InvalidArgumentException.php b/vendor/symfony/string/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..6aa586b --- /dev/null +++ b/vendor/symfony/string/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/string/Exception/RuntimeException.php b/vendor/symfony/string/Exception/RuntimeException.php new file mode 100644 index 0000000..77cb091 --- /dev/null +++ b/vendor/symfony/string/Exception/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/string/Inflector/EnglishInflector.php b/vendor/symfony/string/Inflector/EnglishInflector.php new file mode 100644 index 0000000..c41bade --- /dev/null +++ b/vendor/symfony/string/Inflector/EnglishInflector.php @@ -0,0 +1,586 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +final class EnglishInflector implements InflectorInterface +{ + /** + * Map English plural to singular suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const PLURAL_MAP = [ + // First entry: plural suffix, reversed + // Second entry: length of plural suffix + // Third entry: Whether the suffix may succeed a vowel + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: singular suffix, normal + + // bacteria (bacterium) + ['airetcab', 8, true, true, 'bacterium'], + + // corpora (corpus) + ['aroproc', 7, true, true, 'corpus'], + + // criteria (criterion) + ['airetirc', 8, true, true, 'criterion'], + + // curricula (curriculum) + ['alucirruc', 9, true, true, 'curriculum'], + + // quora (quorum) + ['arouq', 5, true, true, 'quorum'], + + // genera (genus) + ['areneg', 6, true, true, 'genus'], + + // media (medium) + ['aidem', 5, true, true, 'medium'], + + // memoranda (memorandum) + ['adnaromem', 9, true, true, 'memorandum'], + + // phenomena (phenomenon) + ['anemonehp', 9, true, true, 'phenomenon'], + + // strata (stratum) + ['atarts', 6, true, true, 'stratum'], + + // nebulae (nebula) + ['ea', 2, true, true, 'a'], + + // services (service) + ['secivres', 8, true, true, 'service'], + + // mice (mouse), lice (louse) + ['eci', 3, false, true, 'ouse'], + + // geese (goose) + ['esee', 4, false, true, 'oose'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['i', 1, true, true, 'us'], + + // men (man), women (woman) + ['nem', 3, true, true, 'man'], + + // children (child) + ['nerdlihc', 8, true, true, 'child'], + + // oxen (ox) + ['nexo', 4, false, false, 'ox'], + + // indices (index), appendices (appendix), prices (price) + ['seci', 4, false, true, ['ex', 'ix', 'ice']], + + // codes (code) + ['sedoc', 5, false, true, 'code'], + + // selfies (selfie) + ['seifles', 7, true, true, 'selfie'], + + // zombies (zombie) + ['seibmoz', 7, true, true, 'zombie'], + + // movies (movie) + ['seivom', 6, true, true, 'movie'], + + // names (name) + ['seman', 5, true, false, 'name'], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sesutcep', 8, true, true, 'pectus'], + + // feet (foot) + ['teef', 4, true, true, 'foot'], + + // geese (goose) + ['eseeg', 5, true, true, 'goose'], + + // teeth (tooth) + ['hteet', 5, true, true, 'tooth'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // series (series) + ['seires', 6, true, true, 'series'], + + // babies (baby) + ['sei', 3, false, true, 'y'], + + // accesses (access), addresses (address), kisses (kiss) + ['sess', 4, true, false, 'ss'], + + // statuses (status) + ['sesutats', 8, true, true, 'status'], + + // article (articles), ancle (ancles) + ['sel', 3, true, true, 'le'], + + // analyses (analysis), ellipses (ellipsis), fungi (fungus), + // neuroses (neurosis), theses (thesis), emphases (emphasis), + // oases (oasis), crises (crisis), houses (house), bases (base), + // atlases (atlas) + ['ses', 3, true, true, ['s', 'se', 'sis']], + + // objectives (objective), alternative (alternatives) + ['sevit', 5, true, true, 'tive'], + + // drives (drive) + ['sevird', 6, false, true, 'drive'], + + // lives (life), wives (wife) + ['sevi', 4, false, true, 'ife'], + + // moves (move) + ['sevom', 5, true, true, 'move'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff) + ['sev', 3, true, true, ['f', 've', 'ff']], + + // axes (axis), axes (ax), axes (axe) + ['sexa', 4, false, false, ['ax', 'axe', 'axis']], + + // indexes (index), matrixes (matrix) + ['sex', 3, true, false, 'x'], + + // quizzes (quiz) + ['sezz', 4, true, false, 'z'], + + // bureaus (bureau) + ['suae', 4, false, true, 'eau'], + + // fees (fee), trees (tree), employees (employee) + ['see', 3, true, true, 'ee'], + + // edges (edge) + ['segd', 4, true, true, 'dge'], + + // roses (rose), garages (garage), cassettes (cassette), + // waltzes (waltz), heroes (hero), bushes (bush), arches (arch), + // shoes (shoe) + ['se', 2, true, true, ['', 'e']], + + // status (status) + ['sutats', 6, true, true, 'status'], + + // tags (tag) + ['s', 1, true, true, ''], + + // chateaux (chateau) + ['xuae', 4, false, true, 'eau'], + + // people (person) + ['elpoep', 6, true, true, 'person'], + ]; + + /** + * Map English singular to plural suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const SINGULAR_MAP = [ + // First entry: singular suffix, reversed + // Second entry: length of singular suffix + // Third entry: Whether the suffix may succeed a vowel + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: plural suffix, normal + + // axes (axis) + ['sixa', 4, false, false, 'axes'], + + // criterion (criteria) + ['airetirc', 8, false, false, 'criterion'], + + // nebulae (nebula) + ['aluben', 6, false, false, 'nebulae'], + + // children (child) + ['dlihc', 5, true, true, 'children'], + + // prices (price) + ['eci', 3, false, true, 'ices'], + + // services (service) + ['ecivres', 7, true, true, 'services'], + + // lives (life), wives (wife) + ['efi', 3, false, true, 'ives'], + + // selfies (selfie) + ['eifles', 6, true, true, 'selfies'], + + // movies (movie) + ['eivom', 5, true, true, 'movies'], + + // lice (louse) + ['esuol', 5, false, true, 'lice'], + + // mice (mouse) + ['esuom', 5, false, true, 'mice'], + + // geese (goose) + ['esoo', 4, false, true, 'eese'], + + // houses (house), bases (base) + ['es', 2, true, true, 'ses'], + + // geese (goose) + ['esoog', 5, true, true, 'geese'], + + // caves (cave) + ['ev', 2, true, true, 'ves'], + + // drives (drive) + ['evird', 5, false, true, 'drives'], + + // objectives (objective), alternative (alternatives) + ['evit', 4, true, true, 'tives'], + + // moves (move) + ['evom', 4, true, true, 'moves'], + + // staves (staff) + ['ffats', 5, true, true, 'staves'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['ff', 2, true, true, 'ffs'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['f', 1, true, true, ['fs', 'ves']], + + // arches (arch) + ['hc', 2, true, true, 'ches'], + + // bushes (bush) + ['hs', 2, true, true, 'shes'], + + // teeth (tooth) + ['htoot', 5, true, true, 'teeth'], + + // albums (album) + ['mubla', 5, true, true, 'albums'], + + // quorums (quorum) + ['murouq', 6, true, true, ['quora', 'quorums']], + + // bacteria (bacterium), curricula (curriculum), media (medium), memoranda (memorandum), phenomena (phenomenon), strata (stratum) + ['mu', 2, true, true, 'a'], + + // men (man), women (woman) + ['nam', 3, true, true, 'men'], + + // people (person) + ['nosrep', 6, true, true, ['persons', 'people']], + + // criteria (criterion) + ['noiretirc', 9, true, true, 'criteria'], + + // phenomena (phenomenon) + ['nonemonehp', 10, true, true, 'phenomena'], + + // echoes (echo) + ['ohce', 4, true, true, 'echoes'], + + // heroes (hero) + ['oreh', 4, true, true, 'heroes'], + + // atlases (atlas) + ['salta', 5, true, true, 'atlases'], + + // aliases (alias) + ['saila', 5, true, true, 'aliases'], + + // irises (iris) + ['siri', 4, true, true, 'irises'], + + // analyses (analysis), ellipses (ellipsis), neuroses (neurosis) + // theses (thesis), emphases (emphasis), oases (oasis), + // crises (crisis) + ['sis', 3, true, true, 'ses'], + + // accesses (access), addresses (address), kisses (kiss) + ['ss', 2, true, false, 'sses'], + + // syllabi (syllabus) + ['suballys', 8, true, true, 'syllabi'], + + // buses (bus) + ['sub', 3, true, true, 'buses'], + + // circuses (circus) + ['suc', 3, true, true, 'cuses'], + + // hippocampi (hippocampus) + ['supmacoppih', 11, false, false, 'hippocampi'], + + // campuses (campus) + ['sup', 3, true, true, 'puses'], + + // status (status) + ['sutats', 6, true, true, ['status', 'statuses']], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sutcep', 6, true, true, 'pectuses'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['su', 2, true, true, 'i'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // feet (foot) + ['toof', 4, true, true, 'feet'], + + // chateaux (chateau), bureaus (bureau) + ['uae', 3, false, true, ['eaus', 'eaux']], + + // oxen (ox) + ['xo', 2, false, false, 'oxen'], + + // hoaxes (hoax) + ['xaoh', 4, true, false, 'hoaxes'], + + // indices (index) + ['xedni', 5, false, true, ['indicies', 'indexes']], + + // boxes (box) + ['xo', 2, false, true, 'oxes'], + + // indexes (index), matrixes (matrix) + ['x', 1, true, false, ['cies', 'xes']], + + // appendices (appendix) + ['xi', 2, false, true, 'ices'], + + // babies (baby) + ['y', 1, false, true, 'ies'], + + // quizzes (quiz) + ['ziuq', 4, true, false, 'quizzes'], + + // waltzes (waltz) + ['z', 1, true, true, 'zes'], + ]; + + /** + * A list of words which should not be inflected, reversed. + */ + private const UNINFLECTED = [ + '', + + // data + 'atad', + + // deer + 'reed', + + // equipment + 'tnempiuqe', + + // feedback + 'kcabdeef', + + // fish + 'hsif', + + // health + 'htlaeh', + + // history + 'yrotsih', + + // info + 'ofni', + + // information + 'noitamrofni', + + // money + 'yenom', + + // moose + 'esoom', + + // series + 'seires', + + // sheep + 'peehs', + + // species + 'seiceps', + + // traffic + 'ciffart', + + // aircraft + 'tfarcria', + + // hardware + 'erawdrah', + ]; + + public function singularize(string $plural): array + { + $pluralRev = strrev($plural); + $lowerPluralRev = strtolower($pluralRev); + $pluralLength = \strlen($lowerPluralRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) { + return [$plural]; + } + + // The outer loop iterates over the entries of the plural table + // The inner loop $j iterates over the characters of the plural suffix + // in the plural table to compare them with the characters of the actual + // given plural suffix + foreach (self::PLURAL_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the plural table and of the suffix of the + // given plural one by one + while ($suffix[$j] === $lowerPluralRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the singular suffix to the singular array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $pluralLength) { + $nextIsVowel = str_contains('aeiou', $lowerPluralRev[$j]); + + if (!$map[2] && $nextIsVowel) { + // suffix may not succeed a vowel but next char is one + break; + } + + if (!$map[3] && !$nextIsVowel) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($plural, 0, $pluralLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the plural suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($pluralRev[$j - 1]); + + if (\is_array($newSuffix)) { + $singulars = []; + + foreach ($newSuffix as $newSuffixEntry) { + $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $singulars; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $pluralLength) { + break; + } + } + } + + // Assume that plural and singular is identical + return [$plural]; + } + + public function pluralize(string $singular): array + { + $singularRev = strrev($singular); + $lowerSingularRev = strtolower($singularRev); + $singularLength = \strlen($lowerSingularRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) { + return [$singular]; + } + + // The outer loop iterates over the entries of the singular table + // The inner loop $j iterates over the characters of the singular suffix + // in the singular table to compare them with the characters of the actual + // given singular suffix + foreach (self::SINGULAR_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the singular table and of the suffix of the + // given plural one by one + + while ($suffix[$j] === $lowerSingularRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the plural suffix to the plural array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $singularLength) { + $nextIsVowel = str_contains('aeiou', $lowerSingularRev[$j]); + + if (!$map[2] && $nextIsVowel) { + // suffix may not succeed a vowel but next char is one + break; + } + + if (!$map[3] && !$nextIsVowel) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($singular, 0, $singularLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the singular suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($singularRev[$j - 1]); + + if (\is_array($newSuffix)) { + $plurals = []; + + foreach ($newSuffix as $newSuffixEntry) { + $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $plurals; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $singularLength) { + break; + } + } + } + + // Assume that plural is singular with a trailing `s` + return [$singular.'s']; + } +} diff --git a/vendor/symfony/string/Inflector/FrenchInflector.php b/vendor/symfony/string/Inflector/FrenchInflector.php new file mode 100644 index 0000000..955abbf --- /dev/null +++ b/vendor/symfony/string/Inflector/FrenchInflector.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +/** + * French inflector. + * + * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix". + */ +final class FrenchInflector implements InflectorInterface +{ + /** + * A list of all rules for pluralise. + * + * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php + */ + private const PLURALIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Words finishing with "s", "x" or "z" are invariables + // Les mots finissant par "s", "x" ou "z" sont invariables + ['/(s|x|z)$/i', '\1'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)$/i', '\1x'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/^(landau)$/i', '\1s'], + ['/(au)$/i', '\1x'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/^(pneu|bleu|émeu)$/i', '\1s'], + ['/(eu)$/i', '\1x'], + + // Words finishing with "al" are pluralized with a "aux" excepted + // Les mots finissant en "al" se terminent en "aux" sauf + ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'], + ['/al$/i', '\1aux'], + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'], + + // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel + ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'], + + // Invariable words + ['/^(cinquante|soixante|mille)$/i', '\1'], + + // French titles + ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'], + ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'], + ]; + + /** + * A list of all rules for singularize. + */ + private const SINGULARIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)x$/i', '\1'], + + // Words finishing with "al" are pluralized with a "aux" expected + // Les mots finissant en "al" se terminent en "aux" sauf + ['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/(au)x$/i', '\1'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/(eu)x$/i', '\1'], + + // Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou + // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou + ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'], + + // French titles + ['/^mes(dame|demoiselle)s$/', 'ma\1'], + ['/^Mes(dame|demoiselle)s$/', 'Ma\1'], + ['/^mes(sieur|seigneur)s$/', 'mon\1'], + ['/^Mes(sieur|seigneur)s$/', 'Mon\1'], + + // Default rule + ['/s$/i', ''], + ]; + + /** + * A list of words which should not be inflected. + * This list is only used by singularize. + */ + private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sans|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i'; + + public function singularize(string $plural): array + { + if ($this->isInflectedWord($plural)) { + return [$plural]; + } + + foreach (self::SINGULARIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $plural)) { + return [preg_replace($regexp, $replace, $plural)]; + } + } + + return [$plural]; + } + + public function pluralize(string $singular): array + { + if ($this->isInflectedWord($singular)) { + return [$singular]; + } + + foreach (self::PLURALIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $singular)) { + return [preg_replace($regexp, $replace, $singular)]; + } + } + + return [$singular.'s']; + } + + private function isInflectedWord(string $word): bool + { + return 1 === preg_match(self::UNINFLECTED, $word); + } +} diff --git a/vendor/symfony/string/Inflector/InflectorInterface.php b/vendor/symfony/string/Inflector/InflectorInterface.php new file mode 100644 index 0000000..67f2834 --- /dev/null +++ b/vendor/symfony/string/Inflector/InflectorInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +interface InflectorInterface +{ + /** + * Returns the singular forms of a string. + * + * If the method can't determine the form with certainty, several possible singulars are returned. + * + * @return string[] + */ + public function singularize(string $plural): array; + + /** + * Returns the plural forms of a string. + * + * If the method can't determine the form with certainty, several possible plurals are returned. + * + * @return string[] + */ + public function pluralize(string $singular): array; +} diff --git a/vendor/symfony/string/LICENSE b/vendor/symfony/string/LICENSE new file mode 100644 index 0000000..f37c76b --- /dev/null +++ b/vendor/symfony/string/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-present Fabien Potencier + +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. diff --git a/vendor/symfony/string/LazyString.php b/vendor/symfony/string/LazyString.php new file mode 100644 index 0000000..8f2bbbf --- /dev/null +++ b/vendor/symfony/string/LazyString.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +/** + * A string whose value is computed lazily by a callback. + * + * @author Nicolas Grekas + */ +class LazyString implements \Stringable, \JsonSerializable +{ + private \Closure|string $value; + + /** + * @param callable|array $callback A callable or a [Closure, method] lazy-callable + */ + public static function fromCallable(callable|array $callback, mixed ...$arguments): static + { + if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) instanceof \Closure || 2 < \count($callback))) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, '['.implode(', ', array_map('get_debug_type', $callback)).']')); + } + + $lazyString = new static(); + $lazyString->value = static function () use (&$callback, &$arguments): string { + static $value; + + if (null !== $arguments) { + if (!\is_callable($callback)) { + $callback[0] = $callback[0](); + $callback[1] ??= '__invoke'; + } + $value = $callback(...$arguments); + $callback = !\is_scalar($value) && !$value instanceof \Stringable ? self::getPrettyName($callback) : 'callable'; + $arguments = null; + } + + return $value ?? ''; + }; + + return $lazyString; + } + + public static function fromStringable(string|int|float|bool|\Stringable $value): static + { + if (\is_object($value)) { + return static::fromCallable($value->__toString(...)); + } + + $lazyString = new static(); + $lazyString->value = (string) $value; + + return $lazyString; + } + + /** + * Tells whether the provided value can be cast to string. + */ + final public static function isStringable(mixed $value): bool + { + return \is_string($value) || $value instanceof \Stringable || \is_scalar($value); + } + + /** + * Casts scalars and stringable objects to strings. + * + * @throws \TypeError When the provided value is not stringable + */ + final public static function resolve(\Stringable|string|int|float|bool $value): string + { + return $value; + } + + public function __toString(): string + { + if (\is_string($this->value)) { + return $this->value; + } + + try { + return $this->value = ($this->value)(); + } catch (\Throwable $e) { + if (\TypeError::class === $e::class && __FILE__ === $e->getFile()) { + $type = explode(', ', $e->getMessage()); + $type = substr(array_pop($type), 0, -\strlen(' returned')); + $r = new \ReflectionFunction($this->value); + $callback = $r->getStaticVariables()['callback']; + + $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); + } + + throw $e; + } + } + + public function __sleep(): array + { + $this->__toString(); + + return ['value']; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + private function __construct() + { + } + + private static function getPrettyName(callable $callback): string + { + if (\is_string($callback)) { + return $callback; + } + + if (\is_array($callback)) { + $class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0]; + $method = $callback[1]; + } elseif ($callback instanceof \Closure) { + $r = new \ReflectionFunction($callback); + + if ($r->isAnonymous() || !$class = $r->getClosureCalledClass()) { + return $r->name; + } + + $class = $class->name; + $method = $r->name; + } else { + $class = get_debug_type($callback); + $method = '__invoke'; + } + + return $class.'::'.$method; + } +} diff --git a/vendor/symfony/string/README.md b/vendor/symfony/string/README.md new file mode 100644 index 0000000..9c7e1e1 --- /dev/null +++ b/vendor/symfony/string/README.md @@ -0,0 +1,14 @@ +String Component +================ + +The String component provides an object-oriented API to strings and deals +with bytes, UTF-8 code points and grapheme clusters in a unified way. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/string.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/string/Resources/data/wcswidth_table_wide.php b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php new file mode 100644 index 0000000..8314c8f --- /dev/null +++ b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php @@ -0,0 +1,1155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +if (!\function_exists(u::class)) { + function u(?string $string = ''): UnicodeString + { + return new UnicodeString($string ?? ''); + } +} + +if (!\function_exists(b::class)) { + function b(?string $string = ''): ByteString + { + return new ByteString($string ?? ''); + } +} + +if (!\function_exists(s::class)) { + /** + * @return UnicodeString|ByteString + */ + function s(?string $string = ''): AbstractString + { + $string ??= ''; + + return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string); + } +} diff --git a/vendor/symfony/string/Slugger/AsciiSlugger.php b/vendor/symfony/string/Slugger/AsciiSlugger.php new file mode 100644 index 0000000..d254532 --- /dev/null +++ b/vendor/symfony/string/Slugger/AsciiSlugger.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\Emoji\EmojiTransliterator; +use Symfony\Component\String\AbstractUnicodeString; +use Symfony\Component\String\UnicodeString; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +if (!interface_exists(LocaleAwareInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".'); +} + +/** + * @author Titouan Galopin + */ +class AsciiSlugger implements SluggerInterface, LocaleAwareInterface +{ + private const LOCALE_TO_TRANSLITERATOR_ID = [ + 'am' => 'Amharic-Latin', + 'ar' => 'Arabic-Latin', + 'az' => 'Azerbaijani-Latin', + 'be' => 'Belarusian-Latin', + 'bg' => 'Bulgarian-Latin', + 'bn' => 'Bengali-Latin', + 'de' => 'de-ASCII', + 'el' => 'Greek-Latin', + 'fa' => 'Persian-Latin', + 'he' => 'Hebrew-Latin', + 'hy' => 'Armenian-Latin', + 'ka' => 'Georgian-Latin', + 'kk' => 'Kazakh-Latin', + 'ky' => 'Kirghiz-Latin', + 'ko' => 'Korean-Latin', + 'mk' => 'Macedonian-Latin', + 'mn' => 'Mongolian-Latin', + 'or' => 'Oriya-Latin', + 'ps' => 'Pashto-Latin', + 'ru' => 'Russian-Latin', + 'sr' => 'Serbian-Latin', + 'sr_Cyrl' => 'Serbian-Latin', + 'th' => 'Thai-Latin', + 'tk' => 'Turkmen-Latin', + 'uk' => 'Ukrainian-Latin', + 'uz' => 'Uzbek-Latin', + 'zh' => 'Han-Latin', + ]; + + private \Closure|array $symbolsMap = [ + 'en' => ['@' => 'at', '&' => 'and'], + ]; + private bool|string $emoji = false; + + /** + * Cache of transliterators per locale. + * + * @var \Transliterator[] + */ + private array $transliterators = []; + + public function __construct( + private ?string $defaultLocale = null, + array|\Closure|null $symbolsMap = null, + ) { + $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; + } + + public function setLocale(string $locale): void + { + $this->defaultLocale = $locale; + } + + public function getLocale(): string + { + return $this->defaultLocale; + } + + /** + * @param bool|string $emoji true will use the same locale, + * false will disable emoji, + * and a string to use a specific locale + */ + public function withEmoji(bool|string $emoji = true): static + { + if (false !== $emoji && !class_exists(EmojiTransliterator::class)) { + throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/emoji" package is not installed. Try running "composer require symfony/emoji".', __METHOD__)); + } + + $new = clone $this; + $new->emoji = $emoji; + + return $new; + } + + public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString + { + $locale ??= $this->defaultLocale; + + $transliterator = []; + if ($locale && ('de' === $locale || str_starts_with($locale, 'de_'))) { + // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl) + $transliterator = ['de-ASCII']; + } elseif (\function_exists('transliterator_transliterate') && $locale) { + $transliterator = (array) $this->createTransliterator($locale); + } + + if ($emojiTransliterator = $this->createEmojiTransliterator($locale)) { + $transliterator[] = $emojiTransliterator; + } + + if ($this->symbolsMap instanceof \Closure) { + // If the symbols map is passed as a closure, there is no need to fallback to the parent locale + // as the closure can just provide substitutions for all locales of interest. + $symbolsMap = $this->symbolsMap; + array_unshift($transliterator, static fn ($s) => $symbolsMap($s, $locale)); + } + + $unicodeString = (new UnicodeString($string))->ascii($transliterator); + + if (\is_array($this->symbolsMap)) { + $map = null; + if (isset($this->symbolsMap[$locale])) { + $map = $this->symbolsMap[$locale]; + } else { + $parent = self::getParentLocale($locale); + if ($parent && isset($this->symbolsMap[$parent])) { + $map = $this->symbolsMap[$parent]; + } + } + if ($map) { + foreach ($map as $char => $replace) { + $unicodeString = $unicodeString->replace($char, ' '.$replace.' '); + } + } + } + + return $unicodeString + ->replaceMatches('/[^A-Za-z0-9]++/', $separator) + ->trim($separator) + ; + } + + private function createTransliterator(string $locale): ?\Transliterator + { + if (\array_key_exists($locale, $this->transliterators)) { + return $this->transliterators[$locale]; + } + + // Exact locale supported, cache and return + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) { + return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + // Locale not supported and no parent, fallback to any-latin + if (!$parent = self::getParentLocale($locale)) { + return $this->transliterators[$locale] = null; + } + + // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) { + $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; + } + + private function createEmojiTransliterator(?string $locale): ?EmojiTransliterator + { + if (\is_string($this->emoji)) { + $locale = $this->emoji; + } elseif (!$this->emoji) { + return null; + } + + while (null !== $locale) { + try { + return EmojiTransliterator::create("emoji-$locale"); + } catch (\IntlException) { + $locale = self::getParentLocale($locale); + } + } + + return null; + } + + private static function getParentLocale(?string $locale): ?string + { + if (!$locale) { + return null; + } + if (false === $str = strrchr($locale, '_')) { + // no parent locale + return null; + } + + return substr($locale, 0, -\strlen($str)); + } +} diff --git a/vendor/symfony/string/Slugger/SluggerInterface.php b/vendor/symfony/string/Slugger/SluggerInterface.php new file mode 100644 index 0000000..dd0d581 --- /dev/null +++ b/vendor/symfony/string/Slugger/SluggerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\String\AbstractUnicodeString; + +/** + * Creates a URL-friendly slug from a given string. + * + * @author Titouan Galopin + */ +interface SluggerInterface +{ + /** + * Creates a slug for the given string and locale, using appropriate transliteration when needed. + */ + public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString; +} diff --git a/vendor/symfony/string/UnicodeString.php b/vendor/symfony/string/UnicodeString.php new file mode 100644 index 0000000..4b16caf --- /dev/null +++ b/vendor/symfony/string/UnicodeString.php @@ -0,0 +1,382 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode grapheme clusters encoded as UTF-8. + * + * A letter followed by combining characters (accents typically) form what Unicode defines + * as a grapheme cluster: a character as humans mean it in written texts. This class knows + * about the concept and won't split a letter apart from its combining accents. It also + * ensures all string comparisons happen on their canonically-composed representation, + * ignoring e.g. the order in which accents are listed when a letter has many of them. + * + * @see https://unicode.org/reports/tr15/ + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class UnicodeString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + if ('' === $string || normalizer_is_normalized($this->string = $string)) { + return; + } + + if (false === $string = normalizer_normalize($string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $this->string = $string; + } + + public function append(string ...$suffix): static + { + $str = clone $this; + $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '\X{65535}'; + $length -= 65535; + } + $rx .= '\X{'.$length.'})/u'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith(string|iterable|AbstractString $suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (!\is_string($suffix)) { + return parent::endsWith($suffix); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form); + + if ('' === $suffix || false === $suffix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8'); + } + + return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)); + } + + public function equalsTo(string|iterable|AbstractString $string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (!\is_string($string)) { + return parent::equalsTo($string); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form); + + if ('' !== $string && false !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOf($needle, $offset); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + try { + $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset); + } catch (\ValueError) { + return null; + } + + return false === $i ? null : $i; + } + + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOfLast($needle, $offset); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + $string = $this->string; + + if (0 > $offset) { + // workaround https://bugs.php.net/74264 + if (0 > $offset += grapheme_strlen($needle)) { + $string = grapheme_substr($string, 0, $offset); + } + $offset = 0; + } + + $i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function join(array $strings, ?string $lastGlue = null): static + { + $str = parent::join($strings, $lastGlue); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function length(): int + { + return grapheme_strlen($this->string); + } + + public function normalize(int $form = self::NFC): static + { + $str = clone $this; + + if (\in_array($form, [self::NFC, self::NFKC], true)) { + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + } elseif (!\in_array($form, [self::NFD, self::NFKD], true)) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } elseif (!normalizer_is_normalized($str->string, $form)) { + $str->string = normalizer_normalize($str->string, $form); + $str->ignoreCase = null; + } + + return $str; + } + + public function prepend(string ...$prefix): static + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + + return $str; + } + + public function replace(string $from, string $to): static + { + $str = clone $this; + normalizer_is_normalized($from) ?: $from = normalizer_normalize($from); + + if ('' !== $from && false !== $from) { + $tail = $str->string; + $result = ''; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while ('' !== $tail && false !== $i = $indexOf($tail, $from)) { + $slice = grapheme_substr($tail, 0, $i); + $result .= $slice.$to; + $tail = substr($tail, \strlen($slice) + \strlen($from)); + } + + $str->string = $result.$tail; + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, string|callable $to): static + { + $str = parent::replaceMatches($fromRegexp, $to); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function slice(int $start = 0, ?int $length = null): static + { + $str = clone $this; + + $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647); + + return $str; + } + + public function splice(string $replacement, int $start = 0, ?int $length = null): static + { + $str = clone $this; + + $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; + $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + + return $str; + } + + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (1 > $limit ??= 2147483647) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter); + + if (false === $delimiter) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $tail = $this->string; + $chunks = []; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) { + $str->string = grapheme_substr($tail, 0, $i); + $chunks[] = clone $str; + $tail = substr($tail, \strlen($str->string) + \strlen($delimiter)); + --$limit; + } + + $str->string = $tail; + $chunks[] = clone $str; + + return $chunks; + } + + public function startsWith(string|iterable|AbstractString $prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form); + + if ('' === $prefix || false === $prefix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8'); + } + + return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); + } + + public function __wakeup(): void + { + if (!\is_string($this->string)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + public function __clone() + { + if (null === $this->ignoreCase) { + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + $this->ignoreCase = false; + } +} diff --git a/vendor/symfony/string/composer.json b/vendor/symfony/string/composer.json new file mode 100644 index 0000000..10d0ee6 --- /dev/null +++ b/vendor/symfony/string/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/string", + "type": "library", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "keywords": ["string", "utf8", "utf-8", "grapheme", "i18n", "unicode"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\String\\": "" }, + "files": [ "Resources/functions.php" ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/translation-contracts/CHANGELOG.md b/vendor/symfony/translation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/translation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/translation-contracts/LICENSE b/vendor/symfony/translation-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/translation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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. diff --git a/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/vendor/symfony/translation-contracts/LocaleAwareInterface.php new file mode 100644 index 0000000..db40ba1 --- /dev/null +++ b/vendor/symfony/translation-contracts/LocaleAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @return void + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + + /** + * Returns the current locale. + */ + public function getLocale(): string; +} diff --git a/vendor/symfony/translation-contracts/README.md b/vendor/symfony/translation-contracts/README.md new file mode 100644 index 0000000..b211d58 --- /dev/null +++ b/vendor/symfony/translation-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Translation Contracts +============================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/vendor/symfony/translation-contracts/Test/TranslatorTest.php new file mode 100644 index 0000000..756228a --- /dev/null +++ b/vendor/symfony/translation-contracts/Test/TranslatorTest.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +/** + * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms + * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms. + * + * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. + * The mozilla code is also interesting to check for. + * + * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 + * + * The goal to cover all languages is to far fetched so this test case is smaller. + * + * @author Clemens Tolboom clemens@build2be.nl + */ +class TranslatorTest extends TestCase +{ + private string $defaultLocale; + + protected function setUp(): void + { + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + } + + protected function tearDown(): void + { + \Locale::setDefault($this->defaultLocale); + } + + public function getTranslator(): TranslatorInterface + { + return new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $parameters) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithExplicitLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @requires extension intl + * + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithEnUsPosix($expected, $id, $number) + { + $translator = $this->getTranslator(); + $translator->setLocale('en_US_POSIX'); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testGetSetLocale() + { + $translator = $this->getTranslator(); + + $this->assertEquals('en', $translator->getLocale()); + } + + /** + * @requires extension intl + */ + public function testGetLocaleReturnsDefaultLocaleIfNotSet() + { + $translator = $this->getTranslator(); + + \Locale::setDefault('pt_BR'); + $this->assertEquals('pt_BR', $translator->getLocale()); + + \Locale::setDefault('en'); + $this->assertEquals('en', $translator->getLocale()); + } + + public static function getTransTests() + { + return [ + ['Symfony is great!', 'Symfony is great!', []], + ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], + ]; + } + + public static function getTransChoiceTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0], + ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1], + ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10], + // custom validation messages may be coded with a fixed value + ['There are 2 apples', 'There are 2 apples', 2], + ]; + } + + /** + * @dataProvider getInterval + */ + public function testInterval($expected, $number, $interval) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); + } + + public static function getInterval() + { + return [ + ['foo', 3, '{1,2, 3 ,4}'], + ['bar', 10, '{1,2, 3 ,4}'], + ['bar', 3, '[1,2]'], + ['foo', 1, '[1,2]'], + ['foo', 2, '[1,2]'], + ['bar', 1, ']1,2['], + ['bar', 2, ']1,2['], + ['foo', log(0), '[-Inf,2['], + ['foo', -log(0), '[-2,+Inf]'], + ]; + } + + /** + * @dataProvider getChooseTests + */ + public function testChoose($expected, $id, $number, $locale = null) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number], null, $locale)); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $translator = $this->getTranslator(); + + $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); + } + + /** + * @dataProvider getNonMatchingMessages + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $translator = $this->getTranslator(); + + $this->expectException(\InvalidArgumentException::class); + + $translator->trans($id, ['%count%' => $number]); + } + + public static function getNonMatchingMessages() + { + return [ + ['{0} There are no apples|{1} There is one apple', 2], + ['{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['{1} There is one apple|]2,Inf] There are %count% apples', 2], + ['{0} There are no apples|There is one apple', 2], + ]; + } + + public static function getChooseTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 10 apples', 'There is one apple|There are %count% apples', 10], + + ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10], + + ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10], + + ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1], + + // Indexed only tests which are Gettext PoFile* compatible strings. + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 2 apples', 'There is one apple|There are %count% apples', 2], + + // Tests for float numbers + ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7], + ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1], + ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0], + ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + + // Test texts with new-lines + // with double-quotes and \n in id & double-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 0], + // with double-quotes and \n in id and single-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with double-quotes and id split across lines + ['This is a text with a + new-line in it. Selector = 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + // with single-quotes and id split across lines + ['This is a text with a + new-line in it. Selector > 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with single-quotes and \n in text + ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0], + // with double-quotes and id split across lines + ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1], + // escape pipe + ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0], + // Empty plural set (2 plural forms) from a .PO file + ['', '|', 1], + // Empty plural set (3 plural forms) from a .PO file + ['', '||', 1], + + // Floating values + ['1.5 liters', '%count% liter|%count% liters', 1.5], + ['1.5 litre', '%count% litre|%count% litres', 1.5, 'fr'], + + // Negative values + ['-1 degree', '%count% degree|%count% degrees', -1], + ['-1 degré', '%count% degré|%count% degrés', -1], + ['-1.5 degrees', '%count% degree|%count% degrees', -1.5], + ['-1.5 degré', '%count% degré|%count% degrés', -1.5, 'fr'], + ['-2 degrees', '%count% degree|%count% degrees', -2], + ['-2 degrés', '%count% degré|%count% degrés', -2], + ]; + } + + /** + * @dataProvider failingLangcodes + */ + public function testFailedLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix, false); + } + + /** + * @dataProvider successLangcodes + */ + public function testLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix); + } + + /** + * This array should contain all currently known langcodes. + * + * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. + */ + public static function successLangcodes(): array + { + return [ + ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], + ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM', 'en_US_POSIX']], + ['3', ['be', 'bs', 'cs', 'hr']], + ['4', ['cy', 'mt', 'sl']], + ['6', ['ar']], + ]; + } + + /** + * This array should be at least empty within the near future. + * + * This both depends on a complete list trying to add above as understanding + * the plural rules of the current failing languages. + * + * @return array with nplural together with langcodes + */ + public static function failingLangcodes(): array + { + return [ + ['1', ['fa']], + ['2', ['jbo']], + ['3', ['cbs']], + ['4', ['gd', 'kw']], + ['5', ['ga']], + ]; + } + + /** + * We validate only on the plural coverage. Thus the real rules is not tested. + * + * @param string $nplural Plural expected + * @param array $matrix Containing langcodes and their plural index values + */ + protected function validateMatrix(string $nplural, array $matrix, bool $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms."); + } else { + $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } + } + } + + protected function generateTestData($langCodes) + { + $translator = new class() { + use TranslatorTrait { + getPluralizationRule as public; + } + }; + + $matrix = []; + foreach ($langCodes as $langCode) { + for ($count = 0; $count < 200; ++$count) { + $plural = $translator->getPluralizationRule($count, $langCode); + $matrix[$langCode][$count] = $plural; + } + } + + return $matrix; + } +} diff --git a/vendor/symfony/translation-contracts/TranslatableInterface.php b/vendor/symfony/translation-contracts/TranslatableInterface.php new file mode 100644 index 0000000..8554697 --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatableInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Nicolas Grekas + */ +interface TranslatableInterface +{ + public function trans(TranslatorInterface $translator, ?string $locale = null): string; +} diff --git a/vendor/symfony/translation-contracts/TranslatorInterface.php b/vendor/symfony/translation-contracts/TranslatorInterface.php new file mode 100644 index 0000000..7fa6987 --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorInterface.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Fabien Potencier + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * When a number is provided as a parameter named "%count%", the message is parsed for plural + * forms and a translation is chosen according to this number using the following rules: + * + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * An interval can represent a finite set of numbers: + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @see https://en.wikipedia.org/wiki/ISO_31-11 + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string; + + /** + * Returns the default locale. + */ + public function getLocale(): string; +} diff --git a/vendor/symfony/translation-contracts/TranslatorTrait.php b/vendor/symfony/translation-contracts/TranslatorTrait.php new file mode 100644 index 0000000..63f6fb3 --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorTrait.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private ?string $locale = null; + + /** + * @return void + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + } + + public function getLocale(): string + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + if (null === $id || '' === $id) { + return ''; + } + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = $locale ?: $this->getLocale(); + + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + if ($number == $n) { + return strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ) { + return strtr($matches['message'], $parameters); + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + $position = $this->getPluralizationRule($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return strtr($standardRules[0], $parameters); + } + + $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + + if (class_exists(InvalidArgumentException::class)) { + throw new InvalidArgumentException($message); + } + + throw new \InvalidArgumentException($message); + } + + return strtr($standardRules[$position], $parameters); + } + + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(float $number, string $locale): int + { + $number = abs($number); + + return match ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + 'af', + 'bn', + 'bg', + 'ca', + 'da', + 'de', + 'el', + 'en', + 'en_US_POSIX', + 'eo', + 'es', + 'et', + 'eu', + 'fa', + 'fi', + 'fo', + 'fur', + 'fy', + 'gl', + 'gu', + 'ha', + 'he', + 'hu', + 'is', + 'it', + 'ku', + 'lb', + 'ml', + 'mn', + 'mr', + 'nah', + 'nb', + 'ne', + 'nl', + 'nn', + 'no', + 'oc', + 'om', + 'or', + 'pa', + 'pap', + 'ps', + 'pt', + 'so', + 'sq', + 'sv', + 'sw', + 'ta', + 'te', + 'tk', + 'ur', + 'zu' => (1 == $number) ? 0 : 1, + 'am', + 'bh', + 'fil', + 'fr', + 'gun', + 'hi', + 'hy', + 'ln', + 'mg', + 'nso', + 'pt_BR', + 'ti', + 'wa' => ($number < 2) ? 0 : 1, + 'be', + 'bs', + 'hr', + 'ru', + 'sh', + 'sr', + 'uk' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2), + 'cs', + 'sk' => (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2), + 'ga' => (1 == $number) ? 0 : ((2 == $number) ? 1 : 2), + 'lt' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2), + 'sl' => (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)), + 'mk' => (1 == $number % 10) ? 0 : 1, + 'mt' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)), + 'lv' => (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2), + 'pl' => (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2), + 'cy' => (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)), + 'ro' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2), + 'ar' => (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))), + default => 0, + }; + } +} diff --git a/vendor/symfony/translation-contracts/composer.json b/vendor/symfony/translation-contracts/composer.json new file mode 100644 index 0000000..181651e --- /dev/null +++ b/vendor/symfony/translation-contracts/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/translation-contracts", + "type": "library", + "description": "Generic abstractions related to translation", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Translation\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/twig-bridge/AppVariable.php b/vendor/symfony/twig-bridge/AppVariable.php new file mode 100644 index 0000000..e7b976e --- /dev/null +++ b/vendor/symfony/twig-bridge/AppVariable.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Translation\LocaleSwitcher; + +/** + * Exposes some Symfony parameters and services as an "app" global variable. + * + * @author Fabien Potencier + */ +class AppVariable +{ + private TokenStorageInterface $tokenStorage; + private RequestStack $requestStack; + private string $environment; + private bool $debug; + private LocaleSwitcher $localeSwitcher; + private array $enabledLocales; + + public function setTokenStorage(TokenStorageInterface $tokenStorage): void + { + $this->tokenStorage = $tokenStorage; + } + + public function setRequestStack(RequestStack $requestStack): void + { + $this->requestStack = $requestStack; + } + + public function setEnvironment(string $environment): void + { + $this->environment = $environment; + } + + public function setDebug(bool $debug): void + { + $this->debug = $debug; + } + + public function setLocaleSwitcher(LocaleSwitcher $localeSwitcher): void + { + $this->localeSwitcher = $localeSwitcher; + } + + public function setEnabledLocales(array $enabledLocales): void + { + $this->enabledLocales = $enabledLocales; + } + + /** + * Returns the current token. + * + * @throws \RuntimeException When the TokenStorage is not available + */ + public function getToken(): ?TokenInterface + { + if (!isset($this->tokenStorage)) { + throw new \RuntimeException('The "app.token" variable is not available.'); + } + + return $this->tokenStorage->getToken(); + } + + /** + * Returns the current user. + * + * @see TokenInterface::getUser() + */ + public function getUser(): ?UserInterface + { + if (!isset($this->tokenStorage)) { + throw new \RuntimeException('The "app.user" variable is not available.'); + } + + return $this->tokenStorage->getToken()?->getUser(); + } + + /** + * Returns the current request. + */ + public function getRequest(): ?Request + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.request" variable is not available.'); + } + + return $this->requestStack->getCurrentRequest(); + } + + /** + * Returns the current session. + */ + public function getSession(): ?SessionInterface + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.session" variable is not available.'); + } + $request = $this->getRequest(); + + return $request?->hasSession() ? $request->getSession() : null; + } + + /** + * Returns the current app environment. + */ + public function getEnvironment(): string + { + if (!isset($this->environment)) { + throw new \RuntimeException('The "app.environment" variable is not available.'); + } + + return $this->environment; + } + + /** + * Returns the current app debug mode. + */ + public function getDebug(): bool + { + if (!isset($this->debug)) { + throw new \RuntimeException('The "app.debug" variable is not available.'); + } + + return $this->debug; + } + + public function getLocale(): string + { + if (!isset($this->localeSwitcher)) { + throw new \RuntimeException('The "app.locale" variable is not available.'); + } + + return $this->localeSwitcher->getLocale(); + } + + public function getEnabled_locales(): array + { + if (!isset($this->enabledLocales)) { + throw new \RuntimeException('The "app.enabled_locales" variable is not available.'); + } + + return $this->enabledLocales; + } + + /** + * Returns some or all the existing flash messages: + * * getFlashes() returns all the flash messages + * * getFlashes('notice') returns a simple array with flash messages of that type + * * getFlashes(['notice', 'error']) returns a nested array of type => messages. + */ + public function getFlashes(string|array|null $types = null): array + { + try { + $session = $this->getSession(); + } catch (\RuntimeException) { + return []; + } + + if (!$session instanceof FlashBagAwareSessionInterface) { + return []; + } + + if (null === $types || '' === $types || [] === $types) { + return $session->getFlashBag()->all(); + } + + if (\is_string($types)) { + return $session->getFlashBag()->get($types); + } + + $result = []; + foreach ($types as $type) { + $result[$type] = $session->getFlashBag()->get($type); + } + + return $result; + } + + public function getCurrent_route(): ?string + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.current_route" variable is not available.'); + } + + return $this->getRequest()?->attributes->get('_route'); + } + + /** + * @return array + */ + public function getCurrent_route_parameters(): array + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.current_route_parameters" variable is not available.'); + } + + return $this->getRequest()?->attributes->get('_route_params') ?? []; + } +} diff --git a/vendor/symfony/twig-bridge/Attribute/Template.php b/vendor/symfony/twig-bridge/Attribute/Template.php new file mode 100644 index 0000000..e265e23 --- /dev/null +++ b/vendor/symfony/twig-bridge/Attribute/Template.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Attribute; + +/** + * Define the template to render in the controller. + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +class Template +{ + /** + * @param string $template The name of the template to render + * @param string[]|null $vars The controller method arguments to pass to the template + * @param bool $stream Enables streaming the template + */ + public function __construct( + public string $template, + public ?array $vars = null, + public bool $stream = false, + ) { + } +} diff --git a/vendor/symfony/twig-bridge/CHANGELOG.md b/vendor/symfony/twig-bridge/CHANGELOG.md new file mode 100644 index 0000000..df8f28f --- /dev/null +++ b/vendor/symfony/twig-bridge/CHANGELOG.md @@ -0,0 +1,205 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `emojify` Twig filter + +7.0 +--- + + * Drop support for Twig 2 + +6.4 +--- + + * Allow an array to be passed as the first argument to the `importmap()` Twig function + * Add `TemplatedEmail::locale()` to set the locale for the email rendering + * Add `AppVariable::getEnabledLocales()` to retrieve the enabled locales + * Add `impersonation_path()` and `impersonation_url()` Twig functions + +6.3 +--- + + * Add `AppVariable::getLocale()` to retrieve the current locale when using the `LocaleSwitcher` + +6.2 +--- + + * Add `form_label_content` and `form_help_content` block to form themes + * Add `#[Template()]` to describe how to render arrays returned by controllers + * Add support for toggle buttons in Bootstrap 5 form theme + * Add `app.current_route` and `app.current_route_parameters` variables + +6.1 +--- + + * Wrap help messages on form elements in `div` instead of `p` + +5.4 +--- + +* Add `github` format & autodetection to render errors as annotations when + running the Twig linter command in a Github Actions environment. + +5.3 +--- + + * Add a new `markAsPublic` method on `NotificationEmail` to change the `importance` context option to null after creation + * Add a new `fragment_uri()` helper to generate the URI of a fragment + * Add support of Bootstrap 5 for form theming + * Add a new `serialize` filter to serialize objects using the Serializer component + +5.2.0 +----- + + * added the `impersonation_exit_url()` and `impersonation_exit_path()` functions. They return a URL that allows to switch back to the original user. + * added the `workflow_transition()` function to easily retrieve a specific transition object + * added support for translating `TranslatableInterface` objects + * added the `t()` function to easily create `TranslatableMessage` objects + * Added support for extracting messages from the `t()` function + * Added `field_*` Twig functions to access string values from Form fields + * changed the `importance` context option of `NotificationEmail` to allow `null` + +5.0.0 +----- + + * removed `TwigEngine` class, use `\Twig\Environment` instead. + * removed `transChoice` filter and token + * `HttpFoundationExtension` requires a `UrlHelper` on instantiation + * removed support for implicit STDIN usage in the `lint:twig` command, use `lint:twig -` (append a dash) instead to make it explicit. + * added form theme for Foundation 6 + * added support for Foundation 6 switches: add the `switch-input` class to the attributes of a `CheckboxType` + +4.4.0 +----- + + * added a new `TwigErrorRenderer` for `html` format, integrated with the `ErrorHandler` component + * marked all classes extending twig as `@final` + * deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the + `DebugCommand::__construct()` method, swap the variables position. + * the `LintCommand` lints all the templates stored in all configured Twig paths if none argument is provided + * deprecated accepting STDIN implicitly when using the `lint:twig` command, use `lint:twig -` (append a dash) instead to make it explicit. + * added `--show-deprecations` option to the `lint:twig` command + * added support for Bootstrap4 switches: add the `switch-custom` class to the label attributes of a `CheckboxType` + * Marked the `TwigDataCollector` class as `@final`. + +4.3.0 +----- + + * added the `form_parent()` function that allows to reliably retrieve the parent form in Twig templates + * added the `workflow_transition_blockers()` function + * deprecated the `$requestStack` and `$requestContext` arguments of the + `HttpFoundationExtension`, pass a `Symfony\Component\HttpFoundation\UrlHelper` + instance as the only argument instead + +4.2.0 +----- + + * add bundle name suggestion on wrongly overridden templates paths + * added `name` argument in `debug:twig` command and changed `filter` argument as `--filter` option + * deprecated the `transchoice` tag and filter, use the `trans` ones instead with a `%count%` parameter + +4.1.0 +----- + + * add a `workflow_metadata` function + +3.4.0 +----- + + * added an `only` keyword to `form_theme` tag to disable usage of default themes when rendering a form + * deprecated `Symfony\Bridge\Twig\Form\TwigRenderer` + * deprecated `DebugCommand::set/getTwigEnvironment`. Pass an instance of + `Twig\Environment` as first argument of the constructor instead + * deprecated `LintCommand::set/getTwigEnvironment`. Pass an instance of + `Twig\Environment` as first argument of the constructor instead + +3.3.0 +----- + + * added a `workflow_has_marked_place` function + * added a `workflow_marked_places` function + +3.2.0 +----- + + * added `AppVariable::getToken()` + * Deprecated the possibility to inject the Form `TwigRenderer` into the `FormExtension`. + * [BC BREAK] Registering the `FormExtension` without configuring a runtime loader for the `TwigRenderer` + doesn't work anymore. + + Before: + + ```php + use Symfony\Bridge\Twig\Extension\FormExtension; + use Symfony\Bridge\Twig\Form\TwigRenderer; + use Symfony\Bridge\Twig\Form\TwigRendererEngine; + + // ... + $rendererEngine = new TwigRendererEngine(['form_div_layout.html.twig']); + $rendererEngine->setEnvironment($twig); + $twig->addExtension(new FormExtension(new TwigRenderer($rendererEngine, $csrfTokenManager))); + ``` + + After: + + ```php + // ... + $rendererEngine = new TwigRendererEngine(['form_div_layout.html.twig'], $twig); + // require Twig 1.30+ + $twig->addRuntimeLoader(new \Twig\RuntimeLoader\FactoryRuntimeLoader([ + TwigRenderer::class => function () use ($rendererEngine, $csrfTokenManager) { + return new TwigRenderer($rendererEngine, $csrfTokenManager); + }, + ])); + $twig->addExtension(new FormExtension()); + ``` + * Deprecated the `TwigRendererEngineInterface` interface. + * added WorkflowExtension (provides `workflow_can` and `workflow_transitions`) + +2.7.0 +----- + + * added LogoutUrlExtension (provides `logout_url` and `logout_path`) + * added an HttpFoundation extension (provides the `absolute_url` and the `relative_path` functions) + * added AssetExtension (provides the `asset` and `asset_version` functions) + * Added possibility to extract translation messages from a file or files besides extracting from a directory + +2.5.0 +----- + + * moved command `twig:lint` from `TwigBundle` + +2.4.0 +----- + + * added stopwatch tag to time templates with the WebProfilerBundle + +2.3.0 +----- + + * added helpers form(), form_start() and form_end() + * deprecated form_enctype() in favor of form_start() + +2.2.0 +----- + + * added a `controller` function to help generating controller references + * added a `render_esi` and a `render_hinclude` function + * [BC BREAK] restricted the `render` tag to only accept URIs or ControllerReference instances (the signature changed) + * added a `render` function to render a request + * The `app` global variable is now injected even when using the twig service directly. + * Added an optional parameter to the `path` and `url` function which allows to generate + relative paths (e.g. "../parent-file") and scheme-relative URLs (e.g. "//example.com/dir/file"). + +2.1.0 +----- + + * added global variables access in a form theme + * added TwigEngine + * added TwigExtractor + * added a csrf_token function + * added a way to specify a default domain for a Twig template (via the + 'trans_default_domain' tag) diff --git a/vendor/symfony/twig-bridge/Command/DebugCommand.php b/vendor/symfony/twig-bridge/Command/DebugCommand.php new file mode 100644 index 0000000..21ede17 --- /dev/null +++ b/vendor/symfony/twig-bridge/Command/DebugCommand.php @@ -0,0 +1,589 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\Finder\Finder; +use Twig\Environment; +use Twig\Loader\ChainLoader; +use Twig\Loader\FilesystemLoader; + +/** + * Lists twig functions, filters, globals and tests present in the current project. + * + * @author Jordi Boggiano + */ +#[AsCommand(name: 'debug:twig', description: 'Show a list of twig functions, filters, globals and tests')] +class DebugCommand extends Command +{ + /** + * @var FilesystemLoader[] + */ + private array $filesystemLoaders; + + public function __construct( + private Environment $twig, + private ?string $projectDir = null, + private array $bundlesMetadata = [], + private ?string $twigDefaultPath = null, + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), + new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command outputs a list of twig functions, +filters, globals and tests. + + php %command.full_name% + +The command lists all functions, filters, etc. + + php %command.full_name% @Twig/Exception/error.html.twig + +The command lists all paths that match the given template name. + + php %command.full_name% --filter=date + +The command lists everything that contains the word date. + + php %command.full_name% --format=json + +The command lists everything in a machine readable json format. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $name = $input->getArgument('name'); + $filter = $input->getOption('filter'); + + if (null !== $name && [] === $this->getFilesystemLoaders()) { + throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); + } + + match ($input->getOption('format')) { + 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), + 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->getLoaderPaths())); + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function displayPathsText(SymfonyStyle $io, string $name): void + { + $file = new \ArrayIterator($this->findTemplateFiles($name)); + $paths = $this->getLoaderPaths($name); + + $io->section('Matched File'); + if ($file->valid()) { + if ($fileLink = $this->getFileLink($file->key())) { + $io->block($file->current(), 'OK', sprintf('fg=black;bg=green;href=%s', $fileLink), ' ', true); + } else { + $io->success($file->current()); + } + $file->next(); + + if ($file->valid()) { + $io->section('Overridden Files'); + do { + if ($fileLink = $this->getFileLink($file->key())) { + $io->text(sprintf('* %s', $fileLink, $file->current())); + } else { + $io->text(sprintf('* %s', $file->current())); + } + $file->next(); + } while ($file->valid()); + } + } else { + $alternatives = []; + + if ($paths) { + $shortnames = []; + $dirs = []; + foreach (current($paths) as $path) { + $dirs[] = $this->isAbsolutePath($path) ? $path : $this->projectDir.'/'.$path; + } + foreach (Finder::create()->files()->followLinks()->in($dirs) as $file) { + $shortnames[] = str_replace('\\', '/', $file->getRelativePathname()); + } + + [$namespace, $shortname] = $this->parseTemplateName($name); + $alternatives = $this->findAlternatives($shortname, $shortnames); + if (FilesystemLoader::MAIN_NAMESPACE !== $namespace) { + $alternatives = array_map(fn ($shortname) => '@'.$namespace.'/'.$shortname, $alternatives); + } + } + + $this->error($io, sprintf('Template name "%s" not found', $name), $alternatives); + } + + $io->section('Configured Paths'); + if ($paths) { + $io->table(['Namespace', 'Paths'], $this->buildTableRows($paths)); + } else { + $alternatives = []; + $namespace = $this->parseTemplateName($name)[0]; + + if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { + $message = 'No template paths configured for your application'; + } else { + $message = sprintf('No template paths configured for "@%s" namespace', $namespace); + foreach ($this->getFilesystemLoaders() as $loader) { + $namespaces = $loader->getNamespaces(); + foreach ($this->findAlternatives($namespace, $namespaces) as $namespace) { + $alternatives[] = '@'.$namespace; + } + } + } + + $this->error($io, $message, $alternatives); + + if (!$alternatives && $paths = $this->getLoaderPaths()) { + $io->table(['Namespace', 'Paths'], $this->buildTableRows($paths)); + } + } + } + + private function displayPathsJson(SymfonyStyle $io, string $name): void + { + $files = $this->findTemplateFiles($name); + $paths = $this->getLoaderPaths($name); + + if ($files) { + $data['matched_file'] = array_shift($files); + if ($files) { + $data['overridden_files'] = $files; + } + } else { + $data['matched_file'] = sprintf('Template name "%s" not found', $name); + } + $data['loader_paths'] = $paths; + + $io->writeln(json_encode($data)); + } + + private function displayGeneralText(SymfonyStyle $io, ?string $filter = null): void + { + $decorated = $io->isDecorated(); + $types = ['functions', 'filters', 'tests', 'globals']; + foreach ($types as $index => $type) { + $items = []; + foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) { + if (!$filter || str_contains($name, $filter)) { + $items[$name] = $name.$this->getPrettyMetadata($type, $entity, $decorated); + } + } + + if (!$items) { + continue; + } + + $io->section(ucfirst($type)); + + ksort($items); + $io->listing($items); + } + + if (!$filter && $paths = $this->getLoaderPaths()) { + $io->section('Loader Paths'); + $io->table(['Namespace', 'Paths'], $this->buildTableRows($paths)); + } + + if ($wrongBundles = $this->findWrongBundleOverrides()) { + foreach ($this->buildWarningMessages($wrongBundles) as $message) { + $io->warning($message); + } + } + } + + private function displayGeneralJson(SymfonyStyle $io, ?string $filter): void + { + $decorated = $io->isDecorated(); + $types = ['functions', 'filters', 'tests', 'globals']; + $data = []; + foreach ($types as $type) { + foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) { + if (!$filter || str_contains($name, $filter)) { + $data[$type][$name] = $this->getMetadata($type, $entity); + } + } + } + if (isset($data['tests'])) { + $data['tests'] = array_keys($data['tests']); + } + + if (!$filter && $paths = $this->getLoaderPaths($filter)) { + $data['loader_paths'] = $paths; + } + + if ($wrongBundles = $this->findWrongBundleOverrides()) { + $data['warnings'] = $this->buildWarningMessages($wrongBundles); + } + + $data = json_encode($data, \JSON_PRETTY_PRINT); + $io->writeln($decorated ? OutputFormatter::escape($data) : $data); + } + + private function getLoaderPaths(?string $name = null): array + { + $loaderPaths = []; + foreach ($this->getFilesystemLoaders() as $loader) { + $namespaces = $loader->getNamespaces(); + if (null !== $name) { + $namespace = $this->parseTemplateName($name)[0]; + $namespaces = array_intersect([$namespace], $namespaces); + } + + foreach ($namespaces as $namespace) { + $paths = array_map($this->getRelativePath(...), $loader->getPaths($namespace)); + + if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { + $namespace = '(None)'; + } else { + $namespace = '@'.$namespace; + } + + $loaderPaths[$namespace] = array_merge($loaderPaths[$namespace] ?? [], $paths); + } + } + + return $loaderPaths; + } + + private function getMetadata(string $type, mixed $entity): mixed + { + if ('globals' === $type) { + return $entity; + } + if ('tests' === $type) { + return null; + } + if ('functions' === $type || 'filters' === $type) { + $cb = $entity->getCallable(); + if (null === $cb) { + return null; + } + if (\is_array($cb)) { + if (!method_exists($cb[0], $cb[1])) { + return null; + } + $refl = new \ReflectionMethod($cb[0], $cb[1]); + } elseif (\is_object($cb) && method_exists($cb, '__invoke')) { + $refl = new \ReflectionMethod($cb, '__invoke'); + } elseif (\function_exists($cb)) { + $refl = new \ReflectionFunction($cb); + } elseif (\is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) { + $refl = new \ReflectionMethod($m[1], $m[2]); + } else { + throw new \UnexpectedValueException('Unsupported callback type.'); + } + + $args = $refl->getParameters(); + + // filter out context/environment args + if ($entity->needsEnvironment()) { + array_shift($args); + } + if ($entity->needsContext()) { + array_shift($args); + } + + if ('filters' === $type) { + // remove the value the filter is applied on + array_shift($args); + } + + // format args + $args = array_map(function (\ReflectionParameter $param) { + if ($param->isDefaultValueAvailable()) { + return $param->getName().' = '.json_encode($param->getDefaultValue()); + } + + return $param->getName(); + }, $args); + + return $args; + } + + return null; + } + + private function getPrettyMetadata(string $type, mixed $entity, bool $decorated): ?string + { + if ('tests' === $type) { + return ''; + } + + try { + $meta = $this->getMetadata($type, $entity); + if (null === $meta) { + return '(unknown?)'; + } + } catch (\UnexpectedValueException $e) { + return sprintf(' %s', $decorated ? OutputFormatter::escape($e->getMessage()) : $e->getMessage()); + } + + if ('globals' === $type) { + if (\is_object($meta)) { + return ' = object('.$meta::class.')'; + } + + $description = substr(@json_encode($meta), 0, 50); + + return sprintf(' = %s', $decorated ? OutputFormatter::escape($description) : $description); + } + + if ('functions' === $type) { + return '('.implode(', ', $meta).')'; + } + + if ('filters' === $type) { + return $meta ? '('.implode(', ', $meta).')' : ''; + } + + return null; + } + + private function findWrongBundleOverrides(): array + { + $alternatives = []; + $bundleNames = []; + + if ($this->twigDefaultPath && $this->projectDir) { + $folders = glob($this->twigDefaultPath.'/bundles/*', \GLOB_ONLYDIR); + $relativePath = ltrim(substr($this->twigDefaultPath.'/bundles/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + $bundleNames = array_reduce($folders, function ($carry, $absolutePath) use ($relativePath) { + if (str_starts_with($absolutePath, $this->projectDir)) { + $name = basename($absolutePath); + $path = ltrim($relativePath.$name, \DIRECTORY_SEPARATOR); + $carry[$name] = $path; + } + + return $carry; + }, $bundleNames); + } + + if ($notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata)) { + $alternatives = []; + foreach ($notFoundBundles as $notFoundBundle => $path) { + $alternatives[$path] = $this->findAlternatives($notFoundBundle, array_keys($this->bundlesMetadata)); + } + } + + return $alternatives; + } + + private function buildWarningMessages(array $wrongBundles): array + { + $messages = []; + foreach ($wrongBundles as $path => $alternatives) { + $message = sprintf('Path "%s" not matching any bundle found', $path); + if ($alternatives) { + if (1 === \count($alternatives)) { + $message .= sprintf(", did you mean \"%s\"?\n", $alternatives[0]); + } else { + $message .= ", did you mean one of these:\n"; + foreach ($alternatives as $bundle) { + $message .= sprintf(" - %s\n", $bundle); + } + } + } + $messages[] = trim($message); + } + + return $messages; + } + + private function error(SymfonyStyle $io, string $message, array $alternatives = []): void + { + if ($alternatives) { + if (1 === \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + $io->block($message, null, 'fg=white;bg=red', ' ', true); + } + + private function findTemplateFiles(string $name): array + { + [$namespace, $shortname] = $this->parseTemplateName($name); + + $files = []; + foreach ($this->getFilesystemLoaders() as $loader) { + foreach ($loader->getPaths($namespace) as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->projectDir.'/'.$path; + } + $filename = $path.'/'.$shortname; + + if (is_file($filename)) { + if (false !== $realpath = realpath($filename)) { + $files[$realpath] = $this->getRelativePath($realpath); + } else { + $files[$filename] = $this->getRelativePath($filename); + } + } + } + } + + return $files; + } + + private function parseTemplateName(string $name, string $default = FilesystemLoader::MAIN_NAMESPACE): array + { + if (isset($name[0]) && '@' === $name[0]) { + if (false === ($pos = strpos($name, '/')) || $pos === \strlen($name) - 1) { + throw new InvalidArgumentException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return [$namespace, $shortname]; + } + + return [$default, $name]; + } + + private function buildTableRows(array $loaderPaths): array + { + $rows = []; + $firstNamespace = true; + $prevHasSeparator = false; + + foreach ($loaderPaths as $namespace => $paths) { + if (!$firstNamespace && !$prevHasSeparator && \count($paths) > 1) { + $rows[] = ['', '']; + } + $firstNamespace = false; + foreach ($paths as $path) { + $rows[] = [$namespace, $path.\DIRECTORY_SEPARATOR]; + $namespace = ''; + } + if (\count($paths) > 1) { + $rows[] = ['', '']; + $prevHasSeparator = true; + } else { + $prevHasSeparator = false; + } + } + if ($prevHasSeparator) { + array_pop($rows); + } + + return $rows; + } + + private function findAlternatives(string $name, array $collection): array + { + $alternatives = []; + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $threshold = 1e3; + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + private function getRelativePath(string $path): string + { + if (null !== $this->projectDir && str_starts_with($path, $this->projectDir)) { + return ltrim(substr($path, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + + return $path; + } + + private function isAbsolutePath(string $file): bool + { + return strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) && ':' === $file[1] && strspn($file, '/\\', 2, 1)) || null !== parse_url($file, \PHP_URL_SCHEME); + } + + /** + * @return FilesystemLoader[] + */ + private function getFilesystemLoaders(): array + { + if (isset($this->filesystemLoaders)) { + return $this->filesystemLoaders; + } + $this->filesystemLoaders = []; + + $loader = $this->twig->getLoader(); + if ($loader instanceof FilesystemLoader) { + $this->filesystemLoaders[] = $loader; + } elseif ($loader instanceof ChainLoader) { + foreach ($loader->getLoaders() as $l) { + if ($l instanceof FilesystemLoader) { + $this->filesystemLoaders[] = $l; + } + } + } + + return $this->filesystemLoaders; + } + + private function getFileLink(string $absolutePath): string + { + return (string) $this->fileLinkFormatter?->format($absolutePath, 1); + } + + private function getAvailableFormatOptions(): array + { + return ['text', 'json']; + } +} diff --git a/vendor/symfony/twig-bridge/Command/LintCommand.php b/vendor/symfony/twig-bridge/Command/LintCommand.php new file mode 100644 index 0000000..14c00ba --- /dev/null +++ b/vendor/symfony/twig-bridge/Command/LintCommand.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Finder\Finder; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Loader\ArrayLoader; +use Twig\Loader\FilesystemLoader; +use Twig\Source; + +/** + * Command that will validate your template syntax and output encountered errors. + * + * @author Marc Weistroff + * @author Jérôme Tamarelle + */ +#[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] +class LintCommand extends Command +{ + private array $excludes; + private string $format; + + public function __construct( + private Environment $twig, + private array $namePatterns = ['*.twig'], + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) + ->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors') + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('excludes', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Excluded directories', []) + ->setHelp(<<<'EOF' +The %command.name% command lints a template and outputs to STDOUT +the first encountered syntax error. + +You can validate the syntax of contents passed from STDIN: + + cat filename | php %command.full_name% - + +Or the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $filenames = $input->getArgument('filename'); + $showDeprecations = $input->getOption('show-deprecations'); + $this->excludes = $input->getOption('excludes'); + $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); + + if (['-'] === $filenames) { + return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]); + } + + if (!$filenames) { + $loader = $this->twig->getLoader(); + if ($loader instanceof FilesystemLoader) { + $paths = []; + foreach ($loader->getNamespaces() as $namespace) { + $paths[] = $loader->getPaths($namespace); + } + $filenames = array_merge(...$paths); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe template content to STDIN.'); + } + } + + if ($showDeprecations) { + $prevErrorHandler = set_error_handler(static function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + $templateLine = 0; + if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) { + $templateLine = $matches[1]; + } + + throw new Error($message, $templateLine); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + } + + try { + $filesInfo = $this->getFilesInfo($filenames); + } finally { + if ($showDeprecations) { + restore_error_handler(); + } + } + + return $this->display($input, $output, $io, $filesInfo); + } + + private function getFilesInfo(array $filenames): array + { + $filesInfo = []; + foreach ($filenames as $filename) { + foreach ($this->findFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $file); + } + } + + return $filesInfo; + } + + protected function findFiles(string $filename): iterable + { + if (is_file($filename)) { + return [$filename]; + } elseif (is_dir($filename)) { + return Finder::create()->files()->in($filename)->name($this->namePatterns)->exclude($this->excludes); + } + + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + private function validate(string $template, string $file): array + { + $realLoader = $this->twig->getLoader(); + try { + $temporaryLoader = new ArrayLoader([$file => $template]); + $this->twig->setLoader($temporaryLoader); + $nodeTree = $this->twig->parse($this->twig->tokenize(new Source($template, $file))); + $this->twig->compile($nodeTree); + $this->twig->setLoader($realLoader); + } catch (Error $e) { + $this->twig->setLoader($realLoader); + + return ['template' => $template, 'file' => $file, 'line' => $e->getTemplateLine(), 'valid' => false, 'exception' => $e]; + } + + return ['template' => $template, 'file' => $file, 'valid' => true]; + } + + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files): int + { + return match ($this->format) { + 'txt' => $this->displayTxt($output, $io, $files), + 'json' => $this->displayJson($output, $files), + 'github' => $this->displayTxt($output, $io, $files, true), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + } + + private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int + { + $errors = 0; + $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $output->isVerbose()) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$errors; + $this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter); + } + } + + if (0 === $errors) { + $io->success(sprintf('All %d Twig files contain valid syntax.', \count($filesInfo))); + } else { + $io->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors)); + } + + return min($errors, 1); + } + + private function displayJson(OutputInterface $output, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + unset($v['template']); + if (!$v['valid']) { + $v['message'] = $v['exception']->getMessage(); + unset($v['exception']); + ++$errors; + } + }); + + $output->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function renderException(SymfonyStyle $output, string $template, Error $exception, ?string $file = null, ?GithubActionReporter $githubReporter = null): void + { + $line = $exception->getTemplateLine(); + + $githubReporter?->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); + + if ($file) { + $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); + } else { + $output->text(sprintf(' ERROR (line %s)', $line)); + } + + // If the line is not known (this might happen for deprecations if we fail at detecting the line for instance), + // we render the message without context, to ensure the message is displayed. + if ($line <= 0) { + $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + + return; + } + + foreach ($this->getContext($template, $line) as $lineNumber => $code) { + $output->text(sprintf( + '%s %-6s %s', + $lineNumber === $line ? ' >> ' : ' ', + $lineNumber, + $code + )); + if ($lineNumber === $line) { + $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + } + } + } + + private function getContext(string $template, int $line, int $context = 3): array + { + $lines = explode("\n", $template); + + $position = max(0, $line - $context); + $max = min(\count($lines), $line - 1 + $context); + + $result = []; + while ($position < $max) { + $result[$position + 1] = $lines[$position]; + ++$position; + } + + return $result; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'json', 'github']; + } +} diff --git a/vendor/symfony/twig-bridge/DataCollector/TwigDataCollector.php b/vendor/symfony/twig-bridge/DataCollector/TwigDataCollector.php new file mode 100644 index 0000000..f63d85a --- /dev/null +++ b/vendor/symfony/twig-bridge/DataCollector/TwigDataCollector.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Markup; +use Twig\Profiler\Dumper\HtmlDumper; +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier + * + * @final + */ +class TwigDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private array $computed; + + public function __construct( + private Profile $profile, + private ?Environment $twig = null, + ) { + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + } + + public function reset(): void + { + $this->profile->reset(); + unset($this->computed); + $this->data = []; + } + + public function lateCollect(): void + { + $this->data['profile'] = serialize($this->profile); + $this->data['template_paths'] = []; + + if (null === $this->twig) { + return; + } + + $templateFinder = function (Profile $profile) use (&$templateFinder) { + if ($profile->isTemplate()) { + try { + $template = $this->twig->load($name = $profile->getName()); + } catch (LoaderError) { + $template = null; + } + + if (null !== $template && '' !== $path = $template->getSourceContext()->getPath()) { + $this->data['template_paths'][$name] = $path; + } + } + + foreach ($profile as $p) { + $templateFinder($p); + } + }; + $templateFinder($this->profile); + } + + public function getTime(): float + { + return $this->getProfile()->getDuration() * 1000; + } + + public function getTemplateCount(): int + { + return $this->getComputedData('template_count'); + } + + public function getTemplatePaths(): array + { + return $this->data['template_paths']; + } + + public function getTemplates(): array + { + return $this->getComputedData('templates'); + } + + public function getBlockCount(): int + { + return $this->getComputedData('block_count'); + } + + public function getMacroCount(): int + { + return $this->getComputedData('macro_count'); + } + + public function getHtmlCallGraph(): Markup + { + $dumper = new HtmlDumper(); + $dump = $dumper->dump($this->getProfile()); + + // needed to remove the hardcoded CSS styles + $dump = str_replace([ + '', + '', + '', + '', + ], [ + '', + '', + '', + '', + ], $dump); + + return new Markup($dump, 'UTF-8'); + } + + public function getProfile(): Profile + { + return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => [Profile::class]]); + } + + private function getComputedData(string $index): mixed + { + $this->computed ??= $this->computeData($this->getProfile()); + + return $this->computed[$index]; + } + + private function computeData(Profile $profile): array + { + $data = [ + 'template_count' => 0, + 'block_count' => 0, + 'macro_count' => 0, + ]; + + $templates = []; + foreach ($profile as $p) { + $d = $this->computeData($p); + + $data['template_count'] += ($p->isTemplate() ? 1 : 0) + $d['template_count']; + $data['block_count'] += ($p->isBlock() ? 1 : 0) + $d['block_count']; + $data['macro_count'] += ($p->isMacro() ? 1 : 0) + $d['macro_count']; + + if ($p->isTemplate()) { + if (!isset($templates[$p->getTemplate()])) { + $templates[$p->getTemplate()] = 1; + } else { + ++$templates[$p->getTemplate()]; + } + } + + foreach ($d['templates'] as $template => $count) { + if (!isset($templates[$template])) { + $templates[$template] = $count; + } else { + $templates[$template] += $count; + } + } + } + $data['templates'] = $templates; + + return $data; + } + + public function getName(): string + { + return 'twig'; + } +} diff --git a/vendor/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php b/vendor/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php new file mode 100644 index 0000000..0ea9b9a --- /dev/null +++ b/vendor/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\ErrorRenderer; + +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\RequestStack; +use Twig\Environment; + +/** + * Provides the ability to render custom Twig-based HTML error pages + * in non-debug mode, otherwise falls back to HtmlErrorRenderer. + * + * @author Yonel Ceruto + */ +class TwigErrorRenderer implements ErrorRendererInterface +{ + private HtmlErrorRenderer $fallbackErrorRenderer; + private \Closure|bool $debug; + + /** + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + */ + public function __construct( + private Environment $twig, + ?HtmlErrorRenderer $fallbackErrorRenderer = null, + bool|callable $debug = false, + ) { + $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); + $this->debug = \is_bool($debug) ? $debug : $debug(...); + } + + public function render(\Throwable $exception): FlattenException + { + $flattenException = FlattenException::createFromThrowable($exception); + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($flattenException); + + if ($debug || !$template = $this->findTemplate($flattenException->getStatusCode())) { + return $this->fallbackErrorRenderer->render($exception); + } + + return $flattenException->setAsString($this->twig->render($template, [ + 'exception' => $flattenException, + 'status_code' => $flattenException->getStatusCode(), + 'status_text' => $flattenException->getStatusText(), + ])); + } + + public static function isDebug(RequestStack $requestStack, bool $debug): \Closure + { + return static function () use ($requestStack, $debug): bool { + if (!$request = $requestStack->getCurrentRequest()) { + return $debug; + } + + return $debug && $request->attributes->getBoolean('showException', true); + }; + } + + private function findTemplate(int $statusCode): ?string + { + $template = sprintf('@Twig/Exception/error%s.html.twig', $statusCode); + if ($this->twig->getLoader()->exists($template)) { + return $template; + } + + $template = '@Twig/Exception/error.html.twig'; + if ($this->twig->getLoader()->exists($template)) { + return $template; + } + + return null; + } +} diff --git a/vendor/symfony/twig-bridge/EventListener/TemplateAttributeListener.php b/vendor/symfony/twig-bridge/EventListener/TemplateAttributeListener.php new file mode 100644 index 0000000..f5962de --- /dev/null +++ b/vendor/symfony/twig-bridge/EventListener/TemplateAttributeListener.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\EventListener; + +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Twig\Environment; + +class TemplateAttributeListener implements EventSubscriberInterface +{ + public function __construct( + private Environment $twig, + ) { + } + + public function onKernelView(ViewEvent $event): void + { + $parameters = $event->getControllerResult(); + + if (!\is_array($parameters ?? [])) { + return; + } + $attribute = $event->getRequest()->attributes->get('_template'); + + if (!$attribute instanceof Template && !$attribute = $event->controllerArgumentsEvent?->getAttributes()[Template::class][0] ?? null) { + return; + } + + $parameters ??= $this->resolveParameters($event->controllerArgumentsEvent, $attribute->vars); + $status = 200; + + foreach ($parameters as $k => $v) { + if (!$v instanceof FormInterface) { + continue; + } + if ($v->isSubmitted() && !$v->isValid()) { + $status = 422; + } + $parameters[$k] = $v->createView(); + } + + $event->setResponse($attribute->stream + ? new StreamedResponse(fn () => $this->twig->display($attribute->template, $parameters), $status) + : new Response($this->twig->render($attribute->template, $parameters), $status) + ); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::VIEW => ['onKernelView', -128], + ]; + } + + private function resolveParameters(ControllerArgumentsEvent $event, ?array $vars): array + { + if ([] === $vars) { + return []; + } + + $parameters = $event->getNamedArguments(); + + if (null !== $vars) { + $parameters = array_intersect_key($parameters, array_flip($vars)); + } + + return $parameters; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/AssetExtension.php b/vendor/symfony/twig-bridge/Extension/AssetExtension.php new file mode 100644 index 0000000..ce9fee7 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/AssetExtension.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Asset\Packages; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * Twig extension for the Symfony Asset component. + * + * @author Fabien Potencier + */ +final class AssetExtension extends AbstractExtension +{ + public function __construct( + private Packages $packages, + ) { + } + + public function getFunctions(): array + { + return [ + new TwigFunction('asset', $this->getAssetUrl(...)), + new TwigFunction('asset_version', $this->getAssetVersion(...)), + ]; + } + + /** + * Returns the public url/path of an asset. + * + * If the package used to generate the path is an instance of + * UrlPackage, you will always get a URL and not a path. + */ + public function getAssetUrl(string $path, ?string $packageName = null): string + { + return $this->packages->getUrl($path, $packageName); + } + + /** + * Returns the version of an asset. + */ + public function getAssetVersion(string $path, ?string $packageName = null): string + { + return $this->packages->getVersion($path, $packageName); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/CsrfExtension.php b/vendor/symfony/twig-bridge/Extension/CsrfExtension.php new file mode 100644 index 0000000..951fc31 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/CsrfExtension.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * @author Christian Flothmann + * @author Titouan Galopin + */ +final class CsrfExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('csrf_token', [CsrfRuntime::class, 'getCsrfToken']), + ]; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/CsrfRuntime.php b/vendor/symfony/twig-bridge/Extension/CsrfRuntime.php new file mode 100644 index 0000000..2926711 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/CsrfRuntime.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + +/** + * @author Christian Flothmann + * @author Titouan Galopin + */ +final class CsrfRuntime +{ + public function __construct( + private CsrfTokenManagerInterface $csrfTokenManager, + ) { + } + + public function getCsrfToken(string $tokenId): string + { + return $this->csrfTokenManager->getToken($tokenId)->getValue(); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/DumpExtension.php b/vendor/symfony/twig-bridge/Extension/DumpExtension.php new file mode 100644 index 0000000..a900616 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/DumpExtension.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\TokenParser\DumpTokenParser; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Twig\Environment; +use Twig\Extension\AbstractExtension; +use Twig\Template; +use Twig\TwigFunction; + +/** + * Provides integration of the dump() function with Twig. + * + * @author Nicolas Grekas + */ +final class DumpExtension extends AbstractExtension +{ + public function __construct( + private ClonerInterface $cloner, + private ?HtmlDumper $dumper = null, + ) { + } + + public function getFunctions(): array + { + return [ + new TwigFunction('dump', $this->dump(...), ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]), + ]; + } + + public function getTokenParsers(): array + { + return [new DumpTokenParser()]; + } + + public function dump(Environment $env, array $context): ?string + { + if (!$env->isDebug()) { + return null; + } + + if (2 === \func_num_args()) { + $vars = []; + foreach ($context as $key => $value) { + if (!$value instanceof Template) { + $vars[$key] = $value; + } + } + + $vars = [$vars]; + } else { + $vars = \func_get_args(); + unset($vars[0], $vars[1]); + } + + $dump = fopen('php://memory', 'r+'); + $this->dumper ??= new HtmlDumper(); + $this->dumper->setCharset($env->getCharset()); + + foreach ($vars as $value) { + $this->dumper->dump($this->cloner->cloneVar($value), $dump); + } + + return stream_get_contents($dump, -1, 0); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/EmojiExtension.php b/vendor/symfony/twig-bridge/Extension/EmojiExtension.php new file mode 100644 index 0000000..b98798d --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/EmojiExtension.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Emoji\EmojiTransliterator; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Grégoire Pineau + */ +final class EmojiExtension extends AbstractExtension +{ + private static array $transliterators = []; + + public function __construct( + private readonly string $defaultCatalog = 'text', + ) { + if (!class_exists(EmojiTransliterator::class)) { + throw new \LogicException('You cannot use the "emojify" filter as the "Emoji" component is not installed. Try running "composer require symfony/emoji".'); + } + } + + public function getFilters(): array + { + return [ + new TwigFilter('emojify', $this->emojify(...)), + ]; + } + + /** + * Converts emoji short code (:wave:) to real emoji (👋) + */ + public function emojify(string $string, ?string $catalog = null): string + { + $catalog ??= $this->defaultCatalog; + + try { + $tr = self::$transliterators[$catalog] ??= EmojiTransliterator::create($catalog, EmojiTransliterator::REVERSE); + } catch (\IntlException $e) { + throw new \LogicException(sprintf('The emoji catalog "%s" is not available.', $catalog), previous: $e); + } + + return (string) $tr->transliterate($string); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/ExpressionExtension.php b/vendor/symfony/twig-bridge/Extension/ExpressionExtension.php new file mode 100644 index 0000000..49e4c95 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/ExpressionExtension.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\ExpressionLanguage\Expression; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * ExpressionExtension gives a way to create Expressions from a template. + * + * @author Fabien Potencier + */ +final class ExpressionExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('expression', $this->createExpression(...)), + ]; + } + + public function createExpression(string $expression): Expression + { + return new Expression($expression); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/FormExtension.php b/vendor/symfony/twig-bridge/Extension/FormExtension.php new file mode 100644 index 0000000..0141541 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/FormExtension.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\Node\RenderBlockNode; +use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode; +use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; +use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; +use Symfony\Component\Form\ChoiceList\View\ChoiceView; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\FormView; +use Symfony\Contracts\Translation\TranslatorInterface; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; + +/** + * FormExtension extends Twig with form capabilities. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +final class FormExtension extends AbstractExtension +{ + public function __construct( + private ?TranslatorInterface $translator = null, + ) { + } + + public function getTokenParsers(): array + { + return [ + // {% form_theme form "SomeBundle::widgets.twig" %} + new FormThemeTokenParser(), + ]; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('form_widget', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_errors', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_label', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_help', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_row', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_rest', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_start', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_end', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('csrf_token', [FormRenderer::class, 'renderCsrfToken']), + new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'), + new TwigFunction('field_name', $this->getFieldName(...)), + new TwigFunction('field_value', $this->getFieldValue(...)), + new TwigFunction('field_label', $this->getFieldLabel(...)), + new TwigFunction('field_help', $this->getFieldHelp(...)), + new TwigFunction('field_errors', $this->getFieldErrors(...)), + new TwigFunction('field_choices', $this->getFieldChoices(...)), + ]; + } + + public function getFilters(): array + { + return [ + new TwigFilter('humanize', [FormRenderer::class, 'humanize']), + new TwigFilter('form_encode_currency', [FormRenderer::class, 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]), + ]; + } + + public function getTests(): array + { + return [ + new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'), + new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'), + ]; + } + + public function getFieldName(FormView $view): string + { + $view->setRendered(); + + return $view->vars['full_name']; + } + + public function getFieldValue(FormView $view): string|array + { + return $view->vars['value']; + } + + public function getFieldLabel(FormView $view): ?string + { + if (false === $label = $view->vars['label']) { + return null; + } + + if (!$label && $labelFormat = $view->vars['label_format']) { + $label = str_replace(['%id%', '%name%'], [$view->vars['id'], $view->vars['name']], $labelFormat); + } elseif (!$label) { + $label = ucfirst(strtolower(trim(preg_replace(['/([A-Z])/', '/[_\s]+/'], ['_$1', ' '], $view->vars['name'])))); + } + + return $this->createFieldTranslation( + $label, + $view->vars['label_translation_parameters'] ?: [], + $view->vars['translation_domain'] + ); + } + + public function getFieldHelp(FormView $view): ?string + { + return $this->createFieldTranslation( + $view->vars['help'], + $view->vars['help_translation_parameters'] ?: [], + $view->vars['translation_domain'] + ); + } + + /** + * @return string[] + */ + public function getFieldErrors(FormView $view): iterable + { + /** @var FormError $error */ + foreach ($view->vars['errors'] as $error) { + yield $error->getMessage(); + } + } + + /** + * @return string[]|string[][] + */ + public function getFieldChoices(FormView $view): iterable + { + yield from $this->createFieldChoicesList($view->vars['choices'], $view->vars['choice_translation_domain']); + } + + private function createFieldChoicesList(iterable $choices, string|false|null $translationDomain): iterable + { + foreach ($choices as $choice) { + $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain); + + if ($choice instanceof ChoiceGroupView) { + yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain); + + continue; + } + + /* @var ChoiceView $choice */ + yield $translatableLabel => $choice->value; + } + } + + private function createFieldTranslation(?string $value, array $parameters, string|false|null $domain): ?string + { + if (!$this->translator || !$value || false === $domain) { + return $value; + } + + return $this->translator->trans($value, $parameters, $domain); + } +} + +/** + * Returns whether a choice is selected for a given form value. + * + * This is a function and not callable due to performance reasons. + * + * @see ChoiceView::isSelected() + */ +function twig_is_selected_choice(ChoiceView $choice, string|array|null $selectedValue): bool +{ + if (\is_array($selectedValue)) { + return \in_array($choice->value, $selectedValue, true); + } + + return $choice->value === $selectedValue; +} + +/** + * @internal + */ +function twig_is_root_form(FormView $formView): bool +{ + return null === $formView->parent; +} + +/** + * @internal + */ +function twig_get_form_parent(FormView $formView): ?FormView +{ + return $formView->parent; +} diff --git a/vendor/symfony/twig-bridge/Extension/HtmlSanitizerExtension.php b/vendor/symfony/twig-bridge/Extension/HtmlSanitizerExtension.php new file mode 100644 index 0000000..9549c2a --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/HtmlSanitizerExtension.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Psr\Container\ContainerInterface; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Titouan Galopin + */ +final class HtmlSanitizerExtension extends AbstractExtension +{ + public function __construct( + private ContainerInterface $sanitizers, + private string $defaultSanitizer = 'default', + ) { + } + + public function getFilters(): array + { + return [ + new TwigFilter('sanitize_html', $this->sanitize(...), ['is_safe' => ['html']]), + ]; + } + + public function sanitize(string $html, ?string $sanitizer = null): string + { + return $this->sanitizers->get($sanitizer ?? $this->defaultSanitizer)->sanitize($html); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/HttpFoundationExtension.php b/vendor/symfony/twig-bridge/Extension/HttpFoundationExtension.php new file mode 100644 index 0000000..e06f1b3 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/HttpFoundationExtension.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\UrlHelper; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * Twig extension for the Symfony HttpFoundation component. + * + * @author Fabien Potencier + */ +final class HttpFoundationExtension extends AbstractExtension +{ + public function __construct( + private UrlHelper $urlHelper, + ) { + } + + public function getFunctions(): array + { + return [ + new TwigFunction('absolute_url', $this->generateAbsoluteUrl(...)), + new TwigFunction('relative_path', $this->generateRelativePath(...)), + ]; + } + + /** + * Returns the absolute URL for the given absolute or relative path. + * + * This method returns the path unchanged if no request is available. + * + * @see Request::getUriForPath() + */ + public function generateAbsoluteUrl(string $path): string + { + return $this->urlHelper->getAbsoluteUrl($path); + } + + /** + * Returns a relative path based on the current Request. + * + * This method returns the path unchanged if no request is available. + * + * @see Request::getRelativeUriForPath() + */ + public function generateRelativePath(string $path): string + { + return $this->urlHelper->getRelativePath($path); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/HttpKernelExtension.php b/vendor/symfony/twig-bridge/Extension/HttpKernelExtension.php new file mode 100644 index 0000000..4859f4d --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/HttpKernelExtension.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * Provides integration with the HttpKernel component. + * + * @author Fabien Potencier + */ +final class HttpKernelExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]), + new TwigFunction('render_*', [HttpKernelRuntime::class, 'renderFragmentStrategy'], ['is_safe' => ['html']]), + new TwigFunction('fragment_uri', [HttpKernelRuntime::class, 'generateFragmentUri']), + new TwigFunction('controller', [self::class, 'controller']), + ]; + } + + public static function controller(string $controller, array $attributes = [], array $query = []): ControllerReference + { + return new ControllerReference($controller, $attributes, $query); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/HttpKernelRuntime.php b/vendor/symfony/twig-bridge/Extension/HttpKernelRuntime.php new file mode 100644 index 0000000..0aefed8 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/HttpKernelRuntime.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; + +/** + * Provides integration with the HttpKernel component. + * + * @author Fabien Potencier + */ +final class HttpKernelRuntime +{ + public function __construct( + private FragmentHandler $handler, + private ?FragmentUriGeneratorInterface $fragmentUriGenerator = null, + ) { + } + + /** + * Renders a fragment. + * + * @see FragmentHandler::render() + */ + public function renderFragment(string|ControllerReference $uri, array $options = []): string + { + $strategy = $options['strategy'] ?? 'inline'; + unset($options['strategy']); + + return $this->handler->render($uri, $strategy, $options); + } + + /** + * Renders a fragment. + * + * @see FragmentHandler::render() + */ + public function renderFragmentStrategy(string $strategy, string|ControllerReference $uri, array $options = []): string + { + return $this->handler->render($uri, $strategy, $options); + } + + public function generateFragmentUri(ControllerReference $controller, bool $absolute = false, bool $strict = true, bool $sign = true): string + { + if (null === $this->fragmentUriGenerator) { + throw new \LogicException(sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__)); + } + + return $this->fragmentUriGenerator->generate($controller, null, $absolute, $strict, $sign); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/ImportMapExtension.php b/vendor/symfony/twig-bridge/Extension/ImportMapExtension.php new file mode 100644 index 0000000..2156c74 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/ImportMapExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * @author Kévin Dunglas + */ +final class ImportMapExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('importmap', [ImportMapRuntime::class, 'importmap'], ['is_safe' => ['html']]), + ]; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/ImportMapRuntime.php b/vendor/symfony/twig-bridge/Extension/ImportMapRuntime.php new file mode 100644 index 0000000..902e0a4 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/ImportMapRuntime.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; + +/** + * @author Kévin Dunglas + */ +class ImportMapRuntime +{ + public function __construct( + private readonly ImportMapRenderer $importMapRenderer, + ) { + } + + public function importmap(string|array $entryPoint = 'app', array $attributes = []): string + { + return $this->importMapRenderer->render($entryPoint, $attributes); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/LogoutUrlExtension.php b/vendor/symfony/twig-bridge/Extension/LogoutUrlExtension.php new file mode 100644 index 0000000..15089d3 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/LogoutUrlExtension.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * LogoutUrlHelper provides generator functions for the logout URL to Twig. + * + * @author Jeremy Mikola + */ +final class LogoutUrlExtension extends AbstractExtension +{ + public function __construct( + private LogoutUrlGenerator $generator, + ) { + } + + public function getFunctions(): array + { + return [ + new TwigFunction('logout_url', $this->getLogoutUrl(...)), + new TwigFunction('logout_path', $this->getLogoutPath(...)), + ]; + } + + /** + * Generates the relative logout URL for the firewall. + * + * @param string|null $key The firewall key or null to use the current firewall key + */ + public function getLogoutPath(?string $key = null): string + { + return $this->generator->getLogoutPath($key); + } + + /** + * Generates the absolute logout URL for the firewall. + * + * @param string|null $key The firewall key or null to use the current firewall key + */ + public function getLogoutUrl(?string $key = null): string + { + return $this->generator->getLogoutUrl($key); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/ProfilerExtension.php b/vendor/symfony/twig-bridge/Extension/ProfilerExtension.php new file mode 100644 index 0000000..2dbc4ec --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/ProfilerExtension.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Stopwatch\StopwatchEvent; +use Twig\Extension\ProfilerExtension as BaseProfilerExtension; +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier + */ +final class ProfilerExtension extends BaseProfilerExtension +{ + /** + * @var \SplObjectStorage + */ + private \SplObjectStorage $events; + + public function __construct( + Profile $profile, + private ?Stopwatch $stopwatch = null, + ) { + parent::__construct($profile); + + $this->events = new \SplObjectStorage(); + } + + public function enter(Profile $profile): void + { + if ($this->stopwatch && $profile->isTemplate()) { + $this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template'); + } + + parent::enter($profile); + } + + public function leave(Profile $profile): void + { + parent::leave($profile); + + if ($this->stopwatch && $profile->isTemplate()) { + $this->events[$profile]->stop(); + unset($this->events[$profile]); + } + } +} diff --git a/vendor/symfony/twig-bridge/Extension/RoutingExtension.php b/vendor/symfony/twig-bridge/Extension/RoutingExtension.php new file mode 100644 index 0000000..eace523 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/RoutingExtension.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Twig\Extension\AbstractExtension; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; +use Twig\TwigFunction; + +/** + * Provides integration of the Routing component with Twig. + * + * @author Fabien Potencier + */ +final class RoutingExtension extends AbstractExtension +{ + public function __construct( + private UrlGeneratorInterface $generator, + ) { + } + + public function getFunctions(): array + { + return [ + new TwigFunction('url', $this->getUrl(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]), + new TwigFunction('path', $this->getPath(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]), + ]; + } + + public function getPath(string $name, array $parameters = [], bool $relative = false): string + { + return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function getUrl(string $name, array $parameters = [], bool $schemeRelative = false): string + { + return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * Determines at compile time whether the generated URL will be safe and thus + * saving the unneeded automatic escaping for performance reasons. + * + * The URL generation process percent encodes non-alphanumeric characters. So there is no risk + * that malicious/invalid characters are part of the URL. The only character within a URL that + * must be escaped in html is the ampersand ("&") which separates query params. So we cannot mark + * the URL generation as always safe, but only when we are sure there won't be multiple query + * params. This is the case when there are none or only one constant parameter given. + * E.g. we know beforehand this will be safe: + * - path('route') + * - path('route', {'param': 'value'}) + * But the following may not: + * - path('route', var) + * - path('route', {'param': ['val1', 'val2'] }) // a sub-array + * - path('route', {'param1': 'value1', 'param2': 'value2'}) + * If param1 and param2 reference placeholder in the route, it would still be safe. But we don't know. + * + * @param Node $argsNode The arguments of the path/url function + * + * @return array An array with the contexts the URL is safe + */ + public function isUrlGenerationSafe(Node $argsNode): array + { + // support named arguments + $paramsNode = $argsNode->hasNode('parameters') ? $argsNode->getNode('parameters') : ( + $argsNode->hasNode(1) ? $argsNode->getNode(1) : null + ); + + if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 + && (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) + ) { + return ['html']; + } + + return []; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/SecurityExtension.php b/vendor/symfony/twig-bridge/Extension/SecurityExtension.php new file mode 100644 index 0000000..863df15 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/SecurityExtension.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Security\Acl\Voter\FieldVote; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * SecurityExtension exposes security context features. + * + * @author Fabien Potencier + */ +final class SecurityExtension extends AbstractExtension +{ + public function __construct( + private ?AuthorizationCheckerInterface $securityChecker = null, + private ?ImpersonateUrlGenerator $impersonateUrlGenerator = null, + ) { + } + + public function isGranted(mixed $role, mixed $object = null, ?string $field = null): bool + { + if (null === $this->securityChecker) { + return false; + } + + if (null !== $field) { + $object = new FieldVote($object, $field); + } + + try { + return $this->securityChecker->isGranted($role, $object); + } catch (AuthenticationCredentialsNotFoundException) { + return false; + } + } + + public function getImpersonateExitUrl(?string $exitTo = null): string + { + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateExitUrl($exitTo); + } + + public function getImpersonateExitPath(?string $exitTo = null): string + { + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateExitPath($exitTo); + } + + public function getImpersonateUrl(string $identifier): string + { + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateImpersonationUrl($identifier); + } + + public function getImpersonatePath(string $identifier): string + { + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateImpersonationPath($identifier); + } + + public function getFunctions(): array + { + return [ + new TwigFunction('is_granted', $this->isGranted(...)), + new TwigFunction('impersonation_exit_url', $this->getImpersonateExitUrl(...)), + new TwigFunction('impersonation_exit_path', $this->getImpersonateExitPath(...)), + new TwigFunction('impersonation_url', $this->getImpersonateUrl(...)), + new TwigFunction('impersonation_path', $this->getImpersonatePath(...)), + ]; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/SerializerExtension.php b/vendor/symfony/twig-bridge/Extension/SerializerExtension.php new file mode 100644 index 0000000..f38571e --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/SerializerExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Jesse Rushlow + */ +final class SerializerExtension extends AbstractExtension +{ + public function getFilters(): array + { + return [ + new TwigFilter('serialize', [SerializerRuntime::class, 'serialize']), + ]; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/SerializerRuntime.php b/vendor/symfony/twig-bridge/Extension/SerializerRuntime.php new file mode 100644 index 0000000..2271573 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/SerializerRuntime.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Serializer\SerializerInterface; +use Twig\Extension\RuntimeExtensionInterface; + +/** + * @author Jesse Rushlow + */ +final class SerializerRuntime implements RuntimeExtensionInterface +{ + public function __construct( + private SerializerInterface $serializer, + ) { + } + + public function serialize(mixed $data, string $format = 'json', array $context = []): string + { + return $this->serializer->serialize($data, $format, $context); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/StopwatchExtension.php b/vendor/symfony/twig-bridge/Extension/StopwatchExtension.php new file mode 100644 index 0000000..ba56d12 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/StopwatchExtension.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser; +use Symfony\Component\Stopwatch\Stopwatch; +use Twig\Extension\AbstractExtension; +use Twig\TokenParser\TokenParserInterface; + +/** + * Twig extension for the stopwatch helper. + * + * @author Wouter J + */ +final class StopwatchExtension extends AbstractExtension +{ + public function __construct( + private ?Stopwatch $stopwatch = null, + private bool $enabled = true, + ) { + } + + public function getStopwatch(): Stopwatch + { + return $this->stopwatch; + } + + /** + * @return TokenParserInterface[] + */ + public function getTokenParsers(): array + { + return [ + /* + * {% stopwatch foo %} + * Some stuff which will be recorded on the timeline + * {% endstopwatch %} + */ + new StopwatchTokenParser(null !== $this->stopwatch && $this->enabled), + ]; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/TranslationExtension.php b/vendor/symfony/twig-bridge/Extension/TranslationExtension.php new file mode 100644 index 0000000..bf8b81b --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/TranslationExtension.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; +use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; +use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser; +use Symfony\Bridge\Twig\TokenParser\TransTokenParser; +use Symfony\Component\Translation\TranslatableMessage; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; +use Twig\TwigFunction; + +// Help opcache.preload discover always-needed symbols +class_exists(TranslatorInterface::class); +class_exists(TranslatorTrait::class); + +/** + * Provides integration of the Translation component with Twig. + * + * @author Fabien Potencier + */ +final class TranslationExtension extends AbstractExtension +{ + public function __construct( + private ?TranslatorInterface $translator = null, + private ?TranslationNodeVisitor $translationNodeVisitor = null, + ) { + } + + public function getTranslator(): TranslatorInterface + { + if (null === $this->translator) { + if (!interface_exists(TranslatorInterface::class)) { + throw new \LogicException(sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); + } + + $this->translator = new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + + return $this->translator; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('t', $this->createTranslatable(...)), + ]; + } + + public function getFilters(): array + { + return [ + new TwigFilter('trans', $this->trans(...)), + ]; + } + + public function getTokenParsers(): array + { + return [ + // {% trans %}Symfony is great!{% endtrans %} + new TransTokenParser(), + + // {% trans_default_domain "foobar" %} + new TransDefaultDomainTokenParser(), + ]; + } + + public function getNodeVisitors(): array + { + return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()]; + } + + public function getTranslationNodeVisitor(): TranslationNodeVisitor + { + return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor(); + } + + /** + * @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface + */ + public function trans(string|\Stringable|TranslatableInterface|null $message, array|string $arguments = [], ?string $domain = null, ?string $locale = null, ?int $count = null): string + { + if ($message instanceof TranslatableInterface) { + if ([] !== $arguments && !\is_string($arguments)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a locale passed as a string when the message is a "%s", "%s" given.', __METHOD__, TranslatableInterface::class, get_debug_type($arguments))); + } + + if ($message instanceof TranslatableMessage && '' === $message->getMessage()) { + return ''; + } + + return $message->trans($this->getTranslator(), $locale ?? (\is_string($arguments) ? $arguments : null)); + } + + if (!\is_array($arguments)) { + throw new \TypeError(sprintf('Unless the message is a "%s", argument 2 passed to "%s()" must be an array of parameters, "%s" given.', TranslatableInterface::class, __METHOD__, get_debug_type($arguments))); + } + + if ('' === $message = (string) $message) { + return ''; + } + + if (null !== $count) { + $arguments['%count%'] = $count; + } + + return $this->getTranslator()->trans($message, $arguments, $domain, $locale); + } + + public function createTranslatable(string $message, array $parameters = [], ?string $domain = null): TranslatableMessage + { + if (!class_exists(TranslatableMessage::class)) { + throw new \LogicException(sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__)); + } + + return new TranslatableMessage($message, $parameters, $domain); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/WebLinkExtension.php b/vendor/symfony/twig-bridge/Extension/WebLinkExtension.php new file mode 100644 index 0000000..9eeb305 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/WebLinkExtension.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\Link; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * Twig extension for the Symfony WebLink component. + * + * @author Kévin Dunglas + */ +final class WebLinkExtension extends AbstractExtension +{ + public function __construct( + private RequestStack $requestStack, + ) { + } + + public function getFunctions(): array + { + return [ + new TwigFunction('link', $this->link(...)), + new TwigFunction('preload', $this->preload(...)), + new TwigFunction('dns_prefetch', $this->dnsPrefetch(...)), + new TwigFunction('preconnect', $this->preconnect(...)), + new TwigFunction('prefetch', $this->prefetch(...)), + new TwigFunction('prerender', $this->prerender(...)), + ]; + } + + /** + * Adds a "Link" HTTP header. + * + * @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * + * @return string The relation URI + */ + public function link(string $uri, string $rel, array $attributes = []): string + { + if (!$request = $this->requestStack->getMainRequest()) { + return $uri; + } + + $link = new Link($rel, $uri); + foreach ($attributes as $key => $value) { + $link = $link->withAttribute($key, $value); + } + + $linkProvider = $request->attributes->get('_links', new GenericLinkProvider()); + $request->attributes->set('_links', $linkProvider->withLink($link)); + + return $uri; + } + + /** + * Preloads a resource. + * + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']") + * + * @return string The path of the asset + */ + public function preload(string $uri, array $attributes = []): string + { + return $this->link($uri, 'preload', $attributes); + } + + /** + * Resolves a resource origin as early as possible. + * + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * + * @return string The path of the asset + */ + public function dnsPrefetch(string $uri, array $attributes = []): string + { + return $this->link($uri, 'dns-prefetch', $attributes); + } + + /** + * Initiates a early connection to a resource (DNS resolution, TCP handshake, TLS negotiation). + * + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * + * @return string The path of the asset + */ + public function preconnect(string $uri, array $attributes = []): string + { + return $this->link($uri, 'preconnect', $attributes); + } + + /** + * Indicates to the client that it should prefetch this resource. + * + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * + * @return string The path of the asset + */ + public function prefetch(string $uri, array $attributes = []): string + { + return $this->link($uri, 'prefetch', $attributes); + } + + /** + * Indicates to the client that it should prerender this resource . + * + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * + * @return string The path of the asset + */ + public function prerender(string $uri, array $attributes = []): string + { + return $this->link($uri, 'prerender', $attributes); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/WorkflowExtension.php b/vendor/symfony/twig-bridge/Extension/WorkflowExtension.php new file mode 100644 index 0000000..0fcc9b3 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/WorkflowExtension.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Workflow\Registry; +use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\TransitionBlockerList; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * WorkflowExtension. + * + * @author Grégoire Pineau + * @author Carlos Pereira De Amorim + */ +final class WorkflowExtension extends AbstractExtension +{ + public function __construct( + private Registry $workflowRegistry, + ) { + } + + public function getFunctions(): array + { + return [ + new TwigFunction('workflow_can', $this->canTransition(...)), + new TwigFunction('workflow_transitions', $this->getEnabledTransitions(...)), + new TwigFunction('workflow_transition', $this->getEnabledTransition(...)), + new TwigFunction('workflow_has_marked_place', $this->hasMarkedPlace(...)), + new TwigFunction('workflow_marked_places', $this->getMarkedPlaces(...)), + new TwigFunction('workflow_metadata', $this->getMetadata(...)), + new TwigFunction('workflow_transition_blockers', $this->buildTransitionBlockerList(...)), + ]; + } + + /** + * Returns true if the transition is enabled. + */ + public function canTransition(object $subject, string $transitionName, ?string $name = null): bool + { + return $this->workflowRegistry->get($subject, $name)->can($subject, $transitionName); + } + + /** + * Returns all enabled transitions. + * + * @return Transition[] + */ + public function getEnabledTransitions(object $subject, ?string $name = null): array + { + return $this->workflowRegistry->get($subject, $name)->getEnabledTransitions($subject); + } + + public function getEnabledTransition(object $subject, string $transition, ?string $name = null): ?Transition + { + return $this->workflowRegistry->get($subject, $name)->getEnabledTransition($subject, $transition); + } + + /** + * Returns true if the place is marked. + */ + public function hasMarkedPlace(object $subject, string $placeName, ?string $name = null): bool + { + return $this->workflowRegistry->get($subject, $name)->getMarking($subject)->has($placeName); + } + + /** + * Returns marked places. + * + * @return string[]|int[] + */ + public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, ?string $name = null): array + { + $places = $this->workflowRegistry->get($subject, $name)->getMarking($subject)->getPlaces(); + + if ($placesNameOnly) { + return array_keys($places); + } + + return $places; + } + + /** + * Returns the metadata for a specific subject. + * + * @param string|Transition|null $metadataSubject Use null to get workflow metadata + * Use a string (the place name) to get place metadata + * Use a Transition instance to get transition metadata + */ + public function getMetadata(object $subject, string $key, string|Transition|null $metadataSubject = null, ?string $name = null): mixed + { + return $this + ->workflowRegistry + ->get($subject, $name) + ->getMetadataStore() + ->getMetadata($key, $metadataSubject) + ; + } + + public function buildTransitionBlockerList(object $subject, string $transitionName, ?string $name = null): TransitionBlockerList + { + $workflow = $this->workflowRegistry->get($subject, $name); + + return $workflow->buildTransitionBlockerList($subject, $transitionName); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/YamlExtension.php b/vendor/symfony/twig-bridge/Extension/YamlExtension.php new file mode 100644 index 0000000..cbfcc32 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/YamlExtension.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Yaml\Dumper as YamlDumper; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * Provides integration of the Yaml component with Twig. + * + * @author Fabien Potencier + */ +final class YamlExtension extends AbstractExtension +{ + public function getFilters(): array + { + return [ + new TwigFilter('yaml_encode', $this->encode(...)), + new TwigFilter('yaml_dump', $this->dump(...)), + ]; + } + + public function encode(mixed $input, int $inline = 0, int $dumpObjects = 0): string + { + static $dumper; + + $dumper ??= new YamlDumper(); + + if (\defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { + return $dumper->dump($input, $inline, 0, $dumpObjects); + } + + return $dumper->dump($input, $inline, 0, false, $dumpObjects); + } + + public function dump(mixed $value, int $inline = 0, int $dumpObjects = 0): string + { + if (\is_resource($value)) { + return '%Resource%'; + } + + if (\is_array($value) || \is_object($value)) { + return '%'.\gettype($value).'% '.$this->encode($value, $inline, $dumpObjects); + } + + return $this->encode($value, $inline, $dumpObjects); + } +} diff --git a/vendor/symfony/twig-bridge/Form/TwigRendererEngine.php b/vendor/symfony/twig-bridge/Form/TwigRendererEngine.php new file mode 100644 index 0000000..ff5568e --- /dev/null +++ b/vendor/symfony/twig-bridge/Form/TwigRendererEngine.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\AbstractRendererEngine; +use Symfony\Component\Form\FormView; +use Twig\Environment; +use Twig\Template; + +/** + * @author Bernhard Schussek + */ +class TwigRendererEngine extends AbstractRendererEngine +{ + private Template $template; + + public function __construct( + array $defaultThemes, + private Environment $environment, + ) { + parent::__construct($defaultThemes); + } + + public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string + { + $cacheKey = $view->vars[self::CACHE_KEY_VAR]; + + $context = $this->environment->mergeGlobals($variables); + + ob_start(); + + // By contract,This method can only be called after getting the resource + // (which is passed to the method). Getting a resource for the first time + // (with an empty cache) is guaranteed to invoke loadResourcesFromTheme(), + // where the property $template is initialized. + + // We do not call renderBlock here to avoid too many nested level calls + // (XDebug limits the level to 100 by default) + $this->template->displayBlock($blockName, $context, $this->resources[$cacheKey]); + + return ob_get_clean(); + } + + /** + * Loads the cache with the resource for a given block name. + * + * This implementation eagerly loads all blocks of the themes assigned to the given view + * and all of its ancestors views. This is necessary, because Twig receives the + * list of blocks later. At that point, all blocks must already be loaded, for the + * case that the function "block()" is used in the Twig template. + * + * @see getResourceForBlock() + */ + protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool + { + // The caller guarantees that $this->resources[$cacheKey][$block] is + // not set, but it doesn't have to check whether $this->resources[$cacheKey] + // is set. If $this->resources[$cacheKey] is set, all themes for this + // $cacheKey are already loaded (due to the eager population, see doc comment). + if (isset($this->resources[$cacheKey])) { + // As said in the previous, the caller guarantees that + // $this->resources[$cacheKey][$block] is not set. Since the themes are + // already loaded, it can only be a non-existing block. + $this->resources[$cacheKey][$blockName] = false; + + return false; + } + + // Recursively try to find the block in the themes assigned to $view, + // then of its parent view, then of the parent view of the parent and so on. + // When the root view is reached in this recursion, also the default + // themes are taken into account. + + // Check each theme whether it contains the searched block + if (isset($this->themes[$cacheKey])) { + for ($i = \count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) { + $this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]); + // CONTINUE LOADING (see doc comment) + } + } + + // Check the default themes once we reach the root view without success + if (!$view->parent) { + if (!isset($this->useDefaultThemes[$cacheKey]) || $this->useDefaultThemes[$cacheKey]) { + for ($i = \count($this->defaultThemes) - 1; $i >= 0; --$i) { + $this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]); + // CONTINUE LOADING (see doc comment) + } + } + } + + // Proceed with the themes of the parent view + if ($view->parent) { + $parentCacheKey = $view->parent->vars[self::CACHE_KEY_VAR]; + + if (!isset($this->resources[$parentCacheKey])) { + $this->loadResourceForBlockName($parentCacheKey, $view->parent, $blockName); + } + + // EAGER CACHE POPULATION (see doc comment) + foreach ($this->resources[$parentCacheKey] as $nestedBlockName => $resource) { + if (!isset($this->resources[$cacheKey][$nestedBlockName])) { + $this->resources[$cacheKey][$nestedBlockName] = $resource; + } + } + } + + // Even though we loaded the themes, it can happen that none of them + // contains the searched block + if (!isset($this->resources[$cacheKey][$blockName])) { + // Cache that we didn't find anything to speed up further accesses + $this->resources[$cacheKey][$blockName] = false; + } + + return false !== $this->resources[$cacheKey][$blockName]; + } + + /** + * Loads the resources for all blocks in a theme. + * + * @param mixed $theme The theme to load the block from. This parameter + * is passed by reference, because it might be necessary + * to initialize the theme first. Any changes made to + * this variable will be kept and be available upon + * further calls to this method using the same theme. + */ + protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme): void + { + if (!$theme instanceof Template) { + $theme = $this->environment->load($theme)->unwrap(); + } + + // Store the first Template instance that we find so that + // we can call displayBlock() later on. It doesn't matter *which* + // template we use for that, since we pass the used blocks manually + // anyway. + $this->template ??= $theme; + + // Use a separate variable for the inheritance traversal, because + // theme is a reference and we don't want to change it. + $currentTheme = $theme; + + $context = $this->environment->mergeGlobals([]); + + // The do loop takes care of template inheritance. + // Add blocks from all templates in the inheritance tree, but avoid + // overriding blocks already set. + do { + foreach ($currentTheme->getBlocks() as $block => $blockData) { + if (!isset($this->resources[$cacheKey][$block])) { + // The resource given back is the key to the bucket that + // contains this block. + $this->resources[$cacheKey][$block] = $blockData; + } + } + } while (false !== $currentTheme = $currentTheme->getParent($context)); + } +} diff --git a/vendor/symfony/twig-bridge/LICENSE b/vendor/symfony/twig-bridge/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/twig-bridge/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/twig-bridge/Mime/BodyRenderer.php b/vendor/symfony/twig-bridge/Mime/BodyRenderer.php new file mode 100644 index 0000000..25d8735 --- /dev/null +++ b/vendor/symfony/twig-bridge/Mime/BodyRenderer.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Mime; + +use League\HTMLToMarkdown\HtmlConverterInterface; +use Symfony\Component\Mime\BodyRendererInterface; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter; +use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; +use Symfony\Component\Mime\HtmlToTextConverter\LeagueHtmlToMarkdownConverter; +use Symfony\Component\Mime\Message; +use Symfony\Component\Translation\LocaleSwitcher; +use Twig\Environment; + +/** + * @author Fabien Potencier + */ +final class BodyRenderer implements BodyRendererInterface +{ + private HtmlToTextConverterInterface $converter; + + public function __construct( + private Environment $twig, + private array $context = [], + ?HtmlToTextConverterInterface $converter = null, + private ?LocaleSwitcher $localeSwitcher = null, + ) { + $this->converter = $converter ?: (interface_exists(HtmlConverterInterface::class) ? new LeagueHtmlToMarkdownConverter() : new DefaultHtmlToTextConverter()); + } + + public function render(Message $message): void + { + if (!$message instanceof TemplatedEmail) { + return; + } + + if (null === $message->getTextTemplate() && null === $message->getHtmlTemplate()) { + // email has already been rendered + return; + } + + $callback = function () use ($message) { + $messageContext = $message->getContext(); + + if (isset($messageContext['email'])) { + throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); + } + + $vars = array_merge($this->context, $messageContext, [ + 'email' => new WrappedTemplatedEmail($this->twig, $message), + ]); + + if ($template = $message->getTextTemplate()) { + $message->text($this->twig->render($template, $vars)); + } + + if ($template = $message->getHtmlTemplate()) { + $message->html($this->twig->render($template, $vars)); + } + + $message->markAsRendered(); + + // if text body is empty, compute one from the HTML body + if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { + $text = $this->converter->convert(\is_resource($html) ? stream_get_contents($html) : $html, $message->getHtmlCharset()); + $message->text($text, $message->getHtmlCharset()); + } + }; + + $locale = $message->getLocale(); + + if ($locale && $this->localeSwitcher) { + $this->localeSwitcher->runWithLocale($locale, $callback); + + return; + } + + $callback(); + } +} diff --git a/vendor/symfony/twig-bridge/Mime/NotificationEmail.php b/vendor/symfony/twig-bridge/Mime/NotificationEmail.php new file mode 100644 index 0000000..6e33d33 --- /dev/null +++ b/vendor/symfony/twig-bridge/Mime/NotificationEmail.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Mime; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; +use Twig\Extra\CssInliner\CssInlinerExtension; +use Twig\Extra\Inky\InkyExtension; +use Twig\Extra\Markdown\MarkdownExtension; + +/** + * @author Fabien Potencier + */ +class NotificationEmail extends TemplatedEmail +{ + public const IMPORTANCE_URGENT = 'urgent'; + public const IMPORTANCE_HIGH = 'high'; + public const IMPORTANCE_MEDIUM = 'medium'; + public const IMPORTANCE_LOW = 'low'; + + private string $theme = 'default'; + private array $context = [ + 'importance' => self::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => null, + 'action_url' => null, + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification email sent by Symfony', + ]; + private bool $rendered = false; + + public function __construct(?Headers $headers = null, ?AbstractPart $body = null) + { + $missingPackages = []; + if (!class_exists(CssInlinerExtension::class)) { + $missingPackages['twig/cssinliner-extra'] = 'CSS Inliner'; + } + + if (!class_exists(InkyExtension::class)) { + $missingPackages['twig/inky-extra'] = 'Inky'; + } + + if ($missingPackages) { + throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); + } + + parent::__construct($headers, $body); + } + + /** + * Creates a NotificationEmail instance that is appropriate to send to normal (non-admin) users. + */ + public static function asPublicEmail(?Headers $headers = null, ?AbstractPart $body = null): self + { + $email = new static($headers, $body); + $email->markAsPublic(); + + return $email; + } + + /** + * @return $this + */ + public function markAsPublic(): static + { + $this->context['importance'] = null; + $this->context['footer_text'] = null; + + return $this; + } + + /** + * @return $this + */ + public function markdown(string $content): static + { + if (!class_exists(MarkdownExtension::class)) { + throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__)); + } + + $this->context['markdown'] = true; + + return $this->content($content); + } + + /** + * @return $this + */ + public function content(string $content, bool $raw = false): static + { + $this->context['content'] = $content; + $this->context['raw'] = $raw; + + return $this; + } + + /** + * @return $this + */ + public function action(string $text, string $url): static + { + $this->context['action_text'] = $text; + $this->context['action_url'] = $url; + + return $this; + } + + /** + * @return $this + */ + public function importance(string $importance): static + { + $this->context['importance'] = $importance; + + return $this; + } + + /** + * @return $this + */ + public function exception(\Throwable|FlattenException $exception): static + { + $exceptionAsString = $this->getExceptionAsString($exception); + + $this->context['exception'] = true; + $this->addPart(new DataPart($exceptionAsString, 'exception.txt', 'text/plain')); + $this->importance(self::IMPORTANCE_URGENT); + + if (!$this->getSubject()) { + $this->subject($exception->getMessage()); + } + + return $this; + } + + /** + * @return $this + */ + public function theme(string $theme): static + { + $this->theme = $theme; + + return $this; + } + + public function getTextTemplate(): ?string + { + if ($template = parent::getTextTemplate()) { + return $template; + } + + return '@email/'.$this->theme.'/notification/body.txt.twig'; + } + + public function getHtmlTemplate(): ?string + { + if ($template = parent::getHtmlTemplate()) { + return $template; + } + + return '@email/'.$this->theme.'/notification/body.html.twig'; + } + + /** + * @return $this + */ + public function context(array $context): static + { + $parentContext = []; + + foreach ($context as $key => $value) { + if (\array_key_exists($key, $this->context)) { + $this->context[$key] = $value; + } else { + $parentContext[$key] = $value; + } + } + + parent::context($parentContext); + + return $this; + } + + public function getContext(): array + { + return array_merge($this->context, parent::getContext()); + } + + public function isRendered(): bool + { + return $this->rendered; + } + + public function markAsRendered(): void + { + parent::markAsRendered(); + + $this->rendered = true; + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + + $importance = $this->context['importance'] ?? self::IMPORTANCE_LOW; + $this->priority($this->determinePriority($importance)); + if ($this->context['importance']) { + $headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject())); + } + + return $headers; + } + + private function determinePriority(string $importance): int + { + return match ($importance) { + self::IMPORTANCE_URGENT => self::PRIORITY_HIGHEST, + self::IMPORTANCE_HIGH => self::PRIORITY_HIGH, + self::IMPORTANCE_MEDIUM => self::PRIORITY_NORMAL, + default => self::PRIORITY_LOW, + }; + } + + private function getExceptionAsString(\Throwable|FlattenException $exception): string + { + if (class_exists(FlattenException::class)) { + $exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception); + + return $exception->getAsString(); + } + + $message = $exception::class; + if ('' !== $exception->getMessage()) { + $message .= ': '.$exception->getMessage(); + } + + $message .= ' in '.$exception->getFile().':'.$exception->getLine()."\n"; + $message .= "Stack trace:\n".$exception->getTraceAsString()."\n\n"; + + return rtrim($message); + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->context, $this->theme, $this->rendered, parent::__serialize()]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + if (4 === \count($data)) { + [$this->context, $this->theme, $this->rendered, $parentData] = $data; + } elseif (3 === \count($data)) { + [$this->context, $this->theme, $parentData] = $data; + } else { + // Backwards compatibility for deserializing data structures that were serialized without the theme + [$this->context, $parentData] = $data; + } + + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/twig-bridge/Mime/TemplatedEmail.php b/vendor/symfony/twig-bridge/Mime/TemplatedEmail.php new file mode 100644 index 0000000..2d30894 --- /dev/null +++ b/vendor/symfony/twig-bridge/Mime/TemplatedEmail.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Mime; + +use Symfony\Component\Mime\Email; + +/** + * @author Fabien Potencier + */ +class TemplatedEmail extends Email +{ + private ?string $htmlTemplate = null; + private ?string $textTemplate = null; + private ?string $locale = null; + private array $context = []; + + /** + * @return $this + */ + public function textTemplate(?string $template): static + { + $this->textTemplate = $template; + + return $this; + } + + /** + * @return $this + */ + public function htmlTemplate(?string $template): static + { + $this->htmlTemplate = $template; + + return $this; + } + + /** + * @return $this + */ + public function locale(?string $locale): static + { + $this->locale = $locale; + + return $this; + } + + public function getTextTemplate(): ?string + { + return $this->textTemplate; + } + + public function getHtmlTemplate(): ?string + { + return $this->htmlTemplate; + } + + public function getLocale(): ?string + { + return $this->locale; + } + + /** + * @return $this + */ + public function context(array $context): static + { + $this->context = $context; + + return $this; + } + + public function getContext(): array + { + return $this->context; + } + + public function isRendered(): bool + { + return null === $this->htmlTemplate && null === $this->textTemplate; + } + + public function markAsRendered(): void + { + $this->textTemplate = null; + $this->htmlTemplate = null; + $this->context = []; + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->htmlTemplate, $this->textTemplate, $this->context, parent::__serialize(), $this->locale]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + [$this->htmlTemplate, $this->textTemplate, $this->context, $parentData] = $data; + $this->locale = $data[4] ?? null; + + parent::__unserialize($parentData); + } +} diff --git a/vendor/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php b/vendor/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php new file mode 100644 index 0000000..a327e94 --- /dev/null +++ b/vendor/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Mime; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\File; +use Twig\Environment; + +/** + * @internal + * + * @author Fabien Potencier + */ +final class WrappedTemplatedEmail +{ + public function __construct( + private Environment $twig, + private TemplatedEmail $message, + ) { + } + + public function toName(): string + { + return $this->message->getTo()[0]->getName(); + } + + /** + * @param string $image A Twig path to the image file. It's recommended to define + * some Twig namespace for email images (e.g. '@email/images/logo.png'). + * @param string|null $contentType The media type (i.e. MIME type) of the image file (e.g. 'image/png'). + * Some email clients require this to display embedded images. + */ + public function image(string $image, ?string $contentType = null): string + { + $file = $this->twig->getLoader()->getSourceContext($image); + $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); + $this->message->addPart((new DataPart($body, $image, $contentType))->asInline()); + + return 'cid:'.$image; + } + + /** + * @param string $file A Twig path to the file. It's recommended to define + * some Twig namespace for email files (e.g. '@email/files/contract.pdf'). + * @param string|null $name A custom file name that overrides the original name of the attached file + * @param string|null $contentType The media type (i.e. MIME type) of the file (e.g. 'application/pdf'). + * Some email clients require this to display attached files. + */ + public function attach(string $file, ?string $name = null, ?string $contentType = null): void + { + $file = $this->twig->getLoader()->getSourceContext($file); + $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); + $this->message->addPart(new DataPart($body, $name, $contentType)); + } + + /** + * @return $this + */ + public function setSubject(string $subject): static + { + $this->message->subject($subject); + + return $this; + } + + public function getSubject(): ?string + { + return $this->message->getSubject(); + } + + /** + * @return $this + */ + public function setReturnPath(string $address): static + { + $this->message->returnPath($address); + + return $this; + } + + public function getReturnPath(): string + { + return $this->message->getReturnPath(); + } + + /** + * @return $this + */ + public function addFrom(string $address, string $name = ''): static + { + $this->message->addFrom(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getFrom(): array + { + return $this->message->getFrom(); + } + + /** + * @return $this + */ + public function addReplyTo(string $address): static + { + $this->message->addReplyTo($address); + + return $this; + } + + /** + * @return Address[] + */ + public function getReplyTo(): array + { + return $this->message->getReplyTo(); + } + + /** + * @return $this + */ + public function addTo(string $address, string $name = ''): static + { + $this->message->addTo(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getTo(): array + { + return $this->message->getTo(); + } + + /** + * @return $this + */ + public function addCc(string $address, string $name = ''): static + { + $this->message->addCc(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getCc(): array + { + return $this->message->getCc(); + } + + /** + * @return $this + */ + public function addBcc(string $address, string $name = ''): static + { + $this->message->addBcc(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getBcc(): array + { + return $this->message->getBcc(); + } + + /** + * @return $this + */ + public function setPriority(int $priority): static + { + $this->message->priority($priority); + + return $this; + } + + public function getPriority(): int + { + return $this->message->getPriority(); + } +} diff --git a/vendor/symfony/twig-bridge/Node/DumpNode.php b/vendor/symfony/twig-bridge/Node/DumpNode.php new file mode 100644 index 0000000..f23313c --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/DumpNode.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Attribute\FirstClassTwigCallableReady; +use Twig\Attribute\YieldReady; +use Twig\Compiler; +use Twig\Node\Node; + +/** + * @author Julien Galenski + */ +#[YieldReady] +final class DumpNode extends Node +{ + public function __construct( + private string $varPrefix, + ?Node $values, + int $lineno, + ?string $tag = null, + ) { + $nodes = []; + if (null !== $values) { + $nodes['values'] = $values; + } + + if (class_exists(FirstClassTwigCallableReady::class)) { + parent::__construct($nodes, [], $lineno); + } else { + parent::__construct($nodes, [], $lineno, $tag); + } + + $this->varPrefix = $varPrefix; + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write("if (\$this->env->isDebug()) {\n") + ->indent(); + + if (!$this->hasNode('values')) { + // remove embedded templates (macros) from the context + $compiler + ->write(sprintf('$%svars = [];'."\n", $this->varPrefix)) + ->write(sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) + ->indent() + ->write(sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix)) + ->indent() + ->write(sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ->addDebugInfo($this) + ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); + } elseif (($values = $this->getNode('values')) && 1 === $values->count()) { + $compiler + ->addDebugInfo($this) + ->write('\Symfony\Component\VarDumper\VarDumper::dump(') + ->subcompile($values->getNode(0)) + ->raw(");\n"); + } else { + $compiler + ->addDebugInfo($this) + ->write('\Symfony\Component\VarDumper\VarDumper::dump(['."\n") + ->indent(); + foreach ($values as $node) { + $compiler->write(''); + if ($node->hasAttribute('name')) { + $compiler + ->string($node->getAttribute('name')) + ->raw(' => '); + } + $compiler + ->subcompile($node) + ->raw(",\n"); + } + $compiler + ->outdent() + ->write("]);\n"); + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/vendor/symfony/twig-bridge/Node/FormThemeNode.php b/vendor/symfony/twig-bridge/Node/FormThemeNode.php new file mode 100644 index 0000000..1d07709 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/FormThemeNode.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Symfony\Component\Form\FormRenderer; +use Twig\Attribute\FirstClassTwigCallableReady; +use Twig\Attribute\YieldReady; +use Twig\Compiler; +use Twig\Node\Node; + +/** + * @author Fabien Potencier + */ +#[YieldReady] +final class FormThemeNode extends Node +{ + public function __construct(Node $form, Node $resources, int $lineno, ?string $tag = null, bool $only = false) + { + if (class_exists(FirstClassTwigCallableReady::class)) { + parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno); + } else { + parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag); + } + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('$this->env->getRuntime(') + ->string(FormRenderer::class) + ->raw(')->setTheme(') + ->subcompile($this->getNode('form')) + ->raw(', ') + ->subcompile($this->getNode('resources')) + ->raw(', ') + ->raw(false === $this->getAttribute('only') ? 'true' : 'false') + ->raw(");\n"); + } +} diff --git a/vendor/symfony/twig-bridge/Node/RenderBlockNode.php b/vendor/symfony/twig-bridge/Node/RenderBlockNode.php new file mode 100644 index 0000000..4d4cf61 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/RenderBlockNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\FunctionExpression; + +/** + * Compiles a call to {@link \Symfony\Component\Form\FormRendererInterface::renderBlock()}. + * + * The function name is used as block name. For example, if the function name + * is "foo", the block "foo" will be rendered. + * + * @author Bernhard Schussek + */ +final class RenderBlockNode extends FunctionExpression +{ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + $arguments = iterator_to_array($this->getNode('arguments')); + $compiler->write('$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->renderBlock('); + + if (isset($arguments[0])) { + $compiler->subcompile($arguments[0]); + $compiler->raw(', \''.$this->getAttribute('name').'\''); + + if (isset($arguments[1])) { + $compiler->raw(', '); + $compiler->subcompile($arguments[1]); + } + } + + $compiler->raw(')'); + } +} diff --git a/vendor/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php b/vendor/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php new file mode 100644 index 0000000..fa8653c --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Extension\CoreExtension; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FunctionExpression; + +/** + * @author Bernhard Schussek + */ +final class SearchAndRenderBlockNode extends FunctionExpression +{ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + $compiler->raw('$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock('); + + preg_match('/_([^_]+)$/', $this->getAttribute('name'), $matches); + + $arguments = iterator_to_array($this->getNode('arguments')); + $blockNameSuffix = $matches[1]; + + if (isset($arguments[0])) { + $compiler->subcompile($arguments[0]); + $compiler->raw(', \''.$blockNameSuffix.'\''); + + if (isset($arguments[1])) { + if ('label' === $blockNameSuffix) { + // The "label" function expects the label in the second and + // the variables in the third argument + $label = $arguments[1]; + $variables = $arguments[2] ?? null; + $lineno = $label->getTemplateLine(); + + if ($label instanceof ConstantExpression) { + // If the label argument is given as a constant, we can either + // strip it away if it is empty, or integrate it into the array + // of variables at compile time. + $labelIsExpression = false; + + // Only insert the label into the array if it is not empty + if (null !== $label->getAttribute('value') && false !== $label->getAttribute('value') && '' !== (string) $label->getAttribute('value')) { + $originalVariables = $variables; + $variables = new ArrayExpression([], $lineno); + $labelKey = new ConstantExpression('label', $lineno); + + if (null !== $originalVariables) { + foreach ($originalVariables->getKeyValuePairs() as $pair) { + // Don't copy the original label attribute over if it exists + if ((string) $labelKey !== (string) $pair['key']) { + $variables->addElement($pair['value'], $pair['key']); + } + } + } + + // Insert the label argument into the array + $variables->addElement($label, $labelKey); + } + } else { + // The label argument is not a constant, but some kind of + // expression. This expression needs to be evaluated at runtime. + // Depending on the result (whether it is null or not), the + // label in the arguments should take precedence over the label + // in the attributes or not. + $labelIsExpression = true; + } + } else { + // All other functions than "label" expect the variables + // in the second argument + $label = null; + $variables = $arguments[1]; + $labelIsExpression = false; + } + + if (null !== $variables || $labelIsExpression) { + $compiler->raw(', '); + + if (null !== $variables) { + $compiler->subcompile($variables); + } + + if ($labelIsExpression) { + if (null !== $variables) { + $compiler->raw(' + '); + } + + // Check at runtime whether the label is empty. + // If not, add it to the array at runtime. + if (method_exists(CoreExtension::class, 'testEmpty')) { + $compiler->raw('(CoreExtension::testEmpty($_label_ = '); + } else { + $compiler->raw('(twig_test_empty($_label_ = '); + } + + $compiler->subcompile($label); + $compiler->raw(') ? [] : ["label" => $_label_])'); + } + } + } + } + + $compiler->raw(')'); + } +} diff --git a/vendor/symfony/twig-bridge/Node/StopwatchNode.php b/vendor/symfony/twig-bridge/Node/StopwatchNode.php new file mode 100644 index 0000000..239d1ca --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/StopwatchNode.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Attribute\FirstClassTwigCallableReady; +use Twig\Attribute\YieldReady; +use Twig\Compiler; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Node; + +/** + * Represents a stopwatch node. + * + * @author Wouter J + */ +#[YieldReady] +final class StopwatchNode extends Node +{ + public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, ?string $tag = null) + { + if (class_exists(FirstClassTwigCallableReady::class)) { + parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); + } else { + parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); + } + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('var')) + ->raw(' = ') + ->subcompile($this->getNode('name')) + ->write(";\n") + ->write("\$this->env->getExtension('Symfony\Bridge\Twig\Extension\StopwatchExtension')->getStopwatch()->start(") + ->subcompile($this->getNode('var')) + ->raw(", 'template');\n") + ->subcompile($this->getNode('body')) + ->write("\$this->env->getExtension('Symfony\Bridge\Twig\Extension\StopwatchExtension')->getStopwatch()->stop(") + ->subcompile($this->getNode('var')) + ->raw(");\n") + ; + } +} diff --git a/vendor/symfony/twig-bridge/Node/TransDefaultDomainNode.php b/vendor/symfony/twig-bridge/Node/TransDefaultDomainNode.php new file mode 100644 index 0000000..28cb6f1 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/TransDefaultDomainNode.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Attribute\FirstClassTwigCallableReady; +use Twig\Attribute\YieldReady; +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Node; + +/** + * @author Fabien Potencier + */ +#[YieldReady] +final class TransDefaultDomainNode extends Node +{ + public function __construct(AbstractExpression $expr, int $lineno = 0, ?string $tag = null) + { + if (class_exists(FirstClassTwigCallableReady::class)) { + parent::__construct(['expr' => $expr], [], $lineno); + } else { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + } + + public function compile(Compiler $compiler): void + { + // noop as this node is just a marker for TranslationDefaultDomainNodeVisitor + } +} diff --git a/vendor/symfony/twig-bridge/Node/TransNode.php b/vendor/symfony/twig-bridge/Node/TransNode.php new file mode 100644 index 0000000..4212639 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/TransNode.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Attribute\FirstClassTwigCallableReady; +use Twig\Attribute\YieldReady; +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Node; +use Twig\Node\TextNode; + +/** + * @author Fabien Potencier + */ +#[YieldReady] +final class TransNode extends Node +{ + public function __construct(Node $body, ?Node $domain = null, ?AbstractExpression $count = null, ?AbstractExpression $vars = null, ?AbstractExpression $locale = null, int $lineno = 0, ?string $tag = null) + { + $nodes = ['body' => $body]; + if (null !== $domain) { + $nodes['domain'] = $domain; + } + if (null !== $count) { + $nodes['count'] = $count; + } + if (null !== $vars) { + $nodes['vars'] = $vars; + } + if (null !== $locale) { + $nodes['locale'] = $locale; + } + + if (class_exists(FirstClassTwigCallableReady::class)) { + parent::__construct($nodes, [], $lineno); + } else { + parent::__construct($nodes, [], $lineno, $tag); + } + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $defaults = new ArrayExpression([], -1); + if ($this->hasNode('vars') && ($vars = $this->getNode('vars')) instanceof ArrayExpression) { + $defaults = $this->getNode('vars'); + $vars = null; + } + [$msg, $defaults] = $this->compileString($this->getNode('body'), $defaults, (bool) $vars); + $compiler + ->write('yield $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans(') + ->subcompile($msg) + ; + + $compiler->raw(', '); + + if (null !== $vars) { + $compiler + ->raw('array_merge(') + ->subcompile($defaults) + ->raw(', ') + ->subcompile($this->getNode('vars')) + ->raw(')') + ; + } else { + $compiler->subcompile($defaults); + } + + $compiler->raw(', '); + + if (!$this->hasNode('domain')) { + $compiler->repr('messages'); + } else { + $compiler->subcompile($this->getNode('domain')); + } + + if ($this->hasNode('locale')) { + $compiler + ->raw(', ') + ->subcompile($this->getNode('locale')) + ; + } elseif ($this->hasNode('count')) { + $compiler->raw(', null'); + } + + if ($this->hasNode('count')) { + $compiler + ->raw(', ') + ->subcompile($this->getNode('count')) + ; + } + + $compiler->raw(");\n"); + } + + private function compileString(Node $body, ArrayExpression $vars, bool $ignoreStrictCheck = false): array + { + if ($body instanceof ConstantExpression) { + $msg = $body->getAttribute('value'); + } elseif ($body instanceof TextNode) { + $msg = $body->getAttribute('data'); + } else { + return [$body, $vars]; + } + + preg_match_all('/(?getTemplateLine()); + if (!$vars->hasElement($key)) { + if ('count' === $var && $this->hasNode('count')) { + $vars->addElement($this->getNode('count'), $key); + } else { + $varExpr = new NameExpression($var, $body->getTemplateLine()); + $varExpr->setAttribute('ignore_strict_check', $ignoreStrictCheck); + $vars->addElement($varExpr, $key); + } + } + } + + return [new ConstantExpression(str_replace('%%', '%', trim($msg)), $body->getTemplateLine()), $vars]; + } +} diff --git a/vendor/symfony/twig-bridge/NodeVisitor/Scope.php b/vendor/symfony/twig-bridge/NodeVisitor/Scope.php new file mode 100644 index 0000000..4914506 --- /dev/null +++ b/vendor/symfony/twig-bridge/NodeVisitor/Scope.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\NodeVisitor; + +/** + * @author Jean-François Simon + */ +class Scope +{ + private array $data = []; + private bool $left = false; + + public function __construct( + private ?self $parent = null, + ) { + } + + /** + * Opens a new child scope. + */ + public function enter(): self + { + return new self($this); + } + + /** + * Closes current scope and returns parent one. + */ + public function leave(): ?self + { + $this->left = true; + + return $this->parent; + } + + /** + * Stores data into current scope. + * + * @return $this + * + * @throws \LogicException + */ + public function set(string $key, mixed $value): static + { + if ($this->left) { + throw new \LogicException('Left scope is not mutable.'); + } + + $this->data[$key] = $value; + + return $this; + } + + /** + * Tests if a data is visible from current scope. + */ + public function has(string $key): bool + { + if (\array_key_exists($key, $this->data)) { + return true; + } + + if (null === $this->parent) { + return false; + } + + return $this->parent->has($key); + } + + /** + * Returns data visible from current scope. + */ + public function get(string $key, mixed $default = null): mixed + { + if (\array_key_exists($key, $this->data)) { + return $this->data[$key]; + } + + if (null === $this->parent) { + return $default; + } + + return $this->parent->get($key, $default); + } +} diff --git a/vendor/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/vendor/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php new file mode 100644 index 0000000..364442c --- /dev/null +++ b/vendor/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\NodeVisitor; + +use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Environment; +use Twig\Node\BlockNode; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\Node\SetNode; +use Twig\NodeVisitor\NodeVisitorInterface; + +/** + * @author Fabien Potencier + */ +final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface +{ + private Scope $scope; + + public function __construct() + { + $this->scope = new Scope(); + } + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof BlockNode || $node instanceof ModuleNode) { + $this->scope = $this->scope->enter(); + } + + if ($node instanceof TransDefaultDomainNode) { + if ($node->getNode('expr') instanceof ConstantExpression) { + $this->scope->set('domain', $node->getNode('expr')); + + return $node; + } else { + $var = $this->getVarName(); + $name = new AssignNameExpression($var, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); + + return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); + } + } + + if (!$this->scope->has('domain')) { + return $node; + } + + if ($node instanceof FilterExpression && 'trans' === ($node->hasAttribute('twig_callable') ? $node->getAttribute('twig_callable')->getName() : $node->getNode('filter')->getAttribute('value'))) { + $arguments = $node->getNode('arguments'); + if ($this->isNamedArguments($arguments)) { + if (!$arguments->hasNode('domain') && !$arguments->hasNode(1)) { + $arguments->setNode('domain', $this->scope->get('domain')); + } + } elseif (!$arguments->hasNode(1)) { + if (!$arguments->hasNode(0)) { + $arguments->setNode(0, new ArrayExpression([], $node->getTemplateLine())); + } + + $arguments->setNode(1, $this->scope->get('domain')); + } + } elseif ($node instanceof TransNode) { + if (!$node->hasNode('domain')) { + $node->setNode('domain', $this->scope->get('domain')); + } + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof TransDefaultDomainNode) { + return null; + } + + if ($node instanceof BlockNode || $node instanceof ModuleNode) { + $this->scope = $this->scope->leave(); + } + + return $node; + } + + public function getPriority(): int + { + return -10; + } + + private function isNamedArguments(Node $arguments): bool + { + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + return true; + } + } + + return false; + } + + private function getVarName(): string + { + return sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); + } +} diff --git a/vendor/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php b/vendor/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php new file mode 100644 index 0000000..f2b8f19 --- /dev/null +++ b/vendor/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\NodeVisitor; + +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Environment; +use Twig\Node\Expression\Binary\ConcatBinary; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\FunctionExpression; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; + +/** + * TranslationNodeVisitor extracts translation messages. + * + * @author Fabien Potencier + */ +final class TranslationNodeVisitor implements NodeVisitorInterface +{ + public const UNDEFINED_DOMAIN = '_undefined'; + + private bool $enabled = false; + private array $messages = []; + + public function enable(): void + { + $this->enabled = true; + $this->messages = []; + } + + public function disable(): void + { + $this->enabled = false; + $this->messages = []; + } + + public function getMessages(): array + { + return $this->messages; + } + + public function enterNode(Node $node, Environment $env): Node + { + if (!$this->enabled) { + return $node; + } + + if ( + $node instanceof FilterExpression + && 'trans' === ($node->hasAttribute('twig_callable') ? $node->getAttribute('twig_callable')->getName() : $node->getNode('filter')->getAttribute('value')) + && $node->getNode('node') instanceof ConstantExpression + ) { + // extract constant nodes with a trans filter + $this->messages[] = [ + $node->getNode('node')->getAttribute('value'), + $this->getReadDomainFromArguments($node->getNode('arguments'), 1), + ]; + } elseif ( + $node instanceof FunctionExpression + && 't' === $node->getAttribute('name') + ) { + $nodeArguments = $node->getNode('arguments'); + + if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) { + $this->messages[] = [ + $this->getReadMessageFromArguments($nodeArguments, 0), + $this->getReadDomainFromArguments($nodeArguments, 2), + ]; + } + } elseif ($node instanceof TransNode) { + // extract trans nodes + $this->messages[] = [ + $node->getNode('body')->getAttribute('data'), + $node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null, + ]; + } elseif ( + $node instanceof FilterExpression + && 'trans' === ($node->hasAttribute('twig_callable') ? $node->getAttribute('twig_callable')->getName() : $node->getNode('filter')->getAttribute('value')) + && $node->getNode('node') instanceof ConcatBinary + && $message = $this->getConcatValueFromNode($node->getNode('node'), null) + ) { + $this->messages[] = [ + $message, + $this->getReadDomainFromArguments($node->getNode('arguments'), 1), + ]; + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + return $node; + } + + public function getPriority(): int + { + return 0; + } + + private function getReadMessageFromArguments(Node $arguments, int $index): ?string + { + if ($arguments->hasNode('message')) { + $argument = $arguments->getNode('message'); + } elseif ($arguments->hasNode($index)) { + $argument = $arguments->getNode($index); + } else { + return null; + } + + return $this->getReadMessageFromNode($argument); + } + + private function getReadMessageFromNode(Node $node): ?string + { + if ($node instanceof ConstantExpression) { + return $node->getAttribute('value'); + } + + return null; + } + + private function getReadDomainFromArguments(Node $arguments, int $index): ?string + { + if ($arguments->hasNode('domain')) { + $argument = $arguments->getNode('domain'); + } elseif ($arguments->hasNode($index)) { + $argument = $arguments->getNode($index); + } else { + return null; + } + + return $this->getReadDomainFromNode($argument); + } + + private function getReadDomainFromNode(Node $node): ?string + { + if ($node instanceof ConstantExpression) { + return $node->getAttribute('value'); + } + + if ( + $node instanceof FunctionExpression + && 'constant' === $node->getAttribute('name') + ) { + $nodeArguments = $node->getNode('arguments'); + if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) { + $constantName = $nodeArguments->getIterator()->current()->getAttribute('value'); + if (\defined($constantName)) { + $value = \constant($constantName); + if (\is_string($value)) { + return $value; + } + } + } + } + + return self::UNDEFINED_DOMAIN; + } + + private function getConcatValueFromNode(Node $node, ?string $value): ?string + { + if ($node instanceof ConcatBinary) { + foreach ($node as $nextNode) { + if ($nextNode instanceof ConcatBinary) { + $nextValue = $this->getConcatValueFromNode($nextNode, $value); + if (null === $nextValue) { + return null; + } + $value .= $nextValue; + } elseif ($nextNode instanceof ConstantExpression) { + $value .= $nextNode->getAttribute('value'); + } else { + // this is a node we cannot process (variable, or translation in translation) + return null; + } + } + } elseif ($node instanceof ConstantExpression) { + $value .= $node->getAttribute('value'); + } + + return $value; + } +} diff --git a/vendor/symfony/twig-bridge/README.md b/vendor/symfony/twig-bridge/README.md new file mode 100644 index 0000000..533d573 --- /dev/null +++ b/vendor/symfony/twig-bridge/README.md @@ -0,0 +1,13 @@ +Twig Bridge +=========== + +The Twig bridge provides integration for [Twig](https://twig.symfony.com/) with +various Symfony components. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.html.twig b/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.html.twig new file mode 100644 index 0000000..9027546 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.html.twig @@ -0,0 +1 @@ +{% extends "@email/zurb_2/notification/body.html.twig" %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.txt.twig b/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.txt.twig new file mode 100644 index 0000000..37671b1 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Email/default/notification/body.txt.twig @@ -0,0 +1 @@ +{% extends "@email/zurb_2/notification/body.txt.twig" %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/main.css b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/main.css new file mode 100644 index 0000000..dab0df5 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/main.css @@ -0,0 +1,1667 @@ +/* + * Copyright (c) 2017 ZURB, inc. -- MIT License + * + * https://github.com/foundation/foundation-emails/blob/v2.2.1/dist/foundation-emails.css + */ + +.wrapper { + width: 100%; +} + +#outlook a { + padding: 0; +} + +body { + width: 100% !important; + min-width: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + margin: 0; + Margin: 0; + padding: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.ExternalClass { + width: 100%; +} + +.ExternalClass, +.ExternalClass p, +.ExternalClass span, +.ExternalClass font, +.ExternalClass td, +.ExternalClass div { + line-height: 100%; +} + +#backgroundTable { + margin: 0; + Margin: 0; + padding: 0; + width: 100% !important; + line-height: 100% !important; +} + +img { + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + width: auto; + max-width: 100%; + clear: both; + display: block; +} + +center { + width: 100%; + min-width: 580px; +} + +a img { + border: none; +} + +p { + margin: 0 0 0 10px; + Margin: 0 0 0 10px; +} + +table { + border-spacing: 0; + border-collapse: collapse; +} + +td { + word-wrap: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + border-collapse: collapse !important; +} + +table, +tr, +td { + padding: 0; + vertical-align: top; + text-align: left; +} + +@media only screen { + html { + min-height: 100%; + background: #f3f3f3; + } +} + +table.body { + background: #f3f3f3; + height: 100%; + width: 100%; +} + +table.container { + background: #fefefe; + width: 580px; + margin: 0 auto; + Margin: 0 auto; + text-align: inherit; +} + +table.row { + padding: 0; + width: 100%; + position: relative; +} + +table.spacer { + width: 100%; +} + +table.spacer td { + mso-line-height-rule: exactly; +} + +table.container table.row { + display: table; +} + +td.columns, +td.column, +th.columns, +th.column { + margin: 0 auto; + Margin: 0 auto; + padding-left: 16px; + padding-bottom: 16px; +} + +td.columns .column, +td.columns .columns, +td.column .column, +td.column .columns, +th.columns .column, +th.columns .columns, +th.column .column, +th.column .columns { + padding-left: 0 !important; + padding-right: 0 !important; +} + +td.columns .column center, +td.columns .columns center, +td.column .column center, +td.column .columns center, +th.columns .column center, +th.columns .columns center, +th.column .column center, +th.column .columns center { + min-width: none !important; +} + +td.columns.last, +td.column.last, +th.columns.last, +th.column.last { + padding-right: 16px; +} + +td.columns table:not(.button), +td.column table:not(.button), +th.columns table:not(.button), +th.column table:not(.button) { + width: 100%; +} + +td.large-1, +th.large-1 { + width: 32.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-1.first, +th.large-1.first { + padding-left: 16px; +} + +td.large-1.last, +th.large-1.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-1, +.collapse>tbody>tr>th.large-1 { + padding-right: 0; + padding-left: 0; + width: 48.33333px; +} + +.collapse td.large-1.first, +.collapse th.large-1.first, +.collapse td.large-1.last, +.collapse th.large-1.last { + width: 56.33333px; +} + +td.large-1 center, +th.large-1 center { + min-width: 0.33333px; +} + +.body .columns td.large-1, +.body .column td.large-1, +.body .columns th.large-1, +.body .column th.large-1 { + width: 8.33333%; +} + +td.large-2, +th.large-2 { + width: 80.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-2.first, +th.large-2.first { + padding-left: 16px; +} + +td.large-2.last, +th.large-2.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-2, +.collapse>tbody>tr>th.large-2 { + padding-right: 0; + padding-left: 0; + width: 96.66667px; +} + +.collapse td.large-2.first, +.collapse th.large-2.first, +.collapse td.large-2.last, +.collapse th.large-2.last { + width: 104.66667px; +} + +td.large-2 center, +th.large-2 center { + min-width: 48.66667px; +} + +.body .columns td.large-2, +.body .column td.large-2, +.body .columns th.large-2, +.body .column th.large-2 { + width: 16.66667%; +} + +td.large-3, +th.large-3 { + width: 129px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-3.first, +th.large-3.first { + padding-left: 16px; +} + +td.large-3.last, +th.large-3.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-3, +.collapse>tbody>tr>th.large-3 { + padding-right: 0; + padding-left: 0; + width: 145px; +} + +.collapse td.large-3.first, +.collapse th.large-3.first, +.collapse td.large-3.last, +.collapse th.large-3.last { + width: 153px; +} + +td.large-3 center, +th.large-3 center { + min-width: 97px; +} + +.body .columns td.large-3, +.body .column td.large-3, +.body .columns th.large-3, +.body .column th.large-3 { + width: 25%; +} + +td.large-4, +th.large-4 { + width: 177.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-4.first, +th.large-4.first { + padding-left: 16px; +} + +td.large-4.last, +th.large-4.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-4, +.collapse>tbody>tr>th.large-4 { + padding-right: 0; + padding-left: 0; + width: 193.33333px; +} + +.collapse td.large-4.first, +.collapse th.large-4.first, +.collapse td.large-4.last, +.collapse th.large-4.last { + width: 201.33333px; +} + +td.large-4 center, +th.large-4 center { + min-width: 145.33333px; +} + +.body .columns td.large-4, +.body .column td.large-4, +.body .columns th.large-4, +.body .column th.large-4 { + width: 33.33333%; +} + +td.large-5, +th.large-5 { + width: 225.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-5.first, +th.large-5.first { + padding-left: 16px; +} + +td.large-5.last, +th.large-5.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-5, +.collapse>tbody>tr>th.large-5 { + padding-right: 0; + padding-left: 0; + width: 241.66667px; +} + +.collapse td.large-5.first, +.collapse th.large-5.first, +.collapse td.large-5.last, +.collapse th.large-5.last { + width: 249.66667px; +} + +td.large-5 center, +th.large-5 center { + min-width: 193.66667px; +} + +.body .columns td.large-5, +.body .column td.large-5, +.body .columns th.large-5, +.body .column th.large-5 { + width: 41.66667%; +} + +td.large-6, +th.large-6 { + width: 274px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-6.first, +th.large-6.first { + padding-left: 16px; +} + +td.large-6.last, +th.large-6.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-6, +.collapse>tbody>tr>th.large-6 { + padding-right: 0; + padding-left: 0; + width: 290px; +} + +.collapse td.large-6.first, +.collapse th.large-6.first, +.collapse td.large-6.last, +.collapse th.large-6.last { + width: 298px; +} + +td.large-6 center, +th.large-6 center { + min-width: 242px; +} + +.body .columns td.large-6, +.body .column td.large-6, +.body .columns th.large-6, +.body .column th.large-6 { + width: 50%; +} + +td.large-7, +th.large-7 { + width: 322.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-7.first, +th.large-7.first { + padding-left: 16px; +} + +td.large-7.last, +th.large-7.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-7, +.collapse>tbody>tr>th.large-7 { + padding-right: 0; + padding-left: 0; + width: 338.33333px; +} + +.collapse td.large-7.first, +.collapse th.large-7.first, +.collapse td.large-7.last, +.collapse th.large-7.last { + width: 346.33333px; +} + +td.large-7 center, +th.large-7 center { + min-width: 290.33333px; +} + +.body .columns td.large-7, +.body .column td.large-7, +.body .columns th.large-7, +.body .column th.large-7 { + width: 58.33333%; +} + +td.large-8, +th.large-8 { + width: 370.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-8.first, +th.large-8.first { + padding-left: 16px; +} + +td.large-8.last, +th.large-8.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-8, +.collapse>tbody>tr>th.large-8 { + padding-right: 0; + padding-left: 0; + width: 386.66667px; +} + +.collapse td.large-8.first, +.collapse th.large-8.first, +.collapse td.large-8.last, +.collapse th.large-8.last { + width: 394.66667px; +} + +td.large-8 center, +th.large-8 center { + min-width: 338.66667px; +} + +.body .columns td.large-8, +.body .column td.large-8, +.body .columns th.large-8, +.body .column th.large-8 { + width: 66.66667%; +} + +td.large-9, +th.large-9 { + width: 419px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-9.first, +th.large-9.first { + padding-left: 16px; +} + +td.large-9.last, +th.large-9.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-9, +.collapse>tbody>tr>th.large-9 { + padding-right: 0; + padding-left: 0; + width: 435px; +} + +.collapse td.large-9.first, +.collapse th.large-9.first, +.collapse td.large-9.last, +.collapse th.large-9.last { + width: 443px; +} + +td.large-9 center, +th.large-9 center { + min-width: 387px; +} + +.body .columns td.large-9, +.body .column td.large-9, +.body .columns th.large-9, +.body .column th.large-9 { + width: 75%; +} + +td.large-10, +th.large-10 { + width: 467.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-10.first, +th.large-10.first { + padding-left: 16px; +} + +td.large-10.last, +th.large-10.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-10, +.collapse>tbody>tr>th.large-10 { + padding-right: 0; + padding-left: 0; + width: 483.33333px; +} + +.collapse td.large-10.first, +.collapse th.large-10.first, +.collapse td.large-10.last, +.collapse th.large-10.last { + width: 491.33333px; +} + +td.large-10 center, +th.large-10 center { + min-width: 435.33333px; +} + +.body .columns td.large-10, +.body .column td.large-10, +.body .columns th.large-10, +.body .column th.large-10 { + width: 83.33333%; +} + +td.large-11, +th.large-11 { + width: 515.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-11.first, +th.large-11.first { + padding-left: 16px; +} + +td.large-11.last, +th.large-11.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-11, +.collapse>tbody>tr>th.large-11 { + padding-right: 0; + padding-left: 0; + width: 531.66667px; +} + +.collapse td.large-11.first, +.collapse th.large-11.first, +.collapse td.large-11.last, +.collapse th.large-11.last { + width: 539.66667px; +} + +td.large-11 center, +th.large-11 center { + min-width: 483.66667px; +} + +.body .columns td.large-11, +.body .column td.large-11, +.body .columns th.large-11, +.body .column th.large-11 { + width: 91.66667%; +} + +td.large-12, +th.large-12 { + width: 564px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-12.first, +th.large-12.first { + padding-left: 16px; +} + +td.large-12.last, +th.large-12.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-12, +.collapse>tbody>tr>th.large-12 { + padding-right: 0; + padding-left: 0; + width: 580px; +} + +.collapse td.large-12.first, +.collapse th.large-12.first, +.collapse td.large-12.last, +.collapse th.large-12.last { + width: 588px; +} + +td.large-12 center, +th.large-12 center { + min-width: 532px; +} + +.body .columns td.large-12, +.body .column td.large-12, +.body .columns th.large-12, +.body .column th.large-12 { + width: 100%; +} + +td.large-offset-1, +td.large-offset-1.first, +td.large-offset-1.last, +th.large-offset-1, +th.large-offset-1.first, +th.large-offset-1.last { + padding-left: 64.33333px; +} + +td.large-offset-2, +td.large-offset-2.first, +td.large-offset-2.last, +th.large-offset-2, +th.large-offset-2.first, +th.large-offset-2.last { + padding-left: 112.66667px; +} + +td.large-offset-3, +td.large-offset-3.first, +td.large-offset-3.last, +th.large-offset-3, +th.large-offset-3.first, +th.large-offset-3.last { + padding-left: 161px; +} + +td.large-offset-4, +td.large-offset-4.first, +td.large-offset-4.last, +th.large-offset-4, +th.large-offset-4.first, +th.large-offset-4.last { + padding-left: 209.33333px; +} + +td.large-offset-5, +td.large-offset-5.first, +td.large-offset-5.last, +th.large-offset-5, +th.large-offset-5.first, +th.large-offset-5.last { + padding-left: 257.66667px; +} + +td.large-offset-6, +td.large-offset-6.first, +td.large-offset-6.last, +th.large-offset-6, +th.large-offset-6.first, +th.large-offset-6.last { + padding-left: 306px; +} + +td.large-offset-7, +td.large-offset-7.first, +td.large-offset-7.last, +th.large-offset-7, +th.large-offset-7.first, +th.large-offset-7.last { + padding-left: 354.33333px; +} + +td.large-offset-8, +td.large-offset-8.first, +td.large-offset-8.last, +th.large-offset-8, +th.large-offset-8.first, +th.large-offset-8.last { + padding-left: 402.66667px; +} + +td.large-offset-9, +td.large-offset-9.first, +td.large-offset-9.last, +th.large-offset-9, +th.large-offset-9.first, +th.large-offset-9.last { + padding-left: 451px; +} + +td.large-offset-10, +td.large-offset-10.first, +td.large-offset-10.last, +th.large-offset-10, +th.large-offset-10.first, +th.large-offset-10.last { + padding-left: 499.33333px; +} + +td.large-offset-11, +td.large-offset-11.first, +td.large-offset-11.last, +th.large-offset-11, +th.large-offset-11.first, +th.large-offset-11.last { + padding-left: 547.66667px; +} + +td.expander, +th.expander { + visibility: hidden; + width: 0; + padding: 0 !important; +} + +table.container.radius { + border-radius: 0; + border-collapse: separate; +} + +.block-grid { + width: 100%; + max-width: 580px; +} + +.block-grid td { + display: inline-block; + padding: 8px; +} + +.up-2 td { + width: 274px !important; +} + +.up-3 td { + width: 177px !important; +} + +.up-4 td { + width: 129px !important; +} + +.up-5 td { + width: 100px !important; +} + +.up-6 td { + width: 80px !important; +} + +.up-7 td { + width: 66px !important; +} + +.up-8 td { + width: 56px !important; +} + +table.text-center, +th.text-center, +td.text-center, +h1.text-center, +h2.text-center, +h3.text-center, +h4.text-center, +h5.text-center, +h6.text-center, +p.text-center, +span.text-center { + text-align: center; +} + +table.text-left, +th.text-left, +td.text-left, +h1.text-left, +h2.text-left, +h3.text-left, +h4.text-left, +h5.text-left, +h6.text-left, +p.text-left, +span.text-left { + text-align: left; +} + +table.text-right, +th.text-right, +td.text-right, +h1.text-right, +h2.text-right, +h3.text-right, +h4.text-right, +h5.text-right, +h6.text-right, +p.text-right, +span.text-right { + text-align: right; +} + +span.text-center { + display: block; + width: 100%; + text-align: center; +} + +@media only screen and (max-width: 596px) { + .small-float-center { + margin: 0 auto !important; + float: none !important; + text-align: center !important; + } + .small-text-center { + text-align: center !important; + } + .small-text-left { + text-align: left !important; + } + .small-text-right { + text-align: right !important; + } +} + +img.float-left { + float: left; + text-align: left; +} + +img.float-right { + float: right; + text-align: right; +} + +img.float-center, +img.text-center { + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; +} + +table.float-center, +td.float-center, +th.float-center { + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; +} + +.hide-for-large { + display: none !important; + mso-hide: all; + overflow: hidden; + max-height: 0; + font-size: 0; + width: 0; + line-height: 0; +} + +@media only screen and (max-width: 596px) { + .hide-for-large { + display: block !important; + width: auto !important; + overflow: visible !important; + max-height: none !important; + font-size: inherit !important; + line-height: inherit !important; + } +} + +table.body table.container .hide-for-large * { + mso-hide: all; +} + +@media only screen and (max-width: 596px) { + table.body table.container .hide-for-large, + table.body table.container .row.hide-for-large { + display: table !important; + width: 100% !important; + } +} + +@media only screen and (max-width: 596px) { + table.body table.container .callout-inner.hide-for-large { + display: table-cell !important; + width: 100% !important; + } +} + +@media only screen and (max-width: 596px) { + table.body table.container .show-for-large { + display: none !important; + width: 0; + mso-hide: all; + overflow: hidden; + } +} + +body, +table.body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +td, +th, +a { + color: #0a0a0a; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + padding: 0; + margin: 0; + Margin: 0; + text-align: left; + line-height: 1.3; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: inherit; + word-wrap: normal; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + margin-bottom: 10px; + Margin-bottom: 10px; +} + +h1 { + font-size: 34px; +} + +h2 { + font-size: 30px; +} + +h3 { + font-size: 28px; +} + +h4 { + font-size: 24px; +} + +h5 { + font-size: 20px; +} + +h6 { + font-size: 18px; +} + +body, +table.body, +p, +td, +th { + font-size: 16px; + line-height: 1.3; +} + +p { + margin-bottom: 10px; + Margin-bottom: 10px; +} + +p.lead { + font-size: 20px; + line-height: 1.6; +} + +p.subheader { + margin-top: 4px; + margin-bottom: 8px; + Margin-top: 4px; + Margin-bottom: 8px; + font-weight: normal; + line-height: 1.4; + color: #8a8a8a; +} + +small { + font-size: 80%; + color: #cacaca; +} + +a { + color: #2199e8; + text-decoration: none; +} + +a:hover { + color: #147dc2; +} + +a:active { + color: #147dc2; +} + +a:visited { + color: #2199e8; +} + +h1 a, +h1 a:visited, +h2 a, +h2 a:visited, +h3 a, +h3 a:visited, +h4 a, +h4 a:visited, +h5 a, +h5 a:visited, +h6 a, +h6 a:visited { + color: #2199e8; +} + +pre { + background: #f3f3f3; + margin: 30px 0; + Margin: 30px 0; +} + +pre code { + color: #cacaca; +} + +pre code span.callout { + color: #8a8a8a; + font-weight: bold; +} + +pre code span.callout-strong { + color: #ff6908; + font-weight: bold; +} + +table.hr { + width: 100%; +} + +table.hr th { + height: 0; + max-width: 580px; + border-top: 0; + border-right: 0; + border-bottom: 1px solid #0a0a0a; + border-left: 0; + margin: 20px auto; + Margin: 20px auto; + clear: both; +} + +.stat { + font-size: 40px; + line-height: 1; +} + +p+.stat { + margin-top: -16px; + Margin-top: -16px; +} + +span.preheader { + display: none !important; + visibility: hidden; + mso-hide: all !important; + font-size: 1px; + color: #f3f3f3; + line-height: 1px; + max-height: 0px; + max-width: 0px; + opacity: 0; + overflow: hidden; +} + +table.button { + width: auto; + margin: 0 0 16px 0; + Margin: 0 0 16px 0; +} + +table.button table td { + text-align: left; + color: #fefefe; + background: #2199e8; + border: 2px solid #2199e8; +} + +table.button table td a { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + font-weight: bold; + color: #fefefe; + text-decoration: none; + display: inline-block; + padding: 8px 16px 8px 16px; + border: 0 solid #2199e8; + border-radius: 3px; +} + +table.button.radius table td { + border-radius: 3px; + border: none; +} + +table.button.rounded table td { + border-radius: 500px; + border: none; +} + +table.button:hover table tr td a, +table.button:active table tr td a, +table.button table tr td a:visited, +table.button.tiny:hover table tr td a, +table.button.tiny:active table tr td a, +table.button.tiny table tr td a:visited, +table.button.small:hover table tr td a, +table.button.small:active table tr td a, +table.button.small table tr td a:visited, +table.button.large:hover table tr td a, +table.button.large:active table tr td a, +table.button.large table tr td a:visited { + color: #fefefe; +} + +table.button.tiny table td, +table.button.tiny table a { + padding: 4px 8px 4px 8px; +} + +table.button.tiny table a { + font-size: 10px; + font-weight: normal; +} + +table.button.small table td, +table.button.small table a { + padding: 5px 10px 5px 10px; + font-size: 12px; +} + +table.button.large table a { + padding: 10px 20px 10px 20px; + font-size: 20px; +} + +table.button.expand, +table.button.expanded { + width: 100% !important; +} + +table.button.expand table, +table.button.expanded table { + width: 100%; +} + +table.button.expand table a, +table.button.expanded table a { + text-align: center; + width: 100%; + padding-left: 0; + padding-right: 0; +} + +table.button.expand center, +table.button.expanded center { + min-width: 0; +} + +table.button:hover table td, +table.button:visited table td, +table.button:active table td { + background: #147dc2; + color: #fefefe; +} + +table.button:hover table a, +table.button:visited table a, +table.button:active table a { + border: 0 solid #147dc2; +} + +table.button.secondary table td { + background: #777777; + color: #fefefe; + border: 0px solid #777777; +} + +table.button.secondary table a { + color: #fefefe; + border: 0 solid #777777; +} + +table.button.secondary:hover table td { + background: #919191; + color: #fefefe; +} + +table.button.secondary:hover table a { + border: 0 solid #919191; +} + +table.button.secondary:hover table td a { + color: #fefefe; +} + +table.button.secondary:active table td a { + color: #fefefe; +} + +table.button.secondary table td a:visited { + color: #fefefe; +} + +table.button.success table td { + background: #3adb76; + border: 0px solid #3adb76; +} + +table.button.success table a { + border: 0 solid #3adb76; +} + +table.button.success:hover table td { + background: #23bf5d; +} + +table.button.success:hover table a { + border: 0 solid #23bf5d; +} + +table.button.alert table td { + background: #ec5840; + border: 0px solid #ec5840; +} + +table.button.alert table a { + border: 0 solid #ec5840; +} + +table.button.alert:hover table td { + background: #e23317; +} + +table.button.alert:hover table a { + border: 0 solid #e23317; +} + +table.button.warning table td { + background: #ffae00; + border: 0px solid #ffae00; +} + +table.button.warning table a { + border: 0px solid #ffae00; +} + +table.button.warning:hover table td { + background: #cc8b00; +} + +table.button.warning:hover table a { + border: 0px solid #cc8b00; +} + +table.callout { + margin-bottom: 16px; + Margin-bottom: 16px; +} + +th.callout-inner { + width: 100%; + border: 1px solid #cbcbcb; + padding: 10px; + background: #fefefe; +} + +th.callout-inner.primary { + background: #def0fc; + border: 1px solid #444444; + color: #0a0a0a; +} + +th.callout-inner.secondary { + background: #ebebeb; + border: 1px solid #444444; + color: #0a0a0a; +} + +th.callout-inner.success { + background: #e1faea; + border: 1px solid #1b9448; + color: #fefefe; +} + +th.callout-inner.warning { + background: #fff3d9; + border: 1px solid #996800; + color: #fefefe; +} + +th.callout-inner.alert { + background: #fce6e2; + border: 1px solid #b42912; + color: #fefefe; +} + +.thumbnail { + border: solid 4px #fefefe; + box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2); + display: inline-block; + line-height: 0; + max-width: 100%; + transition: box-shadow 200ms ease-out; + border-radius: 3px; + margin-bottom: 16px; +} + +.thumbnail:hover, +.thumbnail:focus { + box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5); +} + +table.menu { + width: 580px; +} + +table.menu td.menu-item, +table.menu th.menu-item { + padding: 10px; + padding-right: 10px; +} + +table.menu td.menu-item a, +table.menu th.menu-item a { + color: #2199e8; +} + +table.menu.vertical td.menu-item, +table.menu.vertical th.menu-item { + padding: 10px; + padding-right: 0; + display: block; +} + +table.menu.vertical td.menu-item a, +table.menu.vertical th.menu-item a { + width: 100%; +} + +table.menu.vertical td.menu-item table.menu.vertical td.menu-item, +table.menu.vertical td.menu-item table.menu.vertical th.menu-item, +table.menu.vertical th.menu-item table.menu.vertical td.menu-item, +table.menu.vertical th.menu-item table.menu.vertical th.menu-item { + padding-left: 10px; +} + +table.menu.text-center a { + text-align: center; +} + +.menu[align="center"] { + width: auto !important; +} + +body.outlook p { + display: inline !important; +} + +@media only screen and (max-width: 596px) { + table.body img { + width: auto; + height: auto; + } + table.body center { + min-width: 0 !important; + } + table.body .container { + width: 95% !important; + } + table.body .columns, + table.body .column { + height: auto !important; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding-left: 16px !important; + padding-right: 16px !important; + } + table.body .columns .column, + table.body .columns .columns, + table.body .column .column, + table.body .column .columns { + padding-left: 0 !important; + padding-right: 0 !important; + } + table.body .collapse .columns, + table.body .collapse .column { + padding-left: 0 !important; + padding-right: 0 !important; + } + td.small-1, + th.small-1 { + display: inline-block !important; + width: 8.33333% !important; + } + td.small-2, + th.small-2 { + display: inline-block !important; + width: 16.66667% !important; + } + td.small-3, + th.small-3 { + display: inline-block !important; + width: 25% !important; + } + td.small-4, + th.small-4 { + display: inline-block !important; + width: 33.33333% !important; + } + td.small-5, + th.small-5 { + display: inline-block !important; + width: 41.66667% !important; + } + td.small-6, + th.small-6 { + display: inline-block !important; + width: 50% !important; + } + td.small-7, + th.small-7 { + display: inline-block !important; + width: 58.33333% !important; + } + td.small-8, + th.small-8 { + display: inline-block !important; + width: 66.66667% !important; + } + td.small-9, + th.small-9 { + display: inline-block !important; + width: 75% !important; + } + td.small-10, + th.small-10 { + display: inline-block !important; + width: 83.33333% !important; + } + td.small-11, + th.small-11 { + display: inline-block !important; + width: 91.66667% !important; + } + td.small-12, + th.small-12 { + display: inline-block !important; + width: 100% !important; + } + .columns td.small-12, + .column td.small-12, + .columns th.small-12, + .column th.small-12 { + display: block !important; + width: 100% !important; + } + table.body td.small-offset-1, + table.body th.small-offset-1 { + margin-left: 8.33333% !important; + Margin-left: 8.33333% !important; + } + table.body td.small-offset-2, + table.body th.small-offset-2 { + margin-left: 16.66667% !important; + Margin-left: 16.66667% !important; + } + table.body td.small-offset-3, + table.body th.small-offset-3 { + margin-left: 25% !important; + Margin-left: 25% !important; + } + table.body td.small-offset-4, + table.body th.small-offset-4 { + margin-left: 33.33333% !important; + Margin-left: 33.33333% !important; + } + table.body td.small-offset-5, + table.body th.small-offset-5 { + margin-left: 41.66667% !important; + Margin-left: 41.66667% !important; + } + table.body td.small-offset-6, + table.body th.small-offset-6 { + margin-left: 50% !important; + Margin-left: 50% !important; + } + table.body td.small-offset-7, + table.body th.small-offset-7 { + margin-left: 58.33333% !important; + Margin-left: 58.33333% !important; + } + table.body td.small-offset-8, + table.body th.small-offset-8 { + margin-left: 66.66667% !important; + Margin-left: 66.66667% !important; + } + table.body td.small-offset-9, + table.body th.small-offset-9 { + margin-left: 75% !important; + Margin-left: 75% !important; + } + table.body td.small-offset-10, + table.body th.small-offset-10 { + margin-left: 83.33333% !important; + Margin-left: 83.33333% !important; + } + table.body td.small-offset-11, + table.body th.small-offset-11 { + margin-left: 91.66667% !important; + Margin-left: 91.66667% !important; + } + table.body table.columns td.expander, + table.body table.columns th.expander { + display: none !important; + } + table.body .right-text-pad, + table.body .text-pad-right { + padding-left: 10px !important; + } + table.body .left-text-pad, + table.body .text-pad-left { + padding-right: 10px !important; + } + table.menu { + width: 100% !important; + } + table.menu td, + table.menu th { + width: auto !important; + display: inline-block !important; + } + table.menu.vertical td, + table.menu.vertical th, + table.menu.small-vertical td, + table.menu.small-vertical th { + display: block !important; + } + table.menu[align="center"] { + width: auto !important; + } + table.button.small-expand, + table.button.small-expanded { + width: 100% !important; + } + table.button.small-expand table, + table.button.small-expanded table { + width: 100%; + } + table.button.small-expand table a, + table.button.small-expanded table a { + text-align: center !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; + } + table.button.small-expand center, + table.button.small-expanded center { + min-width: 0; + } +} diff --git a/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.html.twig b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.html.twig new file mode 100644 index 0000000..28a62de --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.html.twig @@ -0,0 +1,67 @@ +{% apply inky_to_html|inline_css %} + + + + + + + + + + + + {% block lead %} + {% if importance is not null %}{{ importance|upper }}{% endif %} +

    + {{ email.subject }} +

    + {% endblock %} + + {% block content %} + {% if markdown %} + {{ include('@email/zurb_2/notification/content_markdown.html.twig') }} + {% else %} + {{ raw ? content|raw : content|nl2br }} + {% endif %} + {% endblock %} + + {% block action %} + {% if action_url %} + + + {% endif %} + {% endblock %} + + {% block exception %} + {% if exception %} + +

    Exception stack trace attached.

    + {% endif %} + {% endblock %} +
    +
    + + + + {% block footer %} + {% if footer_text is defined and footer_text is not null %} + + + {% block footer_content %} +

    {{ footer_text }}

    + {% endblock %} +
    +
    + {% endif %} + {% endblock %} +
    +
    +
    + + +{% endapply %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.txt.twig b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.txt.twig new file mode 100644 index 0000000..c98bb08 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.txt.twig @@ -0,0 +1,20 @@ +{% block lead %} +{{ email.subject }} +{% endblock %} + +{% block content %} +{{ content }} +{% endblock %} + +{% block action %} +{% if action_url %} +{{ action_text }}: {{ action_url }} +{% endif %} +{% endblock %} + +{% block exception %} +{% if exception %} +Exception stack trace attached. +{{ exception }} +{% endif %} +{% endblock %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/content_markdown.html.twig b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/content_markdown.html.twig new file mode 100644 index 0000000..120b2ca --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/content_markdown.html.twig @@ -0,0 +1 @@ +{{ content|markdown_to_html }} diff --git a/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/local.css b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/local.css new file mode 100644 index 0000000..2e68dcd --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/local.css @@ -0,0 +1,19 @@ +body { + background: #f3f3f3; +} + +.wrapper.secondary { + background: #f3f3f3; +} + +.container.body_alert { + border-top: 8px solid #ec5840; +} + +.container.body_warning { + border-top: 8px solid #ffae00; +} + +.container.body_default { + border-top: 8px solid #aaaaaa; +} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig new file mode 100644 index 0000000..49cd804 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig @@ -0,0 +1,71 @@ +{% use "bootstrap_3_layout.html.twig" %} + +{% block form_start -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-horizontal')|trim}) %} + {{- parent() -}} +{%- endblock form_start %} + +{# Labels #} + +{% block form_label -%} + {%- if label is same as(false) -%} +
    + {%- else -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%} + {{- parent() -}} + {%- endif -%} +{%- endblock form_label %} + +{% block form_label_class -%} +col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + {{- form_label(form) -}} +
    + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
    +{##} +{%- endblock form_row %} + +{% block submit_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} +
    {#--#} + +{%- endblock submit_row %} + +{% block reset_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} +
    {#--#} + +{%- endblock reset_row %} + +{% block form_group_class -%} +col-sm-10 +{%- endblock form_group_class %} + +{% block checkbox_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
    {#--#} + +{%- endblock checkbox_row %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig new file mode 100644 index 0000000..f4e313b --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig @@ -0,0 +1,220 @@ +{% use "bootstrap_base_layout.html.twig" %} + +{# Widgets #} + +{% block form_widget_simple -%} + {% if type is not defined or type not in ['file', 'hidden'] %} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block button_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) -%} + {{- parent() -}} +{%- endblock button_widget %} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
    + {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
    + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block checkbox_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {% if 'checkbox-inline' in parent_label_class %} + {{- form_label(form, null, { widget: parent() }) -}} + {% else -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- endif -%} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'radio-inline' in parent_label_class -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- else -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- endif -%} +{%- endblock radio_widget %} + +{% block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed %} + +{# Labels #} + +{% block form_label -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' control-label')|trim}) -%} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} + {{- block('form_label') -}} +{% endblock %} + +{% block checkbox_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label -%} + {# Do not display the label if widget is not defined in order to prevent double label rendering #} + {%- if widget is defined -%} + {%- if required -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} + {%- endif -%} + {%- if parent_label_class is defined -%} + {% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%} + {% endif %} + {%- if label is not same as(false) and label is empty -%} + {%- if label_format is not empty -%} + {%- set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) -%} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + + {#- if statement must be kept on the same line, to force the space between widget and label -#} + {{- widget|raw }} {% if label is not same as(false) -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{ label -}} + {%- else -%} + {{ label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{ label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{ label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {%- endif -%} + + {%- endif -%} +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + {{- form_label(form) }} {# -#} + {{ form_widget(form, widget_attr) }} {# -#} + {{- form_help(form) -}} + {{ form_errors(form) }} {# -#} + {# -#} +{%- endblock form_row %} + +{% block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} + + {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} + +{%- endblock checkbox_row %} + +{% block radio_row -%} + + {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} + +{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form is not rootform %}{% else %}
    {% endif %} +
      + {%- for error in errors -%} +
    • {{ error.message }}
    • + {%- endfor -%} +
    + {% if form is not rootform %}{% else %}
    {% endif %} + {%- endif %} +{%- endblock form_errors %} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-block')|trim}) -%} + + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + + {%- endif -%} +{%- endblock form_help %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig new file mode 100644 index 0000000..990b324 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig @@ -0,0 +1,88 @@ +{% use "bootstrap_4_layout.html.twig" %} + +{# Labels #} + +{% block form_label -%} + {%- if label is same as(false) -%} +
    + {%- else -%} + {%- if expanded is not defined or not expanded -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%} + {{- parent() -}} + {%- endif -%} +{%- endblock form_label %} + +{% block form_label_class -%} +col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} + {%- if expanded is defined and expanded -%} + {{ block('fieldset_form_row') }} + {%- else -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + {{- form_label(form) -}} +
    + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} +
    + {##} + {%- endif -%} +{%- endblock form_row %} + +{% block fieldset_form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + +
    + {{- form_label(form) -}} +
    + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
    +
    +{##} +{%- endblock fieldset_form_row %} + +{% block submit_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} +
    {#--#} + +{%- endblock submit_row %} + +{% block reset_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} +
    {#--#} + +{%- endblock reset_row %} + +{% block form_group_class -%} +col-sm-10 +{%- endblock form_group_class %} + +{% block checkbox_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} + {{- form_help(form) -}} +
    {#--#} + +{%- endblock checkbox_row %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig new file mode 100644 index 0000000..458cc68 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig @@ -0,0 +1,319 @@ +{% use "bootstrap_base_layout.html.twig" %} + +{# Widgets #} + +{% block money_widget -%} + {%- set prepend = not (money_pattern starts with '{{') -%} + {%- set append = not (money_pattern ends with '}}') -%} + {%- if prepend or append -%} +
    + {%- if prepend -%} +
    + {{ money_pattern|form_encode_currency }} +
    + {%- endif -%} + {{- block('form_widget_simple') -}} + {%- if append -%} +
    + {{ money_pattern|form_encode_currency }} +
    + {%- endif -%} +
    + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock money_widget %} + +{% block datetime_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {{- parent() -}} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {{- parent() -}} +{%- endblock date_widget %} + +{% block time_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {{- parent() -}} +{%- endblock time_widget %} + +{% block dateinterval_widget -%} + {%- if widget != 'single_text' and not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} + {% set valid = true %} + {%- endif -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
    + {%- if with_years -%} +
    + {{ form_label(form.years) }} + {{ form_widget(form.years) }} +
    + {%- endif -%} + {%- if with_months -%} +
    + {{ form_label(form.months) }} + {{ form_widget(form.months) }} +
    + {%- endif -%} + {%- if with_weeks -%} +
    + {{ form_label(form.weeks) }} + {{ form_widget(form.weeks) }} +
    + {%- endif -%} + {%- if with_days -%} +
    + {{ form_label(form.days) }} + {{ form_widget(form.days) }} +
    + {%- endif -%} + {%- if with_hours -%} +
    + {{ form_label(form.hours) }} + {{ form_widget(form.hours) }} +
    + {%- endif -%} + {%- if with_minutes -%} +
    + {{ form_label(form.minutes) }} + {{ form_widget(form.minutes) }} +
    + {%- endif -%} + {%- if with_seconds -%} +
    + {{ form_label(form.seconds) }} + {{ form_widget(form.seconds) }} +
    + {%- endif -%} + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
    + {%- endif -%} +{%- endblock dateinterval_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
    + {{- block('form_widget_simple') -}} +
    + {{ symbol|default('%') }} +
    +
    + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block file_widget -%} + <{{ element|default('div') }} class="custom-file"> + {%- set type = type|default('file') -%} + {%- set input_lang = 'en' -%} + {% if app is defined and app.request is defined %}{%- set input_lang = app.request.locale -%}{%- endif -%} + {%- set attr = {lang: input_lang} | merge(attr) -%} + {{- block('form_widget_simple') -}} + {%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim })|filter((value, key) => key != 'id') -%} + + +{% endblock %} + +{% block form_widget_simple -%} + {%- if type is not defined or type != 'hidden' -%} + {%- set className = ' form-control' -%} + {%- if type|default('') == 'file' -%} + {%- set className = ' custom-file-input' -%} + {%- elseif type|default('') == 'range' -%} + {%- set className = ' form-control-range' -%} + {%- endif -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ className)|trim}) -%} + {%- endif -%} + {%- if type is defined and (type == 'range' or type == 'color') %} + {# Attribute "required" is not supported #} + {%- set required = false -%} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block widget_attributes -%} + {%- if not valid -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %} + {%- endif -%} + {{ parent() }} +{%- endblock widget_attributes %} + +{% block button_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%} + {{- parent() -}} +{%- endblock button_widget %} + +{% block submit_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-primary'))|trim}) -%} + {{- parent() -}} +{%- endblock submit_widget %} + +{% block checkbox_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'checkbox-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- elseif 'switch-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- endif -%} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'radio-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- endif -%} +{%- endblock radio_widget %} + +{% block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed %} + +{% block choice_widget_expanded -%} +
    + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + valid: valid, + }) -}} + {% endfor -%} +
    +{%- endblock choice_widget_expanded %} + +{# Labels #} + +{% block form_label -%} + {% if label is not same as(false) -%} + {%- if compound is defined and compound -%} + {%- set element = 'legend' -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- else -%} + {%- set label_attr = label_attr|merge({for: id}) -%} + {%- endif -%} + {% if required -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {%- endif -%} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> + {{- block('form_label_content') -}} + {% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %} + {%- else -%} + {%- if errors|length > 0 -%} +
    + {{- form_errors(form) -}} +
    + {%- endif -%} + {%- endif -%} +{%- endblock form_label %} + +{% block checkbox_radio_label -%} + {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} + {%- if widget is defined -%} + {% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class or 'switch-custom' in parent_label_class) %} + {% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class or 'switch-custom' in label_attr.class) %} + {%- if is_parent_custom or is_custom -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' custom-control-label')|trim}) -%} + {%- else %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%} + {%- endif %} + {%- if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {%- if required -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} + {%- endif -%} + {%- if parent_label_class is defined -%} + {% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%} + {% endif %} + + {{ widget|raw }} + + {%- if label is not same as(false) -%} + {{- block('form_label_content') -}} + {%- endif -%} + {{- form_errors(form) -}} + + {%- endif -%} +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} + {%- if compound is defined and compound -%} + {%- set element = 'fieldset' -%} + {%- endif -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + <{{ element|default('div') }}{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group')|trim})} %}{{ block('attributes') }}{% endwith %}> + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + +{%- endblock form_row %} + +{# Errors #} + +{% block form_errors -%} + {%- if errors|length > 0 -%} + + {%- for error in errors -%} + + {{ 'Error'|trans({}, 'validators') }} {{ error.message }} + + {%- endfor -%} + + {%- endif %} +{%- endblock form_errors %} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' form-text text-muted')|trim}) -%} + + {{- block('form_help_content') -}} + + {%- endif -%} +{%- endblock form_help %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_horizontal_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_horizontal_layout.html.twig new file mode 100644 index 0000000..3c24166 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_horizontal_layout.html.twig @@ -0,0 +1,130 @@ +{% use "bootstrap_5_layout.html.twig" %} + +{# Labels #} + +{% block form_label -%} + {%- if label is same as(false) -%} +
    + {%- else -%} + {%- set row_class = row_class|default(row_attr.class|default('')) -%} + {%- if 'form-floating' not in row_class and 'input-group' not in row_class -%} + {%- if expanded is not defined or not expanded -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%} + {%- endif -%} + {{- parent() -}} + {%- endif -%} +{%- endblock form_label %} + +{% block form_label_class -%} + col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} + {%- if expanded is defined and expanded -%} + {{ block('fieldset_form_row') }} + {%- else -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {%- set row_class = row_class|default(row_attr.class|default('mb-3')) -%} + {%- set is_form_floating = is_form_floating|default('form-floating' in row_class) -%} + {%- set is_input_group = is_input_group|default('input-group' in row_class) -%} + {#- Remove behavior class from the main container -#} + {%- set row_class = row_class|replace({'form-floating': '', 'input-group': ''}) -%} + + {%- if is_form_floating or is_input_group -%} +
    +
    + {%- if is_form_floating -%} +
    + {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} +
    + {%- elseif is_input_group -%} +
    + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {#- Hack to properly display help with input group -#} + {{- form_help(form) -}} +
    + {%- endif -%} + {%- if not is_input_group -%} + {{- form_help(form) -}} + {%- endif -%} + {{- form_errors(form) -}} +
    + {%- else -%} + {{- form_label(form) -}} +
    + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
    + {%- endif -%} + {##} + {%- endif -%} +{%- endblock form_row %} + +{% block fieldset_form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + +
    + {{- form_label(form) -}} +
    + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
    +
    + +{%- endblock fieldset_form_row %} + +{% block submit_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} +
    {#--#} + +{%- endblock submit_row %} + +{% block reset_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} +
    {#--#} + +{%- endblock reset_row %} + +{% block button_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} +
    {#--#} + +{%- endblock button_row %} + +{% block checkbox_row -%} + {#--#} +
    {#--#} +
    + {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
    {#--#} + +{%- endblock checkbox_row %} + +{% block form_group_class -%} + col-sm-10 +{%- endblock form_group_class %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_layout.html.twig new file mode 100644 index 0000000..17b28fc --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_5_layout.html.twig @@ -0,0 +1,374 @@ +{% use "bootstrap_base_layout.html.twig" %} + +{# Widgets #} + +{% block money_widget -%} + {%- set prepend = not (money_pattern starts with '{{') -%} + {%- set append = not (money_pattern ends with '}}') -%} + {%- if prepend or append -%} +
    + {%- if prepend -%} + {{ money_pattern|form_encode_currency }} + {%- endif -%} + {{- block('form_widget_simple') -}} + {%- if append -%} + {{ money_pattern|form_encode_currency }} + {%- endif -%} +
    + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock money_widget %} + +{% block date_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} + {%- if datetime is not defined or not datetime -%} +
    + {%- endif %} + {%- if label is not same as(false) -%} +
    + {{- form_label(form.year) -}} + {{- form_label(form.month) -}} + {{- form_label(form.day) -}} +
    + {%- endif -%} +
    + {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
    + {%- if datetime is not defined or not datetime -%} +
    + {%- endif -%} + {%- endif -%} +{%- endblock date_widget %} + +{% block time_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} + {%- if datetime is not defined or false == datetime -%} +
    + {%- endif -%} + {%- if label is not same as(false) -%} +
    + {{- form_label(form.hour) -}} + {%- if with_minutes -%}{{ form_label(form.minute) }}{%- endif -%} + {%- if with_seconds -%}{{ form_label(form.second) }}{%- endif -%} +
    + {%- endif -%} + {% if with_minutes or with_seconds %} +
    + {% endif %} + {{- form_widget(form.hour) -}} + {%- if with_minutes -%} + : + {{- form_widget(form.minute) -}} + {%- endif -%} + {%- if with_seconds -%} + : + {{- form_widget(form.second) -}} + {%- endif -%} + {% if with_minutes or with_seconds %} +
    + {% endif %} + {%- if datetime is not defined or false == datetime -%} +
    + {%- endif -%} + {%- endif -%} +{%- endblock time_widget %} + +{% block datetime_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} +
    + {{- form_widget(form.date, { datetime: true } ) -}} + {{- form_errors(form.date) -}} + {{- form_widget(form.time, { datetime: true } ) -}} + {{- form_errors(form.time) -}} +
    + {%- endif -%} +{%- endblock datetime_widget %} + +{% block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} +
    + {%- if with_years -%} +
    + {{ form_label(form.years) }} + {{ form_widget(form.years) }} +
    + {%- endif -%} + {%- if with_months -%} +
    + {{ form_label(form.months) }} + {{ form_widget(form.months) }} +
    + {%- endif -%} + {%- if with_weeks -%} +
    + {{ form_label(form.weeks) }} + {{ form_widget(form.weeks) }} +
    + {%- endif -%} + {%- if with_days -%} +
    + {{ form_label(form.days) }} + {{ form_widget(form.days) }} +
    + {%- endif -%} + {%- if with_hours -%} +
    + {{ form_label(form.hours) }} + {{ form_widget(form.hours) }} +
    + {%- endif -%} + {%- if with_minutes -%} +
    + {{ form_label(form.minutes) }} + {{ form_widget(form.minutes) }} +
    + {%- endif -%} + {%- if with_seconds -%} +
    + {{ form_label(form.seconds) }} + {{ form_widget(form.seconds) }} +
    + {%- endif -%} + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
    + {%- endif -%} +{%- endblock dateinterval_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
    + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
    + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block form_widget_simple -%} + {%- if type is not defined or type != 'hidden' %} + {%- set widget_class = ' form-control' %} + {%- if type|default('') == 'color' -%} + {%- set widget_class = widget_class ~ ' form-control-color' -%} + {%- elseif type|default('') == 'range' -%} + {%- set widget_class = ' form-range' -%} + {%- endif -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ widget_class)|trim}) -%} + {% endif -%} + {%- if type is defined and type in ['range', 'color'] %} + {# Attribute "required" is not supported #} + {% set required = false %} + {% endif -%} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{%- block widget_attributes -%} + {%- if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %} + {% endif -%} + {{ parent() }} +{%- endblock widget_attributes -%} + +{%- block button_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%} + {{- parent() -}} +{%- endblock button_widget %} + +{%- block submit_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-primary'))|trim}) -%} + {{- parent() -}} +{%- endblock submit_widget %} + +{%- block checkbox_widget -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'checkbox-inline' in parent_label_class %} + {%- set row_class = row_class ~ ' form-check-inline' -%} + {% endif -%} + {%- if 'checkbox-switch' in parent_label_class %} + {%- set row_class = row_class ~ ' form-switch' -%} + {% endif -%} + {%- if row_class is not empty -%} +
    + {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
    + {%- endif -%} +{%- endblock checkbox_widget %} + +{%- block radio_widget -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if 'radio-inline' in parent_label_class -%} + {%- set row_class = row_class ~ ' form-check-inline' -%} + {%- endif -%} + {%- if row_class is not empty -%} +
    + {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
    + {%- endif -%} +{%- endblock radio_widget %} + +{%- block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-select')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_expanded -%} +
    + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + valid: valid, + }) -}} + {% endfor -%} +
    +{%- endblock choice_widget_expanded %} + +{# Labels #} + +{%- block form_label -%} + {% if label is not same as(false) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if compound is defined and compound -%} + {%- set element = 'legend' -%} + {%- if 'col-form-label' not in parent_label_class -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label' )|trim}) -%} + {%- endif -%} + {%- else -%} + {%- set row_class = row_class|default(row_attr.class|default('')) -%} + {%- set label_attr = label_attr|merge({for: id}) -%} + {%- if 'col-form-label' not in parent_label_class -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ('input-group' in row_class ? ' input-group-text' : ' form-label') )|trim}) -%} + {%- endif -%} + {%- endif -%} + {%- endif -%} + {{- parent() -}} +{%- endblock form_label %} + +{%- block checkbox_radio_label -%} + {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} + {%- if widget is defined -%} + {%- set label_attr_class = label_attr_class|default(label_attr.class|default('')) -%} + {%- if 'btn' not in label_attr_class -%} + {%- set label_attr_class = label_attr_class ~ ' form-check-label' -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: label_attr_class|trim}) -%} + {%- if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {%- if required -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} + {%- endif -%} + {%- if parent_label_class is defined -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} + {%- endif -%} + + {{ widget|raw }} + + {%- if label is not same as(false) -%} + {{- block('form_label_content') -}} + {%- endif -%} + + {%- endif -%} +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{%- block form_row -%} + {%- if compound is defined and compound -%} + {%- set element = 'fieldset' -%} + {%- endif -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {%- set row_class = row_class|default(row_attr.class|default('mb-3')|trim) -%} + <{{ element|default('div') }}{% with {attr: row_attr|merge({class: row_class})} %}{{ block('attributes') }}{% endwith %}> + {%- if 'form-floating' in row_class -%} + {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} + {%- else -%} + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {%- endif -%} + {{- form_help(form) -}} + {{- form_errors(form) -}} + +{%- endblock form_row %} + +{%- block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row %} + +{# Errors #} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} + {%- for error in errors -%} +
    {{ error.message }}
    + {%- endfor -%} + {%- endif %} +{%- endblock form_errors %} + +{# Help #} + +{%- block form_help -%} + {%- set row_class = row_attr.class|default('') -%} + {%- set help_class = ' form-text' -%} + {%- if 'input-group' in row_class -%} + {#- Hack to properly display help with input group -#} + {%- set help_class = ' input-group-text' -%} + {%- endif -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ help_class ~ ' mb-0')|trim}) -%} + {%- endif -%} + {{- parent() -}} +{%- endblock form_help %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig new file mode 100644 index 0000000..e8b9318 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig @@ -0,0 +1,208 @@ +{% use "form_div_layout.html.twig" %} + +{# Widgets #} + +{% block textarea_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
    + {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
    + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
    + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
    + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block datetime_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
    + {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + +
    + {%- if form.date.year is defined %}{{ form_label(form.date.year) }}{% endif -%} + {%- if form.date.month is defined %}{{ form_label(form.date.month) }}{% endif -%} + {%- if form.date.day is defined %}{{ form_label(form.date.day) }}{% endif -%} + {%- if form.time.hour is defined %}{{ form_label(form.time.hour) }}{% endif -%} + {%- if form.time.minute is defined %}{{ form_label(form.time.minute) }}{% endif -%} + {%- if form.time.second is defined %}{{ form_label(form.time.second) }}{% endif -%} +
    + + {{- form_widget(form.date, { datetime: true } ) -}} + {{- form_widget(form.time, { datetime: true } ) -}} +
    + {%- endif -%} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} + {%- if datetime is not defined or not datetime -%} +
    + {%- endif %} + {%- if label is not same as(false) -%} +
    + {{ form_label(form.year) }} + {{ form_label(form.month) }} + {{ form_label(form.day) }} +
    + {%- endif -%} + + {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} + {%- if datetime is not defined or not datetime -%} +
    + {%- endif -%} + {%- endif -%} +{%- endblock date_widget %} + +{% block time_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} + {%- if datetime is not defined or false == datetime -%} +
    + {%- endif -%} + {%- if label is not same as(false) -%}
    {{ form_label(form.hour) }}
    {%- endif -%} + {{- form_widget(form.hour) -}} + {%- if with_minutes -%}:{%- if label is not same as(false) -%}
    {{ form_label(form.minute) }}
    {%- endif -%}{{ form_widget(form.minute) }}{%- endif -%} + {%- if with_seconds -%}:{%- if label is not same as(false) -%}
    {{ form_label(form.second) }}
    {%- endif -%}{{ form_widget(form.second) }}{%- endif -%} + {%- if datetime is not defined or false == datetime -%} +
    + {%- endif -%} + {%- endif -%} +{%- endblock time_widget %} + +{%- block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
    + {{- form_errors(form) -}} +
    + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + +
    + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
    + {%- endif -%} +{%- endblock dateinterval_widget -%} + +{% block choice_widget_expanded -%} + {%- if '-inline' in label_attr.class|default('') -%} + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + }) -}} + {% endfor -%} + {%- else -%} +
    + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + }) -}} + {%- endfor -%} +
    + {%- endif -%} +{%- endblock choice_widget_expanded %} + +{# Labels #} + +{% block choice_label -%} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': '', 'switch-custom': ''})|trim}) -%} + {{- block('form_label') -}} +{% endblock choice_label %} + +{% block checkbox_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{# Rows #} + +{% block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row %} + +{% block choice_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock choice_row %} + +{% block date_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock date_row %} + +{% block time_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock time_row %} + +{% block datetime_row -%} + {%- set force_error = true -%} + {{- block('form_row') -}} +{%- endblock datetime_row %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig new file mode 100644 index 0000000..1e421d5 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig @@ -0,0 +1,488 @@ +{# Widgets #} + +{%- block form_widget -%} + {% if compound %} + {{- block('form_widget_compound') -}} + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock form_widget -%} + +{%- block form_widget_simple -%} + {%- set type = type|default('text') -%} + {%- if type == 'range' or type == 'color' -%} + {# Attribute "required" is not supported #} + {%- set required = false -%} + {%- endif -%} + +{%- endblock form_widget_simple -%} + +{%- block form_widget_compound -%} +
    + {%- if form is rootform -%} + {{ form_errors(form) }} + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
    +{%- endblock form_widget_compound -%} + +{%- block collection_widget -%} + {% if prototype is defined and not prototype.rendered %} + {%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%} + {% endif %} + {{- block('form_widget') -}} +{%- endblock collection_widget -%} + +{%- block textarea_widget -%} + +{%- endblock textarea_widget -%} + +{%- block choice_widget -%} + {% if expanded %} + {{- block('choice_widget_expanded') -}} + {% else %} + {{- block('choice_widget_collapsed') -}} + {% endif %} +{%- endblock choice_widget -%} + +{%- block choice_widget_expanded -%} +
    + {%- for child in form %} + {{- form_widget(child) -}} + {{- form_label(child, null, {translation_domain: choice_translation_domain}) -}} + {% endfor -%} +
    +{%- endblock choice_widget_expanded -%} + +{%- block choice_widget_collapsed -%} + {%- if required and placeholder is none and not placeholder_in_choices and not multiple and (attr.size is not defined or attr.size <= 1) -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_options -%} + {% for group_label, choice in options %} + {%- if choice is iterable -%} + + {% set options = choice %} + {{- block('choice_widget_options') -}} + + {%- else -%} + + {%- endif -%} + {% endfor %} +{%- endblock choice_widget_options -%} + +{%- block checkbox_widget -%} + +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + +{%- endblock radio_widget -%} + +{%- block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {%- else -%} +
    + {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + {{- form_widget(form.date) -}} + {{- form_widget(form.time) -}} +
    + {%- endif -%} +{%- endblock datetime_widget -%} + +{%- block date_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} +
    + {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
    + {%- endif -%} +{%- endblock date_widget -%} + +{%- block time_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
    + {{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %} +
    + {%- endif -%} +{%- endblock time_widget -%} + +{%- block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} +
    + {{- form_errors(form) -}} + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
    + {%- endif -%} +{%- endblock dateinterval_widget -%} + +{%- block number_widget -%} + {# type="number" doesn't work with floats in localized formats #} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }} +{%- endblock number_widget -%} + +{%- block integer_widget -%} + {%- set type = type|default('number') -%} + {{ block('form_widget_simple') }} +{%- endblock integer_widget -%} + +{%- block money_widget -%} + {{ money_pattern|form_encode_currency(block('form_widget_simple')) }} +{%- endblock money_widget -%} + +{%- block url_widget -%} + {%- set type = type|default('url') -%} + {{ block('form_widget_simple') }} +{%- endblock url_widget -%} + +{%- block search_widget -%} + {%- set type = type|default('search') -%} + {{ block('form_widget_simple') }} +{%- endblock search_widget -%} + +{%- block percent_widget -%} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }}{% if symbol %} {{ symbol|default('%') }}{% endif %} +{%- endblock percent_widget -%} + +{%- block password_widget -%} + {%- set type = type|default('password') -%} + {{ block('form_widget_simple') }} +{%- endblock password_widget -%} + +{%- block hidden_widget -%} + {%- set type = type|default('hidden') -%} + {{ block('form_widget_simple') }} +{%- endblock hidden_widget -%} + +{%- block email_widget -%} + {%- set type = type|default('email') -%} + {{ block('form_widget_simple') }} +{%- endblock email_widget -%} + +{%- block range_widget -%} + {% set type = type|default('range') %} + {{- block('form_widget_simple') -}} +{%- endblock range_widget %} + +{%- block button_widget -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- elseif label is not same as(false) -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + +{%- endblock button_widget -%} + +{%- block submit_widget -%} + {%- set type = type|default('submit') -%} + {{ block('button_widget') }} +{%- endblock submit_widget -%} + +{%- block reset_widget -%} + {%- set type = type|default('reset') -%} + {{ block('button_widget') }} +{%- endblock reset_widget -%} + +{%- block tel_widget -%} + {%- set type = type|default('tel') -%} + {{ block('form_widget_simple') }} +{%- endblock tel_widget -%} + +{%- block color_widget -%} + {%- set type = type|default('color') -%} + {{ block('form_widget_simple') }} +{%- endblock color_widget -%} + +{%- block week_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
    + {{ form_widget(form.year, vars) }}-{{ form_widget(form.week, vars) }} +
    + {%- endif -%} +{%- endblock week_widget -%} + +{# Labels #} + +{%- block form_label -%} + {% if label is not same as(false) -%} + {% if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {% if required -%} + {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} + {%- endif -%} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> + {{- block('form_label_content') -}} + + {%- endif -%} +{%- endblock form_label -%} + +{%- block form_label_content -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_label_content -%} + +{%- block button_label -%}{%- endblock -%} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%} + <{{ element|default('div') }} id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}> + {{- block('form_help_content') -}} + + {%- endif -%} +{%- endblock form_help %} + +{% block form_help_content -%} + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_help_content %} + +{# Rows #} + +{%- block repeated_row -%} + {# + No need to render the errors here, as all errors are mapped + to the first child (see RepeatedTypeValidatorExtension). + #} + {{- block('form_rows') -}} +{%- endblock repeated_row -%} + +{%- block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + {{- form_label(form) -}} + {{- form_errors(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + +{%- endblock form_row -%} + +{%- block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row -%} + +{%- block hidden_row -%} + {{ form_widget(form) }} +{%- endblock hidden_row -%} + +{# Misc #} + +{%- block form -%} + {{ form_start(form) }} + {{- form_widget(form) -}} + {{ form_end(form) }} +{%- endblock form -%} + +{%- block form_start -%} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} +{%- endblock form_start -%} + +{%- block form_end -%} + {%- if not render_rest is defined or render_rest -%} + {{ form_rest(form) }} + {%- endif -%} + +{%- endblock form_end -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} +
      + {%- for error in errors -%} +
    • {{ error.message }}
    • + {%- endfor -%} +
    + {%- endif -%} +{%- endblock form_errors -%} + +{%- block form_rest -%} + {% for child in form -%} + {% if not child.rendered %} + {{- form_row(child) -}} + {% endif %} + {%- endfor -%} + + {% if not form.methodRendered and form is rootform %} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} + {% endif -%} +{% endblock form_rest %} + +{# Support #} + +{%- block form_rows -%} + {% for child in form|filter(child => not child.rendered) %} + {{- form_row(child) -}} + {% endfor %} +{%- endblock form_rows -%} + +{%- block widget_attributes -%} + id="{{ id }}" name="{{ full_name }}" + {%- if disabled %} disabled="disabled"{% endif -%} + {%- if required %} required="required"{% endif -%} + {{ block('attributes') }} +{%- endblock widget_attributes -%} + +{%- block widget_container_attributes -%} + {%- if id is not empty %}id="{{ id }}"{% endif -%} + {{ block('attributes') }} +{%- endblock widget_container_attributes -%} + +{%- block button_attributes -%} + id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif -%} + {{ block('attributes') }} +{%- endblock button_attributes -%} + +{% block attributes -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ translation_domain is same as(false) or attrvalue is null ? attrvalue : attrvalue|trans(attr_translation_parameters, translation_domain) }}" + {%- elseif attrvalue is same as(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not same as(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} +{%- endblock attributes -%} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig new file mode 100644 index 0000000..00a51ab --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig @@ -0,0 +1,50 @@ +{% use "form_div_layout.html.twig" %} + +{%- block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + + {{- form_label(form) -}} + + + {{- form_errors(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + + +{%- endblock form_row -%} + +{%- block button_row -%} + + + + {{- form_widget(form) -}} + + +{%- endblock button_row -%} + +{%- block hidden_row -%} + {%- set style = row_attr.style is defined ? (row_attr.style ~ (row_attr.style|trim|last != ';' ? '; ')) -%} + + + {{- form_widget(form) -}} + + +{%- endblock hidden_row -%} + +{%- block form_widget_compound -%} + + {%- if form is rootform and errors|length > 0 -%} + + + + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
    + {{- form_errors(form) -}} +
    +{%- endblock form_widget_compound -%} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig new file mode 100644 index 0000000..23e463e --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig @@ -0,0 +1,350 @@ +{% extends "form_div_layout.html.twig" %} + +{# Based on Foundation 5 Doc #} +{# Widgets #} + +{% block form_widget_simple -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block textarea_widget -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock button_widget %} + +{% block money_widget -%} +
    + {% set prepend = '{{' == money_pattern[0:2] %} + {% if not prepend %} +
    + {{ money_pattern|form_encode_currency }} +
    + {% endif %} +
    + {{- block('form_widget_simple') -}} +
    + {% if prepend %} +
    + {{ money_pattern|form_encode_currency }} +
    + {% endif %} +
    +{%- endblock money_widget %} + +{% block percent_widget -%} +
    + {%- if symbol -%} +
    + {{- block('form_widget_simple') -}} +
    +
    + {{ symbol|default('%') }} +
    + {%- else -%} +
    + {{- block('form_widget_simple') -}} +
    + {%- endif -%} +
    +{%- endblock percent_widget %} + +{% block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} +
    +
    {{ form_errors(form.date) }}
    +
    {{ form_errors(form.time) }}
    +
    +
    +
    {{ form_widget(form.date, { datetime: true } ) }}
    +
    {{ form_widget(form.time, { datetime: true } ) }}
    +
    + {% endif %} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or not datetime %} +
    + {% endif %} + {{- date_pattern|replace({ + '{{ year }}': '
    ' ~ form_widget(form.year) ~ '
    ', + '{{ month }}': '
    ' ~ form_widget(form.month) ~ '
    ', + '{{ day }}': '
    ' ~ form_widget(form.day) ~ '
    ', + })|raw -}} + {% if datetime is not defined or not datetime %} +
    + {% endif %} + {% endif %} +{%- endblock date_widget %} + +{% block time_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or false == datetime %} +
    + {% endif %} + {% if with_seconds %} +
    {{ form_widget(form.hour) }}
    +
    +
    +
    + : +
    +
    + {{ form_widget(form.minute) }} +
    +
    +
    +
    +
    +
    + : +
    +
    + {{ form_widget(form.second) }} +
    +
    +
    + {% else %} +
    {{ form_widget(form.hour) }}
    +
    +
    +
    + : +
    +
    + {{ form_widget(form.minute) }} +
    +
    +
    + {% endif %} + {% if datetime is not defined or false == datetime %} +
    + {% endif %} + {% endif %} +{%- endblock time_widget %} + +{% block choice_widget_collapsed -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + + {% if multiple -%} + {% set attr = attr|merge({style: (attr.style|default('') ~ ' height: auto; background-image: none;')|trim}) %} + {% endif %} + + {% if required and placeholder is none and not placeholder_in_choices and not multiple -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed %} + +{% block choice_widget_expanded -%} + {% if '-inline' in label_attr.class|default('') %} +
      + {% for child in form %} +
    • {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }}
    • + {% endfor %} +
    + {% else %} +
    + {% for child in form %} + {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }} + {% endfor %} +
    + {% endif %} +{%- endblock choice_widget_expanded %} + +{% block checkbox_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if 'checkbox-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} +
    + {{ form_label(form, null, { widget: parent() }) }} +
    + {% endif %} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if 'radio-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} +
    + {{ form_label(form, null, { widget: parent() }) }} +
    + {% endif %} +{%- endblock radio_widget %} + +{# Labels #} + +{% block form_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {% set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) %} + {{- block('form_label') -}} +{%- endblock choice_label %} + +{% block checkbox_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label -%} + {% if required %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {% endif %} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {%- if parent_label_class is defined -%} + {% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%} + {% endif %} + {% if label is empty %} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {% endif %} + + {{ widget|raw }} + {%- if label is not same as(false) -%} + {{- block('form_label_content') -}} + {%- endif -%} + +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + +
    + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
    + +{%- endblock form_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} + +
    + {{ form_widget(form) }} + {{- form_help(form) -}} + {{ form_errors(form) }} +
    + +{%- endblock checkbox_row %} + +{% block radio_row -%} + +
    + {{ form_widget(form) }} + {{- form_help(form) -}} + {{ form_errors(form) }} +
    + +{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form is not rootform %}{% else %}
    {% endif %} + {%- for error in errors -%} + {{ error.message }} + {% if not loop.last %}, {% endif %} + {%- endfor -%} + {% if form is not rootform %}{% else %}
    {% endif %} + {%- endif %} +{%- endblock form_errors %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/foundation_6_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/foundation_6_layout.html.twig new file mode 100644 index 0000000..04ed730 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/foundation_6_layout.html.twig @@ -0,0 +1,50 @@ +{% extends "form_div_layout.html.twig" %} + +{%- block checkbox_row -%} + {%- set parent_class = parent_class|default(attr.class|default('')) -%} + {%- if 'switch-input' in parent_class -%} + {{- form_label(form) -}} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' switch-input')|trim}) -%} + {{- form_widget(form) -}} + + {{- form_errors(form) -}} + {%- else -%} + {{- block('form_row') -}} + {%- endif -%} +{%- endblock checkbox_row -%} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
    + {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group-field')|trim}) %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
    + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
    + {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group-field')|trim}) %} + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
    + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock button_widget %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig new file mode 100644 index 0000000..7f31e70 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig @@ -0,0 +1,69 @@ +{% use 'form_div_layout.html.twig' %} + +{%- block form_row -%} + {%- set row_attr = row_attr|merge({ class: row_attr.class|default(row_class|default('mb-6')) }) -%} + {{- parent() -}} +{%- endblock form_row -%} + +{%- block widget_attributes -%} + {%- set attr = attr|merge({ class: attr.class|default(widget_class|default('mt-1 w-full')) ~ (disabled ? ' ' ~ widget_disabled_class|default('border-gray-300 text-gray-500')) ~ (errors|length ? ' ' ~ widget_errors_class|default('border-red-700')) }) -%} + {{- parent() -}} +{%- endblock widget_attributes -%} + +{%- block form_label -%} + {%- set label_attr = label_attr|merge({ class: label_attr.class|default(label_class|default('block text-gray-800')) }) -%} + {{- parent() -}} +{%- endblock form_label -%} + +{%- block form_help -%} + {%- set help_attr = help_attr|merge({ class: help_attr.class|default(help_class|default('mt-1 text-gray-600')) }) -%} + {{- parent() -}} +{%- endblock form_help -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} +
      + {%- for error in errors -%} +
    • {{ error.message }}
    • + {%- endfor -%} +
    + {%- endif -%} +{%- endblock form_errors -%} + +{%- block choice_widget_expanded -%} + {%- set attr = attr|merge({ class: attr.class|default('mt-2') }) -%} +
    + {%- for child in form %} +
    + {{- form_widget(child) -}} + {{- form_label(child, null, { translation_domain: choice_translation_domain }) -}} +
    + {% endfor -%} +
    +{%- endblock choice_widget_expanded -%} + +{%- block checkbox_row -%} + {%- set row_attr = row_attr|merge({ class: row_attr.class|default(row_class|default('mb-6')) }) -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + {{- form_errors(form) -}} +
    + {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} +
    + {{- form_help(form) -}} + +{%- endblock checkbox_row -%} + +{%- block checkbox_widget -%} + {%- set widget_class = widget_class|default('mr-2') -%} + {{- parent() -}} +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + {%- set widget_class = widget_class|default('mr-2') -%} + {{- parent() -}} +{%- endblock radio_widget -%} diff --git a/vendor/symfony/twig-bridge/Test/FormLayoutTestCase.php b/vendor/symfony/twig-bridge/Test/FormLayoutTestCase.php new file mode 100644 index 0000000..1fdd83c --- /dev/null +++ b/vendor/symfony/twig-bridge/Test/FormLayoutTestCase.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Test; + +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Test\Traits\RuntimeLoaderProvider; +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\FormRendererInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Test\FormIntegrationTestCase; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; + +/** + * @author Romain Monteil + */ +abstract class FormLayoutTestCase extends FormIntegrationTestCase +{ + use RuntimeLoaderProvider; + + protected FormRendererInterface $renderer; + + protected function setUp(): void + { + parent::setUp(); + + $loader = new FilesystemLoader($this->getTemplatePaths()); + + $environment = new Environment($loader, ['strict_variables' => true]); + $environment->setExtensions($this->getTwigExtensions()); + + foreach ($this->getTwigGlobals() as $name => $value) { + $environment->addGlobal($name, $value); + } + + $rendererEngine = new TwigRendererEngine($this->getThemes(), $environment); + $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); + $this->registerTwigRuntimeLoader($environment, $this->renderer); + } + + protected function assertMatchesXpath($html, $expression, $count = 1): void + { + $dom = new \DOMDocument('UTF-8'); + + try { + // Wrap in node so we can load HTML with multiple tags at + // the top level + $dom->loadXML(''.$html.''); + } catch (\Exception $e) { + $this->fail(sprintf( + "Failed loading HTML:\n\n%s\n\nError: %s", + $html, + $e->getMessage() + )); + } + $xpath = new \DOMXPath($dom); + $nodeList = $xpath->evaluate('/root'.$expression); + + if ($nodeList->length != $count) { + $dom->formatOutput = true; + $this->fail(sprintf( + "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s", + $expression, + 1 == $count ? 'once' : $count.' times', + 1 == $nodeList->length ? 'once' : $nodeList->length.' times', + // strip away and + substr($dom->saveHTML(), 6, -8) + )); + } else { + $this->addToAssertionCount(1); + } + } + + abstract protected function getTemplatePaths(): array; + + abstract protected function getTwigExtensions(): array; + + protected function getTwigGlobals(): array + { + return []; + } + + abstract protected function getThemes(): array; + + protected function renderForm(FormView $view, array $vars = []): string + { + return $this->renderer->renderBlock($view, 'form', $vars); + } + + protected function renderLabel(FormView $view, $label = null, array $vars = []): string + { + if (null !== $label) { + $vars += ['label' => $label]; + } + + return $this->renderer->searchAndRenderBlock($view, 'label', $vars); + } + + protected function renderHelp(FormView $view): string + { + return $this->renderer->searchAndRenderBlock($view, 'help'); + } + + protected function renderErrors(FormView $view): string + { + return $this->renderer->searchAndRenderBlock($view, 'errors'); + } + + protected function renderWidget(FormView $view, array $vars = []): string + { + return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); + } + + protected function renderRow(FormView $view, array $vars = []): string + { + return $this->renderer->searchAndRenderBlock($view, 'row', $vars); + } + + protected function renderRest(FormView $view, array $vars = []): string + { + return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); + } + + protected function renderStart(FormView $view, array $vars = []): string + { + return $this->renderer->renderBlock($view, 'form_start', $vars); + } + + protected function renderEnd(FormView $view, array $vars = []): string + { + return $this->renderer->renderBlock($view, 'form_end', $vars); + } + + protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true): void + { + $this->renderer->setTheme($view, $themes, $useDefaultThemes); + } +} diff --git a/vendor/symfony/twig-bridge/Test/Traits/RuntimeLoaderProvider.php b/vendor/symfony/twig-bridge/Test/Traits/RuntimeLoaderProvider.php new file mode 100644 index 0000000..52f84a7 --- /dev/null +++ b/vendor/symfony/twig-bridge/Test/Traits/RuntimeLoaderProvider.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Test\Traits; + +use Symfony\Component\Form\FormRenderer; +use Twig\Environment; +use Twig\RuntimeLoader\RuntimeLoaderInterface; + +trait RuntimeLoaderProvider +{ + protected function registerTwigRuntimeLoader(Environment $environment, FormRenderer $renderer) + { + $loader = $this->createMock(RuntimeLoaderInterface::class); + $loader->expects($this->any())->method('load')->willReturnMap([ + ['Symfony\Component\Form\FormRenderer', $renderer], + ]); + $environment->addRuntimeLoader($loader); + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/DumpTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/DumpTokenParser.php new file mode 100644 index 0000000..d4996db --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/DumpTokenParser.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\DumpNode; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'dump' tag. + * + * Dump variables with: + * + * {% dump %} + * {% dump foo %} + * {% dump foo, bar %} + * + * @author Julien Galenski + */ +final class DumpTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $values = null; + if (!$this->parser->getStream()->test(Token::BLOCK_END_TYPE)) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + } + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new DumpNode($this->parser->getVarName(), $values, $token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'dump'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php new file mode 100644 index 0000000..b95a2a0 --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\FormThemeNode; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'form_theme' tag. + * + * @author Fabien Potencier + */ +final class FormThemeTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + $form = $this->parser->getExpressionParser()->parseExpression(); + $only = false; + + if ($this->parser->getStream()->test(Token::NAME_TYPE, 'with')) { + $this->parser->getStream()->next(); + $resources = $this->parser->getExpressionParser()->parseExpression(); + + if ($this->parser->getStream()->nextIf(Token::NAME_TYPE, 'only')) { + $only = true; + } + } else { + $resources = new ArrayExpression([], $stream->getCurrent()->getLine()); + do { + $resources->addElement($this->parser->getExpressionParser()->parseExpression()); + } while (!$stream->test(Token::BLOCK_END_TYPE)); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new FormThemeNode($form, $resources, $lineno, $this->getTag(), $only); + } + + public function getTag(): string + { + return 'form_theme'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php new file mode 100644 index 0000000..810e7c2 --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\StopwatchNode; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the stopwatch tag. + * + * @author Wouter J + */ +final class StopwatchTokenParser extends AbstractTokenParser +{ + public function __construct( + private bool $stopwatchIsAvailable, + ) { + } + + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + // {% stopwatch 'bar' %} + $name = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(Token::BLOCK_END_TYPE); + + // {% endstopwatch %} + $body = $this->parser->subparse($this->decideStopwatchEnd(...), true); + $stream->expect(Token::BLOCK_END_TYPE); + + if ($this->stopwatchIsAvailable) { + return new StopwatchNode($name, $body, new AssignNameExpression($this->parser->getVarName(), $token->getLine()), $lineno, $this->getTag()); + } + + return $body; + } + + public function decideStopwatchEnd(Token $token): bool + { + return $token->test('endstopwatch'); + } + + public function getTag(): string + { + return 'stopwatch'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php new file mode 100644 index 0000000..c6d850d --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'trans_default_domain' tag. + * + * @author Fabien Potencier + */ +final class TransDefaultDomainTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new TransDefaultDomainNode($expr, $token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'trans_default_domain'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/TransTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/TransTokenParser.php new file mode 100644 index 0000000..e60263a --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/TransTokenParser.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Error\SyntaxError; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Node; +use Twig\Node\TextNode; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'trans' tag. + * + * @author Fabien Potencier + */ +final class TransTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + $count = null; + $vars = new ArrayExpression([], $lineno); + $domain = null; + $locale = null; + if (!$stream->test(Token::BLOCK_END_TYPE)) { + if ($stream->test('count')) { + // {% trans count 5 %} + $stream->next(); + $count = $this->parser->getExpressionParser()->parseExpression(); + } + + if ($stream->test('with')) { + // {% trans with vars %} + $stream->next(); + $vars = $this->parser->getExpressionParser()->parseExpression(); + } + + if ($stream->test('from')) { + // {% trans from "messages" %} + $stream->next(); + $domain = $this->parser->getExpressionParser()->parseExpression(); + } + + if ($stream->test('into')) { + // {% trans into "fr" %} + $stream->next(); + $locale = $this->parser->getExpressionParser()->parseExpression(); + } elseif (!$stream->test(Token::BLOCK_END_TYPE)) { + throw new SyntaxError('Unexpected token. Twig was looking for the "with", "from", or "into" keyword.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + // {% trans %}message{% endtrans %} + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse($this->decideTransFork(...), true); + + if (!$body instanceof TextNode && !$body instanceof AbstractExpression) { + throw new SyntaxError('A message inside a trans tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext()); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new TransNode($body, $domain, $count, $vars, $locale, $lineno, $this->getTag()); + } + + public function decideTransFork(Token $token): bool + { + return $token->test(['endtrans']); + } + + public function getTag(): string + { + return 'trans'; + } +} diff --git a/vendor/symfony/twig-bridge/Translation/TwigExtractor.php b/vendor/symfony/twig-bridge/Translation/TwigExtractor.php new file mode 100644 index 0000000..a4b4bbe --- /dev/null +++ b/vendor/symfony/twig-bridge/Translation/TwigExtractor.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Translation; + +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\Extractor\AbstractFileExtractor; +use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Source; + +/** + * TwigExtractor extracts translation messages from a twig template. + * + * @author Michel Salib + * @author Fabien Potencier + */ +class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + /** + * Default domain for found messages. + */ + private string $defaultDomain = 'messages'; + + /** + * Prefix for found message. + */ + private string $prefix = ''; + + public function __construct( + private Environment $twig, + ) { + } + + public function extract($resource, MessageCatalogue $catalogue): void + { + foreach ($this->extractFiles($resource) as $file) { + try { + $this->extractTemplate(file_get_contents($file->getPathname()), $catalogue); + } catch (Error) { + // ignore errors, these should be fixed by using the linter + } + } + } + + public function setPrefix(string $prefix): void + { + $this->prefix = $prefix; + } + + protected function extractTemplate(string $template, MessageCatalogue $catalogue): void + { + $visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor(); + $visitor->enable(); + + $this->twig->parse($this->twig->tokenize(new Source($template, ''))); + + foreach ($visitor->getMessages() as $message) { + $catalogue->set(trim($message[0]), $this->prefix.trim($message[0]), $message[1] ?: $this->defaultDomain); + } + + $visitor->disable(); + } + + protected function canBeExtracted(string $file): bool + { + return $this->isFile($file) && 'twig' === pathinfo($file, \PATHINFO_EXTENSION); + } + + protected function extractFromDirectory($directory): iterable + { + $finder = new Finder(); + + return $finder->files()->name('*.twig')->in($directory); + } +} diff --git a/vendor/symfony/twig-bridge/UndefinedCallableHandler.php b/vendor/symfony/twig-bridge/UndefinedCallableHandler.php new file mode 100644 index 0000000..c9f502f --- /dev/null +++ b/vendor/symfony/twig-bridge/UndefinedCallableHandler.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig; + +use Composer\InstalledVersions; +use Symfony\Bundle\FullStack; +use Twig\Error\SyntaxError; +use Twig\TwigFilter; +use Twig\TwigFunction; + +/** + * @internal + */ +class UndefinedCallableHandler +{ + private const FILTER_COMPONENTS = [ + 'humanize' => 'form', + 'form_encode_currency' => 'form', + 'serialize' => 'serializer', + 'trans' => 'translation', + 'sanitize_html' => 'html-sanitizer', + 'yaml_encode' => 'yaml', + 'yaml_dump' => 'yaml', + ]; + + private const FUNCTION_COMPONENTS = [ + 'asset' => 'asset', + 'asset_version' => 'asset', + 'importmap' => 'asset-mapper', + 'dump' => 'debug-bundle', + 'emojify' => 'emoji', + 'encore_entry_link_tags' => 'webpack-encore-bundle', + 'encore_entry_script_tags' => 'webpack-encore-bundle', + 'expression' => 'expression-language', + 'form_widget' => 'form', + 'form_errors' => 'form', + 'form_label' => 'form', + 'form_help' => 'form', + 'form_row' => 'form', + 'form_rest' => 'form', + 'form' => 'form', + 'form_start' => 'form', + 'form_end' => 'form', + 'csrf_token' => 'form', + 'form_parent' => 'form', + 'field_name' => 'form', + 'field_value' => 'form', + 'field_label' => 'form', + 'field_help' => 'form', + 'field_errors' => 'form', + 'field_choices' => 'form', + 'logout_url' => 'security-http', + 'logout_path' => 'security-http', + 'is_granted' => 'security-core', + 'impersonation_path' => 'security-http', + 'impersonation_url' => 'security-http', + 'impersonation_exit_path' => 'security-http', + 'impersonation_exit_url' => 'security-http', + 't' => 'translation', + 'link' => 'web-link', + 'preload' => 'web-link', + 'dns_prefetch' => 'web-link', + 'preconnect' => 'web-link', + 'prefetch' => 'web-link', + 'prerender' => 'web-link', + 'workflow_can' => 'workflow', + 'workflow_transitions' => 'workflow', + 'workflow_transition' => 'workflow', + 'workflow_has_marked_place' => 'workflow', + 'workflow_marked_places' => 'workflow', + 'workflow_metadata' => 'workflow', + 'workflow_transition_blockers' => 'workflow', + ]; + + private const FULL_STACK_ENABLE = [ + 'html-sanitizer' => 'enable "framework.html_sanitizer"', + 'form' => 'enable "framework.form"', + 'security-core' => 'add the "SecurityBundle"', + 'security-http' => 'add the "SecurityBundle"', + 'web-link' => 'enable "framework.web_link"', + 'workflow' => 'enable "framework.workflows"', + ]; + + public static function onUndefinedFilter(string $name): TwigFilter|false + { + if (!isset(self::FILTER_COMPONENTS[$name])) { + return false; + } + + throw new SyntaxError(self::onUndefined($name, 'filter', self::FILTER_COMPONENTS[$name])); + } + + public static function onUndefinedFunction(string $name): TwigFunction|false + { + if (!isset(self::FUNCTION_COMPONENTS[$name])) { + return false; + } + + if ('webpack-encore-bundle' === self::FUNCTION_COMPONENTS[$name]) { + return new TwigFunction($name, static fn () => ''); + } + + throw new SyntaxError(self::onUndefined($name, 'function', self::FUNCTION_COMPONENTS[$name])); + } + + private static function onUndefined(string $name, string $type, string $component): string + { + if (class_exists(FullStack::class) && isset(self::FULL_STACK_ENABLE[$component])) { + return sprintf('Did you forget to %s? Unknown %s "%s".', self::FULL_STACK_ENABLE[$component], $type, $name); + } + + $missingPackage = 'symfony/'.$component; + + if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled($missingPackage)) { + $missingPackage = 'symfony/twig-bundle'; + } + + return sprintf('Did you forget to run "composer require %s"? Unknown %s "%s".', $missingPackage, $type, $name); + } +} diff --git a/vendor/symfony/twig-bridge/composer.json b/vendor/symfony/twig-bridge/composer.json new file mode 100644 index 0000000..f7f8d32 --- /dev/null +++ b/vendor/symfony/twig-bridge/composer.json @@ -0,0 +1,76 @@ +{ + "name": "symfony/twig-bridge", + "type": "symfony-bridge", + "description": "Provides integration for Twig with various Symfony components", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.9" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Bridge\\Twig\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/twig-bundle/CHANGELOG.md b/vendor/symfony/twig-bundle/CHANGELOG.md new file mode 100644 index 0000000..9172256 --- /dev/null +++ b/vendor/symfony/twig-bundle/CHANGELOG.md @@ -0,0 +1,135 @@ +CHANGELOG +========= + +7.1 +--- + + * Mark class `TemplateCacheWarmer` as `final` + +7.0 +--- + + * Remove the `Twig_Environment` autowiring alias, use `Twig\Environment` instead + * Remove option `twig.autoescape`; create a class that implements your escaping strategy + (check `FileExtensionEscapingStrategy::guess()` for inspiration) and reference it using + the `twig.autoescape_service` option instead + * Drop support for Twig 2 + +6.4 +--- + + * Allow omitting the `autoescape_service_method` option when `autoescape_service` is set to an invokable service id + +6.3 +--- + + * Deprecate the `Twig_Environment` autowiring alias, use `Twig\Environment` instead + +6.2 +--- + + * Add the `twig.mailer.html_to_text_converter` option to allow configuring custom `HtmlToTextConverterInterface` + implementations to be used by the `twig.mime_body_renderer` service + +6.1 +--- + + * Add option `twig.file_name_pattern` to restrict which files are compiled by cache warmer and linter + * Deprecate option `twig.autoescape`, use `twig.autoescape_service[_method]` instead + +6.0 +--- + + * The `twig` service is now private + +5.3 +--- + + * Add support for the new `serialize` filter (from Twig Bridge) + +5.2.0 +----- + + * deprecated the public `twig` service to private + +5.0.0 +----- + + * updated default value for the `strict_variables` option to `%kernel.debug%` parameter + * removed support to load templates from the legacy directories `src/Resources/views/` and `src/Resources//views/` + * removed `TwigEngine` class, use `Twig\Environment` instead + * removed `FilesystemLoader` and `NativeFilesystemLoader`, use Twig notation for templates instead + * removed `twig.exception_controller` configuration option, use `framework.error_controller` option instead + * removed `ExceptionController`, `PreviewErrorController` and all built-in error templates in favor of the new error renderer mechanism + +4.4.0 +----- + + * marked the `TemplateIterator` as `internal` + * added HTML comment to beginning and end of `exception_full.html.twig` + * deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead + * deprecated all built-in error templates in favor of the new error renderer mechanism + * deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` configuration instead + +4.2.0 +----- + + * deprecated support for legacy templates directories `src/Resources/views/` and `src/Resources//views/`, use `templates/` and `templates/bundles//` instead. + +4.1.0 +----- + + * added priority to Twig extensions + * deprecated relying on the default value (`false`) of the `twig.strict_variables` configuration option. The `%kernel.debug%` parameter will be the new default in 5.0 + +4.0.0 +----- + + * removed `ContainerAwareRuntimeLoader` + +3.4.0 +----- + + * added exclusive Twig namespace only for root bundles + * deprecated `Symfony\Bundle\TwigBundle\Command\DebugCommand`, use `Symfony\Bridge\Twig\Command\DebugCommand` instead + * deprecated relying on the `ContainerAwareInterface` implementation for `Symfony\Bundle\TwigBundle\Command\LintCommand` + * added option to configure default path templates (via `default_path`) + +3.3.0 +----- + + * Deprecated `ContainerAwareRuntimeLoader` + +2.7.0 +----- + + * made it possible to configure the default formats for both the `date` and the `number_format` filter + * added support for the new Asset component (from Twig bridge) + * deprecated the assets extension (use the one from the Twig bridge instead) + +2.6.0 +----- + + * [BC BREAK] changed exception.json.twig to match same structure as error.json.twig making clients independent of runtime environment. + +2.3.0 +----- + + * added option to configure a custom template escaping guesser (via `autoescape_service` and `autoescape_service_method`) + +2.2.0 +----- + + * moved the exception controller to be a service (`twig.controller.exception:showAction` vs `Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction`) + * added support for multiple loaders via the "twig.loader" tag. + * added automatic registration of namespaced paths for registered bundles + * added support for namespaced paths + +2.1.0 +----- + + * added a new setting ("paths") to configure more paths for the Twig filesystem loader + * added contextual escaping based on the template file name (disabled if you explicitly pass an autoescape option) + * added a command that extracts translation messages from templates + * added the real template name when an error occurs in a Twig template + * added the twig:lint command that will validate a Twig template syntax. diff --git a/vendor/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php b/vendor/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php new file mode 100644 index 0000000..69b0b2c --- /dev/null +++ b/vendor/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\CacheWarmer; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Twig\Environment; +use Twig\Error\Error; + +/** + * Generates the Twig cache for all templates. + * + * @author Fabien Potencier + * + * @final since Symfony 7.1 + */ +class TemplateCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface +{ + private ContainerInterface $container; + private Environment $twig; + private iterable $iterator; + + public function __construct(ContainerInterface $container, iterable $iterator) + { + // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. + $this->container = $container; + $this->iterator = $iterator; + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + $this->twig ??= $this->container->get('twig'); + + foreach ($this->iterator as $template) { + try { + $this->twig->load($template); + } catch (Error) { + /* + * Problem during compilation, give up for this template (e.g. syntax errors). + * Failing silently here allows to ignore templates that rely on functions that aren't available in + * the current environment. For example, the WebProfilerBundle shouldn't be available in the prod + * environment, but some templates that are never used in prod might rely on functions the bundle provides. + * As we can't detect which templates are "really" important, we try to load all of them and ignore + * errors. Error checks may be performed by calling the lint:twig command. + */ + } + } + + return []; + } + + public function isOptional(): bool + { + return true; + } + + public static function getSubscribedServices(): array + { + return [ + 'twig' => Environment::class, + ]; + } +} diff --git a/vendor/symfony/twig-bundle/Command/LintCommand.php b/vendor/symfony/twig-bundle/Command/LintCommand.php new file mode 100644 index 0000000..10aa983 --- /dev/null +++ b/vendor/symfony/twig-bundle/Command/LintCommand.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Command; + +use Symfony\Bridge\Twig\Command\LintCommand as BaseLintCommand; +use Symfony\Component\Console\Attribute\AsCommand; + +/** + * Command that will validate your template syntax and output encountered errors. + * + * @author Marc Weistroff + * @author Jérôme Tamarelle + */ +#[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] +final class LintCommand extends BaseLintCommand +{ + protected function configure(): void + { + parent::configure(); + + $this + ->setHelp( + $this->getHelp().<<<'EOF' + +Or all template files in a bundle: + + php %command.full_name% @AcmeDemoBundle + +EOF + ) + ; + } + + protected function findFiles(string $filename): iterable + { + if (str_starts_with($filename, '@')) { + $filename = $this->getApplication()->getKernel()->locateResource($filename); + } + + return parent::findFiles($filename); + } +} diff --git a/vendor/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php new file mode 100644 index 0000000..b21e4f3 --- /dev/null +++ b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; + +use Symfony\Component\Asset\Packages; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Emoji\EmojiTransliterator; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Workflow\Workflow; +use Symfony\Component\Yaml\Yaml; + +/** + * @author Jean-François Simon + */ +class ExtensionPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!class_exists(Packages::class)) { + $container->removeDefinition('twig.extension.assets'); + } + + if (!class_exists(\Transliterator::class) || !class_exists(EmojiTransliterator::class)) { + $container->removeDefinition('twig.extension.emoji'); + } + + if (!class_exists(Expression::class)) { + $container->removeDefinition('twig.extension.expression'); + } + + if (!interface_exists(UrlGeneratorInterface::class)) { + $container->removeDefinition('twig.extension.routing'); + } + + if (!class_exists(Yaml::class)) { + $container->removeDefinition('twig.extension.yaml'); + } + + if (!$container->has('asset_mapper')) { + // edge case where AssetMapper is installed, but not enabled + $container->removeDefinition('twig.extension.importmap'); + $container->removeDefinition('twig.runtime.importmap'); + } + + $viewDir = \dirname((new \ReflectionClass(\Symfony\Bridge\Twig\Extension\FormExtension::class))->getFileName(), 2).'/Resources/views'; + $templateIterator = $container->getDefinition('twig.template_iterator'); + $templatePaths = $templateIterator->getArgument(1); + $loader = $container->getDefinition('twig.loader.native_filesystem'); + + if ($container->has('mailer')) { + $emailPath = $viewDir.'/Email'; + $loader->addMethodCall('addPath', [$emailPath, 'email']); + $loader->addMethodCall('addPath', [$emailPath, '!email']); + $templatePaths[$emailPath] = 'email'; + } + + if ($container->has('form.extension')) { + $container->getDefinition('twig.extension.form')->addTag('twig.extension'); + + $coreThemePath = $viewDir.'/Form'; + $loader->addMethodCall('addPath', [$coreThemePath]); + $templatePaths[$coreThemePath] = null; + } + + $templateIterator->replaceArgument(1, $templatePaths); + + if ($container->has('router')) { + $container->getDefinition('twig.extension.routing')->addTag('twig.extension'); + } + + if ($container->has('html_sanitizer')) { + $container->getDefinition('twig.extension.htmlsanitizer')->addTag('twig.extension'); + } + + if ($container->has('fragment.handler')) { + $container->getDefinition('twig.extension.httpkernel')->addTag('twig.extension'); + $container->getDefinition('twig.runtime.httpkernel')->addTag('twig.runtime'); + + if ($container->hasDefinition('fragment.renderer.hinclude')) { + $container->getDefinition('fragment.renderer.hinclude') + ->addTag('kernel.fragment_renderer', ['alias' => 'hinclude']) + ; + } + } + + if ($container->has('request_stack')) { + $container->getDefinition('twig.extension.httpfoundation')->addTag('twig.extension'); + } + + if ($container->getParameter('kernel.debug')) { + $container->getDefinition('twig.extension.profiler')->addTag('twig.extension'); + + // only register if the improved version from DebugBundle is *not* present + if (!$container->has('twig.extension.dump')) { + $container->getDefinition('twig.extension.debug')->addTag('twig.extension'); + } + } + + if ($container->has('web_link.add_link_header_listener')) { + $container->getDefinition('twig.extension.weblink')->addTag('twig.extension'); + } + + $container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false)); + + if ($container->has('assets.packages')) { + $container->getDefinition('twig.extension.assets')->addTag('twig.extension'); + } + + if ($container->hasDefinition('twig.extension.yaml')) { + $container->getDefinition('twig.extension.yaml')->addTag('twig.extension'); + } + + if (class_exists(\Symfony\Component\Stopwatch\Stopwatch::class)) { + $container->getDefinition('twig.extension.debug.stopwatch')->addTag('twig.extension'); + } + + if ($container->hasDefinition('twig.extension.expression')) { + $container->getDefinition('twig.extension.expression')->addTag('twig.extension'); + } + + if ($container->hasDefinition('twig.extension.emoji')) { + $container->getDefinition('twig.extension.emoji')->addTag('twig.extension'); + } + + if (!class_exists(Workflow::class) || !$container->has('workflow.registry')) { + $container->removeDefinition('workflow.twig_extension'); + } else { + $container->getDefinition('workflow.twig_extension')->addTag('twig.extension'); + } + + if ($container->has('serializer')) { + $container->getDefinition('twig.runtime.serializer')->addTag('twig.runtime'); + $container->getDefinition('twig.extension.serializer')->addTag('twig.extension'); + } + } +} diff --git a/vendor/symfony/twig-bundle/DependencyInjection/Compiler/RuntimeLoaderPass.php b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/RuntimeLoaderPass.php new file mode 100644 index 0000000..275f5c9 --- /dev/null +++ b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/RuntimeLoaderPass.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers Twig runtime services. + */ +class RuntimeLoaderPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('twig.runtime_loader')) { + return; + } + + $definition = $container->getDefinition('twig.runtime_loader'); + $mapping = []; + foreach ($container->findTaggedServiceIds('twig.runtime', true) as $id => $attributes) { + $def = $container->getDefinition($id); + $mapping[$def->getClass()] = new Reference($id); + } + + $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $mapping)); + } +} diff --git a/vendor/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php new file mode 100644 index 0000000..104464b --- /dev/null +++ b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Adds tagged twig.extension services to twig service. + * + * @author Fabien Potencier + */ +class TwigEnvironmentPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (false === $container->hasDefinition('twig')) { + return; + } + + $definition = $container->getDefinition('twig'); + + // Extensions must always be registered before everything else. + // For instance, global variable definitions must be registered + // afterward. If not, the globals from the extensions will never + // be registered. + $currentMethodCalls = $definition->getMethodCalls(); + $twigBridgeExtensionsMethodCalls = []; + $othersExtensionsMethodCalls = []; + foreach ($this->findAndSortTaggedServices('twig.extension', $container) as $extension) { + $methodCall = ['addExtension', [$extension]]; + $extensionClass = $container->getDefinition((string) $extension)->getClass(); + + if (\is_string($extensionClass) && str_starts_with($extensionClass, 'Symfony\Bridge\Twig\Extension')) { + $twigBridgeExtensionsMethodCalls[] = $methodCall; + } else { + $othersExtensionsMethodCalls[] = $methodCall; + } + } + + if ($twigBridgeExtensionsMethodCalls || $othersExtensionsMethodCalls) { + $definition->setMethodCalls(array_merge($twigBridgeExtensionsMethodCalls, $othersExtensionsMethodCalls, $currentMethodCalls)); + } + } +} diff --git a/vendor/symfony/twig-bundle/DependencyInjection/Compiler/TwigLoaderPass.php b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/TwigLoaderPass.php new file mode 100644 index 0000000..b4d359e --- /dev/null +++ b/vendor/symfony/twig-bundle/DependencyInjection/Compiler/TwigLoaderPass.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds services tagged twig.loader as Twig loaders. + * + * @author Daniel Leech + */ +class TwigLoaderPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (false === $container->hasDefinition('twig')) { + return; + } + + $prioritizedLoaders = []; + $found = 0; + + foreach ($container->findTaggedServiceIds('twig.loader', true) as $id => $attributes) { + $priority = $attributes[0]['priority'] ?? 0; + $prioritizedLoaders[$priority][] = $id; + ++$found; + } + + if (!$found) { + throw new LogicException('No twig loaders found. You need to tag at least one loader with "twig.loader".'); + } + + if (1 === $found) { + $container->setAlias('twig.loader', $id); + } else { + $chainLoader = $container->getDefinition('twig.loader.chain'); + krsort($prioritizedLoaders); + + foreach ($prioritizedLoaders as $loaders) { + foreach ($loaders as $loader) { + $chainLoader->addMethodCall('addLoader', [new Reference($loader)]); + } + } + + $container->setAlias('twig.loader', 'twig.loader.chain'); + } + } +} diff --git a/vendor/symfony/twig-bundle/DependencyInjection/Configuration.php b/vendor/symfony/twig-bundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000..ca23a0d --- /dev/null +++ b/vendor/symfony/twig-bundle/DependencyInjection/Configuration.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; + +/** + * TwigExtension configuration structure. + * + * @author Jeremy Mikola + */ +class Configuration implements ConfigurationInterface +{ + /** + * Generates the configuration tree builder. + */ + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('twig'); + $rootNode = $treeBuilder->getRootNode(); + + $rootNode->beforeNormalization() + ->ifTrue(fn ($v) => \is_array($v) && \array_key_exists('exception_controller', $v)) + ->then(function ($v) { + if (isset($v['exception_controller'])) { + throw new InvalidConfigurationException('Option "exception_controller" under "twig" must be null or unset, use "error_controller" under "framework" instead.'); + } + + unset($v['exception_controller']); + + return $v; + }) + ->end(); + + $this->addFormThemesSection($rootNode); + $this->addGlobalsSection($rootNode); + $this->addTwigOptions($rootNode); + $this->addTwigFormatOptions($rootNode); + $this->addMailerSection($rootNode); + + return $treeBuilder; + } + + private function addFormThemesSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('form_theme') + ->children() + ->arrayNode('form_themes') + ->addDefaultChildrenIfNoneSet() + ->prototype('scalar')->defaultValue('form_div_layout.html.twig')->end() + ->example(['@My/form.html.twig']) + ->validate() + ->ifTrue(fn ($v) => !\in_array('form_div_layout.html.twig', $v, true)) + ->then(fn ($v) => array_merge(['form_div_layout.html.twig'], $v)) + ->end() + ->end() + ->end() + ; + } + + private function addGlobalsSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('global') + ->children() + ->arrayNode('globals') + ->normalizeKeys(false) + ->useAttributeAsKey('key') + ->example(['foo' => '@bar', 'pi' => 3.14]) + ->prototype('array') + ->normalizeKeys(false) + ->beforeNormalization() + ->ifTrue(fn ($v) => \is_string($v) && str_starts_with($v, '@')) + ->then(function ($v) { + if (str_starts_with($v, '@@')) { + return substr($v, 1); + } + + return ['id' => substr($v, 1), 'type' => 'service']; + }) + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { + if (\is_array($v)) { + $keys = array_keys($v); + sort($keys); + + return $keys !== ['id', 'type'] && $keys !== ['value']; + } + + return true; + }) + ->then(fn ($v) => ['value' => $v]) + ->end() + ->children() + ->scalarNode('id')->end() + ->scalarNode('type') + ->validate() + ->ifNotInArray(['service']) + ->thenInvalid('The %s type is not supported') + ->end() + ->end() + ->variableNode('value')->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addTwigOptions(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('path') + ->children() + ->scalarNode('autoescape_service')->defaultNull()->end() + ->scalarNode('autoescape_service_method')->defaultNull()->end() + ->scalarNode('base_template_class') + ->setDeprecated('symfony/twig-bundle', '7.1') + ->example('Twig\Template') + ->cannotBeEmpty() + ->end() + ->scalarNode('cache')->defaultValue('%kernel.cache_dir%/twig')->end() + ->scalarNode('charset')->defaultValue('%kernel.charset%')->end() + ->booleanNode('debug')->defaultValue('%kernel.debug%')->end() + ->booleanNode('strict_variables')->defaultValue('%kernel.debug%')->end() + ->scalarNode('auto_reload')->end() + ->integerNode('optimizations')->min(-1)->end() + ->scalarNode('default_path') + ->info('The default path used to load templates') + ->defaultValue('%kernel.project_dir%/templates') + ->end() + ->arrayNode('file_name_pattern') + ->example('*.twig') + ->info('Pattern of file name used for cache warmer and linter') + ->beforeNormalization() + ->ifString() + ->then(fn ($value) => [$value]) + ->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('paths') + ->normalizeKeys(false) + ->useAttributeAsKey('paths') + ->beforeNormalization() + ->ifArray() + ->then(function ($paths) { + $normalized = []; + foreach ($paths as $path => $namespace) { + if (\is_array($namespace)) { + // xml + $path = $namespace['value']; + $namespace = $namespace['namespace']; + } + + // path within the default namespace + if (ctype_digit((string) $path)) { + $path = $namespace; + $namespace = null; + } + + $normalized[$path] = $namespace; + } + + return $normalized; + }) + ->end() + ->prototype('variable')->end() + ->end() + ->end() + ; + } + + private function addTwigFormatOptions(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('date') + ->info('The default format options used by the date filter') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('format')->defaultValue('F j, Y H:i')->end() + ->scalarNode('interval_format')->defaultValue('%d days')->end() + ->scalarNode('timezone') + ->info('The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used') + ->defaultNull() + ->end() + ->end() + ->end() + ->arrayNode('number_format') + ->info('The default format options for the number_format filter') + ->addDefaultsIfNotSet() + ->children() + ->integerNode('decimals')->defaultValue(0)->end() + ->scalarNode('decimal_point')->defaultValue('.')->end() + ->scalarNode('thousands_separator')->defaultValue(',')->end() + ->end() + ->end() + ->end() + ; + } + + private function addMailerSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('mailer') + ->children() + ->scalarNode('html_to_text_converter') + ->info(sprintf('A service implementing the "%s"', HtmlToTextConverterInterface::class)) + ->defaultNull() + ->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/vendor/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php b/vendor/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php new file mode 100644 index 0000000..64d76c0 --- /dev/null +++ b/vendor/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Configurator; + +use Symfony\Bridge\Twig\UndefinedCallableHandler; +use Twig\Environment; +use Twig\Extension\CoreExtension; + +/** + * Twig environment configurator. + * + * @author Christian Flothmann + */ +class EnvironmentConfigurator +{ + private string $dateFormat; + private string $intervalFormat; + private ?string $timezone; + private int $decimals; + private string $decimalPoint; + private string $thousandsSeparator; + + public function __construct(string $dateFormat, string $intervalFormat, ?string $timezone, int $decimals, string $decimalPoint, string $thousandsSeparator) + { + $this->dateFormat = $dateFormat; + $this->intervalFormat = $intervalFormat; + $this->timezone = $timezone; + $this->decimals = $decimals; + $this->decimalPoint = $decimalPoint; + $this->thousandsSeparator = $thousandsSeparator; + } + + public function configure(Environment $environment): void + { + $environment->getExtension(CoreExtension::class)->setDateFormat($this->dateFormat, $this->intervalFormat); + + if (null !== $this->timezone) { + $environment->getExtension(CoreExtension::class)->setTimezone($this->timezone); + } + + $environment->getExtension(CoreExtension::class)->setNumberFormat($this->decimals, $this->decimalPoint, $this->thousandsSeparator); + + // wrap UndefinedCallableHandler in closures for lazy-autoloading + $environment->registerUndefinedFilterCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFilter($name)); + $environment->registerUndefinedFunctionCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFunction($name)); + } +} diff --git a/vendor/symfony/twig-bundle/DependencyInjection/TwigExtension.php b/vendor/symfony/twig-bundle/DependencyInjection/TwigExtension.php new file mode 100644 index 0000000..c3e70d4 --- /dev/null +++ b/vendor/symfony/twig-bundle/DependencyInjection/TwigExtension.php @@ -0,0 +1,220 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileExistenceResource; +use Symfony\Component\Console\Application; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\AbstractRendererEngine; +use Symfony\Component\Form\Form; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Translation\LocaleSwitcher; +use Symfony\Component\Translation\Translator; +use Symfony\Contracts\Service\ResetInterface; +use Twig\Extension\ExtensionInterface; +use Twig\Extension\RuntimeExtensionInterface; +use Twig\Loader\LoaderInterface; + +/** + * TwigExtension. + * + * @author Fabien Potencier + * @author Jeremy Mikola + */ +class TwigExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container): void + { + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('twig.php'); + + if ($container::willBeAvailable('symfony/form', Form::class, ['symfony/twig-bundle'])) { + $loader->load('form.php'); + + if (is_subclass_of(AbstractRendererEngine::class, ResetInterface::class)) { + $container->getDefinition('twig.form.engine')->addTag('kernel.reset', [ + 'method' => 'reset', + ]); + } + } + + if ($container::willBeAvailable('symfony/console', Application::class, ['symfony/twig-bundle'])) { + $loader->load('console.php'); + } + + if (!$container::willBeAvailable('symfony/translation', Translator::class, ['symfony/twig-bundle'])) { + $container->removeDefinition('twig.translation.extractor'); + } + + foreach ($configs as $key => $config) { + if (isset($config['globals'])) { + foreach ($config['globals'] as $name => $value) { + if (\is_array($value) && isset($value['key'])) { + $configs[$key]['globals'][$name] = [ + 'key' => $name, + 'value' => $value, + ]; + } + } + } + } + + $configuration = $this->getConfiguration($configs, $container); + + $config = $this->processConfiguration($configuration, $configs); + + if ($container::willBeAvailable('symfony/mailer', Mailer::class, ['symfony/twig-bundle'])) { + $loader->load('mailer.php'); + + if ($htmlToTextConverter = $config['mailer']['html_to_text_converter'] ?? null) { + $container->getDefinition('twig.mime_body_renderer')->setArgument('$converter', new Reference($htmlToTextConverter)); + } + + if (ContainerBuilder::willBeAvailable('symfony/translation', LocaleSwitcher::class, ['symfony/framework-bundle'])) { + $container->getDefinition('twig.mime_body_renderer')->setArgument('$localeSwitcher', new Reference('translation.locale_switcher', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)); + } + } + + if ($container::willBeAvailable('symfony/asset-mapper', AssetMapper::class, ['symfony/twig-bundle'])) { + $loader->load('importmap.php'); + } + + $container->setParameter('twig.form.resources', $config['form_themes']); + $container->setParameter('twig.default_path', $config['default_path']); + $defaultTwigPath = $container->getParameterBag()->resolveValue($config['default_path']); + + $envConfiguratorDefinition = $container->getDefinition('twig.configurator.environment'); + $envConfiguratorDefinition->replaceArgument(0, $config['date']['format']); + $envConfiguratorDefinition->replaceArgument(1, $config['date']['interval_format']); + $envConfiguratorDefinition->replaceArgument(2, $config['date']['timezone']); + $envConfiguratorDefinition->replaceArgument(3, $config['number_format']['decimals']); + $envConfiguratorDefinition->replaceArgument(4, $config['number_format']['decimal_point']); + $envConfiguratorDefinition->replaceArgument(5, $config['number_format']['thousands_separator']); + + $twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.native_filesystem'); + + // register user-configured paths + foreach ($config['paths'] as $path => $namespace) { + if (!$namespace) { + $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path]); + } else { + $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, $namespace]); + } + } + + // paths are modified in ExtensionPass if forms are enabled + $container->getDefinition('twig.template_iterator')->replaceArgument(1, $config['paths']); + + $container->getDefinition('twig.template_iterator')->replaceArgument(3, $config['file_name_pattern']); + + if ($container->hasDefinition('twig.command.lint')) { + $container->getDefinition('twig.command.lint')->replaceArgument(1, $config['file_name_pattern'] ?: ['*.twig']); + } + + foreach ($this->getBundleTemplatePaths($container, $config) as $name => $paths) { + $namespace = $this->normalizeBundleName($name); + foreach ($paths as $path) { + $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, $namespace]); + } + + if ($paths) { + // the last path must be the bundle views directory + $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, '!'.$namespace]); + } + } + + if (file_exists($defaultTwigPath)) { + $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$defaultTwigPath]); + } + $container->addResource(new FileExistenceResource($defaultTwigPath)); + + if (!empty($config['globals'])) { + $def = $container->getDefinition('twig'); + foreach ($config['globals'] as $key => $global) { + if (isset($global['type']) && 'service' === $global['type']) { + $def->addMethodCall('addGlobal', [$key, new Reference($global['id'])]); + } else { + $def->addMethodCall('addGlobal', [$key, $global['value']]); + } + } + } + + if (isset($config['autoescape_service'])) { + $config['autoescape'] = [new Reference($config['autoescape_service']), $config['autoescape_service_method'] ?? '__invoke']; + } else { + $config['autoescape'] = 'name'; + } + + $container->getDefinition('twig')->replaceArgument(1, array_intersect_key($config, [ + 'debug' => true, + 'charset' => true, + 'base_template_class' => true, + 'strict_variables' => true, + 'autoescape' => true, + 'cache' => true, + 'auto_reload' => true, + 'optimizations' => true, + ])); + + $container->registerForAutoconfiguration(ExtensionInterface::class)->addTag('twig.extension'); + $container->registerForAutoconfiguration(LoaderInterface::class)->addTag('twig.loader'); + $container->registerForAutoconfiguration(RuntimeExtensionInterface::class)->addTag('twig.runtime'); + + if (false === $config['cache']) { + $container->removeDefinition('twig.template_cache_warmer'); + } + } + + private function getBundleTemplatePaths(ContainerBuilder $container, array $config): array + { + $bundleHierarchy = []; + foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { + $defaultOverrideBundlePath = $container->getParameterBag()->resolveValue($config['default_path']).'/bundles/'.$name; + + if (file_exists($defaultOverrideBundlePath)) { + $bundleHierarchy[$name][] = $defaultOverrideBundlePath; + } + $container->addResource(new FileExistenceResource($defaultOverrideBundlePath)); + + if (file_exists($dir = $bundle['path'].'/Resources/views') || file_exists($dir = $bundle['path'].'/templates')) { + $bundleHierarchy[$name][] = $dir; + } + $container->addResource(new FileExistenceResource($dir)); + } + + return $bundleHierarchy; + } + + private function normalizeBundleName(string $name): string + { + if (str_ends_with($name, 'Bundle')) { + $name = substr($name, 0, -6); + } + + return $name; + } + + public function getXsdValidationBasePath(): string|false + { + return __DIR__.'/../Resources/config/schema'; + } + + public function getNamespace(): string + { + return 'http://symfony.com/schema/dic/twig'; + } +} diff --git a/vendor/symfony/twig-bundle/LICENSE b/vendor/symfony/twig-bundle/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/twig-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/twig-bundle/README.md b/vendor/symfony/twig-bundle/README.md new file mode 100644 index 0000000..3ae2985 --- /dev/null +++ b/vendor/symfony/twig-bundle/README.md @@ -0,0 +1,13 @@ +TwigBundle +========== + +TwigBundle provides a tight integration of Twig into the Symfony full-stack +framework. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/twig-bundle/Resources/config/console.php b/vendor/symfony/twig-bundle/Resources/config/console.php new file mode 100644 index 0000000..b0303c3 --- /dev/null +++ b/vendor/symfony/twig-bundle/Resources/config/console.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Command\DebugCommand; +use Symfony\Bundle\TwigBundle\Command\LintCommand; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig.command.debug', DebugCommand::class) + ->args([ + service('twig'), + param('kernel.project_dir'), + param('kernel.bundles_metadata'), + param('twig.default_path'), + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('twig.command.lint', LintCommand::class) + ->args([service('twig'), abstract_arg('File name pattern')]) + ->tag('console.command') + ; +}; diff --git a/vendor/symfony/twig-bundle/Resources/config/form.php b/vendor/symfony/twig-bundle/Resources/config/form.php new file mode 100644 index 0000000..9f2efdf --- /dev/null +++ b/vendor/symfony/twig-bundle/Resources/config/form.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Component\Form\FormRenderer; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig.extension.form', FormExtension::class) + ->args([service('translator')->nullOnInvalid()]) + + ->set('twig.form.engine', TwigRendererEngine::class) + ->args([param('twig.form.resources'), service('twig')]) + + ->set('twig.form.renderer', FormRenderer::class) + ->args([service('twig.form.engine'), service('security.csrf.token_manager')->nullOnInvalid()]) + ->tag('twig.runtime') + ; +}; diff --git a/vendor/symfony/twig-bundle/Resources/config/importmap.php b/vendor/symfony/twig-bundle/Resources/config/importmap.php new file mode 100644 index 0000000..c280219 --- /dev/null +++ b/vendor/symfony/twig-bundle/Resources/config/importmap.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\ImportMapExtension; +use Symfony\Bridge\Twig\Extension\ImportMapRuntime; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('twig.runtime.importmap', ImportMapRuntime::class) + ->args([service('asset_mapper.importmap.renderer')]) + ->tag('twig.runtime') + + ->set('twig.extension.importmap', ImportMapExtension::class) + ->tag('twig.extension') + ; +}; diff --git a/vendor/symfony/twig-bundle/Resources/config/mailer.php b/vendor/symfony/twig-bundle/Resources/config/mailer.php new file mode 100644 index 0000000..e43658a --- /dev/null +++ b/vendor/symfony/twig-bundle/Resources/config/mailer.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Mime\BodyRenderer; +use Symfony\Component\Mailer\EventListener\MessageListener; +use Symfony\Component\Mime\BodyRendererInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig.mailer.message_listener', MessageListener::class) + ->args([null, service('twig.mime_body_renderer')]) + ->tag('kernel.event_subscriber') + + ->set('twig.mime_body_renderer', BodyRenderer::class) + ->args([service('twig')]) + ->alias(BodyRendererInterface::class, 'twig.mime_body_renderer') + ; +}; diff --git a/vendor/symfony/twig-bundle/Resources/config/schema/twig-1.0.xsd b/vendor/symfony/twig-bundle/Resources/config/schema/twig-1.0.xsd new file mode 100644 index 0000000..05f949e --- /dev/null +++ b/vendor/symfony/twig-bundle/Resources/config/schema/twig-1.0.xsd @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/twig-bundle/Resources/config/twig.php b/vendor/symfony/twig-bundle/Resources/config/twig.php new file mode 100644 index 0000000..02631d2 --- /dev/null +++ b/vendor/symfony/twig-bundle/Resources/config/twig.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Container\ContainerInterface; +use Symfony\Bridge\Twig\AppVariable; +use Symfony\Bridge\Twig\DataCollector\TwigDataCollector; +use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer; +use Symfony\Bridge\Twig\EventListener\TemplateAttributeListener; +use Symfony\Bridge\Twig\Extension\AssetExtension; +use Symfony\Bridge\Twig\Extension\EmojiExtension; +use Symfony\Bridge\Twig\Extension\ExpressionExtension; +use Symfony\Bridge\Twig\Extension\HtmlSanitizerExtension; +use Symfony\Bridge\Twig\Extension\HttpFoundationExtension; +use Symfony\Bridge\Twig\Extension\HttpKernelExtension; +use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; +use Symfony\Bridge\Twig\Extension\ProfilerExtension; +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Extension\SerializerExtension; +use Symfony\Bridge\Twig\Extension\SerializerRuntime; +use Symfony\Bridge\Twig\Extension\StopwatchExtension; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Bridge\Twig\Extension\WorkflowExtension; +use Symfony\Bridge\Twig\Extension\YamlExtension; +use Symfony\Bridge\Twig\Translation\TwigExtractor; +use Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheWarmer; +use Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator; +use Symfony\Bundle\TwigBundle\TemplateIterator; +use Twig\Cache\FilesystemCache; +use Twig\Environment; +use Twig\Extension\CoreExtension; +use Twig\Extension\DebugExtension; +use Twig\Extension\EscaperExtension; +use Twig\Extension\OptimizerExtension; +use Twig\Extension\StagingExtension; +use Twig\ExtensionSet; +use Twig\Loader\ChainLoader; +use Twig\Loader\FilesystemLoader; +use Twig\Profiler\Profile; +use Twig\RuntimeLoader\ContainerRuntimeLoader; +use Twig\Template; +use Twig\TemplateWrapper; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig', Environment::class) + ->args([service('twig.loader'), abstract_arg('Twig options')]) + ->call('addGlobal', ['app', service('twig.app_variable')]) + ->call('addRuntimeLoader', [service('twig.runtime_loader')]) + ->configurator([service('twig.configurator.environment'), 'configure']) + ->tag('container.preload', ['class' => FilesystemCache::class]) + ->tag('container.preload', ['class' => CoreExtension::class]) + ->tag('container.preload', ['class' => EscaperExtension::class]) + ->tag('container.preload', ['class' => OptimizerExtension::class]) + ->tag('container.preload', ['class' => StagingExtension::class]) + ->tag('container.preload', ['class' => ExtensionSet::class]) + ->tag('container.preload', ['class' => Template::class]) + ->tag('container.preload', ['class' => TemplateWrapper::class]) + ->alias(Environment::class, 'twig') + + ->set('twig.app_variable', AppVariable::class) + ->call('setEnvironment', [param('kernel.environment')]) + ->call('setDebug', [param('kernel.debug')]) + ->call('setTokenStorage', [service('security.token_storage')->ignoreOnInvalid()]) + ->call('setRequestStack', [service('request_stack')->ignoreOnInvalid()]) + ->call('setLocaleSwitcher', [service('translation.locale_switcher')->ignoreOnInvalid()]) + ->call('setEnabledLocales', [param('kernel.enabled_locales')]) + + ->set('twig.template_iterator', TemplateIterator::class) + ->args([service('kernel'), abstract_arg('Twig paths'), param('twig.default_path'), abstract_arg('File name pattern')]) + + ->set('twig.template_cache_warmer', TemplateCacheWarmer::class) + ->args([service(ContainerInterface::class), service('twig.template_iterator')]) + ->tag('kernel.cache_warmer') + ->tag('container.service_subscriber', ['id' => 'twig']) + + ->set('twig.loader.native_filesystem', FilesystemLoader::class) + ->args([[], param('kernel.project_dir')]) + ->tag('twig.loader') + + ->set('twig.loader.chain', ChainLoader::class) + + ->set('twig.extension.profiler', ProfilerExtension::class) + ->args([service('twig.profile'), service('debug.stopwatch')->ignoreOnInvalid()]) + + ->set('twig.profile', Profile::class) + + ->set('data_collector.twig', TwigDataCollector::class) + ->args([service('twig.profile'), service('twig')]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/twig.html.twig', 'id' => 'twig', 'priority' => 257]) + + ->set('twig.extension.trans', TranslationExtension::class) + ->args([service('translator')->nullOnInvalid()]) + ->tag('twig.extension') + + ->set('twig.extension.assets', AssetExtension::class) + ->args([service('assets.packages')]) + + ->set('twig.extension.routing', RoutingExtension::class) + ->args([service('router')]) + + ->set('twig.extension.yaml', YamlExtension::class) + + ->set('twig.extension.debug.stopwatch', StopwatchExtension::class) + ->args([service('debug.stopwatch')->ignoreOnInvalid(), param('kernel.debug')]) + + ->set('twig.extension.expression', ExpressionExtension::class) + + ->set('twig.extension.emoji', EmojiExtension::class) + + ->set('twig.extension.htmlsanitizer', HtmlSanitizerExtension::class) + ->args([tagged_locator('html_sanitizer', 'sanitizer')]) + + ->set('twig.extension.httpkernel', HttpKernelExtension::class) + + ->set('twig.runtime.httpkernel', HttpKernelRuntime::class) + ->args([service('fragment.handler'), service('fragment.uri_generator')->ignoreOnInvalid()]) + + ->set('twig.extension.httpfoundation', HttpFoundationExtension::class) + ->args([service('url_helper')]) + + ->set('twig.extension.debug', DebugExtension::class) + + ->set('twig.extension.weblink', WebLinkExtension::class) + ->args([service('request_stack')]) + + ->set('twig.translation.extractor', TwigExtractor::class) + ->args([service('twig')]) + ->tag('translation.extractor', ['alias' => 'twig']) + + ->set('workflow.twig_extension', WorkflowExtension::class) + ->args([service('workflow.registry')]) + + ->set('twig.configurator.environment', EnvironmentConfigurator::class) + ->args([ + abstract_arg('date format, set in TwigExtension'), + abstract_arg('interval format, set in TwigExtension'), + abstract_arg('timezone, set in TwigExtension'), + abstract_arg('decimals, set in TwigExtension'), + abstract_arg('decimal point, set in TwigExtension'), + abstract_arg('thousands separator, set in TwigExtension'), + ]) + + ->set('twig.runtime_loader', ContainerRuntimeLoader::class) + ->args([abstract_arg('runtime locator')]) + + ->set('twig.error_renderer.html', TwigErrorRenderer::class) + ->decorate('error_renderer.html') + ->args([ + service('twig'), + service('twig.error_renderer.html.inner'), + inline_service('bool') + ->factory([TwigErrorRenderer::class, 'isDebug']) + ->args([service('request_stack'), param('kernel.debug')]), + ]) + + ->set('twig.runtime.serializer', SerializerRuntime::class) + ->args([service('serializer')]) + + ->set('twig.extension.serializer', SerializerExtension::class) + + ->set('controller.template_attribute_listener', TemplateAttributeListener::class) + ->args([service('twig')]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/vendor/symfony/twig-bundle/TemplateIterator.php b/vendor/symfony/twig-bundle/TemplateIterator.php new file mode 100644 index 0000000..bd42f1a --- /dev/null +++ b/vendor/symfony/twig-bundle/TemplateIterator.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * Iterator for all templates in bundles and in the application Resources directory. + * + * @author Fabien Potencier + * + * @internal + * + * @implements \IteratorAggregate + */ +class TemplateIterator implements \IteratorAggregate +{ + private KernelInterface $kernel; + private \Traversable $templates; + private array $paths; + private ?string $defaultPath; + private array $namePatterns; + + /** + * @param array $paths Additional Twig paths to warm + * @param string|null $defaultPath The directory where global templates can be stored + * @param string[] $namePatterns Pattern of file names + */ + public function __construct(KernelInterface $kernel, array $paths = [], ?string $defaultPath = null, array $namePatterns = []) + { + $this->kernel = $kernel; + $this->paths = $paths; + $this->defaultPath = $defaultPath; + $this->namePatterns = $namePatterns; + } + + public function getIterator(): \Traversable + { + if (isset($this->templates)) { + return $this->templates; + } + + $templates = null !== $this->defaultPath ? [$this->findTemplatesInDirectory($this->defaultPath, null, ['bundles'])] : []; + + foreach ($this->kernel->getBundles() as $bundle) { + $name = $bundle->getName(); + if (str_ends_with($name, 'Bundle')) { + $name = substr($name, 0, -6); + } + + $bundleTemplatesDir = is_dir($bundle->getPath().'/Resources/views') ? $bundle->getPath().'/Resources/views' : $bundle->getPath().'/templates'; + + $templates[] = $this->findTemplatesInDirectory($bundleTemplatesDir, $name); + if (null !== $this->defaultPath) { + $templates[] = $this->findTemplatesInDirectory($this->defaultPath.'/bundles/'.$bundle->getName(), $name); + } + } + + foreach ($this->paths as $dir => $namespace) { + $templates[] = $this->findTemplatesInDirectory($dir, $namespace); + } + + return $this->templates = new \ArrayIterator(array_unique(array_merge([], ...$templates))); + } + + /** + * Find templates in the given directory. + * + * @return string[] + */ + private function findTemplatesInDirectory(string $dir, ?string $namespace = null, array $excludeDirs = []): array + { + if (!is_dir($dir)) { + return []; + } + + $templates = []; + foreach (Finder::create()->files()->followLinks()->in($dir)->exclude($excludeDirs)->name($this->namePatterns) as $file) { + $templates[] = (null !== $namespace ? '@'.$namespace.'/' : '').str_replace('\\', '/', $file->getRelativePathname()); + } + + return $templates; + } +} diff --git a/vendor/symfony/twig-bundle/TwigBundle.php b/vendor/symfony/twig-bundle/TwigBundle.php new file mode 100644 index 0000000..5ff13b1 --- /dev/null +++ b/vendor/symfony/twig-bundle/TwigBundle.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle; + +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExtensionPass; +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\RuntimeLoaderPass; +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass; +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass; +use Symfony\Component\Console\Application; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; + +/** + * Bundle. + * + * @author Fabien Potencier + */ +class TwigBundle extends Bundle +{ + public function build(ContainerBuilder $container): void + { + parent::build($container); + + // ExtensionPass must be run before the FragmentRendererPass as it adds tags that are processed later + $container->addCompilerPass(new ExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); + $container->addCompilerPass(new TwigEnvironmentPass()); + $container->addCompilerPass(new TwigLoaderPass()); + $container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING); + } + + public function registerCommands(Application $application): void + { + // noop + } +} diff --git a/vendor/symfony/twig-bundle/composer.json b/vendor/symfony/twig-bundle/composer.json new file mode 100644 index 0000000..88c1dd5 --- /dev/null +++ b/vendor/symfony/twig-bundle/composer.json @@ -0,0 +1,51 @@ +{ + "name": "symfony/twig-bundle", + "type": "symfony-bundle", + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "composer-runtime-api": ">=2.1", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0" + }, + "conflict": { + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/type-info/CHANGELOG.md b/vendor/symfony/type-info/CHANGELOG.md new file mode 100644 index 0000000..6eb821c --- /dev/null +++ b/vendor/symfony/type-info/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +7.1 +--- + + * Add the component as experimental diff --git a/vendor/symfony/type-info/Exception/ExceptionInterface.php b/vendor/symfony/type-info/Exception/ExceptionInterface.php new file mode 100644 index 0000000..6236d9e --- /dev/null +++ b/vendor/symfony/type-info/Exception/ExceptionInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Exception; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/type-info/Exception/InvalidArgumentException.php b/vendor/symfony/type-info/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..67d0f1a --- /dev/null +++ b/vendor/symfony/type-info/Exception/InvalidArgumentException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Exception; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/type-info/Exception/LogicException.php b/vendor/symfony/type-info/Exception/LogicException.php new file mode 100644 index 0000000..9adcaed --- /dev/null +++ b/vendor/symfony/type-info/Exception/LogicException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Exception; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/type-info/Exception/RuntimeException.php b/vendor/symfony/type-info/Exception/RuntimeException.php new file mode 100644 index 0000000..e9cd623 --- /dev/null +++ b/vendor/symfony/type-info/Exception/RuntimeException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Exception; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/type-info/Exception/UnsupportedException.php b/vendor/symfony/type-info/Exception/UnsupportedException.php new file mode 100644 index 0000000..29c76fb --- /dev/null +++ b/vendor/symfony/type-info/Exception/UnsupportedException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Exception; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +class UnsupportedException extends \LogicException implements ExceptionInterface +{ + public function __construct( + string $message, + public readonly mixed $subject, + int $code = 0, + ?\Throwable $previous = null, + ) { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/symfony/type-info/LICENSE b/vendor/symfony/type-info/LICENSE new file mode 100644 index 0000000..e374a5c --- /dev/null +++ b/vendor/symfony/type-info/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024-present Fabien Potencier + +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. diff --git a/vendor/symfony/type-info/README.md b/vendor/symfony/type-info/README.md new file mode 100644 index 0000000..de85ba7 --- /dev/null +++ b/vendor/symfony/type-info/README.md @@ -0,0 +1,47 @@ +TypeInfo Component +================== + +The TypeInfo component extracts PHP types information. + +**This Component is experimental**. +[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) +are not covered by Symfony's +[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). + +Getting Started +--------------- + +```bash +composer require symfony/type-info +composer require phpstan/phpdoc-parser # to support raw string resolving +``` + +```php +resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type instance +$typeResolver->resolve('bool'); // returns a "bool" Type instance + +// Types can be instantiated thanks to static factories +$type = Type::list(Type::nullable(Type::bool())); + +// Type instances have several helper methods +$type->getBaseType() // returns an "array" Type instance +$type->getCollectionKeyType(); // returns an "int" Type instance +$type->getCollectionValueType()->isNullable(); // returns true +``` + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/type-info/Type.php b/vendor/symfony/type-info/Type.php new file mode 100644 index 0000000..45b7c57 --- /dev/null +++ b/vendor/symfony/type-info/Type.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo; + +use Symfony\Component\TypeInfo\Type\BuiltinType; +use Symfony\Component\TypeInfo\Type\ObjectType; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +abstract class Type implements \Stringable +{ + use TypeFactoryTrait; + + abstract public function getBaseType(): BuiltinType|ObjectType; + + /** + * @param TypeIdentifier|class-string $subject + */ + abstract public function isA(TypeIdentifier|string $subject): bool; + + abstract public function asNonNullable(): self; + + /** + * @param callable(Type): bool $callable + */ + public function is(callable $callable): bool + { + return $callable($this); + } + + public function isNullable(): bool + { + return $this->is(fn (Type $t): bool => $t->isA(TypeIdentifier::NULL) || $t->isA(TypeIdentifier::MIXED)); + } +} diff --git a/vendor/symfony/type-info/Type/BackedEnumType.php b/vendor/symfony/type-info/Type/BackedEnumType.php new file mode 100644 index 0000000..32ec3b6 --- /dev/null +++ b/vendor/symfony/type-info/Type/BackedEnumType.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of class-string<\BackedEnum> + * @template U of BuiltinType|BuiltinType + * + * @extends EnumType + * + * @experimental + */ +final class BackedEnumType extends EnumType +{ + /** + * @param T $className + * @param U $backingType + */ + public function __construct( + string $className, + private readonly BuiltinType $backingType, + ) { + parent::__construct($className); + } + + /** + * @return U + */ + public function getBackingType(): BuiltinType + { + return $this->backingType; + } +} diff --git a/vendor/symfony/type-info/Type/BuiltinType.php b/vendor/symfony/type-info/Type/BuiltinType.php new file mode 100644 index 0000000..06f175d --- /dev/null +++ b/vendor/symfony/type-info/Type/BuiltinType.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of TypeIdentifier + * + * @experimental + */ +final class BuiltinType extends Type +{ + /** + * @param T $typeIdentifier + */ + public function __construct( + private readonly TypeIdentifier $typeIdentifier, + ) { + } + + public function getBaseType(): self|ObjectType + { + return $this; + } + + /** + * @return T + */ + public function getTypeIdentifier(): TypeIdentifier + { + return $this->typeIdentifier; + } + + public function isA(TypeIdentifier|string $subject): bool + { + if ($subject instanceof TypeIdentifier) { + return $this->getTypeIdentifier() === $subject; + } + + try { + return TypeIdentifier::from($subject) === $this->getTypeIdentifier(); + } catch (\ValueError) { + return false; + } + } + + /** + * @return self|UnionType|BuiltinType|BuiltinType|BuiltinType|BuiltinType|BuiltinType|BuiltinType> + */ + public function asNonNullable(): self|UnionType + { + if (TypeIdentifier::NULL === $this->typeIdentifier) { + throw new LogicException('"null" cannot be turned as non nullable.'); + } + + // "mixed" is an alias of "object|resource|array|string|float|int|bool|null" + // therefore, its non-nullable version is "object|resource|array|string|float|int|bool" + if (TypeIdentifier::MIXED === $this->typeIdentifier) { + return new UnionType( + new self(TypeIdentifier::OBJECT), + new self(TypeIdentifier::RESOURCE), + new self(TypeIdentifier::ARRAY), + new self(TypeIdentifier::STRING), + new self(TypeIdentifier::FLOAT), + new self(TypeIdentifier::INT), + new self(TypeIdentifier::BOOL), + ); + } + + return $this; + } + + public function __toString(): string + { + return $this->typeIdentifier->value; + } +} diff --git a/vendor/symfony/type-info/Type/CollectionType.php b/vendor/symfony/type-info/Type/CollectionType.php new file mode 100644 index 0000000..4e26291 --- /dev/null +++ b/vendor/symfony/type-info/Type/CollectionType.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Exception\InvalidArgumentException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * Represents a key/value collection type. + * + * It proxies every method to the main type and adds methods related to key and value types. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of BuiltinType|BuiltinType|ObjectType|GenericType + * + * @experimental + */ +final class CollectionType extends Type +{ + /** + * @param T $type + */ + public function __construct( + private readonly BuiltinType|ObjectType|GenericType $type, + private readonly bool $isList = false, + ) { + if ($this->isList()) { + $keyType = $this->getCollectionKeyType(); + + if (!$keyType instanceof BuiltinType || TypeIdentifier::INT !== $keyType->getTypeIdentifier()) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid list key type.', (string) $keyType)); + } + } + } + + public function getBaseType(): BuiltinType|ObjectType + { + return $this->getType()->getBaseType(); + } + + /** + * @return T + */ + public function getType(): BuiltinType|ObjectType|GenericType + { + return $this->type; + } + + public function isA(TypeIdentifier|string $subject): bool + { + return $this->getType()->isA($subject); + } + + public function isList(): bool + { + return $this->isList; + } + + public function asNonNullable(): self + { + return $this; + } + + public function getCollectionKeyType(): Type + { + $defaultCollectionKeyType = self::union(self::int(), self::string()); + + if ($this->type instanceof GenericType) { + return match (\count($this->type->getVariableTypes())) { + 2 => $this->type->getVariableTypes()[0], + 1 => self::int(), + default => $defaultCollectionKeyType, + }; + } + + return $defaultCollectionKeyType; + } + + public function getCollectionValueType(): Type + { + $defaultCollectionValueType = self::mixed(); + + if ($this->type instanceof GenericType) { + return match (\count($this->type->getVariableTypes())) { + 2 => $this->type->getVariableTypes()[1], + 1 => $this->type->getVariableTypes()[0], + default => $defaultCollectionValueType, + }; + } + + return $defaultCollectionValueType; + } + + public function __toString(): string + { + return (string) $this->type; + } + + /** + * Proxies all method calls to the original type. + * + * @param list $arguments + */ + public function __call(string $method, array $arguments): mixed + { + return $this->type->{$method}(...$arguments); + } +} diff --git a/vendor/symfony/type-info/Type/CompositeTypeTrait.php b/vendor/symfony/type-info/Type/CompositeTypeTrait.php new file mode 100644 index 0000000..9fa4f7b --- /dev/null +++ b/vendor/symfony/type-info/Type/CompositeTypeTrait.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Exception\InvalidArgumentException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @internal + * + * @template T of Type + */ +trait CompositeTypeTrait +{ + /** + * @var list + */ + private readonly array $types; + + /** + * @param list $types + */ + public function __construct(Type ...$types) + { + if (\count($types) < 2) { + throw new InvalidArgumentException(sprintf('"%s" expects at least 2 types.', self::class)); + } + + foreach ($types as $t) { + if ($t instanceof self) { + throw new InvalidArgumentException(sprintf('Cannot set "%s" as a "%1$s" part.', self::class)); + } + } + + usort($types, fn (Type $a, Type $b): int => (string) $a <=> (string) $b); + $this->types = array_values(array_unique($types)); + } + + public function isA(TypeIdentifier|string $subject): bool + { + return $this->is(fn (Type $type) => $type->isA($subject)); + } + + /** + * @return list + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * @param callable(T): bool $callable + */ + public function atLeastOneTypeIs(callable $callable): bool + { + foreach ($this->types as $t) { + if ($callable($t)) { + return true; + } + } + + return false; + } + + /** + * @param callable(T): bool $callable + */ + public function everyTypeIs(callable $callable): bool + { + foreach ($this->types as $t) { + if (!$callable($t)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/type-info/Type/EnumType.php b/vendor/symfony/type-info/Type/EnumType.php new file mode 100644 index 0000000..97d7dc2 --- /dev/null +++ b/vendor/symfony/type-info/Type/EnumType.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of class-string<\UnitEnum> + * + * @extends ObjectType + * + * @experimental + */ +class EnumType extends ObjectType +{ +} diff --git a/vendor/symfony/type-info/Type/GenericType.php b/vendor/symfony/type-info/Type/GenericType.php new file mode 100644 index 0000000..2439444 --- /dev/null +++ b/vendor/symfony/type-info/Type/GenericType.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * Represents a generic type, which is a type that holds variable parts. + * + * It proxies every method to the main type and adds methods related to variable types. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of BuiltinType|BuiltinType|ObjectType + * + * @experimental + */ +final class GenericType extends Type +{ + /** + * @var list + */ + private readonly array $variableTypes; + + /** + * @param T $type + */ + public function __construct( + private readonly BuiltinType|ObjectType $type, + Type ...$variableTypes, + ) { + $this->variableTypes = $variableTypes; + } + + public function getBaseType(): BuiltinType|ObjectType + { + return $this->getType(); + } + + /** + * @return T + */ + public function getType(): BuiltinType|ObjectType + { + return $this->type; + } + + public function isA(TypeIdentifier|string $subject): bool + { + return $this->getType()->isA($subject); + } + + public function asNonNullable(): self + { + return $this; + } + + /** + * @return list + */ + public function getVariableTypes(): array + { + return $this->variableTypes; + } + + public function __toString(): string + { + $typeString = (string) $this->type; + + $variableTypesString = ''; + $glue = ''; + foreach ($this->variableTypes as $t) { + $variableTypesString .= $glue.((string) $t); + $glue = ','; + } + + return $typeString.'<'.$variableTypesString.'>'; + } + + /** + * Proxies all method calls to the original type. + * + * @param list $arguments + */ + public function __call(string $method, array $arguments): mixed + { + return $this->type->{$method}(...$arguments); + } +} diff --git a/vendor/symfony/type-info/Type/IntersectionType.php b/vendor/symfony/type-info/Type/IntersectionType.php new file mode 100644 index 0000000..b8547c8 --- /dev/null +++ b/vendor/symfony/type-info/Type/IntersectionType.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of Type + * + * @experimental + */ +final class IntersectionType extends Type +{ + /** + * @use CompositeTypeTrait + */ + use CompositeTypeTrait; + + public function is(callable $callable): bool + { + return $this->everyTypeIs($callable); + } + + public function __toString(): string + { + $string = ''; + $glue = ''; + + foreach ($this->types as $t) { + $string .= $glue.($t instanceof UnionType ? '('.((string) $t).')' : ((string) $t)); + $glue = '&'; + } + + return $string; + } + + /** + * @throws LogicException + */ + public function getBaseType(): BuiltinType|ObjectType + { + throw new LogicException(sprintf('Cannot get base type on "%s" compound type.', $this)); + } + + /** + * @throws LogicException + */ + public function asNonNullable(): self + { + if ($this->isNullable()) { + throw new LogicException(sprintf('"%s cannot be turned as non nullable.', (string) $this)); + } + + return $this; + } +} diff --git a/vendor/symfony/type-info/Type/ObjectType.php b/vendor/symfony/type-info/Type/ObjectType.php new file mode 100644 index 0000000..5d35278 --- /dev/null +++ b/vendor/symfony/type-info/Type/ObjectType.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of class-string + * + * @experimental + */ +class ObjectType extends Type +{ + /** + * @param T $className + */ + public function __construct( + private readonly string $className, + ) { + } + + public function getBaseType(): BuiltinType|self + { + return $this; + } + + public function getTypeIdentifier(): TypeIdentifier + { + return TypeIdentifier::OBJECT; + } + + public function isA(TypeIdentifier|string $subject): bool + { + if ($subject instanceof TypeIdentifier) { + return $this->getTypeIdentifier() === $subject; + } + + return is_a($this->getClassName(), $subject, allow_string: true); + } + + /** + * @return T + */ + public function getClassName(): string + { + return $this->className; + } + + public function asNonNullable(): static + { + return $this; + } + + public function __toString(): string + { + return $this->className; + } +} diff --git a/vendor/symfony/type-info/Type/TemplateType.php b/vendor/symfony/type-info/Type/TemplateType.php new file mode 100644 index 0000000..10a3a90 --- /dev/null +++ b/vendor/symfony/type-info/Type/TemplateType.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * Represents a template placeholder, such as "T" in "Collection". + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +final class TemplateType extends Type +{ + public function __construct( + private readonly string $name, + private readonly Type $bound, + ) { + } + + public function getBaseType(): BuiltinType|ObjectType + { + throw new LogicException(sprintf('Cannot get base type on "%s" template type.', $this)); + } + + public function isA(TypeIdentifier|string $subject): bool + { + return false; + } + + public function getName(): string + { + return $this->name; + } + + public function getBound(): Type + { + return $this->bound; + } + + public function asNonNullable(): self + { + return $this; + } + + public function __toString(): string + { + return $this->name; + } +} diff --git a/vendor/symfony/type-info/Type/UnionType.php b/vendor/symfony/type-info/Type/UnionType.php new file mode 100644 index 0000000..70802f0 --- /dev/null +++ b/vendor/symfony/type-info/Type/UnionType.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Type; + +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @template T of Type + * + * @experimental + */ +final class UnionType extends Type +{ + /** + * @use CompositeTypeTrait + */ + use CompositeTypeTrait; + + public function is(callable $callable): bool + { + return $this->atLeastOneTypeIs($callable); + } + + /** + * @throws LogicException + */ + public function getBaseType(): BuiltinType|ObjectType + { + $nonNullableType = $this->asNonNullable(); + if (!$nonNullableType instanceof self) { + return $nonNullableType->getBaseType(); + } + + throw new LogicException(sprintf('Cannot get base type on "%s" compound type.', $this)); + } + + public function asNonNullable(): Type + { + $nonNullableTypes = []; + foreach ($this->getTypes() as $type) { + if ($type->isA(TypeIdentifier::NULL)) { + continue; + } + + $nonNullableType = $type->asNonNullable(); + $nonNullableTypes = [ + ...$nonNullableTypes, + ...($nonNullableType instanceof self ? $nonNullableType->getTypes() : [$nonNullableType]), + ]; + } + + return \count($nonNullableTypes) > 1 ? new self(...$nonNullableTypes) : $nonNullableTypes[0]; + } + + public function __toString(): string + { + $string = ''; + $glue = ''; + + foreach ($this->types as $t) { + $string .= $glue.($t instanceof IntersectionType ? '('.((string) $t).')' : ((string) $t)); + $glue = '|'; + } + + return $string; + } +} diff --git a/vendor/symfony/type-info/TypeContext/TypeContext.php b/vendor/symfony/type-info/TypeContext/TypeContext.php new file mode 100644 index 0000000..dba4d3f --- /dev/null +++ b/vendor/symfony/type-info/TypeContext/TypeContext.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeContext; + +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; + +/** + * Type resolving context. + * + * Helps to retrieve declaring class, called class, parent class, templates + * and normalize classes according to the current namespace and uses. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +final class TypeContext +{ + /** + * @var array + */ + private static array $classExistCache = []; + + /** + * @param array $uses + * @param array $templates + */ + public function __construct( + public readonly string $calledClassName, + public readonly string $declaringClassName, + public readonly ?string $namespace = null, + public readonly array $uses = [], + public readonly array $templates = [], + ) { + } + + /** + * Normalize class name according to current namespace and uses. + */ + public function normalize(string $name): string + { + if (str_starts_with($name, '\\')) { + return ltrim($name, '\\'); + } + + $nameParts = explode('\\', $name); + $firstNamePart = $nameParts[0]; + if (isset($this->uses[$firstNamePart])) { + if (1 === \count($nameParts)) { + return $this->uses[$firstNamePart]; + } + array_shift($nameParts); + + return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts)); + } + + if (null !== $this->namespace) { + return sprintf('%s\\%s', $this->namespace, $name); + } + + return $name; + } + + /** + * @return class-string + */ + public function getDeclaringClass(): string + { + return $this->normalize($this->declaringClassName); + } + + /** + * @return class-string + */ + public function getCalledClass(): string + { + return $this->normalize($this->calledClassName); + } + + /** + * @return class-string + */ + public function getParentClass(): string + { + $declaringClassName = $this->getDeclaringClass(); + + if (false === $parentClass = get_parent_class($declaringClassName)) { + throw new LogicException(sprintf('"%s" do not extend any class.', $declaringClassName)); + } + + if (!isset(self::$classExistCache[$parentClass])) { + self::$classExistCache[$parentClass] = false; + + if (class_exists($parentClass)) { + self::$classExistCache[$parentClass] = true; + } else { + try { + new \ReflectionClass($parentClass); + self::$classExistCache[$parentClass] = true; + } catch (\Throwable) { + } + } + } + + return self::$classExistCache[$parentClass] ? $parentClass : $this->normalize(str_replace($this->namespace.'\\', '', $parentClass)); + } +} diff --git a/vendor/symfony/type-info/TypeContext/TypeContextFactory.php b/vendor/symfony/type-info/TypeContext/TypeContextFactory.php new file mode 100644 index 0000000..97c7090 --- /dev/null +++ b/vendor/symfony/type-info/TypeContext/TypeContextFactory.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeContext; + +use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; +use Symfony\Component\TypeInfo\Exception\RuntimeException; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver; + +/** + * Creates a type resolving context. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +final class TypeContextFactory +{ + /** + * @var array + */ + private static array $reflectionClassCache = []; + + private ?Lexer $phpstanLexer = null; + private ?PhpDocParser $phpstanParser = null; + + public function __construct( + private readonly ?StringTypeResolver $stringTypeResolver = null, + ) { + } + + public function createFromClassName(string $calledClassName, ?string $declaringClassName = null): TypeContext + { + $declaringClassName ??= $calledClassName; + + $calledClassPath = explode('\\', $calledClassName); + $declaringClassPath = explode('\\', $declaringClassName); + + $declaringClassReflection = self::$reflectionClassCache[$declaringClassName] ??= new \ReflectionClass($declaringClassName); + + $typeContext = new TypeContext( + end($calledClassPath), + end($declaringClassPath), + trim($declaringClassReflection->getNamespaceName(), '\\'), + $this->collectUses($declaringClassReflection), + ); + + return new TypeContext( + $typeContext->calledClassName, + $typeContext->declaringClassName, + $typeContext->namespace, + $typeContext->uses, + $this->collectTemplates($declaringClassReflection, $typeContext), + ); + } + + public function createFromReflection(\Reflector $reflection): ?TypeContext + { + $declaringClassReflection = match (true) { + $reflection instanceof \ReflectionClass => $reflection, + $reflection instanceof \ReflectionMethod => $reflection->getDeclaringClass(), + $reflection instanceof \ReflectionProperty => $reflection->getDeclaringClass(), + $reflection instanceof \ReflectionParameter => $reflection->getDeclaringClass(), + $reflection instanceof \ReflectionFunctionAbstract => $reflection->getClosureScopeClass(), + default => null, + }; + + if (null === $declaringClassReflection) { + return null; + } + + $typeContext = new TypeContext( + $declaringClassReflection->getShortName(), + $declaringClassReflection->getShortName(), + $declaringClassReflection->getNamespaceName(), + $this->collectUses($declaringClassReflection), + ); + + $templates = match (true) { + $reflection instanceof \ReflectionFunctionAbstract => $this->collectTemplates($reflection, $typeContext) + $this->collectTemplates($declaringClassReflection, $typeContext), + $reflection instanceof \ReflectionParameter => $this->collectTemplates($reflection->getDeclaringFunction(), $typeContext) + $this->collectTemplates($declaringClassReflection, $typeContext), + default => $this->collectTemplates($declaringClassReflection, $typeContext), + }; + + return new TypeContext( + $typeContext->calledClassName, + $typeContext->declaringClassName, + $typeContext->namespace, + $typeContext->uses, + $templates, + ); + } + + /** + * @return array + */ + private function collectUses(\ReflectionClass $reflection): array + { + $fileName = $reflection->getFileName(); + if (!\is_string($fileName) || !is_file($fileName)) { + return []; + } + + if (false === $lines = @file($fileName)) { + throw new RuntimeException(sprintf('Unable to read file "%s".', $fileName)); + } + + $uses = []; + $inUseSection = false; + + foreach ($lines as $line) { + if (str_starts_with($line, 'use ')) { + $inUseSection = true; + $use = explode(' as ', substr($line, 4, -2), 2); + + $alias = 1 === \count($use) ? substr($use[0], false !== ($p = strrpos($use[0], '\\')) ? 1 + $p : 0) : $use[1]; + $uses[$alias] = $use[0]; + } elseif ($inUseSection) { + break; + } + } + + $traitUses = []; + foreach ($reflection->getTraits() as $traitReflection) { + $traitUses[] = $this->collectUses($traitReflection); + } + + return array_merge($uses, ...$traitUses); + } + + /** + * @return array + */ + private function collectTemplates(\ReflectionClass|\ReflectionFunctionAbstract $reflection, TypeContext $typeContext): array + { + if (!$this->stringTypeResolver || !class_exists(PhpDocParser::class)) { + return []; + } + + if (!$rawDocNode = $reflection->getDocComment()) { + return []; + } + + $this->phpstanLexer ??= new Lexer(); + $this->phpstanParser ??= new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser()); + + $tokens = new TokenIterator($this->phpstanLexer->tokenize($rawDocNode)); + + $templates = []; + foreach ($this->phpstanParser->parse($tokens)->getTagsByName('@template') as $tag) { + if (!$tag->value instanceof TemplateTagValueNode) { + continue; + } + + $type = Type::mixed(); + $typeString = ((string) $tag->value->bound) ?: null; + + try { + if (null !== $typeString) { + $type = $this->stringTypeResolver->resolve($typeString, $typeContext); + } + } catch (UnsupportedException) { + } + + $templates[$tag->value->name] = $type; + } + + return $templates; + } +} diff --git a/vendor/symfony/type-info/TypeFactoryTrait.php b/vendor/symfony/type-info/TypeFactoryTrait.php new file mode 100644 index 0000000..d87737d --- /dev/null +++ b/vendor/symfony/type-info/TypeFactoryTrait.php @@ -0,0 +1,316 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo; + +use Symfony\Component\TypeInfo\Type\BackedEnumType; +use Symfony\Component\TypeInfo\Type\BuiltinType; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\EnumType; +use Symfony\Component\TypeInfo\Type\GenericType; +use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\Type\TemplateType; +use Symfony\Component\TypeInfo\Type\UnionType; + +/** + * Helper trait to create any type easily. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +trait TypeFactoryTrait +{ + /** + * @template T of TypeIdentifier + * @template U value-of + * + * @param T|U $identifier + * + * @return BuiltinType + */ + public static function builtin(TypeIdentifier|string $identifier): BuiltinType + { + /** @var T $identifier */ + $identifier = \is_string($identifier) ? TypeIdentifier::from($identifier) : $identifier; + + return new BuiltinType($identifier); + } + + /** + * @return BuiltinType + */ + public static function int(): BuiltinType + { + return self::builtin(TypeIdentifier::INT); + } + + /** + * @return BuiltinType + */ + public static function float(): BuiltinType + { + return self::builtin(TypeIdentifier::FLOAT); + } + + /** + * @return BuiltinType + */ + public static function string(): BuiltinType + { + return self::builtin(TypeIdentifier::STRING); + } + + /** + * @return BuiltinType + */ + public static function bool(): BuiltinType + { + return self::builtin(TypeIdentifier::BOOL); + } + + /** + * @return BuiltinType + */ + public static function resource(): BuiltinType + { + return self::builtin(TypeIdentifier::RESOURCE); + } + + /** + * @return BuiltinType + */ + public static function false(): BuiltinType + { + return self::builtin(TypeIdentifier::FALSE); + } + + /** + * @return BuiltinType + */ + public static function true(): BuiltinType + { + return self::builtin(TypeIdentifier::TRUE); + } + + /** + * @return BuiltinType + */ + public static function callable(): BuiltinType + { + return self::builtin(TypeIdentifier::CALLABLE); + } + + /** + * @return BuiltinType + */ + public static function mixed(): BuiltinType + { + return self::builtin(TypeIdentifier::MIXED); + } + + /** + * @return BuiltinType + */ + public static function null(): BuiltinType + { + return self::builtin(TypeIdentifier::NULL); + } + + /** + * @return BuiltinType + */ + public static function void(): BuiltinType + { + return self::builtin(TypeIdentifier::VOID); + } + + /** + * @return BuiltinType + */ + public static function never(): BuiltinType + { + return self::builtin(TypeIdentifier::NEVER); + } + + /** + * @template T of BuiltinType|BuiltinType|ObjectType|GenericType + * + * @param T $type + * + * @return CollectionType + */ + public static function collection(BuiltinType|ObjectType|GenericType $type, ?Type $value = null, ?Type $key = null, bool $asList = false): CollectionType + { + if (!$type instanceof GenericType && (null !== $value || null !== $key)) { + $type = self::generic($type, $key ?? self::union(self::int(), self::string()), $value ?? self::mixed()); + } + + return new CollectionType($type, $asList); + } + + /** + * @return CollectionType> + */ + public static function array(?Type $value = null, ?Type $key = null, bool $asList = false): CollectionType + { + return self::collection(self::builtin(TypeIdentifier::ARRAY), $value, $key, $asList); + } + + /** + * @return CollectionType> + */ + public static function iterable(?Type $value = null, ?Type $key = null, bool $asList = false): CollectionType + { + return self::collection(self::builtin(TypeIdentifier::ITERABLE), $value, $key, $asList); + } + + /** + * @return CollectionType> + */ + public static function list(?Type $value = null): CollectionType + { + return self::array($value, self::int(), asList: true); + } + + /** + * @return CollectionType> + */ + public static function dict(?Type $value = null): CollectionType + { + return self::array($value, self::string()); + } + + /** + * @template T of class-string + * + * @param T|null $className + * + * @return ($className is class-string ? ObjectType : BuiltinType) + */ + public static function object(?string $className = null): BuiltinType|ObjectType + { + return null !== $className ? new ObjectType($className) : new BuiltinType(TypeIdentifier::OBJECT); + } + + /** + * @template T of class-string<\UnitEnum>|class-string<\BackedEnum> + * @template U of BuiltinType|BuiltinType + * + * @param T $className + * @param U|null $backingType + * + * @return ($className is class-string<\BackedEnum> ? ($backingType is U ? BackedEnumType : BackedEnumType|BuiltinType>) : EnumType)) + */ + public static function enum(string $className, ?BuiltinType $backingType = null): EnumType + { + if (is_subclass_of($className, \BackedEnum::class)) { + if (null === $backingType) { + $reflectionBackingType = (new \ReflectionEnum($className))->getBackingType(); + $typeIdentifier = TypeIdentifier::INT->value === (string) $reflectionBackingType ? TypeIdentifier::INT : TypeIdentifier::STRING; + $backingType = new BuiltinType($typeIdentifier); + } + + return new BackedEnumType($className, $backingType); + } + + return new EnumType($className); + } + + /** + * @template T of BuiltinType|BuiltinType|ObjectType + * + * @param T $mainType + * + * @return GenericType + */ + public static function generic(Type $mainType, Type ...$variableTypes): GenericType + { + return new GenericType($mainType, ...$variableTypes); + } + + public static function template(string $name, ?Type $bound = null): TemplateType + { + return new TemplateType($name, $bound ?? Type::mixed()); + } + + /** + * @template T of Type + * + * @param list $types + * + * @return UnionType + */ + public static function union(Type ...$types): UnionType + { + /** @var list $unionTypes */ + $unionTypes = []; + + foreach ($types as $type) { + if (!$type instanceof UnionType) { + $unionTypes[] = $type; + + continue; + } + + foreach ($type->getTypes() as $unionType) { + $unionTypes[] = $unionType; + } + } + + return new UnionType(...$unionTypes); + } + + /** + * @template T of Type + * + * @param list $types + * + * @return IntersectionType + */ + public static function intersection(Type ...$types): IntersectionType + { + /** @var list $intersectionTypes */ + $intersectionTypes = []; + + foreach ($types as $type) { + if (!$type instanceof IntersectionType) { + $intersectionTypes[] = $type; + + continue; + } + + foreach ($type->getTypes() as $intersectionType) { + $intersectionTypes[] = $intersectionType; + } + } + + return new IntersectionType(...$intersectionTypes); + } + + /** + * @template T of Type + * + * @param T $type + * + * @return (T is UnionType ? T : UnionType>) + */ + public static function nullable(Type $type): UnionType + { + if ($type instanceof UnionType) { + return Type::union(Type::null(), ...$type->getTypes()); + } + + return Type::union($type, Type::null()); + } +} diff --git a/vendor/symfony/type-info/TypeIdentifier.php b/vendor/symfony/type-info/TypeIdentifier.php new file mode 100644 index 0000000..45bd547 --- /dev/null +++ b/vendor/symfony/type-info/TypeIdentifier.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo; + +/** + * Identifier of a PHP native type. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +enum TypeIdentifier: string +{ + case ARRAY = 'array'; + case BOOL = 'bool'; + case CALLABLE = 'callable'; + case FALSE = 'false'; + case FLOAT = 'float'; + case INT = 'int'; + case ITERABLE = 'iterable'; + case MIXED = 'mixed'; + case NULL = 'null'; + case OBJECT = 'object'; + case RESOURCE = 'resource'; + case STRING = 'string'; + case TRUE = 'true'; + case NEVER = 'never'; + case VOID = 'void'; + + /** + * @return list + */ + public static function values(): array + { + return array_column(self::cases(), 'value'); + } +} diff --git a/vendor/symfony/type-info/TypeResolver/ReflectionParameterTypeResolver.php b/vendor/symfony/type-info/TypeResolver/ReflectionParameterTypeResolver.php new file mode 100644 index 0000000..93607a5 --- /dev/null +++ b/vendor/symfony/type-info/TypeResolver/ReflectionParameterTypeResolver.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeResolver; + +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContext; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; + +/** + * Resolves type for a given parameter reflection. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @internal + */ +final readonly class ReflectionParameterTypeResolver implements TypeResolverInterface +{ + public function __construct( + private ReflectionTypeResolver $reflectionTypeResolver, + private TypeContextFactory $typeContextFactory, + ) { + } + + public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type + { + if (!$subject instanceof \ReflectionParameter) { + throw new UnsupportedException(sprintf('Expected subject to be a "ReflectionParameter", "%s" given.', get_debug_type($subject)), $subject); + } + + $typeContext ??= $this->typeContextFactory->createFromReflection($subject); + + try { + return $this->reflectionTypeResolver->resolve($subject->getType(), $typeContext); + } catch (UnsupportedException $e) { + $path = null !== $typeContext + ? sprintf('%s::%s($%s)', $typeContext->calledClassName, $subject->getDeclaringFunction()->getName(), $subject->getName()) + : sprintf('%s($%s)', $subject->getDeclaringFunction()->getName(), $subject->getName()); + + throw new UnsupportedException(sprintf('Cannot resolve type for "%s".', $path), $subject, previous: $e); + } + } +} diff --git a/vendor/symfony/type-info/TypeResolver/ReflectionPropertyTypeResolver.php b/vendor/symfony/type-info/TypeResolver/ReflectionPropertyTypeResolver.php new file mode 100644 index 0000000..4737062 --- /dev/null +++ b/vendor/symfony/type-info/TypeResolver/ReflectionPropertyTypeResolver.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeResolver; + +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContext; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; + +/** + * Resolves type for a given property reflection. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @internal + */ +final readonly class ReflectionPropertyTypeResolver implements TypeResolverInterface +{ + public function __construct( + private ReflectionTypeResolver $reflectionTypeResolver, + private TypeContextFactory $typeContextFactory, + ) { + } + + public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type + { + if (!$subject instanceof \ReflectionProperty) { + throw new UnsupportedException(sprintf('Expected subject to be a "ReflectionProperty", "%s" given.', get_debug_type($subject)), $subject); + } + + $typeContext ??= $this->typeContextFactory->createFromReflection($subject); + + try { + return $this->reflectionTypeResolver->resolve($subject->getType(), $typeContext); + } catch (UnsupportedException $e) { + $path = sprintf('%s::$%s', $subject->getDeclaringClass()->getName(), $subject->getName()); + + throw new UnsupportedException(sprintf('Cannot resolve type for "%s".', $path), $subject, previous: $e); + } + } +} diff --git a/vendor/symfony/type-info/TypeResolver/ReflectionReturnTypeResolver.php b/vendor/symfony/type-info/TypeResolver/ReflectionReturnTypeResolver.php new file mode 100644 index 0000000..0dedaaa --- /dev/null +++ b/vendor/symfony/type-info/TypeResolver/ReflectionReturnTypeResolver.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeResolver; + +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContext; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; + +/** + * Resolves return type for a given function reflection. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @internal + */ +final readonly class ReflectionReturnTypeResolver implements TypeResolverInterface +{ + public function __construct( + private ReflectionTypeResolver $reflectionTypeResolver, + private TypeContextFactory $typeContextFactory, + ) { + } + + public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type + { + if (!$subject instanceof \ReflectionFunctionAbstract) { + throw new UnsupportedException(sprintf('Expected subject to be a "ReflectionFunctionAbstract", "%s" given.', get_debug_type($subject)), $subject); + } + + $typeContext ??= $this->typeContextFactory->createFromReflection($subject); + + try { + return $this->reflectionTypeResolver->resolve($subject->getReturnType(), $typeContext); + } catch (UnsupportedException $e) { + $path = null !== $typeContext + ? sprintf('%s::%s()', $typeContext->calledClassName, $subject->getName()) + : sprintf('%s()', $subject->getName()); + + throw new UnsupportedException(sprintf('Cannot resolve type for "%s".', $path), $subject, previous: $e); + } + } +} diff --git a/vendor/symfony/type-info/TypeResolver/ReflectionTypeResolver.php b/vendor/symfony/type-info/TypeResolver/ReflectionTypeResolver.php new file mode 100644 index 0000000..6af8feb --- /dev/null +++ b/vendor/symfony/type-info/TypeResolver/ReflectionTypeResolver.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeResolver; + +use Symfony\Component\TypeInfo\Exception\InvalidArgumentException; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContext; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * Resolves type for a given type reflection. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @internal + */ +final class ReflectionTypeResolver implements TypeResolverInterface +{ + /** + * @var array + */ + private static array $reflectionEnumCache = []; + + public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type + { + if ($subject instanceof \ReflectionUnionType) { + return Type::union(...array_map(fn (mixed $t): Type => $this->resolve($t, $typeContext), $subject->getTypes())); + } + + if ($subject instanceof \ReflectionIntersectionType) { + return Type::intersection(...array_map(fn (mixed $t): Type => $this->resolve($t, $typeContext), $subject->getTypes())); + } + + if (!$subject instanceof \ReflectionNamedType) { + throw new UnsupportedException(sprintf('Expected subject to be a "ReflectionNamedType", a "ReflectionUnionType" or a "ReflectionIntersectionType", "%s" given.', get_debug_type($subject)), $subject); + } + + $identifier = $subject->getName(); + $nullable = $subject->allowsNull(); + + if (TypeIdentifier::ARRAY->value === $identifier) { + $type = Type::array(); + + return $nullable ? Type::nullable($type) : $type; + } + + if (TypeIdentifier::ITERABLE->value === $identifier) { + $type = Type::iterable(); + + return $nullable ? Type::nullable($type) : $type; + } + + if (TypeIdentifier::NULL->value === $identifier || TypeIdentifier::MIXED->value === $identifier) { + return Type::builtin($identifier); + } + + if ($subject->isBuiltin()) { + $type = Type::builtin(TypeIdentifier::from($identifier)); + + return $nullable ? Type::nullable($type) : $type; + } + + if (\in_array(strtolower($identifier), ['self', 'static', 'parent'], true) && !$typeContext) { + throw new InvalidArgumentException(sprintf('A "%s" must be provided to resolve "%s".', TypeContext::class, strtolower($identifier))); + } + + /** @var class-string $className */ + $className = match (true) { + 'self' === strtolower($identifier) => $typeContext->getDeclaringClass(), + 'static' === strtolower($identifier) => $typeContext->getCalledClass(), + 'parent' === strtolower($identifier) => $typeContext->getParentClass(), + default => $identifier, + }; + + if (is_subclass_of($className, \BackedEnum::class)) { + $reflectionEnum = (self::$reflectionEnumCache[$className] ??= new \ReflectionEnum($className)); + $backingType = $this->resolve($reflectionEnum->getBackingType(), $typeContext); + $type = Type::enum($className, $backingType); + } elseif (is_subclass_of($className, \UnitEnum::class)) { + $type = Type::enum($className); + } else { + $type = Type::object($className); + } + + return $nullable ? Type::nullable($type) : $type; + } +} diff --git a/vendor/symfony/type-info/TypeResolver/StringTypeResolver.php b/vendor/symfony/type-info/TypeResolver/StringTypeResolver.php new file mode 100644 index 0000000..aa24a63 --- /dev/null +++ b/vendor/symfony/type-info/TypeResolver/StringTypeResolver.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeResolver; + +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; +use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; +use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; +use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; +use Symfony\Component\TypeInfo\Exception\InvalidArgumentException; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\GenericType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\TypeContext\TypeContext; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * Resolves type for a given string. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @internal + */ +final class StringTypeResolver implements TypeResolverInterface +{ + /** + * @var array + */ + private static array $classExistCache = []; + + private readonly Lexer $lexer; + private readonly TypeParser $parser; + + public function __construct() + { + $this->lexer = new Lexer(); + $this->parser = new TypeParser(new ConstExprParser()); + } + + public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type + { + if ($subject instanceof \Stringable) { + $subject = (string) $subject; + } elseif (!\is_string($subject)) { + throw new UnsupportedException(sprintf('Expected subject to be a "string", "%s" given.', get_debug_type($subject)), $subject); + } + + try { + $tokens = new TokenIterator($this->lexer->tokenize($subject)); + $node = $this->parser->parse($tokens); + + return $this->getTypeFromNode($node, $typeContext); + } catch (\DomainException $e) { + throw new UnsupportedException(sprintf('Cannot resolve "%s".', $subject), $subject, previous: $e); + } + } + + private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Type + { + if ($node instanceof CallableTypeNode) { + return Type::callable(); + } + + if ($node instanceof ArrayTypeNode) { + return Type::list($this->getTypeFromNode($node->type, $typeContext)); + } + + if ($node instanceof ArrayShapeNode) { + return Type::array(); + } + + if ($node instanceof ObjectShapeNode) { + return Type::object(); + } + + if ($node instanceof ThisTypeNode) { + if (null === $typeContext) { + throw new InvalidArgumentException(sprintf('A "%s" must be provided to resolve "$this".', TypeContext::class)); + } + + return Type::object($typeContext->getCalledClass()); + } + + if ($node instanceof ConstTypeNode) { + return match ($node->constExpr::class) { + ConstExprArrayNode::class => Type::array(), + ConstExprFalseNode::class => Type::false(), + ConstExprFloatNode::class => Type::float(), + ConstExprIntegerNode::class => Type::int(), + ConstExprNullNode::class => Type::null(), + ConstExprStringNode::class => Type::string(), + ConstExprTrueNode::class => Type::true(), + default => throw new \DomainException(sprintf('Unhandled "%s" constant expression.', $node->constExpr::class)), + }; + } + + if ($node instanceof IdentifierTypeNode) { + $type = match ($node->name) { + 'bool', 'boolean' => Type::bool(), + 'true' => Type::true(), + 'false' => Type::false(), + 'int', 'integer', 'positive-int', 'negative-int', 'non-positive-int', 'non-negative-int', 'non-zero-int' => Type::int(), + 'float', 'double' => Type::float(), + 'string', + 'class-string', + 'trait-string', + 'interface-string', + 'callable-string', + 'numeric-string', + 'lowercase-string', + 'non-empty-lowercase-string', + 'non-empty-string', + 'non-falsy-string', + 'truthy-string', + 'literal-string', + 'html-escaped-string' => Type::string(), + 'resource' => Type::resource(), + 'object' => Type::object(), + 'callable' => Type::callable(), + 'array', 'non-empty-array' => Type::array(), + 'list', 'non-empty-list' => Type::list(), + 'iterable' => Type::iterable(), + 'mixed' => Type::mixed(), + 'null' => Type::null(), + 'array-key' => Type::union(Type::int(), Type::string()), + 'scalar' => Type::union(Type::int(), Type::float(), Type::string(), Type::bool()), + 'number' => Type::union(Type::int(), Type::float()), + 'numeric' => Type::union(Type::int(), Type::float(), Type::string()), + 'self' => $typeContext ? Type::object($typeContext->getDeclaringClass()) : throw new InvalidArgumentException(sprintf('A "%s" must be provided to resolve "self".', TypeContext::class)), + 'static' => $typeContext ? Type::object($typeContext->getCalledClass()) : throw new InvalidArgumentException(sprintf('A "%s" must be provided to resolve "static".', TypeContext::class)), + 'parent' => $typeContext ? Type::object($typeContext->getParentClass()) : throw new InvalidArgumentException(sprintf('A "%s" must be provided to resolve "parent".', TypeContext::class)), + 'void' => Type::void(), + 'never', 'never-return', 'never-returns', 'no-return' => Type::never(), + default => $this->resolveCustomIdentifier($node->name, $typeContext), + }; + + if ($type instanceof ObjectType && (is_a($type->getClassName(), \Traversable::class, true) || is_a($type->getClassName(), \ArrayAccess::class, true))) { + return Type::collection($type); + } + + return $type; + } + + if ($node instanceof NullableTypeNode) { + return Type::nullable($this->getTypeFromNode($node->type, $typeContext)); + } + + if ($node instanceof GenericTypeNode) { + $type = $this->getTypeFromNode($node->type, $typeContext); + + // handle integer ranges as simple integers + if ($type->isA(TypeIdentifier::INT)) { + return $type; + } + + $variableTypes = array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->genericTypes); + + if ($type instanceof CollectionType) { + $asList = $type->isList(); + $keyType = $type->getCollectionKeyType(); + + $type = $type->getType(); + if ($type instanceof GenericType) { + $type = $type->getType(); + } + + if (1 === \count($variableTypes)) { + return new CollectionType(Type::generic($type, $keyType, $variableTypes[0]), $asList); + } elseif (2 === \count($variableTypes)) { + return Type::collection($type, $variableTypes[1], $variableTypes[0], $asList); + } + } + + if ($type instanceof ObjectType && (is_a($type->getClassName(), \Traversable::class, true) || is_a($type->getClassName(), \ArrayAccess::class, true))) { + return match (\count($variableTypes)) { + 1 => Type::collection($type, $variableTypes[0]), + 2 => Type::collection($type, $variableTypes[1], $variableTypes[0]), + default => Type::collection($type), + }; + } + + return Type::generic($type, ...$variableTypes); + } + + if ($node instanceof UnionTypeNode) { + return Type::union(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->types)); + } + + if ($node instanceof IntersectionTypeNode) { + return Type::intersection(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->types)); + } + + throw new \DomainException(sprintf('Unhandled "%s" node.', $node::class)); + } + + private function resolveCustomIdentifier(string $identifier, ?TypeContext $typeContext): Type + { + $className = $typeContext ? $typeContext->normalize($identifier) : $identifier; + + if (!isset(self::$classExistCache[$className])) { + self::$classExistCache[$className] = false; + + if (class_exists($className) || interface_exists($className)) { + self::$classExistCache[$className] = true; + } else { + try { + new \ReflectionClass($className); + self::$classExistCache[$className] = true; + + return Type::object($className); + } catch (\Throwable) { + } + } + } + + if (self::$classExistCache[$className]) { + return Type::object($className); + } + + if (isset($typeContext?->templates[$identifier])) { + return Type::template($identifier, $typeContext->templates[$identifier]); + } + + throw new \DomainException(sprintf('Unhandled "%s" identifier.', $identifier)); + } +} diff --git a/vendor/symfony/type-info/TypeResolver/TypeResolver.php b/vendor/symfony/type-info/TypeResolver/TypeResolver.php new file mode 100644 index 0000000..c2e8996 --- /dev/null +++ b/vendor/symfony/type-info/TypeResolver/TypeResolver.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeResolver; + +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use Psr\Container\ContainerInterface; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContext; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; + +/** + * Resolves type for a given subject by delegating resolving to nested type resolvers. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +final readonly class TypeResolver implements TypeResolverInterface +{ + /** + * @param ContainerInterface $resolvers Locator of type resolvers, keyed by supported subject type + */ + public function __construct( + private ContainerInterface $resolvers, + ) { + } + + public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type + { + $subjectType = match (\is_object($subject)) { + true => match (true) { + is_subclass_of($subject::class, \ReflectionType::class) => \ReflectionType::class, + is_subclass_of($subject::class, \ReflectionFunctionAbstract::class) => \ReflectionFunctionAbstract::class, + default => $subject::class, + }, + false => get_debug_type($subject), + }; + + if (!$this->resolvers->has($subjectType)) { + if ('string' === $subjectType) { + throw new UnsupportedException('Cannot find any resolver for "string" type. Try running "composer require phpstan/phpdoc-parser".', $subject); + } + + throw new UnsupportedException(sprintf('Cannot find any resolver for "%s" type.', $subjectType), $subject); + } + + /** @param TypeResolverInterface $resolver */ + $resolver = $this->resolvers->get($subjectType); + + return $resolver->resolve($subject, $typeContext); + } + + public static function create(): self + { + $resolvers = new class() implements ContainerInterface { + private readonly array $resolvers; + + public function __construct() + { + $stringTypeResolver = class_exists(PhpDocParser::class) ? new StringTypeResolver() : null; + $typeContextFactory = new TypeContextFactory($stringTypeResolver); + $reflectionTypeResolver = new ReflectionTypeResolver(); + + $resolvers = [ + \ReflectionType::class => $reflectionTypeResolver, + \ReflectionParameter::class => new ReflectionParameterTypeResolver($reflectionTypeResolver, $typeContextFactory), + \ReflectionProperty::class => new ReflectionPropertyTypeResolver($reflectionTypeResolver, $typeContextFactory), + \ReflectionFunctionAbstract::class => new ReflectionReturnTypeResolver($reflectionTypeResolver, $typeContextFactory), + ]; + + if (null !== $stringTypeResolver) { + $resolvers['string'] = $stringTypeResolver; + } + + $this->resolvers = $resolvers; + } + + public function has(string $id): bool + { + return isset($this->resolvers[$id]); + } + + public function get(string $id): TypeResolverInterface + { + return $this->resolvers[$id]; + } + }; + + return new self($resolvers); + } +} diff --git a/vendor/symfony/type-info/TypeResolver/TypeResolverInterface.php b/vendor/symfony/type-info/TypeResolver/TypeResolverInterface.php new file mode 100644 index 0000000..edb1be6 --- /dev/null +++ b/vendor/symfony/type-info/TypeResolver/TypeResolverInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\TypeResolver; + +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContext; + +/** + * Resolves type for a given subject. + * + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @experimental + */ +interface TypeResolverInterface +{ + /** + * Try to resolve a {@see Type} on a $subject. + * If the resolver cannot resolve the type, it will throw a {@see UnsupportedException}. + * + * @throws UnsupportedException + */ + public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type; +} diff --git a/vendor/symfony/type-info/composer.json b/vendor/symfony/type-info/composer.json new file mode 100644 index 0000000..54b1497 --- /dev/null +++ b/vendor/symfony/type-info/composer.json @@ -0,0 +1,48 @@ +{ + "name": "symfony/type-info", + "type": "library", + "description": "Extracts PHP types information.", + "keywords": [ + "type", + "phpdoc", + "phpstan", + "symfony" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-info": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\TypeInfo\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/var-dumper/CHANGELOG.md b/vendor/symfony/var-dumper/CHANGELOG.md new file mode 100644 index 0000000..6a63e88 --- /dev/null +++ b/vendor/symfony/var-dumper/CHANGELOG.md @@ -0,0 +1,103 @@ +CHANGELOG +========= + +7.1 +--- + + * Add support for new DOM extension classes in `DOMCaster` + +7.0 +--- + + * Add argument `$label` to `VarDumper::dump()` + * Require explicit argument when calling `VarDumper::setHandler()` + * Remove display of backtrace in `Twig_Template`, only `Twig\Template` is supported + +6.4 +--- + + * Dump uninitialized properties + +6.3 +--- + + * Add caster for `WeakMap` + * Add support of named arguments to `dd()` and `dump()` to display the argument name + * Add support for `Relay\Relay` + * Add display of invisible characters + +6.2 +--- + + * Add support for `FFI\CData` and `FFI\CType` + * Deprecate calling `VarDumper::setHandler()` without arguments + +5.4 +--- + + * Add ability to style integer and double values independently + * Add casters for Symfony's UUIDs and ULIDs + * Add support for `Fiber` + +5.2.0 +----- + + * added support for PHPUnit `--colors` option + * added `VAR_DUMPER_FORMAT=server` env var value support + * prevent replacing the handler when the `VAR_DUMPER_FORMAT` env var is set + +5.1.0 +----- + + * added `RdKafka` support + +4.4.0 +----- + + * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` + to configure casters & flags to use in tests + * added `ImagineCaster` and infrastructure to dump images + * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data + * added `UuidCaster` + * made all casters final + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added `DsCaster` to support dumping the contents of data structures from the Ds extension + +4.2.0 +----- + + * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` + +4.1.0 +----- + + * added a `ServerDumper` to send serialized Data clones to a server + * added a `ServerDumpCommand` and `DumpServer` to run a server collecting + and displaying dumps on a single place with multiple formats support + * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support + +4.0.0 +----- + + * support for passing `\ReflectionClass` instances to the `Caster::castObject()` + method has been dropped, pass class names as strings instead + * the `Data::getRawData()` method has been removed + * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + +3.4.0 +----- + + * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth + * deprecated `MongoCaster` + +2.7.0 +----- + + * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. diff --git a/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/vendor/symfony/var-dumper/Caster/AmqpCaster.php new file mode 100644 index 0000000..68b1a65 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/AmqpCaster.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Amqp related classes to array representation. + * + * @author Grégoire Pineau + * + * @final + */ +class AmqpCaster +{ + private const FLAGS = [ + \AMQP_DURABLE => 'AMQP_DURABLE', + \AMQP_PASSIVE => 'AMQP_PASSIVE', + \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', + \AMQP_AUTODELETE => 'AMQP_AUTODELETE', + \AMQP_INTERNAL => 'AMQP_INTERNAL', + \AMQP_NOLOCAL => 'AMQP_NOLOCAL', + \AMQP_AUTOACK => 'AMQP_AUTOACK', + \AMQP_IFEMPTY => 'AMQP_IFEMPTY', + \AMQP_IFUNUSED => 'AMQP_IFUNUSED', + \AMQP_MANDATORY => 'AMQP_MANDATORY', + \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE', + \AMQP_MULTIPLE => 'AMQP_MULTIPLE', + \AMQP_NOWAIT => 'AMQP_NOWAIT', + \AMQP_REQUEUE => 'AMQP_REQUEUE', + ]; + + private const EXCHANGE_TYPES = [ + \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', + \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', + \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', + \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', + ]; + + public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPConnection\x00login"])) { + return $a; + } + + // BC layer in the amqp lib + if (method_exists($c, 'getReadTimeout')) { + $timeout = $c->getReadTimeout(); + } else { + $timeout = $c->getTimeout(); + } + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'login' => $c->getLogin(), + $prefix.'password' => $c->getPassword(), + $prefix.'host' => $c->getHost(), + $prefix.'vhost' => $c->getVhost(), + $prefix.'port' => $c->getPort(), + $prefix.'read_timeout' => $timeout, + ]; + + return $a; + } + + public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'channel_id' => $c->getChannelId(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPChannel\x00connection"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'prefetch_size' => $c->getPrefetchSize(), + $prefix.'prefetch_count' => $c->getPrefetchCount(), + ]; + + return $a; + } + + public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPQueue\x00name"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType(); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPExchange\x00name"])) { + $a["\x00AMQPExchange\x00type"] = $type; + + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'type' => $type, + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPEnvelope\x00body"])) { + $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode; + + return $a; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $a += [$prefix.'body' => $c->getBody()]; + } + + $a += [ + $prefix.'delivery_tag' => $c->getDeliveryTag(), + $prefix.'is_redelivery' => $c->isRedelivery(), + $prefix.'exchange_name' => $c->getExchangeName(), + $prefix.'routing_key' => $c->getRoutingKey(), + $prefix.'content_type' => $c->getContentType(), + $prefix.'content_encoding' => $c->getContentEncoding(), + $prefix.'headers' => $c->getHeaders(), + $prefix.'delivery_mode' => $deliveryMode, + $prefix.'priority' => $c->getPriority(), + $prefix.'correlation_id' => $c->getCorrelationId(), + $prefix.'reply_to' => $c->getReplyTo(), + $prefix.'expiration' => $c->getExpiration(), + $prefix.'message_id' => $c->getMessageId(), + $prefix.'timestamp' => $c->getTimeStamp(), + $prefix.'type' => $c->getType(), + $prefix.'user_id' => $c->getUserId(), + $prefix.'app_id' => $c->getAppId(), + ]; + + return $a; + } + + private static function extractFlags(int $flags): ConstStub + { + $flagsArray = []; + + foreach (self::FLAGS as $value => $name) { + if ($flags & $value) { + $flagsArray[] = $name; + } + } + + if (!$flagsArray) { + $flagsArray = ['AMQP_NOPARAM']; + } + + return new ConstStub(implode('|', $flagsArray), $flags); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ArgsStub.php b/vendor/symfony/var-dumper/Caster/ArgsStub.php new file mode 100644 index 0000000..9dc24c1 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ArgsStub.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a list of function arguments. + * + * @author Nicolas Grekas + */ +class ArgsStub extends EnumStub +{ + private static array $parameters = []; + + public function __construct(array $args, string $function, ?string $class) + { + [$variadic, $params] = self::getParameters($function, $class); + + $values = []; + foreach ($args as $k => $v) { + $values[$k] = !\is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; + } + if (null === $params) { + parent::__construct($values, false); + + return; + } + if (\count($values) < \count($params)) { + $params = \array_slice($params, 0, \count($values)); + } elseif (\count($values) > \count($params)) { + $values[] = new EnumStub(array_splice($values, \count($params)), false); + $params[] = $variadic; + } + if (['...'] === $params) { + $this->dumpKeys = false; + $this->value = $values[0]->value; + } else { + $this->value = array_combine($params, $values); + } + } + + private static function getParameters(string $function, ?string $class): array + { + if (isset(self::$parameters[$k = $class.'::'.$function])) { + return self::$parameters[$k]; + } + + try { + $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function); + } catch (\ReflectionException) { + return [null, null]; + } + + $variadic = '...'; + $params = []; + foreach ($r->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + if ($v->isVariadic()) { + $variadic .= $k; + } else { + $params[] = $k; + } + } + + return self::$parameters[$k] = [$variadic, $params]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php new file mode 100644 index 0000000..d9577e7 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/Caster.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Helper for filtering out properties in casters. + * + * @author Nicolas Grekas + * + * @final + */ +class Caster +{ + public const EXCLUDE_VERBOSE = 1; + public const EXCLUDE_VIRTUAL = 2; + public const EXCLUDE_DYNAMIC = 4; + public const EXCLUDE_PUBLIC = 8; + public const EXCLUDE_PROTECTED = 16; + public const EXCLUDE_PRIVATE = 32; + public const EXCLUDE_NULL = 64; + public const EXCLUDE_EMPTY = 128; + public const EXCLUDE_NOT_IMPORTANT = 256; + public const EXCLUDE_STRICT = 512; + public const EXCLUDE_UNINITIALIZED = 1024; + + public const PREFIX_VIRTUAL = "\0~\0"; + public const PREFIX_DYNAMIC = "\0+\0"; + public const PREFIX_PROTECTED = "\0*\0"; + // usage: sprintf(Caster::PATTERN_PRIVATE, $class, $property) + public const PATTERN_PRIVATE = "\0%s\0%s"; + + private static array $classProperties = []; + + /** + * Casts objects to arrays and adds the dynamic property prefix. + * + * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not + */ + public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, ?string $debugClass = null): array + { + if ($hasDebugInfo) { + try { + $debugInfo = $obj->__debugInfo(); + } catch (\Throwable) { + // ignore failing __debugInfo() + $hasDebugInfo = false; + } + } + + $a = $obj instanceof \Closure ? [] : (array) $obj; + + if ($obj instanceof \__PHP_Incomplete_Class) { + return $a; + } + + $classProperties = self::$classProperties[$class] ??= self::getClassProperties(new \ReflectionClass($class)); + $a = array_replace($classProperties, $a); + + if ($a) { + $debugClass ??= get_debug_type($obj); + + $i = 0; + $prefixedKeys = []; + foreach ($a as $k => $v) { + if ("\0" !== ($k[0] ?? '')) { + if (!isset($classProperties[$k])) { + $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; + } + } elseif ($debugClass !== $class && 1 === strpos($k, $class)) { + $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0"); + } + ++$i; + } + if ($prefixedKeys) { + $keys = array_keys($a); + foreach ($prefixedKeys as $i => $k) { + $keys[$i] = $k; + } + $a = array_combine($keys, $a); + } + } + + if ($hasDebugInfo && \is_array($debugInfo)) { + foreach ($debugInfo as $k => $v) { + if (!isset($k[0]) || "\0" !== $k[0]) { + if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) { + continue; + } + $k = self::PREFIX_VIRTUAL.$k; + } + + unset($a[$k]); + $a[$k] = $v; + } + } + + return $a; + } + + /** + * Filters out the specified properties. + * + * By default, a single match in the $filter bit field filters properties out, following an "or" logic. + * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. + * + * @param array $a The array containing the properties to filter + * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out + * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set + * @param int|null &$count Set to the number of removed properties + */ + public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array + { + $count = 0; + + foreach ($a as $k => $v) { + $type = self::EXCLUDE_STRICT & $filter; + + if (null === $v) { + $type |= self::EXCLUDE_NULL & $filter; + $type |= self::EXCLUDE_EMPTY & $filter; + } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { + $type |= self::EXCLUDE_EMPTY & $filter; + } elseif ($v instanceof UninitializedStub) { + $type |= self::EXCLUDE_UNINITIALIZED & $filter; + } + if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_NOT_IMPORTANT; + } + if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_VERBOSE; + } + + if (!isset($k[1]) || "\0" !== $k[0]) { + $type |= self::EXCLUDE_PUBLIC & $filter; + } elseif ('~' === $k[1]) { + $type |= self::EXCLUDE_VIRTUAL & $filter; + } elseif ('+' === $k[1]) { + $type |= self::EXCLUDE_DYNAMIC & $filter; + } elseif ('*' === $k[1]) { + $type |= self::EXCLUDE_PROTECTED & $filter; + } else { + $type |= self::EXCLUDE_PRIVATE & $filter; + } + + if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { + unset($a[$k]); + ++$count; + } + } + + return $a; + } + + public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array + { + if (isset($a['__PHP_Incomplete_Class_Name'])) { + $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')'; + unset($a['__PHP_Incomplete_Class_Name']); + } + + return $a; + } + + private static function getClassProperties(\ReflectionClass $class): array + { + $classProperties = []; + $className = $class->name; + + if ($parent = $class->getParentClass()) { + $classProperties += self::$classProperties[$parent->name] ??= self::getClassProperties($parent); + } + + foreach ($class->getProperties() as $p) { + if ($p->isStatic()) { + continue; + } + + $classProperties[match (true) { + $p->isPublic() => $p->name, + $p->isProtected() => self::PREFIX_PROTECTED.$p->name, + default => "\0".$className."\0".$p->name, + }] = new UninitializedStub($p); + } + + return $classProperties; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ClassStub.php b/vendor/symfony/var-dumper/Caster/ClassStub.php new file mode 100644 index 0000000..5b9ce64 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ClassStub.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP class identifier. + * + * @author Nicolas Grekas + */ +class ClassStub extends ConstStub +{ + /** + * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name + * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier + */ + public function __construct(string $identifier, callable|array|string|null $callable = null) + { + $this->value = $identifier; + + try { + if (null !== $callable) { + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + } elseif (\is_object($callable)) { + $r = [$callable, '__invoke']; + } elseif (\is_array($callable)) { + $r = $callable; + } elseif (false !== $i = strpos($callable, '::')) { + $r = [substr($callable, 0, $i), substr($callable, 2 + $i)]; + } else { + $r = new \ReflectionFunction($callable); + } + } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) { + $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)]; + } else { + $r = new \ReflectionClass($identifier); + } + + if (\is_array($r)) { + try { + $r = new \ReflectionMethod($r[0], $r[1]); + } catch (\ReflectionException) { + $r = new \ReflectionClass($r[0]); + } + } + + if (str_contains($identifier, "@anonymous\0")) { + $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $identifier); + } + + if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { + $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE); + $s = ReflectionCaster::getSignature($s); + + if (str_ends_with($identifier, '()')) { + $this->value = substr_replace($identifier, $s, -2); + } else { + $this->value .= $s; + } + } + } catch (\ReflectionException) { + return; + } finally { + if (0 < $i = strrpos($this->value, '\\')) { + $this->attr['ellipsis'] = \strlen($this->value) - $i; + $this->attr['ellipsis-type'] = 'class'; + $this->attr['ellipsis-tail'] = 1; + } + } + + if ($f = $r->getFileName()) { + $this->attr['file'] = $f; + $this->attr['line'] = $r->getStartLine(); + } + } + + public static function wrapCallable(mixed $callable): mixed + { + if (\is_object($callable) || !\is_callable($callable)) { + return $callable; + } + + if (!\is_array($callable)) { + $callable = new static($callable, $callable); + } elseif (\is_string($callable[0])) { + $callable[0] = new static($callable[0], $callable); + } else { + $callable[1] = new static($callable[1], $callable); + } + + return $callable; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ConstStub.php b/vendor/symfony/var-dumper/Caster/ConstStub.php new file mode 100644 index 0000000..587c6c3 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ConstStub.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP constant and its value. + * + * @author Nicolas Grekas + */ +class ConstStub extends Stub +{ + public function __construct(string $name, string|int|float|null $value = null) + { + $this->class = $name; + $this->value = 1 < \func_num_args() ? $value : $name; + } + + public function __toString(): string + { + return (string) $this->value; + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/vendor/symfony/var-dumper/Caster/CutArrayStub.php new file mode 100644 index 0000000..5912e13 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutArrayStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a cut array. + * + * @author Nicolas Grekas + */ +class CutArrayStub extends CutStub +{ + public array $preservedSubset; + + public function __construct(array $value, array $preservedKeys) + { + parent::__construct($value); + + $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); + $this->cut -= \count($this->preservedSubset); + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutStub.php b/vendor/symfony/var-dumper/Caster/CutStub.php new file mode 100644 index 0000000..772399e --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutStub.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents the main properties of a PHP variable, pre-casted by a caster. + * + * @author Nicolas Grekas + */ +class CutStub extends Stub +{ + public function __construct(mixed $value) + { + $this->value = $value; + + switch (\gettype($value)) { + case 'object': + $this->type = self::TYPE_OBJECT; + $this->class = $value::class; + + if ($value instanceof \Closure) { + ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE); + } + + $this->cut = -1; + break; + + case 'array': + $this->type = self::TYPE_ARRAY; + $this->class = self::ARRAY_ASSOC; + $this->cut = $this->value = \count($value); + break; + + case 'resource': + case 'unknown type': + case 'resource (closed)': + $this->type = self::TYPE_RESOURCE; + $this->handle = (int) $value; + if ('Unknown' === $this->class = @get_resource_type($value)) { + $this->class = 'Closed'; + } + $this->cut = -1; + break; + + case 'string': + $this->type = self::TYPE_STRING; + $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; + $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); + $this->value = ''; + break; + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/DOMCaster.php b/vendor/symfony/var-dumper/Caster/DOMCaster.php new file mode 100644 index 0000000..fa58ec4 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DOMCaster.php @@ -0,0 +1,316 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DOM related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class DOMCaster +{ + private const ERROR_CODES = [ + 0 => 'DOM_PHP_ERR', + \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', + \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', + \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', + \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', + \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', + \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', + \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', + \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', + \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', + \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', + \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', + \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', + \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', + \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', + \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', + \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', + ]; + + private const NODE_TYPES = [ + \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', + \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', + \XML_TEXT_NODE => 'XML_TEXT_NODE', + \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', + \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', + \XML_ENTITY_NODE => 'XML_ENTITY_NODE', + \XML_PI_NODE => 'XML_PI_NODE', + \XML_COMMENT_NODE => 'XML_COMMENT_NODE', + \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', + \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', + \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', + \XML_NOTATION_NODE => 'XML_NOTATION_NODE', + \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', + \XML_DTD_NODE => 'XML_DTD_NODE', + \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', + \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', + \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', + \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', + ]; + + public static function castException(\DOMException|\Dom\Exception $e, array $a, Stub $stub, bool $isNested): array + { + $k = Caster::PREFIX_PROTECTED.'code'; + if (isset($a[$k], self::ERROR_CODES[$a[$k]])) { + $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]); + } + + return $a; + } + + public static function castLength($dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castImplementation(\DOMImplementation|\Dom\Implementation $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'Core' => '1.0', + Caster::PREFIX_VIRTUAL.'XML' => '2.0', + ]; + + return $a; + } + + public static function castNode(\DOMNode|\Dom\Node $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'parentNode' => new CutStub($dom->parentNode), + 'childNodes' => $dom->childNodes, + 'firstChild' => new CutStub($dom->firstChild), + 'lastChild' => new CutStub($dom->lastChild), + 'previousSibling' => new CutStub($dom->previousSibling), + 'nextSibling' => new CutStub($dom->nextSibling), + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, + 'textContent' => new CutStub($dom->textContent), + ]; + + if ($dom instanceof \DOMNode || $dom instanceof \Dom\Element) { + $a += [ + 'attributes' => $dom->attributes, + 'namespaceURI' => $dom->namespaceURI, + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + ]; + } + + return $a; + } + + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'namespaceURI' => $dom->namespaceURI, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'parentNode' => new CutStub($dom->parentNode), + ]; + + return $a; + } + + public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $a += [ + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => new CutStub($dom->documentElement), + 'encoding' => $dom->encoding, + 'xmlEncoding' => $dom->xmlEncoding, + 'xmlStandalone' => $dom->xmlStandalone, + 'xmlVersion' => $dom->xmlVersion, + 'strictErrorChecking' => $dom->strictErrorChecking, + 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, + 'formatOutput' => $dom->formatOutput, + 'validateOnParse' => $dom->validateOnParse, + 'resolveExternals' => $dom->resolveExternals, + 'preserveWhiteSpace' => $dom->preserveWhiteSpace, + 'recover' => $dom->recover, + 'substituteEntities' => $dom->substituteEntities, + ]; + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $formatOutput = $dom->formatOutput; + $dom->formatOutput = true; + $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; + $dom->formatOutput = $formatOutput; + } + + return $a; + } + + public static function castXMLDocument(\Dom\XMLDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $a += [ + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => new CutStub($dom->documentElement), + 'inputEncoding' => $dom->inputEncoding, + 'xmlEncoding' => $dom->xmlEncoding, + 'xmlStandalone' => $dom->xmlStandalone, + 'xmlVersion' => $dom->xmlVersion, + 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, + 'formatOutput' => $dom->formatOutput, + ]; + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $formatOutput = $dom->formatOutput; + $dom->formatOutput = true; + $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; + $dom->formatOutput = $formatOutput; + } + + return $a; + } + + public static function castHTMLDocument(\Dom\HTMLDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $a += [ + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => new CutStub($dom->documentElement), + 'inputEncoding' => $dom->inputEncoding, + 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, + ]; + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $a += [Caster::PREFIX_VIRTUAL.'html' => $dom->saveHTML()]; + } + + return $a; + } + + public static function castCharacterData(\DOMCharacterData|\Dom\CharacterData $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'data' => $dom->data, + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castAttr(\DOMAttr|\Dom\Attr $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'name' => $dom->name, + 'specified' => $dom->specified, + 'value' => $dom->value, + 'ownerElement' => $dom->ownerElement, + ]; + + if ($dom instanceof \DOMAttr) { + $a += [ + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + } + + return $a; + } + + public static function castElement(\DOMElement|\Dom\Element $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'tagName' => $dom->tagName, + ]; + + if ($dom instanceof \DOMElement) { + $a += [ + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + } + + return $a; + } + + public static function castText(\DOMText|\Dom\Text $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'wholeText' => $dom->wholeText, + ]; + + return $a; + } + + public static function castDocumentType(\DOMDocumentType|\Dom\DocumentType $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'name' => $dom->name, + 'entities' => $dom->entities, + 'notations' => $dom->notations, + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'internalSubset' => $dom->internalSubset, + ]; + + return $a; + } + + public static function castNotation(\DOMNotation|\Dom\Notation $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + ]; + + return $a; + } + + public static function castEntity(\DOMEntity|\Dom\Entity $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'notationName' => $dom->notationName, + ]; + + return $a; + } + + public static function castProcessingInstruction(\DOMProcessingInstruction|\Dom\ProcessingInstruction $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'target' => $dom->target, + 'data' => $dom->data, + ]; + + return $a; + } + + public static function castXPath(\DOMXPath|\Dom\XPath $dom, array $a, Stub $stub, bool $isNested): array + { + $a += [ + 'document' => $dom->document, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DateCaster.php b/vendor/symfony/var-dumper/Caster/DateCaster.php new file mode 100644 index 0000000..cc85939 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DateCaster.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DateTimeInterface related classes to array representation. + * + * @author Dany Maillard + * + * @final + */ +class DateCaster +{ + private const PERIOD_LIMIT = 3; + + public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, bool $isNested, int $filter): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $location = $d->getTimezone() ? $d->getTimezone()->getLocation() : null; + $fromNow = (new \DateTimeImmutable())->diff($d); + + $title = $d->format('l, F j, Y') + ."\n".self::formatInterval($fromNow).' from now' + .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '') + ; + + unset( + $a[Caster::PREFIX_DYNAMIC.'date'], + $a[Caster::PREFIX_DYNAMIC.'timezone'], + $a[Caster::PREFIX_DYNAMIC.'timezone_type'] + ); + $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); + + $stub->class .= $d->format(' @U'); + + return $a; + } + + public static function castInterval(\DateInterval $interval, array $a, Stub $stub, bool $isNested, int $filter): array + { + $now = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); + $title = number_format($numberOfSeconds, 0, '.', ' ').'s'; + + $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; + } + + private static function formatInterval(\DateInterval $i): string + { + $format = '%R '; + + if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { + $d = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $i = $d->diff($d->add($i)); // recalculate carry over points + $format .= 0 < $i->days ? '%ad ' : ''; + } else { + $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); + } + + $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; + $format = '%R ' === $format ? '0s' : $format; + + return $i->format(rtrim($format)); + } + + public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, bool $isNested, int $filter): array + { + $location = $timeZone->getLocation(); + $formatted = (new \DateTimeImmutable('now', $timeZone))->format($location ? 'e (P)' : 'P'); + $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : ''; + + $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; + } + + public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, bool $isNested, int $filter): array + { + $dates = []; + foreach (clone $p as $i => $d) { + if (self::PERIOD_LIMIT === $i) { + $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); + $dates[] = sprintf('%s more', ($end = $p->getEndDate()) + ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) + : $p->recurrences - $i + ); + break; + } + $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); + } + + $period = sprintf( + 'every %s, from %s%s %s', + self::formatInterval($p->getDateInterval()), + $p->include_start_date ? '[' : ']', + self::formatDateTime($p->getStartDate()), + ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end).($p->include_end_date ? ']' : '[') : 'recurring '.$p->recurrences.' time/s' + ); + + $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; + } + + private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string + { + return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); + } + + private static function formatSeconds(string $s, string $us): string + { + return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); + } +} diff --git a/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php new file mode 100644 index 0000000..74c06a4 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Doctrine\Common\Proxy\Proxy as CommonProxy; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Proxy\Proxy as OrmProxy; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Doctrine related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class DoctrineCaster +{ + public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, bool $isNested): array + { + foreach (['__cloner__', '__initializer__'] as $k) { + if (\array_key_exists($k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, bool $isNested): array + { + foreach (['_entityPersister', '_identifier'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, bool $isNested): array + { + foreach (['snapshot', 'association', 'typeClass'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { + $a[$k] = new CutStub($a[$k]); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsCaster.php b/vendor/symfony/var-dumper/Caster/DsCaster.php new file mode 100644 index 0000000..b34b670 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsCaster.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ds\Collection; +use Ds\Map; +use Ds\Pair; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Ds extension classes to array representation. + * + * @author Jáchym Toušek + * + * @final + */ +class DsCaster +{ + public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count(); + $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity(); + + if (!$c instanceof Map) { + $a += $c->toArray(); + } + + return $a; + } + + public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c as $k => $v) { + $a[] = new DsPairStub($k, $v); + } + + return $a; + } + + public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c->toArray() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->class = Pair::class; + $stub->value = null; + $stub->handle = 0; + + $a = $c->value; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsPairStub.php b/vendor/symfony/var-dumper/Caster/DsPairStub.php new file mode 100644 index 0000000..afa2727 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsPairStub.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + */ +class DsPairStub extends Stub +{ + public function __construct(mixed $key, mixed $value) + { + $this->value = [ + Caster::PREFIX_VIRTUAL.'key' => $key, + Caster::PREFIX_VIRTUAL.'value' => $value, + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/EnumStub.php b/vendor/symfony/var-dumper/Caster/EnumStub.php new file mode 100644 index 0000000..02ed174 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/EnumStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents an enumeration of values. + * + * @author Nicolas Grekas + */ +class EnumStub extends Stub +{ + public bool $dumpKeys = true; + + public function __construct(array $values, bool $dumpKeys = true) + { + $this->value = $values; + $this->dumpKeys = $dumpKeys; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php new file mode 100644 index 0000000..8727042 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * Casts common Exception classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class ExceptionCaster +{ + public static int $srcContext = 1; + public static bool $traceArgs = true; + public static array $errorTypes = [ + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_ERROR => 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + 2048 => 'E_STRICT', + ]; + + private static array $framesCache = []; + + public static function castError(\Error $e, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); + } + + public static function castException(\Exception $e, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter); + } + + public static function castErrorException(\ErrorException $e, array $a, Stub $stub, bool $isNested): array + { + if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + return $a; + } + + public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, bool $isNested): array + { + $trace = Caster::PREFIX_VIRTUAL.'trace'; + $prefix = Caster::PREFIX_PROTECTED; + $xPrefix = "\0Exception\0"; + + if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { + $b = (array) $a[$xPrefix.'previous']; + $class = get_debug_type($a[$xPrefix.'previous']); + self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); + $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); + } + + unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); + + return $a; + } + + public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, bool $isNested): array + { + $sPrefix = "\0".SilencedErrorContext::class."\0"; + + if (!isset($a[$s = $sPrefix.'severity'])) { + return $a; + } + + if (isset(self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + $trace = [[ + 'file' => $a[$sPrefix.'file'], + 'line' => $a[$sPrefix.'line'], + ]]; + + if (isset($a[$sPrefix.'trace'])) { + $trace = array_merge($trace, $a[$sPrefix.'trace']); + } + + unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']); + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + + return $a; + } + + public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, bool $isNested): array + { + if (!$isNested) { + return $a; + } + $stub->class = ''; + $stub->handle = 0; + $frames = $trace->value; + $prefix = Caster::PREFIX_VIRTUAL; + + $a = []; + $j = \count($frames); + if (0 > $i = $trace->sliceOffset) { + $i = max(0, $j + $i); + } + if (!isset($trace->value[$i])) { + return []; + } + $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : ''; + $frames[] = ['function' => '']; + $collapse = false; + + for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { + $f = $frames[$i]; + $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???'; + + $frame = new FrameStub( + [ + 'object' => $f['object'] ?? null, + 'class' => $f['class'] ?? null, + 'type' => $f['type'] ?? null, + 'function' => $f['function'] ?? null, + ] + $frames[$i - 1], + false, + true + ); + $f = self::castFrameStub($frame, [], $frame, true); + if (isset($f[$prefix.'src'])) { + foreach ($f[$prefix.'src']->value as $label => $frame) { + if (str_starts_with($label, "\0~collapse=0")) { + if ($collapse) { + $label = substr_replace($label, '1', 11, 1); + } else { + $collapse = true; + } + } + $label = substr_replace($label, "title=Stack level $j.&", 2, 0); + } + $f = $frames[$i - 1]; + if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { + $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null); + } + } elseif ('???' !== $lastCall) { + $label = new ClassStub($lastCall); + if (isset($label->attr['ellipsis'])) { + $label->attr['ellipsis'] += 2; + $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()'; + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()'; + } + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall; + } + $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame; + + $lastCall = $call; + } + if (null !== $trace->sliceLength) { + $a = \array_slice($a, 0, $trace->sliceLength, true); + } + + return $a; + } + + public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, bool $isNested): array + { + if (!$isNested) { + return $a; + } + $f = $frame->value; + $prefix = Caster::PREFIX_VIRTUAL; + + if (isset($f['file'], $f['line'])) { + $cacheKey = $f; + unset($cacheKey['object'], $cacheKey['args']); + $cacheKey[] = self::$srcContext; + $cacheKey = implode('-', $cacheKey); + + if (isset(self::$framesCache[$cacheKey])) { + $a[$prefix.'src'] = self::$framesCache[$cacheKey]; + } else { + if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { + $f['file'] = substr($f['file'], 0, -\strlen($match[0])); + $f['line'] = (int) $match[1]; + } + $src = $f['line']; + $srcKey = $f['file']; + $ellipsis = new LinkStub($srcKey, 0); + $srcAttr = 'collapse='.(int) $ellipsis->inVendor; + $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0; + $ellipsis = $ellipsis->attr['ellipsis'] ?? 0; + + if (is_file($f['file']) && 0 <= self::$srcContext) { + if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig\Template')) { + $template = null; + if (isset($f['object'])) { + $template = $f['object']; + } elseif ((new \ReflectionClass($f['class']))->isInstantiable()) { + $template = unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); + } + if (null !== $template) { + $ellipsis = 0; + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (!method_exists($template, 'getSourceContext') || !is_file($templatePath = $template->getSourceContext()->getPath())) { + $templatePath = null; + } + if ($templateSrc) { + $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); + $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; + } + } + } + } + if ($srcKey == $f['file']) { + $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); + $srcKey .= ':'.$f['line']; + if ($ellipsis) { + $ellipsis += 1 + \strlen($f['line']); + } + } + $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']); + } else { + $srcAttr .= '&separator=:'; + } + $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : ''; + self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]); + } + } + + unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']); + if ($frame->inTraceStub) { + unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']); + } + foreach ($a as $k => $v) { + if (!$v) { + unset($a[$k]); + } + } + if ($frame->keepArgs && !empty($f['args'])) { + $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); + } + + return $a; + } + + public static function castFlattenException(FlattenException $e, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $k = sprintf(Caster::PATTERN_PRIVATE, FlattenException::class, 'traceAsString'); + $a[$k] = new CutStub($a[$k]); + } + + return $a; + } + + private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array + { + if (isset($a[$xPrefix.'trace'])) { + $trace = $a[$xPrefix.'trace']; + unset($a[$xPrefix.'trace']); // Ensures the trace is always last + } else { + $trace = []; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + } + if (empty($a[$xPrefix.'previous'])) { + unset($a[$xPrefix.'previous']); + } + unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message']); + + if (isset($a[Caster::PREFIX_PROTECTED.'message']) && str_contains($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) { + $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $a[Caster::PREFIX_PROTECTED.'message']); + } + + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + + return $a; + } + + private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void + { + if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { + return; + } + array_unshift($trace, [ + 'function' => $class ? 'new '.$class : null, + 'file' => $file, + 'line' => $line, + ]); + } + + private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub + { + $srcLines = explode("\n", $srcLines); + $src = []; + + for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { + $src[] = ($srcLines[$i] ?? '')."\n"; + } + + if ($frame['function'] ?? false) { + $stub = new CutStub(new \stdClass()); + $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function']; + $stub->type = Stub::TYPE_OBJECT; + $stub->attr['cut_hash'] = true; + $stub->attr['file'] = $frame['file']; + $stub->attr['line'] = $frame['line']; + + try { + $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); + $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE)); + + if ($f = $caller->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $caller->getStartLine(); + } + } catch (\ReflectionException) { + // ignore fake class/function + } + + $srcLines = ["\0~separator=\0" => $stub]; + } else { + $stub = null; + $srcLines = []; + } + + $ltrim = 0; + do { + $pad = null; + for ($i = $srcContext << 1; $i >= 0; --$i) { + if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { + $pad ??= $c; + if ((' ' !== $c && "\t" !== $c) || $pad !== $c) { + break; + } + } + } + ++$ltrim; + } while (0 > $i && null !== $pad); + + --$ltrim; + + foreach ($src as $i => $c) { + if ($ltrim) { + $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t"); + } + $c = substr($c, 0, -1); + if ($i !== $srcContext) { + $c = new ConstStub('default', $c); + } else { + $c = new ConstStub($c, $stub ? 'in '.$stub->class : ''); + if (null !== $file) { + $c->attr['file'] = $file; + $c->attr['line'] = $line; + } + } + $c->attr['lang'] = $lang; + $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c; + } + + return new EnumStub($srcLines); + } +} diff --git a/vendor/symfony/var-dumper/Caster/FFICaster.php b/vendor/symfony/var-dumper/Caster/FFICaster.php new file mode 100644 index 0000000..a9d2abc --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FFICaster.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use FFI\CData; +use FFI\CType; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts FFI extension classes to array representation. + * + * @author Nesmeyanov Kirill + */ +final class FFICaster +{ + /** + * In case of "char*" contains a string, the length of which depends on + * some other parameter, then during the generation of the string it is + * possible to go beyond the allowable memory area. + * + * This restriction serves to ensure that processing does not take + * up the entire allowable PHP memory limit. + */ + private const MAX_STRING_LENGTH = 255; + + public static function castCTypeOrCData(CData|CType $data, array $args, Stub $stub): array + { + if ($data instanceof CType) { + $type = $data; + $data = null; + } else { + $type = \FFI::typeof($data); + } + + $stub->class = sprintf('%s<%s> size %d align %d', ($data ?? $type)::class, $type->getName(), $type->getSize(), $type->getAlignment()); + + return match ($type->getKind()) { + CType::TYPE_FLOAT, + CType::TYPE_DOUBLE, + \defined('\FFI\CType::TYPE_LONGDOUBLE') ? CType::TYPE_LONGDOUBLE : -1, + CType::TYPE_UINT8, + CType::TYPE_SINT8, + CType::TYPE_UINT16, + CType::TYPE_SINT16, + CType::TYPE_UINT32, + CType::TYPE_SINT32, + CType::TYPE_UINT64, + CType::TYPE_SINT64, + CType::TYPE_BOOL, + CType::TYPE_CHAR, + CType::TYPE_ENUM => null !== $data ? [Caster::PREFIX_VIRTUAL.'cdata' => $data->cdata] : [], + CType::TYPE_POINTER => self::castFFIPointer($stub, $type, $data), + CType::TYPE_STRUCT => self::castFFIStructLike($type, $data), + CType::TYPE_FUNC => self::castFFIFunction($stub, $type), + default => $args, + }; + } + + private static function castFFIFunction(Stub $stub, CType $type): array + { + $arguments = []; + + for ($i = 0, $count = $type->getFuncParameterCount(); $i < $count; ++$i) { + $param = $type->getFuncParameterType($i); + + $arguments[] = $param->getName(); + } + + $abi = match ($type->getFuncABI()) { + CType::ABI_DEFAULT, + CType::ABI_CDECL => '[cdecl]', + CType::ABI_FASTCALL => '[fastcall]', + CType::ABI_THISCALL => '[thiscall]', + CType::ABI_STDCALL => '[stdcall]', + CType::ABI_PASCAL => '[pascal]', + CType::ABI_REGISTER => '[register]', + CType::ABI_MS => '[ms]', + CType::ABI_SYSV => '[sysv]', + CType::ABI_VECTORCALL => '[vectorcall]', + default => '[unknown abi]', + }; + + $returnType = $type->getFuncReturnType(); + + $stub->class = $abi.' callable('.implode(', ', $arguments).'): ' + .$returnType->getName(); + + return [Caster::PREFIX_VIRTUAL.'returnType' => $returnType]; + } + + private static function castFFIPointer(Stub $stub, CType $type, ?CData $data = null): array + { + $ptr = $type->getPointerType(); + + if (null === $data) { + return [Caster::PREFIX_VIRTUAL.'0' => $ptr]; + } + + return match ($ptr->getKind()) { + CType::TYPE_CHAR => [Caster::PREFIX_VIRTUAL.'cdata' => self::castFFIStringValue($data)], + CType::TYPE_FUNC => self::castFFIFunction($stub, $ptr), + default => [Caster::PREFIX_VIRTUAL.'cdata' => $data[0]], + }; + } + + private static function castFFIStringValue(CData $data): string|CutStub + { + $result = []; + $ffi = \FFI::cdef(<<zend_get_page_size(); + + // get cdata address + $start = $ffi->cast('uintptr_t', $ffi->cast('char*', $data))->cdata; + // accessing memory in the same page as $start is safe + $max = min(self::MAX_STRING_LENGTH, ($start | ($pageSize - 1)) - $start); + + for ($i = 0; $i < $max; ++$i) { + $result[$i] = $data[$i]; + + if ("\0" === $data[$i]) { + return implode('', $result); + } + } + + $string = implode('', $result); + $stub = new CutStub($string); + $stub->cut = -1; + $stub->value = $string; + + return $stub; + } + + private static function castFFIStructLike(CType $type, ?CData $data = null): array + { + $isUnion = ($type->getAttributes() & CType::ATTR_UNION) === CType::ATTR_UNION; + + $result = []; + + foreach ($type->getStructFieldNames() as $name) { + $field = $type->getStructFieldType($name); + + // Retrieving the value of a field from a union containing + // a pointer is not a safe operation, because may contain + // incorrect data. + $isUnsafe = $isUnion && CType::TYPE_POINTER === $field->getKind(); + + if ($isUnsafe) { + $result[Caster::PREFIX_VIRTUAL.$name.'?'] = $field; + } elseif (null === $data) { + $result[Caster::PREFIX_VIRTUAL.$name] = $field; + } else { + $fieldName = $data->{$name} instanceof CData ? '' : $field->getName().' '; + $result[Caster::PREFIX_VIRTUAL.$fieldName.$name] = $data->{$name}; + } + } + + return $result; + } +} diff --git a/vendor/symfony/var-dumper/Caster/FiberCaster.php b/vendor/symfony/var-dumper/Caster/FiberCaster.php new file mode 100644 index 0000000..c9df708 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FiberCaster.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Fiber related classes to array representation. + * + * @author Grégoire Pineau + */ +final class FiberCaster +{ + public static function castFiber(\Fiber $fiber, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($fiber->isTerminated()) { + $status = 'terminated'; + } elseif ($fiber->isRunning()) { + $status = 'running'; + } elseif ($fiber->isSuspended()) { + $status = 'suspended'; + } elseif ($fiber->isStarted()) { + $status = 'started'; + } else { + $status = 'not started'; + } + + $a[$prefix.'status'] = $status; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/FrameStub.php b/vendor/symfony/var-dumper/Caster/FrameStub.php new file mode 100644 index 0000000..9968c11 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FrameStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class FrameStub extends EnumStub +{ + public bool $keepArgs; + public bool $inTraceStub; + + public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) + { + $this->value = $frame; + $this->keepArgs = $keepArgs; + $this->inTraceStub = $inTraceStub; + } +} diff --git a/vendor/symfony/var-dumper/Caster/GmpCaster.php b/vendor/symfony/var-dumper/Caster/GmpCaster.php new file mode 100644 index 0000000..b018cc7 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/GmpCaster.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts GMP objects to array representation. + * + * @author Hamza Amrouche + * @author Nicolas Grekas + * + * @final + */ +class GmpCaster +{ + public static function castGmp(\GMP $gmp, array $a, Stub $stub, bool $isNested, int $filter): array + { + $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp)); + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImagineCaster.php b/vendor/symfony/var-dumper/Caster/ImagineCaster.php new file mode 100644 index 0000000..d1289da --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImagineCaster.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Imagine\Image\ImageInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class ImagineCaster +{ + public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array + { + $imgData = $c->get('png'); + if (\strlen($imgData) > 1 * 1000 * 1000) { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()), + ]; + } else { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImgStub.php b/vendor/symfony/var-dumper/Caster/ImgStub.php new file mode 100644 index 0000000..a16681f --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImgStub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * @author Grégoire Pineau + */ +class ImgStub extends ConstStub +{ + public function __construct(string $data, string $contentType, string $size = '') + { + $this->value = ''; + $this->attr['img-data'] = $data; + $this->attr['img-size'] = $size; + $this->attr['content-type'] = $contentType; + } +} diff --git a/vendor/symfony/var-dumper/Caster/IntlCaster.php b/vendor/symfony/var-dumper/Caster/IntlCaster.php new file mode 100644 index 0000000..f386c72 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/IntlCaster.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * @author Jan Schädlich + * + * @final + */ +class IntlCaster +{ + public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + return self::castError($c, $a); + } + + public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += 3; + + return self::castError($c, $a); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub( + [ + 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), + 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), + 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), + 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), + 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), + 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), + 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), + 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), + 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), + 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), + 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), + 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), + 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), + 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), + 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), + 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), + 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), + 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), + 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), + 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE), + ] + ), + Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub( + [ + 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), + 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), + 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), + 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), + 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), + 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), + 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), + 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS), + ] + ), + Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub( + [ + 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), + 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), + 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), + 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), + 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), + 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), + 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), + 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), + 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), + 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), + 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), + 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), + 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), + 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), + 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), + 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), + 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), + 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), + ] + ), + ]; + + return self::castError($c, $a); + } + + public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(), + Caster::PREFIX_VIRTUAL.'id' => $c->getID(), + Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(), + ]; + + if ($c->useDaylightTime()) { + $a += [ + Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(), + ]; + } + + return self::castError($c, $a); + } + + public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'type' => $c->getType(), + Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(), + Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), + Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'time' => $c->getTime(), + Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(), + Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(), + Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(), + Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(), + Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(), + Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + private static function castError(object $c, array $a): array + { + if ($errorCode = $c->getErrorCode()) { + $a += [ + Caster::PREFIX_VIRTUAL.'error_code' => $errorCode, + Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/LinkStub.php b/vendor/symfony/var-dumper/Caster/LinkStub.php new file mode 100644 index 0000000..3acd4fd --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/LinkStub.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a file or a URL. + * + * @author Nicolas Grekas + */ +class LinkStub extends ConstStub +{ + public bool $inVendor = false; + + private static array $vendorRoots; + private static array $composerRoots = []; + + public function __construct(string $label, int $line = 0, ?string $href = null) + { + $this->value = $label; + + if (!\is_string($href ??= $label)) { + return; + } + if (str_starts_with($href, 'file://')) { + if ($href === $label) { + $label = substr($label, 7); + } + $href = substr($href, 7); + } elseif (str_contains($href, '://')) { + $this->attr['href'] = $href; + + return; + } + if (!is_file($href)) { + return; + } + if ($line) { + $this->attr['line'] = $line; + } + if ($label !== $this->attr['file'] = realpath($href) ?: $href) { + return; + } + if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) { + $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1; + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0); + } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) { + $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2))); + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1; + } + } + + private function getComposerRoot(string $file, bool &$inVendor): string|false + { + if (!isset(self::$vendorRoots)) { + self::$vendorRoots = []; + + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname($r->getFileName(), 2); + if (is_file($v.'/composer/installed.json')) { + self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR; + } + } + } + } + $inVendor = false; + + if (isset(self::$composerRoots[$dir = \dirname($file)])) { + return self::$composerRoots[$dir]; + } + + foreach (self::$vendorRoots as $root) { + if ($inVendor = str_starts_with($file, $root)) { + return $root; + } + } + + $parent = $dir; + while (!@is_file($parent.'/composer.json')) { + if (!@file_exists($parent)) { + // open_basedir restriction in effect + break; + } + if ($parent === \dirname($parent)) { + return self::$composerRoots[$dir] = false; + } + + $parent = \dirname($parent); + } + + return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php new file mode 100644 index 0000000..740785c --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Jan Schädlich + * + * @final + */ +class MemcachedCaster +{ + private static array $optionConstants; + private static array $defaultOptions; + + public static function castMemcached(\Memcached $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(), + Caster::PREFIX_VIRTUAL.'options' => new EnumStub( + self::getNonDefaultOptions($c) + ), + ]; + + return $a; + } + + private static function getNonDefaultOptions(\Memcached $c): array + { + self::$defaultOptions ??= self::discoverDefaultOptions(); + self::$optionConstants ??= self::getOptionConstants(); + + $nonDefaultOptions = []; + foreach (self::$optionConstants as $constantKey => $value) { + if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) { + $nonDefaultOptions[$constantKey] = $option; + } + } + + return $nonDefaultOptions; + } + + private static function discoverDefaultOptions(): array + { + $defaultMemcached = new \Memcached(); + $defaultMemcached->addServer('127.0.0.1', 11211); + + $defaultOptions = []; + self::$optionConstants ??= self::getOptionConstants(); + + foreach (self::$optionConstants as $constantKey => $value) { + $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); + } + + return $defaultOptions; + } + + private static function getOptionConstants(): array + { + $reflectedMemcached = new \ReflectionClass(\Memcached::class); + + $optionConstants = []; + foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { + if (str_starts_with($constantKey, 'OPT_')) { + $optionConstants[$constantKey] = $value; + } + } + + return $optionConstants; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MysqliCaster.php b/vendor/symfony/var-dumper/Caster/MysqliCaster.php new file mode 100644 index 0000000..bfe6f08 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MysqliCaster.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class MysqliCaster +{ + public static function castMysqliDriver(\mysqli_driver $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($a as $k => $v) { + if (isset($c->$k)) { + $a[$k] = $c->$k; + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PdoCaster.php b/vendor/symfony/var-dumper/Caster/PdoCaster.php new file mode 100644 index 0000000..1d364cd --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PdoCaster.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts PDO related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class PdoCaster +{ + private const PDO_ATTRIBUTES = [ + 'CASE' => [ + \PDO::CASE_LOWER => 'LOWER', + \PDO::CASE_NATURAL => 'NATURAL', + \PDO::CASE_UPPER => 'UPPER', + ], + 'ERRMODE' => [ + \PDO::ERRMODE_SILENT => 'SILENT', + \PDO::ERRMODE_WARNING => 'WARNING', + \PDO::ERRMODE_EXCEPTION => 'EXCEPTION', + ], + 'TIMEOUT', + 'PREFETCH', + 'AUTOCOMMIT', + 'PERSISTENT', + 'DRIVER_NAME', + 'SERVER_INFO', + 'ORACLE_NULLS' => [ + \PDO::NULL_NATURAL => 'NATURAL', + \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', + \PDO::NULL_TO_STRING => 'TO_STRING', + ], + 'CLIENT_VERSION', + 'SERVER_VERSION', + 'STATEMENT_CLASS', + 'EMULATE_PREPARES', + 'CONNECTION_STATUS', + 'STRINGIFY_FETCHES', + 'DEFAULT_FETCH_MODE' => [ + \PDO::FETCH_ASSOC => 'ASSOC', + \PDO::FETCH_BOTH => 'BOTH', + \PDO::FETCH_LAZY => 'LAZY', + \PDO::FETCH_NUM => 'NUM', + \PDO::FETCH_OBJ => 'OBJ', + ], + ]; + + public static function castPdo(\PDO $c, array $a, Stub $stub, bool $isNested): array + { + $attr = []; + $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); + $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + foreach (self::PDO_ATTRIBUTES as $k => $v) { + if (!isset($k[0])) { + $k = $v; + $v = []; + } + + try { + $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); + if ($v && isset($v[$attr[$k]])) { + $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); + } + } catch (\Exception) { + } + } + if (isset($attr[$k = 'STATEMENT_CLASS'][1])) { + if ($attr[$k][1]) { + $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]); + } + $attr[$k][0] = new ClassStub($attr[$k][0]); + } + + $prefix = Caster::PREFIX_VIRTUAL; + $a += [ + $prefix.'inTransaction' => method_exists($c, 'inTransaction'), + $prefix.'errorInfo' => $c->errorInfo(), + $prefix.'attributes' => new EnumStub($attr), + ]; + + if ($a[$prefix.'inTransaction']) { + $a[$prefix.'inTransaction'] = $c->inTransaction(); + } else { + unset($a[$prefix.'inTransaction']); + } + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); + + return $a; + } + + public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $a[$prefix.'errorInfo'] = $c->errorInfo(); + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php new file mode 100644 index 0000000..7e74500 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts pqsql resources to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class PgSqlCaster +{ + private const PARAM_CODES = [ + 'server_encoding', + 'client_encoding', + 'is_superuser', + 'session_authorization', + 'DateStyle', + 'TimeZone', + 'IntervalStyle', + 'integer_datetimes', + 'application_name', + 'standard_conforming_strings', + ]; + + private const TRANSACTION_STATUS = [ + \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', + \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', + \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', + \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', + \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', + ]; + + private const RESULT_STATUS = [ + \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', + \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', + \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', + \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT', + \PGSQL_COPY_IN => 'PGSQL_COPY_IN', + \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', + \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', + \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', + ]; + + private const DIAG_CODES = [ + 'severity' => \PGSQL_DIAG_SEVERITY, + 'sqlstate' => \PGSQL_DIAG_SQLSTATE, + 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, + 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL, + 'hint' => \PGSQL_DIAG_MESSAGE_HINT, + 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION, + 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION, + 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY, + 'context' => \PGSQL_DIAG_CONTEXT, + 'file' => \PGSQL_DIAG_SOURCE_FILE, + 'line' => \PGSQL_DIAG_SOURCE_LINE, + 'function' => \PGSQL_DIAG_SOURCE_FUNCTION, + ]; + + public static function castLargeObject($lo, array $a, Stub $stub, bool $isNested): array + { + $a['seek position'] = pg_lo_tell($lo); + + return $a; + } + + public static function castLink($link, array $a, Stub $stub, bool $isNested): array + { + $a['status'] = pg_connection_status($link); + $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); + $a['busy'] = pg_connection_busy($link); + + $a['transaction'] = pg_transaction_status($link); + if (isset(self::TRANSACTION_STATUS[$a['transaction']])) { + $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']); + } + + $a['pid'] = pg_get_pid($link); + $a['last error'] = pg_last_error($link); + $a['last notice'] = pg_last_notice($link); + $a['host'] = pg_host($link); + $a['port'] = pg_port($link); + $a['dbname'] = pg_dbname($link); + $a['options'] = pg_options($link); + $a['version'] = pg_version($link); + + foreach (self::PARAM_CODES as $v) { + if (false !== $s = pg_parameter_status($link, $v)) { + $a['param'][$v] = $s; + } + } + + $a['param']['client_encoding'] = pg_client_encoding($link); + $a['param'] = new EnumStub($a['param']); + + return $a; + } + + public static function castResult($result, array $a, Stub $stub, bool $isNested): array + { + $a['num rows'] = pg_num_rows($result); + $a['status'] = pg_result_status($result); + if (isset(self::RESULT_STATUS[$a['status']])) { + $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']); + } + $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING); + + if (-1 === $a['num rows']) { + foreach (self::DIAG_CODES as $k => $v) { + $a['error'][$k] = pg_result_error_field($result, $v); + } + } + + $a['affected rows'] = pg_affected_rows($result); + $a['last OID'] = pg_last_oid($result); + + $fields = pg_num_fields($result); + + for ($i = 0; $i < $fields; ++$i) { + $field = [ + 'name' => pg_field_name($result, $i), + 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)), + 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)), + 'nullable' => (bool) pg_field_is_null($result, $i), + 'storage' => pg_field_size($result, $i).' bytes', + 'display' => pg_field_prtlen($result, $i).' chars', + ]; + if (' (OID: )' === $field['table']) { + $field['table'] = null; + } + if ('-1 bytes' === $field['storage']) { + $field['storage'] = 'variable size'; + } elseif ('1 bytes' === $field['storage']) { + $field['storage'] = '1 byte'; + } + if ('1 chars' === $field['display']) { + $field['display'] = '1 char'; + } + $a['fields'][] = new EnumStub($field); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php new file mode 100644 index 0000000..736a6e7 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @final + */ +class ProxyManagerCaster +{ + public static function castProxy(ProxyInterface $c, array $a, Stub $stub, bool $isNested): array + { + if ($parent = get_parent_class($c)) { + $stub->class .= ' - '.$parent; + } + $stub->class .= '@proxy'; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php b/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php new file mode 100644 index 0000000..5445b2d --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use RdKafka\Conf; +use RdKafka\Exception as RdKafkaException; +use RdKafka\KafkaConsumer; +use RdKafka\Message; +use RdKafka\Metadata\Broker as BrokerMetadata; +use RdKafka\Metadata\Collection as CollectionMetadata; +use RdKafka\Metadata\Partition as PartitionMetadata; +use RdKafka\Metadata\Topic as TopicMetadata; +use RdKafka\Topic; +use RdKafka\TopicConf; +use RdKafka\TopicPartition; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts RdKafka related classes to array representation. + * + * @author Romain Neutron + */ +class RdKafkaCaster +{ + public static function castKafkaConsumer(KafkaConsumer $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $assignment = $c->getAssignment(); + } catch (RdKafkaException) { + $assignment = []; + } + + $a += [ + $prefix.'subscription' => $c->getSubscription(), + $prefix.'assignment' => $assignment, + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castTopic(Topic $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getName(), + ]; + + return $a; + } + + public static function castTopicPartition(TopicPartition $c, array $a): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'offset' => $c->getOffset(), + $prefix.'partition' => $c->getPartition(), + $prefix.'topic' => $c->getTopic(), + ]; + + return $a; + } + + public static function castMessage(Message $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'errstr' => $c->errstr(), + ]; + + return $a; + } + + public static function castConf(Conf $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castTopicConf(TopicConf $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castRdKafka(\RdKafka $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'out_q_len' => $c->getOutQLen(), + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castCollectionMetadata(CollectionMetadata $c, array $a, Stub $stub, bool $isNested): array + { + $a += iterator_to_array($c); + + return $a; + } + + public static function castTopicMetadata(TopicMetadata $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getTopic(), + $prefix.'partitions' => $c->getPartitions(), + ]; + + return $a; + } + + public static function castPartitionMetadata(PartitionMetadata $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'err' => $c->getErr(), + $prefix.'leader' => $c->getLeader(), + ]; + + return $a; + } + + public static function castBrokerMetadata(BrokerMetadata $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + ]; + + return $a; + } + + private static function extractMetadata(KafkaConsumer|\RdKafka $c): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $m = $c->getMetadata(true, null, 500); + } catch (RdKafkaException) { + return []; + } + + return [ + $prefix.'orig_broker_id' => $m->getOrigBrokerId(), + $prefix.'orig_broker_name' => $m->getOrigBrokerName(), + $prefix.'brokers' => $m->getBrokers(), + $prefix.'topics' => $m->getTopics(), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RedisCaster.php b/vendor/symfony/var-dumper/Caster/RedisCaster.php new file mode 100644 index 0000000..5224bc0 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RedisCaster.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Relay\Relay; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Redis class from ext-redis to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class RedisCaster +{ + private const SERIALIZERS = [ + 0 => 'NONE', // Redis::SERIALIZER_NONE + 1 => 'PHP', // Redis::SERIALIZER_PHP + 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY + ]; + + private const MODES = [ + 0 => 'ATOMIC', // Redis::ATOMIC + 1 => 'MULTI', // Redis::MULTI + 2 => 'PIPELINE', // Redis::PIPELINE + ]; + + private const COMPRESSION_MODES = [ + 0 => 'NONE', // Redis::COMPRESSION_NONE + 1 => 'LZF', // Redis::COMPRESSION_LZF + ]; + + private const FAILOVER_OPTIONS = [ + \RedisCluster::FAILOVER_NONE => 'NONE', + \RedisCluster::FAILOVER_ERROR => 'ERROR', + \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', + \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', + ]; + + public static function castRedis(\Redis|Relay $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + if (!$connected = $c->isConnected()) { + return $a + [ + $prefix.'isConnected' => $connected, + ]; + } + + $mode = $c->getMode(); + + return $a + [ + $prefix.'isConnected' => $connected, + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + $prefix.'auth' => $c->getAuth(), + $prefix.'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode, + $prefix.'dbNum' => $c->getDbNum(), + $prefix.'timeout' => $c->getTimeout(), + $prefix.'lastError' => $c->getLastError(), + $prefix.'persistentId' => $c->getPersistentID(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + return $a + [ + $prefix.'hosts' => $c->_hosts(), + $prefix.'function' => ClassStub::wrapCallable($c->_function()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); + + $a += [ + $prefix.'_masters' => $c->_masters(), + $prefix.'_redir' => $c->_redir(), + $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c, [ + 'SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover, + ]), + ]; + + return $a; + } + + private static function getRedisOptions(\Redis|Relay|\RedisArray|\RedisCluster $redis, array $options = []): EnumStub + { + $serializer = $redis->getOption(\defined('Redis::OPT_SERIALIZER') ? \Redis::OPT_SERIALIZER : 1); + if (\is_array($serializer)) { + foreach ($serializer as &$v) { + if (isset(self::SERIALIZERS[$v])) { + $v = new ConstStub(self::SERIALIZERS[$v], $v); + } + } + } elseif (isset(self::SERIALIZERS[$serializer])) { + $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer); + } + + $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; + if (\is_array($compression)) { + foreach ($compression as &$v) { + if (isset(self::COMPRESSION_MODES[$v])) { + $v = new ConstStub(self::COMPRESSION_MODES[$v], $v); + } + } + } elseif (isset(self::COMPRESSION_MODES[$compression])) { + $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression); + } + + $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; + if (\is_array($retry)) { + foreach ($retry as &$v) { + $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); + } + } else { + $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); + } + + $options += [ + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : Relay::OPT_TCP_KEEPALIVE, + 'READ_TIMEOUT' => $redis->getOption(\defined('Redis::OPT_READ_TIMEOUT') ? \Redis::OPT_READ_TIMEOUT : Relay::OPT_READ_TIMEOUT), + 'COMPRESSION' => $compression, + 'SERIALIZER' => $serializer, + 'PREFIX' => $redis->getOption(\defined('Redis::OPT_PREFIX') ? \Redis::OPT_PREFIX : Relay::OPT_PREFIX), + 'SCAN' => $retry, + ]; + + return new EnumStub($options); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php new file mode 100644 index 0000000..e7bd9a1 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php @@ -0,0 +1,446 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Reflector related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class ReflectionCaster +{ + public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; + + private const EXTRA_MAP = [ + 'docComment' => 'getDocComment', + 'extension' => 'getExtensionName', + 'isDisabled' => 'isDisabled', + 'isDeprecated' => 'isDeprecated', + 'isInternal' => 'isInternal', + 'isUserDefined' => 'isUserDefined', + 'isGenerator' => 'isGenerator', + 'isVariadic' => 'isVariadic', + ]; + + public static function castClosure(\Closure $c, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $c = new \ReflectionFunction($c); + + $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); + + if (!$c->isAnonymous()) { + $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; + unset($a[$prefix.'class']); + } + unset($a[$prefix.'extra']); + + $stub->class .= self::getSignature($a); + + if ($f = $c->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $c->getStartLine(); + } + + unset($a[$prefix.'parameters']); + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); + + return []; + } + + if ($f) { + $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); + $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + return $a; + } + + public static function unsetClosureFileInfo(\Closure $c, array $a): array + { + unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']); + + return $a; + } + + public static function castGenerator(\Generator $c, array $a, Stub $stub, bool $isNested): array + { + // Cannot create ReflectionGenerator based on a terminated Generator + try { + $reflectionGenerator = new \ReflectionGenerator($c); + + return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); + } catch (\Exception) { + $a[Caster::PREFIX_VIRTUAL.'closed'] = true; + + return $a; + } + } + + public static function castType(\ReflectionType $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c instanceof \ReflectionNamedType) { + $a += [ + $prefix.'name' => $c->getName(), + $prefix.'allowsNull' => $c->allowsNull(), + $prefix.'isBuiltin' => $c->isBuiltin(), + ]; + } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + self::addMap($a, $c, [ + 'types' => 'getTypes', + ]); + } else { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + } + + return $a; + } + + public static function castAttribute(\ReflectionAttribute $c, array $a, Stub $stub, bool $isNested): array + { + $map = [ + 'name' => 'getName', + 'arguments' => 'getArguments', + ]; + + if (\PHP_VERSION_ID >= 80400) { + unset($map['name']); + } + + self::addMap($a, $c, $map); + + return $a; + } + + public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c->getThis()) { + $a[$prefix.'this'] = new CutStub($c->getThis()); + } + $function = $c->getFunction(); + $frame = [ + 'class' => $function->class ?? null, + 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, + 'function' => $function->name, + 'file' => $c->getExecutingFile(), + 'line' => $c->getExecutingLine(), + ]; + if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) { + $function = new \ReflectionGenerator($c->getExecutingGenerator()); + array_unshift($trace, [ + 'function' => 'yield', + 'file' => $function->getExecutingFile(), + 'line' => $function->getExecutingLine(), + ]); + $trace[] = $frame; + $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); + } else { + $function = new FrameStub($frame, false, true); + $function = ExceptionCaster::castFrameStub($function, [], $function, true); + $a[$prefix.'executing'] = $function[$prefix.'src']; + } + + $a[Caster::PREFIX_VIRTUAL.'closed'] = false; + + return $a; + } + + public static function castClass(\ReflectionClass $c, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($n = \Reflection::getModifierNames($c->getModifiers())) { + $a[$prefix.'modifiers'] = implode(' ', $n); + } + + self::addMap($a, $c, [ + 'extends' => 'getParentClass', + 'implements' => 'getInterfaceNames', + 'constants' => 'getReflectionConstants', + ]); + + foreach ($c->getProperties() as $n) { + $a[$prefix.'properties'][$n->name] = $n; + } + + foreach ($c->getMethods() as $n) { + $a[$prefix.'methods'][$n->name] = $n; + } + + self::addAttributes($a, $c, $prefix); + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'returnsReference' => 'returnsReference', + 'returnType' => 'getReturnType', + 'class' => 'getClosureCalledClass', + 'this' => 'getClosureThis', + ]); + + if (isset($a[$prefix.'returnType'])) { + $v = $a[$prefix.'returnType']; + $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType'] instanceof \ReflectionNamedType && $a[$prefix.'returnType']->allowsNull() && !\in_array($v, ['mixed', 'null'], true) ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } + if (isset($a[$prefix.'class'])) { + $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']); + } + if (isset($a[$prefix.'this'])) { + $a[$prefix.'this'] = new CutStub($a[$prefix.'this']); + } + + foreach ($c->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isVariadic()) { + $k = '...'.$k; + } + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + $a[$prefix.'parameters'][$k] = $v; + } + if (isset($a[$prefix.'parameters'])) { + $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); + } + + self::addAttributes($a, $c, $prefix); + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { + foreach ($v as $k => &$v) { + if (\is_object($v)) { + $a[$prefix.'use']['$'.$k] = new CutStub($v); + } else { + $a[$prefix.'use']['$'.$k] = &$v; + } + } + unset($v); + $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castClassConstant(\ReflectionClassConstant $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + $a[Caster::PREFIX_VIRTUAL.'value'] = $c->getValue(); + + self::addAttributes($a, $c); + + return $a; + } + + public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + return $a; + } + + public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'position' => 'getPosition', + 'isVariadic' => 'isVariadic', + 'byReference' => 'isPassedByReference', + 'allowsNull' => 'allowsNull', + ]); + + self::addAttributes($a, $c, $prefix); + + if ($v = $c->getType()) { + $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + } + + if (isset($a[$prefix.'typeHint'])) { + $v = $a[$prefix.'typeHint']; + $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } else { + unset($a[$prefix.'allowsNull']); + } + + if ($c->isOptional()) { + try { + $a[$prefix.'default'] = $v = $c->getDefaultValue(); + if ($c->isDefaultValueConstant() && !\is_object($v)) { + $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); + } + if (null === $v) { + unset($a[$prefix.'allowsNull']); + } + } catch (\ReflectionException) { + } + } + + return $a; + } + + public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + self::addAttributes($a, $c); + self::addExtra($a, $c); + + return $a; + } + + public static function castReference(\ReflectionReference $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId(); + + return $a; + } + + public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, bool $isNested): array + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'dependencies' => 'getDependencies', + 'iniEntries' => 'getIniEntries', + 'isPersistent' => 'isPersistent', + 'isTemporary' => 'isTemporary', + 'constants' => 'getConstants', + 'functions' => 'getFunctions', + 'classes' => 'getClasses', + ]); + + return $a; + } + + public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, bool $isNested): array + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'author' => 'getAuthor', + 'copyright' => 'getCopyright', + 'url' => 'getURL', + ]); + + return $a; + } + + public static function getSignature(array $a): string + { + $prefix = Caster::PREFIX_VIRTUAL; + $signature = ''; + + if (isset($a[$prefix.'parameters'])) { + foreach ($a[$prefix.'parameters']->value as $k => $param) { + $signature .= ', '; + if ($type = $param->getType()) { + if (!$type instanceof \ReflectionNamedType) { + $signature .= $type.' '; + } else { + if ($param->allowsNull() && !\in_array($type->getName(), ['mixed', 'null'], true)) { + $signature .= '?'; + } + $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' '; + } + } + $signature .= $k; + + if (!$param->isDefaultValueAvailable()) { + continue; + } + $v = $param->getDefaultValue(); + $signature .= ' = '; + + if ($param->isDefaultValueConstant()) { + $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1); + } elseif (null === $v) { + $signature .= 'null'; + } elseif (\is_array($v)) { + $signature .= $v ? '[…'.\count($v).']' : '[]'; + } elseif (\is_string($v)) { + $signature .= 10 > \strlen($v) && !str_contains($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'"; + } elseif (\is_bool($v)) { + $signature .= $v ? 'true' : 'false'; + } elseif (\is_object($v)) { + $signature .= 'new '.substr(strrchr('\\'.get_debug_type($v), '\\'), 1); + } else { + $signature .= $v; + } + } + } + $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')'; + + if (isset($a[$prefix.'returnType'])) { + $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1); + } + + return $signature; + } + + private static function addExtra(array &$a, \Reflector $c): void + { + $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : []; + + if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { + $x['file'] = new LinkStub($m, $c->getStartLine()); + $x['line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + self::addMap($x, $c, self::EXTRA_MAP, ''); + + if ($x) { + $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x); + } + } + + private static function addMap(array &$a, object $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL): void + { + foreach ($map as $k => $m) { + if ('isDisabled' === $k) { + continue; + } + + if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { + $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; + } + } + } + + private static function addAttributes(array &$a, \Reflector $c, string $prefix = Caster::PREFIX_VIRTUAL): void + { + foreach ($c->getAttributes() as $n) { + $a[$prefix.'attributes'][] = $n; + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/vendor/symfony/var-dumper/Caster/ResourceCaster.php new file mode 100644 index 0000000..f775f81 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ResourceCaster.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts common resource types to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class ResourceCaster +{ + public static function castCurl(\CurlHandle $h, array $a, Stub $stub, bool $isNested): array + { + return curl_getinfo($h); + } + + public static function castDba($dba, array $a, Stub $stub, bool $isNested): array + { + $list = dba_list(); + $a['file'] = $list[(int) $dba]; + + return $a; + } + + public static function castProcess($process, array $a, Stub $stub, bool $isNested): array + { + return proc_get_status($process); + } + + public static function castStream($stream, array $a, Stub $stub, bool $isNested): array + { + $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); + if ($a['uri'] ?? false) { + $a['uri'] = new LinkStub($a['uri']); + } + + return $a; + } + + public static function castStreamContext($stream, array $a, Stub $stub, bool $isNested): array + { + return @stream_context_get_params($stream) ?: $a; + } + + public static function castGd($gd, array $a, Stub $stub, bool $isNested): array + { + $a['size'] = imagesx($gd).'x'.imagesy($gd); + $a['trueColor'] = imageistruecolor($gd); + + return $a; + } + + public static function castOpensslX509($h, array $a, Stub $stub, bool $isNested): array + { + $stub->cut = -1; + $info = openssl_x509_parse($h, false); + + $pin = openssl_pkey_get_public($h); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + $a += [ + 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), + 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), + 'expiry' => new ConstStub(date(\DateTimeInterface::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), + 'fingerprint' => new EnumStub([ + 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), + 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), + 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), + 'pin-sha256' => new ConstStub($pin), + ]), + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ScalarStub.php b/vendor/symfony/var-dumper/Caster/ScalarStub.php new file mode 100644 index 0000000..3bb1935 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ScalarStub.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents any arbitrary value. + * + * @author Alexandre Daubois + */ +class ScalarStub extends Stub +{ + public function __construct(mixed $value) + { + $this->value = $value; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SplCaster.php b/vendor/symfony/var-dumper/Caster/SplCaster.php new file mode 100644 index 0000000..c695364 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SplCaster.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts SPL related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class SplCaster +{ + private const SPL_FILE_OBJECT_FLAGS = [ + \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', + \SplFileObject::READ_AHEAD => 'READ_AHEAD', + \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', + \SplFileObject::READ_CSV => 'READ_CSV', + ]; + + public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, bool $isNested): array + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, bool $isNested): array + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castHeap(\Iterator $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), + ]; + + return $a; + } + + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $mode = $c->getIteratorMode(); + $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); + + $a += [ + $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), + $prefix.'dllist' => iterator_to_array($c), + ]; + $c->setIteratorMode($mode); + + return $a; + } + + public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, bool $isNested): array + { + static $map = [ + 'path' => 'getPath', + 'filename' => 'getFilename', + 'basename' => 'getBasename', + 'pathname' => 'getPathname', + 'extension' => 'getExtension', + 'realPath' => 'getRealPath', + 'aTime' => 'getATime', + 'mTime' => 'getMTime', + 'cTime' => 'getCTime', + 'inode' => 'getInode', + 'size' => 'getSize', + 'perms' => 'getPerms', + 'owner' => 'getOwner', + 'group' => 'getGroup', + 'type' => 'getType', + 'writable' => 'isWritable', + 'readable' => 'isReadable', + 'executable' => 'isExecutable', + 'file' => 'isFile', + 'dir' => 'isDir', + 'link' => 'isLink', + 'linkTarget' => 'getLinkTarget', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + unset($a["\0SplFileInfo\0fileName"]); + unset($a["\0SplFileInfo\0pathName"]); + + try { + $c->isReadable(); + } catch (\RuntimeException $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } catch (\Error $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception) { + } + } + + if ($a[$prefix.'realPath'] ?? false) { + $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']); + } + + if (isset($a[$prefix.'perms'])) { + $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']); + } + + static $mapDate = ['aTime', 'mTime', 'cTime']; + foreach ($mapDate as $key) { + if (isset($a[$prefix.$key])) { + $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]); + } + } + + return $a; + } + + public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, bool $isNested): array + { + static $map = [ + 'csvControl' => 'getCsvControl', + 'flags' => 'getFlags', + 'maxLineLen' => 'getMaxLineLen', + 'fstat' => 'fstat', + 'eof' => 'eof', + 'key' => 'key', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception) { + } + } + + if (isset($a[$prefix.'flags'])) { + $flagsArray = []; + foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) { + if ($a[$prefix.'flags'] & $value) { + $flagsArray[] = $name; + } + } + $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']); + } + + if (isset($a[$prefix.'fstat'])) { + $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); + } + + return $a; + } + + public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, bool $isNested): array + { + $storage = []; + unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 + unset($a["\0SplObjectStorage\0storage"]); + + $clone = clone $c; + foreach ($clone as $obj) { + $storage[] = new EnumStub([ + 'object' => $obj, + 'info' => $clone->getInfo(), + ]); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'storage' => $storage, + ]; + + return $a; + } + + public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator(); + + return $a; + } + + public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get(); + + return $a; + } + + public static function castWeakMap(\WeakMap $c, array $a, Stub $stub, bool $isNested): array + { + $map = []; + + foreach (clone $c as $obj => $data) { + $map[] = new EnumStub([ + 'object' => $obj, + 'data' => $data, + ]); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'map' => $map, + ]; + + return $a; + } + + private static function castSplArray(\ArrayObject|\ArrayIterator $c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $flags = $c->getFlags(); + + if (!($flags & \ArrayObject::STD_PROP_LIST)) { + $c->setFlags(\ArrayObject::STD_PROP_LIST); + $a = Caster::castObject($c, $c::class, method_exists($c, '__debugInfo'), $stub->class); + $c->setFlags($flags); + } + + unset($a["\0ArrayObject\0storage"], $a["\0ArrayIterator\0storage"]); + + $a += [ + $prefix.'storage' => $c->getArrayCopy(), + $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), + $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), + ]; + if ($c instanceof \ArrayObject) { + $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass()); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/StubCaster.php b/vendor/symfony/var-dumper/Caster/StubCaster.php new file mode 100644 index 0000000..56742b0 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/StubCaster.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts a caster's Stub. + * + * @author Nicolas Grekas + * + * @final + */ +class StubCaster +{ + public static function castStub(Stub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->type = $c->type; + $stub->class = $c->class; + $stub->value = $c->value; + $stub->handle = $c->handle; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) { + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + } + + $a = []; + } + + return $a; + } + + public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, bool $isNested): array + { + return $isNested ? $c->preservedSubset : $a; + } + + public static function cutInternals($obj, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->cut += \count($a); + + return []; + } + + return $a; + } + + public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->class = $c->dumpKeys ? '' : null; + $stub->handle = 0; + $stub->value = null; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + $a = []; + + if ($c->value) { + foreach (array_keys($c->value) as $k) { + $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; + } + // Preserve references with array_combine() + $a = array_combine($keys, $c->value); + } + } + + return $a; + } + + public static function castScalar(ScalarStub $scalarStub, array $a, Stub $stub): array + { + $stub->type = Stub::TYPE_SCALAR; + $stub->attr['value'] = $scalarStub->value; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php new file mode 100644 index 0000000..5cd90f7 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarExporter\Internal\LazyObjectState; + +/** + * @final + */ +class SymfonyCaster +{ + private const REQUEST_GETTERS = [ + 'pathInfo' => 'getPathInfo', + 'requestUri' => 'getRequestUri', + 'baseUrl' => 'getBaseUrl', + 'basePath' => 'getBasePath', + 'method' => 'getMethod', + 'format' => 'getRequestFormat', + ]; + + public static function castRequest(Request $request, array $a, Stub $stub, bool $isNested): array + { + $clone = null; + + foreach (self::REQUEST_GETTERS as $prop => $getter) { + $key = Caster::PREFIX_PROTECTED.$prop; + if (\array_key_exists($key, $a) && null === $a[$key]) { + $clone ??= clone $request; + $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}(); + } + } + + return $a; + } + + public static function castHttpClient($client, array $a, Stub $stub, bool $isNested): array + { + $multiKey = sprintf("\0%s\0multi", $client::class); + if (isset($a[$multiKey])) { + $a[$multiKey] = new CutStub($a[$multiKey]); + } + + return $a; + } + + public static function castHttpClientResponse($response, array $a, Stub $stub, bool $isNested): array + { + $stub->cut += \count($a); + $a = []; + + foreach ($response->getInfo() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castLazyObjectState($state, array $a, Stub $stub, bool $isNested): array + { + if (!$isNested) { + return $a; + } + + $stub->cut += \count($a) - 1; + + $instance = $a['realInstance'] ?? null; + + $a = ['status' => new ConstStub(match ($a['status']) { + LazyObjectState::STATUS_INITIALIZED_FULL => 'INITIALIZED_FULL', + LazyObjectState::STATUS_INITIALIZED_PARTIAL => 'INITIALIZED_PARTIAL', + LazyObjectState::STATUS_UNINITIALIZED_FULL => 'UNINITIALIZED_FULL', + LazyObjectState::STATUS_UNINITIALIZED_PARTIAL => 'UNINITIALIZED_PARTIAL', + }, $a['status'])]; + + if ($instance) { + $a['realInstance'] = $instance; + --$stub->cut; + } + + return $a; + } + + public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $uuid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toBase32'] = $uuid->toBase32(); + + // symfony/uid >= 5.3 + if (method_exists($uuid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $uuid->getDateTime()->format('Y-m-d H:i:s.u \U\T\C'); + } + + return $a; + } + + public static function castUlid(Ulid $ulid, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $ulid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toRfc4122'] = $ulid->toRfc4122(); + + // symfony/uid >= 5.3 + if (method_exists($ulid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $ulid->getDateTime()->format('Y-m-d H:i:s.v \U\T\C'); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/TraceStub.php b/vendor/symfony/var-dumper/Caster/TraceStub.php new file mode 100644 index 0000000..5766e51 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/TraceStub.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class TraceStub extends Stub +{ + public bool $keepArgs; + public int $sliceOffset; + public ?int $sliceLength; + public int $numberingOffset; + + public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, ?int $sliceLength = null, int $numberingOffset = 0) + { + $this->value = $trace; + $this->keepArgs = $keepArgs; + $this->sliceOffset = $sliceOffset; + $this->sliceLength = $sliceLength; + $this->numberingOffset = $numberingOffset; + } +} diff --git a/vendor/symfony/var-dumper/Caster/UninitializedStub.php b/vendor/symfony/var-dumper/Caster/UninitializedStub.php new file mode 100644 index 0000000..a9bdd9b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/UninitializedStub.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents an uninitialized property. + * + * @author Nicolas Grekas + */ +class UninitializedStub extends ConstStub +{ + public function __construct(\ReflectionProperty $property) + { + parent::__construct('?'.($property->hasType() ? ' '.$property->getType() : ''), 'Uninitialized property'); + } +} diff --git a/vendor/symfony/var-dumper/Caster/UuidCaster.php b/vendor/symfony/var-dumper/Caster/UuidCaster.php new file mode 100644 index 0000000..b102774 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/UuidCaster.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class UuidCaster +{ + public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'uuid' => (string) $c, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php new file mode 100644 index 0000000..672fec6 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XmlReader class to array representation. + * + * @author Baptiste Clavié + * + * @final + */ +class XmlReaderCaster +{ + private const NODE_TYPES = [ + \XMLReader::NONE => 'NONE', + \XMLReader::ELEMENT => 'ELEMENT', + \XMLReader::ATTRIBUTE => 'ATTRIBUTE', + \XMLReader::TEXT => 'TEXT', + \XMLReader::CDATA => 'CDATA', + \XMLReader::ENTITY_REF => 'ENTITY_REF', + \XMLReader::ENTITY => 'ENTITY', + \XMLReader::PI => 'PI (Processing Instruction)', + \XMLReader::COMMENT => 'COMMENT', + \XMLReader::DOC => 'DOC', + \XMLReader::DOC_TYPE => 'DOC_TYPE', + \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', + \XMLReader::NOTATION => 'NOTATION', + \XMLReader::WHITESPACE => 'WHITESPACE', + \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', + \XMLReader::END_ELEMENT => 'END_ELEMENT', + \XMLReader::END_ENTITY => 'END_ENTITY', + \XMLReader::XML_DECLARATION => 'XML_DECLARATION', + ]; + + public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, bool $isNested): array + { + try { + $properties = [ + 'LOADDTD' => @$reader->getParserProperty(\XMLReader::LOADDTD), + 'DEFAULTATTRS' => @$reader->getParserProperty(\XMLReader::DEFAULTATTRS), + 'VALIDATE' => @$reader->getParserProperty(\XMLReader::VALIDATE), + 'SUBST_ENTITIES' => @$reader->getParserProperty(\XMLReader::SUBST_ENTITIES), + ]; + } catch (\Error) { + $properties = [ + 'LOADDTD' => false, + 'DEFAULTATTRS' => false, + 'VALIDATE' => false, + 'SUBST_ENTITIES' => false, + ]; + } + + $props = Caster::PREFIX_VIRTUAL.'parserProperties'; + $info = [ + 'localName' => $reader->localName, + 'prefix' => $reader->prefix, + 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType), + 'depth' => $reader->depth, + 'isDefault' => $reader->isDefault, + 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, + 'xmlLang' => $reader->xmlLang, + 'attributeCount' => $reader->attributeCount, + 'value' => $reader->value, + 'namespaceURI' => $reader->namespaceURI, + 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, + $props => $properties, + ]; + + if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { + $info[$props] = new EnumStub($info[$props]); + $info[$props]->cut = $count; + } + + $a = Caster::filter($a, Caster::EXCLUDE_UNINITIALIZED, [], $count); + $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count); + // +2 because hasValue and hasAttributes are always filtered + $stub->cut += $count + 2; + + return $a + $info; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php new file mode 100644 index 0000000..fd3d3a2 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XML resources to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class XmlResourceCaster +{ + private const XML_ERRORS = [ + \XML_ERROR_NONE => 'XML_ERROR_NONE', + \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', + \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', + \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS', + \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN', + \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN', + \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR', + \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH', + \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE', + \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT', + \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF', + \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY', + \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF', + \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY', + \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF', + \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF', + \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF', + \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI', + \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING', + \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', + \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', + \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', + ]; + + public static function castXml($h, array $a, Stub $stub, bool $isNested): array + { + $a['current_byte_index'] = xml_get_current_byte_index($h); + $a['current_column_number'] = xml_get_current_column_number($h); + $a['current_line_number'] = xml_get_current_line_number($h); + $a['error_code'] = xml_get_error_code($h); + + if (isset(self::XML_ERRORS[$a['error_code']])) { + $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php new file mode 100644 index 0000000..ec6ff8c --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php @@ -0,0 +1,404 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * AbstractCloner implements a generic caster mechanism for objects and resources. + * + * @author Nicolas Grekas + */ +abstract class AbstractCloner implements ClonerInterface +{ + public static array $defaultCasters = [ + '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], + + 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], + 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + 'Symfony\Component\VarDumper\Caster\ScalarStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castScalar'], + + 'Fiber' => ['Symfony\Component\VarDumper\Caster\FiberCaster', 'castFiber'], + + 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], + 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], + 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], + 'ReflectionAttribute' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castAttribute'], + 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], + 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], + 'ReflectionClassConstant' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClassConstant'], + 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], + 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], + 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], + 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], + 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], + 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], + 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], + + 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], + 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], + 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], + 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], + 'Dom\Exception' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], + 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], + 'Dom\Implementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], + 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], + 'Dom\Node' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], + 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], + 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], + 'Dom\XMLDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXMLDocument'], + 'Dom\HTMLDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castHTMLDocument'], + 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'Dom\NodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'Dom\DTDNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], + 'Dom\CharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], + 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], + 'Dom\Attr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], + 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], + 'Dom\Element' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], + 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], + 'Dom\Text' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], + 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], + 'Dom\DocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], + 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], + 'Dom\Notation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], + 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], + 'Dom\Entity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], + 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], + 'Dom\ProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], + 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], + + 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], + + 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], + 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], + 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], + 'Symfony\Bridge\Monolog\Logger' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\HttpClient\AmpHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\Response\AmpResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], + 'Symfony\Component\Uid\Ulid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUlid'], + 'Symfony\Component\Uid\Uuid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUuid'], + 'Symfony\Component\VarExporter\Internal\LazyObjectState' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castLazyObjectState'], + 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], + 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], + 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], + 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\ErrorHandler\Exception\FlattenException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFlattenException'], + 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], + + 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], + + 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], + + 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], + 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], + 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], + + 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], + 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], + 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], + 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], + 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], + + 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], + 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], + 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], + 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], + 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], + 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], + 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], + 'WeakMap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakMap'], + 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], + + 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'Relay\Relay' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], + 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], + + 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], + 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], + 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], + 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], + + 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], + + 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], + 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], + 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], + 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], + 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], + + 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], + + 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], + 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], + 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], + 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], + + 'mysqli_driver' => ['Symfony\Component\VarDumper\Caster\MysqliCaster', 'castMysqliDriver'], + + 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + + ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + + 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + + ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], + ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], + ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], + ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + + 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + + ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], + + 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + + 'RdKafka' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castRdKafka'], + 'RdKafka\Conf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castConf'], + 'RdKafka\KafkaConsumer' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castKafkaConsumer'], + 'RdKafka\Metadata\Broker' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castBrokerMetadata'], + 'RdKafka\Metadata\Collection' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castCollectionMetadata'], + 'RdKafka\Metadata\Partition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castPartitionMetadata'], + 'RdKafka\Metadata\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicMetadata'], + 'RdKafka\Message' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castMessage'], + 'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'], + 'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'], + 'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'], + + 'FFI\CData' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'], + 'FFI\CType' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'], + ]; + + protected int $maxItems = 2500; + protected int $maxString = -1; + protected int $minDepth = 1; + + /** + * @var array> + */ + private array $casters = []; + + /** + * @var callable|null + */ + private $prevErrorHandler; + + private array $classInfo = []; + private int $filter = 0; + + /** + * @param callable[]|null $casters A map of casters + * + * @see addCasters + */ + public function __construct(?array $casters = null) + { + $this->addCasters($casters ?? static::$defaultCasters); + } + + /** + * Adds casters for resources and objects. + * + * Maps resources or objects types to a callback. + * Types are in the key, with a callable caster for value. + * Resource types are to be prefixed with a `:`, + * see e.g. static::$defaultCasters. + * + * @param callable[] $casters A map of casters + */ + public function addCasters(array $casters): void + { + foreach ($casters as $type => $callback) { + $this->casters[$type][] = $callback; + } + } + + /** + * Sets the maximum number of items to clone past the minimum depth in nested structures. + */ + public function setMaxItems(int $maxItems): void + { + $this->maxItems = $maxItems; + } + + /** + * Sets the maximum cloned length for strings. + */ + public function setMaxString(int $maxString): void + { + $this->maxString = $maxString; + } + + /** + * Sets the minimum tree depth where we are guaranteed to clone all the items. After this + * depth is reached, only setMaxItems items will be cloned. + */ + public function setMinDepth(int $minDepth): void + { + $this->minDepth = $minDepth; + } + + /** + * Clones a PHP variable. + * + * @param int $filter A bit field of Caster::EXCLUDE_* constants + */ + public function cloneVar(mixed $var, int $filter = 0): Data + { + $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { + if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { + // Cloner never dies + throw new \ErrorException($msg, 0, $type, $file, $line); + } + + if ($this->prevErrorHandler) { + return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); + } + + return false; + }); + $this->filter = $filter; + + if ($gc = gc_enabled()) { + gc_disable(); + } + try { + return new Data($this->doClone($var)); + } finally { + if ($gc) { + gc_enable(); + } + restore_error_handler(); + $this->prevErrorHandler = null; + } + } + + /** + * Effectively clones the PHP variable. + */ + abstract protected function doClone(mixed $var): array; + + /** + * Casts an object to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + */ + protected function castObject(Stub $stub, bool $isNested): array + { + $obj = $stub->value; + $class = $stub->class; + + if (str_contains($class, "@anonymous\0")) { + $stub->class = get_debug_type($obj); + } + if (isset($this->classInfo[$class])) { + [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; + } else { + $i = 2; + $parents = [$class]; + $hasDebugInfo = method_exists($class, '__debugInfo'); + + foreach (class_parents($class) as $p) { + $parents[] = $p; + ++$i; + } + foreach (class_implements($class) as $p) { + $parents[] = $p; + ++$i; + } + $parents[] = '*'; + + $r = new \ReflectionClass($class); + $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; + } + + $stub->attr += $fileInfo; + $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); + + try { + while ($i--) { + if (!empty($this->casters[$p = $parents[$i]])) { + foreach ($this->casters[$p] as $callback) { + $a = $callback($obj, $a, $stub, $isNested, $this->filter); + } + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } + + /** + * Casts a resource to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + */ + protected function castResource(Stub $stub, bool $isNested): array + { + $a = []; + $res = $stub->value; + $type = $stub->class; + + try { + if (!empty($this->casters[':'.$type])) { + foreach ($this->casters[':'.$type] as $callback) { + $a = $callback($res, $a, $stub, $isNested, $this->filter); + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php new file mode 100644 index 0000000..5a8e2e4 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +interface ClonerInterface +{ + /** + * Clones a PHP variable. + */ + public function cloneVar(mixed $var): Data; +} diff --git a/vendor/symfony/var-dumper/Cloner/Cursor.php b/vendor/symfony/var-dumper/Cloner/Cursor.php new file mode 100644 index 0000000..8923007 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Cursor.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the current state of a dumper while dumping. + * + * @author Nicolas Grekas + */ +class Cursor +{ + public const HASH_INDEXED = Stub::ARRAY_INDEXED; + public const HASH_ASSOC = Stub::ARRAY_ASSOC; + public const HASH_OBJECT = Stub::TYPE_OBJECT; + public const HASH_RESOURCE = Stub::TYPE_RESOURCE; + + public int $depth = 0; + public int $refIndex = 0; + public int $softRefTo = 0; + public int $softRefCount = 0; + public int $softRefHandle = 0; + public int $hardRefTo = 0; + public int $hardRefCount = 0; + public int $hardRefHandle = 0; + public int $hashType; + public string|int|null $hashKey = null; + public bool $hashKeyIsBinary; + public int $hashIndex = 0; + public int $hashLength = 0; + public int $hashCut = 0; + public bool $stop = false; + public array $attr = []; + public bool $skipChildren = false; +} diff --git a/vendor/symfony/var-dumper/Cloner/Data.php b/vendor/symfony/var-dumper/Cloner/Data.php new file mode 100644 index 0000000..71e78a6 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Data.php @@ -0,0 +1,429 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; + +/** + * @author Nicolas Grekas + */ +class Data implements \ArrayAccess, \Countable, \IteratorAggregate, \Stringable +{ + private array $data; + private int $position = 0; + private int|string $key = 0; + private int $maxDepth = 20; + private int $maxItemsPerDepth = -1; + private int $useRefHandles = -1; + private array $context = []; + + /** + * @param array $data An array as returned by ClonerInterface::cloneVar() + */ + public function __construct(array $data) + { + $this->data = $data; + } + + public function getType(): ?string + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return \gettype($item); + } + if (Stub::TYPE_STRING === $item->type) { + return 'string'; + } + if (Stub::TYPE_ARRAY === $item->type) { + return 'array'; + } + if (Stub::TYPE_OBJECT === $item->type) { + return $item->class; + } + if (Stub::TYPE_RESOURCE === $item->type) { + return $item->class.' resource'; + } + + return null; + } + + /** + * Returns a native representation of the original value. + * + * @param array|bool $recursive Whether values should be resolved recursively or not + * + * @return string|int|float|bool|array|Data[]|null + */ + public function getValue(array|bool $recursive = false): string|int|float|bool|array|null + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub) { + return $item; + } + if (Stub::TYPE_STRING === $item->type) { + return $item->value; + } + + $children = $item->position ? $this->data[$item->position] : []; + + foreach ($children as $k => $v) { + if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { + continue; + } + $children[$k] = clone $this; + $children[$k]->key = $k; + $children[$k]->position = $item->position; + + if ($recursive) { + if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { + $recursive = (array) $recursive; + if (isset($recursive[$v->position])) { + continue; + } + $recursive[$v->position] = true; + } + $children[$k] = $children[$k]->getValue($recursive); + } + } + + return $children; + } + + public function count(): int + { + return \count($this->getValue()); + } + + public function getIterator(): \Traversable + { + if (!\is_array($value = $this->getValue())) { + throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, get_debug_type($value))); + } + + yield from $value; + } + + public function __get(string $key): mixed + { + if (null !== $data = $this->seek($key)) { + $item = $this->getStub($data->data[$data->position][$data->key]); + + return $item instanceof Stub || [] === $item ? $data : $item; + } + + return null; + } + + public function __isset(string $key): bool + { + return null !== $this->seek($key); + } + + public function offsetExists(mixed $key): bool + { + return $this->__isset($key); + } + + public function offsetGet(mixed $key): mixed + { + return $this->__get($key); + } + + public function offsetSet(mixed $key, mixed $value): void + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + public function offsetUnset(mixed $key): void + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + public function __toString(): string + { + $value = $this->getValue(); + + if (!\is_array($value)) { + return (string) $value; + } + + return sprintf('%s (count=%d)', $this->getType(), \count($value)); + } + + /** + * Returns a depth limited clone of $this. + */ + public function withMaxDepth(int $maxDepth): static + { + $data = clone $this; + $data->maxDepth = $maxDepth; + + return $data; + } + + /** + * Limits the number of elements per depth level. + */ + public function withMaxItemsPerDepth(int $maxItemsPerDepth): static + { + $data = clone $this; + $data->maxItemsPerDepth = $maxItemsPerDepth; + + return $data; + } + + /** + * Enables/disables objects' identifiers tracking. + * + * @param bool $useRefHandles False to hide global ref. handles + */ + public function withRefHandles(bool $useRefHandles): static + { + $data = clone $this; + $data->useRefHandles = $useRefHandles ? -1 : 0; + + return $data; + } + + public function withContext(array $context): static + { + $data = clone $this; + $data->context = $context; + + return $data; + } + + public function getContext(): array + { + return $this->context; + } + + /** + * Seeks to a specific key in nested data structures. + */ + public function seek(string|int $key): ?static + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { + return null; + } + $keys = [$key]; + + switch ($item->type) { + case Stub::TYPE_OBJECT: + $keys[] = Caster::PREFIX_DYNAMIC.$key; + $keys[] = Caster::PREFIX_PROTECTED.$key; + $keys[] = Caster::PREFIX_VIRTUAL.$key; + $keys[] = "\0$item->class\0$key"; + // no break + case Stub::TYPE_ARRAY: + case Stub::TYPE_RESOURCE: + break; + default: + return null; + } + + $data = null; + $children = $this->data[$item->position]; + + foreach ($keys as $key) { + if (isset($children[$key]) || \array_key_exists($key, $children)) { + $data = clone $this; + $data->key = $key; + $data->position = $item->position; + break; + } + } + + return $data; + } + + /** + * Dumps data with a DumperInterface dumper. + */ + public function dump(DumperInterface $dumper): void + { + $refs = [0]; + $cursor = new Cursor(); + $cursor->hashType = -1; + $cursor->attr = $this->context[SourceContextProvider::class] ?? []; + $label = $this->context['label'] ?? ''; + + if ($cursor->attr || '' !== $label) { + $dumper->dumpScalar($cursor, 'label', $label); + } + $cursor->hashType = 0; + $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); + } + + /** + * Depth-first dumping of items. + * + * @param mixed $item A Stub object or the original value being dumped + */ + private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, mixed $item): void + { + $cursor->refIndex = 0; + $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; + $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; + $firstSeen = true; + + if (!$item instanceof Stub) { + $cursor->attr = []; + $type = \gettype($item); + if ($item && 'array' === $type) { + $item = $this->getStub($item); + } + } elseif (Stub::TYPE_REF === $item->type) { + if ($item->handle) { + if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->hardRefTo = $refs[$r]; + $cursor->hardRefHandle = $this->useRefHandles & $item->handle; + $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0; + } + $cursor->attr = $item->attr; + $type = $item->class ?: \gettype($item->value); + $item = $this->getStub($item->value); + } + if ($item instanceof Stub) { + if ($item->refCount) { + if (!isset($refs[$r = $item->handle])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->softRefTo = $refs[$r]; + } + $cursor->softRefHandle = $this->useRefHandles & $item->handle; + $cursor->softRefCount = $item->refCount; + $cursor->attr = $item->attr; + $cut = $item->cut; + + if ($item->position && $firstSeen) { + $children = $this->data[$item->position]; + + if ($cursor->stop) { + if ($cut >= 0) { + $cut += \count($children); + } + $children = []; + } + } else { + $children = []; + } + switch ($item->type) { + case Stub::TYPE_STRING: + $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); + break; + + case Stub::TYPE_ARRAY: + $item = clone $item; + $item->type = $item->class; + $item->class = $item->value; + // no break + case Stub::TYPE_OBJECT: + case Stub::TYPE_RESOURCE: + $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; + $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); + if ($withChildren) { + if ($cursor->skipChildren) { + $withChildren = false; + $cut = -1; + } else { + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); + } + } elseif ($children && 0 <= $cut) { + $cut += \count($children); + } + $cursor->skipChildren = false; + $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); + break; + + case Stub::TYPE_SCALAR: + $dumper->dumpScalar($cursor, 'default', $item->attr['value']); + break; + + default: + throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); + } + } elseif ('array' === $type) { + $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); + $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); + } elseif ('string' === $type) { + $dumper->dumpString($cursor, $item, false, 0); + } else { + $dumper->dumpScalar($cursor, $type, $item); + } + } + + /** + * Dumps children of hash structures. + * + * @return int The final number of removed items + */ + private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int + { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = \count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $key => $child) { + $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); + $cursor->hashKey = $dumpKeys ? $key : null; + $this->dumpItem($dumper, $cursor, $refs, $child); + if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; + } + } + + return $hashCut; + } + + private function getStub(mixed $item): mixed + { + if (!$item || !\is_array($item)) { + return $item; + } + + $stub = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + foreach ($item as $stub->class => $stub->position) { + } + if (isset($item[0])) { + $stub->cut = $item[0]; + } + $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); + + return $stub; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/vendor/symfony/var-dumper/Cloner/DumperInterface.php new file mode 100644 index 0000000..10f2da0 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/DumperInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * DumperInterface used by Data objects. + * + * @author Nicolas Grekas + */ +interface DumperInterface +{ + /** + * Dumps a scalar value. + */ + public function dumpScalar(Cursor $cursor, string $type, string|int|float|bool|null $value): void; + + /** + * Dumps a string. + * + * @param string $str The string being dumped + * @param bool $bin Whether $str is UTF-8 or binary encoded + * @param int $cut The number of characters $str has been cut by + */ + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut): void; + + /** + * Dumps while entering an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int|null $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + */ + public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild): void; + + /** + * Dumps while leaving an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int|null $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut): void; +} diff --git a/vendor/symfony/var-dumper/Cloner/Internal/NoDefault.php b/vendor/symfony/var-dumper/Cloner/Internal/NoDefault.php new file mode 100644 index 0000000..ed9db98 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Internal/NoDefault.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner\Internal; + +/** + * Flags a typed property that has no default value. + * + * This dummy object is used to distinguish a property with a default value of null + * from a property that is uninitialized by default. + * + * @internal + */ +enum NoDefault +{ + case NoDefault; +} diff --git a/vendor/symfony/var-dumper/Cloner/Stub.php b/vendor/symfony/var-dumper/Cloner/Stub.php new file mode 100644 index 0000000..4455a36 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Stub.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Cloner\Internal\NoDefault; + +/** + * Represents the main properties of a PHP variable. + * + * @author Nicolas Grekas + */ +class Stub +{ + public const TYPE_REF = 1; + public const TYPE_STRING = 2; + public const TYPE_ARRAY = 3; + public const TYPE_OBJECT = 4; + public const TYPE_RESOURCE = 5; + public const TYPE_SCALAR = 6; + + public const STRING_BINARY = 1; + public const STRING_UTF8 = 2; + + public const ARRAY_ASSOC = 1; + public const ARRAY_INDEXED = 2; + + public int $type = self::TYPE_REF; + public string|int|null $class = ''; + public mixed $value = null; + public int $cut = 0; + public int $handle = 0; + public int $refCount = 0; + public int $position = 0; + public array $attr = []; + + private static array $defaultProperties = []; + + /** + * @internal + */ + public function __sleep(): array + { + $properties = []; + + if (!isset(self::$defaultProperties[$c = static::class])) { + $reflection = new \ReflectionClass($c); + self::$defaultProperties[$c] = []; + + foreach ($reflection->getProperties() as $p) { + if ($p->isStatic()) { + continue; + } + + self::$defaultProperties[$c][$p->name] = $p->hasDefaultValue() ? $p->getDefaultValue() : ($p->hasType() ? NoDefault::NoDefault : null); + } + } + + foreach (self::$defaultProperties[$c] as $k => $v) { + if (NoDefault::NoDefault === $v || $this->$k !== $v) { + $properties[] = $k; + } + } + + return $properties; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/VarCloner.php b/vendor/symfony/var-dumper/Cloner/VarCloner.php new file mode 100644 index 0000000..170c8b4 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/VarCloner.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +class VarCloner extends AbstractCloner +{ + private static array $arrayCache = []; + + protected function doClone(mixed $var): array + { + $len = 1; // Length of $queue + $pos = 0; // Number of cloned items past the minimum depth + $refsCounter = 0; // Hard references counter + $queue = [[$var]]; // This breadth-first queue is the return value + $hardRefs = []; // Map of original zval ids to stub objects + $objRefs = []; // Map of original object handles to their stub object counterpart + $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning + $resRefs = []; // Map of original resource handles to their stub object counterpart + $values = []; // Map of stub objects' ids to original values + $maxItems = $this->maxItems; + $maxString = $this->maxString; + $minDepth = $this->minDepth; + $currentDepth = 0; // Current tree depth + $currentDepthFinalIndex = 0; // Final $queue index for current tree depth + $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached + $cookie = (object) []; // Unique object used to detect hard references + $a = null; // Array cast for nested structures + $stub = null; // Stub capturing the main properties of an original item value + // or null if the original value is used directly + + $arrayStub = new Stub(); + $arrayStub->type = Stub::TYPE_ARRAY; + + for ($i = 0; $i < $len; ++$i) { + // Detect when we move on to the next tree depth + if ($i > $currentDepthFinalIndex) { + ++$currentDepth; + $currentDepthFinalIndex = $len - 1; + if ($currentDepth >= $minDepth) { + $minimumDepthReached = true; + } + } + + $refs = $vals = $queue[$i]; + foreach ($vals as $k => $v) { + // $v is the original value or a stub object in case of hard references + + $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; + + if ($zvalRef) { + $vals[$k] = &$stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if (null !== $vals[$k] = $hardRefs[$zvalRef] ?? null) { + $v = $vals[$k]; + if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { + ++$v->value->refCount; + } + ++$v->refCount; + continue; + } + $vals[$k] = new Stub(); + $vals[$k]->value = $v; + $vals[$k]->handle = ++$refsCounter; + $hardRefs[$zvalRef] = $vals[$k]; + } + // Create $stub when the original value $v cannot be used directly + // If $v is a nested structure, put that structure in array $a + switch (true) { + case null === $v: + case \is_bool($v): + case \is_int($v): + case \is_float($v): + continue 2; + case \is_string($v): + if ('' === $v) { + continue 2; + } + if (!preg_match('//u', $v)) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { + $stub->cut = $cut; + $stub->value = substr($v, 0, -$cut); + } else { + $stub->value = $v; + } + } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_UTF8; + $stub->cut = $cut; + $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); + } else { + continue 2; + } + $a = null; + break; + + case \is_array($v): + if (!$v) { + continue 2; + } + $stub = $arrayStub; + + $stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; + $a = $v; + break; + + case \is_object($v): + if (empty($objRefs[$h = spl_object_id($v)])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_OBJECT; + $stub->class = $v::class; + $stub->value = $v; + $stub->handle = $h; + $a = $this->castObject($stub, 0 < $i); + if ($v !== $stub->value) { + if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { + break; + } + $stub->handle = $h = spl_object_id($stub->value); + } + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($objRefs[$h])) { + $objRefs[$h] = $stub; + $objects[] = $v; + } else { + $stub = $objRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + + default: // resource + if (empty($resRefs[$h = (int) $v])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_RESOURCE; + if ('Unknown' === $stub->class = @get_resource_type($v)) { + $stub->class = 'Closed'; + } + $stub->value = $v; + $stub->handle = $h; + $a = $this->castResource($stub, 0 < $i); + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($resRefs[$h])) { + $resRefs[$h] = $stub; + } else { + $stub = $resRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + } + + if ($a) { + if (!$minimumDepthReached || 0 > $maxItems) { + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($pos < $maxItems) { + if ($maxItems < $pos += \count($a)) { + $a = \array_slice($a, 0, $maxItems - $pos, true); + if ($stub->cut >= 0) { + $stub->cut += $pos - $maxItems; + } + } + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($stub->cut >= 0) { + $stub->cut += \count($a); + $stub->position = 0; + } + } + + if ($arrayStub === $stub) { + if ($arrayStub->cut) { + $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; + $arrayStub->cut = 0; + } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { + $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; + } else { + self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; + } + } + + if (!$zvalRef) { + $vals[$k] = $stub; + } else { + $hardRefs[$zvalRef]->value = $stub; + } + } + + $queue[$i] = $vals; + } + + foreach ($values as $h => $v) { + $hardRefs[$h] = $v; + } + + return $queue; + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php new file mode 100644 index 0000000..4450fe9 --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Describe collected data clones for cli output. + * + * @author Maxime Steinhausser + * + * @final + */ +class CliDescriptor implements DumpDescriptorInterface +{ + private CliDumper $dumper; + private mixed $lastIdentifier = null; + + public function __construct(CliDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); + $this->dumper->setColors($output->isDecorated()); + + $rows = [['date', date('r', (int) $context['timestamp'])]]; + $lastIdentifier = $this->lastIdentifier; + $this->lastIdentifier = $clientId; + + $section = "Received from client #$clientId"; + if (isset($context['request'])) { + $request = $context['request']; + $this->lastIdentifier = $request['identifier']; + $section = sprintf('%s %s', $request['method'], $request['uri']); + if ($controller = $request['controller']) { + $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")]; + } + } elseif (isset($context['cli'])) { + $this->lastIdentifier = $context['cli']['identifier']; + $section = '$ '.$context['cli']['command_line']; + } + + if ($this->lastIdentifier !== $lastIdentifier) { + $io->section($section); + } + + if (isset($context['source'])) { + $source = $context['source']; + $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']); + if ($fileLink = $source['file_link'] ?? null) { + $sourceInfo = sprintf('%s', $fileLink, $sourceInfo); + } + $rows[] = ['source', $sourceInfo]; + $file = $source['file_relative'] ?? $source['file']; + $rows[] = ['file', $file]; + } + + $io->table([], $rows); + + $this->dumper->dump($data); + $io->newLine(); + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php new file mode 100644 index 0000000..267d27b --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Maxime Steinhausser + */ +interface DumpDescriptorInterface +{ + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void; +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php new file mode 100644 index 0000000..98f150a --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Describe collected data clones for html output. + * + * @author Maxime Steinhausser + * + * @final + */ +class HtmlDescriptor implements DumpDescriptorInterface +{ + private HtmlDumper $dumper; + private bool $initialized = false; + + public function __construct(HtmlDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + if (!$this->initialized) { + $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css'); + $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js'); + $output->writeln(""); + $this->initialized = true; + } + + $title = '-'; + if (isset($context['request'])) { + $request = $context['request']; + $controller = "{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}"; + $title = sprintf('%s %s', $request['method'], $uri = $request['uri'], $uri); + $dedupIdentifier = $request['identifier']; + } elseif (isset($context['cli'])) { + $title = '$ '.$context['cli']['command_line']; + $dedupIdentifier = $context['cli']['identifier']; + } else { + $dedupIdentifier = uniqid('', true); + } + + $sourceDescription = ''; + if (isset($context['source'])) { + $source = $context['source']; + $projectDir = $source['project_dir'] ?? null; + $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']); + if (isset($source['file_link'])) { + $sourceDescription = sprintf('%s', $source['file_link'], $sourceDescription); + } + } + + $isoDate = $this->extractDate($context, 'c'); + $tags = array_filter([ + 'controller' => $controller ?? null, + 'project dir' => $projectDir ?? null, + ]); + + $output->writeln(<< +
    +
    +

    $title

    + +
    + {$this->renderTags($tags)} +
    +
    +

    + $sourceDescription +

    + {$this->dumper->dump($data, true)} +
    + +HTML + ); + } + + private function extractDate(array $context, string $format = 'r'): string + { + return date($format, (int) $context['timestamp']); + } + + private function renderTags(array $tags): string + { + if (!$tags) { + return ''; + } + + $renderedTags = ''; + foreach ($tags as $key => $value) { + $renderedTags .= sprintf('
  • %s%s
  • ', $key, $value); + } + + return << +
      + $renderedTags +
    + +HTML; + } +} diff --git a/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php new file mode 100644 index 0000000..b64a884 --- /dev/null +++ b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; +use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; +use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Server\DumpServer; + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + * + * @final + */ +#[AsCommand(name: 'server:dump', description: 'Start a dump server that collects and displays dumps in a single place')] +class ServerDumpCommand extends Command +{ + private DumpServer $server; + + /** @var DumpDescriptorInterface[] */ + private array $descriptors; + + public function __construct(DumpServer $server, array $descriptors = []) + { + $this->server = $server; + $this->descriptors = $descriptors + [ + 'cli' => new CliDescriptor(new CliDumper()), + 'html' => new HtmlDescriptor(new HtmlDumper()), + ]; + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', implode(', ', $this->getAvailableFormats())), 'cli') + ->setHelp(<<<'EOF' +%command.name% starts a dump server that collects and displays +dumps in a single place for debugging you application: + + php %command.full_name% + +You can consult dumped data in HTML format in your browser by providing the --format=html option +and redirecting the output to a file: + + php %command.full_name% --format="html" > dump.html + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $format = $input->getOption('format'); + + if (!$descriptor = $this->descriptors[$format] ?? null) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format)); + } + + $errorIo = $io->getErrorStyle(); + $errorIo->title('Symfony Var Dumper Server'); + + $this->server->start(); + + $errorIo->success(sprintf('Server listening on %s', $this->server->getHost())); + $errorIo->comment('Quit the server with CONTROL-C.'); + + $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) { + $descriptor->describe($io, $data, $context, $clientId); + }); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormats()); + } + } + + private function getAvailableFormats(): array + { + return array_keys($this->descriptors); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php new file mode 100644 index 0000000..20a1fcd --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\DumperInterface; + +/** + * Abstract mechanism for dumping a Data object. + * + * @author Nicolas Grekas + */ +abstract class AbstractDumper implements DataDumperInterface, DumperInterface +{ + public const DUMP_LIGHT_ARRAY = 1; + public const DUMP_STRING_LENGTH = 2; + public const DUMP_COMMA_SEPARATOR = 4; + public const DUMP_TRAILING_COMMA = 8; + + /** @var callable|resource|string|null */ + public static $defaultOutput = 'php://output'; + + protected string $line = ''; + /** @var callable|null */ + protected $lineDumper; + /** @var resource|null */ + protected $outputStream; + protected string $decimalPoint = '.'; + protected string $indentPad = ' '; + protected int $flags; + + private string $charset = ''; + + /** + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput + * @param string|null $charset The default character encoding to use for non-UTF8 strings + * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation + */ + public function __construct($output = null, ?string $charset = null, int $flags = 0) + { + $this->flags = $flags; + $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'); + $this->setOutput($output ?: static::$defaultOutput); + if (!$output && \is_string(static::$defaultOutput)) { + static::$defaultOutput = $this->outputStream; + } + } + + /** + * Sets the output destination of the dumps. + * + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path + * + * @return callable|resource|string|null The previous output destination + */ + public function setOutput($output) + { + $prev = $this->outputStream ?? $this->lineDumper; + + if (\is_callable($output)) { + $this->outputStream = null; + $this->lineDumper = $output; + } else { + if (\is_string($output)) { + $output = fopen($output, 'w'); + } + $this->outputStream = $output; + $this->lineDumper = $this->echoLine(...); + } + + return $prev; + } + + /** + * Sets the default character encoding to use for non-UTF8 strings. + * + * @return string The previous charset + */ + public function setCharset(string $charset): string + { + $prev = $this->charset; + + $charset = strtoupper($charset); + $charset = 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset; + + $this->charset = $charset; + + return $prev; + } + + /** + * Sets the indentation pad string. + * + * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level + * + * @return string The previous indent pad + */ + public function setIndentPad(string $pad): string + { + $prev = $this->indentPad; + $this->indentPad = $pad; + + return $prev; + } + + /** + * Dumps a Data object. + * + * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump + * + * @return string|null The dump as string when $output is true + */ + public function dump(Data $data, $output = null): ?string + { + if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) { + setlocale(\LC_NUMERIC, 'C'); + } + + if ($returnDump = true === $output) { + $output = fopen('php://memory', 'r+'); + } + if ($output) { + $prevOutput = $this->setOutput($output); + } + try { + $data->dump($this); + $this->dumpLine(-1); + + if ($returnDump) { + $result = stream_get_contents($output, -1, 0); + fclose($output); + + return $result; + } + } finally { + if ($output) { + $this->setOutput($prevOutput); + } + if ($locale) { + setlocale(\LC_NUMERIC, $locale); + } + } + + return null; + } + + /** + * Dumps the current line. + * + * @param int $depth The recursive depth in the dumped structure for the line being dumped, + * or -1 to signal the end-of-dump to the line dumper callable + */ + protected function dumpLine(int $depth): void + { + ($this->lineDumper)($this->line, $depth, $this->indentPad); + $this->line = ''; + } + + /** + * Generic line dumper callback. + */ + protected function echoLine(string $line, int $depth, string $indentPad): void + { + if (-1 !== $depth) { + fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n"); + } + } + + /** + * Converts a non-UTF-8 string to UTF-8. + */ + protected function utf8Encode(?string $s): ?string + { + if (null === $s || preg_match('//u', $s)) { + return $s; + } + + if (!\function_exists('iconv')) { + throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { + return $c; + } + if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) { + return $c; + } + + return iconv('CP850', 'UTF-8', $s); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php new file mode 100644 index 0000000..df17f2f --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php @@ -0,0 +1,655 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * CliDumper dumps variables for command line output. + * + * @author Nicolas Grekas + */ +class CliDumper extends AbstractDumper +{ + public static bool $defaultColors; + /** @var callable|resource|string|null */ + public static $defaultOutput = 'php://stdout'; + + protected bool $colors; + protected int $maxStringWidth = 0; + protected array $styles = [ + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + 'default' => '0;38;5;208', + 'num' => '1;38;5;38', + 'const' => '1;38;5;208', + 'str' => '1;38;5;113', + 'note' => '38;5;38', + 'ref' => '38;5;247', + 'public' => '', + 'protected' => '', + 'private' => '', + 'meta' => '38;5;170', + 'key' => '38;5;113', + 'index' => '38;5;38', + ]; + + protected static string $controlCharsRx = '/[\x00-\x1F\x7F]+/'; + protected static array $controlCharsMap = [ + "\t" => '\t', + "\n" => '\n', + "\v" => '\v', + "\f" => '\f', + "\r" => '\r', + "\033" => '\e', + ]; + protected static string $unicodeCharsRx = "/[\u{00A0}\u{00AD}\u{034F}\u{061C}\u{115F}\u{1160}\u{17B4}\u{17B5}\u{180E}\u{2000}-\u{200F}\u{202F}\u{205F}\u{2060}-\u{2064}\u{206A}-\u{206F}\u{3000}\u{2800}\u{3164}\u{FEFF}\u{FFA0}\u{1D159}\u{1D173}-\u{1D17A}]/u"; + + protected bool $collapseNextHash = false; + protected bool $expandNextHash = false; + + private array $displayOptions = [ + 'fileLinkFormat' => null, + ]; + + private bool $handlesHrefGracefully; + + public function __construct($output = null, ?string $charset = null, int $flags = 0) + { + parent::__construct($output, $charset, $flags); + + if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { + // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI + $this->setStyles([ + 'default' => '31', + 'num' => '1;34', + 'const' => '1;31', + 'str' => '1;32', + 'note' => '34', + 'ref' => '1;30', + 'meta' => '35', + 'key' => '32', + 'index' => '34', + ]); + } + + $this->displayOptions['fileLinkFormat'] = class_exists(FileLinkFormatter::class) ? new FileLinkFormatter() : (\ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'); + } + + /** + * Enables/disables colored output. + */ + public function setColors(bool $colors): void + { + $this->colors = $colors; + } + + /** + * Sets the maximum number of characters per line for dumped strings. + */ + public function setMaxStringWidth(int $maxStringWidth): void + { + $this->maxStringWidth = $maxStringWidth; + } + + /** + * Configures styles. + * + * @param array $styles A map of style names to style definitions + */ + public function setStyles(array $styles): void + { + $this->styles = $styles + $this->styles; + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions): void + { + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + public function dumpScalar(Cursor $cursor, string $type, string|int|float|bool|null $value): void + { + $this->dumpKey($cursor); + $this->collapseNextHash = $this->expandNextHash = false; + + $style = 'const'; + $attr = $cursor->attr; + + switch ($type) { + case 'default': + $style = 'default'; + break; + + case 'label': + $this->styles += ['label' => $this->styles['default']]; + $style = 'label'; + break; + + case 'integer': + $style = 'num'; + + if (isset($this->styles['integer'])) { + $style = 'integer'; + } + + break; + + case 'double': + $style = 'num'; + + if (isset($this->styles['float'])) { + $style = 'float'; + } + + $value = match (true) { + \INF === $value => 'INF', + -\INF === $value => '-INF', + is_nan($value) => 'NAN', + default => !str_contains($value = (string) $value, $this->decimalPoint) ? $value .= $this->decimalPoint.'0' : $value, + }; + break; + + case 'NULL': + $value = 'null'; + break; + + case 'boolean': + $value = $value ? 'true' : 'false'; + break; + + default: + $attr += ['value' => $this->utf8Encode($value)]; + $value = $this->utf8Encode($type); + break; + } + + $this->line .= $this->style($style, $value, $attr); + + $this->endValue($cursor); + } + + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut): void + { + $this->dumpKey($cursor); + $this->collapseNextHash = $this->expandNextHash = false; + $attr = $cursor->attr; + + if ($bin) { + $str = $this->utf8Encode($str); + } + if ('' === $str) { + $this->line .= '""'; + if ($cut) { + $this->line .= '…'.$cut; + } + $this->endValue($cursor); + } else { + $attr += [ + 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, + 'binary' => $bin, + ]; + $str = $bin && str_contains($str, "\0") ? [$str] : explode("\n", $str); + if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { + unset($str[1]); + $str[0] .= "\n"; + } + $m = \count($str) - 1; + $i = $lineCut = 0; + + if (self::DUMP_STRING_LENGTH & $this->flags) { + $this->line .= '('.$attr['length'].') '; + } + if ($bin) { + $this->line .= 'b'; + } + + if ($m) { + $this->line .= '"""'; + $this->dumpLine($cursor->depth); + } else { + $this->line .= '"'; + } + + foreach ($str as $str) { + if ($i < $m) { + $str .= "\n"; + } + if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { + $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); + $lineCut = $len - $this->maxStringWidth; + } + if ($m && 0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + if ('' !== $str) { + $this->line .= $this->style('str', $str, $attr); + } + if ($i++ == $m) { + if ($m) { + if ('' !== $str) { + $this->dumpLine($cursor->depth); + if (0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + } + $this->line .= '"""'; + } else { + $this->line .= '"'; + } + if ($cut < 0) { + $this->line .= '…'; + $lineCut = 0; + } elseif ($cut) { + $lineCut += $cut; + } + } + if ($lineCut) { + $this->line .= '…'.$lineCut; + $lineCut = 0; + } + + if ($i > $m) { + $this->endValue($cursor); + } else { + $this->dumpLine($cursor->depth); + } + } + } + } + + public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild): void + { + $this->colors ??= $this->supportsColors(); + + $this->dumpKey($cursor); + $this->expandNextHash = false; + $attr = $cursor->attr; + + if ($this->collapseNextHash) { + $cursor->skipChildren = true; + $this->collapseNextHash = $hasChild = false; + } + + $class = $this->utf8Encode($class); + if (Cursor::HASH_OBJECT === $type) { + $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; + } elseif (Cursor::HASH_RESOURCE === $type) { + $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); + } else { + $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; + } + + if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { + $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); + } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { + $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); + } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { + $prefix = substr($prefix, 0, -1); + } + + $this->line .= $prefix; + + if ($hasChild) { + $this->dumpLine($cursor->depth); + } + } + + public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut): void + { + if (empty($cursor->attr['cut_hash'])) { + $this->dumpEllipsis($cursor, $hasChild, $cut); + $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); + } + + $this->endValue($cursor); + } + + /** + * Dumps an ellipsis for cut children. + * + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut): void + { + if ($cut) { + $this->line .= ' …'; + if (0 < $cut) { + $this->line .= $cut; + } + if ($hasChild) { + $this->dumpLine($cursor->depth + 1); + } + } + } + + /** + * Dumps a key in a hash structure. + */ + protected function dumpKey(Cursor $cursor): void + { + if (null !== $key = $cursor->hashKey) { + if ($cursor->hashKeyIsBinary) { + $key = $this->utf8Encode($key); + } + $attr = ['binary' => $cursor->hashKeyIsBinary]; + $bin = $cursor->hashKeyIsBinary ? 'b' : ''; + $style = 'key'; + switch ($cursor->hashType) { + default: + case Cursor::HASH_INDEXED: + if (self::DUMP_LIGHT_ARRAY & $this->flags) { + break; + } + $style = 'index'; + // no break + case Cursor::HASH_ASSOC: + if (\is_int($key)) { + $this->line .= $this->style($style, $key).' => '; + } else { + $this->line .= $bin.'"'.$this->style($style, $key).'" => '; + } + break; + + case Cursor::HASH_RESOURCE: + $key = "\0~\0".$key; + // no break + case Cursor::HASH_OBJECT: + if (!isset($key[0]) || "\0" !== $key[0]) { + $this->line .= '+'.$bin.$this->style('public', $key).': '; + } elseif (0 < strpos($key, "\0", 1)) { + $key = explode("\0", substr($key, 1), 2); + + switch ($key[0][0]) { + case '+': // User inserted keys + $attr['dynamic'] = true; + $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": '; + break 2; + case '~': + $style = 'meta'; + if (isset($key[0][1])) { + parse_str(substr($key[0], 1), $attr); + $attr += ['binary' => $cursor->hashKeyIsBinary]; + } + break; + case '*': + $style = 'protected'; + $bin = '#'.$bin; + break; + default: + $attr['class'] = $key[0]; + $style = 'private'; + $bin = '-'.$bin; + break; + } + + if (isset($attr['collapse'])) { + if ($attr['collapse']) { + $this->collapseNextHash = true; + } else { + $this->expandNextHash = true; + } + } + + $this->line .= $bin.$this->style($style, $key[1], $attr).($attr['separator'] ?? ': '); + } else { + // This case should not happen + $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; + } + break; + } + + if ($cursor->hardRefTo) { + $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' '; + } + } + } + + /** + * Decorates a value with some style. + * + * @param string $style The type of style being applied + * @param string $value The value being styled + * @param array $attr Optional context information + */ + protected function style(string $style, string $value, array $attr = []): string + { + $this->colors ??= $this->supportsColors(); + + $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) + && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); + + if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { + $prefix = substr($value, 0, -$attr['ellipsis']); + if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && str_starts_with($prefix, $_SERVER[$pwd])) { + $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd])); + } + if (!empty($attr['ellipsis-tail'])) { + $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']); + $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']); + } else { + $value = substr($value, -$attr['ellipsis']); + } + + $value = $this->style('default', $prefix).$this->style($style, $value); + + goto href; + } + + $map = static::$controlCharsMap; + $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; + $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; + $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { + $s = $startCchr; + $c = $c[$i = 0]; + do { + $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i])); + } while (isset($c[++$i])); + + return $s.$endCchr; + }, $value, -1, $cchrCount); + + if (!($attr['binary'] ?? false)) { + $value = preg_replace_callback(static::$unicodeCharsRx, function ($c) use (&$cchrCount, $startCchr, $endCchr) { + ++$cchrCount; + + return $startCchr.'\u{'.strtoupper(dechex(mb_ord($c[0]))).'}'.$endCchr; + }, $value); + } + + if ($this->colors && '' !== $value) { + if ($cchrCount && "\033" === $value[0]) { + $value = substr($value, \strlen($startCchr)); + } else { + $value = "\033[{$this->styles[$style]}m".$value; + } + if ($cchrCount && str_ends_with($value, $endCchr)) { + $value = substr($value, 0, -\strlen($endCchr)); + } else { + $value .= "\033[{$this->styles['default']}m"; + } + } + + href: + if ($this->colors && $this->handlesHrefGracefully) { + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + if ('note' === $style) { + $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\"; + } else { + $attr['href'] = $href; + } + } + if (isset($attr['href'])) { + if ('label' === $style) { + $value .= '^'; + } + $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\"; + } + } + + if ('label' === $style && '' !== $value) { + $value .= ' '; + } + + return $value; + } + + protected function supportsColors(): bool + { + if ($this->outputStream !== static::$defaultOutput) { + return $this->hasColorSupport($this->outputStream); + } + if (isset(static::$defaultColors)) { + return static::$defaultColors; + } + if (isset($_SERVER['argv'][1])) { + $colors = $_SERVER['argv']; + $i = \count($colors); + while (--$i > 0) { + if (isset($colors[$i][5])) { + switch ($colors[$i]) { + case '--ansi': + case '--color': + case '--color=yes': + case '--color=force': + case '--color=always': + case '--colors=always': + return static::$defaultColors = true; + + case '--no-ansi': + case '--color=no': + case '--color=none': + case '--color=never': + case '--colors=never': + return static::$defaultColors = false; + } + } + } + } + + $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'w') : $this->outputStream; + + return static::$defaultColors = $this->hasColorSupport($h); + } + + protected function dumpLine(int $depth, bool $endOfValue = false): void + { + if ($this->colors ??= $this->supportsColors()) { + $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); + } + parent::dumpLine($depth); + } + + protected function endValue(Cursor $cursor): void + { + if (-1 === $cursor->hashType) { + return; + } + + if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { + if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { + $this->line .= ','; + } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) { + $this->line .= ','; + } + } + + $this->dumpLine($cursor->depth, true); + } + + /** + * Returns true if the stream supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + */ + private function hasColorSupport(mixed $stream): bool + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + + // Follow https://no-color.org/ + if ('' !== (($_SERVER['NO_COLOR'] ?? getenv('NO_COLOR'))[0] ?? '')) { + return false; + } + + // Detect msysgit/mingw and assume this is a tty because detection + // does not work correctly, see https://github.com/composer/composer/issues/9690 + if (!@stream_isatty($stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR && @sapi_windows_vt100_support($stream)) { + return true; + } + + if ('Hyper' === getenv('TERM_PROGRAM') + || false !== getenv('COLORTERM') + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + ) { + return true; + } + + if ('dumb' === $term = (string) getenv('TERM')) { + return false; + } + + // See https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L157 + return preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term); + } + + /** + * Returns true if the Windows terminal supports true color. + * + * Note that this does not check an output stream, but relies on environment + * variables from known implementations, or a PHP and Windows version that + * supports true color. + */ + private function isWindowsTrueColor(): bool + { + $result = 183 <= getenv('ANSICON_VER') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM'); + + if (!$result) { + $version = sprintf( + '%s.%s.%s', + PHP_WINDOWS_VERSION_MAJOR, + PHP_WINDOWS_VERSION_MINOR, + PHP_WINDOWS_VERSION_BUILD + ); + $result = $version >= '10.0.15063'; + } + + return $result; + } + + private function getSourceLink(string $file, int $line): string|false + { + if ($fmt = $this->displayOptions['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line); + } + + return false; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php new file mode 100644 index 0000000..38f8789 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Tries to provide context on CLI. + * + * @author Maxime Steinhausser + */ +final class CliContextProvider implements ContextProviderInterface +{ + public function getContext(): ?array + { + if ('cli' !== \PHP_SAPI) { + return null; + } + + return [ + 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), + 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php new file mode 100644 index 0000000..532aa0f --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Interface to provide contextual data about dump data clones sent to a server. + * + * @author Maxime Steinhausser + */ +interface ContextProviderInterface +{ + public function getContext(): ?array; +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php new file mode 100644 index 0000000..69dff06 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * Tries to provide context from a request. + * + * @author Maxime Steinhausser + */ +final class RequestContextProvider implements ContextProviderInterface +{ + private RequestStack $requestStack; + private VarCloner $cloner; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(0); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function getContext(): ?array + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return null; + } + + $controller = $request->attributes->get('_controller'); + + return [ + 'uri' => $request->getUri(), + 'method' => $request->getMethod(), + 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, + 'identifier' => spl_object_hash($request), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php new file mode 100644 index 0000000..9477e53 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\VarDumper; +use Twig\Template; + +/** + * Tries to provide context from sources (class name, file, line, code excerpt, ...). + * + * @author Nicolas Grekas + * @author Maxime Steinhausser + */ +final class SourceContextProvider implements ContextProviderInterface +{ + private int $limit; + private ?string $charset; + private ?string $projectDir; + private ?FileLinkFormatter $fileLinkFormatter; + + public function __construct(?string $charset = null, ?string $projectDir = null, ?FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) + { + $this->charset = $charset; + $this->projectDir = $projectDir; + $this->fileLinkFormatter = $fileLinkFormatter; + $this->limit = $limit; + } + + public function getContext(): ?array + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); + + $file = $trace[1]['file']; + $line = $trace[1]['line']; + $name = '-' === $file || 'Standard input code' === $file ? 'Standard input code' : false; + $fileExcerpt = false; + + for ($i = 2; $i < $this->limit; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && VarDumper::class === $trace[$i]['class'] + ) { + $file = $trace[$i]['file'] ?? $file; + $line = $trace[$i]['line'] ?? $line; + + while (++$i < $this->limit) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = []; + + for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; + } + + $fileExcerpt = '
      '.implode("\n", $fileExcerpt).'
    '; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + $context = ['name' => $name, 'file' => $file, 'line' => $line]; + $context['file_excerpt'] = $fileExcerpt; + + if (null !== $this->projectDir) { + $context['project_dir'] = $this->projectDir; + if (str_starts_with($file, $this->projectDir)) { + $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + } + + if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) { + $context['file_link'] = $fileLink; + } + + return $context; + } + + private function htmlEncode(string $s): string + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php new file mode 100644 index 0000000..6de34d9 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * @author Kévin Thérage + */ +class ContextualizedDumper implements DataDumperInterface +{ + private DataDumperInterface $wrappedDumper; + private array $contextProviders; + + /** + * @param ContextProviderInterface[] $contextProviders + */ + public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) + { + $this->wrappedDumper = $wrappedDumper; + $this->contextProviders = $contextProviders; + } + + public function dump(Data $data): ?string + { + $context = $data->getContext(); + foreach ($this->contextProviders as $contextProvider) { + $context[$contextProvider::class] = $contextProvider->getContext(); + } + + return $this->wrappedDumper->dump($data->withContext($context)); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php new file mode 100644 index 0000000..df05b6a --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * DataDumperInterface for dumping Data objects. + * + * @author Nicolas Grekas + */ +interface DataDumperInterface +{ + /** + * @return string|null + */ + public function dump(Data $data); +} diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php new file mode 100644 index 0000000..74468ff --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php @@ -0,0 +1,970 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * HtmlDumper dumps variables as HTML. + * + * @author Nicolas Grekas + */ +class HtmlDumper extends CliDumper +{ + /** @var callable|resource|string|null */ + public static $defaultOutput = 'php://output'; + + protected static $themes = [ + 'dark' => [ + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#56DB3A', + 'note' => 'color:#1299DA', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#B729D9', + 'key' => 'color:#56DB3A', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#FF8400', + 'ns' => 'user-select:none;', + ], + 'light' => [ + 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#629755;', + 'note' => 'color:#6897BB', + 'ref' => 'color:#6E6E6E', + 'public' => 'color:#262626', + 'protected' => 'color:#262626', + 'private' => 'color:#262626', + 'meta' => 'color:#B729D9', + 'key' => 'color:#789339', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#CC7832', + 'ns' => 'user-select:none;', + ], + ]; + + protected ?string $dumpHeader = null; + protected string $dumpPrefix = '
    ';
    +    protected string $dumpSuffix = '
    '; + protected string $dumpId; + protected bool $colors = true; + protected $headerIsDumped = false; + protected int $lastDepth = -1; + + private array $displayOptions = [ + 'maxDepth' => 1, + 'maxStringLength' => 160, + 'fileLinkFormat' => null, + ]; + private array $extraDisplayOptions = []; + + public function __construct($output = null, ?string $charset = null, int $flags = 0) + { + AbstractDumper::__construct($output, $charset, $flags); + $this->dumpId = 'sf-dump-'.mt_rand(); + $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->styles = static::$themes['dark'] ?? self::$themes['dark']; + } + + public function setStyles(array $styles): void + { + $this->headerIsDumped = false; + $this->styles = $styles + $this->styles; + } + + public function setTheme(string $themeName): void + { + if (!isset(static::$themes[$themeName])) { + throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); + } + + $this->setStyles(static::$themes[$themeName]); + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions): void + { + $this->headerIsDumped = false; + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * Sets an HTML header that will be dumped once in the output stream. + */ + public function setDumpHeader(?string $header): void + { + $this->dumpHeader = $header; + } + + /** + * Sets an HTML prefix and suffix that will encapse every single dump. + */ + public function setDumpBoundaries(string $prefix, string $suffix): void + { + $this->dumpPrefix = $prefix; + $this->dumpSuffix = $suffix; + } + + public function dump(Data $data, $output = null, array $extraDisplayOptions = []): ?string + { + $this->extraDisplayOptions = $extraDisplayOptions; + $result = parent::dump($data, $output); + $this->dumpId = 'sf-dump-'.mt_rand(); + + return $result; + } + + /** + * Dumps the HTML header. + */ + protected function getDumpHeader(): string + { + $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; + + if (null !== $this->dumpHeader) { + return $this->dumpHeader; + } + + $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML' +'.$this->dumpHeader; + } + + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut): void + { + if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { + $this->dumpKey($cursor); + $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []); + $this->line .= $cursor->depth >= $this->displayOptions['maxDepth'] ? ' ' : ' '; + $this->endValue($cursor); + $this->line .= $this->indentPad; + $this->line .= sprintf('', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data'])); + $this->endValue($cursor); + } else { + parent::dumpString($cursor, $str, $bin, $cut); + } + } + + public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild): void + { + if (Cursor::HASH_OBJECT === $type) { + $cursor->attr['depth'] = $cursor->depth; + } + parent::enterHash($cursor, $type, $class, false); + + if ($cursor->skipChildren || $cursor->depth >= $this->displayOptions['maxDepth']) { + $cursor->skipChildren = false; + $eol = ' class=sf-dump-compact>'; + } else { + $this->expandNextHash = false; + $eol = ' class=sf-dump-expanded>'; + } + + if ($hasChild) { + $this->line .= 'dumpId, $r); + } + $this->line .= $eol; + $this->dumpLine($cursor->depth); + } + } + + public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut): void + { + $this->dumpEllipsis($cursor, $hasChild, $cut); + if ($hasChild) { + $this->line .= ''; + } + parent::leaveHash($cursor, $type, $class, $hasChild, 0); + } + + protected function style(string $style, string $value, array $attr = []): string + { + if ('' === $value && ('label' !== $style || !isset($attr['file']) && !isset($attr['href']))) { + return ''; + } + + $v = esc($value); + + if ('ref' === $style) { + if (empty($attr['count'])) { + return sprintf('%s', $v); + } + $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); + + return sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); + } + + if ('const' === $style && isset($attr['value'])) { + $style .= sprintf(' title="%s"', esc(\is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); + } elseif ('public' === $style) { + $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); + } elseif ('str' === $style && 1 < $attr['length']) { + $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); + } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { + $style .= ' title=""'; + $attr += [ + 'ellipsis' => \strlen($value) - $c, + 'ellipsis-type' => 'note', + 'ellipsis-tail' => 1, + ]; + } elseif ('protected' === $style) { + $style .= ' title="Protected property"'; + } elseif ('meta' === $style && isset($attr['title'])) { + $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); + } elseif ('private' === $style) { + $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); + } + + if (isset($attr['ellipsis'])) { + $class = 'sf-dump-ellipsis'; + if (isset($attr['ellipsis-type'])) { + $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); + } + $label = esc(substr($value, -$attr['ellipsis'])); + $style = str_replace(' title="', " title=\"$v\n", $style); + $v = sprintf('%s', $class, substr($v, 0, -\strlen($label))); + + if (!empty($attr['ellipsis-tail'])) { + $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); + $v .= sprintf('%s%s', $class, substr($label, 0, $tail), substr($label, $tail)); + } else { + $v .= $label; + } + } + + $map = static::$controlCharsMap; + $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { + $s = $b = ''; + }, $v).''; + + if (!($attr['binary'] ?? false)) { + $v = preg_replace_callback(static::$unicodeCharsRx, function ($c) { + return '\u{'.strtoupper(dechex(mb_ord($c[0]))).'}'; + }, $v); + } + + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + $attr['href'] = $href; + } + if (isset($attr['href'])) { + if ('label' === $style) { + $v .= '^'; + } + $target = isset($attr['file']) ? '' : ' target="_blank"'; + $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); + } + if (isset($attr['lang'])) { + $v = sprintf('%s', esc($attr['lang']), $v); + } + if ('label' === $style) { + $v .= ' '; + } + + return $v; + } + + protected function dumpLine(int $depth, bool $endOfValue = false): void + { + if (-1 === $this->lastDepth) { + $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; + } + if ($this->headerIsDumped !== ($this->outputStream ?? $this->lineDumper)) { + $this->line = $this->getDumpHeader().$this->line; + } + + if (-1 === $depth) { + $args = ['"'.$this->dumpId.'"']; + if ($this->extraDisplayOptions) { + $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); + } + // Replace is for BC + $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args)); + } + $this->lastDepth = $depth; + + $this->line = mb_encode_numericentity($this->line, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); + + if (-1 === $depth) { + AbstractDumper::dumpLine(0); + } + AbstractDumper::dumpLine($depth); + } + + private function getSourceLink(string $file, int $line): string|false + { + $options = $this->extraDisplayOptions + $this->displayOptions; + + if ($fmt = $options['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); + } + + return false; + } +} + +function esc(string $str): string +{ + return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); +} diff --git a/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/vendor/symfony/var-dumper/Dumper/ServerDumper.php new file mode 100644 index 0000000..0d35526 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ServerDumper.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Server\Connection; + +/** + * ServerDumper forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class ServerDumper implements DataDumperInterface +{ + private Connection $connection; + private ?DataDumperInterface $wrappedDumper; + + /** + * @param string $host The server host + * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, ?DataDumperInterface $wrappedDumper = null, array $contextProviders = []) + { + $this->connection = new Connection($host, $contextProviders); + $this->wrappedDumper = $wrappedDumper; + } + + public function getContextProviders(): array + { + return $this->connection->getContextProviders(); + } + + public function dump(Data $data): ?string + { + if (!$this->connection->write($data) && $this->wrappedDumper) { + return $this->wrappedDumper->dump($data); + } + + return null; + } +} diff --git a/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php new file mode 100644 index 0000000..fd8eca9 --- /dev/null +++ b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Exception; + +/** + * @author Nicolas Grekas + */ +class ThrowingCasterException extends \Exception +{ + /** + * @param \Throwable $prev The exception thrown from the caster + */ + public function __construct(\Throwable $prev) + { + parent::__construct('Unexpected '.$prev::class.' thrown from a caster: '.$prev->getMessage(), 0, $prev); + } +} diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE new file mode 100644 index 0000000..29f72d5 --- /dev/null +++ b/vendor/symfony/var-dumper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-present Fabien Potencier + +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. diff --git a/vendor/symfony/var-dumper/README.md b/vendor/symfony/var-dumper/README.md new file mode 100644 index 0000000..a0da8c9 --- /dev/null +++ b/vendor/symfony/var-dumper/README.md @@ -0,0 +1,15 @@ +VarDumper Component +=================== + +The VarDumper component provides mechanisms for walking through any arbitrary +PHP variable. It provides a better `dump()` function that you can use instead +of `var_dump()`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server new file mode 100755 index 0000000..f398fce --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/bin/var-dump-server @@ -0,0 +1,67 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + */ + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); + exit(1); +} + +$input = new ArgvInput(); +$output = new ConsoleOutput(); +$defaultHost = '127.0.0.1:9912'; +$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); +$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; + +$app = new Application(); + +$app->getDefinition()->addOption( + new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) +); + +$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run($input, $output) +; diff --git a/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css new file mode 100644 index 0000000..8f706d6 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css @@ -0,0 +1,130 @@ +body { + display: flex; + flex-direction: column-reverse; + justify-content: flex-end; + max-width: 1140px; + margin: auto; + padding: 15px; + word-wrap: break-word; + background-color: #F9F9F9; + color: #222; + font-family: Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.4; +} +p { + margin: 0; +} +a { + color: #218BC3; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +.text-small { + font-size: 12px !important; +} +article { + margin: 5px; + margin-bottom: 10px; +} +article > header > .row { + display: flex; + flex-direction: row; + align-items: baseline; + margin-bottom: 10px; +} +article > header > .row > .col { + flex: 1; + display: flex; + align-items: baseline; +} +article > header > .row > h2 { + font-size: 14px; + color: #222; + font-weight: normal; + font-family: "Lucida Console", monospace, sans-serif; + word-break: break-all; + margin: 20px 5px 0 0; + user-select: all; +} +article > header > .row > h2 > code { + white-space: nowrap; + user-select: none; + color: #cc2255; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 3px; + margin-right: 5px; + padding: 0 3px; +} +article > header > .row > time.col { + flex: 0; + text-align: right; + white-space: nowrap; + color: #999; + font-style: italic; +} +article > header ul.tags { + list-style: none; + padding: 0; + margin: 0; + font-size: 12px; +} +article > header ul.tags > li { + user-select: all; + margin-bottom: 2px; +} +article > header ul.tags > li > span.badge { + display: inline-block; + padding: .25em .4em; + margin-right: 5px; + border-radius: 4px; + background-color: #6c757d3b; + color: #524d4d; + font-size: 12px; + text-align: center; + font-weight: 700; + line-height: 1; + white-space: nowrap; + vertical-align: baseline; + user-select: none; +} +article > section.body { + border: 1px solid #d8d8d8; + background: #FFF; + padding: 10px; + border-radius: 3px; +} +pre.sf-dump { + border-radius: 3px; + margin-bottom: 0; +} +.hidden { + display: none !important; +} +.dumped-tag > .sf-dump { + display: inline-block; + margin: 0; + padding: 1px 5px; + line-height: 1.4; + vertical-align: top; + background-color: transparent; + user-select: auto; +} +.dumped-tag > pre.sf-dump, +.dumped-tag > .sf-dump-default { + color: #CC7832; + background: none; +} +.dumped-tag > .sf-dump .sf-dump-str { color: #629755; } +.dumped-tag > .sf-dump .sf-dump-private, +.dumped-tag > .sf-dump .sf-dump-protected, +.dumped-tag > .sf-dump .sf-dump-public { color: #262626; } +.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } +.dumped-tag > .sf-dump .sf-dump-key { color: #789339; } +.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } +.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } +.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } +.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } diff --git a/vendor/symfony/var-dumper/Resources/functions/dump.php b/vendor/symfony/var-dumper/Resources/functions/dump.php new file mode 100644 index 0000000..e6ade0d --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/functions/dump.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\VarDumper\Caster\ScalarStub; +use Symfony\Component\VarDumper\VarDumper; + +if (!function_exists('dump')) { + /** + * @author Nicolas Grekas + * @author Alexandre Daubois + */ + function dump(mixed ...$vars): mixed + { + if (!$vars) { + VarDumper::dump(new ScalarStub('ðŸ›')); + + return null; + } + + if (array_key_exists(0, $vars) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + $k = 0; + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } + } + + if (1 < count($vars)) { + return $vars; + } + + return $vars[$k]; + } +} + +if (!function_exists('dd')) { + function dd(mixed ...$vars): never + { + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) && !headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + + if (!$vars) { + VarDumper::dump(new ScalarStub('ðŸ›')); + + exit(1); + } + + if (array_key_exists(0, $vars) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } + } + + exit(1); + } +} diff --git a/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js new file mode 100644 index 0000000..63101e5 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function() { + let prev = null; + Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { + const dedupId = article.dataset.dedupId; + if (dedupId === prev) { + article.getElementsByTagName('header')[0].classList.add('hidden'); + } + prev = dedupId; + }); +}); diff --git a/vendor/symfony/var-dumper/Server/Connection.php b/vendor/symfony/var-dumper/Server/Connection.php new file mode 100644 index 0000000..4383278 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/Connection.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * Forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class Connection +{ + private string $host; + private array $contextProviders; + + /** + * @var resource|null + */ + private $socket; + + /** + * @param string $host The server host + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, array $contextProviders = []) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->contextProviders = $contextProviders; + } + + public function getContextProviders(): array + { + return $this->contextProviders; + } + + public function write(Data $data): bool + { + $socketIsFresh = !$this->socket; + if (!$this->socket = $this->socket ?: $this->createSocket()) { + return false; + } + + $context = ['timestamp' => microtime(true)]; + foreach ($this->contextProviders as $name => $provider) { + $context[$name] = $provider->getContext(); + } + $context = array_filter($context); + $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; + + set_error_handler(static fn () => null); + try { + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + if (!$socketIsFresh) { + stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); + fclose($this->socket); + $this->socket = $this->createSocket(); + } + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + } finally { + restore_error_handler(); + } + + return false; + } + + /** + * @return resource|null + */ + private function createSocket() + { + set_error_handler(static fn () => null); + try { + return stream_socket_client($this->host, $errno, $errstr, 3) ?: null; + } finally { + restore_error_handler(); + } + } +} diff --git a/vendor/symfony/var-dumper/Server/DumpServer.php b/vendor/symfony/var-dumper/Server/DumpServer.php new file mode 100644 index 0000000..a9228a2 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/DumpServer.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Psr\Log\LoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * A server collecting Data clones sent by a ServerDumper. + * + * @author Maxime Steinhausser + * + * @final + */ +class DumpServer +{ + private string $host; + private ?LoggerInterface $logger; + + /** + * @var resource|null + */ + private $socket; + + public function __construct(string $host, ?LoggerInterface $logger = null) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->logger = $logger; + } + + public function start(): void + { + if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { + throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); + } + } + + public function listen(callable $callback): void + { + if (null === $this->socket) { + $this->start(); + } + + foreach ($this->getMessages() as $clientId => $message) { + $this->logger?->info('Received a payload from client {clientId}', ['clientId' => $clientId]); + + $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); + + // Impossible to decode the message, give up. + if (false === $payload) { + $this->logger?->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); + + continue; + } + + if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { + $this->logger?->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); + + continue; + } + + [$data, $context] = $payload; + + $callback($data, $context, $clientId); + } + } + + public function getHost(): string + { + return $this->host; + } + + private function getMessages(): iterable + { + $sockets = [(int) $this->socket => $this->socket]; + $write = []; + + while (true) { + $read = $sockets; + stream_select($read, $write, $write, null); + + foreach ($read as $stream) { + if ($this->socket === $stream) { + $stream = stream_socket_accept($this->socket); + $sockets[(int) $stream] = $stream; + } elseif (feof($stream)) { + unset($sockets[(int) $stream]); + fclose($stream); + } else { + yield (int) $stream => fgets($stream); + } + } + } + } +} diff --git a/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php new file mode 100644 index 0000000..4475efd --- /dev/null +++ b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Test; + +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Nicolas Grekas + */ +trait VarDumperTestTrait +{ + /** + * @internal + */ + private array $varDumperConfig = [ + 'casters' => [], + 'flags' => null, + ]; + + protected function setUpVarDumper(array $casters, ?int $flags = null): void + { + $this->varDumperConfig['casters'] = $casters; + $this->varDumperConfig['flags'] = $flags; + } + + /** + * @after + */ + protected function tearDownVarDumper(): void + { + $this->varDumperConfig['casters'] = []; + $this->varDumperConfig['flags'] = null; + } + + public function assertDumpEquals(mixed $expected, mixed $data, int $filter = 0, string $message = '') + { + $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + public function assertDumpMatchesFormat(mixed $expected, mixed $data, int $filter = 0, string $message = '') + { + $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + protected function getDump(mixed $data, string|int|null $key = null, int $filter = 0): ?string + { + if (null === $flags = $this->varDumperConfig['flags']) { + $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; + $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; + $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; + } + + $cloner = new VarCloner(); + $cloner->addCasters($this->varDumperConfig['casters']); + $cloner->setMaxItems(-1); + $dumper = new CliDumper(null, null, $flags); + $dumper->setColors(false); + $data = $cloner->cloneVar($data, $filter)->withRefHandles(false); + if (null !== $key && null === $data = $data->seek($key)) { + return null; + } + + return rtrim($dumper->dump($data, true)); + } + + private function prepareExpectation(mixed $expected, int $filter): string + { + if (!\is_string($expected)) { + $expected = $this->getDump($expected, null, $filter); + } + + return rtrim($expected); + } +} diff --git a/vendor/symfony/var-dumper/VarDumper.php b/vendor/symfony/var-dumper/VarDumper.php new file mode 100644 index 0000000..423ffed --- /dev/null +++ b/vendor/symfony/var-dumper/VarDumper.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper; + +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\ServerDumper; + +// Load the global dump() function +require_once __DIR__.'/Resources/functions/dump.php'; + +/** + * @author Nicolas Grekas + */ +class VarDumper +{ + /** + * @var callable|null + */ + private static $handler; + + public static function dump(mixed $var, ?string $label = null): mixed + { + if (null === self::$handler) { + self::register(); + } + + return (self::$handler)($var, $label); + } + + public static function setHandler(?callable $callable): ?callable + { + $prevHandler = self::$handler; + + // Prevent replacing the handler with expected format as soon as the env var was set: + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + return $prevHandler; + } + + self::$handler = $callable; + + return $prevHandler; + } + + private static function register(): void + { + $cloner = new VarCloner(); + $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + + $format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null; + switch (true) { + case 'html' === $format: + $dumper = new HtmlDumper(); + break; + case 'cli' === $format: + $dumper = new CliDumper(); + break; + case 'server' === $format: + case $format && 'tcp' === parse_url($format, \PHP_URL_SCHEME): + $host = 'server' === $format ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format; + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliDumper() : new HtmlDumper(); + $dumper = new ServerDumper($host, $dumper, self::getDefaultContextProviders()); + break; + default: + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliDumper() : new HtmlDumper(); + } + + if (!$dumper instanceof ServerDumper) { + $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); + } + + self::$handler = function ($var, ?string $label = null) use ($cloner, $dumper) { + $var = $cloner->cloneVar($var); + + if (null !== $label) { + $var = $var->withContext(['label' => $label]); + } + + $dumper->dump($var); + }; + } + + private static function getDefaultContextProviders(): array + { + $contextProviders = []; + + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) && class_exists(Request::class)) { + $requestStack = new RequestStack(); + $requestStack->push(Request::createFromGlobals()); + $contextProviders['request'] = new RequestContextProvider($requestStack); + } + + $fileLinkFormatter = class_exists(FileLinkFormatter::class) ? new FileLinkFormatter(null, $requestStack ?? null) : null; + + return $contextProviders + [ + 'cli' => new CliContextProvider(), + 'source' => new SourceContextProvider(null, null, $fileLinkFormatter), + ]; + } +} diff --git a/vendor/symfony/var-dumper/composer.json b/vendor/symfony/var-dumper/composer.json new file mode 100644 index 0000000..cbc6717 --- /dev/null +++ b/vendor/symfony/var-dumper/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/var-dumper", + "type": "library", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "keywords": ["dump", "debug"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "autoload": { + "files": [ "Resources/functions/dump.php" ], + "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "minimum-stability": "dev" +} diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md new file mode 100644 index 0000000..fdca002 --- /dev/null +++ b/vendor/symfony/var-exporter/CHANGELOG.md @@ -0,0 +1,25 @@ +CHANGELOG +========= + +6.4 +--- + + * Deprecate per-property lazy-initializers + +6.2 +--- + + * Add support for lazy ghost objects and virtual proxies + * Add `Hydrator::hydrate()` + * Preserve PHP references also when using `Hydrator::hydrate()` or `Instantiator::instantiate()` + * Add support for hydrating from native (array) casts + +5.1.0 +----- + + * added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values + +4.2.0 +----- + + * added the component diff --git a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php new file mode 100644 index 0000000..379a765 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class ClassNotFoundException extends \Exception implements ExceptionInterface +{ + public function __construct(string $class, ?\Throwable $previous = null) + { + parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Exception/ExceptionInterface.php b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php new file mode 100644 index 0000000..adfaed4 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/var-exporter/Exception/LogicException.php b/vendor/symfony/var-exporter/Exception/LogicException.php new file mode 100644 index 0000000..619d055 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php new file mode 100644 index 0000000..b9ba225 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class NotInstantiableTypeException extends \Exception implements ExceptionInterface +{ + public function __construct(string $type, ?\Throwable $previous = null) + { + parent::__construct(sprintf('Type "%s" is not instantiable.', $type), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Hydrator.php b/vendor/symfony/var-exporter/Hydrator.php new file mode 100644 index 0000000..5f456fb --- /dev/null +++ b/vendor/symfony/var-exporter/Hydrator.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Internal\Hydrator as InternalHydrator; + +/** + * Utility class to hydrate the properties of an object. + * + * @author Nicolas Grekas + */ +final class Hydrator +{ + /** + * Sets the properties of an object, including private and protected ones. + * + * For example: + * + * // Sets the public or protected $object->propertyName property + * Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + * + * // Sets a private property defined on its parent Bar class: + * Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + * + * // Alternative way to set the private $object->privateBarProperty property + * Hydrator::hydrate($object, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be hydrated + * by using the special "\0" property name to define their internal value: + * + * // Hydrates an SplObjectStorage where $info1 is attached to $obj1, etc. + * Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // Hydrates an ArrayObject populated with $inputArray + * Hydrator::hydrate($object, ["\0" => [$inputArray]]); + * + * @template T of object + * + * @param T $instance The object to hydrate + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class + * + * @return T + */ + public static function hydrate(object $instance, array $properties = [], array $scopedProperties = []): object + { + if ($properties) { + $class = $instance::class; + $propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class); + + foreach ($properties as $name => &$value) { + [$scope, $name, $readonlyScope] = $propertyScopes[$name] ?? [$class, $name, $class]; + $scopedProperties[$readonlyScope ?? $scope][$name] = &$value; + } + unset($value); + } + + foreach ($scopedProperties as $scope => $properties) { + if ($properties) { + (InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance); + } + } + + return $instance; + } +} diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php new file mode 100644 index 0000000..10200c0 --- /dev/null +++ b/vendor/symfony/var-exporter/Instantiator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; +use Symfony\Component\VarExporter\Internal\Registry; + +/** + * A utility class to create objects without calling their constructor. + * + * @author Nicolas Grekas + */ +final class Instantiator +{ + /** + * Creates an object and sets its properties without calling its constructor nor any other methods. + * + * @see Hydrator::hydrate() for examples + * + * @template T of object + * + * @param class-string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class + * + * @return T + * + * @throws ExceptionInterface When the instance cannot be created + */ + public static function instantiate(string $class, array $properties = [], array $scopedProperties = []): object + { + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + + if (Registry::$cloneable[$class]) { + $instance = clone Registry::$prototypes[$class]; + } elseif (Registry::$instantiableWithoutConstructor[$class]) { + $instance = $reflector->newInstanceWithoutConstructor(); + } elseif (null === Registry::$prototypes[$class]) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) { + $instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}'); + } else { + $instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}'); + } + + return $properties || $scopedProperties ? Hydrator::hydrate($instance, $properties, $scopedProperties) : $instance; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php new file mode 100644 index 0000000..9f4f593 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Exporter.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Exporter +{ + /** + * Prepares an array of values for VarExporter. + * + * For performance this method is public and has no type-hints. + * + * @param array &$values + * @param \SplObjectStorage $objectsPool + * @param array &$refsPool + * @param int &$objectsCount + * @param bool &$valuesAreStatic + * + * @return array + * + * @throws NotInstantiableTypeException When a value cannot be serialized + */ + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic) + { + $refs = $values; + foreach ($values as $k => $value) { + if (\is_resource($value)) { + throw new NotInstantiableTypeException(get_resource_type($value).' resource'); + } + $refs[$k] = $objectsPool; + + if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) { + $values[$k] = &$value; // Break hard references to make $values completely + unset($value); // independent from the original structure + $refs[$k] = $value = $values[$k]; + if ($value instanceof Reference && 0 > $value->id) { + $valuesAreStatic = false; + ++$value->count; + continue; + } + $refsPool[] = [&$refs[$k], $value, &$value]; + $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value); + } + + if (\is_array($value)) { + if ($value) { + $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + } + goto handle_value; + } elseif (!\is_object($value) || $value instanceof \UnitEnum) { + goto handle_value; + } + + $valueIsStatic = false; + if (isset($objectsPool[$value])) { + ++$objectsCount; + $value = new Reference($objectsPool[$value][0]); + goto handle_value; + } + + $class = $value::class; + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + $properties = []; + + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($serializeProperties = $value->__serialize())) { + throw new \TypeError($class.'::__serialize() must return an array'); + } + + if ($reflector->hasMethod('__unserialize')) { + $properties = $serializeProperties; + } else { + foreach ($serializeProperties as $n => $v) { + $c = $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; + $properties[$c][$n] = $v; + } + } + + goto prepare_value; + } + + $sleep = null; + $proto = Registry::$prototypes[$class]; + + if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + // ArrayIterator and ArrayObject need special care because their "flags" + // option changes the behavior of the (array) casting operator. + [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); + + // populates Registry::$prototypes[$class] with a new instance + Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]); + } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) { + // By implementing Serializable, SplObjectStorage breaks + // internal references; let's deal with it on our own. + foreach (clone $value as $v) { + $properties[] = $v; + $properties[] = $value[$v]; + } + $properties = ['SplObjectStorage' => ["\0" => $properties]]; + $arrayValue = (array) $value; + } elseif ($value instanceof \Serializable + || $value instanceof \__PHP_Incomplete_Class + ) { + ++$objectsCount; + $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; + $value = new Reference($id); + goto handle_value; + } else { + if (method_exists($class, '__sleep')) { + if (!\is_array($sleep = $value->__sleep())) { + trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', \E_USER_NOTICE); + $value = null; + goto handle_value; + } + $sleep = array_flip($sleep); + } + + $arrayValue = (array) $value; + } + + $proto = (array) $proto; + + foreach ($arrayValue as $name => $v) { + $i = 0; + $n = (string) $name; + if ('' === $n || "\0" !== $n[0]) { + $c = $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; + } elseif ('*' === $n[1]) { + $n = substr($n, 3); + $c = $reflector->getProperty($n)->class; + if ('Error' === $c) { + $c = 'TypeError'; + } elseif ('Exception' === $c) { + $c = 'ErrorException'; + } + } else { + $i = strpos($n, "\0", 2); + $c = substr($n, 1, $i - 1); + $n = substr($n, 1 + $i); + } + if (null !== $sleep) { + if (!isset($sleep[$name]) && (!isset($sleep[$n]) || ($i && $c !== $class))) { + unset($arrayValue[$name]); + continue; + } + unset($sleep[$name], $sleep[$n]); + } + if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + $properties[$c][$n] = $v; + } + } + if ($sleep) { + foreach ($sleep as $n => $v) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); + } + } + if (method_exists($class, '__unserialize')) { + $properties = $arrayValue; + } + + prepare_value: + $objectsPool[$value] = [$id = \count($objectsPool)]; + $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + ++$objectsCount; + $objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)]; + + $value = new Reference($id); + + handle_value: + if ($isRef) { + unset($value); // Break the hard reference created above + } elseif (!$valueIsStatic) { + $values[$k] = $value; + } + $valuesAreStatic = $valueIsStatic && $valuesAreStatic; + } + + return $values; + } + + public static function export($value, $indent = '') + { + switch (true) { + case \is_int($value) || \is_float($value): return var_export($value, true); + case [] === $value: return '[]'; + case false === $value: return 'false'; + case true === $value: return 'true'; + case null === $value: return 'null'; + case '' === $value: return "''"; + case $value instanceof \UnitEnum: return '\\'.ltrim(var_export($value, true), '\\'); + } + + if ($value instanceof Reference) { + if (0 <= $value->id) { + return '$o['.$value->id.']'; + } + if (!$value->count) { + return self::export($value->value, $indent); + } + $value = -$value->id; + + return '&$r['.$value.']'; + } + $subIndent = $indent.' '; + + if (\is_string($value)) { + $code = sprintf("'%s'", addcslashes($value, "'\\")); + + $code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) { + $m[1] = sprintf('\'."%s".\'', str_replace( + ["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'], + ['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'], + $m[1] + )); + + if ("'" === $m[2]) { + return substr($m[1], 0, -2); + } + + if (str_ends_with($m[1], 'n".\'')) { + return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); + } + + return $m[1].$m[2]; + }, $code, -1, $count); + + if ($count && str_starts_with($code, "''.")) { + $code = substr($code, 3); + } + + return $code; + } + + if (\is_array($value)) { + $j = -1; + $code = ''; + foreach ($value as $k => $v) { + $code .= $subIndent; + if (!\is_int($k) || 1 !== $k - $j) { + $code .= self::export($k, $subIndent).' => '; + } + if (\is_int($k) && $k > $j) { + $j = $k; + } + $code .= self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Values) { + $code = $subIndent."\$r = [],\n"; + foreach ($value->values as $k => $v) { + $code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Registry) { + return self::exportRegistry($value, $indent, $subIndent); + } + + if ($value instanceof Hydrator) { + return self::exportHydrator($value, $indent, $subIndent); + } + + throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', get_debug_type($value))); + } + + private static function exportRegistry(Registry $value, string $indent, string $subIndent): string + { + $code = ''; + $serializables = []; + $seen = []; + $prototypesAccess = 0; + $factoriesAccess = 0; + $r = '\\'.Registry::class; + $j = -1; + + foreach ($value->classes as $k => $class) { + if (':' === ($class[1] ?? null)) { + $serializables[$k] = $class; + continue; + } + if (!Registry::$instantiableWithoutConstructor[$class]) { + if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) { + $serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}'; + } else { + $serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}'; + } + if (is_subclass_of($class, 'Throwable')) { + $eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0"; + $serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4); + } + continue; + } + $code .= $subIndent.(1 !== $k - $j ? $k.' => ' : ''); + $j = $k; + $eol = ",\n"; + $c = '['.self::export($class).']'; + + if ($seen[$class] ?? false) { + if (Registry::$cloneable[$class]) { + ++$prototypesAccess; + $code .= 'clone $p'.$c; + } else { + ++$factoriesAccess; + $code .= '$f'.$c.'()'; + } + } else { + $seen[$class] = true; + if (Registry::$cloneable[$class]) { + $code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p'; + } else { + $code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f'; + $eol = '()'.$eol; + } + $code .= '('.substr($c, 1, -1).'))'; + } + $code .= $eol; + } + + if (1 === $prototypesAccess) { + $code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code); + } + if (1 === $factoriesAccess) { + $code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code); + } + if ('' !== $code) { + $code = "\n".$code.$indent; + } + + if ($serializables) { + $code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')'; + } else { + $code = '['.$code.']'; + } + + return '$o = '.$code; + } + + private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string + { + $code = ''; + foreach ($value->properties as $class => $properties) { + $code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n"; + } + + $code = [ + self::export($value->registry, $subIndent), + self::export($value->values, $subIndent), + '' !== $code ? "[\n".$code.$subIndent.']' : '[]', + self::export($value->value, $subIndent), + self::export($value->wakeups, $subIndent), + ]; + + return '\\'.$value::class."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + } + + /** + * @param \ArrayIterator|\ArrayObject $value + * @param \ArrayIterator|\ArrayObject $proto + */ + private static function getArrayObjectProperties($value, $proto): array + { + $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; + $reflector = Registry::$reflectors[$reflector] ??= Registry::getClassReflector($reflector); + + $properties = [ + $arrayValue = (array) $value, + $reflector->getMethod('getFlags')->invoke($value), + $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', + ]; + + $reflector = $reflector->getMethod('setFlags'); + $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); + + if ($properties[1] & \ArrayObject::STD_PROP_LIST) { + $reflector->invoke($value, 0); + $properties[0] = (array) $value; + } else { + $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); + $arrayValue = (array) $value; + } + $reflector->invoke($value, $properties[1]); + + if ([[], 0, 'ArrayIterator'] === $properties) { + $properties = []; + } else { + if ('ArrayIterator' === $properties[2]) { + unset($properties[2]); + } + $properties = [$reflector->class => ["\0" => $properties]]; + } + + return [$arrayValue, $properties]; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php new file mode 100644 index 0000000..65fdcd1 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Hydrator.php @@ -0,0 +1,299 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Hydrator +{ + public static array $hydrators = []; + public static array $simpleHydrators = []; + public static array $propertyScopes = []; + + public function __construct( + public readonly Registry $registry, + public readonly ?Values $values, + public readonly array $properties, + public readonly mixed $value, + public readonly array $wakeups, + ) { + } + + public static function hydrate($objects, $values, $properties, $value, $wakeups) + { + foreach ($properties as $class => $vars) { + (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects); + } + foreach ($wakeups as $k => $v) { + if (\is_array($v)) { + $objects[-$k]->__unserialize($v); + } else { + $objects[$v]->__wakeup(); + } + } + + return $value; + } + + public static function getHydrator($class) + { + $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + + switch ($class) { + case 'stdClass': + return $baseHydrator; + + case 'ErrorException': + return $baseHydrator->bindTo(null, new class() extends \ErrorException { + }); + + case 'TypeError': + return $baseHydrator->bindTo(null, new class() extends \Error { + }); + + case 'SplObjectStorage': + return static function ($properties, $objects) { + foreach ($properties as $name => $values) { + if ("\0" === $name) { + foreach ($values as $i => $v) { + for ($j = 0; $j < \count($v); ++$j) { + $objects[$i]->attach($v[$j], $v[++$j]); + } + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = $classReflector->getConstructor()->invokeArgs(...); + + return static function ($properties, $objects) use ($constructor) { + foreach ($properties as $name => $values) { + if ("\0" !== $name) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + } + foreach ($properties["\0"] ?? [] as $i => $v) { + $constructor($objects[$i], $v); + } + }; + } + + if (!$classReflector->isInternal()) { + return $baseHydrator->bindTo(null, $class); + } + + if ($classReflector->name !== $class) { + return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + } + } + + if (!$propertySetters) { + return $baseHydrator; + } + + return static function ($properties, $objects) use ($propertySetters) { + foreach ($properties as $name => $values) { + if ($setValue = $propertySetters[$name] ?? null) { + foreach ($values as $i => $v) { + $setValue($objects[$i], $v); + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + public static function getSimpleHydrator($class) + { + $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) { + $readonly = (array) $this; + + foreach ($properties as $name => &$value) { + $object->$name = $value; + + if (!($readonly[$name] ?? false)) { + $object->$name = &$value; + } + } + })->bindTo(new \stdClass()); + + switch ($class) { + case 'stdClass': + return $baseHydrator; + + case 'ErrorException': + return $baseHydrator->bindTo(new \stdClass(), new class() extends \ErrorException { + }); + + case 'TypeError': + return $baseHydrator->bindTo(new \stdClass(), new class() extends \Error { + }); + + case 'SplObjectStorage': + return static function ($properties, $object) { + foreach ($properties as $name => &$value) { + if ("\0" !== $name) { + $object->$name = $value; + $object->$name = &$value; + continue; + } + for ($i = 0; $i < \count($value); ++$i) { + $object->attach($value[$i], $value[++$i]); + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = $classReflector->getConstructor()->invokeArgs(...); + + return static function ($properties, $object) use ($constructor) { + foreach ($properties as $name => &$value) { + if ("\0" === $name) { + $constructor($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + if (!$classReflector->isInternal()) { + $readonly = new \stdClass(); + foreach ($classReflector->getProperties(\ReflectionProperty::IS_READONLY) as $propertyReflector) { + if ($class === $propertyReflector->class) { + $readonly->{$propertyReflector->name} = true; + } + } + + return $baseHydrator->bindTo($readonly, $class); + } + + if ($classReflector->name !== $class) { + return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + } + } + + if (!$propertySetters) { + return $baseHydrator; + } + + return static function ($properties, $object) use ($propertySetters) { + foreach ($properties as $name => &$value) { + if ($setValue = $propertySetters[$name] ?? null) { + $setValue($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + public static function getPropertyScopes($class): array + { + $propertyScopes = []; + $r = new \ReflectionClass($class); + + foreach ($r->getProperties() as $property) { + $flags = $property->getModifiers(); + + if (\ReflectionProperty::IS_STATIC & $flags) { + continue; + } + $name = $property->name; + + if (\ReflectionProperty::IS_PRIVATE & $flags) { + $readonlyScope = null; + if ($flags & \ReflectionProperty::IS_READONLY) { + $readonlyScope = $class; + } + $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, $readonlyScope, $property]; + + continue; + } + $readonlyScope = null; + if ($flags & \ReflectionProperty::IS_READONLY) { + $readonlyScope = $property->class; + } + $propertyScopes[$name] = [$class, $name, $readonlyScope, $property]; + + if (\ReflectionProperty::IS_PROTECTED & $flags) { + $propertyScopes["\0*\0$name"] = $propertyScopes[$name]; + } + } + + while ($r = $r->getParentClass()) { + $class = $r->name; + + foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) { + if (!$property->isStatic()) { + $name = $property->name; + $readonlyScope = $property->isReadOnly() ? $class : null; + $propertyScopes["\0$class\0$name"] = [$class, $name, $readonlyScope, $property]; + $propertyScopes[$name] ??= [$class, $name, $readonlyScope, $property]; + } + } + } + + return $propertyScopes; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php new file mode 100644 index 0000000..f6450ce --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * Stores the state of lazy objects and caches related reflection information. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectRegistry +{ + /** + * @var array + */ + public static array $classReflectors = []; + + /** + * @var array> + */ + public static array $defaultProperties = []; + + /** + * @var array> + */ + public static array $classResetters = []; + + /** + * @var array + */ + public static array $classAccessors = []; + + /** + * @var array + */ + public static array $parentMethods = []; + + public static ?\Closure $noInitializerState = null; + + public static function getClassResetters($class) + { + $classProperties = []; + + if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) { + $propertyScopes = []; + } else { + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + } + + foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + + if ($k === $key && "\0$class\0lazyObjectState" !== $k) { + $classProperties[$readonlyScope ?? $scope][$name] = $key; + } + } + + $resetters = []; + foreach ($classProperties as $scope => $properties) { + $resetters[] = \Closure::bind(static function ($instance, $skippedProperties) use ($properties) { + foreach ($properties as $name => $key) { + if (!\array_key_exists($key, $skippedProperties)) { + unset($instance->$name); + } + } + }, null, $scope); + } + + return $resetters; + } + + public static function getClassAccessors($class) + { + return \Closure::bind(static fn () => [ + 'get' => static function &($instance, $name, $readonly) { + if (!$readonly) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; + }, + 'set' => static function ($instance, $name, $value) { + $instance->$name = $value; + }, + 'isset' => static fn ($instance, $name) => isset($instance->$name), + 'unset' => static function ($instance, $name) { + unset($instance->$name); + }, + ], null, \Closure::class === $class ? null : $class)(); + } + + public static function getParentMethods($class) + { + $parent = get_parent_class($class); + $methods = []; + + foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) { + if (!$parent || !method_exists($parent, '__'.$method)) { + $methods[$method] = false; + } else { + $m = new \ReflectionMethod($parent, '__'.$method); + $methods[$method] = !$m->isAbstract() && !$m->isPrivate(); + } + } + + $methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0; + + return $methods; + } + + public static function getScope($propertyScopes, $class, $property, $readonlyScope = null) + { + if (null === $readonlyScope && !isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) { + return null; + } + $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]; + + if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { + $scope = $frame['object']->class; + } + if (null === $readonlyScope && '*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { + return null; + } + + return $scope; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectState.php b/vendor/symfony/var-exporter/Internal/LazyObjectState.php new file mode 100644 index 0000000..5fc398e --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectState.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Hydrator as PublicHydrator; + +/** + * Keeps the state of lazy objects. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectState +{ + public const STATUS_UNINITIALIZED_FULL = 1; + public const STATUS_UNINITIALIZED_PARTIAL = 2; + public const STATUS_INITIALIZED_FULL = 3; + public const STATUS_INITIALIZED_PARTIAL = 4; + + /** + * @var self::STATUS_* + */ + public int $status = self::STATUS_UNINITIALIZED_FULL; + + public object $realInstance; + + /** + * @param array $skippedProperties + */ + public function __construct( + public readonly \Closure $initializer, + public readonly array $skippedProperties = [], + ) { + } + + public function initialize($instance, $propertyName, $propertyScope) + { + if (self::STATUS_UNINITIALIZED_FULL !== $this->status) { + return $this->status; + } + + $this->status = self::STATUS_INITIALIZED_PARTIAL; + + try { + if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) { + PublicHydrator::hydrate($instance, $defaultProperties); + } + + ($this->initializer)($instance); + } catch (\Throwable $e) { + $this->status = self::STATUS_UNINITIALIZED_FULL; + $this->reset($instance); + + throw $e; + } + + return $this->status = self::STATUS_INITIALIZED_FULL; + } + + public function reset($instance): void + { + $class = $instance::class; + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + $skippedProperties = $this->skippedProperties; + $properties = (array) $instance; + + foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + + if ($k === $key && (null !== $readonlyScope || !\array_key_exists($k, $properties))) { + $skippedProperties[$k] = true; + } + } + + foreach (LazyObjectRegistry::$classResetters[$class] as $reset) { + $reset($instance, $skippedProperties); + } + + foreach ((array) $instance as $name => $value) { + if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties)) { + unset($instance->$name); + } + } + + $this->status = self::STATUS_UNINITIALIZED_FULL; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php new file mode 100644 index 0000000..4a6f232 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\Serializer\Attribute\Ignore; + +if (\PHP_VERSION_ID >= 80300) { + /** + * @internal + */ + trait LazyObjectTrait + { + #[Ignore] + private readonly LazyObjectState $lazyObjectState; + } +} else { + /** + * @internal + */ + trait LazyObjectTrait + { + #[Ignore] + private LazyObjectState $lazyObjectState; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Reference.php b/vendor/symfony/var-exporter/Internal/Reference.php new file mode 100644 index 0000000..2c7bd7b --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Reference.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Reference +{ + public int $count = 0; + + public function __construct( + public readonly int $id, + public readonly mixed $value = null, + ) { + } +} diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php new file mode 100644 index 0000000..9c41684 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Registry.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Registry +{ + public static array $reflectors = []; + public static array $prototypes = []; + public static array $factories = []; + public static array $cloneable = []; + public static array $instantiableWithoutConstructor = []; + + public function __construct( + public readonly array $classes, + ) { + } + + public static function unserialize($objects, $serializables) + { + $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector'); + + try { + foreach ($serializables as $k => $v) { + $objects[$k] = unserialize($v); + } + } finally { + ini_set('unserialize_callback_func', $unserializeCallback); + } + + return $objects; + } + + public static function p($class) + { + self::getClassReflector($class, true, true); + + return self::$prototypes[$class]; + } + + public static function f($class) + { + $reflector = self::$reflectors[$class] ??= self::getClassReflector($class, true, false); + + return self::$factories[$class] = [$reflector, 'newInstanceWithoutConstructor'](...); + } + + public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) + { + if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $reflector = new \ReflectionClass($class); + + if ($instantiableWithoutConstructor) { + $proto = $reflector->newInstanceWithoutConstructor(); + } elseif (!$isClass || $reflector->isAbstract()) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->name !== $class) { + $reflector = self::$reflectors[$name = $reflector->name] ??= self::getClassReflector($name, false, $cloneable); + self::$cloneable[$class] = self::$cloneable[$name]; + self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; + self::$prototypes[$class] = self::$prototypes[$name]; + + return $reflector; + } else { + try { + $proto = $reflector->newInstanceWithoutConstructor(); + $instantiableWithoutConstructor = true; + } catch (\ReflectionException) { + $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; + if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { + $proto = null; + } else { + try { + $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}'); + } catch (\Exception $e) { + if (__FILE__ !== $e->getFile()) { + throw $e; + } + throw new NotInstantiableTypeException($class, $e); + } + if (false === $proto) { + throw new NotInstantiableTypeException($class); + } + } + } + if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && !method_exists($class, '__serialize')) { + try { + serialize($proto); + } catch (\Exception $e) { + throw new NotInstantiableTypeException($class, $e); + } + } + } + + if (null === $cloneable) { + if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && !method_exists($class, '__unserialize'))) { + throw new NotInstantiableTypeException($class); + } + + $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone'); + } + + self::$cloneable[$class] = $cloneable; + self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor; + self::$prototypes[$class] = $proto; + + if ($proto instanceof \Throwable) { + static $setTrace; + + if (null === $setTrace) { + $setTrace = [ + new \ReflectionProperty(\Error::class, 'trace'), + new \ReflectionProperty(\Exception::class, 'trace'), + ]; + $setTrace[0] = $setTrace[0]->setValue(...); + $setTrace[1] = $setTrace[1]->setValue(...); + } + + $setTrace[$proto instanceof \Exception]($proto, []); + } + + return $reflector; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Values.php b/vendor/symfony/var-exporter/Internal/Values.php new file mode 100644 index 0000000..4f20a82 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Values.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Values +{ + public function __construct( + public readonly array $values, + ) { + } +} diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/var-exporter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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. diff --git a/vendor/symfony/var-exporter/LazyGhostTrait.php b/vendor/symfony/var-exporter/LazyGhostTrait.php new file mode 100644 index 0000000..fa82ced --- /dev/null +++ b/vendor/symfony/var-exporter/LazyGhostTrait.php @@ -0,0 +1,352 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; +use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectTrait; + +trait LazyGhostTrait +{ + use LazyObjectTrait; + + /** + * Creates a lazy-loading ghost instance. + * + * Skipped properties should be indexed by their array-cast identifier, see + * https://php.net/manual/language.types.array#language.types.array.casting + * + * @param (\Closure(static):void $initializer The closure should initialize the object it receives as argument + * @param array|null $skippedProperties An array indexed by the properties to skip, a.k.a. the ones + * that the initializer doesn't initialize, if any + * @param static|null $instance + */ + public static function createLazyGhost(\Closure $initializer, ?array $skippedProperties = null, ?object $instance = null): static + { + if (self::class !== $class = $instance ? $instance::class : static::class) { + $skippedProperties["\0".self::class."\0lazyObjectState"] = true; + } + + if (!isset(Registry::$defaultProperties[$class])) { + Registry::$classReflectors[$class] ??= new \ReflectionClass($class); + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + Registry::$defaultProperties[$class] ??= (array) $instance; + Registry::$classResetters[$class] ??= Registry::getClassResetters($class); + + if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { + Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; + } + } else { + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + } + + $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []); + + foreach (Registry::$classResetters[$class] as $reset) { + $reset($instance, $skippedProperties); + } + + return $instance; + } + + /** + * Returns whether the object is initialized. + * + * @param $partial Whether partially initialized objects should be considered as initialized + */ + #[Ignore] + public function isLazyObjectInitialized(bool $partial = false): bool + { + if (!$state = $this->lazyObjectState ?? null) { + return true; + } + + return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; + } + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): static + { + if (!$state = $this->lazyObjectState ?? null) { + return $this; + } + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, '', null); + } + + return $this; + } + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool + { + if (!$state = $this->lazyObjectState ?? null) { + return false; + } + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) { + $state->reset($this); + } + + return true; + } + + public function &__get($name): mixed + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + $state = $this->lazyObjectState ?? null; + + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { + if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) { + // Work around php/php-src#12695 + $property = null === $scope ? $name : "\0$scope\0$name"; + $property = $propertyScopes[$property][3] + ?? Hydrator::$propertyScopes[$this::class][$property][3] = new \ReflectionProperty($scope ?? $class, $name); + } else { + $property = null; + } + + if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope)) { + goto get_in_scope; + } + } + } + + if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) { + if (2 === $parent) { + return parent::__get($name); + } + $value = parent::__get($name); + + return $value; + } + + if (null === $class) { + $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; + trigger_error(sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); + } + + get_in_scope: + + try { + if (null === $scope) { + if (null === $readonlyScope) { + return $this->$name; + } + $value = $this->$name; + + return $value; + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['get']($this, $name, null !== $readonlyScope); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $this->$name = []; + + return $this->$name; + } + + $accessor['set']($this, $name, []); + + return $accessor['get']($this, $name, null !== $readonlyScope); + } catch (\Error) { + if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) { + throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious()); + } + + throw $e; + } + } + } + + public function __set($name, $value): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + $state = $this->lazyObjectState ?? null; + + if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + ) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, $name, $readonlyScope ?? $scope); + } + goto set_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { + parent::__set($name, $value); + + return; + } + + set_in_scope: + + if (null === $scope) { + $this->$name = $value; + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['set']($this, $name, $value); + } + } + + public function __isset($name): bool + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + $state = $this->lazyObjectState ?? null; + + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope) + ) { + goto isset_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { + return parent::__isset($name); + } + + isset_in_scope: + + if (null === $scope) { + return isset($this->$name); + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['isset']($this, $name); + } + + public function __unset($name): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + $state = $this->lazyObjectState ?? null; + + if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + ) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, $name, $readonlyScope ?? $scope); + } + goto unset_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { + parent::__unset($name); + + return; + } + + unset_in_scope: + + if (null === $scope) { + unset($this->$name); + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['unset']($this, $name); + } + } + + public function __clone(): void + { + if ($state = $this->lazyObjectState ?? null) { + $this->lazyObjectState = clone $state; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { + parent::__clone(); + } + } + + public function __serialize(): array + { + $class = self::class; + + if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { + $properties = parent::__serialize(); + } else { + $this->initializeLazyObject(); + $properties = (array) $this; + } + unset($properties["\0$class\0lazyObjectState"]); + + if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { + return $properties; + } + + $scope = get_parent_class($class); + $data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data; + } + + public function __destruct() + { + $state = $this->lazyObjectState ?? null; + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state?->status) { + return; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { + parent::__destruct(); + } + } + + #[Ignore] + private function setLazyObjectAsInitialized(bool $initialized): void + { + if ($state = $this->lazyObjectState ?? null) { + $state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL; + } + } +} diff --git a/vendor/symfony/var-exporter/LazyObjectInterface.php b/vendor/symfony/var-exporter/LazyObjectInterface.php new file mode 100644 index 0000000..3670884 --- /dev/null +++ b/vendor/symfony/var-exporter/LazyObjectInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +interface LazyObjectInterface +{ + /** + * Returns whether the object is initialized. + * + * @param $partial Whether partially initialized objects should be considered as initialized + */ + public function isLazyObjectInitialized(bool $partial = false): bool; + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): object; + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool; +} diff --git a/vendor/symfony/var-exporter/LazyProxyTrait.php b/vendor/symfony/var-exporter/LazyProxyTrait.php new file mode 100644 index 0000000..17ba1db --- /dev/null +++ b/vendor/symfony/var-exporter/LazyProxyTrait.php @@ -0,0 +1,359 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\VarExporter\Hydrator as PublicHydrator; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; +use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectTrait; + +trait LazyProxyTrait +{ + use LazyObjectTrait; + + /** + * Creates a lazy-loading virtual proxy. + * + * @param \Closure():object $initializer Returns the proxied object + * @param static|null $instance + */ + public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static + { + if (self::class !== $class = $instance ? $instance::class : static::class) { + $skippedProperties = ["\0".self::class."\0lazyObjectState" => true]; + } + + if (!isset(Registry::$defaultProperties[$class])) { + Registry::$classReflectors[$class] ??= new \ReflectionClass($class); + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + Registry::$defaultProperties[$class] ??= (array) $instance; + Registry::$classResetters[$class] ??= Registry::getClassResetters($class); + + if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { + Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; + } + } else { + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + } + + $instance->lazyObjectState = new LazyObjectState($initializer); + + foreach (Registry::$classResetters[$class] as $reset) { + $reset($instance, $skippedProperties ??= []); + } + + return $instance; + } + + /** + * Returns whether the object is initialized. + * + * @param $partial Whether partially initialized objects should be considered as initialized + */ + #[Ignore] + public function isLazyObjectInitialized(bool $partial = false): bool + { + return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer; + } + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): parent + { + if ($state = $this->lazyObjectState ?? null) { + return $state->realInstance ??= ($state->initializer)(); + } + + return $this; + } + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool + { + if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) { + return false; + } + + unset($this->lazyObjectState->realInstance); + + return true; + } + + public function &__get($name): mixed + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + + if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + $parent = 2; + goto get_in_scope; + } + } + $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']; + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } else { + if (2 === $parent) { + return parent::__get($name); + } + $value = parent::__get($name); + + return $value; + } + + if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) { + $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; + trigger_error(sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); + } + + get_in_scope: + + try { + if (null === $scope) { + if (null === $readonlyScope && 1 !== $parent) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $instance->$name = []; + + return $instance->$name; + } + + $accessor['set']($instance, $name, []); + + return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); + } catch (\Error) { + throw $e; + } + } + } + + public function __set($name, $value): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + + if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto set_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { + parent::__set($name, $value); + + return; + } + + set_in_scope: + + if (null === $scope) { + $instance->$name = $value; + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['set']($instance, $name, $value); + } + } + + public function __isset($name): bool + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + + if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto isset_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { + return parent::__isset($name); + } + + isset_in_scope: + + if (null === $scope) { + return isset($instance->$name); + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['isset']($instance, $name); + } + + public function __unset($name): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + + if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto unset_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { + parent::__unset($name); + + return; + } + + unset_in_scope: + + if (null === $scope) { + unset($instance->$name); + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['unset']($instance, $name); + } + } + + public function __clone(): void + { + if (!isset($this->lazyObjectState)) { + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { + parent::__clone(); + } + + return; + } + + $this->lazyObjectState = clone $this->lazyObjectState; + + if (isset($this->lazyObjectState->realInstance)) { + $this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance; + } + } + + public function __serialize(): array + { + $class = self::class; + $state = $this->lazyObjectState ?? null; + + if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { + $properties = parent::__serialize(); + } else { + $properties = (array) $this; + + if ($state) { + unset($properties["\0$class\0lazyObjectState"]); + $properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)(); + } + } + + if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { + return $properties; + } + + $scope = get_parent_class($class); + $data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data; + } + + public function __unserialize(array $data): void + { + $class = self::class; + + if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) { + unset($data["\0$class\0lazyObjectReal"]); + + foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { + $reset($this, $data); + } + + if ($data) { + PublicHydrator::hydrate($this, $data); + } + $this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.')); + $this->lazyObjectState->realInstance = $instance; + } elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) { + parent::__unserialize($data); + } else { + PublicHydrator::hydrate($this, $data); + + if (Registry::$parentMethods[$class]['wakeup']) { + parent::__wakeup(); + } + } + } + + public function __destruct() + { + if (isset($this->lazyObjectState)) { + return; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { + parent::__destruct(); + } + } +} diff --git a/vendor/symfony/var-exporter/ProxyHelper.php b/vendor/symfony/var-exporter/ProxyHelper.php new file mode 100644 index 0000000..4cf0f65 --- /dev/null +++ b/vendor/symfony/var-exporter/ProxyHelper.php @@ -0,0 +1,413 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\LogicException; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry; + +/** + * @author Nicolas Grekas + */ +final class ProxyHelper +{ + /** + * Helps generate lazy-loading ghost objects. + * + * @throws LogicException When the class is incompatible with ghost objects + */ + public static function generateLazyGhost(\ReflectionClass $class): string + { + if (\PHP_VERSION_ID < 80300 && $class->isReadOnly()) { + throw new LogicException(sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name)); + } + if ($class->isFinal()) { + throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name)); + } + if ($class->isInterface() || $class->isAbstract()) { + throw new LogicException(sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name)); + } + if (\stdClass::class !== $class->name && $class->isInternal()) { + throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name)); + } + if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) { + throw new LogicException(sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name)); + } + + static $traitMethods; + $traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods(); + + foreach ($traitMethods as $method) { + if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) { + throw new LogicException(sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name)); + } + } + + $parent = $class; + while ($parent = $parent->getParentClass()) { + if (\stdClass::class !== $parent->name && $parent->isInternal()) { + throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name)); + } + } + $propertyScopes = self::exportPropertyScopes($class->name); + + return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface + { + use \Symfony\Component\VarExporter\LazyGhostTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes}; + } + + // Help opcache.preload discover always-needed symbols + class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); + class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); + class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + + EOPHP; + } + + /** + * Helps generate lazy-loading virtual proxies. + * + * @param \ReflectionClass[] $interfaces + * + * @throws LogicException When the class is incompatible with virtual proxies + */ + public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []): string + { + if (!class_exists($class?->name ?? \stdClass::class, false)) { + throw new LogicException(sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name)); + } + if ($class?->isFinal()) { + throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name)); + } + if (\PHP_VERSION_ID < 80300 && $class?->isReadOnly()) { + throw new LogicException(sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name)); + } + + $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []]; + foreach ($interfaces as $interface) { + if (!$interface->isInterface()) { + throw new LogicException(sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name)); + } + $methodReflectors[] = $interface->getMethods(); + } + $methodReflectors = array_merge(...$methodReflectors); + + $extendsInternalClass = false; + if ($parent = $class) { + do { + $extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal(); + } while (!$extendsInternalClass && $parent = $parent->getParentClass()); + } + $methodsHaveToBeProxied = $extendsInternalClass; + $methods = []; + + foreach ($methodReflectors as $method) { + if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) { + continue; + } + $methodsHaveToBeProxied = true; + $trait = new \ReflectionMethod(LazyProxyTrait::class, '__get'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): mixed', '): '.$type, $body[0]); + $methods['__get'] = strtr(implode('', $body).' }', [ + 'Hydrator' => '\\'.Hydrator::class, + 'Registry' => '\\'.LazyObjectRegistry::class, + ]); + break; + } + + foreach ($methodReflectors as $method) { + if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) { + continue; + } + if ($method->isFinal()) { + if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) { + throw new LogicException(sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name)); + } + continue; + } + if (method_exists(LazyProxyTrait::class, $method->name) || ($method->isProtected() && !$method->isAbstract())) { + continue; + } + + $signature = self::exportSignature($method, true, $args); + $parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})"; + + if ($method->isStatic()) { + $body = " $parentCall;"; + } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) { + $body = <<lazyObjectState)) { + (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + } else { + {$parentCall}; + } + EOPHP; + } else { + if (!$methodsHaveToBeProxied && !$method->isAbstract()) { + // Skip proxying methods that might return $this + foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) { + if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) { + continue 2; + } + foreach ([$class, ...$interfaces] as $r) { + if ($r && is_a($r->name, $type, true)) { + continue 3; + } + } + } + } + + $body = <<lazyObjectState)) { + return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + } + + return {$parentCall}; + EOPHP; + } + $methods[$lcName] = " {$signature}\n {\n{$body}\n }"; + } + + $types = $interfaces = array_unique(array_column($interfaces, 'name')); + $interfaces[] = LazyObjectInterface::class; + $interfaces = implode(', \\', $interfaces); + $parent = $class ? ' extends \\'.$class->name : ''; + array_unshift($types, $class ? 'parent' : ''); + $type = ltrim(implode('&\\', $types), '&'); + + if (!$class) { + $trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): parent', '): '.$type, $body[0]); + $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods; + } + $body = $methods ? "\n".implode("\n\n", $methods)."\n" : ''; + $propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]'; + + if ( + $class?->hasMethod('__unserialize') + && !$class->getMethod('__unserialize')->getParameters()[0]->getType() + ) { + // fix contravariance type problem when $class declares a `__unserialize()` method without typehint. + $lazyProxyTraitStatement = <<__doUnserialize(\$data); + } + + EOPHP; + } else { + $lazyProxyTraitStatement = <<class : $function->getNamespaceName().'\\'; + $namespace = substr($namespace, 0, strrpos($namespace, '\\') ?: 0); + foreach ($function->getParameters() as $param) { + $parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '') + .($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '') + .($param->isPassedByReference() ? '&' : '') + .($param->isVariadic() ? '...' : '').'$'.$param->name + .($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : ''); + if ($param->isPassedByReference()) { + $byRefIndex = 1 + $param->getPosition(); + } + $args .= ($param->isVariadic() ? '...$' : '$').$param->name.', '; + } + + if (!$param || !$byRefIndex) { + $args = '...\func_get_args()'; + } elseif ($param->isVariadic()) { + $args = substr($args, 0, -2); + } else { + $args = explode(', ', $args, 1 + $byRefIndex); + $args[$byRefIndex] = sprintf('...\array_slice(\func_get_args(), %d)', $byRefIndex); + $args = implode(', ', $args); + } + + $signature = 'function '.($function->returnsReference() ? '&' : '') + .($function->isClosure() ? '' : $function->name).'('.implode(', ', $parameters).')'; + + if ($function instanceof \ReflectionMethod) { + $signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private ')) + .($function->isStatic() ? 'static ' : '').$signature; + } + if ($function->hasReturnType()) { + $signature .= ': '.self::exportType($function); + } + + static $getPrototype; + $getPrototype ??= (new \ReflectionMethod(\ReflectionMethod::class, 'getPrototype'))->invoke(...); + + while ($function) { + if ($function->hasTentativeReturnType()) { + return '#[\ReturnTypeWillChange] '.$signature; + } + + try { + $function = $function instanceof \ReflectionMethod && $function->isAbstract() ? false : $getPrototype($function); + } catch (\ReflectionException) { + break; + } + } + + return $signature; + } + + public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string + { + if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) { + return null; + } + $class = null; + $types = []; + if ($type instanceof \ReflectionUnionType) { + $reflectionTypes = $type->getTypes(); + $glue = '|'; + } elseif ($type instanceof \ReflectionIntersectionType) { + $reflectionTypes = $type->getTypes(); + $glue = '&'; + } else { + $reflectionTypes = [$type]; + $glue = null; + } + + foreach ($reflectionTypes as $type) { + if ($type instanceof \ReflectionIntersectionType) { + if ('' !== $name = '('.self::exportType($owner, $noBuiltin, $type).')') { + $types[] = $name; + } + continue; + } + $name = $type->getName(); + + if ($noBuiltin && $type->isBuiltin()) { + continue; + } + if (\in_array($name, ['parent', 'self'], true) && $class ??= $owner->getDeclaringClass()) { + $name = 'parent' === $name ? ($class->getParentClass() ?: null)?->name ?? 'parent' : $class->name; + } + + $types[] = ($noBuiltin || $type->isBuiltin() || 'static' === $name ? '' : '\\').$name; + } + + if (!$types) { + return ''; + } + if (null === $glue) { + return (!$noBuiltin && $type->allowsNull() && !\in_array($name, ['mixed', 'null'], true) ? '?' : '').$types[0]; + } + sort($types); + + return implode($glue, $types); + } + + private static function exportPropertyScopes(string $parent): string + { + $propertyScopes = Hydrator::$propertyScopes[$parent] ??= Hydrator::getPropertyScopes($parent); + uksort($propertyScopes, 'strnatcmp'); + foreach ($propertyScopes as $k => $v) { + unset($propertyScopes[$k][3]); + } + $propertyScopes = VarExporter::export($propertyScopes); + $propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes); + $propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes); + $propertyScopes = str_replace("\n", "\n ", $propertyScopes); + + return $propertyScopes; + } + + private static function exportDefault(\ReflectionParameter $param, $namespace): string + { + $default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2)); + + if (\in_array($default, ['', 'NULL'], true)) { + return 'null'; + } + if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) { + return VarExporter::export($param->getDefaultValue()); + } + + $regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/"; + $parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + + $regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(\(?)(?!: )/'; + $callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass()) + ? fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null' => $m[2], + 'NULL' => 'null', + 'self' => '\\'.$class->name, + 'namespace\\parent', + 'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3] + : fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null', 'self', 'parent' => $m[2], + 'NULL' => 'null', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3]; + + return implode('', array_map(fn ($part) => match ($part[0]) { + '"' => $part, // for internal classes only + "'" => false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part, + default => preg_replace_callback($regexp, $callback, $part), + }, $parts)); + } + + private static function exportSymbol(string $symbol, bool $mightBeRootConst, string $namespace): string + { + if (!$mightBeRootConst + || false === ($ns = strrpos($symbol, '\\')) + || substr($symbol, 0, $ns) !== $namespace + || \defined($symbol) + || !\defined(substr($symbol, $ns + 1)) + ) { + return '\\'.$symbol; + } + + return '\\'.substr($symbol, $ns + 1); + } +} diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md new file mode 100644 index 0000000..7195270 --- /dev/null +++ b/vendor/symfony/var-exporter/README.md @@ -0,0 +1,137 @@ +VarExporter Component +===================== + +The VarExporter component provides various tools to deal with the internal state +of objects: + +- `VarExporter::export()` allows exporting any serializable PHP data structure to + plain PHP code. While doing so, it preserves all the semantics associated with + the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, + `__serialize`, `__unserialize`); +- `Instantiator::instantiate()` creates an object and sets its properties without + calling its constructor nor any other methods; +- `Hydrator::hydrate()` can set the properties of an existing object; +- `Lazy*Trait` can make a class behave as a lazy-loading ghost or virtual proxy. + +VarExporter::export() +--------------------- + +The reason to use `VarExporter::export()` *vs* `serialize()` or +[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to +OPcache, the resulting code is significantly faster and more memory efficient +than using `unserialize()` or `igbinary_unserialize()`. + +Unlike `var_export()`, this works on any serializable PHP value. + +It also provides a few improvements over `var_export()`/`serialize()`: + + * the output is PSR-2 compatible; + * the output can be re-indented without messing up with `\r` or `\n` in the data; + * missing classes throw a `ClassNotFoundException` instead of being unserialized + to `PHP_Incomplete_Class` objects; + * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` + instances are preserved; + * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes + throw an exception when being serialized (their unserialized version is broken + anyway, see https://bugs.php.net/76737). + +Instantiator and Hydrator +------------------------- + +`Instantiator::instantiate($class)` creates an object of the given class without +calling its constructor nor any other methods. + +`Hydrator::hydrate()` sets the properties of an existing object, including +private and protected ones. For example: + +```php +// Sets the public or protected $object->propertyName property +Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + +// Sets a private property defined on its parent Bar class: +Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + +// Alternative way to set the private $object->privateBarProperty property +Hydrator::hydrate($object, [], [ + Bar::class => ['privateBarProperty' => $propertyValue], +]); +``` + +`Lazy*Trait` +------------ + +The component provides two lazy-loading patterns: ghost objects and virtual +proxies (see https://martinfowler.com/eaaCatalog/lazyLoad.html for reference). + +Ghost objects work only with concrete and non-internal classes. In the generic +case, they are not compatible with using factories in their initializer. + +Virtual proxies work with concrete, abstract or internal classes. They provide an +API that looks like the actual objects and forward calls to them. They can cause +identity problems because proxies might not be seen as equivalents to the actual +objects they proxy. + +Because of this identity problem, ghost objects should be preferred when +possible. Exceptions thrown by the `ProxyHelper` class can help decide when it +can be used or not. + +Ghost objects and virtual proxies both provide implementations for the +`LazyObjectInterface` which allows resetting them to their initial state or to +forcibly initialize them when needed. Note that resetting a ghost object skips +its read-only properties. You should use a virtual proxy to reset read-only +properties. + +### `LazyGhostTrait` + +By using `LazyGhostTrait` either directly in your classes or by using +`ProxyHelper::generateLazyGhost()`, you can make their instances lazy-loadable. +This works by creating these instances empty and by computing their state only +when accessing a property. + +```php +class FooLazyGhost extends Foo +{ + use LazyGhostTrait; +} + +$foo = FooLazyGhost::createLazyGhost(initializer: function (Foo $instance): void { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the $instance + $instance->__construct(...$dependencies); + // [...] Call setters, etc. if needed +}); + +// $foo is now a lazy-loading ghost object. The initializer will +// be called only when and if a *property* is accessed. +``` + +### `LazyProxyTrait` + +Alternatively, `LazyProxyTrait` can be used to create virtual proxies: + +```php +$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(Foo::class)); +// $proxyCode contains the reference to LazyProxyTrait +// and should be dumped into a file in production envs +eval('class FooLazyProxy'.$proxyCode); + +$foo = FooLazyProxy::createLazyProxy(initializer: function (): Foo { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the $instance + $instance = new Foo(...$dependencies); + // [...] Call setters, etc. if needed + + return $instance; +}); +// $foo is now a lazy-loading virtual proxy object. The initializer will +// be called only when and if a *method* is called. +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_exporter.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php new file mode 100644 index 0000000..22e9b51 --- /dev/null +++ b/vendor/symfony/var-exporter/VarExporter.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Internal\Exporter; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\VarExporter\Internal\Values; + +/** + * Exports serializable PHP values to PHP code. + * + * VarExporter allows serializing PHP data structures to plain PHP code (like var_export()) + * while preserving all the semantics associated with serialize() (unlike var_export()). + * + * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize(). + * + * @author Nicolas Grekas + */ +final class VarExporter +{ + /** + * Exports a serializable PHP value to PHP code. + * + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param array &$foundClasses Classes found in the value are added to this list as both keys and values + * + * @throws ExceptionInterface When the provided value cannot be serialized + */ + public static function export(mixed $value, ?bool &$isStaticValue = null, array &$foundClasses = []): string + { + $isStaticValue = true; + + if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) { + return Exporter::export($value); + } + + $objectsPool = new \SplObjectStorage(); + $refsPool = []; + $objectsCount = 0; + + try { + $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; + } finally { + $references = []; + foreach ($refsPool as $i => $v) { + if ($v[0]->count) { + $references[1 + $i] = $v[2]; + } + $v[0] = $v[1]; + } + } + + if ($isStaticValue) { + return Exporter::export($value); + } + + $classes = []; + $values = []; + $states = []; + foreach ($objectsPool as $i => $v) { + [, $class, $values[], $wakeup] = $objectsPool[$v]; + $foundClasses[$class] = $classes[] = $class; + + if (0 < $wakeup) { + $states[$wakeup] = $i; + } elseif (0 > $wakeup) { + $states[-$wakeup] = [$i, array_pop($values)]; + $values[] = []; + } + } + ksort($states); + + $wakeups = [null]; + foreach ($states as $v) { + if (\is_array($v)) { + $wakeups[-$v[0]] = $v[1]; + } else { + $wakeups[] = $v; + } + } + + if (null === $wakeups[0]) { + unset($wakeups[0]); + } + + $properties = []; + foreach ($values as $i => $vars) { + foreach ($vars as $class => $values) { + foreach ($values as $name => $v) { + $properties[$class][$name][$i] = $v; + } + } + } + + if ($classes || $references) { + $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups); + } else { + $isStaticValue = true; + } + + return Exporter::export($value); + } +} diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json new file mode 100644 index 0000000..f3a227e --- /dev/null +++ b/vendor/symfony/var-exporter/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/var-exporter", + "type": "library", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy-loading", "proxy"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/yaml/CHANGELOG.md b/vendor/symfony/yaml/CHANGELOG.md new file mode 100644 index 0000000..74b0a71 --- /dev/null +++ b/vendor/symfony/yaml/CHANGELOG.md @@ -0,0 +1,263 @@ +CHANGELOG +========= + +7.1 +--- + + * Add support for getting all the enum cases with `!php/enum Foo` + +7.0 +--- + + * Remove the `!php/const:` tag, use `!php/const` instead (without the colon) + +6.3 +--- + + * Add support to dump int keys as strings by using the `Yaml::DUMP_NUMERIC_KEY_AS_STRING` flag + +6.2 +--- + + * Add support for `!php/enum` and `!php/enum *->value` + * Deprecate the `!php/const:` tag in key which will be replaced by the `!php/const` tag (without the colon) since 3.4 + +6.1 +--- + + * In cases where it will likely improve readability, strings containing single quotes will be double-quoted + +5.4 +--- + + * Add new `lint:yaml dirname --exclude=/dirname/foo.yaml --exclude=/dirname/bar.yaml` + option to exclude one or more specific files from multiple file list + * Allow negatable for the parse tags option with `--no-parse-tags` + +5.3 +--- + + * Added `github` format support & autodetection to render errors as annotations + when running the YAML linter command in a Github Action environment. + +5.1.0 +----- + + * Added support for parsing numbers prefixed with `0o` as octal numbers. + * Deprecated support for parsing numbers starting with `0` as octal numbers. They will be parsed as strings as of Symfony 6.0. Prefix numbers with `0o` + so that they are parsed as octal numbers. + + Before: + + ```yaml + Yaml::parse('072'); + ``` + + After: + + ```yaml + Yaml::parse('0o72'); + ``` + + * Added `yaml-lint` binary. + * Deprecated using the `!php/object` and `!php/const` tags without a value. + +5.0.0 +----- + + * Removed support for mappings inside multi-line strings. + * removed support for implicit STDIN usage in the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * Added support for parsing the inline notation spanning multiple lines. + * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. + * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + +4.2.0 +----- + + * added support for multiple files or directories in `LintCommand` + +4.0.0 +----- + + * The behavior of the non-specific tag `!` is changed and now forces + non-evaluating your values. + * complex mappings will throw a `ParseException` + * support for the comma as a group separator for floats has been dropped, use + the underscore instead + * support for the `!!php/object` tag has been dropped, use the `!php/object` + tag instead + * duplicate mapping keys throw a `ParseException` + * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` + flag to cast them to strings + * `%` at the beginning of an unquoted string throw a `ParseException` + * mappings with a colon (`:`) that is not followed by a whitespace throw a + `ParseException` + * the `Dumper::setIndentation()` method has been removed + * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, + `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of + the parser and dumper is no longer supported, pass bitmask flags instead + * the constructor arguments of the `Parser` class have been removed + * the `Inline` class is internal and no longer part of the BC promise + * removed support for the `!str` tag, use the `!!str` tag instead + * added support for tagged scalars. + + ```yml + Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); + // returns TaggedValue('foo', 'bar'); + ``` + +3.4.0 +----- + + * added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method + + * the `Dumper`, `Parser`, and `Yaml` classes are marked as final + + * Deprecated the `!php/object:` tag which will be replaced by the + `!php/object` tag (without the colon) in 4.0. + + * Deprecated the `!php/const:` tag which will be replaced by the + `!php/const` tag (without the colon) in 4.0. + + * Support for the `!str` tag is deprecated, use the `!!str` tag instead. + + * Deprecated using the non-specific tag `!` as its behavior will change in 4.0. + It will force non-evaluating your values in 4.0. Use plain integers or `!!float` instead. + +3.3.0 +----- + + * Starting an unquoted string with a question mark followed by a space is + deprecated and will throw a `ParseException` in Symfony 4.0. + + * Deprecated support for implicitly parsing non-string mapping keys as strings. + Mapping keys that are no strings will lead to a `ParseException` in Symfony + 4.0. Use quotes to opt-in for keys to be parsed as strings. + + Before: + + ```php + $yaml = << new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE | Yaml::DUMP_OBJECT); + ``` + +3.0.0 +----- + + * Yaml::parse() now throws an exception when a blackslash is not escaped + in double-quoted strings + +2.8.0 +----- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/vendor/symfony/yaml/Command/LintCommand.php b/vendor/symfony/yaml/Command/LintCommand.php new file mode 100644 index 0000000..75c09f5 --- /dev/null +++ b/vendor/symfony/yaml/Command/LintCommand.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Yaml; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + */ +#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')] +class LintCommand extends Command +{ + private Parser $parser; + private ?string $format = null; + private bool $displayCorrectFiles; + private ?\Closure $directoryIteratorProvider; + private ?\Closure $isReadableProvider; + + public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null) + { + parent::__construct($name); + + $this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...); + $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...); + } + + protected function configure(): void + { + $this + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) + ->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude') + ->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null) + ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validates YAML contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +You can also exclude one or more specific files: + + php %command.full_name% dirname --exclude="dirname/foo.yaml" --exclude="dirname/bar.yaml" + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $excludes = $input->getOption('exclude'); + $this->format = $input->getOption('format'); + $flags = $input->getOption('parse-tags'); + + if (null === $this->format) { + // Autodetect format according to CI environment + $this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; + } + + $flags = $flags ? Yaml::PARSE_CUSTOM_TAGS : 0; + + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + if (!\in_array($file->getPathname(), $excludes, true)) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, int $flags, ?string $file = null): array + { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + + try { + $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); + } catch (ParseException $e) { + return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()]; + } finally { + restore_error_handler(); + } + + return ['file' => $file, 'valid' => true]; + } + + private function display(SymfonyStyle $io, array $files): int + { + return match ($this->format) { + 'txt' => $this->displayTxt($io, $files), + 'json' => $this->displayJson($io, $files), + 'github' => $this->displayTxt($io, $files, true), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $suggestTagOption = false; + + if ($errorAsGithubAnnotations) { + $githubReporter = new GithubActionReporter($io); + } + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->text(sprintf(' >> %s', $info['message'])); + + if (str_contains($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = true; + } + + if ($errorAsGithubAnnotations) { + $githubReporter->error($info['message'], $info['file'] ?? 'php://stdin', $info['line']); + } + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + + if (isset($v['message']) && str_contains($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { + continue; + } + + yield $file; + } + } + + private function getParser(): Parser + { + return $this->parser ??= new Parser(); + } + + private function getDirectoryIterator(string $directory): iterable + { + $default = fn ($directory) => new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory): bool + { + $default = is_readable(...); + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'json', 'github']; + } +} diff --git a/vendor/symfony/yaml/Dumper.php b/vendor/symfony/yaml/Dumper.php new file mode 100644 index 0000000..4292c36 --- /dev/null +++ b/vendor/symfony/yaml/Dumper.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + * + * @final + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + */ + private int $indentation; + + public function __construct(int $indentation = 4) + { + if ($indentation < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = $indentation; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags = 0): string + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { + $dumpObjectAsInlineMap = !(array) $input; + } + + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || !$input) { + $output .= $prefix.Inline::dump($input, $flags); + } elseif ($input instanceof TaggedValue) { + $output .= $this->dumpTaggedValue($input, $inline, $indent, $flags, $prefix); + } else { + $dumpAsMap = Inline::isHash($input); + + foreach ($input as $key => $value) { + if ('' !== $output && "\n" !== $output[-1]) { + $output .= "\n"; + } + + if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) { + $key = (string) $key; + } + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && str_contains($value, "\n") && !str_contains($value, "\r")) { + $blockIndentationIndicator = $this->getBlockIndentationIndicator($value); + + if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { + $blockChompingIndicator = '+'; + } elseif ("\n" === $value[-1]) { + $blockChompingIndicator = ''; + } else { + $blockChompingIndicator = '-'; + } + + $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); + + foreach (explode("\n", $value) as $row) { + if ('' === $row) { + $output .= "\n"; + } else { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + } + + continue; + } + + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && str_contains($value->getValue(), "\n") && !str_contains($value->getValue(), "\r\n")) { + $blockIndentationIndicator = $this->getBlockIndentationIndicator($value->getValue()); + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { + $dumpObjectAsInlineMap = !(array) $value; + } + + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || !$value; + + $output .= sprintf('%s%s%s%s', + $prefix, + $dumpAsMap ? Inline::dump($key, $flags).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } + + private function dumpTaggedValue(TaggedValue $value, int $inline, int $indent, int $flags, string $prefix): string + { + $output = sprintf('%s!%s', $prefix ? $prefix.' ' : '', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && str_contains($value->getValue(), "\n") && !str_contains($value->getValue(), "\r\n")) { + $blockIndentationIndicator = $this->getBlockIndentationIndicator($value->getValue()); + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + return $output; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + return $output.' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } + + return $output."\n".$this->dump($value->getValue(), $inline - 1, $indent, $flags); + } + + private function getBlockIndentationIndicator(string $value): string + { + $lines = explode("\n", $value); + + // If the first line (that is neither empty nor contains only spaces) + // starts with a space character, the spec requires a block indentation indicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + foreach ($lines as $line) { + if ('' !== trim($line, ' ')) { + return str_starts_with($line, ' ') ? (string) $this->indentation : ''; + } + } + + return ''; + } +} diff --git a/vendor/symfony/yaml/Escaper.php b/vendor/symfony/yaml/Escaper.php new file mode 100644 index 0000000..044f1a3 --- /dev/null +++ b/vendor/symfony/yaml/Escaper.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private const ESCAPEES = ['\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\x7f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ]; + private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\x7f', + '\\N', '\\_', '\\L', '\\P', + ]; + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresDoubleQuoting(string $value): bool + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithDoubleQuotes(string $value): string + { + return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresSingleQuoting(string $value): bool + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[\s\'"\:\{\}\[\],&\*\#\?] | \A[\-?|<>=!%@`\p{Zs}]/xu', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithSingleQuotes(string $value): string + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/vendor/symfony/yaml/Exception/DumpException.php b/vendor/symfony/yaml/Exception/DumpException.php new file mode 100644 index 0000000..cce972f --- /dev/null +++ b/vendor/symfony/yaml/Exception/DumpException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + */ +class DumpException extends RuntimeException +{ +} diff --git a/vendor/symfony/yaml/Exception/ExceptionInterface.php b/vendor/symfony/yaml/Exception/ExceptionInterface.php new file mode 100644 index 0000000..9091316 --- /dev/null +++ b/vendor/symfony/yaml/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/yaml/Exception/ParseException.php b/vendor/symfony/yaml/Exception/ParseException.php new file mode 100644 index 0000000..73c067b --- /dev/null +++ b/vendor/symfony/yaml/Exception/ParseException.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + */ +class ParseException extends RuntimeException +{ + /** + * @param string $rawMessage The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + */ + public function __construct( + private string $rawMessage, + private int $parsedLine = -1, + private ?string $snippet = null, + private ?string $parsedFile = null, + ?\Throwable $previous = null, + ) { + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + */ + public function getSnippet(): string + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + */ + public function setSnippet(string $snippet): void + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + */ + public function getParsedFile(): string + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + */ + public function setParsedFile(string $parsedFile): void + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + */ + public function getParsedLine(): int + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + */ + public function setParsedLine(int $parsedLine): void + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr(): void + { + $this->message = $this->rawMessage; + + $dot = false; + if (str_ends_with($this->message, '.')) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/vendor/symfony/yaml/Exception/RuntimeException.php b/vendor/symfony/yaml/Exception/RuntimeException.php new file mode 100644 index 0000000..3f36b73 --- /dev/null +++ b/vendor/symfony/yaml/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Inline.php b/vendor/symfony/yaml/Inline.php new file mode 100644 index 0000000..b6dc26b --- /dev/null +++ b/vendor/symfony/yaml/Inline.php @@ -0,0 +1,840 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + * + * @internal + */ +class Inline +{ + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + public static int $parsedLineNumber = -1; + public static ?string $parsedFilename = null; + + private static bool $exceptionOnInvalidType = false; + private static bool $objectSupport = false; + private static bool $objectForMap = false; + private static bool $constantSupport = false; + + public static function initialize(int $flags, ?int $parsedLineNumber = null, ?string $parsedFilename = null): void + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } + + /** + * Converts a YAML string to a PHP value. + * + * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior + * @param array $references Mapping of variable names to values + * + * @throws ParseException + */ + public static function parse(string $value, int $flags = 0, array &$references = []): mixed + { + self::initialize($flags); + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + $i = 0; + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { + case '[': + $result = self::parseSequence($value, $flags, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $flags, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, $flags, null, $i, true, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (null !== $tag && '' !== $tag) { + return new TaggedValue($tag, $result); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump(mixed $value, int $flags = 0): string + { + switch (true) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return self::dumpNull($flags); + case $value instanceof \DateTimeInterface: + return $value->format(match (true) { + !$length = \strlen(rtrim($value->format('u'), '0')) => 'c', + $length < 4 => 'Y-m-d\TH:i:s.vP', + default => 'Y-m-d\TH:i:s.uP', + }); + case $value instanceof \UnitEnum: + return sprintf('!php/enum %s::%s', $value::class, $value->name); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); + } + + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object '.self::dump(serialize($value)); + } + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + return self::dumpHashArray($value, $flags); + } + + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return self::dumpNull($flags); + case \is_array($value): + return self::dumpArray($value, $flags); + case null === $value: + return self::dumpNull($flags); + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case \is_int($value): + return $value; + case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"): + $locale = setlocale(\LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(\LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + if (!str_contains($repr, 'E')) { + $repr .= '.0'; + } + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case self::isBinaryString($value): + return '!!binary '.base64_encode($value); + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + $singleQuoted = Escaper::escapeWithSingleQuotes($value); + if (!str_contains($value, "'")) { + return $singleQuoted; + } + // Attempt double-quoting the string instead to see if it's more efficient. + $doubleQuoted = Escaper::escapeWithDoubleQuotes($value); + + return \strlen($doubleQuoted) < \strlen($singleQuoted) ? $doubleQuoted : $singleQuoted; + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + */ + public static function isHash(array|\ArrayObject|\stdClass $value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + private static function dumpArray(array $value, int $flags): string + { + // array + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { + $output = []; + foreach ($value as $val) { + $output[] = self::dump($val, $flags); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + return self::dumpHashArray($value, $flags); + } + + /** + * Dumps hash array to a YAML string. + * + * @param array|\ArrayObject|\stdClass $value The hash array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $flags): string + { + $output = []; + foreach ($value as $key => $val) { + if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) { + $key = (string) $key; + } + + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + + return 'null'; + } + + /** + * Parses a YAML scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar(string $scalar, int $flags = 0, ?array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], ?bool &$isQuoted = null): mixed + { + if (\in_array($scalar[$i], ['"', "'"], true)) { + // quoted scalar + $isQuoted = true; + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), " \n"); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + } + } else { + // "normal" string + $isQuoted = false; + + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + $output = trim($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { + throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $flags, $references, $isQuoted); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar(string $scalar, int &$i = 0): string + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $output = substr($match[0], 1, -1); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array + { + $output = []; + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($sequence, $i, $flags); + switch ($sequence[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($sequence, $flags, $i, $references); + break; + case '{': + // nested mapping + $value = self::parseMapping($sequence, $flags, $i, $references); + break; + default: + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); + + // the value can be an array if a reference has been resolved to an array var + if (\is_string($value) && !$isQuoted && str_contains($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); + } catch (\InvalidArgumentException) { + // no, it's not + } + } + + if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + --$i; + } + + if (null !== $tag && '' !== $tag) { + $value = new TaggedValue($tag, $value); + } + + $output[] = $value; + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Parses a YAML mapping. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []): array|\stdClass + { + $output = []; + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + case "\n": + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $offsetBeforeKeyParsing = $i; + $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false); + + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } + + if ('!php/const' === $key || '!php/enum' === $key) { + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false); + $key = self::evaluateScalar($key, $flags); + } + + if (false === $i = strpos($mapping, ':', $i)) { + break; + } + + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); + } + } + + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); + } + + if ('<<' === $key) { + $allowOverwrite = true; + } + + while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($mapping, $i, $flags); + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + default: + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && !self::isBinaryString($value) && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + --$i; + } + ++$i; + + continue 2; + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], ?bool &$isQuotedString = null): mixed + { + $isQuotedString = false; + $scalar = trim($scalar); + + if (str_starts_with($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (!\array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + return $references[$value]; + } + + $scalarLower = strtolower($scalar); + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + case '!' === $scalar[0]: + switch (true) { + case str_starts_with($scalar, '!!str '): + $s = (string) substr($scalar, 6); + + if (\in_array($s[0] ?? '', ['"', "'"], true)) { + $isQuotedString = true; + $s = self::parseQuotedScalar($s); + } + + return $s; + case str_starts_with($scalar, '! '): + return substr($scalar, 2); + case str_starts_with($scalar, '!php/object'): + if (self::$objectSupport) { + if (!isset($scalar[12])) { + throw new ParseException('Missing value for tag "!php/object".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return unserialize(self::parseScalar(substr($scalar, 12))); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!php/const'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + throw new ParseException('Missing value for tag "!php/const".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!php/enum'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + throw new ParseException('Missing value for tag "!php/enum".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $i = 0; + $enumName = self::parseScalar(substr($scalar, 10), 0, null, $i, false); + $useName = str_contains($enumName, '::'); + $enum = $useName ? strstr($enumName, '::', true) : $enumName; + + if (!enum_exists($enum)) { + throw new ParseException(sprintf('The enum "%s" is not defined.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!$useName) { + return $enum::cases(); + } + if ($useValue = str_ends_with($enumName, '->value')) { + $enumName = substr($enumName, 0, -7); + } + + if (!\defined($enumName)) { + throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enumName), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $value = \constant($enumName); + + if (!$useValue) { + return $value; + } + if (!$value instanceof \BackedEnum) { + throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enumName), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return $value->value; + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as an enum. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!!float '): + return (float) substr($scalar, 8); + case str_starts_with($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + } + + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + case preg_match('/^(?:\+|-)?0o(?P[0-7_]++)$/', $scalar, $matches): + $value = str_replace('_', '', $matches['value']); + + if ('-' === $scalar[0]) { + return -octdec($value); + } + + return octdec($value); + case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', $scalar); + } + + switch (true) { + case ctype_digit($scalar): + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $cast = (int) $scalar; + + return ($scalar === (string) $cast) ? $cast : $scalar; + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + return (float) str_replace('_', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + try { + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + $time = new \DateTimeImmutable($scalar, new \DateTimeZone('UTC')); + } catch (\Exception $e) { + // Some dates accepted by the regex are not valid dates. + throw new ParseException(\sprintf('The date "%s" could not be parsed as it is an invalid date.', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename, $e); + } + + if (Yaml::PARSE_DATETIME & $flags) { + return $time; + } + + if ('' !== rtrim($time->format('u'), '0')) { + return (float) $time->format('U.u'); + } + + try { + if (false !== $scalar = $time->getTimestamp()) { + return $scalar; + } + } catch (\ValueError) { + // no-op + } + + return $time->format('U'); + } + } + + return (string) $scalar; + } + + private static function parseTag(string $value, int &$i, int $flags): ?string + { + if ('!' !== $value[$i]) { + return null; + } + + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + // Is followed by a scalar and is a built-in tag + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || \in_array($tag, ['str', 'php/const', 'php/enum', 'php/object'], true))) { + // Manage in {@link self::evaluateScalar()} + return null; + } + + $i = $nextOffset; + + // Built-in tags + if ('' !== $tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + public static function evaluateBinaryScalar(string $scalar): string + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + + if (0 !== (\strlen($parsedBinaryData) % 4)) { + throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return base64_decode($parsedBinaryData, true); + } + + private static function isBinaryString(string $value): bool + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); + } + + /** + * Gets a regex that matches a YAML date. + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex(): string + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + */ + private static function getHexRegex(): string + { + return '~^0x[0-9a-f_]++$~i'; + } +} diff --git a/vendor/symfony/yaml/LICENSE b/vendor/symfony/yaml/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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. diff --git a/vendor/symfony/yaml/Parser.php b/vendor/symfony/yaml/Parser.php new file mode 100644 index 0000000..7a41509 --- /dev/null +++ b/vendor/symfony/yaml/Parser.php @@ -0,0 +1,1244 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + * + * @final + */ +class Parser +{ + public const TAG_PATTERN = '(?P![\w!.\/:-]+)'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; + + private ?string $filename = null; + private int $offset = 0; + private int $numberOfParsedLines = 0; + private ?int $totalNumberOfLines = null; + private array $lines = []; + private int $currentLineNb = -1; + private string $currentLine = ''; + private array $refs = []; + private array $skippedLineNumbers = []; + private array $locallySkippedLineNumbers = []; + private array $refsBeingParsed = []; + + /** + * Parses a YAML file into a PHP value. + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public function parseFile(string $filename, int $flags = 0): mixed + { + if (!is_file($filename)) { + throw new ParseException(sprintf('File "%s" does not exist.', $filename)); + } + + if (!is_readable($filename)) { + throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); + } + + $this->filename = $filename; + + try { + return $this->parse(file_get_contents($filename), $flags); + } finally { + $this->filename = null; + } + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public function parse(string $value, int $flags = 0): mixed + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); + } + + $this->refs = []; + + try { + $data = $this->doParse($value, $flags); + } finally { + $this->refsBeingParsed = []; + $this->offset = 0; + $this->lines = []; + $this->currentLine = ''; + $this->numberOfParsedLines = 0; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines = null; + } + + return $data; + } + + private function doParse(string $value, int $flags): mixed + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->numberOfParsedLines = \count($this->lines); + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines ??= $this->numberOfParsedLines; + + if (!$this->moveToNextLine()) { + return null; + } + + $data = []; + $context = null; + $allowOverwrite = false; + + while ($this->isCurrentLineEmpty()) { + if (!$this->moveToNextLine()) { + return null; + } + } + + // Resolves the tag and returns if end of the document + if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + + do { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); + + $isRef = $mergeNode = false; + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $context = 'sequence'; + + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // array + if (isset($values['value']) && str_starts_with(ltrim($values['value'], ' '), '-')) { + // Inline first child + $currentLineNumber = $this->getRealCurrentLineNb(); + + $sequenceIndentation = \strlen($values['leadspaces']) + 1; + $sequenceYaml = substr($this->currentLine, $sequenceIndentation); + $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); + + $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || str_starts_with(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); + } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { + $data[] = new TaggedValue( + $subTag, + $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) + ); + } else { + if ( + isset($values['leadspaces']) + && ( + '!' === $values['value'][0] + || self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches) + ) + ) { + $block = $values['value']; + if ($this->isNextLineIndented() || isset($matches['value']) && '>-' === $matches['value']) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + } else { + $data[] = $this->parseValue($values['value'], $flags, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + array_pop($this->refsBeingParsed); + } + } elseif ( + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) + && (!str_contains($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) + ) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + $context = 'mapping'; + + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (!\is_string($key) && !\is_int($key)) { + throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string').' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value'][0]) && '*' === $values['value'][0]) { + $refName = substr(rtrim($values['value']), 1); + if (!\array_key_exists($refName, $this->refs)) { + if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $refValue = $this->refs[$refName]; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { + $refValue = (array) $refValue; + } + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { + $parsed = (array) $parsed; + } + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { + $parsedItem = (array) $parsedItem; + } + + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + $subTag = null; + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' === $values['value'] || str_starts_with($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, ''); + } else { + $data[$key] = null; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + // remember the parsed line number here in case we need it to provide some contexts in error messages below + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { + $value = (array) $value; + } + + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, $value); + } else { + $data[$key] = $value; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); + } + } + } else { + $value = $this->parseValue(rtrim($values['value']), $flags, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + array_pop($this->refsBeingParsed); + } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + if (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + // try to parse the value as a multi-line string as a last resort + if (0 === $this->currentLineNb) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + $value = ''; + + foreach ($this->lines as $line) { + $trimmedLine = trim($line); + if ('#' === ($trimmedLine[0] ?? '')) { + continue; + } + // If the indentation is not consistent at offset 0, it is to be considered as a ParseError + if (0 === $this->offset && isset($line[0]) && ' ' === $line[0]) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (str_contains($line, ': ')) { + throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if ('' === $trimmedLine) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + if ('' !== $trimmedLine && str_ends_with($line, '\\')) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== $trimmedLine) { + $value .= $trimmedLine; + } + + if ('' === $trimmedLine) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif (str_ends_with($line, '\\')) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + } + + try { + return Inline::parse(trim($value)); + } catch (ParseException) { + // fall-through to the ParseException thrown below + } + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } while ($this->moveToNextLine()); + + if (null !== $tag) { + $data = new TaggedValue($tag, $data); + } + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return $data ?: null; + } + + private function parseBlock(int $offset, string $yaml, int $flags): mixed + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self(); + $parser->offset = $offset; + $parser->totalNumberOfLines = $this->totalNumberOfLines; + $parser->skippedLineNumbers = $skippedLineNumbers; + $parser->refs = &$this->refs; + $parser->refsBeingParsed = $this->refsBeingParsed; + + return $parser->doParse($yaml, $flags); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @internal + */ + public function getRealCurrentLineNb(): int + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + private function getCurrentLineIndentation(): int + { + if (' ' !== ($this->currentLine[0] ?? '')) { + return 0; + } + + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int|null $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock(?int $indentation = null, bool $inSequence = false): string + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return ''; + } + + if (null === $indentation) { + $newIndent = null; + $movements = 0; + + do { + $EOF = false; + + // empty and comment-like lines do not influence the indentation depth + if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } else { + $newIndent = $this->getCurrentLineIndentation(); + } + } while (!$EOF && null === $newIndent); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } else { + $newIndent = $indentation; + } + + $data = []; + + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } else { + $this->moveToPreviousLine(); + + return ''; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return ''; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + + while ($this->moveToNextLine()) { + if ($isItComment && !$isItUnindentedCollection) { + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + } + + $indent = $this->getCurrentLineIndentation(); + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return implode("\n", $data); + } + + private function hasMoreLines(): bool + { + return (\count($this->lines) - 1) > $this->currentLineNb; + } + + /** + * Moves the parser to the next line. + */ + private function moveToNextLine(): bool + { + if ($this->currentLineNb >= $this->numberOfParsedLines - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + private function moveToPreviousLine(): bool + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior + * @param string $context The parser context (either sequence or mapping) + * + * @throws ParseException When reference does not exist + */ + private function parseValue(string $value, int $flags, string $context): mixed + { + if (str_starts_with($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!\array_key_exists($value, $this->refs)) { + if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + return $this->refs[$value]; + } + + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = $matches['modifiers'] ?? ''; + + $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); + + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { + if ('!!binary' === $matches['tag']) { + return Inline::evaluateBinaryScalar($data); + } + + return new TaggedValue(substr($matches['tag'], 1), $data); + } + + return $data; + } + + try { + if ('' !== $value && '{' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); + } + + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); + + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } + + return $parsedValue; + default: + $lines = []; + + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); + + break; + } + + $lines[] = trim($this->currentLine); + } + + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + + $parsedValue = Inline::parse($value, $flags, $this->refs); + + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && str_contains($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + return $parsedValue; + } + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + */ + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = []; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + $currentLineLength = \strlen($this->currentLine); + + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank + || self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + */ + private function isNextLineIndented(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isCurrentLineEmpty(): bool + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + private function isCurrentLineBlank(): bool + { + return '' === $this->currentLine || '' === trim($this->currentLine, ' '); + } + + private function isCurrentLineComment(): bool + { + // checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? ltrim($this->currentLine, ' ') : $this->currentLine; + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument(): bool + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + private function cleanup(string $value): string + { + $value = str_replace(["\r\n", "\r"], "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + private function isNextLineUnIndentedCollection(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isStringUnIndentedCollectionItem(): bool + { + return '-' === rtrim($this->currentLine) || str_starts_with($this->currentLine, '- '); + } + + /** + * A local wrapper for "preg_match" which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @internal + */ + public static function preg_match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + throw new ParseException(preg_last_error_msg()); + } + + return $ret; + } + + /** + * Trim the tag on top of the value. + * + * Prevent values such as "!foo {quz: bar}" to be considered as + * a mapping block. + */ + private function trimTag(string $value): string + { + if ('!' === $value[0]) { + return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); + } + + return $value; + } + + private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string + { + if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { + return null; + } + + if ($nextLineCheck && !$this->isNextLineIndented()) { + return null; + } + + $tag = substr($matches['tag'], 1); + + // Built-in tags + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + private function lexInlineQuotedString(int &$cursor = 0): string + { + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; + + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + $lineNumber = 0; + + do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + + if ($this->isCurrentLineBlank()) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if ("'" === $quotation) { + $value .= '\\'; + } elseif (isset($this->currentLine[++$cursor])) { + $value .= '\\'.$this->currentLine[$cursor]; + } + + break; + case $quotation: + ++$cursor; + + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + + return $value.$quotation; + default: + $value .= $this->currentLine[$cursor]; + } + } + + if ($this->isCurrentLineBlank()) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === $this->currentLine[-1]) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function lexUnquotedString(int &$cursor): string + { + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); + + if ($cursor === $offset) { + throw new ParseException('Malformed unquoted YAML string.'); + } + + return substr($this->currentLine, $offset, $cursor - $offset); + } + + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); + } + + private function lexInlineSequence(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, ']'); + } + + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; + + do { + $this->consumeWhitespaces($cursor); + + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } + + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; + + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; + + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + return 0 < $whitespacesConsumed; + } +} diff --git a/vendor/symfony/yaml/README.md b/vendor/symfony/yaml/README.md new file mode 100644 index 0000000..ac25024 --- /dev/null +++ b/vendor/symfony/yaml/README.md @@ -0,0 +1,13 @@ +Yaml Component +============== + +The Yaml component loads and dumps YAML files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/yaml.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/yaml/Resources/bin/yaml-lint b/vendor/symfony/yaml/Resources/bin/yaml-lint new file mode 100755 index 0000000..143869e --- /dev/null +++ b/vendor/symfony/yaml/Resources/bin/yaml-lint @@ -0,0 +1,49 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== \PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} + +/** + * Runs the Yaml lint command. + * + * @author Jan Schädlich + */ + +use Symfony\Component\Console\Application; +use Symfony\Component\Yaml\Command\LintCommand; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the Yaml linter.'.PHP_EOL); + exit(1); +} + +(new Application())->add($command = new LintCommand()) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run() +; diff --git a/vendor/symfony/yaml/Tag/TaggedValue.php b/vendor/symfony/yaml/Tag/TaggedValue.php new file mode 100644 index 0000000..9d29193 --- /dev/null +++ b/vendor/symfony/yaml/Tag/TaggedValue.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Tag; + +/** + * @author Nicolas Grekas + * @author Guilhem N. + */ +final class TaggedValue +{ + public function __construct( + private string $tag, + private mixed $value, + ) { + } + + public function getTag(): string + { + return $this->tag; + } + + public function getValue(): mixed + { + return $this->value; + } +} diff --git a/vendor/symfony/yaml/Unescaper.php b/vendor/symfony/yaml/Unescaper.php new file mode 100644 index 0000000..9e640ff --- /dev/null +++ b/vendor/symfony/yaml/Unescaper.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Unescaper +{ + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + */ + public function unescapeSingleQuotedString(string $value): string + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + */ + public function unescapeDoubleQuotedString(string $value): string + { + $callback = fn ($match) => $this->unescapeCharacter($match[0]); + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + */ + private function unescapeCharacter(string $value): string + { + return match ($value[1]) { + '0' => "\x0", + 'a' => "\x7", + 'b' => "\x8", + 't' => "\t", + "\t" => "\t", + 'n' => "\n", + 'v' => "\xB", + 'f' => "\xC", + 'r' => "\r", + 'e' => "\x1B", + ' ' => ' ', + '"' => '"', + '/' => '/', + '\\' => '\\', + // U+0085 NEXT LINE + 'N' => "\xC2\x85", + // U+00A0 NO-BREAK SPACE + '_' => "\xC2\xA0", + // U+2028 LINE SEPARATOR + 'L' => "\xE2\x80\xA8", + // U+2029 PARAGRAPH SEPARATOR + 'P' => "\xE2\x80\xA9", + 'x' => self::utf8chr(hexdec(substr($value, 2, 2))), + 'u' => self::utf8chr(hexdec(substr($value, 2, 4))), + 'U' => self::utf8chr(hexdec(substr($value, 2, 8))), + default => throw new ParseException(sprintf('Found unknown escape character "%s".', $value)), + }; + } + + /** + * Get the UTF-8 character for the given code point. + */ + private static function utf8chr(int $c): string + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/vendor/symfony/yaml/Yaml.php b/vendor/symfony/yaml/Yaml.php new file mode 100644 index 0000000..e2d2af7 --- /dev/null +++ b/vendor/symfony/yaml/Yaml.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + * + * @final + */ +class Yaml +{ + public const DUMP_OBJECT = 1; + public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + public const PARSE_OBJECT = 4; + public const PARSE_OBJECT_FOR_MAP = 8; + public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + public const PARSE_DATETIME = 32; + public const DUMP_OBJECT_AS_MAP = 64; + public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + public const PARSE_CONSTANT = 256; + public const PARSE_CUSTOM_TAGS = 512; + public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + public const DUMP_NULL_AS_TILDE = 2048; + public const DUMP_NUMERIC_KEY_AS_STRING = 4096; + + /** + * Parses a YAML file into a PHP value. + * + * Usage: + * + * $array = Yaml::parseFile('config.yml'); + * print_r($array); + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public static function parseFile(string $filename, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parseFile($filename, $flags); + } + + /** + * Parses YAML into a PHP value. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * + * @param string $input A string containing YAML + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public static function parse(string $input, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parse($input, $flags); + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string + */ + public static function dump(mixed $input, int $inline = 2, int $indent = 4, int $flags = 0): string + { + $yaml = new Dumper($indent); + + return $yaml->dump($input, $inline, 0, $flags); + } +} diff --git a/vendor/symfony/yaml/composer.json b/vendor/symfony/yaml/composer.json new file mode 100644 index 0000000..d2d6243 --- /dev/null +++ b/vendor/symfony/yaml/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Loads and dumps YAML files", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "minimum-stability": "dev" +} diff --git a/vendor/twig/twig/CHANGELOG b/vendor/twig/twig/CHANGELOG new file mode 100644 index 0000000..44c79b1 --- /dev/null +++ b/vendor/twig/twig/CHANGELOG @@ -0,0 +1,325 @@ +# 3.14.0 (2024-09-09) + + * Fix a security issue when an included sandboxed template has been loaded before without the sandbox context + * Add the possibility to reset globals via `Environment::resetGlobals()` + * Deprecate `Environment::mergeGlobals()` + +# 3.13.0 (2024-09-07) + + * Add the `types` tag (experimental) + * Deprecate the `Twig\Test\NodeTestCase::getTests()` data provider, override `provideTests()` instead. + * Mark `Twig\Test\NodeTestCase::getEnvironment()` as final, override `createEnvironment()` instead. + * Deprecate `Twig\Test\NodeTestCase::getVariableGetter()`, call `createVariableGetter()` instead. + * Deprecate `Twig\Test\NodeTestCase::getAttributeGetter()`, call `createAttributeGetter()` instead. + * Deprecate not overriding `Twig\Test\IntegrationTestCase::getFixturesDirectory()`, this method will be abstract in 4.0 + * Marked `Twig\Test\IntegrationTestCase::getTests()` and `getLegacyTests()` as final + +# 3.12.0 (2024-08-29) + + * Deprecate the fact that the `extends` and `use` tags are always allowed in a sandboxed template. + This behavior will change in 4.0 where these tags will need to be explicitly allowed like any other tag. + * Deprecate the "tag" constructor argument of the "Twig\Node\Node" class as the tag is now automatically set by the Parser when needed + * Fix precedence of two-word tests when the first word is a valid test + * Deprecate the `spaceless` filter + * Deprecate some internal methods from `Parser`: `getBlockStack()`, `hasBlock()`, `getBlock()`, `hasMacro()`, `hasTraits()`, `getParent()` + * Deprecate passing `null` to `Twig\Parser::setParent()` + * Update `Node::__toString()` to include the node tag if set + * Add support for integers in methods of `Twig\Node\Node` that take a Node name + * Deprecate not passing a `BodyNode` instance as the body of a `ModuleNode` or `MacroNode` constructor + * Deprecate returning "null" from "TokenParserInterface::parse()". + * Deprecate `OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES` + * Fix performance regression when `use_yield` is `false` (which is the default) + * Improve compatibility when `use_yield` is `false` (as extensions still using `echo` will work as is) + * Accept colons (`:`) in addition to equals (`=`) to separate argument names and values in named arguments + * Add the `html_cva` function (in the HTML extra package) + * Add support for named arguments to the `block` and `attribute` functions + * Throw a SyntaxError exception at compile time when a Twig callable has not the minimum number of required arguments + * Add a `CallableArgumentsExtractor` class + * Deprecate passing a name to `FunctionExpression`, `FilterExpression`, and `TestExpression`; + pass a `TwigFunction`, `TwigFilter`, or `TestFilter` instead + * Deprecate all Twig callable attributes on `FunctionExpression`, `FilterExpression`, and `TestExpression` + * Deprecate the `filter` node of `FilterExpression` + * Add the notion of Twig callables (functions, filters, and tests) + * Bump minimum PHP version to 8.0 + * Fix integration tests when a test has more than one data/expect section and deprecations + * Add the `enum_cases` function + +# 3.11.0 (2024-08-08) + + * Deprecate `OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER` + * Add `Twig\Cache\ChainCache` and `Twig\Cache\ReadOnlyFilesystemCache` + * Add the possibility to deprecate attributes and nodes on `Node` + * Add the possibility to add a package and a version to the `deprecated` tag + * Add the possibility to add a package for filter/function/test deprecations + * Mark `ConstantExpression` as being `@final` + * Add the `find` filter + * Fix optimizer mode validation in `OptimizerNodeVisitor` + * Add the possibility to yield from a generator in `PrintNode` + * Add the `shuffle` filter + * Add the `singular` and `plural` filters in `StringExtension` + * Deprecate the second argument of `Twig\Node\Expression\CallExpression::compileArguments()` + * Deprecate `Twig\ExpressionParser\parseHashExpression()` in favor of + `Twig\ExpressionParser::parseMappingExpression()` + * Deprecate `Twig\ExpressionParser\parseArrayExpression()` in favor of + `Twig\ExpressionParser::parseSequenceExpression()` + * Add `sequence` and `mapping` tests + * Deprecate `Twig\Node\Expression\NameExpression::isSimple()` and + `Twig\Node\Expression\NameExpression::isSpecial()` + +# 3.10.3 (2024-05-16) + + * Fix missing ; in generated code + +# 3.10.2 (2024-05-14) + + * Fix support for the deprecated escaper signature + +# 3.10.1 (2024-05-12) + + * Fix BC break on escaper extension + * Fix constant return type + +# 3.10.0 (2024-05-11) + + * Make `CoreExtension::formatDate`, `CoreExtension::convertDate`, and + `CoreExtension::formatNumber` part of the public API + * Add `needs_charset` option for filters and functions + * Extract the escaping logic from the `EscaperExtension` class to a new + `EscaperRuntime` class. + + The following methods from ``Twig\\Extension\\EscaperExtension`` are + deprecated: ``setEscaper()``, ``getEscapers()``, ``setSafeClasses``, + ``addSafeClasses()``. Use the same methods on the + ``Twig\\Runtime\\EscaperRuntime`` class instead. + * Fix capturing output from extensions that still use echo + * Fix a PHP warning in the Lexer on malformed templates + * Fix blocks not available under some circumstances + * Synchronize source context in templates when setting a Node on a Node + +# 3.9.3 (2024-04-18) + + * Add missing `twig_escape_filter_is_safe` deprecated function + * Fix yield usage with CaptureNode + * Add missing unwrap call when using a TemplateWrapper instance internally + * Ensure Lexer is initialized early on + +# 3.9.2 (2024-04-17) + + * Fix usage of display_end hook + +# 3.9.1 (2024-04-17) + + * Fix missing `$blocks` variable in `CaptureNode` + +# 3.9.0 (2024-04-16) + + * Add support for PHP 8.4 + * Deprecate AbstractNodeVisitor + * Deprecate passing Template to Environment::resolveTemplate(), Environment::load(), and Template::loadTemplate() + * Add a new "yield" mode for output generation; + Node implementations that use "echo" or "print" should use "yield" instead; + all Node implementations should be flagged with `#[YieldReady]` once they've been made ready for "yield"; + the "use_yield" Environment option can be turned on when all nodes have been made `#[YieldReady]`; + "yield" will be the only strategy supported in the next major version + * Add return type for Symfony 7 compatibility + * Fix premature loop exit in Security Policy lookup of allowed methods/properties + * Deprecate all internal extension functions in favor of methods on the extension classes + * Mark all extension functions as @internal + * Add SourcePolicyInterface to selectively enable the Sandbox based on a template's Source + * Throw a proper Twig exception when using cycle on an empty array + +# 3.8.0 (2023-11-21) + + * Catch errors thrown during template rendering + * Fix IntlExtension::formatDateTime use of date formatter prototype + * Fix premature loop exit in Security Policy lookup of allowed methods/properties + * Remove NumberFormatter::TYPE_CURRENCY (deprecated in PHP 8.3) + * Restore return type annotations + * Allow Symfony 7 packages to be installed + * Deprecate `twig_test_iterable` function. Use the native `is_iterable` instead. + +# 3.7.1 (2023-08-28) + + * Fix some phpdocs + +# 3.7.0 (2023-07-26) + + * Add support for the ...spread operator on arrays and hashes + +# 3.6.1 (2023-06-08) + + * Suppress some native return type deprecation messages + +# 3.6.0 (2023-05-03) + + * Allow psr/container 2.0 + * Add the new PHP 8.0 IntlDateFormatter::RELATIVE_* constants for date formatting + * Make the Lexer initialize itself lazily + +# 3.5.1 (2023-02-08) + + * Arrow functions passed to the "reduce" filter now accept the current key as a third argument + * Restores the leniency of the matches twig comparison + * Fix error messages in sandboxed mode for "has some" and "has every" + +# 3.5.0 (2022-12-27) + + * Make Twig\ExpressionParser non-internal + * Add "has some" and "has every" operators + * Add Compile::reset() + * Throw a better runtime error when the "matches" regexp is not valid + * Add "twig *_names" intl functions + * Fix optimizing closures callbacks + * Add a better exception when getting an undefined constant via `constant` + * Fix `if` nodes when outside of a block and with an empty body + +# 3.4.3 (2022-09-28) + + * Fix a security issue on filesystem loader (possibility to load a template outside a configured directory) + +# 3.4.2 (2022-08-12) + + * Allow inherited magic method to still run with calling class + * Fix CallExpression::reflectCallable() throwing TypeError + * Fix typo in naming (currency_code) + +# 3.4.1 (2022-05-17) + +* Fix optimizing non-public named closures + +# 3.4.0 (2022-05-22) + + * Add support for named closures + +# 3.3.10 (2022-04-06) + + * Enable bytecode invalidation when auto_reload is enabled + +# 3.3.9 (2022-03-25) + + * Fix custom escapers when using multiple Twig environments + * Add support for "constant('class', object)" + * Do not reuse internally generated variable names during parsing + +# 3.3.8 (2022-02-04) + + * Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter + * Fix deprecation notice on `round` + * Fix call to deprecated `convertToHtml` method + +# 3.3.7 (2022-01-03) + +* Allow more null support when Twig expects a string (for better 8.1 support) +* Only use Commonmark extensions if markdown enabled + +# 3.3.6 (2022-01-03) + +* Only use Commonmark extensions if markdown enabled + +# 3.3.5 (2022-01-03) + +* Allow CommonMark extensions to easily be added +* Allow null when Twig expects a string (for better 8.1 support) +* Make some performance optimizations +* Allow Symfony translation contract v3+ + +# 3.3.4 (2021-11-25) + + * Bump minimum supported Symfony component versions + * Fix a deprecated message + +# 3.3.3 (2021-09-17) + + * Allow Symfony 6 + * Improve compatibility with PHP 8.1 + * Explicitly specify the encoding for mb_ord in JS escaper + +# 3.3.2 (2021-05-16) + + * Revert "Throw a proper exception when a template name is an absolute path (as it has never been supported)" + +# 3.3.1 (2021-05-12) + + * Fix PHP 8.1 compatibility + * Throw a proper exception when a template name is an absolute path (as it has never been supported) + +# 3.3.0 (2021-02-08) + + * Fix macro calls in a "cache" tag + * Add the slug filter + * Allow extra bundle to be compatible with Twig 2 + +# 3.2.1 (2021-01-05) + + * Fix extra bundle compat with older versions of Symfony + +# 3.2.0 (2021-01-05) + + * Add the Cache extension in the "extra" repositories: "cache" tag + * Add "registerUndefinedTokenParserCallback" + * Mark built-in node visitors as @internal + * Fix "odd" not working for negative numbers + +# 3.1.1 (2020-10-27) + + * Fix "include(template_from_string())" + +# 3.1.0 (2020-10-21) + + * Fix sandbox support when using "include(template_from_string())" + * Make round brackets optional for one argument tests like "same as" or "divisible by" + * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } + +# 3.0.5 (2020-08-05) + + * Fix twig_compare w.r.t. whitespace trimming + * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag + * Fix a regression when not using a space before an operator + * Restrict callables to closures in filters + * Allow trailing commas in argument lists (in calls as well as definitions) + +# 3.0.4 (2020-07-05) + + * Fix comparison operators + * Fix options not taken into account when using "Michelf\MarkdownExtra" + * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument + * Throw exception in case non-Traversable data is passed to "filter" + * Fix context optimization on PHP 7.4 + * Fix PHP 8 compatibility + * Fix ambiguous syntax parsing + +# 3.0.3 (2020-02-11) + + * Add a check to ensure that iconv() is defined + +# 3.0.2 (2020-02-11) + + * Avoid exceptions when an intl resource is not found + * Fix implementation of case-insensitivity for method names + +# 3.0.1 (2019-12-28) + + * fixed Symfony 5.0 support for the HTML extra extension + +# 3.0.0 (2019-11-15) + + * fixed number formatter in Intl extra extension when using a formatter prototype + +# 3.0.0-BETA1 (2019-11-11) + + * removed the "if" condition support on the "for" tag + * made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats + * removed the "filter" tag + * added type hints everywhere + * changed Environment::resolveTemplate() to always return a TemplateWrapper instance + * removed Template::__toString() + * removed Parser::isReservedMacroName() + * removed SanboxedPrintNode + * removed Node::setTemplateName() + * made classes marked as "@final" final + * removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface + * removed the "spaceless" tag + * removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass() + * removed the "base_template_class" option on Twig\Environment + * bumped minimum PHP version to 7.2 + * removed PSR-0 classes diff --git a/vendor/twig/twig/LICENSE b/vendor/twig/twig/LICENSE new file mode 100644 index 0000000..fd8234e --- /dev/null +++ b/vendor/twig/twig/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009-present by the Twig Team. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Twig nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/twig/twig/README.rst b/vendor/twig/twig/README.rst new file mode 100644 index 0000000..7bf8c67 --- /dev/null +++ b/vendor/twig/twig/README.rst @@ -0,0 +1,23 @@ +Twig, the flexible, fast, and secure template language for PHP +============================================================== + +Twig is a template language for PHP. + +Twig uses a syntax similar to the Django and Jinja template languages which +inspired the Twig runtime environment. + +Sponsors +-------- + +.. raw:: html + + + Blackfire.io + + +More Information +---------------- + +Read the `documentation`_ for more information. + +.. _documentation: https://twig.symfony.com/documentation diff --git a/vendor/twig/twig/composer.json b/vendor/twig/twig/composer.json new file mode 100644 index 0000000..e0c3e6c --- /dev/null +++ b/vendor/twig/twig/composer.json @@ -0,0 +1,53 @@ +{ + "name": "twig/twig", + "type": "library", + "description": "Twig, the flexible, fast, and secure template language for PHP", + "keywords": ["templating"], + "homepage": "https://twig.symfony.com", + "license": "BSD-3-Clause", + "minimum-stability": "dev", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0", + "psr/container": "^1.0|^2.0" + }, + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4" : { + "Twig\\" : "src/" + } + }, + "autoload-dev": { + "psr-4" : { + "Twig\\Tests\\" : "tests/" + } + } +} diff --git a/vendor/twig/twig/src/AbstractTwigCallable.php b/vendor/twig/twig/src/AbstractTwigCallable.php new file mode 100644 index 0000000..f671843 --- /dev/null +++ b/vendor/twig/twig/src/AbstractTwigCallable.php @@ -0,0 +1,136 @@ + + */ +abstract class AbstractTwigCallable implements TwigCallableInterface +{ + protected $options; + + private $name; + private $dynamicName; + private $callable; + private $arguments; + + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $this->dynamicName = $name; + $this->callable = $callable; + $this->arguments = []; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'needs_charset' => false, + 'is_variadic' => false, + 'deprecated' => false, + 'deprecating_package' => '', + 'alternative' => null, + ], $options); + } + + public function __toString(): string + { + return \sprintf('%s(%s)', static::class, $this->name); + } + + public function getName(): string + { + return $this->name; + } + + public function getDynamicName(): string + { + return $this->dynamicName; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function needsCharset(): bool + { + return $this->options['needs_charset']; + } + + public function needsEnvironment(): bool + { + return $this->options['needs_environment']; + } + + public function needsContext(): bool + { + return $this->options['needs_context']; + } + + public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self + { + $new = clone $this; + $new->name = $name; + $new->dynamicName = $dynamicName; + $new->arguments = $arguments; + + return $new; + } + + /** + * @deprecated since Twig 3.12, use withDynamicArguments() instead + */ + public function setArguments(array $arguments): void + { + trigger_deprecation('twig/twig', '3.12', 'The "%s::setArguments()" method is deprecated, use "%s::withDynamicArguments()" instead.', static::class, static::class); + + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function isVariadic(): bool + { + return $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatingPackage(): string + { + return $this->options['deprecating_package']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } + + public function getMinimalNumberOfRequiredArguments(): int + { + return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + \count($this->arguments); + } +} diff --git a/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php b/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php new file mode 100644 index 0000000..ffd8cff --- /dev/null +++ b/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php @@ -0,0 +1,20 @@ + + */ +interface CacheInterface +{ + /** + * Generates a cache key for the given template class name. + */ + public function generateKey(string $name, string $className): string; + + /** + * Writes the compiled template to cache. + * + * @param string $content The template representation as a PHP class + */ + public function write(string $key, string $content): void; + + /** + * Loads a template from the cache. + */ + public function load(string $key): void; + + /** + * Returns the modification timestamp of a key. + */ + public function getTimestamp(string $key): int; +} diff --git a/vendor/twig/twig/src/Cache/ChainCache.php b/vendor/twig/twig/src/Cache/ChainCache.php new file mode 100644 index 0000000..c94afdb --- /dev/null +++ b/vendor/twig/twig/src/Cache/ChainCache.php @@ -0,0 +1,79 @@ + + */ +final class ChainCache implements CacheInterface +{ + /** + * @param iterable $caches The ordered list of caches used to store and fetch cached items + */ + public function __construct( + private iterable $caches, + ) { + } + + public function generateKey(string $name, string $className): string + { + return $className.'#'.$name; + } + + public function write(string $key, string $content): void + { + $splitKey = $this->splitKey($key); + + foreach ($this->caches as $cache) { + $cache->write($cache->generateKey(...$splitKey), $content); + } + } + + public function load(string $key): void + { + [$name, $className] = $this->splitKey($key); + + foreach ($this->caches as $cache) { + $cache->load($cache->generateKey($name, $className)); + + if (class_exists($className, false)) { + break; + } + } + } + + public function getTimestamp(string $key): int + { + $splitKey = $this->splitKey($key); + + foreach ($this->caches as $cache) { + if (0 < $timestamp = $cache->getTimestamp($cache->generateKey(...$splitKey))) { + return $timestamp; + } + } + + return 0; + } + + /** + * @return string[] + */ + private function splitKey(string $key): array + { + return array_reverse(explode('#', $key, 2)); + } +} diff --git a/vendor/twig/twig/src/Cache/FilesystemCache.php b/vendor/twig/twig/src/Cache/FilesystemCache.php new file mode 100644 index 0000000..2e79fac --- /dev/null +++ b/vendor/twig/twig/src/Cache/FilesystemCache.php @@ -0,0 +1,87 @@ + + */ +class FilesystemCache implements CacheInterface +{ + public const FORCE_BYTECODE_INVALIDATION = 1; + + private $directory; + private $options; + + public function __construct(string $directory, int $options = 0) + { + $this->directory = rtrim($directory, '\/').'/'; + $this->options = $options; + } + + public function generateKey(string $name, string $className): string + { + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className); + + return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; + } + + public function load(string $key): void + { + if (is_file($key)) { + @include_once $key; + } + } + + public function write(string $key, string $content): void + { + $dir = \dirname($key); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true)) { + clearstatcache(true, $dir); + if (!is_dir($dir)) { + throw new \RuntimeException(\sprintf('Unable to create the cache directory (%s).', $dir)); + } + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(\sprintf('Unable to write in the cache directory (%s).', $dir)); + } + + $tmpFile = tempnam($dir, basename($key)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { + @chmod($key, 0666 & ~umask()); + + if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { + // Compile cached file into bytecode cache + if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { + @opcache_invalidate($key, true); + } elseif (\function_exists('apc_compile_file')) { + apc_compile_file($key); + } + } + + return; + } + + throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $key)); + } + + public function getTimestamp(string $key): int + { + if (!is_file($key)) { + return 0; + } + + return (int) @filemtime($key); + } +} diff --git a/vendor/twig/twig/src/Cache/NullCache.php b/vendor/twig/twig/src/Cache/NullCache.php new file mode 100644 index 0000000..8d20d59 --- /dev/null +++ b/vendor/twig/twig/src/Cache/NullCache.php @@ -0,0 +1,38 @@ + + */ +final class NullCache implements CacheInterface +{ + public function generateKey(string $name, string $className): string + { + return ''; + } + + public function write(string $key, string $content): void + { + } + + public function load(string $key): void + { + } + + public function getTimestamp(string $key): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php b/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php new file mode 100644 index 0000000..3ba6514 --- /dev/null +++ b/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php @@ -0,0 +1,25 @@ + + */ +class ReadOnlyFilesystemCache extends FilesystemCache +{ + public function write(string $key, string $content): void + { + // Do nothing with the content, it's a read-only filesystem. + } +} diff --git a/vendor/twig/twig/src/Compiler.php b/vendor/twig/twig/src/Compiler.php new file mode 100644 index 0000000..1a43aa7 --- /dev/null +++ b/vendor/twig/twig/src/Compiler.php @@ -0,0 +1,257 @@ + + */ +class Compiler +{ + private $lastLine; + private $source; + private $indentation; + private $debugInfo = []; + private $sourceOffset; + private $sourceLine; + private $varNameSalt = 0; + private $didUseEcho = false; + private $didUseEchoStack = []; + + public function __construct( + private Environment $env, + ) { + } + + public function getEnvironment(): Environment + { + return $this->env; + } + + public function getSource(): string + { + return $this->source; + } + + /** + * @return $this + */ + public function reset(int $indentation = 0) + { + $this->lastLine = null; + $this->source = ''; + $this->debugInfo = []; + $this->sourceOffset = 0; + // source code starts at 1 (as we then increment it when we encounter new lines) + $this->sourceLine = 1; + $this->indentation = $indentation; + $this->varNameSalt = 0; + + return $this; + } + + /** + * @return $this + */ + public function compile(Node $node, int $indentation = 0) + { + $this->reset($indentation); + $this->didUseEchoStack[] = $this->didUseEcho; + + try { + $this->didUseEcho = false; + $node->compile($this); + + if ($this->didUseEcho) { + trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[YieldReady].', $this->didUseEcho, \get_class($node)); + } + + return $this; + } finally { + $this->didUseEcho = array_pop($this->didUseEchoStack); + } + } + + /** + * @return $this + */ + public function subcompile(Node $node, bool $raw = true) + { + if (!$raw) { + $this->source .= str_repeat(' ', $this->indentation * 4); + } + + $this->didUseEchoStack[] = $this->didUseEcho; + + try { + $this->didUseEcho = false; + $node->compile($this); + + if ($this->didUseEcho) { + trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[YieldReady].', $this->didUseEcho, \get_class($node)); + } + + return $this; + } finally { + $this->didUseEcho = array_pop($this->didUseEchoStack); + } + } + + /** + * Adds a raw string to the compiled code. + * + * @return $this + */ + public function raw(string $string) + { + $this->checkForEcho($string); + $this->source .= $string; + + return $this; + } + + /** + * Writes a string to the compiled code by adding indentation. + * + * @return $this + */ + public function write(...$strings) + { + foreach ($strings as $string) { + $this->checkForEcho($string); + $this->source .= str_repeat(' ', $this->indentation * 4).$string; + } + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @return $this + */ + public function string(string $value) + { + $this->source .= \sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @return $this + */ + public function repr($value) + { + if (\is_int($value) || \is_float($value)) { + if (false !== $locale = setlocale(\LC_NUMERIC, '0')) { + setlocale(\LC_NUMERIC, 'C'); + } + + $this->raw(var_export($value, true)); + + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + } elseif (null === $value) { + $this->raw('null'); + } elseif (\is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } elseif (\is_array($value)) { + $this->raw('array('); + $first = true; + foreach ($value as $key => $v) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($v); + } + $this->raw(')'); + } else { + $this->string($value); + } + + return $this; + } + + /** + * @return $this + */ + public function addDebugInfo(Node $node) + { + if ($node->getTemplateLine() != $this->lastLine) { + $this->write(\sprintf("// line %d\n", $node->getTemplateLine())); + + $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); + $this->sourceOffset = \strlen($this->source); + $this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); + + $this->lastLine = $node->getTemplateLine(); + } + + return $this; + } + + public function getDebugInfo(): array + { + ksort($this->debugInfo); + + return $this->debugInfo; + } + + /** + * @return $this + */ + public function indent(int $step = 1) + { + $this->indentation += $step; + + return $this; + } + + /** + * @return $this + * + * @throws \LogicException When trying to outdent too much so the indentation would become negative + */ + public function outdent(int $step = 1) + { + // can't outdent by more steps than the current indentation level + if ($this->indentation < $step) { + throw new \LogicException('Unable to call outdent() as the indentation would become negative.'); + } + + $this->indentation -= $step; + + return $this; + } + + public function getVarName(): string + { + return \sprintf('__internal_compile_%d', $this->varNameSalt++); + } + + private function checkForEcho(string $string): void + { + if ($this->didUseEcho) { + return; + } + + $this->didUseEcho = preg_match('/^\s*+(echo|print)\b/', $string, $m) ? $m[1] : false; + } +} diff --git a/vendor/twig/twig/src/Environment.php b/vendor/twig/twig/src/Environment.php new file mode 100644 index 0000000..24e55e9 --- /dev/null +++ b/vendor/twig/twig/src/Environment.php @@ -0,0 +1,881 @@ + + */ +class Environment +{ + public const VERSION = '3.14.0'; + public const VERSION_ID = 31400; + public const MAJOR_VERSION = 3; + public const MINOR_VERSION = 14; + public const RELEASE_VERSION = 0; + public const EXTRA_VERSION = ''; + + private $charset; + private $loader; + private $debug; + private $autoReload; + private $cache; + private $lexer; + private $parser; + private $compiler; + /** @var array */ + private $globals = []; + private $resolvedGlobals; + private $loadedTemplates; + private $strictVariables; + private $originalCache; + private $extensionSet; + private $runtimeLoaders = []; + private $runtimes = []; + private $optionsHash; + /** @var bool */ + private $useYield; + private $defaultRuntimeLoader; + + /** + * Constructor. + * + * Available options: + * + * * debug: When set to true, it automatically set "auto_reload" to true as + * well (default to false). + * + * * charset: The charset used by the templates (default to UTF-8). + * + * * cache: An absolute path where to store the compiled templates, + * a \Twig\Cache\CacheInterface implementation, + * or false to disable compilation cache (default). + * + * * auto_reload: Whether to reload the template if the original source changed. + * If you don't provide the auto_reload option, it will be + * determined automatically based on the debug value. + * + * * strict_variables: Whether to ignore invalid variables in templates + * (default to false). + * + * * autoescape: Whether to enable auto-escaping (default to html): + * * false: disable auto-escaping + * * html, js: set the autoescaping to one of the supported strategies + * * name: set the autoescaping strategy based on the template name extension + * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" + * + * * optimizations: A flag that indicates which optimizations to apply + * (default to -1 which means that all optimizations are enabled; + * set it to 0 to disable). + * + * * use_yield: true: forces templates to exclusively use "yield" instead of "echo" (all extensions must be yield ready) + * false (default): allows templates to use a mix of "yield" and "echo" calls to allow for a progressive migration + * Switch to "true" when possible as this will be the only supported mode in Twig 4.0 + */ + public function __construct(LoaderInterface $loader, array $options = []) + { + $this->setLoader($loader); + + $options = array_merge([ + 'debug' => false, + 'charset' => 'UTF-8', + 'strict_variables' => false, + 'autoescape' => 'html', + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, + 'use_yield' => false, + ], $options); + + $this->useYield = (bool) $options['use_yield']; + $this->debug = (bool) $options['debug']; + $this->setCharset($options['charset'] ?? 'UTF-8'); + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->strictVariables = (bool) $options['strict_variables']; + $this->setCache($options['cache']); + $this->extensionSet = new ExtensionSet(); + $this->defaultRuntimeLoader = new FactoryRuntimeLoader([ + EscaperRuntime::class => function () { return new EscaperRuntime($this->charset); }, + ]); + + $this->addExtension(new CoreExtension()); + $escaperExt = new EscaperExtension($options['autoescape']); + $escaperExt->setEnvironment($this, false); + $this->addExtension($escaperExt); + if (\PHP_VERSION_ID >= 80000) { + $this->addExtension(new YieldNotReadyExtension($this->useYield)); + } + $this->addExtension(new OptimizerExtension($options['optimizations'])); + } + + /** + * @internal + */ + public function useYield(): bool + { + return $this->useYield; + } + + /** + * Enables debugging mode. + */ + public function enableDebug() + { + $this->debug = true; + $this->updateOptionsHash(); + } + + /** + * Disables debugging mode. + */ + public function disableDebug() + { + $this->debug = false; + $this->updateOptionsHash(); + } + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug() + { + return $this->debug; + } + + /** + * Enables the auto_reload option. + */ + public function enableAutoReload() + { + $this->autoReload = true; + } + + /** + * Disables the auto_reload option. + */ + public function disableAutoReload() + { + $this->autoReload = false; + } + + /** + * Checks if the auto_reload option is enabled. + * + * @return bool true if auto_reload is enabled, false otherwise + */ + public function isAutoReload() + { + return $this->autoReload; + } + + /** + * Enables the strict_variables option. + */ + public function enableStrictVariables() + { + $this->strictVariables = true; + $this->updateOptionsHash(); + } + + /** + * Disables the strict_variables option. + */ + public function disableStrictVariables() + { + $this->strictVariables = false; + $this->updateOptionsHash(); + } + + /** + * Checks if the strict_variables option is enabled. + * + * @return bool true if strict_variables is enabled, false otherwise + */ + public function isStrictVariables() + { + return $this->strictVariables; + } + + /** + * Gets the current cache implementation. + * + * @param bool $original Whether to return the original cache option or the real cache instance + * + * @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function getCache($original = true) + { + return $original ? $this->originalCache : $this->cache; + } + + /** + * Sets the current cache implementation. + * + * @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function setCache($cache) + { + if (\is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new FilesystemCache($cache, $this->autoReload ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new NullCache(); + } elseif ($cache instanceof CacheInterface) { + $this->originalCache = $this->cache = $cache; + } else { + throw new \LogicException('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.'); + } + } + + /** + * Gets the template class associated with the given string. + * + * The generated template class is based on the following parameters: + * + * * The cache key for the given template; + * * The currently enabled extensions; + * * PHP version; + * * Twig version; + * * Options with what environment was created. + * + * @param string $name The name for which to calculate the template class name + * @param int|null $index The index if it is an embedded template + * + * @internal + */ + public function getTemplateClass(string $name, ?int $index = null): string + { + $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; + + return '__TwigTemplate_'.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index); + } + + /** + * Renders a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function render($name, array $context = []): string + { + return $this->load($name)->render($context); + } + + /** + * Displays a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function display($name, array $context = []): void + { + $this->load($name)->display($context); + } + + /** + * Loads a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + */ + public function load($name): TemplateWrapper + { + if ($name instanceof TemplateWrapper) { + return $name; + } + if ($name instanceof Template) { + trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', self::class, __METHOD__); + + return $name; + } + + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + } + + /** + * Loads a template internal representation. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The template name + * @param int|null $index The index if it is an embedded template + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + * + * @internal + */ + public function loadTemplate(string $cls, string $name, ?int $index = null): Template + { + $mainCls = $cls; + if (null !== $index) { + $cls .= '___'.$index; + } + + if (isset($this->loadedTemplates[$cls])) { + return $this->loadedTemplates[$cls]; + } + + if (!class_exists($cls, false)) { + $key = $this->cache->generateKey($name, $mainCls); + + if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { + $this->cache->load($key); + } + + if (!class_exists($cls, false)) { + $source = $this->getLoader()->getSourceContext($name); + $content = $this->compileSource($source); + $this->cache->write($key, $content); + $this->cache->load($key); + + if (!class_exists($mainCls, false)) { + /* Last line of defense if either $this->bcWriteCacheFile was used, + * $this->cache is implemented as a no-op or we have a race condition + * where the cache was cleared between the above calls to write to and load from + * the cache. + */ + eval('?>'.$content); + } + + if (!class_exists($cls, false)) { + throw new RuntimeError(\sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); + } + } + } + + $this->extensionSet->initRuntime(); + + return $this->loadedTemplates[$cls] = new $cls($this); + } + + /** + * Creates a template from source. + * + * This method should not be used as a generic way to load templates. + * + * @param string $template The template source + * @param string|null $name An optional name of the template to be used in error messages + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + */ + public function createTemplate(string $template, ?string $name = null): TemplateWrapper + { + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $template, false); + if (null !== $name) { + $name = \sprintf('%s (string template %s)', $name, $hash); + } else { + $name = \sprintf('__string_template__%s', $hash); + } + + $loader = new ChainLoader([ + new ArrayLoader([$name => $template]), + $current = $this->getLoader(), + ]); + + $this->setLoader($loader); + try { + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + } finally { + $this->setLoader($current); + } + } + + /** + * Returns true if the template is still fresh. + * + * Besides checking the loader for freshness information, + * this method also checks if the enabled extensions have + * not changed. + * + * @param int $time The last modification time of the cached template + */ + public function isTemplateFresh(string $name, int $time): bool + { + return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time); + } + + /** + * Tries to load a template consecutively from an array. + * + * Similar to load() but it also accepts instances of \Twig\TemplateWrapper + * and an array of templates where each is tried to be loaded. + * + * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively + * + * @throws LoaderError When none of the templates can be found + * @throws SyntaxError When an error occurred during compilation + */ + public function resolveTemplate($names): TemplateWrapper + { + if (!\is_array($names)) { + return $this->load($names); + } + + $count = \count($names); + foreach ($names as $name) { + if ($name instanceof Template) { + trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', Template::class, __METHOD__); + + return new TemplateWrapper($this, $name); + } + if ($name instanceof TemplateWrapper) { + return $name; + } + + if (1 !== $count && !$this->getLoader()->exists($name)) { + continue; + } + + return $this->load($name); + } + + throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + } + + public function setLexer(Lexer $lexer) + { + $this->lexer = $lexer; + } + + /** + * @throws SyntaxError When the code is syntactically wrong + */ + public function tokenize(Source $source): TokenStream + { + if (null === $this->lexer) { + $this->lexer = new Lexer($this); + } + + return $this->lexer->tokenize($source); + } + + public function setParser(Parser $parser) + { + $this->parser = $parser; + } + + /** + * Converts a token stream to a node tree. + * + * @throws SyntaxError When the token stream is syntactically or semantically wrong + */ + public function parse(TokenStream $stream): ModuleNode + { + if (null === $this->parser) { + $this->parser = new Parser($this); + } + + return $this->parser->parse($stream); + } + + public function setCompiler(Compiler $compiler) + { + $this->compiler = $compiler; + } + + /** + * Compiles a node and returns the PHP code. + */ + public function compile(Node $node): string + { + if (null === $this->compiler) { + $this->compiler = new Compiler($this); + } + + return $this->compiler->compile($node)->getSource(); + } + + /** + * Compiles a template source code. + * + * @throws SyntaxError When there was an error during tokenizing, parsing or compiling + */ + public function compileSource(Source $source): string + { + try { + return $this->compile($this->parse($this->tokenize($source))); + } catch (Error $e) { + $e->setSourceContext($source); + throw $e; + } catch (\Exception $e) { + throw new SyntaxError(\sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); + } + } + + public function setLoader(LoaderInterface $loader) + { + $this->loader = $loader; + } + + public function getLoader(): LoaderInterface + { + return $this->loader; + } + + public function setCharset(string $charset) + { + if ('UTF8' === $charset = strtoupper($charset ?: '')) { + // iconv on Windows requires "UTF-8" instead of "UTF8" + $charset = 'UTF-8'; + } + + $this->charset = $charset; + } + + public function getCharset(): string + { + return $this->charset; + } + + public function hasExtension(string $class): bool + { + return $this->extensionSet->hasExtension($class); + } + + public function addRuntimeLoader(RuntimeLoaderInterface $loader) + { + $this->runtimeLoaders[] = $loader; + } + + /** + * @template TExtension of ExtensionInterface + * + * @param class-string $class + * + * @return TExtension + */ + public function getExtension(string $class): ExtensionInterface + { + return $this->extensionSet->getExtension($class); + } + + /** + * Returns the runtime implementation of a Twig element (filter/function/tag/test). + * + * @template TRuntime of object + * + * @param class-string $class A runtime class name + * + * @return TRuntime The runtime implementation + * + * @throws RuntimeError When the template cannot be found + */ + public function getRuntime(string $class) + { + if (isset($this->runtimes[$class])) { + return $this->runtimes[$class]; + } + + foreach ($this->runtimeLoaders as $loader) { + if (null !== $runtime = $loader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + } + + if (null !== $runtime = $this->defaultRuntimeLoader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + + throw new RuntimeError(\sprintf('Unable to load the "%s" runtime.', $class)); + } + + public function addExtension(ExtensionInterface $extension) + { + $this->extensionSet->addExtension($extension); + $this->updateOptionsHash(); + } + + /** + * @param ExtensionInterface[] $extensions An array of extensions + */ + public function setExtensions(array $extensions) + { + $this->extensionSet->setExtensions($extensions); + $this->updateOptionsHash(); + } + + /** + * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) + */ + public function getExtensions(): array + { + return $this->extensionSet->getExtensions(); + } + + public function addTokenParser(TokenParserInterface $parser) + { + $this->extensionSet->addTokenParser($parser); + } + + /** + * @return TokenParserInterface[] + * + * @internal + */ + public function getTokenParsers(): array + { + return $this->extensionSet->getTokenParsers(); + } + + /** + * @internal + */ + public function getTokenParser(string $name): ?TokenParserInterface + { + return $this->extensionSet->getTokenParser($name); + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedTokenParserCallback($callable); + } + + public function addNodeVisitor(NodeVisitorInterface $visitor) + { + $this->extensionSet->addNodeVisitor($visitor); + } + + /** + * @return NodeVisitorInterface[] + * + * @internal + */ + public function getNodeVisitors(): array + { + return $this->extensionSet->getNodeVisitors(); + } + + public function addFilter(TwigFilter $filter) + { + $this->extensionSet->addFilter($filter); + } + + /** + * @internal + */ + public function getFilter(string $name): ?TwigFilter + { + return $this->extensionSet->getFilter($name); + } + + public function registerUndefinedFilterCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedFilterCallback($callable); + } + + /** + * Gets the registered Filters. + * + * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. + * + * @return TwigFilter[] + * + * @see registerUndefinedFilterCallback + * + * @internal + */ + public function getFilters(): array + { + return $this->extensionSet->getFilters(); + } + + public function addTest(TwigTest $test) + { + $this->extensionSet->addTest($test); + } + + /** + * @return TwigTest[] + * + * @internal + */ + public function getTests(): array + { + return $this->extensionSet->getTests(); + } + + /** + * @internal + */ + public function getTest(string $name): ?TwigTest + { + return $this->extensionSet->getTest($name); + } + + public function addFunction(TwigFunction $function) + { + $this->extensionSet->addFunction($function); + } + + /** + * @internal + */ + public function getFunction(string $name): ?TwigFunction + { + return $this->extensionSet->getFunction($name); + } + + public function registerUndefinedFunctionCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedFunctionCallback($callable); + } + + /** + * Gets registered functions. + * + * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. + * + * @return TwigFunction[] + * + * @see registerUndefinedFunctionCallback + * + * @internal + */ + public function getFunctions(): array + { + return $this->extensionSet->getFunctions(); + } + + /** + * Registers a Global. + * + * New globals can be added before compiling or rendering a template; + * but after, you can only update existing globals. + * + * @param mixed $value The global value + */ + public function addGlobal(string $name, $value) + { + if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) { + throw new \LogicException(\sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + } + + if (null !== $this->resolvedGlobals) { + $this->resolvedGlobals[$name] = $value; + } else { + $this->globals[$name] = $value; + } + } + + /** + * @internal + * + * @return array + */ + public function getGlobals(): array + { + if ($this->extensionSet->isInitialized()) { + if (null === $this->resolvedGlobals) { + $this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals); + } + + return $this->resolvedGlobals; + } + + return array_merge($this->extensionSet->getGlobals(), $this->globals); + } + + public function resetGlobals(): void + { + $this->resolvedGlobals = null; + $this->extensionSet->resetGlobals(); + } + + /** + * @deprecated since Twig 3.14 + */ + public function mergeGlobals(array $context): array + { + trigger_deprecation('twig/twig', '3.14', 'The "%s" method is deprecated.', __METHOD__); + + return $context + $this->getGlobals(); + } + + /** + * @internal + * + * @return array}> + */ + public function getUnaryOperators(): array + { + return $this->extensionSet->getUnaryOperators(); + } + + /** + * @internal + * + * @return array, associativity: ExpressionParser::OPERATOR_*}> + */ + public function getBinaryOperators(): array + { + return $this->extensionSet->getBinaryOperators(); + } + + private function updateOptionsHash(): void + { + $this->optionsHash = implode(':', [ + $this->extensionSet->getSignature(), + \PHP_MAJOR_VERSION, + \PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + (int) $this->strictVariables, + $this->useYield ? '1' : '0', + ]); + } +} diff --git a/vendor/twig/twig/src/Error/Error.php b/vendor/twig/twig/src/Error/Error.php new file mode 100644 index 0000000..61c309f --- /dev/null +++ b/vendor/twig/twig/src/Error/Error.php @@ -0,0 +1,227 @@ + + */ +class Error extends \Exception +{ + private $lineno; + private $name; + private $rawMessage; + private $sourcePath; + private $sourceCode; + + /** + * Constructor. + * + * By default, automatic guessing is enabled. + * + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Source|null $source The source context where the error occurred + */ + public function __construct(string $message, int $lineno = -1, ?Source $source = null, ?\Throwable $previous = null) + { + parent::__construct('', 0, $previous); + + if (null === $source) { + $name = null; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } + + $this->lineno = $lineno; + $this->name = $name; + $this->rawMessage = $message; + $this->updateRepr(); + } + + public function getRawMessage(): string + { + return $this->rawMessage; + } + + public function getTemplateLine(): int + { + return $this->lineno; + } + + public function setTemplateLine(int $lineno): void + { + $this->lineno = $lineno; + + $this->updateRepr(); + } + + public function getSourceContext(): ?Source + { + return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; + } + + public function setSourceContext(?Source $source = null): void + { + if (null === $source) { + $this->sourceCode = $this->name = $this->sourcePath = null; + } else { + $this->sourceCode = $source->getCode(); + $this->name = $source->getName(); + $this->sourcePath = $source->getPath(); + } + + $this->updateRepr(); + } + + public function guess(): void + { + $this->guessTemplateInfo(); + $this->updateRepr(); + } + + public function appendMessage($rawMessage): void + { + $this->rawMessage .= $rawMessage; + $this->updateRepr(); + } + + private function updateRepr(): void + { + $this->message = $this->rawMessage; + + if ($this->sourcePath && $this->lineno > 0) { + $this->file = $this->sourcePath; + $this->line = $this->lineno; + + return; + } + + $dot = false; + if (str_ends_with($this->message, '.')) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + $questionMark = false; + if (str_ends_with($this->message, '?')) { + $this->message = substr($this->message, 0, -1); + $questionMark = true; + } + + if ($this->name) { + if (\is_string($this->name) || $this->name instanceof \Stringable) { + $name = \sprintf('"%s"', $this->name); + } else { + $name = json_encode($this->name); + } + $this->message .= \sprintf(' in %s', $name); + } + + if ($this->lineno && $this->lineno >= 0) { + $this->message .= \sprintf(' at line %d', $this->lineno); + } + + if ($dot) { + $this->message .= '.'; + } + + if ($questionMark) { + $this->message .= '?'; + } + } + + private function guessTemplateInfo(): void + { + $template = null; + $templateClass = null; + + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT); + foreach ($backtrace as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Template) { + $currentClass = \get_class($trace['object']); + $isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass); + if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + $template = $trace['object']; + $templateClass = \get_class($trace['object']); + } + } + } + + // update template name + if (null !== $template && null === $this->name) { + $this->name = $template->getTemplateName(); + } + + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + + if (null === $template || $this->lineno > -1) { + return; + } + + $r = new \ReflectionObject($template); + $file = $r->getFileName(); + + $exceptions = [$e = $this]; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + while ($e = array_pop($exceptions)) { + $traces = $e->getTrace(); + array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]); + + while ($trace = array_shift($traces)) { + if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { + continue; + } + + foreach ($template->getDebugInfo() as $codeLine => $templateLine) { + if ($codeLine <= $trace['line']) { + // update template line + $this->lineno = $templateLine; + + return; + } + } + } + } + } +} diff --git a/vendor/twig/twig/src/Error/LoaderError.php b/vendor/twig/twig/src/Error/LoaderError.php new file mode 100644 index 0000000..7c8c23c --- /dev/null +++ b/vendor/twig/twig/src/Error/LoaderError.php @@ -0,0 +1,21 @@ + + */ +class LoaderError extends Error +{ +} diff --git a/vendor/twig/twig/src/Error/RuntimeError.php b/vendor/twig/twig/src/Error/RuntimeError.php new file mode 100644 index 0000000..f6b8476 --- /dev/null +++ b/vendor/twig/twig/src/Error/RuntimeError.php @@ -0,0 +1,22 @@ + + */ +class RuntimeError extends Error +{ +} diff --git a/vendor/twig/twig/src/Error/SyntaxError.php b/vendor/twig/twig/src/Error/SyntaxError.php new file mode 100644 index 0000000..841b653 --- /dev/null +++ b/vendor/twig/twig/src/Error/SyntaxError.php @@ -0,0 +1,46 @@ + + */ +class SyntaxError extends Error +{ + /** + * Tweaks the error message to include suggestions. + * + * @param string $name The original name of the item that does not exist + * @param array $items An array of possible items + */ + public function addSuggestions(string $name, array $items): void + { + $alternatives = []; + foreach ($items as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = $lev; + } + } + + if (!$alternatives) { + return; + } + + asort($alternatives); + + $this->appendMessage(\sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives)))); + } +} diff --git a/vendor/twig/twig/src/ExpressionParser.php b/vendor/twig/twig/src/ExpressionParser.php new file mode 100644 index 0000000..dc4a601 --- /dev/null +++ b/vendor/twig/twig/src/ExpressionParser.php @@ -0,0 +1,853 @@ + + */ +class ExpressionParser +{ + public const OPERATOR_LEFT = 1; + public const OPERATOR_RIGHT = 2; + + /** @var array}> */ + private $unaryOperators; + /** @var array, associativity: self::OPERATOR_*}> */ + private $binaryOperators; + private $readyNodes = []; + + public function __construct( + private Parser $parser, + private Environment $env, + ) { + $this->unaryOperators = $env->getUnaryOperators(); + $this->binaryOperators = $env->getBinaryOperators(); + } + + public function parseExpression($precedence = 0, $allowArrow = false) + { + if ($allowArrow && $arrow = $this->parseArrow()) { + return $arrow; + } + + $expr = $this->getPrimary(); + $token = $this->parser->getCurrentToken(); + while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + + if ('is not' === $token->getValue()) { + $expr = $this->parseNotTestExpression($expr); + } elseif ('is' === $token->getValue()) { + $expr = $this->parseTestExpression($expr); + } elseif (isset($op['callable'])) { + $expr = $op['callable']($this->parser, $expr); + } else { + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], true); + $class = $op['class']; + $expr = new $class($expr, $expr1, $token->getLine()); + } + + $token = $this->parser->getCurrentToken(); + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + /** + * @return ArrowFunctionExpression|null + */ + private function parseArrow() + { + $stream = $this->parser->getStream(); + + // short array syntax (one argument, no parentheses)? + if ($stream->look(1)->test(Token::ARROW_TYPE)) { + $line = $stream->getCurrent()->getLine(); + $token = $stream->expect(Token::NAME_TYPE); + $names = [new AssignNameExpression($token->getValue(), $token->getLine())]; + $stream->expect(Token::ARROW_TYPE); + + return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); + } + + // first, determine if we are parsing an arrow function by finding => (long form) + $i = 0; + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) { + return null; + } + ++$i; + while (true) { + // variable name + ++$i; + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) { + break; + } + ++$i; + } + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) { + return null; + } + ++$i; + if (!$stream->look($i)->test(Token::ARROW_TYPE)) { + return null; + } + + // yes, let's parse it properly + $token = $stream->expect(Token::PUNCTUATION_TYPE, '('); + $line = $token->getLine(); + + $names = []; + while (true) { + $token = $stream->expect(Token::NAME_TYPE); + $names[] = new AssignNameExpression($token->getValue(), $token->getLine()); + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + $stream->expect(Token::PUNCTUATION_TYPE, ')'); + $stream->expect(Token::ARROW_TYPE); + + return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); + } + + private function getPrimary(): AbstractExpression + { + $token = $this->parser->getCurrentToken(); + + if ($this->isUnary($token)) { + $operator = $this->unaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + $expr = $this->parseExpression($operator['precedence']); + $class = $operator['class']; + + return $this->parsePostfixExpression(new $class($expr, $token->getLine())); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) { + $this->parser->getStream()->next(); + $expr = $this->parseExpression(); + $this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + private function parseConditionalExpression($expr): AbstractExpression + { + while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) { + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + $expr2 = $this->parseExpression(); + if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + // Ternary operator (expr ? expr2 : expr3) + $expr3 = $this->parseExpression(); + } else { + // Ternary without else (expr ? expr2) + $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); + } + } else { + // Ternary without then (expr ?: expr3) + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); + } + + return $expr; + } + + private function isUnary(Token $token): bool + { + return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); + } + + private function isBinary(Token $token): bool + { + return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); + } + + public function parsePrimaryExpression() + { + $token = $this->parser->getCurrentToken(); + switch ($token->getType()) { + case Token::NAME_TYPE: + $this->parser->getStream()->next(); + switch ($token->getValue()) { + case 'true': + case 'TRUE': + $node = new ConstantExpression(true, $token->getLine()); + break; + + case 'false': + case 'FALSE': + $node = new ConstantExpression(false, $token->getLine()); + break; + + case 'none': + case 'NONE': + case 'null': + case 'NULL': + $node = new ConstantExpression(null, $token->getLine()); + break; + + default: + if ('(' === $this->parser->getCurrentToken()->getValue()) { + $node = $this->getFunctionNode($token->getValue(), $token->getLine()); + } else { + $node = new NameExpression($token->getValue(), $token->getLine()); + } + } + break; + + case Token::NUMBER_TYPE: + $this->parser->getStream()->next(); + $node = new ConstantExpression($token->getValue(), $token->getLine()); + break; + + case Token::STRING_TYPE: + case Token::INTERPOLATION_START_TYPE: + $node = $this->parseStringExpression(); + break; + + case Token::OPERATOR_TYPE: + if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + $node = new NameExpression($token->getValue(), $token->getLine()); + break; + } + + if (isset($this->unaryOperators[$token->getValue()])) { + $class = $this->unaryOperators[$token->getValue()]['class']; + if (!\in_array($class, [NegUnary::class, PosUnary::class])) { + throw new SyntaxError(\sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + + $this->parser->getStream()->next(); + $expr = $this->parsePrimaryExpression(); + + $node = new $class($expr, $token->getLine()); + break; + } + + // no break + default: + if ($token->test(Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseSequenceExpression(); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseMappingExpression(); + } elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { + throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } else { + throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseStringExpression() + { + $stream = $this->parser->getStream(); + + $nodes = []; + // a string cannot be followed by another string in a single expression + $nextCanBeString = true; + while (true) { + if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) { + $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); + $nextCanBeString = false; + } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) { + $nodes[] = $this->parseExpression(); + $stream->expect(Token::INTERPOLATION_END_TYPE); + $nextCanBeString = true; + } else { + break; + } + } + + $expr = array_shift($nodes); + foreach ($nodes as $node) { + $expr = new ConcatBinary($expr, $node, $node->getTemplateLine()); + } + + return $expr; + } + + /** + * @deprecated since 3.11, use parseSequenceExpression() instead + */ + public function parseArrayExpression() + { + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__); + + return $this->parseSequenceExpression(); + } + + public function parseSequenceExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Token::PUNCTUATION_TYPE, '[', 'A sequence element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + $first = false; + + if ($stream->test(Token::SPREAD_TYPE)) { + $stream->next(); + $expr = $this->parseExpression(); + $expr->setAttribute('spread', true); + $node->addElement($expr); + } else { + $node->addElement($this->parseExpression()); + } + } + $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed'); + + return $node; + } + + /** + * @deprecated since 3.11, use parseMappingExpression() instead + */ + public function parseHashExpression() + { + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__); + + return $this->parseMappingExpression(); + } + + public function parseMappingExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + if ($stream->test(Token::SPREAD_TYPE)) { + $stream->next(); + $value = $this->parseExpression(); + $value->setAttribute('spread', true); + $node->addElement($value); + continue; + } + + // a mapping key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if ($token = $stream->nextIf(Token::NAME_TYPE)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + + // {a} is a shortcut for {a:a} + if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) { + $value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine()); + $node->addElement($value, $key); + continue; + } + } elseif (($token = $stream->nextIf(Token::STRING_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + } elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + $key = $this->parseExpression(); + } else { + $current = $stream->getCurrent(); + + throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + } + + $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); + + return $node; + } + + public function parsePostfixExpression($node) + { + while (true) { + $token = $this->parser->getCurrentToken(); + if (Token::PUNCTUATION_TYPE == $token->getType()) { + if ('.' == $token->getValue() || '[' == $token->getValue()) { + $node = $this->parseSubscriptExpression($node); + } elseif ('|' == $token->getValue()) { + $node = $this->parseFilterExpression($node); + } else { + break; + } + } else { + break; + } + } + + return $node; + } + + public function getFunctionNode($name, $line) + { + if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { + $arguments = new ArrayExpression([], $line); + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + + $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); + $node->setAttribute('safe', true); + + return $node; + } + + $args = $this->parseArguments(true); + $function = $this->getFunction($name, $line); + + if ($function->getParserCallable()) { + $fakeNode = new Node(lineno: $line); + $fakeNode->setSourceContext($this->parser->getStream()->getSourceContext()); + + return ($function->getParserCallable())($this->parser, $fakeNode, $args, $line); + } + + if (!isset($this->readyNodes[$class = $function->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } + + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFunction" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + return new $class($ready ? $function : $function->getName(), $args, $line); + } + + public function parseSubscriptExpression($node) + { + $stream = $this->parser->getStream(); + $token = $stream->next(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + $type = Template::ANY_CALL; + if ('.' == $token->getValue()) { + $token = $stream->next(); + if ( + Token::NAME_TYPE == $token->getType() + || + Token::NUMBER_TYPE == $token->getType() + || + (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) + ) { + $arg = new ConstantExpression($token->getValue(), $lineno); + + if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + $type = Template::METHOD_CALL; + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + } + } else { + throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext()); + } + + if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { + $name = $arg->getAttribute('value'); + + $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); + $node->setAttribute('safe', true); + + return $node; + } + } else { + $type = Template::ARRAY_CALL; + + // slice? + $slice = false; + if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + $arg = new ConstantExpression(0, $token->getLine()); + } else { + $arg = $this->parseExpression(); + } + + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + } + + if ($slice) { + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + $length = new ConstantExpression(null, $token->getLine()); + } else { + $length = $this->parseExpression(); + } + + $filter = $this->getFilter('slice', $token->getLine()); + $arguments = new Node([$arg, $length]); + $filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine()); + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + + return $filter; + } + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + } + + return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); + } + + public function parseFilterExpression($node) + { + $this->parser->getStream()->next(); + + return $this->parseFilterExpressionRaw($node); + } + + public function parseFilterExpressionRaw($node) + { + if (\func_num_args() > 1) { + trigger_deprecation('twig/twig', '3.12', 'Passing a second argument to "%s()" is deprecated.', __METHOD__); + } + + while (true) { + $token = $this->parser->getStream()->expect(Token::NAME_TYPE); + + if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) { + $arguments = new Node(); + } else { + $arguments = $this->parseArguments(true, false, true); + } + + $filter = $this->getFilter($token->getValue(), $token->getLine()); + + $ready = true; + if (!isset($this->readyNodes[$class = $filter->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } + + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFilter" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + $node = new $class($node, $ready ? $filter : new ConstantExpression($filter->getName(), $token->getLine()), $arguments, $token->getLine()); + + if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) { + break; + } + + $this->parser->getStream()->next(); + } + + return $node; + } + + /** + * Parses arguments. + * + * @param bool $namedArguments Whether to allow named arguments or not + * @param bool $definition Whether we are parsing arguments for a function (or macro) definition + * + * @return Node + * + * @throws SyntaxError + */ + public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false) + { + $args = []; + $stream = $this->parser->getStream(); + + $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { + if (!empty($args)) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + + // if the comma above was a trailing comma, early exit the argument parse loop + if ($stream->test(Token::PUNCTUATION_TYPE, ')')) { + break; + } + } + + if ($definition) { + $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); + $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); + } else { + $value = $this->parseExpression(0, $allowArrow); + } + + $name = null; + if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) { + if (!$value instanceof NameExpression) { + throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); + } + $name = $value->getAttribute('name'); + + if ($definition) { + $value = $this->parsePrimaryExpression(); + + if (!$this->checkConstantExpression($value)) { + throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext()); + } + } else { + $value = $this->parseExpression(0, $allowArrow); + } + } + + if ($definition) { + if (null === $name) { + $name = $value->getAttribute('name'); + $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine()); + $value->setAttribute('is_implicit', true); + } + $args[$name] = $value; + } else { + if (null === $name) { + $args[] = $value; + } else { + $args[$name] = $value; + } + } + } + $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Node($args); + } + + public function parseAssignmentExpression() + { + $stream = $this->parser->getStream(); + $targets = []; + while (true) { + $token = $this->parser->getCurrentToken(); + if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + } else { + $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to'); + } + $value = $token->getValue(); + if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) { + throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); + } + $targets[] = new AssignNameExpression($value, $token->getLine()); + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Node($targets); + } + + public function parseMultitargetExpression() + { + $targets = []; + while (true) { + $targets[] = $this->parseExpression(); + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Node($targets); + } + + private function parseNotTestExpression(Node $node): NotUnary + { + return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); + } + + private function parseTestExpression(Node $node): TestExpression + { + $stream = $this->parser->getStream(); + $test = $this->getTest($node->getTemplateLine()); + + $arguments = null; + if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + $arguments = $this->parseArguments(true); + } elseif ($test->hasOneMandatoryArgument()) { + $arguments = new Node([0 => $this->parsePrimaryExpression()]); + } + + if ('defined' === $test->getName() && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { + $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + + $ready = $test instanceof TwigTest; + if (!isset($this->readyNodes[$class = $test->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } + + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigTest" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + return new $class($node, $ready ? $test : $test->getName(), $arguments, $this->parser->getCurrentToken()->getLine()); + } + + private function getTest(int $line): TwigTest + { + $stream = $this->parser->getStream(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + if ($stream->test(Token::NAME_TYPE)) { + // try 2-words tests + $name = $name.' '.$this->parser->getCurrentToken()->getValue(); + + if ($test = $this->env->getTest($name)) { + $stream->next(); + } + } else { + $test = $this->env->getTest($name); + } + + if (!$test) { + $e = new SyntaxError(\sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getTests())); + + throw $e; + } + + if ($test->isDeprecated()) { + $stream = $this->parser->getStream(); + $message = \sprintf('Twig Test "%s" is deprecated', $test->getName()); + + if ($test->getAlternative()) { + $message .= \sprintf('. Use "%s" instead', $test->getAlternative()); + } + $src = $stream->getSourceContext(); + $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); + + trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message); + } + + return $test; + } + + private function getFunction(string $name, int $line): TwigFunction + { + if (!$function = $this->env->getFunction($name)) { + $e = new SyntaxError(\sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); + + throw $e; + } + + if ($function->isDeprecated()) { + $message = \sprintf('Twig Function "%s" is deprecated', $function->getName()); + if ($function->getAlternative()) { + $message .= \sprintf('. Use "%s" instead', $function->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + + trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message); + } + + return $function; + } + + private function getFilter(string $name, int $line): TwigFilter + { + if (!$filter = $this->env->getFilter($name)) { + $e = new SyntaxError(\sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); + + throw $e; + } + + if ($filter->isDeprecated()) { + $message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName()); + if ($filter->getAlternative()) { + $message .= \sprintf('. Use "%s" instead', $filter->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + + trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message); + } + + return $filter; + } + + // checks that the node only contains "constant" elements + private function checkConstantExpression(Node $node): bool + { + if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression + || $node instanceof NegUnary || $node instanceof PosUnary + )) { + return false; + } + + foreach ($node as $n) { + if (!$this->checkConstantExpression($n)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/twig/twig/src/Extension/AbstractExtension.php b/vendor/twig/twig/src/Extension/AbstractExtension.php new file mode 100644 index 0000000..a1b083b --- /dev/null +++ b/vendor/twig/twig/src/Extension/AbstractExtension.php @@ -0,0 +1,45 @@ +dateFormats[0] = $format; + } + + if (null !== $dateIntervalFormat) { + $this->dateFormats[1] = $dateIntervalFormat; + } + } + + /** + * Gets the default format to be used by the date filter. + * + * @return array The default date format string and the default date interval format string + */ + public function getDateFormat() + { + return $this->dateFormats; + } + + /** + * Sets the default timezone to be used by the date filter. + * + * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object + */ + public function setTimezone($timezone) + { + $this->timezone = $timezone instanceof \DateTimeZone ? $timezone : new \DateTimeZone($timezone); + } + + /** + * Gets the default timezone to be used by the date filter. + * + * @return \DateTimeZone The default timezone currently in use + */ + public function getTimezone() + { + if (null === $this->timezone) { + $this->timezone = new \DateTimeZone(date_default_timezone_get()); + } + + return $this->timezone; + } + + /** + * Sets the default format to be used by the number_format filter. + * + * @param int $decimal the number of decimal places to use + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + */ + public function setNumberFormat($decimal, $decimalPoint, $thousandSep) + { + $this->numberFormat = [$decimal, $decimalPoint, $thousandSep]; + } + + /** + * Get the default format used by the number_format filter. + * + * @return array The arguments for number_format() + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + public function getTokenParsers(): array + { + return [ + new ApplyTokenParser(), + new ForTokenParser(), + new IfTokenParser(), + new ExtendsTokenParser(), + new IncludeTokenParser(), + new BlockTokenParser(), + new UseTokenParser(), + new MacroTokenParser(), + new ImportTokenParser(), + new FromTokenParser(), + new SetTokenParser(), + new TypesTokenParser(), + new FlushTokenParser(), + new DoTokenParser(), + new EmbedTokenParser(), + new WithTokenParser(), + new DeprecatedTokenParser(), + ]; + } + + public function getFilters(): array + { + return [ + // formatting filters + new TwigFilter('date', [$this, 'formatDate']), + new TwigFilter('date_modify', [$this, 'modifyDate']), + new TwigFilter('format', [self::class, 'sprintf']), + new TwigFilter('replace', [self::class, 'replace']), + new TwigFilter('number_format', [$this, 'formatNumber']), + new TwigFilter('abs', 'abs'), + new TwigFilter('round', [self::class, 'round']), + + // encoding + new TwigFilter('url_encode', [self::class, 'urlencode']), + new TwigFilter('json_encode', 'json_encode'), + new TwigFilter('convert_encoding', [self::class, 'convertEncoding']), + + // string filters + new TwigFilter('title', [self::class, 'titleCase'], ['needs_charset' => true]), + new TwigFilter('capitalize', [self::class, 'capitalize'], ['needs_charset' => true]), + new TwigFilter('upper', [self::class, 'upper'], ['needs_charset' => true]), + new TwigFilter('lower', [self::class, 'lower'], ['needs_charset' => true]), + new TwigFilter('striptags', [self::class, 'striptags']), + new TwigFilter('trim', [self::class, 'trim']), + new TwigFilter('nl2br', [self::class, 'nl2br'], ['pre_escape' => 'html', 'is_safe' => ['html']]), + new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html'], 'deprecated' => '3.12', 'deprecating_package' => 'twig/twig']), + + // array helpers + new TwigFilter('join', [self::class, 'join']), + new TwigFilter('split', [self::class, 'split'], ['needs_charset' => true]), + new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true]), + new TwigFilter('merge', [self::class, 'merge']), + new TwigFilter('batch', [self::class, 'batch']), + new TwigFilter('column', [self::class, 'column']), + new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]), + new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]), + new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]), + new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]), + + // string/array filters + new TwigFilter('reverse', [self::class, 'reverse'], ['needs_charset' => true]), + new TwigFilter('shuffle', [self::class, 'shuffle'], ['needs_charset' => true]), + new TwigFilter('length', [self::class, 'length'], ['needs_charset' => true]), + new TwigFilter('slice', [self::class, 'slice'], ['needs_charset' => true]), + new TwigFilter('first', [self::class, 'first'], ['needs_charset' => true]), + new TwigFilter('last', [self::class, 'last'], ['needs_charset' => true]), + + // iteration and runtime + new TwigFilter('default', [self::class, 'default'], ['node_class' => DefaultFilter::class]), + new TwigFilter('keys', [self::class, 'keys']), + ]; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('parent', null, ['parser_callable' => [self::class, 'parseParentFunction']]), + new TwigFunction('block', null, ['parser_callable' => [self::class, 'parseBlockFunction']]), + new TwigFunction('attribute', null, ['parser_callable' => [self::class, 'parseAttributeFunction']]), + new TwigFunction('max', 'max'), + new TwigFunction('min', 'min'), + new TwigFunction('range', 'range'), + new TwigFunction('constant', [self::class, 'constant']), + new TwigFunction('cycle', [self::class, 'cycle']), + new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]), + new TwigFunction('date', [$this, 'convertDate']), + new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), + new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]), + new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]), + ]; + } + + public function getTests(): array + { + return [ + new TwigTest('even', null, ['node_class' => EvenTest::class]), + new TwigTest('odd', null, ['node_class' => OddTest::class]), + new TwigTest('defined', null, ['node_class' => DefinedTest::class]), + new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]), + new TwigTest('none', null, ['node_class' => NullTest::class]), + new TwigTest('null', null, ['node_class' => NullTest::class]), + new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]), + new TwigTest('constant', null, ['node_class' => ConstantTest::class]), + new TwigTest('empty', [self::class, 'testEmpty']), + new TwigTest('iterable', 'is_iterable'), + new TwigTest('sequence', [self::class, 'testSequence']), + new TwigTest('mapping', [self::class, 'testMapping']), + ]; + } + + public function getNodeVisitors(): array + { + return [new MacroAutoImportNodeVisitor()]; + } + + public function getOperators(): array + { + return [ + [ + 'not' => ['precedence' => 50, 'class' => NotUnary::class], + '-' => ['precedence' => 500, 'class' => NegUnary::class], + '+' => ['precedence' => 500, 'class' => PosUnary::class], + ], + [ + 'or' => ['precedence' => 10, 'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'and' => ['precedence' => 15, 'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-or' => ['precedence' => 16, 'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-xor' => ['precedence' => 17, 'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-and' => ['precedence' => 18, 'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '==' => ['precedence' => 20, 'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '!=' => ['precedence' => 20, 'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<=>' => ['precedence' => 20, 'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<' => ['precedence' => 20, 'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>' => ['precedence' => 20, 'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>=' => ['precedence' => 20, 'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<=' => ['precedence' => 20, 'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'not in' => ['precedence' => 20, 'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'in' => ['precedence' => 20, 'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '~' => ['precedence' => 40, 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '*' => ['precedence' => 60, 'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '/' => ['precedence' => 60, 'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '//' => ['precedence' => 60, 'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '%' => ['precedence' => 60, 'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], + '??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], + ], + ]; + } + + /** + * Cycles over a sequence. + * + * @param array|\ArrayAccess $values A non-empty sequence of values + * @param positive-int $position The position of the value to return in the cycle + * + * @return mixed The value at the given position in the sequence, wrapping around as needed + * + * @internal + */ + public static function cycle($values, $position): mixed + { + if (!\is_array($values)) { + if (!$values instanceof \ArrayAccess) { + throw new RuntimeError('The "cycle" function expects an array or "ArrayAccess" as first argument.'); + } + + if (!is_countable($values)) { + // To be uncommented in 4.0 + // throw new RuntimeError('The "cycle" function expects a countable sequence as first argument.'); + + trigger_deprecation('twig/twig', '3.12', 'Passing a non-countable sequence of values to "%s()" is deprecated.', __METHOD__); + + return $values; + } + + $values = self::toArray($values, false); + } + + if (!$count = \count($values)) { + throw new RuntimeError('The "cycle" function does not work on empty sequences.'); + } + + return $values[$position % $count]; + } + + /** + * Returns a random value depending on the supplied parameter type: + * - a random item from a \Traversable or array + * - a random character from a string + * - a random integer between 0 and the integer parameter. + * + * @param \Traversable|array|int|float|string $values The values to pick a random item from + * @param int|null $max Maximum value used when $values is an int + * + * @return mixed A random value from the given sequence + * + * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) + * + * @internal + */ + public static function random(string $charset, $values = null, $max = null) + { + if (null === $values) { + return null === $max ? mt_rand() : mt_rand(0, (int) $max); + } + + if (\is_int($values) || \is_float($values)) { + if (null === $max) { + if ($values < 0) { + $max = 0; + $min = $values; + } else { + $max = $values; + $min = 0; + } + } else { + $min = $values; + } + + return mt_rand((int) $min, (int) $max); + } + + if (\is_string($values)) { + if ('' === $values) { + return ''; + } + + if ('UTF-8' !== $charset) { + $values = self::convertEncoding($values, 'UTF-8', $charset); + } + + // unicode version of str_split() + // split at all positions, but not after the start and not before the end + $values = preg_split('/(? $value) { + $values[$i] = self::convertEncoding($value, $charset, 'UTF-8'); + } + } + } + + if (!is_iterable($values)) { + return $values; + } + + $values = self::toArray($values); + + if (0 === \count($values)) { + throw new RuntimeError('The random function cannot pick from an empty sequence/mapping.'); + } + + return $values[array_rand($values, 1)]; + } + + /** + * Formats a date. + * + * {{ post.published_at|date("m/d/Y") }} + * + * @param \DateTimeInterface|\DateInterval|string $date A date + * @param string|null $format The target format, null to use the default + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + */ + public function formatDate($date, $format = null, $timezone = null): string + { + if (null === $format) { + $formats = $this->getDateFormat(); + $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; + } + + if ($date instanceof \DateInterval) { + return $date->format($format); + } + + return $this->convertDate($date, $timezone)->format($format); + } + + /** + * Returns a new date object modified. + * + * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} + * + * @param \DateTimeInterface|string $date A date + * @param string $modifier A modifier string + * + * @return \DateTime|\DateTimeImmutable + * + * @internal + */ + public function modifyDate($date, $modifier) + { + return $this->convertDate($date, false)->modify($modifier); + } + + /** + * Returns a formatted string. + * + * @param string|null $format + * @param ...$values + * + * @internal + */ + public static function sprintf($format, ...$values): string + { + return \sprintf($format ?? '', ...$values); + } + + /** + * @internal + */ + public static function dateConverter(Environment $env, $date, $format = null, $timezone = null): string + { + return $env->getExtension(self::class)->formatDate($date, $format, $timezone); + } + + /** + * Converts an input to a \DateTime instance. + * + * {% if date(user.created_at) < date('+2days') %} + * {# do something #} + * {% endif %} + * + * @param \DateTimeInterface|string|null $date A date or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return \DateTime|\DateTimeImmutable + */ + public function convertDate($date = null, $timezone = null) + { + // determine the timezone + if (false !== $timezone) { + if (null === $timezone) { + $timezone = $this->getTimezone(); + } elseif (!$timezone instanceof \DateTimeZone) { + $timezone = new \DateTimeZone($timezone); + } + } + + // immutable dates + if ($date instanceof \DateTimeImmutable) { + return false !== $timezone ? $date->setTimezone($timezone) : $date; + } + + if ($date instanceof \DateTime) { + $date = clone $date; + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; + } + + if (null === $date || 'now' === $date) { + if (null === $date) { + $date = 'now'; + } + + return new \DateTime($date, false !== $timezone ? $timezone : $this->getTimezone()); + } + + $asString = (string) $date; + if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { + $date = new \DateTime('@'.$date); + } else { + $date = new \DateTime($date, $this->getTimezone()); + } + + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; + } + + /** + * Replaces strings within a string. + * + * @param string|null $str String to replace in + * @param array|\Traversable $from Replace values + * + * @internal + */ + public static function replace($str, $from): string + { + if (!is_iterable($from)) { + throw new RuntimeError(\sprintf('The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); + } + + return strtr($str ?? '', self::toArray($from)); + } + + /** + * Rounds a number. + * + * @param int|float|string|null $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding + * + * @return int|float The rounded number + * + * @internal + */ + public static function round($value, $precision = 0, $method = 'common') + { + $value = (float) $value; + + if ('common' === $method) { + return round($value, $precision); + } + + if ('ceil' !== $method && 'floor' !== $method) { + throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); + } + + return $method($value * 10 ** $precision) / 10 ** $precision; + } + + /** + * Formats a number. + * + * All of the formatting options can be left null, in that case the defaults will + * be used. Supplying any of the parameters will override the defaults set in the + * environment object. + * + * @param mixed $number A float/int/string of the number to format + * @param int|null $decimal the number of decimal points to display + * @param string|null $decimalPoint the character(s) to use for the decimal point + * @param string|null $thousandSep the character(s) to use for the thousands separator + */ + public function formatNumber($number, $decimal = null, $decimalPoint = null, $thousandSep = null): string + { + $defaults = $this->getNumberFormat(); + if (null === $decimal) { + $decimal = $defaults[0]; + } + + if (null === $decimalPoint) { + $decimalPoint = $defaults[1]; + } + + if (null === $thousandSep) { + $thousandSep = $defaults[2]; + } + + return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); + } + + /** + * URL encodes (RFC 3986) a string as a path segment or an array as a query string. + * + * @param string|array|null $url A URL or an array of query parameters + * + * @internal + */ + public static function urlencode($url): string + { + if (\is_array($url)) { + return http_build_query($url, '', '&', \PHP_QUERY_RFC3986); + } + + return rawurlencode($url ?? ''); + } + + /** + * Merges any number of arrays or Traversable objects. + * + * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} + * + * {% set items = items|merge({ 'peugeot': 'car' }, { 'banana': 'fruit' }) %} + * + * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'banana': 'fruit' } #} + * + * @param array|\Traversable ...$arrays Any number of arrays or Traversable objects to merge + * + * @internal + */ + public static function merge(...$arrays): array + { + $result = []; + + foreach ($arrays as $argNumber => $array) { + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The merge filter only works with sequences/mappings or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); + } + + $result = array_merge($result, self::toArray($array)); + } + + return $result; + } + + /** + * Slices a variable. + * + * @param mixed $item A variable + * @param int $start Start of the slice + * @param int $length Size of the slice + * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + * + * @internal + */ + public static function slice(string $charset, $item, $start, $length = null, $preserveKeys = false) + { + if ($item instanceof \Traversable) { + while ($item instanceof \IteratorAggregate) { + $item = $item->getIterator(); + } + + if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { + try { + return iterator_to_array(new \LimitIterator($item, $start, $length ?? -1), $preserveKeys); + } catch (\OutOfBoundsException $e) { + return []; + } + } + + $item = iterator_to_array($item, $preserveKeys); + } + + if (\is_array($item)) { + return \array_slice($item, $start, $length, $preserveKeys); + } + + return mb_substr((string) $item, $start, $length, $charset); + } + + /** + * Returns the first element of the item. + * + * @param mixed $item A variable + * + * @return mixed The first element of the item + * + * @internal + */ + public static function first(string $charset, $item) + { + $elements = self::slice($charset, $item, 0, 1, false); + + return \is_string($elements) ? $elements : current($elements); + } + + /** + * Returns the last element of the item. + * + * @param mixed $item A variable + * + * @return mixed The last element of the item + * + * @internal + */ + public static function last(string $charset, $item) + { + $elements = self::slice($charset, $item, -1, 1, false); + + return \is_string($elements) ? $elements : current($elements); + } + + /** + * Joins the values to a string. + * + * The separators between elements are empty strings per default, you can define them with the optional parameters. + * + * {{ [1, 2, 3]|join(', ', ' and ') }} + * {# returns 1, 2 and 3 #} + * + * {{ [1, 2, 3]|join('|') }} + * {# returns 1|2|3 #} + * + * {{ [1, 2, 3]|join }} + * {# returns 123 #} + * + * @param array $value An array + * @param string $glue The separator + * @param string|null $and The separator for the last pair + * + * @internal + */ + public static function join($value, $glue = '', $and = null): string + { + if (!is_iterable($value)) { + $value = (array) $value; + } + + $value = self::toArray($value, false); + + if (0 === \count($value)) { + return ''; + } + + if (null === $and || $and === $glue) { + return implode($glue, $value); + } + + if (1 === \count($value)) { + return $value[0]; + } + + return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; + } + + /** + * Splits the string into an array. + * + * {{ "one,two,three"|split(',') }} + * {# returns [one, two, three] #} + * + * {{ "one,two,three,four,five"|split(',', 3) }} + * {# returns [one, two, "three,four,five"] #} + * + * {{ "123"|split('') }} + * {# returns [1, 2, 3] #} + * + * {{ "aabbcc"|split('', 2) }} + * {# returns [aa, bb, cc] #} + * + * @param string|null $value A string + * @param string $delimiter The delimiter + * @param int|null $limit The limit + * + * @internal + */ + public static function split(string $charset, $value, $delimiter, $limit = null): array + { + $value = $value ?? ''; + + if ('' !== $delimiter) { + return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + } + + if ($limit <= 1) { + return preg_split('/(?getIterator(); + } + + $keys = []; + if ($array instanceof \Iterator) { + $array->rewind(); + while ($array->valid()) { + $keys[] = $array->key(); + $array->next(); + } + + return $keys; + } + + foreach ($array as $key => $item) { + $keys[] = $key; + } + + return $keys; + } + + if (!\is_array($array)) { + return []; + } + + return array_keys($array); + } + + /** + * Reverses a variable. + * + * @param array|\Traversable|string|null $item An array, a \Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not + * + * @return mixed The reversed input + * + * @internal + */ + public static function reverse(string $charset, $item, $preserveKeys = false) + { + if ($item instanceof \Traversable) { + return array_reverse(iterator_to_array($item), $preserveKeys); + } + + if (\is_array($item)) { + return array_reverse($item, $preserveKeys); + } + + $string = (string) $item; + + if ('UTF-8' !== $charset) { + $string = self::convertEncoding($string, 'UTF-8', $charset); + } + + preg_match_all('/./us', $string, $matches); + + $string = implode('', array_reverse($matches[0])); + + if ('UTF-8' !== $charset) { + $string = self::convertEncoding($string, $charset, 'UTF-8'); + } + + return $string; + } + + /** + * Shuffles an array, a \Traversable instance, or a string. + * The function does not preserve keys. + * + * @param array|\Traversable|string|null $item + * + * @return mixed + * + * @internal + */ + public static function shuffle(string $charset, $item) + { + if (\is_string($item)) { + if ('UTF-8' !== $charset) { + $item = self::convertEncoding($item, 'UTF-8', $charset); + } + + $item = preg_split('/(? string + if (\is_int($a) && \is_string($b)) { + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + if ((int) $bTrim == $bTrim) { + return $a <=> (int) $bTrim; + } else { + return (float) $a <=> (float) $bTrim; + } + } + if (\is_string($a) && \is_int($b)) { + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + if ((int) $aTrim == $aTrim) { + return (int) $aTrim <=> $b; + } else { + return (float) $aTrim <=> (float) $b; + } + } + + // float <=> string + if (\is_float($a) && \is_string($b)) { + if (is_nan($a)) { + return 1; + } + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + + return $a <=> (float) $bTrim; + } + if (\is_string($a) && \is_float($b)) { + if (is_nan($b)) { + return 1; + } + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + + return (float) $aTrim <=> $b; + } + + // fallback to <=> + return $a <=> $b; + } + + /** + * @throws RuntimeError When an invalid pattern is used + * + * @internal + */ + public static function matches(string $regexp, ?string $str): int + { + set_error_handler(function ($t, $m) use ($regexp) { + throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); + }); + try { + return preg_match($regexp, $str ?? ''); + } finally { + restore_error_handler(); + } + } + + /** + * Returns a trimmed string. + * + * @param string|null $string + * @param string|null $characterMask + * @param string $side + * + * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') + * + * @internal + */ + public static function trim($string, $characterMask = null, $side = 'both'): string + { + if (null === $characterMask) { + $characterMask = " \t\n\r\0\x0B"; + } + + switch ($side) { + case 'both': + return trim($string ?? '', $characterMask); + case 'left': + return ltrim($string ?? '', $characterMask); + case 'right': + return rtrim($string ?? '', $characterMask); + default: + throw new RuntimeError('Trimming side must be "left", "right" or "both".'); + } + } + + /** + * Inserts HTML line breaks before all newlines in a string. + * + * @param string|null $string + * + * @internal + */ + public static function nl2br($string): string + { + return nl2br($string ?? ''); + } + + /** + * Removes whitespaces between HTML tags. + * + * @param string|null $content + * + * @internal + */ + public static function spaceless($content): string + { + return trim(preg_replace('/>\s+<', $content ?? '')); + } + + /** + * @param string|null $string + * @param string $to + * @param string $from + * + * @internal + */ + public static function convertEncoding($string, $to, $from): string + { + if (!\function_exists('iconv')) { + throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + return iconv($from, $to, $string ?? ''); + } + + /** + * Returns the length of a variable. + * + * @param mixed $thing A variable + * + * @internal + */ + public static function length(string $charset, $thing): int + { + if (null === $thing) { + return 0; + } + + if (\is_scalar($thing)) { + return mb_strlen($thing, $charset); + } + + if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { + return \count($thing); + } + + if ($thing instanceof \Traversable) { + return iterator_count($thing); + } + + if ($thing instanceof \Stringable) { + return mb_strlen((string) $thing, $charset); + } + + return 1; + } + + /** + * Converts a string to uppercase. + * + * @param string|null $string A string + * + * @internal + */ + public static function upper(string $charset, $string): string + { + return mb_strtoupper($string ?? '', $charset); + } + + /** + * Converts a string to lowercase. + * + * @param string|null $string A string + * + * @internal + */ + public static function lower(string $charset, $string): string + { + return mb_strtolower($string ?? '', $charset); + } + + /** + * Strips HTML and PHP tags from a string. + * + * @param string|null $string + * @param string[]|string|null $allowable_tags + * + * @internal + */ + public static function striptags($string, $allowable_tags = null): string + { + return strip_tags($string ?? '', $allowable_tags); + } + + /** + * Returns a titlecased string. + * + * @param string|null $string A string + * + * @internal + */ + public static function titleCase(string $charset, $string): string + { + return mb_convert_case($string ?? '', \MB_CASE_TITLE, $charset); + } + + /** + * Returns a capitalized string. + * + * @param string|null $string A string + * + * @internal + */ + public static function capitalize(string $charset, $string): string + { + return mb_strtoupper(mb_substr($string ?? '', 0, 1, $charset), $charset).mb_strtolower(mb_substr($string ?? '', 1, null, $charset), $charset); + } + + /** + * @internal + */ + public static function callMacro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) + { + if (!method_exists($template, $method)) { + $parent = $template; + while ($parent = $parent->getParent($context)) { + if (method_exists($parent, $method)) { + return $parent->$method(...$args); + } + } + + throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); + } + + return $template->$method(...$args); + } + + /** + * @template TSequence + * + * @param TSequence $seq + * + * @return ($seq is iterable ? TSequence : array{}) + * + * @internal + */ + public static function ensureTraversable($seq) + { + if (is_iterable($seq)) { + return $seq; + } + + return []; + } + + /** + * @internal + */ + public static function toArray($seq, $preserveKeys = true) + { + if ($seq instanceof \Traversable) { + return iterator_to_array($seq, $preserveKeys); + } + + if (!\is_array($seq)) { + return $seq; + } + + return $preserveKeys ? $seq : array_values($seq); + } + + /** + * Checks if a variable is empty. + * + * {# evaluates to true if the foo variable is null, false, or the empty string #} + * {% if foo is empty %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @internal + */ + public static function testEmpty($value): bool + { + if ($value instanceof \Countable) { + return 0 === \count($value); + } + + if ($value instanceof \Traversable) { + return !iterator_count($value); + } + + if ($value instanceof \Stringable) { + return '' === (string) $value; + } + + return '' === $value || false === $value || null === $value || [] === $value; + } + + /** + * Checks if a variable is a sequence. + * + * {# evaluates to true if the foo variable is a sequence #} + * {% if foo is sequence %} + * {# ... #} + * {% endif %} + * + * @param mixed $value + * + * @internal + */ + public static function testSequence($value): bool + { + if ($value instanceof \ArrayObject) { + $value = $value->getArrayCopy(); + } + + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + + return \is_array($value) && array_is_list($value); + } + + /** + * Checks if a variable is a mapping. + * + * {# evaluates to true if the foo variable is a mapping #} + * {% if foo is mapping %} + * {# ... #} + * {% endif %} + * + * @param mixed $value + * + * @internal + */ + public static function testMapping($value): bool + { + if ($value instanceof \ArrayObject) { + $value = $value->getArrayCopy(); + } + + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + + return (\is_array($value) && !array_is_list($value)) || \is_object($value); + } + + /** + * Renders a template. + * + * @param array $context + * @param string|array|TemplateWrapper $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not + * + * @internal + */ + public static function include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false): string + { + $alreadySandboxed = false; + $sandbox = null; + if ($withContext) { + $variables = array_merge($context, $variables); + } + + if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { + $sandbox = $env->getExtension(SandboxExtension::class); + if (!$alreadySandboxed = $sandbox->isSandboxed()) { + $sandbox->enableSandbox(); + } + } + + try { + $loaded = null; + try { + $loaded = $env->resolveTemplate($template); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + + return ''; + } + + if ($isSandboxed) { + $loaded->unwrap()->checkSecurity(); + } + + return $loaded->render($variables); + } finally { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + } + } + + /** + * Returns a template content without rendering it. + * + * @param string $name The template name + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @internal + */ + public static function source(Environment $env, $name, $ignoreMissing = false): string + { + $loader = $env->getLoader(); + try { + return $loader->getSourceContext($name)->getCode(); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + + return ''; + } + } + + /** + * Returns the list of cases of the enum. + * + * @template T of \UnitEnum + * + * @param class-string $enum + * + * @return list + * + * @internal + */ + public static function enumCases(string $enum): array + { + if (!enum_exists($enum)) { + throw new RuntimeError(\sprintf('Enum "%s" does not exist.', $enum)); + } + + return $enum::cases(); + } + + /** + * Provides the ability to get constants from instances as well as class/global constants. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * @param bool $checkDefined Whether to check if the constant is defined or not + * + * @return mixed Class constants can return many types like scalars, arrays, and + * objects depending on the PHP version (\BackedEnum, \UnitEnum, etc.) + * When $checkDefined is true, returns true when the constant is defined, false otherwise + * + * @internal + */ + public static function constant($constant, $object = null, bool $checkDefined = false) + { + if (null !== $object) { + if ('class' === $constant) { + return $checkDefined ? true : \get_class($object); + } + + $constant = \get_class($object).'::'.$constant; + } + + if (!\defined($constant)) { + if ($checkDefined) { + return false; + } + + if ('::class' === strtolower(substr($constant, -7))) { + throw new RuntimeError(\sprintf('You cannot use the Twig function "constant()" to access "%s". You could provide an object and call constant("class", $object) or use the class name directly as a string.', $constant)); + } + + throw new RuntimeError(\sprintf('Constant "%s" is undefined.', $constant)); + } + + return $checkDefined ? true : \constant($constant); + } + + /** + * Batches item. + * + * @param array $items An array of items + * @param int $size The size of the batch + * @param mixed $fill A value used to fill missing items + * + * @internal + */ + public static function batch($items, $size, $fill = null, $preserveKeys = true): array + { + if (!is_iterable($items)) { + throw new RuntimeError(\sprintf('The "batch" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); + } + + $size = (int) ceil($size); + + $result = array_chunk(self::toArray($items, $preserveKeys), $size, $preserveKeys); + + if (null !== $fill && $result) { + $last = \count($result) - 1; + if ($fillCount = $size - \count($result[$last])) { + for ($i = 0; $i < $fillCount; ++$i) { + $result[$last][] = $fill; + } + } + } + + return $result; + } + + /** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see \Twig\Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not + * @param int $lineno The template line where the attribute was called + * + * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true + * + * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal + */ + public static function getAttribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1) + { + // array + if (Template::METHOD_CALL !== $type) { + $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; + + if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) + || ($object instanceof \ArrayAccess && isset($object[$arrayItem])) + ) { + if ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; + } + + if (Template::ARRAY_CALL === $type || !\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if ($object instanceof \ArrayAccess) { + $message = \sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); + } elseif (\is_object($object)) { + $message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); + } elseif (\is_array($object)) { + if (empty($object)) { + $message = \sprintf('Key "%s" does not exist as the sequence/mapping is empty.', $arrayItem); + } else { + $message = \sprintf('Key "%s" for sequence/mapping with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (Template::ARRAY_CALL === $type) { + if (null === $object) { + $message = \sprintf('Impossible to access a key ("%s") on a null variable.', $item); + } else { + $message = \sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + } elseif (null === $object) { + $message = \sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); + } else { + $message = \sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + } + + if (!\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if (null === $object) { + $message = \sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); + } elseif (\is_array($object)) { + $message = \sprintf('Impossible to invoke a method ("%s") on a sequence/mapping.', $item); + } else { + $message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + + if ($object instanceof Template) { + throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source); + } + + // object property + if (Template::METHOD_CALL !== $type) { + if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { + if ($isDefinedTest) { + return true; + } + + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); + } + + return $object->$item; + } + } + + static $cache = []; + + $class = \get_class($object); + + // object method + // precedence: getXxx() > isXxx() > hasXxx() + if (!isset($cache[$class])) { + $methods = get_class_methods($object); + sort($methods); + $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); + $classCache = []; + foreach ($methods as $i => $method) { + $classCache[$method] = $method; + $classCache[$lcName = $lcMethods[$i]] = $method; + + if ('g' === $lcName[0] && str_starts_with($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && str_starts_with($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } elseif ('h' === $lcName[0] && str_starts_with($lcName, 'has')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + if (\in_array('is'.$lcName, $lcMethods)) { + continue; + } + } else { + continue; + } + + // skip get() and is() methods (in which case, $name is empty) + if ($name) { + if (!isset($classCache[$name])) { + $classCache[$name] = $method; + } + + if (!isset($classCache[$lcName])) { + $classCache[$lcName] = $method; + } + } + } + $cache[$class] = $classCache; + } + + $call = false; + if (isset($cache[$class][$item])) { + $method = $cache[$class][$item]; + } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { + $method = $cache[$class][$lcItem]; + } elseif (isset($cache[$class]['__call'])) { + $method = $item; + $call = true; + } else { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source); + } + + if ($isDefinedTest) { + return true; + } + + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source); + } + + // Some objects throw exceptions when they have __call, and the method we try + // to call is not supported. If ignoreStrictCheck is true, we should return null. + try { + $ret = $object->$method(...$arguments); + } catch (\BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) { + return; + } + throw $e; + } + + return $ret; + } + + /** + * Returns the values from a single column in the input array. + * + *
    +     *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
    +     *
    +     *  {% set fruits = items|column('fruit') %}
    +     *
    +     *  {# fruits now contains ['apple', 'orange'] #}
    +     * 
    + * + * @param array|\Traversable $array An array + * @param int|string $name The column name + * @param int|string|null $index The column to use as the index/keys for the returned array + * + * @return array The array of values + * + * @internal + */ + public static function column($array, $name, $index = null): array + { + if ($array instanceof \Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(\sprintf('The column filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + return array_column($array, $name, $index); + } + + /** + * @internal + */ + public static function filter(Environment $env, $array, $arrow) + { + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); + } + + self::checkArrowInSandbox($env, $arrow, 'filter', 'filter'); + + if (\is_array($array)) { + return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); + } + + // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator + return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); + } + + /** + * @internal + */ + public static function find(Environment $env, $array, $arrow) + { + self::checkArrowInSandbox($env, $arrow, 'find', 'filter'); + + foreach ($array as $k => $v) { + if ($arrow($v, $k)) { + return $v; + } + } + + return null; + } + + /** + * @internal + */ + public static function map(Environment $env, $array, $arrow) + { + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "map" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($array))); + } + + self::checkArrowInSandbox($env, $arrow, 'map', 'filter'); + + $r = []; + foreach ($array as $k => $v) { + $r[$k] = $arrow($v, $k); + } + + return $r; + } + + /** + * @internal + */ + public static function reduce(Environment $env, $array, $arrow, $initial = null) + { + self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter'); + + if (!\is_array($array) && !$array instanceof \Traversable) { + throw new RuntimeError(\sprintf('The "reduce" filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + $accumulator = $initial; + foreach ($array as $key => $value) { + $accumulator = $arrow($accumulator, $value, $key); + } + + return $accumulator; + } + + /** + * @internal + */ + public static function arraySome(Environment $env, $array, $arrow) + { + self::checkArrowInSandbox($env, $arrow, 'has some', 'operator'); + + foreach ($array as $k => $v) { + if ($arrow($v, $k)) { + return true; + } + } + + return false; + } + + /** + * @internal + */ + public static function arrayEvery(Environment $env, $array, $arrow) + { + self::checkArrowInSandbox($env, $arrow, 'has every', 'operator'); + + foreach ($array as $k => $v) { + if (!$arrow($v, $k)) { + return false; + } + } + + return true; + } + + /** + * @internal + */ + public static function checkArrowInSandbox(Environment $env, $arrow, $thing, $type) + { + if (!$arrow instanceof \Closure && $env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) { + throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); + } + } + + /** + * @internal to be removed in Twig 4 + */ + public static function captureOutput(iterable $body): string + { + $level = ob_get_level(); + ob_start(); + + try { + foreach ($body as $data) { + echo $data; + } + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + /** + * @internal + */ + public static function parseParentFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression + { + if (!$blockName = $parser->peekBlockStack()) { + throw new SyntaxError('Calling the "parent" function outside of a block is forbidden.', $line, $parser->getStream()->getSourceContext()); + } + + if (!$parser->hasInheritance()) { + throw new SyntaxError('Calling the "parent" function on a template that does not call "extends" or "use" is forbidden.', $line, $parser->getStream()->getSourceContext()); + } + + return new ParentExpression($blockName, $line); + } + + /** + * @internal + */ + public static function parseBlockFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression + { + $fakeFunction = new TwigFunction('block', fn ($name, $template = null) => null); + $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args); + + return new BlockReferenceExpression($args[0], $args[1] ?? null, $line); + } + + /** + * @internal + */ + public static function parseAttributeFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression + { + $fakeFunction = new TwigFunction('attribute', fn ($variable, $attribute, $arguments = null) => null); + $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args); + + return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line); + } +} diff --git a/vendor/twig/twig/src/Extension/DebugExtension.php b/vendor/twig/twig/src/Extension/DebugExtension.php new file mode 100644 index 0000000..dac21c3 --- /dev/null +++ b/vendor/twig/twig/src/Extension/DebugExtension.php @@ -0,0 +1,62 @@ + $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), + ]; + } + + /** + * @internal + */ + public static function dump(Environment $env, $context, ...$vars) + { + if (!$env->isDebug()) { + return; + } + + ob_start(); + + if (!$vars) { + $vars = []; + foreach ($context as $key => $value) { + if (!$value instanceof Template && !$value instanceof TemplateWrapper) { + $vars[$key] = $value; + } + } + + var_dump($vars); + } else { + var_dump(...$vars); + } + + return ob_get_clean(); + } +} diff --git a/vendor/twig/twig/src/Extension/EscaperExtension.php b/vendor/twig/twig/src/Extension/EscaperExtension.php new file mode 100644 index 0000000..52531c4 --- /dev/null +++ b/vendor/twig/twig/src/Extension/EscaperExtension.php @@ -0,0 +1,200 @@ +setDefaultStrategy($defaultStrategy); + } + + public function getTokenParsers(): array + { + return [new AutoEscapeTokenParser()]; + } + + public function getNodeVisitors(): array + { + return [new EscaperNodeVisitor()]; + } + + public function getFilters(): array + { + return [ + new TwigFilter('escape', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]), + new TwigFilter('e', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]), + new TwigFilter('raw', null, ['is_safe' => ['all'], 'node_class' => RawFilter::class]), + ]; + } + + /** + * @deprecated since Twig 3.10 + */ + public function setEnvironment(Environment $environment): void + { + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation) { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated and not needed if you are using methods from "Twig\Runtime\EscaperRuntime".', __METHOD__); + } + + $this->environment = $environment; + $this->escaper = $environment->getRuntime(EscaperRuntime::class); + } + + /** + * @deprecated since Twig 3.10 + */ + public function setEscaperRuntime(EscaperRuntime $escaper) + { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated and not needed if you are using methods from "Twig\Runtime\EscaperRuntime".', __METHOD__); + + $this->escaper = $escaper; + } + + /** + * Sets the default strategy to use when not defined by the user. + * + * The strategy can be a valid PHP callback that takes the template + * name as an argument and returns the strategy to use. + * + * @param string|false|callable(string $templateName): string $defaultStrategy An escaping strategy + */ + public function setDefaultStrategy($defaultStrategy): void + { + if ('name' === $defaultStrategy) { + $defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess']; + } + + $this->defaultStrategy = $defaultStrategy; + } + + /** + * Gets the default strategy to use when not defined by the user. + * + * @param string $name The template name + * + * @return string|false The default strategy to use for the template + */ + public function getDefaultStrategy(string $name) + { + // disable string callables to avoid calling a function named html or js, + // or any other upcoming escaping strategy + if (!\is_string($this->defaultStrategy) && false !== $this->defaultStrategy) { + return \call_user_func($this->defaultStrategy, $name); + } + + return $this->defaultStrategy; + } + + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable(Environment, string, string): string $callable A valid PHP callable + * + * @deprecated since Twig 3.10 + */ + public function setEscaper($strategy, callable $callable) + { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setEscaper()" method instead (be warned that Environment is not passed anymore to the callable).', __METHOD__); + + if (!isset($this->environment)) { + throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); + } + + $this->escapers[$strategy] = $callable; + $callable = function ($string, $charset) use ($callable) { + return $callable($this->environment, $string, $charset); + }; + + $this->escaper->setEscaper($strategy, $callable); + } + + /** + * Gets all defined escapers. + * + * @return array An array of escapers + * + * @deprecated since Twig 3.10 + */ + public function getEscapers() + { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::getEscaper()" method instead.', __METHOD__); + + return $this->escapers; + } + + /** + * @deprecated since Twig 3.10 + */ + public function setSafeClasses(array $safeClasses = []) + { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setSafeClasses()" method instead.', __METHOD__); + + if (!isset($this->escaper)) { + throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); + } + + $this->escaper->setSafeClasses($safeClasses); + } + + /** + * @deprecated since Twig 3.10 + */ + public function addSafeClass(string $class, array $strategies) + { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::addSafeClass()" method instead.', __METHOD__); + + if (!isset($this->escaper)) { + throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); + } + + $this->escaper->addSafeClass($class, $strategies); + } + + /** + * @internal + */ + public static function escapeFilterIsSafe(Node $filterArgs) + { + foreach ($filterArgs as $arg) { + if ($arg instanceof ConstantExpression) { + return [$arg->getAttribute('value')]; + } + + return []; + } + + return ['html']; + } +} diff --git a/vendor/twig/twig/src/Extension/ExtensionInterface.php b/vendor/twig/twig/src/Extension/ExtensionInterface.php new file mode 100644 index 0000000..10a42b6 --- /dev/null +++ b/vendor/twig/twig/src/Extension/ExtensionInterface.php @@ -0,0 +1,75 @@ + + */ +interface ExtensionInterface +{ + /** + * Returns the token parser instances to add to the existing list. + * + * @return TokenParserInterface[] + */ + public function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return TwigFilter[] + */ + public function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return TwigTest[] + */ + public function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return TwigFunction[] + */ + public function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array First array of unary operators, second array of binary operators + * + * @psalm-return array{ + * array}>, + * array, associativity: ExpressionParser::OPERATOR_*}> + * } + */ + public function getOperators(); +} diff --git a/vendor/twig/twig/src/Extension/GlobalsInterface.php b/vendor/twig/twig/src/Extension/GlobalsInterface.php new file mode 100644 index 0000000..d52cd10 --- /dev/null +++ b/vendor/twig/twig/src/Extension/GlobalsInterface.php @@ -0,0 +1,25 @@ + + */ +interface GlobalsInterface +{ + /** + * @return array + */ + public function getGlobals(): array; +} diff --git a/vendor/twig/twig/src/Extension/OptimizerExtension.php b/vendor/twig/twig/src/Extension/OptimizerExtension.php new file mode 100644 index 0000000..d3fe46a --- /dev/null +++ b/vendor/twig/twig/src/Extension/OptimizerExtension.php @@ -0,0 +1,27 @@ +optimizers)]; + } +} diff --git a/vendor/twig/twig/src/Extension/ProfilerExtension.php b/vendor/twig/twig/src/Extension/ProfilerExtension.php new file mode 100644 index 0000000..43e4a44 --- /dev/null +++ b/vendor/twig/twig/src/Extension/ProfilerExtension.php @@ -0,0 +1,52 @@ +actives[] = $profile; + } + + /** + * @return void + */ + public function enter(Profile $profile) + { + $this->actives[0]->addProfile($profile); + array_unshift($this->actives, $profile); + } + + /** + * @return void + */ + public function leave(Profile $profile) + { + $profile->leave(); + array_shift($this->actives); + + if (1 === \count($this->actives)) { + $this->actives[0]->leave(); + } + } + + public function getNodeVisitors(): array + { + return [new ProfilerNodeVisitor(static::class)]; + } +} diff --git a/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php b/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php new file mode 100644 index 0000000..63bc3b1 --- /dev/null +++ b/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php @@ -0,0 +1,19 @@ + + */ +interface RuntimeExtensionInterface +{ +} diff --git a/vendor/twig/twig/src/Extension/SandboxExtension.php b/vendor/twig/twig/src/Extension/SandboxExtension.php new file mode 100644 index 0000000..4e96760 --- /dev/null +++ b/vendor/twig/twig/src/Extension/SandboxExtension.php @@ -0,0 +1,135 @@ +policy = $policy; + $this->sandboxedGlobally = $sandboxed; + $this->sourcePolicy = $sourcePolicy; + } + + public function getTokenParsers(): array + { + return [new SandboxTokenParser()]; + } + + public function getNodeVisitors(): array + { + return [new SandboxNodeVisitor()]; + } + + public function enableSandbox(): void + { + $this->sandboxed = true; + } + + public function disableSandbox(): void + { + $this->sandboxed = false; + } + + public function isSandboxed(?Source $source = null): bool + { + return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source); + } + + public function isSandboxedGlobally(): bool + { + return $this->sandboxedGlobally; + } + + private function isSourceSandboxed(?Source $source): bool + { + if (null === $source || null === $this->sourcePolicy) { + return false; + } + + return $this->sourcePolicy->enableSandbox($source); + } + + public function setSecurityPolicy(SecurityPolicyInterface $policy) + { + $this->policy = $policy; + } + + public function getSecurityPolicy(): SecurityPolicyInterface + { + return $this->policy; + } + + public function checkSecurity($tags, $filters, $functions, ?Source $source = null): void + { + if ($this->isSandboxed($source)) { + $this->policy->checkSecurity($tags, $filters, $functions); + } + } + + public function checkMethodAllowed($obj, $method, int $lineno = -1, ?Source $source = null): void + { + if ($this->isSandboxed($source)) { + try { + $this->policy->checkMethodAllowed($obj, $method); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + public function checkPropertyAllowed($obj, $property, int $lineno = -1, ?Source $source = null): void + { + if ($this->isSandboxed($source)) { + try { + $this->policy->checkPropertyAllowed($obj, $property); + } catch (SecurityNotAllowedPropertyError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + public function ensureToStringAllowed($obj, int $lineno = -1, ?Source $source = null) + { + if ($this->isSandboxed($source) && $obj instanceof \Stringable) { + try { + $this->policy->checkMethodAllowed($obj, '__toString'); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + + return $obj; + } +} diff --git a/vendor/twig/twig/src/Extension/StagingExtension.php b/vendor/twig/twig/src/Extension/StagingExtension.php new file mode 100644 index 0000000..59db2ca --- /dev/null +++ b/vendor/twig/twig/src/Extension/StagingExtension.php @@ -0,0 +1,100 @@ + + * + * @internal + */ +final class StagingExtension extends AbstractExtension +{ + private $functions = []; + private $filters = []; + private $visitors = []; + private $tokenParsers = []; + private $tests = []; + + public function addFunction(TwigFunction $function): void + { + if (isset($this->functions[$function->getName()])) { + throw new \LogicException(\sprintf('Function "%s" is already registered.', $function->getName())); + } + + $this->functions[$function->getName()] = $function; + } + + public function getFunctions(): array + { + return $this->functions; + } + + public function addFilter(TwigFilter $filter): void + { + if (isset($this->filters[$filter->getName()])) { + throw new \LogicException(\sprintf('Filter "%s" is already registered.', $filter->getName())); + } + + $this->filters[$filter->getName()] = $filter; + } + + public function getFilters(): array + { + return $this->filters; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor): void + { + $this->visitors[] = $visitor; + } + + public function getNodeVisitors(): array + { + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser): void + { + if (isset($this->tokenParsers[$parser->getTag()])) { + throw new \LogicException(\sprintf('Tag "%s" is already registered.', $parser->getTag())); + } + + $this->tokenParsers[$parser->getTag()] = $parser; + } + + public function getTokenParsers(): array + { + return $this->tokenParsers; + } + + public function addTest(TwigTest $test): void + { + if (isset($this->tests[$test->getName()])) { + throw new \LogicException(\sprintf('Test "%s" is already registered.', $test->getName())); + } + + $this->tests[$test->getName()] = $test; + } + + public function getTests(): array + { + return $this->tests; + } +} diff --git a/vendor/twig/twig/src/Extension/StringLoaderExtension.php b/vendor/twig/twig/src/Extension/StringLoaderExtension.php new file mode 100644 index 0000000..698d181 --- /dev/null +++ b/vendor/twig/twig/src/Extension/StringLoaderExtension.php @@ -0,0 +1,40 @@ + true]), + ]; + } + + /** + * Loads a template from a string. + * + * {{ include(template_from_string("Hello {{ name }}")) }} + * + * @param string|null $name An optional name of the template to be used in error messages + * + * @internal + */ + public static function templateFromString(Environment $env, string|\Stringable $template, ?string $name = null): TemplateWrapper + { + return $env->createTemplate((string) $template, $name); + } +} diff --git a/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php b/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php new file mode 100644 index 0000000..49dfb80 --- /dev/null +++ b/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php @@ -0,0 +1,30 @@ +useYield)]; + } +} diff --git a/vendor/twig/twig/src/ExtensionSet.php b/vendor/twig/twig/src/ExtensionSet.php new file mode 100644 index 0000000..28d57a4 --- /dev/null +++ b/vendor/twig/twig/src/ExtensionSet.php @@ -0,0 +1,488 @@ + + * + * @internal + */ +final class ExtensionSet +{ + private $extensions; + private $initialized = false; + private $runtimeInitialized = false; + private $staging; + private $parsers; + private $visitors; + /** @var array */ + private $filters; + /** @var array */ + private $dynamicFilters; + /** @var array */ + private $tests; + /** @var array */ + private $dynamicTests; + /** @var array */ + private $functions; + /** @var array */ + private $dynamicFunctions; + /** @var array}> */ + private $unaryOperators; + /** @var array, associativity: ExpressionParser::OPERATOR_*}> */ + private $binaryOperators; + /** @var array */ + private $globals; + private $functionCallbacks = []; + private $filterCallbacks = []; + private $parserCallbacks = []; + private $lastModified = 0; + + public function __construct() + { + $this->staging = new StagingExtension(); + } + + public function initRuntime() + { + $this->runtimeInitialized = true; + } + + public function hasExtension(string $class): bool + { + return isset($this->extensions[ltrim($class, '\\')]); + } + + public function getExtension(string $class): ExtensionInterface + { + $class = ltrim($class, '\\'); + + if (!isset($this->extensions[$class])) { + throw new RuntimeError(\sprintf('The "%s" extension is not enabled.', $class)); + } + + return $this->extensions[$class]; + } + + /** + * @param ExtensionInterface[] $extensions + */ + public function setExtensions(array $extensions): void + { + foreach ($extensions as $extension) { + $this->addExtension($extension); + } + } + + /** + * @return ExtensionInterface[] + */ + public function getExtensions(): array + { + return $this->extensions; + } + + public function getSignature(): string + { + return json_encode(array_keys($this->extensions)); + } + + public function isInitialized(): bool + { + return $this->initialized || $this->runtimeInitialized; + } + + public function getLastModified(): int + { + if (0 !== $this->lastModified) { + return $this->lastModified; + } + + foreach ($this->extensions as $extension) { + $r = new \ReflectionObject($extension); + if (is_file($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) { + $this->lastModified = $extensionTime; + } + } + + return $this->lastModified; + } + + public function addExtension(ExtensionInterface $extension): void + { + $class = \get_class($extension); + + if ($this->initialized) { + throw new \LogicException(\sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); + } + + if (isset($this->extensions[$class])) { + throw new \LogicException(\sprintf('Unable to register extension "%s" as it is already registered.', $class)); + } + + $this->extensions[$class] = $extension; + } + + public function addFunction(TwigFunction $function): void + { + if ($this->initialized) { + throw new \LogicException(\sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName())); + } + + $this->staging->addFunction($function); + } + + /** + * @return TwigFunction[] + */ + public function getFunctions(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->functions; + } + + public function getFunction(string $name): ?TwigFunction + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->functions[$name])) { + return $this->functions[$name]; + } + + foreach ($this->dynamicFunctions as $pattern => $function) { + if (preg_match($pattern, $name, $matches)) { + array_shift($matches); + + return $function->withDynamicArguments($name, $function->getName(), $matches); + } + } + + foreach ($this->functionCallbacks as $callback) { + if (false !== $function = $callback($name)) { + return $function; + } + } + + return null; + } + + public function registerUndefinedFunctionCallback(callable $callable): void + { + $this->functionCallbacks[] = $callable; + } + + public function addFilter(TwigFilter $filter): void + { + if ($this->initialized) { + throw new \LogicException(\sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName())); + } + + $this->staging->addFilter($filter); + } + + /** + * @return TwigFilter[] + */ + public function getFilters(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->filters; + } + + public function getFilter(string $name): ?TwigFilter + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->filters[$name])) { + return $this->filters[$name]; + } + + foreach ($this->dynamicFilters as $pattern => $filter) { + if (preg_match($pattern, $name, $matches)) { + array_shift($matches); + + return $filter->withDynamicArguments($name, $filter->getName(), $matches); + } + } + + foreach ($this->filterCallbacks as $callback) { + if (false !== $filter = $callback($name)) { + return $filter; + } + } + + return null; + } + + public function registerUndefinedFilterCallback(callable $callable): void + { + $this->filterCallbacks[] = $callable; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.'); + } + + $this->staging->addNodeVisitor($visitor); + } + + /** + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a token parser as extensions have already been initialized.'); + } + + $this->staging->addTokenParser($parser); + } + + /** + * @return TokenParserInterface[] + */ + public function getTokenParsers(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->parsers; + } + + public function getTokenParser(string $name): ?TokenParserInterface + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->parsers[$name])) { + return $this->parsers[$name]; + } + + foreach ($this->parserCallbacks as $callback) { + if (false !== $parser = $callback($name)) { + return $parser; + } + } + + return null; + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->parserCallbacks[] = $callable; + } + + /** + * @return array + */ + public function getGlobals(): array + { + if (null !== $this->globals) { + return $this->globals; + } + + $globals = []; + foreach ($this->extensions as $extension) { + if (!$extension instanceof GlobalsInterface) { + continue; + } + + $globals = array_merge($globals, $extension->getGlobals()); + } + + if ($this->initialized) { + $this->globals = $globals; + } + + return $globals; + } + + public function resetGlobals(): void + { + $this->globals = null; + } + + public function addTest(TwigTest $test): void + { + if ($this->initialized) { + throw new \LogicException(\sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName())); + } + + $this->staging->addTest($test); + } + + /** + * @return TwigTest[] + */ + public function getTests(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->tests; + } + + public function getTest(string $name): ?TwigTest + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->tests[$name])) { + return $this->tests[$name]; + } + + foreach ($this->dynamicTests as $pattern => $test) { + if (preg_match($pattern, $name, $matches)) { + array_shift($matches); + + return $test->withDynamicArguments($name, $test->getName(), $matches); + } + } + + return null; + } + + /** + * @return array}> + */ + public function getUnaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->unaryOperators; + } + + /** + * @return array, associativity: ExpressionParser::OPERATOR_*}> + */ + public function getBinaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->binaryOperators; + } + + private function initExtensions(): void + { + $this->parsers = []; + $this->filters = []; + $this->functions = []; + $this->tests = []; + $this->dynamicFilters = []; + $this->dynamicFunctions = []; + $this->dynamicTests = []; + $this->visitors = []; + $this->unaryOperators = []; + $this->binaryOperators = []; + + foreach ($this->extensions as $extension) { + $this->initExtension($extension); + } + $this->initExtension($this->staging); + // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception + $this->initialized = true; + } + + private function initExtension(ExtensionInterface $extension): void + { + // filters + foreach ($extension->getFilters() as $filter) { + $this->filters[$name = $filter->getName()] = $filter; + if (str_contains($name, '*')) { + $this->dynamicFilters['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $filter; + } + } + + // functions + foreach ($extension->getFunctions() as $function) { + $this->functions[$name = $function->getName()] = $function; + if (str_contains($name, '*')) { + $this->dynamicFunctions['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $function; + } + } + + // tests + foreach ($extension->getTests() as $test) { + $this->tests[$name = $test->getName()] = $test; + if (str_contains($name, '*')) { + $this->dynamicTests['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $test; + } + } + + // token parsers + foreach ($extension->getTokenParsers() as $parser) { + if (!$parser instanceof TokenParserInterface) { + throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.'); + } + + $this->parsers[$parser->getTag()] = $parser; + } + + // node visitors + foreach ($extension->getNodeVisitors() as $visitor) { + $this->visitors[] = $visitor; + } + + // operators + if ($operators = $extension->getOperators()) { + if (!\is_array($operators)) { + throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators))); + } + + if (2 !== \count($operators)) { + throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); + } + + $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); + $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + } + } +} diff --git a/vendor/twig/twig/src/FileExtensionEscapingStrategy.php b/vendor/twig/twig/src/FileExtensionEscapingStrategy.php new file mode 100644 index 0000000..812071b --- /dev/null +++ b/vendor/twig/twig/src/FileExtensionEscapingStrategy.php @@ -0,0 +1,60 @@ + + */ +class FileExtensionEscapingStrategy +{ + /** + * Guesses the best autoescaping strategy based on the file name. + * + * @param string $name The template name + * + * @return string|false The escaping strategy name to use or false to disable + */ + public static function guess(string $name) + { + if (\in_array(substr($name, -1), ['/', '\\'])) { + return 'html'; // return html for directories + } + + if (str_ends_with($name, '.twig')) { + $name = substr($name, 0, -5); + } + + $extension = pathinfo($name, \PATHINFO_EXTENSION); + + switch ($extension) { + case 'js': + return 'js'; + + case 'css': + return 'css'; + + case 'txt': + return false; + + default: + return 'html'; + } + } +} diff --git a/vendor/twig/twig/src/Lexer.php b/vendor/twig/twig/src/Lexer.php new file mode 100644 index 0000000..28feaa2 --- /dev/null +++ b/vendor/twig/twig/src/Lexer.php @@ -0,0 +1,584 @@ + + */ +class Lexer +{ + private $isInitialized = false; + + private $tokens; + private $code; + private $cursor; + private $lineno; + private $end; + private $state; + private $states; + private $brackets; + private $env; + private $source; + private $options; + private $regexes; + private $position; + private $positions; + private $currentVarBlockLine; + + public const STATE_DATA = 0; + public const STATE_BLOCK = 1; + public const STATE_VAR = 2; + public const STATE_STRING = 3; + public const STATE_INTERPOLATION = 4; + + public const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; + public const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; + public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + public const REGEX_DQ_STRING_DELIM = '/"/A'; + public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + public const PUNCTUATION = '()[]{}?:.,|'; + + private const SPECIAL_CHARS = [ + 'f' => "\f", + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'v' => "\v", + ]; + + public function __construct(Environment $env, array $options = []) + { + $this->env = $env; + + $this->options = array_merge([ + 'tag_comment' => ['{#', '#}'], + 'tag_block' => ['{%', '%}'], + 'tag_variable' => ['{{', '}}'], + 'whitespace_trim' => '-', + 'whitespace_line_trim' => '~', + 'whitespace_line_chars' => ' \t\0\x0B', + 'interpolation' => ['#{', '}'], + ], $options); + } + + private function initialize() + { + if ($this->isInitialized) { + return; + } + + // when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default + $this->regexes = [ + // }} + 'lex_var' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '#').'\s*'. // -}}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_variable'][1], '#'). // }} + ') + }Ax', + + // %} + 'lex_block' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*\n?'. // -%}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#').'\n?'. // %}\n? + ') + }Ax', + + // {% endverbatim %} + 'lex_raw_data' => '{'. + preg_quote($this->options['tag_block'][0], '#'). // {% + '('. + $this->options['whitespace_trim']. // - + '|'. + $this->options['whitespace_line_trim']. // ~ + ')?\s*endverbatim\s*'. + '(?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%} + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#'). // %} + ') + }sx', + + 'operator' => $this->getOperatorRegex(), + + // #} + 'lex_comment' => '{ + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n? + ') + }sx', + + // verbatim %} + 'lex_block_raw' => '{ + \s*verbatim\s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#'). // %} + ') + }Asx', + + 'lex_block_line' => '{\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '#').'}As', + + // {{ or {% or {# + 'lex_tokens_start' => '{ + ('. + preg_quote($this->options['tag_variable'][0], '#'). // {{ + '|'. + preg_quote($this->options['tag_block'][0], '#'). // {% + '|'. + preg_quote($this->options['tag_comment'][0], '#'). // {# + ')('. + preg_quote($this->options['whitespace_trim'], '#'). // - + '|'. + preg_quote($this->options['whitespace_line_trim'], '#'). // ~ + ')? + }sx', + 'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0], '#').'\s*}A', + 'interpolation_end' => '{\s*'.preg_quote($this->options['interpolation'][1], '#').'}A', + ]; + + $this->isInitialized = true; + } + + public function tokenize(Source $source): TokenStream + { + $this->initialize(); + + $this->source = $source; + $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); + $this->cursor = 0; + $this->lineno = 1; + $this->end = \strlen($this->code); + $this->tokens = []; + $this->state = self::STATE_DATA; + $this->states = []; + $this->brackets = []; + $this->position = -1; + + // find all token starts in one go + preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE); + $this->positions = $matches; + + while ($this->cursor < $this->end) { + // dispatch to the lexing functions depending + // on the current state + switch ($this->state) { + case self::STATE_DATA: + $this->lexData(); + break; + + case self::STATE_BLOCK: + $this->lexBlock(); + break; + + case self::STATE_VAR: + $this->lexVar(); + break; + + case self::STATE_STRING: + $this->lexString(); + break; + + case self::STATE_INTERPOLATION: + $this->lexInterpolation(); + break; + } + } + + $this->pushToken(Token::EOF_TYPE); + + if (!empty($this->brackets)) { + [$expect, $lineno] = array_pop($this->brackets); + throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + return new TokenStream($this->tokens, $this->source); + } + + private function lexData(): void + { + // if no matches are left we return the rest of the template as simple text token + if ($this->position == \count($this->positions[0]) - 1) { + $this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor)); + $this->cursor = $this->end; + + return; + } + + // Find the first token after the current cursor + $position = $this->positions[0][++$this->position]; + while ($position[1] < $this->cursor) { + if ($this->position == \count($this->positions[0]) - 1) { + return; + } + $position = $this->positions[0][++$this->position]; + } + + // push the template text first + $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor); + + // trim? + if (isset($this->positions[2][$this->position][0])) { + if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } elseif ($this->options['whitespace_line_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + $this->pushToken(Token::TEXT_TYPE, $text); + $this->moveCursor($textContent.$position[0]); + + switch ($this->positions[1][$this->position][0]) { + case $this->options['tag_comment'][0]: + $this->lexComment(); + break; + + case $this->options['tag_block'][0]: + // raw data? + if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lexRawData(); + // {% line \d+ %} + } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lineno = (int) $match[1]; + } else { + $this->pushToken(Token::BLOCK_START_TYPE); + $this->pushState(self::STATE_BLOCK); + $this->currentVarBlockLine = $this->lineno; + } + break; + + case $this->options['tag_variable'][0]: + $this->pushToken(Token::VAR_START_TYPE); + $this->pushState(self::STATE_VAR); + $this->currentVarBlockLine = $this->lineno; + break; + } + } + + private function lexBlock(): void + { + if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::BLOCK_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function lexVar(): void + { + if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::VAR_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function lexExpression(): void + { + // whitespace + if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + + if ($this->cursor >= $this->end) { + throw new SyntaxError(\sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); + } + } + + // spread operator + if ('.' === $this->code[$this->cursor] && ($this->cursor + 2 < $this->end) && '.' === $this->code[$this->cursor + 1] && '.' === $this->code[$this->cursor + 2]) { + $this->pushToken(Token::SPREAD_TYPE, '...'); + $this->moveCursor('...'); + } + // arrow function + elseif ('=' === $this->code[$this->cursor] && ($this->cursor + 1 < $this->end) && '>' === $this->code[$this->cursor + 1]) { + $this->pushToken(Token::ARROW_TYPE, '=>'); + $this->moveCursor('=>'); + } + // operators + elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); + $this->moveCursor($match[0]); + } + // names + elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::NAME_TYPE, $match[0]); + $this->moveCursor($match[0]); + } + // numbers + elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { + $number = (float) $match[0]; // floats + if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) { + $number = (int) $match[0]; // integers lower than the maximum + } + $this->pushToken(Token::NUMBER_TYPE, $number); + $this->moveCursor($match[0]); + } + // punctuation + elseif (str_contains(self::PUNCTUATION, $this->code[$this->cursor])) { + // opening bracket + if (str_contains('([{', $this->code[$this->cursor])) { + $this->brackets[] = [$this->code[$this->cursor], $this->lineno]; + } + // closing bracket + elseif (str_contains(')]}', $this->code[$this->cursor])) { + if (empty($this->brackets)) { + throw new SyntaxError(\sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + + [$expect, $lineno] = array_pop($this->brackets); + if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { + throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + } + + $this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); + ++$this->cursor; + } + // strings + elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::STRING_TYPE, $this->stripcslashes(substr($match[0], 1, -1), substr($match[0], 0, 1))); + $this->moveCursor($match[0]); + } + // opening double quoted string + elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = ['"', $this->lineno]; + $this->pushState(self::STATE_STRING); + $this->moveCursor($match[0]); + } + // unlexable + else { + throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + private function stripcslashes(string $str, string $quoteType): string + { + $result = ''; + $length = \strlen($str); + + $i = 0; + while ($i < $length) { + if (false === $pos = strpos($str, '\\', $i)) { + $result .= substr($str, $i); + break; + } + + $result .= substr($str, $i, $pos - $i); + $i = $pos + 1; + + if ($i >= $length) { + $result .= '\\'; + break; + } + + $nextChar = $str[$i]; + + if (isset(self::SPECIAL_CHARS[$nextChar])) { + $result .= self::SPECIAL_CHARS[$nextChar]; + } elseif ('\\' === $nextChar) { + $result .= $nextChar; + } elseif ("'" === $nextChar || '"' === $nextChar) { + if ($nextChar !== $quoteType) { + trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1); + } + $result .= $nextChar; + } elseif ('#' === $nextChar && $i + 1 < $length && '{' === $str[$i + 1]) { + $result .= '#{'; + ++$i; + } elseif ('x' === $nextChar && $i + 1 < $length && ctype_xdigit($str[$i + 1])) { + $hex = $str[++$i]; + if ($i + 1 < $length && ctype_xdigit($str[$i + 1])) { + $hex .= $str[++$i]; + } + $result .= \chr(hexdec($hex)); + } elseif (ctype_digit($nextChar) && $nextChar < '8') { + $octal = $nextChar; + while ($i + 1 < $length && ctype_digit($str[$i + 1]) && $str[$i + 1] < '8' && \strlen($octal) < 3) { + $octal .= $str[++$i]; + } + $result .= \chr(octdec($octal)); + } else { + trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1); + $result .= $nextChar; + } + + ++$i; + } + + return $result; + } + + private function lexRawData(): void + { + if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source); + } + + $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); + $this->moveCursor($text.$match[0][0]); + + // trim? + if (isset($match[1][0])) { + if ($this->options['whitespace_trim'] === $match[1][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } else { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + + $this->pushToken(Token::TEXT_TYPE, $text); + } + + private function lexComment(): void + { + if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source); + } + + $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); + } + + private function lexString(): void + { + if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = [$this->options['interpolation'][0], $this->lineno]; + $this->pushToken(Token::INTERPOLATION_START_TYPE); + $this->moveCursor($match[0]); + $this->pushState(self::STATE_INTERPOLATION); + } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && '' !== $match[0]) { + $this->pushToken(Token::STRING_TYPE, $this->stripcslashes($match[0], '"')); + $this->moveCursor($match[0]); + } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + [$expect, $lineno] = array_pop($this->brackets); + if ('"' != $this->code[$this->cursor]) { + throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + $this->popState(); + ++$this->cursor; + } else { + // unlexable + throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + private function lexInterpolation(): void + { + $bracket = end($this->brackets); + if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) { + array_pop($this->brackets); + $this->pushToken(Token::INTERPOLATION_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function pushToken($type, $value = ''): void + { + // do not push empty text tokens + if (Token::TEXT_TYPE === $type && '' === $value) { + return; + } + + $this->tokens[] = new Token($type, $value, $this->lineno); + } + + private function moveCursor($text): void + { + $this->cursor += \strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + private function getOperatorRegex(): string + { + $operators = array_merge( + ['='], + array_keys($this->env->getUnaryOperators()), + array_keys($this->env->getBinaryOperators()) + ); + + $operators = array_combine($operators, array_map('strlen', $operators)); + arsort($operators); + + $regex = []; + foreach ($operators as $operator => $length) { + // an operator that ends with a character must be followed by + // a whitespace, a parenthesis, an opening map [ or sequence { + $r = preg_quote($operator, '/'); + if (ctype_alpha($operator[$length - 1])) { + $r .= '(?=[\s()\[{])'; + } + + // an operator that begins with a character must not have a dot or pipe before + if (ctype_alpha($operator[0])) { + $r = '(?states[] = $this->state; + $this->state = $state; + } + + private function popState(): void + { + if (0 === \count($this->states)) { + throw new \LogicException('Cannot pop state without a previous state.'); + } + + $this->state = array_pop($this->states); + } +} diff --git a/vendor/twig/twig/src/Loader/ArrayLoader.php b/vendor/twig/twig/src/Loader/ArrayLoader.php new file mode 100644 index 0000000..2bb54b7 --- /dev/null +++ b/vendor/twig/twig/src/Loader/ArrayLoader.php @@ -0,0 +1,75 @@ + + */ +final class ArrayLoader implements LoaderInterface +{ + /** + * @param array $templates An array of templates (keys are the names, and values are the source code) + */ + public function __construct( + private array $templates = [], + ) { + } + + public function setTemplate(string $name, string $template): void + { + $this->templates[$name] = $template; + } + + public function getSourceContext(string $name): Source + { + if (!isset($this->templates[$name])) { + throw new LoaderError(\sprintf('Template "%s" is not defined.', $name)); + } + + return new Source($this->templates[$name], $name); + } + + public function exists(string $name): bool + { + return isset($this->templates[$name]); + } + + public function getCacheKey(string $name): string + { + if (!isset($this->templates[$name])) { + throw new LoaderError(\sprintf('Template "%s" is not defined.', $name)); + } + + return $name.':'.$this->templates[$name]; + } + + public function isFresh(string $name, int $time): bool + { + if (!isset($this->templates[$name])) { + throw new LoaderError(\sprintf('Template "%s" is not defined.', $name)); + } + + return true; + } +} diff --git a/vendor/twig/twig/src/Loader/ChainLoader.php b/vendor/twig/twig/src/Loader/ChainLoader.php new file mode 100644 index 0000000..6e4f951 --- /dev/null +++ b/vendor/twig/twig/src/Loader/ChainLoader.php @@ -0,0 +1,132 @@ + + */ +final class ChainLoader implements LoaderInterface +{ + /** + * @var array + */ + private $hasSourceCache = []; + + /** + * @param iterable $loaders + */ + public function __construct( + private iterable $loaders = [], + ) { + } + + public function addLoader(LoaderInterface $loader): void + { + $current = $this->loaders; + + $this->loaders = (static function () use ($current, $loader): \Generator { + yield from $current; + yield $loader; + })(); + + $this->hasSourceCache = []; + } + + /** + * @return LoaderInterface[] + */ + public function getLoaders(): array + { + if (!\is_array($this->loaders)) { + $this->loaders = iterator_to_array($this->loaders, false); + } + + return $this->loaders; + } + + public function getSourceContext(string $name): Source + { + $exceptions = []; + + foreach ($this->getLoaders() as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->getSourceContext($name); + } catch (LoaderError $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function exists(string $name): bool + { + if (isset($this->hasSourceCache[$name])) { + return $this->hasSourceCache[$name]; + } + + foreach ($this->getLoaders() as $loader) { + if ($loader->exists($name)) { + return $this->hasSourceCache[$name] = true; + } + } + + return $this->hasSourceCache[$name] = false; + } + + public function getCacheKey(string $name): string + { + $exceptions = []; + + foreach ($this->getLoaders() as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->getCacheKey($name); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function isFresh(string $name, int $time): bool + { + $exceptions = []; + + foreach ($this->getLoaders() as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->isFresh($name, $time); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } +} diff --git a/vendor/twig/twig/src/Loader/FilesystemLoader.php b/vendor/twig/twig/src/Loader/FilesystemLoader.php new file mode 100644 index 0000000..c60964f --- /dev/null +++ b/vendor/twig/twig/src/Loader/FilesystemLoader.php @@ -0,0 +1,283 @@ + + */ +class FilesystemLoader implements LoaderInterface +{ + /** Identifier of the main namespace. */ + public const MAIN_NAMESPACE = '__main__'; + + protected $paths = []; + protected $cache = []; + protected $errorCache = []; + + private $rootPath; + + /** + * @param string|array $paths A path or an array of paths where to look for templates + * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) + */ + public function __construct($paths = [], ?string $rootPath = null) + { + $this->rootPath = ($rootPath ?? getcwd()).\DIRECTORY_SEPARATOR; + if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) { + $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; + } + + if ($paths) { + $this->setPaths($paths); + } + } + + /** + * Returns the paths to the templates. + */ + public function getPaths(string $namespace = self::MAIN_NAMESPACE): array + { + return $this->paths[$namespace] ?? []; + } + + /** + * Returns the path namespaces. + * + * The main namespace is always defined. + */ + public function getNamespaces(): array + { + return array_keys($this->paths); + } + + /** + * @param string|array $paths A path or an array of paths where to look for templates + */ + public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void + { + if (!\is_array($paths)) { + $paths = [$paths]; + } + + $this->paths[$namespace] = []; + foreach ($paths as $path) { + $this->addPath($path, $namespace); + } + } + + /** + * @throws LoaderError + */ + public function addPath(string $path, string $namespace = self::MAIN_NAMESPACE): void + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $this->paths[$namespace][] = rtrim($path, '/\\'); + } + + /** + * @throws LoaderError + */ + public function prependPath(string $path, string $namespace = self::MAIN_NAMESPACE): void + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $path = rtrim($path, '/\\'); + + if (!isset($this->paths[$namespace])) { + $this->paths[$namespace][] = $path; + } else { + array_unshift($this->paths[$namespace], $path); + } + } + + public function getSourceContext(string $name): Source + { + if (null === $path = $this->findTemplate($name)) { + return new Source('', $name, ''); + } + + return new Source(file_get_contents($path), $name, $path); + } + + public function getCacheKey(string $name): string + { + if (null === $path = $this->findTemplate($name)) { + return ''; + } + $len = \strlen($this->rootPath); + if (0 === strncmp($this->rootPath, $path, $len)) { + return substr($path, $len); + } + + return $path; + } + + /** + * @return bool + */ + public function exists(string $name) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return true; + } + + return null !== $this->findTemplate($name, false); + } + + public function isFresh(string $name, int $time): bool + { + // false support to be removed in 3.0 + if (null === $path = $this->findTemplate($name)) { + return false; + } + + return filemtime($path) < $time; + } + + /** + * @return string|null + */ + protected function findTemplate(string $name, bool $throw = true) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } + + if (isset($this->errorCache[$name])) { + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + try { + [$namespace, $shortname] = $this->parseName($name); + + $this->validateName($shortname); + } catch (LoaderError $e) { + if (!$throw) { + return null; + } + + throw $e; + } + + if (!isset($this->paths[$namespace])) { + $this->errorCache[$name] = \sprintf('There are no registered paths for namespace "%s".', $namespace); + + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + foreach ($this->paths[$namespace] as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->rootPath.$path; + } + + if (is_file($path.'/'.$shortname)) { + if (false !== $realpath = realpath($path.'/'.$shortname)) { + return $this->cache[$name] = $realpath; + } + + return $this->cache[$name] = $path.'/'.$shortname; + } + } + + $this->errorCache[$name] = \sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + private function normalizeName(string $name): string + { + return preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name)); + } + + private function parseName(string $name, string $default = self::MAIN_NAMESPACE): array + { + if (isset($name[0]) && '@' == $name[0]) { + if (false === $pos = strpos($name, '/')) { + throw new LoaderError(\sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return [$namespace, $shortname]; + } + + return [$default, $name]; + } + + private function validateName(string $name): void + { + if (str_contains($name, "\0")) { + throw new LoaderError('A template name cannot contain NUL bytes.'); + } + + $name = ltrim($name, '/'); + $parts = explode('/', $name); + $level = 0; + foreach ($parts as $part) { + if ('..' === $part) { + --$level; + } elseif ('.' !== $part) { + ++$level; + } + + if ($level < 0) { + throw new LoaderError(\sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); + } + } + } + + private function isAbsolutePath(string $file): bool + { + return strspn($file, '/\\', 0, 1) + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === $file[1] + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, \PHP_URL_SCHEME) + ; + } +} diff --git a/vendor/twig/twig/src/Loader/LoaderInterface.php b/vendor/twig/twig/src/Loader/LoaderInterface.php new file mode 100644 index 0000000..fec7e85 --- /dev/null +++ b/vendor/twig/twig/src/Loader/LoaderInterface.php @@ -0,0 +1,49 @@ + + */ +interface LoaderInterface +{ + /** + * Returns the source context for a given template logical name. + * + * @throws LoaderError When $name is not found + */ + public function getSourceContext(string $name): Source; + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @throws LoaderError When $name is not found + */ + public function getCacheKey(string $name): string; + + /** + * @param int $time Timestamp of the last modification time of the cached template + * + * @throws LoaderError When $name is not found + */ + public function isFresh(string $name, int $time): bool; + + /** + * @return bool + */ + public function exists(string $name); +} diff --git a/vendor/twig/twig/src/Markup.php b/vendor/twig/twig/src/Markup.php new file mode 100644 index 0000000..4fae779 --- /dev/null +++ b/vendor/twig/twig/src/Markup.php @@ -0,0 +1,52 @@ + + */ +class Markup implements \Countable, \JsonSerializable, \Stringable +{ + private $content; + private ?string $charset; + + public function __construct($content, $charset) + { + $this->content = (string) $content; + $this->charset = $charset; + } + + public function __toString() + { + return $this->content; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return mb_strlen($this->content, $this->charset); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->content; + } +} diff --git a/vendor/twig/twig/src/Node/AutoEscapeNode.php b/vendor/twig/twig/src/Node/AutoEscapeNode.php new file mode 100644 index 0000000..ee80639 --- /dev/null +++ b/vendor/twig/twig/src/Node/AutoEscapeNode.php @@ -0,0 +1,40 @@ + + */ +#[YieldReady] +class AutoEscapeNode extends Node +{ + public function __construct($value, Node $body, int $lineno) + { + parent::__construct(['body' => $body], ['value' => $value], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('body')); + } +} diff --git a/vendor/twig/twig/src/Node/BlockNode.php b/vendor/twig/twig/src/Node/BlockNode.php new file mode 100644 index 0000000..b4f939c --- /dev/null +++ b/vendor/twig/twig/src/Node/BlockNode.php @@ -0,0 +1,50 @@ + + */ +#[YieldReady] +class BlockNode extends Node +{ + public function __construct(string $name, Node $body, int $lineno) + { + parent::__construct(['body' => $body], ['name' => $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("/**\n") + ->write(" * @return iterable\n") + ->write(" */\n") + ->write(\sprintf("public function block_%s(array \$context, array \$blocks = []): iterable\n", $this->getAttribute('name')), "{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ; + + $compiler + ->subcompile($this->getNode('body')) + ->write("yield from [];\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/BlockReferenceNode.php b/vendor/twig/twig/src/Node/BlockReferenceNode.php new file mode 100644 index 0000000..7c313a0 --- /dev/null +++ b/vendor/twig/twig/src/Node/BlockReferenceNode.php @@ -0,0 +1,38 @@ + + */ +#[YieldReady] +class BlockReferenceNode extends Node implements NodeOutputInterface +{ + public function __construct(string $name, int $lineno) + { + parent::__construct([], ['name' => $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(\sprintf("yield from \$this->unwrap()->yieldBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) + ; + } +} diff --git a/vendor/twig/twig/src/Node/BodyNode.php b/vendor/twig/twig/src/Node/BodyNode.php new file mode 100644 index 0000000..08115b3 --- /dev/null +++ b/vendor/twig/twig/src/Node/BodyNode.php @@ -0,0 +1,24 @@ + + */ +#[YieldReady] +class BodyNode extends Node +{ +} diff --git a/vendor/twig/twig/src/Node/CaptureNode.php b/vendor/twig/twig/src/Node/CaptureNode.php new file mode 100644 index 0000000..3b7f0b6 --- /dev/null +++ b/vendor/twig/twig/src/Node/CaptureNode.php @@ -0,0 +1,57 @@ + + */ +#[YieldReady] +class CaptureNode extends Node +{ + public function __construct(Node $body, int $lineno) + { + parent::__construct(['body' => $body], ['raw' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + $useYield = $compiler->getEnvironment()->useYield(); + + if (!$this->getAttribute('raw')) { + $compiler->raw("('' === \$tmp = "); + } + $compiler + ->raw($useYield ? "implode('', iterator_to_array(" : '\\Twig\\Extension\\CoreExtension::captureOutput(') + ->raw("(function () use (&\$context, \$macros, \$blocks) {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->write("yield from [];\n") + ->outdent() + ->write('})()') + ; + if ($useYield) { + $compiler->raw(', false))'); + } else { + $compiler->raw(')'); + } + if (!$this->getAttribute('raw')) { + $compiler->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset());"); + } else { + $compiler->raw(';'); + } + } +} diff --git a/vendor/twig/twig/src/Node/CheckSecurityCallNode.php b/vendor/twig/twig/src/Node/CheckSecurityCallNode.php new file mode 100644 index 0000000..9c162d1 --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckSecurityCallNode.php @@ -0,0 +1,30 @@ + + */ +#[YieldReady] +class CheckSecurityCallNode extends Node +{ + public function compile(Compiler $compiler) + { + $compiler + ->write("\$this->sandbox = \$this->extensions[SandboxExtension::class];\n") + ->write("\$this->checkSecurity();\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/CheckSecurityNode.php b/vendor/twig/twig/src/Node/CheckSecurityNode.php new file mode 100644 index 0000000..6e591aa --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckSecurityNode.php @@ -0,0 +1,85 @@ + + */ +#[YieldReady] +class CheckSecurityNode extends Node +{ + private $usedFilters; + private $usedTags; + private $usedFunctions; + + /** + * @param array $usedFilters + * @param array $usedTags + * @param array $usedFunctions + */ + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) + { + $this->usedFilters = $usedFilters; + $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; + + parent::__construct(); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write("\n") + ->write("public function checkSecurity()\n") + ->write("{\n") + ->indent() + ->write('static $tags = ')->repr(array_filter($this->usedTags))->raw(";\n") + ->write('static $filters = ')->repr(array_filter($this->usedFilters))->raw(";\n") + ->write('static $functions = ')->repr(array_filter($this->usedFunctions))->raw(";\n\n") + ->write("try {\n") + ->indent() + ->write("\$this->sandbox->checkSecurity(\n") + ->indent() + ->write(!$this->usedTags ? "[],\n" : "['".implode("', '", array_keys($this->usedTags))."'],\n") + ->write(!$this->usedFilters ? "[],\n" : "['".implode("', '", array_keys($this->usedFilters))."'],\n") + ->write(!$this->usedFunctions ? "[],\n" : "['".implode("', '", array_keys($this->usedFunctions))."'],\n") + ->write("\$this->source\n") + ->outdent() + ->write(");\n") + ->outdent() + ->write("} catch (SecurityError \$e) {\n") + ->indent() + ->write("\$e->setSourceContext(\$this->source);\n\n") + ->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n") + ->outdent() + ->write("}\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/CheckToStringNode.php b/vendor/twig/twig/src/Node/CheckToStringNode.php new file mode 100644 index 0000000..937240c --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckToStringNode.php @@ -0,0 +1,47 @@ + + */ +#[YieldReady] +class CheckToStringNode extends AbstractExpression +{ + public function __construct(AbstractExpression $expr) + { + parent::__construct(['expr' => $expr], [], $expr->getTemplateLine()); + } + + public function compile(Compiler $compiler): void + { + $expr = $this->getNode('expr'); + $compiler + ->raw('$this->sandbox->ensureToStringAllowed(') + ->subcompile($expr) + ->raw(', ') + ->repr($expr->getTemplateLine()) + ->raw(', $this->source)') + ; + } +} diff --git a/vendor/twig/twig/src/Node/DeprecatedNode.php b/vendor/twig/twig/src/Node/DeprecatedNode.php new file mode 100644 index 0000000..0772adf --- /dev/null +++ b/vendor/twig/twig/src/Node/DeprecatedNode.php @@ -0,0 +1,73 @@ + + */ +#[YieldReady] +class DeprecatedNode extends Node +{ + public function __construct(AbstractExpression $expr, int $lineno) + { + parent::__construct(['expr' => $expr], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $expr = $this->getNode('expr'); + + if (!$expr instanceof ConstantExpression) { + $varName = $compiler->getVarName(); + $compiler + ->write(\sprintf('$%s = ', $varName)) + ->subcompile($expr) + ->raw(";\n") + ; + } + + $compiler->write('trigger_deprecation('); + if ($this->hasNode('package')) { + $compiler->subcompile($this->getNode('package')); + } else { + $compiler->raw("''"); + } + $compiler->raw(', '); + if ($this->hasNode('version')) { + $compiler->subcompile($this->getNode('version')); + } else { + $compiler->raw("''"); + } + $compiler->raw(', '); + + if ($expr instanceof ConstantExpression) { + $compiler->subcompile($expr); + } else { + $compiler->write(\sprintf('$%s', $varName)); + } + + $compiler + ->raw('.') + ->string(\sprintf(' in "%s" at line %d.', $this->getTemplateName(), $this->getTemplateLine())) + ->raw(");\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/DoNode.php b/vendor/twig/twig/src/Node/DoNode.php new file mode 100644 index 0000000..1593fd0 --- /dev/null +++ b/vendor/twig/twig/src/Node/DoNode.php @@ -0,0 +1,40 @@ + + */ +#[YieldReady] +class DoNode extends Node +{ + public function __construct(AbstractExpression $expr, int $lineno) + { + parent::__construct(['expr' => $expr], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/EmbedNode.php b/vendor/twig/twig/src/Node/EmbedNode.php new file mode 100644 index 0000000..4cd3b38 --- /dev/null +++ b/vendor/twig/twig/src/Node/EmbedNode.php @@ -0,0 +1,50 @@ + + */ +#[YieldReady] +class EmbedNode extends IncludeNode +{ + // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) + public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno) + { + parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno); + + $this->setAttribute('name', $name); + $this->setAttribute('index', $index); + } + + protected function addGetTemplate(Compiler $compiler): void + { + $compiler + ->write('$this->loadTemplate(') + ->string($this->getAttribute('name')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(', ') + ->string($this->getAttribute('index')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/AbstractExpression.php b/vendor/twig/twig/src/Node/Expression/AbstractExpression.php new file mode 100644 index 0000000..1692f56 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/AbstractExpression.php @@ -0,0 +1,28 @@ + + */ +abstract class AbstractExpression extends Node +{ + public function isGenerator(): bool + { + return $this->hasAttribute('is_generator') && $this->getAttribute('is_generator'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ArrayExpression.php b/vendor/twig/twig/src/Node/Expression/ArrayExpression.php new file mode 100644 index 0000000..5f8b0f6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ArrayExpression.php @@ -0,0 +1,135 @@ +index = -1; + foreach ($this->getKeyValuePairs() as $pair) { + if ($pair['key'] instanceof ConstantExpression && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) { + $this->index = $pair['key']->getAttribute('value'); + } + } + } + + public function getKeyValuePairs(): array + { + $pairs = []; + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = [ + 'key' => $pair[0], + 'value' => $pair[1], + ]; + } + + return $pairs; + } + + public function hasElement(AbstractExpression $key): bool + { + foreach ($this->getKeyValuePairs() as $pair) { + // we compare the string representation of the keys + // to avoid comparing the line numbers which are not relevant here. + if ((string) $key === (string) $pair['key']) { + return true; + } + } + + return false; + } + + public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void + { + if (null === $key) { + $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); + } + + array_push($this->nodes, $key, $value); + } + + public function compile(Compiler $compiler): void + { + $keyValuePairs = $this->getKeyValuePairs(); + $needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($keyValuePairs); + + if ($needsArrayMergeSpread) { + $compiler->raw('CoreExtension::merge('); + } + $compiler->raw('['); + $first = true; + $reopenAfterMergeSpread = false; + $nextIndex = 0; + foreach ($keyValuePairs as $pair) { + if ($reopenAfterMergeSpread) { + $compiler->raw(', ['); + $reopenAfterMergeSpread = false; + } + + if ($needsArrayMergeSpread && $pair['value']->hasAttribute('spread')) { + $compiler->raw('], ')->subcompile($pair['value']); + $first = true; + $reopenAfterMergeSpread = true; + continue; + } + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + if ($pair['value']->hasAttribute('spread') && !$needsArrayMergeSpread) { + $compiler->raw('...')->subcompile($pair['value']); + ++$nextIndex; + } else { + $key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null; + + if ($nextIndex !== $key) { + if (\is_int($key)) { + $nextIndex = $key + 1; + } + $compiler + ->subcompile($pair['key']) + ->raw(' => ') + ; + } else { + ++$nextIndex; + } + + $compiler->subcompile($pair['value']); + } + } + if (!$reopenAfterMergeSpread) { + $compiler->raw(']'); + } + if ($needsArrayMergeSpread) { + $compiler->raw(')'); + } + } + + private function hasSpreadItem(array $pairs): bool + { + foreach ($pairs as $pair) { + if ($pair['value']->hasAttribute('spread')) { + return true; + } + } + + return false; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php b/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php new file mode 100644 index 0000000..2bae4ed --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php @@ -0,0 +1,64 @@ + + */ +class ArrowFunctionExpression extends AbstractExpression +{ + public function __construct(AbstractExpression $expr, Node $names, $lineno) + { + parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->raw('function (') + ; + foreach ($this->getNode('names') as $i => $name) { + if ($i) { + $compiler->raw(', '); + } + + $compiler + ->raw('$__') + ->raw($name->getAttribute('name')) + ->raw('__') + ; + } + $compiler + ->raw(') use ($context, $macros) { ') + ; + foreach ($this->getNode('names') as $name) { + $compiler + ->raw('$context["') + ->raw($name->getAttribute('name')) + ->raw('"] = $__') + ->raw($name->getAttribute('name')) + ->raw('__; ') + ; + } + $compiler + ->raw('return ') + ->subcompile($this->getNode('expr')) + ->raw('; }') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php b/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php new file mode 100644 index 0000000..7dd1bc4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php @@ -0,0 +1,27 @@ +raw('$context[') + ->string($this->getAttribute('name')) + ->raw(']') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php new file mode 100644 index 0000000..c424e5c --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php @@ -0,0 +1,42 @@ + $left, 'right' => $right], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('left')) + ->raw(' ') + ; + $this->operator($compiler); + $compiler + ->raw(' ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + abstract public function operator(Compiler $compiler): Compiler; +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php new file mode 100644 index 0000000..ee4307e --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php @@ -0,0 +1,23 @@ +raw('+'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php new file mode 100644 index 0000000..5f2380d --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php @@ -0,0 +1,23 @@ +raw('&&'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php new file mode 100644 index 0000000..db7d6d6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php @@ -0,0 +1,23 @@ +raw('&'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php new file mode 100644 index 0000000..ce803dd --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php @@ -0,0 +1,23 @@ +raw('|'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php new file mode 100644 index 0000000..5c29785 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php @@ -0,0 +1,23 @@ +raw('^'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php new file mode 100644 index 0000000..f825ab8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php @@ -0,0 +1,23 @@ +raw('.'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php new file mode 100644 index 0000000..e3817d1 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php @@ -0,0 +1,23 @@ +raw('/'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php new file mode 100644 index 0000000..a73a560 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php @@ -0,0 +1,35 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(\sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(\sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(\sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right)) + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php new file mode 100644 index 0000000..5f42319 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 === CoreExtension::compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('=='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php new file mode 100644 index 0000000..d7e7980 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php @@ -0,0 +1,29 @@ +raw('(int) floor('); + parent::compile($compiler); + $compiler->raw(')'); + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('/'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php new file mode 100644 index 0000000..f42de3f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(1 === CoreExtension::compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('>'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php new file mode 100644 index 0000000..0c4f43f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 <= CoreExtension::compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('>='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php new file mode 100644 index 0000000..c57bb20 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php @@ -0,0 +1,33 @@ +raw('CoreExtension::arrayEvery($this->env, ') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php new file mode 100644 index 0000000..12293f8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php @@ -0,0 +1,33 @@ +raw('CoreExtension::arraySome($this->env, ') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php new file mode 100644 index 0000000..68a98fe --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php @@ -0,0 +1,33 @@ +raw('CoreExtension::inFilter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('in'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php new file mode 100644 index 0000000..fb3264a --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(-1 === CoreExtension::compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('<'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php new file mode 100644 index 0000000..8f36538 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 >= CoreExtension::compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('<='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php new file mode 100644 index 0000000..4669044 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php @@ -0,0 +1,33 @@ +raw('CoreExtension::matches(') + ->subcompile($this->getNode('right')) + ->raw(', ') + ->subcompile($this->getNode('left')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php new file mode 100644 index 0000000..271b45c --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php @@ -0,0 +1,23 @@ +raw('%'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php new file mode 100644 index 0000000..6d4c1e0 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php @@ -0,0 +1,23 @@ +raw('*'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php new file mode 100644 index 0000000..d137ef6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 !== CoreExtension::compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('!='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php new file mode 100644 index 0000000..80c8755 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php @@ -0,0 +1,33 @@ +raw('!CoreExtension::inFilter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('not in'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php new file mode 100644 index 0000000..21f87c9 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php @@ -0,0 +1,23 @@ +raw('||'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php new file mode 100644 index 0000000..c9f4c66 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php @@ -0,0 +1,22 @@ +raw('**'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php new file mode 100644 index 0000000..55982c8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php @@ -0,0 +1,33 @@ +raw('range(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('..'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php new file mode 100644 index 0000000..ae5a4a4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php @@ -0,0 +1,22 @@ +raw('<=>'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php new file mode 100644 index 0000000..4519f30 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php @@ -0,0 +1,35 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(\sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(\sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(\sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right)) + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php new file mode 100644 index 0000000..eeb87fa --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php @@ -0,0 +1,23 @@ +raw('-'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php b/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php new file mode 100644 index 0000000..acd231e --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php @@ -0,0 +1,87 @@ + + */ +class BlockReferenceExpression extends AbstractExpression +{ + public function __construct(Node $name, ?Node $template, int $lineno) + { + $nodes = ['name' => $name]; + if (null !== $template) { + $nodes['template'] = $template; + } + + parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('is_defined_test')) { + $this->compileTemplateCall($compiler, 'hasBlock'); + } else { + if ($this->getAttribute('output')) { + $compiler->addDebugInfo($this); + + $compiler->write('yield from '); + $this + ->compileTemplateCall($compiler, 'yieldBlock') + ->raw(";\n"); + } else { + $this->compileTemplateCall($compiler, 'renderBlock'); + } + } + } + + private function compileTemplateCall(Compiler $compiler, string $method): Compiler + { + if (!$this->hasNode('template')) { + $compiler->write('$this'); + } else { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('template')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + $compiler->raw(\sprintf('->unwrap()->%s', $method)); + + return $this->compileBlockArguments($compiler); + } + + private function compileBlockArguments(Compiler $compiler): Compiler + { + $compiler + ->raw('(') + ->subcompile($this->getNode('name')) + ->raw(', $context'); + + if (!$this->hasNode('template')) { + $compiler->raw(', $blocks'); + } + + return $compiler->raw(')'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/CallExpression.php b/vendor/twig/twig/src/Node/Expression/CallExpression.php new file mode 100644 index 0000000..6fc6f66 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/CallExpression.php @@ -0,0 +1,362 @@ +getTwigCallable(); + $callable = $twigCallable->getCallable(); + + if (\is_string($callable) && !str_contains($callable, '::')) { + $compiler->raw($callable); + } else { + $rc = $this->reflectCallable($twigCallable); + $r = $rc->getReflector(); + $callable = $rc->getCallable(); + + if (\is_string($callable)) { + $compiler->raw($callable); + } elseif (\is_array($callable) && \is_string($callable[0])) { + if (!$r instanceof \ReflectionMethod || $r->isStatic()) { + $compiler->raw(\sprintf('%s::%s', $callable[0], $callable[1])); + } else { + $compiler->raw(\sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + } + } elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) { + $class = \get_class($callable[0]); + if (!$compiler->getEnvironment()->hasExtension($class)) { + // Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error + $compiler->raw(\sprintf('$this->env->getExtension(\'%s\')', $class)); + } else { + $compiler->raw(\sprintf('$this->extensions[\'%s\']', ltrim($class, '\\'))); + } + + $compiler->raw(\sprintf('->%s', $callable[1])); + } else { + $compiler->raw(\sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $twigCallable->getDynamicName())); + } + } + + $this->compileArguments($compiler); + } + + protected function compileArguments(Compiler $compiler, $isArray = false): void + { + if (\func_num_args() >= 2) { + trigger_deprecation('twig/twig', '3.11', 'Passing a second argument to "%s()" is deprecated.', __METHOD__); + } + + $compiler->raw($isArray ? '[' : '('); + + $first = true; + + $twigCallable = $this->getAttribute('twig_callable'); + + if ($twigCallable->needsCharset()) { + $compiler->raw('$this->env->getCharset()'); + $first = false; + } + + if ($twigCallable->needsEnvironment()) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->raw('$this->env'); + $first = false; + } + + if ($twigCallable->needsContext()) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->raw('$context'); + $first = false; + } + + foreach ($twigCallable->getArguments() as $argument) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->string($argument); + $first = false; + } + + if ($this->hasNode('node')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($this->getNode('node')); + $first = false; + } + + if ($this->hasNode('arguments')) { + $arguments = (new CallableArgumentsExtractor($this, $this->getTwigCallable()))->extractArguments($this->getNode('arguments')); + foreach ($arguments as $node) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($node); + $first = false; + } + } + + $compiler->raw($isArray ? ']' : ')'); + } + + /** + * @deprecated since 3.12, use Twig\Util\CallableArgumentsExtractor::getArguments() instead + */ + protected function getArguments($callable, $arguments) + { + trigger_deprecation('twig/twig', '3.12', 'The "%s()" method is deprecated, use Twig\Util\CallableArgumentsExtractor::getArguments() instead.', __METHOD__); + + $callType = $this->getAttribute('type'); + $callName = $this->getAttribute('name'); + + $parameters = []; + $named = false; + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + $named = true; + $name = $this->normalizeName($name); + } elseif ($named) { + throw new SyntaxError(\sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + $parameters[$name] = $node; + } + + $isVariadic = $this->getAttribute('twig_callable')->isVariadic(); + if (!$named && !$isVariadic) { + return $parameters; + } + + if (!$callable) { + if ($named) { + $message = \sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); + } else { + $message = \sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); + } + + throw new \LogicException($message); + } + + [$callableParameters, $isPhpVariadic] = $this->getCallableParameters($callable, $isVariadic); + $arguments = []; + $names = []; + $missingArguments = []; + $optionalArguments = []; + $pos = 0; + foreach ($callableParameters as $callableParameter) { + $name = $this->normalizeName($callableParameter->name); + if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { + if ('start' === $name) { + $name = 'low'; + } elseif ('end' === $name) { + $name = 'high'; + } + } + + $names[] = $name; + + if (\array_key_exists($name, $parameters)) { + if (\array_key_exists($pos, $parameters)) { + throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + if (\count($missingArguments)) { + throw new SyntaxError(\sprintf( + 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', + $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) + ), $this->getTemplateLine(), $this->getSourceContext()); + } + + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$name]; + unset($parameters[$name]); + $optionalArguments = []; + } elseif (\array_key_exists($pos, $parameters)) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$pos]; + unset($parameters[$pos]); + $optionalArguments = []; + ++$pos; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1); + } elseif ($callableParameter->isOptional()) { + if (empty($parameters)) { + break; + } else { + $missingArguments[] = $name; + } + } else { + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + } + + if ($isVariadic) { + $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1); + foreach ($parameters as $key => $value) { + if (\is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new ConstantExpression($key, -1)); + } + unset($parameters[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; + } + } + + if (!empty($parameters)) { + $unknownParameter = null; + foreach ($parameters as $parameter) { + if ($parameter instanceof Node) { + $unknownParameter = $parameter; + break; + } + } + + throw new SyntaxError( + \sprintf( + 'Unknown argument%s "%s" for %s "%s(%s)".', + \count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) + ), + $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(), + $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext() + ); + } + + return $arguments; + } + + /** + * @deprecated since 3.12 + */ + protected function normalizeName(string $name): string + { + trigger_deprecation('twig/twig', '3.12', 'The "%s()" method is deprecated.', __METHOD__); + + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); + } + + // To be removed in 4.0 + private function getCallableParameters($callable, bool $isVariadic): array + { + $twigCallable = $this->getAttribute('twig_callable'); + $rc = $this->reflectCallable($twigCallable); + $r = $rc->getReflector(); + $callableName = $rc->getName(); + + $parameters = $r->getParameters(); + if ($this->hasNode('node')) { + array_shift($parameters); + } + if ($twigCallable->needsCharset()) { + array_shift($parameters); + } + if ($twigCallable->needsEnvironment()) { + array_shift($parameters); + } + if ($twigCallable->needsContext()) { + array_shift($parameters); + } + foreach ($twigCallable->getArguments() as $argument) { + array_shift($parameters); + } + + $isPhpVariadic = false; + if ($isVariadic) { + $argument = end($parameters); + $isArray = $argument && $argument->hasType() && $argument->getType() instanceof \ReflectionNamedType && 'array' === $argument->getType()->getName(); + if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { + array_pop($parameters); + } elseif ($argument && $argument->isVariadic()) { + array_pop($parameters); + $isPhpVariadic = true; + } else { + throw new \LogicException(\sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $twigCallable->getName())); + } + } + + return [$parameters, $isPhpVariadic]; + } + + private function reflectCallable(TwigCallableInterface $callable): ReflectionCallable + { + if (!$this->reflector) { + $this->reflector = new ReflectionCallable($callable); + } + + return $this->reflector; + } + + /** + * Overrides the Twig callable based on attributes (as potentially, attributes changed between the creation and the compilation of the node). + * + * To be removed in 4.0 and replace by $this->getAttribute('twig_callable'). + */ + private function getTwigCallable(): TwigCallableInterface + { + $current = $this->getAttribute('twig_callable'); + + $this->setAttribute('twig_callable', match ($this->getAttribute('type')) { + 'test' => (new TwigTest( + $this->getAttribute('name'), + $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), + [ + 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), + ], + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + 'function' => (new TwigFunction( + $this->hasAttribute('name') ? $this->getAttribute('name') : $current->getName(), + $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), + [ + 'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(), + 'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(), + 'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(), + 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), + ], + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + 'filter' => (new TwigFilter( + $this->getAttribute('name'), + $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), + [ + 'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(), + 'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(), + 'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(), + 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), + ], + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + }); + + return $this->getAttribute('twig_callable'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php b/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php new file mode 100644 index 0000000..d7db993 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php @@ -0,0 +1,45 @@ + $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + // Ternary with no then uses Elvis operator + if ($this->getNode('expr1') === $this->getNode('expr2')) { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ?: (') + ->subcompile($this->getNode('expr3')) + ->raw('))'); + } else { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw('))'); + } + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ConstantExpression.php b/vendor/twig/twig/src/Node/Expression/ConstantExpression.php new file mode 100644 index 0000000..2a8909d --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ConstantExpression.php @@ -0,0 +1,31 @@ + $value], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->repr($this->getAttribute('value')); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php b/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php new file mode 100644 index 0000000..75b6d18 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php @@ -0,0 +1,63 @@ + + */ +class DefaultFilter extends FilterExpression +{ + #[FirstClassTwigCallableReady] + public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) + { + if ($filter instanceof TwigFilter) { + $name = $filter->getName(); + $default = new FilterExpression($node, $filter, $arguments, $node->getTemplateLine()); + } else { + $name = $filter->getAttribute('value'); + $default = new FilterExpression($node, new TwigFilter('default', [CoreExtension::class, 'default']), $arguments, $node->getTemplateLine()); + } + + if ('default' === $name && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) { + $test = new DefinedTest(clone $node, new TwigTest('defined'), new Node(), $node->getTemplateLine()); + $false = \count($arguments) ? $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine()); + + $node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine()); + } else { + $node = $default; + } + + parent::__construct($node, $filter, $arguments, $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php b/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php new file mode 100644 index 0000000..e115ab1 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php @@ -0,0 +1,36 @@ + + */ +class RawFilter extends FilterExpression +{ + #[FirstClassTwigCallableReady] + public function __construct(Node $node, TwigFilter|ConstantExpression|null $filter = null, ?Node $arguments = null, int $lineno = 0) + { + parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new Node(), $lineno ?: $node->getTemplateLine()); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/FilterExpression.php b/vendor/twig/twig/src/Node/Expression/FilterExpression.php new file mode 100644 index 0000000..efc9119 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/FilterExpression.php @@ -0,0 +1,73 @@ +getName(); + $filterName = new ConstantExpression($name, $lineno); + } else { + $name = $filter->getAttribute('value'); + $filterName = $filter; + trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFilter" when creating a "%s" filter of type "%s" is deprecated.', $name, static::class); + } + + parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], ['name' => $name, 'type' => 'filter'], $lineno); + + if ($filter instanceof TwigFilter) { + $this->setAttribute('twig_callable', $filter); + } + + $this->deprecateNode('filter', new NameDeprecation('twig/twig', '3.12')); + + $this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12')); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getNode('filter', false)->getAttribute('value'); + if ($name !== $this->getAttribute('name')) { + trigger_deprecation('twig/twig', '3.11', 'Changing the value of a "filter" node in a NodeVisitor class is not supported anymore.'); + $this->removeAttribute('twig_callable'); + } + if ('raw' === $name) { + trigger_deprecation('twig/twig', '3.11', 'Creating the "raw" filter via "FilterExpression" is deprecated; use "RawFilter" instead.'); + + $compiler->subcompile($this->getNode('node')); + + return; + } + + if (!$this->hasAttribute('twig_callable')) { + $this->setAttribute('twig_callable', $compiler->getEnvironment()->getFilter($name)); + } + + $this->compileCallable($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/FunctionExpression.php b/vendor/twig/twig/src/Node/Expression/FunctionExpression.php new file mode 100644 index 0000000..6215c6a --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/FunctionExpression.php @@ -0,0 +1,68 @@ +getName(); + } else { + $name = $function; + trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFunction" when creating a "%s" function of type "%s" is deprecated.', $name, static::class); + } + + parent::__construct(['arguments' => $arguments], ['name' => $name, 'type' => 'function', 'is_defined_test' => false], $lineno); + + if ($function instanceof TwigFunction) { + $this->setAttribute('twig_callable', $function); + } + + $this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12')); + } + + public function compile(Compiler $compiler) + { + $name = $this->getAttribute('name'); + if ($this->hasAttribute('twig_callable')) { + $name = $this->getAttribute('twig_callable')->getName(); + if ($name !== $this->getAttribute('name')) { + trigger_deprecation('twig/twig', '3.12', 'Changing the value of a "function" node in a NodeVisitor class is not supported anymore.'); + $this->removeAttribute('twig_callable'); + } + } + + if (!$this->hasAttribute('twig_callable')) { + $this->setAttribute('twig_callable', $compiler->getEnvironment()->getFunction($name)); + } + + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $this->getNode('arguments')->setNode('checkDefined', new ConstantExpression(true, $this->getTemplateLine())); + } + + $this->compileCallable($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php b/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php new file mode 100644 index 0000000..7e5c25f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php @@ -0,0 +1,41 @@ +getNode('arguments'); + if ($arguments->hasNode('enum')) { + $firstArgument = $arguments->getNode('enum'); + } elseif ($arguments->hasNode('0')) { + $firstArgument = $arguments->getNode('0'); + } else { + $firstArgument = null; + } + + if (!$firstArgument instanceof ConstantExpression || 1 !== \count($arguments)) { + parent::compile($compiler); + + return; + } + + $value = $firstArgument->getAttribute('value'); + + if (!\is_string($value)) { + throw new SyntaxError('The first argument of the "enum_cases" function must be a string.', $this->getTemplateLine(), $this->getSourceContext()); + } + + if (!enum_exists($value)) { + throw new SyntaxError(\sprintf('The first argument of the "enum_cases" function must be the name of an enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext()); + } + + $compiler->raw(\sprintf('%s::cases()', $value)); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php b/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php new file mode 100644 index 0000000..29a446b --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php @@ -0,0 +1,87 @@ + $node, 'attribute' => $attribute]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno); + } + + public function compile(Compiler $compiler): void + { + $env = $compiler->getEnvironment(); + + // optimize array calls + if ( + $this->getAttribute('optimizable') + && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) + && !$this->getAttribute('is_defined_test') + && Template::ARRAY_CALL === $this->getAttribute('type') + ) { + $var = '$'.$compiler->getVarName(); + $compiler + ->raw('(('.$var.' = ') + ->subcompile($this->getNode('node')) + ->raw(') && is_array(') + ->raw($var) + ->raw(') || ') + ->raw($var) + ->raw(' instanceof ArrayAccess ? (') + ->raw($var) + ->raw('[') + ->subcompile($this->getNode('attribute')) + ->raw('] ?? null) : null)') + ; + + return; + } + + $compiler->raw('CoreExtension::getAttribute($this->env, $this->source, '); + + if ($this->getAttribute('ignore_strict_check')) { + $this->getNode('node')->setAttribute('ignore_strict_check', true); + } + + $compiler + ->subcompile($this->getNode('node')) + ->raw(', ') + ->subcompile($this->getNode('attribute')) + ; + + if ($this->hasNode('arguments')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + } else { + $compiler->raw(', []'); + } + + $compiler->raw(', ') + ->repr($this->getAttribute('type')) + ->raw(', ')->repr($this->getAttribute('is_defined_test')) + ->raw(', ')->repr($this->getAttribute('ignore_strict_check')) + ->raw(', ')->repr($env->hasExtension(SandboxExtension::class)) + ->raw(', ')->repr($this->getNode('node')->getTemplateLine()) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/InlinePrint.php b/vendor/twig/twig/src/Node/Expression/InlinePrint.php new file mode 100644 index 0000000..0a3c2e4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/InlinePrint.php @@ -0,0 +1,34 @@ + $node], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('yield ') + ->subcompile($this->getNode('node')) + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php new file mode 100644 index 0000000..01806f9 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -0,0 +1,64 @@ + $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno); + + if ($node instanceof NameExpression) { + $node->setAttribute('always_defined', true); + } + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('is_defined_test')) { + $compiler + ->raw('method_exists($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(')') + ; + + return; + } + + $compiler + ->raw('CoreExtension::callMacro($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(', [') + ; + $first = true; + /** @var ArrayExpression */ + $args = $this->getNode('arguments'); + foreach ($args->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler->subcompile($pair['value']); + } + $compiler + ->raw('], ') + ->repr($this->getTemplateLine()) + ->raw(', $context, $this->getSourceContext())'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/NameExpression.php b/vendor/twig/twig/src/Node/Expression/NameExpression.php new file mode 100644 index 0000000..286aa5a --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/NameExpression.php @@ -0,0 +1,107 @@ + '$this->getTemplateName()', + '_context' => '$context', + '_charset' => '$this->env->getCharset()', + ]; + + public function __construct(string $name, int $lineno) + { + parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getAttribute('name'); + + $compiler->addDebugInfo($this); + + if ($this->getAttribute('is_defined_test')) { + if (isset($this->specialVars[$name])) { + $compiler->repr(true); + } elseif (\PHP_VERSION_ID >= 70400) { + $compiler + ->raw('array_key_exists(') + ->string($name) + ->raw(', $context)') + ; + } else { + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) || array_key_exists(') + ->string($name) + ->raw(', $context))') + ; + } + } elseif (isset($this->specialVars[$name])) { + $compiler->raw($this->specialVars[$name]); + } elseif ($this->getAttribute('always_defined')) { + $compiler + ->raw('$context[') + ->string($name) + ->raw(']') + ; + } else { + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler + ->raw('($context[') + ->string($name) + ->raw('] ?? null)') + ; + } else { + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) || array_key_exists(') + ->string($name) + ->raw(', $context) ? $context[') + ->string($name) + ->raw('] : (function () { throw new RuntimeError(\'Variable ') + ->string($name) + ->raw(' does not exist.\', ') + ->repr($this->lineno) + ->raw(', $this->source); })()') + ->raw(')') + ; + } + } + } + + /** + * @deprecated since Twig 3.11 (to be removed in 4.0) + */ + public function isSpecial() + { + trigger_deprecation('twig/twig', '3.11', 'The "%s()" method is deprecated and will be removed in Twig 4.0.', __METHOD__); + + return isset($this->specialVars[$this->getAttribute('name')]); + } + + /** + * @deprecated since Twig 3.11 (to be removed in 4.0) + */ + public function isSimple() + { + trigger_deprecation('twig/twig', '3.11', 'The "%s()" method is deprecated and will be removed in Twig 4.0.', __METHOD__); + + return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php b/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php new file mode 100644 index 0000000..98630f7 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php @@ -0,0 +1,61 @@ +getTemplateLine()); + // for "block()", we don't need the null test as the return value is always a string + if (!$left instanceof BlockReferenceExpression) { + $test = new AndBinary( + $test, + new NotUnary(new NullTest($left, new TwigTest('null'), new Node(), $left->getTemplateLine()), $left->getTemplateLine()), + $left->getTemplateLine() + ); + } + + parent::__construct($test, $left, $right, $lineno); + } + + public function compile(Compiler $compiler): void + { + /* + * This optimizes only one case. PHP 7 also supports more complex expressions + * that can return null. So, for instance, if log is defined, log("foo") ?? "..." works, + * but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced + * cases might be implemented as an optimizer node visitor, but has not been done + * as benefits are probably not worth the added complexity. + */ + if ($this->getNode('expr2') instanceof NameExpression) { + $this->getNode('expr2')->setAttribute('always_defined', true); + $compiler + ->raw('((') + ->subcompile($this->getNode('expr2')) + ->raw(') ?? (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } else { + parent::compile($compiler); + } + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ParentExpression.php b/vendor/twig/twig/src/Node/Expression/ParentExpression.php new file mode 100644 index 0000000..22fe38f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ParentExpression.php @@ -0,0 +1,46 @@ + + */ +class ParentExpression extends AbstractExpression +{ + public function __construct(string $name, int $lineno) + { + parent::__construct([], ['output' => false, 'name' => $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('output')) { + $compiler + ->addDebugInfo($this) + ->write('yield from $this->yieldParentBlock(') + ->string($this->getAttribute('name')) + ->raw(", \$context, \$blocks);\n") + ; + } else { + $compiler + ->raw('$this->renderParentBlock(') + ->string($this->getAttribute('name')) + ->raw(', $context, $blocks)') + ; + } + } +} diff --git a/vendor/twig/twig/src/Node/Expression/TempNameExpression.php b/vendor/twig/twig/src/Node/Expression/TempNameExpression.php new file mode 100644 index 0000000..004c704 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/TempNameExpression.php @@ -0,0 +1,31 @@ + $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('$_') + ->raw($this->getAttribute('name')) + ->raw('_') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php b/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php new file mode 100644 index 0000000..867fd09 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php @@ -0,0 +1,49 @@ + + */ +class ConstantTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === constant(') + ; + + if ($this->getNode('arguments')->hasNode('1')) { + $compiler + ->raw('get_class(') + ->subcompile($this->getNode('arguments')->getNode('1')) + ->raw(')."::".') + ; + } + + $compiler + ->subcompile($this->getNode('arguments')->getNode('0')) + ->raw('))') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php new file mode 100644 index 0000000..24d3ee8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -0,0 +1,81 @@ + + */ +class DefinedTest extends TestExpression +{ + #[FirstClassTwigCallableReady] + public function __construct(Node $node, TwigTest|string $name, ?Node $arguments, int $lineno) + { + if ($node instanceof NameExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof GetAttrExpression) { + $node->setAttribute('is_defined_test', true); + $this->changeIgnoreStrictCheck($node); + } elseif ($node instanceof BlockReferenceExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { + $node = new ConstantExpression(true, $node->getTemplateLine()); + } elseif ($node instanceof MethodCallExpression) { + $node->setAttribute('is_defined_test', true); + } else { + throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); + } + + if (\is_string($name) && 'defined' !== $name) { + trigger_deprecation('twig/twig', '3.12', 'Creating a "DefinedTest" instance with a test name that is not "defined" is deprecated.'); + } + + parent::__construct($node, $name, $arguments, $lineno); + } + + private function changeIgnoreStrictCheck(GetAttrExpression $node) + { + $node->setAttribute('optimizable', false); + $node->setAttribute('ignore_strict_check', true); + + if ($node->getNode('node') instanceof GetAttrExpression) { + $this->changeIgnoreStrictCheck($node->getNode('node')); + } + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php b/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php new file mode 100644 index 0000000..90d58a4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php @@ -0,0 +1,36 @@ + + */ +class DivisiblebyTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(0 == ') + ->subcompile($this->getNode('node')) + ->raw(' % ') + ->subcompile($this->getNode('arguments')->getNode('0')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php b/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php new file mode 100644 index 0000000..a0e3ed6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php @@ -0,0 +1,35 @@ + + */ +class EvenTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 0') + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/NullTest.php b/vendor/twig/twig/src/Node/Expression/Test/NullTest.php new file mode 100644 index 0000000..45b54ae --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/NullTest.php @@ -0,0 +1,34 @@ + + */ +class NullTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(null === ') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/OddTest.php b/vendor/twig/twig/src/Node/Expression/Test/OddTest.php new file mode 100644 index 0000000..d56c711 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/OddTest.php @@ -0,0 +1,35 @@ + + */ +class OddTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 != 0') + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php b/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php new file mode 100644 index 0000000..f1e24db --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php @@ -0,0 +1,34 @@ + + */ +class SameasTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === ') + ->subcompile($this->getNode('arguments')->getNode('0')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/TestExpression.php b/vendor/twig/twig/src/Node/Expression/TestExpression.php new file mode 100644 index 0000000..080d85a --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/TestExpression.php @@ -0,0 +1,66 @@ + $node]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + if ($test instanceof TwigTest) { + $name = $test->getName(); + } else { + $name = $test; + trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigTest" when creating a "%s" test of type "%s" is deprecated.', $name, static::class); + } + + parent::__construct($nodes, ['name' => $name, 'type' => 'test'], $lineno); + + if ($test instanceof TwigTest) { + $this->setAttribute('twig_callable', $test); + } + + $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12')); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getAttribute('name'); + if ($this->hasAttribute('twig_callable')) { + $name = $this->getAttribute('twig_callable')->getName(); + if ($name !== $this->getAttribute('name')) { + trigger_deprecation('twig/twig', '3.12', 'Changing the value of a "test" node in a NodeVisitor class is not supported anymore.'); + $this->removeAttribute('twig_callable'); + } + } + + if (!$this->hasAttribute('twig_callable')) { + $this->setAttribute('twig_callable', $compiler->getEnvironment()->getTest($this->getAttribute('name'))); + } + + $this->compileCallable($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php new file mode 100644 index 0000000..e31e3f8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php @@ -0,0 +1,34 @@ + $node], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->raw(' '); + $this->operator($compiler); + $compiler->subcompile($this->getNode('node')); + } + + abstract public function operator(Compiler $compiler): Compiler; +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php new file mode 100644 index 0000000..dc2f235 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php @@ -0,0 +1,23 @@ +raw('-'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php new file mode 100644 index 0000000..55c11ba --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php @@ -0,0 +1,23 @@ +raw('!'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php new file mode 100644 index 0000000..4b0a062 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php @@ -0,0 +1,23 @@ +raw('+'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/VariadicExpression.php b/vendor/twig/twig/src/Node/Expression/VariadicExpression.php new file mode 100644 index 0000000..a1bdb48 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/VariadicExpression.php @@ -0,0 +1,24 @@ +raw('...'); + + parent::compile($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/FlushNode.php b/vendor/twig/twig/src/Node/FlushNode.php new file mode 100644 index 0000000..ff3bd1c --- /dev/null +++ b/vendor/twig/twig/src/Node/FlushNode.php @@ -0,0 +1,40 @@ + + */ +#[YieldReady] +class FlushNode extends Node +{ + public function __construct(int $lineno) + { + parent::__construct([], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if ($compiler->getEnvironment()->useYield()) { + $compiler->write("yield '';\n"); + } + + $compiler->write("flush();\n"); + } +} diff --git a/vendor/twig/twig/src/Node/ForLoopNode.php b/vendor/twig/twig/src/Node/ForLoopNode.php new file mode 100644 index 0000000..1f0a4f3 --- /dev/null +++ b/vendor/twig/twig/src/Node/ForLoopNode.php @@ -0,0 +1,51 @@ + + */ +#[YieldReady] +class ForLoopNode extends Node +{ + public function __construct(int $lineno) + { + parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('else')) { + $compiler->write("\$context['_iterated'] = true;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("++\$context['loop']['index0'];\n") + ->write("++\$context['loop']['index'];\n") + ->write("\$context['loop']['first'] = false;\n") + ->write("if (isset(\$context['loop']['revindex0'], \$context['loop']['revindex'])) {\n") + ->indent() + ->write("--\$context['loop']['revindex0'];\n") + ->write("--\$context['loop']['revindex'];\n") + ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") + ->outdent() + ->write("}\n") + ; + } + } +} diff --git a/vendor/twig/twig/src/Node/ForNode.php b/vendor/twig/twig/src/Node/ForNode.php new file mode 100644 index 0000000..2fc0147 --- /dev/null +++ b/vendor/twig/twig/src/Node/ForNode.php @@ -0,0 +1,116 @@ + + */ +#[YieldReady] +class ForNode extends Node +{ + private $loop; + + public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno) + { + $body = new Node([$body, $this->loop = new ForLoopNode($lineno)]); + + $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, ['with_loop' => true], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("\$context['_parent'] = \$context;\n") + ->write("\$context['_seq'] = CoreExtension::ensureTraversable(") + ->subcompile($this->getNode('seq')) + ->raw(");\n") + ; + + if ($this->hasNode('else')) { + $compiler->write("\$context['_iterated'] = false;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("\$context['loop'] = [\n") + ->write(" 'parent' => \$context['_parent'],\n") + ->write(" 'index0' => 0,\n") + ->write(" 'index' => 1,\n") + ->write(" 'first' => true,\n") + ->write("];\n") + ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof \Countable)) {\n") + ->indent() + ->write("\$length = count(\$context['_seq']);\n") + ->write("\$context['loop']['revindex0'] = \$length - 1;\n") + ->write("\$context['loop']['revindex'] = \$length;\n") + ->write("\$context['loop']['length'] = \$length;\n") + ->write("\$context['loop']['last'] = 1 === \$length;\n") + ->outdent() + ->write("}\n") + ; + } + + $this->loop->setAttribute('else', $this->hasNode('else')); + $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); + + $compiler + ->write("foreach (\$context['_seq'] as ") + ->subcompile($this->getNode('key_target')) + ->raw(' => ') + ->subcompile($this->getNode('value_target')) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n") + ; + + if ($this->hasNode('else')) { + $compiler + ->write("if (!\$context['_iterated']) {\n") + ->indent() + ->subcompile($this->getNode('else')) + ->outdent() + ->write("}\n") + ; + } + + $compiler->write("\$_parent = \$context['_parent'];\n"); + + // remove some "private" loop variables (needed for nested loops) + $compiler->write('unset($context[\'_seq\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\']'); + if ($this->hasNode('else')) { + $compiler->raw(', $context[\'_iterated\']'); + } + if ($this->getAttribute('with_loop')) { + $compiler->raw(', $context[\'loop\']'); + } + $compiler->raw(");\n"); + + // keep the values set in the inner context for variables defined in the outer context + $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); + } +} diff --git a/vendor/twig/twig/src/Node/IfNode.php b/vendor/twig/twig/src/Node/IfNode.php new file mode 100644 index 0000000..2af48fa --- /dev/null +++ b/vendor/twig/twig/src/Node/IfNode.php @@ -0,0 +1,75 @@ + + */ +#[YieldReady] +class IfNode extends Node +{ + public function __construct(Node $tests, ?Node $else, int $lineno) + { + $nodes = ['tests' => $tests]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + for ($i = 0, $count = \count($this->getNode('tests')); $i < $count; $i += 2) { + if ($i > 0) { + $compiler + ->outdent() + ->write('} elseif (') + ; + } else { + $compiler + ->write('if (') + ; + } + + $compiler + ->subcompile($this->getNode('tests')->getNode((string) $i)) + ->raw(") {\n") + ->indent() + ; + // The node might not exists if the content is empty + if ($this->getNode('tests')->hasNode((string) ($i + 1))) { + $compiler->subcompile($this->getNode('tests')->getNode((string) ($i + 1))); + } + } + + if ($this->hasNode('else')) { + $compiler + ->outdent() + ->write("} else {\n") + ->indent() + ->subcompile($this->getNode('else')) + ; + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/vendor/twig/twig/src/Node/ImportNode.php b/vendor/twig/twig/src/Node/ImportNode.php new file mode 100644 index 0000000..9a6033f --- /dev/null +++ b/vendor/twig/twig/src/Node/ImportNode.php @@ -0,0 +1,75 @@ + + */ +#[YieldReady] +class ImportNode extends Node +{ + /** + * @param bool $global + */ + public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, $global = true) + { + if (null === $global || \is_string($global)) { + trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); + $global = \func_num_args() > 4 ? func_get_arg(4) : true; + } elseif (!\is_bool($global)) { + throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($global))); + } + + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('$macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + + if ($this->getAttribute('global')) { + $compiler + ->raw('$this->macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + } + + if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { + $compiler->raw('$this'); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')->unwrap()') + ; + } + + $compiler->raw(";\n"); + } +} diff --git a/vendor/twig/twig/src/Node/IncludeNode.php b/vendor/twig/twig/src/Node/IncludeNode.php new file mode 100644 index 0000000..1c18292 --- /dev/null +++ b/vendor/twig/twig/src/Node/IncludeNode.php @@ -0,0 +1,110 @@ + + */ +#[YieldReady] +class IncludeNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno) + { + $nodes = ['expr' => $expr]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('ignore_missing')) { + $template = $compiler->getVarName(); + + $compiler + ->write(\sprintf("$%s = null;\n", $template)) + ->write("try {\n") + ->indent() + ->write(\sprintf('$%s = ', $template)) + ; + + $this->addGetTemplate($compiler); + + $compiler + ->raw(";\n") + ->outdent() + ->write("} catch (LoaderError \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n") + ->write(\sprintf("if ($%s) {\n", $template)) + ->indent() + ->write(\sprintf('yield from $%s->unwrap()->yield(', $template)) + ; + + $this->addTemplateArguments($compiler); + $compiler + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } else { + $compiler->write('yield from '); + $this->addGetTemplate($compiler); + $compiler->raw('->unwrap()->yield('); + $this->addTemplateArguments($compiler); + $compiler->raw(");\n"); + } + } + + protected function addGetTemplate(Compiler $compiler) + { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + protected function addTemplateArguments(Compiler $compiler) + { + if (!$this->hasNode('variables')) { + $compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]'); + } elseif (false === $this->getAttribute('only')) { + $compiler + ->raw('CoreExtension::merge($context, ') + ->subcompile($this->getNode('variables')) + ->raw(')') + ; + } else { + $compiler->raw('CoreExtension::toArray('); + $compiler->subcompile($this->getNode('variables')); + $compiler->raw(')'); + } + } +} diff --git a/vendor/twig/twig/src/Node/MacroNode.php b/vendor/twig/twig/src/Node/MacroNode.php new file mode 100644 index 0000000..5a2543a --- /dev/null +++ b/vendor/twig/twig/src/Node/MacroNode.php @@ -0,0 +1,106 @@ + + */ +#[YieldReady] +class MacroNode extends Node +{ + public const VARARGS_NAME = 'varargs'; + + /** + * @param BodyNode $body + */ + public function __construct(string $name, Node $body, Node $arguments, int $lineno) + { + if (!$body instanceof BodyNode) { + trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class)); + } + + foreach ($arguments as $argumentName => $argument) { + if (self::VARARGS_NAME === $argumentName) { + throw new SyntaxError(\sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext()); + } + } + + parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(\sprintf('public function macro_%s(', $this->getAttribute('name'))) + ; + + $count = \count($this->getNode('arguments')); + $pos = 0; + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->raw('$__'.$name.'__ = ') + ->subcompile($default) + ; + + if (++$pos < $count) { + $compiler->raw(', '); + } + } + + if ($count) { + $compiler->raw(', '); + } + + $compiler + ->raw('...$__varargs__') + ->raw(")\n") + ->write("{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ->write("\$context = [\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->write('') + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") + ; + } + + $node = new CaptureNode($this->getNode('body'), $this->getNode('body')->lineno); + + $compiler + ->write('') + ->string(self::VARARGS_NAME) + ->raw(' => ') + ->raw("\$__varargs__,\n") + ->outdent() + ->write("] + \$this->env->getGlobals();\n\n") + ->write("\$blocks = [];\n\n") + ->write('return ') + ->subcompile($node) + ->raw("\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/ModuleNode.php b/vendor/twig/twig/src/Node/ModuleNode.php new file mode 100644 index 0000000..d2fb216 --- /dev/null +++ b/vendor/twig/twig/src/Node/ModuleNode.php @@ -0,0 +1,489 @@ + + */ +#[YieldReady] +final class ModuleNode extends Node +{ + /** + * @param BodyNode $body + */ + public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source) + { + if (!$body instanceof BodyNode) { + trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class)); + } + + $nodes = [ + 'body' => $body, + 'blocks' => $blocks, + 'macros' => $macros, + 'traits' => $traits, + 'display_start' => new Node(), + 'display_end' => new Node(), + 'constructor_start' => new Node(), + 'constructor_end' => new Node(), + 'class_end' => new Node(), + ]; + if (null !== $parent) { + $nodes['parent'] = $parent; + } + + // embedded templates are set as attributes so that they are only visited once by the visitors + parent::__construct($nodes, [ + 'index' => null, + 'embedded_templates' => $embeddedTemplates, + ], 1); + + // populate the template name of all node children + $this->setSourceContext($source); + } + + public function setIndex($index) + { + $this->setAttribute('index', $index); + } + + public function compile(Compiler $compiler): void + { + $this->compileTemplate($compiler); + + foreach ($this->getAttribute('embedded_templates') as $template) { + $compiler->subcompile($template); + } + } + + protected function compileTemplate(Compiler $compiler) + { + if (!$this->getAttribute('index')) { + $compiler->write('compileClassHeader($compiler); + + $this->compileConstructor($compiler); + + $this->compileGetParent($compiler); + + $this->compileDisplay($compiler); + + $compiler->subcompile($this->getNode('blocks')); + + $this->compileMacros($compiler); + + $this->compileGetTemplateName($compiler); + + $this->compileIsTraitable($compiler); + + $this->compileDebugInfo($compiler); + + $this->compileGetSourceContext($compiler); + + $this->compileClassFooter($compiler); + } + + protected function compileGetParent(Compiler $compiler) + { + if (!$this->hasNode('parent')) { + return; + } + $parent = $this->getNode('parent'); + + $compiler + ->write("protected function doGetParent(array \$context): bool|string|Template|TemplateWrapper\n", "{\n") + ->indent() + ->addDebugInfo($parent) + ->write('return ') + ; + + if ($parent instanceof ConstantExpression) { + $compiler->subcompile($parent); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(')') + ; + } + + $compiler + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassHeader(Compiler $compiler) + { + $compiler + ->write("\n\n") + ; + if (!$this->getAttribute('index')) { + $compiler + ->write("use Twig\Environment;\n") + ->write("use Twig\Error\LoaderError;\n") + ->write("use Twig\Error\RuntimeError;\n") + ->write("use Twig\Extension\CoreExtension;\n") + ->write("use Twig\Extension\SandboxExtension;\n") + ->write("use Twig\Markup;\n") + ->write("use Twig\Sandbox\SecurityError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n") + ->write("use Twig\Source;\n") + ->write("use Twig\Template;\n") + ->write("use Twig\TemplateWrapper;\n") + ->write("\n") + ; + } + $compiler + // if the template name contains */, add a blank to avoid a PHP parse error + ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n") + ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index'))) + ->raw(" extends Template\n") + ->write("{\n") + ->indent() + ->write("private Source \$source;\n") + ->write("/**\n") + ->write(" * @var array\n") + ->write(" */\n") + ->write("private array \$macros = [];\n\n") + ; + } + + protected function compileConstructor(Compiler $compiler) + { + $compiler + ->write("public function __construct(Environment \$env)\n", "{\n") + ->indent() + ->subcompile($this->getNode('constructor_start')) + ->write("parent::__construct(\$env);\n\n") + ->write("\$this->source = \$this->getSourceContext();\n\n") + ; + + // parent + if (!$this->hasNode('parent')) { + $compiler->write("\$this->parent = false;\n\n"); + } + + $countTraits = \count($this->getNode('traits')); + if ($countTraits) { + // traits + foreach ($this->getNode('traits') as $i => $trait) { + $node = $trait->getNode('template'); + + $compiler + ->addDebugInfo($node) + ->write(\sprintf('$_trait_%s = $this->loadTemplate(', $i)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ->write(\sprintf("if (!\$_trait_%s->unwrap()->isTraitable()) {\n", $i)) + ->indent() + ->write("throw new RuntimeError('Template \"'.") + ->subcompile($trait->getNode('template')) + ->raw(".'\" cannot be used as a trait.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->source);\n") + ->outdent() + ->write("}\n") + ->write(\sprintf("\$_trait_%s_blocks = \$_trait_%s->unwrap()->getBlocks();\n\n", $i, $i)) + ; + + foreach ($trait->getNode('targets') as $key => $value) { + $compiler + ->write(\sprintf('if (!isset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("])) {\n") + ->indent() + ->write("throw new RuntimeError('Block ") + ->string($key) + ->raw(' is not defined in trait ') + ->subcompile($trait->getNode('template')) + ->raw(".', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->source);\n") + ->outdent() + ->write("}\n\n") + + ->write(\sprintf('$_trait_%s_blocks[', $i)) + ->subcompile($value) + ->raw(\sprintf('] = $_trait_%s_blocks[', $i)) + ->string($key) + ->raw(\sprintf(']; unset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("]);\n\n") + ; + } + } + + if ($countTraits > 1) { + $compiler + ->write("\$this->traits = array_merge(\n") + ->indent() + ; + + for ($i = 0; $i < $countTraits; ++$i) { + $compiler + ->write(\sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) + ; + } + + $compiler + ->outdent() + ->write(");\n\n") + ; + } else { + $compiler + ->write("\$this->traits = \$_trait_0_blocks;\n\n") + ; + } + + $compiler + ->write("\$this->blocks = array_merge(\n") + ->indent() + ->write("\$this->traits,\n") + ->write("[\n") + ; + } else { + $compiler + ->write("\$this->blocks = [\n") + ; + } + + // blocks + $compiler + ->indent() + ; + + foreach ($this->getNode('blocks') as $name => $node) { + $compiler + ->write(\sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name)) + ; + } + + if ($countTraits) { + $compiler + ->outdent() + ->write("]\n") + ->outdent() + ->write(");\n") + ; + } else { + $compiler + ->outdent() + ->write("];\n") + ; + } + + $compiler + ->subcompile($this->getNode('constructor_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDisplay(Compiler $compiler) + { + $compiler + ->write("protected function doDisplay(array \$context, array \$blocks = []): iterable\n", "{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ->subcompile($this->getNode('display_start')) + ->subcompile($this->getNode('body')) + ; + + if ($this->hasNode('parent')) { + $parent = $this->getNode('parent'); + + $compiler->addDebugInfo($parent); + if ($parent instanceof ConstantExpression) { + $compiler + ->write('$this->parent = $this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(");\n") + ; + } + $compiler->write('yield from '); + + if ($parent instanceof ConstantExpression) { + $compiler->raw('$this->parent'); + } else { + $compiler->raw('$this->getParent($context)'); + } + $compiler->raw("->unwrap()->yield(\$context, array_merge(\$this->blocks, \$blocks));\n"); + } + + $compiler->subcompile($this->getNode('display_end')); + + if (!$this->hasNode('parent')) { + $compiler->write("yield from [];\n"); + } + + $compiler + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassFooter(Compiler $compiler) + { + $compiler + ->subcompile($this->getNode('class_end')) + ->outdent() + ->write("}\n") + ; + } + + protected function compileMacros(Compiler $compiler) + { + $compiler->subcompile($this->getNode('macros')); + } + + protected function compileGetTemplateName(Compiler $compiler) + { + $compiler + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") + ->write("public function getTemplateName(): string\n", "{\n") + ->indent() + ->write('return ') + ->repr($this->getSourceContext()->getName()) + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileIsTraitable(Compiler $compiler) + { + // A template can be used as a trait if: + // * it has no parent + // * it has no macros + // * it has no body + // + // Put another way, a template can be used as a trait if it + // only contains blocks and use statements. + $traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros')); + if ($traitable) { + if ($this->getNode('body') instanceof BodyNode) { + $nodes = $this->getNode('body')->getNode('0'); + } else { + $nodes = $this->getNode('body'); + } + + if (!\count($nodes)) { + $nodes = new Node([$nodes]); + } + + foreach ($nodes as $node) { + if (!\count($node)) { + continue; + } + + $traitable = false; + break; + } + } + + if ($traitable) { + return; + } + + $compiler + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") + ->write("public function isTraitable(): bool\n", "{\n") + ->indent() + ->write("return false;\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDebugInfo(Compiler $compiler) + { + $compiler + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") + ->write("public function getDebugInfo(): array\n", "{\n") + ->indent() + ->write(\sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSourceContext(Compiler $compiler) + { + $compiler + ->write("public function getSourceContext(): Source\n", "{\n") + ->indent() + ->write('return new Source(') + ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '') + ->raw(', ') + ->string($this->getSourceContext()->getName()) + ->raw(', ') + ->string($this->getSourceContext()->getPath()) + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } + + protected function compileLoadTemplate(Compiler $compiler, $node, $var) + { + if ($node instanceof ConstantExpression) { + $compiler + ->write(\sprintf('%s = $this->loadTemplate(', $var)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ; + } else { + throw new \LogicException('Trait templates can only be constant nodes.'); + } + } +} diff --git a/vendor/twig/twig/src/Node/NameDeprecation.php b/vendor/twig/twig/src/Node/NameDeprecation.php new file mode 100644 index 0000000..63ab285 --- /dev/null +++ b/vendor/twig/twig/src/Node/NameDeprecation.php @@ -0,0 +1,46 @@ + + */ +class NameDeprecation +{ + private $package; + private $version; + private $newName; + + public function __construct(string $package = '', string $version = '', string $newName = '') + { + $this->package = $package; + $this->version = $version; + $this->newName = $newName; + } + + public function getPackage(): string + { + return $this->package; + } + + public function getVersion(): string + { + return $this->version; + } + + public function getNewName(): string + { + return $this->newName; + } +} diff --git a/vendor/twig/twig/src/Node/Node.php b/vendor/twig/twig/src/Node/Node.php new file mode 100644 index 0000000..2ccbcf6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Node.php @@ -0,0 +1,281 @@ + + */ +#[YieldReady] +class Node implements \Countable, \IteratorAggregate +{ + /** + * @var array + */ + protected $nodes; + protected $attributes; + protected $lineno; + protected $tag; + + private $sourceContext; + /** @var array */ + private $nodeNameDeprecations = []; + /** @var array */ + private $attributeNameDeprecations = []; + + /** + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number + */ + public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0) + { + foreach ($nodes as $name => $node) { + if (!$node instanceof self) { + throw new \InvalidArgumentException(\sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? $node::class : (null === $node ? 'null' : \gettype($node)), $name, static::class)); + } + } + $this->nodes = $nodes; + $this->attributes = $attributes; + $this->lineno = $lineno; + + if (\func_num_args() > 3) { + trigger_deprecation('twig/twig', '3.12', \sprintf('The "tag" constructor argument of the "%s" class is deprecated and ignored (check which TokenParser class set it to "%s"), the tag is now automatically set by the Parser when needed.', static::class, func_get_arg(3) ?: 'null')); + } + } + + public function __toString() + { + $repr = static::class; + + if ($this->tag) { + $repr .= \sprintf("\n tag: %s", $this->tag); + } + + $attributes = []; + foreach ($this->attributes as $name => $value) { + if (\is_callable($value)) { + $v = '\Closure'; + } elseif ($value instanceof \Stringable) { + $v = (string) $value; + } else { + $v = str_replace("\n", '', var_export($value, true)); + } + $attributes[] = \sprintf('%s: %s', $name, $v); + } + + if ($attributes) { + $repr .= \sprintf("\n attributes:\n %s", implode("\n ", $attributes)); + } + + if (\count($this->nodes)) { + $repr .= "\n nodes:"; + foreach ($this->nodes as $name => $node) { + $len = \strlen($name) + 6; + $noderepr = []; + foreach (explode("\n", (string) $node) as $line) { + $noderepr[] = str_repeat(' ', $len).$line; + } + + $repr .= \sprintf("\n %s: %s", $name, ltrim(implode("\n", $noderepr))); + } + } + + return $repr; + } + + /** + * @return void + */ + public function compile(Compiler $compiler) + { + foreach ($this->nodes as $node) { + $compiler->subcompile($node); + } + } + + public function getTemplateLine(): int + { + return $this->lineno; + } + + public function getNodeTag(): ?string + { + return $this->tag; + } + + /** + * @internal + */ + public function setNodeTag(string $tag): void + { + if ($this->tag) { + throw new \LogicException('The tag of a node can only be set once.'); + } + + $this->tag = $tag; + } + + public function hasAttribute(string $name): bool + { + return \array_key_exists($name, $this->attributes); + } + + public function getAttribute(string $name) + { + if (!\array_key_exists($name, $this->attributes)) { + throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); + } + + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { + $dep = $this->attributeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated, get the "%s" attribute instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + + return $this->attributes[$name]; + } + + public function setAttribute(string $name, $value): void + { + $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; + if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { + $dep = $this->attributeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated, set the "%s" attribute instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + + $this->attributes[$name] = $value; + } + + public function deprecateAttribute(string $name, NameDeprecation $dep): void + { + $this->attributeNameDeprecations[$name] = $dep; + } + + public function removeAttribute(string $name): void + { + unset($this->attributes[$name]); + } + + /** + * @param string|int $name + */ + public function hasNode(string $name): bool + { + return isset($this->nodes[$name]); + } + + /** + * @param string|int $name + */ + public function getNode(string $name): self + { + if (!isset($this->nodes[$name])) { + throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); + } + + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { + $dep = $this->nodeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated, get the "%s" node instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + + return $this->nodes[$name]; + } + + /** + * @param string|int $name + */ + public function setNode(string $name, self $node): void + { + $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; + if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { + $dep = $this->nodeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated, set the "%s" node instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + + if (null !== $this->sourceContext) { + $node->setSourceContext($this->sourceContext); + } + $this->nodes[$name] = $node; + } + + /** + * @param string|int $name + */ + public function removeNode(string $name): void + { + unset($this->nodes[$name]); + } + + /** + * @param string|int $name + */ + public function deprecateNode(string $name, NameDeprecation $dep): void + { + $this->nodeNameDeprecations[$name] = $dep; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->nodes); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->nodes); + } + + public function getTemplateName(): ?string + { + return $this->sourceContext ? $this->sourceContext->getName() : null; + } + + public function setSourceContext(Source $source): void + { + $this->sourceContext = $source; + foreach ($this->nodes as $node) { + $node->setSourceContext($source); + } + } + + public function getSourceContext(): ?Source + { + return $this->sourceContext; + } +} diff --git a/vendor/twig/twig/src/Node/NodeCaptureInterface.php b/vendor/twig/twig/src/Node/NodeCaptureInterface.php new file mode 100644 index 0000000..9fb6a0c --- /dev/null +++ b/vendor/twig/twig/src/Node/NodeCaptureInterface.php @@ -0,0 +1,21 @@ + + */ +interface NodeCaptureInterface +{ +} diff --git a/vendor/twig/twig/src/Node/NodeOutputInterface.php b/vendor/twig/twig/src/Node/NodeOutputInterface.php new file mode 100644 index 0000000..5e35b40 --- /dev/null +++ b/vendor/twig/twig/src/Node/NodeOutputInterface.php @@ -0,0 +1,21 @@ + + */ +interface NodeOutputInterface +{ +} diff --git a/vendor/twig/twig/src/Node/PrintNode.php b/vendor/twig/twig/src/Node/PrintNode.php new file mode 100644 index 0000000..e3c23bb --- /dev/null +++ b/vendor/twig/twig/src/Node/PrintNode.php @@ -0,0 +1,44 @@ + + */ +#[YieldReady] +class PrintNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, int $lineno) + { + parent::__construct(['expr' => $expr], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + /** @var AbstractExpression */ + $expr = $this->getNode('expr'); + + $compiler + ->addDebugInfo($this) + ->write($expr->isGenerator() ? 'yield from ' : 'yield ') + ->subcompile($expr) + ->raw(";\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/SandboxNode.php b/vendor/twig/twig/src/Node/SandboxNode.php new file mode 100644 index 0000000..d51cea4 --- /dev/null +++ b/vendor/twig/twig/src/Node/SandboxNode.php @@ -0,0 +1,54 @@ + + */ +#[YieldReady] +class SandboxNode extends Node +{ + public function __construct(Node $body, int $lineno) + { + parent::__construct(['body' => $body], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("if (!\$alreadySandboxed = \$this->sandbox->isSandboxed()) {\n") + ->indent() + ->write("\$this->sandbox->enableSandbox();\n") + ->outdent() + ->write("}\n") + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("} finally {\n") + ->indent() + ->write("if (!\$alreadySandboxed) {\n") + ->indent() + ->write("\$this->sandbox->disableSandbox();\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/SetNode.php b/vendor/twig/twig/src/Node/SetNode.php new file mode 100644 index 0000000..6772510 --- /dev/null +++ b/vendor/twig/twig/src/Node/SetNode.php @@ -0,0 +1,96 @@ + + */ +#[YieldReady] +class SetNode extends Node implements NodeCaptureInterface +{ + public function __construct(bool $capture, Node $names, Node $values, int $lineno) + { + /* + * Optimizes the node when capture is used for a large block of text. + * + * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo"); + */ + $safe = false; + if ($capture) { + $safe = true; + if ($values instanceof TextNode) { + $values = new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine()); + $capture = false; + } else { + $values = new CaptureNode($values, $values->getTemplateLine()); + } + } + + parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => $safe], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if (\count($this->getNode('names')) > 1) { + $compiler->write('['); + foreach ($this->getNode('names') as $idx => $node) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($node); + } + $compiler->raw(']'); + } else { + $compiler->subcompile($this->getNode('names'), false); + } + $compiler->raw(' = '); + + if ($this->getAttribute('capture')) { + $compiler->subcompile($this->getNode('values')); + } else { + if (\count($this->getNode('names')) > 1) { + $compiler->write('['); + foreach ($this->getNode('values') as $idx => $value) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($value); + } + $compiler->raw(']'); + } else { + if ($this->getAttribute('safe')) { + $compiler + ->raw("('' === \$tmp = ") + ->subcompile($this->getNode('values')) + ->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())") + ; + } else { + $compiler->subcompile($this->getNode('values')); + } + } + + $compiler->raw(';'); + } + + $compiler->raw("\n"); + } +} diff --git a/vendor/twig/twig/src/Node/TextNode.php b/vendor/twig/twig/src/Node/TextNode.php new file mode 100644 index 0000000..fae65fb --- /dev/null +++ b/vendor/twig/twig/src/Node/TextNode.php @@ -0,0 +1,41 @@ + + */ +#[YieldReady] +class TextNode extends Node implements NodeOutputInterface +{ + public function __construct(string $data, int $lineno) + { + parent::__construct([], ['data' => $data], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $compiler + ->write('yield ') + ->string($this->getAttribute('data')) + ->raw(";\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/TypesNode.php b/vendor/twig/twig/src/Node/TypesNode.php new file mode 100644 index 0000000..ebb304d --- /dev/null +++ b/vendor/twig/twig/src/Node/TypesNode.php @@ -0,0 +1,28 @@ + + */ +#[YieldReady] +class TypesNode extends Node +{ + /** + * @param array $types + */ + public function __construct(array $types, int $lineno) + { + parent::__construct([], ['mapping' => $types], $lineno); + } + + public function compile(Compiler $compiler) + { + // Don't compile anything. + } +} diff --git a/vendor/twig/twig/src/Node/WithNode.php b/vendor/twig/twig/src/Node/WithNode.php new file mode 100644 index 0000000..487e280 --- /dev/null +++ b/vendor/twig/twig/src/Node/WithNode.php @@ -0,0 +1,72 @@ + + */ +#[YieldReady] +class WithNode extends Node +{ + public function __construct(Node $body, ?Node $variables, bool $only, int $lineno) + { + $nodes = ['body' => $body]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => $only], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $parentContextName = $compiler->getVarName(); + + $compiler->write(\sprintf("\$%s = \$context;\n", $parentContextName)); + + if ($this->hasNode('variables')) { + $node = $this->getNode('variables'); + $varsName = $compiler->getVarName(); + $compiler + ->write(\sprintf('$%s = ', $varsName)) + ->subcompile($node) + ->raw(";\n") + ->write(\sprintf("if (!is_iterable(\$%s)) {\n", $varsName)) + ->indent() + ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a mapping.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->getSourceContext());\n") + ->outdent() + ->write("}\n") + ->write(\sprintf("\$%s = CoreExtension::toArray(\$%s);\n", $varsName, $varsName)) + ; + + if ($this->getAttribute('only')) { + $compiler->write("\$context = [];\n"); + } + + $compiler->write(\sprintf("\$context = \$%s + \$context + \$this->env->getGlobals();\n", $varsName)); + } + + $compiler + ->subcompile($this->getNode('body')) + ->write(\sprintf("\$context = \$%s;\n", $parentContextName)) + ; + } +} diff --git a/vendor/twig/twig/src/NodeTraverser.php b/vendor/twig/twig/src/NodeTraverser.php new file mode 100644 index 0000000..47a2d5c --- /dev/null +++ b/vendor/twig/twig/src/NodeTraverser.php @@ -0,0 +1,76 @@ + + */ +final class NodeTraverser +{ + private $env; + private $visitors = []; + + /** + * @param NodeVisitorInterface[] $visitors + */ + public function __construct(Environment $env, array $visitors = []) + { + $this->env = $env; + foreach ($visitors as $visitor) { + $this->addVisitor($visitor); + } + } + + public function addVisitor(NodeVisitorInterface $visitor): void + { + $this->visitors[$visitor->getPriority()][] = $visitor; + } + + /** + * Traverses a node and calls the registered visitors. + */ + public function traverse(Node $node): Node + { + ksort($this->visitors); + foreach ($this->visitors as $visitors) { + foreach ($visitors as $visitor) { + $node = $this->traverseForVisitor($visitor, $node); + } + } + + return $node; + } + + private function traverseForVisitor(NodeVisitorInterface $visitor, Node $node): ?Node + { + $node = $visitor->enterNode($node, $this->env); + + foreach ($node as $k => $n) { + if (null !== $m = $this->traverseForVisitor($visitor, $n)) { + if ($m !== $n) { + $node->setNode($k, $m); + } + } else { + $node->removeNode($k); + } + } + + return $visitor->leaveNode($node, $this->env); + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php new file mode 100644 index 0000000..5de35fd --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php @@ -0,0 +1,49 @@ + + * + * @deprecated since 3.9 (to be removed in 4.0) + */ +abstract class AbstractNodeVisitor implements NodeVisitorInterface +{ + final public function enterNode(Node $node, Environment $env): Node + { + return $this->doEnterNode($node, $env); + } + + final public function leaveNode(Node $node, Environment $env): ?Node + { + return $this->doLeaveNode($node, $env); + } + + /** + * Called before child nodes are visited. + * + * @return Node The modified node + */ + abstract protected function doEnterNode(Node $node, Environment $env); + + /** + * Called after child nodes are visited. + * + * @return Node|null The modified node or null if the node must be removed + */ + abstract protected function doLeaveNode(Node $node, Environment $env); +} diff --git a/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php new file mode 100644 index 0000000..32f49ab --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php @@ -0,0 +1,209 @@ + + * + * @internal + */ +final class EscaperNodeVisitor implements NodeVisitorInterface +{ + private $statusStack = []; + private $blocks = []; + private $safeAnalysis; + private $traverser; + private $defaultStrategy = false; + private $safeVars = []; + + public function __construct() + { + $this->safeAnalysis = new SafeAnalysisNodeVisitor(); + } + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) { + $this->defaultStrategy = $defaultStrategy; + } + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof AutoEscapeNode) { + $this->statusStack[] = $node->getAttribute('value'); + } elseif ($node instanceof BlockNode) { + $this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping(); + } elseif ($node instanceof ImportNode) { + $this->safeVars[] = $node->getNode('var')->getAttribute('name'); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $this->defaultStrategy = false; + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof FilterExpression) { + return $this->preEscapeFilterNode($node, $env); + } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) { + $expression = $node->getNode('expr'); + if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { + return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); + } + + return $this->escapePrintNode($node, $env, $type); + } + + if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { + array_pop($this->statusStack); + } elseif ($node instanceof BlockReferenceNode) { + $this->blocks[$node->getAttribute('name')] = $this->needEscaping(); + } + + return $node; + } + + private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, string $type): bool + { + $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env); + $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env); + + return $expr2Safe !== $expr3Safe; + } + + private function unwrapConditional(ConditionalExpression $expression, Environment $env, string $type): ConditionalExpression + { + // convert "echo a ? b : c" to "a ? echo b : echo c" recursively + $expr2 = $expression->getNode('expr2'); + if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) { + $expr2 = $this->unwrapConditional($expr2, $env, $type); + } else { + $expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type); + } + $expr3 = $expression->getNode('expr3'); + if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) { + $expr3 = $this->unwrapConditional($expr3, $env, $type); + } else { + $expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type); + } + + return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine()); + } + + private function escapeInlinePrintNode(InlinePrint $node, Environment $env, string $type): Node + { + $expression = $node->getNode('node'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + return new InlinePrint($this->getEscaperFilter($env, $type, $expression), $node->getTemplateLine()); + } + + private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node + { + $expression = $node->getNode('expr'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + $class = \get_class($node); + + return new $class($this->getEscaperFilter($env, $type, $expression), $node->getTemplateLine()); + } + + private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression + { + if ($filter->hasAttribute('twig_callable')) { + $type = $filter->getAttribute('twig_callable')->getPreEscape(); + } else { + // legacy + $name = $filter->getNode('filter', false)->getAttribute('value'); + $type = $env->getFilter($name)->getPreEscape(); + } + + if (null === $type) { + return $filter; + } + + $node = $filter->getNode('node'); + if ($this->isSafeFor($type, $node, $env)) { + return $filter; + } + + $filter->setNode('node', $this->getEscaperFilter($env, $type, $node)); + + return $filter; + } + + private function isSafeFor(string $type, Node $expression, Environment $env): bool + { + $safe = $this->safeAnalysis->getSafe($expression); + + if (null === $safe) { + if (null === $this->traverser) { + $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]); + } + + $this->safeAnalysis->setSafeVars($this->safeVars); + + $this->traverser->traverse($expression); + $safe = $this->safeAnalysis->getSafe($expression); + } + + return \in_array($type, $safe) || \in_array('all', $safe); + } + + private function needEscaping() + { + if (\count($this->statusStack)) { + return $this->statusStack[\count($this->statusStack) - 1]; + } + + return $this->defaultStrategy ?: false; + } + + private function getEscaperFilter(Environment $env, string $type, Node $node): FilterExpression + { + $line = $node->getTemplateLine(); + $filter = $env->getFilter('escape'); + $args = new Node([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + + return new FilterExpression($node, $filter, $args, $line); + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php new file mode 100644 index 0000000..01d5a99 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -0,0 +1,74 @@ + + * + * @internal + */ +final class MacroAutoImportNodeVisitor implements NodeVisitorInterface +{ + private $inAModule = false; + private $hasMacroCalls = false; + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->hasMacroCalls = false; + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + if ($this->hasMacroCalls) { + $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, true)); + } + } elseif ($this->inAModule) { + if ( + $node instanceof GetAttrExpression + && $node->getNode('node') instanceof NameExpression + && '_self' === $node->getNode('node')->getAttribute('name') + && $node->getNode('attribute') instanceof ConstantExpression + ) { + $this->hasMacroCalls = true; + + $name = $node->getNode('attribute')->getAttribute('value'); + $node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + } + + return $node; + } + + public function getPriority(): int + { + // we must be ran before auto-escaping + return -10; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php b/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php new file mode 100644 index 0000000..59e836d --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php @@ -0,0 +1,46 @@ + + */ +interface NodeVisitorInterface +{ + /** + * Called before child nodes are visited. + * + * @return Node The modified node + */ + public function enterNode(Node $node, Environment $env): Node; + + /** + * Called after child nodes are visited. + * + * @return Node|null The modified node or null if the node must be removed + */ + public function leaveNode(Node $node, Environment $env): ?Node; + + /** + * Returns the priority for this visitor. + * + * Priority should be between -10 and 10 (0 is the default). + * + * @return int The priority level + */ + public function getPriority(); +} diff --git a/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php new file mode 100644 index 0000000..a943f45 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php @@ -0,0 +1,213 @@ + + * + * @internal + */ +final class OptimizerNodeVisitor implements NodeVisitorInterface +{ + public const OPTIMIZE_ALL = -1; + public const OPTIMIZE_NONE = 0; + public const OPTIMIZE_FOR = 2; + public const OPTIMIZE_RAW_FILTER = 4; + public const OPTIMIZE_TEXT_NODES = 8; + + private $loops = []; + private $loopsTargets = []; + + /** + * @param int $optimizers The optimizer mode + */ + public function __construct( + private int $optimizers = -1, + ) { + if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_TEXT_NODES)) { + throw new \InvalidArgumentException(\sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + } + + if (-1 !== $optimizers && self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $optimizers)) { + trigger_deprecation('twig/twig', '3.11', 'The "Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER" option is deprecated and does nothing.'); + } + + if (-1 !== $optimizers && self::OPTIMIZE_TEXT_NODES === (self::OPTIMIZE_TEXT_NODES & $optimizers)) { + trigger_deprecation('twig/twig', '3.12', 'The "Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES" option is deprecated and does nothing.'); + } + } + + public function enterNode(Node $node, Environment $env): Node + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->enterOptimizeFor($node); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->leaveOptimizeFor($node); + } + + $node = $this->optimizePrintNode($node); + + return $node; + } + + /** + * Optimizes print nodes. + * + * It replaces: + * + * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" + */ + private function optimizePrintNode(Node $node): Node + { + if (!$node instanceof PrintNode) { + return $node; + } + + $exprNode = $node->getNode('expr'); + + if ($exprNode instanceof ConstantExpression && \is_string($exprNode->getAttribute('value'))) { + return new TextNode($exprNode->getAttribute('value'), $exprNode->getTemplateLine()); + } + + if ( + $exprNode instanceof BlockReferenceExpression + || $exprNode instanceof ParentExpression + ) { + $exprNode->setAttribute('output', true); + + return $exprNode; + } + + return $node; + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + private function enterOptimizeFor(Node $node): void + { + if ($node instanceof ForNode) { + // disable the loop variable by default + $node->setAttribute('with_loop', false); + array_unshift($this->loops, $node); + array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); + array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); + } elseif (!$this->loops) { + // we are outside a loop + return; + } + + // when do we need to add the loop variable back? + + // the loop variable is referenced for the current loop + elseif ($node instanceof NameExpression && 'loop' === $node->getAttribute('name')) { + $node->setAttribute('always_defined', true); + $this->addLoopToCurrent(); + } + + // optimize access to loop targets + elseif ($node instanceof NameExpression && \in_array($node->getAttribute('name'), $this->loopsTargets)) { + $node->setAttribute('always_defined', true); + } + + // block reference + elseif ($node instanceof BlockReferenceNode || $node instanceof BlockReferenceExpression) { + $this->addLoopToCurrent(); + } + + // include without the only attribute + elseif ($node instanceof IncludeNode && !$node->getAttribute('only')) { + $this->addLoopToAll(); + } + + // include function without the with_context=false parameter + elseif ($node instanceof FunctionExpression + && 'include' === $node->getAttribute('name') + && (!$node->getNode('arguments')->hasNode('with_context') + || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') + ) + ) { + $this->addLoopToAll(); + } + + // the loop variable is referenced via an attribute + elseif ($node instanceof GetAttrExpression + && (!$node->getNode('attribute') instanceof ConstantExpression + || 'parent' === $node->getNode('attribute')->getAttribute('value') + ) + && (true === $this->loops[0]->getAttribute('with_loop') + || ($node->getNode('node') instanceof NameExpression + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) + ) { + $this->addLoopToAll(); + } + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + private function leaveOptimizeFor(Node $node): void + { + if ($node instanceof ForNode) { + array_shift($this->loops); + array_shift($this->loopsTargets); + array_shift($this->loopsTargets); + } + } + + private function addLoopToCurrent(): void + { + $this->loops[0]->setAttribute('with_loop', true); + } + + private function addLoopToAll(): void + { + foreach ($this->loops as $loop) { + $loop->setAttribute('with_loop', true); + } + } + + public function getPriority(): int + { + return 255; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php new file mode 100644 index 0000000..0767216 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -0,0 +1,170 @@ +safeVars = $safeVars; + } + + public function getSafe(Node $node) + { + $hash = spl_object_hash($node); + if (!isset($this->data[$hash])) { + return; + } + + foreach ($this->data[$hash] as $bucket) { + if ($bucket['key'] !== $node) { + continue; + } + + if (\in_array('html_attr', $bucket['value'])) { + $bucket['value'][] = 'html'; + } + + return $bucket['value']; + } + } + + private function setSafe(Node $node, array $safe): void + { + $hash = spl_object_hash($node); + if (isset($this->data[$hash])) { + foreach ($this->data[$hash] as &$bucket) { + if ($bucket['key'] === $node) { + $bucket['value'] = $safe; + + return; + } + } + } + $this->data[$hash][] = [ + 'key' => $node, + 'value' => $safe, + ]; + } + + public function enterNode(Node $node, Environment $env): Node + { + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ConstantExpression) { + // constants are marked safe for all + $this->setSafe($node, ['all']); + } elseif ($node instanceof BlockReferenceExpression) { + // blocks are safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ParentExpression) { + // parent block is safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ConditionalExpression) { + // intersect safeness of both operands + $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); + $this->setSafe($node, $safe); + } elseif ($node instanceof FilterExpression) { + // filter expression is safe when the filter is safe + if ($node->hasAttribute('twig_callable')) { + $filter = $node->getAttribute('twig_callable'); + } else { + // legacy + $filter = $env->getFilter($node->getAttribute('name')); + } + + if ($filter) { + $safe = $filter->getSafe($node->getNode('arguments')); + if (null === $safe) { + $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); + } + $this->setSafe($node, $safe); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof FunctionExpression) { + // function expression is safe when the function is safe + if ($node->hasAttribute('twig_callable')) { + $function = $node->getAttribute('twig_callable'); + } else { + // legacy + $function = $env->getFunction($node->getAttribute('name')); + } + + if ($function) { + $this->setSafe($node, $function->getSafe($node->getNode('arguments'))); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof MethodCallExpression) { + if ($node->getAttribute('safe')) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { + $name = $node->getNode('node')->getAttribute('name'); + if (\in_array($name, $this->safeVars)) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } else { + $this->setSafe($node, []); + } + + return $node; + } + + private function intersectSafe(?array $a = null, ?array $b = null): array + { + if (null === $a || null === $b) { + return []; + } + + if (\in_array('all', $a)) { + return $b; + } + + if (\in_array('all', $b)) { + return $a; + } + + return array_intersect($a, $b); + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php new file mode 100644 index 0000000..37e184a --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php @@ -0,0 +1,139 @@ + + * + * @internal + */ +final class SandboxNodeVisitor implements NodeVisitorInterface +{ + private $inAModule = false; + /** @var array */ + private $tags; + /** @var array */ + private $filters; + /** @var array */ + private $functions; + private $needsToStringWrap = false; + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->tags = []; + $this->filters = []; + $this->functions = []; + + return $node; + } elseif ($this->inAModule) { + // look for tags + if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { + $this->tags[$node->getNodeTag()] = $node->getTemplateLine(); + } + + // look for filters + if ($node instanceof FilterExpression && !isset($this->filters[$node->getAttribute('name')])) { + $this->filters[$node->getAttribute('name')] = $node->getTemplateLine(); + } + + // look for functions + if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) { + $this->functions[$node->getAttribute('name')] = $node->getTemplateLine(); + } + + // the .. operator is equivalent to the range() function + if ($node instanceof RangeBinary && !isset($this->functions['range'])) { + $this->functions['range'] = $node->getTemplateLine(); + } + + if ($node instanceof PrintNode) { + $this->needsToStringWrap = true; + $this->wrapNode($node, 'expr'); + } + + if ($node instanceof SetNode && !$node->getAttribute('capture')) { + $this->needsToStringWrap = true; + } + + // wrap outer nodes that can implicitly call __toString() + if ($this->needsToStringWrap) { + if ($node instanceof ConcatBinary) { + $this->wrapNode($node, 'left'); + $this->wrapNode($node, 'right'); + } + if ($node instanceof FilterExpression) { + $this->wrapNode($node, 'node'); + $this->wrapArrayNode($node, 'arguments'); + } + if ($node instanceof FunctionExpression) { + $this->wrapArrayNode($node, 'arguments'); + } + } + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + + $node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')])); + $node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')])); + } elseif ($this->inAModule) { + if ($node instanceof PrintNode || $node instanceof SetNode) { + $this->needsToStringWrap = false; + } + } + + return $node; + } + + private function wrapNode(Node $node, string $name): void + { + $expr = $node->getNode($name); + if (($expr instanceof NameExpression || $expr instanceof GetAttrExpression) && !$expr->isGenerator()) { + $node->setNode($name, new CheckToStringNode($expr)); + } + } + + private function wrapArrayNode(Node $node, string $name): void + { + $args = $node->getNode($name); + foreach ($args as $name => $_) { + $this->wrapNode($args, $name); + } + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php new file mode 100644 index 0000000..4b190b4 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php @@ -0,0 +1,59 @@ +yieldReadyNodes[$class])) { + return $node; + } + + if (!$this->yieldReadyNodes[$class] = (bool) (new \ReflectionClass($class))->getAttributes(YieldReady::class)) { + if ($this->useYield) { + throw new \LogicException(\sprintf('You cannot enable the "use_yield" option of Twig as node "%s" is not marked as ready for it; please make it ready and then flag it with the #[YieldReady] attribute.', $class)); + } + + trigger_deprecation('twig/twig', '3.9', 'Twig node "%s" is not marked as ready for using "yield" instead of "echo"; please make it ready and then flag it with the #[YieldReady] attribute.', $class); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + return $node; + } + + public function getPriority(): int + { + return 255; + } +} diff --git a/vendor/twig/twig/src/Parser.php b/vendor/twig/twig/src/Parser.php new file mode 100644 index 0000000..40370bb --- /dev/null +++ b/vendor/twig/twig/src/Parser.php @@ -0,0 +1,383 @@ + + */ +class Parser +{ + private $stack = []; + private $stream; + private $parent; + private $visitors; + private $expressionParser; + private $blocks; + private $blockStack; + private $macros; + private $importedSymbols; + private $traits; + private $embeddedTemplates = []; + private $varNameSalt = 0; + + public function __construct( + private Environment $env, + ) { + } + + public function getVarName(): string + { + return \sprintf('__internal_parse_%d', $this->varNameSalt++); + } + + public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode + { + $vars = get_object_vars($this); + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']); + $this->stack[] = $vars; + + // node visitors + if (null === $this->visitors) { + $this->visitors = $this->env->getNodeVisitors(); + } + + if (null === $this->expressionParser) { + $this->expressionParser = new ExpressionParser($this, $this->env); + } + + $this->stream = $stream; + $this->parent = null; + $this->blocks = []; + $this->macros = []; + $this->traits = []; + $this->blockStack = []; + $this->importedSymbols = [[]]; + $this->embeddedTemplates = []; + + try { + $body = $this->subparse($test, $dropNeedle); + + if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) { + $body = new Node(); + } + } catch (SyntaxError $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->stream->getSourceContext()); + } + + if (!$e->getTemplateLine()) { + $e->setTemplateLine($this->getCurrentToken()->getLine()); + } + + throw $e; + } + + $node = new ModuleNode(new BodyNode([$body]), $this->parent, new Node($this->blocks), new Node($this->macros), new Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext()); + + $traverser = new NodeTraverser($this->env, $this->visitors); + + /** + * @var ModuleNode $node + */ + $node = $traverser->traverse($node); + + // restore previous stack so previous parse() call can resume working + foreach (array_pop($this->stack) as $key => $val) { + $this->$key = $val; + } + + return $node; + } + + public function subparse($test, bool $dropNeedle = false): Node + { + $lineno = $this->getCurrentToken()->getLine(); + $rv = []; + while (!$this->stream->isEOF()) { + switch ($this->getCurrentToken()->getType()) { + case Token::TEXT_TYPE: + $token = $this->stream->next(); + $rv[] = new TextNode($token->getValue(), $token->getLine()); + break; + + case Token::VAR_START_TYPE: + $token = $this->stream->next(); + $expr = $this->expressionParser->parseExpression(); + $this->stream->expect(Token::VAR_END_TYPE); + $rv[] = new PrintNode($expr, $token->getLine()); + break; + + case Token::BLOCK_START_TYPE: + $this->stream->next(); + $token = $this->getCurrentToken(); + + if (Token::NAME_TYPE !== $token->getType()) { + throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); + } + + if (null !== $test && $test($token)) { + if ($dropNeedle) { + $this->stream->next(); + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + if (!$subparser = $this->env->getTokenParser($token->getValue())) { + if (null !== $test) { + $e = new SyntaxError(\sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + + $callable = (new ReflectionCallable(new TwigTest('decision', $test)))->getCallable(); + if (\is_array($callable) && $callable[0] instanceof TokenParserInterface) { + $e->appendMessage(\sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $callable[0]->getTag(), $lineno)); + } + } else { + $e = new SyntaxError(\sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers())); + } + + throw $e; + } + + $this->stream->next(); + + $subparser->setParser($this); + $node = $subparser->parse($token); + if (!$node) { + trigger_deprecation('twig/twig', '3.12', 'Returning "null" from "%s" is deprecated and forbidden by "TokenParserInterface".', $subparser::class); + } else { + $node->setNodeTag($subparser->getTag()); + $rv[] = $node; + } + break; + + default: + throw new SyntaxError('The lexer or the parser ended up in an unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); + } + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + public function getBlockStack(): array + { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + + return $this->blockStack; + } + + public function peekBlockStack() + { + return $this->blockStack[\count($this->blockStack) - 1] ?? null; + } + + public function popBlockStack(): void + { + array_pop($this->blockStack); + } + + public function pushBlockStack($name): void + { + $this->blockStack[] = $name; + } + + public function hasBlock(string $name): bool + { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + + return isset($this->blocks[$name]); + } + + public function getBlock(string $name): Node + { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + + return $this->blocks[$name]; + } + + public function setBlock(string $name, BlockNode $value): void + { + if (isset($this->blocks[$name])) { + throw new SyntaxError(\sprintf("The block '%s' has already been defined line %d.", $name, $this->blocks[$name]->getTemplateLine()), $this->getCurrentToken()->getLine(), $this->blocks[$name]->getSourceContext()); + } + + $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine()); + } + + public function hasMacro(string $name): bool + { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + + return isset($this->macros[$name]); + } + + public function setMacro(string $name, MacroNode $node): void + { + $this->macros[$name] = $node; + } + + public function addTrait($trait): void + { + $this->traits[] = $trait; + } + + public function hasTraits(): bool + { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + + return \count($this->traits) > 0; + } + + public function embedTemplate(ModuleNode $template) + { + $template->setIndex(mt_rand()); + + $this->embeddedTemplates[] = $template; + } + + public function addImportedSymbol(string $type, string $alias, ?string $name = null, ?AbstractExpression $node = null): void + { + $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; + } + + public function getImportedSymbol(string $type, string $alias) + { + // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) + return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null); + } + + public function isMainScope(): bool + { + return 1 === \count($this->importedSymbols); + } + + public function pushLocalScope(): void + { + array_unshift($this->importedSymbols, []); + } + + public function popLocalScope(): void + { + array_shift($this->importedSymbols); + } + + public function getExpressionParser(): ExpressionParser + { + return $this->expressionParser; + } + + public function getParent(): ?Node + { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + + return $this->parent; + } + + public function hasInheritance() + { + return $this->parent || 0 < \count($this->traits); + } + + public function setParent(?Node $parent): void + { + if (null === $parent) { + trigger_deprecation('twig/twig', '3.12', 'Passing "null" to "%s()" is deprecated.', __METHOD__); + } + + if (null !== $this->parent) { + throw new SyntaxError('Multiple extends tags are forbidden.', $parent->getTemplateLine(), $parent->getSourceContext()); + } + + $this->parent = $parent; + } + + public function getStream(): TokenStream + { + return $this->stream; + } + + public function getCurrentToken(): Token + { + return $this->stream->getCurrent(); + } + + private function filterBodyNodes(Node $node, bool $nested = false): ?Node + { + // check that the body does not contain non-empty output nodes + if ( + ($node instanceof TextNode && !ctype_space($node->getAttribute('data'))) + || (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) + ) { + if (str_contains((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { + $t = substr($node->getAttribute('data'), 3); + if ('' === $t || ctype_space($t)) { + // bypass empty nodes starting with a BOM + return null; + } + } + + throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + // bypass nodes that "capture" the output + if ($node instanceof NodeCaptureInterface) { + // a "block" tag in such a node will serve as a block definition AND be displayed in place as well + return $node; + } + + // "block" tags that are not captured (see above) are only used for defining + // the content of the block. In such a case, nesting it does not work as + // expected as the definition is not part of the default template code flow. + if ($nested && $node instanceof BlockReferenceNode) { + throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + if ($node instanceof NodeOutputInterface) { + return null; + } + + // here, $nested means "being at the root level of a child template" + // we need to discard the wrapping "Node" for the "body" node + $nested = $nested || Node::class !== \get_class($node); + foreach ($node as $k => $n) { + if (null !== $n && null === $this->filterBodyNodes($n, $nested)) { + $node->removeNode($k); + } + } + + return $node; + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php b/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php new file mode 100644 index 0000000..267718c --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php @@ -0,0 +1,63 @@ + + */ +abstract class BaseDumper +{ + private $root; + + public function dump(Profile $profile): string + { + return $this->dumpProfile($profile); + } + + abstract protected function formatTemplate(Profile $profile, $prefix): string; + + abstract protected function formatNonTemplate(Profile $profile, $prefix): string; + + abstract protected function formatTime(Profile $profile, $percent): string; + + private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string + { + if ($profile->isRoot()) { + $this->root = $profile->getDuration(); + $start = $profile->getName(); + } else { + if ($profile->isTemplate()) { + $start = $this->formatTemplate($profile, $prefix); + } else { + $start = $this->formatNonTemplate($profile, $prefix); + } + $prefix .= $sibling ? '│ ' : ' '; + } + + $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0; + + if ($profile->getDuration() * 1000 < 1) { + $str = $start."\n"; + } else { + $str = \sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); + } + + $nCount = \count($profile->getProfiles()); + foreach ($profile as $i => $p) { + $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount); + } + + return $str; + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php b/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php new file mode 100644 index 0000000..bb3fbb5 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php @@ -0,0 +1,72 @@ + + */ +final class BlackfireDumper +{ + public function dump(Profile $profile): string + { + $data = []; + $this->dumpProfile('main()', $profile, $data); + $this->dumpChildren('main()', $profile, $data); + + $start = \sprintf('%f', microtime(true)); + $str = << $values) { + $str .= "$name//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + } + + return $str; + } + + private function dumpChildren(string $parent, Profile $profile, &$data) + { + foreach ($profile as $p) { + if ($p->isTemplate()) { + $name = $p->getTemplate(); + } else { + $name = \sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); + } + $this->dumpProfile(\sprintf('%s==>%s', $parent, $name), $p, $data); + $this->dumpChildren($name, $p, $data); + } + } + + private function dumpProfile(string $edge, Profile $profile, &$data) + { + if (isset($data[$edge])) { + ++$data[$edge]['ct']; + $data[$edge]['wt'] += floor($profile->getDuration() * 1000000); + $data[$edge]['mu'] += $profile->getMemoryUsage(); + $data[$edge]['pmu'] += $profile->getPeakMemoryUsage(); + } else { + $data[$edge] = [ + 'ct' => 1, + 'wt' => floor($profile->getDuration() * 1000000), + 'mu' => $profile->getMemoryUsage(), + 'pmu' => $profile->getPeakMemoryUsage(), + ]; + } + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php b/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php new file mode 100644 index 0000000..cdab2de --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php @@ -0,0 +1,47 @@ + + */ +final class HtmlDumper extends BaseDumper +{ + private static $colors = [ + 'block' => '#dfd', + 'macro' => '#ddf', + 'template' => '#ffd', + 'big' => '#d44', + ]; + + public function dump(Profile $profile): string + { + return '
    '.parent::dump($profile).'
    '; + } + + protected function formatTemplate(Profile $profile, $prefix): string + { + return \sprintf('%sâ”” %s', $prefix, self::$colors['template'], $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix): string + { + return \sprintf('%sâ”” %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent): string + { + return \sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php b/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php new file mode 100644 index 0000000..1c1f77e --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php @@ -0,0 +1,35 @@ + + */ +final class TextDumper extends BaseDumper +{ + protected function formatTemplate(Profile $profile, $prefix): string + { + return \sprintf('%sâ”” %s', $prefix, $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix): string + { + return \sprintf('%sâ”” %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent): string + { + return \sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); + } +} diff --git a/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php b/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php new file mode 100644 index 0000000..4d8e504 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php @@ -0,0 +1,44 @@ + + */ +#[YieldReady] +class EnterProfileNode extends Node +{ + public function __construct(string $extensionName, string $type, string $name, string $varName) + { + parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write(\sprintf('$%s = $this->extensions[', $this->getAttribute('var_name'))) + ->repr($this->getAttribute('extension_name')) + ->raw("];\n") + ->write(\sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->repr($this->getAttribute('type')) + ->raw(', ') + ->repr($this->getAttribute('name')) + ->raw("));\n\n") + ; + } +} diff --git a/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php b/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php new file mode 100644 index 0000000..bd9227e --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php @@ -0,0 +1,38 @@ + + */ +#[YieldReady] +class LeaveProfileNode extends Node +{ + public function __construct(string $varName) + { + parent::__construct([], ['var_name' => $varName]); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write("\n") + ->write(\sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ; + } +} diff --git a/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php new file mode 100644 index 0000000..1458bc5 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -0,0 +1,69 @@ + + */ +final class ProfilerNodeVisitor implements NodeVisitorInterface +{ + private $varName; + + public function __construct( + private string $extensionName, + ) { + $this->varName = \sprintf('__internal_%s', hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName)); + } + + public function enterNode(Node $node, Environment $env): Node + { + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $this->varName), $node->getNode('display_start')])); + $node->setNode('display_end', new Node([new LeaveProfileNode($this->varName), $node->getNode('display_end')])); + } elseif ($node instanceof BlockNode) { + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $this->varName), + $node->getNode('body'), + new LeaveProfileNode($this->varName), + ])); + } elseif ($node instanceof MacroNode) { + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $this->varName), + $node->getNode('body'), + new LeaveProfileNode($this->varName), + ])); + } + + return $node; + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/Profiler/Profile.php b/vendor/twig/twig/src/Profiler/Profile.php new file mode 100644 index 0000000..2928e16 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Profile.php @@ -0,0 +1,178 @@ + + */ +final class Profile implements \IteratorAggregate, \Serializable +{ + public const ROOT = 'ROOT'; + public const BLOCK = 'block'; + public const TEMPLATE = 'template'; + public const MACRO = 'macro'; + private $starts = []; + private $ends = []; + private $profiles = []; + + public function __construct( + private string $template = 'main', + private string $type = self::ROOT, + private string $name = 'main', + ) { + $this->name = str_starts_with($name, '__internal_') ? 'INTERNAL' : $name; + $this->enter(); + } + + public function getTemplate(): string + { + return $this->template; + } + + public function getType(): string + { + return $this->type; + } + + public function getName(): string + { + return $this->name; + } + + public function isRoot(): bool + { + return self::ROOT === $this->type; + } + + public function isTemplate(): bool + { + return self::TEMPLATE === $this->type; + } + + public function isBlock(): bool + { + return self::BLOCK === $this->type; + } + + public function isMacro(): bool + { + return self::MACRO === $this->type; + } + + /** + * @return Profile[] + */ + public function getProfiles(): array + { + return $this->profiles; + } + + public function addProfile(self $profile): void + { + $this->profiles[] = $profile; + } + + /** + * Returns the duration in microseconds. + */ + public function getDuration(): float + { + if ($this->isRoot() && $this->profiles) { + // for the root node with children, duration is the sum of all child durations + $duration = 0; + foreach ($this->profiles as $profile) { + $duration += $profile->getDuration(); + } + + return $duration; + } + + return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; + } + + /** + * Returns the memory usage in bytes. + */ + public function getMemoryUsage(): int + { + return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; + } + + /** + * Returns the peak memory usage in bytes. + */ + public function getPeakMemoryUsage(): int + { + return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; + } + + /** + * Starts the profiling. + */ + public function enter(): void + { + $this->starts = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + /** + * Stops the profiling. + */ + public function leave(): void + { + $this->ends = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + public function reset(): void + { + $this->starts = $this->ends = $this->profiles = []; + $this->enter(); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->profiles); + } + + public function serialize(): string + { + return serialize($this->__serialize()); + } + + public function unserialize($data): void + { + $this->__unserialize(unserialize($data)); + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles] = $data; + } +} diff --git a/vendor/twig/twig/src/Resources/core.php b/vendor/twig/twig/src/Resources/core.php new file mode 100644 index 0000000..6e2fcfb --- /dev/null +++ b/vendor/twig/twig/src/Resources/core.php @@ -0,0 +1,541 @@ +getCharset(), $values, $max); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->formatDate($date, $format, $timezone); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_date_modify_filter(Environment $env, $date, $modifier) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->modifyDate($date, $modifier); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_sprintf($format, ...$values) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::sprintf($format, ...$values); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_date_converter(Environment $env, $date = null, $timezone = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->convertDate($date, $timezone); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_replace_filter($str, $from) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::replace($str, $from); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_round($value, $precision = 0, $method = 'common') +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::round($value, $precision, $method); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->formatNumber($number, $decimal, $decimalPoint, $thousandSep); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_urlencode_filter($url) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::urlencode($url); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_merge(...$arrays) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::merge(...$arrays); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::slice($env->getCharset(), $item, $start, $length, $preserveKeys); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_first(Environment $env, $item) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::first($env->getCharset(), $item); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_last(Environment $env, $item) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::last($env->getCharset(), $item); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_join_filter($value, $glue = '', $and = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::join($value, $glue, $and); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::split($env->getCharset(), $value, $delimiter, $limit); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_get_array_keys_filter($array) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::keys($array); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::reverse($env->getCharset(), $item, $preserveKeys); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_sort_filter(Environment $env, $array, $arrow = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::sort($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_matches(string $regexp, ?string $str) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::matches($regexp, $str); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_trim_filter($string, $characterMask = null, $side = 'both') +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::trim($string, $characterMask, $side); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_nl2br($string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::nl2br($string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_spaceless($content) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::spaceless($content); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_convert_encoding($string, $to, $from) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::convertEncoding($string, $to, $from); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_length_filter(Environment $env, $thing) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::length($env->getCharset(), $thing); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_upper_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::upper($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_lower_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::lower($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_striptags($string, $allowable_tags = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::striptags($string, $allowable_tags); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_title_string_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::titleCase($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_capitalize_string_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::capitalize($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_test_empty($value) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::testEmpty($value); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_test_iterable($value) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return is_iterable($value); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::include($env, $context, $template, $variables, $withContext, $ignoreMissing, $sandboxed); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_source(Environment $env, $name, $ignoreMissing = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::source($env, $name, $ignoreMissing); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_constant($constant, $object = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::constant($constant, $object); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_constant_is_defined($constant, $object = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::constant($constant, $object, true); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::batch($items, $size, $fill, $preserveKeys); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_column($array, $name, $index = null): array +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::column($array, $name, $index); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_filter(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::filter($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_map(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::map($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::reduce($env, $array, $arrow, $initial); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_some(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::arraySome($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_every(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::arrayEvery($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::checkArrowInSandbox($env, $arrow, $thing, $type); +} diff --git a/vendor/twig/twig/src/Resources/debug.php b/vendor/twig/twig/src/Resources/debug.php new file mode 100644 index 0000000..104b4f4 --- /dev/null +++ b/vendor/twig/twig/src/Resources/debug.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Twig\Environment; +use Twig\Extension\DebugExtension; + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_var_dump(Environment $env, $context, ...$vars) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + DebugExtension::dump($env, $context, ...$vars); +} diff --git a/vendor/twig/twig/src/Resources/escaper.php b/vendor/twig/twig/src/Resources/escaper.php new file mode 100644 index 0000000..a2ee8e7 --- /dev/null +++ b/vendor/twig/twig/src/Resources/escaper.php @@ -0,0 +1,51 @@ +getRuntime(EscaperRuntime::class)->escape($string, $strategy, $charset, $autoescape); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_escape_filter_is_safe(Node $filterArgs) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return EscaperExtension::escapeFilterIsSafe($filterArgs); +} diff --git a/vendor/twig/twig/src/Resources/string_loader.php b/vendor/twig/twig/src/Resources/string_loader.php new file mode 100644 index 0000000..8f0e649 --- /dev/null +++ b/vendor/twig/twig/src/Resources/string_loader.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Twig\Environment; +use Twig\Extension\StringLoaderExtension; +use Twig\TemplateWrapper; + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_template_from_string(Environment $env, $template, ?string $name = null): TemplateWrapper +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return StringLoaderExtension::templateFromString($env, $template, $name); +} diff --git a/vendor/twig/twig/src/Runtime/EscaperRuntime.php b/vendor/twig/twig/src/Runtime/EscaperRuntime.php new file mode 100644 index 0000000..a3ce171 --- /dev/null +++ b/vendor/twig/twig/src/Runtime/EscaperRuntime.php @@ -0,0 +1,327 @@ + */ + private $escapers = []; + + /** @internal */ + public $safeClasses = []; + + /** @internal */ + public $safeLookup = []; + + public function __construct( + private $charset = 'UTF-8', + ) { + } + + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable(string $string, string $charset): string $callable A valid PHP callable + */ + public function setEscaper($strategy, callable $callable) + { + $this->escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return array An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + public function setSafeClasses(array $safeClasses = []) + { + $this->safeClasses = []; + $this->safeLookup = []; + foreach ($safeClasses as $class => $strategies) { + $this->addSafeClass($class, $strategies); + } + } + + public function addSafeClass(string $class, array $strategies) + { + $class = ltrim($class, '\\'); + if (!isset($this->safeClasses[$class])) { + $this->safeClasses[$class] = []; + } + $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies); + + foreach ($strategies as $strategy) { + $this->safeLookup[$strategy][$class] = true; + } + } + + /** + * Escapes a string. + * + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string|null $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @throws RuntimeError + */ + public function escape($string, string $strategy = 'html', ?string $charset = null, bool $autoescape = false) + { + if ($autoescape && $string instanceof Markup) { + return $string; + } + + if (!\is_string($string)) { + if ($string instanceof \Stringable) { + if ($autoescape) { + $c = \get_class($string); + if (!isset($this->safeClasses[$c])) { + $this->safeClasses[$c] = []; + foreach (class_parents($string) + class_implements($string) as $class) { + if (isset($this->safeClasses[$class])) { + $this->safeClasses[$c] = array_unique(array_merge($this->safeClasses[$c], $this->safeClasses[$class])); + foreach ($this->safeClasses[$class] as $s) { + $this->safeLookup[$s][$c] = true; + } + } + } + } + if (isset($this->safeLookup[$strategy][$c]) || isset($this->safeLookup['all'][$c])) { + return (string) $string; + } + } + + $string = (string) $string; + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + // we return the input as is (which can be of any type) + return $string; + } + } + + if ('' === $string) { + return ''; + } + + $charset = $charset ?: $this->charset; + + switch ($strategy) { + case 'html': + // see https://www.php.net/htmlspecialchars + + // Using a static variable to avoid initializing the array + // each time the function is called. Moving the declaration on the + // top of the function slow downs other escaping strategies. + static $htmlspecialcharsCharsets = [ + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ]; + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + $string = $this->convertEncoding($string, 'UTF-8', $charset); + $string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + + return iconv('UTF-8', $charset, $string); + + case 'js': + // escape all non-alphanumeric characters + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = $this->convertEncoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { + $char = $matches[0]; + + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are omitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = [ + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ]; + + if (isset($shortMap[$char])) { + return $shortMap[$char]; + } + + $codepoint = mb_ord($char, 'UTF-8'); + if (0x10000 > $codepoint) { + return \sprintf('\u%04X', $codepoint); + } + + // Split characters outside the BMP into surrogate pairs + // https://tools.ietf.org/html/rfc2781.html#section-2.1 + $u = $codepoint - 0x10000; + $high = 0xD800 | ($u >> 10); + $low = 0xDC00 | ($u & 0x3FF); + + return \sprintf('\u%04X\u%04X', $high, $low); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = $this->convertEncoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { + $char = $matches[0]; + + return \sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = $this->convertEncoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { + /** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License + */ + $chr = $matches[0]; + $ord = \ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1F && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7F && $ord <= 0x9F)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (1 === \strlen($chr)) { + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = [ + 34 => '"', /* quotation mark */ + 38 => '&', /* ampersand */ + 60 => '<', /* less-than sign */ + 62 => '>', /* greater-than sign */ + ]; + + if (isset($entityMap[$ord])) { + return $entityMap[$ord]; + } + + return \sprintf('&#x%02X;', $ord); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return \sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'url': + return rawurlencode($string); + + default: + if (\array_key_exists($strategy, $this->escapers)) { + return $this->escapers[$strategy]($string, $charset); + } + + $validStrategies = implode('", "', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($this->escapers))); + + throw new RuntimeError(\sprintf('Invalid escaping strategy "%s" (valid ones: "%s").', $strategy, $validStrategies)); + } + } + + private function convertEncoding(string $string, string $to, string $from) + { + if (!\function_exists('iconv')) { + throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + return iconv($from, $to, $string); + } +} diff --git a/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php b/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php new file mode 100644 index 0000000..0510668 --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php @@ -0,0 +1,35 @@ + + * @author Robin Chalas + */ +class ContainerRuntimeLoader implements RuntimeLoaderInterface +{ + public function __construct( + private ContainerInterface $container, + ) { + } + + public function load(string $class) + { + return $this->container->has($class) ? $this->container->get($class) : null; + } +} diff --git a/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php b/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php new file mode 100644 index 0000000..5d4e70b --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php @@ -0,0 +1,39 @@ + + */ +class FactoryRuntimeLoader implements RuntimeLoaderInterface +{ + /** + * @param array $map An array where keys are class names and values factory callables + */ + public function __construct( + private array $map = [], + ) { + } + + public function load(string $class) + { + if (!isset($this->map[$class])) { + return null; + } + + $runtimeFactory = $this->map[$class]; + + return $runtimeFactory(); + } +} diff --git a/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php b/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php new file mode 100644 index 0000000..9e5b204 --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php @@ -0,0 +1,27 @@ + + */ +interface RuntimeLoaderInterface +{ + /** + * Creates the runtime implementation of a Twig element (filter/function/test). + * + * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class + */ + public function load(string $class); +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityError.php b/vendor/twig/twig/src/Sandbox/SecurityError.php new file mode 100644 index 0000000..30a404f --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityError.php @@ -0,0 +1,23 @@ + + */ +class SecurityError extends Error +{ +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php new file mode 100644 index 0000000..02d3063 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php @@ -0,0 +1,33 @@ + + */ +final class SecurityNotAllowedFilterError extends SecurityError +{ + private $filterName; + + public function __construct(string $message, string $functionName) + { + parent::__construct($message); + $this->filterName = $functionName; + } + + public function getFilterName(): string + { + return $this->filterName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php new file mode 100644 index 0000000..4f76dc6 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php @@ -0,0 +1,33 @@ + + */ +final class SecurityNotAllowedFunctionError extends SecurityError +{ + private $functionName; + + public function __construct(string $message, string $functionName) + { + parent::__construct($message); + $this->functionName = $functionName; + } + + public function getFunctionName(): string + { + return $this->functionName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php new file mode 100644 index 0000000..8df9d0b --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php @@ -0,0 +1,40 @@ + + */ +final class SecurityNotAllowedMethodError extends SecurityError +{ + private $className; + private $methodName; + + public function __construct(string $message, string $className, string $methodName) + { + parent::__construct($message); + $this->className = $className; + $this->methodName = $methodName; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getMethodName() + { + return $this->methodName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php new file mode 100644 index 0000000..42ec4f3 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php @@ -0,0 +1,40 @@ + + */ +final class SecurityNotAllowedPropertyError extends SecurityError +{ + private $className; + private $propertyName; + + public function __construct(string $message, string $className, string $propertyName) + { + parent::__construct($message); + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getPropertyName() + { + return $this->propertyName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php new file mode 100644 index 0000000..4522150 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php @@ -0,0 +1,33 @@ + + */ +final class SecurityNotAllowedTagError extends SecurityError +{ + private $tagName; + + public function __construct(string $message, string $tagName) + { + parent::__construct($message); + $this->tagName = $tagName; + } + + public function getTagName(): string + { + return $this->tagName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityPolicy.php b/vendor/twig/twig/src/Sandbox/SecurityPolicy.php new file mode 100644 index 0000000..988e372 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityPolicy.php @@ -0,0 +1,130 @@ + + */ +final class SecurityPolicy implements SecurityPolicyInterface +{ + private $allowedTags; + private $allowedFilters; + private $allowedMethods; + private $allowedProperties; + private $allowedFunctions; + + public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) + { + $this->allowedTags = $allowedTags; + $this->allowedFilters = $allowedFilters; + $this->setAllowedMethods($allowedMethods); + $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; + } + + public function setAllowedTags(array $tags): void + { + $this->allowedTags = $tags; + } + + public function setAllowedFilters(array $filters): void + { + $this->allowedFilters = $filters; + } + + public function setAllowedMethods(array $methods): void + { + $this->allowedMethods = []; + foreach ($methods as $class => $m) { + $this->allowedMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]); + } + } + + public function setAllowedProperties(array $properties): void + { + $this->allowedProperties = $properties; + } + + public function setAllowedFunctions(array $functions): void + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions): void + { + foreach ($tags as $tag) { + if (!\in_array($tag, $this->allowedTags)) { + if ('extends' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "extends" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.'); + } elseif ('use' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "use" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.'); + } else { + throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag); + } + } + } + + foreach ($filters as $filter) { + if (!\in_array($filter, $this->allowedFilters)) { + throw new SecurityNotAllowedFilterError(\sprintf('Filter "%s" is not allowed.', $filter), $filter); + } + } + + foreach ($functions as $function) { + if (!\in_array($function, $this->allowedFunctions)) { + throw new SecurityNotAllowedFunctionError(\sprintf('Function "%s" is not allowed.', $function), $function); + } + } + } + + public function checkMethodAllowed($obj, $method): void + { + if ($obj instanceof Template || $obj instanceof Markup) { + return; + } + + $allowed = false; + $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + foreach ($this->allowedMethods as $class => $methods) { + if ($obj instanceof $class && \in_array($method, $methods)) { + $allowed = true; + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedMethodError(\sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); + } + } + + public function checkPropertyAllowed($obj, $property): void + { + $allowed = false; + foreach ($this->allowedProperties as $class => $properties) { + if ($obj instanceof $class && \in_array($property, \is_array($properties) ? $properties : [$properties])) { + $allowed = true; + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedPropertyError(\sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); + } + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php b/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php new file mode 100644 index 0000000..36471c5 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php @@ -0,0 +1,45 @@ + + */ +interface SecurityPolicyInterface +{ + /** + * @param string[] $tags + * @param string[] $filters + * @param string[] $functions + * + * @throws SecurityError + */ + public function checkSecurity($tags, $filters, $functions): void; + + /** + * @param object $obj + * @param string $method + * + * @throws SecurityNotAllowedMethodError + */ + public function checkMethodAllowed($obj, $method): void; + + /** + * @param object $obj + * @param string $property + * + * @throws SecurityNotAllowedPropertyError + */ + public function checkPropertyAllowed($obj, $property): void; +} diff --git a/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php b/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php new file mode 100644 index 0000000..b952f1e --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php @@ -0,0 +1,24 @@ + + */ +final class Source +{ + /** + * @param string $code The template source code + * @param string $name The template logical name + * @param string $path The filesystem path of the template if any + */ + public function __construct( + private string $code, + private string $name, + private string $path = '', + ) { + } + + public function getCode(): string + { + return $this->code; + } + + public function getName(): string + { + return $this->name; + } + + public function getPath(): string + { + return $this->path; + } +} diff --git a/vendor/twig/twig/src/Template.php b/vendor/twig/twig/src/Template.php new file mode 100644 index 0000000..7b3ce81 --- /dev/null +++ b/vendor/twig/twig/src/Template.php @@ -0,0 +1,497 @@ +load() + * instead, which returns an instance of \Twig\TemplateWrapper. + * + * @author Fabien Potencier + * + * @internal + */ +abstract class Template +{ + public const ANY_CALL = 'any'; + public const ARRAY_CALL = 'array'; + public const METHOD_CALL = 'method'; + + protected $parent; + protected $parents = []; + protected $blocks = []; + protected $traits = []; + protected $extensions = []; + protected $sandbox; + + private $useYield; + + public function __construct( + protected Environment $env, + ) { + $this->useYield = $env->useYield(); + $this->extensions = $env->getExtensions(); + } + + /** + * Returns the template name. + */ + abstract public function getTemplateName(): string; + + /** + * Returns debug information about the template. + * + * @return array Debug information + */ + abstract public function getDebugInfo(): array; + + /** + * Returns information about the original template source code. + */ + abstract public function getSourceContext(): Source; + + /** + * Returns the parent template. + * + * This method is for internal use only and should never be called + * directly. + * + * @return self|TemplateWrapper|false The parent template or false if there is no parent + */ + public function getParent(array $context): self|TemplateWrapper|false + { + if (null !== $this->parent) { + return $this->parent; + } + + try { + if (!$parent = $this->doGetParent($context)) { + return false; + } + + if ($parent instanceof self || $parent instanceof TemplateWrapper) { + return $this->parents[$parent->getSourceContext()->getName()] = $parent; + } + + if (!isset($this->parents[$parent])) { + $this->parents[$parent] = $this->loadTemplate($parent); + } + } catch (LoaderError $e) { + $e->setSourceContext(null); + $e->guess(); + + throw $e; + } + + return $this->parents[$parent]; + } + + protected function doGetParent(array $context): bool|string|self|TemplateWrapper + { + return false; + } + + public function isTraitable(): bool + { + return true; + } + + /** + * Displays a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + */ + public function displayParentBlock($name, array $context, array $blocks = []): void + { + foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) { + echo $data; + } + } + + /** + * Displays a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + */ + public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): void + { + foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks, $templateContext) as $data) { + echo $data; + } + } + + /** + * Renders a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return string The rendered block + */ + public function renderParentBlock($name, array $context, array $blocks = []): string + { + if (!$this->useYield) { + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + $this->displayParentBlock($name, $context, $blocks); + + return ob_get_clean(); + } + + $content = ''; + foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) { + $content .= $data; + } + + return $content; + } + + /** + * Renders a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + * + * @return string The rendered block + */ + public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true): string + { + if (!$this->useYield) { + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->displayBlock($name, $context, $blocks, $useBlocks); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + $content = ''; + foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks) as $data) { + $content .= $data; + } + + return $content; + } + + /** + * Returns whether a block exists or not in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param string $name The block name + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return bool true if the block exists, false otherwise + */ + public function hasBlock($name, array $context, array $blocks = []): bool + { + if (isset($blocks[$name])) { + return $blocks[$name][0] instanceof self; + } + + if (isset($this->blocks[$name])) { + return true; + } + + if ($parent = $this->getParent($context)) { + return $parent->hasBlock($name, $context); + } + + return false; + } + + /** + * Returns all block names in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return array An array of block names + */ + public function getBlockNames(array $context, array $blocks = []): array + { + $names = array_merge(array_keys($blocks), array_keys($this->blocks)); + + if ($parent = $this->getParent($context)) { + $names = array_merge($names, $parent->getBlockNames($context)); + } + + return array_unique($names); + } + + /** + * @param string|TemplateWrapper|array $template + */ + protected function loadTemplate($template, $templateName = null, $line = null, $index = null): self|TemplateWrapper + { + try { + if (\is_array($template)) { + return $this->env->resolveTemplate($template); + } + + if ($template instanceof TemplateWrapper) { + return $template; + } + + if ($template instanceof self) { + trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', self::class, __METHOD__); + + return $template; + } + + if ($template === $this->getTemplateName()) { + $class = static::class; + if (false !== $pos = strrpos($class, '___', -1)) { + $class = substr($class, 0, $pos); + } + } else { + $class = $this->env->getTemplateClass($template); + } + + return $this->env->loadTemplate($class, $template, $index); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext()); + } + + if ($e->getTemplateLine() > 0) { + throw $e; + } + + if (!$line) { + $e->guess(); + } else { + $e->setTemplateLine($line); + } + + throw $e; + } + } + + /** + * @internal + */ + public function unwrap(): self + { + return $this; + } + + /** + * Returns all blocks. + * + * This method is for internal use only and should never be called + * directly. + * + * @return array An array of blocks + */ + public function getBlocks(): array + { + return $this->blocks; + } + + public function display(array $context, array $blocks = []): void + { + foreach ($this->yield($context, $blocks) as $data) { + echo $data; + } + } + + public function render(array $context): string + { + if (!$this->useYield) { + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->display($context); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + $content = ''; + foreach ($this->yield($context) as $data) { + $content .= $data; + } + + return $content; + } + + /** + * @return iterable + */ + public function yield(array $context, array $blocks = []): iterable + { + $context += $this->env->getGlobals(); + $blocks = array_merge($this->blocks, $blocks); + + try { + yield from $this->doDisplay($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Throwable $e) { + $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } + + /** + * @return iterable + */ + public function yieldBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): iterable + { + if ($useBlocks && isset($blocks[$name])) { + $template = $blocks[$name][0]; + $block = $blocks[$name][1]; + } elseif (isset($this->blocks[$name])) { + $template = $this->blocks[$name][0]; + $block = $this->blocks[$name][1]; + } else { + $template = null; + $block = null; + } + + // avoid RCEs when sandbox is enabled + if (null !== $template && !$template instanceof self) { + throw new \LogicException('A block must be a method on a \Twig\Template instance.'); + } + + if (null !== $template) { + try { + yield from $template->$block($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Throwable $e) { + $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } elseif ($parent = $this->getParent($context)) { + yield from $parent->unwrap()->yieldBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this); + } elseif (isset($blocks[$name])) { + throw new RuntimeError(\sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext()); + } else { + throw new RuntimeError(\sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext()); + } + } + + /** + * Yields a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return iterable + */ + public function yieldParentBlock($name, array $context, array $blocks = []): iterable + { + if (isset($this->traits[$name])) { + yield from $this->traits[$name][0]->yieldBlock($name, $context, $blocks, false); + } elseif ($parent = $this->getParent($context)) { + yield from $parent->unwrap()->yieldBlock($name, $context, $blocks, false); + } else { + throw new RuntimeError(\sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); + } + } + + /** + * Auto-generated method to display the template with the given context. + * + * @param array $context An array of parameters to pass to the template + * @param array $blocks An array of blocks to pass to the template + * + * @return iterable + */ + abstract protected function doDisplay(array $context, array $blocks = []): iterable; +} diff --git a/vendor/twig/twig/src/TemplateWrapper.php b/vendor/twig/twig/src/TemplateWrapper.php new file mode 100644 index 0000000..135c591 --- /dev/null +++ b/vendor/twig/twig/src/TemplateWrapper.php @@ -0,0 +1,90 @@ + + */ +final class TemplateWrapper +{ + /** + * This method is for internal use only and should never be called + * directly (use Twig\Environment::load() instead). + * + * @internal + */ + public function __construct( + private Environment $env, + private Template $template, + ) { + } + + public function render(array $context = []): string + { + return $this->template->render($context); + } + + public function display(array $context = []) + { + // using func_get_args() allows to not expose the blocks argument + // as it should only be used by internal code + $this->template->display($context, \func_get_args()[1] ?? []); + } + + public function hasBlock(string $name, array $context = []): bool + { + return $this->template->hasBlock($name, $context); + } + + /** + * @return string[] An array of defined template block names + */ + public function getBlockNames(array $context = []): array + { + return $this->template->getBlockNames($context); + } + + public function renderBlock(string $name, array $context = []): string + { + return $this->template->renderBlock($name, $context + $this->env->getGlobals()); + } + + public function displayBlock(string $name, array $context = []) + { + $context += $this->env->getGlobals(); + foreach ($this->template->yieldBlock($name, $context) as $data) { + echo $data; + } + } + + public function getSourceContext(): Source + { + return $this->template->getSourceContext(); + } + + public function getTemplateName(): string + { + return $this->template->getTemplateName(); + } + + /** + * @internal + * + * @return Template + */ + public function unwrap() + { + return $this->template; + } +} diff --git a/vendor/twig/twig/src/Test/IntegrationTestCase.php b/vendor/twig/twig/src/Test/IntegrationTestCase.php new file mode 100644 index 0000000..8690a80 --- /dev/null +++ b/vendor/twig/twig/src/Test/IntegrationTestCase.php @@ -0,0 +1,288 @@ + + * @author Karma Dordrak + */ +abstract class IntegrationTestCase extends TestCase +{ + /** + * @deprecated since Twig 3.13, use getFixturesDirectory() instead. + * + * @return string + */ + protected function getFixturesDir() + { + throw new \BadMethodCallException('Not implemented.'); + } + + protected static function getFixturesDirectory(): string + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * @return RuntimeLoaderInterface[] + */ + protected function getRuntimeLoaders() + { + return []; + } + + /** + * @return ExtensionInterface[] + */ + protected function getExtensions() + { + return []; + } + + /** + * @return TwigFilter[] + */ + protected function getTwigFilters() + { + return []; + } + + /** + * @return TwigFunction[] + */ + protected function getTwigFunctions() + { + return []; + } + + /** + * @return TwigTest[] + */ + protected function getTwigTests() + { + return []; + } + + /** + * @dataProvider getTests + */ + public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + /** + * @dataProvider getLegacyTests + * + * @group legacy + */ + public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + /** + * @final since Twig 3.13 + */ + public function getTests($name, $legacyTests = false) + { + try { + $fixturesDir = static::getFixturesDirectory(); + } catch (\BadMethodCallException) { + trigger_deprecation('twig/twig', '3.13', 'Not overriding "%s::getFixturesDirectory()" in "%s" is deprecated. This method will be abstract in 4.0.', self::class, static::class); + $fixturesDir = $this->getFixturesDir(); + } + + $fixturesDir = realpath($fixturesDir); + $tests = []; + + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if (!preg_match('/\.test$/', $file)) { + continue; + } + + if ($legacyTests xor str_contains($file->getRealpath(), '.legacy.test')) { + continue; + } + + $test = file_get_contents($file->getRealpath()); + + if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = self::parseTemplates($match[4]); + $exception = $match[6]; + $outputs = [[null, $match[5], null, '']]; + } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = self::parseTemplates($match[4]); + $exception = false; + preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, \PREG_SET_ORDER); + } else { + throw new \InvalidArgumentException(\sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); + } + + $tests[] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation]; + } + + if ($legacyTests && empty($tests)) { + // add a dummy test to avoid a PHPUnit message + return [['not', '-', '', [], '', []]]; + } + + return $tests; + } + + /** + * @final since Twig 3.13 + */ + public function getLegacyTests() + { + return $this->getTests('testLegacyIntegration', true); + } + + protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + if (!$outputs) { + $this->markTestSkipped('no tests to run'); + } + + if ($condition) { + $ret = ''; + eval('$ret = '.$condition.';'); + if (!$ret) { + $this->markTestSkipped($condition); + } + } + + foreach ($outputs as $i => $match) { + $config = array_merge([ + 'cache' => false, + 'strict_variables' => true, + ], $match[2] ? eval($match[2].';') : []); + // make sure that template are always compiled even if they are the same (useful when testing with more than one data/expect sections) + foreach ($templates as $j => $template) { + $templates[$j] = $template.str_repeat(' ', $i); + } + $loader = new ArrayLoader($templates); + $twig = new Environment($loader, $config); + $twig->addGlobal('global', 'global'); + foreach ($this->getRuntimeLoaders() as $runtimeLoader) { + $twig->addRuntimeLoader($runtimeLoader); + } + + foreach ($this->getExtensions() as $extension) { + $twig->addExtension($extension); + } + + foreach ($this->getTwigFilters() as $filter) { + $twig->addFilter($filter); + } + + foreach ($this->getTwigTests() as $test) { + $twig->addTest($test); + } + + foreach ($this->getTwigFunctions() as $function) { + $twig->addFunction($function); + } + + $deprecations = []; + try { + $prevHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + + return true; + } + + return $prevHandler ? $prevHandler($type, $msg, $file, $line, $context) : false; + }); + + $template = $twig->load('index.twig'); + } catch (\Exception $e) { + if (false !== $exception) { + $message = $e->getMessage(); + $this->assertSame(trim($exception), trim(\sprintf('%s: %s', \get_class($e), $message))); + $last = substr($message, \strlen($message) - 1); + $this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.'); + + return; + } + + throw new Error(\sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + } finally { + restore_error_handler(); + } + + $this->assertSame($deprecation, implode("\n", $deprecations)); + + try { + $output = trim($template->render(eval($match[1].';')), "\n "); + } catch (\Exception $e) { + if (false !== $exception) { + $this->assertSame(trim($exception), trim(\sprintf('%s: %s', \get_class($e), $e->getMessage()))); + + return; + } + + $e = new Error(\sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + + $output = trim(\sprintf('%s: %s', \get_class($e), $e->getMessage())); + } + + if (false !== $exception) { + [$class] = explode(':', $exception); + $constraintClass = class_exists('PHPUnit\Framework\Constraint\Exception') ? 'PHPUnit\Framework\Constraint\Exception' : 'PHPUnit_Framework_Constraint_Exception'; + $this->assertThat(null, new $constraintClass($class)); + } + + $expected = trim($match[3], "\n "); + + if ($expected !== $output) { + printf("Compiled templates that failed on case %d:\n", $i + 1); + + foreach (array_keys($templates) as $name) { + echo "Template: $name\n"; + echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSourceContext($name)))); + } + } + $this->assertEquals($expected, $output, $message.' (in '.$file.')'); + } + } + + protected static function parseTemplates($test) + { + $templates = []; + preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, \PREG_SET_ORDER); + foreach ($matches as $match) { + $templates[$match[1] ?: 'index.twig'] = $match[2]; + } + + return $templates; + } +} diff --git a/vendor/twig/twig/src/Test/NodeTestCase.php b/vendor/twig/twig/src/Test/NodeTestCase.php new file mode 100644 index 0000000..bac0ea6 --- /dev/null +++ b/vendor/twig/twig/src/Test/NodeTestCase.php @@ -0,0 +1,125 @@ + + */ + public static function provideTests(): iterable + { + trigger_deprecation('twig/twig', '3.13', 'Not implementing "%s()" in "%s" is deprecated. This method will be abstract in 4.0.', __METHOD__, static::class); + + return []; + } + + /** + * @dataProvider getTests + * @dataProvider provideTests + */ + #[DataProvider('getTests'), DataProvider('provideTests')] + public function testCompile($node, $source, $environment = null, $isPattern = false) + { + $this->assertNodeCompilation($source, $node, $environment, $isPattern); + } + + public function assertNodeCompilation($source, Node $node, ?Environment $environment = null, $isPattern = false) + { + $compiler = $this->getCompiler($environment); + $compiler->compile($node); + + if ($isPattern) { + $this->assertStringMatchesFormat($source, trim($compiler->getSource())); + } else { + $this->assertEquals($source, trim($compiler->getSource())); + } + } + + protected function getCompiler(?Environment $environment = null) + { + return new Compiler($environment ?? $this->getEnvironment()); + } + + /** + * @final since Twig 3.13 + */ + protected function getEnvironment() + { + return $this->currentEnv ??= static::createEnvironment(); + } + + protected static function createEnvironment(): Environment + { + return new Environment(new ArrayLoader()); + } + + /** + * @deprecated since Twig 3.13, use createVariableGetter() instead. + */ + protected function getVariableGetter($name, $line = false) + { + trigger_deprecation('twig/twig', '3.13', 'Method "%s()" is deprecated, use "createVariableGetter()" instead.', __METHOD__); + + return self::createVariableGetter($name, $line); + } + + final protected static function createVariableGetter(string $name, bool $line = false): string + { + $line = $line > 0 ? "// line $line\n" : ''; + + return \sprintf('%s($context["%s"] ?? null)', $line, $name); + } + + /** + * @deprecated since Twig 3.13, use createAttributeGetter() instead. + */ + protected function getAttributeGetter() + { + trigger_deprecation('twig/twig', '3.13', 'Method "%s()" is deprecated, use "createAttributeGetter()" instead.', __METHOD__); + + return self::createAttributeGetter(); + } + + final protected static function createAttributeGetter(): string + { + return 'CoreExtension::getAttribute($this->env, $this->source, '; + } + + /** @beforeClass */ + #[BeforeClass] + final public static function checkDataProvider(): void + { + $r = new \ReflectionMethod(static::class, 'getTests'); + if (self::class !== $r->getDeclaringClass()->getName()) { + trigger_deprecation('twig/twig', '3.13', 'Implementing "%s::getTests()" in "%s" is deprecated, implement "provideTests()" instead.', self::class, static::class); + } + } +} diff --git a/vendor/twig/twig/src/Token.php b/vendor/twig/twig/src/Token.php new file mode 100644 index 0000000..237634a --- /dev/null +++ b/vendor/twig/twig/src/Token.php @@ -0,0 +1,180 @@ + + */ +final class Token +{ + public const EOF_TYPE = -1; + public const TEXT_TYPE = 0; + public const BLOCK_START_TYPE = 1; + public const VAR_START_TYPE = 2; + public const BLOCK_END_TYPE = 3; + public const VAR_END_TYPE = 4; + public const NAME_TYPE = 5; + public const NUMBER_TYPE = 6; + public const STRING_TYPE = 7; + public const OPERATOR_TYPE = 8; + public const PUNCTUATION_TYPE = 9; + public const INTERPOLATION_START_TYPE = 10; + public const INTERPOLATION_END_TYPE = 11; + public const ARROW_TYPE = 12; + public const SPREAD_TYPE = 13; + + public function __construct( + private int $type, + private $value, + private int $lineno, + ) { + } + + public function __toString() + { + return \sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + * + * Parameters may be: + * * just type + * * type and value (or array of possible values) + * * just value (or array of possible values) (NAME_TYPE is used as type) + * + * @param array|string|int $type The type to test + * @param array|string|null $values The token value + */ + public function test($type, $values = null): bool + { + if (null === $values && !\is_int($type)) { + $values = $type; + $type = self::NAME_TYPE; + } + + return ($this->type === $type) && ( + null === $values + || (\is_array($values) && \in_array($this->value, $values)) + || $this->value == $values + ); + } + + public function getLine(): int + { + return $this->lineno; + } + + public function getType(): int + { + return $this->type; + } + + public function getValue() + { + return $this->value; + } + + public static function typeToString(int $type, bool $short = false): string + { + switch ($type) { + case self::EOF_TYPE: + $name = 'EOF_TYPE'; + break; + case self::TEXT_TYPE: + $name = 'TEXT_TYPE'; + break; + case self::BLOCK_START_TYPE: + $name = 'BLOCK_START_TYPE'; + break; + case self::VAR_START_TYPE: + $name = 'VAR_START_TYPE'; + break; + case self::BLOCK_END_TYPE: + $name = 'BLOCK_END_TYPE'; + break; + case self::VAR_END_TYPE: + $name = 'VAR_END_TYPE'; + break; + case self::NAME_TYPE: + $name = 'NAME_TYPE'; + break; + case self::NUMBER_TYPE: + $name = 'NUMBER_TYPE'; + break; + case self::STRING_TYPE: + $name = 'STRING_TYPE'; + break; + case self::OPERATOR_TYPE: + $name = 'OPERATOR_TYPE'; + break; + case self::PUNCTUATION_TYPE: + $name = 'PUNCTUATION_TYPE'; + break; + case self::INTERPOLATION_START_TYPE: + $name = 'INTERPOLATION_START_TYPE'; + break; + case self::INTERPOLATION_END_TYPE: + $name = 'INTERPOLATION_END_TYPE'; + break; + case self::ARROW_TYPE: + $name = 'ARROW_TYPE'; + break; + case self::SPREAD_TYPE: + $name = 'SPREAD_TYPE'; + break; + default: + throw new \LogicException(\sprintf('Token of type "%s" does not exist.', $type)); + } + + return $short ? $name : 'Twig\Token::'.$name; + } + + public static function typeToEnglish(int $type): string + { + switch ($type) { + case self::EOF_TYPE: + return 'end of template'; + case self::TEXT_TYPE: + return 'text'; + case self::BLOCK_START_TYPE: + return 'begin of statement block'; + case self::VAR_START_TYPE: + return 'begin of print statement'; + case self::BLOCK_END_TYPE: + return 'end of statement block'; + case self::VAR_END_TYPE: + return 'end of print statement'; + case self::NAME_TYPE: + return 'name'; + case self::NUMBER_TYPE: + return 'number'; + case self::STRING_TYPE: + return 'string'; + case self::OPERATOR_TYPE: + return 'operator'; + case self::PUNCTUATION_TYPE: + return 'punctuation'; + case self::INTERPOLATION_START_TYPE: + return 'begin of string interpolation'; + case self::INTERPOLATION_END_TYPE: + return 'end of string interpolation'; + case self::ARROW_TYPE: + return 'arrow function'; + case self::SPREAD_TYPE: + return 'spread operator'; + default: + throw new \LogicException(\sprintf('Token of type "%s" does not exist.', $type)); + } + } +} diff --git a/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php b/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php new file mode 100644 index 0000000..720ea67 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php @@ -0,0 +1,32 @@ + + */ +abstract class AbstractTokenParser implements TokenParserInterface +{ + /** + * @var Parser + */ + protected $parser; + + public function setParser(Parser $parser): void + { + $this->parser = $parser; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php b/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php new file mode 100644 index 0000000..0a6c1af --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php @@ -0,0 +1,60 @@ +getLine(); + $name = $this->parser->getVarName(); + + $ref = new TempNameExpression($name, $lineno); + $ref->setAttribute('always_defined', true); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideApplyEnd'], true); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new Node([ + new SetNode(true, $ref, $body, $lineno), + new PrintNode($filter, $lineno), + ], [], $lineno); + } + + public function decideApplyEnd(Token $token): bool + { + return $token->test('endapply'); + } + + public function getTag(): string + { + return 'apply'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php b/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php new file mode 100644 index 0000000..b50b29e --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php @@ -0,0 +1,58 @@ +getLine(); + $stream = $this->parser->getStream(); + + if ($stream->test(Token::BLOCK_END_TYPE)) { + $value = 'html'; + } else { + $expr = $this->parser->getExpressionParser()->parseExpression(); + if (!$expr instanceof ConstantExpression) { + throw new SyntaxError('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $value = $expr->getAttribute('value'); + } + + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + + return new AutoEscapeNode($value, $body, $lineno); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endautoescape'); + } + + public function getTag(): string + { + return 'autoescape'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/BlockTokenParser.php b/vendor/twig/twig/src/TokenParser/BlockTokenParser.php new file mode 100644 index 0000000..81d675d --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/BlockTokenParser.php @@ -0,0 +1,75 @@ + + * {% block title %}{% endblock %} - My Webpage + * {% endblock %} + * + * @internal + */ +final class BlockTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + $this->parser->setBlock($name, $block = new BlockNode($name, new Node([]), $lineno)); + $this->parser->pushLocalScope(); + $this->parser->pushBlockStack($name); + + if ($stream->nextIf(Token::BLOCK_END_TYPE)) { + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(Token::NAME_TYPE)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(\sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + } else { + $body = new Node([ + new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno), + ]); + } + $stream->expect(Token::BLOCK_END_TYPE); + + $block->setNode('body', $body); + $this->parser->popBlockStack(); + $this->parser->popLocalScope(); + + return new BlockReferenceNode($name, $lineno); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endblock'); + } + + public function getTag(): string + { + return 'block'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php b/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php new file mode 100644 index 0000000..164ef26 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php @@ -0,0 +1,66 @@ + + * + * @internal + */ +final class DeprecatedTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $stream = $this->parser->getStream(); + $expressionParser = $this->parser->getExpressionParser(); + $expr = $expressionParser->parseExpression(); + $node = new DeprecatedNode($expr, $token->getLine()); + + while ($stream->test(Token::NAME_TYPE)) { + $k = $stream->getCurrent()->getValue(); + $stream->next(); + $stream->expect(Token::OPERATOR_TYPE, '='); + + switch ($k) { + case 'package': + $node->setNode('package', $expressionParser->parseExpression()); + break; + case 'version': + $node->setNode('version', $expressionParser->parseExpression()); + break; + default: + throw new SyntaxError(\sprintf('Unknown "%s" option.', $k), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return $node; + } + + public function getTag(): string + { + return 'deprecated'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/DoTokenParser.php b/vendor/twig/twig/src/TokenParser/DoTokenParser.php new file mode 100644 index 0000000..8afd485 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/DoTokenParser.php @@ -0,0 +1,38 @@ +parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new DoNode($expr, $token->getLine()); + } + + public function getTag(): string + { + return 'do'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php b/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php new file mode 100644 index 0000000..7bf3233 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php @@ -0,0 +1,73 @@ +parser->getStream(); + + $parent = $this->parser->getExpressionParser()->parseExpression(); + + [$variables, $only, $ignoreMissing] = $this->parseArguments(); + + $parentToken = $fakeParentToken = new Token(Token::STRING_TYPE, '__parent__', $token->getLine()); + if ($parent instanceof ConstantExpression) { + $parentToken = new Token(Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine()); + } elseif ($parent instanceof NameExpression) { + $parentToken = new Token(Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine()); + } + + // inject a fake parent to make the parent() function work + $stream->injectTokens([ + new Token(Token::BLOCK_START_TYPE, '', $token->getLine()), + new Token(Token::NAME_TYPE, 'extends', $token->getLine()), + $parentToken, + new Token(Token::BLOCK_END_TYPE, '', $token->getLine()), + ]); + + $module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true); + + // override the parent with the correct one + if ($fakeParentToken === $parentToken) { + $module->setNode('parent', $parent); + } + + $this->parser->embedTemplate($module); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endembed'); + } + + public function getTag(): string + { + return 'embed'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php new file mode 100644 index 0000000..86ddfdf --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -0,0 +1,49 @@ +parser->getStream(); + + if ($this->parser->peekBlockStack()) { + throw new SyntaxError('Cannot use "extend" in a block.', $token->getLine(), $stream->getSourceContext()); + } elseif (!$this->parser->isMainScope()) { + throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext()); + } + + $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new Node([], [], $token->getLine()); + } + + public function getTag(): string + { + return 'extends'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/FlushTokenParser.php b/vendor/twig/twig/src/TokenParser/FlushTokenParser.php new file mode 100644 index 0000000..0d23887 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/FlushTokenParser.php @@ -0,0 +1,38 @@ +parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new FlushNode($token->getLine()); + } + + public function getTag(): string + { + return 'flush'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ForTokenParser.php b/vendor/twig/twig/src/TokenParser/ForTokenParser.php new file mode 100644 index 0000000..cf655f8 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ForTokenParser.php @@ -0,0 +1,78 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * + * @internal + */ +final class ForTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $stream->expect(Token::OPERATOR_TYPE, 'in'); + $seq = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideForFork']); + if ('else' == $stream->next()->getValue()) { + $stream->expect(Token::BLOCK_END_TYPE); + $else = $this->parser->subparse([$this, 'decideForEnd'], true); + } else { + $else = null; + } + $stream->expect(Token::BLOCK_END_TYPE); + + if (\count($targets) > 1) { + $keyTarget = $targets->getNode('0'); + $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); + $valueTarget = $targets->getNode('1'); + } else { + $keyTarget = new AssignNameExpression('_key', $lineno); + $valueTarget = $targets->getNode('0'); + } + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + + return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno); + } + + public function decideForFork(Token $token): bool + { + return $token->test(['else', 'endfor']); + } + + public function decideForEnd(Token $token): bool + { + return $token->test('endfor'); + } + + public function getTag(): string + { + return 'for'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/FromTokenParser.php b/vendor/twig/twig/src/TokenParser/FromTokenParser.php new file mode 100644 index 0000000..2ccff5f --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/FromTokenParser.php @@ -0,0 +1,66 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(Token::NAME_TYPE, 'import'); + + $targets = []; + while (true) { + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(Token::NAME_TYPE)->getValue(); + } + + $targets[$name] = $alias; + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); + $node = new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); + + foreach ($targets as $name => $alias) { + $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); + } + + return $node; + } + + public function getTag(): string + { + return 'from'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/IfTokenParser.php b/vendor/twig/twig/src/TokenParser/IfTokenParser.php new file mode 100644 index 0000000..4ea6f3d --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/IfTokenParser.php @@ -0,0 +1,89 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * {% endif %} + * + * @internal + */ +final class IfTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests = [$expr, $body]; + $else = null; + + $end = false; + while (!$end) { + switch ($stream->next()->getValue()) { + case 'else': + $stream->expect(Token::BLOCK_END_TYPE); + $else = $this->parser->subparse([$this, 'decideIfEnd']); + break; + + case 'elseif': + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests[] = $expr; + $tests[] = $body; + break; + + case 'endif': + $end = true; + break; + + default: + throw new SyntaxError(\sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new IfNode(new Node($tests), $else, $lineno); + } + + public function decideIfFork(Token $token): bool + { + return $token->test(['elseif', 'else', 'endif']); + } + + public function decideIfEnd(Token $token): bool + { + return $token->test(['endif']); + } + + public function getTag(): string + { + return 'if'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ImportTokenParser.php b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php new file mode 100644 index 0000000..f20f35a --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php @@ -0,0 +1,44 @@ +parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect(Token::NAME_TYPE, 'as'); + $var = new AssignNameExpression($this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + $this->parser->addImportedSymbol('template', $var->getAttribute('name')); + + return new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); + } + + public function getTag(): string + { + return 'import'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php b/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php new file mode 100644 index 0000000..466f228 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php @@ -0,0 +1,69 @@ +parser->getExpressionParser()->parseExpression(); + + [$variables, $only, $ignoreMissing] = $this->parseArguments(); + + return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine()); + } + + protected function parseArguments() + { + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->nextIf(Token::NAME_TYPE, 'ignore')) { + $stream->expect(Token::NAME_TYPE, 'missing'); + + $ignoreMissing = true; + } + + $variables = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + } + + $only = false; + if ($stream->nextIf(Token::NAME_TYPE, 'only')) { + $only = true; + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$variables, $only, $ignoreMissing]; + } + + public function getTag(): string + { + return 'include'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/MacroTokenParser.php b/vendor/twig/twig/src/TokenParser/MacroTokenParser.php new file mode 100644 index 0000000..c776207 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/MacroTokenParser.php @@ -0,0 +1,66 @@ + + * {% endmacro %} + * + * @internal + */ +final class MacroTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); + + $stream->expect(Token::BLOCK_END_TYPE); + $this->parser->pushLocalScope(); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(Token::NAME_TYPE)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(\sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + $this->parser->popLocalScope(); + $stream->expect(Token::BLOCK_END_TYPE); + + $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno)); + + return new Node([], [], $lineno); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endmacro'); + } + + public function getTag(): string + { + return 'macro'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php b/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php new file mode 100644 index 0000000..70869fb --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php @@ -0,0 +1,66 @@ +parser->getStream(); + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + + // in a sandbox tag, only include tags are allowed + if (!$body instanceof IncludeNode) { + foreach ($body as $node) { + if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { + continue; + } + + if (!$node instanceof IncludeNode) { + throw new SyntaxError('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()); + } + } + } + + return new SandboxNode($body, $token->getLine()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endsandbox'); + } + + public function getTag(): string + { + return 'sandbox'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/SetTokenParser.php b/vendor/twig/twig/src/TokenParser/SetTokenParser.php new file mode 100644 index 0000000..bb43907 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/SetTokenParser.php @@ -0,0 +1,73 @@ +getLine(); + $stream = $this->parser->getStream(); + $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); + + $capture = false; + if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + + $stream->expect(Token::BLOCK_END_TYPE); + + if (\count($names) !== \count($values)) { + throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } else { + $capture = true; + + if (\count($names) > 1) { + throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $values = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + } + + return new SetNode($capture, $names, $values, $lineno); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endset'); + } + + public function getTag(): string + { + return 'set'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/TokenParserInterface.php b/vendor/twig/twig/src/TokenParser/TokenParserInterface.php new file mode 100644 index 0000000..bb8db3e --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/TokenParserInterface.php @@ -0,0 +1,46 @@ + + */ +interface TokenParserInterface +{ + /** + * Sets the parser associated with this token parser. + */ + public function setParser(Parser $parser): void; + + /** + * Parses a token and returns a node. + * + * @return Node + * + * @throws SyntaxError + */ + public function parse(Token $token); + + /** + * Gets the tag name associated with this token parser. + * + * @return string + */ + public function getTag(); +} diff --git a/vendor/twig/twig/src/TokenParser/TypesTokenParser.php b/vendor/twig/twig/src/TokenParser/TypesTokenParser.php new file mode 100644 index 0000000..2e0850e --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/TypesTokenParser.php @@ -0,0 +1,86 @@ + + * + * @internal + */ +final class TypesTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $stream = $this->parser->getStream(); + + $types = $this->parseSimpleMappingExpression($stream); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new TypesNode($types, $token->getLine()); + } + + /** + * @return array + * + * @throws SyntaxError + */ + private function parseSimpleMappingExpression(TokenStream $stream): array + { + $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); + + $types = []; + + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + $nameToken = $stream->expect(Token::NAME_TYPE); + $isOptional = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '?'); + + $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A type name must be followed by a colon (:)'); + + $valueToken = $stream->expect(Token::STRING_TYPE); + + $types[$nameToken->getValue()] = [ + 'type' => $valueToken->getValue(), + 'optional' => $isOptional, + ]; + } + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); + + return $types; + } + + public function getTag(): string + { + return 'types'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/UseTokenParser.php b/vendor/twig/twig/src/TokenParser/UseTokenParser.php new file mode 100644 index 0000000..1b96b40 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/UseTokenParser.php @@ -0,0 +1,73 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + + if (!$template instanceof ConstantExpression) { + throw new SyntaxError('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $targets = []; + if ($stream->nextIf('with')) { + while (true) { + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(Token::NAME_TYPE)->getValue(); + } + + $targets[$name] = new ConstantExpression($alias, -1); + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); + + return new Node([], [], $token->getLine()); + } + + public function getTag(): string + { + return 'use'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/WithTokenParser.php b/vendor/twig/twig/src/TokenParser/WithTokenParser.php new file mode 100644 index 0000000..8ce4f02 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/WithTokenParser.php @@ -0,0 +1,56 @@ + + * + * @internal + */ +final class WithTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $stream = $this->parser->getStream(); + + $variables = null; + $only = false; + if (!$stream->test(Token::BLOCK_END_TYPE)) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + $only = (bool) $stream->nextIf(Token::NAME_TYPE, 'only'); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse([$this, 'decideWithEnd'], true); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new WithNode($body, $variables, $only, $token->getLine()); + } + + public function decideWithEnd(Token $token): bool + { + return $token->test('endwith'); + } + + public function getTag(): string + { + return 'with'; + } +} diff --git a/vendor/twig/twig/src/TokenStream.php b/vendor/twig/twig/src/TokenStream.php new file mode 100644 index 0000000..c91701b --- /dev/null +++ b/vendor/twig/twig/src/TokenStream.php @@ -0,0 +1,129 @@ + + */ +final class TokenStream +{ + private $current = 0; + + public function __construct( + private array $tokens, + private ?Source $source = null, + ) { + $this->source = $source ?: new Source('', ''); + } + + public function __toString() + { + return implode("\n", $this->tokens); + } + + public function injectTokens(array $tokens) + { + $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current)); + } + + /** + * Sets the pointer to the next token and returns the old one. + */ + public function next(): Token + { + if (!isset($this->tokens[++$this->current])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current - 1]; + } + + /** + * Tests a token, sets the pointer to the next one and returns it or throws a syntax error. + * + * @return Token|null The next token if the condition is true, null otherwise + */ + public function nextIf($primary, $secondary = null) + { + return $this->tokens[$this->current]->test($primary, $secondary) ? $this->next() : null; + } + + /** + * Tests a token and returns it or throws a syntax error. + */ + public function expect($type, $value = null, ?string $message = null): Token + { + $token = $this->tokens[$this->current]; + if (!$token->test($type, $value)) { + $line = $token->getLine(); + throw new SyntaxError(\sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', + $message ? $message.'. ' : '', + Token::typeToEnglish($token->getType()), + $token->getValue() ? \sprintf(' of value "%s"', $token->getValue()) : '', + Token::typeToEnglish($type), $value ? \sprintf(' with value "%s"', $value) : ''), + $line, + $this->source + ); + } + $this->next(); + + return $token; + } + + /** + * Looks at the next token. + */ + public function look(int $number = 1): Token + { + if (!isset($this->tokens[$this->current + $number])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current + $number]; + } + + /** + * Tests the current token. + */ + public function test($primary, $secondary = null): bool + { + return $this->tokens[$this->current]->test($primary, $secondary); + } + + /** + * Checks if end of stream was reached. + */ + public function isEOF(): bool + { + return Token::EOF_TYPE === $this->tokens[$this->current]->getType(); + } + + public function getCurrent(): Token + { + return $this->tokens[$this->current]; + } + + /** + * Gets the source associated with this stream. + * + * @internal + */ + public function getSourceContext(): Source + { + return $this->source; + } +} diff --git a/vendor/twig/twig/src/TwigCallableInterface.php b/vendor/twig/twig/src/TwigCallableInterface.php new file mode 100644 index 0000000..2a8ff61 --- /dev/null +++ b/vendor/twig/twig/src/TwigCallableInterface.php @@ -0,0 +1,53 @@ + + */ +interface TwigCallableInterface extends \Stringable +{ + public function getName(): string; + + public function getType(): string; + + public function getDynamicName(): string; + + /** + * @return callable|array{class-string, string}|null + */ + public function getCallable(); + + public function getNodeClass(): string; + + public function needsCharset(): bool; + + public function needsEnvironment(): bool; + + public function needsContext(): bool; + + public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self; + + public function getArguments(): array; + + public function isVariadic(): bool; + + public function isDeprecated(): bool; + + public function getDeprecatingPackage(): string; + + public function getDeprecatedVersion(): string; + + public function getAlternative(): ?string; + + public function getMinimalNumberOfRequiredArguments(): int; +} diff --git a/vendor/twig/twig/src/TwigFilter.php b/vendor/twig/twig/src/TwigFilter.php new file mode 100644 index 0000000..70b1f8f --- /dev/null +++ b/vendor/twig/twig/src/TwigFilter.php @@ -0,0 +1,74 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#filters + */ +final class TwigFilter extends AbstractTwigCallable +{ + /** + * @param callable|array{class-string, string}|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + parent::__construct($name, $callable, $options); + + $this->options = array_merge([ + 'is_safe' => null, + 'is_safe_callback' => null, + 'pre_escape' => null, + 'preserves_safety' => null, + 'node_class' => FilterExpression::class, + ], $this->options); + } + + public function getType(): string + { + return 'filter'; + } + + public function getSafe(Node $filterArgs): ?array + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return $this->options['is_safe_callback']($filterArgs); + } + + return null; + } + + public function getPreservesSafety(): ?array + { + return $this->options['preserves_safety']; + } + + public function getPreEscape(): ?string + { + return $this->options['pre_escape']; + } + + public function getMinimalNumberOfRequiredArguments(): int + { + return parent::getMinimalNumberOfRequiredArguments() + 1; + } +} diff --git a/vendor/twig/twig/src/TwigFunction.php b/vendor/twig/twig/src/TwigFunction.php new file mode 100644 index 0000000..4a10df9 --- /dev/null +++ b/vendor/twig/twig/src/TwigFunction.php @@ -0,0 +1,63 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#functions + */ +final class TwigFunction extends AbstractTwigCallable +{ + /** + * @param callable|array{class-string, string}|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + parent::__construct($name, $callable, $options); + + $this->options = array_merge([ + 'is_safe' => null, + 'is_safe_callback' => null, + 'node_class' => FunctionExpression::class, + 'parser_callable' => null, + ], $this->options); + } + + public function getType(): string + { + return 'function'; + } + + public function getParserCallable(): ?callable + { + return $this->options['parser_callable']; + } + + public function getSafe(Node $functionArgs): ?array + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return $this->options['is_safe_callback']($functionArgs); + } + + return []; + } +} diff --git a/vendor/twig/twig/src/TwigTest.php b/vendor/twig/twig/src/TwigTest.php new file mode 100644 index 0000000..5e58ad8 --- /dev/null +++ b/vendor/twig/twig/src/TwigTest.php @@ -0,0 +1,67 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#test-operator + */ +final class TwigTest extends AbstractTwigCallable +{ + /** + * @param callable|array{class-string, string}|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + parent::__construct($name, $callable, $options); + + $this->options = array_merge([ + 'node_class' => TestExpression::class, + 'one_mandatory_argument' => false, + ], $this->options); + } + + public function getType(): string + { + return 'test'; + } + + public function needsCharset(): bool + { + return false; + } + + public function needsEnvironment(): bool + { + return false; + } + + public function needsContext(): bool + { + return false; + } + + public function hasOneMandatoryArgument(): bool + { + return (bool) $this->options['one_mandatory_argument']; + } + + public function getMinimalNumberOfRequiredArguments(): int + { + return parent::getMinimalNumberOfRequiredArguments() + 1; + } +} diff --git a/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php b/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php new file mode 100644 index 0000000..8811ca9 --- /dev/null +++ b/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php @@ -0,0 +1,204 @@ + + * + * @internal + */ +final class CallableArgumentsExtractor +{ + private ReflectionCallable $rc; + + public function __construct( + private Node $node, + private TwigCallableInterface $twigCallable, + ) { + $this->rc = new ReflectionCallable($twigCallable); + } + + /** + * @return array + */ + public function extractArguments(Node $arguments): array + { + $extractedArguments = []; + $named = false; + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + $named = true; + $name = $this->normalizeName($name); + } elseif ($named) { + throw new SyntaxError(\sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + $extractedArguments[$name] = $node; + } + + if (!$named && !$this->twigCallable->isVariadic()) { + $min = $this->twigCallable->getMinimalNumberOfRequiredArguments(); + if (\count($extractedArguments) < $this->rc->getReflector()->getNumberOfRequiredParameters() - $min) { + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $this->rc->getReflector()->getParameters()[$min + \count($extractedArguments)]->getName(), $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + return $extractedArguments; + } + + if (!$callable = $this->twigCallable->getCallable()) { + if ($named) { + throw new SyntaxError(\sprintf('Named arguments are not supported for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName())); + } + + throw new SyntaxError(\sprintf('Arbitrary positional arguments are not supported for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName())); + } + + [$callableParameters, $isPhpVariadic] = $this->getCallableParameters(); + $arguments = []; + $names = []; + $missingArguments = []; + $optionalArguments = []; + $pos = 0; + foreach ($callableParameters as $callableParameter) { + $name = $this->normalizeName($callableParameter->name); + if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { + if ('start' === $name) { + $name = 'low'; + } elseif ('end' === $name) { + $name = 'high'; + } + } + + $names[] = $name; + + if (\array_key_exists($name, $extractedArguments)) { + if (\array_key_exists($pos, $extractedArguments)) { + throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $name, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + if (\count($missingArguments)) { + throw new SyntaxError(\sprintf( + 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', + $name, $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) + ), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $extractedArguments[$name]; + unset($extractedArguments[$name]); + $optionalArguments = []; + } elseif (\array_key_exists($pos, $extractedArguments)) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $extractedArguments[$pos]; + unset($extractedArguments[$pos]); + $optionalArguments = []; + ++$pos; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), $this->node->getTemplateLine()); + } elseif ($callableParameter->isOptional()) { + if (!$extractedArguments) { + break; + } + + $missingArguments[] = $name; + } else { + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + } + + if ($this->twigCallable->isVariadic()) { + $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], $this->node->getTemplateLine()) : new ArrayExpression([], $this->node->getTemplateLine()); + foreach ($extractedArguments as $key => $value) { + if (\is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new ConstantExpression($key, $this->node->getTemplateLine())); + } + unset($extractedArguments[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; + } + } + + if ($extractedArguments) { + $unknownArgument = null; + foreach ($extractedArguments as $extractedArgument) { + if ($extractedArgument instanceof Node) { + $unknownArgument = $extractedArgument; + break; + } + } + + throw new SyntaxError( + \sprintf( + 'Unknown argument%s "%s" for %s "%s(%s)".', + \count($extractedArguments) > 1 ? 's' : '', implode('", "', array_keys($extractedArguments)), $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', $names) + ), + $unknownArgument ? $unknownArgument->getTemplateLine() : $this->node->getTemplateLine(), + $unknownArgument ? $unknownArgument->getSourceContext() : $this->node->getSourceContext() + ); + } + + return $arguments; + } + + private function normalizeName(string $name): string + { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); + } + + private function getCallableParameters(): array + { + $parameters = $this->rc->getReflector()->getParameters(); + if ($this->node->hasNode('node')) { + array_shift($parameters); + } + if ($this->twigCallable->needsCharset()) { + array_shift($parameters); + } + if ($this->twigCallable->needsEnvironment()) { + array_shift($parameters); + } + if ($this->twigCallable->needsContext()) { + array_shift($parameters); + } + foreach ($this->twigCallable->getArguments() as $argument) { + array_shift($parameters); + } + + $isPhpVariadic = false; + if ($this->twigCallable->isVariadic()) { + $argument = end($parameters); + $isArray = $argument && $argument->hasType() && $argument->getType() instanceof \ReflectionNamedType && 'array' === $argument->getType()->getName(); + if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { + array_pop($parameters); + } elseif ($argument && $argument->isVariadic()) { + array_pop($parameters); + $isPhpVariadic = true; + } else { + throw new SyntaxError(\sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $this->rc->getName(), $this->twigCallable->getType(), $this->twigCallable->getName())); + } + } + + return [$parameters, $isPhpVariadic]; + } +} diff --git a/vendor/twig/twig/src/Util/DeprecationCollector.php b/vendor/twig/twig/src/Util/DeprecationCollector.php new file mode 100644 index 0000000..0ea26ed --- /dev/null +++ b/vendor/twig/twig/src/Util/DeprecationCollector.php @@ -0,0 +1,77 @@ + + */ +final class DeprecationCollector +{ + public function __construct( + private Environment $twig, + ) { + } + + /** + * Returns deprecations for templates contained in a directory. + * + * @param string $dir A directory where templates are stored + * @param string $ext Limit the loaded templates by extension + * + * @return array An array of deprecations + */ + public function collectDir(string $dir, string $ext = '.twig'): array + { + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY + ), '{'.preg_quote($ext).'$}' + ); + + return $this->collect(new TemplateDirIterator($iterator)); + } + + /** + * Returns deprecations for passed templates. + * + * @param \Traversable $iterator An iterator of templates (where keys are template names and values the contents of the template) + * + * @return array An array of deprecations + */ + public function collect(\Traversable $iterator): array + { + $deprecations = []; + set_error_handler(function ($type, $msg) use (&$deprecations) { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + } + + return false; + }); + + foreach ($iterator as $name => $contents) { + try { + $this->twig->parse($this->twig->tokenize(new Source($contents, $name))); + } catch (SyntaxError $e) { + // ignore templates containing syntax errors + } + } + + restore_error_handler(); + + return $deprecations; + } +} diff --git a/vendor/twig/twig/src/Util/ReflectionCallable.php b/vendor/twig/twig/src/Util/ReflectionCallable.php new file mode 100644 index 0000000..16734d9 --- /dev/null +++ b/vendor/twig/twig/src/Util/ReflectionCallable.php @@ -0,0 +1,92 @@ + + * + * @internal + */ +final class ReflectionCallable +{ + private $reflector; + private $callable; + private $name; + + public function __construct( + TwigCallableInterface $twigCallable, + ) { + $callable = $twigCallable->getCallable(); + if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { + $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; + } + + if (\is_array($callable) && method_exists($callable[0], $callable[1])) { + $this->reflector = $r = new \ReflectionMethod($callable[0], $callable[1]); + $this->callable = $callable; + $this->name = $r->class.'::'.$r->name; + + return; + } + + $checkVisibility = $callable instanceof \Closure; + try { + $closure = \Closure::fromCallable($callable); + } catch (\TypeError $e) { + throw new \LogicException(\sprintf('Callback for %s "%s" is not callable in the current scope.', $twigCallable->getType(), $twigCallable->getName()), 0, $e); + } + $this->reflector = $r = new \ReflectionFunction($closure); + + if (str_contains($r->name, '{closure')) { + $this->callable = $callable; + $this->name = 'Closure'; + + return; + } + + if ($object = $r->getClosureThis()) { + $callable = [$object, $r->name]; + $this->name = get_debug_type($object).'::'.$r->name; + } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { + $callable = [$class->name, $r->name]; + $this->name = $class->name.'::'.$r->name; + } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { + $callable = [\is_array($callable) ? $callable[0] : $class->name, $r->name]; + $this->name = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; + } else { + $callable = $this->name = $r->name; + } + + if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { + $callable = $r->getClosure(); + } + + $this->callable = $callable; + } + + public function getReflector(): \ReflectionFunctionAbstract + { + return $this->reflector; + } + + public function getCallable() + { + return $this->callable; + } + + public function getName(): string + { + return $this->name; + } +} diff --git a/vendor/twig/twig/src/Util/TemplateDirIterator.php b/vendor/twig/twig/src/Util/TemplateDirIterator.php new file mode 100644 index 0000000..3bef14b --- /dev/null +++ b/vendor/twig/twig/src/Util/TemplateDirIterator.php @@ -0,0 +1,36 @@ + + */ +class TemplateDirIterator extends \IteratorIterator +{ + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function current() + { + return file_get_contents(parent::current()); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function key() + { + return (string) parent::key(); + } +}